sdk-10.11.0/000077500000000000000000000000001516266226600124475ustar00rootroot00000000000000sdk-10.11.0/.clang-format000066400000000000000000000154561516266226600150350ustar00rootroot00000000000000# clang-format 18 style configuration --- # Common settings ColumnLimit: 100 IndentWidth: 4 TabWidth: 4 LineEnding: LF UseTab: Never --- ## C++ specific settings Language: Cpp AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveBitFields: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveDeclarations: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveMacros: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: true AlignConsecutiveShortCaseStatements: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCaseColons: false AlignEscapedNewlines: DontAlign AlignOperands: Align AlignTrailingComments: Kind: Never OverEmptyLines: 0 AllowAllArgumentsOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowBreakBeforeNoexceptSpecifier: Never AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false AllowShortCompoundRequirementOnASingleLine: true AllowShortEnumsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: Empty AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes AttributeMacros: - __capability BinPackArguments: false BinPackParameters: false BitFieldColonSpacing: After BraceWrapping: AfterCaseLabel: true AfterClass: true AfterControlStatement: Always AfterEnum: true AfterExternBlock: true AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: true AfterUnion: true BeforeCatch: true BeforeElse: true BeforeLambdaBody: true BeforeWhile: true IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakAdjacentStringLiterals: true BreakAfterAttributes: Leave BreakAfterJavaFieldAnnotations: false BreakArrays: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Custom BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon BreakInheritanceList: AfterColon BreakStringLiterals: true CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: Always ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Regroup IncludeCategories: # Standard libraries - Regex: '<[a-z0-9_]+(\.h)?>' Priority: 3 SortPriority: 0 CaseSensitive: true # 3rd-party libraries - Regex: '<(.+/|Q.+|ccronexpr.+)' Priority: 2 SortPriority: 0 CaseSensitive: false # Everything else - Regex: '.*' Priority: 1 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseBlocks: false IndentCaseLabels: true IndentExternBlock: Indent IndentGotoLabels: false IndentPPDirectives: None IndentRequiresClause: false IndentWrappedFunctionNames: true InsertBraces: false InsertNewlineAtEOF: false InsertTrailingCommas: Wrapped IntegerLiteralSeparator: Binary: 0 BinaryMinDigits: 0 Decimal: 0 DecimalMinDigits: 0 Hex: 0 HexMinDigits: 0 JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false KeepEmptyLinesAtEOF: false LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PackConstructorInitializers: Never PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakOpenParenthesis: 0 PenaltyBreakScopeResolution: 500 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyIndentedWhitespace: 0 PenaltyReturnTypeOnItsOwnLine: 100 PointerAlignment: Left PPIndentWidth: -1 QualifierAlignment: Leave ReferenceAlignment: Left ReflowComments: true RemoveBracesLLVM: false RemoveParentheses: Leave RemoveSemicolon: false RequiresClausePosition: OwnLine RequiresExpressionIndentation: OuterScope SeparateDefinitionBlocks: Always ShortNamespaceLines: 1 SkipMacroDefinitionBody: false SortIncludes: CaseInsensitive SortJavaStaticImport: Before SortUsingDeclarations: LexicographicNumeric SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceAroundPointerQualifiers: Before SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: false SpaceBeforeInheritanceColon: false SpaceBeforeJsonColon: false SpaceBeforeParens: ControlStatementsExceptControlMacros SpaceBeforeParensOptions: AfterControlStatements: true AfterForeachMacros: false AfterFunctionDefinitionName: false AfterFunctionDeclarationName: false AfterIfMacros: false AfterOverloadedOperator: false AfterPlacementOperator: true AfterRequiresInClause: false AfterRequiresInExpression: false BeforeNonEmptyParentheses: false SpaceBeforeRangeBasedForLoopColon: false SpaceBeforeSquareBrackets: false SpaceInEmptyBlock: false SpacesBeforeTrailingComments: 1 SpacesInAngles: Never SpacesInContainerLiterals: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: 1 SpacesInParens: Never SpacesInParensOptions: InCStyleCasts: false InConditionalStatements: false InEmptyParentheses: false Other: false SpacesInSquareBrackets: false Standard: Auto StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - expander - QT_REQUIRE_VERSION VerilogBreakBetweenInstancePorts: true WhitespaceSensitiveMacros: - BOOST_PP_STRINGIZE - CF_SWIFT_NAME - NS_SWIFT_NAME - PP_STRINGIZE - STRINGIZE --- # Objective-C Language: ObjC DisableFormat: true ... sdk-10.11.0/.github/000077500000000000000000000000001516266226600140075ustar00rootroot00000000000000sdk-10.11.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001516266226600161725ustar00rootroot00000000000000sdk-10.11.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012151516266226600206630ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: sergiohs84 --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Environment:** - SDK Version: [e.g. v7.13.0] - OS: [e.g. Ubuntu 22.04.3 LTS] - Device: [e.g. iPhone10] **Additional context** Add any other context about the problem here. sdk-10.11.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011441516266226600217170ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: sergiohs84 --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. sdk-10.11.0/.github/ISSUE_TEMPLATE/general-queries.md000066400000000000000000000002631516266226600216050ustar00rootroot00000000000000--- name: General Queries about: Questions about usage, configurations, or issues that don’t clearly classify as bugs. title: '' labels: question assignees: sergiohs84 --- sdk-10.11.0/.gitignore000066400000000000000000000002341516266226600144360ustar00rootroot00000000000000# Editor and IDE stuff *.user *~ .vs *.vscode *.autosave # clangd index .cache # Building stuff build-*Debug build-*Release *.toml /CMakeUserPresets.json sdk-10.11.0/.gitlab-ci.yml000066400000000000000000000007341516266226600151070ustar00rootroot00000000000000--- variables: GIT_STRATEGY: clone GIT_DEPTH: "200" stages: - lint gitleaks: tags: - secret-scanning stage: lint image: name: mega-docker.artifactory.developers.mega.co.nz:8443/gitleaks:v8.24.0-mega entrypoint: [""] script: - git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME:$CI_MERGE_REQUEST_TARGET_BRANCH_NAME - gitleaks git --verbose --redact -c .gitleaks/gitleaks.toml --log-opts="$CI_MERGE_REQUEST_TARGET_BRANCH_NAME..$CI_COMMIT_SHA" sdk-10.11.0/.gitlab/000077500000000000000000000000001516266226600137675ustar00rootroot00000000000000sdk-10.11.0/.gitlab/CODEOWNERS000066400000000000000000000020521516266226600153610ustar00rootroot00000000000000#all sdk_devs can approve MRs that change anything in the repo... * @SDK/sdk_devs # ... except MRs relative to bindings, which needs approval by the corresponding App's team /bindings/ios/ @mobile/iOS /bindings/java/ @mobile/Android /bindings/megaapi.i @mobile/Android /bindings/qt/ @desktop /src/gfx/GfxProcCG.mm @mobile/iOS /src/osx/osxutils.mm @mobile/iOS @desktop /Package.swift @mobile/iOS /examples/Swift @mobile/iOS /examples/SwiftUI @mobile/iOS # Changes to Jenkinsfiles need approval by sdk_devops /jenkinsfile/ @SDK/sdk_devops # Changes to pipelines in GitLab CI, like gitleaks and alike, # need approval by one SDK team leader /.gitlab-ci.yml @SDK/sdk_tls # Changes to .clang-format need approval by tls-owning-clang-format # (it requires 2 approvals, 1 from MEGAsync team, 1 from SDK team) /.clang-format @SDK/tls-owning-clang-format [2] # Changes to monodic optional should keep compatibility with C++23 ifaces /include/mega/utils_optional.h @SDK/sdk_monodic_optional_owners /tests/unit/utils_optional_test.cpp @SDK/sdk_monodic_optional_owners sdk-10.11.0/.gitleaks/000077500000000000000000000000001516266226600143305ustar00rootroot00000000000000sdk-10.11.0/.gitleaks/gitleaks.toml000066400000000000000000000002701516266226600170270ustar00rootroot00000000000000[extend] # useDefault will extend the base configuration with the default gitleaks config: # https://github.com/zricethezav/gitleaks/blob/master/config/gitleaks.toml useDefault = true sdk-10.11.0/.gitleaksignore000066400000000000000000000041201516266226600154540ustar00rootroot000000000000009b76e8b71b8bda6c96a171e90de5a234c2dc1711:src/megaclient.cpp:generic-api-key:12998 550acd1bfb59c2d043dd6d206c391cbcf68b3abe:src/node.cpp:generic-api-key:246 ade68484b40d2bf8d5194c7b4a824348227d96da:tests/integration/SdkTest_test.cpp:generic-api-key:5835 ce7b5bae383680bfe8ebb6af6d3733235401b2db:src/megaapi_impl.cpp:private-key:8262 f88c248ed4d1d880ec2b5e18e9be0b3e2210231d:tests/crypto_test.cpp:generic-api-key:33 1a153e18996ffb0cf8d4bf037c3111d1e680057d:tests/tests.cpp:generic-api-key:59 8f2eaa46f4549ab358cd73d969b1d4f207acd3dd:tests/unit/Crypto_test.cpp:generic-api-key:363 8f2eaa46f4549ab358cd73d969b1d4f207acd3dd:tests/unit/Crypto_test.cpp:generic-api-key:389 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:1956 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:1960 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:1965 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:2084 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:2088 60dc99d344b2d2b9519aac46b47f898cf3419ee3:src/commands.cpp:generic-api-key:2092 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2277 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2289 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2319 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2331 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2340 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2352 bdf5a426a916c94edd9c8828bbc3a4a77adbe83e:src/commands.cpp:generic-api-key:2389 ccdcc27dc5c293a03ccd99cd7f707ee2c642b800:src/commands.cpp:generic-api-key:2004 ccdcc27dc5c293a03ccd99cd7f707ee2c642b800:src/commands.cpp:generic-api-key:2008 ccdcc27dc5c293a03ccd99cd7f707ee2c642b800:src/commands.cpp:generic-api-key:2020 ade68484b40d2bf8d5194c7b4a824348227d96da:tests/integration/SdkTest_test.cpp:generic-api-key:5833 ade68484b40d2bf8d5194c7b4a824348227d96da:tests/integration/SdkTest_test.cpp:generic-api-key:5834 sdk-10.11.0/CMakeLists.txt000066400000000000000000000146311516266226600152140ustar00rootroot00000000000000# CMakeLists.txt file to build the SDKlib library. # # It can be used to build a standalone library or to be included via add_subdirectory. # # To include the project in your application use the following: # add_subdirectory(path/to/sdk) # target_link_libraries( PRIVATE MEGA::SDKlib) # # To use it as a standalone library, once compiled and installed: # find_package(SDKlib REQUIRED) # target_link_libraries( PRIVATE MEGA::SDKlib) # If you prefer to use pkg-config, use the following instead: # pkg_check_modules(SDKlib REQUIRED IMPORTED_TARGET SDKlib) # target_link_libraries( PRIVATE PkgConfig::SDKlib) # cmake_minimum_required(VERSION 3.20) # Qt Creator configures VCPKG automatically. Disable it, we may want to use different tripplets, paths... set(QT_CREATOR_SKIP_VCPKG_SETUP TRUE CACHE BOOL "") # Main modules location list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) set(VCPKG_ROOT "" CACHE PATH "If set, it will build and use the VCPKG packages defined in the manifest file") ## Configurable options ## include(sdklib_options) # If PROJECT_NAME is not set before project() we are the main project. if(NOT PROJECT_NAME) message(STATUS "[SDKlib] is a top-level project. Install target is enabled by default.") set(SDKLIB_STANDALONE 1) if (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android") message(STATUS "Examples and tests will not be enabled by default for ${CMAKE_SYSTEM_NAME} builds") option(ENABLE_SDKLIB_EXAMPLES "Example application is built if enabled" OFF) option(ENABLE_SDKLIB_TESTS "Integration and unit tests are built if enabled" OFF) else() message(STATUS "Examples and tests will be enabled by default.") option(ENABLE_SDKLIB_EXAMPLES "Example application is built if enabled" ON) option(ENABLE_SDKLIB_TESTS "Integration and unit tests are built if enabled" ON) endif() option(ENABLE_SDKLIB_WERROR "Enable warnings as errors" ON) else() message(STATUS "[SDKlib] is building under project [${PROJECT_NAME}] Install target, examples and tests will not be enabled by default.") set(SDKLIB_STANDALONE 0) option(ENABLE_SDKLIB_EXAMPLES "Example application is built if enabled" OFF) option(ENABLE_SDKLIB_TESTS "Integration and unit tests are built if enabled" OFF) option(ENABLE_SDKLIB_WERROR "Enable warnings as errors." OFF) endif() ## General configuration include(sdklib_variables) if(NOT PROJECT_NAME) if(VCPKG_ROOT) # Include VCPKG management tools. include(vcpkg_management) process_vcpkg_libraries(${CMAKE_CURRENT_LIST_DIR}/cmake) else() # For packages with no pkg-config in the system. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules/packages) message(STATUS "Using system dependencies") endif() endif() # Get SDK library version to use it as the CMake project version. include(load_sdk_version) read_sdk_version(MEGA_SDK_VERSION ${CMAKE_CURRENT_LIST_DIR}/include/mega/version.h) project(SDKlib VERSION ${MEGA_SDK_VERSION} DESCRIPTION "MEGA SDK Library" ) # In-source build not allowed if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) message(FATAL_ERROR "In-source build is not allowed. Remove CMakeCache.txt and the CMakeFiles directory and set a new binary directory different than the source tree.") endif() message(STATUS "Generator: ${CMAKE_GENERATOR}") if(CMAKE_SYSTEM_VERSION) message(STATUS "Target OS version: ${CMAKE_SYSTEM_VERSION}") endif() if(CMAKE_GENERATOR_PLATFORM) message(STATUS "Platform: ${CMAKE_GENERATOR_PLATFORM}") endif() if(APPLE) if(CMAKE_OSX_ARCHITECTURES) message(STATUS "Architectures: ${CMAKE_OSX_ARCHITECTURES}") else() message(STATUS "Architectures: -Not set- (It defaults to the host system architecture)") endif() message(STATUS "Minimum deployment version: ${CMAKE_OSX_DEPLOYMENT_TARGET}") if(CMAKE_OSX_SYSROOT) message(STATUS "Platform SDK: ${CMAKE_OSX_SYSROOT}") endif() endif() message(STATUS "Target System Processor: ${CMAKE_SYSTEM_PROCESSOR}") message(STATUS "Host System Processor: ${CMAKE_HOST_SYSTEM_PROCESSOR}") get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTI_CONFIG) if(CMAKE_CONFIGURATION_TYPES) message(STATUS "Generated with config types: ${CMAKE_CONFIGURATION_TYPES}") else() message(FATAL_ERROR "You must specify CMAKE_CONFIGURATION_TYPES") endif() else() if(CMAKE_BUILD_TYPE) message(STATUS "Build type set to: ${CMAKE_BUILD_TYPE}") else() message(FATAL_ERROR "You must specify CMAKE_BUILD_TYPE. For example: -DCMAKE_BUILD_TYPE=Debug") endif() endif() if($ENV{CFLAGS}) message(STATUS "Environment CFLAGS: $ENV{CFLAGS}") endif() if($ENV{CXXFLAGS}) message(STATUS "Environment CXXFLAGS: $ENV{CXXFLAGS}") endif() if($ENV{LDFLAGS}) message(STATUS "Environment LDFLAGS: $ENV{LDFLAGS}") endif() if(USE_READLINE AND WIN32) message(FATAL_ERROR "Readline is not available in Windows builds. Disable USE_READLINE to continue.") endif() if(ENABLE_ISOLATED_GFX AND NOT USE_FREEIMAGE) message(FATAL_ERROR "USE_FREEIMAGE is required when ENABLE_ISOLATED_GFX is enabled.") endif() if(NOT ENABLE_MEDIA_FILE_METADATA AND USE_MEDIAINFO) message(FATAL_ERROR "Use ENABLE_MEDIA_FILE_METADATA option instead of USE_MEDIAINFO") endif() message(STATUS "Building SDKlib v${PROJECT_VERSION}") include(GNUInstallDirs) # Values for installation directories. All platforms include(CMakePackageConfigHelpers) # For the CMake package include(target_sources_conditional) # To add files to the project without building them include(target_platform_compile_options) # To add compile options depeding on the platform include(sdklib_libraries) # Includes a macro to load the dependencies, both the VCPKG or the system ones. # Load common and per platform configuration for the project include(configuration) ## Start loading targets include(sdklib_target) # Load Qt bindings if(ENABLE_QT_BINDINGS) add_subdirectory(bindings/qt) endif() # Load Java bindings if(ENABLE_JAVA_BINDINGS) add_subdirectory(bindings/java) endif() if(ENABLE_ISOLATED_GFX) add_subdirectory(tools/gfxworker) endif() ## Load examples and tests if(ENABLE_SDKLIB_EXAMPLES) add_subdirectory(examples) endif() if(ENABLE_SDKLIB_TESTS) add_subdirectory(tests) endif() ## Load FUSE support. add_subdirectory(src/fuse) add_subdirectory(third_party) sdk-10.11.0/CMakePresets.json000066400000000000000000000117561516266226600157020ustar00rootroot00000000000000{ "version": 2, "configurePresets": [ { "name": "default", "hidden": true, "binaryDir": "${sourceParentDir}/build-${sourceDirName}-${presetName}", "cacheVariables": { "VCPKG_ROOT": "${sourceParentDir}/vcpkg" } }, { "name": "unix", "hidden": true, "generator": "Unix Makefiles" }, { "name": "windows", "hidden": true, "generator": "Visual Studio 17 2022" }, { "name": "android", "hidden": true, "inherits": "unix", "cacheVariables": { "CMAKE_SYSTEM_NAME": "Android", "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake", "ENABLE_SDKLIB_ANDROID_DYNAMIC_LIBRARY": "ON" } }, { "name": "ios", "hidden": true, "inherits": "unix", "cacheVariables": { "CMAKE_SYSTEM_NAME": "iOS" } }, { "name": "dev", "hidden": true, "inherits": "default", "cacheVariables": { "ENABLE_ASAN": "OFF", "ENABLE_C_ARES_BACKEND": "OFF", "ENABLE_CHAT": "OFF", "ENABLE_DRIVE_NOTIFICATIONS": "OFF", "ENABLE_ISOLATED_GFX": "ON", "ENABLE_JAVA_BINDINGS": "OFF", "ENABLE_LOG_PERFORMANCE": "OFF", "ENABLE_MEDIA_FILE_METADATA" : "ON", "ENABLE_QT_BINDINGS": "OFF", "ENABLE_SDKLIB_EXAMPLES": "ON", "ENABLE_SDKLIB_TESTS": "ON", "ENABLE_SDKLIB_WERROR": "ON", "ENABLE_SYNC": "ON", "ENABLE_TSAN": "OFF", "ENABLE_UBSAN": "OFF", "USE_LIBUV": "ON" } }, { "name": "mega", "hidden": true, "inherits": "default", "cacheVariables": { "ENABLE_MEDIA_FILE_METADATA" : "ON" } }, { "name": "megasync", "hidden": true, "inherits": "default", "cacheVariables": { "ENABLE_ISOLATED_GFX": "ON", "ENABLE_LOG_PERFORMANCE": "ON", "ENABLE_MEDIA_FILE_METADATA" : "ON", "ENABLE_QT_BINDINGS": "ON", "USE_LIBUV": "ON" } }, { "name": "megacmd", "hidden": true, "inherits": "default", "cacheVariables": { "ENABLE_ISOLATED_GFX": "OFF", "ENABLE_MEDIA_FILE_METADATA" : "ON", "USE_LIBUV": "ON", "WITH_FUSE": "ON" } }, { "name": "megaproxy", "hidden": true, "inherits": "default", "cacheVariables": { "ENABLE_ISOLATED_GFX": "OFF", "ENABLE_LOG_PERFORMANCE": "ON", "ENABLE_MEDIA_FILE_METADATA" : "ON", "ENABLE_SYNC": "OFF", "USE_LIBUV": "OFF", "USE_READLINE": "OFF" } }, { "name": "megavpn", "hidden": true, "inherits": "default" }, { "name": "dev-unix", "inherits": [ "dev", "unix" ] }, { "name": "dev-windows", "inherits": [ "dev", "windows" ] }, { "name": "mega-android", "inherits": [ "mega", "android" ] }, { "name": "mega-ios", "inherits": [ "mega", "ios" ] }, { "name": "megasync-unix", "inherits": [ "megasync", "unix" ] }, { "name": "megasync-windows", "inherits": [ "megasync", "windows" ] }, { "name": "megacmd-unix", "inherits": [ "megacmd", "unix" ] }, { "name": "megacmd-windows", "inherits": [ "megacmd", "windows" ] }, { "name": "megaproxy-unix", "inherits": [ "megaproxy", "unix" ] }, { "name": "megavpn-android", "inherits": [ "megavpn", "android" ], "cacheVariables": { "ENABLE_CHAT": "OFF", "ENABLE_SYNC": "OFF", "USE_LIBUV": "OFF" } } ] } sdk-10.11.0/CREDITS.md000066400000000000000000000121621516266226600140700ustar00rootroot00000000000000## CREDITS -------------------------------------------------------------------- #### Dependencies of the MEGA C++ SDK Here is a brief description of all of them: #### libcurl Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. The multiprotocol file transfer library https://curl.haxx.se/libcurl/ License: MIT/X derivate license https://curl.haxx.se/docs/copyright.html #### Crypto++ Copyright (c) 1995-2013 by Wei Dai. (for the compilation) and public domain (for individual files) Crypto++ Library is a free C++ class library of cryptographic schemes. https://www.cryptopp.com/ License: Crypto++ Library is copyrighted as a compilation and (as of version 5.6.2) licensed under the Boost Software License 1.0, while the individual files in the compilation are all public domain. #### OpenSSL Copyright (c) 1998-2016 The OpenSSL Project. All rights reserved. A toolkit implementing SSL v2/v3 and TLS protocols with full-strength cryptography world-wide. https://www.openssl.org/ License: OpenSSL License https://github.com/openssl/openssl/blob/master/LICENSE #### libuv Copyright Joyent, Inc. and other Node contributors. All rights reserved. libuv is a multi-platform support library with a focus on asynchronous I/O. https://github.com/libuv/libuv License: MIT https://github.com/libuv/libuv/blob/v1.x/LICENSE #### freeimage Copyright (c) 2003-2015 by FreeImage. All rights reserved. FreeImage is an Open Source library project for developers who would like to support popular graphics image formats like PNG, BMP, JPEG, TIFF and others as needed by today's multimedia applications. This software uses the FreeImage open source image library. See http://freeimage.sourceforge.net for details. License: FreeImage Public License - Version 1.0. http://freeimage.sourceforge.net/freeimage-license.txt #### SQLite SQLite is an in-process library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine. http://www.sqlite.org/ License: Public Domain http://www.sqlite.org/copyright.html ### Libraries included in this repo #### utf8proc A clean C library for processing UTF-8 Unicode data https://julialang.org/utf8proc/ License: MIT "expat" license https://github.com/JuliaLang/utf8proc/blob/master/LICENSE.md Files included in this repository based on `utf8proc`: - `third_party/utf8proc/utf8proc.cpp` (based on `utf8proc.c`) - `third_party/utf8proc/utf8proc_data.c` (based on `utf8proc_data.c`) - `third_party/utf8proc/utf8proc.h` (based on `utf8proc.h`) - `third_party/utf8proc/LICENSE` (copy of the licence of `utf8proc`) #### Cron expression parsing in ANSI C A clean C library for processing cron expresions and obtaining epoch times https://github.com/staticlibs/ccronexpr License: Apache License 2.0 Files included in this repository based on `ccronexpr`: - `third_party/ccronexpr/ccronexpr.cpp` (based on `ccronexpr.c`) - `third_party/ccronexpr/ccronexpr.h` (based on `ccronexpr.h`) - `third_party/ccronexpr/LICENSE` (copy of the licence of `ccronexpr`) #### http_parser HTTP request/response parser for C https://github.com/nodejs/http-parser License: MIT https://github.com/nodejs/http-parser/blob/master/LICENSE-MIT Files included in this repository based on `http_parser`: - `third_party/http_parser/http_parser.cpp` (based on `http_parser.c`) - `third_party/http_parser/http_parser.h` (based on `http_parser.h`) - `third_party/http_parser/AUTHORS` (copy of the `AUTHORS` file of `http_parser`) - `third_party/http_parser/LICENSE-MIT` (copy of the licence of `http_parser`) #### zxcvbn-c C/C++ version of the zxcvbn password strength estimator https://github.com/tsyrogit/zxcvbn-c License: MIT https://github.com/tsyrogit/zxcvbn-c/blob/master/LICENSE.txt Files included in this repository based on `zxcvbn-c`: - `third_party/zxcvbn-c/zxcvbn.cpp` (based on `zxcvbn.c`) - `third_party/zxcvbn-c/zxcvbn.h` (based on `zxcvbn.h`) - `third_party/zxcvbn-c/dict-src.h` (dictionary file generated with the same wordlist as our webclient) - `third_party/zxcvbn-c/README.md` (copy of the `README.MD` file of `zxcvbn-c`) - `third_party/zxcvbn-c/LICENSE.txt` (copy of the licence of `zxcvbn-c`) #### evt-tls evt-tls is an abstraction layer of OpenSSL using bio pair to expose callback based asynchronous API and should integrate easily with any event based networking library like libuv, libevent and libev or any other network library which want to use OpenSSL as an state machine License: MIT Copyright (c) 2015 Devchandra M. Leishangthem https://github.com/deleisha/evt-tls Files included in this repository based on `evt-tls`: - `third_party/evt-tls/evt_tls.cpp` (based on `evt_tls.c`) - `third_party/evt-tls/evt_tls.h` (based on `evt_tls.h`) - `third_party/evt-tls/queue.h` (based on `queue.h`) #### vincentlaucsb/csv-parser A high-performance, fully-featured CSV parser and serializer for modern C++. https://github.com/vincentlaucsb/csv-parser License: MIT https://github.com/vincentlaucsb/csv-parser/blob/master/LICENSE Files included in this repository based on `vincentlaucsb/csv-parser`: - `third_party/csv/csv.h` (based on `csv.hpp`) - `third_party/csv/LICENSE` (copy of the licence of `csv`) sdk-10.11.0/LICENSE000066400000000000000000000024311516266226600134540ustar00rootroot00000000000000Copyright (c) 2013, Mega Limited All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. sdk-10.11.0/Package.swift000066400000000000000000000112441516266226600150620ustar00rootroot00000000000000// swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MEGASDK", platforms: [ .iOS(.v15), .macOS(.v10_15) ], products: [ .library( name: "MEGASdkCpp", targets: ["MEGASdkCpp"]), .library( name: "MEGASdk", targets: ["MEGASdk"]) ], dependencies: [ ], targets: [ .target( name: "MEGASdkCpp", dependencies: ["libcryptopp", "libmediainfo", "libuv", "libcurl", "libsodium", "libzen"], path: "./", exclude: [ "bindings", "cmake", "contrib", "examples", "src/android", "src/common/client_adapter_with_sync.cpp", "src/common/platform/windows", "src/file_service/documentation", "src/fuse/supported", "third_party/utf8proc/utf8proc_data.c", "src/win32", "tests", "tools" ], cxxSettings: [ .headerSearchPath("bindings/ios"), .headerSearchPath("include/mega/osx"), .headerSearchPath("include/mega/posix"), .headerSearchPath("src/common/platform/posix"), .headerSearchPath("src/file_service"), .headerSearchPath("src/fuse/unsupported"), .headerSearchPath("third_party"), .define("ENABLE_CHAT"), .define("HAVE_LIBUV"), .define("NDEBUG", .when(configuration: .release)) ], linkerSettings: [ // Frameworks .linkedFramework("QuickLookThumbnailing"), .linkedFramework("CoreFoundation"), .linkedFramework("AVFoundation"), .linkedFramework("CoreImage"), .linkedFramework("CoreGraphics"), .linkedFramework("Foundation"), .linkedFramework("ImageIO"), .linkedFramework("Security"), .linkedFramework("UIKit", .when(platforms: [.iOS, .macCatalyst])), .linkedFramework("UniformTypeIdentifiers"), .linkedFramework("SystemConfiguration", .when(platforms: [.macOS])), // Libraries .linkedLibrary("z"), .linkedLibrary("sqlite3"), .linkedLibrary("icucore") ] ), .target( name: "MEGASdk", dependencies: ["MEGASdkCpp"], path: "bindings/ios", cxxSettings: [ .headerSearchPath("../../include"), .define("ENABLE_CHAT"), .define("HAVE_LIBUV"), .headerSearchPath("Private") ] ), .binaryTarget( name: "libcryptopp", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libcryptopp.xcframework.zip", checksum: "f7483596a4a682fbdf38a2a0c919c6407bdbd8c4f3cef1877c105820ae9f9896" ), .binaryTarget( name: "libcurl", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libcurl.xcframework.zip", checksum: "ab3c685d9c20bf22a8f63105bbe3410bf06edf10d3f164a59a81c5bb0a0e4dd3" ), .binaryTarget( name: "libsodium", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libsodium.xcframework.zip", checksum: "edf385ce2b693f864a5879559c9e61c84d4209e62e3e6e37bcd01cd23c0c311c" ), .binaryTarget( name: "libuv", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libuv.xcframework.zip", checksum: "97e387c71773766d0673634a3688550226b0ca5ea0ce0fb0c4a66a7e99ddb6a7" ), .binaryTarget( name: "libmediainfo", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libmediainfo.xcframework.zip", checksum: "d6fa1c5feb6282057a9b4313e77dec9a4d40d5b4a49c62a6e209fb46951a351c" ), .binaryTarget( name: "libzen", url: "https://s3.g.s4.mega.io/dmlaaezwz52y37atz56mfvmrvltfagrltbgpr/xcframeworks-macos-support/libzen.xcframework.zip", checksum: "520bd9579d6174c7e4b2eb989b48429961e6bb10e057119db17f8967dfe9b5a2" ) ], cxxLanguageStandard: .cxx17 ) sdk-10.11.0/README.md000066400000000000000000000214321516266226600137300ustar00rootroot00000000000000# MEGA SDK - Client Access Engine MEGA --- _The Privacy Company_ --- is a Secure Cloud Storage provider that protects your data thanks to end-to-end encryption. We call it User Controlled Encryption, or UCE, and all our clients automatically manage it. All files stored on MEGA are encrypted. All data transfers from and to MEGA are encrypted. And while most cloud storage providers can and do claim the same, MEGA is different – unlike the industry norm where the cloud storage provider holds the decryption key, with MEGA, you control the encryption, you hold the keys, and you decide who you grant or deny access to your files. This SDK brings you all the power of our client applications and lets you create your own or analyze the security of our products. Are you ready to start? Please continue reading. ## SDK Contents In this SDK, you can find our low level SDK, that was already released few months after the MEGA launch, a new intermediate layer to make it easier to use and to bind with other programming languages, and example apps for all our currently supported platforms (Android, GNU/Linux, iOS, macOS and Windows). In the [examples](examples) folder you can find example apps using: 1. The low level SDK: - megacli (a powerful command line tool that allows to use all SDK features) 2. The public API: - A plain C++ example app in `examples/simple_client` - An example app for Android (using Java bindings based on SWIG) in `examples/android` - An example app for iOS (using Objective-C bindings) in `examples/iOS` [MEGAcmd](https://github.com/meganz/megacmd), a higher level command line application that uses the SDK to provide interactive and scriptable access to MEGA. You can use it by running megacmd-server and talk to it from PHP/Python code, for instance. ## How to build the SDK library For the SDK development and compilation we use CMake as the cross-platform project configuration tool. We also use VCPKG to manage the required dependencies to build the SDK in most platforms: GNU/Linux, macOS and Windows. ### Building tools Some common development tools should be available in the system to be able to build the MEGA SDK and the needed dependencies: - Git: Use the one from your system package manager or install it from https://git-scm.com - CMake 3.19 or higher: Use the one from your system package manager or install it from https://cmake.org #### Windows Ensure you have installed Visual Studio, with the necessary components for building C++ sources, and the Windows SDK on your system: - [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) - MSVC v142 - Windows 10 SDK (10.0.22621.0) #### MacOS Xcode and the Developer tools are needed. To install the Developer tools, run the following command and follow the instructions: $ xcode-select --install The following packages should be available in the system as well: - autoconf, autoconf-archive, automake, pkg-config, nasm and libtool. You can use any package manager if you have one installed or build and install them from sources #### Linux For debian-based distributions, you can install the needed compilers and tools using the following command: sudo apt install build-essential curl zip unzip autoconf autoconf-archive nasm libtool-bin Package names may vary for other Linux distros, but it should build successfully with similar packages to the ones listed above. ### Prepare the sources First of all, prepare a directory of your choice to work with the MEGA SDK. The `mega` directory will be used as the workspace directory in the examples in this document. mkdir mega cd mega Then, clone the MEGA SDK repository to obtain the source code for the MEGA SDK. git clone https://github.com/meganz/sdk Next to the MEGA SDK, clone the VCPKG repository. If you are already using VCPKG and have a local clone of the VCPKG repository, you can skip this step and use the VCPKG you already have in your system. git clone https://github.com/microsoft/vcpkg **Note**: VCPKG local repository needs to be updated from time to time. If never done, it will eventually fail to find new dependencies or others updated to versions newer than what it already had. The solution is simple: go to VCPKG local repository and run `git pull`. ### Configuration The following instructions are for configuring the project from the CLI, but cmake-gui or any editor or IDE compatible with CMake should be suitable if the same CMake parameters are configured. The SDK is configured like any other regular CMake project. The only parameter that is always needed is the VCPKG directory to manage the third-party dependencies. To configure the SDK with the default options, from the workspace (`mega` directory), run CMake: cmake -DVCPKG_ROOT=vcpkg -DCMAKE_BUILD_TYPE=Debug -S sdk -B build_dir **Note**: The `-DCMAKE_BUILD_TYPE=` may not be needed for multiconfig generators, like Visual Studio. In the command above, relative paths have been used for simplicity. If you want to change the location of VCPKG, the SDK or the build directory, simply provide a valid relative or absolute path for any of them. During the configuration of the project, VCPKG will build and configure the necessary libraries for the platform. It may take a while on the first run, but once the libraries are built, VCPKG will retrieve them from the binary cache. Some options to configure the SDK library can be found in the [sdklib_options.cmake](cmake/modules/sdklib_options.cmake) file, like ENABLE_SYNC or USE_PDFIUM. The options to manage the examples and tests are in the [CMakeLists.txt](CMakeLists.txt). ### Building the sources Once the MEGA SDK is configured, simply build the complete project: cmake --build build_dir You can specify `--target=` like `SDKlib` or `megacli`, or just leave the command as it is to build all the tagets. Additionally, `-j` can be added to manage concurrency and speed up the build. Once the build is finished, binaries will be available in the `build_dir` ### Run megacli To run the example app `megacli`, go to the `examples/megacli` directory in the `build_dir` and execute the `megacli` binary. ## Minimum supported OS versions ### Android - Android 9.0 ### DMS - DMS 7.2 ### GNU/Linux - Arch - Debian 11 - Fedora 40 - OpenSUSE Leap 15.6 - Raspberry Pi OS Lite (Debian 12) - Ubuntu 20.04 LTS ### iOS - iOS 15 ### macOS - macOS 10.15 (Intel) - macOS 11.1 (Apple silicon) ### Windows - Windows 10 - Windows Server 2019 ## Usage The low level SDK doesn't have inline documentation yet. If you want to use it, please check our example app `examples/megacli`. The intermediate layer has been documented using Doxygen. The only public header that you need to include to use is `include/megaapi.h`. You can read the documentation in that header file. ## Additional info ### Folder syncing In this version, the sync functionality is limited in scope and functionality: * There is no locking between clients accessing the same remote folder. Concurrent creation of identically named files and folders can result in server-side dupes. * Syncing between clients with differing filesystem naming semantics can lead to loss of data, e.g. when syncing a folder containing `ABC.TXT` and `abc.txt` with a Windows client. * On POSIX platforms, filenames are assumed to be encoded in UTF-8. Invalid byte sequences can lead to undefined behaviour. * Local filesystem items must not be exposed to the sync subsystem more than once. Any dupes, whether by nesting syncs or through filesystem links, will lead to unexpected results and loss of data. * No in-place versioning. Deleted remote files can be found in `//bin/SyncDebris` (only when syncing to the logged in account's own cloud drive - there is no SyncDebris facility on syncs to inbound shares), deleted local files in a sync-specific hidden debris folder located in the local sync's root folder. * No delta writes. Changed files are always overwritten as a whole, which means that it is not a good idea to sync e.g. live database tables. * No direct peer-to-peer syncing. Even two machines in the same local subnet will still sync via the remote storage infrastructure. * No support for unidirectional syncing (backup-only, restore-only). Syncing to an inbound share requires it to have full access rights. ### `megacli` on Windows The `megacli` example is currently not handling console Unicode input/output correctly if run in `cmd.exe`. Filename caveats: Please prefix all paths with `\\?\` to avoid the following issues: * The `MAX_PATH` (260 character) length limitation, which would make it impossible to access files in deep directory structures * Prohibited filenames (`con`/`prn`/`aux`/`clock$`/`nul`/`com1`...`com9`/`lpt1`...`lpt9`). Such files and folders will still be inaccessible through e.g. Explorer! Also, disable automatic short name generation to eliminate the risk of clashes with existing short names. sdk-10.11.0/automation/000077500000000000000000000000001516266226600146275ustar00rootroot00000000000000sdk-10.11.0/automation/README.md000066400000000000000000000165751516266226600161240ustar00rootroot00000000000000[[_TOC_]] # MEGA SDK - Release management The following processes have been automated: ## Make a new Release Fill the details in `[make_release]` section of your `config.toml` local copy of [config.toml.template](config.toml.template). You will also need to set the following environment variables to make the script work: - `GITLAB_TOKEN` - `JIRA_TOKEN` - `SLACK_TOKEN` - `GPG_KEYGRIP` - `GPG_PASSWORD` > Note that the version for a new release will be automatically determined unless one was explicitly passed. To explicitly pass one, fill `release_version` argument in this section. > Note that version-file will be updated in the process, where applicable ([version.h](../include/mega/version.h) in SDK). For projects that don't have such a file, `gpg_keygrip` and `gpg_password` will be ignored and can be left empty. From a directory in the repo (!) for which we intend to make a release, run: ```sh python3 path/to/make_release.py path/to/config.toml ``` ## Close a new Release Fill the details in `[close_release]` section of your `config.toml` local copy of [config.toml.template](config.toml.template). You will also need to set the following environment variables to make the script work: - `GITLAB_TOKEN` - `JIRA_TOKEN` - `SLACK_TOKEN` - `GITHUB_TOKEN` - `CONFLUENCE_TOKEN` From a directory in the repo (!) for which we intend to close a release, run: ```sh python3 path/to/close_release.py path/to/config.toml ``` ## Patch a Release > Note that this process can become very complex when multiple releases need to be patched. Because of that, the automation is done for step 7 and further. Fill the details in `[patch_release]` section of your `config.toml` local copy of [config.toml.template](config.toml.template). You will also need to set the following environment variables to make the script work: - `GITLAB_TOKEN` - `JIRA_TOKEN` - `SLACK_TOKEN` - `GPG_KEYGRIP` - `GPG_PASSWORD` From a directory in the repo (!) for which we intend to patch a release, run: ```sh python3 ./patch_release.py path/to/config.toml ``` ## Make another RC Fill the details in `[make_another_rc]` section of your `config.toml` local copy of [config.toml.template](config.toml.template). You will also need to set the following environment variables to make the script work: - `GITLAB_TOKEN` - `JIRA_TOKEN` - `SLACK_TOKEN` > Note that the number of the new RC will be automatically determined, from the last RC already existing for that Release plus 1. From a directory in the repo (!) for which we intend to make a release, run: ```sh python3 path/to/make_another_rc.py path/to/config.toml ``` ## Prerequisites These should only be needed once. ### Python stuff * Install `Python 3`. The scripts were written using `Python 3.12.2`, just in case an older version would fail to run them. * Install `pip`. Something like `python3 -m ensurepip --upgrade` should work. However, Ubuntu apparently is "special" and Python from its repo comes without `ensurepip`. So try `sudo apt install python3-pip`. * Install required modules with `pip install -r requirements.txt` (and upgrade all later with `pip install -U -r requirements.txt`). * In case you encounter the `externally-managed-environment` error, create a virtual environment with `python3 -m venv .venv`, then run `./.venv/bin/python -m pip install -r requirements.txt` to install the required libraries locally. ### GitLab stuff * [Create a personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token) with scopes `api`, `read_api`, `read_user`, `create_runner`, `read_repository`, `write_repository`. * The token created there must be set in enviroment variable `GITLAB_TOKEN`. * Remember to check from time to time that the token has not expired. ### GitHub stuff * [Create a personal access token](https://github.com/settings/tokens/new) with scope `repo` and all its sub-scopes. * fill in `Note` and `Expiration` with what feels appropriate * The token created there must be set in enviroment variable `GITHUB_TOKEN`. * Remember to check from time to time that the token has not expired. ### Slack stuff ##### Setup app * Slack requires an _app_ that will provide a token required for using its API. * Any Slack user can create an app, configure the scope for it, and get the necessary token: * [Go to Your Apps](https://api.slack.com/apps) * click **Create New App** -> **From scratch** * set a name for the new app and set **MEGA** workspace for it * click **Create App**. * Go to **OAuth & Permissions** (on the left side, under _Features_) * Find **Scopes** section -> **User Token Scopes** -> click **Add an OAuth Scope** -> choose `chat:write` * Find **OAuth Tokens for Your Workspace** section -> click **Install to Workspace** -> review the permissions listed there -> click **Allow** * From the same **OAuth Tokens for Your Workspace** section -> copy **User OAuth Token** * Or reuse a _distributed app_ created by someone else, and get whatever token they provide. * The token created there must be set in enviroment variable `SLACK_TOKEN`. * Update the env var when the token has expired. ##### Find channel and thread to post to * Channel can be passed by name, as in `#foo_bar` without the `#` prefix. Another option is to pass it by id, which can be obtained from the web url. The web url of a channel (or direct message) looked like `https://app.slack.com/client/AAAAAAAAA/BBBBBBBBBBB`. The `BBBBBBBBBBB` component is the channel id. * Thread id can be obtained from the web url of the message in a channel, considered the root of a thread. The web url of such a message looked like `https://megaconz.slack.com/archives/BBBBBBBBBBB/pTTTTTTTTTTTTTTTT`. The `TTTTTTTTTTTTTTTT` component needs to be split by a `.` into a 10.6 string. The result is the thread id, as `TTTTTTTTTT.TTTTTT`. ### Confluence stuff * This is optional. If Confluence details are not provided, the rotation of Release Captain will not be executed. * Obtain `wiki-page-id`: open "Release management" page in Confluence, then `...` -> `Page information` -> from the url copy the value after `pageId=`. * Set the id in `confluence_page_id` argument(s) inside `config.toml`. ### gpg stuff * (Installing git, creating gpg key and other stuff required by any commit are not covered here) * Find the KEYGRIP ```sh gpg -k --with-keygrip ``` The output will be something like ```sh /home/USER/.gnupg/pubring.kbx -------------------------------- pub rsa3072 2021-01-04 [SC] AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Keygrip = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB uid [ultimate] My Name (Ubuntu, xps) sub rsa3072 2021-01-04 [E] Keygrip = CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC ``` Value `BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB` is the one needed by `gpg_keygrip` argument(s) inside `config.toml`. #### Known gpg issues Setting up gpg does not work automatically on Windows. In order to run this process on a Windows machine, either be prepared to introduce the passkey when requested by a commit, or run the following setup steps manually in `Git Bash` (!), before starting the release process: ```sh # Caching passphrase must be allowed: echo allow-preset-passphrase >> $(gpgconf --list-dirs homedir)/gpg-agent.conf # Cache passphrase for key (from 'nix shell, git-bash on Win etc.): $(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase -c BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB < bool: return LocalRepository.version_file.is_file() def add_remote( self, remote_name: str, remote_url: str, fetch_is_optional: bool ) -> bool: # add remote or confirm being correctly configured already byte_output = subprocess.check_output( ["git", "remote", "-v"], stderr=subprocess.STDOUT ) remotes = byte_output.decode("utf-8").splitlines() assert isinstance(remotes, list), f"Error:\n git remote -v\n {remotes}" escaped_url = re.escape(remote_url) remote_push = remote_fetch = False for r in remotes: if re.match(rf"{remote_name}\s+{escaped_url}\s+\(push\)", r): remote_push = True elif re.match(rf"{remote_name}\s+{escaped_url}\s+\(fetch\)", r): remote_fetch = True if remote_push and (remote_fetch or fetch_is_optional): return True if not remote_push and not remote_fetch: assert subprocess.run( ["git", "remote", "add", remote_name, remote_url], stdout=subprocess.DEVNULL, ), f"Failed to add remote {remote_name} {remote_url}" print(" Added remote\n ", remote_name, "\t", remote_url, flush=True) return True assert ( remote_push ), f"{remote_name} {remote_url} (push): NOT FOUND\nfound:\n{remotes}" assert ( remote_fetch ), f"{remote_name} {remote_url} (fetch): NOT FOUND\nfound:\n{remotes}" return False def check_for_uncommitted_changes(self): byte_output = subprocess.check_output( ["git", "diff", "--shortstat", "--", ":(exclude,top)version_release"] ) assert ( len(byte_output) == 0 ), f'Found unstaged changes:\n{byte_output.decode("utf-8")}' byte_output = subprocess.check_output( [ "git", "diff", "--shortstat", "--cached", "--", ":(exclude,top)version_release", ] ) assert ( len(byte_output) == 0 ), f'Found staged changes:\n{byte_output.decode("utf-8")}' def _get_current_branch(self) -> str: byte_output = subprocess.check_output(["git", "branch", "--show-current"]) branch_name = byte_output.decode("utf-8").strip() return branch_name def switch_to_branch(self, remote: str, target_branch: str): assert subprocess.run( ["git", "fetch", remote], stdout=subprocess.DEVNULL ), f"Failed to fetch {remote}" cb = self._get_current_branch() if cb != target_branch: print(f"Switching to branch {target_branch} from {cb}...", flush=True) subprocess.run(["git", "switch", target_branch]) assert ( self._get_current_branch() == target_branch ), f"Failed to switch to branch {target_branch}" def sync_current_branch(self, remote: str): my_branch = self._get_current_branch() byte_output = subprocess.check_output( [ "git", "rev-list", "--left-right", "--count", f"{remote}/{my_branch}...{my_branch}", ] ) behind_or_ahead = byte_output.decode("utf-8").split() assert int(behind_or_ahead[1]) == 0, ( # true here means ahead # local branch is ahead, has local-only changes # let a human take action f"{my_branch} is ahead by {behind_or_ahead[1]} commits" ) if int(behind_or_ahead[0]): # true here means behind # local branch is behind; pull latest changes assert subprocess.run( ["git", "pull", remote, my_branch], stdout=subprocess.DEVNULL ), f"Failed to pull from {remote}/{my_branch}" def commit_changes_to_new_branch(self, commit_message: str, branch_name: str): # branch should not exist assert subprocess.run( ["git", "show-ref", "--quiet", f"refs/heads/{branch_name}"], stdout=subprocess.DEVNULL, ), f'Branch "{branch_name}" already existed. Delete it and try again' # create branch assert subprocess.run( ["git", "checkout", "-b", branch_name], stdout=subprocess.DEVNULL ), f'Failed to create new branch "{branch_name}"' self.commit_changes(commit_message) def commit_changes(self, commit_message: str): # stage changes to version file assert subprocess.run( ["git", "add", LocalRepository.version_file.as_posix()], stdout=subprocess.DEVNULL, ), "Failed to stage changes" # commit and sign assert subprocess.run( ["git", "commit", "-S", "-m", commit_message], stdout=subprocess.DEVNULL ), ( # TODO: this can also fail if GPG signing failed "Failed to commit changes" ) def push_branch(self, remote: str, branch: str): assert subprocess.run( ["git", "push", remote, branch], stdout=subprocess.DEVNULL ), f"Failed to push branch {branch} to {remote}" def clean_version_changes(self, new_branch: str, fallback_branch: str): subprocess.run(["git", "reset"], stdout=subprocess.DEVNULL) subprocess.run( ["git", "checkout", "--", self.version_file.as_posix()], stdout=subprocess.DEVNULL, ) subprocess.run(["git", "checkout", fallback_branch], stdout=subprocess.DEVNULL) subprocess.run(["git", "branch", "-D", new_branch], stdout=subprocess.DEVNULL) sdk-10.11.0/automation/lib/private_repository.py000066400000000000000000000174551516266226600217340ustar00rootroot00000000000000import re from gitlab import Gitlab # python-gitlab from gitlab.v4.objects import ( CurrentUser, Project, ProjectLabel, ProjectMergeRequest, ProjectTag, ) import time from typing import cast class GitLabRepository: # use gitlab API """ Functionality: - open MR - merge MR - close MR - create branch - delete branch - create tag - delete tag """ def __init__(self, url: str, gitlab_token: str, project_name: str): gl = Gitlab(url, gitlab_token) # test the authentication with private token # (will throw if url was wrong or token bad/expired) gl.auth() # find project by name possible_projects = gl.projects.list( search=project_name, simple=True, iterator=True ) # There can be multiple projects with the same name, at least in different namespaces. # The one we care about apparently is in a namespace of type "group". # So far filtering by namespace type was enough, otherwise we might need to provide # the exact namespace or directly the project id. valid_projects = [ p for p in possible_projects if p.name == project_name and p.namespace["kind"] == "group" ] if len(valid_projects) != 1: raise NameError( f"{len(valid_projects)} projects found with name {project_name}" ) self._project = cast(Project, valid_projects[0]) # create Release label if missing label_name = "Release" if label_name not in [l.name for l in self._project.labels.list(iterator=True)]: print( f"WARNING: Label {label_name} did not exist. Attempting to create it..." ) l = self._project.labels.create({"name": label_name, "color": "#8899aa"}) assert isinstance(l, ProjectLabel) def get_url_to_private_repo(self) -> str: return self._project.ssh_url_to_repo def _get_open_mr( self, mr_title: str, mr_source: str, mr_target: str ) -> tuple[int, str]: mrs = self._project.mergerequests.list( state="opened", source_branch=mr_source, target_branch=mr_target, iterator=True, ) for mr in mrs: if mr.title == mr_title: return mr.iid, mr.web_url return 0, "" def _get_default_mr_description(self) -> str: # Default MR description configured for a GitLab project is apparently # not accessible, and most likely not appropriate for a Release. Use # only the minimum, potentially required by pipelines. description = ( "ANDROID_BRANCH_TO_TEST=develop\n\n" "IOS_BRANCH_TO_TEST=develop\n\n" # leave empty to receive default value (can be removed after CID-491): "USE_APIURL_TO_TEST=" ) specific_description = { "SDK": "MEGACHAT_BRANCH_TO_TEST=develop", "MEGAchat": "SDK_BRANCH_TO_TEST=develop", } if self._project.name in specific_description: description += "\n\n" + specific_description[self._project.name] return description def open_mr( self, mr_title: str, mr_source: str, mr_target: str, remove_source: bool, squash: bool, labels: str | None = None, ) -> tuple[int, str]: mr_id, mr_url = self._get_open_mr(mr_title, mr_source, mr_target) if mr_id > 0: print(f'MR with title "{mr_title}" was already opened:\n{mr_url}') return 0, "" assert isinstance(self._project.manager.gitlab.user, CurrentUser) default_mr_description = self._get_default_mr_description() if not default_mr_description: print("WARN: default MR description not available, pipeline might fail") mr = self._project.mergerequests.create( { "title": mr_title, "source_branch": mr_source, "target_branch": mr_target, "remove_source_branch": remove_source, "squash": squash, "subscribed": True, "labels": labels, "assignee_id": self._project.manager.gitlab.user.id, "description": default_mr_description, } ) assert isinstance(mr, ProjectMergeRequest) return mr.iid, mr.web_url def merge_mr(self, mr_id: int, wait_seconds: int) -> bool: mr = self._wait_for_mr_approval(mr_id, wait_seconds) if mr: mr.merge() # TODO: check that this actually worked return True return False def _wait_for_mr_approval( self, mr_id: int, seconds: int ) -> ProjectMergeRequest | None: go_sleep = False sleep_interval = 2 for _ in range(int(seconds / sleep_interval + 1)): if go_sleep: time.sleep(sleep_interval) mr = self._project.mergerequests.get(mr_id) assert isinstance(mr, ProjectMergeRequest) if mr.state != "opened": print(f"MR waiting for approval was {mr.state}:\n{mr.web_url}") return None if ( mr.merge_status == "can_be_merged" and mr.detailed_merge_status == "mergeable" and not mr.draft and not mr.work_in_progress and not mr.has_conflicts ): return mr go_sleep = True print("Timeout waiting for MR approval") return None def close_mr(self, mr_id: int): if self._project: mr = self._project.mergerequests.get(mr_id) assert isinstance(mr, ProjectMergeRequest) mr.state_event = "close" mr.save() def create_branch(self, name: str, target: str): branch = self._project.branches.create({"branch": name, "ref": target}) assert branch is not None def delete_branch(self, name: str): self._project.branches.delete(name) def create_tag(self, name: str, target: str): tag = self._project.tags.create({"tag_name": name, "ref": target}) assert tag is not None def delete_tag(self, name: str): self._project.tags.delete(name) def get_tag_url(self, tag_name: str) -> str: tag: ProjectTag = self._project.tags.get(tag_name) commit_url = tag.commit["web_url"] tag_url = commit_url.replace(f"/commit/{tag.target}", f"/commits/{tag.name}") return tag_url def get_release_url(self, tag_name: str) -> str: # GitLab release page URL is stable and doesn't require extra API calls. # Example: https://gitlab.example.com/group/proj/-/releases/v1.2.3 return f"{self._project.web_url}/-/releases/{tag_name}" def get_last_commit_in_branch(self, branch_name: str) -> str: commits = self._project.commits.list( ref_name=branch_name, get_all=False, per_page=1 ) assert isinstance(commits, list) assert len(commits) == 1 return commits[0].id def create_release(self, name: str, target: str, notes: str): release = self._project.releases.create( {"name": name, "tag_name": target, "description": notes} ) assert release is not None return self.get_release_url(target) def get_last_rc(self, release_name: str) -> int: re_pattern = "^" + re.escape(f"{release_name}-rc.") + r"(\d+)$" rc = 0 tag_list = self._project.tags.list(get_all=True) for t in tag_list: if ( isinstance(t.name, str) and (result := re.search(re_pattern, t.name)) and int(result.group(1)) > rc ): rc = int(result.group(1)) return rc sdk-10.11.0/automation/lib/public_repository.py000066400000000000000000000020041516266226600215200ustar00rootroot00000000000000from github3 import login, GitHub # github3.py from github3.repos.repo import Repository from github3.repos.release import Release class GitHubRepository: # use github API def __init__(self, github_token: str, repo_owner: str, repo_name: str): gh = login(token=github_token) assert isinstance(gh, GitHub) self._repo = gh.repository(repo_owner, repo_name) assert isinstance(self._repo, Repository) def create_release(self, version: str, notes: str): assert isinstance(self._repo, Repository) release = self._repo.create_release( tag_name=f"v{version}", name=f"Version {version}", body=notes ) assert isinstance(release, Release) return getattr(release, "html_url", self.get_release_url(version)) def get_release_url(self, version: str) -> str: assert isinstance(self._repo, Repository) # Example: https://github.com/owner/repo/releases/tag/v1.2.3 return f"{self._repo.html_url}/releases/tag/v{version}" sdk-10.11.0/automation/lib/release_process.py000066400000000000000000000670161516266226600211370ustar00rootroot00000000000000import re from lib.chat import Slack from lib.local_repository import LocalRepository from lib.private_repository import GitLabRepository from lib.public_repository import GitHubRepository from lib.sign_commits import setup_gpg_signing from lib.version_management import JiraProject from atlassian import Confluence class ReleaseProcess: def __init__( self, project_name: str, gitlab_token: str, private_host_url: str, private_branch: str, ): self._new_version: str | None = None self._private_branch = private_branch self._jira: JiraProject | None = None self._local_repo: LocalRepository | None = None print("GitLab initializing", flush=True) self._remote_private_repo = GitLabRepository( private_host_url, gitlab_token, project_name ) print("v GitLab initialized", flush=True) self._project_name = project_name self._version_v_prefixed = "" def setup_project_management(self, url: str, token: str): assert self._jira is None print("Jira initializing", flush=True) self._jira = JiraProject( url, token, self._project_name, ) print("v Jira initialized", flush=True) def set_release_version_to_make(self, version: str): assert self._jira is not None self._jira.setup_release() if not version: v = self._jira.get_next_version() version = ".".join(map(str, v)) self._new_version = version self._version_v_prefixed = f"v{self._new_version}" def setup_chat( self, slack_token: str, slack_channel_dev: str, slack_channel_announce: str = "", slack_thread_announce: str = "", ): # Chat has 2 purposes: # - request approvals for MRs (always in the same channel, only for SDK devs); # - make announcements in the given channel, if any. print("Slack initializing", flush=True) self._slack = Slack(slack_token) self._slack_channel_dev_requests = slack_channel_dev self._slack_channel_announce = slack_channel_announce self._slack_thread_announce = slack_thread_announce print("v Slack initialized", flush=True) # STEP 3: update version in local file def update_version_in_local_file( self, gpg_keygrip: str, gpg_password: str, private_remote_name: str, new_branch: str, ): setup_gpg_signing(gpg_keygrip, gpg_password) assert self._local_repo is None private_remote_url = self._remote_private_repo.get_url_to_private_repo() self._local_repo = LocalRepository(private_remote_name, private_remote_url) self._get_branch_locally(private_remote_name, self._private_branch) self._change_version_in_file() self._push_to_new_branch(new_branch, private_remote_name) self._merge_local_changes(new_branch, self._private_branch) def _get_branch_locally( self, remote_name: str, branch: str, ): assert self._local_repo is not None self._local_repo.check_for_uncommitted_changes() self._local_repo.switch_to_branch(remote_name, branch) self._local_repo.sync_current_branch(remote_name) # Edit version file def _change_version_in_file(self): assert self._new_version is not None version = self._new_version.split(".") assert len(version) == 3, f"Invalid requested version: {self._new_version}" # read old version oldMajor = oldMinor = oldMicro = 0 assert LocalRepository.has_version_file() lines = LocalRepository.version_file.read_text().splitlines() for i, line in enumerate(lines): if result := re.search( r"^(#define\s+MEGA_MAJOR_VERSION\s+)(\d+)([.\s]*)", line ): oldMajor = int(result.group(2)) lines[i] = result.group(1) + version[0] + result.group(3) elif result := re.search( r"^(#define\s+MEGA_MINOR_VERSION\s+)(\d+)([.\s]*)", line ): oldMinor = int(result.group(2)) lines[i] = result.group(1) + version[1] + result.group(3) elif result := re.search( r"^(#define\s+MEGA_MICRO_VERSION\s+)(\d+)([.\s]*)", line ): oldMicro = int(result.group(2)) lines[i] = result.group(1) + version[2] + result.group(3) print( f"Updating version: {oldMajor}.{oldMinor}.{oldMicro} -> ", self._new_version, flush=True, ) # validate new version major = int(version[0]) minor = int(version[1]) micro = int(version[2]) assert (major > oldMajor) or ( major == oldMajor and (minor > oldMinor or (minor == oldMinor and micro > oldMicro)) ), f"Invalid version: {oldMajor}.{oldMinor}.{oldMicro} -> {self._new_version}" # write new version LocalRepository.version_file.write_text("\n".join(lines)) # Commit changes in version file def _push_to_new_branch( self, new_branch: str, remote_name: str, ): assert self._local_repo is not None try: self._local_repo.commit_changes_to_new_branch( self._get_mr_title_for_version_update(), new_branch ) print("v Changes committed to", new_branch, flush=True) self._local_repo.push_branch(remote_name, new_branch) print(f"v Branch {new_branch} pushed", flush=True) except AssertionError: self._local_repo.clean_version_changes(new_branch, self._private_branch) raise self._local_repo.clean_version_changes(new_branch, self._private_branch) def _get_mr_title_for_version_update(self) -> str: return f"Update version to {self._new_version}" def _get_mr_title_for_release(self) -> str: return f"Release {self._new_version}" # Merge new branch with changes in version file def _merge_local_changes( self, new_branch: str, target_branch: str, ): mr_id, mr_url = self._remote_private_repo.open_mr( self._get_mr_title_for_version_update(), new_branch, target_branch, remove_source=True, squash=True, ) if mr_id == 0: self._remote_private_repo.delete_branch(new_branch) raise ValueError("Failed to open MR with local changes") print(f"v MR {mr_id} opened (version upgrade):\n {mr_url}") print(" **** Release process will continue after the MR has been approved.") print(" Cancel by manually closing the MR.", flush=True) # Send message to chat for MR approval self._request_mr_approval( f"`{self._project_name}` release `{self._new_version}`:\n{mr_url}" ) # MR not approved within the waiting interval will be closed and # the process aborted. To abort earlier just close the MR manually. if not self._remote_private_repo.merge_mr( mr_id, 3600 ): # wait 1 h max for approval self._remote_private_repo.close_mr(mr_id) self._remote_private_repo.delete_branch(new_branch) raise ValueError("Failed to merge MR with local changes") print("v MR merged for version upgrade", flush=True) def _request_mr_approval(self, reason: str): if self._slack is None or not self._slack_channel_dev_requests: print( f"You need to request MR approval yourself because chat is not available,\n{reason}", flush=True, ) else: self._slack.post_message( self._slack_channel_dev_requests, "", f"Hello ,\n\nPlease approve the MR for {reason}", ) def _filter_release_notes(self, raw_release_notes: str, included_tickets: list[str]): # Regular expression to detect ticket lines and section lines ticket_line_regex = re.compile(r"^• \[<[^|]+\|([A-Z]+-\d+)\>\]") section_header_regex = re.compile(r"^(?!• )\S.*") filtered_lines = [] current_section = [] section_has_content = False for line in raw_release_notes.splitlines(): if section_header_regex.match(line): # Process the previous section if current_section and section_has_content: filtered_lines.extend(current_section) current_section = [line] section_has_content = False elif ticket_line_regex.search(line): ticket_match = ticket_line_regex.search(line) if ticket_match and ticket_match.group(1) in included_tickets: current_section.append(line) section_has_content = True else: current_section.append(line) if line.strip(): section_has_content = True # Append the last section if it has relevant content if current_section and section_has_content: filtered_lines.extend(current_section) filtered_notes = "\n".join(filtered_lines) return filtered_notes # STEP 4: Create "release/vX.Y.Z" branch def create_release_branch(self): self._version_v_prefixed = f"v{self._new_version}" release_branch = self.get_new_release_branch() print("Creating branch", release_branch, flush=True) self._remote_private_repo.create_branch(release_branch, self._private_branch) print("v Created branch", release_branch) # STEP 5: Create rc tag "vX.Y.Z-rc.1" from branch "release/vX.Y.Z" def create_rc_tag(self, rc_num: int): assert self._remote_private_repo is not None assert self._version_v_prefixed self._rc_tag = f"{self._version_v_prefixed}-rc.{rc_num}" print("Creating tag", self._rc_tag, flush=True) try: self._remote_private_repo.create_tag( self._rc_tag, self.get_new_release_branch() ) except Exception as e: print( "Creating tag", self._rc_tag, "for branch", self.get_new_release_branch(), "failed", flush=True, ) raise e print("v Created tag", self._rc_tag) # STEP 6: Open MR to merge branch "release/vX.Y.Z" into public branch (don't merge) def open_mr_for_release_branch(self, public_branch: str): assert self._remote_private_repo is not None assert self._rc_tag is not None release_branch = self.get_new_release_branch() print( f"Opening MR to merge {release_branch} into {public_branch}", flush=True, ) mr_id, _ = self._remote_private_repo.open_mr( self._get_mr_title_for_release(), release_branch, public_branch, remove_source=False, squash=False, labels="Release", ) if mr_id == 0: self._remote_private_repo.delete_branch(release_branch) self._remote_private_repo.delete_tag(self._rc_tag) raise ValueError( f"Failed to open MR to merge {release_branch} into {public_branch}" ) print(f"v Opened MR to merge {release_branch} into {public_branch}") print( " **** Do NOT merge this MR until the release will be closed (dependent apps are live)!!", flush=True, ) # STEP 7: Update and rename previous NextRelease version; create new NextRelease version def manage_versions(self, apps: str): assert self._new_version is not None assert self._jira is not None self._jira.update_current_version( self._new_version, # i.e. "X.Y.Z" apps, # i.e. "iOS A.B / Android C.D / MEGAsync E.F.G" ) self._jira.create_new_version() # STEP 8: Post release notes to Slack def post_notes(self, apps: list[str], releaseType: str, tickets: list[str] = None): print("Generating release notes...", flush=True) assert self._rc_tag is not None assert self._jira is not None assert releaseType is not None tag_url = self._remote_private_repo.get_tag_url(self._rc_tag) if releaseType == "newRelease": notes: str = ( f"\U0001F4E3 \U0001F4E3 *New {self._project_name} version --> `{self._rc_tag}`* (<{tag_url}|Link>)\n\n" ) elif releaseType == "releaseCandidate": notes: str = ( f"\U0001F4E3 \U0001F4E3 *New {self._project_name} version --> `{self._rc_tag}`* (<{tag_url}|Link>)\n" f"\U000026A0 What's new in this candidate:\n\n" ) elif releaseType == "patchRelease": major, minor, micro = (int(n) for n in self._new_version.split(".")) previousVersion = f"{major}.{minor}.{micro - 1}" notes: str = ( f"\U0001F4E3 \U0001F4E3 *New {self._project_name} version --> `{self._rc_tag}`* (<{tag_url}|Link>)\n" f"\U000026A0 \U000026A0 Hotfix created from `{previousVersion}` not develop, please only use this if your app was affected by some of the fixed issues\n\n" ) raw_notes = self._jira.get_release_notes_for_slack(apps) if tickets: filtered_notes = self._filter_release_notes(raw_notes, tickets) else: filtered_notes = raw_notes notes += filtered_notes if not self._slack or not self._slack_channel_announce: print("Enjoy:\n\n" + notes, flush=True) else: self._slack.post_message( self._slack_channel_announce, self._slack_thread_announce, notes ) print( f"v Posted release notes to #{self._slack_channel_announce}", flush=True ) #################### ## Close release #################### def setup_local_repo( self, private_remote_name: str, public_remote_name: str, public_remote_url: str, ): print("Local Git repo initializing", flush=True) assert self._local_repo is None private_remote_url = self._remote_private_repo.get_url_to_private_repo() self._local_repo = LocalRepository(private_remote_name, private_remote_url) if public_remote_name and public_remote_url: self._local_repo.add_remote( public_remote_name, public_remote_url, fetch_is_optional=True ) print("v Local Git repo initialized", flush=True) def setup_public_repo(self, public_repo_token: str, public_repo_owner: str): print("GitHub initializing", flush=True) self._public_repo = GitHubRepository( public_repo_token, public_repo_owner, self._project_name ) print("v GitHub initialized", flush=True) def set_release_version_to_close(self, version: str): assert not self._new_version self._new_version = version self._version_v_prefixed = f"v{self._new_version}" self._release_branch = f"release/{self._version_v_prefixed}" assert self._jira is not None self._jira.setup_release(self._version_v_prefixed) def get_release_type_to_close(self, public_branch: str): assert self._jira is not None, "Init Jira connection first" mr_id, _ = self._remote_private_repo._get_open_mr( self._get_mr_title_for_release(), self.get_new_release_branch(), public_branch, ) if mr_id == 0: return "hotfix" if self._jira.earlier_versions_are_closed(): return "new_release" return "old_release" def confirm_all_earlier_versions_are_closed(self): # This could be implemented in multiple ways. # Relying on Jira looked fine as it's the last update done when closing a Release. assert self._jira is not None, "Init Jira connection first" self._jira.earlier_versions_are_closed() def setup_wiki(self, url: str, token: str): if url and token: print("Confluence initializing", flush=True) self._wiki = Confluence(url=url, token=token) print("v Confluence configured", flush=True) @staticmethod def build_close_announcement_message( *, project_name: str, version_v_prefixed: str, apps: list[str] | None, gitlab_url: str = "", github_url: str = "", ) -> str: lines: list[str] = [] header = ( f"\U0001F4E3 \U0001F4E3 *{project_name} version --> `{version_v_prefixed}` published*" ) lines.append(header) lines.append("") # empty line between header and release links if gitlab_url or github_url: lines.append("Releases") if gitlab_url: lines.append(f"\U00002022 GitLab: {gitlab_url}") if github_url: lines.append(f"\U00002022 GitHub: {github_url}") lines.append("") # empty line between release links and target apps if apps: lines.append("Target apps") for app in apps: lines.append(f"\U00002022 {app}") return "\n".join(lines) # STEP 1 (close): GitLab: Create tag "vX.Y.Z" from last commit of branch "release/vX.Y.Z" def create_release_tag(self): last_commit = self._remote_private_repo.get_last_commit_in_branch( self.get_new_release_branch() ) print("Creating tag", self._version_v_prefixed, flush=True) assert ( self._release_branch ), "Check that set_release_version_to_close() was called" self._remote_private_repo.create_tag(self._version_v_prefixed, last_commit) print( "v Created tag", self._version_v_prefixed, "from commit", last_commit, flush=True, ) # STEP 2 (close): GitLab: Create release "Version X.Y.Z" from tag "vX.Y.Z" plus release notes def create_release_in_private_repo(self): release_name = f"Version {self._new_version}" assert self._jira is not None release_notes = self._jira.get_release_notes_for_gitlab([]) print("Creating release", release_name, flush=True) self._gitlab_release_url = self._remote_private_repo.create_release( release_name, self._version_v_prefixed, release_notes ) print("v Created release", release_name, flush=True) # STEP 3 (close): GitLab, Slack: Merge version upgrade MR into public branch (master) def merge_release_changes_into_public_branch(self, public_branch: str): mr_id, mr_url = self._remote_private_repo._get_open_mr( self._get_mr_title_for_release(), self.get_new_release_branch(), public_branch, ) assert mr_id > 0 self._request_mr_approval( f"`{self._project_name}` close `{self._new_version}`:\n{mr_url}" ) self._remote_private_repo.merge_mr(mr_id, 3600) # must not delete source branch # STEP 4 (close): local git: Push public branch (master) to public remote (github) def push_to_public_repo( self, private_remote_name: str, public_branch: str, public_remote_name: str ): # get "master" branch locally, with latest changes self._get_branch_locally(private_remote_name, public_branch) # push stuff to public repo assert self._local_repo is not None self._local_repo.push_branch( # "master" branch public_remote_name, public_branch ) self._local_repo.push_branch( # "vX.Y.Z" tag public_remote_name, self._version_v_prefixed ) # STEP 4 (close): local git: Push release branch (release/vX.Y.Z) to public remote (github) def push_release_branch_to_public_repo( self, private_remote_name: str, public_remote_name: str ): release_branch = self.get_new_release_branch() # get hotfix branch locally, with latest changes self._get_branch_locally(private_remote_name, release_branch) # push stuff to public repo assert self._local_repo is not None self._local_repo.push_branch(public_remote_name, release_branch) self._local_repo.push_branch( public_remote_name, self._version_v_prefixed ) # "vX.Y.Z" tag # STEP 5 (close): GitHub: Create release in public repo from new tag def create_release_in_public_repo(self, version: str): assert self._jira is not None self._github_release_url = self._public_repo.create_release( version, self._jira.get_release_notes_for_github() ) # STEP 6 (close): Post release announcement to Slack def post_close_announcement(self): """Post a Slack message announcing the release is published/closed. Target apps are derived from the Jira Version description for vX.Y.Z. Optionally include GitLab/GitHub release links if available. """ assert self._new_version is not None assert self._version_v_prefixed if self._jira is not None: apps = self._jira.get_target_apps() else: apps = [] gitlab_url = getattr(self, "_gitlab_release_url", "") if not gitlab_url and self._remote_private_repo is not None: gitlab_url = self._remote_private_repo.get_release_url(self._version_v_prefixed) github_url = getattr(self, "_github_release_url", "") if not github_url and getattr(self, "_public_repo", None) is not None: github_url = self._public_repo.get_release_url(self._new_version) msg = self.build_close_announcement_message( project_name=self._project_name, version_v_prefixed=self._version_v_prefixed, apps=apps, gitlab_url=gitlab_url, github_url=github_url, ) if not getattr(self, "_slack", None) or not getattr( self, "_slack_channel_announce", "" ): print("Close release:\n\n" + msg, flush=True) return self._slack.post_message( self._slack_channel_announce, getattr(self, "_slack_thread_announce", ""), msg, ) print( f"v Posted close announcement to #{self._slack_channel_announce}", flush=True, ) # STEP 7 (close): Jira: mark version as Released, set release date def mark_version_as_released(self): assert self._jira is not None, "Init Jira connection first" self._jira.update_version_close_release() # STEP 8 (close): Confluence: Rotate the first name to the end of the list of release captains def move_release_captain_last(self, page_id: str): if self._wiki is None: print("Wiki connection not available, rotate Release Captain yourself !") return # get page content page = self._wiki.get_page_by_id(page_id, expand="body.storage") if not isinstance(page, dict): print("Wiki page not available, rotate Release Captain yourself !") return content = page["body"]["storage"]["value"] # move current user last re_pattern = ( "Release Captain schedule.*?" "" # Opening tag "(
  • .*?
  • )" # Match first item in the list. Current captain ".*?()" # Skip the rest of the list and matches closing tag. ) match = re.search(re_pattern, content) if match is None: print( "Wiki page content missing current user, rotate Release Captain yourself !" ) return completeMatch = match.group(0) captain = match.group(2) closingTag = match.group(3) new_content = ( content[: match.start(0)] + completeMatch.replace(captain, "").replace(closingTag, captain) + closingTag + content[match.end(0) :] ) self._wiki.update_page(page_id, page["title"], new_content) print("v Release Captain rotated") #################### ## Patch release #################### # Validation (patch): Jira, GitLab: Validate new and previous versions def set_release_version_after_patch(self, version: str) -> str: # validate the new version, ensure it didn't already exist major, minor, micro = (int(n) for n in version.split(".")) assert micro > 0, f"Patched version must be higher than {version}" assert self._jira is not None [exists, _, _] = self._jira.get_version_info(version) assert not exists, f"Version {version} already exists!" # validate previous version, before patch previous_version = f"{major}.{minor}.{micro - 1}" [exists, was_released, app_descr] = self._jira.get_version_info( previous_version ) assert exists, f"Could not find version {previous_version} before patch" assert was_released, "Attempting to patch a non-released version (RC)" assert not self._new_version self._new_version = version self._version_v_prefixed = f"v{self._new_version}" return app_descr def get_new_release_branch(self) -> str: assert self._version_v_prefixed return f"release/{self._version_v_prefixed}" # STEP 7 (patch): Jira: Manage versions def create_new_for_patch(self, for_apps: str): assert self._jira assert self._version_v_prefixed self._jira.create_new_version_for_patch(self._version_v_prefixed, for_apps) self._jira.setup_release(self._version_v_prefixed) def add_fix_version_to_tickets(self, tickets: list[str]): assert self._jira self._jira.add_fix_version_to_tickets(tickets) # STEP 8 (patch): local git, GitLab, Slack: Update version in local file def update_version_in_local_file_from_branch( self, gpg_keygrip: str, gpg_password: str, private_remote_name: str, new_branch: str, target_branch: str, ): setup_gpg_signing(gpg_keygrip, gpg_password) self._get_branch_locally(private_remote_name, target_branch) self._change_version_in_file() assert self._local_repo is not None self._local_repo.commit_changes_to_new_branch( self._get_mr_title_for_version_update(), new_branch ) self._local_repo.push_branch(private_remote_name, new_branch) print(f"v Branch {new_branch} pushed", flush=True) self._merge_local_changes(new_branch, target_branch) #################### ## RC #################### def set_release_version_for_new_rc(self, version: str): assert not self._new_version assert self._jira is not None self._new_version = version self._version_v_prefixed = f"v{self._new_version}" print(f"Version v prefixed is {self._version_v_prefixed}") self._jira.setup_release(self._version_v_prefixed) [exists, was_released, app_descr] = self._jira.get_version_info(version) assert exists, f"Could not find version {version}, for a new RC" assert not was_released, "Cannot make a new RC for a released version" return app_descr def get_last_rc(self): rc = self._remote_private_repo.get_last_rc(self._version_v_prefixed) assert rc, f"No RC found for version {self._new_version}" return rc sdk-10.11.0/automation/lib/sign_commits.py000066400000000000000000000033341516266226600204450ustar00rootroot00000000000000import platform, subprocess from pathlib import Path def setup_gpg_signing(gpg_keygrip: str, gpg_password: str): if platform.system() == "Windows": print( "WARNING: Setup for gpg signing not implemented on Windows. Considering it done..." ) return # Caching password must be allowed: # echo allow-preset-passphrase >> $(gpgconf --list-dirs homedir)/gpg-agent.conf _check_gpg_pass_caching() # Cache password for key (from 'nix shell, git-bash on Win etc.): # $(gpgconf --list-dirs libexecdir)/gpg-preset-passphrase -c < # EOF byte_output = subprocess.check_output(["gpgconf", "--list-dirs", "libexecdir"]) gpg_libexec = byte_output.decode("utf-8").strip() gpg_proc = subprocess.Popen( [gpg_libexec + "/gpg-preset-passphrase", "-c", gpg_keygrip], stdin=subprocess.PIPE, ) assert gpg_proc.stdin is not None gpg_proc.stdin.write(gpg_password.encode()) gpg_proc.stdin.close() def _check_gpg_pass_caching(): byte_output = subprocess.check_output(["gpgconf", "--list-dirs", "homedir"]) gpg_home = byte_output.decode("utf-8").strip() gpg_conf = Path(gpg_home) / "gpg-agent.conf" allow_preset_passphrase = "allow-preset-passphrase" if gpg_conf.is_file(): with gpg_conf.open("r+") as f: while line := f.readline(): if line.strip() == allow_preset_passphrase: return f.write(f"\n{allow_preset_passphrase}\n") else: gpg_conf.write_text(f"{allow_preset_passphrase}\n") assert subprocess.run( ["gpg-connect-agent", "reloadagent", "/bye"], stdout=subprocess.DEVNULL ), "Failed to restart gpg-connect-agent" sdk-10.11.0/automation/lib/version_management.py000066400000000000000000000330411516266226600216310ustar00rootroot00000000000000from collections import defaultdict from jira import JIRA, Project from jira.resources import Issue, Version from requests import Response import re class JiraProject: """ Functionality: - rename NextRelease to the new version - create new NextRelease version - get release notes - check all tickets on a release are resolved or closed """ _NEXT_RELEASE = "NextRelease" def __init__( self, url: str, token: str, project: str, ): self._jira = JIRA(url, token_auth=token) self._project_key = self._get_project_key(project) assert self._project_key, f"No project found with name {project}" self._version_manager_url = f"{self._jira.server_url}/rest/versionmanager/1.0/versionmanager/{self._project_key}" def _get_project_key(self, project_name: str) -> str: all_projects = self._jira.projects() # find by name, otherwise by name that starts with the received value temp_key = "" temp_count = 0 for p in all_projects: assert isinstance(p, Project) if p.name == project_name: return p.key if p.name.startswith(project_name + " "): temp_count += 1 temp_key = p.key return temp_key if temp_count == 1 else "" def setup_release(self, release_name: str = _NEXT_RELEASE): # validate version self._version: Version | None = self._jira.get_project_version_by_name( self._project_key, release_name ) assert self._version is not None assert self._version.released == False self._version_id = self._version.id self._check_all_tickets_are_resolved_or_closed() self._check_all_tickets_have_release_number_affected() @staticmethod def _parse_target_apps_from_version_description(description: str) -> list[str]: """Extract target apps from a Jira Version description. Expected format created by this automation: "Version X.Y.Z - iOS A.B / Android C.D / MEGAsync E.F.G" If the separator is missing, this is treated as "no apps". """ if not description: return [] trimmed = description.strip() _, sep, tail = trimmed.partition(" - ") if not sep: return [] app_part = tail.strip() if not app_part: return [] return [a.strip() for a in app_part.split("/") if a.strip()] def get_target_apps(self) -> list[str]: """Return target apps for the currently selected version (after setup_release()).""" assert self._version is not None return self._parse_target_apps_from_version_description( self._version.description or "" ) def update_current_version(self, to_version: str, used_by_apps: str): from datetime import date today = date.today().isoformat() # YYYY-MM-DD required by the REST api version_data = { "name": f"v{to_version}", "startdate": today, "description": f"Version {to_version} - {used_by_apps}", } self._update_version(version_data) def create_new_version(self): # use the logged in session to access the REST API of the plugin assert self._jira._session is not None r: Response = self._jira._session.post( url=self._version_manager_url, data={"name": self._NEXT_RELEASE} ) r.raise_for_status() def create_new_version_for_patch(self, name: str, for_apps: str): # use the logged in session to access the REST API of the plugin assert self._jira._session is not None from datetime import date today = date.today().isoformat() # YYYY-MM-DD required by the REST api r: Response = self._jira._session.post( url=self._version_manager_url, data={ "name": name, "startdate": today, "description": f"Version {name} - {for_apps}", }, ) r.raise_for_status() def update_version_close_release(self): from datetime import date today = date.today().isoformat() # YYYY-MM-DD required by the REST api version_data = { "releasedate": today, "status": "Released", } self._update_version(version_data) def _update_version(self, new_data: dict): # A Version can be updated only by a project administrator. # To allow updating version details by the release captain, # there is a plugin installed called Version Manager for Jira. # The plugin can only be accessed through its own REST API. # use the logged in session to access the REST API of the plugin assert self._jira._session is not None r: Response = self._jira._session.put( url=f"{self._version_manager_url}/{self._version_id}", data=new_data ) r.raise_for_status() def get_release_notes_for_slack(self, apps: list[str]) -> str: return self._get_notes(apps, formatting="slack", include_urls=True) def get_release_notes_for_gitlab(self, apps: list[str]) -> str: return self._get_notes(apps, formatting="git", include_urls=True) def get_release_notes_for_github(self) -> str: return self._get_notes([], formatting="git", include_urls=False) def _get_notes(self, apps: list[str], formatting: str, include_urls: bool) -> str: if len(apps) == 0: # get apps from description assert self._version is not None apps = self.get_target_apps() # get issues issues_found = self._jira.search_issues( f"project={self._project_key} AND fixVersion={self._version_id} AND status=Resolved AND resolution=Done", fields="issuetype, key, summary, ", maxResults=200, ) issues: dict[str, list[tuple[str, str, str]]] = defaultdict(list) for i in issues_found: assert isinstance(i, Issue) i_type = i.get_field("issuetype").name i_url = i.permalink() i_summary = str(i.get_field("summary")) issues[i_type].append((i_url, i.key, i_summary)) # build notes notes = "" bullet = self._get_bullet_placeholder(formatting) for k, vs in issues.items(): notes += self._get_notes_chapter(k, formatting) for p in vs: url = p[0] if include_urls else "" notes += ( bullet + " " + self._get_notes_issue(p[1], url, p[2], formatting) ) notes += "\n" notes += self._get_notes_chapter("Target apps", formatting) for a in apps: notes += f"{bullet} {a}\n" return notes def _get_bullet_placeholder(self, formatting: str) -> str: if formatting == "slack": return "\U00002022" # utf8 bullet return "-" # "git" and others def _get_notes_chapter(self, title: str, formatting: str) -> str: if formatting == "slack": return f"{title}\n" if formatting == "git": return f"## **{title}**\n\n" return f"{title}\n\n" # return it with no formatting def _get_notes_issue( self, id: str, url: str, description: str, formatting: str ) -> str: prefix = "" if not url: prefix = f"[{id}]" elif formatting == "slack": prefix = f"[<{url}|{id}>]" elif formatting == "git": prefix = f"\\[[{id}]({url})\\]" else: prefix = id # return it with no formatting issue = prefix + f" - {description}\n" return issue def get_version_info(self, version: str) -> tuple[bool, bool, str]: v: Version | None = self._jira.get_project_version_by_name( self._project_key, f"v{version}" ) if v is None: return False, False, "" assert not v.archived, f"Archived v{version} version already exists" # get apps from description app_descr: str = (v.description or "").partition(" - ")[2] return True, v.released, app_descr def earlier_versions_are_closed(self): assert self._version is not None new_major, new_minor, new_micro = ( int(n) for n in self._version.name[1:].split(".") ) all_versions = self._jira.project_versions(self._project_key) for v in all_versions: assert isinstance(v, Version) if ( v.archived or not v.released or v.name == self._version.name or v.name == self._NEXT_RELEASE ): continue match = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", v.name) if not match: continue old_major, old_minor, old_micro = map(int, match.groups()) if ( new_major, new_minor, new_micro, ) < ( old_major, old_minor, old_micro, ): return False return True def get_next_version(self) -> tuple[int, int, int]: highest_existing_version = self._get_highest_existing_version() release_number_affected = self._get_version_component_to_increment() match release_number_affected: case "Major": next_version = (highest_existing_version[0] + 1, 0, 0) case "Minor": next_version = ( highest_existing_version[0], highest_existing_version[1] + 1, 0, ) case _: next_version = ( highest_existing_version[0], highest_existing_version[1], highest_existing_version[2] + 1, ) return next_version def _get_highest_existing_version(self) -> tuple[int, int, int]: # find the highest existing version highest_major = highest_minor = highest_micro = 0 all_versions = self._jira.project_versions(self._project_key) for v in all_versions: assert isinstance(v, Version) if re.match(r"^v(\d)+\.(\d+)\.(\d+)$", v.name): major, minor, micro = (int(n) for n in v.name[1:].split(".")) if major > highest_major: highest_major = major highest_minor = minor highest_micro = micro elif major == highest_major: if minor > highest_minor: highest_minor = minor highest_micro = micro elif minor == highest_minor and micro > highest_micro: highest_micro = micro return (highest_major, highest_minor, highest_micro) def _get_version_component_to_increment(self) -> str: # get id of custom field custom_field_id = "" all_the_fields = self._jira.fields() for f in all_the_fields: if f["custom"] and f["name"] == "Release number affected": custom_field_id = f["id"] # 'customfield_10502' break assert custom_field_id # get relevant issues unreleased_issues = self._jira.search_issues( f"project={self._project_key} AND fixVersion={self._version_id} AND status=Resolved AND resolution=Done", maxResults=200, ) # get higest Release number affected release_number_affected = "Patch" for i in unreleased_issues: assert isinstance(i, Issue) custom_field_data = i.raw.get("fields", {}).get(custom_field_id) assert custom_field_data, f"Missing 'Release number affected' field in ticket: {i.key}" affected = custom_field_data.get("value") if affected == "Major": return affected if affected == "Minor": release_number_affected = affected return release_number_affected def add_fix_version_to_tickets(self, tickets: list[str]): assert self._version for t in tickets: issue: Issue = self._jira.issue(t) fixVersions = [] for v in issue.fields.fixVersions: if v.name != self._version.name: fixVersions.append({"name": v.name}) fixVersions.append({"name": self._version.name}) issue.update({"fixVersions": fixVersions}) def _check_all_tickets_are_resolved_or_closed(self): jql_query = ( f'project = "{self._project_key}" AND fixVersion = "{self._version.name}" ' f'AND status NOT IN ("Resolved", "Closed")' ) issues = self._jira.search_issues(jql_query) assert not issues, ( f"The following tickets are not resolved or closed for Fix Version '{self._version.name}':\n" + "\n".join( f"- {issue.key} -> {issue.fields.status.name}" for issue in issues ) ) def _check_all_tickets_have_release_number_affected(self): jql_query = ( f'project = "{self._project_key}" AND fixVersion = "{self._version.name}" ' f'AND "Release number affected" is EMPTY' ) issues = self._jira.search_issues(jql_query) assert not issues, ( f"The following tickets are missing the release number affected field:\n" + "\n".join( f"- {issue.key}" for issue in issues ) ) sdk-10.11.0/automation/make_another_rc.py000066400000000000000000000042551516266226600203300ustar00rootroot00000000000000from lib.release_process import ReleaseProcess import re import tomllib import os import argparse # Read configuration file path parser = argparse.ArgumentParser( description="Make a new release candidate using the specified config file." ) parser.add_argument( "config_file", type=str, help="Path to the configuration file (TOML)" ) args = parser.parse_args() # Check for required environment variables required_env_vars = [ "GITLAB_TOKEN", "JIRA_TOKEN", "SLACK_TOKEN", ] for var in required_env_vars: if os.getenv(var) is None: print(f"{var} environment variable is not defined.") # runtime arguments with open(args.config_file, "rb") as f: args = tomllib.load(f)["make_another_rc"] # create Release process and do common init release = ReleaseProcess( args["project_name"], os.environ["GITLAB_TOKEN"], args["gitlab_url"], args["private_branch"], ) # prerequisites for a new RC release.setup_project_management( args["jira_url"], os.environ["JIRA_TOKEN"] ) slack_token = os.environ.get("SLACK_TOKEN", "") slack_channel_dev = args.get("slack_channel_dev_requests", "") slack_channel_announce = args.get("slack_channel_announce", "") if slack_token and (slack_channel_dev or slack_channel_announce): slack_thread_announce = args.get("slack_thread_announce", "") release.setup_chat( slack_token, slack_channel_dev, slack_channel_announce, slack_thread_announce ) assert args["release_version"] # "1.0.0" assert args["tickets"] app_descr = release.set_release_version_for_new_rc(args["release_version"]) # Add Fix Version to tickets tickets: list[str] = [t.strip() for t in args["tickets"].split(",")] release.add_fix_version_to_tickets(tickets) # STEP 1: GitLab: step #5 from make_release: # Create new RC tag release.setup_local_repo(args["private_remote_name"], "", "") last_rc = release.get_last_rc() release.create_rc_tag(last_rc + 1) # STEP 2: Slack: step #8 from make_release: # Post release notes to Slack if args["target_apps"]: apps = [a.strip() for a in args["target_apps"].split("/")] else: apps = [a.strip() for a in app_descr.split("/")] release.post_notes(apps, releaseType="releaseCandidate", tickets=tickets) sdk-10.11.0/automation/make_release.py000066400000000000000000000046561516266226600176310ustar00rootroot00000000000000from lib.local_repository import LocalRepository from lib.release_process import ReleaseProcess import tomllib import os import argparse RELEASE_CANDIDATE_NUMBER = 1 # Read configuration file path parser = argparse.ArgumentParser( description="Make a release using the specified config file." ) parser.add_argument( "config_file", type=str, help="Path to the configuration file (TOML)" ) args = parser.parse_args() # Check for required environment variables required_env_vars = [ "GITLAB_TOKEN", "JIRA_TOKEN", "SLACK_TOKEN", "GPG_KEYGRIP", "GPG_PASSWORD", ] for var in required_env_vars: if os.getenv(var) is None: print(f"{var} environment variable is not defined.") # runtime arguments with open(args.config_file, "rb") as f: args = tomllib.load(f)["make_release"] # create Release process and do common init release = ReleaseProcess( args["project_name"], os.environ["GITLAB_TOKEN"], args["gitlab_url"], args["private_branch"], ) # prerequisites for making a release release.setup_project_management(args["jira_url"], os.environ["JIRA_TOKEN"]) release.set_release_version_to_make(args["release_version"]) slack_token = os.environ.get("SLACK_TOKEN", "") slack_channel_dev = args.get("slack_channel_dev_requests", "") slack_channel_announce = args.get("slack_channel_announce", "") if slack_token and (slack_channel_dev or slack_channel_announce): slack_thread_announce = args.get("slack_thread_announce", "") release.setup_chat( slack_token, slack_channel_dev, slack_channel_announce, slack_thread_announce ) if LocalRepository.has_version_file(): # STEP 3: update version in local file release.update_version_in_local_file( os.environ["GPG_KEYGRIP"], os.environ["GPG_PASSWORD"], args["private_remote_name"], "task/update-sdk-version", ) # STEP 4: Create branch "release/vX.Y.Z" release.create_release_branch() # STEP 5: Create rc tag "vX.Y.Z-rc.1" from branch "release/vX.Y.Z" release.create_rc_tag(RELEASE_CANDIDATE_NUMBER) # STEP 6: Open MR from branch "release/vX.Y.Z" to public branch (don't merge) release.open_mr_for_release_branch(args["public_branch"]) # STEP 7: Rename previous NextRelease version; create new NextRelease version release.manage_versions(args["target_apps"]) # STEP 8: Post release notes to Slack apps = [a.strip() for a in args["target_apps"].split("/")] release.post_notes(apps, releaseType="newRelease") sdk-10.11.0/automation/patch_release.py000066400000000000000000000056231516266226600200060ustar00rootroot00000000000000from lib.local_repository import LocalRepository from lib.release_process import ReleaseProcess import tomllib import os import argparse # Read configuration file path parser = argparse.ArgumentParser( description="Patch a release using the specified config file." ) parser.add_argument( "config_file", type=str, help="Path to the configuration file (TOML)" ) args = parser.parse_args() # Check for required environment variables required_env_vars = [ "GITLAB_TOKEN", "JIRA_TOKEN", "SLACK_TOKEN", "GPG_KEYGRIP", "GPG_PASSWORD", ] for var in required_env_vars: if os.getenv(var) is None: print(f"{var} environment variable is not defined.") # runtime arguments with open(args.config_file, "rb") as f: args = tomllib.load(f)["patch_release"] # create Release process and do common init release = ReleaseProcess( args["project_name"], os.environ["GITLAB_TOKEN"], args["gitlab_url"], args["private_branch"], ) # prerequisites for patching a release release.setup_local_repo(args["private_remote_name"], "", "") release.setup_project_management( args["jira_url"], os.environ["JIRA_TOKEN"], ) slack_token = os.environ.get("SLACK_TOKEN", "") slack_channel_dev = args.get("slack_channel_dev_requests", "") slack_channel_announce = args.get("slack_channel_announce", "") if slack_token and (slack_channel_dev or slack_channel_announce): slack_thread_announce = args.get("slack_thread_announce", "") release.setup_chat( slack_token, slack_channel_dev, slack_channel_announce, slack_thread_announce ) assert args["tickets"] # Validation: Jira, GitLab: Validate new and previous versions version = args["release_version"] # "1.0.1" app_descr = release.set_release_version_after_patch(version) if args.get("target_apps"): app_descr = args["target_apps"] # STEP 7: Jira: Manage versions # Create new release # Name: vX.Y.Z (same as for new release branch) # Start date: today # Description: copy from version being patched # Set the new release as Fix Version for the included ticket(s). new_release_id = release.create_new_for_patch(app_descr) tickets: list[str] = [t.strip() for t in args["tickets"].split(",")] release.add_fix_version_to_tickets(tickets) if LocalRepository.has_version_file(): # STEP 8: local git, GitLab, Slack: update version in local file release.update_version_in_local_file_from_branch( os.environ["GPG_KEYGRIP"], os.environ["GPG_PASSWORD"], args["private_remote_name"], "task/update-sdk-version", release.get_new_release_branch(), ) # STEP 9: step #5 from make_release: GitLab: # Create a new release candidate (rc) tag with the format vX.Y.Z-rc.1 from the release/vX.Y.Z branch. release.create_rc_tag(1) # STEP 10: step #8 from make_release: Slack: # Post release notes to Slack apps = [a.strip() for a in app_descr.split("/")] release.post_notes(apps, releaseType="patchRelease") sdk-10.11.0/automation/report_jira_tickets_on_slack.py000066400000000000000000000130171516266226600231220ustar00rootroot00000000000000import os from typing import Callable from jira import JIRA, Issue from jira.client import ResultList from slack_sdk import WebClient from slack_sdk.errors import SlackApiError class JiraTicketsReporter: def __init__( self, jira_url: str, jira_token: str, jira_project_key: str, slack_token: str, slack_channel_id: str, ): self._jira_url = jira_url self._jira_client = JIRA(self._jira_url, token_auth=jira_token) self._jira_project_key = jira_project_key self._slack_client = WebClient(token=slack_token) self._slack_channel = slack_channel_id def get_jira_query_for_issues_missing_fix_version(self, project_key: str): return f'project = {project_key} AND status = Resolved AND resolution = Done AND fixVersion is EMPTY AND "Release number affected" is not EMPTY' def get_jira_query_for_issues_missing_release_number_affected( self, project_key: str ): return f'project = {project_key} AND status = Resolved AND resolution = Done AND fixVersion is not EMPTY AND "Release number affected" is EMPTY' def get_jira_query_for_issues_missing_fix_version_and_release_number_affected( self, project_key: str ): return f'project = {project_key} AND status = Resolved AND resolution = Done AND fixVersion is EMPTY AND "Release number affected" is EMPTY' def fetch_jira_issues(self, jql_query: str) -> ResultList[Issue]: issues = self._jira_client.search_issues( jql_query, fields="key, assignee", maxResults=200, ) return issues def try_lookup_for_user_email(self, email_to_try) -> str | None: try: response = self._slack_client.users_lookupByEmail(email=email_to_try) return response["user"]["id"] except SlackApiError as e: if e.response["error"] != "users_not_found": print( f"Error fetching Slack user ID for {email_to_try}: {e.response['error']}" ) return None def get_slack_user_id_by_email(self, email: str) -> str | None: if not email.endswith("@mega.co.nz") and not email.endswith("@mega.nz"): return None slack_user_id = self.try_lookup_for_user_email(email) if slack_user_id: return slack_user_id alt_email = ( email.replace("@mega.co.nz", "@mega.nz") if email.endswith("@mega.co.nz") else email.replace("@mega.nz", "@mega.co.nz") ) slack_user_id = self.try_lookup_for_user_email(alt_email) return slack_user_id def ensure_user_is_in_the_channel(self, slack_user_id: str): member_list = self._slack_client.conversations_members( channel=self._slack_channel )["members"] if slack_user_id in member_list: return self._slack_client.conversations_invite( channel=self._slack_channel, users=[slack_user_id] ) def post_to_slack( self, issue_key: str, slack_user_id: str | None, missing_field: str ): if not slack_user_id: user_mention = "" else: self.ensure_user_is_in_the_channel(slack_user_id) user_mention = f"<@{slack_user_id}>" issue_url = f"{self._jira_url}/browse/{issue_key}" message = ( f"<{issue_url}|{issue_key}> is marked as *Resolved* but is missing *{missing_field}*.\n" f"CC: {user_mention}" ) self._slack_client.chat_postMessage(channel=self._slack_channel, text=message) print(message) def report_tickets(self, query_function: Callable[[str], str], message: str): issues = self.fetch_jira_issues(query_function(self._jira_project_key)) for issue in issues: issue_key = issue.key assignee = issue.fields.assignee slack_user_id = ( self.get_slack_user_id_by_email(assignee.emailAddress) if assignee else None ) self.post_to_slack(issue_key, slack_user_id, message) def report_tickets_missing_fix_version(self): self.report_tickets( self.get_jira_query_for_issues_missing_fix_version, "Fix Version" ) def report_tickets_missing_release_number_affected(self): self.report_tickets( self.get_jira_query_for_issues_missing_release_number_affected, "Release number affected", ) def report_tickets_missing_fix_version_and_release_number_affected(self): self.report_tickets( self.get_jira_query_for_issues_missing_fix_version_and_release_number_affected, "Fix Version and Release number affected", ) # Get attribute values from the environment JIRA_URL = os.environ["JIRA_URL"] JIRA_PERSONAL_ACCESS_TOKEN = os.environ["JIRA_PERSONAL_ACCESS_TOKEN"] JIRA_PROJECT_KEY = os.environ["JIRA_PROJECT_KEY"] SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] SLACK_CHANNEL = os.environ["SLACK_CHANNEL"] # Instantiate JiraTicketsReporter jiraTicketsReporter = JiraTicketsReporter( jira_url=JIRA_URL, jira_token=JIRA_PERSONAL_ACCESS_TOKEN, jira_project_key=JIRA_PROJECT_KEY, slack_token=SLACK_BOT_TOKEN, slack_channel_id=SLACK_CHANNEL, ) # Report issues jiraTicketsReporter.report_tickets_missing_fix_version() jiraTicketsReporter.report_tickets_missing_release_number_affected() jiraTicketsReporter.report_tickets_missing_fix_version_and_release_number_affected() sdk-10.11.0/automation/requirements.txt000066400000000000000000000000751516266226600201150ustar00rootroot00000000000000jira python-gitlab slack_sdk github3.py atlassian-python-api sdk-10.11.0/bindings/000077500000000000000000000000001516266226600142445ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/000077500000000000000000000000001516266226600150365ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/.gitignore000066400000000000000000000005321516266226600170260ustar00rootroot00000000000000 ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ### Xcode Patch ### *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata /*.gcno sdk-10.11.0/bindings/ios/3rdparty/000077500000000000000000000000001516266226600166065ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/3rdparty/.gitignore000066400000000000000000000000261516266226600205740ustar00rootroot00000000000000/webrtc /xcframework sdk-10.11.0/bindings/ios/3rdparty/build-all.sh000066400000000000000000000004251516266226600210100ustar00rootroot00000000000000#!/bin/sh set -e # MEGA SDK deps sh build-cryptopp.sh sh build-openssl.sh sh build-cares.sh sh build-curl.sh sh build-libuv.sh sh build-libsodium.sh sh build-mediainfolib.sh # MEGAchat deps if [ "$1" == "--enable-chat" ]; then sh build-libwebsockets.sh fi echo "Done." sdk-10.11.0/bindings/ios/3rdparty/build-cryptopp.sh000077500000000000000000000112311516266226600221200ustar00rootroot00000000000000#!/bin/sh CRYPTOPP_VERSION="b6806f47e9fef7556689e7d3e5a458950acd3365" source common.sh set -e # Build libcryptopp for a specific architecture and platform build_arch_platform() { ARCH="$1" PLATFORM="$2" CATALYST="${3:-false}" rm -rf "${CRYPTOPP_VERSION}" tar zxf "${CRYPTOPP_VERSION}.tar.gz" pushd "cryptopp-${CRYPTOPP_VERSION}" if [[ "$ARCH" = "arm64" && ("$PLATFORM" == "iPhoneSimulator" || "$PLATFORM" == "iPhoneOS") ]]; then sed -i '' $'75s/DEF_CPPFLAGS=\"-DNDEBUG\"/DEF_CPPFLAGS=\"-DNDEBUG -DCRYPTOPP_DISABLE_ARM_CRC32\"/' TestScripts/setenv-ios.sh fi if [ "${CATALYST}" == "true" ]; then source TestScripts/setenv-macos.sh ${ARCH} MACOS_CATALYST=1 mkdir -p "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-catalyst-${ARCH}.sdk" elif [ "${PLATFORM}" == "MacOSX" ]; then source TestScripts/setenv-macos.sh ${ARCH} mkdir -p "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-${ARCH}.sdk" else source TestScripts/setenv-ios.sh ${PLATFORM} ${ARCH} mkdir -p "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-${ARCH}.sdk" fi if [ "$PLATFORM" == "MacOSX" ]; then make -f GNUmakefile-cross static -j${CORES} else make -f GNUmakefile-cross lean -j${CORES} fi if [ "${CATALYST}" == "true" ]; then mv libcryptopp.a "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-catalyst-${ARCH}.sdk" mkdir -p "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-catalyst-${ARCH}.sdk/include/cryptopp" cp -f *.h "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-catalyst-${ARCH}.sdk/include/cryptopp" else mv libcryptopp.a "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-${ARCH}.sdk" mkdir -p "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-${ARCH}.sdk/include/cryptopp" cp -f *.h "${CURRENTPATH}/bin/libcryptopp/${PLATFORM}-${ARCH}.sdk/include/cryptopp" fi make clean popd } # Build Catalyst (macOS) targets for arm64 and x86_64 build_catalyst() { build_arch_platform "x86_64" "MacOSX" true build_arch_platform "arm64" "MacOSX" true echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/libcryptopp/catalyst" lipo -create "${CURRENTPATH}/bin/libcryptopp/MacOSX-catalyst-x86_64.sdk/libcryptopp.a" "${CURRENTPATH}/bin/libcryptopp/MacOSX-catalyst-arm64.sdk/libcryptopp.a" -output "${CURRENTPATH}/bin/libcryptopp/catalyst/libcryptopp.a" } # Build macOS targets for arm64 and x86_64 build_mac() { build_arch_platform "x86_64" "MacOSX" build_arch_platform "arm64" "MacOSX" echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/libcryptopp/mac" lipo -create "${CURRENTPATH}/bin/libcryptopp/MacOSX-x86_64.sdk/libcryptopp.a" "${CURRENTPATH}/bin/libcryptopp/MacOSX-arm64.sdk/libcryptopp.a" -output "${CURRENTPATH}/bin/libcryptopp/mac/libcryptopp.a" } # Build iOS target for arm64 build_iOS() { build_arch_platform "arm64" "iPhoneOS" } # Build iOS Simulator targets for arm64 and x86_64 build_iOS_simulator() { build_arch_platform "arm64" "iPhoneSimulator" build_arch_platform "x86_64" "iPhoneSimulator" echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" mkdir -p "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator" lipo -create "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator-x86_64.sdk/libcryptopp.a" "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator-arm64.sdk/libcryptopp.a" -output "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator/libcryptopp.a" } create_XCFramework() { mkdir -p xcframework || true echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator/libcryptopp.a" \ -headers "${CURRENTPATH}/bin/libcryptopp/iPhoneSimulator-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcryptopp/iPhoneOS-arm64.sdk/libcryptopp.a" \ -headers "${CURRENTPATH}/bin/libcryptopp/iPhoneOS-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcryptopp/catalyst/libcryptopp.a" \ -headers "${CURRENTPATH}/bin/libcryptopp/MacOSX-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcryptopp/mac/libcryptopp.a" \ -headers "${CURRENTPATH}/bin/libcryptopp/MacOSX-arm64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libcryptopp.xcframework" } clean_up() { echo "${bold}Cleaning up ${normal}" rm -rf "${CRYPTOPP_VERSION}.tar.gz" rm -rf "cryptopp-${CRYPTOPP_VERSION}" rm -rf bin echo "${bold}Done.${normal}" } # Main build process main() { if [ ! -e "${CRYPTOPP_VERSION}.tar.gz" ]; then curl -LO "https://github.com/jnavarrom/cryptopp/archive/${CRYPTOPP_VERSION}.tar.gz" fi build_mac build_catalyst build_iOS build_iOS_simulator create_XCFramework clean_up } # Run the main build process main sdk-10.11.0/bindings/ios/3rdparty/build-curl.sh000066400000000000000000000125731516266226600212140ustar00rootroot00000000000000#!/bin/bash CURL_VERSION="8.1.2" source common.sh # Build libcurl for a specific architecture and platform build_arch_platform() { ARCH="$1" PLATFORM="$2" CATALYST="${3:-false}" rm -rf "curl-${CURL_VERSION}" tar zxf "curl-${CURL_VERSION}.tar.gz" pushd "curl-${CURL_VERSION}" export BUILD_TOOLS="${DEVELOPER}" export BUILD_DEVROOT="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer" export BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}${SDKVERSION}.sdk" RUNTARGET="" PREFIX="" if [ "${CATALYST}" == "true" ]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-macabi" PREFIX="${CURRENTPATH}/bin/libcurl/${PLATFORM}${SDKVERSION}-catalyst-${ARCH}.sdk" else PREFIX="${CURRENTPATH}/bin/libcurl/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" fi if [ "${PLATFORM}" == "MacOSX" ]; then BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}.sdk" fi if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneSimulator" ]]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-simulator" fi echo "${bold}Building CURL for $PLATFORM (catalyst=$CATALYST) $ARCH $BUILD_SDKROOT ${normal}" export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}" mkdir -p "${PREFIX}" if [[ "${CATALYST}" == "true" || "${PLATFORM}" == "iPhoneOS" || "${PLATFORM}" == "iPhoneSimulator" ]]; then export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -miphoneos-version-min=15.0" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -miphoneos-version-min=15.0 ${RUNTARGET}" export CPPFLAGS="${CFLAGS} -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" else #macOS export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -mmacosx-version-min=10.15 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -mmacosx-version-min=10.15" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" fi if [ "${ARCH}" == "arm64" ]; then HOST="arm-apple-darwin" else HOST="${ARCH}-apple-darwin" fi ./configure --prefix="${PREFIX}" --host=${HOST} --enable-static --disable-shared --with-secure-transport --with-zlib --disable-manual --disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smtp --disable-gopher --disable-sspi --enable-ipv6 --disable-smb --without-brotli --without-zstd make -j${CORES} make install make clean popd } # Build Catalyst (macOS) targets for arm64 and x86_64 build_catalyst() { build_arch_platform "arm64" "MacOSX" true build_arch_platform "x86_64" "MacOSX" true echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/libcurl/catalyst" lipo -create "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-catalyst-x86_64.sdk/lib/libcurl.a" "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-catalyst-arm64.sdk/lib/libcurl.a" -output "${CURRENTPATH}/bin/libcurl/catalyst/libcurl.a" } # Build macOS targets for arm64 and x86_64 build_mac() { build_arch_platform "arm64" "MacOSX" build_arch_platform "x86_64" "MacOSX" echo "${bold}Lipo library for x86_64 and arm64 mac ${normal}" mkdir -p "${CURRENTPATH}/bin/libcurl/mac" lipo -create "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-x86_64.sdk/lib/libcurl.a" "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-arm64.sdk/lib/libcurl.a" -output "${CURRENTPATH}/bin/libcurl/mac/libcurl.a" } # Build iOS target for arm64 build_iOS() { build_arch_platform "arm64" "iPhoneOS" } # Build iOS Simulator targets for arm64 and x86_64 build_iOS_simulator() { build_arch_platform "arm64" "iPhoneSimulator" build_arch_platform "x86_64" "iPhoneSimulator" echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" mkdir -p "${CURRENTPATH}/bin/libcurl/iPhoneSimulator" lipo -create "${CURRENTPATH}/bin/libcurl/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libcurl.a" "${CURRENTPATH}/bin/libcurl/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libcurl.a" -output "${CURRENTPATH}/bin/libcurl/iPhoneSimulator/libcurl.a" } create_XCFramework() { mkdir -p xcframework || true echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/libcurl/iPhoneSimulator/libcurl.a" \ -headers "${CURRENTPATH}/bin/libcurl/iPhoneSimulator${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcurl/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libcurl.a" \ -headers "${CURRENTPATH}/bin/libcurl/iPhoneOS${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcurl/catalyst/libcurl.a" \ -headers "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libcurl/mac/libcurl.a" \ -headers "${CURRENTPATH}/bin/libcurl/MacOSX${SDKVERSION}-x86_64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libcurl.xcframework" } clean_up() { echo "${bold}Cleaning up ${normal}" rm -rf "curl-${CURL_VERSION}" rm -rf "curl-${CURL_VERSION}.tar.gz" rm -rf bin echo "${bold}Done.${normal}" } # Main build process main() { check_xcode_path check_for_spaces if [ ! -e "curl-${CURL_VERSION}.tar.gz" ]; then curl -LO "https://curl.haxx.se/download/curl-${CURL_VERSION}.tar.gz" fi build_mac build_catalyst build_iOS build_iOS_simulator create_XCFramework clean_up } # Run the main build process main sdk-10.11.0/bindings/ios/3rdparty/build-libsodium.sh000066400000000000000000000125151516266226600222320ustar00rootroot00000000000000#!/bin/sh LIBSODIUM_VERSION="1.0.16" source common.sh # Build libsodium for a specific architecture and platform build_arch_platform() { ARCH="$1" PLATFORM="$2" CATALYST="${3:-false}" rm -rf "libsodium-${LIBSODIUM_VERSION}" tar zxf "libsodium-${LIBSODIUM_VERSION}.tar.gz" pushd "libsodium-${LIBSODIUM_VERSION}" export BUILD_TOOLS="${DEVELOPER}" export BUILD_DEVROOT="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer" export BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}${SDKVERSION}.sdk" RUNTARGET="" PREFIX="" if [ "${CATALYST}" == "true" ]; then PREFIX="${CURRENTPATH}/bin/libsodium/${PLATFORM}${SDKVERSION}-catalyst-${ARCH}.sdk" RUNTARGET="-target ${ARCH}-apple-ios15.0-macabi" else PREFIX="${CURRENTPATH}/bin/libsodium/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" fi if [ "${PLATFORM}" == "MacOSX" ]; then BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}.sdk" fi if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneSimulator" ]]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-simulator" fi echo "${bold}Building libsodium for $PLATFORM (catalyst=$CATALYST) $ARCH $BUILD_SDKROOT ${normal}" export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}" mkdir -p ${PREFIX} if [[ "${CATALYST}" == "true" || "${PLATFORM}" == "iPhoneOS" || "${PLATFORM}" == "iPhoneSimulator" ]]; then export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -miphoneos-version-min=15.0 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -miphoneos-version-min=15.0 -DNDEBUG ${RUNTARGET}" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include" export CXXFLAGS="${CPPFLAGS}" else # macOS export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -mmacosx-version-min=10.15 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -mmacosx-version-min=10.15 -DNDEBUG" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include" export CXXFLAGS="${CPPFLAGS}" fi if [ "${ARCH}" == "arm64" ]; then HOST=arm-apple-darwin else HOST=${ARCH}-apple-darwin fi ./configure --prefix=${PREFIX} --host=${HOST} --disable-shared --enable-minimal make -j${CORES} make install make clean popd } # Build Catalyst (macOS) targets for arm64 and x86_64 build_catalyst() { build_arch_platform "arm64" "MacOSX" true build_arch_platform "x86_64" "MacOSX" true echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/libsodium/catalyst" lipo -create "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-catalyst-x86_64.sdk/lib/libsodium.a" "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-catalyst-arm64.sdk/lib/libsodium.a" -output "${CURRENTPATH}/bin/libsodium/catalyst/libsodium.a" } # Build macOS targets for arm64 and x86_64 build_mac() { build_arch_platform "arm64" "MacOSX" build_arch_platform "x86_64" "MacOSX" echo "${bold}Lipo library for x86_64 and arm64 mac ${normal}" mkdir -p "${CURRENTPATH}/bin/libsodium/mac" lipo -create "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-x86_64.sdk/lib/libsodium.a" "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-arm64.sdk/lib/libsodium.a" -output "${CURRENTPATH}/bin/libsodium/mac/libsodium.a" } # Build iOS target for arm64 build_iOS() { build_arch_platform "arm64" "iPhoneOS" } # Build iOS Simulator targets for arm64 and x86_64 build_iOS_simulator() { build_arch_platform "arm64" "iPhoneSimulator" build_arch_platform "x86_64" "iPhoneSimulator" echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" mkdir -p "${CURRENTPATH}/bin/libsodium/iPhoneSimulator" lipo -create "${CURRENTPATH}/bin/libsodium/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libsodium.a" "${CURRENTPATH}/bin/libsodium/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libsodium.a" -output "${CURRENTPATH}/bin/libsodium/iPhoneSimulator/libsodium.a" } create_XCFramework() { mkdir -p xcframework || true echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/libsodium/iPhoneSimulator/libsodium.a" \ -headers "${CURRENTPATH}/bin/libsodium/iPhoneSimulator${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libsodium/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libsodium.a" \ -headers "${CURRENTPATH}/bin/libsodium/iPhoneOS${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libsodium/catalyst/libsodium.a" \ -headers "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libsodium/mac/libsodium.a" \ -headers "${CURRENTPATH}/bin/libsodium/MacOSX${SDKVERSION}-arm64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libsodium.xcframework" } clean_up() { echo "${bold}Cleaning up ${normal}" rm -rf bin rm -rf "libsodium-${LIBSODIUM_VERSION}" rm -rf "libsodium-${LIBSODIUM_VERSION}.tar.gz" echo "${bold}Done.${normal}" } # Main build process main() { check_xcode_path check_for_spaces if [ ! -e "libsodium-${LIBSODIUM_VERSION}.tar.gz" ]; then curl -LO "https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VERSION}/libsodium-${LIBSODIUM_VERSION}.tar.gz" fi build_mac build_catalyst build_iOS build_iOS_simulator create_XCFramework clean_up } # Run the main build process main sdk-10.11.0/bindings/ios/3rdparty/build-libuv.sh000066400000000000000000000120751516266226600213650ustar00rootroot00000000000000#!/bin/sh UV_VERSION="1.45.0" source common.sh # Build libuv for a specific architecture and platform build_arch_platform() { ARCH="$1" PLATFORM="$2" CATALYST="${3:-false}" rm -rf libuv-v${UV_VERSION} tar zxf libuv-v${UV_VERSION}.tar.gz pushd "libuv-v${UV_VERSION}" export BUILD_TOOLS="${DEVELOPER}" export BUILD_DEVROOT="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer" export BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}${SDKVERSION}.sdk" RUNTARGET="" PREFIX="" if [ "${CATALYST}" == "true" ]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-macabi" PREFIX="${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-catalyst-${ARCH}.sdk" else PREFIX="${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" fi if [ "${PLATFORM}" == "MacOSX" ]; then BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}.sdk" fi if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneSimulator" ]]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-simulator" fi echo "${bold}Building libuv for $PLATFORM (catalyst=$CATALYST) $ARCH $BUILD_SDKROOT ${normal}" export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}" mkdir -p ${PREFIX} if [[ "${CATALYST}" == "true" || "${PLATFORM}" == "iPhoneOS" || "${PLATFORM}" == "iPhoneSimulator" ]]; then export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -miphoneos-version-min=15.0" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -miphoneos-version-min=15.0 ${RUNTARGET}" export CPPFLAGS="${CFLAGS} -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" else # macOS export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -mmacosx-version-min=10.15 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -mmacosx-version-min=10.15 -DNDEBUG" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include" export CXXFLAGS="${CPPFLAGS}" fi sh autogen.sh if [ "${ARCH}" == "arm64" ]; then HOST="arm-apple-darwin" else HOST="${ARCH}-apple-darwin" fi ./configure --prefix=${PREFIX} --host=${HOST} --enable-static --disable-shared make -j${CORES} make install make clean popd } # Build Catalyst (macOS) targets for arm64 and x86_64 build_catalyst() { build_arch_platform "arm64" "MacOSX" true build_arch_platform "x86_64" "MacOSX" true echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/libuv/catalyst" lipo -create "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-catalyst-x86_64.sdk/lib/libuv.a" "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-catalyst-arm64.sdk/lib/libuv.a" -output "${CURRENTPATH}/bin/libuv/catalyst/libuv.a" } # Build macOS targets for arm64 and x86_64 build_mac() { build_arch_platform "arm64" "MacOSX" build_arch_platform "x86_64" "MacOSX" echo "${bold}Lipo library for x86_64 and arm64 mac ${normal}" mkdir -p "${CURRENTPATH}/bin/libuv/mac" lipo -create "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-x86_64.sdk/lib/libuv.a" "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-arm64.sdk/lib/libuv.a" -output "${CURRENTPATH}/bin/libuv/mac/libuv.a" } # Build iOS target for arm64 build_iOS() { build_arch_platform "arm64" "iPhoneOS" } # Build iOS Simulator targets for arm64 and x86_64 build_iOS_simulator() { build_arch_platform "arm64" "iPhoneSimulator" build_arch_platform "x86_64" "iPhoneSimulator" echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" mkdir -p "${CURRENTPATH}/bin/libuv/iPhoneSimulator" lipo -create "${CURRENTPATH}/bin/libuv/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libuv.a" "${CURRENTPATH}/bin/libuv/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libuv.a" -output "${CURRENTPATH}/bin/libuv/iPhoneSimulator/libuv.a" } create_XCFramework() { mkdir -p xcframework || true echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/libuv/iPhoneSimulator/libuv.a" \ -headers "${CURRENTPATH}/bin/libuv/iPhoneSimulator${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libuv/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libuv.a" \ -headers "${CURRENTPATH}/bin/libuv/iPhoneOS${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libuv/catalyst/libuv.a" \ -headers "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/libuv/mac/libuv.a" \ -headers "${CURRENTPATH}/bin/libuv/MacOSX${SDKVERSION}-arm64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libuv.xcframework" } clean_up() { echo "${bold}Cleaning up ${normal}" rm -rf bin rm -rf libuv-v${UV_VERSION} rm -rf libuv-v${UV_VERSION}.tar.gz echo "${bold}Done.${normal}" } download_libuv() { if [ ! -e "libuv-v${UV_VERSION}.tar.gz" ]; then curl -LO "http://dist.libuv.org/dist/v${UV_VERSION}/libuv-v${UV_VERSION}.tar.gz" fi } # Main build process main() { check_xcode_path check_for_spaces download_libuv build_mac build_catalyst build_iOS build_iOS_simulator create_XCFramework clean_up } # Run the main build process main sdk-10.11.0/bindings/ios/3rdparty/build-libwebsockets.sh000066400000000000000000000131531516266226600231020ustar00rootroot00000000000000#!/bin/sh MEGACHAT_VERSION="GIT" SDKVERSION=`xcrun -sdk iphoneos --show-sdk-version` ############################################## CURRENTPATH=`pwd` ARCHS="x86_64 arm64 arm64-simulator" DEVELOPER=`xcode-select -print-path` CORES=$(sysctl -n hw.ncpu) # Formating green="\033[32m" bold="\033[0m${green}\033[1m" normal="\033[0m" if [ ! -d "$DEVELOPER" ]; then echo "xcode path is not set correctly $DEVELOPER does not exist (most likely because of xcode > 4.3)" echo "run" echo "sudo xcode-select -switch " echo "for default installation:" echo "sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer" exit 1 fi case $DEVELOPER in *\ * ) echo "Your Xcode path contains whitespaces, which is not supported." exit 1 ;; esac case $CURRENTPATH in *\ * ) echo "Your path contains whitespaces, which is not supported by 'make install'." exit 1 ;; esac set -e if [ ! -d "libwebsockets" ] then git clone -b v4.2-stable https://github.com/warmcat/libwebsockets.git fi for ARCH in ${ARCHS} do if [[ "${ARCH}" == "x86_64" || "${ARCH}" == "arm64-simulator" ]]; then PLATFORM="iPhoneSimulator" if [ "${ARCH}" == "arm64-simulator" ]; then ARCH="arm64" fi else PLATFORM="iPhoneOS" fi echo "${bold}Building libwebsockets for $PLATFORM $ARCH ${normal}" IOSC_TARGET=iphoneos IOSC_OS_VERSION=-mios-version-min=14.0 IOSC_ARCH=${ARCH} IOSC_PLATFORM_SDKNAME=${PLATFORM} IOSC_SYSROOT=`xcrun -sdk $IOSC_TARGET -show-sdk-path` # the same as SDKROOT pushd libwebsockets git reset --hard && git clean -dfx export BUILD_TOOLS="${DEVELOPER}" export BUILD_DEVROOT="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer" export BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}${SDKVERSION}.sdk" RUNTARGET="" if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneSimulator" ]]; then RUNTARGET="-target ${ARCH}-apple-ios14.0-simulator" fi export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}" mkdir -p "${CURRENTPATH}/bin/libwebsockets/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" # Build export LDFLAGS="-arch ${ARCH} -Wl,-dead_strip -miphoneos-version-min=14.0 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -miphoneos-version-min=14.0 -g3 ${RUNTARGET}" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneOS" ]]; then cmake . -DCMAKE_INSTALL_PREFIX=${CURRENTPATH}/bin/libwebsockets/${PLATFORM}${SDKVERSION}-${ARCH}.sdk -DCMAKE_TOOLCHAIN_FILE=${CURRENTPATH}/libwebsockets/contrib/iOS.cmake -DIOS_PLATFORM=OS -DBUILD_ARM64=1 -DLWS_OPENSSL_INCLUDE_DIRS=${CURRENTPATH}/webrtc/third_party/boringssl/src/include -DLWS_OPENSSL_LIBRARIES=${CURRENTPATH}/lib/libwebrtc.xcframework/ios-arm64/libwebrtc.a -DLWS_WITH_LIBUV=1 -DLIBUV_INCLUDE_DIRS=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/include -DLIBUV_LIBRARIES=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/lib/liuv.a -DLWS_IPV6=ON -DLWS_SSL_CLIENT_USE_OS_CA_CERTS=0 -DLWS_WITH_SHARED=OFF -DLWS_WITHOUT_TESTAPPS=ON -DLWS_WITHOUT_SERVER=ON -DLWS_WITH_HTTP2=0 -DLWS_WITH_BORINGSSL=1 -DIOS_BITCODE=0 elif [ "${ARCH}" == "x86_64" ]; then cmake . -DCMAKE_INSTALL_PREFIX=${CURRENTPATH}/bin/libwebsockets/${PLATFORM}${SDKVERSION}-${ARCH}.sdk -DCMAKE_TOOLCHAIN_FILE=${CURRENTPATH}/libwebsockets/contrib/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -DLWS_OPENSSL_INCLUDE_DIRS=${CURRENTPATH}/webrtc/third_party/boringssl/src/include -DLWS_OPENSSL_LIBRARIES=${CURRENTPATH}/lib/libwebrtc.xcframework/ios-arm64_x86_64-simulator/libwebrtc.a -DLWS_WITH_LIBUV=1 -DLIBUV_INCLUDE_DIRS=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/include -DLIBUV_LIBRARIES=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/lib/liuv.a -DLWS_IPV6=ON -DLWS_SSL_CLIENT_USE_OS_CA_CERTS=0 -DLWS_WITH_SHARED=OFF -DLWS_WITHOUT_TESTAPPS=ON -DLWS_WITHOUT_SERVER=ON -DLWS_WITH_HTTP2=0 -DLWS_WITH_BORINGSSL=1 -DIOS_BITCODE=0 else cmake . -DCMAKE_INSTALL_PREFIX=${CURRENTPATH}/bin/libwebsockets/${PLATFORM}${SDKVERSION}-${ARCH}.sdk -DCMAKE_TOOLCHAIN_FILE=${CURRENTPATH}/libwebsockets/contrib/iOS.cmake -DIOS_PLATFORM=OS -DBUILD_ARM64=1 -DLWS_OPENSSL_INCLUDE_DIRS=${CURRENTPATH}/webrtc/third_party/boringssl/src/include -DLWS_OPENSSL_LIBRARIES=${CURRENTPATH}/lib/libwebrtc.xcframework/ios-arm64_x86_64-simulator/libwebrtc.a -DLWS_WITH_LIBUV=1 -DLIBUV_INCLUDE_DIRS=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/include -DLIBUV_LIBRARIES=${CURRENTPATH}/bin/libuv/${PLATFORM}${SDKVERSION}-${ARCH}.sdk/lib/liuv.a -DLWS_IPV6=ON -DLWS_SSL_CLIENT_USE_OS_CA_CERTS=0 -DLWS_WITH_SHARED=OFF -DLWS_WITHOUT_TESTAPPS=ON -DLWS_WITHOUT_SERVER=ON -DLWS_WITH_HTTP2=0 -DLWS_WITH_BORINGSSL=1 -DIOS_BITCODE=0 fi make -j${CORES} make install make clean popd done mkdir xcframework || true echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" lipo -create ${CURRENTPATH}/bin/libwebsockets/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libwebsockets.a ${CURRENTPATH}/bin/libwebsockets/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libwebsockets.a -output ${CURRENTPATH}/bin/libwebsockets/libwebsockets.a echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework -library ${CURRENTPATH}/bin/libwebsockets/libwebsockets.a -headers ${CURRENTPATH}/bin/libwebsockets/iPhoneSimulator${SDKVERSION}-arm64.sdk/include -library ${CURRENTPATH}/bin/libwebsockets/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libwebsockets.a -headers ${CURRENTPATH}/bin/libwebsockets/iPhoneOS${SDKVERSION}-arm64.sdk/include -output ${CURRENTPATH}/xcframework/libwebsockets.xcframework rm -fr bin rm -fr libwebsockets echo "${bold}Done.${normal}" sdk-10.11.0/bindings/ios/3rdparty/build-mediainfolib.sh000066400000000000000000000215531516266226600226670ustar00rootroot00000000000000#!/bin/sh ZENLIB_VERSION="6694a744d82d942c4a410f25f916561270381889" MEDIAINFO_VERSION="4ee7f77c087b29055f48d539cd679de8de6f9c48" source common.sh # Build zenlib and mediainfo for a specific architecture and platform build_arch_platform() { ARCH="$1" PLATFORM="$2" CATALYST="${3:-false}" rm -rf ZenLib-${ZENLIB_VERSION} tar zxf ${ZENLIB_VERSION}.tar.gz pushd "ZenLib-${ZENLIB_VERSION}/Project/GNU/Library" export BUILD_TOOLS="${DEVELOPER}" export BUILD_DEVROOT="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer" export BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}${SDKVERSION}.sdk" RUNTARGET="" PREFIX_MEDIAINFO="" PREFIX_ZENLIB="" if [ "${CATALYST}" == "true" ]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-macabi" PREFIX_ZENLIB="${CURRENTPATH}/bin/zenlib/${PLATFORM}${SDKVERSION}-catalyst-${ARCH}.sdk" PREFIX_MEDIAINFO="${CURRENTPATH}/bin/mediainfo/${PLATFORM}${SDKVERSION}-catalyst-${ARCH}.sdk" else PREFIX_ZENLIB="${CURRENTPATH}/bin/zenlib/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" PREFIX_MEDIAINFO="${CURRENTPATH}/bin/mediainfo/${PLATFORM}${SDKVERSION}-${ARCH}.sdk" fi if [ "${PLATFORM}" == "MacOSX" ]; then BUILD_SDKROOT="${BUILD_DEVROOT}/SDKs/${PLATFORM}.sdk" fi if [[ "${ARCH}" == "arm64" && "$PLATFORM" == "iPhoneSimulator" ]]; then RUNTARGET="-target ${ARCH}-apple-ios15.0-simulator" fi echo "${bold}Building zenlib for $PLATFORM (catalyst=$CATALYST) $ARCH $BUILD_SDKROOT ${normal}" export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}" mkdir -p ${PREFIX_ZENLIB} mkdir -p ${PREFIX_MEDIAINFO} # Build if [[ "${CATALYST}" == "true" || "${PLATFORM}" == "iPhoneOS" || "${PLATFORM}" == "iPhoneSimulator" ]]; then export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -miphoneos-version-min=15.0" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -miphoneos-version-min=15.0 -DMEDIAINFO_ADVANCED_NO ${RUNTARGET}" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" else # macOS export LDFLAGS="-Os -arch ${ARCH} -Wl,-dead_strip -mmacosx-version-min=10.15 -L${BUILD_SDKROOT}/usr/lib" export CFLAGS="-Os -arch ${ARCH} -pipe -no-cpp-precomp -isysroot ${BUILD_SDKROOT} -mmacosx-version-min=10.15 -DMEDIAINFO_ADVANCED_NO" export CPPFLAGS="${CFLAGS} -I${BUILD_SDKROOT}/usr/include -DNDEBUG" export CXXFLAGS="${CPPFLAGS}" fi sh autogen.sh if [ "${ARCH}" == "arm64" ]; then HOST="arm-apple-darwin" else HOST="${ARCH}-apple-darwin" fi ./configure --prefix=${PREFIX_ZENLIB} --host=${HOST} --disable-shared --disable-archive make -j${CORES} make install popd rm -rf ZenLib mv ZenLib-${ZENLIB_VERSION} ZenLib rm -rf MediaInfoLib-${MEDIAINFO_VERSION} tar zxf ${MEDIAINFO_VERSION}.tar.gz pushd "MediaInfoLib-${MEDIAINFO_VERSION}/Project/GNU/Library" echo "${bold}Building mediainfo for $PLATFORM (catalyst=$CATALYST) $ARCH $BUILD_SDKROOT ${normal}" sh autogen.sh ./configure --prefix=${PREFIX_MEDIAINFO} \ --host=${HOST} --disable-shared --enable-minimize-size --enable-minimal --disable-archive \ --disable-image --disable-tag --disable-text --disable-swf --disable-flv --disable-hdsf4m \ --disable-cdxa --disable-dpg --disable-pmp --disable-rm --disable-wtv --disable-mxf \ --disable-dcp --disable-aaf --disable-bdav --disable-bdmv --disable-dvdv --disable-gxf \ --disable-mixml --disable-skm --disable-nut --disable-tsp --disable-hls --disable-dxw \ --disable-dvdif --disable-dashmpd --disable-aic --disable-avsv --disable-canopus \ --disable-ffv1 --disable-flic --disable-huffyuv --disable-prores --disable-y4m \ --disable-adpcm --disable-amr --disable-amv --disable-ape --disable-au --disable-la \ --disable-celt --disable-midi --disable-mpc --disable-openmg --disable-pcm --disable-ps2a \ --disable-rkau --disable-speex --disable-tak --disable-tta --disable-twinvq --disable-references make -j${CORES} make install make clean popd } # Build Catalyst (macOS) targets for arm64 and x86_64 build_catalyst() { build_arch_platform "arm64" "MacOSX" true build_arch_platform "x86_64" "MacOSX" true echo "${bold}Lipo library for x86_64 and arm64 catalyst ${normal}" mkdir -p "${CURRENTPATH}/bin/zenlib/catalyst" mkdir -p "${CURRENTPATH}/bin/mediainfo/catalyst" lipo -create "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-catalyst-x86_64.sdk/lib/libzen.a" \ "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-catalyst-arm64.sdk/lib/libzen.a" \ -output "${CURRENTPATH}/bin/zenlib/catalyst/libzen.a" lipo -create "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-catalyst-x86_64.sdk/lib/libmediainfo.a" \ "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-catalyst-arm64.sdk/lib/libmediainfo.a" \ -output "${CURRENTPATH}/bin/mediainfo/catalyst/libmediainfo.a" } # Build macOS targets for arm64 and x86_64 build_mac() { build_arch_platform "arm64" "MacOSX" build_arch_platform "x86_64" "MacOSX" echo "${bold}Lipo library for x86_64 and arm64 mac ${normal}" mkdir -p "${CURRENTPATH}/bin/zenlib/mac" mkdir -p "${CURRENTPATH}/bin/mediainfo/mac" lipo -create "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-x86_64.sdk/lib/libzen.a" \ "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-arm64.sdk/lib/libzen.a" \ -output "${CURRENTPATH}/bin/zenlib/mac/libzen.a" lipo -create "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-x86_64.sdk/lib/libmediainfo.a" \ "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-arm64.sdk/lib/libmediainfo.a" \ -output "${CURRENTPATH}/bin/mediainfo/mac/libmediainfo.a" } # Build iOS target for arm64 build_iOS() { build_arch_platform "arm64" "iPhoneOS" } # Build iOS Simulator targets for arm64 and x86_64 build_iOS_simulator() { build_arch_platform "arm64" "iPhoneSimulator" build_arch_platform "x86_64" "iPhoneSimulator" echo "${bold}Lipo library for x86_64 and arm64 simulators ${normal}" mkdir -p "${CURRENTPATH}/bin/zenlib/iPhoneSimulator" mkdir -p "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator" lipo -create "${CURRENTPATH}/bin/zenlib/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libzen.a" \ "${CURRENTPATH}/bin/zenlib/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libzen.a" \ -output "${CURRENTPATH}/bin/zenlib/iPhoneSimulator/libzen.a" lipo -create "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator${SDKVERSION}-x86_64.sdk/lib/libmediainfo.a" \ "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator${SDKVERSION}-arm64.sdk/lib/libmediainfo.a" \ -output "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator/libmediainfo.a" } create_XCFramework() { mkdir -p xcframework || true echo "${bold}Creating xcframework ${normal}" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/zenlib/iPhoneSimulator/libzen.a" \ -headers "${CURRENTPATH}/bin/zenlib/iPhoneSimulator${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/zenlib/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libzen.a" \ -headers "${CURRENTPATH}/bin/zenlib/iPhoneOS${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/zenlib/catalyst/libzen.a" \ -headers "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/zenlib/mac/libzen.a" \ -headers "${CURRENTPATH}/bin/zenlib/MacOSX${SDKVERSION}-arm64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libzen.xcframework" xcodebuild -create-xcframework \ -library "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator/libmediainfo.a" \ -headers "${CURRENTPATH}/bin/mediainfo/iPhoneSimulator${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/mediainfo/iPhoneOS${SDKVERSION}-arm64.sdk/lib/libmediainfo.a" \ -headers "${CURRENTPATH}/bin/mediainfo/iPhoneOS${SDKVERSION}-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/mediainfo/catalyst/libmediainfo.a" \ -headers "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-catalyst-arm64.sdk/include" \ -library "${CURRENTPATH}/bin/mediainfo/mac/libmediainfo.a" \ -headers "${CURRENTPATH}/bin/mediainfo/MacOSX${SDKVERSION}-arm64.sdk/include" \ -output "${CURRENTPATH}/xcframework/libmediainfo.xcframework" } clean_up() { echo "${bold}Cleaning up ${normal}" rm -rf bin rm -rf MediaInfoLib-${MEDIAINFO_VERSION} rm -rf ZenLib rm -rf ${ZENLIB_VERSION}.tar.gz rm -rf ${MEDIAINFO_VERSION}.tar.gz echo "${bold}Done.${normal}" } download_zenlib() { if [ ! -e "${ZENLIB_VERSION}.tar.gz" ]; then curl -LO "https://github.com/MediaArea/ZenLib/archive/${ZENLIB_VERSION}.tar.gz" fi } download_mediainfo() { if [ ! -e "${MEDIAINFO_VERSION}.tar.gz" ]; then curl -LO "https://github.com/meganz/MediaInfoLib/archive/${MEDIAINFO_VERSION}.tar.gz" fi } # Main build process main() { check_xcode_path check_for_spaces download_zenlib download_mediainfo build_mac build_catalyst build_iOS build_iOS_simulator create_XCFramework clean_up } # Run the main build process main sdk-10.11.0/bindings/ios/3rdparty/build-webrtc.sh000077500000000000000000000175061516266226600215410ustar00rootroot00000000000000#!/bin/sh set -e COMMIT=79aff54b0fa9238ce3518dd9eaf9610cd6f22e82 ARCHS="arm64 arm64-simulator" LIBWEBRTC_A="libwebrtc.a" ADDITIONAL_LIBS="libnative_api.a libnative_video.a libvideocapture_objc.a libvideoframebuffer_objc.a" FOLDERS_TO_REMOVE="src crypto buildtools tools test out resources tools_webrtc ios testing audio build_overrides examples depot_tools lib rtc_tools build data style-guide base stats common_audio third_party/blink third_party/libjpeg_turbo third_party/ply third_party/hamcrest third_party/iccjpeg third_party/yasm third_party/libjingle_xmpp third_party/arcore-android-sdk third_party/ink third_party/ffmpeg third_party/gif_player third_party/android_platform third_party/webxr_test_pages third_party/pyjson5 third_party/crc32c third_party/mozilla third_party/netty-tcnative third_party/markupsafe third_party/checkstyle third_party/ow2_asm third_party/harfbuzz-ng third_party/llvm-build third_party/mocha third_party/pylint third_party/gvr-android-sdk third_party/shaderc third_party/qcms third_party/widevine third_party/libXNVCtrl third_party/libsrtp third_party/gson third_party/wayland-protocols third_party/blanketjs third_party/isimpledom third_party/libFuzzer third_party/libjpeg third_party/libpng third_party/android_media third_party/libxml third_party/instrumented_libraries third_party/libxslt third_party/mako third_party/jsoncpp third_party/libsync third_party/spirv-cross third_party/chromevox third_party/leveldatabase third_party/libaom third_party/opus third_party/freetype third_party/microsoft_webauthn third_party/ashmem third_party/tcmalloc third_party/grpc third_party/openxr third_party/guava third_party/crashpad third_party/wds third_party/fuchsia-sdk third_party/junit third_party/jinja2 third_party/openvr third_party/auto third_party/jsr-305 third_party/android_sdk third_party/expat third_party/icu4j third_party/jdk third_party/markdown third_party/brotli third_party/SPIRV-Tools third_party/objenesis third_party/decklink third_party/libaddressinput third_party/cacheinvalidation third_party/axe-core third_party/usb_ids third_party/colorama third_party/ub-uiautomator third_party/inspector_protocol third_party/pymock third_party/simplejson third_party/libdrm third_party/modp_b64 third_party/hunspell third_party/minizip third_party/android_crazy_linker third_party/apache-win32 third_party/webgl third_party/wtl third_party/polymer third_party/libevdev third_party/openscreen third_party/nasm third_party/libovr third_party/afl third_party/s2cellid third_party/binutils third_party/sfntly third_party/android_swipe_refresh third_party/android_deps third_party/netty4 third_party/cld_3 third_party/logilab third_party/sqlite third_party/r8 third_party/lcov third_party/minigbm third_party/google_input_tools third_party/flot third_party/google_android_play_core third_party/pystache third_party/zlib third_party/glslang third_party/apache-mac third_party/google_trust_services third_party/cct_dynamic_module third_party/tlslite third_party/android_support_test_runner third_party/fontconfig third_party/requests third_party/android_build_tools third_party/wayland third_party/jacoco third_party/errorprone third_party/apple_apsl third_party/libprotobuf-mutator third_party/adobe third_party/Python-Markdown third_party/javalang third_party/web-animations-js third_party/libwebp third_party/dom_distiller_js third_party/libwebm third_party/pywebsocket third_party/android_data_chart third_party/re2 third_party/pexpect third_party/libvpx third_party/libphonenumber third_party/sudden_motion_sensor third_party/snappy third_party/bouncycastle third_party/usrsctp third_party/win_build_output third_party/chaijs third_party/pycoverage third_party/webdriver third_party/gestures third_party/libcxx-pretty-printers third_party/feed third_party/d3 third_party/closure_compiler third_party/vulkan third_party/accessibility-audit third_party/bazel third_party/libsecret third_party/android_system_sdk third_party/google_appengine_cloudstorage third_party/android_protobuf third_party/unrar third_party/dav1d third_party/private-join-and-compute third_party/qunit third_party/apk-patch-size-estimator third_party/devscripts third_party/depot_tools third_party/googletest third_party/speech-dispatcher third_party/lottie third_party/sqlite4java third_party/spirv-headers third_party/robolectric third_party/proguard third_party/jstemplate third_party/iaccessible2 third_party/apache-portable-runtime third_party/rnnoise third_party/khronos third_party/woff2 third_party/mockito third_party/gradle_wrapper third_party/pffft third_party/flatbuffers third_party/arcore-android-sdk-client third_party/ots third_party/accessibility_test_framework third_party/motemplate third_party/openh264 third_party/glfw third_party/node third_party/smhasher third_party/google-truth third_party/gtest-parallel third_party/.git third_party/byte_buddy third_party/one_euro_filter third_party/v4l-utils third_party/google_toolbox_for_mac third_party/intellij third_party/emoji-segmenter third_party/libusb third_party/mesa_headers third_party/quic_trace third_party/android_opengl third_party/gvr-android-keyboard third_party/ocmock third_party/ced third_party/libudev third_party/breakpad third_party/catapult third_party/ijar third_party/metrics_proto third_party/protobuf third_party/sinonjs third_party/test_fonts third_party/espresso third_party/webrtc_overrides third_party/icu third_party/libipp third_party/custom_tabs_client third_party/xstream third_party/lzma_sdk third_party/bspatch third_party/material_design_icons third_party/protoc_javalite third_party/liblouis" mkdir webrtc pushd webrtc git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git DEPOT_TOOLS_PATH=$PWD/depot_tools/ export PATH=$PATH:$DEPOT_TOOLS_PATH $DEPOT_TOOLS_PATH/fetch --nohooks webrtc_ios pushd src git checkout $COMMIT $DEPOT_TOOLS_PATH/gclient sync mkdir lib pushd lib for ARCH in $ARCHS do TARGET_CPU=$ARCH TARGET_ENVIRONMENT="" if [ "${ARCH}" == "arm64-simulator" ]; then TARGET_CPU="arm64" TARGET_ENVIRONMENT="simulator" fi echo "Building for $ARCH ($TARGET_ENVIRONMENT)..." $DEPOT_TOOLS_PATH/gn gen $ARCH --args='target_os="ios" target_environment="'$TARGET_ENVIRONMENT'" target_cpu="'$TARGET_CPU'" rtc_include_tests=false rtc_build_examples=false treat_warnings_as_errors=false fatal_linker_warnings=false use_custom_libcxx=false is_debug=false ios_deployment_target="15.0" rtc_build_tools=false rtc_enable_protobuf=false is_clang=true is_component_build=false ios_enable_code_signing=false' pushd $ARCH $DEPOT_TOOLS_PATH/ninja -C . popd done echo "Creating xcframework for libwebrtc..." xcodebuild -create-xcframework -library $PWD/arm64-simulator/obj/$LIBWEBRTC_A -library $PWD/arm64/obj/$LIBWEBRTC_A -output ../../../xcframework/libwebrtc.xcframework echo "Creating xcframeworks for ADDITIONAL_LIBS..." # ADDITIONAL_LIBS created 'gn gen' command are a "thin library" and are not valid for xcframeworks. # Next steps, iterate in the thin libraries to create static libraries and be able to create the xcframeworks. for ADDITIONAL_LIB in $ADDITIONAL_LIBS do files=`nm $PWD/arm64/obj/sdk/$ADDITIONAL_LIB | grep -i "\.o:" | sed 's/:$//g' | tr '\n' ' '` for i in `echo $files`; do ar rcs $PWD/arm64/obj/$ADDITIONAL_LIB $PWD/arm64/obj/sdk/$i; done files=`nm $PWD/arm64-simulator/obj/sdk/$ADDITIONAL_LIB | grep -i "\.o:" | sed 's/:$//g' | tr '\n' ' '` for i in `echo $files`; do ar rcs $PWD/arm64-simulator/obj/$ADDITIONAL_LIB $PWD/arm64-simulator/obj/sdk/$i; done xcodebuild -create-xcframework -library $PWD/arm64-simulator/obj/$ADDITIONAL_LIB -library $PWD/arm64/obj/$ADDITIONAL_LIB -output ../../../xcframework/${ADDITIONAL_LIB%.*}.xcframework done echo "Cleaning sources folder..." popd # lib -> src popd # src -> webrtc mv src/* . for FOLDER_TO_REMOVE in $FOLDERS_TO_REMOVE do rm -rf $FOLDER_TO_REMOVE done find . -type f -not \( -name "*.h" -o -name "*.inc" \) -exec rm -f {} \; find . -type d -depth -empty -delete popd # webrtc -> 3rdparty sdk-10.11.0/bindings/ios/3rdparty/common.sh000066400000000000000000000012711516266226600204330ustar00rootroot00000000000000#!/bin/sh SDKVERSION=$(xcrun -sdk iphoneos --show-sdk-version) CURRENTPATH=$(pwd) DEVELOPER=$(xcode-select -print-path) CORES=$(sysctl -n hw.ncpu) # Formating green="\033[32m" bold="\033[0m${green}\033[1m" normal="\033[0m" # Function to print error messages and exit print_error() { echo -e "\033[31mError: $1\033[0m" >&2 exit 1 } # Check if Xcode path is correctly set check_xcode_path() { if [ ! -d "$DEVELOPER" ]; then print_error "Xcode path is not set correctly: $DEVELOPER does not exist." fi } # Check for spaces in paths check_for_spaces() { if [[ "$DEVELOPER" == *" "* || "$CURRENTPATH" == *" "* ]]; then print_error "Paths with spaces are not supported." fi } sdk-10.11.0/bindings/ios/ListenerDispatch.mm000066400000000000000000000027251516266226600206440ustar00rootroot00000000000000/** * @file ListenerDispatch.m * @brief dispatch functions for listener * * (c) 2021 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "ListenerDispatch.h" void dispatch(ListenerQueueType queueType, ListenerBlock block) { switch (queueType) { case ListenerQueueTypeCurrent: block(); break; case ListenerQueueTypeMain: dispatch_async(dispatch_get_main_queue(), block); break; case ListenerQueueTypeGlobalBackground: dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), block); break; case ListenerQueueTypeGlobalUtility: dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), block); break; case ListenerQueueTypeGlobalUserInitiated: dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), block); break; default: block(); } } sdk-10.11.0/bindings/ios/MEGAAccountDetails.mm000066400000000000000000000165001516266226600207270ustar00rootroot00000000000000/** * @file MEGAAccountDetails.mm * @brief Details about a MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountDetails.h" #import "MEGAAccountPlan+init.h" #import "MEGAAccountFeature+init.h" #import "MEGAAccountSubscription+init.h" #import "megaapi.h" #import "MEGAStringIntegerMap+init.h" using namespace mega; @interface MEGAAccountDetails () @property MegaAccountDetails *accountDetails; @property BOOL cMemoryOwn; @end @implementation MEGAAccountDetails - (instancetype)initWithMegaAccountDetails:(MegaAccountDetails *)accountDetails cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil){ _accountDetails = accountDetails; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _accountDetails; } } - (nullable MegaAccountDetails *)getCPtr { return self.accountDetails; } - (long long)storageUsed { return self.accountDetails ? self.accountDetails->getStorageUsed(): -1; } - (long long)versionStorageUsed { return self.accountDetails ? self.accountDetails->getVersionStorageUsed(): -1; } - (long long)storageMax { return self.accountDetails ? self.accountDetails->getStorageMax(): -1; } - (long long)transferUsed { return self.accountDetails ? self.accountDetails->getTransferUsed(): -1; } - (long long)transferMax { return self.accountDetails ? self.accountDetails->getTransferMax(): -1; } - (MEGAAccountType)type { return (MEGAAccountType) (self.accountDetails ? self.accountDetails->getProLevel(): 0); } - (NSInteger)proExpiration { return self.accountDetails ? self.accountDetails->getProExpiration(): -1; } - (MEGASubscriptionStatus)subscriptionStatus __attribute__((deprecated("Use new API version 2 interfaces"))) { return (MEGASubscriptionStatus) (self.accountDetails ? self.accountDetails->getSubscriptionStatus(): -1); } - (NSInteger)subscriptionRenewTime __attribute__((deprecated("Use new API version 2 interfaces"))) { return self.accountDetails ? self.accountDetails->getSubscriptionRenewTime(): -1; } - (nullable NSString *)subscriptionMethod __attribute__((deprecated("Use new API version 2 interfaces"))) { const char *val = self.accountDetails ? self.accountDetails->getSubscriptionMethod() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (MEGAPaymentMethod)subscriptionMethodId __attribute__((deprecated("Use new API version 2 interfaces"))) { return (MEGAPaymentMethod) (self.accountDetails ? self.accountDetails->getSubscriptionMethodId(): -1); } - (nullable NSString *)subscriptionCycle __attribute__((deprecated("Use new API version 2 interfaces"))) { const char *val = self.accountDetails ? self.accountDetails->getSubscriptionCycle() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSInteger)numberUsageItems { return self.accountDetails ? self.accountDetails->getNumUsageItems(): -1; } - (NSInteger)numActiveFeatures { return self.accountDetails ? self.accountDetails->getNumActiveFeatures() : -1; } - (int64_t)subscriptionLevel __attribute__((deprecated("Use new API version 2 interfaces"))) { return self.accountDetails ? self.accountDetails->getSubscriptionLevel() : -1; } - (nullable MEGAAccountFeature *)activeFeatureAtIndex:(NSInteger)index { if (!self.accountDetails) { return nil; } MegaAccountFeature *feature = self.accountDetails->getActiveFeature((int)index); if (!feature) { return nil; } return [[MEGAAccountFeature alloc] initWithMegaAccountFeature:feature cMemoryOwn:YES]; } - (NSDictionary *)subscriptionFeatures __attribute__((deprecated("Use new API version 2 interfaces"))) { if (!self.accountDetails) { return nil; } MegaStringIntegerMap* featuresMap = self.accountDetails->getSubscriptionFeatures(); if (!featuresMap) { return nil; } MEGAStringIntegerMap *megaStringIntegerMap = [[MEGAStringIntegerMap alloc] initWithMegaStringIntegerMap:featuresMap cMemoryOwn:YES]; return [megaStringIntegerMap toDictionary]; } - (long long)storageUsedForHandle:(uint64_t)handle { return self.accountDetails ? self.accountDetails->getStorageUsed(handle): -1; } - (long long)numberFilesForHandle:(uint64_t)handle { return self.accountDetails ? self.accountDetails->getNumFiles(handle): -1; } - (long long)numberFoldersForHandle:(uint64_t)handle { return self.accountDetails ? self.accountDetails->getNumFolders(handle): -1; } - (long long)versionStorageUsedForHandle:(uint64_t)handle { return self.accountDetails ? self.accountDetails->getVersionStorageUsed(handle): -1; } - (long long)numberOfVersionFilesForHandle:(uint64_t)handle { return self.accountDetails ? self.accountDetails->getNumVersionFiles(handle): -1; } - (NSInteger)numberOfPlans { return self.accountDetails ? self.accountDetails->getNumPlans(): 0; } - (nullable MEGAAccountPlan *)planAtIndex:(NSInteger)index { return self.accountDetails ? [[MEGAAccountPlan alloc] initWithMegaAccountPlan: self.accountDetails->getPlan((int)index) cMemoryOwn:YES] : nil; } - (NSInteger)numberOfSubscriptions { return self.accountDetails ? self.accountDetails->getNumSubscriptions() : 0; } - (nullable MEGAAccountSubscription *)subscriptionAtIndex:(NSInteger)index { return self.accountDetails ? [[MEGAAccountSubscription alloc] initWithMegaAccountSubscription:self.accountDetails->getSubscription((int)index) cMemoryOwn:YES] : nil; } + (nullable NSString *)stringForAccountType:(MEGAAccountType)accountType { NSString *result; switch (accountType) { case MEGAAccountTypeFree: result = @"Free"; break; case MEGAAccountTypeProI: result = @"Pro I"; break; case MEGAAccountTypeProII: result = @"Pro II"; break; case MEGAAccountTypeProIII: result = @"Pro III"; break; case MEGAAccountTypeLite: result = @"Pro Lite"; break; case MEGAAccountTypeBusiness: result = @"Business"; break; case MEGAAccountTypeProFlexi: result = @"Pro Flexi"; break; case MEGAAccountTypeStarter: result = @"Starter"; break; case MEGAAccountTypeBasic: result = @"Basic"; break; case MEGAAccountTypeEssential: result = @"Essential"; break; default: result = @"Unknown"; break; } return result; } @end sdk-10.11.0/bindings/ios/MEGAAccountFeature.mm000066400000000000000000000033431516266226600207360ustar00rootroot00000000000000/** * @file MEGAAccountFeature.mm * @brief Details about a MEGA feature * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountFeature.h" #import "MEGAAccountFeature+init.h" using namespace mega; @interface MEGAAccountFeature () @property MegaAccountFeature *megaAccountFeature; @property BOOL cMemoryOwn; @end @implementation MEGAAccountFeature - (instancetype)initWithMegaAccountFeature:(mega::MegaAccountFeature *)megaAccountFeature cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaAccountFeature = megaAccountFeature; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaAccountFeature; } } - (mega::MegaAccountFeature *)getCPtr { return self.megaAccountFeature; } - (int64_t)expiry { return self.megaAccountFeature ? self.megaAccountFeature->getExpiry() : -1; } - (nullable NSString *)featureId { if (!self.megaAccountFeature) { return nil; } const char *cId = self.megaAccountFeature->getId(); if (!cId) { return nil; } NSString *idString = [[NSString alloc] initWithUTF8String:cId]; delete[] cId; return idString; } @end sdk-10.11.0/bindings/ios/MEGAAccountPlan.mm000066400000000000000000000046571516266226600202460ustar00rootroot00000000000000/** * @file MEGAAccountPlan.mm * @brief Details about a MEGA account plan * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountPlan.h" #import "MEGAStringList+init.h" #import "megaapi.h" using namespace mega; @interface MEGAAccountPlan() @property BOOL cMemoryOwn; @property MegaAccountPlan *megaAccountPlan; @end @implementation MEGAAccountPlan - (instancetype)initWithMegaAccountPlan:(MegaAccountPlan *)accountPlan cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaAccountPlan = accountPlan; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaAccountPlan; } } - (nullable MegaAccountPlan *)getCPtr { return self.megaAccountPlan; } - (BOOL)isProPlan { return self.megaAccountPlan ? self.megaAccountPlan->isProPlan() : NO; } - (MEGAAccountType)accountType { return (MEGAAccountType) (self.megaAccountPlan ? self.megaAccountPlan->getAccountLevel() : -1); } - (NSArray*)features { if (!self.megaAccountPlan) return nil; MegaStringList* features = self.megaAccountPlan ? self.megaAccountPlan->getFeatures() : nil; if (!features) return nil; MEGAStringList* megaStringList = [MEGAStringList.alloc initWithMegaStringList:features cMemoryOwn:YES]; return [megaStringList toStringArray]; } - (int64_t)expirationTime { return self.megaAccountPlan ? self.megaAccountPlan->getExpirationTime() : 0; } - (int32_t)type { return self.megaAccountPlan ? self.megaAccountPlan->getType() : 0; } - (nullable NSString *)subscriptionId { const char *val = self.megaAccountPlan ? self.megaAccountPlan->getId() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (BOOL)isTrial { return self.megaAccountPlan ? self.megaAccountPlan->isTrial() : NO; } @end sdk-10.11.0/bindings/ios/MEGAAccountSubscription.mm000066400000000000000000000064161516266226600220330ustar00rootroot00000000000000/** * @file MEGAAccountSubscription.mm * @brief Details about a MEGA account subscription * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountSubscription.h" #import "MEGAStringList+init.h" #import "megaapi.h" using namespace mega; @interface MEGAAccountSubscription() @property BOOL cMemoryOwn; @property MegaAccountSubscription *megaAccountSubscription; @end @implementation MEGAAccountSubscription - (instancetype)initWithMegaAccountSubscription:(MegaAccountSubscription *)accountSubscription cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaAccountSubscription = accountSubscription; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaAccountSubscription; } } - (nullable MegaAccountSubscription *)getCPtr { return self.megaAccountSubscription; } - (nullable NSString *)subcriptionId { const char *val = self.megaAccountSubscription ? self.megaAccountSubscription->getId() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (MEGASubscriptionStatus)status { return (MEGASubscriptionStatus) (self.megaAccountSubscription ? self.megaAccountSubscription->getStatus() : 0); } - (nullable NSString *)cycle { const char *val = self.megaAccountSubscription ? self.megaAccountSubscription->getCycle() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (nullable NSString *)paymentMethod { const char *val = self.megaAccountSubscription ? self.megaAccountSubscription->getPaymentMethod() : nil; if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (MEGAPaymentMethod)paymentMethodId { return (MEGAPaymentMethod) (self.megaAccountSubscription ? self.megaAccountSubscription->getPaymentMethodId() : -1); } - (int64_t)renewTime { return self.megaAccountSubscription ? self.megaAccountSubscription->getRenewTime() : 0; } - (MEGAAccountType)accountType { return (MEGAAccountType) (self.megaAccountSubscription ? self.megaAccountSubscription->getAccountLevel() : -1); } - (NSArray*)features { if (!self.megaAccountSubscription) return nil; MegaStringList* features = self.megaAccountSubscription ? self.megaAccountSubscription->getFeatures() : nil; if (!features) return nil; MEGAStringList* megaStringList = [MEGAStringList.alloc initWithMegaStringList:features cMemoryOwn:YES]; return [megaStringList toStringArray]; } - (BOOL)isTrial { return self.megaAccountSubscription ? self.megaAccountSubscription->isTrial() : NO; } @end sdk-10.11.0/bindings/ios/MEGAAchievementsDetails.mm000066400000000000000000000120011516266226600217360ustar00rootroot00000000000000/** * @file MEGAAchievementsDetails.mm * @brief Achievements that a user can unlock * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAchievementsDetails.h" #import "MEGAAchievementsDetails+init.h" #import "MEGAStringList+init.h" using namespace mega; @interface MEGAAchievementsDetails () @property MegaAchievementsDetails *megaAchievementsDetails; @property BOOL cMemoryOwn; @end @implementation MEGAAchievementsDetails - (instancetype)initWithMegaAchievementsDetails:(mega::MegaAchievementsDetails *)megaAchievementsDetails cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaAchievementsDetails = megaAchievementsDetails; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaAchievementsDetails; } } - (mega::MegaAchievementsDetails *)getCPtr { return self.megaAchievementsDetails; } - (long long)baseStorage { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getBaseStorage() : -1; } - (long long)currentStorage { return self.megaAchievementsDetails ? self.megaAchievementsDetails->currentStorage() : -1; } - (long long)currentTransfer { return self.megaAchievementsDetails ? self.megaAchievementsDetails->currentTransfer() : -1; } - (long long)currentStorageReferrals { return self.megaAchievementsDetails ? self.megaAchievementsDetails->currentStorageReferrals() : -1; } - (long long)currentTransferReferrals { return self.megaAchievementsDetails ? self.megaAchievementsDetails->currentTransferReferrals() : -1; } - (NSUInteger)awardsCount { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getAwardsCount() : 0; } - (NSInteger)rewardsCount { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardsCount() : -1; } - (bool)isValidClass:(NSInteger)classId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->isValidClass((int)classId) : NO; } - (long long)classStorageForClassId:(NSInteger)classId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getClassStorage((int)classId) : -1; } - (long long)classTransferForClassId:(NSInteger)classId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getClassTransfer((int)classId) : -1; } - (NSInteger)classExpireForClassId:(NSInteger)classId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getClassExpire((int)classId) : 0; } - (NSInteger)awardClassAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getAwardClass((unsigned int)index) : 0; } - (NSInteger)awardIdAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getAwardId((unsigned int)index) : 0; } - (NSDate *)awardTimestampAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaAchievementsDetails->getAwardTimestamp((unsigned int)index)] : nil; } - (NSDate *)awardExpirationAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaAchievementsDetails->getAwardExpirationTs((unsigned int)index)] : nil; } - (MEGAStringList *)awardEmailsAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? [[MEGAStringList alloc] initWithMegaStringList:self.megaAchievementsDetails->getAwardEmails((unsigned int)index) cMemoryOwn:YES] : nil; } - (NSInteger)rewardAwardIdAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardAwardId((unsigned int)index) : -1; } - (long long)rewardStorageAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardStorage((unsigned int)index) : -1; } - (long long)rewardTransferAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardTransfer((unsigned int)index) : -1; } - (long long)rewardStorageByAwardId:(NSInteger)awardId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardStorageByAwardId((int)awardId) : -1; } - (long long)rewardTransferByAwardId:(NSInteger)awardId { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardTransferByAwardId((int)awardId) : -1; } - (NSInteger)rewardExpireAtIndex:(NSUInteger)index { return self.megaAchievementsDetails ? self.megaAchievementsDetails->getRewardExpire((unsigned int)index) : 0; } @end sdk-10.11.0/bindings/ios/MEGABackgroundMediaUpload.mm000066400000000000000000000060331516266226600222110ustar00rootroot00000000000000/** * @file MEGABackgroundMediaUpload.mm * @brief Background media upload * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABackgroundMediaUpload.h" #import "megaapi.h" #import "MEGABackgroundMediaUpload+init.h" #import "MEGASdk+init.h" @interface MEGABackgroundMediaUpload () @property (nonatomic) mega::MegaBackgroundMediaUpload *mediaUpload; @end @implementation MEGABackgroundMediaUpload - (instancetype)initWithBackgroundMediaUpload:(mega::MegaBackgroundMediaUpload *)mediaUpload { self = [super init]; if (self) { _mediaUpload = mediaUpload; } return self; } - (instancetype)initWithMEGASdk:(MEGASdk *)sdk { mega::MegaApi *api = sdk.getCPtr; if (api) { return [self initWithBackgroundMediaUpload:mega::MegaBackgroundMediaUpload::createInstance(api)]; } else { return nil; } } - (void)dealloc { delete _mediaUpload; } - (mega::MegaBackgroundMediaUpload *)getCPtr { return self.mediaUpload; } - (BOOL)analyseMediaInfoForFileAtPath:(NSString *)inputFilepath { return self.mediaUpload->analyseMediaInfo(inputFilepath.UTF8String); } - (NSString *)encryptFileAtPath:(NSString *)inputFilePath startPosition:(int64_t)start length:(int64_t *)length outputFilePath:(NSString *)outputFilePath adjustsSizeOnly:(BOOL)adjustsSizeOnly { const char *val = self.mediaUpload->encryptFile(inputFilePath.UTF8String, start, length, outputFilePath.UTF8String, adjustsSizeOnly); NSString *suffix = val == NULL ? nil : @(val); delete [] val; return suffix; } - (NSString *)uploadURLString { const char *val = self.mediaUpload->getUploadURL(); NSString *urlString = val == NULL ? nil : @(val); delete [] val; return urlString; } - (void)setCoordinatesWithLatitude:(double)latitude longitude:(double)longitude isUnshareable:(BOOL)unshareable { self.mediaUpload->setCoordinates(latitude, longitude, unshareable); } - (NSData *)serialize { const char *binary = self.mediaUpload->serialize(); NSData *data = binary == NULL ? nil : [NSData dataWithBytes:binary length:strlen(binary)]; delete [] binary; return data; } + (instancetype)unserializByData:(NSData *)data MEGASdk:(MEGASdk *)sdk { mega::MegaApi *api = sdk.getCPtr; if (api) { mega::MegaBackgroundMediaUpload *mediaUpload = mega::MegaBackgroundMediaUpload::unserialize((const char *)data.bytes, api); return [[self alloc] initWithBackgroundMediaUpload:mediaUpload]; } else { return nil; } } @end sdk-10.11.0/bindings/ios/MEGABackupInfo.mm000066400000000000000000000075571516266226600200620ustar00rootroot00000000000000/** * @file MEGABackupInfo.mm * @brief Represents an user banner in MEGA * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABackupInfo.h" #import "megaapi.h" using namespace mega; @interface MEGABackupInfo () @property MegaBackupInfo *megaBackupInfo; @property BOOL cMemoryOwn; @end @implementation MEGABackupInfo - (instancetype)initWithMegaBackupInfo:(MegaBackupInfo *)megaBackupInfo cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaBackupInfo = megaBackupInfo; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaBackupInfo; } } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[MEGABackupInfo class]]) { return false; } return self.id == ((MEGABackupInfo *)object).id; } - (MegaBackupInfo *)getCPtr { return self.megaBackupInfo; } - (NSUInteger)identifier { return (NSUInteger)self.megaBackupInfo->id(); } - (MEGABackupType)type { return (MEGABackupType) (self.megaBackupInfo ? self.megaBackupInfo->type() : MEGABackupTypeInvalid); } - (uint64_t)root { return self.megaBackupInfo ? self.megaBackupInfo->root() : ::mega::INVALID_HANDLE; } - (NSString *)localFolder { if(!self.megaBackupInfo) return nil; return self.megaBackupInfo->localFolder() ? [[NSString alloc] initWithUTF8String:self.megaBackupInfo->localFolder()] : nil; } - (NSString *)deviceId { if (self.megaBackupInfo) { const char *val = self.megaBackupInfo->deviceId(); if (val) { return [NSString stringWithUTF8String:val]; } } return nil; } - (BackUpState)state { return (BackUpState) (self.megaBackupInfo ? self.megaBackupInfo->state() : BackUpStateUnknown); } - (BackUpSubState)substate { return (BackUpSubState) (self.megaBackupInfo ? self.megaBackupInfo->substate() : BackUpSubStateNoSyncError); } - (NSString *)extra { if(!self.megaBackupInfo) return nil; return self.megaBackupInfo->extra() ? [[NSString alloc] initWithUTF8String:self.megaBackupInfo->extra()] : nil; } - (NSString *)name { if(!self.megaBackupInfo) return nil; return self.megaBackupInfo->name() ? [[NSString alloc] initWithUTF8String:self.megaBackupInfo->name()] : nil; } - (NSDate *)timestamp { return self.megaBackupInfo ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaBackupInfo->ts()] : nil; } - (MEGABackupHeartbeatStatus)status { return (MEGABackupHeartbeatStatus) (self.megaBackupInfo ? self.megaBackupInfo->status() : MEGABackupHeartbeatStatusUnknown); } - (NSUInteger)progress { return self.megaBackupInfo ? self.megaBackupInfo->progress() : 0; } - (NSUInteger)uploads { return self.megaBackupInfo ? self.megaBackupInfo->uploads() : 0; } - (NSUInteger)downloads { return self.megaBackupInfo ? self.megaBackupInfo->downloads() : 0; } - (NSDate *)activityTimestamp { return self.megaBackupInfo ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaBackupInfo->activityTs()] : nil; } - (uint64_t)lastSync { return self.megaBackupInfo ? self.megaBackupInfo->lastSync() : ::mega::INVALID_HANDLE; } - (NSString *)userAgent { if(!self.megaBackupInfo) return nil; return self.megaBackupInfo->deviceUserAgent() ? [[NSString alloc] initWithUTF8String:self.megaBackupInfo->deviceUserAgent()] : nil; } @end sdk-10.11.0/bindings/ios/MEGABackupInfoList.mm000066400000000000000000000031311516266226600206760ustar00rootroot00000000000000/** * @file MEGABackupInfoList.mm * @brief List of MEGATransfer objects * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABackupInfoList.h" #import "MEGABackupInfo+init.h" using namespace mega; @interface MEGABackupInfoList () @property MegaBackupInfoList *backupInfoList; @property BOOL cMemoryOwn; @end @implementation MEGABackupInfoList - (instancetype)initWithBackupInfoList:(MegaBackupInfoList *)backupInfoList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _backupInfoList = backupInfoList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _backupInfoList; } } - (MegaBackupInfoList *)getCPtr { return self.backupInfoList; } - (MEGABackupInfo *)backupInfoAtIndex:(NSInteger)index { return self.backupInfoList ? [[MEGABackupInfo alloc] initWithMegaBackupInfo:self.backupInfoList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSUInteger)size { return self.backupInfoList ? self.backupInfoList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGABanner.mm000066400000000000000000000047631516266226600172420ustar00rootroot00000000000000/** * @file MEGABanner.mm * @brief Represents an user banner in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABanner.h" #import "megaapi.h" using namespace mega; @interface MEGABanner () @property MegaBanner *megaBanner; @property BOOL cMemoryOwn; @end @implementation MEGABanner - (instancetype)initWithMegaBanner:(MegaBanner *)megaBanner cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaBanner = megaBanner; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaBanner; } } - (MegaBanner *)getCPtr { return self.megaBanner; } - (NSUInteger)identifier { return (NSUInteger)self.megaBanner->getId(); } - (NSString *)title { if (!self.megaBanner) return nil; return self.megaBanner->getTitle() ? @(self.megaBanner->getTitle()) : nil; } - (NSString *)description { if (!self.megaBanner) return nil; return self.megaBanner->getDescription() ? @(self.megaBanner->getDescription()) : nil; } - (NSString *)imageFilename { if (!self.megaBanner) return nil; return self.megaBanner->getImage() ? @(self.megaBanner->getImage()) : nil; } - (NSString *)backgroundImageFilename { if (!self.megaBanner) return nil; return self.megaBanner->getBackgroundImage() ? @(self.megaBanner->getBackgroundImage()) : nil; } - (NSString *)imageLocationURLString { if (!self.megaBanner) return nil; return self.megaBanner->getImageLocation() ? @(self.megaBanner->getImageLocation()) : nil; } - (NSString *)URLString { if (!self.megaBanner) return nil; return self.megaBanner->getUrl() ? @(self.megaBanner->getUrl()) : nil; } - (NSInteger)variant { if (!self.megaBanner) return 0; return self.megaBanner->getVariant(); } - (NSString *)button { if (!self.megaBanner) return nil; return self.megaBanner->getButton() ? @(self.megaBanner->getButton()) : nil; } @end sdk-10.11.0/bindings/ios/MEGABannerList.mm000066400000000000000000000030321516266226600200620ustar00rootroot00000000000000/** * @file MEGABannerList.mm * @brief List of MEGABanner objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "megaapi.h" #import "MEGABanner.h" #import "MEGABanner+init.h" #import "MEGABannerList.h" using namespace mega; @interface MEGABannerList () @property MegaBannerList *megaBannerList; @property BOOL cMemoryOwn; @end @implementation MEGABannerList - (instancetype)initWithMegaBannerList:(MegaBannerList *)megaBannerList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaBannerList = megaBannerList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaBannerList; } } - (MEGABanner *)bannerAtIndex:(NSInteger)index { return self.megaBannerList ? [[MEGABanner alloc] initWithMegaBanner:self.megaBannerList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSInteger)size { return self.megaBannerList ? self.megaBannerList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGACancelSubscriptionReason.mm000066400000000000000000000045171516266226600227740ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReason.mm * @brief Represents a reason chosen by a user when canceling a subscription * * (c) 2024- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelSubscriptionReason.h" #import "MEGACancelSubscriptionReason+init.h" #import "megaapi.h" using namespace mega; @interface MEGACancelSubscriptionReason () @property MegaCancelSubscriptionReason *megaCancelSubscriptionReason; @property BOOL cMemoryOwn; @end @implementation MEGACancelSubscriptionReason - (instancetype)initWithMegaCancelSubscriptionReason:(MegaCancelSubscriptionReason *)megaCancelSubscriptionReason cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaCancelSubscriptionReason = megaCancelSubscriptionReason; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaCancelSubscriptionReason; } } - (MegaCancelSubscriptionReason *)getCPtr { return self.megaCancelSubscriptionReason; } + (MEGACancelSubscriptionReason *)create:(NSString *)text position:(NSString *)position { MegaCancelSubscriptionReason *reason = MegaCancelSubscriptionReason::create(text.UTF8String, position.UTF8String); return [[MEGACancelSubscriptionReason alloc] initWithMegaCancelSubscriptionReason:reason->copy() cMemoryOwn:YES]; } - (nullable NSString *)text { if (!self.megaCancelSubscriptionReason) return nil; return self.megaCancelSubscriptionReason->text() ? [[NSString alloc] initWithUTF8String:self.megaCancelSubscriptionReason->text()] : nil; } - (nullable NSString *)position { if (!self.megaCancelSubscriptionReason) return nil; return self.megaCancelSubscriptionReason->position() ? [[NSString alloc] initWithUTF8String:self.megaCancelSubscriptionReason->position()] : nil; } @end sdk-10.11.0/bindings/ios/MEGACancelSubscriptionReasonList.mm000066400000000000000000000050011516266226600236150ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReasonList.mm * @brief Represents a reason chosen from a multiple-choice by a user when canceling a subscription * * (c) 2024- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelSubscriptionReasonList.h" #import "MEGACancelSubscriptionReasonList+init.h" #import "MEGACancelSubscriptionReason+init.h" #import "megaapi.h" using namespace mega; @interface MEGACancelSubscriptionReasonList () @property MegaCancelSubscriptionReasonList *megaCancelSubscriptionReasonList; @property BOOL cMemoryOwn; @end @implementation MEGACancelSubscriptionReasonList - (instancetype)initWithMegaCancelSubscriptionReasonList:(MegaCancelSubscriptionReasonList *)megaCancelSubscriptionReasonList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaCancelSubscriptionReasonList = megaCancelSubscriptionReasonList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaCancelSubscriptionReasonList; } } + (MEGACancelSubscriptionReasonList *)create { MegaCancelSubscriptionReasonList *list = MegaCancelSubscriptionReasonList::create(); return [[MEGACancelSubscriptionReasonList alloc] initWithMegaCancelSubscriptionReasonList:list->copy() cMemoryOwn:YES]; } - (MegaCancelSubscriptionReasonList *)getCPtr { return self.megaCancelSubscriptionReasonList; } - (NSInteger)size { return self.megaCancelSubscriptionReasonList ? self.megaCancelSubscriptionReasonList->size() : 0; } - (void)addReason:(MEGACancelSubscriptionReason *)reason { if (!self.megaCancelSubscriptionReasonList) return; self.megaCancelSubscriptionReasonList->add([reason getCPtr]); } - (nullable MEGACancelSubscriptionReason *)reasonAtIndex:(NSInteger)index { return self.megaCancelSubscriptionReasonList ? [[MEGACancelSubscriptionReason alloc] initWithMegaCancelSubscriptionReason:self.megaCancelSubscriptionReasonList->get((int)index)->copy() cMemoryOwn:YES] : nil; } @end sdk-10.11.0/bindings/ios/MEGACancelToken.mm000066400000000000000000000025351516266226600202160ustar00rootroot00000000000000/** * @file MEGACancelToken.m * @brief Cancel MEGASdk methods. * * (c) 2019- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelToken.h" #import "megaapi.h" #import "MEGACancelToken+init.h" @interface MEGACancelToken () @property (nonatomic) mega::MegaCancelToken *megaCancelToken; @end @implementation MEGACancelToken - (instancetype)init { self = [super init]; if (self) { _megaCancelToken = mega::MegaCancelToken::createInstance(); } return self; } - (void)dealloc { if (_megaCancelToken) { delete _megaCancelToken; } } - (mega::MegaCancelToken *)getCPtr { return self.megaCancelToken; } - (BOOL)isCancelled { return self.megaCancelToken->isCancelled(); } - (void)cancel { if (_megaCancelToken) { self.megaCancelToken->cancel(); } } @end sdk-10.11.0/bindings/ios/MEGAContactRequest.mm000066400000000000000000000053461516266226600207770ustar00rootroot00000000000000/** * @file MEGAContactRequest.mm * @brief Represents a contact request with an user in MEGA * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAContactRequest.h" #import "megaapi.h" using namespace mega; @interface MEGAContactRequest () @property MegaContactRequest *megaContactRequest; @property BOOL cMemoryOwn; @end @implementation MEGAContactRequest - (instancetype)initWithMegaContactRequest:(MegaContactRequest *)megaContactRequest cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaContactRequest = megaContactRequest; _cMemoryOwn = cMemoryOwn; } return self; } - (MegaContactRequest *)getCPtr { return self.megaContactRequest; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaContactRequest; } } - (uint64_t)handle { return self.megaContactRequest ? self.megaContactRequest->getHandle() : ::mega::INVALID_HANDLE; } - (NSString *)sourceEmail{ if (!self.megaContactRequest) return nil; return self.megaContactRequest->getSourceEmail() ? [[NSString alloc] initWithUTF8String:self.megaContactRequest->getSourceEmail()] : nil; } - (NSString *)sourceMessage { if (!self.megaContactRequest) return nil; return self.megaContactRequest->getSourceMessage() ? [[NSString alloc] initWithUTF8String:self.megaContactRequest->getSourceMessage()] : nil; } - (NSString *)targetEmail { if (!self.megaContactRequest) return nil; return self.megaContactRequest->getTargetEmail() ? [[NSString alloc] initWithUTF8String:self.megaContactRequest->getTargetEmail()] : nil; } - (NSDate *)creationTime { return self.megaContactRequest ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaContactRequest->getCreationTime()] : nil; } - (NSDate *)modificationTime { return self.megaContactRequest ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaContactRequest->getModificationTime()] : nil; } - (MEGAContactRequestStatus)status { return self.megaContactRequest ? (MEGAContactRequestStatus)self.megaContactRequest->getStatus() : MEGAContactRequestStatusUnresolved; } - (BOOL)isOutgoing { return self.megaContactRequest ? self.megaContactRequest->isOutgoing() : NO; } @end sdk-10.11.0/bindings/ios/MEGAContactRequestList.mm000066400000000000000000000033551516266226600216310ustar00rootroot00000000000000/** * @file MEGAContactRequestList.mm * @brief List of MEGAContactRequest objects * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAContactRequestList.h" #import "MEGAContactRequest+init.h" using namespace mega; @interface MEGAContactRequestList () @property MegaContactRequestList *megaContactRequestList; @property BOOL cMemoryOwn; @end @implementation MEGAContactRequestList - (instancetype)initWithMegaContactRequestList:(MegaContactRequestList *)megaContactRequestList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaContactRequestList = megaContactRequestList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaContactRequestList; } } - (MegaContactRequestList *)getCPtr { return self.megaContactRequestList; } - (NSInteger)size { return self.megaContactRequestList ? self.megaContactRequestList->size() : -1; } - (MEGAContactRequest *)contactRequestAtIndex:(NSInteger)index { return self.megaContactRequestList ? [[MEGAContactRequest alloc] initWithMegaContactRequest:self.megaContactRequestList->get((int)index)->copy() cMemoryOwn:YES] : nil; } @end sdk-10.11.0/bindings/ios/MEGACreditCardNodeData.mm000066400000000000000000000065451516266226600214410ustar00rootroot00000000000000/** * @file MEGACreditCardNodeData.mm * @brief Object Data for Password Node attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACreditCardNodeData.h" #import "MEGACreditCardNodeData+init.h" @interface MEGACreditCardNodeData() @property MegaCreditCardNodeData *megaCreditCardNodeData; @property BOOL cMemoryOwn; @end @implementation MEGACreditCardNodeData - (instancetype)initWithCardNumber:(NSString *)cardNumber notes:(nullable NSString *)notes cardHolderName:(nullable NSString *)cardHolderName cvv:(nullable NSString *)cvv expirationDate:(nullable NSString *)expirationDate { self = [super init]; if (self) { _megaCreditCardNodeData = MegaCreditCardNodeData::createInstance(cardNumber.UTF8String, notes.UTF8String, cardHolderName.UTF8String, cvv.UTF8String, expirationDate.UTF8String); _cMemoryOwn = YES; } return self; } - (instancetype)initWithMegaCreditCardNodeData:(MegaCreditCardNodeData *)megaCreditCardNodeData cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaCreditCardNodeData = megaCreditCardNodeData; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaCreditCardNodeData; } } - (MegaCreditCardNodeData *)getCPtr { return self.megaCreditCardNodeData; } - (nullable NSString *)cardNumber { const char *cardNumber = self.megaCreditCardNodeData ? self.megaCreditCardNodeData->cardNumber() : nullptr; return cardNumber ? [[NSString alloc] initWithUTF8String:cardNumber] : nil; } - (nullable NSString *)notes { const char *notes = self.megaCreditCardNodeData ? self.megaCreditCardNodeData->notes() : nullptr; return notes ? [[NSString alloc] initWithUTF8String:notes] : nil; } - (nullable NSString *)cardHolderName { const char *cardHolderName = self.megaCreditCardNodeData ? self.megaCreditCardNodeData->cardHolderName() : nullptr; return cardHolderName ? [[NSString alloc] initWithUTF8String:cardHolderName] : nil; } - (nullable NSString *)cvv { const char *cvv = self.megaCreditCardNodeData ? self.megaCreditCardNodeData->cvv() : nullptr; return cvv ? [[NSString alloc] initWithUTF8String:cvv] : nil; } - (nullable NSString *)expirationDate { const char *expirationDate = self.megaCreditCardNodeData ? self.megaCreditCardNodeData->expirationDate() : nullptr; return expirationDate ? [[NSString alloc] initWithUTF8String:expirationDate] : nil; } @end sdk-10.11.0/bindings/ios/MEGACurrency.mm000066400000000000000000000042501516266226600176160ustar00rootroot00000000000000/** * @file MEGACurrency.mm * @brief Details about currencies * * (c) 2021- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACurrency.h" #import "megaapi.h" using namespace mega; @interface MEGACurrency () @property MegaCurrency *currency; @property BOOL cMemoryOwn; @end @implementation MEGACurrency - (instancetype)initWithMegaCurrency:(MegaCurrency *)currency cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _currency = currency; _cMemoryOwn = cMemoryOwn; } return self; } - (MegaCurrency *)getCPtr { return self.currency; } - (void)dealloc { if (self.cMemoryOwn) { delete _currency; } } - (NSString *)currencySymbol { if (self.currency) { const char *symbol = self.currency->getCurrencySymbol(); if (symbol) { return [NSString stringWithUTF8String:symbol]; } } return nil; } - (NSString *)currencyName { if (self.currency) { const char *name = self.currency->getCurrencyName(); if (name) { return [NSString stringWithUTF8String:name]; } } return nil; } - (NSString *)localCurrencySymbol { if (self.currency) { const char *localSymbol = self.currency->getLocalCurrencySymbol(); if (localSymbol) { return [NSString stringWithUTF8String:localSymbol]; } } return nil; } - (NSString *)localCurrencyName { if (self.currency) { const char *localName = self.currency->getLocalCurrencyName(); if (localName) { return [NSString stringWithUTF8String:localName]; } } return nil; } @end sdk-10.11.0/bindings/ios/MEGAError.mm000066400000000000000000000045561516266226600171260ustar00rootroot00000000000000/** * @file MEGAError.mm * @brief Error info * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAError.h" #import "megaapi.h" using namespace mega; @interface MEGAError() @property MegaError *megaError; @property BOOL cMemoryOwn; @end @implementation MEGAError - (instancetype)initWithMegaError:(MegaError *)megaError cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaError = megaError; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaError; } } - (MegaError *)getCPtr { return self.megaError; } - (MEGAErrorType)type { return (MEGAErrorType) (self.megaError ? self.megaError->getErrorCode() : 0); } - (NSString *)name { return [[NSString alloc] initWithUTF8String:self.megaError->getErrorString()]; } - (long long)value { return self.megaError ? self.megaError->getValue() : 0; } - (BOOL)hasExtraInfo { return self.megaError ? self.megaError->hasExtraInfo() : NO; } - (MEGAUserErrorCode)userStatus { return self.megaError ? MEGAUserErrorCode(self.megaError->getUserStatus()) : MEGAUserErrorCodeETDUnknown; } - (MEGALinkErrorCode)linkStatus { return self.megaError ? MEGALinkErrorCode(self.megaError->getLinkStatus()) : MEGALinkErrorCodeUnknown; } - (nullable NSString *)nameWithErrorCode:(NSInteger)errorCode { return MegaError::getErrorString((int)errorCode) ? [[NSString alloc] initWithUTF8String:MegaError::getErrorString((int)errorCode)] : nil; } + (nullable NSString *)errorStringWithErrorCode:(NSInteger)errorCode context:(MEGAErrorContext)context { return MegaError::getErrorString((int)errorCode, (MegaError::ErrorContexts)context) ? [[NSString alloc] initWithUTF8String:MegaError::getErrorString((int)errorCode, (MegaError::ErrorContexts)context)] : nil; } @end sdk-10.11.0/bindings/ios/MEGAEvent.mm000066400000000000000000000034731516266226600171130ustar00rootroot00000000000000/** * @file MEGAEvent.mm * @brief Provides information about an event * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAEvent.h" #import "megaapi.h" using namespace mega; @interface MEGAEvent () @property MegaEvent *megaEvent; @property BOOL cMemoryOwn; @end @implementation MEGAEvent - (instancetype)initWithMegaEvent:(MegaEvent *)megaEvent cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaEvent = megaEvent; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaEvent; } } - (MegaEvent *)getCPtr { return self.megaEvent; } - (Event)type { return (Event) (self.megaEvent ? self.megaEvent->getType() : 0); } - (nullable NSString *)text { if (self.megaEvent) { const char *val = self.megaEvent->getText(); if (val) { return [NSString stringWithUTF8String:val]; } } return nil; } - (NSInteger)number { return self.megaEvent ? self.megaEvent->getNumber() : -1; } - (nullable NSString *)eventString { if (self.megaEvent) { const char *val = self.megaEvent->getEventString(); if (val) { return [NSString stringWithUTF8String:val]; } } return nil; } @end sdk-10.11.0/bindings/ios/MEGAFolderInfo.mm000066400000000000000000000033641516266226600200600ustar00rootroot00000000000000/** * @file MEGAFolderInfo.mm * @brief Folder info * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAFolderInfo.h" #import "megaapi.h" using namespace mega; @interface MEGAFolderInfo() @property MegaFolderInfo *megaFolderInfo; @property BOOL cMemoryOwn; @end @implementation MEGAFolderInfo - (instancetype)initWithMegaFolderInfo:(MegaFolderInfo *)megaFolderInfo cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaFolderInfo = megaFolderInfo; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaFolderInfo; } } - (MegaFolderInfo *)getCPtr { return self.megaFolderInfo; } - (NSInteger)versions { return self.megaFolderInfo ? self.megaFolderInfo->getNumVersions() : 0; } - (NSInteger)files { return self.megaFolderInfo ? self.megaFolderInfo->getNumFiles() : 0; } - (NSInteger)folders { return self.megaFolderInfo ? self.megaFolderInfo->getNumFolders() : 0; } - (long long)currentSize { return self.megaFolderInfo ? self.megaFolderInfo->getCurrentSize() : 0; } - (long long)versionsSize { return self.megaFolderInfo ? self.megaFolderInfo->getVersionsSize() : 0; } @end sdk-10.11.0/bindings/ios/MEGAHandleList.mm000066400000000000000000000042001516266226600200460ustar00rootroot00000000000000/** * @file MEGAHandleList.mm * @brief List of MegaHandle * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAHandleList.h" #import "megaapi.h" using namespace mega; @interface MEGAHandleList () @property MegaHandleList *megaHandleList; @property BOOL cMemoryOwn; @end @implementation MEGAHandleList - (instancetype)init { self = [super init]; if (self != nil) { _megaHandleList = MegaHandleList::createInstance(); } return self; } - (instancetype)initWithMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaHandleList = MegaHandleList::createInstance(); _cMemoryOwn = cMemoryOwn; } return self; } - (instancetype)initWithMegaHandleList:(MegaHandleList *)megaHandleList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaHandleList = megaHandleList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaHandleList; } } - (MegaHandleList *)getCPtr { return self.megaHandleList; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: size=%ld>", [self class], (long)self.size]; } - (NSUInteger)size { return self.megaHandleList ? self.megaHandleList->size() : 0; } - (void)addMegaHandle:(uint64_t)handle { if (!self.megaHandleList) return; self.megaHandleList->addMegaHandle(handle); } - (uint64_t)megaHandleAtIndex:(NSUInteger)index { return self.megaHandleList ? self.megaHandleList->get((unsigned int)index) : INVALID_HANDLE; } @end sdk-10.11.0/bindings/ios/MEGAIntegerList.mm000066400000000000000000000027561516266226600202660ustar00rootroot00000000000000/** * @file MEGAIntegerList.mm * @brief List of integers * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAIntegerList.h" #import "MEGAIntegerList+init.h" using namespace mega; @interface MEGAIntegerList () @property MegaIntegerList *megaIntegerList; @property BOOL cMemoryOwn; @end @implementation MEGAIntegerList - (instancetype)initWithMegaIntegerList:(mega::MegaIntegerList *)megaIntegerList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaIntegerList = megaIntegerList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaIntegerList; } } - (mega::MegaIntegerList *)getCPtr { return self.megaIntegerList; } - (NSInteger)size { return self.megaIntegerList ? self.megaIntegerList->size() : 0; } - (int64_t)integerAtIndex:(NSInteger)index { return self.megaIntegerList ? self.megaIntegerList->get((int)index) : -1; } @end sdk-10.11.0/bindings/ios/MEGANetworkConnectivityResults.mm000066400000000000000000000051071516266226600234400ustar00rootroot00000000000000/** * @file MEGANetworkConnectivityTestResults.mm * @brief Container to store the network connectivity test results. * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANetworkConnectivityTestResults.h" #import "megaapi.h" using namespace mega; @interface MEGANetworkConnectivityTestResults () @property (nonatomic, assign) MegaNetworkConnectivityTestResults *megaNetworkConnectivityTestResults; @property (nonatomic, assign) BOOL cMemoryOwn; @end @implementation MEGANetworkConnectivityTestResults - (instancetype)initWithMegaNetworkConnectivityTestResults:(MegaNetworkConnectivityTestResults *)megaNetworkConnectivityTestResults cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaNetworkConnectivityTestResults = megaNetworkConnectivityTestResults; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaNetworkConnectivityTestResults; } } - (MegaNetworkConnectivityTestResults *)getCPtr { return self.megaNetworkConnectivityTestResults; } - (MEGANetworkConnectivityTestResult)ipv4UDP { return (MEGANetworkConnectivityTestResult) (self.megaNetworkConnectivityTestResults ? self.megaNetworkConnectivityTestResults->getIPv4UDP() : -1); } - (MEGANetworkConnectivityTestResult)ipv4DNS { return (MEGANetworkConnectivityTestResult) (self.megaNetworkConnectivityTestResults ? self.megaNetworkConnectivityTestResults->getIPv4DNS() : -1); } - (MEGANetworkConnectivityTestResult)ipv6UDP { return (MEGANetworkConnectivityTestResult) (self.megaNetworkConnectivityTestResults ? self.megaNetworkConnectivityTestResults->getIPv6UDP() : -1); } - (MEGANetworkConnectivityTestResult)ipv6DNS { return (MEGANetworkConnectivityTestResult) (self.megaNetworkConnectivityTestResults ? self.megaNetworkConnectivityTestResults->getIPv6DNS() : -1); } - (MEGANetworkConnectivityTestResults *)copy { return [[MEGANetworkConnectivityTestResults alloc] initWithMegaNetworkConnectivityTestResults:self.megaNetworkConnectivityTestResults->copy() cMemoryOwn:YES]; } @end sdk-10.11.0/bindings/ios/MEGANode.mm000066400000000000000000000224621516266226600167160ustar00rootroot00000000000000/** * @file MEGANode.mm * @brief Represents a node (file/folder) in the MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANode.h" #import "megaapi.h" #import "PasswordNodeData.h" #import "MEGACreditCardNodeData+init.h" #import "MEGAStringList+init.h" #import "MEGATOTPData+init.h" using namespace mega; @interface MEGANode() @property MegaNode *megaNode; @property BOOL cMemoryOwn; @end @implementation MEGANode - (instancetype)initWithMegaNode:(MegaNode *)megaNode cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaNode = megaNode; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaNode; } } - (BOOL)isEqual:(id)object { if (![object isKindOfClass:[MEGANode class]]) { return false; } return self.handle == ((MEGANode *)object).handle; } - (NSUInteger)hash { return self.handle; } - (MegaNode *)getCPtr { return self.megaNode; } - (MEGANodeType)type { return (MEGANodeType) (self.megaNode ? self.megaNode->getType() : MegaNode::TYPE_UNKNOWN); } - (NSString *)name { if(!self.megaNode) return nil; return self.megaNode->getName() ? [[NSString alloc] initWithUTF8String:self.megaNode->getName()] : nil; } - (NSString *)fingerprint { if(!self.megaNode) return nil; return self.megaNode->getFingerprint() ? [[NSString alloc] initWithUTF8String:self.megaNode->getFingerprint()] : nil; } - (PasswordNodeData *)passwordNodeData { if (!self.megaNode || self.megaNode->getPasswordData() == nil) return nil; MegaNode::PasswordNodeData *data = self.megaNode->getPasswordData(); if (data->password() == nil) return nil; NSString *pwd = [NSString stringWithUTF8String:data->password()]; NSString *notes = data->notes() ? [NSString stringWithUTF8String:data->notes()] : nil; NSString *url = data->url() ? [NSString stringWithUTF8String:data->url()] : nil; NSString *un = data->userName() ? [NSString stringWithUTF8String:data->userName()] : nil; MEGATOTPData *totp = data->totpData() ? [[MEGATOTPData alloc] initWithMegaTotpData:data->totpData()->copy() cMemoryOwn:YES] : nil; PasswordNodeData *passwordNodeData = [[PasswordNodeData alloc] initWithPassword:pwd notes:notes url:url userName:un totp:totp]; return passwordNodeData; } - (nullable MEGACreditCardNodeData *)creditCardNodeData { if (!self.megaNode || self.megaNode->getCreditCardData() == nil) return nil; return [[MEGACreditCardNodeData alloc] initWithMegaCreditCardNodeData:self.megaNode->getCreditCardData() cMemoryOwn:YES]; } - (NSInteger)duration { return self.megaNode ? self.megaNode->getDuration() : -1; } - (NSInteger)width { return self.megaNode ? self.megaNode->getWidth() : -1; } - (NSInteger)height { return self.megaNode ? self.megaNode->getHeight(): -1; } - (NSInteger)shortFormat { return self.megaNode ? self.megaNode->getShortformat() : -1; } - (NSInteger)videoCodecId { return self.megaNode ? self.megaNode->getVideocodecid(): -1; } - (BOOL)isFavourite { return self.megaNode ? self.megaNode->isFavourite() : NO; } - (BOOL)isMarkedSensitive { return self.megaNode ? self.megaNode->isMarkedSensitive() : NO; } - (nullable NSString *)description { if(!self.megaNode || !self.megaNode->getDescription()) return nil; return [NSString.alloc initWithUTF8String:self.megaNode->getDescription()]; } - (MEGANodeLabel)label { return (MEGANodeLabel) (self.megaNode ? self.megaNode->getLabel() : 0); } - (NSNumber *)latitude { if (!self.megaNode) return nil; double latitude = self.megaNode->getLatitude(); return latitude != MegaNode::INVALID_COORDINATE ? [NSNumber numberWithDouble:latitude] : nil; } - (NSNumber *)longitude { if (!self.megaNode) return nil; double longitude = self.megaNode->getLongitude(); return longitude != MegaNode::INVALID_COORDINATE ? [NSNumber numberWithDouble:longitude] : nil; } - (NSString *)base64Handle { if (!self.megaNode) return nil; const char *val = self.megaNode->getBase64Handle(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSNumber *)size { return self.megaNode ? [[NSNumber alloc] initWithUnsignedLongLong:self.megaNode->getSize()] : nil; } - (NSDate *)creationTime { return self.megaNode ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaNode->getCreationTime()] : nil; } - (NSDate *)modificationTime { return self.megaNode ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaNode->getModificationTime()] : nil; } - (NSDate *)publicLinkCreationTime { return self.megaNode ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaNode->getPublicLinkCreationTime()] : nil; } - (uint64_t)handle { return self.megaNode ? self.megaNode->getHandle() : ::mega::INVALID_HANDLE; } - (uint64_t)restoreHandle { return self.megaNode ? self.megaNode->getRestoreHandle() : ::mega::INVALID_HANDLE; } - (uint64_t)parentHandle { return self.megaNode ? self.megaNode->getParentHandle() : ::mega::INVALID_HANDLE; } - (int64_t)expirationTime { return self.megaNode ? self.megaNode->getExpirationTime() : -1; } - (uint64_t)publicHandle { return self.megaNode ? self.megaNode->getPublicHandle() : mega::INVALID_HANDLE; } - (MEGANode *)publicNode { return self.megaNode ? [[MEGANode alloc] initWithMegaNode:self.megaNode->getPublicNode() cMemoryOwn:YES] : nil; } - (NSString *)publicLink { const char *val = self.megaNode->getPublicLink(); if (!val) return nil; NSString *ret = [NSString stringWithUTF8String:val]; delete [] val; return ret; } - (uint64_t)owner { return self.megaNode ? self.megaNode->getOwner() : ::mega::INVALID_HANDLE; } - (NSString *)deviceId { if (self.megaNode) { const char *val = self.megaNode->getDeviceId(); if (val) { return [NSString stringWithUTF8String:val]; } } return nil; } - (BOOL)isFile { return self.megaNode ? self.megaNode->isFile() : NO; } - (BOOL)isFolder { return self.megaNode ? self.megaNode->isFolder() : NO; } - (BOOL)isRemoved { return self.megaNode ? self.megaNode->isRemoved() : NO; } - (BOOL)hasChangedType:(MEGANodeChangeType)changeType { return self.megaNode ? self.megaNode->hasChanged(int(changeType)) : NO; } - (MEGANodeChangeType)getChanges { return (MEGANodeChangeType) (self.megaNode ? self.megaNode->getChanges() : 0); } - (BOOL)hasThumbnail { return self.megaNode ? self.megaNode->hasThumbnail() : NO; } - (BOOL)hasPreview { return self.megaNode ? self.megaNode->hasPreview() : NO; } - (BOOL)isPublic { return self.megaNode ? self.megaNode->isPublic() : NO; } - (BOOL)isShared { return self.megaNode ? self.megaNode->isShared() : NO; } - (BOOL)isOutShare { return self.megaNode ? self.megaNode->isOutShare() : NO; } - (BOOL)isInShare { return self.megaNode ? self.megaNode->isInShare() : NO; } - (BOOL)isExported { return self.megaNode ? self.megaNode->isExported() : NO; } - (BOOL)isExpired { return self.megaNode ? self.megaNode->isExpired() : NO; } - (BOOL)isTakenDown { return self.megaNode ? self.megaNode->isTakenDown() : NO; } - (BOOL)isForeign { return self.megaNode ? self.megaNode->isForeign() : NO; } - (BOOL)isNodeKeyDecrypted { return self.megaNode ? self.megaNode->isNodeKeyDecrypted() : NO; } - (BOOL)isPasswordManagerNode { return self.megaNode ? self.megaNode->isPasswordManagerNode() : false; } - (BOOL)isPasswordNode { return self.megaNode ? self.megaNode->isPasswordNode() : NO; } - (BOOL)isCreditCardNode { return self.megaNode ? self.megaNode->isCreditCardNode() : NO; } - (MEGAStringList *)tags { return self.megaNode ? [[MEGAStringList alloc] initWithMegaStringList:self.megaNode->getTags() cMemoryOwn:YES] : nil; } + (NSString *)stringForNodeLabel:(MEGANodeLabel)nodeLabel { NSString *result; switch (nodeLabel) { case MEGANodeLabelUnknown: result = @""; break; case MEGANodeLabelRed: result = @"Red"; break; case MEGANodeLabelOrange: result = @"Orange"; break; case MEGANodeLabelYellow: result = @"Yellow"; break; case MEGANodeLabelGreen: result = @"Green"; break; case MEGANodeLabelBlue: result = @"Blue"; break; case MEGANodeLabelPurple: result = @"Purple"; break; case MEGANodeLabelGrey: result = @"Grey"; break; default: result = @""; break; } return result; } @end sdk-10.11.0/bindings/ios/MEGANodeList.mm000066400000000000000000000036031516266226600175460ustar00rootroot00000000000000/** * @file MEGANodeList.mm * @brief List of MEGANode objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANodeList.h" #import "MEGANode+init.h" using namespace mega; @interface MEGANodeList () @property MegaNodeList *nodeList; @property BOOL cMemoryOwn; @end @implementation MEGANodeList - (instancetype)init { self = [super init]; if (self != nil) { _nodeList = self.nodeList->createInstance(); _cMemoryOwn = YES; } return self; } - (instancetype)initWithNodeList:(MegaNodeList *)nodelist cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _nodeList = nodelist; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _nodeList; } } - (MegaNodeList *)getCPtr { return self.nodeList; } - (void)addNode:(MEGANode *)node { if (node == nil) return; self.nodeList->addNode([node getCPtr]); } - (nullable MEGANode *)nodeAtIndex:(NSInteger)index { if (self.nodeList == NULL) { return nil; } MegaNode *node = self.nodeList->get((int)index); if (node) { return [[MEGANode alloc] initWithMegaNode:node->copy() cMemoryOwn:YES]; } else { return nil; } } - (NSInteger)size { return self.nodeList ? self.nodeList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGANotification.mm000066400000000000000000000067171516266226600204640ustar00rootroot00000000000000/** * @file MEGAUserAlert.mm * @brief Represents a user alert in MEGA. * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANotification.h" #import "megaapi.h" using namespace mega; @interface MEGANotification () @property MegaNotification *megaNotification; @property BOOL cMemoryOwn; @end @implementation MEGANotification - (instancetype)initWithMegaNotification:(MegaNotification *)megaNotification cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaNotification = megaNotification; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaNotification; } } - (MegaNotification *)getCPtr { return self.megaNotification; } - (NSUInteger)identifier { return self.megaNotification ? self.megaNotification->getID() : 0; } - (nullable NSString *)title { return self.megaNotification ? [[NSString alloc] initWithUTF8String:self.megaNotification->getTitle()] : nil; } - (nullable NSString *)description { return self.megaNotification ? [[NSString alloc] initWithUTF8String:self.megaNotification->getDescription()] : nil; } - (nullable NSString *)imageName { return self.megaNotification ? [[NSString alloc] initWithUTF8String:self.megaNotification->getImageName()] : nil; } - (nullable NSString *)imagePath { return self.megaNotification ? [[NSString alloc] initWithUTF8String:self.megaNotification->getImagePath()] : nil; } - (nullable NSString *)iconName { return self.megaNotification ? [[NSString alloc] initWithUTF8String:self.megaNotification->getIconName()] : nil; } - (nullable NSDate *)startDate { return self.megaNotification ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaNotification->getStart()] : nil; } - (nullable NSDate *)endDate { return self.megaNotification ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaNotification->getEnd()] : nil; } - (BOOL)shouldShowBanner { return self.megaNotification ? self.megaNotification->showBanner() : NO; } - (nullable NSDictionary *)firstCallToAction { if (!self.megaNotification) { return nil; } const MegaStringMap *map = self.megaNotification->getCallToAction1(); return [self callToAction:map]; } - (nullable NSDictionary *)secondCallToAction { if (!self.megaNotification) { return nil; } const MegaStringMap *map = self.megaNotification->getCallToAction2(); return [self callToAction:map]; } #pragma mark - Private - (NSDictionary *)callToAction:(const MegaStringMap *)map { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map->size()]; MegaStringList *keyList = map->getKeys(); for (int i = 0; i < keyList->size(); i++) { const char *key = keyList->get(i); dict[@(key)] = @(map->get(key)); } delete keyList; return [dict copy]; } @end sdk-10.11.0/bindings/ios/MEGANotificationList.mm000066400000000000000000000032071516266226600213070ustar00rootroot00000000000000/** * @file MEGANotificationList.h * @brief List of MEGANotification objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "megaapi.h" #import "MEGANotificationList.h" #import "MEGANotification+init.h" using namespace mega; @interface MEGANotificationList () @property MegaNotificationList *megaNotificationList; @property BOOL cMemoryOwn; @end @implementation MEGANotificationList - (instancetype)initWithMegaNotificationList:(MegaNotificationList *)megaNotificationList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaNotificationList = megaNotificationList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaNotificationList; } } - (nullable MEGANotification *)notificationAtIndex:(NSInteger)index { return self.megaNotificationList ? [[MEGANotification alloc] initWithMegaNotification:self.megaNotificationList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSInteger)size { return self.megaNotificationList ? self.megaNotificationList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGAPricing.mm000066400000000000000000000061611516266226600174220ustar00rootroot00000000000000/** * @file MEGAPricing.mm * @brief Details about pricing plans * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAPricing.h" #import "megaapi.h" using namespace mega; @interface MEGAPricing () @property MegaPricing *pricing; @property BOOL cMemoryOwn; @end @implementation MEGAPricing - (instancetype)initWithMegaPricing:(MegaPricing *)pricing cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _pricing = pricing; _cMemoryOwn = cMemoryOwn; } return self; } - (MegaPricing *)getCPtr { return self.pricing; } - (void)dealloc { if (self.cMemoryOwn) { delete _pricing; } } - (NSInteger)products { return self.pricing ? self.pricing->getNumProducts() : 0; } - (uint64_t)handleAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getHandle((int)index) : INVALID_HANDLE; } - (MEGAAccountType)proLevelAtProductIndex:(NSInteger)index { return (MEGAAccountType) (self.pricing ? self.pricing->getProLevel((int)index) : 0); } - (NSInteger)storageGBAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getGBStorage((int)index) : 0; } - (NSInteger)transferGBAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getGBTransfer((int)index) : 0; } - (NSInteger)monthsAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getMonths((int)index) : 0; } - (NSInteger)amountAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getAmount((int)index) : 0; } - (NSInteger)localPriceAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getLocalPrice((int)index) : 0; } - (NSString *)descriptionAtProductIndex:(NSInteger)index { return self.pricing ? [[NSString alloc] initWithUTF8String:self.pricing->getDescription((int)index)] : nil; } - (NSString *)iOSIDAtProductIndex:(NSInteger)index { return self.pricing ? [[NSString alloc] initWithUTF8String:self.pricing->getIosID((int)index)] : nil; } - (unsigned int)trialDurationInDaysAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->getTrialDurationInDays((int)index) : 0; } - (BOOL)hasMobileOffersAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->hasMobileOffers((int)index) : NO; } - (NSString *)mobileOfferIdAtProductIndex:(NSInteger)index { return self.pricing ? [[NSString alloc] initWithUTF8String:self.pricing->getMobileOfferId((int)index).c_str()] : nil; } - (BOOL)hasMobileOfferUatAtProductIndex:(NSInteger)index { return self.pricing ? self.pricing->hasMobileOfferUat((int)index) : NO; } @end sdk-10.11.0/bindings/ios/MEGAPushNotificationSettings.mm000066400000000000000000000056171516266226600230430ustar00rootroot00000000000000/** * @file MEGAPushNotificationSettings.mm * @brief Push Notification related MEGASdk methods. * * (c) 2019- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAPushNotificationSettings.h" #import "megaapi.h" #import "MEGAPushNotificationSettings+init.h" using namespace mega; @interface MEGAPushNotificationSettings () @property MegaPushNotificationSettings *megaPushNotificationSettings; @property BOOL cMemoryOwn; @end @implementation MEGAPushNotificationSettings #pragma mark - Initializer. - (instancetype)init { self = [super init]; if (self != nil) { _megaPushNotificationSettings = MegaPushNotificationSettings::createInstance(); _cMemoryOwn = YES; } return self; } #pragma mark - Private methods. - (instancetype)initWithMegaPushNotificationSettings:(MegaPushNotificationSettings *)megaPushNotificationSettings cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaPushNotificationSettings = megaPushNotificationSettings; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaPushNotificationSettings; } } - (MegaPushNotificationSettings *)getCPtr { return self.megaPushNotificationSettings; } #pragma mark - Property getter and setter methods. - (int64_t)globalChatsDNDTimestamp { return self.megaPushNotificationSettings->getGlobalChatsDnd(); } - (void)setGlobalChatsDNDTimestamp:(int64_t)globalChatsDNDTimestamp { self.megaPushNotificationSettings->setGlobalChatsDnd(globalChatsDNDTimestamp); } - (BOOL)globalChatsDndEnabled { return self.megaPushNotificationSettings->isGlobalChatsDndEnabled(); } - (void)setGlobalChatsDndEnabled:(BOOL)globalChatsDndEnabled { self.megaPushNotificationSettings->enableChats(!globalChatsDndEnabled); } #pragma mark - Interface methods. - (BOOL)isChatDndEnabledForChatId:(uint64_t)chatId { return self.megaPushNotificationSettings->isChatDndEnabled(chatId); } - (void)setChatEnabled:(BOOL)enabled forChatId:(uint64_t)chatId { self.megaPushNotificationSettings->enableChat(chatId, enabled); } - (int64_t)timestampForChatId:(uint64_t)chatId { return self.megaPushNotificationSettings->getChatDnd(chatId); } - (void)setChatDndForChatId:(uint64_t)chatId untilTimestamp:(int64_t)timestamp { self.megaPushNotificationSettings->setChatDnd(chatId, timestamp); } @end sdk-10.11.0/bindings/ios/MEGARecentActionBucket.mm000066400000000000000000000046031516266226600215420ustar00rootroot00000000000000/** * @file MEGARecentActionBucket.mm * @brief Represents a set of files uploaded or updated in MEGA. * * (c) 2019 - Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGARecentActionBucket.h" #import "megaapi.h" #import "MEGARecentActionBucket+init.h" #import "MEGANodeList+init.h" using namespace mega; @interface MEGARecentActionBucket () @property MegaRecentActionBucket *recentActionBucket; @property BOOL cMemoryOwn; @end @implementation MEGARecentActionBucket - (instancetype)initWithMegaRecentActionBucket:(MegaRecentActionBucket *)megaRecentActionBucket cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _recentActionBucket = megaRecentActionBucket; _cMemoryOwn = cMemoryOwn; } return self; } - (MegaRecentActionBucket *)getCPtr { return self.recentActionBucket; } - (void)dealloc { if (self.cMemoryOwn) { delete _recentActionBucket; } } - (NSDate *)timestamp { return self.recentActionBucket ? [NSDate.alloc initWithTimeIntervalSince1970:self.recentActionBucket->getTimestamp()] : nil; } - (NSString *)userEmail { if (self.recentActionBucket) { return self.recentActionBucket->getUserEmail() ? [NSString.alloc initWithUTF8String:self.recentActionBucket->getUserEmail()] : nil; } else { return nil; } } - (uint64_t)parentHandle { return self.recentActionBucket ? self.recentActionBucket->getParentHandle() : ::mega::INVALID_HANDLE; } - (BOOL)isUpdate { return self.recentActionBucket->isUpdate(); } - (BOOL)isMedia { return self.recentActionBucket->isMedia(); } - (NSString *)bucketId { return self.recentActionBucket ? @(self.recentActionBucket->getId()) : nil; } - (MEGANodeList *)nodesList { return self.recentActionBucket ? [MEGANodeList.alloc initWithNodeList:self.recentActionBucket->getNodes()->copy() cMemoryOwn:YES] : nil; } @end sdk-10.11.0/bindings/ios/MEGARequest.mm000066400000000000000000000333051516266226600174570ustar00rootroot00000000000000/** * @file MEGARequest.mm * @brief Provides information about an asynchronous request * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGARequest.h" #import "MEGANode+init.h" #import "MEGASet+init.h" #import "MEGASetElement+init.h" #import "MEGAPricing+init.h" #import "MEGAAccountDetails+init.h" #import "MEGAAchievementsDetails+init.h" #import "MEGAFolderInfo+init.h" #import "MEGATimeZoneDetails+init.h" #import "MEGAStringList+init.h" #import "MEGAPushNotificationSettings+init.h" #import "MEGABannerList.h" #import "MEGABannerList+init.h" #import "MEGAHandleList+init.h" #import "MEGACurrency+init.h" #import "MEGARecentActionBucket+init.h" #import "MEGABackupInfo+init.h" #import "MEGAVPNCredentials.h" #import "MEGAVPNCredentials+init.h" #import "MEGAVPNRegion.h" #import "MEGAVPNRegion+init.h" #import "MEGANetworkConnectivityTestResults+init.h" #import "MEGANotificationList+init.h" #import "MEGAIntegerList+init.h" using namespace mega; @interface MEGARequest() @property MegaRequest *megaRequest; @property BOOL cMemoryOwn; @end @implementation MEGARequest - (instancetype)initWithMegaRequest:(MegaRequest *)megaRequest cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaRequest = megaRequest; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn){ delete _megaRequest; } } - (MegaRequest *)getCPtr { return self.megaRequest; } - (MEGARequestType)type { return (MEGARequestType) (self.megaRequest ? self.megaRequest->getType() : -1); } - (nullable NSString *)requestString { if(!self.megaRequest) return nil; return self.megaRequest->getRequestString() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getRequestString()] : nil; } - (uint64_t)nodeHandle { return self.megaRequest ? self.megaRequest->getNodeHandle() : ::mega::INVALID_HANDLE; } - (nullable NSString *)link { if (!self.megaRequest) return nil; return self.megaRequest->getLink() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getLink()] : nil; } - (uint64_t)parentHandle { return self.megaRequest ? self.megaRequest->getParentHandle() : ::mega::INVALID_HANDLE; } - (nullable NSString *)sessionKey { if (!self.megaRequest) return nil; return self.megaRequest->getSessionKey() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getSessionKey()] : nil; } - (nullable NSString *)name { if (!self.megaRequest) return nil; return self.megaRequest->getName() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getName()] : nil; } - (nullable NSString *)email { if (!self.megaRequest) return nil; return self.megaRequest->getEmail() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getEmail()] : nil; } - (nullable NSString *)password { if (!self.megaRequest) return nil; return self.megaRequest->getPassword() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getPassword()] : nil; } - (nullable NSString *)newPassword { if (!self.megaRequest) return nil; return self.megaRequest->getNewPassword() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getNewPassword()] : nil; } - (MEGANodeAccessLevel)access { return (MEGANodeAccessLevel) (self.megaRequest ? self.megaRequest->getAccess() : -1); } - (nullable NSString *)file { if (!self.megaRequest) return nil; return self.megaRequest->getFile() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getFile()] : nil; } - (NSInteger)numRetry { return self.megaRequest->getNumRetry() ? self.megaRequest->getNumRetry() : 0; } - (nullable MEGANode *)publicNode { return self.megaRequest && self.megaRequest->getPublicMegaNode() ? [[MEGANode alloc] initWithMegaNode:self.megaRequest->getPublicMegaNode() cMemoryOwn:YES] : nil; } - (NSInteger)paramType { return self.megaRequest ? self.megaRequest->getParamType() : 0; } - (nullable NSString *)text { return self.megaRequest->getText() ? [[NSString alloc] initWithUTF8String:self.megaRequest->getText()] : nil; } - (long long)number { return self.megaRequest ? self.megaRequest->getNumber() : 0; } - (BOOL)flag { return self.megaRequest ? self.megaRequest->getFlag() : NO; } - (long long)transferredBytes { return self.megaRequest ? self.megaRequest->getTransferredBytes() : 0; } - (long long)totalBytes { return self.megaRequest ? self.megaRequest->getTotalBytes() : 0; } - (nullable MEGAAccountDetails *)megaAccountDetails { return self.megaRequest ? [[MEGAAccountDetails alloc] initWithMegaAccountDetails:self.megaRequest->getMegaAccountDetails() cMemoryOwn:YES] : nil; } - (nullable MEGAPricing *)pricing { return self.megaRequest ? [[MEGAPricing alloc] initWithMegaPricing:self.megaRequest->getPricing() cMemoryOwn:YES] : nil; } - (nullable MEGACurrency *)currency { return self.megaRequest ? [[MEGACurrency alloc] initWithMegaCurrency:self.megaRequest->getCurrency() cMemoryOwn:YES] : nil; } - (nullable MEGAAchievementsDetails *)megaAchievementsDetails { return self.megaRequest ? [[MEGAAchievementsDetails alloc] initWithMegaAchievementsDetails:self.megaRequest->getMegaAchievementsDetails() cMemoryOwn:YES] : nil; } - (nullable MEGATimeZoneDetails *)megaTimeZoneDetails { return self.megaRequest ? [[MEGATimeZoneDetails alloc] initWithMegaTimeZoneDetails:self.megaRequest->getMegaTimeZoneDetails() cMemoryOwn:YES] : nil; } - (nullable MEGAFolderInfo *)megaFolderInfo { return self.megaRequest ? [[MEGAFolderInfo alloc] initWithMegaFolderInfo:self.megaRequest->getMegaFolderInfo()->copy() cMemoryOwn:YES] : nil; } - (nullable MEGAPushNotificationSettings *)megaPushNotificationSettings { if (!self.megaRequest) return nil; return self.megaRequest->getMegaPushNotificationSettings() ? [MEGAPushNotificationSettings.alloc initWithMegaPushNotificationSettings:self.megaRequest->getMegaPushNotificationSettings()->copy() cMemoryOwn:YES] : nil; } - (nullable NSDictionary *)megaStringListDictionary { if (!self.megaRequest) { return nil; } MegaStringListMap *map = self.megaRequest->getMegaStringListMap(); NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map->size()]; MegaStringList *keyList = map->getKeys(); for (int i = 0; i < keyList->size(); i++) { const char *key = keyList->get(i); dict[@(key)] = [[MEGAStringList alloc] initWithMegaStringList:(MegaStringList *)map->get(key)->copy() cMemoryOwn:YES]; } delete keyList; return [dict copy]; } - (nullable NSDictionary *)megaStringDictionary { if (!self.megaRequest) { return nil; } MegaStringMap *map = self.megaRequest->getMegaStringMap(); NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map->size()]; MegaStringList *keyList = map->getKeys(); for (int i = 0; i < keyList->size(); i++) { const char *key = keyList->get(i); dict[@(key)] = @(map->get(key)); } delete keyList; return [dict copy]; } - (nullable NSDictionary *)megaStringIntegerDictionary { if (!self.megaRequest) { return nil; } MegaStringIntegerMap *map = self.megaRequest->getMegaStringIntegerMap(); NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map->size()]; MegaStringList *keyList = map->getKeys(); for (int i = 0; i < keyList->size(); i++) { const char *key = keyList->get(i); MegaIntegerList* errorCode = map->get(key); MEGAIntegerList* value = errorCode != nil ? [[MEGAIntegerList alloc] initWithMegaIntegerList:errorCode cMemoryOwn:YES] : nil; dict[@(key)] = value; } delete keyList; return [dict copy]; } - (NSInteger)transferTag { return self.megaRequest ? self.megaRequest->getTransferTag() : 0; } - (NSInteger)numDetails { return self.megaRequest ? self.megaRequest->getNumDetails() : 0; } - (nullable NSArray *> *)stringTableArray { if (!self.megaRequest) { return nil; } MegaStringTable *table = self.megaRequest->getMegaStringTable(); NSMutableArray *> *stringTableArray = [NSMutableArray.alloc initWithCapacity:table->size()]; for (int i = 0; i < table->size(); i++) { const MegaStringList *stringList = table->get(i); NSMutableArray *stringsArray = [NSMutableArray.alloc initWithCapacity:stringList->size()]; for (int j = 0; j < stringList->size(); j++) { [stringsArray addObject:[NSString stringWithUTF8String:stringList->get(j)]]; } [stringTableArray addObject:stringsArray.copy]; } return stringTableArray.copy; } - (nullable MEGABannerList *)bannerList { if (!self.megaRequest) { return nil; } MegaBannerList *bannerList = self.megaRequest->getMegaBannerList() -> copy(); return [[MEGABannerList alloc] initWithMegaBannerList:bannerList cMemoryOwn:YES]; } - (nullable NSArray *)megaHandleArray { if (!self.megaRequest) { return nil; } MEGAHandleList *handleList = [MEGAHandleList.alloc initWithMegaHandleList:self.megaRequest->getMegaHandleList()->copy() cMemoryOwn:YES]; NSMutableArray *handleArray = [NSMutableArray.alloc initWithCapacity:handleList.size]; for (int i = 0; i < handleList.size; i++) { [handleArray addObject:[NSNumber numberWithUnsignedLongLong:[handleList megaHandleAtIndex:i]]]; } return handleArray.copy; } - (nullable NSArray *)recentActionsBuckets { if (!self.megaRequest) { return nil; } MegaRecentActionBucketList *megaRecentActionBucketList = self.megaRequest->getRecentActions(); int count = megaRecentActionBucketList->size(); NSMutableArray *recentActionBucketMutableArray = [NSMutableArray.alloc initWithCapacity:(NSInteger)count]; for (int i = 0; i < count; i++) { MEGARecentActionBucket *recentActionBucket = [MEGARecentActionBucket.alloc initWithMegaRecentActionBucket:megaRecentActionBucketList->get(i)->copy() cMemoryOwn:YES]; [recentActionBucketMutableArray addObject:recentActionBucket]; } return recentActionBucketMutableArray; } - (nullable NSArray *)backupInfoList { if (!self.megaRequest) { return nil; } MegaBackupInfoList *megaBackupInfoList = self.megaRequest->getMegaBackupInfoList(); int count = megaBackupInfoList->size(); NSMutableArray *backupInfoMutableArray = [NSMutableArray.alloc initWithCapacity:(NSInteger)count]; for (int i = 0; i < count; i++) { MEGABackupInfo *megaBackupInfo = [MEGABackupInfo.alloc initWithMegaBackupInfo:megaBackupInfoList->get(i)->copy() cMemoryOwn:YES]; [backupInfoMutableArray addObject:megaBackupInfo]; } return backupInfoMutableArray; } - (nullable MEGASet *)set { return self.megaRequest ? [[MEGASet alloc] initWithMegaSet:self.megaRequest->getMegaSet()->copy() cMemoryOwn:YES] : nil; } - (nullable NSArray *)elementsInSet { if (!self.megaRequest) { return nil; } MegaSetElementList *setElementList = self.megaRequest->getMegaSetElementList(); int size = setElementList->size(); NSMutableArray *setElements = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { [setElements addObject:[[MEGASetElement alloc] initWithMegaSetElement:setElementList->get(i)->copy() cMemoryOwn:YES]]; } return [setElements copy]; } - (nullable MEGAStringList *)megaStringList { return self.megaRequest ? [[MEGAStringList alloc] initWithMegaStringList:self.megaRequest->getMegaStringList()->copy() cMemoryOwn:YES] : nil; } - (nullable MEGAVPNCredentials *)megaVpnCredentials { return self.megaRequest ? [[MEGAVPNCredentials alloc] initWithMegaVpnCredentials:self.megaRequest->getMegaVpnCredentials()->copy() cMemoryOwn:YES] : nil; } - (nullable MEGANetworkConnectivityTestResults *)megaNetworkConnectivityTestResults { return self.megaRequest ? [[MEGANetworkConnectivityTestResults alloc] initWithMegaNetworkConnectivityTestResults:self.megaRequest->getMegaNetworkConnectivityTestResults()->copy() cMemoryOwn:YES] : nil; } - (nonnull NSArray *)megaVpnRegions { if (!self.megaRequest) { return @[]; } mega::MegaVpnRegionList *regionList = self.megaRequest->getMegaVpnRegionsDetailed(); if (!regionList) { return @[]; } int count = regionList->size(); NSMutableArray *regionsArray = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)count]; for (int i = 0; i < count; i++) { const mega::MegaVpnRegion *region = regionList->get(i); if (region) { MEGAVPNRegion *vpnRegion = [[MEGAVPNRegion alloc] initWithMegaVpnRegion:region->copy() cMemoryOwn:YES]; [regionsArray addObject:vpnRegion]; } } return regionsArray; } - (nullable MEGANotificationList*)megaNotifications { return self.megaRequest ? [[MEGANotificationList alloc] initWithMegaNotificationList:self.megaRequest->getMegaNotifications()->copy() cMemoryOwn:YES] : nil; } @end sdk-10.11.0/bindings/ios/MEGAScheduledCopy.mm000066400000000000000000000074561516266226600205720ustar00rootroot00000000000000/** * @file MEGAScheduledCopy.mm * @brief Provides information about a transfer * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAScheduledCopy.h" #import "megaapi.h" #import "MEGATransferList+init.h" using namespace mega; @interface MEGAScheduledCopy () @property MegaScheduledCopy *megaScheduledCopy; @property BOOL cMemoryOwn; @end @implementation MEGAScheduledCopy - (instancetype)initWithMegaScheduledCopy:(MegaScheduledCopy *)megaScheduledCopy cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaScheduledCopy = megaScheduledCopy; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaScheduledCopy; } } - (MegaScheduledCopy *)getCPtr { return self.megaScheduledCopy; } - (uint64_t)handle { return self.megaScheduledCopy ? self.megaScheduledCopy->getMegaHandle() : ::mega::INVALID_HANDLE; } - (NSString *)localFolder { if(!self.megaScheduledCopy) return nil; return self.megaScheduledCopy->getLocalFolder() ? [[NSString alloc] initWithUTF8String:self.megaScheduledCopy->getLocalFolder()] : nil; } - (NSUInteger)tag { return self.megaScheduledCopy ? self.megaScheduledCopy->getTag() : 0; } - (BOOL)attendPastBackups { return self.megaScheduledCopy ? self.megaScheduledCopy->getAttendPastBackups() : NO; } - (int64_t)period { return self.megaScheduledCopy ? self.megaScheduledCopy->getPeriod() : 0; } - (NSString *)periodString { if(!self.megaScheduledCopy) return nil; return self.megaScheduledCopy->getPeriodString() ? [[NSString alloc] initWithUTF8String:self.megaScheduledCopy->getPeriodString()] : nil; } - (long long)nextStartTime { return self.megaScheduledCopy ? self.megaScheduledCopy->getNextStartTime() : -1; } - (NSUInteger)maxBackups { return self.megaScheduledCopy ? self.megaScheduledCopy->getMaxBackups() : 0; } - (MEGAScheduledCopyState)state { return (MEGAScheduledCopyState) (self.megaScheduledCopy ? self.megaScheduledCopy->getState() : MEGAScheduledCopyStateFailed); } - (long long)numberFolders { return self.megaScheduledCopy ? self.megaScheduledCopy->getNumberFolders() : 0; } - (long long)numberFiles { return self.megaScheduledCopy ? self.megaScheduledCopy->getNumberFiles() : 0; } - (long long)totalFiles { return self.megaScheduledCopy ? self.megaScheduledCopy->getTotalFiles() : 0; } - (int64_t)currentBKStartTime { return self.megaScheduledCopy ? self.megaScheduledCopy->getCurrentBKStartTime() : 0; } - (long long)transferredBytes { return self.megaScheduledCopy ? self.megaScheduledCopy->getTransferredBytes() : 0; } - (long long)totalBytes { return self.megaScheduledCopy ? self.megaScheduledCopy->getTotalBytes() : 0; } - (long long)speed { return self.megaScheduledCopy ? self.megaScheduledCopy->getSpeed() : 0; } - (long long)meanSpeed { return self.megaScheduledCopy ? self.megaScheduledCopy->getMeanSpeed() : 0; } - (int64_t)updateTime { return self.megaScheduledCopy ? self.megaScheduledCopy->getUpdateTime() : 0; } - (MEGATransferList *)failedTransfers { if (self.megaScheduledCopy == nil) return nil; return [[MEGATransferList alloc] initWithTransferList:self.megaScheduledCopy->getFailedTransfers() cMemoryOwn:YES]; } @end sdk-10.11.0/bindings/ios/MEGASdk.mm000066400000000000000000004607261516266226600165630ustar00rootroot00000000000000/** * @file MEGASdk.mm * @brief Allows to control a MEGA account or a shared folder * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASdk.h" #import "megaapi.h" #import "MEGANode+init.h" #import "MEGATOTPData+init.h" #import "MEGASet+init.h" #import "MEGASetElement+init.h" #import "MEGAUser+init.h" #import "MEGATransfer+init.h" #import "MEGATransferList+init.h" #import "MEGANodeList+init.h" #import "MEGAUserList+init.h" #import "MEGAUserAlertList+init.h" #import "MEGAIntegerList+init.h" #import "MEGAStringList+init.h" #import "MEGAError+init.h" #import "MEGAShareList+init.h" #import "MEGAContactRequest+init.h" #import "MEGAContactRequestList+init.h" #import "MEGARecentActionBucket+init.h" #import "MEGABackgroundMediaUpload+init.h" #import "DelegateMEGARequestListener.h" #import "DelegateMEGATransferListener.h" #import "DelegateMEGAGlobalListener.h" #import "DelegateMEGAListener.h" #import "DelegateMEGALoggerListener.h" #import "DelegateMEGATreeProcessorListener.h" #import "DelegateMEGAScheduledCopyListener.h" #import "MEGAFileInputStream.h" #import "MEGADataInputStream.h" #import "MEGACancelToken+init.h" #import "MEGAPushNotificationSettings+init.h" #import "MEGACancelSubscriptionReasonList+init.h" #import #import NSString * const MEGAIsBeingLogoutNotification = @"nz.mega.isBeingLogout"; using namespace mega; @interface MEGASdk () { pthread_mutex_t listenerMutex; } @property (nonatomic, assign) std::setactiveRequestListeners; @property (nonatomic, assign) std::setactiveTransferListeners; @property (nonatomic, assign) std::setactiveGlobalListeners; @property (nonatomic, assign) std::setactiveMegaListeners; @property (nonatomic, assign) std::setactiveLoggerListeners; @property (nonatomic, assign) std::setactiveScheduledCopyListeners; - (MegaRequestListener *)createDelegateMEGARequestListener:(id)delegate singleListener:(BOOL)singleListener; - (MegaRequestListener *)createDelegateMEGARequestListener:(id)delegate singleListener:(BOOL)singleListener queueType:(ListenerQueueType)queueType; - (MegaTransferListener *)createDelegateMEGATransferListener:(id)delegate singleListener:(BOOL)singleListener; - (MegaTransferListener *)createDelegateMEGATransferListener:(id)delegate singleListener:(BOOL)singleListener queueType:(ListenerQueueType)queueType; - (MegaGlobalListener *)createDelegateMEGAGlobalListener:(id)delegate queueType:(ListenerQueueType)queueType; - (MegaListener *)createDelegateMEGAListener:(id)delegate; - (MegaLogger *)createDelegateMegaLogger:(id)delegate; - (MegaScheduledCopyListener *)createDelegateMEGAScheduledCopyListener:(id)delegate queueType:(ListenerQueueType)queueType; @property (nonatomic, nullable) MegaApi *megaApi; @end @implementation MEGASdk #pragma mark - Properties - (NSString *)myEmail { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getMyEmail(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSDate *)accountCreationDate { if (self.megaApi == nil) return nil; NSTimeInterval accountCreationTs = self.megaApi->getAccountCreationTs(); return accountCreationTs ? [NSDate dateWithTimeIntervalSince1970:accountCreationTs] : nil; } - (MEGANode *)rootNode { if (self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getRootNode(); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANode *)rubbishNode { if (self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getRubbishNode(); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGATransferList *)transfers { if (self.megaApi == nil) return nil; return [[MEGATransferList alloc] initWithTransferList:self.megaApi->getTransfers() cMemoryOwn:YES]; } - (MEGATransferList *)downloadTransfers { if (self.megaApi == nil) return nil; return [[MEGATransferList alloc] initWithTransferList:self.megaApi->getTransfers(MegaTransfer::TYPE_DOWNLOAD) cMemoryOwn:YES]; } - (MEGATransferList *)uploadTransfers { if (self.megaApi == nil) return nil; return [[MEGATransferList alloc] initWithTransferList:self.megaApi->getTransfers(MegaTransfer::TYPE_UPLOAD) cMemoryOwn:YES]; } - (Retry)waiting { if (self.megaApi == nil) return RetryUnknown; return (Retry) self.megaApi->isWaiting(); } - (unsigned long long)totalNodes { if (self.megaApi == nil) return 0; return self.megaApi->getNumNodes(); } - (NSString *)masterKey { if (self.megaApi == nil) return nil; const char *val = self.megaApi->exportMasterKey(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)userAgent { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getUserAgent(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; return ret; } - (MEGAUser *)myUser { if (self.megaApi == nil) return nil; MegaUser *user = self.megaApi->getMyUser(); return user ? [[MEGAUser alloc] initWithMegaUser:user cMemoryOwn:YES] : nil; } - (BOOL)isAchievementsEnabled { if (self.megaApi == nil) return NO; return self.megaApi->isAchievementsEnabled(); } - (BOOL)isContactVerificationWarningEnabled { if (self.megaApi == nil) return NO; return self.megaApi->contactVerificationWarningEnabled(); } - (BOOL)isNewAccount { if (self.megaApi == nil) return NO; return self.megaApi->accountIsNew(); } #pragma mark - Business - (BOOL)isBusinessAccount { if (self.megaApi == nil) return NO; return self.megaApi->isBusinessAccount(); } - (BOOL)isMasterBusinessAccount { if (self.megaApi == nil) return NO; return self.megaApi->isMasterBusinessAccount(); } - (BOOL)isBusinessAccountActive { if (self.megaApi == nil) return NO; return self.megaApi->isBusinessAccountActive(); } - (BusinessStatus)businessStatus { if (self.megaApi == nil) return BusinessStatusInactive; return (BusinessStatus) self.megaApi->getBusinessStatus(); } - (NSInteger)numUnreadUserAlerts { if (self.megaApi == nil) return 0; return self.megaApi->getNumUnreadUserAlerts(); } - (long long)bandwidthOverquotaDelay { if (self.megaApi == nil) return 0; return self.megaApi->getBandwidthOverquotaDelay(); } #pragma mark - Init - (instancetype)initWithAppKey:(NSString *)appKey userAgent:(NSString *)userAgent { self.megaApi = new MegaApi(appKey.UTF8String, (const char *)NULL, userAgent.UTF8String); if (pthread_mutex_init(&listenerMutex, NULL)) { return nil; } return self; } - (instancetype)initWithAppKey:(NSString *)appKey userAgent:(NSString *)userAgent basePath:(NSString *)basePath { self.megaApi = new MegaApi(appKey.UTF8String, basePath.UTF8String, userAgent.UTF8String); if (pthread_mutex_init(&listenerMutex, NULL)) { return nil; } return self; } - (instancetype)initWithAppKey:(NSString *)appKey userAgent:(NSString *)userAgent basePath:(NSString *)basePath clientType:(MEGAClientType)clientType { self.megaApi = new MegaApi(appKey.UTF8String, basePath.UTF8String, userAgent.UTF8String, 1, (int)clientType); if (pthread_mutex_init(&listenerMutex, NULL)) { return nil; } return self; } - (void)deleteMegaApi { MegaApi *api = _megaApi; _megaApi = nil; delete api; pthread_mutex_destroy(&listenerMutex); } - (void)dealloc { delete _megaApi; _megaApi = nil; pthread_mutex_destroy(&listenerMutex); } - (MegaApi *)getCPtr { return _megaApi; } #pragma mark - Add and remove delegates - (void)addMEGADelegate:(id)delegate { if (self.megaApi) { self.megaApi->addListener([self createDelegateMEGAListener:delegate]); } } - (void)addMEGARequestDelegate:(id)delegate { if (self.megaApi) { self.megaApi->addRequestListener([self createDelegateMEGARequestListener:delegate singleListener:NO]); } } - (void)addMEGARequestDelegate:(id)delegate queueType:(ListenerQueueType)queueType { if (self.megaApi) { self.megaApi->addRequestListener([self createDelegateMEGARequestListener:delegate singleListener:NO queueType:queueType]); } } - (void)addMEGATransferDelegate:(id)delegate { if (self.megaApi) { self.megaApi->addTransferListener([self createDelegateMEGATransferListener:delegate singleListener:NO]); } } - (void)addMEGATransferDelegate:(id)delegate queueType:(ListenerQueueType)queueType { if (self.megaApi) { self.megaApi->addTransferListener([self createDelegateMEGATransferListener:delegate singleListener:NO queueType:queueType]); } } - (void)addMEGAGlobalDelegate:(id)delegate { [self addMEGAGlobalDelegate:delegate queueType:ListenerQueueTypeMain]; } - (void)addMEGAGlobalDelegate:(id)delegate queueType:(ListenerQueueType)queueType { if (self.megaApi) { self.megaApi->addGlobalListener([self createDelegateMEGAGlobalListener:delegate queueType:queueType]); } } - (void)removeMEGADelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeMegaListeners.begin(); while (it != _activeMegaListeners.end()) { DelegateMEGAListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeMegaListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { if (self.megaApi) { self.megaApi->removeListener(listenersToRemove[i]); } delete listenersToRemove[i]; } } - (void)removeMEGARequestDelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeRequestListeners.begin(); while (it != _activeRequestListeners.end()) { DelegateMEGARequestListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeRequestListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { if (self.megaApi) { self.megaApi->removeRequestListener(listenersToRemove[i]); } delete listenersToRemove[i]; } } - (void)removeMEGATransferDelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeTransferListeners.begin(); while (it != _activeTransferListeners.end()) { DelegateMEGATransferListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeTransferListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { if (self.megaApi) { self.megaApi->removeTransferListener(listenersToRemove[i]); } delete listenersToRemove[i]; } } - (void)removeMEGAGlobalDelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeGlobalListeners.begin(); while (it != _activeGlobalListeners.end()) { DelegateMEGAGlobalListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeGlobalListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { if (self.megaApi) { self.megaApi->removeGlobalListener(listenersToRemove[i]); } delete listenersToRemove[i]; } } - (void)addLoggerDelegate:(id)delegate { MegaApi::addLoggerObject([self createDelegateMegaLogger:delegate]); } - (void)removeLoggerDelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeLoggerListeners.begin(); while (it != _activeLoggerListeners.end()) { DelegateMEGALoggerListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeLoggerListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { MegaApi::removeLoggerObject(listenersToRemove[i]); delete listenersToRemove[i]; } } - (void)addMEGAScheduledCopyDelegate:(id)delegate { [self addMEGAScheduledCopyDelegate:delegate queueType:ListenerQueueTypeMain]; } - (void)addMEGAScheduledCopyDelegate:(id)delegate queueType:(ListenerQueueType)queueType { if (self.megaApi) { self.megaApi->addScheduledCopyListener([self createDelegateMEGAScheduledCopyListener:delegate queueType:queueType]); } } - (void)removeMEGAScheduledCopyDelegate:(id)delegate { std::vector listenersToRemove; pthread_mutex_lock(&listenerMutex); std::set::iterator it = _activeScheduledCopyListeners.begin(); while (it != _activeScheduledCopyListeners.end()) { DelegateMEGAScheduledCopyListener *delegateListener = *it; if (delegateListener->getUserListener() == delegate) { listenersToRemove.push_back(delegateListener); _activeScheduledCopyListeners.erase(it++); } else { it++; } } pthread_mutex_unlock(&listenerMutex); for (int i = 0; i < listenersToRemove.size(); i++) { if (self.megaApi) { self.megaApi->removeScheduledCopyListener(listenersToRemove[i]); } delete listenersToRemove[i]; } } #pragma mark - Utils + (uint64_t)handleForBase64Handle:(NSString *)base64Handle { if(base64Handle == nil) return ::mega::INVALID_HANDLE; return MegaApi::base64ToHandle([base64Handle UTF8String]); } + (uint64_t)handleForBase64UserHandle:(NSString *)base64UserHandle { if(base64UserHandle == nil) return ::mega::INVALID_HANDLE; return MegaApi::base64ToUserHandle([base64UserHandle UTF8String]); } + (NSString *)base64HandleForHandle:(uint64_t)handle { const char *val = MegaApi::handleToBase64(handle); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } + (NSString *)base64HandleForUserHandle:(uint64_t)userhandle { const char *val = MegaApi::userHandleToBase64(userhandle); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (void)retryPendingConnections { if (self.megaApi) { self.megaApi->retryPendingConnections(); } } - (void)reconnect { if (self.megaApi) { self.megaApi->retryPendingConnections(true, true); } } - (BOOL)serverSideRubbishBinAutopurgeEnabled { if (self.megaApi == nil) return NO; return self.megaApi->serverSideRubbishBinAutopurgeEnabled(); } - (BOOL)appleVoipPushEnabled { if (self.megaApi == nil) return NO; return self.megaApi->appleVoipPushEnabled(); } - (void)getSessionTransferURL:(NSString *)path delegate:(id)delegate { if (self.megaApi) { self.megaApi->getSessionTransferURL(path.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getSessionTransferURL:(NSString *)path { if (self.megaApi) { self.megaApi->getSessionTransferURL(path.UTF8String); } } - (MEGAStringList *)megaStringListFor:(NSArray*)stringList { MegaStringList* list = mega::MegaStringList::createInstance(); for (NSString* string in stringList) { list->add([string UTF8String]); } return [[MEGAStringList alloc] initWithMegaStringList:list cMemoryOwn:YES]; } #pragma mark - Login Requests - (BOOL)multiFactorAuthAvailable { if (self.megaApi == nil) return NO; return self.megaApi->multiFactorAuthAvailable(); } - (void)multiFactorAuthCheckWithEmail:(NSString *)email delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthCheck(email.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthCheckWithEmail:(NSString *)email { if (self.megaApi) { self.megaApi->multiFactorAuthCheck(email.UTF8String); } } - (void)multiFactorAuthGetCodeWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthGetCode([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthGetCode { if (self.megaApi) { self.megaApi->multiFactorAuthGetCode(); } } - (void)multiFactorAuthEnableWithPin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthEnable(pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthEnableWithPin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthEnable(pin.UTF8String); } } - (void)multiFactorAuthDisableWithPin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthDisable(pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthDisableWithPin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthDisable(pin.UTF8String); } } - (void)multiFactorAuthLoginWithEmail:(NSString *)email password:(NSString *)password pin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthLogin(email.UTF8String, password.UTF8String, pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthLoginWithEmail:(NSString *)email password:(NSString *)password pin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthLogin(email.UTF8String, password.UTF8String, pin.UTF8String); } } - (void)multiFactorAuthChangePassword:(NSString *)oldPassword newPassword:(NSString *)newPassword pin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthChangePassword(oldPassword.UTF8String, newPassword.UTF8String, pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthChangePassword:(NSString *)oldPassword newPassword:(NSString *)newPassword pin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthChangePassword(oldPassword.UTF8String, newPassword.UTF8String, pin.UTF8String); } } - (void)multiFactorAuthChangeEmail:(NSString *)email pin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthChangeEmail(email.UTF8String, pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthChangeEmail:(NSString *)email pin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthChangeEmail(email.UTF8String, pin.UTF8String); } } - (void)multiFactorAuthCancelAccountWithPin:(NSString *)pin delegate:(id)delegate { if (self.megaApi) { self.megaApi->multiFactorAuthCancelAccount(pin.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)multiFactorAuthCancelAccountWithPin:(NSString *)pin { if (self.megaApi) { self.megaApi->multiFactorAuthCancelAccount(pin.UTF8String); } } - (void)fetchTimeZoneWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->fetchTimeZone([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)fetchTimeZone { if (self.megaApi) { self.megaApi->fetchTimeZone(); } } - (void)loginWithEmail:(NSString *)email password:(NSString *)password { if (self.megaApi) { self.megaApi->login(email.UTF8String, password.UTF8String); } } - (void)loginWithEmail:(NSString *)email password:(NSString *)password delegate:(id)delegate{ if (self.megaApi) { self.megaApi->login(email.UTF8String, password.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)sendDevCommand:(NSString *)command email:(NSString *)email delegate:(id)delegate { if (self.megaApi) { self.megaApi->sendDevCommand(command.UTF8String, email.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (NSString *)dumpSession { if (self.megaApi == nil) return nil; const char *val = self.megaApi->dumpSession(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)sequenceNumber { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getSequenceNumber(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)accountAuth { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getAccountAuth(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (void)setAccountAuth:(NSString *)accountAuth { if (self.megaApi) { self.megaApi->setAccountAuth(accountAuth.UTF8String); } } - (void)fastLoginWithSession:(NSString *)session { if (self.megaApi) { self.megaApi->fastLogin(session.UTF8String); } } - (void)fastLoginWithSession:(NSString *)session delegate:(id)delegate { if (self.megaApi) { self.megaApi->fastLogin(session.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)loginToFolderLink:(NSString *)folderLink delegate:(id)delegate { if (self.megaApi) { self.megaApi->loginToFolder(folderLink.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)loginToFolderLink:(NSString *)folderLink { if (self.megaApi) { self.megaApi->loginToFolder(folderLink.UTF8String); } } - (NSInteger)isLoggedIn { if (self.megaApi == nil) return 0; return self.megaApi->isLoggedIn(); } - (BOOL)isEphemeralPlusPlus { if (self.megaApi == nil) return false; return self.megaApi->isEphemeralPlusPlus(); } - (void)fetchNodesWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->fetchNodes([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)fetchNodes { if (self.megaApi) { self.megaApi->fetchNodes(); } } - (void)logoutWithDelegate:(id)delegate { [NSNotificationCenter.defaultCenter postNotificationName:MEGAIsBeingLogoutNotification object:nil]; if (self.megaApi) { self.megaApi->logout([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)logout { [NSNotificationCenter.defaultCenter postNotificationName:MEGAIsBeingLogoutNotification object:nil]; if (self.megaApi) { self.megaApi->logout(NULL); } } - (void)localLogoutWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->localLogout([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)localLogout { if (self.megaApi) { self.megaApi->localLogout(); } } - (void)invalidateCache { if (self.megaApi) { self.megaApi->invalidateCache(); } } - (PasswordStrength)passwordStrength:(NSString *)password { if (self.megaApi == nil) return PasswordStrengthVeryWeak; return (PasswordStrength) self.megaApi->getPasswordStrength(password.UTF8String); } - (BOOL)checkPassword:(NSString *)password { if (self.megaApi == nil) return NO; return self.megaApi->checkPassword(password.UTF8String); } - (NSString *)myCredentials { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getMyCredentials(); if (val) { NSString *ret = [NSString.alloc initWithUTF8String:val]; delete [] val; return ret; } else { return nil; } } - (void)getUserCredentials:(MEGAUser *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserCredentials(user.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserCredentials:(MEGAUser *)user { if (self.megaApi) { self.megaApi->getUserCredentials(user.getCPtr); } } - (BOOL)areCredentialsVerifiedOfUser:(MEGAUser *)user { if (self.megaApi == nil) return NO; return self.megaApi->areCredentialsVerified(user.getCPtr); } - (void)verifyCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->verifyCredentials(user.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)verifyCredentialsOfUser:(MEGAUser *)user { if (self.megaApi) { self.megaApi->verifyCredentials(user.getCPtr); } } - (void)resetCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->resetCredentials(user.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetCredentialsOfUser:(MEGAUser *)user { if (self.megaApi) { self.megaApi->resetCredentials(user.getCPtr); } } #pragma mark - Create account and confirm account Requests - (void)createEphemeralAccountPlusPlusWithFirstname:(NSString *)firstname lastname:(NSString *)lastname { if (self.megaApi) { self.megaApi->createEphemeralAccountPlusPlus(firstname.UTF8String, lastname.UTF8String); } } - (void)createEphemeralAccountPlusPlusWithFirstname:(NSString *)firstname lastname:(NSString *)lastname delegate:(id)delegate { if (self.megaApi) { self.megaApi->createEphemeralAccountPlusPlus(firstname.UTF8String, lastname.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)createAccountWithEmail:(NSString *)email password:(NSString *)password firstname:(NSString *)firstname lastname:(NSString *)lastname { if (self.megaApi) { self.megaApi->createAccount(email.UTF8String, password.UTF8String, firstname.UTF8String, lastname.UTF8String); } } - (void)createAccountWithEmail:(NSString *)email password:(NSString *)password firstname:(NSString *)firstname lastname:(NSString *)lastname delegate:(id)delegate { if (self.megaApi) { self.megaApi->createAccount(email.UTF8String, password.UTF8String, firstname.UTF8String, lastname.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId delegate:(id)delegate { if (self.megaApi) { self.megaApi->resumeCreateAccount(sessionId.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId { if (self.megaApi) { self.megaApi->resumeCreateAccount(sessionId.UTF8String); } } - (void)cancelCreateAccountWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelCreateAccount([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelCreateAccount { if (self.megaApi) { self.megaApi->cancelCreateAccount(); } } - (void)resendSignupLinkWithEmail:(NSString *)email name:(NSString *)name delegate:(id)delegate { if (self.megaApi) { self.megaApi->resendSignupLink(email.UTF8String, name.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)querySignupLink:(NSString *)link { if (self.megaApi) { self.megaApi->querySignupLink(link.UTF8String); } } - (void)querySignupLink:(NSString *)link delegate:(id)delegate { if (self.megaApi) { self.megaApi->querySignupLink(link.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)confirmAccountWithLink:(NSString *)link { if (self.megaApi) { self.megaApi->confirmAccount(link.UTF8String); } } - (void)confirmAccountWithLink:(NSString *)link delegate:(id)delegate { if (self.megaApi) { self.megaApi->confirmAccount(link.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetPasswordWithEmail:(NSString *)email hasMasterKey:(BOOL)hasMasterKey delegate:(id)delegate { if (self.megaApi) { self.megaApi->resetPassword(email.UTF8String, hasMasterKey, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetPasswordWithEmail:(NSString *)email hasMasterKey:(BOOL)hasMasterKey { if (self.megaApi) { self.megaApi->resetPassword(email.UTF8String, hasMasterKey); } } - (void)queryResetPasswordLink:(NSString *)link delegate:(id)delegate { if (self.megaApi) { self.megaApi->queryResetPasswordLink(link.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)queryResetPasswordLink:(NSString *)link { if (self.megaApi) { self.megaApi->queryResetPasswordLink(link.UTF8String); } } - (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey delegate:(id)delegate { if (self.megaApi) { self.megaApi->confirmResetPassword(link.UTF8String, newPassword.UTF8String, masterKey.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey { if (self.megaApi) { self.megaApi->confirmResetPassword(link.UTF8String, newPassword.UTF8String, masterKey.UTF8String); } } - (void)checkRecoveryKey:(NSString *)link recoveryKey:(NSString *)recoveryKey delegate:(id)delegate { if (self.megaApi) { self.megaApi->checkRecoveryKey(link.UTF8String, recoveryKey.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)cancelAccountWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelAccount([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelAccount { if (self.megaApi) { self.megaApi->cancelAccount(); } } - (void)queryCancelLink:(NSString *)link { if (self.megaApi) { self.megaApi->queryCancelLink(link.UTF8String); } } - (void)queryCancelLink:(NSString *)link delegate:(id)delegate { if (self.megaApi) { self.megaApi->queryCancelLink(link.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)password delegate:(id)delegate { if (self.megaApi) { self.megaApi->confirmCancelAccount(link.UTF8String, password.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)password { if (self.megaApi) { self.megaApi->confirmCancelAccount(link.UTF8String, password.UTF8String); } } - (void)resendVerificationEmailWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->resendVerificationEmail([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resendVerificationEmail { if (self.megaApi) { self.megaApi->resendVerificationEmail(); } } - (void)changeEmail:(NSString *)email delegate:(id)delegate { if (self.megaApi) { self.megaApi->changeEmail(email.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)changeEmail:(NSString *)email { if (self.megaApi) { self.megaApi->changeEmail(email.UTF8String); } } - (void)queryChangeEmailLink:(NSString *)link delegate:(id)delegate { if (self.megaApi) { self.megaApi->queryChangeEmailLink(link.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)queryChangeEmailLink:(NSString *)link { if (self.megaApi) { self.megaApi->queryChangeEmailLink(link.UTF8String); } } - (void)confirmChangeEmailWithLink:(NSString *)link password:(NSString *)password delegate:(id)delegate { if (self.megaApi) { self.megaApi->confirmChangeEmail(link.UTF8String, password.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)confirmChangeEmailWithLink:(NSString *)link password:(NSString *)password { if (self.megaApi) { self.megaApi->confirmChangeEmail(link.UTF8String, password.UTF8String); } } - (void)contactLinkCreateRenew:(BOOL)renew delegate:(id)delegate { if (self.megaApi) { self.megaApi->contactLinkCreate(renew, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)contactLinkCreateRenew:(BOOL)renew { if (self.megaApi) { self.megaApi->contactLinkCreate(renew); } } - (void)contactLinkQueryWithHandle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->contactLinkQuery(handle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)contactLinkQueryWithHandle:(uint64_t)handle { if (self.megaApi) { self.megaApi->contactLinkQuery(handle); } } - (void)contactLinkDeleteWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->contactLinkDelete(INVALID_HANDLE, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)contactLinkDelete { if (self.megaApi) { self.megaApi->contactLinkDelete(); } } - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable delegate:(id)delegate { if (self.megaApi) { self.megaApi->keepMeAlive((int) type, enable, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable { if (self.megaApi) { self.megaApi->keepMeAlive((int) type, enable); } } - (void)whyAmIBlockedWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->whyAmIBlocked([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)whyAmIBlocked { if (self.megaApi) { self.megaApi->whyAmIBlocked(); } } - (void)getPSAWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getPSA([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPSA{ if (self.megaApi) { self.megaApi->getPSA(); } } - (void)getURLPublicServiceAnnouncementWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getPSAWithUrl([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setPSAWithIdentifier:(NSInteger)identifier delegate:(id)delegate { if (self.megaApi) { self.megaApi->setPSA((int)identifier, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setPSAWithIdentifier:(NSInteger)identifier { if (self.megaApi) { self.megaApi->setPSA((int)identifier); } } - (void)acknowledgeUserAlertsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->acknowledgeUserAlerts([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)acknowledgeUserAlerts { if (self.megaApi) { self.megaApi->acknowledgeUserAlerts(); } } #pragma mark - Notifications - (void)getLastReadNotificationWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getLastReadNotification([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)setLastReadNotificationWithNotificationId:(uint32_t)notificationId delegate:(id)delegate { if (self.megaApi) { self.megaApi->setLastReadNotification(notificationId, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (nullable MEGAIntegerList *)getEnabledNotifications { if (self.megaApi == nil) return nil; MegaIntegerList* enabledNotifications = self.megaApi->getEnabledNotifications(); return enabledNotifications != nil ? [[MEGAIntegerList alloc] initWithMegaIntegerList:enabledNotifications cMemoryOwn:YES] : nil; } - (void)getNotificationsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getNotifications([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } #pragma mark - Filesystem changes Requests - (void)createFolderWithName:(NSString *)name parent:(MEGANode *)parent delegate:(id)delegate { if (self.megaApi) { self.megaApi->createFolder(name.UTF8String, parent.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)createFolderWithName:(NSString *)name parent:(MEGANode *)parent { if (self.megaApi) { self.megaApi->createFolder(name.UTF8String, parent.getCPtr); } } - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent delegate:(id)delegate { if (self.megaApi) { self.megaApi->moveNode(node.getCPtr, newParent.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent { if (self.megaApi) { self.megaApi->moveNode(node.getCPtr, newParent.getCPtr); } } - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName delegate:(id)delegate { if (self.megaApi) { self.megaApi->moveNode(node.getCPtr, newParent.getCPtr, newName.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName { if (self.megaApi) { self.megaApi->moveNode(node.getCPtr, newParent.getCPtr, newName.UTF8String); } } - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent delegate:(id)delegate { if (self.megaApi) { self.megaApi->copyNode(node.getCPtr, newParent.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent { if (self.megaApi) { self.megaApi->copyNode(node.getCPtr, newParent.getCPtr); } } - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName delegate:(id)delegate { if (self.megaApi) { self.megaApi->copyNode(node.getCPtr, newParent.getCPtr, newName.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName { if (self.megaApi) { self.megaApi->copyNode(node.getCPtr, newParent.getCPtr, newName.UTF8String); } } - (void)renameNode:(MEGANode *)node newName:(NSString *)newName delegate:(id)delegate { if (self.megaApi) { self.megaApi->renameNode(node.getCPtr, newName.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)renameNode:(MEGANode *)node newName:(NSString *)newName { if (self.megaApi) { self.megaApi->renameNode(node.getCPtr, newName.UTF8String); } } - (void)removeNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->remove(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)removeNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->remove(node.getCPtr); } } - (void)removeVersionsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->removeVersions([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)removeVersions { if (self.megaApi) { self.megaApi->removeVersions(); } } - (void)removeVersionNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->removeVersion(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)removeVersionNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->removeVersion(node.getCPtr); } } - (void)restoreVersionNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->restoreVersion(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)restoreVersionNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->restoreVersion(node.getCPtr); } } - (void)cleanRubbishBinWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->cleanRubbishBin([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cleanRubbishBin { if (self.megaApi) { self.megaApi->cleanRubbishBin(); } } #pragma mark - Sharing Requests - (void)shareNode:(MEGANode *)node withUser:(MEGAUser *)user level:(NSInteger)level delegate:(id)delegate { if (self.megaApi) { self.megaApi->share(node.getCPtr, user.getCPtr, (int)level, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)shareNode:(MEGANode *)node withUser:(MEGAUser *)user level:(NSInteger)level { if (self.megaApi) { self.megaApi->share(node.getCPtr, user.getCPtr, (int)level); } } - (void)shareNode:(MEGANode *)node withEmail:(NSString *)email level:(NSInteger)level delegate:(id)delegate { if (self.megaApi) { self.megaApi->share(node.getCPtr, email.UTF8String, (int)level, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)shareNode:(MEGANode *)node withEmail:(NSString *)email level:(NSInteger)level { if (self.megaApi) { self.megaApi->share(node.getCPtr, email.UTF8String, (int)level); } } - (void)importMegaFileLink:(NSString *)megaFileLink parent:(MEGANode *)parent delegate:(id)delegate { if (self.megaApi) { self.megaApi->importFileLink(megaFileLink.UTF8String, parent.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)importMegaFileLink:(NSString *)megaFileLink parent:(MEGANode *)parent { if (self.megaApi) { self.megaApi->importFileLink(megaFileLink.UTF8String, parent.getCPtr); } } - (void)decryptPasswordProtectedLink:(NSString *)link password:(NSString *)password delegate:(id)delegate { if (self.megaApi) { self.megaApi->decryptPasswordProtectedLink(link.UTF8String, password.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)decryptPasswordProtectedLink:(NSString *)link password:(NSString *)password { if (self.megaApi) { self.megaApi->decryptPasswordProtectedLink(link.UTF8String, password.UTF8String); } } - (void)encryptLinkWithPassword:(NSString *)link password:(NSString *)password delegate:(id)delegate { if (self.megaApi) { self.megaApi->encryptLinkWithPassword(link.UTF8String, password.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)encryptLinkWithPassword:(NSString *)link password:(NSString *)password { if (self.megaApi) { self.megaApi->encryptLinkWithPassword(link.UTF8String, password.UTF8String); } } - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink delegate:(id)delegate { if (self.megaApi) { self.megaApi->getPublicNode(megaFileLink.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getDownloadUrl:(MEGANode *)node singleUrl:(BOOL)singleUrl delegate:(id)delegate { if (self.megaApi) { self.megaApi->getDownloadUrl(node.getCPtr, singleUrl, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink { if (self.megaApi) { self.megaApi->getPublicNode(megaFileLink.UTF8String); } } - (NSString *)buildPublicLinkForHandle:(NSString *)publicHandle key:(NSString *)key isFolder:(BOOL)isFolder { if (self.megaApi == nil) return nil; const char *link = self.megaApi->buildPublicLink(publicHandle.UTF8String, key.UTF8String, isFolder); if (!link) return nil; NSString *stringLink = [NSString.alloc initWithUTF8String:link]; delete [] link; return stringLink; } - (void)setNodeLabel:(MEGANode *)node label:(MEGANodeLabel)label delegate:(id)delegate { if (self.megaApi) { self.megaApi->setNodeLabel(node.getCPtr, (int)label, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setNodeLabel:(MEGANode *)node label:(MEGANodeLabel)label { if (self.megaApi) { self.megaApi->setNodeLabel(node.getCPtr, (int)label); } } - (void)resetNodeLabel:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->resetNodeLabel(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetNodeLabel:(MEGANode *)node { if (self.megaApi) { self.megaApi->resetNodeLabel(node.getCPtr); } } - (void)setNodeFavourite:(MEGANode *)node favourite:(BOOL)favourite delegate:(id)delegate { if (self.megaApi) { self.megaApi->setNodeFavourite(node.getCPtr, favourite, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setNodeFavourite:(MEGANode *)node favourite:(BOOL)favourite { if (self.megaApi) { self.megaApi->setNodeFavourite(node.getCPtr, favourite); } } - (void)setNodeSensitive:(MEGANode *)node sensitive:(BOOL)sensitive delegate:(id)delegate { if (self.megaApi) { self.megaApi->setNodeSensitive(node.getCPtr, sensitive, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setNodeSensitive:(MEGANode *)node sensitive:(BOOL)sensitive { if (self.megaApi) { self.megaApi->setNodeSensitive(node.getCPtr, sensitive); } } - (void)setDescription:(nullable NSString *)description forNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->setNodeDescription( node.getCPtr, description.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)favouritesForParent:(nullable MEGANode *)node count:(NSInteger)count delegate:(id)delegate { if (self.megaApi) { self.megaApi->getFavourites(node.getCPtr, (int)count, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)favouritesForParent:(nullable MEGANode *)node count:(NSInteger)count { if (self.megaApi) { self.megaApi->getFavourites(node.getCPtr, (int)count); } } - (void)createSet:(nullable NSString *)name type:(MEGASetType)type delegate:(id)delegate { if (self.megaApi) { self.megaApi->createSet(name.UTF8String, (int)type, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)exportSet:(MEGAHandle)sid delegate:(id)delegate { if (self.megaApi) { self.megaApi->exportSet(sid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)disableExportSet:(MEGAHandle)sid delegate:(id)delegate { if (self.megaApi) { self.megaApi->disableExportSet(sid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)stopPublicSetPreview { if (self.megaApi) { self.megaApi->stopPublicSetPreview(); } } - (BOOL)inPublicSetPreview { if (self.megaApi) { return self.megaApi->inPublicSetPreview(); } } - (nullable MEGASet *)publicSetInPreview { if (self.megaApi) { MegaSet *set = self.megaApi->getPublicSetInPreview(); return set ? [[MEGASet alloc] initWithMegaSet:set->copy() cMemoryOwn:YES] : nil; } } - (void)fetchPublicSet:(NSString *)publicSetLink delegate:(id)delegate { if (self.megaApi) { self.megaApi->fetchPublicSet(publicSetLink.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)previewElementNode:(MEGAHandle)eid delegate:(id)delegate { if (self.megaApi) { self.megaApi->getPreviewElementNode(eid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)updateSetName:(MEGAHandle)sid name:(NSString *)name delegate:(id)delegate { if (self.megaApi) { self.megaApi->updateSetName(sid, name.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)removeSet:(MEGAHandle)sid delegate:(id)delegate { if (self.megaApi) { self.megaApi->removeSet(sid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)putSetCover:(MEGAHandle)sid eid:(MEGAHandle)eid delegate:(id)delegate { if (self.megaApi) { self.megaApi->putSetCover(sid, eid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)createSetElement:(MEGAHandle)sid nodeId:(MEGAHandle)nodeId name:(NSString *)name delegate:(id)delegate { if (self.megaApi) { self.megaApi->createSetElement(sid, nodeId, name.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)updateSetElement:(MEGAHandle)sid eid:(MEGAHandle)eid name:(NSString *)name delegate:(id)delegate { if (self.megaApi) { self.megaApi->updateSetElementName(sid, eid, name.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)updateSetElementOrder:(MEGAHandle)sid eid:(MEGAHandle)eid order:(int64_t)order delegate:(id)delegate { if (self.megaApi) { self.megaApi->updateSetElementOrder(sid, eid, order, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)removeSetElement:(MEGAHandle)sid eid:(MEGAHandle)eid delegate:(id)delegate { if (self.megaApi) { self.megaApi->removeSetElement(sid, eid, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (nullable MEGASet *)setBySid:(MEGAHandle)sid { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE) return nil; MegaSet *set = self.megaApi->getSet(sid); return set ? [[MEGASet alloc] initWithMegaSet:set->copy() cMemoryOwn:YES] : nil; } - (BOOL)isExportedSet:(MEGAHandle)sid { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE) return NO; return self.megaApi->isExportedSet(sid); } - (NSArray *)megaSets { if (self.megaApi == nil) return nil; MegaSetList *setList = self.megaApi->getSets(); int size = setList->size(); NSMutableArray *sets = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASet *megaSet = [[MEGASet alloc] initWithMegaSet:setList->get(i)->copy() cMemoryOwn:YES]; [sets addObject:megaSet]; } delete setList; return [sets copy]; } - (MEGAHandle)megaSetCoverBySid:(MEGAHandle)sid { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE) return ::mega::INVALID_HANDLE; return self.megaApi->getSetCover(sid); } - (nullable NSString *)publicLinkForExportedSetBySid:(MEGAHandle)sid { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE) return NULL; const char *link = self.megaApi->getPublicLinkForExportedSet(sid); if (!link) return nil; NSString *linkStr = [[NSString alloc] initWithUTF8String:link]; delete [] link; return linkStr; } - (nullable MEGASetElement *)megaSetElementBySid:(MEGAHandle)sid eid:(MEGAHandle)eid { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE || eid == ::mega::INVALID_HANDLE) return nil; MegaSetElement *element = self.megaApi->getSetElement(sid, eid); MEGASetElement *setElement = element ? [[MEGASetElement alloc] initWithMegaSetElement:element->copy() cMemoryOwn:YES] : nil; delete element; return setElement; } - (NSArray *)megaSetElementsBySid:(MEGAHandle)sid includeElementsInRubbishBin:(BOOL)includeElementsInRubbishBin { if (self.megaApi == nil) return nil; MegaSetElementList *setElementList = self.megaApi->getSetElements(sid, includeElementsInRubbishBin); int size = setElementList->size(); NSMutableArray *setElements = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASetElement *megaSetElement = [[MEGASetElement alloc] initWithMegaSetElement:setElementList->get(i)->copy() cMemoryOwn:YES]; [setElements addObject:megaSetElement]; } delete setElementList; return [setElements copy]; } - (NSArray *)publicSetElementsInPreview { if (self.megaApi == nil) return nil; MegaSetElementList *setElementList = self.megaApi->getPublicSetElementsInPreview(); int size = setElementList->size(); NSMutableArray *setElements = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASetElement *megaSetElement = [[MEGASetElement alloc] initWithMegaSetElement:setElementList->get(i)->copy() cMemoryOwn:YES]; [setElements addObject:megaSetElement]; } delete setElementList; return [setElements copy]; } - (NSUInteger)megaSetElementCount:(MEGAHandle)sid includeElementsInRubbishBin:(BOOL)includeElementsInRubbishBin { if (self.megaApi == nil || sid == ::mega::INVALID_HANDLE) return 0; return self.megaApi->getSetElementCount(sid, includeElementsInRubbishBin); } - (void)setNodeCoordinates:(MEGANode *)node latitude:(NSNumber *)latitude longitude:(NSNumber *)longitude delegate:(id)delegate { if (self.megaApi) { self.megaApi->setNodeCoordinates(node.getCPtr, (latitude ? latitude.doubleValue : MegaNode::INVALID_COORDINATE), (longitude ? longitude.doubleValue : MegaNode::INVALID_COORDINATE), [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setNodeCoordinates:(MEGANode *)node latitude:(NSNumber *)latitude longitude:(NSNumber *)longitude { if (self.megaApi) { self.megaApi->setNodeCoordinates(node.getCPtr, (latitude ? latitude.doubleValue : MegaNode::INVALID_COORDINATE), (longitude ? longitude.doubleValue : MegaNode::INVALID_COORDINATE)); } } - (void)setUnshareableNodeCoordinates:(MEGANode *)node latitude:(double)latitude longitude:(double)longitude delegate:(id)delegate { if (self.megaApi) { self.megaApi->setUnshareableNodeCoordinates(node.getCPtr, latitude, longitude , [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setUnshareableNodeCoordinates:(MEGANode *)node latitude:(double)latitude longitude:(double)longitude { if (self.megaApi) { self.megaApi->setUnshareableNodeCoordinates(node.getCPtr, latitude, longitude); } } - (void)exportNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->exportNode(node.getCPtr, 0, false, false, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)exportNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->exportNode(node.getCPtr, 0, false, false); } } - (void)exportNode:(MEGANode *)node expireTime:(NSDate *)expireTime delegate:(id)delegate { if (self.megaApi) { self.megaApi->exportNode(node.getCPtr, (int64_t)[expireTime timeIntervalSince1970], false, false, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)exportNode:(MEGANode *)node expireTime:(NSDate *)expireTime { if (self.megaApi) { self.megaApi->exportNode(node.getCPtr, (int64_t)[expireTime timeIntervalSince1970], false, false); } } - (void)disableExportNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->disableExport(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)disableExportNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->disableExport(node.getCPtr); } } - (void)openShareDialog:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->openShareDialog(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } #pragma mark - Attributes Requests - (void)getThumbnailWithNodeHandle:(uint64_t)nodeHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->getThumbnail(nodeHandle, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getThumbnailWithNodeHandle:(uint64_t)nodeHandle destinationFilePath:(NSString *)destinationFilePath { if (self.megaApi) { self.megaApi->getThumbnail(nodeHandle, destinationFilePath.UTF8String); } } - (void)getThumbnailNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->getThumbnail(node.getCPtr, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getThumbnailNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath { if (self.megaApi) { self.megaApi->getThumbnail(node.getCPtr, destinationFilePath.UTF8String); } } - (void)cancelGetThumbnailNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelGetThumbnail(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelGetThumbnailNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->cancelGetThumbnail(node.getCPtr); } } - (void)setThumbnailNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->setThumbnail(node.getCPtr, sourceFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setThumbnailNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath { if (self.megaApi) { self.megaApi->setThumbnail(node.getCPtr, sourceFilePath.UTF8String); } } - (void)getPreviewNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->getPreview(node.getCPtr, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPreviewNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath { if (self.megaApi) { self.megaApi->getPreview(node.getCPtr, destinationFilePath.UTF8String); } } - (void)cancelGetPreviewNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelGetPreview(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelGetPreviewNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->cancelGetPreview(node.getCPtr); } } - (void)setPreviewNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->setPreview(node.getCPtr, sourceFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setPreviewNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath { if (self.megaApi) { self.megaApi->setPreview(node.getCPtr, sourceFilePath.UTF8String); } } - (void)getAvatarUser:(MEGAUser *)user destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAvatar(user.getCPtr, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getAvatarUser:(MEGAUser *)user destinationFilePath:(NSString *)destinationFilePath { if (self.megaApi) { self.megaApi->getUserAvatar(user.getCPtr, destinationFilePath.UTF8String); } } - (void)getAvatarUserWithEmailOrHandle:(NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAvatar(emailOrHandle.UTF8String, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getAvatarUserWithEmailOrHandle:(NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate queueType:(ListenerQueueType)queueType { if (self.megaApi) { self.megaApi->getUserAvatar(emailOrHandle.UTF8String, destinationFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:queueType]); } } - (void)getAvatarUserWithEmailOrHandle:(NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath { if (self.megaApi) { self.megaApi->getUserAvatar(emailOrHandle.UTF8String, destinationFilePath.UTF8String); } } + (NSString *)avatarColorForUser:(MEGAUser *)user { const char *val = MegaApi::getUserAvatarColor(user.getCPtr); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } + (NSString *)avatarColorForBase64UserHandle:(NSString *)base64UserHandle { const char *val = MegaApi::getUserAvatarColor(base64UserHandle.UTF8String); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } + (NSString *)avatarSecondaryColorForUser:(MEGAUser *)user { const char *val = MegaApi::getUserAvatarSecondaryColor(user.getCPtr); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } + (NSString *)avatarSecondaryColorForBase64UserHandle:(NSString *)base64UserHandle { const char *val = MegaApi::getUserAvatarSecondaryColor(base64UserHandle.UTF8String); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (void)setAvatarUserWithSourceFilePath:(NSString *)sourceFilePath delegate:(id)delegate { if (self.megaApi) { self.megaApi->setAvatar(sourceFilePath.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setAvatarUserWithSourceFilePath:(NSString *)sourceFilePath { if (self.megaApi) { self.megaApi->setAvatar(sourceFilePath.UTF8String); } } - (void)getUserAttributeForUser:(MEGAUser *)user type:(MEGAUserAttribute)type { if (self.megaApi) { self.megaApi->getUserAttribute(user.getCPtr, (int)type); } } - (void)getUserAttributeForUser:(MEGAUser *)user type:(MEGAUserAttribute)type delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAttribute(user.getCPtr, (int)type, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserAttributeForEmailOrHandle:(NSString *)emailOrHandle type:(MEGAUserAttribute)type { if (self.megaApi) { self.megaApi->getUserAttribute(emailOrHandle.UTF8String, (int)type); } } - (void)getUserAttributeForEmailOrHandle:(NSString *)emailOrHandle type:(MEGAUserAttribute)type delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAttribute(emailOrHandle.UTF8String, (int)type, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserAttributeType:(MEGAUserAttribute)type { if (self.megaApi) { self.megaApi->getUserAttribute((int)type); } } - (void)getUserAttributeType:(MEGAUserAttribute)type delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAttribute((int)type, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value { if (self.megaApi) { self.megaApi->setUserAttribute((int)type, value.UTF8String); } } - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value delegate:(id)delegate { if (self.megaApi) { self.megaApi->setUserAttribute((int)type, value.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setUserAttributeType:(MEGAUserAttribute)type key:(NSString *)key value:(NSString *)value { if (self.megaApi) { std::unique_ptr base64Value(MegaApi::binaryToBase64((const char *)value.UTF8String, value.length)); std::unique_ptr stringMap(MegaStringMap::createInstance()); stringMap->set(key.UTF8String, base64Value.get()); self.megaApi->setUserAttribute((int)type, stringMap.get()); } } - (void)setUserAttributeType:(MEGAUserAttribute)type key:(NSString *)key value:(NSString *)value delegate:(id)delegate { if (self.megaApi) { std::unique_ptr base64Value(MegaApi::binaryToBase64((const char *)value.UTF8String, value.length)); std::unique_ptr stringMap(MegaStringMap::createInstance()); stringMap->set(key.UTF8String, base64Value.get()); self.megaApi->setUserAttribute((int)type, stringMap.get(), [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserAliasWithHandle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserAlias(handle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserAliasWithHandle:(uint64_t)handle { if (self.megaApi) { self.megaApi->getUserAlias(handle); } } - (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->setUserAlias(handle, alias.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle { if (self.megaApi) { self.megaApi->setUserAlias(handle, alias.UTF8String); } } #pragma mark - S4 Attributes Requests - (BOOL)isS4Enabled { if (self.megaApi == nil) return NO; return self.megaApi->isS4Enabled(); } - (MEGAHandle)getS4ContainerHandle { if (self.megaApi == nil) return ::mega::INVALID_HANDLE; return self.megaApi->getS4Container(); } #pragma mark - Account management Requests - (void)getAccountDetailsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getAccountDetails([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getAccountDetails { if (self.megaApi) { self.megaApi->getAccountDetails(); } } - (void)queryTransferQuotaWithSize:(long long)size delegate:(id)delegate { if (self.megaApi) { self.megaApi->queryTransferQuota(size, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)queryTransferQuotaWithSize:(long long)size { if (self.megaApi) { self.megaApi->queryTransferQuota(size); } } - (void)getRecommendedProLevelWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getRecommendedProLevel([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getPricingWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getPricing([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPricing { if (self.megaApi) { self.megaApi->getPricing(); } } - (void)getPaymentIdForProductHandle:(uint64_t)productHandle delegate:(id)delegate { if (self.megaApi) { self.megaApi->getPaymentId(productHandle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPaymentIdForProductHandle:(uint64_t)productHandle { if (self.megaApi) { self.megaApi->getPaymentId(productHandle); } } - (void)submitPurchase:(MEGAPaymentMethod)gateway receipt:(NSString *)receipt delegate:(id)delegate { if (self.megaApi) { self.megaApi->submitPurchaseReceipt((int)gateway, receipt.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)submitPurchase:(MEGAPaymentMethod)gateway receipt:(NSString *)receipt { if (self.megaApi) { self.megaApi->submitPurchaseReceipt((int)gateway, receipt.UTF8String); } } - (void)creditCardCancelSubscriptions:(nullable NSString *)reason subscriptionId:(nullable NSString *)subscriptionId canContact:(BOOL)canContact delegate:(id)delegate { if (self.megaApi) { self.megaApi->creditCardCancelSubscriptions(reason.UTF8String, subscriptionId.UTF8String, canContact, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)creditCardCancelSubscriptionsWithReasons:(nullable MEGACancelSubscriptionReasonList *)reasonList subscriptionId:(nullable NSString *)subscriptionId canContact:(BOOL)canContact delegate:(id)delegate { if (self.megaApi) { self.megaApi->creditCardCancelSubscriptions(reasonList.getCPtr, subscriptionId.UTF8String, canContact, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)changePassword:(NSString *)oldPassword newPassword:(NSString *)newPassword delegate:(id)delegate { if (self.megaApi) { self.megaApi->changePassword(oldPassword.UTF8String, newPassword.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)changePassword:(NSString *)oldPassword newPassword:(NSString *)newPassword { if (self.megaApi) { self.megaApi->changePassword(oldPassword.UTF8String, newPassword.UTF8String); } } - (void)masterKeyExportedWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->masterKeyExported([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)masterKeyExported { if (self.megaApi) { self.megaApi->masterKeyExported(); } } - (void)passwordReminderDialogSucceededWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->passwordReminderDialogSucceeded([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)passwordReminderDialogSucceeded { if (self.megaApi) { self.megaApi->passwordReminderDialogSucceeded(); } } - (void)passwordReminderDialogSkippedWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->passwordReminderDialogSkipped([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)passwordReminderDialogSkipped { if (self.megaApi) { self.megaApi->passwordReminderDialogSkipped(); } } - (void)passwordReminderDialogBlockedWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->passwordReminderDialogBlocked([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)passwordReminderDialogBlocked { if (self.megaApi) { self.megaApi->passwordReminderDialogBlocked(); } } - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout delegate:(id)delegate { if (self.megaApi) { self.megaApi->shouldShowPasswordReminderDialog(atLogout, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout { if (self.megaApi) { self.megaApi->shouldShowPasswordReminderDialog(atLogout); } } - (void)isMasterKeyExportedWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->isMasterKeyExported([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)isMasterKeyExported { if (self.megaApi) { self.megaApi->isMasterKeyExported(); } } - (void)getVisibleTermsOfServiceWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getVisibleTermsOfService([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setVisibleTermsOfService:(BOOL)visible delegate:(id)delegate { if (self.megaApi) { self.megaApi->setVisibleTermsOfService(visible, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } #ifdef ENABLE_CHAT - (void)enableRichPreviews:(BOOL)enable delegate:(id)delegate { if (self.megaApi) { self.megaApi->enableRichPreviews(enable, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)enableRichPreviews:(BOOL)enable { if (self.megaApi) { self.megaApi->enableRichPreviews(enable); } } - (void)isRichPreviewsEnabledWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->isRichPreviewsEnabled([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)isRichPreviewsEnabled { if (self.megaApi) { self.megaApi->isRichPreviewsEnabled(); } } - (void)shouldShowRichLinkWarningWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->shouldShowRichLinkWarning([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)shouldShowRichLinkWarning { if (self.megaApi) { self.megaApi->shouldShowRichLinkWarning(); } } - (void)setRichLinkWarningCounterValue:(NSUInteger)value delegate:(id)delegate { if (self.megaApi) { self.megaApi->setRichLinkWarningCounterValue((int)value, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setRichLinkWarningCounterValue:(NSUInteger)value { if (self.megaApi) { self.megaApi->setRichLinkWarningCounterValue((int)value); } } - (void)enableGeolocationWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->enableGeolocation([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)enableGeolocation { if (self.megaApi) { self.megaApi->enableGeolocation(); } } - (void)isGeolocationEnabledWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->isGeolocationEnabled([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)isGeolocationEnabled { if (self.megaApi) { self.megaApi->isGeolocationEnabled(); } } #endif - (void)setMyChatFilesFolderWithHandle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->setMyChatFilesFolder(handle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setMyChatFilesFolderWithHandle:(uint64_t)handle { if (self.megaApi) { self.megaApi->setMyChatFilesFolder(handle); } } - (void)getMyChatFilesFolderWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getMyChatFilesFolder([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getMyChatFilesFolder { if (self.megaApi) { self.megaApi->getMyChatFilesFolder(); } } - (void)setCameraUploadsFolderWithHandle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->setCameraUploadsFolder(handle, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)setCameraUploadsFolderWithHandle:(uint64_t)handle { if (self.megaApi) { self.megaApi->setCameraUploadsFolder(handle); } } - (void)getCameraUploadsFolderWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getCameraUploadsFolder([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getCameraUploadsFolder { if (self.megaApi) { self.megaApi->getCameraUploadsFolder(); } } - (void)getCameraUploadsFolderSecondaryWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getCameraUploadsFolderSecondary([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getCameraUploadsFolderSecondary { if (self.megaApi) { self.megaApi->getCameraUploadsFolderSecondary(); } } - (void)getRubbishBinAutopurgePeriodWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getRubbishBinAutopurgePeriod([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getRubbishBinAutopurgePeriod { if (self.megaApi) { self.megaApi->getRubbishBinAutopurgePeriod(); } } - (void)setRubbishBinAutopurgePeriodInDays:(NSInteger)days delegate:(id)delegate { if (self.megaApi) { self.megaApi->setRubbishBinAutopurgePeriod((int)days, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setRubbishBinAutopurgePeriodInDays:(NSInteger)days { if (self.megaApi) { self.megaApi->setRubbishBinAutopurgePeriod((int)days); } } - (void)inviteContactWithEmail:(NSString *)email message:(NSString *)message action:(MEGAInviteAction)action delegate:(id)delegate { if (self.megaApi) { self.megaApi->inviteContact(email.UTF8String, message.UTF8String, (int)action, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)inviteContactWithEmail:(NSString *)email message:(NSString *)message action:(MEGAInviteAction)action { if (self.megaApi) { self.megaApi->inviteContact(email.UTF8String, message.UTF8String, (int)action); } } - (void)inviteContactWithEmail:(NSString *)email message:(NSString *)message action:(MEGAInviteAction)action handle:(uint64_t)handle delegate:(id)delegate { if (self.megaApi) { self.megaApi->inviteContact(email.UTF8String, message.UTF8String, (int)action, handle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)inviteContactWithEmail:(NSString *)email message:(NSString *)message action:(MEGAInviteAction)action handle:(uint64_t)handle { if (self.megaApi) { self.megaApi->inviteContact(email.UTF8String, message.UTF8String, (int)action, handle); } } - (void)replyContactRequest:(MEGAContactRequest *)request action:(MEGAReplyAction)action delegate:(id)delegate { if (self.megaApi) { self.megaApi->replyContactRequest(request.getCPtr, (int)action, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)replyContactRequest:(MEGAContactRequest *)request action:(MEGAReplyAction)action { if (self.megaApi) { self.megaApi->replyContactRequest(request.getCPtr, (int)action); } } - (void)removeContactUser:(MEGAUser *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->removeContact(user.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)removeContactUser:(MEGAUser *)user { if (self.megaApi) { self.megaApi->removeContact(user.getCPtr); } } - (void)submitFeedbackWithRating:(NSInteger)rating comment:(NSString *)comment delegate:(id)delegate { if (self.megaApi) { self.megaApi->submitFeedback((int)rating, comment.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)submitFeedbackWithRating:(NSInteger)rating comment:(NSString *)comment { if (self.megaApi) { self.megaApi->submitFeedback((int)rating, comment.UTF8String); } } - (void)reportDebugEventWithText:(NSString *)text delegate:(id)delegate { if (self.megaApi) { self.megaApi->reportDebugEvent(text.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)reportDebugEventWithText:(NSString *)text { if (self.megaApi) { self.megaApi->reportDebugEvent(text.UTF8String); } } - (void)getUserDataWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserData([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserData { if (self.megaApi) { self.megaApi->getUserData(); } } - (void)getUserDataWithMEGAUser:(MEGAUser *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserData(user.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserDataWithMEGAUser:(MEGAUser *)user { if (self.megaApi) { self.megaApi->getUserData(user.getCPtr); } } - (void)getUserDataWithUser:(NSString *)user delegate:(id)delegate { if (self.megaApi) { self.megaApi->getUserData(user.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getUserDataWithUser:(NSString *)user { if (self.megaApi) { self.megaApi->getUserData(user.UTF8String); } } - (void)getMiscFlagsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getMiscFlags([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getMiscFlags { if (self.megaApi) { self.megaApi->getMiscFlags(); } } - (void)killSession:(uint64_t)sessionHandle delegate:(id)delegate { if (self.megaApi) { self.megaApi->killSession(sessionHandle, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)killSession:(uint64_t)sessionHandle { if (self.megaApi) { self.megaApi->killSession(sessionHandle); } } - (NSDate *)overquotaDeadlineDate { if (self.megaApi == nil) return nil; return [[NSDate alloc] initWithTimeIntervalSince1970:self.megaApi->getOverquotaDeadlineTs()]; } - (NSArray *)overquotaWarningDateList { if (self.megaApi == nil) return nil; MegaIntegerList *warningTimeIntervalList = self.megaApi->getOverquotaWarningsTs(); int sizeOfWarningTimestamps = warningTimeIntervalList->size(); NSMutableArray *warningDateList = [[NSMutableArray alloc] initWithCapacity:sizeOfWarningTimestamps]; for (int i = 0; i < sizeOfWarningTimestamps; i++) { NSDate *warningDate = [[NSDate alloc] initWithTimeIntervalSince1970:warningTimeIntervalList->get(i)]; [warningDateList addObject:warningDate]; } return [warningDateList copy]; } - (BOOL)setRLimitFileCount:(NSInteger)fileCount { if (self.megaApi == nil) return NO; return self.megaApi->platformSetRLimitNumFile((int)fileCount); } - (void)upgradeSecurityWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->upgradeSecurity([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } #pragma mark - Transfer - (MEGATransfer *)transferByTag:(NSInteger)transferTag { if (self.megaApi == nil) return nil; MegaTransfer *transfer = self.megaApi->getTransferByTag((int)transferTag); return transfer ? [[MEGATransfer alloc] initWithMegaTransfer:transfer cMemoryOwn:YES] : nil; } - (void)startUploadForSupportWithLocalPath:(NSString *)localPath isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate { if (self.megaApi) { self.megaApi->startUploadForSupport(localPath.UTF8String, isSourceTemporary, [self createDelegateMEGATransferListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)startUploadForSupportWithLocalPath:(NSString *)localPath isSourceTemporary:(BOOL)isSourceTemporary { if (self.megaApi) { self.megaApi->startUploadForSupport(localPath.UTF8String, isSourceTemporary); } } - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent fileName:(nullable NSString *)fileName appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst cancelToken:(nullable MEGACancelToken *)cancelToken { if (self.megaApi) { self.megaApi->startUpload(localPath.UTF8String, parent.getCPtr, fileName.UTF8String, MegaApi::INVALID_CUSTOM_MOD_TIME, appData.UTF8String, isSourceTemporary, startFirst, cancelToken.getCPtr); } } - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent fileName:(nullable NSString *)fileName appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst cancelToken:(nullable MEGACancelToken *)cancelToken delegate:(id)delegate { if (self.megaApi) { self.megaApi->startUpload(localPath.UTF8String, parent.getCPtr, fileName.UTF8String, MegaApi::INVALID_CUSTOM_MOD_TIME, appData.UTF8String, isSourceTemporary, startFirst, cancelToken.getCPtr, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } } - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent cancelToken:(nullable MEGACancelToken *)cancelToken options:(MEGAUploadOptions *)options delegate:(id)delegate { if (self.megaApi) { auto cppOptions = [self generateUploadOptionsFrom:options]; self.megaApi->startUpload(localPath.UTF8String, parent.getCPtr, cancelToken.getCPtr, &cppOptions, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } } - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent cancelToken:(nullable MEGACancelToken *)cancelToken options:(MEGAUploadOptions *)options { if (self.megaApi) { auto cppOptions = [self generateUploadOptionsFrom:options]; self.megaApi->startUpload(localPath.UTF8String, parent.getCPtr, cancelToken.getCPtr, &cppOptions); } } - (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath fileName:(nullable NSString*)fileName appData:(nullable NSString *)appData startFirst:(BOOL) startFirst cancelToken:(nullable MEGACancelToken *)cancelToken collisionCheck:(CollisionCheck)collisionCheck collisionResolution:(CollisionResolution)collisionResolution { if (self.megaApi) { self.megaApi->startDownload(node.getCPtr, localPath.UTF8String, fileName.UTF8String, appData.UTF8String, startFirst, cancelToken.getCPtr, (int)collisionCheck, (int)collisionResolution, false); } } - (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath fileName:(nullable NSString*)fileName appData:(nullable NSString *)appData startFirst:(BOOL) startFirst cancelToken:(nullable MEGACancelToken *)cancelToken collisionCheck:(CollisionCheck)collisionCheck collisionResolution:(CollisionResolution)collisionResolution delegate:(id)delegate { if (self.megaApi) { self.megaApi->startDownload(node.getCPtr, localPath.UTF8String, fileName.UTF8String, appData.UTF8String, startFirst, cancelToken.getCPtr, (int)collisionCheck, (int)collisionResolution, false, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } } - (void)startStreamingNode:(MEGANode *)node startPos:(NSNumber *)startPos size:(NSNumber *)size delegate:(id)delegate { if (self.megaApi) { self.megaApi->startStreaming(node.getCPtr, (startPos != nil) ? [startPos longLongValue] : 0, (size != nil) ? [size longLongValue] : 0, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } } - (void)startStreamingNode:(MEGANode *)node startPos:(NSNumber *)startPos size:(NSNumber *)size { if (self.megaApi) { self.megaApi->startStreaming(node.getCPtr, (startPos != nil) ? [startPos longLongValue] : 0, (size != nil) ? [size longLongValue] : 0, NULL); } } - (void)cancelTransfer:(MEGATransfer *)transfer delegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelTransfer(transfer.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelTransfer:(MEGATransfer *)transfer { if (self.megaApi) { self.megaApi->cancelTransfer(transfer.getCPtr); } } - (void)retryTransfer:(MEGATransfer *)transfer delegate:(id)delegate { if (self.megaApi) { self.megaApi->retryTransfer(transfer.getCPtr, [self createDelegateMEGATransferListener:delegate singleListener:YES]); } } - (void)retryTransfer:(MEGATransfer *)transfer { if (self.megaApi) { self.megaApi->retryTransfer(transfer.getCPtr); } } - (void)moveTransferToFirst:(MEGATransfer *)transfer delegate:(id)delegate { if (self.megaApi) { self.megaApi->moveTransferToFirst(transfer.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)moveTransferToFirst:(MEGATransfer *)transfer { if (self.megaApi) { self.megaApi->moveTransferToFirst(transfer.getCPtr); } } - (void)moveTransferToLast:(MEGATransfer *)transfer delegate:(id)delegate { if (self.megaApi) { self.megaApi->moveTransferToLast(transfer.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)moveTransferToLast:(MEGATransfer *)transfer { if (self.megaApi) { self.megaApi->moveTransferToLast(transfer.getCPtr); } } - (void)moveTransferBefore:(MEGATransfer *)transfer prevTransfer:(MEGATransfer *)prevTransfer delegate:(id)delegate { if (self.megaApi) { self.megaApi->moveTransferBefore(transfer.getCPtr, prevTransfer.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)moveTransferBefore:(MEGATransfer *)transfer prevTransfer:(MEGATransfer *)prevTransfer { if (self.megaApi) { self.megaApi->moveTransferBefore(transfer.getCPtr, prevTransfer.getCPtr); } } - (void)cancelTransfersForDirection:(NSInteger)direction delegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelTransfers((int)direction, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelTransfersForDirection:(NSInteger)direction { if (self.megaApi) { self.megaApi->cancelTransfers((int)direction); } } - (void)cancelTransferByTag:(NSInteger)transferTag delegate:(id)delegate { if (self.megaApi) { self.megaApi->cancelTransferByTag((int)transferTag, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cancelTransferByTag:(NSInteger)transferTag { if (self.megaApi) { self.megaApi->cancelTransferByTag((int)transferTag); } } - (void)pauseTransfers:(BOOL)pause delegate:(id)delegate { if (self.megaApi) { self.megaApi->pauseTransfers(pause, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)pauseTransfers:(BOOL)pause { if (self.megaApi) { self.megaApi->pauseTransfers(pause); } } - (void)pauseTransfers:(BOOL)pause forDirection:(NSInteger)direction delegate:(id)delegate { if (self.megaApi) { self.megaApi->pauseTransfers(pause, (int)direction, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)pauseTransfers:(BOOL)pause forDirection:(NSInteger)direction { if (self.megaApi) { self.megaApi->pauseTransfers(pause, (int)direction); } } - (void)pauseTransfer:(MEGATransfer *)transfer pause:(BOOL)pause delegate:(id)delegate { if (self.megaApi) { self.megaApi->pauseTransfer(transfer.getCPtr, pause, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)pauseTransfer:(MEGATransfer *)transfer pause:(BOOL)pause { if (self.megaApi) { self.megaApi->pauseTransfer(transfer.getCPtr, pause); } } - (void)pauseTransferByTag:(NSInteger)transferTag pause:(BOOL)pause delegate:(id)delegate { if (self.megaApi) { self.megaApi->pauseTransferByTag((int)transferTag, pause, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)pauseTransferByTag:(NSInteger)transferTag pause:(BOOL)pause { if (self.megaApi) { self.megaApi->pauseTransferByTag((int)transferTag, pause); } } - (BOOL)areTransferPausedForDirection:(NSInteger)direction { if (self.megaApi == nil) return NO; return self.megaApi->areTransfersPaused((int)direction); } - (BOOL)areThereAnyTransferWithAppDataMatching:(BOOL (^)(NSString *appData))filter { if (self.megaApi == nil) return NO; MegaTransferList *transferList = self.megaApi->getTransfers(); for (int i = 0; i < transferList->size(); i++) { MegaTransfer *transfer = transferList->get(i); const char *transferAppData = transfer->getAppData(); if (transferAppData != NULL && filter([[NSString alloc] initWithUTF8String:transferAppData])) { delete transferList; return YES; } } delete transferList; return NO; } - (void)requestBackgroundUploadURLWithFileSize:(int64_t)filesize mediaUpload:(MEGABackgroundMediaUpload *)mediaUpload delegate:(id)delegate { if (self.megaApi) { self.megaApi->backgroundMediaUploadRequestUploadURL(filesize, mediaUpload.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)completeBackgroundMediaUpload:(MEGABackgroundMediaUpload *)mediaUpload fileName:(NSString *)fileName parentNode:(MEGANode *)parentNode fingerprint:(NSString *)fingerprint originalFingerprint:(NSString *)originalFingerprint binaryUploadToken:(NSData *)token delegate:(id)delegate { if (self.megaApi) { const char *base64Token = MegaApi::binaryToBase64((const char *)token.bytes, token.length); self.megaApi->backgroundMediaUploadComplete(mediaUpload.getCPtr, fileName.UTF8String, parentNode.getCPtr, fingerprint.UTF8String, originalFingerprint.UTF8String, base64Token, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); delete[] base64Token; } } - (BOOL)ensureMediaInfo { if (self.megaApi == nil) return NO; return self.megaApi->ensureMediaInfo(); } - (BOOL)testAllocationByAllocationCount:(NSUInteger)count allocationSize:(NSUInteger)size { if (self.megaApi == nil) return NO; return self.megaApi->testAllocation((unsigned)count, size); } #pragma mark - Filesystem inspection - (NSInteger)numberChildrenForParent:(MEGANode *)parent { if (self.megaApi == nil) return 0; return self.megaApi->getNumChildren(parent.getCPtr); } - (NSInteger)numberChildFilesForParent:(MEGANode *)parent { if (self.megaApi == nil) return 0; return self.megaApi->getNumChildFiles(parent.getCPtr); } - (NSInteger)numberChildFoldersForParent:(MEGANode *)parent { if (self.megaApi == nil) return 0; return self.megaApi->getNumChildFolders(parent.getCPtr); } - (MEGANodeList *)childrenForParent:(MEGANode *)parent order:(NSInteger)order { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getChildren(parent.getCPtr, (int)order) cMemoryOwn:YES]; } - (MEGANodeList *)childrenForParent:(MEGANode *)parent { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getChildren(parent.getCPtr) cMemoryOwn:YES]; } - (MEGANodeList *)versionsForNode:(MEGANode *)node { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getVersions(node.getCPtr) cMemoryOwn:YES]; } - (NSInteger)numberOfVersionsForNode:(MEGANode *)node { if (self.megaApi == nil) return 0; return self.megaApi->getNumVersions(node.getCPtr); } - (BOOL)hasVersionsForNode:(MEGANode *)node { if (self.megaApi == nil) return NO; return self.megaApi->hasVersions(node.getCPtr); } - (void)getFolderInfoForNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi) { self.megaApi->getFolderInfo(node.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getFolderInfoForNode:(MEGANode *)node { if (self.megaApi) { self.megaApi->getFolderInfo(node.getCPtr); } } - (MEGANode *)childNodeForParent:(MEGANode *)parent name:(NSString *)name { if (parent == nil || name == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getChildNode([parent getCPtr], [name UTF8String]); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANode *)childNodeForParent:(MEGANode *)parent name:(NSString *)name type:(MEGANodeType)type { if (parent == nil || name == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getChildNodeOfType(parent.getCPtr, name.UTF8String, (int)type); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANode *)parentNodeForNode:(MEGANode *)node { if (node == nil || self.megaApi == nil) return nil; MegaNode *parent = self.megaApi->getParentNode([node getCPtr]); return parent ? [[MEGANode alloc] initWithMegaNode:parent cMemoryOwn:YES] : nil; } - (NSString *)nodePathForNode:(MEGANode *)node { if (node == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->getNodePath([node getCPtr]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (MEGANode *)nodeForPath:(NSString *)path node:(MEGANode *)node { if (path == nil || node == nil || self.megaApi == nil) return nil; MegaNode *n = self.megaApi->getNodeByPath([path UTF8String], [node getCPtr]); return n ? [[MEGANode alloc] initWithMegaNode:n cMemoryOwn:YES] : Nil; } - (MEGANode *)nodeForPath:(NSString *)path { if (path == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getNodeByPath([path UTF8String]); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANode *)nodeForHandle:(uint64_t)handle { if (handle == ::mega::INVALID_HANDLE || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getNodeByHandle(handle); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGAUserList *)contacts { if (self.megaApi == nil) return nil; return [[MEGAUserList alloc] initWithUserList:self.megaApi->getContacts() cMemoryOwn:YES]; } - (MEGAUser *)contactForEmail:(NSString *)email { if (email == nil || self.megaApi == nil) return nil; MegaUser *user = self.megaApi->getContact([email UTF8String]); return user ? [[MEGAUser alloc] initWithMegaUser:user cMemoryOwn:YES] : nil; } - (MEGAUserAlertList *)userAlertList { if (self.megaApi == nil) return nil; return [[MEGAUserAlertList alloc] initWithMegaUserAlertList:self.megaApi->getUserAlerts() cMemoryOwn:YES]; } - (MEGANodeList *)inSharesForUser:(MEGAUser *)user { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getInShares(user.getCPtr) cMemoryOwn:YES]; } - (MEGANodeList *)inShares { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getInShares() cMemoryOwn:YES]; } - (MEGAShareList *)inSharesList:(MEGASortOrderType)order { if (self.megaApi == nil) return nil; return [[MEGAShareList alloc] initWithShareList:self.megaApi->getInSharesList((int)order) cMemoryOwn:YES]; } - (MEGAShareList *)getUnverifiedInShares:(MEGASortOrderType)order { if (self.megaApi == nil) return nil; return [[MEGAShareList alloc] initWithShareList:self.megaApi->getUnverifiedInShares((int)order) cMemoryOwn:YES]; } - (MEGAUser *)userFromInShareNode:(MEGANode *)node { if (self.megaApi == nil) return nil; return [[MEGAUser alloc] initWithMegaUser:self.megaApi->getUserFromInShare(node.getCPtr) cMemoryOwn:YES]; } - (MEGAUser *)userFromInShareNode:(MEGANode *)node recurse:(BOOL)recurse { if (self.megaApi == nil) return nil; return [MEGAUser.alloc initWithMegaUser:self.megaApi->getUserFromInShare(node.getCPtr, recurse) cMemoryOwn:YES]; } - (MEGAShareList *)outShares:(MEGASortOrderType)order { if (self.megaApi == nil) return nil; return [[MEGAShareList alloc] initWithShareList:self.megaApi->getOutShares((int)order) cMemoryOwn:YES]; } - (MEGAShareList *)getUnverifiedOutShares:(MEGASortOrderType)order { if (self.megaApi == nil) return nil; return [[MEGAShareList alloc] initWithShareList:self.megaApi->getUnverifiedOutShares((int)order) cMemoryOwn:YES]; } - (MEGAShareList *)outSharesForNode:(MEGANode *)node { if (self.megaApi == nil) return nil; return [[MEGAShareList alloc] initWithShareList:self.megaApi->getOutShares(node.getCPtr) cMemoryOwn:YES]; } - (BOOL)isPrivateNode:(uint64_t)handle { return self.megaApi->isPrivateNode(handle); } - (BOOL)isForeignNode:(uint64_t)handle{ return self.megaApi->isForeignNode(handle); } - (MEGANodeList *)publicLinks:(MEGASortOrderType)order { if (self.megaApi == nil) return nil; return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getPublicLinks((int)order) cMemoryOwn:YES]; } - (MEGAContactRequestList *)incomingContactRequests { if (self.megaApi == nil) return nil; return [[MEGAContactRequestList alloc] initWithMegaContactRequestList:self.megaApi->getIncomingContactRequests() cMemoryOwn:YES]; } - (MEGAContactRequestList *)outgoingContactRequests { if (self.megaApi == nil) return nil; return [[MEGAContactRequestList alloc] initWithMegaContactRequestList:self.megaApi->getOutgoingContactRequests() cMemoryOwn:YES]; } - (NSString *)fingerprintForFilePath:(NSString *)filePath { if (filePath == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->getFingerprint([filePath UTF8String]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)fingerprintForData:(NSData *)data modificationTime:(NSDate *)modificationTime { if (data == nil || self.megaApi == nil) return nil; MEGADataInputStream mis = MEGADataInputStream(data); return [self fingerprintForInputStream:&mis modificationTime:modificationTime]; } - (NSString *)fingerprintForFilePath:(NSString *)filePath modificationTime:(NSDate *)modificationTime { if (filePath.length == 0) return nil; MEGAFileInputStream mis = MEGAFileInputStream(filePath); return [self fingerprintForInputStream:&mis modificationTime:modificationTime]; } - (NSString *)fingerprintForInputStream:(MegaInputStream *)stream modificationTime:(NSDate *)modificationTime { if (self.megaApi == nil) return nil; const char *val = self.megaApi->getFingerprint(stream, (long long)[modificationTime timeIntervalSince1970]); if (val != NULL) { NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } else { return nil; } } - (MEGANode *)nodeForFingerprint:(NSString *)fingerprint { if (fingerprint == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getNodeByFingerprint([fingerprint UTF8String]); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANode *)nodeForFingerprint:(NSString *)fingerprint parent:(MEGANode *)parent { if (fingerprint == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getNodeByFingerprint(fingerprint.UTF8String, parent.getCPtr); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGANodeList *)nodesForOriginalFingerprint:(NSString *)fingerprint { if (fingerprint.length == 0 || self.megaApi == nil) { return nil; } return [[MEGANodeList alloc] initWithNodeList:self.megaApi->getNodesByOriginalFingerprint([fingerprint UTF8String], NULL) cMemoryOwn:YES]; } - (BOOL)hasFingerprint:(NSString *)fingerprint{ if (fingerprint == nil || self.megaApi == nil) return NO; return self.megaApi->hasFingerprint([fingerprint UTF8String]); } - (NSString *)CRCForFilePath:(NSString *)filePath { if (filePath == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->getCRC([filePath UTF8String]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)CRCForFingerprint:(NSString *)fingerprint{ if (fingerprint == nil || self.megaApi == nil) { return nil; } const char *val = self.megaApi->getCRCFromFingerprint([fingerprint UTF8String]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)CRCForNode:(MEGANode *)node { if (node == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->getCRC([node getCPtr]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (MEGANode *)nodeByCRC:(NSString *)crc parent:(MEGANode *)parent { if (crc == nil || self.megaApi == nil) return nil; MegaNode *node = self.megaApi->getNodeByCRC([crc UTF8String], parent.getCPtr); return node ? [[MEGANode alloc] initWithMegaNode:node cMemoryOwn:YES] : nil; } - (MEGAShareType)accessLevelForNode:(MEGANode *)node { if (node == nil || self.megaApi == nil) return MEGAShareTypeAccessUnknown; return (MEGAShareType) self.megaApi->getAccess([node getCPtr]); } - (MEGAShareType)accessLevelForNodeHande:(uint64_t)nodeHandle { if (self.megaApi == nil) return MEGAShareTypeAccessUnknown; return (MEGAShareType) self.megaApi->getAccess(nodeHandle); } - (MEGAError *)checkAccessErrorExtendedForNode:(MEGANode *)node level:(MEGAShareType)level { if (self.megaApi == nil) return nil; return [[MEGAError alloc] initWithMegaError:self.megaApi->checkAccessErrorExtended(node.getCPtr, (int)level) cMemoryOwn:YES]; } - (BOOL)isNodeInRubbish:(MEGANode *)node { if (self.megaApi == nil) return NO; return self.megaApi->isInRubbish(node.getCPtr); } -(BOOL)isNodeInheritingSensitivity:(MEGANode *)node { if (self.megaApi == nil) return NO; return self.megaApi->isSensitiveInherited(node.getCPtr); } - (nullable NSArray *)nodeTagsForSearchString:(nullable NSString *)searchString cancelToken:(MEGACancelToken *)cancelToken { if (self.megaApi == nil) return nil; MegaStringList *result = self.megaApi->getAllNodeTags(searchString.UTF8String, cancelToken.getCPtr); MEGAStringList *tagsStringList = [MEGAStringList.alloc initWithMegaStringList:result cMemoryOwn:YES]; return tagsStringList.toStringArray; } - (void)addTag:(NSString *)tag toNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi != nil) { self.megaApi->addNodeTag(node.getCPtr, tag.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)removeTag:(NSString *)tag fromNode:(MEGANode *)node delegate:(id)delegate { if (self.megaApi != nil) { self.megaApi->removeNodeTag(node.getCPtr, tag.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (MEGAError *)checkMoveErrorExtendedForNode:(MEGANode *)node target:(MEGANode *)target { if (self.megaApi == nil) return nil; return [[MEGAError alloc] initWithMegaError:self.megaApi->checkMoveErrorExtended(node.getCPtr, target.getCPtr) cMemoryOwn:YES]; } - (MEGANodeList *)searchWith:(MEGASearchFilter *)filter orderType:(MEGASortOrderType)orderType page:(nullable MEGASearchPage *)page cancelToken:(MEGACancelToken *)cancelToken { if (self.megaApi == nil) return nil; auto cppFilter = [self generateSearchFilterFrom:filter]; auto cppPage = [self generateSearchPageFrom:page]; MegaNodeList *nodeList = self.megaApi->search( cppFilter.get(), static_cast(orderType), cancelToken.getCPtr, cppPage.get() ); return [MEGANodeList.alloc initWithNodeList:nodeList cMemoryOwn:YES]; } - (MEGANodeList *)searchNonRecursivelyWith:(MEGASearchFilter *)filter orderType:(MEGASortOrderType)orderType page:(nullable MEGASearchPage *)page cancelToken:(MEGACancelToken *)cancelToken { if (self.megaApi == nil) return nil; auto cppFilter = [self generateSearchFilterFrom:filter]; auto cppPage = [self generateSearchPageFrom:page]; MegaNodeList *nodeList = self.megaApi->getChildren( cppFilter.get(), static_cast(orderType), cancelToken.getCPtr, cppPage.get() ); return [MEGANodeList.alloc initWithNodeList:nodeList cMemoryOwn:YES]; } - (void)getRecentActionsAsyncSinceDays:(NSInteger)days maxNodes:(NSInteger)maxNodes excludeSensitives:(BOOL)excludeSensitives delegate:(id)delegate { if (self.megaApi != nil) { self.megaApi->getRecentActionsAsync((int)days, (unsigned int)maxNodes, excludeSensitives, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)clearRecentActionHistoryUntil:(int64_t)until delegate:(id)delegate { if (self.megaApi != nil) { self.megaApi->clearRecentActionHistory((MegaTimeStamp)until, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (BOOL)processMEGANodeTree:(MEGANode *)node recursive:(BOOL)recursive delegate:(id)delegate { if (self.megaApi == nil) return NO; return self.megaApi->processMegaTree(node.getCPtr, [self createMegaTreeProcessor:delegate], recursive); } - (MEGANode *)authorizeNode:(MEGANode *)node { if (self.megaApi == nil) return nil; return [[MEGANode alloc] initWithMegaNode:self.megaApi->authorizeNode(node.getCPtr) cMemoryOwn:YES]; } #ifdef ENABLE_CHAT - (MEGANode *)authorizeChatNode:(MEGANode *)node cauth:(NSString *)cauth { if (self.megaApi == nil) return nil; return [[MEGANode alloc] initWithMegaNode:self.megaApi->authorizeChatNode(node.getCPtr, cauth.UTF8String) cMemoryOwn:YES]; } #endif - (NSNumber *)sizeForNode:(MEGANode *)node { if (self.megaApi == nil) return nil; return [[NSNumber alloc] initWithLongLong:self.megaApi->getSize([node getCPtr])]; } - (NSString *)escapeFsIncompatible:(NSString *)name destinationPath:(NSString *)destinationPath { if (name == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->escapeFsIncompatible(name.UTF8String, destinationPath.UTF8String); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (NSString *)unescapeFsIncompatible:(NSString *)localName destinationPath:(NSString *)destinationPath { if (localName == nil || self.megaApi == nil) return nil; const char *val = self.megaApi->unescapeFsIncompatible(localName.UTF8String, destinationPath.UTF8String); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (void)changeApiUrl:(NSString *)apiURL disablepkp:(BOOL)disablepkp { if (self.megaApi) { self.megaApi->changeApiUrl(apiURL.UTF8String, disablepkp); } } - (BOOL)setLanguageCode:(NSString *)languageCode { if (self.megaApi == nil) return NO; return self.megaApi->setLanguage(languageCode.UTF8String); } - (void)setLanguangePreferenceCode:(NSString *)languageCode delegate:(id)delegate { if (self.megaApi) { self.megaApi->setLanguagePreference(languageCode.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setLanguangePreferenceCode:(NSString *)languageCode { if (self.megaApi) { self.megaApi->setLanguagePreference(languageCode.UTF8String); } } - (void)getLanguagePreferenceWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getLanguagePreference([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getLanguagePreference { if (self.megaApi) { self.megaApi->getLanguagePreference(); } } - (void)setFileVersionsOption:(BOOL)disable delegate:(id)delegate { if (self.megaApi) { self.megaApi->setFileVersionsOption(disable, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setFileVersionsOption:(BOOL)disable { if (self.megaApi) { self.megaApi->setFileVersionsOption(disable); } } - (void)getFileVersionsOptionWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getFileVersionsOption([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getFileVersionsOption { if (self.megaApi) { self.megaApi->getFileVersionsOption(); } } - (void)setContactLinksOption:(BOOL)enable delegate:(id)delegate { if (self.megaApi) { self.megaApi->setContactLinksOption(enable, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getContactLinksOptionWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getContactLinksOption([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getContactLinksOption { if (self.megaApi) { self.megaApi->getContactLinksOption(); } } - (void)retrySSLErrors:(BOOL)enable { if (self.megaApi) { self.megaApi->retrySSLerrors(enable); } } - (void)setPublicKeyPinning:(BOOL)enable { if (self.megaApi) { self.megaApi->setPublicKeyPinning(enable); } } - (BOOL)createThumbnail:(NSString *)imagePath destinatioPath:(NSString *)destinationPath { if (imagePath == nil || destinationPath == nil || self.megaApi == nil) return NO; return self.megaApi->createThumbnail([imagePath UTF8String], [destinationPath UTF8String]); } - (BOOL)createPreview:(NSString *)imagePath destinatioPath:(NSString *)destinationPath { if (imagePath == nil || destinationPath == nil || self.megaApi == nil) return NO; return self.megaApi->createPreview([imagePath UTF8String], [destinationPath UTF8String]); } - (BOOL)createAvatar:(NSString *)imagePath destinationPath:(NSString *)destinationPath { if (imagePath == nil || destinationPath == nil || self.megaApi == nil) return NO; return self.megaApi->createAvatar([imagePath UTF8String], [destinationPath UTF8String]); } #ifdef HAVE_LIBUV #pragma mark - HTTP Proxy Server - (BOOL)httpServerStart:(BOOL)localOnly port:(NSInteger)port { if (self.megaApi == nil) return NO; return self.megaApi->httpServerStart(localOnly, (int)port, false, NULL, NULL, true); } - (void)httpServerStop { if (self.megaApi) { self.megaApi->httpServerStop(); } } - (NSInteger)httpServerIsRunning { if (self.megaApi == nil) return 0; return (NSInteger)self.megaApi->httpServerIsRunning(); } - (BOOL)httpServerIsLocalOnly { if (self.megaApi == nil) return NO; return self.megaApi->httpServerIsLocalOnly(); } - (void)httpServerEnableFileServer:(BOOL)enable { if (self.megaApi) { self.megaApi->httpServerEnableFileServer(enable); } } - (BOOL)httpServerIsFileServerEnabled { if (self.megaApi == nil) return NO; return self.megaApi->httpServerIsFileServerEnabled(); } - (void)httpServerEnableFolderServer:(BOOL)enable { if (self.megaApi) { self.megaApi->httpServerEnableFolderServer(enable); } } - (BOOL)httpServerIsFolderServerEnabled { if (self.megaApi == nil) return NO; return self.megaApi->httpServerIsFolderServerEnabled(); } - (void)httpServerSetRestrictedMode:(NSInteger)mode { if (self.megaApi) { self.megaApi->httpServerSetRestrictedMode((int)mode); } } - (NSInteger)httpServerGetRestrictedMode { if (self.megaApi == nil) return 0; return (NSInteger)self.megaApi->httpServerGetRestrictedMode(); } - (void)httpServerEnableSubtitlesSupport:(BOOL)enable { if (self.megaApi) { self.megaApi->httpServerEnableSubtitlesSupport(enable); } } - (BOOL)httpServerIsSubtitlesSupportEnabled { if (self.megaApi == nil) return NO; return self.megaApi->httpServerIsSubtitlesSupportEnabled(); } - (void)httpServerAddDelegate:(id)delegate { if (self.megaApi) { self.megaApi->httpServerAddListener([self createDelegateMEGATransferListener:delegate singleListener:NO]); } } - (void)httpServerRemoveDelegate:(id)delegate { if (self.megaApi) { self.megaApi->httpServerRemoveListener([self createDelegateMEGATransferListener:delegate singleListener:NO]); } } - (NSURL *)httpServerGetLocalLink:(MEGANode *)node { if (self.megaApi == nil) return nil; const char *val = self.megaApi->httpServerGetLocalLink([node getCPtr]); if (!val) return nil; NSURL *ret = [NSURL URLWithString:[NSString stringWithUTF8String:val]]; delete [] val; return ret; } - (void)httpServerSetMaxBufferSize:(NSInteger)bufferSize { if (self.megaApi) { self.megaApi->httpServerSetMaxBufferSize((int)bufferSize); } } - (NSInteger)httpServerGetMaxBufferSize { if (self.megaApi == nil) return 0; return (NSInteger)self.megaApi->httpServerGetMaxBufferSize(); } - (void)httpServerSetMaxOutputSize:(NSInteger)outputSize { if (self.megaApi) { self.megaApi->httpServerSetMaxOutputSize((int)outputSize); } } - (NSInteger)httpServerGetMaxOutputSize { if (self.megaApi == nil) return 0; return (NSInteger)self.megaApi->httpServerGetMaxOutputSize(); } #endif + (NSString *)mimeTypeByExtension:(NSString *)extension { const char *val = MegaApi::getMimeType([extension UTF8String]); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } #ifdef ENABLE_CHAT - (void)registeriOSdeviceToken:(NSString *)deviceToken delegate:(id)delegate { if (self.megaApi) { self.megaApi->registerPushNotifications(PushNotificationTokenTypeiOSStandard, deviceToken.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)registeriOSdeviceToken:(NSString *)deviceToken { if (self.megaApi) { self.megaApi->registerPushNotifications(PushNotificationTokenTypeiOSStandard, deviceToken.UTF8String); } } - (void)registeriOSVoIPdeviceToken:(NSString *)deviceToken delegate:(id)delegate { if (self.megaApi) { self.megaApi->registerPushNotifications(PushNotificationTokenTypeiOSVoIP, deviceToken.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)registeriOSVoIPdeviceToken:(NSString *)deviceToken { if (self.megaApi) { self.megaApi->registerPushNotifications(PushNotificationTokenTypeiOSVoIP, deviceToken.UTF8String); } } #endif - (void)getAccountAchievementsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getAccountAchievements([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getAccountAchievements { if (self.megaApi) { self.megaApi->getAccountAchievements(); } } - (void)getMegaAchievementsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getMegaAchievements([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getMegaAchievements { if (self.megaApi) { self.megaApi->getMegaAchievements(); } } - (void)catchupWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->catchup([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPublicLinkInformationWithFolderLink:(NSString *)folderLink delegate:(id)delegate { if (self.megaApi) { self.megaApi->getPublicLinkInformation(folderLink.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPublicLinkInformationWithFolderLink:(NSString *)folderLink { if (self.megaApi) { self.megaApi->getPublicLinkInformation(folderLink.UTF8String); } } #pragma mark - SMS - (SMSState)smsAllowedState { if (self.megaApi == nil) return SMSStateNotAllowed; return (SMSState)self.megaApi->smsAllowedState(); } - (nullable NSString *)smsVerifiedPhoneNumber { if (self.megaApi == nil) return nil; char *number = self.megaApi->smsVerifiedPhoneNumber(); if (number == NULL) { return nil; } NSString *numberString = @(number); delete [] number; return numberString; } - (void)getCountryCallingCodesWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getCountryCallingCodes([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)sendSMSVerificationCodeToPhoneNumber:(NSString *)phoneNumber delegate:(id)delegate { if (self.megaApi) { self.megaApi->sendSMSVerificationCode([phoneNumber UTF8String], [self createDelegateMEGARequestListener:delegate singleListener:YES], YES); } } - (void)checkSMSVerificationCode:(NSString *)verificationCode delegate:(id)delegate { if (self.megaApi) { self.megaApi->checkSMSVerificationCode([verificationCode UTF8String], [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetSmsVerifiedPhoneNumberWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->resetSmsVerifiedPhoneNumber([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)resetSmsVerifiedPhoneNumber { if (self.megaApi) { self.megaApi->resetSmsVerifiedPhoneNumber(); } } #pragma mark - Push Notification Settings - (void)getPushNotificationSettingsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getPushNotificationSettings([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)getPushNotificationSettings { if (self.megaApi) { self.megaApi->getPushNotificationSettings(); } } - (void)setPushNotificationSettings:(MEGAPushNotificationSettings *)pushNotificationSettings delegate:(id)delegate { if (self.megaApi) { self.megaApi->setPushNotificationSettings(pushNotificationSettings.getCPtr, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setPushNotificationSettings:(MEGAPushNotificationSettings *)pushNotificationSettings { if (self.megaApi) { self.megaApi->setPushNotificationSettings(pushNotificationSettings.getCPtr); } } #pragma mark - Banner - (void)getBanners:(id)delegate { if (self.megaApi) { self.megaApi -> getBanners([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)dismissBanner:(NSInteger)bannerIdentifier delegate:(id)delegate { if (self.megaApi) { self.megaApi -> dismissBanner((int)bannerIdentifier, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } #pragma mark - Backup Heartbeat - (void)registerBackup:(BackUpType)type targetNode:(MEGANode *)node folderPath:(NSString *)path name:(NSString *)name state:(BackUpState)state delegate:(id)delegate { if (self.megaApi) { self.megaApi->setBackup((int)type, node.handle, path.UTF8String, name.UTF8String, (int)state, 0, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)updateBackup:(MEGAHandle)backupId backupType:(BackUpType)type targetNode:(nullable MEGANode *)node folderPath:(nullable NSString *)path backupName:(nullable NSString *)name state:(BackUpState)state subState:(BackUpSubState)subState delegate:(id)delegate { if (self.megaApi) { self.megaApi->updateBackup(backupId, (int)type, node.handle, path.UTF8String, name.UTF8String, (int)state, (int)subState, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)unregisterBackup:(MEGAHandle)backupId delegate:(id)delegate { if (self.megaApi) { self.megaApi->removeBackup(backupId, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getBackupInfo:(id)delegate { if (self.megaApi) { self.megaApi->getBackupInfo([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)sendBackupHeartbeat:(MEGAHandle)backupId status:(BackupHeartbeatStatus)status progress:(NSInteger)progress pendingUploadCount:(NSUInteger)pendingUploadCount lastActionDate:(nullable NSDate *)lastActionDate lastBackupNode:(nullable MEGANode *)lastBackupNode delegate:(id)delegate { if (self.megaApi) { self.megaApi->sendBackupHeartbeat(backupId, (int)status, (int)progress, (int)pendingUploadCount, 0, lastActionDate != nil ? (long long)[lastActionDate timeIntervalSince1970] : (long long)0, lastBackupNode != nil ? lastBackupNode.handle : INVALID_HANDLE, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (nullable NSString *)deviceId { if (self.megaApi) { const char *val = self.megaApi->getDeviceId(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } return nil; } - (void)getDeviceName:(nullable NSString *)deviceId delegate:(id)delegate { if (self.megaApi) { self.megaApi->getDeviceName(deviceId.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)renameDevice:(nullable NSString *)deviceId newName:(NSString *)name delegate:(id)delegate { if (self.megaApi) { self.megaApi->setDeviceName(deviceId.UTF8String, name.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } #pragma mark - Debug + (void)setLogLevel:(MEGALogLevel)logLevel { MegaApi::setLogLevel((int)logLevel); } + (void)setLogToConsole:(BOOL)enable { MegaApi::setLogToConsole(enable); } + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message filename:(NSString *)filename line:(NSInteger)line { MegaApi::log((int)logLevel, message.UTF8String, filename.UTF8String, (int)line); } + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message filename:(NSString *)filename { MegaApi::log((int)logLevel, message.UTF8String, filename.UTF8String); } + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message { MegaApi::log((int)logLevel, message.UTF8String); } - (void)sendEvent:(NSInteger)eventType message:(NSString *)message addJourneyId:(BOOL)addJourneyId viewId:(nullable NSString *)viewId delegate:(id)delegate { if (self.megaApi) { self.megaApi->sendEvent((int)eventType, message.UTF8String, addJourneyId, viewId.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)sendEvent:(NSInteger)eventType message:(NSString *)message addJourneyId:(BOOL)addJourneyId viewId:(nullable NSString *)viewId { if (self.megaApi) { self.megaApi->sendEvent((int)eventType, message.UTF8String, addJourneyId, viewId.UTF8String); } } - (NSString *)generateViewId { if (self.megaApi == nil) return nil; const char *val = self.megaApi->generateViewId(); if (!val) return nil; NSString *ret = [[NSString alloc] initWithUTF8String:val]; delete [] val; return ret; } - (void)createSupportTicketWithMessage:(NSString *)message type:(NSInteger)type delegate:(id)delegate { if (self.megaApi) { self.megaApi->createSupportTicket(message.UTF8String, (int)type, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)createSupportTicketWithMessage:(NSString *)message type:(NSInteger)type { if (self.megaApi) { self.megaApi->createSupportTicket(message.UTF8String, (int)type); } } #pragma mark - Private methods - (MegaRequestListener *)createDelegateMEGARequestListener:(id)delegate singleListener:(BOOL)singleListener { return [self createDelegateMEGARequestListener:delegate singleListener:singleListener queueType:ListenerQueueTypeMain]; } - (MegaRequestListener *)createDelegateMEGARequestListener:(id)delegate singleListener:(BOOL)singleListener queueType:(ListenerQueueType)queueType { if (delegate == nil) return nil; DelegateMEGARequestListener *delegateListener = new DelegateMEGARequestListener(self, delegate, singleListener, queueType); pthread_mutex_lock(&listenerMutex); _activeRequestListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaTransferListener *)createDelegateMEGATransferListener:(id)delegate singleListener:(BOOL)singleListener { return [self createDelegateMEGATransferListener:delegate singleListener:singleListener queueType:ListenerQueueTypeMain]; } - (MegaTransferListener *)createDelegateMEGATransferListener:(id)delegate singleListener:(BOOL)singleListener queueType:(ListenerQueueType)queueType { if (delegate == nil) return nil; DelegateMEGATransferListener *delegateListener = new DelegateMEGATransferListener(self, delegate, singleListener, queueType); pthread_mutex_lock(&listenerMutex); _activeTransferListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaGlobalListener *)createDelegateMEGAGlobalListener:(id)delegate queueType:(ListenerQueueType)queueType { if (delegate == nil) return nil; DelegateMEGAGlobalListener *delegateListener = new DelegateMEGAGlobalListener(self, delegate, queueType); pthread_mutex_lock(&listenerMutex); _activeGlobalListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaListener *)createDelegateMEGAListener:(id)delegate { if (delegate == nil) return nil; DelegateMEGAListener *delegateListener = new DelegateMEGAListener(self, delegate); pthread_mutex_lock(&listenerMutex); _activeMegaListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaLogger *)createDelegateMegaLogger:(id)delegate { if (delegate == nil) return nil; DelegateMEGALoggerListener *delegateListener = new DelegateMEGALoggerListener(delegate); pthread_mutex_lock(&listenerMutex); _activeLoggerListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaScheduledCopyListener *)createDelegateMEGAScheduledCopyListener:(id)delegate queueType:(ListenerQueueType)queueType { if (delegate == nil) return nil; DelegateMEGAScheduledCopyListener *delegateListener = new DelegateMEGAScheduledCopyListener(self, delegate, queueType); pthread_mutex_lock(&listenerMutex); _activeScheduledCopyListeners.insert(delegateListener); pthread_mutex_unlock(&listenerMutex); return delegateListener; } - (MegaTreeProcessor *)createMegaTreeProcessor:(id)delegate { if (delegate == nil) return nil; DelegateMEGATreeProcessorListener *delegateListener = new DelegateMEGATreeProcessorListener(delegate); return delegateListener; } - (void)freeRequestListener:(DelegateMEGARequestListener *)delegate { if (delegate == nil) return; pthread_mutex_lock(&listenerMutex); _activeRequestListeners.erase(delegate); pthread_mutex_unlock(&listenerMutex); delete delegate; } - (void)freeTransferListener:(DelegateMEGATransferListener *)delegate { if (delegate == nil) return; pthread_mutex_lock(&listenerMutex); _activeTransferListeners.erase(delegate); pthread_mutex_unlock(&listenerMutex); delete delegate; } - (std::unique_ptr)generateSearchFilterFrom:(MEGASearchFilter *)filter { // createInstance() returns a raw pointer -> wrap in unique_ptr immediately std::unique_ptr megaFilter(MegaSearchFilter::createInstance()); megaFilter->byName(filter.term.UTF8String); megaFilter->byDescription(filter.searchDescription.UTF8String); megaFilter->byNodeType((int)filter.nodeType); megaFilter->byCategory((int)filter.category); megaFilter->bySensitivity((int)filter.sensitiveFilter); megaFilter->byFavourite((int)filter.favouriteFilter); megaFilter->byTag(filter.searchTag.UTF8String); megaFilter->useAndForTextQuery(filter.useAndForTextQuery); if (filter.didSetLocationType) { megaFilter->byLocation(filter.locationType); } if (filter.didSetParentNodeHandle) { megaFilter->byLocationHandle(filter.parentNodeHandle); } if (filter.creationTimeFrame != nil) { megaFilter->byCreationTime(filter.creationTimeFrame.lowerLimit, filter.creationTimeFrame.upperLimit); } if (filter.modificationTimeFrame != nil) { megaFilter->byModificationTime(filter.modificationTimeFrame.lowerLimit, filter.modificationTimeFrame.upperLimit); } // return by value → move semantics applies, caller takes ownership return megaFilter; } - (std::unique_ptr)generateSearchPageFrom:(nullable MEGASearchPage *)page { if (page == nil) { return nullptr; // unique_ptr can be empty } return std::unique_ptr( MegaSearchPage::createInstance(page.startingOffset, page.pageSize) ); } - (MegaUploadOptions)generateUploadOptionsFrom:(MEGAUploadOptions *)options { MegaUploadOptions uploadOptions; if (options.fileName != nil && options.fileName.length > 0) { uploadOptions.fileName = options.fileName.UTF8String; } uploadOptions.mtime = options.mtime; if (options.appData != nil && options.appData.length > 0) { uploadOptions.appData = options.appData.UTF8String; } uploadOptions.isSourceTemporary = options.isSourceTemporary; uploadOptions.startFirst = options.startFirst; uploadOptions.pitagTrigger = options.pitagTrigger; uploadOptions.isChatUpload = options.isChatUpload; uploadOptions.pitagTarget = options.pitagTarget; return uploadOptions; } #pragma mark - Cookie Dialog - (void)setCookieSettings:(NSInteger)settings delegate:(id)delegate { if (self.megaApi) { self.megaApi->setCookieSettings((int)settings, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)setCookieSettings:(NSInteger)settings { if (self.megaApi) { self.megaApi->setCookieSettings((int)settings); } } - (void)cookieSettingsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getCookieSettings([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)cookieSettings { if (self.megaApi) { self.megaApi->getCookieSettings(); } } - (BOOL)cookieBannerEnabled { if (self.megaApi == nil) return NO; return self.megaApi->cookieBannerEnabled(); } #pragma mark - A/B Testing - (NSInteger)getABTestValue:(NSString*)flag { if (self.megaApi == nil) return 0; return self.megaApi->getABTestValue((const char *)flag.UTF8String); } #pragma mark - Remote Feature Flags - (NSInteger)remoteFeatureFlagValue:(NSString *)flag { if (self.megaApi == nil) return 0; MegaFlag *flagValue = self.megaApi->getFlag(flag.UTF8String); uint32_t group = flagValue->getGroup(); delete flagValue; return NSInteger(group); } #pragma mark - Ads - (void)fetchAds:(AdsFlag)adFlags adUnits:(MEGAStringList *)adUnits publicHandle:(MEGAHandle)publicHandle delegate:(id)delegate { if (self.megaApi) { self.megaApi->fetchAds((int)adFlags, adUnits.getCPtr, publicHandle, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)queryAds:(AdsFlag)adFlags publicHandle:(MEGAHandle)publicHandle delegate:(id)delegate { if (self.megaApi) { self.megaApi->queryAds((int)adFlags, publicHandle, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)enableRequestStatusMonitor:(BOOL)enable { if (self.megaApi) { self.megaApi->enableRequestStatusMonitor(enable); } } - (BOOL)isRequestStatusMonitorEnabled { if (self.megaApi) { return self.megaApi->requestStatusMonitorEnabled(); } else { return NO; } } #pragma mark - VPN - (void)getVpnRegionsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getVpnRegions([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getVpnCredentialsWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getVpnCredentials([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)putVpnCredentialWithRegion:(NSString *)region delegate:(id)delegate { if (self.megaApi) { self.megaApi->putVpnCredential(region.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)delVpnCredentialWithSlotID:(NSInteger)slotID delegate:(id)delegate { if (self.megaApi) { self.megaApi->delVpnCredential((int)slotID, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)checkVpnCredentialWithUserPubKey:(NSString *)userPubKey delegate:(id)delegate { if (self.megaApi) { self.megaApi->checkVpnCredential(userPubKey.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getMyIPWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getMyIp([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)runNetworkConnectivityTestWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->runNetworkConnectivityTest([self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)getSubscriptionCancellationDetailsWithGateway:(MEGAPaymentMethod)gateway originalTransactionId:(nullable NSString *)originalTransactionId delegate:(id)delegate { if (self.megaApi) { self.megaApi->getSubscriptionCancellationDetails((int)gateway, originalTransactionId.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } #pragma mark - Password Manager - (void)getPasswordManagerBaseWithDelegate:(id)delegate { if (self.megaApi) { self.megaApi->getPasswordManagerBase([self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (BOOL)isPasswordManagerNodeFolderWithHandle:(MEGAHandle)node { if (self.megaApi == nil) return NO; return self.megaApi->isPasswordManagerNodeFolder(node); } - (void)createPasswordNodeWithName:(NSString *)name data:(PasswordNodeData *)data parent:(MEGAHandle)parent delegate:(id)delegate { if (self.megaApi) { MegaNode::PasswordNodeData *passwordNodeData = MegaNode::PasswordNodeData::createInstance(data.password.UTF8String, data.notes.UTF8String, data.url.UTF8String, data.userName.UTF8String, data.totp.getCPtr); self.megaApi->createPasswordNode(name.UTF8String, passwordNodeData, parent, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)updatePasswordNodeWithHandle:(MEGAHandle)node newData:(PasswordNodeData *)newData delegate:(id)delegate { if (self.megaApi) { MegaNode::PasswordNodeData::TotpData *totpData = nil; if (newData.totp) { totpData = newData.totp.getCPtr; } MegaNode::PasswordNodeData *passwordNodeData = MegaNode::PasswordNodeData::createInstance(newData.password.UTF8String, newData.notes.UTF8String, newData.url.UTF8String, newData.userName.UTF8String, totpData); self.megaApi->updatePasswordNode(node, passwordNodeData, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (void)createCreditCardNodeWithName:(NSString *)name data:(MEGACreditCardNodeData *)data parent:(MEGAHandle)parent delegate:(id)delegate { if (self.megaApi) { MegaNode::CreditCardNodeData *creditCardNodeData = MegaNode::CreditCardNodeData::createInstance(data.cardNumber.UTF8String, data.notes.UTF8String, data.cardHolderName.UTF8String, data.cvv.UTF8String, data.expirationDate.UTF8String); self.megaApi->createCreditCardNode(name.UTF8String, creditCardNodeData, parent, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)updateCreditCardNodeWithHandle:(MEGAHandle)node newData:(MEGACreditCardNodeData *)newData delegate:(id)delegate { if (self.megaApi) { MegaNode::CreditCardNodeData *creditCardNodeData = MegaNode::CreditCardNodeData::createInstance(newData.cardNumber.UTF8String, newData.notes.UTF8String, newData.cardHolderName.UTF8String, newData.cvv.UTF8String, newData.expirationDate.UTF8String); self.megaApi->updateCreditCardNode(node, creditCardNodeData, [self createDelegateMEGARequestListener:delegate singleListener:YES queueType:ListenerQueueTypeCurrent]); } } - (void)importPasswordsFromFile:(NSString *)filePath fileSource:(ImportPasswordFileSource)fileSource parent:(MEGAHandle)parent delegate:(id)delegate { if (self.megaApi) { self.megaApi->importPasswordsFromFile(filePath.UTF8String, (int)fileSource, parent, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } - (nullable MEGATotpTokenGenResult *)generateTotpTokenFromNode:(MEGAHandle)handle { if (self.megaApi == nil) return nil; MegaTotpTokenGenResult tokenGenResult = self.megaApi->generateTotpTokenFromNode(handle); MegaTotpTokenLifetime tokenLifetime = tokenGenResult.result; NSString *token = [NSString stringWithUTF8String:tokenLifetime.token.c_str()]; MEGATotpTokenLifetime *result = [[MEGATotpTokenLifetime alloc] initWithToken:token remainingLifeTimeSeconds:tokenLifetime.remainingLifeTimeSeconds]; MEGATotpTokenGenResult *tokenGenResultObj = [[MEGATotpTokenGenResult alloc] initWithErrorCode:tokenGenResult.errorCode result:result]; return tokenGenResultObj; } + (nullable NSString *)generateRandomPasswordWithCapitalLetters:(BOOL)includeCapitalLetters digits:(BOOL)includeDigits symbols:(BOOL)includeSymbols length:(int)length { const char *result = MegaApi::generateRandomCharsPassword(includeCapitalLetters, includeDigits, includeSymbols, length); if (!result) return nil; NSString *password = [NSString stringWithUTF8String:result]; delete [] result; return password; } - (void)getRecentActionByBucketId:(NSString *)bucketId delegate:(id)delegate { if (self.megaApi) { self.megaApi->getRecentActionById(bucketId.UTF8String, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } } @end sdk-10.11.0/bindings/ios/MEGASeachPage.mm000066400000000000000000000020321516266226600176400ustar00rootroot00000000000000/** * @file MEGASearchFilter.mm * @brief Class which encapsulates all data used for search nodes filtering * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASearchPage.h" NS_ASSUME_NONNULL_BEGIN @implementation MEGASearchPage - (instancetype)initWithStartingOffset:(size_t)startingOffset pageSize:(size_t)pageSize { self = [super init]; if(self != nil) { _startingOffset = startingOffset; _pageSize = pageSize; } return self; } @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/MEGASearchFilter.mm000066400000000000000000000222711516266226600204020ustar00rootroot00000000000000/** * @file MEGASearchFilter.mm * @brief Class which encapsulates all data used for search nodes filtering * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASearchFilter.h" #import "MEGANode.h" NS_ASSUME_NONNULL_BEGIN @implementation MEGASearchFilter - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery { self = [super init]; if (self != nil) { _term = term; _searchDescription = description; _searchTag = tag; _parentNodeHandle = parentNodeHandle; _nodeType = nodeType; _category = category; _sensitiveFilter = sensitiveFilter; _favouriteFilter = favouriteFilter; _locationType = locationType; _creationTimeFrame = creationTimeFrame; _modificationTimeFrame = modificationTimeFrame; _useAndForTextQuery = useAndForTextQuery; } return self; } - (instancetype)initWithTerm:(NSString *)term parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter creationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)modificationTimeFrame { return [self initWithTerm:term description:nil tag:nil parentNodeHandle:parentNodeHandle nodeType:nodeType category:category sensitiveFilter:sensitiveFilter favouriteFilter:favouriteFilter locationType:-1 creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:NO]; } - (instancetype)initWithTerm:(NSString *)term nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)modificationTimeFrame { return [self initWithTerm:term description:nil tag:nil parentNodeHandle:-1 nodeType:nodeType category:category sensitiveFilter:sensitiveFilter favouriteFilter:favouriteFilter locationType:locationType creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:NO]; } - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery { return [self initWithTerm:term description:description tag:tag parentNodeHandle:parentNodeHandle nodeType:nodeType category:category sensitiveFilter:sensitiveFilter favouriteFilter:favouriteFilter locationType:-1 creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:useAndForTextQuery]; } - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery { return [self initWithTerm:term description:description tag:tag parentNodeHandle:-1 nodeType:nodeType category:category sensitiveFilter:sensitiveFilter favouriteFilter:favouriteFilter locationType:locationType creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:useAndForTextQuery]; } - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(int)nodeType category:(int)category sensitivity:(bool)sensitivity favouriteFilter:(int)favouriteFilter creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery { return [self initWithTerm:term description:description tag:tag parentNodeHandle:parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:sensitivity? MEGASearchFilterSensitiveOptionNonSensitiveOnly : MEGASearchFilterSensitiveOptionDisabled favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:-1 creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:useAndForTextQuery]; } - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag nodeType:(int)nodeType category:(int)category sensitivity:(bool)sensitivity favouriteFilter:(int)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery { return [self initWithTerm:term description:description tag:tag parentNodeHandle:-1 nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:sensitivity? MEGASearchFilterSensitiveOptionNonSensitiveOnly : MEGASearchFilterSensitiveOptionDisabled favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:locationType creationTimeFrame:creationTimeFrame modificationTimeFrame:modificationTimeFrame useAndForTextQuery:useAndForTextQuery]; } - (BOOL)didSetParentNodeHandle { return _parentNodeHandle != -1; } - (BOOL)didSetLocationType { return _locationType != -1; } @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/MEGASearchFilterTimeFrame.mm000066400000000000000000000021161516266226600221700ustar00rootroot00000000000000/** * @file MEGASearchFilterTimeFrame.mm * @brief Search time frame used by MEGASearchFilter * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASearchFilterTimeFrame.h" NS_ASSUME_NONNULL_BEGIN @implementation MEGASearchFilterTimeFrame - (instancetype)initWithLowerLimit:(NSDate *)lowerLimit upperLimit:(NSDate *)upperLimit { self = [super init]; if (self != nil) { _lowerLimit = [lowerLimit timeIntervalSince1970]; _upperLimit = [upperLimit timeIntervalSince1970]; } return self; } @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/MEGASet.mm000066400000000000000000000043601516266226600165610ustar00rootroot00000000000000/** * @file MEGASet.mm * @brief Represents a node (file/folder) in the MEGA account * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASet.h" #import "megaapi.h" using namespace mega; @interface MEGASet() @property MegaSet *set; @property BOOL cMemoryOwn; @end @implementation MEGASet - (instancetype)initWithMegaSet:(MegaSet *)set cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _set = set; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _set; } } - (uint64_t)handle { return self.set ? self.set->id(): ::mega::INVALID_HANDLE; } - (uint64_t)userId { return self.set ? self.set->user() : 0; } - (uint64_t)publicId { return self.set ? self.set->publicId() : 0; } - (nullable NSDate *)timestamp { return self.set ? [[NSDate alloc] initWithTimeIntervalSince1970:self.set->ts()] : nil; } - (nullable NSDate *)timestampCreated { return self.set ? [[NSDate alloc] initWithTimeIntervalSince1970:self.set->cts()] : nil; } - (MEGASetType)type { return (MEGASetType) (self.set ? self.set->type() : MEGASetTypeInvalid); } - (nullable NSString *)name { if (!self.set) return nil; return self.set->name() ? [[NSString alloc] initWithUTF8String:self.set->name()] : nil; } - (uint64_t)cover { return self.set ? self.set->cover(): ::mega::INVALID_HANDLE; } - (BOOL)hasChangedType:(MEGASetChangeType)changeType { return self.set ? self.set->hasChanged(int(changeType)) : NO; } - (MEGASetChangeType)changes { return (MEGASetChangeType) (self.set ? self.set->getChanges() : 0); } - (BOOL)isExported { return self.set ? self.set->isExported() : NO; } @end sdk-10.11.0/bindings/ios/MEGASetElement.mm000066400000000000000000000042311516266226600200700ustar00rootroot00000000000000/** * @file MEGASetElement.mm * @brief Represents a node (file/folder) in the MEGA account * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASetElement.h" #import "megaapi.h" using namespace mega; @interface MEGASetElement() @property MegaSetElement *setElement; @property BOOL cMemoryOwn; @end @implementation MEGASetElement - (instancetype)initWithMegaSetElement:(MegaSetElement *)setElement cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _setElement = setElement; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _setElement; } } - (uint64_t)handle { return self.setElement ? self.setElement->id() : ::mega::INVALID_HANDLE; } - (uint64_t)ownerId { return self.setElement ? self.setElement->setId() : ::mega::INVALID_HANDLE; } - (uint64_t)nodeId { return self.setElement ? self.setElement->node() : ::mega::INVALID_HANDLE; } - (uint64_t)order { return self.setElement ? self.setElement->order() : 0; } - (NSDate *)timestamp { return self.setElement ? [[NSDate alloc] initWithTimeIntervalSince1970:self.setElement->ts()] : nil; } - (NSString *)name { if (!self.setElement) return nil; return self.setElement->name() ? [[NSString alloc] initWithUTF8String:self.setElement->name()] : nil; } - (BOOL)hasChangedType:(MEGASetElementChangeType)changeType { return self.setElement ? self.setElement->hasChanged((int)changeType) : NO; } - (MEGASetElementChangeType)changes { return (MEGASetElementChangeType) (self.setElement ? self.setElement->getChanges() : 0); } @end sdk-10.11.0/bindings/ios/MEGAShare.mm000066400000000000000000000037101516266226600170660ustar00rootroot00000000000000/** * @file MEGAShare.mm * @brief Represents the outbound sharing of a folder with an user in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAShare.h" #import "megaapi.h" using namespace mega; @interface MEGAShare () @property MegaShare *megaShare; @property BOOL cMemoryOwn; @end @implementation MEGAShare - (instancetype)initWithMegaShare:(MegaShare *)megaShare cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaShare = megaShare; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaShare; } } - (MegaShare *)getCPtr { return self.megaShare; } - (NSString *)user { if (!self.megaShare) return nil; return self.megaShare->getUser() ? [[NSString alloc] initWithUTF8String:self.megaShare->getUser()] : nil; } - (uint64_t)nodeHandle { return self.megaShare ? self.megaShare->getNodeHandle() : ::mega::INVALID_HANDLE; } - (MEGAShareType)access { return (MEGAShareType) (self.megaShare ? self.megaShare->getAccess() : MegaShare::ACCESS_UNKNOWN); } - (NSDate *)timestamp { return self.megaShare ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaShare->getTimestamp()] : nil; } - (BOOL)isPending { return self.megaShare ? self.megaShare->isPending() : NO; } - (BOOL)isVerified { return self.megaShare ? self.megaShare->isVerified() : NO; } @end sdk-10.11.0/bindings/ios/MEGAShareList.mm000066400000000000000000000031721516266226600177240ustar00rootroot00000000000000/** * @file MEGAShareList.mm * @brief List of MEGAShare objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAShareList.h" #import "MEGAShare+init.h" using namespace mega; @interface MEGAShareList () @property MegaShareList *shareList; @property BOOL cMemoryOwn; @end @implementation MEGAShareList - (instancetype)initWithShareList:(MegaShareList *)shareList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _shareList = shareList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _shareList; } } - (MegaShareList *)getCPtr { return self.shareList; } - (nullable MEGAShare *)shareAtIndex:(NSInteger)index { if (self.shareList == NULL) { return nil; } MegaShare *share = self.shareList->get((int)index); if (share) { return [[MEGAShare alloc] initWithMegaShare:share->copy() cMemoryOwn:YES]; } else { return nil; } } - (NSInteger)size { return self.shareList ? self.shareList->size() : -1; } @end sdk-10.11.0/bindings/ios/MEGAStringIntegerMap.mm000066400000000000000000000052301516266226600212450ustar00rootroot00000000000000/** * @file MEGAStringIntegerMap.mm * @brief Map of integer values with string keys (map) * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAStringIntegerMap.h" #import "MEGAStringIntegerMap+init.h" #import "MEGAStringList.h" #import "MEGAStringList+init.h" using namespace mega; @interface MEGAStringIntegerMap () @property MegaStringIntegerMap *megaStringIntegerMap; @property BOOL cMemoryOwn; @end @implementation MEGAStringIntegerMap - (instancetype)initWithMegaStringIntegerMap:(mega::MegaStringIntegerMap *)megaStringIntegerMap cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaStringIntegerMap = megaStringIntegerMap; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaStringIntegerMap; } } - (mega::MegaStringIntegerMap *)getCPtr { return self.megaStringIntegerMap; } - (NSInteger)size { return self.megaStringIntegerMap ? (NSInteger)self.megaStringIntegerMap->size() : 0; } - (MEGAStringList *)keys { if (!self.megaStringIntegerMap) { return nil; } MegaStringList *keys = self.megaStringIntegerMap->getKeys(); if (!keys) { return nil; } return [[MEGAStringList alloc] initWithMegaStringList:keys cMemoryOwn:YES]; } - (int64_t)integerValueForKey:(NSString *)key { if (!self.megaStringIntegerMap) { return -1; } const char *cKey = [key UTF8String]; MegaIntegerList *values = self.megaStringIntegerMap->get(cKey); if (!values || values->size() == 0) { return -1; } int64_t value = values->get(0); delete values; return value; } - (NSDictionary *)toDictionary { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; MEGAStringList *keyList = [self keys]; if (keyList) { for (NSUInteger i = 0; i < [keyList size]; i++) { NSString *key = [keyList stringAtIndex:i]; int64_t value = [self integerValueForKey:key]; dictionary[key] = @(value); } } return [dictionary copy]; } @end sdk-10.11.0/bindings/ios/MEGAStringList.mm000066400000000000000000000035641516266226600201350ustar00rootroot00000000000000/** * @file MEGAStringList.mm * @brief List of strings * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAStringList.h" #import "MEGAStringList+init.h" using namespace mega; @interface MEGAStringList () @property MegaStringList *megaStringList; @property BOOL cMemoryOwn; @end @implementation MEGAStringList - (instancetype)initWithMegaStringList:(mega::MegaStringList *)megaStringList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaStringList = megaStringList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaStringList; } } - (mega::MegaStringList *)getCPtr { return self.megaStringList; } - (NSInteger)size { return self.megaStringList ? self.megaStringList->size() : 0; } - (nullable NSString *)stringAtIndex:(NSInteger)index { return self.megaStringList ? [[NSString alloc] initWithUTF8String:self.megaStringList->get((int)index)] : nil; } - (nullable NSArray*)toStringArray { if (!self.megaStringList) { return nil; } int size = self.megaStringList->size(); NSMutableArray *array = [NSMutableArray arrayWithCapacity:size]; for (NSUInteger i = 0; i < size; i++) { [array addObject:[self stringAtIndex:i]]; } return [array copy]; } @end sdk-10.11.0/bindings/ios/MEGATOTPData.mm000066400000000000000000000066171516266226600174150ustar00rootroot00000000000000/** * @file MEGATOTPData.mm * @brief Object Data for TOTP attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATOTPData.h" #import "MEGATOTPData+init.h" #import "MEGATOTPDataValidation.h" #import "MEGATOTPDataValidation+init.h" @interface MEGATOTPData() @property MegaTotpData *megaTOTPData; @property BOOL cMemoryOwn; @end @implementation MEGATOTPData - (instancetype)initWithSharedKey:(NSString *)sharedKey expirationTime:(NSInteger)expirationTime hashAlgorithm:(MEGATOTPHashAlgorithm)hashAlgorithm digits:(NSInteger)digits { self = [super init]; if (self) { _megaTOTPData = MegaTotpData::createInstance(sharedKey.UTF8String, (int)expirationTime, (int)hashAlgorithm, (int)digits); _cMemoryOwn = YES; } return self; } - (instancetype)initWithRemovalInstance { self = [super init]; if (self) { _megaTOTPData = MegaTotpData::createRemovalInstance(); _cMemoryOwn = YES; } return self; } - (instancetype)initWithMegaTotpData:(MegaTotpData *)megaTotpData cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaTOTPData = megaTotpData; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaTOTPData; } } - (MegaTotpData *)getCPtr { return self.megaTOTPData; } - (nullable NSString *)sharedKey { const char *sharedSecret = self.megaTOTPData ? self.megaTOTPData->sharedSecret() : nullptr; return sharedSecret ? [[NSString alloc] initWithUTF8String:sharedSecret] : nil; } - (NSInteger)expirationTime { return self.megaTOTPData ? self.megaTOTPData->expirationTime() : 0; } - (MEGATOTPHashAlgorithm)hashAlgorithm { if (self.megaTOTPData == nil) { return MEGATOTPHashUnknown; } return (MEGATOTPHashAlgorithm)self.megaTOTPData->hashAlgorithm(); } - (NSInteger)digits { return self.megaTOTPData ? self.megaTOTPData->nDigits() : 0; } - (nullable MEGATOTPDataValidation *)validation { if (self.megaTOTPData == nil) { return nil; } MegaTotpDataValidation *totpValidation = self.megaTOTPData->getValidation(); return [[MEGATOTPDataValidation alloc] initWithMegaTotpDataValidation:totpValidation cMemoryOwn:NO]; } - (BOOL)markedToRemove { return self.megaTOTPData ? self.megaTOTPData->markedToRemove() : false; } + (NSInteger)totpNoChangeValue { return -1; } + (NSInteger)defaultExpirationTime { return MegaTotpData::DEFAULT_EXPIRATION_TIME_SECS; } + (MEGATOTPHashAlgorithm)defaultHashAlgorithm { return (MEGATOTPHashAlgorithm)MegaTotpData::DEFAULT_HASH_ALGO; } + (NSInteger)defaultDigits { return MegaTotpData::DEFAULT_NDIGITS; } @end sdk-10.11.0/bindings/ios/MEGATOTPDataValidation.mm000066400000000000000000000051561516266226600214250ustar00rootroot00000000000000/** * @file MEGATOTPDataValidation.mm * @brief Object Data for TOTP validation attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATOTPDataValidation.h" #import "MEGATOTPDataValidation+init.h" @interface MEGATOTPDataValidation() @property MegaTotpDataValidation *megaTOTPDataValidation; @property BOOL cMemoryOwn; @end @implementation MEGATOTPDataValidation - (instancetype)initWithMegaTotpDataValidation:(MegaTotpDataValidation *)megaTotpDataValidation cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaTOTPDataValidation = megaTotpDataValidation; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaTOTPDataValidation; } } - (MegaTotpDataValidation *)getCPtr { return self.megaTOTPDataValidation; } - (BOOL)sharedSecretExist { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->sharedSecretExist() : false; } - (BOOL)sharedSecretValid { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->sharedSecretValid() : false; } - (BOOL)algorithmExist { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->algorithmExist() : false; } - (BOOL)algorithmValid { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->algorithmValid() : false; } - (BOOL)expirationTimeExist { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->expirationTimeExist() : false; } - (BOOL)expirationTimeValid { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->expirationTimeValid() : false; } - (BOOL)digitsExist { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->nDigitsExist() : false; } - (BOOL)digitsValid { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->nDigitsValid() : false; } - (BOOL)isValidForCreate { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->isValidForCreate() : false; } - (BOOL)isValidForUpdate { return self.megaTOTPDataValidation ? self.megaTOTPDataValidation->isValidForUpdate() : false; } @end sdk-10.11.0/bindings/ios/MEGATimeZoneDetails.mm000066400000000000000000000037121516266226600210660ustar00rootroot00000000000000/** * @file MEGATimeZoneDetails.mm * @brief Time zone details * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATimeZoneDetails.h" #import "megaapi.h" using namespace mega; @interface MEGATimeZoneDetails () @property MegaTimeZoneDetails *megaTimeZoneDetails; @property BOOL cMemoryOwn; @end @implementation MEGATimeZoneDetails - (NSString *)description { return [NSString stringWithFormat:@"<%@: Time zone: %@>", self.class, [self timeZoneAtIndex:self.defaultTimeZone]]; } - (instancetype)initWithMegaTimeZoneDetails:(MegaTimeZoneDetails *)megaTimeZoneDetails cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaTimeZoneDetails = megaTimeZoneDetails; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaTimeZoneDetails; } } - (NSInteger)numTimeZones { return self.megaTimeZoneDetails ? self.megaTimeZoneDetails->getNumTimeZones() : 0; } - (NSInteger)defaultTimeZone { return self.megaTimeZoneDetails ? self.megaTimeZoneDetails->getDefault() : 0; } - (nullable NSString *)timeZoneAtIndex:(NSInteger)index { return self.megaTimeZoneDetails ? [[NSString alloc] initWithUTF8String:self.megaTimeZoneDetails->getTimeZone((int)index)] : nil; } - (NSInteger)timeOffsetAtIndex:(NSInteger)index { return self.megaTimeZoneDetails ? self.megaTimeZoneDetails->getTimeOffset((int)index) : 0; } @end sdk-10.11.0/bindings/ios/MEGATotpTokenGenResult.mm000066400000000000000000000017261516266226600216110ustar00rootroot00000000000000/** * @file MEGATotpTokenGenResult.mm * @brief Object Data for TOTP token generation result * * (c) 2025- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATotpTokenGenResult.h" @implementation MEGATotpTokenGenResult - (instancetype)initWithErrorCode:(int)errorCode result:(MEGATotpTokenLifetime *)result { self = [super init]; if (self) { _errorCode = errorCode; _result = result; } return self; } @end sdk-10.11.0/bindings/ios/MEGATotpTokenLifetime.mm000066400000000000000000000020011516266226600214220ustar00rootroot00000000000000/** * @file MEGATotpTokenLifetime.mm * @brief Represents a TOTP token and its lifetime * * (c) 2025- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATotpTokenLifetime.h" @implementation MEGATotpTokenLifetime - (instancetype)initWithToken:(NSString *)token remainingLifeTimeSeconds:(NSUInteger)remainingLifeTimeSeconds { self = [super init]; if (self) { _token = token; _remainingLifeTimeSeconds = remainingLifeTimeSeconds; } return self; } @end sdk-10.11.0/bindings/ios/MEGATransfer.mm000066400000000000000000000131241516266226600176100ustar00rootroot00000000000000/** * @file MEGATransfer.mm * @brief Provides information about a transfer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATransfer.h" #import "MEGANode+init.h" #import "MEGAError+init.h" using namespace mega; @interface MEGATransfer () @property MegaTransfer *megaTransfer; @property BOOL cMemoryOwn; @end @implementation MEGATransfer - (instancetype)initWithMegaTransfer:(MegaTransfer *)megaTransfer cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaTransfer = megaTransfer; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaTransfer; } } - (MegaTransfer *)getCPtr { return self.megaTransfer; } - (MEGATransferType)type { return (MEGATransferType) (self.megaTransfer ? self.megaTransfer->getType() : 0); } - (nullable NSString *)transferString { if (!self.megaTransfer) return nil; return self.megaTransfer->getTransferString() ? [[NSString alloc] initWithUTF8String:self.megaTransfer->getTransferString()] : nil; } - (nullable NSDate *)startTime { return self.megaTransfer ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaTransfer->getStartTime()] : nil; } - (long long)transferredBytes { return self.megaTransfer ? self.megaTransfer->getTransferredBytes() : 0; } - (long long)totalBytes { return self.megaTransfer ? self.megaTransfer->getTotalBytes() : 0; } - (nullable NSString *)path { if (!self.megaTransfer) return nil; return self.megaTransfer->getPath() ? [[NSString alloc] initWithUTF8String:self.megaTransfer->getPath()] : nil; } - (nullable NSString *)parentPath { if (!self.megaTransfer) return nil; return self.megaTransfer->getParentPath() ? [[NSString alloc] initWithUTF8String:self.megaTransfer->getParentPath()] : nil; } - (uint64_t)nodeHandle { return self.megaTransfer ? self.megaTransfer->getNodeHandle() : ::mega::INVALID_HANDLE; } - (uint64_t)parentHandle { return self.megaTransfer ? self.megaTransfer->getParentHandle() : ::mega::INVALID_HANDLE; } - (long long)startPos { return self.megaTransfer ? self.megaTransfer->getStartPos() : 0; } - (long long)endPos { return self.megaTransfer ? self.megaTransfer->getEndPos() : 0; } - (nullable NSString *)fileName { if (!self.megaTransfer) return nil; return self.megaTransfer->getFileName() ? [[NSString alloc] initWithUTF8String:self.megaTransfer->getFileName()] : nil; } - (NSInteger) numRetry { return self.megaTransfer ? self.megaTransfer->getNumRetry() : 0; } - (NSInteger) maxRetries { return self.megaTransfer ? self.megaTransfer->getMaxRetries() : 0; } - (NSInteger)tag { return self.megaTransfer ? self.megaTransfer->getTag() : 0; } - (long long)speed { return self.megaTransfer ? self.megaTransfer->getSpeed() : 0; } - (long long)deltaSize { return self.megaTransfer ? self.megaTransfer->getDeltaSize() : 0; } - (nullable NSDate *)updateTime { return self.megaTransfer ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaTransfer->getUpdateTime()] : nil; } - (nullable MEGANode *)publicNode { if (self.megaTransfer) { MegaNode *n = self.megaTransfer->getPublicMegaNode(); if (n) { MEGANode *node = [[MEGANode alloc] initWithMegaNode:n cMemoryOwn:YES]; return node; } } return nil; } - (BOOL)isStreamingTransfer { return self.megaTransfer ? (BOOL) self.megaTransfer->isStreamingTransfer() : NO; } - (BOOL)isFinished { return self.megaTransfer ? self.megaTransfer->isFinished() : NO; } - (BOOL)isForeignOverquota { return self.megaTransfer ? self.megaTransfer->isForeignOverquota() : NO; } - (nullable MEGAError *)lastErrorExtended { mega::MegaError *e = (mega::MegaError *)self.megaTransfer->getLastErrorExtended(); return e ? [[MEGAError alloc] initWithMegaError:e cMemoryOwn:NO] : nil; } - (BOOL)isFolderTransfer { return self.megaTransfer ? (BOOL) self.megaTransfer->isFolderTransfer() : NO; } - (NSInteger)folderTransferTag { return self.megaTransfer ? self.megaTransfer->getFolderTransferTag() : 0; } - (nullable NSString *)appData { if (!self.megaTransfer) return nil; return self.megaTransfer->getAppData() ? [[NSString alloc] initWithUTF8String:self.megaTransfer->getAppData()] : nil; } - (MEGATransferState)state { return (MEGATransferState) (self.megaTransfer ? self.megaTransfer->getState() : 0); } - (MEGATransferStage)stage { return (MEGATransferStage) (self.megaTransfer ? self.megaTransfer->getStage() : 0); } - (unsigned long long)priority { return self.megaTransfer ? self.megaTransfer->getPriority() : 0; } + (nullable NSString *)stringForTransferStage:(MEGATransferStage)stage { const char *stageString = MegaTransfer::stageToString((unsigned) stage); return stageString ? [NSString stringWithUTF8String:stageString] : nil; } - (long long)notificationNumber { return self.megaTransfer ? self.megaTransfer->getNotificationNumber() : 0; } - (BOOL)targetOverride { return self.megaTransfer ? self.megaTransfer->getTargetOverride() : NO; } @end sdk-10.11.0/bindings/ios/MEGATransferList.mm000066400000000000000000000030701516266226600204430ustar00rootroot00000000000000/** * @file MEGATransferList.mm * @brief List of MEGATransfer objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATransferList.h" #import "MEGATransfer+init.h" using namespace mega; @interface MEGATransferList () @property MegaTransferList *transferList; @property BOOL cMemoryOwn; @end @implementation MEGATransferList - (instancetype)initWithTransferList:(MegaTransferList *)transferList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _transferList = transferList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _transferList; } } - (MegaTransferList *)getCPtr { return self.transferList; } - (nullable MEGATransfer *)transferAtIndex:(NSInteger)index { return self.transferList ? [[MEGATransfer alloc] initWithMegaTransfer:self.transferList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSInteger)size { return self.transferList ? self.transferList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGAUploadOptions.mm000066400000000000000000000066411516266226600206320ustar00rootroot00000000000000/** * @file MEGAUploadOptions.mm * @brief Optional parameters to customize an upload. * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUploadOptions.h" #import "MEGASdk.h" #import "megaapi.h" using namespace mega; @implementation MEGAUploadOptions - (instancetype)init { return [self initWithFileName:nil mtime:MegaApi::INVALID_CUSTOM_MOD_TIME appData:nil isSourceTemporary:NO startFirst:NO pitagTrigger:MEGAPitagTriggerNotApplicable isChatUpload:NO pitagTarget:MEGAPitagTargetNotApplicable]; } - (instancetype)initWithFileName:(nullable NSString *)fileName { return [self initWithFileName:fileName mtime:MegaApi::INVALID_CUSTOM_MOD_TIME appData:nil isSourceTemporary:NO startFirst:NO pitagTrigger:MEGAPitagTriggerNotApplicable isChatUpload:NO pitagTarget:MEGAPitagTargetNotApplicable]; } - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime { return [self initWithFileName:fileName mtime:mtime appData:nil isSourceTemporary:NO startFirst:NO pitagTrigger:MEGAPitagTriggerNotApplicable isChatUpload:NO pitagTarget:MEGAPitagTargetNotApplicable]; } - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime appData:(nullable NSString *)appData { return [self initWithFileName:fileName mtime:mtime appData:appData isSourceTemporary:NO startFirst:NO pitagTrigger:MEGAPitagTriggerNotApplicable isChatUpload:NO pitagTarget:MEGAPitagTargetNotApplicable]; } - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst pitagTrigger:(MEGAPitagTrigger)pitagTrigger isChatUpload:(BOOL)isChatUpload pitagTarget:(MEGAPitagTarget)pitagTarget { self = [super init]; if (self) { _fileName = [fileName copy]; _mtime = mtime; _appData = [appData copy]; _isSourceTemporary = isSourceTemporary; _startFirst = startFirst; _pitagTrigger = pitagTrigger; _isChatUpload = isChatUpload; _pitagTarget = pitagTarget; } return self; } @end sdk-10.11.0/bindings/ios/MEGAUser.mm000066400000000000000000000041371516266226600167460ustar00rootroot00000000000000/** * @file MEGAUser.mm * @brief Represents an user in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUser.h" #import "megaapi.h" using namespace mega; @interface MEGAUser () @property MegaUser *megaUser; @property BOOL cMemoryOwn; @end @implementation MEGAUser - (instancetype)initWithMegaUser:(MegaUser *)megaUser cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _megaUser = megaUser; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaUser; } } - (MegaUser *)getCPtr { return self.megaUser; } - (nullable NSString *)email { if (!self.megaUser) return nil; return self.megaUser->getEmail() ? [[NSString alloc] initWithUTF8String:self.megaUser->getEmail()] : nil; } - (uint64_t)handle { return self.megaUser ? self.megaUser->getHandle() : mega::INVALID_HANDLE; } - (MEGAUserVisibility)visibility { return (MEGAUserVisibility) (self.megaUser ? self.megaUser->getVisibility() : ::mega::MegaUser::VISIBILITY_UNKNOWN); } - (MEGAUserChangeType)changes { return (MEGAUserChangeType) (self.megaUser ? self.megaUser->getChanges() : 0); } - (NSInteger)isOwnChange { return self.megaUser ? self.megaUser->isOwnChange() : 0; } - (nullable NSDate *)timestamp { return self.megaUser ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaUser->getTimestamp()] : nil; } - (BOOL)hasChangedType:(MEGAUserChangeType)changeType { return self.megaUser ? self.megaUser->hasChanged((int)changeType) : NO; } @end sdk-10.11.0/bindings/ios/MEGAUserAlert.mm000066400000000000000000000134751516266226600177430ustar00rootroot00000000000000/** * @file MEGAUserAlert.mm * @brief Represents a user alert in MEGA. * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUserAlert.h" #import "megaapi.h" #import "MEGAStringList+init.h" using namespace mega; @interface MEGAUserAlert () @property MegaUserAlert *megaUserAlert; @property BOOL cMemoryOwn; @end @implementation MEGAUserAlert - (instancetype)initWithMegaUserAlert:(MegaUserAlert *)megaUserAlert cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaUserAlert = megaUserAlert; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaUserAlert; } } - (MegaUserAlert *)getCPtr { return self.megaUserAlert; } - (NSUInteger)identifier { return self.megaUserAlert ? self.megaUserAlert->getId() : 0; } - (BOOL)isSeen { return self.megaUserAlert ? self.megaUserAlert->getSeen(): NO; } - (BOOL)isRelevant { return self.megaUserAlert ? self.megaUserAlert->getRelevant() : NO; } - (MEGAUserAlertType)type { return (MEGAUserAlertType)(self.megaUserAlert ? self.megaUserAlert->getType() : 0); } - (nullable NSString *)typeString { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getTypeString() ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getTypeString()] : nil; } - (uint64_t)userHandle { return self.megaUserAlert ? self.megaUserAlert->getUserHandle() : ::mega::INVALID_HANDLE; } - (uint64_t)nodeHandle { return self.megaUserAlert ? self.megaUserAlert->getNodeHandle() : ::mega::INVALID_HANDLE; } - (uint64_t)pendingContactRequestHandle { return self.megaUserAlert ? self.megaUserAlert->getPcrHandle() : ::mega::INVALID_HANDLE; } - (nullable NSString *)email { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getEmail() ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getEmail()] : nil; } - (nullable NSString *)path { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getPath()? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getPath()] : nil; } - (nullable NSString *)name { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getName() ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getName()] : nil; } - (nullable NSString *)heading { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getHeading() ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getHeading()] : nil; } - (nullable NSString *)title { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getTitle() ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getTitle()] : nil; } - (BOOL)isOwnChange { return self.megaUserAlert ? self.megaUserAlert->isOwnChange() : NO; } #ifdef ENABLE_CHAT - (uint64_t)scheduledMeetingId { return self.megaUserAlert ? self.megaUserAlert->getSchedId() : ::mega::INVALID_HANDLE; } #endif - (int64_t)numberAtIndex:(NSUInteger)index { return self.megaUserAlert ? self.megaUserAlert->getNumber((unsigned int) index) : -1; } - (int64_t)timestampAtIndex:(NSUInteger)index { return self.megaUserAlert ? self.megaUserAlert->getTimestamp((unsigned int) index) : -1; } - (nullable NSString *)stringAtIndex:(NSUInteger)index { if (!self.megaUserAlert) return nil; return self.megaUserAlert->getString((unsigned int)index) ? [[NSString alloc] initWithUTF8String:self.megaUserAlert->getString((unsigned int)index)] : nil; } #ifdef ENABLE_CHAT - (BOOL)hasScheduledMeetingChangeType:(MEGAUserAlertScheduledMeetingChangeType)changeType { if (!self.megaUserAlert) return NO; return self.megaUserAlert->hasSchedMeetingChanged(int(changeType)); } - (nullable MEGAStringList *)titleList { return self.megaUserAlert ? [MEGAStringList.alloc initWithMegaStringList:self.megaUserAlert->getUpdatedTitle() cMemoryOwn:YES] : nil; } - (nullable NSArray *)startDateList { if (!self.megaUserAlert || !self.megaUserAlert->getUpdatedStartDate()) { return nil; } MegaIntegerList *integerList = self.megaUserAlert->getUpdatedStartDate()->copy(); NSMutableArray *dateArray = [NSMutableArray arrayWithCapacity:integerList->size()]; for (int i = 0; i < integerList->size(); i++) { NSInteger timeInterval = integerList->get(i); if (timeInterval != -1) { NSDate *date = [NSDate dateWithTimeIntervalSince1970:integerList->get(i)]; if (date != nil) { [dateArray addObject:date]; } } } delete integerList; return dateArray; } - (nullable NSArray *)endDateList { if (!self.megaUserAlert || !self.megaUserAlert->getUpdatedEndDate()) { return nil; } MegaIntegerList *integerList = self.megaUserAlert->getUpdatedEndDate()->copy(); NSMutableArray *dateArray = [NSMutableArray arrayWithCapacity:integerList->size()]; for (int i = 0; i < integerList->size(); i++) { NSInteger timeInterval = integerList->get(i); if (timeInterval != -1) { NSDate *date = [NSDate dateWithTimeIntervalSince1970:integerList->get(i)]; if (date != nil) { [dateArray addObject:date]; } } } delete integerList; return dateArray; } #endif @end sdk-10.11.0/bindings/ios/MEGAUserAlertList.mm000066400000000000000000000031521516266226600205660ustar00rootroot00000000000000/** * @file MEGAUserAlertList.mm * @brief List of MEGAUserAlert objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "megaapi.h" #import "MEGAUserAlertList.h" #import "MEGAUserAlertList+init.h" #import "MEGAUserAlert+init.h" using namespace mega; @interface MEGAUserAlertList () @property MegaUserAlertList *megaUserAlertList; @property BOOL cMemoryOwn; @end @implementation MEGAUserAlertList - (instancetype)initWithMegaUserAlertList:(MegaUserAlertList *)megaUserAlertList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaUserAlertList = megaUserAlertList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaUserAlertList; } } - (nullable MEGAUserAlert *)usertAlertAtIndex:(NSInteger)index { return self.megaUserAlertList ? [[MEGAUserAlert alloc] initWithMegaUserAlert:self.megaUserAlertList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSInteger)size { return self.megaUserAlertList ? self.megaUserAlertList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGAUserList.mm000066400000000000000000000027301516266226600175770ustar00rootroot00000000000000/** * @file MEGAUserList.mm * @brief List of MEGAUser objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUserList.h" #import "MEGAUser+init.h" using namespace mega; @interface MEGAUserList () @property MegaUserList *userList; @property BOOL cMemoryOwn; @end @implementation MEGAUserList - (instancetype)initWithUserList:(MegaUserList *)userList cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self != nil) { _userList = userList; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _userList; } } - (MegaUserList *)getCPtr { return self.userList; } - (nullable MEGAUser *)userAtIndex:(NSInteger)index { return self.userList ? [[MEGAUser alloc] initWithMegaUser:self.userList->get((int)index)->copy() cMemoryOwn:YES] : nil; } - (NSInteger)size { return self.userList ? self.userList->size() : 0; } @end sdk-10.11.0/bindings/ios/MEGAVPNCluster.mm000066400000000000000000000044771516266226600200440ustar00rootroot00000000000000/** * @file MEGAVPNCluster.mm * @brief Container to store information of a VPN Cluster. * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAVPNCluster.h" #import "MEGAStringList+init.h" @interface MEGAVPNCluster () @property (nonatomic) mega::MegaVpnCluster *megaVpnCluster; @property (nonatomic) BOOL cMemoryOwn; @end @implementation MEGAVPNCluster - (instancetype)initWithMegaVpnCluster:(mega::MegaVpnCluster *)megaVpnCluster cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaVpnCluster = megaVpnCluster; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (_cMemoryOwn) { delete _megaVpnCluster; } } - (mega::MegaVpnCluster *)getCPtr { return self.megaVpnCluster; } - (nonnull NSString *)host { const char *host = self.megaVpnCluster->getHost(); return host ? [NSString stringWithUTF8String:host] : @""; } - (nonnull NSArray *)dns { if (!self.megaVpnCluster) { return @[]; } return [self _dnsListFromMegaStringList:self.megaVpnCluster->getDns()]; } - (nonnull NSArray *)adBlockingDns { if (!self.megaVpnCluster) { return @[]; } return [self _dnsListFromMegaStringList:self.megaVpnCluster->getAdBlockingDns()]; } - (nonnull NSArray *)_dnsListFromMegaStringList:(mega::MegaStringList *)dnsList { if (!dnsList) { return @[]; } int count = dnsList->size(); NSMutableArray *dnsArray = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)count]; for (int i = 0; i < count; i++) { const char *dnsEntry = dnsList->get(i); if (dnsEntry) { [dnsArray addObject:[NSString stringWithUTF8String:dnsEntry]]; } } delete dnsList; return [dnsArray copy]; } @end sdk-10.11.0/bindings/ios/MEGAVPNCredentials.mm000066400000000000000000000066401516266226600206520ustar00rootroot00000000000000/** * @file MEGAVPNCredentials.mm * @brief Container to store and load Mega VPN credentials data * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAVPNCredentials.h" #import "MEGAIntegerList+init.h" #import "MEGAStringList+init.h" #import "MEGAVPNRegion+init.h" @interface MEGAVPNCredentials () @property mega::MegaVpnCredentials *megaVpnCredentials; @property BOOL cMemoryOwn; @end @implementation MEGAVPNCredentials - (instancetype)initWithMegaVpnCredentials:(mega::MegaVpnCredentials *)megaVpnCredentials cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaVpnCredentials = megaVpnCredentials; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (self.cMemoryOwn) { delete _megaVpnCredentials; } } - (mega::MegaVpnCredentials *)getCPtr { return self.megaVpnCredentials; } - (nonnull MEGAIntegerList *)slotIDs { return [[MEGAIntegerList alloc] initWithMegaIntegerList:self.megaVpnCredentials->getSlotIDs() cMemoryOwn:YES]; } - (nonnull MEGAStringList *)vpnRegions { return [[MEGAStringList alloc] initWithMegaStringList:self.megaVpnCredentials->getVpnRegions() cMemoryOwn:YES]; } - (nonnull NSArray *)vpnRegionsDetailed { if (!self.megaVpnCredentials) { return @[]; } mega::MegaVpnRegionList *regionList = self.megaVpnCredentials->getVpnRegionsDetailed(); if (!regionList) { return @[]; } int count = regionList->size(); NSMutableArray *regionsArray = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)count]; for (int i = 0; i < count; i++) { const mega::MegaVpnRegion *region = regionList->get(i); if (region) { MEGAVPNRegion *vpnRegion = [[MEGAVPNRegion alloc] initWithMegaVpnRegion:region->copy() cMemoryOwn:YES]; [regionsArray addObject:vpnRegion]; } } delete regionList; return [regionsArray copy]; } - (nullable NSString *)ipv4ForSlotID:(NSInteger)slotID { const char *ipv4 = self.megaVpnCredentials->getIPv4((int)slotID); return ipv4 ? [[NSString alloc] initWithUTF8String:ipv4] : nil; } - (nullable NSString *)ipv6ForSlotID:(NSInteger)slotID { const char *ipv6 = self.megaVpnCredentials->getIPv6((int)slotID); return ipv6 ? [[NSString alloc] initWithUTF8String:ipv6] : nil; } - (nullable NSString *)deviceIDForSlotID:(NSInteger)slotID { const char *deviceID = self.megaVpnCredentials->getDeviceID((int)slotID); return deviceID ? [[NSString alloc] initWithUTF8String:deviceID] : nil; } - (NSInteger)clusterIDForSlotID:(NSInteger)slotID { return self.megaVpnCredentials->getClusterID((int)slotID); } - (nullable NSString *)clusterPublicKeyForClusterID:(NSInteger)clusterID { const char *publicKey = self.megaVpnCredentials->getClusterPublicKey((int)clusterID); return publicKey ? [[NSString alloc] initWithUTF8String:publicKey] : nil; } @end sdk-10.11.0/bindings/ios/MEGAVPNRegion.mm000066400000000000000000000060561516266226600176410ustar00rootroot00000000000000/** * @file MEGAVPNRegion.mm * @brief Container to store information of a VPN Region. * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAVPNRegion.h" #import "MEGAVPNCluster.h" #import "MEGAVPNCluster+init.h" #import "MEGAIntegerList+init.h" @interface MEGAVPNRegion () @property (nonatomic) mega::MegaVpnRegion *megaVpnRegion; @property (nonatomic) BOOL cMemoryOwn; @end @implementation MEGAVPNRegion - (instancetype)initWithMegaVpnRegion:(mega::MegaVpnRegion *)megaVpnRegion cMemoryOwn:(BOOL)cMemoryOwn { self = [super init]; if (self) { _megaVpnRegion = megaVpnRegion; _cMemoryOwn = cMemoryOwn; } return self; } - (void)dealloc { if (_cMemoryOwn) { delete _megaVpnRegion; } } - (mega::MegaVpnRegion *)getCPtr { return self.megaVpnRegion; } - (nonnull NSString *)name { const char *name = self.megaVpnRegion->getName(); return name ? [NSString stringWithUTF8String:name] : @""; } - (nonnull NSString *)countryCode { const char *countryCode = self.megaVpnRegion->getCountryCode(); return countryCode ? [NSString stringWithUTF8String:countryCode] : @""; } - (nonnull NSString *)countryName { const char *countryName = self.megaVpnRegion->getCountryName(); return countryName ? [NSString stringWithUTF8String:countryName] : @""; } - (nonnull NSString *)regionName { const char *regionName = self.megaVpnRegion->getRegionName(); return regionName ? [NSString stringWithUTF8String:regionName] : @""; } - (nonnull NSString *)townName { const char *townName = self.megaVpnRegion->getTownName(); return townName ? [NSString stringWithUTF8String:townName] : @""; } - (nonnull NSDictionary *)clusters { if (!self.megaVpnRegion) { return @{}; } mega::MegaVpnClusterMap *clusterMap = self.megaVpnRegion->getClusters(); if (!clusterMap) { return @{}; } mega::MegaIntegerList *keys = clusterMap->getKeys(); int count = (int)keys->size(); NSMutableDictionary *clustersDict = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)count]; for (int i = 0; i < count; i++) { int64_t key = keys->get(i); mega::MegaVpnCluster *cluster = clusterMap->get(key); if (cluster) { MEGAVPNCluster *vpnCluster = [[MEGAVPNCluster alloc] initWithMegaVpnCluster:cluster->copy() cMemoryOwn:YES]; clustersDict[@(key)] = vpnCluster; } } delete keys; delete clusterMap; return [clustersDict copy]; } @end sdk-10.11.0/bindings/ios/PasswordNodeData.mm000066400000000000000000000027551516266226600206040ustar00rootroot00000000000000/** * @file PasswordNodeData.mm * @brief Object Data for Password Node attributes * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "PasswordNodeData.h" #import "MEGATOTPData.h" @interface PasswordNodeData () @property (readwrite, nonatomic) NSString *password; @property (readwrite, nonatomic, nullable) NSString *notes; @property (readwrite, nonatomic, nullable) NSString *url; @property (readwrite, nonatomic, nullable) NSString *userName; @property (readwrite, nonatomic, nullable) MEGATOTPData *totp; @end @implementation PasswordNodeData - (instancetype)initWithPassword:(NSString *)password notes:(nullable NSString *)notes url:(nullable NSString *)url userName:(nullable NSString *)userName totp:(nullable MEGATOTPData *)totp { self = [super init]; if (self) { _password = [password copy]; _notes = [notes copy]; _url = [url copy]; _userName = [userName copy]; _totp = totp; } return self; } @endsdk-10.11.0/bindings/ios/Private/000077500000000000000000000000001516266226600164505ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/Private/DelegateMEGAGlobalListener.h000066400000000000000000000034021516266226600236130ustar00rootroot00000000000000/** * @file DelegateMEGAGlobalListener.h * @brief Listener to reveice and send global events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "megaapi.h" #import "MEGASdk.h" #import "ListenerDispatch.h" class DelegateMEGAGlobalListener : public mega::MegaGlobalListener { public: DelegateMEGAGlobalListener(MEGASdk *megaSDK, id listener, ListenerQueueType queueType); id getUserListener(); void onUsersUpdate(mega::MegaApi* api, mega::MegaUserList* userList); void onUserAlertsUpdate(mega::MegaApi* api, mega::MegaUserAlertList *userAlertList); void onNodesUpdate(mega::MegaApi* api, mega::MegaNodeList* nodeList); void onSetsUpdate(mega::MegaApi* api, mega::MegaSetList* setList); void onSetElementsUpdate(mega::MegaApi* api, mega::MegaSetElementList* setElementList); void onAccountUpdate(mega::MegaApi *api); void onContactRequestsUpdate(mega::MegaApi* api, mega::MegaContactRequestList* contactRequestList); void onReloadNeeded(mega::MegaApi* api); void onEvent(mega::MegaApi* api, mega::MegaEvent *event); private: MEGASdk *megaSDK; __weak id listener; ListenerQueueType queueType; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGAGlobalListener.mm000066400000000000000000000164211516266226600240020ustar00rootroot00000000000000/** * @file DelegateMEGAGlobalListener.mm * @brief Listener to reveice and send global events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGAGlobalListener.h" #import "MEGAUserList+init.h" #import "MEGAUserAlertList+init.h" #import "MEGANodeList+init.h" #import "MEGAContactRequestList+init.h" #import "MEGAEvent+init.h" #import "MEGASet+init.h" #import "MEGASetElement+init.h" using namespace mega; DelegateMEGAGlobalListener::DelegateMEGAGlobalListener(MEGASdk *megaSDK, idlistener, ListenerQueueType queueType) { this->megaSDK = megaSDK; this->listener = listener; this->queueType = queueType; } id DelegateMEGAGlobalListener::getUserListener() { return listener; } void DelegateMEGAGlobalListener::onUsersUpdate(mega::MegaApi *api, mega::MegaUserList *userList) { if (listener !=nil && [listener respondsToSelector:@selector(onUsersUpdate:userList:)]) { MegaUserList *tempUserList = NULL; if (userList) { tempUserList = userList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onUsersUpdate:tempMegaSDK userList:(tempUserList ? [[MEGAUserList alloc] initWithUserList:tempUserList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAGlobalListener::onUserAlertsUpdate(mega::MegaApi *api, mega::MegaUserAlertList *userAlertList) { if (listener && [listener respondsToSelector:@selector(onUserAlertsUpdate:userAlertList:)]) { MegaUserAlertList *tempUserAlertList = NULL; if (userAlertList) { tempUserAlertList = userAlertList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onUserAlertsUpdate:tempMegaSDK userAlertList:(tempUserAlertList ? [[MEGAUserAlertList alloc] initWithMegaUserAlertList:tempUserAlertList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAGlobalListener::onNodesUpdate(mega::MegaApi *api, mega::MegaNodeList *nodeList) { if (listener !=nil && [listener respondsToSelector:@selector(onNodesUpdate:nodeList:)]) { MegaNodeList *tempNodesList = NULL; if (nodeList) { tempNodesList = nodeList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onNodesUpdate:tempMegaSDK nodeList:(tempNodesList ? [[MEGANodeList alloc] initWithNodeList:tempNodesList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAGlobalListener::onSetsUpdate(mega::MegaApi *api, mega::MegaSetList *setList) { if (listener !=nil && [listener respondsToSelector:@selector(onSetsUpdate:sets:)]) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; int size = 0; if (setList) { size = setList->size(); } else { dispatch(this->queueType, ^{ [tempListener onSetsUpdate:tempMegaSDK sets:nil]; }); return; } NSMutableArray *sets = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASet *megaSet = [[MEGASet alloc] initWithMegaSet:setList->get(i)->copy() cMemoryOwn:YES]; [sets addObject:megaSet]; } dispatch(this->queueType, ^{ [tempListener onSetsUpdate:tempMegaSDK sets:[sets copy]]; }); } } void DelegateMEGAGlobalListener::onSetElementsUpdate(mega::MegaApi* api, mega::MegaSetElementList* setElementList) { if (listener !=nil && [listener respondsToSelector:@selector(onSetElementsUpdate:setElements:)]) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; int size = 0; if (setElementList) { size = setElementList->size(); } else { dispatch(this->queueType, ^{ [tempListener onSetElementsUpdate:tempMegaSDK setElements:nil]; }); return; } NSMutableArray *setsElements = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASetElement *megaSetElement = [[MEGASetElement alloc] initWithMegaSetElement:setElementList->get(i)->copy() cMemoryOwn:YES]; [setsElements addObject:megaSetElement]; } dispatch(this->queueType, ^{ [tempListener onSetElementsUpdate:tempMegaSDK setElements:[setsElements copy]]; }); } } void DelegateMEGAGlobalListener::onAccountUpdate(mega::MegaApi *api) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; if (listener !=nil && [listener respondsToSelector:@selector(onAccountUpdate:)]) { dispatch(this->queueType, ^{ [tempListener onAccountUpdate:tempMegaSDK]; }); } } void DelegateMEGAGlobalListener::onContactRequestsUpdate(mega::MegaApi* api, mega::MegaContactRequestList* contactRequestList) { if (listener != nil && [listener respondsToSelector:@selector(onContactRequestsUpdate:contactRequestList:)]) { MegaContactRequestList *tempContactRequestList = NULL; if(contactRequestList) { tempContactRequestList = contactRequestList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onContactRequestsUpdate:tempMegaSDK contactRequestList:(tempContactRequestList ? [[MEGAContactRequestList alloc] initWithMegaContactRequestList:tempContactRequestList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAGlobalListener::onReloadNeeded(mega::MegaApi* api) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; if (listener !=nil && [listener respondsToSelector:@selector(onReloadNeeded:)]) { dispatch(this->queueType, ^{ [tempListener onReloadNeeded:tempMegaSDK]; }); } } void DelegateMEGAGlobalListener::onEvent(mega::MegaApi *api, mega::MegaEvent *event) { if (listener != nil && [listener respondsToSelector:@selector(onEvent:event:)]) { MegaEvent *tempEvent = event->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onEvent:tempMegaSDK event:(tempEvent ? [[MEGAEvent alloc] initWithMegaEvent:tempEvent cMemoryOwn:YES] : nil)]; }); } } sdk-10.11.0/bindings/ios/Private/DelegateMEGAListener.h000066400000000000000000000045761516266226600225070ustar00rootroot00000000000000/** * @file DelegateMEGAListener.h * @brief Listener to reveice and send events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGADelegate.h" #import "MEGATransfer.h" #import "MEGARequest.h" #import "MEGAError.h" #import "megaapi.h" #import "MEGASdk.h" class DelegateMEGAListener : public mega::MegaListener { public: DelegateMEGAListener(MEGASdk *megaSDK, idlistener); idgetUserListener(); void onRequestStart(mega::MegaApi *api, mega::MegaRequest *request); void onRequestFinish(mega::MegaApi *api, mega::MegaRequest *request, mega::MegaError *e); void onRequestUpdate(mega::MegaApi *api, mega::MegaRequest *request); void onRequestTemporaryError(mega::MegaApi *api, mega::MegaRequest *request, mega::MegaError *e); void onTransferStart(mega::MegaApi *api, mega::MegaTransfer *transfer); void onTransferFinish(mega::MegaApi *api, mega::MegaTransfer *transfer, mega::MegaError *e); void onTransferUpdate(mega::MegaApi *api, mega::MegaTransfer *transfer); void onTransferTemporaryError(mega::MegaApi *api, mega::MegaTransfer *transfer, mega::MegaError *e); void onUsersUpdate(mega::MegaApi* api, mega::MegaUserList* userList); void onUserAlertsUpdate(mega::MegaApi* api, mega::MegaUserAlertList *userAlertList); void onNodesUpdate(mega::MegaApi* api, mega::MegaNodeList* nodeList); void onSetsUpdate(mega::MegaApi* api, mega::MegaSetList* nodeList); void onSetElementsUpdate(mega::MegaApi* api, mega::MegaSetElementList* nodeList); void onAccountUpdate(mega::MegaApi *api); void onContactRequestsUpdate(mega::MegaApi* api, mega::MegaContactRequestList* contactRequestList); void onReloadNeeded(mega::MegaApi *api); void onEvent(mega::MegaApi* api, mega::MegaEvent *event); private: MEGASdk *megaSDK; id listener; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGAListener.mm000066400000000000000000000300201516266226600226500ustar00rootroot00000000000000/** * @file DelegateMEGAListener.mm * @brief Listener to reveice and send events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGAListener.h" #import "MEGATransfer+init.h" #import "MEGAError+init.h" #import "MEGARequest+init.h" #import "MEGANodeList+init.h" #import "MEGAUserList+init.h" #import "MEGAUserAlertList+init.h" #import "MEGAContactRequestList+init.h" #import "MEGAEvent+init.h" #import "MEGASet+init.h" #import "MEGASetElement+init.h" using namespace mega; DelegateMEGAListener::DelegateMEGAListener(MEGASdk *megaSDK, idlistener) { this->megaSDK = megaSDK; this->listener = listener; } idDelegateMEGAListener::getUserListener() { return listener; } void DelegateMEGAListener::onRequestStart(MegaApi *api, MegaRequest *request) { if (listener != nil && [listener respondsToSelector:@selector(onRequestStart:request:)]) { MegaRequest *tempRequest = request->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onRequestStart:tempMegaSDK request:[[MEGARequest alloc]initWithMegaRequest:tempRequest cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onRequestFinish:request:error:)]) { MegaRequest *tempRequest = request->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onRequestFinish:tempMegaSDK request:[[MEGARequest alloc]initWithMegaRequest:tempRequest cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onRequestUpdate(MegaApi *api, MegaRequest *request) { if (listener != nil && [listener respondsToSelector:@selector(onRequestUpdate:request:)]) { MegaRequest *tempRequest = request->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onRequestUpdate:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onRequestTemporaryError:request:error:)]) { MegaRequest *tempRequest = request->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onRequestTemporaryError:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onTransferStart(MegaApi *api, MegaTransfer *transfer) { if (listener != nil && [listener respondsToSelector:@selector(onTransferStart:transfer:)]) { MegaTransfer *tempTransfer = transfer->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onTransferStart:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onTransferFinish:transfer:error:)]) { MegaTransfer *tempTransfer = transfer->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onTransferFinish:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) { if (listener != nil && [listener respondsToSelector:@selector(onTransferUpdate:transfer:)]) { MegaTransfer *tempTransfer = transfer->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onTransferUpdate:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onTransferTemporaryError:transfer:error:)]) { MegaTransfer *tempTransfer = transfer->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onTransferTemporaryError:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } void DelegateMEGAListener::onUsersUpdate(mega::MegaApi *api, mega::MegaUserList *userList) { if (listener !=nil && [listener respondsToSelector:@selector(onUsersUpdate:userList:)]) { MegaUserList *tempUserList = NULL; if (userList) { tempUserList = userList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onUsersUpdate:tempMegaSDK userList:(tempUserList ? [[MEGAUserList alloc] initWithUserList:tempUserList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAListener::onUserAlertsUpdate(mega::MegaApi *api, mega::MegaUserAlertList *userAlertList) { if (listener && [listener respondsToSelector:@selector(onUserAlertsUpdate:userAlertList:)]) { MegaUserAlertList *tempUserAlertList = NULL; if (userAlertList) { tempUserAlertList = userAlertList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onUserAlertsUpdate:tempMegaSDK userAlertList:(tempUserAlertList ? [[MEGAUserAlertList alloc] initWithMegaUserAlertList:tempUserAlertList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAListener::onNodesUpdate(mega::MegaApi *api, mega::MegaNodeList *nodeList) { if (listener !=nil && [listener respondsToSelector:@selector(onNodesUpdate:nodeList:)]) { MegaNodeList *tempNodesList = NULL; if (nodeList) { tempNodesList = nodeList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onNodesUpdate:tempMegaSDK nodeList:(tempNodesList ? [[MEGANodeList alloc] initWithNodeList:tempNodesList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAListener::onSetsUpdate(mega::MegaApi *api, mega::MegaSetList *setList) { if (listener == nil || ![listener respondsToSelector:@selector(onSetsUpdate:sets:)]) { return; } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; int size = 0; if (setList) { size = setList->size(); } else { dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onSetsUpdate:tempMegaSDK sets:nil]; }); return; } NSMutableArray *sets = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASet *megaSet = [[MEGASet alloc] initWithMegaSet:setList->get(i)->copy() cMemoryOwn:YES]; [sets addObject:megaSet]; } dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onSetsUpdate:tempMegaSDK sets:[sets copy]]; }); } void DelegateMEGAListener::onSetElementsUpdate(mega::MegaApi* api, mega::MegaSetElementList* setElementList) { if (listener == nil || ![listener respondsToSelector:@selector(onSetElementsUpdate:setElements:)]) { return; } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; int size = 0; if (setElementList) { size = setElementList->size(); } else { dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onSetElementsUpdate:tempMegaSDK setElements:nil]; }); return; } NSMutableArray *setsElements = [[NSMutableArray alloc] initWithCapacity:size]; for (int i = 0; i < size; i++) { MEGASetElement *megaSetElement = [[MEGASetElement alloc] initWithMegaSetElement:setElementList->get(i)->copy() cMemoryOwn:YES]; [setsElements addObject:megaSetElement]; } dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onSetElementsUpdate:tempMegaSDK setElements:[setsElements copy]]; }); } void DelegateMEGAListener::onAccountUpdate(mega::MegaApi *api) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; if (listener !=nil && [listener respondsToSelector:@selector(onAccountUpdate:)]) { dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onAccountUpdate:tempMegaSDK]; }); } } void DelegateMEGAListener::onContactRequestsUpdate(mega::MegaApi* api, mega::MegaContactRequestList* contactRequestList) { if (listener != nil && [listener respondsToSelector:@selector(onContactRequestsUpdate:contactRequestList:)]) { MegaContactRequestList *tempContactRequestList = NULL; if(contactRequestList) { tempContactRequestList = contactRequestList->copy(); } MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onContactRequestsUpdate:tempMegaSDK contactRequestList:(tempContactRequestList ? [[MEGAContactRequestList alloc] initWithMegaContactRequestList:tempContactRequestList cMemoryOwn:YES] : nil)]; }); } } void DelegateMEGAListener::onReloadNeeded(MegaApi *api) { MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; if (listener != nil && [listener respondsToSelector:@selector(onReloadNeeded:)]) { dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onReloadNeeded:tempMegaSDK]; }); } } void DelegateMEGAListener::onEvent(mega::MegaApi *api, mega::MegaEvent *event) { if (listener != nil && [listener respondsToSelector:@selector(onEvent:event:)]) { MegaEvent *tempEvent = event->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch_async(dispatch_get_main_queue(), ^{ [tempListener onEvent:tempMegaSDK event:(tempEvent ? [[MEGAEvent alloc] initWithMegaEvent:tempEvent cMemoryOwn:YES] : nil)]; }); } } sdk-10.11.0/bindings/ios/Private/DelegateMEGALoggerListener.h000066400000000000000000000022661516266226600236410ustar00rootroot00000000000000/** * @file DelegateMEGALoggerListener.h * @brief Listener to reveice and send logs to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "megaapi.h" #import "MEGASdk.h" class DelegateMEGALoggerListener : public mega::MegaLogger { public: DelegateMEGALoggerListener(id listener); idgetUserListener(); void log(const char *time, int logLevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, int numberMessages #endif ); private: MEGASdk *megaSDK; id listener; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGALoggerListener.mm000066400000000000000000000044041516266226600240170ustar00rootroot00000000000000/** * @file DelegateMEGALoggerListener.mm * @brief Listener to reveice and send logs to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "DelegateMEGALoggerListener.h" #include using namespace mega; DelegateMEGALoggerListener::DelegateMEGALoggerListener(idlistener) { this->listener = listener; } idDelegateMEGALoggerListener::getUserListener() { return listener; } void DelegateMEGALoggerListener::log(const char *time, int logLevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, int numberMessages #endif ) { if (listener != nil && [listener respondsToSelector:@selector(logWithTime:logLevel:source:message: #ifdef ENABLE_LOG_PERFORMANCE directMessages:numberMessages: #endif )]) { #ifdef ENABLE_LOG_PERFORMANCE NSMutableArray *messages = [NSMutableArray arrayWithCapacity:numberMessages]; for (int i = 0; i < numberMessages; i++) { [messages addObject:[NSString stringWithUTF8String:directMessages[i]]]; } #endif [listener logWithTime:(time ? [NSString stringWithUTF8String:time] : @"") logLevel:(MEGALogLevel)logLevel source:(source ? [NSString stringWithUTF8String:source] : @"") message:(message ? [NSString stringWithUTF8String:message] : @"") #ifdef ENABLE_LOG_PERFORMANCE directMessages:messages.copy numberMessages:(NSInteger)numberMessages #endif ]; } } sdk-10.11.0/bindings/ios/Private/DelegateMEGARequestListener.h000066400000000000000000000030201516266226600240370ustar00rootroot00000000000000/** * @file DelegateMEGARequestListener.h * @brief Listener to reveice and send request events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGARequestDelegate.h" #import "megaapi.h" #import "MEGASdk.h" #import "ListenerDispatch.h" class DelegateMEGARequestListener : public mega::MegaRequestListener { public: DelegateMEGARequestListener(MEGASdk *megaSDK, idlistener, bool singleListener, ListenerQueueType queueType); idgetUserListener(); void onRequestStart(mega::MegaApi *api, mega::MegaRequest *request); void onRequestFinish(mega::MegaApi *api, mega::MegaRequest *request, mega::MegaError *e); void onRequestUpdate(mega::MegaApi *api, mega::MegaRequest *request); void onRequestTemporaryError(mega::MegaApi *api, mega::MegaRequest *request, mega::MegaError *e); private: MEGASdk *megaSDK; idlistener; bool singleListener; ListenerQueueType queueType; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGARequestListener.mm000066400000000000000000000073601516266226600242340ustar00rootroot00000000000000/** * @file DelegateMEGARequestListener.mm * @brief Listener to reveice and send request events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGARequestListener.h" #import "MEGARequest+init.h" #import "MEGAError+init.h" #import "MEGASdk+init.h" using namespace mega; DelegateMEGARequestListener::DelegateMEGARequestListener(MEGASdk *megaSDK, idlistener, bool singleListener, ListenerQueueType queueType) { this->megaSDK = megaSDK; this->listener = listener; this->singleListener = singleListener; this->queueType = queueType; } idDelegateMEGARequestListener::getUserListener() { return listener; } void DelegateMEGARequestListener::onRequestStart(MegaApi *api, MegaRequest *request) { if (listener != nil && [listener respondsToSelector:@selector(onRequestStart:request:)]) { MegaRequest *tempRequest = request->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onRequestStart:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES]]; }); } } void DelegateMEGARequestListener::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onRequestFinish:request:error:)]) { MegaRequest *tempRequest = request->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; bool tempSingleListener = singleListener; dispatch(this->queueType, ^{ [tempListener onRequestFinish:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; if (tempSingleListener) { [megaSDK freeRequestListener:this]; } }); } } void DelegateMEGARequestListener::onRequestUpdate(MegaApi *api, MegaRequest *request) { if (listener != nil && [listener respondsToSelector:@selector(onRequestUpdate:request:)]) { MegaRequest *tempRequest = request->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onRequestUpdate:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES]]; }); } } void DelegateMEGARequestListener::onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onRequestTemporaryError:request:error:)]) { MegaRequest *tempRequest = request->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onRequestTemporaryError:tempMegaSDK request:[[MEGARequest alloc] initWithMegaRequest:tempRequest cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } sdk-10.11.0/bindings/ios/Private/DelegateMEGAScheduledCopyListener.h000066400000000000000000000031541516266226600251520ustar00rootroot00000000000000/** * @file DelegateMEGAScheduledCopyListener.h * @brief Listener to reveice and send backup events to the app * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAScheduledCopyDelegate.h" #import "megaapi.h" #import "MEGASdk.h" #import "ListenerDispatch.h" class DelegateMEGAScheduledCopyListener : public mega::MegaScheduledCopyListener { public: DelegateMEGAScheduledCopyListener(MEGASdk *megaSDK, idlistener, ListenerQueueType queueType); idgetUserListener(); void onBackupStateChanged(mega::MegaApi *api, mega::MegaScheduledCopy *backup); void onBackupStart(mega::MegaApi *api, mega::MegaScheduledCopy *backup); void onBackupFinish(mega::MegaApi *api, mega::MegaScheduledCopy *backup, mega::MegaError *e); void onBackupUpdate(mega::MegaApi *api, mega::MegaScheduledCopy *backup); void onBackupTemporaryError(mega::MegaApi *api, mega::MegaScheduledCopy *backup, mega::MegaError *e); private: MEGASdk *megaSDK; idlistener; ListenerQueueType queueType; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGAScheduledCopyListener.mm000066400000000000000000000077021516266226600253370ustar00rootroot00000000000000/** * @file DelegateMEGAScheduledCopyListener.mm * @brief Listener to reveice and send transfer events to the app * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGAScheduledCopyListener.h" #import "MEGAScheduledCopy+init.h" #import "MEGAError+init.h" #import "MEGASdk+init.h" using namespace mega; DelegateMEGAScheduledCopyListener::DelegateMEGAScheduledCopyListener(MEGASdk *megaSDK, idlistener, ListenerQueueType queueType) { this->megaSDK = megaSDK; this->listener = listener; this->queueType = queueType; } idDelegateMEGAScheduledCopyListener::getUserListener() { return listener; } void DelegateMEGAScheduledCopyListener::onBackupStateChanged(mega::MegaApi *api, mega::MegaScheduledCopy *backup) { if (listener != nil && [listener respondsToSelector:@selector(onBackupStateChanged:backup:request:)]) { MegaScheduledCopy *tempBackup = backup->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onBackupStateChanged:tempMegaSDK backup:[[MEGAScheduledCopy alloc] initWithMegaScheduledCopy:tempBackup cMemoryOwn:YES]]; }); } } void DelegateMEGAScheduledCopyListener::onBackupStart(mega::MegaApi *api, mega::MegaScheduledCopy *backup) { if (listener != nil && [listener respondsToSelector:@selector(onBackupStateChanged:backup:request:)]) { MegaScheduledCopy *tempBackup = backup->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onBackupStart:tempMegaSDK backup:[[MEGAScheduledCopy alloc] initWithMegaScheduledCopy:tempBackup cMemoryOwn:YES]]; }); } } void DelegateMEGAScheduledCopyListener::onBackupFinish(mega::MegaApi *api, mega::MegaScheduledCopy *backup, mega::MegaError *e) { MegaScheduledCopy *tempBackup = backup->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onBackupFinish:tempMegaSDK backup:[[MEGAScheduledCopy alloc] initWithMegaScheduledCopy:tempBackup cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } void DelegateMEGAScheduledCopyListener::onBackupUpdate(mega::MegaApi *api, mega::MegaScheduledCopy *backup) { MegaScheduledCopy *tempBackup = backup->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onBackupUpdate:tempMegaSDK backup:[[MEGAScheduledCopy alloc] initWithMegaScheduledCopy:tempBackup cMemoryOwn:YES]]; }); } void DelegateMEGAScheduledCopyListener::onBackupTemporaryError(mega::MegaApi *api, mega::MegaScheduledCopy *backup, mega::MegaError *e) { MegaScheduledCopy *tempBackup = backup->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onBackupTemporaryError:tempMegaSDK backup:[[MEGAScheduledCopy alloc] initWithMegaScheduledCopy:tempBackup cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } sdk-10.11.0/bindings/ios/Private/DelegateMEGATransferListener.h000066400000000000000000000035501516266226600242030ustar00rootroot00000000000000/** * @file DelegateMEGATransferListener.h * @brief Listener to reveice and send transfer events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATransferDelegate.h" #import "megaapi.h" #import "MEGASdk.h" #import "ListenerDispatch.h" class DelegateMEGATransferListener : public mega::MegaTransferListener { public: DelegateMEGATransferListener(MEGASdk *megaSDK, idlistener, bool singleListener, ListenerQueueType queueType); idgetUserListener(); void onTransferStart(mega::MegaApi *api, mega::MegaTransfer *transfer); void onTransferFinish(mega::MegaApi *api, mega::MegaTransfer *transfer, mega::MegaError *e); void onTransferUpdate(mega::MegaApi *api, mega::MegaTransfer *transfer); void onFolderTransferUpdate(mega::MegaApi *api, mega::MegaTransfer *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char *currentFolder, const char *currentFileLeafname); void onTransferTemporaryError(mega::MegaApi *api, mega::MegaTransfer *transfer, mega::MegaError *e); bool onTransferData(mega::MegaApi *api, mega::MegaTransfer *transfer, char *buffer, size_t size); private: MEGASdk *megaSDK; idlistener; bool singleListener; ListenerQueueType queueType; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGATransferListener.mm000066400000000000000000000130551516266226600243660ustar00rootroot00000000000000/** * @file DelegateMEGATransferListener.mm * @brief Listener to reveice and send transfer events to the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGATransferListener.h" #import "MEGATransfer+init.h" #import "MEGAError+init.h" #import "MEGASdk+init.h" using namespace mega; DelegateMEGATransferListener::DelegateMEGATransferListener(MEGASdk *megaSDK, idlistener, bool singleListener, ListenerQueueType queueType) { this->megaSDK = megaSDK; this->listener = listener; this->singleListener = singleListener; this->queueType = queueType; } idDelegateMEGATransferListener::getUserListener() { return listener; } void DelegateMEGATransferListener::onTransferStart(MegaApi *api, MegaTransfer *transfer) { if (listener != nil && [listener respondsToSelector:@selector(onTransferStart:transfer:)]) { MegaTransfer *tempTransfer = transfer->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onTransferStart:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES]]; }); } } void DelegateMEGATransferListener::onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onTransferFinish:transfer:error:)]) { MegaTransfer *tempTransfer = transfer->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; bool tempSingleListener = singleListener; dispatch(this->queueType, ^{ [tempListener onTransferFinish:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; if (tempSingleListener) { [tempMegaSDK freeTransferListener:this]; } }); } } void DelegateMEGATransferListener::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) { if (listener != nil && [listener respondsToSelector:@selector(onTransferUpdate:transfer:)]) { MegaTransfer *tempTransfer = transfer->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onTransferUpdate:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES]]; }); } } void DelegateMEGATransferListener::onFolderTransferUpdate(MegaApi *api, mega::MegaTransfer *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char *currentFolder, const char *currentFileLeafname) { if (listener != nil && [listener respondsToSelector:@selector(onFolderTransferUpdate:transfer:stage:folderCount:createdFolderCount:fileCount:currentFolder:currentFileLeafName:)]) { MegaTransfer *tempTransfer = transfer->copy(); MEGASdk *tempMegaSDK = this->megaSDK; NSString *currentFolderString = currentFolder ? [NSString stringWithUTF8String:currentFolder] : nil; NSString *currentFileLeafNameString = currentFileLeafname ? [NSString stringWithUTF8String:currentFileLeafname] : nil; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onFolderTransferUpdate:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] stage:MEGATransferStage(stage) folderCount:foldercount createdFolderCount:createdfoldercount fileCount:filecount currentFolder:currentFolderString currentFileLeafName:currentFileLeafNameString]; }); } } void DelegateMEGATransferListener::onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError *e) { if (listener != nil && [listener respondsToSelector:@selector(onTransferTemporaryError:transfer:error:)]) { MegaTransfer *tempTransfer = transfer->copy(); MegaError *tempError = e->copy(); MEGASdk *tempMegaSDK = this->megaSDK; id tempListener = this->listener; dispatch(this->queueType, ^{ [tempListener onTransferTemporaryError:tempMegaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] error:[[MEGAError alloc] initWithMegaError:tempError cMemoryOwn:YES]]; }); } } bool DelegateMEGATransferListener::onTransferData(mega::MegaApi *api, mega::MegaTransfer *transfer, char *buffer, size_t size) { if (listener != nil && [listener respondsToSelector:@selector(onTransferData:transfer:buffer:)]) { MegaTransfer *tempTransfer = transfer->copy(); return [listener onTransferData:this->megaSDK transfer:[[MEGATransfer alloc] initWithMegaTransfer:tempTransfer cMemoryOwn:YES] buffer:[[NSData alloc] initWithBytes:transfer->getLastBytes() length:(long)transfer->getDeltaSize()]]; } return false; } sdk-10.11.0/bindings/ios/Private/DelegateMEGATreeProcessorListener.h000066400000000000000000000020501516266226600252100ustar00rootroot00000000000000/** * @file DelegateMEGATreeProcessorListener.h * @brief Listener to reveice nodes processed in a tree * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "megaapi.h" #import "MEGATreeProcessorDelegate.h" class DelegateMEGATreeProcessorListener : public mega::MegaTreeProcessor { public: DelegateMEGATreeProcessorListener(idlistener); bool processMegaNode(mega::MegaNode *node); private: idlistener; }; sdk-10.11.0/bindings/ios/Private/DelegateMEGATreeProcessorListener.mm000066400000000000000000000022241516266226600253750ustar00rootroot00000000000000/** * @file DelegateMEGATreeProcessorListener.mm * @brief Listener to reveice nodes processed in a tree * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DelegateMEGATreeProcessorListener.h" #import "MEGANode+init.h" using namespace mega; DelegateMEGATreeProcessorListener::DelegateMEGATreeProcessorListener(id listener) { this->listener = listener; } bool DelegateMEGATreeProcessorListener::processMegaNode(MegaNode *node) { if (this->listener) { return [listener processMEGANode:[[MEGANode alloc] initWithMegaNode:node cMemoryOwn:NO]]; } return false; } sdk-10.11.0/bindings/ios/Private/MEGAAccountDetails+init.h000066400000000000000000000016461516266226600231230ustar00rootroot00000000000000/** * @file MEGAAccountDetails+init.h * @brief Private functions of MEGAAccountDetails * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountDetails.h" #import "megaapi.h" @interface MEGAAccountDetails (init) - (instancetype)initWithMegaAccountDetails:(mega::MegaAccountDetails *)accountDetails cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaAccountDetails *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAAccountFeature+init.h000066400000000000000000000016451516266226600231300ustar00rootroot00000000000000/** * @file MEGAAccountFeature+init.h * @brief Private functions of MEGAAccountFeature * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountFeature.h" #import "megaapi.h" @interface MEGAAccountFeature (init) - (instancetype)initWithMegaAccountFeature:(mega::MegaAccountFeature *)megaAccountFeature cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaAccountFeature *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAAccountPlan+init.h000066400000000000000000000016111516266226600224200ustar00rootroot00000000000000/** * @file MEGAAccountPlan+init.h * @brief Private functions of MEGAAccountPlan * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountPlan.h" #import "megaapi.h" @interface MEGAAccountPlan (init) - (instancetype)initWithMegaAccountPlan:(mega::MegaAccountPlan *)accountPlan cMemoryOwn:(BOOL)cMemoryOwn; + (mega::MegaAccountPlan *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAAccountSubscription+init.h000066400000000000000000000017111516266226600242130ustar00rootroot00000000000000/** * @file MEGAAccountSubscription+init.h * @brief Private functions of MEGAAccountSubscription * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAccountSubscription.h" #import "megaapi.h" @interface MEGAAccountSubscription (init) - (instancetype)initWithMegaAccountSubscription:(mega::MegaAccountSubscription *)accountSubscription cMemoryOwn:(BOOL)cMemoryOwn; + (mega::MegaAccountSubscription *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAAchievementsDetails+init.h000066400000000000000000000017171516266226600241410ustar00rootroot00000000000000/** * @file MEGAAchievementsDetails+init.h * @brief Private functions of MEGAAchievementsDetails * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAAchievementsDetails.h" #import "megaapi.h" @interface MEGAAchievementsDetails (init) - (instancetype)initWithMegaAchievementsDetails:(mega::MegaAchievementsDetails *)megaAchievementsDetails cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaAchievementsDetails *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGABackgroundMediaUpload+init.h000066400000000000000000000006751516266226600244060ustar00rootroot00000000000000// // MEGABackgroundMediaUpload+init.h // MEGASDK // // Created by Simon Wang on 15/10/18. // Copyright © 2018 MEGA. All rights reserved. // #import "MEGABackgroundMediaUpload.h" #import "megaapi.h" NS_ASSUME_NONNULL_BEGIN @interface MEGABackgroundMediaUpload (init) - (instancetype)initWithBackgroundMediaUpload:(mega::MegaBackgroundMediaUpload *)mediaUpload; - (mega::MegaBackgroundMediaUpload *)getCPtr; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/Private/MEGABackupInfo+init.h000066400000000000000000000016051516266226600222350ustar00rootroot00000000000000/** * @file MEGABackupInfo+init.h * @brief Private functions of MEGABackupInfo * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABackupInfo.h" #import "megaapi.h" @interface MEGABackupInfo (init) - (instancetype)initWithMegaBackupInfo:(mega::MegaBackupInfo *)megaBackupInfo cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaBackupInfo *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGABanner+init.h000066400000000000000000000015561516266226600214260ustar00rootroot00000000000000/** * @file MEGABanner+init.h * @brief Private functions of MEGABanner * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABanner.h" #import "megaapi.h" @interface MEGABanner (init) - (instancetype)initWithMegaBanner:(mega::MegaBanner *)megaBanner cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaBanner *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGABannerList+init.h000066400000000000000000000016131516266226600222540ustar00rootroot00000000000000/** * @file MEGABannerList+init.h * @brief Private functions of MEGABannerList * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGABannerList.h" #import "megaapi.h" @interface MEGABannerList (init) - (instancetype)initWithMegaBannerList:(mega::MegaBannerList *)megaBanner cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaBannerList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGACancelSubscriptionReason+init.h000066400000000000000000000017751516266226600251660ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReason+init.h * @brief Private functions of MEGACancelSubscriptionReason * * (c) 2024-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelSubscriptionReason.h" #import "megaapi.h" @interface MEGACancelSubscriptionReason (init) - (instancetype)initWithMegaCancelSubscriptionReason:(mega::MegaCancelSubscriptionReason *)megaCancelSubscriptionReason cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaCancelSubscriptionReason *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGACancelSubscriptionReasonList+init.h000066400000000000000000000020351516266226600260100ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReasonList+init.h * @brief Private functions of MEGACancelSubscriptionReasonList * * (c) 2024-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelSubscriptionReasonList.h" #import "megaapi.h" @interface MEGACancelSubscriptionReasonList (init) - (instancetype)initWithMegaCancelSubscriptionReasonList:(mega::MegaCancelSubscriptionReasonList *)megaCancelSubscriptionReasonList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaCancelSubscriptionReasonList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGACancelToken+init.h000066400000000000000000000015221516266226600224000ustar00rootroot00000000000000/** * @file MEGACancelToken+init.h * @brief Private functions of MEGACancelToken * * (c) 2019 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACancelToken.h" #import "megaapi.h" NS_ASSUME_NONNULL_BEGIN @interface MEGACancelToken (init) - (mega::MegaCancelToken *)getCPtr; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/Private/MEGAContactRequest+init.h000066400000000000000000000016531516266226600231630ustar00rootroot00000000000000/** * @file MEGAContactRequest+init.h * @brief Private functions of MEGAContactRequest * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAContactRequest.h" #import "megaapi.h" @interface MEGAContactRequest (init) - (instancetype)initWithMegaContactRequest:(mega::MegaContactRequest *)megaContactRequest cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaContactRequest *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAContactRequestList+init.h000066400000000000000000000017131516266226600240140ustar00rootroot00000000000000/** * @file MEGAContactRequestList+init.h * @brief Private functions of MEGAContactRequestList * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAContactRequestList.h" #import "megaapi.h" @interface MEGAContactRequestList (init) - (instancetype)initWithMegaContactRequestList:(mega::MegaContactRequestList *)megaContactRequestList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaContactRequestList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGACreditCardNodeData+init.h000066400000000000000000000017751516266226600236300ustar00rootroot00000000000000/** * @file MEGACreditCardNodeData+init.h * @brief Private functions of MEGACreditCardNodeData * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACreditCardNodeData.h" #import "megaapi.h" using MegaCreditCardNodeData = mega::MegaNode::CreditCardNodeData; @interface MEGACreditCardNodeData (init) - (instancetype)initWithMegaCreditCardNodeData:(MegaCreditCardNodeData *)megaCreditCardNodeData cMemoryOwn:(BOOL)cMemoryOwn; - (MegaCreditCardNodeData *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGACurrency+init.h000066400000000000000000000015621516266226600220100ustar00rootroot00000000000000/** * @file MEGACurrency+init.h * @brief Private functions of MEGACurrency * * (c) 2021- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGACurrency.h" #import "megaapi.h" @interface MEGACurrency (init) - (instancetype)initWithMegaCurrency:(mega::MegaCurrency *)currency cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaCurrency *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGADataInputStream.h000066400000000000000000000016741516266226600223300ustar00rootroot00000000000000/** * @file MEGADataInputStream.h * @brief Implementation of MegaInputStream * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "megaapi.h" class MEGADataInputStream : public mega::MegaInputStream { public: MEGADataInputStream(NSData *data); int64_t getSize(); bool read(char *buffer, size_t size); private: NSData *data; long offset; }; sdk-10.11.0/bindings/ios/Private/MEGADataInputStream.mm000066400000000000000000000023131516266226600225010ustar00rootroot00000000000000/** * @file MEGADataInputStream.mm * @brief Implementation of MegaInputStream * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGADataInputStream.h" MEGADataInputStream::MEGADataInputStream(NSData *data) { this->data = data; this->offset = 0; } int64_t MEGADataInputStream::getSize() { return (int64_t)data.length; } bool MEGADataInputStream::read(char *buffer, size_t size) { if (offset + (long)size > data.length) { return false; } if (buffer == NULL) { offset += (long)size; return true; } memcpy(buffer, (unsigned char *)[data bytes] + offset, size); offset += size; return true; } sdk-10.11.0/bindings/ios/Private/MEGAError+init.h000066400000000000000000000015421516266226600213050ustar00rootroot00000000000000/** * @file MEGAError+init.h * @brief Private functions of MEGAError * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAError.h" #import "megaapi.h" @interface MEGAError (init) - (instancetype)initWithMegaError:(mega::MegaError *)megaError cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaError *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAEvent+init.h000066400000000000000000000015461516266226600213010ustar00rootroot00000000000000/** * @file MEGAHandleList+init * @brief Private functions of MEGAEvent * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAEvent.h" #import "megaapi.h" @interface MEGAEvent (init) - (instancetype)initWithMegaEvent:(mega::MegaEvent *)megaEvent cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaEvent *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAFileInputStream.h000066400000000000000000000017161516266226600223330ustar00rootroot00000000000000/** * @file MEGAFileInputStream.h * @brief Implementation of MegaInputStream * * (c) 2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "megaapi.h" class MEGAFileInputStream : public mega::MegaInputStream { public: MEGAFileInputStream(NSString *filePath); int64_t getSize(); bool read(char *buffer, size_t size); private: NSFileHandle *fileHandle; int64_t fileSize; }; sdk-10.11.0/bindings/ios/Private/MEGAFileInputStream.mm000066400000000000000000000031241516266226600225100ustar00rootroot00000000000000/** * @file MEGAFileInputStream.mm * @brief Implementation of MegaInputStream * * (c) 2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAFileInputStream.h" MEGAFileInputStream::MEGAFileInputStream(NSString *filePath) { this->fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; this->fileSize = [this->fileHandle seekToEndOfFile]; [this->fileHandle seekToFileOffset:0]; } int64_t MEGAFileInputStream::getSize() { return this->fileSize; } bool MEGAFileInputStream::read(char *buffer, size_t size) { if (this->fileHandle == nil) { return false; } unsigned long long currentOffset = this->fileHandle.offsetInFile; if (currentOffset + size > this->fileSize) { return false; } @try { if (buffer == NULL) { [this->fileHandle seekToFileOffset:currentOffset + size]; return true; } memcpy(buffer, [this->fileHandle readDataOfLength:size].bytes, size); return true; } @catch (NSException *exception) { return false; } } sdk-10.11.0/bindings/ios/Private/MEGAFolderInfo+init.h000066400000000000000000000016101516266226600222370ustar00rootroot00000000000000/** * @file MEGAFolderInfo+init.h * @brief Private functions of MEGAFolderInfo * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAFolderInfo.h" #import "megaapi.h" @interface MEGAFolderInfo (init) - (instancetype)initWithMegaFolderInfo:(mega::MegaFolderInfo *)megaFolderInfo cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaFolderInfo *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAHandleList+init.h000066400000000000000000000016751516266226600222520ustar00rootroot00000000000000/** * @file MEGAHandleList+init * @brief Private functions of MEGAHandleList * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAHandleList.h" #import "megaapi.h" @interface MEGAHandleList (init) - (instancetype)initWithMemoryOwn:(BOOL)cMemoryOwn; - (instancetype)initWithMegaHandleList:(mega::MegaHandleList *)megaHandleList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaHandleList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAIntegerList+init.h000066400000000000000000000016171516266226600224500ustar00rootroot00000000000000/** * @file MEGAIntegerList+init.h * @brief Private functions of MEGAIntegerList * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAIntegerList.h" #import "megaapi.h" @interface MEGAIntegerList (init) - (instancetype)initWithMegaIntegerList:(mega::MegaIntegerList *)megaIntegerList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaIntegerList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGANetworkConnectivityTestResults+init.h000066400000000000000000000020551516266226600264660ustar00rootroot00000000000000/** * @file MEGANetworkConnectivityTestResults+init.h * @brief Private functions of MEGANetworkConnectivityTestResults * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANetworkConnectivityTestResults.h" #import "megaapi.h" using namespace mega; @interface MEGANetworkConnectivityTestResults (init) - (instancetype)initWithMegaNetworkConnectivityTestResults:(MegaNetworkConnectivityTestResults *)megaNetworkConnectivityTestResults cMemoryOwn:(BOOL)cMemoryOwn; - (MegaNetworkConnectivityTestResults *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGANode+init.h000066400000000000000000000015331516266226600211010ustar00rootroot00000000000000/** * @file MEGANode+init.h * @brief Private functions of MEGAError * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANode.h" #import "megaapi.h" @interface MEGANode (init) - (instancetype)initWithMegaNode:(mega::MegaNode *)megaNode cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaNode *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGANodeList+init.h000066400000000000000000000015621516266226600217370ustar00rootroot00000000000000/** * @file MEGANodeList+init.h * @brief Private functions of MEGANodeList * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANodeList.h" #import "megaapi.h" @interface MEGANodeList (init) - (instancetype)initWithNodeList:(mega::MegaNodeList *)nodelist cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaNodeList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGANotification+init.h000066400000000000000000000016311516266226600226410ustar00rootroot00000000000000/** * @file MEGANotification+init.h * @brief Private functions of MEGANotification * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANotification.h" #import "megaapi.h" @interface MEGANotification (init) - (instancetype)initWithMegaNotification:(mega::MegaNotification *)notification cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaNotification *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGANotificationList+init.h000066400000000000000000000016571516266226600235050ustar00rootroot00000000000000/** * @file MEGANotificationList.h * @brief List of MEGANotification objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGANotificationList.h" #import "megaapi.h" @interface MEGANotificationList (init) - (instancetype)initWithMegaNotificationList:(mega::MegaNotificationList *)megaNotificationList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaNotificationList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAPricing+init.h000066400000000000000000000015561516266226600216140ustar00rootroot00000000000000/** * @file MEGAPricing+init.h * @brief Private functions of MEGAPricing * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAPricing.h" #import "megaapi.h" @interface MEGAPricing (init) - (instancetype)initWithMegaPricing:(mega::MegaPricing *)pricing cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaPricing *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAPushNotificationSettings+init.h000066400000000000000000000020511516266226600252170ustar00rootroot00000000000000/** * @file MEGAPushNotificationSettings+init.h * @brief Private functions of MEGAPushNotificationSettings * * (c) 2019 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAPushNotificationSettings.h" #import "megaapi.h" NS_ASSUME_NONNULL_BEGIN @interface MEGAPushNotificationSettings (init) - (instancetype)initWithMegaPushNotificationSettings:(mega::MegaPushNotificationSettings *)megaPushNotificationSettings cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaPushNotificationSettings *)getCPtr; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/Private/MEGARecentActionBucket+init.h000066400000000000000000000017211516266226600237270ustar00rootroot00000000000000/** * @file MEGARecentActionBucket+init.h * @brief Private functions of MEGARecentActionBucket * * (c) 2019 - Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGARecentActionBucket.h" #import "megaapi.h" @interface MEGARecentActionBucket (init) - (instancetype)initWithMegaRecentActionBucket:(mega::MegaRecentActionBucket *)megaRecentActionBucket cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaRecentActionBucket *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGARequest+init.h000066400000000000000000000015571516266226600216520ustar00rootroot00000000000000/** * @file MEGARequest+init.h * @brief Private functions of MEGARequest * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGARequest.h" #import "megaapi.h" @interface MEGARequest (init) - (instancetype)initWithMegaRequest:(mega::MegaRequest *)megaNode cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaRequest *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAScheduledCopy+init.h000066400000000000000000000016331516266226600227500ustar00rootroot00000000000000/** * @file MEGAScheduledCopy+init.h * @brief Private functions of MEGAScheduledCopy * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAScheduledCopy.h" #import "megaapi.h" @interface MEGAScheduledCopy (init) - (instancetype)initWithMegaScheduledCopy:(mega::MegaScheduledCopy *)scheduledCopy cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaScheduledCopy *) getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGASdk+init.h000066400000000000000000000017401516266226600207350ustar00rootroot00000000000000/** * @file MEGASdk+init.h * @brief Private functions of MEGASdk * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASdk.h" #import "DelegateMEGARequestListener.h" #import "DelegateMEGATransferListener.h" @interface MEGASdk (init) - (void)freeRequestListener:(nullable DelegateMEGARequestListener *)delegate; - (void)freeTransferListener:(nullable DelegateMEGATransferListener *)delegate; - (nullable mega::MegaApi *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGASet+init.h000066400000000000000000000014621516266226600207500ustar00rootroot00000000000000/** * @file MEGASet+init.h * @brief Private functions of MEGASet * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASet.h" #import "megaapi.h" @interface MEGASet (init) - (instancetype)initWithMegaSet:(mega::MegaSet *)megaSet cMemoryOwn:(BOOL)cMemoryOwn; @end sdk-10.11.0/bindings/ios/Private/MEGASetElement+init.h000066400000000000000000000015431516266226600222620ustar00rootroot00000000000000/** * @file MEGASetElement+init.h * @brief Private functions of MEGASetElement * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGASetElement.h" #import "megaapi.h" @interface MEGASetElement (init) - (instancetype)initWithMegaSetElement:(mega::MegaSetElement *)megaSetElement cMemoryOwn:(BOOL)cMemoryOwn; @end sdk-10.11.0/bindings/ios/Private/MEGAShare+init.h000066400000000000000000000015421516266226600212560ustar00rootroot00000000000000/** * @file MEGAShare+init.h * @brief Private functions of MEGAShare * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAShare.h" #import "megaapi.h" @interface MEGAShare (init) - (instancetype)initWithMegaShare:(mega::MegaShare *)megaShare cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaShare *) getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAShareList+init.h000066400000000000000000000015721516266226600221150ustar00rootroot00000000000000/** * @file MEGAShareList+init.h * @brief Private functions of MEGAShareList * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAShareList.h" #import "megaapi.h" @interface MEGAShareList (init) - (instancetype)initWithShareList:(mega::MegaShareList *)shareList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaShareList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAStringIntegerMap+init.h000066400000000000000000000016701516266226600234400ustar00rootroot00000000000000/** * @file MEGAStringIntegerMap+init.h * @brief Private functions of MEGAStringIntegerMap * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAStringIntegerMap.h" #import "megaapi.h" @interface MEGAStringIntegerMap (init) - (instancetype)initWithMegaStringIntegerMap:(mega::MegaStringIntegerMap *)megaStringIntegerMap cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaStringIntegerMap *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAStringList+init.h000066400000000000000000000016071516266226600223200ustar00rootroot00000000000000/** * @file MEGAStringList+init.h * @brief Private functions of MEGAStringList * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAStringList.h" #import "megaapi.h" @interface MEGAStringList (init) - (instancetype)initWithMegaStringList:(mega::MegaStringList *)megaStringList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaStringList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGATOTPData+init.h000066400000000000000000000016531516266226600215770ustar00rootroot00000000000000/** * @file MEGATOTPData+init.h * @brief Private functions of MEGATOTPData * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATOTPData.h" #import "megaapi.h" using MegaTotpData = mega::MegaNode::PasswordNodeData::TotpData; @interface MEGATOTPData (init) - (instancetype)initWithMegaTotpData:(MegaTotpData *)megaTotpData cMemoryOwn:(BOOL)cMemoryOwn; - (MegaTotpData *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGATOTPDataValidation+init.h000066400000000000000000000020211516266226600236000ustar00rootroot00000000000000/** * @file MEGATOTPDataValidation+init.h * @brief Private functions of MEGATOTPDataValidation * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATOTPDataValidation.h" #import "megaapi.h" using MegaTotpDataValidation = mega::MegaNode::PasswordNodeData::TotpData::Validation; @interface MEGATOTPDataValidation (init) - (instancetype)initWithMegaTotpDataValidation:(MegaTotpDataValidation *)megaTotpDataValidation cMemoryOwn:(BOOL)cMemoryOwn; - (MegaTotpDataValidation *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGATimeZoneDetails+init.h000066400000000000000000000016601516266226600232550ustar00rootroot00000000000000/** * @file MEGATimeZoneDetails+init.h * @brief Private functions of MEGATimeZoneDetails * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATimeZoneDetails.h" #import "megaapi.h" @interface MEGATimeZoneDetails (init) - (instancetype)initWithMegaTimeZoneDetails:(mega::MegaTimeZoneDetails *)megaTimeZoneDetails cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaTimeZoneDetails *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGATransfer+init.h000066400000000000000000000015721516266226600220030ustar00rootroot00000000000000/** * @file MEGATransfer+init.h * @brief Private functions of MEGATransfer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATransfer.h" #import "megaapi.h" @interface MEGATransfer (init) - (instancetype)initWithMegaTransfer:(mega::MegaTransfer *)megaTransfer cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaTransfer *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGATransferList+init.h000066400000000000000000000016221516266226600226330ustar00rootroot00000000000000/** * @file MEGATransferList+init.h * @brief Private functions of MEGATransferList * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGATransferList.h" #import "megaapi.h" @interface MEGATransferList (init) - (instancetype)initWithTransferList:(mega::MegaTransferList *)transferList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaTransferList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAUser+init.h000066400000000000000000000015321516266226600211310ustar00rootroot00000000000000/** * @file MEGAUser+init.h * @brief Private functions of MEGAUser * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUser.h" #import "megaapi.h" @interface MEGAUser (init) - (instancetype)initWithMegaUser:(mega::MegaUser *)megaUser cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaUser *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAUserAlert+init.h000066400000000000000000000016011516266226600221160ustar00rootroot00000000000000/** * @file MEGAUserAlert+init.h * @brief Private functions of MEGAUserAlert * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUserAlert.h" #import "megaapi.h" @interface MEGAUserAlert (init) - (instancetype)initWithMegaUserAlert:(mega::MegaUserAlert *)userAlert cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaUserAlert *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAUserAlertList+init.h000066400000000000000000000016451516266226600227620ustar00rootroot00000000000000/** * @file MEGAUserAlertList+init.h * @brief Private functions of MEGAUserAlertList * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUserAlertList.h" #import "megaapi.h" @interface MEGAUserAlertList (init) - (instancetype)initWithMegaUserAlertList:(mega::MegaUserAlertList *)megauserAlertList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaUserAlertList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAUserList+init.h000066400000000000000000000015621516266226600217700ustar00rootroot00000000000000/** * @file MEGAUserList+init.h * @brief Private functions of MEGAUserList * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAUserList.h" #import "megaapi.h" @interface MEGAUserList (init) - (instancetype)initWithUserList:(mega::MegaUserList *)userList cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaUserList *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAVPNCluster+init.h000066400000000000000000000016611516266226600222230ustar00rootroot00000000000000/** * @file MEGAVPNCluster+init.h * @brief Private functions of MEGAVPNCluster * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright * Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this program. */ #import "MEGAVPNCluster.h" #import "megaapi.h" NS_ASSUME_NONNULL_BEGIN @interface MEGAVPNCluster (init) - (instancetype)initWithMegaVpnCluster:(mega::MegaVpnCluster *)megaVpnCluster cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaVpnCluster *)getCPtr; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/Private/MEGAVPNCredentials+init.h000066400000000000000000000022151516266226600230330ustar00rootroot00000000000000/** * @file MEGAVPNCredentials+init.h * @brief Private functions of MEGAVPNCredentials * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MEGAVPNCredentials.h" #import "megaapi.h" @interface MEGAVPNCredentials (init) /** * @brief Initializes a new instance of MEGAVPNCredentials * * @param megaVpnCredentials The pointer to the C++ MegaVpnCredentials instance * @param cMemoryOwn Indicates whether this instance owns the C++ object memory */ - (instancetype)initWithMegaVpnCredentials:(mega::MegaVpnCredentials *)megaVpnCredentials cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaVpnCredentials *)getCPtr; @end sdk-10.11.0/bindings/ios/Private/MEGAVPNRegion+init.h000066400000000000000000000016511516266226600220240ustar00rootroot00000000000000/** * @file MEGAVPNRegion+init.h * @brief Private functions of MEGAVPNRegion * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright * Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this program. */ #import "MEGAVPNRegion.h" #import "megaapi.h" NS_ASSUME_NONNULL_BEGIN @interface MEGAVPNRegion (init) - (instancetype)initWithMegaVpnRegion:(mega::MegaVpnRegion *)megaVpnRegion cMemoryOwn:(BOOL)cMemoryOwn; - (mega::MegaVpnRegion *)getCPtr; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/000077500000000000000000000000001516266226600164615ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/include/BackUpState.h000066400000000000000000000017751516266226600210120ustar00rootroot00000000000000/** * @file BackUpState.h * @brief Backup sync states * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ typedef NS_ENUM(NSInteger, BackUpState) { BackUpStateInvalid = -1, BackUpStateNotInitialized = 0, BackUpStateActive = 1, BackUpStateFailed = 2, BackUpStateTemporaryDisabled = 3, BackUpStateDisabled = 4, BackUpStatePauseUp = 5, BackUpStatePauseDown = 6, BackUpStatePauseFull = 7, BackUpStateDeleted = 8, BackUpStateUnknown = 9 }; sdk-10.11.0/bindings/ios/include/BackUpSubState.h000066400000000000000000000053441516266226600214600ustar00rootroot00000000000000/** * @file BackUpSubState.h * @brief Backup substates * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ typedef NS_ENUM (NSInteger, BackUpSubState) { BackUpSubStateInvalid = -1, BackUpSubStateNoSyncError = 0, BackUpSubStateUnknownError = 1, BackUpSubStateUnsupportedFileSystem = 2, BackUpSubStateInvalidRemoteType = 3, BackUpSubStateInvalidLocalType = 4, BackUpSubStateInitialScanFailed = 5, BackUpSubStateLocalPathTemporaryUnavailable = 6, BackUpSubStateLocalPathUnavailable = 7, BackUpSubStateRemoteNodeNotFound = 8, BackUpSubStateStorageOverquota = 9, BackUpSubStateAccountExpired = 10, BackUpSubStateForeignTargetOverstorage = 11, BackUpSubStateRemotePathHasChanged = 12, BackUpSubStateShareNonFullAccess = 14, BackUpSubStateLocalFilesystemMismatch = 15, BackUpSubStatePutNodesError = 16, BackUpSubStateActiveSyncBelowPath = 17, BackUpSubStateActiveSyncAbovePath = 18, BackUpSubStateRemoteNodeMovedToRubbish = 19, BackUpSubStateRemoteNodeInsideRubbish = 20, BackUpSubStateVBoxSharedFolderUnsupported = 21, BackUpSubStateLocalPathSyncCollision = 22, BackUpSubStateAccountBlocked = 23, BackUpSubStateUnknownTemporaryError = 24, BackUpSubStateTooManyActionPackets = 25, BackUpSubStateLoggedOut = 26, BackUpSubStateWholeAccountRefetched = 27, BackUpSubStateMissingParentNode = 28, BackUpSubStateBackupModified = 29, BackUpSubStateBackupSourceNotBelowDrive = 30, BackUpSubStateSyncConfigWriteFailure = 31, BackUpSubStateActiveSyncSamePath = 32, BackUpSubStateCouldNotMoveCloudNodes = 33, BackUpSubStateCouldNotCreateIgnoreFile = 34, BackUpSubStateSyncConfigReadFailure = 35, BackUpSubStateUnknownDrivePath = 36, BackUpSubStateInvalidScanInterval = 37, BackUpSubStateNotificationSystemUnavailable = 38, BackUpSubStateUnableToAddWatch = 39, BackUpSubStateUnableToRetrieveRootFSID = 40, BackUpSubStateUnableToOpenDatabase = 41, BackUpSubStateInsufficientDiskSpace = 42, BackUpSubStateFailureAccessingPersistentStorage = 43, BackUpSubStateMismatchOfRootRSID = 44, BackUpSubStateFilesystemFileIdsAreUnstable = 45, BackUpSubStateFilesystemIDUnavailable = 46 }; sdk-10.11.0/bindings/ios/include/ListenerDispatch.h000066400000000000000000000025451516266226600221050ustar00rootroot00000000000000/** * @file ListenerDispatch.h * @brief dispatch functions for listener * * (c) 2021 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #ifndef ListenerDispatch_h #define ListenerDispatch_h typedef NS_ENUM (NSUInteger, ListenerQueueType) { ListenerQueueTypeCurrent, // Current thread will be used. It is recommended to use current thread whenever it is possible. It has no thread switching, hence good performance ListenerQueueTypeMain, // Main queue for UI update ListenerQueueTypeGlobalBackground, // Global queue with QOS background ListenerQueueTypeGlobalUtility, // Global queue with QOS utility ListenerQueueTypeGlobalUserInitiated, // Global queue with QOS user initiated }; typedef void (^ListenerBlock)(void); void dispatch(ListenerQueueType queueType, ListenerBlock block); #endif sdk-10.11.0/bindings/ios/include/MEGAAccountDetails.h000066400000000000000000000167741516266226600222050ustar00rootroot00000000000000/** * @file MEGAAccountDetails.h * @brief Details about a MEGA account * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAAccountFeature.h" #import "MEGAAccountFeature.h" #import "MEGAAccountPlan.h" #import "MEGAAccountSubscription.h" #import "MEGASubscriptionStatus.h" #import "MEGAAccountType.h" #import "MEGAPaymentMethod.h" #import "MEGAStringIntegerMap.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Details about a MEGA account. */ @interface MEGAAccountDetails : NSObject /** * @brief Used storage for the account (in bytes). */ @property (readonly, nonatomic) long long storageUsed; /** * @brief The used storage by versions (in bytes) */ @property (readonly) long long versionStorageUsed; /** * @brief Maximum storage for the account (in bytes). */ @property (readonly, nonatomic) long long storageMax; /** * @brief Used bandwidth allowance including own, free and served to other users (in bytes). */ @property (readonly, nonatomic) long long transferUsed; /** * @brief Maximum available bandwidth for the account (in bytes). */ @property (readonly, nonatomic) long long transferMax; /** * @brief PRO level of the MEGA account. * * Valid values are: * - MEGAAccountTypeFree = 0 * - MEGAAccountTypeProI = 1 * - MEGAAccountTypeProII = 2 * - MEGAAccountTypeProIII = 3 * - MEGAAccountTypeLite = 4 * - MEGAAccountTypeStarter = 11 * - MEGAAccountTypeBasic = 12 * - MEGAAccountTypeEssential = 13 * - MEGAAccountTypeBusiness = 100 * - MEGAAccountTypeProFlexi = 101 */ @property (readonly, nonatomic) MEGAAccountType type; /** * @brief The expiration time for the current PRO status (in seconds since the Epoch) */ @property (readonly, nonatomic) NSInteger proExpiration; /** * @brief Check if there is a valid subscription * * If this value is MEGASubscriptionStatusValid, the PRO account will be * automatically renewed. * See [MEGAAccountDetails subscriptionRenewTime] * * Information about about the subscription status * * Valid values are: * - MEGASubscriptionStatusNone = 0 * There isn't any active subscription * * - MEGASubscriptionStatusValid = 1 * There is an active subscription * * - MEGASubscriptionStatusInvalid = 2 * A subscription exists, but it uses a payment gateway that is no longer valid * */ @property (readonly, nonatomic) MEGASubscriptionStatus subscriptionStatus; /** * @brief The time when the the PRO account will be renewed (in seconds since the Epoch) */ @property (readonly, nonatomic) NSInteger subscriptionRenewTime; /** * @brief The subscription method. For example "Credit Card". * */ @property (readonly, nonatomic, nullable) NSString *subscriptionMethod; /** * @brief The subscription method. For example 16. * */ @property (readonly, nonatomic) MEGAPaymentMethod subscriptionMethodId; /** * @brief The subscription cycle * * This value will show if the subscription will be montly or yearly renewed. * Example return values: "1 M", "1 Y". * */ @property (readonly, nonatomic, nullable) NSString *subscriptionCycle; /** * @brief The number of nodes with account usage info * * You can get information about each node using [MEGAAccountDetails storageUsedForHandle:], * [MEGAAccountDetailsn numberFilesForHandle:], [MEGAAccountDetails numberFoldersForHandle:] * * This function can return: * - 0 (no info about any node) * - 3 (info about the root node, the inbox node and the rubbish node) * Use [MEGASdk rootNode], [MEGASdk inboxNode] and [MEGASdk rubbishNode] to get those nodes. * * - >3 (info about root, inbox, rubbish and incoming shares) * Use [MEGASdk inShares] to get the incoming shares * */ @property (readonly, nonatomic) NSInteger numberUsageItems; /** * @brief Number of active MegaAccountFeature objects associated with the account. */ @property (readonly, nonatomic) NSInteger numActiveFeatures; /** * @brief Get feature account level for feature-related subscriptions. * * @return Level for feature-related subscriptions. */ @property (readonly, nonatomic) int64_t subscriptionLevel; /** * @brief Returns the active MegaAccountFeature object associated with an index. * * You take the ownership of the returned value. * * @param index Index of the object. * @return MegaAccountFeature object. */ - (nullable MEGAAccountFeature *)activeFeatureAtIndex:(NSInteger)index; /** * @brief Subscription features for the account. * * You take the ownership of the returned value. */ @property (readonly, nonatomic) NSDictionary *subscriptionFeatures; /** * @brief Get the used storage in for a node. * * Only root nodes are supported. * * @param handle Handle of the node to check. * @return Used storage (in bytes). * @see [MEGASdk rootNode], [MEGASdk rubbishNode], [MEGASdk inboxNode]. */ - (long long)storageUsedForHandle:(uint64_t)handle; /** * @brief Get the number of files in a node. * * Only root nodes are supported. * * @param handle Handle of the node to check. * @return Number of files in the node. * @see [MEGASdk rootNode], [MEGASdk rubbishNode], [MEGASdk inboxNode]. */ - (long long)numberFilesForHandle:(uint64_t)handle; /** * @brief Get the number of folders in a node. * * Only root nodes are supported. * * @param handle Handle of the node to check. * @return Number of folders in the node. * @see [MEGASdk rootNode], [MEGASdk rubbishNode], [MEGASdk inboxNode]. */ - (long long)numberFoldersForHandle:(uint64_t)handle; /** * @brief Get the used storage by versions in for a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Used storage by versions (in bytes) * @see [MEGASdk rootNode], [MEGASdk rubbishNode], [MEGASdk inboxNode]; */ - (long long)versionStorageUsedForHandle:(uint64_t)handle; /** * @brief Get the number of versioned files in a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Number of versioned files in the node * @see [MEGASdk rootNode], [MEGASdk rubbishNode], [MEGASdk inboxNode]; */ - (long long)numberOfVersionFilesForHandle:(uint64_t)handle; + (nullable NSString *)stringForAccountType:(MEGAAccountType)accountType; /** * @brief Get the number of active plans in the account. * * @return Number of active plans */ @property (readonly, nonatomic) NSInteger numberOfPlans; /** * @brief Returns the MEGAAccountPlan object associated with an index * * @param index Index of the object * @return MEGAAccountPlan object */ - (nullable MEGAAccountPlan *)planAtIndex:(NSInteger)index; /** * @brief Get the number of active subscriptions in the account. * * You can use [MEGAAccountDetails subscription] to get each of those objects. * * @return Number of active subscriptions */ @property (readonly, nonatomic) NSInteger numberOfSubscriptions; /** * @brief Returns the MEGAAccountSubscription object associated with an index * * * @param index Index of the object * @return MEGAAccountSubscription object */ - (nullable MEGAAccountSubscription *)subscriptionAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAAccountFeature.h000066400000000000000000000020141516266226600221710ustar00rootroot00000000000000/** * @file MEGAAccountFeature.h * @brief Details about a MEGA feature * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Details about a MEGA feature. */ @interface MEGAAccountFeature : NSObject /** * @brief The expiry timestamp. */ @property (nonatomic, readonly) int64_t expiry; /** * @brief The id of this feature. */ @property (nonatomic, readonly, nullable) NSString *featureId; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAAccountPlan.h000066400000000000000000000047071516266226600215030ustar00rootroot00000000000000/** * @file MEGAAccountPlan.h * @brief Details about a MEGA account plan * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAAccountType.h" #import "MEGAStringList.h" /** * @brief Details about a MEGA account plan. */ @interface MEGAAccountPlan : NSObject /** * @brief Check if the plan is a PRO plan or a feature plan. * * @return YES if the plan is a PRO plan */ @property (readonly, nonatomic) BOOL isProPlan; /** * @brief Get account level of the plan * * @return Plan level of the MEGA account. * Valid values for PRO plans are: * - MEGAAccountTypeFree = 0 * - MEGAAccountTypeProI = 1 * - MEGAAccountTypeProII = 2 * - MEGAAccountTypeProIII = 3 * - MEGAAccountTypeLite = 4 * - MEGAAccountTypeStarter = 11 * - MEGAAccountTypeBasic = 12 * - MEGAAccountTypeEssential = 13 * - MEGAAccountTypeBusiness = 100 * - MEGAAccountTypeProFlexi = 101 * * Valid value for feature plans is: * - MEGAAccountTypeFeature = 99999 */ @property (readonly, nonatomic) MEGAAccountType accountType; /** * @brief Get the expiration time for the plan * * @return The time the plan expires */ @property (readonly, nonatomic) int64_t expirationTime; /** * @brief Get the features granted by this plan * * @return Features granted by this plan. */ @property (readonly, nonatomic, nullable) NSArray *features; /** * @brief The type of plan. Why it was granted. * * Not available for Bussiness/Pro Flexi. * * @return Plan type */ @property (readonly, nonatomic) int32_t type; /** * @brief Get the relating subscription ID * * Only available if the plan relates to a subscription. * * @return ID of this subscription */ @property (readonly, nonatomic, nullable) NSString *subscriptionId; /** * @brief Check if the plan is related to an active trial * * @return YES if the plan is related to an active trial, otherwise NO. */ @property (readonly, nonatomic) BOOL isTrial; @end sdk-10.11.0/bindings/ios/include/MEGAAccountSubscription.h000066400000000000000000000066661516266226600233030ustar00rootroot00000000000000/** * @file MEGAAccountSubscription.h * @brief Details about a MEGA account subscription * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAAccountType.h" #import "MEGAPaymentMethod.h" #import "MEGAStringList.h" #import "MEGASubscriptionStatus.h" /** * @brief Details about a MEGA account subscription. */ @interface MEGAAccountSubscription : NSObject /** * @brief Get the ID of this subscription * * @return ID of this subscription */ @property (readonly, nonatomic, nullable) NSString *subcriptionId; /** * @brief Check if the subscription is active * * If this function returns MEGASubscriptionStatusValid * the subscription will be automatically renewed. * See [MEGAAccountSubscription renewTime] * * @return Information about the subscription status * * Valid return values are: * - MEGASubscriptionStatusNone = 0 * There isn't any active subscription * * - MEGASubscriptionStatusValid = 1 * There is an active subscription * * - MEGASubscriptionStatusInvalid = 2 * A subscription exists, but it uses a payment gateway that is no longer valid */ @property (readonly, nonatomic) MEGASubscriptionStatus status; /** * @brief Get the subscription cycle * * The return value will show if the subscription will be montly or yearly renewed. * Example return values: "1 M", "1 Y". * * @return Subscription cycle */ @property (readonly, nonatomic, nullable) NSString *cycle; /** * @brief Get the subscription payment provider name * * @return Payment provider name */ @property (readonly, nonatomic, nullable) NSString *paymentMethod; /** * @brief Get the subscription payment provider ID * * @return Information about the payment provider ID */ @property (readonly, nonatomic) MEGAPaymentMethod paymentMethodId; /** * @brief Get the subscription renew timestamp * * @return Renewal timestamp (in seconds since epoch) */ @property (readonly, nonatomic) int64_t renewTime; /** * @brief Get the subscription account level * * @return Subscription account level * Valid values for PRO plan subscriptions: * - MEGAAccountTypeFree = 0 * - MEGAAccountTypeProI = 1 * - MEGAAccountTypeProII = 2 * - MEGAAccountTypeProIII = 3 * - MEGAAccountTypeLite = 4 * - MEGAAccountTypeStarter = 11 * - MEGAAccountTypeBasic = 12 * - MEGAAccountTypeEssential = 13 * - MEGAAccountTypeBusiness = 100 * - MEGAAccountTypeProFlexi = 101 * * Valid value for feature plan subscriptions: * - MEGAAccountTypeFeature = 99999 */ @property (readonly, nonatomic) MEGAAccountType accountType; /** * @brief Get the features granted by this subscription * * @return Features granted by this subscription. */ @property (readonly, nonatomic, nullable) NSArray *features; /** * @brief Check if the subscription is related to an active trial * * @return YES if the subscription is related to an active trial, otherwise NO. */ @property (readonly, nonatomic) BOOL isTrial; @end sdk-10.11.0/bindings/ios/include/MEGAAccountType.h000066400000000000000000000021641516266226600215250ustar00rootroot00000000000000/** * @file MEGAAccountType.h * @brief Details about a MEGA account type enum * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM (NSInteger, MEGAAccountType) { MEGAAccountTypeUnknown = -1, MEGAAccountTypeFree = 0, MEGAAccountTypeProI = 1, MEGAAccountTypeProII = 2, MEGAAccountTypeProIII = 3, MEGAAccountTypeLite = 4, MEGAAccountTypeStarter = 11, MEGAAccountTypeBasic = 12, MEGAAccountTypeEssential = 13, MEGAAccountTypeBusiness = 100, MEGAAccountTypeProFlexi = 101, MEGAAccountTypeFeature = 99999 }; sdk-10.11.0/bindings/ios/include/MEGAAchievementsDetails.h000066400000000000000000000240101516266226600232020ustar00rootroot00000000000000/** * @file MEGAAchievementsDetails.h * @brief Achievements that a user can unlock * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAStringList.h" NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, MEGAAchievement) { MEGAAchievementWelcome = 1, MEGAAchievementInvite = 3, MEGAAchievementDesktopInstall = 4, MEGAAchievementMobileInstall = 5, MEGAAchievementAddPhone = 9, MEGAAchievementPassFreeTrial = 10, MEGAAchievementVPNFreeTrial = 11 }; /** * @brief The MEGAAchievementsDetails class * * There are several MEGA Achievements that a user can unlock, resulting in a * temporary extension of the storage and/or transfer quota during a period of * time. * * Currently there are 4 different classes of MEGA Achievements: * * - Welcome: Create your free account and get 35 GB of complimentary storage space, * valid for 30 days. * * - Invite: Invite as many friends or coworkers as you want. For every signup under the * invited email address, you will receive 10 GB of complimentary storage plus 20 GB * of transfer quota, both valid for 365 days, provided that the new user installs * either MEGAsync or a mobile app and starts using MEGA. * * - Desktop install: When you install MEGAsync you get 20 GB of complimentary storage * space plus 40 GB of transfer quota, both valid for 180 days. * * - Mobile install: When you install our mobile app you get 15 GB of complimentary * storage space plus 30 GB transfer quota, both valid for 180 days. * * When the user unlocks one of the achievements above, it unlocks an "Award". The award * includes a timestamps to indicate when it was unlocked, plus an expiration timestamp. * Afterwards, the award will not be active. Additionally, each award results in a "Reward". * The reward is linked to the corresponding award and includes the storage and transfer * quota obtained thanks to the unlocked award. * * @note It may take 2-3 days for achievements to show on the account after they have been completed. */ @interface MEGAAchievementsDetails : NSObject /** * @brief The base storage value for this account, in bytes */ @property (nonatomic, readonly) long long baseStorage; /** * @brief Returns the actual storage achieved by this account * * This function considers the base storage (permanent) plus all the * storage granted to the logged in account as result of the unlocked * achievements. It does not consider the expired achievements. * * @return The achieved storage for this account */ @property (nonatomic, readonly) long long currentStorage; /** * @brief Returns the actual transfer quota achieved by this account * * This function considers all the transfer quota granted to the logged * in account as result of the unlocked achievements. It does not consider * the expired achievements. * * @return The achieved transfer quota for this account */ @property (nonatomic, readonly) long long currentTransfer; /** * @brief Returns the actual achieved storage due to referrals * * This function considers all the storage granted to the logged in account * as result of the successful invitations (referrals). It does not consider * the expired achievements. * * @return The achieved storage by this account as result of referrals */ @property (nonatomic, readonly) long long currentStorageReferrals; /** * @brief Returns the actual achieved transfer quota due to referrals * * This function considers all the transfer quota granted to the logged * in account as result of the successful invitations (referrals). It * does not consider the expired achievements. * * @return The transfer achieved quota by this account as result of referrals */ @property (nonatomic, readonly) long long currentTransferReferrals; /** * @brief The number of unlocked awards for this account */ @property (nonatomic, readonly) NSUInteger awardsCount; /** * @brief The number of active rewards for this account */ @property (nonatomic, readonly) NSInteger rewardsCount; /** * @brief Checks if the corresponding achievement is valid * * Some achievements are valid only for some users. * * The following classes are valid: * - MEGAAchievementWelcome = 1 * - MEGAAchievementInvite = 3 * - MEGAAchievementDesktopInstall = 4 * - MEGAAchievementMobileInstall = 5 * - MEGAAchievementAddPhone = 9 * - MEGAAchievementPassFreeTrial = 10 * - MEGAAchievementVPNFreeTrial = 11 * * @param classId Id of the achievement. * @return True if it is valid, false otherwise */ - (bool)isValidClass:(NSInteger)classId; /** * @brief Get the storage granted by a MEGA achievement class * * The following classes are valid: * - MEGAAchievementWelcome = 1 * - MEGAAchievementInvite = 3 * - MEGAAchievementDesktopInstall = 4 * - MEGAAchievementMobileInstall = 5 * - MEGAAchievementAddPhone = 9 * - MEGAAchievementPassFreeTrial = 10 * - MEGAAchievementVPNFreeTrial = 11 * * @param classId Id of the MEGA achievement * @return Storage granted by this MEGA achievement class, in bytes */ - (long long)classStorageForClassId:(NSInteger)classId; /** * @brief Get the transfer quota granted by a MEGA achievement class * * The following classes are valid: * - MEGAAchievementWelcome = 1 * - MEGAAchievementInvite = 3 * - MEGAAchievementDesktopInstall = 4 * - MEGAAchievementMobileInstall = 5 * - MEGAAchievementAddPhone = 9 * - MEGAAchievementPassFreeTrial = 10 * - MEGAAchievementVPNFreeTrial = 11 * * @param classId Id of the MEGA achievement * @return Transfer quota granted by this MEGA achievement class, in bytes */ - (long long)classTransferForClassId:(NSInteger)classId; /** * @brief Get the duration of storage/transfer quota granted by a MEGA achievement class * * The following classes are valid: * - MEGAAchievementWelcome = 1 * - MEGAAchievementInvite = 3 * - MEGAAchievementDesktopInstall = 4 * - MEGAAchievementMobileInstall = 5 * - MEGAAchievementAddPhone = 9 * - MEGAAchievementPassFreeTrial = 10 * - MEGAAchievementVPNFreeTrial = 11 * * The storage and transfer quota resulting from a MEGA achievement may expire after * certain number of days. In example, the "Welcome" reward lasts for 30 days and afterwards * the granted storage and transfer quota is revoked. * * @param classId Id of the MEGA achievement * @return Number of days for the storage/transfer quota granted by this MEGA achievement class */ - (NSInteger)classExpireForClassId:(NSInteger)classId; /** * @brief Get the MEGA achievement class of the award * @param index Position of the award in the list of unlocked awards * @return The achievement class associated to the award in position \c index */ - (NSInteger)awardClassAtIndex:(NSUInteger)index; /** * @brief Get the id of the award * @param index Position of the award in the list of unlocked awards * @return The id of the award in position \c index */ - (NSInteger)awardIdAtIndex:(NSUInteger)index; /** * @brief Get the timestamp of the award (when it was unlocked) * @param index Position of the award in the list of unlocked awards * @return The timestamp of the award (when it was unlocked) in position \c index */ - (nullable NSDate *)awardTimestampAtIndex:(NSUInteger)index; /** * @brief Get the expiration timestamp of the award * * After this moment, the storage and transfer quota granted as result of the award * will not be valid anymore. * * @note The expiration time may not be the \c awardTimestamp plus the number of days * returned by \c classExpireForClassId, since the award can be unlocked but not yet granted. It * typically takes 2 days from unlocking the award until the user is actually rewarded. * * @param index Position of the award in the list of unlocked awards * @return The expiration timestamp of the award in position \c index */ - (nullable NSDate *)awardExpirationAtIndex:(NSUInteger)index; /** * @brief Get the list of referred emails for the award * * This function is specific for the achievements of class MEGAAchievementInvite. * * @param index Position of the award in the list of unlocked awards * @return The list of invited emails for the award in position \c index */ - (nullable MEGAStringList *)awardEmailsAtIndex:(NSUInteger)index; /** * @brief Get the id of the award associated with the reward * @param index Position of the reward in the list of active rewards * @return The id of the award associated with the reward */ - (NSInteger)rewardAwardIdAtIndex:(NSUInteger)index; /** * @brief Get the storage rewarded by the award * @param index Position of the reward in the list of active rewards * @return The storage rewarded by the award */ - (long long)rewardStorageAtIndex:(NSUInteger)index; /** * @brief Get the transfer quota rewarded by the award * @param index Position of the reward in the list of active rewards * @return The transfer quota rewarded by the award */ - (long long)rewardTransferAtIndex:(NSUInteger)index; /** * @brief Get the storage rewarded by the awardId * @param awardId The id of the award * @return The storage rewarded by the awardId */ - (long long)rewardStorageByAwardId:(NSInteger)awardId; /** * @brief Get the transfer rewarded by the awardId * @param awardId The id of the award * @return The transfer rewarded by the awardId */ - (long long)rewardTransferByAwardId:(NSInteger)awardId; /** * @brief Get the duration of the reward * @param index Position of the reward in the list of active rewards * @return The duration of the reward, in days */ - (NSInteger)rewardExpireAtIndex:(NSUInteger)index; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGABackgroundMediaUpload.h000066400000000000000000000161321516266226600234530ustar00rootroot00000000000000/** * @file MEGABackgroundMediaUpload.h * @brief Background media upload * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @class MEGASdk; @interface MEGABackgroundMediaUpload : NSObject /** * @brief Initial step to upload a photo/video via iOS low-power background upload feature. * * Creates an object which can be used to encrypt a media file, and upload it outside of the SDK, * eg. in order to take advantage of a particular platform's low power background upload functionality. * * @param sdk The MEGASdk instance the new object will be used with. It must live longer than the new object. * @return A pointer to an object that keeps some needed state through the process of * uploading a media file via iOS low power background uploads (or similar). */ - (nullable instancetype)initWithMEGASdk:(MEGASdk *)sdk; /** * @brief Extract mediainfo information about the photo or video. * * Call this function once with the file to be uploaded. It uses mediainfo to extract information that will * help other clients to show or to play the files. The information is stored in this object until the whole * operation completes. * * Call ensureMediaInfo in MEGASdk first in order prepare the library to attach file attributes * that enable videos to be identified and played in the web browser. * * @param inputFilepath The file to analyse with MediaInfo. * @return YES if analysis was performed (and any relevant attributes stored ready for upload), NO if mediainfo was not ready yet. */ - (BOOL)analyseMediaInfoForFileAtPath:(NSString *)inputFilepath; /** * @brief Encrypt the file or a portion of it. * * Call this function once with the file to be uploaded. It uses mediainfo to extract information that will * help the webclient show or play the file in various browsers. The information is stored in this object * until the whole operation completes. The encrypted data is stored in a new file. * * In order to save space on mobile devices, this function can be called in such a way that the last portion * of the file is encrypted (to a new file), and then that last portion of the file is removed by file truncation. * That operation can be repeated until the file is completely encrypted, and only the encrypted version remains, * and takes up the same amount of space on the device. The size of the portions must first be calculated by using * the 'adjustsSizeOnly' parameter, and iterating from the start of the file, specifying the approximate sizes of the portions. * * Encryption is done by reading small pieces of the file, encrypting them, and outputting to the new file, * so that RAM usage is not excessive. * * @param inputFilePath The file to encrypt a portion of (and the one that is ultimately being uploaded). * @param start The index of the first byte of the file to encrypt. * @param length The number of bytes of the file to encrypt. The function will round this value up by up to 1MB to fit the * MEGA internal chunking algorithm. The number of bytes actually encrypted and stored in the new file is the updated number. * You can supply -1 as input to request the remainder file (from start) be encrypted. * @param outputFilePath The name of the new file to create, and store the encrypted data in. * @param adjustsSizeOnly If this is set YES, then encryption is not performed, and only the length parameter is adjusted. * This feature is to enable precalculating the exact sizes of the file portions for upload. * @return If the function tries to encrypt and succeeds, the return value is the suffix to append to the URL when uploading this enrypted chunk. * If adjustsizeonly was set, and the function succeeds, the return value will be a nonempty string. * If the function fails, the return value is an empty string, and an error will have been logged. */ - (nullable NSString *)encryptFileAtPath:(NSString *)inputFilePath startPosition:(int64_t)start length:(int64_t *)length outputFilePath:(nullable NSString *)outputFilePath adjustsSizeOnly:(BOOL)adjustsSizeOnly; /** * @brief Retrieves the value of the uploadURL once it has been successfully requested via requestBackgroundUploadURLWithFileSize:mediaUpload:delegate: in MEGASdk. * * @return The URL to upload to (after appending the suffix), if one has been received. Otherwise the string will be empty. */ - (nullable NSString *)uploadURLString; /** * @brief Sets the GPS coordinates for the node * * The node created via completeBackgroundMediaUpload:fileName:parentNode:fingerprint:originalFingerprint:binaryUploadToken:delegate: in MEGASdk * will gain these coordinates as part of the * node creation. If the unshareable flag is set, the coodinates are encrypted in a way that even if the * node is later shared, the GPS coordinates cannot be decrypted by a different account. * * @param latitude The GPS latitude * @param longitude The GPS longitude * @param unshareable Set this true to prevent the coordinates being readable by other accounts. */ - (void)setCoordinatesWithLatitude:(double)latitude longitude:(double)longitude isUnshareable:(BOOL)unshareable; /** * @brief Turns the data stored in this object into a base 64 encoded binary data. * * The object can then be recreated via unserialize method in MEGABackgroundMediaUpload and supplying the returned binary data. * * You take ownership of the returned value. * * @return serialized version of this object (including URL, mediainfo attributes, and internal data suitable to resume uploading with in future). */ - (nullable NSData *)serialize; /** * @brief Get back the needed MEGABackgroundMediaUpload after the iOS app exited and restarted. * * In case the iOS app exits while a background upload is going on, and the app is started again * to complete the operation, call this function to recreate the MEGABackgroundMediaUpload object * needed for a call to completeBackgroundMediaUpload:fileName:parentNode:fingerprint:originalFingerprint:binaryUploadToken:delegate: in MEGASdk. * The object must have been serialised before the app was unloaded by using serialize method in MEGABackgroundMediaUpload. * * You take ownership of the returned value. * * @param data The binary the object was serialized to previously. * @param sdk The MEGASdk this object will be used with. It must live longer than this object. * @return A new MEGABackgroundMediaUpload instance with all fields set to the data that was * stored in the serialized binary data. */ + (nullable instancetype)unserializByData:(NSData *)data MEGASdk:(MEGASdk *)sdk; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGABackupInfo.h000066400000000000000000000275121516266226600213140ustar00rootroot00000000000000/** * @file MEGABackupInfo.h * @brief Represents information of a Backup in MEGA * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "BackUpState.h" #import "BackUpSubState.h" typedef NS_ENUM (NSInteger, MEGABackupType) { MEGABackupTypeInvalid = -1, MEGABackupTypeTwoWay = 0, MEGABackupTypeUpSync = 1, MEGABackupTypeDownSync = 2, MEGABackupTypeCameraUpload = 3, MEGABackupTypeMediaUpload = 4, MEGABackupTypeBackupUpload = 5, }; typedef NS_ENUM(NSUInteger, MEGABackupHeartbeatStatus) { MEGABackupHeartbeatStatusUpToDate = 1, MEGABackupHeartbeatStatusSyncing = 2, MEGABackupHeartbeatStatusPending = 3, MEGABackupHeartbeatStatusInactive = 4, MEGABackupHeartbeatStatusUnknown = 5, MEGABackupHeartbeatStatusStalled = 6, }; NS_ASSUME_NONNULL_BEGIN @interface MEGABackupInfo : NSObject /** * @brief Returns Backup id. */ @property (readonly, nonatomic) NSUInteger id; /** * @brief Returns Backup type. * * @return Sync state of the backup. * * It can be one of the following values: * - MEGABackupTypeInvalid = -1, * Invalid backup type. * * - MEGABackupTypeTwoWay = 0, * Two way backup type. * * - MEGABackupTypeUpSync = 1, * Up sync backup type. * * - MEGABackupTypeDownSync = 2, * Down sync backup type. * * - MEGABackupTypeCameraUpload = 3, * Camera Upload backup type. * * - MEGABackupTypeMediaUpload = 4, * Media Upload backup type. * * - MEGABackupTypeBackupUpload = 5, * Backup upload backup type. * */ @property (readonly, nonatomic) MEGABackupType type; /** * @brief Returns handle of Backup root. */ @property (readonly, nonatomic) uint64_t root; /** * @brief Returns the name of the backed up local folder. */ @property (readonly, nonatomic, nullable) NSString *localFolder; /** * @brief Returns the id of the device where the backup originated. */ @property (readonly, nonatomic, nullable) NSString *deviceId; /** * @brief Returns the sync state of the backup. * * @return Sync state of the backup. * * It can be one of the following values: * - BackUpStateInvalid = -1, * Not initialized sync state. * * - BackUpStateNotInitialized = 0, * Not initialized sync state. * * - BackUpStateActive = 1, * Working fine (enabled) * * - BackUpStateFailed = 2, * Failed (permanently disabled) * * - BackUpStateTemporaryDisabled = 3, * emporarily disabled due to a transient situation (e.g: account blocked). Will be resumed when the condition passes * * - BackUpStateDisabled = 4, * Disabled by the user * * - BackUpStatePauseUp = 5, * Active but upload transfers paused in the SDK * * - BackUpStatePauseDown = 6, * Active but download transfers paused in the SDK * * - BackUpStatePauseFull = 7, * Active but transfers paused in the SDK * * - BackUpStateDeleted = 8, * Sync needs to be deleted, as required by sync-desired-state received from BackupCenter (WebClient) * * - BackUpStateUnknown = 9, * Unknown status. * */ @property (readonly, nonatomic) BackUpState state; /** * @brief Returns the sync substate of the backup. * * @return Substate of the backup. * * It can be one of the following values: * - BackUpSubStateInvalid = -1, * No synchronization error. * * - BackUpSubStateNoSyncError = 0, * No synchronization error. * * - BackUpSubStateUnknownError = 1, * Unknown error occurred during the backup process. * * - BackUpSubStateUnsupportedFileSystem = 2, * The file system used is not supported. * * - BackUpSubStateInvalidRemoteType = 3, * Invalid remote type, it is not a folder that can be synced. * * - BackUpSubStateInvalidLocalType = 4, * Invalid local type, its path does not refer to a folder. * * - BackUpSubStateInitialScanFailed = 5, * Initial scan failed. * * - BackUpSubStateLocalPathTemporaryUnavailable = 6, * Temporary unavailability of the local path. This is fatal when adding a sync. * * - BackUpSubStateLocalPathUnavailable = 7, * The local path is unavailable (can't be opened). * * - BackUpSubStateRemoteNodeNotFound = 8, * The remote node no longer exists. * * - BackUpSubStateStorageOverquota = 9, * Account reached storage overquota. * * - BackUpSubStateAccountExpired = 10, * Account expired (business or pro flexi). * * - BackUpSubStateForeignTargetOverstorage = 11, * Sync transfer fails (upload into an inshare whose account is overquota). * * - BackUpSubStateRemotePathHasChanged = 12, * The remote path has changed (currently unused: not an error). * * - BackUpSubStateShareNonFullAccess = 14, * Existing inbound share sync or part thereof lost full access. * * - BackUpSubStateLocalFilesystemMismatch = 15, * Filesystem fingerprint does not match the one stored for the synchronization. * * - BackUpSubStatePutNodesError = 16, * Error processing put nodes result. * * - BackUpSubStateActiveSyncBelowPath = 17, * There's a synced node below the path to be synced. * * - BackUpSubStateActiveSyncAbovePath = 18, * There's a synced node above the path to be synced. * * - BackUpSubStateRemoteNodeMovedToRubbish = 19, * The remote node for backup was moved to the rubbish bin. * * - BackUpSubStateRemoteNodeInsideRubbish = 20, * The remote node for backup is attempted to be added in rubbish. * * - BackUpSubStateVBoxSharedFolderUnsupported = 21, * Found unsupported VBoxSharedFolderFS. * * - BackUpSubStateLocalPathSyncCollision = 22, * Local path includes a synced path or is included within one. * * - BackUpSubStateAccountBlocked = 23, * The backup account has been blocked. * * - BackUpSubStateUnknownTemporaryError = 24, * Unknown temporary error occurred during backup. * * - BackUpSubStateTooManyActionPackets = 25, * Too many changes in account, local state discarded. * * - BackUpSubStateLoggedOut = 26, * The user has been logged out. * * - BackUpSubStateWholeAccountRefetched = 27, * The whole account was reloaded, missed actionpacket changes could not have been applied. * * - BackUpSubStateMissingParentNode = 28, * Setting a new parent to a parent whose LocalNode is missing its corresponding Node crossref. * * - BackUpSubStateBackupModified = 29, * The backup has been externally modified. * * - BackUpSubStateBackupSourceNotBelowDrive = 30, * The backup source path not below drive path. * * - BackUpSubStateSyncConfigWriteFailure = 31, * Unable to write sync config to disk. * * - BackUpSubStateActiveSyncSamePath = 32, * There's a synced node at the path to be synced. * * - BackUpSubStateCouldNotMoveCloudNodes = 33, * rename() failed. * * - BackUpSubStateCouldNotCreateIgnoreFile = 34, * Couldn't create a sync's initial ignore file. * * - BackUpSubStateSyncConfigReadFailure = 35, * Couldn't read sync configs from disk. * * - BackUpSubStateUnknownDrivePath = 36, * Sync's drive path isn't known. * * - BackUpSubStateInvalidScanInterval = 37, * The user's specified an invalid scan interval. * * - BackUpSubStateNotificationSystemUnavailable = 38, * Filesystem notification subsystem has encountered an unrecoverable error. * * - BackUpSubStateUnableToAddWatch = 39, * Unable to add a filesystem watch. * * - BackUpSubStateUnableToRetrieveRootFSID = 40, * Unable to retrieve a sync root's FSID. * * - BackUpSubStateUnableToOpenDatabase = 41, * Unable to open state cache database. * * - BackUpSubStateInsufficientDiskSpace = 42, * Insufficient space for download. * * - BackUpSubStateFailureAccessingPersistentStorage = 43, * Failure accessing persistent storage. * * - BackUpSubStateMismatchOfRootRSID = 44, * The sync root's FSID changed. So this is a different folder. * * - BackUpSubStateFilesystemFileIdsAreUnstable = 45, * On MAC, the FSID of a file in an exFAT drive can change frequently. * * - BackUpSubStateFilesystemIDUnavailable = 46, * Could not get the filesystem's id * */ @property (readonly, nonatomic) BackUpSubState substate; /** * @brief Returns extra information, used as source for extracting other details. */ @property (readonly, nonatomic, nullable) NSString *extra; /** * @brief Returns the name of the backup. */ @property (readonly, nonatomic, nullable) NSString *name; /** * @brief Returns the timestamp of the backup, as reported by heartbeats. */ @property (readonly, nonatomic, nullable) NSDate *timestamp; /** * @brief Returns the status of the backup, as reported by heartbeats. * * @return Heartbeat substatus * * It can be one of the following values: * - MEGABackupHeartbeatStatusUpToDate = 1, * The backup status is up to date. * * - MEGABackupHeartbeatStatusSyncing = 2, * The backup status is currently syncing. * * - MEGABackupHeartbeatStatusPending = 3, * The backup status is pending. * * - MEGABackupHeartbeatStatusInactive = 4, * The backup status is inactive. * * - MEGABackupHeartbeatStatusUnknown = 5, * The backup status is unknown. * * - MEGABackupHeartbeatStatusStalled = 6, * The backup status is stalled. */ @property (readonly, nonatomic) MEGABackupHeartbeatStatus status; /** * @brief Returns the progress of the backup, as reported by heartbeats. */ @property (readonly, nonatomic) NSUInteger progress; /** * @brief Returns upload count. */ @property (readonly, nonatomic) NSUInteger uploads; /** * @brief Returns download count. */ @property (readonly, nonatomic) NSUInteger downloads; /** * @brief Returns the last activity timestamp, as reported by heartbeats. */ @property (readonly, nonatomic, nullable) NSDate *activityTimestamp; /** * @brief Returns handle of the last synced node. */ @property (readonly, nonatomic) uint64_t lastSync; /** * @brief Returns the user-agent associated with the device where the backup originated. * */ @property (readonly, nonatomic, nullable) NSString *userAgent; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGABackupInfoList.h000066400000000000000000000025241516266226600221440ustar00rootroot00000000000000/** * @file MEGABackupInfoList.h * @brief List of MEGABackupInfo objects * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGABackupInfo.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGABackupInfo objects. * */ @interface MEGABackupInfoList : NSObject /** * @brief The number of MEGABackupInfo objects in the list. */ @property (readonly, nonatomic) NSUInteger size; /** * @brief The MEGABackupInfo at the position index in the MEGABackupInfoList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGABackupInfo that we want to get for the list. * @return MEGABackupInfo at the position index in the list. */ - (nullable MEGABackupInfo *)backupInfoAtIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGABanner.h000066400000000000000000000047201516266226600204740ustar00rootroot00000000000000/** * @file MEGABanner.h * @brief Represents an user banner in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGABanner : NSObject /** * @brief Returns the id of the MEGABanner * * @return Id of this banner */ @property (nonatomic, readonly) NSUInteger identifier; /** * @brief Returns the title associated with the MEGABanner object * * @return Title associated with the MEGABanner object */ @property (nonatomic, readonly, nullable) NSString *title; /** * @brief Returns the description associated with the MEGABanner object * * @return Description associated with the MEGABanner object */ @property (nonatomic, readonly, nullable) NSString *description; /** * @brief Returns the filename of the image used by the MegaBanner object * * @return Filename of the image used by the MegaBanner object */ @property (nonatomic, readonly, nullable) NSString *imageFilename; /** * @brief Returns the filename of the background image used by the MEGABanner object * * @return Filename of the background image used by the MEGABanner object */ @property (nonatomic, readonly, nullable) NSString *backgroundImageFilename; /** * @brief Returns the URL where images are located * * @return URL where images are located */ @property (nonatomic, readonly, nullable) NSString *imageLocationURLString; /** * @brief Returns the URL associated with the MEGABanner object * * @return URL associated with the MEGABanner object */ @property (nonatomic, readonly, nullable) NSString *URLString; /** * @brief Returns the variant associated with the MEGABanner object * * @return variant associated with the MEGABanner object */ @property (nonatomic, readonly) NSInteger variant; /** * @brief Returns the button associated with the MEGABanner object * * @return button associated with the MEGABanner object */ @property (nonatomic, readonly, nullable) NSString *button; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGABannerList.h000066400000000000000000000030321516266226600213230ustar00rootroot00000000000000/** * @file MEGABannerList.h * @brief List of MEGABanner * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGABanner.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGABanner objects * * A MEGABannerList has the ownership of the MEGABanner objects that it contains, so they will be * only valid until the MEGABannerList is deleted. * */ @interface MEGABannerList : NSObject /** * @brief The number of banners in the list */ @property (nonatomic, readonly) NSInteger size; /** * @brief Returns the MEGABanner at position index in the MEGABannerList * * The MEGABannerList retains the ownership of the returned MEGABanner. It will be only valid until * the MEGABannerList is deleted. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGABanner that we want to get for the list * @return MEGABanner at position i in the list */ - (nullable MEGABanner *)bannerAtIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGACancelSubscriptionReason.h000066400000000000000000000030261516266226600242270ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReason.h * @brief Represents a reason chosen by a user when canceling a subscription * * (c) 2024- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGACancelSubscriptionReason : NSObject /** * @brief Create new instance of reason * * @param text The actual text of the reason. * @param position The rendered position of the selected reason (for example "1", "1.a", "2" etc.) * * @return New instance of a reason */ + (MEGACancelSubscriptionReason *)create:(NSString *)text position:(NSString *)position; /** * @brief Returns the text of the reason * * @return The actual text of the reason */ @property (nonatomic, readonly, nullable) NSString *text; /** * @brief Returns the rendered position of the selected reason * * @return The rendered position of the selected reason (for example "1", "1.a", "2" etc.) */ @property (nonatomic, readonly, nullable) NSString *position; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGACancelSubscriptionReasonList.h000066400000000000000000000033621516266226600250660ustar00rootroot00000000000000/** * @file MEGACancelSubscriptionReasonList.h * @brief Represents a reason chosen from a multiple-choice by a user when canceling a subscription * * (c) 2024- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGACancelSubscriptionReason.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGACancelSubscriptionReason objects * */ @interface MEGACancelSubscriptionReasonList : NSObject /** * @brief Create new instance of reason list * * @return New instance of a reason list */ + (MEGACancelSubscriptionReasonList *)create; /** * @brief The number of MEGACancelSubscriptionReason objects in the list */ @property (readonly, nonatomic) NSInteger size; /** * @brief Add new reason to the list * @param reason to be added */ - (void)addReason:(MEGACancelSubscriptionReason *)reason; /** * @brief Returns the MEGACancelSubscriptionReason at the position index in the MEGACancelSubscriptionReasonList. * @param index The position index of the MEGACancelSubscriptionReason to retrieve. * @return The MEGACancelSubscriptionReason at the given index or NULL if index was out of range. */ - (nullable MEGACancelSubscriptionReason *)reasonAtIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGACancelToken.h000066400000000000000000000017041516266226600214540ustar00rootroot00000000000000/** * @file MEGACancelToken.h * @brief Cancel MEGASdk methods. * * (c) 2019- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGACancelToken : NSObject /** * @brief The state of the flag */ @property (nonatomic, readonly, getter=isCancelled) BOOL cancelled; /** * @brief Allows to set the value of the flag */ - (void)cancel; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAContactRequest.h000066400000000000000000000067231516266226600222400ustar00rootroot00000000000000/** * @file MEGAContactRequest.h * @brief Represents a contact request with an user in MEGA * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM(NSUInteger, MEGAContactRequestStatus) { MEGAContactRequestStatusUnresolved = 0, MEGAContactRequestStatusAccepted, MEGAContactRequestStatusDenied, MEGAContactRequestStatusIgnored, MEGAContactRequestStatusDeleted, MEGAContactRequestStatusReminded }; typedef NS_ENUM(NSUInteger, MEGAReplyAction) { MEGAReplyActionAccept = 0, MEGAReplyActionDeny, MEGAReplyActionIgnore }; typedef NS_ENUM(NSUInteger, MEGAInviteAction) { MEGAInviteActionAdd = 0, MEGAInviteActionDelete, MEGAInviteActionRemind }; /** * @brief Provides information about a contact request * * Developers can use delegates (MEGADelegate, MEGAGlobalDelegate) * to track the progress of each contact. MEGAContactRequest objects are provided in callbacks sent * to these delegates and allow developers to know the state of the contact requests, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the contact request * when the object is created, they are immutable. * */ NS_ASSUME_NONNULL_BEGIN @interface MEGAContactRequest : NSObject /** * @brief The handle of this MEGAContactRequest object */ @property (readonly, nonatomic) uint64_t handle; /** * @brief The email of the request creator */ @property (nullable, readonly, nonatomic) NSString *sourceEmail; /** * @brief The message that the creator of the contact request has added */ @property (nullable, readonly, nonatomic) NSString *sourceMessage; /** * @brief The email of the recipient or nil if the current account is the recipient */ @property (nullable, readonly, nonatomic) NSString *targetEmail; /** * @brief The creation time of the contact request (in seconds since the Epoch) */ @property (nullable, readonly, nonatomic) NSDate *creationTime; /** * @brief The last update time of the contact request (in seconds since the Epoch) */ @property (nullable, readonly, nonatomic) NSDate *modificationTime; /** * @brief The status of the contact request * * It can be one of the following values: * - MEGAContactRequestStatusUnresolved = 0 * The request is pending * * - MEGAContactRequestStatusAccepted = 1 * The request has been accepted * * - MEGAContactRequestStatusDenied = 2 * The request has been denied * * - MEGAContactRequestStatusIgnored = 3 * The request has been ignored * * - MEGAContactRequestDeleted = 4 * The request has been deleted * * - MEGAContactRequestReminded = 5 * The request has been reminded * * @return Status of the contact request */ @property (readonly, nonatomic) MEGAContactRequestStatus status; /** * @brief Direction of the request * @return YES if the request is outgoing and NO if it's incoming */ - (BOOL)isOutgoing; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAContactRequestList.h000066400000000000000000000027351516266226600230730ustar00rootroot00000000000000/** * @file MEGAContactRequestList.h * @brief List of MEGAContactRequest objects * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAContactRequest.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGAContactRequest objects * * Objects of this class are immutable. * * @see [MEGASdk contactRequests] */ @interface MEGAContactRequestList : NSObject /** * @brief The number of MEGAContactRequest objects in the list. */ @property (readonly, nonatomic) NSInteger size; /** * @brief Returns the MEGAContactRequest at the position index in the MEGAContactRequestList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGAContactRequest that we want to get for the list. * @return MEGAContactRequest at the position index in the list. */ - (nullable MEGAContactRequest *)contactRequestAtIndex:(NSInteger)index; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGACreditCardNodeData.h000066400000000000000000000053441516266226600226760ustar00rootroot00000000000000/** * @file MEGACreditCardNodeData.h * @brief Object Data for Password Node attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGACreditCardNodeData : NSObject /** * @brief Get cardNumber attribute value. * * @return A string with the cardNumber value or nil if not set */ @property (readonly, nonatomic, nullable) NSString *cardNumber; /** * @brief Get notes attribute value. * * @return A string with the notes value or nil if not set */ @property (readonly, nonatomic, nullable) NSString *notes; /** * @brief Get cardHolderName attribute value. * * @return A string with the cardHolderName value or nil if not set */ @property (readonly, nonatomic, nullable) NSString *cardHolderName; /** * @brief Get cvv attribute value. * * @return A string with the cvv value or nil if not set */ @property (readonly, nonatomic, nullable) NSString *cvv; /** * @brief Get expiration date attribute value (in MM/YY format). * * @return A string with the expirationDate value or nil if not set */ @property (readonly, nonatomic, nullable) NSString *expirationDate; /** * @brief Creates a new instance of MEGACreditCardNodeData. * * @param cardNumber Number of the card (All characters must be digits). This field * cannot be null nor empty when creating a new Credit card Node * @param notes Notes to attach to the Credit card node * @param cardHolderName Name of holder of Credit Card * @param cvv card verification Value of the Credit Card (All characters must be digits) * @param expirationDate expiration date of Credit card (Expected format `MM/YY` with MM * and YY digits) * * @note: nil can be used to specify that a field is not to be updated when calling * [MEGASdk updateCreditCardNodeWithHandle:newData:delegate:]. * * @return A newly created MEGACreditCardNodeData object. */ -(instancetype)initWithCardNumber:(NSString *)cardNumber notes:(nullable NSString *)notes cardHolderName:(nullable NSString *)cardHolderName cvv:(nullable NSString *)cvv expirationDate:(nullable NSString *)expirationDate; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGACurrency.h000066400000000000000000000023631516266226600210620ustar00rootroot00000000000000/** * @file MEGACurrency.h * @brief Details about currencies * * (c) 2021- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGACurrency : NSObject /// @brief The currency symbol of prices, ie. € @property (readonly, nonatomic, nullable) NSString *currencySymbol; /// @brief The currency name of prices, ie. EUR @property (readonly, nonatomic, nullable) NSString *currencyName; /// @brief The currency symbol of local prices, ie. $ @property (readonly, nonatomic, nullable) NSString *localCurrencySymbol; /// @brief The currency name of local prices, ie. NZD @property (readonly, nonatomic, nullable) NSString *localCurrencyName; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGADelegate.h000066400000000000000000000325071516266226600210050ustar00rootroot00000000000000/** * @file MEGADelegate.h * @brief Delegate to get all events related to a MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATransfer.h" #import "MEGARequest.h" #import "MEGAError.h" #import "MEGANodeList.h" #import "MEGAUserList.h" #import "MEGAUserAlertList.h" #import "MEGAContactRequestList.h" #import "MEGAEvent.h" NS_ASSUME_NONNULL_BEGIN @class MEGASdk; /** * @brief Protocol to get all events related to a MEGA account. * * Implementations of this protocol can receive all events (request, transfer, global). * The SDK will provide a new interface to get synchronization events separately in future updates. * */ @protocol MEGADelegate @optional /** * @brief This function is called when a request is about to start being processed. * * @param api MEGASdk object that started the request. * @param request Information about the request. */ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request; /** * @brief This function is called when a request has finished. * * There won't be more callbacks about this request. * The last parameter provides the result of the request. If the request finished without problems, * the error code will be MEGAErrorTypeApiOk. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @param error Error information. */ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error; /** * @brief This function is called to inform about the progress of a request. * * Currently, this callback is only used for fetchNodes (MEGARequestTypeFetchNodes) requests. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @see [MEGARequest totalBytes] [MEGARequest transferredBytes]. */ - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request; /** * @brief This function is called when there is a temporary error processing a request. * * The request continues after this callback, so expect more [MEGARequestDelegate onRequestTemporaryError:request:error:] or * a [MEGARequestDelegate onRequestFinish:request:error:] callback. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @param error Error information. */ - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error; /** * @brief This function is called when a transfer is about to start being processed. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. */ - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer; /** * @brief This function is called when a transfer has finished. * * There won't be more callbacks about this transfer. * The last parameter provides the result of the transfer. If the transfer finished without problems, * the error code will be MEGAErrorTypeApiOk. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * @param error Error information. */ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error; /** * @brief This function is called to inform about the progress of a transfer. * * In case this transfer represents a recursive operation (folder upload/download) SDK will * notify apps about the stages transition. * * Current recursive operation stage can be retrieved with method MegaTransfer::getStage. * This method returns the following values: * - MEGATransferStageScan = 1 * - MEGATransferStageCreateTreee = 2 * - MEGATransferStageTransferringFiles = 3 * For more information about stages refer to [MEGATransfer stage] * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * * @see [MEGATransfer transferredBytes], [MEGATransfer speed], [MEGATransfer stage]. */ - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer; /** * @brief This function is called when there is a temporary error processing a transfer. * * The transfer continues after this callback, so expect more * [MEGATransferDelegateonTransferTemporaryError:transfer:error:] or * a [MEGATransferDelegate onTransferFinish:transfer:error:] callback. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * @param error Error information. */ - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error; /** * @brief This function is called when there are new or updated contacts in the account. * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil * * @param api MEGASdk object connected to the account. * @param userList List that contains the new or updated contacts. */ - (void)onUsersUpdate:(MEGASdk *)api userList:(nullable MEGAUserList *)userList; /** * @brief This function is called when there are new or updated user alerts in the account * * The SDK retains the ownership of the MEGAUserAlertList in the second parameter. The list and all the * MEGAUserAlert objects that it contains will be valid until this function returns. If you want to save the * list, use [MEGAUserAlertList clone]. If you want to save only some of the MEGAUserAlert objects, use [MEGAUserAlert clone] * for those objects. * * When there is a problem parsing the incoming information from the server or the full * account is reloaded or a large number of server notifications arrives at once, the second * parameter will be nil. * * @param api MEGASdk object connected to the account * @param userAlertList List that contains the new or updated contacts */ - (void)onUserAlertsUpdate:(MEGASdk *)api userAlertList:(nullable MEGAUserAlertList *)userAlertList; /** * @brief This function is called when there are new or updated nodes in the account. * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be nil. * * @param api MEGASdk object connected to the account. * @param nodeList List that contains the new or updated nodes. */ - (void)onNodesUpdate:(MEGASdk *)api nodeList:(nullable MEGANodeList *)nodeList; /** * @brief This function is called when a Set has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil. * * @param api MEGASdk object connected to the account * @param sets Array that contains the new or updated Sets */ - (void)onSetsUpdate:(MEGASdk *)api sets:(nullable NSArray *)sets; /** * @brief This function is called when a SetElement has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil. * * @param api MEGASdk object connected to the account * @param setElements Array that contains the new or updated Set-Elements */ - (void)onSetElementsUpdate:(MEGASdk *)api setElements:(nullable NSArray *)setElements; /** * @brief This function is called when the account has been updated (confirmed/upgraded/downgraded) * * The usage of this delegate to handle the external account confirmation is deprecated. * Instead, you should use [MEGAGlobalDelegate onEvent:event:]. * * @param api MEGASdk object connected to the account */ - (void)onAccountUpdate:(MEGASdk *)api; /** * @brief This function is called when there are new or updated contact requests in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be nil. * * @param api MEGASdk object connected to the account * @param contactRequestList List that contains the new or updated contact requests */ - (void)onContactRequestsUpdate:(MEGASdk *)api contactRequestList:(nullable MEGAContactRequestList *)contactRequestList; /** * @brief This function is called when an inconsistency is detected in the local cache. * * You should call [MEGASdk fetchNodes] when this callback is received. * * @param api MEGASdk object connected to the account. */ - (void)onReloadNeeded:(MEGASdk *)api; /** * The details about the event, like the type of event and optionally any * additional parameter, is received in the \c params parameter. * * You can check the type of event by calling [MEGAEvent type] * * Currently, the following type of events are notified: * * - EventCommitDB: when the SDK commits the ongoing DB transaction. * This event can be used to keep synchronization between the SDK cache and the * cache managed by the app thanks to the sequence number. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: sequence number recorded by the SDK when this event happened * * - EventAccountConfirmation: when a new account is finally confirmed * by the user by confirming the signup link. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: email address used to confirm the account * * - EventDisconnect: when the SDK performs a disconnect to reset all the * existing open-connections, since they have become unusable. It's recommended that the app * receiving this event reset its connections with other servers, since the disconnect * performed by the SDK is due to a network change or IP addresses becoming invalid. * * - EventAccountBlocked: when the account get blocked, typically because of * infringement of the Mega's terms of service repeatedly. This event is followed by an automatic * logout. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: message to show to the user. * - [MEGAEvent number]: code representing the reason for being blocked. * 200: suspension message for any type of suspension, but copyright suspension. * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. * 700: the account is supended for Weak Account Protection. * * - EventStorage: when the status of the storage changes. * * For this event type, [MEGAEvent number] provides the current status of the storage * * There are four possible storage states: * - StorageStateGreen = 0 * There are no storage problems * * - StorageStateOrange = 1 * The account is almost full * * - StorageStateRed = 2 * The account is full. Uploads have been stopped * * - StorageStateChange = 3 * There is a possible significant change in the storage state. * It's needed to call [MEGASdk getAccountDetails] to check the storage status. * After calling it, this callback will be called again with the corresponding * state if there is really a change. * * - StorageStatePaywall = 4 * The account has been full for a long time. Now most of actions are disallowed. * You will need to call [MEGASdk getUserData] before retrieving the overquota deadline/warnings * timestamps. * * - EventNodesCurrent: when all external changes have been received * * - EventMediaInfoReady: when codec-mappings have been received * * - EventBusinessStatus: when the status of a business account has changed. * * For this event type, [MEGAEvent number] provides the new business status. * * The posible values are: * - BusinessStatusExpired = -1 * - BusinessStatusInactive = 0 * - BusinessStatusActive = 1 * - BusinessStatusGracePeriod = 2 * * - EventKeyModified: when the key of a user has changed. * * For this event type, [MEGAEvent handle] provides the handle of the user whose key has been modified. * For this event type, [MEGAEvent number] provides type of key that has been modified. * * The posible values are: * - Public chat key (Cu25519) = 0 * - Public signing key (Ed25519) = 1 * - Public RSA key = 2 * - Signature of chat key = 3 * - Signature of RSA key = 4 * * - EventMiscFlagsReady: when the miscellaneous flags are available/updated. * * - EventReqStatProgress: Provides the per mil progress of a long-running API operation * in [MEGAEvent number], or -1 if there isn't any operation in progress. * * - EventReloading: when the API server has forced a full reload. The app should show a * similar UI to the one displayed during the initial load (fetchnodes). * * @param api MEGASdk object connected to the account * @param event Details about the event */ - (void)onEvent:(MEGASdk *)api event:(MEGAEvent *)event; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAError.h000066400000000000000000000153271516266226600203650ustar00rootroot00000000000000/** * @file MEGAError.h * @brief Error info * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Declaration of API error codes. */ typedef NS_ENUM(NSInteger, MEGAErrorType) { MEGAErrorTypeApiOk = 0, MEGAErrorTypeApiEInternal = -1, // internal error MEGAErrorTypeApiEArgs = -2, // bad arguments MEGAErrorTypeApiEAgain = -3, // request failed, retry with exponential backoff MEGAErrorTypeApiERateLimit = -4, // too many requests, slow down MEGAErrorTypeApiEFailed = -5, // request failed permanently MEGAErrorTypeApiETooMany = -6, // too many requests for this resource MEGAErrorTypeApiERange = -7, // resource access out of rage MEGAErrorTypeApiEExpired = -8, // resource expired MEGAErrorTypeApiENoent = -9, // resource does not exist MEGAErrorTypeApiECircular = -10, // circular linkage MEGAErrorTypeApiEAccess = -11, // access denied MEGAErrorTypeApiEExist = -12, // resource already exists MEGAErrorTypeApiEIncomplete = -13, // request incomplete MEGAErrorTypeApiEKey = -14, // cryptographic error MEGAErrorTypeApiESid = -15, // bad session ID MEGAErrorTypeApiEBlocked = -16, // resource administratively blocked MEGAErrorTypeApiEOverQuota = -17, // quote exceeded MEGAErrorTypeApiETempUnavail = -18, // resource temporarily not available MEGAErrorTypeApiETooManyConnections = -19, // too many connections on this resource MEGAErrorTypeApiEWrite = -20, // file could not be written to MEGAErrorTypeApiERead = -21, // file could not be read from MEGAErrorTypeApiEAppKey = -22, // invalid or missing application key MEGAErrorTypeApiESSL = -23, // invalid SSL key MEGAErrorTypeApiEgoingOverquota = -24, // Not enough quota MEGAErrorTypeApiERolledBack = -25, // A strongly-grouped request was rolled back MEGAErrorTypeApiEMFARequired = -26, // Multi-factor authentication required MEGAErrorTypeApiEMasterOnly = -27, ///< Access denied for sub-users (only for business accounts) MEGAErrorTypeApiEBusinessPastDue = -28, ///< Business account expired MEGAErrorTypeApiEPaywall = -29 ///< Over Disk Quota Paywall }; /** * @brief Api error code context. */ typedef NS_ENUM(NSInteger, MEGAErrorContext) { MEGAErrorContextDefault = 0, ///< Default error code context MEGAErrorContextDownload = 1, ///< Download transfer context. MEGAErrorContextImport = 2, ///< Import context. MEGAErrorContextUpload = 3 ///< Upload transfer context. }; /** * @brief User custom error details */ typedef NS_ENUM(NSInteger, MEGAUserErrorCode) { MEGAUserErrorCodeETDUnknown = -1, ///< Unknown state MEGAUserErrorCodeCopyrightSuspension = 4, /// Account suspended by copyright MEGAUserErrorCodeSuspendedAdminFullDisable = 5, MEGAUserErrorCodeETDSuspension = 7 ///< Account suspend by an ETD/ToS 'severe' }; /** * @brief Link custom error details */ typedef NS_ENUM(NSInteger, MEGALinkErrorCode) { MEGALinkErrorCodeUnknown = -1, ///< Unknown state MEGALinkErrorCodeUndeleted = 0, ///< Link is undeleted MEGALinkErrorCodeUndeletedDown = 1, ///< Link is deleted or down MEGALinkErrorCodeDownETD = 2 ///< Link is down due to an ETD specifically }; /** * @brief Provides information about an error. */ @interface MEGAError : NSObject /** * @brief The error code associated with this MEGAError. */ @property (readonly, nonatomic) MEGAErrorType type; /** * @brief Readable description of the error. */ @property (readonly, nonatomic) NSString *name; /** * @brief Value associated with the error * * Currently, this value is only useful when it is related to an MEGAErrorTypeApiEOverQuota * error related to a transfer. In that case, it's the number of seconds until * the more bandwidth will be available for the account. * * In any other case, this value will be 0 */ @property (readonly, nonatomic) long long value; /** * @brief YES if error has extra info * * @note Can return YES for: * - MEGARequestTypeFetchnodes with error MEGAErrorTypeApiENoent * - MEGARequestTypeGetPublicNode with error MEGAErrorTypeApiETooMany * - MEGARequestTypeImportLink with error MEGAErrorTypeApiETooMany * - [MEGATransferDelegate onTransferFinish:transfer:error:] with error MEGAErrorTypeApiETooMany */ @property (readonly, nonatomic, getter=hasExtraInfo) BOOL extraInfo; /** * @brief The user status * * This property contains valid value when extraInfo is YES * Possible values: * MEGAUserErrorCodeETDSuspension * * Otherwise, it value is MEGAUserErrorCodeETDUnknown * */ @property (readonly, nonatomic) MEGAUserErrorCode userStatus; /** * @brief The link status * * This property contains valid value when extraInfo is YES * Possible values: * MEGALinkErrorCodeUndeleted * MEGALinkErrorCodeUndeletedDown * MEGALinkErrorCodeDownETD * * Otherwise, it value is MEGALinkErrorCodeUnknown * */ @property (readonly, nonatomic) MEGALinkErrorCode linkStatus; /** * @brief Provides the error description associated with an error code. * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer. * * @param errorCode Error code for which the description will be returned. * @return Description associated with the error code. */ - (nullable NSString *)nameWithErrorCode:(NSInteger)errorCode; /** * @brief Provides the error description associated with an error code * given a certain context. * * @param errorCode Error code for which the description will be returned * @param context Context to provide a more accurate description (MEGAErrorContext) * @return Description associated with the error code */ + (nullable NSString *)errorStringWithErrorCode:(NSInteger)errorCode context:(MEGAErrorContext)context; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAEvent.h000066400000000000000000000066751516266226600203630ustar00rootroot00000000000000/** * @file MEGAEvent.h * @brief Provides information about an event * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM(NSUInteger, Event) { EventCommitDB = 0, EventAccountConfirmation = 1, EventChangeToHttps = 2, EventDisconnect = 3, EventAccountBlocked = 4, EventStorage = 5, EventNodesCurrent = 6, EventMediaInfoReady = 7, EventStorageSumChanged = 8, EventBusinessStatus = 9, EventKeyModified = 10, EventMiscFlagsReady = 11, #ifdef ENABLE_SYNC EventSyncsDisabled = 13, EventSyncsRestored = 14, #endif EventReqStatProgress = 15, EventReloading = 16, EventFatalError = 17, EventUpgradeSecurity = 18, EventDowngradeAttack = 19, EventConfirmUserEmail = 20, EventCreditCardExpiry = 21, }; typedef NS_ENUM(NSInteger, ReasonError) { ReasonErrorUnknown = -1, // Unknown reason ReasonErrorNoError = 0, // No error ReasonErrorFailureUnserializeNode = 1, // Failure when node is unserialized from DB ReasonErrorDBIOFailure = 2, // Input/output error at DB layer ReasonErrorDBFull = 3, // Failure at DB layer because disk is full ReasonErrorDBIndexOverflow = 4, // Index used to primary key at db overflow ReasonErrorNoJSCD = 5, // No JSON Sync Config Data ReasonErrorGenerateJSCD = 6, // JSON Sync Config Data has been regenerated ReasonErrorDBCorrupt = 7, // DB file is corrupted }; NS_ASSUME_NONNULL_BEGIN /** * @brief Provides information about an event * * Objects of this class aren't live, they are snapshots of the state of the event * when the object is created, they are immutable. */ NS_SWIFT_SENDABLE @interface MEGAEvent : NSObject /** * @brief The type of the event associated with the object */ @property (nonatomic, readonly) Event type; /** * @brief Text relative to this event */ @property (nonatomic, readonly, nullable) NSString *text; /// Number relative to this event /// /// For `EventStorageSumChanged`, this number is the new storage sum. /// /// For `EventReqStatProgress`, this number is the per mil progress of a long-running /// API operation, or -1 if there isn't any operation in progress. /// /// For `EventFatalError`, these values can be taken: /// - `ReasonErrorUnknown` = -1 -> Unknown reason /// - `ReasonErrorNoError` = 0 -> No error /// - `ReasonErrorFailureUnserializeNode` = 1 -> Failure when node is unserialized from DB /// - `ReasonErrorDBIOFailure` = 2 -> Input/output error at DB layer /// - `ReasonErrorDBFull` = 3 -> Failure at DB layer because disk is full /// - `ReasonErrorDBIndexOverflow` = 4 -> Index used to primary key at db overflow /// @property (nonatomic, readonly) NSInteger number; /// Readable description of the event @property (nonatomic, readonly, nullable) NSString *eventString; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAFolderInfo.h000066400000000000000000000037131516266226600213170ustar00rootroot00000000000000/** * @file MEGAFolderInfo.h * @brief Folder info * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Provides information about the contents of a folder * * This object is related to provide the results of the function [MEGASdk getFolderInfoWithNode:] * * Objects of this class aren't live, they are snapshots of the state of the contents of the * folder when the object is created, they are immutable. * */ @interface MEGAFolderInfo : NSObject /** * The number of file versions inside the folder * * The current version of files is not taken into account for the return value of this function */ @property (readonly, nonatomic) NSInteger versions; /** * The number of files inside the folder * * File versions are not counted for the return value of this function */ @property (readonly, nonatomic) NSInteger files; /** * The number of folders inside the folder */ @property (readonly, nonatomic) NSInteger folders; /** * The total size of files inside the folder * * File versions are not taken into account for the return value of this function */ @property (readonly, nonatomic) long long currentSize; /** * The total size of file versions inside the folder * * The current version of files is not taken into account for the return value of this function */ @property (readonly, nonatomic) long long versionsSize; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAGlobalDelegate.h000066400000000000000000000222741516266226600221260ustar00rootroot00000000000000/** * @file MEGAGlobalDelegate.h * @brief Delegate to get global events * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Protocol to get information about global events. * * You can implement this interface and start receiving events calling [MEGASdk addMEGAGlobalDelegate:]. * * MEGADelegate objects can also receive global events. */ @protocol MEGAGlobalDelegate @optional /** * @brief This function is called when there are new or updated contacts in the account. * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil * * @param api MEGASdk object connected to the account. * @param userList List that contains the new or updated contacts. */ - (void)onUsersUpdate:(MEGASdk *)api userList:(nullable MEGAUserList *)userList; /** * @brief This function is called when there are new or updated user alerts in the account * * The SDK retains the ownership of the MEGAUserAlertList in the second parameter. The list and all the * MEGAUserAlert objects that it contains will be valid until this function returns. If you want to save the * list, use [MEGAUserAlertList clone]. If you want to save only some of the MEGAUserAlert objects, use [MEGAUserAlert clone] * for those objects. * * When there is a problem parsing the incoming information from the server or the full * account is reloaded or a large number of server notifications arrives at once, the second * parameter will be nil. * * @param api MEGASdk object connected to the account * @param userAlertList List that contains the new or updated contacts */ - (void)onUserAlertsUpdate:(MEGASdk *)api userAlertList:(nullable MEGAUserAlertList *)userAlertList; /** * @brief This function is called when there are new or updated nodes in the account. * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be nil. * * @param api MEGASdk object connected to the account. * @param nodeList List that contains the new or updated nodes. */ - (void)onNodesUpdate:(MEGASdk *)api nodeList:(nullable MEGANodeList *)nodeList; /** * @brief This function is called when a Set has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil. * * @param api MEGASdk object connected to the account * @param sets Array that contains the new or updated Sets */ - (void)onSetsUpdate:(MEGASdk *)api sets:(nullable NSArray *)sets; /** * @brief This function is called when a SetElement has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be nil. * * @param api MEGASdk object connected to the account * @param setElements Array that contains the new or updated Set-Elements */ - (void)onSetElementsUpdate:(MEGASdk *)api setElements:(nullable NSArray *)setElements; /** * @brief This function is called when the account has been updated (confirmed/upgraded/downgraded) * * The usage of this delegate to handle the external account confirmation is deprecated. * Instead, you should use [MEGAGlobalDelegate onEvent:event:]. * * @param api MEGASdk object connected to the account */ - (void)onAccountUpdate:(MEGASdk *)api; /** * @brief This function is called when there are new or updated contact requests in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be nil. * * @param api MEGASdk object connected to the account * @param contactRequestList List that contains the new or updated contact requests */ - (void)onContactRequestsUpdate:(MEGASdk *)api contactRequestList:(nullable MEGAContactRequestList *)contactRequestList; /** * @brief This function is called when an inconsistency is detected in the local cache. * * You should call [MEGASdk fetchNodes] when this callback is received. * * @param api MEGASdk object connected to the account. */ - (void)onReloadNeeded:(MEGASdk *)api; /** * The details about the event, like the type of event and optionally any * additional parameter, is received in the \c params parameter. * * You can check the type of event by calling [MEGAEvent type] * * Currently, the following type of events are notified: * * - EventCommitDB: when the SDK commits the ongoing DB transaction. * This event can be used to keep synchronization between the SDK cache and the * cache managed by the app thanks to the sequence number. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: sequence number recorded by the SDK when this event happened * * - EventAccountConfirmation: when a new account is finally confirmed * by the user by confirming the signup link. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: email address used to confirm the account * * - EventDisconnect: when the SDK performs a disconnect to reset all the * existing open-connections, since they have become unusable. It's recommended that the app * receiving this event reset its connections with other servers, since the disconnect * performed by the SDK is due to a network change or IP addresses becoming invalid. * * - EventAccountBlocked: when the account get blocked, typically because of * infringement of the Mega's terms of service repeatedly. This event is followed by an automatic * logout. * * Valid data in the MegaEvent object received in the callback: * - [MEGAEvent text]: message to show to the user. * - [MEGAEvent number]: code representing the reason for being blocked. * 200: suspension message for any type of suspension, but copyright suspension. * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. * 700: the account is supended for Weak Account Protection. * * - EventStorage: when the status of the storage changes. * * For this event type, [MEGAEvent number] provides the current status of the storage * * There are four possible storage states: * - StorageStateGreen = 0 * There are no storage problems * * - StorageStateOrange = 1 * The account is almost full * * - StorageStateRed = 2 * The account is full. Uploads have been stopped * * - StorageStateChange = 3 * There is a possible significant change in the storage state. * It's needed to call [MEGASdk getAccountDetails] to check the storage status. * After calling it, this callback will be called again with the corresponding * state if there is really a change. * * - StorageStatePaywall = 4 * The account has been full for a long time. Now most of actions are disallowed. * You will need to call [MEGASdk getUserData] before retrieving the overquota deadline/warnings * timestamps. * * - EventNodesCurrent: when all external changes have been received * * - EventMediaInfoReady: when codec-mappings have been received * * - EventBusinessStatus: when the status of a business account has changed. * * For this event type, [MEGAEvent number] provides the new business status. * * The posible values are: * - BusinessStatusExpired = -1 * - BusinessStatusInactive = 0 * - BusinessStatusActive = 1 * - BusinessStatusGracePeriod = 2 * * - EventKeyModified: when the key of a user has changed. * * For this event type, [MEGAEvent handle] provides the handle of the user whose key has been modified. * For this event type, [MEGAEvent number] provides type of key that has been modified. * * The posible values are: * - Public chat key (Cu25519) = 0 * - Public signing key (Ed25519) = 1 * - Public RSA key = 2 * - Signature of chat key = 3 * - Signature of RSA key = 4 * * - EventMiscFlagsReady: when the miscellaneous flags are available/updated. * * - EventReqStatProgress: Provides the per mil progress of a long-running API operation * in [MEGAEvent number], or -1 if there isn't any operation in progress. * * - EventReloading: when the API server has forced a full reload. The app should show a * similar UI to the one displayed during the initial load (fetchnodes). * * @param api MEGASdk object connected to the account * @param event Details about the event */ - (void)onEvent:(MEGASdk *)api event:(MEGAEvent *)event; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAHandleList.h000066400000000000000000000025741516266226600213230ustar00rootroot00000000000000/** * @file MEGAHandleList.h * @brief List of MegaHandle * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief List of MegaHandle objects * */ @interface MEGAHandleList : NSObject /** * @brief The number of handles in the list */ @property (readonly, nonatomic) NSUInteger size; /** * @brief Add new handle to handleList * @param handle to be added. */ - (void)addMegaHandle:(uint64_t)handle; /** * @brief Returns the MegaHandle at the position index in the MEGAHandleList * * * If the index is >= the size of the list, this function returns INVALID_HANDLE. * * @param index Position of the MegaHandle that we want to get for the list * @return handle at the position iindex in the list */ - (uint64_t)megaHandleAtIndex:(NSUInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAIntegerList.h000066400000000000000000000023271516266226600215210ustar00rootroot00000000000000/** * @file MEGAIntegerList.h * @brief List of integers * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import /** * @brief List of integers * * Objects of this class are immutable. */ @interface MEGAIntegerList : NSObject /** * @brief The number of integers in the list */ @property (nonatomic, readonly) NSInteger size; /** * @brief Returns the integer at the position index in the MEGAIntegerList * * If the index is >= the size of the list, this function returns -1. * * @param index Position of the integer that we want to get for the list * @return Integer at the position index in the list */ - (int64_t)integerAtIndex:(NSInteger)index; @end sdk-10.11.0/bindings/ios/include/MEGALogLevel.h000066400000000000000000000022031516266226600207720ustar00rootroot00000000000000/** * @file MEGALogLevel..h * @brief Log levels * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ typedef NS_ENUM (NSInteger, MEGALogLevel) { MEGALogLevelFatal = 0, MEGALogLevelError, // Error information but will continue application to keep running. MEGALogLevelWarning, // Information representing errors in application but application will keep running MEGALogLevelInfo, // Mainly useful to represent current progress of application. MEGALogLevelDebug, // Informational logs, that are useful for developers. Only applicable if DEBUG is defined. MEGALogLevelMax }; sdk-10.11.0/bindings/ios/include/MEGALoggerDelegate.h000066400000000000000000000037531516266226600221460ustar00rootroot00000000000000/** * @file MEGALoggerDelegate.h * @brief Delegate to get SDK logs * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGALogLevel.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Protocol to receive information about SDK logs * * You can implement this class and pass an object of your subclass to [MEGASdk setLogObject] * to receive SDK logs. You will have to use also [MEGASdk setLogLevel] to select the level of * the logs that you want to receive. * */ @protocol MEGALoggerDelegate @optional /** * @brief This function will be called with all logs with level <= your selected * level of logging (by default it is MEGALogLevelInfo). * * @param time Readable string representing the current time. * @param logLevel Log level of this message. * * Valid values are: * - MEGALogLevelFatal = 0 * - MEGALogLevelError = 1 * - MEGALogLevelWarning = 2 * - MEGALogLevelInfo = 3 * - MEGALogLevelDebug = 4 * - MEGALogLevelMax = 5 * * @param source Location where this log was generated. * * For logs generated inside the SDK, this will contain the source file and the line of code. * * @param message Log message. * * */ - (void)logWithTime:(NSString*)time logLevel:(MEGALogLevel)logLevel source:(NSString *)source message:(NSString *)message #ifdef ENABLE_LOG_PERFORMANCE directMessages:(NSArray *)directMessages numberMessages:(NSInteger)numberMessages #endif ; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGANetworkConnectivityTestResults.h000066400000000000000000000043571516266226600255470ustar00rootroot00000000000000/** * @file MEGANetworkConnectivityTestResults.h * @brief Container to store the network connectivity test results. * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Enumeration of possible network connectivity test results. */ typedef NS_ENUM(NSInteger, MEGANetworkConnectivityTestResult) { MEGANetworkConnectivityTestPass = 0, MEGANetworkConnectivityTestFail = 1, MEGANetworkConnectivityTestNetUnreachable = 2, }; /** * @brief Container class to store the results of network connectivity tests. */ @interface MEGANetworkConnectivityTestResults : NSObject /** * @brief Get the result of testing UDP communication over IPv4. * * @return The test result as one of the MEGANetworkConnectivityTestResult values. */ - (MEGANetworkConnectivityTestResult)ipv4UDP; /** * @brief Get the result of testing DNS resolution over IPv4. * * @return The test result as one of the MEGANetworkConnectivityTestResult values. */ - (MEGANetworkConnectivityTestResult)ipv4DNS; /** * @brief Get the result of testing UDP communication over IPv6. * * @return The test result as one of the MEGANetworkConnectivityTestResult values. */ - (MEGANetworkConnectivityTestResult)ipv6UDP; /** * @brief Get the result of testing DNS resolution over IPv6. * * @return The test result as one of the MEGANetworkConnectivityTestResult values. */ - (MEGANetworkConnectivityTestResult)ipv6DNS; /** * @brief Create a copy of this network connectivity test results object. * * The caller takes ownership of the returned object. * * @return A new instance of MEGANetworkConnectivityTestResults. */ - (MEGANetworkConnectivityTestResults *)copy; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGANode.h000066400000000000000000000432571516266226600201640ustar00rootroot00000000000000/** * @file MEGANode.h * @brief Represents a node (file/folder) in the MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "PasswordNodeData.h" #import "MEGACreditCardNodeData.h" typedef NS_ENUM (NSInteger, MEGANodeType) { MEGANodeTypeUnknown = -1, MEGANodeTypeFile = 0, MEGANodeTypeFolder, MEGANodeTypeRoot, MEGANodeTypeIncoming, MEGANodeTypeRubbish }; typedef NS_ENUM(NSInteger, MEGANodeLabel) { MEGANodeLabelUnknown = 0, MEGANodeLabelRed, MEGANodeLabelOrange, MEGANodeLabelYellow, MEGANodeLabelGreen, MEGANodeLabelBlue, MEGANodeLabelPurple, MEGANodeLabelGrey }; typedef NS_ENUM(NSUInteger, MEGANodeChangeType) { MEGANodeChangeTypeRemoved = 0x01, MEGANodeChangeTypeAttributes = 0x02, MEGANodeChangeTypeOwner = 0x04, MEGANodeChangeTypeTimestamp = 0x08, MEGANodeChangeTypeFileAttributes = 0x10, MEGANodeChangeTypeInShare = 0x20, MEGANodeChangeTypeOutShare = 0x40, MEGANodeChangeTypeParent = 0x80, MEGANodeChangeTypePendingShare = 0x100, MEGANodeChangeTypePublicLink = 0x200, MEGANodeChangeTypeNew = 0x400, MEGANodeChangeTypeName = 0x800, MEGANodeChangeTypeFavourite = 0x1000, MEGANodeChangeTypeSensitive = 0x4000, MEGANodeChangeTypeDescription = 0x10000, MEGANodeChangeTypeTags = 0x20000 }; typedef NS_ENUM (NSInteger, MEGANodeFormatType) { MEGANodeFormatTypeUnknown = 0, MEGANodeFormatTypePhoto, MEGANodeFormatTypeAudio, MEGANodeFormatTypeVideo, MEGANodeFormatTypeDocument, MEGANodeFormatTypePdf, MEGANodeFormatTypePresentation, MEGANodeFormatTypeArchive, MEGANodeFormatTypeProgram, MEGANodeFormatTypeMisc, MEGANodeFormatTypeSpreadsheet, MEGANodeFormatTypeAllDocs, MEGANodeFormatTypeOthers, MEGANodeFormatTypeAllVisualMedia }; @class MEGAStringList; NS_ASSUME_NONNULL_BEGIN /** * @brief Represents a node (file/folder) in the MEGA account. * * It allows to get all data related to a file/folder in MEGA. It can be also used * to start SDK requests ([MEGASdk renameNode:newName:], [MEGASdk moveNode:newParent:], etc.) * * Objects of this class aren't live, they are snapshots of the state of a node * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can inspect the MEGA filesystem and get these objects using * [MEGASdk childrenForParent:], [MEGASdk childNodeForParent:name:] and other MEGASdk functions. * */ @interface MEGANode : NSObject /** * @brief Type of the node. * * Valid values are: * - MEGANodeTypeUnknown = -1, * Unknown node type. * * - MEGANodeTypeFile = 0, * The MEGANode object represents a file in MEGA. * * - MEGANodeTypeFolder = 1 * The MEGANode object represents a folder in MEGA. * * - MEGANodeTypeRoot = 2 * The MEGANode object represents root of the MEGA Cloud Drive. * * - MEGANodeTypeIncoming = 3 * The MEGANode object represents root of the MEGA Inbox. * * - MEGANodeTypeRubbish = 4 * The MEGANode object represents root of the MEGA Rubbish Bin. * */ @property (readonly, nonatomic) MEGANodeType type; /** * @brief Name of the node. * * The name is only valid for nodes of type MEGANodeTypeFile or MEGANodeTypeFolder. * For other MEGANode types, the name is undefined. * */ @property (readonly, nonatomic, nullable) NSString *name; /** * @brief The fingerprint (Base64-encoded) of the node * * Only files have a fingerprint, and there could be files without it. * If the node doesn't have a fingerprint, this funtion returns nil * * Base64-encoded fingerprint of the node, or nil it the node doesn't have a fingerprint. */ @property (readonly, nonatomic, nullable) NSString *fingerprint; /** * @brief Duration of the node for audio/video files, in seconds. -1 if not set. */ @property (readonly, nonatomic) NSInteger duration; /** * @brief Width of the node for video files, in pixels. -1 if not set. */ @property (readonly, nonatomic) NSInteger width; /** * @brief Height of the node for video files, in pixels. -1 if not set. */ @property (readonly, nonatomic) NSInteger height; /** * @brief ShortCode of the node for video files. -1 if not set. */ @property (readonly, nonatomic) NSInteger shortFormat; /** * @brief VideoCodecId of the node for video files. -1 if not set. */ @property (readonly, nonatomic) NSInteger videoCodecId; /** * @brief Get the attribute of the node representing if node is marked as favourite. * * @return YES if node is marked as favourite, otherwise return NO (attribute is not set). */ @property (readonly, nonatomic, getter=isFavourite) BOOL favourite; /** * @brief Get the attribute of the node representing if node is marked as sensitive. * * @return YES if node is marked as sensitive, otherwise return NO (attribute is not set). */ @property (readonly, nonatomic) BOOL isMarkedSensitive; /** * @brief Get the attribute of the node representing the description * * @return Node description */ @property (readonly, nonatomic, nullable) NSString *description; /** * @brief Get the attribute of the node representing its label. * * @return The label of the node, valid values are: * - MEGANodeLabelUnknown = 0 * - MEGANodeLabelRed = 1 * - MEGANodeLabelOrange = 2 * - MEGANodeLabelYellow = 3 * - MEGANodeLabelGreen = 4 * - MEGANodeLabelBlue = 5 * - MEGANodeLabelPurple = 6 * - MEGANodeLabelGrey = 7 */ @property (readonly, nonatomic) MEGANodeLabel label; /** * @brief Attribute of the node representing the latitude coordinate in its decimal * degree notation, or nil if this attribute is not set. * * The purpose of this attribute is to store the latitude coordinate where a photo was taken. * */ @property (readonly, nonatomic, nullable) NSNumber *latitude; /** * @brief Attribute of the node representing the longitude coordinate in its decimal * degree notation, or nil if this attribute is not set. * * The purpose of this attribute is to store the longitude coordinate where a photo was taken. * */ @property (readonly, nonatomic, nullable) NSNumber *longitude; /** * @brief Handle of this MEGANode in a Base64-encoded string. */ @property (readonly, nonatomic, nullable) NSString *base64Handle; /** * @brief Size of the node. * * The value is only valid for nodes of type MEGANodeTypeFile. * */ @property (readonly, nonatomic, nullable) NSNumber *size; /** * @brief Creation time of the node in MEGA (in seconds since the epoch). * * The value is only valid for nodes of type MEGANodeTypeFile or MEGANodeTypeFolder. * */ @property (readonly, nonatomic, nullable) NSDate *creationTime; /** * @brief Modification time of the file that was uploaded to MEGA (in seconds since the epoch). * * The value is only valid for nodes of type MEGANodeTypeFile. * */ @property (readonly, nonatomic, nullable) NSDate *modificationTime; /** * @brief Public link creation time of the file to MEGA (in seconds since the epoch). * * The value is only valid for nodes of type MEGANodeTypeFile. * */ @property (readonly, nonatomic, nullable) NSDate *publicLinkCreationTime; /** * @brief Handle to identify this MEGANode. * * You can use [MEGASdk nodeForHandle:] to recover the node later. * */ @property (readonly, nonatomic) uint64_t handle; /** * The handle of the previous parent of this node. * * This attribute is set when nodes are moved to the Rubbish Bin to * ease their restoration. If the attribute is not set for the node, * this function returns MegaApi::INVALID_HANDLE */ @property (readonly, nonatomic) uint64_t restoreHandle; /** * @brief The handle of the parent node * * You can use [MEGASdk nodeForHandle:] to recover the node later. * */ @property (readonly, nonatomic) uint64_t parentHandle; /** * @brief The expiration time of a public link (in seconds since the epoch), if any * * 0 for non-expire links, and -1 if the MEGANode is not exported. */ @property (readonly, nonatomic) int64_t expirationTime; /** * @brief The public handle of an exported node. If the MEGANode * has not been exported, it returns UNDEF. * * Only exported nodes have a public handle. * */ @property (readonly, nonatomic) uint64_t publicHandle; /** * @brief A public node for the exported node. If the MEGANode has not been * exported or it has expired, then it returns nil. */ @property (readonly, nonatomic, nullable) MEGANode *publicNode; /** * @brief The URL for the public link of the exported node. If the MEGANode * has not been exported, it returns nil. */ @property (readonly, nonatomic, nullable) NSString *publicLink; /** * @brief The handle of the owner of the node. */ @property (readonly, nonatomic) uint64_t owner; /** * @brief The device id stored as a Node attribute of a Backup folder. * It will be an empty string for other nodes. */ @property (readonly, nonatomic, nullable) NSString *deviceId; /** * @breif Get a list of tags from a node. */ @property (readonly, nonatomic, nullable) MEGAStringList *tags; /** * @brief Returns a BOOL value that indicates if the node represents a file (type == MEGANodeTypeFile) * @return YES if the node represents a file, otherwise NO. */ - (BOOL)isFile; /** * @brief Returns a BOOL value that indicates if the node represents a folder or a root node. * * @return YES if the node represents a folder or a root node, otherwise NO. */ - (BOOL)isFolder; /** * @brief Returns a BOOL value that indicates if the node has been removed from the MEGA account. * * This value is only useful for nodes notified by [MEGADelegate onNodesUpdate:nodeList:] or * [MEGAGlobalDelegate onNodesUpdate:nodeList:] that can notify about deleted nodes. * * In other cases, the return value of this function will be always NO. * * @return YES if the node has been removed from the MEGA account, otherwise NO */ - (BOOL)isRemoved; /** * @brief Returns YES if this node has an specific change * * This value is only useful for nodes notified by [MEGADelegate onNodesUpdate:nodeList:] or * [MEGAGlobalDelegate onNodesUpdate:nodeList:] that can notify about node modifications. * * In other cases, the return value of this function will be always NO. * * @param changeType The type of change to check. It can be one of the following values: * * - MEGANodeChangeTypeRemoved = 0x01 * Check if the node is being removed * * - MEGANodeChangeTypeAttributes = 0x02 * Check if an attribute of the node has changed, usually the namespace name * * - MEGANodeChangeTypeOwner = 0x04 * Check if the owner of the node has changed * * - MEGANodeChangeTypeTimestamp = 0x08 * Check if the modification time of the node has changed * * - MEGANodeChangeTypeFileAttributes = 0x10 * Check if file attributes have changed, usually the thumbnail or the preview for images * * - MEGANodeChangeTypeInShare = 0x20 * Check if the node is a new or modified inshare * * - MEGANodeChangeTypeOutShare = 0x40 * Check if the node is a new or modified outshare * * - MEGANodeChangeTypeParent = 0x80 * Check if the parent of the node has changed * * - MEGANodeChangeTypePendingShare = 0x100 * Check if the pending share of the node has changed * * - MEGANodeChangeTypePublicLink = 0x200 * Check if the public link of the node has changed * * - MEGANodeChangeTypeNew = 0x400 * Check if the node is new * * @return YES if this node has an specific change */ - (BOOL)hasChangedType:(MEGANodeChangeType)changeType; /** * @brief Returns a bit field with the changes of the node * * This value is only useful for nodes notified by [MEGADelegate onNodesUpdate:nodeList:] or * [MEGAGlobalDelegate onNodesUpdate:nodeList:] that can notify about node modifications. * * @return The returned value is an OR combination of these flags: * * * - MEGANodeChangeTypeRemoved = 0x01 * The node is being removed * * - MEGANodeChangeTypeAttributes = 0x02 * An attribute of the node has changed, usually the namespace name * * - MEGANodeChangeTypeOwner = 0x04 * The owner of the node has changed * * - MEGANodeChangeTypeTimestamp = 0x08 * The modification time of the node has changed * * - MEGANodeChangeTypeFileAttributes = 0x10 * File attributes have changed, usually the thumbnail or the preview for images * * - MEGANodeChangeTypeInShare = 0x20 * The node is a new or modified inshare * * - MEGANodeChangeTypeOutShare = 0x40 * The node is a new or modified outshare * * - MEGANodeChangeTypeParent = 0x80 * Check if the parent of the node has changed * * - MEGANodeChangeTypePendingShare = 0x100 * Check if the pending share of the node has changed * * - MEGANodeChangeTypePublicLink = 0x200 * Check if the public link of the node has changed * * - MEGANodeChangeTypeNew = 0x400 * Check if the node is new */ - (MEGANodeChangeType)getChanges; /** * @brief Returns YES if the node has an associated thumbnail * @return YES if the node has an associated thumbnail, otherwise NO */ - (BOOL)hasThumbnail; /** * @brief Returns YES if the node has an associated preview * @return YES if the node has an associated preview, otherwise NO */ - (BOOL)hasPreview; /** * @brief Returns YES if this is a public node * * Only MEGANode objects generated with MegaApi::getPublicMegaNode * will return YES * * @return YES if this is a public node, otherwise NO */ - (BOOL)isPublic; /** * @brief Returns a BOOL value that indicates if the node is a shared * * For nodes that are being shared, you can get a list of MEGAShare * objects using [MEGASdk outShares], or a list of MEGANode objects * using [MEGASdk inShares] * * @return YES is the MegaNode is being shared, otherwise NO * @note Exported nodes (public link) are not considered to be shared nodes */ - (BOOL)isShared; /** * @brief Check if the MEGANode is being shared with other users * * For nodes that are being shared, you can get a list of MEGAShare * objects using [MEGASdk outShares] * * @return YES is the MEGANode is being shared, otherwise NO */ - (BOOL)isOutShare; /** * @brief Check if a MEGANode belong to another MEGAUser, but it is shared with you * * For nodes that are being shared, you can get a list of MEGANode * objects using [MEGASdk inShares] * * @return YES is the MEGANode is being shared, otherwise NO */ - (BOOL)isInShare; /** * @brief Returns YES if the node has been exported (has a public link) * * Public links are created by calling [MEGASdk exportNode] * * @return YES if this is an exported node, otherwise NO */ - (BOOL)isExported; /** * @brief Returns YES if the node has been exported (has a temporal public link) * and the related public link has expired * * Public links are created by calling [MEGASdk exportNode] * * @return YES if the public link has expired, otherwise NO */ - (BOOL)isExpired; /** * @brief Returns YES if this the node has been exported * and the related public link has been taken down * * Public links are created by calling [MEGASdk exportNode] * * @return YES if the public link has been taken down, otherwise NO */ - (BOOL)isTakenDown; /** * @brief Returns true if this MEGANode is a private node from a foreign account * * Only MEGANodes created with [MEGASdk createForeignFileNode and MegaApi::createForeignFolderNode * returns true in this function. * * @return YES if this node is a private node from a foreign account */ - (BOOL)isForeign; /** * @brief Returns true if the node key is decrypted * * For nodes in shared folders, there could be missing keys. Also, faulty * clients might create invalid keys. In those cases, the node's key might * not be decrypted successfully. * * @return True if the node key is decrypted */ - (BOOL)isNodeKeyDecrypted; + (nullable NSString *)stringForNodeLabel:(MEGANodeLabel)nodeLabel; #pragma mark - Password Manager /** * @brief Returns true if this MEGANode is a Password Manager Node * * A node is considered a Password Manager Node if Password Manager Base is its * ancestor and it's not a Password Manager Node Folder. * * @return true if this node is a Password Manager Node, false otherwise. * In case node doesn't exists this method will also returns false. */ - (BOOL)isPasswordManagerNode; /** * @brief Returns true if this MEGANode is a Password Node * * Only MEGANodes created with [MEGASdk createPasswordNodeWithName:data:parent:delegate:] return true in this function. * * @return true if this node is a Password Node */ - (BOOL)isPasswordNode; /** * @brief Returns true if this MEGANode is a Credit Card Node * * @note: A Credit Card Node is a Password Manager Node with credit card information. * * Only MEGANodes created with [MEGASdk createCreditCardNodeWithName:data:parent:delegate:] return true in this function. * * @return true if this node is a Credit Card Node */ - (BOOL)isCreditCardNode; /** * @brief The Password Node Data if the node is a Password Node. */ @property (readonly, nonatomic, nullable) PasswordNodeData *passwordNodeData; /** * @brief The Credit Card Node Data if the node is a Credit Card Node. */ @property (readonly, nonatomic, nullable) MEGACreditCardNodeData *creditCardNodeData; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGANodeList.h000066400000000000000000000030421516266226600210040ustar00rootroot00000000000000/** * @file MEGANodeList.h * @brief List of MEGANode objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGANode.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGANode objects. * * Objects of this class are immutable. * * @see [MEGASdk childrenForParent:], [MEGASdk inShares:]. */ NS_SWIFT_SENDABLE @interface MEGANodeList : NSObject /** * @brief The number of MEGANode objects in the list. */ @property (readonly, nonatomic) NSInteger size; /** * @brief Add new node to nodeList * @param node to be added. The node inserted is a copy from 'node' */ - (void)addNode:(MEGANode *)node; /** * @brief Returns the MEGANode at the position index in the MEGANodeList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGANode that we want to get for the list. * @return MEGANode at the position index in the list. */ - (nullable MEGANode *)nodeAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGANotification.h000066400000000000000000000073021516266226600217140ustar00rootroot00000000000000/** * @file MEGANotification.h * @brief Represents a notification in MEGA * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Container class to store all information of a notification. * * - ID. * - Title. * - Description. * - Image name for the notification. * - Default static path for the notification image. * - Timestamp of when the notification became available to the user. * - Timestamp of when the notification will expire. * - Whether it should show a banner or only render a notification. * - Metadata for the first call-to-action ("link" and "text" attributes). * - Metadata for the second call-to-action ("link" and "text" attributes). * * Objects of this class are immutable. */ @interface MEGANotification : NSObject /** * @brief Get the ID associated with this notification. * * @return the ID associated with this notification. */ @property (nonatomic, readonly) NSUInteger identifier; /** * @brief Get the title of this notification. * * @return the title of this notification. */ @property (nonatomic, readonly, nullable) NSString *title; /** * @brief Get the description for this notification. * * @return the description for this notification. */ @property (nonatomic, readonly, nullable) NSString *description; /** * @brief Get the image name for this notification. * * @return the image name for this notification. */ @property (nonatomic, readonly, nullable) NSString *imageName; /** * @brief Get the default static path of the image associated with this notification. * * @return the default static path of the image associated with this notification. */ @property (nonatomic, readonly, nullable) NSString *imagePath; /** * @brief Get the name of the icon for this notification. * * @return the name of the icon for this notification. */ @property (nonatomic, readonly, nullable) NSString *iconName; /** * @brief Get the date of when the notification became available to the user. * * @return the date of when the notification became available to the user. */ @property (nonatomic, readonly, nullable) NSDate *startDate; /** * @brief Get the date of when the notification will expire. * * @return the date of when the notification will expire. */ @property (nonatomic, readonly, nullable) NSDate *endDate; /** * @brief Report whether it should show a banner or only render a notification. * * @return whether it should show a banner or only render a notification. */ @property (nonatomic, readonly, getter=shouldShowBanner) BOOL showBanner; /** * @brief Get metadata for the first call to action, represented by attributes "link" and "text", * and their corresponding values. * * @return metadata for the first call to action. */ @property (nonatomic, readonly, nullable) NSDictionary *firstCallToAction; /** * @brief Get metadata for the second call-to-action, represented by attributes "link" and "text", * and their corresponding values. * * @return metadata for the second call-to-action. */ @property (nonatomic, readonly, nullable) NSDictionary *secondCallToAction; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGANotificationList.h000066400000000000000000000033221516266226600225460ustar00rootroot00000000000000/** * @file MEGANotificationList.h * @brief List of MEGANotification objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @class MEGANotification; /** * @brief List of MEGANotification objects * * A MEGANotificationList has the ownership of the MEGANotification objects that it contains, so they will be * only valid until the MEGANotificationList is deleted. * * Objects of this class are immutable. */ @interface MEGANotificationList : NSObject /** * @brief Returns the number of MEGANotification objects in the list */ @property (readonly, nonatomic) NSInteger size; /** * @brief Returns the MEGANotification at position index in the list * * The MEGANotificationList retains the ownership of the returned MEGANotification. It will be only valid until * the MEGANotificationList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param index Position of the MEGANotification that we want to get from the list * @return MEGANotification at position index in the list */ - (nullable MEGANotification *)notificationAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAPaymentMethod.h000066400000000000000000000026561516266226600220530ustar00rootroot00000000000000/** * @file MEGAPaymentMethod.h * @brief Payment methods * * (c) 2021- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ typedef NS_ENUM(NSInteger, MEGAPaymentMethod) { MEGAPaymentMethodBalance = 0, MEGAPaymentMethodPaypal = 1, MEGAPaymentMethodItunes = 2, MEGAPaymentMethodGoogleWallet = 3, MEGAPaymentMethodBitcoin = 4, MEGAPaymentMethodUnionPay = 5, MEGAPaymentMethodFortumo = 6, MEGAPaymentMethodStripe = 7, MEGAPaymentMethodCreditCard = 8, MEGAPaymentMethodCentili = 9, MEGAPaymentMethodPaysafeCard = 10, MEGAPaymentMethodAstropay = 11, MEGAPaymentMethodReserved = 12, MEGAPaymentMethodWindowsStore = 13, MEGAPaymentMethodTpay = 14, MEGAPaymentMethodDirectReseller = 15, MEGAPaymentMethodECP = 16, MEGAPaymentMethodSabadell = 17, MEGAPaymentMethodHuaweiWallet = 18, MEGAPaymentMethodStripe2 = 19, MEGAPaymentMethodWireTransfer = 999 }; sdk-10.11.0/bindings/ios/include/MEGAPricing.h000066400000000000000000000116731516266226600206670ustar00rootroot00000000000000/** * @file MEGAPricing.h * @brief Details about pricing plans * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAAccountDetails.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Details about pricing plans * * Use [MEGASdk pricing] to get the pricing plans to upgrade MEGA accounts */ @interface MEGAPricing : NSObject /** * @brief Number of available products to upgrade the account. */ @property (readonly, nonatomic) NSInteger products; /** * @brief Get the handle of a product. * @param index Product index (from 0 to [MEGAPricing products]). * @return Handle of the product. * @see [MEGASdk getPaymentIdForProductHandle:]. */ - (uint64_t)handleAtProductIndex:(NSInteger)index; /** * @brief Get the PRO level associated with the product. * @param index Product index (from 0 to [MEGAPricing products]). * @return PRO level associated with the product: * Valid values are: * - MEGAAccountTypeFree = 0 * - MEGAAccountTypeProI = 1 * - MEGAAccountTypeProII = 2 * - MEGAAccountTypeProIII = 3 * - MEGAAccountTypeLite = 4 * - MEGAAccountTypeStarter = 11 * - MEGAAccountTypeBasic = 12 * - MEGAAccountTypeEssential = 13 * - MEGAAccountTypeBusiness = 100 * - MEGAAccountTypeProFlexi = 101 */ - (MEGAAccountType)proLevelAtProductIndex:(NSInteger)index; /** * @brief Get the number of GB of storage associated with the product. * @param index Product index (from 0 to [MEGAPricing products]). * @return number of GB of storage. */ - (NSInteger)storageGBAtProductIndex:(NSInteger)index; /** * @brief Get the number of GB of bandwidth associated with the product. * @param index Product index (from 0 to [MEGAPricing products]). * @return number of GB of bandwidth. */ - (NSInteger)transferGBAtProductIndex:(NSInteger)index; /** * @brief Get the duration of the product (in months). * @param index Product index (from 0 to [MEGAPricing products]). * @return duration of the product (in months). */ - (NSInteger)monthsAtProductIndex:(NSInteger)index; /** * @brief Get the price of the product (in cents). * @param index Product index (from 0 to [MEGAPricing products]). * @return Price of the product (in cents). */ - (NSInteger)amountAtProductIndex:(NSInteger)index; /** * @brief Get the price in the local currency (in cents) * @param index Product index (from 0 to MegaPricing::getNumProducts) * @return Price of the product (in cents) */ - (NSInteger)localPriceAtProductIndex:(NSInteger)index; /** * @brief Get a description of the product * * @param index Product index (from 0 to [MEGAPricing products]) * @return Description of the product */ - (nullable NSString *)descriptionAtProductIndex:(NSInteger)index; /** * @brief Get the iOS ID of the product * * @param index Product index (from 0 to [MEGAPricing products]) * @return iOS ID of the product */ - (nullable NSString *)iOSIDAtProductIndex:(NSInteger)index; /** * @brief Get trial duration in days * * The returned value will be 0 if the plan is not eligible for trial. * * @param index Product index (from 0 to [MEGAPricing products]) * @return Trial duration in days */ - (unsigned int)trialDurationInDaysAtProductIndex:(NSInteger)index; /** * @brief Check whether the product has a mobile offer * * Determines if the specified product includes an associated mobile offer. * * @param index Product index (from 0 to [MEGAPricing products]) * @return True if the product has a mobile offer, false otherwise */ - (BOOL)hasMobileOffersAtProductIndex:(NSInteger)index; /** * @brief Get the mobile offer identifier * * Returns the identifier of the mobile offer associated with the given * product. * * If the product does not have a mobile offer, this method returns a empty * string. * * @param index Product index (from 0 to [MEGAPricing products]) * @return A null-terminated string containing the mobile offer ID */ - (nullable NSString *)mobileOfferIdAtProductIndex:(NSInteger)index; /** * @brief Check whether the mobile offer title should be used * * Possible values are: * - false: The mobile offer title should not be displayed. * - true: The mobile offer title should be displayed. * * If the product does not have a mobile offer, this method returns false. * * @param index Product index (from 0 to [MEGAPricing products]) * @return True if the mobile offer title should be displayed, false * otherwise */ - (BOOL)hasMobileOfferUatAtProductIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAPushNotificationSettings.h000066400000000000000000000106631516266226600243010ustar00rootroot00000000000000/** * @file MEGAPushNotificationSettings.h * @brief Push Notification related MEGASdk methods. * * (c) 2019- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Provides information about the notification settings * * The notifications can be configured: * * 1. Globally * 1.1. Mute all notifications * 1.2. Notify only during a schedule: from one time to another time of the day, specifying the timezone of reference * 1.3. Do Not Disturb for a period of time: it overrides the schedule, if any (no notification will be generated) * * 2. Chats: Mute for all chats notifications * * 3. Per chat: * 2.1. Mute all notifications from the specified chat * 2.2. Always notify for the specified chat * 2.3. Do Not Disturb for a period of time for the specified chat * * @note Notification settings per chat override any global notification setting. * @note The DND mode per chat is not compatible with the option to always notify and viceversa. * * 4. Contacts: new incoming contact request, outgoing contact request accepted... * 5. Shared folders: new shared folder, access removed... * */ @interface MEGAPushNotificationSettings : NSObject /** * @brief Getter returns the timestamp (in seconds since the Epoch) until the chats DND mode is enabled and setter can be used to set the global DND mode for chat for a period of time. * * This property returns valid value only if the value of property globalChatsDndEnabled is YES. * No chat notifications will be generated until the specified timestamp. * * * If there's no DND mode established, this function returns -1. * @note a DND value of 0 means the DND does not expire. * */ @property (assign, nonatomic) int64_t globalChatsDNDTimestamp; /** * @brief Getter returns whether Do-Not-Disturb mode for chats is enabled or not and setter can be used to enable or disable notifications related to all chats. */ @property (assign, nonatomic) BOOL globalChatsDndEnabled; /** * @brief Returns whether Do-Not-Disturb mode for a chat is enabled or not * * @param chatId handle of the node that identifies the chat room * @return YES if enabled, NO otherwise */ - (BOOL)isChatDndEnabledForChatId:(uint64_t)chatId; /** * @brief Enable or disable notifications for a chat * * If notifications for this chat are disabled, the DND settings for this chat, * if any, will be cleared. * * @note Settings per chat override any global notification setting. * * @param enabled YES to enable, NO to disable * @param chatId handle of the node that identifies the chat room */ - (void)setChatEnabled:(BOOL)enabled forChatId:(uint64_t)chatId; /** * @brief Returns the timestamp until the Do-Not-Disturb mode for a chat * * This method returns a valid value only if [MEGAPushNotificationSettings isChatEnabled] * returns NO and [MEGAPushNotificationSettings isChatDndEnabled] returns YES. * * If there's no DND mode established for the specified chat, this function returns -1. * @note a DND value of 0 means the DND does not expire. * * @param chatId handle of the Node that identifies the chat room * @return timestamp until DND mode is enabled (in seconds since the Epoch) */ - (int64_t)timestampForChatId:(uint64_t)chatId; /** * @brief Set the DND mode for a chat for a period of time * * No notifications will be generated until the specified timestamp. * * This setting is not compatible with the "Always notify". If DND mode is * configured, the "Always notify" will be disabled. * * If chat notifications were totally disabled for the specified chat, this * function will enable them back (but will not generate notification until * the specified timestamp). * * @param chatId handle of the node that identifies the chat room * @param timestamp timestamp until DND mode is enabled (in seconds since the Epoch) */ - (void)setChatDndForChatId:(uint64_t)chatId untilTimestamp:(int64_t)timestamp; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGARecentActionBucket.h000066400000000000000000000061601516266226600230030ustar00rootroot00000000000000/** * @file MEGARecentActionBucket.h * @brief Represents a set of files uploaded or updated in MEGA. * * (c) 2019 - Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @class MEGANodeList; /** * @brief Represents a set of files uploaded or updated in MEGA. * These are used to display the recent changes to an account. * * Objects of this class aren't live, they are snapshots of the state * in MEGA when the object is created, they are immutable. * * MEGARecentActionBucket objects can be retrieved with -[MEGASdk recentActionsSinceDate:maxNodes:] * */ @interface MEGARecentActionBucket : NSObject /** * @brief Returns a timestamp reflecting when these changes occurred * * @return Timestamp indicating when the changes occurred */ @property (readonly, nonatomic, nullable) NSDate *timestamp; /** * @brief Returns the email of the user who made the changes * * @return the associated user's email */ @property (readonly, nonatomic, nullable) NSString *userEmail; /** * @brief Returns the handle of the parent folder these changes occurred in * * @return the handle of the parent folder for these changes. */ @property (readonly, nonatomic) uint64_t parentHandle; /** * @brief Returns whether the changes are updated files, or new files * * @return YES if the changes are updates rather than newly uploaded files. */ @property (readonly, nonatomic, getter=isUpdate) BOOL update; /** * @brief Returns whether the files are photos or videos * * @return YES if the files in this change are media files. */ @property (readonly, nonatomic, getter=isMedia) BOOL media; /** * @brief Returns nodes representing the files changed in this bucket * * @return A list of the files in the bucket. The bucket retains ownership. */ @property (readonly, nonatomic, nullable) MEGANodeList *nodesList; /** * @brief Returns the identifier for this bucket * * Format: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRecentActionBucket object is deleted. * * @return The bucket identifier string */ @property (readonly, nonatomic, nullable) NSString *bucketId; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGARequest.h000066400000000000000000000673321516266226600207270ustar00rootroot00000000000000/** * @file MEGARequest.h * @brief Provides information about an asynchronous request * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGANode.h" #import "MEGAAccountDetails.h" #import "MEGAPricing.h" #import "MEGAAchievementsDetails.h" #import "MEGAFolderInfo.h" #import "MEGATimeZoneDetails.h" #import "MEGAStringList.h" #import "MEGAPushNotificationSettings.h" #import "MEGABannerList.h" #import "MEGAHandleList.h" #import "MEGACurrency.h" #import "MEGARecentActionBucket.h" #import "MEGABackupInfo.h" #import "MEGASet.h" #import "MEGASetElement.h" #import "MEGAVPNCredentials.h" #import "MEGANetworkConnectivityTestResults.h" #import "MEGAVPNRegion.h" #import "MEGANotificationList.h" NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM (NSInteger, MEGARequestType) { MEGARequestTypeLogin = 0, MEGARequestTypeCreateFolder = 1, MEGARequestTypeMove = 2, MEGARequestTypeCopy = 3, MEGARequestTypeRename = 4, MEGARequestTypeRemove = 5, MEGARequestTypeShare = 6, MEGARequestTypeImportLink = 7, MEGARequestTypeExport = 8, MEGARequestTypeFetchNodes = 9, MEGARequestTypeAccountDetails = 10, MEGARequestTypeChangePassword = 11, MEGARequestTypeUpload = 12, MEGARequestTypeLogout = 13, MEGARequestTypeGetPublicNode = 14, MEGARequestTypeGetAttrFile = 15, MEGARequestTypeSetAttrFile = 16, MEGARequestTypeGetAttrUser = 17, MEGARequestTypeSetAttrUser = 18, MEGARequestTypeRetryPendingConnections = 19, MEGARequestTypeRemoveContact = 20, MEGARequestTypeCreateAccount = 21, MEGARequestTypeConfirmAccount = 22, MEGARequestTypeQuerySignUpLink = 23, MEGARequestTypeAddSync = 24, MEGARequestTypeRemoveSync = 25, MEGARequestTypeDisableSync = 26, // Deprecated MEGARequestTypeEnableSync = 27, // Deprecated MEGARequestTypeCopySyncConfig = 28, MEGARequestTypeCopyCachedConfig = 29, MEGARequestTypeImportSyncConfigs = 30, MEGARequestTypeRemoveSyncs = 31, MEGARequestTypePauseTransfers = 32, MEGARequestTypeCancelTransfer = 33, MEGARequestTypeCancelTransfers = 34, MEGARequestTypeDelete = 35, MEGARequestTypeReportEvent = 36, MEGARequestTypeCancelAttrFile = 37, MEGARequestTypeGetPricing = 38, MEGARequestTypeGetPaymentId = 39, MEGARequestTypeGetUserData = 40, MEGARequestTypeLoadBalancing = 41, MEGARequestTypeKillSession = 42, MEGARequestTypeSubmitPurchaseReceipt = 43, MEGARequestTypeCreditCardStore = 44, MEGARequestTypeUpgradeAccount = 45, MEGARequestTypeCreditCardQuerySubscriptions = 46, MEGARequestTypeCreditCardCancelSubscriptions = 47, MEGARequestTypeGetSessionTransferUrl = 48, MEGARequestTypeGetPaymentMethods = 49, MEGARequestTypeInviteContact = 50, MEGARequestTypeReplyContactRequest = 51, MEGARequestTypeSubmitFeedback = 52, MEGARequestTypeSendEvent = 53, MEGARequestTypeCleanRubbishBin = 54, MEGARequestTypeSetAttrNode = 55, MEGARequestTypeChatCreate = 56, MEGARequestTypeChatFetch = 57, MEGARequestTypeChatInvite = 58, MEGARequestTypeChatRemove = 59, MEGARequestTypeChatUrl = 60, MEGARequestTypeChatGrantAccess = 61, MEGARequestTypeChatRemoveAccess = 62, MEGARequestTypeUseHttpsOnly = 63, // Obsolete MEGARequestTypeSetProxy = 64, MEGARequestTypeGetRecoveryLink = 65, MEGARequestTypeQueryRecoveryLink = 66, MEGARequestTypeConfirmRecoveryLink = 67, MEGARequestTypeGetCancelLink = 68, MEGARequestTypeConfirmCancelLink = 69, MEGARequestTypeGetChangeEmailLink = 70, MEGARequestTypeConfirmChangeEmailLink = 71, MEGARequestTypeChatUpdatePermissions = 72, MEGARequestTypeChatTruncate = 73, MEGARequestTypeChatSetTitle = 74, MEGARequestTypeSetMaxConnections = 75, MEGARequestTypePauseTransfer = 76, MEGARequestTypeMoveTransfer = 77, MEGARequestTypeChatPresenceUrl = 78, MEGARequestTypeRegisterPushNotification = 79, MEGARequestTypeGetUserEmail = 80, MEGARequestTypeAppVersion = 81, MEGARequestTypeGetLocalSSLCertificate = 82, MEGARequestTypeSendSignupLink = 83, MEGARequestTypeQueryDns = 84, MEGARequestTypeQueryGelb = 85, // Deprecated MEGARequestTypeChatStats = 86, MEGARequestTypeDownloadFile = 87, MEGARequestTypeQueryTransferQuota = 88, MEGARequestTypePasswordLink = 89, MEGARequestTypeGetAchievements = 90, MEGARequestTypeRestore = 91, MEGARequestTypeRemoveVersions = 92, MEGARequestTypeChatArchive = 93, MEGARequestTypeWhyAmIBlocked = 94, MEGARequestTypeContactLinkCreate = 95, MEGARequestTypeContactLinkQuery = 96, MEGARequestTypeContactLinkDelete = 97, MEGARequestTypeFolderInfo = 98, MEGARequestTypeRichLink = 99, MEGARequestTypeKeepMeAlive = 100, MEGARequestTypeMultiFactorAuthCheck = 101, MEGARequestTypeMultiFactorAuthGet = 102, MEGARequestTypeMultiFactorAuthSet = 103, MEGARequestTypeAddBackup = 104, MEGARequestTypeRemoveBackup = 105, MEGARequestTypeTimer = 106, MEGARequestTypeAbortCurrentBackup = 107, MEGARequestTypeGetPSA = 108, MEGARequestTypeFetchTimeZone = 109, MEGARequestTypeUseralertAcknowledge = 110, MEGARequestTypeChatLinkHandle = 111, MEGARequestTypeChatLinkUrl = 112, MEGARequestTypeSetPrivateMode = 113, MEGARequestTypeAutojoinPublicChat = 114, MEGARequestTypeCatchup = 115, MEGARequestTypePublicLinkInformation = 116, MEGARequestTypeGetBackgroundUploadURL = 117, MEGARequestTypeCompleteBackgroundUpload = 118, MEGARequestTypeCloudStorageUsed = 119, MEGARequestTypeSendSMSVerificationCode = 120, MEGARequestTypeCheckSMSVerificationCode = 121, MEGARequestTypeGetRegisteredContacts = 122, // Deprecated MEGARequestTypeGetCountryCallingCodes = 123, MEGARequestTypeVerifyCredentials = 124, MEGARequestTypeGetMiscFlags = 125, MEGARequestTypeResendVerificationEmail = 126, MEGARequestTypeSupportTicket = 127, MEGARequestTypeRetentionTime = 128, MEGARequestTypeResetSmsVerifiedNumber = 129, MEGARequestTypeSendDevCommand = 130, MEGARequestTypeGetBanners = 131, MEGARequestTypeDismissBanner = 132, MEGARequestTypeBackupPut = 133, MEGARequestTypeBackupRemove = 134, MEGARequestTypeBackupPutHeartbeat = 135, MEGARequestTypeFetchAds = 136, MEGARequestTypeQueryAds = 137, MEGARequestTypeGetAttrNode = 138, MEGARequestTypeLoadExternalDriveBackups = 139, MEGARequestTypeCloseExternalDriveBackups = 140, MEGARequestTypeGetDownloadUrls = 141, MEGARequestTypeStartChatCall = 142, MEGARequestTypeJoinChatCall = 143, MEGARequestTypeEndChatCall = 144, MEGARequestTypeGetFAUploadUrl = 145, MEGARequestTypeExecuteOnThread = 146, MEGARequestTypeGetChatOptions = 147, MEGARequestTypeGetRecentActions = 148, MEGARequestTypeCheckRecoveryKey = 149, MEGARequestTypeSetMyBackups = 150, MEGARequestTypePutSet = 151, MEGARequestTypeRemoveSet = 152, MEGARequestTypeFetchSet = 153, MEGARequestTypePutSetElement = 154, MEGARequestTypeRemoveSetElement = 155, MEGARequestTypeRemoveOldBackupNodes = 156, MEGARequestTypeSetSyncRunstate = 157, MEGARequestTypeAddUpdateScheduledMeeting = 158, MEGARequestTypeDelScheduledMeeting = 159, MEGARequestTypeFetchScheduledMeeting = 160, MEGARequestTypeFetchScheduledMeetingOccurrences = 161, MEGARequestTypeOpenShareDialog = 162, MEGARequestTypeUpgradeSecurity = 163, MEGARequestTypePutSetElements = 164, MEGARequestTypeRemoveSetElements = 165, MEGARequestTypeExportSet = 166, MEGARequestTypeExportedSetElement = 167, MEGARequestTypeGetRecommenedProPlan = 168, MEGARequestTypeBackupInfo = 169, MEGARequestTypeBackupRemoveMD = 170, MEGARequestTypeABTestActive = 171, MEGARequestTypeGetVPNRegions = 172, MEGARequestTypeGetVPNCredentials = 173, MEGARequestTypePutVPNCredentials = 174, MEGARequestTypeDeleteVPNCredentials = 175, MEGARequestTypeCheckVPNCredentials = 176, MEGARequestTypeGetSyncStallList = 177, MEGARequestTypeFetchCreditCardInfo = 178, MEGARequestTypeMoveToDebris = 179, MEGARequestTypeRingIndividualInCall = 180, MEGARequestTypeCreateNodeTree = 181, MEGARequestTypeCreatePasswordManagerBase = 182, MEGARequestTypeCreatePasswordNode = 183, MEGARequestTypeUpdatePasswordNode = 184, MEGARequestTypeGetNotifications = 185, MEGARequestTypeTagNode = 186, MEGARequestTypeAddMount = 187, MEGARequestTypeDisableMount = 188, MEGARequestTypeEnableMount = 189, MEGARequestTypeRemoveMount = 190, MEGARequestTypeSetMountFlags = 191, MEGARequestTypeDelAttrUser = 192, MEGARequestTypeBackupPauseMD = 194, MEGARequestTypeBackupResumeMD = 195, MEGARequestTypeImportPasswordsFromFile = 196, MEGARequestTypeGetActiveSurveyTriggerActions = 197, MEGARequestTypeGetSurvey = 198, MEGARequestTypeAnswerSurvey = 199, MEGARequestTypeChangeSyncRoot = 200, MEGARequestTypeGetMyIP = 201, MEGARequestTypeSetSyncUploadThrottleValues = 202, MEGARequestTypeGetSyncUploadThrottleValues = 203, MEGARequestTypeGetSyncUploadThrottleLimits = 204, MEGARequestTypeCheckSyncUploadThrottledValues = 205, MEGARequestTypeRunNetworkConnectivityTest = 206, MEGARequestTypeAddSyncPrevalidation = 207, MEGARequestTypeGetSubscriptionCancellationDetails = 208, TotalOfRequestTypes = 209 }; typedef NS_ENUM (NSInteger, MEGANodeAccessLevel) { MEGANodeAccessLevelAccessUnknown = -1, MEGANodeAccessLevelRdOnly = 0, // cannot add, rename or delete MEGANodeAccessLevelRdWr, // cannot rename or delete MEGANodeAccessLevelFull, // all operations that do not require ownership permitted MEGANodeAccessLevelOwner, // node is in caller's ROOT, INCOMING or RUBBISH trees MEGANodeAccessLevelOwnerPreLogin }; /** * @brief Provides information about an asynchronous request. * * Most functions in this API are asynchonous, except the ones that never require to * contact MEGA servers. Developers can use delegates (MEGADelegate, MEGARequestDelegate) * to track the progress of each request. MEGARequest objects are provided in callbacks sent * to these delegates and allow developers to know the state of the request, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the request * when the object is created, they are immutable. * * These objects have a high number of 'properties', but only some of them return valid values * for each type of request. Documentation of each request specify which fields are valid. * */ NS_SWIFT_SENDABLE @interface MEGARequest : NSObject /** * @brief Type of request associated with the object. */ @property (readonly, nonatomic) MEGARequestType type; /** * @brief A readable string that shows the type of request. * * This property is a pointer to a statically allocated buffer. * You don't have to free the returned pointer * */ @property (readonly, nonatomic, nullable) NSString *requestString; /** * @brief The handle of a node related to the request. * * This value is valid for these requests: * - [MEGASdk moveNode:newParent:] - Returns the handle of the node to move * - [MEGASdk copyNode:newParent:] - Returns the handle of the node to copy * - [MEGASdk renameNode:newName:] - Returns the handle of the node to rename * - [MEGASdk removeNode:] - Returns the handle of the node to remove * - [MEGASdk shareNode:withUser:level:] - Returns the handle of the folder to share * - [MEGASdk getThumbnailNode:destinationFilePath:] - Returns the handle of the node to get the thumbnail * - [MEGASdk getPreviewlNode:destinationFilePath:] - Return the handle of the node to get the preview * - [MEGASdk cancelGetThumbnailNode:] - Return the handle of the node * - [MEGASdk cancelGetPreviewNode:] - Returns the handle of the node * - [MEGASdk setThumbnailNode:sourceFilePath:] - Returns the handle of the node * - [MEGASdk setPreviewNode:sourceFilePath:] - Returns the handle of the node * - [MEGASdk exportNode:] - Returns the handle of the node * - [MEGASdk disableExportNode:] - Returns the handle of the node * - [MEGASdk getPaymentIdForProductHandle:] - Returns the handle of the folder in MEGA * * This value is valid for these requests in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk createFolderWithName:parent:] - Returns the handle of the new folder * - [MEGASdk copyNode:newParent:] - Returns the handle of the new node * - [MEGASdk importMegaFileLink:parent:] - Returns the handle of the new node * */ @property (readonly, nonatomic) uint64_t nodeHandle; /** * @brief A link related to the request. * * This value is valid for these requests: * - [MEGASdk querySignupLink:] - Returns the confirmation link * - [MEGASdk confirmAccountWithLink:password:] - Returns the confirmation link * - [MEGASdk loginToFolderLink:] - Returns the link to the folder * - [MEGASdk importMegaFileLink:parent:] - Returns the link to the file to import * - [MEGASdk publicNodeForMegaFileLink:] - Returns the link to the file * * This value is valid for these requests in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk exportNode:] - Returns the public link * - [MEGASdk getPaymentIdForProductHandle:] - Returns the payment link * */ @property (readonly, nonatomic, nullable) NSString *link; /** * @brief The handle of a parent node related to the request. * * This value is valid for these requests: * - [MEGASdk createFolderWithName:parent:] - Returns the handle of the parent folder * - [MEGASdk moveNode:newParent:] - Returns the handle of the new parent for the node * - [MEGASdk copyNode:newParent:] - Returns the handle of the parent for the new node * - [MEGASdk importMegaFileLink:parent:] - Returns the handle of the node that receives the imported file * */ @property (readonly, nonatomic) uint64_t parentHandle; /** * @brief A session key related to the request. * * This value is valid for these requests: * - [MEGASdk fastLoginWithSession:] - Returns session key used to access the account * */ @property (readonly, nonatomic, nullable) NSString *sessionKey; /** * @brief A name related to the request. * * This value is valid for these requests: * - [MEGASdk createAccountWithEmail:password:name:] - Returns the name of the user * - [MEGASdk createFolderWithName:parent:] - Returns the name of the new folder * - [MEGASdk renameNode:newName:] - Returns the new name for the node * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk querySignupLink:] - Returns the name of the user * - [MEGASdk confirmAccountWithLink:password:] - Returns the name of the user * */ @property (readonly, nonatomic, nullable) NSString *name; /** * @brief An email related to the request. * * This value is valid for these requests: * - [MEGASdk loginWithEmail:password:] - Returns the email of the account * - [MEGASdk fastLoginWithEmail:password:] - Returns the email of the account * - [MEGASdk loginToFolderLink:] - Returns the string "FOLDER" * - [MEGASdk createAccountWithEmail:password:name:] - Returns the name of the user * - [MEGASdk shareNode:withUser:level:] - Returns the handle of the folder to share * - [MEGASdk getAvatarUser:destinationFilePath:] - Returns the email of the user to get the avatar * - [MEGASdk removeContactWithEmail:] - Returns the email of the contact * - [MEGASdk getUserData] - Returns the name of the user * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk querySignupLink:] - Returns the name of the user * - [MEGASdk confirmAccountWithLink:password:] - Returns the name of the user * */ @property (readonly, nonatomic, nullable) NSString *email; /** * @brief A password related to the request. * * This value is valid for these requests: * - [MEGASdk loginWithEmail:password:] - Returns the email of the account * - [MEGASdk fastLoginWithEmail:password:] - Returns the email of the account * - [MEGASdk createAccountWithEmail:password:name:] - Returns the name of the user * - [MEGASdk confirmAccountWithLink:password:] - Returns the password for the account * - [MEGASdk changePassword:newPassword:] - Returns the old password of the account (first parameter) * */ @property (readonly, nonatomic, nullable) NSString *password; /** * @brief A new password related to the request. * * This value is valid for these requests: * - [MEGASdk changePassword:newPassword:] - Returns the old password of the account (first parameter) * */ @property (readonly, nonatomic, nullable) NSString *newPassword; /** * @brief An access level related to the request. * * This value is valid for these requests: * - [MEGASdk shareNode:withUser:level:] - Returns the access level for the shared folder * - [MEGASdk exportNode:] - Returns YES * - [MEGASdk disableExportNode:] - Returns NO * */ @property (readonly, nonatomic) MEGANodeAccessLevel access; /** * @brief The path of a file related to the request. * * This value is valid for these requests: * - [MEGASdk getThumbnailNode:destinationFilePath:] - Returns the destination path for the thumbnail * - [MEGASdk getPreviewlNode:destinationFilePath:] - Returns the destination path for the preview * - [MEGASdk getAvatarUser:destinationFilePath:] - Returns the destination path for the avatar * - [MEGASdk setThumbnailNode:sourceFilePath:] - Returns the source path for the thumbnail * - [MEGASdk setPreviewNode:sourceFilePath:] - Returns the source path for the preview * - [MEGASdk setAvatarUserWithSourceFilePath:] - Returns the source path for the avatar * */ @property (readonly, nonatomic, nullable) NSString *file; /** * @brief Number of times that a request has temporarily failed. */ @property (readonly, nonatomic) NSInteger numRetry; /** * @brief A public node related to the request. * * If you want to use the returned node beyond the deletion of the MEGARequest object, * you must call [MEGANode clone] or use [MEGARequest publicNode] instead. * * @return Public node related to the request * */ @property (readonly, nonatomic, nullable) MEGANode *publicNode; /** * @brief The type of parameter related to the request. * * This value is valid for these requests: * - [MEGASdk getThumbnailNode:destinationFilePath:] - Returns MEGAAttributeTypeThumbnail * - [MEGASdk getPreviewNode:destinationFilePath:] - Returns MEGAAttributeTypePreview * - [MEGASdk setThumbnailNode:sourceFilePath:] - Returns MEGAAttributeTypeThumbnail * - [MEGASdk setPreviewNode:sourceFilePath:] - Returns MEGAAttributeTypePreview * */ @property (readonly, nonatomic) NSInteger paramType; /** * @brief Text relative to this request. * * This value is valid for these requests: * - [MEGASdk submitFeedbackWithRating:comment:] - Returns the comment about the app * - [MEGASdk reportDebugEventWithText:] - Returns the debug message * */ @property (readonly, nonatomic, nullable) NSString *text; /** * @brief Number related to this request * * This value is valid for these requests: * - [MEGASdk retryPendingConnections] - Returns if transfers are retried * - [MEGASdk submitFeedbackWithRating:comment:] - Returns the rating for the app * */ @property (readonly, nonatomic) long long number; /** * @brief A flag related to the request. * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk queryTransferQuota] - YES if it is expected to get an overquota error, otherwise NO * */ @property (readonly, nonatomic) BOOL flag; /** * @brief Number of transferred bytes during the request. */ @property (readonly, nonatomic) long long transferredBytes; /** * @brief Number of bytes that the SDK will have to transfer to finish the request. */ @property (readonly, nonatomic) long long totalBytes; /** * @brief Details related to the MEGA account. * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getAccountDetails] - Details of the MEGA account * */ @property (readonly, nonatomic, nullable) MEGAAccountDetails *megaAccountDetails; /** * @brief Available pricing plans to upgrade a MEGA account. * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getPricing] - Returns the available pricing plans * */ @property (readonly, nonatomic, nullable) MEGAPricing *pricing; /** * @brief Currency data related to prices * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getPricing] - Returns the currency data related to prices */ @property (readonly, nonatomic, nullable) MEGACurrency *currency; /** * @brief Details related to the MEGA Achievements of this account * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getMegaAchievements] - Details of the MEGA Achievements of this account * */ @property (readonly, nonatomic, nullable) MEGAAchievementsDetails *megaAchievementsDetails; /** * @brief Get details about timezones and the current default * * This value is valid for these request in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk fetchTimeZones] - Details about timezones and the current default * * In any other case, this function returns NULL. * * @return Details about timezones and the current default */ @property (readonly, nonatomic, nullable) MEGATimeZoneDetails *megaTimeZoneDetails; /** * @brief Information about the contents of a folder * * This value is valid for these requests in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getFolderInfoForNode:] - Returns the information related to the folder * */ @property (readonly, nonatomic, nullable) MEGAFolderInfo *megaFolderInfo; /** * @brief Returns settings for push notifications * * This value is valid for these requests in onRequestFinish when the * error code is MEGAErrorTypeApiOk: * - [MEGASdk getPushNotificationSettingsWithDelegate] - Returns settings for push notifications */ @property (readonly, nonatomic, nullable) MEGAPushNotificationSettings *megaPushNotificationSettings; /** * @brief Tag of a transfer related to the request. * * This value is valid for these requests: * - [MEGASdk cancelTransfer:] - Returns the tag of the cancelled transfer ([MEGATransfer tag]) * */ @property (readonly, nonatomic) NSInteger transferTag; /** * @brief Number of details related to this request. */ @property (readonly, nonatomic) NSInteger numDetails; /** * @brief Returns a dictionary of mega string list. */ @property (readonly, nonatomic, nullable) NSDictionary *megaStringListDictionary; /** * @brief Returns the attribute values as dictionary with key and value as string. * * This value is valid for these requests: * - [MEGASdk getUserAttributeType:] - Returns the attribute value of the current account. * * @return Dictionary containing the key-value pairs of the attribute as string. */ @property (readonly, nonatomic, nullable) NSDictionary *megaStringDictionary; /** * @brief Returns problematic content as key and error. * * This value is valid for these requests: * - [MEGASdk importPasswordsFromFile:] - Import passwords from a file into your Password Manager tree. * * @return Dictionary containing the key-value pairs of problematic content as key and error code as value. */ @property (readonly, nonatomic, nullable) NSDictionary *megaStringIntegerDictionary; /** * @brief Gets the string table response from a request mapped into a collection of NSArray of NSStrings. * */ @property (readonly, nonatomic, nullable) NSArray *> *stringTableArray; /** * @brief Gets the banners response from a request mapped into MEGABannerList. * * This value is valid for these requests: * - [MEGASdk getBanners:] - Obtains the user's banner in MEGA. * */ @property (readonly, nonatomic, nullable) MEGABannerList *bannerList; /** * @brief Array of MEGAHandle (NSNumber) * */ @property (readonly, nonatomic, nullable) NSArray *megaHandleArray; /// Array of recent actions buckets /// /// This value is valid for these requests: /// /// - [MEGASdk getRecentActionsAsyncSinceDays:maxNodes:delegate:] /// /// - [MEGASdk getRecentActionsAsyncSinceDays:maxNodes:] /// @property (readonly, nonatomic, nullable) NSArray *recentActionsBuckets; /// Array of all registered backups of the current use /// /// This value is valid for these requests: /// /// - [MEGASdk getBackupInfo:] /// @property (readonly, nonatomic, nullable) NSArray *backupInfoList; /** * @brief Returns a MEGASet explicitly fetched from online API (typically using 'aft' command) * * This value is valid for these requests: * - [MEGASdk fetchSet:delegate:] * * @return requested MEGASet or nil if not found */ @property (readonly, nonatomic, nullable) MEGASet *set; /** * @brief Returns the list of elements, part of the MEGASetElement explicitly fetched from online API (typically using 'aft' command) * * This value is valid for these requests: * - [MEGASdk fetchSet:delegate:] * * @return list of elements in the requested MEGASet, or nil if Set not found */ @property (readonly, nonatomic, nullable) NSArray *elementsInSet; /** * @brief Returns the string list * * This value is valid for these requests: * - [MEGASdk fetchAds:delegate] - A list of the adslot ids to fetch * * @return an object of MEGAStringList */ @property (readonly, nonatomic, nullable) MEGAStringList *megaStringList; /** * @brief Container class to store and load Mega VPN credentials data. * * - SlotIDs occupied by VPN credentials. * - Full list of VPN regions. * - IPv4 and IPv6 used on each SlotID. * - ClusterID used on each SlotID. * - Cluster Public Key associated to each ClusterID. * * @return an object of MEGAVPNCredentials */ @property (readonly, nonatomic, nullable) MEGAVPNCredentials *megaVpnCredentials; /** * @brief Container class to store the results of a network connectivity test * * @return an object of MEGANetworkConnectivityTestResult */ @property (readonly, nonatomic, nullable) MEGANetworkConnectivityTestResults *megaNetworkConnectivityTestResults; /** * @brief Provide all available VPN Regions, including their details. * * The data included for each Region is the following: * - Name (example: hMLKTUojS6o, 1MvzBCx1Uf4) * - Country Code (example: ES, LU) * - Country Name (example: Spain, Luxembourg) * - Region Name (optional) (example: Esch-sur-Alzette) * - Town Name (Optional) (example: Bettembourg) * - Map of {ClusterID, Cluster}. * - For each Cluster: * · Host. * · DNS IP list (as a MEGAStringList). * * @return An array of MEGAVPNRegion objects with available VPN Regions, if the relevant request was sent; * Returns empty if otherwise. */ @property (readonly, nonatomic) NSArray *megaVpnRegions; /** * @brief Get list of available notifications for Notification Center * * This value is valid only for the following requests: * - [MEGASdk getNotificationsWithDelegate] * * @return an object of MEGANotificationList or nil if not found */ @property (readonly, nonatomic, nullable) MEGANotificationList *megaNotifications; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGARequestDelegate.h000066400000000000000000000062501516266226600223520ustar00rootroot00000000000000/** * @file MEGARequestDelegate.h * @brief Delegate to get request events * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGARequest.h" #import "MEGAError.h" NS_ASSUME_NONNULL_BEGIN @class MEGASdk; /** * @brief Protocol to receive information about requests. * * All requests allows to pass a pointer to an implementation of this protocol in the last parameter. * You can also get information about all requests using [MEGASdk addMEGARequestDelegate:] * * MEGADelegate objects can also receive information about requests * * This protocol uses MEGARequest objects to provide information of requests. Take into account that not all * fields of MEGARequest objects are valid for all requests. See the documentation about each request to know * which fields contain useful information for each one. * */ @protocol MEGARequestDelegate @optional /** * @brief This function is called when a request is about to start being processed. * * @param api MEGASdk object that started the request. * @param request Information about the request. */ - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request; /** * @brief This function is called when a request has finished. * * There won't be more callbacks about this request. * The last parameter provides the result of the request. If the request finished without problems, * the error code will be MEGAErrorTypeApiOk. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @param error Error information. */ - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error; /** * @brief This function is called to inform about the progres of a request. * * Currently, this callback is only used for fetchNodes (MEGARequestTypeFetchNodes) requests. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @see [MEGARequest totalBytes] [MEGARequest transferredBytes]. */ - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request; /** * @brief This function is called when there is a temporary error processing a request. * * The request continues after this callback, so expect more * [MEGARequestDelegate onRequestTemporaryError:request:error:] or * a [MEGARequestDelegate onRequestFinish:request:error:] callback. * * @param api MEGASdk object that started the request. * @param request Information about the request. * @param error Error information. */ - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAScheduledCopy.h000066400000000000000000000151241516266226600220220ustar00rootroot00000000000000/** * @file MEGAScheduledCopy.h * @brief Provides information about a backup * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATransferList.h" typedef NS_ENUM(NSInteger, MEGAScheduledCopyState) { MEGAScheduledCopyStateFailed = -2, MEGAScheduledCopyStateCanceled = -1, MEGAScheduledCopyStateInitialScan = 0, MEGAScheduledCopyStateActive = 1, MEGAScheduledCopyStateOnGoing = 2, MEGAScheduledCopyStateSkipping = 3, MEGAScheduledCopyStateRemovingExceeding = 4, }; NS_ASSUME_NONNULL_BEGIN @interface MEGAScheduledCopy : NSObject /** * @brief Get the handle of the folder that is being backed up * @return Handle of the folder that is being backed up in MEGA */ @property (readonly, nonatomic) uint64_t handle; /** * @brief Get the path of the local folder that is being backed up * * @return Local folder that is being backed up */ @property (readonly, nonatomic, nullable) NSString *localFolder; /** * @brief Returns the identifier of this backup * * @return Identifier of the backup */ @property (readonly, nonatomic) NSUInteger tag; /** * @brief Returns if backups that should have happen in the past should be taken care of * * @return Whether past backups should be taken care of */ @property (readonly, nonatomic) BOOL attendPastBackups; /** * @brief Returns the period of the backup * * @return The period of the backup in deciseconds */ @property (readonly, nonatomic) int64_t period; /** * @brief Returns the period string of the backup * Any of these 6 fields may be an asterisk (*). This would mean the entire range of possible values, i.e. each minute, each hour, etc. * * Period is formatted as follows * - - - - - - * | | | | | | * | | | | | | * | | | | | +---- Day of the Week (range: 1-7, 1 standing for Monday) * | | | | +------ Month of the Year (range: 1-12) * | | | +-------- Day of the Month (range: 1-31) * | | +---------- Hour (range: 0-23) * | +------------ Minute (range: 0-59) * +-------------- Second (range: 0-59) * * E.g: * - daily at 04:00:00 (UTC): "0 0 4 * * *" * - every 15th day at 00:00:00 (UTC) "0 0 0 15 * *" * - mondays at 04.30.00 (UTC): "0 30 4 * * 1" * * @return The period string of the backup */ @property (readonly, nonatomic, nullable) NSString *periodString; /** * @brief Returns the next absolute timestamp of the next backup. backup. If none provided it'll use current one. * * Successive nested calls to this functions will give you a full schedule of the next backups. * * Timestamp measures are given in number of seconds that elapsed since January 1, 1970 (midnight UTC/GMT), * not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). * * @return timestamp of the next backup. */ @property (readonly, nonatomic) long long nextStartTime; /** * @brief Returns the number of backups to keep * * @return Maximun number of Backups to store */ @property (readonly, nonatomic) NSUInteger maxBackups; /** * * Possible values are: * - MEGAScheduledCopyStateFailed = -2 * The backup has failed and has been disabled * * - MEGAScheduledCopyStateCanceled = -1, * The backup has failed and has been disabled * * - MEGAScheduledCopyStateInitialScan = 0, * The backup is doing the initial scan * * - MEGAScheduledCopyStateActive = 1, * The backup is active * * - MEGAScheduledCopyStateOnGoing = 2, * A backup is being performed * * - MEGAScheduledCopyStateSkipping = 3, * A backup is being skipped * * - MEGAScheduledCopyStateRemovingExceeding = 4, * The backup is active and an exceeding backup is being removed * @return State of the backup */ @property (readonly, nonatomic) MEGAScheduledCopyState state; // Current backup data: /** * @brief Returns the number of folders created in the backup * @return number of folders created in the backup */ @property (readonly, nonatomic) long long numberFolders; /** * @brief Returns the number of files created in the backup * @return number of files created in the backup */ @property (readonly, nonatomic) long long numberFiles; /** * @brief Returns the number of files to be created in the backup * @return number of files to be created in the backup */ @property (readonly, nonatomic) long long totalFiles; /** * @brief Returns the starting time of the current backup being processed (in deciseconds) * * The returned value is a monotonic time since some unspecified starting point expressed in * deciseconds. * * @return Starting time of the backup (in deciseconds) */ @property (readonly, nonatomic) int64_t currentBKStartTime; /** * @brief Returns the number of transferred bytes during last backup * @return Transferred bytes during this backup */ @property (readonly, nonatomic) long long transferredBytes; /** * @brief Returns the total bytes to be transferred to complete last backup * @return Total bytes to be transferred to complete the backup */ @property (readonly, nonatomic) long long totalBytes; /** * @brief Returns the current speed of last backup * @return Current speed of this backup */ @property (readonly, nonatomic) long long speed; /** * @brief Returns the average speed of last backup * @return Average speed of this backup */ @property (readonly, nonatomic) long long meanSpeed; /** * @brief Returns the timestamp when the last data was received (in deciseconds) * * This timestamp doesn't have a defined starting point. Use the difference between * the return value of this function and MegaScheduledCopy::getCurrentBKStartTime to know how * much time the backup has been running. * * @return Timestamp when the last data was received (in deciseconds) */ @property (readonly, nonatomic) int64_t updateTime; /** * @brief Returns the list with the transfers that have failed for during last backup * * You take the ownership of the returned value * * @return List with all failed transfers */ @property (readonly, nonatomic) MEGATransferList *failedTransfers; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAScheduledCopyDelegate.h000066400000000000000000000073551516266226600234640ustar00rootroot00000000000000/** * @file MEGAScheduledCopyDelegate.h * @brief Delegate to get global events * * (c) 2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAScheduledCopy.h" #import "MEGAError.h" NS_ASSUME_NONNULL_BEGIN @class MEGASdk; /** * @brief Protocol to get information about global events. * * You can implement this interface and start receiving events calling [MEGASdk addMEGAScheduledCopyDelegate:]. * * MEGADelegate objects can also receive global events. */ @protocol MEGAScheduledCopyDelegate @optional /** * @brief This function is called when the state of the backup changes * * The SDK calls this function when the state of the backup changes, for example * from 'active' to 'ongoing' or 'removing exceeding'. * * You can use [MEGAScheduledCopy state] to get the new state. * * @param api MEGASdk object that is backing up files * @param backup MEGAScheduledCopy object that has changed the state */ -(void)onBackupStateChanged:(MEGASdk *)api backup:(MEGAScheduledCopy *)backup; /** * @brief This function is called when a backup is about to start being processed * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MEGASdk object that started the backup * @param backup Information about the backup */ -(void)onBackupStart:(MEGASdk *)api backup:(MEGAScheduledCopy *)backup; /** * @brief This function is called when a backup has finished * * The api object is the one created by the application, it will be valid until * the application deletes it. * * There won't be more callbacks about this backup. * The last parameter provides the result of the backup: * If the backup finished without problems, * the error code will be MEGAErrorTypeApiOk. * If some transfer failed, the error code will be MEGAErrorTypeApiEIncomplete. * If the backup has been skipped the error code will be MEGAErrorTypeApiEExpired. * If the backup folder cannot be found, the error will be MEGAErrorTypeApiENoent. * * * @param api MEGASdk object that started the backup * @param backup Information about the backup * @param error Error information */ -(void)onBackupFinish:(MEGASdk *)api backup:(MEGAScheduledCopy *)backup error:(MEGAError *)error; /** * @brief This function is called to inform about the progress of a backup * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MEGASdk object that started the backup * @param backup Information about the backup * * @see [MEGAScheduledCopy transferredBytes], [MEGAScheduledCopy speed] */ -(void)onBackupUpdate:(MEGASdk *)api backup:(MEGAScheduledCopy *)backup; /** * @brief This function is called when there is a temporary error processing a backup * * The backup continues after this callback, so expect more MEGAScheduledCopyDelegate onBackupTemporaryError or * a MEGAScheduledCopyDelegate onBackupFinish callback * * @param api MEGASdk object that started the backup * @param backup Information about the backup * @param error Error information */ -(void)onBackupTemporaryError:(MEGASdk *)api backup:(MEGAScheduledCopy *)backup error:(MEGAError *)error; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASdk.h000066400000000000000000015144351516266226600200220ustar00rootroot00000000000000/** * @file MEGASdk.h * @brief Allows to control a MEGA account or a public folder * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAAccountDetails.h" #import "MEGAAchievementsDetails.h" #import "MEGAContactRequest.h" #import "MEGAContactRequestList.h" #import "MEGADelegate.h" #import "MEGAError.h" #import "MEGAEvent.h" #import "MEGAGlobalDelegate.h" #import "MEGANodeList.h" #import "MEGANode.h" #import "MEGALoggerDelegate.h" #import "MEGAPricing.h" #import "MEGARecentActionBucket.h" #import "MEGARequest.h" #import "MEGARequestDelegate.h" #import "MEGAShareList.h" #import "MEGATransfer.h" #import "MEGATransferDelegate.h" #import "MEGATransferList.h" #import "MEGATreeProcessorDelegate.h" #import "MEGAUser.h" #import "MEGAUserList.h" #import "MEGABackgroundMediaUpload.h" #import "MEGACancelToken.h" #import "MEGAPushNotificationSettings.h" #import "MEGAPaymentMethod.h" #import "MEGALogLevel.h" #import "ListenerDispatch.h" #import "MEGAUserAlert.h" #import "MEGABackupInfo.h" #import "MEGABackupInfoList.h" #import "MEGAScheduledCopy.h" #import "MEGAScheduledCopyDelegate.h" #import "BackUpState.h" #import "BackUpSubState.h" #import "MEGASearchFilter.h" #import "MEGASearchFilterTimeFrame.h" #import "MEGASearchPage.h" #import "PasswordNodeData.h" #import "MEGANotification.h" #import "MEGACancelSubscriptionReasonList.h" #import "MEGATotpTokenGenResult.h" #import "MEGAUploadOptions.h" NS_ASSUME_NONNULL_BEGIN /** * @brief MEGAIsBeingLogoutNotification will be published before app starts logout. */ extern NSString * const MEGAIsBeingLogoutNotification; typedef uint64_t MEGAHandle; typedef NS_ENUM (NSInteger, MEGASortOrderType) { MEGASortOrderTypeNone = 0, MEGASortOrderTypeDefaultAsc = 1, MEGASortOrderTypeDefaultDesc = 2, MEGASortOrderTypeSizeAsc = 3, MEGASortOrderTypeSizeDesc = 4, MEGASortOrderTypeCreationAsc = 5, MEGASortOrderTypeCreationDesc = 6, MEGASortOrderTypeModificationAsc = 7, MEGASortOrderTypeModificationDesc = 8, MEGASortOrderTypeLinkCreationAsc = 15, MEGASortOrderTypeLinkCreationDesc = 16, MEGASortOrderTypeLabelAsc = 17, MEGASortOrderTypeLabelDesc = 18, MEGASortOrderTypeFavouriteAsc = 19, MEGASortOrderTypeFavouriteDesc = 20, MEGASortOrderTypeShareCreationAsc = 21, MEGASortOrderTypeShareCreationDesc = 22, }; typedef NS_ENUM (NSInteger, MEGAFolderTargetType) { MEGAFolderTargetTypeInShare = 0, MEGAFolderTargetTypeOutShare, MEGAFolderTargetTypePublicLink, MEGAFolderTargetTypeRootNode, MEGAFolderTargetTypeAll, }; typedef NS_ENUM (NSInteger, MEGAEventType) { MEGAEventTypeFeedback = 0, MEGAEventTypeDebug, MEGAEventTypeInvalid }; typedef NS_ENUM (NSInteger, MEGAAttributeType) { MEGAAttributeTypeThumbnail = 0, MEGAAttributeTypePreview }; typedef NS_ENUM(NSInteger, MEGAUserAttribute) { MEGAUserAttributeAvatar = 0, // public - char array MEGAUserAttributeFirstname = 1, // public - char array MEGAUserAttributeLastname = 2, // public - char array MEGAUserAttributeAuthRing = 3, // private - byte array MEGAUserAttributeLastInteraction = 4, // private - byte array MEGAUserAttributeED25519PublicKey = 5, // public - byte array MEGAUserAttributeCU25519PublicKey = 6, // public - byte array MEGAUserAttributeKeyring = 7, // private - byte array MEGAUserAttributeSigRsaPublicKey = 8, // public - byte array MEGAUserAttributeSigCU255PublicKey = 9, // public - byte array MEGAUserAttributeLanguage = 14, // private - char array MEGAUserAttributePwdReminder = 15, // private - char array MEGAUserAttributeDisableVersions = 16, // private - byte array MEGAUserAttributeContactLinkVerification = 17, // private - byte array MEGAUserAttributeRichPreviews = 18, // private - byte array MEGAUserAttributeRubbishTime = 19, // private - byte array MEGAUserAttributeLastPSA = 20, // private - char array MEGAUserAttributeStorageState = 21, // private - char array MEGAUserAttributeGeolocation = 22, // private - byte array MEGAUserAttributeCameraUploadsFolder = 23, // private - byte array MEGAUserAttributeMyChatFilesFolder = 24, // private - byte array MEGAUserAttributePushSettings = 25, // private - char array MEGAUserAttributeAlias = 27, // private - char array MEGAUserAttributeDeviceNames = 30, // private - byte array MEGAUserAttributeBackupsFolder = 31, // private - byte array // MEGAUserAttributeBackupNames = 32, (deprecated) // private - byte array MEGAUserAttributeCookieSettings = 33, // private - byte array MEGAUserAttributeJsonSyncConfigData = 34, // private - byte array // MEGAUserAttributeDrivesName = 35, (deprecated) // private - byte array MEGAUserAttributeNoCallKit = 36, // private - byte array MEGAUserAttributeAppsPreferences = 38, // private - byte array - versioned (apps preferences) MEGAUserAttributeContentConsumptionPreferences = 39, // private - byte array - versioned (content consumption preferences) MEGAUserAttributeLastReadNotification = 44, // private - char array MEGAUserAttributeS4 = 48, // private - non-encrypted - char array MEGAUserAttributeS4Container = 49 // private - non-encrypted - char array }; typedef NS_ENUM(NSInteger, MEGANodeAttribute) { MEGANodeAttributeDuration = 0, MEGANodeAttributeCoordinates = 1, MEGANodeAttributeOriginalFingerprint = 2, MEGANodeAttributeLabel = 3, MEGANodeAttributeFav = 4, MEGANodeAttributeSen = 6, MEGANodeDescription = 7 }; typedef NS_ENUM(NSInteger, MEGASetAttribute) { MEGASetAttributeCreate = 0, MEGASetAttributeName = 1, MEGASetAttributeCover = 2 }; typedef NS_ENUM(NSInteger, MEGASetElementAttribute) { MEGASetElementAttributeCreate = 0, MEGASetElementAttributeName = 1, MEGASetElementAttributeOrder = 2 }; typedef NS_ENUM(NSInteger, HTTPServer) { HTTPServerDenyAll = -1, HTTPServerAllowAll = 0, HTTPServerAllowCreatedLocalLinks = 1, HTTPServerAllowLastLocalLink = 2 }; typedef NS_ENUM(NSUInteger, PushNotificationTokenType) { PushNotificationTokenTypeAndroid = 1, PushNotificationTokenTypeiOSVoIP = 2, PushNotificationTokenTypeiOSStandard = 3 }; typedef NS_ENUM(NSUInteger, PasswordStrength) { PasswordStrengthVeryWeak = 0, PasswordStrengthWeak = 1, PasswordStrengthMedium = 2, PasswordStrengthGood = 3, PasswordStrengthStrong = 4 }; typedef NS_ENUM(NSUInteger, Retry) { RetryNone = 0, RetryConnectivity = 1, RetryServersBusy = 2, RetryApiLock = 3, RetryRateLimit = 4, // RetryLocalLock = 5, (deprecated) RetryUnknown = 6 }; typedef NS_ENUM(NSInteger, KeepMeAlive) { KeepMeAliveCameraUploads = 0 }; typedef NS_ENUM(NSUInteger, StorageState) { StorageStateGreen = 0, StorageStateOrange = 1, StorageStateRed = 2, StorageStateChange = 3, StorageStatePaywall = 4 }; typedef NS_ENUM(NSInteger, SMSState) { SMSStateNotAllowed = 0, SMSStateOnlyUnblock = 1, SMSStateOptInAndUnblock = 2, }; typedef NS_ENUM(NSInteger, AccountSuspensionType) { AccountSuspensionTypeNone = 0, // The account is not blocked AccountSuspensionTypeCopyright = 200, // suspension only for multiple copyright violations AccountSuspensionTypeNonCopyright = 300, // suspension for any type of suspension, but copyright suspension AccountSuspensionTypeBusinessDisabled = 400, // the subuser of a business account has been disabled AccountSuspensionTypeBusinessRemoved = 401, // the subuser of a business account has been removed AccountSuspensionTypeSMSVerification = 500, // The account needs to be verified by an SMS code. AccountSuspensionTypeEmailVerification = 700, // The account needs to be verified by password change trough email. }; typedef NS_ENUM(NSInteger, BusinessStatus) { BusinessStatusExpired = -1, BusinessStatusInactive = 0, // no business subscription BusinessStatusActive = 1, BusinessStatusGracePeriod = 2 }; typedef NS_ENUM(NSInteger, BackUpType) { BackUpTypeInvalid = -1, BackUpTypeTwoWaySync = 0, BackUpTypeUpSync = 1, BackUpTypeDownSync = 2, BackUpTypeCameraUploads = 3, BackUpTypeMediaUploads = 4 }; typedef NS_ENUM(NSUInteger, BackupHeartbeatStatus) { BackupHeartbeatStatusUpToDate = 1, BackupHeartbeatStatusSyncing = 2, BackupHeartbeatStatusPending = 3, BackupHeartbeatStatusInactive = 4, BackupHeartbeatStatusUnknown = 5 }; typedef NS_ENUM(NSInteger, AccountActionType) { AccountActionTypeCreate = 0, AccountActionTypeResume = 1, AccountActionTypeCancel = 2, AccountActionTypeCreateEphemeralPlusPlus = 3, AccountActionTypeResumeEphemeralPlusPlus = 4, }; typedef NS_ENUM(NSInteger, CollisionCheck) { CollisionCheckAssumeSame = 1, CollisionCheckAlwaysError = 2, CollisionCheckFingerprint = 3, CollisionCheckMetaMac = 4, CollisionCheckAssumeDifferent = 5, }; typedef NS_ENUM(NSInteger, CollisionResolution) { CollisionResolutionOverwrite = 1, CollisionResolutionNewWithN = 2, CollisionResolutionExistingToOldN = 3, }; typedef NS_ENUM(NSInteger, AdsFlag) { AdsFlagDefault = 0x0, // If you don't want to set any overrides/flags, then please provide 0 AdsFlagForceAds = 0x200, // Force enable ads regardless of any other factors. AdsFlagIgnoreMega = 0x400, // Show ads even if the current user or file owner is a MEGA employee. AdsFlagIgnoreCountry = 0x800, // Show ads even if the user is not within an enabled country. AdsFlagIgnoreIP = 0x1000, // Show ads even if the user is on a blacklisted IP (MEGA ips). AdsFlagIgnorePRO = 0x2000, // Show ads even if the current user or file owner is a PRO user. AdsFlagIgnoreRollout = 0x4000 // Ignore the rollout logic which only servers ads to 10% of users based on their IP. }; typedef NS_ENUM(NSInteger, MEGAClientType) { MEGAClientTypeDefault = 0, // Cloud storage MEGAClientTypeVPN = 1, // VPN MEGAClientTypePasswordManager = 2 // Password Manager }; typedef NS_ENUM(NSInteger, ImportPasswordFileSource) { ImportPasswordSourceGoogle = 0, // Google Password Manager }; typedef NS_ENUM(NSInteger, PasswordManagerNodeType) { PasswordManagerNodeTypePassword = 1, // Password node PasswordManagerNodeTypeCreditCard = 2 // Credit card node }; /** * @brief Allows to control a MEGA account or a public folder. * * You must provide an appKey to use this SDK. You can generate an appKey for your app for free here: * - https://mega.co.nz/#sdk * * You can enable local node caching by passing a local path in the constructor of this class. That saves many data usage * and many time starting your app because the entire filesystem won't have to be downloaded each time. The persistent * node cache will only be loaded by logging in with a session key. To take advantage of this feature, apart of passing the * local path to the constructor, your application have to save the session key after login ([MEGASdk dumpSession]) and use * it to log in the next time. This is highly recommended also to enhance the security, because in this was the access password * doesn't have to be stored by the application. * * To access MEGA using this SDK, you have to create an object of this class and use one of the [MEGASdk loginWithEmail:password:] * options (to log in to a MEGA account or a public folder). If the login request succeed, you must call [MEGASdk fetchnodes] to get the * filesystem in MEGA. * After that, you can use all other requests, manage the files and start transfers. * * After using [MEGASdk logout] you can reuse the same MEGASdk object to log in to another MEGA account or a public folder. * */ @interface MEGASdk : NSObject #pragma mark - Properties /** * @brief Email of the currently open account. * * If the MEGASdk object isn't logged in or the email isn't available, * this property is nil. * */ @property (readonly, nonatomic, nullable) NSString *myEmail; /** * @brief Date when the account was created * */ @property (readonly, nonatomic, nullable) NSDate *accountCreationDate; /** * @brief Root node of the account. * * If you haven't successfully called [MEGASdk fetchNodes] before, * this property is nil. * */ @property (readonly, nonatomic, nullable) MEGANode *rootNode; /** * @brief Rubbish node of the account. * * If you haven't successfully called [MEGASdk fetchNodes] before, * this property is nil. * */ @property (readonly, nonatomic, nullable) MEGANode *rubbishNode; /** * @brief All active transfers. */ @property (readonly, nonatomic) MEGATransferList *transfers; /** * @brief Download active transfers. */ @property (readonly, nonatomic) MEGATransferList *downloadTransfers; /** * @brief Upload active transfers. */ @property (readonly, nonatomic) MEGATransferList *uploadTransfers; /** * @brief Check if the SDK is waiting to complete a request and get the reason * @return State of SDK. * * Valid values are: * - RetryNone = 0 * SDK is not waiting for the server to complete a request * * - RetryConnectivity = 1 * SDK is waiting for the server to complete a request due to connectivity issues * * - RetryServersBusy = 2 * SDK is waiting for the server to complete a request due to a HTTP error 500 * * - RetryApiLock = 3 * SDK is waiting for the server to complete a request due to an API lock (API error -3) * * - RetryRateLimit = 4, * SDK is waiting for the server to complete a request due to a rate limit (API error -4) * * - RetryLocalLock = 5 * SDK is waiting for a local locked file * * - RetryUnknown = 6 * SDK is waiting for the server to complete a request with unknown reason * */ @property (readonly, nonatomic) Retry waiting; /** * @brief The total number of nodes in the account */ @property (readonly, nonatomic) unsigned long long totalNodes; /** * @brief The master key of the account. * * The value is a Base64-encoded string. * * With the master key, it's possible to start the recovery of an account when the * password is lost: * - https://mega.co.nz/#recovery * */ @property (readonly, nonatomic, nullable) NSString *masterKey; /** * @brief User-Agent header used by the SDK * * The User-Agent used by the SDK */ @property (readonly, nonatomic, nullable) NSString *userAgent; /** * @brief MEGAUser of the currently open account * * If the MEGASdk object isn't logged in, this property is nil. */ @property (readonly, nonatomic, nullable) MEGAUser *myUser; /** * @brief Returns whether MEGA Achievements are enabled for the open account * YES if enabled, NO otherwise. */ @property (readonly, nonatomic, getter=isAchievementsEnabled) BOOL achievementsEnabled; /** * @brief Returns whether displaying contact verification warnings is enabled from the webclient * YES if enabled, NO otherwise. */ @property (readonly, nonatomic, getter=isContactVerificationWarningEnabled) BOOL isContactVerificationWarningEnabled; /** * @brief Check if the logged in account is considered new * * This will NOT return a valid value until the callback onEvent with * type EventMiscFlagsReady is received. You can also rely on the completion of * a fetchnodes to check this value. * * YES if account is considered new. Otherwise, NO. */ @property (readonly, nonatomic, getter=isNewAccount) BOOL newAccount; #pragma mark - Business /** * @brief Returns YES if it's a business account, otherwise NO. * * @note This function must be called only if we have received the callback * [MEGAGlobalDelegate onEvent:event:] and the callback [MEGADelegate onEvent:event:] * with the event type EventBusinessStatus * */ @property (readonly, nonatomic, getter=isBusinessAccount) BOOL businessAccount; /** * @brief Returns YES if it's a master account, NO if it's a sub-user account. * * When a business account is a sub-user, not the master, some user actions will be blocked. * In result, the API will return the error code MEGAErrorTypeApiEMasterOnly. Some examples of * requests that may fail with this error are: * - [MEGASdk cancelAccount] * - [MEGASdk changeEmail] * - [MEGASdk remove] * - [MEGASdk removeVersion] * * @note This function must be called only if we have received the callback * [MEGAGlobalDelegate onEvent:event:] and the callback [MEGADelegate onEvent:event:] * with the event type EventBusinessStatus * */ @property (readonly, nonatomic, getter=isMasterBusinessAccount) BOOL masterBusinessAccount; /** * @brief Returns YES if it is an active business account, otherwise NO. * * When a business account is not active, some user actions will be blocked. In result, the API * will return the error code MEGAErrorTypeApiEBusinessPastDue. Some examples of requests * that may fail with this error are: * - [MEGASdk startDownload] * - [MEGASdk startUpload] * - [MEGASdk copyNode] * - [MEGASdk shareNode] * - [MEGASdk cleanRubbishBin] * * @note This function must be called only if we have received the callback * [MEGAGlobalDelegate onEvent:event:] and the callback [MEGADelegate onEvent:event:] * with the event type EventBusinessStatus */ @property (readonly, nonatomic, getter=isBusinessAccountActive) BOOL businessAccountActive; /** * @brief Get the status of a business account. * * @note This function must be called only if we have received the callback * [MEGAGlobalDelegate onEvent:event:] and the callback [MEGADelegate onEvent:event:] * with the event type EventBusinessStatus * * @return Returns the business account status, possible values: * BusinessStatusExpired = -1 * BusinessStatusInactive = 0 * BusinessStatusActive = 1 * BusinessStatusGracePeriod = 2 */ @property (readonly, nonatomic) BusinessStatus businessStatus; /** * @brief The number of unread user alerts for the logged in user */ @property (readonly, nonatomic) NSInteger numUnreadUserAlerts; /** * @brief The time (in seconds) during which transfers will be stopped due to a bandwidth overquota, otherwise 0 */ @property (readonly, nonatomic) long long bandwidthOverquotaDelay; #pragma mark - Init /** * @brief Constructor suitable for most applications. * @param appKey AppKey of your application. * You can generate your AppKey for free here: * - https://mega.co.nz/#sdk * * @param userAgent User agent to use in network requests. * If you pass nil to this parameter, a default user agent will be used[]. * */ - (nullable instancetype)initWithAppKey:(NSString *)appKey userAgent:(nullable NSString *)userAgent; /** * @brief Constructor suitable for most applications. * @param appKey AppKey of your application. * You can generate your AppKey for free here: * - https://mega.co.nz/#sdk * * @param userAgent User agent to use in network requests. * If you pass nil to this parameter, a default user agent will be used. * * @param basePath Base path to store the local cache. * If you pass nil to this parameter, the SDK won't use any local cache. * */ - (nullable instancetype)initWithAppKey:(NSString *)appKey userAgent:(nullable NSString *)userAgent basePath:(nullable NSString *)basePath; /** * @brief Constructor suitable for most applications. * @param appKey AppKey of your application. * You can generate your AppKey for free here: * - https://mega.co.nz/#sdk * * @param userAgent User agent to use in network requests. * If you pass nil to this parameter, a default user agent will be used. * * @param basePath Base path to store the local cache. * If you pass nil to this parameter, the SDK won't use any local cache. * * @param clientType The client type of the application: Default (Cloud Storage), VPN or Password Manager. * */ - (nullable instancetype)initWithAppKey:(NSString *)appKey userAgent:(nullable NSString *)userAgent basePath:(nullable NSString *)basePath clientType:(MEGAClientType)clientType; /** * @brief Delete MegaApi object */ - (void)deleteMegaApi; #pragma mark - Add and remove delegates /** * @brief Register a delegate to receive all events (requests, transfers, global). * * You can use [MEGASdk removeMEGADelegate:] to stop receiving events. * * @param delegate Delegate that will receive all events (requests, transfers, global). */ - (void)addMEGADelegate:(id)delegate; /** * @brief Register a delegate to receive all events about requests. * * You can use [MEGASdk removeMEGARequestDelegate:] to stop receiving events. * * @param delegate Delegate that will receive all events about requests. */ - (void)addMEGARequestDelegate:(id)delegate; /** * @brief Register a delegate with queue type to receive all events about requests. * * You can use [MEGASdk removeMEGARequestDelegateAsync:] to stop receiving events. * * @param delegate Delegate that will receive all events about requests. * @param queueType ListenerQueueType to receive the MEGARequest events on. */ - (void)addMEGARequestDelegate:(id)delegate queueType:(ListenerQueueType)queueType; /** * @brief Register a delegate to receive all events about transfers. * * You can use [MEGASdk removeMEGATransferDelegate:] to stop receiving events. * * @param delegate Delegate that will receive all events about transfers. */ - (void)addMEGATransferDelegate:(id)delegate; /** * @brief Register a delegate to receive all events about transfers. * * You can use [MEGASdk removeMEGATransferDelegate:] to stop receiving events. * * @param delegate Delegate that will receive all events about transfers. * @param queueType ListenerQueueType to receive the MEGARequest events on. */ - (void)addMEGATransferDelegate:(id)delegate queueType:(ListenerQueueType)queueType; /** * @brief Register a delegate to receive global events. * * You can use [MEGASdk removeMEGAGlobalDelegate:] to stop receiving events. * * @param delegate Delegate that will receive global events. */ - (void)addMEGAGlobalDelegate:(id)delegate; /** * @brief Register a delegate to receive global events. * * You can use [MEGASdk removeMEGAGlobalDelegate:] to stop receiving events. * * @param delegate Delegate that will receive global events. * @param queueType ListenerQueueType to receive the global events on. */ - (void)addMEGAGlobalDelegate:(id)delegate queueType:(ListenerQueueType)queueType; /** * @brief Unregister a delegate. * * This delegate won't receive more events. * * @param delegate Delegate that is unregistered. */ - (void)removeMEGADelegate:(id)delegate; /** * @brief Unregister a MEGARequestDelegate. * * This delegate won't receive more events. * * @param delegate Delegate that is unregistered. */ - (void)removeMEGARequestDelegate:(id)delegate; /** * @brief Unregister a MEGATransferDelegate. * * This delegate won't receive more events. * * @param delegate Delegate that is unregistered. */ - (void)removeMEGATransferDelegate:(id)delegate; /** * @brief Unregister a MEGAGlobalDelegate. * * This delegate won't receive more events. * * @param delegate Delegate that is unregistered. */ - (void)removeMEGAGlobalDelegate:(id)delegate; /** * @brief Add a MEGALoggerDelegate implementation to receive SDK logs * * Logs received by this objects depends on the active log level. * By default, it is MEGALogLevelInfo. You can change it * using [MEGASdk setLogLevel]. * * You can remove the existing logger by using [MEGASdk removeLoggerObject:]. * * @param delegate Delegate implementation */ - (void)addLoggerDelegate:(id)delegate; /** * @brief Remove a MEGALoggerDelegate implementation to stop receiving SDK logs * * If the logger was registered in the past, it will stop receiving log * messages after the call to this function. * * @param delegate Previously registered MegaLogger implementation */ - (void)removeLoggerDelegate:(id)delegate; /** * @brief Add a MEGAScheduledCopyDelegate implementation to receive SDK logs * * This delegate receive backups events. * * @param delegate Delegate implementation */ - (void)addMEGAScheduledCopyDelegate:(id)delegate; /** * @brief Add a MEGAScheduledCopyDelegate implementation to receive SDK logs * * This delegate won't receive more events. * * @param delegate Delegate implementation */ - (void)removeMEGAScheduledCopyDelegate:(id)delegate; #pragma mark - Utils /** * @brief Converts a Base64-encoded node handle to a MegaHandle. * * The returned value can be used to recover a MEGANode using [MEGASdk nodeForHandle:]. * You can revert this operation using [MEGASdk base64handleForHandle:]. * * @param base64Handle Base64-encoded node handle. * @return Node handle. */ + (uint64_t)handleForBase64Handle:(NSString *)base64Handle; /** * @brief Converts a Base64-encoded user handle to a MegaHandle * * You can revert this operation using [MEGASdk base64handleForUserHandle:]. * * @param base64UserHandle Base64-encoded user handle * @return User handle */ + (uint64_t)handleForBase64UserHandle:(NSString *)base64UserHandle; /** * @brief Converts the handle of a node to a Base64-encoded NSString * * You can revert this operation using [MEGASdk handleForBase64Handle:] * * @param handle Node handle to be converted * @return Base64-encoded node handle */ + (nullable NSString *)base64HandleForHandle:(uint64_t)handle; /** * @brief Converts the handle of a user to a Base64-encoded string * * @param userhandle User handle to be converted * @return Base64-encoded user handle */ + (nullable NSString *)base64HandleForUserHandle:(uint64_t)userhandle; /** * @brief Retry all pending requests. * * When requests fails they wait some time before being retried. That delay grows exponentially if the request * fails again. * * The associated request type with this request is MEGARequestTypeRetryPendingConnections. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * - [MEGARequest number] - Returns the second parameter */ - (void)retryPendingConnections; /** * @brief Retry all pending requests and transfers. * * When requests and/or transfers fails they wait some time before being retried. That delay grows exponentially * if the request or transfers fails again. * * Disconnect already connected requests and transfers * * The associated request type with this request is MEGARequestTypeRetryPendingConnections. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * - [MEGARequest number] - Returns the second parameter */ - (void)reconnect; /** * @brief Check if server-side Rubbish Bin autopurging is enabled for the current account * @return YES if this feature is enabled. Otherwise NO. */ - (BOOL)serverSideRubbishBinAutopurgeEnabled; /** * @brief Check if the account has VOIP push enabled * @return YES if this feature is enabled. Otherwise NO. */ - (BOOL)appleVoipPushEnabled; /* This function creates a new session for the link so logging out in the web client won't log out * the current session. * * The associated request type with this request is MEGARequestTypeGetSessionTransferUrl * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - URL to open the desired page with the same account * * If the client is logged in, but the account is not fully confirmed (ie. singup not * completed yet), this method will return API_EACCESS. * * If the client is not logged in, there won't be any session to transfer, but this method * will still return the MEGA's host (ie. https://mega.app) followed by /#. * * @param url URL inside the MEGA's host that we want to open with the current session * * For example, if you want to open https://mega.app/#pro, the parameter of this function should be "pro". * * @param delegate MEGARequestDelegate to track this request */ - (void)getSessionTransferURL:(NSString *)path delegate:(id)delegate; /* This function creates a new session for the link so logging out in the web client won't log out * the current session. * * The associated request type with this request is MEGARequestTypeGetSessionTransferUrl * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - URL to open the desired page with the same account * * If the client is logged in, but the account is not fully confirmed (ie. singup not * completed yet), this method will return API_EACCESS. * * If the client is not logged in, there won't be any session to transfer, but this method * will still return the MEGA's host (ie. https://mega.app) followed by /#. * * @param url URL inside the MEGA's host that we want to open with the current session * * For example, if you want to open https://mega.app/#pro, the parameter of this function should be "pro". */ - (void)getSessionTransferURL:(NSString *)path; /** * @brief Returns a new MEGAStringList that contains the given list of strings. * * @param stringList Array of string that will be converted to MEGAStringList. * @return MEGAStringList from the given list of strings. */ - (MEGAStringList *)megaStringListFor:(NSArray*)stringList; #pragma mark - Login Requests /** * @brief Check if multi-factor authentication can be enabled for the current account. * * It's needed to be logged into an account and with the nodes loaded (login + fetchNodes) before * using this function. Otherwise it will always return NO. * * @return YES if multi-factor authentication can be enabled for the current account, otherwise NO. */ - (BOOL)multiFactorAuthAvailable; /** * @brief Check if multi-factor authentication is enabled for an account * * The associated request type with this request is MEGARequestTypeMultiFactorAuthCheck * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email sent in the first parameter * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if multi-factor authentication is enabled or NO if it's disabled. * * @param email Email to check * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthCheckWithEmail:(NSString *)email delegate:(id)delegate; /** * @brief Check if multi-factor authentication is enabled for an account * * The associated request type with this request is MEGARequestTypeMultiFactorAuthCheck * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email sent in the first parameter * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if multi-factor authentication is enabled or NO if it's disabled. * * @param email Email to check */ - (void)multiFactorAuthCheckWithEmail:(NSString *)email; /** * @brief Get the secret code of the account to enable multi-factor authentication * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthGet * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the Base32 secret code needed to configure multi-factor authentication. * * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthGetCodeWithDelegate:(id)delegate; /** * @brief Get the secret code of the account to enable multi-factor authentication * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthGet * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the Base32 secret code needed to configure multi-factor authentication. * */ - (void)multiFactorAuthGetCode; /** * @brief Enable multi-factor authentication for the account * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns YES * - [MEGARequest password] - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthEnableWithPin:(NSString *)pin delegate:(id)delegate; /** * @brief Enable multi-factor authentication for the account * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns YES * - [MEGARequest password] - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication */ - (void)multiFactorAuthEnableWithPin:(NSString *)pin; /** * @brief Disable multi-factor authentication for the account * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns NO * - [MEGARequest password] - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthDisableWithPin:(NSString *)pin delegate:(id)delegate; /** * @brief Disable multi-factor authentication for the account * The MEGASdk object must be logged into an account to successfully use this function. * * The associated request type with this request is MEGARequestTypeMultiFactorAuthSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns NO * - [MEGARequest password] - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication */ - (void)multiFactorAuthDisableWithPin:(NSString *)pin; /** * @brief Log in to a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the first parameter * - [MEGARequest password] - Returns the second parameter * - [MEGARequest text] - Returns the third parameter * * If the email/password aren't valid the error code provided in onRequestFinish is * MEGAErrorTypeApiENoent. * * @param email Email of the user * @param password Password * @param pin Pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthLoginWithEmail:(NSString *)email password:(NSString *)password pin:(NSString *)pin delegate:(id)delegate; /** * @brief Log in to a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the first parameter * - [MEGARequest password] - Returns the second parameter * - [MEGARequest text] - Returns the third parameter * * If the email/password aren't valid the error code provided in onRequestFinish is * MEGAErrorTypeApiENoent. * * @param email Email of the user * @param password Password * @param pin Pin code for multi-factor authentication */ - (void)multiFactorAuthLoginWithEmail:(NSString *)email password:(NSString *)password pin:(NSString *)pin; /** * @brief Change the password of a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MEGARequestTypeChangePassword * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest password] - Returns the old password (if it was passed as parameter) * - [MEGARequest newPassword] - Returns the new password * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * @param oldPassword Old password (optional, it can be nil to not check the old password) * @param newPassword New password * @param pin Pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthChangePassword:(nullable NSString *)oldPassword newPassword:(NSString *)newPassword pin:(NSString *)pin delegate:(id)delegate; /** * @brief Change the password of a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MEGARequestTypeChangePassword * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest password] - Returns the old password (if it was passed as parameter) * - [MEGARequest newPassword] - Returns the new password * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * @param oldPassword Old password (optional, it can be nil to not check the old password) * @param newPassword New password * @param pin Pin code for multi-factor authentication */ - (void)multiFactorAuthChangePassword:(nullable NSString *)oldPassword newPassword:(NSString *)newPassword pin:(NSString *)pin; /** * @brief Initialize the change of the email address associated to an account with multi-factor authentication enabled. * * The associated request type with this request is MEGARequestTypeGetChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish(). * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param email The new email to be associated to the account. * @param pin Pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthChangeEmail:(NSString *)email pin:(NSString *)pin delegate:(id)delegate; /** * @brief Initialize the change of the email address associated to an account with multi-factor authentication enabled. * * The associated request type with this request is MEGARequestTypeGetChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish(). * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param email The new email to be associated to the account. * @param pin Pin code for multi-factor authentication */ - (void)multiFactorAuthChangeEmail:(NSString *)email pin:(NSString *)pin; /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MEGARequestTypeGetCancelLink. * * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish(). * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @see [MEGASdk confirmCancelAccountWithLink:password:] * * @param pin Pin code for multi-factor authentication * @param delegate MEGARequestDelegate to track this request */ - (void)multiFactorAuthCancelAccountWithPin:(NSString *)pin delegate:(id)delegate; /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MEGARequestTypeGetCancelLink. * * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish(). * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest text] - Returns the pin code for multi-factor authentication * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @see [MEGASdk confirmCancelAccountWithLink:password:] * * @param pin Pin code for multi-factor authentication */ - (void)multiFactorAuthCancelAccountWithPin:(NSString *)pin; /** * @brief Fetch details related to time zones and the current default * * The associated request type with this request is MEGARequestTypeFetchTimeZone. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaTimeZoneDetails] - Returns details about timezones and the current default * * @param delegate MEGARequestDelegate to track this request */ - (void)fetchTimeZoneWithDelegate:(id)delegate; /** * @brief Fetch details related to time zones and the current default * * The associated request type with this request is MEGARequestTypeFetchTimeZone. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaTimeZoneDetails] - Returns details about timezones and the current default * */ - (void)fetchTimeZone; /** * @brief Log in to a MEGA account. * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the first parameter * - [MEGARequest password] - Returns the second parameter * * If the email/password aren't valid the error code provided in onRequestFinish is * MEGAErrorTypeApiENoent. * * @param email Email of the user. * @param password Password. * @param delegate Delegate to track this request. */ - (void)loginWithEmail:(NSString *)email password:(NSString *)password delegate:(id)delegate; /** * @brief Log in to a MEGA account. * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the first parameter * - [MEGARequest password] - Returns the second parameter * * If the email/password aren't valid the error code provided in onRequestFinish is * MEGAErrorTypeApiENoent. * * @param email Email of the user. * @param password Password. */ - (void)loginWithEmail:(NSString *)email password:(NSString *)password; /** * @brief Log in to a MEGA account using a session key. * * The associated request type with this request is MEGARequestTypeFastLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest sessionKey] - Returns the session key. * * @param session Session key previously dumped with [MEGASdk dumpSession]. * @param delegate Delegate to track this request. */ - (void)fastLoginWithSession:(NSString *)session delegate:(id)delegate; /** * @brief Log in to a MEGA account using a session key. * * The associated request type with this request is MEGARequestTypeFastLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest sessionKey] - Returns the session key * * @param session Session key previously dumped with [MEGASdk dumpSession]. */ - (void)fastLoginWithSession:(NSString *)session; /** * @brief Log in to a public folder using a folder link. * * After a successful login, you should call [MEGAsdk fetchnodes] to get filesystem and * start working with the folder. * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Retuns the string "FOLDER" * - [MEGARequest link] - Returns the public link to the folder * * @param folderLink Link to a folder in MEGA. * @param delegate Delegate to track this request. */ - (void)loginToFolderLink:(NSString *)folderLink delegate:(id)delegate; /** * @brief Log in to a public folder using a folder link. * * After a successful login, you should call [MEGAsdk fetchnodes] to get filesystem and * start working with the folder. * * The associated request type with this request is MEGARequestTypeLogin. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Retuns the string "FOLDER" * - [MEGARequest link] - Returns the public link to the folder * * @param folderLink Link to a folder in MEGA. */ - (void)loginToFolderLink:(NSString *)folderLink; /** * @brief Trigger special account state changes for own accounts, for testing * * Because the dev API command allows a wide variety of state changes including suspension and unsuspension, * it has restrictions on which accounts you can target, and where it can be called from. * * Your client must be on a company VPN IP address. * * The target account must be an @mega email address. The target account must either be the calling account, * OR a related account via a prefix and + character. For example if the calling account is name1+test@mega.co.nz * then it can perform a dev command on itself or on name1@mega.co.nz, name1+bob@mega.co.nz etc, but NOT on * name2@mega.co.nz or name2+test@meg.co.nz. * * The associated request type with this request is MEGARequestTypeSendDevCommand. * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest name] - Returns the first parameter * - [MEGARequest email] - Returns the second parameter * * Possible errors are: * - MEGAErrorTypeApiEAccess if the calling account is not allowed to perform this method (not a mega email account, not the right IP, etc). * - MEGAErrorTypeApiEArgs if the subcommand is not present or is invalid * - MEGAErrorTypeApiEBlocked if the target account is not allowed (this could also happen if the target account does not exist) * * Possible commands: * - "aodq" - Advance ODQ Warning State * If called, this will advance your ODQ warning state until the final warning state, * at which point it will turn on the ODQ paywall for your account. It requires an account lock on the target account. * This subcommand will return the 'step' of the warning flow you have advanced to - 1, 2, 3 or 4 * (the paywall is turned on at step 4) * * Valid data in the MEGARequest object received in onRequestFinish when the error code is MEGAErrorTypeApiOk: * + [MEGARequest number] - Returns the number of warnings (1, 2, 3 or 4). * * Possible errors in addition to the standard dev ones are: * + MEGAErrorTypeApiEFailed - your account is not in the RED stoplight state * * @param command The subcommand for the specific operation * @param email Optional email of the target email's account. If nil, it will use the logged-in account * @param delegate MEGARequestDelegate to track this request */ - (void)sendDevCommand:(NSString *)command email:(NSString *)email delegate:(id)delegate; /** * @brief Returns the current session key. * * You have to be logged in to get a valid session key. Otherwise, * this function returns nil. * * @return Current session key. */ - (nullable NSString *)dumpSession; /** * @brief Returns the current sequence number * * The sequence number indicates the state of a MEGA account known by the SDK. * When external changes are received via actionpackets, the sequence number is * updated and changes are commited to the local cache. * * @return The current sequence number */ - (nullable NSString *)sequenceNumber; /** * @brief Get an authentication token that can be used to identify the user account * * If this MEGASdk object is not logged into an account, this function will return nil * * The value returned by this function can be used in other instances of MEGASdk * thanks to the function [MEGASdk setAccountAuth]. * * @return Authentication token */ - (nullable NSString *)accountAuth; /** * @brief Use an authentication token to identify an account while accessing public folders * * This function is useful to preserve the PRO status when a public folder is being * used. The identifier will be sent in all API requests made after the call to this function. * * To stop using the current authentication token, it's needed to explicitly call * this function with nil as parameter. Otherwise, the value set would continue * being used despite this MEGASdk object is logged in or logged out. * * It's recommended to call this function before the usage of [MEGASdk loginToFolder] * * @param accountAuth Authentication token used to identify the account of the user. * You can get it using [MEGASdk accountAuth] with an instance of MEGASdk logged into * an account. */ - (void)setAccountAuth:(nullable NSString *)accountAuth; /** * @brief Check if the MEGASdk object is logged in. * @return 0 if not logged in, Otherwise, a number > 0. */ - (NSInteger)isLoggedIn; /** * @brief Check if we are logged in into an Ephemeral account ++ * @return true if logged into an Ephemeral account ++, Otherwise return false */ - (BOOL)isEphemeralPlusPlus; /** * @brief Fetch the filesystem in MEGA. * * The MEGASdk object must be logged in in an account or a public folder * to successfully complete this request. * * The associated request type with this request is MEGARequestTypeFetchNodes. * * @param delegate Delegate to track this request. */ - (void)fetchNodesWithDelegate:(id)delegate; /** * @brief Fetch the filesystem in MEGA. * * The MEGASdk object must be logged in in an account or a public folder * to successfully complete this request. * * The associated request type with this request is MEGARequestTypeFetchNodes. * */ - (void)fetchNodes; /** * @brief Logout of the MEGA account. * * The associated request type with this request is MEGARequestTypeLogout. * * @param delegate Delegate to track this request. */ - (void)logoutWithDelegate:(id)delegate; /** * @brief Logout of the MEGA account. * * The associated request type with this request is MEGARequestTypeLogout. * */ - (void)logout; /** * @brief Logout of the MEGA account without invalidating the session * * The associated request type with this request is MEGARequestTypeLogout * * @param delegate Delegate to track this request. */ - (void)localLogoutWithDelegate:(id)delegate; /** * @brief Logout of the MEGA account without invalidating the session * * The associated request type with this request is MEGARequestTypeLogout * */ - (void)localLogout; /** * @brief Invalidate the existing cache and create a fresh one */ - (void)invalidateCache; /** * @brief Estimate the strength of a password * * Possible return values are: * - PasswordStrengthVeryWeak = 0 * - PasswordStrengthWeak = 1 * - PasswordStrengthMedium = 2 * - PasswordStrengthGood = 3 * - PasswordStrengthStrong = 4 * * @param password Password to check * @return Estimated strength of the password */ - (PasswordStrength)passwordStrength:(NSString *)password; /** * @brief Check if the password is correct for the current account * @param password Password to check * @return YES if the password is correct for the current account, otherwise NO. */ - (BOOL)checkPassword:(NSString *)password; /** * @brief Returns the credentials of the currently open account * * If the MEGASdk object isn't logged in or there's no signing key available, * this function returns nil * * @return Fingerprint of the signing key of the current account */ - (NSString *)myCredentials; /** * Returns the credentials of a given user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns MEGAUserAttributeED25519PublicKey * - [MEGARequest flag] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest password] - Returns the credentials in hexadecimal format * * @param user MEGAUser of the contact (@see [MEGASDK contactForEmail:]) to get the fingerprint * @param delegate MEGARequestDelegate to track this request */ - (void)getUserCredentials:(MEGAUser *)user delegate:(id)delegate; /** * @brief Checks if credentials are verified for the given user * * @param user MEGAUser of the contact whose credentiasl want to be checked * @return YES if verified, NO otherwise */ - (BOOL)areCredentialsVerifiedOfUser:(MEGAUser *)user; /** * @brief Verify credentials of a given user * * This function allow to tag credentials of a user as verified. It should be called when the * logged in user compares the fingerprint of the user (provided by an independent and secure * method) with the fingerprint shown by the app (@see [MEGASDK getUserCredentials:]). * * The associated request type with this request is MEGARequestTypeVerifyCredentials * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns userhandle * * @param user MEGAUser of the contact whose credentials want to be verified * @param delegate MEGARequestDelegate to track this request */ - (void)verifyCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate; /** * @brief Reset credentials of a given user * * Call this function to forget the existing authentication of keys and signatures for a given * user. A full reload of the account will start the authentication process again. * * The associated request type with this request is MEGARequestTypeVerifyCredentials * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns userhandle * - [MEGARequest flag] - Returns YES * * @param user MEGAUser of the contact whose credentials want to be reset * @param delegate MEGARequestDelegate to track this request */ - (void)resetCredentialsOfUser:(MEGAUser *)user delegate:(id)delegate; /** * @brief Reset credentials of a given user * * Call this function to forget the existing authentication of keys and signatures for a given * user. A full reload of the account will start the authentication process again. * * The associated request type with this request is MEGARequestTypeVerifyCredentials * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns userhandle * - [MEGARequest flag] - Returns YES * * @param user MEGAUser of the contact whose credentials want to be reset */ - (void)resetCredentialsOfUser:(MEGAUser *)user; #pragma mark - Create account and confirm account Requests /** * @brief Resume a registration process for an Ephemeral++ account * * When a user begins the account registration process by calling * [MEGASdk createEphemeralAccountPlusPlus] an ephemeral++ account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see [MEGASdk sendSignupLink] and also to receive notifications in case the * user confirms the account using another client ([MegaGlobalListener onAccountUpdate] or * [MEGADelegate onAccountUpdate]. It is also possible to cancel the registration process by * [MEGASdk cancelCreateAccount], which invalidates the signup link associated to the ephemeral * session (the session will be still valid). * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MegaRequest object received on callbacks: * - [MegaRequest getSessionKey] - Returns the session id to resume the process * - [MegaRequest getParamType] - Returns the value 4 * * In case the account is already confirmed, the associated request will fail with * error MEGAErrorTypeApiEArgs. * * @param firstname Firstname of the user * @param lastname Lastname of the user */ - (void)createEphemeralAccountPlusPlusWithFirstname:(NSString *)firstname lastname:(NSString *)lastname; /** * @brief Resume a registration process for an Ephemeral++ account * * When a user begins the account registration process by calling * [MEGASdk createEphemeralAccountPlusPlus] an ephemeral++ account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see [MEGASdk sendSignupLink] and also to receive notifications in case the * user confirms the account using another client ([MegaGlobalListener onAccountUpdate] or * [MEGADelegate onAccountUpdate]. It is also possible to cancel the registration process by * [MEGASdk cancelCreateAccount], which invalidates the signup link associated to the ephemeral * session (the session will be still valid). * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MegaRequest object received on callbacks: * - [MegaRequest getSessionKey] - Returns the session id to resume the process * - [MegaRequest getParamType] - Returns the value 4 * * In case the account is already confirmed, the associated request will fail with * error MEGAErrorTypeApiEArgs * * @param firstname Firstname of the user * @param lastname Lastname of the user * @param delegate Delegate to track this request. */ - (void)createEphemeralAccountPlusPlusWithFirstname:(NSString *)firstname lastname:(NSString *)lastname delegate:(id)delegate; /** * @brief Initialize the creation of a new MEGA account. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest password] - Returns the password for the account * - [MEGARequest name] - Returns the firstname of the user * - [MEGARequest text] - Returns the lastname of the user * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest sessionKey] - Returns the session id to resume the process * * If this request succeed, a new ephemeral session will be created for the new user * and a confirmation email will be sent to the specified email address. The app may * resume the create-account process by using [MEGASdk resumeCreateAccountWithSessionId:]. * * If an account with the same email already exists, you will get the error code * MEGAErrorTypeApiEExist in onRequestFinish * * @param email Email for the account * @param password Password for the account * @param firstname Firstname of the user * @param lastname Lastname of the user * @param delegate Delegate to track this request. */ - (void)createAccountWithEmail:(NSString *)email password:(NSString *)password firstname:(NSString *)firstname lastname:(NSString *)lastname delegate:(id)delegate; /** * @brief Initialize the creation of a new MEGA account. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest password] - Returns the password for the account * - [MEGARequest name] - Returns the firstname of the user * - [MEGARequest text] - Returns the lastname of the user * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest sessionKey] - Returns the session id to resume the process * * If this request succeed, a new ephemeral session will be created for the new user * and a confirmation email will be sent to the specified email address. The app may * resume the create-account process by using [MEGASdk resumeCreateAccountWithSessionId:]. * * If an account with the same email already exists, you will get the error code * MEGAErrorTypeApiEExist in onRequestFinish * * @param email Email for the account * @param password Password for the account * @param firstname Firstname of the user * @param lastname Lastname of the user */ - (void)createAccountWithEmail:(NSString *)email password:(NSString *)password firstname:(NSString *)firstname lastname:(NSString *)lastname; /** * @brief Resume a registration process * * When a user begins the account registration process by calling [MEGASdk createAccountWithEmail: * password:firstname:lastname:delegate:], an ephemeral account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see [MEGASdk sendSignupLinkWithEmail:name:password:delegate:]) and also * to receive notifications in case the user confirms the account using another client * ([MEGAGlobalDelegate onAccountUpdate:] or [MEGADelegate onAccountUpdate:]).It is also possible * to cancel the registration process by [MEGASdk cancelCreateAccount:delegate:], which invalidates * the signup link associated to the ephemeral session (the session will be still valid). * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest sessionKey] - Returns the session id to resume the process * - [MEGARequest paramType] - Returns the value 1 * * In case the account is already confirmed, the associated request will fail with * error MEGAErrorTypeApiEArgs. * * @param sessionId Session id valid for the ephemeral account (@see [MEGASdk createAccountWithEmail:password:firstname:lastname:]) * @param delegate MEGARequestDelegate to track this request */ - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId delegate:(id)delegate; /** * @brief Resume a registration process * * When a user begins the account registration process by calling [MEGASdk createAccountWithEmail: * password:firstname:lastname:delegate:], an ephemeral account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see [MEGASdk sendSignupLinkWithEmail:name:password:delegate:]) and also * to receive notifications in case the user confirms the account using another client * ([MEGAGlobalDelegate onAccountUpdate:] or [MEGADelegate onAccountUpdate:]).It is also possible * to cancel the registration process by [MEGASdk cancelCreateAccount:delegate:], which invalidates * the signup link associated to the ephemeral session (the session will be still valid). * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest sessionKey] - Returns the session id to resume the process * - [MEGARequest paramType] - Returns the value 1 * * In case the account is already confirmed, the associated request will fail with * error MEGAErrorTypeApiEArgs. * * @param sessionId Session id valid for the ephemeral account (@see [MEGASdk createAccountWithEmail:password:firstname:lastname:]) */ - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId; /** * @brief Cancel a registration process * * If a signup link has been generated during registration process, call this function * to invalidate it. The ephemeral session will not be invalidated, only the signup link. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the value 2 * * @param delegate MEGARequestDelegate to track this request */ - (void)cancelCreateAccountWithDelegate:(id)delegate; /** * @brief Cancel a registration process * * If a signup link has been generated during registration process, call this function * to invalidate it. The ephemeral session will not be invalidated, only the signup link. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the value 2 * */ - (void)cancelCreateAccount; /** * @brief Sends the confirmation email for a new account * * This function is useful to send the confirmation link again or to send it to a different * email address, in case the user mistyped the email at the registration form. It can only * be used after a successful call to [MEGASdk createAccount] or [MEGASdk resumeCreateAccount]. * * The associated request type with this request is MEGARequestTypeSendSignupLink. * * @param email Email for the account * @param name Firstname of the user * @param delegate MEGARequestDelegate to track this request * */ - (void)resendSignupLinkWithEmail:(NSString *)email name:(NSString *)name delegate:(id)delegate; /** * @brief Get information about a confirmation link or a new signup link. * * The associated request type with this request is MEGARequestTypeQuerySignUpLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the confirmation link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the confirmation link. * - [MEGARequest name] - Returns the name associated with the confirmation link. * - [MEGARequest flag] - Returns true if the account was automatically confirmed, otherwise false * * If already logged-in into a different account, you will get the error code MEGAErrorTypeApiEAccess * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MEGAErrorTypeApiEExpired in onRequestFinish. * In both cases, the [MEGARequest email] will return the email of the account that was attempted * to confirm, and the [MEGARequest name] will return the name. * * @param link Confirmation link * @param delegate Delegate to track this request */ - (void)querySignupLink:(NSString *)link delegate:(id)delegate; /** * @brief Get information about a confirmation link or a new signup link. * * The associated request type with this request is MEGARequestTypeQuerySignUpLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the confirmation link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the confirmation link. * - [MEGARequest name] - Returns the name associated with the confirmation link. * - [MEGARequest flag] - Returns true if the account was automatically confirmed, otherwise false * * If already logged-in into a different account, you will get the error code MEGAErrorTypeApiEAccess * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MEGAErrorTypeApiEExpired in onRequestFinish. * In both cases, the [MEGARequest email] will return the email of the account that was attempted * to confirm, and the [MEGARequest name] will return the name. * * @param link Confirmation link. */ - (void)querySignupLink:(NSString *)link; /** * @brief Confirm a MEGA account using a confirmation link and the user password. * * The associated request type with this request is MEGARequestTypeConfirmAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the confirmation link * - [MEGARequest password] - Returns the password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Email of the account * - [MEGARequest name] - Name of the user * * As a result of a successfull confirmation, the app will receive the callback * [MEGADelegate onEvent: event:] and [MEGAGlobalDelegate onEvent: event:] with an event of type * EventAccountConfirmation. You can check the email used to confirm * the account by checking [MEGAEvent text]. @see [MEGADelegate onEvent: event:]. * * If already logged-in into a different account, you will get the error code MEGAErrorTypeApiEAccess * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MEGAErrorTypeApiEExpired in onRequestFinish. * In both cases, the [MEGARequest email] will return the email of the account that was attempted * to confirm, and the [MEGARequest name] will return the name. * * @param link Confirmation link. * @param delegate Delegate to track this request. */ - (void)confirmAccountWithLink:(NSString *)link delegate:(id)delegate; /** * @brief Confirm a MEGA account using a confirmation link and the user password. * * The associated request type with this request is MEGARequestTypeConfirmAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the confirmation link * - [MEGARequest password] - Returns the password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Email of the account * - [MEGARequest name] - Name of the user * * As a result of a successfull confirmation, the app will receive the callback * [MEGADelegate onEvent: event:] and [MEGAGlobalDelegate onEvent: event:] with an event of type * EventAccountConfirmation. You can check the email used to confirm * the account by checking [MEGAEvent text]. @see [MEGADelegate onEvent: event:]. * * If already logged-in into a different account, you will get the error code MEGAErrorTypeApiEAccess * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MEGAErrorTypeApiEExpired in onRequestFinish. * In both cases, the [MEGARequest email] will return the email of the account that was attempted * to confirm, and the [MEGARequest name] will return the name. * * @param link Confirmation link. */ - (void)confirmAccountWithLink:(NSString *)link; /** * @brief Initialize the reset of the existing password, with and without the Master Key. * * The associated request type with this request is MEGARequestTypeGetRecoveryLink. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest flag] - Returns whether the user has a backup of the master key or not. * * If this request succeed, a recovery link will be sent to the user. * If no account is registered under the provided email, you will get the error code * MEGAErrorTypeApiENoent in onRequestFinish * * @param email Email used to register the account whose password wants to be reset. * @param hasMasterKey YES if the user has a backup of the master key. Otherwise, NO. * @param delegate Delegate to track this request. */ - (void)resetPasswordWithEmail:(NSString *)email hasMasterKey:(BOOL)hasMasterKey delegate:(id)delegate; /** * @brief Initialize the reset of the existing password, with and without the Master Key. * * The associated request type with this request is MEGARequestTypeGetRecoveryLink. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email for the account * - [MEGARequest flag] - Returns whether the user has a backup of the master key or not. * * If this request succeed, a recovery link will be sent to the user. * If no account is registered under the provided email, you will get the error code * MEGAErrorTypeApiENoent in onRequestFinish * * @param email Email used to register the account whose password wants to be reset. * @param hasMasterKey YES if the user has a backup of the master key. Otherwise, NO. */ - (void)resetPasswordWithEmail:(NSString *)email hasMasterKey:(BOOL)hasMasterKey; /** * @brief Get information about a recovery link created by [MEGASdk resetPasswordWithEmail:hasMasterKey:]. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * - [MEGARequest flag] - Return whether the link requires masterkey to reset password. * * @param link Recovery link (#recover) * @param delegate Delegate to track this request */ - (void)queryResetPasswordLink:(NSString *)link delegate:(id)delegate; /** * @brief Get information about a recovery link created by [MEGASdk resetPasswordWithEmail:hasMasterKey:]. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * - [MEGARequest flag] - Return whether the link requires masterkey to reset password. * * @param link Recovery link (#recover) */ - (void)queryResetPasswordLink:(NSString *)link; /** * @brief Set a new password for the account pointed by the recovery link. * * Recovery links are created by calling [MEGASdk resetPasswordWithEmail:hasMasterKey:] and may or may not * require to provide the master key. * * @see The flag of the MEGARequestTypeQueryRecoveryLink in [MEGASdk queryResetPasswordLink:] * * The associated request type with this request is MEGARequestTypeConfirmRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * - [MEGARequest flag] - Return whether the link requires masterkey to reset password. * * @param link The recovery link sent to the user's email address. * @param newPassword The new password to be set. * @param masterKey Base64-encoded string containing the master key (optional). * @param delegate Delegate to track this request */ - (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey delegate:(id)delegate; /** * @brief Set a new password for the account pointed by the recovery link. * * Recovery links are created by calling [MEGASdk resetPasswordWithEmail:hasMasterKey:] and may or may not * require to provide the master key. * * @see The flag of the MEGARequestTypeQueryRecoveryLink in [MEGASdk queryResetPasswordLink:] * * The associated request type with this request is MEGARequestTypeConfirmRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * - [MEGARequest flag] - Return whether the link requires masterkey to reset password. * * @param link The recovery link sent to the user's email address. * @param newPassword The new password to be set. * @param masterKey Base64-encoded string containing the master key (optional). */ - (void)confirmResetPasswordWithLink:(NSString *)link newPassword:(NSString *)newPassword masterKey:(nullable NSString *)masterKey; /** * @brief Check that the provided recovery key (master key) is correct * * The associated request type with this request is MEGARequestTypeCheckRecoveryKey * No data in the MEGARequest object received on all callbacks * * @param link The recovery link sent to the user's email address. * @param recoveryKey Base64-encoded string containing the recoveryKey (masterKey). * @param delegate Delegate to track this request */ - (void)checkRecoveryKey:(NSString *)link recoveryKey:(NSString *)recoveryKey delegate:(id)delegate; /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MEGARequestTypeGetCancelLink. * * If this request succeed, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @see [MEGASdk confirmCancelAccountWithLink:password:] * * @param delegate Delegate to track this request */ - (void)cancelAccountWithDelegate:(id)delegate; /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MEGARequestTypeGetCancelLink. * * If this request succeed, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @see [MEGASdk confirmCancelAccountWithLink:password:] * */ - (void)cancelAccount; /** * @brief Get information about a cancel link created by [MEGASdk cancelAccount]. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the cancel link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Cancel link (#cancel) * @param delegate Delegate to track this request */ - (void)queryCancelLink:(NSString *)link delegate:(id)delegate; /** * @brief Get information about a cancel link created by [MEGASdk cancelAccount]. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the cancel link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Cancel link (#cancel) */ - (void)queryCancelLink:(NSString *)link; /** * @brief Effectively parks the user's account without creating a new fresh account. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * * The associated request type with this request is MEGARequestTypeConfirmCancelLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Cancellation link sent to the user's email address; * @param password Password for the account. * @param delegate Delegate to track this request */ - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)password delegate:(id)delegate; /** * @brief Effectively parks the user's account without creating a new fresh account. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * * The associated request type with this request is MEGARequestTypeConfirmCancelLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Cancellation link sent to the user's email address; * @param password Password for the account. */ - (void)confirmCancelAccountWithLink:(NSString *)link password:(NSString *)password; /** * @brief Allow to resend the verification email for Weak Account Protection * * The verification email will be resent to the same address as it was previously sent to. * * This function can be called if the the reason for being blocked is: * 700: the account is supended for Weak Account Protection. * * If the logged in account is not suspended or is suspended for some other reason, * onRequestFinish will be called with the error code MEGAErrorTypeApiEAccess. * * If the logged in account has not been sent the unlock email before, * onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. * * @param delegate MEGARequestDelegate to track this request */ - (void)resendVerificationEmailWithDelegate:(id)delegate; /** * @brief Allow to resend the verification email for Weak Account Protection * * The verification email will be resent to the same address as it was previously sent to. * * This function can be called if the the reason for being blocked is: * 700: the account is supended for Weak Account Protection. * * If the logged in account is not suspended or is suspended for some other reason, * onRequestFinish will be called with the error code MEGAErrorTypeApiEAccess. * * If the logged in account has not been sent the unlock email before, * onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. */ - (void)resendVerificationEmail; /** * @brief Initialize the change of the email address associated to the account. * * The associated request type with this request is MEGARequestTypeGetChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest email] - Return the email associated with the link * * If this request succeed, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param email The new email to be associated to the account. * @param delegate Delegate to track this request */ - (void)changeEmail:(NSString *)email delegate:(id)delegate; /** * @brief Initialize the change of the email address associated to the account. * * The associated request type with this request is MEGARequestTypeGetChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest email] - Return the email associated with the link * * If this request succeed, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * @param email The new email to be associated to the account. */ - (void)changeEmail:(NSString *)email; /** * @brief Get information about a change-email link created by [MEGASdk changeEmail:]. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Change-email link (#verify) * @param delegate Delegate to track this request */ - (void)queryChangeEmailLink:(NSString *)link delegate:(id)delegate; /** * @brief Get information about a change-email link created by [MEGASdk changeEmail:]. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The associated request type with this request is MEGARequestTypeQueryRecoveryLink * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * If the account logged-in is different account than the one for which the link * was generated, onRequestFinish will be called with the error code MEGAErrorTypeApiEAccess. * * @param link Change-email link (#verify) */ - (void)queryChangeEmailLink:(NSString *)link; /** * @brief Effectively changes the email address associated to the account. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The associated request type with this request is MEGARequestTypeConfirmChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Change-email link sent to the user's email address. * @param password Password for the account. * @param delegate Delegate to track this request */ - (void)confirmChangeEmailWithLink:(NSString *)link password:(NSString *)password delegate:(id)delegate; /** * @brief Effectively changes the email address associated to the account. * * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. * * The associated request type with this request is MEGARequestTypeConfirmChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link * - [MEGARequest password] - Returns the new password * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest email] - Return the email associated with the link * * @param link Change-email link sent to the user's email address. * @param password Password for the account. */ - (void)confirmChangeEmailWithLink:(NSString *)link password:(NSString *)password; /** * @brief Create a contact link * * The associated request type with this request is MEGARequestTypeContactLinkCreate. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest flag] - Returns the value of \c renew parameter * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Return the handle of the new contact link * * @param renew YES to invalidate the previous contact link (if any). * @param delegate Delegate to track this request */ - (void)contactLinkCreateRenew:(BOOL)renew delegate:(id)delegate; /** * @brief Create a contact link * * The associated request type with this request is MEGARequestTypeContactLinkCreate. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest flag] - Returns the value of \c renew parameter * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Return the handle of the new contact link * * @param renew YES to invalidate the previous contact link (if any). */ - (void)contactLinkCreateRenew:(BOOL)renew; /** * @brief Get information about a contact link * * The associated request type with this request is MEGARequestTypeContactLinkQuery. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest parentHandle] - Returns the userhandle of the contact * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest name] - Returns the first name of the contact * - [MEGARequest text] - Returns the last name of the contact * * @param handle Handle of the contact link to check * @param delegate Delegate to track this request */ - (void)contactLinkQueryWithHandle:(uint64_t)handle delegate:(id)delegate; /** * @brief Get information about a contact link * * The associated request type with this request is MEGARequestTypeContactLinkQuery. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest parentHandle] - Returns the userhandle of the contact * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest name] - Returns the first name of the contact * - [MEGARequest text] - Returns the last name of the contact * * @param handle Handle of the contact link to check */ - (void)contactLinkQueryWithHandle:(uint64_t)handle; /** * @brief Delete the active contact link * * The associated request type with this request is MEGARequestTypeContactLinkDelete. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact link * * @param delegate Delegate to track this request */ - (void)contactLinkDeleteWithDelegate:(id)delegate; /** * @brief Delete the active contact link * * The associated request type with this request is MEGARequestTypeContactLinkDelete. * * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact link */ - (void)contactLinkDelete; /** * @brief Command to keep mobile apps alive when needed * * When this feature is enabled, API servers will regularly send push notifications * to keep the application running. Before using this function, it's needed to register * a notification token using [MEGASdk registeriOSdeviceToken:] * * The associated request type with this request is MEGARequestTypeKeepMeAlive. * * Valid data in the MEGARequest object received on all callbacks: * - MEGARequest.paramType - Returns the type send in the first parameter * - MEGARequest.flag - Returns YES when the feature is being enabled, otherwise NO * * @param type Type of keep alive desired * Valid values for this parameter: * - KeepMeAliveCameraUploads = 0 * * @param enable YES to enable this feature, NO to disable it * @param delegate MEGARequestDelegate to track this request * * @see [MEGASdk registeriOSdeviceToken:] */ - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable delegate:(id)delegate; /** * @brief Command to keep mobile apps alive when needed * * When this feature is enabled, API servers will regularly send push notifications * to keep the application running. Before using this function, it's needed to register * a notification token using [MEGASdk registeriOSdeviceToken:] * * The associated request type with this request is MEGARequestTypeKeepMeAlive. * * Valid data in the MEGARequest object received on all callbacks: * - MEGARequest.paramType - Returns the type send in the first parameter * - MEGARequest.flag - Returns YES when the feature is being enabled, otherwise NO * * @param type Type of keep alive desired * Valid values for this parameter: * - KeepMeAliveCameraUploads = 0 * * @param enable YES to enable this feature, NO to disable it * * @see [MEGASdk registeriOSdeviceToken:] */ - (void)keepMeAliveWithType:(KeepMeAlive)type enable:(BOOL)enable; /** * @brief Check the reason of being blocked. * * The associated request type with this request is MEGARequestTypeWhyAmIBlocked. * * This request can be sent internally at anytime (whenever an account gets blocked), so * a MEGAGlobalListener should process the result, show the reason and logout. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - MEGARequest.text - Returns the reason string (in English) * - MEGARequest.number - Returns the reason code. Possible values: * 0: The account is not blocked * 200: suspension message for any type of suspension, but copyright suspension. * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. * 700: the account is supended for Weak Account Protection. * * If the error code in the MEGARequest object received in onRequestFinish * is MEGAErrorTypeApiOk, the user is not blocked. * * @param delegate MEGARequestDelegate to track this request */ - (void)whyAmIBlockedWithDelegate:(id)delegate; /** * @brief Check the reason of being blocked. * * The associated request type with this request is MEGARequestTypeWhyAmIBlocked. * * This request can be sent internally at anytime (whenever an account gets blocked), so * a MEGAGlobalListener should process the result, show the reason and logout. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - MEGARequest.text - Returns the reason string (in English) * - MEGARequest.number - Returns the reason code. Possible values: * 0: The account is not blocked * 200: suspension message for any type of suspension, but copyright suspension. * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. * 700: the account is supended for Weak Account Protection. * * If the error code in the MEGARequest object received in onRequestFinish * is MEGAErrorTypeApiOk, the user is not blocked. */ - (void)whyAmIBlocked; /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * * After the PSA has been accepted or dismissed by the user, app should * use [MEGASdk setPSAWithIdentifier:] [MEGASdk setPSAWithIdentifier:delegate:] to notify API servers about * this event and do not get the same PSA again in the next call to this function. * * The associated request type with this request is MEGARequestTypeGetPSA. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the id of the PSA (useful to call [MEGASdk setPSAWithIdentifier:] * [MEGASdk setPSAWithIdentifier:delegate:] later) * - [MEGARequest name] - Returns the title of the PSA * - [MEGARequest text] - Returns the text of the PSA * - [MEGARequest file] - Returns the URL of the image of the PSA * - [MEGARequest password] - Returns the text for the possitive button (or an empty string) * - [MEGARequest link] - Returns the link for the possitive button (or an empty string) * * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MEGAErrorTypeApiENoent * * @param delegate MEGARequestDelegate to track this request * @see [MEGASdk setPSAWithIdentifier:] [MEGASdk setPSAWithIdentifier:delegate:] */ - (void)getPSAWithDelegate:(id)delegate; /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * * After the PSA has been accepted or dismissed by the user, app should * use [MEGASdk setPSAWithIdentifier:] [MEGASdk setPSAWithIdentifier:delegate:] to notify API servers about * this event and do not get the same PSA again in the next call to this function. * * The associated request type with this request is MEGARequestTypeGetPSA. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the id of the PSA (useful to call [MEGASdk setPSAWithIdentifier:] * [MEGASdk setPSAWithIdentifier:delegate:] later) * - [MEGARequest name] - Returns the title of the PSA * - [MEGARequest text] - Returns the text of the PSA * - [MEGARequest file] - Returns the URL of the image of the PSA * - [MEGARequest password] - Returns the text for the possitive button (or an empty string) * - [MEGARequest link] - Returns the link for the possitive button (or an empty string) * * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MEGAErrorTypeApiENoent * * @see [MEGASdk setPSAWithIdentifier:] [MEGASdk setPSAWithIdentifier:delegate:] */ - (void)getPSA; /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * * After the PSA has been accepted or dismissed by the user, app should * use [MEGASdk setPSAWithIdentifier:] or [MEGASdk setPSAWithIdentifier:delegate:] to notify API servers about * this event and do not get the same PSA again in the next call to this function. * * The associated request type with this request is MEGARequestTypeGetPSA. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the id of the PSA (useful to call [MEGASdk setPSAWithIdentifier:] * [MEGASdk setPSAWithIdentifier:delegate:] later) * - [MEGARequest email] - Returns the URL (or an empty string) * - [MEGARequest name] - Returns the title of the PSA * - [MEGARequest text] - Returns the text of the PSA * - [MEGARequest file] - Returns the URL of the image of the PSA * - [MEGARequest password] - Returns the text for the possitive button (or an empty string) * - [MEGARequest link] - Returns the link for the possitive button (or an empty string) * * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MEGAErrorTypeApiENoent * * @param delegate MEGARequestDelegate to track this request * @see [MEGASdk setPSAWithIdentifier:] [MEGASdk setPSAWithIdentifier:delegate:] */ - (void)getURLPublicServiceAnnouncementWithDelegate:(id)delegate; /** * @brief Notify API servers that a PSA (Public Service Announcement) has been already seen * * The associated request type with this request is MEGARequestTypeSetAttrUser. * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeLastPSA * - [MEGARequest text] - Returns the id passed in the first parameter (as a string) * * @param identifier Identifier of the PSA * @param delegate MEGARequestDelegate to track this request * * @see [MEGASdk getPSA] [MEGASdk getPSAWithDelegate:] */ - (void)setPSAWithIdentifier:(NSInteger)identifier delegate:(id)delegate; /** * @brief Notify API servers that a PSA (Public Service Announcement) has been already seen * * The associated request type with this request is MEGARequestTypeSetAttrUser. * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeLastPSA * - [MEGARequest text] - Returns the id passed in the first parameter (as a string) * * @param identifier Identifier of the PSA * * @see [MEGASdk getPSA] [MEGASdk getPSAWithDelegate:] */ - (void)setPSAWithIdentifier:(NSInteger)identifier; /** * @brief Command to acknowledge user alerts. * * Other clients will be notified that alerts to this point have been seen. * * @see [MEGASdk userAlertList] */ - (void)acknowledgeUserAlertsWithDelegate:(id)delegate; /** * @brief Command to acknowledge user alerts. * * Other clients will be notified that alerts to this point have been seen. * * @see [MEGASdk userAlertList] */ - (void)acknowledgeUserAlerts; #pragma mark - Notifications /** * @brief Set last read notification for Notification Center * * The type associated with this request is MEGARequestTypeSetAttrUser * * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeLastReadNotification * - [MEGARequest number] - Returns the ID to be set as last read * * Note that any notifications with ID equal to or less than the given one will be marked as seen * in Notification Center. * * @param notificationId ID of the notification to be set as last read. Value `0` is an invalid ID. * Passing `0` will clear a previously set last read value. * @param delegate MEGARequestDelegate to track this request */ - (void)setLastReadNotificationWithNotificationId:(uint32_t)notificationId delegate:(id)delegate; /** * @brief Get last read notification for Notification Center * * The type associated with this request is MEGARequestTypeSetAttrUser * * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeLastReadNotification * * When onRequestFinish received MEGAErrorTypeApiOk, valid data in the MegaRequest object is: * - [MEGARequest number] - Returns the ID of the last read Notification * Note that when the ID returned here was `0` it means that no ID was set as last read. * Note that the value returned here should be treated like a 32bit unsigned int. * * @param delegate MEGARequestDelegate to track this request */ - (void)getLastReadNotificationWithDelegate:(id)delegate; /** * @brief Get the list of IDs for enabled notifications * * You take the ownership of the returned value * * @return List of IDs for enabled notifications */ - (nullable MEGAIntegerList *)getEnabledNotifications; /** * @brief Get list of available notifications for Notification Center * * The associated request type with this request is MEGARequestTypeGetNotifications * * When onRequestFinish received MEGAErrorTypeApiOk, valid data in the MegaRequest object is: * - [MegaRequest megaNotifications] - Returns the list of notifications * * When onRequestFinish errored, the error code associated to the MegaError can be: * - MEGAErrorTypeApiENoent - No such notifications exist, and MegaRequest::getMegaNotifications * will return a non-null, empty list. * - MEGAErrorTypeApiEAccess - No user was logged in. * - MEGAErrorTypeApiEInternal - Received answer could not be read. * * @param delegate MEGARequestDelegate to track this request */ - (void)getNotificationsWithDelegate:(id)delegate; #pragma mark - Filesystem changes Requests /** * @brief Create a folder in the MEGA account. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns the handle of the parent folder * - [MEGARequest name] - Returns the name of the new folder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new folder * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param name Name of the new folder. * @param parent Parent folder. * @param delegate Delegate to track this request. */ - (void)createFolderWithName:(NSString *)name parent:(MEGANode *)parent delegate:(id)delegate; /** * @brief Create a folder in the MEGA account. * * The associated request type with this request is MEGARequestTypeCreateAccount. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns the handle of the parent folder * - [MEGARequest name] - Returns the name of the new folder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new folder * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param name Name of the new folder. * @param parent Parent folder. */ - (void)createFolderWithName:(NSString *)name parent:(MEGANode *)parent; /** * @brief Move a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeMove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to move. * @param newParent New parent for the node. * @param delegate Delegate to track this request. */ - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent delegate:(id)delegate; /** * @brief Move a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeMove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to move. * @param newParent New parent for the node. */ - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent; /** * @brief Move a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeMove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * - [MEGARequest name] - Returns the name for the new node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to move. * @param newParent New parent for the node. * @param newName Name for the new node. * @param delegate Delegate to track this request. */ - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName delegate:(id)delegate; /** * @brief Move a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeMove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * - [MEGARequest name] - Returns the name for the new node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to move. * @param newParent New parent for the node. * @param newName Name for the new node. */ - (void)moveNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName; /** * @brief Copy a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeCopy. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * - [MEGARequest publicNode] - Returns the node to copy (if it is a public node) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to copy. * @param newParent New parent for the node. * @param delegate Delegate to track this request. */ - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent delegate:(id)delegate; /** * @brief Copy a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeCopy. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to move * - [MEGARequest parentHandle] - Returns the handle of the new parent for the node * - [MEGARequest publicNode] - Returns the node to copy (if it is a public node) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to copy. * @param newParent New parent for the node. */ - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent; /** * @brief Copy a node in the MEGA account changing the file name * * The associated request type with this request is MEGARequestTypeCopy * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to copy * - [MEGARequest parentHandle] - Returns the handle of the new parent for the new node * - [MEGARequest publicNode] - Returns the node to copy * - [MEGARequest name] - Returns the name for the new node * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to copy * @param newParent Parent for the new node * @param newName Name for the new node * * This parameter is only used if the original node is a file and it isn't a public node, * otherwise, it's ignored. * * @param delegate Delegate to track this request */ - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName delegate:(id)delegate; /** * @brief Copy a node in the MEGA account changing the file name * * The associated request type with this request is MEGARequestTypeCopy * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to copy * - [MEGARequest parentHandle] - Returns the handle of the new parent for the new node * - [MEGARequest publicNode] - Returns the node to copy * - [MEGARequest name] - Returns the name for the new node * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to copy * @param newParent Parent for the new node * @param newName Name for the new node * * This parameter is only used if the original node is a file and it isn't a public node, * otherwise, it's ignored. */ - (void)copyNode:(MEGANode *)node newParent:(MEGANode *)newParent newName:(NSString *)newName; /** * @brief Rename a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeRename. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to rename * - [MEGARequest name] - Returns the new name for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to modify. * @param newName New name for the node. * @param delegate Delegate to track this request. */ - (void)renameNode:(MEGANode *)node newName:(NSString *)newName delegate:(id)delegate; /** * @brief Rename a node in the MEGA account. * * The associated request type with this request is MEGARequestTypeRename. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to rename * - [MEGARequest name] - Returns the new name for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to modify. * @param newName New name for the node. */ - (void)renameNode:(MEGANode *)node newName:(NSString *)newName; /** * @brief Remove a node from the MEGA account. * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use [MEGASdk moveNode:newParent:delegate:]. * * If the node has previous versions, they will be deleted too. * * The associated request type with this request is MEGARequestTypeRemove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to rename * - [MEGARequest flag] - Returns NO because previous versions won't be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param node Node to remove. * @param delegate Delegate to track this request. */ - (void)removeNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Remove a node from the MEGA account. * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use [MEGASdk moveNode:newParent:delegate:]. * * If the node has previous versions, they will be deleted too. * * The associated request type with this request is MEGARequestTypeRemove. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to rename * - [MEGARequest flag] - Returns NO because previous versions won't be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param node Node to remove. */ - (void)removeNode:(MEGANode *)node; /** * @brief Remove all versions from the MEGA account * * The associated request type with this request is MEGARequestTypeRemoveVersions * * When the request finishes, file versions might not be deleted yet. * Deletions are notified using onNodesUpdate callbacks. * * @param delegate MEGARequestDelegate Delegate to track this request */ - (void)removeVersionsWithDelegate:(id)delegate; /** * @brief Remove all versions from the MEGA account * * The associated request type with this request is MEGARequestTypeRemoveVersions * * When the request finishes, file versions might not be deleted yet. * Deletions are notified using onNodesUpdate callbacks. * */ - (void)removeVersions; /** * @brief Remove a version of a file from the MEGA account * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use [MEGASdk moveNode:newParent:delegate:]. * * If the node has previous versions, they won't be deleted. * * The associated request type with this request is MEGARequestTypeRemove * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to remove * - [MEGARequest flag] - Returns YES because previous versions will be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param node Node to remove * @param delegate MEGARequestDelegate to track this request */ - (void)removeVersionNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Remove a version of a file from the MEGA account * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use [MEGASdk moveNode:newParent:delegate:]. * * If the node has previous versions, they won't be deleted. * * The associated request type with this request is MEGARequestTypeRemove * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to remove * - [MEGARequest flag] - Returns YES because previous versions will be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param node Node to remove */ - (void)removeVersionNode:(MEGANode *)node; /** * @brief Restore a previous version of a file * * Only versions of a file can be restored, not the current version (because it's already current). * The node will be copied and set as current. All the version history will be preserved without changes, * being the old current node the previous version of the new current node, and keeping the restored * node also in its previous place in the version history. * * The associated request type with this request is MEGARequestTypeRestore * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to restore * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node with the version to restore * @param delegate MEGARequestDelegate to track this request */ - (void)restoreVersionNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Restore a previous version of a file * * Only versions of a file can be restored, not the current version (because it's already current). * The node will be copied and set as current. All the version history will be preserved without changes, * being the old current node the previous version of the new current node, and keeping the restored * node also in its previous place in the version history. * * The associated request type with this request is MEGARequestTypeRestore * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node to restore * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node with the version to restore */ - (void)restoreVersionNode:(MEGANode *)node; /** * @brief Clean the Rubbish Bin in the MEGA account * * This function effectively removes every node contained in the Rubbish Bin. In order to * avoid accidental deletions, you might want to warn the user about the action. * * The associated request type with this request is MEGARequestTypeCleanRubbishBin. This * request returns MEGAErrorTypeApiENoent if the Rubbish bin is already empty. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param delegate MEGARequestDelegate to track this request */ - (void)cleanRubbishBinWithDelegate:(id)delegate; /** * @brief Clean the Rubbish Bin in the MEGA account * * This function effectively removes every node contained in the Rubbish Bin. In order to * avoid accidental deletions, you might want to warn the user about the action. * * The associated request type with this request is MEGARequestTypeCleanRubbishBin. This * request returns MEGAErrorTypeApiENoent if the Rubbish bin is already empty. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * */ - (void)cleanRubbishBin; #pragma mark - Sharing Requests /** * @brief Share or stop sharing a folder in MEGA with another user using a MEGAUser. * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MEGAShareTypeAccessUnknown. * * The associated request type with this request is MEGARequestTypeShare. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the folder to share * - [MEGARequest email] - Returns the email of the user that receives the shared folder * - [MEGARequest access] - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node The folder to share. It must be a non-root folder. * @param user User that receives the shared folder. * @param level Permissions that are granted to the user. * Valid values for this parameter: * - MEGAShareTypeAccessUnknown = -1 * Stop sharing a folder with this user * * - MEGAShareTypeAccessRead = 0 * - MEGAShareTypeAccessReadWrite = 1 * - MEGAShareTypeAccessFull = 2 * - MEGAShareTypeAccessOwner = 3 * * @param delegate Delegate to track this request. */ - (void)shareNode:(MEGANode *)node withUser:(MEGAUser *)user level:(NSInteger)level delegate:(id)delegate; /** * @brief Share or stop sharing a folder in MEGA with another user using a MEGAUser. * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MEGAShareTypeAccessUnknown. * * The associated request type with this request is MEGARequestTypeShare. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the folder to share * - [MEGARequest email] - Returns the email of the user that receives the shared folder * - [MEGARequest access] - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node The folder to share. It must be a non-root folder. * @param user User that receives the shared folder. * @param level Permissions that are granted to the user. * Valid values for this parameter: * - MEGAShareTypeAccessUnknown = -1 * Stop sharing a folder with this user * * - MEGAShareTypeAccessRead = 0 * - MEGAShareTypeAccessReadWrite = 1 * - MEGAShareTypeAccessFull = 2 * - MEGAShareTypeAccessOwner = 3 * */ - (void)shareNode:(MEGANode *)node withUser:(MEGAUser *)user level:(NSInteger)level; /** * @brief Share or stop sharing a folder in MEGA with another user using his email * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MEGAShareTypeAccessUnknown * * The associated request type with this request is MEGARequestTypeShare * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the folder to share * - [MEGARequest email] - Returns the email of the user that receives the shared folder * - [MEGARequest access] - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node The folder to share. It must be a non-root folder * @param email Email of the user that receives the shared folder. If it doesn't have a MEGA account, the folder will be shared anyway * and the user will be invited to register an account. * * @param level Permissions that are granted to the user * Valid values for this parameter: * - MEGAShareTypeAccessUnknown = -1 * Stop sharing a folder with this user * * - MEGAShareTypeAccessRead = 0 * - MEGAShareTypeAccessReadWrite = 1 * - MEGAShareTypeAccessFull = 2 * - MEGAShareTypeAccessOwner = 3 * * @param delegate MEGARequestDelegate to track this request */ - (void)shareNode:(MEGANode *)node withEmail:(NSString *)email level:(NSInteger)level delegate:(id)delegate; /** * @brief Share or stop sharing a folder in MEGA with another user using his email * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MEGAShareTypeAccessUnknown * * The associated request type with this request is MEGARequestTypeShare * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the folder to share * - [MEGARequest email] - Returns the email of the user that receives the shared folder * - [MEGARequest access] - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node The folder to share. It must be a non-root folder * @param email Email of the user that receives the shared folder. If it doesn't have a MEGA account, the folder will be shared anyway * and the user will be invited to register an account. * * @param level Permissions that are granted to the user * Valid values for this parameter: * - MEGAShareTypeAccessUnknown = -1 * Stop sharing a folder with this user * * - MEGAShareTypeAccessRead = 0 * - MEGAShareTypeAccessReadWrite = 1 * - MEGAShareTypeAccessFull = 2 * - MEGAShareTypeAccessOwner = 3 * */ - (void)shareNode:(MEGANode *)node withEmail:(NSString *)email level:(NSInteger)level; /** * @brief Import a public link to the account. * * The associated request type with this request is MEGARequestTypeImportLink. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to the file * - [MEGARequest parentHandle] - Returns the folder that receives the imported file * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new node in the account * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param megaFileLink Public link to a file in MEGA. * @param parent Parent folder for the imported file. * @param delegate Delegate to track this request. */ - (void)importMegaFileLink:(NSString *)megaFileLink parent:(MEGANode *)parent delegate:(id)delegate; /** * @brief Import a public link to the account. * * The associated request type with this request is MEGARequestTypeImportLink. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to the file * - [MEGARequest parentHandle] - Returns the folder that receives the imported file * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new node in the account * * @param megaFileLink Public link to a file in MEGA. * @param parent Parent folder for the imported file. */ - (void)importMegaFileLink:(NSString *)megaFileLink parent:(MEGANode *)parent; /** * @brief Decrypt password-protected public link * * The associated request type with this request is MEGARequestTypePasswordLink * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the encrypted public link to the file/folder * - [MEGARequest password] - Returns the password to decrypt the link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Decrypted public link * * @param link Password/protected public link to a file/folder in MEGA * @param password Password to decrypt the link * @param delegate MEGARequestDelegate to track this request */ - (void)decryptPasswordProtectedLink:(NSString *)link password:(NSString *)password delegate:(id)delegate; /** * @brief Decrypt password-protected public link * * The associated request type with this request is MEGARequestTypePasswordLink * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the encrypted public link to the file/folder * - [MEGARequest password] - Returns the password to decrypt the link * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Decrypted public link * * @param link Password/protected public link to a file/folder in MEGA * @param password Password to decrypt the link */ - (void)decryptPasswordProtectedLink:(NSString *)link password:(NSString *)password; /** * @brief Encrypt public link with password * * The associated request type with this request is MEGARequestTypePasswordLink * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to be encrypted * - [MEGARequest password] - Returns the password to encrypt the link * - [MEGARequest flag] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Encrypted public link * * @param link Public link to be encrypted, including encryption key for the link * @param password Password to encrypt the link * @param delegate MEGARequestDelegate to track this request */ - (void)encryptLinkWithPassword:(NSString *)link password:(NSString *)password delegate:(id)delegate; /** * @brief Encrypt public link with password * * The associated request type with this request is MEGARequestTypePasswordLink * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to be encrypted * - [MEGARequest password] - Returns the password to encrypt the link * - [MEGARequest flag] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Encrypted public link * * @param link Public link to be encrypted, including encryption key for the link * @param password Password to encrypt the link */ - (void)encryptLinkWithPassword:(NSString *)link password:(NSString *)password; /** * @brief Get a MEGANode from a public link to a file. * * A public node can be imported using [MEGASdk copyNode:newParent:] or downloaded using [MEGASdk startDownloadNode:localPath:] * * The associated request type with this request is MEGARequestTypeGetPublicNode. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to the file * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest publicNode] - Public MEGANode corresponding to the public link * * @param megaFileLink Public link to a file in MEGA. * @param delegate Delegate to track this request. */ - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink delegate:(id)delegate; /** * @brief Get downloads urls for a node * * The associated request type with this request is MEGARequestTypeGetDownloadUrls * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk * - [MEGARequest name] - Returns semicolon-separated download URL(s) to the file * - [MEGARequest link] - Returns semicolon-separated IPv4 of the server in the URL(s) * - [MEGARequest text] - Returns semicolon-separated IPv6 of the server in the URL(s) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue * * @param node Node to get the downloads URLs * @param singleUrl Always return one URL (even for raided files) * @param delegate MEGARequestDelegate to track this request */ - (void)getDownloadUrl:(MEGANode *)node singleUrl:(BOOL)singleUrl delegate:(id)delegate; /** * @brief Get a MEGANode from a public link to a file. * * A public node can be imported using [MEGASdk copyNode:newParent:] or downloaded using [MEGASdk startDownloadNode:localPath:]. * * The associated request type with this request is MEGARequestTypeGetPublicNode. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest link] - Returns the public link to the file * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest publicNode] - Public MEGANode corresponding to the public link * * @param megaFileLink Public link to a file in MEGA. */ - (void)publicNodeForMegaFileLink:(NSString *)megaFileLink; /** * @brief Build the URL for a public link * * @note This function does not create the public link itself. It simply builds the URL * from the provided data. * * @param publicHandle Public handle of the link, in B64url encoding. * @param key Encryption key of the link. * @param isFolder True for folder links, false for file links. * @return The public link for the provided data */ - (NSString *)buildPublicLinkForHandle:(NSString *)publicHandle key:(NSString *)key isFolder:(BOOL)isFolder; /** * @brief Set node label as a node attribute. * Valid values for label attribute are: * - MEGANodeLabelRed = 1 * - MEGANodeLabelOrange = 2 * - MEGANodeLabelYellow = 3 * - MEGANodeLabelGreen = 4 * - MEGANodeLabelBlue = 5 * - MEGANodeLabelPurple = 6 * - MEGANodeLabelGrey = 7 * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns the label for the node * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeLabel * * @param node Node that will receive the information. * @param label Label of the node * @param delegate MEGARequestDelegate to track this request */ - (void)setNodeLabel:(MEGANode *)node label:(MEGANodeLabel)label delegate:(id)delegate; /** * @brief Set node label as a node attribute. * Valid values for label attribute are: * - MEGANodeLabelRed = 1 * - MEGANodeLabelOrange = 2 * - MEGANodeLabelYellow = 3 * - MEGANodeLabelGreen = 4 * - MEGANodeLabelBlue = 5 * - MEGANodeLabelPurple = 6 * - MEGANodeLabelGrey = 7 * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns the label for the node * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeLabel * * @param node Node that will receive the information. * @param label Label of the node */ - (void)setNodeLabel:(MEGANode *)node label:(MEGANodeLabel)label; /** * @brief Remove node label * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeLabel * * @param node Node that will receive the information. * @param delegate MEGARequestDelegate to track this request */ - (void)resetNodeLabel:(MEGANode *)node delegate:(id)delegate; /** * @brief Remove node label * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeLabel * * @param node Node that will receive the information. */ - (void)resetNodeLabel:(MEGANode *)node; /** * @brief Set node favourite as a node attribute. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns 1 if node is set as favourite, otherwise return 0 * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeFav * * @param node Node that will receive the information. * @param favourite if YES set node as favourite, otherwise remove the attribute * @param delegate MEGARequestDelegate to track this request */ - (void)setNodeFavourite:(MEGANode *)node favourite:(BOOL)favourite delegate:(id)delegate; /** * @brief Set node favourite as a node attribute. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns 1 if node is set as favourite, otherwise return 0 * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeFav * * @param node Node that will receive the information. * @param favourite if YES set node as favourite, otherwise remove the attribute */ - (void)setNodeFavourite:(MEGANode *)node favourite:(BOOL)favourite; /** * @brief Mark a node as sensitive * * @note Descendants will inherit the sensitive property. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns 1 if node is set as favourite, otherwise return 0 * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeSen * * @param node Node that will receive the information. * @param sensitive if true set node as sensitive, otherwise remove the attribute * @param delegate MEGARequestDelegate to track this request */ - (void)setNodeSensitive:(MEGANode *)node sensitive:(BOOL)sensitive delegate:(id)delegate; /** * @brief Mark a node as sensitive * * @note Descendants will inherit the sensitive property. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest numDetails] - Returns 1 if node is set as favourite, otherwise return 0 * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeSen * * @param node Node that will receive the information. * @param sensitive if true set node as sensitive, otherwise remove the attribute */ - (void)setNodeSensitive:(MEGANode *)node sensitive:(BOOL)sensitive; /** * @brief Set node description as a node attribute * * To remove node description, set description to nil * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that received the attribute * - [MEGARequest flag] - Returns true (official * attribute) * - MEGARequest paramType] - Returns MEGANodeDescription * - [MEGARequest getText] - Returns node description * If the size of the description is greater than 3000, onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. * If the MEGA account is a business account and its status is expired, onRequestFinish will be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param description Description of the node. Set nil to remove. * @param node Node that will receive the information. * @param delegate MEGARequestListener to track this request */ - (void)setDescription:(nullable NSString *)description forNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Get a list of favourite nodes. * * The associated request type with this request is MEGARequestTypeGetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node provided * - [MEGARequest paramType] - Returns MEGANodeAttributeFav * - [MEGARequest numDetails] - Returns the count requested * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaHandleList] - List of handles of favourite nodes * * @param node Node and its children that will be searched for favourites. Search all nodes if nil * @param count if count is zero return all favourite nodes, otherwise return only 'count' favourite nodes * @param delegate MEGARequestListener to track this request */ - (void)favouritesForParent:(nullable MEGANode *)node count:(NSInteger)count delegate:(id)delegate; /** * @brief Get a list of favourite nodes. * * The associated request type with this request is MEGARequestTypeGetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node provided * - [MEGARequest paramType] - Returns MEGANodeAttributeFav * - [MEGARequest numDetails] - Returns the count requested * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaHandleList] - List of handles of favourite nodes * * @param node Node and its children that will be searched for favourites. Search all nodes if nil * @param count if count is zero return all favourite nodes, otherwise return only 'count' favourite nodes */ - (void)favouritesForParent:(nullable MEGANode *)node count:(NSInteger)count; /** * @brief Request creation of a new Set * * The associated request type with this request is MEGARequestTypePutSet * Valid data in the MEGARequest object received on callbacks: * * - [MEGARequest parentHandle] - Returns INVALID_HANDLE * - [MEGARequest text] - Returns name of the Set * - [MEGARequest paramType] - Returns MEGASetAttributeCreate, possibly combined with MEGASetAttributeName * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest set] - Returns either the new Set, or nil if it was not created. * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param name the name that should be given to the new Set * @param type the type that should be given to the new Set * @param delegate MEGARequestDelegate to track this request */ -(void)createSet:(nullable NSString *)name type:(MEGASetType)type delegate:(id)delegate; /** * @brief Generate a public link of a Set in MEGA * * The associated request type with this request is MEGARequestTypeExportSet * Valid data in the MEGARequest object received on callbacks: * * - MEGARequest::nodeHandle - Returns id of the Set used as parameter * - MEGARequest::flag - Returns a boolean set to true representing the call was * meant to enable/create the export * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest set] - MEGASet including the public id * - [MEGARequest link] - Public link * * MEGAErrorTypeApiOk results in onSetsUpdate being triggered as well * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param sid The id of the Set to get the public link * @param delegate MEGARequestDelegate to track this request */ -(void)exportSet:(MEGAHandle)sid delegate:(id)delegate; /** * @brief Stop sharing a Set * * The associated request type with this request is MEGARequestTypeExportSet * Valid data in the MEGARequest object received on callbacks: * * - [MEGARequest nodeHandle] - Returns id of the Set used as parameter * - [MEGARequest flag] - Returns a boolean set to false representing the call was meant to disable the export * * MEGAErrorTypeApiOk results in onSetsUpdate being triggered as well * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param sid The id of the Set to stop sharing * @param delegate MEGARequestDelegate to track this request */ -(void)disableExportSet:(MEGAHandle)sid delegate:(id)delegate; /** * @brief Stops public Set preview mode for current SDK instance * * MEGASDK instance is no longer useful until a new login * */ -(void)stopPublicSetPreview; /** * @brief Returns if this MEGASDK instance is in a public/exported Set preview mode * * @returns True if public Set preview mode is enabled * */ -(BOOL)inPublicSetPreview; /** * @brief Get current public/exported Set in Preview mode * * The response value is stored as a MEGASet. * * You take the ownership of the returned value * * @return Current public/exported Set in preview mode or nullptr if there is none * */ -(nullable MEGASet *)publicSetInPreview; /** * @brief Request to fetch a public/exported Set and its Elements. * * The associated request type with this request is MEGARequestTypeFetchSet * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest link] - Returns the link used for the public Set fetch request * * In addition to fetching the Set (including Elements), SDK's instance is set * to preview mode for the public Set. This mode allows downloading of foreign * SetElements included in the public Set. * * To disable the preview mode and release resources used by the preview Set, * use [MEGASdk stopPublicSetPreview] * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk * - [MEGARequest set] - Returns the Set * - [MEGARequest elementsInSet] - Returns the list of Elements * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiENoent - Set could not be found. * - MEGAErrorTypeApiEInternal - Received answer could not be read or decrypted. * - MEGAErrorTypeApiEArgs - Malformed (from API). * - MEGAErrorTypeApiEAccess - Permissions Error (from API). * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue * * @param publicSetLink Public link to a Set in MEGA * @param delegate MegaRequestListener to track this request */ - (void)fetchPublicSet:(NSString *)publicSetLink delegate:(id)delegate; /** * @brief Gets a MEGANode for the foreign MEGASetElement that can be used to download the Element * * The associated request type with this request is MEGARequestTypeExportedSetElement * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest publicNode] - Returns the MEGANode * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiEAccess - Public Set preview mode is not enabled * - MEGAErrorTypeApiEArgs - MEGAHandle for MEGASetElement provided as param doesn't match any Element in previewed Set * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param eid MEGAHandle of target MEGASetElement from Set in preview mode * @param delegate MEGARequestDelegate to track this request */ - (void)previewElementNode:(MEGAHandle)eid delegate:(id)delegate; /** * @brief Request to update the name of a Set * * The associated request type with this request is MEGARequestTypePutSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Set to be updated * - [MEGARequest text] - Returns new name of the Set * - [MEGARequest paramType] - Returns MEGASetAttributeName * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Set with the given id could not be found (before or after the request) * - MEGAErrorTypeApiEInternal - Received answer could not be read * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set to be updated * @param name the new name that should be given to the Set * @param delegate MEGARequestDelegate to track this request */ -(void)updateSetName:(MEGAHandle)sid name:(NSString *)name delegate:(id)delegate; /** * @brief Request to remove a Set * * The associated request type with this request is MEGARequestTypeRemoveSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Set to be removed * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Set could not be found * - MEGAErrorTypeApiEInternal - Received answer could not be read * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set to be removed * @param delegate MEGARequestDelegate to track this request */ -(void)removeSet:(MEGAHandle)sid delegate:(id)delegate; /** * @brief Request to update the cover of a Set * * The associated request type with this request is MEGARequestTypePutSet * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Set to be updated * - [MEGARequest nodeHandle] - Returns Element id to be set as the new cover * - [MEGARequest paramType] - Returns MEGASetAttributeCover * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Set with the given id could not be found (before or after the request). * - MEGAErrorTypeApiEInternal - Received answer could not be read. * - MEGAErrorTypeApiEArgs - Given Element id was not part of the current Set; Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set to be updated * @param eid the id of the Element to be set as cover * @param delegate MEGARequestDelegate to track this request */ -(void)putSetCover:(MEGAHandle)sid eid:(MEGAHandle)eid delegate:(id)delegate; /** * @brief Request creation of a new Element for a Set * * The associated request type with this request is MEGARequestTypePutSetElement * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns INVALID_HANDLE * - [MEGARequest totalBytes] - Returns the id of the Set * - [MEGARequest paramType] - Returns MEGASetElementAttributeCreate, possibly combined with MEGASetElementAttributeName * - [MEGARequest text] - Returns new name of the Element * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest elementsInSet] - Returns a list containing only the new Element * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Set could not be found, or node could not be found. * - MEGAErrorTypeApiEInternal - Received answer could not be read or decrypted. * - MEGAErrorTypeApiEKey - File-node had no key. * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set that will own the new Element * @param nodeId the handle of the file-node that will be represented by the new Element * @param name the name that should be given to the new Element * @param delegate MEGARequestDelegate to track this request */ -(void)createSetElement:(MEGAHandle)sid nodeId:(MEGAHandle)nodeId name:(nullable NSString *)name delegate:(id)delegate; /** * @brief Request to update the name of an Element * * The associated request type with this request is MEGARequestTypePutSetElement * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Element to be updated * - [MEGARequest totalBytes] - Returns the id of the Set * - [MEGARequest paramType] - Returns MEGASetElementAttributeName * - [MEGARequest text] - Returns new name of the Element * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Element could not be found. * - MEGAErrorTypeApiEInternal - Received answer could not be read or decrypted. * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param name the new name that should be given to the Element * @param delegate MEGARequestDelegate to track this request */ -(void)updateSetElement:(MEGAHandle)sid eid:(MEGAHandle)eid name:(NSString *)name delegate:(id)delegate; /** * @brief Request to update the order of an Element * * The associated request type with this request is MEGARequestTypePutSetElement * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Element to be updated * - [MEGARequest totalBytes] - Returns the id of the Set * - [MEGARequest paramType] - Returns MEGASetElementAttributeOrder * - [MEGARequest number] - Returns order of the Element * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - Element could not be found. * - MEGAErrorTypeApiEInternal - Received answer could not be read or decrypted. * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param order the new order of the Element * @param delegate MEGARequestDelegate to track this request */ -(void)updateSetElementOrder:(MEGAHandle)sid eid:(MEGAHandle)eid order:(int64_t)order delegate:(id)delegate; /** * @brief Request to remove an Element * * The associated request type with this request is MEGARequestTypeRemoveSetElement * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Returns id of the Element to be removed * - [MEGARequest totalBytes] - Returns the id of the Set * * On the onRequestFinish error, the error code associated to the MEGAErrorType can be: * - MEGAErrorTypeApiENoent - No Set or no Element with given ids could be found (before or after the request). * - MEGAErrorTypeApiEInternal - Received answer could not be read. * - MEGAErrorTypeApiEArgs - Malformed * - MEGAErrorTypeApiEAccess - Permissions Error * * @param sid the id of the Set that owns the Element * @param eid the id of the Element to be removed * @param delegate MEGARequestDelegate to track this request */ -(void)removeSetElement:(MEGAHandle)sid eid:(MEGAHandle)eid delegate:(id)delegate; /** * @brief Get the Set with the given id, for current user. * * The response value is stored as a MEGASet. * * @param sid the id of the Set to be retrieved * * @return the requested MEGASet, or nil if not found */ -(nullable MEGASet *)setBySid:(MEGAHandle)sid; /** * @brief Returns true if the Set has been exported (has a public link) * * Public links are created by calling MEGASDK::exportSet * * @param sid the id of the Set to check * * @return true if param sid is an exported Set */ -(BOOL)isExportedSet:(MEGAHandle)sid; /** * @brief Get a list of all Sets available for current user. * * The response value is stored as a MEGASet array. * * @return array of MEGASets */ -(NSArray*)megaSets; /** * @brief Get the cover (Element id) of the Set with the given id, for current user. * * @param sid the id of the Set to retrieve the cover for * * @return Element id of the cover, or INVALID_HANDLE if not set or invalid id */ -(MEGAHandle)megaSetCoverBySid:(MEGAHandle)sid; /** * @brief Gets a MEGANode for the foreign MEGASetElement that can be used to download the Element * * @param sid MEGAHandle of target Set to get its public link/URL * * @return Nsstring with the public URL if success, null otherwise * In any case, one of the followings error codes with the result can be found in the log: * - MEGAErrorTypeApiOk on success * - MEGAErrorTypeApiENoent if sid doesn't match any owned Set or the Set is not exported * - MEGAErrorTypeApiEArgs if there was an internal error composing the URL */ -(nullable NSString *)publicLinkForExportedSetBySid:(MEGAHandle)sid; /** * @brief Get a particular Element in a particular Set, for current user. * * The response value is stored as a MEGASetElement. * * @param sid the id of the Set owning the Element * @param eid the id of the Element to be retrieved * * @return requested Element, or nil if not found */ -(nullable MEGASetElement *)megaSetElementBySid:(MEGAHandle)sid eid:(MEGAHandle)eid; /** * @brief Get all Elements in the Set with given id, for current user. * * The response value is stored as a MEGASetElement array. * * @param sid the id of the Set owning the Elements * @param includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * * @return all Elements in that Set, or nil if not found or none added */ -(NSArray *)megaSetElementsBySid:(MEGAHandle)sid includeElementsInRubbishBin:(BOOL)includeElementsInRubbishBin; /** * @brief Get current public/exported MEGASetElement in Preview mode * * The response value is stored as a MEGASetElement array. * * You take the ownership of the returned value * * @return Current public/exported MEGASetElements in preview mode or nullptr if there is none * */ -(NSArray *)publicSetElementsInPreview; /** * @brief Get Element count of the Set with the given id, for current user. * * @param sid the id of the Set to get Element count for * @param includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * * @return Element count of requested Set, or 0 if not found */ -(NSUInteger)megaSetElementCount:(MEGAHandle)sid includeElementsInRubbishBin:(BOOL)includeElementsInRubbishBin; /** * @brief Set the GPS coordinates of image files as a node attribute. * * To remove the existing coordinates, set both the latitude and longitude to nil. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeCoordinates * - [MEGARequest numDetails] - Returns the longitude, scaled to integer in the range of [0, 2^24] * - [MEGARequest transferTag] - Returns the latitude, scaled to integer in the range of [0, 2^24) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode that will receive the information. * @param latitude Latitude in signed decimal degrees notation. * @param longitude Longitude in signed decimal degrees notation. * @param delegate Delegate to track this request. */ - (void)setNodeCoordinates:(MEGANode *)node latitude:(nullable NSNumber *)latitude longitude:(nullable NSNumber *)longitude delegate:(id)delegate; /** * @brief Set the GPS coordinates of image files as a node attribute. * * To remove the existing coordinates, set both the latitude and longitude to nil. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeCoordinates * - [MEGARequest numDetails] - Returns the longitude, scaled to integer in the range of [0, 2^24] * - [MEGARequest transferTag] - Returns the latitude, scaled to integer in the range of [0, 2^24) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode that will receive the information. * @param latitude Latitude in signed decimal degrees notation. * @param longitude Longitude in signed decimal degrees notation. */ - (void)setNodeCoordinates:(MEGANode *)node latitude:(nullable NSNumber *)latitude longitude:(nullable NSNumber *)longitude; /** * @brief Set the GPS coordinates of image files as a node attribute. * * To remove the existing coordinates, set both the latitude and longitude to nil. * * The 'unshareable' variant of this function stores the coordinates with an extra * layer of encryption which only this user can decrypt, so that even if this node is shared * with others, they cannot read the coordinates. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeCoordinates * - [MEGARequest numDetails] - Returns the longitude, scaled to integer in the range of [0, 2^24] * - [MEGARequest transferTag] - Returns the latitude, scaled to integer in the range of [0, 2^24) * * @param node MEGANode that will receive the information. * @param latitude Latitude in signed decimal degrees notation. * @param longitude Longitude in signed decimal degrees notation. * @param delegate Delegate to track this request. */ - (void)setUnshareableNodeCoordinates:(MEGANode *)node latitude:(double)latitude longitude:(double)longitude delegate:(id)delegate; /** * @brief Set the GPS coordinates of image files as a node attribute. * * To remove the existing coordinates, set both the latitude and longitude to nil. * * The 'unshareable' variant of this function stores the coordinates with an extra * layer of encryption which only this user can decrypt, so that even if this node is shared * with others, they cannot read the coordinates. * * The associated request type with this request is MEGARequestTypeSetAttrNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that receive the attribute * - [MEGARequest flag] - Returns YES (official attribute) * - [MEGARequest paramType] - Returns MEGANodeAttributeCoordinates * - [MEGARequest numDetails] - Returns the longitude, scaled to integer in the range of [0, 2^24] * - [MEGARequest transferTag] - Returns the latitude, scaled to integer in the range of [0, 2^24) * * @param node MEGANode that will receive the information. * @param latitude Latitude in signed decimal degrees notation. * @param longitude Longitude in signed decimal degrees notation. */ - (void)setUnshareableNodeCoordinates:(MEGANode *)node latitude:(double)latitude longitude:(double)longitude; /** * @brief Generate a public link of a file/folder in MEGA. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Public link * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to get the public link. * @param delegate Delegate to track this request. */ - (void)exportNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Generate a public link of a file/folder in MEGA. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Public link * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to get the public link. */ - (void)exportNode:(MEGANode *)node; /** * @brief Generate a public link of a file/folder in MEGA. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Public link * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to get the public link. * @param expireTime NSDate until the public link will be valid * @param delegate MEGARequestDelegate to track this request. */ - (void)exportNode:(MEGANode *)node expireTime:(NSDate *)expireTime delegate:(id)delegate; /** * @brief Generate a public link of a file/folder in MEGA. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Public link * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to get the public link. * @param expireTime NSDate until the public link will be valid */ - (void)exportNode:(MEGANode *)node expireTime:(NSDate *)expireTime; /** * @brief Stop sharing a file/folder. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns NO * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to stop sharing. * @param delegate Delegate to track this request. */ - (void)disableExportNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Stop sharing a file/folder. * * The associated request type with this request is MEGARequestTypeExport. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest access] - Returns NO * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node MEGANode to stop sharing. */ - (void)disableExportNode:(MEGANode *)node; /** * @brief Creates a new share key for the node if there is no share key already created. * * Call it before starting any new share. * * @param node The folder to share. It must be a non-root folder * @param delegate Delegate to track this request. */ - (void)openShareDialog:(MEGANode *)node delegate:(id)delegate; #pragma mark - Attributes Requests /** * @brief Get the thumbnail of a node. * * If the node doesn't have a thumbnail the request fails with the MEGAErrorTypeApiENoent * error code. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param nodeHandle Handle of the Node to get the thumbnail. * @param destinationFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate Delegate to track this request. */ - (void)getThumbnailWithNodeHandle:(uint64_t)nodeHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate; /** * @brief Get the thumbnail of a node. * * If the node doesn't have a thumbnail the request fails with the MEGAErrorTypeApiENoent * error code. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param nodeHandle Handle of the Node to get the thumbnail. * @param destinationFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * */ - (void)getThumbnailWithNodeHandle:(uint64_t)nodeHandle destinationFilePath:(NSString *)destinationFilePath; /** * @brief Get the thumbnail of a node. * * If the node doesn't have a thumbnail the request fails with the MEGAErrorTypeApiENoent * error code. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node Node to get the thumbnail. * @param destinationFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate Delegate to track this request. */ - (void)getThumbnailNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate; /** * @brief Get the thumbnail of a node. * * If the node doesn't have a thumbnail the request fails with the MEGAErrorTypeApiENoent * error code. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node Node to get the thumbnail. * @param destinationFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * */ - (void)getThumbnailNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath; /** * @brief Cancel the retrieval of a thumbnail. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node Node to cancel the retrieval of the thumbnail. * @param delegate Delegate to track this request. * * @see [MEGASdk getThumbnailNode:destinationFilePath:]. */ - (void)cancelGetThumbnailNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Cancel the retrieval of a thumbnail. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node Node to cancel the retrieval of the thumbnail. * * @see [MEGASdk getThumbnailNode:destinationFilePath:]. */ - (void)cancelGetThumbnailNode:(MEGANode *)node; /** * @brief Set the thumbnail of a MEGANode. * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the source path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node MEGANode to set the thumbnail. * @param sourceFilePath Source path of the file that will be set as thumbnail. * @param delegate Delegate to track this request. */ - (void)setThumbnailNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath delegate:(id)delegate; /** * @brief Set the thumbnail of a MEGANode. * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the source path * - [MEGARequest paramType] - Returns MEGAAttributeTypeThumbnail * * @param node MEGANode to set the thumbnail. * @param sourceFilePath Source path of the file that will be set as thumbnail. */ - (void)setThumbnailNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath; /** * @brief Get the preview of a node. * * If the node doesn't have a preview the request fails with the MEGAErrorTypeApiENoent * error code * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to get the preview. * @param destinationFilePath Destination path for the preview. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate Delegate to track this request. */ - (void)getPreviewNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate; /** * @brief Get the preview of a node. * * If the node doesn't have a preview the request fails with the MEGAErrorTypeApiENoent * error code. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the destination path * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to get the preview. * @param destinationFilePath Destination path for the preview. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ - (void)getPreviewNode:(MEGANode *)node destinationFilePath:(NSString *)destinationFilePath; /** * @brief Cancel the retrieval of a preview. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to cancel the retrieval of the preview. * @param delegate Delegate to track this request. * * @see [MEGASdk getPreviewNode:destinationFilePath:]. */ - (void)cancelGetPreviewNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Cancel the retrieval of a preview. * * The associated request type with this request is MEGARequestTypeGetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to cancel the retrieval of the preview. * * @see [MEGASdk getPreviewNode:destinationFilePath:]. */ - (void)cancelGetPreviewNode:(MEGANode *)node; /** * @brief Set the preview of a node. * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the source path * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to set the preview. * @param sourceFilePath Source path of the file that will be set as preview. * @param delegate Delegate to track this request. */ - (void)setPreviewNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath delegate:(id)delegate; /** * @brief Set the preview of a MEGANode. * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node * - [MEGARequest file] - Returns the source path * - [MEGARequest paramType] - Returns MEGAAttributeTypePreview * * @param node Node to set the preview. * @param sourceFilePath Source path of the file that will be set as preview. */ - (void)setPreviewNode:(MEGANode *)node sourceFilePath:(NSString *)sourceFilePath; /** * @brief Get the avatar of a MEGAUser. * * The associated request type with this request is MEGARequestTypeGetAttrUser. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the destination path * - [MEGARequest email] - Returns the email of the user * * @param user MEGAUser to get the avatar. * @param destinationFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate Delegate to track this request. */ - (void)getAvatarUser:(MEGAUser *)user destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate; /** * @brief Get the avatar of a MEGAUser. * * The associated request type with this request is MEGARequestTypeGetAttrUser. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the destination path * - [MEGARequest email] - Returns the email of the user * * @param user MEGAUser to get the avatar. * @param destinationFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * */ - (void)getAvatarUser:(MEGAUser *)user destinationFilePath:(NSString *)destinationFilePath; /** * @brief Get the avatar of any user in MEGA * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the destination path * - [MEGARequest email] - Returns the email or the handle of the user (the provided one as parameter) * * @param emailOrHandle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to nil, the avatar is obtained for the active account * @param destinationFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate MEGARequestDelegate to track this request. */ - (void)getAvatarUserWithEmailOrHandle:(nullable NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate; /** * @brief Get the avatar of any user in MEGA * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the destination path * - [MEGARequest email] - Returns the email or the handle of the user (the provided one as parameter) * * @param emailOrHandle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to nil, the avatar is obtained for the active account * @param destinationFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param delegate MEGARequestDelegate to track this request. * @param queueType ListenerQueueType to receive the global events on.. */ - (void)getAvatarUserWithEmailOrHandle:(nullable NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath delegate:(id)delegate queueType:(ListenerQueueType)queueType; /** * @brief Get the avatar of any user in MEGA * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the destination path * - [MEGARequest email] - Returns the email or the handle of the user (the provided one as parameter) * * @param emailOrHandle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to nil, the avatar is obtained for the active account * @param destinationFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * */ - (void)getAvatarUserWithEmailOrHandle:(nullable NSString *)emailOrHandle destinationFilePath:(NSString *)destinationFilePath; /** * @brief Get the default color for the avatar. * * This color should be used only when the user doesn't have an avatar. * * @param user MEGAUser to get the color of the avatar. If this parameter is set to nil, the color * is obtained for the active account. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" * If the user is not found, this function always returns the same color. */ + (nullable NSString *)avatarColorForUser:(nullable MEGAUser *)user; /** * @brief Get the default color for the avatar. * * This color should be used only when the user doesn't have an avatar. * * @param base64UserHandle User handle (Base64 encoded) to get the avatar. If this parameter is * set to nil, the avatar is obtained for the active account. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" * If the user is not found, this function always returns the same color. */ + (nullable NSString *)avatarColorForBase64UserHandle:(nullable NSString *)base64UserHandle; /** * @brief Get the secondary color for the avatar. * * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from avatarColorForUser. * * @param user MEGAUser to get the color of the avatar. If this parameter is set to nil, the color * is obtained for the active account. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" * If the user is not found, this function always returns the same color. */ + (nullable NSString *)avatarSecondaryColorForUser:(nullable MEGAUser *)user; /** * @brief Get the secondary color for the avatar. * * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from avatarColorForBase64UserHandle. * * @param base64UserHandle User handle (Base64 encoded) to get the avatar. If this parameter is * set to nil, the avatar is obtained for the active account. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" * If the user is not found, this function always returns the same color. */ + (nullable NSString *)avatarSecondaryColorForBase64UserHandle:(nullable NSString *)base64UserHandle; /** * @brief Set/Remove the avatar of the MEGA account * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the source path * * @param sourceFilePath Source path of the file that will be set as avatar. * If nil, the existing avatar will be removed (if any). * @param delegate Delegate to track this request. * In case the avatar never existed before, removing the avatar returns MEGAErrorApiENoent. */ - (void)setAvatarUserWithSourceFilePath:(nullable NSString *)sourceFilePath delegate:(id)delegate; /** * @brief Set/Remove the avatar of the MEGA account * * The associated request type with this request is MEGARequestTypeSetAttrFile. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest file] - Returns the source path (optional) * * @param sourceFilePath Source path of the file that will be set as avatar. * If nil, the existing avatar will be removed (if any). * In case the avatar never existed before, removing the avatar returns MEGAErrorApiENoent. */ - (void)setAvatarUserWithSourceFilePath:(nullable NSString *)sourceFilePath; /** * @brief Get an attribute of a MEGAUser. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value of the attribute * * @param user MEGAUser to get the attribute. If this parameter is set to nil, the attribute * is obtained for the active account * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * */ - (void)getUserAttributeForUser:(nullable MEGAUser *)user type:(MEGAUserAttribute)type; /** * @brief Get an attribute of a MEGAUser. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value of the attribute * * @param user MEGAUser to get the attribute. If this parameter is set to nil, the attribute * is obtained for the active account * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * * @param delegate MEGARequestDelegate to track this request */ - (void)getUserAttributeForUser:(nullable MEGAUser *)user type:(MEGAUserAttribute)type delegate:(id)delegate; /** * @brief Get an attribute of any user in MEGA. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest email] - Returns the email or the handle of the user (the provided one as parameter) * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value for public attributes * * @param emailOrHandle Email or user handle (Base64 encoded) to get the attribute. * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * */ - (void)getUserAttributeForEmailOrHandle:(NSString *)emailOrHandle type:(MEGAUserAttribute)type; /** * @brief Get an attribute of any user in MEGA. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest email] - Returns the email or the handle of the user (the provided one as parameter) * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value for public attributes * * @param emailOrHandle Email or user handle (Base64 encoded) to get the attribute. * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * * @param delegate MEGARequestDelegate to track this request */ - (void)getUserAttributeForEmailOrHandle:(NSString *)emailOrHandle type:(MEGAUserAttribute)type delegate:(id)delegate; /** * @brief Get an attribute of the current account. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value of the attribute * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * */ - (void)getUserAttributeType:(MEGAUserAttribute)type; /** * @brief Get an attribute of the current account. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the value of the attribute * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Get the firstname of the user (public) * MEGAUserAttributeLastname = 2 * Get the lastname of the user (public) * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeED25519PublicKey = 5 * Get the public key Ed25519 of the user (public) * MEGAUserAttributeCU25519PublicKey = 6 * Get the public key Cu25519 of the user (public) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeSigRsaPublicKey = 8 * Get the signature of RSA public key of the user (public) * MEGAUserAttributeSigCU255PublicKey = 9 * Get the signature of Cu25519 public key of the user (public) * MEGAUserAttributeLanguage = 14 * Get the preferred language of the user (private, non-encrypted) * MEGAUserAttributePwdReminder = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MEGAUserAttributeDisableVersions = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Get number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeStorageState = 21 * Get the state of the storage (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Get whether the user has enabled send geolocation messages (private) * MEGAUserAttributeCameraUploadsFolder = 23 * Get the target folder for Camera Uploads (private) * MEGAUserAttributeMyChatFilesFolder = 24 * Get the target folder for My chat files (private) * MEGAUserAttributePushSettings = 25 * Get whether user has push settings enabled (private) * MEGAUserAttributeAlias = 27 * Get the list of the users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Get the list of device names (private) * MEGAUserAttributeBackupsFolder = 31 * Get the target folder for My Backups (private) * MEGAUserAttributeCookieSettings = 33 * Get whether user has Cookie Settings enabled * MEGAUserAttributeJsonSyncConfigData = 34 * Get name and key to cypher sync-configs file * MEGAUserAttributeDrivesName = 35 * Get external drive names by id * MEGAUserAttributeNoCallKit = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * * @param delegate MEGARequestDelegate to track this request */ - (void)getUserAttributeType:(MEGAUserAttribute)type delegate:(id)delegate; /** * @brief Set an attribute of the current user. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest text] - Return the new value for the attibute * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Set the firstname of the user * MEGAUserAttributeLastname = 2 * Set the lastname of the user * MEGAUserAttributeRubbishTime = 19 * Set the number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeNoCallKit = 36 * Set whether user has iOS CallKit disabled or enabled (private, non-encrypted) * * If the MEGA account is a sub-user business account, and the value of the parameter * type is equal to MEGAUserAttributeFirstname or MEGAUserAttributeLastname * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param value New attribute value */ - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value; /** * @brief Set an attribute of the current user. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest text] - Return the new value for the attibute * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeFirstname = 1 * Set the firstname of the user * MEGAUserAttributeLastname = 2 * Set the lastname of the user * MEGAUserAttributeRubbishTime = 19 * Set the number of days for rubbish-bin cleaning scheduler (private, non-encrypted) * MEGAUserAttributeNoCallKit = 36 * Set whether user has iOS CallKit disabled or enabled (private, non-encrypted) * * If the MEGA account is a sub-user business account, and the value of the parameter * type is equal to MEGAUserAttributeFirstname or MEGAUserAttributeLastname * be called with the error code MEGAErrorTypeApiEMasterOnly. * * @param value New attribute value * @param delegate MEGARequestDelegate to track this request */ - (void)setUserAttributeType:(MEGAUserAttribute)type value:(NSString *)value delegate:(id)delegate; /** * @brief Set a private attribute of the current user * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest megaStringDictionary] - Returns the new value for the attribute * * You can remove existing records/keypairs from the following attributes: * - MEGAUserAttributeAlias * - MEGAUserAttributeDeviceNames * - MEGAUserAttributeAppsPreferences * - MEGAUserAttributeContentConsumptionPreferences * by adding a keypair into MegaStringMap whit the key to remove and an empty C-string null terminated as value. * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Set whether the user can send geolocation messages (private) * MEGAUserAttributeAlias = 27 * Set the list of users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Set the list of device names (private) * MEGAUserAttributeAppsPreferences = 38 * Set the apps prefs (private) * MEGAUserAttributeContentConsumptionPreferences = 39 * Set the content consumption prefs (private) * * @param key Key for the new attribute in the string map * @param value New attribute value */ - (void)setUserAttributeType:(MEGAUserAttribute)type key:(NSString *)key value:(NSString *)value; /** * @brief Set a private attribute of the current user * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type * - [MEGARequest megaStringDictionary] - Returns the new value for the attribute * * You can remove existing records/keypairs from the following attributes: * - MEGAUserAttributeAlias * - MEGAUserAttributeDeviceNames * - MEGAUserAttributeAppsPreferences * - MEGAUserAttributeContentConsumptionPreferences * by adding a keypair into MegaStringMap whit the key to remove and an empty C-string null terminated as value. * * @param type Attribute type * * Valid values are: * * MEGAUserAttributeAuthRing = 3 * Get the authentication ring of the user (private) * MEGAUserAttributeLastInteraction = 4 * Get the last interaction of the contacts of the user (private) * MEGAUserAttributeKeyring = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MEGAUserAttributeRichPreviews = 18 * Get whether user generates rich-link messages or not (private) * MEGAUserAttributeRubbishTime = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MEGAUserAttributeGeolocation = 22 * Set whether the user can send geolocation messages (private) * MEGAUserAttributeAlias = 27 * Set the list of users's aliases (private) * MEGAUserAttributeDeviceNames = 30 * Set the list of device names (private) * MEGAUserAttributeAppsPreferences = 38 * Set the apps prefs (private) * MEGAUserAttributeContentConsumptionPreferences = 39 * Set the content consumption prefs (private) * * @param key Key for the new attribute in the string map * @param value New attribute value * @param delegate MEGARequestDelegate to track this request */ - (void)setUserAttributeType:(MEGAUserAttribute)type key:(NSString *)key value:(NSString *)value delegate:(id)delegate; /** * @brief Gets the alias for an user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias * - [MEGARequest nodeHandle] - Returns the handle of the node as binary * - [MEGARequest text] - Return the handle of the node as base 64 string. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest name] - Returns the user alias. * * If the user alias doesn't exists the request will fail with the error code MEGAErrorTypeApiENoent * * @param handle Handle of the contact * @param delegate MEGARequestDelegate to track this request */ - (void)getUserAliasWithHandle:(uint64_t)handle delegate:(id)delegate; /** * @brief Gets the alias for an user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias * - [MEGARequest nodeHandle] - Returns the handle of the node as binary * - [MEGARequest text] - Return the handle of the node as base64 string. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest name] - Returns the user alias. * * If the user alias doesn't exists the request will fail with the error code MEGAErrorTypeApiENoent * * @param handle Handle of the contact */ - (void)getUserAliasWithHandle:(uint64_t)handle; /** * @brief Set or reset an alias for a user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias * - [MEGARequest nodeHandle] - Returns the handle of the node as binary * - [MEGARequest text] - Return the handle of the node as base 64 string. * * @param alias the user alias, or null to reset the existing * @param handle Handle of the contact * @param delegate MEGARequestDelegate to track this request */ - (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle delegate:(id)delegate; /** * @brief Set or reset an alias for a user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeAlias * - [MEGARequest nodeHandle] - Returns the handle of the node as binary * - [MEGARequest text] - Return the handle of the node as base 64 string. * * @param alias the user alias, or null to reset the existing * @param handle Handle of the contact */ - (void)setUserAlias:(nullable NSString *)alias forHandle:(uint64_t)handle; #pragma mark - S4 Attributes Requests /** * @brief Returns true if S4 object storage is enabled * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return True if enabled, false if disabled */ - (BOOL)isS4Enabled; /** * @brief Returns the node's handle of the S4 container * * S4 requires a folder in the root of the Cloud Drive to operate. * This method returns the handle of the related node, or INVALID_HANDLE if the * S4 service is disabled. * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return The node's handle, or INVALID_HANDLE if not set. */ - (MEGAHandle)getS4ContainerHandle; #pragma mark - Account management Requests /** * @brief Get details about the MEGA account. * * The associated request type with this request is MEGARequestTypeAccountDetails. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaAccountDetails] - Details of the MEGA account * * @param delegate Delegate to track this request. */ - (void)getAccountDetailsWithDelegate:(id)delegate; /** * @brief Get details about the MEGA account. * * The associated request type with this request is MEGARequestTypeAccountDetails. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaAccountDetails] - Details of the MEGA account. * */ - (void)getAccountDetails; /** * @brief Check if the available bandwidth quota is enough to transfer an amount of bytes * * The associated request type with this request is MEGARequestTypeQueryTransferQuota * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the amount of bytes to be transferred * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - YES if it is expected to get an overquota error, otherwise NO * * @param size Amount of bytes to be transferred * @param delegate MEGARequestDelegate to track this request */ - (void)queryTransferQuotaWithSize:(long long)size delegate:(id)delegate; /** * @brief Check if the available bandwidth quota is enough to transfer an amount of bytes * * The associated request type with this request is MEGARequestTypeQueryTransferQuota * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the amount of bytes to be transferred * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - YES if it is expected to get an overquota error, otherwise NO * * @param size Amount of bytes to be transferred */ - (void)queryTransferQuotaWithSize:(long long)size; /** * @brief Get the recommended PRO level. The smallest plan that is an upgrade (free -> lite -> proi -> proii -> proiii) * and has enough space. * * The associated request type with this request is MEGARequestTypeGetRecommenedProPlan. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest getNumber] the recommended PRO level: * Valid values are (there are other account types): * - MEGAAccountTypeFree = 0 * - MEGAAccountTypeProI = 1 * - MEGAAccountTypeProII = 2 * - MEGAAccountTypeProIII = 3 * * @param delegate MEGARequestDelegate to track this request */ - (void)getRecommendedProLevelWithDelegate:(id)delegate; /** * @brief Get the available pricing plans to upgrade a MEGA account. * * You can get a payment URL for any of the pricing plans provided by this function * using [MEGASdk getPaymentIdForProductHandle:]. * * The associated request type with this request is MEGARequestTypeGetPricing. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest pricing] - MEGAPricing object with all pricing plans * - [MEGARequest currency] - MEGACurrency object with currency data related to prices * * @param delegate Delegate to track this request. * * @see [MEGASdk getPaymentIdForProductHandle:]. */ - (void)getPricingWithDelegate:(id)delegate; /** * @brief Get the available getPricing plans to upgrade a MEGA account. * * You can get a payment URL for any of the getPricing plans provided by this function * using [MEGASdk getPaymentIdForProductHandle:]. * * The associated request type with this request is MEGARequestTypeGetPricing. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest pricing] - MEGAPricing object with all pricing plans * - [MEGARequest currency] - MEGACurrency object with currency data related to prices * * @see [MEGASdk getPaymentIdForProductHandle:]. */ - (void)getPricing; /** * @brief Get the payment URL for an upgrade. * * The associated request type with this request is MEGARequestTypeGetPaymentId. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the product * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Payment link * * @param productHandle Handle of the product (see [MEGASdk getPricing]). * @param delegate Delegate to track this request. * * @see [MEGASdk getPricing]. */ - (void)getPaymentIdForProductHandle:(uint64_t)productHandle delegate:(id)delegate; /** * @brief Get the payment URL for an upgrade. * * The associated request type with this request is MEGARequestTypeGetPaymentId. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the product * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest link] - Payment link * * @param productHandle Handle of the product (see [MEGASdk getPricing]). * * @see [MEGASdk getPricing]. */ - (void)getPaymentIdForProductHandle:(uint64_t)productHandle; /** * @brief Submit a purchase receipt for verification * * The associated request type with this request is MEGARequestTypeSubmitPurchaseReceipt. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the payment gateway * - [MEGARequest text] - Returns the purchase receipt * - [MEGARequest parentHandle] - Returns the last public node handle accessed * * @param gateway Payment gateway * Currently supported payment gateways are: * - MEGAPaymentMethodItunes = 2 * - MEGAPaymentMethodGoogleWallet = 3 * - MEGAPaymentMethodWindowsStore = 13 * * @param receipt Purchase receipt * @param delegate Delegate to track this request */ - (void)submitPurchase:(MEGAPaymentMethod)gateway receipt:(NSString *)receipt delegate:(id)delegate; /** * @brief Submit a purchase receipt for verification * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the payment gateway * - [MEGARequest text] - Returns the purchase receipt * - [MEGARequest parentHandle] - Returns the last public node handle accessed * * @param gateway Payment gateway * Currently supported payment gateways are: * - MEGAPaymentMethodItunes = 2 * - MEGAPaymentMethodGoogleWallet = 3 * - MEGAPaymentMethodWindowsStore = 13 * * @param receipt Purchase receipt */ - (void)submitPurchase:(MEGAPaymentMethod)gateway receipt:(NSString *)receipt; /** * @brief Cancel credit card subscriptions of the account * * The associated request type with this request is MEGARequestTypeCreditCardCancelSubscriptions * @param reason Reason for the cancellation. It can be nil. * @param subscriptionId The subscription ID for the cancellation. It can be nil. * @param canContact Whether the user has permitted MEGA to contact them for the cancellation. * @param delegate MEGARequestDelegate to track this request */ - (void)creditCardCancelSubscriptions:(nullable NSString *)reason subscriptionId:(nullable NSString *)subscriptionId canContact:(BOOL)canContact delegate:(id)delegate; /** * @brief Cancel credit card subscriptions of the account * * The associated request type with this request is MEGARequestTypeCreditCardCancelSubscriptions * @param reasonList List of reasons for the cancellation. It can be nil. * @param subscriptionId The subscription ID for the cancellation. It can be nil. * @param canContact Whether the user has permitted MEGA to contact them for the cancellation. * @param delegate MEGARequestDelegate to track this request */ - (void)creditCardCancelSubscriptionsWithReasons:(nullable MEGACancelSubscriptionReasonList *)reasonList subscriptionId:(nullable NSString *)subscriptionId canContact:(BOOL)canContact delegate:(id)delegate; /** * @brief Change the password of the MEGA account. * * The associated request type with this request is MEGARequestTypeChangePassword. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest password] - Returns the old password * - [MEGARequest newPassword] - Returns the new password * * @param oldPassword Old password (optional, it can be nil to not check the old password). * @param newPassword New password. * @param delegate Delegate to track this request. */ - (void)changePassword:(nullable NSString *)oldPassword newPassword:(NSString *)newPassword delegate:(id)delegate; /** * @brief Change the password of the MEGA account. * * The associated request type with this request is MEGARequestTypeChangePassword. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest password] - Returns the old password * - [MEGARequest newPassword] - Returns the new password * * @param oldPassword Old password (optional, it can be nil to not check the old password). * @param newPassword New password. */ - (void)changePassword:(nullable NSString *)oldPassword newPassword:(NSString *)newPassword; /** * @brief Notify the user has exported the master key * * This function should be called when the user exports the master key by * clicking on "Copy" or "Save file" options. * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember the user has a backup of his/her master key. In consequence, * MEGA will not ask the user to remind the password for the account. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest: text] - Returns the new value for the attribute * * @param delegate MEGARequestDelegate to track this request */ - (void)masterKeyExportedWithDelegate:(id)delegate; /** * @brief Notify the user has exported the master key * * This function should be called when the user exports the master key by * clicking on "Copy" or "Save file" options. * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember the user has a backup of his/her master key. In consequence, * MEGA will not ask the user to remind the password for the account. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute */ - (void)masterKeyExported; /** * @brief Notify the user has successfully checked his password * * This function should be called when the user demonstrates that he remembers * the password to access the account * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute * * @param delegate MEGARequestDelegate to track this request */ - (void)passwordReminderDialogSucceededWithDelegate:(id)delegate; /** * @brief Notify the user has successfully checked his password * * This function should be called when the user demonstrates that he remembers * the password to access the account * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute */ - (void)passwordReminderDialogSucceeded; /** * @brief Notify the user has successfully skipped the password check * * This function should be called when the user skips the verification of * the password to access the account * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute * * @param delegate MEGARequestDelegate to track this request */ - (void)passwordReminderDialogSkippedWithDelegate:(id)delegate; /** * @brief Notify the user has successfully skipped the password check * * This function should be called when the user skips the verification of * the password to access the account * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute */ - (void)passwordReminderDialogSkipped; /** * @brief Notify the user wants to totally disable the password check * * This function should be called when the user rejects to verify that he remembers * the password to access the account and doesn't want to see the reminder again. * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not ask the user * to remind the password for the account again. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute * * @param delegate MEGARequestDelegate to track this request */ - (void)passwordReminderDialogBlockedWithDelegate:(id)delegate; /** * @brief Notify the user wants to totally disable the password check * * This function should be called when the user rejects to verify that he remembers * the password to access the account and doesn't want to see the reminder again. * * As result, the user attribute MEGAUserAttributePwdReminder will be updated * to remember this event. In consequence, MEGA will not ask the user * to remind the password for the account again. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * - [MEGARequest text] - Returns the new value for the attribute */ - (void)passwordReminderDialogBlocked; /** * @brief Check if the app should show the password reminder dialog to the user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if the password reminder dialog should be shown * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent but the value of [MEGARequest flag] will still * be valid. * * @param atLogout YES if the check is being done just before a logout * @param delegate MEGARequestDelegate to track this request */ - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout delegate:(id)delegate; /** * @brief Check if the app should show the password reminder dialog to the user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if the password reminder dialog should be shown * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent but the value of [MEGARequest flag] will still * be valid. * * @param atLogout YES if the check is being done just before a logout */ - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout; /** * @brief Check if the master key has been exported * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest access] - Returns YES if the master key has been exported * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent. * * @param delegate MEGARequestDelegate to track this request */ - (void)isMasterKeyExportedWithDelegate:(id)delegate; /** * @brief Check if the master key has been exported * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest access] - Returns YES if the master key has been exported * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent. * */ - (void)isMasterKeyExported; /** * @brief Get Terms of Service for VPN visibility. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest access] - Returns YES if the Terms Of Service should be visible for user * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent. * * @param delegate MEGARequestDelegate to track this request */ - (void)getVisibleTermsOfServiceWithDelegate:(id)delegate; /** * @brief Set Terms of Service for VPN visibility. * * @param visible True to set Terms of Service visibility on, false otherwise. * @param delegate MEGARequestDelegate to track this request */ - (void)setVisibleTermsOfService:(BOOL)visible delegate:(id)delegate; #ifdef ENABLE_CHAT /** * @brief Enable or disable the generation of rich previews * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * * @param enable YES to enable the generation of rich previews * @param delegate MEGARequestDelegate to track this request */ - (void)enableRichPreviews:(BOOL)enable delegate:(id)delegate; /** * @brief Enable or disable the generation of rich previews * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * * @param enable YES to enable the generation of rich previews */ - (void)enableRichPreviews:(BOOL)enable; /** * @brief Check if rich previews are automatically generated * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * - [MEGARequest numDetails] - Returns zero * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if generation of rich previews is enabled * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent, but the value of [MEGARequest flag] will still be valid (NO). * * @param delegate MEGARequestDelegate to track this request */ - (void)isRichPreviewsEnabledWithDelegate:(id)delegate; /** * @brief Check if rich previews are automatically generated * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * - [MEGARequest numDetails] - Returns zero * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if generation of rich previews is enabled * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent, but the value of [MEGARequest flag] will still be valid (NO). * */ - (void)isRichPreviewsEnabled; /** * @brief Check if the app should show the rich link warning dialog to the user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * - [MEGARequest numDetails] - Returns one * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if it is necessary to show the rich link warning * - [MEGARequest number] - Returns the number of times that user has indicated that doesn't want * modify the message with a rich link. If number is bigger than three, the extra option "Never" * must be added to the warning dialog. * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent, but the value of [MEGARequest flag] will still be valid (YES). * * @param delegate MEGARequestDelegate to track this request */ - (void)shouldShowRichLinkWarningWithDelegate:(id)delegate; /** * @brief Check if the app should show the rich link warning dialog to the user * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * - [MEGARequest numDetails] - Returns one * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest flag] - Returns YES if it is necessary to show the rich link warning * - [MEGARequest number] - Returns the number of times that user has indicated that doesn't want * modify the message with a rich link. If number is bigger than three, the extra option "Never" * must be added to the warning dialog. * * If the corresponding user attribute is not set yet, the request will fail with the * error code MEGAErrorTypeApiENoent, but the value of [MEGARequest flag] will still be valid (YES). * */ - (void)shouldShowRichLinkWarning; /** * @brief Set the number of times "Not now" option has been selected in the rich link warning dialog * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * * @param value Number of times "Not now" option has been selected * @param delegate MEGARequestDelegate to track this request */ - (void)setRichLinkWarningCounterValue:(NSUInteger)value delegate:(id)delegate; /** * @brief Set the number of times "Not now" option has been selected in the rich link warning dialog * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRichPreviews * * @param value Number of times "Not now" option has been selected */ - (void)setRichLinkWarningCounterValue:(NSUInteger)value; /** * @brief Enable the sending of geolocation messages * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeGeolocation * * @param delegate MEGARequestDelegate to track this request */ - (void)enableGeolocationWithDelegate:(id)delegate; /** * @brief Enable the sending of geolocation messages * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeGeolocation */ - (void)enableGeolocation; /** * @brief Check if the sending of geolocation messages is enabled * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeGeolocation * * Sending a Geolocation message is enabled if the MEGARequest object, received in onRequestFinish, * has error code MEGAErrorTypeApiOk. In other cases, send geolocation messages is not enabled and * the application has to answer before send a message of this type. * * @param delegate MEGARequestDelegate to track this request */ - (void)isGeolocationEnabledWithDelegate:(id)delegate; /** * @brief Check if the sending of geolocation messages is enabled * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeGeolocation * * Sending a Geolocation message is enabled if the MEGARequest object, received in onRequestFinish, * has error code MEGAErrorTypeApiOk. In other cases, send geolocation messages is not enabled and * the application has to answer before send a message of this type. */ - (void)isGeolocationEnabled; #endif /** * @brief Set My Chat Files target folder. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeMyChatFilesFolder * - [MEGARequest megaStringDictionary] - Returns a megaStringDictionary. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param handle Handle of the node to be used as target folder * @param delegate MEGARequestDelegate to track this request */ - (void)setMyChatFilesFolderWithHandle:(uint64_t)handle delegate:(id)delegate; /** * @brief Set My Chat Files target folder. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeMyChatFilesFolder * - [MEGARequest megaStringDictionary] - Returns a megaStringDictionary. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param handle Handle of the node to be used as target folder */ - (void)setMyChatFilesFolderWithHandle:(uint64_t)handle; /** * @brief Gets My chat files target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeMyChatFilesFolder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where My Chat Files are stored * * @param delegate MEGARequestDelegate to track this request */ - (void)getMyChatFilesFolderWithDelegate:(id)delegate; /** * @brief Gets My chat files target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeMyChatFilesFolder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where My Chat Files are stored */ - (void)getMyChatFilesFolder; /** * @brief Set Camera Uploads target folder. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * - [MEGARequest megaStringDictionary] - Returns a megaStringDictionary. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param handle Handle of the node to be used as target folder * @param delegate MEGARequestDelegate to track this request */ - (void)setCameraUploadsFolderWithHandle:(uint64_t)handle delegate:(id)delegate; /** * @brief Set Camera Uploads target folder. * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * - [MEGARequest megaStringDictionary] - Returns a megaStringDictionary. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param handle Handle of the node to be used as target folder */ - (void)setCameraUploadsFolderWithHandle:(uint64_t)handle; /** * @brief Gets Camera Uploads target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where Camera Uploads files are stored * * @param delegate MEGARequestDelegate to track this request */ - (void)getCameraUploadsFolderWithDelegate:(id)delegate; /** * @brief Gets Camera Uploads target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where Camera Uploads files are stored */ - (void)getCameraUploadsFolder; /** * @brief Gets Camera Uploads secondary target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * - [MEGARequest flag] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where Camera Uploads files are stored * * If the secondary folder is not set, the request will fail with the error code MEGAErrorTypeApiENoent. * * @param delegate MEGARequestDelegate to track this request */ - (void)getCameraUploadsFolderSecondaryWithDelegate:(id)delegate; /** * @brief Gets Camera Uploads target folder. * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCameraUploadsFolder * - [MEGARequest flag] - Returns YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Returns the handle of the node where Camera Uploads files are stored * * If the secondary folder is not set, the request will fail with the error code MEGAErrorTypeApiENoent. */ - (void)getCameraUploadsFolderSecondary; /** * @brief Get the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRubbishTime * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the days for rubbish-bin cleaning scheduler. * Zero means that the rubbish-bin cleaning scheduler is disabled (only if the account is PRO) * Any negative value means that the configured value is invalid. * * @param delegate MEGARequestDelegate to track this request */ - (void)getRubbishBinAutopurgePeriodWithDelegate:(id)delegate; /** * @brief Get the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRubbishTime * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the days for rubbish-bin cleaning scheduler. * Zero means that the rubbish-bin cleaning scheduler is disabled (only if the account is PRO) * Any negative value means that the configured value is invalid. * */ - (void)getRubbishBinAutopurgePeriod; /** * @brief Set the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRubbishTime * - [MEGARequest number] - Returns the days for rubbish-bin cleaning scheduler passed as parameter * * @param days Number of days for rubbish-bin cleaning scheduler. It must be >= 0. * The value zero disables the rubbish-bin cleaning scheduler (only for PRO accounts). * * @param delegate MEGARequestDelegate to track this request */ - (void)setRubbishBinAutopurgePeriodInDays:(NSInteger)days delegate:(id)delegate; /** * @brief Set the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeRubbishTime * - [MEGARequest number] - Returns the days for rubbish-bin cleaning scheduler passed as parameter * * @param days Number of days for rubbish-bin cleaning scheduler. It must be >= 0. * The value zero disables the rubbish-bin cleaning scheduler (only for PRO accounts). * */ - (void)setRubbishBinAutopurgePeriodInDays:(NSInteger)days; /** * @brief Invite another person to be your MEGA contact * * The user doesn't need to be registered on MEGA. If the email isn't associated with * a MEGA account, an invitation email will be sent with the text in the "message" parameter. * * The associated request type with this request is MEGARequestTypeInviteContact * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest text] - Returns the text of the invitation * - [MEGARequest number] - Returns the action * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MEGAErrorTypeApiEAccess. * * @param email Email of the new contact * @param message Message for the user (can be nil) * @param action Action for this contact request. Valid values are: * - MEGAInviteActionAdd = 0 * - MEGAInviteActionDelete = 1 * - MEGAInviteActionRemind = 2 * * @param delegate MEGARequestDelegate to track this request */ - (void)inviteContactWithEmail:(NSString *)email message:(nullable NSString *)message action:(MEGAInviteAction)action delegate:(id)delegate; /** * @brief Invite another person to be your MEGA contact * * The user doesn't need to be registered on MEGA. If the email isn't associated with * a MEGA account, an invitation email will be sent with the text in the "message" parameter. * * The associated request type with this request is MEGARequestTypeInviteContact * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest text] - Returns the text of the invitation * - [MEGARequest number] - Returns the action * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MEGAErrorTypeApiEAccess. * * @param email Email of the new contact * @param message Message for the user (can be nil) * @param action Action for this contact request. Valid values are: * - MEGAInviteActionAdd = 0 * - MEGAInviteActionDelete = 1 * - MEGAInviteActionRemind = 2 * */ - (void)inviteContactWithEmail:(NSString *)email message:(nullable NSString *)message action:(MEGAInviteAction)action; /** * @brief Invite another person to be your MEGA contact using a contact link handle * * The associated request type with this request is MEGARequestTypeInviteContact * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest text] - Returns the text of the invitation * - [MEGARequest number] - Returns the action * - [MEGARequest nodeHandle] - Returns the contact link handle * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MEGAErrorTypeApiEAccess. * * @param email Email of the new contact * @param message Message for the user (can be nil) * @param action Action for this contact request. Valid values are: * - MEGAInviteActionAdd = 0 * - MEGAInviteActionDelete = 1 * - MEGAInviteActionRemind = 2 * * @param handle Contact link handle of the other account. This parameter is considered only if the * \c action is MEGAInviteActionAdd. Otherwise, it's ignored and it has no effect. * @param delegate MEGARequestDelegate to track this request */ - (void)inviteContactWithEmail:(NSString *)email message:(nullable NSString *)message action:(MEGAInviteAction)action handle:(uint64_t)handle delegate:(id)delegate; /** * @brief Invite another person to be your MEGA contact using a contact link handle * * The associated request type with this request is MEGARequestTypeInviteContact * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * - [MEGARequest text] - Returns the text of the invitation * - [MEGARequest number] - Returns the action * - [MEGARequest nodeHandle] - Returns the contact link handle * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MEGAErrorTypeApiEAccess. * * @param email Email of the new contact * @param message Message for the user (can be nil) * @param action Action for this contact request. Valid values are: * - MEGAInviteActionAdd = 0 * - MEGAInviteActionDelete = 1 * - MEGAInviteActionRemind = 2 * * @param handle Contact link handle of the other account. This parameter is considered only if the * \c action is MEGAInviteActionAdd. Otherwise, it's ignored and it has no effect. */ - (void)inviteContactWithEmail:(NSString *)email message:(nullable NSString *)message action:(MEGAInviteAction)action handle:(uint64_t)handle; /** * @brief Reply to a contact request * @param request Contact request. You can get your pending contact requests using [MEGASdk incomingContactRequests] * @param action Action for this contact request. Valid values are: * - MEGAReplyActionAccept = 0 * - MEGAReplyActionDeny = 1 * - MEGAReplyActionIgnore = 2 * * The associated request type with this request is MEGARequestTypeReplyContactRequest * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact request * - [MEGARequest number] - Returns the action * * @param delegate MEGARequestDelegate to track this request */ - (void)replyContactRequest:(MEGAContactRequest *)request action:(MEGAReplyAction)action delegate:(id)delegate; /** * @brief Reply to a contact request * @param request Contact request. You can get your pending contact requests using [MEGASdk incomingContactRequests] * @param action Action for this contact request. Valid values are: * - MEGAReplyActionAccept = 0 * - MEGAReplyActionDeny = 1 * - MEGAReplyActionIgnore = 2 * * The associated request type with this request is MEGARequestTypeReplyContactRequest * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the contact request * - [MEGARequest number] - Returns the action * */ - (void)replyContactRequest:(MEGAContactRequest *)request action:(MEGAReplyAction)action; /** * @brief Remove a contact from the MEGA account. * * The associated request type with this request is MEGARequestTypeRemoveContact. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * * @param user User of the contact to be removed. * @param delegate Delegate to track this request. */ - (void)removeContactUser:(MEGAUser *)user delegate:(id)delegate; /** * @brief Remove a contact from the MEGA account. * * The associated request type with this request is MEGARequestTypeRemoveContact. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * * @param user User of the contact to be removed. */ - (void)removeContactUser:(MEGAUser *)user; /** * @brief Submit feedback about the app. * * The User-Agent is used to identify the app. It can be set in [MEGASdk initWithAppKey:userAgent:] * * The associated request type with this request is MEGARequestTypeReportEvent. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns MEGAEventTypeFeedback * - [MEGARequest text] - Retuns the comment about the app * - [MEGARequest number] - Returns the rating for the app * * @param rating Integer to rate the app. Valid values: from 1 to 5. * @param comment Comment about the app. * @param delegate Delegate to track this request. * * @deprecated This function is for internal usage of MEGA apps. This feedback * is sent to MEGA servers. * */ - (void)submitFeedbackWithRating:(NSInteger)rating comment:(NSString *)comment delegate:(id)delegate __attribute__((deprecated("This function is for internal usage of MEGA apps."))); /** * @brief Submit feedback about the app. * * The User-Agent is used to identify the app. It can be set in [MEGASdk initWithAppKey:userAgent:] * * The associated request type with this request is MEGARequestTypeReportEvent. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns MEGAEventTypeFeedback * - [MEGARequest text] - Retuns the comment about the app * - [MEGARequest number] - Returns the rating for the app * * @param rating Integer to rate the app. Valid values: from 1 to 5. * @param comment Comment about the app. * * @deprecated This function is for internal usage of MEGA apps. This feedback * is sent to MEGA servers. * */ - (void)submitFeedbackWithRating:(NSInteger)rating comment:(NSString *)comment __attribute__((deprecated("This function is for internal usage of MEGA apps."))); /** * @brief Send a debug report. * * The User-Agent is used to identify the app. It can be set in [MEGASdk initWithAppKey:userAgent:] * * The associated request type with this request is MEGARequestTypeReportEvent. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns MEGAEventTypeFeedback * - [MEGARequest text] - Retuns the debug message * * @param text Debug message. * @param delegate Delegate to track this request. * * @deprecated This function is for internal usage of MEGA apps. This feedback * is sent to MEGA servers. */ - (void)reportDebugEventWithText:(NSString *)text delegate:(id)delegate __attribute__((deprecated("This function is for internal usage of MEGA apps."))); /** * @brief Send a debug report. * * The User-Agent is used to identify the app. It can be set in [MEGASdk initWithAppKey:userAgent:] * * The associated request type with this request is MEGARequestTypeReportEvent. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns MEGAEventTypeFeedback * - [MEGARequest text] - Retuns the debug message * * @param text Debug message. * * @deprecated This function is for internal usage of MEGA apps. This feedback * is sent to MEGA servers. */ - (void)reportDebugEventWithText:(NSString *)text __attribute__((deprecated("This function is for internal usage of MEGA apps."))); /** * @brief Get data about the logged account * * The associated request type with this request is MEGARequestTypeGetUserData. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest name] - Returns the name of the logged user * - [MEGARequest password] - Returns the public RSA key of the account, Base64-encoded * * @param delegate MEGARequestDelegate to track this request */ - (void)getUserDataWithDelegate:(id)delegate; /** * @brief Get data about the logged account * * The associated request type with this request is MEGARequestTypeGetUserData. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest name] - Returns the name of the logged user * - [MEGARequest password] - Returns the public RSA key of the account, Base64-encoded * */ - (void)getUserData; /** * @brief Get data about a contact * * The associated request type with this request is MEGARequestTypeGetUserData. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the XMPP ID of the contact * - [MEGARequest password] - Returns the public RSA key of the contact, Base64-encoded * * @param user Contact to get the data * @param delegate MEGARequestDelegate to track this request */ - (void)getUserDataWithMEGAUser:(MEGAUser *)user delegate:(id)delegate; /** * @brief Get data about a contact * * The associated request type with this request is MEGARequestTypeGetUserData. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email of the contact * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the XMPP ID of the contact * - [MEGARequest password] - Returns the public RSA key of the contact, Base64-encoded * * @param user Contact to get the data */ - (void)getUserDataWithMEGAUser:(MEGAUser *)user; /** * @brief Get data about a contact * * The associated request type with this request is MEGARequestTypeGetUserData. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email or the Base64 handle of the contact * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the XMPP ID of the contact * - [MEGARequest password] - Returns the public RSA key of the contact, Base64-encoded * * @param user Email or Base64 handle of the contact * @param delegate MEGARequestDelegate to track this request */ - (void)getUserDataWithUser:(NSString *)user delegate:(id)delegate; /** * @brief Get data about a contact * * The associated request type with this request is MEGARequestTypeGetUserData. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest email] - Returns the email or the Base64 handle of the contact * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the XMPP ID of the contact * - [MEGARequest password] - Returns the public RSA key of the contact, Base64-encoded * * @param user Email or Base64 handle of the contact */ - (void)getUserDataWithUser:(NSString *)user; /** * @brief Fetch miscellaneous flags when not logged in * * The associated request type with this request is MEGARequestTypeGetMiscFlags. * * When onRequestFinish is called with MEGAErrorTypeApiOk, the miscellaneous flags are available. * If you are logged in into an account, the error code provided in onRequestFinish is * MEGAErrorTypeApiEAccess. * * @see [MEGASDK multiFactorAuthAvailable] * @see [MEGASDK newLinkFormatEnabled] * @see [MEGASDK smsAllowedState] * * @param delegate MEGARequestDelegate to track this request */ - (void)getMiscFlagsWithDelegate:(id)delegate; /** * @brief Close a MEGA session * * All clients using this session will be automatically logged out. * * You can get session information using [MEGASdk getExtendedAccountDetailsWithSessions:purchases:transactions:]. * Then use [MEGAAccountDetails numSessions] and [MEGAAccountDetails session] * to get session info. * [MEGAAccountDetails handle] provides the handle that this function needs. * * If you use -1, all sessions except the current one will be closed * * @param sessionHandle Handle of the session. Use -1 to cancel all sessions except the current one * @param delegate Delegate to track this request */ - (void)killSession:(uint64_t)sessionHandle delegate:(id)delegate; /** * @brief Close a MEGA session * * All clients using this session will be automatically logged out. * * You can get session information using [MEGASdk getExtendedAccountDetailsWithSessions:purchases:transactions:]. * Then use [MEGAAccountDetails numSessions] and [MEGAAccountDetails session] * to get session info. * [MEGAAccountDetails handle] provides the handle that this function needs. * * If you use -1, all sessions except the current one will be closed * * @param sessionHandle Handle of the session. Use -1 to cancel all sessions except the current one */ - (void)killSession:(uint64_t)sessionHandle; /** * @brief Returns the deadline to remedy the storage overquota situation * * This value is valid only when [MEGASdk getUserData] has been called after * receiving a callback [MEGAGlobalDelegate onEvent:event] of type * EventStorage, reporting StorageStatePaywall. * The value will become invalid once the state of storage changes. * * @return `NSDate` instance representing the deadline to remedy the overquota */ - (NSDate *)overquotaDeadlineDate; /** * @brief Returns when the user was warned about overquota state * * This value is valid only when [MEGASdk getUserData] has been called after * receiving a callback [MEGAGlobalDelegate onEvent:event] of type * EventStorage, reporting StorageStatePaywall. * The value will become invalid once the state of storage changes. * * @return An array of `NSDate` with the timestamp corresponding to each warning */ -(NSArray *)overquotaWarningDateList; /** * @brief Call the low level function setrlimit() for NOFILE, needed for some platforms. * * Particularly on phones, the system default limit for the number of open files (and sockets) * is quite low. When the SDK can be working on many files and many sockets at once, * we need a higher limit. Those limits need to take into account the needs of the whole * app and not just the SDK, of course. This function is provided in order that the app * can make that call and set appropriate limits. * * @param fileCount The new limit of file and socket handles for the whole app. * * @return YES when there were no errors setting the new limit (even when clipped to the maximum * allowed value). It returns NO when setting a new limit failed. */ - (BOOL)setRLimitFileCount:(NSInteger)fileCount; /** * @brief Upgrade cryptographic security * * This should be called only after MEGAEvents EventUpgradeSecurity is received to effectively * proceed with the cryptographic upgrade process. * This should happen only once per account. * * @param delegate Delegate to track this request. */ - (void)upgradeSecurityWithDelegate:(id)delegate; #pragma mark - Transfers /** * @brief Get the transfer with a transfer tag * * That tag can be got using [MEGATransfer tag] * * @param transferTag tag to check * @return MEGATransfer object with that tag, or nil if there isn't any * active transfer with it * */ - (nullable MEGATransfer *)transferByTag:(NSInteger)transferTag; /** * @brief Upload a file to support * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * For folders, onTransferFinish will be called with error MEGAErrorTypeApiEArgs. * * @param localPath Local path of the file * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. * @param delegate MEGATransferDelegate to track this transfer * */ - (void)startUploadForSupportWithLocalPath:(NSString *)localPath isSourceTemporary:(BOOL)isSourceTemporary delegate:(id)delegate; /** * @brief Upload a file to support * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * For folders, onTransferFinish will be called with error MEGAErrorTypeApiEArgs. * * @param localPath Local path of the file * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to YES only if you are sure about what are you doing. * */ - (void)startUploadForSupportWithLocalPath:(NSString *)localPath isSourceTemporary:(BOOL)isSourceTemporary; /** * @brief Upload a file or a folder * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * In case any other folder is being uploaded/downloaded, and [MEGATransfer stage] for that transfer returns * a value between the following stages: MEGATransferStageScan and MEGATransferStageProcessTransferQueue * both included, don't use [MEGASDK cancelTransfer] to cancel this transfer (it could generate a deadlock), * instead of that, use [MEGACancelToken cancel] calling through MEGACancelToken instance associated to this transfer. * * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using [MEGATransfer appData] in delegates * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error MEGAErrorTypeApiEExist and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * + If you don't need this param provide NULL as value * @param fileName Custom file name for the file or folder in MEGA * + If you don't need this param provide NULL as value * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to true only if you are sure about what are you doing. * + If you don't need this param provide false as value * @param startFirst puts the transfer on top of the upload queue * + If you don't need this param provide false as value * @param cancelToken MEGACancelToken to be able to cancel a folder/file upload process. * This param is required to be able to cancel the transfer safely by calling [MEGACancelToken cancel] * You preserve the ownership of this param. */ - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent fileName:(nullable NSString *)fileName appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst cancelToken:(nullable MEGACancelToken *)cancelToken; /** * @brief Upload a file or a folder * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * In case any other folder is being uploaded/downloaded, and [MEGATransfer stage] for that transfer returns * a value between the following stages: MEGATransferStageScan and MEGATransferStageProcessTransferQueue * both included, don't use [MEGASDK cancelTransfer] to cancel this transfer (it could generate a deadlock), * instead of that, use [MEGACancelToken cancel] calling through MEGACancelToken instance associated to this transfer. * * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using [MEGATransfer appData] in delegates * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error MEGAErrorTypeApiEExist and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * + If you don't need this param provide NULL as value * @param fileName Custom file name for the file or folder in MEGA * + If you don't need this param provide NULL as value * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to true only if you are sure about what are you doing. * + If you don't need this param provide false as value * @param startFirst puts the transfer on top of the upload queue * + If you don't need this param provide false as value * @param cancelToken MEGACancelToken to be able to cancel a folder/file upload process. * This param is required to be able to cancel the transfer safely by calling [MEGACancelToken cancel] * You preserve the ownership of this param. * @param delegate MEGATransferDelegate to track this transfer */ - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent fileName:(nullable NSString *)fileName appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst cancelToken:(nullable MEGACancelToken *)cancelToken delegate:(id)delegate; /** * @brief Upload a file or a folder. * * This method starts an upload transfer for a local file or folder into the specified * parent node. * * Business account overdue: * If the status of the business account is expired/overdue, * MEGATransferDelegate::onTransferFinish() will be called with error code * MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar * to "Your business account is overdue, please contact your administrator." * * Folder batch deadlock considerations: * When uploading a batch of items that contains at least one folder, the SDK mutex will be * partially locked until: * - onTransferStart has been received for every file in the batch, and * - onTransferUpdate has been received with MEGATransfer::stage == * MEGATransferStageTranferringFiles for every folder in the batch. * * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by * calling [MEGACancelToken cancel:YES]. This cancels all transfers (not finished yet) * associated with that cancel token instance. * * Important considerations about cancel tokens: * - A MEGACancelToken instance can be shared by multiple transfers. Calling cancel:YES * affects all transfers that share the token. * - It is the app responsibility to keep the MEGACancelToken instance alive until * MEGATransferDelegate::onTransferFinish() is received for all MEGATransfers that share * it. * * For more information about MEGATransfer stages please refer to * MEGATransferDelegate::onTransferUpdate documentation. * * @param localPath Local path of the file or folder to upload. * @param parent Parent node where the file/folder will be created in the MEGA account. * @param cancelToken MEGACancelToken used to cancel the upload process safely (required for * safe cancellation). App retains ownership and must keep it alive as described above. * @param options Optional upload customization parameters (can be nil for default behavior). * @param delegate Optional MEGATransferDelegate to track this transfer. * * @note In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update its mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * MEGAErrorTypeApiEWrite. */ - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent cancelToken:(nullable MEGACancelToken *)cancelToken options:(MEGAUploadOptions *)options delegate:(id)delegate; /** * @brief Upload a file or a folder. * * This method starts an upload transfer for a local file or folder into the specified * parent node. * * This version of the method does not use a transfer delegate to track the transfer. * You won't receive any callbacks about the transfer. Useful when you only need to use * a global delegate or don't need transfer-specific tracking. * * See the full method documentation in startUploadWithLocalPath:parent:cancelToken:options:delegate: * * @param localPath Local path of the file or folder to upload. * @param parent Parent node where the file/folder will be created in the MEGA account. * @param cancelToken MEGACancelToken used to cancel the upload process safely. * @param options Optional upload customization parameters (can be nil for default behavior). */ - (void)startUploadWithLocalPath:(NSString *)localPath parent:(MEGANode *)parent cancelToken:(nullable MEGACancelToken *)cancelToken options:(MEGAUploadOptions *)options; /** * @brief Download a file or a folder from MEGA, saving custom app data during the transfer * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * In case any other folder is being uploaded/downloaded, and [MEGATransfer stage] for that transfer returns * a value between the following stages: MEGATransferStageScan and MEGATransferStageProcessTransferQueue * both included, don't use [MEGASDK cancelTransfer] to cancel this transfer (it could generate a deadlock), * instead of that, use [MEGACancelToken cancel] calling through MEGACancelToken instance associated to this transfer. * * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param node MEGANode that identifies the file or folder * @param localPath Destination path for the file or folder * If this path is a local folder, it must end with a '\' or '/' character and the file name * in MEGA will be used to store a file inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using [MEGATransfer appData] in delegates * related to the transfer. * + If you don't need this param provide NULL as value * @param fileName Custom file name for the file or folder in local destination * + If you don't need this param provide NULL as value * @param startFirst puts the transfer on top of the download queue * + If you don't need this param provide false as value * @param cancelToken MEGACancelToken to be able to cancel a folder/file download process. * This param is required to be able to cancel the transfer safely by calling [MEGACancelToken cancel] * You preserve the ownership of this param. * @param collisionCheck Indicates the collision check on same files * @param collisionResolution Indicates how to save same files */ - (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath fileName:(nullable NSString*)fileName appData:(nullable NSString *)appData startFirst:(BOOL) startFirst cancelToken:(nullable MEGACancelToken *)cancelToken collisionCheck:(CollisionCheck)collisionCheck collisionResolution:(CollisionResolution)collisionResolution; /** * @brief Download a file or a folder from MEGA, saving custom app data during the transfer * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * In case any other folder is being uploaded/downloaded, and [MEGATransfer stage] for that transfer returns * a value between the following stages: MEGATransferStageScan and MEGATransferStageProcessTransferQueue * both included, don't use [MEGASDK cancelTransfer] to cancel this transfer (it could generate a deadlock), * instead of that, use [MEGACancelToken cancel] calling through MEGACancelToken instance associated to this transfer. * * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param node MEGANode that identifies the file or folder * @param localPath Destination path for the file or folder * If this path is a local folder, it must end with a '\' or '/' character and the file name * in MEGA will be used to store a file inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using [MEGATransfer appData] in delegates * related to the transfer. * + If you don't need this param provide NULL as value * @param fileName Custom file name for the file or folder in local destination * + If you don't need this param provide NULL as value * @param startFirst puts the transfer on top of the download queue * + If you don't need this param provide false as value * @param cancelToken MEGACancelToken to be able to cancel a folder/file download process. * This param is required to be able to cancel the transfer safely by calling [MEGACancelToken cancel] * You preserve the ownership of this param. * @param collisionCheck Indicates the collision check on same files * @param collisionResolution Indicates how to save same files * @param delegate Delegate to track this transfer. */ - (void)startDownloadNode:(MEGANode *)node localPath:(NSString *)localPath fileName:(nullable NSString*)fileName appData:(nullable NSString *)appData startFirst:(BOOL) startFirst cancelToken:(nullable MEGACancelToken *)cancelToken collisionCheck:(CollisionCheck)collisionCheck collisionResolution:(CollisionResolution)collisionResolution delegate:(id)delegate; /** * @brief Start an streaming download for a file in MEGA * * Streaming downloads don't save the downloaded data into a local file. It is provided * in the callback [MEGATransferDelegate onTransferData:transfer:]. Only the MEGATransferDelegate * passed to this function will receive [MEGATransferDelegate onTransferData:transfer:] callbacks. * MEGATransferDelegate objects registered with [MEGASdk addMEGATransferDelegate:] won't * receive them for performance reasons. * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * @param node MEGANode that identifies the file (public nodes aren't supported yet) * @param startPos First byte to download from the file * @param size Size of the data to download * @param delegate MEGATransferDelegate to track this transfer */ - (void)startStreamingNode:(MEGANode *)node startPos:(NSNumber *)startPos size:(NSNumber *)size delegate:(id)delegate; /** * @brief Start an streaming download for a file in MEGA * * Streaming downloads don't save the downloaded data into a local file. It is provided * in the callback [MEGATransferDelegate onTransferData:transfer:]. Only the MEGATransferDelegate * passed to this function will receive [MEGATransferDelegate onTransferData:transfer:] callbacks. * MEGATransferDelegate objects registered with [MEGASdk addMEGATransferDelegate:] won't * receive them for performance reasons. * * If the status of the business account is expired, onTransferFinish will be called with the error * code MEGAErrorTypeApiEBusinessPastDue. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * @param node MEGANode that identifies the file (public nodes aren't supported yet) * @param startPos First byte to download from the file * @param size Size of the data to download */ - (void)startStreamingNode:(MEGANode *)node startPos:(NSNumber *)startPos size:(NSNumber *)size; /** * @brief Cancel a transfer. * * When a transfer is cancelled, it will finish and will provide the error code * MEGAErrorTypeApiEIncomplete in [MEGATransferDelegate onTransferFinish:transfer:error:] and * [MEGADelegate onTransferFinish:transfer:error:]. * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the cancelled transfer ([MEGATransfer tag]) * * @param transfer MEGATransfer object that identifies the transfer. * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * * @param delegate Delegate to track this request. */ - (void)cancelTransfer:(MEGATransfer *)transfer delegate:(id)delegate; /** * @brief Cancel a transfer. * * When a transfer is cancelled, it will finish and will provide the error code * MEGAErrorTypeApiEIncomplete in [MEGATransferDelegate onTransferFinish] and * [MEGADelegate onTransferFinish] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the cancelled transfer ([MEGATransfer tag]) * * @param transfer MEGATransfer object that identifies the transfer * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * */ - (void)cancelTransfer:(MEGATransfer *)transfer; /** * @brief Retry a transfer * * This function allows to start a transfer based on a MEGATransfer object. It can be used, * for example, to retry transfers that finished with an error. To do it, you can retain the * MEGATransfer object in onTransferFinish (calling [MEGATransfer clone] to take the ownership) * and use it later with this function. * * If the transfer parameter is nil or is not of type MEGATransferTypeDownload or * MEGATransferTypeUpload (transfers started with [MEGASdk startDownload] or * [MEGASdk startUpload) the function returns without doing anything. * * @param transfer Transfer to be retried * @param delegate MEGATransferDelegate to track this transfer */ - (void)retryTransfer:(MEGATransfer *)transfer delegate:(id)delegate; /** * @brief Retry a transfer * * This function allows to start a transfer based on a MEGATransfer object. It can be used, * for example, to retry transfers that finished with an error. To do it, you can retain the * MEGATransfer object in onTransferFinish (calling [MEGATransfer clone] to take the ownership) * and use it later with this function. * * If the transfer parameter is nil or is not of type MEGATransferTypeDownload or * MEGATransferTypeUpload (transfers started with [MEGASdk startDownload] or * [MEGASdk startUpload) the function returns without doing anything. * * @param transfer Transfer to be retried */ - (void)retryTransfer:(MEGATransfer *)transfer; /** * @brief Move a transfer to the top of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer MEGATransfer object that identifies the transfer. * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * * @param delegate Delegate to track this request. */ - (void)moveTransferToFirst:(MEGATransfer *)transfer delegate:(id)delegate; /** * @brief Move a transfer to the top of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer MEGATransfer object that identifies the transfer */ - (void)moveTransferToFirst:(MEGATransfer *)transfer; /** * @brief Move a transfer to the bottom of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer MEGATransfer object that identifies the transfer. * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * * @param delegate Delegate to track this request. */ - (void)moveTransferToLast:(MEGATransfer *)transfer delegate:(id)delegate; /** * @brief Move a transfer to the bottom of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer MEGATransfer object that identifies the transfer. * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * */ - (void)moveTransferToLast:(MEGATransfer *)transfer; /** * @brief Move a transfer before another one in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer Transfer to move * @param prevTransfer Transfer with the target position * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * * @param delegate Delegate to track this request. */ - (void)moveTransferBefore:(MEGATransfer *)transfer prevTransfer:(MEGATransfer *)prevTransfer delegate:(id)delegate; /** * @brief Move a transfer before another one in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using [MEGATransfer priority] * * The associated request type with this request is MEGARequestTypeCancelTransfer. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to move * * @param transfer Transfer to move * @param prevTransfer Transfer with the target position * You can get this object in any MEGATransferDelegate callback or any MEGADelegate callback * related to transfers. * */ - (void)moveTransferBefore:(MEGATransfer *)transfer prevTransfer:(MEGATransfer *)prevTransfer; /** * @brief Cancel all transfers of the same type. * * The associated request type with this request is MEGARequestTypeCancelTransfers. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the first parameter * * @param direction Type of transfers to cancel. * Valid values are: * - MEGATransferTypeDownload = 0 * - MEGATransferTypeUpload = 1 * * @param delegate Delegate to track this request. */ - (void)cancelTransfersForDirection:(NSInteger)direction delegate:(id)delegate; /** * @brief Cancel all transfers of the same type. * * The associated request type with this request is MEGARequestTypeCancelTransfers. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the first parameter * * @param direction Type of transfers to cancel. * Valid values are: * - MEGATransferTypeDownload = 0 * - MEGATransferTypeUpload = 1 * */ - (void)cancelTransfersForDirection:(NSInteger)direction; /** * @brief Cancel the transfer with a specific tag * * When a transfer is cancelled, it will finish and will provide the error code * MEGAErrorTypeApiEIncomplete in [MEGATransferDelegate onTransferFinish:] and * [MEGADelegate onTransferFinish:] * * The associated request type with this request is MEGARequestTypeCancelTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the cancelled transfer ([MEGATransfer tag]) * * @param transferTag tag that identifies the transfer * You can get this tag using [MEGATransfer tag] * * @param delegate MEGARequestDelegate to track this request */ - (void)cancelTransferByTag:(NSInteger)transferTag delegate:(id)delegate; /** * @brief Cancel the transfer with a specific tag * * When a transfer is cancelled, it will finish and will provide the error code * MEGAErrorTypeApiEIncomplete in [MEGATransferDelegate onTransferFinish:] and * [MEGADelegate onTransferFinish:] * * The associated request type with this request is MEGARequestTypeCancelTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the cancelled transfer ([MEGATransfer tag]) * * @param transferTag tag that identifies the transfer * You can get this tag using [MEGATransfer tag] * */ - (void)cancelTransferByTag:(NSInteger)transferTag; /** * @brief Pause/resume all transfers. * * The associated request type with this request is MEGARequestTypePauseTransfers. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * * @param pause YES to pause all transfers / NO to resume all transfers. * @param delegate Delegate to track this request. */ - (void)pauseTransfers:(BOOL)pause delegate:(id)delegate; /** * @brief Pause/resume all transfers. * * The associated request type with this request is MEGARequestTypePauseTransfers. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * * @param pause YES to pause all transfers / NO to resume all transfers. */ - (void)pauseTransfers:(BOOL)pause; /** * @brief Pause/resume a transfer * * The associated request type with this request is MEGARequestTypePauseTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to pause or resume * - [MEGARequest flag] - Returns YES if the transfer has to be pause or NO if it has to be resumed * * @param transfer Transfer to pause or resume * @param pause YES to pause the transfer or NO to resume it * @param delegate MEGARequestDelegate to track this request */ - (void)pauseTransfer:(MEGATransfer *)transfer pause:(BOOL)pause delegate:(id)delegate; /** * @brief Pause/resume a transfer * * The associated request type with this request is MEGARequestTypePauseTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to pause or resume * - [MEGARequest flag] - Returns YES if the transfer has to be pause or NO if it has to be resumed * * @param transfer Transfer to pause or resume * @param pause YES to pause the transfer or NO to resume it */ - (void)pauseTransfer:(MEGATransfer *)transfer pause:(BOOL)pause; /** * @brief Pause/resume a transfer * * The associated request type with this request is MEGARequestTypePauseTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to pause or resume * - [MEGARequest flag] - Returns YES if the transfer has to be pause or NO if it has to be resumed * * @param transferTag Tag of the transfer to pause or resume * @param pause YES to pause the transfer or NO to resume it * @param delegate MEGARequestDelegate to track this request */ - (void)pauseTransferByTag:(NSInteger)transferTag pause:(BOOL)pause delegate:(id)delegate; /** * @brief Pause/resume a transfer * * The associated request type with this request is MEGARequestTypePauseTransfer * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest transferTag] - Returns the tag of the transfer to pause or resume * - [MEGARequest flag] - Returns YES if the transfer has to be pause or NO if it has to be resumed * * @param transferTag Tag of the transfer to pause or resume * @param pause YES to pause the transfer or NO to resume it */ - (void)pauseTransferByTag:(NSInteger)transferTag pause:(BOOL)pause; /** * @brief Pause/resume all transfers in one direction (uploads or downloads) * * The associated request type with this request is MEGARequestTypePauseTransfers * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * - [MEGARequest number] - Returns the direction of the transfers to pause/resume * * @param pause YES to pause transfers / NO to resume transfers * @param direction Direction of transfers to pause/resume * Valid values for this parameter are: * - MEGATransferTypeDownload = 0 * - MEGATransferTypeUpload = 1 * * @param delegate MEGARequestDelegate to track this request */ - (void)pauseTransfers:(BOOL)pause forDirection:(NSInteger)direction delegate:(id)delegate; /** * @brief Pause/resume all transfers in one direction (uploads or downloads) * * The associated request type with this request is MEGARequestTypePauseTransfers * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Returns the first parameter * - [MEGARequest number] - Returns the direction of the transfers to pause/resume * * @param pause YES to pause transfers / NO to resume transfers * @param direction Direction of transfers to pause/resume * Valid values for this parameter are: * - MEGATransferTypeDownload = 0 * - MEGATransferTypeUpload = 1 * */ - (void)pauseTransfers:(BOOL)pause forDirection:(NSInteger)direction; /** * @brief Returns the state (paused/unpaused) of transfers * @param direction Direction of transfers to check * Valid values for this parameter are: * - MEGATransferTypeDownload = 0 * - MEGATransferTypeUpload = 1 * * @return YES if transfers on that direction are paused, NO otherwise */ - (BOOL)areTransferPausedForDirection:(NSInteger)direction; /** * @brief Checks if there are any active transfers that match the provided filter criteria. * * This method searches through all current transfers (both uploads and downloads) to determine * if any transfer matches the criteria defined by the filter block. The filter block is called * for each active transfer with its app data, allowing for custom matching logic. * * @param filter A block that receives the app data of each transfer and returns YES if the * transfer matches the desired criteria, NO otherwise. The block parameter is the app data * string associated with the transfer, which may be nil if no app data was set. * * @return YES if at least one transfer matching the filter criteria is found, NO otherwise. * Also returns NO if there are no active transfers. */ - (BOOL)areThereAnyTransferWithAppDataMatching:(BOOL (^)(NSString *appData))filter; /** * @brief Request the URL suitable for uploading a media file. * * This function requests the URL needed for uploading the file. The URL will need the urlSuffix * from the encryptFileAtPath:startPosition:length:outputFilePath:urlSuffix:adjustsSizeOnly: * in MEGABackgroundMediaUpload to be appended before actually sending. * The result of the request is signalled by the delegate onRequestFinsish callback with MEGARequestTypeGetBackgroundUploadURL. * Provided the error code is MEGAErrorTypeApiOk, the URL is available from uploadURLString in the MEGABackgroundMediaUpload. * * Call this function just once (per file) to find out the URL to upload to, and upload all the pieces to the same * URL. If errors are encountered and the operation must be restarted from scratch, then a new URL should be requested. * A new URL could specify a different upload server for example. * * @param filesize The size of the file * @param mediaUpload A pointer to the MEGABackgroundMediaUpload object tracking this upload * @param delegate The MEGARequestDelegate to be called back with the result */ - (void)requestBackgroundUploadURLWithFileSize:(int64_t)filesize mediaUpload:(MEGABackgroundMediaUpload *)mediaUpload delegate:(id)delegate; /** * @brief Create the node after completing the background upload of the file. * * Call this function after completing the background upload of all the file data * The node representing the file will be created in the cloud, with all the suitable * attributes and file attributes attached. * The associated request type with this request is MEGARequestTypeCompleteBackgroundUpload. * * @param mediaUpload The MEGABackgroundMediaUpload object tracking this upload. * @param fileName The leaf name of the file, utf-8 encoded. * @param parentNode The folder node under which this new file should appear. * @param fingerprint The fingerprint for the uploaded file. * To generate this, you can use the following APIs in MEGASdk: * - fingerprintForFilePath: * - fingerprintForData:modificationTime: * - fingerprintForFilePath:modificationTime: * @param originalFingerprint If the file uploaded is modified from the original, * pass the fingerprint of the original file here, otherwise nil. * @param token The N binary bytes of the token returned from the file upload (of the last portion). N=36 currently. * @param delegate The MEGARequestDelegate to be called back with the result. */ - (void)completeBackgroundMediaUpload:(MEGABackgroundMediaUpload *)mediaUpload fileName:(NSString *)fileName parentNode:(MEGANode *)parentNode fingerprint:(NSString *)fingerprint originalFingerprint:(nullable NSString *)originalFingerprint binaryUploadToken:(NSData *)token delegate:(id)delegate; /** * @brief Call this to enable the library to attach media info attributes. * * Those attributes allows to know if a file is a video, and play it with the correct codec. * * If media info is not ready, this function returns NO and automatically retrieves the mappings for type names * and MEGA encodings, required to analyse media files. When media info is received, the callbacks * onEvent is called with the EventMediaInfoReady event type. * * @return YES if the library is ready, otherwise NO (the request for media translation data is sent to MEGA). */ - (BOOL)ensureMediaInfo; /** * @brief confirm available memory to avoid OOM situations * * Before queueing a thumbnail or preview upload (or other memory intensive task), * it may be useful on some devices to check if there is plenty of memory available * in the memory pool used by MEGASdk (especially since some platforms may not have * the facility to check for themselves, and/or deallocation may need to wait on a GC) * and if not, delay until any current resource constraints (eg. other current operations, * or other RAM-hungry apps in the device), have finished. This function just * makes several memory allocations and then immediately releases them. If all allocations * succeeded, it returns YES, indicating that memory is (probably) available. * Of course, another app or operation may grab that memory immediately so it not a * guarantee. However it may help to reduce the frequency of OOM situations on phones for example. * * @param count The number of allocations to make * @param size The size of those memory allocations * @return YES if all the allocations succeeded */ - (BOOL)testAllocationByAllocationCount:(NSUInteger)count allocationSize:(NSUInteger)size; #pragma mark - Filesystem inspection /** * @brief Get the number of child nodes. * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0. * * This function doesn't search recursively, only returns the direct child nodes. * * @param parent Parent node. * @return Number of child nodes. */ - (NSInteger)numberChildrenForParent:(MEGANode *)parent; /** * @brief Get the number of child files of a node. * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0. * * This function doesn't search recursively, only returns the direct child files. * * @param parent Parent node. * @return Number of child files. */ - (NSInteger)numberChildFilesForParent:(MEGANode *)parent; /** * @brief Get the number of child folders of a node. * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0. * * This function doesn't search recursively, only returns the direct child folders. * * @param parent Parent node. * @return Number of child folders. */ - (NSInteger)numberChildFoldersForParent:(MEGANode *)parent; /** * @brief Get all children of a MEGANode. * * @param parent Parent node. * @param order Order for the returned list. * Valid values for this parameter are: * - MEGASortOrderTypeNone = 0 * Undefined order * * - MEGASortOrderTypeDefaultAsc = 1 * Folders first in alphabetical order, then files in the same order * * - MEGASortOrderTypeDefaultDesc = 2 * Files first in reverse alphabetical order, then folders in the same order * * - MEGASortOrderTypeSizeAsc = 3 * Sort by size, ascending * * - MEGASortOrderTypeSizeDesc = 4 * Sort by size, descending * * - MEGASortOrderTypeCreationAsc = 5 * Sort by creation time in MEGA, ascending * * - MEGASortOrderTypeCreationDesc = 6 * Sort by creation time in MEGA, descending * * - MEGASortOrderTypeModificationAsc = 7 * Sort by modification time of the original file, ascending * * - MEGASortOrderTypeModificationDesc = 8 * Sort by modification time of the original file, descending * * - MEGASortOrderTypePhotoAsc = 11 * Sort with photos first, then by date ascending * * - MEGASortOrderTypePhotoDesc = 12 * Sort with photos first, then by date descending * * - MEGASortOrderTypeVideoAsc = 13 * Sort with videos first, then by date ascending * * - MEGASortOrderTypeVideoDesc = 14 * Sort with videos first, then by date descending * * - MEGASortOrderTypeLinkCreationAsc = 15 * * - MEGASortOrderTypeLinkCreationDesc = 16 * * - MEGASortOrderTypeLabelAsc = 17 * Sort by color label, ascending. With this order, folders are returned first, then files * * - MEGASortOrderTypeLabelDesc = 18 * Sort by color label, descending. With this order, folders are returned first, then files * * - MEGASortOrderTypeFavouriteAsc = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files * * - MEGASortOrderTypeFavouriteDesc = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * * @return List with all child MEGANode objects. */ - (MEGANodeList *)childrenForParent:(MEGANode *)parent order:(NSInteger)order; /** * @brief Get all children of a MEGANode. * * @param parent Parent node. Sort in alphabetical order, descending * * @return List with all child MEGANode objects. */ - (MEGANodeList *)childrenForParent:(MEGANode *)parent; /** * @brief Get the child node with the provided name. * * If the node doesn't exist, this function returns nil. * * @param parent Parent node. * @param name Name of the node. * @return The MEGANode that has the selected parent and name. */ - (nullable MEGANode *)childNodeForParent:(MEGANode *)parent name:(NSString *)name; /** * @brief Get the child node with the provided name. * * If the node doesn't exist, this function returns nil. * It's possible to have multiple nodes with the same name. * This function will return one of them. * * @param parent Parent node. * @param name Name of the node. * @param type Type of the node. Allowed types: MEGANodeTypeFile and MEGANodeTypeFolder. * @return The MEGANode that has the selected parent, name and type. */ - (nullable MEGANode *)childNodeForParent:(MEGANode *)parent name:(NSString *)name type:(MEGANodeType)type; /** * @brief Get all versions of a file * @param node Node to check * @return List with all versions of the node, including the current version */ - (MEGANodeList *)versionsForNode:(MEGANode *)node; /** * @brief Get the number of versions of a file * @param node Node to check * @return Number of versions of the node, including the current version */ - (NSInteger)numberOfVersionsForNode:(MEGANode *)node; /** * @brief Check if a file has previous versions * @param node Node to check * @return YES if the node has any previous version */ - (BOOL)hasVersionsForNode:(MEGANode *)node; /** * @brief Get information about the contents of a folder * * The associated request type with this request is MEGARequestTypeFolderInfo * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaFolderInfo] - MEGAFolderInfo object with the information related to the folder * * @param node Folder node to inspect * @param delegate MEGARequestDelegate to track this request */ - (void)getFolderInfoForNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Get information about the contents of a folder * * The associated request type with this request is MEGARequestTypeFolderInfo * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaFolderInfo] - MEGAFolderInfo object with the information related to the folder * * @param node Folder node to inspect */ - (void)getFolderInfoForNode:(MEGANode *)node; /** * @brief Get the parent node of a MEGANode. * * If the node doesn't exist in the account or * it is a root node, this function returns nil. * * @param node MEGANode to get the parent. * @return The parent of the provided node. */ - (nullable MEGANode *)parentNodeForNode:(MEGANode *)node; /** * @brief Get the path of a MEGANode. * * If the node doesn't exist, this function returns nil. * You can recoved the node later using [MEGASdk nodeForPath:] * except if the path contains names with '/', '\' or ':' characters. * * @param node MEGANode for which the path will be returned. * @return The path of the node. */ - (nullable NSString *)nodePathForNode:(MEGANode *)node; /** * @brief Get the MEGANode in a specific path in the MEGA account. * * The path separator character is '/' * The root node is / * The Inbox root node is //in/ * The Rubbish root node is //bin/ * * Paths with names containing '/', '\' or ':' aren't compatible * with this function. * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return nil. * * @param path Path to check. * @param node Base node if the path is relative. * @return The MEGANode object in the path, otherwise nil. */ - (nullable MEGANode *)nodeForPath:(NSString *)path node:(MEGANode *)node; /** * @brief Get the MEGANode in a specific path in the MEGA account. * * The path separator character is '/' * The root node is / * The Inbox root node is //in/ * The Rubbish root node is //bin/ * * Paths with names containing '/', '\' or ':' aren't compatible * with this function. * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return nil. * * @param path Path to check. * @return The MEGANode object in the path, otherwise nil. */ - (nullable MEGANode *)nodeForPath:(NSString *)path; /** * @brief Get the MEGANode that has a specific handle. * * You can get the handle of a MEGANode using [MEGANode handle]. The same handle * can be got in a Base64-encoded string using [MEGANode base64Handle]. Conversions * between these formats can be done using [MEGASdk handleForBase64Handle:] and [MEGASdk base64HandleForHandle:]. * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return nil. * * @param handle Node handle to check. * @return MEGANode object with the handle, otherwise nil. */ - (nullable MEGANode *)nodeForHandle:(uint64_t)handle; /** * @brief Get all contacts of this MEGA account. * * @return List of MEGAUser object with all contacts of this account. */ - (MEGAUserList *)contacts; /** * @brief Get the MEGAUser that has a specific email address. * * You can get the email of a MEGAUser using [MEGAUser email]. * * @param email Email address to check. * @return MEGAUser that has the email address, otherwise nil. */ - (nullable MEGAUser *)contactForEmail:(nullable NSString *)email; /** * @brief Get all MEGAUserAlerts for the logged in user * * @return List of MEGAUserAlert objects */ - (MEGAUserAlertList *)userAlertList; /** * @brief Get a list with all inbound sharings from one MEGAUser. * * @param user MEGAUser sharing folders with this account. * @return List of MEGANode objects that this user is sharing with this account. */ - (MEGANodeList *)inSharesForUser:(MEGAUser *)user; /** * @brief Get a list with all inboud sharings. * * @return List of MEGANode objects that other users are sharing with this account. */ - (MEGANodeList *)inShares; /** * @brief Get a list with all active inboud sharings * * @param order Order for the returned list. * @return List of MegaShare objects that other users are sharing with this account */ - (MEGAShareList *)inSharesList:(MEGASortOrderType)order; /** * @brief Get a list with all unverified inbound sharings * * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects that other users are sharing with this account */ - (MEGAShareList *)getUnverifiedInShares:(MEGASortOrderType)order; /** * @brief Get the user relative to an incoming share * * This function will return nil if the node is not found or doesn't represent * the root of an incoming share. * * @param node Incoming share * @return MEGAUser relative to the incoming share */ - (nullable MEGAUser *)userFromInShareNode:(MEGANode *)node; /** * @brief Get the user relative to an incoming share * * This function will return nil if the node is not found. * * If recurse is true, it will return nil if the root corresponding to * the node received as argument doesn't represent the root of an incoming share. * Otherwise, it will return nil if the node doesn't represent * the root of an incoming share. * * @param node Node to look for inshare user. * @param recurse use root node corresponding to the node passed * @return MegaUser relative to the incoming share */ - (nullable MEGAUser *)userFromInShareNode:(MEGANode *)node recurse:(BOOL)recurse; /** * @brief Get a list with all active outbound sharings * * @param order Order for the returned list. * @return List of MegaShare objects */ - (MEGAShareList *)outShares:(MEGASortOrderType)order; /** * @brief Get a list with all unverified sharings * * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects */ - (MEGAShareList *)getUnverifiedOutShares:(MEGASortOrderType)order; /** * @brief Get a list with the active outbound sharings for a MEGANode. * * If the node doesn't exist in the account, this function returns an empty list. * * @param node MEGANode to check. * @return List of MEGAShare objects. */ - (MEGAShareList *)outSharesForNode:(MEGANode *)node; /** * @brief Check if a node belongs to your own cloud * @param handle Node to check * @return YES if it belongs to your own cloud */ - (BOOL)isPrivateNode:(uint64_t)handle; /** * @brief Check if a node does NOT belong to your own cloud * * In example, nodes from incoming shared folders do not belong to your cloud. * * @param handle Node to check * @return YES if it does NOT belong to your own cloud */ - (BOOL)isForeignNode:(uint64_t)handle; /** * @brief Get a list with all public links * * @param order Order for the returned list. * Valid value for order are: MEGASortOrderTypeNone, MEGASortOrderTypeDefaultAsc, * MEGASortOrderTypeDefaultDesc, MEGASortOrderTypeLinkCreationAsc, * MEGASortOrderTypeLinkCreationDesc * @return List of MEGANode objects that are shared with everyone via public link */ - (MEGANodeList *)publicLinks:(MEGASortOrderType)order; /** * @brief Get a list with all incoming contact requests * * @return List of MEGAContactRequest objects */ - (MEGAContactRequestList *)incomingContactRequests; /** * @brief Get a list with all outgoing contact requests * * @return List of MEGAContactRequest objects */ - (MEGAContactRequestList *)outgoingContactRequests; /** * @brief Get a Base64-encoded fingerprint for a local file. * * The fingerprint is created taking into account the modification time of the file * and file contents. This fingerprint can be used to get a corresponding node in MEGA * using [MEGASdk nodeForFingerprint:]. * * If the file can't be found or can't be opened, this function returns nil. * * @param filePath Local file path. * @return Base64-encoded fingerprint for the file. */ - (nullable NSString *)fingerprintForFilePath:(NSString *)filePath; /** * @brief Get a Base64-encoded fingerprint from a NSData and a modification time * * If the input stream is nil, has a negative size or can't be read, this function returns nil * * @param data NSData that provides the data to create the fingerprint * @param modificationTime Modification time that will be taken into account for the creation of the fingerprint * @return Base64-encoded fingerprint */ - (nullable NSString *)fingerprintForData:(NSData *)data modificationTime:(NSDate *)modificationTime; /** * @brief Get a Base64-encoded fingerprint from a local file and a modification time * * If the file can't be found or can't be opened, this function returns nil. * * @param filePath Local file path. * @param modificationTime Modification time that will be taken into account for the creation of the fingerprint * @return Base64-encoded fingerprint */ - (nullable NSString *)fingerprintForFilePath:(NSString *)filePath modificationTime:(NSDate *)modificationTime; /** * @brief Returns a node with the provided fingerprint. * * If there isn't any node in the account with that fingerprint, this function returns nil. * * @param fingerprint Fingerprint to check. * @return MEGANode object with the provided fingerprint. */ - (nullable MEGANode *)nodeForFingerprint:(NSString *)fingerprint; /** * @brief Returns a node with the provided fingerprint. * * If there isn't any node in the account with that fingerprint, this function returns nil. * * @param fingerprint Fingerprint to check. * @param parent Preferred parent node * @return MEGANode object with the provided fingerprint. */ - (nullable MEGANode *)nodeForFingerprint:(NSString *)fingerprint parent:(MEGANode *)parent; /** * @brief Returns nodes that have an original fingerprint equal to the supplied value * * Search the node tree and return a list of nodes that have an original fingerprint, which * matches the supplied originalfingerprint. * * @param fingerprint Original fingerprint to check * @return List of nodes with the same original fingerprint */ - (MEGANodeList *)nodesForOriginalFingerprint:(NSString *)fingerprint; /** * @brief Check if the account already has a node with the provided fingerprint. * * A fingerprint for a local file can be generated using [MEGASdk fingerprintForFilePath:]. * * @param fingerprint Fingerprint to check. * @return YES if the account contains a node with the same fingerprint. */ - (BOOL)hasFingerprint:(NSString *)fingerprint; /** * @brief Get the CRC of a file * * The CRC of a file is a hash of its contents. * If you need a more realiable method to check files, use fingerprint functions * ([MEGASdk fingerprintForFilePath:], [MEGASdk nodeForFingerprint:]) that also takes into * account the size and the modification time of the file to create the fingerprint. * * @param filePath Local file path * @return Base64-encoded CRC of the file */ - (nullable NSString *)CRCForFilePath:(NSString *)filePath; /** * @brief Get the CRC of a node * * The CRC of a node is a hash of its contents. * If you need a more realiable method to check files, use fingerprint functions * ([MEGASdk fingerprintForFilePath:], [MEGASdk nodeForFingerprint:]) that also takes into * account the size and the modification time of the node to create the fingerprint. * * @param node MEGANode for which we want to get the CRC * @return Base64-encoded CRC of the node */ - (nullable NSString *)CRCForNode:(MEGANode *)node; /** * @brief Get the CRC from a fingerPrint * * @param fingerprint fingerPrint from which we want to get the CRC * @return Base64-encoded CRC from the fingerPrint */ - (nullable NSString *)CRCForFingerprint:(NSString *)fingerprint; /** * @brief Returns a node with the provided CRC * * If there isn't any node in the selected folder with that CRC, this function returns nil. * If there are several nodes with the same CRC, anyone can be returned. * * @param crc CRC to check * @param parent Parent MEGANode to scan. It must be a folder. * @return node with the selected CRC in the selected folder, or nil * if it's not found. */ - (nullable MEGANode *)nodeByCRC:(NSString *)crc parent:(MEGANode *)parent; /** * @brief Get the access level of a MEGANode. * @param node MEGANode to check. * @return Access level of the node. * Valid values are: * - MEGAShareTypeAccessOwner * - MEGAShareTypeAccessFull * - MEGAShareTypeAccessReadWrite * - MEGAShareTypeAccessRead * - MEGAShareTypeAccessUnknown */ - (MEGAShareType)accessLevelForNode:(MEGANode *)node; /** * @brief Get the access level of a nodeHandle. * @param nodeHandle Node handle to check. * @return Access level of the node. * Valid values are: * - MEGAShareTypeAccessOwner * - MEGAShareTypeAccessFull * - MEGAShareTypeAccessReadWrite * - MEGAShareTypeAccessRead * - MEGAShareTypeAccessUnknown */ - (MEGAShareType)accessLevelForNodeHande:(uint64_t)nodeHandle; /** * @brief Check if a node has an access level * * @param node Node to check * @param level Access level to check * Valid values for this parameter are: * - MEGAShareTypeAccessOwner * - MEGAShareTypeAccessFull * - MEGAShareTypeAccessReadWrite * - MEGAShareTypeAccessRead * * @return Error with the result. * Valid values for the error code are: * - MEGAErrorTypeApiOk - The node has the required access level * - MEGAErrorTypeApiEAccess - The node doesn't have the required access level * - MEGAErrorTypeApiENoent - The node doesn't exist in the account * - MEGAErrorTypeApiEArgs - Invalid parameters */ - (MEGAError *)checkAccessErrorExtendedForNode:(MEGANode *)node level:(MEGAShareType)level; /** * @brief Check if a node can be moved to a target node. * * @param node Node to check. * @param target Target for the move operation. * @return MEGAError object with the result: * Valid values for the error code are: * - MEGAErrorTypeApiOk - The node can be moved to the target * - MEGAErrorTypeApiEAccess - The node can't be moved because of permissions problems * - MEGAErrorTypeApiECircular - The node can't be moved because that would create a circular linkage * - MEGAErrorTypeApiENoent - The node or the target doesn't exist in the account * - MEGAErrorTypeApiEArgs - Invalid parameters */ - (MEGAError *)checkMoveErrorExtendedForNode:(MEGANode *)node target:(MEGANode *)target; /** * @brief Check if a node is in the Rubbish bin tree * * @param node Node to check * @return YES if the node is in the Rubbish bin */ - (BOOL)isNodeInRubbish:(MEGANode *)node; /** * @brief Ascertain if the node is marked as sensitive or a descendent of such * * see [MEGANode isMarkedSensitive] to see if the node is sensitive * * @param node node to inspect */ -(BOOL)isNodeInheritingSensitivity:(MEGANode *)node; /** * @brief Retrieve all unique node tags present across all nodes in the account * * @note If the searchString contains invalid characters, such as ',', an empty list will be * returned. * * @note This function allows to cancel the processing at any time by passing a * MEGACancelToken and calling to [MEGACancelToken cancel] . * * * @param searchString Optional parameter to filter the tags based on a specific search * string. If set to nil, all node tags will be retrieved. * @param cancelToken MEGACancelToken to be able to cancel the processing at any time. * * @return All the unique node tags that match the search criteria. */ - (nullable NSArray *)nodeTagsForSearchString:(nullable NSString *)searchString cancelToken:(MEGACancelToken *)cancelToken; /** * @brief Add new tag stored as node attribute * * The associated request type with this request is MEGARequestTypeNodeTag * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that received the tag * - [MEGARequest paramType] - Returns operation type (0 - Add tag, 1 - Remove tag, 2 - Update tag) * - [MEGARequest getText] - Returns tag * * ',' is an invalid character to be used in a tag. If it is contained in the tag, * onRequestFinish will be called with the error code MEGAErrorTypeApiEArgs. * * If the length of all tags is higher than 3000 onRequestFinish will be called with * the error code MEGAErrorTypeApiEArgs * * If tag already exists, onRequestFinish will be called with the error code MEGAErrorTypeApiEExist * * If number of tags exceed the maximum number of tags (10), * onRequestFinish will be called with the error code MEGAErrorTypeApiETooMany * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param tag New tag * @param node Node that will receive the information. * @param delegate MEGARequestDelegate to track this request */ - (void)addTag:(NSString *)tag toNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Remove a tag stored as a node attribute * * The associated request type with this request is MEGARequestTypeNodeTag * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - Returns the handle of the node that received the tag * - [MEGARequest paramType] - Returns operation type (0 - Add tag, 1 - Remove tag, 2 - Update tag) * - [MEGARequest getText] - Returns tag * * If tag doesn't exist, onRequestFinish will be called with the error code MEGAErrorTypeApiENoent * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param tag Tag to be removed * @param node Node that will receive the information. * @param delegate MEGARequestDelegate to track this request */ - (void)removeTag:(NSString *)tag fromNode:(MEGANode *)node delegate:(id)delegate; /** * @brief Search nodes with applied filter recursively. * * The search is case-insensitive. * * @param filter Filter we should apply to the current search. * @param orderType Order type we should apply to the current search. * @param page Paged criteria for request * * @return List of nodes that contain the desired string in their name. */ - (MEGANodeList *)searchWith:(MEGASearchFilter *)filter orderType:(MEGASortOrderType)orderType page:(nullable MEGASearchPage *)page cancelToken:(MEGACancelToken *)cancelToken; /** * @brief Search nodes with applied filter non-recursively. * * The search is case-insensitive. * * @param filter Filter we should apply to the current search. * @param orderType Order type we should apply to the current search. * @param page Paged criteria for request * NO if you want to search in the children of the node only * * @return List of nodes that contain the desired string in their name. */ - (MEGANodeList *)searchNonRecursivelyWith:(MEGASearchFilter *)filter orderType:(MEGASortOrderType)orderType page:(nullable MEGASearchPage *)page cancelToken:(MEGACancelToken *)cancelToken; /// Get a list of buckets, each bucket containing a list of recently added/modified nodes /// /// Each bucket contains files that were added/modified in a set, by a single user. /// /// Valid data in the MEGARequest object received on callbacks: /// /// - [MEGARequest number] - Returns the number of days since nodes will be considered /// /// - [MEGARequest paramType] - Returns the maximum number of nodes /// /// The associated request type with this request is MEGARequestTypeGetRecentActions /// Valid data in the MegaRequest object received in onRequestFinish when the error code /// is MEGAErrorTypeApiOk: /// /// - [MEGARequest recentActionsBuckets] - Returns an array of buckets recently added/modified nodes /// /// The recommended values for the following parameters are to consider /// interactions during the last 30 days and maximum 500 nodes. /// /// @param days Age of actions since added/modified nodes will be considered (in days) /// @param maxNodes Maximum amount of nodes to be considered /// @param excludeSensitives Set to true to filter out sensitive nodes (Nodes are considered /// @param delegate MEGARequestDelegate to track this request - (void)getRecentActionsAsyncSinceDays:(NSInteger)days maxNodes:(NSInteger)maxNodes excludeSensitives:(BOOL)excludeSensitives delegate:(id)delegate; /// Clear the account's recent actions history up to a given timestamp. /// /// This method clears the recent actions history on the account by setting a /// "recent clear" timestamp. All actions that occurred at or before the given /// timestamp are considered cleared. /// /// The associated request type with this request is MEGARequestTypeSetAttrUser /// /// Valid data in the MEGARequest object received on callbacks: /// /// - [MEGARequest paramType] - Returns the user attribute type MEGAUserAttributeRecentClearTimestamp /// /// - [MEGARequest number] - Returns the epoch time (in seconds) used as the recent /// actions history clear timestamp /// /// @param until Epoch time (in seconds). Recent actions up to this time will be cleared. /// @param delegate MEGARequestDelegate to track this request - (void)clearRecentActionHistoryUntil:(int64_t)until delegate:(id)delegate; /// Get a recent action bucket by its identifier. /// /// The identifier format is: /// dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives /// /// Valid data in the MEGARequest object received on callbacks: /// /// - [MEGARequest text] - Returns the bucket identifier /// /// The associated request type with this request is MEGARequestTypeGetRecentActionById /// /// Valid data in the MEGARequest object received in onRequestFinish when the error code /// is MEGAErrorTypeApiOk: /// /// - [MEGARequest recentActionsBuckets] - Returns an array with 1 bucket /// /// @param bucketId Bucket identifier returned by MEGARecentActionBucket's identifier /// @param delegate MEGARequestDelegate to track this request - (void)getRecentActionByBucketId:(NSString *)bucketId delegate:(id)delegate; /** * @brief Process a node tree using a MEGATreeProcessorDelegate implementation * @param node The parent node of the tree to explore * @param recursive YES if you want to recursively process the whole node tree. * @param delegate MEGATreeProcessorDelegate that will receive callbacks for every node in the tree * NO if you want to process the children of the node only * * @return YES if all nodes were processed. NO otherwise (the operation can be * cancelled by [MEGATreeProcessorDelegate processMEGANode:]) */ - (BOOL)processMEGANodeTree:(MEGANode *)node recursive:(BOOL)recursive delegate:(id)delegate; /** * @brief Returns a MEGANode that can be downloaded with any instance of MEGASdk * * This function only allows to authorize file nodes. * * You can use [MEGASdk startDownloadNode:localPath:] with the resulting node with any instance * of MEGASdk, even if it's logged into another account, a public folder, or not * logged in. * * If the first parameter is a public node or an already authorized node, this * function returns a copy of the node, because it can be already downloaded * with any MEGASdk instance. * * If the node in the first parameter belongs to the account or public folder * in which the current MEGASdk object is logged in, this funtion returns an * authorized node. * * If the first parameter is nil or a node that is not a public node, is not * already authorized and doesn't belong to the current MEGASdk, this function * returns nil. * * @param node MEGANode to authorize * @return Authorized node, or nil if the node can't be authorized or is not a file */ - (nullable MEGANode *)authorizeNode:(MEGANode *)node; #ifdef ENABLE_CHAT /** * @brief Returns a MegaNode that can be downloaded/copied with a chat-authorization * * During preview of chat-links, you need to call this method to authorize the MegaNode * from a node-attachment message, so the API allows to access to it. The parameter to * authorize the access can be retrieved from [MEGAChatRoom authorizationToken] when * the chatroom in in preview mode. * * You can use [MEGASdk startDownload] and/or [MEGASdk copyNode] with the resulting * node with any instance of MEGASdk, even if it's logged into another account, * a public folder, or not logged in. * * @param node MEGANode to authorize * @param cauth Authorization token (public handle of the chatroom in B64url encoding) * @return Authorized node, or nil if the node can't be authorized */ - (nullable MEGANode *)authorizeChatNode:(MEGANode *)node cauth:(NSString *)cauth; #endif /** * @brief Get the size of a node tree. * * If the MEGANode is a file, this function returns the size of the file. * If it's a folder, this fuction returns the sum of the sizes of all nodes * in the node tree. * * @param node Parent node. * @return Size of the node tree. */ - (NSNumber *)sizeForNode:(MEGANode *)node; /** * @brief Make a name suitable for a file name in the local filesystem * * This function escapes (%xx) forbidden characters in the local filesystem if needed. * You can revert this operation using [MEGASdk unescapeFsIncompatible:] * * The input string must be UTF8 encoded. The returned value will be UTF8 too. * * @param name Name to convert (UTF8) * @param destinationPath Destination file path * @return Converted name (UTF8) */ - (nullable NSString *)escapeFsIncompatible:(NSString *)name destinationPath:(nullable NSString *)destinationPath; /** * @brief Unescape a file name escaped with [MEGASdk escapeFsIncompatible:] * * The input string must be UTF8 encoded. The returned value will be UTF8 too. * * @param localName Escaped name to convert (UTF8) * @param destinationPath Destination file path * @return Converted name (UTF8) */ - (nullable NSString *)unescapeFsIncompatible:(NSString *)localName destinationPath:(NSString *)destinationPath; /** * @brief Change the API URL * * This function allows to change the API URL. * It's only useful for testing or debugging purposes. * * @param apiURL New API URL * @param disablepkp YES to disable public key pinning for this URL */ - (void)changeApiUrl:(NSString *)apiURL disablepkp:(BOOL)disablepkp; /** * @brief Set the language code used by the app * @param languageCode Language code used by the app * * @return YES if the language code is known for the SDK, otherwise NO */ -(BOOL)setLanguageCode:(NSString *)languageCode; /** * @brief Set the preferred language of the user * * Valid data in the MEGARequest object received in onRequestFinish: * - [MEGARequest text] - Return the language code * * If the language code is unknown for the SDK, the error code will be MEGAErrorTypeApiENoent * * This attribute is automatically created by the server. Apps only need * to set the new value when the user changes the language. * * @param languageCode code to be set * @param delegate MEGARequestDelegate to track this request */ - (void)setLanguangePreferenceCode:(NSString *)languageCode delegate:(id)delegate; /** * @brief Set the preferred language of the user * * Valid data in the MEGARequest object received in onRequestFinish: * - [MEGARequest text] - Return the language code * * If the language code is unknown for the SDK, the error code will be MEGAErrorTypeApiENoent * * This attribute is automatically created by the server. Apps only need * to set the new value when the user changes the language. * * @param languageCode code to be set */ - (void)setLanguangePreferenceCode:(NSString *)languageCode; /** * @brief Get the preferred language of the user * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Return the language code * * @param delegate MEGARequestDelegate to track this request */ - (void)getLanguagePreferenceWithDelegate:(id)delegate; /** * @brief Get the preferred language of the user * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Return the language code * */ - (void)getLanguagePreference; /** * @brief Enable or disable file versioning * * The associated request type with this request is MEGARequestTypeSetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeDisableVersions * * Valid data in the MEGARequest object received in onRequestFinish: * - [MEGARequest text] - "1" for disable, "0" for enable * * @param disable YES to disable file versioning. NO to enable it * @param delegate MEGARequestDelegate to track this request */ - (void)setFileVersionsOption:(BOOL)disable delegate:(id)delegate; /** * @brief Enable or disable file versioning * * The associated request type with this request is MEGARequestTypeSetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeDisableVersions * * Valid data in the MEGARequest object received in onRequestFinish: * - [MEGARequest text] - "1" for disable, "0" for enable * * @param disable YES to disable file versioning. NO to enable it */ - (void)setFileVersionsOption:(BOOL)disable; /** * @brief Check if file versioning is enabled or disabled * * If the option has never been set, the error code will be MEGAErrorTypeApiENoent. * * The associated request type with this request is MEGARequestTypeGetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeDisableVersions * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - "1" for disable, "0" for enable * - [MEGARequest flag] - YES if disabled, NO if enabled * * @param delegate MEGARequestDelegate to track this request */ - (void)getFileVersionsOptionWithDelegate:(id)delegate; /** * @brief Check if file versioning is enabled or disabled * * If the option has never been set, the error code will be MEGAErrorTypeApiENoent. * * The associated request type with this request is MEGARequestTypeGetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeDisableVersions * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - "1" for disable, "0" for enable * - [MEGARequest flag] - YES if disabled, NO if enabled */ - (void)getFileVersionsOption; /** * @brief Enable or disable the automatic approval of incoming contact requests using a contact link * * The associated request type with this request is MEGARequestTypeSetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeContactLinkVerification * * Valid data in the MEGARequest object received in onRequestFinish: * - [MEGARequest text] - "0" for disable, "1" for enable * * @param enable YES to enable the automatic approval of incoming contact requests using a contact link * @param delegate MEGARequestDelegate to track this request */ - (void)setContactLinksOption:(BOOL)enable delegate:(id)delegate; /** * @brief Check if the automatic approval of incoming contact requests using contact links is enabled or disabled * * If the option has never been set, the error code will be MEGAErrorTypeApiENoent. * * The associated request type with this request is MEGARequestTypeGetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeContactLinkVerification * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - "0" for disable, "1" for enable * - [MEGARequest flag] - NO if disabled, YES if enabled * * @param delegate MEGARequestDelegate to track this request */ - (void)getContactLinksOptionWithDelegate:(id)delegate; /** * @brief Check if the automatic approval of incoming contact requests using contact links is enabled or disabled * * If the option has never been set, the error code will be MEGAErrorTypeApiENoent. * * The associated request type with this request is MEGARequestTypeGetAttrUser * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeContactLinkVerification * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - "0" for disable, "1" for enable * - [MEGARequest flag] - NO if disabled, YES if enabled */ - (void)getContactLinksOption; /** * @brief Keep retrying when public key pinning fails * * By default, when the check of the MEGA public key fails, it causes an automatic * logout. Pass NO to this function to disable that automatic logout and * keep the SDK retrying the request. * * Even if the automatic logout is disabled, a request of the type MEGARequestTypeLogout * will be automatically created and callbacks (onRequestStart, onRequestFinish) will * be sent. However, logout won't be really executed and in onRequestFinish the error code * for the request will be MEGAErrorTypeApiEIncomplete * * @param enable YES to keep retrying failed requests due to a fail checking the MEGA public key * or NO to perform an automatic logout in that case */ - (void)retrySSLErrors:(BOOL)enable; /** * @brief Enable / disable the public key pinning * * Public key pinning is enabled by default for all sensible communications. * It is strongly discouraged to disable this feature. * * @param enable YES to keep public key pinning enabled, NO to disable it */ - (void)setPublicKeyPinning:(BOOL)enable; /** * @brief Create a thumbnail for an image * @param imagePath Image path * @param destinationPath Destination path for the thumbnail (including the file name) * @return YES if the thumbnail was successfully created, otherwise NO. */ - (BOOL)createThumbnail:(NSString *)imagePath destinatioPath:(NSString *)destinationPath; /** * @brief Create a preview for an image * @param imagePath Image path * @param destinationPath Destination path for the thumbnail (including the file name) * @return YES if the preview was successfully created, otherwise NO. */ - (BOOL)createPreview:(NSString *)imagePath destinatioPath:(NSString *)destinationPath; /** * @brief Create an avatar for an image * @param imagePath Image path * @param destinationPath Destination path for the avatar (including the file name) * @return YES if the avatar was successfully created, otherwise NO. */ - (BOOL)createAvatar:(NSString *)imagePath destinationPath:(NSString *)destinationPath; #ifdef HAVE_LIBUV #pragma mark - HTTP Proxy Server /** * @brief Start an HTTP proxy server in specified port * * If this function returns YES, that means that the server is * ready to accept connections. The initialization is synchronous. * * The server will serve files using this URL format: * http://[::1]// * * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MEGANode using [MEGASdk httpServerGetLocalLink] * * If the node handle belongs to a folder node, a web with the list of files * inside the folder is returned. * * It's important to know that the HTTP proxy server has several configuration options * that can restrict the nodes that will be served and the connections that will be accepted. * * These are the default options: * - The restricted mode of the server is set to HTTPServerAllowCreatedLocalLinks * (see [MEGASdk httServerSetRestrictedMode]) * * - Folder nodes are NOT allowed to be served (see [MEGASdk httpServerEnableFolderServer]) * - File nodes are allowed to be served (see [MEGASdk httpServerEnableFileServer]) * - Subtitles support is disabled (see [MEGASdk httpServerEnableSubtitlesSupport]) * * The HTTP server will only stream a node if it's allowed by all configuration options. * * @param localOnly YES to listen on ::1 only, NO to listen on all network interfaces * @param port Port in which the server must accept connections * @return YES is the server is ready, NO if the initialization failed */ - (BOOL)httpServerStart:(BOOL)localOnly port:(NSInteger)port; /** * @brief Stop the HTTP proxy server * * When this function returns, the server is already shutdown. * If the HTTP proxy server isn't running, this functions does nothing */ - (void)httpServerStop; /** * @brief Check if the HTTP proxy server is running * @return 0 if the server is not running. Otherwise the port in which it's listening to */ - (NSInteger)httpServerIsRunning; /** * @brief Check if the HTTP proxy server is listening on all network interfaces * @return YES if the HTTP proxy server is listening on 127.0.0.1 only, or it's not started. * If it's started and listening on all network interfaces, this function returns NO */ - (BOOL)httpServerIsLocalOnly; /** * @brief Allow/forbid to serve files * * By default, files are served (when the server is running) * * Even if files are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerSetRestrictedMode]) are still applied. * * @param enable YES to allow to server files, NO to forbid it */ - (void)httpServerEnableFileServer:(BOOL)enable; /** * @brief Check if it's allowed to serve files * * This function can return YES even if the HTTP proxy server is not running * * Even if files are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerSetRestrictedMode]) are still applied. * * @return YES if it's allowed to serve files, otherwise NO */ - (BOOL)httpServerIsFileServerEnabled; /** * @brief Allow/forbid to serve folders * * By default, folders are NOT served * * Even if folders are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerSetRestrictedMode]) are still applied. * * @param enable YES to allow to server folders, NO to forbid it */ - (void)httpServerEnableFolderServer:(BOOL)enable; /** * @brief Check if it's allowed to serve folders * * This function can return YES even if the HTTP proxy server is not running * * Even if folders are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerSetRestrictedMode]) are still applied. * * @return YES if it's allowed to serve folders, otherwise NO */ - (BOOL)httpServerIsFolderServerEnabled; /** * @brief Enable/disable the restricted mode of the HTTP server * * This function allows to restrict the nodes that are allowed to be served. * For not allowed links, the server will return "407 Forbidden". * * Possible values are: * - HTTPServerDenyAll = -1 * All nodes are forbidden * * - HTTPServerAllowAll = 0 * All nodes are allowed to be served * * - HTTPServerAllowCreatedLocalLinks = 1 (default) * Only links created with [MEGASdk httpServerGetLocalLink] are allowed to be served * * - HTTPServerAllowLastLocalLink = 2 * Only the last link created with [MEGASdk httpServerGetLocalLink] is allowed to be served * * If a different value from the list above is passed to this function, it won't have any effect and the previous * state of this option will be preserved. * * The default value of this property is HTTPServerAllowCreatedLocalLinks * * The state of this option is preserved even if the HTTP server is restarted, but the * the HTTP proxy server only remembers the generated links since the last call to * [MEGASdk httpServerStart] * * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerEnableFileServer], * [MEGASdk httpServerEnableFolderServer]) are still applied. * * @param mode Required state for the restricted mode of the HTTP proxy server */ - (void)httpServerSetRestrictedMode:(NSInteger)mode; /** * @brief Check if the HTTP proxy server is working in restricted mode * * Possible return values are: * - HTTPServerDenyAll = -1 * All nodes are forbidden * * - HTTPServerAllowAll = 0 * All nodes are allowed to be served * * - HTTPServerAllowCreatedLocalLinks = 1 (default) * Only links created with [MEGASdk httpServerGetLocalLink] are allowed to be served * * - HTTPServerAllowLastLocalLink = 2 * Only the last link created with [MEGASdk httpServerGetLocalLink] is allowed to be served * * The default value of this property is HTTPServerAllowCreatedLocalLinks * * See [MEGASdk httpServerEnableRestrictedMode] and [MEGASdk httpServerStart] * * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options ([MEGASdk httpServerEnableFileServer], * [MEGASdk httpServerEnableFolderServer]) are still applied. * * @return State of the restricted mode of the HTTP proxy server */ - (NSInteger)httpServerGetRestrictedMode; /** * @brief Enable/disable the support for subtitles * * Subtitles support allows to stream some special links that otherwise wouldn't be valid. * For example, let's suppose that the server is streaming this video: * http://120.0.0.1:4443//MyHolidays.avi * * Some media players scan HTTP servers looking for subtitle files and request links like these ones: * http://120.0.0.1:4443//MyHolidays.txt * http://120.0.0.1:4443//MyHolidays.srt * * Even if a file with that name is in the same folder of the MEGA account, the node wouldn't be served because * the node handle wouldn't match. * * When this feature is enabled, the HTTP proxy server will check if there are files with that name * in the same folder as the node corresponding to the handle in the link. * * If a matching file is found, the name is exactly the same as the the node with the specified handle * (except the extension), the node with that handle is allowed to be streamed and this feature is enabled * the HTTP proxy server will serve that file. * * This feature is disabled by default. * * @param enable YES to enable subtitles support, NO to disable it */ - (void)httpServerEnableSubtitlesSupport:(BOOL)enable; /** * @brief Check if the support for subtitles is enabled * * See [MEGASdk httpServerEnableSubtitlesSupport]. * * This feature is disabled by default. * * @return YES of the support for subtibles is enables, otherwise NO */ - (BOOL)httpServerIsSubtitlesSupportEnabled; /** * @brief Add a delegate to receive information about the HTTP proxy server * * This is the valid data that will be provided on callbacks: * - [MEGATransfer type] - It will be MEGATransferTypeLocalHTTPDownload * - [MEGATransfer path] - URL requested to the HTTP proxy server * - [MEGATransfer fileName] - Name of the requested file (if any, otherwise nil) * - [MEGATransfer nodeHandle] - Handle of the requested file (if any, otherwise nil) * - [MEGATransfer totalBytes] - Total bytes of the response (response headers + file, if required) * - [MEGATransfer startPos] - Start position (for range requests only, otherwise -1) * - [MEGATransfer endPos] - End position (for range requests only, otherwise -1) * * On the onTransferFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEIncomplete - If the whole response wasn't sent * (it's normal to get this error code sometimes because media players close connections when they have * the data that they need) * * - MEGAErrorTypeApiERead - If the connection with MEGA storage servers failed * - MEGAErrorTypeApiEAgain - If the download speed is too slow for streaming * - A number > 0 means an HTTP error code returned to the client * * @param delegate Delegate to receive information about the HTTP proxy server */ - (void)httpServerAddDelegate:(id)delegate; /** * @brief Stop the reception of callbacks related to the HTTP proxy server on this delegate * @param delegate Delegate that won't continue receiving information */ - (void)httpServerRemoveDelegate:(id)delegate; /** * @brief Returns a URL to a node in the local HTTP proxy server * * The HTTP proxy server must be running before using this function, otherwise * it will return nil. * * @param node Node to generate the local HTTP link * @return URL to the node in the local HTTP proxy server, otherwise nil */ - (nullable NSURL *)httpServerGetLocalLink:(MEGANode *)node; /** * @brief Set the maximum buffer size for the internal buffer * * The HTTP proxy server has an internal buffer to store the data received from MEGA * while it's being sent to clients. When the buffer is full, the connection with * the MEGA storage server is closed, when the buffer has few data, the connection * with the MEGA storage server is started again. * * Even with very fast connections, due to the possible latency starting new connections, * if this buffer is small the streaming can have problems due to the overhead caused by * the excessive number of POST requests. * * It's recommended to set this buffer at least to 1MB * * For connections that request less data than the buffer size, the HTTP proxy server * will only allocate the required memory to complete the request to minimize the * memory usage. * * The new value will be taken into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param bufferSize Maximum buffer size (in bytes) or a number <= 0 to use the * internal default value */ - (void)httpServerSetMaxBufferSize:(NSInteger)bufferSize; /** * @brief Get the maximum size of the internal buffer size * * See [MEGASdk httpServerSetMaxBufferSize] * * @return Maximum size of the internal buffer size (in bytes) */ - (NSInteger)httpServerGetMaxBufferSize; /** * @brief Set the maximum size of packets sent to clients * * For each connection, the HTTP proxy server only sends one write to the underlying * socket at once. This parameter allows to set the size of that write. * * A small value could cause a lot of writes and would lower the performance. * * A big value could send too much data to the output buffer of the socket. That could * keep the internal buffer full of data that hasn't been sent to the client yet, * preventing the retrieval of additional data from the MEGA storage server. In that * circumstances, the client could read a lot of data at once and the HTTP server * could not have enough time to get more data fast enough. * * It's recommended to set this value to at least 8192 and no more than the 25% of * the maximum buffer size ([MEGASdk httpServerSetMaxBufferSize]). * * The new value will be takein into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param outputSize Maximun size of data packets sent to clients (in bytes) or * a number <= 0 to use the internal default value */ - (void)httpServerSetMaxOutputSize:(NSInteger)outputSize; /** * @brief Get the maximum size of the packets sent to clients * * See [MEGASdk httpServerSetMaxOutputSize] * * @return Maximum size of the packets sent to clients (in bytes) */ - (NSInteger)httpServerGetMaxOutputSize; #endif /** * @brief Get the MIME type associated with the extension * * @param extension File extension (with or without a leading dot) * @return MIME type associated with the extension */ + (nullable NSString *)mimeTypeByExtension:(NSString *)extension; #ifdef ENABLE_CHAT /** * @brief Register a device token for iOS push notifications * * This function attach a device token to the current session, which is intended to get push notifications. * * The associated request type with this request is MEGARequestTypeRegisterPushNotification * Valid data in the MEGARequest object received on delegate: * - [MEGARequest text] - Returns the device token provided. * * @param deviceToken NSString representing the device token to be registered. * @param delegate MEGARequestDelegate to track this request */ - (void)registeriOSdeviceToken:(NSString *)deviceToken delegate:(id)delegate; /** * @brief Register a device token for iOS push notifications * * This function attach a device token to the current session, which is intended to get push notifications. * * The associated request type with this request is MEGARequestTypeRegisterPushNotification * Valid data in the MEGARequest object received on delegate: * - [MEGARequest text] - Returns the device token provided. * * @param deviceToken NSString representing the device token to be registered. */ - (void)registeriOSdeviceToken:(NSString *)deviceToken; /** * @brief Register a device token for iOS VoIP push notifications * * This function attach a device token to the current session, which is intended to get push notifications. * * The associated request type with this request is MEGARequestTypeRegisterPushNotification * Valid data in the MEGARequest object received on delegate: * - [MEGARequest text] - Returns the device token provided. * * @param deviceToken NSString representing the device token to be registered. * @param delegate MEGARequestDelegate to track this request */ - (void)registeriOSVoIPdeviceToken:(NSString *)deviceToken delegate:(id)delegate; /** * @brief Register a device token for iOS VoIP push notifications * * This function attach a device token to the current session, which is intended to get push notifications. * * The associated request type with this request is MEGARequestTypeRegisterPushNotification * Valid data in the MEGARequest object received on delegate: * - [MEGARequest text] - Returns the device token provided. * * @param deviceToken NSString representing the device token to be registered. */ - (void)registeriOSVoIPdeviceToken:(NSString *)deviceToken; #endif /** * @brief Get the MEGA Achievements of the account logged in * * The associated request type with this request is MEGARequestTypeGetAchievements * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Always NO * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaAchievementsDetails] - Details of the MEGA Achievements of this account * * @param delegate MEGARequestDelegate to track this request */ - (void)getAccountAchievementsWithDelegate:(id)delegate; /** * @brief Get the MEGA Achievements of the account logged in * * The associated request type with this request is MEGARequestTypeGetAchievements * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Always NO * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaAchievementsDetails] - Details of the MEGA Achievements of this account * */ - (void)getAccountAchievements; /** * @brief Get the list of existing MEGA Achievements * * Similar to [MEGASdk getAccountAchievements], this method returns only the base storage and * the details for the different achievement classes, related to the * account that is logged in. * This function can be used to give an indication of what is available for advertising * for unregistered users, despite it can be used with a logged in account with no difference. * * @note: if the IP address is not achievement enabled (it belongs to a country where MEGA * Achievements are not enabled), the request will fail with MEGAErrorTypeApiEAccess. * * The associated request type with this request is MEGARequestTypeGetAchievements * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Always YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequestm megaAchievementsDetails] - Details of the list of existing MEGA Achievements * * @param delegate MEGARequestDelegate to track this request */ - (void)getMegaAchievementsWithDelegate:(id)delegate; /** * @brief Get the list of existing MEGA Achievements * * Similar to [MEGASdk getAccountAchievements], this method returns only the base storage and * the details for the different achievement classes, related to the * account that is logged in. * This function can be used to give an indication of what is available for advertising * for unregistered users, despite it can be used with a logged in account with no difference. * * @note: if the IP address is not achievement enabled (it belongs to a country where MEGA * Achievements are not enabled), the request will fail with MEGAErrorTypeApiEAccess. * * If the IP address is not achievement enabled, the request will fail with MEGAErrorTypeApiEAccess. * * The associated request type with this request is MEGARequestTypeGetAchievements * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest flag] - Always YES * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequestm megaAchievementsDetails] - Details of the list of existing MEGA Achievements * */ - (void)getMegaAchievements; /** * @brief Catch up with API for pending actionpackets * * The associated request type with this request is MEGARequestTypeCatchup * * When onRequestFinish is called with MEGAErrorTypeApiOk, the SDK is guaranteed to be * up to date (as for the time this function is called). * * @param delegate MEGARequestDelegate to track this request */ - (void)catchupWithDelegate:(id)delegate; /** * @brief Retrieve basic information about a folder link * * This function retrieves basic information from a folder link, like the number of files / folders * and the name of the folder. For folder links containing a lot of files/folders, * this function is more efficient than a fetchnodes. * * Valid data in the MegaRequest object received on all callbacks: * - [MEGARequest link] - Returns the public link to the folder * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaFolderInfo] - Returns information about the contents of the folder * - [MEGARequest nodeHandle] - Returns the public handle of the folder * - [MEGARequest parentHandle] - Returns the handle of the owner of the folder * - [MEGARequest text] - Returns the name of the folder. * If there's no name, it returns the special status string "CRYPTO_ERROR". * If the length of the name is zero, it returns the special status string "BLANK". * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiEArgs - If the link is not a valid folder link * - MEGAErrorTypeApiEKey - If the public link does not contain the key or it is invalid * * @param folderLink Public link to a folder in MEGA * @param delegate MEGARequestDelegate to track this request */ - (void)getPublicLinkInformationWithFolderLink:(NSString *)folderLink delegate:(id)delegate; /** * @brief Retrieve basic information about a folder link * * This function retrieves basic information from a folder link, like the number of files / folders * and the name of the folder. For folder links containing a lot of files/folders, * this function is more efficient than a fetchnodes. * * Valid data in the MegaRequest object received on all callbacks: * - [MEGARequest link] - Returns the public link to the folder * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaFolderInfo] - Returns information about the contents of the folder * - [MEGARequest nodeHandle] - Returns the public handle of the folder * - [MEGARequest parentHandle] - Returns the handle of the owner of the folder * - [MEGARequest text] - Returns the name of the folder. * If there's no name, it returns the special status string "CRYPTO_ERROR". * If the length of the name is zero, it returns the special status string "BLANK". * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiEArgs - If the link is not a valid folder link * - MEGAErrorTypeApiEKey - If the public link does not contain the key or it is invalid * * @param folderLink Public link to a folder in MEGA */ - (void)getPublicLinkInformationWithFolderLink:(NSString *)folderLink; #pragma mark - SMS /** * @brief Check if the opt-in or account ublocking SMS is allowed. * * The result indicated whether the sendSMSVerificationCode() function can be used. * * @return SMSState enum to indicate the SMS state for the current account. */ - (SMSState)smsAllowedState; /** * @brief Get the verified phone number for the account logged in * * Returns the phone number previously confirmed with sendSMSVerificationCodeToPhoneNumber:delegate * and checkSMSVerificationCode:delegate metnhods in MEGASdk. * * @return nil if there is no verified number, otherwise a string containing that phone number. */ - (nullable NSString *)smsVerifiedPhoneNumber; /** * @brief Requests the currently available country calling codes * * The response value is stored as a dictionary of mapping from two-letter country code * to a list of calling codes. For instance: * { * "AD": ["376"], * "AE": ["971", "13"], * } * * Valid data in the delegate object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk * * @param delegate MEGARequestDelegate to track this request */ - (void)getCountryCallingCodesWithDelegate:(id)delegate; /** * @brief Send a verification code txt to the supplied phone number * * Sends a 6 digit code to the user's phone. The phone number is supplied in this function call. * The code is sent by SMS to the user. Once the user receives it, they can type it into the app * and the call checkSMSVerificationCode:delegate: in MEGASdk to validate the user did * receive the verification code, so that really is their phone number. * * The frequency with which this call can be used is very limited (the API allows at most * two SMS mssages sent for phone number per 24 hour period), so it's important to get the * number right on the first try. The result will be MEGAErrorTypeApiETempUnavail if it has * been tried too frequently. * * Make sure to test the result of smsAllowedState in MEGASdk before calling this function. * * Valid data in the MegaRequest object received on callbacks: * - text in MEGARequest - the phoneNumber as supplied to this function * * When the operation completes, MEGAErrorType can be: * - MEGAErrorTypeApiETempUnavail if a limit is reached. * - MEGAErrorTypeApiEAccess if your account is already verified with an SMS number * - MEGAErrorTypeApiEExist if the number is already verified for some other account. * - MEGAErrorTypeApiEArgs if the phone number is badly formatted or invalid. * - MEGAErrorTypeApiOk is returned upon success. * * @param phoneNumber The phone number to txt the code to, supplied by the user. * @param delegate A MEGARequestDelegate callback to track this request */ - (void)sendSMSVerificationCodeToPhoneNumber:(NSString *)phoneNumber delegate:(id)delegate; /** * @brief Check a verification code that the user should have received via txt * * This function validates that the user received the verification code sent by sendSMSVerificationCodeToPhoneNumber:delegate in MEGASdk. * * Valid data in the MEGARequest object received on callbacks: * - text in MEGARequest - the verificationCode as supplied to this function * * When the operation completes, MEGAErrorType can be: * - MEGAErrorTypeApiEAccess if you have reached the verification limits. * - MEGAErrorTypeApiEFailed if the verification code does not match. * - MEGAErrorTypeApiEExpired if the phone number was verified on a different account. * - MEGAErrorTypeApiOk is returned upon success. * * @param verificationCode A string supplied by the user, that they should have received via txt. * @param delegate A MEGARequestDelegate callback to track this request */ - (void)checkSMSVerificationCode:(NSString *)verificationCode delegate:(id)delegate; /** * @brief Reset the verified phone number for the account logged in. * * The associated request type with this request is MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER * If there's no verified phone number associated for the account logged in, the error code * provided in onRequestFinish is MegaError::API_ENOENT. * * @param delegate MEGARequestDelegate to track this request */ - (void)resetSmsVerifiedPhoneNumberWithDelegate:(id)delegate; /** * @brief Reset the verified phone number for the account logged in. * * The associated request type with this request is MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER * If there's no verified phone number associated for the account logged in, the error code * provided in onRequestFinish is MegaError::API_ENOENT. * */ - (void)resetSmsVerifiedPhoneNumber; #pragma mark - Push Notification Settings /** * @brief Get push notification settings * * The associated request type with this request is MEGARequestTypeSetAttrUser * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePushSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaPushNotificationSettings] Returns settings for push notifications * * @param delegate MEGARequestDelegate to track this request */ - (void)getPushNotificationSettingsWithDelegate:(id)delegate; /** * @brief Get push notification settings * * The associated request type with this request is MEGARequestTypeSetAttrUser * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePushSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaPushNotificationSettings] Returns settings for push notifications */ - (void)getPushNotificationSettings; /** * @brief Set push notification settings. * * The associated request type with this request is MEGARequestTypeSetAttrUser * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePushSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaPushNotificationSettings] Returns settings for push notifications * * @param pushNotificationSettings Push notification settings of the user. (An instance of MEGAPushNotificationSettings). * @param delegate MEGARequestDelegate to track this request */ - (void)setPushNotificationSettings:(MEGAPushNotificationSettings *)pushNotificationSettings delegate:(id)delegate; /** * @brief Set push notification settings. * * The associated request type with this request is MEGARequestTypeSetAttrUser * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePushSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaPushNotificationSettings] Returns settings for push notifications * * @param pushNotificationSettings Push notification settings of the user. (An instance of MEGAPushNotificationSettings). */ - (void)setPushNotificationSettings:(MEGAPushNotificationSettings *)pushNotificationSettings; #pragma mark - Debug /** * @brief Set the active log level * * This function sets the log level of the logging system. If you set a log delegate using * [MEGASdk setLoggerObject:], you will receive logs with the same or a lower level than * the one passed to this function. * * @param logLevel Active log level. * * These are the valid values for this parameter: * - MEGALogLevelFatal = 0 * - MEGALogLevelError = 1 * - MEGALogLevelWarning = 2 * - MEGALogLevelInfo = 3 * - MEGALogLevelDebug = 4 * - MEGALogLevelMax = 5 */ + (void)setLogLevel:(MEGALogLevel)logLevel; /** * @brief Enable log to console * * By default, log to console is NO. * * @param enable YES to show messages in console, NO to skip them. */ + (void)setLogToConsole:(BOOL)enable; /** * @brief Send a log to the logging system * * This log will be received by the active logger object ([MEGASdk setLogObject]) if * the log level is the same or lower than the active log level ([MEGASdk setLogLevel]) * * The third and the fouth parameget are optional. You may want to use __FILE__ and __LINE__ * to complete them. * * @param logLevel Log level for this message * @param message Message for the logging system * @param filename Origin of the log message * @param line Line of code where this message was generated */ + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message filename:(NSString *)filename line:(NSInteger)line; /** * @brief Send a log to the logging system * * This log will be received by the active logger object ([MEGASdk setLogObject]) if * the log level is the same or lower than the active log level ([MEGASdk setLogLevel]) * * The third and the fouth parameget are optional. You may want to use __FILE__ and __LINE__ * to complete them. * * @param logLevel Log level for this message * @param message Message for the logging system * @param filename Origin of the log message */ + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message filename:(NSString *)filename; /** * @brief Send a log to the logging system * * This log will be received by the active logger object ([MEGASdk setLogObject]) if * the log level is the same or lower than the active log level ([MEGASdk setLogLevel]) * * The third and the fouth parameget are optional. You may want to use __FILE__ and __LINE__ * to complete them. * * @param logLevel Log level for this message * @param message Message for the logging system */ + (void)logWithLevel:(MEGALogLevel)logLevel message:(NSString *)message; /** * @brief Send events to the stats server * * The associated request type with this request is MEGARequestTypeSendEvent * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the event type * - [MEGARequest text] - Returns the event message * - [MEGARequest flag] - Returns the addJourneyId flag * - [MEGARequest sessionKey] - Returns the ViewID * * @param eventType Event type * @param message Event message * @param addJourneyId True if JourneyID should be included. Otherwise, false. * @param viewId ViewID value (C-string null-terminated) to be sent with the event. * This value should have been generated with [MEGASdk generateViewId] method. * @param delegate Delegate to track this request * * @warning This function is for internal usage of MEGA apps for debug purposes. This info * is sent to MEGA servers. * * @note Event types are restricted to the following ranges: * - MEGAcmd: [98900, 99000) * - MEGAchat: [99000, 99150) * - Android: [99200, 99300) * - iOS: [99300, 99400) * - MEGA SDK: [99400, 99500) * - MEGAsync: [99500, 99600) * - Webclient: [99600, 99800] */ - (void)sendEvent:(NSInteger)eventType message:(NSString *)message addJourneyId:(BOOL)addJourneyId viewId:(nullable NSString *)viewId delegate:(id)delegate; /** * @brief Send events to the stats server * * The associated request type with this request is MEGARequestTypeSendEvent * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the event type * - [MEGARequest text] - Returns the event message * - [MEGARequest flag] - Returns the addJourneyId flag * - [MEGARequest sessionKey] - Returns the ViewID * * @param eventType Event type * @param message Event message * @param addJourneyId True if JourneyID should be included. Otherwise, false. * @param viewId ViewID value (C-string null-terminated) to be sent with the event. * This value should have been generated with [MEGASdk generateViewId] method. * * @warning This function is for internal usage of MEGA apps for debug purposes. This info * is sent to MEGA servers. * * @note Event types are restricted to the following ranges: * - MEGAcmd: [98900, 99000) * - MEGAchat: [99000, 99150) * - Android: [99200, 99300) * - iOS: [99300, 99400) * - MEGA SDK: [99400, 99500) * - MEGAsync: [99500, 99600) * - Webclient: [99600, 99800] */ - (void)sendEvent:(NSInteger)eventType message:(NSString *)message addJourneyId:(BOOL)addJourneyId viewId:(nullable NSString *)viewId; /** * Generate an unique ViewID * * The caller gets the ownership of the object. * * A ViewID consists of a random generated id, encoded in hexadecimal as 16 characters of a null-terminated string. */ - (nullable NSString *)generateViewId; /** * @brief Create a new ticket for support with attached description * * The associated request type with this request is MEGARequestTypeSupportTicket * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the type of the ticket * - [MEGARequest text] - Returns the description of the issue * * @param message Description of the issue for support * @param type Ticket type. These are the available types: * 0 for General Enquiry * 1 for Technical Issue * 2 for Payment Issue * 3 for Forgotten Password * 4 for Transfer Issue * 5 for Contact/Sharing Issue * 6 for MEGAsync Issue * 7 for Missing/Invisible Data * 8 for help-centre clarifications * 9 for iOS issue * @param delegate MEGARequestDelegate to track this request */ - (void)createSupportTicketWithMessage:(NSString *)message type:(NSInteger)type delegate:(id)delegate; /** * @brief Create a new ticket for support with attached description * * The associated request type with this request is MEGARequestTypeSupportTicket * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the type of the ticket * - [MEGARequest text] - Returns the description of the issue * * @param message Description of the issue for support * @param type Ticket type. These are the available types: * 0 for General Enquiry * 1 for Technical Issue * 2 for Payment Issue * 3 for Forgotten Password * 4 for Transfer Issue * 5 for Contact/Sharing Issue * 6 for MEGAsync Issue * 7 for Missing/Invisible Data * 8 for help-centre clarifications * 9 for iOS issue */ - (void)createSupportTicketWithMessage:(NSString *)message type:(NSInteger)type; #pragma mark - Banner /** * @brief Requests a list of all Smart Banners available for current user. * * The response value is stored as a MegaBannerList. * * The associated request type with this request is MEGARequestTypeGetBanners * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - MEGABannerList: to get the list of banners * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiEAccess - If called with no user being logged in. * - MEGAErrorTypeApiEInternal - If the internally used user attribute exists but can't be decoded. * - MEGAErrorTypeApiENoent - If there are no banners to return to the user. * * @param delegate MEGARequestDelegate to track this request */ - (void)getBanners:(id)delegate; /** * @brief No longer show the Smart Banner with the specified id to the current user. * * The associated request type with this request is MEGARequestTypeDismissBanner */ - (void)dismissBanner:(NSInteger)bannerIdentifier delegate:(id)delegate; #pragma mark - Backup Heartbeat /** * @brief Registers a backup to display in Backup Centre * * Apps should register backups, like CameraUploads, in order to be listed in the * BackupCentre. The client should send heartbeats to indicate the progress of the * backup. * * @see [MEGASDK sendBackupHeartbeat] * * Possible types of backups: * BackUpTypeCameraUploads = 3 * * The associated request type with this request is MEGARequestTypeBackupPut * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest getNodeHandle] - Returns the target node of the backup * - [MEGARequest getName] - Returns the backup name of the remote location * - [MEGARequest getAccess] - Returns the backup state * - [MEGARequest getFile] - Returns the path of the local folder * - [MEGARequest getText] - Returns the extraData associated with the request * - [MEGARequest getTotalBytes] - Returns the backup type * - [MEGARequest getNumDetails] - Returns the backup substate * - [MEGARequest getFlag] - Returns YES * * @param type BackUpType requested for the service * @param node MEGA target node folder to hold the backups * @param path Local path of the folder * @param name Back up name of the backup * @param state BackUpState type state * @param delegate MEGARequestDelegate to track this request */ - (void)registerBackup:(BackUpType)type targetNode:(MEGANode *)node folderPath:(nullable NSString *)path name:(NSString *)name state:(BackUpState)state delegate:(id)delegate; /** * @brief Update the information about a registered backup for Backup Centre * * Possible types of backups: * BackUpTypeCameraUploads = 3 * * The associated request type with this request is MEGARequestTypeBackupPut * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest getParentHandle] - Returns the backupId * - [MEGARequest getTotalBytes] - Returns the backup type * - [MEGARequest getNodeHandle] - Returns the target node of the backup * - [MEGARequest getFile] - Returns the path of the local folder * - [MEGARequest getAccess] - Returns the backup state * - [MEGARequest getNumDetails] - Returns the backup substate * - [MEGARequest getText] - Returns the extraData associated with the request * * @param backupId backup id identifying the backup to be updated * @param type BackUpType requested for the service * @param node MEGA target node folder to hold the backups * @param path Local path of the folder * @param state BackUpState type backup state * @param subState BackUpState type backup sub-state * @param delegate MEGARequestDelegate to track this request */ - (void)updateBackup:(MEGAHandle)backupId backupType:(BackUpType)type targetNode:(nullable MEGANode *)node folderPath:(nullable NSString *)path backupName:(nullable NSString *)name state:(BackUpState)state subState:(BackUpSubState)subState delegate:(id)delegate; /** * @brief Fetch information about all registered backups for Backup Centre * The associated request type with this request is MEGARequestTypeBackupInfo * Valid data in the MEGARequest object received on callbacks: * - backupInfoList: to get the list of backups. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest backupInfoList] - Returns information about all registered backups * * @param delegate MEGARequestDelegate to track this request */ - (void)getBackupInfo:(id)delegate; /** * @brief Unregister a backup already registered for the Backup Centre * * This method allows to remove a backup from the list of backups displayed in the * Backup Centre. * * @see [MEGASdk registerBackup] * * The associated request type with this request is MEGARequestTypeBackupRemove * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest getParentHandle] - Returns the backupId * * @param backupId backup id identifying the backup to be removed * @param delegate MEGARequestDelegate to track this request */ - (void)unregisterBackup:(MEGAHandle)backupId delegate:(id)delegate; /** * @brief Send heartbeat associated with an existing backup * * The client should call this method regularly for every registered backup, in order to * inform about the status of the backup. * * The associated request type with this request is MEGARequestTypeBackupPutHeartbeat * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest getParentHandle] - Returns the backupId * - [MEGARequest getAccess] - Returns the backup state * - [MEGARequest getNumDetails] - Returns the backup substate * - [MEGARequest getParamType] - Returns the number of pending upload transfers * - [MEGARequest getTransferTag] - Returns the number of pending download transfers * - [MEGARequest getNumber] - Returns the last action timestamp * - [MEGARequest getNodeHandle] - Returns the last node handle to be synced * * @param backupId backup id identifying the backup * @param status BackupHeartbeatStatus type backup state * @param progress backup progress * @param pendingUploadCount Count of pending upload transfers * @param lastActionDate Last action date * @param lastBackupNode Last node to be synced * @param delegate MEGARequestDelegate to track this request */ - (void)sendBackupHeartbeat:(MEGAHandle)backupId status:(BackupHeartbeatStatus)status progress:(NSInteger)progress pendingUploadCount:(NSUInteger)pendingUploadCount lastActionDate:(nullable NSDate *)lastActionDate lastBackupNode:(nullable MEGANode *)lastBackupNode delegate:(id)delegate; /** * @brief Returns the device id stored as a Node attribute. * It will be an empty string for other nodes than device folders related to backups. * * @return The device id associated with the Node of a Backup folder. */ - (nullable NSString *)deviceId; /** * @brief Returns the name previously set for a device * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - paramType - Returns the attribute type MEGAUserAttributeDeviceNames * - text - Returns passed device id (or the value returned by deviceId() * if deviceId was initially passed as null). * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - name - Returns device name. * * @param deviceId The id of the device to get the name for. If null, the value returned * by deviceId() will be used instead. * @param delegate MEGARequestDelegate to track this request */ - (void)getDeviceName:(nullable NSString *)deviceId delegate:(id)delegate; /** * @brief Sets name for specific device * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - paramType - Returns the attribute type MEGAUserAttributeDeviceNames * - deviceId - Returns the device id. * - name - Returns device name. * * @param deviceId String with device id * @param name String with device name * @param delegate MEGARequestDelegate to track this request */ - (void)renameDevice:(nullable NSString *)deviceId newName:(NSString *)name delegate:(id)delegate; #pragma mark - Cookie Dialog /** * @brief Set a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCookieSettings * - [MEGARequest numDetails] - Return a bitmap with cookie settings * * @param settings A bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * @param delegate MEGARequestDelegate to track this request */ - (void)setCookieSettings:(NSInteger)settings delegate:(id)delegate; /** * @brief Set a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MEGARequestTypeSetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributeCookieSettings * - [MEGARequest numDetails] - Return a bitmap with cookie settings * * @param settings A bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty */ - (void)setCookieSettings:(NSInteger)settings; /** * @brief Get a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeCookieSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest numDetails] Return the bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEInternal - If the value for cookie settings bitmap was invalid * * @param delegate MEGARequestDelegate to track this request */ - (void)cookieSettingsWithDelegate:(id)delegate; /** * @brief Get a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MEGARequestTypeGetAttrUser * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest paramType] - Returns the value MEGAUserAttributeCookieSettings * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest numDetails] Return the bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEInternal - If the value for cookie settings bitmap was invalid */ - (void)cookieSettings; /** * @brief Check if the app can start showing the cookie banner * * This function will NOT return a valid value until the callback onEvent with * type EventMiscFlagsReady is received. You can also rely on the completion of * a fetchnodes to check this value, but only when it follows a login with user and password, * not when an existing session is resumed. * * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return YES if this feature is enabled. Otherwise, NO. */ - (BOOL)cookieBannerEnabled; #pragma mark - A/B Testing /** * @brief Get the value of an A/B Test flag * * Any value greater than 0 means the flag is active. * * @param flag Name or key of the value to be retrieved. * * @return An unsigned integer with the value of the flag. */ - (NSInteger)getABTestValue:(NSString*)flag; #pragma mark - Remote feature flags /** * @brief Get the value for the flag with the given name, * if present among either A/B Test or Feature flags. * @param flag Name or key of the value to be retrieved * @return A integer with the value of the flag, value above 0 means feature enabled */ - (NSInteger)remoteFeatureFlagValue:(NSString *)flag; #pragma mark - Ads /** * @brief Fetch ads * * The associated request type with this request is MEGARequestTypeFetchAds * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest number] A bitmap flag used to communicate with the API * - [MEGASDK megaStringListFor:] List of the adslot ids to fetch * - [MEGARequest nodeHandle] Public handle that the user is visiting * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaStringDictionary] map with relationship between ids and ius * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - AdsFlagDefault = 0x0 * - AdsFlagForceAds = 0x200 * - AdsFlagIgnoreMega = 0x400 * - AdsFlagIgnoreCountry = 0x800 * - AdsFlagIgnoreIP = 0x1000 * - AdsFlagIgnorePRO = 0x2000 * - AdsFlagIgnoreRollout = 0x400 * @param adUnits A list of the adslot ids to fetch; it cannot be null nor empty * @param publicHandle Provide the public handle that the user is visiting * @param delegate MEGARequestDelegate to track this request */ - (void)fetchAds:(AdsFlag)adFlags adUnits:(MEGAStringList *)adUnits publicHandle:(MEGAHandle)publicHandle delegate:(id)delegate; /** * @brief Check if ads should show or not * * The associated request type with this request is MEGARequestTypeQueryAds * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest number] A bitmap flag used to communicate with the API * - [MEGARequest nodeHandle] Public handle that the user is visiting * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest numDetails] Return if ads should be show or not * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - AdsFlagDefault = 0x0 * - AdsFlagForceAds = 0x200 * - AdsFlagIgnoreMega = 0x400 * - AdsFlagIgnoreCountry = 0x800 * - AdsFlagIgnoreIP = 0x1000 * - AdsFlagIgnorePRO = 0x2000 * - AdsFlagIgnoreRollout = 0x400 * @param publicHandle Provide the public handle that the user is visiting * @param delegate MEGARequestDelegate to track this request */ - (void)queryAds:(AdsFlag)adFlags publicHandle:(MEGAHandle)publicHandle delegate:(id)delegate; /// Enable or disable the request status monitor /// /// - Note: When it's enabled, the request status monitor generates events of type /// `EventReqStatProgress` with the per mille progress in /// the field [MEGAEvent number], or -1 if there isn't any operation in progress. /// /// - Parameters: /// - enable: YES to enable the request status monitor, or No to disable it - (void)enableRequestStatusMonitor:(BOOL)enable; /// Get the status of the request status monitor /// - Returns: YES when the request status monitor is enabled, or NO if it's disabled @property (readonly, nonatomic, getter=isRequestStatusMonitorEnabled) BOOL requestStatusMonitorEnabled; #pragma mark - VPN /** * @brief Gets a list with the available regions for MEGA VPN. * * The associated request type with this request is MEGARequestTypeGetVPNRegions. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaStringList] - Returns the list with the VPN regions. * * @param delegate MEGARequestDelegate to track this request. */ - (void)getVpnRegionsWithDelegate:(id)delegate; /** * @brief Gets the MEGA VPN credentials currently active for the user. * * Important consideration: * These credentials do NOT contain the User Private Key, which is required for VPN connection. * Credentials containing the User Private Key are generated by * [MEGASdk putVpnCredentialWithRegion] and cannot be retrieved afterwards. * * The associated request type with this request is MEGARequestTypeGetVPNCredentials. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaVpnCredentials] - Returns the MEGAVPNCredentials object. * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiENoent - The user has no credentials registered. * * @param delegate MEGARequestDelegate to track this request. */ - (void)getVpnCredentialsWithDelegate:(id)delegate; /** * @brief Adds new MEGA VPN credentials on an empty slot. * * A pair of private and public keys are generated for the user during this request. * The User Public Key value is intended for use with [MEGASdk checkVpnCredentialWithUserPubKey]. * The User Private Key value is included in the VPN credentials. * Once returned, neither of these keys can be retrieved, not even using [MEGASdk getVpnCredentialsWithDelegate]. * * The user must be a PRO user and have unoccupied VPN slots in order to add new VPN credentials. * * The associated request type with this request is MEGARequestTypePutVPNCredential. * * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest text] - Returns the VPN region used for the VPN credentials. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest number] - Returns the SlotID attached to the new VPN credentials. * - [MEGARequest password] - Returns the User Public Key used to register the new VPN credentials. * - [MEGARequest sessionKey] - Returns a string with the new VPN credentials. * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEArgs - Public Key does not have a correct format/length. * - MEGAErrorTypeApiEAccess - User is not PRO. * - User is not logged in. * - Public Key is already taken. * - MEGAErrorTypeApiETooMany - User has too many registered credentials. * * @param region The VPN region to be used on the new VPN credential. * @param delegate MEGARequestDelegate to track this request. */ - (void)putVpnCredentialWithRegion:(NSString *)region delegate:(id)delegate; /** * @brief Delete the current MEGA VPN credentials used on a slot. * * The associated request type with this request is MEGARequestTypeDeleteVPNCredential. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest number] - Returns the SlotID used as a parameter for credential removal. * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEArgs - SlotID is not valid. * - MEGAErrorTypeApiENoEnt - SlotID is not occupied. * * @param slotID The SlotID from which to remove the VPN credentials. * @param delegate MEGARequestDelegate to track this request. */ - (void)delVpnCredentialWithSlotID:(NSInteger)slotID delegate:(id)delegate; /** * @brief Check the current status of MEGA VPN credentials using the User Public Key. * * The User Public Key is obtained from [MEGASdk putVpnCredentialWithRegion]. * * The associated request type with this request is MEGARequestTypeCheckVPNCredential. * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest text] - Returns the User Public Key used as a parameter to verify the status of the VPN credentials. * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEAccess - Public Key is not valid. * * @param userPubKey The User Public Key used to register the VPN credentials. * @param delegate MEGARequestDelegate to track this request. */ - (void)checkVpnCredentialWithUserPubKey:(NSString *)userPubKey delegate:(id)delegate; /** * @brief Gets the public IP address and country code. * * The associated request type with this request is MEGARequestTypeGetMyIP. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest name] - Returns the country code. * - [MEGARequest text] - Returns the public IP address. * * @param delegate MEGARequestDelegate to track this request. */ - (void)getMyIPWithDelegate:(id)delegate; /** * @brief Run a network connectivity test. * * The associated request type with this request is MEGARequestTypeRunNetworkConnectivityTest. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest megaNetworkConnectivityTestResults] - Returns the results of the test. * * If the network connectivity test server could not be retrieved, the test will not run and * the request will fail with MEGAErrorTypeApiESid. * * @param delegate MEGARequestDelegate to track this request. */ - (void)runNetworkConnectivityTestWithDelegate:(id)delegate; /** * @brief Retrieve the cancellation details of a subscription * * This function requests information about the cancellation status of a subscription. * If the optional original transaction ID is not provided, the details of the most recent * subscription will be returned. * * The associated request type with this request is * MEGARequestTypeGetSubscriptionCancellationDetails * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest text] - Returns the original transaction ID * - [MEGARequest number] - Returns the subscription's expiration timestamp * - [MEGARequest numDetails] - Returns the cancellation timestamp, or 0 if not cancelled * * Possible errors: * - MEGAErrorTypeApiEArgs - If the gateway is not provided or not equal to MEGAPaymentMethodItunes * (because only Apple is supported as of now), or if the transaction ID is not a string * - MEGAErrorTypeApiENoent - If the provided transaction ID is not valid, or the user does * not have a subscription via the specified gateway * * @param gateway Payment gateway * Currently supported payment gateways are: * - MEGAPaymentMethodItunes = 2 * @param originalTransactionId Original transaction ID. Optional. If not provided, the last * subscription's details will be returned. * @param delegate MEGARequestDelegate to track this request. */ - (void)getSubscriptionCancellationDetailsWithGateway:(MEGAPaymentMethod)gateway originalTransactionId:(nullable NSString *)originalTransactionId delegate:(id)delegate; #pragma mark - Password Manager /** * @brief Get Password Manager Base folder node from the MEGA account * * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_MANAGER_BASE * Valid data in the MegaRequest object received on callbacks: * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the folder * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param delegate MEGARequestDelegate to track this request */ - (void)getPasswordManagerBaseWithDelegate:(id)delegate; /** * @brief Returns true if provided MEGAHandle belongs to a Password Manager Node Folder * * A folder is considered a Password Manager Node Folder if Password Manager Base is its * ancestor, or if the node is the Password Manager Base folder itself. * * @param node MEGAHandle of the node to check if it is a Password Manager Node Folder * @return true if this node is a Password Manager Node Folder, false otherwise. * In case node doesn't exists this method will also returns false. */ - (BOOL)isPasswordManagerNodeFolderWithHandle:(MEGAHandle)node; /** * @brief Create a new Password Node in your Password Manager tree * * The associated request type with this request is MEGARequestTypeCreatePasswordNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Handle of the parent provided as an argument * - [MEGARequest name] - name for the new Password Node provided as an argument * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new Password Node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param name Name for the new Password Node * @param data The data of the new Password Node * @param parent Parent folder for the new Password Node * @param delegate MEGARequestDelegate to track this request */ - (void)createPasswordNodeWithName:(NSString *)name data:(PasswordNodeData *)data parent:(MEGAHandle)parent delegate:(id)delegate; /** * @brief Update a Password Node in the MEGA account according to the parameters * * The associated request type with this request is MEGARequestTypeUpdatePasswordNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest nodeHandle] - handle provided of the Password Node to update * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * @param node Node to modify * @param newData The new data of the Password Node to update * @param delegate MEGARequestDelegate to track this request */ - (void)updatePasswordNodeWithHandle:(MEGAHandle)node newData:(PasswordNodeData *)newData delegate:(id)delegate; /** * @brief Create a new Credit Card Node in your Password Manager tree * * The associated request type with this request is MEGARequestTypeCreatePasswordNode * Valid data in the MEGARequest object received on callbacks: * - [MEGARequest parentHandle] - Handle of the parent provided as an argument * - [MEGARequest name] - Name for the new Password Node provided as an argument * - [MEGARequest paramType] - Returns PasswordManagerNodeTypeCreditCard * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest nodeHandle] - Handle of the new Password Node * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEBusinessPastDue: * + If the MEGA account is a business account and it's status is expired * - MEGAErrorTypeApiEArgs: * + If `name` is nil or empty string * + If `data` is nil * + If `parent` does belong to a passwordNodeFolder * - MEGAErrorTypeApiEExist: * + If there already is a Password Manager Node in the target path with the same name. In * that case, the existing Password Manager Node MegaHandle can be retrieved by * [MEGARequest nodeHandle]. * - MEGAErrorTypeApiEAppKey: * + If the `data` is ill-formed. These are the format requirements for the data in the * MEGACreditCardNodeData object: * - `cardNumber`: Mandatory (not nil nor empty string). Can only contain digits (no * spaces or other characters are allowed) * - `cvv`: Optional. If defined, must contain only digits (no spaces or other * characters are allowed) * - `expirationDate`: Optional. If defined must follow exactly the format: MM/YY, where * MM and YY are digits and MM must be between 01 and 12. Some examples: * + Valid inputs: 01/11, 12/25, 05/99. * + Invalid inputs: 13/25, 1/30, 03/5. * - `notes` and `cardHolderName`: Optionals and with no format restrictions. * * @param name Name for the new Credit Card Node * @param data Credit Card Node data for the Credit Card Node * @param parent Parent folder for the new Credit Card Node * @param delegate MEGARequestDelegate to track this request */ - (void)createCreditCardNodeWithName:(NSString *)name data:(MEGACreditCardNodeData *)data parent:(MEGAHandle)parent delegate:(id)delegate; /** * @brief Update a Credit Card Node in the MEGA account according to the parameters * * The associated request type with this request is MEGARequestTypeUpdatePasswordNode * Valid data in the MegaRequest object received on callbacks: * - [MEGARequest nodeHandle] - Handle provided of the Password Node to update * - [MEGARequest paramType] - Returns PasswordManagerNodeTypeCreditCard * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MEGAErrorTypeApiEBusinessPastDue. * * On the onRequestFinish error, the error code associated to the MEGAError can be: * - MEGAErrorTypeApiEBusinessPastDue: * + If the MEGA account is a business account and it's status is expired * - MEGAErrorTypeApiEArgs: * + If `newData` is nullptr or empty * + If `node` does not exist or does not belong to a Credit Card Node * - MEGAErrorTypeApiEAppKey: * + If the node ends up in an invalid state after applying the provided updates in * `newData`. See [MEGASDK createCreditCardNodeWithName:data:parent:delegate:] documentation for more details on the * expected format of each field if specified for the update. * * @param node Node to modify * @param newData New data for the Credit Card Node to update * @param delegate MEGARequestDelegate to track this request */ - (void)updateCreditCardNodeWithHandle:(MEGAHandle)node newData:(MEGACreditCardNodeData *)newData delegate:(id)delegate; /** * @brief Import passwords from a file into your Password Manager tree * * The associated request type with this request is * MEGARequestTypeImportPasswordsFromFile. Valid data in the MEGARequest object * received on callbacks: * - [MEGARequest getFile] - Path of the file provided as an argument. * - [MEGARequest getParamType] - Source of the file provided as an argument (see * fileSource documentation). * - [MEGARequest getParentHandle] - Handle of the parent provided as an argument. * * Valid data in the MEGARequest object received in onRequestFinish when the error code * is MEGAErrorTypeApiOk: * - [MEGARequest getMegaHandleList] - A list with all the handles for all the new imported * Password Nodes. * - [MEGARequest getMegaStringIntegerMap] - A map with problematic content as key and error * code as value * Possible error codes are: * IMPORTED_PASSWORD_ERROR_PARSER = 1 * IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD = 2 * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MEGAErrorTypeApiEArgs: * + Invalid parent (parent doesn't exist or isn't password node) * + Invalid fileSource * + NULL at filePath * + File with wrong format * - MEGAErrorTypeApiERead: * + File can't be opened * - MEGAErrorTypeApiEAccess * + File is empty * * @param filePath Path to the file containing the passwords to import. * @param fileSource Type for the source from where the file was exported. * Valid values are: * - ImportPasswordSourceGoogle = 0 * @param parent Parent handle for node that will contain new nodes as children. * @param delegate MEGARequestDelegate to track this request. */ - (void)importPasswordsFromFile:(NSString *)filePath fileSource:(ImportPasswordFileSource)fileSource parent:(MEGAHandle)parent delegate:(id)delegate; /** * @brief Generate a TOTP token and its lifetime with the data stored in the node with the * given handle. * * @note This performs a synchronous operation. * * @param handle The handle of the password node with the required totp data needed to * compute the totp token and its lifetime. * @return A `MEGATotpTokenGenResult` object: * - `result`: `int` An error code that can be one of: * + MEGAErrorTypeApiEArgs: The input handle is `UNDEF` * + MEGAErrorTypeApiENoent: The input handle does not correspond to a password node * + MEGAErrorTypeApiEKey: The input handle corresponds to a password node with no TOTP data * + MEGAErrorTypeApiEInternal: The TOTP data stored in the password node is ill-formed and cannot be * used to generate valid tokens. * + MEGAErrorTypeApiOk: the generation succeeded and the result can be retrieved from `tokenLifetime` * - `tokenLifetime`: A `MEGATotpTokenLifetime` object: * + `token`: `NSString` The generated token * + `lifetime`: `NSUInteger` The remaining life time in seconds for the generated token */ - (nullable MEGATotpTokenGenResult *)generateTotpTokenFromNode:(MEGAHandle)handle; /** * @brief Generate a new pseudo-randomly characters-based password * * @param includeCapitalLetters indicating if at least 1 upper case letter shall be included * @param includeDigits indicating if at least 1 digit shall be included * @param includeSymbols bool indicating if at least 1 symbol from !@#$%^&*() shall be included * @param length unsigned int with the number of characters that will be included. * Minimum valid length is 8 and maximum valid is 64. * @return newly generated password string, or nil if the password generation fails due to invalid length parameter. */ + (nullable NSString *)generateRandomPasswordWithCapitalLetters:(BOOL)includeCapitalLetters digits:(BOOL)includeDigits symbols:(BOOL)includeSymbols length:(int)length; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASearchFilter.h000066400000000000000000000156751516266226600216550ustar00rootroot00000000000000/** * @file MEGASearchFilter.h * @brief Class which encapsulates all data used for search nodes filtering * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGANode.h" #import "MEGASearchFilterTimeFrame.h" typedef NS_ENUM (NSInteger, MEGASearchFilterSensitiveOption) { MEGASearchFilterSensitiveOptionDisabled = 0, MEGASearchFilterSensitiveOptionNonSensitiveOnly = 1, MEGASearchFilterSensitiveOptionSensitiveOnly = 2 }; typedef NS_ENUM (NSInteger, MEGASearchFilterFavouriteOption) { MEGASearchFilterFavouriteOptionDisabled = 0, MEGASearchFilterFavouriteOptionFavouritesOnly = 1, MEGASearchFilterFavouriteOptionNonFavouritesOnly = 2 }; NS_ASSUME_NONNULL_BEGIN @interface MEGASearchFilter : NSObject @property NSString * term; @property NSString * _Nullable searchDescription; @property NSString * _Nullable searchTag; @property MEGASearchFilterTimeFrame * _Nullable creationTimeFrame; @property MEGASearchFilterTimeFrame * _Nullable modificationTimeFrame; @property uint64_t parentNodeHandle; /** * @brief Set option for filtering by predefined node types. * * Valid values for this parameter are (invalid values will be ignored): * - MEGANodeTypeUnknown = -1 --> all types * - MEGANodeTypeFile = 0 --> Returns file nodes only * - MEGANodeTypeFolder = 1 --> Returns folder nodes only */ @property (readonly, nonatomic) MEGANodeType nodeType; /** * @brief Set option for filtering by predefined file categories. * * Category of files requested in the search * Valid values for this parameter are (invalid values will be ignored): * - MEGANodeFormatTypeUnknown = 0 --> no particular category, include folders too * - MEGANodeFormatTypePhoto = 1 * - MEGANodeFormatTypeAudio = 2 * - MEGANodeFormatTypeVideo = 3 * - MEGANodeFormatTypeDocument = 4 * - MEGANodeFormatTypePdf = 5 * - MEGANodeFormatTypePresentation = 6 * - MEGANodeFormatTypeArchive = 7 * - MEGANodeFormatTypeProgram = 8 * - MEGANodeFormatTypeMisc = 9 * - MEGANodeFormatTypeSpreadsheet = 10 * - MEGANodeFormatTypeAllDocs = 11 --> any of {DOCUMENT, PDF, PRESENTATION, SPREADSHEET} * - MEGANodeFormatTypeOthers = 12 * - MEGANodeFormatTypeAllVisualMedia = 13 --> any of {PHOTO, VIDEO} */ @property (readonly, nonatomic) MEGANodeFormatType category; /** * @brief Option for filtering out sensitive nodes. * * Option to determine node inclusion based on sensitive criteria. * * Valid values for this parameter are (invalid values will be ignored): * - MEGASearchFilterSensitiveOptionDisabled = 0 --> All nodes are taken in consideration, no * filter is applied * - MEGASearchFilterSensitiveOptionNonSensitiveOnly = 1 --> Returns nodes not marked as sensitive (nodes * with property set or if any of their ancestors have it are considered sensitive) * - MEGASearchFilterSensitiveOptionSensitiveOnly = 2 --> Returns nodes with property set to true * (regardless of their children) */ @property (readonly, nonatomic) MEGASearchFilterSensitiveOption sensitiveFilter; /** * @brief Set option for filtering out nodes based on isFavourite. * * Valid values for this parameter are (invalid values will be ignored): * - MEGASearchFilterFavouriteOptionDisabled = 0 --> Both favourites and non favourites are considered * - MEGASearchFilterFavouriteOptionFavouritesOnly = 1 --> Only favourites * - MEGASearchFilterFavouriteOptionNonFavouritesOnly = 2 --> Only non favourites */ @property (readonly, nonatomic) MEGASearchFilterFavouriteOption favouriteFilter; @property int locationType; @property BOOL useAndForTextQuery; // TODO: This is a temporary proxy to support existing higher modules calls without having to modify their code. To be removed before Search By Description feature is fully implemented. - (instancetype)initWithTerm:(NSString *)term parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter creationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)modificationTimeFrame; // TODO: This is a temporary proxy to support existing higher modules calls without having to modify their code. To be removed before Search By Description feature is fully implemented. - (instancetype)initWithTerm:(NSString *)term nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame* _Nullable)modificationTimeFrame; - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag parentNodeHandle:(uint64_t)parentNodeHandle nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery; - (instancetype)initWithTerm:(NSString *)term description:(NSString * _Nullable)description tag:(NSString * _Nullable)tag nodeType:(MEGANodeType)nodeType category:(MEGANodeFormatType)category sensitiveFilter:(MEGASearchFilterSensitiveOption)sensitiveFilter favouriteFilter:(MEGASearchFilterFavouriteOption)favouriteFilter locationType:(int)locationType creationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)creationTimeFrame modificationTimeFrame:(MEGASearchFilterTimeFrame * _Nullable)modificationTimeFrame useAndForTextQuery:(BOOL)useAndForTextQuery; - (BOOL)didSetParentNodeHandle; - (BOOL)didSetLocationType; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASearchFilterTimeFrame.h000066400000000000000000000017721516266226600234400ustar00rootroot00000000000000/** * @file MEGASearchFilterTimeFrame.h * @brief Search time frame used by MEGASearchFilter * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGASearchFilterTimeFrame: NSObject @property (readonly, nonatomic) int64_t lowerLimit; @property (readonly, nonatomic) int64_t upperLimit; - (instancetype)initWithLowerLimit:(NSDate *)lowerLimit upperLimit:(NSDate *)upperLimit; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASearchPage.h000066400000000000000000000023711516266226600212710ustar00rootroot00000000000000/** * @file MEGASearchPage.h * @brief Store pagination options used in searches @see MegaApi::search, MegaApi::getChildren. * * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGASearchPage : NSObject /** @brief Return the first position in the list of results to be included in the returned page (starts from 0) **/ @property (readonly, nonatomic) size_t startingOffset; /** * @brief Return the maximum number of results included in the page, or 0 to return all (remaining) results **/ @property (readonly, nonatomic) size_t pageSize; -(instancetype)initWithStartingOffset:(size_t)startingOffset pageSize:(size_t)pageSize; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASet.h000066400000000000000000000103541516266226600200220ustar00rootroot00000000000000/** * @file MEGASet.h * @brief Represents a MEGASet in MEGA * * It allows to get all data related to a Set in MEGA. * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM (NSInteger, MEGASetType) { MEGASetTypeInvalid = -1, MEGASetTypeAlbum = 0, MEGASetTypePlaylist }; typedef NS_ENUM(NSUInteger, MEGASetChangeType) { MEGASetChangeTypeNew = 0x01, MEGASetChangeTypeName = 0x02, MEGASetChangeTypeCover = 0x04, MEGASetChangeTypeRemoved = 0x08, MEGASetChangeTypeExported = 0x10 }; NS_ASSUME_NONNULL_BEGIN /** * @brief Represents a Set in MEGA * * It allows to get all data related to a Set in MEGA. * * Objects of this class aren't live, they are snapshots of the state of a Set * in MEGA when the object is created, they are immutable. * */ @interface MEGASet : NSObject /** * @brief Returns id of current Set. * * @return Set id. */ @property (readonly, nonatomic) uint64_t handle; /** * @brief Returns id of user that owns current Set. * * @return user id. */ @property (readonly, nonatomic) uint64_t userId; /** * @brief Returns public id of current Set if it was exported. INVALID_HANDLE otherwise * * @return Public id of Set. */ @property (readonly, nonatomic) uint64_t publicId; /** * @brief Returns id of Element set as 'cover' for current Set. * * It will return INVALID_HANDLE if no cover was set or if the Element became invalid * (was removed) in the meantime. * * @return Element id. */ @property (readonly, nonatomic) uint64_t cover; /** * @brief Returns timestamp of latest changes to current Set (but not to its Elements). * * @return timestamp value. */ @property (readonly, nonatomic, nullable) NSDate *timestamp; /** * @brief Returns creation timestamp of current Set. * * @return timestamp value. */ @property (readonly, nonatomic, nullable) NSDate *timestampCreated; /** * @brief Type of the current set. * * Valid values are: * * - MEGASetTypeInvalid = -1 * Invalid type * * - MEGASetTypeAlbum = 0 * Set is an album * * - MEGASetTypePlaylist = 1 * Set is a playlist * * @return type value. */ @property (readonly, nonatomic) MEGASetType type; /** * @brief Returns name of current Set. * * @return name of current Set. */ @property (readonly, nonatomic, nullable) NSString *name; /** * @brief Returns YES if this Set has a specific change * * This value is only useful for Sets notified by [MEGADelegate onSetsUpdate:sets:] or * [MEGAGlobalDelegate onSetsUpdate:sets:] that can notify about Set modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MEGASetChangeTypeNew = 0x01 * Check if the Set was new * * - MEGASetChangeTypeName = 0x02 * Check if Set name has changed * * - MEGASetChangeTypeCover = 0x04 * Check if Set cover has changed * * - MEGASetChangeTypeRemoved = 0x08 * Check if the Set was removed * * - MEGASetChangeTypeExported = 0x10 * Check if the Set was exported or disabled (i.e. exporting ended) * * @return YES if this Set has a specific change */ - (BOOL)hasChangedType:(MEGASetChangeType)changeType; /** * @brief Returns changes for MEGASet * * Note that the position of each bit matches the MEGASetChangeType value * * @return combination of changes in */ - (MEGASetChangeType)changes; /** * @brief Returns true if this Set is exported (can be accessed via public link) * * Public link is retrieved when the Set becomes exported * * @return true if this Set is exported */ - (BOOL)isExported; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGASetElement.h000066400000000000000000000066471516266226600213460ustar00rootroot00000000000000/** * @file MEGASetElement.h * @brief Represents an MEGASetElement of a MEGASet in MEGA * * It allows to get all data related to an Element of a Set in MEGA. * * (c) 2022- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM(NSUInteger, MEGASetElementChangeType) { MEGASetElementChangeTypeNew = 0x01, MEGASetElementChangeTypeName = 0x02, MEGASetElementChangeTypeOrder = 0x04, MEGASetElementChangeTypeRemoved = 0x08 }; NS_ASSUME_NONNULL_BEGIN /** * @brief Represents an Element of a Set in MEGA * * It allows to get all data related to an Element of a Set in MEGA. * * Objects of this class aren't live, they are snapshots of the state of an Element of a Set * in MEGA when the object is created, they are immutable. * */ @interface MEGASetElement : NSObject /** * @brief Returns id of current Element. * * @return Element id. */ @property (readonly, nonatomic) uint64_t handle; /** * @brief Returns id of MegaSet current MegaSetElement belongs to. * * @return MegaSet id. */ @property (readonly, nonatomic) uint64_t ownerId; /** * @brief Returns order of current Element. * * If not set explicitly, the API will typically set it to multiples of 1000. * * @return order of current Element. */ @property (readonly, nonatomic) uint64_t order; /** * @brief Returns handle of file-node represented by current Element. * * @return file-node handle. */ @property (readonly, nonatomic) uint64_t nodeId; /** * @brief Returns timestamp of latest changes to current Element. * * @return timestamp value. */ @property (readonly, nonatomic, nullable) NSDate *timestamp; /** * @brief Returns name of current Element. * * @return name of current Element. */ @property (readonly, nonatomic, nullable) NSString *name; /** * @brief Returns YES if this SetElement has a specific change * * This value is only useful for Sets notified by [MEGADelegate onSetElementsUpdate:sets:] or * [MEGAGlobalDelegate onSetElementsUpdate:sets:] that can notify about Set modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MEGASetElementChangeTypeNew = 0x01 * Check if the Set was new * * - MEGASetElementChangeTypeName = 0x02 * Check if Set name has changed * * - MEGASetElementChangeTypeOrder = 0x04 * Check if Set cover has changed * * - MEGASetElementChangeTypeSize = 0x08 * Check if the Set was removed * * @return YES if this SetElement has a specific change */ - (BOOL)hasChangedType:(MEGASetElementChangeType)changeType; /** * @brief Returns changes for MEGASetElement * * Note that the position of each bit matches the MEGASetElementChangeType value * * @return combination of changes in */ - (MEGASetElementChangeType)changes; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAShare.h000066400000000000000000000061271516266226600203340ustar00rootroot00000000000000/** * @file MEGAShare.h * @brief Represents the outbound sharing of a folder with an user in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM (NSInteger, MEGAShareType) { MEGAShareTypeAccessUnknown = -1, MEGAShareTypeAccessRead = 0, MEGAShareTypeAccessReadWrite, MEGAShareTypeAccessFull, MEGAShareTypeAccessOwner }; /** * @brief Represents the outbound sharing of a folder with an user in MEGA. * * It allows to get all data related to the sharing. You can start sharing a folder with * a contact or cancel an existing sharing using [MEGASdk shareNodeWithUser:level:]. A public link of a folder * is also considered a sharing and can be cancelled. * * Objects of this class aren't live, they are snapshots of the state of the sharing * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can get current active sharings using [MEGASdk outSharesForNode:] * */ NS_ASSUME_NONNULL_BEGIN @interface MEGAShare : NSObject /** * @brief The email of the user with whom we are sharing the folder. * * For public shared folders, this property is nil. * */ @property (nullable, readonly, nonatomic) NSString *user; /** * @brief The handle of the folder that is being shared. */ @property (readonly, nonatomic) uint64_t nodeHandle; /** * @brief The access level of the sharing. * * Possible return values are: * - MEGAShareTypeAccessUnknown = -1 * It means that the access level is unknown * * - MEGAShareTypeAccessRead = 0 * The user can read the folder only * * - MEGAShareTypeAccessReadWrite = 1 * The user can read and write the folder * * - MEGAShareTypeAccessFull = 2 * The user has full permissions over the folder * * - MEGAShareTypeAccessOwner = 3 * The user is the owner of the folder * */ @property (readonly, nonatomic) MEGAShareType access; /** * @brief The timestamp when the sharing was created (in seconds since the epoch) */ @property (nullable, readonly, nonatomic) NSDate *timestamp; /** * @brief YES if the sharing is pending, otherwise NO. * * A sharing is pending when the folder has been shared with a user (or email) that * is not still a contact of this account. */ @property (nonatomic, readonly, getter=isPending) BOOL pending; /** * @brief Returns YES if the sharing is verified * * A sharing is verified when the keys have been shared with the other user after * verifying his credentials (see MegaApi::verifyCredentials). */ @property (nonatomic, readonly, getter=isVerified) BOOL verified; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAShareList.h000066400000000000000000000025571516266226600211730ustar00rootroot00000000000000/** * @file MEGAShareList.h * @brief List of MEGAShare objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAShare.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGAShare objects. * * Objects of this class are immutable. * * @see [MEGASdk outSharesForNode:] */ @interface MEGAShareList : NSObject /** * @brief Number of MEGAShare objects in the list. */ @property (readonly, nonatomic) NSInteger size; /** * @brief Returns the MEGAShare at the position index in the MEGAShareList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGAShare that we want to get for the list. * @return MEGAShare at the position index in the list. */ - (nullable MEGAShare *)shareAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAStringIntegerMap.h000066400000000000000000000032311516266226600225050ustar00rootroot00000000000000/** * @file MEGAStringIntegerMap.h * @brief Map of integer values with string keys (map) * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAStringList.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Map of integer values with string keys. */ @interface MEGAStringIntegerMap : NSObject /** * @brief The number of (string, int64_t) pairs in the map. */ @property (nonatomic, readonly) NSInteger size; /** * @brief Returns the list of keys in the MEGAStringIntegerMap. * * @return A MEGAStringList containing the keys present in the MEGAStringIntegerMap. */ - (MEGAStringList *)keys; /** * @brief Returns the integer value for the provided key. * * @param key The key of the element that you want to get from the map. * @return The value for the provided key. */ - (int64_t)integerValueForKey:(NSString *)key; /** * @brief Returns a dictionary containing all (string, int64_t) pairs in the map. * * @return An NSDictionary containing the keys and their corresponding integer values. */ - (NSDictionary *)toDictionary; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAStringList.h000066400000000000000000000026121516266226600213670ustar00rootroot00000000000000/** * @file MEGAStringList.h * @brief List of strings * * (c) 2017- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief List of strings * * Objects of this class are immutable. */ @interface MEGAStringList : NSObject /** * @brief The number of strings in the list */ @property (nonatomic, readonly) NSInteger size; /** * @brief Returns the string at the position i in the MEGAStringList * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the string that we want to get for the list * @return string at the position index in the list */ - (nullable NSString *)stringAtIndex:(NSInteger)index; /** * @brief Returns values from MEGAStringList mapped to a NSString array * */ - (nullable NSArray*)toStringArray; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGASubscriptionStatus.h000066400000000000000000000015571516266226600231640ustar00rootroot00000000000000/** * @file MEGAAccountType.h * @brief Details about a MEGA subscription status enum * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import typedef NS_ENUM(NSInteger, MEGASubscriptionStatus) { MEGASubscriptionStatusNone = 0, MEGASubscriptionStatusValid = 1, MEGASubscriptionStatusInvalid = 2 }; sdk-10.11.0/bindings/ios/include/MEGATOTPData.h000066400000000000000000000072701516266226600206520ustar00rootroot00000000000000/** * @file MEGATOTPData.h * @brief Object Data for TOTP attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATOTPDataValidation.h" NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, MEGATOTPHashAlgorithm) { MEGATOTPHashUnknown = -1, MEGATOTPHashSha1 = 0, MEGATOTPHashSha256 = 1, MEGATOTPHashSha512 = 2 }; @interface MEGATOTPData : NSObject /** * @brief Returns the shared secret key for TOTP * * @return shared secret key for TOTP if any, nil otherwise */ @property (readonly, nonatomic, nullable) NSString *sharedKey; /** * @brief Expiration time in seconds * * @return The expiration time in seconds */ @property (readonly, nonatomic) NSInteger expirationTime; /** * @brief Hashing algorithm to be used * Values: * Unknown = -1 * SHA1 = 0 * SHA256 = 1 * SHA512 = 2 * * @return The hashing algorithm `MEGATOTPHashAlgorithm` to be used */ @property (readonly, nonatomic) MEGATOTPHashAlgorithm hashAlgorithm; /** * @brief Number of digits in the generated TOTP code * * @return The number of digits in the generated TOTP code */ @property (readonly, nonatomic) NSInteger digits; /** * @brief Returns a `MEGATOTPDataValidation` instance that can be used to check any error detected * in the TotpData object * * @return A pointer to a newly created `MEGATOTPDataValidation` instance. */ - (nullable MEGATOTPDataValidation *)validation; /** * @brief Check if MEGATOTPData is marked to be removed * * @return true if MEGATOTPData is marked to be removed, otherwise false */ - (BOOL)markedToRemove; /** * @brief Use this constant to leave a field untouched * * @return -1 value */ + (NSInteger)totpNoChangeValue; /** * @brief Default value when expirationTime is not provided by the authentication provider * * @return 30 secs as default */ + (NSInteger)defaultExpirationTime; /** * @brief Default value when hashAlgorithm is not provided by the authentication provider * * @return SHA1 = 0 as default */ + (MEGATOTPHashAlgorithm)defaultHashAlgorithm; /** * @brief Default value when digits is not provided by the authentication provider * * @return 6 digits as default */ + (NSInteger)defaultDigits; /** * @brief Initializes a MEGATOTPData instance with the given parameters. * * @param sharedKey The shared secret key for TOTP. Can be nil if not available. * @param expirationTime The expiration time of the TOTP code in seconds. * @param hashAlgorithm The hashing algorithm to be used for generating the TOTP code. * @param digits The number of digits in the generated TOTP code. * * @return An initialized instance of MEGATOTPData. */ - (instancetype)initWithSharedKey:(NSString *)sharedKey expirationTime:(NSInteger)expirationTime hashAlgorithm:(MEGATOTPHashAlgorithm)hashAlgorithm digits:(NSInteger)digits; /** * @brief Creates a special instance of `MEGATOTPData` marked for removal. * This instance could be used to remove TOTP data from PasswordNodeData. * * @return A pointer to a `MEGATOTPData` instance marked for removal. */ - (instancetype)initWithRemovalInstance; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGATOTPDataValidation.h000066400000000000000000000046771516266226600226750ustar00rootroot00000000000000/** * @file MEGATOTPDataValidation.h * @brief Object Data for TOTP validation attributes * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @interface MEGATOTPDataValidation : NSObject /** * @brief Returns `true` if shared secret exists, `false` otherwise */ @property (readonly, nonatomic) BOOL sharedSecretExist; /** * @brief Returns `true` if shared secret is valid, `false` otherwise */ @property (readonly, nonatomic) BOOL sharedSecretValid; /** * @brief Returns `true` if algorithm has a value different from -1, `false` otherwise */ @property (readonly, nonatomic) BOOL algorithmExist; /** * @brief Returns `true` if algorithm is valid, `false` otherwise */ @property (readonly, nonatomic) BOOL algorithmValid; /** * @brief Returns `true` if expiration time has a value different from -1, `false` otherwise */ @property (readonly, nonatomic) BOOL expirationTimeExist; /** * @brief Returns `true` if expiration time is valid, `false` otherwise */ @property (readonly, nonatomic) BOOL expirationTimeValid; /** * @brief Returns `true` if number of digits has a value different from -1, `false` otherwise */ @property (readonly, nonatomic) BOOL digitsExist; /** * @brief Returns `true` if number of digits is valid, `false` otherwise */ @property (readonly, nonatomic) BOOL digitsValid; /** * @brief Returns `true` if MEGATOTPData instance is valid for initializing a new * totp data field in a password node, `false` otherwise * @note For initializing the totpData field in a password node, mandatory * fields such as the shared secret must be present in the TotpData instance. */ @property (readonly, nonatomic) BOOL isValidForCreate; /** * @brief Returns `true` if MEGATOTPData instance is valid just for updating * existing totp data on a password node, `false` otherwise */ @property (readonly, nonatomic) BOOL isValidForUpdate; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGATimeZoneDetails.h000066400000000000000000000037741516266226600223370ustar00rootroot00000000000000/** * @file MEGATimeZoneDetails.h * @brief Time zone details * * (c) 2018 - by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Provides information about timezones * * This object is related to results of the function [MEGASdk fetchTimeZone] * * Objects of this class aren't live, they are snapshots of the state of the contents of the * folder when the object is created, they are immutable. * */ @interface MEGATimeZoneDetails : NSObject /** * @brief The number of timezones in this object * */ @property (readonly, nonatomic) NSInteger numTimeZones; /** * @brief The default time zone index * * If there isn't any good default known, this function will return -1 */ @property (readonly, nonatomic) NSInteger defaultTimeZone; /** * @brief Returns the timezone at an index * * @param index Index in the list (it must be lower than [MEGATimeZoneDetails numTimeZones]) * @return Timezone at an index */ - (nullable NSString *)timeZoneAtIndex:(NSInteger)index; /** * @brief Returns the current time offset of the time zone at an index, respect to UTC (in seconds, it can be negative) * * @param index Index in the list (it must be lower than [MEGATimeZoneDetails numTimeZones]) * @return Current time offset of the time zone at an index, respect to UTC (in seconds, it can be negative) * @see [MEGATimeZoneDetails timeZoneAtIndex:] */ - (NSInteger)timeOffsetAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGATotpTokenGenResult.h000066400000000000000000000022171516266226600230460ustar00rootroot00000000000000/** * @file MEGATotpTokenGenResult.h * @brief Object Data for TOTP token generation result * * (c) 2025- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATotpTokenLifetime.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Represents the result of generating a TOTP token, including a result code and token lifetime info. */ @interface MEGATotpTokenGenResult : NSObject @property (nonatomic, assign) int errorCode; @property (nonatomic, strong) MEGATotpTokenLifetime *result; - (instancetype)initWithErrorCode:(int)errorCode result:(MEGATotpTokenLifetime *)result; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGATotpTokenLifetime.h000066400000000000000000000021151516266226600226710ustar00rootroot00000000000000/** * @file MEGATotpTokenLifetime.h * @brief Represents a TOTP token and its lifetime * * (c) 2025- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Represents a TOTP token and its lifetime. */ @interface MEGATotpTokenLifetime : NSObject @property (nonatomic, strong) NSString *token; @property (nonatomic, assign) NSUInteger remainingLifeTimeSeconds; - (instancetype)initWithToken:(NSString *)token remainingLifeTimeSeconds:(NSUInteger)remainingLifeTimeSeconds; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGATransfer.h000066400000000000000000000251701516266226600210550ustar00rootroot00000000000000/** * @file MEGATransfer.h * @brief Provides information about a transfer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGANode.h" #import "MEGAError.h" NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM (NSInteger, MEGATransferType) { MEGATransferTypeDownload, MEGATransferTypeUpload, MEGATransferTypeLocalTCPDownload, MEGATransferTypeLocalHTTPDownload = 2 //Kept for backwards compatibility }; typedef NS_ENUM (NSInteger, MEGATransferState) { MEGATransferStateNone, MEGATransferStateQueued, MEGATransferStateActive, MEGATransferStatePaused, MEGATransferStateRetrying, MEGATransferStateCompleting, MEGATransferStateComplete, MEGATransferStateCancelled, MEGATransferStateFailed }; typedef NS_ENUM (NSUInteger, MEGATransferStage) { MEGATransferStageNone = 0, MEGATransferStageScan, MEGATransferStageCreateTree, MEGATransferStageTransferringFiles, MEGATransferStageMax = MEGATransferStageTransferringFiles, }; /** * @brief Provides information about a transfer. * * Developers can use delegates (MEGADelegate, MEGATransferDelegate) * to track the progress of each transfer. MEGATransfer objects are provided in callbacks sent * to these delegates and allow developers to know the state of the transfers, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the transfer * when the object is created, they are immutable. * */ @interface MEGATransfer : NSObject /** * @brief Type of the transfer (MEGATransferTypeDownload, MEGATransferTypeUpload). */ @property (readonly, nonatomic) MEGATransferType type; /** * @brief A readable string showing the type of transfer (UPLOAD, DOWNLOAD). */ @property (readonly, nonatomic, nullable) NSString *transferString; /** * @brief The starting time of the transfer. */ @property (readonly, nonatomic, nullable) NSDate *startTime; /** * @brief Transferred bytes during this transfer. */ @property (readonly, nonatomic) long long transferredBytes; /** * @brief Total bytes to be transferred to complete the transfer. */ @property (readonly, nonatomic) long long totalBytes; /** * @brief Local path related to this transfer. * * For uploads, this property is the path to the source file. For downloads, it * is the path of the destination file. * */ @property (readonly, nonatomic, nullable) NSString *path; /** * @brief The parent path related to this transfer. * * For uploads, this property is the path to the folder containing the source file. * For downloads, it is that path to the folder containing the destination file. * */ @property (readonly, nonatomic, nullable) NSString *parentPath; /** * @brief Handle related to this transfer. * * For downloads, this property is the handle of the source node. * * For uploads, this property is the handle of the new node in [MEGATransferDelegate onTransferFinish:transfer:error:] and [MEGADelegate onTransferFinish:transfer:error:] * when the error code is MEGAErrorTypeApiOk, otherwise the value is mega::INVALID_HANDLE. * */ @property (readonly, nonatomic) uint64_t nodeHandle; /** * @brief Handle of the parent node related to this transfer. * * For downloads, this property is mega::INVALID_HANDLE. For uploads, * it is the handle of the destination node (folder) for the uploaded file. * */ @property (readonly, nonatomic) uint64_t parentHandle; /** * @brief The starting position of the transfer for streaming downloads * * The value of this fuction will be 0 if the transfer isn't a streaming * download ([MEGASdk startStreamingNode:startPos:size:]) */ @property (readonly, nonatomic) long long startPos; /** * @brief The end position of the transfer for streaming downloads * * The value of this fuction will be 0 if the transfer isn't a streaming * download ([MEGASdk startStreamingNode:startPos:size:]) */ @property (readonly, nonatomic) long long endPos; /** * @brief Name of the file that is being transferred. * * It's possible to upload a file with a different name ([MEGASdk startUploadWithLocalPath:parent:]). In that case, * this property is the destination name. * */ @property (readonly, nonatomic, nullable) NSString *fileName; /** * @brief Number of times that a transfer has temporarily failed. */ @property (readonly, nonatomic) NSInteger numRetry; /** * @brief Maximum number of times that the transfer will be retried. */ @property (readonly, nonatomic) NSInteger maxRetries; /** * @brief An integer that identifies this transfer. */ @property (readonly, nonatomic) NSInteger tag; /** * @brief The average speed of this transfer. */ @property (readonly, nonatomic) long long speed; /** * @brief Number of bytes transferred since the previous callback. * @see [MEGADelegate onTransferUpdate:transfer:], [MEGATransferDelegate onTransferUpdate:transfer:] */ @property (readonly, nonatomic) long long deltaSize; /** * @brief Timestamp when the last data was received * * This timestamp doesn't have a defined starting point. Use the difference between * the value of this property and [MEGATransfer startTime] to know how * much time the transfer has been running. * */ @property (readonly, nonatomic, nullable) NSDate *updateTime; /** * @brief A public node related to the transfer * * The value is only valid for downloads of public nodes. * */ @property (readonly, nonatomic, nullable) MEGANode *publicNode; /** * @brief YES if this is a streaming transfer, NO otherwise * @see [MEGASdk startStreamingNode:startPos:size:]; */ @property (readonly, nonatomic) BOOL isStreamingTransfer; /** * @brief YES if the transfer is at finished state (completed, cancelled or failed) * @return YES if this transfer is finished, NO otherwise */ @property (readonly, nonatomic, getter=isFinished) BOOL finished; /** * @brief YES if the transfer has failed with MEGAErrorTypeApiEOverquota * and the target is foreign. * * @return YES if the transfer has failed with MEGAErrorTypeApiEOverquota and the target is foreign. */ @property (readonly, nonatomic, getter=isForeignOverquota) BOOL foreignOverquota; /** * @brief The last error related to the transfer with extra info * */ @property (readonly, nonatomic, nullable) MEGAError *lastErrorExtended; /** * @brief YES if it's a folder transfer, otherwise (file transfer) it returns NO */ @property (readonly, nonatomic) BOOL isFolderTransfer; /** * @brief The identifier of the folder transfer associated to this transfer. * Tag of the associated folder transfer. * * This property is only useful for transfers automatically started in the context of a folder transfer. * For folder transfers (the ones directly started with startUpload), it returns -1 * Otherwise, it returns 0 * */ @property (readonly, nonatomic) NSInteger folderTransferTag; /** * @brief The application data associated with this transfer * * You can set the data returned by this function in [MEGASdk startDownloadNode:localPath:] and * [MEGASdk startDownloadNode:localPath:delegate:] * */ @property (readonly, nonatomic, nullable) NSString *appData; /** * @brief State of the transfer * * It can be one of these values: * - MEGATransferStateNone = 0 * Unknown state. This state should be never returned. * * - MEGATransferStateQueued = 1 * The transfer is queued. No data related to it is being transferred. * * - MEGATransferStateActive = 2 * The transfer is active. Its data is being transferred. * * - MEGATransferStatePaused= 3 * The transfer is paused. It won't be activated until it's resumed. * * - MEGATransferStateRetrying = 4 * The transfer is waiting to be retried due to a temporary error. * * - MEGATransferStateCompleting = 5 * The transfer is being completed. All data has been transferred * but it's still needed to attach the resulting node to the * account (uploads), to attach thumbnails/previews to the * node (uploads of images) or to create the resulting local * file (downloads). The transfer should be completed in a short time. * * - MEGATransferStateComplete = 6 * The transfer has being finished. * * - MEGATransferStateCancelled = 7 * The transfer was cancelled by the user. * * - MEGATransferStateFailed = 8 * The transfer was cancelled by the SDK due to a fatal error or * after a high number of retries. * */ @property (readonly, nonatomic) MEGATransferState state; /** * @brief The current stage in case this transfer represents a recursive operation. * This method can return the following values: * - MEGATransferStageScan = 1 * - MEGATransferStageCreateTreee = 2 * - MEGATransferStageTransferringFiles = 3 * Any other returned value, must be ignored. * * Note: a recursive operation (folder upload/download) can be cancelled using a MEGACancelToken, * but this cancellation mechanism will only have effect between the following stages: * MEGATransferStageScan and MEGATransferStageProcessTransferQueue both included. * */ @property (readonly, nonatomic) MEGATransferStage stage; /** * @brief Returns the priority of the transfer * * This value is intended to keep the order of the transfer queue on apps. * * @return Priority of the transfer */ @property (readonly, nonatomic) unsigned long long priority; /** * @brief Returns a string that identify the recursive operation stage * * @return A string that identify the recursive operation stage */ + (nullable NSString *)stringForTransferStage:(MEGATransferStage)stage; /** * @brief Returns the notification number of the SDK when this MEGATransfer was generated * * The notification number of the SDK is increased every time the SDK sends a callback * to the app. * * @return Notification number */ @property (readonly, nonatomic) long long notificationNumber; /** * @brief Returns whether the target folder of the transfer was overriden by the API server * * It may happen that the target folder fo a transfer is deleted by the time the node * is going to be added. Hence, the API will create the node in the rubbish bin. * * @return YES if target folder was overriden (apps can check the final parent) */ @property (readonly, nonatomic) BOOL targetOverride; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGATransferDelegate.h000066400000000000000000000136531516266226600225130ustar00rootroot00000000000000/** * @file MEGATransferDelegate.h * @brief Delegate to get transfer events * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATransfer.h" #import "MEGAError.h" NS_ASSUME_NONNULL_BEGIN @class MEGASdk; /** * @brief Protocol to receive information about transfers. * * All transfers allows to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all transfers using [MEGASdk addMEGATransferDelegate:]. * * MEGADelegate objects can also receive information about transfers. * * This protocol uses MEGATransfer objects to provide information of transfers. Take into account that not all * fields of MEGATransfer objects are valid for all transfers. See the documentation about each transfer to know * which fields contain useful information for each one. * */ @protocol MEGATransferDelegate @optional /** * @brief This function is called when a transfer is about to start being processed. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. */ - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer; /** * @brief This function is called when a transfer has finished. * * There won't be more callbacks about this transfer. * The last parameter provides the result of the transfer. If the transfer finished without problems, * the error code will be MEGAErrorTypeApiOk. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * @param error Error information. */ - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error; /** * @brief This function is called to inform about the progress of a transfer. * * In case this transfer represents a recursive operation (folder upload/download) SDK will * notify apps about the stages transition. * * Current recursive operation stage can be retrieved with method MegaTransfer::getStage. * This method returns the following values: * - MEGATransferStageScan = 1 * - MEGATransferStageCreateTreee = 2 * - MEGATransferStageTransferringFiles = 3 * For more information about stages refer to [MEGATransfer stage] * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * * @see [MEGATransfer transferredBytes], [MEGATransfer speed], [MEGATransfer stage]. */ - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer; /** * @brief This function is called to inform about the progress of a folder transfer * * The api object is the one created by the application, it will be valid until * the application deletes it. * * This callback is only made for folder transfers, and only to the listener for that * transfer, not for any globally registered listeners. The callback is only made * during the scanning phase. * * This function can be used to give feedback to the user as to how scanning is progressing, * since scanning may take a while and the application may be showing a modal dialog during * this time. * * Note that this function could be called from a variety of threads during the * overall operation, so proper thread safety should be observed. * * @param api MEGASdk object that started the transfer * @param transfer Information about the transfer * @param stage MEGATransferStageScan or a later value in that enum * @param folderCount The count of folders scanned so far * @param createdFolderCount The count of folders created so far (only relevant in MEGATransferStageCreateTree) * @param fileCount The count of files scanned (and fingerprinted) so far. 0 if not in scanning stage * @param currentFolder The path of the folder currently being scanned (nil except in the scan stage) * @param currentFileLeafName The leaft name of the file currently being fingerprinted (can be nil for the first call in a new folder, and when not scanning anymore) */ -(void)onFolderTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer stage:(MEGATransferStage)stage folderCount:(NSUInteger)folderCount createdFolderCount:(NSUInteger)createdFolderCount fileCount:(NSUInteger)fileCount currentFolder:(NSString *)currentFolder currentFileLeafName:(NSString *)currentFileLeafName; /** * @brief This function is called when there is a temporary error processing a transfer. * * The transfer continues after this callback, so expect more * [MEGATransferDelegate onTransferTemporaryError:transfer:error:] or * a [MEGATransferDelegate onTransferFinish:transfer:error:] callback. * * @param api MEGASdk object that started the transfer. * @param transfer Information about the transfer. * @param error Error information. */ - (void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error; /** * @brief This function is called to provide the last readed bytes of streaming downloads * * This function won't be called for non streaming downloads. * * @param api MEGASdk object that started the transfer * @param transfer Information about the transfer * @param buffer Buffer with the last readed bytes * @return YES to continue the transfer, NO to cancel it * * @see [MEGASdk startStreamingNode:startPos:size:] */ - (BOOL)onTransferData:(MEGASdk *)api transfer:(MEGATransfer *)transfer buffer:(NSData *)buffer; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGATransferList.h000066400000000000000000000026101516266226600217030ustar00rootroot00000000000000/** * @file MEGATransferList.h * @brief List of MEGATransfer objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATransfer.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGATransfer objects. * * Objects of this class are immutable. * * @see [MEGASdk transfers]. */ @interface MEGATransferList : NSObject /** * @brief The number of MEGATransfer objects in the list. */ @property (readonly, nonatomic) NSInteger size; /** * @brief The MEGATransfer at the position index in the MEGATransferList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGATransfer that we want to get for the list. * @return MEGATransfer at the position index in the list. */ - (nullable MEGATransfer *)transferAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGATreeProcessorDelegate.h000066400000000000000000000024611516266226600235210ustar00rootroot00000000000000/** * @file MEGATreeProcessorDelegate.h * @brief Delegate to process node trees * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGANode.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Protocol to process node trees * * An implementation of this class can be used to process a node tree passing a pointer to * [MEGASdk processMEGANodeTree:recursive:delegate:] * * The implementation will receive callbacks from an internal worker thread. * */ @protocol MEGATreeProcessorDelegate /** * @brief Function that will be called for all nodes in a node tree * @param node MEGANode to be processed * @return YES to continue processing nodes, NO to stop */ - (BOOL)processMEGANode:(MEGANode *)node; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAUploadOptions.h000066400000000000000000000145671516266226600221010ustar00rootroot00000000000000/** * @file MEGAUploadOptions.h * @brief Optional parameters to customize an upload. * * (c) 2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Upload trigger tag values */ typedef NS_ENUM(char, MEGAPitagTrigger) { MEGAPitagTriggerNotApplicable = '.', MEGAPitagTriggerPicker = 'p', MEGAPitagTriggerDragAndDrop = 'd', MEGAPitagTriggerCamera = 'c', MEGAPitagTriggerScanner = 's', MEGAPitagTriggerSyncAlgorithm = 'a', MEGAPitagTriggerShareFromApp = 'S', MEGAPitagTriggerCameraCapture = 'C', MEGAPitagTriggerExplorerExtension = 'e', MEGAPitagTriggerVoiceRecorder = 'v' }; /** * @brief Upload target tag values */ typedef NS_ENUM(char, MEGAPitagTarget) { MEGAPitagTargetNotApplicable = '.', MEGAPitagTargetCloudDrive = 'D', MEGAPitagTargetChat1To1 = 'c', MEGAPitagTargetChatGroup = 'C', MEGAPitagTargetNoteToSelf = 's', MEGAPitagTargetIncomingShare = 'i', MEGAPitagTargetMultipleChats = 'M' }; /** * @brief Options for uploading files and folders to MEGA. * * This class encapsulates various configuration options for upload transfers, * including custom file naming, modification times, and upload behavior. */ @interface MEGAUploadOptions : NSObject /** * @brief Custom file or folder name in MEGA. * * If nil or empty, the name is taken from the local path. */ @property (nonatomic, readonly, nullable) NSString *fileName; /** * @brief Custom modification time for files (seconds since epoch). * * Use MEGASdk.INVALID_CUSTOM_MOD_TIME to keep the local mtime. * Default value is MEGASdk.INVALID_CUSTOM_MOD_TIME (-1). */ @property (nonatomic) int64_t mtime; /** * @brief Custom app data associated with the transfer. * * Accessible via MEGATransfer.appData. * Default value is nil. */ @property (nonatomic, readonly, nullable) NSString *appData; /** * @brief If YES, the SDK deletes the local file when the upload finishes. * * Intended for temporary files only. * Default value is NO. */ @property (nonatomic) BOOL isSourceTemporary; /** * @brief If YES, the upload is put on top of the upload queue. * * Default value is NO. */ @property (nonatomic) BOOL startFirst; /** * @brief One-byte upload trigger tag. * * Valid values are: * - MEGAPitagTriggerNotApplicable = '.' * - MEGAPitagTriggerPicker = 'p' * - MEGAPitagTriggerDragAndDrop = 'd' * - MEGAPitagTriggerCamera = 'c' * - MEGAPitagTriggerScanner = 's' * - MEGAPitagTriggerSyncAlgorithm = 'a' * - MEGAPitagTriggerShareFromApp = 'S' * - MEGAPitagTriggerCameraCapture = 'C' * - MEGAPitagTriggerExplorerExtension = 'e' * - MEGAPitagTriggerVoiceRecorder = 'v' * * Default value is MEGAPitagTriggerNotApplicable. */ @property (nonatomic) MEGAPitagTrigger pitagTrigger; /** * @brief Indicate if the upload is done to a chat. * * Default value is NO. */ @property (nonatomic) BOOL isChatUpload; /** * @brief One-byte upload target tag. * * Allows specifying destinations such as chat uploads. * Apps uploading to chats should set the appropriate chat target (c, C, or s); * for other uploads keep the default value to avoid interfering with internal logic. * * Valid values are: * - MEGAPitagTargetNotApplicable = '.' * - MEGAPitagTargetCloudDrive = 'D' * - MEGAPitagTargetChat1To1 = 'c' * - MEGAPitagTargetChatGroup = 'C' * - MEGAPitagTargetNoteToSelf = 's' * - MEGAPitagTargetIncomingShare = 'i' * - MEGAPitagTargetMultipleChats = 'M' * * Default value is MEGAPitagTargetNotApplicable. */ @property (nonatomic) MEGAPitagTarget pitagTarget; /** * @brief Creates a new instance with default values. * * @return A new MEGAUploadOptions instance with all default values. */ - (instancetype)init; /** * @brief Creates a new instance with a custom file name. * * @param fileName The custom name for the file or folder in MEGA. * @return A new MEGAUploadOptions instance. */ - (instancetype)initWithFileName:(nullable NSString *)fileName; /** * @brief Creates a new instance with custom file name and modification time. * * @param fileName The custom name for the file or folder in MEGA. * @param mtime Custom modification time (seconds since epoch). * @return A new MEGAUploadOptions instance. */ - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime; /** * @brief Creates a new instance with all common options. * * @param fileName The custom name for the file or folder in MEGA. * @param mtime Custom modification time (seconds since epoch). * @param appData Custom app data associated with the transfer. * @return A new MEGAUploadOptions instance. */ - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime appData:(nullable NSString *)appData; /** * @brief Creates a new instance with all available options. * * @param fileName The custom name for the file or folder in MEGA. * @param mtime Custom modification time (seconds since epoch). * @param appData Custom app data associated with the transfer. * @param isSourceTemporary If YES, deletes the local file after upload. * @param startFirst If YES, puts the upload at the top of the queue. * @param pitagTrigger One-byte upload trigger tag. * @param isChatUpload Indicate if the upload is done to a chat. * @param pitagTarget One-byte upload target tag. * @return A new MEGAUploadOptions instance. */ - (instancetype)initWithFileName:(nullable NSString *)fileName mtime:(int64_t)mtime appData:(nullable NSString *)appData isSourceTemporary:(BOOL)isSourceTemporary startFirst:(BOOL)startFirst pitagTrigger:(MEGAPitagTrigger)pitagTrigger isChatUpload:(BOOL)isChatUpload pitagTarget:(MEGAPitagTarget)pitagTarget NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAUser.h000066400000000000000000000302211516266226600202000ustar00rootroot00000000000000/** * @file MEGAUser.h * @brief Represents an user in MEGA * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM (NSInteger, MEGAUserVisibility) { MEGAUserVisibilityUnknown = -1, MEGAUserVisibilityHidden = 0, MEGAUserVisibilityVisible = 1, MEGAUserVisibilityInactive = 2, MEGAUserVisibilityBlocked = 3 }; typedef NS_ENUM(NSInteger, MEGAUserChangeType) { MEGAUserChangeTypeAuth = 0x01, MEGAUserChangeTypeLstint = 0x02, MEGAUserChangeTypeAvatar = 0x04, MEGAUserChangeTypeFirstname = 0x08, MEGAUserChangeTypeLastname = 0x10, MEGAUserChangeTypeEmail = 0x20, MEGAUserChangeTypeKeyring = 0x40, MEGAUserChangeTypeCountry = 0x80, MEGAUserChangeTypeBirthday = 0x100, MEGAUserChangeTypePubKeyCu255 = 0x200, MEGAUserChangeTypePubKeyEd255 = 0x400, MEGAUserChangeTypeSigPubKeyRsa = 0x800, MEGAUserChangeTypeSigPubKeyCu255 = 0x1000, MEGAUserChangeTypeLanguage = 0x2000, MEGAUserChangeTypePwdReminder = 0x4000, MEGAUserChangeTypeDisableVersions = 0x8000, MEGAUserChangeTypeContactLinkVerification = 0x10000, MEGAUserChangeTypeRichPreviews = 0x20000, MEGAUserChangeTypeRubbishTime = 0x40000, MEGAUserChangeTypeStorageState = 0x80000, MEGAUserChangeTypeGeolocation = 0x100000, MEGAUserChangeTypeCameraUploadsFolder = 0x200000, MEGAUserChangeTypeMyChatFilesFolder = 0x400000, MEGAUserChangeTypePushSettings = 0x800000, MEGAUserChangeTypeUserAlias = 0x1000000, MEGAUserChangeTypeUnshareableKey = 0x2000000, MEGAUserChangeTypeDeviceNames = 0x4000000, MEGAUserChangeTypeBackupFolder = 0x8000000, MEGAUserChangeTypeCookieSetting = 0x10000000, MEGAUserChangeTypeNoCallKit = 0x20000000, MEGAUserChangeTypeAppsPrefs = 0x40000000, MEGAUserChangeTypeCCPrefs = 0x80000000 }; /** * @brief Represents an user in MEGA. * * It allows to get all data related to an user in MEGA. It can be also used * to start SDK requests ([MEGASdk shareNodeWithUser:level:] [MEGASdk removeContactUser:], etc.). * * Objects of this class aren't live, they are snapshots of the state of an user * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can get the contacts of an account using * [MEGASdk contacts] and [MEGASdk contactForEmail:]. * */ @interface MEGAUser : NSObject /** * @brief The email associated with the contact. * * The email can be used to recover the MEGAUser object later using [MEGASdk contactForEmail:] * */ @property (readonly, nonatomic, nullable) NSString *email; /** * @brief The handle associated with the contact. * */ @property (readonly, nonatomic) uint64_t handle; /** * @brief The current visibility of the contact. * * The returned value will be one of these: * * - MEGAUserVisibilityUnknown = -1 * The visibility of the contact isn't know * * - MEGAUserVisibilityHidden = 0 * The contact is currently hidden * * - MEGAUserVisibilityVisible = 1 * The contact is currently visible * * - MEGAUserVisibilityInactive = 2 * The contact is currently inactive * * - MEGAUserVisibilityBlocked = 3 * The contact is currently blocked * * @note The visibility of your own user is undefined and shouldn't be used. * @return Current visibility of the contact */ @property (readonly, nonatomic) MEGAUserVisibility visibility; /** * @brief A bit field with the changes of the user * * This value is only useful for nodes notified by [MEGADelegate onUsersUpdate:userList:] or * [MEGAGlobalDelegate onUsersUpdate:userList:] that can notify about user modifications. * * The value is an OR combination of these flags: * * - MEGAUserChangeTypeAuth = 0x01 * Check if the user has new or modified authentication information * * - MEGAUserChangeTypeLstint = 0x02 * Check if the last interaction timestamp is modified * * - MEGAUserChangeTypeAvatar = 0x04 * Check if the user has a new or modified avatar image * * - MEGAUserChangeTypeFirstname = 0x08 * Check if the user has new or modified firstname * * - MEGAUserChangeTypeLastname = 0x10 * Check if the user has new or modified lastname * * - MEGAUserChangeTypeEmail = 0x20 * Check if the user has modified email * * - MEGAUserChangeTypeKeyring = 0x40 * Check if the user has new or modified keyring * * - MEGAUserChangeTypeCountry = 0x80 * Check if the user has new or modified country * * - MEGAUserChangeTypeBirthday = 0x100 * Check if the user has new or modified birthday, birthmonth or birthyear * * - MEGAUserChangeTypePubKeyCu255 = 0x200 * Check if the user has new or modified public key for chat * * - MEGAUserChangeTypePubKeyEd255 = 0x400 * Check if the user has new or modified public key for signing * * - MEGAUserChangeTypeSigPubKeyRsa = 0x800 * Check if the user has new or modified signature for RSA public key * * - MEGAUserChangeTypeSigPubKeyCu255 = 0x1000 * Check if the user has new or modified signature for Cu25519 public key * * - MEGAUserChangeTypeLanguage = 0x2000 * Check if the user has modified the preferred language * * - MEGAUserChangeTypePwdReminder = 0x4000 * Check if the data related to the password reminder dialog has changed * * - MEGAUserChangeTypeDisableVersions = 0x8000 * Check if option for file versioning has changed * * - MEGAUserChangeTypeContactLinkVerification = 0x10000 * Check if option for automatic contact-link verification has changed * * - MEGAUserChangeTypeRichPreviews = 0x20000 * Check if option for rich links has changed * * - MEGAUserChangeTypeRubbishTime = 0x40000 * Check if rubbish time for autopurge has changed * * - MEGAUserChangeTypeStorageState = 0x80000 * Check if the state of the storage has changed * * - MEGAUserChangeTypeGeolocation = 0x100000 * Check if option for geolocation messages has changed * * - MEGAUserChangeTypeCameraUploadsFolder = 0x200000 * Check if the Camera Uploads Folder has changed * * - MEGAUserChangeTypeMyChatFilesFolder = 0x400000 * Check if the My Chat Files Folder has changed * * - MEGAUserChangeTypePushSettings = 0x800000 * Check if settings for push notifications have changed * * - MEGAUserChangeTypeUserAlias = 0x1000000 * Check if aliases have changed * * - MEGAUserChangeTypeUnshareableKey = 0x2000000 * The unshareable key has been created * * - MEGAUserChangeTypeDeviceNames = 0x4000000 * Check if device names have changed * * - MEGAUserChangeTypeBackupFolder = 0x8000000 * Check if the Backup Root Folder has changed * * - MEGAUserChangeTypeCookieSetting = 0x10000000 * Check if the Cookie Setting has changed * * - MEGAUserChangeTypeNoCallKit = 0x20000000 * Check if option for iOS CallKit has changed * * - MEGAUserChangeTypeAppsPrefs = 0x40000000 * Check if apps prefs have changed * * - MEGAUserChangeTypeCCPrefs = 0x80000000 * Check if content consumption prefs have changed * */ @property (readonly, nonatomic) MEGAUserChangeType changes; /** * @brief Indicates if the user is changed by yourself or by another client. * * This value is only useful for users notified by [MEGADelegate onUsersUpdate:userList:] or * [MEGAGlobalDelegate onUsersUpdate:userList:] that can notify about user modifications. * * @return 0 if the change is external. >0 if the change is the result of an * explicit request, -1 if the change is the result of an implicit request * made by the SDK internally. */ @property (readonly, nonatomic) NSInteger isOwnChange; /** * @brief The timestamp when the contact was added to the contact list (in seconds since the epoch). */ @property (readonly, nonatomic, nullable) NSDate *timestamp; /** * @brief Returns YES if this user has an specific change * * This value is only useful for nodes notified by [MEGADelegate onUsersUpdate:userList:] or * [MEGAGlobalDelegate onUsersUpdate:userList:] that can notify about user modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MEGAUserChangeTypeAuth = 0x01 * Check if the user has new or modified authentication information * * - MEGAUserChangeTypeLstint = 0x02 * Check if the last interaction timestamp is modified * * - MEGAUserChangeTypeAvatar = 0x04 * Check if the user has a new or modified avatar image * * - MEGAUserChangeTypeFirstname = 0x08 * Check if the user has new or modified firstname * * - MEGAUserChangeTypeLastname = 0x10 * Check if the user has new or modified lastname * * - MEGAUserChangeTypeEmail = 0x20 * Check if the user has modified email * * - MEGAUserChangeTypeKeyring = 0x40 * Check if the user has new or modified keyring * * - MEGAUserChangeTypeCountry = 0x80 * Check if the user has new or modified country * * - MEGAUserChangeTypeBirthday = 0x100 * Check if the user has new or modified birthday, birthmonth or birthyear * * - MEGAUserChangeTypePubKeyCu255 = 0x200 * Check if the user has new or modified public key for chat * * - MEGAUserChangeTypePubKeyEd255 = 0x400 * Check if the user has new or modified public key for signing * * - MEGAUserChangeTypeSigPubKeyRsa = 0x800 * Check if the user has new or modified signature for RSA public key * * - MEGAUserChangeTypeSigPubKeyCu255 = 0x1000 * Check if the user has new or modified signature for Cu25519 public key * * - MEGAUserChangeTypeLanguage = 0x2000 * Check if the user has new or modified signature for RSA public key * * - MEGAUserChangeTypePwdReminder = 0x4000 * Check if the data related to the password reminder dialog has changed * * - MEGAUserChangeTypeDisableVersions = 0x8000 * Check if option for file versioning has changed * * - MEGAUserChangeTypeContactLinkVerification = 0x10000 * Check if option for automatic contact-link verification has changed * * - MEGAUserChangeTypeRichPreviews = 0x20000 * Check if option for rich links has changed * * - MEGAUserChangeTypeRubbishTime = 0x40000 * Check if rubbish time for autopurge has changed * * - MEGAUserChangeTypeStorageState = 0x80000 * Check if the state of the storage has changed * * - MEGAUserChangeTypeGeolocation = 0x100000 * Check if option for geolocation messages has changed * * - MEGAUserChangeTypeCameraUploadsFolder = 0x200000 * Check if the Camera Uploads Folder has changed * * - MEGAUserChangeTypeMyChatFilesFolder = 0x400000 * Check if the My Chat Files Folder has changed * * - MEGAUserChangeTypePushSettings = 0x800000 * Check if settings for push notifications have changed * * - MEGAUserChangeTypeUserAlias = 0x1000000 * Check if aliases have changed * * - MEGAUserChangeTypeUnshareableKey = 0x2000000 * The unshareable key has been created * * - MEGAUserChangeTypeDeviceNames = 0x4000000 * Check if device names have changed * * - MEGAUserChangeTypeBackupFolder = 0x8000000 * Check if the Backup Root Folder has changed * * - MEGAUserChangeTypeCookieSetting = 0x10000000 * Check if the Cookie Setting has changed * * - MEGAUserChangeTypeNoCallKit = 0x20000000 * Check if option for iOS CallKit has changed * * @return YES if this user has an specific change */ - (BOOL)hasChangedType:(MEGAUserChangeType)changeType; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAUserAlert.h000066400000000000000000000263401516266226600211770ustar00rootroot00000000000000/** * @file MEGAUserAlert.h * @brief Represents a user alert in MEGA. * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAStringList.h" typedef NS_ENUM(NSInteger, MEGAUserAlertType) { MEGAUserAlertTypeIncomingPendingContactRequest, MEGAUserAlertTypeIncomingPendingContactCancelled, MEGAUserAlertTypeIncomingPendingContactReminder, MEGAUserAlertTypeContactChangeDeletedYou, MEGAUserAlertTypeContactChangeContactEstablished, MEGAUserAlertTypeContactChangeAccountDeleted, MEGAUserAlertTypeContactChangeBlockedYou, MEGAUserAlertTypeUpdatePendingContactIncomingIgnored, MEGAUserAlertTypeUpdatePendingContactIncomingAccepted, MEGAUserAlertTypeUpdatePendingContactIncomingDenied, MEGAUserAlertTypeUpdatePendingContactOutgoingAccepted, MEGAUserAlertTypeUpdatePendingContactOutgoingDenied, MEGAUserAlertTypeNewShare, MEGAUserAlertTypeDeletedShare, MEGAUserAlertTypeNewShareNodes, MEGAUserAlertTypeRemovedSharesNodes, MEGAUserAlertTypeUpdatedSharedNodes, MEGAUserAlertTypePaymentSucceeded, MEGAUserAlertTypePaymentFailed, MEGAUserAlertTypePaymentReminder, MEGAUserAlertTypeTakedown, MEGAUserAlertTypeTakedownReinstated, MEGAUserAlertTypeScheduledMeetingNew, MEGAUserAlertTypeScheduledMeetingDeleted, MEGAUserAlertTypeScheduledMeetingUpdated, MEGAUserAlertTypeTotal }; typedef NS_ENUM(NSInteger, MEGAUserAlertScheduledMeetingChangeType) { MEGAUserAlertScheduledMeetingChangeTypeTitle = 0x01, // Title has changed MEGAUserAlertScheduledMeetingChangeTypeDescription = 0x02, // Description has changed MEGAUserAlertScheduledMeetingChangeTypeCancelled = 0x04, // Cancelled flag has changed MEGAUserAlertScheduledMeetingChangeTypeTimeZone = 0x08, // Timezone has changed MEGAUserAlertScheduledMeetingChangeTypeStartDate = 0x10, // Start date time has changed MEGAUserAlertScheduledMeetingChangeTypeEndDate = 0x20, // End date time has changed MEGAUserAlertScheduledMeetingChangeTypeRules = 0x40, // Repetition rules have changed }; NS_ASSUME_NONNULL_BEGIN /** * @brief Represents a user alert in MEGA. * Alerts are the notifictions appearing under the bell in the webclient * * Objects of this class aren't live, they are snapshots of the state * in MEGA when the object is created, they are immutable. * * MEGAUserAlerts can be retrieved with [MEGASdk userAlertList] * */ @interface MEGAUserAlert : NSObject /** * @brief The id of the alert * * The ids are assigned to alerts sequentially from program start, * however there may be gaps. The id can be used to create an * association with a UI element in order to process updates in callbacks. */ @property (nonatomic, readonly) NSUInteger identifier; /** * @brief Whether the alert has been acknowledged by this client or another */ @property (nonatomic, readonly, getter=isSeen) BOOL seen; /** * @brief Whether the alert is still relevant to the logged in user. * * An alert may be relevant initially but become non-relevant, eg. payment reminder. * Alerts which are no longer relevant are usually removed from the visible list. */ @property (nonatomic, readonly, getter=isRelevant) BOOL relevant; /** * @brief The type of alert associated with the object * * @return Type of alert associated with the object */ @property (nonatomic, readonly) MEGAUserAlertType type; /** * @brief A readable string that shows the type of alert * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer */ @property (nonatomic, readonly, nullable) NSString *typeString; /** * @brief The handle of a user related to the alert * * This value is valid for user related alerts. * * @return the associated user's handle, otherwise UNDEF */ @property (readonly, nonatomic) uint64_t userHandle; /** * @brief The handle of a node related to the alert * * This value is valid for alerts that relate to a single node. * * This value is also valid for the following alerts: * MEGAUserAlertTypeScheduledMeetingNew (chatid), * MEGAUserAlertTypeScheduledMeetingDeleted (chatid), * MEGAUserAlertTypeScheduledMeetingUpdated (chatid) * * @return the relevant node handle, or UNDEF if this alert does not have one. */ @property (readonly, nonatomic) uint64_t nodeHandle; /** * @brief Returns the handle of a Pending Contact Request related to the alert * * This value is valid for user related alerts: * MEGAUserAlertTypeIncomingPendingContactRequest * MEGAUserAlertTypeIncomingPendingContactCancelled * MEGAUserAlertTypeIncomingPendingContactReminder * * This value is also valid for the following alerts: * MEGAUserAlertTypeScheduledMeetingNew * * @return the relevant node handle, or UNDEF if this alert does not have one. */ @property (readonly, nonatomic) uint64_t pendingContactRequestHandle; /** * @brief An email related to the alert * * This value is valid for alerts that relate to another user, provided the * user could be looked up at the time the alert arrived. If it was not available, * this function will return false and the client can request it via the userHandle. */ @property (nonatomic, readonly, nullable) NSString *email; /** * @brief The path of a file, folder, or node related to the alert * * This value is valid for those alerts that relate to a single path, provided * it could be looked up from the cached nodes at the time the alert arrived. * Otherwise, it may be obtainable via the nodeHandle. */ @property (nonatomic, readonly, nullable) NSString *path; /** * @brief The name of a file, folder, or node related to the alert * * This value is valid for those alerts that relate to a single name, provided * it could be looked up from the cached nodes at the time the alert arrived. * Otherwise, it may be obtainable via the nodeHandle. */ @property (nonatomic, readonly, nullable) NSString *name; /** * @brief The heading related to this alert * * This value is valid for all alerts, and similar to the strings displayed in the * webclient alerts. */ @property (nonatomic, readonly, nullable) NSString *heading; /** * @brief The title related to this alert * * This value is valid for all alerts, and similar to the strings displayed in the * webclient alerts. */ @property (nonatomic, readonly, nullable) NSString *title; /** * @brief Indicates if the user alert is changed by yourself or by another client. * * This value is only useful for user alerts notified by [MEGADelegate onUserAlertsUpdate] or * [MEGAGlobalDelegate onUserAlertsUpdate] that can notify about user alerts modifications. * * @return NO if the change is external. YES if the change is the result of a * request sent by this instance of the SDK. */ @property (nonatomic, readonly, getter=isOwnChange) BOOL ownChange; /** * @brief Returns the scheduled meeting id, related to the alert * * This value is currently only valid for type * MEGAUserAlertTypeScheduledMeetingNew, * MEGAUserAlertTypeScheduledMeetingUpdated, * MEGAUserAlertTypeScheduledMeetingDeleted, * * @return the relevant scheduled meeting id, or UNDEF. */ #ifdef ENABLE_CHAT @property (readonly, nonatomic) uint64_t scheduledMeetingId; /** * @brief Returns a MegaStringList that contains old and new title for the scheduled meeting * * This value is currently only valid for MEGAUserAlertType MEGAUserAlertTypeScheduledMeetingUpdated, * and MEGAUserAlertScheduledMeetingChangeType MEGAUserAlertScheduledMeetingChangeTypeTitle * * @return MEGAStringList that contains old and new title for ther scheduled meeting */ @property (readonly, nonatomic, nullable) MEGAStringList *titleList; /** * @brief Returns a array of dates that contains old and new StartDateTime for the scheduled meeting * * This value is currently only valid for MEGAUserAlertType MEGAUserAlertTypeScheduledMeetingUpdated, * and MEGAUserAlertScheduledMeetingChangeType MEGAUserAlertScheduledMeetingChangeTypeStartDate * * @return Array of dates that contains old and new StartDateTime for ther scheduled meeting */ @property (readonly, nonatomic, nullable) NSArray *startDateList; /** * @brief Returns a array of dates that contains old and new EndDateTime for the scheduled meeting * * This value is currently only valid for MEGAUserAlertType MEGAUserAlertTypeScheduledMeetingUpdated, * and MEGAUserAlertScheduledMeetingChangeType MEGAUserAlertScheduledMeetingChangeTypeStartDate * * @return Array of dates that contains old and new EndDateTime for ther scheduled meeting */ @property (readonly, nonatomic, nullable) NSArray *endDateList; #endif /** * @brief Returns a number related to this alert * * This value is valid for these alerts: * MEGAUserAlertTypeNewShareNodes (0: folder count 1: file count ) * MEGAUserAlertTypeRemovedSharesNodes (0: item count ) * * @return Number related to this request, or -1 if the index is invalid */ - (int64_t)numberAtIndex:(NSUInteger)index; /** * @brief Returns a timestamp related to this alert * * This value is valid for index 0 for all requests, indicating when the alert occurred. * Additionally MEGAUserAlertTypePaymentReminder index 1 is the timestamp of the expiry of the period. * * @return Timestamp related to this request, or -1 if the index is invalid */ - (int64_t)timestampAtIndex:(NSUInteger)index; /** * @brief Returns an additional string, related to the alert * * This value is currently only valid for * MEGAUserAlertTypePaymentSucceeded index 0: the plan name * MEGAUserAlertTypePaymentFailed index 0: the plan name * * @return a pointer to the string if index is valid; otherwise nil */ - (nullable NSString *)stringAtIndex:(NSUInteger)index; #ifdef ENABLE_CHAT /** * @brief Returns true if the scheduled meeting associated to this alert has an specific change * * This value is currently only valid for type: MEGAUserAlertTypeScheduledMeetingUpdated * * @param changeType The type of change to check. It can be one of the following values: * MEGAUserAlertScheduledMeetingChangeTypeTitle, // Title has changed * MEGAUserAlertScheduledMeetingChangeTypeDescription, // Description has changed * MEGAUserAlertScheduledMeetingChangeTypeCancelled, // Cancelled flag has changed * MEGAUserAlertScheduledMeetingChangeTypeTimeZone, // Timezone has changed * MEGAUserAlertScheduledMeetingChangeTypeStartDate, // Start date time has changed * MEGAUserAlertScheduledMeetingChangeTypeEndDate, // End date time has changed * MEGAUserAlertScheduledMeetingChangeTypeRules, // Repetition rules have changed * * @return a pointer to the string if index is valid; otherwise nil */ - (BOOL)hasScheduledMeetingChangeType:(MEGAUserAlertScheduledMeetingChangeType)changeType; #endif NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAUserAlertList.h000066400000000000000000000034761516266226600220400ustar00rootroot00000000000000/** * @file MEGAUserAlertList.h * @brief List of MEGAUserAlert objects * * (c) 2018-Present by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import NS_ASSUME_NONNULL_BEGIN @class MEGAUserAlert; /** * @brief List of MEGAUserAlert objects * * A MEGAUserAlertList has the ownership of the MEGAUserAlert objects that it contains, so they will be * only valid until the MEGAUserAlertList is deleted. If you want to retain a MEGAUserAlert returned by * a MEGAUserAlertList, use [MEGAUserAlert clone]. * * Objects of this class are immutable. * * @see [MEGASdk userAlertList] * */ @interface MEGAUserAlertList : NSObject /** * @brief Returns the number of MEGAUserAlert objects in the list */ @property (readonly, nonatomic) NSInteger size; /** * @brief Returns the MEGAUserAlert at the position index in the MEGAUserAlertList * * The MEGAUserAlertList retains the ownership of the returned MEGAUserAlert. It will be only valid until * the MEGAUserAlertList is deleted. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGAUserAlert that we want to get for the list * @return MEGAUserAlert at the position index in the list */ - (nullable MEGAUserAlert *)usertAlertAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAUserList.h000066400000000000000000000025311516266226600210370ustar00rootroot00000000000000/** * @file MEGAUserList.h * @brief List of MEGAUser objects * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAUser.h" NS_ASSUME_NONNULL_BEGIN /** * @brief List of MEGAUser objects. * * Objects of this class are immutable. * * @see [MEGASdk contacts]. * */ @interface MEGAUserList : NSObject /** * @brief The number of MEGAUser objects in the list */ @property (readonly, nonatomic) NSInteger size; /** * @brief The MEGAUser at the position index in the MEGAUserList. * * If the index is >= the size of the list, this function returns nil. * * @param index Position of the MEGAUser that we want to get for the list. * @return MEGAUser at the position index in the list. */ - (nullable MEGAUser *)userAtIndex:(NSInteger)index; NS_ASSUME_NONNULL_END @end sdk-10.11.0/bindings/ios/include/MEGAVPNCluster.h000066400000000000000000000030531516266226600212720ustar00rootroot00000000000000/** * @file MEGAVPNCluster.h * @brief Container to store information of a VPN Cluster. * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright * Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this program. */ #import NS_ASSUME_NONNULL_BEGIN /** * @brief Container to store information of a VPN Cluster. * * - Host * - DNS: list of IPs * * Instances of this class are immutable. */ @interface MEGAVPNCluster : NSObject /** * @brief Get the host of this VPN Cluster. * * @return The host of this VPN Cluster, always not-null. */ @property (nonatomic, readonly) NSString *host; /** * @brief Get the list of IPs for the current VPN Cluster. * * @return An array containing the IPs for the current VPN Cluster, always not-null. */ @property (nonatomic, readonly) NSArray *dns; /** * @brief Get the list of ad-blocking DNS IPs. * * You take the ownership of the returned value * * @return A array containing the IPs for ad blocking DNS, always not-null */ @property (nonatomic, readonly) NSArray *adBlockingDns; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAVPNCredentials.h000066400000000000000000000051251516266226600221100ustar00rootroot00000000000000/** * @file MEGAVPNCredentials.h * @brief Container to store information of a VPN Cluster. * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGAIntegerList.h" #import "MEGAStringList.h" #import "MEGAVPNRegion.h" NS_ASSUME_NONNULL_BEGIN @interface MEGAVPNCredentials : NSObject /** * @brief Gets the list of SlotIDs. * * @return A MEGAIntegerList containing the SlotIDs. */ - (MEGAIntegerList *)slotIDs; /** * @brief Gets the list of available VPN regions. * * @return A MEGAStringList containing the VPN regions. */ - (MEGAStringList *)vpnRegions; /** * @brief Get the list of the available VPN regions, including the clusters for each region. * * @return An NSArray of MEGAVPNRegion objects. */ - (NSArray *)vpnRegionsDetailed; /** * @brief Gets the IPv4 address associated with a given SlotID. * * @param slotID The SlotID for which the IPv4 address is requested. * @return A string containing the IPv4 address. */ - (nullable NSString *)ipv4ForSlotID:(NSInteger)slotID; /** * @brief Gets the IPv6 address associated with a given SlotID. * * @param slotID The SlotID for which the IPv6 address is requested. * @return A string containing the IPv6 address. */ - (nullable NSString *)ipv6ForSlotID:(NSInteger)slotID; /** * @brief Gets the DeviceID associated with a given SlotID. * * @param slotID The SlotID for which the DeviceID is requested. * @return A string containing the DeviceID. */ - (nullable NSString *)deviceIDForSlotID:(NSInteger)slotID; /** * @brief Gets the ClusterID associated with a given SlotID. * * @param slotID The SlotID for which the ClusterID is requested. * @return An integer containing the ClusterID. */ - (NSInteger)clusterIDForSlotID:(NSInteger)slotID; /** * @brief Gets the Cluster Public Key associated with a given ClusterID. * * @param clusterID The ClusterID for which the Public Key is requested. * @return A string containing the Cluster Public Key. */ - (nullable NSString *)clusterPublicKeyForClusterID:(NSInteger)clusterID; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/MEGAVPNRegion.h000066400000000000000000000050661516266226600211020ustar00rootroot00000000000000/** * @file MEGAVPNRegion.h * @brief Container to store information of a VPN Region. * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright * Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this program. */ #import #import "MEGAVPNCluster.h" NS_ASSUME_NONNULL_BEGIN /** * @brief Container to store information of a VPN Region. * * - Name (example: hMLKTUojS6o, 1MvzBCx1Uf4) * - Country Code (example: ES, LU) * - Country Name (example: Spain, Luxembourg) * - Region Name (optional) (example: Esch-sur-Alzette) * - Town Name (Optional) (example: Bettembourg) * - Clusters (contain information like host, DNS list, possibly others) * * Instances of this class are immutable. */ @interface MEGAVPNRegion : NSObject /** * @brief Get the name of this VPN Region. * * @return The name of this VPN Region, always not-null. */ @property (nonatomic, readonly) NSString *name; /** * @brief Get the country code where the VPN Region is located. * * @return The country code for this VPN Region, always not-null. */ @property (nonatomic, readonly) NSString *countryCode; /** * @brief Get the name of the country where the VPN Region is located. * * @return The country name for this VPN Region, always not-null. */ @property (nonatomic, readonly) NSString *countryName; /** * @brief Get the name of the country region where this VPN Region is located. * * Optional value. It may be empty for certain VPN Regions. * * @return The country region name for this VPN Region, always not-null. */ @property (nonatomic, readonly) NSString *regionName; /** * @brief Get the name of the town where this VPN is located. * * Optional value. It may be empty for certain VPN Regions. * * @return The name of the town for this VPN Region, always not-null. */ @property (nonatomic, readonly) NSString *townName; /** * @brief Get a container with all Clusters of this VPN Region. * * @return A dictionary mapping cluster IDs (NSNumber) to MEGAVPNCluster objects, always not-null. */ @property (nonatomic, readonly) NSDictionary *clusters; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/include/PasswordNodeData.h000066400000000000000000000047721516266226600220460ustar00rootroot00000000000000/** * @file PasswordNodeData.h * @brief Object Data for Password Node attributes * * (c) 2023- by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGATOTPData.h" NS_ASSUME_NONNULL_BEGIN @interface PasswordNodeData : NSObject /** * @brief The password value of the node. * * @return A string containing the password value. */ @property (readonly, nonatomic) NSString *password; /** * @brief The notes associated with the password node. * * @return A string containing the notes or nil if not set. */ @property (readonly, nonatomic, nullable) NSString *notes; /** * @brief The URL associated with the password node. * * @return A string containing the URL or nil if not set. */ @property (readonly, nonatomic, nullable) NSString *url; /** * @brief The username associated with the password node. * * @return A string containing the username or nil if not set. */ @property (readonly, nonatomic, nullable) NSString *userName; /** * @brief Represents data related to TOTP (Time-based One-Time Password) token * generation * * @return A `MEGATOTPData` object containing the TOTP data or nil if not set. */ @property (readonly, nonatomic, nullable) MEGATOTPData *totp; /** * @brief Initializes a new instance with the provided password-related attributes. * * This initializer creates a new object using the attributes from `mega::MEGANode::PasswordNodeData`. * * @param password The password string (required). * @param notes Additional notes related to the password (optional). * @param url The associated URL, if any (optional). * @param userName The username linked to the password (optional). * @param totp A `MEGATOTPData` instance representing TOTP data (optional). * * @return An initialized instance of the object. */ - (instancetype)initWithPassword:(NSString *)password notes:(nullable NSString *)notes url:(nullable NSString *)url userName:(nullable NSString *)userName totp:(nullable MEGATOTPData *)totp; @end NS_ASSUME_NONNULL_END sdk-10.11.0/bindings/ios/mega/000077500000000000000000000000001516266226600157475ustar00rootroot00000000000000sdk-10.11.0/bindings/ios/mega/config.h000066400000000000000000000146441516266226600173760ustar00rootroot00000000000000/* include/mega/config.h. Generated from config.h.in by configure. */ /* include/mega/config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if you have the header file. */ #define HAVE_ARPA_INET_H 1 /* Define to 1 if you have the header file. */ #define HAVE_CRYPTOPP_CRYPTLIB_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_DB_CXX_H */ /* Define to 1 if you have the header file. */ #define HAVE_DLFCN_H 1 /* Define to 1 if you have the `fdopendir' function. */ //#define HAVE_FDOPENDIR 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_FREEIMAGE_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_HTONL */ /* Define to 1 if you have the `inotify_init' function. #define HAVE_INOTIFY_INIT 1*/ /* Defined if std::int64_t and time_t are distinct. */ #define HAVE_DISTINCT_TIME_T 1 /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if your system has a GNU libc compatible `malloc' function, and to 0 otherwise. */ #define HAVE_MALLOC 1 /* Define to 1 if you have the header file. */ #define HAVE_MCHECK_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the header file. */ #define HAVE_NETDB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_NETINET_IN_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_READLINE_READLINE_H */ /* Define to 1 if you have the `select' function. */ #define HAVE_SELECT 1 /* Define to 1 if you have the `sendfile' function. */ //#define HAVE_SENDFILE 1 /* Define to 1 if you have the header file. */ #define HAVE_SQLITE3_H 1 /* Define to 1 if the system has the type `ssize_t'. */ #define HAVE_SSIZE_T 1 /* Define to 1 if stdbool.h conforms to C99. */ #define HAVE_STDBOOL_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDDEF_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the header file. #define HAVE_SYS_INOTIFY_H 1*/ /* Define to 1 if you have the header file. */ #define HAVE_SYS_SOCKET_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TIMEB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UNISTD_H 1 /* Define to 1 if you have the header file. */ #define HAVE_DIRENT_H 1 /* Define to 1 if the system has the type `_Bool'. */ /* #undef HAVE__BOOL */ /* Define to the sub-directory in which libtool stores uninstalled libraries. */ #define LT_OBJDIR ".libs/" /* Name of package */ #define PACKAGE "libmega" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "https://github.com/megaprivacy" /* Define to the full name of this package. */ #define PACKAGE_NAME "libmega" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "libmega" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* The size of `uint64_t', as computed by sizeof. */ #define SIZEOF_UINT64_T 8 /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if your declares `struct tm'. */ /* #undef TM_IN_SYS_TIME */ /* Define to use libcrypto++ */ #define USE_CRYPTOPP 1 //#define USE_FREEIMAGE 1 #define USE_PTHREAD 1 #define USE_IOS 1 #define HAVE_LIBUV 1 #define ENABLE_CHAT 1 #define USE_MEDIAINFO 1 #define UNICODE 1 /* Use inotify API #define USE_INOTIFY 1*/ /* Define to use SQLite */ #define USE_SQLITE 1 /* Version number of package */ #define VERSION "0.2.3" /* Enable large inode numbers on Mac OS X 10.5. */ #ifndef _DARWIN_USE_64_BIT_INODE # define _DARWIN_USE_64_BIT_INODE 1 #endif /* Define to 64 for large file support on some hosts */ #define _FILE_OFFSET_BITS 64 /* use GNU extensions */ #if !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif /* Define for large files, on AIX-style hosts. */ /* #undef _LARGE_FILES */ /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT32_T */ /* Define for Solaris 2.5.1 so the uint64_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT64_T */ /* Define for Solaris 2.5.1 so the uint8_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT8_T */ /* Force definition of constant macros for C++ */ #define __STDC_CONSTANT_MACROS /**/ /* Force definition of format macros for C++ */ #define __STDC_FORMAT_MACROS /**/ /* Force definition of limit macros for C++ */ #define __STDC_LIMIT_MACROS /**/ /* Define to rpl_malloc if the replacement function should be used. */ /* #undef malloc */ /* Define to `long int' if does not define. */ /* #undef off_t */ /* Define to `int' if does not define. */ /* #undef pid_t */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ /* Define to the type of an unsigned integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ /* #undef uint16_t */ /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ /* #undef uint32_t */ /* Define to the type of an unsigned integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ /* #undef uint64_t */ /* Define to the type of an unsigned integer type of width exactly 8 bits if such a type exists and the standard includes do not define it. */ /* #undef uint8_t */ sdk-10.11.0/bindings/java/000077500000000000000000000000001516266226600151655ustar00rootroot00000000000000sdk-10.11.0/bindings/java/CMakeLists.txt000066400000000000000000000047121516266226600177310ustar00rootroot00000000000000find_package(Java REQUIRED) find_package(SWIG REQUIRED) include(UseSWIG) # Set the output directory for generated Java files set(JAVA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bindings/java/nz/mega/sdk) # Define the interface file and its properties set(SWIG_INTERFACE_FILE ${CMAKE_CURRENT_LIST_DIR}/../megaapi.i) set_source_files_properties(${SWIG_INTERFACE_FILE} PROPERTIES CPLUSPLUS ON ) # Set SWIG flags for generating Java code if(ENABLE_SYNC) list(APPEND ADDITIONAL_SDK_SWIG_DEFINES -DENABLE_SYNC) endif() if(USE_LIBUV) list(APPEND ADDITIONAL_SDK_SWIG_DEFINES -DHAVE_LIBUV) endif() if(ENABLE_CHAT) list(APPEND ADDITIONAL_SDK_SWIG_DEFINES -DENABLE_CHAT) endif() if(ENABLE_LOG_PERFORMANCE) list(APPEND ADDITIONAL_SDK_SWIG_DEFINES -DENABLE_LOG_PERFORMANCE) endif() set(CMAKE_SWIG_FLAGS -c++ -package "nz.mega.sdk" ${ADDITIONAL_SDK_SWIG_DEFINES} -I${CMAKE_CURRENT_LIST_DIR}/../../include) set(SDKLIB_ANDROID_LIBRARY_TYPE USE_BUILD_SHARED_LIBS) if(ENABLE_SDKLIB_ANDROID_DYNAMIC_LIBRARY) set(SDKLIB_ANDROID_LIBRARY_TYPE SHARED) endif() # Generate java binding files swig_add_library(SDKJavaBindings TYPE ${SDKLIB_ANDROID_LIBRARY_TYPE} LANGUAGE java SOURCES ${SWIG_INTERFACE_FILE} OUTPUT_DIR ${JAVA_OUTPUT_DIR} ) if (ENABLE_SDKLIB_ANDROID_DYNAMIC_LIBRARY) # Rename when a single dynamic library is produced set_target_properties(SDKJavaBindings PROPERTIES OUTPUT_NAME "mega" # libmega.so ) endif() set_target_properties(SDKJavaBindings PROPERTIES POSITION_INDEPENDENT_CODE ON ) # For Android we do not need JNI if(ANDROID) set(JNI_INCLUDE_DIRS ${ANDROID_NDK_HOME}/sysroot/usr/include ${ANDROID_NDK_HOME}/sysroot/usr/include/${ANDROID_ABI} ) else() find_package(JNI REQUIRED) endif() # If the library needs JNI_OnLoad if(SDKLIB_STANDALONE AND (BUILD_SHARED_LIBS OR ENABLE_SDKLIB_ANDROID_DYNAMIC_LIBRARY)) message(STATUS "JNI_OnLoad will be available in the SDK bindings dynamic library.") target_compile_definitions(SDKJavaBindings PRIVATE SDKLIB_ONLOAD ) endif() target_include_directories(SDKJavaBindings PRIVATE ${Java_INCLUDE_DIRS} ${JNI_INCLUDE_DIRS} ${JAVA_OUTPUT_DIR} ) target_link_libraries(SDKJavaBindings PRIVATE MEGA::SDKlib ) # Compile Java code add_custom_command(TARGET SDKJavaBindings POST_BUILD COMMAND ${Java_JAVAC_EXECUTABLE} -d ${JAVA_OUTPUT_DIR} -cp ${JAVA_OUTPUT_DIR} ${JAVA_OUTPUT_DIR}/*.java COMMENT "Compiling Java classes..." ) sdk-10.11.0/bindings/java/nz/000077500000000000000000000000001516266226600156145ustar00rootroot00000000000000sdk-10.11.0/bindings/java/nz/mega/000077500000000000000000000000001516266226600165255ustar00rootroot00000000000000sdk-10.11.0/bindings/java/nz/mega/sdk/000077500000000000000000000000001516266226600173065ustar00rootroot00000000000000sdk-10.11.0/bindings/java/nz/mega/sdk/AndroidGfxProcessor.java000066400000000000000000000404221516266226600241000ustar00rootroot00000000000000package nz.mega.sdk; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.media.MediaMetadataRetriever; import android.media.ThumbnailUtils; import android.net.Uri; import android.provider.BaseColumns; import android.provider.MediaStore; import androidx.exifinterface.media.ExifInterface; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URLConnection; import java.util.Objects; public class AndroidGfxProcessor extends MegaGfxProcessor { Rect size; int orientation; String srcPath; Bitmap bitmap; byte[] bitmapData; static Context context = null; protected AndroidGfxProcessor() { if (context == null) { try { context = (Context) Class.forName("android.app.AppGlobals") .getMethod("getInitialApplication") .invoke(null, (Object[]) null); } catch (Exception e) { } } } // Return file extensions for all supported formats: images,videos,pdf and etc, must end with . public String supportedImageFormats() { return ".jpg.png.bmp.jpeg.cut.dds.g3.gif.hdr.ico.iff.ilbm" + ".jbig.jng.jif.koala.pcd.mng.pcx.pbm.pgm.ppm.pfm.pds.raw.3fr.ari" + ".arw.bay.crw.cr2.cap.dcs.dcr.dng.drf.eip.erf.fff.iiq.k25.kdc.mdc.mef.mos.mrw" + ".nef.nrw.obm.orf.pef.ptx.pxn.r3d.raf.raw.rwl.rw2.rwz.sr2.srf.srw.x3f.ras.tga" + ".xbm.xpm.jp2.j2k.jpf.jpx.webp" + ".cur.heic.jc2.pnm.psd.tif.tiff.3g2.3gp.avi.m4v.mov.mp4.mqv.qt.jxl.avif.pdf."; } // Return null to skip extra checking public String supportedVideoFormats() { return null; } public static boolean isVideoFile(String path) { try { String mimeType = URLConnection.guessContentTypeFromName(path); if (mimeType == null) { Uri uri = Uri.parse(path); mimeType = context.getContentResolver().getType(uri); } return mimeType != null && mimeType.startsWith("video"); } catch (Exception e) { return false; } } public static Rect getImageDimensions(String path, int orientation) { Rect rect = new Rect(); if (isVideoFile(path)) { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { setMediaMetadataRetrieverDataSource(retriever, path); int width; int height; int interchangeOrientation = Integer.parseInt(Objects.requireNonNull( retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION))); if (interchangeOrientation == 90 || interchangeOrientation == 270) { width = Integer.parseInt(Objects.requireNonNull(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT))); height = Integer.parseInt(Objects.requireNonNull(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH))); } else { width = Integer.parseInt(Objects.requireNonNull(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH))); height = Integer.parseInt(Objects.requireNonNull(retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT))); } rect.right = width; rect.bottom = height; } catch (Exception e) { System.out.println("Error in getImageDimensions for video: " + e); } finally { try { retriever.release(); } catch (IOException e) { System.out.println("Error releasing MediaMetadataRetriever for video: " + e); } } } else { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; InputStream inputStream = getInputStreamFromPath(path); BitmapFactory.decodeStream(inputStream, null, options); if ((options.outWidth > 0) && (options.outHeight > 0)) { if ((orientation < 5) || (orientation > 8)) { rect.right = options.outWidth; rect.bottom = options.outHeight; } else { rect.bottom = options.outWidth; rect.right = options.outHeight; } } } catch (Exception e) { } } return rect; } private static InputStream getInputStreamFromPath(String path) { try { Uri uri = Uri.parse(path); String scheme = uri == null ? null : uri.getScheme(); if (scheme != null) { return context.getContentResolver().openInputStream(uri); } else { return new FileInputStream(path); } } catch (Exception e) { return null; } } private static void setMediaMetadataRetrieverDataSource(MediaMetadataRetriever retriever, String path) { Uri uri = Uri.parse(path); String scheme = uri == null ? null : uri.getScheme(); if (scheme != null) { retriever.setDataSource(context, uri); } else { retriever.setDataSource(path); } } public boolean readBitmap(String path) { if (isVideoFile(path)) { srcPath = path; size = getImageDimensions(srcPath, orientation); return (size.right != 0) && (size.bottom != 0); } else { srcPath = path; orientation = getExifOrientation(path); size = getImageDimensions(srcPath, orientation); return (size.right != 0) && (size.bottom != 0); } } public int getWidth() { return size.right; } public int getHeight() { return size.bottom; } static public Bitmap getBitmap(String path, Rect rect, int orientation, int w, int h) { int width; int height; if (isVideoFile(path)) { Uri uri = Uri.parse(path); String scheme = uri == null ? null : uri.getScheme(); boolean isContentUri = scheme != null && scheme.equals("content"); Bitmap bmThumbnail = null; Cursor cursor = null; if (!isContentUri) { try { bmThumbnail = ThumbnailUtils.createVideoThumbnail( path, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND); if (context != null && bmThumbnail == null) { String SELECTION = MediaStore.MediaColumns.DATA + "=?"; String[] PROJECTION = {BaseColumns._ID}; uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; String[] selectionArgs = {path}; ContentResolver cr = context.getContentResolver(); cursor = cr.query(uri, PROJECTION, SELECTION, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { long videoId = cursor.getLong(0); bmThumbnail = MediaStore.Video.Thumbnails.getThumbnail( cr, videoId, MediaStore.Video.Thumbnails.FULL_SCREEN_KIND, null); } } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } if (bmThumbnail == null) { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { setMediaMetadataRetrieverDataSource(retriever, path); bmThumbnail = retriever.getFrameAtTime(); } catch (Exception e1) { e1.printStackTrace(); } finally { try { retriever.release(); } catch (Exception ex) { ex.printStackTrace(); } } } if (!isContentUri && bmThumbnail == null) { try { bmThumbnail = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND); if (context != null && bmThumbnail == null) { String SELECTION = MediaStore.MediaColumns.DATA + "=?"; String[] PROJECTION = {BaseColumns._ID}; uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; String[] selectionArgs = {path}; ContentResolver cr = context.getContentResolver(); cursor = cr.query(uri, PROJECTION, SELECTION, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { long videoId = cursor.getLong(0); bmThumbnail = MediaStore.Video.Thumbnails.getThumbnail(cr, videoId, MediaStore.Video.Thumbnails.MINI_KIND, null); } } } catch (Exception e2) { e2.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } } try { if (bmThumbnail != null) { return Bitmap.createScaledBitmap(bmThumbnail, w, h, true); } } catch (Exception e) { } } else { if ((orientation < 5) || (orientation > 8)) { width = rect.right; height = rect.bottom; } else { width = rect.bottom; height = rect.right; } try { int scale = 1; while (width / scale / 2 >= w && height / scale / 2 >= h) scale *= 2; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inSampleSize = scale; InputStream inputStream = getInputStreamFromPath(path); Bitmap tmp = BitmapFactory.decodeStream(inputStream, null, options); tmp = fixExifOrientation(tmp, orientation); return Bitmap.createScaledBitmap(tmp, w, h, true); } catch (Exception e) { } } return null; } public static int getExifOrientation(String srcPath) { int orientation = ExifInterface.ORIENTATION_UNDEFINED; int i = 0; boolean isError = false; while ((i < 5) && (orientation == ExifInterface.ORIENTATION_UNDEFINED)) { try { ExifInterface exif = new ExifInterface(srcPath); orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, orientation); isError = false; } catch (IOException e) { isError = true; try { Thread.sleep(100); } catch (InterruptedException e1) { } } finally { if (isError) { InputStream inputStream = getInputStreamFromPath(srcPath); try { ExifInterface exif = new ExifInterface(inputStream); orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, orientation); isError = false; } catch (Exception e) {} } } i++; } return orientation; } /* * Change image orientation based on EXIF image data */ public static Bitmap fixExifOrientation(Bitmap bitmap, int orientation) { if (bitmap == null) return null; if ((orientation < 2) || (orientation > 8)) { // No changes required or invalid orientation return bitmap; } Matrix matrix = new Matrix(); switch (orientation) { case ExifInterface.ORIENTATION_TRANSPOSE: case ExifInterface.ORIENTATION_ROTATE_90: matrix.postRotate(90); break; case ExifInterface.ORIENTATION_ROTATE_180: case ExifInterface.ORIENTATION_FLIP_VERTICAL: matrix.postRotate(180); break; case ExifInterface.ORIENTATION_TRANSVERSE: case ExifInterface.ORIENTATION_ROTATE_270: matrix.postRotate(270); break; default: break; } if ((orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL) || (orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL)) matrix.preScale(-1, 1); else if ((orientation == ExifInterface.ORIENTATION_TRANSPOSE) || (orientation == ExifInterface.ORIENTATION_TRANSVERSE)) matrix.preScale(1, -1); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } public static Bitmap extractRect(Bitmap bitmap, int px, int py, int rw, int rh) { if (bitmap == null) return null; int w = bitmap.getWidth(); int h = bitmap.getHeight(); if ((px != 0) || (py != 0) || (rw != w) || (rh != h)) { Bitmap.Config conf = Bitmap.Config.ARGB_8888; Bitmap scaled = Bitmap.createBitmap(rw, rh, conf); Canvas canvas = new Canvas(scaled); canvas.drawBitmap(bitmap, new Rect(px, py, px + rw, py + rh), new Rect(0, 0, rw, rh), null); bitmap = scaled; } return bitmap; } public static boolean saveBitmap(Bitmap bitmap, File file) { if (bitmap == null) return false; try (FileOutputStream stream = new FileOutputStream(file)) { return bitmap.compress(Bitmap.CompressFormat.JPEG, 85, stream); } catch (Exception e) { e.printStackTrace(); } return false; } public int getBitmapDataSize(int w, int h, int px, int py, int rw, int rh, int hint) { if (bitmap == null) bitmap = getBitmap(srcPath, size, orientation, w, h); else bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); bitmap = extractRect(bitmap, px, py, rw, rh); if (bitmap == null) return 0; try { Bitmap.CompressFormat targetFormat = Bitmap.CompressFormat.JPEG; int targetQuality = 85; if (hint == MegaGfxProcessor.GFX_HINT_FORMAT_PNG) { boolean hasTransparency = bitmap.hasAlpha(); if (hasTransparency) { targetFormat = Bitmap.CompressFormat.PNG; targetQuality = 75; } } ByteArrayOutputStream stream = new ByteArrayOutputStream(); if (!bitmap.compress(targetFormat, targetQuality, stream)) return 0; bitmapData = stream.toByteArray(); return bitmapData.length; } catch (Exception e) { } return 0; } public boolean getBitmapData(byte[] buffer) { try { System.arraycopy(bitmapData, 0, buffer, 0, bitmapData.length); return true; } catch (Exception e) { } return false; } public void freeBitmap() { bitmap = null; bitmapData = null; size = null; srcPath = null; orientation = 0; } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaGlobalListener.java000066400000000000000000000156261516266226600253160ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import java.util.ArrayList; /** * Listener to receive and send global events to the app. * * @see MegaGlobalListenerInterface * @see MegaGlobalListener */ class DelegateMegaGlobalListener extends MegaGlobalListener { MegaApiJava megaApi; MegaGlobalListenerInterface listener; DelegateMegaGlobalListener(MegaApiJava megaApi, MegaGlobalListenerInterface listener) { this.megaApi = megaApi; this.listener = listener; } MegaGlobalListenerInterface getUserListener() { return listener; } /** * This function is called when there are new or updated contacts in the account. *

    * The SDK retains the ownership of the MegaUserList in the second parameter. * The list and all the MegaUser objects that it contains will be valid until this function returns. * If you want to save the list, use MegaUserList.copy(). * If you want to save only some of the MegaUser objects, use MegaUser.copy() for those objects. * * @param api * API object connected to account. * @param userList * List that contains the new or updated contacts. * @see MegaGlobalListenerInterface#onUsersUpdate(MegaApiJava api, ArrayList users) * @see MegaGlobalListener#onUsersUpdate(MegaApi api, MegaUserList users) */ @Override public void onUsersUpdate(MegaApi api, MegaUserList userList) { if (listener != null) { final ArrayList users = MegaApiJava .userListToArray(userList); megaApi.runCallback(new Runnable() { public void run() { listener.onUsersUpdate(megaApi, users); } }); } } /** * This function is called when there are new or updated user alerts in the account * * The SDK retains the ownership of the MegaUserAlertList in the second parameter. The list and all the * MegaUserAlert objects that it contains will be valid until this function returns. If you want to save the * list, use MegaUserAlertList::copy. If you want to save only some of the MegaUserAlert objects, use MegaUserAlert::copy * for those objects. * * @param api MegaApi object connected to the account * @param userAlertList List that contains the new or updated contacts */ @Override public void onUserAlertsUpdate(MegaApi api, MegaUserAlertList userAlertList){ if (listener != null){ final ArrayList userAlerts = MegaApiJava.userAlertListToArray(userAlertList); megaApi.runCallback(new Runnable() { @Override public void run() { listener.onUserAlertsUpdate(megaApi, userAlerts); } }); } } /** * This function is called when there are new or updated nodes in the account. *

    * When the full account is reloaded or a large number of server notifications arrives at once, * the second parameter will be null. * The SDK retains the ownership of the MegaNodeList in the second parameter. * The list and all the MegaNode objects that it contains will be valid until this function returns. * If you want to save the list, use MegaNodeList.copy(). * If you want to save only some of the MegaNode objects, use MegaNode.copy() for those nodes. * * @param api * API object connected to account. * @param nodeList * List of new or updated Nodes. * @see MegaGlobalListenerInterface#onNodesUpdate(MegaApiJava api, ArrayList nodes) * @see MegaGlobalListener#onNodesUpdate(MegaApi api, MegaNodeList nodes) */ @Override public void onNodesUpdate(MegaApi api, MegaNodeList nodeList) { if (listener != null) { final ArrayList nodes = MegaApiJava .nodeListToArray(nodeList); megaApi.runCallback(new Runnable() { public void run() { listener.onNodesUpdate(megaApi, nodes); } }); } } @Override public void onAccountUpdate(MegaApi api) { if (listener != null) { megaApi.runCallback(new Runnable() { public void run() { listener.onAccountUpdate(megaApi); } }); } } @Override public void onContactRequestsUpdate(MegaApi api, MegaContactRequestList contactRequestList) { if (listener != null) { final ArrayList requests = MegaApiJava.contactRequestListToArray(contactRequestList); megaApi.runCallback(new Runnable() { public void run() { listener.onContactRequestsUpdate(megaApi, requests); } }); } } @Override public void onEvent(MegaApi api, MegaEvent event){ if (listener != null) { final MegaEvent megaEvent = event.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onEvent(megaApi, megaEvent); } }); } } @Override public void onSetsUpdate(MegaApi api, MegaSetList setList) { if (listener != null) { final ArrayList sets = MegaApiJava.megaSetListToArray(setList); megaApi.runCallback((new Runnable() { public void run() { listener.onSetsUpdate(megaApi, sets); } })); } } @Override public void onSetElementsUpdate(MegaApi api, MegaSetElementList elementList) { if (listener != null) { final ArrayList elements = MegaApiJava.megaSetElementListToArray(elementList); megaApi.runCallback((new Runnable() { public void run() { listener.onSetElementsUpdate(megaApi, elements); } })); } } @Override public void onGlobalSyncStateChanged(MegaApi api) { if (listener != null) { megaApi.runCallback((new Runnable() { public void run() { listener.onGlobalSyncStateChanged(megaApi); } })); } } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaListener.java000066400000000000000000000377311516266226600241760ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import java.util.ArrayList; /** * Listener to receive and send events to the app. * * @see MegaListenerInterface * @see MegaListener */ class DelegateMegaListener extends MegaListener { MegaApiJava megaApi; MegaListenerInterface listener; DelegateMegaListener(MegaApiJava megaApi, MegaListenerInterface listener) { this.megaApi = megaApi; this.listener = listener; } MegaListenerInterface getUserListener() { return listener; } /** * This function is called when a request is about to start being processed. *

    * The SDK retains the ownership of the request parameter. Do not use it after this function returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API object that started the request. * @param request * Information about the request. * @see MegaRequestListenerInterface#onRequestStart(MegaApiJava api, MegaRequest request) * @see MegaListener#onRequestStart(MegaApi api, MegaRequest request) */ @Override public void onRequestStart(MegaApi api, MegaRequest request) { if (listener != null) { final MegaRequest megaRequest = request.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestStart(megaApi, megaRequest); } }); } } /** * This function is called when a request has finished. *

    * There will not be more callbacks about this request. The last parameter provides the result of the request. * If the request finished without problems, the error code will be API_OK. The SDK retains the ownership of * the request and error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API object that started the request. * @param request * The MegaRequestType that has finished. * @param e * Error Information. * @see MegaRequestListenerInterface#onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) * @see MegaListener#onRequestFinish(MegaApi api, MegaRequest request, MegaError e) */ @Override public void onRequestFinish(MegaApi api, MegaRequest request, MegaError e) { if (listener != null) { final MegaRequest megaRequest = request.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestFinish(megaApi, megaRequest, megaError); } }); } } /** * This function is called when there is a temporary error processing a request. *

    * The request continues after this callback, so expect more MegaRequestListener.onRequestTemporaryError * or a MegaRequestListener.onRequestFinish callback. The SDK retains the ownership of the request and error * parameters. * Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API object that started the request. * @param request * Information about the request. * @param e * Error Information. * @see MegaRequestListenerInterface#onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) * @see MegaListener#onRequestTemporaryError(MegaApi api, MegaRequest request, MegaError error) */ @Override public void onRequestTemporaryError(MegaApi api, MegaRequest request, MegaError e) { if (listener != null) { final MegaRequest megaRequest = request.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestTemporaryError(megaApi, megaRequest, megaError); } }); } } /** * This function is called when a transfer is about to start being processed. *

    * The SDK retains the ownership of the transfer parameter. * Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API object that started the request. * @param transfer * Information about the transfer. * @see MegaTransferListenerInterface#onTransferStart(MegaApiJava api, MegaTransfer transfer) * @see MegaListener#onTransferStart(MegaApi api, MegaTransfer transfer) */ @Override public void onTransferStart(MegaApi api, MegaTransfer transfer) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferStart(megaApi, megaTransfer); } }); } } /** * This function is called when a transfer has finished. *

    * The SDK retains the ownership of the transfer and error parameters. * Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * There will not be more callbacks about this transfer. * The last parameter provides the result of the transfer. * If the transfer finishes without errors, the error code will be API_OK. * * @param api * API object that started the request. * @param transfer * Information about the transfer. * @param e * Error Information. * @see MegaTransferListenerInterface#onTransferFinish(MegaApiJava api, MegaTransfer transfer, MegaError e) * @see MegaListener#onTransferFinish(MegaApi api, MegaTransfer transfer, MegaError error) */ @Override public void onTransferFinish(MegaApi api, MegaTransfer transfer, MegaError e) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferFinish(megaApi, megaTransfer, megaError); } }); } } /** * This function is called to inform about the progress of a transfer. *

    * The SDK retains the ownership of the transfer parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API object that started the request. * @param transfer * Information about the transfer. * @see MegaTransferListenerInterface#onTransferUpdate(MegaApiJava api, MegaTransfer transfer) * @see MegaListener#onTransferUpdate(MegaApi api, MegaTransfer transfer) */ @Override public void onTransferUpdate(MegaApi api, MegaTransfer transfer) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferUpdate(megaApi, megaTransfer); } }); } } /** * This function is called when there is a temporary error processing a transfer. *

    * The transfer continues after this callback, so expect more MegaTransferListener.onTransferTemporaryError * or a MegaTransferListener.onTransferFinish callback. The SDK retains the ownership of the transfer and * error parameters. Do not use them after this function returns. * * @param api * API object that started the request. * @param transfer * Information about the transfer. * @param e * Error Information. * @see MegaTransferListenerInterface#onTransferTemporaryError(MegaApiJava api, MegaTransfer transfer, MegaError e) * @see MegaListener#onTransferTemporaryError(MegaApi api, MegaTransfer transfer, MegaError error) */ @Override public void onTransferTemporaryError(MegaApi api, MegaTransfer transfer, MegaError e) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferTemporaryError(megaApi, megaTransfer, megaError); } }); } } /** * This function is called when there are new or updated contacts in the account. *

    * The SDK retains the ownership of the MegaUserList in the second parameter. * The list and all the MegaUser objects that it contains will be valid until this function returns. * If you want to save the list, use MegaUserList.copy(). * If you want to save only some of the MegaUser objects, use MegaUser.copy() for those objects. * * @param api * API object that started the request. * @param userList * List that contains new or updated contacts. * @see MegaGlobalListenerInterface#onUsersUpdate(MegaApiJava api, ArrayList users) * @see MegaListener#onUsersUpdate(MegaApi api, MegaUserList users) */ @Override public void onUsersUpdate(MegaApi api, MegaUserList userList) { if (listener != null) { final ArrayList users = MegaApiJava.userListToArray(userList); megaApi.runCallback(new Runnable() { public void run() { listener.onUsersUpdate(megaApi, users); } }); } } /** * This function is called when there are new or updated user alerts in the account * * The SDK retains the ownership of the MegaUserAlertList in the second parameter. The list and all the * MegaUserAlert objects that it contains will be valid until this function returns. If you want to save the * list, use MegaUserAlertList::copy. If you want to save only some of the MegaUserAlert objects, use MegaUserAlert::copy * for those objects. * * @param api MegaApi object connected to the account * @param userAlertList List that contains the new or updated contacts */ @Override public void onUserAlertsUpdate(MegaApi api, final MegaUserAlertList userAlertList){ if (listener != null){ final ArrayList userAlerts = MegaApiJava.userAlertListToArray(userAlertList); megaApi.runCallback(new Runnable() { public void run() { listener.onUserAlertsUpdate(megaApi, userAlerts); } }); } } /** * This function is called when there are new or updated nodes in the account. *

    * When the full account is reloaded or a large number of server notifications arrives at once, * the second parameter will be null. * The SDK retains the ownership of the MegaNodeList in the second parameter. * The list and all the MegaNode objects that it contains will be valid until this function returns. * If you want to save the list, use MegaNodeList.copy(). * If you want to save only some of the MegaNode objects, use MegaNode.copy() for those nodes. * * @param api * API object that started the request. * @param nodeList * List that contains new or updated nodes. * @see MegaGlobalListenerInterface#onNodesUpdate(MegaApiJava api, ArrayList nodes) * @see MegaListener#onNodesUpdate(MegaApi api, MegaNodeList nodes) */ @Override public void onNodesUpdate(MegaApi api, MegaNodeList nodeList) { if (listener != null) { final ArrayList nodes = MegaApiJava.nodeListToArray(nodeList); megaApi.runCallback(new Runnable() { public void run() { listener.onNodesUpdate(megaApi, nodes); } }); } } @Override public void onAccountUpdate(MegaApi api) { if (listener != null) { megaApi.runCallback(new Runnable() { public void run() { listener.onAccountUpdate(megaApi); } }); } } @Override public void onContactRequestsUpdate(MegaApi api, MegaContactRequestList contactRequestList) { if (listener != null) { final ArrayList requests = MegaApiJava.contactRequestListToArray(contactRequestList); megaApi.runCallback(new Runnable() { public void run() { listener.onContactRequestsUpdate(megaApi, requests); } }); } } @Override public void onEvent(MegaApi api, MegaEvent event){ if (listener != null) { final MegaEvent megaEvent = event.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onEvent(megaApi, megaEvent); } }); } } @Override public void onSetsUpdate(MegaApi api, MegaSetList setList){ if (listener != null) { final ArrayList sets = MegaApiJava.megaSetListToArray(setList); megaApi.runCallback(new Runnable() { public void run() { listener.onSetsUpdate(megaApi, sets); } }); } } @Override public void onSetElementsUpdate(MegaApi api, MegaSetElementList elementList){ if (listener != null) { final ArrayList elements = MegaApiJava.megaSetElementListToArray(elementList); megaApi.runCallback(new Runnable() { public void run() { listener.onSetElementsUpdate(megaApi, elements); } }); } } @Override public void onSyncDeleted(MegaApi api, MegaSync sync) { if (listener != null) { final MegaSync megaSync = sync.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onSyncDeleted(megaApi, megaSync); } }); } } @Override public void onSyncStatsUpdated(MegaApi api, MegaSyncStats stats) { if (listener != null) { final MegaSyncStats megaStats = stats.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onSyncStatsUpdated(megaApi, megaStats); } }); } } @Override public void onSyncStateChanged(MegaApi api, MegaSync sync) { if (listener != null) { final MegaSync megaSync = sync.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onSyncStateChanged(megaApi, megaSync); } }); } } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaLogger.java000066400000000000000000000053531516266226600236230ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import java.lang.String; /** * Interface to receive SDK logs. * You can implement this class and pass an object of your subclass to MegaApiJava.setLoggerClass() * to receive SDK logs. You will also have to use MegaApiJava.setLogLevel() to select the level of * the logs that you want to receive. * * @see MegaLoggerInterface * @see MegaLogger */ class DelegateMegaLogger extends MegaLogger { MegaLoggerInterface listener; DelegateMegaLogger(MegaLoggerInterface listener) { this.listener = listener; } MegaLoggerInterface getUserListener() { return listener; } /** * This function will be called with all logs with level <= your selected level of logging. * By default logging level is MegaApi.LOG_LEVEL_INFO. * * @param time * Readable string representing the current time. * The SDK retains the ownership of this string, it will not be valid after this function returns. * @param loglevel * Log level of this message. * Valid values are:
    * - LOG_LEVEL_FATAL = 0.
    * - LOG_LEVEL_ERROR = 1.
    * - LOG_LEVEL_WARNING = 2.
    * - LOG_LEVEL_INFO = 3.
    * - LOG_LEVEL_DEBUG = 4.
    * - LOG_LEVEL_MAX = 5. * @param source * Location where this log was generated. * For logs generated inside the SDK, this will contain the source file and the line of code. * The SDK retains the ownership of this string, it will not be valid after this function returns. * @param message * Log message. * The SDK retains the ownership of this string, it will be valid after this function returns. * @see MegaLoggerInterface#log(String time, int loglevel, String source, String message) * @see MegaLogger#log(String time, int loglevel, String source, String message) */ public void log(String time, int loglevel, String source, String message) { if (listener != null) { listener.log(time, loglevel, source, message); } } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaRequestListener.java000066400000000000000000000154301516266226600255370ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import nz.mega.sdk.MegaApi; import nz.mega.sdk.MegaRequest; /** * Interface to receive information about requests. *

    * All requests allows to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all requests using MegaApi.addRequestListener(). * MegaListener objects can also receive information about requests. * This interface uses MegaRequest objects to provide information about requests. * Please note that not all fields of MegaRequest objects are valid for all requests. * See the documentation about each request to know which fields contain useful information for each one. * * @see MegaRequestListenerInterface * @see MegaRequestListener */ class DelegateMegaRequestListener extends MegaRequestListener { MegaApiJava megaApi; MegaRequestListenerInterface listener; boolean singleListener; DelegateMegaRequestListener(MegaApiJava megaApi, MegaRequestListenerInterface listener, boolean singleListener) { this.megaApi = megaApi; this.listener = listener; this.singleListener = singleListener; } MegaRequestListenerInterface getUserListener() { return listener; } /** * This function is called when a request is about to start being processed. *

    * The SDK retains the ownership of the request parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @see MegaRequestListenerInterface#onRequestStart(MegaApiJava api, MegaRequest request) * @see MegaRequestListener#onRequestStart(MegaApi api, MegaRequest request) */ @Override public void onRequestStart(MegaApi api, MegaRequest request) { if (listener != null) { final MegaRequest megaRequest = request.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestStart(megaApi, megaRequest); } }); } } /** * This function is called to get details about the progress of a request. *

    * Currently, this callback is only used for fetchNodes requests (MegaRequest.TYPE_FETCH_NODES). * The SDK retains the ownership of the request parameter. Do not use it after this function returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @see MegaRequestListenerInterface#onRequestUpdate(MegaApiJava api, MegaRequest request) * @see MegaRequestListener#onRequestUpdate(MegaApi api, MegaRequest request) */ @Override public void onRequestUpdate(MegaApi api, MegaRequest request) { if (listener != null) { final MegaRequest megaRequest = request.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestUpdate(megaApi, megaRequest); } }); } } /** * This function is called when a request has finished. *

    * There will be no further callbacks related to this request. * The MegaError parameter provides the result of the request. * If the request completed without problems, the error code will be API_OK. The SDK retains the ownership * of the request and error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @param e * Error Information. * @see MegaRequestListenerInterface#onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) * @see MegaRequestListener#onRequestFinish(MegaApi api, MegaRequest request, MegaError e) */ @Override public void onRequestFinish(MegaApi api, MegaRequest request, MegaError e) { if (listener != null) { final MegaRequest megaRequest = request.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestFinish(megaApi, megaRequest, megaError); if (singleListener) { megaApi.privateFreeRequestListener(DelegateMegaRequestListener.this); } } }); } } /** * This function is called when there is a temporary error processing a request. *

    * The request continues after this callback, so expect more MegaRequestListener.onRequestTemporaryError * or a MegaRequestListener.onRequestFinish callback. The SDK retains the ownership of the request and * error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @param e * Error Information. * @see MegaRequestListenerInterface#onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) * @see MegaRequestListener#onRequestTemporaryError(MegaApi api, MegaRequest request, MegaError error) */ @Override public void onRequestTemporaryError(MegaApi api, MegaRequest request, MegaError e) { if (listener != null) { final MegaRequest megaRequest = request.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onRequestTemporaryError(megaApi, megaRequest, megaError); } }); } } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaTransferListener.java000066400000000000000000000236461516266226600257030ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import nz.mega.sdk.MegaApi; import nz.mega.sdk.MegaTransfer; /** * Interface to receive information about transfers. *

    * All transfers are able to pass a pointer to an implementation of this interface in the singleListener parameter. * You can also get information about all transfers using MegaApi.addTransferListener(). * MegaListener objects can also receive information about transfers. * * @see MegaTransferListenerInterface * @see MegaTransferListener */ class DelegateMegaTransferListener extends MegaTransferListener { MegaApiJava megaApi; MegaTransferListenerInterface listener; boolean singleListener; DelegateMegaTransferListener(MegaApiJava megaApi, MegaTransferListenerInterface listener, boolean singleListener) { this.megaApi = megaApi; this.listener = listener; this.singleListener = singleListener; } MegaTransferListenerInterface getUserListener() { return listener; } /** * This function is called when a transfer is about to start being processed. *

    * The SDK retains the ownership of the transfer parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @see MegaTransferListenerInterface#onTransferStart(MegaApiJava api, MegaTransfer transfer) * @see MegaTransferListener#onTransferStart(MegaApi api, MegaTransfer transfer) */ @Override public void onTransferStart(MegaApi api, MegaTransfer transfer) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferStart(megaApi, megaTransfer); } }); } } /** * This function is called when a transfer has finished. *

    * The SDK retains the ownership of the transfer and error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * There will not be further callbacks relating to this transfer. The last parameter provides the result of the * transfer. If the transfer finished without problems, the error code will be API_OK. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param e * Error information. * @see MegaTransferListenerInterface#onTransferFinish(MegaApiJava api, MegaTransfer transfer, MegaError e) * @see MegaTransferListener#onTransferFinish(MegaApi api, MegaTransfer transfer, MegaError error) */ @Override public void onTransferFinish(MegaApi api, MegaTransfer transfer, MegaError e) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferFinish(megaApi, megaTransfer, megaError); if (singleListener) { megaApi.privateFreeTransferListener(DelegateMegaTransferListener.this); } } }); } } /** * This function is called to get details about the progress of a transfer. *

    * The SDK retains the ownership of the transfer parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @see MegaTransferListenerInterface#onTransferUpdate(MegaApiJava api, MegaTransfer transfer) * @see MegaTransferListener#onTransferUpdate(MegaApi api, MegaTransfer transfer) */ @Override public void onTransferUpdate(MegaApi api, MegaTransfer transfer) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferUpdate(megaApi, megaTransfer); } }); } } /** * This function is called when there is a temporary error processing a transfer. *

    * The transfer continues after this callback, so expect more MegaTransferListener.onTransferTemporaryError * or a MegaTransferListener.onTransferFinish callback. The SDK retains the ownership of the transfer and * error parameters. Do not use them after this function returns. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param e * Error information. * @see MegaTransferListenerInterface#onTransferTemporaryError(MegaApiJava api, MegaTransfer transfer, MegaError e) * @see MegaTransferListener#onTransferTemporaryError(MegaApi api, MegaTransfer transfer, MegaError error) */ @Override public void onTransferTemporaryError(MegaApi api, MegaTransfer transfer, MegaError e) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); final MegaError megaError = e.copy(); megaApi.runCallback(new Runnable() { public void run() { listener.onTransferTemporaryError(megaApi, megaTransfer, megaError); } }); } } /** * This function is called to provide the last read bytes of streaming downloads. *

    * This function will not be called for non streaming downloads. You can get the same buffer provided by this * function in MegaTransferListener.onTransferUpdate, using MegaTransfer.getLastBytes() and * MegaTransfer.getDeltaSize(). The SDK retains the ownership of the transfer and buffer parameters. * Do not use them after this functions returns. This callback is mainly provided for compatibility with other * programming languages. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param buffer * Buffer with the last read bytes. * @return * Size of the buffer. * @see MegaTransferListenerInterface#onTransferData(MegaApiJava api, MegaTransfer transfer, byte[] buffer) * @see MegaTransferListener#onTransferData(MegaApi api, MegaTransfer transfer, byte[] buffer) */ public boolean onTransferData(MegaApi api, MegaTransfer transfer, byte[] buffer) { if (listener != null) { final MegaTransfer megaTransfer = transfer.copy(); return listener.onTransferData(megaApi, megaTransfer, buffer); } return false; } /** * This function is called to inform about the progress of a folder transfer *

    * The api object is the one created by the application, it will be valid until * the application deletes it. *

    * This callback is only made for folder transfers, and only to the listener for that * transfer, not for any globally registered listeners. The callback is only made * during the scanning phase. *

    * This function can be used to give feedback to the user as to how scanning is progressing, * since scanning may take a while and the application may be showing a modal dialog during * this time. *

    * Note that this function could be called from a variety of threads during the * overall operation, so proper thread safety should be observed. * * @param api MEGASdk object that started the transfer * @param transfer Information about the transfer * @param stage MEGATransferStageScan or a later value in that enum * @param folderCount The count of folders scanned so far * @param createdFolderCount The count of folders created so far (only relevant in MEGATransferStageCreateTree) * @param fileCount The count of files scanned (and fingerprinted) so far. 0 if not in scanning stage * @param currentFolder The path of the folder currently being scanned (nil except in the scan stage) * @param currentFileLeafName The leaft name of the file currently being fingerprinted (can be nil for the first call in a new folder, and when not scanning anymore) */ @Override public void onFolderTransferUpdate(MegaApi api, MegaTransfer transfer, int stage, long folderCount, long createdFolderCount, long fileCount, String currentFolder, String currentFileLeafName) { if (listener != null) { megaApi.runCallback(new Runnable() { public void run() { final MegaTransfer megaTransfer = transfer.copy(); listener.onFolderTransferUpdate(megaApi, megaTransfer, stage, folderCount, createdFolderCount, fileCount, currentFolder, currentFileLeafName); } }); } } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateMegaTreeProcessor.java000066400000000000000000000032261516266226600252000ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; /** * Interface to process node trees. *

    * An implementation of this class can be used to process a node tree passing a pointer to MegaApi.processMegaTree(). * * @see MegaTreeProcessorInterface * @see MegaTreeProcessor */ class DelegateMegaTreeProcessor extends MegaTreeProcessor { MegaApiJava megaApi; MegaTreeProcessorInterface listener; DelegateMegaTreeProcessor(MegaApiJava megaApi, MegaTreeProcessorInterface listener) { this.megaApi = megaApi; this.listener = listener; } /** * Function that will be called for all nodes in a node tree. * * @param node * Node to be processed. * @return * true to continue processing nodes, false to stop. * @see MegaTreeProcessorInterface#processMegaNode(MegaApiJava megaApi, MegaNode node) * @see MegaTreeProcessor#processMegaNode(MegaNode node) */ public boolean processMegaNode(MegaNode node) { if (listener != null) return listener.processMegaNode(megaApi, node); return false; } } sdk-10.11.0/bindings/java/nz/mega/sdk/DelegateOutputMegaTransferListener.java000066400000000000000000000051371516266226600271170ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import java.io.IOException; import java.io.OutputStream; /** * The listener interface for receiving delegateOutputMegaTransfer events. *

    * The class that is interested in processing a delegateOutputMegaTransfer event implements this interface. * The object created with that class is registered with a component using the component's * addDelegateOutputMegaTransferListener method. When the delegateOutputMegaTransfer event occurs, * that object's appropriate method is invoked. * * @see DelegateMegaTransferListener */ public class DelegateOutputMegaTransferListener extends DelegateMegaTransferListener { OutputStream outputStream; /** * Instantiates a new delegate output mega transfer listener. * * @param megaApi * the mega api. * @param outputStream * the output stream. * @param listener * the listener. * @param singleListener * the single listener. */ public DelegateOutputMegaTransferListener(MegaApiJava megaApi, OutputStream outputStream, MegaTransferListenerInterface listener, boolean singleListener) { super(megaApi, listener, singleListener); this.outputStream = outputStream; } /** * Provides the last read bytes of streaming downloads. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param buffer * Buffer with the last read bytes. * @return * true, if successful. * @see DelegateMegaTransferListener#onTransferData(MegaApi api, MegaTransfer transfer, byte[] buffer) */ public boolean onTransferData(MegaApi api, MegaTransfer transfer, byte[] buffer) { if (outputStream != null) { try { outputStream.write(buffer); return true; } catch (IOException e) { } } return false; } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaApiAndroid.java000066400000000000000000000013301516266226600227520ustar00rootroot00000000000000package nz.mega.sdk; import android.os.Handler; import android.os.Looper; public class MegaApiAndroid extends MegaApiJava { static Handler handler = new Handler(Looper.getMainLooper()); public MegaApiAndroid(String appKey, String userAgent, String path) { super(appKey, userAgent, path, new AndroidGfxProcessor()); } /** * WARNING: Do not remove this, this constructor is used by VPN and Password Manager projects. */ public MegaApiAndroid(String appKey, String userAgent, String path, int clientType) { super(appKey, userAgent, path, new AndroidGfxProcessor(), clientType); } @Override void runCallback(Runnable runnable) { handler.post(runnable); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaApiJava.java000066400000000000000000021545071516266226600222740ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import static nz.mega.sdk.MegaSync.SyncRunningState.RUNSTATE_RUNNING; import static nz.mega.sdk.MegaSync.SyncRunningState.RUNSTATE_SUSPENDED; import org.jetbrains.annotations.Nullable; import java.io.OutputStream; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; /** * Java Application Programming Interface (API) to access MEGA SDK services on a MEGA account or shared public folder. *

    * An appKey must be specified to use the MEGA SDK. Generate an appKey for free here:
    * - https://mega.co.nz/#sdk *

    * Save on data usage and start up time by enabling local node caching. This can be enabled by passing a local path * in the constructor. Local node caching prevents the need to download the entire file system each time the MegaApiJava * object is logged in. *

    * To take advantage of local node caching, the application needs to save the session key after login * (MegaApiJava.dumpSession()) and use it to login during the next session. A persistent local node cache will only be * loaded if logging in with a session key. * Local node caching is also recommended in order to enhance security as it prevents the account password from being * stored by the application. *

    * To access MEGA services using the MEGA SDK, an object of this class (MegaApiJava) needs to be created and one of the * MegaApiJava.login() options used to log into a MEGA account or a public folder. If the login request succeeds, * call MegaApiJava.fetchNodes() to get the account's file system from MEGA. Once the file system is retrieved, all other * requests including file management and transfers can be used. *

    * After using MegaApiJava.logout() you can reuse the same MegaApi object to log in to another MEGA account or a public * folder. */ public class MegaApiJava { MegaApi megaApi; MegaGfxProcessor gfxProcessor; void runCallback(Runnable runnable) { runnable.run(); } private static final Set activeRequestListeners = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeTransferListeners = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeGlobalListeners = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeMegaListeners = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeMegaLoggers = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeMegaTreeProcessors = Collections.synchronizedSet(new LinkedHashSet()); private static final Set activeHttpServerListeners = Collections.synchronizedSet(new LinkedHashSet()); /** * INVALID_HANDLE Invalid value for a handle *

    * This value is used to represent an invalid handle. Several MEGA objects can have * a handle but it will never be INVALID_HANDLE. */ public final static long INVALID_HANDLE = ~(long) 0; // Very severe error event that will presumably lead the application to abort. public final static int LOG_LEVEL_FATAL = MegaApi.LOG_LEVEL_FATAL; // Error information but application will continue run. public final static int LOG_LEVEL_ERROR = MegaApi.LOG_LEVEL_ERROR; // Information representing errors in application but application will keep running public final static int LOG_LEVEL_WARNING = MegaApi.LOG_LEVEL_WARNING; // Mainly useful to represent current progress of application. public final static int LOG_LEVEL_INFO = MegaApi.LOG_LEVEL_INFO; // Informational logs, that are useful for developers. Only applicable if DEBUG is defined. public final static int LOG_LEVEL_DEBUG = MegaApi.LOG_LEVEL_DEBUG; public final static int LOG_LEVEL_MAX = MegaApi.LOG_LEVEL_MAX; public final static int ATTR_TYPE_THUMBNAIL = MegaApi.ATTR_TYPE_THUMBNAIL; public final static int ATTR_TYPE_PREVIEW = MegaApi.ATTR_TYPE_PREVIEW; public final static int USER_ATTR_AVATAR = MegaApi.USER_ATTR_AVATAR; public final static int USER_ATTR_FIRSTNAME = MegaApi.USER_ATTR_FIRSTNAME; public final static int USER_ATTR_LASTNAME = MegaApi.USER_ATTR_LASTNAME; public final static int USER_ATTR_AUTHRING = MegaApi.USER_ATTR_AUTHRING; public final static int USER_ATTR_LAST_INTERACTION = MegaApi.USER_ATTR_LAST_INTERACTION; public final static int USER_ATTR_ED25519_PUBLIC_KEY = MegaApi.USER_ATTR_ED25519_PUBLIC_KEY; public final static int USER_ATTR_CU25519_PUBLIC_KEY = MegaApi.USER_ATTR_CU25519_PUBLIC_KEY; public final static int USER_ATTR_KEYRING = MegaApi.USER_ATTR_KEYRING; public final static int USER_ATTR_SIG_RSA_PUBLIC_KEY = MegaApi.USER_ATTR_SIG_RSA_PUBLIC_KEY; public final static int USER_ATTR_SIG_CU255_PUBLIC_KEY = MegaApi.USER_ATTR_SIG_CU255_PUBLIC_KEY; public final static int USER_ATTR_LANGUAGE = MegaApi.USER_ATTR_LANGUAGE; public final static int USER_ATTR_PWD_REMINDER = MegaApi.USER_ATTR_PWD_REMINDER; public final static int USER_ATTR_DISABLE_VERSIONS = MegaApi.USER_ATTR_DISABLE_VERSIONS; public final static int USER_ATTR_CONTACT_LINK_VERIFICATION = MegaApi.USER_ATTR_CONTACT_LINK_VERIFICATION; public final static int USER_ATTR_RICH_PREVIEWS = MegaApi.USER_ATTR_RICH_PREVIEWS; public final static int USER_ATTR_RUBBISH_TIME = MegaApi.USER_ATTR_RUBBISH_TIME; public final static int USER_ATTR_LAST_PSA = MegaApi.USER_ATTR_LAST_PSA; public final static int USER_ATTR_STORAGE_STATE = MegaApi.USER_ATTR_STORAGE_STATE; public final static int USER_ATTR_GEOLOCATION = MegaApi.USER_ATTR_GEOLOCATION; public final static int USER_ATTR_CAMERA_UPLOADS_FOLDER = MegaApi.USER_ATTR_CAMERA_UPLOADS_FOLDER; public final static int USER_ATTR_MY_CHAT_FILES_FOLDER = MegaApi.USER_ATTR_MY_CHAT_FILES_FOLDER; public final static int USER_ATTR_PUSH_SETTINGS = MegaApi.USER_ATTR_PUSH_SETTINGS; public final static int USER_ATTR_ALIAS = MegaApi.USER_ATTR_ALIAS; public final static int USER_ATTR_DEVICE_NAMES = MegaApi.USER_ATTR_DEVICE_NAMES; public final static int USER_ATTR_MY_BACKUPS_FOLDER = MegaApi.USER_ATTR_MY_BACKUPS_FOLDER; public final static int USER_ATTR_APPS_PREFS = MegaApi.USER_ATTR_APPS_PREFS; public final static int USER_ATTR_CC_PREFS = MegaApi.USER_ATTR_CC_PREFS; // deprecated: public final static int USER_ATTR_BACKUP_NAMES = MegaApi.USER_ATTR_BACKUP_NAMES; public final static int USER_ATTR_COOKIE_SETTINGS = MegaApi.USER_ATTR_COOKIE_SETTINGS; public final static int NODE_ATTR_DURATION = MegaApi.NODE_ATTR_DURATION; public final static int NODE_ATTR_COORDINATES = MegaApi.NODE_ATTR_COORDINATES; public final static int NODE_ATTR_LABEL = MegaApi.NODE_ATTR_LABEL; public final static int NODE_ATTR_FAV = MegaApi.NODE_ATTR_FAV; public final static int PAYMENT_METHOD_BALANCE = MegaApi.PAYMENT_METHOD_BALANCE; public final static int PAYMENT_METHOD_PAYPAL = MegaApi.PAYMENT_METHOD_PAYPAL; public final static int PAYMENT_METHOD_ITUNES = MegaApi.PAYMENT_METHOD_ITUNES; public final static int PAYMENT_METHOD_GOOGLE_WALLET = MegaApi.PAYMENT_METHOD_GOOGLE_WALLET; public final static int PAYMENT_METHOD_BITCOIN = MegaApi.PAYMENT_METHOD_BITCOIN; public final static int PAYMENT_METHOD_UNIONPAY = MegaApi.PAYMENT_METHOD_UNIONPAY; public final static int PAYMENT_METHOD_FORTUMO = MegaApi.PAYMENT_METHOD_FORTUMO; public final static int PAYMENT_METHOD_STRIPE = MegaApi.PAYMENT_METHOD_STRIPE; public final static int PAYMENT_METHOD_CREDIT_CARD = MegaApi.PAYMENT_METHOD_CREDIT_CARD; public final static int PAYMENT_METHOD_CENTILI = MegaApi.PAYMENT_METHOD_CENTILI; public final static int PAYMENT_METHOD_PAYSAFE_CARD = MegaApi.PAYMENT_METHOD_PAYSAFE_CARD; public final static int PAYMENT_METHOD_ASTROPAY = MegaApi.PAYMENT_METHOD_ASTROPAY; public final static int PAYMENT_METHOD_RESERVED = MegaApi.PAYMENT_METHOD_RESERVED; public final static int PAYMENT_METHOD_WINDOWS_STORE = MegaApi.PAYMENT_METHOD_WINDOWS_STORE; public final static int PAYMENT_METHOD_TPAY = MegaApi.PAYMENT_METHOD_TPAY; public final static int PAYMENT_METHOD_DIRECT_RESELLER = MegaApi.PAYMENT_METHOD_DIRECT_RESELLER; public final static int PAYMENT_METHOD_ECP = MegaApi.PAYMENT_METHOD_ECP; public final static int PAYMENT_METHOD_SABADELL = MegaApi.PAYMENT_METHOD_SABADELL; public final static int PAYMENT_METHOD_HUAWEI_WALLET = MegaApi.PAYMENT_METHOD_HUAWEI_WALLET; public final static int PAYMENT_METHOD_STRIPE2 = MegaApi.PAYMENT_METHOD_STRIPE2; public final static int PAYMENT_METHOD_WIRE_TRANSFER = MegaApi.PAYMENT_METHOD_WIRE_TRANSFER; public final static int TRANSFER_METHOD_NORMAL = MegaApi.TRANSFER_METHOD_NORMAL; public final static int TRANSFER_METHOD_ALTERNATIVE_PORT = MegaApi.TRANSFER_METHOD_ALTERNATIVE_PORT; public final static int TRANSFER_METHOD_AUTO = MegaApi.TRANSFER_METHOD_AUTO; public final static int TRANSFER_METHOD_AUTO_NORMAL = MegaApi.TRANSFER_METHOD_AUTO_NORMAL; public final static int TRANSFER_METHOD_AUTO_ALTERNATIVE = MegaApi.TRANSFER_METHOD_AUTO_ALTERNATIVE; public final static int PUSH_NOTIFICATION_ANDROID = MegaApi.PUSH_NOTIFICATION_ANDROID; public final static int PUSH_NOTIFICATION_IOS_VOIP = MegaApi.PUSH_NOTIFICATION_IOS_VOIP; public final static int PUSH_NOTIFICATION_IOS_STD = MegaApi.PUSH_NOTIFICATION_IOS_STD; public final static int PASSWORD_STRENGTH_VERYWEAK = MegaApi.PASSWORD_STRENGTH_VERYWEAK; public final static int PASSWORD_STRENGTH_WEAK = MegaApi.PASSWORD_STRENGTH_WEAK; public final static int PASSWORD_STRENGTH_MEDIUM = MegaApi.PASSWORD_STRENGTH_MEDIUM; public final static int PASSWORD_STRENGTH_GOOD = MegaApi.PASSWORD_STRENGTH_GOOD; public final static int PASSWORD_STRENGTH_STRONG = MegaApi.PASSWORD_STRENGTH_STRONG; public final static int RETRY_NONE = MegaApi.RETRY_NONE; public final static int RETRY_CONNECTIVITY = MegaApi.RETRY_CONNECTIVITY; public final static int RETRY_SERVERS_BUSY = MegaApi.RETRY_SERVERS_BUSY; public final static int RETRY_API_LOCK = MegaApi.RETRY_API_LOCK; public final static int RETRY_RATE_LIMIT = MegaApi.RETRY_RATE_LIMIT; public final static int RETRY_UNKNOWN = MegaApi.RETRY_UNKNOWN; public final static int KEEP_ALIVE_CAMERA_UPLOADS = MegaApi.KEEP_ALIVE_CAMERA_UPLOADS; public final static int STORAGE_STATE_UNKNOWN = MegaApi.STORAGE_STATE_UNKNOWN; public final static int STORAGE_STATE_GREEN = MegaApi.STORAGE_STATE_GREEN; public final static int STORAGE_STATE_ORANGE = MegaApi.STORAGE_STATE_ORANGE; public final static int STORAGE_STATE_RED = MegaApi.STORAGE_STATE_RED; public final static int STORAGE_STATE_CHANGE = MegaApi.STORAGE_STATE_CHANGE; public final static int STORAGE_STATE_PAYWALL = MegaApi.STORAGE_STATE_PAYWALL; public final static int BUSINESS_STATUS_EXPIRED = MegaApi.BUSINESS_STATUS_EXPIRED; public final static int BUSINESS_STATUS_INACTIVE = MegaApi.BUSINESS_STATUS_INACTIVE; public final static int BUSINESS_STATUS_ACTIVE = MegaApi.BUSINESS_STATUS_ACTIVE; public final static int BUSINESS_STATUS_GRACE_PERIOD = MegaApi.BUSINESS_STATUS_GRACE_PERIOD; public final static int AFFILIATE_TYPE_INVALID = MegaApi.AFFILIATE_TYPE_INVALID; public final static int AFFILIATE_TYPE_ID = MegaApi.AFFILIATE_TYPE_ID; public final static int AFFILIATE_TYPE_FILE_FOLDER = MegaApi.AFFILIATE_TYPE_FILE_FOLDER; public final static int AFFILIATE_TYPE_CHAT = MegaApi.AFFILIATE_TYPE_CHAT; public final static int AFFILIATE_TYPE_CONTACT = MegaApi.AFFILIATE_TYPE_CONTACT; public final static int CREATE_ACCOUNT = MegaApi.CREATE_ACCOUNT; public final static int RESUME_ACCOUNT = MegaApi.RESUME_ACCOUNT; public final static int CANCEL_ACCOUNT = MegaApi.CANCEL_ACCOUNT; public final static int CREATE_EPLUSPLUS_ACCOUNT = MegaApi.CREATE_EPLUSPLUS_ACCOUNT; public final static int RESUME_EPLUSPLUS_ACCOUNT = MegaApi.RESUME_EPLUSPLUS_ACCOUNT; public final static int ORDER_NONE = MegaApi.ORDER_NONE; public final static int ORDER_DEFAULT_ASC = MegaApi.ORDER_DEFAULT_ASC; public final static int ORDER_DEFAULT_DESC = MegaApi.ORDER_DEFAULT_DESC; public final static int ORDER_SIZE_ASC = MegaApi.ORDER_SIZE_ASC; public final static int ORDER_SIZE_DESC = MegaApi.ORDER_SIZE_DESC; public final static int ORDER_CREATION_ASC = MegaApi.ORDER_CREATION_ASC; public final static int ORDER_CREATION_DESC = MegaApi.ORDER_CREATION_DESC; public final static int ORDER_SHARE_CREATION_ASC = MegaApi.ORDER_SHARE_CREATION_ASC; public final static int ORDER_SHARE_CREATION_DESC = MegaApi.ORDER_SHARE_CREATION_DESC; public final static int ORDER_MODIFICATION_ASC = MegaApi.ORDER_MODIFICATION_ASC; public final static int ORDER_MODIFICATION_DESC = MegaApi.ORDER_MODIFICATION_DESC; public final static int ORDER_LINK_CREATION_ASC = MegaApi.ORDER_LINK_CREATION_ASC; public final static int ORDER_LINK_CREATION_DESC = MegaApi.ORDER_LINK_CREATION_DESC; public final static int ORDER_LABEL_ASC = MegaApi.ORDER_LABEL_ASC; public final static int ORDER_LABEL_DESC = MegaApi.ORDER_LABEL_DESC; public final static int ORDER_FAV_ASC = MegaApi.ORDER_FAV_ASC; public final static int ORDER_FAV_DESC = MegaApi.ORDER_FAV_DESC; public final static int TCP_SERVER_DENY_ALL = MegaApi.TCP_SERVER_DENY_ALL; public final static int TCP_SERVER_ALLOW_ALL = MegaApi.TCP_SERVER_ALLOW_ALL; public final static int TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS = MegaApi.TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS; public final static int TCP_SERVER_ALLOW_LAST_LOCAL_LINK = MegaApi.TCP_SERVER_ALLOW_LAST_LOCAL_LINK; public final static int HTTP_SERVER_DENY_ALL = MegaApi.HTTP_SERVER_DENY_ALL; public final static int HTTP_SERVER_ALLOW_ALL = MegaApi.HTTP_SERVER_ALLOW_ALL; public final static int HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = MegaApi.HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS; public final static int HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = MegaApi.HTTP_SERVER_ALLOW_LAST_LOCAL_LINK; public final static int FILE_TYPE_DEFAULT = MegaApi.FILE_TYPE_DEFAULT; public final static int FILE_TYPE_PHOTO = MegaApi.FILE_TYPE_PHOTO; public final static int FILE_TYPE_AUDIO = MegaApi.FILE_TYPE_AUDIO; public final static int FILE_TYPE_VIDEO = MegaApi.FILE_TYPE_VIDEO; public final static int FILE_TYPE_DOCUMENT = MegaApi.FILE_TYPE_DOCUMENT; public final static int FILE_TYPE_PDF = MegaApi.FILE_TYPE_PDF; public final static int FILE_TYPE_PRESENTATION = MegaApi.FILE_TYPE_PRESENTATION; public final static int FILE_TYPE_ARCHIVE = MegaApi.FILE_TYPE_ARCHIVE; public final static int FILE_TYPE_PROGRAM = MegaApi.FILE_TYPE_PROGRAM; public final static int FILE_TYPE_MISC = MegaApi.FILE_TYPE_MISC; public final static int FILE_TYPE_SPREADSHEET = MegaApi.FILE_TYPE_SPREADSHEET; public final static int FILE_TYPE_ALL_DOCS = MegaApi.FILE_TYPE_ALL_DOCS; public final static int FILE_TYPE_OTHERS = MegaApi.FILE_TYPE_OTHERS; public final static int FILE_TYPE_ALL_VISUAL_MEDIA = MegaApi.FILE_TYPE_ALL_VISUAL_MEDIA; public final static int FILE_TYPE_LAST = MegaApi.FILE_TYPE_LAST; public final static int SEARCH_TARGET_INSHARE = MegaApi.SEARCH_TARGET_INSHARE; public final static int SEARCH_TARGET_OUTSHARE = MegaApi.SEARCH_TARGET_OUTSHARE; public final static int SEARCH_TARGET_PUBLICLINK = MegaApi.SEARCH_TARGET_PUBLICLINK; public final static int SEARCH_TARGET_ROOTNODE = MegaApi.SEARCH_TARGET_ROOTNODE; public final static int SEARCH_TARGET_ALL = MegaApi.SEARCH_TARGET_ALL; public final static int ACCOUNT_NOT_BLOCKED = MegaApi.ACCOUNT_NOT_BLOCKED; public final static int ACCOUNT_BLOCKED_TOS_COPYRIGHT = MegaApi.ACCOUNT_BLOCKED_TOS_COPYRIGHT; public final static int ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = MegaApi.ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT; public final static int ACCOUNT_BLOCKED_SUBUSER_DISABLED = MegaApi.ACCOUNT_BLOCKED_SUBUSER_DISABLED; public final static int ACCOUNT_BLOCKED_SUBUSER_REMOVED = MegaApi.ACCOUNT_BLOCKED_SUBUSER_REMOVED; public final static int ACCOUNT_BLOCKED_VERIFICATION_SMS = MegaApi.ACCOUNT_BLOCKED_VERIFICATION_SMS; public final static int ACCOUNT_BLOCKED_VERIFICATION_EMAIL = MegaApi.ACCOUNT_BLOCKED_VERIFICATION_EMAIL; public final static int BACKUP_TYPE_INVALID = MegaApi.BACKUP_TYPE_INVALID; public final static int BACKUP_TYPE_TWO_WAY_SYNC = MegaApi.BACKUP_TYPE_TWO_WAY_SYNC; public final static int BACKUP_TYPE_UP_SYNC = MegaApi.BACKUP_TYPE_UP_SYNC; public final static int BACKUP_TYPE_DOWN_SYNC = MegaApi.BACKUP_TYPE_DOWN_SYNC; public final static int BACKUP_TYPE_CAMERA_UPLOADS = MegaApi.BACKUP_TYPE_CAMERA_UPLOADS; public final static int BACKUP_TYPE_MEDIA_UPLOADS = MegaApi.BACKUP_TYPE_MEDIA_UPLOADS; public final static int BACKUP_TYPE_BACKUP_UPLOAD = MegaApi.BACKUP_TYPE_BACKUP_UPLOAD; public final static int ADS_DEFAULT = MegaApi.ADS_DEFAULT; public final static int ADS_FORCE_ADS = MegaApi.ADS_FORCE_ADS; public final static int ADS_IGNORE_MEGA = MegaApi.ADS_IGNORE_MEGA; public final static int ADS_IGNORE_COUNTRY = MegaApi.ADS_IGNORE_COUNTRY; public final static int ADS_IGNORE_IP = MegaApi.ADS_IGNORE_IP; public final static int ADS_IGNORE_PRO = MegaApi.ADS_IGNORE_PRO; public final static int ADS_FLAG_IGNORE_ROLLOUT = MegaApi.ADS_FLAG_IGNORE_ROLLOUT; public final static int CLIENT_TYPE_DEFAULT = MegaApi.CLIENT_TYPE_DEFAULT; public final static int CLIENT_TYPE_VPN = MegaApi.CLIENT_TYPE_VPN; public final static int CLIENT_TYPE_PASSWORD_MANAGER = MegaApi.CLIENT_TYPE_PASSWORD_MANAGER; /** * PITAG trigger codes exposed at API level. * * Maps 1:1 with PitagTrigger in types.h. */ public final static char PITAG_TRIGGER_NOT_APPLICABLE = MegaApi.PITAG_TRIGGER_NOT_APPLICABLE; public final static char PITAG_TRIGGER_PICKER = MegaApi.PITAG_TRIGGER_PICKER; public final static char PITAG_TRIGGER_DRAG_AND_DROP = MegaApi.PITAG_TRIGGER_DRAG_AND_DROP; public final static char PITAG_TRIGGER_CAMERA = MegaApi.PITAG_TRIGGER_CAMERA; public final static char PITAG_TRIGGER_SCANNER = MegaApi.PITAG_TRIGGER_SCANNER; public final static char PITAG_TRIGGER_SYNC_ALGORITHM = MegaApi.PITAG_TRIGGER_SYNC_ALGORITHM; public final static char PITAG_TRIGGER_SHARE_FROM_APP = MegaApi.PITAG_TRIGGER_SHARE_FROM_APP; public final static char PITAG_TRIGGER_CAMERA_CAPTURE = MegaApi.PITAG_TRIGGER_CAMERA_CAPTURE; public final static char PITAG_TRIGGER_EXPLORER_EXTENSION = MegaApi.PITAG_TRIGGER_EXPLORER_EXTENSION; public final static char PITAG_TRIGGER_VOICE_RECORDER = MegaApi.PITAG_TRIGGER_VOICE_RECORDER; /** * PITAG target codes exposed at API level. * * Maps 1:1 with PitagTarget in types.h. * Apps uploading to chats should set the appropriate chat target (c, C, or s); * for other uploads keep the default value to avoid interfering with internal logic. */ public final static char PITAG_TARGET_NOT_APPLICABLE = MegaApi.PITAG_TARGET_NOT_APPLICABLE; public final static char PITAG_TARGET_CLOUD_DRIVE = MegaApi.PITAG_TARGET_CLOUD_DRIVE; public final static char PITAG_TARGET_CHAT_1TO1 = MegaApi.PITAG_TARGET_CHAT_1TO1; public final static char PITAG_TARGET_CHAT_GROUP = MegaApi.PITAG_TARGET_CHAT_GROUP; public final static char PITAG_TARGET_NOTE_TO_SELF = MegaApi.PITAG_TARGET_NOTE_TO_SELF; public final static char PITAG_TARGET_INCOMING_SHARE = MegaApi.PITAG_TARGET_INCOMING_SHARE; public final static char PITAG_TARGET_MULTIPLE_CHATS = MegaApi.PITAG_TARGET_MULTIPLE_CHATS; public final static int IMPORT_PASSWORD_SOURCE_GOOGLE = MegaApi.IMPORT_PASSWORD_SOURCE_GOOGLE; MegaApi getMegaApi() { return megaApi; } /** * MegaApi Constructor that allows use of a custom GFX processor. *

    * The SDK attaches thumbnails and previews to all uploaded images. To generate them, it needs a graphics processor. * You can build the SDK with one of the provided built-in graphics processors. If none are available * in your app, you can implement the MegaGfxProcessor interface to provide a custom processor. Please * read the documentation of MegaGfxProcessor carefully to ensure that your implementation is valid. * * @param appKey AppKey of your application. * Generate an AppKey for free here: https://mega.co.nz/#sdk * @param userAgent User agent to use in network requests. * If you pass null to this parameter, a default user agent will be used. * @param basePath Base path to store the local cache. * If you pass null to this parameter, the SDK won't use any local cache. * @param gfxProcessor Image processor. The SDK will use it to generate previews and thumbnails. * If you pass null to this parameter, the SDK will try to use the built-in image processors. */ public MegaApiJava(String appKey, String userAgent, String basePath, MegaGfxProcessor gfxProcessor) { this.gfxProcessor = gfxProcessor; megaApi = new MegaApi(appKey, MegaGfxProvider.createExternalInstance(gfxProcessor), basePath, userAgent); } /** * MegaApi Constructor that allows use of a custom GFX processor & specify client type. *

    * The SDK attaches thumbnails and previews to all uploaded images. To generate them, it needs a graphics processor. * You can build the SDK with one of the provided built-in graphics processors. If none are available * in your app, you can implement the MegaGfxProcessor interface to provide a custom processor. Please * read the documentation of MegaGfxProcessor carefully to ensure that your implementation is valid. * * @param appKey AppKey of your application. * Generate an AppKey for free here: https://mega.co.nz/#sdk * @param userAgent User agent to use in network requests. * If you pass null to this parameter, a default user agent will be used. * @param basePath Base path to store the local cache. * If you pass null to this parameter, the SDK won't use any local cache. * @param gfxProcessor Image processor. The SDK will use it to generate previews and thumbnails. * If you pass null to this parameter, the SDK will try to use the built-in image processors. * @param clientType Client type (default, VPN or Password Manager) enables SDK to function differently * Possible values: * MegaApi::CLIENT_TYPE_DEFAULT = 0 * MegaApi::CLIENT_TYPE_VPN = 1 * MegaApi::CLIENT_TYPE_PASSWORD_MANAGER = 2 * * WARNING: Do not remove this, this constructor is used by VPN and Password Manager projects. */ public MegaApiJava(String appKey, String userAgent, String basePath, MegaGfxProcessor gfxProcessor, int clientType) { this.gfxProcessor = gfxProcessor; megaApi = new MegaApi(appKey, MegaGfxProvider.createExternalInstance(gfxProcessor), basePath, userAgent, 1, clientType); } //****************************************************************************************************/ // LISTENER MANAGEMENT //****************************************************************************************************/ /** * Register a listener to receive all events (requests, transfers, global, synchronization). *

    * You can use MegaApiJava.removeListener() to stop receiving events. * * @param listener Listener that will receive all events (requests, transfers, global, synchronization). */ public void addListener(MegaListenerInterface listener) { megaApi.addListener(createDelegateMegaListener(listener)); } /** * Register a listener to receive all events about requests. *

    * You can use MegaApiJava.removeRequestListener() to stop receiving events. * * @param listener Listener that will receive all events about requests. */ public void addRequestListener(MegaRequestListenerInterface listener) { megaApi.addRequestListener(createDelegateRequestListener(listener, false)); } /** * Register a listener to receive all events about transfers. *

    * You can use MegaApiJava.removeTransferListener() to stop receiving events. * * @param listener Listener that will receive all events about transfers. */ public void addTransferListener(MegaTransferListenerInterface listener) { megaApi.addTransferListener(createDelegateTransferListener(listener, false)); } /** * Register a listener to receive global events. *

    * You can use MegaApiJava.removeGlobalListener() to stop receiving events. * * @param listener Listener that will receive global events. */ public void addGlobalListener(MegaGlobalListenerInterface listener) { megaApi.addGlobalListener(createDelegateGlobalListener(listener)); } /** * Unregister a listener. *

    * This listener won't receive more events. * * @param listener Object that is unregistered. */ public void removeListener(MegaListenerInterface listener) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeMegaListeners) { Iterator it = activeMegaListeners.iterator(); while (it.hasNext()) { DelegateMegaListener delegate = it.next(); if (delegate.getUserListener() == listener) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { megaApi.removeListener(listenersToRemove.get(i)); } } /** * Unregister a MegaRequestListener. *

    * This listener won't receive more events. * * @param listener Object that is unregistered. */ public void removeRequestListener(MegaRequestListenerInterface listener) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeRequestListeners) { Iterator it = activeRequestListeners.iterator(); while (it.hasNext()) { DelegateMegaRequestListener delegate = it.next(); if (delegate.getUserListener() == listener) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { megaApi.removeRequestListener(listenersToRemove.get(i)); } } /** * Unregister a MegaTransferListener. *

    * This listener won't receive more events. * * @param listener Object that is unregistered. */ public void removeTransferListener(MegaTransferListenerInterface listener) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeTransferListeners) { Iterator it = activeTransferListeners.iterator(); while (it.hasNext()) { DelegateMegaTransferListener delegate = it.next(); if (delegate.getUserListener() == listener) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { megaApi.removeTransferListener(listenersToRemove.get(i)); } } /** * Unregister a MegaGlobalListener. *

    * This listener won't receive more events. * * @param listener Object that is unregistered. */ public void removeGlobalListener(MegaGlobalListenerInterface listener) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeGlobalListeners) { Iterator it = activeGlobalListeners.iterator(); while (it.hasNext()) { DelegateMegaGlobalListener delegate = it.next(); if (delegate.getUserListener() == listener) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { megaApi.removeGlobalListener(listenersToRemove.get(i)); } } //****************************************************************************************************/ // UTILS //****************************************************************************************************/ /** * Enable request status monitor to receive EVENT_REQSTAT_PROGRESS events */ public void enableRequestStatusMonitor() { megaApi.enableRequestStatusMonitor(true); } /** * Get an URL to transfer the current session to the webclient *

    * This function creates a new session for the link so logging out in the web client won't * log out the current session. *

    * The associated request type is MegaRequest::TYPE_GET_SESSION_TRANSFER_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - URL to open the desired page with the same account *

    * If the client is logged in, but the account is not fully confirmed (ie. singup not * completed yet), this method will return API_EACCESS. *

    * If the client is not logged in, there won't be any session to transfer, but this method * will still return the MEGA's host (ie. https://mega.app) followed by /#. * * @param path Path inside the MEGA's host that we want to open with the current session. * For example, if you want to open https://mega.app/#pro, the parameter of this function * should be "pro". * @param listener MegaRequestListener to track this request */ public void getSessionTransferURL(String path, MegaRequestListenerInterface listener) { megaApi.getSessionTransferURL(path, createDelegateRequestListener(listener)); } /** * Get an URL to transfer the current session to the webclient *

    * This function creates a new session for the link so logging out in the web client won't * log out the current session. *

    * The associated request type is MegaRequest::TYPE_GET_SESSION_TRANSFER_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - URL to open the desired page with the same account *

    * If the client is logged in, but the account is not fully confirmed (ie. singup not * completed yet), this method will return API_EACCESS. *

    * If the client is not logged in, there won't be any session to transfer, but this method * will still return the MEGA's host (ie. https://mega.app) followed by /#. * * @param path Path inside the MEGA's host that we want to open with the current session. * For example, if you want to open https://mega.app/#pro, the parameter of this function * should be "pro". */ public void getSessionTransferURL(String path) { megaApi.getSessionTransferURL(path); } /** * Converts a Base32-encoded user handle (JID) to a MegaHandle. *

    * * @param base32Handle Base32-encoded handle (JID). * @return User handle. */ public static long base32ToHandle(String base32Handle) { return MegaApi.base32ToHandle(base32Handle); } /** * Converts a Base64-encoded node handle to a MegaHandle. *

    * The returned value can be used to recover a MegaNode using MegaApi::getNodeByHandle * You can revert this operation using MegaApi::handleToBase64 * * @param base64Handle Base64-encoded node handle * @return Node handle */ public static long base64ToHandle(String base64Handle) { return MegaApi.base64ToHandle(base64Handle); } /** * Converts a Base64-encoded user handle to a MegaHandle *

    * You can revert this operation using MegaApi::userHandleToBase64 * * @param base64Handle Base64-encoded node handle * @return User handle */ public static long base64ToUserHandle(String base64Handle) { return MegaApi.base64ToUserHandle(base64Handle); } /** * Converts the handle of a node to a Base64-encoded string *

    * You take the ownership of the returned value * You can revert this operation using MegaApi::base64ToHandle * * @param handle Node handle to be converted * @return Base64-encoded node handle */ public static String handleToBase64(long handle) { return MegaApi.handleToBase64(handle); } /** * Converts a MegaHandle to a Base64-encoded string *

    * You take the ownership of the returned value * You can revert this operation using MegaApi::base64ToUserHandle * * @param handle User handle to be converted * @return Base64-encoded user handle */ public static String userHandleToBase64(long handle) { return MegaApi.userHandleToBase64(handle); } /** * Add entropy to internal random number generators *

    * It's recommended to call this function with random data specially to * enhance security, * * @param data Byte array with random data * @param size Size of the byte array (in bytes) */ public void addEntropy(String data, long size) { megaApi.addEntropy(data, size); } /** * Reconnect and retry all transfers. */ public void reconnect() { megaApi.retryPendingConnections(true, true); } /** * Retry all pending requests. *

    * When requests fails they wait some time before being retried. That delay grows exponentially if the request * fails again. For this reason, and since this request is very lightweight, it's recommended to call it with * the default parameters on every user interaction with the application. This will prevent very big delays * completing requests. */ public void retryPendingConnections() { megaApi.retryPendingConnections(); } /** * Check if server-side Rubbish Bin autopurging is enabled for the current account *

    * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchNodes to check this value, but only when it follows a login with user and password, * not when an existing session is resumed. * * @return True if this feature is enabled. Otherwise false. */ public boolean serverSideRubbishBinAutopurgeEnabled() { return megaApi.serverSideRubbishBinAutopurgeEnabled(); } /** * Check if the new format for public links is enabled *

    * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchNodes to check this value, but only when it follows a login with user and password, * not when an existing session is resumed. *

    * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if this feature is enabled. Otherwise, false. */ public boolean newLinkFormatEnabled() { return megaApi.newLinkFormatEnabled(); } /** * Check if the logged in account is considered new * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * @return True if account is considered new. Otherwise, false. */ public Boolean accountIsNew() { return megaApi.accountIsNew(); } /** * Get the value of an A/B Test flag *

    * Any value greater than 0 means he flag is active. * * @param flag Name or key of the value to be retrieved, flag should not have ab_ prefix. * * @return A long with the value of the flag. */ public long getABTestValue(String flag) { return megaApi.getABTestValue(flag); } /** * Check if multi-factor authentication can be enabled for the current account. *

    * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchNodes to check this value, but only when it follows a login with user and password, * not when an existing session is resumed. *

    * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if multi-factor authentication can be enabled for the current account, otherwise false. */ public boolean multiFactorAuthAvailable() { return megaApi.multiFactorAuthAvailable(); } /** * Reset the verified phone number for the account logged in. *

    * The associated request type with this request is MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER * If there's no verified phone number associated for the account logged in, the error code * provided in onRequestFinish is MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ public void resetSmsVerifiedPhoneNumber(MegaRequestListenerInterface listener) { megaApi.resetSmsVerifiedPhoneNumber(createDelegateRequestListener(listener)); } /** * Reset the verified phone number for the account logged in. *

    * The associated request type with this request is MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER * If there's no verified phone number associated for the account logged in, the error code * provided in onRequestFinish is MegaError::API_ENOENT. */ public void resetSmsVerifiedPhoneNumber() { megaApi.resetSmsVerifiedPhoneNumber(); } /** * Check if multi-factor authentication is enabled for an account *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email sent in the first parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if multi-factor authentication is enabled or false if it's disabled. * * @param email Email to check * @param listener MegaRequestListener to track this request */ public void multiFactorAuthCheck(String email, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthCheck(email, createDelegateRequestListener(listener)); } /** * Check if multi-factor authentication is enabled for an account *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email sent in the first parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if multi-factor authentication is enabled or false if it's disabled. * * @param email Email to check */ public void multiFactorAuthCheck(String email) { megaApi.multiFactorAuthCheck(email); } /** * Get the secret code of the account to enable multi-factor authentication * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the Base32 secret code needed to configure multi-factor authentication. * * @param listener MegaRequestListener to track this request */ public void multiFactorAuthGetCode(MegaRequestListenerInterface listener) { megaApi.multiFactorAuthGetCode(createDelegateRequestListener(listener)); } /** * Get the secret code of the account to enable multi-factor authentication * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the Base32 secret code needed to configure multi-factor authentication. */ public void multiFactorAuthGetCode() { megaApi.multiFactorAuthGetCode(); } /** * Enable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns true * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ public void multiFactorAuthEnable(String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthEnable(pin, createDelegateRequestListener(listener)); } /** * Enable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns true * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication */ public void multiFactorAuthEnable(String pin) { megaApi.multiFactorAuthEnable(pin); } /** * Disable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ public void multiFactorAuthDisable(String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthDisable(pin, createDelegateRequestListener(listener)); } /** * Disable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. *

    * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication */ public void multiFactorAuthDisable(String pin) { megaApi.multiFactorAuthDisable(pin); } /** * Log in to a MEGA account with multi-factor authentication enabled *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter * - MegaRequest::getText - Returns the third parameter *

    * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ public void multiFactorAuthLogin(String email, String password, String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthLogin(email, password, pin, createDelegateRequestListener(listener)); } /** * Log in to a MEGA account with multi-factor authentication enabled *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter * - MegaRequest::getText - Returns the third parameter *

    * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password * @param pin Pin code for multi-factor authentication */ public void multiFactorAuthLogin(String email, String password, String pin) { megaApi.multiFactorAuthLogin(email, password, pin); } /** * Change the password of a MEGA account with multi-factor authentication enabled *

    * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * - MegaRequest::getText - Returns the pin code for multi-factor authentication * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ public void multiFactorAuthChangePassword(String oldPassword, String newPassword, String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthChangePassword(oldPassword, newPassword, pin, createDelegateRequestListener(listener)); } /** * Change the password of a MEGA account with multi-factor authentication enabled *

    * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * - MegaRequest::getText - Returns the pin code for multi-factor authentication * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password * @param pin Pin code for multi-factor authentication */ public void multiFactorAuthChangePassword(String oldPassword, String newPassword, String pin) { megaApi.multiFactorAuthChangePassword(oldPassword, newPassword, pin); } /** * Initialize the change of the email address associated to an account with multi-factor authentication enabled. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getText - Returns the pin code for multi-factor authentication *

    * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param email The new email to be associated to the account. * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ public void multiFactorAuthChangeEmail(String email, String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthChangeEmail(email, pin, createDelegateRequestListener(listener)); } /** * Initialize the change of the email address associated to an account with multi-factor authentication enabled. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getText - Returns the pin code for multi-factor authentication *

    * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param email The new email to be associated to the account. * @param pin Pin code for multi-factor authentication */ public void multiFactorAuthChangeEmail(String email, String pin) { megaApi.multiFactorAuthChangeEmail(email, pin); } /** * Initialize the cancellation of an account. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CANCEL_LINK. *

    * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getText - Returns the pin code for multi-factor authentication *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request * @see MegaApi::confirmCancelAccount */ public void multiFactorAuthCancelAccount(String pin, MegaRequestListenerInterface listener) { megaApi.multiFactorAuthCancelAccount(pin, createDelegateRequestListener(listener)); } /** * Initialize the cancellation of an account. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CANCEL_LINK. *

    * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getText - Returns the pin code for multi-factor authentication *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param pin Pin code for multi-factor authentication * @see MegaApi::confirmCancelAccount */ public void multiFactorAuthCancelAccount(String pin) { megaApi.multiFactorAuthCancelAccount(pin); } /** * Fetch details related to time zones and the current default *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_TIMEZONE. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaTimeZoneDetails - Returns details about timezones and the current default * * @param listener MegaRequestListener to track this request */ void fetchTimeZone(MegaRequestListenerInterface listener) { megaApi.fetchTimeZone(createDelegateRequestListener(listener)); } /** * Fetch details related to time zones and the current default *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_TIMEZONE. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaTimeZoneDetails - Returns details about timezones and the current default */ void fetchTimeZone() { megaApi.fetchTimeZone(); } /** * Log in to a MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter *

    * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password * @param listener MegaRequestListener to track this request */ public void login(String email, String password, MegaRequestListenerInterface listener) { megaApi.login(email, password, createDelegateRequestListener(listener)); } /** * Log in to a MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter *

    * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password */ public void login(String email, String password) { megaApi.login(email, password); } /** * Log in to a public folder using a folder link *

    * After a successful login, you should call MegaApi::fetchNodes to get filesystem and * start working with the folder. *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the string "FOLDER" * - MegaRequest::getLink - Returns the public link to the folder * * @param megaFolderLink Public link to a folder in MEGA * @param listener MegaRequestListener to track this request */ public void loginToFolder(String megaFolderLink, MegaRequestListenerInterface listener) { megaApi.loginToFolder(megaFolderLink, createDelegateRequestListener(listener)); } /** * Log in to a public folder using a folder link *

    * After a successful login, you should call MegaApi::fetchNodes to get filesystem and * start working with the folder. *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the string "FOLDER" * - MegaRequest::getLink - Returns the public link to the folder * * @param megaFolderLink Public link to a folder in MEGA */ public void loginToFolder(String megaFolderLink) { megaApi.loginToFolder(megaFolderLink); } /** * Log in to a MEGA account using a session key *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session key * * @param session Session key previously dumped with MegaApi::dumpSession * @param listener MegaRequestListener to track this request */ public void fastLogin(String session, MegaRequestListenerInterface listener) { megaApi.fastLogin(session, createDelegateRequestListener(listener)); } /** * Log in to a MEGA account using a session key *

    * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session key * * @param session Session key previously dumped with MegaApi::dumpSession */ public void fastLogin(String session) { megaApi.fastLogin(session); } /** * Close a MEGA session. *

    * All clients using this session will be automatically logged out. *

    * You can get session information using MegaApiJava.getExtendedAccountDetails(). * Then use MegaAccountDetails.getNumSessions and MegaAccountDetails.getSession * to get session info. * MegaAccountSession.getHandle provides the handle that this function needs. *

    * If you use mega.INVALID_HANDLE, all sessions except the current one will be closed. * * @param sessionHandle of the session. Use mega.INVALID_HANDLE to cancel all sessions except the current one. * @param listener MegaRequestListenerInterface to track this request. */ public void killSession(long sessionHandle, MegaRequestListenerInterface listener) { megaApi.killSession(sessionHandle, createDelegateRequestListener(listener)); } /** * Close a MEGA session *

    * All clients using this session will be automatically logged out. *

    * You can get session information using MegaApi::getExtendedAccountDetails. * Then use MegaAccountDetails::getNumSessions and MegaAccountDetails::getSession * to get session info. * MegaAccountSession::getHandle provides the handle that this function needs. *

    * If you use mega::INVALID_HANDLE, all sessions except the current one will be closed * * @param sessionHandle Handle of the session. Use mega::INVALID_HANDLE to cancel all sessions except the current one */ public void killSession(long sessionHandle) { megaApi.killSession(sessionHandle); } /** * Get data about the logged account *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the name of the logged user * - MegaRequest::getPassword - Returns the the public RSA key of the account, Base64-encoded * - MegaRequest::getPrivateKey - Returns the private RSA key of the account, Base64-encoded * * @param listener MegaRequestListener to track this request */ public void getUserData(MegaRequestListenerInterface listener) { megaApi.getUserData(createDelegateRequestListener(listener)); } /** * Get data about the logged account *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the name of the logged user * - MegaRequest::getPassword - Returns the the public RSA key of the account, Base64-encoded * - MegaRequest::getPrivateKey - Returns the private RSA key of the account, Base64-encoded */ public void getUserData() { megaApi.getUserData(); } /** * Get data about a contact *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the contact, Base64-encoded * * @param user Contact to get the data * @param listener MegaRequestListener to track this request */ public void getUserData(MegaUser user, MegaRequestListenerInterface listener) { megaApi.getUserData(user, createDelegateRequestListener(listener)); } /** * Get data about a contact *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the contact, Base64-encoded * * @param user Contact to get the data */ public void getUserData(MegaUser user) { megaApi.getUserData(user); } /** * Get information about a MEGA user *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email or the Base64 handle of the user, * depending on the value provided as user parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the user, Base64-encoded * * @param user Email or Base64 handle of the user * @param listener MegaRequestListener to track this request */ public void getUserData(String user, MegaRequestListenerInterface listener) { megaApi.getUserData(user, createDelegateRequestListener(listener)); } /** * Get information about a MEGA user *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email or the Base64 handle of the user, * depending on the value provided as user parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the user, Base64-encoded * * @param user Email or Base64 handle of the user */ public void getUserData(String user) { megaApi.getUserData(user); } /** * Fetch miscellaneous flags when not logged in *

    * The associated request type with this request is MegaRequest::TYPE_GET_MISC_FLAGS. *

    * When onRequestFinish is called with MegaError::API_OK, the miscellaneous flags are available. * If you are logged in into an account, the error code provided in onRequestFinish is * MegaError::API_EACCESS. * * @param listener MegaRequestListener to track this request * @see MegaApi::multiFactorAuthAvailable * @see MegaApi::newLinkFormatEnabled * @see MegaApi::smsAllowedState */ public void getMiscFlags(MegaRequestListenerInterface listener) { megaApi.getMiscFlags(createDelegateRequestListener(listener)); } /** * Fetch miscellaneous flags when not logged in *

    * The associated request type with this request is MegaRequest::TYPE_GET_MISC_FLAGS. *

    * When onRequestFinish is called with MegaError::API_OK, the miscellaneous flags are available. * If you are logged in into an account, the error code provided in onRequestFinish is * MegaError::API_EACCESS. * * @see MegaApi::multiFactorAuthAvailable * @see MegaApi::newLinkFormatEnabled * @see MegaApi::smsAllowedState */ public void getMiscFlags() { megaApi.getMiscFlags(); } /** * Returns the current session key *

    * You have to be logged in to get a valid session key. Otherwise, * this function returns NULL. *

    * You take the ownership of the returned value. * * @return Current session key */ @Nullable public String dumpSession() { return megaApi.dumpSession(); } /** * Get an authentication token that can be used to identify the user account *

    * If this MegaApi object is not logged into an account, this function will return NULL *

    * The value returned by this function can be used in other instances of MegaApi * thanks to the function MegaApi::setAccountAuth. *

    * You take the ownership of the returned value * * @return Authentication token */ @Nullable public String getAccountAuth() { return megaApi.getAccountAuth(); } /** * Use an authentication token to identify an account while accessing public folders *

    * This function is useful to preserve the PRO status when a public folder is being * used. The identifier will be sent in all API requests made after the call to this function. *

    * To stop using the current authentication token, it's needed to explicitly call * this function with NULL as parameter. Otherwise, the value set would continue * being used despite this MegaApi object is logged in or logged out. *

    * It's recommended to call this function before the usage of MegaApi::loginToFolder * * @param auth Authentication token used to identify the account of the user. * You can get it using MegaApi::getAccountAuth with an instance of MegaApi logged into * an account. */ public void setAccountAuth(String auth) { megaApi.setAccountAuth(auth); } /** * Initialize the creation of a new MEGA account, with firstname and lastname *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getPassword - Returns the password for the account * - MegaRequest::getName - Returns the firstname of the user * - MegaRequest::getText - Returns the lastname of the user * - MegaRequest::getParamType - Returns the value MegaApi::CREATE_ACCOUNT *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getSessionKey - Returns the session id to resume the process *

    * If this request succeeds, a new ephemeral account will be created for the new user * and a confirmation email will be sent to the specified email address. The app may * resume the create-account process by using MegaApi::resumeCreateAccount. *

    * If an account with the same email already exists, you will get the error code * MegaError::API_EEXIST in onRequestFinish * * @param email Email for the account * @param password Password for the account * @param firstname Firstname of the user * @param lastname Lastname of the user * @param listener MegaRequestListener to track this request */ public void createAccount(String email, String password, String firstname, String lastname, MegaRequestListenerInterface listener) { megaApi.createAccount(email, password, firstname, lastname, createDelegateRequestListener(listener)); } /** * Initialize the creation of a new MEGA account, with firstname and lastname *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getPassword - Returns the password for the account * - MegaRequest::getName - Returns the firstname of the user * - MegaRequest::getText - Returns the lastname of the user * - MegaRequest::getParamType - Returns the value MegaApi::CREATE_ACCOUNT *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getSessionKey - Returns the session id to resume the process *

    * If this request succeeds, a new ephemeral account will be created for the new user * and a confirmation email will be sent to the specified email address. The app may * resume the create-account process by using MegaApi::resumeCreateAccount. *

    * If an account with the same email already exists, you will get the error code * MegaError::API_EEXIST in onRequestFinish * * @param email Email for the account * @param password Password for the account * @param firstname Firstname of the user * @param lastname Lastname of the user */ public void createAccount(String email, String password, String firstname, String lastname) { megaApi.createAccount(email, password, firstname, lastname); } /** * Create Ephemeral++ account *

    * This kind of account allows to join chat links and to keep the session in the device * where it was created. *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the firstname of the user * - MegaRequest::getText - Returns the lastname of the user * - MegaRequest::getParamType - Returns the value MegaApi:CREATE_EPLUSPLUS_ACCOUNT *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getSessionKey - Returns the session id to resume the process *

    * If this request succeeds, a new ephemeral++ account will be created for the new user. * The app may resume the create-account process by using MegaApi::resumeCreateAccountEphemeralPlusPlus. * * @param firstname Firstname of the user * @param lastname Lastname of the user * @param listener MegaRequestListener to track this request * @implSpec This account should be confirmed in same device it was created */ public void createEphemeralAccountPlusPlus(String firstname, String lastname, MegaRequestListenerInterface listener) { megaApi.createEphemeralAccountPlusPlus(firstname, lastname, createDelegateRequestListener(listener)); } /** * Resume a registration process *

    * When a user begins the account registration process by calling MegaApi::createAccount, * an ephemeral account is created. *

    * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see MegaApi::sendSignupLink) and also to receive notifications in case the * user confirms the account using another client (MegaGlobalListener::onAccountUpdate or * MegaListener::onAccountUpdate). It is also possible to cancel the registration process by * MegaApi::cancelCreateAccount, which invalidates the signup link associated to the ephemeral * session (the session will be still valid). *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session id to resume the process * - MegaRequest::getParamType - Returns the value MegaApi::RESUME_ACCOUNT *

    * In case the account is already confirmed, the associated request will fail with * error MegaError::API_EARGS. * * @param sid Session id valid for the ephemeral account (@see MegaApi::createAccount) * @param listener MegaRequestListener to track this request */ public void resumeCreateAccount(String sid, MegaRequestListenerInterface listener) { megaApi.resumeCreateAccount(sid, createDelegateRequestListener(listener)); } /** * Resume a registration process *

    * When a user begins the account registration process by calling MegaApi::createAccount, * an ephemeral account is created. *

    * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see MegaApi::sendSignupLink) and also to receive notifications in case the * user confirms the account using another client (MegaGlobalListener::onAccountUpdate or * MegaListener::onAccountUpdate). It is also possible to cancel the registration process by * MegaApi::cancelCreateAccount, which invalidates the signup link associated to the ephemeral * session (the session will be still valid). *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session id to resume the process * - MegaRequest::getParamType - Returns the value MegaApi::RESUME_ACCOUNT *

    * In case the account is already confirmed, the associated request will fail with * error MegaError::API_EARGS. * * @param sid Session id valid for the ephemeral account (@see MegaApi::createAccount) */ public void resumeCreateAccount(String sid) { megaApi.resumeCreateAccount(sid); } /** * Sends the confirmation email for a new account *

    * This function is useful to send the confirmation link again or to send it to a different * email address, in case the user mistyped the email at the registration form. It can only * be used after a successful call to MegaApi::createAccount or MegaApi::resumeCreateAccount. *

    * The associated request type with this request is MegaRequest::TYPE_SEND_SIGNUP_LINK. * * @param email Email for the account * @param name Full name of the user (firstname + lastname) * @param listener MegaRequestListener to track this request */ public void resendSignupLink(String email, String name, MegaRequestListenerInterface listener) { megaApi.resendSignupLink(email, name, createDelegateRequestListener(listener)); } /** * Get information about a confirmation link or a new signup link *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_SIGNUP_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the confirmation link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getName - Returns the name associated with the link (available only for confirmation links) * - MegaRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false *

    * If MegaRequest::getFlag returns true, the account was automatically confirmed and it's not needed * to call MegaApi::confirmAccount. If it returns false, it's needed to call MegaApi::confirmAccount * as usual. New accounts (V2, starting from April 2018) do not require a confirmation with the password, * but old confirmation links (V1) require it, so it's needed to check that parameter in onRequestFinish * to know how to proceed. *

    * If already logged-in into a different account, you will get the error code MegaError::API_EACCESS * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MegaError::API_EEXPIRED in onRequestFinish. * In both cases, the MegaRequest::getEmail will return the email of the account that was attempted * to confirm, and the MegaRequest::getName will return the name. * * @param link Confirmation link (confirm) or new signup link (newsignup) * @param listener MegaRequestListener to track this request */ public void querySignupLink(String link, MegaRequestListenerInterface listener) { megaApi.querySignupLink(link, createDelegateRequestListener(listener)); } /** * Get information about a confirmation link or a new signup link *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_SIGNUP_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the confirmation link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getName - Returns the name associated with the link (available only for confirmation links) * - MegaRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false *

    * If MegaRequest::getFlag returns true, the account was automatically confirmed and it's not needed * to call MegaApi::confirmAccount. If it returns false, it's needed to call MegaApi::confirmAccount * as usual. New accounts (V2, starting from April 2018) do not require a confirmation with the password, * but old confirmation links (V1) require it, so it's needed to check that parameter in onRequestFinish * to know how to proceed. *

    * If already logged-in into a different account, you will get the error code MegaError::API_EACCESS * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MegaError::API_EEXPIRED in onRequestFinish. * In both cases, the MegaRequest::getEmail will return the email of the account that was attempted * to confirm, and the MegaRequest::getName will return the name. * * @param link Confirmation link (confirm) or new signup link (newsignup) */ public void querySignupLink(String link) { megaApi.querySignupLink(link); } /** * Initialize the reset of the existing password, with and without the Master Key. *

    * The associated request type with this request is MegaRequest::TYPE_GET_RECOVERY_LINK. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getFlag - Returns whether the user has a backup of the master key or not. *

    * If this request succeeds, a recovery link will be sent to the user. * If no account is registered under the provided email, you will get the error code * MegaError::API_ENOENT in onRequestFinish * * @param email Email used to register the account whose password wants to be reset. * @param hasMasterKey True if the user has a backup of the master key. Otherwise, false. * @param listener MegaRequestListener to track this request */ public void resetPassword(String email, boolean hasMasterKey, MegaRequestListenerInterface listener) { megaApi.resetPassword(email, hasMasterKey, createDelegateRequestListener(listener)); } /** * Get information about a recovery link created by MegaApi::resetPassword. *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getFlag - Return whether the link requires masterkey to reset password. * * @param link Recovery link (recover) * @param listener MegaRequestListener to track this request */ public void queryResetPasswordLink(String link, MegaRequestListenerInterface listener) { megaApi.queryResetPasswordLink(link, createDelegateRequestListener(listener)); } /** * Set a new password for the account pointed by the recovery link. *

    * Recovery links are created by calling MegaApi::resetPassword and may or may not * require to provide the Master Key. *

    * The associated request type with this request is MegaRequest::TYPE_CONFIRM_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link * - MegaRequest::getPassword - Returns the new password * - MegaRequest::getPrivateKey - Returns the Master Key, when provided *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getFlag - Return whether the link requires masterkey to reset password. *

    * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link The recovery link sent to the user's email address. * @param newPwd The new password to be set. * @param masterKey Base64-encoded string containing the master key (optional). * @param listener MegaRequestListener to track this request * @see MegaApi::queryResetPasswordLink and the flag of the MegaRequest::TYPE_QUERY_RECOVERY_LINK in there. */ public void confirmResetPassword(String link, String newPwd, String masterKey, MegaRequestListenerInterface listener) { megaApi.confirmResetPassword(link, newPwd, masterKey, createDelegateRequestListener(listener)); } /** * Initialize the cancellation of an account. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CANCEL_LINK. *

    * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param listener MegaRequestListener to track this request * @see MegaApi::confirmCancelAccount */ public void cancelAccount(MegaRequestListenerInterface listener) { megaApi.cancelAccount(createDelegateRequestListener(listener)); } /** * Get information about a cancel link created by MegaApi::cancelAccount. *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the cancel link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * @param link Cancel link (cancel) * @param listener MegaRequestListener to track this request */ public void queryCancelLink(String link, MegaRequestListenerInterface listener) { megaApi.queryCancelLink(link, createDelegateRequestListener(listener)); } /** * Effectively parks the user's account without creating a new fresh account. *

    * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. *

    * The associated request type with this request is MegaRequest::TYPE_CONFIRM_CANCEL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link * - MegaRequest::getPassword - Returns the new password *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * @param link Cancellation link sent to the user's email address; * @param pwd Password for the account. * @param listener MegaRequestListener to track this request */ public void confirmCancelAccount(String link, String pwd, MegaRequestListenerInterface listener) { megaApi.confirmCancelAccount(link, pwd, createDelegateRequestListener(listener)); } /** * Allow to resend the verification email for Weak Account Protection *

    * The verification email will be resent to the same address as it was previously sent to. *

    * This function can be called if the the reason for being blocked is: * 700: the account is suspended for Weak Account Protection. *

    * If the logged in account is not suspended or is suspended for some other reason, * onRequestFinish will be called with the error code MegaError::API_EACCESS. *

    * If the logged in account has not been sent the unlock email before, * onRequestFinish will be called with the error code MegaError::API_EARGS. *

    * If the logged in account has already sent the unlock email and until it's available again, * onRequestFinish will be called with the error code MegaError::API_ETEMPUNAVAIL. * * @param listener MegaRequestListener to track this request */ public void resendVerificationEmail(MegaRequestListenerInterface listener) { megaApi.resendVerificationEmail(createDelegateRequestListener(listener)); } /** * Allow to resend the verification email for Weak Account Protection *

    * The verification email will be resent to the same address as it was previously sent to. *

    * This function can be called if the the reason for being blocked is: * 700: the account is suspended for Weak Account Protection. *

    * If the logged in account is not suspended or is suspended for some other reason, * onRequestFinish will be called with the error code MegaError::API_EACCESS. *

    * If the logged in account has not been sent the unlock email before, * onRequestFinish will be called with the error code MegaError::API_EARGS. *

    * If the logged in account has already sent the unlock email and until it's available again, * onRequestFinish will be called with the error code MegaError::API_ETEMPUNAVAIL. */ public void resendVerificationEmail() { megaApi.resendVerificationEmail(); } /** * Initialize the change of the email address associated to the account. *

    * The associated request type with this request is MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getEmail - Returns the email for the account *

    * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param email The new email to be associated to the account. * @param listener MegaRequestListener to track this request */ public void changeEmail(String email, MegaRequestListenerInterface listener) { megaApi.changeEmail(email, createDelegateRequestListener(listener)); } /** * Get information about a change-email link created by MegaApi::changeEmail. *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link *

    * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link Change-email link (verify) * @param listener MegaRequestListener to track this request */ public void queryChangeEmailLink(String link, MegaRequestListenerInterface listener) { megaApi.queryChangeEmailLink(link, createDelegateRequestListener(listener)); } /** * Effectively changes the email address associated to the account. *

    * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). *

    * The associated request type with this request is MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link * - MegaRequest::getPassword - Returns the password *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link *

    * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link Change-email link sent to the user's email address. * @param pwd Password for the account. * @param listener MegaRequestListener to track this request */ public void confirmChangeEmail(String link, String pwd, MegaRequestListenerInterface listener) { megaApi.confirmChangeEmail(link, pwd, createDelegateRequestListener(listener)); } /** * Set proxy settings *

    * The SDK will start using the provided proxy settings as soon as this function returns. * * @param proxySettings Proxy settings * @see MegaProxy */ public void setProxySettings(MegaProxy proxySettings) { megaApi.setProxySettings(proxySettings); } /** * Try to detect the system's proxy settings *

    * Automatic proxy detection is currently supported on Windows only. * On other platforms, this function will return a MegaProxy object * of type MegaProxy::PROXY_NONE *

    * You take the ownership of the returned value. * * @return MegaProxy object with the detected proxy settings */ @Nullable public MegaProxy getAutoProxySettings() { return megaApi.getAutoProxySettings(); } /** * Check if the MegaApi object is logged in * * @return 0 if not logged in, Otherwise, a number > 0 */ public int isLoggedIn() { return megaApi.isLoggedIn(); } /** * Check if we are logged in into an Ephemeral account ++ * * @return true if logged into an Ephemeral account ++, Otherwise return false */ public boolean isEphemeralPlusPlus() { return megaApi.isEphemeralPlusPlus(); } /** * Check the reason of being blocked. *

    * The associated request type with this request is MegaRequest::TYPE_WHY_AM_I_BLOCKED. *

    * This request can be sent internally at anytime (whenever an account gets blocked), so * a MegaGlobalListener should process the result, show the reason and logout. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the reason string (in English) * - MegaRequest::getNumber - Returns the reason code. Possible values: * - MegaApi::ACCOUNT_NOT_BLOCKED = 0 * Account is not blocked in any way. *

    * - MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT = 200 * Suspension only for multiple copyright violations. *

    * - MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = 300 * Suspension message for any type of suspension, but copyright suspension. *

    * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED = 400 * Subuser of the business account has been disabled. *

    * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED = 401 * Subuser of business account has been removed. *

    * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS = 500 * The account is temporary blocked and needs to be verified by an SMS code. *

    * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL = 700 * The account is temporary blocked and needs to be verified by email (Weak Account Protection). *

    * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. * * @param listener MegaRequestListener to track this request */ public void whyAmIBlocked(MegaRequestListenerInterface listener) { megaApi.whyAmIBlocked(createDelegateRequestListener(listener)); } /** * Check the reason of being blocked. *

    * The associated request type with this request is MegaRequest::TYPE_WHY_AM_I_BLOCKED. *

    * This request can be sent internally at anytime (whenever an account gets blocked), so * a MegaGlobalListener should process the result, show the reason and logout. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the reason string (in English) * - MegaRequest::getNumber - Returns the reason code. Possible values: * - MegaApi::ACCOUNT_NOT_BLOCKED = 0 * Account is not blocked in any way. *

    * - MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT = 200 * Suspension only for multiple copyright violations. *

    * - MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = 300 * Suspension message for any type of suspension, but copyright suspension. *

    * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED = 400 * Subuser of the business account has been disabled. *

    * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED = 401 * Subuser of business account has been removed. *

    * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS = 500 * The account is temporary blocked and needs to be verified by an SMS code. *

    * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL = 700 * The account is temporary blocked and needs to be verified by email (Weak Account Protection). *

    * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. */ public void whyAmIBlocked() { megaApi.whyAmIBlocked(); } /** * Create a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_CREATE. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getFlag - Returns the value of \c renew parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Return the handle of the new contact link * * @param renew True to invalidate the previous contact link (if any). * @param listener MegaRequestListener to track this request */ public void contactLinkCreate(boolean renew, MegaRequestListenerInterface listener) { megaApi.contactLinkCreate(renew, createDelegateRequestListener(listener)); } /** * Create a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_CREATE. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getFlag - Returns the value of \c renew parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Return the handle of the new contact link */ public void contactLinkCreate() { megaApi.contactLinkCreate(); } /** * Get information about a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_QUERY. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the userhandle of the contact * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getName - Returns the first name of the contact * - MegaRequest::getText - Returns the last name of the contact * - MegaRequest::getFile - Returns the avatar of the contact (JPG with Base64 encoding) * * @param handle Handle of the contact link to check * @param listener MegaRequestListener to track this request */ public void contactLinkQuery(long handle, MegaRequestListenerInterface listener) { megaApi.contactLinkQuery(handle, createDelegateRequestListener(listener)); } /** * Get information about a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_QUERY. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the userhandle of the contact * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getName - Returns the first name of the contact * - MegaRequest::getText - Returns the last name of the contact * - MegaRequest::getFile - Returns the avatar of the contact (JPG with Base64 encoding) * * @param handle Handle of the contact link to check */ public void contactLinkQuery(long handle) { megaApi.contactLinkQuery(handle); } /** * Delete a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_DELETE. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link * * @param handle Handle of the contact link to delete * If the parameter is INVALID_HANDLE, the active contact link is deleted * @param listener MegaRequestListener to track this request */ public void contactLinkDelete(long handle, MegaRequestListenerInterface listener) { megaApi.contactLinkDelete(handle, createDelegateRequestListener(listener)); } /** * Delete a contact link *

    * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_DELETE. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link * * @param handle Handle of the contact link to delete * If the parameter is INVALID_HANDLE, the active contact link is deleted */ public void contactLinkDelete(long handle) { megaApi.contactLinkDelete(handle); } /** * Get the next PSA (Public Service Announcement) that should be shown to the user *

    * After the PSA has been accepted or dismissed by the user, app should * use MegaApi::setPSA to notify API servers about this event and * do not get the same PSA again in the next call to this function. *

    * The associated request type with this request is MegaRequest::TYPE_GET_PSA. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the id of the PSA (useful to call MegaApi::setPSA later) * Depending on the format of the PSA, the request may additionally return, for the new format: * - MegaRequest::getEmail - Returns the URL (or an empty string)) * - MegaRequest::getName - Returns the title of the PSA * - MegaRequest::getText - Returns the text of the PSA * - MegaRequest::getFile - Returns the URL of the image of the PSA * - MegaRequest::getPassword - Returns the text for the positive button (or an empty string) * - MegaRequest::getLink - Returns the link for the positive button (or an empty string) *

    * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request * @see MegaApi::setPSA */ public void getPSAWithUrl(MegaRequestListenerInterface listener) { megaApi.getPSAWithUrl(createDelegateRequestListener(listener)); } /** * Notify API servers that a PSA (Public Service Announcement) has been already seen *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_LAST_PSA * - MegaRequest::getText - Returns the id passed in the first parameter (as a string) * * @param id Identifier of the PSA * @param listener MegaRequestListener to track this request * @see MegaApi::getPSA */ public void setPSA(int id, MegaRequestListenerInterface listener) { megaApi.setPSA(id, createDelegateRequestListener(listener)); } /** * Notify API servers that a PSA (Public Service Announcement) has been already seen *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_LAST_PSA * - MegaRequest::getText - Returns the id passed in the first parameter (as a string) * * @param id Identifier of the PSA¡ * @see MegaApi::getPSA */ public void setPSA(int id) { megaApi.setPSA(id); } /** * Command to acknowledge user alerts. *

    * Other clients will be notified that alerts to this point have been seen. *

    * The associated request type with this request is MegaRequest::TYPE_USERALERT_ACKNOWLEDGE. * * @param listener MegaRequestListener to track this request * @see MegaApi::getUserAlerts */ public void acknowledgeUserAlerts(MegaRequestListenerInterface listener) { megaApi.acknowledgeUserAlerts(createDelegateRequestListener(listener)); } /** * Command to acknowledge user alerts. *

    * Other clients will be notified that alerts to this point have been seen. *

    * The associated request type with this request is MegaRequest::TYPE_USERALERT_ACKNOWLEDGE. * * @see MegaApi::getUserAlerts */ public void acknowledgeUserAlerts() { megaApi.acknowledgeUserAlerts(); } /** * Returns the email of the currently open account *

    * If the MegaApi object isn't logged in or the email isn't available, * this function returns NULL *

    * You take the ownership of the returned value * * @return Email of the account */ @Nullable public String getMyEmail() { return megaApi.getMyEmail(); } /** * Returns the user handle of the currently open account *

    * If the MegaApi object isn't logged in, * this function returns NULL *

    * You take the ownership of the returned value * * @return User handle of the account */ @Nullable public String getMyUserHandle() { return megaApi.getMyUserHandle(); } /** * Returns the user handle of the currently open account *

    * If the MegaApi object isn't logged in, * this function returns INVALID_HANDLE * * @return User handle of the account */ public long getMyUserHandleBinary() { return megaApi.getMyUserHandleBinary(); } /** * Get the MegaUser of the currently open account *

    * If the MegaApi object isn't logged in, this function returns NULL. *

    * You take the ownership of the returned value * * @return MegaUser of the currently open account, otherwise NULL * @implSpec The visibility of your own user is undefined and shouldn't be used. */ @Nullable public MegaUser getMyUser() { return megaApi.getMyUser(); } /** * Returns whether MEGA Achievements are enabled for the open account * * @return True if enabled, false otherwise. */ public boolean isAchievementsEnabled() { return megaApi.isAchievementsEnabled(); } /** * Check if the account is a business account. * * @return returns true if it's a business account, otherwise false */ public boolean isBusinessAccount() { return megaApi.isBusinessAccount(); } /** * Check if the account is a master account. *

    * When a business account is a sub-user, not the master, some user actions will be blocked. * In result, the API will return the error code MegaError::API_EMASTERONLY. Some examples of * requests that may fail with this error are: * - MegaApi::cancelAccount * - MegaApi::changeEmail * - MegaApi::remove * - MegaApi::removeVersion * * @return returns true if it's a master account, false if it's a sub-user account */ public boolean isMasterBusinessAccount() { return megaApi.isMasterBusinessAccount(); } /** * Check if the business account is active or not. *

    * When a business account is not active, some user actions will be blocked. In result, the API * will return the error code MegaError::API_EBUSINESSPASTDUE. Some examples of requests * that may fail with this error are: * - MegaApi::startDownload * - MegaApi::startUpload * - MegaApi::copyNode * - MegaApi::share * - MegaApi::cleanRubbishBin * * @return returns true if the account is active, otherwise false */ public boolean isBusinessAccountActive() { return megaApi.isBusinessAccountActive(); } /** * Get the status of a business account. * * @return Returns the business account status, possible values: * MegaApi::BUSINESS_STATUS_EXPIRED = -1 * MegaApi::BUSINESS_STATUS_INACTIVE = 0 * MegaApi::BUSINESS_STATUS_ACTIVE = 1 * MegaApi::BUSINESS_STATUS_GRACE_PERIOD = 2 */ public int getBusinessStatus() { return megaApi.getBusinessStatus(); } /** * Returns the deadline to remedy the storage overquota situation *

    * This value is valid only when MegaApi::getUserData has been called after * receiving a callback MegaListener/MegaGlobalListener::onEvent of type * MegaEvent::EVENT_STORAGE, reporting STORAGE_STATE_PAYWALL. * The value will become invalid once the state of storage changes. * * @return Timestamp representing the deadline to remedy the overquota */ public long getOverquotaDeadlineTs() { return megaApi.getOverquotaDeadlineTs(); } /** * Returns when the user was warned about overquota state *

    * This value is valid only when MegaApi::getUserData has been called after * receiving a callback MegaListener/MegaGlobalListener::onEvent of type * MegaEvent::EVENT_STORAGE, reporting STORAGE_STATE_PAYWALL. * The value will become invalid once the state of storage changes. *

    * You take the ownership of the returned value. * * @return MegaIntegerList with the timestamp corresponding to each warning */ public MegaIntegerList getOverquotaWarningsTs() { return megaApi.getOverquotaWarningsTs(); } /** * Check if the password is correct for the current account * * @param password Password to check * @return True if the password is correct for the current account, otherwise false. */ public boolean checkPassword(String password) { return megaApi.checkPassword(password); } /** * Returns the credentials of the currently open account *

    * If the MegaApi object isn't logged in or there's no signing key available, * this function returns NULL *

    * You take the ownership of the returned value. * Use delete [] to free it. * * @return Fingerprint of the signing key of the current account */ public String getMyCredentials() { return megaApi.getMyCredentials(); } /** * Returns the credentials of a given user *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns MegaApi::USER_ATTR_ED25519_PUBLIC_KEY * - MegaRequest::getFlag - Returns true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the credentials in hexadecimal format * * @param user MegaUser of the contact (see MegaApi::getContact) to get the fingerprint * @param listener MegaRequestListener to track this request */ public void getUserCredentials(MegaUser user, MegaRequestListenerInterface listener) { megaApi.getUserCredentials(user, createDelegateRequestListener(listener)); } /** * Checks if credentials are verified for the given user * * @param user MegaUser of the contact whose credentials want to be checked * @return true if verified, false otherwise */ public boolean areCredentialsVerified(MegaUser user) { return megaApi.areCredentialsVerified(user); } /** * Verify credentials of a given user *

    * This function allow to tag credentials of a user as verified. It should be called when the * logged in user compares the fingerprint of the user (provided by an independent and secure * method) with the fingerprint shown by the app (@see MegaApi::getUserCredentials). *

    * The associated request type with this request is MegaRequest::TYPE_VERIFY_CREDENTIALS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns userhandle * * @param user MegaUser of the contact whose credentials want to be verified * @param listener MegaRequestListener to track this request */ public void verifyCredentials(MegaUser user, MegaRequestListenerInterface listener) { megaApi.verifyCredentials(user, createDelegateRequestListener(listener)); } /** * Reset credentials of a given user *

    * Call this function to forget the existing authentication of keys and signatures for a given * user. A full reload of the account will start the authentication process again. *

    * The associated request type with this request is MegaRequest::TYPE_VERIFY_CREDENTIALS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns userhandle * - MegaRequest::getFlag - Returns true * * @param user MegaUser of the contact whose credentials want to be reset * @param listener MegaRequestListener to track this request */ public void resetCredentials(MegaUser user, MegaRequestListenerInterface listener) { megaApi.resetCredentials(user, createDelegateRequestListener(listener)); } /** * Set the active log level *

    * This function sets the log level of the logging system. Any log listener registered by * MegaApi::addLoggerObject will receive logs with the same or a lower level than * the one passed to this function. * * @param logLevel Active log level * These are the valid values for this parameter: * - MegaApi::LOG_LEVEL_FATAL = 0 * - MegaApi::LOG_LEVEL_ERROR = 1 * - MegaApi::LOG_LEVEL_WARNING = 2 * - MegaApi::LOG_LEVEL_INFO = 3 * - MegaApi::LOG_LEVEL_DEBUG = 4 * - MegaApi::LOG_LEVEL_MAX = 5 */ public static void setLogLevel(int logLevel) { MegaApi.setLogLevel(logLevel); } /** * Add a MegaLogger implementation to receive SDK logs *

    * Logs received by this objects depends on the active log level. * By default, it is MegaApi::LOG_LEVEL_INFO. You can change it * using MegaApi::setLogLevel. *

    * You can remove the existing logger by using MegaApi::removeLoggerObject. *

    * In performance mode, it is assumed that this is only called on startup and * not while actively logging. * * @param megaLogger MegaLogger implementation */ public static void addLoggerObject(MegaLoggerInterface megaLogger) { MegaApi.addLoggerObject(createDelegateMegaLogger(megaLogger)); } /** * Remove a MegaLogger implementation to stop receiving SDK logs *

    * If the logger was registered in the past, it will stop receiving log * messages after the call to this function. *

    * In performance mode, it is assumed that this is only called on shutdown and * not while actively logging. * * @param megaLogger Previously registered MegaLogger implementation */ public static void removeLoggerObject(MegaLoggerInterface megaLogger) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeMegaLoggers) { Iterator it = activeMegaLoggers.iterator(); while (it.hasNext()) { DelegateMegaLogger delegate = it.next(); if (delegate.getUserListener() == megaLogger) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { MegaApi.removeLoggerObject(listenersToRemove.get(i)); } } /** * Send a log to the logging system *

    * This log will be received by the active logger object (MegaApi::setLoggerObject) if * the log level is the same or lower than the active log level (MegaApi::setLogLevel) *

    * The third and the fourth parameter are optional. You may want to use __FILE__ and __LINE__ * to complete them. *

    * In performance mode, only logging to console is serialized through a mutex. * Logging to `MegaLogger`s is not serialized and has to be done by the subclasses if needed. * * @param logLevel Log level for this message * @param message Message for the logging system * @param filename Origin of the log message * @param line Line of code where this message was generated */ public static void log(int logLevel, String message, String filename, int line) { MegaApi.log(logLevel, message, filename, line); } /** * Send a log to the logging system *

    * This log will be received by the active logger object (MegaApi::setLoggerObject) if * the log level is the same or lower than the active log level (MegaApi::setLogLevel) *

    * The third is optional. You may want to use __FILE__ to complete it. *

    * In performance mode, only logging to console is serialized through a mutex. * Logging to `MegaLogger`s is not serialized and has to be done by the subclasses if needed. * * @param logLevel Log level for this message * @param message Message for the logging system * @param filename Origin of the log message */ public static void log(int logLevel, String message, String filename) { MegaApi.log(logLevel, message, filename); } /** * Send a log to the logging system *

    * This log will be received by the active logger object (MegaApi::setLoggerObject) if * the log level is the same or lower than the active log level (MegaApi::setLogLevel) *

    * In performance mode, only logging to console is serialized through a mutex. * Logging to `MegaLogger`s is not serialized and has to be done by the subclasses if needed. * * @param logLevel Log level for this message * @param message Message for the logging system */ public static void log(int logLevel, String message) { MegaApi.log(logLevel, message); } /** * Create a folder in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_FOLDER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the handle of the parent folder * - MegaRequest::getName - Returns the name of the new folder *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new folder * - MegaRequest::getFlag - True if target folder (\c parent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param name Name of the new folder * @param parent Parent folder * @param listener MegaRequestListener to track this request */ public void createFolder(String name, MegaNode parent, MegaRequestListenerInterface listener) { megaApi.createFolder(name, parent, createDelegateRequestListener(listener)); } /** * Create a folder in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_FOLDER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the handle of the parent folder * - MegaRequest::getName - Returns the name of the new folder *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new folder * - MegaRequest::getFlag - True if target folder (\c parent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param name Name of the new folder * @param parent Parent folder */ public void createFolder(String name, MegaNode parent) { megaApi.createFolder(name, parent); } /** * Get Password Manager Base folder node from the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_MANAGER_BASE * Valid data in the MegaRequest object received on callbacks: *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the folder *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param listener MegaRequestListener to track this request */ public void getPasswordManagerBase(MegaRequestListenerInterface listener) { megaApi.getPasswordManagerBase(createDelegateRequestListener(listener)); } /** * Returns true if provided MegaHandle is of a Password Node Folder * * A folder is considered a Password Node Folder if Password Manager Base is its * ancestor. * * @param node MegaHandle of the node to check if it is a Password Node Folder * @return true if this node is a Password Node Folder * * @deprecated Moved to isPasswordManagerNodeFolder. */ @Deprecated public boolean isPasswordNodeFolder(long node) { return megaApi.isPasswordNodeFolder(node); } /** * Returns true if provided MegaHandle belongs to a Password Manager Node Folder * * A folder is considered a Password Manager Node Folder if Password Manager Base is its * ancestor, or if the node is the Password Manager Base folder itself. * * @param node MegaHandle of the node to check if it is a Password Manager Node Folder * @return true if this node is a Password Manager Node Folder, false otherwise. * In case node doesn't exists this method will also returns false. */ public boolean isPasswordManagerNodeFolder(long node) { return megaApi.isPasswordManagerNodeFolder(node); }; /** * Create a new Credit Card Node in your Password Manager tree * * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Handle of the parent provided as an argument * - MegaRequest::getName - name for the new Password Node provided as an argument * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_CREDIT_CARD * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new Password Node * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `name` is nullptr or empty string * + If `data` is nullptr * + If `parent` does belong to a passwordNodeFolder * - MegaError::API_EEXIST: * + If there already is a Password Manager Node in the target path with the same name. In * that case, the existing Password Manager Node MegaHandle can be retrieved by * MegaRequest::getNodeHandle. * - MegaError::API_EAPPKEY: * + If the `data` is ill-formed. These are the format requirements for the data in the * CreditCardNodeData object: * - `cardNumber`: Mandatory (not nullptr nor empty string). Can only contain digits (no * spaces or other characters are allowed) * - `cvv`: Optional. If defined, must contain only digits (no spaces or other * characters are allowed) * - `expirationDate`: Optional. If defined must follow exactly the format: MM/YY, where * MM and YY are digits and MM must be between 01 and 12. Some examples: * + Valid inputs: 01/11, 12/25, 05/99. * + Invalid inputs: 13/25, 1/30, 03/5. * - `notes` and `cardHolderName`: Optionals and with no format restrictions. * * @param name Name for the new Credit Card Node * @param data Credit Card Node data for the Credit Card Node * @param parent Parent folder for the new Credit Card Node * @param listener MegaRequestListener to track this request */ public void createCreditCardNode(String name, MegaNode.CreditCardNodeData data, long parent, MegaRequestListenerInterface listener) { megaApi.createCreditCardNode(name, data, parent, createDelegateRequestListener(listener)); } /** * Create a new Password Node in your Password Manager tree *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Handle of the parent provided as an argument * - MegaRequest::getName - name for the new Password Node provided as an argument *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new Password Node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param name Name for the new Password Node * @param data Password Node data for the Password Node * @param parent Parent folder for the new Password Node * @param listener MegaRequestListener to track this request */ public void createPasswordNode(String name, MegaNode.PasswordNodeData data, long parent, MegaRequestListenerInterface listener) { megaApi.createPasswordNode(name, data, parent, createDelegateRequestListener(listener)); } /** * Update a Creadit Card Node in the MEGA account according to the parameters * * The associated request type with this request is MegaRequest::TYPE_UPDATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - handle provided of the Password Node to update * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_CREDIT_CARD * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `newData` is nullptr or empty * + If `node` does not exist or does not belong to a Credit Card Node * - MegaError::API_EAPPKEY: * + If the node ends up in an invalid state after applying the provided updates in * `newData`. See `MegaApi::createCreditCardNode` documentation for more details on the * expected format of each field if specified for the update. * * @param node Node to modify * @param newData New data for the Credit Card Node to update * @param listener MegaRequestListener to track this request */ public void updateCreditCardNode(long node, MegaNode.CreditCardNodeData newData, MegaRequestListenerInterface listener) { megaApi.updateCreditCardNode(node, newData, createDelegateRequestListener(listener)); } /** * Update a Password Node in the MEGA account according to the parameters *

    * The associated request type with this request is MegaRequest::TYPE_UPDATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - handle provided of the Password Node to update *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to modify * @param newData New data for the Password Node to update * @param listener MegaRequestListener to track this request */ public void updatePasswordNode(long node, MegaNode.PasswordNodeData newData, MegaRequestListenerInterface listener) { megaApi.updatePasswordNode(node, newData, createDelegateRequestListener(listener)); } /** * Import passwords from a file into your Password Manager tree * * The associated request type with this request is * MegaRequest::TYPE_IMPORT_PASSWORDS_FROM_FILE. Valid data in the MegaRequest object * received on callbacks: * - MegaRequest::getFile - Path of the file provided as an argument. * - MegaRequest::getParamType - Source of the file provided as an argument (see * fileSource documentation). * - MegaRequest::getParentHandle - Handle of the parent provided as an argument. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaHandleList - A list with all the handles for all the new imported * Password Nodes. * - MegaRequest::getMegaStringIntegerMap - A map with problematic content as key and error * code as value * Possible error codes are: * IMPORTED_PASSWORD_ERROR_PARSER = 1 * IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD = 2 * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS: * + Invalid parent (parent doesn't exist or isn't password node) * + Invalid fileSource * + NULL at filePath * + File with wrong format * - MegaError::API_EREAD: * + File can't be opened * - MegaError::API_EACESS * + File is empty * * @param filePath Path to the file containing the passwords to import. * @param fileSource Type for the source from where the file was exported. * Valid values are: * - IMPORT_PASSWORD_SOURCE_GOOGLE = 0 * @param parent Parent handle for node that will contain new nodes as children. * @param listener MegaRequestListener to track this request. */ public void importPasswordsFromFile(String filePath, int fileSource, long parent, MegaRequestListenerInterface listener) { megaApi.importPasswordsFromFile(filePath, fileSource, parent, createDelegateRequestListener(listener)); } /** * Move a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node * @param listener MegaRequestListener to track this request */ public void moveNode(MegaNode node, MegaNode newParent, MegaRequestListenerInterface listener) { megaApi.moveNode(node, newParent, createDelegateRequestListener(listener)); } /** * Move a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node */ public void moveNode(MegaNode node, MegaNode newParent) { megaApi.moveNode(node, newParent); } /** * Move a node in the MEGA account changing the file name *

    * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node * - MegaRequest::getName - Returns the name for the new node *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - True if target folder (\c newParent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node * @param newName Name for the new node * @param listener MegaRequestListener to track this request */ public void moveNode(MegaNode node, MegaNode newParent, String newName, MegaRequestListenerInterface listener) { megaApi.moveNode(node, newParent, newName, createDelegateRequestListener(listener)); } /** * Move a node in the MEGA account changing the file name *

    * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node * - MegaRequest::getName - Returns the name for the new node *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - True if target folder (\c newParent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node * @param newName Name for the new node */ public void moveNode(MegaNode node, MegaNode newParent, String newName) { megaApi.moveNode(node, newParent, newName); } /** * Copy a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy (if it is a public node) *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node * - MegaRequest::getFlag - True if target folder (\c newParent) was overridden *

    * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @param listener MegaRequestListener to track this request * @implSpec In case the target folder was overridden, the MegaRequest::getParentHandle still keeps * the handle of the original target folder. You can check the final parent by checking the * value returned by MegaNode::getParentHandle */ public void copyNode(MegaNode node, MegaNode newParent, MegaRequestListenerInterface listener) { megaApi.copyNode(node, newParent, createDelegateRequestListener(listener)); } /** * Copy a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy (if it is a public node) *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node * - MegaRequest::getFlag - True if target folder (\c newParent) was overridden *

    * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @implSpec In case the target folder was overridden, the MegaRequest::getParentHandle still keeps * the handle of the original target folder. You can check the final parent by checking the * value returned by MegaNode::getParentHandle */ public void copyNode(MegaNode node, MegaNode newParent) { megaApi.copyNode(node, newParent); } /** * Copy a node in the MEGA account changing the file name *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy * - MegaRequest::getName - Returns the name for the new node *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node *

    * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @param newName Name for the new node * @param listener MegaRequestListener to track this request */ public void copyNode(MegaNode node, MegaNode newParent, String newName, MegaRequestListenerInterface listener) { megaApi.copyNode(node, newParent, newName, createDelegateRequestListener(listener)); } /** * Copy a node in the MEGA account changing the file name *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy * - MegaRequest::getName - Returns the name for the new node *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node *

    * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @param newName Name for the new node */ public void copyNode(MegaNode node, MegaNode newParent, String newName) { megaApi.copyNode(node, newParent, newName); } /** * Rename a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_RENAME * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to rename * - MegaRequest::getName - Returns the new name for the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to modify * @param newName New name for the node * @param listener MegaRequestListener to track this request */ public void renameNode(MegaNode node, String newName, MegaRequestListenerInterface listener) { megaApi.renameNode(node, newName, createDelegateRequestListener(listener)); } /** * Rename a node in the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_RENAME * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to rename * - MegaRequest::getName - Returns the new name for the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to modify * @param newName New name for the node */ public void renameNode(MegaNode node, String newName) { megaApi.renameNode(node, newName); } /** * Remove a node from the MEGA account *

    * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use MegaApi::moveNode *

    * If the node has previous versions, they will be deleted too *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to remove * - MegaRequest::getFlag - Returns false because previous versions won't be preserved *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param node Node to remove * @param listener MegaRequestListener to track this request */ public void remove(MegaNode node, MegaRequestListenerInterface listener) { megaApi.remove(node, createDelegateRequestListener(listener)); } /** * Remove a node from the MEGA account *

    * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use MegaApi::moveNode *

    * If the node has previous versions, they will be deleted too *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to remove * - MegaRequest::getFlag - Returns false because previous versions won't be preserved *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param node Node to remove */ public void remove(MegaNode node) { megaApi.remove(node); } /** * Remove all versions from the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_VERSIONS *

    * When the request finishes, file versions might not be deleted yet. * Deletions are notified using onNodesUpdate callbacks. * * @param listener MegaRequestListener to track this request */ public void removeVersions(MegaRequestListenerInterface listener) { megaApi.removeVersions(createDelegateRequestListener(listener)); } /** * Remove a version of a file from the MEGA account *

    * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use MegaApi::moveNode. *

    * If the node has previous versions, they won't be deleted. *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to remove * - MegaRequest::getFlag - Returns true because previous versions will be preserved *

    * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param node Node to remove * @param listener MegaRequestListener to track this request */ public void removeVersion(MegaNode node, MegaRequestListenerInterface listener) { megaApi.removeVersion(node, createDelegateRequestListener(listener)); } /** * Restore a previous version of a file *

    * Only versions of a file can be restored, not the current version (because it's already current). * The node will be copied and set as current. All the version history will be preserved without changes, * being the old current node the previous version of the new current node, and keeping the restored * node also in its previous place in the version history. *

    * The associated request type with this request is MegaRequest::TYPE_RESTORE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to restore *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param version Node with the version to restore * @param listener MegaRequestListener to track this request */ public void restoreVersion(MegaNode version, MegaRequestListenerInterface listener) { megaApi.restoreVersion(version, createDelegateRequestListener(listener)); } /** * Clean the Rubbish Bin in the MEGA account *

    * This function effectively removes every node contained in the Rubbish Bin. In order to * avoid accidental deletions, you might want to warn the user about the action. *

    * The associated request type with this request is MegaRequest::TYPE_CLEAN_RUBBISH_BIN. This * request returns MegaError::API_ENOENT if the Rubbish bin is already empty. *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param listener MegaRequestListener to track this request */ public void cleanRubbishBin(MegaRequestListenerInterface listener) { megaApi.cleanRubbishBin(createDelegateRequestListener(listener)); } /** * Clean the Rubbish Bin in the MEGA account *

    * This function effectively removes every node contained in the Rubbish Bin. In order to * avoid accidental deletions, you might want to warn the user about the action. *

    * The associated request type with this request is MegaRequest::TYPE_CLEAN_RUBBISH_BIN. This * request returns MegaError::API_ENOENT if the Rubbish bin is already empty. *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. */ public void cleanRubbishBin() { megaApi.cleanRubbishBin(); } /** * Send a node to the Inbox of another MEGA user using a MegaUser *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to send * - MegaRequest::getEmail - Returns the email of the user that receives the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to send * @param user User that receives the node * @param listener MegaRequestListener to track this request */ public void sendFileToUser(MegaNode node, MegaUser user, MegaRequestListenerInterface listener) { megaApi.sendFileToUser(node, user, createDelegateRequestListener(listener)); } /** * Send a node to the Inbox of another MEGA user using a MegaUser *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to send * - MegaRequest::getEmail - Returns the email of the user that receives the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to send * @param user User that receives the node */ public void sendFileToUser(MegaNode node, MegaUser user) { megaApi.sendFileToUser(node, user); } /** * Send a node to the Inbox of another MEGA user using his email *

    * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to send * - MegaRequest::getEmail - Returns the email of the user that receives the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to send * @param email Email of the user that receives the node * @param listener MegaRequestListener to track this request */ public void sendFileToUser(MegaNode node, String email, MegaRequestListenerInterface listener) { megaApi.sendFileToUser(node, email, createDelegateRequestListener(listener)); } /** * Upgrade cryptographic security *

    * This should be called only after MegaEvent::EVENT_UPGRADE_SECURITY event is received to effectively * proceed with the cryptographic upgrade process. * This should happen only once per account. * * @param listener MegaRequestListener to track this request */ public void upgradeSecurity(MegaRequestListenerInterface listener) { megaApi.upgradeSecurity(createDelegateRequestListener(listener)); } /** * Get the contact verification warning flag status * * It returns if showing the warnings to verify contacts is enabled. * * @return True if showing the warnings are enabled, false otherwise. */ public boolean contactVerificationWarningEnabled() { return megaApi.contactVerificationWarningEnabled(); } /** * Creates a new share key for the node if there is no share key already created. *

    * Call it before starting any new share. * * @param node The folder to share. It must be a non-root folder * @param listener MegaRequestListener to track this request */ public void openShareDialog(MegaNode node, MegaRequestListenerInterface listener) { megaApi.openShareDialog(node, createDelegateRequestListener(listener)); } /** * Share or stop sharing a folder in MEGA with another user using a MegaUser *

    * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN *

    * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param user User that receives the shared folder * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 * @param listener MegaRequestListener to track this request */ public void share(MegaNode node, MegaUser user, int level, MegaRequestListenerInterface listener) { megaApi.share(node, user, level, createDelegateRequestListener(listener)); } /** * Share or stop sharing a folder in MEGA with another user using a MegaUser *

    * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN *

    * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param user User that receives the shared folder * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 */ public void share(MegaNode node, MegaUser user, int level) { megaApi.share(node, user, level); } /** * Share or stop sharing a folder in MEGA with another user using his email *

    * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN *

    * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param email Email of the user that receives the shared folder. If it doesn't have a MEGA account, the folder will be shared anyway * and the user will be invited to register an account. * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user *

    * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 * @param listener MegaRequestListener to track this request */ public void share(MegaNode node, String email, int level, MegaRequestListenerInterface listener) { megaApi.share(node, email, level, createDelegateRequestListener(listener)); } /** * Share or stop sharing a folder in MEGA with another user using his email *

    * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN *

    * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param email Email of the user that receives the shared folder. If it doesn't have a MEGA account, the folder will be shared anyway * and the user will be invited to register an account. * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user *

    * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 */ public void share(MegaNode node, String email, int level) { megaApi.share(node, email, level); } /** * Import a public link to the account *

    * The associated request type with this request is MegaRequest::TYPE_IMPORT_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file * - MegaRequest::getParentHandle - Returns the folder that receives the imported file *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node in the account * - MegaRequest::getFlag - True if target folder (\c parent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA * @param parent Parent folder for the imported file * @param listener MegaRequestListener to track this request */ public void importFileLink(String megaFileLink, MegaNode parent, MegaRequestListenerInterface listener) { megaApi.importFileLink(megaFileLink, parent, createDelegateRequestListener(listener)); } /** * Import a public link to the account *

    * The associated request type with this request is MegaRequest::TYPE_IMPORT_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file * - MegaRequest::getParentHandle - Returns the folder that receives the imported file *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node in the account * - MegaRequest::getFlag - True if target folder (\c parent) was overridden *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA * @param parent Parent folder for the imported file */ public void importFileLink(String megaFileLink, MegaNode parent) { megaApi.importFileLink(megaFileLink, parent); } /** * Decrypt password-protected public link *

    * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the encrypted public link to the file/folder * - MegaRequest::getPassword - Returns the password to decrypt the link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Decrypted public link * * @param link Password/protected public link to a file/folder in MEGA * @param password Password to decrypt the link * @param listener MegaRequestListener to track this request */ public void decryptPasswordProtectedLink(String link, String password, MegaRequestListenerInterface listener) { megaApi.decryptPasswordProtectedLink(link, password, createDelegateRequestListener(listener)); } /** * Decrypt password-protected public link *

    * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the encrypted public link to the file/folder * - MegaRequest::getPassword - Returns the password to decrypt the link *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Decrypted public link * * @param link Password/protected public link to a file/folder in MEGA * @param password Password to decrypt the link */ public void decryptPasswordProtectedLink(String link, String password) { megaApi.decryptPasswordProtectedLink(link, password); } /** * Encrypt public link with password *

    * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to be encrypted * - MegaRequest::getPassword - Returns the password to encrypt the link * - MegaRequest::getFlag - Returns true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Encrypted public link * * @param link Public link to be encrypted, including encryption key for the link * @param password Password to encrypt the link * @param listener MegaRequestListener to track this request */ public void encryptLinkWithPassword(String link, String password, MegaRequestListenerInterface listener) { megaApi.encryptLinkWithPassword(link, password, createDelegateRequestListener(listener)); } /** * Encrypt public link with password *

    * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to be encrypted * - MegaRequest::getPassword - Returns the password to encrypt the link * - MegaRequest::getFlag - Returns true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Encrypted public link * * @param link Public link to be encrypted, including encryption key for the link * @param password Password to encrypt the link */ public void encryptLinkWithPassword(String link, String password) { megaApi.encryptLinkWithPassword(link, password); } /** * Get a MegaNode from a public link to a file *

    * A public node can be imported using MegaApi::copyNode or downloaded using MegaApi::startDownload *

    * The associated request type with this request is MegaRequest::TYPE_GET_PUBLIC_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPublicMegaNode - Public MegaNode corresponding to the public link * - MegaRequest::getFlag - Return true if the provided key along the link is invalid. *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA * @param listener MegaRequestListener to track this request */ public void getPublicNode(String megaFileLink, MegaRequestListenerInterface listener) { megaApi.getPublicNode(megaFileLink, createDelegateRequestListener(listener)); } /** * Get a MegaNode from a public link to a file *

    * A public node can be imported using MegaApi::copyNode or downloaded using MegaApi::startDownload *

    * The associated request type with this request is MegaRequest::TYPE_GET_PUBLIC_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPublicMegaNode - Public MegaNode corresponding to the public link * - MegaRequest::getFlag - Return true if the provided key along the link is invalid. *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA */ public void getPublicNode(String megaFileLink) { megaApi.getPublicNode(megaFileLink); } /** * Get the thumbnail of a node *

    * If the node doesn't have a thumbnail the request fails with the MegaError::API_ENOENT * error code *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the thumbnail * @param dstFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param listener MegaRequestListener to track this request */ public void getThumbnail(MegaNode node, String dstFilePath, MegaRequestListenerInterface listener) { megaApi.getThumbnail(node, dstFilePath, createDelegateRequestListener(listener)); } /** * Get the thumbnail of a node *

    * If the node doesn't have a thumbnail the request fails with the MegaError::API_ENOENT * error code *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the thumbnail * @param dstFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ public void getThumbnail(MegaNode node, String dstFilePath) { megaApi.getThumbnail(node, dstFilePath); } /** * Get the preview of a node *

    * If the node doesn't have a preview the request fails with the MegaError::API_ENOENT * error code *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the preview * @param dstFilePath Destination path for the preview. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "1.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param listener MegaRequestListener to track this request */ public void getPreview(MegaNode node, String dstFilePath, MegaRequestListenerInterface listener) { megaApi.getPreview(node, dstFilePath, createDelegateRequestListener(listener)); } /** * Get the preview of a node *

    * If the node doesn't have a preview the request fails with the MegaError::API_ENOENT * error code *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the preview * @param dstFilePath Destination path for the preview. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "1.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ public void getPreview(MegaNode node, String dstFilePath) { megaApi.getPreview(node, dstFilePath); } /** * Get the avatar of a MegaUser *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param user MegaUser to get the avatar. If this parameter is set to NULL, the avatar is obtained * for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param listener MegaRequestListener to track this request */ public void getUserAvatar(MegaUser user, String dstFilePath, MegaRequestListenerInterface listener) { megaApi.getUserAvatar(user, dstFilePath, createDelegateRequestListener(listener)); } /** * Get the avatar of a MegaUser *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param user MegaUser to get the avatar. If this parameter is set to NULL, the avatar is obtained * for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ public void getUserAvatar(MegaUser user, String dstFilePath) { megaApi.getUserAvatar(user, dstFilePath); } /** * Get the avatar of any user in MEGA *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) * * @param email_or_handle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to NULL, the avatar is obtained for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param listener MegaRequestListener to track this request */ public void getUserAvatar(String email_or_handle, String dstFilePath, MegaRequestListenerInterface listener) { megaApi.getUserAvatar(email_or_handle, dstFilePath, createDelegateRequestListener(listener)); } /** * Get the avatar of any user in MEGA *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) * * @param email_or_handle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to NULL, the avatar is obtained for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ public void getUserAvatar(String email_or_handle, String dstFilePath) { megaApi.getUserAvatar(email_or_handle, dstFilePath); } /** * Get the avatar of the active account *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param listener MegaRequestListener to track this request */ public void getUserAvatar(String dstFilePath, MegaRequestListenerInterface listener) { megaApi.getUserAvatar(dstFilePath, createDelegateRequestListener(listener)); } /** * Get the avatar of the active account *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. */ public void getUserAvatar(String dstFilePath) { megaApi.getUserAvatar(dstFilePath); } /** * Get the default color for the avatar. *

    * This color should be used only when the user doesn't have an avatar. *

    * You take the ownership of the returned value. * * @param user MegaUser to get the color of the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ @Nullable public String getUserAvatarColor(@Nullable MegaUser user) { return MegaApi.getUserAvatarColor(user); } /** * Get the default color for the avatar. *

    * This color should be used only when the user doesn't have an avatar. *

    * You take the ownership of the returned value. * * @param userhandle User handle (Base64 encoded) to get the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ @Nullable public String getUserAvatarColor(@Nullable String userhandle) { return MegaApi.getUserAvatarColor(userhandle); } /** * Get the secondary color for the avatar. *

    * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from getUserAvatarColor. *

    * You take the ownership of the returned value. * * @param user MegaUser to get the color of the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ @Nullable public String getUserAvatarSecondaryColor(@Nullable MegaUser user) { return MegaApi.getUserAvatarSecondaryColor(user); } /** * Get the secondary color for the avatar. *

    * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from getUserAvatarColor. *

    * You take the ownership of the returned value. * * @param userhandle User handle (Base64 encoded) to get the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ @Nullable public String getUserAvatarSecondaryColor(@Nullable String userhandle) { return MegaApi.getUserAvatarSecondaryColor(userhandle); } /** * Get an attribute of a MegaUser. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param user MegaUser to get the attribute. If this parameter is set to NULL, the attribute * is obtained for the active account * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * MegaApi::ATTR_GEOLOCATION = 22 * Get the user geolocation (private) * MegaApi::ATTR_CAMERA_UPLOADS_FOLDER = 23 * Get the target folder for Camera Uploads (private) * MegaApi::ATTR_MY_CHAT_FILES_FOLDER = 24 * Get the target folder for My chat files (private) * MegaApi::ATTR_ALIAS = 27 * Get the list of the users' aliases (private) * MegaApi::ATTR_DEVICE_NAMES = 30 * Get the list of device names (private) * MegaApi::ATTR_MY_BACKUPS_FOLDER = 31 * Get the target folder for My Backups (private) * @param listener MegaRequestListener to track this request */ public void getUserAttribute(MegaUser user, int type, MegaRequestListenerInterface listener) { megaApi.getUserAttribute(user, type, createDelegateRequestListener(listener)); } /** * Get an attribute of a MegaUser. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param user MegaUser to get the attribute. If this parameter is set to NULL, the attribute * is obtained for the active account * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * MegaApi::ATTR_GEOLOCATION = 22 * Get the user geolocation (private) * MegaApi::ATTR_CAMERA_UPLOADS_FOLDER = 23 * Get the target folder for Camera Uploads (private) * MegaApi::ATTR_MY_CHAT_FILES_FOLDER = 24 * Get the target folder for My chat files (private) * MegaApi::ATTR_ALIAS = 27 * Get the list of the users' aliases (private) * MegaApi::ATTR_DEVICE_NAMES = 30 * Get the list of device names (private) * MegaApi::ATTR_MY_BACKUPS_FOLDER = 31 * Get the target folder for My Backups (private) */ public void getUserAttribute(MegaUser user, int type) { megaApi.getUserAttribute(user, type); } /** * Get an attribute of any user in MEGA. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param email_or_handle Email or user handle (Base64 encoded) to get the attribute. * If this parameter is set to NULL, the attribute is obtained for the active account. * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * @param listener MegaRequestListener to track this request */ public void getUserAttribute(String email_or_handle, int type, MegaRequestListenerInterface listener) { megaApi.getUserAttribute(email_or_handle, type, createDelegateRequestListener(listener)); } /** * Get an attribute of any user in MEGA. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param email_or_handle Email or user handle (Base64 encoded) to get the attribute. * If this parameter is set to NULL, the attribute is obtained for the active account. * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) */ public void getUserAttribute(String email_or_handle, int type) { megaApi.getUserAttribute(email_or_handle, type); } /** * Get an attribute of the current account. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 * Get the settings for push notifications (private non-encrypted) * @param listener MegaRequestListener to track this request */ public void getUserAttribute(int type, MegaRequestListenerInterface listener) { megaApi.getUserAttribute(type, createDelegateRequestListener(listener)); } /** * Get an attribute of the current account. *

    * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get whether the user has enabled send geolocation messages (private) * MegaApi::USER_ATTR_PUSH_SETTINGS = 23 * Get the settings for push notifications (private non-encrypted) */ public void getUserAttribute(int type) { megaApi.getUserAttribute(type); } /** * Cancel the retrieval of a thumbnail *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node Node to cancel the retrieval of the thumbnail * @param listener MegaRequestListener to track this request * @see MegaApi::getThumbnail */ public void cancelGetThumbnail(MegaNode node, MegaRequestListenerInterface listener) { megaApi.cancelGetThumbnail(node, createDelegateRequestListener(listener)); } /** * Cancel the retrieval of a thumbnail *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node Node to cancel the retrieval of the thumbnail * @see MegaApi::getThumbnail */ public void cancelGetThumbnail(MegaNode node) { megaApi.cancelGetThumbnail(node); } /** * Cancel the retrieval of a preview *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node Node to cancel the retrieval of the preview * @param listener MegaRequestListener to track this request * @see MegaApi::getPreview */ public void cancelGetPreview(MegaNode node, MegaRequestListenerInterface listener) { megaApi.cancelGetPreview(node, createDelegateRequestListener(listener)); } /** * Cancel the retrieval of a preview *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node Node to cancel the retrieval of the preview * @see MegaApi::getPreview */ public void cancelGetPreview(MegaNode node) { megaApi.cancelGetPreview(node); } /** * Set the thumbnail of a MegaNode *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node MegaNode to set the thumbnail * @param srcFilePath Source path of the file that will be set as thumbnail * @param listener MegaRequestListener to track this request */ public void setThumbnail(MegaNode node, String srcFilePath, MegaRequestListenerInterface listener) { megaApi.setThumbnail(node, srcFilePath, createDelegateRequestListener(listener)); } /** * Set the thumbnail of a MegaNode *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node MegaNode to set the thumbnail * @param srcFilePath Source path of the file that will be set as thumbnail */ public void setThumbnail(MegaNode node, String srcFilePath) { megaApi.setThumbnail(node, srcFilePath); } /** * Set the preview of a MegaNode *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node MegaNode to set the preview * @param srcFilePath Source path of the file that will be set as preview * @param listener MegaRequestListener to track this request */ public void setPreview(MegaNode node, String srcFilePath, MegaRequestListenerInterface listener) { megaApi.setPreview(node, srcFilePath, createDelegateRequestListener(listener)); } /** * Set the preview of a MegaNode *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node MegaNode to set the preview * @param srcFilePath Source path of the file that will be set as preview */ public void setPreview(MegaNode node, String srcFilePath) { megaApi.setPreview(node, srcFilePath); } /** * Set/Remove the avatar of the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the source path (optional) * * @param srcFilePath Source path of the file that will be set as avatar. * If NULL, the existing avatar will be removed (if any). * In case the avatar never existed before, removing the avatar returns MegaError::API_ENOENT * @param listener MegaRequestListener to track this request */ public void setAvatar(String srcFilePath, MegaRequestListenerInterface listener) { megaApi.setAvatar(srcFilePath, createDelegateRequestListener(listener)); } /** * Set/Remove the avatar of the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the source path (optional) * * @param srcFilePath Source path of the file that will be set as avatar. * If NULL, the existing avatar will be removed (if any). * In case the avatar never existed before, removing the avatar returns MegaError::API_ENOENT */ public void setAvatar(String srcFilePath) { megaApi.setAvatar(srcFilePath); } /** * Set a public attribute of the current user *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getText - Returns the new value for the attribute * * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Set the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Set the lastname of the user (public) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Set the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Set the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) *

    * If the MEGA account is a sub-user business account, and the value of the parameter * type is equal to MegaApi::USER_ATTR_FIRSTNAME or MegaApi::USER_ATTR_LASTNAME * onRequestFinish will be called with the error code MegaError::API_EMASTERONLY. * @param value New attribute value * @param listener MegaRequestListener to track this request */ public void setUserAttribute(int type, String value, MegaRequestListenerInterface listener) { megaApi.setUserAttribute(type, value, createDelegateRequestListener(listener)); } /** * Set a public attribute of the current user *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getText - Returns the new value for the attribute * * @param type Attribute type * Valid values are: * MegaApi::USER_ATTR_FIRSTNAME = 1 * Set the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Set the lastname of the user (public) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Set the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Set the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) *

    * If the MEGA account is a sub-user business account, and the value of the parameter * type is equal to MegaApi::USER_ATTR_FIRSTNAME or MegaApi::USER_ATTR_LASTNAME * onRequestFinish will be called with the error code MegaError::API_EMASTERONLY. * @param value New attribute value */ public void setUserAttribute(int type, String value) { megaApi.setUserAttribute(type, value); } /** * Set a private attribute of the current user * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getMegaStringMap - Returns the new value for the attribute * * You can remove existing records/keypairs from the following attributes: * - MegaApi::ATTR_ALIAS * - MegaApi::ATTR_DEVICE_NAMES * - MegaApi::USER_ATTR_APPS_PREFS * - MegaApi::USER_ATTR_CC_PREFS * by adding a keypair into MegaStringMap whit the key to remove and an empty C-string null terminated as value. * * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Set whether the user can send geolocation messages (private) * MegaApi::ATTR_ALIAS = 27 * Set the list of users's aliases (private) * MegaApi::ATTR_DEVICE_NAMES = 30 * Set the list of device names (private) * MegaApi::ATTR_APPS_PREFS = 38 * Set the apps prefs (private) * * @param value New attribute value * @param listener MegaRequestListener to track this request */ public void setUserAttribute(int type, MegaStringMap value, MegaRequestListenerInterface listener) { megaApi.setUserAttribute(type, value, createDelegateRequestListener(listener)); } /** * Set a private attribute of the current user * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getMegaStringMap - Returns the new value for the attribute * * You can remove existing records/keypairs from the following attributes: * - MegaApi::ATTR_ALIAS * - MegaApi::ATTR_DEVICE_NAMES * - MegaApi::USER_ATTR_APPS_PREFS * - MegaApi::USER_ATTR_CC_PREFS * by adding a keypair into MegaStringMap whit the key to remove and an empty C-string null terminated as value. * * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Set whether the user can send geolocation messages (private) * MegaApi::ATTR_ALIAS = 27 * Set the list of users's aliases (private) * MegaApi::ATTR_DEVICE_NAMES = 30 * Set the list of device names (private) * MegaApi::ATTR_APPS_PREFS = 38 * Set the apps prefs (private) * * @param value New attribute value */ public void setUserAttribute(int type, MegaStringMap value) { megaApi.setUserAttribute(type, value); } /** * Set a custom attribute for the node *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getName - Returns the name of the custom attribute * - MegaRequest::getText - Returns the text for the attribute * - MegaRequest::getFlag - Returns false (not official attribute) *

    * The attribute name must be an UTF8 string with between 1 and 7 bytes * If the attribute already has a value, it will be replaced * If value is NULL, the attribute will be removed from the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the attribute * @param attrName Name of the custom attribute. * The length of this parameter must be between 1 and 7 UTF8 bytes * @param value Value for the attribute * @param listener MegaRequestListener to track this request */ public void setCustomNodeAttribute(MegaNode node, String attrName, String value, MegaRequestListenerInterface listener) { megaApi.setCustomNodeAttribute(node, attrName, value, createDelegateRequestListener(listener)); } /** * Set a custom attribute for the node *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getName - Returns the name of the custom attribute * - MegaRequest::getText - Returns the text for the attribute * - MegaRequest::getFlag - Returns false (not official attribute) *

    * The attribute name must be an UTF8 string with between 1 and 7 bytes * If the attribute already has a value, it will be replaced * If value is NULL, the attribute will be removed from the node *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the attribute * @param attrName Name of the custom attribute. * The length of this parameter must be between 1 and 7 UTF8 bytes * @param value Value for the attribute */ public void setCustomNodeAttribute(MegaNode node, String attrName, String value) { megaApi.setCustomNodeAttribute(node, attrName, value); } /** * Set node label as a node attribute. * Valid values for label attribute are: * - MegaNode::NODE_LBL_RED = 1 * - MegaNode::NODE_LBL_ORANGE = 2 * - MegaNode::NODE_LBL_YELLOW = 3 * - MegaNode::NODE_LBL_GREEN = 4 * - MegaNode::NODE_LBL_BLUE = 5 * - MegaNode::NODE_LBL_PURPLE = 6 * - MegaNode::NODE_LBL_GREY = 7 *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns the label for the node * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. * @param label Label of the node * @param listener MegaRequestListener to track this request */ public void setNodeLabel(MegaNode node, int label, MegaRequestListenerInterface listener) { megaApi.setNodeLabel(node, label, createDelegateRequestListener(listener)); } /** * Set node label as a node attribute. * Valid values for label attribute are: * - MegaNode::NODE_LBL_RED = 1 * - MegaNode::NODE_LBL_ORANGE = 2 * - MegaNode::NODE_LBL_YELLOW = 3 * - MegaNode::NODE_LBL_GREEN = 4 * - MegaNode::NODE_LBL_BLUE = 5 * - MegaNode::NODE_LBL_PURPLE = 6 * - MegaNode::NODE_LBL_GREY = 7 *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns the label for the node * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. * @param label Label of the node */ public void setNodeLabel(MegaNode node, int label) { megaApi.setNodeLabel(node, label); } /** * Remove node label *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. * @param listener MegaRequestListener to track this request */ public void resetNodeLabel(MegaNode node, MegaRequestListenerInterface listener) { megaApi.resetNodeLabel(node, createDelegateRequestListener(listener)); } /** * Remove node label *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. */ public void resetNodeLabel(MegaNode node) { megaApi.resetNodeLabel(node); } /** * Set node favourite as a node attribute. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as favourite, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_FAV * * @param node Node that will receive the information. * @param favourite if true set node as favourite, otherwise remove the attribute * @param listener MegaRequestListener to track this request */ public void setNodeFavourite(MegaNode node, boolean favourite, MegaRequestListenerInterface listener) { megaApi.setNodeFavourite(node, favourite, createDelegateRequestListener(listener)); } /** * Set node favourite as a node attribute. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as favourite, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_FAV * * @param node Node that will receive the information. * @param favourite if true set node as favourite, otherwise remove the attribute */ public void setNodeFavourite(MegaNode node, boolean favourite) { megaApi.setNodeFavourite(node, favourite); } /** * Mark a node as sensitive *

    * Descendants will inherit the sensitive property. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as sensitive, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_SENSITIVE * * @param node Node that will receive the information. * @param sensitive if true set node as sensitive, otherwise remove the attribute * @param listener MegaRequestListener to track this request */ public void setNodeSensitive(MegaNode node, boolean sensitive, MegaRequestListenerInterface listener) { megaApi.setNodeSensitive(node, sensitive, createDelegateRequestListener(listener)); } /** * Mark a node as sensitive *

    * Descendants will inherit the sensitive property. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as sensitive, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_SENSITIVE * * @param node Node that will receive the information. * @param sensitive if true set node as sensitive, otherwise remove the attribute */ public void setNodeSensitive(MegaNode node, boolean sensitive) { megaApi.setNodeSensitive(node, sensitive); } /** * Ascertain if the node is marked as sensitive or a descendent of such *

    * see MegaNode::isMarkedSensitive to see if the node is sensitive * * @param node node to inspect */ public boolean isSensitiveInherited(MegaNode node) { return megaApi.isSensitiveInherited(node); } /** * Get a list of favourite nodes. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node provided * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_FAV * - MegaRequest::getNumDetails - Returns the count requested *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaHandleList - List of handles of favourite nodes * * @param node Node and its children that will be searched for favourites. Search all nodes if null * @param count if count is zero return all favourite nodes, otherwise return only 'count' favourite nodes * @param listener MegaRequestListener to track this request */ public void getFavourites(MegaNode node, int count, MegaRequestListenerInterface listener) { megaApi.getFavourites(node, count, createDelegateRequestListener(listener)); } /** * Set the GPS coordinates of image files as a node attribute. *

    * To remove the existing coordinates, set both the latitude and longitude to * the value MegaNode::INVALID_COORDINATE. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_COORDINATES * - MegaRequest::getNumDetails - Returns the longitude, scaled to integer in the range of [0, 2^24] * - MegaRequest::getTransferTag() - Returns the latitude, scaled to integer in the range of [0, 2^24) *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param latitude Latitude in signed decimal degrees notation * @param longitude Longitude in signed decimal degrees notation * @param listener MegaRequestListener to track this request */ public void setNodeCoordinates(MegaNode node, double latitude, double longitude, MegaRequestListenerInterface listener) { megaApi.setNodeCoordinates(node, latitude, longitude, createDelegateRequestListener(listener)); } /** * Set node description as a node attribute * * To remove node description, set description to NULL * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_DESCRIPTION * - MegaRequest::getText - Returns node description * * If the size of the description is greater than MAX_NODE_DESCRIPTION_SIZE, onRequestFinish will be * called with the error code MegaError::API_EARGS. * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param description Node description * @param listener MegaRequestListener to track this request */ public void setNodeDescription(MegaNode node, String description, MegaRequestListenerInterface listener){ megaApi.setNodeDescription(node, description, createDelegateRequestListener(listener)); } /** * Add new tag stored as node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Remove tag, 2 - Update tag) * - MegaRequest::getText - Returns tag * * ',' is an invalid character to be used in a tag. If it is contained in the tag, * onRequestFinish will be called with the error code MegaError::API_EARGS. * * If the length of all tags is higher than 3000 onRequestFinish will be called with * the error code MegaError::API_EARGS * * If tag already exists, onRequestFinish will be called with the error code MegaError::API_EEXISTS * * If number of tags exceed the maximum number of tags (10), * onRequestFinish will be called with the error code MegaError::API_ETOOMANY * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param tag New tag * @param listener MegaRequestListener to track this request */ public void addNodeTag(MegaNode node, String tag, MegaRequestListenerInterface listener){ megaApi.addNodeTag(node, tag, createDelegateRequestListener(listener)); } /** * Remove a tag stored as a node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Temove tag, 2 - Update tag) * - MegaRequest::getText - Returns tag * * If tag doesn't exist, onRequestFinish will be called with the error code MegaError::API_ENOENT * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param tag Tag to be removed * @param listener MegaRequestListener to track this request */ public void removeNodeTag(MegaNode node, String tag, MegaRequestListenerInterface listener){ megaApi.removeNodeTag(node, tag, createDelegateRequestListener(listener)); } /** * Update a tag stored as a node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Temove tag, 2 - Update tag) * - MegaRequest::getText - Returns new tag * - MegaRequest::getName - Returns old tag * * ',' is an invalid character to be used in a tag. If it is contained in the tag, * onRequestFinish will be called with the error code MegaError::API_EARGS. * * If the length of all tags is higher than 3000 characters onRequestFinish will be called with * the error code MegaError::API_EARGS * * If newTag already exists, onRequestFinish will be called with the error code MegaError::API_EEXISTS * If oldTag doesn't exist, onRequestFinish will be called with the error code MegaError::API_ENOENT * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param newTag New tag value * @param oldTag Old tag value * @param listener MegaRequestListener to track this request */ public void updateNodeTag(MegaNode node, String newTag, String oldTag, MegaRequestListenerInterface listener){ megaApi.updateNodeTag(node, newTag, oldTag, createDelegateRequestListener(listener)); } /** * Retrieve all unique node tags present across all nodes in the account * * If the searchString contains invalid characters, such as ',', an empty list will be * returned. * * This function allows to cancel the processing at any time by passing a * MegaCancelToken and calling to MegaCancelToken::setCancelFlag(true). * * You take ownership of the returned value. * * @param searchString Optional parameter to filter the tags based on a specific search * string. If set to nullptr, all node tags will be retrieved. * @param cancelToken MegaCancelToken to be able to cancel the processing at any time. * * @return All the unique node tags that match the search criteria. */ public MegaStringList getAllNodeTags(String searchString, MegaCancelToken cancelToken){ return megaApi.getAllNodeTags(searchString, cancelToken); } /** * Generate a public link of a file/folder in MEGA *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns true * - MegaRequest::getNumber - Returns expire time * - MegaRequest::getFlag - Returns true if writable * - MegaRequest::getTransferTag - Returns if share key is shared with mega *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Public link * - MegaRequest::getPrivateKey - Authentication token (only if writable=true) * - MegaRequest::getPassword - Returns base64 encryption key used for share-key (only if * writable=true and megaHosted=true) *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to get the public link * @param expireTime Unix timestamp until the public link will be valid * @param listener MegaRequestListener to track this request */ public void exportNode(MegaNode node, int expireTime, MegaRequestListenerInterface listener) { megaApi.exportNode(node, expireTime, false, false, createDelegateRequestListener(listener)); } /** * Generate a public link of a file/folder in MEGA *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns true * - MegaRequest::getNumber - Returns expire time * - MegaRequest::getFlag - Returns true if writable * - MegaRequest::getTransferTag - Returns if share key is shared with mega *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Public link * - MegaRequest::getPrivateKey - Authentication token (only if writable=true) * - MegaRequest::getPassword - Returns base64 encryption key used for share-key (only if * writable=true and megaHosted=true) *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to get the public link * @param listener MegaRequestListener to track this request */ public void exportNode(MegaNode node, MegaRequestListenerInterface listener) { megaApi.exportNode(node, 0, false, false, createDelegateRequestListener(listener)); } /** * Stop sharing a file/folder *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns false *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to stop sharing * @param listener MegaRequestListener to track this request */ public void disableExport(MegaNode node, MegaRequestListenerInterface listener) { megaApi.disableExport(node, createDelegateRequestListener(listener)); } /** * Stop sharing a file/folder *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns false *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to stop sharing */ public void disableExport(MegaNode node) { megaApi.disableExport(node); } /** * Fetch the filesystem in MEGA and resumes syncs following a successful fetch *

    * The MegaApi object must be logged in in an account or a public folder * to successfully complete this request. *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_NODES *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if logged in into a folder and the provided key is invalid. Otherwise, false. * - MegaRequest::getNodeHandle - Returns the public handle if logged into a public folder. Otherwise, INVALID_HANDLE * * @param listener MegaRequestListener to track this request */ public void fetchNodes(MegaRequestListenerInterface listener) { megaApi.fetchNodes(createDelegateRequestListener(listener)); } /** * Fetch the filesystem in MEGA and resumes syncs following a successful fetch *

    * The MegaApi object must be logged in in an account or a public folder * to successfully complete this request. *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_NODES *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if logged in into a folder and the provided key is invalid. Otherwise, false. * - MegaRequest::getNodeHandle - Returns the public handle if logged into a public folder. Otherwise, INVALID_HANDLE */ public void fetchNodes() { megaApi.fetchNodes(); } /** * Get details about the MEGA account *

    * Only basic data will be available. If you can get more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) * * @param listener MegaRequestListener to track this request */ public void getAccountDetails(MegaRequestListenerInterface listener) { megaApi.getAccountDetails(createDelegateRequestListener(listener)); } /** * Get details about the MEGA account *

    * Only basic data will be available. If you can get more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) */ public void getAccountDetails() { megaApi.getAccountDetails(); } /** * Get details about the MEGA account *

    * Only basic data will be available. If you need more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Use this version of the function to get just the details you need, to minimise server load * and keep the system highly available for all. At least one flag must be set. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) *

    * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param storage If true, account storage details are requested * @param transfer If true, account transfer details are requested * @param pro If true, pro level of account is requested * @param listener MegaRequestListener to track this request */ public void getSpecificAccountDetails(boolean storage, boolean transfer, boolean pro, MegaRequestListenerInterface listener) { megaApi.getSpecificAccountDetails(storage, transfer, pro, -1, createDelegateRequestListener(listener)); } /** * Get details about the MEGA account *

    * Only basic data will be available. If you need more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Use this version of the function to get just the details you need, to minimise server load * and keep the system highly available for all. At least one flag must be set. *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) *

    * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param storage If true, account storage details are requested * @param transfer If true, account transfer details are requested * @param pro If true, pro level of account is requested */ public void getSpecificAccountDetails(boolean storage, boolean transfer, boolean pro) { megaApi.getSpecificAccountDetails(storage, transfer, pro, -1); } /** * Get details about the MEGA account *

    * This function allows to optionally get data about sessions, transactions and purchases related to the account. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - transactions: (numDetails & 0x08) * - purchases: (numDetails & 0x10) * - sessions: (numDetails & 0x020) *

    * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param sessions If true, sessions are requested * @param purchases If true, purchases are requested * @param transactions If true, transactions are requested * @param listener MegaRequestListener to track this request */ public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions, MegaRequestListenerInterface listener) { megaApi.getExtendedAccountDetails(sessions, purchases, transactions, createDelegateRequestListener(listener)); } /** * Get details about the MEGA account *

    * This function allows to optionally get data about sessions, transactions and purchases related to the account. *

    * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags *

    * The available flags are: * - transactions: (numDetails & 0x08) * - purchases: (numDetails & 0x10) * - sessions: (numDetails & 0x020) *

    * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param sessions If true, sessions are requested * @param purchases If true, purchases are requested * @param transactions If true, transactions are requested */ public void getExtendedAccountDetails(boolean sessions, boolean purchases, boolean transactions) { megaApi.getExtendedAccountDetails(sessions, purchases, transactions); } /** * Get the available pricing plans to upgrade a MEGA account *

    * You can get a payment ID for any of the pricing plans provided by this function * using MegaApi::getPaymentId *

    * The associated request type with this request is MegaRequest::TYPE_GET_PRICING *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPricing - MegaPricing object with all pricing plans * - MegaRequest::getCurrency - MegaCurrency object with currency data related to prices * * @param listener MegaRequestListener to track this request * @see MegaApi::getPaymentId */ public void getPricing(MegaRequestListenerInterface listener) { megaApi.getPricing(createDelegateRequestListener(listener)); } /** * Get the available pricing plans to upgrade a MEGA account *

    * You can get a payment ID for any of the pricing plans provided by this function * using MegaApi::getPaymentId *

    * The associated request type with this request is MegaRequest::TYPE_GET_PRICING *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPricing - MegaPricing object with all pricing plans * - MegaRequest::getCurrency - MegaCurrency object with currency data related to prices * * @see MegaApi::getPaymentId */ public void getPricing() { megaApi.getPricing(); } /** * Upgrade an account * * @param productHandle Product handle to purchase *

    * It's possible to get all pricing plans with their product handles using * MegaApi::getPricing *

    * The associated request type with this request is MegaRequest::TYPE_UPGRADE_ACCOUNT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the product * - MegaRequest::getNumber - Returns the payment method * @param paymentMethod Payment method * Valid values are: * - MegaApi::PAYMENT_METHOD_BALANCE = 0 * Use the account balance for the payment *

    * - MegaApi::PAYMENT_METHOD_CREDIT_CARD = 8 * Complete the payment with your credit card. Use MegaApi::creditCardStore to add * a credit card to your account * @param listener MegaRequestListener to track this request */ public void upgradeAccount(long productHandle, int paymentMethod, MegaRequestListenerInterface listener) { megaApi.upgradeAccount(productHandle, paymentMethod, createDelegateRequestListener(listener)); } /** * Upgrade an account * * @param productHandle Product handle to purchase *

    * It's possible to get all pricing plans with their product handles using * MegaApi::getPricing *

    * The associated request type with this request is MegaRequest::TYPE_UPGRADE_ACCOUNT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the product * - MegaRequest::getNumber - Returns the payment method * @param paymentMethod Payment method * Valid values are: * - MegaApi::PAYMENT_METHOD_BALANCE = 0 * Use the account balance for the payment *

    * - MegaApi::PAYMENT_METHOD_CREDIT_CARD = 8 * Complete the payment with your credit card. Use MegaApi::creditCardStore to add * a credit card to your account */ public void upgradeAccount(long productHandle, int paymentMethod) { megaApi.upgradeAccount(productHandle, paymentMethod); } /** * Submit a purchase receipt for verification *

    * The associated request type with this request is MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the payment gateway * - MegaRequest::getText - Returns the purchase receipt * * @param gateway Payment gateway * Currently supported payment gateways are: * - MegaApi::PAYMENT_METHOD_ITUNES = 2 * - MegaApi::PAYMENT_METHOD_GOOGLE_WALLET = 3 * - MegaApi::PAYMENT_METHOD_WINDOWS_STORE = 13 * @param receipt Purchase receipt * @param listener MegaRequestListener to track this request */ public void submitPurchaseReceipt(int gateway, String receipt, MegaRequestListenerInterface listener) { megaApi.submitPurchaseReceipt(gateway, receipt, createDelegateRequestListener(listener)); } /** * Store a credit card *

    * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_STORE * * @param address1 Billing address * @param address2 Second line of the billing address (optional) * @param city City of the billing address * @param province Province of the billing address * @param country Country of the billing address * @param postalcode Postal code of the billing address * @param firstname Firstname of the owner of the credit card * @param lastname Lastname of the owner of the credit card * @param creditcard Credit card number. Only digits, no spaces nor dashes * @param expire_month Expire month of the credit card. Must have two digits ("03" for example) * @param expire_year Expire year of the credit card. Must have four digits ("2010" for example) * @param cv2 Security code of the credit card (3 digits) * @param listener MegaRequestListener to track this request */ public void creditCardStore(String address1, String address2, String city, String province, String country, String postalcode, String firstname, String lastname, String creditcard, String expire_month, String expire_year, String cv2, MegaRequestListenerInterface listener) { megaApi.creditCardStore(address1, address2, city, province, country, postalcode, firstname, lastname, creditcard, expire_month, expire_year, cv2, createDelegateRequestListener(listener)); } /** * Store a credit card *

    * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_STORE * * @param address1 Billing address * @param address2 Second line of the billing address (optional) * @param city City of the billing address * @param province Province of the billing address * @param country Country of the billing address * @param postalcode Postal code of the billing address * @param firstname Firstname of the owner of the credit card * @param lastname Lastname of the owner of the credit card * @param creditcard Credit card number. Only digits, no spaces nor dashes * @param expire_month Expire month of the credit card. Must have two digits ("03" for example) * @param expire_year Expire year of the credit card. Must have four digits ("2010" for example) * @param cv2 Security code of the credit card (3 digits) */ public void creditCardStore(String address1, String address2, String city, String province, String country, String postalcode, String firstname, String lastname, String creditcard, String expire_month, String expire_year, String cv2) { megaApi.creditCardStore(address1, address2, city, province, country, postalcode, firstname, lastname, creditcard, expire_month, expire_year, cv2); } /** * Get the credit card subscriptions of the account *

    * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Number of credit card subscriptions * * @param listener MegaRequestListener to track this request */ public void creditCardQuerySubscriptions(MegaRequestListenerInterface listener) { megaApi.creditCardQuerySubscriptions(createDelegateRequestListener(listener)); } /** * Get the credit card subscriptions of the account *

    * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Number of credit card subscriptions */ public void creditCardQuerySubscriptions() { megaApi.creditCardQuerySubscriptions(); } /** * Cancel the credit card subscriptions of the account * * The associated request type with this request is * MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS * @param reason The reason for the cancellation. It can be NULL. * @param id The subscription ID for the cancellation. It can be NULL. * @param canContact Whether the user has permitted MEGA to contact them for the * cancellation. * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO = 0 * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_YES = 1 * @param listener MegaRequestListener to track this request */ public void creditCardCancelSubscriptions(String reason, String id, int canContact, MegaRequestListenerInterface listener) { megaApi.creditCardCancelSubscriptions(reason, id, canContact, createDelegateRequestListener(listener)); } /** * Get the available payment methods *

    * The associated request type with this request is MegaRequest::TYPE_GET_PAYMENT_METHODS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Bitfield with available payment methods *

    * To know if a payment method is available, you can do a check like this one: * request->getNumber() & (1 << MegaApi::PAYMENT_METHOD_CREDIT_CARD) * * @param listener MegaRequestListener to track this request */ public void getPaymentMethods(MegaRequestListenerInterface listener) { megaApi.getPaymentMethods(createDelegateRequestListener(listener)); } /** * Get the available payment methods *

    * The associated request type with this request is MegaRequest::TYPE_GET_PAYMENT_METHODS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Bitfield with available payment methods *

    * To know if a payment method is available, you can do a check like this one: * request->getNumber() & (1 << MegaApi::PAYMENT_METHOD_CREDIT_CARD) */ public void getPaymentMethods() { megaApi.getPaymentMethods(); } /** * Export the master key of the account *

    * The returned value is a Base64-encoded string *

    * With the master key, it's possible to start the recovery of an account when the * password is lost: * - MegaApi::resetPassword() *

    * You take the ownership of the returned value. * * @return Base64-encoded master key */ @Nullable public String exportMasterKey() { return megaApi.exportMasterKey(); } /** * Notify the user has exported the master key *

    * This function should be called when the user exports the master key by * clicking on "Copy" or "Save file" options. *

    * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember the user has a backup of his/her master key. In consequence, * MEGA will not ask the user to remind the password for the account. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ public void masterKeyExported(MegaRequestListenerInterface listener) { megaApi.masterKeyExported(createDelegateRequestListener(listener)); } /** * Notify the user has successfully checked his password *

    * This function should be called when the user demonstrates that he remembers * the password to access the account *

    * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ public void passwordReminderDialogSucceeded(MegaRequestListenerInterface listener) { megaApi.passwordReminderDialogSucceeded(createDelegateRequestListener(listener)); } /** * Notify the user has successfully skipped the password check *

    * This function should be called when the user skips the verification of * the password to access the account *

    * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ public void passwordReminderDialogSkipped(MegaRequestListenerInterface listener) { megaApi.passwordReminderDialogSkipped(createDelegateRequestListener(listener)); } /** * Notify the user wants to totally disable the password check *

    * This function should be called when the user rejects to verify that he remembers * the password to access the account and doesn't want to see the reminder again. *

    * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not ask the user * to remind the password for the account again. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ public void passwordReminderDialogBlocked(MegaRequestListenerInterface listener) { megaApi.passwordReminderDialogBlocked(createDelegateRequestListener(listener)); } /** * Check if the app should show the password reminder dialog to the user *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if the password reminder dialog should be shown *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT but the value of MegaRequest::getFlag will still * be valid. * * @param atLogout True if the check is being done just before a logout * @param listener MegaRequestListener to track this request */ public void shouldShowPasswordReminderDialog(boolean atLogout, MegaRequestListenerInterface listener) { megaApi.shouldShowPasswordReminderDialog(atLogout, createDelegateRequestListener(listener)); } /** * Check if the master key has been exported *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getAccess - Returns true if the master key has been exported *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ public void isMasterKeyExported(MegaRequestListenerInterface listener) { megaApi.isMasterKeyExported(createDelegateRequestListener(listener)); } /** * Enable or disable the generation of rich previews *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param enable True to enable the generation of rich previews * @param listener MegaRequestListener to track this request */ public void enableRichPreviews(boolean enable, MegaRequestListenerInterface listener) { megaApi.enableRichPreviews(enable, createDelegateRequestListener(listener)); } /** * Enable or disable the generation of rich previews *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param enable True to enable the generation of rich previews */ public void enableRichPreviews(boolean enable) { megaApi.enableRichPreviews(enable); } /** * Check if rich previews are automatically generated *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns zero *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if generation of rich previews is enabled * - MegaRequest::getMegaStringMap - Returns the raw content of the attribute: []* *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (false). * * @param listener MegaRequestListener to track this request */ public void isRichPreviewsEnabled(MegaRequestListenerInterface listener) { megaApi.isRichPreviewsEnabled(createDelegateRequestListener(listener)); } /** * Check if rich previews are automatically generated *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns zero *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if generation of rich previews is enabled * - MegaRequest::getMegaStringMap - Returns the raw content of the attribute: []* *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (false). */ public void isRichPreviewsEnabled() { megaApi.isRichPreviewsEnabled(); } /** * Check if the app should show the rich link warning dialog to the user *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns one *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if it is necessary to show the rich link warning * - MegaRequest::getNumber - Returns the number of times that user has indicated that doesn't want * modify the message with a rich link. If number is bigger than three, the extra option "Never" * must be added to the warning dialog. * - MegaRequest::getMegaStringMap - Returns the raw content of the attribute: []* *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (true). * * @param listener MegaRequestListener to track this request */ public void shouldShowRichLinkWarning(MegaRequestListenerInterface listener) { megaApi.shouldShowRichLinkWarning(createDelegateRequestListener(listener)); } /** * Check if the app should show the rich link warning dialog to the user *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns one *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if it is necessary to show the rich link warning * - MegaRequest::getNumber - Returns the number of times that user has indicated that doesn't want * modify the message with a rich link. If number is bigger than three, the extra option "Never" * must be added to the warning dialog. * - MegaRequest::getMegaStringMap - Returns the raw content of the attribute: []* *

    * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (true). */ public void shouldShowRichLinkWarning() { megaApi.shouldShowRichLinkWarning(); } /** * Set the number of times "Not now" option has been selected in the rich link warning dialog *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param value Number of times "Not now" option has been selected * @param listener MegaRequestListener to track this request */ public void setRichLinkWarningCounterValue(int value, MegaRequestListenerInterface listener) { megaApi.setRichLinkWarningCounterValue(value, createDelegateRequestListener(listener)); } /** * Set the number of times "Not now" option has been selected in the rich link warning dialog *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param value Number of times "Not now" option has been selected */ public void setRichLinkWarningCounterValue(int value) { megaApi.setRichLinkWarningCounterValue(value); } /** * Enable the sending of geolocation messages *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_GEOLOCATION * * @param listener MegaRequestListener to track this request */ public void enableGeolocation(MegaRequestListenerInterface listener) { megaApi.enableGeolocation(createDelegateRequestListener(listener)); } /** * Check if the sending of geolocation messages is enabled *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_GEOLOCATION *

    * Sending a Geolocation message is enabled if the MegaRequest object, received in onRequestFinish, * has error code MegaError::API_OK. In other cases, send geolocation messages is not enabled and * the application has to answer before send a message of this type. * * @param listener MegaRequestListener to track this request */ public void isGeolocationEnabled(MegaRequestListenerInterface listener) { megaApi.isGeolocationEnabled(createDelegateRequestListener(listener)); } /** * Set My Chat Files target folder. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER * - MegaRequest::getMegaStringMap - Returns a MegaStringMap. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param nodehandle MegaHandle of the node to be used as target folder * @param listener MegaRequestListener to track this request */ public void setMyChatFilesFolder(long nodehandle, MegaRequestListenerInterface listener) { megaApi.setMyChatFilesFolder(nodehandle, createDelegateRequestListener(listener)); } /** * Gets My chat files target folder. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the node where My Chat Files are stored *

    * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ public void getMyChatFilesFolder(MegaRequestListenerInterface listener) { megaApi.getMyChatFilesFolder(createDelegateRequestListener(listener)); } /** * Set Camera Uploads primary target folder. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns false * - MegaRequest::getNodehandle - Returns the provided node handle * - MegaRequest::getMegaStringMap - Returns a MegaStringMap. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param nodehandle MegaHandle of the node to be used as primary target folder * @param listener MegaRequestListener to track this request */ public void setCameraUploadsFolder(long nodehandle, MegaRequestListenerInterface listener) { megaApi.setCameraUploadsFolder(nodehandle, createDelegateRequestListener(listener)); } /** * Set Camera Uploads for both primary and secondary target folder. *

    * If only one of the target folders wants to be set, simply pass a INVALID_HANDLE to * as the other target folder and it will remain untouched. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getNodehandle - Returns the provided node handle for primary folder * - MegaRequest::getParentHandle - Returns the provided node handle for secondary folder * * @param primaryFolder MegaHandle of the node to be used as primary target folder * @param secondaryFolder MegaHandle of the node to be used as secondary target folder * @param listener MegaRequestListener to track this request */ public void setCameraUploadsFolders(long primaryFolder, long secondaryFolder, MegaRequestListenerInterface listener) { megaApi.setCameraUploadsFolders(primaryFolder, secondaryFolder, createDelegateRequestListener(listener)); } /** * Gets Camera Uploads primary target folder. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns false *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the primary node where Camera Uploads files are stored *

    * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ public void getCameraUploadsFolder(MegaRequestListenerInterface listener) { megaApi.getCameraUploadsFolder(createDelegateRequestListener(listener)); } /** * Gets Camera Uploads secondary target folder. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the secondary node where Camera Uploads files are stored *

    * If the secondary folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ public void getCameraUploadsFolderSecondary(MegaRequestListenerInterface listener) { megaApi.getCameraUploadsFolderSecondary(createDelegateRequestListener(listener)); } /** * Gets the alias for an user *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_ALIAS * - MegaRequest::getNodeHandle - user handle in binary * - MegaRequest::getText - user handle encoded in B64 *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the user alias *

    * If the user alias doesn't exists the request will fail with the error code MegaError::API_ENOENT. * * @param uh handle of the user in binary * @param listener MegaRequestListener to track this request */ public void getUserAlias(long uh, MegaRequestListenerInterface listener) { megaApi.getUserAlias(uh, createDelegateRequestListener(listener)); } /** * Set or reset an alias for a user *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_ALIAS * - MegaRequest::getNodeHandle - Returns the user handle in binary * - MegaRequest::getText - Returns the user alias * * @param uh handle of the user in binary * @param alias the user alias, or null to reset the existing * @param listener MegaRequestListener to track this request */ public void setUserAlias(long uh, String alias, MegaRequestListenerInterface listener) { megaApi.setUserAlias(uh, alias, createDelegateRequestListener(listener)); } /** * Get push notification settings *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PUSH_SETTINGS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaPushNotificationSettings - Returns settings for push notifications * * @param listener MegaRequestListener to track this request * @see MegaPushNotificationSettings class for more details. */ public void getPushNotificationSettings(MegaRequestListenerInterface listener) { megaApi.getPushNotificationSettings(createDelegateRequestListener(listener)); } /** * Set push notification settings *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PUSH_SETTINGS * - MegaRequest::getMegaPushNotificationSettings - Returns settings for push notifications * * @param settings MegaPushNotificationSettings with the new settings * @param listener MegaRequestListener to track this request * @see MegaPushNotificationSettings class for more details. You can prepare a new object by * calling MegaPushNotificationSettings::createInstance. */ public void setPushNotificationSettings(MegaPushNotificationSettings settings, MegaRequestListenerInterface listener) { megaApi.setPushNotificationSettings(settings, createDelegateRequestListener(listener)); } /** * Get the number of days for rubbish-bin cleaning scheduler *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RUBBISH_TIME *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the days for rubbish-bin cleaning scheduler. * Zero means that the rubbish-bin cleaning scheduler is disabled (only if the account is PRO) * Any negative value means that the configured value is invalid. * * @param listener MegaRequestListener to track this request */ public void getRubbishBinAutopurgePeriod(MegaRequestListenerInterface listener) { megaApi.getRubbishBinAutopurgePeriod(createDelegateRequestListener(listener)); } /** * Set the number of days for rubbish-bin cleaning scheduler *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RUBBISH_TIME * - MegaRequest::getNumber - Returns the days for rubbish-bin cleaning scheduler passed as parameter * * @param days Number of days for rubbish-bin cleaning scheduler. It must be >= 0. * The value zero disables the rubbish-bin cleaning scheduler (only for PRO accounts). * @param listener MegaRequestListener to track this request */ public void setRubbishBinAutopurgePeriod(int days, MegaRequestListenerInterface listener) { megaApi.setRubbishBinAutopurgePeriod(days, createDelegateRequestListener(listener)); } /** * Returns the id of this device *

    * You take the ownership of the returned value. * * @return The id of this device */ @Nullable public String getDeviceId() { return megaApi.getDeviceId(); } /** * Returns the name previously set for a device *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getText - Returns passed device id (or the value returned by getDeviceId() * if deviceId was initially passed as null). *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns device name. * * @param deviceId The id of the device to get the name for. If null, the value returned * by getDeviceId() will be used instead. * @param listener MegaRequestListener to track this request */ public void getDeviceName(String deviceId, MegaRequestListenerInterface listener) { megaApi.getDeviceName(deviceId, createDelegateRequestListener(listener)); } /** * Sets name for specified device *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getName - Returns device name. * - MegaRequest::getText - Returns passed device id (or the value returned by getDeviceId() * if deviceId was initially passed as null). * * @param deviceId The id of the device to set the name for. If null, the value returned * by getDeviceId() will be used instead. * @param deviceName String with device name * @param listener MegaRequestListener to track this request */ public void setDeviceName(String deviceId, String deviceName, MegaRequestListenerInterface listener) { megaApi.setDeviceName(deviceId, deviceName, createDelegateRequestListener(listener)); } /** * Returns the name set for this drive *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DRIVE_NAMES * - MegaRequest::getFile - Returns the path to the drive *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns drive name. * * @param pathToDrive Path to the root of the external drive * @param listener MegaRequestListener to track this request */ public void getDriveName(String pathToDrive, MegaRequestListenerInterface listener) { megaApi.getDriveName(pathToDrive, createDelegateRequestListener(listener)); } /** * Returns the name set for this drive *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DRIVE_NAMES * - MegaRequest::getFile - Returns the path to the drive *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns drive name. * * @param pathToDrive Path to the root of the external drive */ public void getDriveName(String pathToDrive) { megaApi.getDriveName(pathToDrive); } /** * Sets drive name *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DRIVE_NAMES * - MegaRequest::getName - Returns drive name. * - MegaRequest::getFile - Returns the path to the drive * * @param pathToDrive Path to the root of the external drive * @param driveName String with drive name * @param listener MegaRequestListener to track this request */ public void setDriveName(String pathToDrive, String driveName, MegaRequestListenerInterface listener) { megaApi.setDriveName(pathToDrive, driveName, createDelegateRequestListener(listener)); } /** * Sets drive name *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DRIVE_NAMES * - MegaRequest::getName - Returns drive name. * - MegaRequest::getFile - Returns the path to the drive * * @param pathToDrive Path to the root of the external drive * @param driveName String with drive name */ public void setDriveName(String pathToDrive, String driveName) { megaApi.setDriveName(pathToDrive, driveName); } /** * Change the password of the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password * @param listener MegaRequestListener to track this request */ public void changePassword(String oldPassword, String newPassword, MegaRequestListenerInterface listener) { megaApi.changePassword(oldPassword, newPassword, createDelegateRequestListener(listener)); } /** * Change the password of the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password */ public void changePassword(String oldPassword, String newPassword) { megaApi.changePassword(oldPassword, newPassword); } /** * Invite another person to be your MEGA contact *

    * The user doesn't need to be registered on MEGA. If the email isn't associated with * a MEGA account, an invitation email will be sent with the text in the "message" parameter. *

    * The associated request type with this request is MegaRequest::TYPE_INVITE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getText - Returns the text of the invitation * - MegaRequest::getNumber - Returns the action *

    * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MegaError::API_EACCESS. * * @param email Email of the new contact * @param message Message for the user (can be NULL) * @param action Action for this contact request. Valid values are: * - MegaContactRequest::INVITE_ACTION_ADD = 0 * - MegaContactRequest::INVITE_ACTION_DELETE = 1 * - MegaContactRequest::INVITE_ACTION_REMIND = 2 * @param listener MegaRequestListener to track this request */ public void inviteContact(String email, String message, int action, MegaRequestListenerInterface listener) { megaApi.inviteContact(email, message, action, createDelegateRequestListener(listener)); } /** * Invite another person to be your MEGA contact *

    * The user doesn't need to be registered on MEGA. If the email isn't associated with * a MEGA account, an invitation email will be sent with the text in the "message" parameter. *

    * The associated request type with this request is MegaRequest::TYPE_INVITE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getText - Returns the text of the invitation * - MegaRequest::getNumber - Returns the action *

    * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MegaError::API_EACCESS. * * @param email Email of the new contact * @param message Message for the user (can be NULL) * @param action Action for this contact request. Valid values are: * - MegaContactRequest::INVITE_ACTION_ADD = 0 * - MegaContactRequest::INVITE_ACTION_DELETE = 1 * - MegaContactRequest::INVITE_ACTION_REMIND = 2 */ public void inviteContact(String email, String message, int action) { megaApi.inviteContact(email, message, action); } /** * Invite another person to be your MEGA contact using a contact link handle *

    * The associated request type with this request is MegaRequest::TYPE_INVITE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getText - Returns the text of the invitation * - MegaRequest::getNumber - Returns the action * - MegaRequest::getNodeHandle - Returns the contact link handle *

    * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MegaError::API_EACCESS. * * @param email Email of the new contact * @param message Message for the user (can be NULL) * @param action Action for this contact request. Valid values are: * - MegaContactRequest::INVITE_ACTION_ADD = 0 * - MegaContactRequest::INVITE_ACTION_DELETE = 1 * - MegaContactRequest::INVITE_ACTION_REMIND = 2 * @param contactLink Contact link handle of the other account. This parameter is considered only if the * \c action is MegaContactRequest::INVITE_ACTION_ADD. Otherwise, it's ignored and it has no effect. * @param listener MegaRequestListener to track this request */ public void inviteContact(String email, String message, int action, long contactLink, MegaRequestListenerInterface listener) { megaApi.inviteContact(email, message, action, contactLink, createDelegateRequestListener(listener)); } /** * Reply to a contact request * * @param request Contact request. You can get your pending contact requests using MegaApi::getIncomingContactRequests * @param action Action for this contact request. Valid values are: * - MegaContactRequest::REPLY_ACTION_ACCEPT = 0 * - MegaContactRequest::REPLY_ACTION_DENY = 1 * - MegaContactRequest::REPLY_ACTION_IGNORE = 2 *

    * The associated request type with this request is MegaRequest::TYPE_REPLY_CONTACT_REQUEST * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact request * - MegaRequest::getNumber - Returns the action * @param listener MegaRequestListener to track this request */ public void replyContactRequest(MegaContactRequest request, int action, MegaRequestListenerInterface listener) { megaApi.replyContactRequest(request, action, createDelegateRequestListener(listener)); } /** * Reply to a contact request * * @param request Contact request. You can get your pending contact requests using MegaApi::getIncomingContactRequests * @param action Action for this contact request. Valid values are: * - MegaContactRequest::REPLY_ACTION_ACCEPT = 0 * - MegaContactRequest::REPLY_ACTION_DENY = 1 * - MegaContactRequest::REPLY_ACTION_IGNORE = 2 *

    * The associated request type with this request is MegaRequest::TYPE_REPLY_CONTACT_REQUEST * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact request * - MegaRequest::getNumber - Returns the action */ public void replyContactRequest(MegaContactRequest request, int action) { megaApi.replyContactRequest(request, action); } /** * Remove a contact to the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * * @param user MegaUser of the contact (see MegaApi::getContact) * @param listener MegaRequestListener to track this request */ public void removeContact(MegaUser user, MegaRequestListenerInterface listener) { megaApi.removeContact(user, createDelegateRequestListener(listener)); } /** * Remove a contact to the MEGA account *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * * @param user MegaUser of the contact (see MegaApi::getContact) */ public void removeContact(MegaUser user) { megaApi.removeContact(user); } /** * Logout of the MEGA account invalidating the session *

    * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the keepSyncConfigsFile * - MegaRequest::getFlag - Returns true *

    * Under certain circumstances, this request might return the error code * MegaError::API_ESID. It should not be taken as an error, since the reason * is that the logout action has been notified before the reception of the * logout response itself. *

    * In case of an automatic logout (ie. when the account become blocked by * ToS infringement), the MegaRequest::getParamType indicates the error that * triggered the automatic logout (MegaError::API_EBLOCKED for the example). * * @param listener MegaRequestListener to track this request */ public void logout(MegaRequestListenerInterface listener) { megaApi.logout(false, createDelegateRequestListener(listener)); } /** * Logout of the MEGA account invalidating the session *

    * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the keepSyncConfigsFile * - MegaRequest::getFlag - Returns true *

    * Under certain circumstances, this request might return the error code * MegaError::API_ESID. It should not be taken as an error, since the reason * is that the logout action has been notified before the reception of the * logout response itself. *

    * In case of an automatic logout (ie. when the account become blocked by * ToS infringement), the MegaRequest::getParamType indicates the error that * triggered the automatic logout (MegaError::API_EBLOCKED for the example). */ public void logout() { megaApi.logout(false, null); } /** * Logout of the MEGA account without invalidating the session *

    * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false * * @param listener MegaRequestListener to track this request */ public void localLogout(MegaRequestListenerInterface listener) { megaApi.localLogout(createDelegateRequestListener(listener)); } /** * Logout of the MEGA account without invalidating the session *

    * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false */ public void localLogout() { megaApi.localLogout(); } /** * Invalidate the existing cache and create a fresh one */ public void invalidateCache() { megaApi.invalidateCache(); } /** * Estimate the strength of a password *

    * Possible return values are: * - PASSWORD_STRENGTH_VERYWEAK = 0 * - PASSWORD_STRENGTH_WEAK = 1 * - PASSWORD_STRENGTH_MEDIUM = 2 * - PASSWORD_STRENGTH_GOOD = 3 * - PASSWORD_STRENGTH_STRONG = 4 * * @param password Password to check * @return Estimated strength of the password */ public int getPasswordStrength(String password) { return megaApi.getPasswordStrength(password); } /** * Generate a new pseudo-randomly characters-based password * * You take ownership of the returned value. * Use delete[] to free it. * * @param useUpper boolean indicating if at least 1 upper case letter shall be included * @param useDigit boolean indicating if at least 1 digit shall be included * @param useSymbol boolean indicating if at least 1 symbol from !@#$%^&*() shall be included * @param length int with the number of characters that will be included. * Minimum valid length is 8 and maximum valid is 64. * @return Null-terminated char string containing the newly generated password. */ public static String generateRandomCharsPassword(boolean useUpper, boolean useDigit, boolean useSymbol, int length) { return MegaApi.generateRandomCharsPassword(useUpper, useDigit, useSymbol, length); } /** * Send events to the stats server *

    * The associated request type with this request is MegaRequest::TYPE_SEND_EVENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the event type * - MegaRequest::getText - Returns the event message * - MegaRequest::getFlag - Returns the addJourneyId flag * - MegaRequest::getSessionKey - Returns the ViewID * * @param eventType Event type * Event types are restricted to the following ranges: * - MEGAcmd: [98900, 99000) * - MEGAchat: [99000, 99199) * - Android: [99200, 99300) * - iOS: [99300, 99400) * - MEGA SDK: [99400, 99500) * - MEGAsync: [99500, 99600) * - Webclient: [99600, 99800] * @param message Event message * @param addJourneyId True if JourneyID should be included. Otherwise, false. * @param viewId ViewID value (C-string null-terminated) to be sent with the event. * This value should have been generated with MegaApi::generateViewId method. * @deprecated This function is for internal usage of MEGA apps for debug purposes. This info * is sent to MEGA servers. */ @Deprecated public void sendEvent(int eventType, String message, boolean addJourneyId, @Nullable String viewId) { megaApi.sendEvent(eventType, message, addJourneyId, viewId); } /** * Create a new ticket for support with attached description *

    * The associated request type with this request is MegaRequest::TYPE_SUPPORT_TICKET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the type of the ticket * - MegaRequest::getText - Returns the description of the issue * * @param message Description of the issue for support * @param type Ticket type. These are the available types: * 0 for General Enquiry * 1 for Technical Issue * 2 for Payment Issue * 3 for Forgotten Password * 4 for Transfer Issue * 5 for Contact/Sharing Issue * 6 for MEGAsync Issue * 7 for Missing/Invisible Data * 8 for help-centre clarifications * 9 for iOS issue * 10 for Android issue * @param listener MegaRequestListener to track this request */ public void createSupportTicket(String message, int type, MegaRequestListenerInterface listener) { megaApi.createSupportTicket(message, type, createDelegateRequestListener(listener)); } //****************************************************************************************************/ // TRANSFERS //****************************************************************************************************/ /** * Upload a file to support *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." *

    * For folders, onTransferFinish will be called with error MegaError:API_EARGS; * * @param localPath Local path of the file * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to true only if you are sure about what are you doing. * @param listener MegaTransferListener to track this transfer */ public void startUploadForSupport(String localPath, boolean isSourceTemporary, MegaTransferListenerInterface listener) { megaApi.startUploadForSupport(localPath, isSourceTemporary, createDelegateTransferListener(listener)); } /** * Upload a file or a folder *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." *

    * When user wants to upload/download a batch of items that at least contains one folder, SDK mutex will be partially * locked until: * - we have received onTransferStart for every file in the batch * - we have received onTransferUpdate with MegaTransfer::getStage == MegaTransfer::STAGE_TRANSFERRING_FILES * for every folder in the batch *

    * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by calling CancelToken::cancel(true). * This method will cancel all transfers(not finished yet). *

    * Important considerations: * - A cancel token instance can be shared by multiple transfers, and calling CancelToken::cancel(true) will affect all * of those transfers. *

    * - It's app responsibility, to keep cancel token instance alive until receive MegaTransferListener::onTransferFinish for all MegaTransfers * that shares the same cancel token instance. *

    * In case any other folder is being uploaded/downloaded, and MegaTransfer::getStage for that transfer returns * a value between the following stages: MegaTransfer::STAGE_SCAN and MegaTransfer::STAGE_PROCESS_TRANSFER_QUEUE * both included, don't use MegaApi::cancelTransfer to cancel this transfer (it could generate a deadlock), * instead of that, use MegaCancelToken::cancel(true) calling through MegaCancelToken instance associated to this transfer. *

    * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param fileName Custom file name for the file or folder in MEGA * + If you don't need this param provide NULL as value * @param mtime Custom modification time for the file in MEGA (in seconds since the epoch) * + If you don't need this param provide MegaApi::INVALID_CUSTOM_MOD_TIME as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error API_EEXISTS and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * + If you don't need this param provide NULL as value * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to true only if you are sure about what are you doing. * + If you don't need this param provide false as value * @param startFirst puts the transfer on top of the upload queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file upload process. * This param is required to be able to cancel the transfer safely. * App retains the ownership of this param. * @param listener MegaTransferListener to track this transfer * * @deprecated This version of the function is deprecated. Please, use the non-deprecated * one. */ @Deprecated public void startUpload(String localPath, MegaNode parent, String fileName, long mtime, String appData, boolean isSourceTemporary, boolean startFirst, MegaCancelToken cancelToken, MegaTransferListenerInterface listener) { megaApi.startUpload(localPath, parent, fileName, mtime, appData, isSourceTemporary, startFirst, cancelToken, createDelegateTransferListener(listener)); } /** * Upload a file or a folder *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." *

    * When user wants to upload/download a batch of items that at least contains one folder, SDK mutex will be partially * locked until: * - we have received onTransferStart for every file in the batch * - we have received onTransferUpdate with MegaTransfer::getStage == MegaTransfer::STAGE_TRANSFERRING_FILES * for every folder in the batch *

    * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by calling CancelToken::cancel(true). * This method will cancel all transfers(not finished yet). *

    * Important considerations: * - A cancel token instance can be shared by multiple transfers, and calling CancelToken::cancel(true) will affect all * of those transfers. *

    * - It's app responsibility, to keep cancel token instance alive until receive MegaTransferListener::onTransferFinish for all MegaTransfers * that shares the same cancel token instance. *

    * In case any other folder is being uploaded/downloaded, and MegaTransfer::getStage for that transfer returns * a value between the following stages: MegaTransfer::STAGE_SCAN and MegaTransfer::STAGE_PROCESS_TRANSFER_QUEUE * both included, don't use MegaApi::cancelTransfer to cancel this transfer (it could generate a deadlock), * instead of that, use MegaCancelToken::cancel(true) calling through MegaCancelToken instance associated to this transfer. *

    * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param fileName Custom file name for the file or folder in MEGA * + If you don't need this param provide NULL as value * @param mtime Custom modification time for the file in MEGA (in seconds since the epoch) * + If you don't need this param provide MegaApi::INVALID_CUSTOM_MOD_TIME as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error API_EEXISTS and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * + If you don't need this param provide NULL as value * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it when the upload finishes. * This parameter is intended to automatically delete temporary files that are only created to be uploaded. * Use this parameter with caution. Set it to true only if you are sure about what are you doing. * + If you don't need this param provide false as value * @param startFirst puts the transfer on top of the upload queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file upload process. * This param is required to be able to cancel the transfer safely. * App retains the ownership of this param. * * @deprecated This version of the function is deprecated. Please, use the non-deprecated * one. */ @Deprecated public void startUpload(String localPath, MegaNode parent, String fileName, long mtime, String appData, boolean isSourceTemporary, boolean startFirst, MegaCancelToken cancelToken) { megaApi.startUpload(localPath, parent, fileName, mtime, appData, isSourceTemporary, startFirst, cancelToken); } /** * Upload a file or a folder. *

    * This method starts an upload transfer for a local file or folder into the specified * parent node. *

    * Business account overdue: * If the status of the business account is expired/overdue, * MegaTransferListener::onTransferFinish() will be called with error code * MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar * to "Your business account is overdue, please contact your administrator." *

    * Folder batch deadlock considerations: * When uploading a batch of items that contains at least one folder, the SDK mutex will be * partially locked until: * - onTransferStart has been received for every file in the batch, and * - onTransferUpdate has been received with MegaTransfer::getStage() == * MegaTransfer::STAGE_TRANSFERRING_FILES for every folder in the batch. *

    * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by * calling CancelToken::cancel(true). This cancels all transfers (not finished yet) * associated with that cancel token instance. *

    * Important considerations about cancel tokens: * - A MegaCancelToken instance can be shared by multiple transfers. Calling cancel(true) * affects all transfers that share the token. * - It is the app responsibility to keep the MegaCancelToken instance alive until * MegaTransferListener::onTransferFinish() is received for all MegaTransfers that share * it. *

    * For more information about MegaTransfer stages please refer to * MegaTransferListener::onTransferUpdate documentation. * * @param localPath Local path of the file or folder to upload. * @param parent Parent node where the file/folder will be created in the MEGA account. * @param cancelToken MegaCancelToken used to cancel the upload process safely (required for * safe cancellation). App retains ownership and must keep it alive as described above. * @param options Optional upload customization parameters. * @param listener Optional MegaTransferListener to track this transfer. The app retains the * ownership of the object. It can be deleted after the call returns. *

    * NOTE In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. */ public void startUpload(String localPath, MegaNode parent, MegaCancelToken cancelToken, MegaUploadOptions options, MegaTransferListenerInterface listener) { megaApi.startUpload(localPath, parent, cancelToken, options, createDelegateTransferListener(listener)); } /** * Upload a file or a folder. *

    * This method starts an upload transfer for a local file or folder into the specified * parent node. *

    * Business account overdue: * If the status of the business account is expired/overdue, * MegaTransferListener::onTransferFinish() will be called with error code * MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar * to "Your business account is overdue, please contact your administrator." *

    * Folder batch deadlock considerations: * When uploading a batch of items that contains at least one folder, the SDK mutex will be * partially locked until: * - onTransferStart has been received for every file in the batch, and * - onTransferUpdate has been received with MegaTransfer::getStage() == * MegaTransfer::STAGE_TRANSFERRING_FILES for every folder in the batch. *

    * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by * calling CancelToken::cancel(true). This cancels all transfers (not finished yet) * associated with that cancel token instance. *

    * Important considerations about cancel tokens: * - A MegaCancelToken instance can be shared by multiple transfers. Calling cancel(true) * affects all transfers that share the token. * - It is the app responsibility to keep the MegaCancelToken instance alive until * MegaTransferListener::onTransferFinish() is received for all MegaTransfers that share * it. *

    * For more information about MegaTransfer stages please refer to * MegaTransferListener::onTransferUpdate documentation. * * @param localPath Local path of the file or folder to upload. * @param parent Parent node where the file/folder will be created in the MEGA account. * @param cancelToken MegaCancelToken used to cancel the upload process safely (required for * safe cancellation). App retains ownership and must keep it alive as described above. * @param options Optional upload customization parameters. *

    * NOTE In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. */ public void startUpload(String localPath, MegaNode parent, MegaCancelToken cancelToken, MegaUploadOptions options) { megaApi.startUpload(localPath, parent, cancelToken, options); } /** * Download a file or a folder from MEGA, saving custom app data during the transfer *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." *

    * In case any other folder is being uploaded/downloaded, and MegaTransfer::getStage for that transfer returns * a value between the following stages: MegaTransfer::STAGE_SCAN and MegaTransfer::STAGE_PROCESS_TRANSFER_QUEUE * both included, don't use MegaApi::cancelTransfer to cancel this transfer (it could generate a deadlock), * instead of that, use MegaCancelToken::cancel(true) calling through MegaCancelToken instance associated to this transfer. *

    * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param node MegaNode that identifies the file or folder * @param localPath Destination path for the file or folder * If this path is a local folder, it must end with a '\' or '/' character and the file name * in MEGA will be used to store a file inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param fileName Custom file name for the file or folder in local destination * + If you don't need this param provide NULL as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. * + If you don't need this param provide NULL as value * @param startFirst puts the transfer on top of the download queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file download process. * This param is required to be able to cancel the transfer safely by calling MegaCancelToken::cancel(true) * You preserve the ownership of this param. * @param collisionCheck Indicates collision check on same files, valid values are: * - MegaTransfer::COLLISION_CHECK_ASSUMESAME = 1, * - MegaTransfer::COLLISION_CHECK_ALWAYSERROR = 2, * - MegaTransfer::COLLISION_CHECK_FINGERPRINT = 3, * - MegaTransfer::COLLISION_CHECK_METAMAC = 4, * - MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT = 5, * * @param collisionResolution Indicates how to save same files, valid values are: * - MegaTransfer::COLLISION_RESOLUTION_OVERWRITE = 1, * - MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N = 2, * - MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN = 3, * @param listener MegaTransferListener to track this transfer */ public void startDownload(MegaNode node, String localPath, String fileName, String appData, boolean startFirst, MegaCancelToken cancelToken, int collisionCheck, int collisionResolution, MegaTransferListenerInterface listener) { megaApi.startDownload(node, localPath, fileName, appData, startFirst, cancelToken, collisionCheck, collisionResolution, false, createDelegateTransferListener(listener)); } /** * Download a file or a folder from MEGA, saving custom app data during the transfer *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." *

    * In case any other folder is being uploaded/downloaded, and MegaTransfer::getStage for that transfer returns * a value between the following stages: MegaTransfer::STAGE_SCAN and MegaTransfer::STAGE_PROCESS_TRANSFER_QUEUE * both included, don't use MegaApi::cancelTransfer to cancel this transfer (it could generate a deadlock), * instead of that, use MegaCancelToken::cancel(true) calling through MegaCancelToken instance associated to this transfer. *

    * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param node MegaNode that identifies the file or folder * @param localPath Destination path for the file or folder * If this path is a local folder, it must end with a '\' or '/' character and the file name * in MEGA will be used to store a file inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param fileName Custom file name for the file or folder in local destination * + If you don't need this param provide NULL as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. * + If you don't need this param provide NULL as value * @param startFirst puts the transfer on top of the download queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file download process. * This param is required to be able to cancel the transfer safely by calling MegaCancelToken::cancel(true) * You preserve the ownership of this param. * @param collisionCheck Indicates collision check on same files, valid values are: * - MegaTransfer::COLLISION_CHECK_ASSUMESAME = 1, * - MegaTransfer::COLLISION_CHECK_ALWAYSERROR = 2, * - MegaTransfer::COLLISION_CHECK_FINGERPRINT = 3, * - MegaTransfer::COLLISION_CHECK_METAMAC = 4, * - MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT = 5, * * @param collisionResolution Indicates how to save same files, valid values are: * - MegaTransfer::COLLISION_RESOLUTION_OVERWRITE = 1, * - MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N = 2, * - MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN = 3, */ public void startDownload(MegaNode node, String localPath, String fileName, String appData, boolean startFirst, MegaCancelToken cancelToken, int collisionCheck, int collisionResolution) { megaApi.startDownload(node, localPath, fileName, appData, startFirst, cancelToken, collisionCheck, collisionResolution, false); } /** * Start an streaming download for a file in MEGA *

    * Streaming downloads don't save the downloaded data into a local file. It is provided * in MegaTransferListener::onTransferUpdate in a byte buffer. The pointer is returned by * MegaTransfer::getLastBytes and the size of the buffer in MegaTransfer::getDeltaSize *

    * The same byte array is also provided in the callback MegaTransferListener::onTransferData for * compatibility with other programming languages. Only the MegaTransferListener passed to this function * will receive MegaTransferListener::onTransferData callbacks. MegaTransferListener objects registered * with MegaApi::addTransferListener won't receive them for performance reasons *

    * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * @param node MegaNode that identifies the file * @param startPos First byte to download from the file * @param size Size of the data to download * @param listener MegaTransferListener to track this transfer */ public void startStreaming(MegaNode node, long startPos, long size, MegaTransferListenerInterface listener) { megaApi.startStreaming(node, startPos, size, createDelegateTransferListener(listener)); } /** * Cancel a transfer. *

    * When a transfer is cancelled, it will finish and will provide the error code * MegaError.API_EINCOMPLETE in MegaTransferListener.onTransferFinish() and * MegaListener.onTransferFinish(). *

    * The associated request type with this request is MegaRequest.TYPE_CANCEL_TRANSFER * Valid data in the MegaRequest object received on callbacks:
    * - MegaRequest.getTransferTag() - Returns the tag of the cancelled transfer (MegaTransfer.getTag). * * @param transfer MegaTransfer object that identifies the transfer. * You can get this object in any MegaTransferListener callback or any MegaListener callback * related to transfers. * @param listener MegaRequestListener to track this request. */ public void cancelTransfer(MegaTransfer transfer, MegaRequestListenerInterface listener) { megaApi.cancelTransfer(transfer, createDelegateRequestListener(listener)); } /** * Cancel a transfer. * * @param transfer MegaTransfer object that identifies the transfer. * You can get this object in any MegaTransferListener callback or any MegaListener callback * related to transfers. */ public void cancelTransfer(MegaTransfer transfer) { megaApi.cancelTransfer(transfer); } /** * Move a transfer one position up in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferUp(MegaTransfer transfer, MegaRequestListenerInterface listener) { megaApi.moveTransferUp(transfer, createDelegateRequestListener(listener)); } /** * Move a transfer one position up in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transfer Transfer to move */ public void moveTransferUp(MegaTransfer transfer) { megaApi.moveTransferUp(transfer); } /** * Move a transfer one position up in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferUpByTag(int transferTag, MegaRequestListenerInterface listener) { megaApi.moveTransferUpByTag(transferTag, createDelegateRequestListener(listener)); } /** * Move a transfer one position up in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transferTag Tag of the transfer to move */ public void moveTransferUpByTag(int transferTag) { megaApi.moveTransferUpByTag(transferTag); } /** * Move a transfer one position down in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferDown(MegaTransfer transfer, MegaRequestListenerInterface listener) { megaApi.moveTransferDown(transfer, createDelegateRequestListener(listener)); } /** * Move a transfer one position down in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transfer Transfer to move */ public void moveTransferDown(MegaTransfer transfer) { megaApi.moveTransferDown(transfer); } /** * Move a transfer one position down in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferDownByTag(int transferTag, MegaRequestListenerInterface listener) { megaApi.moveTransferDownByTag(transferTag, createDelegateRequestListener(listener)); } /** * Move a transfer one position down in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transferTag Tag of the transfer to move */ public void moveTransferDownByTag(int transferTag) { megaApi.moveTransferDownByTag(transferTag); } /** * Move a transfer to the top of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferToFirst(MegaTransfer transfer, MegaRequestListenerInterface listener) { megaApi.moveTransferToFirst(transfer, createDelegateRequestListener(listener)); } /** * Move a transfer to the top of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transfer Transfer to move */ public void moveTransferToFirst(MegaTransfer transfer) { megaApi.moveTransferToFirst(transfer); } /** * Move a transfer to the top of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferToFirstByTag(int transferTag, MegaRequestListenerInterface listener) { megaApi.moveTransferToFirstByTag(transferTag, createDelegateRequestListener(listener)); } /** * Move a transfer to the top of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transferTag Tag of the transfer to move */ public void moveTransferToFirstByTag(int transferTag) { megaApi.moveTransferToFirstByTag(transferTag); } /** * Move a transfer to the bottom of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferToLast(MegaTransfer transfer, MegaRequestListenerInterface listener) { megaApi.moveTransferToLast(transfer, createDelegateRequestListener(listener)); } /** * Move a transfer to the bottom of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transfer Transfer to move */ public void moveTransferToLast(MegaTransfer transfer) { megaApi.moveTransferToLast(transfer); } /** * Move a transfer to the bottom of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ public void moveTransferToLastByTag(int transferTag, MegaRequestListenerInterface listener) { megaApi.moveTransferToLastByTag(transferTag, createDelegateRequestListener(listener)); } /** * Move a transfer to the bottom of the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transferTag Tag of the transfer to move */ public void moveTransferToLastByTag(int transferTag) { megaApi.moveTransferToLastByTag(transferTag); } /** * Move a transfer before another one in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transfer Transfer to move * @param prevTransfer Transfer with the target position * @param listener MegaRequestListener to track this request */ public void moveTransferBefore(MegaTransfer transfer, MegaTransfer prevTransfer, MegaRequestListenerInterface listener) { megaApi.moveTransferBefore(transfer, prevTransfer, createDelegateRequestListener(listener)); } /** * Move a transfer before another one in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transfer Transfer to move * @param prevTransfer Transfer with the target position */ public void moveTransferBefore(MegaTransfer transfer, MegaTransfer prevTransfer) { megaApi.moveTransferBefore(transfer, prevTransfer); } /** * Move a transfer before another one in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transferTag Tag of the transfer to move * @param prevTransferTag Tag of the transfer with the target position * @param listener MegaRequestListener to track this request */ public void moveTransferBeforeByTag(int transferTag, int prevTransferTag, MegaRequestListenerInterface listener) { megaApi.moveTransferBeforeByTag(transferTag, prevTransferTag, createDelegateRequestListener(listener)); } /** * Move a transfer before another one in the transfer queue *

    * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority *

    * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transferTag Tag of the transfer to move * @param prevTransferTag Tag of the transfer with the target position */ public void moveTransferBeforeByTag(int transferTag, int prevTransferTag) { megaApi.moveTransferBeforeByTag(transferTag, prevTransferTag); } /** * Cancel the transfer with a specific tag *

    * When a transfer is cancelled, it will finish and will provide the error code * MegaError::API_EINCOMPLETE in MegaTransferListener::onTransferFinish and * MegaListener::onTransferFinish *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the cancelled transfer (MegaTransfer::getTag) * * @param transferTag tag that identifies the transfer * You can get this tag using MegaTransfer::getTag * @param listener MegaRequestListener to track this request */ public void cancelTransferByTag(int transferTag, MegaRequestListenerInterface listener) { megaApi.cancelTransferByTag(transferTag, createDelegateRequestListener(listener)); } /** * Cancel the transfer with a specific tag. * * @param transferTag tag that identifies the transfer. * You can get this tag using MegaTransfer.getTag(). */ public void cancelTransferByTag(int transferTag) { megaApi.cancelTransferByTag(transferTag); } /** * Cancel all transfers of the same type *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the first parameter * * @param direction Type of transfers to cancel. * Valid values are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * @param listener MegaRequestListener to track this request */ public void cancelTransfers(int direction, MegaRequestListenerInterface listener) { megaApi.cancelTransfers(direction, createDelegateRequestListener(listener)); } /** * Cancel all transfers of the same type *

    * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the first parameter * * @param direction Type of transfers to cancel. * Valid values are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 */ public void cancelTransfers(int direction) { megaApi.cancelTransfers(direction); } /** * Pause/resume all transfers *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * * @param pause true to pause all transfers / false to resume all transfers * @param listener MegaRequestListener to track this request */ public void pauseTransfers(boolean pause, MegaRequestListenerInterface listener) { megaApi.pauseTransfers(pause, createDelegateRequestListener(listener)); } /** * Pause/resume all transfers *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * * @param pause true to pause all transfers / false to resume all transfers */ public void pauseTransfers(boolean pause) { megaApi.pauseTransfers(pause); } /** * Pause/resume all transfers in one direction (uploads or downloads) *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * - MegaRequest::getNumber - Returns the direction of the transfers to pause/resume * * @param pause true to pause transfers / false to resume transfers * @param direction Direction of transfers to pause/resume * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * @param listener MegaRequestListener to track this request */ public void pauseTransfers(boolean pause, int direction, MegaRequestListenerInterface listener) { megaApi.pauseTransfers(pause, direction, createDelegateRequestListener(listener)); } /** * Pause/resume all transfers in one direction (uploads or downloads) *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * - MegaRequest::getNumber - Returns the direction of the transfers to pause/resume * * @param pause true to pause transfers / false to resume transfers * @param direction Direction of transfers to pause/resume * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 */ public void pauseTransfers(boolean pause, int direction) { megaApi.pauseTransfers(pause, direction); } /** * Pause/resume a transfer *

    * The request finishes with MegaError::API_OK if the state of the transfer is the * desired one at that moment. That means that the request succeed when the transfer * is successfully paused or resumed, but also if the transfer was already in the * desired state and it wasn't needed to change anything. *

    * Resumed transfers don't necessarily continue just after the resumption. They * are tagged as queued and are processed according to its position on the request queue. *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to pause or resume * - MegaRequest::getFlag - Returns true if the transfer has to be pause or false if it has to be resumed * * @param transfer Transfer to pause or resume * @param pause True to pause the transfer or false to resume it * @param listener MegaRequestListener to track this request */ public void pauseTransfer(MegaTransfer transfer, boolean pause, MegaRequestListenerInterface listener) { megaApi.pauseTransfer(transfer, pause, createDelegateRequestListener(listener)); } /** * Pause/resume a transfer *

    * The request finishes with MegaError::API_OK if the state of the transfer is the * desired one at that moment. That means that the request succeed when the transfer * is successfully paused or resumed, but also if the transfer was already in the * desired state and it wasn't needed to change anything. *

    * Resumed transfers don't necessarily continue just after the resumption. They * are tagged as queued and are processed according to its position on the request queue. *

    * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to pause or resume * - MegaRequest::getFlag - Returns true if the transfer has to be pause or false if it has to be resumed * * @param transferTag Tag of the transfer to pause or resume * @param pause True to pause the transfer or false to resume it * @param listener MegaRequestListener to track this request */ public void pauseTransferByTag(int transferTag, boolean pause, MegaRequestListenerInterface listener) { megaApi.pauseTransferByTag(transferTag, pause, createDelegateRequestListener(listener)); } /** * Returns the state (paused/unpaused) of transfers * * @param direction Direction of transfers to check * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * @return true if transfers on that direction are paused, false otherwise */ public boolean areTransfersPaused(int direction) { return megaApi.areTransfersPaused(direction); } /** * Resume incomplete transfers started while not logged in *

    * This method resumes transfers that were cached while using a non-logged-in MegaApi * instance *

    * This method can be called when the app detects that there is no session to resume. * If a valid session exists, the app should proceed with resuming it, and calling * this method will have no effect. * * @note If there are transfers in progress and the app logs in, * any incomplete transfers will be aborted immediately. *

    * Please avoid calling this method when logged in. */ public void resumeTransfersForNotLoggedInInstance() { megaApi.resumeTransfersForNotLoggedInInstance(); } /** * Set the transfer method for downloads *

    * Valid methods are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. *

    * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. *

    * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically *

    * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. *

    * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @param method Selected transfer method for downloads */ public void setDownloadMethod(int method) { megaApi.setDownloadMethod(method); } /** * Set the transfer method for uploads *

    * Valid methods are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. *

    * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. *

    * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically *

    * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. *

    * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @param method Selected transfer method for uploads */ public void setUploadMethod(int method) { megaApi.setUploadMethod(method); } /** * Get the maximum download speed in bytes per second *

    * The value 0 means unlimited speed * * @return Download speed in bytes per second */ public int getMaxDownloadSpeed() { return megaApi.getMaxDownloadSpeed(); } /** * Get the maximum upload speed in bytes per second *

    * The value 0 means unlimited speed * * @return Upload speed in bytes per second */ public int getMaxUploadSpeed() { return megaApi.getMaxUploadSpeed(); } /** * Return the current download speed * * @return Download speed in bytes per second */ public int getCurrentDownloadSpeed() { return megaApi.getCurrentDownloadSpeed(); } /** * Return the current download speed * * @return Download speed in bytes per second */ public int getCurrentUploadSpeed() { return megaApi.getCurrentUploadSpeed(); } /** * Return the current transfer speed * * @param type Type of transfer to get the speed. * Valid values are MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * @return Transfer speed for the transfer type, or 0 if the parameter is invalid */ public int getCurrentSpeed(int type) { return megaApi.getCurrentSpeed(type); } /** * Get the active transfer method for downloads *

    * Valid values for the return parameter are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. *

    * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. *

    * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically *

    * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. *

    * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @return Active transfer method for downloads */ public int getDownloadMethod() { return megaApi.getDownloadMethod(); } /** * Get the active transfer method for uploads *

    * Valid values for the return parameter are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. *

    * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. *

    * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically *

    * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. *

    * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @return Active transfer method for uploads */ public int getUploadMethod() { return megaApi.getUploadMethod(); } /** * Get information about transfer queues * * @param listener MegaTransferListener to start receiving information about transfers * @return Information about transfer queues */ @Nullable public MegaTransferData getTransferData(MegaTransferListenerInterface listener) { return megaApi.getTransferData(createDelegateTransferListener(listener, true)); } /** * Get information about transfer queues * * @return Information about transfer queues */ @Nullable public MegaTransferData getTransferData() { return megaApi.getTransferData(); } /** * Get the first transfer in a transfer queue *

    * You take the ownership of the returned value. * * @param type Transfer queue to get the first transfer (MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD) * @return MegaTransfer object related to the first transfer in the queue or NULL if there isn't any transfer */ public MegaTransfer getFirstTransfer(int type) { return megaApi.getFirstTransfer(type); } /** * Force an onTransferUpdate callback for the specified transfer *

    * The callback will be received by transfer listeners registered to receive all * callbacks related to callbacks and additionally by the listener in the last * parameter of this function, if it's not NULL. * * @param transfer Transfer that will be provided in the onTransferUpdate callback * @param listener Listener that will receive the callback */ public void notifyTransfer(MegaTransfer transfer, MegaTransferListenerInterface listener) { megaApi.notifyTransfer(transfer, createDelegateTransferListener(listener)); } /** * Force an onTransferUpdate callback for the specified transfer *

    * The callback will be received by transfer listeners registered to receive all * callbacks related to callbacks and additionally by the listener in the last * parameter of this function, if it's not NULL. * * @param transferTag Tag of the transfer that will be provided in the onTransferUpdate callback * @param listener Listener that will receive the callback */ public void notifyTransferByTag(int transferTag, MegaTransferListenerInterface listener) { megaApi.notifyTransferByTag(transferTag, createDelegateTransferListener(listener)); } /** * Get the transfer with a unique id *

    * That unique identifier of a transfer can be retrieved using MegaTransfer::getUniqueId *

    * You take the ownership of the returned value * * @param transferUniqueId unique Id to find * @return MegaTransfer object with that unique Id, or nullptr if there isn't any * active transfer with it */ @Nullable public MegaTransfer getTransferByUniqueId(long transferUniqueId) { return megaApi.getTransferByUniqueId(transferUniqueId); } /** * Get all active transfers *

    * You take the ownership of the returned value * * @return List with all active transfers * @see MegaApi::startUpload, MegaApi::startDownload */ @Nullable public ArrayList getTransfers() { return transferListToArray(megaApi.getTransfers()); } /** * Get the transfer with a transfer tag *

    * That tag can be got using MegaTransfer::getTag *

    * You take the ownership of the returned value * * @param transferTag tag to check * @return MegaTransfer object with that tag, or NULL if there isn't any * active transfer with it */ @Nullable public MegaTransfer getTransferByTag(int transferTag) { return megaApi.getTransferByTag(transferTag); } /** * Get all transfers of a specific type (downloads or uploads) *

    * If the parameter isn't MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * this function returns an empty list. *

    * You take the ownership of the returned value * * @param type MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * @return List with transfers of the desired type */ @Nullable public ArrayList getTransfers(int type) { return transferListToArray(megaApi.getTransfers(type)); } /** * Get a list of transfers that belong to a folder transfer *

    * This function provides the list of transfers started in the context * of a folder transfer. *

    * If the tag in the parameter doesn't belong to a folder transfer, * this function returns an empty list. *

    * The transfers provided by this function are the ones that are added to the * transfer queue when this function is called. Finished transfers, or transfers * not added to the transfer queue yet (for example, uploads that are waiting for * the creation of the parent folder in MEGA) are not returned by this function. *

    * You take the ownership of the returned value * * @param transferTag Tag of the folder transfer to check * @return List of transfers in the context of the selected folder transfer * @see MegaTransfer::isFolderTransfer, MegaTransfer::getFolderTransferTag */ @Nullable public ArrayList getChildTransfers(int transferTag) { return transferListToArray(megaApi.getChildTransfers(transferTag)); } /** * Check if the SDK is waiting to complete a request and get the reason * * @return State of SDK. *

    * Valid values are: * - MegaApi::RETRY_NONE = 0 * SDK is not waiting for the server to complete a request *

    * - MegaApi::RETRY_CONNECTIVITY = 1 * SDK is waiting for the server to complete a request due to connectivity issues *

    * - MegaApi::RETRY_SERVERS_BUSY = 2 * SDK is waiting for the server to complete a request due to a HTTP error 500 *

    * - MegaApi::RETRY_API_LOCK = 3 * SDK is waiting for the server to complete a request due to an API lock (API error -3) *

    * - MegaApi::RETRY_RATE_LIMIT = 4, * SDK is waiting for the server to complete a request due to a rate limit (API error -4) *

    * - MegaApi::RETRY_LOCAL_LOCK = 5 * SDK is waiting for a local locked file *

    * - MegaApi::RETRY_UNKNOWN = 6 * SDK is waiting for the server to complete a request with unknown reason */ public int isWaiting() { return megaApi.isWaiting(); } /** * Get the total number of nodes in the account * * @return Total number of nodes in the account */ public BigInteger getNumNodes() { return megaApi.getNumNodes(); } /** * Starts an unbuffered download of a node (file) from the user's MEGA account. * * @param node The MEGA node to download. * @param startOffset long. The byte to start from. * @param size long. Size of the download. * @param outputStream The output stream object to use for this download. * @param listener MegaRequestListener to track this request. */ public void startUnbufferedDownload(MegaNode node, long startOffset, long size, OutputStream outputStream, MegaTransferListenerInterface listener) { DelegateMegaTransferListener delegateListener = new DelegateOutputMegaTransferListener(this, outputStream, listener, true); activeTransferListeners.add(delegateListener); megaApi.startStreaming(node, startOffset, size, delegateListener); } /** * Starts an unbuffered download of a node (file) from the user's MEGA account. * * @param node The MEGA node to download. * @param outputStream The output stream object to use for this download. * @param listener MegaRequestListener to track this request. */ public void startUnbufferedDownload(MegaNode node, OutputStream outputStream, MegaTransferListenerInterface listener) { startUnbufferedDownload(node, 0, node.getSize(), outputStream, listener); } //****************************************************************************************************/ // FILESYSTEM METHODS //****************************************************************************************************/ /** * Get the number of child nodes *

    * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 *

    * This function doesn't search recursively, only returns the direct child nodes. * * @param parent Parent node * @return Number of child nodes */ public int getNumChildren(MegaNode parent) { return megaApi.getNumChildren(parent); } /** * Get the number of child files of a node *

    * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 *

    * This function doesn't search recursively, only returns the direct child files. * * @param parent Parent node * @return Number of child files */ public int getNumChildFiles(MegaNode parent) { return megaApi.getNumChildFiles(parent); } /** * Get the number of child folders of a node *

    * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 *

    * This function doesn't search recursively, only returns the direct child folders. * * @param parent Parent node * @return Number of child folders */ public int getNumChildFolders(MegaNode parent) { return megaApi.getNumChildFolders(parent); } /** * Get children of a particular parent or a predefined location, and allow filtering * the results. @see MegaSearchFilter * The look-up is case-insensitive. * For invalid filtering options, this function returns an empty list. * * You take the ownership of the returned value * * This function allows to cancel the processing at any time by passing a MegaCancelToken and calling * to MegaCancelToken::setCancelFlag(true). * * @param filter Container for filtering options. In order to be considered valid it must * - be not null * - have valid ancestor handle (different than INVALID_HANDLE) set by calling byLocationHandle(), * and in consequence it must have default value for location (SEARCH_TARGET_ALL) * @param order Order for the returned list * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Folders first in alphabetical order, then files in the same order * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Files first in reverse alphabetical order, then folders in the same order * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, ascending * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, descending * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, ascending * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, descending * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, ascending * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, descending * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, ascending. With this order, folders are returned first, then files * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, descending. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * * @param cancelToken MegaCancelToken to be able to cancel the processing at any time. * @param searchPage Container for pagination options; if null, all results will be returned * * @return List with found children as MegaNode objects */ public ArrayList getChildren(MegaSearchFilter filter, int order, MegaCancelToken cancelToken, MegaSearchPage searchPage) { return nodeListToArray(megaApi.getChildren(filter, order, cancelToken, searchPage)); } /** * Get all children of a MegaNode *

    * If the parent node doesn't exist or it isn't a folder, this function * returns an empty list *

    * You take the ownership of the returned value * * @param parent Parent node * @param order Order for the returned list * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order *

    * - MegaApi::ORDER_DEFAULT_ASC = 1 * Folders first in alphabetical order, then files in the same order *

    * - MegaApi::ORDER_DEFAULT_DESC = 2 * Files first in reverse alphabetical order, then folders in the same order *

    * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, ascending *

    * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, descending *

    * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, ascending *

    * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, descending *

    * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, ascending *

    * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, descending *

    * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, ascending. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, descending. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * @return List with all child MegaNode objects */ public ArrayList getChildren(MegaNode parent, int order) { return nodeListToArray(megaApi.getChildren(parent, order)); } /** * Get all children of a list of MegaNodes *

    * If any parent node doesn't exist or it isn't a folder, that parent * will be skipped. *

    * You take the ownership of the returned value * * @param parentNodes List of parent nodes * @param order Order for the returned list * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order *

    * - MegaApi::ORDER_DEFAULT_ASC = 1 * Folders first in alphabetical order, then files in the same order *

    * - MegaApi::ORDER_DEFAULT_DESC = 2 * Files first in reverse alphabetical order, then folders in the same order *

    * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, ascending *

    * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, descending *

    * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, ascending *

    * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, descending *

    * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, ascending *

    * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, descending *

    * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, ascending. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, descending. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files *

    * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * @return List with all child MegaNode objects */ public ArrayList getChildren(MegaNodeList parentNodes, int order) { return nodeListToArray(megaApi.getChildren(parentNodes, order)); } /** * Get all children of a MegaNode *

    * If the parent node doesn't exist or it isn't a folder, this function * returns an empty list *

    * You take the ownership of the returned value * * @param parent Parent node * @return List with all child MegaNode objects */ public ArrayList getChildren(MegaNode parent) { return nodeListToArray(megaApi.getChildren(parent)); } /** * Get all versions of a file * * @param node Node to check * @return List with all versions of the node, including the current version */ public ArrayList getVersions(MegaNode node) { return nodeListToArray(megaApi.getVersions(node)); } /** * Get the number of versions of a file * * @param node Node to check * @return Number of versions of the node, including the current version */ public int getNumVersions(MegaNode node) { return megaApi.getNumVersions(node); } /** * Check if a file has previous versions * * @param node Node to check * @return true if the node has any previous version */ public boolean hasVersions(MegaNode node) { return megaApi.hasVersions(node); } /** * Get information about the contents of a folder *

    * The associated request type with this request is MegaRequest::TYPE_FOLDER_INFO * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaFolderInfo - MegaFolderInfo object with the information related to the folder * * @param node Folder node to inspect * @param listener MegaRequestListener to track this request */ public void getFolderInfo(MegaNode node, MegaRequestListenerInterface listener) { megaApi.getFolderInfo(node, createDelegateRequestListener(listener)); } /** * Returns true if the node has children * * @return true if the node has children */ public boolean hasChildren(MegaNode parent) { return megaApi.hasChildren(parent); } /** * Get the child node with the provided name *

    * If the node doesn't exist, this function returns NULL *

    * You take the ownership of the returned value * * @param parent Parent node * @param name Name of the node * @return The MegaNode that has the selected parent and name */ @Nullable public MegaNode getChildNode(MegaNode parent, String name) { return megaApi.getChildNode(parent, name); } /** * Get the parent node of a MegaNode *

    * If the node doesn't exist in the account or * it is a root node, this function returns NULL *

    * You take the ownership of the returned value. * * @param node MegaNode to get the parent * @return The parent of the provided node */ @Nullable public MegaNode getParentNode(MegaNode node) { return megaApi.getParentNode(node); } /** * Get the path of a MegaNode *

    * If the node doesn't exist, this function returns NULL. * You can recover the node later using MegaApi::getNodeByPath * except if the path contains names with '/', '\' or ':' characters. *

    * You take the ownership of the returned value * * @param node MegaNode for which the path will be returned * @return The path of the node */ @Nullable public String getNodePath(MegaNode node) { return megaApi.getNodePath(node); } /** * Get the path of a Node given its MegaHandle *

    * If the node doesn't exist, this function returns NULL. * You can recover the node later using MegaApi::getNodeByPath * except if the path contains names with '/', '\' or ':' characters. *

    * You take the ownership of the returned value * * @param handle MegaNode handle for which the path will be returned * @return The path of the node */ @Nullable public String getNodePathByHandle(Long handle) { return megaApi.getNodePathByNodeHandle(handle); } /** * Get the MegaNode in a specific path in the MEGA account *

    * The path separator character is '/' * The Root node is / * The Inbox root node is //in/ * The Rubbish root node is //bin/ *

    * Paths with names containing '/', '\' or ':' aren't compatible * with this function. *

    * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. *

    * You take the ownership of the returned value * * @param path Path to check * @param n Base node if the path is relative * @return The MegaNode object in the path, otherwise NULL */ @Nullable public MegaNode getNodeByPath(String path, MegaNode n) { return megaApi.getNodeByPath(path, n); } /** * Get the MegaNode in a specific path in the MEGA account *

    * The path separator character is '/' * The Root node is / * The Inbox root node is //in/ * The Rubbish root node is //bin/ *

    * Paths with names containing '/', '\' or ':' aren't compatible * with this function. *

    * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. *

    * You take the ownership of the returned value * * @param path Path to check * @return The MegaNode object in the path, otherwise NULL */ @Nullable public MegaNode getNodeByPath(String path) { return megaApi.getNodeByPath(path); } /** * Get the MegaNode that has a specific handle *

    * You can get the handle of a MegaNode using MegaNode::getHandle. The same handle * can be got in a Base64-encoded string using MegaNode::getBase64Handle. Conversions * between these formats can be done using MegaApi::base64ToHandle and MegaApi::handleToBase64 *

    * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. *

    * You take the ownership of the returned value. * * @param h Node handle to check * @return MegaNode object with the handle, otherwise NULL */ @Nullable public MegaNode getNodeByHandle(long h) { return megaApi.getNodeByHandle(h); } /** * Generate a TOTP token and its lifetime with the data stored in the node with the * given handle. * * This performs a synchronous operation. * * @param handle The handle of the password node with the required totp data needed to * compute the totp token and its lifetime. * @return A MegaTotpTokenGenResult with: * - `errorCode`: An error code that can be one of: * + API_EARGS: The input handle is `UNDEF` * + API_ENOENT: The input handle does not correspond to a password node * + API_EKEY: The input handle corresponds to a password node with no TOTP data * + API_EINTERNAL: The TOTP data stored in the password node is ill-formed and cannot be * used to generate valid tokens. * + API_OK: the generation succeeded and the result can be retrieved from `second` * - `MegaTotpTokenLifetime`: * + `token`: The generated token * + `remainingLifeTimeSeconds`: The remaining time */ public MegaTotpTokenGenResult generateTotpTokenFromNode(long handle){ return megaApi.generateTotpTokenFromNode(handle); } /** * Get the MegaContactRequest that has a specific handle *

    * You can get the handle of a MegaContactRequest using MegaContactRequest::getHandle. *

    * You take the ownership of the returned value. * * @param handle Contact request handle to check * @return MegaContactRequest object with the handle, otherwise NULL */ @Nullable public MegaContactRequest getContactRequestByHandle(long handle) { return megaApi.getContactRequestByHandle(handle); } /** * Get all contacts of this MEGA account *

    * You take the ownership of the returned value * * @return List of MegaUser object with all contacts of this account */ public ArrayList getContacts() { return userListToArray(megaApi.getContacts()); } /** * Get the MegaUser that has a specific email address *

    * You can get the email of a MegaUser using MegaUser::getEmail *

    * You take the ownership of the returned value * * @param user Email or Base64 handle of the user * @return MegaUser that has the email address, otherwise NULL */ @Nullable public MegaUser getContact(String user) { return megaApi.getContact(user); } /** * Get all MegaUserAlerts for the logged in user *

    * You take the ownership of the returned value * * @return List of MegaUserAlert objects */ public ArrayList getUserAlerts() { return userAlertListToArray(megaApi.getUserAlerts()); } /** * Get the number of unread user alerts for the logged in user * * @return Number of unread user alerts */ public int getNumUnreadUserAlerts() { return megaApi.getNumUnreadUserAlerts(); } /** * Get a list with all inbound sharings from one MegaUser *

    * You take the ownership of the returned value * * @param user MegaUser sharing folders with this account * @return List of MegaNode objects that this user is sharing with this account */ public ArrayList getInShares(MegaUser user) { return nodeListToArray(megaApi.getInShares(user)); } /** * Get a list with all inbound sharings from one MegaUser *

    * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC *

    * You take the ownership of the returned value * * @param user MegaUser sharing folders with this account * @param order Sorting order to use * @return List of MegaNode objects that this user is sharing with this account */ public ArrayList getInShares(MegaUser user, int order) { return nodeListToArray(megaApi.getInShares(user, order)); } /** * Get a list with all inbound sharings *

    * You take the ownership of the returned value * * @return List of MegaNode objects that other users are sharing with this account */ public ArrayList getInShares() { return nodeListToArray(megaApi.getInShares()); } /** * Get a list with all inbound sharings *

    * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaNode objects that other users are sharing with this account */ public ArrayList getInShares(int order) { return nodeListToArray(megaApi.getInShares(order)); } /** * Get a list with all active inbound sharings *

    * You take the ownership of the returned value * * @return List of MegaShare objects that other users are sharing with this account */ public ArrayList getInSharesList() { return shareListToArray(megaApi.getInSharesList()); } /** * Get a list with all active inbound sharings *

    * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects that other users are sharing with this account */ public ArrayList getInSharesList(int order) { return shareListToArray(megaApi.getInSharesList(order)); } /** * Get a list with all unverified inbound sharings *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects that other users are sharing with this account */ public ArrayList getUnverifiedIncomingShares(int order) { return shareListToArray(megaApi.getUnverifiedInShares(order)); } /** * Get the user relative to an incoming share *

    * This function will return NULL if the node is not found *

    * When recurse is true and the root of the specified node is not an incoming share, * this function will return NULL. * When recurse is false and the specified node doesn't represent the root of an * incoming share, this function will return NULL. *

    * You take the ownership of the returned value * * @param node Node to look for inshare user. * @return MegaUser relative to the incoming share */ @Nullable public MegaUser getUserFromInShare(MegaNode node) { return megaApi.getUserFromInShare(node); } /** * Get the user relative to an incoming share *

    * This function will return NULL if the node is not found *

    * When recurse is true and the root of the specified node is not an incoming share, * this function will return NULL. * When recurse is false and the specified node doesn't represent the root of an * incoming share, this function will return NULL. *

    * You take the ownership of the returned value * * @param node Node to look for inshare user. * @param recurse use root node corresponding to the node passed * @return MegaUser relative to the incoming share */ @Nullable public MegaUser getUserFromInShare(MegaNode node, boolean recurse) { return megaApi.getUserFromInShare(node, recurse); } /** * Check if a MegaNode is pending to be shared with another User. This situation * happens when a node is to be shared with a User which is not a contact yet. *

    * For nodes that are pending to be shared, you can get a list of MegaNode * objects using MegaApi::getPendingShares * * @param node Node to check * @return true is the MegaNode is pending to be shared, otherwise false */ public boolean isPendingShare(MegaNode node) { return megaApi.isPendingShare(node); } /** * Get a list with all active and pending outbound sharings *

    * You take the ownership of the returned value * * @return List of MegaShare objects */ public ArrayList getOutShares() { return shareListToArray(megaApi.getOutShares()); } /** * Get a list with all active and pending outbound sharings *

    * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects */ public ArrayList getOutShares(int order) { return shareListToArray(megaApi.getOutShares(order)); } /** * Get a list with the active and pending outbound sharings for a MegaNode *

    * If the node doesn't exist in the account, this function returns an empty list. *

    * You take the ownership of the returned value * * @param node MegaNode to check * @return List of MegaShare objects */ public ArrayList getOutShares(MegaNode node) { return shareListToArray(megaApi.getOutShares(node)); } /** * Get a list with all unverified sharings *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaShare objects */ public ArrayList getUnverifiedOutgoingShares(int order) { return shareListToArray(megaApi.getUnverifiedOutShares(order)); } /** * Check if a node belongs to your own cloud * * @param handle Node to check * @return True if it belongs to your own cloud */ public boolean isPrivateNode(long handle) { return megaApi.isPrivateNode(handle); } /** * Check if a node does NOT belong to your own cloud *

    * In example, nodes from incoming shared folders do not belong to your cloud. * * @param handle Node to check * @return True if it does NOT belong to your own cloud */ public boolean isForeignNode(long handle) { return megaApi.isForeignNode(handle); } /** * Get a list with all public links *

    * You take the ownership of the returned value * * @return List of MegaNode objects that are shared with everyone via public link */ public ArrayList getPublicLinks() { return nodeListToArray(megaApi.getPublicLinks()); } /** * Get a list with all public links *

    * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC, MegaApi::ORDER_LINK_CREATION_ASC, * MegaApi::ORDER_LINK_CREATION_DESC *

    * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaNode objects that are shared with everyone via public link */ public ArrayList getPublicLinks(int order) { return nodeListToArray(megaApi.getPublicLinks(order)); } /** * Get a list with all incoming contact requests. *

    * You take the ownership of the returned value * * @return List of MegaContactRequest objects. */ public ArrayList getIncomingContactRequests() { return contactRequestListToArray(megaApi.getIncomingContactRequests()); } /** * Get a list with all outgoing contact requests. *

    * You take the ownership of the returned value * * @return List of MegaContactRequest objects. */ public ArrayList getOutgoingContactRequests() { return contactRequestListToArray(megaApi.getOutgoingContactRequests()); } /** * Get the access level of a MegaNode * * @param node MegaNode to check * @return Access level of the node * Valid values are: * - MegaShare::ACCESS_OWNER * - MegaShare::ACCESS_FULL * - MegaShare::ACCESS_READWRITE * - MegaShare::ACCESS_READ * - MegaShare::ACCESS_UNKNOWN */ public int getAccess(MegaNode node) { return megaApi.getAccess(node); } /** * Get the size of a node tree *

    * If the MegaNode is a file, this function returns the size of the file. * If it's a folder, this function returns the sum of the sizes of all nodes * in the node tree. * * @param node Parent node * @return Size of the node tree */ public long getSize(MegaNode node) { return megaApi.getSize(node); } /** * Get a Base64-encoded fingerprint for a local file *

    * The fingerprint is created taking into account the modification time of the file * and file contents. This fingerprint can be used to get a corresponding node in MEGA * using MegaApi::getNodeByFingerprint *

    * If the file can't be found or can't be opened, this function returns NULL *

    * You take the ownership of the returned value * * @param filePath Local file path * @return Base64-encoded fingerprint for the file */ @Nullable public String getFingerprint(String filePath) { return megaApi.getFingerprint(filePath); } /** * Returns a node with the provided fingerprint *

    * If there isn't any node in the account with that fingerprint, this function returns NULL. *

    * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return MegaNode object with the provided fingerprint */ @Nullable public MegaNode getNodeByFingerprint(String fingerprint) { return megaApi.getNodeByFingerprint(fingerprint); } /** * Returns a node with the provided fingerprint *

    * If there isn't any node in the account with that fingerprint, this function returns NULL. * If there are several nodes with the same fingerprint, nodes in the preferred * parent folder take precedence. *

    * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @param parent Preferred parent node * @return MegaNode object with the provided fingerprint */ @Nullable public MegaNode getNodeByFingerprint(String fingerprint, MegaNode parent) { return megaApi.getNodeByFingerprint(fingerprint, parent); } /** * Returns all nodes that have a fingerprint *

    * If there isn't any node in the account with that fingerprint, this function returns an empty MegaNodeList. *

    * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return List of nodes with the same fingerprint */ public ArrayList getNodesByFingerprint(String fingerprint) { return nodeListToArray(megaApi.getNodesByFingerprint(fingerprint)); } /** * Returns a node with the provided fingerprint that can be exported *

    * If there isn't any node in the account with that fingerprint, this function returns NULL. * If a file name is passed in the second parameter, it's also checked if nodes with a matching * fingerprint has that name. If there isn't any matching node, this function returns NULL. * This function ignores nodes that are inside the Rubbish Bin because public links to those nodes * can't be downloaded. *

    * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @param name Name that the node should have (optional) * @return Exportable node that meet the requirements */ @Nullable public MegaNode getExportableNodeByFingerprint(String fingerprint, String name) { return megaApi.getExportableNodeByFingerprint(fingerprint, name); } /** * Returns a node with the provided fingerprint that can be exported *

    * If there isn't any node in the account with that fingerprint, this function returns NULL. * If there isn't any matching node, this function returns NULL. * This function ignores nodes that are inside the Rubbish Bin because public links to those nodes * can't be downloaded. *

    * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return Exportable node that meet the requirements */ @Nullable public MegaNode getExportableNodeByFingerprint(String fingerprint) { return megaApi.getExportableNodeByFingerprint(fingerprint); } /** * Check if the account already has a node with the provided fingerprint *

    * A fingerprint for a local file can be generated using MegaApi::getFingerprint * * @param fingerprint Fingerprint to check * @return true if the account contains a node with the same fingerprint */ public boolean hasFingerprint(String fingerprint) { return megaApi.hasFingerprint(fingerprint); } /** * getCRC Get the CRC of a file *

    * The CRC of a file is a hash of its contents. * If you need a more reliable method to check files, use fingerprint functions * (MegaApi::getFingerprint, MegaApi::getNodeByFingerprint) that also takes into * account the size and the modification time of the file to create the fingerprint. *

    * You take the ownership of the returned value. * * @param filePath Local file path * @return Base64-encoded CRC of the file */ @Nullable public String getCRC(String filePath) { return megaApi.getCRC(filePath); } /** * Get the CRC from a fingerprint *

    * You take the ownership of the returned value. * * @param fingerprint fingerprint from which we want to get the CRC * @return Base64-encoded CRC from the fingerprint */ @Nullable public String getCRCFromFingerprint(String fingerprint) { return megaApi.getCRCFromFingerprint(fingerprint); } /** * getCRC Get the CRC of a node *

    * The CRC of a node is a hash of its contents. * If you need a more reliable method to check files, use fingerprint functions * (MegaApi::getFingerprint, MegaApi::getNodeByFingerprint) that also takes into * account the size and the modification time of the node to create the fingerprint. *

    * You take the ownership of the returned value. * * @param node Node for which we want to get the CRC * @return Base64-encoded CRC of the node */ @Nullable public String getCRC(MegaNode node) { return megaApi.getCRC(node); } /** * getNodeByCRC Returns a node with the provided CRC *

    * If there isn't any node in the selected folder with that CRC, this function returns NULL. * If there are several nodes with the same CRC, anyone can be returned. *

    * You take the ownership of the returned value. * * @param crc CRC to check * @param parent Parent node to scan. It must be a folder. * @return Node with the selected CRC in the selected folder, or NULL * if it's not found. */ @Nullable public MegaNode getNodeByCRC(String crc, MegaNode parent) { return megaApi.getNodeByCRC(crc, parent); } /** * Check if a node has an access level *

    * You take the ownership of the returned value * * @param node Node to check * @param level Access level to check * Valid values for this parameter are: * - MegaShare::ACCESS_OWNER * - MegaShare::ACCESS_FULL * - MegaShare::ACCESS_READWRITE * - MegaShare::ACCESS_READ * @return Pointer to MegaError with the result. * Valid values for the error code are: * - MegaError::API_OK - The node has the required access level * - MegaError::API_EACCESS - The node doesn't have the required access level * - MegaError::API_ENOENT - The node doesn't exist in the account * - MegaError::API_EARGS - Invalid parameters */ public MegaError checkAccessErrorExtended(MegaNode node, int level) { return megaApi.checkAccessErrorExtended(node, level); } /** * Check if a node can be moved to a target node *

    * You take the ownership of the returned value * * @param node Node to check * @param target Target for the move operation * @return MegaError object with the result: * Valid values for the error code are: * - MegaError::API_OK - The node can be moved to the target * - MegaError::API_EACCESS - The node can't be moved because of permissions problems * - MegaError::API_ECIRCULAR - The node can't be moved because that would create a circular linkage * - MegaError::API_ENOENT - The node or the target doesn't exist in the account * - MegaError::API_EARGS - Invalid parameters */ public MegaError checkMoveErrorExtended(MegaNode node, MegaNode target) { return megaApi.checkMoveErrorExtended(node, target); } /** * Check if the MEGA filesystem is available in the local computer *

    * This function returns true after a successful call to MegaApi::fetchNodes, * otherwise it returns false * * @return True if the MEGA filesystem is available */ public boolean isFilesystemAvailable() { return megaApi.isFilesystemAvailable(); } /** * Returns the root node of the account *

    * You take the ownership of the returned value *

    * If you haven't successfully called MegaApi::fetchNodes before, * this function returns NULL * * @return Root node of the account */ @Nullable public MegaNode getRootNode() { return megaApi.getRootNode(); } /** * Returns the Vault node of the account *

    * You take the ownership of the returned value *

    * If you haven't successfully called MegaApi::fetchNodes before, * this function returns NULL * * @return Vault node of the account */ @Nullable public MegaNode getVaultNode() { return megaApi.getVaultNode(); } /** * Returns the rubbish node of the account. *

    * If you haven't successfully called MegaApiJava.fetchNodes() before, * this function returns null. * * @return Rubbish node of the account. */ @Nullable public MegaNode getRubbishNode() { return megaApi.getRubbishNode(); } /** * Check if a node is in the Cloud Drive tree * * @param node Node to check * @return True if the node is in the cloud drive */ public boolean isInCloud(MegaNode node) { return megaApi.isInCloud(node); } /** * Check if a node is in the Rubbish bin tree * * @param node Node to check * @return True if the node is in the Rubbish bin */ public boolean isInRubbish(MegaNode node) { return megaApi.isInRubbish(node); } /** * Check if a node is in the Vault tree * * @param node Node to check * @return True if the node is in the Vault */ public boolean isInVault(MegaNode node) { return megaApi.isInVault(node); } /** * Get the time (in seconds) during which transfers will be stopped due to a bandwidth overquota * * @return Time (in seconds) during which transfers will be stopped, otherwise 0 */ public long getBandwidthOverquotaDelay() { return megaApi.getBandwidthOverquotaDelay(); } /** * Search nodes and allow filtering the results. * The search is case-insensitive. * * You take the ownership of the returned value. * * @param filter Container for filtering options, cannot be null * @param order Order for the returned list * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Folders first in alphabetical order, then files in the same order * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Files first in reverse alphabetical order, then folders in the same order * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, ascending * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, descending * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, ascending * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, descending * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, ascending * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, descending * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, ascending. With this order, folders are returned first, then files * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, descending. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * * @param cancelToken MegaCancelToken to be able to cancel the search at any time. * * @return List with found nodes as MegaNode objects */ public ArrayList search(MegaSearchFilter filter, int order, MegaCancelToken cancelToken) { return nodeListToArray(megaApi.search(filter, order, cancelToken)); } /** * Search nodes and allow filtering the results. * The search is case-insensitive. * * You take the ownership of the returned value. * * @param filter Container for filtering options, cannot be null * @param order Order for the returned list * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Folders first in alphabetical order, then files in the same order * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Files first in reverse alphabetical order, then folders in the same order * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, ascending * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, descending * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, ascending * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, descending * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, ascending * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, descending * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, ascending. With this order, folders are returned first, then files * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, descending. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first. With this order, folders are returned first, then files * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last. With this order, folders are returned first, then files * * @param cancelToken MegaCancelToken to be able to cancel the search at any time. * @param searchPage Container for pagination options; if null, all results will be returned * * @return List with found nodes as MegaNode objects */ public ArrayList search(MegaSearchFilter filter, int order, MegaCancelToken cancelToken, MegaSearchPage searchPage) { return nodeListToArray(megaApi.search(filter, order, cancelToken, searchPage)); } /** * Get a list of buckets, each bucket containing a list of recently added/modified nodes *

    * Each bucket contains files that were added/modified in a set, by a single user. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes *

    * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentsBucket - Returns buckets with a list of recently added/modified nodes *

    * The recommended values for the following parameters are to consider * interactions during the last 30 days and maximum 500 nodes. * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered * @param listener MegaRequestListener to track this request */ public void getRecentActionsAsync(long days, long maxnodes, MegaRequestListenerInterface listener) { megaApi.getRecentActionsAsync(days, maxnodes, createDelegateRequestListener(listener)); } /** * Get a list of buckets, each bucket containing a list of recently added/modified nodes *

    * Each bucket contains files that were added/modified in a set, by a single user. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes *

    * The recommended values for the following parameters are to consider * interactions during the last 30 days and maximum 500 nodes. *

    * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentsBucket - Returns buckets with a list of recently added/modified nodes * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered */ public void getRecentActionsAsync(long days, long maxnodes) { megaApi.getRecentActionsAsync(days, maxnodes); } /** * @brief Get a list of buckets, each bucket containing a list of recently added/modified * nodes *

    * Each bucket contains files that were added/modified in a set, by a single user. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes * - MegaRequest::getFlag - Returns true if sensitives are excluded *

    * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns buckets with a list of recently added/modified * nodes *

    * The recommended values for the following parameters are to consider * interactions during the last 30 days and maximum 500 nodes. * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered * @param excludeSensitives Set to true to filter out sensitive nodes (Nodes are considered * sensitive if they have that property set, or one of their ancestors has it) * @param listener MegaRequestListener to track this request */ public void getRecentActionsAsync(long days, long maxnodes, boolean excludeSensitives, MegaRequestListenerInterface listener){ megaApi.getRecentActionsAsync(days, maxnodes, excludeSensitives, createDelegateRequestListener(listener)); } /** * @brief Get a list of buckets, each bucket containing a list of recently added/modified * nodes *

    * Each bucket contains files that were added/modified in a set, by a single user. *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes * - MegaRequest::getFlag - Returns true if sensitives are excluded *

    * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns buckets with a list of recently added/modified * nodes *

    * The recommended values for the following parameters are to consider * interactions during the last 30 days and maximum 500 nodes. * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered * @param excludeSensitives Set to true to filter out sensitive nodes (Nodes are considered * sensitive if they have that property set, or one of their ancestors has it) */ public void getRecentActionsAsync(long days, long maxnodes, boolean excludeSensitives){ megaApi.getRecentActionsAsync(days, maxnodes, excludeSensitives); } /** * @brief Get a recent action bucket by its identifier * * The identifier format is: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the bucket identifier * * The associated request type with this request is * MegaRequest::TYPE_GET_RECENT_ACTION_BY_ID * If the identifier is invalid (for example, invalid token count, invalid handle, * invalid boolean token, or parentHandle/userHandle is UNDEF), the request * finishes with MegaError::API_EARGS. * If the identifier is valid but there is no matching recent-action bucket, * the request finishes with MegaError::API_ENOENT. * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns a list with 1 bucket * * @param id Bucket identifier returned by MegaRecentActionBucket::getId * @param listener MegaRequestListener to track this request */ public void getRecentActionById(String id, MegaRequestListenerInterface listener) { megaApi.getRecentActionById(id, createDelegateRequestListener(listener)); } /** * @brief Get a recent action bucket by its identifier * * The identifier format is: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the bucket identifier * * The associated request type with this request is * MegaRequest::TYPE_GET_RECENT_ACTION_BY_ID * If the identifier is invalid (for example, invalid token count, invalid handle, * invalid boolean token, or parentHandle/userHandle is UNDEF), the request * finishes with MegaError::API_EARGS. * If the identifier is valid but there is no matching recent-action bucket, * the request finishes with MegaError::API_ENOENT. * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns a list with 1 bucket * * @param id Bucket identifier returned by MegaRecentActionBucket::getId * @param excludeSensitives Set to true to filter out sensitive nodes (Nodes are considered * * sensitive if they have that property set, or one of their ancestors has it) * @param listener MegaRequestListener to track this request */ public void getRecentActionById(String id, boolean excludeSensitives, MegaRequestListenerInterface listener) { megaApi.getRecentActionById(id, excludeSensitives, createDelegateRequestListener(listener)); } /** * @brief Clear the account’s recent actions history up to a given timestamp. * * This method clears the recent actions history on the account by setting a * “recent clear” timestamp. All actions that occurred at or before the given * timestamp are considered cleared. * * The associated request type for this operation is * MegaRequest::TYPE_SET_ATTR_USER. * * Valid data available in the MegaRequest object received in callbacks: * - MegaRequest::getParamType - Returns the user attribute type * MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP * - MegaRequest::getNumber - Returns the epoch time (in seconds) used as the recent * actions history clear timestamp. * * @param until Epoch time (in seconds). Recent actions up to this time will be cleared. * @param listener Optional MegaRequestListener to track this request. */ public void clearRecentActionHistory(long until, MegaRequestListenerInterface listener) { megaApi.clearRecentActionHistory(until, createDelegateRequestListener(listener)); } /** * @brief Clear the account’s recent actions history up to a given timestamp. * * This method clears the recent actions history on the account by setting a * “recent clear” timestamp. All actions that occurred at or before the given * timestamp are considered cleared. * * The associated request type for this operation is * MegaRequest::TYPE_SET_ATTR_USER. * * Valid data available in the MegaRequest object received in callbacks: * - MegaRequest::getParamType - Returns the user attribute type * MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP * - MegaRequest::getNumber - Returns the epoch time (in seconds) used as the recent * actions history clear timestamp. * * @param until Epoch time (in seconds). Recent actions up to this time will be cleared. */ public void clearRecentActionHistory(long until) { megaApi.clearRecentActionHistory(until); } /** * Process a node tree using a MegaTreeProcessor implementation * * @param node The parent node of the tree to explore * @param processor MegaTreeProcessor that will receive callbacks for every node in the tree * @param recursive True if you want to recursively process the whole node tree. * False if you want to process the children of the node only * @return True if all nodes were processed. False otherwise (the operation can be * cancelled by MegaTreeProcessor::processMegaNode()) */ public boolean processMegaTree(MegaNode node, MegaTreeProcessorInterface processor, boolean recursive) { DelegateMegaTreeProcessor delegateListener = new DelegateMegaTreeProcessor(this, processor); activeMegaTreeProcessors.add(delegateListener); boolean result = megaApi.processMegaTree(node, delegateListener, recursive); activeMegaTreeProcessors.remove(delegateListener); return result; } /** * Process a node tree using a MegaTreeProcessor implementation * * @param node The parent node of the tree to explore * @param processor MegaTreeProcessor that will receive callbacks for every node in the tree * @return True if all nodes were processed. False otherwise (the operation can be * cancelled by MegaTreeProcessor::processMegaNode()) */ public boolean processMegaTree(MegaNode node, MegaTreeProcessorInterface processor) { DelegateMegaTreeProcessor delegateListener = new DelegateMegaTreeProcessor(this, processor); activeMegaTreeProcessors.add(delegateListener); boolean result = megaApi.processMegaTree(node, delegateListener); activeMegaTreeProcessors.remove(delegateListener); return result; } /** * Returns a MegaNode that can be downloaded with any instance of MegaApi *

    * You can use MegaApi::startDownload with the resulting node with any instance * of MegaApi, even if it's logged into another account, a public folder, or not * logged in. *

    * If the first parameter is a public node or an already authorized node, this * function returns a copy of the node, because it can be already downloaded * with any MegaApi instance. *

    * If the node in the first parameter belongs to the account or public folder * in which the current MegaApi object is logged in, this function returns an * authorized node. *

    * If the first parameter is NULL or a node that is not a public node, is not * already authorized and doesn't belong to the current MegaApi, this function * returns NULL. *

    * You take the ownership of the returned value. * * @param node MegaNode to authorize * @return Authorized node, or NULL if the node can't be authorized */ @Nullable public MegaNode authorizeNode(MegaNode node) { return megaApi.authorizeNode(node); } /** * Returns a MegaNode that can be downloaded/copied with a chat-authorization *

    * During preview of chat-links, you need to call this method to authorize the MegaNode * from a node-attachment message, so the API allows to access to it. The parameter to * authorize the access can be retrieved from MegaChatRoom::getAuthorizationToken when * the chatroom in in preview mode. *

    * You can use MegaApi::startDownload and/or MegaApi::copyNode with the resulting * node with any instance of MegaApi, even if it's logged into another account, * a public folder, or not logged in. *

    * You take the ownership of the returned value. * * @param node MegaNode to authorize * @param cauth Authorization token (public handle of the chatroom in B64url encoding) * @return Authorized node, or NULL if the node can't be authorized */ @Nullable public MegaNode authorizeChatNode(MegaNode node, String cauth) { return megaApi.authorizeChatNode(node, cauth); } /** * Get the SDK version *

    * The returned string is an statically allocated array. * Do not delete it. * * @return SDK version */ @Nullable public String getVersion() { return megaApi.getVersion(); } /** * Get the User-Agent header used by the SDK *

    * The SDK retains the ownership of the returned value. It will be valid until * the MegaApi object is deleted. * * @return User-Agent used by the SDK */ @Nullable public String getUserAgent() { return megaApi.getUserAgent(); } /** * Change the API URL *

    * This function allows to change the API URL. * It's only useful for testing or debugging purposes. * * @param apiURL New API URL * @param disablepkp true to disable public key pinning for this URL */ public void changeApiUrl(String apiURL, boolean disablepkp) { megaApi.changeApiUrl(apiURL, disablepkp); } /** * Change the API URL *

    * This function allows to change the API URL. * It's only useful for testing or debugging purposes. * * @param apiURL New API URL */ public void changeApiUrl(String apiURL) { megaApi.changeApiUrl(apiURL); } /** * Set the language code used by the app * * @param languageCode Language code used by the app * @return True if the language code is known for the SDK, otherwise false */ public boolean setLanguage(String languageCode) { return megaApi.setLanguage(languageCode); } /** * Generate an unique ViewID *

    * The caller gets the ownership of the object. *

    * A ViewID consists of a random generated id, encoded in hexadecimal as 16 characters of a null-terminated string. */ public String generateViewId() { return megaApi.generateViewId(); } /** * Set the preferred language of the user *

    * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - Return the language code *

    * If the language code is unknown for the SDK, the error code will be MegaError::API_ENOENT *

    * This attribute is automatically created by the server. Apps only need * to set the new value when the user changes the language. * * @param languageCode Language code to be set * @param listener MegaRequestListener to track this request */ public void setLanguagePreference(String languageCode, MegaRequestListenerInterface listener) { megaApi.setLanguagePreference(languageCode, createDelegateRequestListener(listener)); } /** * Get the preferred language of the user *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Return the language code * * @param listener MegaRequestListener to track this request */ public void getLanguagePreference(MegaRequestListenerInterface listener) { megaApi.getLanguagePreference(createDelegateRequestListener(listener)); } /** * Enable or disable file versioning *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_DISABLE_VERSIONS *

    * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - "1" for disable, "0" for enable * * @param disable True to disable file versioning. False to enable it * @param listener MegaRequestListener to track this request */ public void setFileVersionsOption(boolean disable, MegaRequestListenerInterface listener) { megaApi.setFileVersionsOption(disable, createDelegateRequestListener(listener)); } /** * Enable or disable the automatic approval of incoming contact requests using a * contact link *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value * MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION *

    * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - "0" for disable, "1" for enable * * @param enable True to enable the automatic approval of incoming contact requests using a * contact link * @param listener MegaRequestListener to track this request */ public void setContactLinksOption(boolean enable, MegaRequestListenerInterface listener) { megaApi.setContactLinksOption(enable, createDelegateRequestListener(listener)); } /** * Check if file versioning is enabled or disabled *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_DISABLE_VERSIONS *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - "1" for disable, "0" for enable * - MegaRequest::getFlag - True if disabled, false if enabled *

    * If the option has never been set, the error code will be MegaError::API_ENOENT. * In that case, file versioning is enabled by default and MegaRequest::getFlag returns false. * * @param listener MegaRequestListener to track this request */ public void getFileVersionsOption(MegaRequestListenerInterface listener) { megaApi.getFileVersionsOption(createDelegateRequestListener(listener)); } /** * Check if the automatic approval of incoming contact requests using contact links is enabled or disabled *

    * If the option has never been set, the error code will be MegaError::API_ENOENT. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - "0" for disable, "1" for enable * - MegaRequest::getFlag - false if disabled, true if enabled * * @param listener MegaRequestListener to track this request */ public void getContactLinksOption(MegaRequestListenerInterface listener) { megaApi.getContactLinksOption(createDelegateRequestListener(listener)); } /** * Keep retrying when public key pinning fails *

    * By default, when the check of the MEGA public key fails, it causes an automatic * logout. Pass false to this function to disable that automatic logout and * keep the SDK retrying the request. *

    * Even if the automatic logout is disabled, a request of the type MegaRequest::TYPE_LOGOUT * will be automatically created and callbacks (onRequestStart, onRequestFinish) will * be sent. However, logout won't be really executed and in onRequestFinish the error code * for the request will be MegaError::API_EINCOMPLETE * * @param enable true to keep retrying failed requests due to a fail checking the MEGA public key * or false to perform an automatic logout in that case */ public void retrySSLerrors(boolean enable) { megaApi.retrySSLerrors(enable); } /** * Enable / disable the public key pinning *

    * Public key pinning is enabled by default for all sensible communications. * It is strongly discouraged to disable this feature. * * @param enable true to keep public key pinning enabled, false to disable it */ public void setPublicKeyPinning(boolean enable) { megaApi.setPublicKeyPinning(enable); } /** * Make a name suitable for a file name in the local filesystem *

    * This function escapes (%xx) forbidden characters in the local filesystem if needed. * You can revert this operation using MegaApi::unescapeFsIncompatible *

    * If no dstPath is provided or filesystem type it's not supported this method will * escape characters contained in the following list: \/:?\"<>|* * Otherwise it will check forbidden characters for local filesystem type *

    * The input string must be UTF8 encoded. The returned value will be UTF8 too. *

    * You take the ownership of the returned value * * @param filename Name to convert (UTF8) * @param dstPath Destination path * @return Converted name (UTF8) */ @Nullable public String escapeFsIncompatible(String filename, String dstPath) { return megaApi.escapeFsIncompatible(filename, dstPath); } /** * Unescape a file name escaped with MegaApi::escapeFsIncompatible *

    * If no localPath is provided or filesystem type it's not supported, this method will * unescape those sequences that once has been unescaped results in any character * of the following list: \/:?\"<>|* * Otherwise it will unescape those characters forbidden in local filesystem type *

    * The input string must be UTF8 encoded. The returned value will be UTF8 too. * You take the ownership of the returned value * * @param name Escaped name to convert (UTF8) * @param localPath Local path * @return Converted name (UTF8) */ @Nullable String unescapeFsIncompatible(String name, String localPath) { return megaApi.unescapeFsIncompatible(name, localPath); } /** * Create a thumbnail for an image * * @param imagePath Image path * @param dstPath Destination path for the thumbnail (including the file name) * @return True if the thumbnail was successfully created, otherwise false. */ public boolean createThumbnail(String imagePath, String dstPath) { return megaApi.createThumbnail(imagePath, dstPath); } /** * Create a preview for an image * * @param imagePath Image path * @param dstPath Destination path for the preview (including the file name) * @return True if the preview was successfully created, otherwise false. */ public boolean createPreview(String imagePath, String dstPath) { return megaApi.createPreview(imagePath, dstPath); } /** * Convert a Base64 string to Base32 *

    * If the input pointer is NULL, this function will return NULL. * If the input character array isn't a valid base64 string * the effect is undefined *

    * You take the ownership of the returned value * * @param base64 NULL-terminated Base64 character array * @return NULL-terminated Base32 character array */ @Nullable public static String base64ToBase32(String base64) { return MegaApi.base64ToBase32(base64); } /** * Convert a Base32 string to Base64 *

    * If the input pointer is NULL, this function will return NULL. * If the input character array isn't a valid base32 string * the effect is undefined *

    * You take the ownership of the returned value * * @param base32 NULL-terminated Base32 character array * @return NULL-terminated Base64 character array */ @Nullable public static String base32ToBase64(String base32) { return MegaApi.base32ToBase64(base32); } /** * Recursively remove all local files/folders inside a local path * * @param path Local path of a folder to start the recursive deletion * The folder itself is not deleted */ public static void removeRecursively(String path) { MegaApi.removeRecursively(path); } /** * Check if the connection with MEGA servers is OK *

    * It can briefly return false even if the connection is good enough when * some storage servers are temporarily not available or the load of API * servers is high. * * @return true if the connection is perfectly OK, otherwise false */ public boolean isOnline() { return megaApi.isOnline(); } /** * Start an HTTP proxy server in specified port *

    * If this function returns true, that means that the server is * ready to accept connections. The initialization is synchronous. *

    * The server will serve files using this URL format: * http://127.0.0.1// *

    * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MegaNode using MegaApi::httpServerGetLocalLink *

    * If the node handle belongs to a folder node, a web with the list of files * inside the folder is returned. *

    * It's important to know that the HTTP proxy server has several configuration options * that can restrict the nodes that will be served and the connections that will be accepted. *

    * These are the default options: * - The restricted mode of the server is set to MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS * (see MegaApi::httpServerSetRestrictedMode) *

    * - Folder nodes are NOT allowed to be served (see MegaApi::httpServerEnableFolderServer) * - File nodes are allowed to be served (see MegaApi::httpServerEnableFileServer) * - Subtitles support is disabled (see MegaApi::httpServerEnableSubtitlesSupport) *

    * The HTTP server will only stream a node if it's allowed by all configuration options. * * @param localOnly true to listen on 127.0.0.1 only, false to listen on all network interfaces * @param port Port in which the server must accept connections * @return True if the server is ready, false if the initialization failed */ public boolean httpServerStart(boolean localOnly, int port) { return megaApi.httpServerStart(localOnly, port); } /** * Start an HTTP proxy server in specified port *

    * If this function returns true, that means that the server is * ready to accept connections. The initialization is synchronous. *

    * The server will serve files using this URL format: * http://127.0.0.1// *

    * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MegaNode using MegaApi::httpServerGetLocalLink *

    * If the node handle belongs to a folder node, a web with the list of files * inside the folder is returned. *

    * It's important to know that the HTTP proxy server has several configuration options * that can restrict the nodes that will be served and the connections that will be accepted. *

    * These are the default options: * - The restricted mode of the server is set to MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS * (see MegaApi::httpServerSetRestrictedMode) *

    * - Folder nodes are NOT allowed to be served (see MegaApi::httpServerEnableFolderServer) * - File nodes are allowed to be served (see MegaApi::httpServerEnableFileServer) * - Subtitles support is disabled (see MegaApi::httpServerEnableSubtitlesSupport) *

    * The HTTP server will only stream a node if it's allowed by all configuration options. * * @param localOnly true to listen on 127.0.0.1 only, false to listen on all network interfaces * @return True if the server is ready, false if the initialization failed */ public boolean httpServerStart(boolean localOnly) { return megaApi.httpServerStart(localOnly); } /** * Start an HTTP proxy server in specified port *

    * If this function returns true, that means that the server is * ready to accept connections. The initialization is synchronous. *

    * The server will serve files using this URL format: * http://127.0.0.1// *

    * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MegaNode using MegaApi::httpServerGetLocalLink *

    * If the node handle belongs to a folder node, a web with the list of files * inside the folder is returned. *

    * It's important to know that the HTTP proxy server has several configuration options * that can restrict the nodes that will be served and the connections that will be accepted. *

    * These are the default options: * - The restricted mode of the server is set to MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS * (see MegaApi::httpServerSetRestrictedMode) *

    * - Folder nodes are NOT allowed to be served (see MegaApi::httpServerEnableFolderServer) * - File nodes are allowed to be served (see MegaApi::httpServerEnableFileServer) * - Subtitles support is disabled (see MegaApi::httpServerEnableSubtitlesSupport) *

    * The HTTP server will only stream a node if it's allowed by all configuration options. * * @return True if the server is ready, false if the initialization failed */ public boolean httpServerStart() { return megaApi.httpServerStart(); } /** * Stop the HTTP proxy server *

    * When this function returns, the server is already shutdown. * If the HTTP proxy server isn't running, this functions does nothing */ public void httpServerStop() { megaApi.httpServerStop(); } /** * Check if the HTTP proxy server is running * * @return 0 if the server is not running. Otherwise the port in which it's listening to */ public int httpServerIsRunning() { return megaApi.httpServerIsRunning(); } /** * Check if the HTTP proxy server is listening on all network interfaces * * @return true if the HTTP proxy server is listening on 127.0.0.1 only, or it's not started. * If it's started and listening on all network interfaces, this function returns false */ public boolean httpServerIsLocalOnly() { return megaApi.httpServerIsLocalOnly(); } /** * Allow/forbid to serve files *

    * By default, files are served (when the server is running) *

    * Even if files are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @param enable true to allow to server files, false to forbid it */ public void httpServerEnableFileServer(boolean enable) { megaApi.httpServerEnableFileServer(enable); } /** * Check if it's allowed to serve files *

    * This function can return true even if the HTTP proxy server is not running *

    * Even if files are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @return true if it's allowed to serve files, otherwise false */ public boolean httpServerIsFileServerEnabled() { return megaApi.httpServerIsFileServerEnabled(); } /** * Allow/forbid to serve folders *

    * By default, folders are NOT served *

    * Even if folders are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @param enable true to allow to server folders, false to forbid it */ public void httpServerEnableFolderServer(boolean enable) { megaApi.httpServerEnableFolderServer(enable); } /** * Check if it's allowed to serve folders *

    * This function can return true even if the HTTP proxy server is not running *

    * Even if folders are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @return true if it's allowed to serve folders, otherwise false */ public boolean httpServerIsFolderServerEnabled() { return megaApi.httpServerIsFolderServerEnabled(); } /** * Enable/disable the restricted mode of the HTTP server *

    * This function allows to restrict the nodes that are allowed to be served. * For not allowed links, the server will return "407 Forbidden". *

    * Possible values are: * - HTTP_SERVER_DENY_ALL = -1 * All nodes are forbidden *

    * - HTTP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served *

    * - HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 (default) * Only links created with MegaApi::httpServerGetLocalLink are allowed to be served *

    * - HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::httpServerGetLocalLink is allowed to be served *

    * If a different value from the list above is passed to this function, it won't have any effect and the previous * state of this option will be preserved. *

    * The default value of this property is MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS *

    * The state of this option is preserved even if the HTTP server is restarted, but * the HTTP proxy server only remembers the generated links since the last call to * MegaApi::httpServerStart *

    * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerEnableFileServer, * MegaApi::httpServerEnableFolderServer) are still applied. * * @param mode Required state for the restricted mode of the HTTP proxy server */ public void httpServerSetRestrictedMode(int mode) { megaApi.httpServerSetRestrictedMode(mode); } /** * Check if the HTTP proxy server is working in restricted mode *

    * Possible return values are: * - HTTP_SERVER_DENY_ALL = -1 * All nodes are forbidden *

    * - HTTP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served *

    * - HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 * Only links created with MegaApi::httpServerGetLocalLink are allowed to be served *

    * - HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::httpServerGetLocalLink is allowed to be served *

    * The default value of this property is MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS *

    * See MegaApi::httpServerEnableRestrictedMode and MegaApi::httpServerStart *

    * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerEnableFileServer, * MegaApi::httpServerEnableFolderServer) are still applied. * * @return State of the restricted mode of the HTTP proxy server */ public int httpServerGetRestrictedMode() { return megaApi.httpServerGetRestrictedMode(); } /** * Enable/disable the support for subtitles *

    * Subtitles support allows to stream some special links that otherwise wouldn't be valid. * For example, let's suppose that the server is streaming this video: * http://120.0.0.1:4443//MyHolidays.avi *

    * Some media players scan HTTP servers looking for subtitle files and request links like these ones: * http://120.0.0.1:4443//MyHolidays.txt * http://120.0.0.1:4443//MyHolidays.srt *

    * Even if a file with that name is in the same folder of the MEGA account, the node wouldn't be served because * the node handle wouldn't match. *

    * When this feature is enabled, the HTTP proxy server will check if there are files with that name * in the same folder as the node corresponding to the handle in the link. *

    * If a matching file is found, the name is exactly the same as the the node with the specified handle * (except the extension), the node with that handle is allowed to be streamed and this feature is enabled * the HTTP proxy server will serve that file. *

    * This feature is disabled by default. * * @param enable True to enable subtitles support, false to disable it */ public void httpServerEnableSubtitlesSupport(boolean enable) { megaApi.httpServerEnableSubtitlesSupport(enable); } /** * Check if the support for subtitles is enabled *

    * See MegaApi::httpServerEnableSubtitlesSupport. *

    * This feature is disabled by default. * * @return true of the support for subtitles is enabled, otherwise false */ public boolean httpServerIsSubtitlesSupportEnabled() { return megaApi.httpServerIsSubtitlesSupportEnabled(); } /** * Add a listener to receive information about the HTTP proxy server *

    * This is the valid data that will be provided on callbacks: * - MegaTransfer::getType - It will be MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD * - MegaTransfer::getPath - URL requested to the HTTP proxy server * - MegaTransfer::getFileName - Name of the requested file (if any, otherwise NULL) * - MegaTransfer::getNodeHandle - Handle of the requested file (if any, otherwise NULL) * - MegaTransfer::getTotalBytes - Total bytes of the response (response headers + file, if required) * - MegaTransfer::getStartPos - Start position (for range requests only, otherwise -1) * - MegaTransfer::getEndPos - End position (for range requests only, otherwise -1) *

    * On the onTransferFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINCOMPLETE - If the whole response wasn't sent * (it's normal to get this error code sometimes because media players close connections when they have * the data that they need) *

    * - MegaError::API_EREAD - If the connection with MEGA storage servers failed * - MegaError::API_EAGAIN - If the download speed is too slow for streaming * - A number > 0 means an HTTP error code returned to the client * * @param listener Listener to receive information about the HTTP proxy server */ public void httpServerAddListener(MegaTransferListenerInterface listener) { megaApi.httpServerAddListener(createDelegateHttpServerListener(listener, false)); } /** * Stop the reception of callbacks related to the HTTP proxy server on this listener * * @param listener Listener that won't continue receiving information */ public void httpServerRemoveListener(MegaTransferListenerInterface listener) { ArrayList listenersToRemove = new ArrayList<>(); synchronized (activeHttpServerListeners) { Iterator it = activeHttpServerListeners.iterator(); while (it.hasNext()) { DelegateMegaTransferListener delegate = it.next(); if (delegate.getUserListener() == listener) { listenersToRemove.add(delegate); it.remove(); } } } for (int i = 0; i < listenersToRemove.size(); i++) { megaApi.httpServerRemoveListener(listenersToRemove.get(i)); } } /** * Returns a URL to a node in the local HTTP proxy server *

    * The HTTP proxy server must be running before using this function, otherwise * it will return NULL. *

    * You take the ownership of the returned value * * @param node Node to generate the local HTTP link * @return URL to the node in the local HTTP proxy server, otherwise NULL */ @Nullable public String httpServerGetLocalLink(MegaNode node) { return megaApi.httpServerGetLocalLink(node); } /** * Set the maximum buffer size for the internal buffer *

    * The HTTP proxy server has an internal buffer to store the data received from MEGA * while it's being sent to clients. When the buffer is full, the connection with * the MEGA storage server is closed, when the buffer has few data, the connection * with the MEGA storage server is started again. *

    * Even with very fast connections, due to the possible latency starting new connections, * if this buffer is small the streaming can have problems due to the overhead caused by * the excessive number of POST requests. *

    * It's recommended to set this buffer at least to 1MB *

    * For connections that request less data than the buffer size, the HTTP proxy server * will only allocate the required memory to complete the request to minimize the * memory usage. *

    * The new value will be taken into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param bufferSize Maximum buffer size (in bytes) or a number <= 0 to use the * internal default value */ public void httpServerSetMaxBufferSize(int bufferSize) { megaApi.httpServerSetMaxBufferSize(bufferSize); } /** * Get the maximum size of the internal buffer size *

    * See MegaApi::httpServerSetMaxBufferSize * * @return Maximum size of the internal buffer size (in bytes) */ public int httpServerGetMaxBufferSize() { return megaApi.httpServerGetMaxBufferSize(); } /** * Set the maximum size of packets sent to clients *

    * For each connection, the HTTP proxy server only sends one write to the underlying * socket at once. This parameter allows to set the size of that write. *

    * A small value could cause a lot of writes and would lower the performance. *

    * A big value could send too much data to the output buffer of the socket. That could * keep the internal buffer full of data that hasn't been sent to the client yet, * preventing the retrieval of additional data from the MEGA storage server. In that * circumstances, the client could read a lot of data at once and the HTTP server * could not have enough time to get more data fast enough. *

    * It's recommended to set this value to at least 8192 and no more than the 25% of * the maximum buffer size (MegaApi::httpServerSetMaxBufferSize). *

    * The new value will be taken into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param outputSize Maximum size of data packets sent to clients (in bytes) or * a number <= 0 to use the internal default value */ public void httpServerSetMaxOutputSize(int outputSize) { megaApi.httpServerSetMaxOutputSize(outputSize); } /** * Get the maximum size of the packets sent to clients *

    * See MegaApi::httpServerSetMaxOutputSize * * @return Maximum size of the packets sent to clients (in bytes) */ public int httpServerGetMaxOutputSize() { return megaApi.httpServerGetMaxOutputSize(); } /** * Get the MIME type associated with the extension *

    * You take the ownership of the returned value * * @param extension File extension (with or without a leading dot) * @return MIME type associated with the extension */ @Nullable public static String getMimeType(String extension) { return MegaApi.getMimeType(extension); } /** * Register a token for push notifications *

    * This function attach a token to the current session, which is intended to get push notifications * on mobile platforms like Android and iOS. *

    * The push notification mechanism is platform-dependent. Hence, the app should indicate the * type of push notification to be registered. Currently, the different types are: * - MegaApi::PUSH_NOTIFICATION_ANDROID = 1 * - MegaApi::PUSH_NOTIFICATION_IOS_VOIP = 2 * - MegaApi::PUSH_NOTIFICATION_IOS_STD = 3 * - MegaApi::PUSH_NOTIFICATION_ANDROID_HUAWEI = 4 *

    * The associated request type with this request is MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the token provided. * - MegaRequest::getNumber - Returns the device type provided. * * @param deviceType Type of notification to be registered. * @param token Character array representing the token to be registered. * @param listener MegaRequestListener to track this request */ public void registerPushNotifications(int deviceType, String token, MegaRequestListenerInterface listener) { megaApi.registerPushNotifications(deviceType, token, createDelegateRequestListener(listener)); } /** * Register a token for push notifications *

    * This function attach a token to the current session, which is intended to get push notifications * on mobile platforms like Android and iOS. *

    * The push notification mechanism is platform-dependent. Hence, the app should indicate the * type of push notification to be registered. Currently, the different types are: * - MegaApi::PUSH_NOTIFICATION_ANDROID = 1 * - MegaApi::PUSH_NOTIFICATION_IOS_VOIP = 2 * - MegaApi::PUSH_NOTIFICATION_IOS_STD = 3 * - MegaApi::PUSH_NOTIFICATION_ANDROID_HUAWEI = 4 *

    * The associated request type with this request is MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the token provided. * - MegaRequest::getNumber - Returns the device type provided. * * @param deviceType Type of notification to be registered. * @param token Character array representing the token to be registered. */ public void registerPushNotifications(int deviceType, String token) { megaApi.registerPushNotifications(deviceType, token); } /** * Get the MEGA Achievements of the account logged in *

    * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always false *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the MEGA Achievements of this account * * @param listener MegaRequestListener to track this request */ public void getAccountAchievements(MegaRequestListenerInterface listener) { megaApi.getAccountAchievements(createDelegateRequestListener(listener)); } /** * Get the MEGA Achievements of the account logged in *

    * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always false *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the MEGA Achievements of this account */ public void getAccountAchievements() { megaApi.getAccountAchievements(); } /** * Get the list of existing MEGA Achievements *

    * Similar to MegaApi::getAccountAchievements, this method returns only the base storage and * the details for the different achievement classes, but not awards or rewards related to the * account that is logged in. * This function can be used to give an indication of what is available for advertising * for unregistered users, despite it can be used with a logged in account with no difference. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the list of existing MEGA Achievements * * @param listener MegaRequestListener to track this request * @implNote if the IP address is not achievement enabled (it belongs to a country where MEGA * Achievements are not enabled), the request will fail with MegaError::API_EACCESS. */ public void getMegaAchievements(MegaRequestListenerInterface listener) { megaApi.getMegaAchievements(createDelegateRequestListener(listener)); } /** * Get the list of existing MEGA Achievements *

    * Similar to MegaApi::getAccountAchievements, this method returns only the base storage and * the details for the different achievement classes, but not awards or rewards related to the * account that is logged in. * This function can be used to give an indication of what is available for advertising * for unregistered users, despite it can be used with a logged in account with no difference. *

    * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always true *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the list of existing MEGA Achievements * * @implNote if the IP address is not achievement enabled (it belongs to a country where MEGA * Achievements are not enabled), the request will fail with MegaError::API_EACCESS. */ public void getMegaAchievements() { megaApi.getMegaAchievements(); } /** * Set the OriginalFingerprint of a node. *

    * Use this call to attach an originalFingerprint to a node. The fingerprint must * be generated from the file prior to modification, where this node is the modified file. *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the specified fingerprint * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_ORIGINALFINGERPRINT * * @param node The node to attach the originalFingerprint to. * @param originalFingerprint The fingerprint of the file before modification * @param listener MegaRequestListener to track this request */ public void setOriginalFingerprint(MegaNode node, String originalFingerprint, MegaRequestListenerInterface listener) { megaApi.setOriginalFingerprint(node, originalFingerprint, createDelegateRequestListener(listener)); } /** * Returns nodes that have an originalFingerprint equal to the supplied value *

    * Search the node tree and return a list of nodes that have an originalFingerprint, which * matches the supplied originalFingerprint. *

    * If the parent node supplied is not NULL, it only searches nodes below that parent folder, * otherwise all nodes are searched. If no nodes are found with that original fingerprint, * this function returns an empty MegaNodeList. *

    * You take the ownership of the returned value. * * @param originalFingerprint Original Fingerprint to check * @param parent Only return nodes below this specified parent folder. Pass NULL to consider all nodes. * @return List of nodes with the same original fingerprint */ public MegaNodeList getNodesByOriginalFingerprint(String originalFingerprint, MegaNode parent) { return megaApi.getNodesByOriginalFingerprint(originalFingerprint, parent); } /** * Retrieve basic information about a folder link *

    * This function retrieves basic information from a folder link, like the number of files / folders * and the name of the folder. For folder links containing a lot of files/folders, * this function is more efficient than a fetchNodes. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink() - Returns the public link to the folder *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaFolderInfo() - Returns information about the contents of the folder * - MegaRequest::getNodeHandle() - Returns the public handle of the folder * - MegaRequest::getParentHandle() - Returns the handle of the owner of the folder * - MegaRequest::getText() - Returns the name of the folder. * If there's no name, it returns the special status string "CRYPTO_ERROR". * If the length of the name is zero, it returns the special status string "BLANK". *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the link is not a valid folder link * - MegaError::API_EKEY - If the public link does not contain the key or it is invalid * * @param megaFolderLink Public link to a folder in MEGA * @param listener MegaRequestListener to track this request */ public void getPublicLinkInformation(String megaFolderLink, MegaRequestListenerInterface listener) { megaApi.getPublicLinkInformation(megaFolderLink, createDelegateRequestListener(listener)); } /** * Retrieve basic information about a folder link *

    * This function retrieves basic information from a folder link, like the number of files / folders * and the name of the folder. For folder links containing a lot of files/folders, * this function is more efficient than a fetchNodes. *

    * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink() - Returns the public link to the folder *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaFolderInfo() - Returns information about the contents of the folder * - MegaRequest::getNodeHandle() - Returns the public handle of the folder * - MegaRequest::getParentHandle() - Returns the handle of the owner of the folder * - MegaRequest::getText() - Returns the name of the folder. * If there's no name, it returns the special status string "CRYPTO_ERROR". * If the length of the name is zero, it returns the special status string "BLANK". *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the link is not a valid folder link * - MegaError::API_EKEY - If the public link does not contain the key or it is invalid * * @param megaFolderLink Public link to a folder in MEGA */ public void getPublicLinkInformation(String megaFolderLink) { megaApi.getPublicLinkInformation(megaFolderLink); } /** * Call the low level function setrlimit() for NOFILE, needed for some platforms. *

    * Particularly on phones, the system default limit for the number of open files (and sockets) * is quite low. When the SDK can be working on many files and many sockets at once, * we need a higher limit. Those limits need to take into account the needs of the whole * app and not just the SDK, of course. This function is provided in order that the app * can make that call and set appropriate limits. * * @param newNumFileLimit The new limit of file and socket handles for the whole app. * @return True when there were no errors setting the new limit (even when clipped to the maximum * allowed value). It returns false when setting a new limit failed. */ public boolean platformSetRLimitNumFile(int newNumFileLimit) { return megaApi.platformSetRLimitNumFile(newNumFileLimit); } /** * Call the low level function getrlimit() for NOFILE, needed for some platforms. * * @return The current limit for the number of open files (and sockets) for the app, or -1 if error. */ public int platformGetRLimitNumFile() { return megaApi.platformGetRLimitNumFile(); } /** * Requests a list of all Smart Banners available for current user. *

    * The response value is stored as a MegaBannerList. *

    * The associated request type with this request is MegaRequest::TYPE_GET_BANNERS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBannerList: the list of banners *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If called with no user being logged in. * - MegaError::API_EINTERNAL - If the internally used user attribute exists but can't be decoded. * - MegaError::API_ENOENT if there are no banners to return to the user. * * @param listener MegaRequestListener to track this request */ public void getBanners(MegaRequestListenerInterface listener) { megaApi.getBanners(createDelegateRequestListener(listener)); } /** * Requests a list of all Smart Banners available for current user. *

    * The response value is stored as a MegaBannerList. *

    * The associated request type with this request is MegaRequest::TYPE_GET_BANNERS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBannerList: the list of banners *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If called with no user being logged in. * - MegaError::API_EINTERNAL - If the internally used user attribute exists but can't be decoded. * - MegaError::API_ENOENT if there are no banners to return to the user. */ public void getBanners() { megaApi.getBanners(); } /** * No longer show the Smart Banner with the specified id to the current user. * * @param id The identifier of the Smart Banner * @param listener MegaRequestListener to track this request */ public void dismissBanner(int id, MegaRequestListenerInterface listener) { megaApi.dismissBanner(id, createDelegateRequestListener(listener)); } /** * No longer show the Smart Banner with the specified id to the current user. * * @param id The identifier of the Smart Banner */ public void dismissBanner(int id) { megaApi.dismissBanner(id); } //****************************************************************************************************/ // INTERNAL METHODS //****************************************************************************************************/ private MegaRequestListener createDelegateRequestListener(MegaRequestListenerInterface listener) { DelegateMegaRequestListener delegateListener = new DelegateMegaRequestListener(this, listener, true); activeRequestListeners.add(delegateListener); return delegateListener; } private MegaRequestListener createDelegateRequestListener(MegaRequestListenerInterface listener, boolean singleListener) { DelegateMegaRequestListener delegateListener = new DelegateMegaRequestListener(this, listener, singleListener); activeRequestListeners.add(delegateListener); return delegateListener; } private MegaTransferListener createDelegateTransferListener(MegaTransferListenerInterface listener) { DelegateMegaTransferListener delegateListener = new DelegateMegaTransferListener(this, listener, true); activeTransferListeners.add(delegateListener); return delegateListener; } private MegaTransferListener createDelegateTransferListener(MegaTransferListenerInterface listener, boolean singleListener) { DelegateMegaTransferListener delegateListener = new DelegateMegaTransferListener(this, listener, singleListener); activeTransferListeners.add(delegateListener); return delegateListener; } private MegaGlobalListener createDelegateGlobalListener(MegaGlobalListenerInterface listener) { DelegateMegaGlobalListener delegateListener = new DelegateMegaGlobalListener(this, listener); activeGlobalListeners.add(delegateListener); return delegateListener; } private MegaListener createDelegateMegaListener(MegaListenerInterface listener) { DelegateMegaListener delegateListener = new DelegateMegaListener(this, listener); activeMegaListeners.add(delegateListener); return delegateListener; } private static MegaLogger createDelegateMegaLogger(MegaLoggerInterface listener) { DelegateMegaLogger delegateLogger = new DelegateMegaLogger(listener); activeMegaLoggers.add(delegateLogger); return delegateLogger; } private MegaTransferListener createDelegateHttpServerListener(MegaTransferListenerInterface listener) { DelegateMegaTransferListener delegateListener = new DelegateMegaTransferListener(this, listener, true); activeHttpServerListeners.add(delegateListener); return delegateListener; } private MegaTransferListener createDelegateHttpServerListener(MegaTransferListenerInterface listener, boolean singleListener) { DelegateMegaTransferListener delegateListener = new DelegateMegaTransferListener(this, listener, singleListener); activeHttpServerListeners.add(delegateListener); return delegateListener; } void privateFreeRequestListener(DelegateMegaRequestListener listener) { activeRequestListeners.remove(listener); } void privateFreeTransferListener(DelegateMegaTransferListener listener) { activeTransferListeners.remove(listener); } static public ArrayList nodeListToArray(MegaNodeList nodeList) { if (nodeList == null) { return null; } ArrayList result = new ArrayList<>(nodeList.size()); for (int i = 0; i < nodeList.size(); i++) { result.add(nodeList.get(i).copy()); } return result; } static ArrayList shareListToArray(MegaShareList shareList) { if (shareList == null) { return null; } ArrayList result = new ArrayList<>(shareList.size()); for (int i = 0; i < shareList.size(); i++) { result.add(shareList.get(i).copy()); } return result; } static ArrayList contactRequestListToArray(MegaContactRequestList contactRequestList) { if (contactRequestList == null) { return null; } ArrayList result = new ArrayList<>(contactRequestList.size()); for (int i = 0; i < contactRequestList.size(); i++) { result.add(contactRequestList.get(i).copy()); } return result; } static ArrayList transferListToArray(MegaTransferList transferList) { if (transferList == null) { return null; } ArrayList result = new ArrayList<>(transferList.size()); for (int i = 0; i < transferList.size(); i++) { MegaTransfer transfer = transferList.get(i); if (transfer != null) { result.add(transfer.copy()); } } result.trimToSize(); return result; } static ArrayList userListToArray(MegaUserList userList) { if (userList == null) { return null; } ArrayList result = new ArrayList<>(userList.size()); for (int i = 0; i < userList.size(); i++) { result.add(userList.get(i).copy()); } return result; } static ArrayList userAlertListToArray(MegaUserAlertList userAlertList) { if (userAlertList == null) { return null; } ArrayList result = new ArrayList<>(userAlertList.size()); for (int i = 0; i < userAlertList.size(); i++) { result.add(userAlertList.get(i).copy()); } return result; } static ArrayList megaSetListToArray(MegaSetList megaSetList) { if (megaSetList == null) { return null; } ArrayList result = new ArrayList<>((int) megaSetList.size()); for (int i = 0; i < megaSetList.size(); i++) { result.add(megaSetList.get(i).copy()); } return result; } static ArrayList megaSetElementListToArray(MegaSetElementList megaSetElementList) { if (megaSetElementList == null) { return null; } ArrayList result = new ArrayList<>((int) megaSetElementList.size()); for (int i = 0; i < megaSetElementList.size(); i++) { result.add(megaSetElementList.get(i).copy()); } return result; } /** * Creates a copy of MegaRecentActionBucket required for its usage in the app. * * @param bucket The MegaRecentActionBucket received. * @return A copy of MegaRecentActionBucket. */ public MegaRecentActionBucket copyBucket(MegaRecentActionBucket bucket) { return bucket.copy(); } /** * Returns whether notifications about a chat have to be generated. * * @param chatid MegaHandle that identifies the chat room. * @return true if notification has to be created. */ public boolean isChatNotifiable(long chatid) { return megaApi.isChatNotifiable(chatid); } /** * Provide a phone number to get verification code. * * @param phoneNumber the phone number to receive the txt with verification code. * @param listener callback of this request. */ public void sendSMSVerificationCode(String phoneNumber, nz.mega.sdk.MegaRequestListenerInterface listener) { megaApi.sendSMSVerificationCode(phoneNumber, createDelegateRequestListener(listener)); } public void sendSMSVerificationCode(String phoneNumber, nz.mega.sdk.MegaRequestListenerInterface listener, boolean reverifying_whitelisted) { megaApi.sendSMSVerificationCode(phoneNumber, createDelegateRequestListener(listener), reverifying_whitelisted); } /** * Send the verification code to verify phone number. * * @param verificationCode received 6 digits verification code. * @param listener callback of this request. */ public void checkSMSVerificationCode(String verificationCode, nz.mega.sdk.MegaRequestListenerInterface listener) { megaApi.checkSMSVerificationCode(verificationCode, createDelegateRequestListener(listener)); } /** * Get the verified phone number of the mega account. * * @return verified phone number. */ @Nullable public String smsVerifiedPhoneNumber() { return megaApi.smsVerifiedPhoneNumber(); } /** * Requests the currently available country calling codes * * @param listener MegaRequestListener to track this request */ public void getCountryCallingCodes(nz.mega.sdk.MegaRequestListenerInterface listener) { megaApi.getCountryCallingCodes(createDelegateRequestListener(listener)); } /** * Get the state to see whether blocked account could do SMS verification * * @return the state */ public int smsAllowedState() { return megaApi.smsAllowedState(); } /** * Get the email address of any user in MEGA. *

    * The associated request type with this request is MegaRequest::TYPE_GET_USER_EMAIL * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the user (the provided one as parameter) *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Returns the email address * * @param handle Handle of the user to get the attribute. * @param listener MegaRequestListener to track this request */ public void getUserEmail(long handle, MegaRequestListenerInterface listener) { megaApi.getUserEmail(handle, createDelegateRequestListener(listener)); } /** * Resume a registration process for an Ephemeral++ account *

    * When a user begins the account registration process by calling * MegaApi::createEphemeralAccountPlusPlus an ephemeral++ account is created. *

    * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see MegaApi::sendSignupLink) and also to receive notifications in case the * user confirms the account using another client (MegaGlobalListener::onAccountUpdate or * MegaListener::onAccountUpdate). It is also possible to cancel the registration process by * MegaApi::cancelCreateAccount, which invalidates the signup link associated to the ephemeral * session (the session will be still valid). *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session id to resume the process * - MegaRequest::getParamType - Returns the value MegaApi::RESUME_EPLUSPLUS_ACCOUNT *

    * In case the account is already confirmed, the associated request will fail with * error MegaError::API_EARGS. * * @param sid Session id valid for the ephemeral++ account (@see MegaApi::createEphemeralAccountPlusPlus) * @param listener MegaRequestListener to track this request */ public void resumeCreateAccountEphemeralPlusPlus(String sid, MegaRequestListenerInterface listener) { megaApi.resumeCreateAccountEphemeralPlusPlus(sid, createDelegateRequestListener(listener)); } /** * Cancel a registration process *

    * If a signup link has been generated during registration process, call this function * to invalidate it. The ephemeral session will not be invalidated, only the signup link. *

    * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::CANCEL_ACCOUNT * * @param listener MegaRequestListener to track this request */ public void cancelCreateAccount(MegaRequestListenerInterface listener) { megaApi.cancelCreateAccount(createDelegateRequestListener(listener)); } /** * @param backupType back up type requested for the service * @param targetNode MEGA folder to hold the backups * @param localFolder Local path of the folder * @param backupName Name of the backup * @param state state * @param subState subState * @param listener MegaRequestListener to track this request * Registers a backup to display in Backup Centre *

    * Apps should register backups, like CameraUploads, in order to be listed in the * BackupCentre. The client should send heartbeats to indicate the progress of the * backup (see \c MegaApi::sendBackupHeartbeats). *

    * Possible types of backups: * BACKUP_TYPE_CAMERA_UPLOADS = 3, * BACKUP_TYPE_MEDIA_UPLOADS = 4, // Android has a secondary CU *

    * Note that the backup name is not registered in the API as part of the data of this * backup. It will be stored in a user's attribute after this request finished. For * more information, see \c MegaApi::setBackupName and MegaApi::getBackupName. *

    * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getNodeHandle - Returns the target node of the backup * - MegaRequest::getName - Returns the backup name of the remote location * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getTotalBytes - Returns the backup type * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getFlag - Returns true * - MegaRequest::getListener - Returns the MegaRequestListener to track this request */ public void setBackup(int backupType, long targetNode, String localFolder, String backupName, int state, int subState, MegaRequestListenerInterface listener) { megaApi.setBackup(backupType, targetNode, localFolder, backupName, state, subState, createDelegateRequestListener(listener)); } /** * @param backupId backup id identifying the backup to be updated * @param backupType back up type requested for the service * @param targetNode MEGA folder to hold the backups * @param localFolder Local path of the folder * @param state backup state * @param subState backup subState * @param listener MegaRequestListener to track this request * Update the information about a registered backup for Backup Centre *

    * Possible types of backups: * BACKUP_TYPE_INVALID = -1, * BACKUP_TYPE_CAMERA_UPLOADS = 3, * BACKUP_TYPE_MEDIA_UPLOADS = 4, // Android has a secondary CU *

    * Params that keep the same value are passed with invalid value to avoid to send to the server * Invalid values: * - type: BACKUP_TYPE_INVALID * - nodeHandle: UNDEF * - localFolder: nullptr * - deviceId: nullptr * - state: -1 * - subState: -1 * - extraData: nullptr *

    * If you want to update the backup name, use \c MegaApi::setBackupName. *

    * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getTotalBytes - Returns the backup type * - MegaRequest::getNodeHandle - Returns the target node of the backup * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getListener - Returns the MegaRequestListener to track this request */ public void updateBackup(long backupId, int backupType, long targetNode, String localFolder, String backupName, int state, int subState, MegaRequestListenerInterface listener) { megaApi.updateBackup(backupId, backupType, targetNode, localFolder, backupName, state, subState, createDelegateRequestListener(listener)); } /** * @param backupId backup id identifying the backup to be removed * @param listener MegaRequestListener to track this request * Unregister a backup already registered for the Backup Centre *

    * This method allows to remove a backup from the list of backups displayed in the * Backup Centre. @see \c MegaApi::setBackup. *

    * The associated request type with this request is MegaRequest::TYPE_BACKUP_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getListener - Returns the MegaRequestListener to track this request */ public void removeBackup(long backupId, MegaRequestListenerInterface listener) { megaApi.removeBackup(backupId, createDelegateRequestListener(listener)); } /** * Fetch information about all registered backups for Backup Centre *

    * The associated request type with this request is MegaRequest::TYPE_BACKUP_INFO * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getListener - Returns the MegaRequestListener to track this request *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBackupInfoList - Returns information about all registered backups * * @param listener MegaRequestListener to track this request */ public void getBackupInfo(MegaRequestListenerInterface listener) { megaApi.getBackupInfo(createDelegateRequestListener(listener)); } /** * @param backupId backup id identifying the backup * @param status backup state * @param progress backup progress * @param ups Number of pending upload transfers * @param downs Number of pending download transfers * @param ts Last action timestamp * @param lastNode Last node handle to be synced * @param listener MegaRequestListener to track this request * Send heartbeat associated with an existing backup *

    * The client should call this method regularly for every registered backup, in order to * inform about the status of the backup. *

    * Progress, last timestamp and last node are not always meaningful (ie. when the Camera * Uploads starts a new batch, there isn't a last node, or when the CU up to date and * inactive for long time, the progress doesn't make sense). In consequence, these parameters * are optional. They will not be sent to API if they take the following values: * - lastNode = INVALID_HANDLE * - lastTs = -1 * - progress = -1 *

    * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT_HEART_BEAT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getParamType - Returns the number of pending upload transfers * - MegaRequest::getTransferTag - Returns the number of pending download transfers * - MegaRequest::getNumber - Returns the last action timestamp * - MegaRequest::getNodeHandle - Returns the last node handle to be synced */ public void sendBackupHeartbeat(long backupId, int status, int progress, int ups, int downs, long ts, long lastNode, MegaRequestListenerInterface listener) { megaApi.sendBackupHeartbeat(backupId, status, progress, ups, downs, ts, lastNode, createDelegateRequestListener(listener)); } /** * Fetch ads *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_ADS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber A bitmap flag used to communicate with the API * - MegaRequest::getMegaStringList List of the adslot ids to fetch * - MegaRequest::getNodeHandle Public handle that the user is visiting *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaStringMap: map with relationship between ids and ius * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - ADS_DEFAULT = 0x0 * - ADS_FORCE_ADS = 0x200 * - ADS_IGNORE_MEGA = 0x400 * - ADS_IGNORE_COUNTRY = 0x800 * - ADS_IGNORE_IP = 0x1000 * - ADS_IGNORE_PRO = 0x2000 * - ADS_FLAG_IGNORE_ROLLOUT = 0x4000 * @param adUnits MegaStringList, a list of the adslot ids to fetch; it cannot be null nor empty * @param publicHandle MegaHandle, provide the public handle that the user is visiting * @param listener MegaRequestListener to track this request */ public void fetchAds(int adFlags, MegaStringList adUnits, long publicHandle, MegaRequestListenerInterface listener) { megaApi.fetchAds(adFlags, adUnits, publicHandle, createDelegateRequestListener(listener)); }; /** * Check if ads should show or not *

    * The associated request type with this request is MegaRequest::TYPE_QUERY_ADS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber A bitmap flag used to communicate with the API * - MegaRequest::getNodeHandle Public handle that the user is visiting *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumDetails Return if ads should be show or not * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - ADS_DEFAULT = 0x0 * - ADS_FORCE_ADS = 0x200 * - ADS_IGNORE_MEGA = 0x400 * - ADS_IGNORE_COUNTRY = 0x800 * - ADS_IGNORE_IP = 0x1000 * - ADS_IGNORE_PRO = 0x2000 * - ADS_FLAG_IGNORE_ROLLOUT = 0x4000 * @param publicHandle MegaHandle, provide the public handle that the user is visiting * @param listener MegaRequestListener to track this request */ public void queryAds(int adFlags, long publicHandle, MegaRequestListenerInterface listener) { megaApi.queryAds(adFlags, publicHandle, createDelegateRequestListener(listener)); } /** * Set a bitmap to indicate whether some cookies are enabled or not *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getNumDetails - Return a bitmap with cookie settings * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param settings A bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * @param listener MegaRequestListener to track this request */ public void setCookieSettings(int settings, MegaRequestListenerInterface listener) { megaApi.setCookieSettings(settings, createDelegateRequestListener(listener)); } /** * Set a bitmap to indicate whether some cookies are enabled or not *

    * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getNumDetails - Return a bitmap with cookie settings * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param settings A bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty */ public void setCookieSettings(int settings) { megaApi.setCookieSettings(settings); } /** * Get a bitmap to indicate whether some cookies are enabled or not *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getListener - Returns the MegaRequestListener to track this request *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumDetails Return the bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINTERNAL - If the value for cookie settings bitmap was invalid * * @param listener MegaRequestListener to track this request */ public void getCookieSettings(MegaRequestListenerInterface listener) { megaApi.getCookieSettings(createDelegateRequestListener(listener)); } /** * Get a bitmap to indicate whether some cookies are enabled or not *

    * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getListener - Returns the MegaRequestListener to track this request *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumDetails Return the bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINTERNAL - If the value for cookie settings bitmap was invalid */ public void getCookieSettings() { megaApi.getCookieSettings(); } /** * Check if the app can start showing the cookie banner *

    * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value, but only when it follows a login with user and password, * not when an existing session is resumed. *

    * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if this feature is enabled. Otherwise, false. */ public boolean isCookieBannerEnabled() { return megaApi.cookieBannerEnabled(); } /** * Creates the special folder for backups ("My backups") *

    * It creates a new folder inside the Vault rootnode and later stores the node's * handle in a user's attribute, MegaApi::USER_ATTR_MY_BACKUPS_FOLDER. *

    * Apps should first check if this folder exists already, by calling * MegaApi::getUserAttribute for the corresponding attribute. *

    * The associated request type with this request is MegaRequest::TYPE_SET_MY_BACKUPS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the name provided as parameter *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the node handle of the folder created *

    * If the folder for backups already existed, the request will fail with the error API_EACCESS. * * @param localizedName Localized name for "My backups" folder * @param listener MegaRequestListener to track this request */ public void setMyBackupsFolder(String localizedName, MegaRequestListenerInterface listener) { megaApi.setMyBackupsFolder(localizedName, createDelegateRequestListener(listener)); } /** * Request creation of a new Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getText - Returns name of the Set * - MegaRequest::getParamType - Returns CREATE_SET, possibly combined with OPTION_SET_NAME *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - Returns either the new Set, or null if it was not created. *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param name the name that should be given to the new Set * @param type the type of the Set (see MegaSet for possible types) * @param listener MegaRequestListener to track this request */ public void createSet(String name, int type, MegaRequestListenerInterface listener) { megaApi.createSet(name, type, createDelegateRequestListener(listener)); } /** * Request creation of a new Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getText - Returns name of the Set * - MegaRequest::getParamType - Returns CREATE_SET, possibly combined with OPTION_SET_NAME *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - Returns either the new Set, or null if it was not created. *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param name the name that should be given to the new Set */ public void createSet(String name) { megaApi.createSet(name); } /** * Request to update the name of a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getText - Returns new name of the Set * - MegaRequest::getParamType - Returns OPTION_SET_NAME *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param name the new name that should be given to the Set * @param listener MegaRequestListener to track this request */ public void updateSetName(long sid, String name, MegaRequestListenerInterface listener) { megaApi.updateSetName(sid, name, createDelegateRequestListener(listener)); } /** * Request to update the name of a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getText - Returns new name of the Set * - MegaRequest::getParamType - Returns OPTION_SET_NAME *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param name the new name that should be given to the Set */ public void updateSetName(long sid, String name) { megaApi.updateSetName(sid, name); } /** * Request to update the cover of a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getNodeHandle - Returns Element id to be set as the new cover * - MegaRequest::getParamType - Returns OPTION_SET_COVER *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Given Element id was not part of the current Set; Malformed (from API). * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param eid the id of the Element to be set as cover * @param listener MegaRequestListener to track this request */ public void putSetCover(long sid, long eid, MegaRequestListenerInterface listener) { megaApi.putSetCover(sid, eid, createDelegateRequestListener(listener)); } /** * Request to update the cover of a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getNodeHandle - Returns Element id to be set as the new cover * - MegaRequest::getParamType - Returns OPTION_SET_COVER *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Given Element id was not part of the current Set; Malformed (from API). * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param eid the id of the Element to be set as cover */ public void putSetCover(long sid, long eid) { megaApi.putSetCover(sid, eid); } /** * Request to remove a Set *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be removed *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be removed * @param listener MegaRequestListener to track this request */ public void removeSet(long sid, MegaRequestListenerInterface listener) { megaApi.removeSet(sid, createDelegateRequestListener(listener)); } /** * Request to remove a Set *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be removed *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be removed */ public void removeSet(long sid) { megaApi.removeSet(sid); } /** * Request creation of multiple Elements for a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getMegaHandleList - Returns a list containing the file handles corresponding to the new Elements * - MegaRequest::getMegaStringList - Returns a list containing the names corresponding to the new Elements *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Elements * - MegaRequest::getMegaIntegerList - Returns a list containing error codes for all requested Elements *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not besetExcludedNames found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Elements * @param nodes the handles of the file-nodes that will be represented by the new Elements * @param names the names that should be given to the new Elements (param names must be either null or have * the same size() as param nodes) * @param listener MegaRequestListener to track this request */ public void createSetElements(long sid, MegaHandleList nodes, MegaStringList names, MegaRequestListenerInterface listener) { megaApi.createSetElements(sid, nodes, names, createDelegateRequestListener(listener)); } /** * Request creation of a new Element for a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns CREATE_ELEMENT, possibly combined with OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found, or node could not be found. * - MegaError::API_EKEY - File-node had no key. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Element * @param node the handle of the file-node that will be represented by the new Element * @param name the name that should be given to the new Element * @param listener MegaRequestListener to track this request */ public void createSetElement(long sid, long node, String name, MegaRequestListenerInterface listener) { megaApi.createSetElement(sid, node, name, createDelegateRequestListener(listener)); } /** * Request creation of a new Element for a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns CREATE_ELEMENT, possibly combined with OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found, or node could not be found. * - MegaError::API_EKEY - File-node had no key. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Element * @param node the handle of the file-node that will be represented by the new Element * @param name the name that should be given to the new Element */ public void createSetElement(long sid, long node, String name) { megaApi.createSetElement(sid, node, name); } /** * Request creation of a new Element for a Set *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns CREATE_ELEMENT, possibly combined with OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found, or node could not be found. * - MegaError::API_EKEY - File-node had no key. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Element * @param node the handle of the file-node that will be represented by the new Element */ public void createSetElement(long sid, long node) { megaApi.createSetElement(sid, node); } /** * Request to update the name of an Element *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param name the new name that should be given to the Element * @param listener MegaRequestListener to track this request */ public void updateSetElementName(long sid, long eid, String name, MegaRequestListenerInterface listener) { megaApi.updateSetElementName(sid, eid, name, createDelegateRequestListener(listener)); } /** * Request to update the name of an Element *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param name the new name that should be given to the Element */ public void updateSetElementName(long sid, long eid, String name) { megaApi.updateSetElementName(sid, eid, name); } /** * Request to update the order of an Element *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_ORDER * - MegaRequest::getNumber - Returns order of the Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param order the new order of the Element * @param listener MegaRequestListener to track this request */ public void updateSetElementOrder(long sid, long eid, long order, MegaRequestListenerInterface listener) { megaApi.updateSetElementOrder(sid, eid, order, createDelegateRequestListener(listener)); } /** * Request to update the order of an Element *

    * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_ORDER * - MegaRequest::getNumber - Returns order of the Element *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param order the new order of the Element */ public void updateSetElementOrder(long sid, long eid, long order) { megaApi.updateSetElementOrder(sid, eid, order); } /** * Request removal of multiple Elements from a Set *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET_ELEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getMegaHandleList - Returns a list containing the handles of Elements to be removed *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaIntegerList - Returns a list containing error codes for all Elements intended for removal *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Elements * @param eids the ids of Elements to be removed * @param listener MegaRequestListener to track this request */ public void removeSetElements(long sid, MegaHandleList eids, MegaRequestListenerInterface listener) { megaApi.removeSetElements(sid, eids, createDelegateRequestListener(listener)); } /** * Request to remove an Element *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be removed * - MegaRequest::getTotalBytes - Returns the id of the Set *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - No Set or no Element with given ids could be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element to be removed * @param listener MegaRequestListener to track this request */ public void removeSetElement(long sid, long eid, MegaRequestListenerInterface listener) { megaApi.removeSetElement(sid, eid, createDelegateRequestListener(listener)); } /** * Request to remove an Element *

    * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be removed * - MegaRequest::getTotalBytes - Returns the id of the Set *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - No Set or no Element with given ids could be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element to be removed */ public void removeSetElement(long sid, long eid) { megaApi.removeSetElement(sid, eid); } /** * Get a list of all Sets available for current user. *

    * The response value is stored as a MegaSetList. *

    * You take the ownership of the returned value * * @return list of Sets */ public MegaSetList getSets() { return megaApi.getSets(); } /** * Get the Set with the given id, for current user. *

    * The response value is stored as a MegaSet. *

    * You take the ownership of the returned value * * @param sid the id of the Set to be retrieved * @return the requested Set, or null if not found */ public MegaSet getSet(long sid) { return megaApi.getSet(sid); } /** * Get the cover (Element id) of the Set with the given id, for current user. * * @param sid the id of the Set to retrieve the cover for * @return Element id of the cover, or INVALIDHANDLE if not set or invalid id */ public long getSetCover(long sid) { return megaApi.getSetCover(sid); } /** * Get Element count of the Set with the given id, for current user. * * @param sid the id of the Set to get Element count for * @return Element count of requested Set, or 0 if not found */ public long getSetElementCount(long sid) { return megaApi.getSetElementCount(sid); } /** * Get Element count of the Set with the given id, for current user. * * @param sid the id of the Set to get Element count for * @param includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * @return Element count of requested Set, or 0 if not found */ public long getSetElementCount(long sid, boolean includeElementsInRubbishBin) { return megaApi.getSetElementCount(sid, includeElementsInRubbishBin); } /** * Get all Elements in the Set with given id, for current user. *

    * The response value is stored as a MegaSetElementList. *

    * You take the ownership of the returned value * * @param sid the id of the Set owning the Elements * @return all Elements in that Set, or null if not found or none added */ public MegaSetElementList getSetElements(long sid) { return megaApi.getSetElements(sid); } /** * Get all Elements in the Set with given id, for current user. *

    * The response value is stored as a MegaSetElementList. *

    * You take the ownership of the returned value * * @param sid the id of the Set owning the Elements * @param includeElementsInRubbishBin includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * @return all Elements in that Set, or null if not found or none added */ public MegaSetElementList getSetElements(long sid, boolean includeElementsInRubbishBin) { return megaApi.getSetElements(sid, includeElementsInRubbishBin); } /** * Get a particular Element in a particular Set, for current user. *

    * The response value is stored as a MegaSetElement. *

    * You take the ownership of the returned value * * @param sid the id of the Set owning the Element * @param eid the id of the Element to be retrieved * @return requested Element, or null if not found */ public MegaSetElement getSetElement(long sid, long eid) { return megaApi.getSetElement(sid, eid); } /** * Start a Sync or Backup between a local folder and a folder in MEGA * * This function should be used to add a new synchronization/backup task for the MegaApi. * To resume a previously configured task folder, use MegaApi::enableSync. * * Both TYPE_TWOWAY and TYPE_BACKUP are supported for the first parameter. * * The sync/backup's name is optional. If not provided, it will take the name of the leaf folder of * the local path. In example, for "/home/user/Documents", it will become "Documents". * * The remote sync root folder should be INVALID_HANDLE for syncs of TYPE_BACKUP. The handle of the * remote node, which is created as part of this request, will be set to the MegaRequest::getNodeHandle. * * The associated request type with this request is MegaRequest::TYPE_ADD_SYNC * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder in MEGA * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getName - Returns the name of the sync * - MegaRequest::getParamType - Returns the type of the sync * - MegaRequest::getLink - Returns the drive root if external backup * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * - MegaRequest::getNumDetails - If different than NO_SYNC_ERROR, it returns additional info for * the specific sync error (MegaSync::Error). It could happen both when the request has succeeded (API_OK) and * also in some cases of failure, when the request error is not accurate enough. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is other than MegaError::API_OK: * - MegaRequest::getNumber - Fingerprint of the local folder. Note, fingerprint will only be valid * if the sync was added with no errors * - MegaRequest::getParentHandle - Returns the sync backupId * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the local folder was not set or is not a folder. * - MegaError::API_EACCESS - If the user was invalid, or did not have an attribute for "My Backups" folder, * or the attribute was invalid, or "My Backups"/`DEVICE_NAME` existed but was not a folder, or it had the * wrong 'dev-id'/'drv-id' tag. * - MegaError::API_EINTERNAL - If the user attribute for "My Backups" folder did not have a record containing * the handle. * - MegaError::API_ENOENT - If the handle of "My Backups" folder contained in the user attribute was invalid * - or the node could not be found. * - MegaError::API_EINCOMPLETE - If device id was not set, or if current user did not have an attribute for * device name, or the attribute was invalid, or the attribute did not contain a record for the device name, * or device name was empty. * - MegaError::API_EEXIST - If this is a new device, but a folder with the same device-name already exists. * * @param syncType Type of sync. Currently supported: TYPE_TWOWAY and TYPE_BACKUP. * @param localSyncRootFolder Path of the Local folder to sync/backup. * @param name Name given to the sync. You can pass NULL, and the folder name will be used instead. * @param remoteSyncRootFolder Handle of MEGA folder. If you have a MegaNode for that folder, use its getHandle() * @param driveRootIfExternal Only relevant for backups, and only if the backup is on an external disk. Otherwise use NULL. * @param listener MegaRequestListener to track this request */ public void syncFolder( MegaSync.SyncType syncType, String localSyncRootFolder, String name, long remoteSyncRootFolder, String driveRootIfExternal, MegaRequestListenerInterface listener ) { megaApi.syncFolder( syncType, localSyncRootFolder, name, remoteSyncRootFolder, driveRootIfExternal, createDelegateRequestListener(listener, true) ); } /** * Get all configured syncs * * You take the ownership of the returned value * * @return List of MegaSync objects with all syncs */ public MegaSyncList getSyncs() { return megaApi.getSyncs(); } /** * De-configure the sync/backup of a folder * * The folder will stop being synced. No files in the local nor in the remote folder * will be deleted due to the usage of this function. * * The synchronization will stop and the local sync database will be deleted * The backupId of this sync will be invalid going forward. * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SYNC * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns sync backupId * - MegaRequest::getFlag - Returns true * - MegaRequest::getFile - Returns the path of the local folder (for active syncs only) * * @param backupId Identifier of the Sync (unique per user, provided by API) */ public void removeSync(long backupId) { megaApi.removeSync(backupId); } /** * Resumes all sync folder pairs */ public void resumeAllSyncs() { MegaSyncList syncs = megaApi.getSyncs(); int syncsSize = syncs.size(); for (int i = 0; i < syncsSize; i++) { MegaSync sync = syncs.get(i); megaApi.setSyncRunState(sync.getBackupId(), RUNSTATE_RUNNING); } } /** * Returns true if the Set has been exported (has a public link) *

    * Public links are created by calling MegaApi::exportSet * * @param sid the id of the Set to check * @return true if param sid is an exported Set */ public boolean isExportedSet(long sid) { return megaApi.isExportedSet(sid); } /** * Generate a public link of a Set in MEGA *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns id of the Set used as parameter * - MegaRequest::getFlag - Returns a boolean set to true representing the call was * meant to enable/create the export *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - MegaSet including the public id * - MegaRequest::getLink - Public link *

    * MegaError::API_OK results in onSetsUpdate being triggered as well *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param sid MegaHandle to get the public link * @param listener MegaRequestListener to track this request */ public void exportSet(long sid, MegaRequestListenerInterface listener) { megaApi.exportSet(sid, createDelegateRequestListener(listener)); } /** * Stop sharing a Set *

    * The associated request type with this request is MegaRequest::TYPE_EXPORT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns id of the Set used as parameter * - MegaRequest::getFlag - Returns a boolean set to false representing the call was * meant to disable the export *

    * MegaError::API_OK results in onSetsUpdate being triggered as well *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param sid Set MegaHandle to stop sharing * @param listener MegaRequestListener to track this request */ public void disableExportSet(long sid, MegaRequestListenerInterface listener) { megaApi.disableExportSet(sid, createDelegateRequestListener(listener)); } /** * Request to fetch a public/exported Set and its Elements. *

    * The associated request type with this request is MegaRequest::TYPE_FETCH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the link used for the public Set fetch request *

    * In addition to fetching the Set (including Elements), SDK's instance is set * to preview mode for the public Set. This mode allows downloading of foreign * SetElements included in the public Set. *

    * To disable the preview mode and release resources used by the preview Set, * use MegaApi::stopPublicSetPreview *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - Returns the Set * - MegaRequest::getMegaSetElementList - Returns the list of Elements *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. *

    * * @param publicSetLink Public link to a Set in MEGA * @param listener MegaRequestListener to track this request */ public void fetchPublicSet(String publicSetLink, MegaRequestListenerInterface listener) { megaApi.fetchPublicSet(publicSetLink, createDelegateRequestListener(listener)); } /** * Stops public Set preview mode for current SDK instance *

    * MegaApi instance is no longer useful until a new login */ public void stopPublicSetPreview() { megaApi.stopPublicSetPreview(); } /** * Returns if this MegaApi instance is in a public/exported Set preview mode * * @return True if public Set preview mode is enabled */ public boolean inPublicSetPreview() { return megaApi.inPublicSetPreview(); } /** * Get current public/exported Set in Preview mode *

    * The response value is stored as a MegaSet. *

    * You take the ownership of the returned value * * @return Current public/exported Set in preview mode or nullptr if there is none */ @Nullable public MegaSet getPublicSetInPreview() { return megaApi.getPublicSetInPreview(); } /** * Get current public/exported SetElements in Preview mode *

    * The response value is stored as a MegaSetElementList. *

    * You take the ownership of the returned value * * @return Current public/exported SetElements in preview mode or nullptr if there is none */ public MegaSetElementList getPublicSetElementsInPreview() { return megaApi.getPublicSetElementsInPreview(); } /** * Gets a MegaNode for the foreign MegaSetElement that can be used to download the Element *

    * The associated request type with this request is MegaRequest::TYPE_GET_EXPORTED_SET_ELEMENT *

    * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPublicMegaNode - Returns the MegaNode (ownership transferred) *

    * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - Public Set preview mode is not enabled * - MegaError::API_EARGS - MegaHandle for SetElement provided as param doesn't match any Element * in previewed Set *

    * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param eid MegaHandle of target SetElement from Set in preview mode * @param listener MegaRequestListener to track this request */ public void getPreviewElementNode(long eid, MegaRequestListenerInterface listener) { megaApi.getPreviewElementNode(eid, createDelegateRequestListener(listener)); } /** * Gets a MegaNode for the foreign MegaSetElement that can be used to download the Element * * @param sid MegaHandle of target Set to get its public link/URL * @return const char* with the public URL if success, nullptr otherwise * In any case, one of the followings error codes with the result can be found in the log: * - API_OK on success * - API_ENOENT if sid doesn't match any owned Set or the Set is not exported * - API_EARGS if there was an internal error composing the URL */ @Nullable public String getPublicLinkForExportedSet(long sid) { return megaApi.getPublicLinkForExportedSet(sid); } /** * Get the list of IDs for enabled notifications *

    * You take the ownership of the returned value * * @return List of IDs for enabled notifications */ public MegaIntegerList getEnabledNotifications() { return megaApi.getEnabledNotifications(); } /** * Get list of available notifications for Notification Center *

    * The associated request type with this request is MegaRequest::TYPE_GET_NOTIFICATIONS. *

    * When onRequestFinish received MegaError::API_OK, valid data in the MegaRequest object is: * - MegaRequest::getMegaNotifications - Returns the list of notifications *

    * When onRequestFinish errored, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - No such notifications exist, and MegaRequest::getMegaNotifications * will return a non-null, empty list. * - MegaError::API_EACCESS - No user was logged in. * - MegaError::API_EINTERNAL - Received answer could not be read. * * @param listener MegaRequestListener to track this request */ public void getNotifications(MegaRequestListenerInterface listener) { megaApi.getNotifications(createDelegateRequestListener(listener)); } /** * Set last read notification for Notification Center *

    * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_READ_NOTIFICATION * - MegaRequest::getNumber - Returns the ID to be set as last read *

    * Note that any notifications with ID equal to or less than the given one will be marked as seen * in Notification Center. * * @param notificationId ID of the notification to be set as last read. Value `0` is an invalid ID. * Passing `0` will clear a previously set last read value. * @param listener MegaRequestListener to track this request */ public void setLastReadNotification(long notificationId, MegaRequestListenerInterface listener) { megaApi.setLastReadNotification(notificationId, createDelegateRequestListener(listener)); } /** * Get last read notification for Notification Center *

    * The type associated with this request is MegaRequest::TYPE_GET_ATTR_USER *

    * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_READ_NOTIFICATION *

    * When onRequestFinish received MegaError::API_OK, valid data in the MegaRequest object is: * - MegaRequest::getNumber - Returns the ID of the last read Notification * Note that when the ID returned here was `0` it means that no ID was set as last read. * Note that the value returned here should be treated like a 32bit unsigned int. * * @param listener MegaRequestListener to track this request */ public void getLastReadNotification(MegaRequestListenerInterface listener) { megaApi.getLastReadNotification(createDelegateRequestListener(listener)); } /** * @Deprecated: Use the other MegaApi::getFlag overload instead * @param flagName * @param commit * @return */ public MegaFlag getFlag(String flagName, Boolean commit, MegaRequestListenerInterface listener) { return megaApi.getFlag(flagName, commit, createDelegateRequestListener(listener)); } /** * Get the type and value for the flag with the given name, if present among either * A/B Test or Feature flags. * * If found among A/B Test flags and commit was true, also inform the API that a user has become * relevant for that A/B Test flag (via a request of type MegaRequest::TYPE_AB_TEST_ACTIVE, * for which the response is not relevant for the calling app) * * @param flagName Name or key of the value to be retrieved (and possibly be sent to API as active). * @param commit Determine whether an A/B Test flag will be sent to API as active. * * @return A MegaFlag instance with the type and value of the flag. */ public MegaFlag getFlag(String flagName, Boolean commit) { return megaApi.getFlag(flagName, commit); } /** * Initiate an asynchronous request to receive stalled issues. * * Use MegaRequestListenerInterface to subscribe for result. * Result is of MegaRequest.TYPE_GET_SYNC_STALL_LIST type. */ public void requestMegaSyncStallList(MegaRequestListenerInterface listener) { megaApi.getMegaSyncStallList(createDelegateRequestListener(listener)); } /** * Find out if the syncs need User intervention for some files/folders * * use getMegaSyncStallList() to find out what needs attention. * * @return true if the User is needs to intervene. * */ public boolean isSyncStalled() { return megaApi.isSyncStalled(); } /** * Resume a previously suspended sync */ public void resumeSync(long backupId) { megaApi.setSyncRunState(backupId, RUNSTATE_RUNNING); } /** * Suspend a sync *

    * Use this method to pause a running Sync. The sync can be resumed later by calling MegaApi::resumeSync. */ public void pauseSync(long backupId) { megaApi.setSyncRunState(backupId, RUNSTATE_SUSPENDED); } /** * Check if it's possible to start synchronizing a folder node. Return SyncError errors. * * Possible return values for this function are: * - MegaError::API_OK if the folder is syncable * - MegaError::API_ENOENT if the node doesn't exist in the account * - MegaError::API_EARGS if the node is NULL or is not a folder * * - MegaError::API_EACCESS: * SyncError: SHARE_NON_FULL_ACCESS An ancestor node does not have full access * SyncError: REMOTE_NODE_INSIDE_RUBBISH * - MegaError::API_EEXIST if there is a conflicting synchronization (nodes can't be synced twice) * SyncError: ACTIVE_SYNC_BELOW_PATH - There's a synced node below the path to be synced * SyncError: ACTIVE_SYNC_ABOVE_PATH - There's a synced node above the path to be synced * SyncError: ACTIVE_SYNC_SAME_PATH - There's a synced node at the path to be synced * - MegaError::API_EINCOMPLETE if the SDK hasn't been built with support for synchronization * * @return API_OK if syncable. Error otherwise sets syncError in the returned MegaError * caller must free */ public MegaError isNodeSyncableWithError(MegaNode megaNode) { return megaApi.isNodeSyncableWithError(megaNode); } /** * Move or Remove the nodes that used to be part of backup. * * The folder must be in folder Vault//, and will be moved, or permanently deleted. * Deletion is permanent (not to trash) and is selected with destination INVALID_HANDLE. * To move the nodes instead, specify the destination folder in backupDestination. * * These nodes cannot be deleted with the usual remove() function as they are in the Vault. * * The associated request type with this request is MegaRequest::TYPE_REMOVE_OLD_BACKUP_NODES * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the deconfiguredBackupRoot handle * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - deconfiguredBackupRoot was not valid * - MegaError::API_EARGS - deconfiguredBackupRoot was not in the Vault, * or backupDestination was not in Files or Rubbish * * @param deconfiguredBackupRoot Identifier of the Sync (unique per user, provided by API) * @param backupDestination If INVALID_HANDLE, files will be permanently deleted, otherwise files will be moved there. * @param listener MegaRequestListener to track this request */ public void moveOrRemoveDeconfiguredBackupNodes(long deconfiguredBackupRoot, long backupDestination, MegaRequestListenerInterface listener) { megaApi.moveOrRemoveDeconfiguredBackupNodes(deconfiguredBackupRoot, backupDestination, createDelegateRequestListener(listener)); } /** * @brief Change the local path that is being used as root for a sync. * * The associated request type with this request is MegaRequest::TYPE_CHANGE_SYNC_ROOT. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the path of the new local folder to use as root * - MegaRequest::getNodeHandle - Returns the affected sync backup ID. * - MegaRequest::getListener - Returns the MegaRequestListener to track this request. * - MegaRequest::getNumDetails - If different than NO_SYNC_ERROR, it returns additional * info for the specific sync error (MegaSync::Error). This can occur both when the * request has succeeded (API_OK) and in some cases of failure when the request error is * not sufficiently descriptive. * * On the onRequestFinish callback, the error code associated with the MegaError * (MegaError::getErrorCode()) and the SyncError (if relevant, MegaError::getSyncError()) * can be: * - MegaError::API_OK: * + SyncError::NO_SYNC_ERROR the new root has been changed successfully * - MegaError::API_EARGS: * + SyncError::LOCAL_PATH_UNAVAILABLE the given path is nullptr or is empty * + SyncError::UNKNOWN_ERROR The given backupId does not match any of the registered * syncs * + SyncError::LOCAL_PATH_SYNC_COLLISION The local path conflicts with existing * synchronization paths (nested syncs are not allowed) * + SyncError::FILESYSTEM_ID_UNAVAILABLE unable to get the file system fingerprint with * the given path * + SyncError::LOCAL_FILESYSTEM_MISMATCH The given path is in a different file system * comparing with the previous one. We don't allow this operation * + SyncError::UNABLE_TO_RETRIEVE_ROOT_FSID The new root directory cannot be opened * + SyncError::BACKUP_SOURCE_NOT_BELOW_DRIVE The new root directory is not contained in * the previous external path. That operation is not allowed. * - MegaError::API_EWRITE: * + SyncError::SYNC_CONFIG_WRITE_FAILURE We couldn't write into the database to commit * the change. * - MegaError::API_EFAILED: * + SyncError::LOCAL_PATH_MOUNTED trying to sync bellow a FUSE mount point * + SyncError::UNSUPPORTED_FILE_SYSTEM the given path is in a not supported file system * - MegaError::API_ETEMPUNAVAIL: * + SyncError::LOCAL_PATH_TEMPORARY_UNAVAILABLE the given new path is temporarily * unavailable * - MegaError::API_ENOENT: * + SyncError::LOCAL_PATH_UNAVAILABLE the given new path is not available * - MegaError::API_EACCESS: * + SyncError::INVALID_LOCAL_TYPE the given path is not a directory * * @param syncBackupId The handle (backup ID) of the sync whose local root is to be changed. * @param newLocalSyncRootPath The new local path to set as the sync root. * @param listener A MegaRequestListener to track this request. This parameter is optional. */ public void changeSyncLocalRoot(long syncBackupId, String newLocalSyncRootPath, MegaRequestListenerInterface listener) { megaApi.changeSyncLocalRoot(syncBackupId, newLocalSyncRootPath, createDelegateRequestListener(listener)); } /** * @param link The recovery link sent to the user's email address. * @param recoveryKey Base64-encoded string containing the recoveryKey (masterKey). * @param listener MegaRequestListener to track this request * @brief Check that the provided recovery key (master key) is correct *

    * The associated request type with this request is MegaRequest::TYPE_CHECK_RECOVERY_KEY * No data in the MegaRequest object received on all callbacks */ public void checkRecoveryKey(String link, String recoveryKey, MegaRequestListenerInterface listener) { megaApi.checkRecoveryKey(link, recoveryKey, createDelegateRequestListener(listener)); } /** * @brief Returns true if S4 object storage is enabled * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return True if enabled, false if disabled */ public boolean isS4Enabled() { return megaApi.isS4Enabled(); } /** * @brief Returns the node's handle of the S4 container * * S4 requires a folder in the root of the Cloud Drive to operate. * This method returns the handle of the related node, or INVALID_HANDLE if the * S4 service is disabled. * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return The node's handle, or INVALID_HANDLE if not set. */ public long getS4Container() { return megaApi.getS4Container(); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaApiSwing.java000066400000000000000000000033201516266226600224620ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import javax.swing.SwingUtilities; /** * Control a MEGA account or a shared folder using a Java Swing GUI. * * @see MegaApiJava */ public class MegaApiSwing extends MegaApiJava { /** * Instantiates a new MEGA API using Swing. * * @param appKey * AppKey of your application. You can generate your AppKey for free here:
    * https://mega.co.nz/#sdk. * @param userAgent * User agent to use in network requests. If you pass null to this parameter, * a default user agent will be used. * @param path * Base path to store the local cache. If you pass null to this parameter, * the SDK will not use any local cache. * @see MegaApiJava#MegaApiJava(String appKey, String userAgent, String basePath, MegaGfxProcessor gfxProcessor) */ public MegaApiSwing(String appKey, String userAgent, String path) { super(appKey, userAgent, path, new MegaGfxProcessor()); } @Override void runCallback(Runnable runnable) { SwingUtilities.invokeLater(runnable); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaChildren.java000066400000000000000000000021531516266226600224740ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; import java.util.ArrayList; public class MegaChildren { private ArrayList fileList = new ArrayList(); private ArrayList folderList = new ArrayList(); public ArrayList getFileList(){ return fileList; } public ArrayList getFolderList(){ return folderList; } public void setFileList (ArrayList fileList){ this.fileList = fileList; } public void setFolderList (ArrayList folderList){ this.folderList = folderList; } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaGlobalListenerInterface.kt000066400000000000000000000254271516266226600252010ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk /** * Interface to get information about global events. * * * You can implement this interface and start receiving events calling MegaApiJava.addGlobalListener(). * MegaListener objects can also receive global events. */ interface MegaGlobalListenerInterface { /** * This function is called when there are new or updated contacts in the account. * * * The SDK retains the ownership of the MegaUserList in the second parameter. * The list and all the MegaUser objects that it contains will be valid until this function returns. * If you want to save the list, use MegaUserList.copy(). * If you want to save only some of the MegaUser objects, use MegaUser.copy() for those objects. * @param api * Mega Java API connected to account. * @param users * List of new or updated Contacts. */ fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) /** * This function is called when there are new or updated user alerts in the account * * The SDK retains the ownership of the MegaUserAlertList in the second parameter. The list and all the * MegaUserAlert objects that it contains will be valid until this function returns. If you want to save the * list, use MegaUserAlertList::copy. If you want to save only some of the MegaUserAlert objects, use MegaUserAlert::copy * for those objects. * * @param api MegaApi object connected to the account * @param userAlerts List that contains the new or updated contacts */ fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList?) /** * This function is called when there are new or updated nodes in the account. * * * When the full account is reloaded or a large number of server notifications arrives at once, * the second parameter will be null. * The SDK retains the ownership of the MegaNodeList in the second parameter. * The list and all the MegaNode objects that it contains will be valid until this function returns. * If you want to save the list, use MegaNodeList.copy(). * If you want to save only some of the MegaNode objects, use MegaNode.copy() for those nodes. * * @param api * API connected to account. * @param nodeList * List of new or updated Nodes. */ fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList?) /** * This function is called when the account has been updated (confirmed/upgraded/downgraded) * * The usage of this callback to handle the external account confirmation is deprecated. * Instead, you should use MegaGlobalListener::onEvent. * * @param api MegaApi object connected to the account */ fun onAccountUpdate(api: MegaApiJava) /** * This function is called when there are new or updated contact requests in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be NULL. * * The SDK retains the ownership of the MegaContactRequestList in the second parameter. The list and all the * MegaContactRequest objects that it contains will be valid until this function returns. If you want to save the * list, use MegaContactRequestList::copy. If you want to save only some of the MegaContactRequest objects, use MegaContactRequest::copy * for them. * * @param api MegaApi object connected to the account * @param requests List that contains the new or updated contact requests */ fun onContactRequestsUpdate(api: MegaApiJava, requests: ArrayList?) /** * The details about the event, like the type of event and optionally any * additional parameter, is received in the \c params parameter. * * You can check the type of event by calling MegaEvent::getType * * The SDK retains the ownership of the details of the event (\c event). * Don't use them after this functions returns. * * Currently, the following type of events are notified: * * - MegaEvent::EVENT_COMMIT_DB: when the SDK commits the ongoing DB transaction. * This event can be used to keep synchronization between the SDK cache and the * cache managed by the app thanks to the sequence number. * * Valid data in the MegaEvent object received in the callback: * - MegaEvent::getText: sequence number recorded by the SDK when this event happened * * - MegaEvent::EVENT_ACCOUNT_CONFIRMATION: when a new account is finally confirmed * by the user by confirming the signup link. * * Valid data in the MegaEvent object received in the callback: * - MegaEvent::getText: email address used to confirm the account * * - MegaEvent::EVENT_DISCONNECT: when the SDK performs a disconnect to reset all the * existing open-connections, since they have become unusable. It's recommended that the app * receiving this event reset its connections with other servers, since the disconnect * performed by the SDK is due to a network change or IP addresses becoming invalid. * * - MegaEvent::EVENT_ACCOUNT_BLOCKED: when the account get blocked, typically because of * infringement of the Mega's terms of service repeatedly. This event is followed by an automatic * logout. * * Valid data in the MegaEvent object received in the callback: * - MegaEvent::getText: message to show to the user. * - MegaEvent::getNumber: code representing the reason for being blocked. * 200: suspension message for any type of suspension, but copyright suspension. * 300: suspension only for multiple copyright violations. * 400: the subuser account has been disabled. * 401: the subuser account has been removed. * 500: The account needs to be verified by an SMS code. * 700: the account is supended for Weak Account Protection. * * - MegaEvent::EVENT_STORAGE: when the status of the storage changes. * * For this event type, MegaEvent::getNumber provides the current status of the storage * * There are three possible storage states: * - MegaApi::STORAGE_STATE_GREEN = 0 * There are no storage problems * * - MegaApi::STORAGE_STATE_ORANGE = 1 * The account is almost full * * - MegaApi::STORAGE_STATE_RED = 2 * The account is full. Uploads have been stopped * * - MegaApi::STORAGE_STATE_CHANGE = 3 * There is a possible significant change in the storage state. * It's needed to call MegaApi::getAccountDetails to check the storage status. * After calling it, this callback will be called again with the corresponding * state if there is really a change. * * - MegaApi::STORAGE_STATE_PAYWALL = 4 * The account has been full for a long time. Now most of actions are disallowed. * It's needed to call MegaApi::getUserData in order to retrieve the deadline/warnings * timestamps. @see MegaApi::getOverquotaDeadlineTs and MegaApi::getOverquotaWarningsTs. * * - MegaEvent::EVENT_NODES_CURRENT: when all external changes have been received * * - MegaEvent::EVENT_MEDIA_INFO_READY: when codec-mappings have been received * * - MegaEvent::EVENT_STORAGE_SUM_CHANGED: when the storage sum has changed. * * For this event type, MegaEvent::getNumber provides the new storage sum. * * - MegaEvent::EVENT_BUSINESS_STATUS: when the status of a business account has changed. * * For this event type, MegaEvent::getNumber provides the new business status. * * The posible values are: * - BUSINESS_STATUS_EXPIRED = -1 * - BUSINESS_STATUS_INACTIVE = 0 * - BUSINESS_STATUS_ACTIVE = 1 * - BUSINESS_STATUS_GRACE_PERIOD = 2 * * - MegaEvent::EVENT_KEY_MODIFIED: when the key of a user has changed. * * For this event type, MegaEvent::getHandle provides the handle of the user whose key has been modified. * For this event type, MegaEvent::getNumber provides type of key that has been modified. * * The possible values are: * - Public chat key (Cu25519) = 0 * - Public signing key (Ed25519) = 1 * - Public RSA key = 2 * - Signature of chat key = 3 * - Signature of RSA key = 4 * * - MegaEvent::EVENT_GLOBAL_FLAGS_READY: when the global flags are available/updated. * * @param api MegaApi object connected to the account * @param event Details about the event */ fun onEvent(api: MegaApiJava, event: MegaEvent?) /** * This function is called when a Set has been updated (created / updated / removed) * * The SDK retains the ownership of the MegaSetList in the second parameter. The list and all the * MegaSet objects that it contains will be valid until this function returns. If you want to save the * list, use MegaSetList::copy. If you want to save only some of the MegaSet objects, use MegaSet::copy * for them. * * @param api MegaApi object connected to the account * @param sets List that contains the new or updated Sets */ fun onSetsUpdate(api: MegaApiJava, sets: ArrayList?) /** * This function is called when a Set-Element has been updated (created / updated / removed) * * The SDK retains the ownership of the MegaSetElementList in the second parameter. The list and all the * MegaSetElement objects that it contains will be valid until this function returns. If you want to save the * list, use MegaSetElementList::copy. If you want to save only some of the MegaSetElement objects, use * MegaSetElement::copy for them. * * @param api MegaApi object connected to the account * @param elements List that contains the new or updated Set-Elements */ fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList?) /** * @brief This function is called with the state of the synchronization engine has changed * * You can call MegaApi::isScanning and MegaApi::isWaiting to know the global state * of the synchronization engine. * * @param api MegaApi object related to the event */ fun onGlobalSyncStateChanged(api: MegaApiJava) } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaListenerInterface.kt000066400000000000000000000024311516266226600240460ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk /** * Interface to get all information related to a MEGA account. * * * Implementations of this interface can receive all events (request, transfer, global) and two additional * events related to the synchronization engine. The SDK will provide a new interface to get synchronization * events separately in future updates. * Multiple inheritance is not used for compatibility with other programming languages. * * @see MegaGlobalListenerInterface * * @see MegaRequestListenerInterface * * @see MegaTransferListenerInterface */ interface MegaListenerInterface : MegaRequestListenerInterface, MegaGlobalListenerInterface, MegaTransferListenerInterface, MegaSyncListenerInterfacesdk-10.11.0/bindings/java/nz/mega/sdk/MegaLoggerInterface.kt000066400000000000000000000040031516266226600234750ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk /** * Interface to receive SDK logs. *

    * You can implement this class and pass an object of your subclass to MegaApi.setLoggerClass() to receive SDK logs. * You will also have to use MegaApi.setLogLevel() to select the level of the logs that you want to receive. */ interface MegaLoggerInterface { /** * This function will be called for all logs with level <= your selected level of logging. *

    * By default log level is MegaApi.LOG_LEVEL_INFO. * * @param time * Readable string representing the current time. * The SDK retains the ownership of this string, it will not be valid after this function returns. * @param logLevel * Log level of this message. * Valid values are:
    * - LOG_LEVEL_FATAL = 0.
    * - LOG_LEVEL_ERROR = 1.
    * - LOG_LEVEL_WARNING = 2.
    * - LOG_LEVEL_INFO = 3.
    * - LOG_LEVEL_DEBUG = 4.
    * - LOG_LEVEL_MAX = 5. * @param source * Location where this log was generated. * For logs generated inside the SDK, this will contain the source file and the line of code. * The SDK retains the ownership of this string, it won't be valid after this function returns. * @param message * Log message. * The SDK retains the ownership of this string, it won't be valid after this function returns. */ fun log(time: String, logLevel: Int, source: String, message: String) }sdk-10.11.0/bindings/java/nz/mega/sdk/MegaPushNotificationSettingsAndroid.java000066400000000000000000000006411516266226600272540ustar00rootroot00000000000000package nz.mega.sdk; public class MegaPushNotificationSettingsAndroid { /** * Creates a copy of MegaPushNotificationSetting. * * @param receivedPush The MegaPushNotificationsSetting received. * @return A copy of MegaPushNotificationSettings. */ public static MegaPushNotificationSettings copy(MegaPushNotificationSettings receivedPush) { return receivedPush.copy(); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaRecentActionBucketListAndroid.java000066400000000000000000000006201516266226600266120ustar00rootroot00000000000000package nz.mega.sdk; public class MegaRecentActionBucketListAndroid { /** * Creates a copy of MegaRecentActionBucketList. * * @param bucketList The MegaRecentActionBucketList received. * @return A copy of MegaRecentActionBucketList. */ public static MegaRecentActionBucketList copy(MegaRecentActionBucketList bucketList) { return bucketList.copy(); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaRequestListenerInterface.kt000066400000000000000000000075031516266226600254240ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk /** * Interface to receive information about requests. * * * All requests are able to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all requests using MegaApi.addRequestListener(). * MegaListener objects can also receive information about requests. * This interface uses MegaRequest objects to provide information of requests. Take into account that not all fields * of MegaRequest objects are valid for all requests. See the documentation about each request to know which fields * contain useful information for each one. */ interface MegaRequestListenerInterface { /** * This function is called when a request is about to start being processed. *

    * The SDK retains the ownership of the request parameter. * Don't use it after this function returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request.

    */ fun onRequestStart(api: MegaApiJava, request: MegaRequest) /** * This function is called to inform about the progress of a request. * * * Currently, this callback is only used for fetchNodes requests (MegaRequest.TYPE_FETCH_NODES). * The SDK retains the ownership of the request parameter. Do not use it after this function returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. */ fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) /** * This function is called when a request has finished. * * * There will be no more callbacks about this request. The last parameter provides the result of the request. * If the request finished without problems, the error code will be API_OK. The SDK retains ownership of the * request and error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @param e * Error Information. */ fun onRequestFinish(api: MegaApiJava, request: MegaRequest, e: MegaError) /** * This function is called when there is a temporary error processing a request. * * * The request continues after this callback, so expect more MegaRequestListener.onRequestTemporaryError * or a MegaRequestListener.onRequestFinish callback. The SDK retains the ownership of the request and * error parameters. Do not use them after this function returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * API that started the request. * @param request * Information about the request. * @param e * Error Information. */ fun onRequestTemporaryError(api: MegaApiJava, request: MegaRequest, e: MegaError) }sdk-10.11.0/bindings/java/nz/mega/sdk/MegaStringMapAndroid.java000066400000000000000000000005431516266226600241520ustar00rootroot00000000000000package nz.mega.sdk; public class MegaStringMapAndroid { /** * Creates a copy of MegaStringMap required for its usage in the app. * * @param map The MegaStringMap received. * @return A copy of MegaStringMap. */ public static MegaStringMap copy(MegaStringMap megaStringMap) { return megaStringMap.copy(); } } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaSyncListenerInterface.kt000066400000000000000000000032301516266226600247010ustar00rootroot00000000000000package nz.mega.sdk /** * Interface to receive information about the syncs */ interface MegaSyncListenerInterface { /** * @brief This callback will be called when a sync is removed. * * This entail that the sync is completely removed from cache * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object representing a sync */ fun onSyncDeleted(api: MegaApiJava, sync: MegaSync) /** * @brief This function is called when there is an update on * the number of nodes or transfers in the sync * * The SDK retains the ownership of the MegaSyncStats. * Don't use it after this functions returns. But you can copy it * * @param api MegaApi object that is synchronizing files * @param syncStats Identifies the sync and provides the counts */ fun onSyncStatsUpdated(api: MegaApiJava, syncStats: MegaSyncStats) /** * @brief This function is called when the state of the synchronization changes * * The SDK calls this function when the state of the synchronization changes. you can use * MegaSync::getRunState to get the new state of the synchronization * and MegaSync::getError to get the error if any. * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object that has changed its state */ fun onSyncStateChanged(api: MegaApiJava, sync: MegaSync) }sdk-10.11.0/bindings/java/nz/mega/sdk/MegaTransferListenerInterface.kt000066400000000000000000000142131516266226600255540ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk /** * Interface to receive information about transfers. * * * All transfers are able to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all transfers using MegaApi.addTransferListener(). * MegaListener objects can also receive information about transfers. */ interface MegaTransferListenerInterface { /** * This function is called when a transfer is about to start being processed. * * * The SDK retains the ownership of the transfer parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. */ fun onTransferStart(api: MegaApiJava, transfer: MegaTransfer) /** * This function is called when a transfer has finished. * * * The SDK retains the ownership of the transfer and error parameters. Do not use them after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * There will not be further callbacks relating to this transfer. The last parameter provides the result of the * transfer. If the transfer finished without problems, the error code will be API_OK. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param e * Error information. */ fun onTransferFinish(api: MegaApiJava, transfer: MegaTransfer, e: MegaError) /** * This function is called to get details about the progress of a transfer. * * * The SDK retains the ownership of the transfer parameter. Do not use it after this functions returns. * The api object is the one created by the application, it will be valid until the application deletes it. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. */ fun onTransferUpdate(api: MegaApiJava, transfer: MegaTransfer) /** * This function is called when there is a temporary error processing a transfer. * * * The transfer continues after this callback, so expect more MegaTransferListener.onTransferTemporaryError * or a MegaTransferListener.onTransferFinish callback. The SDK retains the ownership of the transfer and * error parameters. Do not use them after this function returns. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param e * Error information. */ fun onTransferTemporaryError(api: MegaApiJava, transfer: MegaTransfer, e: MegaError) /** * This function is called to provide the last read bytes of streaming downloads. * * * This function will not be called for non streaming downloads. You can get the same buffer provided by this * function in MegaTransferListener.onTransferUpdate, using MegaTransfer.getLastBytes() and * MegaTransfer.getDeltaSize(). The SDK retains the ownership of the transfer and buffer parameters. * Do not use them after this functions returns. This callback is mainly provided for compatibility with other * programming languages. * * @param api * MegaApi object that started the transfer. * @param transfer * Information about the transfer. * @param buffer * Buffer with the last read bytes. * @return * Size of the buffer. */ fun onTransferData(api: MegaApiJava, transfer: MegaTransfer, buffer: ByteArray): Boolean /** * @brief This function is called to inform about the progress of a folder transfer * * The api object is the one created by the application, it will be valid until * the application deletes it. * * This callback is only made for folder transfers, and only to the listener for that * transfer, not for any globally registered listeners. The callback is only made * during the scanning phase. * * This function can be used to give feedback to the user as to how scanning is progressing, * since scanning may take a while and the application may be showing a modal dialog during * this time. * * Note that this function could be called from a variety of threads during the * overall operation, so proper thread safety should be observed. * * @param api MEGASdk object that started the transfer * @param transfer Information about the transfer * @param stage MEGATransferStageScan or a later value in that enum * @param folderCount The count of folders scanned so far * @param createdFolderCount The count of folders created so far (only relevant in MEGATransferStageCreateTree) * @param fileCount The count of files scanned (and fingerprinted) so far. 0 if not in scanning stage * @param currentFolder The path of the folder currently being scanned (nil except in the scan stage) * @param currentFileLeafName The leaft name of the file currently being fingerprinted (can be nil for the first call in a new folder, and when not scanning anymore) */ fun onFolderTransferUpdate( api: MegaApiJava, transfer: MegaTransfer, stage: Int, folderCount: Long, createdFolderCount: Long, fileCount: Long, currentFolder: String?, currentFileLeafName: String?, ) }sdk-10.11.0/bindings/java/nz/mega/sdk/MegaTreeProcessorInterface.java000066400000000000000000000016051516266226600253650ustar00rootroot00000000000000/* * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful,\ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ package nz.mega.sdk; /** * Interface to process node trees. *

    * An implementation of this class can be used to process a node tree passing a pointer * to MegaApiJava.processMegaTree(). */ public interface MegaTreeProcessorInterface { boolean processMegaNode(MegaApiJava megaApi, MegaNode node); } sdk-10.11.0/bindings/java/nz/mega/sdk/MegaUtilsAndroid.java000066400000000000000000000103131516266226600233420ustar00rootroot00000000000000package nz.mega.sdk; import java.io.File; import java.util.ArrayList; import android.graphics.Bitmap; import android.graphics.Rect; public class MegaUtilsAndroid { private static int THUMBNAIL_SIZE = 200; private static int AVATAR_SIZE = 250; private static int PREVIEW_SIZE = 1000; public static boolean createAvatar(File image, File avatar) { if (!image.exists()) return false; if (avatar.exists()) avatar.delete(); String path = image.getAbsolutePath(); int orientation = AndroidGfxProcessor.getExifOrientation(path); Rect rect = AndroidGfxProcessor.getImageDimensions(path, orientation); if (rect.isEmpty()) return false; int w = rect.right; int h = rect.bottom; if ((w == 0) || (h == 0)) return false; if (w < h) { h = h * AVATAR_SIZE / w; w = AVATAR_SIZE; } else { w = w * AVATAR_SIZE / h; h = AVATAR_SIZE; } if ((w == 0) || (h == 0)) return false; int px = (w - AVATAR_SIZE) / 2; int py = (h - AVATAR_SIZE) / 3; Bitmap bitmap = AndroidGfxProcessor.getBitmap(path, rect, orientation, w, h); bitmap = AndroidGfxProcessor.extractRect(bitmap, px, py, AVATAR_SIZE, AVATAR_SIZE); if (bitmap == null) return false; return AndroidGfxProcessor.saveBitmap(bitmap, avatar); } public static boolean createThumbnail(File image, File thumbnail) { if (!image.exists()) return false; if (thumbnail.exists()) thumbnail.delete(); String path = image.getAbsolutePath(); int orientation = AndroidGfxProcessor.getExifOrientation(path); Rect rect = AndroidGfxProcessor.getImageDimensions(path, orientation); if (rect.isEmpty()) return false; int w = rect.right; int h = rect.bottom; if ((w == 0) || (h == 0)) return false; if (w < h) { h = h * THUMBNAIL_SIZE / w; w = THUMBNAIL_SIZE; } else { w = w * THUMBNAIL_SIZE / h; h = THUMBNAIL_SIZE; } if ((w == 0) || (h == 0)) return false; int px = (w - THUMBNAIL_SIZE) / 2; int py = (h - THUMBNAIL_SIZE) / 3; Bitmap bitmap = AndroidGfxProcessor.getBitmap(path, rect, orientation, w, h); bitmap = AndroidGfxProcessor.extractRect(bitmap, px, py, THUMBNAIL_SIZE, THUMBNAIL_SIZE); if (bitmap == null) return false; return AndroidGfxProcessor.saveBitmap(bitmap, thumbnail); } public static boolean createPreview(File image, File preview) { if (!image.exists()) return false; if (preview.exists()) preview.delete(); String path = image.getAbsolutePath(); int orientation = AndroidGfxProcessor.getExifOrientation(path); Rect rect = AndroidGfxProcessor.getImageDimensions(path, orientation); int w = rect.right; int h = rect.bottom; if ((w == 0) || (h == 0)) return false; if (w >= PREVIEW_SIZE || h >= PREVIEW_SIZE) { if (h > w) { w = w * PREVIEW_SIZE / h; h = PREVIEW_SIZE; } else { h = h * PREVIEW_SIZE / w; w = PREVIEW_SIZE; } } if ((w == 0) || (h == 0)) return false; Bitmap bitmap = AndroidGfxProcessor.getBitmap(path, rect, orientation, w, h); return AndroidGfxProcessor.saveBitmap(bitmap, preview); } /** * Convert MegaBannerList to Java ArrayList of MegaBanner objects * @param megaBannerList the MegaBannerList object * @return the Java ArrayList of MegaBanner objects */ public static ArrayList bannersToArray(MegaBannerList megaBannerList) { if (megaBannerList == null) { return null; } ArrayList result = new ArrayList<>(megaBannerList.size()); for (int i = 0; i< megaBannerList.size(); i++) { result.add(megaBannerList.get(i).copy()); } return result; } } sdk-10.11.0/bindings/java/nz/mega/sdk/StalledIssuesReceiver.kt000066400000000000000000000023301516266226600241150ustar00rootroot00000000000000package nz.mega.sdk /** * A class to asynchronously receive Stalled Issues list * @property onStallListLoaded - lambda that returns Stalled Issues */ class StalledIssuesReceiver( private val onStallListLoaded: (List) -> Unit, ) : MegaRequestListenerInterface { override fun onRequestStart(api: MegaApiJava, request: MegaRequest) { } override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) { } override fun onRequestFinish(api: MegaApiJava, request: MegaRequest, e: MegaError) { if (request.type == MegaRequest.TYPE_GET_SYNC_STALL_LIST) { val stallList = request.megaSyncStallList if (stallList == null) { onStallListLoaded(emptyList()) return } // Copy objects with memory ownership to prevent SIGSEGV null pointer dereference stallList.copy().let { list -> val copiedList = (0 until list.size()).mapNotNull { index -> list.get(index)?.copy() } onStallListLoaded(copiedList) } } } override fun onRequestTemporaryError(api: MegaApiJava, request: MegaRequest, e: MegaError) { } }sdk-10.11.0/bindings/megaapi.i000066400000000000000000000503221516266226600160230ustar00rootroot00000000000000%module(directors="1") mega %{ #include "megaapi.h" #ifdef __ANDROID__ #include #endif // __ANDROID__ #ifdef SWIGJAVA #include extern JavaVM* MEGAjvm; jstring strEncodeUTF8 = NULL; jclass clsString = NULL; jmethodID ctorString = NULL; jmethodID getBytes = NULL; extern jclass fileWrapper; extern jclass integerClass; extern jclass arrayListClass; namespace megajni { // Clear any pending Java exception; return true if there was one. static inline bool megajni_clear_pending_exception(JNIEnv* env, const char* where = nullptr) { if (!env) return false; if (env->ExceptionCheck()) { env->ExceptionDescribe(); // useful during bring-up; remove if too chatty env->ExceptionClear(); // You can also log `where` to your native logger if desired. return true; } return false; } // After making *any* JNI call (NewObject, CallObjectMethod, FindClass, etc.) #define MEGAJNI_CHECK(env, where) do { megajni::megajni_clear_pending_exception((env), (where)); } while(0) // Optional: safely create a global ref (returns nullptr on failure, with exception cleared) static inline jclass megajni_new_global_ref_class(JNIEnv* env, jclass local, const char* where) { if (!env || !local) return nullptr; jclass g = (jclass)env->NewGlobalRef(local); MEGAJNI_CHECK(env, where); return g; } jint on_load(JavaVM *jvm, void *reserved) { MEGAjvm = jvm; JNIEnv* jenv = NULL; jvm->GetEnv((void**)&jenv, JNI_VERSION_1_6); jclass clsStringLocal = jenv->FindClass("java/lang/String"); clsString = (jclass)jenv->NewGlobalRef(clsStringLocal); jenv->DeleteLocalRef(clsStringLocal); ctorString = jenv->GetMethodID(clsString, "", "([BLjava/lang/String;)V"); getBytes = jenv->GetMethodID(clsString, "getBytes", "(Ljava/lang/String;)[B"); jstring strEncodeUTF8Local = jenv->NewStringUTF("UTF-8"); strEncodeUTF8 = (jstring)jenv->NewGlobalRef(strEncodeUTF8Local); jenv->DeleteLocalRef(strEncodeUTF8Local); jclass localfileWrapper = jenv->FindClass("mega/privacy/android/data/filewrapper/FileWrapper"); if (!localfileWrapper) { jenv->ExceptionDescribe(); jenv->ExceptionClear(); } fileWrapper = megajni_new_global_ref_class(jenv, localfileWrapper, "global ref FileWrapper"); if (localfileWrapper) jenv->DeleteLocalRef(localfileWrapper); jclass localIntegerClass = jenv->FindClass("java/lang/Integer"); if (!localIntegerClass) { jenv->ExceptionDescribe(); jenv->ExceptionClear(); } integerClass = megajni_new_global_ref_class(jenv, localIntegerClass, "global ref Integer"); if (localIntegerClass) jenv->DeleteLocalRef(localIntegerClass); jclass localArrayListClass = jenv->FindClass("java/util/ArrayList"); if (!localArrayListClass) { jenv->ExceptionDescribe(); jenv->ExceptionClear(); } arrayListClass = megajni_new_global_ref_class(jenv, localArrayListClass, "global ref ArrayList"); if (localArrayListClass) jenv->DeleteLocalRef(localArrayListClass); return JNI_VERSION_1_6; } } // namespace megajni #ifdef SDKLIB_ONLOAD extern "C" jint JNIEXPORT JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { return megajni::on_load(jvm, reserved); } #endif // SDKLIB_ONLOAD #endif // SWIGJAVA %} #ifdef SWIGJAVA //Automatic load of the native library %pragma(java) jniclasscode=%{ static { try { System.loadLibrary("mega"); } catch (UnsatisfiedLinkError e1) { try { System.load(System.getProperty("user.dir") + "/libmega.so"); } catch (UnsatisfiedLinkError e2) { try { System.load(System.getProperty("user.dir") + "/libs/libmegajava.so"); } catch (UnsatisfiedLinkError e3) { try { System.load(System.getProperty("user.dir") + "/libs/mega.dll"); } catch (UnsatisfiedLinkError e4) { try { System.load(System.getProperty("user.dir") + "/libmega.dylib"); } catch (UnsatisfiedLinkError e5) { try { System.load(System.getProperty("user.dir") + "/libs/libmegajava.dylib"); } catch (UnsatisfiedLinkError e6) { System.err.println("Native code library failed to load. \n" + e1 + "\n" + e2 + "\n" + e3 + "\n" + e4 + "\n" + e5 + "\n" + e6); System.exit(1); } } } } } } } %} %exception { // If a Java exception is already pending when entering native, clear it so JNI is usable. megajni::megajni_clear_pending_exception(jenv, "pre-native-entry"); try { $action } catch (const std::exception& e) { jclass ex = jenv->FindClass("java/lang/RuntimeException"); if (ex) jenv->ThrowNew(ex, e.what()); // leave with a Java exception pending; JNI will return safely to JVM } catch (...) { jclass ex = jenv->FindClass("java/lang/RuntimeException"); if (ex) jenv->ThrowNew(ex, "Native code threw an unknown exception"); } } //Use compilation-time constants in Java %javaconst(1); //Reduce the visibility of internal classes %typemap(javaclassmodifiers) mega::MegaApi "class"; %typemap(javaclassmodifiers) mega::MegaListener "class"; %typemap(javaclassmodifiers) mega::MegaRequestListener "class"; %typemap(javaclassmodifiers) mega::MegaTransferListener "class"; %typemap(javaclassmodifiers) mega::MegaGlobalListener "class"; %typemap(javaclassmodifiers) mega::MegaTreeProcessor "class"; %typemap(javaclassmodifiers) mega::MegaLogger "class"; %typemap(javaclassmodifiers) mega::NodeList "class"; %typemap(javaclassmodifiers) mega::TransferList "class"; %typemap(javaclassmodifiers) mega::ShareList "class"; %typemap(javaclassmodifiers) mega::UserList "class"; %typemap(javaclassmodifiers) mega::UserAlertList "class"; %typemap(out) char* %{ if ($1) { int len = (int)strlen($1); jbyteArray $1_array = jenv->NewByteArray(len); MEGAJNI_CHECK(jenv, "out char* NewByteArray"); if ($1_array) { jenv->SetByteArrayRegion($1_array, 0, len, (const jbyte*)$1); MEGAJNI_CHECK(jenv, "out char* SetByteArrayRegion"); $result = (jstring) jenv->NewObject(clsString, ctorString, $1_array, strEncodeUTF8); MEGAJNI_CHECK(jenv, "out char* NewObject(String)"); jenv->DeleteLocalRef($1_array); MEGAJNI_CHECK(jenv, "out char* DeleteLocalRef"); } else { $result = nullptr; // OOM or pending exception just cleared } } else { $result = nullptr; } %} %typemap(in) char* %{ jbyteArray $1_array = 0; $1 = 0; if ($input) { $1_array = (jbyteArray) jenv->CallObjectMethod($input, getBytes, strEncodeUTF8); MEGAJNI_CHECK(jenv, "in char* String.getBytes"); if ($1_array) { jsize $1_size = jenv->GetArrayLength($1_array); MEGAJNI_CHECK(jenv, "in char* GetArrayLength"); $1 = new char[$1_size + 1]; if ($1 && $1_size) { jenv->GetByteArrayRegion($1_array, 0, $1_size, (jbyte*)$1); MEGAJNI_CHECK(jenv, "in char* GetByteArrayRegion"); } if ($1) $1[$1_size] = '\0'; } // else: exception cleared; $1 remains null } %} %typemap(freearg) char* %{ if ($1) { delete [] $1; } if ($1_array) { jenv->DeleteLocalRef($1_array); MEGAJNI_CHECK(jenv, "freearg char* DeleteLocalRef"); } %} %typemap(directorin,descriptor="Ljava/lang/String;") char * %{ $input = 0; if ($1) { int len = strlen($1); jbyteArray $1_array = jenv->NewByteArray(len); jenv->SetByteArrayRegion($1_array, 0, len, (const jbyte*)$1); $input = (jstring) jenv->NewObject(clsString, ctorString, $1_array, strEncodeUTF8); jenv->DeleteLocalRef($1_array); } Swig::LocalRefGuard $1_refguard(jenv, $input); %} //Make the "delete" method protected %typemap(javadestruct, methodname="delete", methodmodifiers="protected synchronized") SWIGTYPE { if (swigCPtr != 0) { if (swigCMemOwn) { swigCMemOwn = false; $jnicall; } swigCPtr = 0; } } %javamethodmodifiers copy "" #endif %feature("director:before") { jenv->PushLocalFrame(32); // adjust number based on expected refs per callback } %feature("director:after") { jenv->PopLocalFrame(nullptr); MEGAJNI_CHECK(jenv, "director PopLocalFrame"); } #ifdef __ANDROID__ %feature("director:except") { if (jenv->ExceptionCheck()) { jthrowable pending = jenv->ExceptionOccurred(); jenv->ExceptionClear(); // Use the app's class loader to reliably find the Kotlin class on any thread jclass reporterClass = nullptr; jclass activityThread = jenv->FindClass("android/app/ActivityThread"); if (activityThread) { jmethodID currentApp = jenv->GetStaticMethodID( activityThread, "currentApplication", "()Landroid/app/Application;" ); jobject app = jenv->CallStaticObjectMethod(activityThread, currentApp); if (app) { jclass appClass = jenv->GetObjectClass(app); jmethodID getClassLoader = jenv->GetMethodID( appClass, "getClassLoader", "()Ljava/lang/ClassLoader;" ); jobject loader = jenv->CallObjectMethod(app, getClassLoader); if (loader) { jclass loaderClass = jenv->FindClass("java/lang/ClassLoader"); jmethodID loadClass = jenv->GetMethodID( loaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;" ); jstring className = jenv->NewStringUTF("mega.privacy.android.app.jni.JniExceptionReporter"); reporterClass = (jclass) jenv->CallObjectMethod(loader, loadClass, className); jenv->DeleteLocalRef(className); jenv->DeleteLocalRef(loaderClass); jenv->DeleteLocalRef(loader); } jenv->DeleteLocalRef(appClass); jenv->DeleteLocalRef(app); } jenv->DeleteLocalRef(activityThread); } __android_log_print(ANDROID_LOG_ERROR, "MEGAJNI", "JniExceptionReporter class %sfound", reporterClass ? "" : "NOT "); if (reporterClass) { jfieldID handlerField = jenv->GetStaticFieldID( reporterClass, "handler", "Lmega/privacy/android/app/jni/JniExceptionHandler;" ); jobject handler = jenv->GetStaticObjectField(reporterClass, handlerField); if (handler) { jclass handlerClass = jenv->GetObjectClass(handler); jmethodID onException = jenv->GetMethodID( handlerClass, "onJniException", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" ); if (onException) { jclass throwableClass = jenv->GetObjectClass(pending); jmethodID getMessage = jenv->GetMethodID(throwableClass, "getMessage", "()Ljava/lang/String;"); jmethodID toString = jenv->GetMethodID(throwableClass, "toString", "()Ljava/lang/String;"); jstring jmsg = (jstring) jenv->CallObjectMethod(pending, getMessage); jstring jstack = (jstring) jenv->CallObjectMethod(pending, toString); jstring jlocation = jenv->NewStringUTF("$action"); jenv->CallVoidMethod(handler, onException, jlocation, jmsg, jstack); jenv->DeleteLocalRef(jlocation); jenv->DeleteLocalRef(jmsg); jenv->DeleteLocalRef(jstack); jenv->DeleteLocalRef(throwableClass); } jenv->DeleteLocalRef(handlerClass); jenv->DeleteLocalRef(handler); } jenv->DeleteLocalRef(reporterClass); } jenv->DeleteLocalRef(pending); } } #endif // __ANDROID__ //Generate inheritable wrappers for listener objects %feature("director") mega::MegaRequestListener; %feature("director") mega::MegaTransferListener; %feature("director") mega::MegaLogger; #ifdef SWIGJAVA #if SWIG_VERSION < 0x030000 %typemap(directorargout) (const char *time, int loglevel, const char *source, const char *message) %{ jenv->DeleteLocalRef(jtime); jenv->DeleteLocalRef(jsource); jenv->DeleteLocalRef(jmessage); %} #endif %apply (char *STRING, size_t LENGTH) {(char *buffer, size_t size)}; #if SWIG_VERSION < 0x030012 %typemap(directorargout) (char *buffer, size_t size) %{ jenv->DeleteLocalRef($input); %} #else %typemap(directorargout) (char *buffer, size_t size) %{ // not copying the buffer back to improve performance %} #endif #endif %feature("director") mega::MegaGlobalListener; %feature("director") mega::MegaListener; %feature("director") mega::MegaTreeProcessor; %feature("director") mega::MegaGfxProcessor; #ifdef SWIGJAVA #if SWIG_VERSION < 0x030000 %typemap(directorargout) (const char* path) %{ jenv->DeleteLocalRef(jpath); %} #endif %apply (char *STRING, size_t LENGTH) {(char *bitmapData, size_t size)}; %typemap(directorin, descriptor="[B") (char *bitmapData, size_t size) %{ jbyteArray jb = (jenv)->NewByteArray($2); MEGAJNI_CHECK(jenv, "bitmap NewByteArray"); $input = jb; %} %typemap(directorargout) (char *bitmapData, size_t size) %{ if ($input && $1 && $2 > 0) { jenv->GetByteArrayRegion($input, 0, $2, (jbyte*)$1); MEGAJNI_CHECK(jenv, "bitmap GetByteArrayRegion"); } if ($input) { jenv->DeleteLocalRef($input); MEGAJNI_CHECK(jenv, "bitmap DeleteLocalRef"); } %} #endif %ignore mega::MegaApi::MEGA_DEBRIS_FOLDER; %ignore mega::MegaNode::getNodeKey; %ignore mega::MegaNode::getAttrString; %ignore mega::MegaNode::getPrivateAuth; %ignore mega::MegaNode::getPublicAuth; %ignore mega::MegaApi::createForeignFileNode; %ignore mega::MegaApi::createForeignFolderNode; %ignore mega::MegaListener::onSyncFileStateChanged; %ignore mega::MegaTransfer::getListener; %ignore mega::MegaRequest::getListener; %ignore mega::MegaHashSignature; %ignore mega::SynchronousRequestListener; %ignore mega::SynchronousTransferListener; %ignore mega::MegaUser::CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP; %extend mega::MegaUser { %proxycode %{ /* * Manually injected constants resolved the uint64_t overflow issue. */ public final static long CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP = 0x100000000L; %} } %newobject mega::MegaError::copy; %newobject mega::MegaRequest::copy; %newobject mega::MegaTransfer::copy; %newobject mega::MegaTransferList::copy; %newobject mega::MegaNode::copy; %newobject mega::MegaNodeList::copy; %newobject mega::MegaChildrenList::copy; %newobject mega::MegaShare::copy; %newobject mega::MegaShareList::copy; %newobject mega::MegaUser::copy; %newobject mega::MegaUserList::copy; %newobject mega::MegaSetElement::copy; %newobject mega::MegaSet::copy; %newobject mega::MegaEvent::copy; %newobject mega::MegaSync::copy; %newobject mega::MegaSyncStats::copy; %newobject mega::MegaRecentActionBucket::copy; %newobject mega::MegaRecentActionBucketList::copy; %newobject mega::MegaSyncStall::copy; %newobject mega::MegaSyncStallList::copy; %newobject mega::MegaStringMap::copy; %newobject mega::MegaContactRequest::copy; %newobject mega::MegaContactRequestList::copy; %newobject mega::MegaStringList::copy; %newobject mega::MegaAchievementsDetails::copy; %newobject mega::MegaTimeZoneDetails::copy; %newobject mega::MegaUserAlert::copy; %newobject mega::MegaUserAlertList::copy; %newobject mega::MegaAchievementsDetails::getAwardEmails; %newobject mega::MegaTransfer::getPublicMegaNode; %newobject mega::MegaApi::getBase64PwKey; %newobject mega::MegaApi::getStringHash; %newobject mega::MegaApi::handleToBase64; %newobject mega::MegaApi::userHandleToBase64; %newobject mega::MegaApi::dumpSession; %newobject mega::MegaApi::dumpXMPPSession; %newobject mega::MegaApi::getMyEmail; %newobject mega::MegaApi::getMyUserHandle; %newobject mega::MegaApi::getMyUser; %newobject mega::MegaApi::getMyXMPPJid; %newobject mega::MegaApi::getMyFingerprint; %newobject mega::MegaApi::exportMasterKey; %newobject mega::MegaApi::getTransfers; %newobject mega::MegaApi::getTransferByTag; %newobject mega::MegaApi::getTransferData; %newobject mega::MegaApi::getChildTransfers; %newobject mega::MegaApi::getChildren; %newobject mega::MegaApi::getChildNode; %newobject mega::MegaApi::getParentNode; %newobject mega::MegaApi::getNodePath; %newobject mega::MegaApi::getNodePathByNodeHandle; %newobject mega::MegaApi::getNodeByPath; %newobject mega::MegaApi::getNodeByHandle; %newobject mega::MegaApi::getContactRequestByHandle; %newobject mega::MegaApi::getContacts; %newobject mega::MegaApi::getContact; %newobject mega::MegaApi::getUserAlerts; %newobject mega::MegaApi::getInShares; %newobject mega::MegaApi::getInSharesList; %newobject mega::MegaApi::getOutShares; %newobject mega::MegaApi::getPendingOutShares; %newobject mega::MegaApi::getPublicLinks; %newobject mega::MegaApi::getIncomingContactRequests; %newobject mega::MegaApi::getOutgoingContactRequests; %newobject mega::MegaApi::getFingerprint; %newobject mega::MegaApi::getNodeByFingerprint; %newobject mega::MegaApi::getNodesByFingerprint; %newobject mega::MegaApi::getNodesByOriginalFingerprint; %newobject mega::MegaApi::getExportableNodeByFingerprint; %newobject mega::MegaApi::getCRC; %newobject mega::MegaApi::getNodeByCRC; %newobject mega::MegaApi::getRootNode; %newobject mega::MegaApi::getInboxNode; %newobject mega::MegaApi::getRubbishNode; %newobject mega::MegaApi::escapeFsIncompatible; %newobject mega::MegaApi::unescapeFsIncompatible; %newobject mega::MegaApi::base64ToBase32; %newobject mega::MegaApi::base32ToBase64; %newobject mega::MegaApi::search; %newobject mega::MegaApi::getCRCFromFingerprint; %newobject mega::MegaApi::getSessionTransferURL; %newobject mega::MegaApi::getAccountAuth; %newobject mega::MegaApi::authorizeNode; %newobject mega::MegaApi::getSyncs; %newobject mega::MegaApi::getEnabledNotifications; %newobject mega::MegaApi::getMimeType; %newobject mega::MegaApi::isNodeSyncableWithError; %newobject mega::MegaApi::getVersions; %newobject mega::MegaApi::getAutoProxySettings; %newobject mega::MegaApi::getOverquotaWarningsTs; %newobject mega::MegaApi::getMyCredentials; %newobject mega::MegaApi::getUserAvatarColor; %newobject mega::MegaApi::getUserAvatarSecondaryColor; %newobject mega::MegaApi::getDeviceId; %newobject mega::MegaApi::getFirstTransfer; %newobject mega::MegaApi::getTransferByUniqueId; %newobject mega::MegaApi::getAllNodeTags; %newobject mega::MegaRequest::getPublicMegaNode; %newobject mega::MegaRequest::getMegaTimeZoneDetails; %newobject mega::MegaRequest::getMegaAccountDetails; %newobject mega::MegaRequest::getPricing; %newobject mega::MegaRequest::getMegaAchievementsDetails; %newobject mega::MegaRequest::getCurrency; %newobject mega::MegaAccountDetails::getSubscriptionMethod; %newobject mega::MegaAccountDetails::getSubscriptionCycle; %newobject mega::MegaAccountDetails::copy; %newobject mega::MegaAccountDetails::getBalance; %newobject mega::MegaAccountDetails::getSession; %newobject mega::MegaAccountDetails::getPurchase; %newobject mega::MegaAccountDetails::getTransaction; %newobject mega::MegaAccountDetails::getPlan; %newobject mega::MegaAccountDetails::getSubscription; %newobject mega::MegaNode::getBase64Handle; %newobject mega::MegaNode::getFileAttrString; %newobject mega::MegaNode::PasswordNodeData::createInstance; %newobject mega::MegaNode::unserialize; %newobject mega::MegaNode::getTags; %newobject mega::MegaNode::getCustomAttrNames; %newobject mega::MegaNode::copy; %newobject mega::MegaNode::getPublicNode; %newobject mega::MegaNode::getPublicLink; %newobject mega::MegaNode::getCreditCardData; %newobject mega::MegaNode::getPasswordData; %newobject mega::MegaNode::serialize; typedef long long time_t; typedef long long uint64_t; typedef long long int64_t; typedef long long uint32_t; typedef long long int32_t; %include "std_string.i" %include "megaapi.h" sdk-10.11.0/bindings/qt/000077500000000000000000000000001516266226600146705ustar00rootroot00000000000000sdk-10.11.0/bindings/qt/CMakeLists.txt000066400000000000000000000027171516266226600174370ustar00rootroot00000000000000add_library(SDKQtBindings) add_library(MEGA::SDKQtBindings ALIAS SDKQtBindings) target_sources(SDKQtBindings PRIVATE QTMegaEvent.h QTMegaGlobalListener.h QTMegaListener.h QTMegaRequestListener.h QTMegaTransferListener.h QTMegaApiManager.h QTMegaEvent.cpp QTMegaGlobalListener.cpp QTMegaListener.cpp QTMegaRequestListener.cpp QTMegaTransferListener.cpp QTMegaApiManager.cpp ) # Activate the meta-object code generator for Qt. set_target_properties(SDKQtBindings PROPERTIES AUTOMOC ON) ## Required for the Qt generated files, to be able to include other headers target_include_directories(SDKQtBindings PUBLIC $ # For the top level projects. $ # For the external projects. ) find_package(Qt5 REQUIRED COMPONENTS Core) message(STATUS "Building the SDK Qt Bindings with Qt v${Qt5_VERSION} from ${Qt5_DIR}") target_link_libraries(SDKQtBindings PRIVATE Qt5::Core MEGA::SDKlib ) ## Adjust compilation flags for warnings and errors ## target_platform_compile_options( TARGET SDKQtBindings UNIX $<$:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_CHATLIB_WERROR) target_platform_compile_options( TARGET SDKQtBindings UNIX $<$: -Werror -Wno-error=deprecated-declarations> # Kept as a warning, do not promote to error. ) endif() sdk-10.11.0/bindings/qt/QTMegaApiManager.cpp000066400000000000000000000037131516266226600204430ustar00rootroot00000000000000#include "QTMegaApiManager.h" #include "megaapi.h" using namespace mega; QList QTMegaApiManager::mMegaApis = QList(); QReadWriteLock QTMegaApiManager::mLock = QReadWriteLock(); void QTMegaApiManager::createMegaApi(MegaApi*& api, const char* appKey, const char* basePath, const char* userAgent, bool enableKeyPinning) { QWriteLocker lock(&mLock); api = new MegaApi(appKey, basePath, userAgent); api->setPublicKeyPinning(enableKeyPinning); mMegaApis.append(std::addressof(api)); } void QTMegaApiManager::createMegaApi(MegaApi*& api, const char* appKey, MegaGfxProvider* gfxProvider, const char* basePath, const char* userAgent, bool enableKeyPinning) { QWriteLocker lock(&mLock); api = new MegaApi(appKey, gfxProvider, basePath, userAgent); api->setPublicKeyPinning(enableKeyPinning); mMegaApis.append(std::addressof(api)); } bool QTMegaApiManager::isMegaApiValid(MegaApi* api) { if (api) { QReadLocker lock(&mLock); auto found = std::find_if(mMegaApis.cbegin(), mMegaApis.cend(), [api](MegaApi** pToApi) { return (*pToApi) == api; }); return found != mMegaApis.cend(); } return false; } void QTMegaApiManager::removeMegaApis() { QWriteLocker lock(&mLock); for (int index = 0; index < mMegaApis.size(); ++index) { auto& api(*mMegaApis[index]); if (api) { delete api; api = nullptr; } } mMegaApis.clear(); } sdk-10.11.0/bindings/qt/QTMegaApiManager.h000066400000000000000000000017141516266226600201070ustar00rootroot00000000000000#ifndef QTMEGAAPICREATOR_H #define QTMEGAAPICREATOR_H #include #include #include namespace mega { class QTMegaApiManager { public: static void createMegaApi(MegaApi*& api, const char* appKey, const char* basePath, const char* userAgent, bool enableKeyPinning); static void createMegaApi(MegaApi*& api, const char* appKey, MegaGfxProvider* gfxProvider, const char* basePath, const char* userAgent, bool enableKeyPinning); static bool isMegaApiValid(MegaApi* api); static void removeMegaApis(); private: QTMegaApiManager() = default; static QList mMegaApis; static QReadWriteLock mLock; }; } #endif // QTMEGAAPICREATOR_H sdk-10.11.0/bindings/qt/QTMegaEvent.cpp000066400000000000000000000051501516266226600175150ustar00rootroot00000000000000#include "QTMegaEvent.h" using namespace mega; using namespace std; QTMegaEvent::QTMegaEvent(MegaApi* api, Type type): QEvent(type) { megaApi = api; request = NULL; transfer = NULL; error = NULL; nodes = NULL; users = NULL; userAlerts = NULL; event = NULL; #ifdef ENABLE_SYNC sync = NULL; syncStats = NULL; localPath = NULL; newState = 0; #endif } QTMegaEvent::~QTMegaEvent() { delete request; delete transfer; delete error; delete nodes; delete users; delete userAlerts; delete event; #ifdef ENABLE_SYNC delete sync; delete syncStats; delete localPath; #endif } MegaApi *QTMegaEvent::getMegaApi() const { return megaApi; } MegaRequest *QTMegaEvent::getRequest() { return request; } MegaTransfer *QTMegaEvent::getTransfer() { return transfer; } MegaError *QTMegaEvent::getError() { return error; } MegaNodeList *QTMegaEvent::getNodes() { return nodes; } MegaUserList *QTMegaEvent::getUsers() { return users; } MegaUserAlertList *QTMegaEvent::getUserAlerts() { return userAlerts; } void QTMegaEvent::setRequest(MegaRequest *request) { this->request = request; } void QTMegaEvent::setTransfer(MegaTransfer *transfer) { this->transfer = transfer; } void QTMegaEvent::setError(MegaError *error) { this->error = error; } void QTMegaEvent::setNodes(MegaNodeList *nodes) { this->nodes = nodes; } void QTMegaEvent::setUsers(MegaUserList *users) { this->users = users; } void QTMegaEvent::setUserAlerts(MegaUserAlertList *userAlerts) { this->userAlerts = userAlerts; } MegaEvent *QTMegaEvent::getEvent() { return event; } void QTMegaEvent::setEvent(MegaEvent* event) { this->event = event; } #ifdef ENABLE_SYNC MegaSync *QTMegaEvent::getSync() { return sync; } MegaSyncStats *QTMegaEvent::getSyncStats() { return syncStats; } string *QTMegaEvent::getLocalPath() { return localPath; } int QTMegaEvent::getNewState() { return newState; } void QTMegaEvent::setSync(MegaSync *sync) { this->sync = sync; } void QTMegaEvent::setSyncStats(MegaSyncStats *stats) { this->syncStats = stats; } void QTMegaEvent::setLocalPath(string *localPath) { this->localPath = localPath; } void QTMegaEvent::setNewState(int newState) { this->newState = newState; } #endif const std::string& QTMegaEvent::getMountPath() const { return mMountPath; } int QTMegaEvent::getMountResult() const { return mMountResult; } void QTMegaEvent::setMountPath(const std::string& path) { mMountPath = path; } void QTMegaEvent::setMountResult(int result) { mMountResult = result; } sdk-10.11.0/bindings/qt/QTMegaEvent.h000066400000000000000000000047561516266226600171750ustar00rootroot00000000000000#pragma once #include #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif namespace mega { class QTMegaEvent: public QEvent { public: enum MegaType { OnRequestStart = QEvent::User + 100, OnRequestUpdate, OnRequestFinish, OnRequestTemporaryError, OnTransferStart, OnTransferTemporaryError, OnTransferUpdate, OnTransferFolderUpdate, OnTransferFinish, OnUsersUpdate, OnUserAlertsUpdate, OnNodesUpdate, OnAccountUpdate, OnReloadNeeded, OnEvent, #if ENABLE_SYNC OnSyncStateChanged, OnSyncStatsUpdated, OnFileSyncStateChanged, OnSyncAdded, OnSyncDeleted, OnGlobalSyncStateChanged, OnSyncRemoteRootChanged, #endif OnMountAdded, OnMountChanged, OnMountDisabled, OnMountEnabled, OnMountRemoved }; QTMegaEvent(MegaApi* api, Type type); ~QTMegaEvent() override; MegaApi *getMegaApi() const; MegaRequest* getRequest(); MegaTransfer* getTransfer(); MegaError* getError(); MegaNodeList* getNodes(); MegaUserList* getUsers(); MegaUserAlertList* getUserAlerts(); MegaEvent* getEvent(); void setRequest(MegaRequest *request); void setTransfer(MegaTransfer *transfer); void setError(MegaError *error); void setNodes(MegaNodeList *nodes); void setUsers(MegaUserList *users); void setUserAlerts(MegaUserAlertList *userAlerts); void setEvent(MegaEvent *event); #ifdef ENABLE_SYNC MegaSync *getSync(); void setSync(MegaSync *sync); void setSyncStats(MegaSyncStats *stats); MegaSyncStats *getSyncStats(); std::string *getLocalPath(); void setLocalPath(std::string *localPath); int getNewState(); void setNewState(int newState); #endif const std::string& getMountPath() const; int getMountResult() const; void setMountPath(const std::string& path); void setMountResult(int result); private: MegaApi* megaApi; MegaRequest *request; MegaTransfer *transfer; MegaError *error; MegaNodeList *nodes; MegaUserList *users; MegaUserAlertList *userAlerts; MegaEvent *event; #ifdef ENABLE_SYNC MegaSync *sync; MegaSyncStats *syncStats = nullptr; std::string* localPath; int newState; #endif std::string mMountPath; int mMountResult; }; } sdk-10.11.0/bindings/qt/QTMegaGlobalListener.cpp000066400000000000000000000061301516266226600213410ustar00rootroot00000000000000#include "QTMegaGlobalListener.h" #include "QTMegaApiManager.h" #include "QTMegaEvent.h" #include using namespace mega; QTMegaGlobalListener::QTMegaGlobalListener(MegaApi* megaApi, MegaGlobalListener* listener): QObject() { this->megaApi = megaApi; this->listener = listener; } QTMegaGlobalListener::~QTMegaGlobalListener() { this->listener = NULL; if (QTMegaApiManager::isMegaApiValid(megaApi)) { megaApi->removeGlobalListener(this); } } void QTMegaGlobalListener::onUsersUpdate(MegaApi *api, MegaUserList *users) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnUsersUpdate); event->setUsers(users ? users->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaGlobalListener::onUserAlertsUpdate(MegaApi *api, MegaUserAlertList *alerts) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnUserAlertsUpdate); event->setUserAlerts(alerts ? alerts->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaGlobalListener::onNodesUpdate(MegaApi *api, MegaNodeList *nodes) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnNodesUpdate); event->setNodes(nodes ? nodes->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaGlobalListener::onAccountUpdate(MegaApi *api) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnAccountUpdate); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaGlobalListener::onEvent(MegaApi *api, MegaEvent *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnEvent); event->setEvent(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } #ifdef ENABLE_SYNC void QTMegaGlobalListener::onGlobalSyncStateChanged(MegaApi *api) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnGlobalSyncStateChanged); QCoreApplication::postEvent(this, event, INT_MIN); } #endif void QTMegaGlobalListener::customEvent(QEvent *e) { QTMegaEvent *event = (QTMegaEvent *)e; switch(QTMegaEvent::MegaType(event->type())) { case QTMegaEvent::OnUsersUpdate: if(listener) listener->onUsersUpdate(event->getMegaApi(), event->getUsers()); break; case QTMegaEvent::OnUserAlertsUpdate: if(listener) listener->onUserAlertsUpdate(event->getMegaApi(), event->getUserAlerts()); break; case QTMegaEvent::OnNodesUpdate: if(listener) listener->onNodesUpdate(event->getMegaApi(), event->getNodes()); break; case QTMegaEvent::OnAccountUpdate: if(listener) listener->onAccountUpdate(event->getMegaApi()); break; case QTMegaEvent::OnEvent: if(listener) listener->onEvent(event->getMegaApi(), event->getEvent()); break; #ifdef ENABLE_SYNC case QTMegaEvent::OnGlobalSyncStateChanged: if(listener) listener->onGlobalSyncStateChanged(event->getMegaApi()); break; #endif default: break; } } sdk-10.11.0/bindings/qt/QTMegaGlobalListener.h000066400000000000000000000017071516266226600210130ustar00rootroot00000000000000#pragma once #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #include "megaapi.h" namespace mega { class QTMegaGlobalListener : public QObject, public MegaGlobalListener { Q_OBJECT public: explicit QTMegaGlobalListener(MegaApi* megaApi, MegaGlobalListener* parent = NULL); ~QTMegaGlobalListener() override; void onUsersUpdate(MegaApi* api, MegaUserList *users) override; void onUserAlertsUpdate(MegaApi* api, MegaUserAlertList *alerts) override; void onNodesUpdate(MegaApi* api, MegaNodeList *nodes) override; void onAccountUpdate(MegaApi* api) override; void onEvent(MegaApi* api, MegaEvent *e) override; #ifdef ENABLE_SYNC void onGlobalSyncStateChanged(MegaApi* api) override; #endif protected: void customEvent(QEvent * event) override; MegaApi* megaApi; MegaGlobalListener *listener; }; } sdk-10.11.0/bindings/qt/QTMegaListener.cpp000066400000000000000000000270071516266226600202260ustar00rootroot00000000000000#include "QTMegaListener.h" #include "QTMegaApiManager.h" #include "QTMegaEvent.h" #include using namespace mega; using namespace std; QTMegaListener::QTMegaListener(MegaApi* megaApi, MegaListener* listener): QObject() { this->megaApi = megaApi; this->listener = listener; } QTMegaListener::~QTMegaListener() { this->listener = NULL; if (QTMegaApiManager::isMegaApiValid(megaApi)) { megaApi->removeListener(this); } } void QTMegaListener::onRequestStart(MegaApi *api, MegaRequest *request) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestStart); event->setRequest(request->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestFinish); event->setRequest(request->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onRequestUpdate(MegaApi *api, MegaRequest *request) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestUpdate); event->setRequest(request->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestTemporaryError); event->setRequest(request->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onTransferStart(MegaApi *api, MegaTransfer *transfer) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferStart); event->setTransfer(transfer->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferFinish); event->setTransfer(transfer->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferUpdate); event->setTransfer(transfer->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferTemporaryError); event->setTransfer(transfer->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onUsersUpdate(MegaApi *api, MegaUserList *users) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnUsersUpdate); event->setUsers(users ? users->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onUserAlertsUpdate(MegaApi *api, MegaUserAlertList *alerts) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnUserAlertsUpdate); event->setUserAlerts(alerts ? alerts->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onNodesUpdate(MegaApi *api, MegaNodeList *nodes) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnNodesUpdate); event->setNodes(nodes ? nodes->copy() : NULL); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onAccountUpdate(MegaApi *api) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnAccountUpdate); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onReloadNeeded(MegaApi *api) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnReloadNeeded); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onEvent(MegaApi *api, MegaEvent *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnEvent); event->setEvent(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } #ifdef ENABLE_SYNC void QTMegaListener::onSyncStateChanged(MegaApi *api, MegaSync *sync) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnSyncStateChanged); event->setSync(sync->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onSyncStatsUpdated(MegaApi *api, MegaSyncStats* syncStats) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnSyncStatsUpdated); event->setSyncStats(syncStats->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onSyncFileStateChanged(MegaApi *api, MegaSync *sync, string *localPath, int newState) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnFileSyncStateChanged); event->setSync(sync->copy()); event->setLocalPath(new string(*localPath)); event->setNewState(newState); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onSyncAdded(MegaApi *api, MegaSync *sync) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnSyncAdded); event->setSync(sync->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onSyncDeleted(MegaApi *api, MegaSync *sync) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnSyncDeleted); event->setSync(sync->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onGlobalSyncStateChanged(MegaApi *api) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnGlobalSyncStateChanged); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaListener::onSyncRemoteRootChanged(MegaApi* api, MegaSync* sync) { QTMegaEvent* event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnSyncRemoteRootChanged); event->setSync(sync->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } #endif void QTMegaListener::onMountAdded(MegaApi *api, const char* path, int result) { postMountEvent(QTMegaEvent::OnMountAdded, api, path, result); } void QTMegaListener::onMountChanged(MegaApi *api, const char* path, int result) { postMountEvent(QTMegaEvent::OnMountChanged, api, path, result); } void QTMegaListener::onMountDisabled(MegaApi *api, const char* path, int result) { postMountEvent(QTMegaEvent::OnMountDisabled, api, path, result); } void QTMegaListener::onMountEnabled(MegaApi *api, const char* path, int result) { postMountEvent(QTMegaEvent::OnMountEnabled, api, path, result); } void QTMegaListener::onMountRemoved(MegaApi *api, const char* path, int result) { postMountEvent(QTMegaEvent::OnMountRemoved, api, path, result); } void QTMegaListener::customEvent(QEvent *e) { QTMegaEvent *event = (QTMegaEvent *)e; switch(QTMegaEvent::MegaType(event->type())) { case QTMegaEvent::OnRequestStart: if(listener) listener->onRequestStart(event->getMegaApi(), event->getRequest()); break; case QTMegaEvent::OnRequestUpdate: if(listener) listener->onRequestUpdate(event->getMegaApi(), event->getRequest()); break; case QTMegaEvent::OnRequestFinish: if(listener) listener->onRequestFinish(event->getMegaApi(), event->getRequest(), event->getError()); break; case QTMegaEvent::OnRequestTemporaryError: if(listener) listener->onRequestTemporaryError(event->getMegaApi(), event->getRequest(), event->getError()); break; case QTMegaEvent::OnTransferStart: if(listener) listener->onTransferStart(event->getMegaApi(), event->getTransfer()); break; case QTMegaEvent::OnTransferTemporaryError: if(listener) listener->onTransferTemporaryError(event->getMegaApi(), event->getTransfer(), event->getError()); break; case QTMegaEvent::OnTransferUpdate: if(listener) listener->onTransferUpdate(event->getMegaApi(), event->getTransfer()); break; case QTMegaEvent::OnTransferFinish: if(listener) listener->onTransferFinish(event->getMegaApi(), event->getTransfer(), event->getError()); break; case QTMegaEvent::OnUsersUpdate: if(listener) listener->onUsersUpdate(event->getMegaApi(), event->getUsers()); break; case QTMegaEvent::OnUserAlertsUpdate: if(listener) listener->onUserAlertsUpdate(event->getMegaApi(), event->getUserAlerts()); break; case QTMegaEvent::OnNodesUpdate: if(listener) listener->onNodesUpdate(event->getMegaApi(), event->getNodes()); break; case QTMegaEvent::OnAccountUpdate: if(listener) listener->onAccountUpdate(event->getMegaApi()); break; case QTMegaEvent::OnEvent: if(listener) listener->onEvent(event->getMegaApi(), event->getEvent()); break; #if ENABLE_SYNC case QTMegaEvent::OnSyncStateChanged: if(listener) listener->onSyncStateChanged(event->getMegaApi(), event->getSync()); break; case QTMegaEvent::OnSyncStatsUpdated: if(listener) listener->onSyncStatsUpdated(event->getMegaApi(), event->getSyncStats()); break; case QTMegaEvent::OnFileSyncStateChanged: if(listener) listener->onSyncFileStateChanged(event->getMegaApi(), event->getSync(), event->getLocalPath(), event->getNewState()); break; case QTMegaEvent::OnSyncAdded: if(listener) listener->onSyncAdded(event->getMegaApi(), event->getSync()); break; case QTMegaEvent::OnSyncDeleted: if(listener) listener->onSyncDeleted(event->getMegaApi(), event->getSync()); break; case QTMegaEvent::OnGlobalSyncStateChanged: if(listener) listener->onGlobalSyncStateChanged(event->getMegaApi()); break; case QTMegaEvent::OnSyncRemoteRootChanged: if (listener) listener->onSyncRemoteRootChanged(event->getMegaApi(), event->getSync()); break; #endif case QTMegaEvent::OnMountAdded: onMountEvent(&MegaListener::onMountAdded, *event); break; case QTMegaEvent::OnMountChanged: onMountEvent(&MegaListener::onMountChanged, *event); break; case QTMegaEvent::OnMountDisabled: onMountEvent(&MegaListener::onMountDisabled, *event); break; case QTMegaEvent::OnMountEnabled: onMountEvent(&MegaListener::onMountEnabled, *event); break; case QTMegaEvent::OnMountRemoved: onMountEvent(&MegaListener::onMountRemoved, *event); break; default: break; } } void QTMegaListener::onMountEvent(FuseEventHandler handler, const QTMegaEvent& event) { if (!listener) return; (listener->*handler)(event.getMegaApi(), event.getMountPath().c_str(), event.getMountResult()); } void QTMegaListener::postMountEvent(QTMegaEvent::MegaType eventType, MegaApi *api, const std::string& path, int result) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)eventType); event->setMountPath(path); event->setMountResult(result); QCoreApplication::postEvent(this, event, INT_MIN); } sdk-10.11.0/bindings/qt/QTMegaListener.h000066400000000000000000000053761516266226600177000ustar00rootroot00000000000000#pragma once #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #include "megaapi.h" #include "QTMegaEvent.h" namespace mega { class QTMegaListener : public QObject, public MegaListener { Q_OBJECT public: explicit QTMegaListener(MegaApi* megaApi, MegaListener* parent = NULL); ~QTMegaListener() override; void onRequestStart(MegaApi* api, MegaRequest *request) override; void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError* e) override; void onRequestUpdate(MegaApi* api, MegaRequest *request) override; void onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError* e) override; void onTransferStart(MegaApi *api, MegaTransfer *transfer) override; void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e) override; void onTransferUpdate(MegaApi *api, MegaTransfer *transfer) override; void onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError* e) override; void onUsersUpdate(MegaApi* api, MegaUserList *users) override; void onUserAlertsUpdate(MegaApi* api, MegaUserAlertList *alerts) override; void onNodesUpdate(MegaApi* api, MegaNodeList *nodes) override; void onAccountUpdate(MegaApi* api) override; void onReloadNeeded(MegaApi* api); void onEvent(MegaApi* api, MegaEvent *e) override; #ifdef ENABLE_SYNC void onSyncStateChanged(MegaApi *api, MegaSync *sync) override; void onSyncStatsUpdated(MegaApi *api, MegaSyncStats *stats) override; void onSyncFileStateChanged(MegaApi *api, MegaSync *sync, std::string *localPath, int newState) override; void onSyncAdded(MegaApi *api, MegaSync *sync) override; void onSyncDeleted(MegaApi *api, MegaSync *sync) override; void onGlobalSyncStateChanged(MegaApi* api) override; void onSyncRemoteRootChanged(MegaApi* api, MegaSync* sync) override; #endif void onMountAdded(MegaApi* api, const char* path, int result) override; void onMountChanged(MegaApi* api, const char* path, int result) override; void onMountDisabled(MegaApi* api, const char* path, int result) override; void onMountEnabled(MegaApi* api, const char* path, int result) override; void onMountRemoved(MegaApi* api, const char* path, int result) override; protected: void customEvent(QEvent * event) override; using FuseEventHandler = void (MegaListener::*)(MegaApi*, const char*, int); void onMountEvent(FuseEventHandler handler, const QTMegaEvent& event); void postMountEvent(QTMegaEvent::MegaType eventType, MegaApi *api, const std::string& path, int result); MegaApi* megaApi; MegaListener *listener; }; } sdk-10.11.0/bindings/qt/QTMegaRequestListener.cpp000066400000000000000000000050051516266226600215710ustar00rootroot00000000000000#include "QTMegaRequestListener.h" #include "QTMegaApiManager.h" #include "QTMegaEvent.h" #include using namespace mega; QTMegaRequestListener::QTMegaRequestListener(MegaApi* megaApi, MegaRequestListener* listener): QObject() { this->megaApi = megaApi; this->listener = listener; } QTMegaRequestListener::~QTMegaRequestListener() { this->listener = NULL; if (QTMegaApiManager::isMegaApiValid(megaApi)) { megaApi->removeRequestListener(this); } } void QTMegaRequestListener::onRequestStart(MegaApi *api, MegaRequest *request) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestStart); event->setRequest(request->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaRequestListener::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestFinish); event->setRequest(request->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaRequestListener::onRequestUpdate(MegaApi *api, MegaRequest *request) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestUpdate); event->setRequest(request->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaRequestListener::onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnRequestTemporaryError); event->setRequest(request->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaRequestListener::customEvent(QEvent *e) { QTMegaEvent *event = (QTMegaEvent *)e; switch(QTMegaEvent::MegaType(event->type())) { case QTMegaEvent::OnRequestStart: if(listener) listener->onRequestStart(event->getMegaApi(), event->getRequest()); break; case QTMegaEvent::OnRequestUpdate: if(listener) listener->onRequestUpdate(event->getMegaApi(), event->getRequest()); break; case QTMegaEvent::OnRequestFinish: if(listener) listener->onRequestFinish(event->getMegaApi(), event->getRequest(), event->getError()); break; case QTMegaEvent::OnRequestTemporaryError: if(listener) listener->onRequestTemporaryError(event->getMegaApi(), event->getRequest(), event->getError()); break; default: break; } } sdk-10.11.0/bindings/qt/QTMegaRequestListener.h000066400000000000000000000016461516266226600212450ustar00rootroot00000000000000#pragma once #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #include "megaapi.h" #include namespace mega { class QTMegaRequestListener : public QObject, public MegaRequestListener { Q_OBJECT public: QTMegaRequestListener(MegaApi* megaApi, MegaRequestListener* listener = NULL); ~QTMegaRequestListener() override; //Request callbacks void onRequestStart(MegaApi* api, MegaRequest *request) override; void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError* e) override; void onRequestUpdate(MegaApi* api, MegaRequest *request) override; void onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError* e) override; protected: virtual void customEvent(QEvent * event) override; MegaApi* megaApi; MegaRequestListener* listener; }; } sdk-10.11.0/bindings/qt/QTMegaTransferListener.cpp000066400000000000000000000074641516266226600217400ustar00rootroot00000000000000#include "QTMegaTransferListener.h" #include "QTMegaApiManager.h" #include "QTMegaEvent.h" #include using namespace mega; struct QtMegaFolderEvent : public QTMegaEvent { QtMegaFolderEvent(MegaApi* megaApi, Type type) :QTMegaEvent(megaApi, type) {} int stage; uint32_t foldercount; uint32_t createdfoldercount; uint32_t filecount; }; QTMegaTransferListener::QTMegaTransferListener(MegaApi* megaApi, MegaTransferListener* listener): QObject() { this->megaApi = megaApi; this->listener = listener; } QTMegaTransferListener::~QTMegaTransferListener() { this->listener = NULL; if (QTMegaApiManager::isMegaApiValid(megaApi)) { megaApi->removeTransferListener(this); } } void QTMegaTransferListener::onTransferStart(MegaApi *api, MegaTransfer *transfer) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferStart); event->setTransfer(transfer->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaTransferListener::onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferFinish); event->setTransfer(transfer->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaTransferListener::onTransferUpdate(MegaApi *api, MegaTransfer *transfer) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferUpdate); event->setTransfer(transfer->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaTransferListener::onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError *e) { QTMegaEvent *event = new QTMegaEvent(api, (QEvent::Type)QTMegaEvent::OnTransferTemporaryError); event->setTransfer(transfer->copy()); event->setError(e->copy()); QCoreApplication::postEvent(this, event, INT_MIN); } void mega::QTMegaTransferListener::onFolderTransferUpdate(mega::MegaApi* api, mega::MegaTransfer* transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char*, const char*) { QtMegaFolderEvent* event = new QtMegaFolderEvent(api, (QEvent::Type)QTMegaEvent::OnTransferFolderUpdate); event->setTransfer(transfer->copy()); event->stage = stage; event->foldercount = foldercount; event->createdfoldercount = createdfoldercount; event->filecount = filecount; QCoreApplication::postEvent(this, event, INT_MIN); } void QTMegaTransferListener::customEvent(QEvent *e) { QTMegaEvent *event = (QTMegaEvent *)e; switch(QTMegaEvent::MegaType(event->type())) { case QTMegaEvent::OnTransferStart: if(listener) listener->onTransferStart(event->getMegaApi(), event->getTransfer()); break; case QTMegaEvent::OnTransferTemporaryError: if(listener) listener->onTransferTemporaryError(event->getMegaApi(), event->getTransfer(), event->getError()); break; case QTMegaEvent::OnTransferUpdate: if(listener) listener->onTransferUpdate(event->getMegaApi(), event->getTransfer()); break; case QTMegaEvent::OnTransferFolderUpdate: if (listener) { if (auto folderEvent = dynamic_cast(e)) { listener->onFolderTransferUpdate(folderEvent->getMegaApi(), folderEvent->getTransfer(), folderEvent->stage, folderEvent->foldercount, folderEvent->createdfoldercount, folderEvent->filecount, nullptr, nullptr); } } break; case QTMegaEvent::OnTransferFinish: if(listener) listener->onTransferFinish(event->getMegaApi(), event->getTransfer(), event->getError()); break; default: break; } } sdk-10.11.0/bindings/qt/QTMegaTransferListener.h000066400000000000000000000021241516266226600213710ustar00rootroot00000000000000#pragma once #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #include namespace mega { class QTMegaTransferListener : public QObject, public MegaTransferListener { Q_OBJECT public: QTMegaTransferListener(MegaApi* megaApi, MegaTransferListener* listener); ~QTMegaTransferListener() override; public: void onTransferStart(MegaApi *api, MegaTransfer *transfer) override; void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e) override; void onTransferUpdate(MegaApi *api, MegaTransfer *transfer) override; void onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError* e) override; void onFolderTransferUpdate(mega::MegaApi*, mega::MegaTransfer* transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char*, const char*)override; protected: void customEvent(QEvent * event) override; MegaApi* megaApi; MegaTransferListener* listener; }; } sdk-10.11.0/build/000077500000000000000000000000001516266226600135465ustar00rootroot00000000000000sdk-10.11.0/build/README.md000066400000000000000000000052271516266226600150330ustar00rootroot00000000000000# SDK Build Directory This directory contains all necessary scripts and templates for building the SDK application across multiple distributions, including Debian, RPM, and Arch Linux. ## Directory Contents - **create_tarball.sh**: A script to generate the tarball for the SDK package. - **Debian-specific files**: - `debian.postinst`: Post-installation script for the Debian package. - `debian.prerm`: Pre-removal script for the Debian package. - `debian.postrm`: Post-removal script for the Debian package. - `debian.rules`: Defines the Debian package build steps using `cmake`. - `debian.changes`: Log file containing details of changes between Debian package versions. - `debian.install`: Specifies which files to install and their destination directories during package installation. - **RPM-specific template**: - `megasdk.spec`: RPM spec file template that contains the build instructions using `cmake`. - **Arch-specific template**: - `PKGBUILD`: Build script for creating the Arch Linux package. - **Common template**: - `megasdk.dsc`: Template for the Debian source control file. Currently, these packages are not in use and serve no immediate practical purpose. However, in the future, they may become useful. The goal is to ensure that we are using the same build system as the SDK clients, so that we can debug locally and respond quickly in case of a failure. ## Building the SDK To build the SDK, the process varies slightly based on the distribution. Below are the commands used in both `megasdk.spec` (RPM) and `debian.rules` (Debian) to configure and initiate the build: ```bash cmake ${vcpkg_root} -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -S . -B %{_builddir}/build_dir ``` Explanation of CMake Command: ${vcpkg_root}: Path to the vcpkg installation or root directory. -DCMAKE_VERBOSE_MAKEFILE=ON: Enables verbose output for makefile generation, helpful for debugging. -DCMAKE_BUILD_TYPE=RelWithDebInfo: Builds the SDK in Release mode with Debug information included. -S .: Specifies the source directory for the build (the current directory). -B %{_builddir}/build_dir: Specifies the build directory where all compilation artifacts will be placed. ## Usage Instructions - Clone the SDK repository. - Modify the templates (megasdk.dsc, megasdk.spec, PKGBUILD) as necessary for your distribution. - Run the create_tarball.sh script to package the SDK. - Build the package using the appropriate packaging system (Debian, RPM, or Arch) by following their respective guidelines. For further information, refer to the specific files or the documentation related to your distribution's packaging system.sdk-10.11.0/build/create_tarball.sh000077500000000000000000000076001516266226600170540ustar00rootroot00000000000000#!/bin/bash -x ## # @file build/create_tarball.sh # @brief Generates SDK tarballs and compilation scripts # # (c) 2013-2014 by Mega Limited, Auckland, New Zealand # # This file is part of the MEGA SDK - Client Access Engine. # # SDK is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # @copyright Simplified (2-clause) BSD License. # # You should have received a copy of the license along with this # program. ## set -euo pipefail IFS=$'\n\t' BASEPATH=$(pwd)/../ # get current version megasdk_VERSION=$(cat $BASEPATH/include/mega/version.h | grep -Po "MEGA_.*_VERSION [0-9]*"| awk '{print $2}' | paste -sd '.') export megasdk_NAME=megasdk-$megasdk_VERSION rm -rf $megasdk_NAME.tar.gz rm -rf $megasdk_NAME echo "sdk version: $megasdk_VERSION" # delete previously generated files rm -fr megasdk/megasdk*.dsc # fix version number in template files and copy to appropriate directories sed -e "s/megasdk_VERSION/$megasdk_VERSION/g" templates/megasdk/megasdk.spec | sed "s#^ *##g" > megasdk/megasdk.spec sed -e "s/megasdk_VERSION/$megasdk_VERSION/g" templates/megasdk/megasdk.dsc > megasdk/megasdk.dsc sed -e "s/megasdk_VERSION/$megasdk_VERSION/g" templates/megasdk/PKGBUILD > megasdk/PKGBUILD for dscFile in `find templates/megasdk/ -name megasdk-xUbuntu_* -o -name megasdk-Debian_* -o -name megasdk-Raspbian_*`; do sed -e "s/megasdk_VERSION/$megasdk_VERSION/g" "${dscFile}" > megasdk/`basename ${dscFile}` done # read the last generated ChangeLog version version_file="version" if [ -s "$version_file" ]; then last_version=$(cat "$version_file") else last_version="none" fi if [ "$last_version" != "$megasdk_VERSION" ]; then # add RPM ChangeLog entry changelog="megasdk/megasdk.changes" changelogold="megasdk/megasdk.changes.old" if [ -f $changelog ]; then mv $changelog $changelogold fi ./generate_rpm_changelog_entry.sh $megasdk_VERSION $BASEPATH/include/mega/version.h > $changelog #TODO: read this from somewhere if [ -f $changelogold ]; then cat $changelogold >> $changelog rm $changelogold fi # add DEB ChangeLog entry changelog="megasdk/debian.changelog" changelogold="megasdk/debian.changelog.old" if [ -f $changelog ]; then mv $changelog $changelogold fi ./generate_deb_changelog_entry.sh $megasdk_VERSION $BASEPATH/include/mega/version.h > $changelog #TODO: read this from somewhere if [ -f $changelogold ]; then cat $changelogold >> $changelog rm $changelogold fi # update version file echo $megasdk_VERSION > $version_file fi # create archive mkdir $megasdk_NAME ln -s ../megasdk/megasdk.spec $megasdk_NAME/megasdk.spec ln -s ../megasdk/debian.postinst $megasdk_NAME/debian.postinst ln -s ../megasdk/debian.prerm $megasdk_NAME/debian.prerm ln -s ../megasdk/debian.postrm $megasdk_NAME/debian.postrm ln -s ../megasdk/debian.copyright $megasdk_NAME/debian.copyright ln -s $BASEPATH/src $megasdk_NAME/ ln -s $BASEPATH/include $megasdk_NAME ln -s $BASEPATH/third_party $megasdk_NAME/ ln -s $BASEPATH/tests $megasdk_NAME/ ln -s $BASEPATH/CMakeLists.txt $megasdk_NAME/ ln -s $BASEPATH/vcpkg.json $megasdk_NAME/ ln -s $BASEPATH/examples $megasdk_NAME/ mkdir $megasdk_NAME/tools ln -s $BASEPATH/tools/gfxworker $megasdk_NAME/tools/ ln -s $BASEPATH/cmake $megasdk_NAME/ mkdir $megasdk_NAME/bindings ln -s $BASEPATH/bindings/qt $megasdk_NAME/bindings/ tar czfh $megasdk_NAME.tar.gz --exclude-vcs $megasdk_NAME rm -rf $megasdk_NAME # delete any previous archive rm -fr megasdk/megasdk_*.tar.gz # transform arch name, to satisfy Debian requirements mv $megasdk_NAME.tar.gz megasdk/megasdk_$megasdk_VERSION.tar.gz #get md5sum and replace in PKGBUILD MD5SUM=`md5sum megasdk/megasdk_$megasdk_VERSION.tar.gz | awk '{print $1}'` sed "s/MD5SUM/$MD5SUM/g" -i megasdk/PKGBUILD ###### ###### sdk-10.11.0/build/generate_deb_changelog_entry.sh000077500000000000000000000021111516266226600217340ustar00rootroot00000000000000#!/bin/bash ## # @file src/build/generate_changelog_entry.sh # @brief Processes the input file and prints DEB ChangeLog entry # # (c) 2013-2014 by Mega Limited, Auckland, New Zealand # # This file is part of the SDK. # # SDK is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # @copyright Simplified (2-clause) BSD License. # # You should have received a copy of the license along with this # program. ## if [ "$#" -ne 2 ]; then echo " $0 [version] [input file path]" exit 1 fi in_file="$2" out1=$(printf "#include using namespace std; #include \"$2\" int main() { cout << megasdk::megasdkchangelog << endl; }" | g++ -x c++ - -o /tmp/printChangeLogMmegasdk && /tmp/printChangeLogMmegasdk | awk '{print " * "$0}' && rm /tmp/printChangeLogMmegasdk ) # print ChangeLog entry NOW=$(LANG=C date -R) echo "megasdk ($1) stable; urgency=low" echo "" echo "$out1" | sed 's#\\"#"#g' echo "" echo " -- MEGA Team $NOW" echo "" sdk-10.11.0/build/generate_rpm_changelog_entry.sh000077500000000000000000000021621516266226600220060ustar00rootroot00000000000000#!/bin/bash ## # @file src/build/generate_changelog_entry.sh # @brief Processes the input file and prints RPM ChangeLog entry # # (c) 2013-2014 by Mega Limited, Auckland, New Zealand # # This file is part of the SDK. # # SDK is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # @copyright Simplified (2-clause) BSD License. # # You should have received a copy of the license along with this # program. ## if [ "$#" -ne 2 ]; then echo " $0 [version] [input file path]" exit 1 fi in_file="$2" out1=$(printf "#include using namespace std; #include \"$2\" int main() { cout << megasdk::megasdkchangelog << endl; }" | g++ -x c++ - -o /tmp/printChangeLogMmegasdk && /tmp/printChangeLogMmegasdk | awk '{print " * "$0}' && rm /tmp/printChangeLogMmegasdk ) # print ChangeLog entry NOW=$(LANG=en_us_8859_1;date) echo $NOW - linux@mega.co.nz echo "- Update to version $1:" echo "$out1" | sed 's#\\"#"#g' echo "" echo "-------------------------------------------------------------------" sdk-10.11.0/build/megasdk/000077500000000000000000000000001516266226600151615ustar00rootroot00000000000000sdk-10.11.0/build/megasdk/debian.changelog000066400000000000000000000001511516266226600202510ustar00rootroot00000000000000megasdk (7.9.0) stable; urgency=low -- MEGA Team Mon, 02 Sep 2024 16:08:57 +0000 sdk-10.11.0/build/megasdk/debian.compat000066400000000000000000000000021516266226600176000ustar00rootroot000000000000009 sdk-10.11.0/build/megasdk/debian.control000066400000000000000000000012461516266226600200100ustar00rootroot00000000000000Source: megasdk Section: Tools Priority: normal Maintainer: MEGA Linux Team Build-Depends: debhelper, wget, libtool, dh-autoreconf, cdbs, autoconf, autoconf-archive, nasm, cmake Package: megasdk Architecture: any Depends: ${shlibs:Depends}, apt-transport-https, gpg Description: MEGA SDK - Client Access Engine This SDK brings you all the power of our client applications and let you create your own or analyze the security of our products. Package: megasdk-dbg Architecture: any Section: debug Priority: extra Depends: megasdk Description: debugging symbols for sdk sdk-10.11.0/build/megasdk/debian.copyright000066400000000000000000000033111516266226600203330ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: sdk Upstream-Contact: Source: https://github.com/meganz/sdk Files: * Copyright: 2013, Mega Limited License: Simplified (2-clause) BSD License ================================ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The previous applies to all sources and binaries within the SDK save those explicitly distributed under the terms of the GNU General Public License (see http://www.gnu.org/copyleft/gpl.txt for details). sdk-10.11.0/build/megasdk/debian.postinst000066400000000000000000000000141516266226600202030ustar00rootroot00000000000000#!/bin/bash sdk-10.11.0/build/megasdk/debian.postrm000066400000000000000000000000141516266226600176440ustar00rootroot00000000000000#!/bin/bash sdk-10.11.0/build/megasdk/debian.prerm000066400000000000000000000000141516266226600174450ustar00rootroot00000000000000#!/bin/bash sdk-10.11.0/build/megasdk/debian.rules000066400000000000000000000057561516266226600174740ustar00rootroot00000000000000#!/usr/bin/make -f NJOBS := $(shell echo ${DEB_BUILD_OPTIONS} | sed -rn 's/.*parallel=([0-9]*).*/\1/p') VCPKG_ROOT := $(shell [ -f /opt/vcpkg.tar.gz ] && echo "-DVCPKG_ROOT=vcpkg" || echo "") EXPORT_VCPKG_FORCE_SYSTEM_BINARIES := $(shell uname -m | grep "armv7l" >/dev/null && echo "VCPKG_FORCE_SYSTEM_BINARIES=1" || echo "") export VCPKG_DEFAULT_BINARY_CACHE := $(shell [ -f /opt/vcpkg.tar.gz ] && echo "/opt/persistent/vcpkg_cache" || echo "") export PATH := $(shell [ -f /opt/cmake.tar.gz ] && echo "`pwd`/cmake_inst/bin:${PATH}" || echo "${PATH}") MEGA_BUILD_ID := $(shell cat MEGA_BUILD_ID || echo "1") QTINSTALL := $(shell cat /etc/issue | grep "Ubuntu 20.04" >/dev/null && echo "-DCMAKE_PREFIX_PATH=/opt/mega" || echo "") QTDEFINES := $(shell [ -f /opt/mega/bin/qmake ] && echo "${QTINSTALL}" || echo "" ) build: build-stamp build-stamp: sed -i -E "0,/megasdk \(([0-9.]*)[^\)]*\)/s//megasdk \(\1-$(MEGA_BUILD_ID).1)/" debian.changelog || : if [ -f /opt/vcpkg.tar.gz ]; then \ tar xzf /opt/vcpkg.tar.gz; \ mkdir -p $(VCPKG_DEFAULT_BINARY_CACHE); \ fi if [ -f /opt/cmake.tar.gz ]; then \ HASH=$$(md5sum /opt/cmake.tar.gz | awk '{print $$1}'); \ if [ "$$HASH" = "ebc26503469f12bf1e956c564fcfa82a" ] || [ "$$HASH" = "2e278820bb4ec6fb71dbef1704fb5359" ]; then \ echo "Valid cmake.tar.gz (hash: $$HASH)"; \ else \ echo "Invalid cmake.tar.gz hash: $$HASH" >&2; \ exit 1; \ fi; \ fi if [ -f /opt/cmake.tar.gz ]; then \ tar xzf /opt/cmake.tar.gz; \ ln -s cmake-*-linux* cmake_inst; \ fi cmake --version $(EXPORT_VCPKG_FORCE_SYSTEM_BINARIES) cmake $(VCPKG_ROOT) -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_QT_BINDINGS=ON -DENABLE_LOG_PERFORMANCE=ON -DUSE_LIBUV=ON $(QTDEFINES) -S $(CURDIR) -B $(CURDIR)/build_dir cmake --build $(CURDIR)/build_dir -j$(NJOBS) cmake --install $(CURDIR)/build_dir --prefix $(CURDIR)/install_dir touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp install: build dh_auto_install --destdir=debian/megasdk -- INSTALL_ROOT=$(CURDIR)/debian/megasdk # dh_install install_dir/* / # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot # dh_installdebconf dh_installdocs dh_installexamples dh_installmenu # dh_installlogrotate # dh_installemacsen # dh_installpam # dh_installmime # dh_installinit dh_installcron dh_installman dh_installinfo # dh_undocumented dh_installchangelogs dh_link dh_strip --dbg-package=megasdk-dbg dh_compress dh_fixperms # dh_makeshlibs dh_installdeb # dh_perl dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info dh_gencontrol dh_md5sums dh_builddeb -- -Zxz binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install sdk-10.11.0/build/megasdk/megasdk.changes000066400000000000000000000000001516266226600201140ustar00rootroot00000000000000sdk-10.11.0/build/megasdk/megasdk.install000066400000000000000000000000721516266226600201630ustar00rootroot00000000000000post_install() { } pre_upgrade() { } post_upgrade() { } sdk-10.11.0/build/templates/000077500000000000000000000000001516266226600155445ustar00rootroot00000000000000sdk-10.11.0/build/templates/megasdk/000077500000000000000000000000001516266226600171575ustar00rootroot00000000000000sdk-10.11.0/build/templates/megasdk/PKGBUILD000066400000000000000000000044041516266226600203050ustar00rootroot00000000000000## # @file build/templates/sdk/PKGBUILD # @brief script to generate package sdk for ArchLinux # # (c) 2013-2016 by Mega Limited, Auckland, New Zealand # # This file is part of the MEGA SDK - Client Access Engine. # # SDK is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # @copyright Simplified (2-clause) BSD License. # # You should have received a copy of the license along with this # program. ## pkgname=megasdk pkgver=megasdk_VERSION pkgrel=`cat MEGA_BUILD_ID || echo "1"` epoch= pkgdesc="MEGA SDK - Client Access Engine" arch=('i686' 'x86_64') url="https://mega.io/developers" license=("https://github.com/meganz/sdk/blob/master/LICENSE") groups=() depends=('glibc>=2.37' 'qt5-base>=5.15' 'qt5-tools>=5.15' 'qt5-svg>=5.15' 'qt5-x11extras' 'qt5-graphicaleffects' 'qt5-quickcontrols2' 'qt5-quickcontrols') makedepends=('unzip' 'wget' 'ca-certificates' 'qt5-tools' 'qt5-declarative' 'cmake' 'zip' 'python3' 'autoconf-archive' 'nasm') checkdepends=() optdepends=('sni-qt: fix systray issue on KDE and LXQt') provides=("megasdk=${pkgver}") replaces=() backup=() options=(!lto) # ffmpeg is not relocatable install=megasdk.install changelog= #TODO source=("./${pkgname}_$pkgver.tar.gz" ) noextract=() md5sums=('MD5SUM') # generated with makepkg -g validpgpkeys=() prepare() { cd "$pkgname-$pkgver" if [ -f /opt/vcpkg.tar.gz ]; then tar xzf /opt/vcpkg.tar.gz fi } build() { set -x cd "$pkgname-$pkgver" megasrcdir="${PWD}" megabuilddir="${megasrcdir}/build_dir" if [ -d "${megasrcdir}/vcpkg" ]; then export VCPKG_DEFAULT_BINARY_CACHE=/opt/persistent/vcpkg_cache mkdir -p ${VCPKG_DEFAULT_BINARY_CACHE} vcpkg_root="-DVCPKG_ROOT=${megasrcdir}/vcpkg" fi cmake --version cmake ${vcpkg_root} -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_QT_BINDINGS=ON -DENABLE_LOG_PERFORMANCE=ON -DUSE_LIBUV=ON -S "${megasrcdir}" -B "${megabuilddir}" cmake --build "${megabuilddir}" ${BUILD_PARALLEL_JOBS} touch build-stamp } check() { cd "$pkgname-$pkgver" } package() { cd "$pkgname-$pkgver" megasrcdir="${srcdir}/${pkgname}-${pkgver}" megabuilddir="${megasrcdir}/build_dir" cmake --install "${megabuilddir}" --prefix $pkgdir } sdk-10.11.0/build/templates/megasdk/megasdk-xUbuntu_20.04.dsc000066400000000000000000000010151516266226600234550ustar00rootroot00000000000000Format: 1.0 Source: megasdk Binary: megasdk Standards-Version: 3.6.1 Architecture: any Version: megasdk_VERSION DEBTRANSFORM-RELEASE: 1 Maintainer: MEGA Linux Team Homepage: https://mega.io/developers Build-Depends: qt-mega, pkg-config, debhelper, wget, dh-autoreconf, cdbs, unzip, pkg-config, autoconf, autoconf-archive, nasm, cmake, libtool-bin (>= 2.4.2-1.10) | libtool (<< 2.4.2-1.10) Package-List: megasdk deb gnome optional Files: 00000000000000000000000000000000 0 megasdk_megasdk_VERSION.tar.gz sdk-10.11.0/build/templates/megasdk/megasdk.dsc000066400000000000000000000012041516266226600212620ustar00rootroot00000000000000Format: 1.0 Source: megasdk Binary: megasdk Standards-Version: 3.6.1 Architecture: any Version: megasdk_VERSION DEBTRANSFORM-RELEASE: 1 Maintainer: MEGA Linux Team Homepage: https://mega.io/developers Build-Depends: qtbase5-dev, qt5-qmake, qttools5-dev-tools, libqt5dbus5, libqt5svg5-dev, libqt5x11extras5-dev, qtdeclarative5-dev, qttools5-dev, pkg-config, debhelper, wget, dh-autoreconf, cdbs, unzip, pkg-config, autoconf, autoconf-archive, nasm, cmake, libtool-bin (>= 2.4.2-1.10) | libtool (<< 2.4.2-1.10) Package-List: megasdk deb gnome optional Files: 00000000000000000000000000000000 0 megasdk_megasdk_VERSION.tar.gz sdk-10.11.0/build/templates/megasdk/megasdk.spec000066400000000000000000000102141516266226600214440ustar00rootroot00000000000000Name: megasdk Version: megasdk_VERSION Release: %(cat MEGA_BUILD_ID || echo "1").1 Summary: MEGA SDK - Client Access Engine License: https://github.com/meganz/megacmd/blob/master/LICENSE Group: Unspecified Url: https://mega.io/developers Source0: megasdk_%{version}.tar.gz Vendor: MEGA Limited Packager: MEGA Linux Team %global __requires_exclude ^lib(avcodec|avformat|avutil|swresample|swscale)\\.so\\. BuildRequires: autoconf, autoconf-archive, automake, libtool, gcc-c++ BuildRequires: hicolor-icon-theme, zip, unzip, nasm, cmake, perl #OpenSUSE %if 0%{?suse_version} || 0%{?sle_version} # disabling post-build-checks that ocassionally prevent opensuse rpms from being generated # plus it speeds up building process #!BuildIgnore: post-build-checks BuildRequires: libqt5-qtbase-devel, libqt5-linguist-devel, libqt5-qtsvg-devel, libqt5-qtx11extras-devel, libqt5-qtdeclarative-devel Requires: libQt5Core5 libqt5-qtquickcontrols libqt5-qtquickcontrols2 BuildRequires: kernel-devel %else BuildRequires: python3, kernel-headers %endif %if 0%{?suse_version} && 0%{?suse_version} <= 1500 BuildRequires: gcc14-c++ %else BuildRequires: gcc-c++ %endif # OpenSuse Leap 15.6 -> python311 # OpenSuse Leap 16.0 -> python313 # OpenSuse Tumbleweed -> python313 %if 0%{?sle_version} && 0%{?suse_version} < 1600 BuildRequires: python311 %endif %if (0%{?sle_version} || 0%{?is_opensuse}) && 0%{?suse_version} >= 1600 BuildRequires: python313 %endif #Fedora specific %if 0%{?fedora} # allowing for rpaths (taken as invalid, as if they were not absolute paths when they are) %if 0%{?fedora_version} >= 40 %define __brp_check_rpaths QA_RPATHS=0x0002 /usr/lib/rpm/check-rpaths BuildRequires: qt5-qtbase-devel qt5-qttools-devel, qt5-qtsvg-devel, qt5-qtx11extras-devel, qt5-qtdeclarative-devel, wget2, wget2-wget Requires: qt5-qtbase >= 5.15, qt5-qtsvg, qt5-qtdeclarative, qqc2-desktop-style, qt5-qtquickcontrols, qt5-qtquickcontrols2 %endif %endif # RHEL/CentOS/Alma/Rocky/Oracle >= 9: allow $ORIGIN %if (0%{?rhel} >= 9) || (0%{?rhel_version} >= 900) || (0%{?centos_version} >= 900) %define __brp_check_rpaths QA_RPATHS=$(( 0x0002|0x0008 )) /usr/lib/rpm/check-rpaths %endif #CentOS/RedHat/AlmaLinux %if 0%{?centos_version} || 0%{?rhel_version} BuildRequires: desktop-file-utils BuildRequires: qt5-qtbase-devel qt5-qttools-devel, qt5-linguist, qt5-qtsvg-devel, qt5-qtx11extras-devel, qt5-qtdeclarative-devel Requires: qt5-qtbase qt5-qtquickcontrols qt5-qtquickcontrols2 qt5-qtdeclarative %endif %description This SDK brings you all the power of our client applications and let you create your own or analyze the security of our products. %prep %global debug_package %{nil} %setup -q if [ -f /opt/vcpkg.tar.gz ]; then export VCPKG_DEFAULT_BINARY_CACHE=/opt/persistent/vcpkg_cache mkdir -p ${VCPKG_DEFAULT_BINARY_CACHE} tar xzf /opt/vcpkg.tar.gz vcpkg_root="-DVCPKG_ROOT=vcpkg" fi # use a custom cmake if required/available: if [ -f /opt/cmake.tar.gz ]; then echo "8dc99be7ba94ad6e14256b049e396b40 /opt/cmake.tar.gz" | md5sum -c - tar xzf /opt/cmake.tar.gz ln -s cmake-*-Linux* cmake_inst export PATH="${PWD}/cmake_inst/bin:${PATH}" fi # OpenSuse Leap 15.x defaults to gcc7. # Python>=10 needed for VCPKG pkgconf %if 0%{?suse_version} && 0%{?suse_version} <= 1500 export CC=gcc-14 export CXX=g++-14 mkdir python311 ln -sf /usr/bin/python3.11 python311/python3 export PATH=$PWD/python311:$PATH %endif cmake --version cmake ${vcpkg_root} -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_QT_BINDINGS=ON -DENABLE_LOG_PERFORMANCE=ON -DUSE_LIBUV=ON -S . -B %{_builddir}/build_dir %build if [ -f /opt/cmake.tar.gz ]; then export PATH="${PWD}/cmake_inst/bin:${PATH}" fi cmake --build %{_builddir}/build_dir %{?_smp_mflags} %install if [ -f /opt/cmake.tar.gz ]; then export PATH="${PWD}/cmake_inst/bin:${PATH}" fi cmake --install %{_builddir}/build_dir --prefix %{buildroot} ls -l %{buildroot} pwd %post %preun %postun %posttrans %clean %{?buildroot:%__rm -rf "%{buildroot}"} %files %defattr(-,root,root) %changelog sdk-10.11.0/cmake/000077500000000000000000000000001516266226600135275ustar00rootroot00000000000000sdk-10.11.0/cmake/.gitattributes000066400000000000000000000000171516266226600164200ustar00rootroot00000000000000*.patch binary sdk-10.11.0/cmake/config.h.in000066400000000000000000000233151516266226600155560ustar00rootroot00000000000000/* Generated from config.h.in.cmake by cmake. */ /* Define to enable chat */ #ifndef ENABLE_CHAT #cmakedefine ENABLE_CHAT 1 #endif /* Defined if sync subsystem is enabled */ #ifndef ENABLE_SYNC #cmakedefine ENABLE_SYNC 1 #endif #ifndef ENABLE_LOG_PERFORMANCE #cmakedefine ENABLE_LOG_PERFORMANCE 1 #endif #ifndef ENABLE_DRIVE_NOTIFICATIONS #cmakedefine ENABLE_DRIVE_NOTIFICATIONS 1 #endif /* Define to use FreeImage library. */ #ifndef USE_FREEIMAGE #cmakedefine USE_FREEIMAGE 1 #endif #ifndef HAVE_FREEIMAGECONFIG_H #cmakedefine HAVE_FREEIMAGECONFIG_H 1 #endif #if defined(USE_FREEIMAGE) && defined(HAVE_FREEIMAGECONFIG_H) #include "FreeImageConfig.h" // for FREEIMAGE_LIB setting #endif /* Define to enable isolated GFX process. */ #ifndef ENABLE_ISOLATED_GFX #cmakedefine ENABLE_ISOLATED_GFX 1 #endif /* Define to indicate AIO presence in librt */ #cmakedefine HAVE_AIO_RT 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ #cmakedefine HAVE_DIRENT_H 1 #if !defined(_WIN32) && !defined(__APPLE__) /* Define to 1 if you have the `fdopendir' function. */ #define HAVE_FDOPENDIR 1 #endif /* Define to use FFMPEG */ #ifndef HAVE_FFMPEG #cmakedefine HAVE_FFMPEG 1 #endif /* Define to use PDFIUM */ #ifndef HAVE_PDFIUM #cmakedefine HAVE_PDFIUM 1 #endif /* Defined if std::int64_t and time_t are distinct. */ #cmakedefine HAVE_DISTINCT_TIME_T 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_INTTYPES_H 1 /* Define to use libraw */ #cmakedefine HAVE_LIBRAW 1 /* Define to use libuv */ #cmakedefine HAVE_LIBUV 1 /* Define to not use readline */ #cmakedefine NO_READLINE 1 /* Define to 1 if you have the header file. */ #define HAVE_MALLOC_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_MALLOC_MALLOC_H */ /* Define to 1 if you have the header file. */ #define HAVE_MCHECK_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_NDIR_H */ /* Define to 1 if you have the header file. */ #define HAVE_NETDB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_NETINET_IN_H 1 /* Define to 1 if you have the header file. */ #define HAVE_OPENSSL_SSL_H 1 /* If available, contains the Python version number currently in use. */ /* #undef HAVE_PYTHON */ /* Define to 1 if you have the header file. */ #define HAVE_READLINE_READLINE_H 1 /* Define to 1 if you have the `select' function. */ #define HAVE_SELECT 1 /* Define to 1 if you have the header file. */ #define HAVE_SODIUM_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SQLITE3_H 1 /* Define to 1 if the system has the type `ssize_t'. */ #define HAVE_SSIZE_T 1 /* Define to 1 if stdbool.h conforms to C99. */ /* #undef HAVE_STDBOOL_H */ /* Define to 1 if you have the header file. */ #define HAVE_STDDEF_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_DIR_H */ /* Define to 1 if you have the header file. */ #ifndef __APPLE__ #define HAVE_SYS_INOTIFY_H 1 #endif /* Define to 1 if you have the header file. */ /* #undef HAVE_SYS_MALLOC_H */ /* Define to 1 if you have the header file, and it defines `DIR'. */ /* #undef HAVE_SYS_NDIR_H */ /* Define to 1 if you have the header file. */ #define HAVE_SYS_SOCKET_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TIMEB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_TERMCAP_H 1 /* Define to 1 if you have the header file. */ #ifndef _WIN32 #define HAVE_UNISTD_H 1 #endif /* Define to 1 if you have the header file. */ /* #undef HAVE_UV_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_ZENLIB_ZTRING_H */ /* Define to 1 if you have the header file. */ #define HAVE_ZLIB_H 1 /* Define to 1 if the system has the type `_Bool'. */ /* #undef HAVE__BOOL */ /* Define to the sub-directory where libtool stores uninstalled libraries. */ #define LT_OBJDIR ".libs/" /* cpu-machine-OS */ #define OS "x86_64-pc-linux-gnu" /* Name of package */ #define PACKAGE "libmega" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "https://github.com/meganz/sdk" /* Define to the full name of this package. */ #define PACKAGE_NAME "libmega" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "libmega 3.3.8" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "libmega" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* Define to the version of this package. */ #define PACKAGE_VERSION "3.3.8" /* The size of `uint64_t', as computed by sizeof. */ #define SIZEOF_UINT64_T 8 /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if your declares `struct tm'. */ /* #undef TM_IN_SYS_TIME */ /* Define to use UNICODE (for MediaInfo) */ #cmakedefine UNICODE 1 /* Define to use libcryptopp */ #cmakedefine USE_CRYPTOPP 1 /* Define to use Berkeley DB */ #define USE_DB 0 /* Use inotify API */ #if !defined(__APPLE__) && !defined(_WIN32) #define USE_INOTIFY 1 #endif /* Use IOS */ /* #undef USE_IOS */ /* Define to use libmediainfo */ #cmakedefine USE_MEDIAINFO 1 /* Defined if MEGA API enabled */ #cmakedefine USE_MEGAAPI 1 /* Define to use OpenSSL */ #cmakedefine USE_OPENSSL 1 /* Defined if pthreads are available */ #cmakedefine USE_PTHREAD 1 /* Defined if cppthreads are available */ #cmakedefine USE_CPPTHREAD 1 /* Define to use libsodium */ #cmakedefine USE_SODIUM 1 /* Define to use SQLite */ #cmakedefine USE_SQLITE 1 /* Enable extensions on AIX 3, Interix. */ #ifndef _ALL_SOURCE # define _ALL_SOURCE 1 #endif /* Enable GNU extensions on systems that have them. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE 1 #endif /* Enable threading extensions on Solaris. */ #ifndef _POSIX_PTHREAD_SEMANTICS # define _POSIX_PTHREAD_SEMANTICS 1 #endif /* Enable extensions on HP NonStop. */ #ifndef _TANDEM_SOURCE # define _TANDEM_SOURCE 1 #endif /* Enable general extensions on Solaris. */ #ifndef __EXTENSIONS__ # define __EXTENSIONS__ 1 #endif /* Define to use zlib */ #define USE_ZLIB 1 /* Version number of package */ #define VERSION "3.3.8" /* Define _DARWIN_C_SOURCE */ /* #undef _DARWIN_C_SOURCE */ /* Enable large inode numbers on Mac OS X 10.5. */ #ifndef _DARWIN_USE_64_BIT_INODE # define _DARWIN_USE_64_BIT_INODE 1 #endif /* Number of bits in a file offset, on hosts where this is settable. */ #if (defined(__ANDROID__) && (defined(__arm__) || defined(__i386__))) #define _FILE_OFFSET_BITS 64 #endif /* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ /* #undef _LARGEFILE_SOURCE */ /* Define for large files, on AIX-style hosts. */ /* #undef _LARGE_FILES */ /* Define to 1 if on MINIX. */ /* #undef _MINIX */ /* Define to 2 if the system does not provide POSIX.1 features except with this defined. */ /* #undef _POSIX_1_SOURCE */ /* Define to 1 if you need to in order for `stat' and other things to work. */ /* #undef _POSIX_SOURCE */ /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT32_T */ /* Define for Solaris 2.5.1 so the uint64_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT64_T */ /* Define for Solaris 2.5.1 so the uint8_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT8_T */ /* Define _XOPEN_SOURCE */ /* #undef _XOPEN_SOURCE */ /* Force definition of constant macros for C++ */ #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS /**/ #endif /* Force definition of format macros for C++ */ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS /**/ #endif /* Force definition of limit macros for C++ */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS /**/ #endif /* Define to `long int' if does not define. */ /* #undef off_t */ /* Define to `int' if does not define. */ /* #undef pid_t */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ /* Define to the type of an unsigned integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ /* #undef uint16_t */ /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ /* #undef uint32_t */ /* Define to the type of an unsigned integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ /* #undef uint64_t */ /* Define to the type of an unsigned integer type of width exactly 8 bits if such a type exists and the standard includes do not define it. */ /* #undef uint8_t */ sdk-10.11.0/cmake/modules/000077500000000000000000000000001516266226600151775ustar00rootroot00000000000000sdk-10.11.0/cmake/modules/Config.cmake.in000066400000000000000000000002151516266226600200110ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) include(${CMAKE_CURRENT_LIST_DIR}/sdklibTargets.cmake) check_required_components(SDKlib) sdk-10.11.0/cmake/modules/checks/000077500000000000000000000000001516266226600164375ustar00rootroot00000000000000sdk-10.11.0/cmake/modules/checks/has_distinct_time_t.cpp000066400000000000000000000003211516266226600231540ustar00rootroot00000000000000#include #include #include template struct Test; template<> struct Test {}; template<> struct Test {}; int main() { return EXIT_SUCCESS; } sdk-10.11.0/cmake/modules/checks/need_to_link_with_atomic.cmake000066400000000000000000000005721516266226600244660ustar00rootroot00000000000000function(check_if_atomic_is_needed) try_compile(NEEDS_ATOMIC_LIB "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_LIST_DIR}/checks/needs_atomic_lib.cpp" LINK_LIBRARIES atomic) if (NEEDS_ATOMIC_LIB) set(NEEDS_ATOMIC_LIB TRUE PARENT_SCOPE) else() set(NEEDS_ATOMIC_LIB FALSE PARENT_SCOPE) endif() endfunction()sdk-10.11.0/cmake/modules/checks/needs_atomic_lib.cpp000066400000000000000000000003271516266226600224250ustar00rootroot00000000000000// Extracted from http://bugs.debian.org/797228 #include #include int main() { std::atomic a{}; int64_t v = 5; int64_t r = a.fetch_add(v); return static_cast(r); } sdk-10.11.0/cmake/modules/checks/supports_ti_emulation_mode.cpp000066400000000000000000000001171516266226600246160ustar00rootroot00000000000000int main() { unsigned int __attribute__((mode(TI))) var{}; return 0; } sdk-10.11.0/cmake/modules/configuration.cmake000066400000000000000000000047571516266226600210650ustar00rootroot00000000000000# Define debug symbols covering all platforms. add_compile_definitions( $<$:DEBUG> $<$:_DEBUG> ) # Build the project with C++17 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Check if this platform's time_t type is distinct from int64_t. try_compile(HAVE_DISTINCT_TIME_T "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_LIST_DIR}/checks/has_distinct_time_t.cpp") if (MSVC) # https://gitlab.kitware.com/cmake/cmake/-/issues/18837 add_compile_options(/Zc:__cplusplus) # Enable updated __cplusplus macro # Enable build with multiple processes. add_compile_options(/MP) # Create a separated PDB file with debug symbols. add_compile_options($<$:/Zi>) # Define _WIN32_WINNT to specify the minimum supported Windows version. 0x0A00 corresponds to # Windows 10. Refer to sdkddkver.h in the Windows SDK for other possible values. WINVER and # NTDDI_VERSION are set automatically in sdkddkver.h. add_compile_definitions( _WIN32_WINNT=0x0A00 ) else() include(CheckIncludeFile) include(CheckFunctionExists) check_include_file(inttypes.h HAVE_INTTYPES_H) check_include_file(dirent.h HAVE_DIRENT_H) include(CheckSymbolExists) check_symbol_exists(glob glob.h HAVE_GLOB_H) check_function_exists(aio_write, HAVE_AIO_RT) # Check if our toolchain supports TI emulation mode. try_compile(SUPPORTS_TI_EMULATION_MODE "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_LIST_DIR}/checks/supports_ti_emulation_mode.cpp") # Toolchain supports TI emulation mode. if (SUPPORTS_TI_EMULATION_MODE) add_compile_definitions(SUPPORTS_TI_EMULATION_MODE=1) endif() if (UNIX AND NOT APPLE) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/checks) include("need_to_link_with_atomic") check_if_atomic_is_needed() if (NEEDS_ATOMIC_LIB) link_libraries(atomic) endif() endif() endif() if(NOT WIN32) if(ENABLE_ASAN) add_compile_options("-fsanitize=address" "-fno-omit-frame-pointer" "-fno-common") link_libraries("-fsanitize=address") endif() if(ENABLE_UBSAN) add_compile_options("-fsanitize=undefined" "-fno-omit-frame-pointer") link_libraries("-fsanitize=undefined") endif() if(ENABLE_TSAN) add_compile_options("-fsanitize=thread" "-fno-omit-frame-pointer") link_libraries("-fsanitize=thread") endif() endif() sdk-10.11.0/cmake/modules/detect_host_architecture.cmake000066400000000000000000000015601516266226600232520ustar00rootroot00000000000000# Detects the host architecture (HOST_ARCH) on Linux systems using `uname -m`. # If the value of HOST_ARCH is already set, it will not be changed, allowing # users to override the default detection if needed. # # This module should be included before calling project(). # # Usage: # # include(detect_host_architecture) # # After inclusion, HOST_ARCH will be set to one of the following values: # - "x86_64" for 64-bit x86 systems # - "ARM" for ARM-based systems (includes aarch64, armv7, etc.) # - "Unknown" if the architecture is not recognized if (UNIX) if (NOT DEFINED HOST_ARCH) execute_process(COMMAND uname -m OUTPUT_VARIABLE HOST_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE) set(HOST_ARCH ${HOST_ARCHITECTURE} CACHE STRING "Host architecture") unset(HOST_ARCHITECTURE) endif () endif ()sdk-10.11.0/cmake/modules/load_sdk_version.cmake000066400000000000000000000007631516266226600215340ustar00rootroot00000000000000function(read_sdk_version out_var version_file) file(READ ${version_file} FILE_CONTENTS) string(REGEX MATCH "define MEGA_MAJOR_VERSION ([0-9]*)" _ ${FILE_CONTENTS}) set(VER_STRING ${CMAKE_MATCH_1}) string(REGEX MATCH "define MEGA_MINOR_VERSION ([0-9]*)" _ ${FILE_CONTENTS}) set(VER_STRING ${VER_STRING}.${CMAKE_MATCH_1}) string(REGEX MATCH "define MEGA_MICRO_VERSION ([0-9]*)" _ ${FILE_CONTENTS}) set(${out_var} ${VER_STRING}.${CMAKE_MATCH_1} PARENT_SCOPE) endfunction() sdk-10.11.0/cmake/modules/packages/000077500000000000000000000000001516266226600167555ustar00rootroot00000000000000sdk-10.11.0/cmake/modules/packages/FindFreeImage.cmake000066400000000000000000000027251516266226600224120ustar00rootroot00000000000000## Module to find the FreeImage library. ## There is no one in cmake and the FreeImage library don't usually have a .pc file # Try to find the header find_path(FreeImage_INCLUDE_DIR NAMES FreeImage.h ) mark_as_advanced(FreeImage_INCLUDE_DIR) # Try to find the library find_library(FreeImage_LIBRARY NAMES freeimage libfreeimage ) mark_as_advanced(FreeImage_LIBRARY) file(READ "${FreeImage_INCLUDE_DIR}/FreeImage.h" FILE_CONTENTS) string(REGEX MATCH "#define FREEIMAGE_MAJOR_VERSION *([0-9]*)" _ ${FILE_CONTENTS}) set(FreeImage_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "#define FREEIMAGE_MINOR_VERSION *([0-9]*)" _ ${FILE_CONTENTS}) set(FreeImage_VERSION ${FreeImage_VERSION}.${CMAKE_MATCH_1}) string(REGEX MATCH "#define FREEIMAGE_RELEASE_SERIAL *([0-9]*)" _ ${FILE_CONTENTS}) set(FreeImage_VERSION ${FreeImage_VERSION}.${CMAKE_MATCH_1}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(FreeImage REQUIRED_VARS FreeImage_INCLUDE_DIR FreeImage_LIBRARY VERSION_VAR FreeImage_VERSION ) # Create target if(FreeImage_FOUND) set(FreeImage_INCLUDE_DIRS ${FreeImage_INCLUDE_DIR}) set(FreeImage_LIBRARIES ${FreeImage_LIBRARY}) if(NOT TARGET FreeImage::FreeImage) add_library(FreeImage::FreeImage UNKNOWN IMPORTED) set_target_properties(FreeImage::FreeImage PROPERTIES IMPORTED_LOCATION "${FreeImage_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${FreeImage_INCLUDE_DIR}") endif() endif() sdk-10.11.0/cmake/modules/packages/FindPHP.cmake000066400000000000000000000021141516266226600212050ustar00rootroot00000000000000# Try to find the PHP executable and php-config executable find_program(PHP_EXECUTABLE NAMES php php.exe) FIND_PROGRAM(PHP_CONFIG_EXECUTABLE NAMES php-config) if(NOT PHP_EXECUTABLE) message(FATAL_ERROR "PHP executable not found. Please ensure PHP is installed and available in PATH.") endif() if(NOT PHP_CONFIG_EXECUTABLE) message(FATAL_ERROR "PHP configuration executable not found. Please check php-config is available in the PATH.") endif() # Get include paths using php-config execute_process( COMMAND ${PHP_CONFIG_EXECUTABLE} --includes OUTPUT_VARIABLE PHP_INCLUDES_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE ) if(NOT PHP_INCLUDES_OUTPUT) message(FATAL_ERROR "PHP include directories not found using php-config.") else() message(STATUS "Raw include directories output: ${PHP_INCLUDES_OUTPUT}") endif() # Create a list with the include directories string(REPLACE "-I" "" PHP_INCLUDE_DIRS "${PHP_INCLUDES_OUTPUT}") string(REGEX REPLACE " " ";" PHP_INCLUDE_DIRS_LIST "${PHP_INCLUDE_DIRS}") message(STATUS "Processed PHP include directories: ${PHP_INCLUDE_DIRS_LIST}")sdk-10.11.0/cmake/modules/sdklib.pc.in000066400000000000000000000004621516266226600174020ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ Name: @PROJECT_NAME@ Description: @PROJECT_DESCRIPTION@ Version: @PROJECT_VERSION@ Requires: Libs: -L${libdir} -l@SDKLIB_NAME@ Cflags: -I${includedir} sdk-10.11.0/cmake/modules/sdklib_libraries.cmake000066400000000000000000000151341516266226600215110ustar00rootroot00000000000000macro(load_sdklib_libraries) target_link_libraries(SDKlib PUBLIC ccronexpr) target_link_libraries(SDKlib PUBLIC csv) if(USE_LIBUV AND USE_OPENSSL) target_link_libraries(SDKlib PUBLIC evt-tls) endif() if(NOT HAVE_GLOB_H AND NOT WIN32) target_link_libraries(SDKlib PUBLIC glob) endif() target_link_libraries(SDKlib PUBLIC http_parser) target_link_libraries(SDKlib PUBLIC utf8proc) target_link_libraries(SDKlib PUBLIC zxcvbn-c) if(VCPKG_ROOT) find_package(cryptopp CONFIG REQUIRED) target_link_libraries(SDKlib PUBLIC cryptopp::cryptopp) # TODO: Private for SDK core find_package(unofficial-sodium REQUIRED) if(WIN32) target_link_libraries(SDKlib PUBLIC unofficial-sodium::sodium) # TODO: Private for SDK core else() target_link_libraries(SDKlib PRIVATE unofficial-sodium::sodium) endif() if(IOS) # IOS requires the system sqlite3 libray find_package(SQLite3 REQUIRED) target_link_libraries(SDKlib PRIVATE SQLite::SQLite3) else() find_package(unofficial-sqlite3 REQUIRED) target_link_libraries(SDKlib PRIVATE unofficial::sqlite3::sqlite3) endif() find_package(CURL REQUIRED) target_link_libraries(SDKlib PRIVATE CURL::libcurl) if(USE_OPENSSL) if(USE_WEBRTC) # Defined in MEGAchat. # find_package(OpenSSL) works for BoringSSL but it does not load the flags from the .pc files. find_package() has # its own way to prepare the OpenSSL needed flags and configurations. # Linking gfxworker when using BoringSSL from WebRTC requires the -lX11 flag for WebRTC, despite we only need # the BoringSSL symbols. # Using pkg-config in that specific case, honors the "OpenSSL" .pc files used as a way to impersonate OpenSSL/BoringSSL # using the one contained in WebRTC library. find_package(PkgConfig REQUIRED) pkg_check_modules(openssl REQUIRED IMPORTED_TARGET openssl) target_link_libraries(SDKlib PRIVATE PkgConfig::openssl) else() find_package(OpenSSL REQUIRED) target_link_libraries(SDKlib PRIVATE OpenSSL::SSL OpenSSL::Crypto) endif() endif() if(ENABLE_MEDIA_FILE_METADATA) # MediaInfo is not setting libzen dependency correctly. Preload it. find_package(ZenLib CONFIG REQUIRED) target_link_libraries(SDKlib PRIVATE zen) find_package(MediaInfoLib REQUIRED) target_link_libraries(SDKlib PRIVATE mediainfo) set(USE_MEDIAINFO 1) endif() if(USE_FREEIMAGE) find_package(freeimage REQUIRED) target_link_libraries(SDKlib PRIVATE freeimage::FreeImage) endif() if(USE_FFMPEG) find_package(FFMPEG REQUIRED) target_include_directories(SDKlib PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_directories(SDKlib PRIVATE ${FFMPEG_LIBRARY_DIRS}) target_link_libraries(SDKlib PRIVATE ${FFMPEG_LIBRARIES}) set(HAVE_FFMPEG 1) endif() if(USE_LIBUV) find_package(libuv REQUIRED) target_link_libraries(SDKlib PRIVATE $,libuv::uv_a,libuv::uv>) set(HAVE_LIBUV 1) endif() if(USE_PDFIUM) find_package(pdfium REQUIRED) target_link_libraries(SDKlib PRIVATE PDFIUM::pdfium) set(HAVE_PDFIUM 1) endif() find_package(ICU COMPONENTS i18n uc data REQUIRED) target_link_libraries(SDKlib PRIVATE ICU::i18n ICU::uc ICU::data) if(USE_READLINE) find_package(Readline-unix REQUIRED) target_link_libraries(SDKlib PRIVATE Readline::Readline) # Curses is needed by Readline set(CURSES_NEED_NCURSES TRUE) find_package(Curses REQUIRED) target_include_directories(SDKlib PRIVATE ${CURSES_INCLUDE_DIRS}) target_compile_options(SDKlib PRIVATE ${CURSES_CFLAGS}) target_link_libraries(SDKlib PRIVATE ${CURSES_LIBRARIES}) else() set(NO_READLINE 1) endif() else() # No VCPKG usage. Use pkg-config find_package(PkgConfig REQUIRED) # For libraries loaded using pkg-config pkg_check_modules(cryptopp REQUIRED IMPORTED_TARGET libcrypto++) target_link_libraries(SDKlib PUBLIC PkgConfig::cryptopp) # TODO: Private for SDK core pkg_check_modules(sodium REQUIRED IMPORTED_TARGET libsodium) target_link_libraries(SDKlib PRIVATE PkgConfig::sodium) pkg_check_modules(sqlite3 REQUIRED IMPORTED_TARGET sqlite3) target_link_libraries(SDKlib PRIVATE PkgConfig::sqlite3) pkg_check_modules(curl REQUIRED IMPORTED_TARGET libcurl) target_link_libraries(SDKlib PRIVATE PkgConfig::curl) find_package(ICU COMPONENTS uc data REQUIRED) target_link_libraries(SDKlib PRIVATE ICU::uc ICU::data) if(USE_OPENSSL) find_package(OpenSSL REQUIRED) target_link_libraries(SDKlib PRIVATE OpenSSL::SSL OpenSSL::Crypto) endif() if(ENABLE_MEDIA_FILE_METADATA) pkg_check_modules(mediainfo REQUIRED IMPORTED_TARGET libmediainfo) target_link_libraries(SDKlib PRIVATE PkgConfig::mediainfo) set(USE_MEDIAINFO 1) endif() if(USE_FREEIMAGE) # FreeImage has no pkg-config file. Use out own FindFreeImage.cmake to find the library. find_package(FreeImage REQUIRED) target_link_libraries(SDKlib PRIVATE FreeImage::FreeImage) endif() if(USE_FFMPEG) pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavformat libavutil libavcodec libswscale libswresample) target_link_libraries(SDKlib PRIVATE PkgConfig::ffmpeg) set(HAVE_FFMPEG 1) endif() if(USE_LIBUV) pkg_check_modules(uv REQUIRED IMPORTED_TARGET libuv) target_link_libraries(SDKlib PRIVATE PkgConfig::uv) set(HAVE_LIBUV 1) endif() if(USE_PDFIUM) pkg_check_modules(pdfium REQUIRED IMPORTED_TARGET pdfium) target_link_libraries(SDKlib PRIVATE PkgConfig::pdfium) set(HAVE_PDFIUM 1) endif() if(USE_READLINE) pkg_check_modules(readline REQUIRED IMPORTED_TARGET readline) target_link_libraries(SDKlib PRIVATE PkgConfig::readline) else() set(NO_READLINE 1) endif() endif() endmacro() sdk-10.11.0/cmake/modules/sdklib_options.cmake000066400000000000000000000054221516266226600212270ustar00rootroot00000000000000if (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android") if (CMAKE_SYSTEM_NAME STREQUAL "Android") option(ENABLE_SYNC "Turns on sync functionality" ON) option(ENABLE_JAVA_BINDINGS "Enable the target to build the Java Bindings" ON) option(ENABLE_SDKLIB_WERROR "Disable warnings as errors" OFF) else() option(ENABLE_SYNC "Turns on sync functionality" OFF) option(ENABLE_JAVA_BINDINGS "Enable the target to build the Java Bindings" OFF) endif() option(ENABLE_CHAT "Turns on chat management functionality" ON) option(USE_FREEIMAGE "Used to create previews/thumbnails for photos/pictures" OFF) option(USE_FFMPEG "Used to create previews/thumbnails for video files" OFF) option(USE_LIBUV "Includes the library and turns on internal web and ftp server functionality" ON) option(USE_PDFIUM "Used to create previews/thumbnails for PDF files" OFF) option(USE_READLINE "Use the readline library for the console" OFF) else() option(ENABLE_SYNC "Turns on sync functionality" ON) option(ENABLE_CHAT "Turns on chat management functionality" OFF) option(USE_FREEIMAGE "Used to create previews/thumbnails for photos/pictures" ON) option(USE_FFMPEG "Used to create previews/thumbnails for video files" ON) option(USE_LIBUV "Includes the library and turns on internal web and ftp server functionality" OFF) option(USE_PDFIUM "Used to create previews/thumbnails for PDF files" ON) if (WIN32) option(USE_READLINE "Use the readline library for the console" OFF) else() option(USE_READLINE "Use the readline library for the console" ON) endif() option(ENABLE_JAVA_BINDINGS "Enable the target to build the Java Bindings" OFF) endif() if(APPLE) option(USE_OPENSSL "Use the OpenSSL library or a compatible one" OFF) else() option(USE_OPENSSL "Use the OpenSSL library or a compatible one" ON) endif() option(ENABLE_MEDIA_FILE_METADATA "Enables the detection of multimedia metadata and sets them as node attributes" OFF) option(ENABLE_LOG_PERFORMANCE "Faster log message generation" OFF) option(ENABLE_DRIVE_NOTIFICATIONS "Allows to monitor (external) drives being [dis]connected to the computer" OFF) option(ENABLE_QT_BINDINGS "Enable the target to build the Qt Bindings" OFF) if (USE_FREEIMAGE) option(ENABLE_ISOLATED_GFX "Turns on isolated GFX processor" ON) else() option(ENABLE_ISOLATED_GFX "Turns on isolated GFX processor" OFF) endif() option(ENABLE_ASAN "Enable address sanitizer" OFF) option(ENABLE_UBSAN "Enable undefined behavior sanitizer" OFF) option(ENABLE_TSAN "Enable thread sanitizer" OFF) option(ENABLE_C_ARES_BACKEND "Enable c-ares backend as the DNS resolver for curl" OFF) option(ENABLE_SDKLIB_ANDROID_DYNAMIC_LIBRARY "It builds a final dynamic library for Android." OFF) sdk-10.11.0/cmake/modules/sdklib_target.cmake000066400000000000000000000311361516266226600210230ustar00rootroot00000000000000## SDKlib target ## add_library(SDKlib) add_library(MEGA::SDKlib ALIAS SDKlib) set(SDKLIB_PUB_HEADERS include/megaapi.h ) set(SDKLIB_HEADERS include/mega.h include/megaapi_impl.h include/megautils.h include/mega/transferslot.h include/mega/scoped_helpers.h include/mega/traits.h include/mega/scoped_timer.h include/mega/canceller.h include/mega/command.h include/mega/thread.h include/mega/json.h include/mega/base64.h include/mega/gfx.h include/mega/proxy.h include/mega/crypto/sodium.h include/mega/crypto/cryptopp.h include/mega/http.h include/mega/useralerts.h include/mega/pendingcontactrequest.h include/mega/textchat.h include/mega/megaapp.h include/mega/console.h include/mega/user.h include/mega/db.h include/mega/megaclient.h include/mega/autocomplete.h include/mega/serialize64.h include/mega/nodemanager.h include/mega/setandelement.h include/mega/testhooks.h include/mega/share.h include/mega/gfx/GfxProcCG.h include/mega/gfx/freeimage.h include/mega/gfx/gfx_pdfium.h include/mega/gfx/external.h include/mega/pubkeyaction.h include/mega/waiter.h include/mega/db/sqlite.h include/mega/types.h include/mega/filefingerprint.h include/mega/localpath.h include/mega/filesystem.h include/mega/backofftimer.h include/mega/raid.h include/mega/raidproxy.h include/mega/logging.h include/mega/file.h include/mega/sync.h include/mega/syncfilter.h include/mega/syncinternals/mac_computation_state.h include/mega/syncinternals/syncinternals_logging.h include/mega/syncinternals/syncinternals.h include/mega/syncinternals/synciuploadthrottlingmanager.h include/mega/syncinternals/syncuploadthrottlingfile.h include/mega/syncinternals/syncuploadthrottlingmanager.h include/mega/heartbeats.h include/mega/utils.h include/mega/hashcash.h include/mega/utils_optional.h include/mega/account.h include/mega/transfer.h include/mega/transferstats.h include/mega/totp.h include/mega/treeproc.h include/mega/arguments.h include/mega/attrmap.h include/mega/sharenodekeys.h include/mega/request.h include/mega/fileattributefetch.h include/mega/version.h include/mega/node.h include/mega/mediafileattribute.h include/mega/process.h include/mega/name_collision.h include/mega/name_id.h include/mega/pwm_file_parser.h include/mega/tlv.h include/mega/dns_lookup_pseudomessage.h include/mega/network_connectivity_test.h include/mega/network_connectivity_test_helpers.h include/mega/udp_socket.h include/mega/udp_socket_tester.h include/mega/user_attribute.h include/mega/user_attribute_definition.h include/mega/user_attribute_manager.h include/mega/user_attribute_types.h include/mega/log_level.h include/mega/log_level_forward.h include/mega/overloaded.h include/mega/banner.h include/mega/auto_file_handle.h # megaapi_impl related headers include/impl/share.h ) set(SDKLIB_SOURCES src/megaapi.cpp src/megaapi_impl.cpp src/megaapi_impl_sync.cpp src/arguments.cpp src/attrmap.cpp src/autocomplete.cpp src/backofftimer.cpp src/base64.cpp src/canceller.cpp src/command.cpp src/commands.cpp src/db.cpp src/file.cpp src/fileattributefetch.cpp src/filefingerprint.cpp src/filesystem.cpp src/gfx.cpp src/gfx/external.cpp src/gfx/gfx_pdfium.cpp src/hashcash.cpp src/http.cpp src/json.cpp src/logging.cpp src/localpath.cpp src/mediafileattribute.cpp src/megaclient.cpp src/node.cpp src/pendingcontactrequest.cpp src/textchat.cpp src/proxy.cpp src/pubkeyaction.cpp src/raid.cpp src/raidproxy.cpp src/recent_actions.cpp src/request.cpp src/serialize64.cpp src/nodemanager.cpp src/setandelement.cpp src/share.cpp src/sharenodekeys.cpp src/sync.cpp src/syncfilter.cpp src/syncinternals/syncinternals.cpp src/syncinternals/syncuploadthrottlingfile.cpp src/syncinternals/syncuploadthrottlingmanager.cpp src/heartbeats.cpp src/testhooks.cpp src/transfer.cpp src/transferslot.cpp src/transferstats.cpp src/treeproc.cpp src/totp.cpp src/user.cpp src/useralerts.cpp src/utils.cpp src/waiterbase.cpp src/crypto/cryptopp.cpp src/crypto/sodium.cpp src/db/sqlite.cpp src/process.cpp src/name_collision.cpp src/pwm_file_parser.cpp src/tlv.cpp src/dns_lookup_pseudomessage.cpp src/network_connectivity_test.cpp src/udp_socket.cpp src/udp_socket_tester.cpp src/user_attribute.cpp src/user_attribute_definition.cpp src/user_attribute_manager.cpp src/megautils.cpp src/log_level.cpp # megaapi_impl related sources src/impl/share.cpp ) target_sources(SDKlib PRIVATE ${SDKLIB_HEADERS} ${SDKLIB_SOURCES} ${SDKLIB_PUB_HEADERS} ${PROJECT_BINARY_DIR}/mega/config.h # Generated config.h file ) # Files by platform and/or feature # Files should appear only once. # If the FLAG is not true for a file, it will be added as non-buildable source despite then the file is added again as a buildable one. target_sources_conditional(SDKlib FLAG WIN32 PRIVATE include/mega/win32/megafs.h include/mega/win32/megaconsolewaiter.h include/mega/win32/megaconsole.h include/mega/win32/megawaiter.h include/mega/win32/megasys.h include/mega/win32/meganet.h # it includes posix/meganet.h src/win32/fs.cpp src/win32/consolewaiter.cpp src/win32/console.cpp src/win32/waiter.cpp src/win32/net.cpp # it includes posix/net.cpp ) target_sources_conditional(SDKlib FLAG WIN32 AND ENABLE_ISOLATED_GFX PRIVATE include/mega/win32/gfx/worker/comms.h include/mega/win32/gfx/worker/comms_client.h src/win32/gfx/worker/comms.cpp src/win32/gfx/worker/comms_client.cpp ) target_sources_conditional(SDKlib FLAG UNIX AND ENABLE_ISOLATED_GFX PRIVATE include/mega/posix/gfx/worker/comms.h include/mega/posix/gfx/worker/comms_client.h include/mega/posix/gfx/worker/socket_utils.h src/posix/gfx/worker/comms.cpp src/posix/gfx/worker/comms_client.cpp src/posix/gfx/worker/socket_utils.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_ISOLATED_GFX PRIVATE include/mega/gfx/isolatedprocess.h include/mega/gfx/worker/tasks.h include/mega/gfx/worker/commands.h include/mega/gfx/worker/comms.h include/mega/gfx/worker/command_serializer.h include/mega/gfx/worker/client.h include/mega/gfx/worker/comms_client_common.h include/mega/gfx/worker/comms_client.h src/gfx/isolatedprocess.cpp src/gfx/worker/client.cpp src/gfx/worker/commands.cpp src/gfx/worker/command_serializer.cpp ) target_sources_conditional(SDKlib FLAG UNIX PRIVATE include/mega/posix/megawaiter.h include/mega/thread/posixthread.h include/mega/posix/megaconsole.h include/mega/posix/megafs.h include/mega/posix/megaconsolewaiter.h include/mega/posix/meganet.h include/mega/posix/megasys.h src/posix/waiter.cpp src/thread/posixthread.cpp src/posix/console.cpp src/posix/fs.cpp src/posix/consolewaiter.cpp src/posix/net.cpp ) target_sources_conditional(SDKlib FLAG APPLE PRIVATE include/mega/osx/osxutils.h include/mega/osx/megafs.h src/osx/osxutils.mm src/osx/fs.cpp ) target_sources_conditional(SDKlib FLAG IOS PRIVATE src/gfx/GfxProcCG.mm ) target_sources_conditional(SDKlib FLAG ANDROID PRIVATE include/mega/android/androidFileSystem.h include/mega/android/megafs.h src/android/androidFileSystem.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_DRIVE_NOTIFICATIONS PRIVATE include/mega/drivenotify.h src/drivenotify.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_DRIVE_NOTIFICATIONS AND WIN32 PRIVATE include/mega/win32/drivenotifywin.h src/win32/drivenotifywin.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_DRIVE_NOTIFICATIONS AND APPLE PRIVATE include/mega/osx/drivenotifyosx.h src/osx/drivenotifyosx.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_DRIVE_NOTIFICATIONS AND NOT (APPLE OR WIN32) PRIVATE include/mega/posix/drivenotifyposix.h src/posix/drivenotifyposix.cpp ) target_sources_conditional(SDKlib FLAG USE_FREEIMAGE PRIVATE src/gfx/freeimage.cpp ) target_sources_conditional(SDKlib FLAG USE_CPPTHREAD PRIVATE include/mega/thread/cppthread.h src/thread/cppthread.cpp ) # Include directories target_include_directories(SDKlib PUBLIC $ # For the top level projects. $ # For the external projects. # PRIVATE # TODO: Private for SDK core $ $<$:${CMAKE_CURRENT_SOURCE_DIR}/include/mega/osx> $<$:${CMAKE_CURRENT_SOURCE_DIR}/include/mega/win32> $<$:${CMAKE_CURRENT_SOURCE_DIR}/include/mega/android> # Before posix. $<$:${CMAKE_CURRENT_SOURCE_DIR}/include/mega/posix> ) if (WIN32) target_compile_definitions(SDKlib PRIVATE _CRT_SECURE_NO_WARNINGS # warning in ccronexpr $<$:USE_CPPTHREAD> UNICODE # Disable warning C4996: 'inet_ntoa': Use inet_ntop() or InetNtop() instead or define # _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings _WINSOCK_DEPRECATED_NO_WARNINGS ) # Increase number of sections in .obj files. (megaapi_impl.cpp, Sync_test.cpp, ...) target_compile_options(SDKlib PRIVATE /bigobj) endif() target_compile_definitions(SDKlib PUBLIC $<$:ENABLE_LOG_PERFORMANCE> $<$:ENABLE_CHAT> $<$:ENABLE_SYNC> $<$:HAVE_LIBUV> $<$:USE_IOS> $<$:USE_POLL> $<$:USE_INOTIFY> $<$:HAVE_SDK_CONFIG_H> ) set_target_properties(SDKlib PROPERTIES VERSION ${PROJECT_VERSION} DEBUG_POSTFIX "d" ) if(ENABLE_JAVA_BINDINGS) set_target_properties(SDKlib PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() include(sdklib_target_common) include(sdklib_target_file_service) ## Load and link needed libraries for the SDKlib target ## # Load 3rd parties load_sdklib_libraries() # System libraries if((NOT (WIN32 OR APPLE OR ANDROID)) AND CMAKE_CXX_STANDARD LESS_EQUAL 17) # Needed for std::experimental::filesystem # Needed for c++17 and std::filesystem for some compilers. Not needed starting in gcc9, but harmless. target_link_libraries(SDKlib PRIVATE stdc++fs) endif() if(ENABLE_DRIVE_NOTIFICATIONS) if(WIN32) target_link_libraries(SDKlib PRIVATE wbemuuid) elseif(NOT APPLE) # Linux target_link_libraries(SDKlib PRIVATE udev) endif() set(USE_DRIVE_NOTIFICATIONS 1) endif() if(WIN32) target_link_libraries(SDKlib PRIVATE ws2_32 winhttp Shlwapi Secur32.lib crypt32.lib Wldap32.lib $<$:Kernel32.lib Iphlpapi.lib Userenv.lib Psapi.lib> $<$:Mfplat.lib mfuuid.lib strmiids.lib> $<$:wbemuuid> ) else() if(APPLE) target_link_libraries(SDKlib PRIVATE "-framework CoreServices" "-framework Cocoa" "-framework SystemConfiguration" "-framework DiskArbitration" "-framework CoreFoundation" ) endif() endif() ## Adjust compilation flags for warnings and errors ## target_platform_compile_options( TARGET SDKlib WINDOWS /W4 UNIX $<$:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET SDKlib WINDOWS /WX UNIX $<$: -Werror -Wno-error=deprecated-declarations> # Kept as a warning, do not promote to error. ) endif() ## Create config files ## configure_file( cmake/config.h.in ${PROJECT_BINARY_DIR}/mega/config.h ) configure_package_config_file( cmake/modules/Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/sdklibConfig.cmake INSTALL_DESTINATION cmake ) set(SDKLIB_NAME "SDKlib") if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(SDKLIB_NAME "${SDKLIB_NAME}d") endif() configure_file( cmake/modules/sdklib.pc.in ${CMAKE_CURRENT_BINARY_DIR}/sdklib.pc @ONLY ) sdk-10.11.0/cmake/modules/sdklib_target_common.cmake000066400000000000000000000146241516266226600223760ustar00rootroot00000000000000add_library(CommonHeaderPaths INTERFACE) add_library(MEGA::CommonHeaderPaths ALIAS CommonHeaderPaths) target_include_directories(CommonHeaderPaths INTERFACE $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/common/platform/posix> $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/common/platform/windows> ) target_link_libraries(SDKlib PRIVATE CommonHeaderPaths) target_sources(SDKlib PRIVATE include/mega/common/activity_monitor.h include/mega/common/activity_monitor_forward.h include/mega/common/badge.h include/mega/common/badge_forward.h include/mega/common/client.h include/mega/common/client_adapter.h include/mega/common/client_callbacks.h include/mega/common/client_forward.h include/mega/common/database.h include/mega/common/database_builder.h include/mega/common/database_forward.h include/mega/common/database_utilities.h include/mega/common/date_time.h include/mega/common/date_time_forward.h include/mega/common/deciseconds.h include/mega/common/directory.h include/mega/common/error_or.h include/mega/common/error_or_forward.h include/mega/common/expected.h include/mega/common/expected_forward.h include/mega/common/lock.h include/mega/common/lock_forward.h include/mega/common/lockable.h include/mega/common/lockable_forward.h include/mega/common/logger.h include/mega/common/logger_forward.h include/mega/common/logging.h include/mega/common/node_event.h include/mega/common/node_event_forward.h include/mega/common/node_event_observer.h include/mega/common/node_event_observer_forward.h include/mega/common/node_event_queue.h include/mega/common/node_event_queue_forward.h include/mega/common/node_event_type.h include/mega/common/node_event_type_forward.h include/mega/common/node_info.h include/mega/common/node_info_forward.h include/mega/common/node_key_data.h include/mega/common/node_key_data_forward.h include/mega/common/normalized_path.h include/mega/common/normalized_path_forward.h include/mega/common/partial_download.h include/mega/common/partial_download_callback.h include/mega/common/partial_download_callback_forward.h include/mega/common/partial_download_forward.h include/mega/common/pending_callbacks.h include/mega/common/query.h include/mega/common/query_forward.h include/mega/common/scoped_query.h include/mega/common/scoped_query_forward.h include/mega/common/serialization_traits.h include/mega/common/serialization_traits_forward.h include/mega/common/shared_mutex.h include/mega/common/shared_mutex_forward.h include/mega/common/status_flag.h include/mega/common/subsystem_logger.h include/mega/common/task_executor.h include/mega/common/task_executor_flags.h include/mega/common/task_executor_flags_forward.h include/mega/common/task_executor_forward.h include/mega/common/task_queue.h include/mega/common/task_queue_forward.h include/mega/common/transaction.h include/mega/common/transaction_forward.h include/mega/common/type_traits.h include/mega/common/unexpected.h include/mega/common/unexpected_forward.h include/mega/common/upload.h include/mega/common/upload_callbacks.h include/mega/common/upload_forward.h include/mega/common/utility.h ) target_sources_conditional(SDKlib FLAG UNIX PRIVATE src/common/platform/posix/mega/common/platform/date_time.h src/common/platform/posix/mega/common/platform/folder_locker.h ) target_sources_conditional(SDKlib FLAG WIN32 PRIVATE src/common/platform/windows/mega/common/platform/date_time.h src/common/platform/windows/mega/common/platform/folder_locker.h src/common/platform/windows/mega/common/platform/folder_locker.cpp ) target_sources(SDKlib PRIVATE src/common/activity_monitor.cpp src/common/client.cpp src/common/client_adapter.cpp src/common/database.cpp src/common/database_builder.cpp src/common/date_time.cpp src/common/directory.cpp src/common/logger.cpp src/common/node_event_type.cpp src/common/normalized_path.cpp src/common/pending_callbacks.cpp src/common/query.cpp src/common/scoped_query.cpp src/common/shared_mutex.cpp src/common/subsystem_logger.cpp src/common/task_executor.cpp src/common/task_queue.cpp src/common/transaction.cpp src/common/upload.cpp src/common/utility.cpp ) target_sources_conditional(SDKlib FLAG ENABLE_SYNC PRIVATE src/common/client_adapter_with_sync.cpp ) target_sources_conditional(SDKlib FLAG NOT ENABLE_SYNC PRIVATE src/common/client_adapter_without_sync.cpp ) sdk-10.11.0/cmake/modules/sdklib_target_file_service.cmake000066400000000000000000000262541516266226600235470ustar00rootroot00000000000000add_library(FileServiceHeaderPaths INTERFACE) add_library(MEGA::FileServiceHeaderPaths ALIAS FileServiceHeaderPaths) target_include_directories(FileServiceHeaderPaths INTERFACE src/file_service ) target_link_libraries(SDKlib PRIVATE FileServiceHeaderPaths) target_sources(SDKlib PRIVATE include/mega/file_service/file.h include/mega/file_service/file_callbacks.h include/mega/file_service/file_context_badge_forward.h include/mega/file_service/file_context_forward.h include/mega/file_service/file_context_pointer.h include/mega/file_service/file_event.h include/mega/file_service/file_event_emitter_forward.h include/mega/file_service/file_event_forward.h include/mega/file_service/file_event_observer.h include/mega/file_service/file_event_observer_id.h include/mega/file_service/file_event_observer_result.h include/mega/file_service/file_event_observer_result_forward.h include/mega/file_service/file_flush_event.h include/mega/file_service/file_flush_event_forward.h include/mega/file_service/file_forward.h include/mega/file_service/file_id.h include/mega/file_service/file_id_forward.h include/mega/file_service/file_info.h include/mega/file_service/file_info_context_forward.h include/mega/file_service/file_info_context_pointer.h include/mega/file_service/file_info_forward.h include/mega/file_service/file_location.h include/mega/file_service/file_location_forward.h include/mega/file_service/file_move_event.h include/mega/file_service/file_move_event_forward.h include/mega/file_service/file_range.h include/mega/file_service/file_range_forward.h include/mega/file_service/file_range_vector.h include/mega/file_service/file_read_result.h include/mega/file_service/file_remove_event.h include/mega/file_service/file_remove_event_forward.h include/mega/file_service/file_result.h include/mega/file_service/file_result_forward.h include/mega/file_service/file_result_or.h include/mega/file_service/file_result_or_forward.h include/mega/file_service/file_service.h include/mega/file_service/file_service_callbacks.h include/mega/file_service/file_service_context_badge.h include/mega/file_service/file_service_context_badge_forward.h include/mega/file_service/file_service_context_forward.h include/mega/file_service/file_service_context_pointer.h include/mega/file_service/file_service_forward.h include/mega/file_service/file_service_options.h include/mega/file_service/file_service_options_forward.h include/mega/file_service/file_service_result.h include/mega/file_service/file_service_result_forward.h include/mega/file_service/file_service_result_or.h include/mega/file_service/file_service_result_or_forward.h include/mega/file_service/file_touch_event.h include/mega/file_service/file_touch_event_forward.h include/mega/file_service/file_truncate_event.h include/mega/file_service/file_truncate_event_forward.h include/mega/file_service/file_write_event.h include/mega/file_service/file_write_event_forward.h include/mega/file_service/file_write_result.h include/mega/file_service/sink.h include/mega/file_service/sink_forward.h include/mega/file_service/source.h include/mega/file_service/source_forward.h ) target_sources(SDKlib PRIVATE src/file_service/mega/file_service/avl_tree.h src/file_service/mega/file_service/avl_tree_forward.h src/file_service/mega/file_service/avl_tree_iterator.h src/file_service/mega/file_service/avl_tree_node.h src/file_service/mega/file_service/avl_tree_traits.h src/file_service/mega/file_service/buffer.h src/file_service/mega/file_service/buffer_forward.h src/file_service/mega/file_service/buffer_pointer.h src/file_service/mega/file_service/database_builder.h src/file_service/mega/file_service/displaced_buffer.h src/file_service/mega/file_service/displaced_buffer_forward.h src/file_service/mega/file_service/displaced_buffer_pointer.h src/file_service/mega/file_service/file_access.h src/file_service/mega/file_service/file_append_request.h src/file_service/mega/file_service/file_append_request_forward.h src/file_service/mega/file_service/file_buffer.h src/file_service/mega/file_service/file_buffer_forward.h src/file_service/mega/file_service/file_buffer_pointer.h src/file_service/mega/file_service/file_context.h src/file_service/mega/file_service/file_context_badge.h src/file_service/mega/file_service/file_event_emitter.h src/file_service/mega/file_service/file_event_vector.h src/file_service/mega/file_service/file_fetch_request.h src/file_service/mega/file_service/file_fetch_request_forward.h src/file_service/mega/file_service/file_flush_request.h src/file_service/mega/file_service/file_flush_request_forward.h src/file_service/mega/file_service/file_id_vector.h src/file_service/mega/file_service/file_info_context.h src/file_service/mega/file_service/file_info_context_badge.h src/file_service/mega/file_service/file_info_context_badge_forward.h src/file_service/mega/file_service/file_range_context.h src/file_service/mega/file_service/file_range_context_forward.h src/file_service/mega/file_service/file_range_context_manager.h src/file_service/mega/file_service/file_range_context_manager_forward.h src/file_service/mega/file_service/file_range_context_pointer.h src/file_service/mega/file_service/file_range_context_pointer_map.h src/file_service/mega/file_service/file_range_map.h src/file_service/mega/file_service/file_range_set.h src/file_service/mega/file_service/file_range_traits.h src/file_service/mega/file_service/file_range_tree.h src/file_service/mega/file_service/file_range_tree_node.h src/file_service/mega/file_service/file_range_tree_traits.h src/file_service/mega/file_service/file_read_request.h src/file_service/mega/file_service/file_read_request_forward.h src/file_service/mega/file_service/file_read_request_set.h src/file_service/mega/file_service/file_read_write_state.h src/file_service/mega/file_service/file_reclaim_request.h src/file_service/mega/file_service/file_reclaim_request_forward.h src/file_service/mega/file_service/file_remove_request.h src/file_service/mega/file_service/file_remove_request_forward.h src/file_service/mega/file_service/file_request.h src/file_service/mega/file_service/file_request_list.h src/file_service/mega/file_service/file_request_tags.h src/file_service/mega/file_service/file_service_context.h src/file_service/mega/file_service/file_service_queries.h src/file_service/mega/file_service/file_size_info.h src/file_service/mega/file_service/file_size_info_forward.h src/file_service/mega/file_service/file_storage.h src/file_service/mega/file_service/file_touch_request.h src/file_service/mega/file_service/file_touch_request_forward.h src/file_service/mega/file_service/file_truncate_request.h src/file_service/mega/file_service/file_truncate_request_forward.h src/file_service/mega/file_service/file_write_request.h src/file_service/mega/file_service/file_write_request_forward.h src/file_service/mega/file_service/from_file_id_map.h src/file_service/mega/file_service/logger.h src/file_service/mega/file_service/logging.h src/file_service/mega/file_service/memory_buffer.h src/file_service/mega/file_service/sparse_file_buffer.h src/file_service/mega/file_service/type_traits.h ) target_sources(SDKlib PRIVATE src/file_service/buffer.cpp src/file_service/database_builder.cpp src/file_service/displaced_buffer.cpp src/file_service/file.cpp src/file_service/file_access.cpp src/file_service/file_buffer.cpp src/file_service/file_context.cpp src/file_service/file_event_emitter.cpp src/file_service/file_id.cpp src/file_service/file_info.cpp src/file_service/file_info_context.cpp src/file_service/file_range.cpp src/file_service/file_range_context.cpp src/file_service/file_read_request_set.cpp src/file_service/file_read_write_state.cpp src/file_service/file_result.cpp src/file_service/file_service.cpp src/file_service/file_service_context.cpp src/file_service/file_service_queries.cpp src/file_service/file_service_result.cpp src/file_service/file_storage.cpp src/file_service/logger.cpp src/file_service/memory_buffer.cpp src/file_service/sparse_file_buffer.cpp ) sdk-10.11.0/cmake/modules/sdklib_variables.cmake000066400000000000000000000014541516266226600215050ustar00rootroot00000000000000# No configurable values. set(USE_SQLITE 1) set(USE_SODIUM 1) set(USE_CRYPTOPP 1) if (NOT WIN32) set(USE_PTHREAD 1) set(USE_CPPTHREAD 0) else() set(USE_CPPTHREAD 1) set(CMAKE_GENERATOR_TOOLSET "v142") endif() include(detect_host_architecture) # Configure CMAKE_OSX_DEPLOYMENT_TARGET if not already set include(set_osx_deployment_target) set_osx_deployment_target( ARM64 "11.1" x86_64 "10.15" ) if (CMAKE_SYSTEM_NAME STREQUAL "Android") # Support for 16 KB devices. Needed if the NDK version is < r28 set(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES ON) # Variable for the Android toolchain. if (NOT ANDROID_PLATFORM) message(WARNING "Android API level not set. It defaults to 28.") set(ANDROID_PLATFORM 28) # Variable for the Android toolchain. endif() endif() sdk-10.11.0/cmake/modules/set_osx_deployment_target.cmake000066400000000000000000000031261516266226600234750ustar00rootroot00000000000000# It configures the default OSX deployment target (CMAKE_OSX_DEPLOYMENT_TARGET) # If the value is already set it will not be changed, to allow users to override # the default ones. # # This function should be called before project(). # # set_osx_deployment_target( # ARM64 # x86_64 # ) # function(set_osx_deployment_target) set(options "") set(oneValueArgs ARM64 x86_64) set(multiValueArgs "") cmake_parse_arguments("set_osx_deployment_target" "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if(NOT CMAKE_OSX_DEPLOYMENT_TARGET) # CMAKE_OSX_DEPLOYMENT_TARGET also affects IOS builds if(CMAKE_HOST_APPLE AND NOT (CMAKE_SYSTEM_NAME STREQUAL "iOS")) # Minimum deployment target may differ if we are building for intel or arm64 targets # CMAKE_SYSTEM_PROCESSOR and CMAKE_HOST_SYSTEM_PROCESSOR are only available after project() execute_process( COMMAND uname -m OUTPUT_VARIABLE HOST_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE ) if(CMAKE_OSX_ARCHITECTURES MATCHES "arm64" OR (NOT CMAKE_OSX_ARCHITECTURES AND HOST_ARCHITECTURE STREQUAL "arm64")) set(CMAKE_OSX_DEPLOYMENT_TARGET ${set_osx_deployment_target_ARM64} CACHE STRING "Minimum OS X deployment version") else() set(CMAKE_OSX_DEPLOYMENT_TARGET ${set_osx_deployment_target_x86_64} CACHE STRING "Minimum OS X deployment version") endif() unset(HOST_ARCHITECTURE) endif() endif() endfunction() sdk-10.11.0/cmake/modules/target_platform_compile_options.cmake000066400000000000000000000027651516266226600246700ustar00rootroot00000000000000# It allows to add different flags for a specific target depending on the OS. # Since order matters to apply the flags, note that UNIX is applied before APPLE. # Values are passed to target_compile_options(), so any format accepted by target_compile_options() should work. # # target_platform_compile_options( # TARGET # WINDOWS # UNIX # APPLE # ) # # Example: # target_platform_compile_options( # TARGET SDKlib # WINDOWS /WX # UNIX -Wall $<$:-Werror> # APPLE $<$: -Wno-sign-conversion> # ) # function(target_platform_compile_options) set(options "") set(oneValueArgs TARGET) set(multiValueArgs WINDOWS UNIX APPLE) cmake_parse_arguments("target_platform_compile_options" "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) if(WIN32 AND target_platform_compile_options_WINDOWS) target_compile_options(${target_platform_compile_options_TARGET} PRIVATE ${target_platform_compile_options_WINDOWS}) endif() if(UNIX AND target_platform_compile_options_UNIX) target_compile_options(${target_platform_compile_options_TARGET} PRIVATE ${target_platform_compile_options_UNIX}) endif() if(APPLE AND target_platform_compile_options_APPLE) target_compile_options(${target_platform_compile_options_TARGET} PRIVATE ${target_platform_compile_options_APPLE}) endif() endfunction() sdk-10.11.0/cmake/modules/target_sources_conditional.cmake000066400000000000000000000043571516266226600236260ustar00rootroot00000000000000# It allows to add sources which will be built only if the FLAG is true. Otherwise, the # files will be visible in the IDE but will not be part of the target build. # Useful for sources for different platforms, configuration files, .md files, ... # Except for the "FLAG " field, syntax is similar to target_sources(). # If the files may be processed by the Qt preprocessors add the QT_AWARE flag, so the # files are also excluded from AUTMOC, AUTOUIC and AUTORCC when the FLAG is false. # # target_sources_conditional( FLAG [QT_AWARE] # [items1...] # [ [items2...] ...]) # # Example: # target_sources_conditional(SDKlib # FLAG ENABLE_FEATURE AND NOT (APPLE OR WIN32) # QT_AWARE # PRIVATE # first_source.h # first_source.cpp # MainWindow.ui # PUBLIC # whatever.h # ) function(target_sources_conditional) set(options QT_AWARE) set(oneValueArgs "") set(multiValueArgs FLAG INTERFACE PUBLIC PRIVATE) set(TARGET ${ARGV0}) cmake_parse_arguments(PARSE_ARGV 1 "target_sources_conditional" "${options}" "${oneValueArgs}" "${multiValueArgs}" ) target_sources(${TARGET} INTERFACE ${target_sources_conditional_INTERFACE} PUBLIC ${target_sources_conditional_PUBLIC} PRIVATE ${target_sources_conditional_PRIVATE} ) if(NOT (${target_sources_conditional_FLAG})) set_source_files_properties(${target_sources_conditional_INTERFACE} PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties(${target_sources_conditional_PUBLIC} PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties(${target_sources_conditional_PRIVATE} PROPERTIES HEADER_FILE_ONLY TRUE) if(${target_sources_conditional_QT_AWARE}) set_source_files_properties(${target_sources_conditional_INTERFACE} PROPERTIES SKIP_AUTOGEN ON) set_source_files_properties(${target_sources_conditional_PUBLIC} PROPERTIES SKIP_AUTOGEN ON) set_source_files_properties(${target_sources_conditional_PRIVATE} PROPERTIES SKIP_AUTOGEN ON) endif() endif() endfunction() sdk-10.11.0/cmake/modules/vcpkg_management.cmake000066400000000000000000000117241516266226600215140ustar00rootroot00000000000000macro(process_vcpkg_libraries overlays_path) set(VCPKG_TOOLCHAIN_PATH "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") if (NOT EXISTS ${VCPKG_TOOLCHAIN_PATH}) message(FATAL_ERROR "Invalid VCPKG_ROOT path: ${VCPKG_ROOT}") endif() set(CMAKE_TOOLCHAIN_FILE ${VCPKG_TOOLCHAIN_PATH}) if (VCPKG_CHAINLOAD_TOOLCHAIN_FILE) message(STATUS "${VCPKG_CHAINLOAD_TOOLCHAIN_FILE} will be processed.") endif() # Use internal VCPKG tools set(VCPKG_BOOTSTRAP_OPTIONS "-disableMetrics") foreach(path IN ITEMS ${overlays_path}) if(CMAKE_SYSTEM_NAME STREQUAL "iOS" AND EXISTS "${path}/vcpkg_overlay_ports/ios_overlays") list(APPEND VCPKG_OVERLAY_PORTS "${path}/vcpkg_overlay_ports/ios_overlays") endif() list(APPEND VCPKG_OVERLAY_PORTS "${path}/vcpkg_overlay_ports") list(APPEND VCPKG_OVERLAY_TRIPLETS "${path}/vcpkg_overlay_triplets") endforeach() list(REMOVE_DUPLICATES VCPKG_OVERLAY_PORTS) list(REMOVE_DUPLICATES VCPKG_OVERLAY_TRIPLETS) if(NOT VCPKG_TARGET_TRIPLET) # Try to guess the triplet if it is not set. if(CMAKE_SYSTEM_NAME STREQUAL "Android") if(CMAKE_ANDROID_ARCH_ABI) set(TMP_ANDROID_ARCH_ABI ${CMAKE_ANDROID_ARCH_ABI}) elseif(ANDROID_ABI) set(TMP_ANDROID_ARCH_ABI ${ANDROID_ABI}) endif() if(TMP_ANDROID_ARCH_ABI STREQUAL "arm64-v8a") set(VCPKG_TARGET_TRIPLET "arm64-android-mega") elseif(TMP_ANDROID_ARCH_ABI STREQUAL "armeabi-v7a") set(VCPKG_TARGET_TRIPLET "arm-android-mega") elseif(TMP_ANDROID_ARCH_ABI STREQUAL "x86_64") set(VCPKG_TARGET_TRIPLET "x64-android-mega") elseif(TMP_ANDROID_ARCH_ABI STREQUAL "x86") set(VCPKG_TARGET_TRIPLET "x86-android-mega") endif() unset(TMP_ANDROID_ARCH_ABI) elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS") if(CMAKE_OSX_SYSROOT STREQUAL "iphonesimulator") # if CMAKE_OSX_ARCHITECTURES is arm64 or if it is empty. Empty builds for arm64 by default. set(VCPKG_TARGET_TRIPLET "arm64-ios-simulator-mega") else() set(VCPKG_TARGET_TRIPLET "arm64-ios-mega") endif() elseif(APPLE) # macOS if (CMAKE_OSX_ARCHITECTURES MATCHES "x86_64" OR (NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")) set(VCPKG_TARGET_TRIPLET "x64-osx-mega") else() set(VCPKG_TARGET_TRIPLET "arm64-osx-mega") endif() elseif(WIN32) if(CMAKE_GENERATOR_PLATFORM MATCHES "Win32") set(VCPKG_TARGET_TRIPLET "x86-windows-mega") elseif(CMAKE_GENERATOR_PLATFORM MATCHES "ARM64") set(VCPKG_TARGET_TRIPLET "arm64-windows-mega") else() set(VCPKG_TARGET_TRIPLET "x64-windows-mega") endif() else() # Linux if (CMAKE_SYSTEM_PROCESSOR MATCHES "armv7l" OR (NOT CMAKE_SYSTEM_PROCESSOR AND HOST_ARCH MATCHES "armv7l")) set(VCPKG_TARGET_TRIPLET "arm-linux") elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64" OR (NOT CMAKE_SYSTEM_PROCESSOR AND HOST_ARCH MATCHES "aarch64|arm64")) set(VCPKG_TARGET_TRIPLET "arm64-linux-mega") else() set(VCPKG_TARGET_TRIPLET "x64-linux-mega") endif() endif() endif() if (USE_OPENSSL) list(APPEND VCPKG_MANIFEST_FEATURES "use-openssl") endif() if (ENABLE_MEDIA_FILE_METADATA) list(APPEND VCPKG_MANIFEST_FEATURES "use-mediainfo") endif() if (USE_FREEIMAGE) list(APPEND VCPKG_MANIFEST_FEATURES "use-freeimage") endif() if (USE_FFMPEG) list(APPEND VCPKG_MANIFEST_FEATURES "use-ffmpeg") # Remove -flto[=n] from CFLAGS if set. It cause link errors in ffmpeg due to assembler code string(REGEX MATCH "-flto[^ \r\n]*" LTO_MATCHES "$ENV{CFLAGS}") if(LTO_MATCHES) message(STATUS "Removing ${LTO_MATCHES} from the environment CFLAGS variable") string(REPLACE "${LTO_MATCHES}" "" NEW_CFLAGS "$ENV{CFLAGS}") set(ENV{CFLAGS} "${NEW_CFLAGS}") endif() endif() if (USE_LIBUV) list(APPEND VCPKG_MANIFEST_FEATURES "use-libuv") endif() if (USE_PDFIUM) list(APPEND VCPKG_MANIFEST_FEATURES "use-pdfium") endif() if (USE_READLINE) list(APPEND VCPKG_MANIFEST_FEATURES "use-readline") endif() if (ENABLE_SDKLIB_TESTS) list(APPEND VCPKG_MANIFEST_FEATURES "sdk-tests") endif() if (ENABLE_C_ARES_BACKEND) list(APPEND VCPKG_MANIFEST_FEATURES "c-ares-backend-curl") endif() message(STATUS "Using VCPKG dependencies. VCPKG base path: ${VCPKG_ROOT} and tripplet ${VCPKG_TARGET_TRIPLET}") message(STATUS "Overlay for VCPKG ports: ${VCPKG_OVERLAY_PORTS}") message(STATUS "Overlay for VCPKG triplets: ${VCPKG_OVERLAY_TRIPLETS}") endmacro() sdk-10.11.0/cmake/vcpkg_overlay_ports/000077500000000000000000000000001516266226600176315ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/000077500000000000000000000000001516266226600204115ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/darwin-rpath.patch000066400000000000000000000016211516266226600240320ustar00rootroot00000000000000diff --git a/source/config/mh-darwin b/source/config/mh-darwin index 7b15709..e2cdbdb 100644 --- a/source/config/mh-darwin +++ b/source/config/mh-darwin @@ -31,7 +31,8 @@ SHLIB.cc= $(CXX) -dynamiclib -dynamic $(CXXFLAGS) $(LDFLAGS) $(LD_SOOPTIONS) ## Compiler switches to embed a library name and version information ifeq ($(ENABLE_RPATH),YES) -LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(libdir)/$(notdir $(MIDDLE_SO_TARGET)) +ID_PREFIX = @rpath +LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(ID_PREFIX)/$(notdir $(MIDDLE_SO_TARGET)) else LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(notdir $(MIDDLE_SO_TARGET)) $(PKGDATA_TRAILING_SPACE) endif sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/disable-escapestr-tool.patch000066400000000000000000000011221516266226600257730ustar00rootroot00000000000000diff --git a/source/tools/Makefile.in b/source/tools/Makefile.in index e0896f1..5ead980 100644 --- a/source/tools/Makefile.in +++ b/source/tools/Makefile.in @@ -19,9 +19,9 @@ SUBDIRS = toolutil ctestfw makeconv genrb genbrk \ gencnval gensprep icuinfo genccode gencmn icupkg pkgdata \ gentest gennorm2 gencfu gendict icuexportdata -ifneq (@platform_make_fragment_name@,mh-cygwin-msvc) -SUBDIRS += escapesrc -endif +#ifneq (@platform_make_fragment_name@,mh-cygwin-msvc) +#SUBDIRS += escapesrc +#endif ## List of phony targets .PHONY : all all-local all-recursive install install-local \ sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/disable-static-prefix.patch000066400000000000000000000010441516266226600256140ustar00rootroot00000000000000diff --git a/source/icudefs.mk.in b/source/icudefs.mk.in index 24bd97a..33169de 100644 --- a/source/icudefs.mk.in +++ b/source/icudefs.mk.in @@ -213,13 +213,13 @@ LIBICU = $(LIBPREFIX)$(ICUPREFIX) ## If we can't use the shared libraries, use the static libraries ifneq ($(ENABLE_SHARED),YES) -STATIC_PREFIX_WHEN_USED = s +STATIC_PREFIX_WHEN_USED = else STATIC_PREFIX_WHEN_USED = endif # Static library prefix and file extension -STATIC_PREFIX = s +STATIC_PREFIX = LIBSICU = $(LIBPREFIX)$(STATIC_PREFIX)$(ICUPREFIX) A = a SOBJ = $(SO) sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/filter.json000066400000000000000000000016031516266226600225710ustar00rootroot00000000000000{ "localeFilter": { "filterType": "locale", "includeChildren": "false", "includelist": [ "en_GB" ] }, "featureFilters": { "brkitr_rules": "exclude", "brkitr_dictionaries": "exclude", "brkitr_tree": "exclude", "conversion_mappings": "exclude", "confusables": "exclude", "curr_supplemental": "exclude", "curr_tree": "exclude", "lang_tree": "exclude", "normalization": "exclude", "region_tree": "exclude", "rbnf_tree": "exclude", "stringprep": "exclude", "zone_tree": "exclude", "zone_supplemental": "exclude", "translit": "exclude", "uemoji": "exclude", "unames": "exclude", "ulayout": "exclude", "unit_tree": "exclude", "cnvalias": "exclude", "locales_tre": "exclude" } } sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/fix-extra.patch000066400000000000000000000005361516266226600233450ustar00rootroot00000000000000diff --urN a/source/extra/Makefile.in b/source/extra/Makefile.in --- a/source/extra/Makefile.in +++ b/source/extra/Makefile.in @@ -23,7 +23,7 @@ ## Files to remove for 'make clean' CLEANFILES = *~ -SUBDIRS = scrptrun uconv +SUBDIRS = uconv ## List of phony targets .PHONY : all all-local all-recursive install install-local \ sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/fix-win-build.patch000066400000000000000000000023231516266226600241100ustar00rootroot00000000000000diff --git a/source/configure.ac b/source/configure.ac index 425fdc7..97210f8 100644 --- a/source/configure.ac +++ b/source/configure.ac @@ -114,6 +114,15 @@ AC_ARG_ENABLE([icu-config], esac], [enable_icu_config=true]) AC_SUBST(INSTALL_ICU_CONFIG, [$enable_icu_config]) +AC_ARG_ENABLE([icu-build-win], + AS_HELP_STRING([--enable-icu-build-win], [install icu-build-win]), + [case "${enableval}" in + yes) enable_icu_build_win=true ;; + no) enable_icu_build_win=false ;; + *) AC_MSG_ERROR([bad value '${enableval}' for --enable-icu-build-win]) ;; + esac], [enable_icu_build_win=true]) +AC_SUBST(INSTALL_ICU_BUILD_WIN, [$enable_icu_build_win]) + # Check whether to build debug libraries AC_MSG_CHECKING([whether to build debug libraries]) enabled=no @@ -263,12 +272,14 @@ ICU_CHECK_MH_FRAG # Checks for libraries and other host specific stuff # On HP/UX, don't link to -lm from a shared lib because it isn't # PIC (at least on 10.2) +if test "$enable_icu_build_win" = no; then case "${host}" in *-*-hpux*) AC_CHECK_LIB(m, floor, LIB_M="-lm") ;; *) AC_CHECK_LIB(m, floor) LIB_M="" ;; esac +fi AC_SUBST(LIB_M) # Check whether to build shared libraries sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/fix_parallel_build_on_windows.patch000066400000000000000000000014011516266226600275150ustar00rootroot00000000000000diff --urN a/source/data/Makefile.in b/source/data/Makefile.in --- a/source/data/Makefile.in +++ b/source/data/Makefile.in @@ -236,11 +236,12 @@ ## Include the main build rules for data files include $(top_builddir)/$(subdir)/rules.mk +PKGDATA_LIST = $(TMP_DIR)/icudata.lst ifeq ($(ENABLE_SO_VERSION_DATA),1) ifeq ($(PKGDATA_MODE),dll) SO_VERSION_DATA = $(OUTTMPDIR)/icudata.res -$(SO_VERSION_DATA) : $(MISCSRCDIR)/icudata.rc | $(TMP_DIR)/dirs.timestamp +$(SO_VERSION_DATA) : $(MISCSRCDIR)/icudata.rc $(PKGDATA_LIST) ifeq ($(MSYS_RC_MODE),1) rc.exe -i$(srcdir)/../common -i$(top_builddir)/common -fo$@ $(CPPFLAGS) $< else @@ -249,7 +250,6 @@ endif endif -PKGDATA_LIST = $(TMP_DIR)/icudata.lst ##################################################### sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/mingw-dll-install.patch000066400000000000000000000023511516266226600247710ustar00rootroot00000000000000diff --git a/source/config/mh-mingw b/source/config/mh-mingw index 30f6e5be81..b6364551ea 100644 --- a/source/config/mh-mingw +++ b/source/config/mh-mingw @@ -13,7 +13,7 @@ # On Windows we generally have the DLLs in the bin directory rather than the lib directory. # This setting moves the ICU DLLs into the bin folder for MinGW/MSYS2 when "make install" is run. # If you prefer to have the DLLs in the lib folder, then set this to NO instead. -MINGW_MOVEDLLSTOBINDIR = YES +MINGW_MOVEDLLSTOBINDIR = NO # We install sbin tools into the same bin directory because # pkgdata needs some of the tools in sbin, and we can't always depend on diff --git a/source/config/mh-mingw64 b/source/config/mh-mingw64 index fb64c56260..a43cc4dd71 100644 --- a/source/config/mh-mingw64 +++ b/source/config/mh-mingw64 @@ -10,7 +10,7 @@ # On Windows we generally have the DLLs in the bin directory rather than the lib directory. # This setting moves the ICU DLLs into the bin folder for MinGW/MSYS2 when "make install" is run. # If you prefer to have the DLLs in the lib folder, then set this to NO instead. -MINGW_MOVEDLLSTOBINDIR = YES +MINGW_MOVEDLLSTOBINDIR = NO # This file is similar to mh-mingw # Any changes made here may also need to be made in mh-mingw sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/mingw-strict-ansi.diff000066400000000000000000000005001516266226600246150ustar00rootroot00000000000000diff --git a/source/common/putil.cpp b/source/common/putil.cpp index ab25f3b..94782f8 100644 --- a/source/common/putil.cpp +++ b/source/common/putil.cpp @@ -48,7 +48,6 @@ #if U_PLATFORM == U_PF_MINGW && defined __STRICT_ANSI__ /* tzset isn't defined in strict ANSI on MinGW. */ -#undef __STRICT_ANSI__ #endif /* sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/plurrule.cpp.patch000066400000000000000000000007601516266226600240700ustar00rootroot00000000000000diff --git a/source/i18n/plurrule.cpp b/source/i18n/plurrule.cpp index 839d141..e3379f6 100644 --- a/source/i18n/plurrule.cpp +++ b/source/i18n/plurrule.cpp @@ -37,7 +37,6 @@ #include "ustrfmt.h" #include "uassert.h" #include "uvectr32.h" -#include "sharedpluralrules.h" #include "unifiedcache.h" #include "number_decimalquantity.h" #include "util.h" @@ -47,6 +46,8 @@ #if !UCONFIG_NO_FORMATTING +#include "sharedpluralrules.h" + U_NAMESPACE_BEGIN using namespace icu::pluralimpl; sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/portfile.cmake000066400000000000000000000157341516266226600232510ustar00rootroot00000000000000if(NOT VCPKG_TARGET_IS_WINDOWS) message(WARNING "${PORT} currently requires the following programs from the system package manager: autoconf automake autoconf-archive On Debian and Ubuntu derivatives: sudo apt-get install autoconf automake autoconf-archive On recent Red Hat and Fedora derivatives: sudo dnf install autoconf automake autoconf-archive On Arch Linux and derivatives: sudo pacman -S autoconf automake autoconf-archive On Alpine: apk add autoconf automake autoconf-archive On macOS: brew install autoconf automake autoconf-archive\n") endif() string(REGEX MATCH "^[0-9]*" ICU_VERSION_MAJOR "${VERSION}") string(REPLACE "." "_" VERSION2 "${VERSION}") string(REPLACE "." "-" VERSION3 "${VERSION}") vcpkg_download_distfile( ARCHIVE URLS "https://github.com/unicode-org/icu/releases/download/release-${VERSION3}/icu4c-${VERSION2}-src.tgz" FILENAME "icu4c-${VERSION2}-src.tgz" SHA512 e6c7876c0f3d756f3a6969cad9a8909e535eeaac352f3a721338b9cbd56864bf7414469d29ec843462997815d2ca9d0dab06d38c37cdd4d8feb28ad04d8781b0 ) vcpkg_extract_source_archive(SOURCE_PATH ARCHIVE "${ARCHIVE}" PATCHES disable-escapestr-tool.patch remove-MD-from-configure.patch fix_parallel_build_on_windows.patch fix-extra.patch mingw-dll-install.patch disable-static-prefix.patch # https://gitlab.kitware.com/cmake/cmake/-/issues/16617; also mingw. fix-win-build.patch vcpkg-cross-data.patch darwin-rpath.patch mingw-strict-ansi.diff # backport of https://github.com/unicode-org/icu/pull/3003 uconfig.h.patch plurrule.cpp.patch ) vcpkg_download_distfile( ICUDATA URLS "https://github.com/unicode-org/icu/releases/download/release-${VERSION3}/icu4c-${VERSION2}-data.zip" FILENAME "icu4c-${VERSION2}-data.zip" SHA512 f9dbd303f78de1bf9089262211f3b618f1ec915e57877855d0bc6496332620f4ea92eabe1dff9fa721600e9a6ce56885f79361bbcdf97d0cfedd18e4a2d58ad0 ) vcpkg_extract_archive( ARCHIVE "${ICUDATA}" DESTINATION "${SOURCE_PATH}.temp" ) file(REMOVE_RECURSE "${SOURCE_PATH}/source/data") file(COPY "${SOURCE_PATH}.temp/" DESTINATION "${SOURCE_PATH}/source/") file(REMOVE_RECURSE "${SOURCE_PATH}.temp") vcpkg_find_acquire_program(PYTHON3) set(ENV{PYTHON} "${PYTHON3}") vcpkg_list(SET CONFIGURE_OPTIONS) vcpkg_list(SET BUILD_OPTIONS) if(VCPKG_TARGET_IS_EMSCRIPTEN) vcpkg_list(APPEND CONFIGURE_OPTIONS --disable-extras) vcpkg_list(APPEND BUILD_OPTIONS "PKGDATA_OPTS=--without-assembly -O ../data/icupkg.inc") elseif(VCPKG_TARGET_IS_UWP) vcpkg_list(APPEND CONFIGURE_OPTIONS --disable-extras ac_cv_func_tzset=no ac_cv_func__tzset=no) string(APPEND VCPKG_C_FLAGS " -DU_PLATFORM_HAS_WINUWP_API=1") string(APPEND VCPKG_CXX_FLAGS " -DU_PLATFORM_HAS_WINUWP_API=1") vcpkg_list(APPEND BUILD_OPTIONS "PKGDATA_OPTS=--windows-uwp-build -O ../data/icupkg.inc") elseif(VCPKG_TARGET_IS_OSX AND VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") vcpkg_list(APPEND CONFIGURE_OPTIONS --enable-rpath) if(DEFINED CMAKE_INSTALL_NAME_DIR) vcpkg_list(APPEND BUILD_OPTIONS "ID_PREFIX=${CMAKE_INSTALL_NAME_DIR}") endif() endif() if(VCPKG_TARGET_IS_WINDOWS) list(APPEND CONFIGURE_OPTIONS --enable-icu-build-win) endif() if("tools" IN_LIST FEATURES) list(APPEND CONFIGURE_OPTIONS --enable-tools) else() list(APPEND CONFIGURE_OPTIONS --disable-tools) endif() if(CMAKE_HOST_WIN32 AND VCPKG_TARGET_IS_MINGW AND NOT HOST_TRIPLET MATCHES "mingw") # Assuming no cross compiling because the host (windows) pkgdata tool doesn't # use the '/' path separator when creating compiler commands for mingw bash. elseif(VCPKG_CROSSCOMPILING) set(TOOL_PATH "${CURRENT_HOST_INSTALLED_DIR}/tools/${PORT}") # convert to unix path string(REGEX REPLACE "^([a-zA-Z]):/" "/\\1/" _VCPKG_TOOL_PATH "${TOOL_PATH}") list(APPEND CONFIGURE_OPTIONS "--with-cross-build=${_VCPKG_TOOL_PATH}") endif() file(COPY ${CURRENT_PORT_DIR}/filter.json DESTINATION ${SOURCE_PATH}/) set(ENV{ICU_DATA_FILTER_FILE} "${SOURCE_PATH}/filter.json") vcpkg_configure_make( SOURCE_PATH "${SOURCE_PATH}" PROJECT_SUBPATH source AUTOCONFIG DETERMINE_BUILD_TRIPLET ADDITIONAL_MSYS_PACKAGES autoconf-archive OPTIONS ${CONFIGURE_OPTIONS} --disable-samples --disable-tests --disable-layoutex OPTIONS_RELEASE --disable-debug --enable-release OPTIONS_DEBUG --enable-debug --disable-release ) vcpkg_install_make(OPTIONS ${BUILD_OPTIONS}) file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/share" "${CURRENT_PACKAGES_DIR}/debug/share" "${CURRENT_PACKAGES_DIR}/lib/icu" "${CURRENT_PACKAGES_DIR}/debug/lib/icu" "${CURRENT_PACKAGES_DIR}/debug/lib/icud") file(GLOB TEST_LIBS "${CURRENT_PACKAGES_DIR}/lib/*test*" "${CURRENT_PACKAGES_DIR}/debug/lib/*test*") if(TEST_LIBS) file(REMOVE ${TEST_LIBS}) endif() if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") # force U_STATIC_IMPLEMENTATION macro foreach(HEADER utypes.h utf_old.h platform.h) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/unicode/${HEADER}" "defined(U_STATIC_IMPLEMENTATION)" "1") endforeach() endif() # Install executables from /tools/icu/sbin to /tools/icu/bin on unix (/bin because icu require this for cross compiling) if(VCPKG_TARGET_IS_LINUX OR VCPKG_TARGET_IS_OSX AND "tools" IN_LIST FEATURES) vcpkg_copy_tools( TOOL_NAMES icupkg gennorm2 gencmn genccode gensprep SEARCH_DIR "${CURRENT_PACKAGES_DIR}/tools/icu/sbin" DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}/bin" ) endif() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/tools/icu/sbin" "${CURRENT_PACKAGES_DIR}/tools/icu/debug") # To cross compile, we need some files at specific positions. So lets copy them file(GLOB CROSS_COMPILE_DEFS "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/config/icucross.*") file(INSTALL ${CROSS_COMPILE_DEFS} DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}/config") file(GLOB RELEASE_DLLS "${CURRENT_PACKAGES_DIR}/lib/*icu*${ICU_VERSION_MAJOR}.dll") file(COPY ${RELEASE_DLLS} DESTINATION "${CURRENT_PACKAGES_DIR}/tools/${PORT}/bin") # copy dlls file(GLOB RELEASE_DLLS "${CURRENT_PACKAGES_DIR}/lib/*icu*${ICU_VERSION_MAJOR}.dll") file(COPY ${RELEASE_DLLS} DESTINATION "${CURRENT_PACKAGES_DIR}/bin") if(NOT VCPKG_BUILD_TYPE) file(GLOB DEBUG_DLLS "${CURRENT_PACKAGES_DIR}/debug/lib/*icu*${ICU_VERSION_MAJOR}.dll") file(COPY ${DEBUG_DLLS} DESTINATION "${CURRENT_PACKAGES_DIR}/debug/bin") endif() # remove any remaining dlls in /lib file(GLOB DUMMY_DLLS "${CURRENT_PACKAGES_DIR}/lib/*.dll" "${CURRENT_PACKAGES_DIR}/debug/lib/*.dll") if(DUMMY_DLLS) file(REMOVE ${DUMMY_DLLS}) endif() vcpkg_copy_pdbs() vcpkg_fixup_pkgconfig() vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/icu/bin/icu-config" "${CURRENT_INSTALLED_DIR}" "`dirname $0`/../../../" IGNORE_UNCHANGED) file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/remove-MD-from-configure.patch000066400000000000000000000011741516266226600261500ustar00rootroot00000000000000diff -urN a/source/runConfigureICU b/source/runConfigureICU --- a/source/runConfigureICU 2018-03-26 21:38:44.000000000 +0800 +++ b/source/runConfigureICU 2018-08-26 09:04:53.197454400 +0800 @@ -322,10 +322,10 @@ THE_COMP="Microsoft Visual C++" CC=cl; export CC CXX=cl; export CXX - RELEASE_CFLAGS='-Gy -MD' - RELEASE_CXXFLAGS='-Gy -MD' - DEBUG_CFLAGS='-FS -Zi -MDd' - DEBUG_CXXFLAGS='-FS -Zi -MDd' + RELEASE_CFLAGS='-Gy' + RELEASE_CXXFLAGS='-Gy' + DEBUG_CFLAGS='-FS -Zi' + DEBUG_CXXFLAGS='-FS -Zi' DEBUG_LDFLAGS='-DEBUG' ;; *BSD) sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/uconfig.h.patch000066400000000000000000000010561516266226600233140ustar00rootroot00000000000000diff --git a/source/common/unicode/uconfig.h b/source/common/unicode/uconfig.h index 3818ca0..835d9c2 100644 --- a/source/common/unicode/uconfig.h +++ b/source/common/unicode/uconfig.h @@ -215,7 +215,7 @@ * @stable ICU 2.4 */ #ifndef UCONFIG_ONLY_COLLATION -# define UCONFIG_ONLY_COLLATION 0 +# define UCONFIG_ONLY_COLLATION 1 #endif #if UCONFIG_ONLY_COLLATION @@ -308,7 +308,7 @@ * @stable ICU 2.4 */ #ifndef UCONFIG_NO_LEGACY_CONVERSION -# define UCONFIG_NO_LEGACY_CONVERSION 0 +# define UCONFIG_NO_LEGACY_CONVERSION 1 #endif /** sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/vcpkg-cmake-wrapper.cmake000066400000000000000000000002721516266226600252620ustar00rootroot00000000000000cmake_policy(PUSH) cmake_policy(SET CMP0057 NEW) if(NOT "COMPONENTS" IN_LIST ARGS) _find_package(${ARGS} COMPONENTS data) else() _find_package(${ARGS}) endif() cmake_policy(POP) sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/vcpkg-cross-data.patch000066400000000000000000000010451516266226600246020ustar00rootroot00000000000000diff --git a/source/configure.ac b/source/configure.ac index 1bd5871..c508f48 100644 --- a/source/configure.ac +++ b/source/configure.ac @@ -1151,7 +1151,7 @@ AC_ARG_ENABLE(fuzzer, fuzzer=false) ICU_CONDITIONAL(FUZZER, test "$fuzzer" = true) -ICU_CONDITIONAL(DATA, test "$tools" = true || test "$cross_compiling" = "yes") +ICU_CONDITIONAL(DATA, test "$tools" = true || test "$cross_compiling" = "yes" || test -n "$cross_buildroot") AC_ARG_WITH(data-packaging, [ --with-data-packaging specify how to package ICU data. Possible values: sdk-10.11.0/cmake/vcpkg_overlay_ports/icu/vcpkg.json000066400000000000000000000007761516266226600224300ustar00rootroot00000000000000{ "name": "icu", "version": "74.2", "port-version": 4, "description": "Mature and widely used Unicode and localization library.", "homepage": "https://icu.unicode.org/home", "license": "ICU", "dependencies": [ { "name": "icu", "host": true, "features": [ "tools" ] } ], "features": { "tools": { "description": "Build tools", "supports": "!uwp" } } } sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/000077500000000000000000000000001516266226600223475ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/cryptopp/000077500000000000000000000000001516266226600242275ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/cryptopp/cryptopp.patch000066400000000000000000000024161516266226600271330ustar00rootroot00000000000000diff --git a/secblock.h b/secblock.h index 5ab920f9..74d939cf 100644 --- a/secblock.h +++ b/secblock.h @@ -270,7 +270,7 @@ public: /// \details VS.NET STL enforces the policy of "All STL-compliant allocators /// have to provide a template class member called rebind". template struct rebind { typedef AllocatorWithCleanup other; }; -#if (CRYPTOPP_MSC_VERSION >= 1500) +#if (CRYPTOPP_MSC_VERSION >= 1500) || defined(__clang__) AllocatorWithCleanup() {} template AllocatorWithCleanup(const AllocatorWithCleanup &) {} #endif diff --git a/zdeflate.cpp b/zdeflate.cpp index b3514b55..20717c24 100644 --- a/zdeflate.cpp +++ b/zdeflate.cpp @@ -413,7 +413,7 @@ unsigned int Deflator::LongestMatch(unsigned int &bestMatch) const { CRYPTOPP_ASSERT(scan[2] == match[2]); unsigned int len = (unsigned int)( -#if defined(_STDEXT_BEGIN) && !(defined(CRYPTOPP_MSC_VERSION) && (CRYPTOPP_MSC_VERSION < 1400 || CRYPTOPP_MSC_VERSION >= 1600)) && !defined(_STLPORT_VERSION) +#if defined(_STDEXT_BEGIN) && !(defined(CRYPTOPP_MSC_VERSION) && (CRYPTOPP_MSC_VERSION < 1400 || CRYPTOPP_MSC_VERSION >= 1600)) && !defined(_STLPORT_VERSION) && !defined(__clang__) stdext::unchecked_mismatch #else std::mismatch sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/cryptopp/patch.patch000066400000000000000000000024111516266226600263450ustar00rootroot00000000000000diff --git a/config_cxx.h b/config_cxx.h index ffd57ad..353d7ce 100644 --- a/config_cxx.h +++ b/config_cxx.h @@ -217,7 +217,7 @@ // Also see https://github.com/weidai11/cryptopp/issues/980. I'm not sure what // to do when the compiler defines __cpp_lib_uncaught_exceptions but the platform // does not support std::uncaught_exceptions. What was Apple thinking??? -#if defined(__clang__) +#if defined(__clang__) && !defined(CRYPTOPP_MSC_VERSION) # if __EXCEPTIONS && __has_feature(cxx_exceptions) # if __cpp_lib_uncaught_exceptions >= 201411L # define CRYPTOPP_CXX17_UNCAUGHT_EXCEPTIONS 1 diff --git a/config_os.h b/config_os.h index 0994563..4546585 100644 --- a/config_os.h +++ b/config_os.h @@ -29,7 +29,7 @@ // https://www.cryptopp.com/wiki/Release_Process#Self_Tests // The problems with Clang pretending to be other compilers is // discussed at http://github.com/weidai11/cryptopp/issues/147. -#if (defined(_MSC_VER) && defined(__clang__)) +#if (defined(_MSC_VER) && _MSC_VER < 1930 && defined(__clang__)) # error: "Unsupported configuration" #endif @@ -126,6 +126,7 @@ #endif #ifdef CRYPTOPP_WIN32_AVAILABLE +#include # if !defined(WINAPI_FAMILY) # define THREAD_TIMER_AVAILABLE # elif defined(WINAPI_FAMILY) sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/cryptopp/portfile.cmake000066400000000000000000000074111516266226600270600ustar00rootroot00000000000000vcpkg_check_linkage(ONLY_STATIC_LIBRARY) string(REPLACE "." "_" CRYPTOPP_VERSION "${VERSION}") vcpkg_from_github( OUT_SOURCE_PATH CMAKE_SOURCE_PATH REPO abdes/cryptopp-cmake REF "CRYPTOPP_${CRYPTOPP_VERSION}" SHA512 3ec33b107ab627a514e1ebbc4b6522ee8552525f36730d9b5feb85e61ba7fc24fd36eb6050e328c6789ff60d47796beaa8eebf7dead787a34395294fae9bb733 HEAD_REF master ) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO weidai11/cryptopp REF "CRYPTOPP_${CRYPTOPP_VERSION}" SHA512 28a67141155c9c15e3e6a2173b3a8487cc38a2a2ade73bf4a09814ca541be6b06e9a501be26f7e2f42a2f80df21b076aa5d8ad4224dc0a1f8d7f3b24deae465e HEAD_REF master PATCHES patch.patch cryptopp.patch ) file(COPY "${CMAKE_SOURCE_PATH}/cryptopp" DESTINATION "${SOURCE_PATH}") file(COPY "${CMAKE_SOURCE_PATH}/cmake" DESTINATION "${SOURCE_PATH}") file(COPY "${CMAKE_SOURCE_PATH}/test" DESTINATION "${SOURCE_PATH}") file(COPY "${CMAKE_SOURCE_PATH}/cryptopp/cryptoppConfig.cmake" DESTINATION "${SOURCE_PATH}") file(COPY "${CMAKE_SOURCE_PATH}/CMakeLists.txt" DESTINATION "${SOURCE_PATH}") if("pem-pack" IN_LIST FEATURES) vcpkg_from_github( OUT_SOURCE_PATH PEM_PACK_SOURCE_PATH REPO noloader/cryptopp-pem REF 095f08ff2ef9bca7b81036a59f2395e4f08ce2e8 SHA512 49912758a635faca1f49665ac9552b20576b46e0283aaabc19bb012bdc80586106452018e5088b9b46967717982ca6022ca968edc4cac96a7506d2b1a3e4bf13 HEAD_REF master ) file(GLOB PEM_PACK_FILES ${PEM_PACK_SOURCE_PATH}/*.h ${PEM_PACK_SOURCE_PATH}/*.cpp ) file(INSTALL ${PEM_PACK_FILES} DESTINATION "${CURRENT_PACKAGES_DIR}/include/${PORT}") endif() # disable assembly on ARM Windows to fix broken build if (VCPKG_TARGET_IS_WINDOWS AND VCPKG_TARGET_ARCHITECTURE MATCHES "^arm") set(CRYPTOPP_DISABLE_ASM "ON") elseif(NOT DEFINED CRYPTOPP_DISABLE_ASM) # Allow disabling using a triplet file set(CRYPTOPP_DISABLE_ASM "OFF") endif() # Dynamic linking should be avoided for Crypto++ to reduce the attack surface, # so generate a static lib for both dynamic and static vcpkg targets. # See also: # https://www.cryptopp.com/wiki/Visual_Studio#Dynamic_Runtime_Linking # https://www.cryptopp.com/wiki/Visual_Studio#The_DLL vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS -DCRYPTOPP_SOURCES=${SOURCE_PATH} -DCRYPTOPP_BUILD_SHARED=OFF -DBUILD_STATIC=ON -DCRYPTOPP_BUILD_TESTING=OFF -DCRYPTOPP_BUILD_DOCUMENTATION=OFF -DDISABLE_ASM=${CRYPTOPP_DISABLE_ASM} -DUSE_INTERMEDIATE_OBJECTS_TARGET=OFF # Not required when we build static only -DCMAKE_POLICY_DEFAULT_CMP0063=NEW # Honor "_VISIBILITY_PRESET" properties -DCMAKE_CXX_VISIBILITY_PRESET=default -DCMAKE_VISIBILITY_INLINES_HIDDEN=OFF MAYBE_UNUSED_VARIABLES BUILD_STATIC USE_INTERMEDIATE_OBJECTS_TARGET CMAKE_POLICY_DEFAULT_CMP0063 ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(CONFIG_PATH share/cmake/cryptopp) if(NOT VCPKG_BUILD_TYPE) file(RENAME "${CURRENT_PACKAGES_DIR}/debug/share/pkgconfig" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig") endif() file(RENAME "${CURRENT_PACKAGES_DIR}/share/pkgconfig" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig") vcpkg_fixup_pkgconfig() # There is no way to suppress installation of the headers and resource files in debug build. file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin") endif() # Handle copyright file(COPY "${SOURCE_PATH}/License.txt" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") file(RENAME "${CURRENT_PACKAGES_DIR}/share/${PORT}/License.txt" "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright") sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/cryptopp/vcpkg.json000066400000000000000000000007311516266226600262350ustar00rootroot00000000000000{ "name": "cryptopp", "version": "8.9.0", "port-version": 1, "description": "Crypto++ is a free C++ class library of cryptographic schemes.", "homepage": "https://github.com/weidai11/cryptopp", "license": "BSL-1.0", "dependencies": [ { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true } ], "features": { "pem-pack": { "description": "Crypto++ with PEM pack" } } } sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/sqlite3/000077500000000000000000000000001516266226600237335ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/sqlite3/portfile.cmake000066400000000000000000000002101516266226600265520ustar00rootroot00000000000000set(VCPKG_POLICY_EMPTY_PACKAGE enabled) message(NOTICE "Skip ${PORT} build. VCPKG will try to use the ${PORT} provided by the system.") sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/sqlite3/vcpkg.json000066400000000000000000000004121516266226600257350ustar00rootroot00000000000000{ "name": "sqlite3", "version-string": "system", "description": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.", "homepage": "https://sqlite.org/", "license": "blessing" } sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/zlib/000077500000000000000000000000001516266226600233075ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/zlib/portfile.cmake000066400000000000000000000010461516266226600261360ustar00rootroot00000000000000set(VCPKG_POLICY_EMPTY_PACKAGE enabled) if(VCPKG_OSX_SYSROOT STREQUAL "iphonesimulator") set(target_environment "iPhoneSimulator") else() set(target_environment "iPhoneOS") endif() message(NOTICE "Creating .pc file for ${PORT}, pointing to the zlib provided by the system in the ${target_environment} sysroot/SDK") configure_file("${CMAKE_CURRENT_LIST_DIR}/zlib.pc.in" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/zlib.pc" @ONLY) configure_file("${CMAKE_CURRENT_LIST_DIR}/zlib.pc.in" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/zlib.pc" @ONLY) sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/zlib/vcpkg.json000066400000000000000000000002311516266226600253100ustar00rootroot00000000000000{ "name": "zlib", "version-string": "system", "description": "A compression library", "homepage": "https://www.zlib.net/", "license": "Zlib" } sdk-10.11.0/cmake/vcpkg_overlay_ports/ios_overlays/zlib/zlib.pc.in000066400000000000000000000005071516266226600252020ustar00rootroot00000000000000prefix=/Applications/Xcode.app/Contents/Developer/Platforms/@target_environment@.platform/Developer/SDKs/@target_environment@.sdk exec_prefix=${prefix}/usr libdir=${exec_prefix}/lib includedir=${prefix}/usr/include Name: zlib Description: zlib compression library Version: system Libs: -L${libdir} -lz Cflags: -I${includedir} sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/000077500000000000000000000000001516266226600211235ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/CMakeLists.txt000066400000000000000000000060741516266226600236720ustar00rootroot00000000000000# Copyright Mathieu Malaterre # BSD (Same as jxrlib) # Based on https://jxrlib.codeplex.com/discussions/440294 # and modified for vcpkg packaging cmake_minimum_required(VERSION 2.8) project(jxrlib C) # Need shared libs for ABI option(BUILD_SHARED_LIBS "Build shared libraries" ON) # Add a debug postfix set(CMAKE_DEBUG_POSTFIX "d") # helper macro to preserve original Makefile convention macro(JXR_MAKE_OBJ SET_NAME) foreach(src ${SRC_${SET_NAME}}) list(APPEND OBJ_${SET_NAME} ${DIR_${SET_NAME}}/${src}) endforeach() endmacro() if(NOT WIN32) add_definitions(-D__ANSI__) endif() if(NOT MSVC) add_compile_options( -Wno-error=implicit-function-declaration -Wno-endif-labels -Wno-incompatible-pointer-types # https://gcc.gnu.org/gcc-14/porting_to.html#incompatible-pointer-types ) endif() include(TestBigEndian) test_big_endian(ISBIGENDIAN) if(ISBIGENDIAN) set(DEF_ENDIAN _BIG__ENDIAN_) endif() set(DIR_SYS image/sys) set(DIR_DEC image/decode) set(DIR_ENC image/encode) set(DIR_GLUE jxrgluelib) set(DIR_TEST jxrtestlib) set(DIR_EXEC jxrencoderdecoder) if(NOT JXRLIB_INSTALL_BIN_DIR) set(JXRLIB_INSTALL_BIN_DIR "bin") endif() if(NOT JXRLIB_INSTALL_LIB_DIR) set(JXRLIB_INSTALL_LIB_DIR "lib") endif() if(NOT JXRLIB_INSTALL_INCLUDE_DIR) set(JXRLIB_INSTALL_INCLUDE_DIR "include/jxrlib") endif() include_directories( common/include ${DIR_SYS} ${DIR_GLUE} ${DIR_TEST} ) # JPEG-XR set(SRC_SYS adapthuff.c image.c strcodec.c strPredQuant.c strTransform.c perfTimerANSI.c) JXR_MAKE_OBJ(SYS) set(SRC_DEC decode.c postprocess.c segdec.c strdec.c strInvTransform.c strPredQuantDec.c JXRTranscode.c) JXR_MAKE_OBJ(DEC) set(SRC_ENC encode.c segenc.c strenc.c strFwdTransform.c strPredQuantEnc.c) JXR_MAKE_OBJ(ENC) add_library(jpegxr ${OBJ_ENC} ${OBJ_DEC} ${OBJ_SYS}) set_property(TARGET jpegxr PROPERTY COMPILE_DEFINITIONS DISABLE_PERF_MEASUREMENT ${DEF_ENDIAN} ) set_property(TARGET jpegxr PROPERTY LINK_INTERFACE_LIBRARIES "") install(TARGETS jpegxr EXPORT JXRLibTargets RUNTIME DESTINATION ${JXRLIB_INSTALL_BIN_DIR} LIBRARY DESTINATION ${JXRLIB_INSTALL_LIB_DIR} ARCHIVE DESTINATION ${JXRLIB_INSTALL_LIB_DIR} ) # JXR-GLUE set(SRC_GLUE JXRGlue.c JXRMeta.c JXRGluePFC.c JXRGlueJxr.c) JXR_MAKE_OBJ(GLUE) set(SRC_TEST JXRTest.c JXRTestBmp.c JXRTestHdr.c JXRTestPnm.c JXRTestTif.c JXRTestYUV.c) JXR_MAKE_OBJ(TEST) add_library(jxrglue ${OBJ_GLUE} ${OBJ_TEST}) set_property(TARGET jxrglue PROPERTY COMPILE_DEFINITIONS DISABLE_PERF_MEASUREMENT ${DEF_ENDIAN} ) set_property(TARGET jxrglue PROPERTY LINK_INTERFACE_LIBRARIES "") install(TARGETS jxrglue EXPORT JXRLibTargets RUNTIME DESTINATION ${JXRLIB_INSTALL_BIN_DIR} LIBRARY DESTINATION ${JXRLIB_INSTALL_LIB_DIR} ARCHIVE DESTINATION ${JXRLIB_INSTALL_LIB_DIR} ) target_link_libraries(jxrglue jpegxr) # install rules install(FILES jxrgluelib/JXRGlue.h jxrgluelib/JXRMeta.h jxrtestlib/JXRTest.h image/sys/windowsmediaphoto.h DESTINATION ${JXRLIB_INSTALL_INCLUDE_DIR} COMPONENT Headers ) install(DIRECTORY common/include/ DESTINATION ${JXRLIB_INSTALL_INCLUDE_DIR} FILES_MATCHING PATTERN "*.h" ) sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/FindJXR.cmake000066400000000000000000000015531516266226600233750ustar00rootroot00000000000000# - Find JXR # Find the JXR library # This module defines # JXR_INCLUDE_DIRS, where to find jxrlib/JXRGlue.h # JXR_LIBRARIES, the libraries needed to use JXR # find_path(JXR_INCLUDE_DIRS NAMES JXRGlue.h PATH_SUFFIXES jxrlib ) mark_as_advanced(JXR_INCLUDE_DIRS) include(SelectLibraryConfigurations) find_library(JPEGXR_LIBRARY_RELEASE NAMES jpegxr PATH_SUFFIXES lib) find_library(JPEGXR_LIBRARY_DEBUG NAMES jpegxrd PATH_SUFFIXES lib) select_library_configurations(JPEGXR) find_library(JXRGLUE_LIBRARY_RELEASE NAMES jxrglue PATH_SUFFIXES lib) find_library(JXRGLUE_LIBRARY_DEBUG NAMES jxrglued PATH_SUFFIXES lib) select_library_configurations(JXRGLUE) set(JXR_LIBRARIES ${JXRGLUE_LIBRARY} ${JPEGXR_LIBRARY}) mark_as_advanced(JXR_LIBRARIES) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(JXR DEFAULT_MSG JXR_INCLUDE_DIRS JXR_LIBRARIES) sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/fix-mingw.patch000066400000000000000000000025231516266226600240530ustar00rootroot00000000000000diff --git a/common/include/wmspecstrings_adt.h b/common/include/wmspecstrings_adt.h index ca7f25f..d5eb028 100644 --- a/common/include/wmspecstrings_adt.h +++ b/common/include/wmspecstrings_adt.h @@ -36,6 +36,10 @@ __type_has_adt_prop(compname,valid_schars) \ __type_has_adt_prop(compname,correct_len) \ __nullterminated +#ifdef __MINGW32__ +#undef __$compname_props +#define __$compname_props +#endif #if defined(UNICODE) || defined(_UNICODE) #define __$TCHAR unsigned short #else diff --git a/image/sys/strcodec.h b/image/sys/strcodec.h index 695a454..9fad5b6 100644 --- a/image/sys/strcodec.h +++ b/image/sys/strcodec.h @@ -59,7 +59,7 @@ //#ifdef WIN32 #if defined(WIN32) && !defined(UNDER_CE) // WIN32 seems to be defined always in VS2005 for ARM platform #define PLATFORM_X86 -#include "..\x86\x86.h" +#include "../x86/x86.h" #endif #ifndef UNREFERENCED_PARAMETER diff --git a/jxrgluelib/JXRMeta.h b/jxrgluelib/JXRMeta.h index b7b5880..7c9d653 100644 --- a/jxrgluelib/JXRMeta.h +++ b/jxrgluelib/JXRMeta.h @@ -111,6 +111,18 @@ #define __out_win __out #endif +#ifndef __in +#define __in +#endif +#ifndef __out +#define __out +#endif +#ifndef __in_ecount +#define __in_ecount(x) +#endif +#ifndef __out_ecount +#define __out_ecount(x) +#endif //================================================================ sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/guiddef.patch000066400000000000000000000010451516266226600235530ustar00rootroot00000000000000diff --git a/common/include/guiddef.h b/common/include/jxrguiddef.h similarity index 100% rename from common/include/guiddef.h rename to common/include/jxrguiddef.h diff --git a/jxrgluelib/JXRGlue.h b/jxrgluelib/JXRGlue.h index d0b219c..c3e5d2b 100644 --- a/jxrgluelib/JXRGlue.h +++ b/jxrgluelib/JXRGlue.h @@ -32,7 +32,11 @@ extern "C" { #endif #include +#ifdef _WIN32 #include +#else +#include +#endif //================================================================ #define WMP_SDK_VERSION 0x0101 sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/portfile.cmake000066400000000000000000000020751516266226600237550ustar00rootroot00000000000000vcpkg_check_linkage(ONLY_STATIC_LIBRARY) vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO 4creators/jxrlib REF f7521879862b9085318e814c6157490dd9dbbdb4 SHA512 f5617cbe73b6b905cc6bba181e6a3efedd59584f7a8c90e0f34db580cfdad4239a2ab753df4e221f26a5c0db51475b021052e3b9e3ab3673573573b1d57f3fdb HEAD_REF master PATCHES guiddef.patch fix-mingw.patch tmpnam.patch ) file(COPY "${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt" DESTINATION "${SOURCE_PATH}") vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" ) vcpkg_cmake_install() vcpkg_copy_pdbs() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/FindJXR.cmake" DESTINATION "${CURRENT_PACKAGES_DIR}/share/jxr") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" DESTINATION "${CURRENT_PACKAGES_DIR}/share/jxr") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/tmpnam.patch000066400000000000000000000023551516266226600234450ustar00rootroot00000000000000diff --git a/image/encode/strenc.c b/image/encode/strenc.c index d6e970e..1a7e0b2 100644 --- a/image/encode/strenc.c +++ b/image/encode/strenc.c @@ -482,11 +482,22 @@ Int StrIOEncInit(CWMImageStrCodec* pSC) pSC->ppTempFile[i] = (char *)malloc(FILENAME_MAX * sizeof(char)); if(pSC->ppTempFile[i] == NULL) return ICERR_ERROR; - if ((pFilename = tmpnam(NULL)) == NULL) + char tmpnambuf[] = {'f', 'i', 'l', 'e', 'X', 'X', 'X', 'X', 'X', 'X', '\0'}; + int fileDescriptor = mkstemp(tmpnambuf); + if (fileDescriptor == -1) return ICERR_ERROR; + close(fileDescriptor); strcpy(pSC->ppTempFile[i], pFilename); #endif - if(CreateWS_File(pSC->ppWStream + i, pFilename, "w+b") != ICERR_OK) return ICERR_ERROR; + if(CreateWS_File(pSC->ppWStream + i, pFilename, "w+b") != ICERR_OK) + { + #ifdef _WINDOWS_ + DeleteFileA(pFilename); + #else + remove(pFilename); + #endif + return ICERR_ERROR; + } } else { sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/usage000066400000000000000000000003161516266226600221520ustar00rootroot00000000000000The package jxrlib provides CMake integration: find_package(JXR REQUIRED) target_include_directories(main PRIVATE ${JXR_INCLUDE_DIRS}) target_link_libraries(main PRIVATE ${JXR_LIBRARIES}) sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/vcpkg-cmake-wrapper.cmake000066400000000000000000000002601516266226600257710ustar00rootroot00000000000000set(JXR_PREV_MODULE_PATH ${CMAKE_MODULE_PATH}) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) _find_package(${ARGS}) set(CMAKE_MODULE_PATH ${JXR_PREV_MODULE_PATH}) sdk-10.11.0/cmake/vcpkg_overlay_ports/jxrlib/vcpkg.json000066400000000000000000000005011516266226600231240ustar00rootroot00000000000000{ "name": "jxrlib", "version": "2019.10.9", "port-version": 7, "description": "Open source implementation of the jpegxr image format standard.", "homepage": "https://github.com/4creators/jxrlib", "license": "BSD-2-Clause", "dependencies": [ { "name": "vcpkg-cmake", "host": true } ] } sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/000077500000000000000000000000001516266226600217245ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/msvc_dotprod.patch000066400000000000000000000012301516266226600254440ustar00rootroot00000000000000diff --git a/lib/arm/cpu_features.h b/lib/arm/cpu_features.h index dc9ab8a..89bff65 100644 --- a/lib/arm/cpu_features.h +++ b/lib/arm/cpu_features.h @@ -189,7 +189,7 @@ static inline u32 get_arm_cpu_features(void) { return 0; } # define HAVE_DOTPROD(features) ((features) & ARM_CPU_FEATURE_DOTPROD) #endif #if defined(ARCH_ARM64) && HAVE_NEON_INTRIN && \ - (GCC_PREREQ(8, 1) || CLANG_PREREQ(7, 0, 10010000) || defined(_MSC_VER)) + (GCC_PREREQ(8, 1) || CLANG_PREREQ(7, 0, 10010000) || (defined(_MSC_VER) && _MSC_VER >= 1940)) # define HAVE_DOTPROD_INTRIN 1 /* * Use an inline assembly fallback for clang 15 and earlier, which only sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/portfile.cmake000066400000000000000000000036561516266226600245640ustar00rootroot00000000000000vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO ebiggers/libdeflate REF "v${VERSION}" SHA512 c20a772aeeac593c34e8a68be80b23cb116699141de269d94df072636b6c90572f541b3344d830325cf45b03e7a1303e0274d79ce96c360fd421d4eb05ae1f92 HEAD_REF master PATCHES remove_wrong_c_flags_modification.diff msvc_dotprod.patch ) vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS FEATURES compression LIBDEFLATE_COMPRESSION_SUPPORT decompression LIBDEFLATE_DECOMPRESSION_SUPPORT gzip LIBDEFLATE_GZIP_SUPPORT zlib LIBDEFLATE_ZLIB_SUPPORT ) string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" LIBDEFLATE_BUILD_STATIC) string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" LIBDEFLATE_BUILD_SHARED) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" OPTIONS -DLIBDEFLATE_BUILD_SHARED_LIB=${LIBDEFLATE_BUILD_SHARED} -DLIBDEFLATE_BUILD_STATIC_LIB=${LIBDEFLATE_BUILD_STATIC} -DLIBDEFLATE_BUILD_GZIP=OFF ${FEATURE_OPTIONS} ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/libdeflate") vcpkg_fixup_pkgconfig() if(VCPKG_TARGET_IS_WINDOWS) if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/include/libdeflate.h" "defined(LIBDEFLATE_DLL)" "1") elseif(NOT VCPKG_TARGET_IS_MINGW) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libdeflate.pc" " -ldeflate" " -ldeflatestatic") if(NOT VCPKG_BUILD_TYPE) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libdeflate.pc" " -ldeflate" " -ldeflatestatic") endif() endif() endif() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/remove_wrong_c_flags_modification.diff000066400000000000000000000006641516266226600315000ustar00rootroot00000000000000diff --git a/CMakeLists.txt b/CMakeLists.txt index 0acd26f..218c48b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,6 @@ if(NOT LIBDEFLATE_BUILD_STATIC_LIB) endif() # Set common C compiler flags for all targets (the library and the programs). -set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") set(CMAKE_C_STANDARD 99) if(NOT MSVC) check_c_compiler_flag(-Wdeclaration-after-statement HAVE_WDECLARATION_AFTER_STATEMENT) sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/usage000066400000000000000000000003521516266226600227530ustar00rootroot00000000000000libdeflate provides CMake targets: find_package(libdeflate CONFIG REQUIRED) target_link_libraries(main PRIVATE $,libdeflate::libdeflate_shared,libdeflate::libdeflate_static>) sdk-10.11.0/cmake/vcpkg_overlay_ports/libdeflate/vcpkg.json000066400000000000000000000014171516266226600237340ustar00rootroot00000000000000{ "name": "libdeflate", "version": "1.24", "description": "libdeflate is a library for fast, whole-buffer DEFLATE-based compression and decompression.", "homepage": "https://github.com/ebiggers/libdeflate", "license": "MIT", "dependencies": [ { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true } ], "default-features": [ "compression", "decompression", "gzip", "zlib" ], "features": { "compression": { "description": "Support compression" }, "decompression": { "description": "Support decompression" }, "gzip": { "description": "Support the gzip format" }, "zlib": { "description": "Support the zlib format" } } } sdk-10.11.0/cmake/vcpkg_overlay_ports/libmediainfo/000077500000000000000000000000001516266226600222535ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/libmediainfo/dependencies.diff000066400000000000000000000050021516266226600255300ustar00rootroot00000000000000diff --git a/Project/CMake/CMakeLists.txt b/Project/CMake/CMakeLists.txt index 700dce3..449a6dd 100644 --- a/Project/CMake/CMakeLists.txt +++ b/Project/CMake/CMakeLists.txt @@ -80,7 +80,7 @@ endif() set(MediaInfoLib_SOURCES_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../Source) # use bundled tinyxml only if no system -find_package(TinyXML) +find_package(TinyXML NAMES TinyXML2 REQUIRED) # use system curl if is present find_package(CURL) @@ -389,8 +389,7 @@ target_compile_definitions(mediainfo PRIVATE FMT_UNICODE=0) if(NOT CURL_FOUND) target_compile_definitions(mediainfo PRIVATE MEDIAINFO_LIBCURL_NO) else() - include_directories(${CURL_INCLUDE_DIRS}) - target_link_libraries(mediainfo ${CURL_LIBRARIES}) + target_link_libraries(mediainfo PRIVATE CURL::libcurl) set(CURL_PC " libcurl") set(CURL_LIB " -lcurl") endif() @@ -411,14 +410,14 @@ target_include_directories(mediainfo PRIVATE if(NOT TinyXML_FOUND) target_include_directories(mediainfo PRIVATE ${MediaInfoLib_SOURCES_PATH}/ThirdParty/tinyxml2/) else() - target_link_libraries(mediainfo "${TinyXML_LIBRARIES}") + target_link_libraries(mediainfo PRIVATE tinyxml2::tinyxml2) endif() if(BUILD_ZENLIB) target_include_directories(mediainfo PRIVATE ${ZLIB_INCLUDE_DIRS}) endif() -target_link_libraries(mediainfo ${ZenLib_LIBRARY} ${ZLIB_LIBRARIES}) +target_link_libraries(mediainfo PUBLIC zen PRIVATE ZLIB::ZLIB) if(MSVC AND BUILD_SHARED_LIBS) install(FILES $ DESTINATION ${BIN_INSTALL_DIR} OPTIONAL) diff --git a/Project/CMake/MediaInfoLibConfig.cmake.in b/Project/CMake/MediaInfoLibConfig.cmake.in index 76fec5d..97d8286 100644 --- a/Project/CMake/MediaInfoLibConfig.cmake.in +++ b/Project/CMake/MediaInfoLibConfig.cmake.in @@ -1,3 +1,11 @@ +include(CMakeFindDependencyMacro) +find_dependency(tinyxml2 CONFIG) +find_dependency(ZenLib CONFIG) +find_dependency(ZLIB) +if("@VCPKG_LOCK_FIND_PACKAGE_CURL@") + find_dependency(CURL) +endif() + # known at buildtime set(MediaInfoLib_VERSION "@MediaInfoLib_VERSION@") set(MediaInfoLib_VERSION_MAJOR @MediaInfoLib_MAJOR_VERSION@) diff --git a/Project/CMake/libmediainfo.pc.in b/Project/CMake/libmediainfo.pc.in index 31e53fe..2382088 100644 --- a/Project/CMake/libmediainfo.pc.in +++ b/Project/CMake/libmediainfo.pc.in @@ -8,6 +8,6 @@ Name: libmediainfo Version: @MediaInfoLib_VERSION@ Description: MediaInfoLib Requires: libzen -Requires.private:@CURL_PC@ -Libs: -L${libdir} -lmediainfo -lz +Requires.private: @CURL_PC@ tinyxml2 zlib +Libs: -L${libdir} -lmediainfo Cflags: -I${includedir} sdk-10.11.0/cmake/vcpkg_overlay_ports/libmediainfo/features.diff000066400000000000000000000042351516266226600247270ustar00rootroot00000000000000diff --git a/Project/CMake/CMakeLists.txt b/Project/CMake/CMakeLists.txt index 2496742..8146650 100644 --- a/Project/CMake/CMakeLists.txt +++ b/Project/CMake/CMakeLists.txt @@ -386,6 +386,74 @@ endif() # disable some features. Maybe it should be enabled. target_compile_definitions(mediainfo PRIVATE MEDIAINFO_LIBMMS_NO) target_compile_definitions(mediainfo PRIVATE FMT_UNICODE=0) +# Flags in order as defined in configure.ac. +target_compile_definitions(mediainfo + PRIVATE + # Global flags + MEDIAINFO_MINIMAL_YES + MEDIAINFO_CONFORMANCE_YES # Bug in the minimal configuration. + MEDIAINFO_DEMUX_YES # Fix some .wav parsing crashes + MEDIAINFO_EVENTS_YES # Required for DEMUX + MEDIAINFO_ARCHIVE_NO + # MEDIAINFO_IMAGE_NO + MEDIAINFO_TEXT_NO + + # Disable all tag except the id3 + MEDIAINFO_APETAG_NO + MEDIAINFO_C2PA_NO + # MEDIAINFO_EXIF_NO # Required by mpeg4 code + # MEDIAINFO_ICC_NO # Required by webp code + MEDIAINFO_IIM_NO + MEDIAINFO_LYRICS3_NO + MEDIAINFO_LYRICS3V2_NO + MEDIAINFO_PROPERTYLIST_NO + MEDIAINFO_SPHERICALVIDEO_NO + MEDIAINFO_VORBISCOM_NO + # MEDIAINFO_XMP_NO # Required by mpeg4 code + + # Disable some "Multiple" formats + MEDIAINFO_ANCILLARY_NO + MEDIAINFO_BDAV_NO + MEDIAINFO_BDMV_NO + MEDIAINFO_CDXA_NO + MEDIAINFO_DASHMPD_NO + MEDIAINFO_DCP_NO + MEDIAINFO_DPG_NO + MEDIAINFO_REFERENCES_NO + MEDIAINFO_DVDIF_NO + MEDIAINFO_DVDV_NO + MEDIAINFO_DXW_NO + MEDIAINFO_FLV_NO + MEDIAINFO_GXF_NO + MEDIAINFO_HDSF4M_NO + MEDIAINFO_HLS_NO + MEDIAINFO_MIXML_NO + MEDIAINFO_MXF_NO + MEDIAINFO_NUT_NO + MEDIAINFO_PMP_NO + MEDIAINFO_SKM_NO + MEDIAINFO_SWF_NO + MEDIAINFO_VBI_NO + + # Disable some "Video" formats + MEDIAINFO_AIC_NO + MEDIAINFO_AV2_NO + MEDIAINFO_AVSV_NO + MEDIAINFO_CANOPUS_NO + MEDIAINFO_FFV1_NO + MEDIAINFO_FLIC_NO + MEDIAINFO_HUFFYUV_NO + MEDIAINFO_VC3_NO + MEDIAINFO_Y4M_NO + + # Disable some "Audio" formats + MEDIAINFO_ADIF_NO + MEDIAINFO_ADPCM_NO + MEDIAINFO_CELT_NO + MEDIAINFO_DAT_NO + MEDIAINFO_PS2A_NO + MEDIAINFO_TTA_NO +) if(NOT CURL_FOUND) target_compile_definitions(mediainfo PRIVATE MEDIAINFO_LIBCURL_NO) else() sdk-10.11.0/cmake/vcpkg_overlay_ports/libmediainfo/portfile.cmake000066400000000000000000000023751516266226600251100ustar00rootroot00000000000000string(REGEX REPLACE "^([0-9]+)[.]([1-9])\$" "\\1.0\\2" MEDIAINFO_VERSION "${VERSION}") vcpkg_from_github( OUT_SOURCE_PATH SOURCE_PATH REPO MediaArea/MediaInfoLib REF "v${MEDIAINFO_VERSION}" SHA512 90151a43cb8c2882f0e4529960fae2ed585982b6d36711e0fe435dbca6fbdd234dc130e2fd7dc7546902959247f730618a4baccd6f8ede66c04ed06b4a4975ad HEAD_REF master PATCHES dependencies.diff features.diff ) file(REMOVE_RECURSE "${SOURCE_PATH}/Source/ThirdParty/tinyxml2") vcpkg_find_acquire_program(PKGCONFIG) set(ENV{PKG_CONFIG} "${PKGCONFIG}") vcpkg_check_features(OUT_FEATURE_OPTIONS options FEATURES curl VCPKG_LOCK_FIND_PACKAGE_CURL ) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}/Project/CMake" OPTIONS ${options} -DBUILD_ZENLIB=0 -DBUILD_ZLIB=0 -DCMAKE_REQUIRE_FIND_PACKAGE_PkgConfig=1 ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(PACKAGE_NAME mediainfolib) vcpkg_fixup_pkgconfig() if(NOT VCPKG_BUILD_TYPE AND VCPKG_TARGET_IS_WINDOWS) vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libmediainfo.pc" " -lmediainfo" " -lmediainfod") endif() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE") sdk-10.11.0/cmake/vcpkg_overlay_ports/libmediainfo/vcpkg.json000066400000000000000000000011301516266226600242530ustar00rootroot00000000000000{ "name": "libmediainfo", "version": "25.10", "description": "Get most relevant technical and tag data from video and audio files", "homepage": "https://github.com/MediaArea/MediaInfoLib", "dependencies": [ "libzen", "tinyxml2", { "name": "vcpkg-cmake", "host": true }, { "name": "vcpkg-cmake-config", "host": true }, "zlib" ], "features": { "curl": { "description": "Use curl for network access", "dependencies": [ { "name": "curl", "default-features": false } ] } } } sdk-10.11.0/cmake/vcpkg_overlay_ports/ncurses/000077500000000000000000000000001516266226600213135ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/ncurses/portfile.cmake000066400000000000000000000041651516266226600241470ustar00rootroot00000000000000vcpkg_download_distfile( ARCHIVE_PATH URLS "https://invisible-mirror.net/archives/ncurses/ncurses-${VERSION}.tar.gz" "ftp://ftp.invisible-island.net/ncurses/ncurses-${VERSION}.tar.gz" "https://ftp.gnu.org/gnu/ncurses/ncurses-${VERSION}.tar.gz" FILENAME "ncurses-${VERSION}.tgz" SHA512 fc5a13409d2a530a1325776dcce3a99127ddc2c03999cfeb0065d0eee2d68456274fb1c7b3cc99c1937bc657d0e7fca97016e147f93c7821b5a4a6837db821e8 ) vcpkg_extract_source_archive( SOURCE_PATH ARCHIVE "${ARCHIVE_PATH}" ) vcpkg_list(SET OPTIONS) if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") list(APPEND OPTIONS --with-cxx-shared --with-shared # "lib model" --without-normal # "lib model" ) endif() if(NOT "wide-char" IN_LIST FEATURES) list(APPEND OPTIONS --disable-widec # Enabled by default starting at 6.5. Disabled by default here for compatibility. ) endif() if(NOT VCPKG_TARGET_IS_MINGW) list(APPEND OPTIONS --enable-mixed-case ) endif() if(VCPKG_TARGET_IS_MINGW) list(APPEND OPTIONS --disable-home-terminfo --enable-term-driver --disable-termcap ) endif() if(VCPKG_TARGET_IS_LINUX) list(APPEND OPTIONS "CFLAGS=-std=gnu17") endif() vcpkg_configure_make( SOURCE_PATH "${SOURCE_PATH}" DETERMINE_BUILD_TRIPLET NO_ADDITIONAL_PATHS OPTIONS ${OPTIONS} --disable-db-install --enable-pc-files --without-ada --without-debug # "lib model" --without-manpages --without-progs --without-tack --without-tests --with-pkg-config-libdir=libdir ) vcpkg_install_make() vcpkg_fixup_pkgconfig() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/bin") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share") file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") sdk-10.11.0/cmake/vcpkg_overlay_ports/ncurses/usage000066400000000000000000000005071516266226600223440ustar00rootroot00000000000000The package ncurses is compatible with built-in CMake variables: set(CURSES_NEED_NCURSES TRUE) find_package(Curses REQUIRED) target_include_directories(main PRIVATE ${CURSES_INCLUDE_DIRS}) target_compile_options(main PRIVATE ${CURSES_CFLAGS}) target_link_libraries(main PRIVATE ${CURSES_LIBRARIES}) sdk-10.11.0/cmake/vcpkg_overlay_ports/ncurses/vcpkg.json000066400000000000000000000005711516266226600233230ustar00rootroot00000000000000{ "name": "ncurses", "version": "6.5", "port-version": 0, "description": "Free software emulation of curses in System V Release 4.0, and more", "homepage": "https://invisible-island.net/ncurses/announce.html", "license": "MIT", "supports": "!windows | mingw", "features": { "wide-char": { "description": "Compile with wide-character code" } } } sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/000077500000000000000000000000001516266226600211155ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/CMakeLists.txt000066400000000000000000000657521516266226600236740ustar00rootroot00000000000000 cmake_minimum_required(VERSION 3.19) project(pdfium_by_cmake) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG -DDEBUG") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(WIN32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /EHsc /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /MP ") add_definitions(-DHAVE_BOOLEAN -DNOMINMAX -DWIN32_LEAN_AND_MEAN) endif() if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" ) endif() add_definitions(-DCMS_NO_REGISTER_KEYWORD -DUNICODE -D_UNICODE -DUSE_LIBJPEG_TURBO -DPNG_PREFIX -DUSE_SYSTEM_ICUUC -DUSE_SYSTEM_LIBJPEG -DUSE_SYSTEM_LCMS2 -DUSE_SYSTEM_ZLIB -DUSE_SYSTEM_LIBOPENJPEG2 -DOPJ_STATIC -DDEFINE_PS_TABLES) # no skia, V8, or xfa (xfa seems to require V8 anyway) find_package(OpenJPEG REQUIRED) find_package(lcms REQUIRED) find_package(JPEG REQUIRED) find_package(libpng REQUIRED) find_package(Freetype REQUIRED) find_package(ICU COMPONENTS uc REQUIRED) include_directories( ${JPEG_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR}/third_party/abseil-cpp "." ) if(WIN32) set(WIN 1) else() set(WIN 0) endif() if (APPLE) set(MAC 1) else() set(MAC 0) endif() if (NOT WIN32 AND NOT APPLE) set(LINUX 1) else() set(LINUX 0) endif() set(ANDROID 0) add_library(pdfium STATIC) # File extraction: # Configure the project using the depot_tools, using the following parameters. It may require some adjustments depending on the OS: # gn gen ../out/build_x64 --args="is_component_build=false use_custom_libcxx=false treat_warnings_as_errors=false fatal_linker_warnings=false pdf_enable_v8=false pdf_is_complete_lib=true use_sysroot=false is_clang=false is_debug=true" # Compile with ninja using -v and -j1 parameters. # ninja -C ../out/build_x64 -j1 -v :pdfium > build_output.txt # Process the output with the following AWK script: # awk ' # { # for (i = 1; i < NF; i++) { # if ($i ~ /\.(c|cc|cpp|cxx|C|c\+\+)$/ && ($(i+1) == "-o" || $(i+1) ~ /\.(obj)$/)) { # sub("^\\.\\.\\/\\.\\.\\/pdfium/", "", $i) # print $i # } # } # } # ' build_output.txt > build_output_processed.txt # Output will need some manual parsing and adjustments. target_sources(pdfium PRIVATE constants/annotation_common.cpp constants/appearance.cpp constants/font_encodings.cpp constants/form_fields.cpp constants/page_object.cpp constants/stream_dict_common.cpp constants/transparency.cpp core/fdrm/fx_crypt.cpp core/fdrm/fx_crypt_aes.cpp core/fdrm/fx_crypt_sha.cpp core/fpdfapi/cmaps/CNS1/Adobe-CNS1-UCS2_5.cpp core/fpdfapi/cmaps/CNS1/B5pc-H_0.cpp core/fpdfapi/cmaps/CNS1/B5pc-V_0.cpp core/fpdfapi/cmaps/CNS1/CNS-EUC-H_0.cpp core/fpdfapi/cmaps/CNS1/CNS-EUC-V_0.cpp core/fpdfapi/cmaps/CNS1/ETen-B5-H_0.cpp core/fpdfapi/cmaps/CNS1/ETen-B5-V_0.cpp core/fpdfapi/cmaps/CNS1/ETenms-B5-H_0.cpp core/fpdfapi/cmaps/CNS1/ETenms-B5-V_0.cpp core/fpdfapi/cmaps/CNS1/HKscs-B5-H_5.cpp core/fpdfapi/cmaps/CNS1/HKscs-B5-V_5.cpp core/fpdfapi/cmaps/CNS1/UniCNS-UCS2-H_3.cpp core/fpdfapi/cmaps/CNS1/UniCNS-UCS2-V_3.cpp core/fpdfapi/cmaps/CNS1/UniCNS-UTF16-H_0.cpp core/fpdfapi/cmaps/CNS1/cmaps_cns1.cpp core/fpdfapi/cmaps/GB1/Adobe-GB1-UCS2_5.cpp core/fpdfapi/cmaps/GB1/GB-EUC-H_0.cpp core/fpdfapi/cmaps/GB1/GB-EUC-V_0.cpp core/fpdfapi/cmaps/GB1/GBK-EUC-H_2.cpp core/fpdfapi/cmaps/GB1/GBK-EUC-V_2.cpp core/fpdfapi/cmaps/GB1/GBK2K-H_5.cpp core/fpdfapi/cmaps/GB1/GBK2K-V_5.cpp core/fpdfapi/cmaps/GB1/GBKp-EUC-H_2.cpp core/fpdfapi/cmaps/GB1/GBKp-EUC-V_2.cpp core/fpdfapi/cmaps/GB1/GBpc-EUC-H_0.cpp core/fpdfapi/cmaps/GB1/GBpc-EUC-V_0.cpp core/fpdfapi/cmaps/GB1/UniGB-UCS2-H_4.cpp core/fpdfapi/cmaps/GB1/UniGB-UCS2-V_4.cpp core/fpdfapi/cmaps/GB1/cmaps_gb1.cpp core/fpdfapi/cmaps/Japan1/83pv-RKSJ-H_1.cpp core/fpdfapi/cmaps/Japan1/90ms-RKSJ-H_2.cpp core/fpdfapi/cmaps/Japan1/90ms-RKSJ-V_2.cpp core/fpdfapi/cmaps/Japan1/90msp-RKSJ-H_2.cpp core/fpdfapi/cmaps/Japan1/90msp-RKSJ-V_2.cpp core/fpdfapi/cmaps/Japan1/90pv-RKSJ-H_1.cpp core/fpdfapi/cmaps/Japan1/Add-RKSJ-H_1.cpp core/fpdfapi/cmaps/Japan1/Add-RKSJ-V_1.cpp core/fpdfapi/cmaps/Japan1/Adobe-Japan1-UCS2_4.cpp core/fpdfapi/cmaps/Japan1/EUC-H_1.cpp core/fpdfapi/cmaps/Japan1/EUC-V_1.cpp core/fpdfapi/cmaps/Japan1/Ext-RKSJ-H_2.cpp core/fpdfapi/cmaps/Japan1/Ext-RKSJ-V_2.cpp core/fpdfapi/cmaps/Japan1/H_1.cpp core/fpdfapi/cmaps/Japan1/UniJIS-UCS2-HW-H_4.cpp core/fpdfapi/cmaps/Japan1/UniJIS-UCS2-HW-V_4.cpp core/fpdfapi/cmaps/Japan1/UniJIS-UCS2-H_4.cpp core/fpdfapi/cmaps/Japan1/UniJIS-UCS2-V_4.cpp core/fpdfapi/cmaps/Japan1/V_1.cpp core/fpdfapi/cmaps/Japan1/cmaps_japan1.cpp core/fpdfapi/cmaps/Korea1/Adobe-Korea1-UCS2_2.cpp core/fpdfapi/cmaps/Korea1/KSC-EUC-H_0.cpp core/fpdfapi/cmaps/Korea1/KSC-EUC-V_0.cpp core/fpdfapi/cmaps/Korea1/KSCms-UHC-HW-H_1.cpp core/fpdfapi/cmaps/Korea1/KSCms-UHC-HW-V_1.cpp core/fpdfapi/cmaps/Korea1/KSCms-UHC-H_1.cpp core/fpdfapi/cmaps/Korea1/KSCms-UHC-V_1.cpp core/fpdfapi/cmaps/Korea1/KSCpc-EUC-H_0.cpp core/fpdfapi/cmaps/Korea1/UniKS-UCS2-H_1.cpp core/fpdfapi/cmaps/Korea1/UniKS-UCS2-V_1.cpp core/fpdfapi/cmaps/Korea1/UniKS-UTF16-H_0.cpp core/fpdfapi/cmaps/Korea1/cmaps_korea1.cpp core/fpdfapi/cmaps/fpdf_cmaps.cpp core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp core/fpdfapi/edit/cpdf_creator.cpp core/fpdfapi/edit/cpdf_npagetooneexporter.cpp core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp core/fpdfapi/edit/cpdf_pagecontentmanager.cpp core/fpdfapi/edit/cpdf_pageexporter.cpp core/fpdfapi/edit/cpdf_pageorganizer.cpp core/fpdfapi/edit/cpdf_stringarchivestream.cpp core/fpdfapi/font/cfx_cttgsubtable.cpp core/fpdfapi/font/cfx_stockfontarray.cpp core/fpdfapi/font/cpdf_cid2unicodemap.cpp core/fpdfapi/font/cpdf_cidfont.cpp core/fpdfapi/font/cpdf_cmap.cpp core/fpdfapi/font/cpdf_cmapparser.cpp core/fpdfapi/font/cpdf_font.cpp core/fpdfapi/font/cpdf_fontencoding.cpp core/fpdfapi/font/cpdf_fontglobals.cpp core/fpdfapi/font/cpdf_simplefont.cpp core/fpdfapi/font/cpdf_tounicodemap.cpp core/fpdfapi/font/cpdf_truetypefont.cpp core/fpdfapi/font/cpdf_type1font.cpp core/fpdfapi/font/cpdf_type3char.cpp core/fpdfapi/font/cpdf_type3font.cpp core/fpdfapi/page/cpdf_allstates.cpp core/fpdfapi/page/cpdf_annotcontext.cpp core/fpdfapi/page/cpdf_basedcs.cpp core/fpdfapi/page/cpdf_clippath.cpp core/fpdfapi/page/cpdf_color.cpp core/fpdfapi/page/cpdf_colorspace.cpp core/fpdfapi/page/cpdf_colorstate.cpp core/fpdfapi/page/cpdf_contentmarkitem.cpp core/fpdfapi/page/cpdf_contentmarks.cpp core/fpdfapi/page/cpdf_contentparser.cpp core/fpdfapi/page/cpdf_devicecs.cpp core/fpdfapi/page/cpdf_dib.cpp core/fpdfapi/page/cpdf_docpagedata.cpp core/fpdfapi/page/cpdf_expintfunc.cpp core/fpdfapi/page/cpdf_form.cpp core/fpdfapi/page/cpdf_formobject.cpp core/fpdfapi/page/cpdf_function.cpp core/fpdfapi/page/cpdf_generalstate.cpp core/fpdfapi/page/cpdf_graphicstates.cpp core/fpdfapi/page/cpdf_iccprofile.cpp core/fpdfapi/page/cpdf_image.cpp core/fpdfapi/page/cpdf_imageloader.cpp core/fpdfapi/page/cpdf_imageobject.cpp core/fpdfapi/page/cpdf_indexedcs.cpp core/fpdfapi/page/cpdf_meshstream.cpp core/fpdfapi/page/cpdf_occontext.cpp core/fpdfapi/page/cpdf_page.cpp core/fpdfapi/page/cpdf_pageimagecache.cpp core/fpdfapi/page/cpdf_pagemodule.cpp core/fpdfapi/page/cpdf_pageobject.cpp core/fpdfapi/page/cpdf_pageobjectholder.cpp core/fpdfapi/page/cpdf_path.cpp core/fpdfapi/page/cpdf_pathobject.cpp core/fpdfapi/page/cpdf_pattern.cpp core/fpdfapi/page/cpdf_patterncs.cpp core/fpdfapi/page/cpdf_psengine.cpp core/fpdfapi/page/cpdf_psfunc.cpp core/fpdfapi/page/cpdf_sampledfunc.cpp core/fpdfapi/page/cpdf_shadingobject.cpp core/fpdfapi/page/cpdf_shadingpattern.cpp core/fpdfapi/page/cpdf_stitchfunc.cpp core/fpdfapi/page/cpdf_streamcontentparser.cpp core/fpdfapi/page/cpdf_streamparser.cpp core/fpdfapi/page/cpdf_textobject.cpp core/fpdfapi/page/cpdf_textstate.cpp core/fpdfapi/page/cpdf_tilingpattern.cpp core/fpdfapi/page/cpdf_transferfunc.cpp core/fpdfapi/page/cpdf_transferfuncdib.cpp core/fpdfapi/page/cpdf_transparency.cpp core/fpdfapi/parser/cfdf_document.cpp core/fpdfapi/parser/cpdf_array.cpp core/fpdfapi/parser/cpdf_boolean.cpp core/fpdfapi/parser/cpdf_cross_ref_avail.cpp core/fpdfapi/parser/cpdf_cross_ref_table.cpp core/fpdfapi/parser/cpdf_crypto_handler.cpp core/fpdfapi/parser/cpdf_data_avail.cpp core/fpdfapi/parser/cpdf_dictionary.cpp core/fpdfapi/parser/cpdf_document.cpp core/fpdfapi/parser/cpdf_encryptor.cpp core/fpdfapi/parser/cpdf_flateencoder.cpp core/fpdfapi/parser/cpdf_hint_tables.cpp core/fpdfapi/parser/cpdf_indirect_object_holder.cpp core/fpdfapi/parser/cpdf_linearized_header.cpp core/fpdfapi/parser/cpdf_name.cpp core/fpdfapi/parser/cpdf_null.cpp core/fpdfapi/parser/cpdf_number.cpp core/fpdfapi/parser/cpdf_object.cpp core/fpdfapi/parser/cpdf_object_avail.cpp core/fpdfapi/parser/cpdf_object_stream.cpp core/fpdfapi/parser/cpdf_object_walker.cpp core/fpdfapi/parser/cpdf_page_object_avail.cpp core/fpdfapi/parser/cpdf_parser.cpp core/fpdfapi/parser/cpdf_read_validator.cpp core/fpdfapi/parser/cpdf_reference.cpp core/fpdfapi/parser/cpdf_security_handler.cpp core/fpdfapi/parser/cpdf_simple_parser.cpp core/fpdfapi/parser/cpdf_stream.cpp core/fpdfapi/parser/cpdf_stream_acc.cpp core/fpdfapi/parser/cpdf_string.cpp core/fpdfapi/parser/cpdf_syntax_parser.cpp core/fpdfapi/parser/fpdf_parser_decode.cpp core/fpdfapi/parser/fpdf_parser_utility.cpp core/fpdfapi/parser/object_tree_traversal_util.cpp core/fpdfapi/render/charposlist.cpp core/fpdfapi/render/cpdf_devicebuffer.cpp core/fpdfapi/render/cpdf_docrenderdata.cpp core/fpdfapi/render/cpdf_imagerenderer.cpp core/fpdfapi/render/cpdf_pagerendercontext.cpp core/fpdfapi/render/cpdf_progressiverenderer.cpp core/fpdfapi/render/cpdf_rendercontext.cpp core/fpdfapi/render/cpdf_renderoptions.cpp core/fpdfapi/render/cpdf_rendershading.cpp core/fpdfapi/render/cpdf_renderstatus.cpp core/fpdfapi/render/cpdf_rendertiling.cpp core/fpdfapi/render/cpdf_textrenderer.cpp core/fpdfapi/render/cpdf_type3cache.cpp core/fpdfapi/render/cpdf_type3glyphmap.cpp $<${WIN}:core/fpdfapi/render/cpdf_scaledrenderbuffer.cpp> $<${WIN}:core/fpdfapi/render/cpdf_windowsrenderdevice.cpp> core/fpdfdoc/cpdf_aaction.cpp core/fpdfdoc/cpdf_action.cpp core/fpdfdoc/cpdf_annot.cpp core/fpdfdoc/cpdf_annotlist.cpp core/fpdfdoc/cpdf_apsettings.cpp core/fpdfdoc/cpdf_bafontmap.cpp core/fpdfdoc/cpdf_bookmark.cpp core/fpdfdoc/cpdf_bookmarktree.cpp core/fpdfdoc/cpdf_color_utils.cpp core/fpdfdoc/cpdf_defaultappearance.cpp core/fpdfdoc/cpdf_dest.cpp core/fpdfdoc/cpdf_filespec.cpp core/fpdfdoc/cpdf_formcontrol.cpp core/fpdfdoc/cpdf_formfield.cpp core/fpdfdoc/cpdf_generateap.cpp core/fpdfdoc/cpdf_icon.cpp core/fpdfdoc/cpdf_iconfit.cpp core/fpdfdoc/cpdf_interactiveform.cpp core/fpdfdoc/cpdf_link.cpp core/fpdfdoc/cpdf_linklist.cpp core/fpdfdoc/cpdf_metadata.cpp core/fpdfdoc/cpdf_nametree.cpp core/fpdfdoc/cpdf_numbertree.cpp core/fpdfdoc/cpdf_pagelabel.cpp core/fpdfdoc/cpdf_structelement.cpp core/fpdfdoc/cpdf_structtree.cpp core/fpdfdoc/cpdf_viewerpreferences.cpp core/fpdfdoc/cpvt_fontmap.cpp core/fpdfdoc/cpvt_section.cpp core/fpdfdoc/cpvt_variabletext.cpp core/fpdfdoc/cpvt_wordinfo.cpp core/fpdftext/cpdf_linkextract.cpp core/fpdftext/cpdf_textpage.cpp core/fpdftext/cpdf_textpagefind.cpp core/fpdftext/unicodenormalizationdata.cpp core/fxcodec/basic/basicmodule.cpp core/fxcodec/data_and_bytes_consumed.cpp core/fxcodec/fax/faxmodule.cpp core/fxcodec/flate/flatemodule.cpp core/fxcodec/fx_codec.cpp core/fxcodec/icc/icc_transform.cpp core/fxcodec/jbig2/JBig2_ArithDecoder.cpp core/fxcodec/jbig2/JBig2_ArithIntDecoder.cpp core/fxcodec/jbig2/JBig2_BitStream.cpp core/fxcodec/jbig2/JBig2_Context.cpp core/fxcodec/jbig2/JBig2_DocumentContext.cpp core/fxcodec/jbig2/JBig2_GrdProc.cpp core/fxcodec/jbig2/JBig2_GrrdProc.cpp core/fxcodec/jbig2/JBig2_HtrdProc.cpp core/fxcodec/jbig2/JBig2_HuffmanDecoder.cpp core/fxcodec/jbig2/JBig2_HuffmanTable.cpp core/fxcodec/jbig2/JBig2_Image.cpp core/fxcodec/jbig2/JBig2_PatternDict.cpp core/fxcodec/jbig2/JBig2_PddProc.cpp core/fxcodec/jbig2/JBig2_SddProc.cpp core/fxcodec/jbig2/JBig2_Segment.cpp core/fxcodec/jbig2/JBig2_SymbolDict.cpp core/fxcodec/jbig2/JBig2_TrdProc.cpp core/fxcodec/jbig2/jbig2_decoder.cpp core/fxcodec/jpeg/jpeg_common.cpp core/fxcodec/jpeg/jpegmodule.cpp core/fxcodec/jpx/cjpx_decoder.cpp core/fxcodec/jpx/jpx_decode_utils.cpp core/fxcodec/scanlinedecoder.cpp core/fxcrt/binary_buffer.cpp core/fxcrt/bytestring.cpp core/fxcrt/cfx_bitstream.cpp core/fxcrt/cfx_datetime.cpp core/fxcrt/cfx_read_only_span_stream.cpp core/fxcrt/cfx_read_only_string_stream.cpp core/fxcrt/cfx_read_only_vector_stream.cpp core/fxcrt/cfx_seekablestreamproxy.cpp core/fxcrt/cfx_timer.cpp core/fxcrt/debug/alias.cc core/fxcrt/fx_bidi.cpp core/fxcrt/fx_codepage.cpp core/fxcrt/fx_coordinates.cpp core/fxcrt/fx_extension.cpp core/fxcrt/fx_memory.cpp core/fxcrt/fx_number.cpp core/fxcrt/fx_random.cpp core/fxcrt/fx_stream.cpp core/fxcrt/fx_string.cpp core/fxcrt/fx_system.cpp core/fxcrt/fx_unicode.cpp core/fxcrt/observed_ptr.cpp core/fxcrt/string_data_template.cpp core/fxcrt/string_template.cpp core/fxcrt/widestring.cpp core/fxcrt/widetext_buffer.cpp core/fxcrt/xml/cfx_xmlchardata.cpp core/fxcrt/xml/cfx_xmldocument.cpp core/fxcrt/xml/cfx_xmlelement.cpp core/fxcrt/xml/cfx_xmlinstruction.cpp core/fxcrt/xml/cfx_xmlnode.cpp core/fxcrt/xml/cfx_xmlparser.cpp core/fxcrt/xml/cfx_xmltext.cpp core/fxcrt/fx_memory_malloc.cpp $<${WIN}:core/fxcrt/cfx_fileaccess_windows.cpp> $<${MAC}:core/fxcrt/cfx_fileaccess_posix.cpp> $<${LINUX}:core/fxcrt/cfx_fileaccess_posix.cpp> $<${WIN}:core/fxcrt/code_point_view.cpp> $<${WIN}:core/fxcrt/fx_folder_windows.cpp> $<${MAC}:core/fxcrt/fx_folder_posix.cpp> $<${LINUX}:core/fxcrt/fx_folder_posix.cpp> $<${WIN}:core/fxcrt/win/win_util.cc> core/fxge/agg/cfx_agg_bitmapcomposer.cpp core/fxge/agg/cfx_agg_cliprgn.cpp core/fxge/agg/cfx_agg_devicedriver.cpp core/fxge/agg/cfx_agg_imagerenderer.cpp core/fxge/calculate_pitch.cpp core/fxge/cfx_color.cpp core/fxge/cfx_defaultrenderdevice.cpp core/fxge/cfx_drawutils.cpp core/fxge/cfx_face.cpp core/fxge/cfx_folderfontinfo.cpp core/fxge/cfx_font.cpp core/fxge/cfx_fontcache.cpp core/fxge/cfx_fontmapper.cpp core/fxge/cfx_fontmgr.cpp core/fxge/cfx_gemodule.cpp core/fxge/cfx_glyphbitmap.cpp core/fxge/cfx_glyphcache.cpp core/fxge/cfx_graphstate.cpp core/fxge/cfx_graphstatedata.cpp core/fxge/cfx_path.cpp core/fxge/cfx_renderdevice.cpp core/fxge/cfx_substfont.cpp core/fxge/cfx_unicodeencoding.cpp core/fxge/dib/blend.cpp core/fxge/dib/cfx_bitmapstorer.cpp core/fxge/dib/cfx_cmyk_to_srgb.cpp core/fxge/dib/cfx_dibbase.cpp core/fxge/dib/cfx_dibitmap.cpp core/fxge/dib/cfx_imagestretcher.cpp core/fxge/dib/cfx_imagetransformer.cpp core/fxge/dib/cfx_scanlinecompositor.cpp core/fxge/dib/cstretchengine.cpp core/fxge/dib/fx_dib.cpp core/fxge/fontdata/chromefontdata/FoxitDingbats.cpp core/fxge/fontdata/chromefontdata/FoxitFixed.cpp core/fxge/fontdata/chromefontdata/FoxitFixedBold.cpp core/fxge/fontdata/chromefontdata/FoxitFixedBoldItalic.cpp core/fxge/fontdata/chromefontdata/FoxitFixedItalic.cpp core/fxge/fontdata/chromefontdata/FoxitSans.cpp core/fxge/fontdata/chromefontdata/FoxitSansBold.cpp core/fxge/fontdata/chromefontdata/FoxitSansBoldItalic.cpp core/fxge/fontdata/chromefontdata/FoxitSansItalic.cpp core/fxge/fontdata/chromefontdata/FoxitSansMM.cpp core/fxge/fontdata/chromefontdata/FoxitSerif.cpp core/fxge/fontdata/chromefontdata/FoxitSerifBold.cpp core/fxge/fontdata/chromefontdata/FoxitSerifBoldItalic.cpp core/fxge/fontdata/chromefontdata/FoxitSerifItalic.cpp core/fxge/fontdata/chromefontdata/FoxitSerifMM.cpp core/fxge/fontdata/chromefontdata/FoxitSymbol.cpp core/fxge/freetype/fx_freetype.cpp core/fxge/fx_font.cpp core/fxge/renderdevicedriver_iface.cpp core/fxge/scoped_font_transform.cpp core/fxge/text_char_pos.cpp core/fxge/text_glyph_pos.cpp $<${LINUX}:core/fxge/linux/fx_linux_impl.cpp> $<${MAC}:core/fxge/apple/fx_apple_impl.cpp> $<${MAC}:core/fxge/apple/fx_apple_platform.cpp> $<${MAC}:core/fxge/apple/fx_quartz_device.cpp> $<${WIN}:core/fxge/cfx_windowsrenderdevice.cpp> $<${WIN}:core/fxge/win32/cfx_psfonttracker.cpp> $<${WIN}:core/fxge/win32/cfx_psrenderer.cpp> $<${WIN}:core/fxge/win32/cgdi_device_driver.cpp> $<${WIN}:core/fxge/win32/cgdi_display_driver.cpp> $<${WIN}:core/fxge/win32/cgdi_plus_ext.cpp> $<${WIN}:core/fxge/win32/cgdi_printer_driver.cpp> $<${WIN}:core/fxge/win32/cps_printer_driver.cpp> $<${WIN}:core/fxge/win32/cpsoutput.cpp> $<${WIN}:core/fxge/win32/ctext_only_printer_driver.cpp> $<${WIN}:core/fxge/win32/cwin32_platform.cpp> fpdfsdk/cpdfsdk_annot.cpp fpdfsdk/cpdfsdk_annotiteration.cpp fpdfsdk/cpdfsdk_annotiterator.cpp fpdfsdk/cpdfsdk_appstream.cpp fpdfsdk/cpdfsdk_baannot.cpp fpdfsdk/cpdfsdk_customaccess.cpp fpdfsdk/cpdfsdk_filewriteadapter.cpp fpdfsdk/cpdfsdk_formfillenvironment.cpp fpdfsdk/cpdfsdk_helpers.cpp fpdfsdk/cpdfsdk_interactiveform.cpp fpdfsdk/cpdfsdk_pageview.cpp fpdfsdk/cpdfsdk_pauseadapter.cpp fpdfsdk/cpdfsdk_renderpage.cpp fpdfsdk/cpdfsdk_widget.cpp fpdfsdk/fpdf_annot.cpp fpdfsdk/fpdf_attachment.cpp fpdfsdk/fpdf_catalog.cpp fpdfsdk/fpdf_dataavail.cpp fpdfsdk/fpdf_doc.cpp fpdfsdk/fpdf_editimg.cpp fpdfsdk/fpdf_editpage.cpp fpdfsdk/fpdf_editpath.cpp fpdfsdk/fpdf_edittext.cpp fpdfsdk/fpdf_ext.cpp fpdfsdk/fpdf_flatten.cpp fpdfsdk/fpdf_formfill.cpp fpdfsdk/fpdf_javascript.cpp fpdfsdk/fpdf_ppo.cpp fpdfsdk/fpdf_progressive.cpp fpdfsdk/fpdf_save.cpp fpdfsdk/fpdf_searchex.cpp fpdfsdk/fpdf_signature.cpp fpdfsdk/fpdf_structtree.cpp fpdfsdk/fpdf_sysfontinfo.cpp fpdfsdk/fpdf_text.cpp fpdfsdk/fpdf_thumbnail.cpp fpdfsdk/fpdf_transformpage.cpp fpdfsdk/fpdf_view.cpp fpdfsdk/formfiller/cffl_button.cpp fpdfsdk/formfiller/cffl_checkbox.cpp fpdfsdk/formfiller/cffl_combobox.cpp fpdfsdk/formfiller/cffl_fieldaction.cpp fpdfsdk/formfiller/cffl_formfield.cpp fpdfsdk/formfiller/cffl_interactiveformfiller.cpp fpdfsdk/formfiller/cffl_listbox.cpp fpdfsdk/formfiller/cffl_perwindowdata.cpp fpdfsdk/formfiller/cffl_pushbutton.cpp fpdfsdk/formfiller/cffl_radiobutton.cpp fpdfsdk/formfiller/cffl_textfield.cpp fpdfsdk/formfiller/cffl_textobject.cpp fpdfsdk/pwl/cpwl_button.cpp fpdfsdk/pwl/cpwl_caret.cpp fpdfsdk/pwl/cpwl_cbbutton.cpp fpdfsdk/pwl/cpwl_cblistbox.cpp fpdfsdk/pwl/cpwl_combo_box.cpp fpdfsdk/pwl/cpwl_edit.cpp fpdfsdk/pwl/cpwl_edit_impl.cpp fpdfsdk/pwl/cpwl_list_box.cpp fpdfsdk/pwl/cpwl_list_ctrl.cpp fpdfsdk/pwl/cpwl_sbbutton.cpp fpdfsdk/pwl/cpwl_scroll_bar.cpp fpdfsdk/pwl/cpwl_special_button.cpp fpdfsdk/pwl/cpwl_wnd.cpp fxjs/cjs_event_context_stub.cpp fxjs/cjs_runtimestub.cpp fxjs/ijs_runtime.cpp third_party/agg23/agg_curves.cpp third_party/agg23/agg_path_storage.cpp third_party/agg23/agg_rasterizer_scanline_aa.cpp third_party/agg23/agg_vcgen_dash.cpp third_party/agg23/agg_vcgen_stroke.cpp third_party/abseil-cpp/absl/base/internal/cycleclock.cc third_party/abseil-cpp/absl/base/internal/spinlock.cc third_party/abseil-cpp/absl/base/internal/sysinfo.cc third_party/abseil-cpp/absl/base/internal/thread_identity.cc third_party/abseil-cpp/absl/base/internal/unscaledcycleclock.cc third_party/abseil-cpp/absl/base/log_severity.cc third_party/abseil-cpp/absl/base/internal/low_level_alloc.cc third_party/abseil-cpp/absl/base/internal/raw_logging.cc third_party/abseil-cpp/absl/base/internal/spinlock_wait.cc third_party/abseil-cpp/absl/base/internal/strerror.cc third_party/abseil-cpp/absl/base/internal/throw_delegate.cc third_party/abseil-cpp/absl/base/internal/tracing.cc third_party/abseil-cpp/absl/container/internal/hashtablez_sampler.cc third_party/abseil-cpp/absl/container/internal/hashtablez_sampler_force_weak_definition.cc third_party/abseil-cpp/absl/container/internal/raw_hash_set.cc third_party/abseil-cpp/absl/crc/internal/cpu_detect.cc third_party/abseil-cpp/absl/crc/crc32c.cc third_party/abseil-cpp/absl/crc/internal/crc_memcpy_fallback.cc third_party/abseil-cpp/absl/crc/internal/crc_memcpy_x86_arm_combined.cc third_party/abseil-cpp/absl/crc/internal/crc_non_temporal_memcpy.cc third_party/abseil-cpp/absl/crc/internal/crc_cord_state.cc third_party/abseil-cpp/absl/crc/internal/crc.cc third_party/abseil-cpp/absl/crc/internal/crc_x86_arm_combined.cc third_party/abseil-cpp/absl/debugging/internal/address_is_readable.cc third_party/abseil-cpp/absl/debugging/internal/elf_mem_image.cc third_party/abseil-cpp/absl/debugging/internal/vdso_support.cc third_party/abseil-cpp/absl/debugging/internal/decode_rust_punycode.cc third_party/abseil-cpp/absl/debugging/internal/demangle.cc third_party/abseil-cpp/absl/debugging/internal/demangle_rust.cc third_party/abseil-cpp/absl/debugging/internal/examine_stack.cc third_party/abseil-cpp/absl/debugging/failure_signal_handler.cc third_party/abseil-cpp/absl/debugging/stacktrace.cc third_party/abseil-cpp/absl/debugging/symbolize.cc third_party/abseil-cpp/absl/debugging/internal/utf8_for_code_point.cc third_party/abseil-cpp/absl/hash/internal/city.cc third_party/abseil-cpp/absl/hash/internal/hash.cc third_party/abseil-cpp/absl/hash/internal/low_level_hash.cc third_party/abseil-cpp/absl/log/die_if_null.cc third_party/abseil-cpp/absl/log/globals.cc third_party/abseil-cpp/absl/log/initialize.cc third_party/abseil-cpp/absl/log/log_entry.cc third_party/abseil-cpp/absl/log/log_sink.cc third_party/abseil-cpp/absl/log/internal/check_op.cc third_party/abseil-cpp/absl/log/internal/conditions.cc third_party/abseil-cpp/absl/log/internal/fnmatch.cc third_party/abseil-cpp/absl/log/internal/log_format.cc third_party/abseil-cpp/absl/log/internal/globals.cc third_party/abseil-cpp/absl/log/internal/log_message.cc third_party/abseil-cpp/absl/log/internal/log_sink_set.cc third_party/abseil-cpp/absl/log/internal/nullguard.cc third_party/abseil-cpp/absl/log/internal/proto.cc third_party/abseil-cpp/absl/log/internal/vlog_config.cc third_party/abseil-cpp/absl/numeric/int128.cc third_party/abseil-cpp/absl/profiling/internal/exponential_biased.cc third_party/abseil-cpp/absl/random/discrete_distribution.cc third_party/abseil-cpp/absl/random/gaussian_distribution.cc third_party/abseil-cpp/absl/random/seed_gen_exception.cc third_party/abseil-cpp/absl/random/seed_sequences.cc third_party/abseil-cpp/absl/random/internal/randen_round_keys.cc third_party/abseil-cpp/absl/random/internal/pool_urbg.cc third_party/abseil-cpp/absl/random/internal/randen.cc third_party/abseil-cpp/absl/random/internal/randen_detect.cc third_party/abseil-cpp/absl/random/internal/randen_hwaes.cc third_party/abseil-cpp/absl/random/internal/randen_slow.cc third_party/abseil-cpp/absl/random/internal/seed_material.cc third_party/abseil-cpp/absl/status/internal/status_internal.cc third_party/abseil-cpp/absl/status/status.cc third_party/abseil-cpp/absl/status/status_payload_printer.cc third_party/abseil-cpp/absl/status/statusor.cc third_party/abseil-cpp/absl/strings/cord.cc third_party/abseil-cpp/absl/strings/cord_analysis.cc third_party/abseil-cpp/absl/strings/cord_buffer.cc third_party/abseil-cpp/absl/strings/internal/cord_internal.cc third_party/abseil-cpp/absl/strings/internal/cord_rep_btree.cc third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_navigator.cc third_party/abseil-cpp/absl/strings/internal/cord_rep_btree_reader.cc third_party/abseil-cpp/absl/strings/internal/cord_rep_consume.cc third_party/abseil-cpp/absl/strings/internal/cord_rep_crc.cc third_party/abseil-cpp/absl/strings/internal/cordz_functions.cc third_party/abseil-cpp/absl/strings/internal/cordz_handle.cc third_party/abseil-cpp/absl/strings/internal/cordz_info.cc third_party/abseil-cpp/absl/strings/internal/escaping.cc third_party/abseil-cpp/absl/strings/internal/ostringstream.cc third_party/abseil-cpp/absl/strings/internal/utf8.cc third_party/abseil-cpp/absl/strings/internal/str_format/arg.cc third_party/abseil-cpp/absl/strings/internal/str_format/bind.cc third_party/abseil-cpp/absl/strings/internal/str_format/extension.cc third_party/abseil-cpp/absl/strings/internal/str_format/float_conversion.cc third_party/abseil-cpp/absl/strings/internal/str_format/output.cc third_party/abseil-cpp/absl/strings/internal/str_format/parser.cc third_party/abseil-cpp/absl/strings/string_view.cc third_party/abseil-cpp/absl/strings/ascii.cc third_party/abseil-cpp/absl/strings/charconv.cc third_party/abseil-cpp/absl/strings/escaping.cc third_party/abseil-cpp/absl/strings/internal/charconv_bigint.cc third_party/abseil-cpp/absl/strings/internal/charconv_parse.cc third_party/abseil-cpp/absl/strings/internal/damerau_levenshtein_distance.cc third_party/abseil-cpp/absl/strings/internal/memutil.cc third_party/abseil-cpp/absl/strings/internal/stringify_sink.cc third_party/abseil-cpp/absl/strings/match.cc third_party/abseil-cpp/absl/strings/numbers.cc third_party/abseil-cpp/absl/strings/str_cat.cc third_party/abseil-cpp/absl/strings/str_replace.cc third_party/abseil-cpp/absl/strings/str_split.cc third_party/abseil-cpp/absl/strings/substitute.cc third_party/abseil-cpp/absl/synchronization/internal/graphcycles.cc third_party/abseil-cpp/absl/synchronization/internal/kernel_timeout.cc third_party/abseil-cpp/absl/synchronization/barrier.cc third_party/abseil-cpp/absl/synchronization/blocking_counter.cc third_party/abseil-cpp/absl/synchronization/internal/create_thread_identity.cc third_party/abseil-cpp/absl/synchronization/internal/futex_waiter.cc third_party/abseil-cpp/absl/synchronization/internal/per_thread_sem.cc third_party/abseil-cpp/absl/synchronization/internal/pthread_waiter.cc third_party/abseil-cpp/absl/synchronization/internal/sem_waiter.cc third_party/abseil-cpp/absl/synchronization/internal/stdcpp_waiter.cc third_party/abseil-cpp/absl/synchronization/internal/waiter_base.cc third_party/abseil-cpp/absl/synchronization/internal/win32_waiter.cc third_party/abseil-cpp/absl/synchronization/mutex.cc third_party/abseil-cpp/absl/synchronization/notification.cc third_party/abseil-cpp/absl/time/civil_time.cc third_party/abseil-cpp/absl/time/clock.cc third_party/abseil-cpp/absl/time/duration.cc third_party/abseil-cpp/absl/time/format.cc third_party/abseil-cpp/absl/time/time.cc third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_detail.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_posix.cc third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc third_party/abseil-cpp/absl/types/bad_optional_access.cc third_party/abseil-cpp/absl/types/bad_variant_access.cc ) target_include_directories(pdfium PUBLIC $ # For the top level projects. $ # For the external projects. ) target_link_libraries(pdfium openjp2 JPEG::JPEG ${BZIP2_LIBRARIES} Freetype::Freetype ICU::uc ) file(GLOB_RECURSE PUB_HEADERS "public/*.h" "public/cpp/*.h") set_property(TARGET pdfium PROPERTY PUBLIC_HEADER ${PUB_HEADERS}) install( TARGETS pdfium EXPORT pdfiumTargets RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib PUBLIC_HEADER DESTINATION include ) install(EXPORT pdfiumTargets FILE "pdfiumTargets.cmake" NAMESPACE PDFIUM:: DESTINATION share/pdfium ) sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/Config.cmake.in000066400000000000000000000003731516266226600237340ustar00rootroot00000000000000@PACKAGE_INIT@ include(CMakeFindDependencyMacro) find_dependency(Freetype) find_dependency(ICU COMPONENTS uc) find_dependency(JPEG) find_dependency(OpenJPEG) include(${CMAKE_CURRENT_LIST_DIR}/pdfiumTargets.cmake) check_required_components(pdfium) sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/gcc_parentheses_init.patch000066400000000000000000000106221516266226600263170ustar00rootroot00000000000000diff --git a/fpdfsdk/cpdfsdk_appstream.cpp b/fpdfsdk/cpdfsdk_appstream.cpp index 3a9bfabef..b7b06cae8 100644 --- a/fpdfsdk/cpdfsdk_appstream.cpp +++ b/fpdfsdk/cpdfsdk_appstream.cpp @@ -1055,7 +1055,7 @@ ByteString GetDropButtonAppStream(const CFX_FloatRect& rcBBox) { rcBBox, 2, CFX_Color(CFX_Color::Type::kGray, 0), CFX_Color(CFX_Color::Type::kGray, 1), CFX_Color(CFX_Color::Type::kGray, 0.5), BorderStyle::kBeveled, - CPWL_Dash(3, 0, 0)); + CPWL_Dash{3, 0, 0}); } CFX_PointF ptCenter = CFX_PointF((rcBBox.left + rcBBox.right) / 2, @@ -1162,14 +1162,14 @@ void CPDFSDK_AppStream::SetAsPushButton() { CFX_Color crBorder = pControl->GetOriginalBorderColor(); float fBorderWidth = static_cast(widget_->GetBorderWidth()); - CPWL_Dash dsBorder(3, 0, 0); + CPWL_Dash dsBorder{3, 0, 0}; CFX_Color crLeftTop; CFX_Color crRightBottom; BorderStyle nBorderStyle = widget_->GetBorderStyle(); switch (nBorderStyle) { case BorderStyle::kDash: - dsBorder = CPWL_Dash(3, 3, 0); + dsBorder = CPWL_Dash{3, 3, 0}; break; case BorderStyle::kBeveled: fBorderWidth *= 2; @@ -1313,14 +1313,14 @@ void CPDFSDK_AppStream::SetAsCheckBox() { CFX_Color crBackground = pControl->GetOriginalBackgroundColor(); CFX_Color crBorder = pControl->GetOriginalBorderColor(); float fBorderWidth = static_cast(widget_->GetBorderWidth()); - CPWL_Dash dsBorder(3, 0, 0); + CPWL_Dash dsBorder{3, 0, 0}; CFX_Color crLeftTop; CFX_Color crRightBottom; BorderStyle nBorderStyle = widget_->GetBorderStyle(); switch (nBorderStyle) { case BorderStyle::kDash: - dsBorder = CPWL_Dash(3, 3, 0); + dsBorder = CPWL_Dash{3, 3, 0}; break; case BorderStyle::kBeveled: fBorderWidth *= 2; @@ -1392,14 +1392,14 @@ void CPDFSDK_AppStream::SetAsRadioButton() { CFX_Color crBackground = pControl->GetOriginalBackgroundColor(); CFX_Color crBorder = pControl->GetOriginalBorderColor(); float fBorderWidth = static_cast(widget_->GetBorderWidth()); - CPWL_Dash dsBorder(3, 0, 0); + CPWL_Dash dsBorder{3, 0, 0}; CFX_Color crLeftTop; CFX_Color crRightBottom; BorderStyle nBorderStyle = widget_->GetBorderStyle(); switch (nBorderStyle) { case BorderStyle::kDash: - dsBorder = CPWL_Dash(3, 3, 0); + dsBorder = CPWL_Dash{3, 3, 0}; break; case BorderStyle::kBeveled: fBorderWidth *= 2; @@ -1769,7 +1769,7 @@ void CPDFSDK_AppStream::SetAsTextField(std::optional sValue) { ByteString sColor = GetStrokeColorAppStream(widget_->GetBorderPWLColor()); if (sColor.GetLength() > 0) { - CPWL_Dash dsBorder = CPWL_Dash(3, 3, 0); + CPWL_Dash dsBorder = CPWL_Dash{3, 3, 0}; AutoClosedQCommand q(&sLines); sLines << widget_->GetBorderWidth() << " " << kSetLineWidthOperator << "\n" @@ -1876,12 +1876,12 @@ ByteString CPDFSDK_AppStream::GetBorderAppStream() const { CFX_Color crRightBottom; float fBorderWidth = static_cast(widget_->GetBorderWidth()); - CPWL_Dash dsBorder(3, 0, 0); + CPWL_Dash dsBorder{3, 0, 0}; BorderStyle nBorderStyle = widget_->GetBorderStyle(); switch (nBorderStyle) { case BorderStyle::kDash: - dsBorder = CPWL_Dash(3, 3, 0); + dsBorder = CPWL_Dash{3, 3, 0}; break; case BorderStyle::kBeveled: fBorderWidth *= 2; diff --git a/fpdfsdk/formfiller/cffl_formfield.cpp b/fpdfsdk/formfiller/cffl_formfield.cpp index 49edfdaed..bba78eda6 100644 --- a/fpdfsdk/formfiller/cffl_formfield.cpp +++ b/fpdfsdk/formfiller/cffl_formfield.cpp @@ -336,7 +336,7 @@ CPWL_Wnd::CreateParams CFFL_FormField::GetCreateParam() { cp.nBorderStyle = m_pWidget->GetBorderStyle(); switch (cp.nBorderStyle) { case BorderStyle::kDash: - cp.sDash = CPWL_Dash(3, 3, 0); + cp.sDash = CPWL_Dash{3, 3, 0}; break; case BorderStyle::kBeveled: case BorderStyle::kInset: diff --git a/fpdfsdk/pwl/cpwl_wnd.cpp b/fpdfsdk/pwl/cpwl_wnd.cpp index 502568709..88b0cf03e 100644 --- a/fpdfsdk/pwl/cpwl_wnd.cpp +++ b/fpdfsdk/pwl/cpwl_wnd.cpp @@ -40,7 +40,7 @@ CPWL_Wnd::CreateParams::CreateParams(CFX_Timer::HandlerIface* timer_handler, pFillerNotify(filler_notify), pProvider(provider), fFontSize(kDefaultFontSize), - sDash(3, 0, 0) {} + sDash{3, 0, 0} {} CPWL_Wnd::CreateParams::CreateParams(const CreateParams& other) = default; sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/portfile.cmake000066400000000000000000000060411516266226600237440ustar00rootroot00000000000000 # From V8 port file in vcpkg repo: https://github.com/microsoft/vcpkg/blob/master/ports/v8/portfile.cmake function(pdfium_from_git) set(pdfiumArgs DESTINATION URL REF SOURCE) cmake_parse_arguments(pdfium "" "${pdfiumArgs}" "" ${ARGN}) if(EXISTS ${pdfium_SOURCE}/${pdfium_DESTINATION}) vcpkg_execute_required_process( COMMAND ${GIT} reset --hard WORKING_DIRECTORY ${pdfium_SOURCE}/${pdfium_DESTINATION} LOGNAME build-${TARGET_TRIPLET}) else() vcpkg_execute_required_process( COMMAND ${GIT} clone ${pdfium_URL} ${pdfium_DESTINATION} WORKING_DIRECTORY ${pdfium_SOURCE} LOGNAME build-${TARGET_TRIPLET}) vcpkg_execute_required_process( COMMAND ${GIT} fetch --depth 1 origin ${pdfium_REF} WORKING_DIRECTORY ${pdfium_SOURCE}/${pdfium_DESTINATION} LOGNAME build-${TARGET_TRIPLET}) vcpkg_execute_required_process( COMMAND ${GIT} checkout FETCH_HEAD WORKING_DIRECTORY ${pdfium_SOURCE}/${pdfium_DESTINATION} LOGNAME build-${TARGET_TRIPLET}) endif() endfunction() vcpkg_find_acquire_program(GIT) vcpkg_from_git( OUT_SOURCE_PATH SOURCE_PATH URL https://pdfium.googlesource.com/pdfium.git REF 7a8409531fbb58d7d15ae331e645977b113d7ced # chromium/6778 PATCHES gcc_parentheses_init.patch # gcc9, no aggregate initialization with parentheses in C++20. win-compilation-v142.patch # Compiler fails auto type deduction in v142 platform ) message(STATUS "Working on submodules and other dependencies...") pdfium_from_git( DESTINATION build URL https://chromium.googlesource.com/chromium/src/build.git REF 9b11bd3a6a523134ac35bcc9d1f59d04cc6f5821 # The one in pdfium DEPS file, field 'build_revision' SOURCE ${SOURCE_PATH} ) pdfium_from_git( DESTINATION third_party/abseil-cpp URL https://chromium.googlesource.com/chromium/src/third_party/abseil-cpp.git REF d2ea9f0eb1a31f0e5a0ab11837ed19333700ab4c # The one in pdfium DEPS file, field 'abseil_revision' SOURCE ${SOURCE_PATH} ) pdfium_from_git( DESTINATION third_party/fast_float/src URL https://chromium.googlesource.com/external/github.com/fastfloat/fast_float.git REF 3e57d8dcfb0a04b5a8a26b486b54490a2e9b310f # The one in pdfium DEPS file, field 'abseil_revision' SOURCE ${SOURCE_PATH} ) vcpkg_check_linkage(ONLY_STATIC_LIBRARY) file(COPY ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt DESTINATION ${SOURCE_PATH}) vcpkg_cmake_configure( SOURCE_PATH "${SOURCE_PATH}" ) vcpkg_cmake_install() vcpkg_cmake_config_fixup(PACKAGE_NAME pdfium CONFIG_PATH share/pdfium) set(PDFIUM_PREFIX ${CURRENT_PACKAGES_DIR}) include(CMakePackageConfigHelpers) configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/Config.cmake.in "${CURRENT_PACKAGES_DIR}/share/pdfium/pdfiumConfig.cmake" INSTALL_DESTINATION share/pdfium ) file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") file(INSTALL ${SOURCE_PATH}/LICENSE DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}/" RENAME copyright) sdk-10.11.0/cmake/vcpkg_overlay_ports/pdfium/win-compilation-v142.patch000066400000000000000000000037771516266226600257570ustar00rootroot00000000000000diff --git a/core/fpdfdoc/cpdf_action.cpp b/core/fpdfdoc/cpdf_action.cpp index a5b687d76..369511362 100644 --- a/core/fpdfdoc/cpdf_action.cpp +++ b/core/fpdfdoc/cpdf_action.cpp @@ -21,7 +21,7 @@ namespace { -constexpr auto kActionTypeStrings = fxcrt::ToArray({ +constexpr std::array kActionTypeStrings = { "GoTo", "GoToR", "GoToE", @@ -40,7 +40,7 @@ constexpr auto kActionTypeStrings = fxcrt::ToArray({ "Rendition", "Trans", "GoTo3DView", -}); +}; } // namespace diff --git a/core/fpdfdoc/cpdf_dest.cpp b/core/fpdfdoc/cpdf_dest.cpp index 8026a75d5..abcfc863f 100644 --- a/core/fpdfdoc/cpdf_dest.cpp +++ b/core/fpdfdoc/cpdf_dest.cpp @@ -21,12 +21,12 @@ namespace { // These arrays are indexed by the PDFDEST_VIEW_* constants. -constexpr auto kZoomModes = - fxcrt::ToArray({"Unknown", "XYZ", "Fit", "FitH", "FitV", - "FitR", "FitB", "FitBH", "FitBV"}); +constexpr class std::array kZoomModes = + {"Unknown", "XYZ", "Fit", "FitH", "FitV", + "FitR", "FitB", "FitBH", "FitBV"}; -constexpr auto kZoomModeMaxParamCount = - fxcrt::ToArray({0, 3, 0, 1, 1, 4, 0, 1, 1}); +constexpr std::array kZoomModeMaxParamCount = + {0, 3, 0, 1, 1, 4, 0, 1, 1}; } // namespace diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp index e42e6db0c..b90551d71 100644 --- a/fpdfsdk/fpdf_annot.cpp +++ b/fpdfsdk/fpdf_annot.cpp @@ -1085,8 +1085,8 @@ FPDFAnnot_SetAP(FPDF_ANNOTATION annot, if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) return false; - static constexpr auto kModeKeyForMode = - fxcrt::ToArray({"N", "R", "D"}); + static constexpr std::array kModeKeyForMode = + {"N", "R", "D"}; static_assert(kModeKeyForMode.size() == FPDF_ANNOT_APPEARANCEMODE_COUNT, "length of kModeKeyForMode should be equal to " "FPDF_ANNOT_APPEARANCEMODE_COUNT"); sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/000077500000000000000000000000001516266226600223755ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/8.2p1.diff000066400000000000000000000011671516266226600240040ustar00rootroot00000000000000diff --git a/nls.c b/nls.c index 5c6a13b..8c027d6 100644 --- a/nls.c +++ b/nls.c @@ -141,6 +141,10 @@ _rl_init_locale (void) if (lspec == 0) lspec = ""; ret = setlocale (LC_CTYPE, lspec); /* ok, since it does not change locale */ + if (ret == 0 || *ret == 0) + ret = setlocale (LC_CTYPE, (char *)NULL); + if (ret == 0 || *ret == 0) + ret = RL_DEFAULT_LOCALE; #else ret = (lspec == 0 || *lspec == 0) ? RL_DEFAULT_LOCALE : lspec; #endif diff --git a/patchlevel b/patchlevel index d8c9df7..fdf4740 100644 --- a/patchlevel +++ b/patchlevel @@ -1,3 +1,3 @@ # Do not edit -- exists only for use by patch -0 +1 sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/FindReadline-unix.cmake.in000066400000000000000000000025061516266226600273140ustar00rootroot00000000000000# Module to find the Readline library for Unix # Try to find the header find_path(Readline-unix_INCLUDE_DIR NAMES readline/readline.h ) mark_as_advanced(Readline-unix_INCLUDE_DIR) # Try to find the library find_library(Readline-unix_LIBRARY NAMES readline libreadline ) mark_as_advanced(Readline-unix_LIBRARY) file(READ "${Readline-unix_INCLUDE_DIR}/readline/readline.h" FILE_CONTENTS) string(REGEX MATCH "#define RL_VERSION_MAJOR[ \t]*([0-9]*)" _ ${FILE_CONTENTS}) set(Readline-unix_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "#define RL_VERSION_MINOR[ \t]*([0-9]*)" _ ${FILE_CONTENTS}) set(Readline-unix_VERSION ${Readline-unix_VERSION}.${CMAKE_MATCH_1}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Readline-unix REQUIRED_VARS Readline-unix_INCLUDE_DIR Readline-unix_LIBRARY VERSION_VAR Readline-unix_VERSION ) # Create target if(Readline-unix_FOUND) set(Readline-unix_INCLUDE_DIRS ${Readline-unix_INCLUDE_DIR}) set(Readline-unix_LIBRARIES ${Readline-unix_LIBRARY}) if(NOT TARGET Readline::Readline) add_library(Readline::Readline UNKNOWN IMPORTED) set_target_properties(Readline::Readline PROPERTIES IMPORTED_LOCATION "${Readline-unix_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${Readline-unix_INCLUDE_DIR}") endif() endif() sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/portfile.cmake000066400000000000000000000022401516266226600252210ustar00rootroot00000000000000set(filename readline-${VERSION}.tar.gz) vcpkg_download_distfile( ARCHIVE URLS "https://ftpmirror.gnu.org/gnu/readline/${filename}" "https://ftp.gnu.org/gnu/readline/${filename}" FILENAME "${filename}" SHA512 0a451d459146bfdeecc9cdd94bda6a6416d3e93abd80885a40b334312f16eb890f8618a27ca26868cebbddf1224983e631b1cbc002c1a4d1cd0d65fba9fea49a ) vcpkg_extract_source_archive(SOURCE_PATH ARCHIVE "${ARCHIVE}" PATCHES 8.2p1.diff ) vcpkg_configure_make( SOURCE_PATH "${SOURCE_PATH}" DETERMINE_BUILD_TRIPLET OPTIONS --with-curses=yes --disable-install-examples ) vcpkg_install_make() file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share" "${CURRENT_PACKAGES_DIR}/tools" ) vcpkg_fixup_pkgconfig() configure_file("${CMAKE_CURRENT_LIST_DIR}/FindReadline-unix.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/FindReadline-unix.cmake" @ONLY) file(COPY "${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/vcpkg-cmake-wrapper.cmake000066400000000000000000000003321516266226600272430ustar00rootroot00000000000000set(READLINE_PREV_MODULE_PATH ${CMAKE_MODULE_PATH}) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) cmake_policy(SET CMP0012 NEW) _find_package(${ARGS}) set(CMAKE_MODULE_PATH ${READLINE_PREV_MODULE_PATH}) sdk-10.11.0/cmake/vcpkg_overlay_ports/readline-unix/vcpkg.json000066400000000000000000000006151516266226600244040ustar00rootroot00000000000000{ "name": "readline-unix", "version": "8.2", "port-version": 1, "description": "The GNU Readline library provides a set of functions for use by applications that allow users to edit command lines as they are typed in.", "homepage": "https://tiswww.case.edu/php/chet/readline/rltop.html", "license": "GPL-3.0-or-later", "supports": "!windows", "dependencies": [ "ncurses" ] } sdk-10.11.0/cmake/vcpkg_overlay_triplets/000077500000000000000000000000001516266226600203305ustar00rootroot00000000000000sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm-android-mega.cmake000066400000000000000000000006121516266226600244350ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME Android) set(VCPKG_CMAKE_SYSTEM_VERSION 28) set(VCPKG_MAKE_BUILD_TRIPLET "--host=armv7a-linux-androideabi") set(VCPKG_CMAKE_CONFIGURE_OPTIONS -DANDROID_ABI=armeabi-v7a -DANDROID_PLATFORM=android-${VCPKG_CMAKE_SYSTEM_VERSION} -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON) sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm-linux.cmake000066400000000000000000000011601516266226600232440ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Linux) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() if(PORT MATCHES "pdfium") set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -march=armv7-a") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -march=armv7-a") endif() set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -mfpu=vfp") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -mfpu=vfp")sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-android-mega.cmake000066400000000000000000000006071516266226600246130ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm64) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME Android) set(VCPKG_CMAKE_SYSTEM_VERSION 28) set(VCPKG_MAKE_BUILD_TRIPLET "--host=aarch64-linux-android") set(VCPKG_CMAKE_CONFIGURE_OPTIONS -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-${VCPKG_CMAKE_SYSTEM_VERSION} -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON) sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-ios-mega.cmake000066400000000000000000000002641516266226600237640ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME iOS) set(VCPKG_OSX_DEPLOYMENT_TARGET 15.0) sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-ios-simulator-mega.cmake000066400000000000000000000003341516266226600257770ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME iOS) set(VCPKG_OSX_DEPLOYMENT_TARGET 15.0) set(VCPKG_OSX_SYSROOT iphonesimulator) sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-linux-mega.cmake000066400000000000000000000005661516266226600243360ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Linux) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-osx-mega.cmake000066400000000000000000000013471516266226600240060ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE arm64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Darwin) set(VCPKG_OSX_ARCHITECTURES arm64) set(VCPKG_OSX_DEPLOYMENT_TARGET 11.1) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() set(_macos_version_min_flag "-mmacosx-version-min=${VCPKG_OSX_DEPLOYMENT_TARGET}") set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} ${_macos_version_min_flag}") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} ${_macos_version_min_flag}") set(VCPKG_LINKER_FLAGS "${VCPKG_LINKER_FLAGS} ${_macos_version_min_flag}") sdk-10.11.0/cmake/vcpkg_overlay_triplets/arm64-windows-mega.cmake000066400000000000000000000011131516266226600246560ustar00rootroot00000000000000include(${CMAKE_CURRENT_LIST_DIR}/find_visual_studio_path.cmake) find_visual_studio_path() set(VCPKG_VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH}) set(VCPKG_PLATFORM_TOOLSET v142) set(VCPKG_TARGET_ARCHITECTURE arm64) # use dynamic C and CPP libraries (needed if we use any DLLs, eg Qt) set(VCPKG_CRT_LINKAGE dynamic) if(PORT MATCHES "ffmpeg") # build this library as DLL (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() sdk-10.11.0/cmake/vcpkg_overlay_triplets/find_visual_studio_path.cmake000066400000000000000000000013241516266226600262400ustar00rootroot00000000000000function(find_visual_studio_path) set(VISUAL_STUDIO_PATHS "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise" "C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional" "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community" ) set(VISUAL_STUDIO_FOUND FALSE) foreach(VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATHS}) if(EXISTS ${VISUAL_STUDIO_PATH}) set(VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH} PARENT_SCOPE) set(VISUAL_STUDIO_FOUND TRUE) break() endif() endforeach() if(NOT VISUAL_STUDIO_FOUND) message(FATAL_ERROR "No compatible Visual Studio 2022 installation found.") endif() endfunction() sdk-10.11.0/cmake/vcpkg_overlay_triplets/x64-android-mega.cmake000066400000000000000000000006011516266226600242750ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME Android) set(VCPKG_CMAKE_SYSTEM_VERSION 28) set(VCPKG_MAKE_BUILD_TRIPLET "--host=x86_64-linux-android") set(VCPKG_CMAKE_CONFIGURE_OPTIONS -DANDROID_ABI=x86_64 -DANDROID_PLATFORM=android-${VCPKG_CMAKE_SYSTEM_VERSION} -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON) sdk-10.11.0/cmake/vcpkg_overlay_triplets/x64-linux-mega.cmake000066400000000000000000000010321516266226600240130ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Linux) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_FIXUP_ELF_RPATH ON) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS}") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS}") set(VCPKG_LINKER_FLAGS "${VCPKG_LINKER_FLAGS}") sdk-10.11.0/cmake/vcpkg_overlay_triplets/x64-osx-mega.cmake000066400000000000000000000013461516266226600234750ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Darwin) set(VCPKG_OSX_ARCHITECTURES x86_64) set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() set(_macos_version_min_flag "-mmacosx-version-min=${VCPKG_OSX_DEPLOYMENT_TARGET}") set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} ${_macos_version_min_flag}") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} ${_macos_version_min_flag}") set(VCPKG_LINKER_FLAGS "${VCPKG_LINKER_FLAGS} ${_macos_version_min_flag}") sdk-10.11.0/cmake/vcpkg_overlay_triplets/x64-windows-mega-staticdev.cmake000066400000000000000000000014531516266226600263410ustar00rootroot00000000000000# This triplet file shows how to build an all-static version, which is easy to develop with but not suitable for release due to third party licensing. # Additionally it shows how to work with VS community edition, for home enthusiasts. # When buliding all dependencies this way, debug iterators can be turned off - those can cause large delays in the SDK which can make testing in debug a bit slow. include(${CMAKE_CURRENT_LIST_DIR}/find_visual_studio_path.cmake) find_visual_studio_path() set(VCPKG_VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH}) set(VCPKG_PLATFORM_TOOLSET v142) set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") sdk-10.11.0/cmake/vcpkg_overlay_triplets/x64-windows-mega.cmake000066400000000000000000000011111516266226600243440ustar00rootroot00000000000000include(${CMAKE_CURRENT_LIST_DIR}/find_visual_studio_path.cmake) find_visual_studio_path() set(VCPKG_VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH}) set(VCPKG_PLATFORM_TOOLSET v142) set(VCPKG_TARGET_ARCHITECTURE x64) # use dynamic C and CPP libraries (needed if we use any DLLs, eg Qt) set(VCPKG_CRT_LINKAGE dynamic) if(PORT MATCHES "ffmpeg") # build this library as DLL (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() sdk-10.11.0/cmake/vcpkg_overlay_triplets/x86-android-mega.cmake000066400000000000000000000005741516266226600243120ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x86) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CMAKE_SYSTEM_NAME Android) set(VCPKG_CMAKE_SYSTEM_VERSION 28) set(VCPKG_MAKE_BUILD_TRIPLET "--host=i686-linux-android") set(VCPKG_CMAKE_CONFIGURE_OPTIONS -DANDROID_ABI=x86 -DANDROID_PLATFORM=android-${VCPKG_CMAKE_SYSTEM_VERSION} -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON) sdk-10.11.0/cmake/vcpkg_overlay_triplets/x86-linux-mega.cmake000066400000000000000000000010321516266226600240170ustar00rootroot00000000000000set(VCPKG_TARGET_ARCHITECTURE x86) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_CMAKE_SYSTEM_NAME Linux) if(PORT MATCHES "ffmpeg") # build this library as dynamic (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_FIXUP_ELF_RPATH ON) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS}") set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS}") set(VCPKG_LINKER_FLAGS "${VCPKG_LINKER_FLAGS}") sdk-10.11.0/cmake/vcpkg_overlay_triplets/x86-windows-mega-staticdev.cmake000066400000000000000000000014531516266226600263450ustar00rootroot00000000000000# This triplet file shows how to build an all-static version, which is easy to develop with but not suitable for release due to third party licensing. # Additionally it shows how to work with VS community edition, for home enthusiasts. # When buliding all dependencies this way, debug iterators can be turned off - those can cause large delays in the SDK which can make testing in debug a bit slow. include(${CMAKE_CURRENT_LIST_DIR}/find_visual_studio_path.cmake) find_visual_studio_path() set(VCPKG_VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH}) set(VCPKG_PLATFORM_TOOLSET v142) set(VCPKG_TARGET_ARCHITECTURE x86) set(VCPKG_CRT_LINKAGE static) set(VCPKG_LIBRARY_LINKAGE static) set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") sdk-10.11.0/cmake/vcpkg_overlay_triplets/x86-windows-mega.cmake000066400000000000000000000011061516266226600243540ustar00rootroot00000000000000include(${CMAKE_CURRENT_LIST_DIR}/find_visual_studio_path.cmake) find_visual_studio_path() set(VCPKG_VISUAL_STUDIO_PATH ${VISUAL_STUDIO_PATH}) set(VCPKG_PLATFORM_TOOLSET v142) set(VCPKG_TARGET_ARCHITECTURE x86) # use dynamic C and CPP libraries (needed if we use any DLLs, eg Qt) set(VCPKG_CRT_LINKAGE dynamic) if(PORT MATCHES "ffmpeg") # build this library as DLL (usually because it is LGPL licensed) set(VCPKG_LIBRARY_LINKAGE dynamic) else() # build this library statically (much simpler installation, debugging, etc) set(VCPKG_LIBRARY_LINKAGE static) endif() sdk-10.11.0/contrib/000077500000000000000000000000001516266226600141075ustar00rootroot00000000000000sdk-10.11.0/contrib/tools/000077500000000000000000000000001516266226600152475ustar00rootroot00000000000000sdk-10.11.0/contrib/tools/logs_beautifier.py000066400000000000000000000060701516266226600207670ustar00rootroot00000000000000#!/usr/bin/python # This python script can be used to extract HTTP request and responses from an SDK log # and beautify json payloads from __future__ import print_function import json, sys, re import argparse parser = argparse.ArgumentParser() parser.add_argument('--print-cs-post', '-r', action='store_true', dest='postcs', help='print lines with cs POST') parser.set_defaults(postcs=False) parser.add_argument('--print-sc-post', '-a', action='store_true', dest='postsc', help='print lines with sc POST') parser.set_defaults(postsc=False) parser.add_argument('--only-action-packets', '-s', action='store_true', dest='onlyactionpackets', help='do not print cs requests/responses') parser.set_defaults(onlyactionpackets=False) parser.add_argument('--only-client-requests', '-c', action='store_true', dest='onlyclientreqs', help='do not print sc requests/responses') parser.set_defaults(onlyclientreqs=False) parser.add_argument('--include-lines-matching', '-i', dest='includepattern', help='include lines matching some pattern') parser.set_defaults(includepattern=None) parser.add_argument('file', nargs=argparse.REMAINDER) args = parser.parse_args() fToParse = sys.stdin if not len(args.file) else open(args.file[0]) scpatterns=["sc Received", "sc Sending"] cspatterns=["cs Received", "cs Sending"] patterns=[] if not args.onlyactionpackets: patterns+=(cspatterns) if not args.onlyclientreqs: patterns+=(scpatterns) for l in fToParse: if args.postcs and "cs POST target" in l: print (l.strip(),) if args.postsc and "sc POST target" in l: print (l.strip(),) if any(x in l for x in patterns) and "sc Received 1: 0" not in l and " sc Sending 0:" not in l: m = re.search('(.*): (\{.*\}|\[.*\])', l) if m: header = found = m.group(1) found = m.group(2) if header and found: try: contents = json.dumps(json.loads(found), sort_keys=False, indent=4) print (header+contents) except: #try this other format: m = re.search('(.*)((sc|cs) (Received|Sending) [0-9]*: *)(\{.*\}|\[.*\]) *(\[.*cpp.*\])*', l) if m and m is not None and len(m.groups()) > 4: header = found = m.group(1) sendrecv = m.group(2) found = m.group(5) fil = m.group(6) if header and sendrecv and found: try: contents = json.dumps(json.loads(found), sort_keys=False, indent=4) print (header+sendrecv+contents) except: print (l) else: print (l,) else: print (l,) else: print (l,) else: print (l,) elif args.includepattern: m = re.search(args.includepattern, l) if m: print (l.strip(),) sdk-10.11.0/dockerfile/000077500000000000000000000000001516266226600145565ustar00rootroot00000000000000sdk-10.11.0/dockerfile/android-cross-build.dockerfile000066400000000000000000000060431516266226600224560ustar00rootroot00000000000000# Dockerfile for cross-compiling for Android and its different architectures. # # Build the Docker image: # docker build -t android-build-env -f /path/to/your/sdk/dockerfile/android-cross-build.dockerfile . # -t : Tags the built container with a name # -f : Specify dockerfile to be build, replace /path/to/your/sdk with your local path to it # # Run the Docker container and build the project for a specific architecture: # docker run -v /path/to/your/sdk:/mega/sdk -v /path/to/your/vcpkg:/mega/vcpkg [-e ARCH=[arm, arm64, x86, x64]] -it android-build-env # -v : Mounts a local directory into the container, replace /path/to/your/sdk and /path/to/your/vcpkg with your local paths # -e : Sets an environment variable, `ARCH` environment variable is used to specify the target architecture # -it : Starts an interactive terminal session inside the container after the cmake project is configured and build # Base image FROM ubuntu:24.04 # Install dependencies RUN apt-get --quiet=2 update && DEBCONF_NOWARNINGS=yes apt-get --quiet=2 install \ autoconf \ autoconf-archive \ build-essential \ cmake \ curl \ git \ libtool-bin \ nasm \ openjdk-21-jdk \ pkg-config \ python3 \ python3-pip \ swig \ unzip \ wget \ zip \ 1> /dev/null # Install AWS CLI v2 RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ ./aws/install && \ rm -rf awscliv2.zip aws # Download, extract and set the Android NDK RUN mkdir -p /mega/android-ndk && \ chmod 777 /mega && \ cd /mega/android-ndk && \ wget https://dl.google.com/android/repository/android-ndk-r27b-linux.zip && \ unzip android-ndk-r27b-linux.zip && \ rm android-ndk-r27b-linux.zip ENV ANDROID_NDK_HOME=/mega/android-ndk/android-ndk-r27b ENV JAVA_HOME=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 ENV PATH=$ANDROID_NDK_HOME:$JAVA_HOME:$PATH # Set up work directory WORKDIR /mega # Set default architecture ENV ARCH=x64 # Configure and build CMake command, this will be executed when running the container CMD ["sh", "-c", "\ userdel -f ubuntu && \ owner_uid=$(stat -c '%u' /mega/sdk) && \ owner_gid=$(stat -c '%g' /mega/sdk) && \ groupadd -g $owner_gid me && \ echo 'Adding \"me\" user...' && \ useradd -r -M -u $owner_uid -g $owner_gid -d /mega -s /bin/bash me && \ case ${ARCH} in \ arm) \ export ANDROID_ARCH='armeabi-v7a';; \ arm64) \ export ANDROID_ARCH='arm64-v8a';; \ x86) \ export ANDROID_ARCH='x86';; \ x64) \ export ANDROID_ARCH='x86_64';; \ *) \ echo 'error: Unsupported architecture: ${ARCH}' && exit 1;; \ esac && \ su - me -w 'ANDROID_NDK_HOME,PATH,JAVA_HOME,ANDROID_ARCH,VCPKG_BINARY_SOURCES,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_ENDPOINT_URL' -c ' \ cmake --preset mega-android -B buildAndroid -S sdk \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DANDROID_ABI=${ANDROID_ARCH} && \ cmake --build buildAndroid' && \ exec /bin/bash"] sdk-10.11.0/dockerfile/clang-format-mr-check.sh000066400000000000000000000034141516266226600211550ustar00rootroot00000000000000#!/usr/bin/env bash set -u TARGET_REF="${1:-}" SOURCE_REF="${2:-}" if [[ -z "$TARGET_REF" || -z "$SOURCE_REF" ]]; then echo "Usage: $(basename "$0") " >&2 echo "Example: $(basename "$0") origin/develop origin/feature" >&2 exit 2 fi COMMITS=$(git log --pretty=format:%H "${TARGET_REF}..${SOURCE_REF}" || true) FOUND_ISSUES=0 BAD_COMMITS=() echo " ######################" echo " # ClangFormat output #" echo " ######################" echo "" echo " Range: ${TARGET_REF}..${SOURCE_REF}" echo " Commits: ${COMMITS}" echo "" if [[ -z "$COMMITS" ]]; then echo " No commits to check." echo "" echo " #############################" echo " # End of ClangFormat output #" echo " #############################" exit 0 fi for COMMIT in $COMMITS do OUTPUT=$(git -c color.ui=always clang-format --diff \ --extensions "c,c++,c++m,cc,ccm,cp,cpp,cppm,cxx,cxxm,h,hh,hpp,hxx" \ "$COMMIT^" "$COMMIT" || true) echo " Commit: ${COMMIT}" if [[ -z "$OUTPUT" ]]; then echo " No ClangFormat issues found." continue fi if grep -q 'no modified files to format' <<<"$OUTPUT"; then echo " ${OUTPUT}" continue fi if grep -q 'clang-format did not modify any files' <<<"$OUTPUT"; then echo " ${OUTPUT}" continue fi echo " ClangFormat:" printf '%s\n' "$OUTPUT" echo "" FOUND_ISSUES=1 BAD_COMMITS+=("$COMMIT") done if [[ $FOUND_ISSUES -ne 0 ]]; then echo " ############################" echo " # ClangFormat issues found #" echo " ############################" echo "" echo " Commits with diffs: ${BAD_COMMITS[*]}" echo "" exit 1 fi echo " #############################" echo " # End of ClangFormat output #" echo " #############################" sdk-10.11.0/dockerfile/clang-format.dockerfile000066400000000000000000000007551516266226600211700ustar00rootroot00000000000000# MEGA clang-format Linter # mega-docker.artifactory.developers.mega.co.nz:8443/clang-format-sdk:24.04_v18 FROM ubuntu:24.04 ENV DEBCONF_NOWARNINGS=yes ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ apt-get install -y git clang-format && \ useradd clang-format -d /var/lib/clang-format -m -s /bin/bash COPY clang-format-mr-check.sh /usr/local/bin/clang-format-mr-check RUN chmod +x /usr/local/bin/clang-format-mr-check USER clang-format WORKDIR /var/lib/clang-format sdk-10.11.0/dockerfile/dms-cross-build.dockerfile000066400000000000000000000062541516266226600216250ustar00rootroot00000000000000# Dockerfile for cross-compiling for DMS in its different architectures. # # Build the Docker image: # docker build -t dms-build-env -f /path/to/your/sdk/dockerfile/dms-cross-build.dockerfile /path/to/your/sdk/dockerfile/ --build-arg PLATFORM=platformName # -t : Tags the built container with a name # -f : Specify dockerfile to be build, replace /path/to/your/sdk with your local path to the sdk # --build-arg : adds an argument to the build, you should select one of the possible architectures # # Run the Docker container and build the project for a specific architecture: # docker run -v /path/to/your/sdk:/mega/sdk -v /path/to/your/vcpkg:/mega/vcpkg -it dms-build-env # -v : Mounts a local directory into the container, replace /path/to/your/sdk and /path/to/your/vcpkg with your local paths # -it : Starts an interactive terminal session inside the container after the cmake project is configured and build # # Possible platforms are: [alpine alpine4k apollolake armada37xx armada38x avoton broadwell broadwellnk broadwellnkv2 broadwellntbap bromolow braswell denverton epyc7002 geminilake grantley kvmx64 monaco purley r1000 rtd1296 rtd1619b v1000] # Base image FROM ubuntu:22.04 # Set default architecture ARG PLATFORM=alpine ENV PLATFORM=${PLATFORM} # Install dependencies RUN apt-get --quiet=2 update && DEBCONF_NOWARNINGS=yes apt-get --quiet=2 install \ aria2 \ autoconf \ autoconf-archive \ build-essential \ cmake \ curl \ fakeroot \ git \ libtool \ nasm \ pkg-config \ python3 \ tar \ unzip \ wget \ xz-utils \ zip \ 1> /dev/null # Install AWS CLI v2 RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ ./aws/install && \ rm -rf awscliv2.zip aws # Set up work directory WORKDIR /mega RUN chmod 777 /mega # Clone and checkout known pkgscripts baseline RUN git clone https://github.com/SynologyOpenSource/pkgscripts-ng.git pkgscripts \ && git -C ./pkgscripts checkout e1d9f52 # Copy the shell script and configuration files COPY dms-toolchain.sh /mega/ COPY dms-toolchains.conf /mega/ # Make the helper shell script executable RUN chmod +x /mega/dms-toolchain.sh # Configure and build CMake command CMD ["sh", "-c", "\ owner_uid=$(stat -c '%u' /mega/sdk) && \ owner_gid=$(stat -c '%g' /mega/sdk) && \ groupadd -g $owner_gid me && \ echo 'Adding \"me\" user...' && \ useradd -r -M -u $owner_uid -g $owner_gid -d /mega -s /bin/bash me && \ export PLATFORM=${PLATFORM} && \ su - me -w 'PLATFORM,VCPKG_BINARY_SOURCES,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_ENDPOINT_URL' -c ' \ /mega/dms-toolchain.sh ${PLATFORM} && \ cmake -B buildDMS -S sdk \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DENABLE_LOG_PERFORMANCE=ON \ -DENABLE_SDKLIB_EXAMPLES=OFF \ -DENABLE_SDKLIB_TESTS=OFF \ -DENABLE_SDKLIB_WERROR=OFF \ -DUSE_LIBUV=ON \ -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=/mega/${PLATFORM}.toolchain.cmake \ -DVCPKG_OVERLAY_TRIPLETS=/mega \ -DVCPKG_ROOT=/mega/vcpkg \ -DVCPKG_TARGET_TRIPLET=${PLATFORM} && \ cmake --build buildDMS' && \ exec /bin/bash"] sdk-10.11.0/dockerfile/dms-toolchain.sh000066400000000000000000000102121516266226600176470ustar00rootroot00000000000000#!/usr/bin/env bash # Load the configuration source /mega/dms-toolchains.conf # Function to download the toolchain and verify its integrity download_toolchain() { local platform="$1" local url="${TOOLCHAIN_URLS[$platform]}" local file="${TOOLCHAIN_FILES[$platform]}" local md5="${TOOLCHAIN_MD5_CHECKSUMS[$platform]}" echo "Downloading toolchain for $platform..." wget -q "$url" -O "/mega/$file.txz" echo "Verifying MD5 checksum..." echo "$md5 /mega/$file.txz" | md5sum -c - if [ $? -ne 0 ]; then echo "MD5 checksum verification failed!" exit 1 fi echo "Extracting toolchain..." mkdir -p /mega/toolchain tar -xf "/mega/$file.txz" -C /mega/toolchain rm "/mega/$file.txz" } # Generate CMake toolchain file generate_cmake_toolchain_file() { local toolchain_path="$1" local bitMode=32 # Variables loaded from pkscripts platform file. [ -v ToolChainDir64 ] && bitMode=64 arch="ARCH" c_compiler="CC$bitMode" c_compiler_flags="CFLAGS$bitMode" cxx_compiler="CXX$bitMode" cxx_compiler_flags="CFLAGS$bitMode" linker_flags="LDFLAGS$bitMode" toolchain_system_root_dir="ToolChainSysRoot$bitMode" ( printf 'set(CMAKE_SYSTEM_NAME Linux)\n' printf 'set(CMAKE_SYSTEM_PROCESSOR %s)\n' "${!arch}" printf 'set(CMAKE_C_COMPILER "%s")\n' "/${!c_compiler/wrap-}" printf 'set(CMAKE_C_FLAGS_INIT "%s")\n' "${!c_compiler_flags} -Wno-psabi" printf 'set(CMAKE_CXX_COMPILER "%s")\n' "/${!cxx_compiler/wrap-}" printf 'set(CMAKE_CXX_FLAGS_INIT "%s -Wno-psabi")\n' "${!cxx_compiler_flags} -Wno-psabi" printf 'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n' printf 'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n' printf 'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n' printf 'set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n' printf 'set(CMAKE_SYSROOT "%s")\n' "/${!toolchain_system_root_dir}" ) | sed -e "s!/usr/local!$toolchain_path!g;s!-mfpu=neon[^ ]\+!!g" } # Generate VCPKG triplet file generate_vcpkg_triplet_file() { local platform="$1" local architecture="$2" printf 'set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "%s%s")\n' \ '${CMAKE_CURRENT_LIST_DIR}/' \ "$platform.toolchain.cmake" printf 'set(VCPKG_CMAKE_SYSTEM_NAME Linux)\n' printf 'set(VCPKG_LIBRARY_LINKAGE static)\n' printf 'set(VCPKG_CRT_LINKAGE dynamic)\n' printf 'set(VCPKG_TARGET_ARCHITECTURE %s)\n' "$(to_vcpkg_architecture "$architecture")" } # Translate architecture names for VCPKG to_vcpkg_architecture() { case "$1" in 'x86_64') echo x64 ;; *) echo $1 ;; esac } if [ "$#" -ne 1 ]; then echo "Usage: $0 " exit 1 fi platform="$1" # Check if the architecture is valid if [[ ! " ${PLATFORM_LIST[@]} " =~ " $platform " ]]; then echo "Unsupported architecture: $platform" exit 1 fi download_toolchain $platform toolchain_path="mega/toolchain" # Source the platform's file from the pkgscripts provided by Synology # Some useful variables used in this script from the platfom file: # # ARCH: Target processor architecture (x86_64, arm, arm64,...) # CC32, CC64: C Compiler, for 32 and 64 bits. # CFLAGS32, CFLAGS64: C flags, for 32 and 64 bits. # CXX32, CXX64: C++ Compiler, for 32 and 64 bits. # CFLAGS32, CLFAGS64: C++ flags, for 32 and 64 bits. # LDFLAGS32, LDFLAGS64: Linker flags, for 32 and 64 bits. # ToolChainDir32, ToolChainDir64: Root of compiler, sysroot,... for 32 and 64 bits. # ToolChainSysRoot32, ToolChainSysRoot: sysroot path, for 32 and 64 bits. # # Note: 64 variables only exists for 64 capable platforms. That could be used to detect them source "/mega/pkgscripts/include/platform.$platform" || { echo "Failed to source platform file"; exit 1; } # Generate CMake toolchain file generate_cmake_toolchain_file "$toolchain_path" > "/mega/$platform.toolchain.cmake" || { echo "Couldn't generate toolchain file for \"$platform\""; exit 1; } # Generate VCPKG triplet file generate_vcpkg_triplet_file "$platform" "$ARCH" > "/mega/$platform.cmake" || { echo "Couldn't generate triplet file for \"$platform\""; exit 1; } sdk-10.11.0/dockerfile/dms-toolchains.conf000066400000000000000000000156411516266226600203600ustar00rootroot00000000000000# toolchains.conf # Base URL for downloading Synology Toolchains # To see original toolchain list access https://archive.synology.com/download/ToolChain/toolchain/7.2-63134 TOOLCHAIN_BASE_URL="https://global.synologydownload.com/download/ToolChain/toolchain/7.2-72746" # Associative arrays to store toolchain information for each architecture declare -A TOOLCHAIN_URLS declare -A TOOLCHAIN_FILES declare -A TOOLCHAIN_MD5_CHECKSUMS # Platforms list PLATFORM_LIST=("alpine" "alpine4k" "apollolake" "armada37xx" "armada38x" "avoton" "broadwell" "broadwellnk" "broadwellnkv2" "broadwellntbap" "bromolow" "braswell" "denverton" "epyc7002" "geminilake" "grantley" "kvmx64" "monaco" "purley" "r1000" "rtd1296" "rtd1619b" "v1000") # Toolchain information TOOLCHAIN_URLS["alpine"]="$TOOLCHAIN_BASE_URL/Annapurna%20Alpine%20Linux%203.10.108/alpine-gcc1220_glibc236_hard-GPL.txz" TOOLCHAIN_FILES["alpine"]="alpine-gcc1220_glibc236_hard-GPL" TOOLCHAIN_MD5_CHECKSUMS["alpine"]="d8eeadfa7464ccaee5cc32cf155dfbda" TOOLCHAIN_URLS["alpine4k"]="$TOOLCHAIN_BASE_URL/Annapurna%20Alpine%20Linux%203.10.108/alpine4k-gcc1220_glibc236_hard-GPL.txz" TOOLCHAIN_FILES["alpine4k"]="alpine4k-gcc1220_glibc236_hard-GPL" TOOLCHAIN_MD5_CHECKSUMS["alpine4k"]="eb29894eefa98b30b5807293692aa03f" TOOLCHAIN_URLS["apollolake"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.180%20%28Apollolake%29/apollolake-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["apollolake"]="apollolake-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["apollolake"]="3dbbef8b117dbbe699cf2ff8e9e11904" TOOLCHAIN_URLS["armada37xx"]="$TOOLCHAIN_BASE_URL/Marvell%20Armada%2037xx%20Linux%204.4.302/armada37xx-gcc1220_glibc236_armv8-GPL.txz" TOOLCHAIN_FILES["armada37xx"]="armada37xx-gcc1220_glibc236_armv8-GPL" TOOLCHAIN_MD5_CHECKSUMS["armada37xx"]="5fb60dccf7d7ea4864f1276bf221828d" TOOLCHAIN_URLS["armada38x"]="$TOOLCHAIN_BASE_URL/Marvell%20Armada%2038x%20Linux%203.10.108/armada38x-gcc1220_glibc236_hard-GPL.txz" TOOLCHAIN_FILES["armada38x"]="armada38x-gcc1220_glibc236_hard-GPL" TOOLCHAIN_MD5_CHECKSUMS["armada38x"]="5891351682940baea9cc745916a6cd58" TOOLCHAIN_URLS["avoton"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%203.10.108%20%28Avoton%29/avoton-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["avoton"]="avoton-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["avoton"]="2dfa97024ca647712fb4131a1b36c7db" TOOLCHAIN_URLS["braswell"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%203.10.108%20%28Braswell%29/braswell-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["braswell"]="braswell-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["braswell"]="62d89ff557f06566a6a75dba501ec84c" TOOLCHAIN_URLS["broadwell"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.180%20%28Broadwell%29/broadwell-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["broadwell"]="broadwell-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["broadwell"]="170844b7815bb65b034ebe88aef9f5d8" TOOLCHAIN_URLS["broadwellnk"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Broadwellnk%29/broadwellnk-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["broadwellnk"]="broadwellnk-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["broadwellnk"]="9eb4d301b87fb1a4898fb415a3dfc673" TOOLCHAIN_URLS["broadwellnkv2"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Broadwellnkv2%29/broadwellnkv2-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["broadwellnkv2"]="broadwellnkv2-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["broadwellnkv2"]="92a970128cf24b6383d37be7c03e0a5f" TOOLCHAIN_URLS["broadwellntbap"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Broadwellntbap%29/broadwellntbap-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["broadwellntbap"]="broadwellntbap-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["broadwellntbap"]="9ab356d43d7f15c39862b8b377fbd62b" TOOLCHAIN_URLS["bromolow"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20linux%203.10.108%20%28Bromolow%29/bromolow-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["bromolow"]="bromolow-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["bromolow"]="25e567a034044e5ad78b6212c561438a" TOOLCHAIN_URLS["denverton"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Denverton%29/denverton-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["denverton"]="denverton-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["denverton"]="1c621992c61e400fe310459d66f0d01f" TOOLCHAIN_URLS["epyc7002"]="$TOOLCHAIN_BASE_URL/AMD%20x86%20Linux%20Linux%205.10.55%20%28epyc7002%29/epyc7002-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["epyc7002"]="epyc7002-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["epyc7002"]="6996a1fa9a0901e011d95dcf733fd395" TOOLCHAIN_URLS["geminilake"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28GeminiLake%29/geminilake-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["geminilake"]="geminilake-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["geminilake"]="224104f875f50e8942edb467a23ba9ac" TOOLCHAIN_URLS["grantley"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%203.10.108%20%28Grantley%29/grantley-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["grantley"]="grantley-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["grantley"]="feedbd57a987f08c2b5a1d897d1f5999" TOOLCHAIN_URLS["kvmx64"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Kvmx64%29/kvmx64-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["kvmx64"]="kvmx64-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["kvmx64"]="44876e1583a2fbc4f297f93ef1d5808a" TOOLCHAIN_URLS["monaco"]="$TOOLCHAIN_BASE_URL/STMicroelectronics%20Monaco%20Linux%203.10.108/monaco-gcc1220_glibc236_hard-GPL.txz" TOOLCHAIN_FILES["monaco"]="monaco-gcc1220_glibc236_armv8-GPL" TOOLCHAIN_MD5_CHECKSUMS["monaco"]="a525ee451c1ba4232d6095ba8125fd92" TOOLCHAIN_URLS["purley"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28Purley%29/purley-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["purley"]="purley-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["purley"]="365ce203bc21ad4235cd3b60fd1963e6" TOOLCHAIN_URLS["r1000"]="$TOOLCHAIN_BASE_URL/AMD%20x86%20Linux%204.4.302%20%28r1000%29/r1000-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["r1000"]="r1000-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["r1000"]="a06941ceaaaa7646e273b9c6a07625ab" TOOLCHAIN_URLS["rtd1296"]="$TOOLCHAIN_BASE_URL/Realtek%20RTD129x%20Linux%204.4.302/rtd1296-gcc1220_glibc236_armv8-GPL.txz" TOOLCHAIN_FILES["rtd1296"]="rtd1296-gcc1220_glibc236_armv8-GPL" TOOLCHAIN_MD5_CHECKSUMS["rtd1296"]="8f466e2d7a05f09ceea7abd97f450b56" TOOLCHAIN_URLS["rtd1619b"]="$TOOLCHAIN_BASE_URL/Realtek%20RTD16xxb%20Linux%205.10.55/rtd1619b-gcc1220_glibc236_armv8-GPL.txz" TOOLCHAIN_FILES["rtd1619b"]="rtd1619b-gcc1220_glibc236_armv8-GPL" TOOLCHAIN_MD5_CHECKSUMS["rtd1619b"]="f2d2db6795ff0465a827b19b28797bf1" TOOLCHAIN_URLS["v1000"]="$TOOLCHAIN_BASE_URL/Intel%20x86%20Linux%204.4.302%20%28V1000%29/v1000-gcc1220_glibc236_x86_64-GPL.txz" TOOLCHAIN_FILES["v1000"]="v1000-gcc1220_glibc236_x86_64-GPL" TOOLCHAIN_MD5_CHECKSUMS["v1000"]="091301936a9bfbc0ec8dbcfc7a740818"sdk-10.11.0/dockerfile/linux-build.dockerfile000066400000000000000000000137771516266226600210620ustar00rootroot00000000000000# docker build --build-arg DISTRO= --build-arg ARCH= --build-arg CMAKE_PRESET= -f /path/to/linux-build.dockerfile -t sdk-linux-build . # # Where is one of the following: # - debian:11 # - debian:12 # - debian:13 # - debian:testing # - archlinux # - almalinux:9 # - centos:stream9 (or quay.io/centos/centos:stream9) # - opensuse/leap:15.6 # - opensuse/leap:16.0 # - opensuse/tumbleweed # - ubuntu:22.04 # - ubuntu:24.04 # - ubuntu:25.04 # - ubuntu:25.10 # - fedora:42 # - fedora:43 # # Where is one of the following: # - x64 (default) # # Where is one of the following: # - dev-unix (default) # # docker run -v /path/to/sdk:/mega/sdk sdk-linux-build # Define global args only visible to "FROM" directive(s) ARG DISTRO=ubuntu:22.04 # Map centos:stream* to quay.io namespace at pull time. FROM ${DISTRO/*centos:stream/quay.io/centos/centos:stream} # Define args visible in current "FROM" directive # Redeclaring DISTRO without a value inherits the global default ARG DISTRO ARG CMAKE_PRESET=dev-unix ARG ARCH=x64 ENV ARCH=$ARCH ENV DISTRO=$DISTRO ENV CMAKE_PRESET=$CMAKE_PRESET ENV SDK_BUILD_ENV_FILE=/mega/sdk-build.env WORKDIR /mega RUN bash -euo pipefail <<'EOF' echo "Building ${ARCH} architecture for ${DISTRO}" SDK_BUILD_PATH_PREFIX="" SDK_BUILD_CC="" SDK_BUILD_CXX="" SDK_BUILD_CMAKE_ARGS="" PACKAGES="autoconf autoconf-archive ca-certificates curl git gzip nasm pkg-config python3 tar unzip zip" case "${DISTRO}" in 'opensuse'*) zypper -n --gpg-auto-import-keys refresh zypper -n update zypper -n install shadow gcc gcc-c++ openssh which findutils rpm-build git zypper -n install -t pattern devel_C_C++ if [ "$DISTRO" = "opensuse/leap:16.0" ] || [ "$DISTRO" = "opensuse/tumbleweed" ]; then zypper -n remove zlib-ng-compat-devel || true fi PACKAGES="${PACKAGES} automake awk gcc-c++ cmake libtool make" if [ "$DISTRO" = "opensuse/leap:15.6" ]; then PACKAGES="${PACKAGES} python311" elif [ "$DISTRO" = "opensuse/leap:16.0" ] || [ "$DISTRO" = "opensuse/tumbleweed" ]; then PACKAGES="${PACKAGES} python313" fi if [ "$DISTRO" = "opensuse/leap:15.6" ]; then PACKAGES="${PACKAGES} gcc14 gcc14-c++" SDK_BUILD_CC="gcc-14" SDK_BUILD_CXX="g++-14" SDK_BUILD_CMAKE_ARGS="-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14" fi zypper install -y --force-resolution ${PACKAGES} if [ "$DISTRO" = "opensuse/leap:15.6" ]; then mkdir -p /mega/python311 ln -sf /usr/bin/python3.11 /mega/python311/python3 SDK_BUILD_PATH_PREFIX="/mega/python311" elif [ "$DISTRO" = "opensuse/leap:16.0" ] || [ "$DISTRO" = "opensuse/tumbleweed" ]; then mkdir -p /mega/python313 ln -sf /usr/bin/python3.13 /mega/python313/python3 SDK_BUILD_PATH_PREFIX="/mega/python313" fi ;; 'ubuntu'*|'debian'*) PACKAGES="$PACKAGES build-essential dh-autoreconf" export DEBCONF_NOWARNINGS=yes export DEBIAN_FRONTEND=noninteractive PACKAGES="${PACKAGES} cmake" apt-get update apt-get install -y ${PACKAGES} if [ "$DISTRO" = "debian:11" ]; then CMAKE_VERSION=3.20.2 EXPECTED_CMAKE_INSTALLER_SHA256="ea497b4658816010e5850a3ed53845e430654640aabbe10d93fe67def9503e4d" CMAKE_INSTALLER=cmake-${CMAKE_VERSION}-linux-x86_64.sh curl -fsSL https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_INSTALLER} -o cmake.sh echo "${EXPECTED_CMAKE_INSTALLER_SHA256} cmake.sh" | sha256sum -c || { echo "unexpected checksum for ${CMAKE_INSTALLER}!" exit 1 } chmod u+x cmake.sh ./cmake.sh --prefix=/usr --exclude-subdir --skip-license fi ;; 'archlinux'*) PACKAGES="${PACKAGES} base-devel cmake" pacman --noconfirm -Syu --needed ${PACKAGES} ;; 'fedora'*) dnf -y group install "development-tools" dnf -y remove zlib-ng-compat-devel || true dnf -y install which findutils rpm-build PACKAGES="${PACKAGES} gcc-c++ perl automake libtool awk cmake" # Until the VCPKG one is fixed, we need to install patchelf manually PACKAGES="${PACKAGES} patchelf" dnf install -y ${PACKAGES} ;; 'almalinux'*|'centos:stream'*|'quay.io/centos/centos:stream'*) # EL9 derivatives need EPEL + CRB to provide nasm/patchelf. dnf -y groupinstall "Development Tools" dnf install -y epel-release dnf-plugins-core dnf config-manager --set-enabled crb dnf -y makecache PACKAGES="${PACKAGES//curl/curl-minimal}" PACKAGES="${PACKAGES//pkg-config/pkgconf-pkg-config}" PACKAGES="${PACKAGES} gcc gcc-c++ make perl automake libtool gawk cmake nasm patchelf" dnf install -y ${PACKAGES} ;; *) echo "Error. ${DISTRO} is unknown or unsupported." exit 1 esac # persistent SDK build environment settings { if [ -n "${SDK_BUILD_PATH_PREFIX}" ]; then echo "export PATH=\"${SDK_BUILD_PATH_PREFIX}:\$PATH\"" fi if [ -n "${SDK_BUILD_CC}" ]; then echo "export CC=\"${SDK_BUILD_CC}\"" fi if [ -n "${SDK_BUILD_CXX}" ]; then echo "export CXX=\"${SDK_BUILD_CXX}\"" fi if [ -n "${SDK_BUILD_CMAKE_ARGS}" ]; then echo "export SDK_BUILD_CMAKE_ARGS=\"${SDK_BUILD_CMAKE_ARGS}\"" fi } > "${SDK_BUILD_ENV_FILE}" EOF RUN git clone https://github.com/microsoft/vcpkg.git CMD ["/bin/sh", "-c", "\ echo \"ARCH=$ARCH DISTRO=$DISTRO CMAKE_PRESET=$CMAKE_PRESET\" && \ cat \"$SDK_BUILD_ENV_FILE\" && \ . \"$SDK_BUILD_ENV_FILE\" && \ cmake \ --preset $CMAKE_PRESET \ $SDK_BUILD_CMAKE_ARGS \ -DVCPKG_ROOT=vcpkg \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -S sdk \ -B build && \ cmake --build build -j $(nproc) \ "] sdk-10.11.0/dockerfile/release-management.dockerfile000066400000000000000000000011031516266226600223340ustar00rootroot00000000000000FROM ubuntu:24.04 ENV DEBCONF_NOWARNINGS=yes ENV DEBIAN_FRONTEND=noninteractive ARG USER_ID=1000 ARG GROUP_ID=1000 RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ python3-pip \ git \ gpg \ gpg-agent \ openssh-client \ && apt-get clean && rm -rf /var/lib/apt/lists/* RUN groupadd -g ${GROUP_ID} jenkins 2>/dev/null || true && \ useradd -m -u ${USER_ID} -g ${GROUP_ID} jenkins 2>/dev/null || true COPY requirements.txt ./requirements.txt RUN pip3 install --no-cache-dir -r requirements.txt --break-system-packages sdk-10.11.0/dockerfile/report-jira-slack.dockerfile000066400000000000000000000007551516266226600221470ustar00rootroot00000000000000# This is the container where `automation/report_jira_tickets_on_slack.py` should run FROM debian:12 ARG AUTO_USER=automation ARG AUTO_HOME=/var/lib/automation RUN apt update && \ apt install -y python3-full && \ useradd -md $AUTO_HOME -s /bin/bash $AUTO_USER USER $AUTO_USER WORKDIR $AUTO_HOME ADD automation $AUTO_HOME RUN python3 -m venv $AUTO_HOME/venv && \ $AUTO_HOME/venv/bin/pip install -r requirements.txt CMD ["venv/bin/python3", "report_jira_tickets_on_slack.py"] sdk-10.11.0/examples/000077500000000000000000000000001516266226600142655ustar00rootroot00000000000000sdk-10.11.0/examples/CMakeLists.txt000066400000000000000000000001611516266226600170230ustar00rootroot00000000000000# CMake file to build example apps that use SDKlib. # add_subdirectory(megacli) add_subdirectory(simple_client) sdk-10.11.0/examples/Swift/000077500000000000000000000000001516266226600153615ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/.gitignore000066400000000000000000000017701516266226600173560ustar00rootroot00000000000000### Xcode ### build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.xcuserstate ### Objective-C ### # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Pods/ ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk sdk-10.11.0/examples/Swift/MEGA/000077500000000000000000000000001516266226600160725ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/000077500000000000000000000000001516266226600205775ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/project.pbxproj000066400000000000000000000722721516266226600236650ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 60; objects = { /* Begin PBXBuildFile section */ 410C423A1A60453C00CA67D3 /* Contacts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 410C42391A60453C00CA67D3 /* Contacts.storyboard */; }; 41A29D731A6528C8006B24D2 /* SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 41A29D401A6528C8006B24D2 /* SSKeychain.m */; }; 41A29D741A6528C8006B24D2 /* SSKeychainQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 41A29D421A6528C8006B24D2 /* SSKeychainQuery.m */; }; 41A29D751A6528C8006B24D2 /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 41A29D451A6528C8006B24D2 /* SVProgressHUD.bundle */; }; 41A29D761A6528C8006B24D2 /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 41A29D471A6528C8006B24D2 /* SVProgressHUD.m */; }; 41A990781A5D645600B2094A /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A990771A5D645600B2094A /* MainTabBarViewController.swift */; }; 41A9907B1A5D65C100B2094A /* Cloud.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41A9907A1A5D65C100B2094A /* Cloud.storyboard */; }; 41A9907D1A5D678400B2094A /* CloudDriveTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A9907C1A5D678400B2094A /* CloudDriveTableViewController.swift */; }; 41A9907F1A5D8C3F00B2094A /* NodeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41A9907E1A5D8C3F00B2094A /* NodeTableViewCell.swift */; }; 41AB404E1A52D66500E5E16E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41AB404C1A52D66500E5E16E /* Main.storyboard */; }; 41AB404F1A52D66500E5E16E /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41AB404D1A52D66500E5E16E /* LoginViewController.swift */; }; 41ABAEA11A63DD410026CEAD /* ContactsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41ABAEA01A63DD410026CEAD /* ContactsTableViewController.swift */; }; 41ABAEA31A63DD710026CEAD /* ContactTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41ABAEA21A63DD710026CEAD /* ContactTableViewCell.swift */; }; 41B4A5B51A5E94B8008B6E73 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41B4A5B41A5E94B8008B6E73 /* Helper.swift */; }; 41B4A5BA1A5EF067008B6E73 /* DetailsNodeInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41B4A5B91A5EF067008B6E73 /* DetailsNodeInfoViewController.swift */; }; 41C6A0441A5FD13300865971 /* Offline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41C6A0431A5FD13300865971 /* Offline.storyboard */; }; 41C6A0461A5FD15B00865971 /* OfflineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C6A0451A5FD15B00865971 /* OfflineTableViewController.swift */; }; 41C6A0491A5FF8CC00865971 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41C6A0481A5FF8CC00865971 /* Settings.storyboard */; }; 41C6A04B1A5FFA9B00865971 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41C6A04A1A5FFA9B00865971 /* SettingsViewController.swift */; }; 41F1756F1A51E22500570E30 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41F1756E1A51E22500570E30 /* AppDelegate.swift */; }; 41F175761A51E22500570E30 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41F175751A51E22500570E30 /* Images.xcassets */; }; 41F175791A51E22500570E30 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 41F175771A51E22500570E30 /* LaunchScreen.xib */; }; A8CCCFF42AE18B3300E3CCAB /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A8CCCFF32AE18B3300E3CCAB /* MEGASdk */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 410C42391A60453C00CA67D3 /* Contacts.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Contacts.storyboard; sourceTree = ""; }; 41A29D3F1A6528C8006B24D2 /* SSKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychain.h; sourceTree = ""; }; 41A29D401A6528C8006B24D2 /* SSKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychain.m; sourceTree = ""; }; 41A29D411A6528C8006B24D2 /* SSKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychainQuery.h; sourceTree = ""; }; 41A29D421A6528C8006B24D2 /* SSKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainQuery.m; sourceTree = ""; }; 41A29D441A6528C8006B24D2 /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; }; 41A29D451A6528C8006B24D2 /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; 41A29D461A6528C8006B24D2 /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; 41A29D471A6528C8006B24D2 /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; 41A990771A5D645600B2094A /* MainTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = ""; }; 41A9907A1A5D65C100B2094A /* Cloud.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Cloud.storyboard; path = ../CloudDrive/Cloud.storyboard; sourceTree = ""; }; 41A9907C1A5D678400B2094A /* CloudDriveTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CloudDriveTableViewController.swift; path = ../CloudDrive/CloudDriveTableViewController.swift; sourceTree = ""; }; 41A9907E1A5D8C3F00B2094A /* NodeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NodeTableViewCell.swift; path = ../CloudDrive/NodeTableViewCell.swift; sourceTree = ""; }; 41AB404C1A52D66500E5E16E /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 41AB404D1A52D66500E5E16E /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 41ABAEA01A63DD410026CEAD /* ContactsTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsTableViewController.swift; sourceTree = ""; }; 41ABAEA21A63DD710026CEAD /* ContactTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactTableViewCell.swift; sourceTree = ""; }; 41ABFDEE1CBB93F700CC9D71 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 41ABFDF01CBB942D00CC9D71 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; 41B4A5B41A5E94B8008B6E73 /* Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 41B4A5B91A5EF067008B6E73 /* DetailsNodeInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DetailsNodeInfoViewController.swift; path = ../CloudDrive/DetailsNodeInfoViewController.swift; sourceTree = ""; }; 41C6A0431A5FD13300865971 /* Offline.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Offline.storyboard; sourceTree = ""; }; 41C6A0451A5FD15B00865971 /* OfflineTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OfflineTableViewController.swift; sourceTree = ""; }; 41C6A0481A5FF8CC00865971 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 41C6A04A1A5FFA9B00865971 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 41F175691A51E22500570E30 /* MEGA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MEGA.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41F1756D1A51E22500570E30 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41F1756E1A51E22500570E30 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41F175751A51E22500570E30 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 41F175781A51E22500570E30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 41F175941A51E5E500570E30 /* MEGA-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MEGA-Bridging-Header.h"; sourceTree = ""; }; 41F1759F1A51F17E00570E30 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; 41F175A11A51F18B00570E30 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; A8BF108D21ED0BD7006B283A /* libsodium.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsodium.a; path = ../../../bindings/ios/3rdparty/lib/libsodium.a; sourceTree = ""; }; A8BF108E21ED0BD7006B283A /* libuv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libuv.a; path = ../../../bindings/ios/3rdparty/lib/libuv.a; sourceTree = ""; }; A8BF108F21ED0BD7006B283A /* libcrypto.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcrypto.a; path = ../../../bindings/ios/3rdparty/lib/libcrypto.a; sourceTree = ""; }; A8BF109021ED0BD7006B283A /* libcryptopp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcryptopp.a; path = ../../../bindings/ios/3rdparty/lib/libcryptopp.a; sourceTree = ""; }; A8BF109121ED0BD8006B283A /* libcurl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcurl.a; path = ../../../bindings/ios/3rdparty/lib/libcurl.a; sourceTree = ""; }; A8BF109221ED0BD8006B283A /* libssl.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libssl.a; path = ../../../bindings/ios/3rdparty/lib/libssl.a; sourceTree = ""; }; A8BF109321ED0BD8006B283A /* libzen.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libzen.a; path = ../../../bindings/ios/3rdparty/lib/libzen.a; sourceTree = ""; }; A8BF109421ED0BD8006B283A /* libmediainfo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmediainfo.a; path = ../../../bindings/ios/3rdparty/lib/libmediainfo.a; sourceTree = ""; }; A8BF109521ED0BD8006B283A /* libcares.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libcares.a; path = ../../../bindings/ios/3rdparty/lib/libcares.a; sourceTree = ""; }; A8BF109F21ED0BF7006B283A /* libMEGASDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libMEGASDK.a; sourceTree = BUILT_PRODUCTS_DIR; }; A8BF10A121ED0CF9006B283A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; A8BF10A321ED0D04006B283A /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; A8BF10A521ED0D0D006B283A /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; A8BF10A721ED0D15006B283A /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 41F175661A51E22500570E30 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A8CCCFF42AE18B3300E3CCAB /* MEGASdk in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 410C42381A60453C00CA67D3 /* Contacts */ = { isa = PBXGroup; children = ( 410C42391A60453C00CA67D3 /* Contacts.storyboard */, 41ABAEA01A63DD410026CEAD /* ContactsTableViewController.swift */, 41ABAEA21A63DD710026CEAD /* ContactTableViewCell.swift */, ); path = Contacts; sourceTree = ""; }; 41A29CD91A6528C8006B24D2 /* vendor */ = { isa = PBXGroup; children = ( 41A29D3E1A6528C8006B24D2 /* SSKeychain */, 41A29D431A6528C8006B24D2 /* SVProgressHUD */, ); name = vendor; path = ../../../iOS/Demo/Demo/vendor; sourceTree = ""; }; 41A29D3E1A6528C8006B24D2 /* SSKeychain */ = { isa = PBXGroup; children = ( 41A29D3F1A6528C8006B24D2 /* SSKeychain.h */, 41A29D401A6528C8006B24D2 /* SSKeychain.m */, 41A29D411A6528C8006B24D2 /* SSKeychainQuery.h */, 41A29D421A6528C8006B24D2 /* SSKeychainQuery.m */, ); path = SSKeychain; sourceTree = ""; }; 41A29D431A6528C8006B24D2 /* SVProgressHUD */ = { isa = PBXGroup; children = ( 41A29D441A6528C8006B24D2 /* SVProgressHUD-Prefix.pch */, 41A29D451A6528C8006B24D2 /* SVProgressHUD.bundle */, 41A29D461A6528C8006B24D2 /* SVProgressHUD.h */, 41A29D471A6528C8006B24D2 /* SVProgressHUD.m */, ); path = SVProgressHUD; sourceTree = ""; }; 41A990791A5D65A100B2094A /* CloudDrive */ = { isa = PBXGroup; children = ( 41A9907A1A5D65C100B2094A /* Cloud.storyboard */, 41A9907C1A5D678400B2094A /* CloudDriveTableViewController.swift */, 41A9907E1A5D8C3F00B2094A /* NodeTableViewCell.swift */, 41B4A5B91A5EF067008B6E73 /* DetailsNodeInfoViewController.swift */, ); name = CloudDrive; path = MEGA/CloudDrive; sourceTree = SOURCE_ROOT; }; 41AB404B1A52D66500E5E16E /* Login */ = { isa = PBXGroup; children = ( 41AB404C1A52D66500E5E16E /* Main.storyboard */, 41AB404D1A52D66500E5E16E /* LoginViewController.swift */, 41A990771A5D645600B2094A /* MainTabBarViewController.swift */, ); path = Login; sourceTree = ""; }; 41B4A5B31A5E949D008B6E73 /* Utils */ = { isa = PBXGroup; children = ( 41B4A5B41A5E94B8008B6E73 /* Helper.swift */, ); path = Utils; sourceTree = ""; }; 41C6A0421A5FD13300865971 /* Offline */ = { isa = PBXGroup; children = ( 41C6A0431A5FD13300865971 /* Offline.storyboard */, 41C6A0451A5FD15B00865971 /* OfflineTableViewController.swift */, ); path = Offline; sourceTree = ""; }; 41C6A0471A5FF8CC00865971 /* Settings */ = { isa = PBXGroup; children = ( 41C6A0481A5FF8CC00865971 /* Settings.storyboard */, 41C6A04A1A5FFA9B00865971 /* SettingsViewController.swift */, ); path = Settings; sourceTree = ""; }; 41F175601A51E22500570E30 = { isa = PBXGroup; children = ( 41F175931A51E57C00570E30 /* Frameworks */, 41F1756B1A51E22500570E30 /* MEGA */, 41F1756A1A51E22500570E30 /* Products */, ); sourceTree = ""; }; 41F1756A1A51E22500570E30 /* Products */ = { isa = PBXGroup; children = ( 41F175691A51E22500570E30 /* MEGA.app */, ); name = Products; sourceTree = ""; }; 41F1756B1A51E22500570E30 /* MEGA */ = { isa = PBXGroup; children = ( 41F1756E1A51E22500570E30 /* AppDelegate.swift */, 41AB404B1A52D66500E5E16E /* Login */, 41A990791A5D65A100B2094A /* CloudDrive */, 41C6A0421A5FD13300865971 /* Offline */, 410C42381A60453C00CA67D3 /* Contacts */, 41C6A0471A5FF8CC00865971 /* Settings */, 41A29CD91A6528C8006B24D2 /* vendor */, 41B4A5B31A5E949D008B6E73 /* Utils */, 41F175751A51E22500570E30 /* Images.xcassets */, 41F175771A51E22500570E30 /* LaunchScreen.xib */, 41F1756C1A51E22500570E30 /* Supporting Files */, 41F175941A51E5E500570E30 /* MEGA-Bridging-Header.h */, ); path = MEGA; sourceTree = ""; }; 41F1756C1A51E22500570E30 /* Supporting Files */ = { isa = PBXGroup; children = ( 41F1756D1A51E22500570E30 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 41F175931A51E57C00570E30 /* Frameworks */ = { isa = PBXGroup; children = ( A8BF10A721ED0D15006B283A /* libresolv.tbd */, A8BF10A521ED0D0D006B283A /* libz.tbd */, A8BF10A321ED0D04006B283A /* libsqlite3.tbd */, A8BF10A121ED0CF9006B283A /* libc++.tbd */, A8BF109F21ED0BF7006B283A /* libMEGASDK.a */, A8BF109521ED0BD8006B283A /* libcares.a */, A8BF108F21ED0BD7006B283A /* libcrypto.a */, A8BF109021ED0BD7006B283A /* libcryptopp.a */, A8BF109121ED0BD8006B283A /* libcurl.a */, A8BF109421ED0BD8006B283A /* libmediainfo.a */, A8BF108D21ED0BD7006B283A /* libsodium.a */, A8BF109221ED0BD8006B283A /* libssl.a */, A8BF108E21ED0BD7006B283A /* libuv.a */, A8BF109321ED0BD8006B283A /* libzen.a */, 41ABFDF01CBB942D00CC9D71 /* CoreMedia.framework */, 41ABFDEE1CBB93F700CC9D71 /* AVFoundation.framework */, 41F175A11A51F18B00570E30 /* MobileCoreServices.framework */, 41F1759F1A51F17E00570E30 /* ImageIO.framework */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 41F175681A51E22500570E30 /* MEGA */ = { isa = PBXNativeTarget; buildConfigurationList = 41F175881A51E22500570E30 /* Build configuration list for PBXNativeTarget "MEGA" */; buildPhases = ( 41F175651A51E22500570E30 /* Sources */, 41F175661A51E22500570E30 /* Frameworks */, 41F175671A51E22500570E30 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = MEGA; packageProductDependencies = ( A8CCCFF32AE18B3300E3CCAB /* MEGASdk */, ); productName = MEGA; productReference = 41F175691A51E22500570E30 /* MEGA.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 41F175611A51E22500570E30 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = ""; LastSwiftMigration = 0710; LastSwiftUpdateCheck = 0710; LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Mega Limited"; TargetAttributes = { 41F175681A51E22500570E30 = { CreatedOnToolsVersion = 6.1.1; DevelopmentTeam = T9RH74Y7L9; LastSwiftMigration = 1500; }; }; }; buildConfigurationList = 41F175641A51E22500570E30 /* Build configuration list for PBXProject "MEGA" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 41F175601A51E22500570E30; packageReferences = ( A8CCCFF22AE18B3300E3CCAB /* XCLocalSwiftPackageReference "../../.." */, ); productRefGroup = 41F1756A1A51E22500570E30 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 41F175681A51E22500570E30 /* MEGA */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 41F175671A51E22500570E30 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 41AB404E1A52D66500E5E16E /* Main.storyboard in Resources */, 41C6A0441A5FD13300865971 /* Offline.storyboard in Resources */, 41F175791A51E22500570E30 /* LaunchScreen.xib in Resources */, 41C6A0491A5FF8CC00865971 /* Settings.storyboard in Resources */, 41A29D751A6528C8006B24D2 /* SVProgressHUD.bundle in Resources */, 41A9907B1A5D65C100B2094A /* Cloud.storyboard in Resources */, 410C423A1A60453C00CA67D3 /* Contacts.storyboard in Resources */, 41F175761A51E22500570E30 /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 41F175651A51E22500570E30 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 41A990781A5D645600B2094A /* MainTabBarViewController.swift in Sources */, 41A9907F1A5D8C3F00B2094A /* NodeTableViewCell.swift in Sources */, 41A29D741A6528C8006B24D2 /* SSKeychainQuery.m in Sources */, 41ABAEA31A63DD710026CEAD /* ContactTableViewCell.swift in Sources */, 41A29D731A6528C8006B24D2 /* SSKeychain.m in Sources */, 41ABAEA11A63DD410026CEAD /* ContactsTableViewController.swift in Sources */, 41C6A0461A5FD15B00865971 /* OfflineTableViewController.swift in Sources */, 41AB404F1A52D66500E5E16E /* LoginViewController.swift in Sources */, 41A9907D1A5D678400B2094A /* CloudDriveTableViewController.swift in Sources */, 41F1756F1A51E22500570E30 /* AppDelegate.swift in Sources */, 41B4A5BA1A5EF067008B6E73 /* DetailsNodeInfoViewController.swift in Sources */, 41A29D761A6528C8006B24D2 /* SVProgressHUD.m in Sources */, 41B4A5B51A5E94B8008B6E73 /* Helper.swift in Sources */, 41C6A04B1A5FFA9B00865971 /* SettingsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 41F175771A51E22500570E30 /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( 41F175781A51E22500570E30 /* Base */, ); name = LaunchScreen.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 41F175861A51E22500570E30 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 41F175871A51E22500570E30 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 41F175891A51E22500570E30 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = T9RH74Y7L9; INFOPLIST_FILE = MEGA/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "nz.mega.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/MEGA/MEGA-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = ( ../../../bindings/ios, ../../../include, ); }; name = Debug; }; 41F1758A1A51E22500570E30 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = T9RH74Y7L9; INFOPLIST_FILE = MEGA/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "nz.mega.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/MEGA/MEGA-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; USER_HEADER_SEARCH_PATHS = ( ../../../bindings/ios, ../../../include, ); }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 41F175641A51E22500570E30 /* Build configuration list for PBXProject "MEGA" */ = { isa = XCConfigurationList; buildConfigurations = ( 41F175861A51E22500570E30 /* Debug */, 41F175871A51E22500570E30 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 41F175881A51E22500570E30 /* Build configuration list for PBXNativeTarget "MEGA" */ = { isa = XCConfigurationList; buildConfigurations = ( 41F175891A51E22500570E30 /* Debug */, 41F1758A1A51E22500570E30 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ A8CCCFF22AE18B3300E3CCAB /* XCLocalSwiftPackageReference "../../.." */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../..; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ A8CCCFF32AE18B3300E3CCAB /* MEGASdk */ = { isa = XCSwiftPackageProductDependency; productName = MEGASdk; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 41F175611A51E22500570E30 /* Project object */; } sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/project.xcworkspace/000077500000000000000000000000001516266226600245755ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/project.xcworkspace/contents.xcworkspacedata000066400000000000000000000002071516266226600315360ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/project.xcworkspace/xcshareddata/000077500000000000000000000000001516266226600272305ustar00rootroot00000000000000IDEWorkspaceChecks.plist000066400000000000000000000003561516266226600336330ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA.xcodeproj/project.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning sdk-10.11.0/examples/Swift/MEGA/MEGA/000077500000000000000000000000001516266226600166035ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/AppDelegate.swift000066400000000000000000000077531516266226600220500ustar00rootroot00000000000000/** * @file AppDelegate.swift * @brief The AppDelegate of the app * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, MEGARequestDelegate { var window: UIWindow? let megaapi = MEGASdk(appKey: "", userAgent: nil, basePath: FileManager.default.temporaryDirectory.path)! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { MEGASdk.setLogLevel(.max) MEGASdk.setLogToConsole(true) let storyboard = UIStoryboard(name: "Main", bundle: nil) if (SSKeychain.password(forService: "MEGA", account: "session") != nil) { megaapi.fastLogin(withSession: SSKeychain.password(forService: "MEGA", account: "session"), delegate: self) let tabBarC = storyboard.instantiateViewController(withIdentifier: "TabBarControllerID") as! UITabBarController window?.rootViewController = tabBarC } else { let loginVC = storyboard.instantiateViewController(withIdentifier: "LoginViewControllerID") window?.rootViewController = loginVC } return true } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } // MARK: - MEGA Request delegate func onRequestStart(_ api: MEGASdk, request: MEGARequest) { if request.type == MEGARequestType.MEGARequestTypeFetchNodes { SVProgressHUD.show(withStatus: "Updating nodes...") } } func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { return } if request.type == MEGARequestType.MEGARequestTypeLogin { megaapi.fetchNodes(with: self) } } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Base.lproj/000077500000000000000000000000001516266226600206025ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Base.lproj/LaunchScreen.xib000066400000000000000000000071651516266226600236710ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/Base.lproj/Main.storyboard000066400000000000000000000030551516266226600236030ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/CloudDrive/000077500000000000000000000000001516266226600206435ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/CloudDrive/Cloud.storyboard000066400000000000000000000650251516266226600240330ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/CloudDrive/CloudDriveTableViewController.swift000066400000000000000000000325021516266226600276320ustar00rootroot00000000000000/** * @file CloudDriveTableViewController.swift * @brief View controller that show MEGA nodes and allow navigate through folders * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import AssetsLibrary import MEGASdk class CloudDriveTableViewController: UITableViewController, MEGADelegate, UIActionSheetDelegate, UIAlertViewDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate { @IBOutlet weak var headerView: UIView! @IBOutlet weak var filesFolderLabel: UILabel! @IBOutlet weak var addBarButtonItem: UIBarButtonItem! var parentNode : MEGANode! var nodes : MEGANodeList! let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() let thumbsURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] let thumbsDirectory = thumbsURL.appendingPathComponent("thumbs") if !FileManager.default.fileExists(atPath: thumbsDirectory.path) { do { try FileManager.default.createDirectory(atPath: thumbsDirectory.path, withIntermediateDirectories: true, attributes: nil) } catch let error as NSError { NSLog("Unable to create directory \(error.debugDescription)") } } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) megaapi.add(self) megaapi.retryPendingConnections() reloadUI() } override func viewWillDisappear(_ animated: Bool) { megaapi.remove(self) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func reloadUI() { var filesAndFolders : String! if parentNode == nil { navigationItem.title = "Cloud Drive" guard let rootNode = megaapi.rootNode else { return } nodes = megaapi.children(forParent: rootNode) let files = megaapi.numberChildFiles(forParent: rootNode) let folders = megaapi.numberChildFolders(forParent: rootNode) if files == 0 || files > 1 { if folders == 0 || folders > 1 { filesAndFolders = "\(folders) folders, \(files) files" } else if folders == 0 { filesAndFolders = "\(folders) folder, \(files) files" } } else if files == 1 { if folders == 0 || folders > 1 { filesAndFolders = "\(folders) folders, \(files) file" } else if folders == 1 { filesAndFolders = "\(folders) folder, \(files) file" } } } else { navigationItem.title = parentNode.name nodes = megaapi.children(forParent: parentNode) let files = megaapi.numberChildFiles(forParent: parentNode) let folders = megaapi.numberChildFolders(forParent: parentNode) if files == 0 || files > 1 { if folders == 0 || folders > 1 { filesAndFolders = "\(folders) folders, \(files) files" } else if folders == 0 { filesAndFolders = "\(folders) folder, \(files) files" } } else if files == 1 { if folders == 0 || folders > 1 { filesAndFolders = "\(folders) folders, \(files) file" } else if folders == 1 { filesAndFolders = "\(folders) folder, \(files) file" } } } filesFolderLabel.text = filesAndFolders tableView.reloadData() } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { nodes.size } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "nodeCell", for: indexPath) as! NodeTableViewCell guard let node = nodes.node(at: indexPath.row) else { return UITableViewCell() } cell.nameLabel.text = node.name let thumbnailFilePath = Helper.pathForNode(node, path:FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: thumbnailFilePath) if !fileExists { if node.hasThumbnail() { megaapi.getThumbnailNode(node, destinationFilePath: thumbnailFilePath) } else { cell.thumbnailImageView.image = Helper.imageForNode(node) } } else { cell.thumbnailImageView.image = UIImage(named: thumbnailFilePath) } if (node.isFile()) { cell.subtitleLabel.text = ByteCountFormatter.string(fromByteCount: (node.size?.int64Value)!, countStyle: ByteCountFormatter.CountStyle.memory) } else { let files = megaapi.numberChildFiles(forParent: node) let folders = megaapi.numberChildFolders(forParent: node) cell.subtitleLabel.text = "\(folders) folders, \(files) files" } cell.nodeHandle = node.handle return cell } override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return true } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return headerView } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 20.0 } override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { guard let node = nodes.node(at: indexPath.row) else { return } megaapi.remove(node) } } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let node = nodes.node(at: indexPath.row) if (node?.isFolder())! { let storyboard = UIStoryboard(name: "Cloud", bundle: nil) let cdtvc = storyboard.instantiateViewController(withIdentifier: "CloudDriveID") as!CloudDriveTableViewController cdtvc.parentNode = node self.navigationController?.pushViewController(cdtvc, animated: true) } } override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { let node = nodes.node(at: indexPath.row) let storyboard = UIStoryboard(name: "Cloud", bundle: nil) let nidvc = storyboard.instantiateViewController(withIdentifier: "nodeInfoDetails") as! DetailsNodeInfoViewController nidvc.node = node self.navigationController?.pushViewController(nidvc, animated: true) } // MARK: - IBActions @IBAction func addOption(_ sender: UIBarButtonItem) { let actionSheet = UIActionSheet(title: nil, delegate: self, cancelButtonTitle: "Cancel", destructiveButtonTitle: nil, otherButtonTitles: "Create folder", "Upload photo") actionSheet.show(from: (self.tabBarController?.tabBar)!) } // MARK: - UIActionSheetDelegate func actionSheet(_ actionSheet: UIActionSheet, clickedButtonAt buttonIndex: Int) { if buttonIndex == 1 { //Create new folder let folderAlertView = UIAlertView(title: "Create new folder", message: "Name of the new folder", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Create") folderAlertView.alertViewStyle = UIAlertViewStyle.plainTextInput folderAlertView.textField(at: 0)?.text = "" folderAlertView.tag = 1 folderAlertView.show() } else if buttonIndex == 2 { //Upload photo let imagePickerController = UIImagePickerController() imagePickerController.modalPresentationStyle = UIModalPresentationStyle.currentContext imagePickerController.sourceType = UIImagePickerController.SourceType.photoLibrary imagePickerController.delegate = self self.tabBarController?.present(imagePickerController, animated: true, completion: nil) } } // MARK: - UIAlertViewDelegate func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { if alertView.tag == 1 { if buttonIndex == 1 { if parentNode != nil { megaapi.createFolder(withName: (alertView.textField(at: 0)?.text ?? ""), parent: parentNode) } else { guard let rootNode = megaapi.rootNode else { return } megaapi.createFolder(withName: (alertView.textField(at: 0)?.text) ?? "", parent: rootNode) } } } } // MARK: - UIImagePickerControllerDelegate func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // Local variable inserted by Swift 4.2 migrator. let info = convertFromUIImagePickerControllerInfoKeyDictionary(info) let assetURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.referenceURL)] as! URL let library = ALAssetsLibrary() library.asset(for: assetURL, resultBlock: { (asset: ALAsset!) in let name: String = asset.defaultRepresentation().filename() let modificationTime: Date = asset.value(forProperty: ALAssetPropertyDate) as! Date let imageView = UIImageView() imageView.image = (info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.originalImage)] as! UIImage) let webData: Data = imageView.image!.jpegData(compressionQuality: 0.9)! let localFileURL = URL(fileURLWithPath:NSTemporaryDirectory()) let localFilePath = localFileURL.appendingPathComponent(name) try? webData.write(to: URL(fileURLWithPath: localFilePath.path), options: [.atomic]) let attributes = [FileAttributeKey.modificationDate : modificationTime] try? FileManager.default.setAttributes(attributes, ofItemAtPath: localFilePath.path) if let parentNode = self.parentNode { self.megaapi.startUpload(withLocalPath: localFilePath.path, parent: parentNode, fileName: nil, appData: nil, isSourceTemporary: false, startFirst: false, cancelToken: MEGACancelToken()) } self.dismiss(animated: true, completion: nil) }, failureBlock: nil) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { self.dismiss(animated: true, completion: nil) } // MARK: - MEGA Request delegate func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { return } switch request.type { case MEGARequestType.MEGARequestTypeFetchNodes: SVProgressHUD.dismiss() case MEGARequestType.MEGARequestTypeGetAttrFile: guard let cells = tableView.visibleCells as? [NodeTableViewCell] else { break } for tableViewCell in cells where request.nodeHandle == tableViewCell.nodeHandle { let node = megaapi.node(forHandle: request.nodeHandle) let thumbnailFilePath = Helper.pathForNode(node!, path: FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: thumbnailFilePath) if fileExists { tableViewCell.thumbnailImageView.image = UIImage(named: thumbnailFilePath) } } default: break } } // MARK: - MEGA Global delegate func onNodesUpdate(_ api: MEGASdk, nodeList: MEGANodeList) { reloadUI() } } // Helper function inserted by Swift 4.2 migrator. fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] { return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)}) } // Helper function inserted by Swift 4.2 migrator. fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String { return input.rawValue } sdk-10.11.0/examples/Swift/MEGA/MEGA/CloudDrive/DetailsNodeInfoViewController.swift000066400000000000000000000253771516266226600276450ustar00rootroot00000000000000/** * @file DetailsNodeInfoViewController.swift * @brief View controller that show details info about a node * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class DetailsNodeInfoViewController: UIViewController, MEGADelegate, UIAlertViewDelegate { @IBOutlet weak var thumbnailImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var modificationTimeLabel: UILabel! @IBOutlet weak var sizeLabel: UILabel! @IBOutlet weak var downloadButton: UIButton! @IBOutlet weak var downloadProgressView: UIProgressView! @IBOutlet weak var saveLabel: UILabel! @IBOutlet weak var cancelButton: UIButton! var node : MEGANode! var currentTransfer : MEGATransfer! var renameAlertView : UIAlertView! var removeAlertView : UIAlertView! let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) reloadUI() megaapi.add(self) megaapi.retryPendingConnections() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) megaapi.remove(self) } func reloadUI() { if node.type == MEGANodeType.folder { downloadButton.isHidden = true } let thumbnailFilePath = Helper.pathForNode(node, path: FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: thumbnailFilePath) if !fileExists { thumbnailImageView.image = Helper.imageForNode(node) } else { thumbnailImageView.image = UIImage(contentsOfFile: thumbnailFilePath) } title = node.name nameLabel.text = node.name let dateFormatter = DateFormatter() let theDateFormat = DateFormatter.Style.short let theTimeFormat = DateFormatter.Style.short dateFormatter.dateStyle = theDateFormat dateFormatter.timeStyle = theTimeFormat if node.isFile() { if let date = node.modificationTime { modificationTimeLabel.text = dateFormatter.string(from: date) } if let size = node.size { sizeLabel.text = ByteCountFormatter.string(fromByteCount: size.int64Value, countStyle: ByteCountFormatter.CountStyle.memory) } } else { if let date = node.creationTime { modificationTimeLabel.text = dateFormatter.string(from: date) } sizeLabel.text = ByteCountFormatter.string(fromByteCount: megaapi.size(for: node).int64Value, countStyle: ByteCountFormatter.CountStyle.memory) } let documentFilePath = Helper.pathForNode(node, path: FileManager.SearchPathDirectory.documentDirectory) let fileDocumentExists = FileManager.default.fileExists(atPath: documentFilePath) if fileDocumentExists { downloadProgressView.isHidden = true cancelButton.isHidden = true saveLabel.isHidden = false downloadButton.setImage(UIImage(named: "savedFile"), for: UIControl.State()) saveLabel.text = "Saved for offline" } else if megaapi.transfers.size > 0 { downloadProgressView.isHidden = true cancelButton.isHidden = true for i in 0 ..< megaapi.transfers.size { if let transfer = megaapi.transfers.transfer(at: i) { if transfer.nodeHandle == node.handle { downloadProgressView.isHidden = false cancelButton.isHidden = false currentTransfer = transfer let progress = Float(transfer.transferredBytes / transfer.totalBytes) downloadProgressView.setProgress(progress, animated: true) continue } } } } else { downloadProgressView.isHidden = true cancelButton.isHidden = true } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - IBActions @IBAction func touchUpInsideDownload(_ sender: UIButton) { if node.type == MEGANodeType.file { let documentFilePath = Helper.pathForNode(node, path: FileManager.SearchPathDirectory.documentDirectory) let fileExists = FileManager.default.fileExists(atPath: documentFilePath) if !fileExists { // megaapi.startDownloadNode(node, localPath: documentFilePath, fileName: nil, appData: nil, startFirst: false, cancelToken: MEGACancelToken(), collisionCheck: CollisionCheckFingerprint, collisionResolution: CollisionResolutionNewWithN) } } } @IBAction func touchUpInsideGenerateLink(_ sender: UIButton) { megaapi.export(node) } @IBAction func touchUpInsideRename(_ sender: UIButton) { renameAlertView = UIAlertView(title: "Rename", message: "Enter the new name", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Rename") renameAlertView.alertViewStyle = UIAlertViewStyle.plainTextInput guard let name = node.name else { return } let nameURL = URL(string: name)?.deletingPathExtension() renameAlertView.textField(at: 0)?.text = nameURL!.path renameAlertView.tag = 0 renameAlertView.show() } @IBAction func touchUpInsideDelete(_ sender: UIButton) { removeAlertView = UIAlertView(title: "Remove node", message: "Are you sure?", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Remove") removeAlertView.alertViewStyle = UIAlertViewStyle.default removeAlertView.tag = 1 removeAlertView.show() } @IBAction func touchUpInsideCancelDownload(_ sender: AnyObject) { megaapi.cancelTransfer(currentTransfer) } // MARK: - AlertView delegate func alertView(_ alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) { if alertView.tag == 0 { guard let name = node.name else { return } let nameURL = URL(string: name) if buttonIndex == 1 { if nameURL!.pathExtension == "" { megaapi.renameNode(node, newName: alertView.textField(at: 0)?.text ?? "") } else { let newName = (alertView.textField(at: 0)?.text ?? "") + ("." + (nameURL?.pathExtension ?? "")) nameLabel.text = newName megaapi.renameNode(node, newName: newName) } } } if alertView.tag == 1 { if buttonIndex == 1 { megaapi.remove(node) navigationController?.popViewController(animated: true) } } } // MARK: - MEGA Request delegate func onRequestStart(_ api: MEGASdk, request: MEGARequest) { if request.type == MEGARequestType.MEGARequestTypeExport { SVProgressHUD.show(withStatus: "Generate link...") } } func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { return } switch request.type { case MEGARequestType.MEGARequestTypeGetAttrFile: if request.nodeHandle == node.handle { let node = megaapi.node(forHandle: request.nodeHandle) let thumbnailFilePath = Helper.pathForNode(node!, path: FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: thumbnailFilePath) if fileExists { thumbnailImageView.image = UIImage(contentsOfFile: thumbnailFilePath) } } case MEGARequestType.MEGARequestTypeExport: SVProgressHUD.showSuccess(withStatus: "Link Generate") SVProgressHUD.dismiss() let items = [request.link] let activity : UIActivityViewController = UIActivityViewController(activityItems: items as [Any], applicationActivities: nil) activity.excludedActivityTypes = [UIActivity.ActivityType.print, UIActivity.ActivityType.copyToPasteboard, UIActivity.ActivityType.assignToContact, UIActivity.ActivityType.saveToCameraRoll] self.present(activity, animated: true, completion: nil) default: break } } // MARK: - MEGA Global delegate func onNodesUpdate(_ api: MEGASdk, nodeList: MEGANodeList) { node = nodeList.node(at: 0) } // MARK: - MEGA Transfer delegate func onTransferStart(_ api: MEGASdk, transfer: MEGATransfer) { downloadProgressView.isHidden = false downloadProgressView.setProgress(0.0, animated: true) cancelButton.isHidden = false currentTransfer = transfer } func onTransferUpdate(_ api: MEGASdk, transfer: MEGATransfer) { if transfer.nodeHandle == node.handle { let progress = Float(transfer.transferredBytes / transfer.totalBytes) downloadProgressView.setProgress(progress, animated: true) } else { downloadProgressView.setProgress(0.0, animated: true) } } func onTransferFinish(_ api: MEGASdk, transfer: MEGATransfer, error: MEGAError) { downloadProgressView.isHidden = true cancelButton.isHidden = true if error.type == MEGAErrorType.apiEIncomplete { downloadProgressView.setProgress(0.0, animated: true) } else { downloadProgressView.setProgress(1.0, animated: true) saveLabel.isHidden = false downloadButton.setImage(UIImage(named: "savedFile"), for: UIControl.State()) saveLabel.text = "Saved for offline" } } } sdk-10.11.0/examples/Swift/MEGA/MEGA/CloudDrive/NodeTableViewCell.swift000066400000000000000000000022701516266226600252120ustar00rootroot00000000000000/** * @file NodeTableViewCell.swift * @brief Custom node table view cell of the app. * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit class NodeTableViewCell: UITableViewCell { @IBOutlet weak var thumbnailImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var subtitleLabel: UILabel! var nodeHandle: UInt64! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Contacts/000077500000000000000000000000001516266226600203615ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Contacts/ContactTableViewCell.swift000066400000000000000000000022201516266226600254310ustar00rootroot00000000000000/** * @file ContactTableViewCell.swift * @brief Custom contact table view cell of the app. * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit class ContactTableViewCell: UITableViewCell { @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var shareLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Contacts/Contacts.storyboard000066400000000000000000000210441516266226600242520ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/Contacts/ContactsTableViewController.swift000066400000000000000000000103051516266226600270630ustar00rootroot00000000000000/** * @file ContactsTableViewController.swift * @brief View controller that show your contacts * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class ContactsTableViewController: UITableViewController, MEGARequestDelegate { var users : MEGAUserList! var filterUsers = [MEGAUser]() let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() users = megaapi.contacts() for i in 0 ..< users.size { let user = users.user(at: i) if user?.visibility == MEGAUserVisibility.visible { filterUsers.append(user!) } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return filterUsers.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactTableViewCell let user = filterUsers[indexPath.row] cell.nameLabel.text = user.email let avatarFilePath = Helper.pathForUser(user, path: FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: avatarFilePath) if fileExists { cell.avatarImageView.image = UIImage(named: avatarFilePath) cell.avatarImageView.layer.cornerRadius = cell.avatarImageView.frame.size.width/2 cell.avatarImageView.layer.masksToBounds = true } else { megaapi.getAvatarUser(user, destinationFilePath: avatarFilePath, delegate: self) } let numFilesShares = megaapi.inShares(for: user).size if numFilesShares == 0 { cell.shareLabel.text = "No folders shared" } else if numFilesShares == 1 { cell.shareLabel.text = "\(numFilesShares) folder shared" } else { cell.shareLabel.text = "\(numFilesShares) folders shared" } return cell } // MARK: - MEGA Request delegate func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { return } switch request.type { case MEGARequestType.MEGARequestTypeGetAttrUser: guard let cells = tableView.visibleCells as? [ContactTableViewCell] else { break } for tableViewCell in cells where request.email == tableViewCell.nameLabel.text { let filename = request.email let avatarURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] let avatarFilePath = avatarURL.appendingPathComponent("thumbs").appendingPathComponent(filename!) let fileExists = FileManager.default.fileExists(atPath: avatarFilePath.path) if fileExists { tableViewCell.avatarImageView.image = UIImage(named: avatarFilePath.path) tableViewCell.avatarImageView.layer.cornerRadius = tableViewCell.avatarImageView.frame.size.width/2 tableViewCell.avatarImageView.layer.masksToBounds = true } } default: break } } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/000077500000000000000000000000001516266226600216445ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/000077500000000000000000000000001516266226600253415ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000046131516266226600300350ustar00rootroot00000000000000{ "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "1x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "icon-Small-40@2x.png", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "57x57", "scale" : "1x" }, { "idiom" : "iphone", "size" : "57x57", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "icon-60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "icon-60@3x.png", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "icon-Small-40.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "icon-Small-40@2x-1.png", "scale" : "2x" }, { "idiom" : "ipad", "size" : "50x50", "scale" : "1x" }, { "idiom" : "ipad", "size" : "50x50", "scale" : "2x" }, { "idiom" : "ipad", "size" : "72x72", "scale" : "1x" }, { "idiom" : "ipad", "size" : "72x72", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "icon-76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "icon-76@2x.png", "scale" : "2x" }, { "idiom" : "ipad", "size" : "83.5x83.5", "scale" : "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-60@2x.png000066400000000000000000000045161516266226600276020ustar00rootroot00000000000000PNG  IHDRxx9d6 IDATxlUgcd q`) z2dꠃR`Di)2\s:eE@tƠ#v(e[P11h{m{Ϗ{CvH9soI}'O$"X"%"X"%"X"%$9?'{>K֊6+Nւǹ-oɽ4Y"81-s} r ⴂ9pΒ},ُ A3q6v."X[u;o`'&=0>6Y> jY(v"ؕc\LU^!ߞDb!3ѪJaS݂Or@9j܋Xp 'T e=K}̵ɨl+JƂFuGXp #at1ci2KzA.I$۽CdlU['8tS؂>2nknQa /Qլ.V6]`gw0|)\^u2\d?MUW˭.iHg w'MVNm0ͯ`;9WXG?p S[zoюx}. FFyciW ۙI/#/ ,Wܬ'C!+ 2;j`d>.Yk;It/ *c_vU@+G)YE!@9-.^T҃K t`$~ R/tEMȍM02u(0n YsZA#63IKD'?2 zxՂC$i[{IAw0ktzjbHű7]!%1.vPWi]U@7 D0umձ9tnX#w @'H;=ꂟ5] $1P4>R3$?b#wŝv޲Wo[T $"1$t^%p'I,;I:,)xU\2a˯RGɭ';cwAE&:uKkUy 3^)V%S' TiOBvN튈`?3JԿy;]()p8arD0KL&Ϝ}SoC#plpAvAn%gu-xKU^S> 1>5~kŌܬDKDDKDDKD`I,>_IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-60@3x.png000066400000000000000000000074661516266226600276120ustar00rootroot00000000000000PNG  IHDReIDATxҁ K?v_ȁȁȁȁ.9#{pTe8㔑qfw( I{ BqTj"WEܑ@)E_ZPV wd/gܞ_L[5%=s6g$\~#UjرslwO3 `"/-?,blFl!8Jrdm|=^z4 ޢLÁ8EB7wS1H.p)FB>f +1  %d>e31e2 Cy(Rs?o D(Xa3;|Mw]P6{ 䰚DmVV0>kIZZ GiB3V}H ;L!9ةo`ch _ ux0v@ap<MrtUX̓&B}xB-T""h3"!GR~cRWvYzAsТ(;z珒1D-BS)c Dd@CgڲMJhSAкCQGVIw-Z 40KH}Č 6f:y?J$ͨ#dr(Kf 9UoAKXˡjKbvE{],V1E٭r#m>9SQH#2 vJS*r*0GD'ˍ.CJ+f3h89RrA۝.q1LwZ*b2P.#`L .`v}$f`Β52L$ 0!^.9$"rrȡ?+9ȡjyH iI Ma9ԝ$uLJCJIwLI)LeLec#7Cr%)#WP"9 {RGc.%:<"U_?"*Th[aCݑJ~ONI$?}9<(`ꈦ8ɑzre?=IXؤB)x&JCރxٙf0g 18P\2'43P #c)IrH a; c rHӟWjKABX]@1d>ap,Q@7O,)9Q6f0(eIF9+`R21fn(SlCQcOKX 9j܊HVlCL[H,^z?Rq.³T 9RohJçN\( xv5l-rGv5Ɗ]$E\"h)7Er%vWf+G X_!+kw< 87Xp2F(1!ۛer E\ft/9my8njk1O9%q_ր0{Z5az/9.u) nF@-15N1!sk;nolF=.x;J"mWj:96 m4㻟p.r>-#<[9fXsC5dڏpEId@-#2y5o!瘒seZؖ_ɥȔ{@ W"pı#:-OuHH(j9Per$-3r6r`x4vb.D[ 29'ZC=P6bbf[ 29 -瘔象ҩ=J&l4~I9ZKMzgHh=,l90J+ 'gȱ!?,<$2 b,#RSr,]ՅCP`AbvGId΍(-#nsɁ+瑢PG,(-C9u&@"'Mh Oi`T3rE /hΙ( ó"Fim&[0ѥyWRS6fU rDrI9I98+!\v90D[`H9V:Oc)9@hFGLʁI9UIĎ}|bFWQAIdnU%Q=nnӃ!s=bRbR%'"`]r=Bi_*;*( ç}bFo!9Tը ;MDQU`drg8G>}ZZ49PPOx"P=۝PP◱؁ivʣNLȧq ]{+a&Gb"\ Um8/"U[[#1,roql}1 +7c]6Fo_1g[Ĥdͻroq`!A9HU]͹UQi*Na9@q1 %u$#᜴U>B!rhM9=3\ 9@lkHlK5GA|% F# 'rbB ^'@9@bbH Orsn` 01-i2r嫹9=D8Vkj B ^8|ZA@+nYs[n<k;FOiN$:~9C[v4VTfg @(%?`,=ø8Ej@r%g«7?բAp TUG"7= Uq (r>BC 9BC 9!@!r71HGIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-76.png000066400000000000000000000032271516266226600272350ustar00rootroot00000000000000PNG  IHDRLLH|^IDATxklSeN !؞V.# a@`n]D \e48#CD0Pİ]> ɲT<}~ٵ=ͼy}^ՌXM}Ûy󺂩Q856* $z›A؈[q!)l" b@}]5%FBܷ5% F ɽ9s WB ra![lIU $?WѪ*X ԫ;U*^ {?KT3LV\(9H6~3aZr${r$iE@* +L93֗PX$qL+bƥe$Fm#=q _חhyT҅X֟FԦҬi]ԸFcT9+޴!cR)TCJvF#HTG /SJG:چ !(OVRjܿ@hlV 2^}C 6wذ^޻4!#C,Q*At-dX@/h` 09*A2q\0^EyM#uM QT)q Hvcװ^TѸe" rvxQp4(ժ%Q5 #b_uhWbW&zKmC;MB<\Cbqevs*w^=粳bLj#DÙ Sr0M;U%BT:M;B? uQ/ࡓӯRBj[ү\a] I2mL.|H,^[&\.N`01 Wn@V +ȄIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-76@2x.png000066400000000000000000000062231516266226600276060ustar00rootroot00000000000000PNG  IHDRv ZIDATx{pToaFG)>ل1TB D @(Z8B}նj ؇L(RT|v:ڙ:"yy'wfw9t(rv9'` 3;$D**JD*E%RETQ"UȬɻ_fv'Ylv&6w+{\>˰[{_`;Q"HxlǬl3+[ G@ÕHʄ_{7zZo`?z)FHI #!r#*.8q5Jd^1+vfe+ u#a0%R,q;d.0+[xR<sV8 iXcf+۲늊69W"3Ya|"7[UAs ܾO."ّgf*K-/K]2w>M%M3ǖH3]~9McRm2d*x2{i=hJW4NK=]/2d3A hE&'2[NW TBPdlإ qDbӎtE(P \H#i5Q(2w"NjLg2T *BQ$g+2kb D{KR5 aPDTuN(PsH#5QwWv@Gܻ7_hH/I* (VF;EP,5wS_ojDLDB (_zB"әvÿB!Tb *2M;(p"Y8XJ YBL?کW(@ɂaߠ/WzQ2_T]SE& ߨ/SQ5]H?wLi TŅA߆IDf~(KrOh[ '_Vq#a0~VU^"ӯ)D W`E\@(nOrHH}]1RpDe/0T"B1aLLkc2)rP̀Q47s X^HArKo <ǰPT_ю \s 0(_Ș1[BA~Xy#q=D|9"'+yEN$pWZ2 2D",Olmra"@HǏF|W .7ɚGYеDW [DG,\u1pE]KP`Yd֌j#Dpĺ57[ۭ[DZ$K'[HKnolv.#EeY~Ӱ2:%ގ-%($2Cj,H$RP٥tpa[!r/a1Ң ’{H$Œds_XƑ D" \6M:v"H$2zEkl&Pag_o"BZTt&K"Z:@,em[-pD|Kytp^>]%?[@q5!- ",ʪy+ˮgX.S>Xp)\ E|K, U< Rw- GyAp.I]H> E8qp.u#R-U!&-69Yp88F BZDX$PVE3vk1!"8EE. ]B37kZ̀d/t]"I|- -lqH^AKoLE-D׶{btl{.  BZDXݼ"Ah:sܒKl!8"!’_9\$6cAWqEB%{+99eMhl)IZDX:|tCPQKך#\8X-"' $- ",~[,"Apa1xYA1F9X&~H",Dڍr"D;әy#&1]KP"os=6PU.Eh4 `H( xO^"89PuU?-_(a>"@.}><ԋ |9H@HdlKXi# v)"#7P"4 {JIt;ʗ&B^߰#AXi#" F(_HDkܢD^$j7_%1a`.QdfP/!K.\ ^J\ ſ Qdv6鳚|mH pH:ɐbhmfda,g#KF0@p {bbEPUH|K9"GDxb{j E"!E9B* z<#BzQ2H$'TtDz#ij!ogt(H${=[: ˑdHu].T$sU\P=-ݳ@E=U߸gYDQm"F E"ڝ; +@.y04:B$>t_ #r o#Dܣ􈃺8@H;N/E9Xlbb (GDnjQ&"8\$buWpTj1cqPrY5S?PEb3#mpD",tܩ,Tz ?ԩ?-s {=gYēsdD"^ӄc9Rb"L<#$~+UxcH$oQbQxG+ Ofxǜ"L&}9ūÇӋ=Ul gal DDx >bP8fQ"ČO9:ρ`0!9(S4l:v0p 3D $F^\acp(BzsEW"\KyQ"2qxwuq,D Ŀ|;nvpٚo.s{z]{]v >˰[q%JD(*JD(*J_l aIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-Small-40.png000066400000000000000000000016301516266226600302660ustar00rootroot00000000000000PNG  IHDR(( H_PLTEeIt9i}*\(N)[|}(O(M2`'K'M'I'I'G'J&F0[&Dz&B&C%?z%=0U&J%;+P*N$9&I$6%B]v$4$;^w>Z#1$81M#.0K#-bwD^PWi@IDATx1kA?;RV).H| APPP" װ&M A"יJHWngvէݰXY] $Ke9%˟<@G^tQ~~́[8w0a-8M,py+\XQ! " I+29"!XÑ̔i /!:XVy6nK9ju.aöK-Sنjg ;fBJ3o|;fH麏,9t0N X"|cC ,~ߞO3,5+X<|:n1Zixܝٺa\j`;OÉs@&AARrL^kL,BRށ>{u8VE[&P'mqSKKcIhAc6O&+PR>+=C\_x̓L`Qi&j9 :+sf pzǾ7I.ND8eVـo&1l؄l/TK pO1ḻ3x&JlQmULb8uc݋U ;f p>K0 p5hkGU9wm<F' ܟ`ml5h|5prñUXI8{>x m+<O8ldp)O94&X:d$E`Cp+ŷ(fK=$[öv},P|&lQЪ^V0I mBGoQ(@]0fni$Ell؄ZYGxp7&c-u\jO$畀Y ~Y8jg,|=LbP&rglQ_*$0J~_`ZrR Qf X&`Ka5 &rg&PJlo[,;$a\JLf+'ZuCAh_/NӨ˦;}Z5P2e2p |kM&d}IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/AppIcon.appiconset/icon-Small-40@2x.png000066400000000000000000000032611516266226600306420ustar00rootroot00000000000000PNG  IHDRPPsexIDATxiej 1D!zMU.Ń+,FP(&j ~#%q(.Hd r,mvNۙyΙvwLygK \.X5N6$Ȅb)q-& h1 Kn*_\ 4}?{`odfɵ5k`[ "iloIylIS!׊yD[ht4gU'8Y,.p:#mRpXB!b/*@pPrsGHpq>! " I+29"!XÑ̔i /!:XVy6nK9ju.aöK-Sنjg ;fBJ3o|;fH麏,9t0N X"|cC ,~ߞO3,5+X<|:n1Zixܝٺa\j`;OÉs@&AARrL^kL,BRށ>{u8VE[&P'mqSKKcIhAc6O&+PR>+=C\_x̓L`Qi&j9 :+sf pzǾ7I.ND8eVـo&1l؄l/TK pO1ḻ3x&JlQmULb8uc݋U ;f p>K0 p5hkGU9wm<F' ܟ`ml5h|5prñUXI8{>x m+<O8ldp)O94&X:d$E`Cp+ŷ(fK=$[öv},P|&lQЪ^V0I mBGoQ(@]0fni$Ell؄ZYGxp7&c-u\jO$畀Y ~Y8jg,|=LbP&rglQ_*$0J~_`ZrR Qf X&`Ka5 &rg&PJlo[,;$a\JLf+'ZuCAh_/NӨ˦;}Z5P2e2p |kM&d}IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/000077500000000000000000000000001516266226600246535ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/delete.imageset/000077500000000000000000000000001516266226600277125ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/delete.imageset/Contents.json000066400000000000000000000002331516266226600324000ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "delete.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/delete.imageset/delete.pdf000066400000000000000000000171171516266226600316560ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3"f&k&2, endstream endobj 5 0 obj 48 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 22 0 R >> /XObject << /Fm4 16 0 R /Fm2 10 0 R /Fm1 7 0 R /Fm5 19 0 R /Fm3 13 0 R >> >> endobj 16 0 obj << /Length 17 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [14 6 15 18] /Resources 18 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T04Q0PU049 D(h\]BQQB8P@L!SA߹H!3dK#c F :D)K0MR endstream endobj 17 0 obj 99 endobj 18 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [12 23 17 25] /Resources 12 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x=K 0 D9ŬP#EtdBC%˼G#Xq2'ި4^:IMK=%T9KO?LvV6p[T亵 lLa+ endstream endobj 11 0 obj 133 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 3 24 27] /Resources 9 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream xMPn0 +x.0/Vh Ht`AeJ"/Kx VHHC,(s%!ǩS,@-Vt}U(C#͒)!tE+ ljmR 2LEM6uZ945ިv41c3zTb-Yn Me|9E5xq/v@IV ̃?F?҇%X+o*P-9L b>Ggc ~1CXf7,b5}r҄a9/6r endstream endobj 8 0 obj 278 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 19 0 obj << /Length 20 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 6 11 18] /Resources 21 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T04P0PU049 D(h\]BQQB8P@L!SA߹H!3dS#c f h!204-@ۇ endstream endobj 20 0 obj 99 endobj 21 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 13 0 obj << /Length 14 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [18 6 19 18] /Resources 15 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T0\CK@fՅk))1*s2<#8.NV04V02Zads@! c04*N  endstream endobj 14 0 obj 99 endobj 15 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 22 0 obj << /Type /ExtGState /ca 0.3 >> endobj 25 0 obj << /Length 26 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 26 0 obj 1047 endobj 23 0 obj [ /ICCBased 25 0 R ] endobj 27 0 obj << /Length 28 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 28 0 obj 2612 endobj 24 0 obj [ /ICCBased 27 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 29 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 30 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 31 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 30 0 R /CreationDate 31 0 R /ModDate 31 0 R >> endobj xref 0 32 0000000000 65535 f 0000006887 00000 n 0000000162 00000 n 0000006649 00000 n 0000000022 00000 n 0000000144 00000 n 0000000264 00000 n 0000001244 00000 n 0000001731 00000 n 0000001750 00000 n 0000000807 00000 n 0000001154 00000 n 0000001174 00000 n 0000002220 00000 n 0000002532 00000 n 0000002551 00000 n 0000000406 00000 n 0000000718 00000 n 0000000737 00000 n 0000001819 00000 n 0000002131 00000 n 0000002150 00000 n 0000002621 00000 n 0000003839 00000 n 0000006612 00000 n 0000002668 00000 n 0000003818 00000 n 0000003876 00000 n 0000006591 00000 n 0000006730 00000 n 0000006794 00000 n 0000006845 00000 n trailer << /Size 32 /Root 29 0 R /Info 1 0 R /ID [ <21d5330f20b3da40fa718d7bec7cfe00> <21d5330f20b3da40fa718d7bec7cfe00> ] >> startxref 6962 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/moveFile.imageset/000077500000000000000000000000001516266226600302165ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/moveFile.imageset/Contents.json000066400000000000000000000002311516266226600327020ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "move.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/moveFile.imageset/move.pdf000066400000000000000000000175031516266226600316650ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x]=n1{z ׉:U0R~DvSWz+-NE,͘ et!Ʊ +kA I%MH"ȃUǛQK%ԢR.7)G1焦{M>֧^^a^S5QwGWD*?Fy%;p5,Ҩ5o;:d!VNɩY~pB;'g*oD?7-Om' v#Pg]2!t0n+;7eЄ* endstream endobj 5 0 obj 299 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> /ExtGState << /Gs1 20 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R /Fm4 17 0 R /Fm3 14 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [8 18 20 27] /Resources 9 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEPN@ W0ɼ|*Hip8Wܡ֤͎/`%w7;SiVeYeaKduўB!$ڠY{5^#7i5/G1e,RcM9ה` >Rwģ8w@۱%l$r$L1u9n)9/vod~%7ܜO>dž* [(kXmg%~?Fq{ Wb6 endstream endobj 8 0 obj 250 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [2 10 11 20] /Resources 12 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEMN0 =[#a8͚jP@ijZ_y~;:t$SMj7aMe[[*ޠHYF9u%yS%tȰQ>&h`kz8V6InjDSV$T%Ɯe@)+4#g񴥸- O[˻7-ɜ<1b|o$>%ޟ7\=!}88\-FC /n^r=l; endstream endobj 11 0 obj 270 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 17 0 obj << /Length 18 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [8 3 20 12] /Resources 19 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEQA1 >ꍝLt;elEr2o:ӛd!z>87aj NҢJbFu*W 0;UQƄʋzUX>)0*isG& P@p"BlVV u!f0֥W*n B<^<"}1h{DRWbnI? -ڙ+0SMM_vt]v:\7 endstream endobj 18 0 obj 243 endobj 19 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [17 10 26 20] /Resources 16 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xMQAn1sx} rTTJRm]3x?BOR!ٸmF@}E]J]Xypᦕ4Gd!zoFh? ƵO95ئ.-siTQNO@<"(1|`ޓCCiE̹l8;yRa3ȴEIb X11C΍'s;8+gu[Ӄ|X .pI?D_r endstream endobj 15 0 obj 279 endobj 16 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 20 0 obj << /Type /ExtGState /ca 0.3 >> endobj 22 0 obj << /Length 23 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 23 0 obj 2612 endobj 13 0 obj [ /ICCBased 22 0 R ] endobj 24 0 obj << /Length 25 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 25 0 obj 1047 endobj 21 0 obj [ /ICCBased 24 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 26 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 27 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 28 0 obj (D:20141030204035Z00'00') endobj 1 0 obj << /Producer 27 0 R /CreationDate 28 0 R /ModDate 28 0 R >> endobj xref 0 29 0000000000 65535 f 0000007191 00000 n 0000000414 00000 n 0000006953 00000 n 0000000022 00000 n 0000000395 00000 n 0000000516 00000 n 0000000676 00000 n 0000001136 00000 n 0000001155 00000 n 0000001224 00000 n 0000001707 00000 n 0000001727 00000 n 0000005708 00000 n 0000002342 00000 n 0000002835 00000 n 0000002855 00000 n 0000001797 00000 n 0000002252 00000 n 0000002272 00000 n 0000002925 00000 n 0000006916 00000 n 0000002972 00000 n 0000005687 00000 n 0000005745 00000 n 0000006895 00000 n 0000007034 00000 n 0000007098 00000 n 0000007149 00000 n trailer << /Size 29 /Root 26 0 R /Info 1 0 R /ID [ ] >> startxref 7266 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/renameFile.imageset/000077500000000000000000000000001516266226600305175ustar00rootroot00000000000000Contents.json000066400000000000000000000002331516266226600331260ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/renameFile.imageset{ "images" : [ { "idiom" : "universal", "filename" : "rename.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/renameFile.imageset/rename.pdf000066400000000000000000000144651516266226600324730ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3 \ endstream endobj 5 0 obj 34 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 13 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 4 24 23] /Resources 9 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xE0 Eld/X4 C4]mB:$-H endstream endobj 8 0 obj 202 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [12 11 30 29] /Resources 12 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xmKN#a 9,5'ȊD (w`4(~|ntwtYNbVmWgU8XyrnB4ܻ#N@esj5hʌ֒ /3nC0&%DI%R85sOIFun肅io;갞\ѵI #3i ™vHMmeA<4v&pJeeayKM Z6ldL}J4Rk" {brGis!!y3?.n揉%~vO8Urœ_/Tdx,p5὎w?w endstream endobj 11 0 obj 336 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 13 0 obj << /Type /ExtGState /ca 0.3 >> endobj 16 0 obj << /Length 17 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 17 0 obj 1047 endobj 14 0 obj [ /ICCBased 16 0 R ] endobj 18 0 obj << /Length 19 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 19 0 obj 2612 endobj 15 0 obj [ /ICCBased 18 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 20 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 21 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 22 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 21 0 R /CreationDate 22 0 R /ModDate 22 0 R >> endobj xref 0 23 0000000000 65535 f 0000005761 00000 n 0000000148 00000 n 0000005523 00000 n 0000000022 00000 n 0000000130 00000 n 0000000250 00000 n 0000000356 00000 n 0000000767 00000 n 0000000786 00000 n 0000000855 00000 n 0000001405 00000 n 0000001425 00000 n 0000001495 00000 n 0000002713 00000 n 0000005486 00000 n 0000001542 00000 n 0000002692 00000 n 0000002750 00000 n 0000005465 00000 n 0000005604 00000 n 0000005668 00000 n 0000005719 00000 n trailer << /Size 23 /Root 20 0 R /Info 1 0 R /ID [ <6cfe4fd32301805f58952e1fd0b68a69> <6cfe4fd32301805f58952e1fd0b68a69> ] >> startxref 5836 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/saveFile.imageset/000077500000000000000000000000001516266226600302065ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/saveFile.imageset/Contents.json000066400000000000000000000002351516266226600326760ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "download.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/saveFile.imageset/download.pdf000066400000000000000000000164331516266226600325170ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream xU1D ) I}NT7)Ez+k gU>Kvkk/;i|M8̥0dtQ ]> oEԨט#μ8Z dME!>?3s>5<¹N}UN\̙e,Tu{&җej峬kHʆAѨZAg%jtPk1X[DΤFdԬ"yf`>=#_IతN(9mS)#C`ҿNV#ˀu|xKGV͜;0XN ;#M0xecYW`AsesEA̶MLpȅf3ꁓՕ]t2IDhuܜWbVZN9Զ #R5e5Ԙ_KDTťVdt$PyU>[so"_ooCˏUy8Z!7kp p %~+WUK;lmICU endstream endobj 5 0 obj 573 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 9 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T0TT02W0T(JUWSw/6TH/Vw5VpW© endstream endobj 8 0 obj 48 endobj 9 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 17 0 R >> /XObject << /Fm3 14 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 12 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T04P0RT04S(JUWSw/6TH/Vw5QpW endstream endobj 11 0 obj 48 endobj 12 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 17 0 R >> /XObject << /Fm4 18 0 R >> >> endobj 18 0 obj << /Length 19 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 20 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T04P0RT04S(JUWS03STw.6RH.8YTX!WDh] e 2`iZ ~ endstream endobj 19 0 obj 78 endobj 20 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 21 0 R >> >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 16 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T0TT02W0T(JUWS03STw.6RH.8YADX!DQ0Q ) |B F endstream endobj 15 0 obj 79 endobj 16 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 21 0 R >> >> endobj 17 0 obj << /Type /ExtGState /ca 0.3 >> endobj 22 0 obj << /Length 23 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 23 0 obj 1047 endobj 13 0 obj [ /ICCBased 22 0 R ] endobj 24 0 obj << /Length 25 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 25 0 obj 2612 endobj 21 0 obj [ /ICCBased 24 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 26 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 27 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 28 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 27 0 R /CreationDate 28 0 R /ModDate 28 0 R >> endobj xref 0 29 0000000000 65535 f 0000006639 00000 n 0000000688 00000 n 0000006401 00000 n 0000000022 00000 n 0000000669 00000 n 0000000790 00000 n 0000000867 00000 n 0000001124 00000 n 0000001142 00000 n 0000001237 00000 n 0000001498 00000 n 0000001517 00000 n 0000003591 00000 n 0000001993 00000 n 0000002284 00000 n 0000002303 00000 n 0000002373 00000 n 0000001613 00000 n 0000001904 00000 n 0000001923 00000 n 0000006364 00000 n 0000002420 00000 n 0000003570 00000 n 0000003628 00000 n 0000006343 00000 n 0000006482 00000 n 0000006546 00000 n 0000006597 00000 n trailer << /Size 29 /Root 26 0 R /Info 1 0 R /ID [ <862472231fc96d1ce650196689100783> <862472231fc96d1ce650196689100783> ] >> startxref 6714 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/savedFile.imageset/000077500000000000000000000000001516266226600303525ustar00rootroot00000000000000Contents.json000066400000000000000000000002401516266226600327570ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/savedFile.imageset{ "images" : [ { "idiom" : "universal", "filename" : "downloadRed.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }downloadRed.pdf000066400000000000000000000146071516266226600332400ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/savedFile.imageset%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream xU1D ) I}NT7)Ez+k gU>Kvkk/;i|M8̥0dtQ ]> oEԨט#μ8Z dME!>?3s>5<¹N}UN\̙e,Tu{&җej峬kHʆAѨZAg%jtPk1X[DΤFdԬ"yf`>=#_IతN(9mS)#C`ҿNV#ˀu|xKGV͜;0XN ;#M0xecYW`AsesEA̶MLpȅf3ꁓՕ]t2IDhuܜWbVZN9Զ #R5e5Ԙ_KDTťVdt$PyU>[so"_ooCˏUy8Z!7kp p %~+WUK;lmICU endstream endobj 5 0 obj 573 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /XObject << /Fm2 10 0 R /Fm1 7 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 12 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x5K@@NQk ZχY;!A2!W"#VP^ڳ ISI"yvUҌC 'j3ee8rZ2>G endstream endobj 11 0 obj 89 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 14 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 9 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x=; 0{O1|4V@b46;;!b&n,> >> endobj 15 0 obj << /Length 16 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 16 0 obj 2612 endobj 14 0 obj [ /ICCBased 15 0 R ] endobj 17 0 obj << /Length 18 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 18 0 obj 1047 endobj 13 0 obj [ /ICCBased 17 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 19 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 20 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 21 0 obj (D:20141030183620Z00'00') endobj 1 0 obj << /Producer 20 0 R /CreationDate 21 0 R /ModDate 21 0 R >> endobj xref 0 22 0000000000 65535 f 0000005863 00000 n 0000000688 00000 n 0000005625 00000 n 0000000022 00000 n 0000000669 00000 n 0000000790 00000 n 0000001258 00000 n 0000001557 00000 n 0000001575 00000 n 0000000867 00000 n 0000001169 00000 n 0000001188 00000 n 0000005588 00000 n 0000004380 00000 n 0000001644 00000 n 0000004359 00000 n 0000004417 00000 n 0000005567 00000 n 0000005706 00000 n 0000005770 00000 n 0000005821 00000 n trailer << /Size 22 /Root 19 0 R /Info 1 0 R /ID [ <0c22306c488c1d8d4759f330f6a458c6> <0c22306c488c1d8d4759f330f6a458c6> ] >> startxref 5938 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/shareFile.imageset/000077500000000000000000000000001516266226600303525ustar00rootroot00000000000000Contents.json000066400000000000000000000002321516266226600327600ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/shareFile.imageset{ "images" : [ { "idiom" : "universal", "filename" : "share.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/DetailsNodeInfo/shareFile.imageset/share.pdf000066400000000000000000000142131516266226600321500ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3 \ endstream endobj 5 0 obj 34 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 13 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 12 19 28] /Resources 9 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xmO0 +|!$iӭg`}R4$ԇcnVK HMbDQ rjf4”34CfE}$?/* wc(yfڜ鱶 +:=: endstream endobj 8 0 obj 156 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 2 24 21] /Resources 12 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream x=K0 D9Ŭ+S]TzqV?c)&l#SaMI&K4Ģ`ci #+r2 u ngCZ1W${+TfQeJfiN#:S x8)Bioݞ󆇒p|B}Zo\;ܙ yR Wp;rM^G9`a P endstream endobj 11 0 obj 212 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 13 0 obj << /Type /ExtGState /ca 0.3 >> endobj 16 0 obj << /Length 17 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 17 0 obj 2612 endobj 15 0 obj [ /ICCBased 16 0 R ] endobj 18 0 obj << /Length 19 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 19 0 obj 1047 endobj 14 0 obj [ /ICCBased 18 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 20 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 21 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 22 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 21 0 R /CreationDate 22 0 R /ModDate 22 0 R >> endobj xref 0 23 0000000000 65535 f 0000005591 00000 n 0000000148 00000 n 0000005353 00000 n 0000000022 00000 n 0000000130 00000 n 0000000250 00000 n 0000000356 00000 n 0000000723 00000 n 0000000742 00000 n 0000000811 00000 n 0000001235 00000 n 0000001255 00000 n 0000001325 00000 n 0000005316 00000 n 0000004108 00000 n 0000001372 00000 n 0000004087 00000 n 0000004145 00000 n 0000005295 00000 n 0000005434 00000 n 0000005498 00000 n 0000005549 00000 n trailer << /Size 23 /Root 20 0 R /Info 1 0 R /ID [ <0cf523468e3e6692dd20259cad775229> <0cf523468e3e6692dd20259cad775229> ] >> startxref 5666 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/000077500000000000000000000000001516266226600236105ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/3D.imageset/000077500000000000000000000000001516266226600256535ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/3D.imageset/3D.png000066400000000000000000000015741516266226600266360ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""载ξ޿DtRNS:*SIDATx^gAa:L9goN|EiF;. <GQEQ7ⶎf6ᶞ#vX~='Mbl=g>w  w_x.=v1qĝjp'#G]ۓ}щanጧŚZq:v[MrfԓʽA7ǫN.++w:eo%H'k.6A;Z;{f6D qX\\&qxheaB#0W̭ +S! qIP.0%AxaΑP䀙(#R9#8:UpuUePo;YW δ$%qpӸe{a:׏Ñs,(N捑uXÿRGn,Lݍ)ڳ_{G~0;ttBM 3΢W 0V7V=BMpQ8#.v9\> hCQEQ-X0)IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/3D.imageset/3D@2x.png000066400000000000000000000032111516266226600271760ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""뿿㾾½ IItRNS Sh cIDATx^1 NX8z @XhB|uQM_-p9%,4뮊S8ܓ2XǺ^Ua p.mԹu ($A&&[-J3% hhFF dѕak\(*pؾrDa{>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#æmpǰ}g'.Mv^Z+d?nEB)7!ntKQ]Kxnr0 @# Z8UHH JRoPFٴ X߽%?o \0{ #;'_#㗛 c 7>>4 䯩ȓ_$l?S J,yݲub}iEפ*v̦}҆2) ;H f%<<=!\sS% kۧUd1B8-Cc/%1\׉n-{N# 0ŁFۼx0#,ΑGqDvӑ=4լv럷[-V8p8p18U26g) ,,Ф,X(#O lc.GJ< jKV:8 P IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/3D.imageset/Contents.json000066400000000000000000000005131516266226600303420ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "3D.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "3D@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/aftereffects.imageset/000077500000000000000000000000001516266226600300465ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/aftereffects.imageset/Contents.json000066400000000000000000000005371516266226600325430ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "aftereffects.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "aftereffects@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }aftereffects.png000066400000000000000000000022001516266226600331300ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/aftereffects.imagesetPNG  IHDRnnPLTE""""""""""վտտ־־%tRNS:*e6IDATx^Փ0690333_hdӽ$rvSˮl6QfVq`CU\]#`5\1tW\= T¡v4&*P;)8(8(8(8(8(8(8(8(8H8H8H8hFШ6zq-r$%,goPH.pXAP3% =*X͹A! x͹$6ց\dc Sy!ܡ$3Su|!(߆!n{pqPf9Y2_P&8_zQTLN<\s W܍.,ۏ,`灱([0ns&5ב6S펂Mwǜ;wQ 3ށ nEhusY \3\٫lMrt5f6͇ap|8l>6͇ap|8l>6͇a3tw=*'s9qHqg:Xq}V\ \p\p\pκH(| #bD4qM HN, Y7/.sFϑ][}9<U]U=Kp.l=vD,Yr ݊˺$N~v .t .}t@ܜw.@. 4\^933- pc3tqh@'_kMQp1h{y^hf_|.vmzN*Q-0H佪 0,$/qp/CvlV gspG<Q wޚ.l:3.tQMAslNI[ClsB\ո -Dx964D-H7X_7wM\78rs!FͶ>o|("y.op1ާxo|MIx֠ס.pV?4״8,l%%pe:ԗ p=#bmF7e"AC2ܢ 8\[Y|[@d]\3Iq! oX:.Z&708?I 8jܷr;Vy$ޚeB4I2\L 8/IW}l;k 2܄q?lzygtE+,skqS!6w0nqf҇ ^uqC뵟m y]U\72a.XL* "n )Y͐kD4Uj} G]@:#Պ[ 1@ν_Jū6apKuKQSdeqgɿ+|`ϢۈQ~'8Kg~"G-y)8U0jv.4.EuYH! 7;Nn!K9a.zĸ q9av?vB3bG8'9[E۔~O[l}(!160Tv B?16ޯvryV^Kbgm4sP <<8@b<)z(&;pL0-VjIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/audio.imageset/000077500000000000000000000000001516266226600265065ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/audio.imageset/Contents.json000066400000000000000000000005211516266226600311740ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "audio.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "audio@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/audio.imageset/audio.png000066400000000000000000000012371516266226600303200ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x{ۀ׊yyռ{֛zތۇڅګ2tRNS:*G6V\IDATx^N0ad{{_$8ǖ"YC1MT%uA*0 RSW mngI$ m[p,8h4 ͂fAYpL8h&4 WfԽ!W%'#Gc;m,ߞ8ɭmQ6b \*I踫ugt(} Ȝ#G\cƝu#nw_p?ap"apW4]wSr/Fzk<>{EX쏴0aoUȑ#G9rދPiWɇAkc,`_2tIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/audio.imageset/audio@2x.png000066400000000000000000000023461516266226600306740ustar00rootroot00000000000000PNG  IHDR,?MPLTE""""""""""""""}׭~y넿ِݠ}֪|֪x{ڵ{֗ߥ޹ݻڃ؀ף끾ا R;tRNS Sh Y/IDATx^1 NX8z 0X?M`C;oxsG 7[ yٳԄj׿^gBBA܀qB/dtV78]@C $[ZC4J[gK{>[p|8l>6͇ap|8l>6͇ap|8l>6͇ap،6n=ǦͰpÉwN4 Rr,;+.E Wp+\ Wpq>NqgN~(kHLztaET\0&E%L͹={C{;݌rJչр+o5ꌢW:inF\ND*q^H).bMk p:\}j{=n0T{sqG wsꕙJ~tK&Ӄ F.888h;"wz8 ׵cE]%6Zp"I-(S ln/8ev( iQXq)B;bߐ6Z&_tSd/DT'[q]?l~8pd懓'N6?l~8pd懓'N6?l~8pd懓'N6?lWpdý${ĥyݤ-MqZ8?\:N}'KGW\zX 8p8p8p8pvgG*`q0[{j˴žv?}یq>;pMc׬pd%@V~8aGÁgYoJp{R^`S7Ҁ`0Ue"DlY8p8p8p8p8pM8pY+NVp=nq2b ,,Фu<)+N lc.ǵJ.}ӪuZmA\hzjо]ŕZBSqIf MeM2T\h".kمBqK:n hfgdUL6Ua3LVn98'E]U+j.6jWS_^ϐg7IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/compressed.imageset/compressed@2x.png000066400000000000000000000012761516266226600330030ustar00rootroot00000000000000PNG  IHDR,?QPLTE""""""""""""""TtRNS Sh oqIDATx^1 NX8z 0Xj@S%.PEG8т͝%Vs^ea P@ p-`A$5gehdɟ5ߟ!3}mpm=lW-W<\pvNW<\peÕ-W4hKJApeÕ-W<\p-)eÕ-W<\peKÍ-W<\peK=-x;rRpW8}L\߽n&RCR;답D888888888)8888)8888ppppp#en pppppppppppppppy888888888888887mVM'sP <<8@bPF;)z(%;pL0-VۭN`ėIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/database.imageset/000077500000000000000000000000001516266226600271515ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/database.imageset/Contents.json000066400000000000000000000005271516266226600316450ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "database.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "database@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/database.imageset/database.png000066400000000000000000000015611516266226600314260ustar00rootroot00000000000000PNG  IHDRnn8PLTE"""""""""".50BU֣ǂȂ1LաzP6ma٩Gw2t9<;ΑV͎?glˊܱ`Βۯ[:bZܰ߷ҙkˋC̍gۮ@љ߸4#tRNS:*aYIDATx^ŎAt6Mef033 JC2H{Xb42:2\. dnp]~r. g(8i 4g3MLSp)8$i4 g2MkqnBnM(92{BnXrۯfT-aqKuȸ5B6*H&Df$WDY$o@Õq4uOí1w!j+IT;Wp _S@õTq-l}D7!⾐\Bĕd+T73q;d2n :gVV~w!~+AYz.r"PR38X,8OIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/database.imageset/database@2x.png000066400000000000000000000026371516266226600320050ustar00rootroot00000000000000PNG  IHDR,?MPLTE""""""""""""""2աm50aP96GǂwȂ֣1<٩tzgU.lۯ;ܰVLZˊbΑљ͎̍߷ܱ`BҚ?Β[ˋg@C:kۮ߸ҙј_#tRNS Sh uUIDATx^1 NX8z 0Xj@S%jPEC8Y=-V_쵧 ⸊T.-rm\ } X >*^[~vp2y! s2Ivjk1Wlͭڠ[8؞hfw8n(6>l|8p`Ƈ6>l|8p`Ƈ6>l|8p`Ƈ6FcpF =UNݬ[VR]\μNw+vAjSiqQW^VVẖIcťE2JH<)Yuk lJ}yJۢ-*#.fYJܒbč*!E[V=< ˾^~Pgz]CR9q)<*ј[i>}\חo7!x\Dcu dpyo1ۀ`{3G?>qsglnp\ VQ$~1[Ydr1)>s;G]M?-Em2QSӜ49iNs@~a1B߫Y_/԰IENDB`dreamweaver@2x.png000066400000000000000000000044651516266226600332230ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/dreamweaver.imagesetPNG  IHDR,?yPLTE""""""""""""""ߝى}ݗޭ؃ּׁߚ؅ۏ⣨z絧{؆ڎʩڌܑې泬׃ݔܕܔżޚց䬸ܕրيѿܒ}河ێր{䭿麱ىߛցڋ齺ޘڌ뿻ޘҸݔúݗׅ㨰؇ѧzȮ׆ľߜӨ}߻ޙ趭׃࠳ڍ꿺ޖ؈ݕᡯ؇üޛctRNS Sh  F'RIDATx^1 NX8z 0X?M<`>B7xsO ʃ;[`[ zٳ ¾gVsY|!Qᒣa(ʐ[u~a;͇ap|8l>6͇ap|8l>6͇ap|8l>6͈{`^36n¦íi1sV[;rܬO+.ş׸5qrk_MQnАA0*C DZ*zGݶZmպݛ{'{|${~y{;7yAtlM?&>~q9ٱU@΋#*v^Qu`spq<898pp~6x'nx>x45d7%i[?drgx88$ CqTT7" G[`'r.Ejnɵ)χLR}Jqdց12Yq$!K`LY9C慵)Dü8(@9 D{\Hd\26- ,GQjh8Γy ɽݰQ2" H-w!PlFCv[MHlUŘ۠x .#4΅Ģr#t+SD ؏ ?/`'쿚RDQ2R谷ja:¥ƭGrĨZqd13f"-WIԸe1ߘ6g7%/1,_Ķ jqJl Z1/_ J%\5jő8eFV|7Ἠ&6Y^6-_ۑp]xz1qlzqT,BƧFg͖/O1=ӓ`8|hqnZ. w"2>!FZ0L:ƕ#hˮY$g,xpqmH|Ef2Vs)q"y-ݣ W~BF+sBʍ쥯 `zY;GƔ RDsn/'M|8/rN'PI zu ;G!l~?.Tlw9!X{Qd>>qn!hx= t)0.WekF];!A%i;w;L=pUCQ -~W:@ΏP;t(I,зt{<]5p 龖%pϰY4B67rB berDž!9yl<)93A8 H"h,q+Ȏ|c^>QEݙˀCwcD+8iSV )c-W 0niT#@ ϕ}_3_qDh?+9EjHF};6 Bf;o| }lQ mt9AXXM IYIPF^N\^=xZ8&@dԖ +U܁VIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/excel.imageset/000077500000000000000000000000001516266226600265055ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/excel.imageset/Contents.json000066400000000000000000000005211516266226600311730ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "excel.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "excel@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/excel.imageset/excel.png000066400000000000000000000016771516266226600303260ustar00rootroot00000000000000PNG  IHDRnnwPLTE""""""""""RY&V!TéюW"b{Qܪj<_-b1S伹ڣwL`ωc2qEaۥk=΅S՗x߶؟ḯ^g8b0a0sGޛ{ˀoBuI]*֔r[(uJ́n㺿ݫ`.l?sH֚~\)佁Z`/灼Y}TԔ}ҏqDf6W#e5c١CtRNS:*fFIDATx^oA Nc 3334.=J/adVcG(M4xMM;' ą&X \褉57"ˑv4i@#M#M#M#M#M#M#M#M#M#M#M#MK&5X qq \i䌁SN9F-cp9a;~AL^pݤŲMvj#6f'8z8d!xn4K./j+]fznvbE;!.Wف ]J\wlM6?w.7w O--ݣ)<8ohzexYp_$s[/^>3q!>:Vq0pm hr)r)c"ߋJ44MӴ]@7ܯIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/excel.imageset/excel@2x.png000066400000000000000000000033471516266226600306740ustar00rootroot00000000000000PNG  IHDR,?(PLTE""""""""""""""_-㺈bU SW"{QY%c2Yf6TюY&V!pC՗j<ᶙy似ۧ`.b1b0ܪωRqD١vY솿aϽܨ|ci{Ӓe5U ҮԔ֚ni:^+͹ڣ΅a0g8]*uI[oB؟qEyPk=^n@לxsX$l>⇿asG՘ۥwL؞֞ˀSmzQu߯Њd3ol?l΅_[(}UuJẃ\)`ݫm@sH~Z`/}T}ҏW#k#tRNS Sh '[UIDATx^1 NX8v C1(i?*峀eohi݇8ɹs=%X`sks^-ElX `xˣ/c[ky{۫ߓy,9g&{=qEؗnlq).{US-m4'qa'qa'qa'qa'qa'qa'qa'qa'qa'qa'!7qA7q-6nyn&q-m4O}yf؟7\8v8{9gsf'8 Np'8 Np'8T0g 8˜l'V΂}Ξ .Q0)-(Lf0na)~q~eUC^iƍڻnQ~:~uk;KBR4J:@ķi}e\?HU5kL&ÏG-OSYjM"=~g/Q34ݯNr.l9e2%Q) i>8ʴ۴-њ.k8ZӔޥp G[y8N94wI9&.ڧs>"W Gq7un[ ڸƥWC\CʸW b¸]=S,a^F젊{pTWĽ~V`@ΰ)V'E\2RFe#=\wr=~F 1Ng`9DB g}5&J27:8+MF ]TpyF :ohp`߼Nʰ[q 8 Np'8 Np'8 Np'8 Np'8 \0\Ąѹ"3?50 K3sp4)FO e+qO^;a8\IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/executable.imageset/000077500000000000000000000000001516266226600275265ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/executable.imageset/Contents.json000066400000000000000000000005331516266226600322170ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "executable.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "executable@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/executable.imageset/executable.png000066400000000000000000000014021516266226600323520ustar00rootroot00000000000000PNG  IHDRnnPLTE""""ĢĢĢĢĢĢĢĢĢĢĢĢĢħ֟񢢢əЛ𫫫ѝ󩩩٦µn|jwBtRNS&DE0MIDATx^9 @Da_.U=sI/AUT/:} 1\FAWnT+Yލ.Y)B Dʎ-5a60j[U R";P13uf$JDϵ\6ƑMm\Qc1nG?9s!nH%lA[{ӯ^cށʌQع"#dJ jL'3U94~^JHvv}2 dO [qoh'0wY6z= q䎨J"f8l~$6w6½}'wr{t%Z=?sE߽yX>]">=1sziLc ؑDHҞڛk9Ou96|#{w>ځcAOm]IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/executable.imageset/executable@2x.png000066400000000000000000000017471516266226600327400ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""ڢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢkxyĚlzѫjw𢢢H_&tRNS h+Uܥq] m-^IDATx^àS`gQ U}O+&Eݔ'\^̧G5\O v{pX' w\74rh8U އϪ.u1Å+V8?|nΞq>N0 g8}ٷa ','y*F=x|Jaw|2&ۖ#8#8#N 8BqޞYe>kUtDV6qh]8CRǡ uq@6m+^vlK6rm̅?lXbʤ=/M.O(G/GqGqGq Hl?}ndt00?pA6yT[8-#ް.r!;pL0m 8YU矝䐍ᆃ50NJ眙ea睚㍊챯FB曙?:餢|ur䑎䒐饢fb=9啒ꪨC>ᅂ94;6㎋<tRNS:*kðIDATx^N@ af/p!+%Vo|+JYGYeYQn.˫Jݕ+/U\ j(s8V WMkZ㺮hr΅38-wgӄ\t ;M¡X%@0zp#=x y8$xz2ר'xzרw2;4bvVFh'wC MD!{9 9{ѽstxMQj#+6 xMΡw}ɹZ>~Coh5? 383θb ݉ђWHOXeY7!+IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/fla_lang.imageset/fla_lang@2x.png000066400000000000000000000026351516266226600320010ustar00rootroot00000000000000PNG  IHDR,?nPLTE""""""""""""""3.ꫩ{x:5<7UQgc矝ᆃC>lh䏌䒐ᅂ>950E@qmMIyu≇旔JF啒FBzw72uq⇄䐍ZV�~{yvKG=995Ⴠ晖饢챯tpシID?:≆kgEA=8㍋~if믭61[Wplwt@;}衞5083_[첰*BrtRNS Sh IDATx^1 NX8st @ThJԴ&N׽.ę%fUѹ97SP*ulno_p˞ P͠k|6Z]TBUw ) X_$|")CQ.NJRVɀm%}lFefCpl88lſLL\?UPDb,S*.}383838383θksׇ_i8_o?kTaq% $k)7hL.(ذ^q!+9.[k^ۯ[n75 Gّ]3.6;Zc] mfMQtApMy֠~ΠVJɋMmgowf >[.>s 6A珣M?D:'nMBW- :lۨ;U NQ'T?D M9s)6Upw*p]Mйh;s].6rW+sp pq :a866u'l uis :il8A'q\P:u븹as :76uNݢ=[uwhK[umi7[\wFAm=*ck:1>buO˸!Qla nz %lpݫC>oEuKJ>|K\%.qK\%.qK\%.qK\%.qK׫n7^79AXXBC IYIPF^N\^=xZ8&@dԖ +*IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash.imageset/000077500000000000000000000000001516266226600265025ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash.imageset/Contents.json000066400000000000000000000005211516266226600311700ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "flash.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "flash@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash.imageset/flash.png000066400000000000000000000006271516266226600303120ustar00rootroot00000000000000PNG  IHDRnnZPLTE""""""""""~op_utRNS:*啇IDATx^ڹ1a=_QYQd ijH^-(RvrC\Y᪍5K8hۃG2+Acp4Acp4Ap(4 uhy\k&&g 'N39q8q!'N܂N8q'=;s ި72'N8qek@xё,P|R\D8J) gIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash.imageset/flash@2x.png000066400000000000000000000013421516266226600306570ustar00rootroot00000000000000PNG  IHDR,?PLTE"""""""""""""""~op|üqsĎ tRNS  ShΏiiIDATxj@Fc!6ma%FNS]P9׆cZ1̮VCinGV]V힋5j\p.VĂ:twnǵmLۿJ,:>1V#z۫:^`lmT Q6*Fۨ`lmT Q6*Fۨ`lmT Q6*Fۨ`nF-ݿp͖k|y!/D#'׀k0^%ĉ'N8qwϵw;:9qĉ'N8itrĉ'N8qn2qF''N8qĉĉ'N8qĉ'n֦ c!q`=1ǷcKc; n{n)NM'N8qĉ'N8qĉ'N8qĉ'N8qKZ_\یm1nX;NXw.p9QcQĉ'N8qĉ'N8qĉ'N>mj/=IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash_video.imageset/000077500000000000000000000000001516266226600276705ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash_video.imageset/Contents.json000066400000000000000000000005351516266226600323630ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "flash_video.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "flash_video@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash_video.imageset/flash_video.png000066400000000000000000000011031516266226600326540ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""3.[WMI]YჀokOKC>~啒;6:5ꩧ50묪mi50旔B>シdtRNS:*[q-'Q|m3zèt6׏wk.v NI#8i'4FpNIC8i'nӣqeĜ"q̭YnBrGr)-Q.==w$'ʥKfriB9M4 $N@8M4 $ N 8er' wuLVkas6sEP N8՟TS NG8TSNG 0gΜ9s̵/_t4QqUM[Լqq|(kjIENDB`flash_video@2x.png000066400000000000000000000016751516266226600331650ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/flash_video.imagesetPNG  IHDR,?PLTE""""""""""""""50ჀokC>jg}zOKLG]Y;6咐YU묪ꧥA=旔3.94ﻹ皘50}jtRNS Sh }~IDATx^1 NX8z @XhJZqQM_-p9%VuWE% rIAssG cs{>_3 0]Z R0_sFwYdt0Mn]8lAhztvP-͇ap|8l>6͇ap|8l>6͇ap|8l>6M[Ǧía6m8qWպ@d|zZqV\]lj . . nl}={/-\qފ8Z !(qDA# BQ0∂G8`%(qDA# FQP∂G8#((qD# JQ0∂G8`1qZe?)8֦rt\QG|8p#>Gt8p#:G|8p#BG8 čZ3 . . . . . . .'fvlĶo ?3 ,,Ф,X(#O lc.GJ< jK-D+XIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder.imageset/000077500000000000000000000000001516266226600266605ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder.imageset/Contents.json000066400000000000000000000005231516266226600313500ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "folder.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "folder@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder.imageset/folder.png000066400000000000000000000003751516266226600306460ustar00rootroot00000000000000PNG  IHDRnn4u;!PLTE$$$$$$$$$q tRNS naIDATx^!P^N% *z4)=%d~H/u7ܵG20^nZ~]09*ݾֹ T;p8Qɸr<< TzIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder.imageset/folder@2x.png000066400000000000000000000007321516266226600312150ustar00rootroot00000000000000PNG  IHDR,?6PLTE$$$$$$$$$$$$$$$$LtRNSx $ y"aBBIDATx^ K1@ 63ɗ qkG[ :V u:R\Eg@t8I\W~ ⼭ 㰝8ʸ&MJ%z4nܡ0 qX]@~#}el{ Wyupí7Lw* O} > 1IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder_shared.imageset/000077500000000000000000000000001516266226600302065ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder_shared.imageset/Contents.json000066400000000000000000000005411516266226600326760ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "folder_shared.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "folder_shared@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }folder_shared.png000066400000000000000000000021511516266226600334350ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder_shared.imagesetPNG  IHDRnnPLTE$$$$$$$$2'+,*C<.$&9Q݂x`)b}߈;@(\M{JKY5G0iU?-[T]akZ3އSDޅ7yddR4hzmu݄fe.T tRNS R"IDATx^ K_?ma q O24{.샲 {^Nl<"w_G'$z;j17s\;n#GyqW\=$(Puq}S^wyZ|Fyh^GoDb?kגHKӫ#q?5Q若fe`Wf0߷%Gvw\u[Ε{'2꩸{?sHLZ^wJ{+2bA(t"eK2[b>I_&` %r7f`ommG_sFG{X0rЙon?Xaq)áy4.h ;IENDB`folder_shared@2x.png000066400000000000000000000036731516266226600340210ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/folder_shared.imagesetPNG  IHDR,?PLTE$$$$$$$$$$$$$$$2݄ߊ[%3?aކqEH.*T+}vM)ߌD9ߋg:j܀ޅ5?-@^<݂FS8{;$NuZs1O'L4i߉X܁݃,]~hdbYW(0QC6zURIPyf_&p/_tRNSx "y$7:IDATx^ KN1i5t7{i<Iwm-9 n$<t6.,]<Xx\h@\suyơl8ʸ,܏q}(Jع(!`\.j - H9iW%Jђ}l.rֶAsoч%{dHNnNNNNN+[VVZN_Qsw忹p~NGfFsה[VY \NW.vGt΁.9×*!ܷ(` uвB_X5s[bLnB^7-g=Wso渃qz3z@B{(1lfHnD=BH{ w(˼?/V|/KNɕn`; ?GQ$Hm'2tz0'Ѻ~:8Ź_bUER8ؙxxqkńV;Pw6`B#tt1 nO1U6"+cqﯿ5Tr8f}L Nq&@Ѷ`q.7DW Oa]\wBОEܻqe C,nAfSVNq2hGntxj/p @,\wLj=4flI;NS.uJpe~~el/F4d:K[ˈ5],GtC># uz9C?ϟ0zvLq0νYf.nO4 [JJ-e[K8ZҼ }&}%Մ' 0: i=1\>esIiq6TbiZ~-p%8̇5>ҎCn(n$DǹAawz=ܦR Vq PNw'2H% *S%*ߔuX9 x*n0n*n0Y=8~a㴅z.M~N8888˧gO0e"z SDLW)߲0̿/8`tV`[IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/font.imageset/000077500000000000000000000000001516266226600263535ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/font.imageset/Contents.json000066400000000000000000000005171516266226600310460ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "font.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "font@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/font.imageset/font.png000066400000000000000000000021351516266226600300300ustar00rootroot00000000000000PNG  IHDRnnePLTE""""""""""Ùʲ䛛ؘѰݶު»ҿ𥥥ﳳ常룣ߵཽ㦦ꢢּ|<tRNS:*UJuIDATx^U@Nm73233c7qi٣VDأKtuww:Dsٚnp(G۲h#HHHHHHHHHHHHcHcEuNuNNpӜvY9'C`|g ٸ"\1q(aV@pٻYc8m{?S:[9p<W=ĥ\%E -.2n 妏2`8qX9axoN0+TA}̾V~EH0d{ +܈nZ qIt[+s15h'F-81flCB.鈆i:AP=R`; H vP–^}uJYGU xNF᧡j#|CR%wt Ul7q \J F(30S]TR.*n ~HyD f-s ച󃊎N\cN|16 GLzo9N_jvf&ݤE[rCuym(ҙ+}4_t~zjNsӜ49E{=puot؄luҷqt:N Q IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/font.imageset/font@2x.png000066400000000000000000000036251516266226600304070ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""䢢ӞĿ󱱱߲羾Ч񫫫͵ǼֹԦ츸ũ载қतG=tRNS Sh 5yIDATx^1 NX8st @ThB4&N׽.@yǂ{RP,ulno_p\˞6PeiU0xk@cnۼԠLU=j\ʇap|8l>6͇ap|8l>6͇ap|8l>6͇fĝp6nMm_9qˉjwap>\wfu . . .κH D6BT7\6q_m,fH&a>CO/9҇ 0V[*0V'Pu'aU !nIf-|EX ME ]$l)t!õqpW=T5al+LWF֐঑AQ@-nlTKpS?Nm]6y=]@7dh0<6Xk%/M\4t#U;k݂*)-AD-~i#l. ꁓ}Δ-ӫV у7u/g 0dUd{%U8 NȍE 7w:M5[qeû=DeYWk g#ZAZ{RviۦRnJ1X4礃串w Ңȿċ\Kً;Jb6RΕWbW )e1΍Y#8Awty5O; N k1`k%ooEߑJazoPAG^\K[lMS0lX%^wy1UwEcbXY'KJ-U q-ijYc{1ɭS?!ng^l!J+'=ocO2 N׆Aa6p'ÕyJK6Rj^QUO[/OKW*ZD.+ZW? k(hOg8ZH~O˾CTo2dǰZDybv^xh^$.Ek)q]ԛ9AXXM IYIPF^N\^=xZ8&@dԖ +ۖ,IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/generic.imageset/000077500000000000000000000000001516266226600270215ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/generic.imageset/Contents.json000066400000000000000000000005251516266226600315130ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "generic.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "generic@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/generic.imageset/generic.png000066400000000000000000000005261516266226600311460ustar00rootroot00000000000000PNG  IHDRnn?PLTE""""""""""NNtRNS:*>^IDATx^ 1Q>3`Ϻ[_8KTKnݿږ#eNqJqWnI_-SD',4 'Ip,4 'Ip,4 'IpL.mq[bmFE_sppppppppppppppppppppppppp5>:J$*s@!""NC0 ;"IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/generic.imageset/generic@2x.png000066400000000000000000000011461516266226600315170ustar00rootroot00000000000000PNG  IHDR,?NPLTE""""""""""""""".pktRNS  ShΏiiIDATx1n@E7_]ѕqb)s8A'_tRNS:*司=IDATx^E@T&I $fd033333GRmo5I[YwsdVr\#wtK$[Z:Us%Z֨nj6hcmj&X[GRkx5<ǚc ϱ&űXsIq9D8dNvճK\䔢s\<0@ySmVUnN$*jp\JǴ =.鸖0|0mb#o^;o O-lp}7ni>7?mp W3p W E3pc^q͸pwG 7;2c;bi75nmL'O8:m3^Vtܘ6U/.v%}5_qSn+]1ȋ>j9G;\p^{r{NH^Zmѫ 2oc.Ex.aJ%>G-W&?7Iq4$q?2ML$G9qs- Nt$U$yd" ir~rt^@IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/gis.imageset/gis@2x.png000066400000000000000000000037031516266226600300340ustar00rootroot00000000000000PNG  IHDR,?RPLTE""""""""""""""QP=;tr=;߀KIGE뮭쳲a_CAUSᄑ⊉~~}jh?=RPVT><⋊ki;9喕矞ᆄ첱A?㏎pousZX@>SQge騧DBFD<:䕔TS曚a`챱杜[ZECJIhfWVHF뱰WUᆅ睜LJcakjrpꫪCB촳믮TRYWonnl嗖袡{ywv⌋mk暙yxfd}|砟馥ih￿[Y瞝nm밯JHdb`^ሇNL饤eczyB@ba驨\Zxv駦edPN|zIGsr}tRNS Sh  IIDATx^1 NX8v C1(iHL헾y:wox'ʃ`ͭ |ٳw¾ib *A8`{H ct9p?[uH2]RD,mL8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8T8\8\8\8t#l&n ̈́;횜wWɉ[DTX'g+.ފbWpWpWp׵}ՏG}7}ypׇ]jj#0lU{@B&xA `; 2j 8'9ry✳7/C\fF NWSݥTb\%)p+*ܧ|gm$63/fX^K 0+"x)3f&6Z nGژ5Y8]%ܦ0*֦8EV½ivol.:NޖnOl| 5F;Evs6Ϯ.mvYfny;/h[*CX#{Њ=l$F6fUjJWpi*JIߵl-D,t J$pn:AitTgh Õ܉]2(OUQpU$pm(m%s|2]:u8P"^p e!]ywe"8h@úYu(B@W.iƑCXW0N⦷ 0"3# d ^,p .:ޖLǽYp8ֶyۢ ރLl[ Mi#ȅWmp~>IïZA>۾)$8X +n?8Hyq'>S8S8S8S8S8S8S8S8S8S8K%[-LR.9sP <<8@b<)z(&;pL0-V-Q=IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/graphic.imageset/000077500000000000000000000000001516266226600270225ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/graphic.imageset/Contents.json000066400000000000000000000005251516266226600315140ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "graphic.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "graphic@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/graphic.imageset/graphic.png000066400000000000000000000013321516266226600311440ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""~ݺx՛pgbWٲѢǏsi˘޽È͚ʔǏƌÇ݋رׯՈԩĿݻatRNS:*3hIDATx^N0al{9/}eFZ4 <ؒ5 si9nWkreڒwz^)A[^dLڊ'AIp$8h4 M&A᠉pD8h2\2UOk'uMO3ƛN9$8HR. HW㈝KC)7K \bn&uk'3;SE̔j9K4mnsЈ9hD~ # 2r# qr$9K%FnqظW"xx)l|8p`Ƈ6>l|8p`Ƈ6>l|8p1`^FlϞλw"rrÊ #+.\̧O8N8N8a:n W5}7.GeMkʵvk{ڶ8g{s fUTVW6[_d,Ґ9nHc"M:ehԂ|+&/}8G?4 hOCYu9ֻWZzyy'%}{I5 (!#(W#4h y?Uo$6Nkݩ7g{m6`h6uq?'۽w Έꄧ=]FS},(X?+ηc"(Fd@1͐ކHSsYEp Lkq=KonҋnmwE^~%-0&%Dp{TQ58J Vϳțdؖ;m]AהoɴcsB 8W/Vی"?ZA < N 9CU =XhٞQ0?IO4--p:8!۲18\VH9 Iahm\b~Hl{!D+ ʫݬtLK&;\: # o0֗˳#͸ס 3mO :EIreEZy {tS-<082J͢{6Hq1řQGѨlY6PR%>8BMg2ރ\@p,YC'?1dʗܲ2D2oɞ7bP¢c3 5Bo(T/d BΈ(`5{QCnZFx fN0lj4jم+ȁ2)-u!h5OW'Vs'TYdTZdyOWE ƦAs xݽ16ϝ [qSpհ589(. @\q )ǹeTsgv[ݡBtʠܠn&*=_dp*/bԢڅF0eDTOAV7c^QpkRqU"o3!4kh~fJSCY 3 <Ǩ&VU̖N9`~ mסizz ht !lǴBqMUU"6$F&DP"Qe(?F? &D;h*!VN)xFC3'csݨ`4 dIK+$[jy 6l@_wܞecI%w]퐊 MGpv3pKVL$|MZ^ PLg)pb1D+yj٘0 d րc&T:Q@B> k c}><Br 'F0&s$Bg*YA[ԅT+S@ S/NaÔMQI?j~qW(J#@$| ,LC 4TCP' Q' R19.5܃.p#z̆2D&gy4^* CZYI#sǏ=dp 18X 18cp `HX5irǖ.Gp=$u~6ٔLQ:?MpgC6{( Bs5G\ZbO Y glZz_,~JVs|*rkVMleh#YѕR׬VKYy׬k2 Nl&K .Lpf $6\ Hl&@b3 .Lpf $6\ Hl&@b3 .Lpf $6\ Hl&@b3E*O =lcb׮c~OOyNfk%ay>[2ca&{Kgj'3+&\0fL8XDz*+#RATG@Q*V(JK 6tod,I&dr{n_^ߛ̈́}Ϲ'MLs?}+8'Z^'8' p"Nke'cWSC֗=~+xYLKeUM5'u΄^'[=hcMOҪsxB9% p- LQ-ի>+Wu>ӣ>lNE{յG>TL]dʀS6OeU, p-\V/e'B БڝW)U&VsHou/|o+7ؘ'9;gh%chFO+ KIuӶ긬#7p4hl5=՝+ToOs\}gKVt_Hi8n*6RR{oƤ<+ٜv#"* pC,s޼m`F$*ooL}ush+ p\iYGJ- ROΩ+u2p=#/ p =B$oP?Cs^`wV{ }ɚa#+׮9;" pS,W"]†~5:.VN#}Rck{~d>x % p9X0Y-kڊG+zGJ<9 [jf09lӻsGB"&a/_8qFrBh4j.?UadX) ƇcӼ@- pvz7ZR+WmB%fYҹHU,i>@8gRN;K[> ĩ¤ I;}-Be:BT7p9wJp\+هؕm _Rn3 \LZ?'su(.ljPt6pkծ76-̀&w2;c2c\K{fjBϻwL{tNBͳe$P ;'^3MaΣ^b΄i&A$\:=`. Kf5 4iKW^]7h}P]ْ@rޤ (r_vRDІ]7}|-. ptq\I@3ggtj+xPeTO>b(/u8.`ư)d¿Xq͠Lh jneNÐl$+f!ru4 vrQ:j3a=E]Wn5P2<(1'9`x=r$CĮmbN@kl8 ֌+$Qh(WLPo lv`E2)$ý $qг.MW2S2":7;e#!0pmz7gAjwrnmYulfbx^I*!6:!p?3uKH5p'p8ncrO^ۂfMd\!MV6>֒N\4g,[M4H"'J0:u[(RX3h'iagvmT`s15]LZy1I=5l)%%2G&x%J0Moa\}Kר=3.z7x\%]lK oq[St35TucKXwHh}Ws"iҒki02y\RÞNrIͷA.ejx}[[(hD%l%l<8NQ"HՁ(j{u`~wwk-ݝDZZ8grkjD|->Eku ڹn4q3=XT8ۚd']00tpv> 6TtFB8tJ n4M"!BM+"aжgҵKCzfܓG t3WPanڳ[g(s.;w}9[P+d^u<LIJ{ړoڥ;֜zk …ԇ5*wZg3˕8*D4Ҥ*x5MM53F-v"pɳ™jSnB |c":y.H pź}{kxJ61DpEO;pO(\D+Izg]:8Lrm-B?2i)`)I)ha XxA&ue6kt%{v>g3sfw;_e.Mkߐ 1y>8fQ۞Nz |nDybhu7" B7_n:1l2 ); ˏ5p~^Z&u*`Iذbg\O'p[uWx@$ ͎Gbӄ-8IÓ^ i9e07R׷Vrxvb L݈X.D_ xN83'K^$Pjǝ vhDrϊV 'po9XN2ɆG̎Xi4qOn +"5U8&=:byX#yV6@8@8@8@86SamIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/illustrator.imageset/000077500000000000000000000000001516266226600277715ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/illustrator.imageset/Contents.json000066400000000000000000000005351516266226600324640ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "illustrator.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "illustrator@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/illustrator.imageset/illustrator.png000066400000000000000000000016531516266226600330700ustar00rootroot00000000000000PNG  IHDRnnGPLTE""""""""""ciŝƟgd˜oudɥӵzĜkؾέήievֺ}ͬбȢʦt̩ЯmȤ~ѳjsnԸTtRNS:*iIDATx^o0ik;s(yg{?7!}DJ,˲'pzF\wezN>q.1iMuA;584848484848484848T8T8T8thZܐzܠ܀;08BBT1juEvg|䛩s+1ws+.{ϴ]&Mʿ̫n%TnDNmo[C^4w]\9B)s3`~Ci ^ryܚ,pcY ޷&ٴ(\#^. <W(,E wmq$)mQrKJV|%x;)= MJq }jqY).<}& čB_e):}#4/í~ph?yNWsgqg\&HJ.MƱ,˲;͋DIENDB`illustrator@2x.png000066400000000000000000000032011516266226600333520ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/illustrator.imagesetPNG  IHDR,?PLTE""""""""""""""vcϯ~ykٿigtʨ~Ȣŝzֺӵn}eֹҳşаewĝԸqѳ̩uʥtdiopѲšҴպƠչsЯȣʦfƟjԷǡɥxd׼tؾ4tRNS Sh Ύ0OIDATx^1 NX8v C1(iU$&g~(gs-wrByps{ ھ/{v@AHx]ƾERzBң6/5(So8l7mWB6!M&a q؄8lB6!M&a q؄8lB6!M&a q؄8lB6#͇{p'l>ܵmr=wj`pB\wnu\p\p\p?n+( p3P>PbƊ (6lGfj/{fq[`%߼Yg/+,%[a5{ eLwvWMj(t  v+"ҡtqͰȩL&n".&XR˫ .[iu+.J'r]@磐NS&S!G|Q< }f0~u ).5fR??hFB;zD4\AJr5@Uݸ:ċsA\(NC#rb)F Ւ!M'ƵOݍpZEr51M۰ykۄgy̸{NqqqlQHĸ0 ʋۂ,;M^d t,vA@ A'H+.ϊ]Ap6VR;)΃K P&¥Jܚ`-­#ܪgݭ<9\{k/pېv:B5N{t_A ՛r\lZ]ɿA2TmTn.e.\Tl\3Tk\8T!c!*\?t L-߱_ W&TpT]LfgA ApkRBWըM7Ϻ7ǾSvG,pnU,آlnW-"/sP <<8@b<)z(&;pL0-V dSIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/image.imageset/000077500000000000000000000000001516266226600264675ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/image.imageset/Contents.json000066400000000000000000000005211516266226600311550ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "image.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "image@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/image.imageset/image.png000066400000000000000000000015051516266226600302600ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""Ͻ׼سݫࡡ춶ӿͦԩ$JtRNS:*)IDATx^W0dw!l{{{/ts>h{ {(I$inŢJ]gr+jY<T#*fI4i‘‘‘í-G2]NWm殌]vU'$<ڦ 2G.N\+;wY3:=bkcyZFomh=jk׈u k=3gil}+_?<6q.r=Ъ/;7S[s`CSnh& Pŝ==;/nZg{ =q0_cdY(-`5wș{Ox'bKwf[MdSJd_ 'p '\ rT4oI$InwIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/image.imageset/image@2x.png000066400000000000000000000033441516266226600306350ustar00rootroot00000000000000PNG  IHDR,?qPLTE""""""""""""""멩۫򼼼⟟֝Ꞟ˸șཽΨ䛛ɧ餤Ǧͱ󰰰ó壣ԷtRNS Sh Ρos IDATx^1 NX8v C1(I[|,`~gs-9wv0$SDPC;[Ok@KnۼԠLU}j\߶+͇ap|8l>6͇ap|8l>6͇ap|8l>6wŦ=t6ٶo9qˉ;vE38ۊ;pX)p~\p\p\p\pĭ ɟA ? Ǝ.y].][eKP,HHsΝSt`4G/\7gkS~>0+2z} |I/,w0[A[Wbd;RVq!D%t0bg؆j;`I͓1Ϋ4s$}!1BB\7wS|"e1:ơݠRzPN]~:3)pՃ8)DBLh49G=li;$=·X1@wѤ!su@@ w[Bns(2umaq3fe%8sq5fSg)qvkecM( *ӥl-\6LT\ ~:.DZnKpKǕ0\>`a /x&xp&eQg'ByRxe;s9zPiMvaS[b'IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/indesign.imageset/000077500000000000000000000000001516266226600272055ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/indesign.imageset/Contents.json000066400000000000000000000005271516266226600317010ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "indesign.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "indesign@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/indesign.imageset/indesign.png000066400000000000000000000012331516266226600315120ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x~y{{yEtRNS:*䛨<UIDATx^ڷN1aؽ17gr9 qj-1ijracZHVVn%ͩlBͩdPPP`\S5Wڊqu) W #Gj#g  ;֦=?H %/!ǭi jlc¡- sU-:'(HgF:g(D);9Eq:/PܥΫ!vęuw?~xpff}8^3IgN'?09rȑ#K1${RBqc/\ܰerIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/indesign.imageset/indesign@2x.png000066400000000000000000000023621516266226600320700ustar00rootroot00000000000000PNG  IHDR,?tPLTE""""""""""""""y{~{yzx|tRNS Sh Ύ`IDATx^1 NX8z @XhhZ_jo;%VuWEK8Y2Xg9P QyN@dFK [e۷ƥv(͇ap|8l>6͇ap|8l>6͇ap|8l>6¦=tl:ܽmr=wjwcp>\wjuu\p\p\p?7u p"HllSB w{zsXQ|=ұdif#8#N8#8#p7)aoXt>*AHL8;2Qq5*.׸rK K> FU:&%ۆBe4nF*n!z^a&dENձpc< < +<  %s g2nQ Z"s2,WpYpe7p[2pc6NdoveÕ^6JX8$%pإ;9•.]4_>_\bԕ>S!dɹTmTãѢ8{z|'}OWgX?c_m>?GqGqGqGqGqGqGqGq>|_ppW`a 4/x&xp&eQg'ByRxe;s9zPiMvaS[b'߶N{IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/java.imageset/000077500000000000000000000000001516266226600263265ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/java.imageset/Contents.json000066400000000000000000000005171516266226600310210ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "java.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "java@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/java.imageset/java.png000066400000000000000000000027261516266226600277640ustar00rootroot00000000000000PNG  IHDRnnIPLTE""""""""""l Πv/m"[˰򢢢񷷷~iϟu.{7wѹy3o$ʹFܚny4`o%WcʯƨA|8Jŧ>q'~;NиQr*=ĥMPx1?\qhsz5ӼѺ£u-ZĦɭY嶶տbε{q(󴴴m!K/tRNS:*7(IDATx^es:in`Jfff23333333eeēvS{w-i&OgfwIZ(F1bdO"cc2ŅDH 2JNd (HR~AB8E)Nфp&S4!5kjQnK̚s:NM M9յL|XY' ǫɿZsz9s;O rL^.BUzyy@R!4d-( jVd%-)wG'#i3 IV`.-&-Y. H&"c&@k2vqFnsp>-r_VE4wDRzr+ʝ&k;CK9C/?ZR"%":f뉖s{<]߷3?W -l؀p(g5Z5BIy$CMp))`Nuȡ]ErR$Pj@g`z``aWe&ؕƆeˀ{<Ɨp٪ȨטpUMo1\a?p{Li*mxc&zu:6+'`<2~)v;?IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/java.imageset/java@2x.png000066400000000000000000000065061516266226600303360ustar00rootroot00000000000000PNG  IHDRZρ IDATxYlT<8OPKZڦm楫UժZi+QZHҴib0 -1L,f-`{_,3{g7[FNU?>l? uGoG7L{gKPկZ=+zAL3$V1尖*wU8 F#-UɽyapgWp0rqXGt~e_,7=v ™-ݙ2%43IM{sEkY) 3[LNj;L3y.}U ݓ WN;/+Tl7\ .p wl4 2V%cpP&4SClH%p;)"k6sު_Rm%C8%șFfm*u*ws.E3TQS67뿡|:WmbmhE83uÔ8-YІI@ţpf ' CZIe ܶ7҃Dd n}ۓd@eݘ ׬dp`\fɑK&_BC8}*8N8gE&? Xn#!2Қ$(3ztd7=C8GMWO3ֵ&DZ5Z>eA8.ȵUI RkEzxFˆp]f,)&ڧ~e_lpyU3uvwe^߇FlO8zsvgd#;T yFA[|К'd m~ΨSuFpIgh 4&UC8_ 'md{|Ld9UN٢L&RW9"cIӿevNQpgl+zd 90î3_! _*LtU˵m2C8L[zU%yh*p$q^7 GC8 \=g(Җ$rt$b;@8KR6-Uh;nw,2CyT|BBCjp7Mrꩫp^O`!\6buiݷ6E80ut;~AwdI8%pn˻o'yT+R߉D9N#brآ9ofLdDZ k^B~זqk'۳ݮ?+ ҥC΋tV3V].vpg \j0C8OXncV>.}{Ndj C8˅XGk?\%0Ca-;0jw &Q̑CBSyg߾^8CBZ5Tn vmi'U<=A8+D =-wpC1ߘk蒚G[! \F#p76+ވppppppp.iܛҥKŋXdZbھ}jkkSpÇLc֪^C܉blg#G ˗)QWWg/<ى@9zC8o?8QZZZԙ3gG{{jhhPVrٳgxqy|rD~qAI+WxbQ' e̕nӯ)NC8-Q,x]NիW#֭5&uVJN;v#D%ӻ|pZH)֭p&_^ڒG8y'rfv @8c@w/۱c2^WH|#UkD6tB8̾}|+\kkkND6޷lݴi.7]nܸ֬Y˖-+Zp~Vnٲ=B1֦bp>N"y~C>88:_/lc᰽Tl555v@+#WeeVE T8VJ~QIJ@.]N8a1_^KbF8ÄRUUU4 J%&op>ΔKI'Hj`A8 ': (8nԹ>A8˙G#,wi%@8^9t!k[ 4- m;J)&HɩfSR-޼yӎV;!\V$^.z#(!\T,K 3<+ܜ{zz_A8ÄN:U!܄,;gpҵJ }KE6ɝ`DC dkSG8+;L FBv*l~ #¹.b&e;!#.\ Y/a*pppp7^m۶(}uRQS*msАfJ^z-24/•p҇DdA8+Ғr"ʭ:v͒ᜑ~"3]~3Cp!;|%BSf RA|NJZJμgϞ&)ML|;;C>7-DFduuuDkg8p@i !! ! !  !   !! \ 75Y){y@yV҄QMIiӑtdg%M).\EqT`pg9#!$ғn21m2&y*oT"˘:T0?"3IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/midi.imageset/000077500000000000000000000000001516266226600263275ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/midi.imageset/Contents.json000066400000000000000000000005171516266226600310220ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "midi.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "midi@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/midi.imageset/midi.png000066400000000000000000000007701516266226600277630ustar00rootroot00000000000000PNG  IHDRnn]PLTE""""""""""tRNS:*f~6IDATx^j0FNκRnB Օ3q?WE]L ~G)zl,w6bund naD2¡ -C28 -C28 -CKR8nmlZmK"}&gVĉw*@7YNܧwcVaNo'818 '#uCޏLwN\t-5?G]W:o_qc2we-w}h|cy+P؛;qG8q':: ^e-08J)ƻIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/midi.imageset/midi@2x.png000066400000000000000000000015241516266226600303330ustar00rootroot00000000000000PNG  IHDR,?lPLTE"""""""""""""">tRNS Sh RD~IDATx^1 NX8sv @$%"*X_FɳK#Tѹ'9w<6վ<={GA (ABOIsZ8ܻ@RdK [eۧƥ aەM&a q؄8lB6!M&a q؄8lB6!M&a q؄8lB6!͈b^'l>n۷w"=nYq]? . . Ǿ `c8inE e;E>fjs8pn*9׎g+8p7TRd8pPS8[MفΗ5UkZoE[ax}y}*1U\kqns8$tRNS:*&IDATx^EsTAa.fq.?Bo5SwNT^YtUY%d+^( XF$ R^qB[|IQℶX8pBc  '4Nh,X8pB &>.\ F.X.OزN5NQ׌]ٸȥd ׊sD38u\\%"E!;;y6.ҥqT|>)|\neq]}>nH(ۉs+8q[с\e Z2q.S&$XXL5;X|hPYjHsن= 77`LT/r UR%NzNn5Μz,u6%9 $\'O%U`=Cd9[ ;(Ý\5! ٨J<+.,#W̘&i캚\-Н6}fqSE.u'.ťO-N#+~gl@F錐灅*219GN灆\a+3욞#+ (kp.5GM188?HO3:,4﫮y~qvAH)0TZ鉍:Aw|!)[ainE?E떙ch鈍'/ꍑ%-NU9@sx肇OV膊zrw*1X^pu5<~x}듗胈瀄흠脈KQ=D@GBILR땙왝흡QWDKꑕ'ꏓot뒖W]mr/6uzbg엛agZ`אָRXty𤋮6=U[hmIP^d節鈌2:y}v{銎EKjo#*rw\bퟣ߉KptRNS Sh );IDATx^1 NX8z @XhBZqQM_-p9%J4뮊K8Y2X5_v2H+ pTmHdwVe`Vg]qwQa22 |A%*_<www_tmnB=u_TO=uNM P@3>)[@ViD _RS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS6M+M*em7Lw7%+iͅU'zW {uz&{~w,@tè> :Pzgk<\{(s2L "G[g8\1]<,dq<ppu;R)6NDpe7@#ĵ[ p`.5Ip* kppmIYd.%ZH n(%2L# {]$ˇQH?m="qԃq/L\~8KLnL\ba0J %Dm2=R7/7dzqP4 Å(KHDO1* mO]YwK$\ws g]'A^7[_ { Ӈj ºX!j`Xe,lJ*ú!!pkS Əΐ^tV6vc.{@1j#dE9-|,-`)ipejЕ>Ɓłt9$^kyFYPaVV̎?[c w?ҹ#Dm^5FFL̦3;hZd#p ;{Yq gG㋜/WIqs^I%ldwrHEV )0-H{&Q Gp-;+ 6%b\lS)p~hrxʶS{[t~`YErpT<98888888888888kr@)ഭ]~4f2?50 K3sp4):;)ȓ+ѫOk;a&$b$]IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/photoshop.imageset/000077500000000000000000000000001516266226600274305ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/photoshop.imageset/Contents.json000066400000000000000000000005311516266226600321170ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "photoshop.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "photoshop@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/photoshop.imageset/photoshop.png000066400000000000000000000020011516266226600321520ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""xy}z|~}x{eXytRNS:*a!ZIDATx^U@nOv3ff.3333/u4HH,ى,(I$iP8֭+ ׶OvtB*CmAh&p5555555555555K>]u#ף:99p ve򥤵}™sC +Vgt~OjÛp 0<7s37Xlq̼Źvp|i! e3I,<gͺ+2|{uXmJnF)==$rXYS//Gco^q؛\mr=p6l -~ތ=KuU3_Hr1-EeZ&,Ho564V\U-2 'p 'p…TН+_U"hiI$I g \0IDATx^1 NX8v C1(iHL~y:wlx'!'7`ͭ zٳs# oDlY58l:em|8l>6͇ap|8l>6͇ap|8l>6͇ap|8lFwcvts͉؜mU jܬW+\p\p\p}^/7vݶa6GI r=ajT]29?+Zi~ogbe$8aΠT̓ԈqPꜺ.7T#0p4qƁCd\gucGcD C (2yr'?cW#-5'n Z±Z•kP2seZCa㧣ӵup%huB^F` Zњ`zQD7x >n.Fw~1:R!U7%#'p: )C [uk %(z`۝o`L%/@6 ¸(PXFSa\/M PƕAaaq!(R- v(p((*A4Z8\UEMI( {_1 0 f>%TB  }lQ mt9AXXBh I1xRV(^N\k=x8&@dԖ +GnɘBIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/playlist.imageset/000077500000000000000000000000001516266226600272465ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/playlist.imageset/Contents.json000066400000000000000000000005271516266226600317420ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "playlist.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "playlist@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/playlist.imageset/playlist.png000066400000000000000000000011541516266226600316160ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x|ִ肾yՋ}֏ז~נ{֚y/tRNS:*(VIDATx^j@LDr-ɬKz=`1s%Bb} `5#J)nviKz%ַj%Uڞu{-/]rCY9ځs8hq8hq8hF⠱>4 ׶56ĉ'Nr ,}GdZ=|맧T.sՐ7*k""ٸ蜰wBqW~}sw3ʨ()kʽrө┯JD!̇[b}0+Y@nm8Lɜʼn'N8qRkH,6ԓ4DRJ4HrVIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/playlist.imageset/playlist@2x.png000066400000000000000000000021531516266226600321700ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""۲yޕܚx׶ݟك{֩䁾بޤz~טyՓ-9<tRNS Sh Ρ3IDATx^1 NX8z 0X&N~ 4_y UtلI%( ln/8eqB(z_j=DŽo"ԉ_Pw#$N=8lR2Vq~a+͇ap|8l>6͇ap|8l>6͇ap|8l>6w=t6ٶw9qˉwE28͊;p . . . . . .; >HCݒc9E5Uj{o3ݟ+&xr\X­v c,Nu?pKWbb8_JUAYMvKo~)ngùJ=RC1bJ\v:{7B{ s2Ng89J\q /I ᬸ+Gq3cUq78Bh@CX%P{t8teoX尷p6!CkmWc~5"zTNf8#gćSBip#8)Xp $UBRnG55οڱ A 2׿[-Nj.1?50 K3sp4)FO e+qO^;aX0P-+IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/podcast.imageset/000077500000000000000000000000001516266226600270425ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/podcast.imageset/Contents.json000066400000000000000000000005251516266226600315340ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "podcast.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "podcast@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/podcast.imageset/podcast.png000066400000000000000000000035561516266226600312160ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ʺԹOIR׾ǶͿֱݰƵ¯챚ij;্zw}۾䪐뤉YS\QKTñ巢ʻԟojr˽̼ܿ弧|Ĉjelrmt鎋pkrVQYe`hڡPJS롄㿽_Ya[U]snua]eRLUTNW~ymhoxsz{x~}pls_Zbidk`ZbŝǸxltRNS:*>fIDATx^UV@i;v]ffff 333333LRFǖ*y={[^,w23333GWn訾 UzWTo>w+D]қv8U#8U3S5#8U3S5#8U3S5#8U3S5#8U3S5C8U3S5cf7PpqW%mkhyے$p¿<]Ą j-8jD\zssahUӥsƍw`f_Pw^lVI.H7\m^*9(vV`M$xXیᚂ2Dccff74}iJ|Fpo!k-XF1>kw%&Εƥ̱ۧrEd\v?L Bnee-H̅11c}ҤI:b+^ښg\F"pjX)n;>.qNX>E,R9O4ܧp}=WZҝ&V;U>Enq?bӭg#.-5nm)NLL䢕~H?:Jߨ螀ԿqkqIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/podcast.imageset/podcast@2x.png000066400000000000000000000110121516266226600315520ustar00rootroot00000000000000PNG  IHDRZρIDATxݱjSa:S.ܒ7m( qN"ZA;HPDbCpZ%y,mr I~$÷@@p 8@p 8@p 8 8 8kN[YlT:EZer^Y]?lvI oMp'kShg*"G*Y6 낛 l. {0UL&?>\ nVVBFaAfk]l~F¬ nV6 n Npb3̓LpfK$6\"Hl&Db3% .LpfK$6\"Hl&Db3% .LpfK$6\"Hl&Db3% .SS?mKl&:b35lzSl n~>[ naa&fKRK_ۯqnܹ*?++)-)-8nTe9?i0^<|=+!EVfveԑh{_7;(;}I*v|e&er|$qRN16Ib3`0`ЅȊdIܒ$е+!i%$}_\ѷ{zA8WTٙ{5ogpA19[rSvUJ965P \v}&a=Z,pFrRuVvl<"8ImH8 ݋e}cIbZjJ{E=3 #%32%327;۰-+9&l]ݖvJGP\:= SY%b;I-Ԏ ncFVzh}`}s Zb[ȯޯڃ2r}BR!5M8 Å+gSy,v-\9ZBjFvY%{7WxykjAMW?-p; */'nw ܐj.c$~RW)۰-I6jӁV ΁1sWKpmo>Aӄf .} ˁ,Z#}ѡ W%fjiC[OO,.hzI9`A1Clc3ʿj)S,;6?/;,JR%ԉn[,p<oQyt[dGjtr0㎒ɥ/,pa3U~Q'7Ň3BwB߶R@zG.&27?a[j&Є6nդr誻 su,Yc$-t]ޚ|# nͷN1%Jn|<@fJ?$qkT-hT'WxhFLD9u\zEeܒ !{@ͪ4*+rs C6fsK;F< #Y1/huM2墔)?[ȖS-M Y,p+ZLRQҹ2NLdT\8QYlBAMY/Z<-p+䌰H/ۼ_EšW\fP43c@-ZѢ._g[8i_23Yy"]:MlH&'']?  ]g[98FgTJO*KdzjV%D{j~\%/QW}Ԏ hvWBF ܪaJ*'ǧ8zh9;ljڔLƱ}QhvW8 ܪAwUn{ºĚ>*˜qPl=Z]>TWtn]XCef"+H~uW-weC#Z]>W0pC`3SUݽ Ag=ѩoY cs)fDkt-V,@K7Wp܊ܖ%@d\:ܽcOB͋1g+rt뜎tAR' -hrŋ<,p"bIx(Rպu<|o4ţJ{|H&lprP;|gK n1+ ~)55-@Y҅-M=p==cݹE/fͬ<1Y ıT= [W 3_8 ٻ{֦83tPiZA N:(IV2"Hg;uJC*6'¹ƞ)%8翯K+-hxk`pe \pelW- p\ݧwᮥ8J nsw:zhppO,\>o9oxY@7@r '(wWM'0-sqo/CټDqlcll4g/17jp}#q1K=D { ^^?>r|Č38?gqgO<ã\.Uj_(scYeY_7!ճFIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/powerpoint.imageset/powerpoint@2x.png000066400000000000000000000024071516266226600331040ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""pCq:s~w#3Dv!x&x%8Y=ȦEz'̭Nϱe{T{)?lhu BU`QǤԹ׾ry&ğaWqH|ҷ5LkίuOw$m_jּ6p@׿>ɧgrΰ|,s?x_tRNS Sh v/ IDATx^1 NX8v0 C1סN{Q _: /qU |t3tn ;7w`[g'9!VvVM_ԀLyAzaԸt8l q؄8lB6!M&a q؄8lB6!M&a q؄8lB6!M&aql>͇;cmcW['zo+_ . . .o׼aYƽa>1Ȱ0, s,ҤiiFgHO_[{G N,{p8(Ns\ƙgI`Iq&Tƙfd!6 Z֡O{rjV k?B _#Fo '1]KP8 L<&ʧh*UΠ~FY#<*EùOYp*/T^•WTBE]2zn\q;b̎&N?%sãcS9w߉ gtN`qMwy%렠nnE@qwĥ|1D\XK(kTY"8#8#8#8#8ۘ8#8#8#8#8#8#8#acs6LJljtN+sP <<8@b<)z(&;pL0-VZ6{:IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/premiere.imageset/000077500000000000000000000000001516266226600272155ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/premiere.imageset/Contents.json000066400000000000000000000005271516266226600317110ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "premiere.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "premiere@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/premiere.imageset/premiere.png000066400000000000000000000013511516266226600315330ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ͧӱҰЬԳүյͨѮնΪҲٽΩұػ׹ͩӳԴЭϫ ttRNS:* 1oIDATx^n@NB; 3CQItcsJߝ-Kwq-L 7gY80p-I7 #mE"8i4G# HCp!8 i4G"*q\HD% 79;=+\l\*jVS3eGeRPN:*j|wڣqP٫pÚ㹏9{nc8cc=::-$˗YYV]hb<I5Ohe5w}ӻ μtro/B)%{[¸nz %l~};Ss1,c8c9cy@E!fz3Lk8+IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/premiere.imageset/premiere@2x.png000066400000000000000000000024501516266226600321060ustar00rootroot00000000000000PNG  IHDR,?ePLTE""""""""""""""ΩϫЭͧ׹ΪͨټյپӳշҰѮԵѰЬնѯ׺ַػͩغ׻ΫۿҲָ̧ھٽ tRNS Sh h)YIDATx^1 NX8sv @$%Ǣ5a<`Cnxs{ 7w[ |ٳw¾1>W N-nec!Ni-H i18lmW*RX%m\|8l>6͇ap|8l>6͇ap|8l>6͇ap|8lFateʼnw[iT뤸5b쭸+.$.qK\%.qKz}No'ia[!Umǘ,dD-U'm}/)|81{iaɩTRBwj+$kiW3Nط0ϑ0_S=8u ƩK0N%$y8U99men135M?[#z1lOpkIZe5# G VSp&ϖ(\gW$\ɸ9if2d9וɼ.M Y= Muz꼞 :ḑN8&.\}p^=pqg9t[w\ˮ^Jۮ ~03vN^g(krq+}qI*A$ I(G$WQ_Ȭ-¯KDp88888opppppppp1.:?gGc\lnt`a /x&xp&ŨIYvRxe;s9PKvaS[b'{V.*<IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/raw.imageset/000077500000000000000000000000001516266226600261765ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/raw.imageset/Contents.json000066400000000000000000000005151516266226600306670ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "raw.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "raw@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/raw.imageset/raw.png000066400000000000000000000020771516266226600275030ustar00rootroot00000000000000PNG  IHDRnn#PLTE""""""""""⤤㙙򿿿鹹Ǧ︸ɺߞ婩ܣݶ޳ϱŘB!tRNS:*.NIDATx^gJᐓ>$vzァ~QƊbkX)!Y<2 ?Nƚ2.4F+5 7ȘMa8?(0kOcMǚ 5k*8TpXScM ǚ5%kjIp3Y(䦌9aXӜg\9z s%~xoJY6V}6kY! ni266|kXk;wKY%Y~šySoV]k7 kW ^E pAqcnrm ku\x发K$EF;/ܤ(x epbm1/e9loӭ%¹ѢYKl5D8o;0QQx^z[\սƪosnnk΁O>w(9=G_DY p\.ї`(ډ?_-Ҫ$-vs[*ox*!v ?@+4;Pvn4h+}RP =XP*Rtc4͍g5 ϡp5_6͇ap|8l>6͇ap|8l>6͇fĽpwƦ] '.K8qsVۤ'r+. ׸5qk\cܞҺ0Nf:KpDpD%jPR1MSa ʜ>7>fudg7]`O\;r?șrxJALr境aNB^eP P!eګi/( s<(0|xQ$T~*ks.<$h4 ^9)8.(< r&8^RAl5zh49G tF,) j`I3$˹M<Ѭ>gƱ X.VIcٓ+vD)(߁aDց95 PԄrwKG<(7C1|8?]&kfJ'd4 ~aP6_ c| ]z=4S˅xANIp: er9oG Oҕ$9 J b1D Lc0O, U"SKQ ,|X#7z [F)-r A!8b?O])#W>ծ?ܪJ TbܒQF a1rv=O y1r*GM~#(H_X\9/`9֮X<хo3BN Q \~B"*k]EDP\ uCcr[P.ҳrj:tW L#Ppf)}VɽX7:C#H ;MF!?uk< &.A_8&@.5ruorO#:sS5kc_xO]P'mmL@VD\1+hM\4qӄhU%U,WD{HHH...~~~COBtѝ???wc˰冹ןxAj{{{#}pw[ЊJ2偁 ri0,o)h@?~xbԨ 㸔tRNS:*8IDATx^>e=)!333Ø?h9'/:4GO*bX,VO*nq>!\RuԞpHt'CZ@C i48FC i48FCiT8QF -. q\0s49.81rz]/3{\ K\DeSkqsݖR(w|eaŸ)NZqɖ mQ N$ɺ]X=h&3l4iĜy {ܖFfre=Zv1s-_(zU7LEZi=0Ųc g&"z1DqԶAF ΕځEo @-iL>lkA \Bk֮x^C.ryf6.rF9Cv,3$rfVKvr& r6"4C;b/ha!BNõ JH8 t}ɵv*rd'B ;-GwFΞ#E\*tYky*p&yx nR%- s pq[o)Dޱb!枠o-~yy&PT%iO3#Xx!CF('v%xT>Iw6/_:60x4S&F FKFFGŕhqEp4jܿYO*81qjwCay󾊟JaX,' + tIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/real_audio.imageset/real_audio@2x.png000066400000000000000000000070371516266226600327040ustar00rootroot00000000000000PNG  IHDRZρ IDATxitU#jM*"mŽkBiˏV[lZh]j'@+8OL6D-j1P0! A1$B& !>vϾ%}w}߷$ܜ/s-'Ō43.3r3`j2ftb&hу PCKa&h矉fLĕH ually8XTX8ï655P(njN@TLkK¬N0jkkc*'??fvvСiLadC6@8@6dC6@8@6@8ddC8C6@8ddC6@8@6@8dC6@8@6@8ddC6@8dC8dC6@8@6@8dC6l] oe¹#elY ;TsGF3D@8K#iD{j3¹+Y)pppppppppppppppppppr㐔%[˔ =)@Wʗ&dK{%csc<1G*:#On,Kd?KiH%քm K#[k|Sya]b׮yw.(# w*3,C1Y[u!vh =uCF$_|hi̿!ۇpgb:!2jfYGE[4Eby ⲣp *E1|=_,,o9?gU}xAso?"&')=~CN|$&G=Vd~A+3&%ʨ ɦxi}Tmߵ+vS>=&u莮.>ƏɹQvy hlD~r5[p'h|Lq.y1&a2i[S^p,nbhƄSnU-vlJGK7TIKM,{=nݺ9Fu=zT222dĈү_?ӧ5zm}NeQ 2ѳ .Ʉ7;qN6`m>luuubKcΜ9||CCL4ɒϛ:uj_˶9[.I-E`w聸W+MΝ-)<۷o5|zsɭs7Χ=}GiK^ʦ?]i $PHyKHeѣy)^hy39՛$n6w47WQܮ8cǎCF,Z>|xLSۖ4KspO(=1&{.FvڵmbN:o]8-9gwW-9D&/6z6ܱD8 BM9ƃ3fi2qDΖR)**ӧː!C.f~*s7U/FhÆ nIvE <[aW]uU]|ŒLVzz*Wli}xɁLߥ~mu3PPQc\&mG:  ~DkMjF gw$^tt#==]J3!+:K5YϹɒA8 8mڡ:LsaX]]X8}'ΠN&͙1/fZYY)3glw}_V֫8n!S%cM=sҲ¾'jyK0)Y[-g[;6)ϯ)UזNACxՔ{>^%pcoդ{a(vIz%pS+ˢ`ڦWjDݎm[}^p &YwSr+&>8[x %p+}4t:4'SZy}.ehW흷l[|.ASj!y1.MY$=on1mɥ쟌`YLߗVoX8E+gp/h7s^3--~7u]sE;Nseܵ׸Vſ$XP!f6ޕHK"Z.nx,nGMڮjkh\jDK/"mE7Ͱ΃}A ׼̜_Wk+)Zˑf>_hpI&\KV}h>&BtӍh[F5"\kFru2kQS?F7-e0Ձj5Wvdhm;~n. ߮s2 N۳[fAjpnm6J,ۋn"!EDdY]~@J]=9Wzt2C&iWMdjpQe &GќW7ɹVmS[2swgm9K1濣OĨpN-!y$;Q FLTC8-pAd\ͅ F5C8WN>D5C8fJ.Q  \pz ǖ5qBTC8%D5C8/xfv!ԛy޷pZpU')@TC8sg@K24 '!¹Ls#!hi;!Gh!  !   !! ! 1,\, 1 t :gˇVQ^~+A$\ 'Fu,\jp8R1c:G99 ".QʖI#]ꙇ׋I`:3WRl0c7OIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/sourcecode.imageset/000077500000000000000000000000001516266226600275405ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/sourcecode.imageset/Contents.json000066400000000000000000000005331516266226600322310ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "sourcecode.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "sourcecode@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/sourcecode.imageset/sourcecode.png000066400000000000000000000011751516266226600324050ustar00rootroot00000000000000PNG  IHDRnn{PLTE""""""""""䛛ȥۙ tRNS:*>^IDATx^n0Ѻi fO \a9Wdsc[~y$IQ^VVblŻ:lfvK⠽}A0FQ8hFQ8hq8h$nm"WG&ϜKs{.{\^Ǯw./-vAӽgˍ5xs<\Z"j*&*Np;p5I%oZ=h9h$\aװ{q{:dЩ9׏%Qyva`H[=5Aكd%!9|G"_ N:yXqO+.JV܊s/@g8p;En\ L^;` _[*> _N8spcnw~wu%:#6;8ڨ#6,h8ؠ3:`6ꈃ :8ڨ#6Lh8ؠ3:`6 .tF|O6ܸOuʯFu_h $L5u6k8mp۬˷YQo.f G]|9u6øԆzrnC{Jج;i‘{_B˿UefEW$o.wl?óUO + z-F]S.W=OqY-G'";ΝƵ}2ڢq! E4.7qS89 c=g}8?V]*3@dr>.tmh K6-SV 5٨=JmL6`5phЄ]43 jS~/:2D8N8$(p 'p 'p 'p 'p_Ǝm} m6?A 0 h_LMʢN2v:rӚ1 NX8SSDIJNIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/spreadsheet.imageset/000077500000000000000000000000001516266226600277145ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/spreadsheet.imageset/Contents.json000066400000000000000000000005351516266226600324070ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "spreadsheet.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "spreadsheet@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/spreadsheet.imageset/spreadsheet.png000066400000000000000000000005471516266226600327370ustar00rootroot00000000000000PNG  IHDRnnBPLTE""""""""""RYtRNS:ד*WPIDATx^9A_ u_3%p2Rj),_G}ݢL)nG[Ycl]5SD ':8iN98iN98iNfy4>n-N'Q^vꈃW88888Ec^Ƙ@DT CDDC,yGIENDB`spreadsheet@2x.png000066400000000000000000000012201516266226600332170ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/spreadsheet.imagesetPNG  IHDR,?TPLTE"""""""""""""""R\)%tRNS  ShΏiiIDATxݱn1JvKJq.J͂&3ć̬1vDwiaGnL?&NbfUgottf@O2; pm?hLiNi';ڀWl^5ƂU Q6*Fۨ`lmT Q6*Fۨ`lmT Q6*Fۨ`lm{͝[4\+8N${5K\pĉ'Klmçܜ8qĉ'N8qĉ'N8qĭ8 /rsrw>ȩٸ}Kqĉ'N8qĉ'N8qĉͪ'nj@ĉ'N8qĉ'N8qĉ'N7nN8qĉ'N8ݥm,n]Ɲ{q,Uqtuµ =GQ;Kp$N8qĉ'N8qĉ'N8qbt~ɬIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/swf.imageset/000077500000000000000000000000001516266226600262045ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/swf.imageset/Contents.json000066400000000000000000000005151516266226600306750ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "swf.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "swf@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/swf.imageset/swf.png000066400000000000000000000014261516266226600275140ustar00rootroot00000000000000PNG  IHDRnn)PLTE""""""""""3.∅okjg:550咐TPYUvs;6uqJF?:50lhC>SO<7顟Zc_IEQL^ZჀꩦA=찮XTieeb_[ᄑ饣LGyu~{|癗EA颠944/61FB`\⅂韂/tRNS:*%tIDATx^N,Aa3*PfI7SQiZwGXxuB@zL8!dGNDq8@h N4' ĉDq8@h0.*KpḘI3)W`͍["+7- 1K2+|Xʹmni3ci|s,M;aiKۄD[ eVD[+!p[j)\c1ۇr CcAo)Kg.s.ZXJTen-Y]6HτE$Wk$+ %p?|A9SN9S5Nޢ1ܫgii\DIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/swf.imageset/swf@2x.png000066400000000000000000000026651516266226600300740ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""]YB>蟝[W72斔A[1ّIIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/text.imageset/text@2x.png000066400000000000000000000020231516266226600304320ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""𞞞ͩ뭭˚纺ޫȶſִꝝদ۵tRNS Sh {IDATx^1 NX8v0 C1)Nc{Q _: /qe |t3t9 ;7`[g8 D1D|g&#UJ J.֑%;p tэa}}p|8l>6͇ap|8l>6͇ap|8l>6͇ap، 6~¦pm+'s9qHq:Zq}V\\p\p\pzc'= Q5$&lC yyŦ r͗ݳ9R-{4/_Rl,+Yʮ|q+06ŽJk]Zv2-lXntnٺnlMNEYNڿBpNI#8i'4FpNIC8i ps[]ڬ$gV8ےܡ㸧M+{|\3s^vCUg7 {kKTr%\r=p?:Z 7ieYeokDyIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/torrent.imageset/torrent@2x.png000066400000000000000000000017351516266226600316650ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""_ޯl߰|kX˳ғӔY}hOtRNS Sh Ύ'YvGIDATx^1 NX8z @XhBZ_jo/*Ь*:w± f ʜ{R`g80D٥%PBWs\`ɒ]22q * 8lKmS>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#M;pGl:#W9qwSm. ·:[qW+.ʇf܌|;w@ DQ i , IB-^a`Ϻ=..^qcĸ7=^spSzbgܻ478 <׭-NC)SmqJbPJ2}qJI-NC)ɴi(58 Ni(u/NCi(5>v(5>v(5.8 e{#?bP6PPPP)8 e"7C4I(ɼ.JMqT CYp8pc8P6EWX\>rmgU%֣Xl˯$L0 ܬh"܅J<1q :ryJ#x/ ^O͖18p8pprld[a­A 0 h_LMʢN2v:rӚ1 NX8@`x[IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vcard.imageset/000077500000000000000000000000001516266226600265045ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vcard.imageset/Contents.json000066400000000000000000000005211516266226600311720ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "vcard.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "vcard@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vcard.imageset/vcard.png000066400000000000000000000013261516266226600303130ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ⳳ읝ęҘ͟MtRNS:*ySmIDATx^N0a|(.c;l7H893/]ز8D|ӊ=pX7+&h{uhv)8(8(8(8(8(8(8(8(8H8H8H8hCq~#q 3qL d\z5Sm~RpT 4@U)RYdʍ;*3*73Yk-0X5D:#rwvw=WE'&r㨪Ec)k7й2w-+Vˁ pt.} _SdP \.SF"=\CőQks_}[g舻RV=ݻ^6ʲ͋n8 {p\耓tSrg7L c9|ѭxpgu<88$EIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vcard.imageset/vcard@2x.png000066400000000000000000000025601516266226600306660ustar00rootroot00000000000000PNG  IHDR,?,PLTE""""""""""""""՗׼󚚚ź任ꦦ͘޾ܱƨ²utRNS Sh ΏY DIDATx^1 NX8sv @$%5aܫ`>B;xs[ ʃ;[`[ ~ٳwyc~: 4.D-H@s۩Bra{Gd2m>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#Mpl:-mp} 'nj)|\'+.ӊb!͸׶Jwajiسݴ0 ep !qDٜuiK{襁I=w,w}4W0QBlӀgHȴq&3qRqoHAjf1NjPV=B#-n ʶhq+([J\+q%ĕ:Yqu֤2"ŵ)&U )n HqMdIsB( iʆq hq EËZFVHl>=Cb/N'8z E6=9@B? t;AL;9G)n8-Y1 s?(.=|/ysHmN@j8;BjMKZ\BQXR⮆2d p.2{֕*Ipw]|-R3Z2㌧UT3ȡ!{KKOn6[t& 7"׺1Oަ*62pUhi!-=CK"p h!6"p=t/w -ER$MIВ/7"pn"p;sTk}_̅1"\3[>(zۅ6 ,,Ф,X(#O lc.GJ< jK욕KIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vector.imageset/000077500000000000000000000000001516266226600267075ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vector.imageset/Contents.json000066400000000000000000000005231516266226600313770ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "vector.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "vector@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vector.imageset/vector.png000066400000000000000000000027501516266226600307230ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""𺼾GtRNS:*rlpIDATx^oXh{af033CyOSz'gMޓ"|ӹ:e_(QDe)ZN9eq %o9ܪBqZ9*'or"9q} \8ԸpqP¡ƅC 5.j\8ԸpPáƉ+DW$0)U%Xf:awwV-EN`k=Nz88q$1tGSc#[bzK]GrmUK|7v6vgoZˆQG4&Wu#Zڜ7TCM9t9\r_Et.It~V2OGqz)vwttlJ>Bn$OHq*'`C80fF4c8ɫ{^FڱEKCOl)V]='?dZEqxĿ=-+У/{5%ԹG48/R3 m>cFvqȔiohp`Kk߂ l V#Z8|УA@PHVQ [-5]Ixq˂ L$zq#s> I|RlF]n&6BĚ#[nЛƔpU8khsFfE {+:p#D1:zXhVk$7Y iǿo’ZJ{lSjqΈC/Uds#$Z[:s4Xy̐Chױ7d*p}7 \8]u8q88{#p0+S8S8P ё'<7%J(QAe)GIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/vector.imageset/vector@2x.png000066400000000000000000000053671516266226600313040ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""ʺһJyztRNS Sh WIDATx^1 NX8v C1(m!|,`~[ͣs%rNssG ھ<;{M ( 1RxrV@M~ZɭN?9Jş*[j7R*p>S{aO>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#Ħ- mCø}ӡY\}OV-74[q97sJ}>m(-p1 @ΈCgġ3q8tF:#ΈCgġ}܅:i 8~5H lc4lE Ĉn )$ D)iKD#R%F}WUJUoZU]ߣtS#~z^.gt6[ :8pѫL*291n3?疦B!Ŀ7QGW ?=1aF򆃎6ξYKvcY'vU!fu:h?O4*fNG{_%}\yvM5Xo lneLŒ-%tq8W, 4$e/E+NsS/ I9 6t{BY̆:rs𥱈B֫6Xf\H.+q(c3Wlas3[SBg`L[m՟lv`oo}7<6Ggbl+O'@۷o0_5kQ}cyyIdڷdqiVGw2 `+g%4n8t&I:wiNחDpR؛?!K"lP맹)pzI$[>5Sl5[kkDnfD!CvIz T]:t1[KKgpbE'<[ggTͅwK"fCv:r8$¶i)AG ^g' cl :r8`5h' :z8v :8Olmjn-z;Iy6^G( A%;O&TKl[ϣБAs-IvJ:z8l'БáI67:w'Y?[_v$TIG D̶[28Tʖ),̌^zՕ 'u[MUw.P|W+ xl?jCs)Z?c#pf7[޵]뎖Rno8[};ep,/SۘYR NNNNNNNnslC .69AXXBh I1xRV(^N\k=x8&@dԖ +z3G IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/video.imageset/000077500000000000000000000000001516266226600265135ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/video.imageset/Contents.json000066400000000000000000000005211516266226600312010ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "video.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "video@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/video.imageset/video.png000066400000000000000000000007651516266226600303370ustar00rootroot00000000000000PNG  IHDRnnrPLTE""""""""""xۃ~z՟ZۚtRNS:* wQIDATx^j0DI]K"rb*.Dzcr9vݲ[[B7xsg ʃZ`[ yٳ[m5Hkpyo @2HX6έS*p؞_qض͇ap|8l>6͇ap|8l>6͇ap|8l>6͇fݰp wjN\,ŝWbxXqU@p\p\p#}ٷ ٰ "(} ߹rA*u/!t0ې ې ې ې ې ې ې ې ې ې ې ېmf W\f,q xWb+Wp\p\p\p[p\p4u7oSΎJA/ն{y}߶>-=~aEBWl᛻o"㡛ft)l᜞k㤭قv뾁=q%Sq&X\賽ÅC뼩zkjoz2뽚d}8Us'Hx0|6Jqv,᝵܍A^·Gn nߔʞtRNS:*[M/IDATx^e;6YCaZff-33333ӽhC,u=Dx핲*''(++&%P WT҂SJr6lńB9h************2"n*RCrR%G]s49͝<59+F~|ǥXH^,kZQZvωb͔g}e\OWҤ -)=WN7z7.6ٰozJךvBT{yq˝19\, `Dt9-1/:&K٢&2B9 EUhڀI]`ոXlf3oOƙwVT.JtÛ}psI+yX% pivndj܂.r˻{-Q> 5(!F02fĖz2h,ell7=ԥ9qh),'E^eDhvSƉ+q!"yGxA,5=_[%hј;x)mpK-*jԀܳOWs71܉9<(ZǮ~~rg]cK_lcv':_iNsӜ49Ii_t+ WRM 4|IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/web_data.imageset/web_data@2x.png000066400000000000000000000045101516266226600320010ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""r&Fgfv-g鷧xy1q{4jމIڄkw/|z2G櫾ᛌN{Ƙ`llgۈp$q%s'd}8~9ÔZߔf뾄BhXҭفRo"x0ݏ쾕]㤠m?=W娍Pcn!miHz3Ѐ=t贂>kᝃ@܌u+ͭق|5ۆE뽎Qs(}bh`:⟔[Y;܊ސq&Sv,殺ߕmJ깦v<꺬ـjTn w.ՅC^t*}7OUۉ|6㣼k܋ޓsıۇph䥝@eatRNS Sh Ρh*iPIDATx^1 NX8z 0Xj@S%.PEC8ւV^((40 Tl=Y.=8lR2Vq~a͇ۗap|8l>6͇ap|8l>6͇ap|8l>6͈{`'l:wkۧw"ŽmxZq] . . . . .Y℁jw vhn-"\{wNHssnȧ}FzOΎ1ԞVR˃Wq BPΛ<[/ƒ:44[LbPλD]1&O2i!ʅE&u̕JtQoYa1ROz&QVRlkd G ʩž1(3, ԫTSQɬO- - ?NAksൟ98jFQ9-g9"p\m=mNN9ઈ BUkх ,,Ф,X(#O lc.GJ< jK\WIENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/web_lang.imageset/000077500000000000000000000000001516266226600271635ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/web_lang.imageset/Contents.json000066400000000000000000000005271516266226600316570ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "web_lang.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "web_lang@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/web_lang.imageset/web_lang.png000066400000000000000000000012711516266226600314500ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""kl˜ʥ^vPzw'lm 5sCҳsǠytcWxʦ׼D4}wjo qu+DtRNS:*qCIDATx^j@ (ZoObK(,4a0?ʲ,˚mQᒴZԩ\i@fFI+Qڒip48h4 MAࠩpT8h*4MkPgq]j:EEmM&gqa}cxr:C翻 ϴ Ow sY=GdqgqPDot${_& {Dz,˲&`:M`!$UVa߁Lup0ÏbZLjt=p؆yIm8lϭ_qخ͇ap|8l>6͇ap|8l>6͇ap|8l>6͈[b3l:-6lO͉МiUKq ·u^p/Ua\ Ȭ?hʍ& %6,`wt\dG ;r{g1Z'&6hMMDfvl̠ ͎AGYS]me5bh3bhwtG۝&n 㳅pMh>>ч xU|\mpN7MuGݢls CtupN78NЉ6`G`:nnpN=፣` GnuhnGGj[ݷ{PuGp08ꚢNmO`G2.nvtp=+s؂ਛ^B{ [0\жi-lQpݻlҟҧ3_~'4_%.qK\%.qK\%.qK\%.qK\766W`~jaPghRuvR,'W1W%o ?%v >*v~IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/word.imageset/000077500000000000000000000000001516266226600263605ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/word.imageset/Contents.json000066400000000000000000000005171516266226600310530ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "word.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "word@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/word.imageset/word.png000066400000000000000000000020651516266226600300440ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""Sh/I,F3Mˀ-Gbu.G?WmcvBYͦv8QVkꃒWlh{`t3Lj|ާZnJ`Nd9RሗoF]퐞t=UwXl:Rr.H5NF\2Ki|BZk}z«汻ۺ1JUj;S\pxǸOdYmŲO鶰tRNS:*JIDATx^EoAtdI-1̜uUD{UwK껽GrDz4d2ls;ZTK$T}K54ֶNy*Q ږPϔcm+X c-Zk!8BpX ő#-GZ0 P\jS 8;m{xgg_,qSZ+e(p 'p '\B5WH^)~#d2i {IENDB`sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/Filetypes/word.imageset/word@2x.png000066400000000000000000000036771516266226600304300ustar00rootroot00000000000000PNG  IHDR,?UPLTE""""""""""""""BZ4Mm:R.Gi|Uj3Mw3L,Fq5Nꔡtsex.H`t8Pj|D[XlWl\pCZYm@WbuShv?W-G8Q2KȀNdo◤}Ä=U㎜˫;᭷Tiǽᩴh{ߜ/IVk]q9R癦0J^rF\6OzJ`k}OdꞪœat;S1JBYdwА̝u쏝[o7OfyײܱE\F]|ô܅NJɶ޼PfLbKb>VphzӓnG^ btRNS ShΡ 9=IDATx^1 NX8sv @$%5a|`C7xsg 7w[ kٳ[ L4S3 yC%(#K£!өFׁ6/)SmKk}mÇap|8l>6͇ap|8l>6͇ap|8l>6͇f]p6M{5wÉWE{28݊;po\p\p\p\p}0 3DҟH " Ԑ*-fw|a}fދ:wVSD?8 z9CAf).pkpfܨcdH"0,^$#W&'V\&λt!܄+%[qPT/` YK /U^ d-6`\92.2^\?ڔ=^#`1_7T7DxgS1(JfsVO|4 pbMXL˛L5a5=ؖ{{-q惣7*nH3մU狴OI#{tk2nN%yb)90O #+\~Fgb'.#T;O #ia0}W ^)p<Ⴝ>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf cloud drive 2014-10-22T23:06:23+01:00 2014-10-22T23:06:23+01:00 2014-10-22T23:06:23+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 188 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAvAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FX//2Q== uuid:60e83ef0-da2e-1445-931b-2f108df5179d xmp.did:b253e777-6c1c-4cb7-9214-a9440229b5e5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:d994746e-cdde-405c-8143-58767d1d9fa9 xmp.did:d994746e-cdde-405c-8143-58767d1d9fa9 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:b253e777-6c1c-4cb7-9214-a9440229b5e5 2014-10-22T23:06:18+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White CMYK PROCESS 0.000000 0.000000 0.000000 0.000000 Black CMYK PROCESS 0.000000 0.000000 0.000000 100.000000 CMYK Red CMYK PROCESS 0.000000 100.000000 100.000000 0.000000 CMYK Yellow CMYK PROCESS 0.000000 0.000000 100.000000 0.000000 CMYK Green CMYK PROCESS 100.000000 0.000000 100.000000 0.000000 CMYK Cyan CMYK PROCESS 100.000000 0.000000 0.000000 0.000000 CMYK Blue CMYK PROCESS 100.000000 100.000000 0.000000 0.000000 CMYK Magenta CMYK PROCESS 0.000000 100.000000 0.000000 0.000000 C=15 M=100 Y=90 K=10 CMYK PROCESS 15.000000 100.000000 90.000000 10.000000 C=0 M=90 Y=85 K=0 CMYK PROCESS 0.000000 90.000000 85.000000 0.000000 C=0 M=80 Y=95 K=0 CMYK PROCESS 0.000000 80.000000 95.000000 0.000000 C=0 M=50 Y=100 K=0 CMYK PROCESS 0.000000 50.000000 100.000000 0.000000 C=0 M=35 Y=85 K=0 CMYK PROCESS 0.000000 35.000000 85.000000 0.000000 C=5 M=0 Y=90 K=0 CMYK PROCESS 5.000000 0.000000 90.000000 0.000000 C=20 M=0 Y=100 K=0 CMYK PROCESS 20.000000 0.000000 100.000000 0.000000 C=50 M=0 Y=100 K=0 CMYK PROCESS 50.000000 0.000000 100.000000 0.000000 C=75 M=0 Y=100 K=0 CMYK PROCESS 75.000000 0.000000 100.000000 0.000000 C=85 M=10 Y=100 K=10 CMYK PROCESS 85.000000 10.000000 100.000000 10.000000 C=90 M=30 Y=95 K=30 CMYK PROCESS 90.000000 30.000000 95.000000 30.000000 C=75 M=0 Y=75 K=0 CMYK PROCESS 75.000000 0.000000 75.000000 0.000000 C=80 M=10 Y=45 K=0 CMYK PROCESS 80.000000 10.000000 45.000000 0.000000 C=70 M=15 Y=0 K=0 CMYK PROCESS 70.000000 15.000000 0.000000 0.000000 C=85 M=50 Y=0 K=0 CMYK PROCESS 85.000000 50.000000 0.000000 0.000000 C=100 M=95 Y=5 K=0 CMYK PROCESS 100.000000 95.000000 5.000000 0.000000 C=100 M=100 Y=25 K=25 CMYK PROCESS 100.000000 100.000000 25.000000 25.000000 C=75 M=100 Y=0 K=0 CMYK PROCESS 75.000000 100.000000 0.000000 0.000000 C=50 M=100 Y=0 K=0 CMYK PROCESS 50.000000 100.000000 0.000000 0.000000 C=35 M=100 Y=35 K=10 CMYK PROCESS 35.000000 100.000000 35.000000 10.000000 C=10 M=100 Y=50 K=0 CMYK PROCESS 10.000000 100.000000 50.000000 0.000000 C=0 M=95 Y=20 K=0 CMYK PROCESS 0.000000 95.000000 20.000000 0.000000 C=25 M=25 Y=40 K=0 CMYK PROCESS 25.000000 25.000000 40.000000 0.000000 C=40 M=45 Y=50 K=5 CMYK PROCESS 40.000000 45.000000 50.000000 5.000000 C=50 M=50 Y=60 K=25 CMYK PROCESS 50.000000 50.000000 60.000000 25.000000 C=55 M=60 Y=65 K=40 CMYK PROCESS 55.000000 60.000000 65.000000 40.000000 C=25 M=40 Y=65 K=0 CMYK PROCESS 25.000000 40.000000 65.000000 0.000000 C=30 M=50 Y=75 K=10 CMYK PROCESS 30.000000 50.000000 75.000000 10.000000 C=35 M=60 Y=80 K=25 CMYK PROCESS 35.000000 60.000000 80.000000 25.000000 C=40 M=65 Y=90 K=35 CMYK PROCESS 40.000000 65.000000 90.000000 35.000000 C=40 M=70 Y=100 K=50 CMYK PROCESS 40.000000 70.000000 100.000000 50.000000 C=50 M=70 Y=80 K=70 CMYK PROCESS 50.000000 70.000000 80.000000 70.000000 Grays 1 C=0 M=0 Y=0 K=100 CMYK PROCESS 0.000000 0.000000 0.000000 100.000000 C=0 M=0 Y=0 K=90 CMYK PROCESS 0.000000 0.000000 0.000000 89.999400 C=0 M=0 Y=0 K=80 CMYK PROCESS 0.000000 0.000000 0.000000 79.998800 C=0 M=0 Y=0 K=70 CMYK PROCESS 0.000000 0.000000 0.000000 69.999700 C=0 M=0 Y=0 K=60 CMYK PROCESS 0.000000 0.000000 0.000000 59.999100 C=0 M=0 Y=0 K=50 CMYK PROCESS 0.000000 0.000000 0.000000 50.000000 C=0 M=0 Y=0 K=40 CMYK PROCESS 0.000000 0.000000 0.000000 39.999400 C=0 M=0 Y=0 K=30 CMYK PROCESS 0.000000 0.000000 0.000000 29.998800 C=0 M=0 Y=0 K=20 CMYK PROCESS 0.000000 0.000000 0.000000 19.999700 C=0 M=0 Y=0 K=10 CMYK PROCESS 0.000000 0.000000 0.000000 9.999100 C=0 M=0 Y=0 K=5 CMYK PROCESS 0.000000 0.000000 0.000000 4.998800 Brights 1 C=0 M=100 Y=100 K=0 CMYK PROCESS 0.000000 100.000000 100.000000 0.000000 C=0 M=75 Y=100 K=0 CMYK PROCESS 0.000000 75.000000 100.000000 0.000000 C=0 M=10 Y=95 K=0 CMYK PROCESS 0.000000 10.000000 95.000000 0.000000 C=85 M=10 Y=100 K=0 CMYK PROCESS 85.000000 10.000000 100.000000 0.000000 C=100 M=90 Y=0 K=0 CMYK PROCESS 100.000000 90.000000 0.000000 0.000000 C=60 M=90 Y=0 K=0 CMYK PROCESS 60.000000 90.000000 0.003100 0.003100 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream HLSK1 @my3b5B# X4pF]vsr{1|uƦ1{aZ߯p5ǟGdpkNܷ .8J9&&#&FI,`b:#gv93բ um|^pPٗjp䲡:FRv,Dh  .N_ JkS1nϏ)uI"љ|jOG=ͻnoI2=cPf ŎHl0)*7*\c *zJYWB,Dg L#K3]b)JA+ `pw^ժ`j6|`Un_/lC;}Iwm줥\lU ^ 1񰪂E5/`|P#Oږ endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000041074 00000 n 0000000000 00000 f 0000042592 00000 n 0000042890 00000 n 0000041125 00000 n 0000041376 00000 n 0000042778 00000 n 0000041873 00000 n 0000042031 00000 n 0000042079 00000 n 0000042662 00000 n 0000042693 00000 n 0000042913 00000 n trailer <<2C2C473FEE1244418907788DF22D71A1>]>> startxref 43107 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/contactsIcon.imageset/000077500000000000000000000000001516266226600302175ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/contactsIcon.imageset/Contents.json000066400000000000000000000002351516266226600327070ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "contacts.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/contactsIcon.imageset/contacts.pdf000066400000000000000000001160471516266226600325410ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf contacts 2014-10-23T00:04:54+01:00 2014-10-23T00:04:54+01:00 2014-10-23T00:04:54+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 196 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAxAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FX//2Q== uuid:e09b4e88-b7b1-074a-83a7-ddfd9c66d44b xmp.did:adeef859-535d-48b0-b145-2b19703ae63d uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:3eb16e9b-4e89-4a68-9251-740e966b84a5 xmp.did:3eb16e9b-4e89-4a68-9251-740e966b84a5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:adeef859-535d-48b0-b145-2b19703ae63d 2014-10-23T00:04:52+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream H\Wˎ ++AI f5,d6g pT%'h˦d,o~~{:Ǒ:O~N&YU3.KOK|J&o V? 0s,Uڸk\哨@ Gһ.-X;pAq~yNg9_^`zkB& M!kk{7a#h;T/t4!C'(I"NlFxG\_za>T"\ ek02Opւ>@B8FzvVM8rxߠtImN73R&N{RYi1rh3}O-ygL`}7K|E,ٜ36n +e..(!sZfV0TFKGM-}Meٵwm,}hUƐkQ~`z2TK ,n $1MdW1WY$P!Fr0Ulh&n`VTPN #w<2޲PQCA,6EKb&=2'U!N=ϒ 5/(ߠE@ʔLgJ B,:[HBVKBqsdl]`_m_΁F0RJj/m.ЙMHJlxg'X2pz7nY2r/ӗ΀"@+!Yo9+M^R@yj[t)0#w#Uj9)$Nf! ]ޙziE3o64 f-hb5~I%7=S"u2by4_E9r @:x@"&سƙɚs=4H#'O(tJ{{d^7֌iy5߄yc.E!1&TD"4Q_k(PO439&٤V>Yܨ׵rԉ8p Hz?VZ9eHkҔV:I͓H ƍE&Fpk se%F9RP.lCf;́NYpCDt6%V4jPSeGXn#6n4)HU3&=)CFU& hC>5t.5N w qS:6 k:_g$*}Gx7 ķc}pU(qMF0R!O+Ubwh,9iqH7ep(<&X/{Pj@ "ۡ$+ӻjQĪd2Vw;VY6]MPwAeXC%.Pܷ6 Ub<ߦP,#L׆CaђR?^ .T= ‚`ȣBh*Y5 E_3HvH+ve #L1#(7"~i Zk=w, R bV'5x2:Ĥ?T%\F@/QKRƊ"[ tⱞ?xEo옡+ T"U"x7ޒrY˼e{I핗[Wx1,>r&R{}hwFmrsxcI:x endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000035957 00000 n 0000000000 00000 f 0000038994 00000 n 0000039292 00000 n 0000036008 00000 n 0000036257 00000 n 0000039180 00000 n 0000038275 00000 n 0000038433 00000 n 0000038481 00000 n 0000039064 00000 n 0000039095 00000 n 0000039315 00000 n trailer <<8848DBCC7DBA4089B6F54950BDFFC3B8>]>> startxref 39506 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/offlineIcon.imageset/000077500000000000000000000000001516266226600300235ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/offlineIcon.imageset/Contents.json000066400000000000000000000002351516266226600325130ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "transfer.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/offlineIcon.imageset/transfer.pdf000066400000000000000000001140751516266226600323520ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf transfer 2014-10-23T00:05:38+01:00 2014-10-23T00:05:38+01:00 2014-10-23T00:05:38+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== uuid:5c21857f-a95d-a14d-9ae4-b4079a183407 xmp.did:730d4081-7825-40b4-b57e-eafad033d735 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:be8ffa56-2a53-47a8-8c9a-a969c771b403 xmp.did:be8ffa56-2a53-47a8-8c9a-a969c771b403 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:730d4081-7825-40b4-b57e-eafad033d735 2014-10-23T00:05:36+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream HTSKN@ @ܱl)U* H-=$U=ގx8>=n0MXjh?) 0ƌQW0G , L)(3V\L%džj,D2;ŢY(Ѥ-c2ZcI&qn"I*δ9~" gȻr?v}a0/Jf` yymh U VuiGK:˲pv=BaxrOmwYƆLMdX;6Q칖 mJlE}8ha3řcd۶a =tV%P6r   endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000036552 00000 n 0000000000 00000 f 0000037992 00000 n 0000038290 00000 n 0000036603 00000 n 0000036850 00000 n 0000038178 00000 n 0000037273 00000 n 0000037431 00000 n 0000037479 00000 n 0000038062 00000 n 0000038093 00000 n 0000038313 00000 n trailer <<1648566EACEC4EB7B9D21804D45E5D49>]>> startxref 38504 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/settingsIcon.imageset/000077500000000000000000000000001516266226600302415ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/settingsIcon.imageset/Contents.json000066400000000000000000000002351516266226600327310ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "settings.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/Swift/MEGA/MEGA/Images.xcassets/TabBarIcons/settingsIcon.imageset/settings.pdf000066400000000000000000001150061516266226600325770ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf settings 2014-10-23T00:04:35+01:00 2014-10-23T00:04:35+01:00 2014-10-23T00:04:35+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== uuid:2ea2799c-0d92-5243-96ec-d5766ebf3a87 xmp.did:3eb16e9b-4e89-4a68-9251-740e966b84a5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:1b005aaf-9e27-47af-affe-ca73d93817bb xmp.did:1b005aaf-9e27-47af-affe-ca73d93817bb uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:3eb16e9b-4e89-4a68-9251-740e966b84a5 2014-10-23T00:04:33+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream H\Vn! mZuUQQͤRjѕ.Ǿǣ}xz GkL崵XGo5<|?FbO9},z5|j\)Tںbo'{toht:Oi^4C66Xǁ6Vq8Z/ z٧k,%ޠŮLk-=Y`鴮Тɮں9z^6ͷ֧GҌq~s㌖~,\wÉ=h r OyzGf i,r 4C%OCL&c`A'ԗB6߈.PVD|юx(vW$2(oRMPB&V@8J8$[NR12+x=HHˤ0-rc(@L* l<6|2mT˨FLa.mttu/S6;;vL6QR--t"1ػf6 m=svֽGۨ9 5yfdę}1o!3ѝu[|]4]yL5S>i&8zln;Qw-_9 p2ă~/ B"^(ʞ -:57j[!dҁT sOe[B2ѩ$hwSlwnQl84&ɶS6Ns*&zRDHٻ σ&2n%CVSxE΅??|{>l endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000036552 00000 n 0000000000 00000 f 0000038449 00000 n 0000038747 00000 n 0000036603 00000 n 0000036850 00000 n 0000038635 00000 n 0000037730 00000 n 0000037888 00000 n 0000037936 00000 n 0000038519 00000 n 0000038550 00000 n 0000038770 00000 n trailer <<0E258367E9554B39BDCE0E4F66292EF8>]>> startxref 38961 %%EOF sdk-10.11.0/examples/Swift/MEGA/MEGA/Info.plist000066400000000000000000000027271516266226600205630ustar00rootroot00000000000000 CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight sdk-10.11.0/examples/Swift/MEGA/MEGA/Login/000077500000000000000000000000001516266226600176535ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Login/LoginViewController.swift000066400000000000000000000143431516266226600247050ustar00rootroot00000000000000/** * @file LoginViewController.swift * @brief View controller that allow login in your MEGA account * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class LoginViewController: UIViewController, MEGARequestDelegate { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var loginProgressView: UIProgressView! @IBOutlet weak var loginButton: UIButton! @IBOutlet weak var informationLabel: UILabel! let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() emailTextField.becomeFirstResponder() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - IBActions @IBAction func touchUpInsideLogin(_ sender: UIButton) { if validateForm() { passwordTextField.resignFirstResponder() emailTextField.resignFirstResponder() megaapi.login(withEmail: emailTextField.text ?? "", password: passwordTextField.text ?? "", delegate: self) } } // MARK: - Validate methods func validateForm() -> Bool { if !validateEmail(emailTextField.text!) { let alertController = UIAlertController(title: "Invalid email", message: "Enter a valid email", preferredStyle: UIAlertController.Style.alert) alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default,handler: nil)) self.present(alertController, animated: true, completion: nil) emailTextField.becomeFirstResponder() return false } else if !validatePassword(passwordTextField.text!) { let alertController = UIAlertController(title: "Invalid password", message: "Enter a valid password", preferredStyle: UIAlertController.Style.alert) alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default,handler: nil)) self.present(alertController, animated: true, completion: nil) passwordTextField.becomeFirstResponder() return false } return true } func validatePassword(_ password : String) -> Bool { return (password.isEmpty) ? false : true } func validateEmail(_ email : String) -> Bool { let emailRegex = "(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" let resultPredicate : NSPredicate = NSPredicate(format: "SELF MATCHES[c] %@", emailRegex) return resultPredicate.evaluate(with: email) } // MARK: - MEGA Request delegate func onRequestStart(_ api: MEGASdk, request: MEGARequest) { if request.type == MEGARequestType.MEGARequestTypeLogin { loginButton.isEnabled = false loginProgressView.isHidden = false } } func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { loginButton.isEnabled = true loginProgressView.isHidden = true informationLabel.isHidden = true switch error.type { case MEGAErrorType.apiEArgs: let alertController = UIAlertController(title: "Error", message: "Email or password invalid", preferredStyle: UIAlertController.Style.alert) alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default,handler: nil)) self.present(alertController, animated: true, completion: nil) case MEGAErrorType.apiENoent: let alertController = UIAlertController(title: "Error", message: "User does not exist", preferredStyle: UIAlertController.Style.alert) alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default,handler: nil)) self.present(alertController, animated: true, completion: nil) default: break } return } switch request.type { case MEGARequestType.MEGARequestTypeLogin: let session = megaapi.dumpSession() SSKeychain.setPassword(session, forService: "MEGA", account: "session") api.fetchNodes(with: self) case MEGARequestType.MEGARequestTypeFetchNodes: self.performSegue(withIdentifier: "showCloudDrive", sender: self) default: break } } func onRequestUpdate(_ api: MEGASdk, request: MEGARequest) { if request.type == MEGARequestType.MEGARequestTypeFetchNodes { let progress = Float(request.transferredBytes / request.totalBytes) if progress > 0 && progress < 0.99 { informationLabel.text = "Fectching nodes" loginProgressView.setProgress(progress, animated: true) } else if progress > 0.99 || progress < 0 { informationLabel.text = "Preparing nodes" loginProgressView.setProgress(1.0, animated: true) } } } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Login/Main.storyboard000066400000000000000000000230731516266226600226560ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/Login/MainTabBarViewController.swift000066400000000000000000000031611516266226600255710ustar00rootroot00000000000000/** * @file MainTabBarController.swift * @brief Main tab bar of the app * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit class MainTabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() let viewControllerArray : NSMutableArray = NSMutableArray(capacity: 5) viewControllerArray.add(UIStoryboard(name: "Cloud", bundle: nil).instantiateInitialViewController()!) viewControllerArray.add(UIStoryboard(name: "Offline", bundle: nil).instantiateInitialViewController()!) viewControllerArray.add(UIStoryboard(name: "Contacts", bundle: nil).instantiateInitialViewController()!) viewControllerArray.add(UIStoryboard(name: "Settings", bundle: nil).instantiateInitialViewController()!) let viewControllers = viewControllerArray.copy() self.setViewControllers(viewControllers as? [UIViewController], animated: false) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } sdk-10.11.0/examples/Swift/MEGA/MEGA/MEGA-Bridging-Header.h000066400000000000000000000002021516266226600223700ustar00rootroot00000000000000#ifndef MEGA_MEGA_Bridging_Header_h #define MEGA_MEGA_Bridging_Header_h #import "SSKeychain.h" #import "SVProgressHUD.h" #endif sdk-10.11.0/examples/Swift/MEGA/MEGA/Offline/000077500000000000000000000000001516266226600201655ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Offline/Offline.storyboard000066400000000000000000000212241516266226600236620ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/Offline/OfflineTableViewController.swift000066400000000000000000000100011516266226600264640ustar00rootroot00000000000000/** * @file OfflineTableViewController.swift * @brief View controller that show offline files * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class OfflineTableViewController: UITableViewController, MEGATransferDelegate { var offlineDocuments = [MEGANode]() let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) reloadUI() megaapi.add(self) megaapi.retryPendingConnections() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) megaapi.remove(self) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func reloadUI() { offlineDocuments = [MEGANode]() let documentDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] if let directoryContent : Array = try? FileManager.default.contentsOfDirectory(atPath: documentDirectory) { for i in 0 ..< directoryContent.count { let filename: String = String(directoryContent[i] as NSString) if !((filename.lowercased() as NSString).pathExtension == "mega") { if let node = megaapi.node(forHandle: MEGASdk.handle(forBase64Handle: filename)) { offlineDocuments.append(node) } } } } tableView.reloadData() } // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return offlineDocuments.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "nodeCell", for: indexPath) as! NodeTableViewCell let node = offlineDocuments[indexPath.row] cell.nameLabel.text = node.name let thumbnailFilePath = Helper.pathForNode(node, path:FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: thumbnailFilePath) if !fileExists { cell.thumbnailImageView.image = Helper.imageForNode(node) } else { cell.thumbnailImageView.image = UIImage(named: thumbnailFilePath) } if node.isFile() { cell.subtitleLabel.text = ByteCountFormatter().string(fromByteCount: node.size?.int64Value ?? 0) } else { let files = megaapi.numberChildFiles(forParent: node) let folders = megaapi.numberChildFolders(forParent: node) cell.subtitleLabel.text = "\(folders) folders, \(files) files" cell.thumbnailImageView.image = UIImage(named: "folder") } return cell } // MARK: - MEGA Transfer delegate func onTransferFinish(_ api: MEGASdk, transfer: MEGATransfer, error: MEGAError) { reloadUI() } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Settings/000077500000000000000000000000001516266226600204035ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Settings/Settings.storyboard000066400000000000000000000253361516266226600243260ustar00rootroot00000000000000 sdk-10.11.0/examples/Swift/MEGA/MEGA/Settings/SettingsViewController.swift000066400000000000000000000131531516266226600261630ustar00rootroot00000000000000/** * @file SettingsViewController.swift * @brief View controller that show the settings of the user * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class SettingsViewController: UIViewController, MEGARequestDelegate { @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var emailLabel: UILabel! @IBOutlet weak var spaceUsedProgressView: UIProgressView! @IBOutlet weak var spaceUsedLabel: UILabel! @IBOutlet weak var accountTypeLabel: UILabel! let megaapi = (UIApplication.shared.delegate as! AppDelegate).megaapi override func viewDidLoad() { super.viewDidLoad() emailLabel.text = megaapi.myEmail setUserAvatar() megaapi.getAccountDetails(with: self) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setUserAvatar() { guard let user = megaapi.myUser else { return } let avatarFilePath = Helper.pathForUser(user, path: FileManager.SearchPathDirectory.cachesDirectory, directory: "thumbs") let fileExists = FileManager.default.fileExists(atPath: avatarFilePath) if !fileExists { megaapi.getAvatarUser(user, destinationFilePath: avatarFilePath, delegate: self) } else { avatarImageView.image = UIImage(named: avatarFilePath) avatarImageView.layer.cornerRadius = avatarImageView.frame.size.width / 2 avatarImageView.layer.masksToBounds = true } } // MARK: - IBAction @IBAction func logout(_ sender: UIBarButtonItem) { megaapi.logout(with: self) } // MARK: - MEGA Request delegate func onRequestStart(_ api: MEGASdk, request: MEGARequest) { if request.type == MEGARequestType.MEGARequestTypeLogout { SVProgressHUD.show(withStatus: "Logout") } } func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if error.type != MEGAErrorType.apiOk { return } switch request.type { case MEGARequestType.MEGARequestTypeLogout: SSKeychain.deletePassword(forService: "MEGA", account: "session") let thumbsURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] let thumbsDirectory = thumbsURL.appendingPathComponent("thumbs") var error : NSError? var success: Bool do { try FileManager.default.removeItem(atPath: thumbsDirectory.path) success = true } catch let error1 as NSError { error = error1 success = false } if (!success || error != nil) { print("(Cache) Remove file error: \(error!)") } let documentDirectory : String = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] do { try FileManager.default.removeItem(atPath: documentDirectory) success = true } catch let error1 as NSError { error = error1 success = false } if (!success || error != nil) { print("(Document) Remove file error: \(error!)") } SVProgressHUD.dismiss() let storyboard = UIStoryboard(name: "Main", bundle: nil) let lvc = storyboard.instantiateViewController(withIdentifier: "LoginViewControllerID") as! LoginViewController present(lvc, animated: true, completion: nil) case MEGARequestType.MEGARequestTypeGetAttrUser: setUserAvatar() case MEGARequestType.MEGARequestTypeAccountDetails: guard let megaAccountDetails = request.megaAccountDetails else { return } spaceUsedLabel.text = "\(ByteCountFormatter.string(fromByteCount: megaAccountDetails.storageUsed, countStyle: ByteCountFormatter.CountStyle.memory)) of \(ByteCountFormatter.string(fromByteCount: megaAccountDetails.storageMax, countStyle: ByteCountFormatter.CountStyle.memory))" let progress = Float(megaAccountDetails.storageUsed / megaAccountDetails.storageMax) spaceUsedProgressView.setProgress(progress, animated: true) switch megaAccountDetails.type { case MEGAAccountType.free: accountTypeLabel.text = "Account Type: FREE" case MEGAAccountType.proI: accountTypeLabel.text = "Account Type: PRO I" case MEGAAccountType.proII: accountTypeLabel.text = "Account Type: PRO II" case MEGAAccountType.proIII: accountTypeLabel.text = "Account Type: PRO III" default: break } default: break } } } sdk-10.11.0/examples/Swift/MEGA/MEGA/Utils/000077500000000000000000000000001516266226600177035ustar00rootroot00000000000000sdk-10.11.0/examples/Swift/MEGA/MEGA/Utils/Helper.swift000066400000000000000000000155331516266226600222070ustar00rootroot00000000000000/** * @file Helper.swift * @brief Helper * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ import UIKit import MEGASdk class Helper { struct workaround { static let icons : [String : String] = ["3ds":"3D", "3dm":"3D", "3fr":"raw", "3g2":"video", "3gp":"video", "7z":"compressed", "aac":"audio", "ac3":"audio", "accdb":"database", "aep":"aftereffects", "aet":"aftereffects", "ai":"illustrator", "aif":"audio", "aiff":"audio", "ait":"illustrator", "ans":"text", "apk":"executable", "app":"executable", "arw":"raw", "as":"fla_lang", "asc":"fla_lang", "ascii":"text", "asf":"video", "asp":"web_lang", "aspx":"web_lang", "asx":"playlist", "avi":"video", "bay":"raw", "bmp":"graphic", "bz2":"compressed", "c":"sourcecode", "cc":"sourcecode", "cdr":"vector", "cgi":"web_lang", "class":"java", "com":"executable", "cpp":"sourcecode", "cr2":"raw", "css":"web_data", "cxx":"sourcecode", "dcr":"raw", "db":"database", "dbf":"database", "dhtml":"html", "dll":"sourcecode", "dng":"raw", "doc":"word", "docx":"word", "dotx":"word", "dwg":"cad", "dwt":"dreamweaver", "dxf":"cad", "eps":"vector", "exe":"executable", "fff":"raw", "fla":"flash", "flac":"audio", "flv":"flash_video", "fnt":"font", "fon":"font", "gadget":"executable", "gif":"graphic", "gpx":"gis", "gsheet":"spreadsheet", "gz":"compressed", "h":"sourcecode", "hpp":"sourcecode", "htm":"html", "html":"html", "iff":"audio", "inc":"web_lang", "indd":"indesign", "jar":"java", "java":"java", "jpeg":"image", "jpg":"image", "js":"web_data", "key":"generic", "kml":"gis", "log":"text", "m":"sourcecode", "mm":"sourcecode", "m3u":"playlist", "m4a":"audio", "max":"3D", "mdb":"database", "mef":"raw", "mid":"midi", "midi":"midi", "mkv":"video", "mov":"video", "mp3":"audio", "mp4":"video", "mpeg":"video", "mpg":"video", "mrw":"raw", "msi":"executable", "nb":"spreadsheet", "numbers":"spreadsheet", "nef":"raw", "obj":"3D", "odp":"generic", "ods":"spreadsheet", "odt":"text", "ogv":"video", "otf":"font", "ots":"spreadsheet", "orf":"raw", "pages":"text", "pcast":"podcast", "pdb":"database", "pdf":"pdf", "pef":"raw", "php":"web_lang", "php3":"web_lang", "php4":"web_lang", "php5":"web_lang", "phtml":"web_lang", "pl":"web_lang", "pls":"playlist", "png":"graphic", "ppj":"premiere", "pps":"powerpoint", "ppt":"powerpoint", "pptx":"powerpoint", "prproj":"premiere", "psb":"photoshop", "psd":"photoshop", "py":"web_lang", "ra":"real_audio", "ram":"real_audio", "rar":"compressed", "rm":"real_audio", "rtf":"text", "rw2":"raw", "rwl":"raw", "sh":"sourcecode", "shtml":"web_data", "sitx":"compressed", "sql":"database", "srf":"raw", "srt":"video_subtitles", "stl":"3D", "svg":"vector", "svgz":"vector", "swf":"swf", "tar":"compressed", "tbz":"compressed", "tga":"graphic", "tgz":"compressed", "tif":"graphic", "tiff":"graphic", "torrent":"torrent", "ttf":"font", "txt":"text", "vcf":"vcard", "vob":"video_vob", "wav":"audio", "webm":"video", "wma":"audio", "wmv":"video", "wpd":"text", "wps":"word", "xhtml":"html", "xlr":"spreadsheet", "xls":"excel", "xlsx":"excel", "xlt":"excel", "xltm":"excel", "xml":"web_data", "zip":"compressed"] } class func imageForNode(_ node : MEGANode) -> UIImage { switch node.type { case MEGANodeType.folder: if node.isInShare() { return UIImage(named: "folder_shared")! } else { return UIImage(named: "folder")! } case MEGANodeType.file: let im = workaround.icons[(node.name!.lowercased() as NSString).pathExtension] if im != nil { return UIImage(named: im!)! } default: return UIImage(named: "generic")! } return UIImage(named: "generic")! } class func pathForNode(_ node : MEGANode, path : FileManager.SearchPathDirectory, directory : String) -> String { let destinationPath : String = NSSearchPathForDirectoriesInDomains(path, FileManager.SearchPathDomainMask.userDomainMask, true)[0] let filename = node.base64Handle let destinationFilePath = directory == "" ? (destinationPath as NSString).appendingPathComponent(filename!) : ((destinationPath as NSString).appendingPathComponent(directory) as NSString).appendingPathComponent(filename!) return destinationFilePath } class func pathForNode(_ node : MEGANode, path : FileManager.SearchPathDirectory) -> String { return pathForNode(node, path: path, directory: "") } class func pathForUser(_ user : MEGAUser, path : FileManager.SearchPathDirectory, directory : String) -> String { let destinationPath : String = NSSearchPathForDirectoriesInDomains(path, FileManager.SearchPathDomainMask.userDomainMask, true)[0] let filename = user.email let destinationFilePath = directory == "" ? (destinationPath as NSString).appendingPathComponent(filename!) : ((destinationPath as NSString).appendingPathComponent(directory) as NSString).appendingPathComponent(filename!) return destinationFilePath } } sdk-10.11.0/examples/Swift/README.md000066400000000000000000000020461516266226600166420ustar00rootroot00000000000000# iOS Swift Example app Xcode project for developing a MEGA app for iOS. Target OS: iOS 8.0 Demo application that allows: - Login into user account - List folders and files stored into the user account - Navigate along the folders structure (forward and backward) - Download files - Delete, rename and export nodes - View contacts - Logout ## How to build and run the project: To build and run the project, follow theses steps: 1. Download or clone the whole SDK 2. Download the prebuilt third party dependencies from this link: https://mega.nz/#!gN9w0I7Q!-OPUWBoEnvXeZWkkC5oUho2MFm_49nwBwN6q07sxKII 3. Uncompress the content and move `include`and `lib`to the directory `sdk/bindings/ios/3rdparty` 4. Go to `sdk/examples/Swift` and open `MEGAWS.xcworkspace` 5. Make sure the `MEGA` target is selected 6. Build and run the application If you want to build the third party dependencies by yourself: open a terminal in the directory `sdk/bindings/ios/3rdparty`. Run `sh build-all.sh` (Wait until the process ends, it will take some minutes ~20) sdk-10.11.0/examples/SwiftUI/000077500000000000000000000000001516266226600156175ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/.gitignore000066400000000000000000000005301516266226600176050ustar00rootroot00000000000000## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xccheckout *.xcscmblueprint ### Xcode Patch ### *.xcodeproj/* !*.xcodeproj/project.pbxproj !*.xcodeproj/xcshareddata/ !*.xcworkspace/contents.xcworkspacedata /*.gcnosdk-10.11.0/examples/SwiftUI/ExampleApp/000077500000000000000000000000001516266226600176535ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/000077500000000000000000000000001516266226600237035ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.pbxproj000066400000000000000000000540311516266226600267620ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 60; objects = { /* Begin PBXBuildFile section */ A85CEDA52ACEEDAD00190183 /* NodeListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85CEDA42ACEEDAD00190183 /* NodeListView.swift */; }; A85CEDA72ACEEDE600190183 /* NodeListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85CEDA62ACEEDE600190183 /* NodeListViewModel.swift */; }; A87266AB2ACF214D00090A5D /* NodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A87266AA2ACF214D00090A5D /* NodeView.swift */; }; A87266AD2ACF218300090A5D /* NodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A87266AC2ACF218300090A5D /* NodeViewModel.swift */; }; A89C7F462B7A8D1B00F4BBEB /* MEGADesignToken in Frameworks */ = {isa = PBXBuildFile; productRef = A89C7F452B7A8D1B00F4BBEB /* MEGADesignToken */; }; A89C7F482B7A8D1B00F4BBEB /* TokenCodegenGenerator in Frameworks */ = {isa = PBXBuildFile; productRef = A89C7F472B7A8D1B00F4BBEB /* TokenCodegenGenerator */; }; A8D756482ACEB14200EA3DC6 /* ExampleAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D756472ACEB14200EA3DC6 /* ExampleAppApp.swift */; }; A8D7564C2ACEB14500EA3DC6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A8D7564B2ACEB14500EA3DC6 /* Assets.xcassets */; }; A8D756502ACEB14500EA3DC6 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A8D7564F2ACEB14500EA3DC6 /* Preview Assets.xcassets */; }; A8D756592ACEB32200EA3DC6 /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A8D756582ACEB32200EA3DC6 /* MEGASdk */; }; A8D7565E2ACEB37100EA3DC6 /* MEGASdkRepo in Frameworks */ = {isa = PBXBuildFile; productRef = A8D7565D2ACEB37100EA3DC6 /* MEGASdkRepo */; }; A8D756602ACEB38400EA3DC6 /* MEGASdkRepo in Resources */ = {isa = PBXBuildFile; fileRef = A8D7565F2ACEB38400EA3DC6 /* MEGASdkRepo */; }; A8D756622ACEB5ED00EA3DC6 /* MEGADomain in Resources */ = {isa = PBXBuildFile; fileRef = A8D756612ACEB5ED00EA3DC6 /* MEGADomain */; }; A8D756662ACEB7F800EA3DC6 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D756642ACEB7F800EA3DC6 /* LoginViewModel.swift */; }; A8D756672ACEB7F800EA3DC6 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D756652ACEB7F800EA3DC6 /* LoginView.swift */; }; A8D7566A2ACEC8D600EA3DC6 /* Dependency.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D756692ACEC8D600EA3DC6 /* Dependency.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ A85CEDA42ACEEDAD00190183 /* NodeListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListView.swift; sourceTree = ""; }; A85CEDA62ACEEDE600190183 /* NodeListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListViewModel.swift; sourceTree = ""; }; A87266AA2ACF214D00090A5D /* NodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeView.swift; sourceTree = ""; }; A87266AC2ACF218300090A5D /* NodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeViewModel.swift; sourceTree = ""; }; A8D756442ACEB14200EA3DC6 /* ExampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; A8D756472ACEB14200EA3DC6 /* ExampleAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppApp.swift; sourceTree = ""; }; A8D7564B2ACEB14500EA3DC6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; A8D7564D2ACEB14500EA3DC6 /* ExampleApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ExampleApp.entitlements; sourceTree = ""; }; A8D7564F2ACEB14500EA3DC6 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; A8D7565F2ACEB38400EA3DC6 /* MEGASdkRepo */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGASdkRepo; sourceTree = ""; }; A8D756612ACEB5ED00EA3DC6 /* MEGADomain */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MEGADomain; sourceTree = ""; }; A8D756642ACEB7F800EA3DC6 /* LoginViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; A8D756652ACEB7F800EA3DC6 /* LoginView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; A8D756692ACEC8D600EA3DC6 /* Dependency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependency.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ A8D756412ACEB14200EA3DC6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A89C7F462B7A8D1B00F4BBEB /* MEGADesignToken in Frameworks */, A8D756592ACEB32200EA3DC6 /* MEGASdk in Frameworks */, A89C7F482B7A8D1B00F4BBEB /* TokenCodegenGenerator in Frameworks */, A8D7565E2ACEB37100EA3DC6 /* MEGASdkRepo in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ A85CED9F2ACEEC5300190183 /* Nodes */ = { isa = PBXGroup; children = ( A85CEDA42ACEEDAD00190183 /* NodeListView.swift */, A85CEDA62ACEEDE600190183 /* NodeListViewModel.swift */, A87266AA2ACF214D00090A5D /* NodeView.swift */, A87266AC2ACF218300090A5D /* NodeViewModel.swift */, ); path = Nodes; sourceTree = ""; }; A8D7563B2ACEB14200EA3DC6 = { isa = PBXGroup; children = ( A8D756462ACEB14200EA3DC6 /* ExampleApp */, A8D756452ACEB14200EA3DC6 /* Products */, ); sourceTree = ""; }; A8D756452ACEB14200EA3DC6 /* Products */ = { isa = PBXGroup; children = ( A8D756442ACEB14200EA3DC6 /* ExampleApp.app */, ); name = Products; sourceTree = ""; }; A8D756462ACEB14200EA3DC6 /* ExampleApp */ = { isa = PBXGroup; children = ( A8D756562ACEB18200EA3DC6 /* Modules */, A8D756472ACEB14200EA3DC6 /* ExampleAppApp.swift */, A8D7564B2ACEB14500EA3DC6 /* Assets.xcassets */, A8D7564D2ACEB14500EA3DC6 /* ExampleApp.entitlements */, A8D7564E2ACEB14500EA3DC6 /* Preview Content */, ); path = ExampleApp; sourceTree = ""; }; A8D7564E2ACEB14500EA3DC6 /* Preview Content */ = { isa = PBXGroup; children = ( A8D7564F2ACEB14500EA3DC6 /* Preview Assets.xcassets */, ); path = "Preview Content"; sourceTree = ""; }; A8D756562ACEB18200EA3DC6 /* Modules */ = { isa = PBXGroup; children = ( A8D7566B2ACED11C00EA3DC6 /* Presentation */, A8D756682ACEC8C000EA3DC6 /* Dependencies */, A8D7565B2ACEB33A00EA3DC6 /* Domain */, A8D7565A2ACEB33300EA3DC6 /* Repository */, ); path = Modules; sourceTree = ""; }; A8D7565A2ACEB33300EA3DC6 /* Repository */ = { isa = PBXGroup; children = ( A8D7565F2ACEB38400EA3DC6 /* MEGASdkRepo */, ); path = Repository; sourceTree = ""; }; A8D7565B2ACEB33A00EA3DC6 /* Domain */ = { isa = PBXGroup; children = ( A8D756612ACEB5ED00EA3DC6 /* MEGADomain */, ); path = Domain; sourceTree = ""; }; A8D756632ACEB7D300EA3DC6 /* Login */ = { isa = PBXGroup; children = ( A8D756652ACEB7F800EA3DC6 /* LoginView.swift */, A8D756642ACEB7F800EA3DC6 /* LoginViewModel.swift */, ); path = Login; sourceTree = ""; }; A8D756682ACEC8C000EA3DC6 /* Dependencies */ = { isa = PBXGroup; children = ( A8D756692ACEC8D600EA3DC6 /* Dependency.swift */, ); path = Dependencies; sourceTree = ""; }; A8D7566B2ACED11C00EA3DC6 /* Presentation */ = { isa = PBXGroup; children = ( A85CED9F2ACEEC5300190183 /* Nodes */, A8D756632ACEB7D300EA3DC6 /* Login */, ); path = Presentation; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ A8D756432ACEB14200EA3DC6 /* ExampleApp */ = { isa = PBXNativeTarget; buildConfigurationList = A8D756532ACEB14500EA3DC6 /* Build configuration list for PBXNativeTarget "ExampleApp" */; buildPhases = ( A8D756402ACEB14200EA3DC6 /* Sources */, A8D756412ACEB14200EA3DC6 /* Frameworks */, A8D756422ACEB14200EA3DC6 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ExampleApp; packageProductDependencies = ( A8D756582ACEB32200EA3DC6 /* MEGASdk */, A8D7565D2ACEB37100EA3DC6 /* MEGASdkRepo */, A89C7F452B7A8D1B00F4BBEB /* MEGADesignToken */, A89C7F472B7A8D1B00F4BBEB /* TokenCodegenGenerator */, ); productName = ExampleApp; productReference = A8D756442ACEB14200EA3DC6 /* ExampleApp.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ A8D7563C2ACEB14200EA3DC6 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; LastUpgradeCheck = 1520; TargetAttributes = { A8D756432ACEB14200EA3DC6 = { CreatedOnToolsVersion = 15.0; }; }; }; buildConfigurationList = A8D7563F2ACEB14200EA3DC6 /* Build configuration list for PBXProject "ExampleApp" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = A8D7563B2ACEB14200EA3DC6; packageReferences = ( A8D756572ACEB32200EA3DC6 /* XCLocalSwiftPackageReference "../../.." */, A89C7F442B7A8D1B00F4BBEB /* XCRemoteSwiftPackageReference "MEGADesignToken" */, ); productRefGroup = A8D756452ACEB14200EA3DC6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( A8D756432ACEB14200EA3DC6 /* ExampleApp */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ A8D756422ACEB14200EA3DC6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( A8D756622ACEB5ED00EA3DC6 /* MEGADomain in Resources */, A8D756602ACEB38400EA3DC6 /* MEGASdkRepo in Resources */, A8D756502ACEB14500EA3DC6 /* Preview Assets.xcassets in Resources */, A8D7564C2ACEB14500EA3DC6 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ A8D756402ACEB14200EA3DC6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( A87266AD2ACF218300090A5D /* NodeViewModel.swift in Sources */, A85CEDA72ACEEDE600190183 /* NodeListViewModel.swift in Sources */, A8D756482ACEB14200EA3DC6 /* ExampleAppApp.swift in Sources */, A8D756672ACEB7F800EA3DC6 /* LoginView.swift in Sources */, A8D7566A2ACEC8D600EA3DC6 /* Dependency.swift in Sources */, A87266AB2ACF214D00090A5D /* NodeView.swift in Sources */, A8D756662ACEB7F800EA3DC6 /* LoginViewModel.swift in Sources */, A85CEDA52ACEEDAD00190183 /* NodeListView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ A8D756512ACEB14500EA3DC6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; A8D756522ACEB14500EA3DC6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; }; name = Release; }; A8D756542ACEB14500EA3DC6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = ExampleApp/ExampleApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"ExampleApp/Preview Content\""; DEVELOPMENT_TEAM = T9RH74Y7L9; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = nz.mega.ExampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; A8D756552ACEB14500EA3DC6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = ExampleApp/ExampleApp.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"ExampleApp/Preview Content\""; DEVELOPMENT_TEAM = T9RH74Y7L9; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = nz.mega.ExampleApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ A8D7563F2ACEB14200EA3DC6 /* Build configuration list for PBXProject "ExampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( A8D756512ACEB14500EA3DC6 /* Debug */, A8D756522ACEB14500EA3DC6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; A8D756532ACEB14500EA3DC6 /* Build configuration list for PBXNativeTarget "ExampleApp" */ = { isa = XCConfigurationList; buildConfigurations = ( A8D756542ACEB14500EA3DC6 /* Debug */, A8D756552ACEB14500EA3DC6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ A8D756572ACEB32200EA3DC6 /* XCLocalSwiftPackageReference "../../.." */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../..; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */ A89C7F442B7A8D1B00F4BBEB /* XCRemoteSwiftPackageReference "MEGADesignToken" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/meganz/MEGADesignToken"; requirement = { branch = main; kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ A89C7F452B7A8D1B00F4BBEB /* MEGADesignToken */ = { isa = XCSwiftPackageProductDependency; package = A89C7F442B7A8D1B00F4BBEB /* XCRemoteSwiftPackageReference "MEGADesignToken" */; productName = MEGADesignToken; }; A89C7F472B7A8D1B00F4BBEB /* TokenCodegenGenerator */ = { isa = XCSwiftPackageProductDependency; package = A89C7F442B7A8D1B00F4BBEB /* XCRemoteSwiftPackageReference "MEGADesignToken" */; productName = TokenCodegenGenerator; }; A8D756582ACEB32200EA3DC6 /* MEGASdk */ = { isa = XCSwiftPackageProductDependency; productName = MEGASdk; }; A8D7565D2ACEB37100EA3DC6 /* MEGASdkRepo */ = { isa = XCSwiftPackageProductDependency; productName = MEGASdkRepo; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = A8D7563C2ACEB14200EA3DC6 /* Project object */; } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/000077500000000000000000000000001516266226600277015ustar00rootroot00000000000000contents.xcworkspacedata000066400000000000000000000002071516266226600345630ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/000077500000000000000000000000001516266226600323345ustar00rootroot00000000000000IDEWorkspaceChecks.plist000066400000000000000000000003561516266226600367370ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning swiftpm/000077500000000000000000000000001516266226600337465ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddataPackage.resolved000066400000000000000000000010761516266226600370520ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm{ "pins" : [ { "identity" : "megadesigntoken", "kind" : "remoteSourceControl", "location" : "https://github.com/meganz/MEGADesignToken", "state" : { "branch" : "main", "revision" : "11ee50eed976c628aa5c8f2a0bb54e6cc28887a9" } }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-syntax", "state" : { "revision" : "74203046135342e4a4a627476dd6caf8b28fe11b", "version" : "509.0.0" } } ], "version" : 2 } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/xcshareddata/000077500000000000000000000000001516266226600263365ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/xcshareddata/xcschemes/000077500000000000000000000000001516266226600303205ustar00rootroot00000000000000ExampleApp.xcscheme000066400000000000000000000054651516266226600340300ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp.xcodeproj/xcshareddata/xcschemes sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/000077500000000000000000000000001516266226600217075ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/000077500000000000000000000000001516266226600250055ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/AccentColor.colorset/000077500000000000000000000000001516266226600310325ustar00rootroot00000000000000Contents.json000066400000000000000000000001731516266226600334440ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/AccentColor.colorset{ "colors" : [ { "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/000077500000000000000000000000001516266226600305025ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000017471516266226600332030ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" }, { "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Assets.xcassets/Contents.json000066400000000000000000000000771516266226600275010ustar00rootroot00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/ExampleApp.entitlements000066400000000000000000000005551516266226600264050ustar00rootroot00000000000000 com.apple.security.app-sandbox com.apple.security.files.user-selected.read-only com.apple.security.network.client sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/ExampleAppApp.swift000066400000000000000000000002621516266226600254620ustar00rootroot00000000000000 import SwiftUI @main struct ExampleAppApp: App { var body: some Scene { WindowGroup { LoginView(viewModel: Dependency.loginViewModel) } } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/000077500000000000000000000000001516266226600233175ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Dependencies/000077500000000000000000000000001516266226600257055ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Dependencies/Dependency.swift000066400000000000000000000005151516266226600310420ustar00rootroot00000000000000import MEGADomain import MEGASdkRepo enum Dependency { static var loginViewModel: LoginViewModel { LoginViewModel(authUseCase: AuthUseCase(repo: AuthRepository.newRepo)) } static var nodeListViewModel: NodeListViewModel { NodeListViewModel(nodeUseCase: NodeUseCase(repo: NodeRepository.newRepo)) } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/000077500000000000000000000000001516266226600245265ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/000077500000000000000000000000001516266226600263675ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.gitignore000066400000000000000000000002371516266226600303610ustar00rootroot00000000000000.DS_Store /.build /Packages xcuserdata/ DerivedData/ .swiftpm/configuration/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.swiftpm/000077500000000000000000000000001516266226600301365ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.swiftpm/xcode/000077500000000000000000000000001516266226600312405ustar00rootroot00000000000000package.xcworkspace/000077500000000000000000000000001516266226600351045ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.swiftpm/xcodexcshareddata/000077500000000000000000000000001516266226600375375ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.swiftpm/xcode/package.xcworkspaceIDEWorkspaceChecks.plist000066400000000000000000000003561516266226600442210ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/.swiftpm/xcode/package.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Package.swift000066400000000000000000000005321516266226600310000ustar00rootroot00000000000000// swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MEGADomain", platforms: [ .macOS(.v10_15), .iOS(.v14) ], products: [ .library( name: "MEGADomain", targets: ["MEGADomain"]), ], targets: [ .target( name: "MEGADomain") ] ) sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/000077500000000000000000000000001516266226600300125ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/000077500000000000000000000000001516266226600316535ustar00rootroot00000000000000Architecture/000077500000000000000000000000001516266226600342165ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomainRepositoryProtocol.swift000066400000000000000000000001141516266226600411710ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/Architecturepublic protocol RepositoryProtocol { static var newRepo: Self { get } } Entity/000077500000000000000000000000001516266226600330505ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomainNode/000077500000000000000000000000001516266226600337355ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/EntityNodeEntity.swift000066400000000000000000000012761516266226600371030ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/Entity/Nodeimport Foundation public typealias HandleEntity = UInt64 public struct NodeEntity: Sendable { public let name: String public let handle: HandleEntity public let size: UInt64 public let creationTime: Date public let modificationTime: Date public init(name: String, handle: HandleEntity, size: UInt64, creationTime: Date, modificationTime: Date) { self.name = name self.handle = handle self.size = size self.creationTime = creationTime self.modificationTime = modificationTime } } extension NodeEntity: Identifiable { public var id: HandleEntity { handle } } RepositoryProtocol/000077500000000000000000000000001516266226600354755ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomainAuthentication/000077500000000000000000000000001516266226600404545ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocolAuthRepositoryProtocol.swift000066400000000000000000000002621516266226600462550ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Authenticationimport Foundation public protocol AuthRepositoryProtocol: RepositoryProtocol { func login(email: String, password: String) async throws func fetchNodes() async throws } Node/000077500000000000000000000000001516266226600363625ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocolNodeRepositoryProtocol.swift000066400000000000000000000001751516266226600441520ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/RepositoryProtocol/Nodeimport Foundation public protocol NodeRepositoryProtocol: RepositoryProtocol { func childrenForRoot() -> [NodeEntity] } UseCase/000077500000000000000000000000001516266226600331245ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomainAuthentication/000077500000000000000000000000001516266226600361035ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCaseAuthUseCase.swift000066400000000000000000000011041516266226600413270ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Authenticationimport Foundation public protocol AuthUseCaseProtocol { func login(email: String, password: String) async throws func fetchNodes() async throws } public struct AuthUseCase: AuthUseCaseProtocol { private let repo: any AuthRepositoryProtocol public init(repo: some AuthRepositoryProtocol) { self.repo = repo } public func login(email: String, password: String) async throws { try await repo.login(email: email, password: password) } public func fetchNodes() async throws { try await repo.fetchNodes() } } Node/000077500000000000000000000000001516266226600340115ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCaseNodeUseCase.swift000066400000000000000000000006071516266226600372300ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Domain/MEGADomain/Sources/MEGADomain/UseCase/Nodeimport Foundation public protocol NodeUseCaseProtocol { func childrenForRoot() -> [NodeEntity] } public struct NodeUseCase: NodeUseCaseProtocol { private let repo: any NodeRepositoryProtocol public init(repo: some NodeRepositoryProtocol) { self.repo = repo } public func childrenForRoot() -> [NodeEntity] { repo.childrenForRoot() } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/000077500000000000000000000000001516266226600257725ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Login/000077500000000000000000000000001516266226600270425ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Login/LoginView.swift000066400000000000000000000031511516266226600320230ustar00rootroot00000000000000import SwiftUI import MEGADesignToken struct LoginView: View { @Bindable var viewModel: LoginViewModel @State private var showNodes = false var body: some View { NavigationStack { VStack { Spacer() Form(content: { TextField("Email", text: $viewModel.email, prompt: Text("Email")) .disableAutocorrection(true) #if os(iOS) .autocapitalization(.none) .keyboardType(.emailAddress) #endif SecureField("Password", text: $viewModel.password, prompt: Text("Password")) }) Spacer() Text(viewModel.message) .opacity(viewModel.message.isEmpty ? 0.0 : 1.0) Spacer() Button("Login", action: { viewModel.message = "Log in..." Task { do { try await viewModel.login() try await viewModel.fetchNodes() showNodes = true } catch { viewModel.message = "Invalid email or password" } } }) } .navigationDestination(isPresented: $showNodes) { NodeListView() } .padding(30) .background(TokenColors.Background.page.swiftUI) } } } #Preview { LoginView(viewModel: Dependency.loginViewModel) } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Login/LoginViewModel.swift000066400000000000000000000010051516266226600330000ustar00rootroot00000000000000import Foundation import MEGADomain @Observable class LoginViewModel { private var authUseCase: any AuthUseCaseProtocol var email: String = "" var password: String = "" var message: String = "" init(authUseCase: some AuthUseCaseProtocol) { self.authUseCase = authUseCase } func login() async throws { try await authUseCase.login(email: email, password: password) } func fetchNodes() async throws { try await authUseCase.fetchNodes() } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Nodes/000077500000000000000000000000001516266226600270425ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Nodes/NodeListView.swift000066400000000000000000000012171516266226600324750ustar00rootroot00000000000000 import SwiftUI struct NodeListView: View { var viewModel = Dependency.nodeListViewModel var body: some View { NavigationStack { if viewModel.nodes.isEmpty { Spacer() Button("Load nodes", action: { viewModel.childrenForRootNode() }) Spacer() } else { List(viewModel.nodes) { node in NodeView(viewModel: NodeViewModel(node: node)) } } } .navigationTitle("Cloud Drive") .navigationBarBackButtonHidden() } } #Preview { NodeListView() } NodeListViewModel.swift000066400000000000000000000005411516266226600333760ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Nodesimport Foundation import MEGADomain @Observable class NodeListViewModel { private var nodeUseCase: any NodeUseCaseProtocol var nodes: [NodeEntity] = [] init(nodeUseCase: some NodeUseCaseProtocol) { self.nodeUseCase = nodeUseCase } func childrenForRootNode() { nodes = nodeUseCase.childrenForRoot() } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Nodes/NodeView.swift000066400000000000000000000013671516266226600316470ustar00rootroot00000000000000import SwiftUI import MEGADomain struct NodeView: View { var viewModel: NodeViewModel var body: some View { VStack(alignment: .leading) { Text(viewModel.name) .font(.headline) Text(viewModel.creationString) .font(.subheadline) } .frame(maxWidth: .infinity, alignment: .leading) } } #Preview { NodeView(viewModel: NodeViewModel(node: NodeEntity(name: "example", handle: 1, size: 1024, creationTime: Date.now, modificationTime: .now))) } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Presentation/Nodes/NodeViewModel.swift000066400000000000000000000004711516266226600326230ustar00rootroot00000000000000import Foundation import MEGADomain @Observable class NodeViewModel { private var node: NodeEntity var name: String var creationString: String init(node: NodeEntity) { self.node = node self.name = node.name self.creationString = node.creationTime.description } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/000077500000000000000000000000001516266226600254765ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/000077500000000000000000000000001516266226600274775ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.gitignore000066400000000000000000000002371516266226600314710ustar00rootroot00000000000000.DS_Store /.build /Packages xcuserdata/ DerivedData/ .swiftpm/configuration/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.swiftpm/000077500000000000000000000000001516266226600312465ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.swiftpm/xcode/000077500000000000000000000000001516266226600323505ustar00rootroot00000000000000package.xcworkspace/000077500000000000000000000000001516266226600362145ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.swiftpm/xcodexcshareddata/000077500000000000000000000000001516266226600406475ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.swiftpm/xcode/package.xcworkspaceIDEWorkspaceChecks.plist000066400000000000000000000003561516266226600453310ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/.swiftpm/xcode/package.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Package.swift000066400000000000000000000010431516266226600321060ustar00rootroot00000000000000// swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MEGASdkRepo", platforms: [ .macOS(.v10_15), .iOS(.v15) ], products: [ .library( name: "MEGASdkRepo", targets: ["MEGASdkRepo"] ), ], dependencies: [ .package(path: "../../../../../../../MEGASdk"), .package(path: "../../MEGADomain") ], targets: [ .target( name: "MEGASdkRepo", dependencies: ["MEGASdk", "MEGADomain"] ) ] ) sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/000077500000000000000000000000001516266226600311225ustar00rootroot00000000000000MEGASdkRepo/000077500000000000000000000000001516266226600330445ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/SourcesMEGASdk+SharedInstance.swift000066400000000000000000000012131516266226600401610ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepoimport MEGADomain import MEGASdk public extension MEGASdk { /// MEGASdk instance used for the user logged account static let sharedSdk: MEGASdk = { MEGASdk.setLogLevel(.max) MEGASdk.setLogToConsole(true) let baseURL: URL? = try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let sdk = MEGASdk(appKey:"", userAgent: nil, basePath: baseURL?.path) sdk?.setRLimitFileCount(20000) sdk?.retrySSLErrors(true) guard let sdk else { fatalError("Can't create shared sdk") } return sdk }() } Mappers/000077500000000000000000000000001516266226600344535ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepoNodeEntity/000077500000000000000000000000001516266226600365355ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/MappersNodeEntity+Mapper.swift000066400000000000000000000016311516266226600431160ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/Mappers/NodeEntityimport Foundation import MEGADomain import MEGASdk extension NodeEntity { public func toMEGANode(in sdk: MEGASdk) -> MEGANode? { sdk.node(forHandle: handle) } } extension Array where Element == NodeEntity { public func toMEGANodes(in sdk: MEGASdk) -> [MEGANode] { compactMap { $0.toMEGANode(in: sdk) } } } extension Array where Element: MEGANode { public func toNodeEntities() -> [NodeEntity] { map { $0.toNodeEntity() } } } extension MEGANode { public func toNodeEntity() -> NodeEntity { NodeEntity(node: self) } } fileprivate extension NodeEntity { init(node: MEGANode) { self.init( name : node.name ?? "", handle : node.handle, size : node.size?.uint64Value ?? 0, creationTime : node.creationTime ?? Date(), modificationTime : node.modificationTime ?? Date() ) } } Repository/000077500000000000000000000000001516266226600352235ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepoAuthentication/000077500000000000000000000000001516266226600402025ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/RepositoryAuthRepository.swift000066400000000000000000000023301516266226600442570ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/Repository/Authenticationimport Foundation import MEGADomain import MEGASdk public struct AuthRepository: AuthRepositoryProtocol { public static var newRepo: AuthRepository { AuthRepository(sdk: MEGASdk.sharedSdk) } private let sdk: MEGASdk public init(sdk: MEGASdk) { self.sdk = sdk } public func login(email: String, password: String) async throws { try await withCheckedThrowingContinuation { continuation in sdk.login(withEmail: email, password: password, delegate: RequestDelegate { result in switch result { case .success: continuation.resume() case .failure(let error): continuation.resume(throwing: error) } }) } } public func fetchNodes() async throws { try await withCheckedThrowingContinuation { continuation in sdk.fetchNodes(with: RequestDelegate { result in switch result { case .success: continuation.resume() case .failure(let error): continuation.resume(throwing: error) } }) } } } Node/000077500000000000000000000000001516266226600361105ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/RepositoryNodeRepository.swift000066400000000000000000000010201516266226600421440ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/Repository/Nodeimport Foundation import MEGADomain import MEGASdk public struct NodeRepository: NodeRepositoryProtocol { public static var newRepo: NodeRepository { NodeRepository(sdk: MEGASdk.sharedSdk) } private let sdk: MEGASdk public init(sdk: MEGASdk) { self.sdk = sdk } public func childrenForRoot() -> [NodeEntity] { guard let parent = sdk.rootNode else { return [] } let nodeList = sdk.children(forParent: parent) return nodeList.toNodeEntities() } } SDK/000077500000000000000000000000001516266226600334655ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepoModelMapping/000077500000000000000000000000001516266226600360415ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/SDKMEGAError+Mapper.swift000066400000000000000000000000561516266226600420630ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/SDK/ModelMappingimport MEGASdk extension MEGAError: Error {} Node/000077500000000000000000000000001516266226600367265ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/SDK/ModelMappingNodeListEntity+Mapping.swift000066400000000000000000000003521516266226600443110ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Modules/Repository/MEGASdkRepo/Sources/MEGASdkRepo/SDK/ModelMapping/Nodeimport MEGADomain import MEGASdk extension MEGANodeList { public func toNodeEntities() -> [NodeEntity] { guard size > 0 else { return [] } return (0..) -> Void public class RequestDelegate: NSObject, MEGARequestDelegate { let completion: MEGARequestCompletion private let successCodes: [MEGAErrorType] public init(successCodes: [MEGAErrorType] = [.apiOk], completion: @escaping MEGARequestCompletion) { self.successCodes = successCodes self.completion = completion super.init() } public func onRequestFinish(_ api: MEGASdk, request: MEGARequest, error: MEGAError) { if successCodes.contains(error.type) { self.completion(.success(request)) } else { self.completion(.failure(error)) } } } sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Preview Content/000077500000000000000000000000001516266226600247235ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Preview Content/Preview Assets.xcassets/000077500000000000000000000000001516266226600314235ustar00rootroot00000000000000Contents.json000066400000000000000000000000771516266226600340400ustar00rootroot00000000000000sdk-10.11.0/examples/SwiftUI/ExampleApp/ExampleApp/Preview Content/Preview Assets.xcassets{ "info" : { "author" : "xcode", "version" : 1 } } sdk-10.11.0/examples/android/000077500000000000000000000000001516266226600157055ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/000077500000000000000000000000001516266226600177415ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/.gitignore000066400000000000000000000005741516266226600217370ustar00rootroot00000000000000/local.properties /build/ /app/build/ /app/src/main/obj/ /app/src/main/jniLibs/ /app/src/main/libs/ /app/src/main/java/nz/mega/sdk/ /app/src/main/jni/build-dir ### Android Studio files ### *.iml .idea/ # User-specific stuff: # (covered by .idea/ above) # Sensitive or high-churn files: # (covered by .idea/ above) # Gradle: .gradle ## File-based project format: *.ipr *.iws sdk-10.11.0/examples/android/ExampleApp/README.md000066400000000000000000000053001516266226600212160ustar00rootroot00000000000000# Android Example App for AndroidStudio AndroidStudio project for developing a MEGA app for Android ## Setup development environment * [Android Studio and SDK tools](https://developer.android.com/studio) * [Android NDK](https://developer.android.com/ndk/downloads): this is only required if you want to build the native libraries and Java bindings by yourself. **Required version of NDK**: 27.1.12297006 or newer. ## Build the third party libraries and the MEGA SDK * Ensure that you have installed `git`, `swig`, `autotools` (`automake`, `autoconf`), `libtool` and other common tools (`wget`, `curl`, `unzip`, `tar`, ...). * Clone the MEGA SDK repository to a folder in your computer: ``` git clone https://github.com/meganz/sdk.git ``` ### Configure the environment: To build the third party libraries and the MEGA SDK to create the `libmega.so` library, you'll have to set up some environment variables or set symbolic links as follows: * **NDK_ROOT**: Export `NDK_ROOT` environment variable or create a symbolic link at `${HOME}/android-ndk` pointing to your Android NDK installation path. * **ANDROID_HOME**: Export `ANDROID_HOME` environment variable or create a symbolic link at `${HOME}/android-sdk` pointing to your Android SDK installation path. * **JAVA_HOME**: Export `JAVA_HOME` environment variable or create a symbolic link at `${HOME}/android-java` pointing to your Java installation path. Example: ``` export NDK_ROOT=/path/to/ndk export ANDROID_HOME=/path/to/sdk export JAVA_HOME=/path/to/java ``` or ``` ln -s /path/to/ndk ${HOME}/android-ndk ln -s /path/to/ndk ${HOME}/android-sdk ln -s /path/to/ndk ${HOME}/android-java ``` ### Run the build script Open a terminal in the path `examples/android/ExampleApp/app/src/main/jni/` and run `bash cmake.sh` to build the required libraries and the SDK. You can also run `rm -fr build-dir` to clean previous executions. **IMPORTANT:** Wait for the `"Task finished OK"` message at the end to ensure everything went well. If you want a more verbose output to inspect the output looking for build errors, change the `LOG_FILE` variable in the `build.sh` script from `/dev/null` to `/dev/stdout` or to a file like `/tmp/build.log` ## Build the Android Example Application Open the project with Android Studio, let it build the project and hit _*Run*_ ### Notes To compile the MEGA SDK (required for this example), the building scripts consider that the Android example is located inside the SDK folder: `/examples/android/ExampleApp`. In case you want to copy the example to a different path of your choice, you need to place a copy of the SDK in the folder `/ExampleApp/app/src/main/jni/mega` (or you can clone the repository, so you can keep the SDK up to date). sdk-10.11.0/examples/android/ExampleApp/app/000077500000000000000000000000001516266226600205215ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/build.gradle.kts000066400000000000000000000037311516266226600236040ustar00rootroot00000000000000plugins { id("com.android.application") id("org.jetbrains.kotlin.android") alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) } android { compileSdk = 36 namespace = "nz.mega.android.bindingsample" defaultConfig { applicationId = "nz.mega.android.bindingsample" minSdk = 28 targetSdk = 36 versionCode = 1 versionName = "1.0" } sourceSets.getByName("main") { java.srcDirs("../../../../bindings/java/nz/mega/sdk") java.exclude("**/MegaApiSwing.java") jniLibs.setSrcDirs(listOf("src/main/jniLibs")) } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } kotlinOptions { jvmTarget = "21" } buildFeatures { compose = true } } kotlin { jvmToolchain(21) } dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(libs.appcompat) implementation(libs.exifinterface) implementation(libs.jetbrains.annotations) // Compose BOM implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) implementation(libs.compose.ui.tooling.preview) implementation(libs.compose.material3) implementation(libs.compose.activity) // ViewModel implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.lifecycle.viewmodel.compose) implementation(libs.lifecycle.runtime.ktx) implementation(libs.lifecycle.runtime.compose) // Navigation Compose implementation(libs.navigation.compose) implementation(libs.kotlinx.serialization.json) // Debug tooling debugImplementation(libs.compose.ui.tooling) } sdk-10.11.0/examples/android/ExampleApp/app/proguard-project.txt000066400000000000000000000014151516266226600245520ustar00rootroot00000000000000# To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} sdk-10.11.0/examples/android/ExampleApp/app/proguard-rules.pro000066400000000000000000000012361516266226600242200ustar00rootroot00000000000000# Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /home/sergio/MEGA/dev/android-sdk-linux/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} sdk-10.11.0/examples/android/ExampleApp/app/src/000077500000000000000000000000001516266226600213105ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/000077500000000000000000000000001516266226600222345ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/AndroidManifest.xml000066400000000000000000000015631516266226600260320ustar00rootroot00000000000000 sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/000077500000000000000000000000001516266226600231555ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/000077500000000000000000000000001516266226600236045ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/000077500000000000000000000000001516266226600245155ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/000077500000000000000000000000001516266226600261355ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/000077500000000000000000000000001516266226600307515ustar00rootroot00000000000000DemoAndroidApplication.kt000066400000000000000000000037531516266226600356130ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsamplepackage nz.mega.android.bindingsample import android.app.Application import android.content.pm.PackageInfo import android.content.pm.PackageManager import nz.mega.android.bindingsample.data.SdkRepository import nz.mega.android.bindingsample.logger.AndroidLogger import nz.mega.sdk.MegaApiAndroid /** * DemoAndroidApplication.kt * Application class * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ class DemoAndroidApplication : Application() { private var megaApi: MegaApiAndroid? = null companion object { const val APP_KEY = "l4cmkI7B" const val USER_AGENT = "MEGA Android Simple Demo SDK" } override fun onCreate() { super.onCreate() MegaApiAndroid.addLoggerObject(AndroidLogger()) MegaApiAndroid.setLogLevel(MegaApiAndroid.LOG_LEVEL_MAX) // Initialize SdkRepository with MegaApi instance val api = getMegaApi() SdkRepository.initialize(api) } fun getMegaApi(): MegaApiAndroid { if (megaApi == null) { val m = packageManager val s = packageName var path: String? = null try { val p: PackageInfo = m.getPackageInfo(s, 0) path = p.applicationInfo?.dataDir?.let { "$it/" } } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() } megaApi = MegaApiAndroid(APP_KEY, USER_AGENT, path) } return megaApi!! } }sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/data/000077500000000000000000000000001516266226600316625ustar00rootroot00000000000000SdkRepository.kt000066400000000000000000000254641516266226600347770ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/data/** * SdkRepository.kt * Repository class for MEGA SDK operations * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.data import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava import nz.mega.sdk.MegaError import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaRequest import nz.mega.sdk.MegaRequestListenerInterface /** * Sealed interface for login operation results */ sealed interface LoginResult { /** * Login successful result */ data object LoginSuccess : LoginResult /** * Login failure result * @param errorMessage Error message describing the failure */ data class LoginFailure(val errorMessage: String) : LoginResult } /** * Sealed interface for fetch nodes operation results */ sealed interface FetchNodesResult { /** * Fetch nodes operation started */ data object Started : FetchNodesResult /** * Fetch nodes operation in progress * @param progressValue Progress value between 0.0 and 1.0 */ data class Progressing(val progressValue: Float) : FetchNodesResult /** * Fetch nodes operation completed successfully */ data object Completed : FetchNodesResult /** * Fetch nodes operation failed * @param errorMessage Error message describing the failure */ data class Error(val errorMessage: String) : FetchNodesResult } /** * Sealed interface for logout operation results */ sealed interface LogoutResult { /** * Logout successful result */ data object LogoutSuccess : LogoutResult /** * Logout failure result * @param errorMessage Error message describing the failure */ data class LogoutFailure(val errorMessage: String) : LogoutResult } /** * Singleton repository class that provides a clean interface to MEGA SDK operations. * This abstracts the SDK implementation details from the ViewModel layer. */ object SdkRepository { @Volatile private var megaApi: MegaApiAndroid? = null /** * Initialize the repository with MegaApiAndroid instance * Must be called before using the repository */ fun initialize(megaApi: MegaApiAndroid) { this.megaApi = megaApi } /** * Login to MEGA account * @param email User email address * @param password User password * @return Flow of LoginResult (LoginSuccess or LoginFailure) * * This method ensures all SDK operations run on a worker thread (IO dispatcher) * to avoid blocking the main thread. */ fun login(email: String, password: String): Flow = callbackFlow { val api = megaApi ?: run { trySend(LoginResult.LoginFailure("MegaApi not initialized")) close() return@callbackFlow } val listener = object : MegaRequestListenerInterface { override fun onRequestStart(api: MegaApiJava, request: MegaRequest) { // Login started - no action needed } override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) { // Login doesn't have progress updates } override fun onRequestFinish(api: MegaApiJava, request: MegaRequest, e: MegaError) { if (request.type == MegaRequest.TYPE_LOGIN) { if (e.errorCode == MegaError.API_OK) { trySend(LoginResult.LoginSuccess) } else { val errorMessage = if (e.errorCode == MegaError.API_ENOENT) { "Incorrect email or password" } else { e.errorString ?: "Login failed" } trySend(LoginResult.LoginFailure(errorMessage)) } close() } } override fun onRequestTemporaryError( api: MegaApiJava, request: MegaRequest, e: MegaError ) { // Handle temporary errors if needed } } api.login(email, password, listener) awaitClose { // Cleanup if needed } }.flowOn(Dispatchers.IO) /** * Fetch nodes from MEGA account * @return Flow of FetchNodesResult (Started, Progressing, or Completed) * * This method ensures all SDK operations run on a worker thread (IO dispatcher) * to avoid blocking the main thread. */ fun fetchNodes(): Flow = callbackFlow { val api = megaApi ?: run { close() return@callbackFlow } val listener = object : MegaRequestListenerInterface { override fun onRequestStart(api: MegaApiJava, request: MegaRequest) { if (request.type == MegaRequest.TYPE_FETCH_NODES) { trySend(FetchNodesResult.Started) } } override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) { if (request.type == MegaRequest.TYPE_FETCH_NODES) { if (request.totalBytes > 0) { var progressValue = request.transferredBytes.toFloat() / request.totalBytes.toFloat() // Clamp progress between 0.0 and 1.0 progressValue = progressValue.coerceIn(0.0f, 1.0f) trySend(FetchNodesResult.Progressing(progressValue)) } } } override fun onRequestFinish(api: MegaApiJava, request: MegaRequest, e: MegaError) { if (request.type == MegaRequest.TYPE_FETCH_NODES) { if (e.errorCode == MegaError.API_OK) { trySend(FetchNodesResult.Completed) } else { val errorMessage = e.errorString ?: "Failed to fetch nodes" trySend(FetchNodesResult.Error(errorMessage)) } close() } } override fun onRequestTemporaryError( api: MegaApiJava, request: MegaRequest, e: MegaError ) { // Handle temporary errors if needed } } api.fetchNodes(listener) awaitClose { // Cleanup if needed } }.flowOn(Dispatchers.IO) /** * Helper function to execute SDK operations on IO dispatcher. * Ensures the API is initialized before executing the operation. */ private suspend fun executeOnIo(operation: (MegaApiAndroid) -> T): T? = withContext(Dispatchers.IO) { val api = megaApi ?: return@withContext null operation(api) } /** * Get a node by its handle. * @param handle The handle of the node to retrieve * @return The MegaNode if found, null if not found or if API is not initialized * * This method runs on a worker thread (IO dispatcher) to avoid blocking the main thread. */ suspend fun getNodeByHandle(handle: Long): MegaNode? = executeOnIo { api -> api.getNodeByHandle(handle) } /** * Get the root node of the MEGA account. * @return The root MegaNode, or null if not available or if API is not initialized * * This method runs on a worker thread (IO dispatcher) to avoid blocking the main thread. */ suspend fun getRootNode(): MegaNode? = executeOnIo { api -> api.getRootNode() } /** * Get the children of a node. * @param node The parent node * @return List of child MegaNodes, or null if the node is null, not available, or if API is not initialized * * This method runs on a worker thread (IO dispatcher) to avoid blocking the main thread. */ suspend fun getChildren(node: MegaNode?): ArrayList? = executeOnIo { api -> node?.let { api.getChildren(it) } } /** * Get the parent node of a given node. * @param node The child node * @return The parent MegaNode, or null if the node is root, null, or if API is not initialized * * This method runs on a worker thread (IO dispatcher) to avoid blocking the main thread. */ suspend fun getParentNode(node: MegaNode?): MegaNode? = executeOnIo { api -> node?.let { api.getParentNode(it) } } /** * Logout from MEGA account * @return Flow of LogoutResult (LogoutSuccess or LogoutFailure) * * This method ensures all SDK operations run on a worker thread (IO dispatcher) * to avoid blocking the main thread. */ fun logout(): Flow = callbackFlow { val api = megaApi ?: run { trySend(LogoutResult.LogoutFailure("MegaApi not initialized")) close() return@callbackFlow } val listener = object : MegaRequestListenerInterface { override fun onRequestStart(api: MegaApiJava, request: MegaRequest) { // Logout started - no action needed } override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) { // Logout doesn't have progress updates } override fun onRequestFinish(api: MegaApiJava, request: MegaRequest, e: MegaError) { if (request.type == MegaRequest.TYPE_LOGOUT) { if (e.errorCode == MegaError.API_OK) { trySend(LogoutResult.LogoutSuccess) } else { val errorMessage = e.errorString ?: "Logout failed" trySend(LogoutResult.LogoutFailure(errorMessage)) } close() } } override fun onRequestTemporaryError( api: MegaApiJava, request: MegaRequest, e: MegaError ) { // Handle temporary errors if needed } } api.logout(listener) awaitClose { // Cleanup if needed } }.flowOn(Dispatchers.IO) } sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/logger/000077500000000000000000000000001516266226600322305ustar00rootroot00000000000000AndroidLogger.kt000066400000000000000000000016601516266226600352340ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/loggerpackage nz.mega.android.bindingsample.logger import android.util.Log import nz.mega.sdk.MegaLoggerInterface /** * AndroidLogger.kt * Class for managing logs * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ class AndroidLogger : MegaLoggerInterface { override fun log(time: String, logLevel: Int, source: String, message: String) { Log.d("AndroidLogger", "$source: $message") } }sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/navigation/000077500000000000000000000000001516266226600331105ustar00rootroot00000000000000Screen.kt000066400000000000000000000015041516266226600346100ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/navigation/** * Screen.kt * Navigation routes for Navigation Compose * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.navigation /** * Navigation routes for the app */ object Screen { const val Login = "login" const val Navigation = "navigation" } presentation/000077500000000000000000000000001516266226600334055ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsamplebrowswer/000077500000000000000000000000001516266226600352575ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentationEmptyFolderView.kt000066400000000000000000000033601516266226600407060ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/browswer/** * EmptyFolderView.kt * Compose component for displaying empty folder state * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.browswer import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import nz.mega.android.bindingsample.R /** * Composable for displaying empty folder message */ @Composable fun EmptyFolderView( modifier: Modifier = Modifier ) { Box( modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text( text = stringResource(R.string.empty_folder), color = Color(0xFF7A7A7A), fontSize = 16.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(top = 10.dp) ) } } NavigationScreen.kt000066400000000000000000000171401516266226600410610ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/browswer/** * NavigationScreen.kt * Compose screen for file browser navigation * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.browswer import android.app.Application import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import nz.mega.android.bindingsample.R /** * Main navigation screen composable that manages ViewModel and handles navigation logic. */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun NavigationScreen( onLogout: () -> Unit, savedParentHandle: Long? = null, modifier: Modifier = Modifier ) { val application = androidx.compose.ui.platform.LocalContext.current.applicationContext as Application val viewModel: NavigationViewModel = viewModel( factory = object : androidx.lifecycle.ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { return NavigationViewModel(application) as T } } ) val uiState by viewModel.uiState.collectAsStateWithLifecycle() // Handle initial load and saved state restoration LaunchedEffect(Unit) { if (uiState is NavigationUiState.Initial) { if (savedParentHandle != null) { viewModel.handleSavedState(savedParentHandle) } else { viewModel.loadNodes(-1) } } } // Handle logout success - trigger navigation callback LaunchedEffect(uiState) { if (uiState is NavigationUiState.LogoutSuccess) { onLogout() } } // Handle back button navigation // We can navigate up if we're not at root val currentUiStateForBack = uiState val canNavigateUp = when (currentUiStateForBack) { is NavigationUiState.Content -> currentUiStateForBack.parentHandle != -1L is NavigationUiState.Empty -> currentUiStateForBack.parentHandle != -1L else -> false } BackHandler(enabled = canNavigateUp) { viewModel.navigateUp() } NavigationActivityTheme { Scaffold( topBar = { TopAppBar( title = { val currentState = uiState Text( text = when (currentState) { is NavigationUiState.Content -> currentState.parentName is NavigationUiState.Empty -> currentState.parentName is NavigationUiState.Error -> currentState.parentName.ifEmpty { stringResource(R.string.cloud_drive) } else -> stringResource(R.string.cloud_drive) } ) }, actions = { TextButton(onClick = { viewModel.logout() }) { Text( text = stringResource(R.string.action_logout), style = MaterialTheme.typography.bodyMedium ) } } ) } ) { paddingValues -> val currentState = uiState Box( modifier = modifier .fillMaxSize() .padding(paddingValues) ) { when (currentState) { is NavigationUiState.Initial -> { // Initial state - show loading CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } is NavigationUiState.Loading -> { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } is NavigationUiState.Content -> { if (currentState.nodes.isEmpty()) { EmptyFolderView() } else { LazyColumn( modifier = Modifier.fillMaxSize() ) { items( items = currentState.nodes, key = { it.node.handle } ) { nodeInfo -> NodeListItem( nodeInfo = nodeInfo, onNodeClick = { viewModel.navigateToFolder(nodeInfo.node) } ) } } } } is NavigationUiState.Empty -> { EmptyFolderView() } is NavigationUiState.Error -> { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text( text = currentState.errorMessage, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodyLarge ) } } is NavigationUiState.LoggingOut -> { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } is NavigationUiState.LogoutSuccess -> { // This state should trigger navigation, handled by LaunchedEffect above CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } } } } } } @Composable fun NavigationActivityTheme(content: @Composable () -> Unit) { MaterialTheme { content() } } NavigationUiState.kt000066400000000000000000000053041516266226600412170ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/browswer/** * NavigationUiState.kt * UI state data class for navigation screen * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.browswer import nz.mega.sdk.MegaNode /** * Data class to hold node with its display information */ data class NodeDisplayInfo( val node: MegaNode, val folderInfo: String? = null // null for files, computed info for folders ) /** * Sealed interface representing different states of the navigation UI. * Each state represents a specific phase of the file browser. */ sealed interface NavigationUiState { /** * Initial state - initializing navigation */ data object Initial : NavigationUiState /** * Loading state - loading nodes for current folder */ data object Loading : NavigationUiState /** * Content state - displaying nodes with current parent info * @param nodes List of child nodes with display info * @param parentHandle Handle of the current parent node * @param parentName Name of the current parent node */ data class Content( val nodes: List, val parentHandle: Long, val parentName: String ) : NavigationUiState /** * Empty state - current folder is empty * @param parentHandle Handle of the current parent node * @param parentName Name of the current parent node */ data class Empty( val parentHandle: Long, val parentName: String ) : NavigationUiState /** * Error state - error occurred while loading nodes * @param errorMessage Error message describing the failure * @param parentHandle Handle of the current parent node (if available) * @param parentName Name of the current parent node (if available) */ data class Error( val errorMessage: String, val parentHandle: Long = -1, val parentName: String = "" ) : NavigationUiState /** * Logging out state - logout request is in progress */ data object LoggingOut : NavigationUiState /** * Logout success state - logout completed successfully */ data object LogoutSuccess : NavigationUiState } NavigationViewModel.kt000066400000000000000000000212511516266226600415330ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/browswer/** * NavigationViewModel.kt * ViewModel for NavigationScreen to manage file browser state and business logic * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.browswer import android.app.Application import android.content.Context import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import nz.mega.android.bindingsample.R import nz.mega.android.bindingsample.data.LogoutResult import nz.mega.android.bindingsample.data.SdkRepository import nz.mega.sdk.MegaNode class NavigationViewModel(application: Application) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(NavigationUiState.Initial) val uiState: StateFlow = _uiState.asStateFlow() /** * Load nodes for a given parent handle. * If parentHandle is -1, loads root node. */ fun loadNodes(parentHandle: Long = -1) { viewModelScope.launch { _uiState.value = NavigationUiState.Loading try { val parentNode = if (parentHandle == -1L) { SdkRepository.getRootNode() } else { SdkRepository.getNodeByHandle(parentHandle) } if (parentNode == null) { _uiState.value = NavigationUiState.Error( errorMessage = "Failed to load parent node", parentHandle = parentHandle ) return@launch } val children = SdkRepository.getChildren(parentNode) val parentName = if (parentNode.type == MegaNode.TYPE_ROOT) { getApplication().getString(R.string.cloud_drive) } else { parentNode.name } if (children.isNullOrEmpty()) { _uiState.value = NavigationUiState.Empty( parentHandle = parentNode.handle, parentName = parentName ) } else { // Compute folder info for each folder node val nodesWithInfo = children.map { node -> if (node.isFolder) { val folderChildren = SdkRepository.getChildren(node) val folderInfo = formatFolderInfo(folderChildren, getApplication()) NodeDisplayInfo(node, folderInfo) } else { NodeDisplayInfo(node) } } _uiState.value = NavigationUiState.Content( nodes = nodesWithInfo, parentHandle = parentNode.handle, parentName = parentName ) } } catch (e: Exception) { _uiState.value = NavigationUiState.Error( errorMessage = e.message ?: "Unknown error occurred", parentHandle = parentHandle ) } } } /** * Navigate into a folder node. */ fun navigateToFolder(node: MegaNode) { if (node.isFolder) { loadNodes(node.handle) } } /** * Navigate to parent folder. */ fun navigateUp() { val currentState = _uiState.value val currentParentHandle = when (currentState) { is NavigationUiState.Content -> currentState.parentHandle is NavigationUiState.Empty -> currentState.parentHandle else -> -1L } if (currentParentHandle == -1L) { // Already at root, cannot navigate up return } viewModelScope.launch { try { val currentNode = SdkRepository.getNodeByHandle(currentParentHandle) if (currentNode == null) { // Invalid state, load root loadNodes(-1) return@launch } // Check if current node is root if (currentNode.type == MegaNode.TYPE_ROOT) { // Already at root, cannot navigate up return@launch } val parentNode = SdkRepository.getParentNode(currentNode) if (parentNode != null) { loadNodes(parentNode.handle) } else { // No parent means we're at root, load root loadNodes(-1) } } catch (e: Exception) { _uiState.value = NavigationUiState.Error( errorMessage = e.message ?: "Failed to navigate up", parentHandle = currentParentHandle ) } } } /** * Initiate logout. */ fun logout() { _uiState.value = NavigationUiState.LoggingOut viewModelScope.launch { SdkRepository.logout().collect { result -> when (result) { is LogoutResult.LogoutSuccess -> { _uiState.value = NavigationUiState.LogoutSuccess } is LogoutResult.LogoutFailure -> { val currentState = _uiState.value val parentHandle = when (currentState) { is NavigationUiState.Content -> currentState.parentHandle is NavigationUiState.Empty -> currentState.parentHandle else -> -1L } val parentName = when (currentState) { is NavigationUiState.Content -> currentState.parentName is NavigationUiState.Empty -> currentState.parentName else -> "" } _uiState.value = NavigationUiState.Error( errorMessage = result.errorMessage, parentHandle = parentHandle, parentName = parentName ) } } } } } /** * Handle saved state restoration. * Should be called from Activity onCreate with saved state. */ fun handleSavedState(parentHandle: Long?) { if (_uiState.value is NavigationUiState.Initial) { loadNodes(parentHandle ?: -1) } } /** * Format folder info string (number of folders and files) */ private fun formatFolderInfo(children: ArrayList?, context: Context): String { if (children == null) return "0 files" var numFolders = 0 var numFiles = 0 for (c in children) { if (c.isFolder) { numFolders++ } else { numFiles++ } } return when { numFolders > 0 && numFiles > 0 -> { val folderPlural = context.resources.getQuantityString( R.plurals.general_num_folders, numFolders ) val filePlural = context.resources.getQuantityString( R.plurals.general_num_files, numFiles ) "$numFolders $folderPlural, $numFiles $filePlural" } numFolders > 0 -> { val folderPlural = context.resources.getQuantityString( R.plurals.general_num_folders, numFolders ) "$numFolders $folderPlural" } numFiles > 0 -> { val filePlural = context.resources.getQuantityString( R.plurals.general_num_files, numFiles ) "$numFiles $filePlural" } else -> "Empty" } } } NodeListItem.kt000066400000000000000000000110451516266226600401600ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/browswer/** * NodeListItem.kt * Compose component for displaying a file/folder node in the list * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.browswer import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import nz.mega.android.bindingsample.R import java.text.DecimalFormat /** * Format file size to human-readable string */ private fun formatFileSize(size: Long): String { val decf = DecimalFormat("###.##") val KB = 1024f val MB = KB * 1024 val GB = MB * 1024 val TB = GB * 1024 return when { size < KB -> "$size B" size < MB -> "${decf.format(size / KB)} KB" size < GB -> "${decf.format(size / MB)} MB" size < TB -> "${decf.format(size / GB)} GB" else -> "${decf.format(size / TB)} TB" } } /** * Composable for displaying a file/folder node in the list */ @Composable fun NodeListItem( nodeInfo: NodeDisplayInfo, onNodeClick: (NodeDisplayInfo) -> Unit, modifier: Modifier = Modifier ) { val node = nodeInfo.node Row( modifier = modifier .fillMaxWidth() .clickable(enabled = node.isFolder) { if (node.isFolder) { onNodeClick(nodeInfo) } } .padding(horizontal = 5.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { // Thumbnail icon Icon( painter = painterResource( id = if (node.isFile) { R.drawable.mime_file } else { R.drawable.mime_folder } ), contentDescription = if (node.isFile) "File" else "Folder", modifier = Modifier.size(60.dp), tint = if (node.isFile) { MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.secondary } ) Spacer(modifier = Modifier.width(3.dp)) // File name and size/info Column( modifier = Modifier .weight(1f) .padding(start = 3.dp) ) { Text( text = node.name, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(bottom = 4.dp) ) // For files, show size. For folders, show folder info if (node.isFile) { Text( text = formatFileSize(node.size), style = MaterialTheme.typography.bodySmall, color = Color(0xFF848484), fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis ) } else { // Show folder info if available Text( text = nodeInfo.folderInfo ?: "Folder", style = MaterialTheme.typography.bodySmall, color = Color(0xFF848484), fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis ) } } } } login/000077500000000000000000000000001516266226600345155ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentationLoginScreen.kt000066400000000000000000000213311516266226600372650ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/login/** * LoginScreen.kt * Compose version of the login screen converted from activity_main.xml * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.login import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import nz.mega.android.bindingsample.R @Composable fun LoginScreen( uiState: LoginUiState, onEmailChange: (String) -> Unit = {}, onPasswordChange: (String) -> Unit = {}, onLoginClick: () -> Unit = {}, onLoginSuccess: () -> Unit = {} ) { // Handle navigation when login is successful LaunchedEffect(uiState) { if (uiState is LoginUiState.Success) { onLoginSuccess() } } Column( modifier = Modifier .fillMaxSize() .padding( horizontal = 16.dp, vertical = 16.dp ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { when (uiState) { is LoginUiState.Initial -> { Text( text = stringResource(R.string.login_text), fontSize = 28.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(bottom = 16.dp) ) // Email EditText equivalent OutlinedTextField( value = uiState.email, onValueChange = onEmailChange, label = { Text(stringResource(R.string.email_text)) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, imeAction = ImeAction.Next ), singleLine = true, isError = uiState.emailError != null, supportingText = uiState.emailError?.let { { Text(it) } }, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) // Password EditText equivalent OutlinedTextField( value = uiState.password, onValueChange = onPasswordChange, label = { Text(stringResource(R.string.password_text)) }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), singleLine = true, isError = uiState.passwordError != null, supportingText = uiState.passwordError?.let { { Text(it) } }, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) // Login Button equivalent Button( onClick = onLoginClick, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.login_text)) } } is LoginUiState.LoggingIn -> { Text( text = stringResource(R.string.logging_in), fontSize = 28.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(bottom = 16.dp) ) } is LoginUiState.FetchingNodes -> { // Animated progress bar showing actual progress val animatedProgress by animateFloatAsState( targetValue = uiState.progress, animationSpec = tween(durationMillis = 300), label = "progress_animation" ) Text( text = stringResource(R.string.logging_in), fontSize = 28.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(bottom = 16.dp) ) LinearProgressIndicator( progress = { animatedProgress }, modifier = Modifier .fillMaxWidth() .padding(top = 10.dp) ) } is LoginUiState.Error -> { // Display general error message Text( text = uiState.errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 14.sp, textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) // Email EditText equivalent OutlinedTextField( value = uiState.email, onValueChange = onEmailChange, label = { Text(stringResource(R.string.email_text)) }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, imeAction = ImeAction.Next ), singleLine = true, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) // Password EditText equivalent OutlinedTextField( value = uiState.password, onValueChange = onPasswordChange, label = { Text(stringResource(R.string.password_text)) }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), singleLine = true, modifier = Modifier .fillMaxWidth() .padding(bottom = 8.dp) ) // Login Button equivalent Button( onClick = onLoginClick, modifier = Modifier.fillMaxWidth() ) { Text(stringResource(R.string.login_text)) } } is LoginUiState.Success -> { // This state should not be displayed as navigation happens immediately // But we can show a success message or loading indicator as fallback } } } } @Preview(showBackground = true) @Composable fun LoginScreenPreview() { MaterialTheme { LoginScreen( uiState = LoginUiState.Initial() ) } } @Preview(showBackground = true) @Composable fun LoginScreenLoadingPreview() { MaterialTheme { LoginScreen( uiState = LoginUiState.FetchingNodes( email = "test@example.com", progress = 0.5f ) ) } } LoginUiState.kt000066400000000000000000000045571516266226600374370ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/login/** * LoginUiState.kt * UI state data class for login screen * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.login /** * Sealed interface representing different states of the login UI. * Each state represents a specific phase of the login process. */ sealed interface LoginUiState { val email: String? val password: String? /** * Initial state - user can input credentials. * Form fields are visible and editable. */ data class Initial( override val email: String = "", override val password: String = "", val emailError: String? = null, val passwordError: String? = null ) : LoginUiState /** * Logging in state - login request is in progress. * Form fields are hidden, loading indicator is shown. */ data class LoggingIn( override val email: String, override val password: String ) : LoginUiState /** * Fetching nodes state - nodes are being fetched after successful login. * Progress bar is visible. * @param progress Progress value between 0.0f and 1.0f */ data class FetchingNodes( override val email: String = "", override val password: String = "", val progress: Float = 0.0f ) : LoginUiState /** * Error state - login or network error occurred. * Form fields are visible with error message displayed. */ data class Error( override val email: String = "", override val password: String = "", val errorMessage: String ) : LoginUiState /** * Success state - login successful. * Navigation should be triggered from the Composable. */ data class Success( override val email: String = "", override val password: String = "", ) : LoginUiState } MainActivity.kt000066400000000000000000000076321516266226600374660ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/login/** * MainActivity.kt * Initial activity of the demo app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ package nz.mega.android.bindingsample.presentation.login import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import nz.mega.android.bindingsample.navigation.Screen import nz.mega.android.bindingsample.presentation.browswer.NavigationScreen class MainActivity : ComponentActivity() { private val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { val navController = rememberNavController() MainActivityTheme { NavHost( navController = navController, startDestination = Screen.Login ) { composable(Screen.Login) { LoginScreenContent( viewModel = viewModel, onLoginSuccess = { navController.navigate(Screen.Navigation) { popUpTo(Screen.Login) { inclusive = true } } } ) } composable(Screen.Navigation) { NavigationScreen( onLogout = { navController.navigate(Screen.Login) { popUpTo(Screen.Navigation) { inclusive = true } } // Reset login state when logging out viewModel.resetState() } ) } } } } } } @Composable private fun LoginScreenContent( viewModel: MainActivityViewModel, onLoginSuccess: () -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() Scaffold( modifier = Modifier.fillMaxSize() ) { paddingValues -> Box( modifier = Modifier .fillMaxSize() .padding(paddingValues), contentAlignment = Alignment.Center ) { LoginScreen( uiState = uiState, onEmailChange = { viewModel.updateEmail(it) }, onPasswordChange = { viewModel.updatePassword(it) }, onLoginClick = { viewModel.onLoginClick() }, onLoginSuccess = onLoginSuccess ) } } } @Composable fun MainActivityTheme(content: @Composable () -> Unit) { MaterialTheme { content() } } MainActivityViewModel.kt000066400000000000000000000205251516266226600412760ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/java/nz/mega/android/bindingsample/presentation/loginpackage nz.mega.android.bindingsample.presentation.login import android.app.Application import android.util.Log import android.util.Patterns import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import nz.mega.android.bindingsample.DemoAndroidApplication import nz.mega.android.bindingsample.R import nz.mega.android.bindingsample.data.FetchNodesResult import nz.mega.android.bindingsample.data.LoginResult import nz.mega.android.bindingsample.data.SdkRepository import nz.mega.sdk.MegaApiAndroid /** * MainActivityViewModel.kt * ViewModel for MainActivity to manage login state and business logic * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ class MainActivityViewModel(application: Application) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(LoginUiState.Initial()) val uiState: StateFlow = _uiState.asStateFlow() private var megaApi: MegaApiAndroid? = null init { val app = application as? DemoAndroidApplication megaApi = app?.getMegaApi() } fun updateEmail(email: String) { val currentState = _uiState.value when (currentState) { is LoginUiState.Initial -> { _uiState.value = currentState.copy( email = email, emailError = null ) } is LoginUiState.Error -> { _uiState.value = LoginUiState.Initial( email = email, password = currentState.password, emailError = null, passwordError = null ) } else -> { // Ignore updates in other states } } } fun updatePassword(password: String) { val currentState = _uiState.value when (currentState) { is LoginUiState.Initial -> { _uiState.value = currentState.copy( password = password, passwordError = null ) } is LoginUiState.Error -> { _uiState.value = LoginUiState.Initial( email = currentState.email, password = password, emailError = null, passwordError = null ) } else -> { // Ignore updates in other states } } } fun validateEmail(email: String): String? { val context = getApplication() return when { email.isEmpty() -> context.getString(R.string.error_enter_email) !Patterns.EMAIL_ADDRESS.matcher(email).matches() -> context.getString(R.string.error_invalid_email) else -> null } } fun validatePassword(password: String): String? { val context = getApplication() return if (password.isEmpty()) { context.getString(R.string.error_enter_password) } else { null } } fun validateForm(): Boolean { val currentState = _uiState.value val email: String val password: String when (currentState) { is LoginUiState.Initial -> { email = currentState.email password = currentState.password } is LoginUiState.Error -> { email = currentState.email password = currentState.password } else -> return false } val emailError = validateEmail(email) val passwordError = validatePassword(password) _uiState.value = LoginUiState.Initial( email = email, password = password, emailError = emailError, passwordError = passwordError ) return emailError == null && passwordError == null } fun onLoginClick() { if (!validateForm()) { return } val currentState = _uiState.value val email: String val password: String when (currentState) { is LoginUiState.Initial -> { email = currentState.email.lowercase().trim() password = currentState.password } else -> { Log.e("MainActivityViewModel", "incorrect state after clicking login") return } } // Update UI state to LoggingIn _uiState.value = LoginUiState.LoggingIn( email = email, password = password ) // Perform login viewModelScope.launch { SdkRepository.login(email, password).collect { result -> when (result) { is LoginResult.LoginSuccess -> { handleLoginSuccess() } is LoginResult.LoginFailure -> { handleLoginError(result.errorMessage, email, password) } } } } } private fun handleLoginError(errorMessage: String, email: String, password: String) { _uiState.value = LoginUiState.Error( email = email, password = password, errorMessage = errorMessage ) } fun handleLoginSuccess() { val email = when (val currentState = _uiState.value) { is LoginUiState.LoggingIn -> currentState.email else -> "" } // Transition to FetchingNodes state _uiState.value = LoginUiState.FetchingNodes( email = email, progress = 0.0f ) // Start fetching nodes viewModelScope.launch { SdkRepository.fetchNodes().collect { result -> when (result) { is FetchNodesResult.Started -> { val currentState = _uiState.value if (currentState is LoginUiState.FetchingNodes) { _uiState.value = currentState.copy(progress = 0.0f) } } is FetchNodesResult.Progressing -> { val currentState = _uiState.value if (currentState is LoginUiState.FetchingNodes) { _uiState.value = currentState.copy(progress = result.progressValue) } } is FetchNodesResult.Completed -> { val currentState = _uiState.value val email = when (currentState) { is LoginUiState.FetchingNodes -> currentState.email else -> "" } _uiState.value = LoginUiState.Success(email = email) } is FetchNodesResult.Error -> { val currentState = _uiState.value val email = when (currentState) { is LoginUiState.FetchingNodes -> currentState.email else -> "" } _uiState.value = LoginUiState.Error( email = email, password = "", errorMessage = result.errorMessage ) } } } } } fun resetState() { _uiState.value = LoginUiState.Initial( email = "", password = "", emailError = null, passwordError = null ) } }sdk-10.11.0/examples/android/ExampleApp/app/src/main/jni/000077500000000000000000000000001516266226600230145ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/jni/cmake.sh000077500000000000000000000053111516266226600244330ustar00rootroot00000000000000#!/bin/bash -i set -e start_time=$SECONDS ANDROID_API_LEVEL=28 # Check if NDK_ROOT is set. if [ -z "${NDK_ROOT}" ] || [ ! -f "${NDK_ROOT}/ndk-build" ]; then echo "NDK_ROOT is not correctly set. Please download the Android NDK and export NDK_ROOT variable pointing to your Android NDK installation path and try again." exit 1 fi export ANDROID_NDK_HOME=${NDK_ROOT} export SDK_DIR=../../../../../../../ if [[ "$OSTYPE" == "darwin"* ]]; then STRIP_TOOL=${NDK_ROOT}/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-strip else STRIP_TOOL=${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip fi # LIST OF ARCHS TO BE BUILT. if [ -z "${BUILD_ARCHS}" ]; then # If no environment variable is defined, use all archs. BUILD_ARCHS="x86 armeabi-v7a x86_64 arm64-v8a" fi if [ -z "${LOG_FILE}" ]; then # If no build log variable is defined, use below value. LOG_FILE=/dev/stdout # Ensure you use a full path fi # Check if VCPKG_ROOT is set. if [ -z "${VCPKG_ROOT}" ]; then echo "VCPKG_ROOT is not set. Exiting." echo "Please run 'git clone https://github.com/microsoft/vcpkg.git' and set VCPKG_ROOT to the path of the cloned repository." echo "VCPKG is suggested to be installed out of the project directory." exit 1 else if [ ! -f "${VCPKG_ROOT}/bootstrap-vcpkg.sh" ]; then echo "vcpkg installation not found. Exiting." echo "Please ensure you have cloned the vcpkg repository('git clone https://github.com/microsoft/vcpkg.git')." echo "VCPKG is suggested to be installed out of the project directory." exit 1 fi fi if [[ "$OSTYPE" == "darwin"* ]]; then JOBS=$(sysctl -n hw.ncpu) else JOBS=$(nproc) fi echo "-- JOBS: ${JOBS}" echo "-- BUILD_ARCHS: ${BUILD_ARCHS}" echo "-- VCPKG_ROOT: ${VCPKG_ROOT}" echo "-- ANDROID_NDK_HOME: ${ANDROID_NDK_HOME}" for ABI in ${BUILD_ARCHS}; do BUILD_DIR=build-dir/${ABI} LIB_OUTPUT_DIR=../jniLibs/${ABI} mkdir -p "${LIB_OUTPUT_DIR}" echo "* Configuring MEGA SDK - ${ABI}" cmake -S ${SDK_DIR} --preset mega-android \ -B ${BUILD_DIR} \ -DVCPKG_ROOT=${VCPKG_ROOT} \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DANDROID_PLATFORM=${ANDROID_API_LEVEL} \ -DANDROID_ABI=${ABI} &>>${LOG_FILE} echo "* Building MEGA SDK - ${ABI}" cmake --build "${BUILD_DIR}" -j "${JOBS}" &>>${LOG_FILE} cp -fv ${BUILD_DIR}/bindings/java/libmega.so ${LIB_OUTPUT_DIR} &>>${LOG_FILE} cp -fv ${BUILD_DIR}/bindings/java/nz/mega/sdk/*.java ../java/nz/mega/sdk &>>${LOG_FILE} ${STRIP_TOOL} "../jniLibs/${ABI}/libmega.so" &>> ${LOG_FILE} done end_time=$SECONDS total_time=$((end_time - start_time)) echo "* Task finished OK" echo "Total execution time: ${total_time} seconds" sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/000077500000000000000000000000001516266226600230255ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-hdpi/000077500000000000000000000000001516266226600255305ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-hdpi/ic_launcher.png000066400000000000000000000226751516266226600305260ustar00rootroot00000000000000PNG  IHDRHHUG AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~7IDATx\keEu^{ǽ>gD : &ZP"cŪPjI,fD, -1$82$Tq@ 5Q F0G`3wuvս9330c}ݽzk}w}Y~G _-D~c?g̲.B l*60Šs n/n+}ϝ+ {pd*νa0ٟ{eً܀ΙMܼYxўBbYbVvq #UFm>M W>Gr@9׿: NنNX01e65eaz1p&iZ(20C*fY G u 4,Euա,wm^T;Oy$:Ƭ>/({,lLDLƨ s)#r2N-ɈX QW,ԻN6g@AZV}[;N@'yV Kq ;G`LL"Jf0"eR(8pV}|$@ybG$L',aӈ "iT@iE!h(1bP0DbCyͬՅi٠eWX`6An) 9ӚwZ05\-XtJF`0J GҴbt$;Hf#u*5U-_ 48Yd/ܰ@c_8͜v'1 uW%N'B'5 (9wN=kiz:ɤ 'RhXbekXrնt]z(*N?7N=bjM8@yl#3jtjkMBOGF>(Q>-:u@Earl zzAK,;jUV4Rq}oɕe7шE3});9_,d.ijXoZCkp5t?$g$ |l"4-_l##'ZN,_i61#(|w8 +Q0LF^4VOSlNtV8FsFMAt 8aYH kRejz}>LP/#L_T&` 7V4q:>#PgN j4*vE%t}qoOpIvHl2,ޕ%K-?j-]z:r0=%@>v '5 +1"C^\mQ Fm\g˵cˑG@EqGᶅ[ĽڵVY14a)=}ԵX)|چnBv`CpqএX'0C? #RʈJIRE}P+sḾt)}jnR3X{؊'^\q`;x:0@燱nqq rk' 1*p,)5 )6]zPAAP(q6ݻصӚc'HҢSxϕ'cUHf*!zxŻnG=_:Djru9<*CE @v5  #WkC7yʚ|,/9}43%mV#cc`D2屬hB!Dh wN2x"?1ZN^֡RMESk &]H2m\},/Ƭعˊ{Pr @_^vFxE0ƛpOָEЗ%,lfn k*t[re9EO-$@%9I 5%Uє,8p԰oZ ?b^tR +I{3bS 7F:/G ۹;K qgcDF~Z^Ju3- neAsv;|%WfOg(SNĻp}pӠ0Swab(v:KmRql-KbI<2tL%x]=i Oa1>׾2@NgW^"FN&_< Vy5v}WG׽[X\o+{{#y l)6o8q}t"'$ L0uvzW/ѽOÍg;ao% F@h)jDgC;oV{ٖ)hX\R#(9m*e۶˛TY,b֭ǒDRuQW >XcաѬou76#2Gx 4rq`Wt*Ť&,͖3r'5}ȋ[92i=Z 0t+OS )gg4~`()sfeٵyo \|Ik閂SιJ7oKnp-FdȇN<)f|=zEWVd<)M!Ci.CR[=Ns|V{nTI)[= w'ETF-/lLV,*?DҚ~"Jp2}ˉǦ~IeԍNDj_^W3_hC\M.1G ү^j ܿQO=xm\ py:%Z7OKϓ+U/bLR[#Gez=ZP#Mz#/R', ữL0ɀ@Zއfg P<\fi`.SCY#BD#@߁Q]9Hlųר_>(QږpB夗y39-D)1('!ZT|L2NP U'5ŞŷPHh"N5+f_3",.*CM 4O"=bX Ab~~2}V O'h*361S`cԥ ŊU 9,~2=(*n;׻P_p dU cR4oxf{,vJJI^&؉I0J791ݍH2`W߈K(w[{"M[/s^W]Cל=z 4c ?z@OG`ѣryrijdr+o_ߙ}v{]dL+#ꌜ}詎/߱}mAGg{/RӊnhtNzMG9QDZ J|^A..AN҆wɫYG%2VF^o;ӭ1:v3;̷J-d#TS<8ki١\:4h*1<N(d`;568/oK2%@'0"iA"P.˟$4ҥ5\Vq 3xuQkWt#nrM n&q(sWO=F&WkM%9*c+[?NU!T+lC=;>ub("GNn!5Gma˱79>R:XkU'\LI[8|Vos<^\CХRb؏ֲ{P)E'b][ F"<1<|PM,\ ֽUwӇVJƬk2TE⪎T-AꂹxJ%DA?<.Fvmғ,R뢾ţ.qlDw J7u#o`,k_*^6bW2ɚ({F^GNT<[!3Цf$!,0Et㕜l)Q6T,lD߄tݸ̵Q(ҦNÓO~O_4P'ISv'8_shbCXSy />>BRD"HM8Wc Q6E$1ʷsT,%En FhW'>DKbx0Yp$={NFY.62}XKO:$/.O~5O:h sQϖN^yQN*\snݺJm2FWݟκ= ǝXv0a2#F| H HOZ0:p9-xz3V EYG)cY-Ufȏx}JCbIijpigpylbJ'OlsYXr38+/ P`e2tС:2"KDlٟZGmږ/=m@:с$s"+\"ʣP2IG#9]"h܅i'k|JCIu[Lo~ <T˿6Q-)G5MQC暤p3 *Hi3NƯ6 ^śjM;ÈB~jIs9TC-EMR3tU.'?׉_$V\\m]:̏_^?[Z𺧰3p^7}/Wa(ٵ/Pj뱈?C.~ ̂Suw +T!'Q2D-ɲAʭIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-hdpi/mime_file.png000066400000000000000000000005211516266226600301620ustar00rootroot00000000000000PNG  IHDRHH{IDATx?j0FG11r%C9^^TH4 *+R}A*K  j,i75$fTY,0p!ԭ1'rdAQgܙ;/vD*(޿hY8Cw${.Dα;@YPsʁ68qPAe8(< *miߠPuyPH H H H H Xwd}q';2TX (rzr:+ݢ3IENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-hdpi/mime_folder.png000066400000000000000000000004331516266226600305200ustar00rootroot00000000000000PNG  IHDRHHUGIDATxڽ PTiu-BPWpp̏<.p ,$I$)ŪkN"VG{2Nm@Ոj%Լ{[M%#VOPZ$@-h"@ds&>3̟g? @ @ @ @uryn_r0e%I$'O^uIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-ldpi/000077500000000000000000000000001516266226600255345ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-ldpi/ic_launcher.png000066400000000000000000000116301516266226600305170ustar00rootroot00000000000000PNG  IHDR$$ AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ IDATX ͗k]Usg: NڦKbJQ?1&T-*#-hBj*bl*JH#b)M `t:{;ϙ/;\Z1O)u0;#J^  o/ʱZkGm|彋]}O}K.ED2lTk:-lO4B dm_q[~ 'a؉ jQ4< ֖9 ޜ.Pdswځ=rv`vla"ZD!!-ļ9L0x!dzO+VHQ(eX.HmaObE0 wOܵ-@-%СaŒ2 BEtEB#8T~ Az&\O3Ry pt0\E ˆKBfϩ8(¸siO h ayѣ L 9-Տ/'cn%ِnJf*H ^ ˤ$<N -|G_UprN;4 r;!=]-LOeTņR^߁ GD4<39~G Zg!lv_DǧF!v,C`(BuFӿFy:֟ՎZUiMnVFtbk{X`+e lL He+5'7 HZ!qL\ú:v<􂾙5L@~)±T+7Jgɥ<Y倸]<tJ/Z⓿@XyŔA'3}2^8�jؚ!hjYJBWzKDn&R>=e/BPQ> dad?qXXoĉ3hd{45v`rf ]q~\0F6"d49<%D|eBD%ԫ_FuL Ygҡ -[–u=]3]`tY3"W&+F640]I N+P퍷cyA V%h_ o W.U01 pW"&~nP:(xUXy{ m^;J;\ }/}5 uГSJd&/Us&ܲ晿i[cxrt.lrnHgCnS7+E㊃"^Tȟv⭁˯7 yPomɒ}zxzIz+@Vс b&_y9uQ{'SGtOK,ώb rIg{dc$ђ($W we=G@O}B9.!礠6(uɛj[Hmm6wmȽ*a50}9q6-"+O^T ڍݹn^L{ %cU>G&P\ |+W\4B ЬsgsAZǯgu]͆?(uVq3JJn˼xtUPCy@жwfsV֒byslN-10ppvA9Pb ):@>cY,ɯ$/H#qъS^g~# 4ÛL&?0h_MF@u0ʕӥ*gJ#V\{Q`;U> !3Rmw3B(0|>kkMz XB4\#o cqg/n;Dֹ Tu7efj#Ch1B{tvg'h _ P>YgIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-ldpi/mime_file.png000066400000000000000000000003371516266226600301730ustar00rootroot00000000000000PNG  IHDR$$K PIDATx!!8Wnu_ͪE߯6mxHPFq'>[wk9-O4/|MDR4ÀOƙ?[QP A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~PIDAThYk]U^{g촵TE@RCBFS4R406 Q "&6P@"Qki|Am;-N;}ϝ{gq9^{}x.|x](i̩i L"Ew 2/g ^Ixѽ'q be@ME #~VBW[/oK|wȋaCbF)"Cᑀ86A㌋Aj-NDB@lHK^ SV%޶gzCbzEuo'D QZp^ JV0` NOhLv?\.)`nycMOothTF] ?=@ HL)>t I6& _ozj3˜[lR/p ~`H$|gԴWH ZjɰXUf RuHe-yҏ/# 5ߜu*34cb`0ع^He?sdԩu+r8-0JG9#~%mG&mc/V푼aO0Jeh=kdRK8n钑P#0ԕęI黥SF6L㦟K٨}P꫆q, O]DK?Oה huq=EQ}ӥ{M)x\2>d~PRQZH'qR* bWPN1% S'UgTg>]8CJ_^l@uVv  f=*0R)̝v%ȟ$E~Ae&C GӶyNsKfRNs>=sMפj|h8B 9H p)TTLj?s$V3Jjř}ʒ i^q )L UT}YvA]DT-|umTFiJ;jDgkP9Y_K[+Zyd2MIgv[EHSb|!/ef p.$c]gʲ7 h>W^*o^~8,HPgK! vp㢮UFh>bTX3*re$":6kX*L]0+iiCPك:zf"PIbӏ[^ >4簧 xD /o_YQ>YOjR] iV6$ @te ,2j`0w8HD̏gv|J2Ă"+¶/jf{ \Q]zLq6q <2)akhU BGD\Y d%/hB:uk]E *Qp!#HEZA9϶pIdfP281!ؤⱊHlPy﹥ Mvv5 AgЋө.8a'cSj憖Ӳ}Dx]EޥK<}C@Z`VvSVe4R$sq hC9ɿ[0(C [,}?O`ٯV* w7:"A#9R{MI&#)ЕD a:3mȶjT_^ؒ¶, V#z 4yq5-Mpwl5 Üd! h 5RǙ]bR2 Sw6hj@`#8VC ̐SVg{آS#Ї$cȪ;:wev:v5ETz`= {A3H/MC:00ǖ|lwXRG Or^SLk/DUWKӉU@ oo6Ruc+ê G#g?H`Ν-P6gl  M ( -:!&[\6WP) cmMdu3ԩ-hc{۷1[!n<9:byu A TqcӀrh#s6O䢭wD>|ڃTfwZ64'̶_L-tĸftVPn?#`JT4`TZ2PEm7X3HH [Wukg9 P,~GDž^*HtR[Qv,pZRH5D"]ĭ<L~ɫK4}SB@`yGЀ4 h "C|ܰ& O?HE7';<qA&ڰaIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-mdpi/mime_folder.png000066400000000000000000000003071516266226600305250ustar00rootroot00000000000000PNG  IHDR00WIDAThر 0En" QgX$9Ecb2VkV,iU ؤ9\C=s?K?k .1y Ӌ3/IENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xhdpi/000077500000000000000000000000001516266226600257205ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xhdpi/ic_launcher.png000066400000000000000000000330021516266226600307000ustar00rootroot00000000000000PNG  IHDR``w8 AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~+|IDATxyeG}[fh$͌6/UP R N9v I6^a !K`@ h ,E`F)űDQX@ $͌fs|7f}t[~ݿi@d,0l5CVW΋nWO:SNUUy,%4WǪM]3]ѽUj{? btN#3l̥jn.5:\-hj*VFUJNjF4MOn՗3-gw>q\P_~;i9;NLΤ4y5=9,T(60"IhJM=97)>Tӳˡ׏3+n?+.hinnuM?'3aю=eme TA!3Zc&۪VK3ϧ/=ӎxFP_qUc#W(04{Oipf4fuSň4'24 'B W :9AY0"UkWjݪ!xHg1Mfgψn?Z1ruSsɪfz9aF3`ТQmbxNYv M$)og PWF3)$!+WΚUڰ6u+53VHlw?Oo[z#5iT7ڛ{dx9@ᦙ҈hOrx_#>l+'%X% ktܮW^cc:huYXvjQS'r ,y{/ 9`cjJU1FXg]G̀ssH6V.t-ad4u4҆us҆T U6ӯn9^Ƈ+2ݕٻG=FGq$ɺ\fl!F26G>"4%OG%.KL@mkZ:u77멙7_a8P]{{ڵ0jy-\@&r; *'DubtSa Q*.RF?-8Of.46 Xm|-^QHxBJ:ZL)GͥTyvGwaO)T*72lҞ}%c1 H2G60p\acG Vg|ʌ@Wxo_!pԞlLN$$prBR>0m&;#oS8Wۤ@N^e?I\not-1_>2F2:$ kw5Cτg&* &юr#0DnZ)@GtC;aG9/PD)\szI_ѱdS}Moi᭸/om%1S(ナHrI0r6uͭH*e1'ZK 1 $H0qfnv&պ^?#;e|Y[%C:ݒFgIS}<[\gD=$+EFW Fe܀go#⹜VC ?d&=lʦlB{EӋA‘ Bظ.дhQ1f7i0=t`򤸝;Ӻ)2~#;/Ét!Ule#U̍M9D L]ضbb}ࣺ Wvcb fœ,T=xڮEN:>?7^њ2\nnG#?nE:L]9l1I6z ]v,\xbHY>8&iIBu啹"<ʇf;)F +l˨lAGӇ6兇r;`$hLQgt+]dٰWl\幽ЛXǁ-ud B?zT〽F+PZ;G/(T!2C mVTPwHd4q(-m@! xpB ڀ-4fŒe$#7hJ{0xis3]` F DTE_K҂KSUme!:Qa'FFwV=AQ2ZعiT C{ˌa1-ĸF-mof8'1b# #Ec i] h92tX#?:?WF44;0.QԶM0/ |p6 3:H^uHc3X:3EK_J^W )CDKGPک?νH:4ټUZ݆[yW 8|־!͉F;42CuHEe*mU Ug+8Kkɣ0m}}t ǕG9:5gZx.Nφ>rT뭏z [Vs Ф,KC\3Hkzajot<约}Yaa:"G M?RWO:2o[xv sF mhϠ] gjwUH~OakҾ 7>$D*'wNuۂG+V6Ŧ]~WݼЈE lno0xoI +]y">#C6/FEiͶAՑl۩>s2.4uK8 뫡uS:O+~7>۹굯̒R&K`Q,LlI7!0jÎgrxK 0(w]8~%8gJ"2Y9t]7.@Y`)0pETbDTg`\=a}v?н6BYXNx@\HJD,@-;}sZ '8sBdJ6 tqSnF ;G2򃃺g>:1<`)@rFZ;ך]# swz>RQ=_5S2>[*|y6tʴ $?tBq{oI?wm!astǑgnd?}aaC42E+SuQ%; " 񃫾.fgzzrniYxzx`9,U8v #[3T D3W(ߘzOkY6_طȖ K[rkb쪻Ɏ0MMJx0 g@Y龅 9悲h7?O;: lW͞S=pTԣvN8XLrNaoDG=0\6ՠrl[h)rcXвK%H ,hByZJqLmr%jL\犵 rPj6i}YfM06+6KLT[>D< pi'ӋY8Ws+ú&XknMc55cXk}}? hVbx|i57WJdPL'A%cH`Upu0b*jOHw^iIx 5+e!Igӡm ڴRɧҷ/^L^8̃]vŃR 0z玸 >Hm¨غqs`q@Kd 31([0eL8-.~#Q/'|br = ,^k?VI76{<@,#b2bAa~2ڵ÷3cVsA*@8 $[(!jaKpC1N.zK&x𭗥=xWiBmaq B QV \f Ș֭$l7{)L^da#gТ  -!fDz_jLxh8>V.ã-!9l1?1i,QUF;Yxl`NT CLpF9p¸,(=wёp2Dq_z);hI`GkUӜP g9רXh{llw}o Sg0N R.Q7m/4吊7 pIUNP`!v"ȿ=xD[YK * IW;N!r?-sP.P@:@/*+W/8E9@9!*^3`ϑe(`Ȥ5:_˲CH.T#FwuÜCBԲSKc<b8Sq~u@橞!SN(vqexd} l[1y8MEjhԼIpO;KQk7ܒӉy{|9kUX:E;gDglDѷ *",P^##_x]z9RW)f:];Yk_Iu7Aq-i9{ʢ9\Ka&Y ,̕5Ż7ngĭ b>]G:@4 T-|`d* ]f;uOIk:k.Đ^e5\f[NNX6SiHlGT'hF¬Bkeu:Vk TeLL3'Z=P?+~c6ouPy h_kл+9@Rje_3hZ)/r':r&}){C KS+EjC{@JЖA! WsR~}E?q@oU$7~ 9Aw̓04$IG2 ZHư  >9U:#8neFι%:}Pc:Shj3l;j>tfԘm1s6:LIuFtcY); Չh=&8iALFц5IAw ޹>*f ?~< jKuGA` z󉵍$&mۼ:Pa ,'<:v߽\(-Zyb0$u\N4c* 8 bzZ uxD CJ*( o<`"XBCh4y[F?* 2p?a#ٽkPiMܴ/Ҳ`&#oyli;%*J&wewt\hNNl!u0 3˂A*hp?ᢠq"CU * 2^ nv}yіqZV}9Fޞ[*= P):˾}iԴ"9ܤl%HLQ K8Hb0*'ܸAHg06sO|KvLVoT+_~UG UKu4@߶0qc<p:G0<%>a m^73vjN, 3tϴGu6 Fڌ\pQ>COq)+Ɇ4fok#J ~gl­8|V8~!j߶bowp/ PLDwD6]17mS(K;G2@;Y껨lAP"D]#vw_uͭG3ŮG[<U\ڠa6ϊ'n0 e iʌ+h&NU ?LT=įm͇,j 8ToC98| Cc{EÊcl*\nQCqkj0w5['Mt}0甄C3tz??zϯ<Ƕ?qd8.+dv}7!Mz>l=l=lf=?xYhOG'1,MYҝEd ؾ5>kIgcOlHXi$         @͘+wSk^`Az9 V@ rI3% R`OBNW3@ȺaPx,:IENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xhdpi/mime_folder.png000066400000000000000000000005071516266226600307120ustar00rootroot00000000000000PNG  IHDR``w8IDATxܱ @Tiu-BPWpp(XX؄](IH$I$I$}nM;ǺXО{l[ p؅>+m@==Ɩ;VEޮV0;pVw 2j$Ou*.)Xb̧b1-````xQ} 'ziOXJ$I$Ibza&3;IENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xxhdpi/000077500000000000000000000000001516266226600261105ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xxhdpi/ic_launcher.png000066400000000000000000000607331516266226600311030ustar00rootroot00000000000000PNG  IHDRF AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~@IDATx eYY޽9w袛nZQ ~N"mQDA1I>D 4' h$&JP0v4]M]Us]Sw;w{l6nO{x=0>^n=n健}d;NfFfWwxs6NƣݬtTf7^mukGKمZ|[߼`4~۠[Wh&vK&MFwUmlGi7[WLlp(JfוOO.FHeN*/Ow n6QT|#)˞{V@h{>[<[W7Ȓ:f7w:nQySpgs)MR+%`< NVKW*_[FWO6Fz?mC[}ܹh;4hyFj$9۫ػWc^߿DAD08ða!I9 Hf:=DLBKZ(|(FWU^m 5V 㫺ߚWߦ6@fKV0{$'cQhi(4tEm F ϼo]rD~D!p 5V4+j7:Iǎm]j2boO_] ͥT^tiPKA3v\sC]wg@a"(ͬV#r:SX2@aaP,pZ2n7"THIkt]ek+f_[~H=M,**zGyDy-ה38T&MhJnĢ[lm hxrt}ў}'ʸu[uM{+jAt koP=Le/k"P@o ")"i<`)X7I)3Ĵ `’Hu֒+2k#-5N9IZ]ܫ+~\3jh?[[z/gͮs5 6)pX T@L9qAJ4K"L 'ݮ+e"  XjNfRPr6"и{3"1)g]#i ;:JK{o5zJ0Z]zFoTkQh)h4nхMu SCtK.p_6U**HV%l? U` 8CACeMk 5m:Ѯ4|w3WI[K~}4Rs}LWmbAFOWGzCuF "x*N-@"H hZX)_ݯ?:"H hG͕C4֓QTF)1"i=bTbI4;nrI#i jHӭ"/|jz6t3MW@>MW#h<BOU8H^OGhʫLG`45jSD*g!Ui<PL!ZH3HcF#6%NS~xe\}ro-@m7&kffW^׍tg}ٷaS G`Tk# C)TS&6ڶ_19I7[Mͧt'WҷHv_ `"p4]iY A9qp2Sx.t2E%0*JS`OY<-C4tY: 4:ٔ 24dLg&fF<YR-i^cuZ,`9{F+%&O7K͞w{gݍr,69X0]1h4!@Ӣ &+ʼnrpitjAƕON b ܶHɱ$71x qq@ FuvjaT6REZ`Awi'tBh7Ϳ^7vNrhBKZ6xm*`PiOOĉt_C S}SW3UoB8δxO/jxaTlcn Umr \  2ȶψJ $< c{FܩCc8U{Ew&;m{sO؃@ӧn;i/~?V]/o2uQGX̸w` wzvF \T8:TPTU+H,y3B:ES-8Uj-EM@(%FPP$x$Hr95Ng*3BKAwУտꮹ-@o|I\+D+xf]Z'  8h/tzq~/v јuE#r(4N8wZQU+Q"n BFC= '%(3E-3ݐlFMɌv '"'_$ faᑄ;-:UvԺ(/㡦 5D2gQmй0V $0( CW(=2 L'g@l -.P<6(]i;FA2SmCl\ nzh:/B^;0,鮬hc-",F9(=B-jG$C_cn *ݮmռ@/>՟mN~sFS;o>(ܝw?tݿ86}v1'K#72G|bw (RD@7Z\bNqߨDr]<B™X 8`U4*!c8jq7뤑颗?"m+>Fk7/GH8͢9b*&MD/@xOz]{>1ަgfC롓w|q}DkcK7MF]3n"^~wGwX uΑg 꾊p\I/fX{ɀGUCNRΔ u0pRYՈnk@@#0%ÊƪSaĒm>z2EimʝT[,j]ԗ+-O@' gc ͍@jf{nw\6#r0$d0 ɕF)4̽ƍqs)$n4e_%#sRrFT.8:t05"ks|͋*$8x:Ǟ6F#r6YiQ=ݣ*-~Gc ͟8GGZ#>f!M)#e8<(Ɲޥ^8e ATδȤp4sJPdcLYTHYlJ+|j9tkY$a$4$]T7˄%ktG֔_TreF 7hҧa[3ɽ{Mo c B+gu7|U=Wim;=|ȦQ$>xhxK?VGh2cTF=#pa cKZ y[(͡F[d]EUїBE^5z35hMmGOuy(A#}&hEZPW\]n;-hmi 33> HEIv)BU SzzB Cj5`/vOqe2}̵dH)L%&0Tp`*rރNne TDhVQwtׅòLQe#ˎ}&G1hc[ ްMkqx=7(d+{ j#AQk}+xTINfY)xs ƴKj5R _ǃGTi'E/dJT6 #G8#wQ|t: DY +zQyMY-͕ Z3A5<完}Hأ-i~F606 jF4U qH(G1 ˙aɶс eh\X:h23y"6D ` ,Fc3<,Az LKvzbB&d=A7,0B Z!) kpU Tc^⳪qzA51J\ MI =M舷?oԝfW_;U,wT(>"_DN a1)*M 7*qi,܀;u;r>^HN3J1QF"wXXP\uбٺo3dePei+NySS(ՕKOz~7}x$-ŖfrU{EY||W_ *>$}?Ws:c{^uAu`çOhA5G/z"ppuij S$9hi KnEnyɪ.n;U{IxaNWu'NSb^ӥQxCP} |CWO 7]||փ:K@ןPIw2냀|D]D^ nCak%n]PI$4vX:6heuna$MO4 4 hj1+k{ t=K3 ٝ_A[:kB8Z">>ܴ61܌:# ^JTΚ:pNPuwQ{2k 脆aR3(wi:Ω:-&vcD\LȵSxɢ>s֏qD2G U3 Ymnt#vw4XѺ2y^g-'XxQ6e`y,' S>ϧk!?CCƷ7҃G鋮%չְ~p2{#15 #4\~M=TO(h:/eOZlDS'N:n;[g@`FCCEќAF@3u-}4[z٥0T A,X#)O꿴N&8eM3?]C9> 39_HF+,Aa}0WKql tI#Ѯ_܍AdXjS˂pޱ!:\ ACx" dF=\yk}ҀJʹ( 5" l߫>Շ,:OF!ngNuo+ !H-;? 2)aҮ[m| "J91/[t!8翤}o#L 0Tf#,Fڒ-9 :վui|FnoDH!&lQ_t)zhne\(i3Ufg6kafs{l|͍Z0Ҡ((-(w6Y7}FCQIӱ"xuKsuKC%nZ@= Mrзʢ^ h7AK{hwJAY/ "iĐ:X FA`` 0x wΡ1A[q>04moӋFMk"+CH0 0Di`Nxb$z{$boAYxQؿm Lݴe]KʬX3-}C<|iko[8 Ub*{]w{eh$T-G#%>i TQߚU~j1@w%zMgx`c.D;G!9 iUܘgh$? ݨG_T} u2LU*iIL=K<MaruTuQU0Xm?tB3)FEeH{V5 };tpf60! e ʄ􀓎1qhS%BShAF}hzd~#W@2#'fWٶ ld;Oyt<Ħ^#*n 7{)hoUQyTS}5O>]ܝ 0M8TJWb`mtOB72Yʣ-؄<. :ɶU"r1`OXuw|Kcggzd> 4(0;_AۍMuTQy՝"DZ*3%\P9m #Рv:R\O Bm>vv.8 Еkin@[Ćov"6i=qG͚?h8NPԧkUKf/{ܶU*9撚@ IW.( BcDH U>bw@{*x&lcX\B-&n~i1YA)f|"׿w'5t涸􄲢1Y fN0˃Ǚsβ4.&^DD`oWT79~ǀ6&͍ ڜ~? ^k|5:h1.BP wdE.44OgU* Bqbt@3Ŝq~hF'P`vw!T8aMub8AtkW]n7ff2zfCD<`b4FRp-\*,mǨ,%9;;)=(>ρV>:_![%Ye%s_ԠEsV,Y8G:3򺝗#@SP9"d5emeNcxgf*9-] Q\q> ?Koy'#?ކn?lr5öf0F\Nc [?ܣS)֭%W>㤹[-znsc+4){KA$?N(-Lxh-0hB}rr!M\@lQDv}d`Yߜ^}g~֞<NF3pPQg[ S-&P3B\)E &I*xԇ83uK Bg¬u`w6d9[Guv/GpoT&0jiM?$^HW DaBUL1ry ]?o Q?='>sJf\?.| 5-Ze6qt ɣwX1^NC;[H֏' ^q4v4v,c$UPԋֈ9or!} !vG%g8BA,nYL/\/LdGԔ|gw3^zfym<TldUU@_Lf1¢>H_*D 5`UXPJ41+blF @4xJ,5G`r c9+6N܌t.0z7@ Mݓ<>DP4}?X!HDTQz1gbfh7 r)2 Qr3:4^TTF%n#ҥ%)!@xӻ タ? 'Pn*<>=Q}(xv5lX^xzp/Y<5y $D \{ә>ofI[kX)}gWc䖥,>ǖ S]/~K x/YSF% HuC@!U{fHQcC)ρ&z钪y4]%yWI@Mhmc 5]# Feh!&(9svsU((Ӱp?Ŗ{~4IG:"-yUSk%ݔYubH۔ ~Oɑ! \J\]@x{Eࡥ23cp8]Pt^i < vjxz\rdizЙΚ[S0e/S6ZHLYq,s)N ľDpg6NH?g2X$<"KZuJvP4d\Pc"H{}q"%2(^5!/^]w[,j$Љ_S].4JZrDh;F{S8@Z71n>o82 %[ e2EsLȧl‚Ƅ[96O.eJ͕FT[%:'P }w<2,Nz|OSex^@Zf4 "Nh8P jiq?%4ڐ-7 (-:sрn{gR5f!,a:[r3mKs] 9a}D/<6ywo}$j76 sGʢy3i 9JT:),bA+чW{F/L)= 2Z'ڼ@]QQ6!}I-e-إsSڴ`DM H: XROt ICwd;r#H #YynӖͱÍEh $Z`G:Cja+ 9,phEd9V"6eӘlIo !'OGMAmg'AUl` J Ϧ u8R$èPLE41Bpul6v_'xv}Y0*r$ȃeQr|$-nW9WĢ1VEYc:^*Sd!xW/x~Pc~|Nf/ѧ4q7&oqbW(B/xx 9+-a.zDԇ*YS9xrMq-hik\!5j9qlWu$ew="%]# _.oYH KMqT2刭:5Xvn9ܜ);fS F>fMHk "=^BԎyw[*{-h-dOlM*s*Hhjq\*5uM;:tE *4ToM88@_ZEObJ㟔LZaqU0"s  BVbdaO7;CDDk2$^]?'6kѳ-g6x:\j|xPLJNA4 ftF ΔGz la8 4i8diDp IPqҠҨ2#!$Fz;sZ,HȊ݆CeY~MovO_F!]??m~3uY, Pu uQj8bձa /$\myل=B9[;ɾhA^ww<óҷx _8e0醘e@B,YN!ehsud‚ESD d!QiCpçxer?=%OIN:42H_ׁ-^-HI eZir/ASBMWq.;T>|g#$*fDj޸MZ9Qxl yF>xJztv\B%57!}E l;F*+_cQcm GC>䝙$P*rN [WBmdь8> 0t5Y#@;"R2}N57O0)*.9x܆z^eU(e;'נŬjJ21~$rHٰ›Vȑ Ǽ~r )ZKw@ⵜuC5 Q\m.}s PTg(`xj2{$IGfK ~hेaO/i_YκM !۹g"e8d]1IjW0-:59#a>BSJMgyϸo0(s[v鏦;5᪘NH%i3;̖^*)Aj@r1/XP:֥]<'uX^~{@/]E,Y[-0_b1h(n/nJg@N|T? BdopUĐ,fisdUQ4]0$,+ x7KֺPbE!ǪP6h\ s>czirKءah'ߢ\z[ؾDm"82 sS@@9FpC} .nRmX1ejvXmC8>j»:F'_b_1>w%BjD"P\%^(=;⡟"歂=1n_6a!o0tdzхֿ%}d5WCBp}/EYA>HeQ*Zn q!ةZ6DϷ _jo^YT#<(ƣn麑-l:qlCoP[lPmxSRxRsjOQEUQj>6- i)%ڤԯ:M8alFIw H1?!/GKxnwpjMc/(~(͗1k~Ψ`4F#r@ ֐Q)lIt 06G~"R1b88 q` LBNрR56P0CeJuTL[[ȅ۱mlX470ᐉ8۩/kmXIH'[TSDh JPJ@mwJ+>4wunlGw$ir`YtG%1]̢PF'Uq4ĻJh3̌6)1:(KW1Q!kMKO`w+IIDATY 'LA2tuVOLؿ"@|@iYigʀf j"`Gr%5Bm:ߏ&^:L 4D葁\\ ,@buTׂWJBd#6:Y_Wn%l,v++o -ƾnel*7@:nͰ4muޮS4"tXm&`Mn=MB$bB0&ZDˠfQ7kʉ(x?ZMYU i0Tf6MպAr\+pҪVtYcp"CJOp#PV*yvîp=i;?Z<,?[?t}\^վ=BzN,:;?)BҖp\eA}uQǂCK۹! @ˇGoY<ԙT2,͗iy#o~UGDt?flb"z`8x4x.}xI?;[M[z++WV7C)K\gH3r5PP#+w9m1%-mqǜ+vVWnmddA[zewZFcp$ڕ[i` *T_GsD܍=:\QhFCB`Vʭ-h+*LQ60sPT]c{ 93-ü .j/9a2.?OυO¾t\hzLM3P qP=]̅/adNtfB^DU,%[Dgg V/STaNqk@ &>X [4⑅dFKk9뢏$q:YڱD7a  15p|`X[ n li+!nf o+8dĹ} 䯼"Gt+y(̫ZD`fcl r2C8JGXN/kHFg8O7*N߻[Y}xyzw%]24mgtAn>s gSݏ&QCЄv^*%˝ˍTb WD2 ȎoBi@&; )NѤQX qT=,]eȿ8YF*v~lzJ5Hأ-4V/qhc8ХNw|Z瘰Hˎjbh\<‡]y > ttTljoй<lhsRf='-!nf heߝ}ч?nt|E3Ac*e5NV;A8pʤo"oAt;~@+fta d!C5We\T.#M_/4EYdʳIh2i,H}t aPݸ أqώ@|A1X5#T'aP N$N)'2^U2KcBeKo/PhsXnMTqKTe ?2z[T,'<=ZZlس1~P焥@Xu~aaՋ-/ cDҿ+z)W=: :h@,6e3XqpC`NHQt!lȡoPNĔ\뱄Yy1-b>M3͉}_NxaSݶi:g@sV[M7]0r\WKԒcp[\% :L\Pd<ƞ1"++jl$nP 3KNlN ( _47duSd:֖/(x.oWD$0ڃF+t@n+Yd~$>9VJ!Ga쀡Xi0-W @cpM`j *XhtᒅB&M"'X I)QIc~K$cO f֡k+W; NI#g߲ `Wmwf~l?)iƏ 7~Cv(1W .Z W <eʜ[σ삇2}3# New1Q(3pTN*F'WWW+ptŅI )70+ks<GM{ɧW道َ_nc(f:X<$՗Qqig3w !2z\'gs-Bg`?}qV2]2tv:^4-hA7-`Ҡx-%a(siϱ+yWخ#njv\yGPokgjuV5dN Ctc .Pu[ s/^oFX3nHC COq-$. "iadLX5Y^nsx[MHa蛻}oW^/:aLv't^?")(+߮et-hPSrj{@;r]jEy-CE)|&$* mAF5F#ù&Ho?tg_NXr>N>>ڣCXh2izxO&mPuzYGN9f1ZKwuw"9ly%LH-B\vp@A+v19O++zpmv LF t]پϦw;9}4d4¡՝v4ӹgDc/CorZH,Hd. TT:7⃴DP˘l*Fdg>p$&E6ĻUs yY^ly\-[,U1ݿ6/6 "Mm{4E٘6J lwp\'A(, 4 A~0,"L9."P(@42`(M`8K#Z+OFK/8k'TWP_oՆ7X.r(@bсEQy\7˭t {׷/twaZf|lwxd/ktG?KJ$6-yĂ6a\x!)aRvG`B(*<4M 9_Szug{}92ݪy{ZLQ2띥Zc:cm6hKOg{!#NzcP+XK)ׂ 3A9Dp<Քg:Lj "~qAW=>w%6k=7|F븏{h-^~Z֯<A!-!7\E9ybz>/>acuœ>ڶg]!}WfCu٥ooԔu7]4|湳5wPwLv)n>7_ӻu\h䋢ޏBzwY}VC,o],^Hf8~@9 OIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xxxhdpi/000077500000000000000000000000001516266226600263005ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable-xxxhdpi/ic_launcher.png000066400000000000000000001126551516266226600312740ustar00rootroot00000000000000PNG  IHDRRl AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~@IDATx mYU߹>{P&cH%niMZVDy/F^T$ DAB4$h1HPGv[<{{GԼw9x1ǜkg^Cd~(D P|RG I=9PNv_ÍDd_= IQ' ^5?2^ū=sCIPүJ-TwLpz[׊ca"6b8oӤ;Y>OjC_ۚ?y:DNp?&ϝS8?,ΨYsE`bPk2ꏫ(5b_ﲢ3TBy YѓY}(=H|MM$ZVz_}vX>IpF1kׄ[[ JϦ^w5KgK$C[EwP)c([Òaq'p;j_EAWz~mlx?Ix-Zۍg [jrJp1SgګjĨU^H𒙂A$lciy$!7SL]qcd\ h 3۟&\4!ǎj"hB8O]_Y}&[v^e'xI3vO?Ba{ڞ-i^O{4N鉤'DYjP7$I|m0:[Wp& `x-( +fS WN0h|eDg +B`FO_ }bm~z}őڏN>q[@Oq3v%W jJOғVUkФpsE `Vw%6iG1;JIFlZ`Kd )rxZTtby qg[a8+qMmL[[x}B'jc|KsgQISq=>>%yuuH:F̒d֜WZ .uq<6 ֕J Izb µdž1Mregu>bG77/ gwM6 7v_G-ÙskV{^+p/ۜ{T߫pNɯ9ؒ>bNqC~AK 7I{s2,"y$'jE g>Hv~ ٕ2A@&jauBaq& LO[ AO zOХ5Wia; p.%'[yJ0~dI<:H* V~ I,uw7 TNOe DB׍* 8DDRv8B$B@2D;iґaW&õlyV*kdx?'d1|gVoimm;wR}Zyszf܉&u"V=lkoMO(b2( OeoYY`~_ 4DsbTrPn 8z_i|Ц{Akh[V:f۴sR}/WGuo/ԪFݴ>ZIスJ{ӏ/{'$3^c;fL1%gN*71D! 58$FhKSr߶ *f^\|jz. s*\ו lV<8g":<_5:II4IG nj*h$Q Y)=`4/]C&۬WMBeŇO@2j( `&1AK,yB۟%_7,Nk\41GJ['}𗏋 SggΝܩU_$>{SOwhJ'2Q?$6h1k )giO:szMZBm7(jѨv)Ey'zƞ1I:=kنyf_'ʋ4 D6Owo=ӏEiAxOo;/֞zU?&h$d-YnrkF) eNDj9CtN8!!iJ<xuv;4{}2OC6叔8`Wxp}X% x,oG3;O_z/QǃoԖؑR";wW[y}R]^Y`r .ݪq_'%GRFZQ{T77`{4I4DpCUObRUf9mm]T{M2ʦ\86K 5K/AXj y>!dOMD]$u m70 7j2`'ߏ>(D G*r?-sזgU3W~WGz $ sKW@tҩ / IV}Rt:ҩn4wjMLt|@Ý6t+s)3t-Yg!sK ]y#T4to5ٙm5Ul& 7hK>ߠDGީW>x0X'H}ؠUgmJ|&=볺@S+&N0zZ\G/"n4L5@*$YMrA PV,lkN`+ : [4)74Sf?%]B%nM 't5F{cn}-׼~i4AxPMo;],?G|MVJSJsj˳S:ˠ&a 6.JsdjtNxkAtڳmܸha"Qn <{gZc >7'^= t_@{5g͸?ozSKX .ez\W>8oy=\Yy-cHjp~{hػO+D?&L:xѣFno#ċ c.LdD,̹[FJ;ADd^-jF7iw "_'Dlt5XC4N/ǿ. ^j; =;<ښ_V OEI4B+|[3,!J1!JTN7 rӍH2ٹ!JMo`F^2 Bb78&k#X%9BKmQ:5=.|,Y &M7i)Z,ֱ7~cV}L'bi%|7$mV=}>r7< ym24qfL. $0d6f`^X|ޣ~N>Inv|=<IS jd-iQ)RB F]GZ0!鉘ƪ:Vp摵j-zbҊqrXݮZt=S_O:v_Xv_Tҳe;=yMdOd΃I14!(iv v)djR'Č/("Q c;e rHD5Li郔Tj: k4JHڲBf*dʄNfJj7}ٌ6a[0jM1&R$"bDB?VZE2F ޭ-̟sscxCዎ;=RrO *>uf^iXhי~w\f`TdmLK5 ĹAVeLp $1 \‚Zi3"JX2aj4FJ0HOhh\"fFdl$pӔdS㊰Ч5﫭q[2h/ @Tu%X3?W9w~zz]?Z>}3]; 7Ij'BwJ C@!Ѓ oϗEy"eҡJ$M: TBjI[ fam:M'mn^}"Gķ\p݆)иM%:p*J&j<.n J}_/0M{'Si-oS} ztM5\|Yۼ6=g_ށR6ID TX rzq)Bd SC:{7]ӾHq hv8 njBia Q0?ҊFHq:"cnOnK"f`92{}l51rCϿppK_`)ևaOrի uox>z~RD<gg(BxsAB4 t Q@a8l*+ ^JUm0m3AhrC*go^#x1ˀeVKVBO=,:ࢌ9!b oDNqlZ U|}[yXuU!JJO jv3^Ryի`7mNDlzԨbޚsp0*F`ϴ}%'yItτs\0΁71>{I{괮X=YL?y,vO]x$,83kEu n:.Ije[ G@u 8gV+P LG'mU^ep j`Saщ*<ʑ3&构!JČIwSURμ)"}O}KUtV1}~n\>mܦZgX2Nf^f,'&X`ﳻeܑVp`8g6LrDPQPJDl3 7uL_b$Y`N`TjFf:EGe!G˞g)<{ mF.heyǟ<I^[>}>( ǯcߧYq]eӼJ3{8ʫ}ӫg(*yGGa.RWc 5_jD0gA`ӵ5V@K O`wz 48xd3&l:hdƇoږ >4ۺ8%Gm'0ؚzCcL j >ԕgnxoH9N+džk*`V/6]{diOz嘿B+7Dq|)!FpPL)" \ȲW(M*Gy&t0ANiW5)%QH"W1A m;0Uҡ|u Fb> H dcپpiQJ#T˲:W_W~neK%p]:^^f.yW[t/q^ix~{Vռ_0!g~@K\*R?,I$鵭h |:ڮ#83[ox?Q\֥x ~^i,Xm[kl{Xq9ļIcnR{NltZN1r&<|c@ gɨ*F DX=+= NM+mXACg1pS…l8 @{rB,EK˰ ѴݩӉ{~j? yYw䳶̣.-ܨwznfb/酷l}>9'<[2lj<qiF7WǞhSв` <!SCƐ!}% 1;Vͩe4IUAz68$M,Deɶ1B;n 6G5E6(7ņ%7n~Blۤљ KҜ~RlȮ;u?;x+hpх=Aэ [.] { .! s L-RƵ{H5 Z՝])5d3M *zlTBD:QmÄ]ws}vC+=i-V-?MM;cjݓ)c:~- @}mKB:EU*o/Kjr`Q=|\:wzC{!P?w¦2wa A;x%AF?A¬Ap#0wnLvCz.kzXPPW3M#qSJJs*i\lFDY2Rx`lHG@u8iCBRBa&^61A_ϋlU="N7?j8]<_6n~u l}x`#X8e p {-@'Ek 'L2m>ԲjtIuB+j̹ n~Oq D ub2 ΖڠM䍭L +/UgEpNY9t՘<_փ6.>2A$@ 3jk*H?̖4ʠb\K ܧ89dDr'~""]M*ǮК:h8fčᒝx깋pyMje`*$ϋtcB#M ʴv+^iN?~\ }㫍^dNO~:41thc9jC/'7jD Wp.~رO6*")RBdk>?{]m!}[~h,\Vz7۰-%8]3o`&[8q'/Y<uXX q}_Omop_|ȽzH>|Pwt)%!0,V@Jm0qYqRǸ|Mq8v4rmG8d7}pN?&Q(XU7 IFʆY߇87E߲‹AlY;rgv˞YbJH)<-cOU?0\vxLl8Y/\{#chPLДl382_,Lb}Č _{9/\ &%7[Mx%#22,"/NsŬP!4+-~ '~ZkDI쒩):wfE`X80Q|b-x"GPs.~$D tj }+7E>$@Qt@d aBbEm/jf dDpG^n{qV}4~vS/e>Q,9vUymkEl{q[d5՜7M|J~xj0~*3|?O7Yɯ'>WD `&sژ.'fB +fB" YlI||.ɰZ=3|>8+gL}[?7R]x0 ֆK4ihCX :n!\F}pk8#V28{ aPM:4%TTНBnK = txc#ZG @wG Q]h#:puLޠCۋ+B}l(#4 | 䉷EѰsHKѰUVƩ8U_oNNB_5\AlxQNۡܗE'~/l}_{룚{J@DzߺDMGf14;t L$To7'|pBVQ^[-ہI)642uá# W7 JȝbI^o;sL;_d~y 7mP⚢KC;Vy*11YsnZx|3ߓuT"%S3PO†zEN,'Al s 1@]%+@Lp 2\gjHD|y;T6%4ƚV#ud,_]䠞VL@])@m.S 3AmVKbC^G˓P<0; p:_aӄF211%MrWOG5VKha&ٕ6?zˆ(Y>Nv;4Lt \삪a+siD[t8 g ocFG^K<и竏W~Yu=xunOޏ]7>:"J? Z?EYɯcVo& F#e-=Zo>e.^ Fh^ NHVf&2k1=!T/Nˆ3nV #`[Ցf'z.G$}U$[k?GOߍx]eZ3:mwaդ;4NΌ%וO69It! # 6hw?lʁ@B$}C-jmdp@A/37A@@I-@d>(I4CC`kTإC9<}'(O;O}8͛xLJe6ѡedqWѣkiވl$Ǣ 7楾a̜BPΔ@056󧼢 XSyWCOw'1bLq`NêAmQ!䡅w&"AO.pTH ǣ+ovW+7gK.8}J;5UNyQ2YvKa Y3pn1$`pON$_d8t),j_:C/K3&W\~݆T^0ԹRrW{ Pr.8$ǒ,@WA9}@/^j;XUVq~{(P1*>YI{fCɡ czNзiY/':Rm\5Ŀj/x5b#yyYGRc31t$o_-l$RFr$L—ʪ?o2@9}@7v]GWOAwFamd1H{ v^-'qn.N8hGFM[~Fy>5 rs #ٸg)ZX%2bPEbsˤ:xJ?3͚{7w#6q1>A"r+'xO &LSMV"9'nQz->޷Z,8bOrYGgM+Q4MpdH\S"Av* ~p*n 0 XmndxOOALeۜ:F#Ц8ɯ5?[!/dc`엤 Sxބ"e5g̘iμ&j*D}Wi=_$NoC[|^8WiUF7#2Bx4խQ%U_1/o<3@rM_Z: (7q4#(QS~[&@d$ȯ3bv?`g`Dd5SN`PyiLy6ng)$.:,%ff,a}puǽûIKT҇= %<̃{p@( [_ԻR*D>3~ȉP|q#9gs 6P@TKwLZY k1٘K6.aG[$j5cvؙ4ur[4ץ]aax=íOy9;*@+I%9wJ~VL!wLt3]bZHqi 1^`<)RyIq y[\GiEEHm,΀Ïn:'΃L ߴ JS[1{7Lpɧ ᨢ_v{x-0dip$0]"-'?wع"wD>t ঴k, y*⪳gRy0m4(~X60 -N'>X/으kSu%x{@<(@wa[>0wd{[V>lS\mE$=7Iy}E|ݹm t)s@ 0&Yմ1i\z y5-46 n6`;u%\ .9t^}RGG#e?Qv^^٨!,ҙc05=z #>mh:e:darGIuv=_4|ܲQOakyE CF0Z' z MDJF BnO|3e6 5-B;vi!s)d3ޫm;!dKWAb;ݾOEquE0^gc :! ȄqFЏpnYsb:$Oo˕^&%PP_LM|c]dd&Q\N}Ӥijyr[enyLD0j{~O?MzXeEy iF&#fM cj{a9H0~<vMiIWRVb((< +Qy ч\[br#G0yRI0$Dn7)80R8j5 =BVixdF.u 0,~BY픪Jr 1sLO 1-Ɖc`tBfq&t _TlrQ눦ۡltIcu\>e4*7:F͉k\A=ʨ#i7#@fc>lt‰Ɓ"nZPM`#;o,n3l4z,kv>qza Ш#;BEN vQcˈ-VW}f4[DAfF"} N}Ê NѧJ?oåm^v=\LU<9h՗-`B%1j#AdQ[PphR7uOhш^&:'%)ˤ^n?׏; a˾ ŒgZ/HkX(3F5(p> y*3?+9#(&2G!7tyX|JKR.?z>8(jUJ NHq$֘cJP sӭRYi\Ӄ,. wtؑ߼fԋpˆ_\,\$Ûz=ܺ&um?Y'Se ԸزS~"\8_e5G'Go^eU$^Omgjͅߎ Jt*^+:ϙI&o;FB!YFQͿ05!x U]=2SYJG*mXw(ŐL%L@[1OmxMGm.[WR6`]Mi;\]ɫQ:#KTݴ%O#Ll@ Nw$U?hRÖĵQM#UgtaK$J`3Sf`Y Сsض¹l*h.:I2xc;AsGYĠtB]xJk'D[KڴwuU~oz=Qr[V~61*# h>a$nmܷ,ڙEؖBc:_tiu9$S?V=O/(&ů3k41Perq:R Q[3ݴl$&⸺Tu8d?'\ @W`8aX\@^ss^ocb'ŕ&*[C]auJ;%̖M"0';bvI2LMl}dؑ^KT4c1Mn_Ѫ-7 tJ3 xTJSPOƥIa`B$ų7!z,ۨtؚ퐶B2L~F I'L268"Ebʐ~˫9y\u:lרhl#bO@~ejsú$x>'8߶䋝aעxdM]3ޖɆ-,F,$jT!3>pZFVpg:ӎNi|42"H4.J|:[>pXo-#ٓ#..mjW@1ujX yz*!]+ʄe'~`&ʇ:͇xb*yLTwmaGZwfr +m$&[Mޘ&z,ާhx-1(Ol7DEX~$7ZpeTbj\0AMt g@Nfkj)胦hm*7xhaqd}Ll=б\A4˵m+R!">6>d!O==oӫ~fx'5/2wZsjXlD6#엽BjjA>mz:$(^yW>g^p=?4+xO5aKI/*z #:%GۊWlfsu ~xOm IVJb^O4C6\4]Ǚ! F^2^`/w'ne޺kܺ> +ūK25~7olGEtc5E0s W5bfL ×9$֗fVUbقQ{Pi^w =숎?_~(,W?z# udƄ}6jmQRd_Uбny7`vc:*BE4%GůP092U#p҇ .ëڡjna&Nҍo-ϑYXZ)PюtM,S %׀r.Ў3 DF<"GE 0 fsֽO#dXjiTU4tId7e\C| p:*N"* .rxKYWuэ֜ F]hƃ´b8Hz#*e}J$~pmJT\r Sw(Vg}2 "bsmh" ڢvj&?}5ӟ%Ƕ 韊%\@!]~s ϦYB[}3e *]qjyɔypdJU AuℝI1:S=nV/F@S+jGon%ՇIpNF ᠯtBc+l1ZaBuaݜm;ۇP`۟͞8怱4pY<^}eW.W-/:̲\a[_?[o'& 1Tv3#t.RЧ.qvȌ  rK&AYon(tE];`gCST CY']1tcJ@jEigO{)zFiX2~4%GUж=e@ pp= mNJKU[]Qp șa 혌QӅzjR52i Fi_#dKzedLEuwHP3O$olle}@m7jT0x.[Q.'FN8h,oZ Ă;xyJG()ÜU/ s{k~m8.)7$46[@ULMq x\x*^b݁t^d& L6GבVM8 O#ʉ^xk37+Ds\G$ʌb1qXm׼7ϙ=8]t5Ү>`4bc2~SOw};v?w ` ;쩸T3 sPʦi%I*0" m88hT, > +Ma[ѮQ/'sIMe  -`I*t-yEلIXbm0roP=fS鱗MXC:#tz߼35:yĒRqKu?z7wެ p~\fHzop30-gc dP4B^U7dGvg.Mx0v]Hp]-awѩL?ƫfH5}'xBEF01EN'Ǻm*QC`W/Ji'c-`w|@+ƔN*]`B⧕KTvv/CCMco֓_*kW̸q^``hS (n*Vѐ}|?!A Y| ,Moaf9&]%+eʌ!:0 L#klKIE$73Hy#Fp@*H4Y p. PYv !>E*jgm3ؐ*γ(k_ï?;xb&7uO@ʯ JڝpԑlƂIӗG^ m{cOÇaόlb>f#dOĻDdO^+FI3uI4N9&GX |+@i$We}&Pc^>=|wUL2(-[}1hf#4tX7+ Z|V5K>^njGbk}r3nC{gNz9\z&+7dT`i1Y쫆F>Ad2,}pRXxI) )3qGmj>:1NjXfV/=Ȟ7!)ቈ0b'Ž6l,J':~j`W=ؠh>'gᒤM拌˶ZkyE < qPWCmc4!Q Bab6lǮ|'!ˡ5|tsBJZqˠc;G"m[2hS:hrY|wDb>lU}^G@Uڪ74 Wp"z#ii[ 6ZbIWC;DӢߕ'9 DmgL3oY/ ңMpx- Tk'Ĺ`l&ړfOV9xS~wg ɏCOGGd8{Z?Ui]hYc^&4C :H2(a5 ATQXiJt,R~Hփ:" KSl*6NKpw~9H-*y2C鍸dt+vhXﮀĎ2#J6c<= ĘPdE iI&pUQ82gtUsU}#ȑ{ qrymaGlg.{0uY9dOO(9𓓆ّ4?mf/b)s]0w55=O0| V,.:Z?cvIB'de1:h>1zJDV1ұae:j\ |$fGDwT~·\EN͗SM9ɍ"+t>`"Jkem= BA V׳ssm΀h k-(^"$b%1% (F0(&bC! 6U)tJ˥R(-U:s|~ϳ~3st9߻^Yk~2cUv|&dÉzr$1RzbN |gPq ^w?I|v}[ǹ/sC5JiƉiZTUij}`f5.霩;nGYM wk KԹa6fQ؝hQLed{̵7WF/yH.Ȃ|lhč(x L;ڴ !BnR6RO%n|g hý(ZbgVO< WK h*w.luzWKFE[ə;zHn\j1R9AI!d6n D E+n̂<6U׀2Z@6W˷ir?Ag !Ot ,3 1)6h*O'@z{ ԅ1DAU6]S rHӢIS\JNx =7wp}QΗed皻g4)JujՖH?zOtk'_fl$֥#t쎦kO\ZZ\r " ~VFD0U$磮̓K8#۴R)K4z`#4i:BVŽ܀(#вZ˲d`T_&>]]?w'+u`>;Wg9Ի83VLU4t4wv@gӢ(G6ְlCN0m Q*#D ٻс?(\D#$(&^@s13LTGGWyBt{\S,5IET\m+:%+=L6͂Ѿy<"yL.p85^}@$˵r.aL?@۩ #WҪnXxkY 90Jםb#)39"(]S=ʷբ`gFR6d?mSor1%GE\k3 )hY*U{お ؔ:FG}&s[P$DMl'o e@->Za7-Əsjlk~b]38)V㹶HL-wk܈yCq0X Wz`\h{kXɄuwQbWTƎ|(0j[BF@']/N襎+-EA9F{ BI`)&A SXKl(YfsgqרB.aKβbn}}w9?>sg5po^Y_b]7 U 铫4[AӍ TuOF6F/:k[dKʱ߃e ]) O'X G䢀 f*g־I7x5Ԗ(~֤\Bӱe8ʥh_YZ 5 ](hNSs~TUr BLO?|1kMO%mKg= p?%/^r(G Y:ȝDgMIfraO:֝ pf֠x̳ж8׀u+piMLLTa?lEбi)hVǙtaDH'Vm._foXC%ۄymD2~;,-ٚMN ҡ6R֎0%P;ʆY9>kz&M94Ng=uMQ@G-0#ܬK fH5]boehxDpV*ks1V)@"r !?E#ͦڦfk$Y@KuB4< a(٤6ѠćR8iIOUDVnmַ/75]Zev_{~=XY_{uxmu:'8s{rYN\HAM$q0I;/2U6n/l7=S=¼蟺Je?'Ha4آ VE|@]د6czaE`/SB[i+9n*}j@NP76JJwK v(;2>+㻲zbs灝t&U.dg9C׉pݭKt2t"Õ+)mAO|t`FZNiaDe"m*`/'Ҩ1x(VSe26AݾU&sM0fNOVLh P p5(WPa,S9,l5ô=wTeJZ(t=K>Zg @M:p.#nPp,K! )M#R8XHJWvo qgz8QVrGw0QFc4Yf Vvx0mMeaz2z3sٛ7A X$ Cq6/BxPC6E?"\0vNְf.uGmcc ZuU/y E )Dnv~ګ7VUIvN'kxr}]_ Q~Q" ʊlC%0өكzQ-ZޏjS壣 3҇5F5PV/gt'V`m"NRXJG^5:nzZ-vjdt 4OĨx)-ʵ%lX Ė̊IQm/_`~0nr{e|\uuu:T^őFi&TNO'jKu34y:lV;h.E!?vEhut1Wm͞-õ5'6",hcn)ITR>.7F%kFƠ|ZvBfLu{feJ ubi}<< #OuܱXq'L5̓W{hHGXf#.с]`3ACAH#7Q4\uv-= Bl7^l S@tExӈw0e* [vPi@LED ]ڶ g )+$^ބHmP4.tz后SG$N qD{iŷ(rVbP!+wǖ@-(ⷴ͵td##Trl JLh]&¸AfoÑKZm)۸MI(Ks`'֖yTq<9;\%S< Kw$9BXW#4Bb@CK6.1+C8{G1*́BpucZ C׻,PՈZ}ߞ)C Bybk/5r)E';YloYD@͓zk[=4 : |Iƻ?i;HǶ V5ne[JAְ8pl Xfk`UrTyMGmm 0ƴ2Q,Oԯeo0֕ GʝMxIh!z,f:Cs܅Ck9/TI-o0l.-O8\n.Xvuׂn:Y铔ܙD i6|pUg21`GVrPڛŖ5g.xLЍ7 gz,{K'1Wc%s'uِ.4 ov "P?ߎ7\$Too{ :hLSI% BI>qMn'TԹ-]S i{pۭ`/:f6'zs7U<ϑ&֙Ѣmpw|м4ܫcL쮍w&6l ] 6WSf(,yΣ}~laQ}{'ctwӡOɻUW 5yc0UE k6z,(G##^+7J_mCysˮšRO} 3]0!ڠ%m_!nĶoe vBb#& k~^kmv޶d Knؓ7;;xEͭ{ #$k23Gan>K<Ͱ(֦Zҏik3,q'k1l+Ҹ 51D- :|4/N vAf\-f+Wh2cvyG>~e7s48#b iŷ54=DNmq!\uxӚGaDEaC̷]x}L'vE(fmDG]6./]}r5w)(S`]#/y<ųރ?+c. @ۋf |wGwqѣM6IyVa=ZR*CQIf{vå>%;sFȈne }څq#I0MV(NXK%S puo*Y'D!d+p7+Xx뷸eGk?O/ @Cu$xB_!ܯAM3,bME”@t3q!Jo7Jnt !"j%$[vpLS=Y:HĚadUPT}m+SKÐ*ǎYYkkz_you[l. @ r4H.\|94ÂDV\4zT`Lr?]zQ%vk`Pa1md\Z,te7 K\Z]NT.tQ.N'˟ݩ%ѳ[Wr#~tVWUKp_bi>g*?6V JN .4 lhTLIPBtTgb:n6US/>cF'@0.ԇZK %ZA]J=嫱5WIϾ^GIm}ر̅l뇺t4M:"82H82 S h(WaVVM+Sdf~gv#GVHg:Urb5| Q4C.p:،(xWOr8'BYY[}Cq)Szs{}-ٻ}TW'X1Z9)6`jC[y^lf KbJt;.]t}04`FhɋGdȌ 1Tef8GYr;ǘN,SϏK&$'@? ㋥/sZ8:G+EZteh 4 t<`rT(#']g7o$Q):O;0XY5PSȫc+X"+Wt&yzsuC1pyn/ }&1ODX S2 s&,ÂrGPʰ4r VDp]DC4Ȓ f,F!?A) yo\.|? 5|4AW_/(.snn[GM'_ 94%:1*B,fLx(OzYThsQ(1Ԗ#>O`co  }w q='|| ߫;ߦsg1 N}8_ZMui#'a٤\w|"ᓅ\{d8QwYYb[r^er?em{=kwhɾC<7W& ?랾8 _Wm <"u$ 0;gġzzuL(sā./}>Ge;'N~^[l],v&XDޡw;G a6LZҫ@Ogͣx^PBGKݭ}@{۵?pʧ|Z]I[\[=k>[_ȹA{DzY:"<]`R%!pB5YG x,ĽWmp;<]M^]-qlyP}w;KJv=pefGk#}'|N?C J)'+ŸՏ\{ =JRJaΏZQ?"5a>%4ٳ|+[[>_N=]IgW&KnÕ_3+W&0Wp=?,IENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable/000077500000000000000000000000001516266226600246065ustar00rootroot00000000000000custom_progress_bar_horizontal_login.xml000066400000000000000000000017021516266226600347740ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/drawable sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-hdpi/000077500000000000000000000000001516266226600252325ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-hdpi/ic_launcher.png000066400000000000000000000065321516266226600302220ustar00rootroot00000000000000PNG  IHDRHHUGgAMA a cHRMz&u0`:pQ<bKGDC pHYs   \IDATxyǿ` 9C1D %K2U (x$b,ģ*TD#D$Qc`Yv~g{zzzzf V߯WzWz)d~NtN!yS mJ a0:b9s9O-GW]{ 6kvW6"ݙQkwZa)CcC:J @ (KuĈ"@t /0`qxbg/%g9w5 ԥW($Wed@CxԿ1m R/MSmۺXCLdlIGL: i{~/y$_0tݛ?Ňo`̖{e 5n\ 2lMr~ME4 >D ܥҼ=)n۽O^mކ2s 36wGm;,;h$pgӪ<;^;c9|.` 鷋̙ SG+rPsy(sơnұ+5X114LJ"=,=†Hk#zR_Ao  /᫃w/=!R D:l5Jc'U/GDZOQ80_KĴ@G3XRtG}ug~ ]LgS/)`1p@LhWLw)-<#rgY!TO@5GAćyNww4zH1c6|QF nդo"*'z4BoifKqb-xlK"[Sփr|Yփml>v`G'~c7"(^zݷ|vA>ރޅH١uرD\6e͈K}76BVOabpZo߲|=,^v٢*ÓoGR#uB87!܀LT>[9[Ă7) Qz8/2XyE/HQ#>Pp&kMHj9}nc 6^ﴱ%oVEPQq]m7ܣ=""@zkY1༂1i=g#Ms$WcrsN8AnSJ 6s()ƀkk|9(~rK&X.{N@EVIJdb"'T IdsկUn!|.b q8epyA&FFVa&lDF@!%ju1$Zjnss[;, 'Ȭd_& %pE1tbGr>pRĕM֋AE˲(q~5k&wt9W䕂zNi@UQ4\UWI.zt?qGRz®pVݓ5UN8AQec! D+:q&f0tp*^:P1@ @O2 ј'e;2=1YX9>sE}SJx-I " 䦦9Jv2[)>[%ՇkwvD!22l͞;V7) w-& f`ﶮWV8}|wQ V0u6XtsC`DΠOpyhM/Hw]9IϻGo `\TY@ @-6: RA $;Aa, #Zp5PcRyzWzWz@$~c%tEXtdate:create2014-09-05T10:57:58-07:00A%tEXtdate:modify2014-09-05T10:57:58-07:00͚RIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-mdpi/000077500000000000000000000000001516266226600252375ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-mdpi/ic_launcher.png000066400000000000000000000042361516266226600302260ustar00rootroot00000000000000PNG  IHDR00WgAMA a cHRMz&u0`:pQ<bKGDC pHYs  IDAThY]lΝw`&8`Y412DҖRP) J+AVhJH*m&`EE54&qulvv,Zvv~kܐ"uvܚ "?>oҷtI|moEdHΚאw!)w߸0XQxsu\|b}iW{_|[s?8)b.CcH'z}VRfRO`"e1r|n_g~ʎˆ ɱ=ΪKf0gwY ~{ӑ{il_}Glܦ&V\Ètԗ ԙ3g ľl8q Ư@{lx)0_iO4~6`[l(51b v[cMM2 Q-U@w]t DV5~ɾ JMO=W+K[f.fdjнJm~ϛ7ɍA (:NIԣ3#W2H$W=ZBQF @@nYӰ>3{1Ae@GGGlͣmWkxmբe9(h }Ryů BѬD@~gO|.w<&x^޴\#zvkWgDߍ;Ar 6_l&:̐ONeS-"m~8pY_hhbcv4$M%c8;@>h9WKp}ڔlw)q+)_"☵KԳ_YF@mZB|@Hn~t_lDo>2Lһg 6u-=(km[ԝ|&*dSj1@i7'O#S85*. і;I_[hJ\eOP#79} LJa+nJ Z"(Dz_)`/޻p)v֛dd Z2E_ /tޑBȜZo$+VtjqepdNa^V{F.mIT"@HCY TPxJ岅aCd%+YYi+y~ bt@ߗ}]Ia٠rwzf&à֙IAʼn"dKd?H ""@ژ p*= ~>"h@~Mp:@غs m  [6u#qu%6/Y) "@~]"D圿+A/x#j=)LPЙԎ%ٗ ABFKMV\MO.7|PtO*hbq@s t-Nk2)_V7B=`'Si-}mVrsFk!"sCtH*FE1t@GòεerjB4!1U!ϥ%еzFd&/$y2e趗@WDd&({ityX>H(n@>U@hZ:֗^Wu^(NM+v>/"볕 Y@u/OB1k!;I$ l_J"Tkem1̥S¬}? ]u9HRbomL7V9c/{1n%gI?vj¿-#dm셇\:QMꓯl,=_.knRW*(\aloF,WPk'ը0ܔ/G j$qC/#`8%tEXtdate:create2014-09-05T10:57:58-07:00A%tEXtdate:modify2014-09-05T10:57:58-07:00͚RIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-xhdpi/000077500000000000000000000000001516266226600254225ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-xhdpi/ic_launcher.png000066400000000000000000000113521516266226600304060ustar00rootroot00000000000000PNG  IHDR``w8gAMA a cHRMz&u0`:pQ<bKGDC pHYs  IDATx]{ՙ}iy0Ð fAHLDѓ5jFO"*Il.9=DI k F(Qaa~T}z|>@խ{=!!!!G*lb mVߵt">xeA*aYm©|5΅{F>7y/_ 6aÿɻ+&گΟZ\a)@=q| (LAF_s;a1zȋo6V47~vam\I\eO>J 7jkT&m\LJ{v!$ppAe3G`zjR o ƍ.2qƴtT~sξ*A7?{?-+v'/u l\Lƞ^t-.rQ9}QBF5B.1OC$H$A#t?7Cwo3BDO_zrϦ?=zE rWWVgݯ_@'^ TtPc7Դ}| |{WM=5v,O 8Xq(}{[7,nmm߿$Np@!¬R(wYs =e~8o}h1Ѫw'%1򍗮̌u{/Dj=n9 (@js+4ޱPd(jinvrլoxq ۋ++$u=#FH(R!jብRl-O&?tB\pP4ж"ʽYƂA(nVaVl%R twLb@J/ #ЪODkƬU!5) 0x+{,MP%Dž͓OYSTŧ_s 0{⛗MH?T׾yb?:aY7 EMI#gY a tף.nTի/zvb]KW,E@T${q{iBM^SiToljZP__[tӨy[*qS3BzWo$~Qۭ*@K rALJc?8)HW>o@ۭAsnZsgHo.҅6bb<T?Ӄc8>TFF`R|X $9xR<,oA@9.~JԴM3G|d1;m0ف(a9iu1q7mĮvol߼``&hgeӫ 4r6挼*Gp ",dzG^聣qspa5<4O=p Im~^{z>(K N=Ew0mB܆(|{n×ޟpa]U@IX[STyH㨌4mSQ`ɋrqTE>Qo\yϼNc ~ TՎ w3 ncOJI;y$oAa)߀$^<|[Ə9ANv Òh6 DUcݯ`kVjM&;xxj_Xe4EQq $FO`NןqwTB\A$>݆ө.V J{sg+V~&8:^lH/"~C[--H~ W6IWTڻlk<7rZ Xn֢JsyޖdJun+s疞-,qo)DK'n>R4ba-n*,YW gq_$qVfh@$NVt)?0nI3R G^c^ o͍+l?AZҫeeDf&1z]{Opؑc䖭V5G?F9a /+oO^[哽f'fY|N}X) y >CN`ϳRbyYҡ'C-EeLҥq\| Eu%x{LήZ< 9Y61  H^ ?3>0 4Og kfYRMx*tumY)DȠ(A52iDm?4 ^(|_RLJDY5\l̕+y05g݄~ 3u^+%Y̆ Y?Yvi{(iӯtڨdLM\MRS:k5EB:].MZ %ƴK1U>^9%? q+g?E E Ɔoݶ*պ; 4%TQne'&ڼA|s9ZeJw&%fǷ'rTsڹ|F/0TB!A"0e=cߚ`Y!?їLpцr| 49MA򖜤q9WZ*`d<ޑ"q &M ڿs0ffO'c ~ϱ~ 5)T"#p@sG\.Ƀ pBvPyWJN`8o/]J'~WwwwB{Lcr?!?ԓ9O[3CI $>a)ݬg\ X4ÉĮ w@;P<wtUP9iΨSɱh(ߜ>Ϫ'ܞUӽmz[7xmX#ސVA$R-Eo^@: LAO*IAxMAf( `0Wq&(CKgQ    Q>?Mh%tEXtdate:create2014-09-05T10:57:59-07:00JZ%tEXtdate:modify2014-09-05T10:57:59-07:00kIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-xxhdpi/000077500000000000000000000000001516266226600256125ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/mipmap-xxhdpi/ic_launcher.png000066400000000000000000000170461516266226600306040ustar00rootroot00000000000000PNG  IHDRFgAMA a cHRMz&u0`:pQ<bKGDC pHYs  (IDATx]iյΩ\ L (5F'q1qxѧ&q5$OM\1ꊑȒ{5HbpĀ0cUk꾤?V}ΰ>_P:ꨣ:ꨣ:ꨣ:ꨣ:ꨣ*֩3{PzhW7\F0E1u$X{otЙ]ɝ\ª۷ D"0B?K/=2X掷s4((4o7D^!G 7?< DʝTG) =E.9WDw~cj#-Wu񍣥ǒR?IYd$RѮMwHQ]r?\Kg!`<@{Y$bг𶕿@ L@u.&Ӆ㧎OxխuMop-(u oh]`džU>r(dWEpBHO;aS I" v숑z/Cu,w~39i񍫮ޗ @ *@9iOVۼޅzzRSg-jii2)eG ~L䀺ܠPjKȩUKFӕv&ݕo/Ld:RNXTJxg~g/ GT Zdxi٫; K:N4 DcݖJ F)08f8E%]=籋Tʱq9]:4شpExq: c` 1IR)( ?ʁ!ds+{6G}(ʈTi|sGXԢRl]N!ߜ5:yZt >kbꄔp>;W=*dקaY Ν{t+>$|F R( bGxp4@Mb=s{?>÷^Vɮ'^„~QKjg-w+w r?:E 9hкΘyȻN"">ɕNlg( N9wf 'h| 'frB̒ >o!| a.1ogWEˤ& 4yF %˗$7>x 2yz ?9TaZy] @K?>lLl>q3~wN]x7AR@_s ]g>b G ƞnmQ yD{Zz֗{_ҕ dRP͉:e$Z=zAW{3|4<>xGy~d+Ey9 C97"#dtb5 T ޝ~>ܔAѲC_9+V۲~?!r|("AN$Z{xp:dtbQQ1Cl976͒3o9H{Z(cpxyz֮I=× 4=X1zٵC&:G ebۗT I7o t(zw'DɄ4>Y G~QԳkC1$Jߥs{v B>Պ HBb!_hGtu pS䬔 3V]0&!T$ގQ\! pƵol}a<* mB=C|U󣞗 1xA!'fJA7f 3΁/V  2K2̰Ut OGzIu؞g;Z0|pGι9WuM{D,7=vwڥhRhEeH;OgHP9ޮBlYKzy-OU~Ԩ64{~U4>fIKcڱrT\F`V#7 $s᝗,{8$ +lloo=a#JW\~~2G;r?bLBEhmEUx7(nРA{~woHQbTvfVB@__X ) y,eEz+N<$p_$zK@Ct !O~wő;MWb?Xm{0c#ֽ@@AĞeʓ̺k jէU͊);F7#R*ȂkX*"٢N4Li ˑĿ<H_w\˩$8}b84 & m/N|.D1P4+n7zgD S`SC0(6. v3AZ{)xAG|%FsȈ}.˲5ѓ#bˢO?ڇ m*[[[c?zWPKgXDL8mQ)Ov?1*Ⱥ}#p6 <2'__kWrgo^’/ӽ* \|P[ O(lR)9%(1$>Ėz1G"ԆކS@Ioe+\z^5)kxn:T`r0:ꨉ#\K1l"#Cq׀# HxI^pm ;`L^ʑI6?6aXQ7\eQ0UBEFbB,}0Er\;8Jٯ 3uJG%u] AF \ sPq-ǻJbxdWy4${VOQgA o,:Okd81^y Zo{ rꑣ"=Oх})-Z Bqq+GsMdC3n_0۲9mV8藥~H$_q;7 #& ʫ2/;#(fDϸj1*>#811(mخݩؗ p> <.JNJ3u +&h2f2"+ ӯ;+~Sc{H]uzw]j|j sw"IO'/;R+w.s,f|Cj.O;Vǥ%Y3).g&0S19|SEZ +"I;-" _ IF4NAoJ;a>t(@9uyu&ؕ܀ϓ8ߪ&Hiٳox| yI)lz J,Nnk[]Moz`Hw+Qf/ 6< Nlպ\}ؓNjUDxD)=-.oE>NvS=5Hq_}IǜӲe,OxE._:OrxZh-'Q_uZT۲Aږo q[C1 CF*c1 ]<*uQqeʵ@Ǩo {?<ndo9*3hLG- ? (XךGp*hï#qN&5c<#fO2OwFJ>[űm<hؘf j1p .C*0 7p|IQ!U D&#-|[ w#?{_  ͑8eFh!o p?4N4nN ,/SŸAH)% +Mc{{[FUa e,/! RBG^ q B2), \lPt@ZCtJČ3\%2%*&6 6qx]NPB5,@#6RdLcI!uF(P)MKTO@LL<R3m]L1⩫w{1 osoL ({iFm(dL%*~[g(/g)Yd`bR Wbfx2Nq$G a>n8UBdPJIGʲY4yLKA̎P:͒@$@V$tkej= EJ'Gw)ٔdl}t1AVFb].1,bLmHi*P&e3ULlBͥioZe@Zme (2 6Xt@ѻ0]32bo9-,*婔Q yK(VW3$ʴH7%$Ҵ=5jiOeq*,zw;2絾5D6 E8?AK1WG L j;+9 mAc `l ha}ʷ-P spTVc.'rBXBKJu6K֗*m (u~KXƃβy:Z?҃Z3:]Ze9E Ȋ[ (2 0^2hr`̹BNVuuaj +w|/K;܀U>I<Ø~&r #l( du41rq9僙!nڪ y|&\0@@Kk@,jz7H |Gt{/){q[JF FUu{+r˅\/B4X-1@|X˿TtخN& jPlڳ@:r!]P~:d*lb#/''`n <<<tP$;CPinD A:q^gsq8[ )kڅ52#fp&McO~GjхYe#ݙ!hh ڡS].L SdA|ypy)<0>s]%q؍It k<@Hb׬ e]Fs9G`M\cڎYrTC $vDIdli.;}]'U0P aLᄡJKS;އLZ <]}[>!ʌZufrGJ8)3`޼{ /|'\;uΈDD D93x Q8/C:we=z՛7rR/$\-s#)>U|N4 ?@sҥk=yQZ] <ٖ޺kOz~)+Eq~ĸx1y7<B"D!B"D!B"D!B"D!B1@-@rj 5nATK4`e0m7%=s܍Q$V"X{k|OW,=ʔsS)8k}Z_|kȆ\ J{݂i/,_{$,-&1rR[ܾ Bej=/]t=\(|MBqUAS`qe.@Os+Nw ZCT*yu-7rqn4,Q—n<UWmr{d5R#Ƀ\AW|Զ#v#fHh' ͌UEPи@ݽU oEW TjAG~+y5􌲊C@@ۺy1kCRPD[_|) UQY |ڣ'EZP FPlX۹l)p+n[ 1}{JxԸ3i`J6~Pl<~˛ YRw.>+BY qg!'!` )vJSGq+TBxrc絢 ʾ]OYIT AL6M$3~+\:J/*A_W/@,yUtJw-4l EE$(~m;[6ye &ϩ΀T<~щi4L k{oX( *AV* ~5wwvHVҴ⛎_B^(ꎯ]%}#"|-Gݠ/? P4O`ģM['l9;o/oAemen fيV7Z!t_zS&Otbb]۷=).kg["eA:~MMM9yU1 1>j~nz eT9}}yZ4 L)^xh.= }Ҙ§yәP7^ެ1s=7!$H{$. {1ф-MD('2dW,?d̀u0xo|2N)=r{g7wiKJݗOΝs .PmGhpG:1K@[wkw[k$:g7Ο?zYc7"My)*YHT,g Q2A J%h m G8pOx\|ZH]]}k[\NbM}]u%6fWl|(,Qw^;= _niYr(@4hNX1jޖޢEa_OpO_(\6!RL%CmMK<EAbH2<ӷK|5;׬ٟvjlkDkbw:aXP|y[(@2W"H~A f9}Yh$f.~bQ7uw@D6S~ҤVW4mҴS3 Rqњ(.!"ׁξ샟m:::T'\%cݨnh_p—tMeH'%F3=ݭ=ISfʠ"gQwDoY2_Q+5laPF^'GZ#;n܌€X:xvqY82v%=Rb?h\ zv^yO5Œ;[۟\sd,KWOu`PzM{u-kBiˤ/c6@hG=+{Q޵ӗus^t_n[DA)- BU4oyQ#'y7D\qd9 3mt?w|IATUWH#7+toN 3GS6v"V7h?F>Y"+ɂ;!="|Hiaou ~&0 5羐&]>GﳿkX>\QQ i6p]T(N]  ]uǒfe$ݐ,VQiD^|v[:ֽ[}]R _93ԒX q;a̕Ρ+Խ;O9%|t`2HyOxGZ͵>?*seb]^93Ԋi ?~c4k")AܯmS L~72sB3?4Psf YYN=cyұۊ8P*#c3Nxױ{D`nszh&f@6l}}cTLL ,Lޚ_ٺ [kZjMoY(qNj TP0>}5eS-t'Mi]қ ]ֻh4zyq"(촭+ Xh0>LYgw\Y+b7TJDú=z۪@?_s@-)V9%#W;9Ww|(q'W.jF jE2kJۏYNǧ~WQT>DG[yO>Qw~֔4~_v賢5?ami|4;})yO>$S~E?ԉ_9s+Iy^jdP JIj̜K'\9Y/wMRyyTRd4YcgSowwKAUҼz@ym\Vmn+r*>yԷNmf -QCھNs,ݍPWYrRh{9fL~v3|Vٚ)@sg6,kW׎_)ɍ޳IS~@AR"/lwkbV+ Y9ō0|qc E޾'OpI'`:&޲0~35ÿmGH{C@8Eљg2_SnJ.Rg![=sf_a_ (*@|亓.פ5KS?X?5({~z=iZuT[---~y43+=/:Qd{VS7{`FXD\:ABa,x$?Ks'E;{â(jAgKѾԯF`X[r>}1SWJtN΢6?)hC|!:1"Y{^.܋<\L>0d*l-o}DTRۊ^p5<'F 2l!7Fkl6lyt_2p'FB95u"Lhָ:S۰g=6t[;e9-3]6߈c0,Z(7ςHbpYF `㐶%aB47iaV.28,vu=ՓEݠJ(U+~HRugcnYnk6dك2sqH5쇮6tw _Sdc3LV>Q{*5 Wݞh|2RZx0wwP\u/2l[ WUv6= Hғ[8b3_k"kIn|<`ʰB#&)NV| pD0e}6q{݁6LVO}!6x2Hnͳ1r}X-˄.$aPOԑ-;v * E\zr/,{܍+8#%:K^ ;d X0&T .etM7uHk@0!4;g D]"뒇{QO&]"œ*a#$qgkp3: ;b䍨 ] 'L M `QzbՁ|ldgYץtႽP qe6dN31,?U!:[[_ l#a3}Dt6r^.ղjFu--=-ۖ6 5g{Ƹ9gh)ɗՏA$|\oT,@;PFߪ xO94{BI)(:Sckϫ 6r|:v)ÿ Ms1":aK+Si˟(vҟZ˾qVq'm:ܬ]RB $CRH3p.Rbt!B%p+ 3,{@f6CQWFfЗ&Hb4D~5̄ GxʑI`V@:0|fU5+XVs^DVdnÝ(iև6g dLl x#. #"?#z#"SyY)+`j21 s9.oݓ?">WA_<푣P^te+*I|?Ү[ٽ {N8p$&f&>P Tփ2}M uK'3O"@fаY W I >:ˊ0h&%qb!L7oR-UAi< 0Ҳq@YڷqX=flE 1 uP',%`5 D&sQ*_PA) +@je`̚RjKeӥA̾,(,lj*[u˶s2r*9B(/2(dR Bt,S@ J ch mA b8Ġb6>< 1 AIVaMee#^MlCZq)_5(Q3i|bVW%rD,+-N"_NBYaىh\\>} RӋa}zȥO)s95qAI㨒߅y4u<Mӌ \ 23ŰA,<%4/TL&e"B#1B~wNWZ@lXTNh 9TRGqr5uru>& @5?1Ȏ "4&R;AMyztu KÎ\uILZc9*q(w*ow!\)ʭf]ZlwŲ?B5$)L'pi8٢, N\4j]3H&%m㻔aϡR\~`鄆a:"PIhs5Eբ 53458:3VT.Ɏ8^$ӣ~m@rq+DE6b:9Hjv"#dS.ê 4q9\CΫj@ꊴ 4hBDʠ,U"\En|2q :'Eⳬ\<3;~갫B9t'kp_H\Ħ'M"q\uh&JVuxp}*)@ѸҊٔMO`_\C >2 \oC(e4eZ:դ J@MqSbP RA-b.i~J,k6@4iwJYV.H鲙2m ʸc>*ٴjrkYGCk/('U%Tt}TJ͘F !]="_/raT^ ,$[v+ߟ;ӎ9hQ*e,2*]uxm/3%0mtr8i,Lj &tA@{R(±lvnC%jhB~@@ZDʊ1m/W>OrCoau )z+aLy +/=J8_ hRjDs%0;μj:UJo,{|z&/!J`G~#2]i@g{k~R @Ԭwџ 4*F*x+ޢ>SAL0:3—dkS; ]v^ z3`*yQޡ,f}$lݭx8?EWF~)H$dӲ= pKbh k?:FJ&] -"Q_1ˡ{oQf}(G/=%%P|t}U0Wߛwg)$ڍ  J fӝ߯@禤s֟\QX.Z[pBWXQvϣxW[I1,~9<`M 1% w&wU%ˣ107^#JDg\juJr|'֟ua%Fd|D_Zd\& ayn6 RRv^Pboyt[1XPw!Ͱ[(PPIPZE)f'ycw ?)W'o[Z01$-v+NPK;;.]_tow[]+ Yl^14-]殃YOіEX}oqRh^IT%Di[ؕ—&+e *z vufSA/ h˹nw_dCr ^@$cw䷃7\Y2@|aոA1wdQwӘ w*v@s< ˍyEֿV{@sFh'+ﷂB >f穥 `<|'&әpSe*XF~ tLjEJ' \^sXWnn ` E (d+ń-Y@~ &.PmL{'pnjx s/s` ?3P08@FsQ7ޕjH]+Zn,Ƀ̊v.@)Qz#<- lPD~#U8)꣤վl++n-r(zBUZǩ+.(Y)G~ٕyʂ %`lZ߹ :7 ^v2K\j=yĖTxϞEA@ z=,_) LgL}~ V{\X}:{{>1Q dO%\K@O~Ն\`Z}/I";e2 *G @l૒̖|JBTؿ]p˂O~*;7%1#'3O% @}sL=diİĬx c/(F^@MXY5\1OL6Nꧫ_zFQDOPݟ$ %JAA 'N㨮C>Tӛ+U@,CkQP;0\~hÅؽN(B!{ z_k˞cu@Yw~b*d`} Fd_@Q7H;pBUU Կꠗ6=V]G!CڂqAUUU3*"g[=k5!`F;ءD$CB!C%a;Q"D!B"D!B"D!B"D!ih%tEXtdate:create2014-09-05T10:57:59-07:00JZ%tEXtdate:modify2014-09-05T10:57:59-07:00kIENDB`sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-v11/000077500000000000000000000000001516266226600247315ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-v11/styles.xml000066400000000000000000000000321516266226600267710ustar00rootroot00000000000000 sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-v14/000077500000000000000000000000001516266226600247345ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-v14/styles.xml000066400000000000000000000000321516266226600267740ustar00rootroot00000000000000 sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-w820dp/000077500000000000000000000000001516266226600253465ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values-w820dp/dimens.xml000066400000000000000000000005651516266226600273550ustar00rootroot00000000000000 64dp sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values/000077500000000000000000000000001516266226600243245ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values/colors.xml000066400000000000000000000003201516266226600263420ustar00rootroot00000000000000 #3F51B5 #303F9F #FF4081 sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values/dimens.xml000066400000000000000000000003251516266226600263250ustar00rootroot00000000000000 16dp 16dp sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values/strings.xml000066400000000000000000000024541516266226600265440ustar00rootroot00000000000000 Android Demo Logout Login Email Password Please enter your email address Invalid email address Please enter your password Incorrect email or password Generating keys Logging in Fetching nodes Preparing nodes Cloud Drive Empty folder file files folder folders Logout successful sdk-10.11.0/examples/android/ExampleApp/app/src/main/res/values/styles.xml000066400000000000000000000006101516266226600263660ustar00rootroot00000000000000 sdk-10.11.0/examples/android/ExampleApp/build.gradle.kts000066400000000000000000000012021516266226600230130ustar00rootroot00000000000000// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { mavenCentral() google() } dependencies { classpath("com.android.tools.build:gradle:8.12.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { mavenCentral() google() } } tasks.register("clean", Delete::class) { delete(rootProject.layout.buildDirectory) } sdk-10.11.0/examples/android/ExampleApp/gradle.properties000066400000000000000000000017421516266226600233210ustar00rootroot00000000000000# Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true org.gradle.jvmargs=-Xmx10248m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 android.enableJetifier=true android.useAndroidX=truesdk-10.11.0/examples/android/ExampleApp/gradle/000077500000000000000000000000001516266226600211775ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/gradle/libs.versions.toml000066400000000000000000000042521516266226600246770ustar00rootroot00000000000000[versions] appcompat = "1.6.1" exifinterface = "1.3.6" jetbrains-annotations = "20.1.0" compose-bom = "2024.12.01" compose-compiler = "1.5.15" lifecycle-viewmodel = "2.8.6" lifecycle-runtime = "2.8.6" activity-compose = "1.9.3" kotlin = "2.0.21" navigation-compose = "2.8.4" kotlinx-serialization = "1.7.3" [libraries] appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version.ref = "exifinterface" } jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" } compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } compose-ui = { group = "androidx.compose.ui", name = "ui" } compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } compose-material3 = { group = "androidx.compose.material3", name = "material3" } compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle-viewmodel" } lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle-viewmodel" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime" } lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle-runtime" } navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } [plugins] kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } sdk-10.11.0/examples/android/ExampleApp/gradle/wrapper/000077500000000000000000000000001516266226600226575ustar00rootroot00000000000000sdk-10.11.0/examples/android/ExampleApp/gradle/wrapper/gradle-wrapper.properties000066400000000000000000000003741516266226600277150ustar00rootroot00000000000000distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists sdk-10.11.0/examples/android/ExampleApp/gradlew000077500000000000000000000206521516266226600213210ustar00rootroot00000000000000#!/bin/sh # # Copyright © 2015 the original authors. # # 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 # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to 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. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" sdk-10.11.0/examples/android/ExampleApp/gradlew.bat000066400000000000000000000055201516266226600220600ustar00rootroot00000000000000@rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega sdk-10.11.0/examples/android/ExampleApp/settings.gradle000066400000000000000000000000171516266226600227570ustar00rootroot00000000000000include ':app' sdk-10.11.0/examples/iOS/000077500000000000000000000000001516266226600147575ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/.gitignore000066400000000000000000000020441516266226600167470ustar00rootroot00000000000000### Xcode ### build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.xcuserstate ### Objective-C ### # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate ### Objective-C Patch ### *.xcscmblueprint # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # # Pods/ ### OSX ### .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk sdk-10.11.0/examples/iOS/Demo/000077500000000000000000000000001516266226600156435ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/000077500000000000000000000000001516266226600205235ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/project.pbxproj000066400000000000000000001007531516266226600236050ustar00rootroot00000000000000// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 60; objects = { /* Begin PBXBuildFile section */ 4114CBC81A07E9C100B7D475 /* OfflineTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4114CBC71A07E9C100B7D475 /* OfflineTableViewController.m */; }; 41663FAB1A01571E0035F7D6 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41663FAA1A01571E0035F7D6 /* SettingsViewController.m */; }; 4168127719EE6E5700C72800 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 4168127619EE6E5700C72800 /* main.m */; }; 4168127A19EE6E5700C72800 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 4168127919EE6E5700C72800 /* AppDelegate.m */; }; 4168127D19EE6E5700C72800 /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4168127C19EE6E5700C72800 /* LoginViewController.m */; }; 4168128019EE6E5700C72800 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4168127E19EE6E5700C72800 /* Main.storyboard */; }; 4168128519EE6E5700C72800 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4168128319EE6E5700C72800 /* LaunchScreen.xib */; }; 416812A819EE8E9500C72800 /* CloudDriveTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 416812A719EE8E9500C72800 /* CloudDriveTableViewController.m */; }; 416812AF19EE8FAC00C72800 /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 416812AC19EE8FAC00C72800 /* SVProgressHUD.bundle */; }; 416812B019EE8FAC00C72800 /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 416812AE19EE8FAC00C72800 /* SVProgressHUD.m */; }; 41954E8B1A1BAF1500C4BB02 /* DetailsNodeInfoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41954E8A1A1BAF1500C4BB02 /* DetailsNodeInfoViewController.m */; }; 41AD803F1A2745580015B79B /* SSKeychain.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AD803C1A2745580015B79B /* SSKeychain.m */; }; 41AD80401A2745580015B79B /* SSKeychainQuery.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AD803E1A2745580015B79B /* SSKeychainQuery.m */; }; 41AFC10E1A2C745800867F17 /* MainTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41AFC10D1A2C745800867F17 /* MainTabBarController.m */; }; 41AFC1181A2C746B00867F17 /* Cloud.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41AFC1101A2C746B00867F17 /* Cloud.storyboard */; }; 41AFC1191A2C746B00867F17 /* Contacts.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41AFC1121A2C746B00867F17 /* Contacts.storyboard */; }; 41AFC11A1A2C746B00867F17 /* Offline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41AFC1141A2C746B00867F17 /* Offline.storyboard */; }; 41AFC11B1A2C746B00867F17 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41AFC1161A2C746B00867F17 /* Settings.storyboard */; }; 41B09CB41A26479000CCBC49 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41B09CB31A26479000CCBC49 /* Images.xcassets */; }; 41B4553B19F1218000439D45 /* NodeTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B4553A19F1218000439D45 /* NodeTableViewCell.m */; }; 41C681D31A2330220088F2E6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 41C681D11A2330220088F2E6 /* Localizable.strings */; }; 41C681E71A2363700088F2E6 /* ContactTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 41C681E61A2363700088F2E6 /* ContactTableViewCell.m */; }; 41C681EA1A2363E20088F2E6 /* ContactsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41C681E91A2363E20088F2E6 /* ContactsTableViewController.m */; }; 41C681F31A23658C0088F2E6 /* MEGASdkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 41C681F21A23658C0088F2E6 /* MEGASdkManager.m */; }; A8CCCFF82AE1954B00E3CCAB /* MEGASdk in Frameworks */ = {isa = PBXBuildFile; productRef = A8CCCFF72AE1954B00E3CCAB /* MEGASdk */; }; E80D15911A236C7100148421 /* Helper.m in Sources */ = {isa = PBXBuildFile; fileRef = E80D15901A236C7100148421 /* Helper.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 4114CBC61A07E9C100B7D475 /* OfflineTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OfflineTableViewController.h; path = Offline/OfflineTableViewController.h; sourceTree = ""; }; 4114CBC71A07E9C100B7D475 /* OfflineTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = OfflineTableViewController.m; path = Offline/OfflineTableViewController.m; sourceTree = ""; }; 41663FA91A01571E0035F7D6 /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SettingsViewController.h; path = Settings/SettingsViewController.h; sourceTree = ""; }; 41663FAA1A01571E0035F7D6 /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = SettingsViewController.m; path = Settings/SettingsViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 4168127119EE6E5700C72800 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4168127519EE6E5700C72800 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4168127619EE6E5700C72800 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 4168127819EE6E5700C72800 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 4168127919EE6E5700C72800 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 4168127B19EE6E5700C72800 /* LoginViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LoginViewController.h; path = Login/LoginViewController.h; sourceTree = ""; }; 4168127C19EE6E5700C72800 /* LoginViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = LoginViewController.m; path = Login/LoginViewController.m; sourceTree = ""; }; 4168127F19EE6E5700C72800 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 4168128419EE6E5700C72800 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 416812A619EE8E9500C72800 /* CloudDriveTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CloudDriveTableViewController.h; path = CloudDrive/CloudDriveTableViewController.h; sourceTree = ""; }; 416812A719EE8E9500C72800 /* CloudDriveTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; name = CloudDriveTableViewController.m; path = CloudDrive/CloudDriveTableViewController.m; sourceTree = ""; }; 416812AB19EE8FAC00C72800 /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; }; 416812AC19EE8FAC00C72800 /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; 416812AD19EE8FAC00C72800 /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; 416812AE19EE8FAC00C72800 /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; 417BBFF019F57FF10081D102 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 41954E891A1BAF1500C4BB02 /* DetailsNodeInfoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsNodeInfoViewController.h; path = CloudDrive/DetailsNodeInfoViewController.h; sourceTree = ""; }; 41954E8A1A1BAF1500C4BB02 /* DetailsNodeInfoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsNodeInfoViewController.m; path = CloudDrive/DetailsNodeInfoViewController.m; sourceTree = ""; }; 41AB68F91A0943FE003FE608 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 41AB68FD1A094459003FE608 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; 41ACFBC61A1155AC00905ACF /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 41AD803B1A2745580015B79B /* SSKeychain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychain.h; sourceTree = ""; }; 41AD803C1A2745580015B79B /* SSKeychain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychain.m; sourceTree = ""; }; 41AD803D1A2745580015B79B /* SSKeychainQuery.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKeychainQuery.h; sourceTree = ""; }; 41AD803E1A2745580015B79B /* SSKeychainQuery.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKeychainQuery.m; sourceTree = ""; }; 41AFC10C1A2C745800867F17 /* MainTabBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MainTabBarController.h; path = Login/MainTabBarController.h; sourceTree = ""; }; 41AFC10D1A2C745800867F17 /* MainTabBarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MainTabBarController.m; path = Login/MainTabBarController.m; sourceTree = ""; }; 41AFC1111A2C746B00867F17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Cloud.storyboard; sourceTree = ""; }; 41AFC1131A2C746B00867F17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Contacts.storyboard; sourceTree = ""; }; 41AFC1151A2C746B00867F17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Offline.storyboard; sourceTree = ""; }; 41AFC1171A2C746B00867F17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = ""; }; 41B09CB31A26479000CCBC49 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 41B4553919F1218000439D45 /* NodeTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NodeTableViewCell.h; path = CloudDrive/NodeTableViewCell.h; sourceTree = ""; }; 41B4553A19F1218000439D45 /* NodeTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NodeTableViewCell.m; path = CloudDrive/NodeTableViewCell.m; sourceTree = ""; }; 41C681D21A2330220088F2E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 41C681E51A2363700088F2E6 /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactTableViewCell.h; path = Contacts/ContactTableViewCell.h; sourceTree = ""; }; 41C681E61A2363700088F2E6 /* ContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactTableViewCell.m; path = Contacts/ContactTableViewCell.m; sourceTree = ""; }; 41C681E81A2363E20088F2E6 /* ContactsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContactsTableViewController.h; path = Contacts/ContactsTableViewController.h; sourceTree = ""; }; 41C681E91A2363E20088F2E6 /* ContactsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ContactsTableViewController.m; path = Contacts/ContactsTableViewController.m; sourceTree = ""; }; 41C681F11A23658C0088F2E6 /* MEGASdkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MEGASdkManager.h; sourceTree = ""; }; 41C681F21A23658C0088F2E6 /* MEGASdkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MEGASdkManager.m; sourceTree = ""; }; A8BF10C021ED1114006B283A /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; A8BF10C221ED1119006B283A /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; E80D158F1A236C7100148421 /* Helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helper.h; sourceTree = ""; }; E80D15901A236C7100148421 /* Helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Helper.m; sourceTree = ""; }; E85477501A24944900C848F8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Main.strings; sourceTree = ""; }; E85477511A24944900C848F8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; E85477521A24944900C848F8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; E85477541A249F8800C848F8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreen.strings; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 4168126E19EE6E5700C72800 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( A8CCCFF82AE1954B00E3CCAB /* MEGASdk in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 4168126819EE6E5700C72800 = { isa = PBXGroup; children = ( 4168129C19EE6E6D00C72800 /* Frameworks */, 4168127319EE6E5700C72800 /* Demo */, 4168127219EE6E5700C72800 /* Products */, ); sourceTree = ""; }; 4168127219EE6E5700C72800 /* Products */ = { isa = PBXGroup; children = ( 4168127119EE6E5700C72800 /* Demo.app */, ); name = Products; sourceTree = ""; }; 4168127319EE6E5700C72800 /* Demo */ = { isa = PBXGroup; children = ( 41B09CB31A26479000CCBC49 /* Images.xcassets */, 41C681F01A23658C0088F2E6 /* Utils */, 416812A919EE8FAC00C72800 /* vendor */, 4168127819EE6E5700C72800 /* AppDelegate.h */, 4168127919EE6E5700C72800 /* AppDelegate.m */, 4168128319EE6E5700C72800 /* LaunchScreen.xib */, 41C681EF1A2364270088F2E6 /* Login */, 41C681EE1A23641B0088F2E6 /* Cloud drive */, 41C681ED1A23640A0088F2E6 /* Offline */, 41C681EB1A2363EA0088F2E6 /* Contacts */, 41C681EC1A2363FF0088F2E6 /* Settings */, 4168127419EE6E5700C72800 /* Supporting Files */, ); path = Demo; sourceTree = ""; }; 4168127419EE6E5700C72800 /* Supporting Files */ = { isa = PBXGroup; children = ( 4168127519EE6E5700C72800 /* Info.plist */, 4168127619EE6E5700C72800 /* main.m */, 41C681D11A2330220088F2E6 /* Localizable.strings */, ); name = "Supporting Files"; sourceTree = ""; }; 4168129C19EE6E6D00C72800 /* Frameworks */ = { isa = PBXGroup; children = ( A8BF10C221ED1119006B283A /* CoreMedia.framework */, A8BF10C021ED1114006B283A /* AVFoundation.framework */, 41ACFBC61A1155AC00905ACF /* MapKit.framework */, 41AB68FD1A094459003FE608 /* ImageIO.framework */, 41AB68F91A0943FE003FE608 /* MobileCoreServices.framework */, 417BBFF019F57FF10081D102 /* AssetsLibrary.framework */, ); name = Frameworks; sourceTree = ""; }; 416812A919EE8FAC00C72800 /* vendor */ = { isa = PBXGroup; children = ( 41AD803A1A2745580015B79B /* SSKeychain */, 416812AA19EE8FAC00C72800 /* SVProgressHUD */, ); path = vendor; sourceTree = ""; }; 416812AA19EE8FAC00C72800 /* SVProgressHUD */ = { isa = PBXGroup; children = ( 416812AB19EE8FAC00C72800 /* SVProgressHUD-Prefix.pch */, 416812AC19EE8FAC00C72800 /* SVProgressHUD.bundle */, 416812AD19EE8FAC00C72800 /* SVProgressHUD.h */, 416812AE19EE8FAC00C72800 /* SVProgressHUD.m */, ); path = SVProgressHUD; sourceTree = ""; }; 41AD803A1A2745580015B79B /* SSKeychain */ = { isa = PBXGroup; children = ( 41AD803B1A2745580015B79B /* SSKeychain.h */, 41AD803C1A2745580015B79B /* SSKeychain.m */, 41AD803D1A2745580015B79B /* SSKeychainQuery.h */, 41AD803E1A2745580015B79B /* SSKeychainQuery.m */, ); path = SSKeychain; sourceTree = ""; }; 41C681EB1A2363EA0088F2E6 /* Contacts */ = { isa = PBXGroup; children = ( 41AFC1121A2C746B00867F17 /* Contacts.storyboard */, 41C681E51A2363700088F2E6 /* ContactTableViewCell.h */, 41C681E61A2363700088F2E6 /* ContactTableViewCell.m */, 41C681E81A2363E20088F2E6 /* ContactsTableViewController.h */, 41C681E91A2363E20088F2E6 /* ContactsTableViewController.m */, ); name = Contacts; sourceTree = ""; }; 41C681EC1A2363FF0088F2E6 /* Settings */ = { isa = PBXGroup; children = ( 41AFC1161A2C746B00867F17 /* Settings.storyboard */, 41663FA91A01571E0035F7D6 /* SettingsViewController.h */, 41663FAA1A01571E0035F7D6 /* SettingsViewController.m */, ); name = Settings; sourceTree = ""; }; 41C681ED1A23640A0088F2E6 /* Offline */ = { isa = PBXGroup; children = ( 41AFC1141A2C746B00867F17 /* Offline.storyboard */, 4114CBC61A07E9C100B7D475 /* OfflineTableViewController.h */, 4114CBC71A07E9C100B7D475 /* OfflineTableViewController.m */, ); name = Offline; sourceTree = ""; }; 41C681EE1A23641B0088F2E6 /* Cloud drive */ = { isa = PBXGroup; children = ( 41AFC1101A2C746B00867F17 /* Cloud.storyboard */, 416812A619EE8E9500C72800 /* CloudDriveTableViewController.h */, 416812A719EE8E9500C72800 /* CloudDriveTableViewController.m */, 41B4553919F1218000439D45 /* NodeTableViewCell.h */, 41B4553A19F1218000439D45 /* NodeTableViewCell.m */, 41954E891A1BAF1500C4BB02 /* DetailsNodeInfoViewController.h */, 41954E8A1A1BAF1500C4BB02 /* DetailsNodeInfoViewController.m */, ); name = "Cloud drive"; sourceTree = ""; }; 41C681EF1A2364270088F2E6 /* Login */ = { isa = PBXGroup; children = ( 4168127E19EE6E5700C72800 /* Main.storyboard */, 41AFC10C1A2C745800867F17 /* MainTabBarController.h */, 41AFC10D1A2C745800867F17 /* MainTabBarController.m */, 4168127B19EE6E5700C72800 /* LoginViewController.h */, 4168127C19EE6E5700C72800 /* LoginViewController.m */, ); name = Login; sourceTree = ""; }; 41C681F01A23658C0088F2E6 /* Utils */ = { isa = PBXGroup; children = ( E80D158F1A236C7100148421 /* Helper.h */, E80D15901A236C7100148421 /* Helper.m */, 41C681F11A23658C0088F2E6 /* MEGASdkManager.h */, 41C681F21A23658C0088F2E6 /* MEGASdkManager.m */, ); path = Utils; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 4168127019EE6E5700C72800 /* Demo */ = { isa = PBXNativeTarget; buildConfigurationList = 4168129419EE6E5700C72800 /* Build configuration list for PBXNativeTarget "Demo" */; buildPhases = ( 4168126D19EE6E5700C72800 /* Sources */, 4168126E19EE6E5700C72800 /* Frameworks */, 4168126F19EE6E5700C72800 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Demo; packageProductDependencies = ( A8CCCFF72AE1954B00E3CCAB /* MEGASdk */, ); productName = Demo; productReference = 4168127119EE6E5700C72800 /* Demo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 4168126919EE6E5700C72800 /* Project object */ = { isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = ""; LastUpgradeCheck = 1500; ORGANIZATIONNAME = MEGA; TargetAttributes = { 4168127019EE6E5700C72800 = { CreatedOnToolsVersion = 6.0.1; DevelopmentTeam = T9RH74Y7L9; }; }; }; buildConfigurationList = 4168126C19EE6E5700C72800 /* Build configuration list for PBXProject "Demo" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, es, ); mainGroup = 4168126819EE6E5700C72800; packageReferences = ( A8CCCFF62AE1954B00E3CCAB /* XCLocalSwiftPackageReference "../../.." */, ); productRefGroup = 4168127219EE6E5700C72800 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 4168127019EE6E5700C72800 /* Demo */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 4168126F19EE6E5700C72800 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 4168128019EE6E5700C72800 /* Main.storyboard in Resources */, 41AFC1191A2C746B00867F17 /* Contacts.storyboard in Resources */, 41C681D31A2330220088F2E6 /* Localizable.strings in Resources */, 4168128519EE6E5700C72800 /* LaunchScreen.xib in Resources */, 41AFC1181A2C746B00867F17 /* Cloud.storyboard in Resources */, 41AFC11A1A2C746B00867F17 /* Offline.storyboard in Resources */, 41B09CB41A26479000CCBC49 /* Images.xcassets in Resources */, 416812AF19EE8FAC00C72800 /* SVProgressHUD.bundle in Resources */, 41AFC11B1A2C746B00867F17 /* Settings.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 4168126D19EE6E5700C72800 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 41954E8B1A1BAF1500C4BB02 /* DetailsNodeInfoViewController.m in Sources */, 4168127D19EE6E5700C72800 /* LoginViewController.m in Sources */, 41C681E71A2363700088F2E6 /* ContactTableViewCell.m in Sources */, 41C681EA1A2363E20088F2E6 /* ContactsTableViewController.m in Sources */, 41AD80401A2745580015B79B /* SSKeychainQuery.m in Sources */, 4168127A19EE6E5700C72800 /* AppDelegate.m in Sources */, 4114CBC81A07E9C100B7D475 /* OfflineTableViewController.m in Sources */, 41C681F31A23658C0088F2E6 /* MEGASdkManager.m in Sources */, 416812B019EE8FAC00C72800 /* SVProgressHUD.m in Sources */, 41AFC10E1A2C745800867F17 /* MainTabBarController.m in Sources */, 41B4553B19F1218000439D45 /* NodeTableViewCell.m in Sources */, 416812A819EE8E9500C72800 /* CloudDriveTableViewController.m in Sources */, E80D15911A236C7100148421 /* Helper.m in Sources */, 41663FAB1A01571E0035F7D6 /* SettingsViewController.m in Sources */, 4168127719EE6E5700C72800 /* main.m in Sources */, 41AD803F1A2745580015B79B /* SSKeychain.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 4168127E19EE6E5700C72800 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 4168127F19EE6E5700C72800 /* Base */, E85477501A24944900C848F8 /* es */, ); name = Main.storyboard; sourceTree = ""; }; 4168128319EE6E5700C72800 /* LaunchScreen.xib */ = { isa = PBXVariantGroup; children = ( 4168128419EE6E5700C72800 /* Base */, E85477511A24944900C848F8 /* es */, E85477541A249F8800C848F8 /* en */, ); name = LaunchScreen.xib; sourceTree = ""; }; 41AFC1101A2C746B00867F17 /* Cloud.storyboard */ = { isa = PBXVariantGroup; children = ( 41AFC1111A2C746B00867F17 /* Base */, ); name = Cloud.storyboard; sourceTree = ""; }; 41AFC1121A2C746B00867F17 /* Contacts.storyboard */ = { isa = PBXVariantGroup; children = ( 41AFC1131A2C746B00867F17 /* Base */, ); name = Contacts.storyboard; sourceTree = ""; }; 41AFC1141A2C746B00867F17 /* Offline.storyboard */ = { isa = PBXVariantGroup; children = ( 41AFC1151A2C746B00867F17 /* Base */, ); name = Offline.storyboard; sourceTree = ""; }; 41AFC1161A2C746B00867F17 /* Settings.storyboard */ = { isa = PBXVariantGroup; children = ( 41AFC1171A2C746B00867F17 /* Base */, ); name = Settings.storyboard; sourceTree = ""; }; 41C681D11A2330220088F2E6 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 41C681D21A2330220088F2E6 /* Base */, E85477521A24944900C848F8 /* es */, ); name = Localizable.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 4168129219EE6E5700C72800 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; 4168129319EE6E5700C72800 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 12.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 4168129519EE6E5700C72800 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; INFOPLIST_FILE = Demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/../../../bindings/ios/3rdparty/lib", ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "MEGA.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; USER_HEADER_SEARCH_PATHS = ""; }; name = Debug; }; 4168129619EE6E5700C72800 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; INFOPLIST_FILE = Demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/../../../bindings/ios/3rdparty/lib", ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "MEGA.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; USER_HEADER_SEARCH_PATHS = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 4168126C19EE6E5700C72800 /* Build configuration list for PBXProject "Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( 4168129219EE6E5700C72800 /* Debug */, 4168129319EE6E5700C72800 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 4168129419EE6E5700C72800 /* Build configuration list for PBXNativeTarget "Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( 4168129519EE6E5700C72800 /* Debug */, 4168129619EE6E5700C72800 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ A8CCCFF62AE1954B00E3CCAB /* XCLocalSwiftPackageReference "../../.." */ = { isa = XCLocalSwiftPackageReference; relativePath = ../../..; }; /* End XCLocalSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ A8CCCFF72AE1954B00E3CCAB /* MEGASdk */ = { isa = XCSwiftPackageProductDependency; productName = MEGASdk; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 4168126919EE6E5700C72800 /* Project object */; } sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/project.xcworkspace/000077500000000000000000000000001516266226600245215ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata000066400000000000000000000002071516266226600314620ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/000077500000000000000000000000001516266226600271545ustar00rootroot00000000000000IDEWorkspaceChecks.plist000066400000000000000000000003561516266226600335570ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata IDEDidComputeMac32BitWarning sdk-10.11.0/examples/iOS/Demo/Demo/000077500000000000000000000000001516266226600165275ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/AppDelegate.h000066400000000000000000000015221516266226600210530ustar00rootroot00000000000000/** * @file AppDelegate.h * @brief The AppDelegate of the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end sdk-10.11.0/examples/iOS/Demo/Demo/AppDelegate.m000066400000000000000000000102201516266226600210530ustar00rootroot00000000000000/** * @file AppDelegate.m * @brief The AppDelegate of the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "AppDelegate.h" #import "SSKeychain.h" #import "SVProgressHUD.h" #define kUserAgent @"iOS Example/1.0" #define kAppKey @"hNF3ELhK" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [MEGASdkManager setAppKey:kAppKey]; [MEGASdkManager setUserAgent:kUserAgent]; [MEGASdkManager sharedMEGASdk]; [MEGASdk setLogLevel:MEGALogLevelInfo]; UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; if ([SSKeychain passwordForService:@"MEGA" account:@"session"]) { [[MEGASdkManager sharedMEGASdk] fastLoginWithSession:[SSKeychain passwordForService:@"MEGA" account:@"session"] delegate:self]; UITabBarController *tabBarVC = [storyboard instantiateViewControllerWithIdentifier:@"TabBarControllerID"]; self.window.rootViewController = tabBarVC; } else { UIViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; self.window.rootViewController = loginVC; } return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { switch ([request type]) { case MEGARequestTypeFetchNodes: [SVProgressHUD showWithStatus:NSLocalizedString(@"updatingNodes", @"Updating nodes...")]; break; default: break; } } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { return; } if ([request type] == MEGARequestTypeLogin) { [[MEGASdkManager sharedMEGASdk] fetchNodesWithDelegate:self]; } } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/000077500000000000000000000000001516266226600205265ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Cloud.storyboard000066400000000000000000000614241516266226600237150ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Contacts.storyboard000066400000000000000000000207001516266226600244150ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/LaunchScreen.xib000066400000000000000000000072221516266226600236070ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Localizable.strings000066400000000000000000000025201516266226600243610ustar00rootroot00000000000000"cancel" = "Cancel"; "ok" = "OK"; "error" = "Error"; //LoginViewController "invalidMailOrPassword" = "Invalid e-mail and/or password. Please try again"; "emailIvalidFormat" = "Enter a valid email"; "passwordInvalidFormat" = "Enter a valid password"; "fetchingNodes" = "Fetching nodes"; "preparingNodes" = "Preparing nodes"; //UITABBARCONTROLLER (1) CLOUD DRIVE "cloudDrive" = "Cloud Drive"; "updatingNodes" = "Updating nodes..."; "foldersFiles" = "%d folders, %d files"; "foldersFile" = "%d folders, %d file"; "folderFiles" = "%d folder, %d files"; "folderFile" = "%d folder, %d file"; //DetailsNodeInfoViewController "savedForOffline" = "Saved for offline"; "generateLink" = "Generate link..."; //"+" //Upload photo "uploadPhoto" = "Upload photo"; //Create folder "newFolderTitle" = "New folder"; "newFolderMessage" = "Name for the new folder"; "createFolderButton" = "Create"; "createFolder" = "Create folder"; //UITABBARCONTROLLER (2) OFFLINE //UITABBARCONTROLLER (3) CONTACTS "noFoldersShare" = "No folders share"; "oneFolderShare" = "folder shared"; "foldersShare" = "folders shared"; //Rename "renameNodeTitle" = "Rename"; "renameNodeMessage" = "Enter the new name"; "renameNodeButton" = "Rename"; //Remove "removeNodeTitle" = "Confirm delete"; "removeNodeMessage" = "Are you sure?"; //UITABBARCONTROLLER (4) SETTINGS "logout" = "Logout...";sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Main.storyboard000066400000000000000000000153211516266226600235260ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Offline.storyboard000066400000000000000000000210371516266226600242250ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/Base.lproj/Settings.storyboard000066400000000000000000000135211516266226600244420ustar00rootroot00000000000000 sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/000077500000000000000000000000001516266226600205675ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/CloudDriveTableViewController.h000066400000000000000000000017761516266226600266620ustar00rootroot00000000000000/** * @file CloudDriveTableViewController.h * @brief View controller that show MEGA nodes and allow navigate through folders * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface CloudDriveTableViewController : UITableViewController @property (nonatomic, strong) MEGANode *parentNode; @end sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/CloudDriveTableViewController.m000066400000000000000000000413141516266226600266570ustar00rootroot00000000000000/** * @file CloudDriveTableViewController.m * @brief Cloud drive table view controller of the app. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "CloudDriveTableViewController.h" #import "NodeTableViewCell.h" #import "SVProgressHUD.h" #import "LoginViewController.h" #import #import "DetailsNodeInfoViewController.h" #import "Helper.h" @interface CloudDriveTableViewController () { UIAlertView *folderAlertView; NSInteger indexNodeSelected; } @property (nonatomic, strong) NSMutableArray *cloudImages; @property (nonatomic, strong) MEGANodeList *nodes; @property (weak, nonatomic) IBOutlet UIBarButtonItem *addItem; @property (weak, nonatomic) IBOutlet UIView *headerView; @property (weak, nonatomic) IBOutlet UILabel *filesFolderLabel; @property (nonatomic) UIImagePickerController *imagePickerController; @end @implementation CloudDriveTableViewController - (void)viewDidLoad { [super viewDidLoad]; NSArray *buttonsItems = @[self.addItem]; self.navigationItem.rightBarButtonItems = buttonsItems; NSString *thumbsDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"thumbs"]; NSError *error; if (![[NSFileManager defaultManager] fileExistsAtPath:thumbsDirectory]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:thumbsDirectory withIntermediateDirectories:NO attributes:nil error:&error]) { NSLog(@"Create directory error: %@", error); } } NSString *previewsDirectory = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"previews"]; if (![[NSFileManager defaultManager] fileExistsAtPath:previewsDirectory]) { if (![[NSFileManager defaultManager] createDirectoryAtPath:previewsDirectory withIntermediateDirectories:NO attributes:nil error:&error]) { NSLog(@"Create directory error: %@", error); } } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[MEGASdkManager sharedMEGASdk] addMEGADelegate:self]; [[MEGASdkManager sharedMEGASdk] retryPendingConnections]; [self reloadUI]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[MEGASdkManager sharedMEGASdk] removeMEGADelegate:self]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[self.nodes size] integerValue]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NodeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"nodeCell" forIndexPath:indexPath]; MEGANode *node = [self.nodes nodeAtIndex:indexPath.row]; NSString *thumbnailFilePath = [Helper pathForNode:node searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; if (!fileExists && [node hasThumbnail]) { [[MEGASdkManager sharedMEGASdk] getThumbnailNode:node destinationFilePath:thumbnailFilePath]; } if (!fileExists) { [cell.thumbnailImageView setImage:[Helper imageForNode:node]]; } else { [cell.thumbnailImageView setImage:[UIImage imageWithContentsOfFile:thumbnailFilePath]]; } cell.nameLabel.text = [node name]; if ([node type] == MEGANodeTypeFile) { struct tm *timeinfo; char buffer[80]; time_t rawtime = [[node modificationTime] timeIntervalSince1970]; timeinfo = localtime(&rawtime); strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); cell.modificationLabel.text = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; } else { struct tm *timeinfo; char buffer[80]; time_t rawtime = [[node creationTime] timeIntervalSince1970]; timeinfo = localtime(&rawtime); strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); cell.modificationLabel.text = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; } cell.nodeHandle = [node handle]; return cell; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { return self.headerView; } - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return 20.0; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { MEGANode *node = [self.nodes nodeAtIndex:indexPath.row]; switch ([node type]) { case MEGANodeTypeFolder: { CloudDriveTableViewController *cdvc = [self.storyboard instantiateViewControllerWithIdentifier:@"CloudDriveID"]; [cdvc setParentNode:node]; [self.navigationController pushViewController:cdvc animated:YES]; break; } case MEGANodeTypeFile: { break; } default: break; } } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { MEGANode *node = [self.nodes nodeAtIndex:indexPath.row]; DetailsNodeInfoViewController *nodeInfoDetailsVC = [self.storyboard instantiateViewControllerWithIdentifier:@"nodeInfoDetails"]; [nodeInfoDetailsVC setNode:node]; [self.navigationController pushViewController:nodeInfoDetailsVC animated:YES]; } - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { return (UITableViewCellEditingStyleDelete); } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle==UITableViewCellEditingStyleDelete) { MEGANode *node = [self.nodes nodeAtIndex:indexPath.row]; [[MEGASdkManager sharedMEGASdk] removeNode:node]; } } - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { } - (IBAction)optionAdd:(id)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", @"Cancel") destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"createFolder", @"Create folder"), NSLocalizedString(@"uploadPhoto", @"Upload photo"), nil]; [actionSheet showFromTabBar:self.tabBarController.tabBar]; } #pragma mark - Action sheet delegate - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 0) { folderAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"newFolderTitle", @"Create new folder") message:NSLocalizedString(@"newFolderMessage", @"Name for the new folder") delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", @"Cancel") otherButtonTitles:NSLocalizedString(@"createFolderButton", @"Create"), nil]; [folderAlertView setAlertViewStyle:UIAlertViewStylePlainTextInput]; [folderAlertView textFieldAtIndex:0].text = @""; folderAlertView.tag = 1; [folderAlertView show]; } else if (buttonIndex == 1) { [self showImagePickerForSourceType:UIImagePickerControllerSourceTypePhotoLibrary]; } } #pragma mark - Alert delegate - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { if (alertView.tag == 1) { if (buttonIndex == 1) { [[MEGASdkManager sharedMEGASdk] createFolderWithName:[[folderAlertView textFieldAtIndex:0] text] parent:self.parentNode]; } } } #pragma mark - UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL]; ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library assetForURL:assetURL resultBlock:^(ALAsset *asset) { NSString *name = asset.defaultRepresentation.filename; NSDate *modificationTime = [asset valueForProperty:ALAssetPropertyDate]; UIImageView *imageView = [[UIImageView alloc] init]; imageView.image= [info objectForKey:@"UIImagePickerControllerOriginalImage"]; NSData *webData = UIImageJPEGRepresentation(imageView.image, 0.9); NSString *localFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:name]; [webData writeToFile:localFilePath atomically:YES]; NSError *error = nil; NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObject:modificationTime forKey:NSFileModificationDate]; [[NSFileManager defaultManager] setAttributes:attributesDictionary ofItemAtPath:localFilePath error:&error]; if (error) { NSLog(@"Error change modification date of file %@", error); } [[MEGASdkManager sharedMEGASdk] startUploadWithLocalPath:localFilePath parent:self.parentNode fileName:nil appData:nil isSourceTemporary:false startFirst:false cancelToken:MEGACancelToken.alloc.init]; } failureBlock:nil]; [self dismissViewControllerAnimated:YES completion:NULL]; self.imagePickerController = nil; } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [self dismissViewControllerAnimated:YES completion:NULL]; } #pragma mark - Private methods - (void)reloadUI { NSString *filesAndFolders; if (!self.parentNode) { [self.navigationItem setTitle:NSLocalizedString(@"cloudDrive", @"Cloud drive")]; self.nodes = [[MEGASdkManager sharedMEGASdk] childrenForParent:[[MEGASdkManager sharedMEGASdk] rootNode]]; NSInteger files = [[MEGASdkManager sharedMEGASdk] numberChildFilesForParent:[[MEGASdkManager sharedMEGASdk] rootNode]]; NSInteger folders = [[MEGASdkManager sharedMEGASdk] numberChildFoldersForParent:[[MEGASdkManager sharedMEGASdk] rootNode]]; if (files == 0 || files > 1) { if (folders == 0 || folders > 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"foldersFiles", @"Folders, files"), (int)folders, (int)files]; } else if (folders == 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"folderFiles", @"Folder, files"), (int)folders, (int)files]; } } else if (files == 1) { if (folders == 0 || folders > 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"foldersFile", @"Folders, file"), (int)folders, (int)files]; } else if (folders == 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"folderFile", @"Folders, file"), (int)folders, (int)files]; } } } else { [self.navigationItem setTitle:[self.parentNode name]]; self.nodes = [[MEGASdkManager sharedMEGASdk] childrenForParent:self.parentNode]; NSInteger files = [[MEGASdkManager sharedMEGASdk] numberChildFilesForParent:self.parentNode]; NSInteger folders = [[MEGASdkManager sharedMEGASdk] numberChildFoldersForParent:self.parentNode]; if (files == 0 || files > 1) { if (folders == 0 || folders > 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"foldersFiles", @"Folders, files"), (int)folders, (int)files]; } else if (folders == 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"folderFiles", @"Folder, files"), (int)folders, (int)files]; } } else if (files == 1) { if (folders == 0 || folders > 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"foldersFile", @"Folders, file"), (int)folders, (int)files]; } else if (folders == 1) { filesAndFolders = [NSString stringWithFormat:NSLocalizedString(@"folderFile", @"Folders, file"), (int)folders, (int)files]; } } } self.filesFolderLabel.text = filesAndFolders; [self.tableView reloadData]; } - (void)showImagePickerForSourceType:(UIImagePickerControllerSourceType)sourceType { UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init]; imagePickerController.modalPresentationStyle = UIModalPresentationCurrentContext; imagePickerController.sourceType = sourceType; imagePickerController.delegate = self; self.imagePickerController = imagePickerController; [self.tabBarController presentViewController:self.imagePickerController animated:YES completion:nil]; } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { switch ([request type]) { case MEGARequestTypeExport: [SVProgressHUD showWithStatus:NSLocalizedString(@"generateLink", @"Generate link...")]; break; default: break; } } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { return; } switch ([request type]) { case MEGARequestTypeFetchNodes: [SVProgressHUD dismiss]; break; case MEGARequestTypeGetAttrFile: { for (NodeTableViewCell *ntvc in [self.tableView visibleCells]) { if ([request nodeHandle] == [ntvc nodeHandle]) { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:[request nodeHandle]]; NSString *thumbnailFilePath = [Helper pathForNode:node searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; if (fileExists) { [ntvc.thumbnailImageView setImage:[UIImage imageWithContentsOfFile:thumbnailFilePath]]; } } } break; } case MEGARequestTypeExport: { [SVProgressHUD dismiss]; NSArray *itemsArray = [NSArray arrayWithObjects:[request link], nil]; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:itemsArray applicationActivities:nil]; activityVC.excludedActivityTypes = @[UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll]; [self presentViewController:activityVC animated:YES completion:nil ]; break; } default: break; } } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } #pragma mark - MEGAGlobalDelegate - (void)onReloadNeeded:(MEGASdk *)api { } - (void)onNodesUpdate:(MEGASdk *)api nodeList:(MEGANodeList *)nodeList { [self reloadUI]; } #pragma mark - MEGATransferDelegate - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { if ([transfer type] == MEGATransferTypeUpload) { NSError *e = nil; NSString *localFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[transfer fileName]]; BOOL success = [[NSFileManager defaultManager] removeItemAtPath:localFilePath error:&e]; if (!success || e) { NSLog(@"remove file error %@", e); } } } -(void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/DetailsNodeInfoViewController.h000066400000000000000000000016071516266226600266520ustar00rootroot00000000000000/** * @file DetailsNodeInfoViewController.h * @brief View controller that show details info about a node * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface DetailsNodeInfoViewController : UIViewController @property (nonatomic, strong) MEGANode *node; @end sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/DetailsNodeInfoViewController.m000066400000000000000000000231761516266226600266640ustar00rootroot00000000000000/** * @file DetailsNodeInfoViewController.m * @brief View controller that show details info about a node * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "DetailsNodeInfoViewController.h" #import "SVProgressHUD.h" #import "Helper.h" @interface DetailsNodeInfoViewController () { UIAlertView *renameAlertView; UIAlertView *removeAlertView; } @property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView; @property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property (weak, nonatomic) IBOutlet UILabel *modificationTimeLabel; @property (weak, nonatomic) IBOutlet UILabel *sizeLabel; @property (weak, nonatomic) IBOutlet UIProgressView *downloadProgressView; @property (weak, nonatomic) IBOutlet UILabel *saveLabel; @property (weak, nonatomic) IBOutlet UIButton *downloadButton; @end @implementation DetailsNodeInfoViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self reloadUI]; [[MEGASdkManager sharedMEGASdk] addMEGADelegate:self]; [[MEGASdkManager sharedMEGASdk] retryPendingConnections]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[MEGASdkManager sharedMEGASdk] removeMEGADelegate:self]; } - (void)reloadUI { if ([self.node type] == MEGANodeTypeFolder) { [self.downloadButton setHidden:YES]; } NSString *thumbnailFilePath = [Helper pathForNode:self.node searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; if (!fileExists) { [self.thumbnailImageView setImage:[Helper imageForNode:self.node]]; } else { [self.thumbnailImageView setImage:[UIImage imageWithContentsOfFile:thumbnailFilePath]]; } self.nameLabel.text = [self.node name]; struct tm *timeinfo; char buffer[80]; time_t rawtime; if ([self.node isFile]) { rawtime = [[self.node modificationTime] timeIntervalSince1970]; } else { rawtime = [[self.node creationTime] timeIntervalSince1970]; } timeinfo = localtime(&rawtime); strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); self.modificationTimeLabel.text = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; if ([self.node isFile]) { self.sizeLabel.text = [NSByteCountFormatter stringFromByteCount:[[self.node size] longLongValue] countStyle:NSByteCountFormatterCountStyleMemory]; } else { self.sizeLabel.text = [NSByteCountFormatter stringFromByteCount:[[[MEGASdkManager sharedMEGASdk] sizeForNode:self.node] longLongValue] countStyle:NSByteCountFormatterCountStyleMemory]; } self.title = [self.node name]; NSString *documentFilePath = [Helper pathForNode:self.node searchPath:NSDocumentDirectory]; BOOL fileDocumentExists = [[NSFileManager defaultManager] fileExistsAtPath:documentFilePath]; if (fileDocumentExists) { [self.downloadProgressView setHidden:YES]; [self.saveLabel setHidden:NO]; [self.downloadButton setImage:[UIImage imageNamed:@"savedFile"] forState:UIControlStateNormal]; self.saveLabel.text = NSLocalizedString(@"savedForOffline", @"Saved for offline"); } } - (IBAction)tapDownload:(UIButton *)sender { if ([self.node type] == MEGANodeTypeFile) { NSString *documentFilePath = [Helper pathForNode:self.node searchPath:NSDocumentDirectory]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:documentFilePath]; if (!fileExists) { [[MEGASdkManager sharedMEGASdk] startDownloadNode:self.node localPath:documentFilePath fileName:nil appData:nil startFirst:false cancelToken:MEGACancelToken.alloc.init collisionCheck:CollisionCheckFingerprint collisionResolution:CollisionResolutionNewWithN]; } } } - (IBAction)tapGenerateLink:(UIButton *)sender { [[MEGASdkManager sharedMEGASdk] exportNode:self.node]; } - (IBAction)tapRename:(UIButton *)sender { renameAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"renameNodeTitle", @"Rename") message:NSLocalizedString(@"renameNodeMessage", @"Enter the new name") delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", @"Cancel") otherButtonTitles:NSLocalizedString(@"renameNodeButton", @"Rename"), nil]; [renameAlertView setAlertViewStyle:UIAlertViewStylePlainTextInput]; [renameAlertView textFieldAtIndex:0].text = [[[self.node name] lastPathComponent] stringByDeletingPathExtension]; removeAlertView.tag = 0; [renameAlertView show]; } - (IBAction)tapDelete:(UIButton *)sender { removeAlertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"removeNodeTitle", @"Remove node") message:NSLocalizedString(@"removeNodeMessage", @"Are you sure?") delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", @"Cancel") otherButtonTitles:NSLocalizedString(@"ok", @"OK"), nil]; [removeAlertView show]; removeAlertView.tag = 1; [removeAlertView show]; } #pragma mark - Alert delegate - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex { if (alertView.tag == 0){ if (buttonIndex == 1) { if ([[[self.node name] pathExtension] isEqualToString:@""]) { [[MEGASdkManager sharedMEGASdk] renameNode:self.node newName:[alertView textFieldAtIndex:0].text]; } else { NSString *newName = [[alertView textFieldAtIndex:0].text stringByAppendingFormat:@".%@", [[self.node name] pathExtension]]; self.nameLabel.text = newName; self.title = newName; [[MEGASdkManager sharedMEGASdk] renameNode:self.node newName:newName]; } } } if (alertView.tag == 1) { if (buttonIndex == 1) { [[MEGASdkManager sharedMEGASdk] removeNode:self.node]; [self.navigationController popViewControllerAnimated:YES]; } } } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { switch ([request type]) { case MEGARequestTypeExport: [SVProgressHUD showWithStatus:NSLocalizedString(@"generateLink", @"Generate link...")]; break; default: break; } } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { return; } switch ([request type]) { case MEGARequestTypeGetAttrFile: { if ([request nodeHandle] == [self.node handle]) { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:[request nodeHandle]]; NSString *thumbnailFilePath = [Helper pathForNode:node searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; if (fileExists) { [self.thumbnailImageView setImage:[UIImage imageWithContentsOfFile:thumbnailFilePath]]; } } break; } case MEGARequestTypeExport: { [SVProgressHUD dismiss]; NSArray *itemsArray = [NSArray arrayWithObjects:[request link], nil]; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:itemsArray applicationActivities:nil]; activityVC.excludedActivityTypes = @[UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll]; [self presentViewController:activityVC animated:YES completion:nil ]; break; } default: break; } } - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request { } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } #pragma mark - MEGAGlobalDelegate - (void)onUsersUpdate:(MEGASdk *)api userList:(MEGAUserList *)userList{ } - (void)onReloadNeeded:(MEGASdk *)api { } - (void)onNodesUpdate:(MEGASdk *)api nodeList:(MEGANodeList *)nodeList { self.node = [nodeList nodeAtIndex:0]; } #pragma mark - MEGATransferDelegate - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer { [self.downloadProgressView setHidden:NO]; [self.downloadProgressView setProgress:0]; } - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer { float progress = [[transfer transferredBytes] floatValue] / [[transfer totalBytes] floatValue]; [self.downloadProgressView setProgress:progress]; } - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { [self.downloadProgressView setHidden:YES]; [self.downloadProgressView setProgress:1]; [self.saveLabel setHidden:NO]; [self.downloadButton setImage:[UIImage imageNamed:@"savedFile"] forState:UIControlStateNormal]; self.saveLabel.text = NSLocalizedString(@"savedForOffline", @"Saved for offline"); } -(void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/NodeTableViewCell.h000066400000000000000000000017401516266226600242320ustar00rootroot00000000000000/** * @file NodeTableViewCell.h * @brief Custom node table view cell of the app. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import @interface NodeTableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UIImageView *thumbnailImageView; @property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property (weak, nonatomic) IBOutlet UILabel *modificationLabel; @property (nonatomic) uint64_t nodeHandle; @end sdk-10.11.0/examples/iOS/Demo/Demo/CloudDrive/NodeTableViewCell.m000066400000000000000000000013551516266226600242410ustar00rootroot00000000000000/** * @file NodeTableViewCell.m * @brief Custom node table view cell of the app. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "NodeTableViewCell.h" @implementation NodeTableViewCell @end sdk-10.11.0/examples/iOS/Demo/Demo/Contacts/000077500000000000000000000000001516266226600203055ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Contacts/ContactTableViewCell.h000066400000000000000000000004101516266226600244470ustar00rootroot00000000000000#import @interface ContactTableViewCell : UITableViewCell @property (weak, nonatomic) IBOutlet UIImageView *avatarImageView; @property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property (weak, nonatomic) IBOutlet UILabel *shareLabel; @end sdk-10.11.0/examples/iOS/Demo/Demo/Contacts/ContactTableViewCell.m000066400000000000000000000001151516266226600244560ustar00rootroot00000000000000#import "ContactTableViewCell.h" @implementation ContactTableViewCell @end sdk-10.11.0/examples/iOS/Demo/Demo/Contacts/ContactsTableViewController.h000066400000000000000000000014771516266226600261140ustar00rootroot00000000000000/** * @file ContactsTableViewController.h * @brief View controller that show your contacts * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface ContactsTableViewController : UITableViewController @end sdk-10.11.0/examples/iOS/Demo/Demo/Contacts/ContactsTableViewController.m000066400000000000000000000116311516266226600261120ustar00rootroot00000000000000/** * @file ContactsTableViewController.m * @brief View controller that show your contacts * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "ContactsTableViewController.h" #import "ContactTableViewCell.h" #import "Helper.h" @interface ContactsTableViewController () @property (nonatomic, strong) MEGAUserList *users; @property (nonatomic, strong) NSMutableArray *usersArray; @end @implementation ContactsTableViewController - (void)viewDidLoad { [super viewDidLoad]; self.usersArray = [NSMutableArray new]; self.users = [[MEGASdkManager sharedMEGASdk] contacts]; for (NSInteger i = 0; i < [[self.users size] integerValue] ; i++) { MEGAUser *u = [self.users userAtIndex:i]; if ([u visibility] == MEGAUserVisibilityVisible) [self.usersArray addObject:u]; } } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.usersArray count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ContactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"contactCell" forIndexPath:indexPath]; MEGAUser *user = [self.usersArray objectAtIndex:indexPath.row]; cell.nameLabel.text = [user email]; NSString *avatarFilePath = [Helper pathForUser:user searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:avatarFilePath]; if (fileExists) { [cell.avatarImageView setImage:[UIImage imageWithContentsOfFile:avatarFilePath]]; cell.avatarImageView.layer.cornerRadius = cell.avatarImageView.frame.size.width/2; cell.avatarImageView.layer.masksToBounds = YES; } else { [[MEGASdkManager sharedMEGASdk] getAvatarUser:user destinationFilePath:avatarFilePath delegate:self]; } int numFilesShares = [[[[MEGASdkManager sharedMEGASdk] inSharesForUser:user] size] intValue]; if (numFilesShares == 0) { cell.shareLabel.text = NSLocalizedString(@"noFoldersShare", @"No folders shared"); } else if (numFilesShares == 1 ) { NSString *localizedString = NSLocalizedString(@"oneFolderShare", @" folder shared"); cell.shareLabel.text = [NSString stringWithFormat:@"%d %@", numFilesShares, localizedString]; } else { NSString *localizedString = NSLocalizedString(@"foldersShare", @" folders shared"); cell.shareLabel.text = [NSString stringWithFormat:@"%d %@", numFilesShares, localizedString]; } return cell; } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { return; } switch ([request type]) { case MEGARequestTypeGetAttrUser: { for (ContactTableViewCell *ctvc in [self.tableView visibleCells]) { if ([[request email] isEqualToString:[ctvc.nameLabel text]]) { NSString *fileName = [request email]; NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *avatarFilePath = [cacheDirectory stringByAppendingPathComponent:@"thumbs"]; avatarFilePath = [avatarFilePath stringByAppendingPathComponent:fileName]; avatarFilePath = [avatarFilePath stringByAppendingString:@".png"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:avatarFilePath]; if (fileExists) { [ctvc.avatarImageView setImage:[UIImage imageWithContentsOfFile:avatarFilePath]]; ctvc.avatarImageView.layer.cornerRadius = ctvc.avatarImageView.frame.size.width/2; ctvc.avatarImageView.layer.masksToBounds = YES; } } } break; } default: break; } } - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request { } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/000077500000000000000000000000001516266226600215705ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/000077500000000000000000000000001516266226600252655ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000037351516266226600277650ustar00rootroot00000000000000{ "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "1x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "icon-Small-40@2x.png", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "57x57", "scale" : "1x" }, { "idiom" : "iphone", "size" : "57x57", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "icon-60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "icon-60@3x.png", "scale" : "3x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "1x" }, { "idiom" : "ipad", "size" : "29x29", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "icon-Small-40.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "icon-Small-40@2x-1.png", "scale" : "2x" }, { "idiom" : "ipad", "size" : "50x50", "scale" : "1x" }, { "idiom" : "ipad", "size" : "50x50", "scale" : "2x" }, { "idiom" : "ipad", "size" : "72x72", "scale" : "1x" }, { "idiom" : "ipad", "size" : "72x72", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "icon-76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "icon-76@2x.png", "scale" : "2x" }, { "idiom" : "car", "size" : "120x120", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-60@2x.png000066400000000000000000000045161516266226600275260ustar00rootroot00000000000000PNG  IHDRxx9d6 IDATxlUgcd q`) z2dꠃR`Di)2\s:eE@tƠ#v(e[P11h{m{Ϗ{CvH9soI}'O$"X"%"X"%"X"%$9?'{>K֊6+Nւǹ-oɽ4Y"81-s} r ⴂ9pΒ},ُ A3q6v."X[u;o`'&=0>6Y> jY(v"ؕc\LU^!ߞDb!3ѪJaS݂Or@9j܋Xp 'T e=K}̵ɨl+JƂFuGXp #at1ci2KzA.I$۽CdlU['8tS؂>2nknQa /Qլ.V6]`gw0|)\^u2\d?MUW˭.iHg w'MVNm0ͯ`;9WXG?p S[zoюx}. FFyciW ۙI/#/ ,Wܬ'C!+ 2;j`d>.Yk;It/ *c_vU@+G)YE!@9-.^T҃K t`$~ R/tEMȍM02u(0n YsZA#63IKD'?2 zxՂC$i[{IAw0ktzjbHű7]!%1.vPWi]U@7 D0umձ9tnX#w @'H;=ꂟ5] $1P4>R3$?b#wŝv޲Wo[T $"1$t^%p'I,;I:,)xU\2a˯RGɭ';cwAE&:uKkUy 3^)V%S' TiOBvN튈`?3JԿy;]()p8arD0KL&Ϝ}SoC#plpAvAn%gu-xKU^S> 1>5~kŌܬDKDDKDDKD`I,>_IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-60@3x.png000066400000000000000000000074661516266226600275360ustar00rootroot00000000000000PNG  IHDReIDATxҁ K?v_ȁȁȁȁ.9#{pTe8㔑qfw( I{ BqTj"WEܑ@)E_ZPV wd/gܞ_L[5%=s6g$\~#UjرslwO3 `"/-?,blFl!8Jrdm|=^z4 ޢLÁ8EB7wS1H.p)FB>f +1  %d>e31e2 Cy(Rs?o D(Xa3;|Mw]P6{ 䰚DmVV0>kIZZ GiB3V}H ;L!9ةo`ch _ ux0v@ap<MrtUX̓&B}xB-T""h3"!GR~cRWvYzAsТ(;z珒1D-BS)c Dd@CgڲMJhSAкCQGVIw-Z 40KH}Č 6f:y?J$ͨ#dr(Kf 9UoAKXˡjKbvE{],V1E٭r#m>9SQH#2 vJS*r*0GD'ˍ.CJ+f3h89RrA۝.q1LwZ*b2P.#`L .`v}$f`Β52L$ 0!^.9$"rrȡ?+9ȡjyH iI Ma9ԝ$uLJCJIwLI)LeLec#7Cr%)#WP"9 {RGc.%:<"U_?"*Th[aCݑJ~ONI$?}9<(`ꈦ8ɑzre?=IXؤB)x&JCރxٙf0g 18P\2'43P #c)IrH a; c rHӟWjKABX]@1d>ap,Q@7O,)9Q6f0(eIF9+`R21fn(SlCQcOKX 9j܊HVlCL[H,^z?Rq.³T 9RohJçN\( xv5l-rGv5Ɗ]$E\"h)7Er%vWf+G X_!+kw< 87Xp2F(1!ۛer E\ft/9my8njk1O9%q_ր0{Z5az/9.u) nF@-15N1!sk;nolF=.x;J"mWj:96 m4㻟p.r>-#<[9fXsC5dڏpEId@-#2y5o!瘒seZؖ_ɥȔ{@ W"pı#:-OuHH(j9Per$-3r6r`x4vb.D[ 29'ZC=P6bbf[ 29 -瘔象ҩ=J&l4~I9ZKMzgHh=,l90J+ 'gȱ!?,<$2 b,#RSr,]ՅCP`AbvGId΍(-#nsɁ+瑢PG,(-C9u&@"'Mh Oi`T3rE /hΙ( ó"Fim&[0ѥyWRS6fU rDrI9I98+!\v90D[`H9V:Oc)9@hFGLʁI9UIĎ}|bFWQAIdnU%Q=nnӃ!s=bRbR%'"`]r=Bi_*;*( ç}bFo!9Tը ;MDQU`drg8G>}ZZ49PPOx"P=۝PP◱؁ivʣNLȧq ]{+a&Gb"\ Um8/"U[[#1,roql}1 +7c]6Fo_1g[Ĥdͻroq`!A9HU]͹UQi*Na9@q1 %u$#᜴U>B!rhM9=3\ 9@lkHlK5GA|% F# 'rbB ^'@9@bbH Orsn` 01-i2r嫹9=D8Vkj B ^8|ZA@+nYs[n<k;FOiN$:~9C[v4VTfg @(%?`,=ø8Ej@r%g«7?բAp TUG"7= Uq (r>BC 9BC 9!@!r71HGIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-76.png000066400000000000000000000032271516266226600271610ustar00rootroot00000000000000PNG  IHDRLLH|^IDATxklSeN !؞V.# a@`n]D \e48#CD0Pİ]> ɲT<}~ٵ=ͼy}^ՌXM}Ûy󺂩Q856* $z›A؈[q!)l" b@}]5%FBܷ5% F ɽ9s WB ra![lIU $?WѪ*X ԫ;U*^ {?KT3LV\(9H6~3aZr${r$iE@* +L93֗PX$qL+bƥe$Fm#=q _חhyT҅X֟FԦҬi]ԸFcT9+޴!cR)TCJvF#HTG /SJG:چ !(OVRjܿ@hlV 2^}C 6wذ^޻4!#C,Q*At-dX@/h` 09*A2q\0^EyM#uM QT)q Hvcװ^TѸe" rvxQp4(ժ%Q5 #b_uhWbW&zKmC;MB<\Cbqevs*w^=粳bLj#DÙ Sr0M;U%BT:M;B? uQ/ࡓӯRBj[ү\a] I2mL.|H,^[&\.N`01 Wn@V +ȄIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-76@2x.png000066400000000000000000000062231516266226600275320ustar00rootroot00000000000000PNG  IHDRv ZIDATx{pToaFG)>ل1TB D @(Z8B}նj ؇L(RT|v:ڙ:"yy'wfw9t(rv9'` 3;$D**JD*E%RETQ"UȬɻ_fv'Ylv&6w+{\>˰[{_`;Q"HxlǬl3+[ G@ÕHʄ_{7zZo`?z)FHI #!r#*.8q5Jd^1+vfe+ u#a0%R,q;d.0+[xR<sV8 iXcf+۲늊69W"3Ya|"7[UAs ܾO."ّgf*K-/K]2w>M%M3ǖH3]~9McRm2d*x2{i=hJW4NK=]/2d3A hE&'2[NW TBPdlإ qDbӎtE(P \H#i5Q(2w"NjLg2T *BQ$g+2kb D{KR5 aPDTuN(PsH#5QwWv@Gܻ7_hH/I* (VF;EP,5wS_ojDLDB (_zB"әvÿB!Tb *2M;(p"Y8XJ YBL?کW(@ɂaߠ/WzQ2_T]SE& ߨ/SQ5]H?wLi TŅA߆IDf~(KrOh[ '_Vq#a0~VU^"ӯ)D W`E\@(nOrHH}]1RpDe/0T"B1aLLkc2)rP̀Q47s X^HArKo <ǰPT_ю \s 0(_Ș1[BA~Xy#q=D|9"'+yEN$pWZ2 2D",Olmra"@HǏF|W .7ɚGYеDW [DG,\u1pE]KP`Yd֌j#Dpĺ57[ۭ[DZ$K'[HKnolv.#EeY~Ӱ2:%ގ-%($2Cj,H$RP٥tpa[!r/a1Ң ’{H$Œds_XƑ D" \6M:v"H$2zEkl&Pag_o"BZTt&K"Z:@,em[-pD|Kytp^>]%?[@q5!- ",ʪy+ˮgX.S>Xp)\ E|K, U< Rw- GyAp.I]H> E8qp.u#R-U!&-69Yp88F BZDX$PVE3vk1!"8EE. ]B37kZ̀d/t]"I|- -lqH^AKoLE-D׶{btl{.  BZDXݼ"Ah:sܒKl!8"!’_9\$6cAWqEB%{+99eMhl)IZDX:|tCPQKך#\8X-"' $- ",~[,"Apa1xYA1F9X&~H",Dڍr"D;әy#&1]KP"os=6PU.Eh4 `H( xO^"89PuU?-_(a>"@.}><ԋ |9H@HdlKXi# v)"#7P"4 {JIt;ʗ&B^߰#AXi#" F(_HDkܢD^$j7_%1a`.QdfP/!K.\ ^J\ ſ Qdv6鳚|mH pH:ɐbhmfda,g#KF0@p {bbEPUH|K9"GDxb{j E"!E9B* z<#BzQ2H$'TtDz#ij!ogt(H${=[: ˑdHu].T$sU\P=-ݳ@E=U߸gYDQm"F E"ڝ; +@.y04:B$>t_ #r o#Dܣ􈃺8@H;N/E9Xlbb (GDnjQ&"8\$buWpTj1cqPrY5S?PEb3#mpD",tܩ,Tz ?ԩ?-s {=gYēsdD"^ӄc9Rb"L<#$~+UxcH$oQbQxG+ Ofxǜ"L&}9ūÇӋ=Ul gal DDx >bP8fQ"ČO9:ρ`0!9(S4l:v0p 3D $F^\acp(BzsEW"\KyQ"2qxwuq,D Ŀ|;nvpٚo.s{z]{]v >˰[q%JD(*JD(*J_l aIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-Small-40.png000066400000000000000000000016301516266226600302120ustar00rootroot00000000000000PNG  IHDR(( H_PLTEeIt9i}*\(N)[|}(O(M2`'K'M'I'I'G'J&F0[&Dz&B&C%?z%=0U&J%;+P*N$9&I$6%B]v$4$;^w>Z#1$81M#.0K#-bwD^PWi@IDATx1kA?;RV).H| APPP" װ&M A"יJHWngvէݰXY] $Ke9%˟<@G^tQ~~́[8w0a-8M,py+\XQ! " I+29"!XÑ̔i /!:XVy6nK9ju.aöK-Sنjg ;fBJ3o|;fH麏,9t0N X"|cC ,~ߞO3,5+X<|:n1Zixܝٺa\j`;OÉs@&AARrL^kL,BRށ>{u8VE[&P'mqSKKcIhAc6O&+PR>+=C\_x̓L`Qi&j9 :+sf pzǾ7I.ND8eVـo&1l؄l/TK pO1ḻ3x&JlQmULb8uc݋U ;f p>K0 p5hkGU9wm<F' ܟ`ml5h|5prñUXI8{>x m+<O8ldp)O94&X:d$E`Cp+ŷ(fK=$[öv},P|&lQЪ^V0I mBGoQ(@]0fni$Ell؄ZYGxp7&c-u\jO$畀Y ~Y8jg,|=LbP&rglQ_*$0J~_`ZrR Qf X&`Ka5 &rg&PJlo[,;$a\JLf+'ZuCAh_/NӨ˦;}Z5P2e2p |kM&d}IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/AppIcon.appiconset/icon-Small-40@2x.png000066400000000000000000000032611516266226600305660ustar00rootroot00000000000000PNG  IHDRPPsexIDATxiej 1D!zMU.Ń+,FP(&j ~#%q(.Hd r,mvNۙyΙvwLygK \.X5N6$Ȅb)q-& h1 Kn*_\ 4}?{`odfɵ5k`[ "iloIylIS!׊yD[ht4gU'8Y,.p:#mRpXB!b/*@pPrsGHpq>! " I+29"!XÑ̔i /!:XVy6nK9ju.aöK-Sنjg ;fBJ3o|;fH麏,9t0N X"|cC ,~ߞO3,5+X<|:n1Zixܝٺa\j`;OÉs@&AARrL^kL,BRށ>{u8VE[&P'mqSKKcIhAc6O&+PR>+=C\_x̓L`Qi&j9 :+sf pzǾ7I.ND8eVـo&1l؄l/TK pO1ḻ3x&JlQmULb8uc݋U ;f p>K0 p5hkGU9wm<F' ܟ`ml5h|5prñUXI8{>x m+<O8ldp)O94&X:d$E`Cp+ŷ(fK=$[öv},P|&lQЪ^V0I mBGoQ(@]0fni$Ell؄ZYGxp7&c-u\jO$畀Y ~Y8jg,|=LbP&rglQ_*$0J~_`ZrR Qf X&`Ka5 &rg&PJlo[,;$a\JLf+'ZuCAh_/NӨ˦;}Z5P2e2p |kM&d}IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/000077500000000000000000000000001516266226600245775ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/delete.imageset/000077500000000000000000000000001516266226600276365ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/delete.imageset/Contents.json000066400000000000000000000002331516266226600323240ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "delete.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/delete.imageset/delete.pdf000066400000000000000000000171171516266226600316020ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3"f&k&2, endstream endobj 5 0 obj 48 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 22 0 R >> /XObject << /Fm4 16 0 R /Fm2 10 0 R /Fm1 7 0 R /Fm5 19 0 R /Fm3 13 0 R >> >> endobj 16 0 obj << /Length 17 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [14 6 15 18] /Resources 18 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T04Q0PU049 D(h\]BQQB8P@L!SA߹H!3dK#c F :D)K0MR endstream endobj 17 0 obj 99 endobj 18 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [12 23 17 25] /Resources 12 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x=K 0 D9ŬP#EtdBC%˼G#Xq2'ި4^:IMK=%T9KO?LvV6p[T亵 lLa+ endstream endobj 11 0 obj 133 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 3 24 27] /Resources 9 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream xMPn0 +x.0/Vh Ht`AeJ"/Kx VHHC,(s%!ǩS,@-Vt}U(C#͒)!tE+ ljmR 2LEM6uZ945ިv41c3zTb-Yn Me|9E5xq/v@IV ̃?F?҇%X+o*P-9L b>Ggc ~1CXf7,b5}r҄a9/6r endstream endobj 8 0 obj 278 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 19 0 obj << /Length 20 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 6 11 18] /Resources 21 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T04P0PU049 D(h\]BQQB8P@L!SA߹H!3dS#c f h!204-@ۇ endstream endobj 20 0 obj 99 endobj 21 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 13 0 obj << /Length 14 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [18 6 19 18] /Resources 15 0 R /Group << /S /Transparency /CS 23 0 R /I true /K false >> >> stream x+TT(T0\CK@fՅk))1*s2<#8.NV04V02Zads@! c04*N  endstream endobj 14 0 obj 99 endobj 15 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 24 0 R >> >> endobj 22 0 obj << /Type /ExtGState /ca 0.3 >> endobj 25 0 obj << /Length 26 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 26 0 obj 1047 endobj 23 0 obj [ /ICCBased 25 0 R ] endobj 27 0 obj << /Length 28 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 28 0 obj 2612 endobj 24 0 obj [ /ICCBased 27 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 29 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 30 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 31 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 30 0 R /CreationDate 31 0 R /ModDate 31 0 R >> endobj xref 0 32 0000000000 65535 f 0000006887 00000 n 0000000162 00000 n 0000006649 00000 n 0000000022 00000 n 0000000144 00000 n 0000000264 00000 n 0000001244 00000 n 0000001731 00000 n 0000001750 00000 n 0000000807 00000 n 0000001154 00000 n 0000001174 00000 n 0000002220 00000 n 0000002532 00000 n 0000002551 00000 n 0000000406 00000 n 0000000718 00000 n 0000000737 00000 n 0000001819 00000 n 0000002131 00000 n 0000002150 00000 n 0000002621 00000 n 0000003839 00000 n 0000006612 00000 n 0000002668 00000 n 0000003818 00000 n 0000003876 00000 n 0000006591 00000 n 0000006730 00000 n 0000006794 00000 n 0000006845 00000 n trailer << /Size 32 /Root 29 0 R /Info 1 0 R /ID [ <21d5330f20b3da40fa718d7bec7cfe00> <21d5330f20b3da40fa718d7bec7cfe00> ] >> startxref 6962 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/moveFile.imageset/000077500000000000000000000000001516266226600301425ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/moveFile.imageset/Contents.json000066400000000000000000000002311516266226600326260ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "move.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/moveFile.imageset/move.pdf000066400000000000000000000175031516266226600316110ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x]=n1{z ׉:U0R~DvSWz+-NE,͘ et!Ʊ +kA I%MH"ȃUǛQK%ԢR.7)G1焦{M>֧^^a^S5QwGWD*?Fy%;p5,Ҩ5o;:d!VNɩY~pB;'g*oD?7-Om' v#Pg]2!t0n+;7eЄ* endstream endobj 5 0 obj 299 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> /ExtGState << /Gs1 20 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R /Fm4 17 0 R /Fm3 14 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [8 18 20 27] /Resources 9 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEPN@ W0ɼ|*Hip8Wܡ֤͎/`%w7;SiVeYeaKduўB!$ڠY{5^#7i5/G1e,RcM9ה` >Rwģ8w@۱%l$r$L1u9n)9/vod~%7ܜO>dž* [(kXmg%~?Fq{ Wb6 endstream endobj 8 0 obj 250 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [2 10 11 20] /Resources 12 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEMN0 =[#a8͚jP@ijZ_y~;:t$SMj7aMe[[*ޠHYF9u%yS%tȰQ>&h`kz8V6InjDSV$T%Ɯe@)+4#g񴥸- O[˻7-ɜ<1b|o$>%ޟ7\=!}88\-FC /n^r=l; endstream endobj 11 0 obj 270 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 17 0 obj << /Length 18 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [8 3 20 12] /Resources 19 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xEQA1 >ꍝLt;elEr2o:ӛd!z>87aj NҢJbFu*W 0;UQƄʋzUX>)0*isG& P@p"BlVV u!f0֥W*n B<^<"}1h{DRWbnI? -ڙ+0SMM_vt]v:\7 endstream endobj 18 0 obj 243 endobj 19 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [17 10 26 20] /Resources 16 0 R /Group << /S /Transparency /CS 21 0 R /I true /K false >> >> stream xMQAn1sx} rTTJRm]3x?BOR!ٸmF@}E]J]Xypᦕ4Gd!zoFh? ƵO95ئ.-siTQNO@<"(1|`ޓCCiE̹l8;yRa3ȴEIb X11C΍'s;8+gu[Ӄ|X .pI?D_r endstream endobj 15 0 obj 279 endobj 16 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 13 0 R >> >> endobj 20 0 obj << /Type /ExtGState /ca 0.3 >> endobj 22 0 obj << /Length 23 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 23 0 obj 2612 endobj 13 0 obj [ /ICCBased 22 0 R ] endobj 24 0 obj << /Length 25 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 25 0 obj 1047 endobj 21 0 obj [ /ICCBased 24 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 26 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 27 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 28 0 obj (D:20141030204035Z00'00') endobj 1 0 obj << /Producer 27 0 R /CreationDate 28 0 R /ModDate 28 0 R >> endobj xref 0 29 0000000000 65535 f 0000007191 00000 n 0000000414 00000 n 0000006953 00000 n 0000000022 00000 n 0000000395 00000 n 0000000516 00000 n 0000000676 00000 n 0000001136 00000 n 0000001155 00000 n 0000001224 00000 n 0000001707 00000 n 0000001727 00000 n 0000005708 00000 n 0000002342 00000 n 0000002835 00000 n 0000002855 00000 n 0000001797 00000 n 0000002252 00000 n 0000002272 00000 n 0000002925 00000 n 0000006916 00000 n 0000002972 00000 n 0000005687 00000 n 0000005745 00000 n 0000006895 00000 n 0000007034 00000 n 0000007098 00000 n 0000007149 00000 n trailer << /Size 29 /Root 26 0 R /Info 1 0 R /ID [ ] >> startxref 7266 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/renameFile.imageset/000077500000000000000000000000001516266226600304435ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/renameFile.imageset/Contents.json000066400000000000000000000002331516266226600331310ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "rename.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/renameFile.imageset/rename.pdf000066400000000000000000000144651516266226600324170ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3 \ endstream endobj 5 0 obj 34 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 13 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 4 24 23] /Resources 9 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xE0 Eld/X4 C4]mB:$-H endstream endobj 8 0 obj 202 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [12 11 30 29] /Resources 12 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xmKN#a 9,5'ȊD (w`4(~|ntwtYNbVmWgU8XyrnB4ܻ#N@esj5hʌ֒ /3nC0&%DI%R85sOIFun肅io;갞\ѵI #3i ™vHMmeA<4v&pJeeayKM Z6ldL}J4Rk" {brGis!!y3?.n揉%~vO8Urœ_/Tdx,p5὎w?w endstream endobj 11 0 obj 336 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 13 0 obj << /Type /ExtGState /ca 0.3 >> endobj 16 0 obj << /Length 17 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 17 0 obj 1047 endobj 14 0 obj [ /ICCBased 16 0 R ] endobj 18 0 obj << /Length 19 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 19 0 obj 2612 endobj 15 0 obj [ /ICCBased 18 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 20 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 21 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 22 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 21 0 R /CreationDate 22 0 R /ModDate 22 0 R >> endobj xref 0 23 0000000000 65535 f 0000005761 00000 n 0000000148 00000 n 0000005523 00000 n 0000000022 00000 n 0000000130 00000 n 0000000250 00000 n 0000000356 00000 n 0000000767 00000 n 0000000786 00000 n 0000000855 00000 n 0000001405 00000 n 0000001425 00000 n 0000001495 00000 n 0000002713 00000 n 0000005486 00000 n 0000001542 00000 n 0000002692 00000 n 0000002750 00000 n 0000005465 00000 n 0000005604 00000 n 0000005668 00000 n 0000005719 00000 n trailer << /Size 23 /Root 20 0 R /Info 1 0 R /ID [ <6cfe4fd32301805f58952e1fd0b68a69> <6cfe4fd32301805f58952e1fd0b68a69> ] >> startxref 5836 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/saveFile.imageset/000077500000000000000000000000001516266226600301325ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/saveFile.imageset/Contents.json000066400000000000000000000002351516266226600326220ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "download.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/saveFile.imageset/download.pdf000066400000000000000000000164331516266226600324430ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream xU1D ) I}NT7)Ez+k gU>Kvkk/;i|M8̥0dtQ ]> oEԨט#μ8Z dME!>?3s>5<¹N}UN\̙e,Tu{&җej峬kHʆAѨZAg%jtPk1X[DΤFdԬ"yf`>=#_IతN(9mS)#C`ҿNV#ˀu|xKGV͜;0XN ;#M0xecYW`AsesEA̶MLpȅf3ꁓՕ]t2IDhuܜWbVZN9Զ #R5e5Ԙ_KDTťVdt$PyU>[so"_ooCˏUy8Z!7kp p %~+WUK;lmICU endstream endobj 5 0 obj 573 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 9 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T0TT02W0T(JUWSw/6TH/Vw5VpW© endstream endobj 8 0 obj 48 endobj 9 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 17 0 R >> /XObject << /Fm3 14 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 12 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T04P0RT04S(JUWSw/6TH/Vw5QpW endstream endobj 11 0 obj 48 endobj 12 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 17 0 R >> /XObject << /Fm4 18 0 R >> >> endobj 18 0 obj << /Length 19 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 20 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T04P0RT04S(JUWS03STw.6RH.8YTX!WDh] e 2`iZ ~ endstream endobj 19 0 obj 78 endobj 20 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 21 0 R >> >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 16 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x+TT(T0TT02W0T(JUWS03STw.6RH.8YADX!DQ0Q ) |B F endstream endobj 15 0 obj 79 endobj 16 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 21 0 R >> >> endobj 17 0 obj << /Type /ExtGState /ca 0.3 >> endobj 22 0 obj << /Length 23 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 23 0 obj 1047 endobj 13 0 obj [ /ICCBased 22 0 R ] endobj 24 0 obj << /Length 25 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 25 0 obj 2612 endobj 21 0 obj [ /ICCBased 24 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 26 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 27 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 28 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 27 0 R /CreationDate 28 0 R /ModDate 28 0 R >> endobj xref 0 29 0000000000 65535 f 0000006639 00000 n 0000000688 00000 n 0000006401 00000 n 0000000022 00000 n 0000000669 00000 n 0000000790 00000 n 0000000867 00000 n 0000001124 00000 n 0000001142 00000 n 0000001237 00000 n 0000001498 00000 n 0000001517 00000 n 0000003591 00000 n 0000001993 00000 n 0000002284 00000 n 0000002303 00000 n 0000002373 00000 n 0000001613 00000 n 0000001904 00000 n 0000001923 00000 n 0000006364 00000 n 0000002420 00000 n 0000003570 00000 n 0000003628 00000 n 0000006343 00000 n 0000006482 00000 n 0000006546 00000 n 0000006597 00000 n trailer << /Size 29 /Root 26 0 R /Info 1 0 R /ID [ <862472231fc96d1ce650196689100783> <862472231fc96d1ce650196689100783> ] >> startxref 6714 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/savedFile.imageset/000077500000000000000000000000001516266226600302765ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/savedFile.imageset/Contents.json000066400000000000000000000002401516266226600327620ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "downloadRed.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }downloadRed.pdf000066400000000000000000000146071516266226600331640ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/savedFile.imageset%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream xU1D ) I}NT7)Ez+k gU>Kvkk/;i|M8̥0dtQ ]> oEԨט#μ8Z dME!>?3s>5<¹N}UN\̙e,Tu{&җej峬kHʆAѨZAg%jtPk1X[DΤFdԬ"yf`>=#_IతN(9mS)#C`ҿNV#ˀu|xKGV͜;0XN ;#M0xecYW`AsesEA̶MLpȅf3ꁓՕ]t2IDhuܜWbVZN9Զ #R5e5Ԙ_KDTťVdt$PyU>[so"_ooCˏUy8Z!7kp p %~+WUK;lmICU endstream endobj 5 0 obj 573 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /XObject << /Fm2 10 0 R /Fm1 7 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 2 19 18] /Resources 12 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x5K@@NQk ZχY;!A2!W"#VP^ڳ ISI"yvUҌC 'j3ee8rZ2>G endstream endobj 11 0 obj 89 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 14 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [1 9 28 28] /Resources 9 0 R /Group << /S /Transparency /CS 13 0 R /I true /K false >> >> stream x=; 0{O1|4V@b46;;!b&n,> >> endobj 15 0 obj << /Length 16 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 16 0 obj 2612 endobj 14 0 obj [ /ICCBased 15 0 R ] endobj 17 0 obj << /Length 18 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 18 0 obj 1047 endobj 13 0 obj [ /ICCBased 17 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 19 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 20 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 21 0 obj (D:20141030183620Z00'00') endobj 1 0 obj << /Producer 20 0 R /CreationDate 21 0 R /ModDate 21 0 R >> endobj xref 0 22 0000000000 65535 f 0000005863 00000 n 0000000688 00000 n 0000005625 00000 n 0000000022 00000 n 0000000669 00000 n 0000000790 00000 n 0000001258 00000 n 0000001557 00000 n 0000001575 00000 n 0000000867 00000 n 0000001169 00000 n 0000001188 00000 n 0000005588 00000 n 0000004380 00000 n 0000001644 00000 n 0000004359 00000 n 0000004417 00000 n 0000005567 00000 n 0000005706 00000 n 0000005770 00000 n 0000005821 00000 n trailer << /Size 22 /Root 19 0 R /Info 1 0 R /ID [ <0c22306c488c1d8d4759f330f6a458c6> <0c22306c488c1d8d4759f330f6a458c6> ] >> startxref 5938 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/shareFile.imageset/000077500000000000000000000000001516266226600302765ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/shareFile.imageset/Contents.json000066400000000000000000000002321516266226600327630ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "share.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/DetailsNodeInfo/shareFile.imageset/share.pdf000066400000000000000000000142131516266226600320740ustar00rootroot00000000000000%PDF-1.3 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x+TT(Tw/6TH/Vw5TpG3 \ endstream endobj 5 0 obj 34 endobj 2 0 obj << /Type /Page /Parent 3 0 R /Resources 6 0 R /Contents 4 0 R /MediaBox [0 0 30 30] >> endobj 6 0 obj << /ProcSet [ /PDF ] /ExtGState << /Gs1 13 0 R >> /XObject << /Fm1 7 0 R /Fm2 10 0 R >> >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [10 12 19 28] /Resources 9 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream xmO0 +|!$iӭg`}R4$ԇcnVK HMbDQ rjf4”34CfE}$?/* wc(yfڜ鱶 +:=: endstream endobj 8 0 obj 156 endobj 9 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 10 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [5 2 24 21] /Resources 12 0 R /Group << /S /Transparency /CS 14 0 R /I true /K false >> >> stream x=K0 D9Ŭ+S]TzqV?c)&l#SaMI&K4Ģ`ci #+r2 u ngCZ1W${+TfQeJfiN#:S x8)Bioݞ󆇒p|B}Zo\;ܙ yR Wp;rM^G9`a P endstream endobj 11 0 obj 212 endobj 12 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs2 15 0 R >> >> endobj 13 0 obj << /Type /ExtGState /ca 0.3 >> endobj 16 0 obj << /Length 17 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xwTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf endstream endobj 17 0 obj 2612 endobj 15 0 obj [ /ICCBased 16 0 R ] endobj 18 0 obj << /Length 19 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream xUoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqx endstream endobj 19 0 obj 1047 endobj 14 0 obj [ /ICCBased 18 0 R ] endobj 3 0 obj << /Type /Pages /MediaBox [0 0 30 30] /Count 1 /Kids [ 2 0 R ] >> endobj 20 0 obj << /Type /Catalog /Pages 3 0 R /Version /1.4 >> endobj 21 0 obj (Mac OS X 10.10 Quartz PDFContext) endobj 22 0 obj (D:20141030135709Z00'00') endobj 1 0 obj << /Producer 21 0 R /CreationDate 22 0 R /ModDate 22 0 R >> endobj xref 0 23 0000000000 65535 f 0000005591 00000 n 0000000148 00000 n 0000005353 00000 n 0000000022 00000 n 0000000130 00000 n 0000000250 00000 n 0000000356 00000 n 0000000723 00000 n 0000000742 00000 n 0000000811 00000 n 0000001235 00000 n 0000001255 00000 n 0000001325 00000 n 0000005316 00000 n 0000004108 00000 n 0000001372 00000 n 0000004087 00000 n 0000004145 00000 n 0000005295 00000 n 0000005434 00000 n 0000005498 00000 n 0000005549 00000 n trailer << /Size 23 /Root 20 0 R /Info 1 0 R /ID [ <0cf523468e3e6692dd20259cad775229> <0cf523468e3e6692dd20259cad775229> ] >> startxref 5666 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/000077500000000000000000000000001516266226600235345ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/3D.imageset/000077500000000000000000000000001516266226600255775ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/3D.imageset/3D.png000066400000000000000000000015741516266226600265620ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""载ξ޿DtRNS:*SIDATx^gAa:L9goN|EiF;. <GQEQ7ⶎf6ᶞ#vX~='Mbl=g>w  w_x.=v1qĝjp'#G]ۓ}щanጧŚZq:v[MrfԓʽA7ǫN.++w:eo%H'k.6A;Z;{f6D qX\\&qxheaB#0W̭ +S! qIP.0%AxaΑP䀙(#R9#8:UpuUePo;YW δ$%qpӸe{a:׏Ñs,(N捑uXÿRGn,Lݍ)ڳ_{G~0;ttBM 3΢W 0V7V=BMpQ8#.v9\> hCQEQ-X0)IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/3D.imageset/3D@2x.png000066400000000000000000000032111516266226600271220ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""뿿㾾½ IItRNS Sh cIDATx^1 NX8z @XhB|uQM_-p9%,4뮊S8ܓ2XǺ^Ua p.mԹu ($A&&[-J3% hhFF dѕak\(*pؾrDa{>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#æmpǰ}g'.Mv^Z+d?nEB)7!ntKQ]Kxnr0 @# Z8UHH JRoPFٴ X߽%?o \0{ #;'_#㗛 c 7>>4 䯩ȓ_$l?S J,yݲub}iEפ*v̦}҆2) ;H f%<<=!\sS% kۧUd1B8-Cc/%1\׉n-{N# 0ŁFۼx0#,ΑGqDvӑ=4լv럷[-V8p8p18U26g) ,,Ф,X(#O lc.GJ< jKV:8 P IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/3D.imageset/Contents.json000066400000000000000000000005131516266226600302660ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "3D.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "3D@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/aftereffects.imageset/000077500000000000000000000000001516266226600277725ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/aftereffects.imageset/Contents.json000066400000000000000000000005371516266226600324670ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "aftereffects.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "aftereffects@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/aftereffects.imageset/aftereffects.png000066400000000000000000000022001516266226600331330ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""վտտ־־%tRNS:*e6IDATx^Փ0690333_hdӽ$rvSˮl6QfVq`CU\]#`5\1tW\= T¡v4&*P;)8(8(8(8(8(8(8(8(8H8H8H8hFШ6zq-r$%,goPH.pXAP3% =*X͹A! x͹$6ց\dc Sy!ܡ$3Su|!(߆!n{pqPf9Y2_P&8_zQTLN<\s W܍.,ۏ,`灱([0ns&5ב6S펂Mwǜ;wQ 3ށ nEhusY \3\٫lMrt5f6͇ap|8l>6͇ap|8l>6͇a3tw=*'s9qHqg:Xq}V\ \p\p\pκH(| #bD4qM HN, Y7/.sFϑ][}9<U]U=Kp.l=vD,Yr ݊˺$N~v .t .}t@ܜw.@. 4\^933- pc3tqh@'_kMQp1h{y^hf_|.vmzN*Q-0H佪 0,$/qp/CvlV gspG<Q wޚ.l:3.tQMAslNI[ClsB\ո -Dx964D-H7X_7wM\78rs!FͶ>o|("y.op1ާxo|MIx֠ס.pV?4״8,l%%pe:ԗ p=#bmF7e"AC2ܢ 8\[Y|[@d]\3Iq! oX:.Z&708?I 8jܷr;Vy$ޚeB4I2\L 8/IW}l;k 2܄q?lzygtE+,skqS!6w0nqf҇ ^uqC뵟m y]U\72a.XL* "n )Y͐kD4Uj} G]@:#Պ[ 1@ν_Jū6apKuKQSdeqgɿ+|`ϢۈQ~'8Kg~"G-y)8U0jv.4.EuYH! 7;Nn!K9a.zĸ q9av?vB3bG8'9[E۔~O[l}(!160Tv B?16ޯvryV^Kbgm4sP <<8@b<)z(&;pL0-VjIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/audio.imageset/000077500000000000000000000000001516266226600264325ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/audio.imageset/Contents.json000066400000000000000000000005211516266226600311200ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "audio.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "audio@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/audio.imageset/audio.png000066400000000000000000000012371516266226600302440ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x{ۀ׊yyռ{֛zތۇڅګ2tRNS:*G6V\IDATx^N0ad{{_$8ǖ"YC1MT%uA*0 RSW mngI$ m[p,8h4 ͂fAYpL8h&4 WfԽ!W%'#Gc;m,ߞ8ɭmQ6b \*I踫ugt(} Ȝ#G\cƝu#nw_p?ap"apW4]wSr/Fzk<>{EX쏴0aoUȑ#G9rދPiWɇAkc,`_2tIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/audio.imageset/audio@2x.png000066400000000000000000000023461516266226600306200ustar00rootroot00000000000000PNG  IHDR,?MPLTE""""""""""""""}׭~y넿ِݠ}֪|֪x{ڵ{֗ߥ޹ݻڃ؀ף끾ا R;tRNS Sh Y/IDATx^1 NX8z 0X?M`C;oxsG 7[ yٳԄj׿^gBBA܀qB/dtV78]@C $[ZC4J[gK{>[p|8l>6͇ap|8l>6͇ap|8l>6͇ap،6n=ǦͰpÉwN4 Rr,;+.E Wp+\ Wpq>NqgN~(kHLztaET\0&E%L͹={C{;݌rJչр+o5ꌢW:inF\ND*q^H).bMk p:\}j{=n0T{sqG wsꕙJ~tK&Ӄ F.888h;"wz8 ׵cE]%6Zp"I-(S ln/8ev( iQXq)B;bߐ6Z&_tSd/DT'[q]?l~8pd懓'N6?l~8pd懓'N6?l~8pd懓'N6?lWpdý${ĥyݤ-MqZ8?\:N}'KGW\zX 8p8p8p8pvgG*`q0[{j˴žv?}یq>;pMc׬pd%@V~8aGÁgYoJp{R^`S7Ҁ`0Ue"DlY8p8p8p8p8pM8pY+NVp=nq2b ,,Фu<)+N lc.ǵJ.}ӪuZmA\hzjо]ŕZBSqIf MeM2T\h".kمBqK:n hfgdUL6Ua3LVn98'E]U+j.6jWS_^ϐg7IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/compressed.imageset/compressed@2x.png000066400000000000000000000012761516266226600327270ustar00rootroot00000000000000PNG  IHDR,?QPLTE""""""""""""""TtRNS Sh oqIDATx^1 NX8z 0Xj@S%.PEG8т͝%Vs^ea P@ p-`A$5gehdɟ5ߟ!3}mpm=lW-W<\pvNW<\peÕ-W4hKJApeÕ-W<\p-)eÕ-W<\peKÍ-W<\peK=-x;rRpW8}L\߽n&RCR;답D888888888)8888)8888ppppp#en pppppppppppppppy888888888888887mVM'sP <<8@bPF;)z(%;pL0-VۭN`ėIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/database.imageset/000077500000000000000000000000001516266226600270755ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/database.imageset/Contents.json000066400000000000000000000005271516266226600315710ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "database.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "database@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/database.imageset/database.png000066400000000000000000000015611516266226600313520ustar00rootroot00000000000000PNG  IHDRnn8PLTE"""""""""".50BU֣ǂȂ1LաzP6ma٩Gw2t9<;ΑV͎?glˊܱ`Βۯ[:bZܰ߷ҙkˋC̍gۮ@љ߸4#tRNS:*aYIDATx^ŎAt6Mef033 JC2H{Xb42:2\. dnp]~r. g(8i 4g3MLSp)8$i4 g2MkqnBnM(92{BnXrۯfT-aqKuȸ5B6*H&Df$WDY$o@Õq4uOí1w!j+IT;Wp _S@õTq-l}D7!⾐\Bĕd+T73q;d2n :gVV~w!~+AYz.r"PR38X,8OIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/database.imageset/database@2x.png000066400000000000000000000026371516266226600317310ustar00rootroot00000000000000PNG  IHDR,?MPLTE""""""""""""""2աm50aP96GǂwȂ֣1<٩tzgU.lۯ;ܰVLZˊbΑљ͎̍߷ܱ`BҚ?Β[ˋg@C:kۮ߸ҙј_#tRNS Sh uUIDATx^1 NX8z 0Xj@S%jPEC8Y=-V_쵧 ⸊T.-rm\ } X >*^[~vp2y! s2Ivjk1Wlͭڠ[8؞hfw8n(6>l|8p`Ƈ6>l|8p`Ƈ6>l|8p`Ƈ6FcpF =UNݬ[VR]\μNw+vAjSiqQW^VVẖIcťE2JH<)Yuk lJ}yJۢ-*#.fYJܒbč*!E[V=< ˾^~Pgz]CR9q)<*ј[i>}\חo7!x\Dcu dpyo1ۀ`{3G?>qsglnp\ VQ$~1[Ydr1)>s;G]M?-Em2QSӜ49iNs@~a1B߫Y_/԰IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/dreamweaver.imageset/dreamweaver@2x.png000066400000000000000000000044651516266226600332260ustar00rootroot00000000000000PNG  IHDR,?yPLTE""""""""""""""ߝى}ݗޭ؃ּׁߚ؅ۏ⣨z絧{؆ڎʩڌܑې泬׃ݔܕܔżޚց䬸ܕրيѿܒ}河ێր{䭿麱ىߛցڋ齺ޘڌ뿻ޘҸݔúݗׅ㨰؇ѧzȮ׆ľߜӨ}߻ޙ趭׃࠳ڍ꿺ޖ؈ݕᡯ؇üޛctRNS Sh  F'RIDATx^1 NX8z 0X?M<`>B7xsO ʃ;[`[ zٳ ¾gVsY|!Qᒣa(ʐ[u~a;͇ap|8l>6͇ap|8l>6͇ap|8l>6͈{`^36n¦íi1sV[;rܬO+.ş׸5qrk_MQnАA0*C DZ*zGݶZmպݛ{'{|${~y{;7yAtlM?&>~q9ٱU@΋#*v^Qu`spq<898pp~6x'nx>x45d7%i[?drgx88$ CqTT7" G[`'r.Ejnɵ)χLR}Jqdց12Yq$!K`LY9C慵)Dü8(@9 D{\Hd\26- ,GQjh8Γy ɽݰQ2" H-w!PlFCv[MHlUŘ۠x .#4΅Ģr#t+SD ؏ ?/`'쿚RDQ2R谷ja:¥ƭGrĨZqd13f"-WIԸe1ߘ6g7%/1,_Ķ jqJl Z1/_ J%\5jő8eFV|7Ἠ&6Y^6-_ۑp]xz1qlzqT,BƧFg͖/O1=ӓ`8|hqnZ. w"2>!FZ0L:ƕ#hˮY$g,xpqmH|Ef2Vs)q"y-ݣ W~BF+sBʍ쥯 `zY;GƔ RDsn/'M|8/rN'PI zu ;G!l~?.Tlw9!X{Qd>>qn!hx= t)0.WekF];!A%i;w;L=pUCQ -~W:@ΏP;t(I,зt{<]5p 龖%pϰY4B67rB berDž!9yl<)93A8 H"h,q+Ȏ|c^>QEݙˀCwcD+8iSV )c-W 0niT#@ ϕ}_3_qDh?+9EjHF};6 Bf;o| }lQ mt9AXXM IYIPF^N\^=xZ8&@dԖ +U܁VIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/excel.imageset/000077500000000000000000000000001516266226600264315ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/excel.imageset/Contents.json000066400000000000000000000005211516266226600311170ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "excel.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "excel@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/excel.imageset/excel.png000066400000000000000000000016771516266226600302520ustar00rootroot00000000000000PNG  IHDRnnwPLTE""""""""""RY&V!TéюW"b{Qܪj<_-b1S伹ڣwL`ωc2qEaۥk=΅S՗x߶؟ḯ^g8b0a0sGޛ{ˀoBuI]*֔r[(uJ́n㺿ݫ`.l?sH֚~\)佁Z`/灼Y}TԔ}ҏqDf6W#e5c١CtRNS:*fFIDATx^oA Nc 3334.=J/adVcG(M4xMM;' ą&X \褉57"ˑv4i@#M#M#M#M#M#M#M#M#M#M#M#MK&5X qq \i䌁SN9F-cp9a;~AL^pݤŲMvj#6f'8z8d!xn4K./j+]fznvbE;!.Wف ]J\wlM6?w.7w O--ݣ)<8ohzexYp_$s[/^>3q!>:Vq0pm hr)r)c"ߋJ44MӴ]@7ܯIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/excel.imageset/excel@2x.png000066400000000000000000000033471516266226600306200ustar00rootroot00000000000000PNG  IHDR,?(PLTE""""""""""""""_-㺈bU SW"{QY%c2Yf6TюY&V!pC՗j<ᶙy似ۧ`.b1b0ܪωRqD١vY솿aϽܨ|ci{Ӓe5U ҮԔ֚ni:^+͹ڣ΅a0g8]*uI[oB؟qEyPk=^n@לxsX$l>⇿asG՘ۥwL؞֞ˀSmzQu߯Њd3ol?l΅_[(}UuJẃ\)`ݫm@sH~Z`/}T}ҏW#k#tRNS Sh '[UIDATx^1 NX8v C1(i?*峀eohi݇8ɹs=%X`sks^-ElX `xˣ/c[ky{۫ߓy,9g&{=qEؗnlq).{US-m4'qa'qa'qa'qa'qa'qa'qa'qa'qa'qa'!7qA7q-6nyn&q-m4O}yf؟7\8v8{9gsf'8 Np'8 Np'8T0g 8˜l'V΂}Ξ .Q0)-(Lf0na)~q~eUC^iƍڻnQ~:~uk;KBR4J:@ķi}e\?HU5kL&ÏG-OSYjM"=~g/Q34ݯNr.l9e2%Q) i>8ʴ۴-њ.k8ZӔޥp G[y8N94wI9&.ڧs>"W Gq7un[ ڸƥWC\CʸW b¸]=S,a^F젊{pTWĽ~V`@ΰ)V'E\2RFe#=\wr=~F 1Ng`9DB g}5&J27:8+MF ]TpyF :ohp`߼Nʰ[q 8 Np'8 Np'8 Np'8 Np'8 \0\Ąѹ"3?50 K3sp4)FO e+qO^;a8\IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/executable.imageset/000077500000000000000000000000001516266226600274525ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/executable.imageset/Contents.json000066400000000000000000000005331516266226600321430ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "executable.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "executable@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/executable.imageset/executable.png000066400000000000000000000014021516266226600322760ustar00rootroot00000000000000PNG  IHDRnnPLTE""""ĢĢĢĢĢĢĢĢĢĢĢĢĢħ֟񢢢əЛ𫫫ѝ󩩩٦µn|jwBtRNS&DE0MIDATx^9 @Da_.U=sI/AUT/:} 1\FAWnT+Yލ.Y)B Dʎ-5a60j[U R";P13uf$JDϵ\6ƑMm\Qc1nG?9s!nH%lA[{ӯ^cށʌQع"#dJ jL'3U94~^JHvv}2 dO [qoh'0wY6z= q䎨J"f8l~$6w6½}'wr{t%Z=?sE߽yX>]">=1sziLc ؑDHҞڛk9Ou96|#{w>ځcAOm]IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/executable.imageset/executable@2x.png000066400000000000000000000017471516266226600326640ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""ڢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢĢkxyĚlzѫjw𢢢H_&tRNS h+Uܥq] m-^IDATx^àS`gQ U}O+&Eݔ'\^̧G5\O v{pX' w\74rh8U އϪ.u1Å+V8?|nΞq>N0 g8}ٷa ','y*F=x|Jaw|2&ۖ#8#8#N 8BqޞYe>kUtDV6qh]8CRǡ uq@6m+^vlK6rm̅?lXbʤ=/M.O(G/GqGqGq Hl?}ndt00?pA6yT[8-#ް.r!;pL0m 8YU矝䐍ᆃ50NJ眙ea睚㍊챯FB曙?:餢|ur䑎䒐饢fb=9啒ꪨC>ᅂ94;6㎋<tRNS:*kðIDATx^N@ af/p!+%Vo|+JYGYeYQn.˫Jݕ+/U\ j(s8V WMkZ㺮hr΅38-wgӄ\t ;M¡X%@0zp#=x y8$xz2ר'xzרw2;4bvVFh'wC MD!{9 9{ѽstxMQj#+6 xMΡw}ɹZ>~Coh5? 383θb ݉ђWHOXeY7!+IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/fla_lang.imageset/fla_lang@2x.png000066400000000000000000000026351516266226600317250ustar00rootroot00000000000000PNG  IHDR,?nPLTE""""""""""""""3.ꫩ{x:5<7UQgc矝ᆃC>lh䏌䒐ᅂ>950E@qmMIyu≇旔JF啒FBzw72uq⇄䐍ZV�~{yvKG=995Ⴠ晖饢챯tpシID?:≆kgEA=8㍋~if믭61[Wplwt@;}衞5083_[첰*BrtRNS Sh IDATx^1 NX8st @ThJԴ&N׽.ę%fUѹ97SP*ulno_p˞ P͠k|6Z]TBUw ) X_$|")CQ.NJRVɀm%}lFefCpl88lſLL\?UPDb,S*.}383838383θksׇ_i8_o?kTaq% $k)7hL.(ذ^q!+9.[k^ۯ[n75 Gّ]3.6;Zc] mfMQtApMy֠~ΠVJɋMmgowf >[.>s 6A珣M?D:'nMBW- :lۨ;U NQ'T?D M9s)6Upw*p]Mйh;s].6rW+sp pq :a866u'l uis :il8A'q\P:u븹as :76uNݢ=[uwhK[umi7[\wFAm=*ck:1>buO˸!Qla nz %lpݫC>oEuKJ>|K\%.qK\%.qK\%.qK\%.qK׫n7^79AXXBC IYIPF^N\^=xZ8&@dԖ +*IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash.imageset/000077500000000000000000000000001516266226600264265ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash.imageset/Contents.json000066400000000000000000000005211516266226600311140ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "flash.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "flash@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash.imageset/flash.png000066400000000000000000000006271516266226600302360ustar00rootroot00000000000000PNG  IHDRnnZPLTE""""""""""~op_utRNS:*啇IDATx^ڹ1a=_QYQd ijH^-(RvrC\Y᪍5K8hۃG2+Acp4Acp4Ap(4 uhy\k&&g 'N39q8q!'N܂N8q'=;s ި72'N8qek@xё,P|R\D8J) gIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash.imageset/flash@2x.png000066400000000000000000000013421516266226600306030ustar00rootroot00000000000000PNG  IHDR,?PLTE"""""""""""""""~op|üqsĎ tRNS  ShΏiiIDATxj@Fc!6ma%FNS]P9׆cZ1̮VCinGV]V힋5j\p.VĂ:twnǵmLۿJ,:>1V#z۫:^`lmT Q6*Fۨ`lmT Q6*Fۨ`lmT Q6*Fۨ`nF-ݿp͖k|y!/D#'׀k0^%ĉ'N8qwϵw;:9qĉ'N8itrĉ'N8qn2qF''N8qĉĉ'N8qĉ'n֦ c!q`=1ǷcKc; n{n)NM'N8qĉ'N8qĉ'N8qĉ'N8qKZ_\یm1nX;NXw.p9QcQĉ'N8qĉ'N8qĉ'N>mj/=IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash_video.imageset/000077500000000000000000000000001516266226600276145ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash_video.imageset/Contents.json000066400000000000000000000005351516266226600323070ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "flash_video.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "flash_video@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash_video.imageset/flash_video.png000066400000000000000000000011031516266226600326000ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""3.[WMI]YჀokOKC>~啒;6:5ꩧ50묪mi50旔B>シdtRNS:*[q-'Q|m3zèt6׏wk.v NI#8i'4FpNIC8i'nӣqeĜ"q̭YnBrGr)-Q.==w$'ʥKfriB9M4 $N@8M4 $ N 8er' wuLVkas6sEP N8՟TS NG8TSNG 0gΜ9s̵/_t4QqUM[Լqq|(kjIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/flash_video.imageset/flash_video@2x.png000066400000000000000000000016751516266226600331700ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""50ჀokC>jg}zOKLG]Y;6咐YU묪ꧥA=旔3.94ﻹ皘50}jtRNS Sh }~IDATx^1 NX8z @XhJZqQM_-p9%VuWE% rIAssG cs{>_3 0]Z R0_sFwYdt0Mn]8lAhztvP-͇ap|8l>6͇ap|8l>6͇ap|8l>6M[Ǧía6m8qWպ@d|zZqV\]lj . . nl}={/-\qފ8Z !(qDA# BQ0∂G8`%(qDA# FQP∂G8#((qD# JQ0∂G8`1qZe?)8֦rt\QG|8p#>Gt8p#:G|8p#BG8 čZ3 . . . . . . .'fvlĶo ?3 ,,Ф,X(#O lc.GJ< jK-D+XIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder.imageset/000077500000000000000000000000001516266226600266045ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder.imageset/Contents.json000066400000000000000000000005231516266226600312740ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "folder.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "folder@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder.imageset/folder.png000066400000000000000000000003751516266226600305720ustar00rootroot00000000000000PNG  IHDRnn4u;!PLTE$$$$$$$$$q tRNS naIDATx^!P^N% *z4)=%d~H/u7ܵG20^nZ~]09*ݾֹ T;p8Qɸr<< TzIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder.imageset/folder@2x.png000066400000000000000000000007321516266226600311410ustar00rootroot00000000000000PNG  IHDR,?6PLTE$$$$$$$$$$$$$$$$LtRNSx $ y"aBBIDATx^ K1@ 63ɗ qkG[ :V u:R\Eg@t8I\W~ ⼭ 㰝8ʸ&MJ%z4nܡ0 qX]@~#}el{ Wyupí7Lw* O} > 1IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder_shared.imageset/000077500000000000000000000000001516266226600301325ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder_shared.imageset/Contents.json000066400000000000000000000005411516266226600326220ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "folder_shared.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "folder_shared@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }folder_shared.png000066400000000000000000000021511516266226600333610ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder_shared.imagesetPNG  IHDRnnPLTE$$$$$$$$2'+,*C<.$&9Q݂x`)b}߈;@(\M{JKY5G0iU?-[T]akZ3އSDޅ7yddR4hzmu݄fe.T tRNS R"IDATx^ K_?ma q O24{.샲 {^Nl<"w_G'$z;j17s\;n#GyqW\=$(Puq}S^wyZ|Fyh^GoDb?kגHKӫ#q?5Q若fe`Wf0߷%Gvw\u[Ε{'2꩸{?sHLZ^wJ{+2bA(t"eK2[b>I_&` %r7f`ommG_sFG{X0rЙon?Xaq)áy4.h ;IENDB`folder_shared@2x.png000066400000000000000000000036731516266226600337450ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/folder_shared.imagesetPNG  IHDR,?PLTE$$$$$$$$$$$$$$$2݄ߊ[%3?aކqEH.*T+}vM)ߌD9ߋg:j܀ޅ5?-@^<݂FS8{;$NuZs1O'L4i߉X܁݃,]~hdbYW(0QC6zURIPyf_&p/_tRNSx "y$7:IDATx^ KN1i5t7{i<Iwm-9 n$<t6.,]<Xx\h@\suyơl8ʸ,܏q}(Jع(!`\.j - H9iW%Jђ}l.rֶAsoч%{dHNnNNNNN+[VVZN_Qsw忹p~NGfFsה[VY \NW.vGt΁.9×*!ܷ(` uвB_X5s[bLnB^7-g=Wso渃qz3z@B{(1lfHnD=BH{ w(˼?/V|/KNɕn`; ?GQ$Hm'2tz0'Ѻ~:8Ź_bUER8ؙxxqkńV;Pw6`B#tt1 nO1U6"+cqﯿ5Tr8f}L Nq&@Ѷ`q.7DW Oa]\wBОEܻqe C,nAfSVNq2hGntxj/p @,\wLj=4flI;NS.uJpe~~el/F4d:K[ˈ5],GtC># uz9C?ϟ0zvLq0νYf.nO4 [JJ-e[K8ZҼ }&}%Մ' 0: i=1\>esIiq6TbiZ~-p%8̇5>ҎCn(n$DǹAawz=ܦR Vq PNw'2H% *S%*ߔuX9 x*n0n*n0Y=8~a㴅z.M~N8888˧gO0e"z SDLW)߲0̿/8`tV`[IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/font.imageset/000077500000000000000000000000001516266226600262775ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/font.imageset/Contents.json000066400000000000000000000005171516266226600307720ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "font.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "font@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/font.imageset/font.png000066400000000000000000000021351516266226600277540ustar00rootroot00000000000000PNG  IHDRnnePLTE""""""""""Ùʲ䛛ؘѰݶު»ҿ𥥥ﳳ常룣ߵཽ㦦ꢢּ|<tRNS:*UJuIDATx^U@Nm73233c7qi٣VDأKtuww:Dsٚnp(G۲h#HHHHHHHHHHHHcHcEuNuNNpӜvY9'C`|g ٸ"\1q(aV@pٻYc8m{?S:[9p<W=ĥ\%E -.2n 妏2`8qX9axoN0+TA}̾V~EH0d{ +܈nZ qIt[+s15h'F-81flCB.鈆i:AP=R`; H vP–^}uJYGU xNF᧡j#|CR%wt Ul7q \J F(30S]TR.*n ~HyD f-s ച󃊎N\cN|16 GLzo9N_jvf&ݤE[rCuym(ҙ+}4_t~zjNsӜ49E{=puot؄luҷqt:N Q IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/font.imageset/font@2x.png000066400000000000000000000036251516266226600303330ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""䢢ӞĿ󱱱߲羾Ч񫫫͵ǼֹԦ츸ũ载қतG=tRNS Sh 5yIDATx^1 NX8st @ThB4&N׽.@yǂ{RP,ulno_p\˞6PeiU0xk@cnۼԠLU=j\ʇap|8l>6͇ap|8l>6͇ap|8l>6͇fĝp6nMm_9qˉjwap>\wfu . . .κH D6BT7\6q_m,fH&a>CO/9҇ 0V[*0V'Pu'aU !nIf-|EX ME ]$l)t!õqpW=T5al+LWF֐঑AQ@-nlTKpS?Nm]6y=]@7dh0<6Xk%/M\4t#U;k݂*)-AD-~i#l. ꁓ}Δ-ӫV у7u/g 0dUd{%U8 NȍE 7w:M5[qeû=DeYWk g#ZAZ{RviۦRnJ1X4礃串w Ңȿċ\Kً;Jb6RΕWbW )e1΍Y#8Awty5O; N k1`k%ooEߑJazoPAG^\K[lMS0lX%^wy1UwEcbXY'KJ-U q-ijYc{1ɭS?!ng^l!J+'=ocO2 N׆Aa6p'ÕyJK6Rj^QUO[/OKW*ZD.+ZW? k(hOg8ZH~O˾CTo2dǰZDybv^xh^$.Ek)q]ԛ9AXXM IYIPF^N\^=xZ8&@dԖ +ۖ,IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/generic.imageset/000077500000000000000000000000001516266226600267455ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/generic.imageset/Contents.json000066400000000000000000000005251516266226600314370ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "generic.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "generic@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/generic.imageset/generic.png000066400000000000000000000005261516266226600310720ustar00rootroot00000000000000PNG  IHDRnn?PLTE""""""""""NNtRNS:*>^IDATx^ 1Q>3`Ϻ[_8KTKnݿږ#eNqJqWnI_-SD',4 'Ip,4 'Ip,4 'IpL.mq[bmFE_sppppppppppppppppppppppppp5>:J$*s@!""NC0 ;"IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/generic.imageset/generic@2x.png000066400000000000000000000011461516266226600314430ustar00rootroot00000000000000PNG  IHDR,?NPLTE""""""""""""""".pktRNS  ShΏiiIDATx1n@E7_]ѕqb)s8A'_tRNS:*司=IDATx^E@T&I $fd033333GRmo5I[YwsdVr\#wtK$[Z:Us%Z֨nj6hcmj&X[GRkx5<ǚc ϱ&űXsIq9D8dNvճK\䔢s\<0@ySmVUnN$*jp\JǴ =.鸖0|0mb#o^;o O-lp}7ni>7?mp W3p W E3pc^q͸pwG 7;2c;bi75nmL'O8:m3^Vtܘ6U/.v%}5_qSn+]1ȋ>j9G;\p^{r{NH^Zmѫ 2oc.Ex.aJ%>G-W&?7Iq4$q?2ML$G9qs- Nt$U$yd" ir~rt^@IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/gis.imageset/gis@2x.png000066400000000000000000000037031516266226600277600ustar00rootroot00000000000000PNG  IHDR,?RPLTE""""""""""""""QP=;tr=;߀KIGE뮭쳲a_CAUSᄑ⊉~~}jh?=RPVT><⋊ki;9喕矞ᆄ첱A?㏎pousZX@>SQge騧DBFD<:䕔TS曚a`챱杜[ZECJIhfWVHF뱰WUᆅ睜LJcakjrpꫪCB촳믮TRYWonnl嗖袡{ywv⌋mk暙yxfd}|砟馥ih￿[Y瞝nm밯JHdb`^ሇNL饤eczyB@ba驨\Zxv駦edPN|zIGsr}tRNS Sh  IIDATx^1 NX8v C1(iHL헾y:wox'ʃ`ͭ |ٳw¾ib *A8`{H ct9p?[uH2]RD,mL8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8L8T8\8\8\8t#l&n ̈́;횜wWɉ[DTX'g+.ފbWpWpWp׵}ՏG}7}ypׇ]jj#0lU{@B&xA `; 2j 8'9ry✳7/C\fF NWSݥTb\%)p+*ܧ|gm$63/fX^K 0+"x)3f&6Z nGژ5Y8]%ܦ0*֦8EV½ivol.:NޖnOl| 5F;Evs6Ϯ.mvYfny;/h[*CX#{Њ=l$F6fUjJWpi*JIߵl-D,t J$pn:AitTgh Õ܉]2(OUQpU$pm(m%s|2]:u8P"^p e!]ywe"8h@úYu(B@W.iƑCXW0N⦷ 0"3# d ^,p .:ޖLǽYp8ֶyۢ ރLl[ Mi#ȅWmp~>IïZA>۾)$8X +n?8Hyq'>S8S8S8S8S8S8S8S8S8S8K%[-LR.9sP <<8@b<)z(&;pL0-V-Q=IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/graphic.imageset/000077500000000000000000000000001516266226600267465ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/graphic.imageset/Contents.json000066400000000000000000000005251516266226600314400ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "graphic.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "graphic@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/graphic.imageset/graphic.png000066400000000000000000000013321516266226600310700ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""~ݺx՛pgbWٲѢǏsi˘޽È͚ʔǏƌÇ݋رׯՈԩĿݻatRNS:*3hIDATx^N0al{9/}eFZ4 <ؒ5 si9nWkreڒwz^)A[^dLڊ'AIp$8h4 M&A᠉pD8h2\2UOk'uMO3ƛN9$8HR. HW㈝KC)7K \bn&uk'3;SE̔j9K4mnsЈ9hD~ # 2r# qr$9K%FnqظW"xx)l|8p`Ƈ6>l|8p`Ƈ6>l|8p1`^FlϞλw"rrÊ #+.\̧O8N8N8a:n W5}7.GeMkʵvk{ڶ8g{s fUTVW6[_d,Ґ9nHc"M:ehԂ|+&/}8G?4 hOCYu9ֻWZzyy'%}{I5 (!#(W#4h y?Uo$6Nkݩ7g{m6`h6uq?'۽w Έꄧ=]FS},(X?+ηc"(Fd@1͐ކHSsYEp Lkq=KonҋnmwE^~%-0&%Dp{TQ58J Vϳțdؖ;m]AהoɴcsB 8W/Vی"?ZA < N 9CU =XhٞQ0?IO4--p:8!۲18\VH9 Iahm\b~Hl{!D+ ʫݬtLK&;\: # o0֗˳#͸ס 3mO :EIreEZy {tS-<082J͢{6Hq1řQGѨlY6PR%>8BMg2ރ\@p,YC'?1dʗܲ2D2oɞ7bP¢c3 5Bo(T/d BΈ(`5{QCnZFx fN0lj4jم+ȁ2)-u!h5OW'Vs'TYdTZdyOWE ƦAs xݽ16ϝ [qSpհ589(. @\q )ǹeTsgv[ݡBtʠܠn&*=_dp*/bԢڅF0eDTOAV7c^QpkRqU"o3!4kh~fJSCY 3 <Ǩ&VU̖N9`~ mסizz ht !lǴBqMUU"6$F&DP"Qe(?F? &D;h*!VN)xFC3'csݨ`4 dIK+$[jy 6l@_wܞecI%w]퐊 MGpv3pKVL$|MZ^ PLg)pb1D+yj٘0 d րc&T:Q@B> k c}><Br 'F0&s$Bg*YA[ԅT+S@ S/NaÔMQI?j~qW(J#@$| ,LC 4TCP' Q' R19.5܃.p#z̆2D&gy4^* CZYI#sǏ=dp 18X 18cp `HX5irǖ.Gp=$u~6ٔLQ:?MpgC6{( Bs5G\ZbO Y glZz_,~JVs|*rkVMleh#YѕR׬VKYy׬k2 Nl&K .Lpf $6\ Hl&@b3 .Lpf $6\ Hl&@b3 .Lpf $6\ Hl&@b3E*O =lcb׮c~OOyNfk%ay>[2ca&{Kgj'3+&\0fL8XDz*+#RATG@Q*V(JK 6tod,I&dr{n_^ߛ̈́}Ϲ'MLs?}+8'Z^'8' p"Nke'cWSC֗=~+xYLKeUM5'u΄^'[=hcMOҪsxB9% p- LQ-ի>+Wu>ӣ>lNE{յG>TL]dʀS6OeU, p-\V/e'B БڝW)U&VsHou/|o+7ؘ'9;gh%chFO+ KIuӶ긬#7p4hl5=՝+ToOs\}gKVt_Hi8n*6RR{oƤ<+ٜv#"* pC,s޼m`F$*ooL}ush+ p\iYGJ- ROΩ+u2p=#/ p =B$oP?Cs^`wV{ }ɚa#+׮9;" pS,W"]†~5:.VN#}Rck{~d>x % p9X0Y-kڊG+zGJ<9 [jf09lӻsGB"&a/_8qFrBh4j.?UadX) ƇcӼ@- pvz7ZR+WmB%fYҹHU,i>@8gRN;K[> ĩ¤ I;}-Be:BT7p9wJp\+هؕm _Rn3 \LZ?'su(.ljPt6pkծ76-̀&w2;c2c\K{fjBϻwL{tNBͳe$P ;'^3MaΣ^b΄i&A$\:=`. Kf5 4iKW^]7h}P]ْ@rޤ (r_vRDІ]7}|-. ptq\I@3ggtj+xPeTO>b(/u8.`ư)d¿Xq͠Lh jneNÐl$+f!ru4 vrQ:j3a=E]Wn5P2<(1'9`x=r$CĮmbN@kl8 ֌+$Qh(WLPo lv`E2)$ý $qг.MW2S2":7;e#!0pmz7gAjwrnmYulfbx^I*!6:!p?3uKH5p'p8ncrO^ۂfMd\!MV6>֒N\4g,[M4H"'J0:u[(RX3h'iagvmT`s15]LZy1I=5l)%%2G&x%J0Moa\}Kר=3.z7x\%]lK oq[St35TucKXwHh}Ws"iҒki02y\RÞNrIͷA.ejx}[[(hD%l%l<8NQ"HՁ(j{u`~wwk-ݝDZZ8grkjD|->Eku ڹn4q3=XT8ۚd']00tpv> 6TtFB8tJ n4M"!BM+"aжgҵKCzfܓG t3WPanڳ[g(s.;w}9[P+d^u<LIJ{ړoڥ;֜zk …ԇ5*wZg3˕8*D4Ҥ*x5MM53F-v"pɳ™jSnB |c":y.H pź}{kxJ61DpEO;pO(\D+Izg]:8Lrm-B?2i)`)I)ha XxA&ue6kt%{v>g3sfw;_e.Mkߐ 1y>8fQ۞Nz |nDybhu7" B7_n:1l2 ); ˏ5p~^Z&u*`Iذbg\O'p[uWx@$ ͎Gbӄ-8IÓ^ i9e07R׷Vrxvb L݈X.D_ xN83'K^$Pjǝ vhDrϊV 'po9XN2ɆG̎Xi4qOn +"5U8&=:byX#yV6@8@8@8@86SamIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/illustrator.imageset/000077500000000000000000000000001516266226600277155ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/illustrator.imageset/Contents.json000066400000000000000000000005351516266226600324100ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "illustrator.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "illustrator@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/illustrator.imageset/illustrator.png000066400000000000000000000016531516266226600330140ustar00rootroot00000000000000PNG  IHDRnnGPLTE""""""""""ciŝƟgd˜oudɥӵzĜkؾέήievֺ}ͬбȢʦt̩ЯmȤ~ѳjsnԸTtRNS:*iIDATx^o0ik;s(yg{?7!}DJ,˲'pzF\wezN>q.1iMuA;584848484848484848T8T8T8thZܐzܠ܀;08BBT1juEvg|䛩s+1ws+.{ϴ]&Mʿ̫n%TnDNmo[C^4w]\9B)s3`~Ci ^ryܚ,pcY ޷&ٴ(\#^. <W(,E wmq$)mQrKJV|%x;)= MJq }jqY).<}& čB_e):}#4/í~ph?yNWsgqg\&HJ.MƱ,˲;͋DIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/illustrator.imageset/illustrator@2x.png000066400000000000000000000032011516266226600333550ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""vcϯ~ykٿigtʨ~Ȣŝzֺӵn}eֹҳşаewĝԸqѳ̩uʥtdiopѲšҴպƠչsЯȣʦfƟjԷǡɥxd׼tؾ4tRNS Sh Ύ0OIDATx^1 NX8v C1(iU$&g~(gs-wrByps{ ھ/{v@AHx]ƾERzBң6/5(So8l7mWB6!M&a q؄8lB6!M&a q؄8lB6!M&a q؄8lB6#͇{p'l>ܵmr=wj`pB\wnu\p\p\p?n+( p3P>PbƊ (6lGfj/{fq[`%߼Yg/+,%[a5{ eLwvWMj(t  v+"ҡtqͰȩL&n".&XR˫ .[iu+.J'r]@磐NS&S!G|Q< }f0~u ).5fR??hFB;zD4\AJr5@Uݸ:ċsA\(NC#rb)F Ւ!M'ƵOݍpZEr51M۰ykۄgy̸{NqqqlQHĸ0 ʋۂ,;M^d t,vA@ A'H+.ϊ]Ap6VR;)΃K P&¥Jܚ`-­#ܪgݭ<9\{k/pېv:B5N{t_A ՛r\lZ]ɿA2TmTn.e.\Tl\3Tk\8T!c!*\?t L-߱_ W&TpT]LfgA ApkRBWըM7Ϻ7ǾSvG,pnU,آlnW-"/sP <<8@b<)z(&;pL0-V dSIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/image.imageset/000077500000000000000000000000001516266226600264135ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/image.imageset/Contents.json000066400000000000000000000005211516266226600311010ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "image.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "image@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/image.imageset/image.png000066400000000000000000000015051516266226600302040ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""Ͻ׼سݫࡡ춶ӿͦԩ$JtRNS:*)IDATx^W0dw!l{{{/ts>h{ {(I$inŢJ]gr+jY<T#*fI4i‘‘‘í-G2]NWm殌]vU'$<ڦ 2G.N\+;wY3:=bkcyZFomh=jk׈u k=3gil}+_?<6q.r=Ъ/;7S[s`CSnh& Pŝ==;/nZg{ =q0_cdY(-`5wș{Ox'bKwf[MdSJd_ 'p '\ rT4oI$InwIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/image.imageset/image@2x.png000066400000000000000000000033441516266226600305610ustar00rootroot00000000000000PNG  IHDR,?qPLTE""""""""""""""멩۫򼼼⟟֝Ꞟ˸șཽΨ䛛ɧ餤Ǧͱ󰰰ó壣ԷtRNS Sh Ρos IDATx^1 NX8v C1(I[|,`~gs-9wv0$SDPC;[Ok@KnۼԠLU}j\߶+͇ap|8l>6͇ap|8l>6͇ap|8l>6wŦ=t6ٶo9qˉ;vE38ۊ;pX)p~\p\p\p\pĭ ɟA ? Ǝ.y].][eKP,HHsΝSt`4G/\7gkS~>0+2z} |I/,w0[A[Wbd;RVq!D%t0bg؆j;`I͓1Ϋ4s$}!1BB\7wS|"e1:ơݠRzPN]~:3)pՃ8)DBLh49G=li;$=·X1@wѤ!su@@ w[Bns(2umaq3fe%8sq5fSg)qvkecM( *ӥl-\6LT\ ~:.DZnKpKǕ0\>`a /x&xp&eQg'ByRxe;s9zPiMvaS[b'IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/indesign.imageset/000077500000000000000000000000001516266226600271315ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/indesign.imageset/Contents.json000066400000000000000000000005271516266226600316250ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "indesign.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "indesign@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/indesign.imageset/indesign.png000066400000000000000000000012331516266226600314360ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x~y{{yEtRNS:*䛨<UIDATx^ڷN1aؽ17gr9 qj-1ijracZHVVn%ͩlBͩdPPP`\S5Wڊqu) W #Gj#g  ;֦=?H %/!ǭi jlc¡- sU-:'(HgF:g(D);9Eq:/PܥΫ!vęuw?~xpff}8^3IgN'?09rȑ#K1${RBqc/\ܰerIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/indesign.imageset/indesign@2x.png000066400000000000000000000023621516266226600320140ustar00rootroot00000000000000PNG  IHDR,?tPLTE""""""""""""""y{~{yzx|tRNS Sh Ύ`IDATx^1 NX8z @XhhZ_jo;%VuWEK8Y2Xg9P QyN@dFK [e۷ƥv(͇ap|8l>6͇ap|8l>6͇ap|8l>6¦=tl:ܽmr=wjwcp>\wjuu\p\p\p?7u p"HllSB w{zsXQ|=ұdif#8#N8#8#p7)aoXt>*AHL8;2Qq5*.׸rK K> FU:&%ۆBe4nF*n!z^a&dENձpc< < +<  %s g2nQ Z"s2,WpYpe7p[2pc6NdoveÕ^6JX8$%pإ;9•.]4_>_\bԕ>S!dɹTmTãѢ8{z|'}OWgX?c_m>?GqGqGqGqGqGqGqGq>|_ppW`a 4/x&xp&eQg'ByRxe;s9zPiMvaS[b'߶N{IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/java.imageset/000077500000000000000000000000001516266226600262525ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/java.imageset/Contents.json000066400000000000000000000005171516266226600307450ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "java.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "java@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/java.imageset/java.png000066400000000000000000000027261516266226600277100ustar00rootroot00000000000000PNG  IHDRnnIPLTE""""""""""l Πv/m"[˰򢢢񷷷~iϟu.{7wѹy3o$ʹFܚny4`o%WcʯƨA|8Jŧ>q'~;NиQr*=ĥMPx1?\qhsz5ӼѺ£u-ZĦɭY嶶տbε{q(󴴴m!K/tRNS:*7(IDATx^es:in`Jfff23333333eeēvS{w-i&OgfwIZ(F1bdO"cc2ŅDH 2JNd (HR~AB8E)Nфp&S4!5kjQnK̚s:NM M9յL|XY' ǫɿZsz9s;O rL^.BUzyy@R!4d-( jVd%-)wG'#i3 IV`.-&-Y. H&"c&@k2vqFnsp>-r_VE4wDRzr+ʝ&k;CK9C/?ZR"%":f뉖s{<]߷3?W -l؀p(g5Z5BIy$CMp))`Nuȡ]ErR$Pj@g`z``aWe&ؕƆeˀ{<Ɨp٪ȨטpUMo1\a?p{Li*mxc&zu:6+'`<2~)v;?IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/java.imageset/java@2x.png000066400000000000000000000065061516266226600302620ustar00rootroot00000000000000PNG  IHDRZρ IDATxYlT<8OPKZڦm楫UժZi+QZHҴib0 -1L,f-`{_,3{g7[FNU?>l? uGoG7L{gKPկZ=+zAL3$V1尖*wU8 F#-UɽyapgWp0rqXGt~e_,7=v ™-ݙ2%43IM{sEkY) 3[LNj;L3y.}U ݓ WN;/+Tl7\ .p wl4 2V%cpP&4SClH%p;)"k6sު_Rm%C8%șFfm*u*ws.E3TQS67뿡|:WmbmhE83uÔ8-YІI@ţpf ' CZIe ܶ7҃Dd n}ۓd@eݘ ׬dp`\fɑK&_BC8}*8N8gE&? Xn#!2Қ$(3ztd7=C8GMWO3ֵ&DZ5Z>eA8.ȵUI RkEzxFˆp]f,)&ڧ~e_lpyU3uvwe^߇FlO8zsvgd#;T yFA[|К'd m~ΨSuFpIgh 4&UC8_ 'md{|Ld9UN٢L&RW9"cIӿevNQpgl+zd 90î3_! _*LtU˵m2C8L[zU%yh*p$q^7 GC8 \=g(Җ$rt$b;@8KR6-Uh;nw,2CyT|BBCjp7Mrꩫp^O`!\6buiݷ6E80ut;~AwdI8%pn˻o'yT+R߉D9N#brآ9ofLdDZ k^B~זqk'۳ݮ?+ ҥC΋tV3V].vpg \j0C8OXncV>.}{Ndj C8˅XGk?\%0Ca-;0jw &Q̑CBSyg߾^8CBZ5Tn vmi'U<=A8+D =-wpC1ߘk蒚G[! \F#p76+ވppppppp.iܛҥKŋXdZbھ}jkkSpÇLc֪^C܉blg#G ˗)QWWg/<ى@9zC8o?8QZZZԙ3gG{{jhhPVrٳgxqy|rD~qAI+WxbQ' e̕nӯ)NC8-Q,x]NիW#֭5&uVJN;v#D%ӻ|pZH)֭p&_^ڒG8y'rfv @8c@w/۱c2^WH|#UkD6tB8̾}|+\kkkND6޷lݴi.7]nܸ֬Y˖-+Zp~Vnٲ=B1֦bp>N"y~C>88:_/lc᰽Tl555v@+#WeeVE T8VJ~QIJ@.]N8a1_^KbF8ÄRUUU4 J%&op>ΔKI'Hj`A8 ': (8nԹ>A8˙G#,wi%@8^9t!k[ 4- m;J)&HɩfSR-޼yӎV;!\V$^.z#(!\T,K 3<+ܜ{zz_A8ÄN:U!܄,;gpҵJ }KE6ɝ`DC dkSG8+;L FBv*l~ #¹.b&e;!#.\ Y/a*pppp7^m۶(}uRQS*msАfJ^z-24/•p҇DdA8+Ғr"ʭ:v͒ᜑ~"3]~3Cp!;|%BSf RA|NJZJμgϞ&)ML|;;C>7-DFduuuDkg8p@i !! ! !  !   !! \ 75Y){y@yV҄QMIiӑtdg%M).\EqT`pg9#!$ғn21m2&y*oT"˘:T0?"3IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/midi.imageset/000077500000000000000000000000001516266226600262535ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/midi.imageset/Contents.json000066400000000000000000000005171516266226600307460ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "midi.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "midi@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/midi.imageset/midi.png000066400000000000000000000007701516266226600277070ustar00rootroot00000000000000PNG  IHDRnn]PLTE""""""""""tRNS:*f~6IDATx^j0FNκRnB Օ3q?WE]L ~G)zl,w6bund naD2¡ -C28 -C28 -CKR8nmlZmK"}&gVĉw*@7YNܧwcVaNo'818 '#uCޏLwN\t-5?G]W:o_qc2we-w}h|cy+P؛;qG8q':: ^e-08J)ƻIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/midi.imageset/midi@2x.png000066400000000000000000000015241516266226600302570ustar00rootroot00000000000000PNG  IHDR,?lPLTE"""""""""""""">tRNS Sh RD~IDATx^1 NX8sv @$%"*X_FɳK#Tѹ'9w<6վ<={GA (ABOIsZ8ܻ@RdK [eۧƥ aەM&a q؄8lB6!M&a q؄8lB6!M&a q؄8lB6!͈b^'l>n۷w"=nYq]? . . Ǿ `c8inE e;E>fjs8pn*9׎g+8p7TRd8pPS8[MفΗ5UkZoE[ax}y}*1U\kqns8$tRNS:*&IDATx^EsTAa.fq.?Bo5SwNT^YtUY%d+^( XF$ R^qB[|IQℶX8pBc  '4Nh,X8pB &>.\ F.X.OزN5NQ׌]ٸȥd ׊sD38u\\%"E!;;y6.ҥqT|>)|\neq]}>nH(ۉs+8q[с\e Z2q.S&$XXL5;X|hPYjHsن= 77`LT/r UR%NzNn5Μz,u6%9 $\'O%U`=Cd9[ ;(Ý\5! ٨J<+.,#W̘&i캚\-Н6}fqSE.u'.ťO-N#+~gl@F錐灅*219GN灆\a+3욞#+ (kp.5GM188?HO3:,4﫮y~qvAH)0TZ鉍:Aw|!)[ainE?E떙ch鈍'/ꍑ%-NU9@sx肇OV膊zrw*1X^pu5<~x}듗胈瀄흠脈KQ=D@GBILR땙왝흡QWDKꑕ'ꏓot뒖W]mr/6uzbg엛agZ`אָRXty𤋮6=U[hmIP^d節鈌2:y}v{銎EKjo#*rw\bퟣ߉KptRNS Sh );IDATx^1 NX8z @XhBZqQM_-p9%J4뮊K8Y2X5_v2H+ pTmHdwVe`Vg]qwQa22 |A%*_<www_tmnB=u_TO=uNM P@3>)[@ViD _RS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS68eS6M+M*em7Lw7%+iͅU'zW {uz&{~w,@tè> :Pzgk<\{(s2L "G[g8\1]<,dq<ppu;R)6NDpe7@#ĵ[ p`.5Ip* kppmIYd.%ZH n(%2L# {]$ˇQH?m="qԃq/L\~8KLnL\ba0J %Dm2=R7/7dzqP4 Å(KHDO1* mO]YwK$\ws g]'A^7[_ { Ӈj ºX!j`Xe,lJ*ú!!pkS Əΐ^tV6vc.{@1j#dE9-|,-`)ipejЕ>Ɓłt9$^kyFYPaVV̎?[c w?ҹ#Dm^5FFL̦3;hZd#p ;{Yq gG㋜/WIqs^I%ldwrHEV )0-H{&Q Gp-;+ 6%b\lS)p~hrxʶS{[t~`YErpT<98888888888888kr@)ഭ]~4f2?50 K3sp4):;)ȓ+ѫOk;a&$b$]IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/photoshop.imageset/000077500000000000000000000000001516266226600273545ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/photoshop.imageset/Contents.json000066400000000000000000000005311516266226600320430ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "photoshop.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "photoshop@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/photoshop.imageset/photoshop.png000066400000000000000000000020011516266226600320760ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""xy}z|~}x{eXytRNS:*a!ZIDATx^U@nOv3ff.3333/u4HH,ى,(I$iP8֭+ ׶OvtB*CmAh&p5555555555555K>]u#ף:99p ve򥤵}™sC +Vgt~OjÛp 0<7s37Xlq̼Źvp|i! e3I,<gͺ+2|{uXmJnF)==$rXYS//Gco^q؛\mr=p6l -~ތ=KuU3_Hr1-EeZ&,Ho564V\U-2 'p 'p…TН+_U"hiI$I g \0IDATx^1 NX8v C1(iHL~y:wlx'!'7`ͭ zٳs# oDlY58l:em|8l>6͇ap|8l>6͇ap|8l>6͇ap|8lFwcvts͉؜mU jܬW+\p\p\p}^/7vݶa6GI r=ajT]29?+Zi~ogbe$8aΠT̓ԈqPꜺ.7T#0p4qƁCd\gucGcD C (2yr'?cW#-5'n Z±Z•kP2seZCa㧣ӵup%huB^F` Zњ`zQD7x >n.Fw~1:R!U7%#'p: )C [uk %(z`۝o`L%/@6 ¸(PXFSa\/M PƕAaaq!(R- v(p((*A4Z8\UEMI( {_1 0 f>%TB  }lQ mt9AXXBh I1xRV(^N\k=x8&@dԖ +GnɘBIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/playlist.imageset/000077500000000000000000000000001516266226600271725ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/playlist.imageset/Contents.json000066400000000000000000000005271516266226600316660ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "playlist.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "playlist@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/playlist.imageset/playlist.png000066400000000000000000000011541516266226600315420ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""x|ִ肾yՋ}֏ז~נ{֚y/tRNS:*(VIDATx^j@LDr-ɬKz=`1s%Bb} `5#J)nviKz%ַj%Uڞu{-/]rCY9ځs8hq8hq8hF⠱>4 ׶56ĉ'Nr ,}GdZ=|맧T.sՐ7*k""ٸ蜰wBqW~}sw3ʨ()kʽrө┯JD!̇[b}0+Y@nm8Lɜʼn'N8qRkH,6ԓ4DRJ4HrVIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/playlist.imageset/playlist@2x.png000066400000000000000000000021531516266226600321140ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""۲yޕܚx׶ݟك{֩䁾بޤz~טyՓ-9<tRNS Sh Ρ3IDATx^1 NX8z 0X&N~ 4_y UtلI%( ln/8eqB(z_j=DŽo"ԉ_Pw#$N=8lR2Vq~a+͇ap|8l>6͇ap|8l>6͇ap|8l>6w=t6ٶw9qˉwE28͊;p . . . . . .; >HCݒc9E5Uj{o3ݟ+&xr\X­v c,Nu?pKWbb8_JUAYMvKo~)ngùJ=RC1bJ\v:{7B{ s2Ng89J\q /I ᬸ+Gq3cUq78Bh@CX%P{t8teoX尷p6!CkmWc~5"zTNf8#gćSBip#8)Xp $UBRnG55οڱ A 2׿[-Nj.1?50 K3sp4)FO e+qO^;aX0P-+IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/podcast.imageset/000077500000000000000000000000001516266226600267665ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/podcast.imageset/Contents.json000066400000000000000000000005251516266226600314600ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "podcast.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "podcast@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/podcast.imageset/podcast.png000066400000000000000000000035561516266226600311420ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ʺԹOIR׾ǶͿֱݰƵ¯챚ij;্zw}۾䪐뤉YS\QKTñ巢ʻԟojr˽̼ܿ弧|Ĉjelrmt鎋pkrVQYe`hڡPJS롄㿽_Ya[U]snua]eRLUTNW~ymhoxsz{x~}pls_Zbidk`ZbŝǸxltRNS:*>fIDATx^UV@i;v]ffff 333333LRFǖ*y={[^,w23333GWn訾 UzWTo>w+D]қv8U#8U3S5#8U3S5#8U3S5#8U3S5#8U3S5C8U3S5cf7PpqW%mkhyے$p¿<]Ą j-8jD\zssahUӥsƍw`f_Pw^lVI.H7\m^*9(vV`M$xXیᚂ2Dccff74}iJ|Fpo!k-XF1>kw%&Εƥ̱ۧrEd\v?L Bnee-H̅11c}ҤI:b+^ښg\F"pjX)n;>.qNX>E,R9O4ܧp}=WZҝ&V;U>Enq?bӭg#.-5nm)NLL䢕~H?:Jߨ螀ԿqkqIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/podcast.imageset/podcast@2x.png000066400000000000000000000110121516266226600314760ustar00rootroot00000000000000PNG  IHDRZρIDATxݱjSa:S.ܒ7m( qN"ZA;HPDbCpZ%y,mr I~$÷@@p 8@p 8@p 8 8 8kN[YlT:EZer^Y]?lvI oMp'kShg*"G*Y6 낛 l. {0UL&?>\ nVVBFaAfk]l~F¬ nV6 n Npb3̓LpfK$6\"Hl&Db3% .LpfK$6\"Hl&Db3% .LpfK$6\"Hl&Db3% .SS?mKl&:b35lzSl n~>[ naa&fKRK_ۯqnܹ*?++)-)-8nTe9?i0^<|=+!EVfveԑh{_7;(;}I*v|e&er|$qRN16Ib3`0`ЅȊdIܒ$е+!i%$}_\ѷ{zA8WTٙ{5ogpA19[rSvUJ965P \v}&a=Z,pFrRuVvl<"8ImH8 ݋e}cIbZjJ{E=3 #%32%327;۰-+9&l]ݖvJGP\:= SY%b;I-Ԏ ncFVzh}`}s Zb[ȯޯڃ2r}BR!5M8 Å+gSy,v-\9ZBjFvY%{7WxykjAMW?-p; */'nw ܐj.c$~RW)۰-I6jӁV ΁1sWKpmo>Aӄf .} ˁ,Z#}ѡ W%fjiC[OO,.hzI9`A1Clc3ʿj)S,;6?/;,JR%ԉn[,p<oQyt[dGjtr0㎒ɥ/,pa3U~Q'7Ň3BwB߶R@zG.&27?a[j&Є6nդr誻 su,Yc$-t]ޚ|# nͷN1%Jn|<@fJ?$qkT-hT'WxhFLD9u\zEeܒ !{@ͪ4*+rs C6fsK;F< #Y1/huM2墔)?[ȖS-M Y,p+ZLRQҹ2NLdT\8QYlBAMY/Z<-p+䌰H/ۼ_EšW\fP43c@-ZѢ._g[8i_23Yy"]:MlH&'']?  ]g[98FgTJO*KdzjV%D{j~\%/QW}Ԏ hvWBF ܪaJ*'ǧ8zh9;ljڔLƱ}QhvW8 ܪAwUn{ºĚ>*˜qPl=Z]>TWtn]XCef"+H~uW-weC#Z]>W0pC`3SUݽ Ag=ѩoY cs)fDkt-V,@K7Wp܊ܖ%@d\:ܽcOB͋1g+rt뜎tAR' -hrŋ<,p"bIx(Rպu<|o4ţJ{|H&lprP;|gK n1+ ~)55-@Y҅-M=p==cݹE/fͬ<1Y ıT= [W 3_8 ٻ{֦83tPiZA N:(IV2"Hg;uJC*6'¹ƞ)%8翯K+-hxk`pe \pelW- p\ݧwᮥ8J nsw:zhppO,\>o9oxY@7@r '(wWM'0-sqo/CټDqlcll4g/17jp}#q1K=D { ^^?>r|Č38?gqgO<ã\.Uj_(scYeY_7!ճFIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/powerpoint.imageset/powerpoint@2x.png000066400000000000000000000024071516266226600330300ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""pCq:s~w#3Dv!x&x%8Y=ȦEz'̭Nϱe{T{)?lhu BU`QǤԹ׾ry&ğaWqH|ҷ5LkίuOw$m_jּ6p@׿>ɧgrΰ|,s?x_tRNS Sh v/ IDATx^1 NX8v0 C1סN{Q _: /qU |t3tn ;7w`[g'9!VvVM_ԀLyAzaԸt8l q؄8lB6!M&a q؄8lB6!M&a q؄8lB6!M&aql>͇;cmcW['zo+_ . . .o׼aYƽa>1Ȱ0, s,ҤiiFgHO_[{G N,{p8(Ns\ƙgI`Iq&Tƙfd!6 Z֡O{rjV k?B _#Fo '1]KP8 L<&ʧh*UΠ~FY#<*EùOYp*/T^•WTBE]2zn\q;b̎&N?%sãcS9w߉ gtN`qMwy%렠nnE@qwĥ|1D\XK(kTY"8#8#8#8#8ۘ8#8#8#8#8#8#8#acs6LJljtN+sP <<8@b<)z(&;pL0-VZ6{:IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/premiere.imageset/000077500000000000000000000000001516266226600271415ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/premiere.imageset/Contents.json000066400000000000000000000005271516266226600316350ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "premiere.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "premiere@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/premiere.imageset/premiere.png000066400000000000000000000013511516266226600314570ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ͧӱҰЬԳүյͨѮնΪҲٽΩұػ׹ͩӳԴЭϫ ttRNS:* 1oIDATx^n@NB; 3CQItcsJߝ-Kwq-L 7gY80p-I7 #mE"8i4G# HCp!8 i4G"*q\HD% 79;=+\l\*jVS3eGeRPN:*j|wڣqP٫pÚ㹏9{nc8cc=::-$˗YYV]hb<I5Ohe5w}ӻ μtro/B)%{[¸nz %l~};Ss1,c8c9cy@E!fz3Lk8+IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/premiere.imageset/premiere@2x.png000066400000000000000000000024501516266226600320320ustar00rootroot00000000000000PNG  IHDR,?ePLTE""""""""""""""ΩϫЭͧ׹ΪͨټյپӳշҰѮԵѰЬնѯ׺ַػͩغ׻ΫۿҲָ̧ھٽ tRNS Sh h)YIDATx^1 NX8sv @$%Ǣ5a<`Cnxs{ 7w[ |ٳw¾1>W N-nec!Ni-H i18lmW*RX%m\|8l>6͇ap|8l>6͇ap|8l>6͇ap|8lFateʼnw[iT뤸5b쭸+.$.qK\%.qKz}No'ia[!Umǘ,dD-U'm}/)|81{iaɩTRBwj+$kiW3Nط0ϑ0_S=8u ƩK0N%$y8U99men135M?[#z1lOpkIZe5# G VSp&ϖ(\gW$\ɸ9if2d9וɼ.M Y= Muz꼞 :ḑN8&.\}p^=pqg9t[w\ˮ^Jۮ ~03vN^g(krq+}qI*A$ I(G$WQ_Ȭ-¯KDp88888opppppppp1.:?gGc\lnt`a /x&xp&ŨIYvRxe;s9PKvaS[b'{V.*<IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/raw.imageset/000077500000000000000000000000001516266226600261225ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/raw.imageset/Contents.json000066400000000000000000000005151516266226600306130ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "raw.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "raw@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/raw.imageset/raw.png000066400000000000000000000020771516266226600274270ustar00rootroot00000000000000PNG  IHDRnn#PLTE""""""""""⤤㙙򿿿鹹Ǧ︸ɺߞ婩ܣݶ޳ϱŘB!tRNS:*.NIDATx^gJᐓ>$vzァ~QƊbkX)!Y<2 ?Nƚ2.4F+5 7ȘMa8?(0kOcMǚ 5k*8TpXScM ǚ5%kjIp3Y(䦌9aXӜg\9z s%~xoJY6V}6kY! ni266|kXk;wKY%Y~šySoV]k7 kW ^E pAqcnrm ku\x发K$EF;/ܤ(x epbm1/e9loӭ%¹ѢYKl5D8o;0QQx^z[\սƪosnnk΁O>w(9=G_DY p\.ї`(ډ?_-Ҫ$-vs[*ox*!v ?@+4;Pvn4h+}RP =XP*Rtc4͍g5 ϡp5_6͇ap|8l>6͇ap|8l>6͇fĽpwƦ] '.K8qsVۤ'r+. ׸5qk\cܞҺ0Nf:KpDpD%jPR1MSa ʜ>7>fudg7]`O\;r?șrxJALr境aNB^eP P!eګi/( s<(0|xQ$T~*ks.<$h4 ^9)8.(< r&8^RAl5zh49G tF,) j`I3$˹M<Ѭ>gƱ X.VIcٓ+vD)(߁aDց95 PԄrwKG<(7C1|8?]&kfJ'd4 ~aP6_ c| ]z=4S˅xANIp: er9oG Oҕ$9 J b1D Lc0O, U"SKQ ,|X#7z [F)-r A!8b?O])#W>ծ?ܪJ TbܒQF a1rv=O y1r*GM~#(H_X\9/`9֮X<хo3BN Q \~B"*k]EDP\ uCcr[P.ҳrj:tW L#Ppf)}VɽX7:C#H ;MF!?uk< &.A_8&@.5ruorO#:sS5kc_xO]P'mmL@VD\1+hM\4qӄhU%U,WD{HHH...~~~COBtѝ???wc˰冹ןxAj{{{#}pw[ЊJ2偁 ri0,o)h@?~xbԨ 㸔tRNS:*8IDATx^>e=)!333Ø?h9'/:4GO*bX,VO*nq>!\RuԞpHt'CZ@C i48FC i48FCiT8QF -. q\0s49.81rz]/3{\ K\DeSkqsݖR(w|eaŸ)NZqɖ mQ N$ɺ]X=h&3l4iĜy {ܖFfre=Zv1s-_(zU7LEZi=0Ųc g&"z1DqԶAF ΕځEo @-iL>lkA \Bk֮x^C.ryf6.rF9Cv,3$rfVKvr& r6"4C;b/ha!BNõ JH8 t}ɵv*rd'B ;-GwFΞ#E\*tYky*p&yx nR%- s pq[o)Dޱb!枠o-~yy&PT%iO3#Xx!CF('v%xT>Iw6/_:60x4S&F FKFFGŕhqEp4jܿYO*81qjwCay󾊟JaX,' + tIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/real_audio.imageset/real_audio@2x.png000066400000000000000000000070371516266226600326300ustar00rootroot00000000000000PNG  IHDRZρ IDATxitU#jM*"mŽkBiˏV[lZh]j'@+8OL6D-j1P0! A1$B& !>vϾ%}w}߷$ܜ/s-'Ō43.3r3`j2ftb&hу PCKa&h矉fLĕH ually8XTX8ï655P(njN@TLkK¬N0jkkc*'??fvvСiLadC6@8@6dC6@8@6@8ddC8C6@8ddC6@8@6@8dC6@8@6@8ddC6@8dC8dC6@8@6@8dC6l] oe¹#elY ;TsGF3D@8K#iD{j3¹+Y)pppppppppppppppppppr㐔%[˔ =)@Wʗ&dK{%csc<1G*:#On,Kd?KiH%քm K#[k|Sya]b׮yw.(# w*3,C1Y[u!vh =uCF$_|hi̿!ۇpgb:!2jfYGE[4Eby ⲣp *E1|=_,,o9?gU}xAso?"&')=~CN|$&G=Vd~A+3&%ʨ ɦxi}Tmߵ+vS>=&u莮.>ƏɹQvy hlD~r5[p'h|Lq.y1&a2i[S^p,nbhƄSnU-vlJGK7TIKM,{=nݺ9Fu=zT222dĈү_?ӧ5zm}NeQ 2ѳ .Ʉ7;qN6`m>luuubKcΜ9||CCL4ɒϛ:uj_˶9[.I-E`w聸W+MΝ-)<۷o5|zsɭs7Χ=}GiK^ʦ?]i $PHyKHeѣy)^hy39՛$n6w47WQܮ8cǎCF,Z>|xLSۖ4KspO(=1&{.FvڵmbN:o]8-9gwW-9D&/6z6ܱD8 BM9ƃ3fi2qDΖR)**ӧː!C.f~*s7U/FhÆ nIvE <[aW]uU]|ŒLVzz*Wli}xɁLߥ~mu3PPQc\&mG:  ~DkMjF gw$^tt#==]J3!+:K5YϹɒA8 8mڡ:LsaX]]X8}'ΠN&͙1/fZYY)3glw}_V֫8n!S%cM=sҲ¾'jyK0)Y[-g[;6)ϯ)UזNACxՔ{>^%pcoդ{a(vIz%pS+ˢ`ڦWjDݎm[}^p &YwSr+&>8[x %p+}4t:4'SZy}.ehW흷l[|.ASj!y1.MY$=on1mɥ쟌`YLߗVoX8E+gp/h7s^3--~7u]sE;Nseܵ׸Vſ$XP!f6ޕHK"Z.nx,nGMڮjkh\jDK/"mE7Ͱ΃}A ׼̜_Wk+)Zˑf>_hpI&\KV}h>&BtӍh[F5"\kFru2kQS?F7-e0Ձj5Wvdhm;~n. ߮s2 N۳[fAjpnm6J,ۋn"!EDdY]~@J]=9Wzt2C&iWMdjpQe &GќW7ɹVmS[2swgm9K1濣OĨpN-!y$;Q FLTC8-pAd\ͅ F5C8WN>D5C8fJ.Q  \pz ǖ5qBTC8%D5C8/xfv!ԛy޷pZpU')@TC8sg@K24 '!¹Ls#!hi;!Gh!  !   !! ! 1,\, 1 t :gˇVQ^~+A$\ 'Fu,\jp8R1c:G99 ".QʖI#]ꙇ׋I`:3WRl0c7OIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/sourcecode.imageset/000077500000000000000000000000001516266226600274645ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/sourcecode.imageset/Contents.json000066400000000000000000000005331516266226600321550ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "sourcecode.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "sourcecode@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/sourcecode.imageset/sourcecode.png000066400000000000000000000011751516266226600323310ustar00rootroot00000000000000PNG  IHDRnn{PLTE""""""""""䛛ȥۙ tRNS:*>^IDATx^n0Ѻi fO \a9Wdsc[~y$IQ^VVblŻ:lfvK⠽}A0FQ8hFQ8hq8h$nm"WG&ϜKs{.{\^Ǯw./-vAӽgˍ5xs<\Z"j*&*Np;p5I%oZ=h9h$\aװ{q{:dЩ9׏%Qyva`H[=5Aكd%!9|G"_ N:yXqO+.JV܊s/@g8p;En\ L^;` _[*> _N8spcnw~wu%:#6;8ڨ#6,h8ؠ3:`6ꈃ :8ڨ#6Lh8ؠ3:`6 .tF|O6ܸOuʯFu_h $L5u6k8mp۬˷YQo.f G]|9u6øԆzrnC{Jج;i‘{_B˿UefEW$o.wl?óUO + z-F]S.W=OqY-G'";ΝƵ}2ڢq! E4.7qS89 c=g}8?V]*3@dr>.tmh K6-SV 5٨=JmL6`5phЄ]43 jS~/:2D8N8$(p 'p 'p 'p 'p_Ǝm} m6?A 0 h_LMʢN2v:rӚ1 NX8SSDIJNIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/spreadsheet.imageset/000077500000000000000000000000001516266226600276405ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/spreadsheet.imageset/Contents.json000066400000000000000000000005351516266226600323330ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "spreadsheet.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "spreadsheet@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/spreadsheet.imageset/spreadsheet.png000066400000000000000000000005471516266226600326630ustar00rootroot00000000000000PNG  IHDRnnBPLTE""""""""""RYtRNS:ד*WPIDATx^9A_ u_3%p2Rj),_G}ݢL)nG[Ycl]5SD ':8iN98iN98iNfy4>n-N'Q^vꈃW88888Ec^Ƙ@DT CDDC,yGIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/spreadsheet.imageset/spreadsheet@2x.png000066400000000000000000000012201516266226600332220ustar00rootroot00000000000000PNG  IHDR,?TPLTE"""""""""""""""R\)%tRNS  ShΏiiIDATxݱn1JvKJq.J͂&3ć̬1vDwiaGnL?&NbfUgottf@O2; pm?hLiNi';ڀWl^5ƂU Q6*Fۨ`lmT Q6*Fۨ`lmT Q6*Fۨ`lm{͝[4\+8N${5K\pĉ'Klmçܜ8qĉ'N8qĉ'N8qĭ8 /rsrw>ȩٸ}Kqĉ'N8qĉ'N8qĉͪ'nj@ĉ'N8qĉ'N8qĉ'N7nN8qĉ'N8ݥm,n]Ɲ{q,Uqtuµ =GQ;Kp$N8qĉ'N8qĉ'N8qbt~ɬIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/swf.imageset/000077500000000000000000000000001516266226600261305ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/swf.imageset/Contents.json000066400000000000000000000005151516266226600306210ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "swf.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "swf@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/swf.imageset/swf.png000066400000000000000000000014261516266226600274400ustar00rootroot00000000000000PNG  IHDRnn)PLTE""""""""""3.∅okjg:550咐TPYUvs;6uqJF?:50lhC>SO<7顟Zc_IEQL^ZჀꩦA=찮XTieeb_[ᄑ饣LGyu~{|癗EA颠944/61FB`\⅂韂/tRNS:*%tIDATx^N,Aa3*PfI7SQiZwGXxuB@zL8!dGNDq8@h N4' ĉDq8@h0.*KpḘI3)W`͍["+7- 1K2+|Xʹmni3ci|s,M;aiKۄD[ eVD[+!p[j)\c1ۇr CcAo)Kg.s.ZXJTen-Y]6HτE$Wk$+ %p?|A9SN9S5Nޢ1ܫgii\DIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/swf.imageset/swf@2x.png000066400000000000000000000026651516266226600300200ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""]YB>蟝[W72斔A[1ّIIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/text.imageset/text@2x.png000066400000000000000000000020231516266226600303560ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""𞞞ͩ뭭˚纺ޫȶſִꝝদ۵tRNS Sh {IDATx^1 NX8v0 C1)Nc{Q _: /qe |t3t9 ;7`[g8 D1D|g&#UJ J.֑%;p tэa}}p|8l>6͇ap|8l>6͇ap|8l>6͇ap، 6~¦pm+'s9qHq:Zq}V\\p\p\pzc'= Q5$&lC yyŦ r͗ݳ9R-{4/_Rl,+Yʮ|q+06ŽJk]Zv2-lXntnٺnlMNEYNڿBpNI#8i'4FpNIC8i ps[]ڬ$gV8ےܡ㸧M+{|\3s^vCUg7 {kKTr%\r=p?:Z 7ieYeokDyIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/torrent.imageset/torrent@2x.png000066400000000000000000000017351516266226600316110ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""_ޯl߰|kX˳ғӔY}hOtRNS Sh Ύ'YvGIDATx^1 NX8z @XhBZ_jo/*Ь*:w± f ʜ{R`g80D٥%PBWs\`ɒ]22q * 8lKmS>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#M;pGl:#W9qwSm. ·:[qW+.ʇf܌|;w@ DQ i , IB-^a`Ϻ=..^qcĸ7=^spSzbgܻ478 <׭-NC)SmqJbPJ2}qJI-NC)ɴi(58 Ni(u/NCi(5>v(5>v(5.8 e{#?bP6PPPP)8 e"7C4I(ɼ.JMqT CYp8pc8P6EWX\>rmgU%֣Xl˯$L0 ܬh"܅J<1q :ryJ#x/ ^O͖18p8pprld[a­A 0 h_LMʢN2v:rӚ1 NX8@`x[IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vcard.imageset/000077500000000000000000000000001516266226600264305ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vcard.imageset/Contents.json000066400000000000000000000005211516266226600311160ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "vcard.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "vcard@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vcard.imageset/vcard.png000066400000000000000000000013261516266226600302370ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""ⳳ읝ęҘ͟MtRNS:*ySmIDATx^N0a|(.c;l7H893/]ز8D|ӊ=pX7+&h{uhv)8(8(8(8(8(8(8(8(8H8H8H8hCq~#q 3qL d\z5Sm~RpT 4@U)RYdʍ;*3*73Yk-0X5D:#rwvw=WE'&r㨪Ec)k7й2w-+Vˁ pt.} _SdP \.SF"=\CőQks_}[g舻RV=ݻ^6ʲ͋n8 {p\耓tSrg7L c9|ѭxpgu<88$EIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vcard.imageset/vcard@2x.png000066400000000000000000000025601516266226600306120ustar00rootroot00000000000000PNG  IHDR,?,PLTE""""""""""""""՗׼󚚚ź任ꦦ͘޾ܱƨ²utRNS Sh ΏY DIDATx^1 NX8sv @$%5aܫ`>B;xs[ ʃ;[`[ ~ٳwyc~: 4.D-H@s۩Bra{Gd2m>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#Mpl:-mp} 'nj)|\'+.ӊb!͸׶Jwajiسݴ0 ep !qDٜuiK{襁I=w,w}4W0QBlӀgHȴq&3qRqoHAjf1NjPV=B#-n ʶhq+([J\+q%ĕ:Yqu֤2"ŵ)&U )n HqMdIsB( iʆq hq EËZFVHl>=Cb/N'8z E6=9@B? t;AL;9G)n8-Y1 s?(.=|/ysHmN@j8;BjMKZ\BQXR⮆2d p.2{֕*Ipw]|-R3Z2㌧UT3ȡ!{KKOn6[t& 7"׺1Oަ*62pUhi!-=CK"p h!6"p=t/w -ER$MIВ/7"pn"p;sTk}_̅1"\3[>(zۅ6 ,,Ф,X(#O lc.GJ< jK욕KIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vector.imageset/000077500000000000000000000000001516266226600266335ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vector.imageset/Contents.json000066400000000000000000000005231516266226600313230ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "vector.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "vector@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vector.imageset/vector.png000066400000000000000000000027501516266226600306470ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""𺼾GtRNS:*rlpIDATx^oXh{af033CyOSz'gMޓ"|ӹ:e_(QDe)ZN9eq %o9ܪBqZ9*'or"9q} \8ԸpqP¡ƅC 5.j\8ԸpPáƉ+DW$0)U%Xf:awwV-EN`k=Nz88q$1tGSc#[bzK]GrmUK|7v6vgoZˆQG4&Wu#Zڜ7TCM9t9\r_Et.It~V2OGqz)vwttlJ>Bn$OHq*'`C80fF4c8ɫ{^FڱEKCOl)V]='?dZEqxĿ=-+У/{5%ԹG48/R3 m>cFvqȔiohp`Kk߂ l V#Z8|УA@PHVQ [-5]Ixq˂ L$zq#s> I|RlF]n&6BĚ#[nЛƔpU8khsFfE {+:p#D1:zXhVk$7Y iǿo’ZJ{lSjqΈC/Uds#$Z[:s4Xy̐Chױ7d*p}7 \8]u8q88{#p0+S8S8P ё'<7%J(QAe)GIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/vector.imageset/vector@2x.png000066400000000000000000000053671516266226600312300ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""ʺһJyztRNS Sh WIDATx^1 NX8v C1(m!|,`~[ͣs%rNssG ھ<;{M ( 1RxrV@M~ZɭN?9Jş*[j7R*p>S{aO>6͇ap|8l>6͇ap|8l>6͇ap|8l>6#Ħ- mCø}ӡY\}OV-74[q97sJ}>m(-p1 @ΈCgġ3q8tF:#ΈCgġ}܅:i 8~5H lc4lE Ĉn )$ D)iKD#R%F}WUJUoZU]ߣtS#~z^.gt6[ :8pѫL*291n3?疦B!Ŀ7QGW ?=1aF򆃎6ξYKvcY'vU!fu:h?O4*fNG{_%}\yvM5Xo lneLŒ-%tq8W, 4$e/E+NsS/ I9 6t{BY̆:rs𥱈B֫6Xf\H.+q(c3Wlas3[SBg`L[m՟lv`oo}7<6Ggbl+O'@۷o0_5kQ}cyyIdڷdqiVGw2 `+g%4n8t&I:wiNחDpR؛?!K"lP맹)pzI$[>5Sl5[kkDnfD!CvIz T]:t1[KKgpbE'<[ggTͅwK"fCv:r8$¶i)AG ^g' cl :r8`5h' :z8v :8Olmjn-z;Iy6^G( A%;O&TKl[ϣБAs-IvJ:z8l'БáI67:w'Y?[_v$TIG D̶[28Tʖ),̌^zՕ 'u[MUw.P|W+ xl?jCs)Z?c#pf7[޵]뎖Rno8[};ep,/SۘYR NNNNNNNnslC .69AXXBh I1xRV(^N\k=x8&@dԖ +z3G IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/video.imageset/000077500000000000000000000000001516266226600264375ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/video.imageset/Contents.json000066400000000000000000000005211516266226600311250ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "video.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "video@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/video.imageset/video.png000066400000000000000000000007651516266226600302630ustar00rootroot00000000000000PNG  IHDRnnrPLTE""""""""""xۃ~z՟ZۚtRNS:* wQIDATx^j0DI]K"rb*.Dzcr9vݲ[[B7xsg ʃZ`[ yٳ[m5Hkpyo @2HX6έS*p؞_qض͇ap|8l>6͇ap|8l>6͇ap|8l>6͇fݰp wjN\,ŝWbxXqU@p\p\p#}ٷ ٰ "(} ߹rA*u/!t0ې ې ې ې ې ې ې ې ې ې ې ېmf W\f,q xWb+Wp\p\p\p[p\p4u7oSΎJA/ն{y}߶>-=~aEBWl᛻o"㡛ft)l᜞k㤭قv뾁=q%Sq&X\賽ÅC뼩zkjoz2뽚d}8Us'Hx0|6Jqv,᝵܍A^·Gn nߔʞtRNS:*[M/IDATx^e;6YCaZff-33333ӽhC,u=Dx핲*''(++&%P WT҂SJr6lńB9h************2"n*RCrR%G]s49͝<59+F~|ǥXH^,kZQZvωb͔g}e\OWҤ -)=WN7z7.6ٰozJךvBT{yq˝19\, `Dt9-1/:&K٢&2B9 EUhڀI]`ոXlf3oOƙwVT.JtÛ}psI+yX% pivndj܂.r˻{-Q> 5(!F02fĖz2h,ell7=ԥ9qh),'E^eDhvSƉ+q!"yGxA,5=_[%hј;x)mpK-*jԀܳOWs71܉9<(ZǮ~~rg]cK_lcv':_iNsӜ49Ii_t+ WRM 4|IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/web_data.imageset/web_data@2x.png000066400000000000000000000045101516266226600317250ustar00rootroot00000000000000PNG  IHDR,?PLTE""""""""""""""r&Fgfv-g鷧xy1q{4jމIڄkw/|z2G櫾ᛌN{Ƙ`llgۈp$q%s'd}8~9ÔZߔf뾄BhXҭفRo"x0ݏ쾕]㤠m?=W娍Pcn!miHz3Ѐ=t贂>kᝃ@܌u+ͭق|5ۆE뽎Qs(}bh`:⟔[Y;܊ސq&Sv,殺ߕmJ깦v<꺬ـjTn w.ՅC^t*}7OUۉ|6㣼k܋ޓsıۇph䥝@eatRNS Sh Ρh*iPIDATx^1 NX8z 0Xj@S%.PEC8ւV^((40 Tl=Y.=8lR2Vq~a͇ۗap|8l>6͇ap|8l>6͇ap|8l>6͈{`'l:wkۧw"ŽmxZq] . . . . .Y℁jw vhn-"\{wNHssnȧ}FzOΎ1ԞVR˃Wq BPΛ<[/ƒ:44[LbPλD]1&O2i!ʅE&u̕JtQoYa1ROz&QVRlkd G ʩž1(3, ԫTSQɬO- - ?NAksൟ98jFQ9-g9"p\m=mNN9ઈ BUkх ,,Ф,X(#O lc.GJ< jK\WIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/web_lang.imageset/000077500000000000000000000000001516266226600271075ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/web_lang.imageset/Contents.json000066400000000000000000000005271516266226600316030ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "web_lang.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "web_lang@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/web_lang.imageset/web_lang.png000066400000000000000000000012711516266226600313740ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""kl˜ʥ^vPzw'lm 5sCҳsǠytcWxʦ׼D4}wjo qu+DtRNS:*qCIDATx^j@ (ZoObK(,4a0?ʲ,˚mQᒴZԩ\i@fFI+Qڒip48h4 MAࠩpT8h*4MkPgq]j:EEmM&gqa}cxr:C翻 ϴ Ow sY=GdqgqPDot${_& {Dz,˲&`:M`!$UVa߁Lup0ÏbZLjt=p؆yIm8lϭ_qخ͇ap|8l>6͇ap|8l>6͇ap|8l>6͈[b3l:-6lO͉МiUKq ·u^p/Ua\ Ȭ?hʍ& %6,`wt\dG ;r{g1Z'&6hMMDfvl̠ ͎AGYS]me5bh3bhwtG۝&n 㳅pMh>>ч xU|\mpN7MuGݢls CtupN78NЉ6`G`:nnpN=፣` GnuhnGGj[ݷ{PuGp08ꚢNmO`G2.nvtp=+s؂ਛ^B{ [0\жi-lQpݻlҟҧ3_~'4_%.qK\%.qK\%.qK\%.qK\766W`~jaPghRuvR,'W1W%o ?%v >*v~IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/word.imageset/000077500000000000000000000000001516266226600263045ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/word.imageset/Contents.json000066400000000000000000000005171516266226600307770ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "scale" : "1x", "filename" : "word.png" }, { "idiom" : "universal", "scale" : "2x", "filename" : "word@2x.png" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/word.imageset/word.png000066400000000000000000000020651516266226600277700ustar00rootroot00000000000000PNG  IHDRnnPLTE""""""""""Sh/I,F3Mˀ-Gbu.G?WmcvBYͦv8QVkꃒWlh{`t3Lj|ާZnJ`Nd9RሗoF]퐞t=UwXl:Rr.H5NF\2Ki|BZk}z«汻ۺ1JUj;S\pxǸOdYmŲO鶰tRNS:*JIDATx^EoAtdI-1̜uUD{UwK껽GrDz4d2ls;ZTK$T}K54ֶNy*Q ږPϔcm+X c-Zk!8BpX ő#-GZ0 P\jS 8;m{xgg_,qSZ+e(p 'p '\B5WH^)~#d2i {IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/Filetypes/word.imageset/word@2x.png000066400000000000000000000036771516266226600303540ustar00rootroot00000000000000PNG  IHDR,?UPLTE""""""""""""""BZ4Mm:R.Gi|Uj3Mw3L,Fq5Nꔡtsex.H`t8Pj|D[XlWl\pCZYm@WbuShv?W-G8Q2KȀNdo◤}Ä=U㎜˫;᭷Tiǽᩴh{ߜ/IVk]q9R癦0J^rF\6OzJ`k}OdꞪœat;S1JBYdwА̝u쏝[o7OfyײܱE\F]|ô܅NJɶ޼PfLbKb>VphzӓnG^ btRNS ShΡ 9=IDATx^1 NX8sv @$%5a|`C7xsg 7w[ kٳ[ L4S3 yC%(#K£!өFׁ6/)SmKk}mÇap|8l>6͇ap|8l>6͇ap|8l>6͇f]p6M{5wÉWE{28݊;po\p\p\p\p}0 3DҟH " Ԑ*-fw|a}fދ:wVSD?8 z9CAf).pkpfܨcdH"0,^$#W&'V\&λt!܄+%[qPT/` YK /U^ d-6`\92.2^\?ڔ=^#`1_7T7DxgS1(JfsVO|4 pbMXL˛L5a5=ؖ{{-q惣7*nH3մU狴OI#{tk2nN%yb)90O #+\~Fgb'.#T;O #ia0}W ^)p<Ⴝ>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf cloud drive 2014-10-22T23:06:23+01:00 2014-10-22T23:06:23+01:00 2014-10-22T23:06:23+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 188 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAvAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FX//2Q== uuid:60e83ef0-da2e-1445-931b-2f108df5179d xmp.did:b253e777-6c1c-4cb7-9214-a9440229b5e5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:d994746e-cdde-405c-8143-58767d1d9fa9 xmp.did:d994746e-cdde-405c-8143-58767d1d9fa9 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:b253e777-6c1c-4cb7-9214-a9440229b5e5 2014-10-22T23:06:18+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White CMYK PROCESS 0.000000 0.000000 0.000000 0.000000 Black CMYK PROCESS 0.000000 0.000000 0.000000 100.000000 CMYK Red CMYK PROCESS 0.000000 100.000000 100.000000 0.000000 CMYK Yellow CMYK PROCESS 0.000000 0.000000 100.000000 0.000000 CMYK Green CMYK PROCESS 100.000000 0.000000 100.000000 0.000000 CMYK Cyan CMYK PROCESS 100.000000 0.000000 0.000000 0.000000 CMYK Blue CMYK PROCESS 100.000000 100.000000 0.000000 0.000000 CMYK Magenta CMYK PROCESS 0.000000 100.000000 0.000000 0.000000 C=15 M=100 Y=90 K=10 CMYK PROCESS 15.000000 100.000000 90.000000 10.000000 C=0 M=90 Y=85 K=0 CMYK PROCESS 0.000000 90.000000 85.000000 0.000000 C=0 M=80 Y=95 K=0 CMYK PROCESS 0.000000 80.000000 95.000000 0.000000 C=0 M=50 Y=100 K=0 CMYK PROCESS 0.000000 50.000000 100.000000 0.000000 C=0 M=35 Y=85 K=0 CMYK PROCESS 0.000000 35.000000 85.000000 0.000000 C=5 M=0 Y=90 K=0 CMYK PROCESS 5.000000 0.000000 90.000000 0.000000 C=20 M=0 Y=100 K=0 CMYK PROCESS 20.000000 0.000000 100.000000 0.000000 C=50 M=0 Y=100 K=0 CMYK PROCESS 50.000000 0.000000 100.000000 0.000000 C=75 M=0 Y=100 K=0 CMYK PROCESS 75.000000 0.000000 100.000000 0.000000 C=85 M=10 Y=100 K=10 CMYK PROCESS 85.000000 10.000000 100.000000 10.000000 C=90 M=30 Y=95 K=30 CMYK PROCESS 90.000000 30.000000 95.000000 30.000000 C=75 M=0 Y=75 K=0 CMYK PROCESS 75.000000 0.000000 75.000000 0.000000 C=80 M=10 Y=45 K=0 CMYK PROCESS 80.000000 10.000000 45.000000 0.000000 C=70 M=15 Y=0 K=0 CMYK PROCESS 70.000000 15.000000 0.000000 0.000000 C=85 M=50 Y=0 K=0 CMYK PROCESS 85.000000 50.000000 0.000000 0.000000 C=100 M=95 Y=5 K=0 CMYK PROCESS 100.000000 95.000000 5.000000 0.000000 C=100 M=100 Y=25 K=25 CMYK PROCESS 100.000000 100.000000 25.000000 25.000000 C=75 M=100 Y=0 K=0 CMYK PROCESS 75.000000 100.000000 0.000000 0.000000 C=50 M=100 Y=0 K=0 CMYK PROCESS 50.000000 100.000000 0.000000 0.000000 C=35 M=100 Y=35 K=10 CMYK PROCESS 35.000000 100.000000 35.000000 10.000000 C=10 M=100 Y=50 K=0 CMYK PROCESS 10.000000 100.000000 50.000000 0.000000 C=0 M=95 Y=20 K=0 CMYK PROCESS 0.000000 95.000000 20.000000 0.000000 C=25 M=25 Y=40 K=0 CMYK PROCESS 25.000000 25.000000 40.000000 0.000000 C=40 M=45 Y=50 K=5 CMYK PROCESS 40.000000 45.000000 50.000000 5.000000 C=50 M=50 Y=60 K=25 CMYK PROCESS 50.000000 50.000000 60.000000 25.000000 C=55 M=60 Y=65 K=40 CMYK PROCESS 55.000000 60.000000 65.000000 40.000000 C=25 M=40 Y=65 K=0 CMYK PROCESS 25.000000 40.000000 65.000000 0.000000 C=30 M=50 Y=75 K=10 CMYK PROCESS 30.000000 50.000000 75.000000 10.000000 C=35 M=60 Y=80 K=25 CMYK PROCESS 35.000000 60.000000 80.000000 25.000000 C=40 M=65 Y=90 K=35 CMYK PROCESS 40.000000 65.000000 90.000000 35.000000 C=40 M=70 Y=100 K=50 CMYK PROCESS 40.000000 70.000000 100.000000 50.000000 C=50 M=70 Y=80 K=70 CMYK PROCESS 50.000000 70.000000 80.000000 70.000000 Grays 1 C=0 M=0 Y=0 K=100 CMYK PROCESS 0.000000 0.000000 0.000000 100.000000 C=0 M=0 Y=0 K=90 CMYK PROCESS 0.000000 0.000000 0.000000 89.999400 C=0 M=0 Y=0 K=80 CMYK PROCESS 0.000000 0.000000 0.000000 79.998800 C=0 M=0 Y=0 K=70 CMYK PROCESS 0.000000 0.000000 0.000000 69.999700 C=0 M=0 Y=0 K=60 CMYK PROCESS 0.000000 0.000000 0.000000 59.999100 C=0 M=0 Y=0 K=50 CMYK PROCESS 0.000000 0.000000 0.000000 50.000000 C=0 M=0 Y=0 K=40 CMYK PROCESS 0.000000 0.000000 0.000000 39.999400 C=0 M=0 Y=0 K=30 CMYK PROCESS 0.000000 0.000000 0.000000 29.998800 C=0 M=0 Y=0 K=20 CMYK PROCESS 0.000000 0.000000 0.000000 19.999700 C=0 M=0 Y=0 K=10 CMYK PROCESS 0.000000 0.000000 0.000000 9.999100 C=0 M=0 Y=0 K=5 CMYK PROCESS 0.000000 0.000000 0.000000 4.998800 Brights 1 C=0 M=100 Y=100 K=0 CMYK PROCESS 0.000000 100.000000 100.000000 0.000000 C=0 M=75 Y=100 K=0 CMYK PROCESS 0.000000 75.000000 100.000000 0.000000 C=0 M=10 Y=95 K=0 CMYK PROCESS 0.000000 10.000000 95.000000 0.000000 C=85 M=10 Y=100 K=0 CMYK PROCESS 85.000000 10.000000 100.000000 0.000000 C=100 M=90 Y=0 K=0 CMYK PROCESS 100.000000 90.000000 0.000000 0.000000 C=60 M=90 Y=0 K=0 CMYK PROCESS 60.000000 90.000000 0.003100 0.003100 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream HLSK1 @my3b5B# X4pF]vsr{1|uƦ1{aZ߯p5ǟGdpkNܷ .8J9&&#&FI,`b:#gv93բ um|^pPٗjp䲡:FRv,Dh  .N_ JkS1nϏ)uI"љ|jOG=ͻnoI2=cPf ŎHl0)*7*\c *zJYWB,Dg L#K3]b)JA+ `pw^ժ`j6|`Un_/lC;}Iwm줥\lU ^ 1񰪂E5/`|P#Oږ endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000041074 00000 n 0000000000 00000 f 0000042592 00000 n 0000042890 00000 n 0000041125 00000 n 0000041376 00000 n 0000042778 00000 n 0000041873 00000 n 0000042031 00000 n 0000042079 00000 n 0000042662 00000 n 0000042693 00000 n 0000042913 00000 n trailer <<2C2C473FEE1244418907788DF22D71A1>]>> startxref 43107 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/contactsIcon.imageset/000077500000000000000000000000001516266226600301435ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/contactsIcon.imageset/Contents.json000066400000000000000000000002351516266226600326330ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "contacts.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/contactsIcon.imageset/contacts.pdf000066400000000000000000001160471516266226600324650ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf contacts 2014-10-23T00:04:54+01:00 2014-10-23T00:04:54+01:00 2014-10-23T00:04:54+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 196 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAxAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FX//2Q== uuid:e09b4e88-b7b1-074a-83a7-ddfd9c66d44b xmp.did:adeef859-535d-48b0-b145-2b19703ae63d uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:3eb16e9b-4e89-4a68-9251-740e966b84a5 xmp.did:3eb16e9b-4e89-4a68-9251-740e966b84a5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:adeef859-535d-48b0-b145-2b19703ae63d 2014-10-23T00:04:52+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream H\Wˎ ++AI f5,d6g pT%'h˦d,o~~{:Ǒ:O~N&YU3.KOK|J&o V? 0s,Uڸk\哨@ Gһ.-X;pAq~yNg9_^`zkB& M!kk{7a#h;T/t4!C'(I"NlFxG\_za>T"\ ek02Opւ>@B8FzvVM8rxߠtImN73R&N{RYi1rh3}O-ygL`}7K|E,ٜ36n +e..(!sZfV0TFKGM-}Meٵwm,}hUƐkQ~`z2TK ,n $1MdW1WY$P!Fr0Ulh&n`VTPN #w<2޲PQCA,6EKb&=2'U!N=ϒ 5/(ߠE@ʔLgJ B,:[HBVKBqsdl]`_m_΁F0RJj/m.ЙMHJlxg'X2pz7nY2r/ӗ΀"@+!Yo9+M^R@yj[t)0#w#Uj9)$Nf! ]ޙziE3o64 f-hb5~I%7=S"u2by4_E9r @:x@"&سƙɚs=4H#'O(tJ{{d^7֌iy5߄yc.E!1&TD"4Q_k(PO439&٤V>Yܨ׵rԉ8p Hz?VZ9eHkҔV:I͓H ƍE&Fpk se%F9RP.lCf;́NYpCDt6%V4jPSeGXn#6n4)HU3&=)CFU& hC>5t.5N w qS:6 k:_g$*}Gx7 ķc}pU(qMF0R!O+Ubwh,9iqH7ep(<&X/{Pj@ "ۡ$+ӻjQĪd2Vw;VY6]MPwAeXC%.Pܷ6 Ub<ߦP,#L׆CaђR?^ .T= ‚`ȣBh*Y5 E_3HvH+ve #L1#(7"~i Zk=w, R bV'5x2:Ĥ?T%\F@/QKRƊ"[ tⱞ?xEo옡+ T"U"x7ޒrY˼e{I핗[Wx1,>r&R{}hwFmrsxcI:x endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000035957 00000 n 0000000000 00000 f 0000038994 00000 n 0000039292 00000 n 0000036008 00000 n 0000036257 00000 n 0000039180 00000 n 0000038275 00000 n 0000038433 00000 n 0000038481 00000 n 0000039064 00000 n 0000039095 00000 n 0000039315 00000 n trailer <<8848DBCC7DBA4089B6F54950BDFFC3B8>]>> startxref 39506 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/offlineIcon.imageset/000077500000000000000000000000001516266226600277475ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/offlineIcon.imageset/Contents.json000066400000000000000000000002351516266226600324370ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "transfer.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/offlineIcon.imageset/transfer.pdf000066400000000000000000001140751516266226600322760ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf transfer 2014-10-23T00:05:38+01:00 2014-10-23T00:05:38+01:00 2014-10-23T00:05:38+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== uuid:5c21857f-a95d-a14d-9ae4-b4079a183407 xmp.did:730d4081-7825-40b4-b57e-eafad033d735 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:be8ffa56-2a53-47a8-8c9a-a969c771b403 xmp.did:be8ffa56-2a53-47a8-8c9a-a969c771b403 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:730d4081-7825-40b4-b57e-eafad033d735 2014-10-23T00:05:36+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream HTSKN@ @ܱl)U* H-=$U=ގx8>=n0MXjh?) 0ƌQW0G , L)(3V\L%džj,D2;ŢY(Ѥ-c2ZcI&qn"I*δ9~" gȻr?v}a0/Jf` yymh U VuiGK:˲pv=BaxrOmwYƆLMdX;6Q칖 mJlE}8ha3řcd۶a =tV%P6r   endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000036552 00000 n 0000000000 00000 f 0000037992 00000 n 0000038290 00000 n 0000036603 00000 n 0000036850 00000 n 0000038178 00000 n 0000037273 00000 n 0000037431 00000 n 0000037479 00000 n 0000038062 00000 n 0000038093 00000 n 0000038313 00000 n trailer <<1648566EACEC4EB7B9D21804D45E5D49>]>> startxref 38504 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/settingsIcon.imageset/000077500000000000000000000000001516266226600301655ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/settingsIcon.imageset/Contents.json000066400000000000000000000002351516266226600326550ustar00rootroot00000000000000{ "images" : [ { "idiom" : "universal", "filename" : "settings.pdf" } ], "info" : { "version" : 1, "author" : "xcode" } }sdk-10.11.0/examples/iOS/Demo/Demo/Images.xcassets/TabBarIcons/settingsIcon.imageset/settings.pdf000066400000000000000000001150061516266226600325230ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf settings 2014-10-23T00:04:35+01:00 2014-10-23T00:04:35+01:00 2014-10-23T00:04:35+01:00 Adobe Illustrator CC 2014 (Macintosh) 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== uuid:2ea2799c-0d92-5243-96ec-d5766ebf3a87 xmp.did:3eb16e9b-4e89-4a68-9251-740e966b84a5 uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf xmp.iid:1b005aaf-9e27-47af-affe-ca73d93817bb xmp.did:1b005aaf-9e27-47af-affe-ca73d93817bb uuid:5D20892493BFDB11914A8590D31508C8 proof:pdf saved xmp.iid:e199a11d-9e6d-48d9-a66c-b3d0fd22d639 2014-10-22T22:33:57+01:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:3eb16e9b-4e89-4a68-9251-740e966b84a5 2014-10-23T00:04:33+01:00 Adobe Illustrator CC 2014 (Macintosh) / Print False False 1 64.000000 49.000000 Pixels Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 35 31 32 CMYK Red RGB PROCESS 236 28 36 CMYK Yellow RGB PROCESS 255 241 0 CMYK Green RGB PROCESS 0 165 81 CMYK Cyan RGB PROCESS 0 173 238 CMYK Blue RGB PROCESS 46 49 145 CMYK Magenta RGB PROCESS 235 0 139 C=15 M=100 Y=90 K=10 RGB PROCESS 190 30 45 C=0 M=90 Y=85 K=0 RGB PROCESS 238 64 54 C=0 M=80 Y=95 K=0 RGB PROCESS 240 90 40 C=0 M=50 Y=100 K=0 RGB PROCESS 246 146 30 C=0 M=35 Y=85 K=0 RGB PROCESS 250 175 64 C=5 M=0 Y=90 K=0 RGB PROCESS 249 236 49 C=20 M=0 Y=100 K=0 RGB PROCESS 214 222 35 C=50 M=0 Y=100 K=0 RGB PROCESS 139 197 63 C=75 M=0 Y=100 K=0 RGB PROCESS 55 179 74 C=85 M=10 Y=100 K=10 RGB PROCESS 0 147 69 C=90 M=30 Y=95 K=30 RGB PROCESS 0 104 56 C=75 M=0 Y=75 K=0 RGB PROCESS 41 180 115 C=80 M=10 Y=45 K=0 RGB PROCESS 0 166 156 C=70 M=15 Y=0 K=0 RGB PROCESS 38 169 224 C=85 M=50 Y=0 K=0 RGB PROCESS 27 117 187 C=100 M=95 Y=5 K=0 RGB PROCESS 43 56 143 C=100 M=100 Y=25 K=25 RGB PROCESS 38 34 97 C=75 M=100 Y=0 K=0 RGB PROCESS 101 45 144 C=50 M=100 Y=0 K=0 RGB PROCESS 144 39 142 C=35 M=100 Y=35 K=10 RGB PROCESS 158 31 99 C=10 M=100 Y=50 K=0 RGB PROCESS 217 28 92 C=0 M=95 Y=20 K=0 RGB PROCESS 236 41 123 C=25 M=25 Y=40 K=0 RGB PROCESS 193 180 154 C=40 M=45 Y=50 K=5 RGB PROCESS 154 132 121 C=50 M=50 Y=60 K=25 RGB PROCESS 113 101 88 C=55 M=60 Y=65 K=40 RGB PROCESS 90 74 66 C=25 M=40 Y=65 K=0 RGB PROCESS 195 153 107 C=30 M=50 Y=75 K=10 RGB PROCESS 168 124 79 C=35 M=60 Y=80 K=25 RGB PROCESS 138 93 59 C=40 M=65 Y=90 K=35 RGB PROCESS 117 76 40 C=40 M=70 Y=100 K=50 RGB PROCESS 96 56 19 C=50 M=70 Y=80 K=70 RGB PROCESS 59 35 20 Grays 1 C=0 M=0 Y=0 K=100 RGB PROCESS 35 31 32 C=0 M=0 Y=0 K=90 RGB PROCESS 64 64 65 C=0 M=0 Y=0 K=80 RGB PROCESS 88 89 91 C=0 M=0 Y=0 K=70 RGB PROCESS 109 110 112 C=0 M=0 Y=0 K=60 RGB PROCESS 128 129 132 C=0 M=0 Y=0 K=50 RGB PROCESS 146 148 151 C=0 M=0 Y=0 K=40 RGB PROCESS 166 168 171 C=0 M=0 Y=0 K=30 RGB PROCESS 187 189 191 C=0 M=0 Y=0 K=20 RGB PROCESS 208 210 211 C=0 M=0 Y=0 K=10 RGB PROCESS 230 231 232 C=0 M=0 Y=0 K=5 RGB PROCESS 241 241 242 Brights 1 C=0 M=100 Y=100 K=0 RGB PROCESS 236 28 36 C=0 M=75 Y=100 K=0 RGB PROCESS 241 101 34 C=0 M=10 Y=95 K=0 RGB PROCESS 255 221 21 C=85 M=10 Y=100 K=0 RGB PROCESS 0 161 75 C=100 M=90 Y=0 K=0 RGB PROCESS 34 64 153 C=60 M=90 Y=0 K=0 RGB PROCESS 127 63 151 Adobe PDF library 11.00 endstream endobj 3 0 obj <> endobj 7 0 obj <>/Properties<>>>/Thumb 10 0 R/TrimBox[0.0 0.0 64.0 49.0]/Type/Page>> endobj 8 0 obj <>stream H\Vn! mZuUQQͤRjѕ.Ǿǣ}xz GkL崵XGo5<|?FbO9},z5|j\)Tںbo'{toht:Oi^4C66Xǁ6Vq8Z/ z٧k,%ޠŮLk-=Y`鴮Тɮں9z^6ͷ֧GҌq~s㌖~,\wÉ=h r OyzGf i,r 4C%OCL&c`A'ԗB6߈.PVD|юx(vW$2(oRMPB&V@8J8$[NR12+x=HHˤ0-rc(@L* l<6|2mT˨FLa.mttu/S6;;vL6QR--t"1ػf6 m=svֽGۨ9 5yfdę}1o!3ѝu[|]4]yL5S>i&8zln;Qw-_9 p2ă~/ B"^(ʞ -:57j[!dҁT sOe[B2ѩ$hwSlwnQl84&ɶS6Ns*&zRDHٻ σ&2n%CVSxE΅??|{>l endstream endobj 10 0 obj <>stream 8;Xp,SMU endstream endobj 11 0 obj [/Indexed/DeviceRGB 255 12 0 R] endobj 12 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 5 0 obj <> endobj 13 0 obj [/View/Design] endobj 14 0 obj <>>> endobj 9 0 obj <> endobj 6 0 obj [5 0 R] endobj 15 0 obj <> endobj xref 0 16 0000000000 65535 f 0000000016 00000 n 0000000144 00000 n 0000036552 00000 n 0000000000 00000 f 0000038449 00000 n 0000038747 00000 n 0000036603 00000 n 0000036850 00000 n 0000038635 00000 n 0000037730 00000 n 0000037888 00000 n 0000037936 00000 n 0000038519 00000 n 0000038550 00000 n 0000038770 00000 n trailer <<0E258367E9554B39BDCE0E4F66292EF8>]>> startxref 38961 %%EOF sdk-10.11.0/examples/iOS/Demo/Demo/Info.plist000066400000000000000000000023051516266226600204770ustar00rootroot00000000000000 CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleLocalizations en CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight sdk-10.11.0/examples/iOS/Demo/Demo/Login/000077500000000000000000000000001516266226600175775ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Login/LoginViewController.h000066400000000000000000000014711516266226600237220ustar00rootroot00000000000000/** * @file LoginViewController.h * @brief View controller that allow login in your MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface LoginViewController : UIViewController @end sdk-10.11.0/examples/iOS/Demo/Demo/Login/LoginViewController.m000066400000000000000000000122441516266226600237270ustar00rootroot00000000000000/** * @file LoginViewController.m * @brief View controller that allow login in your MEGA account * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "LoginViewController.h" #import "CloudDriveTableViewController.h" #import "SVProgressHUD.h" #import "SSKeychain.h" @interface LoginViewController () @property (weak, nonatomic) IBOutlet UITextField *emailTextField; @property (weak, nonatomic) IBOutlet UITextField *passwordTextField; @end @implementation LoginViewController - (void)viewDidLoad { [super viewDidLoad]; [self.emailTextField becomeFirstResponder]; } #pragma mark - Private methods - (IBAction)tapLogin:(id)sender { if ([self validateForm]) { [[MEGASdkManager sharedMEGASdk] loginWithEmail:self.emailTextField.text password:self.passwordTextField.text delegate:self]; } } - (BOOL)validateForm { if (![self validateEmail:self.emailTextField.text]) { [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"emailIvalidFormat", @"Enter a valid email")]; [self.emailTextField becomeFirstResponder]; return NO; } else if (![self validatePassword:self.passwordTextField.text]) { [SVProgressHUD showErrorWithStatus:NSLocalizedString(@"passwordInvalidFormat", @"Enter a valid password")]; [self.passwordTextField becomeFirstResponder]; return NO; } return YES; } - (BOOL)validatePassword:(NSString *)password { if (password.length == 0) { return NO; } else { return YES; } } - (BOOL)validateEmail:(NSString *)email { NSString *emailRegex = @"(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}" @"~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\" @"x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-" @"z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5" @"]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" @"9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21" @"-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES[c] %@", emailRegex]; return [emailTest evaluateWithObject:email]; } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { [SVProgressHUD show]; } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { [SVProgressHUD dismiss]; switch ([error type]) { case MEGAErrorTypeApiEArgs: case MEGAErrorTypeApiENoent: { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"error", @"Error") message:NSLocalizedString(@"invalidMailOrPassword", @"Email or password invalid.") delegate:self cancelButtonTitle:NSLocalizedString(@"ok", @"OK") otherButtonTitles:nil]; [alert show]; break; } default: break; } return; } switch ([request type]) { case MEGARequestTypeLogin: { NSString *session = [[MEGASdkManager sharedMEGASdk] dumpSession]; [SSKeychain setPassword:session forService:@"MEGA" account:@"session"]; [api fetchNodesWithDelegate:self]; break; } case MEGARequestTypeFetchNodes: { [SVProgressHUD dismiss]; [self performSegueWithIdentifier:@"showCloudDrive" sender:self]; break; } default: break; } } - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request { if ([request type] == MEGARequestTypeFetchNodes){ float progress = [[request transferredBytes] floatValue] / [[request totalBytes] floatValue]; if (progress > 0 && progress <0.99) { [SVProgressHUD showProgress:progress status:NSLocalizedString(@"fetchingNodes", @"Fetching nodes")]; } else if (progress > 0.99 || progress < 0) { [SVProgressHUD showProgress:1 status:NSLocalizedString(@"preparingNodes", @"Preparing nodes")]; } } } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } #pragma mark - Dismiss keyboard - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.view endEditing:YES]; } @end sdk-10.11.0/examples/iOS/Demo/Demo/Login/MainTabBarController.h000066400000000000000000000014121516266226600237520ustar00rootroot00000000000000/** * @file MainTabBarController.h * @brief Main tab bar of the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import @interface MainTabBarController : UITabBarController @end sdk-10.11.0/examples/iOS/Demo/Demo/Login/MainTabBarController.m000066400000000000000000000027241516266226600237660ustar00rootroot00000000000000/** * @file MainTabBarController.h * @brief Main tab bar of the app * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "MainTabBarController.h" @interface MainTabBarController () @end @implementation MainTabBarController - (void)viewDidLoad { [super viewDidLoad]; NSMutableArray *viewControllerArray = [[NSMutableArray alloc] initWithCapacity:5]; [viewControllerArray addObject:[[UIStoryboard storyboardWithName:@"Cloud" bundle:nil] instantiateInitialViewController]]; [viewControllerArray addObject:[[UIStoryboard storyboardWithName:@"Offline" bundle:nil] instantiateInitialViewController]]; [viewControllerArray addObject:[[UIStoryboard storyboardWithName:@"Contacts" bundle:nil] instantiateInitialViewController]]; [viewControllerArray addObject:[[UIStoryboard storyboardWithName:@"Settings" bundle:nil] instantiateInitialViewController]]; [self setViewControllers:viewControllerArray]; } @end sdk-10.11.0/examples/iOS/Demo/Demo/Offline/000077500000000000000000000000001516266226600201115ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Offline/OfflineTableViewController.h000066400000000000000000000014761516266226600255230ustar00rootroot00000000000000/** * @file OfflineTableViewController.h * @brief View controller that show offline files * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface OfflineTableViewController : UITableViewController @end sdk-10.11.0/examples/iOS/Demo/Demo/Offline/OfflineTableViewController.m000066400000000000000000000127431516266226600255270ustar00rootroot00000000000000/** * @file OfflineTableViewController.m * @brief View controller that show offline files * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "OfflineTableViewController.h" #import "NodeTableViewCell.h" #import #import "Helper.h" @interface OfflineTableViewController () @property (nonatomic, strong) NSMutableArray *offlineDocuments; @property (nonatomic, strong) NSMutableArray *offlineImages; @end @implementation OfflineTableViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self reloadUI]; [[MEGASdkManager sharedMEGASdk] addMEGATransferDelegate:self]; [[MEGASdkManager sharedMEGASdk] retryPendingConnections]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [[MEGASdkManager sharedMEGASdk] removeMEGATransferDelegate:self]; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.offlineDocuments count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NodeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"nodeCell" forIndexPath:indexPath]; MEGANode *node = [[self.offlineDocuments objectAtIndex:indexPath.row] objectForKey:kMEGANode]; NSString *name = [node name]; NSString *thumbnailFilePath = [Helper pathForNode:node searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:thumbnailFilePath]; if (!fileExists) { [cell.thumbnailImageView setImage:[Helper imageForNode:node]]; } else { [cell.thumbnailImageView setImage:[UIImage imageWithContentsOfFile:thumbnailFilePath]]; } cell.nameLabel.text = name; struct tm *timeinfo; char buffer[80]; time_t rawtime = [[node creationTime] timeIntervalSince1970]; timeinfo = localtime(&rawtime); strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); cell.modificationLabel.text = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding]; return cell; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { MEGANode *node = [[self.offlineDocuments objectAtIndex:indexPath.row] objectForKey:kMEGANode]; NSString *name = [node name]; if (isVideo(name.lowercaseString.pathExtension)) { NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSMutableString *filePath = [NSMutableString new]; [filePath appendFormat:@"%@/%@.%@", documentDirectory, [node base64Handle], [name pathExtension]]; NSURL *fileURL = [NSURL fileURLWithPath:filePath]; MPMoviePlayerViewController *videoPlayerView = [[MPMoviePlayerViewController alloc] initWithContentURL:fileURL]; [self presentMoviePlayerViewControllerAnimated:videoPlayerView]; [videoPlayerView.moviePlayer play]; } } #pragma mark - Private methods - (void)reloadUI { self.offlineDocuments = [NSMutableArray new]; self.offlineImages = [NSMutableArray new]; int i; NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentDirectory error:NULL]; int offsetIndex = 0; for (i = 0; i < (int)[directoryContent count]; i++) { NSString *filePath = [documentDirectory stringByAppendingPathComponent:[directoryContent objectAtIndex:i]]; NSString *filename = [NSString stringWithFormat:@"%@", [directoryContent objectAtIndex:i]]; if (![filename.lowercaseString.pathExtension isEqualToString:@"mega"]) { MEGANode *node = [[MEGASdkManager sharedMEGASdk] nodeForHandle:[MEGASdk handleForBase64Handle:filename]]; if (node == nil) continue; NSMutableDictionary *tempDictionary = [NSMutableDictionary new]; [tempDictionary setValue:node forKey:kMEGANode]; [tempDictionary setValue:[NSNumber numberWithInt:offsetIndex] forKey:kIndex]; [self.offlineDocuments addObject:tempDictionary]; } } [self.tableView reloadData]; } #pragma mark - MEGATransferDelegate - (void)onTransferStart:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } - (void)onTransferUpdate:(MEGASdk *)api transfer:(MEGATransfer *)transfer { } - (void)onTransferFinish:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { [self reloadUI]; } -(void)onTransferTemporaryError:(MEGASdk *)api transfer:(MEGATransfer *)transfer error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/Settings/000077500000000000000000000000001516266226600203275ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Settings/SettingsViewController.h000066400000000000000000000014601516266226600252000ustar00rootroot00000000000000/** * @file SettingsViewController.h * @brief View controller that show your settings * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "MEGASdkManager.h" @interface SettingsViewController : UIViewController @end sdk-10.11.0/examples/iOS/Demo/Demo/Settings/SettingsViewController.m000066400000000000000000000105551516266226600252120ustar00rootroot00000000000000/** * @file SettingsViewController.m * @brief View controller that show your settings * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import "SettingsViewController.h" #import "SVProgressHUD.h" #import "LoginViewController.h" #import "Helper.h" #import "SSKeychain.h" @interface SettingsViewController () @property (weak, nonatomic) IBOutlet UILabel *emailLabel; @property (weak, nonatomic) IBOutlet UIImageView *avatarImageView; @end @implementation SettingsViewController - (void)viewDidLoad { [super viewDidLoad]; self.emailLabel.text = [[MEGASdkManager sharedMEGASdk] myEmail]; [self setUserAvatar]; } #pragma mark - Private Methods - (void)setUserAvatar { MEGAUser *user = [[MEGASdkManager sharedMEGASdk] myUser]; NSString *avatarFilePath = [Helper pathForUser:user searchPath:NSCachesDirectory directory:@"thumbs"]; BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:avatarFilePath]; if (!fileExists) { [[MEGASdkManager sharedMEGASdk] getAvatarUser:user destinationFilePath:avatarFilePath delegate:self]; } else { [self.avatarImageView setImage:[UIImage imageNamed:avatarFilePath]]; self.avatarImageView.layer.cornerRadius = self.avatarImageView.frame.size.width/2; self.avatarImageView.layer.masksToBounds = YES; } } - (IBAction)logout:(id)sender { [[MEGASdkManager sharedMEGASdk] logoutWithDelegate:self]; } #pragma mark - MEGARequestDelegate - (void)onRequestStart:(MEGASdk *)api request:(MEGARequest *)request { switch ([request type]) { case MEGARequestTypeLogout: [SVProgressHUD showWithStatus:NSLocalizedString(@"logout", @"Logout...")]; break; default: break; } } - (void)onRequestFinish:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { if ([error type]) { return; } switch ([request type]) { case MEGARequestTypeLogout: { [SSKeychain deletePasswordForService:@"MEGA" account:@"session"]; NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; NSString *cacheDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; for (NSString *file in [fm contentsOfDirectoryAtPath:cacheDirectory error:&error]) { BOOL success = [fm removeItemAtPath:[NSString stringWithFormat:@"%@/%@", cacheDirectory, file] error:&error]; if (!success || error) { NSLog(@"remove file error %@", error); } } NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; for (NSString *file in [fm contentsOfDirectoryAtPath:documentDirectory error:&error]) { BOOL success = [fm removeItemAtPath:[NSString stringWithFormat:@"%@/%@", documentDirectory, file] error:&error]; if (!success || error) { NSLog(@"remove file error %@", error); } } [SVProgressHUD dismiss]; UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; LoginViewController *loginVC = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewControllerID"]; [self presentViewController:loginVC animated:YES completion:nil]; break; } case MEGARequestTypeGetAttrUser: { [self setUserAvatar]; } default: break; } } - (void)onRequestUpdate:(MEGASdk *)api request:(MEGARequest *)request { } - (void)onRequestTemporaryError:(MEGASdk *)api request:(MEGARequest *)request error:(MEGAError *)error { } @end sdk-10.11.0/examples/iOS/Demo/Demo/Utils/000077500000000000000000000000001516266226600176275ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/Utils/Helper.h000066400000000000000000000016041516266226600212200ustar00rootroot00000000000000#import #import #import "MEGASdk.h" #define imagesSet [[NSSet alloc] initWithObjects:@"gif", @"jpg", @"tif", @"jpeg", @"bmp", @"png",@"nef", nil] #define videoSet [[NSSet alloc] initWithObjects:/*@"mkv",*/ @"avi", @"mp4", @"m4v", @"mpg", @"mpeg", @"mov", @"3gp",/*@"aaf",*/ nil] #define isImage(n) [imagesSet containsObject:n] #define isVideo(n) [videoSet containsObject:n] #define kMEGANode @"kMEGANode" #define kIndex @"kIndex" @interface Helper : NSObject + (UIImage *)imageForNode:(MEGANode *)node; + (NSString *)pathForNode:(MEGANode *)node searchPath:(NSSearchPathDirectory)path directory:(NSString *)directory; + (NSString *)pathForNode:(MEGANode *)node searchPath:(NSSearchPathDirectory)path; + (NSString *)pathForUser:(MEGAUser *)user searchPath:(NSSearchPathDirectory)path directory:(NSString *)directory; @end sdk-10.11.0/examples/iOS/Demo/Demo/Utils/Helper.m000066400000000000000000000271671516266226600212410ustar00rootroot00000000000000#import "Helper.h" #import "MEGASdkManager.h" @implementation Helper + (UIImage *)imageForNode:(MEGANode *)node { NSDictionary *dictionary = @{@"3ds":@"3D.png", @"3dm":@"3D.png", @"3fr":@"raw.png", @"3g2":@"video.png", @"3gp":@"video.png", @"7z":@"compressed.png", @"aac":@"audio.png", @"ac3":@"audio.png", @"accdb":@"database.png", @"aep":@"aftereffects.png", @"aet":@"aftereffects.png", @"ai":@"illustrator.png", @"aif":@"audio.png", @"aiff":@"audio.png", @"ait":@"illustrator.png", @"ans":@"text.png", @"apk":@"executable.png", @"app":@"executable.png", @"arw":@"raw.png", @"as":@"fla_lang.png", @"asc":@"fla_lang.png", @"ascii":@"text.png", @"asf":@"video.png", @"asp":@"web_lang.png", @"aspx":@"web_lang.png", @"asx":@"playlist.png", @"avi":@"video.png", @"bay":@"raw.png", @"bmp":@"graphic.png", @"bz2":@"compressed.png", @"c":@"sourcecode.png", @"cc":@"sourcecode.png", @"cdr":@"vector.png", @"cgi":@"web_lang.png", @"class":@"java.png", @"com":@"executable.png", @"cpp":@"sourcecode.png", @"cr2":@"raw.png", @"css":@"web_data.png", @"cxx":@"sourcecode.png", @"dcr":@"raw.png", @"db":@"database.png", @"dbf":@"database.png", @"dhtml":@"html.png", @"dll":@"sourcecode.png", @"dng":@"raw.png", @"doc":@"word.png", @"docx":@"word.png", @"dotx":@"word.png", @"dwg":@"cad.png", @"dwt":@"dreamweaver.png", @"dxf":@"cad.png", @"eps":@"vector.png", @"exe":@"executable.png", @"fff":@"raw.png", @"fla":@"flash.png", @"flac":@"audio.png", @"flv":@"flash_video.png", @"fnt":@"font.png", @"fon":@"font.png", @"gadget":@"executable.png", @"gif":@"graphic.png", @"gpx":@"gis.png", @"gsheet":@"spreadsheet.png", @"gz":@"compressed.png", @"h":@"sourcecode.png", @"hpp":@"sourcecode.png", @"htm":@"html.png", @"html":@"html.png", @"iff":@"audio.png", @"inc":@"web_lang.png", @"indd":@"indesign.png", @"jar":@"java.png", @"java":@"java.png", @"jpeg":@"image.png", @"jpg":@"image.png", @"js":@"web_data.png", @"key":@"generic.png", @"kml":@"gis.png", @"log":@"text.png", @"m":@"sourcecode.png", @"mm":@"sourcecode.png", @"m3u":@"playlist.png", @"m4a":@"audio.png", @"max":@"3D.png", @"mdb":@"database.png", @"mef":@"raw.png", @"mid":@"midi.png", @"midi":@"midi.png", @"mkv":@"video.png", @"mov":@"video.png", @"mp3":@"audio.png", @"mp4":@"video.png", @"mpeg":@"video.png", @"mpg":@"video.png", @"mrw":@"raw.png", @"msi":@"executable.png", @"nb":@"spreadsheet.png", @"numbers":@"spreadsheet.png", @"nef":@"raw.png", @"obj":@"3D.png", @"odp":@"generic.png", @"ods":@"spreadsheet.png", @"odt":@"text.png", @"ogv":@"video.png", @"otf":@"font.png", @"ots":@"spreadsheet.png", @"orf":@"raw.png", @"pages":@"text.png", @"pcast":@"podcast.png", @"pdb":@"database.png", @"pdf":@"pdf.png", @"pef":@"raw.png", @"php":@"web_lang.png", @"php3":@"web_lang.png", @"php4":@"web_lang.png", @"php5":@"web_lang.png", @"phtml":@"web_lang.png", @"pl":@"web_lang.png", @"pls":@"playlist.png", @"png":@"graphic.png", @"ppj":@"premiere.png", @"pps":@"powerpoint.png", @"ppt":@"powerpoint.png", @"pptx":@"powerpoint.png", @"prproj":@"premiere.png", @"psb":@"photoshop.png", @"psd":@"photoshop.png", @"py":@"web_lang.png", @"ra":@"real_audio.png", @"ram":@"real_audio.png", @"rar":@"compressed.png", @"rm":@"real_audio.png", @"rtf":@"text.png", @"rw2":@"raw.png", @"rwl":@"raw.png", @"sh":@"sourcecode.png", @"shtml":@"web_data.png", @"sitx":@"compressed.png", @"sql":@"database.png", @"srf":@"raw.png", @"srt":@"video_subtitles.png", @"stl":@"3D.png", @"svg":@"vector.png", @"svgz":@"vector.png", @"swf":@"swf.png", @"tar":@"compressed.png", @"tbz":@"compressed.png", @"tga":@"graphic.png", @"tgz":@"compressed.png", @"tif":@"graphic.png", @"tiff":@"graphic.png", @"torrent":@"torrent.png", @"ttf":@"font.png", @"txt":@"text.png", @"vcf":@"vcard.png", @"vob":@"video_vob.png", @"wav":@"audio.png", @"webm":@"video.png", @"wma":@"audio.png", @"wmv":@"video.png", @"wpd":@"text.png", @"wps":@"word.png", @"xhtml":@"html.png", @"xlr":@"spreadsheet.png", @"xls":@"excel.png", @"xlsx":@"excel.png", @"xlt":@"excel.png", @"xltm":@"excel.png", @"xml":@"web_data.png", @"zip":@"compressed.png"}; MEGANodeType nodeType = [node type]; switch (nodeType) { case MEGANodeTypeFolder: { if (node.isInShare) return [UIImage imageNamed:@"folder_shared"]; else return [UIImage imageNamed:@"folder"]; } case MEGANodeTypeRubbish: return [UIImage imageNamed:@"folder"]; case MEGANodeTypeFile: { NSString *im = [dictionary valueForKey:[node name].lowercaseString.pathExtension]; if (im && im.length>0) { return [UIImage imageNamed:im]; } } default: return [UIImage imageNamed:@"generic"]; } } + (NSString *)pathForNode:(MEGANode *)node searchPath:(NSSearchPathDirectory)path directory:(NSString *)directory { NSString *extension = [@"." stringByAppendingString:[[node name] pathExtension]]; NSString *destinationPath = [NSSearchPathForDirectoriesInDomains(path, NSUserDomainMask, YES) objectAtIndex:0]; NSString *fileName = [[node base64Handle] stringByAppendingString:extension]; NSString *destinationFilePath = nil; destinationFilePath = [directory isEqualToString:@""] ? [destinationPath stringByAppendingPathComponent:fileName] :[[destinationPath stringByAppendingPathComponent:directory] stringByAppendingPathComponent:fileName]; return destinationFilePath; } + (NSString *)pathForNode:(MEGANode *)node searchPath:(NSSearchPathDirectory)path { return [self pathForNode:node searchPath:path directory:@""]; } + (NSString *)pathForUser:(MEGAUser *)user searchPath:(NSSearchPathDirectory)path directory:(NSString *)directory { NSString *destinationPath = [NSSearchPathForDirectoriesInDomains(path, NSUserDomainMask, YES) objectAtIndex:0]; NSString *fileName = [[user email] stringByAppendingString:@".png"]; NSString *destinationFilePath = nil; destinationFilePath = [directory isEqualToString:@""] ? [destinationPath stringByAppendingPathComponent:fileName] :[[destinationPath stringByAppendingPathComponent:directory] stringByAppendingPathComponent:fileName]; return destinationFilePath; } @end sdk-10.11.0/examples/iOS/Demo/Demo/Utils/MEGASdkManager.h000066400000000000000000000004021516266226600224420ustar00rootroot00000000000000#import #import "MEGASdk.h" @interface MEGASdkManager : NSObject @property (nonatomic, strong) MEGASdk *megaSDK; + (void)setAppKey:(NSString *)appKey; + (void)setUserAgent:(NSString *)userAgent; + (MEGASdk *)sharedMEGASdk; @end sdk-10.11.0/examples/iOS/Demo/Demo/Utils/MEGASdkManager.m000066400000000000000000000014551516266226600224600ustar00rootroot00000000000000#import "MEGASdkManager.h" @implementation MEGASdkManager static NSString *_appKey = nil; static NSString *_userAgent = nil; static MEGASdk *_megaSDK = nil; + (void)setAppKey:(NSString *)appKey { _appKey = appKey; } + (void)setUserAgent:(NSString *)userAgent { _userAgent = userAgent; } + (MEGASdk *)sharedMEGASdk { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSAssert(_appKey != nil, @"setAppKey: should be called first"); NSAssert(_userAgent != nil, @"setUserAgent: should be called first"); NSString *basePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; _megaSDK = [[MEGASdk alloc] initWithAppKey:_appKey userAgent:_userAgent basePath:basePath]; }); return _megaSDK; } @end sdk-10.11.0/examples/iOS/Demo/Demo/en.lproj/000077500000000000000000000000001516266226600202565ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/en.lproj/LaunchScreen.strings000066400000000000000000000004261516266226600242450ustar00rootroot00000000000000 /* Class = "IBUILabel"; text = " Copyright (c) 2014 MEGA. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ "8ie-xW-0ye.text" = " Copyright (c) 2014 MEGA. All rights reserved."; /* Class = "IBUILabel"; text = "Demo"; ObjectID = "kId-c2-rCX"; */ "kId-c2-rCX.text" = "Demo"; sdk-10.11.0/examples/iOS/Demo/Demo/es.lproj/000077500000000000000000000000001516266226600202635ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/es.lproj/LaunchScreen.strings000066400000000000000000000004401516266226600242460ustar00rootroot00000000000000 /* Class = "IBUILabel"; text = " Copyright (c) 2014 MEGA. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ "8ie-xW-0ye.text" = " Copyright (c) 2014 MEGA. Todos los derechos reservados."; /* Class = "IBUILabel"; text = "Demo"; ObjectID = "kId-c2-rCX"; */ "kId-c2-rCX.text" = "Demo"; sdk-10.11.0/examples/iOS/Demo/Demo/es.lproj/Localizable.strings000066400000000000000000000026311516266226600241210ustar00rootroot00000000000000"cancel" = "Cancelar"; "ok" = "Aceptar"; "error" = "Error"; //LoginViewController "invalidMailOrPassword" = "Email o contraseña no válida."; "emailIvalidFormat" = "Introduzca un email válido"; "passwordInvalidFormat" = "Introduzca una contraseña válida"; "fetchingNodes" = "Recuperando nodos"; "preparingNodes" = "Preparando nodos"; //UITABBARCONTROLLER (1) CLOUD DRIVE "cloudDrive" = "Cloud"; "updatingNodes" = "Actualizando nodos..."; "foldersFiles" = "%d carpetas, %d ficheros"; "foldersFile" = "%d carpetas, %d fichero"; "folderFiles" = "%d carpeta, %d ficheros"; "folderFile" = "%d carpeta, %d fichero"; //DetailsNodeInfoViewController "savedForOffline" = "Guardado en offline"; "generateLink" = "Generando link..."; //"+" //Upload photo "uploadPhoto" = "Subir foto"; //Create folder "createFolder" = "Crear carpeta"; "newFolderTitle" = "Nueva carpeta"; "newFolderMessage" = "Nombre para la carpeta"; "createFolderButton" = "Crear"; //UITABBARCONTROLLER (2) OFFLINE //UITABBARCONTROLLER (3) CONTACTS "noFoldersShare" = "No hay carpetas compartidas"; "oneFolderShare" = "carpeta compartida"; "foldersShare" = "carpetas compartidas"; //Rename "renameNodeTitle" = "Cambiar nombre"; "renameNodeMessage" = "Introduce otro nombre"; "renameNodeButton" = "Renombrar"; //Remove "removeNodeTitle" = "Eliminar"; "removeNodeMessage" = "¿Estás seguro?"; //UITABBARCONTROLLER (4) SETTINGS "logout" = "Cerrando sesión...";sdk-10.11.0/examples/iOS/Demo/Demo/es.lproj/Main.strings000066400000000000000000000045331516266226600225670ustar00rootroot00000000000000 /* Class = "IBUITabBarItem"; title = "Cloud Drive"; ObjectID = "1bz-iN-B5v"; */ "1bz-iN-B5v.title" = "Cloud"; /* Class = "IBUITextField"; placeholder = "Email"; ObjectID = "4EC-4o-G5t"; */ "4EC-4o-G5t.placeholder" = "Email"; /* Class = "IBUITabBarItem"; title = "Contacts"; ObjectID = "7iB-AC-2aH"; */ "7iB-AC-2aH.title" = "Contactos"; /* Class = "IBUILabel"; text = "Modification Time"; ObjectID = "9nn-R0-1sF"; */ "9nn-R0-1sF.text" = "Modificación"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "FSd-tP-4bg"; */ "FSd-tP-4bg.text" = "Label"; /* Class = "IBUINavigationItem"; title = "MEGA"; ObjectID = "LgB-8Q-p3f"; */ "LgB-8Q-p3f.title" = "MEGA"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "MpR-gE-z6F"; */ "MpR-gE-z6F.text" = "Label"; /* Class = "IBUIButton"; normalTitle = "Login"; ObjectID = "Q05-sf-hPc"; */ "Q05-sf-hPc.normalTitle" = "Iniciar sesión"; /* Class = "IBUITabBarItem"; title = "Settings"; ObjectID = "RSY-4Z-iP2"; */ "RSY-4Z-iP2.title" = "Ajustes"; /* Class = "IBUITextField"; placeholder = "Password"; ObjectID = "Xkf-mm-cAV"; */ "Xkf-mm-cAV.placeholder" = "Contraseña"; /* Class = "IBUITabBarItem"; title = "Offline"; ObjectID = "cRT-iu-juh"; */ "cRT-iu-juh.title" = "Offline"; /* Class = "IBUINavigationItem"; title = "Settings"; ObjectID = "cVP-5B-dHr"; */ "cVP-5B-dHr.title" = "Ajustes"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "iXq-aa-JXB"; */ "iXq-aa-JXB.text" = "Label"; /* Class = "IBUIBarButtonItem"; title = "Logout"; ObjectID = "ivV-ol-K60"; */ "ivV-ol-K60.title" = "Cerrar sesión"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "k2G-mp-ecO"; */ "k2G-mp-ecO.text" = "Label"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "naF-lZ-sOV"; */ "naF-lZ-sOV.text" = "Label"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "nam-ZF-2MZ"; */ "nam-ZF-2MZ.text" = "Label"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "pCW-aM-GxL"; */ "pCW-aM-GxL.text" = "Label"; /* Class = "IBUINavigationItem"; title = "Offline documents"; ObjectID = "q14-i5-Qcs"; */ "q14-i5-Qcs.title" = "Documentos offline"; /* Class = "IBUILabel"; text = "Name"; ObjectID = "qTp-Ed-t4G"; */ "qTp-Ed-t4G.text" = "Nombre"; /* Class = "IBUILabel"; text = "Label"; ObjectID = "sun-dh-P3O"; */ "sun-dh-P3O.text" = "Label"; /* Class = "IBUINavigationItem"; title = "Contacts"; ObjectID = "zIq-IE-piT"; */ "zIq-IE-piT.title" = "Contactos"; sdk-10.11.0/examples/iOS/Demo/Demo/main.m000066400000000000000000000014661516266226600176400ustar00rootroot00000000000000/** * @file main.m * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } sdk-10.11.0/examples/iOS/Demo/Demo/vendor/000077500000000000000000000000001516266226600200245ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SSKeychain/000077500000000000000000000000001516266226600220255ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SSKeychain/SSKeychain.h000077500000000000000000000121371516266226600242060ustar00rootroot00000000000000// // SSKeychain.h // SSKeychain // // Created by Sam Soffes on 5/19/10. // Copyright (c) 2010-2014 Sam Soffes. All rights reserved. // #import "SSKeychainQuery.h" /** Error code specific to SSKeychain that can be returned in NSError objects. For codes returned by the operating system, refer to SecBase.h for your platform. */ typedef NS_ENUM(OSStatus, SSKeychainErrorCode) { /** Some of the arguments were invalid. */ SSKeychainErrorBadArguments = -1001, }; /** SSKeychain error domain */ extern NSString *const kSSKeychainErrorDomain; /** Account name. */ extern NSString *const kSSKeychainAccountKey; /** Time the item was created. The value will be a string. */ extern NSString *const kSSKeychainCreatedAtKey; /** Item class. */ extern NSString *const kSSKeychainClassKey; /** Item description. */ extern NSString *const kSSKeychainDescriptionKey; /** Item label. */ extern NSString *const kSSKeychainLabelKey; /** Time the item was last modified. The value will be a string. */ extern NSString *const kSSKeychainLastModifiedKey; /** Where the item was created. */ extern NSString *const kSSKeychainWhereKey; /** Simple wrapper for accessing accounts, getting passwords, setting passwords, and deleting passwords using the system Keychain on Mac OS X and iOS. This was originally inspired by EMKeychain and SDKeychain (both of which are now gone). Thanks to the authors. SSKeychain has since switched to a simpler implementation that was abstracted from [SSToolkit](http://sstoolk.it). */ @interface SSKeychain : NSObject #pragma mark - Classic methods /** Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. @param serviceName The service for which to return the corresponding password. @param account The account for which to return the corresponding password. @return Returns a string containing the password for a given account and service, or `nil` if the Keychain doesn't have a password for the given parameters. */ + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account; + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; /** Deletes a password from the Keychain. @param serviceName The service for which to delete the corresponding password. @param account The account for which to delete the corresponding password. @return Returns `YES` on success, or `NO` on failure. */ + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account; + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; /** Sets a password in the Keychain. @param password The password to store in the Keychain. @param serviceName The service for which to set the corresponding password. @param account The account for which to set the corresponding password. @return Returns `YES` on success, or `NO` on failure. */ + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account; + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError **)error; /** Returns an array containing the Keychain's accounts, or `nil` if the Keychain has no accounts. See the `NSString` constants declared in SSKeychain.h for a list of keys that can be used when accessing the dictionaries returned by this method. @return An array of dictionaries containing the Keychain's accounts, or `nil` if the Keychain doesn't have any accounts. The order of the objects in the array isn't defined. */ + (NSArray *)allAccounts; /** Returns an array containing the Keychain's accounts for a given service, or `nil` if the Keychain doesn't have any accounts for the given service. See the `NSString` constants declared in SSKeychain.h for a list of keys that can be used when accessing the dictionaries returned by this method. @param serviceName The service for which to return the corresponding accounts. @return An array of dictionaries containing the Keychain's accounts for a given `serviceName`, or `nil` if the Keychain doesn't have any accounts for the given `serviceName`. The order of the objects in the array isn't defined. */ + (NSArray *)accountsForService:(NSString *)serviceName; #pragma mark - Configuration #if __IPHONE_4_0 && TARGET_OS_IPHONE /** Returns the accessibility type for all future passwords saved to the Keychain. @return Returns the accessibility type. The return value will be `NULL` or one of the "Keychain Item Accessibility Constants" used for determining when a keychain item should be readable. @see setAccessibilityType */ + (CFTypeRef)accessibilityType; /** Sets the accessibility type for all future passwords saved to the Keychain. @param accessibilityType One of the "Keychain Item Accessibility Constants" used for determining when a keychain item should be readable. If the value is `NULL` (the default), the Keychain default will be used. @see accessibilityType */ + (void)setAccessibilityType:(CFTypeRef)accessibilityType; #endif @end sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SSKeychain/SSKeychain.m000077500000000000000000000052501516266226600242110ustar00rootroot00000000000000// // SSKeychain.m // SSKeychain // // Created by Sam Soffes on 5/19/10. // Copyright (c) 2010-2014 Sam Soffes. All rights reserved. // #import "SSKeychain.h" NSString *const kSSKeychainErrorDomain = @"com.samsoffes.sskeychain"; NSString *const kSSKeychainAccountKey = @"acct"; NSString *const kSSKeychainCreatedAtKey = @"cdat"; NSString *const kSSKeychainClassKey = @"labl"; NSString *const kSSKeychainDescriptionKey = @"desc"; NSString *const kSSKeychainLabelKey = @"labl"; NSString *const kSSKeychainLastModifiedKey = @"mdat"; NSString *const kSSKeychainWhereKey = @"svce"; #if __IPHONE_4_0 && TARGET_OS_IPHONE static CFTypeRef SSKeychainAccessibilityType = NULL; #endif @implementation SSKeychain + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account { return [self passwordForService:serviceName account:account error:nil]; } + (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account error:(NSError *__autoreleasing *)error { SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = serviceName; query.account = account; [query fetch:error]; return query.password; } + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account { return [self deletePasswordForService:serviceName account:account error:nil]; } + (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account error:(NSError *__autoreleasing *)error { SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = serviceName; query.account = account; return [query deleteItem:error]; } + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account { return [self setPassword:password forService:serviceName account:account error:nil]; } + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account error:(NSError *__autoreleasing *)error { SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = serviceName; query.account = account; query.password = password; return [query save:error]; } + (NSArray *)allAccounts { return [self accountsForService:nil]; } + (NSArray *)accountsForService:(NSString *)serviceName { SSKeychainQuery *query = [[SSKeychainQuery alloc] init]; query.service = serviceName; return [query fetchAll:nil]; } #if __IPHONE_4_0 && TARGET_OS_IPHONE + (CFTypeRef)accessibilityType { return SSKeychainAccessibilityType; } + (void)setAccessibilityType:(CFTypeRef)accessibilityType { CFRetain(accessibilityType); if (SSKeychainAccessibilityType) { CFRelease(SSKeychainAccessibilityType); } SSKeychainAccessibilityType = accessibilityType; } #endif @end sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SSKeychain/SSKeychainQuery.h000077500000000000000000000071641516266226600252400ustar00rootroot00000000000000// // SSKeychainQuery.h // SSKeychain // // Created by Caleb Davenport on 3/19/13. // Copyright (c) 2013-2014 Sam Soffes. All rights reserved. // #import #import #if __IPHONE_7_0 || __MAC_10_9 // Keychain synchronization available at compile time #define SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE 1 #endif #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE typedef NS_ENUM(NSUInteger, SSKeychainQuerySynchronizationMode) { SSKeychainQuerySynchronizationModeAny, SSKeychainQuerySynchronizationModeNo, SSKeychainQuerySynchronizationModeYes }; #endif /** Simple interface for querying or modifying keychain items. */ @interface SSKeychainQuery : NSObject /** kSecAttrAccount */ @property (nonatomic, copy) NSString *account; /** kSecAttrService */ @property (nonatomic, copy) NSString *service; /** kSecAttrLabel */ @property (nonatomic, copy) NSString *label; #if __IPHONE_3_0 && TARGET_OS_IPHONE /** kSecAttrAccessGroup (only used on iOS) */ @property (nonatomic, copy) NSString *accessGroup; #endif #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE /** kSecAttrSynchronizable */ @property (nonatomic) SSKeychainQuerySynchronizationMode synchronizationMode; #endif /** Root storage for password information */ @property (nonatomic, copy) NSData *passwordData; /** This property automatically transitions between an object and the value of `passwordData` using NSKeyedArchiver and NSKeyedUnarchiver. */ @property (nonatomic, copy) id passwordObject; /** Convenience accessor for setting and getting a password string. Passes through to `passwordData` using UTF-8 string encoding. */ @property (nonatomic, copy) NSString *password; ///------------------------ /// @name Saving & Deleting ///------------------------ /** Save the receiver's attributes as a keychain item. Existing items with the given account, service, and access group will first be deleted. @param error Populated should an error occur. @return `YES` if saving was successful, `NO` otherwise. */ - (BOOL)save:(NSError **)error; /** Delete keychain items that match the given account, service, and access group. @param error Populated should an error occur. @return `YES` if saving was successful, `NO` otherwise. */ - (BOOL)deleteItem:(NSError **)error; ///--------------- /// @name Fetching ///--------------- /** Fetch all keychain items that match the given account, service, and access group. The values of `password` and `passwordData` are ignored when fetching. @param error Populated should an error occur. @return An array of dictionaries that represent all matching keychain items or `nil` should an error occur. The order of the items is not determined. */ - (NSArray *)fetchAll:(NSError **)error; /** Fetch the keychain item that matches the given account, service, and access group. The `password` and `passwordData` properties will be populated unless an error occurs. The values of `password` and `passwordData` are ignored when fetching. @param error Populated should an error occur. @return `YES` if fetching was successful, `NO` otherwise. */ - (BOOL)fetch:(NSError **)error; ///----------------------------- /// @name Synchronization Status ///----------------------------- #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE /** Returns a boolean indicating if keychain synchronization is available on the device at runtime. The #define SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE is only for compile time. If you are checking for the presence of synchronization, you should use this method. @return A value indicating if keychain synchronization is available */ + (BOOL)isSynchronizationAvailable; #endif @end sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SSKeychain/SSKeychainQuery.m000077500000000000000000000166731516266226600252520ustar00rootroot00000000000000// // SSKeychainQuery.m // SSKeychain // // Created by Caleb Davenport on 3/19/13. // Copyright (c) 2013-2014 Sam Soffes. All rights reserved. // #import "SSKeychainQuery.h" #import "SSKeychain.h" @implementation SSKeychainQuery @synthesize account = _account; @synthesize service = _service; @synthesize label = _label; @synthesize passwordData = _passwordData; #if __IPHONE_3_0 && TARGET_OS_IPHONE @synthesize accessGroup = _accessGroup; #endif #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE @synthesize synchronizationMode = _synchronizationMode; #endif #pragma mark - Public - (BOOL)save:(NSError *__autoreleasing *)error { OSStatus status = SSKeychainErrorBadArguments; if (!self.service || !self.account || !self.passwordData) { if (error) { *error = [[self class] errorWithCode:status]; } return NO; } [self deleteItem:nil]; NSMutableDictionary *query = [self query]; [query setObject:self.passwordData forKey:(__bridge id)kSecValueData]; if (self.label) { [query setObject:self.label forKey:(__bridge id)kSecAttrLabel]; } #if __IPHONE_4_0 && TARGET_OS_IPHONE CFTypeRef accessibilityType = [SSKeychain accessibilityType]; if (accessibilityType) { [query setObject:(__bridge id)accessibilityType forKey:(__bridge id)kSecAttrAccessible]; } #endif status = SecItemAdd((__bridge CFDictionaryRef)query, NULL); if (status != errSecSuccess && error != NULL) { *error = [[self class] errorWithCode:status]; } return (status == errSecSuccess); } - (BOOL)deleteItem:(NSError *__autoreleasing *)error { OSStatus status = SSKeychainErrorBadArguments; if (!self.service || !self.account) { if (error) { *error = [[self class] errorWithCode:status]; } return NO; } NSMutableDictionary *query = [self query]; #if TARGET_OS_IPHONE status = SecItemDelete((__bridge CFDictionaryRef)query); #else CFTypeRef result = NULL; [query setObject:@YES forKey:(__bridge id)kSecReturnRef]; status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status == errSecSuccess) { status = SecKeychainItemDelete((SecKeychainItemRef)result); CFRelease(result); } #endif if (status != errSecSuccess && error != NULL) { *error = [[self class] errorWithCode:status]; } return (status == errSecSuccess); } - (NSArray *)fetchAll:(NSError *__autoreleasing *)error { OSStatus status = SSKeychainErrorBadArguments; NSMutableDictionary *query = [self query]; [query setObject:@YES forKey:(__bridge id)kSecReturnAttributes]; [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit]; CFTypeRef result = NULL; status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status != errSecSuccess && error != NULL) { *error = [[self class] errorWithCode:status]; return nil; } return (__bridge_transfer NSArray *)result; } - (BOOL)fetch:(NSError *__autoreleasing *)error { OSStatus status = SSKeychainErrorBadArguments; if (!self.service || !self.account) { if (error) { *error = [[self class] errorWithCode:status]; } return NO; } CFTypeRef result = NULL; NSMutableDictionary *query = [self query]; [query setObject:@YES forKey:(__bridge id)kSecReturnData]; [query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result); if (status != errSecSuccess && error != NULL) { *error = [[self class] errorWithCode:status]; return NO; } self.passwordData = (__bridge_transfer NSData *)result; return YES; } #pragma mark - Accessors - (void)setPasswordObject:(id)object { self.passwordData = [NSKeyedArchiver archivedDataWithRootObject:object]; } - (id)passwordObject { if ([self.passwordData length]) { return [NSKeyedUnarchiver unarchiveObjectWithData:self.passwordData]; } return nil; } - (void)setPassword:(NSString *)password { self.passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; } - (NSString *)password { if ([self.passwordData length]) { return [[NSString alloc] initWithData:self.passwordData encoding:NSUTF8StringEncoding]; } return nil; } #pragma mark - Synchronization Status #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE + (BOOL)isSynchronizationAvailable { #if TARGET_OS_IPHONE // Apple suggested way to check for 7.0 at runtime // https://developer.apple.com/library/ios/documentation/userexperience/conceptual/transitionguide/SupportingEarlieriOS.html return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1; #else return floor(NSFoundationVersionNumber) > NSFoundationVersionNumber10_8_4; #endif } #endif #pragma mark - Private - (NSMutableDictionary *)query { NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:3]; [dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; if (self.service) { [dictionary setObject:self.service forKey:(__bridge id)kSecAttrService]; } if (self.account) { [dictionary setObject:self.account forKey:(__bridge id)kSecAttrAccount]; } #if __IPHONE_3_0 && TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR if (self.accessGroup) { [dictionary setObject:self.accessGroup forKey:(__bridge id)kSecAttrAccessGroup]; } #endif #ifdef SSKEYCHAIN_SYNCHRONIZATION_AVAILABLE if ([[self class] isSynchronizationAvailable]) { id value; switch (self.synchronizationMode) { case SSKeychainQuerySynchronizationModeNo: { value = @NO; break; } case SSKeychainQuerySynchronizationModeYes: { value = @YES; break; } case SSKeychainQuerySynchronizationModeAny: { value = (__bridge id)(kSecAttrSynchronizableAny); break; } } [dictionary setObject:value forKey:(__bridge id)(kSecAttrSynchronizable)]; } #endif return dictionary; } + (NSError *)errorWithCode:(OSStatus) code { NSString *message = nil; switch (code) { case errSecSuccess: return nil; case SSKeychainErrorBadArguments: message = NSLocalizedStringFromTable(@"SSKeychainErrorBadArguments", @"SSKeychain", nil); break; #if TARGET_OS_IPHONE case errSecUnimplemented: { message = NSLocalizedStringFromTable(@"errSecUnimplemented", @"SSKeychain", nil); break; } case errSecParam: { message = NSLocalizedStringFromTable(@"errSecParam", @"SSKeychain", nil); break; } case errSecAllocate: { message = NSLocalizedStringFromTable(@"errSecAllocate", @"SSKeychain", nil); break; } case errSecNotAvailable: { message = NSLocalizedStringFromTable(@"errSecNotAvailable", @"SSKeychain", nil); break; } case errSecDuplicateItem: { message = NSLocalizedStringFromTable(@"errSecDuplicateItem", @"SSKeychain", nil); break; } case errSecItemNotFound: { message = NSLocalizedStringFromTable(@"errSecItemNotFound", @"SSKeychain", nil); break; } case errSecInteractionNotAllowed: { message = NSLocalizedStringFromTable(@"errSecInteractionNotAllowed", @"SSKeychain", nil); break; } case errSecDecode: { message = NSLocalizedStringFromTable(@"errSecDecode", @"SSKeychain", nil); break; } case errSecAuthFailed: { message = NSLocalizedStringFromTable(@"errSecAuthFailed", @"SSKeychain", nil); break; } default: { message = NSLocalizedStringFromTable(@"errSecDefault", @"SSKeychain", nil); } #else default: message = (__bridge_transfer NSString *)SecCopyErrorMessageString(code, NULL); #endif } NSDictionary *userInfo = nil; if (message) { userInfo = @{ NSLocalizedDescriptionKey : message }; } return [NSError errorWithDomain:kSSKeychainErrorDomain code:code userInfo:userInfo]; } @end sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/000077500000000000000000000000001516266226600224425ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD-Prefix.pch000066400000000000000000000002471516266226600270120ustar00rootroot00000000000000// // Prefix header for all source files of the 'SVProgressHUD' target in the 'SVProgressHUD' project // #ifdef __OBJC__ #import #endif sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.bundle/000077500000000000000000000000001516266226600263305ustar00rootroot00000000000000sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png000066400000000000000000000163721516266226600314400ustar00rootroot00000000000000PNG  IHDRXtEXtSoftwareAdobe ImageReadyqe<#iTXtXML:com.adobe.xmp pmIDATx:'1grffA]Mjv d3jRd,ޚؖvs췵s~n?$ȱ|duۤۊȯis6^qL/bd3G&0-,AϹ:ц&A:-ؠDU@P6ӱwbcgy[@VOٍ1Fuן2բ^m+H3l^*ye*m6 N8& ME&9xg[֧p VrKXM \9<ŀYVkXźK gB|hϝo.Xw +~XuVQ6+gDPR5 y$`nI@Tٶ[ p YU,V3U,& Ze(@kWj=dHEB7 ʜRC*BzFXZ(oj|AC:+[DG/{jK '\)ݯcOdF VdXVZ =q1 /TXkH Amm,+x&ڀ1)O`F~qYKq(@-S/l[2vHg{!sڳނ-!=ȴQ5fB(d|#wB#cY%jc/8&~HɬfU+* ,JJb»+G(J&23Xs4AI( }H*Ȃ%+|UeXrIdFPƟ?fyc>0eYN6+AG29;[K#Z3lZ grz(8}(X hBJu0^_mR E 㳔3+V+MfxQ2ȬIW! (e#Q%qeb Rg/Tfaui63q܌,H"*2k:!7[%8< YAV ;V`6<3vlƏDԳ?4/D3An)2ȂLPV ??ɫQ V! bنasfNyCBpLXqe1سm%0@im.0he^EOO fvGa#S"=騒e^D%PU@DWf`A}2/Z,V|4c9 UߚfeͶXLeZٕ a[,w0G,֬nU~ sg/V.GT${,hGb} 0,G)X02,Ve@g)jW#XY7PȰX,v40,V JVF&AU /Ybyԣ:gE3Մ˲QAd(Q5a@EDAhs; GE,V'[J+Y= 4hVm~XՕdzkV je-fTcU,)iGr CҬ8! aW?PD EPq{^*lVۄ;\ bf+pN>fF>H6#fibP,„cE< ѶYJ+ŪfUgÑ GAW4 3Nt)kVTL{eb{|HM͸;!;>>)gKEOPcmh%W@0\5r g 3 Dig `QZYDT><U6 n#*^jY$rxaZtZX )ւV˳̘%H*ث+I*V4XT_V`"9=fUk58Tg4VHVNH, YޱGTs8c leQKk!VY`0 0*ljzC!#xbp#| 0TdF" ZYo$XCw.14r0LUQڝj&}hGr7 k3:vTSŊMA XU"b[3±z,Qbt40nU FoyB=*"=vk߲gvq5^XD$[[e L\lbū"Z.͈ehb/6#Ճ+-UHE \6|_q[W)aQJXV϶\,U$oaf M,ꡁ#WU,x-2 (GE< DboƎ@®^1sG20vy`7|9_o큐no @ @*i*BOډ0rJ"d=zhp @PBe<ٗQd=$uf#=29 4'8ye*a\.r}U9i!+>xQby7;o٬c}~H.~2 (^4ϛ@$h4`4Up2Y}/H[78YU /ȫ$Zna,mGy ,fPϬ&Lt=K9  ݲQmbtg {[UVmZEÏ GAca,ʝ5.+yx;@=CeFNis>=SI| WCBV,I>c3&5kqUP YC5sse]@v^bCE*m;3.߿uoSME"vKSk6,ebvjvpQH bmu@DXO1hE P(vd*G$O@AÚE  /Yv10Ր ZHɲ7?2$ jmJaQr=D p8\62+CI:;<_].ծH@Gkgjc)qFK)ޅf)ZD`s/$]3sj1Zb9R@*379Bш_[cTQx7KUB*/HGam]c(`CD+A ,ޗ6R 2h|JBo(E4[`xW=Wʁm}lJDA.y2^YaUgk$ @sTi* Cr`d&㥢J 1 j ˞aD KKdVY+f,nIJ[2]U^` R1P4DB;=<FvJN,U 2֖omؕz VXʔRppVTenÑKd3TY'Z ֣L0 Tqb "pBQ Ny(AA`$jFDjeެUuY uFդ+*X|Pf)f $H@GDaUhPg}3*[Y=e`TUDXJrUĆ}[4x&lP\Q6, f e'oi`4㷡-D ZѬu]vXXUagٺ,E*jٰ 5~O85D ŨժVh]΍7^EinT:XfJ,8 $=6kǙ ^yo Ǩ7 Dlr~: ӲM{,;ۙeފ ,-i YY-C*b*KXG{8PnQ&on@'bci7>Z!9E$wį_o{Ս j4$H,k"J|[Ȝ^=ͲX f)?9OZVpY0QM,l. ^5aa- 0`ͪ B VZ\c>H -WZj . Z"O27lAŰ QV@4%XuD#mG#Q 弈mDZZ?h "~+(?be3|hXͨbY&: :2-hhk*Uy7@TeWAPZi jdtym7lUf+b1$w̵WVeʶYSwez(k=` >rxBzDEV^EmV$d* %{T٫jK=BzhL8odd0@1dA77#+{[y3uDM!Qm8}?Xj dEWe`+yJy*XJZa5*= E=P@.Df|+ನ+oгX+gf QBR&ͱͪG$, `!82ګNcQ$ ]M4)8Y} h%0U,KHoE6AȰZcPHխ,k޲nvH?5AUk'pG[,eic"}(dK=yeI$d?Q*IՊ(;GU$l@rvyד?"6lt '7τ$<—m]![-4D2'd Z  hH#a#Vx<; HhYjyj7xIdP"$KҫXI=, ^#sDգbE*,!d[p],(WX̼˰XeeKUTE^#{U&=cjŲZ33 /ZeYk -֪pDU$6 @\!Q-?@-bUoAv@,ņM|3dQ= { }UcXU,u;7 J֊=y!S=<@jWgXw!e݌.(8"^EO^R[TꁎB^a HE&a"{8Ћ eAaf' "w]e*30R '],A%H(g_Vj5ɶRdohXp0@*"AhVU vکjV両ZdoR٪uףÐlilٗAMتTH6c#s!W="a}ucUK)K="=PɮjT$,~t"*ZʪZ'iJeD/Fhy{%D&(UPb,Tp 0<9d#ÑW)V T^+28Qe*8vd7;d[: Cy݊fDƵ^ cB8u-[tDsW@*L(ګh97ν{2*a=fUh_+V}ģ*LuV+85a0U$;haj99c; R5W1!\]""19̂i> Ey*;<| ތ5E]X{]Z*BE?eUMIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.bundle/error@2x.png000066400000000000000000000013431516266226600305420ustar00rootroot00000000000000PNG  IHDR88;tEXtSoftwareAdobe ImageReadyqe<IDATx웋m0@M!l&h&`0fAt2Ti|;">]B%J}o]I%zi]ɂPn(OXhRR?G`}ɣs<ɡA {|ILgdبW;nRY>mmU+| rl zB$[0%t~Cš@Ăypͬ˧,ظek#1@i]ϰBq(NaS1"qDG+m}(4x'@N1&.'kT` Ьzq!aG’{ 5S4Ff07H85›i#ӵ$ b]ڠEU0W)d*dRX(G,iI28j@ HR86pXg3yhI!#@FC'=pB^f.>Z:P O%^l_%c G^9va/_ԍCp6\Ff ݇Bhd44+/F <6LϪң5$%x-*L~>y}0.~yҩzoy#ʢAzIENDB`sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.bundle/success@2x.png000066400000000000000000000015501516266226600310610ustar00rootroot00000000000000PNG  IHDR88;tEXtSoftwareAdobe ImageReadyqe< IDATx[m0 U  L L۠  9DLp#>u]["<2xYx+2xfO|1[{3ujst`t.EGg|m Jxhvr19hZ-VrN/@d˵%%8ȅkgS 3<|%dJÌWUQ(%܄o4, l` flːZ Z`*ken S󻍌d߈Yg*T$uV#˯1V6U3db?+ѳ `Άɽ)3qC.8-rm0 3wd4l[*-t?R0P>=ZN<1Ēaz>]TAj-D-m9AbE&9^jtn"iaRp1D:Bv hsdkn!:@5rh vy&>p隐̙Ƭ i܂eOs]Q錦ju^ԋRɋqr.#~z(Sh|܅_e:_JnA<leb)s;?#;߷]*BHxs5t%Q!RȧJ"n15]#)"fD6E.&ו)ʊkrێ=Te#7rItX8ȽIUZОeiᙍ .*-mtV|"wBɆ/>Rz(c/Q[IENDB`sdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.h000066400000000000000000000052011516266226600252270ustar00rootroot00000000000000// // SVProgressHUD.h // // Created by Sam Vermette on 27.03.11. // Copyright 2011 Sam Vermette. All rights reserved. // // https://github.com/samvermette/SVProgressHUD // #import #import extern NSString * const SVProgressHUDDidReceiveTouchEventNotification; extern NSString * const SVProgressHUDWillDisappearNotification; extern NSString * const SVProgressHUDDidDisappearNotification; extern NSString * const SVProgressHUDWillAppearNotification; extern NSString * const SVProgressHUDDidAppearNotification; extern NSString * const SVProgressHUDStatusUserInfoKey; typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) { SVProgressHUDMaskTypeNone = 1, // allow user interactions while HUD is displayed SVProgressHUDMaskTypeClear, // don't allow SVProgressHUDMaskTypeBlack, // don't allow and dim the UI in the back of the HUD SVProgressHUDMaskTypeGradient // don't allow and dim the UI with a a-la-alert-view bg gradient }; @interface SVProgressHUD : UIView #pragma mark - Customization + (void)setBackgroundColor:(UIColor*)color; // default is [UIColor whiteColor] + (void)setForegroundColor:(UIColor*)color; // default is [UIColor blackColor] + (void)setRingThickness:(CGFloat)width; // default is 4 pt + (void)setFont:(UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] + (void)setSuccessImage:(UIImage*)image; // default is bundled success image from Glyphish + (void)setErrorImage:(UIImage*)image; // default is bundled error image from Glyphish #pragma mark - Show Methods + (void)show; + (void)showWithMaskType:(SVProgressHUDMaskType)maskType; + (void)showWithStatus:(NSString*)status; + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType; + (void)showProgress:(float)progress; + (void)showProgress:(float)progress status:(NSString*)status; + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType; + (void)setStatus:(NSString*)string; // change the HUD loading status while it's showing // stops the activity indicator, shows a glyph + status, and dismisses HUD 1s later + (void)showSuccessWithStatus:(NSString*)string; + (void)showErrorWithStatus:(NSString *)string; + (void)showImage:(UIImage*)image status:(NSString*)status; // use 28x28 white pngs + (void)setOffsetFromCenter:(UIOffset)offset; + (void)resetOffsetFromCenter; + (void)popActivity; + (void)dismiss; + (BOOL)isVisible; @end @interface SVIndefiniteAnimatedView : UIView @property (nonatomic, assign) CGFloat strokeThickness; @property (nonatomic, assign) CGFloat radius; @property (nonatomic, strong) UIColor *strokeColor; @endsdk-10.11.0/examples/iOS/Demo/Demo/vendor/SVProgressHUD/SVProgressHUD.m000066400000000000000000001136431516266226600252460ustar00rootroot00000000000000// // SVProgressHUD.m // // Created by Sam Vermette on 27.03.11. // Copyright 2011 Sam Vermette. All rights reserved. // // https://github.com/samvermette/SVProgressHUD // #if !__has_feature(objc_arc) #error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #endif #import "SVProgressHUD.h" #import NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification"; NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification"; NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"; NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification"; NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification"; NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey"; static UIColor *SVProgressHUDBackgroundColor; static UIColor *SVProgressHUDForegroundColor; static CGFloat SVProgressHUDRingThickness; static UIFont *SVProgressHUDFont; static UIImage *SVProgressHUDSuccessImage; static UIImage *SVProgressHUDErrorImage; static const CGFloat SVProgressHUDRingRadius = 18; static const CGFloat SVProgressHUDRingNoTextRadius = 24; static const CGFloat SVProgressHUDParallaxDepthPoints = 10; @interface SVProgressHUD () @property (nonatomic, readwrite) SVProgressHUDMaskType maskType; @property (nonatomic, strong, readonly) NSTimer *fadeOutTimer; @property (nonatomic, readonly, getter = isClear) BOOL clear; @property (nonatomic, strong) UIControl *overlayView; @property (nonatomic, strong) UIView *hudView; @property (nonatomic, strong) UILabel *stringLabel; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) SVIndefiniteAnimatedView *indefiniteAnimatedView; @property (nonatomic, readwrite) CGFloat progress; @property (nonatomic, readwrite) NSUInteger activityCount; @property (nonatomic, strong) CAShapeLayer *backgroundRingLayer; @property (nonatomic, strong) CAShapeLayer *ringLayer; @property (nonatomic, readonly) CGFloat visibleKeyboardHeight; @property (nonatomic, assign) UIOffset offsetFromCenter; - (void)showProgress:(float)progress status:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType; - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration; - (void)dismiss; - (void)setStatus:(NSString*)string; - (void)registerNotifications; - (NSDictionary *)notificationUserInfo; - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle; - (void)positionHUD:(NSNotification*)notification; - (NSTimeInterval)displayDurationForString:(NSString*)string; @end @implementation SVProgressHUD + (SVProgressHUD*)sharedView { static dispatch_once_t once; static SVProgressHUD *sharedView; dispatch_once(&once, ^ { sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); return sharedView; } #pragma mark - Setters + (void)setStatus:(NSString *)string { [[self sharedView] setStatus:string]; } + (void)setBackgroundColor:(UIColor *)color { [self sharedView].hudView.backgroundColor = color; SVProgressHUDBackgroundColor = color; } + (void)setForegroundColor:(UIColor *)color { [self sharedView]; SVProgressHUDForegroundColor = color; } + (void)setFont:(UIFont *)font { [self sharedView]; SVProgressHUDFont = font; } + (void)setRingThickness:(CGFloat)width { [self sharedView]; SVProgressHUDRingThickness = width; } + (void)setSuccessImage:(UIImage *)image { [self sharedView]; SVProgressHUDSuccessImage = image; } + (void)setErrorImage:(UIImage *)image { [self sharedView]; SVProgressHUDErrorImage = image; } #pragma mark - Show Methods + (void)show { [[self sharedView] showProgress:-1 status:nil maskType:SVProgressHUDMaskTypeNone]; } + (void)showWithStatus:(NSString *)status { [[self sharedView] showProgress:-1 status:status maskType:SVProgressHUDMaskTypeNone]; } + (void)showWithMaskType:(SVProgressHUDMaskType)maskType { [[self sharedView] showProgress:-1 status:nil maskType:maskType]; } + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { [[self sharedView] showProgress:-1 status:status maskType:maskType]; } + (void)showProgress:(float)progress { [[self sharedView] showProgress:progress status:nil maskType:SVProgressHUDMaskTypeNone]; } + (void)showProgress:(float)progress status:(NSString *)status { [[self sharedView] showProgress:progress status:status maskType:SVProgressHUDMaskTypeNone]; } + (void)showProgress:(float)progress status:(NSString *)status maskType:(SVProgressHUDMaskType)maskType { [[self sharedView] showProgress:progress status:status maskType:maskType]; } #pragma mark - Show then dismiss methods + (void)showSuccessWithStatus:(NSString *)string { [self sharedView]; [self showImage:SVProgressHUDSuccessImage status:string]; } + (void)showErrorWithStatus:(NSString *)string { [self sharedView]; [self showImage:SVProgressHUDErrorImage status:string]; } + (void)showImage:(UIImage *)image status:(NSString *)string { NSTimeInterval displayInterval = [[SVProgressHUD sharedView] displayDurationForString:string]; [[self sharedView] showImage:image status:string duration:displayInterval]; } #pragma mark - Dismiss Methods + (void)popActivity { [self sharedView].activityCount--; if([self sharedView].activityCount == 0) [[self sharedView] dismiss]; } + (void)dismiss { if ([self isVisible]) { [[self sharedView] dismiss]; } } #pragma mark - Offset + (void)setOffsetFromCenter:(UIOffset)offset { [self sharedView].offsetFromCenter = offset; } + (void)resetOffsetFromCenter { [self setOffsetFromCenter:UIOffsetZero]; } #pragma mark - Instance Methods - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { self.userInteractionEnabled = NO; self.backgroundColor = [UIColor clearColor]; self.alpha = 0; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.activityCount = 0; SVProgressHUDBackgroundColor = [UIColor whiteColor]; SVProgressHUDForegroundColor = [UIColor blackColor]; if ([UIFont respondsToSelector:@selector(preferredFontForTextStyle:)]) { SVProgressHUDFont = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; } else { SVProgressHUDFont = [UIFont systemFontOfSize:14.0]; SVProgressHUDBackgroundColor = [UIColor colorWithWhite:0 alpha:0.8]; SVProgressHUDForegroundColor = [UIColor whiteColor]; } if ([[UIImage class] instancesRespondToSelector:@selector(imageWithRenderingMode:)]) { SVProgressHUDSuccessImage = [[UIImage imageNamed:@"SVProgressHUD.bundle/success"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; SVProgressHUDErrorImage = [[UIImage imageNamed:@"SVProgressHUD.bundle/error"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } else { SVProgressHUDSuccessImage = [UIImage imageNamed:@"SVProgressHUD.bundle/success"]; SVProgressHUDErrorImage = [UIImage imageNamed:@"SVProgressHUD.bundle/error"]; } SVProgressHUDRingThickness = 4; } return self; } - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); switch (self.maskType) { case SVProgressHUDMaskTypeBlack: { [[UIColor colorWithWhite:0 alpha:0.5] set]; CGContextFillRect(context, self.bounds); break; } case SVProgressHUDMaskTypeGradient: { size_t locationsCount = 2; CGFloat locations[2] = {0.0f, 1.0f}; CGFloat colors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount); CGColorSpaceRelease(colorSpace); CGFloat freeHeight = self.bounds.size.height - self.visibleKeyboardHeight; CGPoint center = CGPointMake(self.bounds.size.width/2, freeHeight/2); float radius = MIN(self.bounds.size.width , self.bounds.size.height) ; CGContextDrawRadialGradient (context, gradient, center, 0, center, radius, kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient); break; } } } - (void)updatePosition { CGFloat hudWidth = 100; CGFloat hudHeight = 100; CGFloat stringHeightBuffer = 20; CGFloat stringAndImageHeightBuffer = 80; CGFloat stringWidth = 0; CGFloat stringHeight = 0; CGRect labelRect = CGRectZero; NSString *string = self.stringLabel.text; // False if it's text-only BOOL imageUsed = (self.imageView.image) || (self.imageView.hidden); if(string) { CGSize constraintSize = CGSizeMake(200, 300); CGRect stringRect; if ([string respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { stringRect = [string boundingRectWithSize:constraintSize options:(NSStringDrawingUsesFontLeading|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName: self.stringLabel.font} context:NULL]; } else { CGSize stringSize; #ifdef __IPHONE_8_0 stringSize = [string sizeWithAttributes:@{NSFontAttributeName:[UIFont fontWithName:self.stringLabel.font.fontName size:self.stringLabel.font.pointSize]}]; #else stringSize = [string sizeWithFont:self.stringLabel.font constrainedToSize:CGSizeMake(200, 300)]; #endif stringRect = CGRectMake(0.0f, 0.0f, stringSize.width, stringSize.height); } stringWidth = stringRect.size.width; stringHeight = ceil(stringRect.size.height); if (imageUsed) hudHeight = stringAndImageHeightBuffer + stringHeight; else hudHeight = stringHeightBuffer + stringHeight; if(stringWidth > hudWidth) hudWidth = ceil(stringWidth/2)*2; CGFloat labelRectY = imageUsed ? 68 : 9; if(hudHeight > 100) { labelRect = CGRectMake(12, labelRectY, hudWidth, stringHeight); hudWidth+=24; } else { hudWidth+=24; labelRect = CGRectMake(0, labelRectY, hudWidth, stringHeight); } } self.hudView.bounds = CGRectMake(0, 0, hudWidth, hudHeight); if(string) self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, 36); else self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, CGRectGetHeight(self.hudView.bounds)/2); self.stringLabel.hidden = NO; self.stringLabel.frame = labelRect; [CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; if(string) { self.indefiniteAnimatedView.radius = SVProgressHUDRingRadius; [self.indefiniteAnimatedView sizeToFit]; CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36); self.indefiniteAnimatedView.center = center; if(self.progress != -1) self.backgroundRingLayer.position = self.ringLayer.position = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36); } else { self.indefiniteAnimatedView.radius = SVProgressHUDRingNoTextRadius; [self.indefiniteAnimatedView sizeToFit]; CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); self.indefiniteAnimatedView.center = center; if(self.progress != -1) self.backgroundRingLayer.position = self.ringLayer.position = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); } [CATransaction commit]; } - (void)setStatus:(NSString *)string { self.stringLabel.text = string; [self updatePosition]; } - (void)setFadeOutTimer:(NSTimer *)newTimer { if(_fadeOutTimer) [_fadeOutTimer invalidate], _fadeOutTimer = nil; if(newTimer) _fadeOutTimer = newTimer; } - (void)registerNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidShowNotification object:nil]; } - (NSDictionary *)notificationUserInfo { return (self.stringLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.stringLabel.text} : nil); } - (void)positionHUD:(NSNotification*)notification { CGFloat keyboardHeight; double animationDuration; UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; // no transforms applied to window in iOS 8, but only if compiled with iOS 8 sdk as base sdk, otherwise system supports old rotation logic. BOOL ignoreOrientation = NO; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if ([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) { ignoreOrientation = YES; } #endif if(notification) { NSDictionary* keyboardInfo = [notification userInfo]; CGRect keyboardFrame = [[keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; animationDuration = [[keyboardInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { if(ignoreOrientation || UIInterfaceOrientationIsPortrait(orientation)) keyboardHeight = keyboardFrame.size.height; else keyboardHeight = keyboardFrame.size.width; } else keyboardHeight = 0; } else { keyboardHeight = self.visibleKeyboardHeight; } CGRect orientationFrame = self.window.bounds; CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; if(!ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation)) { float temp = orientationFrame.size.width; orientationFrame.size.width = orientationFrame.size.height; orientationFrame.size.height = temp; temp = statusBarFrame.size.width; statusBarFrame.size.width = statusBarFrame.size.height; statusBarFrame.size.height = temp; } CGFloat activeHeight = orientationFrame.size.height; if(keyboardHeight > 0) activeHeight += statusBarFrame.size.height*2; activeHeight -= keyboardHeight; CGFloat posY = floor(activeHeight*0.45); CGFloat posX = orientationFrame.size.width/2; CGPoint newCenter; CGFloat rotateAngle; if (ignoreOrientation) { rotateAngle = 0.0; newCenter = CGPointMake(posX, posY); } else { switch (orientation) { case UIInterfaceOrientationPortraitUpsideDown: rotateAngle = M_PI; newCenter = CGPointMake(posX, orientationFrame.size.height-posY); break; case UIInterfaceOrientationLandscapeLeft: rotateAngle = -M_PI/2.0f; newCenter = CGPointMake(posY, posX); break; case UIInterfaceOrientationLandscapeRight: rotateAngle = M_PI/2.0f; newCenter = CGPointMake(orientationFrame.size.height-posY, posX); break; default: // as UIInterfaceOrientationPortrait rotateAngle = 0.0; newCenter = CGPointMake(posX, posY); break; } } if(notification) { [UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ [self moveToPoint:newCenter rotateAngle:rotateAngle]; } completion:NULL]; } else { [self moveToPoint:newCenter rotateAngle:rotateAngle]; } } - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { self.hudView.transform = CGAffineTransformMakeRotation(angle); self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical); } - (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent *)event { [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification object:event]; } #pragma mark - Master show/dismiss methods - (void)showProgress:(float)progress status:(NSString*)string maskType:(SVProgressHUDMaskType)hudMaskType { if(!self.overlayView.superview){ NSEnumerator *frontToBackWindows = [[[UIApplication sharedApplication]windows]reverseObjectEnumerator]; for (UIWindow *window in frontToBackWindows) if (window.windowLevel == UIWindowLevelNormal) { [window addSubview:self.overlayView]; break; } } if(!self.superview) [self.overlayView addSubview:self]; self.fadeOutTimer = nil; self.imageView.hidden = YES; self.maskType = hudMaskType; self.progress = progress; self.stringLabel.text = string; [self updatePosition]; if(progress >= 0) { self.imageView.image = nil; self.imageView.hidden = NO; [self.indefiniteAnimatedView removeFromSuperview]; self.ringLayer.strokeEnd = progress; if(progress == 0) self.activityCount++; } else { self.activityCount++; [self cancelRingLayerAnimation]; [self.hudView addSubview:self.indefiniteAnimatedView]; } if(self.maskType != SVProgressHUDMaskTypeNone) { self.overlayView.userInteractionEnabled = YES; self.accessibilityLabel = string; self.isAccessibilityElement = YES; } else { self.overlayView.userInteractionEnabled = NO; self.hudView.accessibilityLabel = string; self.hudView.isAccessibilityElement = YES; } [self.overlayView setHidden:NO]; self.overlayView.backgroundColor = [UIColor clearColor]; [self positionHUD:nil]; if(self.alpha != 1) { NSDictionary *userInfo = [self notificationUserInfo]; [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification object:nil userInfo:userInfo]; [self registerNotifications]; self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3); if(self.isClear) { self.alpha = 1; self.hudView.alpha = 0; } [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^{ self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1/1.3, 1/1.3); if(self.isClear) // handle iOS 7 UIToolbar not answer well to hierarchy opacity change self.hudView.alpha = 1; else self.alpha = 1; } completion:^(BOOL finished){ [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification object:nil userInfo:userInfo]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, string); }]; [self setNeedsDisplay]; } } - (UIImage *)image:(UIImage *)image withTintColor:(UIColor *)color { CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale); CGContextRef c = UIGraphicsGetCurrentContext(); [image drawInRect:rect]; CGContextSetFillColorWithColor(c, [color CGColor]); CGContextSetBlendMode(c, kCGBlendModeSourceAtop); CGContextFillRect(c, rect); UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return tintedImage; } - (void)showImage:(UIImage *)image status:(NSString *)string duration:(NSTimeInterval)duration { self.progress = -1; [self cancelRingLayerAnimation]; if(![self.class isVisible]) [self.class show]; if ([self.imageView respondsToSelector:@selector(setTintColor:)]) { self.imageView.tintColor = SVProgressHUDForegroundColor; } else { image = [self image:image withTintColor:SVProgressHUDForegroundColor]; } self.imageView.image = image; self.imageView.hidden = NO; self.stringLabel.text = string; [self updatePosition]; [self.indefiniteAnimatedView removeFromSuperview]; if(self.maskType != SVProgressHUDMaskTypeNone) { self.accessibilityLabel = string; self.isAccessibilityElement = YES; } else { self.hudView.accessibilityLabel = string; self.hudView.isAccessibilityElement = YES; } UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, string); self.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:self selector:@selector(dismiss) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.fadeOutTimer forMode:NSRunLoopCommonModes]; } - (void)dismiss { NSDictionary *userInfo = [self notificationUserInfo]; [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification object:nil userInfo:userInfo]; self.activityCount = 0; [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationCurveEaseIn | UIViewAnimationOptionAllowUserInteraction animations:^{ self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 0.8, 0.8); if(self.isClear) // handle iOS 7 UIToolbar not answer well to hierarchy opacity change self.hudView.alpha = 0; else self.alpha = 0; } completion:^(BOOL finished){ if(self.alpha == 0 || self.hudView.alpha == 0) { self.alpha = 0; self.hudView.alpha = 0; [[NSNotificationCenter defaultCenter] removeObserver:self]; [self cancelRingLayerAnimation]; [_hudView removeFromSuperview]; _hudView = nil; [_overlayView removeFromSuperview]; _overlayView = nil; [_indefiniteAnimatedView removeFromSuperview]; _indefiniteAnimatedView = nil; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification object:nil userInfo:userInfo]; // Tell the rootViewController to update the StatusBar appearance UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController; if ([rootController respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { [rootController setNeedsStatusBarAppearanceUpdate]; } // uncomment to make sure UIWindow is gone from app.windows //NSLog(@"%@", [UIApplication sharedApplication].windows); //NSLog(@"keyWindow = %@", [UIApplication sharedApplication].keyWindow); } }]; } #pragma mark - Ring progress animation - (SVIndefiniteAnimatedView *)indefiniteAnimatedView { if (_indefiniteAnimatedView == nil) { _indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero]; _indefiniteAnimatedView.radius = self.stringLabel.text ? SVProgressHUDRingRadius : SVProgressHUDRingNoTextRadius; [_indefiniteAnimatedView sizeToFit]; } return _indefiniteAnimatedView; } - (CAShapeLayer *)ringLayer { if(!_ringLayer) { CGPoint center = CGPointMake(CGRectGetWidth(_hudView.frame)/2, CGRectGetHeight(_hudView.frame)/2); _ringLayer = [self createRingLayerWithCenter:center radius:SVProgressHUDRingRadius lineWidth:SVProgressHUDRingThickness color:SVProgressHUDForegroundColor]; [self.hudView.layer addSublayer:_ringLayer]; } return _ringLayer; } - (CAShapeLayer *)backgroundRingLayer { if(!_backgroundRingLayer) { CGPoint center = CGPointMake(CGRectGetWidth(_hudView.frame)/2, CGRectGetHeight(_hudView.frame)/2); _backgroundRingLayer = [self createRingLayerWithCenter:center radius:SVProgressHUDRingRadius lineWidth:SVProgressHUDRingThickness color:[SVProgressHUDForegroundColor colorWithAlphaComponent:0.1]]; _backgroundRingLayer.strokeEnd = 1; [self.hudView.layer addSublayer:_backgroundRingLayer]; } return _backgroundRingLayer; } - (void)cancelRingLayerAnimation { [CATransaction begin]; [CATransaction setDisableActions:YES]; [_hudView.layer removeAllAnimations]; _ringLayer.strokeEnd = 0.0f; if (_ringLayer.superlayer) { [_ringLayer removeFromSuperlayer]; } _ringLayer = nil; if (_backgroundRingLayer.superlayer) { [_backgroundRingLayer removeFromSuperlayer]; } _backgroundRingLayer = nil; [CATransaction commit]; } - (CAShapeLayer *)createRingLayerWithCenter:(CGPoint)center radius:(CGFloat)radius lineWidth:(CGFloat)lineWidth color:(UIColor *)color { UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius) radius:radius startAngle:-M_PI_2 endAngle:(M_PI + M_PI_2) clockwise:YES]; CAShapeLayer *slice = [CAShapeLayer layer]; slice.contentsScale = [[UIScreen mainScreen] scale]; slice.frame = CGRectMake(center.x-radius, center.y-radius, radius*2, radius*2); slice.fillColor = [UIColor clearColor].CGColor; slice.strokeColor = color.CGColor; slice.lineWidth = lineWidth; slice.lineCap = kCALineCapRound; slice.lineJoin = kCALineJoinBevel; slice.path = smoothedPath.CGPath; return slice; } #pragma mark - Utilities + (BOOL)isVisible { return ([self sharedView].alpha == 1); } #pragma mark - Getters - (NSTimeInterval)displayDurationForString:(NSString*)string { return MIN((float)string.length*0.06 + 0.3, 5.0); } - (BOOL)isClear { // used for iOS 7 return (self.maskType == SVProgressHUDMaskTypeClear || self.maskType == SVProgressHUDMaskTypeNone); } - (UIControl *)overlayView { if(!_overlayView) { _overlayView = [[UIControl alloc] initWithFrame:[UIScreen mainScreen].bounds]; _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _overlayView.backgroundColor = [UIColor clearColor]; [_overlayView addTarget:self action:@selector(overlayViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown]; } return _overlayView; } - (UIView *)hudView { if(!_hudView) { _hudView = [[UIView alloc] initWithFrame:CGRectZero]; _hudView.backgroundColor = SVProgressHUDBackgroundColor; _hudView.layer.cornerRadius = 14; _hudView.layer.masksToBounds = YES; _hudView.autoresizingMask = (UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin); if ([_hudView respondsToSelector:@selector(addMotionEffect:)]) { UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath: @"center.x" type: UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath: @"center.y" type: UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); [_hudView addMotionEffect: effectX]; [_hudView addMotionEffect: effectY]; } [self addSubview:_hudView]; } return _hudView; } - (UILabel *)stringLabel { if (_stringLabel == nil) { _stringLabel = [[UILabel alloc] initWithFrame:CGRectZero]; _stringLabel.backgroundColor = [UIColor clearColor]; _stringLabel.adjustsFontSizeToFitWidth = YES; _stringLabel.textAlignment = NSTextAlignmentCenter; _stringLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; _stringLabel.textColor = SVProgressHUDForegroundColor; _stringLabel.font = SVProgressHUDFont; _stringLabel.numberOfLines = 0; } if(!_stringLabel.superview) [self.hudView addSubview:_stringLabel]; return _stringLabel; } - (UIImageView *)imageView { if (_imageView == nil) _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 28, 28)]; if(!_imageView.superview) [self.hudView addSubview:_imageView]; return _imageView; } - (CGFloat)visibleKeyboardHeight { UIWindow *keyboardWindow = nil; for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) { if(![[testWindow class] isEqual:[UIWindow class]]) { keyboardWindow = testWindow; break; } } for (__strong UIView *possibleKeyboard in [keyboardWindow subviews]) { if([possibleKeyboard isKindOfClass:NSClassFromString(@"UIPeripheralHostView")] || [possibleKeyboard isKindOfClass:NSClassFromString(@"UIKeyboard")]) return possibleKeyboard.bounds.size.height; } return 0; } @end #pragma mark SVIndefiniteAnimatedView @interface SVIndefiniteAnimatedView () @property (nonatomic, strong) CAShapeLayer *indefiniteAnimatedLayer; @end @implementation SVIndefiniteAnimatedView - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.strokeThickness = SVProgressHUDRingThickness; self.radius = SVProgressHUDRingRadius; self.strokeColor = SVProgressHUDForegroundColor; } return self; } - (void)willMoveToSuperview:(UIView *)newSuperview { if (newSuperview != nil) { [self layoutAnimatedLayer]; } else { [_indefiniteAnimatedLayer removeFromSuperlayer]; _indefiniteAnimatedLayer = nil; } } - (void)layoutAnimatedLayer { CALayer *layer = self.indefiniteAnimatedLayer; [self.layer addSublayer:layer]; layer.position = CGPointMake(self.bounds.size.width - layer.bounds.size.width / 2, self.bounds.size.height - layer.bounds.size.height / 2); } - (CAShapeLayer*)indefiniteAnimatedLayer { if(!_indefiniteAnimatedLayer) { CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); CGRect rect = CGRectMake(0, 0, arcCenter.x*2, arcCenter.y*2); UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:M_PI*3/2 endAngle:M_PI/2+M_PI*5 clockwise:YES]; _indefiniteAnimatedLayer = [CAShapeLayer layer]; _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; _indefiniteAnimatedLayer.frame = rect; _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor; _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor; _indefiniteAnimatedLayer.lineWidth = self.strokeThickness; _indefiniteAnimatedLayer.lineCap = kCALineCapRound; _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel; _indefiniteAnimatedLayer.path = smoothedPath.CGPath; CALayer *maskLayer = [CALayer layer]; maskLayer.contents = (id)[[UIImage imageNamed:@"SVProgressHUD.bundle/angle-mask@2x.png"] CGImage]; maskLayer.frame = _indefiniteAnimatedLayer.bounds; _indefiniteAnimatedLayer.mask = maskLayer; NSTimeInterval animationDuration = 1; CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; animation.fromValue = 0; animation.toValue = [NSNumber numberWithFloat:M_PI*2]; animation.duration = animationDuration; animation.timingFunction = linearCurve; animation.removedOnCompletion = NO; animation.repeatCount = INFINITY; animation.fillMode = kCAFillModeForwards; animation.autoreverses = NO; [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"]; CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.duration = animationDuration; animationGroup.repeatCount = INFINITY; animationGroup.removedOnCompletion = NO; animationGroup.timingFunction = linearCurve; CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; strokeStartAnimation.fromValue = @0.015; strokeStartAnimation.toValue = @0.515; CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeEndAnimation.fromValue = @0.485; strokeEndAnimation.toValue = @0.985; animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation]; [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"]; } return _indefiniteAnimatedLayer; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; if (self.superview != nil) { [self layoutAnimatedLayer]; } } - (void)setRadius:(CGFloat)radius { _radius = radius; [_indefiniteAnimatedLayer removeFromSuperlayer]; _indefiniteAnimatedLayer = nil; [self layoutAnimatedLayer]; } - (void)setStrokeColor:(UIColor *)strokeColor { _strokeColor = strokeColor; _indefiniteAnimatedLayer.strokeColor = strokeColor.CGColor; } - (void)setStrokeThickness:(CGFloat)strokeThickness { _strokeThickness = strokeThickness; _indefiniteAnimatedLayer.lineWidth = _strokeThickness; } - (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); } @end sdk-10.11.0/examples/iOS/README.md000066400000000000000000000020731516266226600162400ustar00rootroot00000000000000# iOS Example app Xcode project for developing a MEGA app for iOS. Target OS: iOS 7.0 Demo application that allows: - Login into user account - List folders and files stored into the user account - Navigate along the folders structure (forward and backward) - Download files, upload photos, create folders - Delete, rename and export nodes - View contacts - Logout ## How to build and run the project: To build and run the project, follow theses steps: 1. Download or clone the whole SDK 2. Download the prebuilt third party dependencies from this link: https://mega.nz/#!gN9w0I7Q!-OPUWBoEnvXeZWkkC5oUho2MFm_49nwBwN6q07sxKII 3. Uncompress the content and move `include`and `lib`to the directory `sdk/bindings/ios/3rdparty` 4. Go to `sdk/examples/iOS` and open `MEGA.xcworkspace` 5. Make sure the `Demo` target is selected 6. Build and run the application If you want to build the third party dependencies by yourself: open a terminal in the directory `sdk/bindings/ios/3rdparty`. Run `sh build-all.sh` (Wait until the process ends, it will take some minutes ~20) sdk-10.11.0/examples/java/000077500000000000000000000000001516266226600152065ustar00rootroot00000000000000sdk-10.11.0/examples/java/.gitignore000066400000000000000000000000311516266226600171700ustar00rootroot00000000000000bin libs src/nz/mega/sdk sdk-10.11.0/examples/java/README.md000066400000000000000000000120471516266226600164710ustar00rootroot00000000000000# Java Example App **NOTE**: the Java Example App and the instructions in this page are out of date. The **example is phased out and it will be eventually retired from the repository**. If you want to keep it, feel free to make a fork. ## Introduction There are two examples provided for the Java bindings: * A simple GUI (Swing) to allow the user log into MEGA. If login is successfull, it retrieves the MEGA filesystem and lists files/folders in the root folder. * A simple console example that will demonstrate a simple "CRUD" (create, read, update delete) workflow, using this README file to upload and download. This example uses no GUI, and will display all interactions on the console using the Java provided logging framework. Below, you will find instructions to generate the required sources and library (prerequisites) for developing a MEGA app in Java. Afterwards, the Example application can be compiled and executed. ## Prerequisites in Linux: 1. Download, build and install the SDK ``` git clone https://github.com/meganz/sdk cd sdk sh autogen.sh ./configure --enable-java make ``` It might need to be necessary to pass the Java include directory for `jni.h` to the `configure` command, e. g. `--with-java-include-dir=/usr/lib/jvm/java-8-openjdk-amd64/include` 2. Copy or symlink the library `libmegajava.so` into the `libs` folder in your project. ``` mkdir examples/java/libs cp bindings/java/.libs/libmegajava.so examples/java/libs/ # Alternatively for linking: ln -s ../../../bindings/java/.libs/libmegajava.so examples/java/libs/. ``` 3. Copy or symlink the Java classes into the `src` folder in your project. ``` cp -a bindings/java/nz/mega/sdk examples/java/src/nz/mega/ # Alternatively for linking: ln -s ../../../../../bindings/java/nz/mega/sdk examples/java/src/nz/mega/. ``` ## Prerequisites in Windows: You have two options, using a prebuilt native library or building it by yourself. ### To use a prebuilt library (the easy way), follow these steps: 1. Download and extract the SDK to a folder in your computer ([link](https://github.com/meganz/sdk/archive/master.zip)). 2. Download the prebuilt library (`mega.dll`) along with its corresponding Java classes from [here](https://mega.nz/#!N0VmQRDD!HJc5-kUu_EDMwnAFUYueePuhW6pI0ytEff88ZWxHPxc). Commit: 92cb2b896cfe5ee9d3520cc05641ef248963f3de 3. Extract the content into `sdk/examples/java/`, keeping the folder structure. ### To build the library by yourself Instead of downloading the prebuilt library, you can build it directly from the sources. 1. Download and extract the SDK to a folder in your computer ([link](https://github.com/meganz/sdk/archive/master.zip)). 2. Follow the instructions in [this guide](https://github.com/meganz/sdk/blob/master/bindings/java/contrib/vs2015/README.md). 3. Copy the new file `mega.dll` from `sdk/bindings/java/contrib/vs2015/Release` and its dependencies into `sdk/examples/java/libs`. 4. Copy the Java classes from `sdk/bindings/java/nz/mega/sdk` into `sdk/examples/java/src/nz/mega/sdk`. ## How to run the application At this point, you should have all the required source code and the MEGA library in `src` and `libs` folders respectively. You can simply compile the application and execute it: ``` mkdir bin # For the Java Swing GUI example: javac -d bin -sourcepath src/ src/nz/mega/bindingsample/SwingExample.java java -cp bin nz.mega.bindingsample.SwingExample # For the Java console example (`-D` parameter is to configure logging): javac -d bin -sourcepath src/ src/nz/mega/bindingsample/CrudExample.java java -cp bin nz.mega.bindingsample.CrudExample ``` Notes: - If you have problems loading `mega.dll`, please add the folder that contains that library to your `PATH` environment variable: ``` set PATH=%PATH%;\examples\java\libs ``` - If setting the `PATH` environment variable doesn't solve the solve the problem loading the library, you may try moving `mega.dll` and all its dependencies to the working directory. - A 64 bit version of all required libraries is provided in the `x64` folder inside the `libs` folder of our package with prebuilt libraries. You can replace the 32-bit DLLs with the 64-bit ones to run the example app in a 64 bit environment. Since all prebuilt libraries are built with Visual Studio 2015, you will need to install the Microsoft Visual Studio 2015 Redistributable Package (32 bit or 64 bit) to use them. - The console example will not work from some IDEs (like Eclipse) as it requires the hidden password entry using the console directly. Or you might prefer to use Eclipse: 1. Create a new Java project. 2. Copy `src` and `libs` folders into the folder of your project. 3. Build and run the project in Eclipse. ## Notes The file `nz/mega/sdk/MegaApiJava.java` contains JavaDoc documentation. It is recommended to use the `MegaApiSwing` subclass instead because it makes some needed initialization and sends callbacks to the UI thread, but the documentation in `MegaApiJava` applies also for `MegaApiSwing`. sdk-10.11.0/examples/java/logging.properties000066400000000000000000000037301516266226600207550ustar00rootroot00000000000000# Properties file which configures the operation of the JDK # logging facility. # The system will look for this config file, first using # a System property specified at startup: # # >java -Djava.util.logging.config.file=myLoggingConfigFilePath # # If this property is not specified, then the config file is # retrieved from its default location at: # # JDK_HOME/jre/lib/logging.properties # Global logging properties. # ------------------------------------------ # The set of handlers to be loaded upon startup. # Comma-separated list of class names. # (? LogManager docs say no comma here, but JDK example has comma.) handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler # Default global logging level. # Loggers and Handlers may override this level .level=FINE # Loggers # ------------------------------------------ # Loggers are usually attached to packages. # Here, the level for each package is specified. # The global level is used by default, so levels # specified here simply act as an override. #myapp.ui.level=ALL #myapp.business.level=CONFIG #myapp.data.level=SEVERE # Handlers # ----------------------------------------- # --- ConsoleHandler --- # Override of global logging level java.util.logging.ConsoleHandler.level=FINE java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter # --- FileHandler --- # Override of global logging level java.util.logging.FileHandler.level=ALL # Naming style for the output file: # (The output file is placed in the directory # defined by the "user.home" System property.) java.util.logging.FileHandler.pattern=%h/java%u.log # Limiting size of output file in bytes: java.util.logging.FileHandler.limit=50000 # Number of output files to cycle through, by appending an # integer to the base file name: java.util.logging.FileHandler.count=1 # Style of output (Simple or XML): java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%4$s [%1$tc]: %5$s%n sdk-10.11.0/examples/java/src/000077500000000000000000000000001516266226600157755ustar00rootroot00000000000000sdk-10.11.0/examples/java/src/nz/000077500000000000000000000000001516266226600164245ustar00rootroot00000000000000sdk-10.11.0/examples/java/src/nz/mega/000077500000000000000000000000001516266226600173355ustar00rootroot00000000000000sdk-10.11.0/examples/java/src/nz/mega/bindingsample/000077500000000000000000000000001516266226600221515ustar00rootroot00000000000000sdk-10.11.0/examples/java/src/nz/mega/bindingsample/CrudExample.java000066400000000000000000000434641516266226600252400ustar00rootroot00000000000000/* * CrudExample.java * * This file is part of the Mega SDK Java bindings example code. * * Created: 2015-07-09 Guy K. Kloss * Changed: * * (c) Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ package nz.mega.bindingsample; import java.io.Console; import java.util.ArrayList; import java.util.logging.Logger; import nz.mega.sdk.MegaAccountDetails; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaGlobalListenerInterface; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaRequestListenerInterface; import nz.mega.sdk.MegaTransfer; import nz.mega.sdk.MegaTransferListenerInterface; import nz.mega.sdk.MegaUser; import nz.mega.sdk.MegaContactRequest; /** * A simple example implementation that demonstrates a CRUD (create, read, * update, delete) scenario for file storage. * * Note: To "force" the fully asynchronous Mega API into a mocked synchronous * mode of operation, a condition variable was used to signal to and wait * for synchronisation points according to this tutorial: * http://tutorials.jenkov.com/java-concurrency/thread-signaling.html * * @author Guy K. Kloss */ public class CrudExample implements MegaRequestListenerInterface, MegaTransferListenerInterface, MegaGlobalListenerInterface { /** Reference to the Mega API object. */ static MegaApiJava megaApi = null; /** * Mega SDK application key. * Generate one for free here: https://mega.io/developers */ static final String APP_KEY = "YYJwAIRI"; /** Condition variable to signal asynchronous continuation. */ private Object continueEvent = null; /** Signal for condition variable to indicate signalling. */ private boolean wasSignalled = false; /** Root node of the logged in account. */ private MegaNode rootNode = null; /** Java logging utility. */ private static final Logger log = Logger.getLogger(CrudExample.class.getName()); /** Constructor. */ public CrudExample() { // Make a new condition variable to signal continuation. this.continueEvent = new Object(); // Get reference to Mega API. if (megaApi == null) { String path = System.getProperty("user.dir"); megaApi = new MegaApiJava(CrudExample.APP_KEY, path); } } public static void main(String[] args) { CrudExample myListener = new CrudExample(); // Execute a sequence of jobs on the Mega API. long startTime = System.currentTimeMillis(); // Log in. log.info("*** start: login ***"); Console console = System.console(); String email = console.readLine("Email: "); char[] passwordArray = console.readPassword("Password: "); String password = new String(passwordArray); synchronized(myListener.continueEvent) { megaApi.login(email, password, myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("login interrupted: " + e.toString()); } } myListener.wasSignalled = false; } // Set our current working directory. MegaNode cwd = myListener.rootNode; log.info("*** done: login ***"); // Who am I. log.info("*** start: whoami ***"); log.info("My email: " + megaApi.getMyEmail()); synchronized(myListener.continueEvent) { megaApi.getAccountDetails(myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("whoami interrupted: " + e.toString()); } } myListener.wasSignalled = false; } log.info("*** done: whoami ***"); // Make a directory. log.info("*** start: mkdir ***"); MegaNode check = megaApi.getNodeByPath("sandbox", cwd); if (check == null) { synchronized(myListener.continueEvent) { megaApi.createFolder("sandbox", cwd, myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("mkdir interrupted: " + e.toString()); } } myListener.wasSignalled = false; } } else { log.info("Path already exists: " + megaApi.getNodePath(check)); } log.info("*** done: mkdir ***"); // Now go and play in the sandbox. log.info("*** start: cd ***"); MegaNode node = megaApi.getNodeByPath("sandbox", cwd); if (node == null) { log.warning("No such file or directory: sandbox"); } if (node.getType() == MegaNode.TYPE_FOLDER) { cwd = node; } else { log.warning("Not a directory: sandbox"); } log.info("*** done: cd ***"); // Upload a file (create). log.info("*** start: upload ***"); synchronized(myListener.continueEvent) { megaApi.startUpload("README.md" , cwd /*parent node*/ , null /*filename*/ , 0 /*mtime*/ , null /*appData*/ , false /*isSourceTemporary*/ , false /*startFirst*/ , null /*cancelToken*/ , myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("upload interrupted: " + e.toString()); } } myListener.wasSignalled = false; } log.info("*** done: upload ***"); // Download a file (read). log.info("*** start: download ***"); node = megaApi.getNodeByPath("README.md", cwd); if (node != null) { synchronized(myListener.continueEvent) { megaApi.startDownload(node , "README_returned.md" /*local path*/ , null /*custom name*/ , null /*app data*/ , false /*start first*/ , null /*cancel token*/ , MegaTransfer.COLLISION_CHECK_FINGERPRINT /* collisionCheck*/ , MegaTransfer.COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution*/ , myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("download interrupted: " + e.toString()); } } myListener.wasSignalled = false; } } else { log.warning("Node not found: README.md"); } log.info("*** done: download ***"); // Change a file (update). // Note: A new upload won't overwrite, but create a new node with same // name! log.info("*** start: update ***"); MegaNode oldNode = megaApi.getNodeByPath("README.md", cwd); synchronized(myListener.continueEvent) { megaApi.startUpload("README.md" , cwd /*parent node*/ , null /*filename*/ , 0 /*mtime*/ , null /*appData*/ , false /*isSourceTemporary*/ , false /*startFirst*/ , null /*cancelToken*/ , myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("upload interrupted: " + e.toString()); } } myListener.wasSignalled = false; } if (oldNode != null) { // Remove the old node with the same name. synchronized(myListener.continueEvent) { megaApi.remove(oldNode, myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("remove interrupted: " + e.toString()); } } myListener.wasSignalled = false; } } else { log.info("No old file node needs removing"); } log.info("*** done: update ***"); // Delete a file. log.info("*** start: delete ***"); node = megaApi.getNodeByPath("README.md", cwd); if (node != null) { synchronized(myListener.continueEvent) { megaApi.remove(node, myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("remove interrupted: " + e.toString()); } } myListener.wasSignalled = false; } } else { log.warning("Node not found: README.md"); } log.info("*** done: delete ***"); // Logout. log.info("*** start: logout ***"); synchronized(myListener.continueEvent) { megaApi.logout(myListener); while (!myListener.wasSignalled) { try { myListener.continueEvent.wait(); } catch(InterruptedException e) { log.warning("remove interrupted: " + e.toString()); } } myListener.wasSignalled = false; } myListener.rootNode = null; log.info("*** done: logout ***"); log.info("Total time taken: " + ((System.currentTimeMillis() - startTime) / 1000) + " s"); } // Implementation of listener methods. /** * {@inheritDoc} * * @see nz.mega.sdk.MegaRequestListenerInterface#onRequestStart(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaRequest) */ @Override public void onRequestStart(MegaApiJava api, MegaRequest request) { log.fine("Request start (" + request.getRequestString() + ")"); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaRequestListenerInterface#onRequestUpdate(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaRequest) */ @Override public void onRequestUpdate(MegaApiJava api, MegaRequest request) { log.fine("Request update (" + request.getRequestString() + ")"); if (request.getType() == MegaRequest.TYPE_FETCH_NODES) { if (request.getTotalBytes() > 0) { double progressValue = 100.0 * request.getTransferredBytes() / request.getTotalBytes(); if ((progressValue > 99) || (progressValue < 0)) { progressValue = 100; } log.fine("Preparing nodes ... " + String.valueOf((int)progressValue) + "%"); } } } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaRequestListenerInterface#onRequestFinish(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaRequest, nz.mega.sdk.MegaError) */ @Override public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { log.fine("Request finish (" + request.getRequestString() + "); Result: " + e.toString()); int requestType = request.getType(); if (requestType == MegaRequest.TYPE_LOGIN) { if (e.getErrorCode() != MegaError.API_OK) { String errorMessage = e.getErrorString(); if (e.getErrorCode() == MegaError.API_ENOENT) { errorMessage = "Login error: Incorrect email or password!"; } log.severe(errorMessage); return; } megaApi.fetchNodes(this); } else if (requestType == MegaRequest.TYPE_FETCH_NODES) { this.rootNode = api.getRootNode(); } else if (requestType == MegaRequest.TYPE_ACCOUNT_DETAILS) { MegaAccountDetails accountDetails = request.getMegaAccountDetails(); log.info("Account details received"); log.info("Storage: " + accountDetails.getStorageUsed() + " of " + accountDetails.getStorageMax() + " (" + String.valueOf((int)(100.0 * accountDetails.getStorageUsed() / accountDetails.getStorageMax())) + " %)"); log.info("Pro level: " + accountDetails.getProLevel()); } // Send the continue event so our synchronised code continues. if (requestType != MegaRequest.TYPE_LOGIN) { synchronized(this.continueEvent){ this.wasSignalled = true; this.continueEvent.notify(); } } } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaRequestListenerInterface#onRequestTemporaryError(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaRequest, nz.mega.sdk.MegaError) */ @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { log.warning("Request temporary error (" + request.getRequestString() + "); Error: " + e.toString()); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaTransferListenerInterface#onTransferStart(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaTransfer) */ @Override public void onTransferStart(MegaApiJava api, MegaTransfer transfer) { log.fine("Transfer start: " + transfer.getFileName()); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaTransferListenerInterface#onTransferFinish(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaTransfer, nz.mega.sdk.MegaError) */ @Override public void onTransferFinish(MegaApiJava api, MegaTransfer transfer, MegaError e) { log.fine("Transfer finished (" + transfer.getFileName() + "); Result: " + e.toString() + " "); // Signal the other thread we're done. synchronized(this.continueEvent){ this.wasSignalled = true; this.continueEvent.notify(); } } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaTransferListenerInterface#onTransferUpdate(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaTransfer) */ @Override public void onTransferUpdate(MegaApiJava api, MegaTransfer transfer) { log.fine("Transfer finished (" + transfer.getFileName() + "): " + transfer.getSpeed() + " B/s "); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaTransferListenerInterface#onTransferTemporaryError(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaTransfer, nz.mega.sdk.MegaError) */ @Override public void onTransferTemporaryError(MegaApiJava api, MegaTransfer transfer, MegaError e) { log.warning("Transfer temporary error (" + transfer.getFileName() + "); Error: " + e.toString()); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaTransferListenerInterface#onTransferData(nz.mega.sdk.MegaApiJava, nz.mega.sdk.MegaTransfer, byte[]) */ @Override public boolean onTransferData(MegaApiJava api, MegaTransfer transfer, byte[] buffer) { log.fine("Got transfer data."); return true; } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaGlobalListenerInterface#onUsersUpdate(nz.mega.sdk.MegaApiJava, java.util.ArrayList) */ @Override public void onUsersUpdate(MegaApiJava api, ArrayList users) { // TODO Auto-generated method stub } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaGlobalListenerInterface#onNodesUpdate(nz.mega.sdk.MegaApiJava, java.util.ArrayList) */ @Override public void onNodesUpdate(MegaApiJava api, ArrayList nodes) { if (nodes != null) { log.info("Nodes updated (" + nodes.size() + ")"); } // Signal the other thread we're done. synchronized(this.continueEvent){ this.wasSignalled = true; this.continueEvent.notify(); } } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaGlobalListenerInterface#onContactRequestsUpdate(nz.mega.sdk.MegaApiJava, java.util.ArrayList) */ @Override public void onContactRequestsUpdate(MegaApiJava api, ArrayList contactRequests) { if (contactRequests != null && contactRequests.size() != 0) { log.info("Contact request received (" + contactRequests.size() + ")"); } } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaGlobalListenerInterface#onAccountUpdate(nz.mega.sdk.MegaApiJava) */ @Override public void onAccountUpdate(MegaApiJava api) { log.info("Account updated"); } /** * {@inheritDoc} * * @see nz.mega.sdk.MegaGlobalListenerInterface#onReloadNeeded(nz.mega.sdk.MegaApiJava) */ @Override public void onReloadNeeded(MegaApiJava api) { // TODO Auto-generated method stub } } sdk-10.11.0/examples/java/src/nz/mega/bindingsample/SwingExample.java000066400000000000000000000305211516266226600254200ustar00rootroot00000000000000/* * SwingExample.java * * This file is part of the Mega SDK Java bindings example code. * * Created: 2015-04-30 Sergio Hernández Sánchez * Changed: * * (c) Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ package nz.mega.bindingsample; import java.util.ArrayList; import java.util.Locale; import java.util.regex.Pattern; import java.awt.HeadlessException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JScrollPane; import javax.swing.JTextField; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaApiSwing; import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaLoggerInterface; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaRequestListenerInterface; /** * A simple Java Swing UI example implementation that demonstrates accessing * Mega's file storage. * * @author Sergio Hernández Sánchez */ public class SwingExample extends JFrame implements MegaRequestListenerInterface, MegaLoggerInterface { private static final long serialVersionUID = 1L; /** Reference to the Mega API object. */ static MegaApiSwing megaApi = null; /** * Mega SDK application key. * Generate one for free here: https://mega.io/developers */ static final String APP_KEY = "YYJwAIRI"; /** User agent string used for HTTP requests to the Mega API. */ static final String USER_AGENT = "MEGA Java Sample Demo SDK"; // UI elements. static JPanel panel; static JTextField loginText; static JTextField passwordText; static JButton loginButton; static JScrollPane listFiles; static DefaultListModel listModel; static JLabel statusLabel; // Some externalised strings. private static final String STR_APP_TITLE = "Java Bindings Example"; private static final String STR_EMAIL_TEXT = "Email:"; private static final String STR_PWD_TEXT = "Password:"; private static final String STR_LOGIN_TEXT = "Login"; private static final String STR_LOGOUT_TEXT = "Logout"; private static final String STR_INITIAL_STATUS = "Please, enter your login details"; private static final String STR_ERROR_ENTER_EMAIL = "Please, enter your email address"; private static final String STR_ERROR_INVALID_EMAIL = "Invalid email address"; private static final String STR_ERROR_ENTER_PWD = "Please, enter your password"; private static final String STR_ERROR_INCORRECT_EMAIL_OR_PWD = "Incorrect email or password"; private static final String STR_LOGGING_IN = "Logging in..."; private static final String STR_FETCHING_NODES = "Fetching nodes..."; private static final String STR_PREPARING_NODES = "Preparing nodes..."; public SwingExample() throws HeadlessException { super(STR_APP_TITLE); initializeMegaApi(); initializeGUI(); } /** * Set logger and get reference to MEGA API */ private void initializeMegaApi() { MegaApiSwing.addLoggerObject(this); MegaApiSwing.setLogLevel(MegaApiSwing.LOG_LEVEL_MAX); if (megaApi == null) { String path = System.getProperty("user.dir"); megaApi = new MegaApiSwing(SwingExample.APP_KEY, SwingExample.USER_AGENT, path); } } /** * Create GUI components and configure them */ private void initializeGUI() { // Create and set up the window. this.setSize(270, 160); this.setResizable(false); this.setLocationByPlatform(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); panel = new JPanel(); this.add(panel); panel.setLayout(null); JLabel loginLabel = new JLabel(STR_EMAIL_TEXT); loginLabel.setBounds(10, 10, 80, 25); panel.add(loginLabel); loginText = new JTextField(20); loginText.setBounds(100, 10, 160, 25); panel.add(loginText); JLabel passwordLabel = new JLabel(STR_PWD_TEXT); passwordLabel.setBounds(10, 40, 80, 25); panel.add(passwordLabel); passwordText = new JPasswordField(20); passwordText.setBounds(100, 40, 160, 25); panel.add(passwordText); loginButton = new JButton(STR_LOGIN_TEXT); loginButton.setBounds(160, 70, 100, 25); panel.add(loginButton); loginButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JButton loginButton = (JButton) e.getSource(); if (loginButton.getText().compareTo(STR_LOGIN_TEXT) == 0) { initLogin(); } else if (loginButton.getText().compareTo(STR_LOGOUT_TEXT) == 0) { initLogout(); } } }); getRootPane().setDefaultButton(loginButton); listModel = new DefaultListModel(); JList list = new JList(listModel); listFiles = new JScrollPane(list); listFiles.setBounds(10, 100, this.getWidth() - 20, 200); // panel.add(listFiles); // Do not add it yet statusLabel = new JLabel(); statusLabel.setBounds(10, 110, this.getWidth() - 20, 15); setStatus(STR_INITIAL_STATUS); panel.add(statusLabel); this.setVisible(true); } /** * Change the design of Main window when a session is opened. * - Prevent user to modify 'email' and 'password' * - Rename button to 'Logout' * - Prevent user to click on button 'login' * - Show the component where nodes are listed */ private void setLoggedInMode() { loginButton.setText(STR_LOGOUT_TEXT); loginButton.setEnabled(true); // It might be disabled if called during a login process loginText.setEnabled(false); passwordText.setEnabled(false); setSize(270, 360); statusLabel.setBounds(10, 310, getWidth() - 20, 15); panel.add(listFiles); } /** * Change the design of Main window when a session is closed: * - Allow user to modify 'email' and 'password' * - Rename button to 'Login' * - Allow user to click on button 'login' * - Hide the component where nodes are listed */ private void setLoggedOutMode() { loginButton.setText(STR_LOGIN_TEXT); enableUserInteraction(true); setSize(270, 160); statusLabel.setBounds(10, 110, getWidth() - 20, 15); panel.remove(listFiles); } private void enableUserInteraction(boolean b) { loginText.setEnabled(b); passwordText.setEnabled(b); loginButton.setEnabled(b); } private void initLogin() { if (!validateForm()) { return; } enableUserInteraction(false); String email = loginText.getText().toString().toLowerCase(Locale.ENGLISH).trim(); String password = passwordText.getText().toString(); megaApi.login(email, password, this); } private boolean validateForm() { String emailError = getEmailError(); String passwordError = getPasswordError(); if (emailError != null) { JOptionPane.showMessageDialog(null, emailError); loginText.requestFocus(); return false; } else if (passwordError != null) { JOptionPane.showMessageDialog(null, passwordError); passwordText.requestFocus(); return false; } return true; } /** * Validate email: not empty and valid format */ private String getEmailError() { String value = loginText.getText(); if (value.length() == 0) { return STR_ERROR_ENTER_EMAIL; } if (!rfc2822.matcher(value).matches()) { return STR_ERROR_INVALID_EMAIL; } return null; } final static Pattern rfc2822 = Pattern .compile("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"); /** * Validate password: not empty */ private String getPasswordError() { String value = passwordText.getText(); if (value.length() == 0) { return STR_ERROR_ENTER_PWD; } return null; } protected void initLogout() { megaApi.logout(this); setLoggedOutMode(); setStatus(STR_LOGOUT_TEXT); } public static void main(String[] args) { // Schedule a job for the event-dispatching thread javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { new SwingExample(); } }); } @Override public void onRequestStart(MegaApiJava api, MegaRequest request) { log("onRequestStart: " + request.getRequestString()); if (request.getType() == MegaRequest.TYPE_LOGIN) { setStatus(STR_LOGGING_IN); } else if (request.getType() == MegaRequest.TYPE_FETCH_NODES) { setStatus(STR_FETCHING_NODES); } } @Override public void onRequestUpdate(MegaApiJava api, MegaRequest request) { log("onRequestUpdate: " + request.getRequestString()); if (request.getType() == MegaRequest.TYPE_FETCH_NODES) { if (request.getTotalBytes() > 0) { double progressValue = 100.0 * request.getTransferredBytes() / request.getTotalBytes(); if ((progressValue > 99) || (progressValue < 0)) { progressValue = 100; } log("progressValue = " + (int) progressValue); setStatus(STR_PREPARING_NODES + String.valueOf((int) progressValue) + "%"); } } } @Override public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { log("onRequestFinish: " + request.getRequestString()); if (request.getType() == MegaRequest.TYPE_LOGIN) { if (e.getErrorCode() == MegaError.API_OK) { megaApi.fetchNodes(this); } else { String errorMessage = e.getErrorString(); if (e.getErrorCode() == MegaError.API_ENOENT) { errorMessage = STR_ERROR_INCORRECT_EMAIL_OR_PWD; } JOptionPane.showMessageDialog(null, errorMessage); setStatus(errorMessage); // Enable user to change the credentials and hit 'login' again enableUserInteraction(true); } } else if (request.getType() == MegaRequest.TYPE_FETCH_NODES) { if (e.getErrorCode() != MegaError.API_OK) { JOptionPane.showMessageDialog(null, e.getErrorString()); setStatus(e.getErrorString()); // TODO: investigate errors from Request==TYPE_FETCH_NODES // and adapt the behaviour of GUI components below enableUserInteraction(true); } else { MegaNode parentNode = megaApi.getRootNode(); ArrayList nodes = megaApi.getChildren(parentNode); listModel.clear(); MegaNode temp; for (int i = 0; i < nodes.size(); i++) { temp = nodes.get(i); listModel.addElement(temp.getName()); } setLoggedInMode(); setStatus("Done"); } } } @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { log("onRequestTemporaryError: " + request.getRequestString()); } public static void log(String message) { MegaApiSwing.log(MegaApiSwing.LOG_LEVEL_INFO, message, "MainActivity"); } @Override public void log(String time, int loglevel, String source, String message) { System.out.println("[" + time + "] " + message + " (Source: " + source + ")"); } private void setStatus(String status) { statusLabel.setText(status); } } sdk-10.11.0/examples/megacli/000077500000000000000000000000001516266226600156665ustar00rootroot00000000000000sdk-10.11.0/examples/megacli/CMakeLists.txt000066400000000000000000000020441516266226600204260ustar00rootroot00000000000000 add_executable(megacli megacli.h megacli.cpp ) # Link with SDKlib. # megacli does not use the intermediate layer. target_link_libraries(megacli PRIVATE MEGA::SDKlib) # Adjust compilation flags for warnings and errors target_platform_compile_options( TARGET megacli WINDOWS /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET megacli WINDOWS /WX UNIX $<$: -Werror> ) endif() # Starting on Xcode 15: https://gitlab.kitware.com/cmake/cmake/-/issues/25297 if(APPLE AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15.0") target_link_options(megacli PRIVATE LINKER:-no_warn_duplicate_libraries) endif() if(TARGET gfxworker) add_custom_command( TARGET megacli POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMENT "Copying gfxworker for megacli." ) endif() sdk-10.11.0/examples/megacli/megacli.cpp000066400000000000000000016113101516266226600177760ustar00rootroot00000000000000/** * @file examples/megaclient.cpp * @brief Sample application, interactive GNU Readline CLI * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "megacli.h" #include "mega.h" #include "mega/arguments.h" #include "mega/filesystem.h" #include "mega/gfx.h" #include "mega/pwm_file_parser.h" #include "mega/testhooks.h" #include "mega/tlv.h" #include "mega/totp.h" #include "mega/udp_socket.h" #include "mega/user_attribute.h" #include "mega/utils_optional.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) && defined(_DEBUG) // so we can delete a secret internal CrytpoPP singleton #include #endif #define USE_VARARGS #define PREFER_STDARG #ifndef NO_READLINE #include #include #include #endif #include #ifdef USE_FREEIMAGE #include "mega/gfx/freeimage.h" #endif #ifdef ENABLE_ISOLATED_GFX #include "mega/gfx/isolatedprocess.h" #endif #ifdef WIN32 #include #endif namespace ac = ::mega::autocomplete; namespace fs = std::filesystem; #include // FUSE #include #include #include #include // File Service. #include #include #include #include #include #include #include using namespace mega; using std::cout; using std::cerr; using std::endl; using std::flush; using std::ifstream; using std::ofstream; using std::setw; using std::hex; using std::dec; MegaClient* client; MegaClient* clientFolder; std::string megacliUserAgent{"megacli/" TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING( MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION)}; int gNextClientTag = 1; std::map> gOnPutNodeTag; bool gVerboseMode = false; // new account signup e-mail address and name static string signupemail, signupname; // signup code being confirmed static string signupcode; // signup password challenge and encrypted master key static byte signuppwchallenge[SymmCipher::KEYLENGTH]; // password recovery e-mail address and code being confirmed static string recoveryemail, recoverycode; // password recovery code requires MK or not static bool hasMasterKey; // master key for password recovery static byte masterkey[SymmCipher::KEYLENGTH]; // change email link to be confirmed static string changeemail, changecode; // public link information static string publiclink; // local console Console* console; // loading progress of lengthy API responses int responseprogress = -1; //2FA pin attempts int attempts = 0; //Ephemeral account plus plus std::string ephemeralFirstname; std::string ephemeralLastName; // external drive id, used for name filtering static string allExtDrives = "*"; string b64driveid; void uploadLocalPath(nodetype_t type, std::string name, const LocalPath& localname, Node* parent, const std::string& targetuser, TransferDbCommitter& committer, int& total, bool recursive, VersioningOption vo, std::function(LocalPath)> onCompletedGenerator, bool noRetries, bool allowDuplicateVersions); static std::string USAGE = R"( Mega command line Usage: megacli [OPTION...] -h Show help -v Verbose -c=arg Client type. default|vpn|password_manager (default: default))" #if defined(ENABLE_ISOLATED_GFX) R"( -e=arg Use the isolated gfx processor. This gives executable binary path -n=arg Endpoint name (default: mega_gfxworker_megacli) )" #endif ; struct Config { std::string executable; std::string endpointName; std::string clientType; static Config fromArguments(const Arguments& arguments); }; Config Config::fromArguments(const Arguments& arguments) { Config config; #if defined(ENABLE_ISOLATED_GFX) // executable config.executable = arguments.getValue("-e", ""); FSACCESS_CLASS fsAccess; if (!config.executable.empty() && !fsAccess.fileExistsAt(LocalPath::fromAbsolutePath(config.executable))) { throw std::runtime_error("Couldn't find Executable: " + config.executable); } // endpoint name config.endpointName = arguments.getValue("-n", "mega_gfxworker_megacli"); #endif config.clientType = arguments.getValue("-c", "default"); return config; } static std::unique_ptr createGfxProvider([[maybe_unused]] const Config& config) { #if defined(ENABLE_ISOLATED_GFX) GfxIsolatedProcess::Params params{config.endpointName, config.executable}; if (auto provider = GfxProviderIsolatedProcess::create(params)) { return provider; } #endif return IGfxProvider::createInternalGfxProvider(); } #ifdef ENABLE_SYNC // converts the given sync configuration to a string std::string syncConfigToString(const SyncConfig& config) { std::string description(Base64Str(config.mBackupId)); if (config.getType() == SyncConfig::TYPE_TWOWAY) { description.append(" TWOWAY"); } else if (config.getType() == SyncConfig::TYPE_UP) { description.append(" UP"); } else if (config.getType() == SyncConfig::TYPE_DOWN) { description.append(" DOWN"); } return description; } #endif static const char* getAccessLevelStr(int access) { switch(access) { case ACCESS_UNKNOWN: return "unkown"; case RDONLY: return "read-only"; case RDWR: return "read/write"; case FULL: return "full access"; case OWNER: return "owner access"; case OWNERPRELOGIN: return "owner (prelogin) access"; default: return "UNDEFINED"; } } const char* errorstring(error e) { switch (e) { case API_OK: return "No error"; case API_EINTERNAL: return "Internal error"; case API_EARGS: return "Invalid argument"; case API_EAGAIN: return "Request failed, retrying"; case API_ERATELIMIT: return "Rate limit exceeded"; case API_EFAILED: return "Transfer failed"; case API_ETOOMANY: return "Too many concurrent connections or transfers"; case API_ERANGE: return "Out of range"; case API_EEXPIRED: return "Expired"; case API_ENOENT: return "Not found"; case API_ECIRCULAR: return "Circular linkage detected"; case API_EACCESS: return "Access denied"; case API_EEXIST: return "Already exists"; case API_EINCOMPLETE: return "Incomplete"; case API_EKEY: return "Invalid key/integrity check failed"; case API_ESID: return "Bad session ID"; case API_EBLOCKED: return "Blocked"; case API_EOVERQUOTA: return "Over quota"; case API_ETEMPUNAVAIL: return "Temporarily not available"; case API_ETOOMANYCONNECTIONS: return "Connection overflow"; case API_EWRITE: return "Write error"; case API_EREAD: return "Read error"; case API_EAPPKEY: return "Invalid application key"; case API_EGOINGOVERQUOTA: return "Not enough quota"; case API_EROLLEDBACK: return "Strongly-ordered request rolled back"; case API_EMFAREQUIRED: return "Multi-factor authentication required"; case API_EMASTERONLY: return "Access denied for users"; case API_EBUSINESSPASTDUE: return "Business account has expired"; case API_EPAYWALL: return "Over Disk Quota Paywall"; case API_ESUBUSERKEYMISSING: return "A business error where a subuser has not yet encrypted their master key for " "the admin user and tries to perform a disallowed command (currently u and p)"; case LOCAL_ENOSPC: return "Insufficient disk space"; default: return "Unknown error"; } } string verboseErrorString(error e) { return (string("Error message: ") + errorstring(e) + string(" (error code ") + std::to_string(e) + ")"); } struct ConsoleLock { static std::recursive_mutex outputlock; std::ostream& os; bool locking = false; inline ConsoleLock(std::ostream& o) : os(o), locking(true) { outputlock.lock(); } ConsoleLock(ConsoleLock&& o) : os(o.os), locking(o.locking) { o.locking = false; } ~ConsoleLock() { if (locking) { outputlock.unlock(); } } template std::ostream& operator<<(T&& arg) { return os << std::forward(arg); } }; std::recursive_mutex ConsoleLock::outputlock; ConsoleLock conlock(std::ostream& o) { // Returns a temporary object that has locked a mutex. The temporary's destructor will unlock the object. // So you can get multithreaded non-interleaved console output with just conlock(cout) << "some " << "strings " << endl; // (as the temporary's destructor will run at the end of the outermost enclosing expression). // Or, move-assign the temporary to an lvalue to control when the destructor runs (to lock output over several statements). // Be careful not to have cout locked across a g_megaApi member function call, as any callbacks that also log could then deadlock. return ConsoleLock(o); } static error startxfer(TransferDbCommitter& committer, unique_ptr file, const string& path, int tag) { error result = API_OK; if (client->startxfer(GET, file.get(), committer, false, false, false, NoVersioning, &result, tag)) { file->appxfer_it = appxferq[GET].insert(appxferq[GET].end(), file.get()); file.release(); } else { conlock(cout) << "Unable to download file: " << path << " -> " << file->getLocalname().toPath(false) << ": " << errorstring(result) << endl; } return result; } static error startxfer(TransferDbCommitter& committer, unique_ptr file, const Node& node, int tag) { return startxfer(committer, std::move(file), node.displaypath(), tag); } AppFile::AppFile() { static int nextseqno; seqno = ++nextseqno; } // transfer start void AppFilePut::start() { } void AppFileGet::start() { } // transfer completion void AppFileGet::completed(Transfer*, putsource_t /*source*/) { if (onCompleted) onCompleted(); // (at this time, the file has already been placed in the final location) delete this; } // transfer terminated - too many failures, or unrecoverable failure, or cancelled void AppFileGet::terminated(error) { delete this; } void AppFilePut::completed(Transfer* t, putsource_t source) { // perform standard completion (place node in user filesystem etc.) //File::completed(t, source); // standard completion except we want onCompleted to run at the end of putnodes: assert(!transfer || t == transfer); assert(source == PUTNODES_APP); // derived class for sync doesn't use this code path assert(t->type == PUT); auto onCompleted_foward = onCompleted; sendPutnodesOfUpload( t->client, t->uploadhandle, "", *t->ultoken, t->filekey, source, NodeHandle(), [onCompleted_foward](const Error& e, targettype_t, vector&, bool /*targetOverride*/, int /*tag*/, const map& /*fileHandles*/) { if (e) { cout << "Putnodes error is breaking upload/download cycle: " << e << endl; } else if (onCompleted_foward) onCompleted_foward(); }, nullptr, false); delete this; } // transfer terminated - too many failures, or unrecoverable failure, or cancelled void AppFilePut::terminated(error) { delete this; } AppFileGet::~AppFileGet() { if (appxfer_it != appxferq[GET].end()) appxferq[GET].erase(appxfer_it); } AppFilePut::~AppFilePut() { if (appxfer_it != appxferq[PUT].end()) appxferq[PUT].erase(appxfer_it); } void AppFilePut::displayname(string* dname) { *dname = getLocalname().toName(*transfer->client->fsaccess); } // transfer progress callback void AppFile::progress() { } static void displaytransferdetails(Transfer* t, const string& action) { string name; for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++) { if (it != t->files.begin()) { cout << "/"; } (*it)->displayname(&name); cout << name; } cout << ": " << (t->type == GET ? "Incoming" : "Outgoing") << " file transfer " << action << ": " << t->localfilename.toPath(false); } // a new transfer was added void DemoApp::transfer_added(Transfer* /*t*/) { } // a queued transfer was removed void DemoApp::transfer_removed(Transfer* t) { displaytransferdetails(t, "removed\n"); } void DemoApp::transfer_update(Transfer* /*t*/) { // (this is handled in the prompt logic) } void DemoApp::transfer_failed(Transfer* t, const Error& e, dstime) { if (e == API_ETOOMANY && e.hasExtraInfo()) { displaytransferdetails(t, "failed (" + getExtraInfoErrorString(e) + ")\n"); } else { displaytransferdetails(t, "failed (" + string(errorstring(e)) + ")\n"); } } void DemoApp::transfer_complete(Transfer* t) { if (gVerboseMode) { displaytransferdetails(t, "completed, "); if (t->slot) { cout << t->slot->progressreported * 10 / (1024 * (Waiter::ds - t->slot->starttime + 1)) << " KB/s" << endl; } else { cout << "delayed" << endl; } } } // transfer about to start - make final preparations (determine localfilename, create thumbnail for image upload) void DemoApp::transfer_prepare(Transfer* t) { if (gVerboseMode) { displaytransferdetails(t, "starting\n"); } if (t->type == GET) { // only set localfilename if the engine has not already done so if (t->localfilename.empty()) { t->localfilename = LocalPath::fromAbsolutePath("."); t->localfilename.appendWithSeparator(LocalPath::tmpNameLocal(), false); } } } #ifdef ENABLE_SYNC void DemoApp::syncupdate_stateconfig(const SyncConfig& config) { conlock(cout) << "Sync config updated: " << toHandle(config.mBackupId) << " state: " << int(config.mRunState) << " error: " << config.mError << endl; } void DemoApp::sync_added(const SyncConfig& config) { handle backupId = config.mBackupId; conlock(cout) << "Sync - added " << toHandle(backupId) << " " << config.getLocalPath().toPath(false) << " enabled: " << config.getEnabled() << " syncError: " << config.mError << " " << int(config.mRunState) << endl; } void DemoApp::sync_removed(const SyncConfig& config) { conlock(cout) << "Sync - removed: " << toHandle(config.mBackupId) << endl; } void DemoApp::syncs_restored(SyncError syncError) { conlock(cout) << "Sync - restoration " << (syncError != NO_SYNC_ERROR ? "failed" : "completed") << ": " << SyncConfig::syncErrorToStr(syncError) << endl; } void DemoApp::syncupdate_scanning(bool active) { if (active) { conlock(cout) << "Sync - scanning local files and folders" << endl; } else { conlock(cout) << "Sync - scan completed" << endl; } } void DemoApp::syncupdate_syncing(bool active) { if (active) { conlock(cout) << "Sync - syncs are busy" << endl; } else { conlock(cout) << "Sync - syncs are idle" << endl; } } void DemoApp::syncupdate_stalled(bool stalled) { if (stalled) { conlock(cout) << "Sync - stalled" << endl; } else { conlock(cout) << "Sync - stall ended" << endl; } } void DemoApp::syncupdate_conflicts(bool conflicts) { if (conflicts) { conlock(cout) << "Sync - conflicting paths detected" << endl; } else { conlock(cout) << "Sync - all conflicting paths resolved" << endl; } } // flags to turn off cout output that can be too volumnous/time consuming bool syncout_local_change_detection = true; bool syncout_remote_change_detection = true; bool syncout_transfer_activity = true; bool syncout_folder_sync_state = false; static const char* treestatename(treestate_t ts) { switch (ts) { case TREESTATE_NONE: return "None/Undefined"; case TREESTATE_SYNCED: return "Synced"; case TREESTATE_PENDING: return "Pending"; case TREESTATE_SYNCING: return "Syncing"; case TREESTATE_IGNORED: return "Ignored"; } return "UNKNOWN"; } void DemoApp::syncupdate_treestate(const SyncConfig &, const LocalPath& lp, treestate_t ts, nodetype_t type) { if (syncout_folder_sync_state) { if (type != FILENODE) { conlock(cout) << "Sync - state change of folder " << lp.toPath(false) << " to " << treestatename(ts) << endl; } } } #endif AppFileGet::AppFileGet(Node* n, NodeHandle ch, const byte* cfilekey, m_off_t csize, m_time_t cmtime, const string* cfilename, const string* cfingerprint, const string& targetfolder) { appxfer_it = appxferq[GET].end(); if (n) { h = n->nodeHandle(); hprivate = true; *(FileFingerprint*) this = *n; name = n->displayname(); } else { h = ch; memcpy(filekey, cfilekey, sizeof filekey); hprivate = false; size = csize; mtime = cmtime; if (!cfingerprint->size() || !unserializefingerprint(cfingerprint)) { memcpy(crc.data(), filekey, sizeof crc); } } string s = targetfolder; if (s.empty()) s = "."; auto fstype = client->fsaccess->getlocalfstype(LocalPath::fromAbsolutePath(s)); if (cfilename) { name = *cfilename; } auto ln = LocalPath::fromRelativeName(name, *client->fsaccess, fstype); ln.prependWithSeparator(LocalPath::fromAbsolutePath(s)); setLocalname(ln); } AppFilePut::AppFilePut(const LocalPath& clocalname, NodeHandle ch, const char* ctargetuser) { appxfer_it = appxferq[PUT].end(); // full local path setLocalname(clocalname); // target parent node h = ch; // target user targetuser = ctargetuser; name = clocalname.leafName().toName(*client->fsaccess); } // user addition/update (users never get deleted) void DemoApp::users_updated(User** u, int count) { if (count == 1) { cout << "1 user received or updated" << endl; } else { cout << count << " users received or updated" << endl; } if (u) { User* user; for (int i = 0; i < count; i++) { user = u[i]; cout << "User " << user->email; if (user->getTag()) // false if external change { cout << " has been changed by your own client" << endl; } else { cout << " has been changed externally" << endl; } } } } bool notifyAlerts = true; string displayUser(handle user, MegaClient* mc) { User* u = mc->finduser(user); return u ? u->email : ""; } string displayTime(m_time_t t) { char timebuf[32]; struct tm tmptr; m_localtime(t, &tmptr); strftime(timebuf, sizeof timebuf, "%c", &tmptr); return timebuf; } void printAlert(UserAlert::Base& b) { string header, title; b.text(header, title, client); cout << "**alert " << b.id << ": " << header << " - " << title << " [at " << displayTime(b.ts()) << "]" << " seen: " << b.seen() << endl; } void DemoApp::useralerts_updated(UserAlert::Base** b, int count) { if (b && notifyAlerts) { for (int i = 0; i < count; ++i) { if (!b[i]->seen()) { printAlert(*b[i]); } } } } #ifdef ENABLE_CHAT void DemoApp::chatcreate_result(TextChat *chat, error e) { if (e) { cout << "Chat creation failed (" << errorstring(e) << ")" << endl; } else { cout << "Chat created successfully" << endl; printChatInformation(chat); cout << endl; } } void DemoApp::chatinvite_result(error e) { if (e) { cout << "Chat invitation failed (" << errorstring(e) << ")" << endl; } else { cout << "Chat invitation successful" << endl; } } void DemoApp::chatremove_result(error e) { if (e) { cout << "Peer removal failed (" << errorstring(e) << ")" << endl; } else { cout << "Peer removal successful" << endl; } } void DemoApp::chaturl_result(string *url, error e) { if (e) { cout << "Chat URL retrieval failed (" << errorstring(e) << ")" << endl; } else { cout << "Chat URL: " << *url << endl; } } void DemoApp::chatgrantaccess_result(error e) { if (e) { cout << "Grant access to node failed (" << errorstring(e) << ")" << endl; } else { cout << "Access to node granted successfully" << endl; } } void DemoApp::chatremoveaccess_result(error e) { if (e) { cout << "Revoke access to node failed (" << errorstring(e) << ")" << endl; } else { cout << "Access to node removed successfully" << endl; } } void DemoApp::chatupdatepermissions_result(error e) { if (e) { cout << "Permissions update failed (" << errorstring(e) << ")" << endl; } else { cout << "Permissions updated successfully" << endl; } } void DemoApp::chattruncate_result(error e) { if (e) { cout << "Truncate message/s failed (" << errorstring(e) << ")" << endl; } else { cout << "Message/s truncated successfully" << endl; } } void DemoApp::chatsettitle_result(error e) { if (e) { cout << "Set title failed (" << errorstring(e) << ")" << endl; } else { cout << "Title updated successfully" << endl; } } void DemoApp::chatpresenceurl_result(string *url, error e) { if (e) { cout << "Presence URL retrieval failed (" << errorstring(e) << ")" << endl; } else { cout << "Presence URL: " << *url << endl; } } void DemoApp::chatlink_result(handle h, error e) { if (e) { cout << "Chat link failed (" << errorstring(e) << ")" << endl; } else { if (ISUNDEF(h)) { cout << "Chat link deleted successfully" << endl; } else { char hstr[sizeof(handle) * 4 / 3 + 4]; Base64::btoa((const byte *)&h, MegaClient::CHATLINKHANDLE, hstr); cout << "Chat link: " << hstr << endl; } } } void DemoApp::chatlinkclose_result(error e) { if (e) { cout << "Set private mode for chat failed (" << errorstring(e) << ")" << endl; } else { cout << "Private mode successfully set" << endl; } } void DemoApp::chatlinkurl_result (handle chatid, int shard, string* url, string* ct, int numPeers, m_time_t ts, bool meetingRoom, int chatOptions, const std::vector>* smList, handle callid, error e) { if (e) { cout << "URL request for chat-link failed (" << errorstring(e) << ")" << endl; } else { ::mega::ChatOptions opts(static_cast<::mega::ChatOptions_t>(chatOptions)); char idstr[sizeof(handle) * 4 / 3 + 4]; Base64::btoa((const byte *)&chatid, MegaClient::CHATHANDLE, idstr); cout << "Chatid: " << idstr << " (shard " << shard << ")" << endl; cout << "URL for chat-link: " << url->c_str() << endl; cout << "Encrypted chat-topic: " << ct->c_str() << endl; cout << "Creation timestamp: " << ts << endl; cout << "Num peers: " << numPeers << endl; cout << "Callid: " << Base64Str(callid) << endl; cout << "Meeting room: " << meetingRoom << endl; cout << "Waiting room: " << opts.waitingRoom() << endl; cout << "Open invite: " << opts.openInvite() << endl; cout << "Speak request: " << opts.speakRequest() << endl; cout << "Scheduled meeting: " << smList << endl; } } void DemoApp::chatlinkjoin_result(error e) { if (e) { cout << "Join to openchat failed (" << errorstring(e) << ")" << endl; } else { cout << "Joined to openchat successfully." << endl; } } void DemoApp::chats_updated(textchat_map *chats, int count) { if (count == 1) { cout << "1 chat received or updated" << endl; } else { cout << count << " chats received or updated" << endl; } if (chats) { textchat_map::iterator it; for (it = chats->begin(); it != chats->end(); it++) { printChatInformation(it->second); } } } void DemoApp::printChatInformation(TextChat *chat) { if (!chat) { return; } cout << "Chat ID: " << Base64Str(chat->getChatId()) << endl; cout << "\tOwn privilege level: " << DemoApp::getPrivilegeString(chat->getOwnPrivileges()) << endl; cout << "\tCreation ts: " << chat->getTs() << endl; cout << "\tChat shard: " << chat->getShard() << endl; cout << "\tGroup chat: " << ((chat->getGroup()) ? "yes" : "no") << endl; cout << "\tArchived chat: " << ((chat->isFlagSet(TextChat::FLAG_OFFSET_ARCHIVE)) ? "yes" : "no") << endl; cout << "\tPublic chat: " << ((chat->publicChat()) ? "yes" : "no") << endl; if (chat->publicChat()) { cout << "\tUnified key: " << chat->getUnifiedKey() << endl; cout << "\tMeeting room: " << (chat->getMeeting() ? "yes" : "no") << endl; } cout << "\tPeers:"; if (chat->getUserPrivileges()) { cout << "\t\t(userhandle)\t(privilege level)" << endl; for (const auto& up : *chat->getUserPrivileges()) { Base64Str hstr(up.first); cout << "\t\t\t" << hstr; cout << "\t" << DemoApp::getPrivilegeString(up.second) << endl; } } else { cout << " no peers (only you as participant)" << endl; } cout << "\tIs own change: " << (chat->getTag() ? "yes" : "no") << endl; if (!chat->getTitle().empty()) { cout << "\tTitle: " << chat->getTitle() << endl; } } string DemoApp::getPrivilegeString(privilege_t priv) { switch (priv) { case PRIV_STANDARD: return "PRIV_STANDARD (standard access)"; case PRIV_MODERATOR: return "PRIV_MODERATOR (moderator)"; case PRIV_RO: return "PRIV_RO (read-only)"; case PRIV_RM: return "PRIV_RM (removed)"; case PRIV_UNKNOWN: default: return "PRIV_UNKNOWN"; } } #endif void DemoApp::pcrs_updated(PendingContactRequest** list, int count) { int deletecount = 0; int updatecount = 0; if (list != NULL) { for (int i = 0; i < count; i++) { if (list[i]->changed.deleted) { deletecount++; } else { updatecount++; } } } else { // All pcrs are updated for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++) { if (it->second->changed.deleted) { deletecount++; } else { updatecount++; } } } if (deletecount != 0) { cout << deletecount << " pending contact request" << (deletecount != 1 ? "s" : "") << " deleted" << endl; } if (updatecount != 0) { cout << updatecount << " pending contact request" << (updatecount != 1 ? "s" : "") << " received or updated" << endl; } } static void setattr_result(NodeHandle, Error e) { if (e) { cout << "Node attribute update failed (" << errorstring(e) << ")" << endl; } } static void rename_result(NodeHandle, error e) { if (e) { cout << "Node move failed (" << errorstring(e) << ")" << endl; } } void DemoApp::unlink_result(handle, error e) { if (e) { cout << "Node deletion failed (" << errorstring(e) << ")" << endl; } } void DemoApp::fetchnodes_result(const Error& e) { if (e) { if (e == API_ENOENT && e.hasExtraInfo()) { cout << "File/folder retrieval failed: " << getExtraInfoErrorString(e) << endl; } else { cout << "File/folder retrieval failed (" << errorstring(e) << ")" << endl; } } else { // check if we fetched a folder link and the key is invalid if (client->loggedIntoFolder()) { if (client->isValidFolderLink()) { cout << "Folder link loaded correctly." << endl; } else { assert(client->nodeByHandle(client->mNodeManager.getRootNodeFiles())); // node is there, but cannot be decrypted cout << "Folder retrieval succeed, but encryption key is wrong." << endl; } } if (client->ephemeralSessionPlusPlus) { client->putua(ATTR_FIRSTNAME, (const byte*)ephemeralFirstname.c_str(), unsigned(ephemeralFirstname.size())); client->putua(ATTR_LASTNAME, (const byte*)ephemeralLastName.c_str(), unsigned(ephemeralLastName.size())); } } } void DemoApp::putnodes_result(const Error& e, targettype_t t, vector& nn, bool targetOverride, int tag, const map& /*fileHandles*/) { if (t == USER_HANDLE) { if (!e) { cout << "Success." << endl; } } if (e) { cout << "Node addition failed (" << errorstring(e) << ")" << endl; } if (targetOverride) { cout << "Target folder has changed!" << endl; } auto i = gOnPutNodeTag.find(tag); if (i != gOnPutNodeTag.end()) { for (auto &newNode : nn) { std::shared_ptr n = client->nodebyhandle(newNode.mAddedHandle); if (n) { i->second(n.get()); } } gOnPutNodeTag.erase(i); } } void DemoApp::setpcr_result(handle h, error e, opcactions_t action) { if (e) { cout << "Outgoing pending contact request failed (" << errorstring(e) << ")" << endl; } else { if (h == UNDEF) { // must have been deleted cout << "Outgoing pending contact request " << (action == OPCA_DELETE ? "deleted" : "reminded") << " successfully" << endl; } else { cout << "Outgoing pending contact request succeeded, id: " << Base64Str(h) << endl; } } } void DemoApp::updatepcr_result(error e, ipcactions_t action) { if (e) { cout << "Incoming pending contact request update failed (" << errorstring(e) << ")" << endl; } else { string labels[3] = {"accepted", "denied", "ignored"}; cout << "Incoming pending contact request successfully " << labels[(int)action] << endl; } } void DemoApp::fa_complete(handle h, fatype type, const char* /*data*/, uint32_t len) { cout << "Got attribute of type " << type << " (" << len << " byte(s))"; std::shared_ptr n = client->nodebyhandle(h); if (n) { cout << " for " << n->displayname() << endl; } } int DemoApp::fa_failed(handle, fatype type, int retries, error e) { cout << "File attribute retrieval of type " << type << " failed (retries: " << retries << ") error: " << e << endl; return retries > 2; } void DemoApp::putfa_result(handle, fatype, error e) { if (e) { cout << "File attribute attachment failed (" << errorstring(e) << ")" << endl; } } void DemoApp::removecontact_result(error e) { if (e) { cout << "Contact removal failed (" << errorstring(e) << ")" << endl; } else { cout << "Success." << endl; } } void DemoApp::putua_result(error e) { if (e) { cout << "User attribute update failed (" << errorstring(e) << ")" << endl; } else { cout << "Success." << endl; } } void DemoApp::getua_result(error e) { cout << "User attribute retrieval failed (" << errorstring(e) << ")" << endl; } void DemoApp::getua_result(byte* data, unsigned l, attr_t type) { if (gVerboseMode) { cout << "Received " << l << " byte(s) of user attribute: "; fwrite(data, 1, l, stdout); cout << endl; if (type == ATTR_ED25519_PUBK) { cout << "Credentials: " << AuthRing::fingerprint(string((const char*)data, l), true) << endl; } } if (type == ATTR_COOKIE_SETTINGS) { unsigned long cs = strtoul((const char*)data, nullptr, 10); std::bitset<32> bs(cs); cout << "Cookie settings = " << cs << " (" << bs << ')' << endl << "\tessential: " << bs[0] << endl << "\tpreferences: " << bs[1] << endl << "\tperformance: " << bs[2] << endl << "\tadvertising: " << bs[3] << endl << "\tthird party: " << bs[4] << endl; } if (type == ATTR_FIRSTNAME || type == ATTR_LASTNAME) { cout << string((char*)data, l) << endl; } if (type == ATTR_KEYS) { cout << client->mKeyManager.toString(); } if (type == mega::ATTR_S4) { cout << "S4 enabled: " << client->mIsS4Enabled << endl; } if (type == mega::ATTR_S4_CONTAINER) { cout << "S4 container: " << client->mS4Container << endl; } } void DemoApp::getua_result(unique_ptr records, attr_t type) { if (!records) { cout << "Error getting private user attribute: No valid records in the attribute" << endl; } else if (!gVerboseMode) { cout << "Received a TLV with " << records->size() << " item(s) of user attribute: " << endl; if (type == ATTR_DEVICE_NAMES) { cout << '(' << (b64driveid.empty() ? "Printing only Device names" : (b64driveid == allExtDrives ? "Printing only External-Drive names" : "Printing name of the specified External-Drive only")) << ')' << endl; } bool printDriveId = false; for (const auto& record: *records) { const string& key = record.first.empty() ? "(no key)" : record.first; // external drive names can be filtered if (type == ATTR_DEVICE_NAMES) { bool isExtDrive = key.rfind(User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true), 0) == 0; // starts with "ext:" prefix // print all device names, OR all ext-drive names, OR the name of the selected ext-drive printDriveId = (b64driveid.empty() && !isExtDrive) || // device name (isExtDrive && (b64driveid == allExtDrives || key == User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true) + b64driveid)); if (!printDriveId) { continue; } } // print user attribute values const string& value = record.second; if (value.empty()) { cout << "\t" << key << "\t" << "(no value)"; } else { cout << "\t" << key << "\t"; if (type == ATTR_DEVICE_NAMES || type == ATTR_ALIAS) { // Values that are known to contain only printable characters are ok to display directly. cout << value << " (real text value)"; } else { // Some values may contain non-printable characters, so display them as base64 encoded. const string& b64value = Base64::btoa(value); cout << b64value << " (base64 encoded value)"; } } if (key == client->getDeviceidHash()) { cout << " (own device)"; } cout << endl; } // echo specific drive name not found if (!printDriveId && !b64driveid.empty()) { cout << "Specified drive could not be found" << endl; } b64driveid.clear(); // in case this was for a request that used it } } #ifdef DEBUG void DemoApp::delua_result(error e) { if (e) { cout << "User attribute removal failed (" << errorstring(e) << ")" << endl; } else { cout << "Success." << endl; } } void DemoApp::senddevcommand_result(int value) { cout << "Dev subcommand finished with code: " << value << endl; } void exec_devcommand(autocomplete::ACState& s) { const std::string_view subcommand {s.words[1].s}; std::string email; const bool isEmailProvided = s.extractflagparam("-e", email); std::string campaign; const bool isCampaingProvided = s.extractflagparam("-c", campaign); std::string groupId; const bool isGroupIdProvided = s.extractflagparam("-g", groupId); std::string quotaLengthInMonths; const bool isQuotaLengthInMonths = s.extractflagparam("-q", quotaLengthInMonths); std::string accountLevel; const bool isAccountLevel = s.extractflagparam("-l", accountLevel); const auto printElement = [](const auto& p){ std::cout << " " << p; }; struct Param { bool exists; std::string_view name; }; auto notifyIgnoredParams = [&printElement, &subcommand](std::vector params) { std::vector toIgnore; std::for_each(begin(params), end(params), [&toIgnore](Param& param) { if (param.exists) toIgnore.emplace_back(param.name); }); if (!toIgnore.empty()) { std::cout << "devcommand " << subcommand << " will ignore unrequired"; std::for_each(std::begin(toIgnore), std::end(toIgnore), printElement); std::cout << " provided options\n"; } }; auto requiredParamsPresent = [&printElement, &subcommand](std::vector params) -> bool { std::vector missing; std::for_each(begin(params), end(params), [&missing](Param& param) { if (!param.exists) missing.emplace_back(param.name); }); if (!missing.empty()) { std::cout << "devcommand " << subcommand << " missing required "; std::for_each(std::begin(missing), std::end(missing), printElement); std::cout << " options\n"; return false; } return true; }; const auto checkNatural = [&subcommand](const size_t& length, const std::string& numberAsString, const std::string_view p) -> bool { if (length != numberAsString.size()) { std::cout << subcommand << " param " << p << " must be a natural number: " << numberAsString << " provided\n"; return false; } return true; }; if (subcommand == "abs") { notifyIgnoredParams({Param{isEmailProvided, "-e"}, Param{isQuotaLengthInMonths, "-q"}, Param{isAccountLevel, "-l"}}); if (!requiredParamsPresent( {Param{isCampaingProvided, "-c"}, Param{isGroupIdProvided, "-g"}})) return; size_t l; const int g = std::stoi(groupId, &l); // it's okay throwing in megacli for non-numeric if (!checkNatural(l, groupId, "-g")) return; client->senddevcommand(subcommand.data(), nullptr, 0, 0, g, campaign.c_str()); } else if (subcommand == "sal") { notifyIgnoredParams({Param{isEmailProvided, "-e"}, Param{isCampaingProvided, "-c"}, Param{isGroupIdProvided, "-g"}}); if (!requiredParamsPresent( {Param{isQuotaLengthInMonths, "-q"}, Param{isAccountLevel, "-l"}})) return; size_t l = 0; const long long q = std::stoll(quotaLengthInMonths, &l); if (!checkNatural(l, quotaLengthInMonths, "-q")) return; l = 0; const int al = std::stoi(accountLevel, &l); if (!checkNatural(l, accountLevel, "-a")) return; constexpr int businessStatus = 0; client->senddevcommand(subcommand.data(), nullptr, q, businessStatus, al); } else { notifyIgnoredParams({Param{isCampaingProvided, "-c"}, Param{isGroupIdProvided, "-g"}, Param{isQuotaLengthInMonths, "-q"}, Param{isAccountLevel, "-l"}}); client->senddevcommand(subcommand.data(), isEmailProvided ? email.c_str() : nullptr); } } #endif void DemoApp::notify_retry(dstime dsdelta, retryreason_t) { if (dsdelta) { cout << "API request failed, retrying in " << dsdelta * 100 << " ms - Use 'retry' to retry immediately..." << endl; } else { cout << "Retried API request completed" << endl; } } string DemoApp::getExtraInfoErrorString(const Error& e) { string textError; if (e.getUserStatus() == 7) { textError.append("User status is suspended due to ETD. "); } textError.append("Link status is: "); switch (e.getLinkStatus()) { case 0: textError.append("Undeleted"); break; case 1: textError.append("Deleted/down"); break; case 2: textError.append("Down due to an ETD specifically"); break; default: textError.append("Unknown link status"); break; } return textError; } static void store_line(char*); static void process_line(char *); static char* line; static std::shared_ptr account = std::make_shared(); // Current remote directory. static NodeHandle cwd; // Where we were on the local filesystem when megacli started. static unique_ptr startDir(new LocalPath); static void nodestats(int* c, const char* action) { if (c[FILENODE]) { cout << c[FILENODE] << ((c[FILENODE] == 1) ? " file" : " files"); } if (c[FILENODE] && c[FOLDERNODE]) { cout << " and "; } if (c[FOLDERNODE]) { cout << c[FOLDERNODE] << ((c[FOLDERNODE] == 1) ? " folder" : " folders"); } if (c[FILENODE] || c[FOLDERNODE]) { cout << " " << action << endl; } } // list available top-level nodes and contacts/incoming shares static void listtrees() { if (!client->mNodeManager.getRootNodeFiles().isUndef()) { cout << "ROOT on /" << endl; } if (!client->mNodeManager.getRootNodeVault().isUndef()) { cout << "VAULT on //in" << endl; } if (!client->mNodeManager.getRootNodeRubbish().isUndef()) { cout << "RUBBISH on //bin" << endl; } for (user_map::iterator uit = client->users.begin(); uit != client->users.end(); uit++) { User* u = &uit->second; std::shared_ptr n; if (u->show == VISIBLE || u->sharing.size()) { for (handle_set::iterator sit = u->sharing.begin(); sit != u->sharing.end(); sit++) { if ((n = client->nodebyhandle(*sit)) && n->inshare) { cout << "INSHARE on " << u->email << ":" << n->displayname() << " (" << getAccessLevelStr(n->inshare->access) << ")" << endl; } } } } if (clientFolder && !clientFolder->mNodeManager.getRootNodeFiles().isUndef()) { std::shared_ptr n = clientFolder->nodeByHandle(clientFolder->mNodeManager.getRootNodeFiles()); if (n) { cout << "FOLDERLINK on " << n->displayname() << ":" << endl; } } } bool handles_on = false; bool showattrs = false; // returns node pointer determined by path relative to cwd // path naming conventions: // * path is relative to cwd // * /path is relative to ROOT // * //in is in VAULT (formerly INBOX) // * //bin is in RUBBISH // * X: is user X's VAULT (formerly INBOX) // * X:SHARE is share SHARE from user X // * Y:name is folder in FOLDERLINK, Y is the public handle // * : and / filename components, as well as the \, must be escaped by \. // (correct UTF-8 encoding is assumed) // returns NULL if path malformed or not found static std::shared_ptr nodebypath(const char* ptr, string* user = NULL, string* namepart = NULL) { if (!ptr) { return nullptr; } vector c; string s; size_t l = 0; const char* bptr = ptr; int remote = 0; int folderlink = 0; std::shared_ptr n; std::shared_ptr nn; // special case access by handle, same syntax as megacmd if (handles_on && ptr && strlen(ptr) == 10 && *ptr == 'H' && ptr[1] == ':') { handle h8=0; Base64::atob(ptr+2, (byte*)&h8, MegaClient::NODEHANDLE); return client->nodeByHandle(NodeHandle().set6byte(h8)); } // split path by / or : do { if (!l) { if (*(const signed char*)ptr >= 0) { if (*ptr == '\\') { if (ptr > bptr) { s.append(bptr, static_cast(ptr - bptr)); } bptr = ++ptr; if (*bptr == 0) { c.push_back(s); break; } continue; } if (*ptr == '/' || *ptr == ':' || !*ptr) { if (*ptr == ':') { if (c.size()) { return NULL; } remote = 1; } if (ptr > bptr) { s.append(bptr, static_cast(ptr - bptr)); } bptr = ptr + 1; c.push_back(s); s.erase(); } } else if ((*ptr & 0xf0) == 0xe0) { l = 1; } else if ((*ptr & 0xf8) == 0xf0) { l = 2; } else if ((*ptr & 0xfc) == 0xf8) { l = 3; } else if ((*ptr & 0xfe) == 0xfc) { l = 4; } } else { l--; } } while (*ptr++); if (l) { return NULL; } if (remote) { // target: user inbox - record username/email and return NULL if (c.size() == 2 && c[0].find("@") != string::npos && !c[1].size()) { if (user) { *user = c[0]; } return NULL; } // target is not a user, but a public folder link if (c.size() >= 2 && c[0].find("@") == string::npos) { if (!clientFolder) { return NULL; } n = clientFolder->nodeByHandle(clientFolder->mNodeManager.getRootNodeFiles()); if (c.size() == 2 && c[1].empty()) { return n; } l = 1; // :[/][/] folderlink = 1; } User* u; if ((u = client->finduser(c[0].c_str()))) { // locate matching share from this user handle_set::iterator sit; string name; for (sit = u->sharing.begin(); sit != u->sharing.end(); sit++) { if ((n = client->nodebyhandle(*sit))) { if(!name.size()) { name = c[1]; LocalPath::utf8_normalize(&name); } if (!strcmp(name.c_str(), n->displayname())) { l = 2; break; } } } } if (!l) { return NULL; } } else { // path starting with / if (c.size() > 1 && !c[0].size()) { // path starting with // if (c.size() > 2 && !c[1].size()) { if (c[2] == "in") { n = client->nodeByHandle(client->mNodeManager.getRootNodeVault()); } else if (c[2] == "bin") { n = client->nodeByHandle(client->mNodeManager.getRootNodeRubbish()); } else { return NULL; } l = 3; } else { n = client->nodeByHandle(client->mNodeManager.getRootNodeFiles()); l = 1; } } else { n = client->nodeByHandle(cwd); } } // parse relative path while (n && l < c.size()) { if (c[l] != ".") { if (c[l] == "..") { if (n->parent) { n = n->parent; } } else { // locate child node (explicit ambiguity resolution: not implemented) if (c[l].size()) { if (folderlink) { nn = clientFolder->childnodebyname(n.get(), c[l].c_str()); } else { nn = client->childnodebyname(n.get(), c[l].c_str()); } if (!nn) { // mv command target? return name part of not found if (namepart && l == c.size() - 1) { *namepart = c[l]; return n; } return NULL; } n = nn; } } } l++; } return n; } static void listnodeshares(Node* n, bool printLinks) { if(n->outshares) { for (share_map::iterator it = n->outshares->begin(); it != n->outshares->end(); it++) { assert(!it->second->pcr); if (printLinks && !it->second->user) { cout << "\t" << n->displayname(); cout << ", shared as exported folder link" << endl; } if (!printLinks && it->second->user) { cout << "\t" << n->displayname(); cout << ", shared with " << it->second->user->email << " (" << getAccessLevelStr(it->second->access) << ")" << (client->mKeyManager.isUnverifiedOutShare(n->nodehandle, toHandle(it->second->user->userhandle)) ? " (unverified)" : "") << endl; } } } } static void listnodependingshares(Node* n) { if(n->pendingshares) { for (share_map::iterator it = n->pendingshares->begin(); it != n->pendingshares->end(); it++) { cout << "\t" << n->displayname(); assert(it->second->pcr); assert(!it->second->user); cout << ", pending share with " << it->second->pcr->targetemail << " (" << getAccessLevelStr(it->second->access) << ")" << (client->mKeyManager.isUnverifiedOutShare(n->nodehandle, it->second->pcr->targetemail) ? " (unverified)" : "") << endl; } } } static void listallshares() { cout << "Outgoing shared folders:" << endl; sharedNode_vector outshares = client->mNodeManager.getNodesWithOutShares(); for (auto& share : outshares) { listnodeshares(share.get(), false); } cout << "Incoming shared folders:" << endl; for (user_map::iterator uit = client->users.begin(); uit != client->users.end(); uit++) { User* u = &uit->second; std::shared_ptr n; if (u->show == VISIBLE && u->sharing.size()) { cout << "From " << u->email << ":" << endl; for (handle_set::iterator sit = u->sharing.begin(); sit != u->sharing.end(); sit++) { if ((n = client->nodebyhandle(*sit))) { cout << "\t" << n->displayname() << " (" << getAccessLevelStr(n->inshare->access) << ")" << (client->mKeyManager.isUnverifiedInShare(n->nodehandle, u->userhandle) ? " (unverified)" : "") << endl; } } } } cout << "Pending outgoing shared folders:" << endl; // pending outgoing sharedNode_vector pendingoutshares = client->mNodeManager.getNodesWithPendingOutShares(); for (auto& share : pendingoutshares) { listnodependingshares(share.get()); } cout << "Public folder links:" << endl; sharedNode_vector links = client->mNodeManager.getNodesWithLinks(); for (auto& share : links) { listnodeshares(share.get(), true); } } static void dumptree(Node* n, bool recurse, int depth, const char* title, ofstream* toFile) { std::ostream& stream = toFile ? *toFile : cout; string titleTmp; if (depth) { if (!toFile) { if (!title && !(title = n->displayname())) { title = "CRYPTO_ERROR"; } for (int i = depth; i--; ) { stream << "\t"; } } else { titleTmp = n->displaypath(); title = titleTmp.c_str(); } stream << title << " ("; switch (n->type) { case FILENODE: { stream << n->size; if (handles_on) { Base64Str handlestr(n->nodehandle); stream << " " << handlestr.chars; } const char* p; if ((p = strchr(n->fileattrstring.c_str(), ':'))) { stream << ", has file attributes " << p + 1; } if (showattrs && n->attrs.map.size()) { stream << ", has name"; for (auto& a : n->attrs.map) { char namebuf[100]{}; AttrMap::nameid2string(a.first, namebuf); stream << " " << namebuf << "=" << a.second; } } sharedNode_list nodeChildren = client->mNodeManager.getChildren(n); if (nodeChildren.size()) { Node *version = n; int i = 0; while (nodeChildren.size() && (version = nodeChildren.back().get())) { i++; if (handles_on) { if (i == 1) stream << ", has versions: "; Base64Str handlestr(version->nodehandle); stream << " [" << i << "] " << handlestr.chars; } nodeChildren = client->mNodeManager.getChildren(version); } if (!handles_on) { stream << ", has " << i << " versions"; } } if (n->plink) { stream << ", shared as exported"; if (n->plink->ets) { stream << " temporal"; } else { stream << " permanent"; } stream << " file link"; } break; } case FOLDERNODE: if (n->isPasswordManagerNode()) { stream << "password manager node entry of type "; if (n->isPasswordNode()) stream << "password"; else if (n->isCreditCardNode()) stream << "credit card"; else { stream << "unknown type"; assert(false); } } else if (n->isPasswordManagerNodeFolder()) stream << "password folder"; else stream << "folder"; if (handles_on) { Base64Str handlestr(n->nodehandle); stream << " " << handlestr.chars; } if(n->outshares) { for (share_map::iterator it = n->outshares->begin(); it != n->outshares->end(); it++) { if (it->first) { stream << ", shared with " << it->second->user->email << ", access " << getAccessLevelStr(it->second->access); } } if (n->plink) { stream << ", shared as exported"; if (n->plink->ets) { stream << " temporal"; } else { stream << " permanent"; } stream << " folder link"; } } if (n->pendingshares) { for (share_map::iterator it = n->pendingshares->begin(); it != n->pendingshares->end(); it++) { if (it->first) { stream << ", shared (still pending) with " << it->second->pcr->targetemail << ", access " << getAccessLevelStr(it->second->access); } } } if (n->inshare) { stream << ", inbound " << getAccessLevelStr(n->inshare->access) << " share"; } if (showattrs && n->attrs.map.size()) { stream << ", has name"; for (auto& a : n->attrs.map) { char namebuf[100]{}; AttrMap::nameid2string(a.first, namebuf); stream << " " << namebuf << "=" << a.second; } } break; default: stream << "unsupported type, please upgrade"; } stream << ")" << (n->changed.removed ? " (DELETED)" : "") << endl; if (!recurse) { return; } } if (n->type != FILENODE) { for (auto& node : client->getChildren(n)) { dumptree(node.get(), recurse, depth + 1, NULL, toFile); } } } static void local_dumptree(const fs::path& de, int recurse, int depth = 0) { if (depth) { for (int i = depth; i--; ) { cout << "\t"; } cout << de.filename().u8string() << " ("; if (fs::is_directory(de)) { cout << "folder"; } cout << ")" << endl; if (!recurse) { return; } } if (fs::is_directory(de)) { for (auto i = fs::directory_iterator(de); i != fs::directory_iterator(); ++i) { local_dumptree(*i, recurse, depth + 1); } } } static void nodepath(NodeHandle h, string* path) { std::shared_ptr n = client->nodeByHandle(h); *path = n ? n->displaypath() : ""; } appfile_list appxferq[2]; static const char* prompts[] = { "MEGAcli> ", "Password:", "Old Password:", "New Password:", "Retype New Password:", "Master Key (base64):", "Type 2FA pin:", "Type pin to enable 2FA:", "-Input m to get more, q to quit-" }; enum prompttype { COMMAND, LOGINPASSWORD, OLDPASSWORD, NEWPASSWORD, PASSWORDCONFIRM, MASTERKEY, LOGINTFA, SETTFA, PAGER }; static prompttype prompt = COMMAND; #if defined(WIN32) && defined(NO_READLINE) static char pw_buf[512]; // double space for unicode #else static char pw_buf[256]; #endif static int pw_buf_pos; static void setprompt(prompttype p) { auto cl = conlock(cout); // use this wherever we might have output threading issues prompt = p; if (p == COMMAND) { console->setecho(true); } else if (p == PAGER) { cout << endl << prompts[p] << flush; console->setecho(false); // doesn't seem to do anything } else { pw_buf_pos = 0; #if defined(WIN32) && defined(NO_READLINE) static_cast(console)->updateInputPrompt(prompts[p]); #else cout << prompts[p] << flush; #endif console->setecho(false); } } class TreeProcCopy_mcli : public TreeProc { // This is a duplicate of the TreeProcCopy declared in treeproc.h and defined in megaapi_impl.cpp. // However some products are built with the megaapi_impl intermediate layer and some without so // we can avoid duplicated symbols in some products this way public: vector nn; unsigned nc = 0; bool populated = false; void allocnodes() { nn.resize(nc); populated = true; } // determine node tree size (nn = NULL) or write node tree to new nodes array void proc(MegaClient* mc, std::shared_ptr n) { if (populated) { mc->putnodes_prepareCopy(nn, nc, n->type, n->nodehandle, n->parent ? n->parent->nodehandle : UNDEF, n->nodekey(), n->attrs, false, false); } else { nc++; } } }; int loadfile(LocalPath& localPath, string* data) { auto fa = client->fsaccess->newfileaccess(); if (fa->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError)) { data->resize(size_t(fa->size)); fa->fread(data, unsigned(data->size()), 0, 0, FSLogging::logOnError); return 1; } return 0; } void xferq(direction_t d, int cancel, bool showActive, bool showAll, bool showCount) { string name; int count = 0, activeCount = 0; TransferDbCommitter committer(client->tctable); for (appfile_list::iterator it = appxferq[d].begin(); it != appxferq[d].end(); ) { if (cancel < 0 || cancel == (*it)->seqno) { bool active = (*it)->transfer && (*it)->transfer->slot; (*it)->displayname(&name); if ((active && showActive) || showAll) { cout << (*it)->seqno << ": " << name; if (d == PUT) { AppFilePut* f = (AppFilePut*)*it; cout << " -> "; if (f->targetuser.size()) { cout << f->targetuser << ":"; } else { string path; nodepath(f->h, &path); cout << path; } } if (active) { cout << " [ACTIVE] " << ((*it)->transfer->slot->progressreported * 100 / ((*it)->transfer->size ? (*it)->transfer->size : 1)) << "% of " << (*it)->transfer->size; } cout << endl; } if (cancel >= 0) { cout << "Cancelling..." << endl; if ((*it)->transfer) { client->stopxfer(*it++, &committer); // stopping calls us back, we delete it, destructor removes it from the map } continue; } ++count; activeCount += active ? 1 : 0; } ++it; } if (showCount) { cout << "Transfer count: " << count << " active: " << activeCount << endl; } } #ifdef USE_MEDIAINFO string showMediaInfo(const MediaProperties& mp, MediaFileInfo& mediaInfo, bool oneline) { ostringstream out; string sep(oneline ? " " : "\n"); MediaFileInfo::MediaCodecs::shortformatrec sf; sf.containerid = 0; sf.videocodecid = 0; sf.audiocodecid = 0; if (mp.shortformat == 255) { return "MediaInfo could not identify this file"; } else if (mp.shortformat == 0) { // from attribute 9 sf.containerid = mp.containerid; sf.videocodecid = mp.videocodecid; sf.audiocodecid = mp.audiocodecid; } else if (mp.shortformat < mediaInfo.mediaCodecs.shortformats.size()) { sf = mediaInfo.mediaCodecs.shortformats[mp.shortformat]; } for (std::map::const_iterator i = mediaInfo.mediaCodecs.containers.begin(); i != mediaInfo.mediaCodecs.containers.end(); ++i) { if (i->second == sf.containerid) { out << "Format: " << i->first << sep; } } for (std::map::const_iterator i = mediaInfo.mediaCodecs.videocodecs.begin(); i != mediaInfo.mediaCodecs.videocodecs.end(); ++i) { if (i->second == sf.videocodecid) { out << "Video: " << i->first << sep; } } for (std::map::const_iterator i = mediaInfo.mediaCodecs.audiocodecs.begin(); i != mediaInfo.mediaCodecs.audiocodecs.end(); ++i) { if (i->second == sf.audiocodecid) { out << "Audio: " << i->first << sep; } } if (mp.width > 0) { out << "Width: " << mp.width << sep; } if (mp.height > 0) { out << "Height: " << mp.height << sep; } if (mp.fps > 0) { out << "Fps: " << mp.fps << sep; } if (mp.playtime > 0) { out << "Playtime: " << mp.playtime << sep; } string result = out.str(); result.erase(result.size() - (result.empty() ? 0 : 1)); return result; } string showMediaInfo(const std::string& fileattributes, uint32_t fakey[4], MediaFileInfo& mediaInfo, bool oneline) { MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(fileattributes, fakey); return showMediaInfo(mp, mediaInfo, oneline); } string showMediaInfo(Node* n, MediaFileInfo& /*mediaInfo*/, bool oneline) { if (n->hasfileattribute(fa_media)) { MediaProperties mp = MediaProperties::decodeMediaPropertiesAttributes(n->fileattrstring, (uint32_t*)(n->nodekey().data() + FILENODEKEYLENGTH / 2)); return showMediaInfo(mp, client->mediaFileInfo, oneline); } return "The node has no mediainfo attribute"; } #endif // password change-related state information static byte pwkey[SymmCipher::KEYLENGTH]; static byte pwkeybuf[SymmCipher::KEYLENGTH]; static byte newpwkey[SymmCipher::KEYLENGTH]; static string newpassword; #ifndef NO_READLINE // Where our command history will be recorded. string historyFile; void exec_history(autocomplete::ACState& s) { // history clear // history list // history read file // history record file // history write file // What does the user want to do? const auto& command = s.words[1].s; // Does the user want to clear their recorded history? if (command == "clear") { if (!historyFile.empty() && history_truncate_file(historyFile.c_str(), 0)) { cerr << "Unable to clear recorded history." << endl; return; } // Clear recorded history. clear_history(); // We're done. return; } // Is the user interested in viewing their recorded history? if (command == "list") { auto** history = history_list(); if (!history) { cout << "No history has been recorded." << endl; return; } for (auto i = 0; history[i]; ++i) { cout << i + history_base << ": " << history[i]->line << endl; } return; } // Does the user want to load their history from a file? if (command == "read") { if (read_history(s.words[2].s.c_str())) { cerr << "Unable to read history from: " << s.words[2].s << endl; return; } cout << "Successfully loaded history from: " << s.words[2].s << endl; return; } // User wants to record history to a file? if (command == "record") { // Clear recorded history. clear_history(); // Truncate history file. if (write_history(s.words[2].s.c_str())) { cerr << "Unable to truncate history file: " << s.words[2].s.c_str(); return; } // Remember where we should write the history to. historyFile = s.words[2].s; cout << "Now recording history to: " << historyFile << endl; return; } // Only branch left. assert(command == "write"); if (write_history(s.words[2].s.c_str())) { cerr << "Unable to write history to: " << s.words[2].s.c_str(); return; } cout << "History written to: " << s.words[2].s << endl; } #endif // ! NO_READLINE // readline callback - exit if EOF, add to history unless password #if !defined(WIN32) || !defined(NO_READLINE) static void store_line(char* l) { if (!l) { #ifndef NO_READLINE rl_callback_handler_remove(); #endif /* ! NO_READLINE */ delete console; exit(0); } #ifndef NO_READLINE if (*l && prompt == COMMAND) { char* expansion = nullptr; // Try and expand any "event designators." auto result = history_expand(l, &expansion); // Was the designator bogus? if (result < 0) { add_history(l); // Then assume it's a normal command. return line = l, void(); } // Otherwise, we have a valid expansion. add_history(expansion); // Flush the history to disk. if (!historyFile.empty()) write_history(historyFile.c_str()); // Display but don't execute the expansion. if (result == 2) { cout << expansion << endl; return free(expansion); } // Execute the expansion. line = expansion; // Release the input string. return free(l); } #endif line = l; } #endif class FileFindCommand : public Command { public: struct Stack : public std::deque { size_t filesLeft = 0; set servers; }; FileFindCommand(std::shared_ptr& s, MegaClient* mc) : stack(s) { h = stack->front(); stack->pop_front(); client = mc; cmd("g"); arg("n", (byte*)&h, MegaClient::NODEHANDLE); arg("g", 1); arg("v", 2); // version 2: server can supply details for cloudraid files arg("ssl", 2); } static string server(const string& url) { const string pattern("://"); size_t start_index = url.find(pattern); if (start_index != string::npos) { start_index += pattern.size(); const size_t end_index = url.find("/", start_index); if (end_index != string::npos) { return url.substr(start_index, end_index - start_index); } } return ""; } // process file credentials bool procresult(Result r, JSON& json) override { if (!r.wasErrorOrOK()) { std::vector tempurls; bool done = false; while (!done) { switch (json.getnameid()) { case EOO: done = true; break; case makeNameid("g"): if (json.enterarray()) // now that we are requesting v2, the reply will be an array of 6 URLs for a raid download, or a single URL for the original direct download { for (;;) { std::string tu; if (!json.storeobject(&tu)) { break; } tempurls.push_back(tu); } json.leavearray(); if (tempurls.size() == 6) { if (std::shared_ptr n = client->nodebyhandle(h)) { cout << n->displaypath() << endl; for (const auto& url : tempurls) { stack->servers.insert(server(url)); } } } break; } // fall-through default: json.storeobject(); } } } // now query for the next one - we don't send them all at once as there may be a lot! --stack->filesLeft; if (!stack->empty()) { client->queueCommand(new FileFindCommand(stack, client)); } else if (!stack->filesLeft) { cout << "" << endl; for (auto s : stack->servers) { cout << s << endl; } } return true; } private: handle h; std::shared_ptr stack; }; void getDepthFirstFileHandles(Node* n, deque& q) { for (auto c : client->getChildren(n)) { if (c->type == FILENODE) { q.push_back(c->nodehandle); } } for (auto& c : client->getChildren(n)) { if (c->type > FILENODE) { getDepthFirstFileHandles(c.get(), q); } } } void exec_find(autocomplete::ACState& s) { if (s.words[1].s == "raided") { if (std::shared_ptr n = client->nodeByHandle(cwd)) { auto q = std::make_shared(); getDepthFirstFileHandles(n.get(), *q); q->filesLeft = q->size(); cout << "size() << " files>" << endl; if (q->empty()) { cout << "" << endl; } else { for (int i = 0; i < 25 && !q->empty(); ++i) { client->queueCommand(new FileFindCommand(q, client)); } } } } } bool recurse_findemptysubfoldertrees(Node* n, bool moveToTrash) { if (n->type == FILENODE) { return false; } sharedNode_vector emptyFolders; bool empty = true; std::shared_ptr trash = client->nodeByHandle(client->mNodeManager.getRootNodeRubbish()); sharedNode_list children = client->getChildren(n); for (auto& c : children) { bool subfolderEmpty = recurse_findemptysubfoldertrees(c.get(), moveToTrash); if (subfolderEmpty) { emptyFolders.push_back(c); } empty = empty && subfolderEmpty; } if (!empty) { for (auto& c : emptyFolders) { if (moveToTrash) { cout << "moving to trash: " << c->displaypath() << endl; client->rename(c, trash, SYNCDEL_NONE, NodeHandle(), nullptr, false, rename_result); } else { cout << "empty folder tree at: " << c->displaypath() << endl; } } } return empty; } void exec_findemptysubfoldertrees(autocomplete::ACState& s) { bool moveToTrash = s.extractflag("-movetotrash"); if (std::shared_ptr n = client->nodeByHandle(cwd)) { if (recurse_findemptysubfoldertrees(n.get(), moveToTrash)) { cout << "the search root path only contains empty folders: " << n->displaypath() << endl; } } } void exec_fileversions(autocomplete::ACState& s) { if (std::shared_ptr node = client->nodeByPath(s.words[1].s.c_str())) { std::shared_ptr current = client->nodebyhandle(node->nodehandle); if (current && current->type == FILENODE) { vector> versions; versions.push_back(current); bool lookingFor = true; while (lookingFor) { sharedNode_list nodeList = client->getChildren(current.get(), mega::CancelToken(), true); if (nodeList.empty()) { lookingFor = false; } else { assert(nodeList.back()->parent == current); current = nodeList.back(); assert(current->type == FILENODE); versions.push_back(current); } } cout << "Versions: " << endl; int i = 1; for (auto v: versions) { cout << i << ". " << v->displayname() << " - " << v->nodeHandle() << endl; i++; } } } } bool typematchesnodetype(nodetype_t pathtype, nodetype_t nodetype) { switch (pathtype) { case FILENODE: case FOLDERNODE: return nodetype == pathtype; default: return false; } } bool recursiveCompare(Node* mn, fs::path p) { nodetype_t pathtype = fs::is_directory(p) ? FOLDERNODE : fs::is_regular_file(p) ? FILENODE : TYPE_UNKNOWN; if (!typematchesnodetype(pathtype, mn->type)) { cout << "Path type mismatch: " << mn->displaypath() << ":" << mn->type << " " << p.u8string() << ":" << pathtype << endl; return false; } if (pathtype == FILENODE) { uint64_t size = (uint64_t) fs::file_size(p); if (size != (uint64_t) mn->size) { cout << "File size mismatch: " << mn->displaypath() << ":" << mn->size << " " << p.u8string() << ":" << size << endl; } } if (pathtype != FOLDERNODE) { return true; } std::string path = p.u8string(); auto fileSystemType = client->fsaccess->getlocalfstype(LocalPath::fromAbsolutePath(path)); multimap > ms; multimap ps; for (auto& m : client->getChildren(mn)) { string leafname = m->displayname(); client->fsaccess->escapefsincompatible(&leafname, fileSystemType); ms.emplace(leafname, m); } for (fs::directory_iterator pi(p); pi != fs::directory_iterator(); ++pi) { auto leafname = pi->path().filename().u8string(); client->fsaccess->escapefsincompatible(&leafname, fileSystemType); ps.emplace(leafname, pi->path()); } for (auto p_iter = ps.begin(); p_iter != ps.end(); ) { auto er = ms.equal_range(p_iter->first); auto next_p = p_iter; ++next_p; for (auto i = er.first; i != er.second; ++i) { if (recursiveCompare(i->second.get(), p_iter->second)) { ms.erase(i); ps.erase(p_iter); break; } } p_iter = next_p; } if (ps.empty() && ms.empty()) { return true; } else { cout << "Extra content detected between " << mn->displaypath() << " and " << p.u8string() << endl; for (auto& mi : ms) cout << "Extra remote: " << mi.first << endl; for (auto& pi : ps) cout << "Extra local: " << pi.second << endl; return false; }; } std::shared_ptr nodeFromRemotePath(const string& s) { std::shared_ptr n; if (s.empty()) { n = client->nodeByHandle(cwd); } else { n = nodebypath(s.c_str()); } if (!n) { cout << "remote path not found: '" << s << "'" << endl; } return n; } #ifdef MEGA_MEASURE_CODE void exec_deferRequests(autocomplete::ACState& s) { // cause all the API requests of this type to be gathered up so they will be sent in a single batch, for timing purposes bool putnodes = s.extractflag("-putnodes"); bool movenode = s.extractflag("-movenode"); bool delnode = s.extractflag("-delnode"); client->reqs.deferRequests = [=](Command* c) { return (putnodes && dynamic_cast(c)) || (movenode && dynamic_cast(c)) || (delnode && dynamic_cast(c)); }; } void exec_sendDeferred(autocomplete::ACState& s) { // send those gathered up commands, and optionally reset the gathering client->reqs.sendDeferred(); if (s.extractflag("-reset")) { client->reqs.deferRequests = nullptr; } } void exec_codeTimings(autocomplete::ACState& s) { bool reset = s.extractflag("-reset"); cout << client->performanceStats.report(reset, client->httpio, client->waiter.get(), client->reqs) << flush; } #endif std::function onCompletedUploads; void setAppendAndUploadOnCompletedUploads(string local_path, int count, bool allowDuplicateVersions) { onCompletedUploads = [local_path, count, allowDuplicateVersions](){ { ofstream f(local_path, std::ios::app); f << count << endl; } cout << count << endl; TransferDbCommitter committer(client->tctable); int total = 0; auto lp = LocalPath::fromAbsolutePath(local_path); uploadLocalPath(FILENODE, lp.leafName().toPath(false), lp, client->nodeByHandle(cwd).get(), "", committer, total, false, ClaimOldVersion, nullptr, false, allowDuplicateVersions); if (count > 0) { setAppendAndUploadOnCompletedUploads(local_path, count-1, allowDuplicateVersions); } else { onCompletedUploads = nullptr; } }; } std::deque> mainloopActions; fs::path pathFromLocalPath(const string& s, bool mustexist) { fs::path p = s.empty() ? fs::current_path() : fs::u8path(s); if (mustexist && !fs::exists(p)) { cout << "local path not found: '" << s << "'"; return fs::path(); } return p; } void exec_treecompare(autocomplete::ACState& s) { fs::path p = pathFromLocalPath(s.words[1].s, true); std::shared_ptr n = nodeFromRemotePath(s.words[2].s); if (n && !p.empty()) { recursiveCompare(n.get(), p); } } bool buildLocalFolders(fs::path targetfolder, const string& prefix, int foldersperfolder, int recurselevel, int filesperfolder, uint64_t filesize, int& totalfilecount, int& totalfoldercount, vector* localPaths) { fs::path p = targetfolder / fs::u8path(prefix); if (!fs::is_directory(p) && !fs::create_directory(p)) return false; ++totalfoldercount; for (int i = 0; i < filesperfolder; ++i) { string filename = prefix + "_file_" + std::to_string(++totalfilecount); fs::path fp = p / fs::u8path(filename); if (localPaths) localPaths->push_back(LocalPath::fromAbsolutePath(fp.u8string())); ofstream fs(fp.u8string(), std::ios::binary); char buffer[64 * 1024]; fs.rdbuf()->pubsetbuf(buffer, sizeof(buffer)); int counter = totalfilecount; for (auto j = filesize / sizeof(int); j--; ) { fs.write((char*)&counter, sizeof(int)); ++counter; } fs.write((char*)&counter, filesize % sizeof(int)); } if (recurselevel > 1) { for (int i = 0; i < foldersperfolder; ++i) { if (!buildLocalFolders(p, prefix + "_" + std::to_string(i), foldersperfolder, recurselevel - 1, filesperfolder, filesize, totalfilecount, totalfoldercount, nullptr)) return false; } } return true; } void exec_generatetestfilesfolders(autocomplete::ACState& s) { string param, nameprefix = "test"; int folderdepth = 1, folderwidth = 1, filecount = 100; int64_t filesize = 1024; if (s.extractflagparam("-folderdepth", param)) folderdepth = atoi(param.c_str()); if (s.extractflagparam("-folderwidth", param)) folderwidth = atoi(param.c_str()); if (s.extractflagparam("-filecount", param)) filecount = atoi(param.c_str()); if (s.extractflagparam("-filesize", param)) filesize = atoll(param.c_str()); if (s.extractflagparam("-nameprefix", param)) nameprefix = param; fs::path p = pathFromLocalPath(s.words[1].s, true); if (!p.empty()) { int totalfilecount = 0, totalfoldercount = 0; buildLocalFolders(p, nameprefix, folderwidth, folderdepth, filecount, static_cast(filesize), totalfilecount, totalfoldercount, nullptr); cout << "created " << totalfilecount << " files and " << totalfoldercount << " folders" << endl; } else { cout << "invalid directory: " << p.u8string() << endl; } } map cycleUploadChunkFails; map cycleDownloadFails; void checkReportCycleFails() { for (auto& i : cycleDownloadFails) cout << i.first << " " << i.second; for (auto& i : cycleUploadChunkFails) cout << i.first << " " << i.second; } std::shared_ptr cycleUploadDownload_cloudWorkingFolder = nullptr; void cycleDownload(LocalPath lp, int count); void cycleUpload(LocalPath lp, int count) { checkReportCycleFails(); TransferDbCommitter committer(client->tctable); LocalPath upload_lp = lp; upload_lp.append(LocalPath::fromRelativePath("_" + std::to_string(count))); string leaf = upload_lp.leafName().toPath(false); int total = 0; uploadLocalPath(FILENODE, leaf, upload_lp, cycleUploadDownload_cloudWorkingFolder.get(), "", committer, total, false, NoVersioning, [lp, count](LocalPath) { return [lp, count]() { cycleDownload(lp, count); }; }, true, true); // also delete the old remote file if (count > 0) { string leaf2 = lp.leafName().toPath(false) + "_" + std::to_string(count-1); if (std::shared_ptr lastuploaded = client->childnodebyname(cycleUploadDownload_cloudWorkingFolder.get(), leaf2.c_str(), true)) { client->unlink(lastuploaded.get(), false, client->nextreqtag(), false, nullptr); } } } void cycleDownload(LocalPath lp, int count) { checkReportCycleFails(); string leaf = lp.leafName().toPath(false) + "_" + std::to_string(count); std::shared_ptr uploaded = client->childnodebyname(cycleUploadDownload_cloudWorkingFolder.get(), leaf.c_str(), true); if (!uploaded) { cout << "Uploaded file " << leaf << " not found, cycle broken" << endl; return; } LocalPath downloadName = lp; downloadName.append(LocalPath::fromRelativePath("_" + std::to_string(count+1))); string newleaf = lp.leafName().toPath(false); newleaf += "_" + std::to_string(count + 1); auto f = new AppFileGet(uploaded.get(), NodeHandle(), NULL, -1, 0, &newleaf, NULL, lp.parentPath().toPath(false)); f->noRetries = true; f->onCompleted = [lp, count]() { cycleUpload(lp, count+1); }; f->appxfer_it = appxferq[GET].insert(appxferq[GET].end(), f); TransferDbCommitter committer(client->tctable); client->startxfer(GET, f, committer, false, false, false, NoVersioning, nullptr, client->nextreqtag()); // also delete the old local file lp.append(LocalPath::fromRelativePath("_" + std::to_string(count))); client->fsaccess->unlinklocal(lp); } int gap_resumed_uploads = 0; void exec_cycleUploadDownload(autocomplete::ACState& s) { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onUploadChunkFailed = [](error e) { ++cycleUploadChunkFails["upload-chunk-err-" + std::to_string(int(e))]; }; globalMegaTestHooks.onDownloadFailed = [](error e) { if (e != API_EINCOMPLETE) { ++cycleDownloadFails["download-err-" + std::to_string(int(e))]; } }; globalMegaTestHooks.onUploadChunkSucceeded = [](Transfer* t, TransferDbCommitter& committer) { if (t->chunkmacs.hasUnfinishedGap(1024ll*1024*1024*1024*1024)) //if (t->pos > 5000000 && rand() % 2 == 0) { ++gap_resumed_uploads; // simulate this transfer string serialized; t->serialize(&serialized); // put the transfer in cachedtransfers so we can resume it Transfer::unserialize(client, &serialized, client->multi_cachedtransfers); // prep to try to resume this upload after we get back to our main loop auto fpstr = t->files.front()->getLocalname().toPath(false); auto countpos = fpstr.find_last_of('_'); auto count = atoi(fpstr.c_str() + countpos + 1); fpstr.resize(countpos); mainloopActions.push_back([fpstr, count](){ cycleUpload(LocalPath::fromAbsolutePath(fpstr), count); }); //terminate this transfer t->failed(API_EINCOMPLETE, committer); return false; // exit doio() for this transfer } return true; }; #endif string param, nameprefix = "cycleUpDown"; int filecount = 10; int64_t filesize = 305560; if (s.extractflagparam("-filecount", param)) filecount = atoi(param.c_str()); if (s.extractflagparam("-filesize", param)) filesize = atoll(param.c_str()); if (s.extractflagparam("-nameprefix", param)) nameprefix = param; fs::path p = pathFromLocalPath(s.words[1].s, true); cycleUploadDownload_cloudWorkingFolder = nodeFromRemotePath(s.words[2].s); if (!p.empty()) { int totalfilecount = 0, totalfoldercount = 0; vector localPaths; buildLocalFolders(p, nameprefix, 1, 1, filecount, static_cast(filesize), totalfilecount, totalfoldercount, &localPaths); cout << "created " << totalfilecount << " files and " << totalfoldercount << " folders" << endl; for (auto& fp : localPaths) { LocalPath startPath = fp; startPath.append(LocalPath::fromRelativePath("_0")); client->fsaccess->renamelocal(fp, startPath, true); cycleUpload(fp, 0); } } else { cout << "invalid directory: " << p.u8string() << endl; } } void exec_generate_put_fileversions(autocomplete::ACState& s) { int count = 100; string param; if (s.extractflagparam("-count", param)) count = atoi(param.c_str()); setAppendAndUploadOnCompletedUploads(s.words[1].s, count, true); onCompletedUploads(); } void exec_generatesparsefile(autocomplete::ACState& s) { int64_t filesize = int64_t(2) * 1024 * 1024 * 1024 * 1024; string param; if (s.extractflagparam("-filesize", param)) filesize = atoll(param.c_str()); fs::path p = pathFromLocalPath(s.words[1].s, false); std::ofstream(p).put('a'); cout << "File size: " << fs::file_size(p) << '\n' << "Free space: " << fs::space(p).free << '\n'; #ifdef WIN32 HANDLE hFile = CreateFileW((LPCWSTR)p.u16string().data(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL); DWORD bytesReturned = 0; if (!DeviceIoControl( hFile, // handle to a file FSCTL_SET_SPARSE, // dwIoControlCode (PFILE_SET_SPARSE_BUFFER) NULL, // input buffer (DWORD) 0, // size of input buffer NULL, // lpOutBuffer 0, // nOutBufferSize &bytesReturned, // number of bytes returned NULL)) // OVERLAPPED structure { cout << "Set sparse file operation failed." << endl; } CloseHandle(hFile); #endif //WIN32 fs::resize_file(p, static_cast(filesize)); cout << "File size: " << fs::file_size(p) << '\n' << "Free space: " << fs::space(p).free << '\n'; } void exec_lreplace(autocomplete::ACState& s) { bool file = s.extractflag("-file"); bool folder = s.extractflag("-folder"); fs::path p = pathFromLocalPath(s.words[1].s, true); // replace (or create) a file/folder - this is to test a changed fsid in sync code if (file) { string content = s.words[2].s; ofstream f(p); f << content; } else if (folder) { if (fs::exists(p)) fs::remove(p); fs::create_directory(p); } } void exec_lrenamereplace(autocomplete::ACState& s) { bool file = s.extractflag("-file"); bool folder = s.extractflag("-folder"); fs::path p = pathFromLocalPath(s.words[1].s, true); string content = s.words[2].s; fs::path p2 = pathFromLocalPath(s.words[3].s, false); // replace (or create) a file/folder - this is to test a changed fsid in sync code fs::rename(p, p2); if (file) { ofstream f(p); f << content; } else if (folder) { fs::create_directory(p); } } void exec_getcloudstorageused(autocomplete::ACState&) { if (client->loggedin() != FULLACCOUNT && !client->loggedIntoFolder()) { cout << "Not logged in" << endl; return; } NodeCounter nc = client->mNodeManager.getCounterOfRootNodes(); cout << "Total cloud storage: " << nc.storage + nc.versionStorage << " bytes" << endl; } void exec_getuserquota(autocomplete::ACState& s) { bool storage = s.extractflag("-storage"); bool transfer = s.extractflag("-transfer"); bool pro = s.extractflag("-pro"); if (!storage && !transfer && !pro) { storage = transfer = pro = true; } client->getaccountdetails(std::make_shared(), storage, transfer, pro, false, false, false, -1); } void exec_getdiscountcodeinfo(autocomplete::ACState& ac) { string code = ac.words[1].s; if (code.empty()) { cout << "Please provide a discount code" << endl; return; } client->getDiscountCodeInformation( code, [code](const Error& e, DiscountCodeInfoExtended&& info) { if (e == API_OK) { cout << "Discount code info for '" << info.alfanumDiscountCode << "': valid" << endl; cout << "discount code: " << info.alfanumDiscountCode << endl; cout << "item: " << info.item << endl; cout << "accountLevel:" << info.accountLevel << endl; cout << "behaviourType: " << info.behaviourType << endl; cout << "percentageDiscount: " << info.percentageDiscount << endl; cout << "numMonths: " << (int)info.numMonths << endl; cout << "expiry: " << info.expiry << endl; cout << "compulsorySubscription: " << info.compulsorySubscription << endl; cout << "multiDiscount: " << info.multiDiscount << endl; cout << "euroTotalPrice: " << info.euroTotalPrice << endl; cout << "euroDiscountAmount: " << info.euroDiscountAmount << endl; cout << "euroDiscountedTotalPrice: " << info.euroDiscountedTotalPrice << endl; cout << "euroDiscountedMonthlyPrice: " << info.euroDiscountedMonthlyPrice << endl; cout << "euroTotalPriceNet: " << info.euroTotalPriceNet << endl; cout << "euroDiscountAmountNet: " << info.euroDiscountAmountNet << endl; cout << "euroDiscountedTotalPriceNet: " << info.euroDiscountedTotalPriceNet << endl; cout << "euroDiscountedMonthlyPriceNet: " << info.euroDiscountedMonthlyPriceNet << endl; cout << "localCurrencyCode: " << info.localCurrencyCode << endl; cout << "localCurrencySymbol: " << info.localCurrencySymbol << endl; cout << "localTotalPrice: " << info.localTotalPrice << endl; cout << "localDiscountAmount: " << info.localDiscountAmount << endl; cout << "localDiscountedTotalPrice: " << info.localDiscountedTotalPrice << endl; cout << "localDiscountedMonthlyPrice: " << info.localDiscountedMonthlyPrice << endl; cout << "localTotalPriceNet: " << info.localTotalPriceNet << endl; cout << "localDiscountAmountNet: " << info.localDiscountAmountNet << endl; cout << "localDiscountedTotalPriceNet: " << info.localDiscountedTotalPriceNet << endl; cout << "localDiscountedMonthlyPriceNet: " << info.localDiscountedMonthlyPriceNet << endl; } else { cout << "Discount code info for '" << code << "': invalid, error " << e << endl; } }); } void exec_getuserdata(autocomplete::ACState&) { if (client->loggedin()) client->getuserdata(client->reqtag); else client->getmiscflags(); } void exec_querytransferquota(autocomplete::ACState& ac) { client->querytransferquota(atoll(ac.words[1].s.c_str())); } void DemoApp::querytransferquota_result(int n) { cout << "querytransferquota_result: " << n << endl; } autocomplete::ACN autocompleteTemplate; void exec_help(ac::ACState&) { cout << *autocompleteTemplate << flush; } bool quit_flag = false; void exec_quit(ac::ACState&) { quit_flag = true; } void exec_showattributes(autocomplete::ACState& s) { if (const std::shared_ptr n = nodeFromRemotePath(s.words[1].s)) { for (auto pair : n->attrs.map) { char namebuf[10]{}; AttrMap::nameid2string(pair.first, namebuf); if (pair.first == 'c') { FileFingerprint f; f.unserializefingerprint(&pair.second); cout << namebuf << ": " << pair.second << " (fingerprint: size " << f.size << " mtime " << f.mtime << " crc " << std::hex << f.crc[0] << " " << f.crc[1] << " " << f.crc[2] << " " << f.crc[3] << std::dec << ")" << " (node fingerprint: size " << n->size << " mtime " << n->mtime << " crc " << std::hex << n->crc[0] << " " << n->crc[1] << " " << n->crc[2] << " " << n->crc[3] << std::dec << ")" << endl; } else { cout << namebuf << ": " << pair.second << endl; } } } } void printAuthringInformation(handle userhandle) { for (auto &it : client->mAuthRings) { AuthRing &authring = it.second; attr_t at = it.first; cout << User::attr2string(at) << ": " << endl; for (auto &uh : authring.getTrackedUsers()) { if (uh == userhandle || ISUNDEF(userhandle)) // no user was typed --> show authring for all users { User *user = client->finduser(uh); string email = user ? user->email : "not a contact"; cout << "\tUserhandle: \t" << Base64Str(uh) << endl; cout << "\tEmail: \t" << email << endl; cout << "\tFingerprint:\t" << Utils::stringToHex(authring.getFingerprint(uh)) << endl; cout << "\tAuth. level: \t" << AuthRing::authMethodToStr(authring.getAuthMethod(uh)) << endl; } } } } void exec_setmaxconnections(autocomplete::ACState& s) { auto direction = s.words[1].s == "put" ? PUT : GET; if (s.words.size() == 3) { client->setmaxconnections(direction, atoi(s.words[2].s.c_str())); } cout << "connections: " << (int)client->connections[direction] << endl; } class MegaCLILogger : public ::mega::Logger { public: ofstream mLogFile; string mLogFileName; bool logToConsole = false; void log(const char*, int /*loglevel*/, const char*, const char* message #ifdef ENABLE_LOG_PERFORMANCE , const char** directMessages, size_t* directMessagesSizes, unsigned numberMessages #endif ) override { using namespace std::chrono; auto et =system_clock::now().time_since_epoch(); auto millisec_since_epoch = duration_cast(et).count(); auto sec_since_epoch = duration_cast(et).count(); char ts[50]; auto t = std::time(NULL); t = (m_time_t) sec_since_epoch; if (!std::strftime(ts, sizeof(ts), "%H:%M:%S", std::localtime(&t))) { ts[0] = '\0'; } auto ms = std::to_string(unsigned(millisec_since_epoch - 1000*sec_since_epoch)); string s; s.reserve(1024); s += ts; s += "." + string(3 - std::min(3, ms.size()), '0') + ms; s += " "; if (message) s += message; #ifdef ENABLE_LOG_PERFORMANCE for (unsigned i = 0; i < numberMessages; ++i) s.append(directMessages[i], directMessagesSizes[i]); #endif if (logToConsole) { std::cout << s << std::endl; } if (mLogFile.is_open()) { mLogFile << s << std::endl; } #ifdef WIN32 // Supply the log strings to Visual Studio Output window, regardless of toconsole/file settings s += "\r\n"; OutputDebugStringA(s.c_str()); #endif } }; LocalPath localPathArg(string s) { if (s.empty()) return LocalPath(); return LocalPath::fromAbsolutePath(s); } void exec_fingerprint(autocomplete::ACState& s) { auto localfilepath = localPathArg(s.words[1].s); auto fa = client->fsaccess->newfileaccess(); if (fa->fopen(localfilepath, OPEN_RDONLY, FSLogging::logOnError, nullptr)) { FileFingerprint fp; fp.genfingerprint(fa.get()); cout << Utils::stringToHex(std::string((const char*)&fp.size, sizeof(fp.size))) << "/" << Utils::stringToHex(std::string((const char*)&fp.mtime, sizeof(fp.mtime))) << "/" << Utils::stringToHex(std::string((const char*)&fp.crc, sizeof(fp.crc))) << endl; } else { cout << "Failed to open: " << s.words[1].s << endl; } } void exec_showattrs(autocomplete::ACState& s) { if (s.words.size() == 2) { if (s.words[1].s == "on") { showattrs = true; } else if (s.words[1].s == "off") { showattrs = false; } else { cout << "invalid showattrs setting" << endl; } } else { cout << " showattrs on|off " << endl; } } void exec_timelocal(autocomplete::ACState& s) { bool get = s.words[1].s == "get"; auto localfilepath = localPathArg(s.words[2].s); if ((get && s.words.size() != 3) || (!get && s.words.size() != 4)) { cout << "wrong number of arguments for : " << s.words[1].s << endl; return; } m_time_t set_time = 0; if (!get) { // similar to Transfers::complete() std::istringstream is(s.words[3].s); std::tm tm_record; is >> std::get_time(&tm_record, "%Y-%m-%d %H:%M:%S"); set_time = m_mktime(&tm_record); cout << "Setting mtime to " << set_time << endl; bool success = client->fsaccess->setmtimelocal(localfilepath, set_time); if (!success) { cout << "setmtimelocal failed! Was it transient? " << client->fsaccess->transient_error << endl; } } // perform get in both cases auto fa = client->fsaccess->newfileaccess(); if (fa->fopen(localfilepath, OPEN_RDONLY, FSLogging::logOnError)) { FileFingerprint fp; fp.genfingerprint(fa.get()); if (fp.isvalid) { std::tm tm_record; m_localtime(fp.mtime, &tm_record); cout << "mtime for file is " << fp.mtime << ": " << std::put_time(&tm_record, "%Y-%m-%d %H:%M:%S") << endl; if (!get) { if (abs(set_time - fp.mtime) <= 2) { cout << "mtime read back is within 2 seconds, so success. Actual difference: " << abs(set_time - fp.mtime) << endl; } else { cout << "ERROR Silent failure in setmtimelocal, difference is " << abs(set_time - fp.mtime) << endl; } } } else { cout << "fingerprint generation failed: " << localfilepath.toPath(false) << endl; } } else { cout << "fopen failed: " << localfilepath.toPath(false) << endl; } } void putua_map(const std::string& b64key, const std::string& b64value, attr_t attrtype) { User* ownUser = client->ownuser(); if (!ownUser) { cout << "Must be logged in to set own attributes." << endl; return; } string_map destination; const UserAttribute* attribute = ownUser->getAttribute(attrtype); if (!attribute || attribute->isNotExisting()) // attr doesn't exist -> create it { const string& realValue = Base64::atob(b64value); destination[b64key] = realValue; // real value, non-B64 } else if (attribute->isExpired()) { cout << "User attribute is outdated"; cout << "Fetch the attribute first" << endl; return; } else { std::unique_ptr oldRecords{ tlv::containerToRecords(attribute->value(), client->key)}; if (oldRecords) { destination.swap(*oldRecords); } string_map attrMap; attrMap[b64key] = b64value; // User::mergeUserAttribute() expects B64 values if (!User::mergeUserAttribute(attrtype, attrMap, destination)) { cout << "Failed to merge with existing values" << endl; return; } } client->putua(attrtype, std::move(destination)); } void exec_setdevicename(autocomplete::ACState& s) { const string& b64idhash = client->getDeviceidHash(); // already in B64 const string& devname = s.words[1].s; const string& b64devname = Base64::btoa(devname); putua_map(b64idhash, b64devname, ATTR_DEVICE_NAMES); } void exec_getdevicename(autocomplete::ACState&) { User* u = client->ownuser(); if (!u) { cout << "Must be logged in to query own attributes." << endl; return; } b64driveid.clear(); // filter out all external drives client->getua(u, ATTR_DEVICE_NAMES); } void exec_setextdrivename(autocomplete::ACState& s) { const string& drivepath = s.words[1].s; const string& drivename = s.words[2].s; // check if the drive-id was already created // read /.megabackup/drive-id handle driveid; error e = readDriveId(*client->fsaccess, drivepath.c_str(), driveid); if (e == API_ENOENT) { // generate new id driveid = generateDriveId(client->rng); // write /.megabackup/drive-id e = writeDriveId(*client->fsaccess, drivepath.c_str(), driveid); } if (e != API_OK) { cout << "Failed to get drive-id for " << drivepath << endl; return; } putua_map(User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true) + string(Base64Str(driveid)), Base64::btoa(drivename), ATTR_DEVICE_NAMES); } void exec_getextdrivename(autocomplete::ACState& s) { User* u = client->ownuser(); if (!u) { cout << "Must be logged in to query own attributes." << endl; return; } bool idFlag = s.extractflag("-id"); bool pathFlag = s.extractflag("-path"); b64driveid = allExtDrives; // list all external drives if (s.words.size() == 2) { if (idFlag) { b64driveid = s.words[1].s; } else if (pathFlag) { // read drive-id from /.megabackup/drive-id const string& drivepath = s.words[1].s; handle driveid = 0; error e = readDriveId(*client->fsaccess, drivepath.c_str(), driveid); if (e == API_ENOENT) { cout << "Drive-id not set for " << drivepath << endl; return; } b64driveid = string(Base64Str(driveid)); } } client->getua(u, ATTR_DEVICE_NAMES); } void exec_setmybackups(autocomplete::ACState& s) { const string& bkpsFolder = s.words[1].s; std::function completion = [bkpsFolder](Error e) { if (e == API_OK) { cout << "\"My Backups\" folder set to " << bkpsFolder << endl; } else { cout << "Failed to set \"My Backups\" folder to " << bkpsFolder << " (remote error " << error(e) << ": " << errorstring(e) << ')' << endl; } }; error err = client->setbackupfolder(bkpsFolder.c_str(), 0, completion); if (err != API_OK) { cout << "Failed to set \"My Backups\" folder to " << bkpsFolder << " (" << err << ": " << errorstring(err) << ')' << endl; } } void exec_getmybackups(autocomplete::ACState&) { User* u = client->ownuser(); if (!u) { cout << "Login first." << endl; return; } const UserAttribute* attribute = u->getAttribute(ATTR_MY_BACKUPS_FOLDER); if (!attribute || attribute->isNotExisting()) { cout << "\"My Backups\" folder has not been set." << endl; return; } handle h = 0; memcpy(&h, attribute->value().data(), MegaClient::NODEHANDLE); if (!h || h == UNDEF) { cout << "Invalid handle stored for \"My Backups\" folder." << endl; return; } std::shared_ptr n = client->nodebyhandle(h); if (!n) { cout << "\"My Backups\" folder could not be found." << toHandle(h) << endl; return; } cout << "\"My Backups\" folder (handle " << toHandle(h) << "): " << n->displaypath() << endl; } #ifdef ENABLE_SYNC std::string deviceCenterStateToString(CommandBackupPut::SPState state) { switch (state) { case CommandBackupPut::SPState::STATE_NOT_INITIALIZED: return "STATE_NOT_INITIALIZED"; case CommandBackupPut::SPState::ACTIVE: return "ACTIVE"; case CommandBackupPut::SPState::FAILED: return "FAILED"; case CommandBackupPut::SPState::TEMPORARY_DISABLED: return "TEMPORARY_DISABLED"; case CommandBackupPut::SPState::DISABLED: return "DISABLED"; case CommandBackupPut::SPState::PAUSE_UP: return "PAUSE_UP"; case CommandBackupPut::SPState::PAUSE_DOWN: return "PAUSE_DOWN"; case CommandBackupPut::SPState::PAUSE_FULL: return "PAUSE_FULL"; case CommandBackupPut::SPState::DELETED: return "DELETED"; } return "UNKNOWN"; } void exec_backupcentreUpdateState(const string& backupIdStr, CommandBackupPut::SPState newState) { handle backupId = 0; Base64::atob(backupIdStr.c_str(), (byte*)&backupId, MegaClient::BACKUPHANDLE); // determine if it's a backup or other type of sync SyncConfig c; bool found = client->syncs.configById(backupId, c); string syncType = found && c.isBackup() ? "backup" : "sync"; client->updateStateInBC(backupId, newState, [newState, syncType, backupId](const Error& e) { string newStateStr = newState == CommandBackupPut::TEMPORARY_DISABLED ? "pause" : "resume"; if (e == API_OK) { cout << "Backup Centre - " << newStateStr << "d " << syncType << ' ' << toHandle(backupId) << endl; } else { cout << "Backup Centre - Failed to " << newStateStr << ' ' << syncType << ' ' << toHandle(backupId) << " (" << errorstring(e) << ')' << endl; } }); } void exec_backupcentre(autocomplete::ACState& s) { bool delFlag = s.extractflag("-del"); bool purgeFlag = s.extractflag("-purge"); bool stopFlag = s.extractflag("-stop"); bool pauseFlag = s.extractflag("-pause"); bool resumeFlag = s.extractflag("-resume"); if (s.words.size() == 1) { client->getBackupInfo([purgeFlag](const Error& e, const vector& data) { if (e) { cout << "Backup Center - failed to get info about Backups: " << e << endl; } else { for (auto& d : data) { if (purgeFlag) { client->queueCommand(new CommandBackupRemove( client, d.backupId, [&](Error e) { if (e) { cout << "Backup Center - failed to purge id: " << toHandle(d.backupId) << endl; } })); } else { cout << "Backup ID: " << toHandle(d.backupId) << " (" << d.backupId << ')' << endl; cout << " backup type: " << backupTypeToStr(d.backupType) << endl; cout << " root handle: " << toNodeHandle(d.rootNode) << endl; cout << " local folder: " << d.localFolder << endl; cout << " device id: " << d.deviceId << endl; cout << " device user-agent: " << d.deviceUserAgent << endl; cout << " sync state: " << deviceCenterStateToString( static_cast(d.syncState)) << endl; cout << " sync substate: " << SyncConfig::syncErrorToStr(static_cast(d.syncSubstate)) << endl; cout << " extra: " << d.extra << endl; cout << " backup name: " << d.backupName << endl; cout << " heartbeat timestamp: " << d.hbTimestamp << endl; cout << " heartbeat status: " << d.hbStatus << endl; cout << " heartbeat progress: " << d.hbProgress << endl; cout << " heartbeat uploads: " << d.uploads << endl; cout << " heartbeat downloads: " << d.downloads << endl; cout << " last activity time: " << d.lastActivityTs << endl; cout << " last node handle: " << toNodeHandle(d.lastSyncedNodeHandle) << endl << endl; } } if (purgeFlag) { cout << "Backup Center - Purging registered syncs/backups from API..." << endl; } else { cout << "Backup Centre - Sync / backup count: " << data.size() << endl; } } }); } else if ((delFlag && s.words.size() >= 2) || // remove backup && (move or delete) its contents (stopFlag && s.words.size() == 2)) // stop non-backup sync { handle backupId = 0; const string& backupIdStr = s.words[1].s; Base64::atob(backupIdStr.c_str(), (byte*)&backupId, MegaClient::BACKUPHANDLE); // get move destination for the removed backup handle hDest = 0; if (delFlag && s.words.size() == 3) { Base64::atob(s.words[2].s.c_str(), (byte*)&hDest, MegaClient::NODEHANDLE); // validation std::shared_ptr targetDest = client->nodebyhandle(hDest); if (!targetDest) { cout << "Backup Centre - Move destination " << s.words[2].s << " not found" << endl; return; } } else { hDest = UNDEF; } // determine if it's a backup or other type of sync SyncConfig c; bool found = client->syncs.configById(backupId, c); bool isBackup = found && c.isBackup(); // request removal client->removeFromBC(backupId, hDest, [backupId, isBackup, hDest](const Error& e) { if (e == API_OK) { cout << "Backup Centre - " << (isBackup ? "Backup " : "Sync ") << toHandle(backupId); if (isBackup) { cout << " removed and contents " << (hDest == UNDEF ? "deleted" : "moved") << endl; } else { cout << " stopped" << endl; } } else { cout << "Backup Centre - Failed to " << (isBackup ? "remove Backup " : "stop sync ") << toHandle(backupId); if (isBackup) { cout << " and " << (hDest == UNDEF ? "deleted" : "moved") << " its contents"; } cout << " (" << errorstring(e) << ')' << endl; } }); } else if ((pauseFlag || resumeFlag) && s.words.size() == 2) // pause/resume sync (any kind) { exec_backupcentreUpdateState(s.words[1].s, pauseFlag ? CommandBackupPut::TEMPORARY_DISABLED : CommandBackupPut::ACTIVE); } } #endif #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED void exec_simulatecondition(autocomplete::ACState& s) { auto condition = s.words[1].s; if (condition == "ETOOMANY") { globalMegaTestHooks.interceptSCRequest = [](std::unique_ptr& pendingsc){ pendingsc.reset(new HttpReq); pendingsc->status = REQ_SUCCESS; pendingsc->in = "-6"; globalMegaTestHooks.interceptSCRequest = nullptr; cout << "ETOOMANY prepared and reset" << endl; }; client->waiter->notify(); } else { cout << "unknown condition: " << condition << endl; } } #endif #ifdef ENABLE_SYNC void exec_syncoutput(autocomplete::ACState& s) { bool onOff = s.words[3].s == "on"; if (s.words[2].s == "local_change_detection") { syncout_local_change_detection = onOff; } else if (s.words[2].s == "remote_change_detection") { syncout_remote_change_detection = onOff; } else if (s.words[2].s == "transfer_activity") { syncout_transfer_activity = onOff; } else if (s.words[2].s == "folder_sync_state") { syncout_transfer_activity = onOff; } else if (s.words[2].s == "detail_log") { client->syncs.mDetailedSyncLogging = onOff; } else if (s.words[2].s == "all") { syncout_local_change_detection = onOff; syncout_remote_change_detection = onOff; syncout_transfer_activity = onOff; syncout_transfer_activity = onOff; client->syncs.mDetailedSyncLogging = onOff; } } #endif static void exec_fusedb(autocomplete::ACState& state) { using namespace fuse; // Upgrade or downgrade? auto function = ([&]() { if (state.words[2].s == "downgrade") return &fuse::Service::downgrade; return &fuse::Service::upgrade; })(); // What version are we moving to? auto version = ([&]() { std::istringstream istream(state.words[4].s); std::size_t result = 0; istream >> result; return result; })(); // Try and downgrade / upgrade the specified database. auto path = localPathArg(state.words[3].s); auto result = (client->mFuseService.*function)(path, version); if (result != MOUNT_SUCCESS) { std::cerr << "Unable to " << state.words[2].s << " the database \"" << state.words[3].s << "\" to version " << state.words[4].s << ": " << toString(result) << std::endl; return; } auto command = state.words[2].s; command.front() = static_cast(std::toupper(command.front())); std::cout << command << "d database \"" << state.words[3].s << "\" to version " << state.words[4].s << std::endl; } static bool isFullAccount(const std::string& message) { if (client->loggedin() == FULLACCOUNT) return true; std::cerr << message << std::endl; return false; } static void exec_fuseflags(autocomplete::ACState& state) { using std::chrono::seconds; using std::stoul; auto parseCacheFlags = [&](fuse::InodeCacheFlags& flags) { std::string ageThreshold; std::string interval; std::string maxSize; std::string sizeThreshold; state.extractflagparam("-cache-clean-age-threshold", ageThreshold); state.extractflagparam("-cache-clean-interval", interval); state.extractflagparam("-cache-clean-size-threshold", sizeThreshold); state.extractflagparam("-cache-max-size", maxSize); if (!ageThreshold.empty()) flags.mCleanAgeThreshold = seconds(stoul(ageThreshold)); if (!interval.empty()) flags.mCleanInterval = seconds(stoul(interval)); if (!maxSize.empty()) flags.mMaxSize = stoul(maxSize); if (!sizeThreshold.empty()) flags.mCleanSizeThreshold = stoul(sizeThreshold); }; // parseCacheFlags auto parseExecutorFlags = [&](common::TaskExecutorFlags& flags, const std::string& type) { std::string idle; std::string max; std::string min; state.extractflagparam("-" + type + "-max-thread-count", max); state.extractflagparam("-" + type + "-max-thread-idle-time", idle); state.extractflagparam("-" + type + "-min-thread-count", min); if (!idle.empty()) flags.mIdleTime = seconds(stoul(idle)); if (!max.empty()) flags.mMaxWorkers = stoul(max); if (!min.empty()) flags.mMinWorkers = stoul(min); }; // parseExecutorFlags std::string flushDelay; std::string logLevel; std::string fileExplorerView; state.extractflagparam("-flush-delay", flushDelay); state.extractflagparam("-log-level", logLevel); state.extractflagparam("-file-explorer-view", fileExplorerView); auto flags = client->mFuseService.serviceFlags(); if (!flushDelay.empty()) flags.mFlushDelay = std::chrono::seconds(std::stoul(flushDelay)); if (!logLevel.empty()) flags.mLogLevel = toLogLevel(logLevel); if (!fileExplorerView.empty()) flags.mFileExplorerView = fuse::toFileExplorerView(fileExplorerView); parseCacheFlags(flags.mInodeCacheFlags); parseExecutorFlags(flags.mMountExecutorFlags, "mount"); parseExecutorFlags(flags.mServiceExecutorFlags, "service"); client->mFuseService.serviceFlags(flags); std::cout << "Cache Clean Age Threshold: " << flags.mInodeCacheFlags.mCleanAgeThreshold.count() << "\n" << "Cache Clean Interval: " << flags.mInodeCacheFlags.mCleanInterval.count() << "\n" << "Cache Clean Size Threshold: " << flags.mInodeCacheFlags.mCleanSizeThreshold << "\n" << "Cache Max Size: " << flags.mInodeCacheFlags.mMaxSize << "\n" << "Flush Delay: " << flags.mFlushDelay.count() << "s\n" << "Log Level: " << toString(flags.mLogLevel) << "\n" << "File Explorer View: " << toString(flags.mFileExplorerView) << "\n" << "Mount Max Thread Count: " << flags.mMountExecutorFlags.mMaxWorkers << "\n" << "Mount Max Thread Idle Time: " << flags.mMountExecutorFlags.mIdleTime.count() << "s\n" << "Mount Min Thread Count: " << flags.mMountExecutorFlags.mMinWorkers << "\n" << "Service Max Thread Count: " << flags.mServiceExecutorFlags.mMaxWorkers << "\n" << "Service Max Thread Idle Time: " << flags.mServiceExecutorFlags.mIdleTime.count() << "s\n" << "Service Min Thread Count: " << flags.mServiceExecutorFlags.mMinWorkers << std::endl; } static void exec_fusemountadd(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to add a FUSE mount.")) return; using namespace fuse; MountInfo info; state.extractflagparam("-name", info.mFlags.mName); info.mFlags.mPersistent = state.extractflag("-persistent"); info.mFlags.mReadOnly = state.extractflag("-read-only"); const auto sourcePath = state.words[3].s; const auto sourceNode = nodebypath(sourcePath.c_str()); if (!sourceNode) { std::cerr << "Unable to add a mount against \"" << sourcePath << "\" as the node does not exist." << std::endl; return; } if (info.name().empty()) { info.name(sourceNode->displayname()); if (!sourceNode->parent) info.name("MEGA"); } info.mHandle = sourceNode->nodeHandle(); if (state.words.size() > 4) info.mPath = localPathArg(state.words[4].s); auto result = client->mFuseService.add(info); if (result != MOUNT_SUCCESS) { std::cerr << "Failed to add mount \"" << info.name() << "\": " << toString(result) << std::endl; return; } std::cout << "Successfully added mount \"" << info.name() << "\"." << std::endl; } static void exec_fusemountdisable(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to disable FUSE mounts.")) return; using namespace fuse; auto name = state.words[3].s; auto callback = [=](MountResult result) { if (result == MOUNT_SUCCESS) { std::cout << "Successfully disabled mount \"" << name << "\"." << std::endl; return; } std::cerr << "Failed to disable mount \"" << name << "\": " << toString(result) << std::endl; }; auto remember = state.extractflag("-remember"); client->mFuseService.disable(std::move(callback), name, remember); } static void exec_fusemountenable(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to enable FUSE mounts.")) return; using fuse::MOUNT_SUCCESS; const auto& name = state.words[3].s; auto remember = state.extractflag("-remember"); auto result = client->mFuseService.enable(name, remember); if (result == MOUNT_SUCCESS) { std::cout << "Successfully enabled mount \"" << name << "\"." << std::endl; return; } std::cerr << "Failed to enable mount \"" << name << "\": " << toString(result) << std::endl; } static void exec_fusemountflags(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to alter FUSE mount flags.")) return; const auto& name = state.words[3].s; auto flags = client->mFuseService.flags(name); if (!flags) { std::cerr << "Couldn't retrieve flags for mount \"" << name << "\"." << std::endl; return; } auto disabled = state.extractflag("-disabled-at-startup"); auto enabled = state.extractflag("-enabled-at-startup"); if (disabled && enabled) { std::cerr << "A mount is either disabled or enabled at startup." << std::endl; return; } flags->mEnableAtStartup |= enabled; flags->mEnableAtStartup &= !disabled; flags->mPersistent |= enabled || disabled; state.extractflagparam("-name", flags->mName); auto readOnly = state.extractflag("-read-only"); auto writable = state.extractflag("-writable"); if (readOnly && writable) { std::cerr << "A mount is either read-only or writable." << std::endl; return; } flags->mReadOnly |= readOnly; flags->mReadOnly &= !writable; auto persistent = state.extractflag("-persistent"); auto transient = state.extractflag("-transient"); if (persistent && transient) { std::cerr << "A mount is either persistent or transient." << std::endl; return; } flags->mPersistent |= persistent; flags->mPersistent &= !transient; auto result = client->mFuseService.flags(name, *flags); if (result != fuse::MOUNT_SUCCESS) { std::cerr << "Unable to update mount flags: " << toString(result) << std::endl; return; } std::cout << "Enabled at startup: " << flags->mEnableAtStartup << "\n" << "Name: " << flags->mName << "\n" << "Persistent: " << flags->mPersistent << "\n" << "Read-Only: " << flags->mReadOnly << std::endl; } static void exec_fusemountlist(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to list FUSE mounts.")) return; auto active = state.extractflag("-only-active"); auto mounts = client->mFuseService.get(active); if (mounts.empty()) { std::cout << "There are no FUSE mounts." << std::endl; return; } for (auto i = 0u; i < mounts.size(); ++i) { const auto& info = mounts[i]; auto sourceNode = client->nodeByHandle(info.mHandle); std::string sourcePath = "N/A"; if (sourceNode) sourcePath = sourceNode->displaypath(); std::cout << "Mount #" << (i + 1) << ":\n" << " Enabled at Startup: " << (info.mFlags.mEnableAtStartup ? "Yes" : "No") << "\n" << " Enabled: " << client->mFuseService.enabled(info.name()) << "\n" << " Name: \"" << info.name() << "\"\n" << " Read " << (info.mFlags.mReadOnly ? "Only" : "Write") << "\n" << " Source Handle: " << toNodeHandle(info.mHandle) << "\n" << " Source Path: " << sourcePath << "\n" << " Target Path: " << info.mPath.toPath(true) << "\n" << std::endl; } std::cout << "Listed " << mounts.size() << " FUSE mount(s)." << std::endl; } static void exec_fusemountremove(autocomplete::ACState& state) { if (!isFullAccount("You must be logged in to remove a FUSE mount.")) return; using namespace fuse; const auto& name = state.words[3].s; auto result = client->mFuseService.remove(name); if (result == MOUNT_SUCCESS) { std::cout << "Successfully removed mount \"" << name << "\"." << std::endl; return; } std::cerr << "Failed to remove mount \"" << name << "\": " << toString(result) << std::endl; } static void exec_thumbnail(autocomplete::ACState& state) { // Clarity. const auto& destination = state.words[1].s; const auto& source = state.words[2].s; const auto& dimension = [&state]() { size_t index = GfxProc::THUMBNAIL; if (state.extractflag("-thumbnail")) { index = GfxProc::THUMBNAIL; } else if (state.extractflag("-preview")) { index = GfxProc::PREVIEW; } return GfxProc::DIMENSIONS[index]; }(); // Try writing a thumbnail for source to destination. auto result = client->gfx->savefa(localPathArg(source), dimension, localPathArg(destination)); auto* message = "Saved generated thumbnail for "; auto* ostream = &std::cout; // Couldn't write a thumbnail for some reason. if (!result) { message = "Couldn't save generated thumbnail for "; ostream = &std::cerr; } *ostream << message << source << " to " << destination << std::endl; } MegaCLILogger gLogger; void exec_tag_add_remove(autocomplete::ACState& state) { // Convenience. using TagCommandCallback = CommandSetAttr::Completion; using TagCommand = error (MegaClient::*)(std::shared_ptr, const std::string&, TagCommandCallback&&); // Assume we're adding a new tag. TagCommand command = &MegaClient::addTagToNode; // We're actually removing an existing tag. if (state.words[1].s == "remove") command = &MegaClient::removeTagFromNode; // Called when the tag's been added or removed. auto callback = [command = state.words[1].s](NodeHandle, Error result) { conlock(std::cout) << "Tag " << command << " result: " << result << std::endl; }; // callback // Locate the node specified by the user. auto node = nodebypath(state.words[2].s.c_str()); // Couldn't find the node. if (!node) return callback(NodeHandle(), API_ENOENT); // Try and add (or remove) the specified tag. auto result = (client->*command)(std::move(node), state.words[3].s, TagCommandCallback(callback)); // Client eagerly detected an error. if (result != API_OK) callback(NodeHandle(), result); } template static void listTags(const T& tags) { auto iterator = std::ostream_iterator(std::cout, "\n"); std::copy(tags.begin(), tags.end(), iterator); } void exec_tag_list_all(autocomplete::ACState&) { // Retrieve all tags in this account. auto tags = client->getNodeTagsBelow(NodeHandle()); // No tags. if (!tags) return; // Convenience. using NaturalStringMultiSet = std::multiset; // List tags in natural order. listTags(NaturalStringMultiSet(tags->begin(), tags->end())); } void exec_tag_list_at(autocomplete::ACState& state) { // Locate the node specified by the user. auto node = nodebypath(state.words[3].s.c_str()); // Couldn't find the specified node. if (!node) { conlock(std::cout) << "Couldn't find a node at " << state.words[3].s << std::endl; return; } // List the node's tags. listTags(client->getNodeTags(std::move(node))); } void exec_tag_list_below(autocomplete::ACState& state) { // Assume the user wants to list all tags below the roots. NodeHandle handle; // User wants to list all tags below a particular node. if (state.words.size() > 2) { // Try and locate the node speciifed by the user. auto node = nodebypath(state.words[3].s.c_str()); // Couldn't find the specified node. if (!node) { conlock(std::cout) << "Couldn't find a node at " << state.words[3].s << std::endl; return; } // Capture the node's handle. handle = node->nodeHandle(); } // Retrieve tags. auto tags = client->getNodeTagsBelow(handle); // No tags. if (!tags) return; using NaturalStringMultiSet = std::multiset; // List tags in natural order. listTags(NaturalStringMultiSet(tags->begin(), tags->end())); } void exec_proxyget(autocomplete::ACState&) { // Retrieve current proxy settings. auto settings = client->httpio->getproxy(); // No settings to display. if (!settings) return std::cout << "No proxy configured" << std::endl, void(); // Display proxy settings. std::cout << "Proxy Type: " << *proxyTypeToString(settings->getProxyType()) << "\n" << "Proxy URI: " << settings->getProxyURL() << "\n" << "Proxy Username: " << settings->getUsername() << "\n" << "Proxy Password: " << settings->getPassword() << "\n"; } void exec_proxyset(autocomplete::ACState& state) { // Try and retrieve current proxy settings. Proxy settings = client->httpio->getproxy().value_or(Proxy()); // Update settings. settings.setCredentials(state.extractflagparam("-user").value_or(settings.getUsername()), state.extractflagparam("-password").value_or(settings.getPassword())); settings.setProxyURL(state.extractflagparam("-uri").value_or(settings.getProxyURL())); auto oldType = proxyTypeToString(settings.getProxyType()); auto newType = proxyTypeFromString(state.extractflagparam("-type").value_or(*oldType)); settings.setProxyType(newType); // Apply new settings. client->httpio->setproxy(settings); } void exec_udp_send_recv(autocomplete::ACState& state) { const string& address = state.words[1].s; bool addressIsIPv4 = address.find(':') == std::string::npos; bool convertIPv4toIPv6 = state.extractflag("-IPv4toIPv6"); const string& finalAddress = addressIsIPv4 && convertIPv4toIPv6 ? "::ffff:" + address : address; UdpSocket socket(finalAddress, atoi(state.words[2].s.c_str())); auto sendError = socket.sendSyncMessage(state.words[3].s); if (sendError.code) { cout << "Failed to send (" << sendError.code << "): " << sendError.message << endl; return; } auto timeout = std::chrono::high_resolution_clock::now() + std::chrono::seconds(atol(state.words[4].s.c_str())); auto received = socket.receiveSyncMessage(timeout); if (received.code) { cout << "Failed to receive (" << received.code << "): " << received.message << endl; } else { cout << "Received: " << received.message << endl; } } static file_service::FileID toFileID(autocomplete::ACState& state) { using file_service::FileID; // User's passed us an encoded ID. if (auto id = state.extractflagparam("-id")) return FileID::from(*id); // User's passed us a path. if (auto path = state.extractflagparam("-path")) { // Found the node denoted by the user's path. if (auto node = nodebypath(path->c_str())) return FileID::from(node->nodeHandle()); } // User's passed us bad flags or a bad node path. return FileID(); } static void exec_fileserviceinfo(autocomplete::ACState& state) { using file_service::toString; // What file does the user want us to describe? auto id = toFileID(state); // Try and retrieve a description of the file the user's specified. auto info = client->mFileService.info(id); // Couldn't retrieve a description. if (!info) { conlock(std::cerr) << "Couldn't retrieve file information: " << toString(info.error()) << std::endl; return; } // Print the description. conlock(std::cout) << "File " << toString(id) << ":\n" << "Allocated Size" << info->allocatedSize() << "\n" << "Handle: " << toNodeHandle(info->handle()) << "\n" << "Modified: " << displayTime(info->modified()) << "\n" << "Reported Size: " << info->reportedSize() << "\n" << "Size: " << info->size() << std::endl; } static void exec_fileserviceread(autocomplete::ACState& state) { // Convenience. using namespace file_service; // Disambiguate. using file_service::File; // What file does the user want to read from? auto id = toFileID(state); // Try and open the file the user's specified. auto file = client->mFileService.open(id); // Couldn't open the file. if (!file) { conlock(std::cerr) << "Couldn't open file for reading: " << toString(file.error()); return; } // Try and translate a string into a u64 value. auto translate = [](const std::string& string) { // An empty string can't contain a value. if (string.empty()) return std::optional(); std::uint64_t value; // Convenience. auto begin = string.data(); auto end = begin + string.size(); // Try and translate string into a u64 value. auto [last, result] = std::from_chars(begin, end, value); // Couldn't translate the string. if (last != end || result != std::errc{}) return std::optional(); // Return value to caller. return std::optional(value); }; // translate // Assume the user wants to read the entire file. std::uint64_t offset = 0; std::uint64_t length = file->info().size(); // User wants to start reading from a particular offset. if (state.words.size() > 2) { // Try and translate the user's offset. auto offset_ = translate(state.words[2].s); // Couldn't translate the user's offset. if (!offset_) { conlock(std::cerr) << "Invalid offset: " << state.words[2].s << std::endl; return; } offset = *offset_; } // User wants to read only so much of the file. if (state.words.size() > 3) { // Try and translate the user's length. auto length_ = translate(state.words[3].s); // Couldn't translate the user's length. if (!length_) { conlock(std::cerr) << "Invalid length: " << state.words[3].s << std::endl; return; } length = *length_; } // Cleaner than complex capture lists. class Reader { File mFile; std::uint64_t mOffset; std::uint64_t mLength; public: Reader(File file, std::uint64_t offset, std::uint64_t length): mFile(std::move(file)), mOffset(offset), mLength(length) {} // Called when our read has completed. void operator()(FileResultOr result) { // The read failed. if (!result) { conlock(std::cerr) << "Read failed: " << toString(result.error()) << std::endl; return; } // How much data were we able to read? auto length = result->mLength; // Read's completed. if (!length) { conlock(std::cout) << "Read completed." << std::endl; return; } // Bump our offset. mOffset += length; // Kick off another read. read(); } // Kick off the read. void read() { mFile.read(*this, mOffset, mLength - mOffset); } }; // Reader // Kick off the read. Reader(std::move(*file), offset, length).read(); } autocomplete::ACN autocompleteSyntax() { using namespace autocomplete; std::unique_ptr p(new Either(" ")); p->Add(exec_apiurl, sequence(text("apiurl"), opt(sequence(param("url"), opt(param("disablepkp")))))); p->Add(exec_useragent, sequence(text("useragent"), opt(param("new_user_agent")))); p->Add(exec_login, sequence(text("login"), opt(flag("-fresh")), either(sequence(param("email"), opt(param("password"))), sequence(exportedLink(false, true), opt(param("auth_key"))), param("session"), sequence(text("autoresume"), opt(param("id")))))); p->Add(exec_begin, sequence(text("begin"), opt(flag("-e++")), opt(either(sequence(param("firstname"), param("lastname")), // to create an ephemeral++ param("ephemeralhandle#ephemeralpw"), // to resume an ephemeral param("session"))))); // to resume an ephemeral++ p->Add(exec_signup, sequence(text("signup"), either(sequence(param("email"), param("name")), param("confirmationlink")))); p->Add(exec_cancelsignup, sequence(text("cancelsignup"))); p->Add(exec_session, sequence(text("session"), opt(sequence(text("autoresume"), opt(param("id")))))); p->Add(exec_mount, sequence(text("mount"))); p->Add(exec_ls, sequence(text("ls"), opt(flag("-R")), opt(sequence(flag("-tofile"), param("filename"))), opt(remoteFSFolder(client, &cwd)))); p->Add(exec_cd, sequence(text("cd"), opt(remoteFSFolder(client, &cwd)))); p->Add(exec_pwd, sequence(text("pwd"))); p->Add(exec_lcd, sequence(text("lcd"), opt(localFSFolder()))); p->Add(exec_llockfile, sequence(text("llockfile"), opt(flag("-read")), opt(flag("-write")), opt(flag("-unlock")), localFSFile())); p->Add(exec_lls, sequence(text("lls"), opt(flag("-R")), opt(localFSFolder()))); p->Add(exec_lpwd, sequence(text("lpwd"))); p->Add(exec_lmkdir, sequence(text("lmkdir"), localFSFolder())); p->Add(exec_import, sequence(text("import"), exportedLink(true, false))); p->Add(exec_folderlinkinfo, sequence(text("folderlink"), opt(param("link")))); p->Add(exec_open, sequence(text("open"), exportedLink(false, true), opt(param("authToken")))); p->Add(exec_put, sequence(text("put"), opt(flag("-r")), opt(flag("-noversion")), opt(flag("-version")), opt(flag("-versionreplace")), opt(flag("-allowduplicateversions")), localFSPath("localpattern"), opt(either(remoteFSPath(client, &cwd, "dst"),param("dstemail"))))); p->Add(exec_putq, sequence(text("putq"), repeat(either(flag("-active"), flag("-all"), flag("-count"))), opt(param("cancelslot")))); p->Add(exec_get, sequence(text("get"), opt(sequence(flag("-r"), opt(flag("-foldersonly")))), remoteFSPath(client, &cwd), opt(sequence(param("offset"), opt(param("length")))))); p->Add(exec_get, sequence(text("get"), flag("-re"), param("regularexpression"))); p->Add(exec_get, sequence(text("get"), exportedLink(true, false), opt(sequence(param("offset"), opt(param("length")))))); p->Add(exec_getq, sequence(text("getq"), repeat(either(flag("-active"), flag("-all"), flag("-count"))), opt(param("cancelslot")))); p->Add(exec_more, sequence(text("more"), opt(remoteFSPath(client, &cwd)))); p->Add(exec_pause, sequence(text("pause"), either(text("status"), sequence(opt(either(text("get"), text("put"))), opt(text("hard")))))); p->Add(exec_getfa, sequence(text("getfa"), wholenumber(1), opt(remoteFSPath(client, &cwd)), opt(text("cancel")))); #ifdef USE_MEDIAINFO p->Add(exec_mediainfo, sequence(text("mediainfo"), either(sequence(text("calc"), localFSFile()), sequence(text("show"), remoteFSFile(client, &cwd))))); #endif p->Add(exec_smsverify, sequence(text("smsverify"), either(sequence(text("send"), param("phonenumber"), opt(param("reverifywhitelisted"))), sequence(text("code"), param("verificationcode"))))); p->Add(exec_verifiedphonenumber, sequence(text("verifiedphone"))); p->Add(exec_resetverifiedphonenumber, sequence(text("resetverifiedphone"))); p->Add(exec_mkdir, sequence(text("mkdir"), opt(flag("-allowduplicate")), opt(flag("-exactleafname")), opt(flag("-writevault")), remoteFSFolder(client, &cwd))); p->Add(exec_rm, sequence(text("rm"), remoteFSPath(client, &cwd), opt(sequence(flag("-regexchild"), param("regex"))))); p->Add(exec_mv, sequence(text("mv"), remoteFSPath(client, &cwd, "src"), remoteFSPath(client, &cwd, "dst"))); p->Add(exec_cp, sequence(text("cp"), opt(flag("-noversion")), opt(flag("-version")), opt(flag("-versionreplace")), opt(flag("-allowduplicateversions")), remoteFSPath(client, &cwd, "src"), either(remoteFSPath(client, &cwd, "dst"), param("dstemail")))); p->Add(exec_du, sequence(text("du"), opt(flag("-listfolders")), opt(remoteFSPath(client, &cwd)))); p->Add(exec_numberofnodes, sequence(text("nn"))); p->Add(exec_numberofchildren, sequence(text("nc"), opt(remoteFSPath(client, &cwd)))); p->Add(exec_searchbyname, sequence(text("sbn"), param("name"), opt(param("nodeHandle")), opt(flag("-norecursive")), opt(flag("-nosensitive")))); p->Add(exec_nodedescription, sequence(text("nodedescription"), remoteFSPath(client, &cwd), opt(either(flag("-remove"), sequence(flag("-set"), param("description")))))); p->Add(exec_nodelabel, sequence(text("nodelabel"), remoteFSPath(client, &cwd), opt(either(flag("-remove"), sequence(flag("-set"), param("label")))))); p->Add(exec_nodesensitive, sequence(text("nodesensitive"), remoteFSPath(client, &cwd), opt(flag("-remove")))); p->Add(exec_nodeTag, sequence(text("nodetag"), remoteFSPath(client, &cwd), opt(either(sequence(flag("-remove"), param("tag")), sequence(flag("-add"), param("tag")), sequence(flag("-update"), param("newtag"), param("oldtag")))))); #ifdef ENABLE_SYNC p->Add(exec_setdevicename, sequence(text("setdevicename"), param("device_name"))); p->Add(exec_getdevicename, sequence(text("getdevicename"))); p->Add(exec_setextdrivename, sequence(text("setextdrivename"), param("drive_path"), param("drive_name"))); p->Add(exec_getextdrivename, sequence(text("getextdrivename"), opt(either(sequence(flag("-id"), param("b64driveid")), sequence(flag("-path"), param("drivepath")))))); p->Add(exec_setmybackups, sequence(text("setmybackups"), param("mybackup_folder"))); p->Add(exec_getmybackups, sequence(text("getmybackups"))); p->Add(exec_backupcentre, sequence(text("backupcentre"), opt(either( sequence(flag("-del"), param("backup_id"), opt(param("move_to_handle"))), sequence(flag("-purge")), sequence(either(flag("-stop"), flag("-pause"), flag("-resume")), param("backup_id")))))); p->Add(exec_syncadd, sequence(text("sync"), text("add"), opt(flag("-scan-only")), opt(sequence(flag("-scan-interval"), param("interval-secs"))), either( sequence(flag("-backup"), opt(sequence(flag("-external"), param("drivePath"))), opt(sequence(flag("-name"), param("syncname"))), localFSFolder("source")), sequence(opt(sequence(flag("-name"), param("syncname"))), localFSFolder("source"), remoteFSFolder(client, &cwd, "target"))))); p->Add(exec_syncrename, sequence(text("sync"), text("rename"), backupID(*client), param("newname"))); p->Add(exec_syncclosedrive, sequence(text("sync"), text("closedrive"), localFSFolder("drive"))); p->Add(exec_syncexport, sequence(text("sync"), text("export"), opt(localFSFile("outputFile")))); p->Add(exec_syncimport, sequence(text("sync"), text("import"), localFSFile("inputFile"))); p->Add(exec_syncopendrive, sequence(text("sync"), text("opendrive"), localFSFolder("drive"))); p->Add(exec_synclist, sequence(text("sync"), text("list"))); p->Add(exec_syncremove, sequence(text("sync"), text("remove"), either(backupID(*client), sequence(flag("-by-local-path"), localFSFolder()), sequence(flag("-by-remote-path"), remoteFSFolder(client, &cwd))), opt(param("backupdestinationfolder")))); p->Add(exec_syncstatus, sequence(text("sync"), text("status"), opt(param("id")))); p->Add(exec_syncxable, sequence(text("sync"), either(text("run"), text("pause"), text("suspend"), text("disable")), opt(sequence(flag("-error"), param("errorID"))), param("id"))); p->Add(exec_syncrescan, sequence(text("sync"), text("rescan"), param("id"))); p->Add(exec_syncoutput, sequence(text("sync"), text("output"), either(text("local_change_detection"), text("remote_change_detection"), text("transfer_activity"), text("folder_sync_state"), text("detail_log"), text("all")), either(text("on"), text("off")))); #endif p->Add(exec_export, sequence(text("export"), remoteFSPath(client, &cwd), opt(flag("-mega-hosted")), opt(either(flag("-writable"), param("expiretime"), text("del"))))); p->Add(exec_encryptLink, sequence(text("encryptlink"), param("link"), param("password"))); p->Add(exec_decryptLink, sequence(text("decryptlink"), param("link"), param("password"))); p->Add(exec_share, sequence(text("share"), opt(sequence(remoteFSPath(client, &cwd), opt(sequence(contactEmail(client), opt(either(text("r"), text("rw"), text("full"))), opt(param("origemail")))))))); p->Add(exec_invite, sequence(text("invite"), param("dstemail"), opt(either(sequence(text("clink"), param("link")), text("del"), text("rmd"), param("origemail"))))); p->Add(exec_clink, sequence(text("clink"), either(text("renew"), sequence(text("query"), param("handle")), sequence(text("del"), opt(param("handle")))))); p->Add(exec_ipc, sequence(text("ipc"), param("handle"), either(text("a"), text("d"), text("i")))); p->Add(exec_showpcr, sequence(text("showpcr"))); p->Add(exec_users, sequence(text("users"), opt(sequence(contactEmail(client), text("del"))))); p->Add(exec_getemail, sequence(text("getemail"), param("handle_b64"))); p->Add(exec_getua, sequence(text("getua"), param("attrname"), opt(contactEmail(client)))); p->Add(exec_putua, sequence(text("putua"), param("attrname"), opt(either( text("del"), sequence(text("set"), param("string")), sequence(text("map"), param("key"), param("value")), sequence(text("load"), localFSFile()))))); #ifdef DEBUG p->Add(exec_delua, sequence(text("delua"), param("attrname"))); p->Add(exec_devcommand, sequence(text("devcommand"), param("subcommand"), opt(sequence(flag("-e"), param("email"))), opt(sequence(flag("-c"), param("campaign"), flag("-g"), param("group_id"))), opt(sequence(flag("-q"), param("quota_in_months"), flag("-l"), param("account_level"))))); #endif #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED p->Add(exec_simulatecondition, sequence(text("simulatecondition"), opt(text("ETOOMANY")))); #endif p->Add(exec_alerts, text("alerts")); p->Add(exec_alerts_new, sequence(text("alerts"), text("new"))); p->Add(exec_alerts_old, sequence(text("alerts"), text("old"))); p->Add(exec_alerts_number, sequence(text("alerts"), wholenumber(10))); p->Add(exec_alerts_notify, sequence(text("alerts"), text("notify"))); p->Add(exec_alerts_seen, sequence(text("alerts"), text("seen"))); p->Add(exec_alerts_test_reminder, sequence(text("alerts"), text("test_reminder"))); p->Add(exec_alerts_test_payment, sequence(text("alerts"), text("test_payment"))); p->Add(exec_alerts_test_payment_v2, sequence(text("alerts"), text("test_payment_v2"))); p->Add(exec_alerts_add_reminder, sequence(text("alerts"), text("add_reminder"), param("timestamp_offset"), param("expiry_offset"))); p->Add( exec_alerts_add_payment, sequence(text("alerts"), text("add_payment"), param("timestamp_offset"), param("result"))); p->Add(exec_recentactions, sequence(text("recentactions"), param("hours"), param("maxcount"), opt(flag("-nosensitive")))); p->Add(exec_recentnodes, sequence(text("recentnodes"), param("hours"), param("maxcount"))); p->Add(exec_killsession, sequence(text("killsession"), either(text("all"), param("sessionid")))); p->Add(exec_whoami, sequence(text("whoami"), repeat(either(flag("-storage"), flag("-transfer"), flag("-pro"), flag("-transactions"), flag("-purchases"), flag("-sessions"))))); p->Add(exec_verifycredentials, sequence(text("credentials"), either(text("show"), text("status"), text("verify"), text("reset")), opt(contactEmail(client)))); p->Add(exec_manualverif, sequence(text("verification"), opt(either(flag("-on"), flag("-off"))))); p->Add(exec_passwd, sequence(text("passwd"))); p->Add(exec_reset, sequence(text("reset"), contactEmail(client), opt(text("mk")))); p->Add(exec_recover, sequence(text("recover"), param("recoverylink"))); p->Add(exec_cancel, sequence(text("cancel"), opt(param("cancellink")))); p->Add(exec_email, sequence(text("email"), opt(either(param("newemail"), param("emaillink"))))); p->Add(exec_retry, sequence(text("retry"))); p->Add(exec_recon, sequence(text("recon"))); p->Add(exec_reload, sequence(text("reload"), opt(text("nocache")))); p->Add(exec_logout, sequence(text("logout"), opt(flag("-keepsyncconfigs")))); p->Add(exec_locallogout, sequence(text("locallogout"))); p->Add(exec_version, sequence(text("version"))); p->Add(exec_debug, sequence(text("debug"), opt(either(flag("-on"), flag("-off"), flag("-verbose"))), opt(either(flag("-console"), flag("-noconsole"))), opt(either(flag("-nofile"), sequence(flag("-file"), localFSFile()))) )); #if defined(WIN32) && defined(NO_READLINE) p->Add(exec_clear, sequence(text("clear"))); p->Add(exec_codepage, sequence(text("codepage"), opt(sequence(wholenumber(65001), opt(wholenumber(65001)))))); p->Add(exec_log, sequence(text("log"), either(text("utf8"), text("utf16"), text("codepage")), localFSFile())); #endif p->Add(exec_test, sequence(text("test"), opt(param("data")))); p->Add(exec_fingerprint, sequence(text("fingerprint"), localFSFile("localfile"))); #ifdef ENABLE_CHAT p->Add(exec_chats, sequence(text("chats"), opt(param("chatid")))); p->Add(exec_chatc, sequence(text("chatc"), param("group"), repeat(opt(sequence(contactEmail(client), either(text("ro"), text("sta"), text("mod"))))))); p->Add(exec_chati, sequence(text("chati"), param("chatid"), contactEmail(client), either(text("ro"), text("sta"), text("mod")))); p->Add(exec_chatcp, sequence(text("chatcp"), flag("-meeting"), param("mownkey"), opt(sequence(text("t"), param("title64"))), repeat(sequence(contactEmail(client), either(text("ro"), text("sta"), text("mod")))))); p->Add(exec_chatr, sequence(text("chatr"), param("chatid"), opt(contactEmail(client)))); p->Add(exec_chatu, sequence(text("chatu"), param("chatid"))); p->Add(exec_chatup, sequence(text("chatup"), param("chatid"), param("userhandle"), either(text("ro"), text("sta"), text("mod")))); p->Add(exec_chatpu, sequence(text("chatpu"))); p->Add(exec_chatga, sequence(text("chatga"), param("chatid"), param("nodehandle"), param("uid"))); p->Add(exec_chatra, sequence(text("chatra"), param("chatid"), param("nodehandle"), param("uid"))); p->Add(exec_chatst, sequence(text("chatst"), param("chatid"), param("title64"))); p->Add(exec_chata, sequence(text("chata"), param("chatid"), param("archive"))); p->Add(exec_chatl, sequence(text("chatl"), param("chatid"), either(text("del"), text("query")))); p->Add(exec_chatsm, sequence(text("chatsm"), param("chatid"), opt(param("title64")))); p->Add(exec_chatlu, sequence(text("chatlu"), param("publichandle"))); p->Add(exec_chatlj, sequence(text("chatlj"), param("publichandle"), param("unifiedkey"))); #endif p->Add(exec_setmaxdownloadspeed, sequence(text("setmaxdownloadspeed"), opt(wholenumber(10000)))); p->Add(exec_setmaxuploadspeed, sequence(text("setmaxuploadspeed"), opt(wholenumber(10000)))); p->Add(exec_setmaxloglinesize, sequence(text("setmaxloglinesize"), wholenumber(10000))); p->Add(exec_setlogjson, sequence(text("setlogjson"), repeat(either(flag("-chunk-received"), flag("-chunk-processing"), flag("-chunk-consumed"), flag("-sending"), flag("-nonchunk-received"))))); p->Add(exec_getlogjson, sequence(text("getlogjson"))); p->Add(exec_handles, sequence(text("handles"), opt(either(text("on"), text("off"))))); p->Add(exec_showattrs, sequence(text("showattrs"), opt(either(text("on"), text("off"))))); p->Add(exec_timelocal, sequence(text("mtimelocal"), either(text("set"), text("get")), localFSPath(), opt(param("datetime")))); p->Add(exec_mfac, sequence(text("mfac"), param("email"))); p->Add(exec_mfae, sequence(text("mfae"))); p->Add(exec_mfad, sequence(text("mfad"), param("pin"))); #if defined(WIN32) && defined(NO_READLINE) p->Add(exec_autocomplete, sequence(text("autocomplete"), opt(either(text("unix"), text("dos"))))); p->Add(exec_history, sequence(text("history"))); #elif !defined(NO_READLINE) p->Add(exec_history, sequence(text("history"), either(text("clear"), text("list"), sequence(either(text("read"), text("record"), text("write")), localFSFile("history"))))); #endif p->Add(exec_help, either(text("help"), text("h"), text("?"))); p->Add(exec_quit, either(text("quit"), text("q"), text("exit"))); p->Add(exec_find, sequence(text("find"), text("raided"))); p->Add(exec_findemptysubfoldertrees, sequence(text("findemptysubfoldertrees"), opt(flag("-movetotrash")))); p->Add(exec_fileversions, sequence(text("fversions"), param("path"))); #ifdef MEGA_MEASURE_CODE p->Add(exec_deferRequests, sequence(text("deferrequests"), repeat(either(flag("-putnodes"))))); p->Add(exec_sendDeferred, sequence(text("senddeferred"), opt(flag("-reset")))); p->Add(exec_codeTimings, sequence(text("codetimings"), opt(flag("-reset")))); #endif p->Add(exec_treecompare, sequence(text("treecompare"), localFSPath(), remoteFSPath(client, &cwd))); p->Add(exec_generatetestfilesfolders, sequence(text("generatetestfilesfolders"), repeat(either( sequence(flag("-folderdepth"), param("depth")), sequence(flag("-folderwidth"), param("width")), sequence(flag("-filecount"), param("count")), sequence(flag("-filesize"), param("size")), sequence(flag("-nameprefix"), param("prefix")))), localFSFolder("parent"))); p->Add(exec_generatesparsefile, sequence(text("generatesparsefile"), opt(sequence(flag("-filesize"), param("size"))), localFSFile("targetfile"))); p->Add(exec_generate_put_fileversions, sequence(text("generate_put_fileversions"), opt(sequence(flag("-count"), param("n"))), localFSFile("targetfile"))); p->Add(exec_lreplace, sequence(text("lreplace"), either(flag("-file"), flag("-folder")), localFSPath("existing"), param("content"))); p->Add(exec_lrenamereplace, sequence(text("lrenamereplace"), either(flag("-file"), flag("-folder")), localFSPath("existing"), param("content"), localFSPath("renamed"))); p->Add(exec_cycleUploadDownload, sequence(text("cycleuploaddownload"), repeat(either( sequence(flag("-filecount"), param("count")), sequence(flag("-filesize"), param("size")), sequence(flag("-nameprefix"), param("prefix")))), localFSFolder("localworkingfolder"), remoteFSFolder(client, &cwd, "remoteworkingfolder"))); p->Add(exec_querytransferquota, sequence(text("querytransferquota"), param("filesize"))); p->Add(exec_getcloudstorageused, sequence(text("getcloudstorageused"))); p->Add(exec_getuserquota, sequence(text("getuserquota"), repeat(either(flag("-storage"), flag("-transfer"), flag("-pro"))))); p->Add(exec_getuserdata, text("getuserdata")); p->Add(exec_getdiscountcodeinfo, sequence(text("getdiscountcodeinfo"), param("code"))); p->Add(exec_showattributes, sequence(text("showattributes"), remoteFSPath(client, &cwd))); p->Add(exec_setmaxconnections, sequence(text("setmaxconnections"), either(text("put"), text("get")), opt(wholenumber(4)))); p->Add(exec_metamac, sequence(text("metamac"), localFSPath(), remoteFSPath(client, &cwd))); p->Add(exec_compare_file_and_node, sequence(text("compfilewithnode"), localFSPath(), remoteFSPath(client, &cwd))); p->Add(exec_banner, sequence(text("banner"), either(text("get"), sequence(text("dismiss"), param("id"))))); p->Add(exec_drivemonitor, sequence(text("drivemonitor"), opt(either(flag("-on"), flag("-off"))))); p->Add(exec_driveid, sequence(text("driveid"), either(sequence(text("get"), localFSFolder()), sequence(text("set"), localFSFolder(), opt(text("force")))))); p->Add(exec_randomfile, sequence(text("randomfile"), localFSPath("outputPath"), opt(param("lengthKB")))); p->Add(exec_setsandelements, sequence(text("setsandelements"), either(text("list"), sequence(text("newset"), param("type"), opt(param("name"))), sequence(text("updateset"), param("id"), opt(sequence(flag("-n"), opt(param("name")))), opt(sequence(flag("-c"), opt(param("cover"))))), sequence(text("removeset"), param("id")), sequence(text("newelement"), param("setid"), param("nodehandle"), opt(sequence(flag("-n"), param("name"))), opt(sequence(flag("-o"), param("order")))), sequence(text("updateelement"), param("sid"), param("eid"), opt(sequence(flag("-n"), opt(param("name")))), opt(sequence(flag("-o"), param("order")))), sequence(text("removeelement"), param("sid"), param("eid")), sequence(text("export"), param("sid"), opt(flag("-disable"))), sequence(text("getpubliclink"), param("sid")), sequence(text("fetchpublicset"), param("publicsetlink")), text("getsetinpreview"), text("stoppublicsetpreview"), sequence(text("downloadelement"), param("sid"), param("eid")) ))); p->Add(exec_reqstat, sequence(text("reqstat"), opt(either(flag("-on"), flag("-off"))))); p->Add(exec_getABTestValue, sequence(text("getabflag"), param("flag"))); p->Add(exec_contactVerificationWarning, sequence(text("verificationwarnings"), opt(either(flag("-on"), flag("-off"))))); /* MEGA VPN commands */ p->Add(exec_getvpnregions, text("getvpnregions")); p->Add(exec_getvpncredentials, sequence(text("getvpncredentials"), opt(sequence(flag("-s"), param("slotID"))), opt(flag("-noregions")))); p->Add(exec_putvpncredential, sequence(text("putvpncredential"), param("region"), opt(sequence(flag("-file"), param("credentialfilewithoutextension"))), opt(flag("-noconsole")))); p->Add(exec_delvpncredential, sequence(text("delvpncredential"), param("slotID"))); p->Add(exec_checkvpncredential, sequence(text("checkvpncredential"), param("userpublickey"))); p->Add(exec_getnetworktestserver, text("getnetworktestserver")); p->Add(exec_networktest, text("networktest")); /* MEGA VPN commands END */ p->Add(exec_fetchcreditcardinfo, text("cci")); p->Add( exec_passwordmanager, sequence( text("pwdman"), either(text("list"), text("getbase"), text("createbase"), text("removebase"), sequence(text("newfolder"), param("parenthandle"), param("name")), sequence(text("renamefolder"), param("handle"), param("name")), sequence(text("removefolder"), param("handle")), sequence(text("newpassentry"), param("parenthandle"), param("name"), param("pwd"), opt(sequence(flag("-url"), param("url"))), opt(sequence(flag("-u"), param("username"))), opt(sequence(flag("-n"), param("notes"))), opt(sequence(flag("-totp-shse"), param("shared_secret"))), opt(sequence(flag("-totp-nd"), param("n_digits"))), opt(sequence(flag("-totp-expt"), param("expiration_time"))), opt(sequence(flag("-totp-alg"), either(text("sha1"), text("sha256"), text("sha512"))))), sequence(text("newcreditcardentry"), param("parenthandle"), param("name"), param("cardnumber"), opt(sequence(flag("-n"), param("notes"))), opt(sequence(flag("-u"), param("card_holder"))), opt(sequence(flag("-cvv"), param("cvv"))), opt(sequence(flag("-exp"), param("expirationdate")))), sequence(text("newpassentries"), param("parenthandle"), repeat(sequence(param("name"), param("uname"), param("pwd")))), sequence(text("getentrydata"), param("nodehandle")), sequence(text("renameentry"), param("nodehandle"), param("name")), sequence( text("updatepassentry"), param("nodehandle"), opt(sequence(flag("-p"), param("pwd"))), opt(sequence(flag("-url"), param("url"))), opt(sequence(flag("-u"), param("username"))), opt(sequence(flag("-n"), param("note"))), opt(either( flag("-totp-remove"), sequence(opt(sequence(flag("-totp-shse"), param("shared_secret"))), opt(sequence(flag("-totp-nd"), param("n_digits"))), opt(sequence(flag("-totp-expt"), param("expiration_time"))), opt(sequence( flag("-totp-alg"), either(text("sha1"), text("sha256"), text("sha512")))))))), sequence(text("updatecreditcardentry"), param("nodehandle"), opt(sequence(flag("-nu"), param("card_number"))), opt(sequence(flag("-n"), param("notes"))), opt(sequence(flag("-u"), param("card_holder"))), opt(sequence(flag("-cvv"), param("cvv"))), opt(sequence(flag("-exp"), param("expirationdate")))), sequence(text("removeentry"), param("nodehandle")), sequence(text("generatetotptoken"), param("nodehandle")), sequence(text("import"), sequence(flag("-source"), either(text("google"))), localFSPath("file"), param("parenthandle"))))); p->Add(exec_generatepassword, sequence(text("generatepassword"), either(sequence(text("chars"), param("length"), opt(flag("-useUpper")), opt(flag("-useDigits")), opt(flag("-useSymbols"))) ))); p->Add(exec_fusedb, sequence(text("fuse"), text("db"), either(text("downgrade"), text("upgrade")), localFSFile("database"), wholenumber(0))); p->Add( exec_fuseflags, sequence(text("fuse"), text("flags"), repeat(either( sequence(flag("-cache-clean-age-threshold"), wholenumber("seconds", 5 * 60)), sequence(flag("-cache-clean-interval"), wholenumber("seconds", 5 * 60)), sequence(flag("-cache-clean-size-threshold"), wholenumber("count", 64)), sequence(flag("-cache-max-size"), wholenumber("count", 256)), sequence(flag("-flush-delay"), wholenumber("seconds", 4)), sequence(flag("-log-level"), either(text("DEBUG"), text("ERROR"), text("INFO"), text("WARNING"))), sequence(flag("-file-explorer-view"), either(text("NONE"), text("LIST"))), sequence(flag("-mount-max-thread-count"), wholenumber("count", 16)), sequence(flag("-mount-max-thread-idle-time"), wholenumber("seconds", 16)), sequence(flag("-mount-min-thread-count"), wholenumber("count", 0)), sequence(flag("-service-max-thread-count"), wholenumber("count", 16)), sequence(flag("-service-max-thread-idle-time"), wholenumber("seconds", 16)), sequence(flag("-service-min-thread-count"), wholenumber("count", 0)))))); p->Add(exec_fusemountadd, sequence(text("fuse"), text("mount"), text("add"), repeat(either(sequence(flag("-name"), param("name")), flag("-persistent"), flag("-read-only"))), remoteFSFolder(client, &cwd, "source"), opt(localFSFolder("target")))); p->Add(exec_fusemountdisable, sequence(text("fuse"), text("mount"), text("disable"), param("name"), opt(flag("-remember")))); p->Add(exec_fusemountenable, sequence(text("fuse"), text("mount"), text("enable"), param("name"), opt(flag("-remember")))); p->Add(exec_fusemountflags, sequence(text("fuse"), text("mount"), text("flags"), param("name"), repeat(either(flag("-disabled-at-startup"), flag("-enabled-at-startup"), sequence(flag("-name"), param("name")), flag("-persistent"), flag("-read-only"), flag("-transient"), flag("-writable"))))); p->Add(exec_fusemountlist, sequence(text("fuse"), text("mount"), text("list"), opt(flag("-only-active")))); p->Add(exec_fusemountremove, sequence(text("fuse"), text("mount"), text("remove"), param("name"))); p->Add(exec_getpricing, sequence(text("getpricing"), opt(sequence(flag("-country"), param("countryCode"))))); p->Add(exec_collectAndPrintTransferStats, sequence(text("getTransferStats"), opt(either(flag("-uploads"), flag("-downloads"))))); p->Add(exec_hashcash, sequence(text("hashcash"), opt(either(flag("-on"), flag("-off"))))); p->Add(exec_thumbnail, sequence(text("thumbnail"), localFSFile("destinationPath"), localFSFile("sourcePath"), opt(either(flag("-thumbnail"), flag("-preview"))))); p->Add(exec_getmyip, text("getmyip")); p->Add(exec_tag_add_remove, sequence(text("tag"), either(text("add"), text("remove")), remoteFSPath(client, &cwd, "node-path"), param("tag"))); p->Add(exec_tag_list_all, sequence(text("tag"), text("list"), text("all"))); p->Add( exec_tag_list_at, sequence(text("tag"), text("list"), text("at"), remoteFSPath(client, &cwd, "node-path"))); p->Add(exec_tag_list_below, sequence(text("tag"), text("list"), text("below"), opt(remoteFSPath(client, &cwd, "node-path")))); p->Add(exec_proxyget, sequence(text("proxy"), text("get"))); p->Add(exec_proxyset, sequence(text("proxy"), text("set"), opt(sequence(flag("-uri"), param("uri"))), opt(sequence(flag("-user"), param("user"))), opt(sequence(flag("-password"), param("password"))), opt(sequence(flag("-type"), param("type"))))); p->Add(exec_udp_send_recv, sequence(text("udp_send_recv"), param("ip"), param("port"), param("message"), param("recv_timeout_sec"), opt(flag("-IPv4toIPv6")))); p->Add(exec_dnsservers, sequence(text("setdns"), either(param("dnslist"), flag("-clear")))); p->Add(exec_cleanVault, text("cleanvault")); p->Add(exec_fileserviceinfo, sequence(text("file-service"), text("info"), either(sequence(flag("-id"), param("id")), sequence(flag("-path"), remoteFSFile(client, &cwd))))); p->Add(exec_fileserviceread, sequence(text("file-service"), text("read"), either(sequence(flag("-id"), param("id")), sequence(flag("-path"), remoteFSFile(client, &cwd))), opt(sequence(param("offset"), opt(param("length")))))); return autocompleteTemplate = std::move(p); } bool recursiveget(fs::path&& localpath, Node* n, bool folders, unsigned& queued) { if (n->type == FILENODE) { if (!folders) { TransferDbCommitter committer(client->tctable); auto file = std::make_unique(n, NodeHandle(), nullptr, -1, 0, nullptr, nullptr, localpath.u8string()); error result = startxfer(committer, std::move(file), *n, client->nextreqtag()); queued += result == API_OK ? 1 : 0; } } else if (n->type == FOLDERNODE || n->type == ROOTNODE) { fs::path newpath = localpath / fs::u8path(n->type == ROOTNODE ? "ROOTNODE" : n->displayname()); if (folders) { std::error_code ec; if (fs::create_directory(newpath, ec) || !ec) { cout << newpath << endl; } else { cout << "Failed trying to create " << newpath << ": " << ec.message() << endl; return false; } } for (auto& node : client->getChildren(n)) { if (!recursiveget(std::move(newpath), node.get(), folders, queued)) { return false; } } } return true; } bool regexget(const string& expression, Node* n, unsigned& queued) { try { std::regex re(expression); if (n->type == FOLDERNODE || n->type == ROOTNODE) { TransferDbCommitter committer(client->tctable); for (auto& node : client->getChildren(n)) { if (node->type == FILENODE) { if (regex_search(string(node->displayname()), re)) { auto file = std::make_unique(node.get()); error result = startxfer(committer, std::move(file), *node, client->nextreqtag()); queued += result == API_OK ? 1 : 0; } } } } } catch (std::exception& e) { cout << "ERROR: " << e.what() << endl; return false; } return true; } struct Login { string email, password, salt, pin; int version; bool succeeded = false; Login() : version(0) { } void reset() { *this = Login(); } void login(MegaClient* mc) { byte keybuf[SymmCipher::KEYLENGTH]; if (version == 1) { if (error e = mc->pw_key(password.c_str(), keybuf)) { cout << "Login error: " << e << endl; } else { mc->saveV1Pwd(password.c_str()); // for automatic upgrade to V2 mc->login(email.c_str(), keybuf, (!pin.empty()) ? pin.c_str() : NULL); } } else if (version == 2 && !salt.empty()) { mc->login2(email.c_str(), password.c_str(), &salt, (!pin.empty()) ? pin.c_str() : NULL); } else { cout << "Login unexpected error" << endl; } } void fetchnodes(MegaClient* mc) { assert(succeeded); cout << "Retrieving account after a succesful login..." << endl; mc->fetchnodes(false, true, false); succeeded = false; } }; static Login login; ofstream* pread_file = NULL; m_off_t pread_file_end = 0; // execute command static void process_line(char* l) { switch (prompt) { case LOGINTFA: if (strlen(l) > 1) { login.pin = l; login.login(client); } else { cout << endl << "The pin length is invalid, please try to login again." << endl; } setprompt(COMMAND); return; case SETTFA: client->multifactorauthsetup(l); setprompt(COMMAND); return; case LOGINPASSWORD: if (signupcode.size()) { // verify correctness of supplied signup password client->pw_key(l, pwkey); SymmCipher pwcipher(pwkey); pwcipher.ecb_decrypt(signuppwchallenge); if (MemAccess::get((const char*)signuppwchallenge + 4)) { cout << endl << "Incorrect password, please try again." << endl; } signupcode.clear(); } else if (recoverycode.size()) // cancelling account --> check password { client->pw_key(l, pwkey); client->validatepwd(l); } else if (changecode.size()) // changing email --> check password to avoid creating an invalid hash { client->pw_key(l, pwkey); client->validatepwd(l); } else { login.password = l; login.login(client); cout << endl << "Logging in..." << endl; } setprompt(COMMAND); return; case OLDPASSWORD: client->pw_key(l, pwkeybuf); if (!memcmp(pwkeybuf, pwkey, sizeof pwkey)) { cout << endl; setprompt(NEWPASSWORD); } else { cout << endl << "Bad password, please try again" << endl; setprompt(COMMAND); } return; case NEWPASSWORD: newpassword = l; client->pw_key(l, newpwkey); cout << endl; setprompt(PASSWORDCONFIRM); return; case PASSWORDCONFIRM: client->pw_key(l, pwkeybuf); if (memcmp(pwkeybuf, newpwkey, sizeof pwkeybuf)) { cout << endl << "Mismatch, please try again" << endl; } else { if (signupemail.size()) { string buf = client->sendsignuplink2(signupemail.c_str(), newpassword.c_str(), signupname.c_str()); cout << endl << "Updating derived key of ephemeral session, session ID: "; cout << Base64Str(client->me) << "#"; cout << Base64Str((const byte*)buf.data()) << endl; } else if (recoveryemail.size() && recoverycode.size()) { cout << endl << "Resetting password..." << endl; if (hasMasterKey) { client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpassword.c_str(), masterkey); } else { client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpassword.c_str(), NULL); } recoverycode.clear(); recoveryemail.clear(); hasMasterKey = false; memset(masterkey, 0, sizeof masterkey); } else { if (client->changepw(newpassword.c_str()) == API_OK) { memcpy(pwkey, newpwkey, sizeof pwkey); cout << endl << "Changing password..." << endl; } else { cout << "You must be logged in to change your password." << endl; } } } setprompt(COMMAND); signupemail.clear(); return; case MASTERKEY: cout << endl << "Retrieving private RSA key for checking integrity of the Master Key..." << endl; Base64::atob(l, masterkey, sizeof masterkey); client->getprivatekey(recoverycode.c_str()); return; case COMMAND: try { std::string consoleOutput; ac::autoExec(string(l), string::npos, autocompleteTemplate, false, consoleOutput, true); // todo: pass correct unixCompletions flag if (!consoleOutput.empty()) { cout << consoleOutput << flush; } } catch (std::exception& e) { cout << "Command failed: " << e.what() << endl; } return; case PAGER: if (strlen(l) && l[0] == 'q') { setprompt(COMMAND); // quit pager view if 'q' is sent, see README } else { autocomplete::ACState nullState; //not entirely sure about this exec_more(nullState); //else, get one more page } return; } } void exec_ls(autocomplete::ACState& s) { std::shared_ptr n; bool recursive = s.extractflag("-R"); string toFilename; bool toFileFlag = s.extractflagparam("-tofile", toFilename); ofstream toFile; if (toFileFlag) { toFile.open(toFilename); } if (s.words.size() > 1) { n = nodebypath(s.words[1].s.c_str()); } else { n = client->nodeByHandle(cwd); } if (n) { dumptree(n.get(), recursive, 0, NULL, toFileFlag ? &toFile : nullptr); } } void exec_cd(autocomplete::ACState& s) { if (s.words.size() > 1) { if (std::shared_ptr n = nodebypath(s.words[1].s.c_str())) { if (n->type == FILENODE) { cout << s.words[1].s << ": Not a directory" << endl; } else { cwd = n->nodeHandle(); } } else { cout << s.words[1].s << ": No such file or directory" << endl; } } else { cwd = client->mNodeManager.getRootNodeFiles(); } } void exec_rm(autocomplete::ACState& s) { string childregexstring; bool useregex = s.extractflagparam("-regexchild", childregexstring); if (std::shared_ptr n = nodebypath(s.words[1].s.c_str())) { vector > v; sharedNode_list children; if (useregex) { std::regex re(childregexstring); children = client->getChildren(n.get()); for (auto& c : children) { if (std::regex_match(c->displayname(), re)) { v.push_back(c); } } } else { v.push_back(n); } for (auto& d : v) { error e = client->unlink(d.get(), false, 0, false); if (e) { cout << d->displaypath() << ": Deletion failed (" << errorstring(e) << ")" << endl; } } } else { cout << s.words[1].s << ": No such file or directory" << endl; } } void exec_mv(autocomplete::ACState& s) { std::shared_ptr n, tn; string newname; if (s.words.size() > 2) { // source node must exist if ((n = nodebypath(s.words[1].s.c_str()))) { // we have four situations: // 1. target path does not exist - fail // 2. target node exists and is folder - move // 3. target node exists and is file - delete and rename (unless same) // 4. target path exists, but filename does not - rename if ((tn = nodebypath(s.words[2].s.c_str(), NULL, &newname))) { error e; if (newname.size()) { if (tn->type == FILENODE) { cout << s.words[2].s << ": Not a directory" << endl; return; } else { if ((e = client->checkmove(n.get(), tn.get())) == API_OK) { if (!client->checkaccess(n.get(), RDWR)) { cout << "Write access denied" << endl; return; } // rename LocalPath::utf8_normalize(&newname); if ((e = client->setattr(n, attr_map('n', newname), setattr_result, false))) { cout << "Cannot rename file (" << errorstring(e) << ")" << endl; } } else { cout << "Cannot rename file (" << errorstring(e) << ")" << endl; } } } else { if (tn->type == FILENODE) { // (there should never be any orphaned filenodes) if (!tn->parent) { return; } if ((e = client->checkmove(n.get(), tn->parent.get())) == API_OK) { if (!client->checkaccess(n.get(), RDWR)) { cout << "Write access denied" << endl; return; } // overwrite existing target file: rename source... e = client->setattr(n, attr_map('n', tn->attrs.map['n']), setattr_result, false); if (e) { cout << "Rename failed (" << errorstring(e) << ")" << endl; } if (n != tn) { // ...delete target... e = client->unlink(tn.get(), false, 0, false); if (e) { cout << "Remove failed (" << errorstring(e) << ")" << endl; } } } // ...and set target to original target's parent tn = tn->parent; } else { e = client->checkmove(n.get(), tn.get()); } } if (n->parent != tn) { if (e == API_OK) { e = client->rename(n, tn, SYNCDEL_NONE, NodeHandle(), nullptr, false, rename_result); if (e) { cout << "Move failed (" << errorstring(e) << ")" << endl; } } else { cout << "Move not permitted - try copy" << endl; } } } else { cout << s.words[2].s << ": No such directory" << endl; } } else { cout << s.words[1].s << ": No such file or directory" << endl; } } } void exec_cp(autocomplete::ACState& s) { std::shared_ptr n, tn; string targetuser; string newname; error e; VersioningOption vo = UseLocalVersioningFlag; if (s.extractflag("-noversion")) vo = NoVersioning; if (s.extractflag("-version")) vo = ClaimOldVersion; if (s.extractflag("-versionreplace")) vo = ReplaceOldVersion; bool allowDuplicateVersions = s.extractflag("-allowduplicateversions"); if (s.words.size() > 2) { if ((n = nodebypath(s.words[1].s.c_str()))) { if ((tn = nodebypath(s.words[2].s.c_str(), &targetuser, &newname))) { if (!client->checkaccess(tn.get(), RDWR)) { cout << "Write access denied" << endl; return; } if (tn->type == FILENODE) { if (n->type == FILENODE) { // overwrite target if source and taret are files // (there should never be any orphaned filenodes) if (!tn->parent) { return; } // ...delete target... e = client->unlink(tn.get(), false, 0, false); if (e) { cout << "Cannot delete existing file (" << errorstring(e) << ")" << endl; } // ...and set target to original target's parent tn = tn->parent; } else { cout << "Cannot overwrite file with folder" << endl; return; } } } TreeProcCopy_mcli tc; NodeHandle ovhandle; if (!n->keyApplied()) { cout << "Cannot copy a node without key" << endl; return; } if (n->attrstring) { n->applykey(); n->setattr(); if (n->attrstring) { cout << "Cannot copy undecryptable node" << endl; return; } } string sname; if (newname.size()) { sname = newname; LocalPath::utf8_normalize(&sname); } else { attr_map::iterator it = n->attrs.map.find('n'); if (it != n->attrs.map.end()) { sname = it->second; } } if (tn && n->type == FILENODE && !allowDuplicateVersions) { std::shared_ptr ovn = client->childnodebyname(tn.get(), sname.c_str(), true); if (ovn) { if (n->isvalid && ovn->isvalid && *(FileFingerprint*)n.get() == *(FileFingerprint*)ovn.get()) { cout << "Skipping identical node" << endl; return; } ovhandle = ovn->nodeHandle(); } } // determine number of nodes to be copied client->proctree(n, &tc, false, !ovhandle.isUndef()); tc.allocnodes(); // build new nodes array client->proctree(n, &tc, false, !ovhandle.isUndef()); // if specified target is a filename, use it if (newname.size()) { SymmCipher key; string attrstring; // copy source attributes and rename AttrMap attrs; attrs.map = n->attrs.map; attrs.map['n'] = sname; key.setkey((const byte*)tc.nn[0].nodekey.data(), tc.nn[0].type); // JSON-encode object and encrypt attribute string attrs.getjson(&attrstring); tc.nn[0].attrstring.reset(new string); client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); } // tree root: no parent tc.nn[0].parenthandle = UNDEF; tc.nn[0].ovhandle = ovhandle; if (tn) { // add the new nodes client->putnodes(tn->nodeHandle(), vo, std::move(tc.nn), nullptr, gNextClientTag++, false); } else { if (targetuser.size()) { cout << "Attempting to drop into user " << targetuser << "'s inbox..." << endl; client->putnodes(targetuser.c_str(), std::move(tc.nn), gNextClientTag++); } else { cout << s.words[2].s << ": No such file or directory" << endl; } } } else { cout << s.words[1].s << ": No such file or directory" << endl; } } } void exec_du(autocomplete::ACState &s) { bool listfolders = s.extractflag("-listfolders"); std::shared_ptr n; if (s.words.size() > 1) { n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory" << endl; return; } } else { n = client->nodeByHandle(cwd); if (!n) { cout << "cwd not set" << endl; return; } } if (listfolders) { auto list = client->getChildren(n.get()); vector > vec(list.begin(), list.end()); std::sort(vec.begin(), vec.end(), [](shared_ptr & a, shared_ptr & b){ return a->getCounter().files + a->getCounter().folders < b->getCounter().files + b->getCounter().folders; }); for (auto& f : vec) { if (f->type == FOLDERNODE) { NodeCounter nc = f->getCounter(); cout << "folders:" << nc.folders << " files: " << nc.files << " versions: " << nc.versions << " storage: " << (nc.storage + nc.versionStorage) << " " << f->displayname() << endl; } } } else { NodeCounter nc = n->getCounter(); cout << "Total storage used: " << nc.storage << endl; cout << "Total storage used by versions: " << nc.versionStorage << endl << endl; cout << "Total # of files: " << nc.files << endl; cout << "Total # of folders: " << nc.folders << endl; cout << "Total # of versions: " << nc.versions << endl; } } void exec_get(autocomplete::ACState& s) { static int dummyAppData = 0; std::shared_ptr n; string regularexpression; if (s.extractflag("-r")) { // recursive get. create local folder structure first, then queue transfer of all files bool foldersonly = s.extractflag("-foldersonly"); if (!(n = nodebypath(s.words[1].s.c_str()))) { cout << s.words[1].s << ": No such folder (or file)" << endl; } else if (n->type != FOLDERNODE && n->type != ROOTNODE) { cout << s.words[1].s << ": not a folder" << endl; } else { unsigned queued = 0; cout << "creating folders: " << endl; if (recursiveget(fs::current_path(), n.get(), true, queued)) { if (!foldersonly) { cout << "queueing files..." << endl; bool alldone = recursiveget(fs::current_path(), n.get(), false, queued); cout << "queued " << queued << " files for download" << (!alldone ? " before failure" : "") << endl; } } } } else if (s.extractflagparam("-re", regularexpression)) { if (!(n = nodebypath("."))) { cout << ": No current folder" << endl; } else if (n->type != FOLDERNODE && n->type != ROOTNODE) { cout << ": not in a folder" << endl; } else { unsigned queued = 0; if (regexget(regularexpression, n.get(), queued)) { cout << "queued " << queued << " files for download" << endl; } } } else { handle ph = UNDEF; byte key[FILENODEKEYLENGTH]; if (client->parsepubliclink(s.words[1].s.c_str(), ph, key, TypeOfLink::FILE) == API_OK) { cout << "Checking link..." << endl; client->queueCommand(new CommandGetFile( client, key, FILENODEKEYLENGTH, false, ph, false, nullptr, nullptr, nullptr, false /*singleURL*/, true /*forceSSL*/, [key, ph](const Error& e, m_off_t size, dstime /*timeleft*/, std::string* filename, std::string* fingerprint, std::string* fileattrstring, const std::vector& /*tempurls*/, const std::vector& /*ips*/, const std::string& /*fileHandle*/) { if (!fingerprint) // failed processing the command { if (e == API_ETOOMANY && e.hasExtraInfo()) { cout << "Link check failed: " << DemoApp::getExtraInfoErrorString(e) << endl; } else { cout << "Link check failed: " << errorstring(e) << endl; } return true; } cout << "Name: " << *filename << ", size: " << size; if (fingerprint->size()) { cout << ", fingerprint available"; } if (fileattrstring->size()) { cout << ", has attributes"; } cout << endl; if (e) { cout << "Not available: " << errorstring(e) << endl; } else { cout << "Initiating download..." << endl; TransferDbCommitter committer(client->tctable); auto file = std::make_unique(nullptr, NodeHandle().set6byte(ph), (byte*)key, size, 0, filename, fingerprint); startxfer(committer, std::move(file), *filename, client->nextreqtag()); } return true; })); return; } n = nodebypath(s.words[1].s.c_str()); if (n) { if (s.words.size() > 2) { // read file slice m_off_t offset = atol(s.words[2].s.c_str()); m_off_t count = (s.words.size() > 3) ? atol(s.words[3].s.c_str()) : 0; if (offset + count > n->size) { if (offset < n->size) { count = n->size - offset; cout << "Count adjusted to " << count << " bytes (filesize is " << n->size << " bytes)" << endl; } else { cout << "Nothing to read: offset + length > filesize (" << offset << " + " << count << " > " << n->size << " bytes)" << endl; return; } } if (s.words.size() == 5) { pread_file = new ofstream(s.words[4].s.c_str(), std::ios_base::binary); pread_file_end = offset + count; } client->pread(n.get(), offset, count, &dummyAppData); } else { TransferDbCommitter committer(client->tctable); // queue specified file... if (n->type == FILENODE) { auto f = std::make_unique(n.get()); string::size_type index = s.words[1].s.find(":"); // node from public folder link if (index != string::npos && s.words[1].s.substr(0, index).find("@") == string::npos) { handle h = clientFolder->mNodeManager.getRootNodeFiles().as8byte(); char *pubauth = new char[12]; Base64::btoa((byte*)&h, MegaClient::NODEHANDLE, pubauth); f->pubauth = pubauth; f->hprivate = true; f->hforeign = true; memcpy(f->filekey, n->nodekey().data(), FILENODEKEYLENGTH); } startxfer(committer, std::move(f), *n, client->nextreqtag()); } else { // ...or all files in the specified folder (non-recursive) for (auto& node : client->getChildren(n.get())) { if (node->type == FILENODE) { auto f = std::make_unique(node.get()); startxfer(committer, std::move(f), *node.get(), client->nextreqtag()); } } } } } else { cout << s.words[1].s << ": No such file or folder" << endl; } } } /* more_node here is intentionally defined with filescope, it allows us to * resume an interrupted pagination. * Node contents are fetched one page at a time, defaulting to 1KB of data. * Improvement: Get console layout and use width*height for precise pagination. */ static std::shared_ptr more_node = nullptr; // Remote node that we are paging through static m_off_t more_offset = 0; // Current offset in the remote file static const m_off_t MORE_BYTES = 1024; void exec_more(autocomplete::ACState& s) { static int dummyAppData = 0; if(s.words.size() > 1) // set up new node for pagination { more_offset = 0; more_node = nodebypath(s.words[1].s.c_str()); } if(more_node && (more_node->type == FILENODE)) { m_off_t count = (more_offset + MORE_BYTES <= more_node->size) ? MORE_BYTES : (more_node->size - more_offset); client->pread(more_node.get(), more_offset, count, &dummyAppData); } } void uploadLocalFolderContent(const LocalPath& localname, Node* cloudFolder, VersioningOption vo, bool allowDuplicateVersions); void uploadLocalPath(nodetype_t type, std::string name, const LocalPath& localname, Node* parent, const std::string& targetuser, TransferDbCommitter& committer, int& total, bool recursive, VersioningOption vo, std::function(LocalPath)> onCompletedGenerator, bool noRetries, bool allowDuplicateVersions) { std::shared_ptr previousNode = client->childnodebyname(parent, name.c_str(), false); if (type == FILENODE) { auto fa = client->fsaccess->newfileaccess(); if (fa->fopen(localname, OPEN_RDONLY, FSLogging::logOnError)) { FileFingerprint fp; fp.genfingerprint(fa.get()); if (previousNode) { if (previousNode->type == FILENODE) { if (!allowDuplicateVersions && fp.isvalid && previousNode->isvalid && fp == *((FileFingerprint *)previousNode.get())) { cout << "Identical file already exist. Skipping transfer of " << name << endl; return; } } else { cout << "Can't upload file over the top of a folder with the same name: " << name << endl; return; } } fa.reset(); AppFilePut* f = new AppFilePut(localname, parent ? parent->nodeHandle() : NodeHandle(), targetuser.c_str()); f->noRetries = noRetries; if (onCompletedGenerator) f->onCompleted = onCompletedGenerator(localname); *static_cast(f) = fp; f->appxfer_it = appxferq[PUT].insert(appxferq[PUT].end(), f); client->startxfer(PUT, f, committer, false, false, false, vo, nullptr, client->nextreqtag()); total++; } else { cout << "Can't open file: " << name << endl; } } else if (type == FOLDERNODE && recursive) { if (previousNode) { if (previousNode->type == FILENODE) { cout << "Can't upload a folder over the top of a file with the same name: " << name << endl; return; } else { // upload into existing folder with the same name uploadLocalFolderContent(localname, previousNode.get(), vo, true); } } else { vector nn(1); client->putnodes_prepareOneFolder(&nn[0], name, false); gOnPutNodeTag[gNextClientTag] = [localname, vo](Node* parent) { auto tmp = localname; uploadLocalFolderContent(tmp, parent, vo, true); }; client->putnodes(parent->nodeHandle(), NoVersioning, std::move(nn), nullptr, gNextClientTag++, false); } } } string localpathToUtf8Leaf(const LocalPath& itemlocalname) { return itemlocalname.leafName().toPath(true); // true since it's used for upload } void uploadLocalFolderContent(const LocalPath& localname, Node* cloudFolder, VersioningOption vo, bool allowDuplicateVersions) { #ifndef DONT_USE_SCAN_SERVICE auto fa = client->fsaccess->newfileaccess(); fa->fopen(localname, FSLogging::logOnError); if (fa->type != FOLDERNODE) { cout << "Path is not a folder: " << localname.toPath(false); return; } ScanService s; ScanService::RequestPtr r = s.queueScan(localname, fa->fsid, false, {}, client->waiter); while (!r->completed()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } if (r->completionResult() != SCAN_SUCCESS) { cout << "Scan failed: " << r->completionResult() << " for path: " << localname.toPath(false); return; } std::vector results = r->resultNodes(); TransferDbCommitter committer(client->tctable); int total = 0; for (auto& rr : results) { auto newpath = localname; newpath.appendWithSeparator(rr.localname, true); uploadLocalPath(rr.type, rr.localname.toPath(false), newpath, cloudFolder, "", committer, total, true, vo, nullptr, false, allowDuplicateVersions); } if (gVerboseMode) { cout << "Queued " << total << " more uploads from folder " << localname.toPath(false) << endl; } #else auto da = client->fsaccess->newdiraccess(); LocalPath lp(localname); if (da->dopen(&lp, NULL, false)) { TransferDbCommitter committer(client->tctable); int total = 0; nodetype_t type; LocalPath itemlocalleafname; while (da->dnext(lp, itemlocalleafname, true, &type)) { string leafNameUtf8 = localpathToUtf8Leaf(itemlocalleafname); if (gVerboseMode) { cout << "Queueing " << leafNameUtf8 << "..." << endl; } auto newpath = lp; newpath.appendWithSeparator(itemlocalleafname, true); uploadLocalPath(type, leafNameUtf8, newpath, cloudFolder, "", committer, total, true, vo, nullptr, true); } if (gVerboseMode) { cout << "Queued " << total << " more uploads from folder " << localpathToUtf8Leaf(localname) << endl; } } #endif } void exec_put(autocomplete::ACState& s) { NodeHandle target = cwd; string targetuser; string newname; int total = 0; std::shared_ptr n; VersioningOption vo = UseLocalVersioningFlag; if (s.extractflag("-noversion")) vo = NoVersioning; if (s.extractflag("-version")) vo = ClaimOldVersion; if (s.extractflag("-versionreplace")) vo = ReplaceOldVersion; bool allowDuplicateVersions = s.extractflag("-allowduplicateversions"); bool recursive = s.extractflag("-r"); if (s.words.size() > 2) { if ((n = nodebypath(s.words[2].s.c_str(), &targetuser, &newname))) { target = n->nodeHandle(); } } else // target is current path { n = client->nodeByHandle(target); } if (client->loggedin() == NOTLOGGEDIN && !targetuser.size() && !client->loggedIntoWritableFolder()) { cout << "Not logged in." << endl; return; } if (recursive && !targetuser.empty()) { cout << "Sorry, can't send recursively to a user" << endl; } auto localname = localPathArg(s.words[1].s); auto da = client->fsaccess->newdiraccess(); // search with glob, eg *.txt if (da->dopen(&localname, NULL, true)) { TransferDbCommitter committer(client->tctable); nodetype_t type; LocalPath itemlocalname; while (da->dnext(localname, itemlocalname, true, &type)) { string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname); if (gVerboseMode) { cout << "Queueing " << leafNameUtf8 << "..." << endl; } uploadLocalPath(type, leafNameUtf8, itemlocalname, n.get(), targetuser, committer, total, recursive, vo, nullptr, false, allowDuplicateVersions); } } cout << "Queued " << total << " file(s) for upload, " << appxferq[PUT].size() << " file(s) in queue" << endl; } void exec_pwd(autocomplete::ACState&) { string path; nodepath(cwd, &path); cout << path << endl; } void exec_lcd(autocomplete::ACState& s) { if (s.words.size() != 2) { cout << "lcd

    " << endl; return; } LocalPath localpath = localPathArg(s.words[1].s); if (!client->fsaccess->chdirlocal(localpath)) { cout << s.words[1].s << ": Failed" << endl; } } void exec_llockfile(autocomplete::ACState& s) { bool readlock = s.extractflag("-read"); bool writelock = s.extractflag("-write"); bool unlock = s.extractflag("-unlock"); if (!readlock && !writelock && !unlock) { readlock = true; writelock = true; } LocalPath localpath = localPathArg(s.words[1].s); #ifdef WIN32 static map llockedFiles; if (unlock) { if (llockedFiles.find(localpath) == llockedFiles.end()) return; CloseHandle(llockedFiles[localpath]); } else { string pe = localpath.platformEncoded(); HANDLE hFile = CreateFileW(wstring((wchar_t*)pe.data(), pe.size()/2).c_str(), readlock ? GENERIC_READ : (writelock ? GENERIC_WRITE : 0), 0, // no sharing NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { auto err = GetLastError(); cout << "Error locking file: " << err; } else { llockedFiles[localpath] = hFile; } } #else cout << " sorry, not implemented yet" << endl; #endif } void exec_lls(autocomplete::ACState& s) { bool recursive = s.extractflag("-R"); fs::path ls_folder = s.words.size() > 1 ? fs::u8path(s.words[1].s) : fs::current_path(); std::error_code ec; [[maybe_unused]] auto status = fs::status(ls_folder, ec); if (ec) { cerr << ec.message() << endl; } else if (!fs::exists(ls_folder)) { cerr << "not found" << endl; } else { local_dumptree(ls_folder, recursive); } } void exec_ipc(autocomplete::ACState& s) { // incoming pending contact action handle phandle; if (s.words.size() == 3 && Base64::atob(s.words[1].s.c_str(), (byte*) &phandle, sizeof phandle) == sizeof phandle) { ipcactions_t action; if (s.words[2].s == "a") { action = IPCA_ACCEPT; } else if (s.words[2].s == "d") { action = IPCA_DENY; } else if (s.words[2].s == "i") { action = IPCA_IGNORE; } else { return; } client->updatepcr(phandle, action); } } #if defined(WIN32) && defined(NO_READLINE) void exec_log(autocomplete::ACState& s) { if (s.words.size() == 1) { // close log static_cast(console)->log("", WinConsole::no_log); cout << "log closed" << endl; } else if (s.words.size() == 3) { // open log WinConsole::logstyle style = WinConsole::no_log; if (s.words[1].s == "utf8") { style = WinConsole::utf8_log; } else if (s.words[1].s == "utf16") { style = WinConsole::utf16_log; } else if (s.words[1].s == "codepage") { style = WinConsole::codepage_log; } else { cout << "unknown log style" << endl; } if (!static_cast(console)->log(s.words[2].s, style)) { cout << "failed to open log file" << endl; } } } #endif void exec_putq(autocomplete::ACState& s) { bool showActive = s.extractflag("-active"); bool showAll = s.extractflag("-all"); bool showCount = s.extractflag("-count"); if (!showActive && !showAll && !showCount) { showCount = true; } xferq(PUT, s.words.size() > 1 ? atoi(s.words[1].s.c_str()) : -1, showActive, showAll, showCount); } void exec_getq(autocomplete::ACState& s) { bool showActive = s.extractflag("-active"); bool showAll = s.extractflag("-all"); bool showCount = s.extractflag("-count"); if (!showActive && !showAll && !showCount) { showCount = true; } xferq(GET, s.words.size() > 1 ? atoi(s.words[1].s.c_str()) : -1, showActive, showAll, showCount); } void exec_open(autocomplete::ACState& s) { if (strstr(s.words[1].s.c_str(), "#F!") || strstr(s.words[1].s.c_str(), "folder/")) // folder link indicator { if (!clientFolder) { using namespace mega; auto provider = IGfxProvider::createInternalGfxProvider(); GfxProc* gfx = provider ? new GfxProc(std::move(provider)) : nullptr; if (gfx) gfx->startProcessingThread(); // create a new MegaClient with a different MegaApp to process callbacks // from the client logged into a folder. Reuse the waiter and httpio clientFolder = new MegaClient(new DemoAppFolder, client->waiter, client->httpio, #ifdef DBACCESS_CLASS new DBACCESS_CLASS(*startDir), #else NULL, #endif gfx, "megacli_folder/" TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING( MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION), 2, client->getClientType()); } else { clientFolder->logout(false); } const char* authToken = nullptr; if (s.words.size() > 2) authToken = s.words[2].s.c_str(); return clientFolder->app->login_result(clientFolder->folderaccess(s.words[1].s.c_str(), authToken)); } else { cout << "Invalid folder link." << endl; } } #ifdef ENABLE_SYNC void exec_syncrescan(autocomplete::ACState& s) { handle backupId = 0; Base64::atob(s.words[2].s.c_str(), (byte*)&backupId, int(sizeof(backupId))); client->syncs.setSyncsNeedFullSync(true, true, backupId); } #endif void exec_lpwd(autocomplete::ACState&) { cout << fs::current_path().u8string() << endl; } void exec_test(autocomplete::ACState&) {} void exec_mfad(autocomplete::ACState& s) { client->multifactorauthdisable(s.words[1].s.c_str()); } void exec_mfac(autocomplete::ACState& s) { string email; if (s.words.size() == 2) { email = s.words[1].s; } else { email = login.email; } client->multifactorauthcheck(email.c_str()); } void exec_mfae(autocomplete::ACState&) { client->multifactorauthsetup(); } void exec_login(autocomplete::ACState& s) { bool fresh = s.extractflag("-fresh"); if (client->loggedin() == NOTLOGGEDIN) { if (s.words.size() > 1) { if ((s.words.size() == 2 || s.words.size() == 3) && s.words[1].s == "autoresume") { string filename = "megacli_autoresume_session" + (s.words.size() == 3 ? "_" + s.words[2].s : ""); ifstream file(filename.c_str()); string session; file >> session; if (file.is_open() && session.size()) { cout << "Resuming session..." << endl; return client->login(Base64::atob(session)); } cout << "Failed to get a valid session id from file " << filename << endl; } else if (strchr(s.words[1].s.c_str(), '@')) { login.reset(); login.email = s.words[1].s; // full account login if (s.words.size() > 2) { login.password = s.words[2].s; cout << "Initiated login attempt..." << endl; } client->prelogin(login.email.c_str()); } else { const char* ptr; if ((ptr = strchr(s.words[1].s.c_str(), '#'))) // folder link indicator { const char *authKey = s.words.size() == 3 ? s.words[2].s.c_str() : nullptr; bool tryToResumeFolderLinkFromCache = true; if (fresh) { tryToResumeFolderLinkFromCache = false; } return client->app->login_result( client->folderaccess(s.words[1].s.c_str(), authKey, tryToResumeFolderLinkFromCache)); } else { return client->login(Base64::atob(s.words[1].s)); } } } else { cout << " login email [password]" << endl << " login exportedfolderurl#key [authKey]" << endl << " login session" << endl; } } else { cout << "Already logged in. Please log out first." << endl; } } void exec_begin(autocomplete::ACState& s) { bool ephemeralPlusPlus = s.extractflag("-e++"); if (s.words.size() == 1) { cout << "Creating ephemeral session..." << endl; client->createephemeral(); } else if (s.words.size() == 2) // resume session { if (ephemeralPlusPlus) { client->resumeephemeralPlusPlus(Base64::atob(s.words[1].s)); } else { handle uh; byte pw[SymmCipher::KEYLENGTH]; if (Base64::atob(s.words[1].s.c_str(), (byte*) &uh, MegaClient::USERHANDLE) == sizeof uh && Base64::atob( s.words[1].s.c_str() + 12, pw, sizeof pw) == sizeof pw) { client->resumeephemeral(uh, pw); } else { cout << "Malformed ephemeral session identifier." << endl; } } } else if (ephemeralPlusPlus && s.words.size() == 3) // begin -e++ firstname lastname { cout << "Creating ephemeral session plus plus..." << endl; ephemeralFirstname = s.words[1].s; ephemeralLastName = s.words[2].s; client->createephemeralPlusPlus(); } } void exec_mount(autocomplete::ACState& ) { listtrees(); } void exec_share(autocomplete::ACState& s) { bool writable = false; switch (s.words.size()) { case 1: // list all shares (incoming, outgoing and pending outgoing) { listallshares(); } break; case 2: // list all outgoing shares on this path case 3: // remove outgoing share to specified e-mail address case 4: // add outgoing share to specified e-mail address case 5: // user specified a personal representation to appear as for the invitation if (std::shared_ptr n = nodebypath(s.words[1].s.c_str())) { if (s.words.size() == 2) { listnodeshares(n.get(), false); } else { accesslevel_t a = ACCESS_UNKNOWN; const char* personal_representation = NULL; if (s.words.size() > 3) { if (s.words[3].s == "r" || s.words[3].s == "ro") { a = RDONLY; } else if (s.words[3].s == "rw") { a = RDWR; } else if (s.words[3].s == "full") { a = FULL; } else { cout << "Access level must be one of r, rw or full" << endl; return; } if (s.words.size() > 4) { personal_representation = s.words[4].s.c_str(); } } handle nodehandle = n->nodehandle; std::function completeShare = [nodehandle, s, a, writable, personal_representation]() { std::shared_ptr n = client->nodebyhandle(nodehandle); if (!n) { cout << "Node not found." << endl; return; } client->setshare(n, s.words[2].s.c_str(), a, writable, personal_representation, gNextClientTag++, [](Error e, bool) { if (e) { cout << "Share creation/modification request failed (" << errorstring(e) << ")" << endl; } else { cout << "Share creation/modification succeeded." << endl; } }); }; if (a != ACCESS_UNKNOWN) { client->openShareDialog(n.get(), [completeShare](Error e) { if (e) { cout << "Error creating share key (" << errorstring(e) << ")" << endl; return; } completeShare(); }); return; } completeShare(); } } else { cout << s.words[1].s << ": No such directory" << endl; } break; } } void exec_getemail(autocomplete::ACState& s) { if (!client->loggedin()) { cout << "Must be logged in to fetch user emails" << endl; return; } client->getUserEmail(s.words[1].s.c_str()); } void DemoApp::getuseremail_result(string *email, error e) { if (e) { cout << "Failed to retrieve email: " << e << endl; } else if (!email) { cout << "Email: null" << endl; } else { cout << "Email: " << *email << endl; } } void exec_users(autocomplete::ACState& s) { if (s.words.size() == 1) { for (user_map::iterator it = client->users.begin(); it != client->users.end(); it++) { if (it->second.email.size()) { cout << "\t" << it->second.email << " (" << toHandle(it->second.userhandle) << ")"; if (it->second.userhandle == client->me) { cout << ", session user"; } else if (it->second.show == VISIBLE) { cout << ", visible"; } else if (it->second.show == HIDDEN) { cout << ", hidden"; } else if (it->second.show == INACTIVE) { cout << ", inactive"; } else if (it->second.show == BLOCKED) { cout << ", blocked"; } else { cout << ", unknown visibility (" << it->second.show << ")"; } if (it->second.userhandle != client->me && client->areCredentialsVerified(it->second.userhandle)) { cout << ", credentials verified"; } if (it->second.sharing.size()) { cout << ", sharing " << it->second.sharing.size() << " folder(s)"; } if (it->second.pubk.isvalid()) { cout << ", public key cached"; } if (it->second.mBizMode == BIZ_MODE_MASTER) { cout << ", business master user"; } else if (it->second.mBizMode == BIZ_MODE_SUBUSER) { cout << ", business sub-user"; } cout << endl; } } } else if (s.words.size() == 3 && s.words[2].s == "del") { client->removecontact(s.words[1].s.c_str(), HIDDEN); } } void exec_mkdir(autocomplete::ACState& s) { bool allowDuplicate = s.extractflag("-allowduplicate"); bool exactLeafName = s.extractflag("-exactleafname"); bool writevault = s.extractflag("-writevault"); if (s.words.size() > 1) { string newname; std::shared_ptr n; if (exactLeafName) { n = client->nodeByHandle(cwd); newname = s.words[1].s; } else { n = nodebypath(s.words[1].s.c_str(), NULL, &newname); } if (n) { if (!client->checkaccess(n.get(), RDWR)) { cout << "Write access denied" << endl; return; } if (newname.size()) { vector nn(1); client->putnodes_prepareOneFolder(&nn[0], newname, writevault); client->putnodes(n->nodeHandle(), NoVersioning, std::move(nn), nullptr, gNextClientTag++, writevault); } else if (allowDuplicate && n->parent && n->parent->nodehandle != UNDEF) { // the leaf name already exists and was returned in n auto leafname = s.words[1].s; auto pos = leafname.find_last_of("/"); if (pos != string::npos) leafname.erase(0, pos + 1); vector nn(1); client->putnodes_prepareOneFolder(&nn[0], leafname, writevault); client->putnodes(n->parent->nodeHandle(), NoVersioning, std::move(nn), nullptr, gNextClientTag++, writevault); } else { cout << s.words[1].s << ": Path already exists" << endl; } } else { cout << s.words[1].s << ": Target path not found" << endl; } } } void exec_getfa(autocomplete::ACState& s) { std::shared_ptr n; int cancel = s.words.size() > 2 && s.words.back().s == "cancel"; if (s.words.size() < 3) { n = client->nodeByHandle(cwd); } else if (!(n = nodebypath(s.words[2].s.c_str()))) { cout << s.words[2].s << ": Path not found" << endl; } if (n) { int c = 0; fatype type; type = fatype(atoi(s.words[1].s.c_str())); if (n->type == FILENODE) { if (n->hasfileattribute(type)) { client->getfa(n->nodehandle, &n->fileattrstring, n->nodekey(), type, cancel); c++; } } else { for (auto& node : client->getChildren(n.get())) { if (node->type == FILENODE && node->hasfileattribute(type)) { client->getfa(node->nodehandle, &node->fileattrstring, node->nodekey(), type, cancel); c++; } } } cout << (cancel ? "Canceling " : "Fetching ") << c << " file attribute(s) of type " << type << "..." << endl; } } void exec_getua(autocomplete::ACState& s) { User* u = NULL; if (s.words.size() == 3) { // get other user's attribute if (!(u = client->finduser(s.words[2].s.c_str()))) { cout << "Retrieving user attribute for unknown user: " << s.words[2].s << endl; client->getua(s.words[2].s.c_str(), User::string2attr(s.words[1].s.c_str())); return; } } else if (s.words.size() != 2) { cout << " getua attrname [email]" << endl; return; } if (!u) { // get logged in user's attribute if (!(u = client->ownuser())) { cout << "Must be logged in to query own attributes." << endl; return; } } if (s.words[1].s == "pubk") { client->getpubkey(u->uid.c_str()); return; } client->getua(u, User::string2attr(s.words[1].s.c_str())); } void exec_putua(autocomplete::ACState& s) { if (!client->loggedin()) { cout << "Must be logged in to set user attributes." << endl; return; } attr_t attrtype = User::string2attr(s.words[1].s.c_str()); if (attrtype == ATTR_UNKNOWN) { cout << "Attribute not recognized" << endl; return; } if (s.words.size() == 2) { // delete attribute client->putua(attrtype); return; } else if (s.words.size() == 3) { if (s.words[2].s == "del") { client->putua(attrtype); return; } } else if (s.words.size() == 4) { if (s.words[2].s == "set") { client->putua(attrtype, (const byte*)s.words[3].s.c_str(), unsigned(s.words[3].s.size())); return; } else if (s.words[2].s == "set64") { int len = int(s.words[3].s.size() * 3 / 4 + 3); byte* value = new byte[static_cast(len)]; int valuelen = Base64::atob(s.words[3].s.data(), value, len); client->putua(attrtype, value, static_cast(valuelen)); delete[] value; return; } else if (s.words[2].s == "load") { string data; auto localpath = localPathArg(s.words[3].s); if (loadfile(localpath, &data)) { client->putua(attrtype, (const byte*)data.data(), unsigned(data.size())); } else { cout << "Cannot read " << s.words[3].s << endl; } return; } } else if (s.words.size() == 5) { if (s.words[2].s == "map") // putua map { // received will be B64 encoded // received will have the real text value if (User::scope(attrtype) == ATTR_SCOPE_PRIVATE_ENCRYPTED) { putua_map(s.words[3].s, Base64::btoa(s.words[4].s), attrtype); } else { cout << "Attribute not private, cannot be set as a map" << endl; } } } } #ifdef DEBUG void exec_delua(autocomplete::ACState& s) { client->delua(s.words[1].s.c_str()); } #endif void exec_pause(autocomplete::ACState& s) { bool getarg = false, putarg = false, hardarg = false, statusarg = false; for (size_t i = s.words.size(); --i; ) { if (s.words[i].s == "get") { getarg = true; } if (s.words[i].s == "put") { putarg = true; } if (s.words[i].s == "hard") { hardarg = true; } if (s.words[i].s == "status") { statusarg = true; } } if (statusarg) { if (!hardarg && !getarg && !putarg) { if (!client->xferpaused[GET] && !client->xferpaused[PUT]) { cout << "Transfers not paused at the moment." << endl; } else { if (client->xferpaused[GET]) { cout << "GETs currently paused." << endl; } if (client->xferpaused[PUT]) { cout << "PUTs currently paused." << endl; } } } return; } if (!getarg && !putarg) { getarg = true; putarg = true; } TransferDbCommitter committer(client->tctable); if (getarg) { client->pausexfers(GET, client->xferpaused[GET] ^= true, hardarg, committer); if (client->xferpaused[GET]) { cout << "GET transfers paused. Resume using the same command." << endl; } else { cout << "GET transfers unpaused." << endl; } } if (putarg) { client->pausexfers(PUT, client->xferpaused[PUT] ^= true, hardarg, committer); if (client->xferpaused[PUT]) { cout << "PUT transfers paused. Resume using the same command." << endl; } else { cout << "PUT transfers unpaused." << endl; } } } void exec_debug(autocomplete::ACState& s) { if (s.extractflag("-off")) { SimpleLogger::setLogLevel(logWarning); gLogger.logToConsole = false; gLogger.mLogFile.close(); } if (s.extractflag("-on")) { SimpleLogger::setLogLevel(logDebug); } if (s.extractflag("-verbose")) { SimpleLogger::setLogLevel(logVerbose); } if (s.extractflag("-console")) { gLogger.logToConsole = true; } if (s.extractflag("-noconsole")) { gLogger.logToConsole = false; } if (s.extractflag("-nofile")) { gLogger.mLogFile.close(); } string filename; if (s.extractflagparam("-file", filename)) { gLogger.mLogFile.close(); if (!filename.empty()) { gLogger.mLogFile.open(filename.c_str()); if (gLogger.mLogFile.is_open()) { gLogger.mLogFileName = filename; } else { cout << "Log file open failed: '" << filename << "'" << endl; } } } cout << "Debug level set to " << SimpleLogger::getLogLevel() << endl; cout << "Log to console: " << (gLogger.logToConsole ? "on" : "off") << endl; cout << "Log to file: " << (gLogger.mLogFile.is_open() ? gLogger.mLogFileName : "") << endl; } #if defined(WIN32) && defined(NO_READLINE) void exec_clear(autocomplete::ACState& s) { static_cast(console)->clearScreen(); } #endif void exec_retry(autocomplete::ACState&) { if (client->abortbackoff()) { cout << "Retrying..." << endl; } else { cout << "No failed request pending." << endl; } } void exec_recon(autocomplete::ACState&) { cout << "Closing all open network connections..." << endl; client->disconnect(); } void exec_email(autocomplete::ACState& s) { if (s.words.size() == 1) { User *u = client->finduser(client->me); if (u) { cout << "Your current email address is " << u->email << endl; } else { cout << "Please, login first" << endl; } } else if (s.words.size() == 2) { if (s.words[1].s.find("@") != string::npos) // get change email link { client->getemaillink(s.words[1].s.c_str()); } else // confirm change email link { string link = s.words[1].s; size_t pos = link.find(MegaClient::verifyLinkPrefix()); if (pos == link.npos) { cout << "Invalid email change link." << endl; return; } changecode.assign(link.substr(pos + strlen(MegaClient::verifyLinkPrefix()))); client->queryrecoverylink(changecode.c_str()); } } } #ifdef ENABLE_CHAT void exec_chatc(autocomplete::ACState& s) { size_t wordscount = s.words.size(); if (wordscount < 2 || wordscount == 3) { cout << "Invalid syntax to create chatroom" << endl; cout << " chatc group [email ro|sta|mod]* " << endl; return; } int group = atoi(s.words[1].s.c_str()); if (group != 0 && group != 1) { cout << "Invalid syntax to create chatroom" << endl; cout << " chatc group [email ro|sta|mod]* " << endl; return; } unsigned parseoffset = 2; if (((wordscount - parseoffset) % 2) == 0) { if (!group && (wordscount - parseoffset) > 2) { cout << "Peer to peer chats must have no more than one peer" << endl; return; } userpriv_vector userpriv; unsigned numUsers = 0; while ((numUsers + 1) * 2 + parseoffset <= wordscount) { string email = s.words[numUsers * 2 + parseoffset].s; User *u = client->finduser(email.c_str(), 0); if (!u) { cout << "User not found: " << email << endl; return; } string privstr = s.words[numUsers * 2 + parseoffset + 1].s; privilege_t priv; if (!group) // 1:1 chats enforce peer to be moderator { priv = PRIV_MODERATOR; } else { if (privstr == "ro") { priv = PRIV_RO; } else if (privstr == "sta") { priv = PRIV_STANDARD; } else if (privstr == "mod") { priv = PRIV_MODERATOR; } else { cout << "Unknown privilege for " << email << endl; return; } } userpriv.push_back(userpriv_pair(u->userhandle, priv)); numUsers++; } client->createChat(group != 0, false, &userpriv); } } void exec_chati(autocomplete::ACState& s) { if (s.words.size() >= 4 && s.words.size() <= 7) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); string email = s.words[2].s; User *u = client->finduser(email.c_str(), 0); if (!u) { cout << "User not found: " << email << endl; return; } string privstr = s.words[3].s; privilege_t priv; if (privstr == "ro") { priv = PRIV_RO; } else if (privstr == "sta") { priv = PRIV_STANDARD; } else if (privstr == "mod") { priv = PRIV_MODERATOR; } else { cout << "Unknown privilege for " << email << endl; return; } string title; string unifiedKey; if (s.words.size() == 5) { unifiedKey = s.words[4].s; } else if (s.words.size() >= 6 && s.words[4].s == "t") { title = s.words[5].s; if (s.words.size() == 7) { unifiedKey = s.words[6].s; } } const char *t = !title.empty() ? title.c_str() : NULL; const char *uk = !unifiedKey.empty() ? unifiedKey.c_str() : NULL; client->inviteToChat(chatid, u->userhandle, priv, uk, t); return; } } void exec_chatr(autocomplete::ACState& s) { if (s.words.size() > 1 && s.words.size() < 4) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); if (s.words.size() == 2) { client->removeFromChat(chatid, client->me); return; } else if (s.words.size() == 3) { string email = s.words[2].s; User *u = client->finduser(email.c_str(), 0); if (!u) { cout << "User not found: " << email << endl; return; } client->removeFromChat(chatid, u->userhandle); return; } } } void exec_chatu(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); client->getUrlChat(chatid); } void exec_chata(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); bool archive = (s.words[2].s == "1"); if (!archive && (s.words[2].s != "0")) { cout << "Use 1 or 0 to archive/unarchive chats" << endl; return; } client->archiveChat(chatid, archive); } void exec_chats(autocomplete::ACState& s) { if (s.words.size() == 1) { textchat_map::iterator it; for (it = client->chats.begin(); it != client->chats.end(); it++) { DemoApp::printChatInformation(it->second); } return; } if (s.words.size() == 2) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { cout << "Chatid " << s.words[1].s.c_str() << " not found" << endl; return; } DemoApp::printChatInformation(it->second); return; } } void exec_chatl(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*) &chatid, MegaClient::CHATHANDLE); bool delflag = (s.words.size() == 3 && s.words[2].s == "del"); bool createifmissing = s.words.size() == 2 || (s.words.size() == 3 && s.words[2].s != "query"); client->chatlink(chatid, delflag, createifmissing); } #endif void exec_reset(autocomplete::ACState& s) { if (client->loggedin() != NOTLOGGEDIN) { cout << "You're logged in. Please, logout first." << endl; } else if (s.words.size() == 2 || (s.words.size() == 3 && (hasMasterKey = (s.words[2].s == "mk")))) { recoveryemail = s.words[1].s; client->getrecoverylink(recoveryemail.c_str(), hasMasterKey); } else { cout << " reset email [mk]" << endl; } } void exec_clink(autocomplete::ACState& s) { bool renew = false; if (s.words.size() == 1 || (s.words.size() == 2 && (renew = s.words[1].s == "renew"))) { client->contactlinkcreate(renew); } else if ((s.words.size() == 3) && (s.words[1].s == "query")) { handle clink = UNDEF; Base64::atob(s.words[2].s.c_str(), (byte*)&clink, MegaClient::CONTACTLINKHANDLE); client->contactlinkquery(clink); } else if (((s.words.size() == 3) || (s.words.size() == 2)) && (s.words[1].s == "del")) { handle clink = UNDEF; if (s.words.size() == 3) { Base64::atob(s.words[2].s.c_str(), (byte*)&clink, MegaClient::CONTACTLINKHANDLE); } client->contactlinkdelete(clink); } } void exec_apiurl(autocomplete::ACState& s) { if (s.words.size() == 1) { cout << "Current APIURL = " << client->httpio->APIURL << endl; cout << "Current disablepkp = " << (client->httpio->disablepkp ? "true" : "false") << endl; } else if (client->loggedin() != NOTLOGGEDIN) { cout << "You must not be logged in, to change APIURL" << endl; } else if (s.words.size() == 3 || s.words.size() == 2) { if (s.words[1].s.size() < 8 || s.words[1].s.substr(0, 8) != "https://") { s.words[1].s = "https://" + s.words[1].s; } if (s.words[1].s.empty() || s.words[1].s[s.words[1].s.size() - 1] != '/') { s.words[1].s += '/'; } client->httpio->APIURL = s.words[1].s; if (s.words.size() == 3) { client->httpio->disablepkp = s.words[2].s == "true"; } } } void exec_useragent(autocomplete::ACState& s) { if (s.words.size() == 1) { cout << "Current UserAgent = " << client->useragent << endl; } else if (client->loggedin() != NOTLOGGEDIN) { cout << "You must not be logged in, to change UserAgent" << endl; } else if (s.words.size() == 2) { auto newUserAgent = s.words[1].s; client->useragent.replace(0, megacliUserAgent.size(), newUserAgent); client->httpio->setuseragent(&newUserAgent); megacliUserAgent = newUserAgent; } } void exec_passwd(autocomplete::ACState&) { if (client->loggedin() != NOTLOGGEDIN) { setprompt(NEWPASSWORD); } else { cout << "Not logged in." << endl; } } void exec_invite(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cout << "Not logged in." << endl; } else { if (client->ownuser()->email.compare(s.words[1].s)) { int delflag = s.words.size() == 3 && s.words[2].s == "del"; int rmd = s.words.size() == 3 && s.words[2].s == "rmd"; int clink = s.words.size() == 4 && s.words[2].s == "clink"; if (s.words.size() == 2 || s.words.size() == 3 || s.words.size() == 4) { if (delflag || rmd) { client->setpcr(s.words[1].s.c_str(), delflag ? OPCA_DELETE : OPCA_REMIND); } else { handle contactLink = UNDEF; if (clink) { Base64::atob(s.words[3].s.c_str(), (byte*)&contactLink, MegaClient::CONTACTLINKHANDLE); } // Original email is not required, but can be used if this account has multiple email addresses associated, // to have the invite come from a specific email client->setpcr(s.words[1].s.c_str(), OPCA_ADD, "Invite from MEGAcli", s.words.size() == 3 ? s.words[2].s.c_str() : NULL, contactLink); } } else { cout << " invite dstemail [origemail|del|rmd|clink ]" << endl; } } else { cout << "Cannot send invitation to your own user" << endl; } } } void exec_signup(autocomplete::ACState& s) { if (s.words.size() == 2) { const char* ptr = s.words[1].s.c_str(); const char* tptr; if ((tptr = strstr(ptr, "confirm"))) { ptr = tptr + 7; std::string code = Base64::atob(std::string(ptr)); if (code.find("ConfirmCodeV2") != string::npos) { size_t posEmail = 13 + 15; size_t endEmail = code.find("\t", posEmail); if (endEmail != string::npos) { signupemail = code.substr(posEmail, endEmail - posEmail); signupname = code.substr(endEmail + 1, code.size() - endEmail - 9); if (client->loggedin() == FULLACCOUNT) { cout << "Already logged in." << endl; } else // not-logged-in / ephemeral account / partially confirmed { client->confirmsignuplink2((const byte*)code.data(), unsigned(code.size())); } } } else { cout << "Received argument was not a confirmation link." << endl; } } else { cout << "New accounts must follow registration flow v2. Old flow is not supported anymore." << endl; } } else if (s.words.size() == 3) { switch (client->loggedin()) { case FULLACCOUNT: cout << "Already logged in." << endl; break; case CONFIRMEDACCOUNT: cout << "Current account already confirmed." << endl; break; case EPHEMERALACCOUNT: case EPHEMERALACCOUNTPLUSPLUS: if (s.words[1].s.find('@') + 1 && s.words[1].s.find('.') + 1) { signupemail = s.words[1].s; signupname = s.words[2].s; cout << endl; setprompt(NEWPASSWORD); } else { cout << "Please enter a valid e-mail address." << endl; } break; case NOTLOGGEDIN: cout << "Please use the begin command to commence or resume the ephemeral session to be upgraded." << endl; } } } void exec_cancelsignup(autocomplete::ACState&) { client->cancelsignup(); } void exec_whoami(autocomplete::ACState& s) { if (client->loggedin() == NOTLOGGEDIN) { cout << "Not logged in." << endl; } else { User* u; if ((u = client->finduser(client->me))) { cout << "Account e-mail: " << u->email << " handle: " << Base64Str(client->me) << endl; if (client->mEd255Key) { string pubKey((const char*)client->mEd255Key->pubKey, EdDSA::PUBLIC_KEY_LENGTH); cout << "Credentials: " << AuthRing::fingerprint(pubKey, true) << endl; } } bool storage = s.extractflag("-storage"); bool transfer = s.extractflag("-transfer"); bool pro = s.extractflag("-pro"); bool transactions = s.extractflag("-transactions"); bool purchases = s.extractflag("-purchases"); bool sessions = s.extractflag("-sessions"); bool all = !storage && !transfer && !pro && !transactions && !purchases && !sessions; cout << "Retrieving account status..." << endl; client->getaccountdetails(account, all || storage, all || transfer, all || pro, all || transactions, all || purchases, all || sessions); } } void exec_verifycredentials(autocomplete::ACState& s) { User* u = nullptr; if (s.words.size() == 2 && (s.words[1].s == "show" || s.words[1].s == "status")) { u = client->finduser(client->me); } else if (s.words.size() == 3) { u = client->finduser(s.words[2].s.c_str()); } else { cout << " credentials show|status|verify|reset [email]" << endl; return; } if (!u) { cout << "Invalid user" << endl; return; } if (s.words[1].s == "show") { const UserAttribute* attribute = u->getAttribute(ATTR_ED25519_PUBK); if (attribute && attribute->isValid()) { cout << "Credentials: " << AuthRing::fingerprint(attribute->value(), true) << endl; } else { cout << "Fetching singing key... " << endl; client->getua(u->uid.c_str(), ATTR_ED25519_PUBK); } } else if (s.words[1].s == "status") { handle uh = s.words.size() == 3 ? u->userhandle : UNDEF; printAuthringInformation(uh); } else if (s.words[1].s == "verify") { error e; if ((e = client->verifyCredentials(u->userhandle, nullptr))) { cout << "Verification failed. Error: " << errorstring(e) << endl; return; } } else if (s.words[1].s == "reset") { error e; if ((e = client->resetCredentials(u->userhandle, nullptr))) { cout << "Reset verification failed. Error: " << errorstring(e) << endl; return; } } } void exec_export(autocomplete::ACState& s) { void exportnode_result(Error e, handle h, handle ph); std::shared_ptr n; int deltmp = 0; int etstmp = 0; bool writable = s.extractflag("-writable"); bool megaHosted = s.extractflag("-mega-hosted"); if ((n = nodebypath(s.words[1].s.c_str()))) { if (s.words.size() > 2) { deltmp = (s.words[2].s == "del"); if (!deltmp) { etstmp = atoi(s.words[2].s.c_str()); } } cout << "Exporting..." << endl; error e; if ((e = client->exportnode(n, deltmp, etstmp, writable, megaHosted, gNextClientTag++, [](Error e, handle h, handle ph, string&&) { exportnode_result(e, h, ph); }))) { cout << s.words[1].s << ": Export rejected (" << errorstring(e) << ")" << endl; } } else { cout << s.words[1].s << ": Not found" << endl; } } void exec_encryptLink(autocomplete::ACState& s) { string link = s.words[1].s; string password = s.words[2].s; string encryptedLink; error e = client->encryptlink(link.c_str(), password.c_str(), &encryptedLink); if (e) { cout << "Failed to encrypt link: " << errorstring(e) << endl; } else { cout << "Password encrypted link: " << encryptedLink << endl; } } void exec_decryptLink(autocomplete::ACState &s) { string link = s.words[1].s; string password = s.words[2].s; string decryptedLink; error e = client->decryptlink(link.c_str(), password.c_str(), &decryptedLink); if (e) { cout << "Failed to decrypt link: " << errorstring(e) << endl; } else { cout << "Decrypted link: " << decryptedLink << endl; } } void exec_import(autocomplete::ACState& s) { handle ph = UNDEF; byte key[FILENODEKEYLENGTH]; error e = client->parsepubliclink(s.words[1].s.c_str(), ph, key, TypeOfLink::FILE); if (e == API_OK) { cout << "Opening link..." << endl; client->openfilelink(ph, key); } else { cout << "Malformed link. Format: Exported URL or fileid#filekey" << endl; } } void exec_folderlinkinfo(autocomplete::ACState& s) { publiclink = s.words[1].s; handle ph = UNDEF; byte folderkey[FOLDERNODEKEYLENGTH]; if (client->parsepubliclink(publiclink.c_str(), ph, folderkey, TypeOfLink::FOLDER) == API_OK) { cout << "Loading public folder link info..." << endl; client->getpubliclinkinfo(ph); } else { cout << "Malformed link: " << publiclink << endl; } } void exec_reload(autocomplete::ACState& s) { cout << "Reloading account..." << endl; bool nocache = false; if (s.words.size() == 2 && s.words[1].s == "nocache") { nocache = true; } cwd = NodeHandle(); client->cachedscsn = UNDEF; client->fetchnodes(nocache, false, true); } void exec_logout(autocomplete::ACState& s) { cout << "Logging off..." << endl; bool keepSyncConfigs = s.extractflag("-keepsyncconfigs"); cwd = NodeHandle(); client->logout(keepSyncConfigs); if (clientFolder) { clientFolder->logout(keepSyncConfigs); delete clientFolder; clientFolder = NULL; } ephemeralFirstname.clear(); ephemeralLastName.clear(); } #ifdef ENABLE_CHAT void exec_chatga(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*) &chatid, MegaClient::CHATHANDLE); handle nodehandle = 0; // make sure top two bytes are 0 Base64::atob(s.words[2].s.c_str(), (byte*) &nodehandle, MegaClient::NODEHANDLE); const char *uid = s.words[3].s.c_str(); client->grantAccessInChat(chatid, nodehandle, uid); } void exec_chatra(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); handle nodehandle = 0; // make sure top two bytes are 0 Base64::atob(s.words[2].s.c_str(), (byte*)&nodehandle, MegaClient::NODEHANDLE); const char *uid = s.words[3].s.c_str(); client->removeAccessInChat(chatid, nodehandle, uid); } void exec_chatst(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); if (s.words.size() == 2) // empty title / remove title { client->setChatTitle(chatid, ""); } else if (s.words.size() == 3) { client->setChatTitle(chatid, s.words[2].s.c_str()); } } void exec_chatpu(autocomplete::ACState&) { client->getChatPresenceUrl(); } void exec_chatup(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); handle uh; Base64::atob(s.words[2].s.c_str(), (byte*)&uh, MegaClient::USERHANDLE); string privstr = s.words[3].s; privilege_t priv; if (privstr == "ro") { priv = PRIV_RO; } else if (privstr == "sta") { priv = PRIV_STANDARD; } else if (privstr == "mod") { priv = PRIV_MODERATOR; } else { cout << "Unknown privilege for " << s.words[2].s << endl; return; } client->updateChatPermissions(chatid, uh, priv); } void exec_chatlu(autocomplete::ACState& s) { handle publichandle = 0; Base64::atob(s.words[1].s.c_str(), (byte*)&publichandle, MegaClient::CHATLINKHANDLE); client->chatlinkurl(publichandle); } void exec_chatsm(autocomplete::ACState& s) { handle chatid; Base64::atob(s.words[1].s.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE); const char *title = (s.words.size() == 3) ? s.words[2].s.c_str() : NULL; client->chatlinkclose(chatid, title); } void exec_chatlj(autocomplete::ACState& s) { handle publichandle = 0; Base64::atob(s.words[1].s.c_str(), (byte*)&publichandle, MegaClient::CHATLINKHANDLE); client->chatlinkjoin(publichandle, s.words[2].s.c_str()); } void exec_chatcp(autocomplete::ACState& s) { bool meeting = s.extractflag("-meeting"); size_t wordscount = s.words.size(); userpriv_vector *userpriv = new userpriv_vector; string_map *userkeymap = new string_map; string mownkey = s.words[1].s; unsigned parseoffset = 2; const char *title = NULL; if (wordscount >= 4) { if (s.words[2].s == "t") { if (s.words[3].s.empty()) { cout << "Title cannot be set to empty string" << endl; delete userpriv; delete userkeymap; return; } title = s.words[3].s.c_str(); parseoffset = 4; } if (((wordscount - parseoffset) % 3) != 0) { cout << "Invalid syntax to create chatroom" << endl; cout << " chatcp mownkey [t title64] [email ro|sta|mod unifiedkey]* " << endl; delete userpriv; delete userkeymap; return; } unsigned numUsers = 0; while ((numUsers + 1) * 3 + parseoffset <= wordscount) { string email = s.words[numUsers * 3 + parseoffset].s; User *u = client->finduser(email.c_str(), 0); if (!u) { cout << "User not found: " << email << endl; delete userpriv; delete userkeymap; return; } string privstr = s.words[numUsers * 3 + parseoffset + 1].s; privilege_t priv; if (privstr == "ro") { priv = PRIV_RO; } else if (privstr == "sta") { priv = PRIV_STANDARD; } else if (privstr == "mod") { priv = PRIV_MODERATOR; } else { cout << "Unknown privilege for " << email << endl; delete userpriv; delete userkeymap; return; } userpriv->push_back(userpriv_pair(u->userhandle, priv)); string unifiedkey = s.words[numUsers * 3 + parseoffset + 2].s; char uhB64[12]; Base64::btoa((byte *)&u->userhandle, MegaClient::USERHANDLE, uhB64); uhB64[11] = '\0'; userkeymap->insert(StringPair(uhB64, unifiedkey)); numUsers++; } } char ownHandleB64[12]; Base64::btoa((byte *)&client->me, MegaClient::USERHANDLE, ownHandleB64); ownHandleB64[11] = '\0'; userkeymap->insert(StringPair(ownHandleB64, mownkey)); client->createChat(true, true, userpriv, userkeymap, title, meeting); delete userpriv; delete userkeymap; } #endif void exec_cancel(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cout << "Please, login into your account first." << endl; return; } if (s.words.size() == 1) // get link { User *u = client->finduser(client->me); if (!u) { cout << "Error retrieving logged user." << endl; return; } client->getcancellink(u->email.c_str()); } else if (s.words.size() == 2) // link confirmation { string link = s.words[1].s; size_t pos = link.find(MegaClient::cancelLinkPrefix()); if (pos == link.npos) { cout << "Invalid cancellation link." << endl; return; } client->confirmcancellink(link.substr(pos + strlen(MegaClient::cancelLinkPrefix())).c_str()); } } static void print_alerts(bool showNew, bool showOld, size_t showN) { if (showOld || showNew || showN > 0) { UserAlerts::Alerts::const_iterator i = client->useralerts.alerts.begin(); if (showN) { size_t n = 0; for (UserAlerts::Alerts::const_reverse_iterator j = client->useralerts.alerts.rbegin(); j != client->useralerts.alerts.rend(); ++j, ++n) { if (!(*j)->removed()) { showN += ((*j)->relevant() || n >= showN) ? 0 : 1; } } } size_t n = client->useralerts.alerts.size(); for (; i != client->useralerts.alerts.end(); ++i) { if ((*i)->relevant() && !(*i)->removed()) { if (--n < showN || (showNew && !(*i)->seen()) || (showOld && (*i)->seen())) { printAlert(**i); } } } } return; } void exec_alerts(autocomplete::ACState&) { return print_alerts(true, true, 0); } void exec_alerts_new(autocomplete::ACState&) { return print_alerts(true, false, 0); } void exec_alerts_old(autocomplete::ACState&) { return print_alerts(false, true, 0); } void exec_alerts_number(autocomplete::ACState& s) { return print_alerts(false, false, static_cast(atoi(s.words[1].s.c_str()))); } void exec_alerts_notify(autocomplete::ACState&) { notifyAlerts = !notifyAlerts; cout << "notification of alerts is now " << (notifyAlerts ? "on" : "off") << endl; return; } void exec_alerts_seen(autocomplete::ACState&) { client->useralerts.acknowledgeAll(); return; } void exec_alerts_test_reminder(autocomplete::ACState&) { client->useralerts.add( new UserAlert::PaymentReminder(time(NULL) - 86000 * 3 / 2, client->useralerts.nextId())); } void exec_alerts_test_payment(autocomplete::ACState&) { client->useralerts.add(new UserAlert::Payment(true, 1, time(NULL) + 86000 * 1, client->useralerts.nextId(), name_id::psts)); } void exec_alerts_test_payment_v2(autocomplete::ACState&) { client->useralerts.add(new UserAlert::Payment(true, 1, time(NULL) + 86000 * 1, client->useralerts.nextId(), name_id::psts_v2)); } void exec_alerts_add_reminder(autocomplete::ACState& s) { // Parameterized command to add payment reminder // Usage: alerts add_reminder if (s.words.size() < 4) { cout << "Usage: alerts add_reminder " << endl; cout << " timestamp_offset: seconds from now (e.g., -86400 = 1 day ago)" << endl; cout << " expiry_offset: seconds from now for expiry (e.g., 2592000 = 30 days)" << endl; cout << "Examples:" << endl; cout << " alerts add_reminder \"-86400\" 2592000 # Created 1 day ago, expires in 30 " "days" << endl; cout << " alerts add_reminder 0 604800 # Created now, expires in 7 days" << endl; return; } m_time_t now = time(NULL); int64_t timestampOffset = std::stoll(s.words[2].s); int64_t expiryOffset = std::stoll(s.words[3].s); // if you want to persist this alert // you can upload a file after, using command 'put [FILE]' client->useralerts.add(new UserAlert::PaymentReminder(now + timestampOffset, now + expiryOffset, client->useralerts.nextId())); cout << "Added payment reminder" << endl; cout << " - Expected timestamp: " << now + timestampOffset << " (" << timestampOffset << "s from now)" << endl; cout << " - Expires at: " << now + expiryOffset << " (" << expiryOffset << "s from now)" << endl; if (!client->useralerts.alerts.empty()) { cout << " - Actual timestamp: " << client->useralerts.alerts.back()->ts() << endl; } cout << " - Total alerts in memory: " << client->useralerts.alerts.size() << endl; return; } void exec_alerts_add_payment(autocomplete::ACState& s) { // Parameterized command to add payment alert // Usage: alerts add_payment if (s.words.size() < 3) { cout << "Usage: alerts add_payment " << endl; cout << " timestamp_offset: seconds from now (e.g., 0 = now, -86400 = 1 day ago)" << endl; cout << " success|failed: payment result" << endl; cout << "Examples:" << endl; cout << " alerts add_payment 0 success # Payment succeeded now," << endl; cout << " alerts add_payment \"-172800\" failed # Payment failed 2 days ago" << endl; return; } m_time_t now = time(NULL); int64_t timestampOffset = std::stoll(s.words[2].s); string result = s.words[3].s; if (result != "success" && result != "failed") { cout << "Error: result must be 'success' or 'failed'" << endl; return; } // if you want to persist this alert // you can upload a file after, using command 'put [FILE]' client->useralerts.add(new UserAlert::Payment(result == "success" ? true : false, 1, now + timestampOffset, client->useralerts.nextId(), name_id::psts_v2)); cout << "Added payment " << result << endl; cout << " - Timestamp: " << now + timestampOffset << " (" << timestampOffset << "s from now)" << endl; cout << " - Total alerts in memory: " << client->useralerts.alerts.size() << endl; return; } void exec_lmkdir(autocomplete::ACState& s) { std::error_code ec; if (!fs::create_directory(s.words[1].s.c_str(), ec)) { cerr << "Create directory failed: " << ec.message() << endl; } } void exec_recover(autocomplete::ACState& s) { if (client->loggedin() != NOTLOGGEDIN) { cout << "You're logged in. Please, logout first." << endl; } else if (s.words.size() == 2) { string link = s.words[1].s; size_t pos = link.find(MegaClient::recoverLinkPrefix()); if (pos == link.npos) { cout << "Invalid recovery link." << endl; } recoverycode.assign(link.substr(pos + strlen(MegaClient::recoverLinkPrefix()))); client->queryrecoverylink(recoverycode.c_str()); } } void exec_session(autocomplete::ACState& s) { string session; int size = client->dumpsession(session); if (size > 0) { if ((s.words.size() == 2 || s.words.size() == 3) && s.words[1].s == "autoresume") { string filename = "megacli_autoresume_session" + (s.words.size() == 3 ? "_" + s.words[2].s : ""); ofstream file(filename.c_str()); if (file.fail() || !file.is_open()) { cout << "could not open file: " << filename << endl; } else { file << Base64::btoa(session); cout << "Your (secret) session is saved in file '" << filename << "'" << endl; } } else { cout << "Your (secret) session is: " << Base64::btoa(session) << endl; } } else if (!size) { cout << "Not logged in." << endl; } else { cout << "Internal error." << endl; } } void exec_version(autocomplete::ACState&) { cout << "MEGA SDK version: " << MEGA_MAJOR_VERSION << "." << MEGA_MINOR_VERSION << "." << MEGA_MICRO_VERSION << endl; cout << "Features enabled:" << endl; #ifdef USE_CRYPTOPP cout << "* CryptoPP" << endl; #endif #ifdef USE_SQLITE cout << "* SQLite" << endl; #endif #ifdef USE_BDB cout << "* Berkeley DB" << endl; #endif #ifdef USE_INOTIFY cout << "* inotify" << endl; #endif #ifdef HAVE_FDOPENDIR cout << "* fdopendir" << endl; #endif #ifdef HAVE_SENDFILE cout << "* sendfile" << endl; #endif #ifdef _LARGE_FILES cout << "* _LARGE_FILES" << endl; #endif #ifdef USE_FREEIMAGE cout << "* FreeImage" << endl; #endif #ifdef HAVE_PDFIUM cout << "* PDFium" << endl; #endif #ifdef HAVE_FFMPEG cout << "* FFmpeg" << endl; #endif #ifdef ENABLE_SYNC cout << "* sync subsystem" << endl; #endif #ifdef USE_MEDIAINFO cout << "* MediaInfo" << endl; #endif cwd = NodeHandle(); } void exec_showpcr(autocomplete::ACState&) { string outgoing; string incoming; for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++) { if (it->second->isoutgoing) { ostringstream os; os << setw(34) << it->second->targetemail; os << "\t(id: "; os << Base64Str(it->second->id); os << ", ts: "; os << it->second->ts; outgoing.append(os.str()); outgoing.append(")\n"); } else { ostringstream os; os << setw(34) << it->second->originatoremail; os << "\t(id: "; os << Base64Str(it->second->id); os << ", ts: "; os << it->second->ts; incoming.append(os.str()); incoming.append(")\n"); } } cout << "Incoming PCRs:" << endl << incoming << endl; cout << "Outgoing PCRs:" << endl << outgoing << endl; } #if defined(WIN32) && defined(NO_READLINE) void exec_history(autocomplete::ACState& s) { static_cast(console)->outputHistory(); } #endif void exec_handles(autocomplete::ACState& s) { if (s.words.size() == 2) { if (s.words[1].s == "on") { handles_on = true; } else if (s.words[1].s == "off") { handles_on = false; } else { cout << "invalid handles setting" << endl; } } else { cout << " handles on|off " << endl; } } #if defined(WIN32) && defined(NO_READLINE) void exec_codepage(autocomplete::ACState& s) { WinConsole* wc = static_cast(console); if (s.words.size() == 1) { UINT cp1, cp2; wc->getShellCodepages(cp1, cp2); cout << "Current codepage is " << cp1; if (cp2 != cp1) { cout << " with failover to codepage " << cp2 << " for any absent glyphs"; } cout << endl; for (int i = 32; i < 256; ++i) { string theCharUtf8 = WinConsole::toUtf8String(WinConsole::toUtf16String(string(1, (char)i), cp1)); cout << " dec/" << i << " hex/" << hex << i << dec << ": '" << theCharUtf8 << "'"; if (i % 4 == 3) { cout << endl; } } } else if (s.words.size() == 2 && atoi(s.words[1].s.c_str()) != 0) { if (!wc->setShellConsole(atoi(s.words[1].s.c_str()), atoi(s.words[1].s.c_str()))) { cout << "Code page change failed - unicode selected" << endl; } } else if (s.words.size() == 3 && atoi(s.words[1].s.c_str()) != 0 && atoi(s.words[2].s.c_str()) != 0) { if (!wc->setShellConsole(atoi(s.words[1].s.c_str()), atoi(s.words[2].s.c_str()))) { cout << "Code page change failed - unicode selected" << endl; } } } #endif #ifdef USE_MEDIAINFO void exec_mediainfo(autocomplete::ACState& s) { if (client->mediaFileInfo.mediaCodecsFailed) { cout << "Sorry, mediainfo lookups could not be retrieved." << endl; return; } else if (!client->mediaFileInfo.mediaCodecsReceived) { client->mediaFileInfo.requestCodecMappingsOneTime(client, LocalPath()); cout << "Mediainfo lookups requested" << endl; } if (s.words.size() == 3 && s.words[1].s == "calc") { MediaProperties mp; auto localFilename = localPathArg(s.words[2].s); string ext; if (client->fsaccess->getextension(localFilename, ext) && MediaProperties::isMediaFilenameExt(ext)) { mp.extractMediaPropertyFileAttributes(localFilename, client->fsaccess.get()); uint32_t dummykey[4] = { 1, 2, 3, 4 }; // check encode/decode string attrs = mp.convertMediaPropertyFileAttributes(dummykey, client->mediaFileInfo); MediaProperties dmp = MediaProperties::decodeMediaPropertiesAttributes(":" + attrs, dummykey); cout << showMediaInfo(dmp, client->mediaFileInfo, false) << endl; } else { cout << "Filename extension is not suitable for mediainfo analysis." << endl; } } else if (s.words.size() == 3 && s.words[1].s == "show") { if (std::shared_ptr n = nodebypath(s.words[2].s.c_str())) { switch (n->type) { case FILENODE: cout << showMediaInfo(n.get(), client->mediaFileInfo, false) << endl; break; case FOLDERNODE: case ROOTNODE: case VAULTNODE: case RUBBISHNODE: { for (auto& m : client->getChildren(n.get())) { if (m->type == FILENODE && m->hasfileattribute(fa_media)) { cout << m->displayname() << " " << showMediaInfo(m.get(), client->mediaFileInfo, true) << endl; } } break; } case TYPE_DONOTSYNC: case TYPE_NESTED_MOUNT: case TYPE_SPECIAL: case TYPE_SYMLINK: case TYPE_UNKNOWN: cout << "node type is inappropriate for mediainfo: " << n->type << endl; break; } } else { cout << "remote file not found: " << s.words[2].s << endl; } } } #endif void exec_smsverify(autocomplete::ACState& s) { if (s.words[1].s == "send") { bool reverifywhitelisted = (s.words.size() == 4 && s.words[3].s == "reverifywhitelisted"); if (client->smsverificationsend(s.words[2].s, reverifywhitelisted) != API_OK) { cout << "phonenumber is invalid" << endl; } } else if (s.words[1].s == "code") { if (client->smsverificationcheck(s.words[2].s) != API_OK) { cout << "verificationcode is invalid" << endl; } } } void exec_verifiedphonenumber(autocomplete::ACState&) { cout << "Verified phone number: " << client->mSmsVerifiedPhone << endl; } void exec_killsession(autocomplete::ACState& s) { if (s.words[1].s == "all") { // Kill all sessions (except current) client->killallsessions(); } else { handle sessionid; if (Base64::atob(s.words[1].s.c_str(), (byte*)&sessionid, sizeof sessionid) == sizeof sessionid) { client->killsession(sessionid); } else { cout << "invalid session id provided" << endl; } } } void exec_locallogout(autocomplete::ACState&) { cout << "Logging off locally..." << endl; cwd = NodeHandle(); client->locallogout(false, true); ephemeralFirstname.clear(); ephemeralLastName.clear(); } void exec_recentnodes(autocomplete::ACState& s) { if (s.words.size() == 3) { int maxElements{}; if (auto [ptr, ec] = std::from_chars(s.words[2].s.data(), s.words[2].s.data() + s.words[2].s.size(), maxElements); ec != std::errc{}) { std::cout << "Invalid max elements parameter" << endl; return; } int time{}; if (auto [ptr, ec] = std::from_chars(s.words[1].s.data(), s.words[1].s.data() + s.words[1].s.size(), time); ec != std::errc{}) { std::cout << "Invalid duration parameter" << endl; return; } NodeSearchFilter filter; filter.byAncestors({client->mNodeManager.getRootNodeFiles().as8byte(), client->mNodeManager.getRootNodeVault().as8byte(), UNDEF}); filter.byCreationTimeLowerLimitInSecs(m_time() - 60 * 60 * time); filter.bySensitivity(NodeSearchFilter::BoolFilter::onlyTrue); filter.byNodeType(FILENODE); filter.setIncludedShares(IN_SHARES); sharedNode_vector nv = client->mNodeManager.searchNodes(filter, OrderByClause::CTIME_DESC, CancelToken(), NodeSearchPage{0, static_cast(maxElements)}); for (unsigned i = 0; i < nv.size(); ++i) { cout << nv[i]->displaypath() << endl; } } } #if defined(WIN32) && defined(NO_READLINE) void exec_autocomplete(autocomplete::ACState& s) { if (s.words[1].s == "unix") { static_cast(console)->setAutocompleteStyle(true); } else if (s.words[1].s == "dos") { static_cast(console)->setAutocompleteStyle(false); } else { cout << "invalid autocomplete style" << endl; } } #endif void exec_recentactions(autocomplete::ACState& s) { const bool excludeSens = s.extractflag("-nosensitive"); recentactions_vector nvv = client->getRecentActions(static_cast(atoi(s.words[2].s.c_str())), m_time() - 60 * 60 * atoi(s.words[1].s.c_str()), excludeSens); for (unsigned i = 0; i < nvv.size(); ++i) { if (i != 0) { cout << "---" << endl; } cout << displayTime(nvv[i].time) << " " << displayUser(nvv[i].meta.user, client) << " " << (nvv[i].meta.updated ? "updated" : "uploaded") << " " << (nvv[i].meta.media ? "media" : "files") << endl; for (unsigned j = 0; j < nvv[i].nodes.size(); ++j) { cout << nvv[i].nodes[j]->displaypath() << " (" << displayTime(nvv[i].nodes[j]->ctime) << ")" << endl; } } } void exec_setmaxuploadspeed(autocomplete::ACState& s) { if (s.words.size() > 1) { bool done = client->setmaxuploadspeed(atoi(s.words[1].s.c_str())); cout << (done ? "Success. " : "Failed. "); } cout << "Max Upload Speed: " << client->getmaxuploadspeed() << endl; } void exec_setmaxdownloadspeed(autocomplete::ACState& s) { if (s.words.size() > 1) { bool done = client->setmaxdownloadspeed(atoi(s.words[1].s.c_str())); cout << (done ? "Success. " : "Failed. "); } cout << "Max Download Speed: " << client->getmaxdownloadspeed() << endl; } void exec_setmaxloglinesize(autocomplete::ACState& s) { if (s.words.size() > 1) { SimpleLogger::setMaxPayloadLogSize(static_cast(std::stoul(s.words[1].s))); } } static void printLogJson() { const uint32_t value = JSONLog::get(); std::ios_base::fmtflags f(cout.flags()); cout << "Current JSON log settings: 0x" << std::hex << value << "\n"; cout << "-chunk-received: " << ((value & JSONLog::CHUNK_RECEIVED) ? "on" : "off") << "\n"; cout << "-chunk-processing: " << ((value & JSONLog::CHUNK_PROCESSING) ? "on" : "off") << "\n"; cout << "-chunk-consumed: " << ((value & JSONLog::CHUNK_CONSUMED) ? "on" : "off") << "\n"; cout << "-sending: " << ((value & JSONLog::SENDING) ? "on" : "off") << "\n"; cout << "-nonchunk-received: " << ((value & JSONLog::NONCHUNK_RECEIVED) ? "on" : "off") << "\n"; cout.flags(f); } void exec_setlogjson(autocomplete::ACState& s) { uint32_t newValue = JSONLog::NONE; newValue |= s.extractflag("-chunk-received") ? JSONLog::CHUNK_RECEIVED : 0; newValue |= s.extractflag("-chunk-processing") ? JSONLog::CHUNK_PROCESSING : 0; newValue |= s.extractflag("-chunk-consumed") ? JSONLog::CHUNK_CONSUMED : 0; newValue |= s.extractflag("-sending") ? JSONLog::SENDING : 0; newValue |= s.extractflag("-nonchunk-received") ? JSONLog::NONCHUNK_RECEIVED : 0; JSONLog::set(newValue); printLogJson(); } void exec_getlogjson(autocomplete::ACState&) { printLogJson(); } void exec_drivemonitor([[maybe_unused]] autocomplete::ACState& s) { #ifdef USE_DRIVE_NOTIFICATIONS bool turnon = s.extractflag("-on"); bool turnoff = s.extractflag("-off"); if (turnon) { // start receiving notifications if (!client->startDriveMonitor()) { // return immediately, when this functionality was not implemented cout << "Failed starting drive notifications" << endl; } } else if (turnoff) { client->stopDriveMonitor(); } cout << "Drive monitor " << (client->driveMonitorEnabled() ? "on" : "off") << endl; #else std::cout << "Failed! This functionality was disabled at compile time." << std::endl; #endif // USE_DRIVE_NOTIFICATIONS } void exec_driveid(autocomplete::ACState& s) { auto drivePath = s.words[2].s.c_str(); auto get = s.words[1].s == "get"; auto force = s.words.size() == 4; if (!force) { auto id = UNDEF; auto result = readDriveId(*client->fsaccess, drivePath, id); switch (result) { case API_ENOENT: if (!get) break; cout << "No drive ID has been assigned to " << drivePath << endl; return; case API_EREAD: cout << "Unable to read drive ID from " << drivePath << endl; return; case API_OK: cout << "Drive " << drivePath << " has the ID " << toHandle(id) << endl; return; default: assert(false && "Unexpected result from readDriveID(...)"); cerr << "Unexpected result from readDriveId(...): " << errorstring(result) << endl; return; } } auto id = generateDriveId(client->rng); auto result = writeDriveId(*client->fsaccess, drivePath, id); if (result != API_OK) { cout << "Unable to write drive ID to " << drivePath << endl; return; } cout << "Drive ID " << toHandle(id) << " has been written to " << drivePath << endl; } void exec_randomfile(autocomplete::ACState& s) { // randomfile path [length] auto length = 2l; if (s.words.size() > 2) length = std::atol(s.words[2].s.c_str()); if (length <= 0) { std::cerr << "Invalid length specified: " << s.words[2].s << std::endl; return; } constexpr auto flags = std::ios::binary | std::ios::out | std::ios::trunc; std::ofstream ostream(s.words[1].s, flags); if (!ostream) { std::cerr << "Unable to open file for writing: " << s.words[1].s << std::endl; return; } std::generate_n(std::ostream_iterator(ostream), length << 10, []() { return (char)std::rand(); }); if (!ostream.flush()) { std::cerr << "Encountered an error while writing: " << s.words[1].s << std::endl; return; } std::cout << "Successfully wrote " << length << " kilobytes of random binary data to: " << s.words[1].s << std::endl; } #ifdef USE_DRIVE_NOTIFICATIONS void DemoApp::drive_presence_changed(bool appeared, const LocalPath& driveRoot) { std::cout << "Drive " << (appeared ? "connected" : "disconnected") << ": " << driveRoot.platformEncoded() << endl; } #endif // USE_DRIVE_NOTIFICATIONS // callback for non-EAGAIN request-level errors // in most cases, retrying is futile, so the application exits // this can occur e.g. with syntactically malformed requests (due to a bug), an invalid application key void DemoApp::request_error(error e) { if ((e == API_ESID) || (e == API_ENOENT)) // Invalid session or Invalid folder handle { cout << "Invalid or expired session, logging out..." << endl; client->locallogout(true, true); return; } else if (e == API_EBLOCKED) { if (client->sid.size()) { cout << "Your account is blocked." << endl; client->whyamiblocked(); } else { cout << "The link has been blocked." << endl; } return; } cout << "FATAL: Request failed (" << errorstring(e) << "), exiting" << endl; #ifndef NO_READLINE rl_callback_handler_remove(); #endif /* ! NO_READLINE */ delete console; exit(0); } void DemoApp::request_response_progress(m_off_t current, m_off_t total) { if (total > 0) { responseprogress = int(current * 100 / total); } else { responseprogress = -1; } } //2FA disable result void DemoApp::multifactorauthdisable_result(error e) { if (!e) { cout << "2FA, disabled succesfully..." << endl; } else { cout << "Error enabling 2FA : " << errorstring(e) << endl; } setprompt(COMMAND); } //2FA check result void DemoApp::multifactorauthcheck_result(int enabled) { if (enabled) { cout << "2FA is enabled for this account" << endl; } else { cout << "2FA is disabled for this account" << endl; } setprompt(COMMAND); } //2FA enable result void DemoApp::multifactorauthsetup_result(string *code, error e) { if (!e) { if (!code) { cout << "2FA enabled successfully" << endl; setprompt(COMMAND); attempts = 0; } else { cout << "2FA code: " << *code << endl; setprompt(SETTFA); } } else { cout << "Error enabling 2FA : " << errorstring(e) << endl; if (e == API_EFAILED) { if (++attempts >= 3) { attempts = 0; cout << "Too many attempts"<< endl; setprompt(COMMAND); } else { setprompt(SETTFA); } } } } void DemoApp::prelogin_result(int version, string* /*email*/, string *salt, error e) { if (e) { cout << "Login error: " << e << endl; setprompt(COMMAND); return; } login.version = version; login.salt = (version == 2 && salt ? *salt : string()); if (login.password.empty()) { setprompt(LOGINPASSWORD); } else { login.login(client); } } // login result void DemoApp::login_result(error e) { if (!e) { login.reset(); cout << "Login successful." << endl; // Delay fetchnodes to give time to the SDK to finish and unlock the internal // "nodeTreeMutex". login.succeeded = true; } else if (e == API_EMFAREQUIRED) { setprompt(LOGINTFA); } else { login.reset(); cout << "Login failed: " << errorstring(e) << endl; } } // ephemeral session result void DemoApp::ephemeral_result(error e) { if (e) { cout << "Ephemeral session error (" << errorstring(e) << ")" << endl; } } // signup link send request result void DemoApp::sendsignuplink_result(error e) { if (e) { cout << "Unable to send signup link (" << errorstring(e) << ")" << endl; } else { cout << "Thank you. Please check your e-mail and enter the command signup followed by the confirmation link." << endl; } } void DemoApp::confirmsignuplink2_result(handle, const char* /*name*/, const char* email, error e) { if (e) { cout << "Signuplink confirmation failed (" << errorstring(e) << ")" << endl; } else { cout << "Signup confirmed successfully. Logging by first time..." << endl; login.reset(); login.email = email; login.password = newpassword; client->prelogin(email); } } // asymmetric keypair configuration result void DemoApp::setkeypair_result(error e) { if (e) { cout << "RSA keypair setup failed (" << errorstring(e) << ")" << endl; } else { cout << "RSA keypair added. Account setup complete." << endl; } } void DemoApp::getrecoverylink_result(error e) { if (e) { cout << "Unable to send the link (" << errorstring(e) << ")" << endl; } else { cout << "Please check your e-mail and enter the command \"recover\" / \"cancel\" followed by the link." << endl; } } void DemoApp::queryrecoverylink_result(error e) { cout << "The link is invalid (" << errorstring(e) << ")." << endl; } void DemoApp::queryrecoverylink_result(int type, const char *email, const char* /*ip*/, time_t /*ts*/, handle /*uh*/, const vector* /*emails*/) { recoveryemail = email ? email : ""; hasMasterKey = (type == RECOVER_WITH_MASTERKEY); cout << "The link is valid"; if (type == RECOVER_WITH_MASTERKEY) { cout << " to reset the password for " << email << " with masterkey." << endl; setprompt(MASTERKEY); } else if (type == RECOVER_WITHOUT_MASTERKEY) { cout << " to reset the password for " << email << " without masterkey." << endl; setprompt(NEWPASSWORD); } else if (type == CANCEL_ACCOUNT) { cout << " to cancel the account for " << email << "." << endl; } else if (type == CHANGE_EMAIL) { cout << " to change the email from " << client->finduser(client->me)->email << " to " << email << "." << endl; changeemail = email ? email : ""; setprompt(LOGINPASSWORD); } } void DemoApp::getprivatekey_result(error e, const byte *privk, const size_t len_privk) { if (e) { cout << "Unable to get private key (" << errorstring(e) << ")" << endl; setprompt(COMMAND); } else { // check the private RSA is valid after decryption with master key SymmCipher key; key.setkey(masterkey); byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; memcpy(privkbuf, privk, len_privk); key.ecb_decrypt(privkbuf, len_privk); AsymmCipher uk; if (!uk.setkey(AsymmCipher::PRIVKEY, privkbuf, static_cast(len_privk))) { cout << "The master key doesn't seem to be correct." << endl; recoverycode.clear(); recoveryemail.clear(); hasMasterKey = false; memset(masterkey, 0, sizeof masterkey); setprompt(COMMAND); } else { cout << "Private key successfully retrieved for integrity check masterkey." << endl; setprompt(NEWPASSWORD); } } } void DemoApp::confirmrecoverylink_result(error e) { if (e) { cout << "Unable to reset the password (" << errorstring(e) << ")" << endl; } else { cout << "Password changed successfully." << endl; } } void DemoApp::confirmcancellink_result(error e) { if (e) { cout << "Unable to cancel the account (" << errorstring(e) << ")" << endl; } else { cout << "Account cancelled successfully." << endl; } } void DemoApp::validatepassword_result(error e) { if (e) { cout << "Wrong password (" << errorstring(e) << ")" << endl; setprompt(LOGINPASSWORD); } else { if (recoverycode.size()) { cout << "Password is correct, cancelling account..." << endl; client->confirmcancellink(recoverycode.c_str()); recoverycode.clear(); } else if (changecode.size()) { cout << "Password is correct, changing email..." << endl; client->confirmemaillink(changecode.c_str(), changeemail.c_str(), pwkey); changecode.clear(); changeemail.clear(); } } } void DemoApp::getemaillink_result(error e) { if (e) { cout << "Unable to send the link (" << errorstring(e) << ")" << endl; } else { cout << "Please check your e-mail and enter the command \"email\" followed by the link." << endl; } } void DemoApp::confirmemaillink_result(error e) { if (e) { cout << "Unable to change the email address (" << errorstring(e) << ")" << endl; } else { cout << "Email address changed successfully to " << changeemail << "." << endl; } } void DemoApp::ephemeral_result(handle uh, const byte* pw) { cout << "Ephemeral session established, session ID: "; if (client->loggedin() == EPHEMERALACCOUNT) { cout << Base64Str(uh) << "#"; cout << Base64Str(pw) << endl; } else { string session; client->dumpsession(session); cout << Base64::btoa(session) << endl; } client->fetchnodes(false, false, false); } void DemoApp::cancelsignup_result(error) { cout << "Singup link canceled. Start again!" << endl; signupcode.clear(); signupemail.clear(); signupname.clear(); } void DemoApp::whyamiblocked_result(int code) { if (code < 0) { error e = (error) code; cout << "Why am I blocked failed: " << errorstring(e) << endl; } else if (code == 0) { cout << "You're not blocked" << endl; } else // code > 0 { string reason = "Your account was terminated due to breach of Mega's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse."; if (code == 100) // deprecated { reason = "You have been suspended due to excess data usage."; } else if (code == 200) { reason = "Your account has been suspended due to copyright violations. Please check your email inbox."; } else if (code == 300) { reason = "Your account was terminated due to a breach of MEGA's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse."; } else if (code == 400) { reason = "Your account has been disabled by your administrator. You may contact your business account administrator for further details."; } else if (code == 401) { reason = "Your account has been removed by your administrator. You may contact your business account administrator for further details."; } else if (code == 500) { reason = "Your account has been blocked pending verification via SMS."; } else if (code == 700) { reason = "Your account has been temporarily suspended for your safety. Please verify your email and follow its steps to unlock your account."; } //else if (code == ACCOUNT_BLOCKED_DEFAULT) --> default reason cout << "Reason: " << reason << endl; if (code != 500 && code != 700) { cout << "Logging out..." << endl; client->locallogout(true, true); } } } void DemoApp::upgrading_security() { cout << "We are upgrading the cryptographic resilience of your account. You will see this message only once. " << "If you see it again in the future, you may be under attack by us. If you have seen it in the past, do not proceed." << endl; cout << "You are currently sharing the following folders." << endl; listallshares(); cout << "------------------------------------------------" << endl; client->upgradeSecurity([](Error e) { if (e) { cout << "Security upgrade failed (" << errorstring(e) << ")" << endl; exit(1); } else { cout << "Security upgrade succeeded." << endl; } }); } void DemoApp::downgrade_attack() { cout << "A downgrade attack has been detected. Removed shares may have reappeared. Please tread carefully."; } // password change result void DemoApp::changepw_result(error e) { if (e) { cout << "Password update failed: " << errorstring(e) << endl; } else { cout << "Password updated." << endl; } } void exportnode_result(Error e, handle h, handle ph) { if (e) { cout << "Export failed: " << errorstring(e) << endl; return; } std::shared_ptr n; if ((n = client->nodebyhandle(h))) { string path; nodepath(NodeHandle().set6byte(h), &path); cout << "Exported " << path << ": "; if (n->type != FILENODE && !n->sharekey) { cout << "No key available for exported folder" << endl; return; } string publicLink; TypeOfLink lType = client->validTypeForPublicURL(n->type); if (n->type == FILENODE) { publicLink = MegaClient::publicLinkURL(client->mNewLinkFormat, lType, ph, Base64Str((const byte*)n->nodekey().data())); } else { publicLink = MegaClient::publicLinkURL(client->mNewLinkFormat, lType, ph, Base64Str(n->sharekey->key)); } cout << publicLink; if (n->plink) { string authKey = n->plink->mAuthKey; if (authKey.size()) { string authToken(publicLink); authToken = authToken.substr(MegaClient::getMegaURL().size() + strlen("/folder/")) .append(":") .append(authKey); cout << "\n AuthToken = " << authToken; } } cout << endl; } else { cout << "Exported node no longer available" << endl; } } // the requested link could not be opened void DemoApp::openfilelink_result(const Error& e) { if (e) { if (e == API_ETOOMANY && e.hasExtraInfo()) { cout << "Failed to open link: " << getExtraInfoErrorString(e) << endl; } else { cout << "Failed to open link: " << errorstring(e) << endl; } } } // the requested link was opened successfully - import to cwd void DemoApp::openfilelink_result(handle ph, const byte* key, m_off_t size, string* a, string* /*fa*/, int) { std::shared_ptr n; if (!key) { cout << "File is valid, but no key was provided." << endl; return; } // check if the file is decryptable string attrstring; attrstring.resize(a->length()*4/3+4); attrstring.resize(Base64::btoa((const byte*)a->data(), a->length(), (char*)attrstring.data())); SymmCipher nodeKey; nodeKey.setkey(key, FILENODE); byte *buf = Node::decryptattr(&nodeKey,attrstring.c_str(), attrstring.size()); if (!buf) { cout << "The file won't be imported, the provided key is invalid." << endl; } else if (client->loggedin() != NOTLOGGEDIN) { n = client->nodeByHandle(cwd); if (!n) { cout << "Target folder not found." << endl; delete [] buf; return; } AttrMap attrs; JSON json; nameid name; string* t; json.begin((char*)buf + 5); vector nn(1); NewNode* newnode = &nn[0]; // set up new node as folder node newnode->source = NEW_PUBLIC; newnode->type = FILENODE; newnode->nodehandle = ph; newnode->parenthandle = UNDEF; newnode->nodekey.assign((char*)key, FILENODEKEYLENGTH); newnode->attrstring.reset(new string(*a)); while ((name = json.getnameid()) != EOO && json.storeobject((t = &attrs.map[name]))) { JSON::unescape(t); if (name == makeNameid("n")) { LocalPath::utf8_normalize(t); } } attr_map::iterator it = attrs.map.find(makeNameid("n")); if (it != attrs.map.end()) { std::shared_ptr ovn = client->childnodebyname(n.get(), it->second.c_str(), true); if (ovn) { attr_map::iterator it2 = attrs.map.find(makeNameid("c")); if (it2 != attrs.map.end()) { FileFingerprint ffp; if (ffp.unserializefingerprint(&it2->second)) { ffp.size = size; if (ffp.isvalid && ovn->isvalid && ffp == *(FileFingerprint*)ovn.get()) { cout << "Success. (identical node skipped)" << endl; delete [] buf; return; } } } newnode->ovhandle = ovn->nodeHandle(); } } client->putnodes(n->nodeHandle(), UseLocalVersioningFlag, std::move(nn), nullptr, client->restag, false); } else { cout << "Need to be logged in to import file links." << endl; } delete [] buf; } void DemoApp::folderlinkinfo_result(error e, handle owner, handle /*ph*/, string* attr, string* k, m_off_t currentSize, uint32_t numFiles, uint32_t numFolders, m_off_t /*versionsSize*/, uint32_t numVersions) { if (e != API_OK) { cout << "Retrieval of public folder link information failed: " << e << endl; return; } handle ph; byte folderkey[FOLDERNODEKEYLENGTH]; #ifndef NDEBUG error eaux = #endif client->parsepubliclink(publiclink.c_str(), ph, folderkey, TypeOfLink::FOLDER); assert(eaux == API_OK); // Decrypt nodekey with the key of the folder link SymmCipher cipher; cipher.setkey(folderkey); const char *nodekeystr = k->data() + 9; // skip the userhandle(8) and the `:` byte nodekey[FOLDERNODEKEYLENGTH]; if (client->decryptkey(nodekeystr, nodekey, sizeof(nodekey), &cipher, 0, UNDEF)) { // Decrypt node attributes with the nodekey cipher.setkey(nodekey); byte* buf = Node::decryptattr(&cipher, attr->c_str(), attr->size()); if (buf) { AttrMap attrs; string fileName; string fingerprint; // raw fingerprint without App's prefix (different layer) FileFingerprint ffp; m_time_t mtime = 0; Node::parseattr(buf, attrs, currentSize, mtime, fileName, fingerprint, ffp); // Normalize node name to UTF-8 string attr_map::iterator it = attrs.map.find('n'); if (it != attrs.map.end() && !it->second.empty()) { LocalPath::utf8_normalize(&(it->second)); fileName = it->second.c_str(); } std::string ownerStr, ownerBin((const char *)&owner, sizeof(owner)); Base64::btoa(ownerBin, ownerStr); cout << "Folder link information:" << publiclink << endl; cout << "\tFolder name: " << fileName << endl; cout << "\tOwner: " << ownerStr << endl; cout << "\tNum files: " << numFiles << endl; cout << "\tNum folders: " << numFolders - 1 << endl; cout << "\tNum versions: " << numVersions << endl; delete [] buf; } else { cout << "folderlink: error decrypting node attributes with decrypted nodekey" << endl; } } else { cout << "folderlink: error decrypting nodekey with folder link key"; } publiclink.clear(); } bool DemoApp::pread_data(byte* data, m_off_t len, m_off_t pos, m_off_t, m_off_t, void* /*appdata*/) { // Improvement: is there a way to have different pread_data receivers for // different modes? if(more_node) // are we paginating through a node? { fwrite(data, 1, size_t(len), stdout); if((pos + len) >= more_node->size) // is this the last chunk? { more_node = nullptr; more_offset = 0; cout << "-End of file-" << endl; setprompt(COMMAND); } else { // there's more to get, so set PAGER prompt setprompt(PAGER); more_offset += len; } } else if (pread_file) { pread_file->write((const char*)data, static_cast(len)); cout << "Received " << len << " partial read byte(s) at position " << pos << endl; if (pread_file_end == pos + len) { delete pread_file; pread_file = NULL; cout << "Completed pread" << endl; } } else { cout << "Received " << len << " partial read byte(s) at position " << pos << ": "; fwrite(data, 1, size_t(len), stdout); cout << endl; } return true; } dstime DemoApp::pread_failure(const Error &e, int retry, void* /*appdata*/, dstime) { if (retry < 5 && !(e == API_ETOOMANY && e.hasExtraInfo())) { cout << "Retrying read (" << errorstring(e) << ", attempt #" << retry << ")" << endl; return (dstime)(retry*10); } else { cout << "Too many failures (" << errorstring(e) << "), giving up" << endl; if (pread_file) { delete pread_file; pread_file = NULL; } return NEVER; } } // reload needed void DemoApp::notifyError(const char* reason, ErrorReason errorReason) { cout << "Error has been detected: " << errorReason << " (" << reason << ")" << endl; } void DemoApp::reloading() { cout << "Reload forced from server. Waiting for response..." << endl; } // reload initiated void DemoApp::clearing() { LOG_debug << "Clearing all nodes/users..."; } // nodes have been modified // (nodes with their removed flag set will be deleted immediately after returning from this call, // at which point their pointers will become invalid at that point.) void DemoApp::nodes_updated(sharedNode_vector* nodes, int count) { int c[2][6] = { { 0 } }; if (nodes) { auto it = nodes->begin(); while (count--) { if ((*it)->type < 6) { c[!(*it)->changed.removed][(*it)->type]++; it++; } } } else { sharedNode_vector rootNodes = client->mNodeManager.getRootNodes(); sharedNode_vector inshares = client->mNodeManager.getNodesWithInShares(); rootNodes.insert(rootNodes.end(), inshares.begin(), inshares.end()); for (auto& node : rootNodes) { if (!node->parent) // No take account nested inshares { c[1][node->type] ++; c[1][FOLDERNODE] += static_cast(node->getCounter().folders); c[1][FILENODE] += static_cast(node->getCounter().files + node->getCounter().versions); } } } nodestats(c[1], "added or updated"); nodestats(c[0], "removed"); if (cwd.isUndef()) { cwd = client->mNodeManager.getRootNodeFiles(); } } // nodes now (almost) current, i.e. no server-client notifications pending void DemoApp::nodes_current() { LOG_debug << "Nodes current."; } void DemoApp::account_updated() { LOG_debug << "Account has been upgraded/downgraded."; } void DemoApp::notify_confirmation(const char* email) { // Upon "uac" action packet. Cryptographic system initialized, ready for login. if (client->loggedin() == EPHEMERALACCOUNT || client->loggedin() == EPHEMERALACCOUNTPLUSPLUS) { LOG_debug << "Account setup completed with email " << email << "."; cout << "Account setup completed with email " << email << ". Proceed to login with credentials." << endl; } } void DemoApp::notify_confirm_user_email(handle user, const char* email) { // Upon "uec" action packet. Account confirmed with the signup link. if (client->loggedin() == EPHEMERALACCOUNT || client->loggedin() == EPHEMERALACCOUNTPLUSPLUS) { LOG_debug << "Account has been confirmed with user " << toHandle(user) << " and email " << email; } } void DemoApp::sequencetag_update(const string& st) { if(gVerboseMode) { conlock(cout) << "Latest seqTag: " << st << endl; } } // set addition/update/removal/export {en|dis}able/ void DemoApp::sets_updated(Set** s, int count) { cout << (count == 1 ? string("1 Set") : (std::to_string(count) + " Sets")) << " received" << endl; if (!s) return; for (int i = 0; i < count; i++) { Set* set = s[i]; cout << "Set " << toHandle(set->id()); if (set->hasChanged(Set::CH_NEW)) { cout << " has been added"; } if (set->hasChanged(Set::CH_EXPORTED)) { cout << " export has been " << (set->publicId() == UNDEF ? "dis" : "en") << "abled"; } else if (set->hasChanged(Set::CH_REMOVED)) { cout << " has been removed"; } else { if (set->hasChanged(Set::CH_NAME)) { cout << endl << "\tchanged name"; } if (set->hasChanged(Set::CH_COVER)) { cout << endl << "\tchanged cover"; } } cout << endl; } } // element addition/update/removal void DemoApp::setelements_updated(SetElement** el, int count) { cout << (count == 1 ? string("1 Element") : (std::to_string(count) + " Elements")) << " received" << endl; if (!el) return; for (int i = 0; i < count; i++) { SetElement* elem = el[i]; cout << "Element " << toHandle(elem->id()); if (elem->hasChanged(SetElement::CH_EL_NEW)) { cout << " has been added"; } else if (elem->hasChanged(Set::CH_REMOVED)) { cout << " has been removed"; } else { if (elem->hasChanged(SetElement::CH_EL_NAME)) { cout << endl << "\tchanged name"; } if (elem->hasChanged(SetElement::CH_EL_ORDER)) { cout << endl << "\tchanged order"; } } cout << endl; } } void DemoApp::enumeratequotaitems_result(const Product& product) { if (product.planType != 1) // All plans but Business { cout << "\n" << product.description << ":\n"; cout << "\tPro level: " << product.proLevel << "\n"; cout << "\tStorage: " << product.gbStorage << "\n"; cout << "\tTransfer: " << product.gbTransfer << "\n"; cout << "\tMonths: " << product.months << "\n"; cout << "\tPrice: " << product.amount << "\n"; cout << "\tPrice net (without tax): " << product.priceNet << "\n"; cout << "\tMonthly base price: " << product.amountMonth << "\n"; cout << "\tMonthly base price net (without tax): " << product.monthlyBasePriceNet << "\n"; cout << "\tLocal price: " << product.localPrice << "\n"; cout << "\tLocal price net (without tax): " << product.localPriceNet << "\n"; cout << "\tFeatures:\n"; if (product.features.empty()) { cout << "\t\t[none]\n"; } else { for (const auto& f: product.features) { cout << "\t\t" << f.first << ": " << f.second << '\n'; } } cout << "\tiOS ID: " << product.iosid << "\n"; cout << "\tAndroid ID: " << product.androidid << "\n"; cout << "\tTest Category: " << product.testCategory << "\n"; cout << "\tTrial Days: " << product.trialDays << endl; if (product.mobileOffer) { cout << "\tMobile offer" << "\n"; cout << "\t\tId: " << product.mobileOffer->id << "\n"; cout << "\t\tUat: " << product.mobileOffer->uat << "\n"; } } else // Business plan (type == 1) { cout << "\n" << product.description << ":\n"; cout << "\tMinimum users: " << product.businessPlan->minUsers << "\n"; cout << "\tStorage per user: " << product.businessPlan->gbStoragePerUser << "\n"; cout << "\tTransfer per user: " << product.businessPlan->gbTransferPerUser << "\n"; cout << "\tPrice per user: " << product.businessPlan->pricePerUser << "\n"; cout << "\tLocal price per user: " << product.businessPlan->localPricePerUser << "\n"; cout << "\tPrice per storage: " << product.businessPlan->pricePerStorage << "\n"; cout << "\tLocal price per storage: " << product.businessPlan->localPricePerStorage << "\n"; cout << "\tGigabytes per storage: " << product.businessPlan->gbPerStorage << "\n"; cout << "\tPrice per transfer: " << product.businessPlan->pricePerTransfer << "\n"; cout << "\tLocal price per transfer: " << product.businessPlan->localPricePerTransfer << "\n"; cout << "\tGigabytes per transfer: " << product.businessPlan->gbPerTransfer << "\n"; cout << "\tTest Category: " << product.testCategory << endl; } } void DemoApp::enumeratequotaitems_result(unique_ptr data) { cout << "\nCurrency data: " << endl; cout << "\tName: " << data->currencyName; cout << "\tSymbol: " << Base64::atob(data->currencySymbol); if (data->localCurrencyName.size()) { cout << "\tName (local): " << data->localCurrencyName; cout << "\tSymbol (local): " << Base64::atob(data->localCurrencySymbol); } cout << endl; } void DemoApp::enumeratequotaitems_result(error e) { if (e != API_OK) { cout << "Error retrieving pricing plans, error code " << e << endl; } } void DemoApp::additem_result(error) { // FIXME: implement } void DemoApp::checkout_result(const char*, error) { // FIXME: implement } void DemoApp::getmegaachievements_result(AchievementsDetails *details, error /*e*/) { // FIXME: implement display of values delete details; } #ifdef ENABLE_CHAT void DemoApp::richlinkrequest_result(string *json, error e) { if (!e) { cout << "Result:" << endl << *json << endl; } else { cout << "Failed to request rich link. Error: " << e << endl; } } #endif void DemoApp::contactlinkcreate_result(error e, handle h) { if (e) { cout << "Failed to create contact link. Error: " << e << endl; } else { cout << "Contact link created successfully: " << LOG_NODEHANDLE(h) << endl; } } void DemoApp::contactlinkquery_result(error e, handle h, string *email, string *fn, string *ln, string* /*avatar*/) { if (e) { cout << "Failed to get contact link details. Error: " << e << endl; } else { cout << "Contact link created successfully: " << endl; cout << "\tUserhandle: " << LOG_HANDLE(h) << endl; cout << "\tEmail: " << *email << endl; cout << "\tFirstname: " << Base64::atob(*fn) << endl; cout << "\tLastname: " << Base64::atob(*ln) << endl; } } void DemoApp::contactlinkdelete_result(error e) { if (e) { cout << "Failed to delete contact link. Error: " << e << endl; } else { cout << "Contact link deleted successfully." << endl; } } void reportNodeStorage(NodeStorage* ns, const string& rootnodename) { cout << "\t\tIn " << rootnodename << ": " << ns->bytes << " byte(s) in " << ns->files << " file(s) and " << ns->folders << " folder(s)" << endl; cout << "\t\tUsed storage by versions: " << ns->version_bytes << " byte(s) in " << ns->version_files << " file(s)" << endl; } // display account details/history void DemoApp::account_details(AccountDetails* ad, bool storage, bool transfer, bool pro, bool purchases, bool transactions, bool sessions) { char timebuf[32], timebuf2[32]; if (storage) { cout << "\tAvailable storage: " << ad->storage_max << " byte(s) used: " << ad->storage_used << " available: " << (ad->storage_max - ad->storage_used) << endl; reportNodeStorage(&ad->storage[client->mNodeManager.getRootNodeFiles().as8byte()], "/"); reportNodeStorage(&ad->storage[client->mNodeManager.getRootNodeVault().as8byte()], "//in"); reportNodeStorage(&ad->storage[client->mNodeManager.getRootNodeRubbish().as8byte()], "//bin"); } if (transfer) { if (ad->transfer_max) { long long transferFreeUsed = 0; for (unsigned i = 0; i < ad->transfer_hist.size(); i++) { transferFreeUsed += ad->transfer_hist[i]; } cout << "\tTransfer in progress: " << ad->transfer_own_reserved << "/" << ad->transfer_srv_reserved << endl; cout << "\tTransfer completed: " << ad->transfer_own_used << "/" << ad->transfer_srv_used << "/" << transferFreeUsed << " of " << ad->transfer_max << " (" << (100 * (ad->transfer_own_used + ad->transfer_srv_used + transferFreeUsed) / ad->transfer_max) << "%)" << endl; cout << "\tServing bandwidth ratio: " << ad->srv_ratio << "%" << endl; } if (ad->transfer_hist_starttime) { m_time_t t = m_time() - ad->transfer_hist_starttime; cout << "\tTransfer history:\n"; for (unsigned i = 0; i < ad->transfer_hist.size(); i++) { cout << "\t\t" << t; t -= ad->transfer_hist_interval; if (t < 0) { cout << " second(s) ago until now: "; } else { cout << "-" << t << " second(s) ago: "; } cout << ad->transfer_hist[i] << " byte(s)" << endl; } } } if (pro) { cout << "\tAccount Subscriptions:" << endl; for (const auto& sub: ad->subscriptions) { cout << "\t\t* ID: " << sub.id << endl; cout << "\t\t\t Status(type): "; switch (sub.type) { case 'S': cout << "VALID"; break; case 'R': cout << "INVALID"; break; default: cout << "NONE"; break; } cout << " (" << sub.type << ")" << endl; cout << "\t\t\t Cycle: " << sub.cycle << endl; cout << "\t\t\t Payment Method: " << sub.paymentMethod << endl; cout << "\t\t\t Payment Method ID: " << sub.paymentMethodId << endl; cout << "\t\t\t Renew time: " << sub.renew << endl; cout << "\t\t\t Account level: " << sub.level << endl; cout << "\t\t\t Is Trial: " << (sub.isTrial ? "Yes" : "No") << endl; cout << "\t\t\t Features: "; for (const auto& f: sub.features) { cout << f << ", "; } cout << endl; } cout << "\tAccount Active Plans:" << endl; for (const auto& plan: ad->plans) { cout << "\t\t* Plan details: " << endl; cout << "\t\t\t Account level: " << plan.level << endl; cout << "\t\t\t Is Trial: " << (plan.isTrial ? "Yes" : "No") << endl; cout << "\t\t\t Features: "; for (const auto& f: plan.features) { cout << f << ", "; } cout << endl; cout << "\t\t\t Expiration time: " << plan.expiration << endl; cout << "\t\t\t Plan type: " << plan.type << endl; cout << "\t\t\t Related subscription id: " << plan.subscriptionId << endl; } cout << "\tLatest PRO plan expiration: " << ad->pro_until << endl; cout << "\tAccount balance:" << endl; for (vector::iterator it = ad->balances.begin(); it != ad->balances.end(); it++) { printf("\t\tBalance: %.3s %.02f\n", it->currency, it->amount); } } if (purchases) { cout << "Purchase history:" << endl; for (vector::iterator it = ad->purchases.begin(); it != ad->purchases.end(); it++) { time_t ts = it->timestamp; strftime(timebuf, sizeof timebuf, "%c", localtime(&ts)); printf("\tID: %.11s Time: %s Amount: %.3s %.02f Payment method: %d\n", it->handle, timebuf, it->currency, it->amount, it->method); } } if (transactions) { cout << "Transaction history:" << endl; for (vector::iterator it = ad->transactions.begin(); it != ad->transactions.end(); it++) { time_t ts = it->timestamp; strftime(timebuf, sizeof timebuf, "%c", localtime(&ts)); printf("\tID: %.11s Time: %s Delta: %.3s %.02f\n", it->handle, timebuf, it->currency, it->delta); } } if (sessions) { cout << "Currently Active Sessions:" << endl; for (vector::iterator it = ad->sessions.begin(); it != ad->sessions.end(); it++) { if (it->alive) { time_t ts = it->timestamp; strftime(timebuf, sizeof timebuf, "%c", localtime(&ts)); ts = it->mru; strftime(timebuf2, sizeof timebuf, "%c", localtime(&ts)); Base64Str id(it->id); if (it->current) { printf("\t* Current Session\n"); } printf("\tSession ID: %s\n\tSession start: %s\n\tMost recent activity: %s\n\tIP: %s\n\tCountry: %.2s\n\tUser-Agent: %s\n\tDevice ID: %s\n\t-----\n", id.chars, timebuf, timebuf2, it->ip.c_str(), it->country, it->useragent.c_str(), it->deviceid.c_str()); } } if(gVerboseMode) { cout << endl << "Full Session history:" << endl; for (vector::iterator it = ad->sessions.begin(); it != ad->sessions.end(); it++) { time_t ts = it->timestamp; strftime(timebuf, sizeof timebuf, "%c", localtime(&ts)); ts = it->mru; strftime(timebuf2, sizeof timebuf, "%c", localtime(&ts)); printf("\tSession start: %s\n\tMost recent activity: %s\n\tIP: %s\n\tCountry: %.2s\n\tUser-Agent: %s\n\t-----\n", timebuf, timebuf2, it->ip.c_str(), it->country, it->useragent.c_str()); } } } } // account details could not be retrieved void DemoApp::account_details(AccountDetails* /*ad*/, error e) { if (e) { cout << "Account details retrieval failed (" << errorstring(e) << ")" << endl; } } // account details could not be retrieved void DemoApp::sessions_killed(handle sessionid, error e) { if (e) { cout << "Session killing failed (" << errorstring(e) << ")" << endl; return; } if (sessionid == UNDEF) { cout << "All sessions except current have been killed" << endl; } else { Base64Str id(sessionid); cout << "Session with id " << id << " has been killed" << endl; } } void DemoApp::smsverificationsend_result(error e) { if (e) { cout << "SMS send failed: " << e << endl; } else { cout << "SMS send succeeded" << endl; } } void DemoApp::smsverificationcheck_result(error e, string *phoneNumber) { if (e) { cout << "SMS verification failed: " << e << endl; } else { cout << "SMS verification succeeded" << endl; if (phoneNumber) { cout << "Phone number: " << *phoneNumber << ")" << endl; } } } // user attribute update notification void DemoApp::userattr_update(User* u, int priv, const char* n) { cout << "Notification: User " << u->email << " -" << (priv ? " private" : "") << " attribute " << n << " added or updated" << endl; } void DemoApp::resetSmsVerifiedPhoneNumber_result(error e) { if (e) { cout << "Reset verified phone number failed: " << e << endl; } else { cout << "Reset verified phone number succeeded" << endl; } } void DemoApp::getbanners_result(error e) { cout << "Getting Smart Banners failed: " << e << endl; } void DemoApp::getbanners_result(vector&& banners) { for (auto& b : banners) { cout << "Smart Banner:" << endl << "\tid : " << b.id << endl << "\ttitle : " << b.title << endl << "\tdescription: " << b.description << endl << "\timage : " << b.image << endl << "\turl : " << b.url << endl << "\tbkgr image : " << b.backgroundImage << endl << "\tdsp : " << b.imageLocation << endl << "\tvariant : " << b.variant << endl << "\tbutton : " << b.button << endl; } } void DemoApp::dismissbanner_result(error e) { if (e) { cout << "Dismissing Smart Banner failed: " << e << endl; } else { cout << "Dismissing Smart Banner succeeded" << endl; } } #ifndef NO_READLINE char* longestCommonPrefix(ac::CompletionState& acs) { string s = acs.completions[0].s; for (size_t i = acs.completions.size(); i--; ) { for (unsigned j = 0; j < s.size() && j < acs.completions[i].s.size(); ++j) { if (s[j] != acs.completions[i].s[j]) { s.erase(j, string::npos); break; } } } return strdup(s.c_str()); } char** my_rl_completion(const char* /*text*/, int /*start*/, int end) { rl_attempted_completion_over = 1; std::string line(rl_line_buffer, static_cast(end)); ac::CompletionState acs = ac::autoComplete(line, line.size(), autocompleteTemplate, true); if (acs.completions.empty()) { return NULL; } if (acs.completions.size() == 1 && !acs.completions[0].couldExtend) { acs.completions[0].s += " "; } char** result = (char**)malloc((sizeof(char*)*(2+acs.completions.size()))); for (size_t i = acs.completions.size(); i--; ) { result[i+1] = strdup(acs.completions[i].s.c_str()); } result[acs.completions.size()+1] = NULL; result[0] = longestCommonPrefix(acs); //for (int i = 0; i <= acs.completions.size(); ++i) //{ // cout << "i " << i << ": " << result[i] << endl; //} rl_completion_suppress_append = true; rl_basic_word_break_characters = " \r\n"; rl_completer_word_break_characters = strdup(" \r\n"); rl_completer_quote_characters = ""; rl_special_prefixes = ""; return result; } #endif // main loop void megacli() { #ifndef NO_READLINE char *saved_line = NULL; int saved_point = 0; rl_attempted_completion_function = my_rl_completion; rl_save_prompt(); // Initialize history. using_history(); #elif defined(WIN32) && defined(NO_READLINE) static_cast(console)->setShellConsole(CP_UTF8, GetConsoleOutputCP()); COORD fontSize; string fontname = static_cast(console)->getConsoleFont(fontSize); cout << "Using font '" << fontname << "', " << fontSize.X << "x" << fontSize.Y << ". will be used for absent characters. If seen, try the 'codepage' command or a different font." << endl; #else #error non-windows platforms must use the readline library #endif for (;;) { if (prompt == COMMAND) { ostringstream dynamicprompt; // display put/get transfer speed in the prompt if (client->tslots.size() || responseprogress >= 0) { m_off_t xferrate[2] = { 0 }; Waiter::bumpds(); for (transferslot_list::iterator it = client->tslots.begin(); it != client->tslots.end(); it++) { if ((*it)->fa) { xferrate[(*it)->transfer->type] += (*it)->mTransferSpeed.getCircularMeanSpeed(); } } xferrate[GET] /= 1024; xferrate[PUT] /= 1024; dynamicprompt << "MEGA"; if (xferrate[GET] || xferrate[PUT] || responseprogress >= 0) { dynamicprompt << " ("; if (xferrate[GET]) { dynamicprompt << "In: " << xferrate[GET] << " KB/s"; if (xferrate[PUT]) { dynamicprompt << "/"; } } if (xferrate[PUT]) { dynamicprompt << "Out: " << xferrate[PUT] << " KB/s"; } if (responseprogress >= 0) { dynamicprompt << responseprogress << "%"; } dynamicprompt << ")"; } dynamicprompt << "> "; } string dynamicpromptstr = dynamicprompt.str(); #if defined(WIN32) && defined(NO_READLINE) { auto cl = conlock(cout); static_cast(console)->updateInputPrompt(!dynamicpromptstr.empty() ? dynamicpromptstr : prompts[COMMAND]); } #else rl_callback_handler_install(!dynamicpromptstr.empty() ? dynamicpromptstr.c_str() : prompts[prompt], store_line); // display prompt if (saved_line) { rl_replace_line(saved_line, 0); free(saved_line); } rl_point = saved_point; rl_redisplay(); #endif } // command editing loop - exits when a line is submitted or the engine requires the CPU for (;;) { int w = client->wait(); if (w & Waiter::HAVESTDIN) { #if defined(WIN32) && defined(NO_READLINE) line = static_cast(console)->checkForCompletedInputLine(); #else if ((prompt == COMMAND) || (prompt == PAGER)) { // Note: this doesn't act like unbuffered input, still // requires Return line ending rl_callback_read_char(); } else { console->readpwchar(pw_buf, sizeof pw_buf, &pw_buf_pos, &line); } #endif } if (w & Waiter::NEEDEXEC || line) { break; } } #ifndef NO_READLINE // save line saved_point = rl_point; saved_line = rl_copy_text(0, rl_end); // remove prompt rl_save_prompt(); rl_replace_line("", 0); rl_redisplay(); #endif if (line) { // execute user command if (*line) { process_line(line); } else if (prompt != COMMAND) { setprompt(prompt); } free(line); line = NULL; if (quit_flag) { #ifndef NO_READLINE rl_callback_handler_remove(); #endif /* ! NO_READLINE */ delete client; client = nullptr; return; } if (!cerr) { cerr.clear(); cerr << "Console error output failed, perhaps on a font related utf8 error or on NULL. It is now reset." << endl; } if (!cout) { cout.clear(); cerr << "Console output failed, perhaps on a font related utf8 error or on NULL. It is now reset." << endl; } } auto puts = appxferq[PUT].size(); auto gets = appxferq[GET].size(); // pass the CPU to the engine (nonblocking) client->exec(); if (clientFolder) clientFolder->exec(); if (login.succeeded) { // Continue with fetchnodes login.fetchnodes(client); client->exec(); } if (puts && !appxferq[PUT].size()) { cout << "Uploads complete" << endl; if (onCompletedUploads) onCompletedUploads(); } if (gets && !appxferq[GET].size()) { cout << "Downloads complete" << endl; } while (!mainloopActions.empty()) { mainloopActions.front()(); mainloopActions.pop_front(); } } } #ifndef NO_READLINE static void onFatalSignal(int signum) { // Restore the terminal's settings. rl_callback_handler_remove(); // Re-trigger the signal. raise(signum); } static void registerSignalHandlers() { std::vector signals = { SIGABRT, SIGBUS, SIGILL, SIGKILL, SIGSEGV, SIGTERM }; // signals struct sigaction action; action.sa_handler = &onFatalSignal; // Restore default signal handler after invoking our own. action.sa_flags = static_cast(SA_NODEFER | SA_RESETHAND); // Don't ignore any signals. sigemptyset(&action.sa_mask); for (int signal : signals) { (void)sigaction(signal, &action, nullptr); } } #endif // ! NO_READLINE MegaClient::ClientType getClientTypeFromArgs(const std::string& clientType) { if (clientType == "vpn") { return MegaClient::ClientType::VPN; } if (clientType == "password_manager") { return MegaClient::ClientType::PASSWORD_MANAGER; } if (clientType != "default") { cout << "WARNING: Invalid argument " << clientType << ". Valid possibilities are: vpn, password_manager, default.\nUsing default instead." << endl; } return MegaClient::ClientType::DEFAULT; } int main(int argc, char* argv[]) { #if defined(_WIN32) && defined(_DEBUG) _CrtSetBreakAlloc(124); // set this to an allocation number to hunt leaks. Prior to 124 and prior are from globals/statics so won't be detected by this #endif #ifndef NO_READLINE registerSignalHandlers(); #endif // NO_READLINE auto arguments = ArgumentsParser::parse(argc, argv); if (arguments.contains("-h")) { cout << USAGE << endl; return 0; } if (arguments.contains("-v")) { cout << "Arguments: \n" << arguments; } // config from arguments Config config; try { config = Config::fromArguments(arguments); } catch(const std::exception& e) { cout << "Error: " << e.what() << endl; cout << USAGE << endl; return -1; } SimpleLogger::setLogLevel(logVerbose); auto gLoggerAddr = &gLogger; getExternalLogger() .addMegaLogger( &gLogger, [gLoggerAddr](const char* time, int loglevel, const char* source, const char* message #ifdef ENABLE_LOG_PERFORMANCE , const char** directMessages, size_t* directMessagesSizes, unsigned numberMessages #endif ) { gLoggerAddr->log(time, loglevel, source, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, numberMessages #endif ); }); console = new CONSOLE_CLASS; std::unique_ptr provider = createGfxProvider(config); mega::GfxProc* gfx = provider ? new GfxProc(std::move(provider)) : nullptr; if (gfx) gfx->startProcessingThread(); // Needed so we can get the cwd. auto fsAccess = std::make_unique(); #ifdef __APPLE__ // Try and raise the file descriptor limit as high as we can. platformSetRLimitNumFile(); #endif // Where are we? if (!fsAccess->cwd(*startDir)) { cerr << "Unable to determine current working directory." << endl; return EXIT_FAILURE; } fsAccess.reset(); auto httpIO = new CurlHttpIO; #ifdef WIN32 auto waiter = std::make_shared(static_cast(console)); #else auto waiter = std::make_shared(); #endif auto demoApp = new DemoApp; auto dbAccess = #ifdef DBACCESS_CLASS new DBACCESS_CLASS(*startDir); #else nullptr; #endif auto clientType = getClientTypeFromArgs(config.clientType); // instantiate app components: the callback processor (DemoApp), // the HTTP I/O engine and the MegaClient itself client = new MegaClient(demoApp, waiter, httpIO, dbAccess, gfx, megacliUserAgent.c_str(), 2, clientType); ac::ACN acs = autocompleteSyntax(); #if defined(WIN32) && defined(NO_READLINE) static_cast(console)->setAutocompleteSyntax((acs)); #endif clientFolder = NULL; // additional for folder links megacli(); delete client; delete httpIO; delete gfx; delete demoApp; acs.reset(); autocompleteTemplate.reset(); delete console; startDir.reset(); #if defined(USE_OPENSSL) && !defined(OPENSSL_IS_BORINGSSL) if (CurlHttpIO::sslMutexes) { int numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; ++i) { delete CurlHttpIO::sslMutexes[i]; } delete [] CurlHttpIO::sslMutexes; } #endif #if defined(_WIN32) && defined(_DEBUG) // Singleton enthusiasts rarely think about shutdown... const CryptoPP::MicrosoftCryptoProvider &hProvider = CryptoPP::Singleton().Ref(); delete &hProvider; _CrtDumpMemoryLeaks(); #endif } void DemoAppFolder::login_result(error e) { if (e) { cout << "Failed to load the folder link: " << errorstring(e) << endl; } else { cout << "Folder link loaded, retrieving account..." << endl; clientFolder->fetchnodes(false, true, false); } } void DemoAppFolder::fetchnodes_result(const Error& e) { bool success = false; if (e) { if (e == API_ENOENT && e.hasExtraInfo()) { cout << "Folder retrieval failed: " << getExtraInfoErrorString(e) << endl; } else { cout << "Folder retrieval failed (" << errorstring(e) << ")" << endl; } } else { // check if the key is invalid if (clientFolder->isValidFolderLink()) { cout << "Folder link loaded correctly." << endl; success = true; } else { assert(client->nodeByHandle(client->mNodeManager.getRootNodeFiles())); // node is there, but cannot be decrypted cout << "Folder retrieval succeed, but encryption key is wrong." << endl; } } if (!success) { delete clientFolder; clientFolder = NULL; } } void DemoAppFolder::nodes_updated(sharedNode_vector* nodes, int count) { int c[2][6] = { { 0 } }; if (nodes) { auto it = nodes->begin(); while (count--) { if ((*it)->type < 6) { c[!(*it)->changed.removed][(*it)->type]++; it++; } } } else { sharedNode_vector rootNodes = client->mNodeManager.getRootNodes(); for (auto& node : rootNodes) { c[1][node->type] ++; c[1][FOLDERNODE] += static_cast(node->getCounter().folders); c[1][FILENODE] += static_cast(node->getCounter().files + node->getCounter().versions); } } cout << "The folder link contains "; nodestats(c[1], ""); } void exec_metamac(autocomplete::ACState& s) { auto p = s.words[2].s.c_str(); std::shared_ptr node = nodebypath(p); if (!node || node->type != FILENODE) { cerr << s.words[2].s << (node ? ": No such file or directory" : ": Not a file") << endl; return; } auto ifAccess = client->fsaccess->newfileaccess(); { auto localPath = localPathArg(s.words[1].s); if (!ifAccess->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError)) { cerr << "Failed to open: " << s.words[1].s << endl; return; } } SymmCipher cipher; int64_t remoteIv; int64_t remoteMac; { std::string remoteKey = node->nodekey(); const char *iva = &remoteKey[SymmCipher::KEYLENGTH]; cipher.setkey((byte*)&remoteKey[0], node->type); remoteIv = MemAccess::get(iva); remoteMac = MemAccess::get(iva + sizeof(int64_t)); } auto result = generateMetaMac(cipher, *ifAccess, remoteIv, p ? p : std::optional{std::nullopt}); if (!result.first) { cerr << "Failed to generate metamac for: " << s.words[1].s << endl; } else { const std::ios::fmtflags flags = cout.flags(); cout << s.words[2].s << " (remote): " << std::hex << (uint64_t)remoteMac << "\n" << s.words[1].s << " (local): " << (uint64_t)result.second << endl; cout.flags(flags); } } void exec_compare_file_and_node(autocomplete::ACState& s) { const std::string nodePath{s.words[2].s}; std::shared_ptr node = nodebypath(nodePath.c_str()); if (!node || node->type != FILENODE) { cerr << nodePath << (node ? ": No such file or directory" : ": Not a file") << endl; return; } const char* iva = &(node->nodekey())[SymmCipher::KEYLENGTH]; const int64_t nodeMetaMac = MemAccess::get(iva + sizeof(int64_t)); const std::string filePath{s.words[1].s}; auto fa = client->fsaccess->newfileaccess(); auto localPath = localPathArg(filePath.c_str()); if (!fa->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError)) { cerr << "Failed to open: " << filePath.c_str() << endl; return; } FileFingerprint localFileFp; if (localFileFp.genfingerprint(fa.get()); localFileFp.size < 0) { cerr << "Failed to generate fingerprint for file: " << filePath.c_str() << endl; return; } const auto [compRes, localFileMac] = CompareLocalFileWithNodeMacAndFpExludingMtime(*client, localPath, localFileFp, node.get()); std::string errMsg{"Node and file content comparisson: "}; switch (compRes) { case NODE_COMP_EREAD: errMsg += "Local file read error"; break; case NODE_COMP_EARGS: errMsg += "Arguments error"; break; case NODE_COMP_PENDING: errMsg += "MAC computation pending"; break; case NODE_COMP_DIFFERS_FP: errMsg += "Local file and Node fingerprints mismatch"; break; case NODE_COMP_DIFFERS_MAC: errMsg += "Local file and Node MetaMacs mismatch"; break; case NODE_COMP_DIFFERS_MTIME: errMsg += "Local file and Node are equal except mtime"; break; case NODE_COMP_EQUAL: errMsg += "Both items are completly equal"; break; } cout << errMsg << endl << "\t LocalFile Path: " << filePath << endl << "\t Node Path: " << nodePath << endl << "\t LocalFile Fingerprint: " << localFileFp.fingerprintDebugString() << endl << "\t Node Fingerprint: " << static_cast(*node).fingerprintDebugString() << endl << "\t LocalFile MetaMac: " << localFileMac << endl << "\t NodeMetaMac: " << nodeMetaMac << endl; } void exec_resetverifiedphonenumber(autocomplete::ACState&) { client->resetSmsVerifiedPhoneNumber(); } void exec_banner(autocomplete::ACState& s) { if (s.words.size() == 2 && s.words[1].s == "get") { cout << "Retrieving banner info..." << endl; client->queueCommand(new CommandGetBanners(client)); } else if (s.words.size() == 3 && s.words[1].s == "dismiss") { cout << "Dismissing banner with id " << s.words[2].s << "..." << endl; client->queueCommand(new CommandDismissBanner(client, stoi(s.words[2].s), m_time(nullptr))); } } #ifdef ENABLE_SYNC void sync_completion(error result, const SyncError& se, handle backupId) { if (backupId == UNDEF) { cerr << "Sync could not be added " << (se == SyncError::PUT_NODES_ERROR ? "(putnodes for backup failed)" : "") << ": " << errorstring(result) << endl; } else if (result == API_OK && se == NO_SYNC_ERROR) { cerr << "Sync added and running: " << toHandle(backupId) << endl; } else { cerr << "Sync added but could not be started: " << errorstring(result) << endl; } } void exec_syncadd(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to create a sync." << endl; return; } string drive, syncname, scanInterval; bool backup = s.extractflag("-backup"); bool external = s.extractflagparam("-external", drive); bool named = s.extractflagparam("-name", syncname); bool scanOnly = s.extractflag("-scan-only"); bool scanIntervalSpecified = s.extractflagparam("-scan-interval", scanInterval); LocalPath sourcePath = localPathArg(s.words[2].s); if (!named) { syncname = sourcePath.leafOrParentName(); } // sync add source target LocalPath drivePath = external ? localPathArg(drive) : LocalPath(); // Create a suitable sync config. auto config = SyncConfig(sourcePath, syncname, NodeHandle(), string(), fsfp_t(), std::move(drivePath), true, backup ? SyncConfig::TYPE_BACKUP : SyncConfig::TYPE_TWOWAY); // Scan interval if (scanIntervalSpecified) { auto i = atoi(scanInterval.c_str()); if (i >= 0) config.mScanIntervalSec = static_cast(i); } // Scan only. if (scanOnly) config.mChangeDetectionMethod = CDM_PERIODIC_SCANNING; if (!backup) // regular sync { // Does the target node exist? const string& targetPath = s.words[3].s; std::shared_ptr targetNode = nodebypath(targetPath.c_str()); if (!targetNode) { cerr << targetPath << ": Not found." << endl; return; } config.mRemoteNode = targetNode ? NodeHandle().set6byte(targetNode->nodehandle) : NodeHandle(); config.mOriginalPathOfRemoteRootNode = targetNode ? targetNode->displaypath() : string(); client->addsync(std::move(config), sync_completion, "", ""); } else // backup { // drive must be without trailing separator if (!drive.empty() && drive.back() == LocalPath::localPathSeparator_utf8) { drive.pop_back(); } #ifdef _WIN32 // source can be with or without trailing separator, except for Win where it must have it for drive root (OMG...) string src = s.words[2].s; if (!src.empty() && src.back() != LocalPath::localPathSeparator_utf8) { src.push_back(LocalPath::localPathSeparator_utf8); sourcePath = LocalPath::fromAbsolutePath(src); } #endif client->preparebackup(config, [](Error err, SyncConfig sc, MegaClient::UndoFunction revertOnError){ if (err != API_OK) { sync_completion(error(err), SyncError::PUT_NODES_ERROR, UNDEF); } else { client->addsync(std::move(sc), [revertOnError](error e, SyncError se, handle h){ if (e != API_OK) { if (revertOnError) { cerr << "Removing the created backup node, as backup sync add failed" << endl; revertOnError(nullptr); } } sync_completion(e, se, h); }, "", ""); } }); } } void exec_syncrename(autocomplete::ACState& s) { // Are we logged in? if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to manipulate backup syncs." << endl; return; } // get id handle backupId = 0; Base64::atob(s.words[2].s.c_str(), (byte*) &backupId, sizeof(handle)); string newname = s.words[3].s; client->syncs.renameSync(backupId, newname, [&](Error e) { if (!e) cout << "Rename succeeded" << endl; else cout << "Rename failed: " << e << endl; }); } void exec_syncclosedrive(autocomplete::ACState& s) { // Are we logged in? if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to manipulate backup syncs." << endl; return; } // sync backup remove drive const auto drivePath = localPathArg(s.words[2].s); client->syncs.backupCloseDrive(drivePath, [](Error e){ conlock(cout) << "syncclosedrive result: " << errorstring(e) << endl; }); } void exec_syncimport(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to import syncs." << endl; return; } auto flags = std::ios::binary | std::ios::in; ifstream istream(s.words[2].s, flags); if (!istream) { cerr << "Unable to open " << s.words[2].s << " for reading." << endl; return; } string data; for (char buffer[512]; istream; ) { istream.read(buffer, sizeof(buffer)); data.append(buffer, static_cast(istream.gcount())); } if (!istream.eof()) { cerr << "Unable to read " << s.words[2].s << endl; return; } auto completion = [](error result) { if (result) { cerr << "Unable to import sync configs: " << errorstring(result) << endl; return; } cout << "Sync configs successfully imported." << endl; }; cout << "Importing sync configs..." << endl; client->importSyncConfigs(data.c_str(), std::move(completion)); } void exec_syncexport(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to export syncs." << endl; return; } auto configs = client->syncs.exportSyncConfigs(); if (s.words.size() == 2) { cout << "Configs exported as: " << configs << endl; return; } auto flags = std::ios::binary | std::ios::out | std::ios::trunc; ofstream ostream(s.words[2].s, flags); ostream.write(configs.data(), static_cast(configs.size())); ostream.close(); if (!ostream.good()) { cout << "Failed to write exported configs to: " << s.words[2].s << endl; } } void exec_syncopendrive(autocomplete::ACState& s) { if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to restore backup syncs." << endl; return; } // sync backup restore drive const auto drivePath = localPathArg(s.words[2].s); client->syncs.backupOpenDrive(drivePath, [](Error e){ conlock(cout) << "syncopendrive result: " << errorstring(e) << endl; }); } void exec_synclist(autocomplete::ACState&) { // Check the user's logged in. if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to list syncs (and backup syncs)." << endl; return; } SyncConfigVector configs = client->syncs.getConfigs(false); if (configs.empty()) { cout << "No syncs configured yet" << endl; return; } for (SyncConfig& config : configs) { // Display name. cout << "Sync " << config.mName << " Id: " << toHandle(config.mBackupId) << "\n"; auto cloudnode = client->nodeByHandle(config.mRemoteNode); string cloudpath = cloudnode ? cloudnode->displaypath() : ""; // Display source/target mapping. cout << " Mapping: " << config.mLocalPath.toPath(false) << " -> " << cloudpath << (!cloudnode || cloudpath != config.mOriginalPathOfRemoteRootNode ? " (originally " + config.mOriginalPathOfRemoteRootNode + ")" : "") << "\n"; string runStateName; switch (config.mRunState) { case SyncRunState::Pending: runStateName = "PENDING"; break; case SyncRunState::Loading: runStateName = "LOADING"; break; case SyncRunState::Run: runStateName = "RUNNING"; break; case SyncRunState::Pause: runStateName = "PAUSED"; break; case SyncRunState::Suspend: runStateName = "SUSPENDED"; break; case SyncRunState::Disable: runStateName = "DISABLED"; break; } // Display status info. cout << " State: " << runStateName << " " << "\n"; // // Display some usage stats. // cout << " Statistics: " // //<< sync->localbytes // //<< " byte(s) across " // << sync->localnodes[FILENODE] // << " file(s) and " // << sync->localnodes[FOLDERNODE] // << " folder(s).\n"; //} //else // Display what status info we can. auto msg = config.syncErrorToStr(); cout << " Enabled: " << config.getEnabled() << "\n" << " Last Error: " << msg << "\n"; // Display sync type. cout << " Type: " << (config.isExternal() ? "EX" : "IN") << "TERNAL " << SyncConfig::synctypename(config.getType()) << "\n"; // Display change detection method. cout << " Change Detection Method: " << changeDetectionMethodToString(config.mChangeDetectionMethod) << "\n"; if (CDM_PERIODIC_SCANNING == config.mChangeDetectionMethod) { // Display scan interval. cout << " Scan Interval (seconds): " << config.mScanIntervalSec << "\n"; } std::promise synchronous; client->syncs.collectSyncNameConflicts(config.mBackupId, [&synchronous](list&& conflicts){ for (auto& c : conflicts) { if (!c.cloudPath.empty() || !c.clashingCloud.empty()) { cout << " Cloud Path conflict at " << c.cloudPath << ": "; for (auto& n : c.clashingCloud) { cout << n.name << " "; } cout << "\n"; } if (!c.localPath.empty() || !c.clashingLocalNames.empty()) { cout << " Local Path conflict at " << c.localPath.toPath(false) << ": "; for (auto& n : c.clashingLocalNames) { cout << n.toPath(false) << " "; } cout << "\n"; } } cout << std::flush; synchronous.set_value(true); }, false); // false so executes on sync thread - we are blocked here on client thread in single-threaded megacli. synchronous.get_future().get(); } SyncStallInfo stall; if (client->syncs.syncStallDetected(stall)) { auto cl = conlock(cout); cout << "Stalled (mutually unresolvable changes detected)!" << endl; for (auto& syncStallInfoMapPair : stall.syncStallInfoMaps) { cout << "=== [SyncID: " << syncStallInfoMapPair.first << "]" << endl; auto& syncStallInfoMap = syncStallInfoMapPair.second; cout << "noProgress: " << syncStallInfoMap.noProgress << ", noProgressCount: " << syncStallInfoMap.noProgressCount << " [HasProgressLack: " << std::string(syncStallInfoMap.hasProgressLack() ? "true" : "false") << "]" << endl; for (auto& p : syncStallInfoMap.cloud) { cout << "stall issue: " << syncWaitReasonDebugString(p.second.reason) << endl; string r1 = p.second.cloudPath1.debugReport(); string r2 = p.second.cloudPath2.debugReport(); string r3 = p.second.localPath1.debugReport(); string r4 = p.second.localPath2.debugReport(); if (!r1.empty()) cout << " MEGA:" << r1 << endl; if (!r2.empty()) cout << " MEGA:" << r2 << endl; if (!r3.empty()) cout << " here:" << r3 << endl; if (!r4.empty()) cout << " here:" << r4 << endl; } for (auto& p : syncStallInfoMap.local) { cout << "stall issue: " << syncWaitReasonDebugString(p.second.reason) << endl; string r1 = p.second.cloudPath1.debugReport(); string r2 = p.second.cloudPath2.debugReport(); string r3 = p.second.localPath1.debugReport(); string r4 = p.second.localPath2.debugReport(); if (!r1.empty()) cout << " MEGA:" << r1 << endl; if (!r2.empty()) cout << " MEGA:" << r2 << endl; if (!r3.empty()) cout << " here:" << r3 << endl; if (!r4.empty()) cout << " here:" << r4 << endl; } } } } void exec_syncremove(autocomplete::ACState& s) { // Are we logged in? if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to manipulate syncs." << endl; return; } string localPath; string remotePath; bool byLocal = s.extractflagparam("-by-local-path", localPath); bool byRemote = s.extractflagparam("-by-remote-path", remotePath); // Get move destination size_t bkpDestPos = (byLocal || byRemote) ? 5 : 4; handle bkpDest = UNDEF; if (s.words.size() > bkpDestPos) { // get final destination std::shared_ptr destination = nodebypath(s.words[bkpDestPos].s.c_str()); if (destination) { bkpDest = destination->nodehandle; } else { cout << "Wrong backup remove destination: " << s.words[bkpDestPos].s << endl; return; } } std::function predicate; bool found = false; bool isBackup = false; if (byLocal) { predicate = [&](SyncConfig& config, Sync*) { auto matched = config.mLocalPath.toPath(false) == localPath; found = found || matched; isBackup |= config.isBackup(); return matched; }; } else if (byRemote) { predicate = [&](SyncConfig& config, Sync*) { auto matched = config.mOriginalPathOfRemoteRootNode == remotePath; found = found || matched; isBackup |= config.isBackup(); return matched; }; } else { predicate = [&](SyncConfig& config, Sync*) { auto id = toHandle(config.mBackupId); auto matched = id == s.words[2].s; found = found || matched; isBackup |= config.isBackup(); return matched; }; } auto v = client->syncs.selectedSyncConfigs(predicate); if (v.size() != 1) { cerr << "Found " << v.size() << " matching syncs." << endl; return; } std::function completion = [=](Error e) { if (e == API_OK) { cout << "Sync - removed" << endl; } else if (e == API_ENOENT) { cout << "Sync - no config exists for " << (byLocal ? localPath : (byRemote ? remotePath : s.words[2].s)); } else { cout << "Sync - Failed to remove (" << error(e) << ": " << errorstring(e) << ')' << endl; } }; if (v[0].isBackup()) { // unlink the backup's Vault nodes after deregistering it NodeHandle source = v[0].mRemoteNode; NodeHandle destination = NodeHandle().set6byte(bkpDest); completion = [completion, source, destination](Error) { client->unlinkOrMoveBackupNodes(source, destination, completion); }; } client->syncs.deregisterThenRemoveSyncById(v[0].mBackupId, std::move(completion)); } void exec_syncstatus(autocomplete::ACState& s) { // Are we logged in? if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to display the status of syncs." << endl; return; } // sync status [id] handle id = UNDEF; // Is the user interested in a particular sync? if (s.words.size() == 3) Base64::atob(s.words[2].s.c_str(), reinterpret_cast(&id), sizeof(id)); // Compute the aggregate transfer speed of the specified syncs. map speeds; for (auto* slot : client->tslots) { // No FA? Not in progress. if (!slot->fa) continue; // Determine the transfer's current speed. auto speed = slot->mTransferSpeed.getCircularMeanSpeed(); // Find out which syncs, if any, are related to this transfer. for (auto* file : slot->transfer->files) { // Not a sync transfer? Not interested! if (!file->syncxfer) continue; // Get our hands on this sync's thread-safe state. auto state = static_cast(file)->syncThreadSafeState; // Is it a sync we're interested in? if (id != UNDEF && id != state->backupId()) continue; // Make sure the speed's never negative. speed = std::max(0, speed); // Add this transfer's speed to the sync's aggregate total. speeds[state->backupId()] += static_cast(speed); } } // Convenience. using SV = vector; std::promise waiter; // Retrieve status information from the engine. client->syncs.getSyncStatusInfo(id, [&](SV info) { waiter.set_value(std::move(info)); }, false); // Wait for the engine to gather our information. auto results = waiter.get_future().get(); // Was anything actually retrieved? if (results.empty()) { // Was the user interested in a specific sync? if (id != UNDEF) { cerr << "Couldn't find an active sync with the ID: " << toHandle(id) << endl; return; } // User was interested in all active syncs. cerr << "There are no active syncs to report on." << endl; return; } // Translate size to a suffixed string. auto toSuffixedString = [](size_t value) { if (value < 1024) return std::to_string(value) + "B"; const char* suffix = "?KMGTPE"; while (value >= 1024) ++suffix, value /= 1024; return std::to_string(value) + *suffix + "B"; }; auto getProgress = [](SyncStatusInfo& i) -> double { auto p = i.mTransferCounts.progress(0); if (p > 1.0) { const std::string errMsg = "exec_syncstatus: Invalid reportCounts progress value"; LOG_err << errMsg; assert(false && errMsg.c_str()); } return p * 100.0; }; // Display status information to the user. for (auto& info: results) { cout << "Sync " << toHandle(info.mBackupID) << ":\n" << " Name: " << info.mName << "\n" << " Total number of synced nodes: " << info.mTotalSyncedNodes << "\n" << " Total size of synced files: " << toSuffixedString(info.mTotalSyncedBytes) << "\n" << " Transfer progress: " << getProgress(info) << "%\n" << " Transfer speed: " << toSuffixedString(speeds[info.mBackupID]) << "/s\n"; } } void exec_syncxable(autocomplete::ACState& s) { // Are we logged in? if (client->loggedin() != FULLACCOUNT) { cerr << "You must be logged in to manipulate syncs." << endl; return; } string errIdString; bool withError = s.extractflagparam("-error", errIdString); auto targetState = SyncRunState::Run; if (s.words[1].s == "run") targetState = SyncRunState::Run; else if (s.words[1].s == "pause") targetState = SyncRunState::Pause; else if (s.words[1].s == "suspend") targetState = SyncRunState::Suspend; else if (s.words[1].s == "disable") targetState = SyncRunState::Disable; handle backupId = 0; Base64::atob(s.words[2].s.c_str(), (byte*) &backupId, sizeof(handle)); SyncConfig config; if (!client->syncs.configById(backupId, config)) { cout << "No sync found with id: " << Base64Str(backupId) << endl; return; } if (config.mRunState == targetState) { cout << "Sync is already in that state" << endl; return; } switch (targetState) { case SyncRunState::Pending: case SyncRunState::Loading: case SyncRunState::Run: { // sync enable id client->syncs.enableSyncByBackupId( backupId, true, [](error err, SyncError, handle) { if (err) { cerr << "Unable to enable sync: " << errorstring(err) << endl; } else { cout << "Sync Running." << endl; } }, true, ""); break; } case SyncRunState::Pause: case SyncRunState::Suspend: case SyncRunState::Disable: { if (targetState == SyncRunState::Pause) { LOG_warn << "[exec_syncxable] Target state: SyncRunState::Pause. Sync will be suspended"; } bool keepSyncDb = targetState == SyncRunState::Pause || targetState == SyncRunState::Suspend; client->syncs.disableSyncByBackupId( backupId, static_cast(withError ? atoi(errIdString.c_str()) : 0), false, keepSyncDb, [targetState](){ cout << (targetState == SyncRunState::Suspend || targetState == SyncRunState::Pause ? "Sync Suspended." : "Sync Disabled.") << endl; }); break; } } } #endif // ENABLE_SYNC std::string setTypeToString(Set::SetType t) { const std::string tStr = std::to_string(t); switch (t) { case Set::TYPE_ALBUM: return "Photo Album (" + tStr + ")"; case Set::TYPE_PLAYLIST: return "Video Playlist (" + tStr + ")"; default: return "Unexpected Set Type with value " + tStr; } } void printSet(const Set* s) { if (!s) { cout << "Set not found" << endl; return; } cout << "Set " << toHandle(s->id()) << endl; cout << "\ttype: " << setTypeToString(s->type()) << endl; cout << "\tpublic id: " << toHandle(s->publicId()) << endl; if (s->getPublicLink() && s->getPublicLink()->isTakenDown()) { cout << "\t\ttake down reason: " << PublicLinkSet::LinkDeletionReasonToString( s->getPublicLink()->getLinkDeletionReason()) << endl; } cout << "\tkey: " << Base64::btoa(s->key()) << endl; cout << "\tuser: " << toHandle(s->user()) << endl; cout << "\tts: " << s->ts() << endl; cout << "\tname: " << s->name() << endl; handle cover = s->cover(); cout << "\tcover: " << (cover == UNDEF ? "(no cover)" : toHandle(cover)) << endl; cout << endl; } void printElements(const elementsmap_t* elems) { if (!elems) { cout << "No elements" << endl; return; } for (const auto& p : *elems) { const SetElement& el = p.second; cout << "\t\telement " << toHandle(el.id()) << endl; cout << "\t\t\tset: " << toHandle(el.set()) << endl; cout << "\t\t\tname: " << el.name() << endl; cout << "\t\t\torder: " << el.order() << endl; cout << "\t\t\tkey: " << (el.key().empty() ? "(no key)" : Base64::btoa(el.key())) << endl; cout << "\t\t\tts: " << el.ts() << endl; cout << "\t\t\tnode: " << toNodeHandle(el.node()) << endl; if (el.nodeMetadata()) { cout << "\t\t\t\tfile name: " << el.nodeMetadata()->filename << endl; cout << "\t\t\t\tfile size: " << el.nodeMetadata()->s << endl; cout << "\t\t\t\tfile attrs: " << el.nodeMetadata()->fa << endl; cout << "\t\t\t\tfingerprint: " << el.nodeMetadata()->fingerprint << endl; cout << "\t\t\t\tts: " << el.nodeMetadata()->ts << endl; cout << "\t\t\t\towner: " << toHandle(el.nodeMetadata()->u) << endl; } } cout << endl; } void exec_setsandelements(autocomplete::ACState& s) { static const set nonLoggedInCmds {"fetchpublicset", "getsetinpreview", "downloadelement", "stoppublicsetpreview" }; const auto command = s.words[1].s; const auto commandRequiresLoggingIn = [&command]() -> bool { return nonLoggedInCmds.find(command) == nonLoggedInCmds.end(); // contains is C++20 }; const auto isClientLoggedIn = []() -> bool { return client->loggedin() == FULLACCOUNT; }; // Are we logged in? if (commandRequiresLoggingIn() && !isClientLoggedIn()) { cerr << "You must be logged in to manipulate Sets. " << "Except for the following commands:\n"; for (const auto& c : nonLoggedInCmds) cerr << "\t" << c << "\n"; return; } if (command == "list") { const auto& sets = client->getSets(); for (auto& set : sets) { printSet(&set.second); printElements(client->getSetElements(set.first)); } } else if (command == "newset") { if (s.words.size() < 3) { cout << "Wrong number of parameters. Try again\n"; return; } const char* name = (s.words.size() == 4) ? s.words[3].s.c_str() : nullptr; Set newset; if (name) { newset.setName(name); } Set::SetType t = static_cast(stoi(s.words[2].s)); newset.setType(t); client->putSet(std::move(newset), [](Error e, const Set* s) { if (e == API_OK && s) { cout << "Created Set with id " << toHandle(s->id()) << endl; printSet(s); } else { cout << "Error creating new Set " << e << endl; } }); } else if (command == "updateset") { handle id = 0; // must have remaining bits set to 0 Base64::atob(s.words[2].s.c_str(), (byte*)&id, MegaClient::SETHANDLE); Set updset; updset.setId(id); string buf; if (s.extractflagparam("-n", buf) || s.extractflag("-n")) { updset.setName(std::move(buf)); } buf.clear(); if (s.extractflagparam("-c", buf) || s.extractflag("-c")) { if (buf.empty()) { updset.setCover(UNDEF); } else { handle hc = 0; Base64::atob(buf.c_str(), (byte*)&hc, MegaClient::SETELEMENTHANDLE); updset.setCover(hc); } } client->putSet(std::move(updset), [id](Error e, const Set*) { if (e == API_OK) { cout << "Updated Set " << toHandle(id) << endl; printSet(client->getSet(id)); printElements(client->getSetElements(id)); } else { cout << "Error updating Set " << toHandle(id) << ' ' << e << endl; } }); } else if (command == "removeset") { handle id = 0; // must have remaining bits set to 0 Base64::atob(s.words[2].s.c_str(), (byte*)&id, MegaClient::SETHANDLE); client->removeSet(id, [id](Error e) { if (e == API_OK) cout << "Removed Set " << toHandle(id) << endl; else cout << "Error removing Set " << toHandle(id) << ' ' << e << endl; }); } else if (command == "getsetinpreview") { if (!client->inPublicSetPreview()) { cout << "Not in Public Set Preview currently\n"; return; } const Set* ps = client->getPreviewSet(); if (ps) { cout << "Fetched Set successfully\n"; printSet(ps); printElements(client->getPreviewSetElements()); } else cout << "Error getting Set from preview: No Set received\n"; } else if (command == "removeelement") { handle sid = 0, eid = 0; // must have remaining bits set to 0 Base64::atob(s.words[2].s.c_str(), (byte*)&sid, MegaClient::SETHANDLE); Base64::atob(s.words[3].s.c_str(), (byte*)&eid, MegaClient::SETELEMENTHANDLE); client->removeSetElement(sid, eid, [sid, eid](Error e) { if (e == API_OK) cout << "Removed Element " << toHandle(eid) << " from Set " << toHandle(sid) << endl; else cout << "Error removing Element " << toHandle(eid) << ' ' << e << endl; }); } else if (command == "export") { handle sid = 0; Base64::atob(s.words[2].s.c_str(), (byte*)&sid, MegaClient::SETHANDLE); string buf; bool isExportSet = !(s.extractflagparam("-disable", buf) || s.extractflag("-disable")); buf.clear(); cout << (isExportSet ? "En" : "Dis") << "abling export for Set " << toHandle(sid) << "\n"; client->exportSet(sid, isExportSet, [sid, isExportSet](Error e) { string msg = (isExportSet ? "en" : "dis") + string("abled"); cout << "\tSet " << toHandle(sid) << " export " << (isExportSet ? "en" : "dis") << "abled " << (e == API_OK ? "" : "un") << "successfully" << (e == API_OK ? "" : ". " + verboseErrorString(e)) << endl; }); } else if (command == "getpubliclink") { handle sid = 0; // must have remaining bits set to 0 Base64::atob(s.words[2].s.c_str(), (byte*)&sid, MegaClient::SETHANDLE); cout << "Requesting public link for Set " << toHandle(sid) << endl; error e; string url; std::tie(e, url) = client->getPublicSetLink(sid); cout << "\tPublic link generated " << (e == API_OK ? "" : "un") << "successfully" << (e == API_OK ? " " + url : ". " + verboseErrorString(e)) << endl; } else if (command == "fetchpublicset") { string publicSetLink = s.words[2].s.c_str(); cout << "Fetching public Set with link " << publicSetLink << endl; client->fetchPublicSet(publicSetLink.c_str(), [](Error e, Set* s, elementsmap_t* elements) { unique_ptr set(s); unique_ptr els(elements); if (e == API_OK) { if (set) cout << "\tPreview mode started for Set " << toHandle(set->id()) << endl; else cout << "\tNull Set returned for started preview mode\n"; printSet(set.get()); printElements(els.get()); } else cout << "\tPreview mode failed: " + verboseErrorString(e) << endl; }); } else if (command == "stoppublicsetpreview") { if (client->inPublicSetPreview()) { cout << "Stopping Public Set preview mode for Set " << toHandle(client->getPreviewSet()->id()) << "\n"; client->stopSetPreview(); cout << "Public Set preview mode stopped " << (client->inPublicSetPreview() ? "un" : "") << "successfully\n"; } else cout << "Not in Public Set Preview mode currently\n"; } else if (command == "downloadelement") { handle sid = 0, eid = 0; Base64::atob(s.words[2].s.c_str(), (byte*)&sid, MegaClient::SETHANDLE); Base64::atob(s.words[3].s.c_str(), (byte*)&eid, MegaClient::SETELEMENTHANDLE); cout << "Requesting to download Element " << toHandle(eid) << " from Set " << toHandle(sid) << endl; cout << "\tSet preview mode " << (client->inPublicSetPreview() ? "en" : "dis") << "abled\n"; const SetElement* element = nullptr; m_off_t fileSize = 0; string fileName; string fingerprint; string fileattrstring; if (client->inPublicSetPreview()) { element = client->getPreviewSetElement(eid); if (element) { cout << "\tElement found in preview Set\n"; if (element->nodeMetadata()) // only present starting with 'aft' v2 { fileSize = element->nodeMetadata()->s; fileName = element->nodeMetadata()->filename; fingerprint = element->nodeMetadata()->fingerprint; fileattrstring = element->nodeMetadata()->fa; } } else if (!isClientLoggedIn()) { cout << "Error: attempting to dowload an element which is not in the previewed " << "Set, and user is not logged in\n"; return; } } if (!element) { element = client->getSetElement(sid, eid); if (element) { cout << "\tElement found in owned Set\n"; std::shared_ptr mn(client->nodebyhandle(element->node())); if (!mn) { cout << "\tElement node not found\n"; return; } fileSize = mn->size; fileName = mn->displayname(); mn->serializefingerprint(&fingerprint); fileattrstring = mn->fileattrstring; } } if (!element) { cout << "\tElement not found as part of provided Set\n"; return; } FileFingerprint ffp; m_time_t tm = 0; if (ffp.unserializefingerprint(&fingerprint)) tm = ffp.mtime; cout << "\tName: " << fileName << ", size: " << fileSize << ", tm: " << tm; if (!fingerprint.empty()) cout << ", fingerprint available"; if (!fileattrstring.empty()) cout << ", has attributes"; cout << endl; cout << "\tInitiating download..." << endl; TransferDbCommitter committer(client->tctable); auto file = std::make_unique(nullptr, NodeHandle().set6byte(element->node()), reinterpret_cast(element->key().c_str()), fileSize, tm, &fileName, &fingerprint); file->hprivate = true; file->hforeign = true; startxfer(committer, std::move(file), fileName, client->nextreqtag()); } else // create or update element { handle setId = 0; // must have remaining bits set to 0 handle node = 0; // must have remaining bits set to 0 handle elemId = 0; // must have remaining bits set to 0 Base64::atob(s.words[2].s.c_str(), (byte*)&setId, MegaClient::SETHANDLE); bool createNew = command == "newelement"; if (createNew) { Base64::atob(s.words[3].s.c_str(), (byte*)&node, MegaClient::NODEHANDLE); elemId = UNDEF; } else // "updateelement" { node = UNDEF; Base64::atob(s.words[3].s.c_str(), (byte*)&elemId, MegaClient::SETELEMENTHANDLE); } SetElement el; el.setSet(setId); el.setId(elemId); el.setNode(node); string param; if (s.extractflagparam("-n", param) || s.extractflag("-n")) { el.setName(std::move(param)); } param.clear(); if (s.extractflagparam("-o", param)) { el.setOrder(atoll(param.c_str())); if (el.order() == 0 && param != "0") { cout << "Invalid order: " << param << endl; return; } } client->putSetElement(std::move(el), [createNew, setId, elemId](Error e, const SetElement* el) { if (createNew) { if (e == API_OK && el) cout << "Created Element " << toHandle(el->id()) << " in Set " << toHandle(setId) << endl; else cout << "Error creating new Element " << e << endl; } else { if (e == API_OK) { cout << "Updated Element " << toHandle(elemId) << " in Set " << toHandle(setId) << endl; } else { cout << "Error updating Element " << toHandle(elemId) << ' ' << e << endl; } } }); } } void exec_reqstat(autocomplete::ACState &s) { bool turnon = s.extractflag("-on"); bool turnoff = s.extractflag("-off"); if (turnon) { client->startRequestStatusMonitor(); } else if (turnoff) { client->stopRequestStatusMonitor(); } cout << "Request status monitor: " << (client->requestStatusMonitorEnabled() ? "on" : "off") << endl; } void DemoApp::reqstat_progress(int permilprogress) { cout << "Progress (per mille) of request: " << permilprogress << endl; } void exec_getABTestValue(autocomplete::ACState &s) { string flag = s.words[1].s; unique_ptr v = client->mABTestFlags.get(flag); string value = v ? std::to_string(*v) : "(not set)"; cout << "[" << flag<< "]:" << value << endl; } void exec_contactVerificationWarning(autocomplete::ACState& s) { bool enable = s.extractflag("-on"); bool disable = s.extractflag("-off"); if (enable) { client->setContactVerificationWarning(true, [](Error e) { if (!e) cout << "Warnings for unverified contacts: Enabled."; }); } else if (disable) { client->setContactVerificationWarning(false, [](Error e) { if (!e) cout << "Warnings for unverified contacts: Disabled."; }); } else { cout << "Warnings for unverified contacts: " << (client->mKeyManager.getContactVerificationWarning() ? "Enabled" : "Disabled") << endl; } } void exec_numberofnodes(autocomplete::ACState&) { uint64_t numberOfNodes = client->mNodeManager.getNodeCount(); // We have to add RootNode, Incoming and rubbish if (!client->loggedIntoFolder()) { numberOfNodes += 3; } cout << "Total nodes: " << numberOfNodes << endl; cout << "Total nodes in RAM: " << client->mNodeManager.getNumberNodesInRam() << endl << endl; cout << "Number of outShares: " << client->mNodeManager.getNodesWithOutShares().size(); } void exec_numberofchildren(autocomplete::ACState &s) { std::shared_ptr n; if (s.words.size() > 1) { n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory" << endl; return; } } else { n = client->nodeByHandle(cwd); } assert(n); size_t folders = client->mNodeManager.getNumberOfChildrenByType(n->nodeHandle(), FOLDERNODE); size_t files = client->mNodeManager.getNumberOfChildrenByType(n->nodeHandle(), FILENODE); cout << "Number of folders: " << folders << endl; cout << "Number of files: " << files << endl; } void exec_searchbyname(autocomplete::ACState &s) { if (s.words.size() >= 2) { bool recursive = !s.extractflag("-norecursive"); bool noSensitive = s.extractflag("-nosensitive"); NodeHandle nodeHandle; if (s.words.size() == 3) { handle h; Base64::atob(s.words[2].s.c_str(), (byte*)&h, MegaClient::NODEHANDLE); nodeHandle.set6byte(h); } if (!recursive && nodeHandle.isUndef()) { cout << "Search no recursive need node handle" << endl; return; } NodeSearchFilter filter; if (nodeHandle.isUndef()) { filter.byAncestors({client->mNodeManager.getRootNodeFiles().as8byte(), UNDEF, UNDEF}); } else { filter.byAncestors({nodeHandle.as8byte(), UNDEF, UNDEF}); } filter.byName(s.words[1].s); filter.bySensitivity(noSensitive ? NodeSearchFilter::BoolFilter::onlyTrue : NodeSearchFilter::BoolFilter::disabled); sharedNode_vector nodes; if (recursive) { nodes = client->mNodeManager.searchNodes(filter, 0 /*Order none*/, CancelToken(), NodeSearchPage{0, 0}); } else { nodes = client->mNodeManager.getChildren(filter, 0 /*Order none*/, CancelToken(), NodeSearchPage{0, 0}); } for (const auto& node : nodes) { cout << "Node: " << node->nodeHandle() << " Name: " << node->displayname() << endl; } } } void exec_manualverif(autocomplete::ACState &s) { if (s.extractflag("-on")) { client->mKeyManager.setManualVerificationFlag(true); } else if (s.extractflag("-off")) { client->mKeyManager.setManualVerificationFlag(false); } } /* MEGA VPN commands */ void exec_getvpnregions(autocomplete::ACState&) { cout << "Getting the list of VPN regions" << endl; client->getVpnRegions([] (const Error& e, std::vector&& vpnRegions) { if (e == API_OK) { cout << "List of VPN regions:" << endl; for (size_t i = 0; i < vpnRegions.size(); i++) { cout << (i + 1) << ". " << vpnRegions[i].getName() << " ---> "; cout << " Country Code: \"" << vpnRegions[i].getCountryCode() << "\","; cout << " Country Name: \"" << vpnRegions[i].getCountryName() << "\""; if (!vpnRegions[i].getRegionName().empty()) { cout << ", Region: \"" << vpnRegions[i].getRegionName() << "\""; } if (!vpnRegions[i].getTownName().empty()) { cout << ", Town: \"" << vpnRegions[i].getTownName() << "\""; } cout << endl; const auto clusters = vpnRegions[i].getClusters(); for (const auto& cluster: clusters) { cout << "\tCluster [" << cluster.first << "] -> " << cluster.second.getHost() << '\n'; cout << "\t\tDNS: "; for (const auto& dns: cluster.second.getDns()) { cout << dns << ", "; } cout << '\n'; cout << "\t\tAd-blocking DNS:"; for (const auto& adBlockingDns: cluster.second.getAdBlockingDns()) { cout << adBlockingDns << ", "; } cout << endl; } } } else { cout << "Getting the MEGA VPN credentials for the user failed. Error value: " << e << ". Reason: '" << errorstring(e) << "'" << endl; } }); } void exec_getvpncredentials(autocomplete::ACState& s) { cout << "Getting the MEGA VPN credentials for the user" << endl; string slotIDstr; int slotID{-1}; if (s.extractflagparam("-s", slotIDstr)) { try { slotID = std::stoi(slotIDstr); } catch (const std::exception& ex) { cout << "Could not convert param SlotID(" << slotIDstr << ") to integer. Exception: " << ex.what() << endl; return; } } bool showVpnRegions = !s.extractflag("-noregions"); client->getVpnCredentials([slotID, showVpnRegions] (const Error& e, CommandGetVpnCredentials::MapSlotIDToCredentialInfo&& mapSlotIDToCredentialInfo, /* Map of SlotID: { ClusterID, IPv4, IPv6, DeviceID } */ CommandGetVpnCredentials::MapClusterPublicKeys&& mapClusterPubKeys, /* Map of ClusterID: Cluster Public Key */ std::vector&& vpnRegions /* VPN Regions */) { if (e == API_OK) { cout << endl; if (slotID > 0) { auto slotInfo = mapSlotIDToCredentialInfo.find(slotID); if (slotInfo != mapSlotIDToCredentialInfo.end()) { cout << "====================================================================" << endl; cout << "SlotID: " << slotInfo->first << endl; auto& credentialInfo = slotInfo->second; cout << "ClusterID: " << credentialInfo.clusterID << endl; cout << "Cluster Public Key: "; auto clusterPublicKey = mapClusterPubKeys.find(credentialInfo.clusterID); if (clusterPublicKey != mapClusterPubKeys.end()) { cout << clusterPublicKey->second << endl; } else { cout << "Not found" << endl; } cout << "IPv4: " << credentialInfo.ipv4 << endl; cout << "IPv6: " << credentialInfo.ipv6 << endl; cout << "DeviceID: " << credentialInfo.deviceID << endl; cout << "====================================================================" << endl; } else { cout << "There are no MEGA VPN credentials on SlotID " << slotID << endl; } } else { if (mapSlotIDToCredentialInfo.empty()) { cout << "List of VPN slots is EMPTY" << endl; } else { cout << "List of VPN slots:\n" << endl; cout << "====================================================================" << endl; for (auto& vpnSlot : mapSlotIDToCredentialInfo) { cout << "SlotID: " << vpnSlot.first << endl; auto& credentialInfo = vpnSlot.second; cout << "ClusterID: " << credentialInfo.clusterID << endl; cout << "IPv4: " << credentialInfo.ipv4 << endl; cout << "IPv6: " << credentialInfo.ipv6 << endl; cout << "DeviceID: " << credentialInfo.deviceID << endl; cout << "====================================================================" << endl; } } cout << endl; if (mapClusterPubKeys.empty()) { cout << "List of Cluster Public Keys is EMPTY" << endl; } else { cout << "List of Cluster Public Keys:\n" << endl; cout << "==========================================================================" << endl; for (auto& clusterPubKey : mapClusterPubKeys) { cout << "ClusterID: " << clusterPubKey.first << ". "; cout << "Public Key: " << clusterPubKey.second << endl; } cout << "==========================================================================" << endl; } } if (showVpnRegions) { if (vpnRegions.empty()) { cout << "List of VPN regions is EMPTY" << endl; } else { cout << "\nList of VPN regions:\n" << endl; cout << "===================================================" << endl; for (size_t i = 0; i < vpnRegions.size(); i++) { cout << (i+1) << ". " << vpnRegions[i].getName() << "." << endl; } cout << "===================================================" << endl; } } } else { cout << "Getting the MEGA VPN credentials for the user failed. Error value: " << e << ". Reason: '"; switch(e) { case API_ENOENT: cout << "The user has no credentials registered"; break; default: cout << errorstring(e); } cout << "'" << endl; } }); } void exec_putvpncredential(autocomplete::ACState& s) { string vpnRegion = s.words[1].s; cout << "Adding new MEGA VPN credentials. VPN region: " << vpnRegion << endl; string filename; if (s.extractflagparam("-file", filename)) { filename.append(".conf"); cout << "Credential data will be saved in: '" << filename << "'" << endl; } bool consoleoutput = !s.extractflag("-noconsole"); client->putVpnCredential(std::move(vpnRegion), [filename, consoleoutput] (const Error& e, int slotID, std::string&& userPubKey, std::string&& newCredential) { if (e == API_OK && (slotID > 0) && !userPubKey.empty() && !newCredential.empty()) { cout << "\nNew MEGA VPN credential added successfully to slot " << slotID << endl; cout << "User Public Key: " << userPubKey << endl; if (consoleoutput || !filename.empty()) { string credentialHeader; credentialHeader.reserve(180); credentialHeader.append("########################################\n") .append("##### MEGA VPN credentials #####\n") .append("##### SlotID ").append(std::to_string(slotID)).append(" #####\n") .append("########################################\n"); if (consoleoutput) { cout << "\n" << credentialHeader << newCredential << endl; } if (!filename.empty()) { if (consoleoutput) { cout << endl; } // Leave a line between credential info and log info below std::ofstream ostream(filename); if (!ostream) { cerr << "Unable to open conf file for writing the new credential: '" << filename << "'" << endl; } else { ostream << credentialHeader << newCredential << endl; if (ostream.flush()) { cout << "VPN credentials saved in: '" << filename << "'" << endl; } else { cerr << "Encountered an error while writing conf file '" << filename << "'" << endl; return; } } } } } else { cout << "Adding new MEGA VPN credentials failed. Error value: " << e << ". Reason: '"; switch(e) { case API_EARGS: cout << "Peer Public Key does not have the correct format/length"; break; case API_EACCESS: cout << "Either the user is not a PRO user, the user is not logged in, or the peer Public Key is already taken"; break; case API_ETOOMANY: cout << "User has too many registered credentials"; break; default: cout << errorstring(e); } cout << "'" << endl; } }); } void exec_delvpncredential(autocomplete::ACState& s) { int slotID = stoi(s.words[1].s); cout << "Deleting the MEGA VPN credential on SlotID: " << slotID << endl; client->delVpnCredential(slotID, [slotID] (const Error& e) { cout << "MEGA VPN credential on slotID " << slotID << " "; if (e == API_OK) { cout << "has been removed OK"; } else { cout << "has not been removed. Error value: " << e << ". Reason: '"; switch(e) { case API_EARGS: cout << "SlotID is not valid"; break; case API_ENOENT: cout << "Slot was not occupied"; break; default: cout << errorstring(e); } cout << "'"; } cout << endl; }); } void exec_checkvpncredential(autocomplete::ACState& s) { string userPubKey = s.words[1].s; cout << "Checking MEGA VPN credentials. User Public Key: " << userPubKey << endl; client->checkVpnCredential(userPubKey.c_str(), // To ensure a copy [userPubKey] (const Error& e) { cout << "MEGA VPN credentials with User Public Key: '" << userPubKey << "' "; if (e == API_OK) { cout << "are valid"; } else if (e == API_EACCESS) { cout << "are not valid"; } else { cout << "could not be checked. Error value: " << e << ". Reason: '" << errorstring(e) << "'"; } cout << endl; }); } void exec_getnetworktestserver(autocomplete::ACState&) { client->getNetworkConnectivityTestServerInfo( [](const Error& e, NetworkConnectivityTestServerInfo&& info) { if (e == API_OK) { cout << "Network connectivity test server info: \n"; cout << "\tIPv4: " << info.ipv4 << "\n"; cout << "\tIPv6: " << info.ipv6 << "\n"; cout << "\tPorts: "; for (const auto port: info.ports) { cout << port << " "; } cout << endl; } else { cout << "Error requesting network connectivity test server info: " << errorstring(e) << " (" << e << ")" << endl; } }); } static string NetworkConnectivityTestStatusToString(NetworkConnectivityTestMessageStatus status) { switch (status) { case NetworkConnectivityTestMessageStatus::PASS: return "Pass"; case NetworkConnectivityTestMessageStatus::FAIL: return "Fail"; case NetworkConnectivityTestMessageStatus::NET_UNREACHABLE: return "Network unreachable"; default: return "Not run / Unknown"; } } void exec_networktest(autocomplete::ACState&) { client->runNetworkConnectivityTest( [](const Error& e, const NetworkConnectivityTestResults& results) { if (e == API_OK) { cout << "Network connectivity test:\n" << "\tIPv4: " << NetworkConnectivityTestStatusToString(results.ipv4.messages) << '\n' << "\tIPv4 DNS: " << NetworkConnectivityTestStatusToString(results.ipv4.dns) << '\n' << "\tIPv4 summary: " << results.ipv4.summary << '\n' << "\tIPv6: " << NetworkConnectivityTestStatusToString(results.ipv6.messages) << '\n' << "\tIPv6 DNS: " << NetworkConnectivityTestStatusToString(results.ipv6.dns) << '\n' << "\tIPv6 summary: " << results.ipv6.summary << endl; } else { cout << "Error running network connectivity test: " << errorstring(e) << " (" << e << ")" << endl; } }); } /* MEGA VPN commands */ void exec_fetchcreditcardinfo(autocomplete::ACState&) { client->fetchCreditCardInfo([](const Error& e, const std::map& creditCardInfo) { if (e == API_OK) { cout << "Credit card info: " << endl; for (const auto& it: creditCardInfo) { cout << " " << it.first << ": " << it.second << endl; } } else { cout << "Error requesting credit card info: " << e << endl; } }); } static void importpasswordsfromgooglefile(const LocalPath& sourceFile, const NodeHandle& parentHandle, const pwm::import::FileSource source) { if (parentHandle.isUndef()) { std::cout << "Parent handle is undef\n"; return; } using namespace pwm::import; const auto [err, badEntries, nGoodEntries] = client->importPasswordsFromFile(sourceFile.platformEncoded(), source, parentHandle, 0); if (err != API_OK) std::cout << "Error importing file. Code " << err << "\n"; else std::cout << "Successfully imported " << nGoodEntries << " entries\n"; std::for_each(begin(badEntries), end(badEntries), [](const auto& badEntry) { std::cout << "Error (" << toString(badEntry.second) << " ) importing line: " << badEntry.first; }); } void exec_passwordmanager(autocomplete::ACState& s) { static const set nonLoggedInCmds {}; const auto command = s.words[1].s; const auto commandRequiresLoggingIn = [&command]() -> bool { return nonLoggedInCmds.find(command) == nonLoggedInCmds.end(); }; const auto isClientLoggedIn = []() -> bool { return client->loggedin() == FULLACCOUNT; }; // Are we logged in? if (commandRequiresLoggingIn() && !isClientLoggedIn()) { cerr << "You must be logged in to manipulate Password items. " << (nonLoggedInCmds.empty() ? "" : "Except for the following commands:") << "\n"; for (const auto& c : nonLoggedInCmds) cerr << "\t" << c << "\n"; return; } const auto moreParamsThan = [&s](size_t min) -> bool { if (s.words.size() <= min) { cout << "Wrong parameters\n"; return false; } return true; }; const auto getNodeHandleFromParam = [&s](size_t paramPos) -> NodeHandle { handle nh; Base64::atob(s.words[paramPos].s.c_str(), (byte*)&nh, MegaClient::NODEHANDLE); return NodeHandle{}.set6byte(nh); }; const auto createPwdTotpData = [](std::optional&& shse, std::optional&& expt, std::optional&& hashAlgo, std::optional&& ndigits) -> std::optional { if (!shse && !expt && !hashAlgo && !ndigits) return std::nullopt; AttrMap pwdData; const auto addData = [&pwdData](std::optional&& data, const std::string_view key) { if (data) pwdData.map[AttrMap::string2nameid(key)] = std::move(*data); }; addData(std::move(shse), MegaClient::PWM_ATTR_PASSWORD_TOTP_SHSE); addData(std::move(expt), MegaClient::PWM_ATTR_PASSWORD_TOTP_EXPT); addData(std::move(hashAlgo), MegaClient::PWM_ATTR_PASSWORD_TOTP_HASH_ALG); addData(std::move(ndigits), MegaClient::PWM_ATTR_PASSWORD_TOTP_NDIGITS); return pwdData; }; static constexpr std::string_view DELETE_STR{"EMPTY"}; const auto getDataSetter = [](AttrMap& toWriteMap) { return [&toWriteMap](std::string&& data, const std::string_view key) { if (data.empty()) return; // patch to allow setting to null taking into account that extractflag doesn't accept "" if (data == DELETE_STR) data.clear(); toWriteMap.map[AttrMap::string2nameid(key)] = std::move(data); }; }; const auto createPwdData = [&getDataSetter](std::string&& pwd, std::string&& url, std::string&& userName, std::string&& notes, std::string&& totpJson) { auto pwdData = std::make_unique(); const auto addData = getDataSetter(*pwdData); addData(std::move(pwd), MegaClient::PWM_ATTR_PASSWORD_PWD); addData(std::move(url), MegaClient::PWM_ATTR_PASSWORD_URL); addData(std::move(userName), MegaClient::PWM_ATTR_PASSWORD_USERNAME); addData(std::move(notes), MegaClient::PWM_ATTR_PASSWORD_NOTES); addData(std::move(totpJson), MegaClient::PWM_ATTR_PASSWORD_TOTP); return pwdData; }; const auto createCcData = [&getDataSetter](std::string&& number, std::string&& notes, std::string&& holder, std::string&& cvv, std::string&& expirationDate) { auto ccData = std::make_unique(); const auto addData = getDataSetter(*ccData); addData(std::string{MegaClient::PWM_ATTR_NODE_TYPE_CREDIT_CARD}, MegaClient::PWM_ATTR_NODE_TYPE); addData(std::move(number), MegaClient::PWM_ATTR_CREDIT_CARD_NUMBER); addData(std::move(notes), MegaClient::PWM_ATTR_CREDIT_NOTES); addData(std::move(holder), MegaClient::PWM_ATTR_CREDIT_CARD_HOLDER); addData(std::move(cvv), MegaClient::PWM_ATTR_CREDIT_CVV); addData(std::move(expirationDate), MegaClient::PWM_ATTR_CREDIT_EXP_DATE); return ccData; }; const auto doesNodeHaveTotpData = [](const Node& n) -> bool { if (!n.isPasswordNode()) return false; const auto pwdData = n.attrs.getNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER); assert(pwdData.has_value()); return pwdData->getStringView(MegaClient::PWM_ATTR_PASSWORD_TOTP).has_value(); }; const auto printAttr = [](const std::string_view attr, const AttrMap& data, const unsigned nest = 1) -> void { for (unsigned i = 0; i < nest; ++i) std::cout << "\t"; std::cout << attr << ": " << data.getStringView(attr).value_or("") << "\n"; }; const auto printEntryDetails = [&printAttr](NodeHandle nh) { auto pwdNode = client->nodeByHandle(nh); assert(pwdNode); assert(pwdNode->isPasswordManagerNode()); auto entryData = pwdNode->attrs.getNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER); assert(entryData.has_value()); if (pwdNode->isPasswordNode()) { [[maybe_unused]] const auto nRemoved = entryData->map.erase(AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP)); assert(nRemoved <= 1); } cout << "PWM data for entry " << pwdNode->displayname() << " (" << toNodeHandle(nh) << ")"; if (pwdNode->isPasswordNode()) { cout << " of type password node:\n"; printAttr(MegaClient::PWM_ATTR_PASSWORD_PWD, *entryData); printAttr(MegaClient::PWM_ATTR_PASSWORD_USERNAME, *entryData); printAttr(MegaClient::PWM_ATTR_PASSWORD_URL, *entryData); printAttr(MegaClient::PWM_ATTR_PASSWORD_NOTES, *entryData); const auto totpData = pwdNode->attrs.getComplexNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER, MegaClient::PWM_ATTR_PASSWORD_TOTP); std::cout << "\t" << MegaClient::PWM_ATTR_PASSWORD_TOTP << ": " << (totpData ? "" : "null") << "\n"; if (totpData) { AttrMap totpMap; totpMap.fromjsonObject( totpData->map.at(AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP)) .c_str()); printAttr(MegaClient::PWM_ATTR_PASSWORD_TOTP_SHSE, totpMap, 2); printAttr(MegaClient::PWM_ATTR_PASSWORD_TOTP_NDIGITS, totpMap, 2); printAttr(MegaClient::PWM_ATTR_PASSWORD_TOTP_EXPT, totpMap, 2); printAttr(MegaClient::PWM_ATTR_PASSWORD_TOTP_HASH_ALG, totpMap, 2); } } else if (pwdNode->isCreditCardNode()) { cout << " of type credit card node:\n"; printAttr(MegaClient::PWM_ATTR_CREDIT_CARD_NUMBER, *entryData); printAttr(MegaClient::PWM_ATTR_CREDIT_NOTES, *entryData); printAttr(MegaClient::PWM_ATTR_CREDIT_CARD_HOLDER, *entryData); printAttr(MegaClient::PWM_ATTR_CREDIT_CVV, *entryData); printAttr(MegaClient::PWM_ATTR_CREDIT_EXP_DATE, *entryData); } else { assert(false); std::cout << "Error in printEntryDetails: Unknown node sub-type"; } }; auto updateEntryCallback = [printEntryDetails](NodeHandle nh, Error e) { if (e == API_OK) printEntryDetails(nh); else std::cout << "Error: " << errorstring(e) << "\n"; }; const auto validateAndSetDefaultsForTotpInit = [](const std::optional& shse, std::optional& expt, std::optional& algo, std::optional& ndig) -> bool { if (!shse && (expt || algo || ndig)) { std::cout << "Invalid input: totp parameters were provided but the shared secret " "(mandatory for initializing) was missing\n"; return false; } expt = expt | or_else( [] { return std::optional{std::to_string(totp::DEF_EXP_TIME.count())}; }); algo = algo | or_else( [] { return std::optional{ std::string{totp::hashAlgorithmToStrView(totp::DEF_ALG)}}; }); ndig = ndig | or_else( [] { return std::optional{std::to_string(totp::DEF_NDIGITS)}; }); return true; }; if (command == "list") { auto n = client->nodeByHandle(client->getPasswordManagerBase()); if (n) { dumptree(n.get(), true, 1, nullptr, nullptr); } } else if (command == "getbase") { cout << "Password Base handle is " << toNodeHandle(client->getPasswordManagerBase()) << "\n"; } else if (command == "createbase") { const UserAttribute* attribute = client->ownuser()->getAttribute(ATTR_PWM_BASE); if (attribute && attribute->isValid()) { assert(attribute->value().size() == MegaClient::NODEHANDLE); std::cout << "Password Manager Base already exists " << toNodeHandle(&attribute->value()) << ". Skipping creation\n"; return; } const auto cb = [](Error e, std::unique_ptr nn) { if (e == API_OK) { assert(nn); auto nh = nn->nodeHandle(); // forced getUA because ATTR_PWM_BASE is not deletable / updatable client->getua(client->ownuser(), ATTR_PWM_BASE, -1, nullptr, [nh](byte*, unsigned, attr_t) { std::cout << "Password Manager Base created with handle " << toNodeHandle(nh) << "\n"; }); } else { std::cout << "Error " << errorstring(e) << " during the creation of Password Manager Base\n"; } }; client->createPasswordManagerBase(-1, cb); } else if (command == "removebase") // only doable in dev / debug conditions { #ifdef NDEBUG std::cout << "This command is only available in debug conditions for dev puporses\nn"; #else const auto nhBase = client->getPasswordManagerBase(); const auto mnBase = client->nodeByHandle(nhBase); client->senddevcommand("pwmhd", client->ownuser()->email.c_str()); // forced erasing the user attribute and base folder node from Vault client->ownuser()->removeAttribute(ATTR_PWM_BASE); if (!mnBase) return; // just in case there was a previous state where the node was deleted const bool keepVersions = false; const int tag = -1; const bool canChangeVault = true; auto cb = [nhBase](NodeHandle nh, Error e) { assert(nh == nhBase); const auto msg = "Password Manager Base " + toNodeHandle(nhBase); if (e == API_OK) { std::cout << msg << " and descendants erased\n"; } else { std::cout << "Error " << errorstring(e) << " erasing " << msg << "\n"; } }; client->unlink(mnBase.get(), keepVersions, tag, canChangeVault, std::move(cb)); #endif } else if (command == "newfolder") { if (!moreParamsThan(3)) return; auto ph = getNodeHandleFromParam(2); auto name = s.words[3].s.c_str(); auto n = client->nodeByHandle(ph); if (!n) { cout << "Parent node with handle " << toNodeHandle(ph) << " not found\n"; return; } client->createFolder(n, name, 0); } else if (command == "renamefolder" || command == "renameentry") { if (!moreParamsThan(3)) return; auto nh = getNodeHandleFromParam(2); auto newName = s.words[3].s.c_str(); CommandSetAttr::Completion cb = [](NodeHandle nh, Error e) { if (e == API_OK) cout << "Node " << toNodeHandle(nh) << " renamed successfully\n"; else cout << "Error renaming the node." << errorstring(e) << "\n"; }; client->renameNode(nh, newName, std::move(cb)); } else if (command == "removefolder" || command == "removeentry") { if (!moreParamsThan(2)) return; auto nh = getNodeHandleFromParam(2); client->removeNode(nh, false, 0); } else if (command == "newpassentry") { if (!moreParamsThan(4)) return; auto ph = getNodeHandleFromParam(2); auto nParent = client->nodeByHandle(ph); if (!nParent) { cout << "Wrong parent handle provided " << toNodeHandle(ph) << "\n"; return; } auto name = s.words[3].s.c_str(); auto pwd = s.words[4].s.c_str(); assert(*name && *pwd); auto totpShse = s.extractflagparam("-totp-shse"); auto totpExpt = s.extractflagparam("-totp-expt"); auto totpAlg = s.extractflagparam("-totp-alg"); auto totpNdig = s.extractflagparam("-totp-nd"); if (!validateAndSetDefaultsForTotpInit(totpShse, totpExpt, totpAlg, totpNdig)) return; const auto totpDataJson = totpShse | and_then( [&createPwdTotpData, expt = std::move(totpExpt), alg = std::move(totpAlg), ndig = std::move(totpNdig)](const std::string& shse) mutable { return createPwdTotpData(shse, std::move(expt), std::move(alg), std::move(ndig)); }) | transform((static_cast(&AttrMap::getJsonObject))); auto pwdData = createPwdData(std::string{pwd}, s.extractflagparam("-url").value_or(""), s.extractflagparam("-u").value_or(""), s.extractflagparam("-n").value_or(""), totpDataJson.value_or("")); if (const auto errCode = client->createPasswordEntry(name, std::move(pwdData), MegaClient::validateNewPasswordNodeData, nParent, 0); errCode != API_OK) std::cout << "Error before sending the putnodes. Code: " << errorstring(errCode) << "\n"; } else if (command == "newcreditcardentry") { if (!moreParamsThan(4)) return; auto ph = getNodeHandleFromParam(2); auto nParent = client->nodeByHandle(ph); if (!nParent) { cout << "Wrong parent handle provided " << toNodeHandle(ph) << "\n"; return; } auto name = s.words[3].s.c_str(); auto cardnumber = s.words[4].s.c_str(); assert(*name && *cardnumber); auto ccData = createCcData(std::string{cardnumber}, s.extractflagparam("-n").value_or(""), s.extractflagparam("-u").value_or(""), s.extractflagparam("-cvv").value_or(""), s.extractflagparam("-exp").value_or("")); if (const auto errCode = client->createPasswordEntry(name, std::move(ccData), MegaClient::validateNewCreditCardNodeData, nParent, 0); errCode != API_OK) std::cout << "Error before sending the putnodes. Code: " << errorstring(errCode) << "\n"; } else if (command == "newpassentries") { if (s.words.size() <= 3) { cout << "Nothing to do\n"; return; } auto ph = getNodeHandleFromParam(2); auto nParent = client->nodeByHandle(ph); if (!nParent) { cout << "Wrong parent handle provided " << toNodeHandle(ph) << "\n"; return; } size_t currentReadIndex = 3; const size_t nWords = s.words.size(); std::map> info; while (currentReadIndex < nWords) { auto name = s.words[currentReadIndex++].s.c_str(); auto userName = s.words[currentReadIndex++].s.c_str(); auto pwd = s.words[currentReadIndex++].s.c_str(); assert(*name && *userName && *pwd); auto pwdData = createPwdData(std::string{pwd}, "", std::string{userName}, "", ""); info[std::move(name)] = std::move(pwdData); } if (const auto errCode = client->createPasswordEntries(std::move(info), MegaClient::validateNewPasswordNodeData, nParent, 0); errCode != API_OK) std::cout << "Error before sending the putnodes. Code: " << errorstring(errCode) << "\n"; } else if (command == "getentrydata") { if (!moreParamsThan(2)) return; auto nh = getNodeHandleFromParam(2); auto pwdNode = client->nodeByHandle(nh); if (!pwdNode) { cout << "No node found with provided handle " << toNodeHandle(nh) << "\n"; return; } if (!pwdNode->isPasswordManagerNode()) { cout << "Node handle provided " << toNodeHandle(nh) << " isn't a Password Manager Node's\n"; return; } printEntryDetails(nh); } else if (command == "updatepassentry") { if (!moreParamsThan(3)) return; auto nh = getNodeHandleFromParam(2); auto n = client->nodeByHandle(nh); if (!(n && n->isPasswordNode())) { cout << "Wrong Password node handle provided " << toNodeHandle(nh) << "\n"; } const bool removeTotp = s.extractflag("-totp-remove"); // If removeTotp is true, all this will be empty (guaranteed by the input parser) auto totpShse = s.extractflagparam("-totp-shse"); auto totpExpt = s.extractflagparam("-totp-expt"); auto totpAlg = s.extractflagparam("-totp-alg"); auto totpNdig = s.extractflagparam("-totp-nd"); if (!doesNodeHaveTotpData(*n) && !validateAndSetDefaultsForTotpInit(totpShse, totpExpt, totpAlg, totpNdig)) return; const auto totpData = createPwdTotpData(std::move(totpShse), std::move(totpExpt), std::move(totpAlg), std::move(totpNdig)) | transform((static_cast(&AttrMap::getJsonObject))); auto pwdData = createPwdData(s.extractflagparam("-p").value_or(""), s.extractflagparam("-url").value_or(""), s.extractflagparam("-u").value_or(""), s.extractflagparam("-n").value_or(""), totpData.value_or(removeTotp ? std::string{DELETE_STR} : "")); if (const auto errCode = client->updatePasswordNode(nh, std::move(pwdData), std::move(updateEntryCallback)); errCode != API_OK) std::cout << "Error before sending the setattr command. Code: " << errorstring(errCode) << "\n"; } else if (command == "updatecreditcardentry") { if (!moreParamsThan(3)) return; auto nh = getNodeHandleFromParam(2); auto n = client->nodeByHandle(nh); if (!(n && n->isPasswordNode())) { cout << "Wrong Password node handle provided " << toNodeHandle(nh) << "\n"; } auto ccData = createCcData(s.extractflagparam("-nu").value_or(""), s.extractflagparam("-n").value_or(""), s.extractflagparam("-u").value_or(""), s.extractflagparam("-cvv").value_or(""), s.extractflagparam("-exp").value_or("")); if (const auto errCode = client->updateCreditCardNode(nh, std::move(ccData), std::move(updateEntryCallback)); errCode != API_OK) std::cout << "Error before sending the setattr command. Code: " << errorstring(errCode) << "\n"; } else if (command == "import") { // Example: (All mandatory) // pwdman import -source google ./test_google.csv 12abcd std::string sourceOriginStr; s.extractflagparam("-source", sourceOriginStr); pwm::import::FileSource sourceOrigin; if (sourceOriginStr == "google") sourceOrigin = pwm::import::FileSource::GOOGLE_PASSWORD; else { assert(false && "Not all sources listed in the command signature are considered in the " "implementation"); return; } const auto sourceFile = localPathArg(s.words[2].s); const auto parentHandle = getNodeHandleFromParam(3); importpasswordsfromgooglefile(sourceFile, parentHandle, sourceOrigin); } else if (command == "generatetotptoken") { if (!moreParamsThan(2)) return; const auto nh = getNodeHandleFromParam(2); const auto [err, tokenResult] = client->generateTotpTokenFromNode(nh.as8byte()); if (err == API_OK) { conlock(cout) << "Totp token generated.\n\t* Token: " << tokenResult.first << "\n\t* Lifetime: " << tokenResult.second << "(secs)\n"; return; } cout << command << ". Error generating Totp token: Error(" << err << ")\n"; } else { cout << command << " not recognized. Ignoring it\n"; } if (!client->isClientType(MegaClient::ClientType::PASSWORD_MANAGER)) { std::cout << "\n*****\n" << "* Password Manager commands executed in a non-Password Manager MegaClient type.\n" << "* Be wary of implications regarding fetch nodes and action packets received.\n" << "* Check megacli help to start it as a Password Manager MegaClient type.\n" << "*****\n\n"; } } void exec_generatepassword(autocomplete::ACState& s) { const auto command = s.words[1].s; if (command == "chars") { if (s.words.size() < 3) { cout << "Wrong parameters"; return; } const auto length = static_cast(std::stoul(s.words[2].s)); const bool useUpper = s.extractflag("-useUpper"); const bool useDigits = s.extractflag("-useDigits"); const bool useSymb = s.extractflag("-useSymbols"); auto pwd = MegaClient::generatePasswordChars(useUpper, useDigits, useSymb, length); if (pwd.empty()) cout << "Error generating the password. Please check the logs (if active)\n"; else cout << "Characers-based password successfully generated: " << pwd << "\n"; } } void exec_nodedescription(autocomplete::ACState& s) { std::shared_ptr n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory" << endl; return; } const bool removeDescription = s.extractflag("-remove"); const bool setDescription = s.extractflag("-set"); const auto descNameId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_DESCRIPTION); auto modifyDescription = [descNameId](const std::string& description, std::shared_ptr n) { AttrMap attrMap; attrMap.map[descNameId] = description; client->setattr( n, std::move(attrMap.map), [](NodeHandle h, Error e) { if (e == API_OK) cout << "Description modified correctly" << endl; else cout << "Error modifying description: " << e << " Node: " << h << endl; }, false); }; if (removeDescription) { modifyDescription("", n); } else if (setDescription) { modifyDescription(s.words[2].s, n); } else if (auto it = n->attrs.map.find(descNameId); it != n->attrs.map.end()) { cout << "Description: " << it->second << endl; } else { cout << "Description not set\n"; } } void exec_nodelabel(autocomplete::ACState& s) { std::shared_ptr n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory" << endl; return; } const bool removeLabel = s.extractflag("-remove"); const bool setLabel = s.extractflag("-set"); const auto labelNameId = AttrMap::string2nameid(MegaClient::NODE_ATTR_LABEL); auto modifyLabel = [labelNameId](const std::string& label, std::shared_ptr n) { AttrMap attrMap; attrMap.map[labelNameId] = label; error e = client->setattr( n, std::move(attrMap.map), [](NodeHandle h, Error e) { if (e == API_OK) cout << "Label modified correctly" << endl; else cout << "Error modifying label: " << e << " Node: " << h << endl; }, false); if (e != API_OK) { cout << "Error modifying label: " << e << endl; } }; if (removeLabel) { modifyLabel("", n); } else if (setLabel) { modifyLabel(s.words[2].s, n); } else if (auto it = n->attrs.map.find(labelNameId); it != n->attrs.map.end()) { cout << "Label: " << it->second << endl; } else { cout << "Label not set\n"; } } void exec_nodesensitive(autocomplete::ACState& s) { std::shared_ptr n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory" << endl; return; } const bool removeSensitive = s.extractflag("-remove"); const auto attrId = AttrMap::string2nameid(MegaClient::NODE_ATTR_SEN); AttrMap attrMap; if (removeSensitive) attrMap.map[attrId] = ""; else attrMap.map[attrId] = "1"; client->setattr( n, std::move(attrMap.map), [removeSensitive](NodeHandle h, Error e) { if (e == API_OK) cout << "Node marked as " << (removeSensitive ? "no" : "") << " sensitive" << endl; else cout << "Error setting sensitivity: " << e << " Node: " << h << endl; }, false); } void exec_nodeTag(autocomplete::ACState& s) { std::shared_ptr n = nodebypath(s.words[1].s.c_str()); if (!n) { cout << s.words[1].s << ": No such file or directory\n"; return; } const bool removeTag = s.extractflag("-remove"); const bool addTag = s.extractflag("-add"); const bool updateTag = s.extractflag("-update"); const auto tagNameId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); if (removeTag) { client->removeTagFromNode(n, s.words[2].s, [](NodeHandle, Error e) { if (e == API_OK) cout << "Tag removed correctly\n"; }); } else if (addTag) { client->addTagToNode(n, s.words[2].s, [](NodeHandle, Error e) { if (e == API_OK) cout << "Tag added correctly\n"; }); } else if (updateTag) { client->updateTagNode(n, s.words[2].s, s.words[3].s, [](NodeHandle, Error e) { if (e == API_OK) cout << "Tag updated correctly\n"; }); } else if (auto it = n->attrs.map.find(tagNameId); it != n->attrs.map.end()) { cout << "Tags: " << it->second << endl; } else { cout << "None tag is defined\n"; } } void exec_getpricing(autocomplete::ACState& s) { cout << "Getting pricing plans"; std::optional countryCode{s.extractflagparam("-country")}; if (countryCode) { cout << " localized for " << *countryCode; } cout << "..." << endl; client->purchase_enumeratequotaitems(countryCode); } void exec_collectAndPrintTransferStats(autocomplete::ACState& state) { bool uploadsOnly = state.extractflag("-uploads"); bool downloadsOnly = state.extractflag("-downloads"); assert(!(uploadsOnly && downloadsOnly)); auto collectAndPrintTransfersMetricsFromType = [](direction_t transferType) { std::cout << "\n===================================================================\n"; std::cout << (transferType == PUT ? "[UploadStatistics]" : "[DownloadStatistics]") << "\n"; std::cout << "Number of transfers: " << client->mTransferStatsManager.size(transferType) << "\n"; std::cout << "Max entries: " << client->mTransferStatsManager.getMaxEntries(transferType) << "\n"; std::cout << "Max age in seconds: " << client->mTransferStatsManager.getMaxAgeSeconds(transferType) << "\n"; std::cout << "-------------------------------------------------------------------\n"; ::mega::stats::TransferStats::Metrics metrics = client->mTransferStatsManager.collectAndPrintMetrics(transferType); std::cout << metrics.toString() << "\n"; std::cout << "-------------------------------------------------------------------\n"; std::cout << "JSON format:\n"; std::cout << metrics.toJson() << "\n"; std::cout << "===================================================================\n\n"; }; if (!downloadsOnly) { collectAndPrintTransfersMetricsFromType(PUT); } if (!uploadsOnly) { collectAndPrintTransfersMetricsFromType(GET); } } void exec_hashcash(autocomplete::ACState& s) { const static string originalUserAgent = client->useragent; const static string hashcashUserAgent = "HashcashDemo"; if (s.words.size() == 1) { cout << "Hashcash demo is " << ((client->useragent == hashcashUserAgent) ? "enabled" : "disabled") << endl; return; } if (s.extractflag("-on")) { g_APIURL_default = "https://staging.api.mega.co.nz/"; client->useragent = hashcashUserAgent; } else if (s.extractflag("-off")) { g_APIURL_default = "https://g.api.mega.co.nz/"; client->useragent = originalUserAgent; } client->httpio->APIURL = g_APIURL_default; string tempUserAgent = client->useragent; client->httpio->setuseragent(&tempUserAgent); client->disconnect(); } void exec_getmyip(autocomplete::ACState&) { client->getMyIp( [](const Error& e, string&& countryCode, string&& ipAddress) { if (e) { cout << "Error requesting IP address: " << e << endl; return; } cout << "Country Code: " << countryCode << endl; cout << "IP address: " << ipAddress << endl; }); } void exec_dnsservers(autocomplete::ACState& s) { CurlHttpIO* curlIO = dynamic_cast(client->httpio); if (!curlIO) { cout << "Functionality only available for CurlHttpIO." << endl; return; } string serverList; if (!s.extractflag("-clear")) { serverList = s.words[1].s; } if (curlIO->setdnsservers(serverList.c_str())) { cout << "DNS list "; if (!serverList.empty()) { cout << "set: " << serverList << endl; } else { cout << "cleared. Using the system DNS." << endl; } } else { cout << "libcurl does not have support for a DNS resolver backend. Build libcurl with " "c-ares support to use this functionality." << endl; } } void exec_cleanVault(autocomplete::ACState&) { std::map nodeHandles; auto cleanVault = [&nodeHandles]() { // If this method is called and both attributes aren't set, we return without doing nothing // In standard situation both vaules are received at ug response // If one of attributes needs to be requested to server we have to wait until is received to // proceed with clean vault auto itBackup = nodeHandles.find(ATTR_MY_BACKUPS_FOLDER); auto itPwmBase = nodeHandles.find(ATTR_PWM_BASE); if (itBackup == nodeHandles.end() || itPwmBase == nodeHandles.end()) { return; } auto vault = client->nodeByHandle(client->mNodeManager.getRootNodeVault()); if (!vault.get()) { return; } auto vaultChildren = client->getChildren(vault.get()); for (auto const& child: vaultChildren) { if (child->nodeHandle() != itBackup->second && child->nodeHandle() != itPwmBase->second) { client->unlink(child.get(), false, 0, true); } } }; // Vault is only clean if both attributes are set auto getUserAttributeAndCleanVault = [&nodeHandles, cleanVault](attr_t attribute) { client->getua( client->ownuser(), attribute, 0, [&nodeHandles, attribute, cleanVault](error e) { if (e == API_ENOENT) { nodeHandles[attribute] = NodeHandle(); } cleanVault(); }, [&nodeHandles, cleanVault](byte* buffer, unsigned, attr_t attr) { handle h; memcpy(&h, buffer, MegaClient::NODEHANDLE); nodeHandles[attr].set6byte(h); cleanVault(); }, nullptr); }; getUserAttributeAndCleanVault(ATTR_PWM_BASE); getUserAttributeAndCleanVault(ATTR_MY_BACKUPS_FOLDER); } sdk-10.11.0/examples/megacli/megacli.h000066400000000000000000000430411516266226600174420ustar00rootroot00000000000000/** * @file megaclient.cpp * @brief sample application, interactive GNU Readline CLI * * (c) 2013 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" using namespace mega; extern MegaClient* client; extern MegaClient* clientFolder; extern void megacli(); extern void term_init(); extern void term_restore(); extern void term_echo(int); extern void read_pw_char(char*, int, int*, char**); typedef list appfile_list; struct AppFile : public File { // app-internal sequence number for queue management int seqno; void progress(); appfile_list::iterator appxfer_it; AppFile(); }; // application-managed GET and PUT queues (only pending and active files) extern appfile_list appxferq[2]; struct AppFileGet : public AppFile { void start() override; void update(); void completed(Transfer*, putsource_t source) override; void terminated(error e) override; std::function onCompleted; bool noRetries = false; bool failed(error e, MegaClient* c) override { if (noRetries) return false; return File::failed(e, c); } AppFileGet(Node*, NodeHandle = NodeHandle(), const byte* = NULL, m_off_t = -1, m_time_t = 0, const string* = NULL, const string* = NULL, const string& targetfolder = ""); ~AppFileGet(); }; struct AppFilePut : public AppFile { void start() override; void update(); void completed(Transfer*, putsource_t source) override; void terminated(error e) override; void displayname(string*); std::function onCompleted; bool noRetries = false; bool failed(error e, MegaClient* c) override { if (noRetries) return false; return File::failed(e, c); } AppFilePut(const LocalPath&, NodeHandle, const char*); ~AppFilePut(); }; struct AppReadContext { SymmCipher key; }; struct DemoApp : public MegaApp { FileAccess* newfile(); void request_error(error) override; void request_response_progress(m_off_t, m_off_t) override; void prelogin_result(int version, string* email, string *salt, error e) override; void login_result(error) override; void multifactorauthdisable_result(error) override; void multifactorauthsetup_result(string *code, error e) override; void multifactorauthcheck_result(int enabled) override; void ephemeral_result(error) override; void ephemeral_result(handle, const byte*) override; void cancelsignup_result(error) override; void whyamiblocked_result(int) override; void sendsignuplink_result(error) override; void confirmsignuplink2_result(handle, const char*, const char*, error) override; void setkeypair_result(error) override; void getrecoverylink_result(error) override; void queryrecoverylink_result(error) override; void queryrecoverylink_result(int type, const char *email, const char *ip, time_t ts, handle uh, const vector *emails) override; void getprivatekey_result(error, const byte *privk, const size_t len_privk) override; void confirmrecoverylink_result(error) override; void confirmcancellink_result(error) override; void validatepassword_result(error) override; void getemaillink_result(error) override; void confirmemaillink_result(error) override; void users_updated(User**, int) override; void useralerts_updated(UserAlert::Base** ua, int count) override; void nodes_updated(sharedNode_vector* nodes, int count) override; void pcrs_updated(PendingContactRequest**, int) override; void nodes_current() override; void account_updated() override; void notify_confirmation(const char *email) override; void notify_confirm_user_email(handle user, const char *email) override; void sets_updated(Set**, int) override; void setelements_updated(SetElement**, int) override; void sequencetag_update(const string&) override; #ifdef ENABLE_CHAT void chatcreate_result(TextChat *, error) override; void chatinvite_result(error) override; void chatremove_result(error) override; void chaturl_result(string *, error) override; void chatgrantaccess_result(error) override; void chatremoveaccess_result(error) override; virtual void chatupdatepermissions_result(error) override; virtual void chattruncate_result(error) override; virtual void chatsettitle_result(error) override; virtual void chatpresenceurl_result(string *, error) override; void chatlink_result(handle, error) override; void chatlinkclose_result(error) override; void chatlinkurl_result(handle chatid, int shard, string* url, string* ct, int numPeers, m_time_t ts, bool meetingRoom, int chatOptions, const std::vector>* smList, handle callid, error e) override; void chatlinkjoin_result(error) override; void chats_updated(textchat_map*, int) override; static void printChatInformation(TextChat *); static string getPrivilegeString(privilege_t priv); void richlinkrequest_result(string*, error) override; #endif void unlink_result(handle, error) override; void fetchnodes_result(const Error&) override; void putnodes_result(const Error&, targettype_t, vector&, bool targetOverride, int tag, const std::map& fileHandles) override; void setpcr_result(handle, error, opcactions_t) override; void updatepcr_result(error, ipcactions_t) override; void fa_complete(handle, fatype, const char*, uint32_t) override; int fa_failed(handle, fatype, int, error) override; void putfa_result(handle, fatype, error) override; void removecontact_result(error) override; void putua_result(error) override; void getua_result(error) override; void getua_result(byte*, unsigned, attr_t) override; void getua_result(unique_ptr, attr_t) override; #ifdef DEBUG void delua_result(error) override; void senddevcommand_result(int) override; #endif void querytransferquota_result(int) override; void account_details(AccountDetails*, bool, bool, bool, bool, bool, bool) override; void account_details(AccountDetails*, error) override; // sessionid is undef if all sessions except the current were killed void sessions_killed(handle sessionid, error e) override; void openfilelink_result(const Error&) override; void openfilelink_result(handle, const byte*, m_off_t, string*, string*, int) override; void folderlinkinfo_result(error, handle, handle, string *, string*, m_off_t, uint32_t, uint32_t, m_off_t, uint32_t) override; dstime pread_failure(const Error&, int, void*, dstime) override; bool pread_data(byte*, m_off_t, m_off_t, m_off_t, m_off_t, void*) override; void transfer_added(Transfer*) override; void transfer_removed(Transfer*) override; void transfer_prepare(Transfer*) override; void transfer_failed(Transfer*, const Error&, dstime) override; void transfer_update(Transfer*) override; void transfer_complete(Transfer*) override; #ifdef ENABLE_SYNC void syncupdate_stateconfig(const SyncConfig& config) override; void sync_added(const SyncConfig&) override; void sync_removed(const SyncConfig& config) override; void syncs_restored(SyncError syncError) override; void syncupdate_syncing(bool) override; void syncupdate_scanning(bool) override; void syncupdate_stalled(bool stalled) override; void syncupdate_conflicts(bool conflicts) override; void syncupdate_treestate(const SyncConfig& config, const LocalPath&, treestate_t, nodetype_t) override; #endif void upgrading_security() override; void downgrade_attack() override; void changepw_result(error) override; void userattr_update(User*, int, const char*) override; void resetSmsVerifiedPhoneNumber_result(error e) override; void enumeratequotaitems_result(const Product& product) override; void enumeratequotaitems_result(unique_ptr) override; void enumeratequotaitems_result(error) override; void additem_result(error) override; void checkout_result(const char*, error) override; void getmegaachievements_result(AchievementsDetails*, error) override; void contactlinkcreate_result(error, handle) override; void contactlinkquery_result(error, handle, string*, string*, string*, string*) override; void contactlinkdelete_result(error) override; void smsverificationsend_result(error) override; void smsverificationcheck_result(error, string*) override; void getbanners_result(error) override; void getbanners_result(vector&& banners) override; void dismissbanner_result(error) override; void reqstat_progress(int) override; void notifyError(const char*, ErrorReason errorReason) override; void reloading() override; void clearing() override; void notify_retry(dstime, retryreason_t) override; void getuseremail_result(string*, error) override; static string getExtraInfoErrorString(const Error&); protected: #ifdef USE_DRIVE_NOTIFICATIONS void drive_presence_changed(bool appeared, const LocalPath& driveRoot) override; #endif // USE_DRIVE_NOTIFICATIONS }; struct DemoAppFolder : public DemoApp { void login_result(error); void fetchnodes_result(const Error&); void nodes_updated(sharedNode_vector* nodes, int count); void users_updated(User**, int) {} void pcrs_updated(PendingContactRequest**, int) {} }; #include void exec_apiurl(autocomplete::ACState& s); void exec_useragent(autocomplete::ACState& s); void exec_login(autocomplete::ACState& s); void exec_begin(autocomplete::ACState& s); void exec_signup(autocomplete::ACState& s); void exec_cancelsignup(autocomplete::ACState& s); void exec_session(autocomplete::ACState& s); void exec_mount(autocomplete::ACState& s); void exec_ls(autocomplete::ACState& s); void exec_cd(autocomplete::ACState& s); void exec_pwd(autocomplete::ACState& s); void exec_lcd(autocomplete::ACState& s); void exec_llockfile(autocomplete::ACState& s); void exec_lls(autocomplete::ACState& s); void exec_lpwd(autocomplete::ACState& s); void exec_lmkdir(autocomplete::ACState& s); void exec_import(autocomplete::ACState& s); void exec_folderlinkinfo(autocomplete::ACState& s); void exec_open(autocomplete::ACState& s); void exec_put(autocomplete::ACState& s); void exec_putq(autocomplete::ACState& s); void exec_get(autocomplete::ACState& s); void exec_getq(autocomplete::ACState& s); void exec_more(autocomplete::ACState& s); void exec_pause(autocomplete::ACState& s); void exec_getfa(autocomplete::ACState& s); void exec_mediainfo(autocomplete::ACState& s); void exec_smsverify(autocomplete::ACState& s); void exec_verifiedphonenumber(autocomplete::ACState& s); void exec_mkdir(autocomplete::ACState& s); void exec_rm(autocomplete::ACState& s); void exec_mv(autocomplete::ACState& s); void exec_cp(autocomplete::ACState& s); void exec_du(autocomplete::ACState& s); void exec_syncrescan(autocomplete::ACState& s); void exec_nodecounter(autocomplete::ACState& s); void exec_numberofnodes(autocomplete::ACState& s); void exec_numberofchildren(autocomplete::ACState& s); void exec_searchbyname(autocomplete::ACState &s); void exec_export(autocomplete::ACState& s); void exec_encryptLink(autocomplete::ACState& s); void exec_decryptLink(autocomplete::ACState& s); void exec_share(autocomplete::ACState& s); void exec_invite(autocomplete::ACState& s); void exec_clink(autocomplete::ACState& s); void exec_ipc(autocomplete::ACState& s); void exec_showpcr(autocomplete::ACState& s); void exec_getemail(autocomplete::ACState& s); void exec_users(autocomplete::ACState& s); void exec_getua(autocomplete::ACState& s); void exec_putua(autocomplete::ACState& s); void exec_delua(autocomplete::ACState& s); void exec_alerts(autocomplete::ACState& s); void exec_alerts_new(autocomplete::ACState& s); void exec_alerts_old(autocomplete::ACState& s); void exec_alerts_number(autocomplete::ACState& s); void exec_alerts_notify(autocomplete::ACState& s); void exec_alerts_seen(autocomplete::ACState& s); void exec_alerts_test_reminder(autocomplete::ACState& s); void exec_alerts_test_payment(autocomplete::ACState& s); void exec_alerts_test_payment_v2(autocomplete::ACState& s); void exec_alerts_add_reminder(autocomplete::ACState& s); void exec_alerts_add_payment(autocomplete::ACState& s); void exec_recentactions(autocomplete::ACState& s); void exec_recentnodes(autocomplete::ACState& s); void exec_killsession(autocomplete::ACState& s); void exec_whoami(autocomplete::ACState& s); void exec_verifycredentials(autocomplete::ACState& s); void exec_manualverif(autocomplete::ACState &s); void exec_passwd(autocomplete::ACState& s); void exec_reset(autocomplete::ACState& s); void exec_recover(autocomplete::ACState& s); void exec_cancel(autocomplete::ACState& s); void exec_email(autocomplete::ACState& s); void exec_retry(autocomplete::ACState& s); void exec_recon(autocomplete::ACState& s); void exec_reload(autocomplete::ACState& s); void exec_logout(autocomplete::ACState& s); void exec_locallogout(autocomplete::ACState& s); void exec_version(autocomplete::ACState& s); void exec_debug(autocomplete::ACState& s); void exec_verbose(autocomplete::ACState& s); void exec_clear(autocomplete::ACState& s); void exec_codepage(autocomplete::ACState& s); void exec_log(autocomplete::ACState& s); void exec_test(autocomplete::ACState& s); void exec_chats(autocomplete::ACState& s); void exec_chatc(autocomplete::ACState& s); void exec_chati(autocomplete::ACState& s); void exec_chatcp(autocomplete::ACState& s); void exec_chatr(autocomplete::ACState& s); void exec_chatu(autocomplete::ACState& s); void exec_chatup(autocomplete::ACState& s); void exec_chatpu(autocomplete::ACState& s); void exec_chatga(autocomplete::ACState& s); void exec_chatra(autocomplete::ACState& s); void exec_chatst(autocomplete::ACState& s); void exec_chata(autocomplete::ACState& s); void exec_chatl(autocomplete::ACState& s); void exec_chatsm(autocomplete::ACState& s); void exec_chatlu(autocomplete::ACState& s); void exec_chatlj(autocomplete::ACState& s); void exec_setmaxdownloadspeed(autocomplete::ACState& s); void exec_setmaxuploadspeed(autocomplete::ACState& s); void exec_setmaxloglinesize(autocomplete::ACState& s); void exec_setlogjson(autocomplete::ACState& s); void exec_getlogjson(autocomplete::ACState& s); void exec_handles(autocomplete::ACState& s); void exec_mfac(autocomplete::ACState& s); void exec_mfae(autocomplete::ACState& s); void exec_mfad(autocomplete::ACState& s); void exec_autocomplete(autocomplete::ACState& s); void exec_history(autocomplete::ACState& s); void exec_help(autocomplete::ACState& s); void exec_quit(autocomplete::ACState& s); void exec_find(autocomplete::ACState& s); void exec_nodedescription(autocomplete::ACState& s); void exec_nodelabel(autocomplete::ACState& s); void exec_nodesensitive(autocomplete::ACState& s); void exec_nodeTag(autocomplete::ACState& s); void exec_treecompare(autocomplete::ACState& s); void exec_querytransferquota(autocomplete::ACState& s); void exec_metamac(autocomplete::ACState& s); void exec_compare_file_and_node(autocomplete::ACState& s); void exec_resetverifiedphonenumber(autocomplete::ACState& s); void exec_banner(autocomplete::ACState& s); void exec_drivemonitor(autocomplete::ACState& s); void exec_driveid(autocomplete::ACState& s); void exec_randomfile(autocomplete::ACState& s); void exec_getABTestValue(autocomplete::ACState& s); void exec_contactVerificationWarning(autocomplete::ACState& s); #ifdef ENABLE_SYNC void exec_syncadd(autocomplete::ACState& s); void exec_syncrename(autocomplete::ACState& s); void exec_syncclosedrive(autocomplete::ACState& s); void exec_syncexport(autocomplete::ACState& s); void exec_syncimport(autocomplete::ACState& s); void exec_syncopendrive(autocomplete::ACState& s); void exec_synclist(autocomplete::ACState& s); void exec_syncremove(autocomplete::ACState& s); void exec_syncstatus(autocomplete::ACState& s); void exec_syncxable(autocomplete::ACState& s); #endif // ENABLE_SYNC void exec_setsandelements(autocomplete::ACState& s); void exec_reqstat(autocomplete::ACState& s); /* MEGA VPN commands */ void exec_getvpnregions(autocomplete::ACState& s); void exec_getvpncredentials(autocomplete::ACState& s); void exec_putvpncredential(autocomplete::ACState& s); void exec_delvpncredential(autocomplete::ACState& s); void exec_checkvpncredential(autocomplete::ACState& s); void exec_getnetworktestserver(autocomplete::ACState& s); void exec_networktest(autocomplete::ACState& s); /* MEGA VPN commands END */ void exec_fetchcreditcardinfo(autocomplete::ACState&); void exec_passwordmanager(autocomplete::ACState&); void exec_generatepassword(autocomplete::ACState&); void exec_getpricing(autocomplete::ACState&); void exec_collectAndPrintTransferStats(autocomplete::ACState&); void exec_hashcash(autocomplete::ACState&); void exec_getmyip(autocomplete::ACState& s); void exec_dnsservers(autocomplete::ACState& s); // This functionality remove all nodes from vault except My backups folder and Password Manager node void exec_cleanVault(autocomplete::ACState& s); sdk-10.11.0/examples/simple_client/000077500000000000000000000000001516266226600171145ustar00rootroot00000000000000sdk-10.11.0/examples/simple_client/.gitignore000066400000000000000000000000631516266226600211030ustar00rootroot000000000000003rdparty *.sdf *.opensdf *.user *.suo *.pdb *.ipch sdk-10.11.0/examples/simple_client/CMakeLists.txt000066400000000000000000000021531516266226600216550ustar00rootroot00000000000000 add_executable(simple_client simple_client.cpp ) # Link with SDKlib. target_link_libraries(simple_client PRIVATE MEGA::SDKlib) # Adjust compilation flags for warnings and errors target_platform_compile_options( TARGET simple_client WINDOWS /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET simple_client WINDOWS /WX UNIX $<$: -Werror> ) endif() # Starting on Xcode 15: https://gitlab.kitware.com/cmake/cmake/-/issues/25297 if(APPLE AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15.0") target_link_options(simple_client PRIVATE LINKER:-no_warn_duplicate_libraries) endif() # Copy files required by this app add_custom_command( TARGET simple_client POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/MEGA.png ${CMAKE_CURRENT_BINARY_DIR}/MEGA.png DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/MEGA.png COMMENT "Copying files required by simple_client." ) sdk-10.11.0/examples/simple_client/MEGA.png000066400000000000000000001022341516266226600203350ustar00rootroot00000000000000PNG  IHDRLWybKGD pHYs   IDATxyeoUutL#PQP{=Q@ފx,(""" $+Aȑd&1ӓQUOy^ytMjz>Tw;#LrLLLLLLLLLLLLL1l)AV;LLLLLLLLLLLLLLLLLLLLLLLLLLL1l)AMeFƤg69{.B9{,"{"g݉)d3d=,*WGZ?uk ÊE,0Sr}w<{[μ>v2R`@J̲/{H枇U Z~i%s]H}-gmH dFdyPf2K^i͓CAˏ6Y"d%]v}~r^3l@"LH~PeoY&s2M3amDcɌr<[l> CfhX;%s ɘBA[)XKrK+}ƿ& 0̝R7.Y\vBA[k n _*u/{sx/ `@2/ʿvi`P%Vy^Ryoy7By,2-2"^-" Z+u~}E%"fyHo,Wo!H',A,:~gʽurؗ"sWYZĖK?6H`,i uʽr_^t`@H#uٗ#_mXݳvU7;r+/FB@0 ȔG+dbI)%>[ޑ)wArߚ)"TR6_#smR$X|AAbɴs7R޻Wx NE^#翓?HOkoL, kv[)w!;/fZ@0`)h*ȿ727$X|AAcIã_)r s 0'*?fIژK?6 6ʽPE;\I0`3r\!ۛlJ, ۀXں=O^ {@0dTeQ9&Ē %}s?Xy2`,,«r›K?6 v}X9},D0dyzsPdbI($ܱWCG(1r߆ S&0>!ʿi$XX{ ʽ?^ @Տve.V֢$K7;%}gsr'? w\F0Yj]$s& 9bX2l%slqGLЧ>J,0ao>['D5pcT+T+*gs$fF^ 7Ē ?d3UP'z5@Lf|_梫z)0A(„lX-ĒYgNTq]r"b~j>SZ G%&gĒn9L K@영MvEWQ,%--A,?~bIv{ 8b`W7۟O}Xba|N,j1%S/HwF|B#  ̪Ln} ubITcW,/W7?%wE]G0t}~-HãݙO}Xba|N,j%z*|$?t5Gi2 < eJ,0in>['D5Ē)ܗ,]A0Xg5R? < eJ,0in>['D5dŒIł y'KN.``YVǿ%~_S0bIKKK쏟Xh-HKY_׮DLVcd.Z ?G%aNZ%O, 4vbɤޢzʽr* r@̓u1#%Q,{+s-W@&Hk*N 1#%4Lfg|N, 2@$ȘWϕ9mN%--A,?~bIqK*|cr=t#Bg|TɧHis F,ii bK;XR>o} &ɿ($ 3%e,$*JPq <"\zmS0bIKKK쏟XhQǒv|SrzLǏ<&įM9%%O, 4n۱}>*wgo^u?F,!XҍmO,iT[~U4 0t6]d\X'w7cI#z#r<6m@ӕǿOK;KZZXbĒ@S,^rp`hor `Ē ?$иK&/xX҅ߖ-3LOWIXXbĒ@s,T~T:9a{a݋eya)%%O, 4ǒF:QO~Hpp̉`{1:&gH>X'wbI]s8fE0>X7 HKZZXbĒ@Nj,s_.m`0t52WX'wcn UtA>tc2$ƄXbĒ@NK,$zʇjŁ `w9#Ē ?$иK&mQ?rLC0lnJ&KZZXbĒ@Ne,PFF5عr"L3L$ ^p:B,ii bK;ձATN @֙ZMgI?m%%O, 4,ŒPϩy$ &af"HbIKKK쏟XhY%GrG_Ss"@F.]8tX'wcIWtީrz4 &AfSY}_n]p:B,ii bKX÷@Ƙ!wXbkL,?~bIqKrGTfp' dٰQOKp:B,ii bKX2{T>29@L #̺Xr#Ē ?$и%s/yg6pB @g6ii#Ē ?$и%-7 $O@03dtߊbIKKK쏟XhĒ}HG̝&H13i!S)D01c.-^pZ@,ii bKXzkޡ~Jf R`)bj5O|G~qi%%O, 4nbI4ۺv_4j E&@?KZZXbĒ@&DW^y u  9|«N %--A,?~bIqKlkgrB @ ~#}ҀbIKKK쏟Xh{ZF8_c3::}`[d+KZZXbĒ@&u].o}')H0 $Yz%KZZXbĒ@&3>Z*Wg+9Y@BL ̊2}RZ7`Z@,ii bKXbo[V'rڍ$322B01 HRZxi%%O, 4nbmQc칋z>_v[sC_ aXEKlL%O, 4nbmщ٬X~r H $1FӤ%KZZXbĒ@&Mmwk_?`Ir7KZZXbĒ@&Mw^vN?$\utĒ ?$и%uGǾv*'4H>ܷB:hbIKKK쏟Xh{ۺ飭ݦߝ/o}8@q ĜٰQ:Ē''7޶nh;MR]f'89 Ęդ17KBHK쏟Xh{ۺ|YFf- )K7ᴀX'7޶nhopFt91 Ĕ&;KZZXbĒ@&M5_[~ʕ|, 1dY#rbIKKK쏟Xh{ۺ&ּ~]S{b;L ft)b%]DKsc$mܤLy 3gI`Z@,ii bKXbo[7}D?8 b\{`Z@,ii bKXbo[7}D2*Fc!a1azTzǥ6Ē ?$$%DEK&"yIb;L Șt7%QM%O, o=Ē1[DG4|Ɍrb @|>XDXbĒp'ѝѭKZy_FpE tW9- %L;1[wG%3ʏ/Q ]g@'IoacbIKKK쏟X$Xbqb,/l@}7]&w8Y@p t1FӉ%QL%O,2&XbK$ɬȿ~i\ tϮ_ƴX'ؙDKbKI .| ][r ̊AG%aN%O,3&D,L-{<20L&뷈%aO*%O,3&D5HRyD#`l/#9 ?$X>X2tƾSΟ`oCJoh% IDlXbaM,~ĒhSkξ{r2K,1/Ē0''ؙDKǒK$idL#`l92K <eA,0k>['XDK3$1OHm]9%0o|\f[q2Ao{@,!K2&D%4Lޢt=w |,bIX bKL%cI%9?IZڝ zb XҕI4$}, dB[TjN1ޒ2ן(=yXB,$X>X2y6Wrd )K˜TK쏟XbgM,~Ka,$ug BYz?Ao{@,!K2&D4\pK !@K9.։%--A,?~bI4$},層nN?BG0(N۞'K%ĒL%c%y*_y"@A'H6ڛ=O K%]DK2K&/跞[O6[qq [?%ybKL%c%Y?SBF0{. zb XҕI4$},r-'bs->|X'ؙDK2K$I0`!1&| zb XҕI4$}X2vm\s#'d ˜FWkWϳ3Ao{@,!K2&DK0dUN ᢫~%xit, zb XҕI4$}X/Pȫo.ډ6t;LS/%%O,3&DK:XE<5t`\z\h&m%bIW&Ē1bIV.k8q@&Љ],h' ub$1Ks c$Vy:@0v/IW?!]v]db\:ĎX%я9d%Wr t`:wK?ao>['XK3$1<I47@&e'_! [b\:ĎX%я9 dB+.hqeRͅ-DK= IDATlXbabG,~ĒǜX"IV59@rl ЂKP_%L,lXbabG,~ĒǜXR=~ܝ-hO~M, KB?$X>F,~[~S" bSY/l%&rg ;bIc&D?$ǒ3ve2hEWKC^K,L։%&vĒL,~)%MCr 43Vι" [b\:ĎX%я9-~Zd* 4ۛ'ׇwaK,0k>['XK3$1,HӪ\;ɯû%X5K,L%яXSK*] \/p.l%&rg ;bIc&D?I~K<s \U8 ub$1KscI_1 s#l{R ub$1KsFb$Us5sY0_#`ĒP'5FҰ5lj4Txđʑ8*Qqco; ǒF1$[B#)/GEQIo IR /W33pa3@JU4vb icw]zzWH~32ں=J÷ܥߤs\:^'ؙDg4l|_z@C6[-#y=7j5pZ+_%M5츝|s?4# &0n>|JĒP'5~{j޻ue; ^zW+b%6B,hvxvɷc*칫wjX~5dEWpZ. [bI$?1=(фXbwRI,}z,Yx%7{;H#c%4}D%V{lK7waK,lRS1>7h?Smo;"Glj˧*#bI  ֩)&0ե׶waK,tKr̋>3% M%] Y%Rc$y '4[`u H/nXbaҜN=lhB, s'̾DbI-I,b(g  R%¤9aG;hB, s'̾ĠGKdGUU.@0_%¤9̿âz44`*B,xY%2d!hr=& I+Jw= bI&a]8hR`(ĒכXwKGkwݫڃ+.L`ܯn$&&y;Msj<{bIcI_+ĒĒe &$1$hx41՘o{bIc w#ĒNwr>$ Hw?(=X'sX@W&n{b1KbI!tvrdPX~RL}hB,xY%g~%$c+$}{슫>y9c [@]}6ч&pĒxp] IF /۞XbuĒPs$}Tq Ӹ@-{X'ڙD š *OD6q 6%q8UkT>d@]}s;sCAX8bILϺg9?ӄX2%:|uK,INr фX2Ē(%9L?R="Ȯ?&PE.$YфX2 '/bIl#3,Zd@v]&PE.$]3='$1KKX,VE0M6JK'X47g 45KVR,yOVbIX=ed$ lv@E.$f&ĒՆK$̽!%ۍ5Ug?D0M%)hr!фXҕuwZbI%vm3\GAD0=&a]Ksә_MnsbIc+0bI I,t6 d@4~("Xn$wl'фXbuaƒX2$Do&bpjKa9sCAXĒl\/M%VV,yסĒXmZ e9svCAXĒli+K9XrW%snHbIigcȔMeiBAXY%&u_hҫ5nĒ%}| I,t6l{d6n3}@v.2H՚PE.wd[Nc:Ý&c+vކtCK"ݷMVUy (L!Ȗe'uK,9}hB,:0c oi!%ۦuW|+)L!Ȗ[E º%`*g}i"T['Ēٗ45}$Do`;Ld @v I6uK,LEƘÌ%?d I,t6/@f`}@fLdX"KsY_VLbIc&DXbg6!U[A0-D"j|Y2M%!9X?bI4bI[7o%q]o\|W;-sL*%/Q%:$cEF0!96L= &KKЉhVehYTKf_bԔ?Ē.qC%KOfdTNhr]WLgӑ?}gcZD0 *x^Kj_Zc4x ՆK~x GPfb%*UWȎ qy'&I?_oNjj+J%@6@$@-{X*%Ē&k4xE+wK:Y/$Ē6Ywjv`dN>WPHT*illLJ'%@-{(Ē@F܉hfфXzMM#26K~j[K_޺k= &iUOOO,(iP((itt K,I.&4YޞC,dSrgI_[Ē{}@@Vr9JDƒɉT*)㞉@-ĒNsSf&ĒN;hjʿ bIlgbU9" zzzR3ļ(&mcYZD.r%)%фXz'cɹ$w&|]{XD@Eԍ+ϫX,` mĒ8RJ79Ēٗ w&X]U\8Ţy*|N\K,_ZtфX2;L,L,9m9H,ĒIS* GWG[FbKc.?$3;H~NA`0aH{K,0~Zx7F vQbICgbIw!;L6===d#̂` \mBXbao(7s:1~FK%$q;ڲq0EjLn.KE0^<΅&hrLфX˹ߒ#$m{pep0EyWj+x\&k%b f<-<1d<&bIBK, R$<8e>v Zx GӢI5XoK8^bIw#8" `5pr%%kH9O 9e<Пobx>ĒC_(Lnlroͩo 6Zxt{7D[?:Ekͧ54ByKebIw!_]ŝsoo0^+BXN>"79?_%y;RKs mqF=Lg 7"HY0!B;#'Ӣsb4Ix,9Ē"tz,$ţH$.a` XBXbi- -bI,Ksi/H6 f=,%A\ 8N2!H5O{I,?~bI/9-9DXҝsH<4;,;Lv"H׷H,?~bIb9v7MKKXci'aE[#qlpO6ۻP$).瞪5) Oxb돿5CB,1tdFF{؁[|p8۰Vej9Eyjv.%ϩ'5wh5 pAj,Β$"tn:_rw]NXO|Vl@<3MxF<8v9[&i2SR,9uĒ"tn؆m@L &+ڭ.A,Ihrp$Xrw% D,1_:vd<痰 ;@0>_K쏟X B^Eb[%IE,1܄~aLنC0>C"KХvI'Ē$"tnB\ Ӄ` }h#?${T5Gb >ĒHn]!>0&gp(5 $K{QUk|ghV,9Ē4!(D 0!HM^KB?$&[^9KbIZKX"If` }&ҧbK4x4|̿B^?įMM[^C,P^r67DI%-9R` }Fڼ$:~b?zU̿$7G?ӤXriInU+֜)CdX4Ēs(p7+۰}鳱5 $mLTǣּ$i#WM&Ye~{n-Zu܇52Ֆ~$%D8 &{bIZbIws4iI_N!]/8m2Kz5oÙ0۴]'kHEE9-`"D:vcgf;Ldm>3ZEv;$uoMhyf){d,m8cʉ;K%ĒLنC0>Mo &:~bISEMn=/Qn&wh9o~d$+bIGlöLX%Ē&49DMǒ.0&gtĒǒi/Dz4~LM#iDLM%LiV{qX| bV؆mbI't<ʌT׮|W|q_N!]/<]]MQAՂ& L,qXq;#府[:~&?Ä` ._wK2ym4MD4x:PmuyVC,~'pGjj5ImE-ҧRх,ҶG]Kt,!t>j.R` uQKB?$gc21 & e bIEUU^1f4L ;{>9{x4ӣM0K>XbXB, y@|q ۬| .#$_έx4^W f3%Z%$8$VT1 nwewbI?ϝ%v7#j=*rGnj eVa,},!<~ j|gCLaB0KbO'Ēm-whrMLuf2 K%k,)bFbd U{&'uiN,$#hB,_o'tXB,  |k Gc&225xA,޶O9Г.9;3?X"bI 9 466F`5E0iz.f- (Wˣ&L:Ý%]:K8* e2y뒤"H m,KybIaC/ʕ&ɮ|O/ad,)K%MH,q=322^6,ۆ4wHBXKL ;&#z؏| b6[FKR7$ 8Z(fPT28SL"{^;OL??DbIgʷ,cN"KbsEDNpc4::ʆ@0>|ē bIw& 4XW !Kbsv$7 !&  H| bIw& \.E.:SeV61bIl! b/v$VV m[q#HyM.%' gj4%&c$>F,9dN/x$X?ϤR53ȱ NO!$ô93:MScIXbXB,?)8 G:KhR%|&g~%ĒL$G6h>F4whq'K"~]4_Xqs Rt&j5cVĒL0>xXbA,t{n&%d0$EE%ExKRw&i Ա1bI&gAbIa{2N IDATQIoXbu|QbImޒH*1F###|r&'"؟dKD(vgK"}]4_XX0AU*DIVS\W'w` }]&XbyRt{%v^%D>~3r@@rȻ3FGGU.>Od&gۭ¹$K,O %qhmwjqĒ_%D>~Agm8( 444VJB0>mbXB,<) {{O&ws\Y=o$Dh($tߎ2} m: sWI@S,bXB,<)j{j`Y$Dw,T./J,|-)pp@D\'juIIM ZXnJXX`oQIqXv*䀔_bKaKJ,|V4<<,qyuWUU*&8a obIF,1`Աn9}D÷ߓ~FcIKߴ= zT.544QUPC1FjUR\$"a vڶ+Ab $w?4YuTziUnbI,VbI7?,ډ>lu]y'u庮ǙgiL|WV3I,#HobXB,d%uE2hrY*(-B,9$bIןVbI7=L0G$ oN obXX剡_M,?R{ΪcN4='}d{%xZ%t.;s &i/%˫v,KO4>bn5R` v_8ĎXMx3M>1$XrXKK" oq rTr\(-hĎXM_Y\+MDC|^gաcVE,8%ܽv icy\ם#IL\[j*NӮ%&vĒWA,M.J//FKXMwؕ g qr9yr$y޴Ǫժ**sbIcI"K&a,&G/ahoɏ%'pbI7ѬH3>$aQXTJRK\.R~ɸ$gK,LxgI 'sihXrĒĿj% /H\ 1{B\W q݀YM &Ē6]\wV0-U- $c DĒxIr &>?gGm8qhskXx 3ƒdxc DĒrrٓ F0R7\UT C2v)^ĒPO1?L&#O&15K6KG~< ߺ[.>go9<R`B!Q_b@K+>ĒPO1&'ԕ3Mg4>bI:^<1% &>uU,{EUUϓ83ui꟩Nٹ矛/F_* $KsI4I+>I{lxn,6f $/;XRsB@LbeD~AbIwX<${ 'MwfpIzL $7oz{{U(Gf{~v/t%M,y&XK,'x/y!L2`Ei$c"ӣi$Mwy/ޅ.$и%1;es491h2KN$tK'@0ֆb2qG|^RIS__zzz}I}ϖ/t%M,߹r6Ge }n%Ēr%cK;`?@&lHeoH۫yT*)Gվgo_-91 mI_ ]bIqKb0owmSi#~P%w7ܥG>Mm[(XXK}W ٙW )=ۿP(dm6m_H~YbIqGxZ,?CޯKVǼ]ox -FHQGUnG:+mjL,|ĒCUerA 3&@-~[=sK, <ĬStƒJrTrJκQq4ڙ6 o&D~bIϡ߾rs'_18ly24NF0-wpWwt{Ձ3b XbyQޑ$OMݾLKaWRɬ8eh3$Kl3\r9y'&H?gRTVU&C  ɾ穧$ r{U9`Ē&ӖH5d%\I6VG,KlFDDC<@QQyzתSqU< x*E9DVgTR?fNS$5t=OJ=T%ƟKr8cQ=~ri (AnllO((˲|為Yk"}ɭĒĒ,gXb|%>$w/q 1bǢt<۶ PRP$aY&}jm^|W\癇)ɭK;$䳈NL,K%YX&`>W;vbu#sMc=Q*Jj4|` c̕Dz,AjRD,1"z!K;$䳈b~$ ??%1sK0m+ CUաuVS% >Ƴ18ä9gY4VX X"Xb1F,1?b}<_dۏ!>-J3RVf:gk$>1A+"(q>%,%csM,1 3* KF}OPecyV/|H,gM,1#kb>fw 8#ve) CEQħPbA%ͦ R4tȝ1.ٶnXҧE4cXb~%ƟE߶{CT* $c(T5==̈́`] S?FXRd嘜COhb\&$C ,Z!ĒA(8[Gv{(1oYd$}8$s%csM,C,1x/γ8dJOzoJ1A0z|]}Α=sKz1b&!e"=0x e\.+ C&&}@e2ojGEβwߑX"O3#kbI~bXbﺓ=wg<ǤJ{9#ILMM 'I)Y:~ pO:z@8KFKL,-3ebI~bX58ug\;oLjJuMF_ȷu]2ER\.SwrX%K5$g},$c9P@g.:)(N|m6\d3)BZY;Y8Ē@,1얉%=%k7 ,Y0o>[ڏ@ny+?h 2!s%rsM,@,1x/-Hw9@0Xqi:׃!)(ChÆ &v[6lkd<*c !Ȟk0b& K$; Ԣn?KDSP QhRmsG!*2΁{e-p%Xbbi-K T +X; g BcS#PZVajirrrc8ORdC, KnXR_DSˁ-9!4g)(J/Gm8+ CC;85>KsC?{= n32[Xl~l 4DN`@au5qa7ܶ[%˲dwٿEGyIԔ8˅E;?>% Cyr_vQkվWĒ"Œ۳K%ǒzK{3 Q<APOm[ȶ-~ &O_3QZV_ d4Mq A.?^^Y%\.s(qb_A,1#kbI#!%%u]J%&J8ۥǙ # ,kj2A9HMOOh}yLDFCFcU"m*rި/x\[ Ē\.w!E,1?Ē?G 1#K:N;XXV(˚4͝82(Ġv_Y" viu+Ѷqh(`iS)LlG*~ 0iee"_4A,$5A,)D,Q"YGf5!g@l!_*455mvζp]Wr:gt~_`2If9s})}*V,΃dL1e( 6E'bIc-5>!VQzٲ%DJdo~9+yzlB0DsN@)uK }q-"H3]I DGo:F{ᲶY Ēƕ[oW~]Ky\CKf'wܩ`PƛGbkry:[q- %-gA?ؚՉPO C@v]EQD,ו{  bɢ7Yl_{,5ybɦ牥G~'}*|=6bID,E[J?5K8oY'jM#bIֱD*?l>NQZe"Y Hy\%x\$3oj6J3L0Js쾣lnaG,YώDjoXNGtޢyĒ|I]/au4v'[~È%[q}_JeѳP,U.566 L FQ\뱠eYKܥGW5[v'[5ب*kH0<-KJBM 3K2s"iJmM'mJr`jmI,,[!oti-P,bDSI%y}to2$+K%RIo1pt; x7_‹~Au>@֐SNEL%ǜ.g!E,1?#K\//g#K8t'/Es`䶭(%Xcg';K̏Xb~Ē|XG9Hr{,!^0Xb<Ug~pK,1?fb1KobIX"I+煾 `>#YbhKNZ}n%L,1?fbI>M,+Fuݲ,ٶ-۶3ng~J||)> #b tM-L%Xb~Ē|X%_(8Ǚ"0Ϛ{7ZL@iZ3?;@A'\ÆX%&|"c&3$&?~bɦ֨e/f,kZ82׭P<{@֖LM,1S.~`J,1?fb1KobI'^~˲0TZؘ(RT`۶M,ĒUʊn߹; O,1,}"/;mSb1K̏X|K?d yg>t"T*jT* X g`ěc /0C8izzcTbIc&3$&?~bɖ|OQ5v|.웘 rL,A>;m?Q%Xb~Ē|X%[:Ps&IEj ÐXP8\.+cp_O9M[YK ~uS&3ĒO,ٚ}-u]Wѹ 3L0J4wN-XR|b5$&?~bܑ5X TVE|'(<0@}_r@_&rjuO,)MX|K?dݽU IDATn^/{sʹm[AH $0q]X8wzNXR|b5$&?~bN/89X̧A@,08PmUU^tw{~Yg乨$`XsM,g%X2?Q?-{ry}M,Ēm:|,M,Ē} .J| ,}EJD`!)yp= W7ŚkbI>M,ĒZZc~w\Spa,`4v+j Ē",_k%7$K_iXbY("@V0)VDm{_emggdnJd_v{WRTF)V?Y`Xb~!G,1?K%ly/]wڶ(^ ,gN0?8#۶)QZVk\Z`Hi}Ɵzqzab\KD,!d F9TiyI8+S`N'+qf~}{c~;j)eEފQ켽·ZSo%rsM,X6,|_`,qGQK 52gdǶm;kOēίn#+i4񜗩_#bI 9b&XB,f )(aqTV$d<ϓ:LQ~K8 @o<^b@,hĒ|X%m1U~MnXy#;Wj X;mרwJXј%7$KIgX"IJXy`=SZUZy 3\xF]%X|K?$,y9f+@$cy Ð&9O~ݟE%7$KI^{kd-.e^0 GVlzhj6XP\U| к.u45Xb~Ē|X%m$%o=vK6NiNsc&3ĒO,n;6nﹻ_-nJE}`ͦ3O:\U\*;c8Gؐ<%7$KێDUn]w+߲>Vز,(bŊ\?Ʋ,U*NS0vTt;?'3$&?~bIv1K~Vqb  YRZj*=}q"?X^%L,g%Xvo;鸭+òޒ Szzw^@<0VK]_"bIv?X2KXҷI9pWԙr00Lfk4㸧b$aH,;VN PVmKM .%L,g%Xv㗾vJފÇ@q)Z-e^4`E@ /_$1KobI'dwO=Q`H,g@&^  'MM<7ݺ$1KobI'dr~^ XTB(½߅X'}OU]%X|K?$HwA?>g,q]XB`wkVzGd^JXј%7$KێSُ^jί/˼@L]TGRЧ$2[&XB,fzq$T*q^&0܃SHs|$1KobI'd[a [9O9`pVTE `3TKs\LKL,-3eb%Ēl=s)J\@3UzKsXLKL,-3eb%Ēl_s=8 x@0!_rE2[&XB,fzm3W!/X0 $ICjieoVk7M,%FN,}}*e-p!WE/V0 &0dFSg]?"Xb~%"KنgySe[V./T0 &0SEz%KqM,Ēc?s9H5U^6]kʡ_\κ#kb%ĒlٶW?]h,"Ǫ]I9w!G,1?K%lCmﳧWJV{[q`ʚj9BXb~L,X6>^{lzΊ~ gK30;+USrBXb~L,X6>+gdYOq]K`PU}rBXb~L,X6>k~]K`U4&KnX"b $m}}\ҙ%8<)^ڷnlRfb%ĒlٶW~VKiA|WT?D,1얉%"Kنg9t_ vX%yA>nf `wcUվ~g=5X"b $m}ˏN}_e|hʡWL-q F,1h&XB,fzm(aO?#|OѕPt#_4KD,!d vg+ԇeu8/20I$L$Iԇ\ d?$;oAwŒ.ػJe)IqfFn+ /!@0̘⟴JRW7=/\%s!؉%}۝)ضV~]^|DZͦdڲ466K`L[FM\p4]vu…X2KXҷIbIh.W)q^lnqT* ?b%3p!c'>K+櫟S驇DFCFCqZu]^H`LsK㧽R{|B,%FN,})*ކfi{=_7[{!§E+~ptB,%FN,} 7'I2/kVj+yXKD,!d vӴ _5 3XSW-/kB,%FN,} 7 Zُ v [ko+*mZ…X2KXҷ@*f,*eUؙB0,I|?}W b\H,1:vbIyg7;q@xK`I}jw.\%s!؉%}bƒ3mb `&x^ڵWty.%7$KێY][WY7`xK`YޠWM hb_J,1>~bIv9FZ˸^ `Y&eky_ZnX2KXҷ@*^,^5\)o]9mGy~ 'b_J,1>~bIv۱ʋж?Xg25hp"]'d0ޭd`\jůW}D,1`K%O,n;$y5_B^{d2Vj.<[Ē.RbKIE/t=`g{mJ~t$b_J,1>~bIfaZUL哟N` `\joS:Xbd>KXv=S?y'm`$I4}5|ˇS3[ZfvK%laC}.1yN wIkcӆ^.M72X<K',;@co{H`wkoQӛ.m3eb%Ēla`eܝ%/&JmM}?"MMYZfh%XB,f + 57)zͅl$o&Bh}&.yL-Z%"Kنo#8Hrwّ"&B[|ߨ -؈%rN,} /OXz*gNP@Nӟ+8bҿXb|Ēi{"{ v~B! 5V3XK匝Xҷ@O,W+?>PH@%F%?ߢX"b $m6ի47ry\Ph@h?6#nVx[KD,!d nòT\r֬f(< `4o&/5&3fb%Ēl<\+>.N 00&T4o[KD,!d nkRw<;-!Vǚ̗M]߄Xbg/gĒR>^R^r]vVD0 cjcSaҗK%YlU)zUd904׆}BSJj6[ KD,!,{ Œ͡v%vۙ `(LC/>m'4}78&P', Y|uU9t.D.;s 5 `dO.5؉%}cIrWyv&@0i^S&`K%O,y@qzr݆`L#=>+?T_J,1>~b$~ҶBE+VegIKMMퟵcU|m]'[POy#`,L?yտ/O!,Ē2b(|^|#e'f4u՚~A yXb|#KmQ%g+\oǓ -3MկQ?E53 yXb|#KPv&t)]:J\O,Yˈ%E%6U>TE+on<.cMo+C)X&?Q%(:L?IX" ÚYC,13!%޺U9>U$G2nM}雚ujyObbi,qwE3ϗǮ<?v~ZźXb|CKwP*i?_7%ǂe|?dcZUg R鸣^œ> PIߪM7KV 9Xb>pMgscA'}F0jǪjOvǬ+~K%}%%w=T:G?MQGȮUyP0@Gy/8ᖈ%˼ρ?h?Gi?f 0麚ܦMyjtڿe-%$9C?lC?h_YZ !ßԼ65K?]͛oS %K*gwOΓ 0ڏ?T|y;~w3d^K]V{o+x0&Ïyպw{ޫߓY_9:ĪFrwi_(wrVA!-Z}PվjG_׫Ln:k IDATK"IfݓlD9;>yo5\<`,VjZ#ظG~l\GS21RT2QԴ麒 [okbRj-=~زUY@VXeZ${lL֊챚5Y+Y-If]KF0H-LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `B0H!LR&) @  `מB:IENDB`sdk-10.11.0/examples/simple_client/README.md000066400000000000000000000010771516266226600204000ustar00rootroot00000000000000# simple_client C++ console app using the public API of MEGA SDK ([include/megaapi.h](../../include/megaapi.h)). This example logs in to your MEGA account, gets your MEGA filesystem, shows the files/folders in your root folder, uploads an image and optionally starts a local HTTP proxy server to browse you MEGA account. [simple_client.cpp](simple_client.cpp) must be edited to replace values of `MEGA_EMAIL` and `MEGA_PASSWORD` with real credentials. Optionally in the same file, more MegaApi calls can be added after the comment `// Add code here to exercise MegaApi`. sdk-10.11.0/examples/simple_client/simple_client.cpp000066400000000000000000000156071516266226600224600ustar00rootroot00000000000000/** * @file examples/simple_client/simple_client.cpp * @brief Example app * * (c) 2013-2025 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include #include #include #include #include // ENTER YOUR CREDENTIALS HERE #define MEGA_EMAIL "EMAIL" #define MEGA_PASSWORD "PASSWORD" // Get yours for free at https://mega.io/developers#source-code #define APP_KEY "9gETCbhB" #define USER_AGENT "Simple-Client example app" using namespace mega; class MyListener: public MegaListener { public: bool finished{}; virtual void onRequestFinish(MegaApi* api, MegaRequest* request, MegaError* e) { if (e->getErrorCode() != MegaError::API_OK) { finished = true; return; } switch (request->getType()) { case MegaRequest::TYPE_LOGIN: { api->fetchNodes(); break; } case MegaRequest::TYPE_FETCH_NODES: { std::cout << "***** Showing files/folders in the root folder:" << std::endl; MegaNode* root = api->getRootNode(); MegaNodeList* list = api->getChildren(root); for (int i = 0; i < list->size(); i++) { MegaNode* node = list->get(i); if (node->isFile()) std::cout << "***** File: "; else std::cout << "***** Folder: "; std::cout << node->getName() << std::endl; } std::cout << "***** Done" << std::endl; delete list; std::cout << "***** Uploading the image MEGA.png" << std::endl; MegaUploadOptions uploadOptions; uploadOptions.mtime = 0; api->startUpload(std::string{"MEGA.png"}, root, nullptr, &uploadOptions, nullptr); delete root; break; } default: break; } } // Currently, this callback is only valid for the request fetchNodes() virtual void onRequestUpdate(MegaApi*, MegaRequest* request) { std::cout << "***** Loading filesystem " << request->getTransferredBytes() << " / " << request->getTotalBytes() << std::endl; } virtual void onRequestTemporaryError(MegaApi*, MegaRequest*, MegaError* error) { std::cout << "***** Temporary error in request: " << error->getErrorString() << std::endl; } virtual void onTransferFinish(MegaApi*, MegaTransfer*, MegaError* error) { if (error->getErrorCode()) { std::cout << "***** Transfer finished with error: " << error->getErrorString() << std::endl; } else { std::cout << "***** Transfer finished OK" << std::endl; } finished = true; } virtual void onTransferUpdate(MegaApi*, MegaTransfer* transfer) { std::cout << "***** Transfer progress: " << transfer->getTransferredBytes() << "/" << transfer->getTotalBytes() << std::endl; } virtual void onTransferTemporaryError(MegaApi*, MegaTransfer*, MegaError* error) { std::cout << "***** Temporary error in transfer: " << error->getErrorString() << std::endl; } virtual void onUsersUpdate(MegaApi*, MegaUserList* users) { if (users == NULL) { // Full account reload return; } std::cout << "***** There are " << users->size() << " new or updated users in your account" << std::endl; } virtual void onNodesUpdate(MegaApi*, MegaNodeList* nodes) { if (nodes == NULL) { // Full account reload return; } std::cout << "***** There are " << nodes->size() << " new or updated node/s in your account" << std::endl; } virtual void onSetsUpdate(MegaApi*, MegaSetList* sets) { if (sets) { std::cout << "***** There are " << sets->size() << " new or updated Set/s in your account" << std::endl; } } virtual void onSetElementsUpdate(MegaApi*, MegaSetElementList* elements) { if (elements) { std::cout << "***** There are " << elements->size() << " new or updated Set-Element/s in your account" << std::endl; } } }; std::string displayTime(time_t t) { char timebuf[32]; strftime(timebuf, sizeof timebuf, "%c", localtime(&t)); return timebuf; } int main() { // Check the documentation of MegaApi to know how to enable local caching MegaApi* megaApi = new MegaApi(APP_KEY, ".", USER_AGENT); // By default, logs are sent to stdout // You can use MegaApi::setLoggerObject to receive SDK logs in your app megaApi->setLogLevel(MegaApi::LOG_LEVEL_INFO); MyListener listener; // Listener to receive information about all request and transfers // It is also possible to register a different listener per request/transfer megaApi->addListener(&listener); if (std::string{MEGA_EMAIL} == "EMAIL") { std::cout << "Please enter your email/password at the top of simple_client.cpp" << std::endl; std::cout << "Press Enter to exit the app..." << std::endl; getchar(); return 0; } // Login. You can get the result in the onRequestFinish callback of your listener megaApi->login(MEGA_EMAIL, MEGA_PASSWORD); // You can use the main thread to show a GUI or anything else. MegaApi runs in a background // thread. while (!listener.finished) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } // Add code here to exercise MegaApi #ifdef HAVE_LIBUV std::cout << "Do you want to enable the local HTTP server (y/n)?" << std::endl; int c = getchar(); if (c == 'y' || c == 'Y') { megaApi->httpServerStart(); megaApi->httpServerSetRestrictedMode(MegaApi::HTTP_SERVER_ALLOW_ALL); megaApi->httpServerEnableFileServer(true); megaApi->httpServerEnableFolderServer(true); std::cout << "You can browse your account now! http://127.0.0.1:4443/" << std::endl; } #endif std::cout << "Press Enter to exit the app..." << std::endl; getchar(); return 0; } sdk-10.11.0/include/000077500000000000000000000000001516266226600140725ustar00rootroot00000000000000sdk-10.11.0/include/impl/000077500000000000000000000000001516266226600150335ustar00rootroot00000000000000sdk-10.11.0/include/impl/share.h000066400000000000000000000034441516266226600163130ustar00rootroot00000000000000#pragma once #include "mega.h" #include "megaapi.h" namespace mega { struct Share; namespace impl { class ShareData { public: ShareData(MegaHandle nodeHandle, const Share* share, bool verified); MegaHandle getNodeHandle() const; const Share* getShare() const; bool isVerified() const; m_time_t creationTime() const; private: MegaHandle mNodeHandle; const Share* mShare; bool mVerified; }; class ShareExtractor { public: using Filter = std::function; // Extract out shares, if filter(data) returns false, the out share is dropped static vector extractOutShares(const sharedNode_vector& sharedNodes, const KeyManager& keyManager, Filter filter = nullptr); // Extract pending out shares static vector extractPendingOutShares(const sharedNode_vector& sharedNodes, const KeyManager& keyManager); private: static std::vector extractPendingOutShares(const Node* n, const KeyManager& keyManager, Filter filter); static std::vector extractOutShares(const Node* n, const KeyManager& keyManager, Filter filter); }; class ShareSorter { public: // Sort in place static void sort(std::vector& shares, int order = MegaApi::ORDER_NONE); private: using CompFunc = std::function; static CompFunc getComparator(int order); }; } // namespace impl } // namespace mega sdk-10.11.0/include/mega.h000066400000000000000000000063421516266226600151610ustar00rootroot00000000000000/** * @file mega.h * @brief Main header file for inclusion by client software. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_H #define MEGA_H 1 #ifndef MEGA_SDK #define MEGA_SDK #endif // version #include "mega/version.h" // project types #include "mega/types.h" // project includes #include "mega/account.h" #include "mega/attrmap.h" #include "mega/backofftimer.h" #include "mega/base64.h" #include "mega/command.h" #include "mega/console.h" #include "mega/db.h" #include "mega/file.h" #include "mega/fileattributefetch.h" #include "mega/filefingerprint.h" #include "mega/filesystem.h" #include "mega/http.h" #include "mega/json.h" #include "mega/logging.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/node.h" #include "mega/pendingcontactrequest.h" #include "mega/proxy.h" #include "mega/pubkeyaction.h" #include "mega/request.h" #include "mega/scoped_helpers.h" #include "mega/serialize64.h" #include "mega/share.h" #include "mega/sharenodekeys.h" #include "mega/sync.h" #include "mega/transfer.h" #include "mega/transferslot.h" #include "mega/treeproc.h" #include "mega/user.h" #include "mega/utils.h" #include "mega/waiter.h" // target-specific headers #include "mega/thread/posixthread.h" #include "mega/thread/cppthread.h" #ifdef USE_IOS #include "mega/posix/megawaiter.h" #include "mega/posix/meganet.h" #include "mega/osx/megafs.h" #include "mega/posix/megaconsole.h" #include "mega/posix/megaconsolewaiter.h" #else #include "megawaiter.h" #include "meganet.h" #include "megafs.h" #include "megaconsole.h" #include "megaconsolewaiter.h" #endif #include "mega/db/sqlite.h" #include "mega/gfx/freeimage.h" #include "mega/gfx/GfxProcCG.h" #if defined(REQUIRE_HAVE_FFMPEG) && !defined(HAVE_FFMPEG) #error compilation with HAVE_FFMPEG is required #endif #if defined(REQUIRE_HAVE_LIBUV) && !defined(HAVE_LIBUV) #error compilation with HAVE_LIBUV is required #endif #if defined(REQUIRE_HAVE_LIBRAW) && !defined(HAVE_LIBRAW) #error compilation with HAVE_LIBRAW is required #endif #if defined(REQUIRE_HAVE_PDFIUM) && !defined(HAVE_PDFIUM) #error compilation with HAVE_PDFIUM is required #endif #if defined(REQUIRE_ENABLE_CHAT) && !defined(ENABLE_CHAT) #error compilation with ENABLE_CHAT is required #endif #if defined(REQUIRE_ENABLE_BACKUPS) && !defined(ENABLE_BACKUPS) #error compilation with ENABLE_BACKUPS is required #endif #if defined(REQUIRE_ENABLE_WEBRTC) && !defined(ENABLE_WEBRTC) #error compilation with ENABLE_WEBRTC is required #endif #if defined(REQUIRE_ENABLE_EVT_TLS) && !defined(ENABLE_EVT_TLS) #error compilation with ENABLE_EVT_TLS is required #endif #if defined(REQUIRE_USE_MEDIAINFO) && !defined(USE_MEDIAINFO) #error compilation with USE_MEDIAINFO is required #endif #endif sdk-10.11.0/include/mega/000077500000000000000000000000001516266226600150035ustar00rootroot00000000000000sdk-10.11.0/include/mega/account.h000066400000000000000000000223041516266226600166110ustar00rootroot00000000000000/** * @file mega/account.h * @brief Classes for manipulating Account data * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_ACCOUNT_H #define MEGA_ACCOUNT_H 1 #include "types.h" namespace mega { // account details/history struct MEGA_API AccountBalance { double amount; char currency[4]; }; struct MEGA_API AccountSession { m_time_t timestamp, mru; string useragent; string ip; char country[3]; int current; handle id; int alive; string deviceid; }; struct MEGA_API AccountPurchase { m_time_t timestamp; char handle[12]; char currency[4]; double amount; int method; }; struct MEGA_API AccountTransaction { m_time_t timestamp; char handle[12]; char currency[4]; double delta; }; struct MEGA_API AccountFeature { m_time_t expiryTimestamp = 0; string featureId; }; // subtree's total storage footprint (excluding the root folder itself) struct MEGA_API NodeStorage { m_off_t bytes; uint32_t files; uint32_t folders; m_off_t version_bytes; uint32_t version_files; }; typedef map handlestorage_map; struct MEGA_API AccountSubscription { string id; // Encrypted subscription ID char type = 0; // 'S' for active payment provider, 'R' otherwise string cycle; // Subscription billing period string paymentMethod; // Payment provider name int32_t paymentMethodId = 0; // Payment provider ID m_time_t renew = mega_invalid_timestamp; // Renewal time int32_t level = ACCOUNT_TYPE_FREE; // Account level vector features; // List of features the subscription grants bool isTrial = false; // If the subscription is related to an active trial }; struct MEGA_API AccountPlan { int32_t level = ACCOUNT_TYPE_FREE; // Account level vector features; // List of features the plan grants m_time_t expiration = mega_invalid_timestamp; // The time the plan expires int32_t type = 0; // Why the plan was granted: payment, achievement, etc. Not included in // Bussiness/Pro Flexi string subscriptionId; // The relating subscription ID if the plan relates to a subscription. bool isTrial = false; // If the plan is related to an active trial bool isProPlan() const { return level > ACCOUNT_TYPE_FREE && level != ACCOUNT_TYPE_FEATURE; } }; struct MEGA_API AccountDetails { vector subscriptions; // Expiration time of the latest PRO plan. // This expiration time could be higher than the currently active PRO plan m_time_t pro_until = 0; // quota related to the session account m_off_t storage_used = 0; m_off_t storage_max = 0; // Own user transfer m_off_t transfer_max = 0; m_off_t transfer_own_used = 0; m_off_t transfer_srv_used = 0; // 3rd party served quota to other users // ratio of your PRO transfer quota that is able to be served to 3rd party double srv_ratio = 0; // storage used for all relevant nodes (root nodes, incoming shares) handlestorage_map storage; // Free IP-based transfer quota related: m_time_t transfer_hist_starttime = 0; // transfer history start timestamp m_time_t transfer_hist_interval = 3600; // timespan that a single transfer window record covers vector transfer_hist; // transfer window - oldest to newest, bytes consumed per time interval bool transfer_hist_valid = true; // transfer hist valid for overquota accounts // Reserved transfer quota for ongoing transfers (currently ignored by clients) m_off_t transfer_reserved = 0; // free IP-based m_off_t transfer_srv_reserved = 0; // 3rd party m_off_t transfer_own_reserved = 0; // own account vector balances; vector sessions; vector purchases; vector transactions; // Features vector activeFeatures; // Active plans for the account. Both PRO and feature plans. vector plans; }; // award classes with the award values the class is supposed to get struct MEGA_API Achievement { m_off_t storage; m_off_t transfer; int expire; // in days }; // awarded to the user struct MEGA_API Award { achievement_class_id achievement_class; int award_id; // not unique, do not use it as key m_time_t ts; m_time_t expire; // not compulsory, some awards don't expire // int c; --> always 0, will be removed (obsolete) // for invites only vector emails_invited; // successfully invited user's emails // int csu; --> always 0, will be removed (obsolete) }; // reward the user has achieved and can see struct MEGA_API Reward { int award_id; m_off_t storage; m_off_t transfer; int expire; // in days }; struct MEGA_API AchievementsDetails { m_off_t permanent_size; // permanent base storage value achievements_map achievements; // map vector awards; vector rewards; }; struct MEGA_API BusinessPlan { int gbStoragePerUser = 0; // -1 means unlimited int gbTransferPerUser = 0; // -1 means unlimited unsigned int minUsers = 0; unsigned int pricePerUser = 0; unsigned int localPricePerUser = 0; unsigned int pricePerStorage = 0; unsigned int localPricePerStorage = 0; int gbPerStorage = 0; unsigned int pricePerTransfer = 0; unsigned int localPricePerTransfer = 0; int gbPerTransfer = 0; }; struct MEGA_API CurrencyData { std::string currencySymbol; // ie. €, encoded in B64url std::string currencyName; // ie. EUR std::string localCurrencySymbol; // ie. $, encoded in B64url std::string localCurrencyName; // ie. NZD }; struct MEGA_API MobileOffer { std::string id; // The ID for the offer bool uat{false}; // If true, the mobile offer title should be displayed }; struct MEGA_API InstantDiscounts { std::string discountCode; // The discount code to be applied std::string discountName; // The name of the discount int discountPercentage{-1}; // The percentage of the discount int discountGroup{-1}; // The group code of the discount int discountMonths{-1}; // The number of months the discount applies to }; struct MEGA_API Product { unsigned int planType = ~(unsigned)0; handle productHandle = UNDEF; unsigned int proLevel = 0; int gbStorage = -1; int gbTransfer = -1; unsigned int months = 0; double amount = {0.0}; double amountMonth = {0.0}; double localPrice = {0.0}; double priceNet{0.0}; double localPriceNet{0.0}; double monthlyBasePriceNet{0.0}; std::string description; std::map features; std::string iosid; std::string androidid; unsigned int testCategory = 0; std::shared_ptr businessPlan; unsigned int trialDays = 0; std::optional mobileOffer; std::optional instantDiscounts; }; struct DiscountCode { std::string alfanumDiscountCode; int item{-1}; int accountLevel{-1}; int behaviourType{-1}; int percentageDiscount{-1}; int numMonths{-1}; // (1 or 12), or 0 if applies to any virtual ~DiscountCode() = default; virtual bool isValidFormat() const { return numMonths >= 0 && numMonths <= 12; } virtual bool hasAlfanumCode() const { return !alfanumDiscountCode.empty(); } }; struct DiscountCodeInfoExtended: public DiscountCode { int expiry{-1}; int compulsorySubscription{-1}; enum TaxFlags { TAX_EXEMPT = 1 << 1, TAX_ON_TOP = 1 << 2, }; std::map features; int txva{-1}; int taxExempt{-1}; int taxRate{-1}; std::string taxName; std::string taxCountry; int multiDiscount{-1}; double euroTotalPrice{0.0}; double euroDiscountAmount{0.0}; double euroDiscountedTotalPrice{0.0}; double euroDiscountedMonthlyPrice{0.0}; double euroTotalPriceNet{0.0}; double euroDiscountAmountNet{0.0}; double euroDiscountedTotalPriceNet{0.0}; double euroDiscountedMonthlyPriceNet{0.0}; std::string localCurrencyCode; std::string localCurrencySymbol; double localTotalPrice{0.0}; double localDiscountAmount{0.0}; double localDiscountedTotalPrice{0.0}; double localDiscountedMonthlyPrice{0.0}; double localTotalPriceNet{0.0}; double localDiscountAmountNet{0.0}; double localDiscountedTotalPriceNet{0.0}; double localDiscountedMonthlyPriceNet{0.0}; bool hasAlfanumCode() const override { assert(false && "DiscountCodeInfoExtended no need AlfanumCode"); return false; } }; } // namespace #endif sdk-10.11.0/include/mega/android/000077500000000000000000000000001516266226600164235ustar00rootroot00000000000000sdk-10.11.0/include/mega/android/androidFileSystem.h000066400000000000000000000310211516266226600222160ustar00rootroot00000000000000/** * @file mega/android/androidFileSystem.h * @brief Android filesystem/directory access */ #ifndef ANDROIDFILESYSTEM_H #define ANDROIDFILESYSTEM_H #include #include #include #include #include #include #include #include extern jclass fileWrapper; extern jclass integerClass; extern jclass arrayListClass; extern JavaVM* MEGAjvm; namespace mega { /* * @brief Encapsulates a Java object to provide file and directory functionalities on Android * * This class minimizes JNI calls by maintaining its instances in an LRU cache. * Rather than creating a new JNI object for every operation, previously created * instances are reused, reducing the number of JNI calls required. * * To get an instance of this class, getAndroidFileWrapper should be called * Pay attention, constructor is private */ class AndroidFileWrapper { public: ~AndroidFileWrapper(); AndroidFileWrapper() = delete; AndroidFileWrapper(const AndroidFileWrapper&) = delete; AndroidFileWrapper& operator=(const AndroidFileWrapper&) = delete; AndroidFileWrapper(AndroidFileWrapper&& other) = delete; AndroidFileWrapper& operator=(AndroidFileWrapper&& other) = delete; bool exists() const; int getFileDescriptor(bool write); std::string getName(); std::vector> getChildren(); // Check if tree exists std::shared_ptr pathExists(const std::vector& subPaths); std::optional createOrReturnElement(const std::string& element, bool create, bool isFolder); // Create child (only first level) std::shared_ptr createChild(const std::string& childName, bool isFolder); // Returns child by name (only first level) std::shared_ptr getChildByName(const std::string& name); // Remove the file associated // this FileWrapper shouldn't be used after call this method bool deleteFile(); // Remove the associated folder if it's empty // If it isn't a folder or it isn't empty, it will return false // this FileWrapper shouldn't be used after call this method bool deleteEmptyFolder(); // Rename an element. It is kept at same folder bool rename(const std::string& parentPath, const std::string& newName, bool overwrite); // Returns true if it's a folder bool isFolder(); // Returns the URI std::string getURI() const; // Returns FileWrapper parent std::shared_ptr getParent() const; // Returns the path if it's possible (/local/..) std::optional getPath(); bool isURI(); static std::shared_ptr getAndroidFileWrapper(const std::string& path); static std::shared_ptr getAndroidFileWrapper(const LocalPath& localPath, bool create, bool lastIsFolder); private: class JavaObject { public: // Note: it should be global reference JavaObject(jobject obj): mObj(obj) {} ~JavaObject() { JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); env->DeleteGlobalRef(mObj); } jobject mObj; }; class URIData { public: std::optional mIsURI; std::optional mIsFolder; std::optional mName; std::optional mPath; }; AndroidFileWrapper(const std::string& path); AndroidFileWrapper(std::shared_ptr); std::shared_ptr mJavaObject; jobject vectorToJavaList(JNIEnv* env, const std::vector& vec); std::string mURI; static constexpr char GET_ANDROID_FILE[] = "getFromUri"; static constexpr char GET_FILE_DESCRIPTOR[] = "getFileDescriptor"; static constexpr char IS_PATH[] = "isPath"; static constexpr char IS_FOLDER[] = "isFolder"; static constexpr char GET_NAME[] = "getName"; static constexpr char GET_CHILDREN_URIS[] = "getChildrenUris"; static constexpr char CHILD_EXISTS[] = "childFileExists"; static constexpr char CREATE_CHILD[] = "createChildFile"; static constexpr char GET_CHILD_BY_NAME[] = "getChildByName"; static constexpr char GET_PARENT[] = "getParentFile"; static constexpr char GET_PATH[] = "getPath"; static constexpr char DELETE_FILE[] = "deleteFile"; static constexpr char DELETE_EMPTY_FOLDER[] = "deleteFolderIfEmpty"; static constexpr char RENAME[] = "rename"; static constexpr char RENAME_OVERRIDE[] = "renameOverwrite"; static constexpr char CREATE_NESTED_PATH[] = "createNestedPath"; void setUriData(const URIData& uriData); std::optional getURIData(const std::string& uri) const; static constexpr std::size_t LRUCacheSize = 30000; static LRUCache URIDataCache; static LRUCache localPathURICache; static std::mutex URIDataCacheLock; static std::mutex localPathURICacheLock; static void setLocalPathURI(const std::string& path, const std::string& uri); static std::optional getLocalPathURI(const std::string& path); static std::shared_ptr getAndroidFileWrapperFromURI(const LocalPath& localPath, bool create, bool lastIsFolder); static std::shared_ptr getAndroidFileWrapperFromPath(const LocalPath& localPath, bool create, bool lastIsFolder); }; /** * @brief Android implementation to handle URIs */ class MEGA_API AndroidPlatformURIHelper: public PlatformURIHelper { public: bool isURI(const std::string& path) override; std::optional getName(const std::string& path) override; // Returns parent URI if it's available std::optional getParentURI(const string_type& uri) override; std::optional getPath(const string_type& uri) override; std::optional getURI(const string_type& uri, const std::vector leaves) override; private: /** * @brief Private default constructor to enforce controlled instantiation. * * The static instance mPlatformHelper is used instead of creating multiple objects. */ AndroidPlatformURIHelper(); ~AndroidPlatformURIHelper() override {} static AndroidPlatformURIHelper mPlatformHelper; }; /** * @brief Implement FileAcces functionality for Android * * For access to file from Android, required data (file descriptor, * name, is folder) are obtained with a JNI call to Android layer * Other required data, as size or creation time, are obtained * with the file descriptor */ class MEGA_API AndroidFileAccess: public FileAccess { public: bool fopen(const LocalPath&, OpenFlag flag, FSLogging, DirAccess* iteratingDir = nullptr, bool ignoreAttributes = false, bool skipcasecheck = false, LocalPath* actualLeafNameIfDifferent = nullptr) override; void fclose() override; bool fwrite(const void* buffer, unsigned long length, m_off_t position, unsigned long* numWritten = nullptr, bool* retry = nullptr) override; bool fstat(m_time_t& modified, m_off_t& size) override; bool ftruncate(m_off_t size = 0) override; void updatelocalname(const LocalPath& name, bool force) override; AndroidFileAccess(Waiter* w, int defaultfilepermissions = 0600, bool followSymLinks = true); virtual ~AndroidFileAccess(); std::shared_ptr stealFileWrapper(); // Mark this file as a sparse file. bool setSparse() override; // Retrieve this file's allocated and reported size. auto getFileSize() const -> std::optional> override; protected: void fCloseInternal(); bool sysread(void* buffer, unsigned long length, m_off_t offset, bool* retry) override; bool sysstat(m_time_t*, m_off_t*, FSLogging) override; bool sysopen(bool async, FSLogging) override; void sysclose() override; std::shared_ptr mFileWrapper; int fd{-1}; int mDefaultFilePermissions{0600}; }; /** * @brief Implement DirAccess functionality for Android * * For access to directory from Android, required data * (list of children) are obtained with a JNI call to Android layer */ class MEGA_API AndroidDirAccess: public DirAccess { public: bool dopen(LocalPath* path, FileAccess* f, bool doglob) override; bool dnext(LocalPath& path, LocalPath& name, bool followsymlinks, nodetype_t* type) override; private: std::shared_ptr mFileWrapper; std::vector> mChildren; size_t mIndex{0}; std::unique_ptr mGlobbing; }; class MEGA_API AndroidFileSystemAccess: public LinuxFileSystemAccess { public: using FileSystemAccess::getlocalfstype; std::unique_ptr newfileaccess(bool followSymLinks = true) override; std::unique_ptr newdiraccess() override; #ifdef ENABLE_SYNC DirNotify* newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter) override; #endif bool getlocalfstype(const LocalPath& path, FileSystemType& type) const override; bool getsname(const LocalPath&, LocalPath&) const override; bool renamelocal(const LocalPath&, const LocalPath&, bool = true) override; bool copylocal(const LocalPath&, const LocalPath&, m_time_t) override; bool unlinklocal(const LocalPath&) override; bool rmdirlocal(const LocalPath&) override; bool mkdirlocal(const LocalPath&, bool hidden, bool logAlreadyExistsError) override; /* On Android we cannot set mtime on files, due to insufficient permissions */ bool setmtimelocal(const LocalPath&, m_time_t) override; std::pair getmtimelocal(const LocalPath& path) override; bool chdirlocal(LocalPath&) const override; bool issyncsupported(const LocalPath&, bool&, SyncError&, SyncWarning&) override; bool expanselocalpath(const LocalPath& path, LocalPath& absolutepath) override; int getdefaultfilepermissions() override; void setdefaultfilepermissions(int) override; int getdefaultfolderpermissions() override; void setdefaultfolderpermissions(int) override; // append local operating system version information to string. // Set includeArchExtraInfo to know if the app is 32 bit running on 64 bit (on windows, that is // via the WOW subsystem) void osversion(string*, bool /*includeArchExtraInfo*/) const override; void statsid(string*) const override; AndroidFileSystemAccess() {} MEGA_DISABLE_COPY_MOVE(AndroidFileSystemAccess); ~AndroidFileSystemAccess() override {} bool cwd(LocalPath& path) const override; #ifdef ENABLE_SYNC // True if the filesystem indicated by the specified path has stable FSIDs. bool fsStableIDs(const LocalPath& path) const override; bool initFilesystemNotificationSystem() override; #endif // ENABLE_SYNC ScanResult directoryScan(const LocalPath& path, handle expectedFsid, map& known, std::vector& results, bool followSymLinks, unsigned& nFingerprinted) override; /* Not implemented yet */ bool hardLink(const LocalPath& source, const LocalPath& target) override; m_off_t availableDiskSpace(const LocalPath& drivePath) override; void addevents(Waiter*, int) override; fsfp_t fsFingerprint(const LocalPath& path) const override; static void emptydirlocal(const LocalPath&, dev_t = 0); static bool isFileWrapperActive(const FileSystemAccess* fsa); bool isFileWrapperActive() const { return fileWrapper != nullptr; } private: LocalPath getStandartPath(const LocalPath& localPath) const; bool copy(const LocalPath& oldname, const LocalPath& newName, bool overwrite); }; class AndroidDirNotify: public LinuxDirNotify { public: AndroidDirNotify(AndroidFileSystemAccess& owner, LocalNode& root, const LocalPath& rootPath); ~AndroidDirNotify() override {} AddWatchResult addWatch(LocalNode& node, const LocalPath& path, handle fsid) override; }; } #endif // ANDROIDFILESYSTEM_H sdk-10.11.0/include/mega/android/megafs.h000066400000000000000000000002001516266226600200260ustar00rootroot00000000000000#ifndef MEGA_ANDROID_FS_H #define MEGA_ANDROID_FS_H #include "mega/android/androidFileSystem.h" #endif // ! MEGA_ANDROID_FS_H sdk-10.11.0/include/mega/arguments.h000066400000000000000000000017201516266226600171610ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { // A simple arguments parser that only supports name=value or name // See unit test for the usage class Arguments { public: using size_type = std::unordered_map::size_type; bool contains(const std::string& name) const; std::string getValue(const std::string& name, const std::string& defaultValue = "") const; bool empty() const; size_type size() const; private: friend class ArgumentsParser; friend std::ostream& operator<<(std::ostream& os, const Arguments& arguments); std::unordered_map mValues; }; std::ostream& operator<<(std::ostream& os, const Arguments& arguments); class ArgumentsParser { public: static Arguments parse(int argc, char* argv[]); private: static std::pair parseOneArgument(const std::string& argument); }; }sdk-10.11.0/include/mega/attrmap.h000066400000000000000000000220641516266226600166300ustar00rootroot00000000000000/** * @file mega/attrmap.h * @brief Class for manipulating file attributes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_ATTRMAP_H #define MEGA_ATTRMAP_H 1 #include "name_id.h" #include "utils.h" #include #include namespace mega { // maps attribute names to attribute values struct attr_map : map { attr_map() {} attr_map(nameid key, string value) { (*this)[key] = std::move(value); } attr_map(map&& m) { m.swap(*this); } bool contains(nameid k) const { return this->find(k) != this->end(); } }; struct MEGA_API AttrMap { attr_map map; bool getBool(const char* name) const; /** * @brief Returns a string with the value matching the given key if it is found on the map */ std::optional getString(const std::string_view name) const; /** * @brief Returns a view on the value matching the given key if it is found on the map */ std::optional getStringView(const std::string_view name) const; // compute rough storage size unsigned storagesize(int) const; // convert nameid to string static int nameid2string(nameid, char*); static string nameid2string(nameid); // convert string to nameid static nameid string2nameid(const char*); static constexpr nameid string2nameid(const std::string_view n) { return makeNameid(n); } // export as JSON string void getjson(string*) const; std::string getjson() const; /** * @brief generates a string by exporting all [key, value] pairs as a JSON object ({"..."}) * * @example * Input: * { * [116] : 20 * [28260]: 8 * [6384743]: sha256 * [1936225125]: abcd * } * * Output: * "{"t":"20","nd":"8","alg":"sha256","shse":"abcd"}" * * @return a string by exporting all [key, value] pairs as a JSON object ({"..."}) */ std::string getJsonObject() const; /** * @brief Imports [key, value] pairs in this AttrMap (discarding any existing value) from a * string with JSON object format * ({"..."}) * * @note: The expected format of input buf must * be a well formed JSON string. e.g: "{"t":"20","nd":"8","alg":"sha256","shse":"abcd"}" * * Also note that this function does not expect nested objects inside another object in that * string. e.g: "totp":"{"t":"70", "subObject":"{"a":"1", "b":"2"}"}" * * @example * Input: * "{"t":"20","nd":"8","alg":"sha256","shse":"abcd"}" * * Result: Stores [k,v] elements in this AttrMap * { * [116] : 20 * [28260]: 8 * [6384743]: sha256 * [1936225125]: abcd * } */ void fromjsonObject(const std::string& buf); // import from JSON string void fromjson(const char* buf); /** * @brief If there is an entry in the map with the given name, a new AttrMap is generated * assuming the value is in json format. */ std::optional getNestedJsonObject(const std::string_view name) const; /** * @brief Generates a new AttrMap from JsonObject String stored in value corresponding to * specific key(parentName) of this AttrMap * * Given a key(parentName), this function searches an object name (childName) in the value of * that key (parentName) and extract that object into a new AttrMap * * @note: The expected format of AttrMap string value that corresponding to key (parentName), * must be a well formed JSON string. e.g: "n":"Pasword1 * notes","u":"myUser","pwd":"abcd","url":"www.website.com","totp":{"t":"70","nd":"8","alg":"sha1","shse":"HVR4CFHAFOWFGGFAGSA5JVTIMMPG6GMT"} * * Also note that this function does not support nested objects inside another object in that * string. e.g: "totp":"{"t":"70", "subObject":"{"a":"1", "b":"2"}"}" * * @example * Input: * { * [110] "Pasword1" * [7370605] "n":"Pasword1 * notes","u":"myUser","pwd":"abcd","url":"www.website.com","totp":{"t":"70","nd":"8","alg":"sha1","shse":"HVR4CFHAFOWFGGFAGSA5JVTIMMPG6GMT"} * } * * Output: * { * [1953461360] : * {"t":"70","nd":"8","alg":"sha1","shse":"HVR4CFHAFOWFGGFAGSA5JVTIMMPG6GMT"} * } * * In example above we can see that we want to extract "totp" object stored at [7370605] * * @param parentName key of this AttrMap where we want to extract JSON object * @param childName Object name inside Value of parentName key in this AttrMap * @return a new AttrMap from JsonObject String stored in value corresponding to * specific key(parentName) of this AttrMap */ std::optional getComplexNestedJsonObject(const std::string_view parentName, const std::string_view childName) const; // export as raw binary serialize void serialize(string*) const; // import raw binary serialize const char* unserialize(const char*, const char*); // overwrite entries in map (or remove them if the value is empty) void applyUpdates(const attr_map& updates); /** * @brief Updates or adds new fields to this object if new values are present in `updates` * * This method, unlike `applyUpdates`, handles updates on nested fields. * @note Just one nesting level is handled. * * @example * This object: * { * "a" : "hello", * "b" : "{"foo": "1", "bar": "2"}" * } * Updating with: * { * "a" : "world", * "b" : "{"bar": "3"}" * } * Final state of this object after this method: * { * "a" : "world", * "b" : "{"foo": "1", "bar": "3"}" * } * Notice the difference if the `applyUpdates` is used instead: * { * "a" : "world", * "b" : "{"bar": "3"}" * } * * @param updates The AttrMap with the new values to set in this object * @param nestedFieldKeys An array with the keys for fields that have nested objects * @note If a dynamic size for the nestedFieldKeys is required, consider changing the type to an * std::span if C++20 is available. */ template void applyUpdatesWithNestedFields(const AttrMap& updates, const std::array& nestedFieldKeys) { const auto getNestedFieldFinalStr = [this, &updates](const std::string_view field) -> std::optional { const auto nameId = AttrMap::string2nameid(field); if (!hasUpdate(nameId, updates.map) || updates.getStringView(field).value_or("").empty()) return std::optional{std::nullopt}; AttrMap complexMapUpdatesValues; assert(updates.map.contains(nameId)); // otherwise doesn't have updates complexMapUpdatesValues.fromjsonObject(updates.map.at(nameId)); AttrMap complexMapCurrentValues; if (this->map.contains(nameId)) { complexMapCurrentValues.fromjsonObject(this->map.at(nameId)); } complexMapCurrentValues.applyUpdates(complexMapUpdatesValues.map); return std::make_optional(complexMapCurrentValues.getJsonObject()); }; std::array, S> finalNestedStrs; std::transform(begin(nestedFieldKeys), end(nestedFieldKeys), begin(finalNestedStrs), getNestedFieldFinalStr); applyUpdates(updates.map); // The following could be nicer in c++23 with zip and filter for (size_t i = 0; i < nestedFieldKeys.size(); ++i) if (auto& finalStr = finalNestedStrs[i]; finalStr.has_value()) map[AttrMap::string2nameid(nestedFieldKeys[i])] = std::move(finalStr.value()); } // determine if the value of attrId will receive an update if applyUpdates() will be called for updates // (an attribute will be updated only if present among received updates; // even for removal, it should be present with an empty value) bool hasUpdate(nameid attrId, const attr_map& updates) const; // determine if attrId differs between the 2 maps bool hasDifferentValue(nameid attrId, const attr_map& other) const; // remove items from the map where the value is an empty string void removeEmptyValues(); }; } // namespace #endif sdk-10.11.0/include/mega/auto_file_handle.h000066400000000000000000000032431516266226600204400ustar00rootroot00000000000000#pragma once #include "mega/types.h" #ifdef WIN32 #include #else #include #endif namespace mega { class AutoFileHandle { #ifdef WIN32 using HandleType = HANDLE; const HandleType UNSET = INVALID_HANDLE_VALUE; #else // _WIN32 using HandleType = int; const HandleType UNSET = -1; #endif // ! _WIN32 HandleType h = UNSET; public: AutoFileHandle() {} AutoFileHandle(HandleType ih): h(ih) {} ~AutoFileHandle() { close(); } void close() { if (h != UNSET) { #ifdef WIN32 ::CloseHandle(h); #else ::close(h); #endif } h = UNSET; } AutoFileHandle& operator=(HandleType ih) { // avoid to leak a handle if changed if (ih != h) close(); h = ih; return *this; } bool isSet() const { return h != UNSET; } // implicit conversion, so can pass into OS API operator HandleType() const { return h; } HandleType* ptr() { return &h; } HandleType get() const { return h; } // Prevent copying to avoid double-close issues AutoFileHandle(const AutoFileHandle&) = delete; AutoFileHandle& operator=(const AutoFileHandle&) = delete; // Allow moving AutoFileHandle(AutoFileHandle&& other) noexcept: h(other.h) { other.h = UNSET; } AutoFileHandle& operator=(AutoFileHandle&& other) noexcept { if (this != &other) { close(); h = other.h; other.h = UNSET; } return *this; } }; } // namespace mega sdk-10.11.0/include/mega/autocomplete.h000066400000000000000000000255041516266226600176630ustar00rootroot00000000000000/** * @file autocomplete.h * @brief Win32 console I/O autocomplete support * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ // autocomplete - so far just megacli and megaclc on windows and linux. #ifndef MEGA_AUTOCOMPLETE_H #define MEGA_AUTOCOMPLETE_H 1 #define HAVE_AUTOCOMPLETE 1 #include "mega/types.h" #include #include #include namespace mega { class MEGA_API MegaClient; namespace autocomplete { struct MEGA_API ACNode; typedef std::shared_ptr ACN; struct MEGA_API ACState { struct Completion { std::string s; bool caseInsensitive = false; bool couldExtend = false; inline Completion(const std::string& str, bool b, bool b2 = true) : s(str), caseInsensitive(b), couldExtend(b2) {} }; std::vector completions; void addCompletion(const std::string& s, bool caseInsenstive = false, bool couldextend = false); void addPathCompletion(std::string&& f, const std::string& relativeRootPath, bool isFolder, char dir_sep, bool caseInsensitive); std::vector> wordPos; struct quoting { quoting(); quoting(std::string&); bool quoted = false; char quote_char = 0; void applyQuotes(std::string& s); }; struct quoted_word { quoted_word(); quoted_word(const std::string &); quoted_word(const std::string &, const quoting&); std::string s; quoting q; string getQuoted(); }; std::vector words; unsigned i = 0; bool unixStyle = false; bool atCursor() { return i+1 >= words.size(); } const quoted_word& word() { return words[i]; } bool extractflag(const string& flag); bool extractflagparam(const string& flag, string& param); // More convenient than the above. std::optional extractflagparam(const std::string& flag); ACN selectedSyntax; }; struct MEGA_API ACNode { // returns true if we should stop searching deeper than this node. Used by autoComplete virtual bool addCompletions(ACState& s) = 0; // indicate whether this node and its subnodes is a match for the state (ie, plausible intrpretation of remaining words). Used by autoExec virtual bool match(ACState& s) const = 0; // output suitable for user 'help' virtual std::ostream& describe(std::ostream& s) const = 0; virtual ~ACNode(); }; std::ostream& operator<<(std::ostream&, const ACNode&); struct MEGA_API Optional : public ACNode { ACN subnode; Optional(ACN n); bool isOptional(); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API Repeat : public ACNode { ACN subnode; Repeat(ACN n); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API Sequence : public ACNode { ACN current, next; Sequence(ACN n1, ACN n2); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API Text : public ACNode { std::string exactText; bool param; Text(const std::string& s, bool isParam); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API ExportedLink : public ACNode { bool filelink, folderlink; static bool isLink(const string& s, bool file, bool folder); ExportedLink(bool file = true, bool folder = true); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API Flag : public ACNode { std::string flagText; Flag(const std::string& s); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API Either : public ACNode { typedef std::function ExecFn; std::vector eithers; std::vector execFuncs; std::string describePrefix; Either(const std::string& describePrefix=""); void Add(ACN n); void Add(ExecFn, ACN n); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API WholeNumber : public ACNode { size_t defaultvalue; std::string description; WholeNumber(const std::string& description, size_t defaultValue); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API LocalFS : public ACNode { bool reportFiles = true; bool reportFolders = true; std::string descPref; LocalFS(bool files, bool folders, const std::string descriptionPrefix); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API MegaFS : public ACNode { MegaClient* client; ::mega::NodeHandle* cwd; bool reportFiles = true; bool reportFolders = true; std::string descPref; MegaFS(bool files, bool folders, MegaClient* a, ::mega::NodeHandle* curDirHandle, const std::string descriptionPrefix); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; struct MEGA_API MegaContactEmail : public ACNode { MegaClient* client; MegaContactEmail(MegaClient*); bool addCompletions(ACState& s) override; std::ostream& describe(std::ostream& s) const override; bool match(ACState& s) const override; }; #ifdef ENABLE_SYNC class MEGA_API BackupID : public ACNode { public: BackupID(MegaClient& client, bool onlyActive); bool addCompletions(ACState& state) override; std::ostream& describe(std::ostream& ostream) const override; bool match(ACState& state) const override; private: struct DontValidate {}; string_vector backupIDs() const; string_vector& filter(string_vector& ids, const ACState& state) const; bool match(const string_vector& ids, ACState& state) const; MegaClient& mClient; bool mOnlyActive; }; // BackupID ACN backupID(MegaClient& client, bool onlyActive = false); #endif // ENABLE_SYNC struct MEGA_API CompletionState { std::string line; std::pair wordPos; ACState::quoted_word originalWord; std::vector completions; bool unixStyle = false; int lastAppliedIndex = -1; bool active = false; bool firstPressDone = false; size_t unixListCount = 0; unsigned calcUnixColumnWidthInGlyphs(int col, int rows); const string& unixColumnEntry(int row, int col, int rows); void tidyCompletions(); }; // helper function - useful in megacli for now ACState prepACState(const std::string line, size_t insertPos, bool unixStyle); // get a list of possible strings at the current cursor position CompletionState autoComplete(const std::string line, size_t insertPos, ACN syntax, bool unixStyle); // put the next possible string or unambiguous portion thereof at the cursor position, or indicate options to the user struct CompletionTextOut { vector> stringgrid; vector columnwidths; }; void applyCompletion(CompletionState& s, bool forwards, unsigned consoleWidth, CompletionTextOut& consoleOutput); // execute the function attached to the matching syntax bool autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput, bool reportNoMatch); // functions to bulid command descriptions template ACN either(Args&&... args) { static_assert((std::is_same_v, ACN> && ...), "All arguments must be of type ACN"); auto n = std::make_shared(); (n->Add(std::forward(args)), ...); return n; } template ACN sequence(ACN n1, Args&&... args) { static_assert((std::is_same_v, ACN> && ...), "All arguments must be of type ACN"); if constexpr (sizeof...(args) == 0) { return n1; } else { static const auto sequenceBuilder = [](ACN n1, ACN n2) -> ACN { return n2 ? std::make_shared(n1, n2) : n1; }; return sequenceBuilder(n1, sequence(std::forward(args)...)); } } ACN text(const std::string s); ACN param(const std::string s); ACN flag(const std::string s); ACN opt(ACN n); ACN repeat(ACN n); ACN exportedLink(bool file = true, bool folder = true); ACN wholenumber(const std::string& description, size_t defaultValue); ACN wholenumber(size_t defaultvalue); ACN localFSPath(const std::string descriptionPrefix = ""); ACN localFSFile(const std::string descriptionPrefix = ""); ACN localFSFolder(const std::string descriptionPrefix = ""); ACN remoteFSPath(MegaClient*, ::mega::NodeHandle*, const std::string descriptionPrefix = ""); ACN remoteFSFile(MegaClient*, ::mega::NodeHandle*, const std::string descriptionPrefix = ""); ACN remoteFSFolder(MegaClient*, ::mega::NodeHandle*, const std::string descriptionPrefix = ""); ACN contactEmail(MegaClient*); }} //namespaces #endif sdk-10.11.0/include/mega/backofftimer.h000066400000000000000000000110561516266226600176130ustar00rootroot00000000000000/** * @file mega/backofftimer.h * @brief Generic timer facility with exponential backoff * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_BACKOFF_TIMER_H #define MEGA_BACKOFF_TIMER_H 1 #include "types.h" namespace mega { // generic timer facility with exponential backoff class MEGA_API BackoffTimer { dstime next; dstime delta; dstime base; PrnGen &rng; public: // reset timer void reset(); // trigger exponential backoff void backoff(); // set absolute backoff void backoff(dstime); // set absolute trigger time void set(dstime); // check if timer has elapsed bool armed() const; // arm timer bool arm(); // time left for event to become armed dstime retryin() const; // current backoff delta dstime backoffdelta(); // time of next trigger or 0 if no trigger since last backoff dstime nextset() const; // update time to wait void update(dstime*); BackoffTimer(PrnGen &rng); }; class MEGA_API BackoffTimerTracked; // This class keeps track of a group of BackoffTimerTracked, which register and deregister themselves. // Timers are in the multimap when they have non-0 non-NEVER timeouts set, giving us a much smaller group should we need to iterate it. class MEGA_API BackoffTimerGroupTracker { std::multimap timeouts; public: typedef std::multimap::iterator Iter; inline Iter add(BackoffTimerTracked* bt); inline void remove(Iter i) { timeouts.erase(i); } // Find out the soonest (non-0 and non-NEVER) timeout in the group. // For transfers, it calls set(0) on any timed out timers, as the old code did. void update(dstime* waituntil, bool transfers); }; // Just like a backoff timer, but is part of a group where we want to know the soonest (non-0) timeout in the group immediately // Also, the enable() function can be used to exclude timers when they are not relevant, while keeping the timer settings. class MEGA_API BackoffTimerTracked { bool mIsEnabled; BackoffTimer bt; BackoffTimerGroupTracker& mTracker; BackoffTimerGroupTracker::Iter mTrackerPos; void untrack(); void track(); public: BackoffTimerTracked(PrnGen &rng, BackoffTimerGroupTracker& tr); ~BackoffTimerTracked(); inline bool arm() { untrack(); bool result = bt.arm(); track(); return result; } inline void backoff() { untrack(); bt.backoff(); track(); } inline void backoff(dstime t) { untrack(); bt.backoff(t); track(); } inline void set(dstime t) { untrack(); bt.set(t); track(); } inline void update(dstime* t) { untrack(); bt.update(t); track(); } inline void reset() { untrack(); bt.reset(); track(); } inline bool armed() const { return bt.armed(); }; inline dstime nextset() const { return bt.nextset(); }; inline dstime retryin() const { return bt.retryin(); } inline void enable(bool b) { untrack(); mIsEnabled = b; track(); } inline bool enabled() { return mIsEnabled; } }; inline auto BackoffTimerGroupTracker::add(BackoffTimerTracked* bt) -> Iter { return timeouts.emplace(bt->nextset() ? bt->nextset() : NEVER, bt); } inline void BackoffTimerTracked::untrack() { if (mIsEnabled && bt.nextset() != 0 && bt.nextset() != NEVER) { mTracker.remove(mTrackerPos); mTrackerPos = BackoffTimerGroupTracker::Iter(); } } inline void BackoffTimerTracked::track() { if (mIsEnabled && bt.nextset() != 0 && bt.nextset() != NEVER) { mTrackerPos = mTracker.add(this); } } inline BackoffTimerTracked::BackoffTimerTracked(PrnGen &rng, BackoffTimerGroupTracker& tr) : mIsEnabled(true), bt(rng), mTracker(tr) { track(); } inline BackoffTimerTracked::~BackoffTimerTracked() { untrack(); } class MEGA_API TimerWithBackoff: public BackoffTimer { public: int tag; TimerWithBackoff(PrnGen &rng, int tag); }; } // namespace #endif sdk-10.11.0/include/mega/banner.h000066400000000000000000000017071516266226600164260ustar00rootroot00000000000000/** * @file mega/banner.h * @brief Banner data structure * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "types.h" namespace mega { struct BannerDetails { int id = 0; std::string title; std::string description; std::string image; std::string url; std::string backgroundImage; std::string imageLocation; int variant = 0; std::string button; }; } // namespacesdk-10.11.0/include/mega/base64.h000066400000000000000000000057061516266226600162500ustar00rootroot00000000000000/** * @file mega/base64.h * @brief modified base64 encoding/decoding * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_BASE64_H #define MEGA_BASE64_H 1 #include "types.h" namespace mega { // modified base64 encoding/decoding (unpadded, -_ instead of +/) class MEGA_API Base64 { static byte to64(byte); static byte from64(byte); public: static size_t btoa(const string&, string&); static string btoa(const string &in); // use Base64Str instead when `size` is known at compile time (more efficient) static size_t btoa(const byte*, size_t, char*); // deprecated static int atob(const string&, string&); static string atob(const string&); static int atob(const char*, byte*, int); // deprecated static void itoa(int64_t, string *); static int64_t atoi(string *); // modify a base64 string to standard conversion: // 1. Trailing(s) '=' if needed to have a "correct" length (ex: from 32 to 44) // 2. '+/' instead of '-_' static void toStandard(string& b64str); }; template struct Base64Str { // provides a way to build the C string on the stack efficiently, using minimal space enum : std::size_t { STRLEN = (BINARYSIZE * 4 + 2) / 3 }; char chars[STRLEN + 1]; // sizeof(chars) can be larger due to alignment etc Base64Str(const void* b): Base64Str(b, BINARYSIZE) {} Base64Str(const void* b, size_t size) { [[maybe_unused]] auto n = Base64::btoa(reinterpret_cast(b), size, chars); assert(n + 1 <= sizeof(chars)); } Base64Str(const handle& h): Base64Str(&h) {} operator const char* () const { return chars; } const byte* bytes() const { return reinterpret_cast(chars); } std::size_t size() const { return STRLEN; } }; // Deduction guide for convenience. template Base64Str(const T* data) -> Base64Str; // lowercase base32 encoding class MEGA_API Base32 { static byte to32(byte); static byte from32(byte); public: static int btoa(const byte*, int, char*); static int atob(const char*, byte*, int); }; class MEGA_API URLCodec { static bool ishexdigit(char c); public: static bool issafe(char c); static void escape(string* plain, string* escaped); static void unescape(string* escaped, string* plain); }; } // namespace #endif sdk-10.11.0/include/mega/canceller.h000066400000000000000000000017471516266226600171150ustar00rootroot00000000000000/** * @file mega/canceller.h * @brief Mega atomic canceller to be used for MegaClient code that should stop in order for app's * requests to be reached. Meant to be used for login / locallogout app requests. */ #pragma once #include #include namespace mega { // 64-bit epoch. Wrap period is ~5.8e5 years at 1 bump/microsecond. using cancel_epoch_t = std::uint64_t; // Use this as a starting point reference for a further cancel_epoch_bump() cancel_epoch_t cancel_epoch_snapshot() noexcept; // Bump the global epoch: invalidates all inflight snapshots. void cancel_epoch_bump() noexcept; struct ScopedCanceller { ScopedCanceller(const ScopedCanceller&) = delete; ScopedCanceller& operator=(const ScopedCanceller&) = delete; explicit ScopedCanceller(const cancel_epoch_t snapshot): m_snapshot{snapshot} {} ScopedCanceller() noexcept; bool triggered() const noexcept; private: cancel_epoch_t m_snapshot{}; }; } // namespace megasdk-10.11.0/include/mega/command.h000066400000000000000000002013271516266226600165770ustar00rootroot00000000000000/** * @file mega/command.h * @brief Request command component * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_COMMAND_H #define MEGA_COMMAND_H 1 #include "account.h" #include "http.h" #include "json.h" #include "network_connectivity_test_helpers.h" #include "node.h" #include "nodemanager.h" #include "setandelement.h" #include "textchat.h" #include "types.h" #include #include #include namespace mega { struct JSON; struct MegaApp; // request command component class MEGA_API Command { error result; protected: bool canceled; JSONWriter jsonWriter; bool mRead = false;// if json has already been read bool loadIpsFromJson(std::vector& ips, JSON& json); void cacheresolvedurls(const std::string& command, const std::vector& urls, const std::vector& ips); // True if the command can be sent to the API lockless CS channel // Updated internally by each command. bool mLockless = false; public: MegaClient* client; // non-owning int tag; string commandStr; // some commands can only succeed if they are in their own batch. eg. smss, when the account is blocked pending validation bool batchSeparately; // Returns true if the command can use the lockless CS channel bool isLockless() const; // true if the command returns strings, arrays or objects, but a seqtag is (optionally) also required. In example: ["seqtag"/error, ] bool mSeqtagArray = false; // filters for JSON parsing in streaming std::map mFilters; void cmd(const char*); void notself(MegaClient*); virtual void cancel(void); void arg(const char*, const char*, int = 1); void arg(const char*, const byte*, size_t); void arg(const char*, NodeHandle); void arg(const char*, m_off_t); void addcomma(); void appendraw(const char*); void appendraw(const char*, int); void beginarray(); void beginarray(const char*); void endarray(); void beginobject(); void beginobject(const char*); void endobject(); void element(int); void element(handle, size_t = sizeof(handle)); void element(const byte*, size_t); void element(const char*); void openobject(); void closeobject(); // `st` seqtags are always extracted before the command's procresult() is called enum Outcome { CmdError, // The reply was an error, already extracted from the JSON. The error code may have been 0 (API_OK) CmdArray, // The reply was an array, and we have already entered it CmdObject, // the reply was an object, and we have already entered it CmdItem }; // The reply was none of the above - so a string struct Result { Outcome mOutcome = CmdError; Error mError = API_OK; Result(Outcome o, Error e = API_OK) : mOutcome(o), mError(e) {} bool succeeded() const { return mOutcome != CmdError || error(mError) == API_OK; } bool hasJsonArray() const { // true if there is JSON Array to process (and we have already entered it) (note some commands that respond with cmdseq plus JSON, so this can happen for actionpacket results) return mOutcome == CmdArray; } bool hasJsonObject() const { // true if there is JSON Object to process (and we have already entered it) (note some commands that respond with cmdseq plus JSON, so this can happen for actionpacket results) return mOutcome == CmdObject; } bool hasJsonItem() const { // true if there is JSON to process but it's not an object or array (note some commands that respond with cmdseq plus JSON, so this can happen for actionpacket results) return mOutcome == CmdItem; } Error errorOrOK() const { assert(mOutcome == CmdError); return mOutcome == CmdError ? mError : Error(API_EINTERNAL); } bool wasErrorOrOK() const { return mOutcome == CmdError; } bool wasError(error e) const { return mOutcome == CmdError && error(mError) == e; } bool wasStrictlyError() const { return mOutcome == CmdError && error(mError) != API_OK; } }; virtual bool procresult(Result, JSON&) = 0; // json for the command is usually pre-generated but can be calculated just before sending, by overriding this function virtual const char* getJSON(MegaClient* clientOfRequest); Command(); virtual ~Command(); bool checkError(Error &errorDetails, JSON &json); void addToNodePendingCommands(Node* n); void removeFromNodePendingCommands(NodeHandle h); #ifdef ENABLE_CHAT // create json structure for scheduled meetings (mcsmp command) void createSchedMeetingJson(const ScheduledMeeting* schedMeeting); #endif MEGA_DEFAULT_COPY_MOVE(Command) }; // list of new file attributes to write // file attribute put struct MEGA_API CommandPutFA : public Command { using Cb = std::function &/*ips*/)>; private: Cb mCompletion; NodeOrUploadHandle th; // if th is UNDEF, just report the handle back to the client app rather than attaching to a node public: bool procresult(Result, JSON&) override; CommandPutFA(NodeOrUploadHandle, fatype, bool forceSSL, int tag, size_t size_only, bool getIP = true, Cb&& completion = nullptr); }; struct MEGA_API HttpReqFA : public HttpReq, public std::enable_shared_from_this { NodeOrUploadHandle th; // if th is UNDEF, just report the handle back to the client app rather than attaching to a node fatype type; m_off_t progressreported; // progress information virtual m_off_t transferred(MegaClient*) override; // either supply only size (to just get the URL) or supply only the data for auto-upload (but not both) HttpReqFA(NodeOrUploadHandle, fatype, int tag, std::unique_ptr faData, bool getIP, MegaClient* client); // generator function because the code allows for retries std::function getURLForFACmd; int tag = 0; private: std::unique_ptr data; }; class MEGA_API CommandGetFA : public Command { int part; public: bool procresult(Result, JSON&) override; CommandGetFA(MegaClient *client, int, handle); }; class MEGA_API CommandPrelogin : public Command { public: using Completion = std::function; Completion mCompletion; string email; public: bool procresult(Result, JSON&) override; CommandPrelogin(MegaClient* client, Completion completion, const char* email); }; class MEGA_API CommandLogin : public Command { public: using Completion = std::function; private: Completion mCompletion; bool checksession; int sessionversion; public: bool procresult(Result, JSON&) override; CommandLogin(MegaClient* client, Completion completion, const char* email, const byte* emailhash, size_t emailhashsize, const byte* sessionkey = NULL, int csessionversion = 0, const char* pin = NULL); }; class MEGA_API CommandSetMasterKey : public Command { byte newkey[SymmCipher::KEYLENGTH]; string salt; public: bool procresult(Result, JSON&) override; CommandSetMasterKey(MegaClient*, const byte*, const byte*, size_t, const byte* clientrandomvalue = NULL, const char* = NULL, string* = NULL); }; class MEGA_API CommandAccountVersionUpgrade : public Command { vector mEncryptedMasterKey; string mSalt; std::function mCompletion; public: bool procresult(Result, JSON&) override; CommandAccountVersionUpgrade(vector&& clRandValue, vector&&encMKey, string&& hashedAuthKey, string&& salt, int ctag, std::function completion); }; class MEGA_API CommandCreateEphemeralSession : public Command { byte pw[SymmCipher::KEYLENGTH]; public: bool procresult(Result, JSON&) override; CommandCreateEphemeralSession(MegaClient*, const byte*, const byte*, const byte*); }; class MEGA_API CommandResumeEphemeralSession : public Command { byte pw[SymmCipher::KEYLENGTH]; handle uh; public: bool procresult(Result, JSON&) override; CommandResumeEphemeralSession(MegaClient*, handle, const byte*, int); }; class MEGA_API CommandCancelSignup : public Command { public: bool procresult(Result, JSON&) override; CommandCancelSignup(MegaClient*); }; class MEGA_API CommandWhyAmIblocked : public Command { public: bool procresult(Result, JSON&) override; CommandWhyAmIblocked(MegaClient*); }; class MEGA_API CommandSendSignupLink2 : public Command { public: bool procresult(Result, JSON&) override; CommandSendSignupLink2(MegaClient*, const char*, const char*); CommandSendSignupLink2(MegaClient*, const char*, const char*, byte *, byte*, byte*, int ctag); }; class MEGA_API CommandConfirmSignupLink2 : public Command { public: bool procresult(Result, JSON&) override; CommandConfirmSignupLink2(MegaClient*, const byte*, unsigned); }; class MEGA_API CommandSetKeyPair : public Command { public: bool procresult(Result, JSON&) override; CommandSetKeyPair(MegaClient*, const byte*, unsigned, const byte*, unsigned); }; // set visibility class MEGA_API CommandRemoveContact : public Command { string email; visibility_t v; public: using Completion = std::function; bool procresult(Result, JSON&) override; CommandRemoveContact(MegaClient*, const char*, visibility_t, Completion completion = nullptr); private: void doComplete(error e); Completion mCompletion; }; // set user attributes with version class MEGA_API CommandPutMultipleUAVer : public Command { userattr_map attrs; // attribute values std::function mCompletion; public: CommandPutMultipleUAVer(MegaClient*, const userattr_map *attrs, int, std::function completion = nullptr); bool procresult(Result, JSON&) override; }; // set user attributes with version class MEGA_API CommandPutUAVer : public Command { attr_t at; // attribute type string av; // attribute value std::function mCompletion; public: CommandPutUAVer(MegaClient*, attr_t, const byte*, unsigned, int, std::function completion = nullptr); bool procresult(Result, JSON&) override; }; // set user attributes class MEGA_API CommandPutUA : public Command { attr_t at; // attribute type string av; // attribute value std::function mCompletion; public: CommandPutUA(MegaClient*, attr_t at, const byte*, unsigned, int, handle = UNDEF, int = 0, int64_t = 0, std::function completion = nullptr); bool procresult(Result, JSON&) override; }; class MEGA_API CommandGetUA : public Command { string uid; attr_t at; // attribute type string ph; // public handle for preview mode, in B64 std::function mCompletion; bool isFromChatPreview() { return !ph.empty(); } public: typedef std::function CompletionErr; typedef std::function CompletionBytes; typedef std::function, attr_t)> CompletionTLV; CommandGetUA(MegaClient*, const char*, attr_t, const char*, int, CompletionErr completionErr, CompletionBytes completionBytes, CompletionTLV completionTLV); bool procresult(Result, JSON&) override; private: CompletionErr mCompletionErr; CompletionBytes mCompletionBytes; CompletionTLV mCompletionTLV; }; #ifdef DEBUG class MEGA_API CommandDelUA : public Command { string an; public: CommandDelUA(MegaClient*, const char*); bool procresult(Result, JSON&) override; }; #endif class MEGA_API CommandSendDevCommand : public Command { public: bool procresult(Result result, JSON& json) override; CommandSendDevCommand(MegaClient* client, const char* command, const char* email = NULL, long long q = 0, int bs = 0, int us = 0, const char* cp = nullptr); }; class MEGA_API CommandGetUserEmail : public Command { public: bool procresult(Result, JSON&) override; CommandGetUserEmail(MegaClient*, const char *uid); }; // reload nodes/shares/contacts class MEGA_API CommandFetchNodes : public Command { bool mLoadSyncs = false; const char* getJSON(MegaClient* clientOfRequest) override; public: bool procresult(Result, JSON&) override; bool parsingFinished(); CommandFetchNodes(MegaClient*, int tag, bool nocache, bool loadSyncs, const NodeHandle partialFetchRoot = NodeHandle{}); ~CommandFetchNodes(); protected: handle mPreviousHandleForAlert = UNDEF; NodeManager::MissingParentNodes mMissingParentNodes; // Field to temporarily save the received scsn handle mScsn; // sequence-tag, saved temporary while processing the response (it's received before nodes) string mSt; std::unique_lock mNodeTreeIsChanging; bool mFirstChunkProcessed = false; }; // update own node keys class MEGA_API CommandNodeKeyUpdate : public Command { public: CommandNodeKeyUpdate(MegaClient*, handle_vector*); bool procresult(Result, JSON&) override { return true; } }; class MEGA_API CommandKeyCR : public Command { bool procresult(Result, JSON&) override { return true; } public: CommandKeyCR(MegaClient*, sharedNode_vector*, sharedNode_vector*, const char*); }; class MEGA_API CommandMoveNode : public Command { public: using Completion = std::function; private: NodeHandle h; NodeHandle pp; // previous parent NodeHandle np; // new parent bool syncop; bool mCanChangeVault; syncdel_t syncdel; Completion completion; public: bool procresult(Result, JSON&) override; CommandMoveNode(MegaClient*, std::shared_ptr, std::shared_ptr, syncdel_t, NodeHandle prevParent, Completion&& c, bool canChangeVault = false); }; class MEGA_API CommandSingleKeyCR : public Command { public: CommandSingleKeyCR(handle, handle, const byte*, size_t); bool procresult(Result, JSON&) override { return true; } }; class MEGA_API CommandDelNode : public Command { NodeHandle h; NodeHandle parent; std::function mResultFunction; public: bool procresult(Result, JSON&) override; CommandDelNode(MegaClient*, NodeHandle, bool keepversions, int tag, std::function&&, bool canChangeVault = false); }; class MEGA_API CommandDelVersions : public Command { public: bool procresult(Result, JSON&) override; CommandDelVersions(MegaClient*); }; class MEGA_API CommandKillSessions : public Command { handle h; public: bool procresult(Result, JSON&) override; CommandKillSessions(MegaClient*, handle); CommandKillSessions(MegaClient*); }; class MEGA_API CommandLogout : public Command { bool incrementedCount = false; const char* getJSON(MegaClient* clientOfRequest) override; public: using Completion = std::function; bool procresult(Result, JSON&) override; CommandLogout(MegaClient* client, Completion completion, bool keepSyncConfigsFile); private: Completion mCompletion; bool mKeepSyncConfigsFile; }; class MEGA_API CommandPubKeyRequest : public Command { User* u; public: bool procresult(Result, JSON&) override; void invalidateUser(); CommandPubKeyRequest(MegaClient*, User*); }; class MEGA_API CommandDirectRead : public Command { DirectReadNode* drn; public: void cancel() override; bool procresult(Result, JSON&) override; CommandDirectRead(MegaClient *client, DirectReadNode*); }; class MEGA_API CommandGetFile : public Command { using Cb = std::function& /*urls*/, const std::vector& /*ips*/, const std::string& /*fileHandle*/)>; Cb mCompletion; void callFailedCompletion (const Error& e); byte filekey[FILENODEKEYLENGTH]; int mFileKeyType; // as expected by SymmCipher::setKey public: // notice: cancelation will entail that mCompletion will not be called void cancel() override; bool procresult(Result, JSON&) override; CommandGetFile(MegaClient* client, const byte* key, size_t keySize, bool undelete, handle h, bool p, const char* privateauth = nullptr, const char* publicauth = nullptr, const char* chatauth = nullptr, bool singleUrl = false, bool forceSSL = true, Cb&& completion = nullptr); }; class MEGA_API CommandPutFile : public Command { TransferSlot* tslot; public: void cancel() override; bool procresult(Result, JSON&) override; CommandPutFile(MegaClient* client, TransferSlot*); }; class MEGA_API CommandGetPutUrl : public Command { using Cb = std::function &/*ips*/)>; Cb mCompletion; public: bool procresult(Result, JSON&) override; CommandGetPutUrl(m_off_t size, bool forceSSL, bool getIP, Cb completion); }; class MEGA_API CommandAttachFA : public Command { handle h; fatype type; public: bool procresult(Result, JSON&) override; // use this one for attribute blobs CommandAttachFA(MegaClient*, handle, fatype, handle, int); // use this one for numeric 64 bit attributes (which must be pre-encrypted with XXTEA) // multiple attributes can be added at once, encryptedAttributes format "*/*" // only the fatype specified will be notified back to the app CommandAttachFA(MegaClient*, handle, fatype, const std::string& encryptedAttributes, int); }; class MEGA_API CommandPutNodes: public Command { public: using Completion = std::function&, bool targetOverride, int tag, const std::map& fileHandles)>; private: friend class MegaClient; vector nn; targettype_t type; putsource_t source; bool emptyResponse = false; NodeHandle targethandle; Completion mResultFunction; void removePendingDBRecordsAndTempFiles(); void performAppCallback(Error e, vector&, bool targetOverride = false, const std::map& fileHandles = {}); public: bool procresult(Result, JSON&) override; CommandPutNodes(MegaClient*, NodeHandle, const char*, VersioningOption, vector&&, int, putsource_t, const char* cauth, Completion&&, bool canChangeVault, const std::string& customerIpPort, std::optional pitag = std::nullopt); }; class MEGA_API CommandSetAttr: public Command { public: using Completion = std::function; private: NodeHandle h; // It's defined here to avoid node will be destroyed and Node::mPendingChanges will be missed std::shared_ptr mNode; attr_map mAttrMapUpdates; error generationError; bool mCanChangeVault; const char* getJSON(MegaClient* clientOfRequest) override; Completion completion; public: bool procresult(Result, JSON&) override; // Apply the internal attr_map updates to the provided attrMap void applyUpdatesTo(AttrMap& attrMap) const; CommandSetAttr(MegaClient*, std::shared_ptr, attr_map&& attrMapUpdates, Completion&& c, bool canChangeVault); }; class MEGA_API CommandSetShare : public Command { handle sh; accesslevel_t access; string msg; string personal_representation; bool mWritable = false; std::function completion; bool procuserresult(MegaClient*, JSON&); public: bool procresult(Result, JSON&) override; CommandSetShare(MegaClient*, std::shared_ptr, User*, accesslevel_t, bool, const char*, bool writable, const char*, int tag, std::function f); }; using CommandPendingKeysReadCompletion = std::function>>)>; class MEGA_API CommandPendingKeys : public Command { public: bool procresult(Result, JSON&) override; // Read pending keys CommandPendingKeys(MegaClient*, CommandPendingKeysReadCompletion); // Delete pending keys CommandPendingKeys(MegaClient*, std::string, std::function); // Send key CommandPendingKeys(MegaClient*, handle user, handle share, byte *key, std::function); protected: std::function mCompletion; CommandPendingKeysReadCompletion mReadCompletion; }; class MEGA_API CommandGetUserData : public Command { public: bool procresult(Result, JSON&) override; CommandGetUserData( MegaClient*, int tag, std::function&&, error)>); protected: void parseUserAttribute(JSON& json, std::string& value, std::string &version, bool asciiToBinary = true); bool updatePrivateEncryptedUserAttribute(User* u, const std::string& value, const std::string& version, mega::attr_t at); bool parseDiscountCodes(JSON& json, std::vector& dciList); std::function&&, error)> mCompletion; }; class MEGA_API CommandGetMiscFlags : public Command { public: bool procresult(Result, JSON&) override; CommandGetMiscFlags(MegaClient*); }; class MEGA_API CommandABTestActive : public Command { public: using Completion = std::function; bool procresult(Result, JSON&) override; CommandABTestActive(MegaClient*, const string& tag, Completion completion); private: Completion mCompletion; }; class MEGA_API CommandSetPendingContact : public Command { opcactions_t action; string temail; // target email public: using Completion = std::function; bool procresult(Result, JSON&) override; CommandSetPendingContact(MegaClient*, const char*, opcactions_t, const char* = NULL, const char* = NULL, handle = UNDEF, Completion completion = nullptr); private: void doComplete(handle handle, error e, opcactions_t actions); Completion mCompletion; }; class MEGA_API CommandUpdatePendingContact : public Command { ipcactions_t action; public: using Completion = std::function; bool procresult(Result, JSON&) override; CommandUpdatePendingContact(MegaClient*, handle, ipcactions_t, Completion completion = nullptr); private: void doComplete(error e, ipcactions_t actions); Completion mCompletion; }; class MEGA_API CommandGetUserQuota : public Command { std::shared_ptr details; bool mStorage; bool mTransfer; bool mPro; std::function, Error)> mCompletion; public: bool procresult(Result, JSON&) override; CommandGetUserQuota(MegaClient*, std::shared_ptr, bool, bool, bool, int, std::function, Error)> = {}); private: bool readSubscriptions(JSON* j); bool readPlans(JSON* j); void processPlans(); }; class MEGA_API CommandQueryTransferQuota : public Command { public: bool procresult(Result, JSON&) override; CommandQueryTransferQuota(MegaClient*, m_off_t size); }; class MEGA_API CommandGetUserTransactions : public Command { std::shared_ptr details; public: bool procresult(Result, JSON&) override; CommandGetUserTransactions(MegaClient*, std::shared_ptr); }; class MEGA_API CommandGetUserPurchases : public Command { std::shared_ptr details; public: bool procresult(Result, JSON&) override; CommandGetUserPurchases(MegaClient*, std::shared_ptr); }; class MEGA_API CommandGetUserSessions : public Command { std::shared_ptr details; public: bool procresult(Result, JSON&) override; CommandGetUserSessions(MegaClient*, std::shared_ptr); }; class MEGA_API CommandSetPH : public Command { public: using CompletionType = std::function; private: handle h; m_time_t ets; bool mWritable = false; bool mDeleting = false; std::string mEncryptionKeyForShareKey; // Base64 string CompletionType mCompletion; void completion(Error, handle nodhandle, handle); public: bool procresult(Result, JSON&) override; CommandSetPH(MegaClient*, Node*, int, m_time_t, bool writable, bool megaHosted, int ctag, CompletionType f); }; class MEGA_API CommandGetPH : public Command { handle ph; byte key[FILENODEKEYLENGTH]; int op; // (op=0 -> download, op=1 fetch data) bool havekey; public: bool procresult(Result, JSON&) override; CommandGetPH(MegaClient*, handle, const byte*, int); }; class MEGA_API CommandPurchaseAddItem : public Command { public: bool procresult(Result, JSON&) override; CommandPurchaseAddItem(MegaClient*, int, handle, unsigned, const char*, unsigned, const char*, handle = UNDEF, int = 0, int64_t = 0); }; class MEGA_API CommandPurchaseCheckout : public Command { public: bool procresult(Result, JSON&) override; CommandPurchaseCheckout(MegaClient*, int); }; class MEGA_API CommandEnumerateQuotaItems : public Command { static constexpr unsigned int INVALID_TEST_CATEGORY = 0; static constexpr unsigned int NO_TRIAL_DAYS = 0; public: bool procresult(Result, JSON&) override; CommandEnumerateQuotaItems(const std::optional& countryCode, MegaClient*); }; class MEGA_API CommandSubmitPurchaseReceipt : public Command { public: bool procresult(Result, JSON&) override; CommandSubmitPurchaseReceipt(MegaClient*, int, const char*, handle = UNDEF, int = 0, int64_t = 0); }; class MEGA_API CommandCreditCardStore : public Command { /* 'a':'ccs', // credit card store 'cc':, 'last4':, 'expm':, 'expy':, 'hash': */ public: bool procresult(Result, JSON&) override; CommandCreditCardStore(MegaClient*, const char *, const char *, const char *, const char *, const char *); }; class MEGA_API CommandCreditCardQuerySubscriptions : public Command { public: bool procresult(Result, JSON&) override; CommandCreditCardQuerySubscriptions(MegaClient*); }; class MEGA_API CommandCreditCardCancelSubscriptions : public Command { public: enum class CanContact { No = 0, Yes = 1 }; class CancelSubscription { public: CancelSubscription(const char* reason, const char* id, int canContact); CancelSubscription(std::vector>&& reasons, const char* id, int canContact); template const T* getReasoning() const { return std::get_if(&mReasoning); } const std::string& getId() const { return mId; } bool canContact() const { return mCanContact == CanContact::Yes; } private: // Can be empty std::variant>> mReasoning; // Can be empty which means all subscriptions std::string mId; CanContact mCanContact{CanContact::No}; }; bool procresult(Result, JSON&) override; CommandCreditCardCancelSubscriptions(MegaClient*, const CancelSubscription& cancelSubscription); }; class MEGA_API CommandCopySession : public Command { public: bool procresult(Result, JSON&) override; CommandCopySession(MegaClient*); }; class MEGA_API CommandGetPaymentMethods : public Command { public: bool procresult(Result, JSON&) override; CommandGetPaymentMethods(MegaClient*); }; class MEGA_API CommandSendReport : public Command { public: bool procresult(Result, JSON&) override; CommandSendReport(MegaClient*, const char *, const char *, const char *); }; class MEGA_API CommandSendEvent : public Command { public: bool procresult(Result, JSON&) override; CommandSendEvent(MegaClient*, int, const char *, bool = false, const char * = nullptr); }; class MEGA_API CommandSupportTicket : public Command { public: bool procresult(Result, JSON&) override; CommandSupportTicket(MegaClient*, const char *message, int type = 1); // by default, 1:technical_issue }; class MEGA_API CommandCleanRubbishBin : public Command { public: bool procresult(Result, JSON&) override; CommandCleanRubbishBin(MegaClient*); }; class MEGA_API CommandGetRecoveryLink : public Command { public: bool procresult(Result, JSON&) override; CommandGetRecoveryLink(MegaClient*, const char *, int, const char* = NULL); }; class MEGA_API CommandQueryRecoveryLink : public Command { public: bool procresult(Result, JSON&) override; CommandQueryRecoveryLink(MegaClient*, const char*); }; class MEGA_API CommandGetPrivateKey : public Command { public: bool procresult(Result, JSON&) override; CommandGetPrivateKey(MegaClient*, const char*); }; class MEGA_API CommandConfirmRecoveryLink : public Command { public: bool procresult(Result, JSON&) override; CommandConfirmRecoveryLink(MegaClient*, const char*, const byte*, size_t, const byte*, const byte*, const byte*); }; class MEGA_API CommandConfirmCancelLink : public Command { public: bool procresult(Result, JSON&) override; CommandConfirmCancelLink(MegaClient *, const char *); }; class MEGA_API CommandResendVerificationEmail : public Command { public: bool procresult(Result, JSON&) override; CommandResendVerificationEmail(MegaClient *); }; class MEGA_API CommandResetSmsVerifiedPhoneNumber : public Command { public: bool procresult(Result, JSON&) override; CommandResetSmsVerifiedPhoneNumber(MegaClient *); }; class MEGA_API CommandValidatePassword : public Command { public: bool procresult(Result, JSON&) override; CommandValidatePassword(MegaClient*, const char*, const vector&); }; class MEGA_API CommandGetEmailLink : public Command { public: bool procresult(Result, JSON&) override; CommandGetEmailLink(MegaClient*, const char*, int, const char *pin = NULL); }; class MEGA_API CommandConfirmEmailLink : public Command { string email; bool replace; public: bool procresult(Result, JSON&) override; CommandConfirmEmailLink(MegaClient*, const char*, const char *, const byte *, bool); }; class MEGA_API CommandGetLocalSSLCertificate : public Command { public: bool procresult(Result, JSON&) override; CommandGetLocalSSLCertificate(MegaClient*); }; #ifdef ENABLE_CHAT class MEGA_API CommandChatCreate : public Command { userpriv_vector *chatPeers; bool mPublicChat; string mTitle; string mUnifiedKey; bool mMeeting; ChatOptions mChatOptions; std::unique_ptr mSchedMeeting; public: bool procresult(Result, JSON&) override; CommandChatCreate(MegaClient*, bool group, bool publicchat, const userpriv_vector*, const string_map* ukm = NULL, const char* title = NULL, bool meetingRoom = false, int chatOptions = ChatOptions::kEmpty, const ScheduledMeeting* schedMeeting = nullptr); ~CommandChatCreate(); }; typedef std::function CommandSetChatOptionsCompletion; class MEGA_API CommandSetChatOptions : public Command { handle mChatid; int mOption; bool mEnabled; CommandSetChatOptionsCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandSetChatOptions(MegaClient*, handle, int option, bool enabled, CommandSetChatOptionsCompletion completion); }; class MEGA_API CommandChatInvite : public Command { handle chatid; handle uh; privilege_t priv; string title; public: bool procresult(Result, JSON&) override; CommandChatInvite(MegaClient*, handle, handle uh, privilege_t, const char *unifiedkey = NULL, const char *title = NULL); }; class MEGA_API CommandChatRemove : public Command { handle chatid; handle uh; public: bool procresult(Result, JSON&) override; CommandChatRemove(MegaClient*, handle, handle uh); }; class MEGA_API CommandChatURL : public Command { public: bool procresult(Result, JSON&) override; CommandChatURL(MegaClient*, handle); }; class MEGA_API CommandChatGrantAccess : public Command { handle chatid; handle h; handle uh; public: bool procresult(Result, JSON&) override; CommandChatGrantAccess(MegaClient*, handle, handle, const char *); }; class MEGA_API CommandChatRemoveAccess : public Command { handle chatid; handle h; handle uh; public: bool procresult(Result, JSON&) override; CommandChatRemoveAccess(MegaClient*, handle, handle, const char *); }; class MEGA_API CommandChatUpdatePermissions : public Command { handle chatid; handle uh; privilege_t priv; public: bool procresult(Result, JSON&) override; CommandChatUpdatePermissions(MegaClient*, handle, handle, privilege_t); }; class MEGA_API CommandChatTruncate : public Command { handle chatid; public: bool procresult(Result, JSON&) override; CommandChatTruncate(MegaClient*, handle, handle); }; class MEGA_API CommandChatSetTitle : public Command { handle chatid; string title; public: bool procresult(Result, JSON&) override; CommandChatSetTitle(MegaClient*, handle, const char *); }; class MEGA_API CommandChatPresenceURL : public Command { public: bool procresult(Result, JSON&) override; CommandChatPresenceURL(MegaClient*); }; class MEGA_API CommandRegisterPushNotification : public Command { public: bool procresult(Result, JSON&) override; CommandRegisterPushNotification(MegaClient*, int, const char*); }; class MEGA_API CommandArchiveChat : public Command { public: bool procresult(Result, JSON&) override; CommandArchiveChat(MegaClient*, handle chatid, bool archive); protected: handle mChatid; bool mArchive; }; class MEGA_API CommandSetChatRetentionTime : public Command { public: bool procresult(Result, JSON&) override; CommandSetChatRetentionTime(MegaClient*, handle , unsigned); protected: handle mChatid; }; class MEGA_API CommandRichLink : public Command { public: bool procresult(Result, JSON&) override; CommandRichLink(MegaClient *client, const char *url); }; class MEGA_API CommandChatLink : public Command { public: bool procresult(Result, JSON&) override; CommandChatLink(MegaClient*, handle chatid, bool del, bool createifmissing); protected: bool mDelete; }; class MEGA_API CommandChatLinkURL : public Command { public: bool procresult(Result, JSON&) override; CommandChatLinkURL(MegaClient*, handle publichandle); }; class MEGA_API CommandChatLinkClose : public Command { public: bool procresult(Result, JSON&) override; CommandChatLinkClose(MegaClient*, handle chatid, const char *title); protected: handle mChatid; string mTitle; }; class MEGA_API CommandChatLinkJoin : public Command { public: bool procresult(Result, JSON&) override; CommandChatLinkJoin(MegaClient*, handle publichandle, const char *unifiedkey); }; #endif class MEGA_API CommandGetMegaAchievements : public Command { AchievementsDetails* details; public: bool procresult(Result, JSON&) override; CommandGetMegaAchievements(MegaClient*, AchievementsDetails *details, bool registered_user = true); }; class MEGA_API CommandMediaCodecs : public Command { public: typedef void(*Callback)(MegaClient* client, JSON& json, int codecListVersion); bool procresult(Result, JSON&) override; CommandMediaCodecs(MegaClient*, Callback ); private: Callback callback; }; class MEGA_API CommandContactLinkCreate : public Command { public: bool procresult(Result, JSON&) override; CommandContactLinkCreate(MegaClient*, bool); }; class MEGA_API CommandContactLinkQuery : public Command { public: bool procresult(Result, JSON&) override; CommandContactLinkQuery(MegaClient*, handle); }; class MEGA_API CommandContactLinkDelete : public Command { public: bool procresult(Result, JSON&) override; CommandContactLinkDelete(MegaClient*, handle); }; class MEGA_API CommandKeepMeAlive : public Command { public: bool procresult(Result, JSON&) override; CommandKeepMeAlive(MegaClient*, int, bool = true); }; class MEGA_API CommandMultiFactorAuthSetup : public Command { public: bool procresult(Result, JSON&) override; CommandMultiFactorAuthSetup(MegaClient*, const char* = NULL); }; class MEGA_API CommandMultiFactorAuthCheck : public Command { public: bool procresult(Result, JSON&) override; CommandMultiFactorAuthCheck(MegaClient*, const char*); }; class MEGA_API CommandMultiFactorAuthDisable : public Command { public: bool procresult(Result, JSON&) override; CommandMultiFactorAuthDisable(MegaClient*, const char*); }; class MEGA_API CommandGetPSA : public Command { public: bool procresult(Result, JSON&) override; CommandGetPSA(bool urlSupport, MegaClient*); }; class MEGA_API CommandFetchTimeZone : public Command { public: bool procresult(Result, JSON&) override; CommandFetchTimeZone(MegaClient*, const char *timezone, const char *timeoffset); }; class MEGA_API CommandSetLastAcknowledged: public Command { public: bool procresult(Result, JSON&) override; CommandSetLastAcknowledged(MegaClient*); }; class MEGA_API CommandSMSVerificationSend : public Command { public: bool procresult(Result, JSON&) override; // don't request if it's definitely not a phone number static bool isPhoneNumber(const string& s); CommandSMSVerificationSend(MegaClient*, const string& phoneNumber, bool reVerifyingWhitelisted); }; class MEGA_API CommandSMSVerificationCheck : public Command { public: bool procresult(Result, JSON&) override; // don't request if it's definitely not a verification code static bool isVerificationCode(const string& s); CommandSMSVerificationCheck(MegaClient*, const string& code); }; class MEGA_API CommandGetCountryCallingCodes : public Command { public: bool procresult(Result, JSON&) override; explicit CommandGetCountryCallingCodes(MegaClient* client); }; class MEGA_API CommandFolderLinkInfo: public Command { handle ph = UNDEF; public: bool procresult(Result, JSON&) override; CommandFolderLinkInfo(MegaClient*, handle); }; class MEGA_API CommandBackupPut : public Command { std::function mCompletion; public: bool procresult(Result, JSON&) override; enum SPState { STATE_NOT_INITIALIZED, ACTIVE = 1, // Working fine (enabled) FAILED = 2, // Failed (permanently disabled) TEMPORARY_DISABLED = 3, // Temporarily disabled due to a transient situation (e.g: account blocked). Will be resumed when the condition passes DISABLED = 4, // Disabled by the user PAUSE_UP = 5, // Active but upload transfers paused in the SDK PAUSE_DOWN = 6, // Active but download transfers paused in the SDK PAUSE_FULL = 7, // Active but transfers paused in the SDK DELETED = 8, // Sync needs to be deleted, as required by sync-desired-state received from BackupCenter (WebClient) }; struct BackupInfo { // if left as UNDEF, you are registering a new Sync/Backup handle backupId = UNDEF; handle driveId = UNDEF; // if registering a new Sync/Backup, these must be set // otherwise, leave as is to not send an update for that field. BackupType type = BackupType::INVALID; string backupName = ""; NodeHandle nodeHandle; // undef by default LocalPath localFolder; // empty string deviceId = ""; SPState state = STATE_NOT_INITIALIZED; int subState = -1; }; CommandBackupPut(MegaClient* client, const BackupInfo&, std::function completion); }; class MEGA_API CommandBackupRemove : public Command { std::function mCompletion; public: bool procresult(Result, JSON&) override; CommandBackupRemove(MegaClient* client, handle backupId, std::function completion); }; class MEGA_API CommandBackupPutHeartBeat : public Command { std::function mCompletion; public: bool procresult(Result, JSON&) override; enum SPHBStatus { STATE_NOT_INITIALIZED, UPTODATE = 1, // Up to date: local and remote paths are in sync SYNCING = 2, // The sync engine is working, transfers are in progress PENDING = 3, // The sync engine is working, e.g: scanning local folders INACTIVE = 4, // Sync is not active. A state != ACTIVE should have been sent through '''sp''' UNKNOWN = 5, // Unknown status STALLED = 6, // a folder is scan-blocked, or some contradictory changes occured between local and remote folders, user must pick one }; CommandBackupPutHeartBeat(MegaClient* client, handle backupId, SPHBStatus status, int8_t progress, uint32_t uploads, uint32_t downloads, m_time_t ts, handle lastNode, std::function); }; class MEGA_API CommandBackupSyncFetch : public Command { public: struct Data { handle backupId = UNDEF; BackupType backupType = BackupType::INVALID; handle rootNode = UNDEF; string localFolder; string deviceId; int syncState = 0; int syncSubstate = 0; string extra; string backupName; string deviceUserAgent; uint64_t hbTimestamp = 0; int hbStatus = 0; int hbProgress = 0; int uploads = 0; int downloads = 0; uint64_t lastActivityTs = 0; handle lastSyncedNodeHandle = UNDEF; }; bool procresult(Result, JSON&) override; CommandBackupSyncFetch(std::function&)>); private: std::function&)> completion; }; class MEGA_API CommandGetBanners : public Command { public: bool procresult(Result, JSON&) override; CommandGetBanners(MegaClient*); }; class MEGA_API CommandDismissBanner : public Command { public: bool procresult(Result, JSON&) override; CommandDismissBanner(MegaClient*, int id, m_time_t ts); }; // // Sets and Elements // class CommandSE : public Command // intermediary class to avoid code duplication { protected: bool procjsonobject(JSON& json, handle& id, m_time_t& ts, handle* u, m_time_t* cts = nullptr, handle* s = nullptr, int64_t* o = nullptr, PublicLinkSet* publicLinkSet = nullptr, uint8_t* setType = nullptr) const; bool procresultid(JSON& json, const Result& r, handle& id, m_time_t& ts, handle* u, m_time_t* cts = nullptr, handle* s = nullptr, int64_t* o = nullptr, PublicLinkSet* publicLinkSet = nullptr, uint8_t* setType = nullptr) const; bool procerrorcode(const Result& r, Error& e) const; bool procExtendedError(JSON& json, int64_t& errCode, handle& eid) const; }; class Set; class MEGA_API CommandPutSet : public CommandSE { public: CommandPutSet(MegaClient*, Set&& s, unique_ptr encrAttrs, string&& encrKey, std::function completion); bool procresult(Result, JSON&) override; private: unique_ptr mSet; // use a pointer to avoid defining Set in this header std::function mCompletion; }; class MEGA_API CommandRemoveSet : public CommandSE { public: CommandRemoveSet(MegaClient*, handle id, std::function completion); bool procresult(Result, JSON&) override; private: handle mSetId = UNDEF; std::function mCompletion; }; class SetElement; class MEGA_API CommandFetchSet : public CommandSE { public: CommandFetchSet(MegaClient*, std::function*)> completion); bool procresult(Result, JSON&) override; private: std::function*)> mCompletion; }; class MEGA_API CommandPutSetElements : public CommandSE { public: CommandPutSetElements(MegaClient*, vector&& el, vector&& encrDetails, std::function*, const vector*)> completion); bool procresult(Result, JSON&) override; private: unique_ptr> mElements; // use a pointer to avoid defining SetElement in this header std::function*, const vector*)> mCompletion; }; class MEGA_API CommandPutSetElement : public CommandSE { public: CommandPutSetElement(MegaClient*, SetElement&& el, unique_ptr encrAttrs, string&& encrKey, std::function completion); bool procresult(Result, JSON&) override; private: unique_ptr mElement; // use a pointer to avoid defining SetElement in this header std::function mCompletion; }; class MEGA_API CommandRemoveSetElements : public CommandSE { public: CommandRemoveSetElements(MegaClient*, handle sid, vector&& eids, std::function*)> completion); bool procresult(Result, JSON&) override; private: handle mSetId = UNDEF; handle_vector mElemIds; std::function*)> mCompletion; }; class MEGA_API CommandRemoveSetElement : public CommandSE { public: CommandRemoveSetElement(MegaClient*, handle sid, handle eid, std::function completion); bool procresult(Result, JSON&) override; private: handle mSetId = UNDEF; handle mElementId = UNDEF; std::function mCompletion; }; class MEGA_API CommandExportSet : public CommandSE { public: CommandExportSet(MegaClient*, Set&& s, bool makePublic, std::function completion); bool procresult(Result, JSON&) override; private: unique_ptr mSet; std::function mCompletion; }; // -------- end of Sets and Elements #ifdef ENABLE_CHAT typedef std::function CommandMeetingStartCompletion; class MEGA_API CommandMeetingStart : public Command { CommandMeetingStartCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandMeetingStart(MegaClient*, const handle chatid, const bool notRinging, CommandMeetingStartCompletion completion); }; typedef std::function CommandMeetingJoinCompletion; class MEGA_API CommandMeetingJoin : public Command { CommandMeetingJoinCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandMeetingJoin(MegaClient*, handle chatid, handle callid, CommandMeetingJoinCompletion completion); }; typedef std::function CommandMeetingEndCompletion; class MEGA_API CommandMeetingEnd : public Command { CommandMeetingEndCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandMeetingEnd(MegaClient*, handle chatid, handle callid, int reason, CommandMeetingEndCompletion completion); }; typedef std::function CommandRingUserCompletion; class MEGA_API CommandRingUser : public Command { CommandRingUserCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandRingUser(MegaClient*, handle chatid, handle userid, CommandRingUserCompletion completion); }; typedef std::function CommandScheduledMeetingAddOrUpdateCompletion; class MEGA_API CommandScheduledMeetingAddOrUpdate : public Command { std::string mChatTitle; std::unique_ptr mScheduledMeeting; CommandScheduledMeetingAddOrUpdateCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandScheduledMeetingAddOrUpdate(MegaClient *, const ScheduledMeeting*, const char*, CommandScheduledMeetingAddOrUpdateCompletion); }; typedef std::function CommandScheduledMeetingRemoveCompletion; class MEGA_API CommandScheduledMeetingRemove : public Command { handle mChatId; handle mSchedId; CommandScheduledMeetingRemoveCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandScheduledMeetingRemove(MegaClient *, handle, handle, CommandScheduledMeetingRemoveCompletion completion); }; typedef std::function>*)> CommandScheduledMeetingFetchCompletion; class MEGA_API CommandScheduledMeetingFetch : public Command { CommandScheduledMeetingFetchCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandScheduledMeetingFetch(MegaClient *, handle, handle, CommandScheduledMeetingFetchCompletion completion); }; typedef std::function>*)> CommandScheduledMeetingFetchEventsCompletion; class MEGA_API CommandScheduledMeetingFetchEvents : public Command { handle mChatId; bool mByDemand; CommandScheduledMeetingFetchEventsCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandScheduledMeetingFetchEvents(MegaClient *, handle, m_time_t, m_time_t, unsigned int, bool, CommandScheduledMeetingFetchEventsCompletion completion); }; #endif typedef std::function CommandFetchAdsCompletion; class MEGA_API CommandFetchAds : public Command { CommandFetchAdsCompletion mCompletion; std::vector mAdUnits; public: bool procresult(Result, JSON&) override; CommandFetchAds(MegaClient*, int adFlags, const std::vector& adUnits, handle publicHandle, CommandFetchAdsCompletion completion); }; typedef std::function CommandQueryAdsCompletion; class MEGA_API CommandQueryAds : public Command { CommandQueryAdsCompletion mCompletion; public: bool procresult(Result, JSON&) override; CommandQueryAds(MegaClient*, int adFlags, handle publicHandle, CommandQueryAdsCompletion completion); }; /* MegaVPN Commands BEGIN */ class VpnCluster { public: VpnCluster(std::string&& host, std::vector&& dns, std::vector&& addBlockingDns): mHost{std::move(host)}, mDns{std::move(dns)}, mAdBlockingDns{std::move(addBlockingDns)} {} const std::string& getHost() const { return mHost; } const std::vector& getDns() const { return mDns; } const std::vector& getAdBlockingDns() const { return mAdBlockingDns; } private: std::string mHost; std::vector mDns; // {"8.8.8.8", "8.8.4.4", ...} std::vector mAdBlockingDns; }; class VpnRegion { public: VpnRegion(std::string&& name): mName{std::move(name)} {} const std::string& getName() const { return mName; } const std::string& getCountryCode() const { return mCountryCode; } const std::string& getCountryName() const { return mCountryName; } const std::string& getRegionName() const { return mRegionName; } const std::string& getTownName() const { return mTownName; } void setCountryCode(std::string&& countryCode) { mCountryCode = std::move(countryCode); } void setCountryName(std::string&& countryName) { mCountryName = std::move(countryName); } void setRegionName(std::string&& regionName) { mRegionName = std::move(regionName); } void setTownName(std::string&& townName) { mTownName = std::move(townName); } const std::map& getClusters() const { return mClusters; } void addCluster(int id, VpnCluster&& cluster) { mClusters.emplace(id, std::move(cluster)); } private: std::string mName; // Name (handle) of the VPN Region std::string mCountryCode; std::string mCountryName; std::string mRegionName; // Country region name std::string mTownName; std::map mClusters; }; class MEGA_API CommandGetVpnRegions : public Command { public: using Cb = std::function&& /* VPN regions */)>; CommandGetVpnRegions(MegaClient*, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; static bool parseRegions(JSON& json, std::vector* vpnRegions); private: static bool parseClusters(JSON& json, VpnRegion* vpnRegion); Cb mCompletion; }; class MEGA_API CommandGetVpnCredentials : public Command { public: struct CredentialInfo { int clusterID; std::string ipv4; std::string ipv6; std::string deviceID; }; using MapSlotIDToCredentialInfo = std::map; using MapClusterPublicKeys = std::map; using Cb = std::function&& /* VPN Regions */)>; CommandGetVpnCredentials(MegaClient*, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; private: Cb mCompletion; }; class MEGA_API CommandPutVpnCredential : public Command { public: using Cb = std::function; CommandPutVpnCredential(MegaClient*, std::string&& /* VPN Region */, StringKeyPair&& /* User Key Pair */, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; private: std::string mRegion; StringKeyPair mUserKeyPair; Cb mCompletion; }; class MEGA_API CommandDelVpnCredential : public Command { public: using Cb = std::function; CommandDelVpnCredential(MegaClient*, int /* SlotID */, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; private: Cb mCompletion; }; class MEGA_API CommandCheckVpnCredential : public Command { public: using Cb = std::function; CommandCheckVpnCredential(MegaClient*, std::string&& /* User Public Key */, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; private: Cb mCompletion; }; class MEGA_API CommandGetNetworkConnectivityTestServerInfo: public Command { public: using Completion = std::function; CommandGetNetworkConnectivityTestServerInfo(MegaClient*, Completion&&); bool procresult(Result, JSON&) override; private: void onParseFailure() { if (mCompletion) mCompletion(API_EINTERNAL, {}); } Completion mCompletion; }; /* MegaVPN Commands END*/ typedef std::function& creditCardInfo)> CommandFetchCreditCardCompletion; class MEGA_API CommandFetchCreditCard : public Command { public: CommandFetchCreditCard(MegaClient* client, CommandFetchCreditCardCompletion completion); bool procresult(Result r, JSON&json) override; private: CommandFetchCreditCardCompletion mCompletion; }; class MEGA_API CommandCreatePasswordManagerBase : public Command { public: using Completion = std::function)>; CommandCreatePasswordManagerBase(MegaClient* cl, std::unique_ptr, int ctag, Completion&& cb = nullptr); bool procresult(Result, JSON&) override; private: std::unique_ptr mNewNode; Completion mCompletion; }; struct DynamicMessageNotification; class MEGA_API CommandGetNotifications : public Command { public: bool procresult(Result, JSON&) override; using ResultFunc = std::function&& notifications)>; CommandGetNotifications(MegaClient*, ResultFunc onResult); private: bool readCallToAction(JSON& json, std::map& action); bool readRenderModes(JSON& json, std::map>& modes); ResultFunc mOnResult; }; class MEGA_API CommandGetActiveSurveyTriggerActions: public Command { public: using Completion = std::function& /*triggerActionIds*/)>; CommandGetActiveSurveyTriggerActions(MegaClient* client, Completion&& completion); bool procresult(Result, JSON&) override; private: std::vector parseTriggerActionIds(JSON& json); void onCompletion(const Error& e, const std::vector triggerActionIds) { if (mCompletion) mCompletion(e, triggerActionIds); } Completion mCompletion; }; class MEGA_API CommandGetSurvey: public Command { public: struct Survey { bool isValid() const { return h != UNDEF; }; // Survey handle handle h{UNDEF}; // Maximum allowed value in the survey response. A small non negative integer. 0 means // a non integer survey reponse is wanted. unsigned int maxResponse{0}; // Name of an image to be display, can be empty std::string image; // Content of the question std::string content; }; using Completion = std::function; CommandGetSurvey(MegaClient* client, unsigned int triggerActionId, Completion&& completion); bool procresult(Result, JSON&) override; private: bool parseSurvey(JSON& json, Survey& survey); void onCompletion(const Error& e, const Survey& survey) { if (mCompletion) mCompletion(e, survey); } Completion mCompletion; }; class MEGA_API CommandAnswerSurvey: public Command { public: class Answer { public: Answer(handle h, unsigned int triggerActionId, const char* response, const char* comment): mHandle{h}, mTriggerActionId{triggerActionId}, mResponse{response ? response : ""}, mComment{comment ? comment : ""} {} private: friend class CommandAnswerSurvey; // Survey handle handle mHandle{UNDEF}; // Trigger action id; unsigned int mTriggerActionId{0}; // the response to the survey std::string mResponse; // the response to tell us more std::string mComment; }; using Completion = std::function; CommandAnswerSurvey(MegaClient* client, const Answer& answer, Completion&& completion); bool procresult(Result, JSON&) override; private: void onCompletion(const Error& e) { if (mCompletion) mCompletion(e); } Completion mCompletion; }; class MEGA_API CommandGetMyIP: public Command { public: using Cb = std::function; CommandGetMyIP(MegaClient*, Cb&& completion = nullptr); bool procresult(Result, JSON&) override; private: Cb mCompletion; }; class MEGA_API CommandSetThrottlingParams: public Command { public: struct ThrottlingParamsFromAPI { m_off_t updateRateInSeconds{-1}; m_off_t maxUploadsBeforeThrottle{-1}; m_off_t uploadCounterInactivityTime{-1}; }; using ResultVariant = std::variant; using Completion = std::function; CommandSetThrottlingParams(const MegaClient&, Completion&& completion); bool procresult(Result, JSON&) override; private: static std::pair> parseJson(JSON& json); Completion mCompletion; }; class MEGA_API CommandGetSubscriptionCancellationDetails: public Command { public: using CompletionCallback = std::function; CommandGetSubscriptionCancellationDetails(MegaClient*, const char* originalTransactionId, unsigned int gatewayId, CompletionCallback&& completion = nullptr); bool procresult(Result, JSON&) override; private: CompletionCallback mCompletion; }; class MEGA_API CommandDiscountCodeGetInfo: public Command { public: using CompletionCallback = std::function; CommandDiscountCodeGetInfo(MegaClient*, const string& discountCode, CompletionCallback&& completion = nullptr); bool procresult(Result, JSON&) override; private: CompletionCallback mCompletion; }; } // namespace #endif sdk-10.11.0/include/mega/common/000077500000000000000000000000001516266226600162735ustar00rootroot00000000000000sdk-10.11.0/include/mega/common/activity_monitor.h000066400000000000000000000026511516266226600220530ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { // Represents some action that is being performed. class Activity { friend class ActivityMonitor; // Informs the monitor that a new activity has begun. Activity(ActivityMonitor& monitor); // Who monitors our activity? ActivityMonitor* mMonitor; public: Activity(); // Informs a monitor that a new activity has begun. Activity(const Activity& other); Activity(Activity&& other); // Informs a monitor that an activity has been completed. ~Activity(); Activity& operator=(const Activity& rhs); Activity& operator=(Activity&& rhs); void swap(Activity& other); }; // Activity // Lets an entity wait until all activities have completed. class ActivityMonitor { friend class Activity; // Signalled when all activity has completed. std::condition_variable mCompleted; // Serializes access to mProcessing. mutable std::mutex mLock; // How many activities are in progress? std::size_t mProcessing; public: ActivityMonitor(); ~ActivityMonitor(); // Are any activities in progress? bool active() const; // Begin a new activity. Activity begin(); // Wait until all activities have completed. void waitUntilIdle(); }; // ActivityMonitor } // common } // mega sdk-10.11.0/include/mega/common/activity_monitor_forward.h000066400000000000000000000001621516266226600235720ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class Activity; class ActivityMonitor; } // common } // mega sdk-10.11.0/include/mega/common/badge.h000066400000000000000000000003561516266226600175120ustar00rootroot00000000000000#pragma once namespace mega { namespace common { template class Badge { friend T; Badge() = default; public: Badge(const Badge& other) = default; ~Badge() = default; }; // Badge } // common } // mega sdk-10.11.0/include/mega/common/badge_forward.h000066400000000000000000000002251516266226600212310ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { template class Badge; } // common } // mega sdk-10.11.0/include/mega/common/client.h000066400000000000000000000167271516266226600177370ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { struct MegaApp; namespace common { // A high-level interface to MEGA's cloud storage. class Client { protected: explicit Client(Logger& logger); // Who should we notify when something changes in the cloud? std::set mEventObservers; // Serializes access to mEventObservers. std::recursive_mutex mEventObserversLock; // What logger should we use? Logger& mLogger; public: virtual ~Client(); // Notify observer when something changes in the cloud. void addEventObserver(NodeEventObserver& observer); // What application is our client bound to? virtual MegaApp& application() = 0; // Retrieve the names of a parent's children. virtual ErrorOr> childNames(NodeHandle parent) const = 0; // Compute a suitable path for a database. virtual LocalPath dbPath(const std::string& name) const = 0; // Query where databases should be stored. virtual LocalPath dbRootPath() const = 0; // Deinitialize the client. virtual void deinitialize() = 0; // Remove a sync previously created with synchronize(...) virtual void desynchronize(mega::handle id) = 0; // Download a file from the cloud. virtual void download(DownloadCallback callback, NodeHandle handle, const LocalPath& logicalPath, const LocalPath& physicalPath) = 0; // Execute a function for each child of a node. virtual void each(std::function function, NodeHandle handle) const = 0; // Execute some function on the client's thread. virtual Task execute(std::function function) = 0; // Query whether a node exists in the cloud. virtual ErrorOr exists(NodeHandle handle) const = 0; // Request access the local filesystem. virtual FileSystemAccess& fsAccess() const = 0; // Retrieve a description of a specific node. virtual ErrorOr get(NodeHandle handle) const = 0; // Retrieve a description of a specific child. virtual ErrorOr get(NodeHandle parent, const std::string& name) const = 0; // Query what a child's node handle is. virtual ErrorOr handle(NodeHandle parent, const std::string& name) const = 0; // Query whether a parent contains any children. virtual ErrorOr hasChildren(NodeHandle parent) const = 0; // Initialize the client for use. virtual void initialize() = 0; // Check whether a node is a directory. ErrorOr isDirectory(NodeHandle handle) const; // Check whether a node is a file. virtual ErrorOr isFile(NodeHandle handle) const = 0; // Retrieve the specified node's key data. virtual ErrorOr keyData(NodeHandle handle, bool authorize) const = 0; // What logger is this client using? Logger& logger() const; // Look up a cloud node by path. template auto lookup(const T& path, NodeHandle parent) -> typename EnableIfPath>::type; // Make a new directory in the cloud. virtual void makeDirectory(MakeDirectoryCallback callback, const std::string& name, NodeHandle parent) = 0; // Make a new directory in the cloud. ErrorOr makeDirectory(const std::string& name, NodeHandle parent); // Check if path is "mountable." // // That is, totally unrelated to any active sync. virtual bool mountable(const NormalizedPath& path) const = 0; // Rename source to name and move it to target. void move(MoveCallback callback, const std::string& name, NodeHandle source, NodeHandle target); Error move(const std::string& name, NodeHandle source, NodeHandle target); // Move source to target. virtual void move(MoveCallback callback, NodeHandle source, NodeHandle target) = 0; Error move(NodeHandle source, NodeHandle target); // Query who a node's parent is. virtual ErrorOr parentHandle(NodeHandle handle) const = 0; // Download part of a local file from the cloud. virtual auto partialDownload(PartialDownloadCallback& callback, NodeHandle handle, std::uint64_t length, std::uint64_t offset) -> ErrorOr = 0; // Download part of a foreign file from the cloud. virtual auto partialDownload(PartialDownloadCallback& callback, NodeHandle handle, const NodeKeyData& keyData, std::uint64_t length, std::uint64_t offset) -> ErrorOr = 0; // What permissions are applicable to a node? virtual ErrorOr permissions(NodeHandle handle) const = 0; // Remove a node. virtual void remove(RemoveCallback callback, NodeHandle handle) = 0; Error remove(NodeHandle handle); // Remove all children of a node. Error removeAll(NodeHandle handle); // Don't send observer any further change notifications. void removeEventObserver(NodeEventObserver& observer); // Rename a node. virtual void rename(RenameCallback callback, const std::string& name, NodeHandle handle) = 0; Error rename(const std::string& name, NodeHandle handle); // Replace target with source. Error replace(NodeHandle source, NodeHandle target); // Retrieve the client's current session ID. virtual std::string sessionID() const = 0; // Retrieve storage statistics from the cloud. virtual void storageInfo(StorageInfoCallback callback) = 0; ErrorOr storageInfo(); // Synchronize a local tree against some location in the cloud. virtual auto synchronize(const NormalizedPath& path, NodeHandle target) -> std::tuple = 0; // Update a file's modification time. virtual void touch(TouchCallback callback, NodeHandle handle, m_time_t modified) = 0; Error touch(NodeHandle handle, m_time_t modified); // Upload a file to the cloud. virtual UploadPtr upload(const LocalPath& logicalPath, const std::string& name, NodeHandle parent, const LocalPath& physicalPath) = 0; }; // Client } // common } // mega sdk-10.11.0/include/mega/common/client_adapter.h000066400000000000000000000141411516266226600214230ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace mega { namespace common { // Wraps MegaClient so it can be used by FUSE. class ClientAdapter : public Client { // So we can track when other threads are busy within us. mutable ActivityMonitor mActivities; // Which client's doing our bidding? MegaClient& mClient; // Whether this client has been deinitialized. std::atomic mDeinitialized; // Serializes access to instance members. std::mutex mLock; // Tracks callbacks waiting to be called. PendingCallbacks mPendingCallbacks; // Tracks queued tasks. TaskQueue mTaskQueue; // So we can check when we're running on the client thread. std::thread::id mThreadID; public: explicit ClientAdapter(MegaClient& client); ~ClientAdapter(); // What application is our client bound to? MegaApp& application() override; // Retrieve the names of a parent's children. ErrorOr> childNames(NodeHandle parent) const override; // Get our hands on the underlying client. MegaClient& client() const; // Compute a suitable path for a database. LocalPath dbPath(const std::string& name) const override; // Query where databases should be stored. LocalPath dbRootPath() const override; // Deinitialize the client. void deinitialize() override; // Remove a sync previously created with synchronize(...) void desynchronize(mega::handle id) override; // Execute queued functions on the client thread. void dispatch(); // Download a file from the cloud. void download(DownloadCallback callback, NodeHandle handle, const LocalPath& logicalPath, const LocalPath& physicalPath) override; // Execute a function for each child of a node. void each(std::function function, NodeHandle handle) const override; // Execute some function on the client's thread. Task execute(std::function function) override; // Query whether a node exists in the cloud. ErrorOr exists(NodeHandle handle) const override; // Request access the local filesystem. FileSystemAccess& fsAccess() const override; // Retrieve a description of a specific node. ErrorOr get(NodeHandle handle) const override; // Retrieve a description of a specific child. ErrorOr get(NodeHandle parent, const std::string& name) const override; // Query what a child's node handle is. ErrorOr handle(NodeHandle parent, const std::string& name) const override; // Query whether a parent contains any children. ErrorOr hasChildren(NodeHandle parent) const override; // Initialize the client. void initialize() override; // Check whether a node is a file. ErrorOr isFile(NodeHandle handle) const override; // Retrieve the specified node's key data. ErrorOr keyData(NodeHandle handle, bool authorize) const override; // Make a new directory in the cloud. void makeDirectory(MakeDirectoryCallback callback, const std::string& name, NodeHandle parent) override; // Check if path is "mountable." bool mountable(const NormalizedPath& path) const override; // Move source to target. void move(MoveCallback callback, NodeHandle source, NodeHandle target) override; // Is this the client thread? bool isClientThread() const; // Query who a node's parent is. ErrorOr parentHandle(NodeHandle handle) const override; // Download part of a file from the cloud. auto partialDownload(PartialDownloadCallback& callback, NodeHandle handle, std::uint64_t length, std::uint64_t offset) -> ErrorOr override; // Download part of a foreign file from the cloud. auto partialDownload(PartialDownloadCallback& callback, NodeHandle handle, const NodeKeyData& keyData, std::uint64_t length, std::uint64_t offset) -> ErrorOr override; // What permissions are applicable to a node? ErrorOr permissions(NodeHandle handle) const override; // Remove a node. void remove(RemoveCallback callback, NodeHandle handle) override; // Rename a node. void rename(RenameCallback callback, const std::string& name, NodeHandle handle) override; // Retrieve the client's current session ID. std::string sessionID() const override; // Retrieve storage statistics from the cloud. void storageInfo(StorageInfoCallback callback) override; // Synchronize a local tree against some location in the cloud. auto synchronize(const NormalizedPath& path, NodeHandle target) -> std::tuple override; // Set a file's modification time. void touch(TouchCallback callback, NodeHandle handle, m_time_t modified) override; // Called when nodes have been updated in the cloud. void updated(const sharedNode_vector& nodes); // Upload a file to the cloud. UploadPtr upload(const LocalPath& logicalPath, const std::string& name, NodeHandle parent, const LocalPath& physicalPath) override; // Wraps the provided callback such that it can be cancelled. template> auto wrap(std::function callback) -> typename std::enable_if>::type { return mPendingCallbacks.wrap(std::move(callback)); } }; // ClientAdapter } // common } // mega sdk-10.11.0/include/mega/common/client_callbacks.h000066400000000000000000000013421516266226600217210ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace common { using DownloadCallback = std::function; using MakeDirectoryCallback = std::function)>; using MoveCallback = std::function; using RemoveCallback = std::function; using RenameCallback = std::function; using ReplaceCallback = std::function; using StorageInfoCallback = std::function)>; using TouchCallback = std::function; } // common } // mega sdk-10.11.0/include/mega/common/client_forward.h000066400000000000000000000001311516266226600214410ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class Client; } // common } // mega sdk-10.11.0/include/mega/common/database.h000066400000000000000000000025041516266226600202110ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include struct sqlite3; namespace mega { namespace common { template<> struct LockableTraits : public LockableTraitsCommon { }; // LockableTraits class Database : public Lockable { // See execute below. std::string execute(const char* statement); // The database's SQL context. sqlite3* mDB; // The databse's logger instance. Logger* mLogger; // The database file this instance is accessing. std::string mPath; public: Database(Logger& logger, const LocalPath& path); Database(Database&& other); ~Database(); // Retrieve a reference to this database's SQL context. sqlite3* get(Badge badge); // Retrieve a reference to this database's logger. Logger& logger() const; // Return a new query that references this database. Query query(); // Return a new transaction that references this database. Transaction transaction(); }; // Database } // common } // mega sdk-10.11.0/include/mega/common/database_builder.h000066400000000000000000000026431516266226600217230ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { struct DatabaseVersion { // Called to undo the actions of mUpgrade. std::function mDowngrade; // Called to perform changes necessary for a given version. std::function mUpgrade; }; // DatabaseVersion // Convenience. using DatabaseVersionVector = std::vector; class DatabaseBuilder { // What versions exist for this database? virtual const DatabaseVersionVector& versions() const = 0; // Downgrade the database to the specified version. void downgrade(const DatabaseVersionVector& versions, std::size_t target); // Upgrade the database to the specified version. void upgrade(const DatabaseVersionVector& versions, std::size_t target); // What database are we operating on? Database& mDatabase; protected: explicit DatabaseBuilder(Database& database); ~DatabaseBuilder() = default; public: // Create or update the database to the latest version. void build(); // Downgrade the database to the specified version. void downgrade(std::size_t target); // Upgrade the database to the specified version. void upgrade(std::size_t target); }; // DatabaseBuilder } // common } // mega sdk-10.11.0/include/mega/common/database_forward.h000066400000000000000000000002451516266226600217350ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class Database; using DatabaseLock = std::unique_lock; } // common } // mega sdk-10.11.0/include/mega/common/database_utilities.h000066400000000000000000000034531516266226600223100ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { // Call function with a transaction object. template> auto withTransaction(Database& database, Function&& function) -> std::enable_if_t> { auto lock = DatabaseLock(database); auto transaction = database.transaction(); std::invoke(std::forward(function), transaction); transaction.commit(); } template> auto withTransaction(Database& database, Function&& function) -> std::enable_if_t> { auto lock = DatabaseLock(database); auto transaction = database.transaction(); auto result = std::invoke(std::forward(function), transaction); transaction.commit(); return result; } // Call function with a query object. template auto withQuery(Database& database, Function&& function) -> std::invoke_result_t { return withTransaction(database, [&function](Transaction& transaction) { return std::invoke(std::forward(function), transaction.query()); }); } // Call function with a scoped query object. template auto withQuery(Database& database, Function&& function, Query& query) -> std::invoke_result_t { return withTransaction(database, [&function, &query](Transaction& transaction) { return std::invoke(std::forward(function), transaction.query(query)); }); } } // common } // mega sdk-10.11.0/include/mega/common/date_time.h000066400000000000000000000056021516266226600204020ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace common { namespace detail { template struct AreTimeValues: std::bool_constant::value && IsTimeValue::value> {}; // AreTimeValues class DateTime { std::uint64_t mValue = 0u; public: DateTime() = default; DateTime(const DateTime& other) = default; template, typename V = typename std::enable_if::type> DateTime(const T& other): mValue(TimeValueTraits::from(other)) {} DateTime& operator=(const DateTime& rhs) = default; template, typename V = typename std::enable_if::type> DateTime& operator=(const T& rhs) { return operator=(DateTime(rhs)); } bool operator==(const DateTime& rhs) const; template, typename V = typename std::enable_if::type> bool operator==(const T& rhs) const { return operator==(DateTime(rhs)); } bool operator!=(const DateTime& rhs) const; template, typename V = typename std::enable_if::type> bool operator!=(const T& rhs) const { return operator!=(DateTime(rhs)); } template, typename V = typename std::enable_if::type> operator T() const { return asValue(); } template, typename V = typename std::enable_if::type> T asValue() const { return TimeValueTraits::to(mValue); } }; // DateTime template struct IsTimeValue: common::IsComplete> {}; // IsTimeValue template<> struct TimeValueTraits { static std::uint64_t from(m_time_t value) { return static_cast(value); } static m_time_t to(std::uint64_t value) { return static_cast(value); } }; // TimeValueTraits // Convenience. using SystemClock = std::chrono::system_clock; using SystemTime = SystemClock::time_point; template<> struct TimeValueTraits { static std::uint64_t from(const SystemTime& value) { return static_cast(SystemClock::to_time_t(value)); } static SystemTime to(std::uint64_t value) { return SystemClock::from_time_t(static_cast(value)); } }; // TimeValueTraits std::string toString(const DateTime& value); } // detail } // common } // mega sdk-10.11.0/include/mega/common/date_time_forward.h000066400000000000000000000007211516266226600221230ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace detail { template struct AreTimeValues; class DateTime; template struct IsTimeValue; template struct TimeValueTraits; } // detail template using AreTimeValues = detail::AreTimeValues; using DateTime = detail::DateTime; template using IsTimeValue = detail::IsTimeValue; } // common } // mega sdk-10.11.0/include/mega/common/deciseconds.h000066400000000000000000000003051516266226600207250ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { using deciseconds = std::chrono::duration; } // common } // mega sdk-10.11.0/include/mega/common/directory.h000066400000000000000000000007421516266226600204530ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { class Directory { FileSystemAccess& mFilesystem; LocalPath mPath; public: Directory(FileSystemAccess& filesystem, Logger& logger, const std::string& name, const LocalPath& rootPath); operator const LocalPath&() const; auto path() const -> const LocalPath&; }; // Directory } // common } // mega sdk-10.11.0/include/mega/common/error_or.h000066400000000000000000000022771516266226600203050ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { namespace detail { // Check if T is an error type. template struct IsError : std::false_type { }; // IsError template<> struct IsError : std::true_type { }; // IsError template<> struct IsError : std::true_type { }; // IsError // Checks if T is an ErrorOr type. template struct IsErrorOr : std::false_type { }; // IsErrorOr template struct IsErrorOr> : std::true_type { }; // IsErrorOr> }; // detail // Checks if T is the Error type. template struct IsError : detail::IsError::type> { }; // IsError // Checks if T is an ErrorOr type. template struct IsErrorOr : detail::IsErrorOr::type> { }; // IsErrorOr // Combines above checks. template struct IsErrorLike : std::integral_constant::value || IsErrorOr::value> { }; // IsErrorLike } // common } // mega sdk-10.11.0/include/mega/common/error_or_forward.h000066400000000000000000000004661516266226600220270ustar00rootroot00000000000000#pragma once #include namespace mega { class Error; namespace common { template using ErrorOr = Expected; template struct IsError; template struct IsErrorOr; template struct IsErrorLike; } // common } // mega sdk-10.11.0/include/mega/common/expected.h000066400000000000000000000141471516266226600202540ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { namespace detail { template struct IsExpected: std::false_type {}; // IsExpected template struct IsExpected>: std::true_type {}; // IsExpected> } // detail template class Expected { template friend class Expected; template static constexpr auto IsConstructibleV = std::is_constructible_v || std::is_convertible_v; template static constexpr auto IsCompatibleValueV = !IsExpectedV && IsConstructibleV; std::variant mValue; public: Expected(): mValue() {} template Expected(Expected&& other): mValue(std::visit( [](auto&& value) { return std::variant(std::move(value)); }, other.mValue)) {} template Expected(const Expected& other): mValue(std::visit( [](const auto& value) { return std::variant(value); }, other.mValue)) {} template Expected(Unexpected&& other): mValue(std::in_place_type_t(), other.value()) {} template Expected(const Unexpected& other): mValue(std::in_place_type_t(), other.value()) {} template>* = nullptr> Expected(U&& other): mValue(std::in_place_type_t(), std::forward(other)) {} explicit operator bool() const { return hasValue(); } template Expected& operator=(Expected&& rhs) { Expected temp(std::move(rhs)); swap(temp); return *this; } template Expected& operator=(const Expected& rhs) { Expected temp(rhs); swap(temp); return *this; } template>* = nullptr> Expected& operator=(U&& rhs) { Expected temp(std::forward(rhs)); swap(temp); return *this; } T* operator->() { return &value(); } const T* operator->() const { return &value(); } T& operator*() & { return value(); } T&& operator*() && { return std::move(*this).value(); } const T& operator*() const& { return value(); } const T&& operator*() const&& { return std::move(*this).value(); } template bool operator==(const Expected& rhs) const { if (hasValue()) return rhs.hasValue() && std::get(mValue) == std::get(rhs.mValue); return rhs.hasError() && std::get(mValue) == std::get(rhs.mValue); } template bool operator==(const Unexpected& rhs) const { return hasError() && error() == rhs.value(); } template>* = nullptr> bool operator==(const U& rhs) const { return hasValue() && value() == rhs; } bool operator!() const { return hasError(); } template bool operator!=(const Expected& rhs) const { return !(*this == rhs); } template bool operator!=(const Unexpected& rhs) const { return !(*this == rhs); } template>* = nullptr> bool operator!=(const U& rhs) const { return !(*this == rhs); } bool hasError() const { return std::holds_alternative(mValue); } bool hasValue() const { return std::holds_alternative(mValue); } E& error() & { assert(hasError()); return std::get(mValue); } E&& error() && { assert(hasError()); return std::get(std::move(mValue)); } const E& error() const& { assert(hasError()); return std::get(mValue); } const E&& error() const&& { assert(hasError()); return std::get(std::move(mValue)); } template auto errorOr(U&& defaultValue) && -> std::enable_if_t, E> { if (hasError()) return std::get(std::move(mValue)); return std::forward(defaultValue); } template auto errorOr(U&& defaultValue) const& -> std::enable_if_t, E> { if (hasError()) return std::get(mValue); return std::forward(defaultValue); } void swap(Expected& other) { using std::swap; swap(mValue, other.mValue); } T& value() & { assert(hasValue()); return std::get(mValue); } T&& value() && { assert(hasValue()); return std::get(std::move(mValue)); } const T& value() const& { assert(hasValue()); return std::get(mValue); } const T&& value() const&& { assert(hasValue()); return std::get(std::move(mValue)); } T valueOr(T&& defaultValue) && { if (hasValue()) return std::get(std::move(mValue)); return std::move(defaultValue); } T valueOr(T&& defaultValue) const& { if (hasValue()) return std::get(mValue); return std::move(defaultValue); } }; // Expected // Sanity. template class Expected>; template class Expected>; template struct IsExpected: detail::IsExpected> {}; // IsExpected } // common } // mega sdk-10.11.0/include/mega/common/expected_forward.h000066400000000000000000000004671516266226600220000ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace detail { template struct IsExpected; } // detail template class Expected; template struct IsExpected; template constexpr auto IsExpectedV = IsExpected::value; } // common } // mega sdk-10.11.0/include/mega/common/instance_logger.h000066400000000000000000000014061516266226600216100ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { template class InstanceLogger { // The name of the class we are logging. const char* mClassName; // The class instance we are logging. const T& mInstance; // The logger we'll use to emit log messages. Logger& mLogger; public: InstanceLogger(const char* className, const T& instance, Logger& logger): mClassName(className), mInstance(instance), mLogger(logger) { LogDebugF(mLogger, "%s (%p) constructed", mClassName, &mInstance); } ~InstanceLogger() { LogDebugF(mLogger, "%s (%p) destructed", mClassName, &mInstance); } }; // InstanceLogger } // common } // mega sdk-10.11.0/include/mega/common/lock.h000066400000000000000000000111311516266226600173710ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { namespace detail { template struct Lock { using mutex_type = T; Lock() = default; explicit Lock(mutex_type& mutex) : Lock(mutex, std::defer_lock) { lock(); } Lock(mutex_type& mutex, std::adopt_lock_t) : mMutex(&mutex) , mOwned(true) { } Lock(mutex_type& mutex, std::defer_lock_t) : mMutex(&mutex) , mOwned(false) { } Lock(mutex_type& mutex, std::try_to_lock_t) : Lock(mutex, std::defer_lock) { static_cast(try_lock()); } Lock(Lock&& other) : mMutex(other.mMutex) , mOwned(other.mOwned) { other.mMutex = nullptr; other.mOwned = false; } ~Lock() { if (!mOwned) return; assert(mMutex); Traits::unlock(*mMutex); } operator bool() const { return owns_lock(); } Lock& operator=(Lock&& rhs) { Lock temp(std::move(rhs)); swap(temp); return *this; } void lock() { assert(mMutex); assert(!mOwned); Traits::lock(*mMutex); mOwned = true; } mutex_type* mutex() const { return mMutex; } bool owns_lock() const { return mOwned; } mutex_type* release() { auto* mutex = mMutex; mMutex = nullptr; mOwned = false; return mutex; } void swap(Lock& other) { using std::swap; swap(mMutex, other.mMutex); swap(mOwned, other.mOwned); } bool try_lock() { assert(mMutex); assert(!mOwned); mOwned = Traits::try_lock(*mMutex); return mOwned; } template bool try_lock_for(std::chrono::duration duration) { auto now = std::chrono::steady_clock::now(); return try_lock_until(now + duration); } bool try_lock_until(std::chrono::steady_clock::time_point time) { assert(mMutex); assert(!mOwned); mOwned = Traits::try_lock_until(*mMutex, time); return mOwned; } void unlock() { assert(mMutex); assert(mOwned); Traits::unlock(*mMutex); mOwned = false; } protected: mutex_type* mMutex = nullptr; bool mOwned = false; }; // Lock template struct SharedLock : Lock> { using Traits = SharedLockTraits; using Base = Lock; using Base::Base; using Base::operator=; }; // SharedLock template SharedLock(Mutex&) -> SharedLock; template SharedLock(Mutex&, LockFlag) -> SharedLock; template struct SharedLockTraits { static void lock(T& mutex) { mutex.lock_shared(); } static bool try_lock(T& mutex) { return mutex.try_lock_shared(); } template static bool try_lock_until(T& mutex, TP time) { return mutex.try_lock_shared_until(time); } static void unlock(T& mutex) { mutex.unlock_shared(); } }; // SharedLockTraits template struct UniqueLock : Lock> { using Traits = UniqueLockTraits; using Base = Lock; using Base::Base; using Base::operator=; // Translate this unique lock to a shared lock. SharedLock to_shared_lock() { // Sanity. assert(this->mMutex); assert(this->mOwned); // Translate our exclusive lock to a shared lock. SharedLock lock(this->mMutex->unique_to_shared(), std::adopt_lock); // We no longer own the mutex. this->mOwned = false; // Return shared lock to our caller. return lock; } }; // UniqueLock template UniqueLock(Mutex&) -> UniqueLock; template UniqueLock(Mutex&, LockFlag) -> UniqueLock; template struct UniqueLockTraits { static void lock(T& mutex) { mutex.lock(); } static bool try_lock(T& mutex) { return mutex.try_lock(); } template static bool try_lock_until(T& mutex, TP time) { return mutex.try_lock_until(time); } static void unlock(T& mutex) { mutex.unlock(); } }; // UniqueLockTraits } // detail } // common } // mega sdk-10.11.0/include/mega/common/lock_forward.h000066400000000000000000000006031516266226600211170ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace detail { template struct Lock; template struct SharedLock; template struct SharedLockTraits; template struct UniqueLock; template struct UniqueLockTraits; } // detail using detail::SharedLock; using detail::UniqueLock; } // common } // mega sdk-10.11.0/include/mega/common/lockable.h000066400000000000000000000046441516266226600202300ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { template class Lockable { // Convenience. using Traits = LockableTraits; using LockType = typename Traits::LockType; // Helpers. void doLock(void (LockType::*lock)()) const { auto& self = static_cast(*this); Traits::acquiring(self); (mLock.*lock)(); Traits::acquired(self); } bool doTryLock(bool (LockType::*tryLock)()) const { auto& self = static_cast(*this); Traits::tryAcquire(self); if ((mLock.*tryLock)()) { Traits::acquired(self); return true; } Traits::couldntAcquire(self); return false; } void doUnlock(void (LockType::*unlock)()) const { auto& self = static_cast(*this); Traits::released(self); (mLock.*unlock)(); } // Serializes access to this object. mutable LockType mLock; protected: Lockable() = default; ~Lockable() = default; public: // Acquire an exclusive lock on this object. void lock() const { doLock(&LockType::lock); } // Acquire an shared lock on this object. void lock_shared() const { doLock(&LockType::lock_shared); } // Try and acquire an exclusive lock on this object. bool try_lock() const { return doTryLock(&LockType::try_lock); } // Try and acquire a shared lock on this object. bool try_lock_shared() const { return doTryLock(&LockType::try_lock_shared); } // Release an exclusive lock on this object. void unlock() const { doUnlock(&LockType::unlock); } // Release a shared lock on this object. void unlock_shared() const { doUnlock(&LockType::unlock_shared); } }; // Lockable // For convenience. template struct LockableTraitsCommon { // What kind of lock does T require? using LockType = LockType_; // Default loggers do nothing. static void acquiring(const T&) { } static void acquired(const T&) { } static void couldntAcquire(const T&) { } static void tryAcquire(const T&) { } static void released(const T&) { } }; // LockableTraitsCommon } // common } // mega sdk-10.11.0/include/mega/common/lockable_forward.h000066400000000000000000000003511516266226600217430ustar00rootroot00000000000000#pragma once namespace mega { namespace common { template class Lockable; template struct LockableTraits; template struct LockableTraitsCommon; } // common } // mega sdk-10.11.0/include/mega/common/logger.h000066400000000000000000000025571516266226600177340ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace common { class Logger { // The name of the subsystem we're logging for. const char *mSubsystemName; public: explicit Logger(const char* subsystemName = nullptr); Logger(const Logger& other) = delete; virtual ~Logger() = default; Logger& operator=(const Logger& rhs) = delete; // Emit an error message. std::runtime_error error(const char* filename, const char* format, unsigned int line, ...) const; // Emit a log message. void log(const char* filename, const std::string& message, unsigned int line, int severity) const; void log(const char* filename, const char* format, unsigned int line, int severity, ...) const; void logv(std::va_list arguments, const char* filename, const char* format, unsigned int line, int severity) const; // Check whether messages at this severity have been masked. virtual bool masked(int severity) const; }; // Logger Logger& logger(); } // common } // mega sdk-10.11.0/include/mega/common/logger_forward.h000066400000000000000000000001311516266226600214420ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class Logger; } // common } // mega sdk-10.11.0/include/mega/common/logging.h000066400000000000000000000030561516266226600200760ustar00rootroot00000000000000#pragma once #include #include #include // Keep things DRY. #define Log1(logger, format, severity) \ { \ if (!(logger).masked((severity))) \ (logger).log(::mega::log_file_leafname(__FILE__), \ (format), \ __LINE__, \ (severity)); \ } \ while (0) #define LogF(logger, format, severity, ...) do \ { \ if (!(logger).masked((severity))) \ (logger).log(::mega::log_file_leafname(__FILE__), \ (format), \ __LINE__, \ (severity), \ __VA_ARGS__); \ } \ while (0) // Emit a debug message. #define LogDebug1(logger, format) \ Log1((logger), (format), ::mega::logDebug) #define LogDebugF(logger, format, ...) \ LogF((logger), (format), ::mega::logDebug, __VA_ARGS__) // Emit an info message. #define LogInfo1(logger, format) \ Log1((logger), (format), ::mega::logInfo) #define LogInfoF(logger, format, ...) \ LogF((logger), (format), ::mega::logInfo, __VA_ARGS__) // Emit an error message. #define LogError1(logger, format) \ (logger).error(::mega::log_file_leafname(__FILE__), (format), __LINE__) #define LogErrorF(logger, format, ...) \ (logger).error(::mega::log_file_leafname(__FILE__), (format), __LINE__, __VA_ARGS__) // Emit a warning message. #define LogWarning1(logger, format) \ Log1((logger), (format), ::mega::logWarning) #define LogWarningF(logger, format, ...) \ LogF((logger), (format), ::mega::logWarning, __VA_ARGS__) sdk-10.11.0/include/mega/common/node_event.h000066400000000000000000000015201516266226600205700ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { class NodeEvent { protected: NodeEvent() = default; ~NodeEvent() = default; public: // Is this node a directory? virtual bool isDirectory() const = 0; // What is this node's handle? virtual NodeHandle handle() const = 0; // Retrieve this node's description. virtual NodeInfo info() const = 0; // What is this node's name? virtual const std::string& name() const = 0; // Who is this node's parent? virtual NodeHandle parentHandle() const = 0; // What kind of event is this? virtual NodeEventType type() const = 0; }; // NodeEvent } // common } // mega sdk-10.11.0/include/mega/common/node_event_forward.h000066400000000000000000000002401516266226600223120ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class NodeEvent; using NodeEventVector = std::vector; } // common } // mega sdk-10.11.0/include/mega/common/node_event_observer.h000066400000000000000000000011151516266226600224770ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { class NodeEventObserver { protected: NodeEventObserver() = default; NodeEventObserver(const NodeEventObserver& other) = delete; public: virtual ~NodeEventObserver() = default; NodeEventObserver& operator=(const NodeEventObserver& rhs) = default; // Called by the client when nodes have changed in the cloud. virtual void updated(NodeEventQueue& events) = 0; }; // NodeEventObserver } // common } // mega sdk-10.11.0/include/mega/common/node_event_observer_forward.h000066400000000000000000000001441516266226600242240ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class NodeEventObserver; } // common } // mega sdk-10.11.0/include/mega/common/node_event_queue.h000066400000000000000000000012171516266226600217770ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { class NodeEventQueue { protected: NodeEventQueue() = default; ~NodeEventQueue() = default; public: // Is the queue empty? virtual bool empty() const = 0; // Retrieve a reference to the first event in the queue. virtual const NodeEvent& front() const = 0; // Pop the first event from the queue. virtual void pop_front() = 0; // How many events are in the queue? virtual std::size_t size() const = 0; }; // NodeEventQueue } // common } // mega sdk-10.11.0/include/mega/common/node_event_queue_forward.h000066400000000000000000000001411516266226600235160ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class NodeEventQueue; } // common } // mega sdk-10.11.0/include/mega/common/node_event_type.h000066400000000000000000000012731516266226600216360ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { #define DEFINE_NODE_EVENT_TYPES(expander) \ expander(ADDED) \ expander(MODIFIED) \ expander(MOVED) \ expander(PERMISSIONS) \ expander(REMOVED) enum NodeEventType : unsigned int { #define DEFINE_NODE_EVENT_TYPE_ENUMERANT(name) NODE_EVENT_ ## name, DEFINE_NODE_EVENT_TYPES(DEFINE_NODE_EVENT_TYPE_ENUMERANT) #undef DEFINE_NODE_EVENT_TYPE_ENUMERANT }; // NodeEventType #define PLUS1(name) + 1 constexpr std::size_t NUM_NODE_EVENT_TYPES = DEFINE_NODE_EVENT_TYPES(PLUS1); #undef PLUS1 const char* toString(NodeEventType type); } // common } // mega sdk-10.11.0/include/mega/common/node_event_type_forward.h000066400000000000000000000001561516266226600233610ustar00rootroot00000000000000#pragma once namespace mega { namespace common { enum NodeEventType : unsigned int; } // common } // mega sdk-10.11.0/include/mega/common/node_info.h000066400000000000000000000006061516266226600204060ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { struct NodeInfo { NodeHandle mHandle; bool mIsDirectory; m_time_t mModified; std::string mName; NodeHandle mParentHandle; accesslevel_t mPermissions; m_off_t mSize; }; // NodeInfo } // common } // mega sdk-10.11.0/include/mega/common/node_info_forward.h000066400000000000000000000004311516266226600221260ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { struct NodeInfo; using NodeInfoList = std::list; using NodeInfoPtr = std::unique_ptr; using NodeInfoVector = std::vector; } // common } // mega sdk-10.11.0/include/mega/common/node_key_data.h000066400000000000000000000006231516266226600212330ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { struct NodeKeyData { std::optional mChatAuth; std::string mKeyAndIV; std::optional mPrivateAuth; std::optional mPublicAuth; bool mIsPublicHandle; }; // NodeKeyData } // common } // mega sdk-10.11.0/include/mega/common/node_key_data_forward.h000066400000000000000000000001361516266226600227560ustar00rootroot00000000000000#pragma once namespace mega { namespace common { struct NodeKeyData; } // common } // mega sdk-10.11.0/include/mega/common/normalized_path.h000066400000000000000000000010531516266226600216230ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { class NormalizedPath : public LocalPath { public: using LocalPath::operator==; using LocalPath::operator!=; NormalizedPath() = default; NormalizedPath(const NormalizedPath& other) = default; NormalizedPath(const LocalPath& other); NormalizedPath& operator=(const NormalizedPath& rhs) = default; NormalizedPath& operator=(const LocalPath& rhs); }; // NormalizedPath } // common } // mega sdk-10.11.0/include/mega/common/normalized_path_forward.h000066400000000000000000000001411516266226600233440ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class NormalizedPath; } // common } // mega sdk-10.11.0/include/mega/common/partial_download.h000066400000000000000000000012221516266226600217640ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { class PartialDownload { protected: PartialDownload() = default; public: virtual ~PartialDownload() = default; // Begin the partial download. virtual void begin() = 0; // Cancel the partial download. virtual bool cancel() = 0; // Is this download cancellable? virtual bool cancellable() const = 0; // Has the download been cancelled? virtual bool cancelled() const = 0; // Has this download completed? virtual bool completed() const = 0; }; // PartialDownload } // common } // mega sdk-10.11.0/include/mega/common/partial_download_callback.h000066400000000000000000000023231516266226600236030ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { class Error; namespace common { class PartialDownloadCallback { protected: PartialDownloadCallback() = default; public: // Indicates the download should be aborted. struct Abort {}; // Abort // Indicates that the download should continue. struct Continue {}; // Continue // Indicates the download should be retried. struct Retry { Retry(deciseconds when): mWhen(when) {} // When the download should be retried. const deciseconds mWhen; }; // Retry virtual ~PartialDownloadCallback() = default; // Called when the download has completed. virtual void completed(Error result) = 0; // Called repeatedly as data is downloaded from the cloud. virtual auto data(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::variant = 0; // Called when the download has failed. virtual auto failed(Error result, int retries) -> std::variant = 0; }; // PartialDownloadCallback } // common } // mega sdk-10.11.0/include/mega/common/partial_download_callback_forward.h000066400000000000000000000001511516266226600253240ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class PartialDownloadCallback; } // common } // mega sdk-10.11.0/include/mega/common/partial_download_forward.h000066400000000000000000000003611516266226600235130ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class PartialDownload; using PartialDownloadPtr = std::shared_ptr; using PartialDownloadWeakPtr = std::weak_ptr; } // common } // mega sdk-10.11.0/include/mega/common/pending_callbacks.h000066400000000000000000000102131516266226600220640ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include namespace mega { namespace common { class PendingCallbacks { class Context; // Convenience. using ContextPtr = std::shared_ptr; using ContextSet = std::set; // Represents a cancellable callback. class Context { // The pending callbacks instance that contains us. PendingCallbacks& mPendingCallbacks; protected: // Constructs a new instance. explicit Context(PendingCallbacks& pendingCallbacks); public: // Destroys an existing instance. virtual ~Context(); // Cancel the callback represented by this context. virtual void cancel() = 0; // Remove this context from the pending callbacks instance that owns it. bool remove(const ContextPtr& context); }; // Context // Represents a specific kind of cancellable callback. template class SpecificContext : public Context { // Sanity. static_assert(IsErrorLike::value, ""); // The callback this context represents. std::function mCallback; public: // Constructs a new instance. SpecificContext(std::function callback, PendingCallbacks& pendingCallbacks) : Context(pendingCallbacks) , mCallback(std::move(callback)) { // Sanity. assert(mCallback); } // Cancels the callback represented by this context. void cancel() override { if constexpr (IsErrorOr::value) invoke(unexpected(API_EINCOMPLETE)); else invoke(API_EINCOMPLETE); } // Invoke the callback represented by this context. void invoke(T result) { // Latch callback. auto callback = std::move(mCallback); // Forward result to callback. callback(std::move(result)); } }; // SpecificContext // Convenience. template using SpecificContextWeakPtr = std::weak_ptr>; // Tracks any pending contexts. ContextSet mContexts; // Serializes access to mCallbacks. std::mutex mLock; public: // Construct a new instance. PendingCallbacks(); // Destroy an existing instance. ~PendingCallbacks(); // Cancel any pending callbacks. void cancel(); // Wrap a callback such that it can be cancelled. template> auto wrap(std::function callback) -> typename std::enable_if>::type { // Sanity. assert(callback); // Wrap callback in a context. auto context = std::make_shared>( std::move(callback), *this); // Forwards result to the callback's context. auto wrapper = [](SpecificContextWeakPtr cookie, T result) { // Check if the context is still alive. auto context = cookie.lock(); // Context isn't alive. if (!context) return; // Remove the context from its owner. auto removed = context->remove(context); // Context was already removed. if (!removed) return; // Forward result to wrapped user callback. context->invoke(std::move(result)); }; // wrapper // Wrap context in another callback. callback = std::bind(std::move(wrapper), context, std::placeholders::_1); // Acquire lock. std::lock_guard guard(mLock); // Add context to pending context set. mContexts.emplace(std::move(context)); // Return wrapper to caller. return callback; } }; // PendingCallbacks } // common } // mega sdk-10.11.0/include/mega/common/query.h000066400000000000000000000107451516266226600176200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include struct sqlite3; struct sqlite3_stmt; namespace mega { namespace common { class Field { void match(const int expected) const; std::string string() const; int type() const; std::uint64_t uint64() const; const int mIndex; Query& mQuery; public: Field(const int index, Query& query); Field(const Field& other) = default; template auto get() const -> std::enable_if_t, T> { return SerializationTraits::from(*this); } template, typename V = HasSerializationTraits> auto get() const -> std::enable_if_t { return static_cast(uint64()); } template auto get() const -> std::enable_if_t, T> { return string(); } bool null() const; }; // Field class Parameter { auto null() -> Parameter&; auto string(const char* data, std::size_t length) -> Parameter&; auto uint64(const std::uint64_t value) -> Parameter&; const int mIndex; Query& mQuery; public: Parameter(const int index, Query& query); Parameter(const Parameter& other) = default; template auto set(const T& value) -> std::enable_if_t, Parameter&> { return SerializationTraits::to(*this, value), *this; } template, typename V = HasSerializationTraits> auto set(T value) -> std::enable_if_t { return uint64(static_cast(value)); } template auto set(const T& value) -> std::enable_if_t, Parameter&> { return string(value.c_str(), value.size()); } template auto set(const T* value) -> std::enable_if_t, Parameter&> { return string(value, std::strlen(value)); } template auto set(T) -> std::enable_if_t, Parameter&> { return null(); } }; // Parameter struct Query { Query(Badge badge, Database& database); Query(Query&& other); ~Query(); Query& operator=(Query&& rhs); Query& operator=(const std::string& rhs); Query& operator=(const char* rhs); operator bool() const; Query& operator++(); bool operator!() const; std::uint64_t changed() const; void clear(); bool execute(); auto field(const std::string& name) -> Field; auto field(const char* name) -> Field; std::uint64_t lastID() const; Logger& logger() const; auto param(const std::string& name) -> Parameter; auto param(const char* name) -> Parameter; void reset(); void swap(Query& other); private: friend class Field; friend class Parameter; sqlite3* database() const; bool execute(const char* prefix); Database* mDB; bool mHasNext; std::map mFields; std::map mParameters; sqlite3_stmt* mStatement; }; // Query template<> struct SerializationTraits { static LocalPath from(const Field& field); static void to(Parameter& parameter, const LocalPath& value); }; // SerializationTraits template<> struct SerializationTraits { static NodeHandle from(const Field& field); static void to(Parameter& parameter, const NodeHandle& value); }; // SerializationTraits template struct SerializationTraits> { static std::optional from(const Field& field) { if (!field.null()) return std::optional(std::in_place, field.get()); return std::nullopt; } static void to(Parameter& parameter, const std::optional& value) { if (value) return parameter.set(*value), void(); parameter.set(nullptr); } }; // SerializationTraits> } // common } // mega sdk-10.11.0/include/mega/common/query_forward.h000066400000000000000000000002571516266226600213410ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class Field; class Parameter; struct Query; } // common } // mega sdk-10.11.0/include/mega/common/scoped_query.h000066400000000000000000000017761516266226600211610ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace common { class ScopedQuery { Query* mQuery; public: ScopedQuery(); ScopedQuery(Badge badge, Query& query); ScopedQuery(ScopedQuery&& other); ~ScopedQuery(); ScopedQuery& operator=(ScopedQuery&& rhs); operator bool() const; ScopedQuery& operator++(); bool operator!() const; std::uint64_t changed() const; void clear(); bool execute(); Field field(const std::string& name); Field field(const char* name); std::uint64_t lastID() const; Parameter param(const std::string& name); Parameter param(const char* name); void reset(); Query& query(); void swap(ScopedQuery& other); }; // ScopedQuery void swap(ScopedQuery& lhs, ScopedQuery& rhs); } // common } // mega sdk-10.11.0/include/mega/common/scoped_query_forward.h000066400000000000000000000001361516266226600226720ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class ScopedQuery; } // common } // mega sdk-10.11.0/include/mega/common/serialization_traits.h000066400000000000000000000004431516266226600227100ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { template struct HasSerializationTraits : IsComplete> { }; // HasSerializationTraits } // common } // mega sdk-10.11.0/include/mega/common/serialization_traits_forward.h000066400000000000000000000004331516266226600244330ustar00rootroot00000000000000#pragma once namespace mega { namespace common { template struct HasSerializationTraits; template static constexpr auto HasSerializationTraitsV = HasSerializationTraits::value; template struct SerializationTraits; } // common } // mega sdk-10.11.0/include/mega/common/shared_mutex.h000066400000000000000000000047531516266226600211450ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace mega { namespace common { class SharedMutex { // Try to acquire shared ownership of this mutex. bool try_lock_shared_until(std::chrono::steady_clock::time_point time, bool validate); // How many threads own this mutex? // // >0 One or more readers own this mutex. // 0 No one owns this mutex. // <0 A writer owns this mutex. std::int64_t mCounter = 0; // Serializes access to instance members. std::mutex mLock; // Used to wake potential readers. std::condition_variable mReaderCV; #ifndef NDEBUG // What readers own this lock? std::map mReaders; #endif // !NDEBUG // Used to wake potential writers. std::condition_variable mWriterCV; // What thread owns this mutex? std::thread::id mWriterID; public: // Acquire shared ownership of this mutex. void lock_shared(); // Acquire exclusive ownership of this mutex. void lock(); // Try to acquire shared ownership of this mutex. bool try_lock_shared(); // Try to acquire shared ownership of this mutex. template bool try_shared_lock_for(std::chrono::duration duration) { auto now = std::chrono::steady_clock::now(); return try_lock_shared_until(now + duration); } // Try to acquire shared ownership of this mutex. bool try_lock_shared_until(std::chrono::steady_clock::time_point time) { return try_lock_shared_until(time, false); } // Try to acquire exclusive ownership of this mutex. bool try_lock(); // Try to acquire exclusive ownership of this mutex. template bool try_lock_for(std::chrono::duration duration) { auto now = std::chrono::steady_clock::now(); return try_lock_until(now + duration); } // Try to acquire exclusive ownership of this mutex. bool try_lock_until(std::chrono::steady_clock::time_point time); // Translate exclusive ownership of this mutex to shared ownership. SharedMutex& unique_to_shared(); // Release exclusive ownership of this mutex. void unlock(); // Release shared ownership of this mutex. void unlock_shared(); }; // SharedMutex } // common } // mega sdk-10.11.0/include/mega/common/shared_mutex_forward.h000066400000000000000000000001361516266226600226600ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class SharedMutex; } // common } // mega sdk-10.11.0/include/mega/common/status_flag.h000066400000000000000000000006761516266226600207710ustar00rootroot00000000000000#pragma once namespace mega { namespace common { enum StatusFlag : unsigned int { // The operation can be cancelled. SF_CANCELLABLE = 0x1, // The operation has been cancelled. SF_CANCELLED = 0x2, // The operation has completed. SF_COMPLETED = 0x4, // The operation is in progress. SF_IN_PROGRESS = 0x8, }; // StatusFlag // A combination of status flags. using StatusFlags = unsigned int; } // common } // mega sdk-10.11.0/include/mega/common/subsystem_logger.h000066400000000000000000000013441516266226600220430ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { class SubsystemLogger : public Logger { // The logger's current log level. // // The logger will emit messages if and only if the message's log level // is less than or equal to the logger's current log level. std::atomic mLogLevel; public: explicit SubsystemLogger(const char* name); // Set the logger's log level. void logLevel(LogLevel level); // Query the logger's log level. LogLevel logLevel() const; // Check whether messages at this severity have been masked. bool masked(int severity) const override; }; // SubsystemLogger } // common } // mega sdk-10.11.0/include/mega/common/task_executor.h000066400000000000000000000047611516266226600213340ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { class TaskExecutor { // Executes queued tasks when appropriate. class Worker; // Convenience. using WorkerPtr = std::unique_ptr; using WorkerList = std::list; // Tracks how many workers are waiting for work. std::size_t mAvailableWorkers; // Signalled when we want our worker's attention. std::condition_variable mCV; // Controls how we spawn our workers and how they behave. TaskExecutorFlags mFlags; // Serializes access to instance members. mutable std::mutex mLock; // What logger should we use? Logger& mLogger; // Tracks what tasks we've queued. TaskQueue mTaskQueue; // Lets the workers know when they should terminate. bool mTerminating; // Tracks who our workers are. WorkerList mWorkers; // Called on worker thread start virtual void workerStarted(std::thread::id){}; // Called on worker thread stop virtual void workerStopped(std::thread::id){}; public: explicit TaskExecutor(const TaskExecutorFlags& flags, Logger& logger); virtual ~TaskExecutor(); // Execute a task at some point in time. Task execute(std::function function, std::chrono::steady_clock::time_point when, bool spawnWorker); // Execute a task at some point in the future. template Task execute(std::function function, std::chrono::duration when, bool spawnWorker) { return execute(std::move(function), std::chrono::steady_clock::now() + when, spawnWorker); } // Execute a task now. Task execute(std::function function, bool spawnWorker) { return execute(std::move(function), std::chrono::steady_clock::now(), spawnWorker); } // Update this executor's flags. void flags(const TaskExecutorFlags& flags); // Retrieve this executor's flags. TaskExecutorFlags flags() const; }; // TaskExecutor } // common } // mega sdk-10.11.0/include/mega/common/task_executor_flags.h000066400000000000000000000007531516266226600225050ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { struct TaskExecutorFlags { // How long should a worker stay idle before it quits? std::chrono::seconds mIdleTime = std::chrono::seconds(16); // Maximum number of worker threads. std::size_t mMaxWorkers = 16; // Minimum number of worker threads. std::size_t mMinWorkers = 0; }; // TaskExecutorFlags } // common } // mega sdk-10.11.0/include/mega/common/task_executor_flags_forward.h000066400000000000000000000001451516266226600242240ustar00rootroot00000000000000#pragma once namespace mega { namespace common { struct TaskExecutorFlags; } // common } // mega sdk-10.11.0/include/mega/common/task_executor_forward.h000066400000000000000000000001371516266226600230510ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class TaskExecutor; } // common } // mega sdk-10.11.0/include/mega/common/task_queue.h000066400000000000000000000053471516266226600206230ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace common { // Represents a task that has been queued for execution. class Task { // So the queue can access our context. friend class TaskQueue; // Describes our task. TaskContextPtr mContext; public: Task() = default; // Create a task that is to run at some point in time. Task(std::function function, Logger& logger, std::chrono::steady_clock::time_point when); // Create a task that is to run at some point in the future. template Task(std::function function, Logger& logger, std::chrono::duration when) : Task(std::move(function), logger, std::chrono::steady_clock::now() + when) { } // Create a task that should run now. Task(std::function function, Logger& logger) : Task(std::move(function), logger, std::chrono::steady_clock::now()) { } Task(const Task& other) = default; Task(Task&& other) = default; Task& operator=(const Task& rhs) = default; Task& operator=(Task&& rhs) = default; // True if this instance references a task. operator bool() const; // True if this instance does not reference a task. bool operator!() const; // Try and abort the task. bool abort(); // Try and cancel the task. bool cancel(); // Has the task been aborted? bool aborted() const; // Has the task been cancelled? bool cancelled() const; // Try and complete the task. bool complete(); // Has the task been completed? bool completed() const; // Detach ourselves from our referenced task. void reset(); }; // Task class TaskQueue { // True if lhs is due earlier than rhs. static bool earlier(const Task& lhs, const Task& rhs); // Tracks what tasks have been queued. std::deque mTasks; public: TaskQueue(); TaskQueue(const TaskQueue& other) = delete; ~TaskQueue(); TaskQueue& operator=(const TaskQueue& rhs) = delete; // Dequeue a number of tasks. void dequeue(std::deque& tasks, std::size_t count); // Dequeue a task. Task dequeue(); // Have any tasks been queued? bool empty() const; // Queue a task for execution. Task queue(Task task); // Is a task ready for execution? bool ready() const; // When will the next task be ready for execution? std::chrono::steady_clock::time_point when() const; }; // TaskQueue } // common } // mega sdk-10.11.0/include/mega/common/task_queue_forward.h000066400000000000000000000003041516266226600223330ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class Task; class TaskContext; class TaskQueue; using TaskContextPtr = std::shared_ptr; } // common } // mega sdk-10.11.0/include/mega/common/transaction.h000066400000000000000000000023271516266226600207750ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include struct sqlite3; namespace mega { namespace common { class Transaction { // What database is this transaction operating on? Database* mDB; // Is this transaction in progress? bool mInProgress; public: Transaction(); Transaction(Badge badge, Database& database); Transaction(Transaction&& other); ~Transaction(); Transaction& operator=(Transaction&& rhs); // Commit the transaction. void commit(); // Is the transaction in progress? bool inProgress() const; // What logger is associated with this transaction? Logger& logger() const; // Rollback the transaction. void rollback(); // Start a query under this transaction. Query query(); // Start a query under this transaction. ScopedQuery query(Query& query); // Swap this transaction with another. void swap(Transaction& other); }; // Transaction } // common } // mega sdk-10.11.0/include/mega/common/transaction_forward.h000066400000000000000000000001361516266226600225150ustar00rootroot00000000000000#pragma once namespace mega { namespace common { class Transaction; } // common } // mega sdk-10.11.0/include/mega/common/type_traits.h000066400000000000000000000016471516266226600210230ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { template class P, typename U, typename... Us> struct AllOf : std::conjunction, P...> { }; // AllOf, U, Us...> template class P, typename U, typename... Us> struct AnyOf : std::disjunction, P...> { }; // AnyOf, U, Us...> template struct IsComplete : std::false_type { }; // IsComplete template struct IsComplete> : std::true_type { }; // IsComplete::type> template struct IsOneOf : std::disjunction, std::is_same...> { }; // IsOneOf template constexpr auto IsOneOfV = IsOneOf::value; } // common } // mega sdk-10.11.0/include/mega/common/unexpected.h000066400000000000000000000021401516266226600206050ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { namespace detail { template struct IsUnexpected: std::false_type {}; // IsUnexpected template struct IsUnexpected>: std::true_type {}; // IsUnexpected> } // detail template class Unexpected { // The error value we're wrapping. T mValue; public: Unexpected(T&& value): mValue(std::move(value)) {} Unexpected(const T& value): mValue(value) {} T& value() & { return mValue; } T&& value() && { return std::move(mValue); } const T& value() const& { return mValue; } const T&& value() const&& { return std::move(mValue); } }; // Unexpected template struct IsUnexpected: detail::IsUnexpected> {}; // IsUnexpected // For convenience. template auto unexpected(T&& value) { return Unexpected(std::forward(value)); } } // common } // mega sdk-10.11.0/include/mega/common/unexpected_forward.h000066400000000000000000000005461516266226600223410ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace detail { template struct IsUnexpected; } // detail template struct IsUnexpected; template class Unexpected; template constexpr auto IsUnexpectedV = IsUnexpected::value; template auto unexpected(T&& value); } // common } // mega sdk-10.11.0/include/mega/common/upload.h000066400000000000000000000014131516266226600177270ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { class Upload { protected: Upload() = default; public: virtual ~Upload() = default; // Begin the upload. void begin(BoundCallback callback); virtual void begin(UploadCallback callback) = 0; // Cancel the upload. // // Returns true if the upload could be cancelled. virtual bool cancel() = 0; // Query whether an upload was cancelled. virtual bool cancelled() const = 0; // Query whether an upload has completed. virtual bool completed() const = 0; // Query the result of the upload. virtual Error result() const = 0; }; // Upload } // common } // mega sdk-10.11.0/include/mega/common/upload_callbacks.h000066400000000000000000000006321516266226600217300ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { using BoundCallback = std::function)>; using BindCallback = std::function; using UploadResult = BindCallback; using UploadCallback = std::function)>; } // common } // mega sdk-10.11.0/include/mega/common/upload_forward.h000066400000000000000000000002301516266226600214470ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { class Upload; using UploadPtr = std::shared_ptr; } // common } // mega sdk-10.11.0/include/mega/common/utility.h000066400000000000000000000024561516266226600201560ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include namespace mega { namespace common { std::chrono::minutes defaultTimeout(); std::string format(const char* format, ...); std::string formatv(std::va_list arguments, const char* format); std::optional fromCharPointer(const char* maybeString); template using SharedPromise = std::shared_ptr>; template SharedPromise makeSharedPromise() { return std::make_shared>(); } std::int64_t now(); const char* toCharPointer(const std::optional& maybeString); template auto waitFor(std::future future) -> typename std::enable_if::value, T>::type { // Wait for the future's promise to transmit a value. auto status = future.wait_for(defaultTimeout()); // Promise didn't transmit a value in time. if (status == std::future_status::timeout) { if constexpr (IsErrorOr::value) return unexpected(LOCAL_ETIMEOUT); else return LOCAL_ETIMEOUT; } // Return the promise's value to the caller. return future.get(); } } // common } // mega sdk-10.11.0/include/mega/config-android.h000066400000000000000000000155331516266226600200460ustar00rootroot00000000000000/* include/mega/config.h. Generated from config.h.in by configure. */ /* include/mega/config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if you have the header file. */ #define HAVE_ARPA_INET_H 1 /* Define to 1 if you have the `clock_gettime' function. */ #define HAVE_CLOCK_GETTIME 1 /* Define to 1 if you have the header file. */ #define HAVE_CRYPTOPP_CRYPTLIB_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_DB_CXX_H */ /* Define to 1 if you have the header file. */ #define HAVE_DLFCN_H 1 /* Define to 1 if you have the `fdopendir' function. */ #define HAVE_FDOPENDIR 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_FREEIMAGE_H */ /* Define to 1 if you have the header file. */ /* #undef HAVE_HTONL */ /* Define to 1 if you have the `inotify_init' function. */ #define HAVE_INOTIFY_INIT 1 /* Defined if std::int64_t and time_t are distinct. */ /* #undef HAVE_DISTINCT_TIME_T */ /* Defined if std::int64_t and time_t are distinct. */ #ifdef __ANDROID__ #if defined(__arm__) || defined(__i386__) #define HAVE_DISTINCT_TIME_T 1 #endif // __arm__ || __i386__ #endif // __ANDROID__ /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if your system has a GNU libc compatible `malloc' function, and to 0 otherwise. */ #define HAVE_MALLOC 1 /* Define to 1 if you have the header file. */ #define HAVE_MCHECK_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the header file. */ #define HAVE_NETDB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_NETINET_IN_H 1 /* Define to 1 if you have the header file. */ /* #undef HAVE_READLINE_READLINE_H */ /* Define to 1 if you have the `select' function. */ #define HAVE_SELECT 1 /* Define to 1 if you have the `sendfile' function. */ //#define HAVE_SENDFILE 1 /* Define to 1 if you have the header file. */ #define HAVE_SQLITE3_H 1 /* Define to 1 if the system has the type `ssize_t'. */ #define HAVE_SSIZE_T 1 /* Define to 1 if stdbool.h conforms to C99. */ #define HAVE_STDBOOL_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDDEF_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_INOTIFY_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_SOCKET_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_STAT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TIMEB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_UNISTD_H 1 /* Define to 1 if you have the header file. */ #define HAVE_DIRENT_H 1 /* Define to 1 if the system has the type `_Bool'. */ /* #undef HAVE__BOOL */ /* Define to the sub-directory in which libtool stores uninstalled libraries. */ #define LT_OBJDIR ".libs/" /* Name of package */ #define PACKAGE "libmega" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "https://github.com/megaprivacy" /* Define to the full name of this package. */ #define PACKAGE_NAME "libmega" /* Define to the one symbol short name of this package. */ #define PACKAGE_TARNAME "libmega" /* Define to the home page for this package. */ #define PACKAGE_URL "" /* The size of `uint64_t', as computed by sizeof. */ #define SIZEOF_UINT64_T 8 /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if your declares `struct tm'. */ /* #undef TM_IN_SYS_TIME */ /* Define to use libcrypto++ */ #define USE_CRYPTOPP 1 /* Define to use Berkeley DB */ #define USE_DB 0 /* Use inotify API */ #define USE_INOTIFY 1 /* Define to use OpenSSL */ #define USE_OPENSSL 1 /* Define to use SQLite */ #define USE_SQLITE 1 #define USE_PTHREAD 1 /* Version number of package */ #define VERSION "0.2.3" /* Enable large inode numbers on Mac OS X 10.5. */ #ifndef _DARWIN_USE_64_BIT_INODE # define _DARWIN_USE_64_BIT_INODE 1 #endif /* Define to 64 for large file support on some hosts */ #if !(defined(__ANDROID__) && (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < 24) #define _FILE_OFFSET_BITS 64 #endif /* use GNU extensions */ #define _GNU_SOURCE 1 /* Define for large files, on AIX-style hosts. */ /* #undef _LARGE_FILES */ /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT32_T */ /* Define for Solaris 2.5.1 so the uint64_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT64_T */ /* Define for Solaris 2.5.1 so the uint8_t typedef from , , or is not used. If the typedef were allowed, the #define below would cause a syntax error. */ /* #undef _UINT8_T */ /* Force definition of constant macros for C++ */ #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS /**/ #endif /* Force definition of format macros for C++ */ #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS /**/ #endif /* Force definition of limit macros for C++ */ #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS /**/ #endif /* Define to rpl_malloc if the replacement function should be used. */ /* #undef malloc */ /* Define to `long int' if does not define. */ /* #undef off_t */ /* Define to `int' if does not define. */ /* #undef pid_t */ /* Define to `unsigned int' if does not define. */ /* #undef size_t */ /* Define to the type of an unsigned integer type of width exactly 16 bits if such a type exists and the standard includes do not define it. */ /* #undef uint16_t */ /* Define to the type of an unsigned integer type of width exactly 32 bits if such a type exists and the standard includes do not define it. */ /* #undef uint32_t */ /* Define to the type of an unsigned integer type of width exactly 64 bits if such a type exists and the standard includes do not define it. */ /* #undef uint64_t */ /* Define to the type of an unsigned integer type of width exactly 8 bits if such a type exists and the standard includes do not define it. */ /* #undef uint8_t */ sdk-10.11.0/include/mega/console.h000066400000000000000000000016321516266226600166200ustar00rootroot00000000000000/** * @file mega/console.h * @brief Generic class for accessing console * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_CONSOLE_H #define MEGA_CONSOLE_H 1 namespace mega { struct MEGA_API Console { virtual void readpwchar(char*, int, int* pw_buf_pos, char**) = 0; virtual void setecho(bool) = 0; virtual ~Console() { } }; } // namespace #endif sdk-10.11.0/include/mega/crypto/000077500000000000000000000000001516266226600163235ustar00rootroot00000000000000sdk-10.11.0/include/mega/crypto/cryptopp.h000066400000000000000000000771161516266226600203700ustar00rootroot00000000000000/** * @file cryptopp.h * @brief Crypto layer using Crypto++ * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_CRYPTOPP #ifndef CRYPTOCRYPTOPP_H #define CRYPTOCRYPTOPP_H 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { /** * @brief A generic pseudo-random number generator. */ class MEGA_API PrnGen : public CryptoPP::AutoSeededRandomPool { public: /** * @brief Generates a block of random bytes of length `len` into a buffer * `buf`. * * @param buf The buffer that takes the generated random bytes. Ensure that * the buffer is of sufficient size to take `len` bytes. * @param len The number of random bytes to generate. */ void genblock(byte* buf, size_t len); /** * @brief Generates a random integer between 0 ... max - 1. * * @param max The maximum of which the number is to generate under. * @return The random number generated. */ uint32_t genuint32(uint64_t max); /** * @brief Generates a string of len random bytes. * * @return A string of len random bytes. */ std::string genstring(const size_t len); }; // symmetric cryptography: AES-128 class MEGA_API SymmCipher { private: CryptoPP::ECB_Mode::Encryption aesecb_e; CryptoPP::ECB_Mode::Decryption aesecb_d; std::optional::Encryption> mAescbc_e; std::optional::Decryption> mAescbc_d; std::optional::Encryption> mAesccm16_e; std::optional::Decryption> mAesccm16_d; std::optional::Encryption> mAesccm8_e; std::optional::Decryption> mAesccm8_d; std::optional::Encryption> mAesgcm_e; std::optional::Decryption> mAesgcm_d; /** * @brief Primary template: expression not detected. * * @tparam AlwaysVoid SFINAE helper. * @tparam Op Template alias that represents the expression to test. * @tparam Args Optional extra template parameters. */ template class Op, class = void> struct detect: std::false_type {}; /** * @brief Partial specialisation: expression detected (substitution succeeds). */ template class Op> struct detect>>: std::true_type {}; /** * @brief Detects presence of SetKeyWithIV(key, keyLen, iv, ivLen). * This overload exists in GCM/CCM cipher modes. */ template using expr_set4 = decltype(std::declval().SetKeyWithIV(static_cast(nullptr), std::size_t{}, static_cast(nullptr), std::size_t{})); /** * @brief Detects presence of Resynchronize(iv, ivLen). * Again specific to GCM/CCM. */ template using expr_resync2 = decltype(std::declval().Resynchronize(static_cast(nullptr), std::size_t{})); /** * @brief Ensure that an optional Crypto++ cipher is ready for use and prepare/resync it as * needed. * * The first call after a setkey() will construct the cipher object and * perform a full key schedule (SetKeyWithIV). Subsequent calls reuse the * schedule and only resynchronise the IV - unless the caller supplies a * different customKey, in which case the schedule is rebuilt. * * @tparam OptCipher std::optional holding a Crypto++ cipher type. * @param opt Optional cipher instance to prepare. * @param iv IV/nonce to use (may be nullptr; defaults to @c zeroiv). * @param ivLen Length of @p iv in bytes. CBC ignores this parameter. * @param customKey Optional key buffer. If nullptr or length is 0, the * existing key schedule is reused. * @param customKeyLen Length of @p customKey in bytes (ignored when @p customKey is nullptr). * @return Reference to the engaged cipher object (never null). */ template typename OptCipher::value_type& prepareCipher(OptCipher& opt, const byte* iv, const size_t ivLen, const byte* customKey = nullptr, const size_t customKeyLen = 0) { const byte* useIV = iv ? iv : zeroiv; auto callSet = [&](auto& cipher, const byte* k, const size_t kLen) { using C = std::decay_t; if constexpr (detect::value) cipher.SetKeyWithIV(k, kLen, useIV, ivLen); else cipher.SetKeyWithIV(k, kLen, useIV); }; auto callSync = [&](auto& cipher) { using C = std::decay_t; if constexpr (detect::value) cipher.Resynchronize(useIV, static_cast(ivLen)); else cipher.Resynchronize(useIV); }; if (!opt) { opt.emplace(); const byte* k = customKey ? customKey : key; const size_t kLen = customKey ? customKeyLen : KEYLENGTH; callSet(*opt, k, kLen); return *opt; } if (customKey && customKeyLen) callSet(*opt, customKey, customKeyLen); else callSync(*opt); return *opt; } /** * @brief Authenticated symmetric encryption using AES in GCM mode. * * This method sets the encryption key and the initialization vector in the symetric cipher * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be encrypted. * @param datasize Size of the data to be encrypted. * @param encryptionKey Encryption key * @param keylength Length of encryption key * @param additionalData Additional data for extra authentication * @param additionalDatalen Length of additional data * @param iv Initialisation vector or nonce to use for encryption. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. * @param result outputData data, including the authentication tag. * @param expectedSize expected size for the encrypted data * @return true if encryption proccess ends succesfully, otherwise returns false */ bool gcm_encrypt(const byte* data, const size_t datasize, const byte* encryptionKey, const size_t keylen, const byte* additionalData, const size_t additionalDatalen, const byte* iv, const size_t ivlen, const size_t taglen, std::string& result, const size_t expectedSize); /** * @brief Authenticated symmetric decryption using AES in GCM mode * * The size of the IV limits the maximum length of data. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be decrypted. * @param datalen Size of data to be decrypted. * @param additionalData Additional data for extra authentication * @param additionalDatalen Length of additional data * @param decryptionKey Decryption key * @param keylength Decryption key length * @param tag authentication tag * @param taglen Length of expected authentication tag. Allowed sizes are 4, 8 and 16 bytes. * @param iv Initialization vector or nonce. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param result Decrypted data, not including the authentication tag. * @param resultSize size of Decrypted data, not including the authentication tag. * @return true if decryption proccess ends succesfully, otherwise returns false */ bool gcm_decrypt(const byte* data, const size_t datalen, const byte* additionalData, const size_t additionalDatalen, const byte* decryptionKey, const size_t keylength, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize); public: static byte zeroiv[CryptoPP::AES::BLOCKSIZE]; static const int BLOCKSIZE = CryptoPP::AES::BLOCKSIZE; static const int KEYLENGTH = CryptoPP::AES::BLOCKSIZE; byte key[KEYLENGTH] = {}; typedef uint64_t ctr_iv; // type != 1 will enatil xoring the second KEYLENGTH bytes into the first ones // otherwise only first KEYLENGTH raw bytes will be used. void setkey(const byte*, int type = 1); bool setkey(const std::string*); /** * @brief Encrypt symmetrically using AES in ECB mode. * * @param data Data to be encrypted. * @param dst Target buffer to encrypt to. If NULL, encrypt in-place (to `data`). * @param len Length of data to be encrypted in bytes. Defaults to * SymCipher::BLOCKSIZE. */ void ecb_encrypt(byte*, byte* = NULL, size_t = BLOCKSIZE); /** * @brief Decrypt symmetrically using AES in ECB mode. * * @param data Data to be decrypted (in-place). * @param len Length of data to be decrypted in bytes. Defaults to * SymCipher::BLOCKSIZE. */ void ecb_decrypt(byte*, size_t = BLOCKSIZE); /** * @brief Encrypt symmetrically using AES in CBC mode. * * The size of the IV is one block in AES-128 (16 bytes). * * @param data Data to be encrypted (encryption in-place). * @param len Length of data to be encrypted in bytes. * @param iv Initialisation vector to use. Choose randomly and never re-use. * @return true if encryption proccess ends succesfully, otherwise returns false */ bool cbc_encrypt(byte* data, size_t len, const byte* iv = NULL); /** * @brief Encrypt symmetrically using AES in CBC mode. * * This method also sets the encryption key and the initialization vector in the symetric cipher * in one step * * The size of the IV is one block in AES-128 (16 bytes). * * @param plain Data to be encrypted * @param cipher Resulting encrypted data * @param encryptionKey Encryption key * @param keylength Length of encryption key * @param iv Initialization vector to use. * @return true if encryption proccess ends succesfully, otherwise returns false */ bool cbc_encrypt_with_key(const std::string& plain, std::string& cipher, const byte* encryptionKey, const size_t keylen, const byte* iv = nullptr); /** * @brief Decrypt symmetrically using AES in CBC mode. * * The size of the IV is one block in AES-128 (16 bytes). * * @param data Data to be decrypted (encryption in-place). * @param len Length of cipher text to be decrypted in bytes. * @param iv Initialisation vector. * * @return true if decryption proccess ends succesfully, otherwise returns false */ bool cbc_decrypt(byte* data, size_t len, const byte* iv = NULL); /** * @brief Decrypt symmetrically using AES in CBC mode. * * This method also sets the encryption key and the initialization vector in the symetric cipher * in one step * * The size of the IV is one block in AES-128 (16 bytes). * * @param cipher Encrypted data to be decrypted * @param plain Resulting decrypted data * @param decryptionKey Decryption key * @param keylength Length of encryption key * @param iv Initialization vector to use. * @return true if decryption proccess ends succesfully, otherwise returns false */ bool cbc_decrypt_with_key(const std::string& cipher, std::string& plain, const byte* decryptionKey, const size_t keylen, const byte* iv = nullptr); /** * @brief Encrypt symmetrically using AES in CBC mode and pkcs padding * * The size of the IV is one block in AES-128 (16 bytes). * * @param data Data to be encrypted * @param iv Initialisation vector. * @param result Encrypted message * * @return true if encryption proccess ends succesfully, otherwise returns false */ bool cbc_encrypt_pkcs_padding(const std::string *data, const byte* iv, std::string *result); /** * @brief Decrypt symmetrically using AES in CBC mode and pkcs padding * * The size of the IV is one block in AES-128 (16 bytes). * * @param data Data to be decrypted * @param iv Initialisation vector. * @param result Where we should write the decrypted message. * * @return true if decryption was successful. */ bool cbc_decrypt_pkcs_padding(const std::string* data, const byte* iv, std::string* result); /** * @brief Decrypt symmetrically using AES in CBC mode and pkcs padding * * The size of the IV is one block in AES-128 (16 bytes). * * @param data Data to be decrypted * @param dataLength Length of data to be decryped. * @param iv Initialisation vector. * @param result Where we should write the decrypted message. * * @return true if decryption was successful. */ bool cbc_decrypt_pkcs_padding(const byte* data, const size_t dataLength, const byte* iv, std::string* result); /** * @brief Authenticated symmetric encryption using AES in CCM mode (counter with CBC-MAC). * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be encrypted. * @param iv Initialisation vector or nonce to use for encryption. Choose randomly * and never re-use. See note on size above. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. Allowed sizes are 8 and 16 bytes. * @param result Encrypted data, including the authentication tag. * * @return true if encryption was successful. */ bool ccm_encrypt(const std::string *data, const byte *iv, unsigned ivlen, unsigned taglen, std::string *result); /** * @brief Authenticated symmetric decryption using AES in CCM mode (counter with CBC-MAC). * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be decrypted. * @param iv Initialisation vector or nonce. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. Allowed sizes are 8 and 16 bytes. * @param result Decrypted data, not including the authentication tag. * * @return true if decryption was successful. */ bool ccm_decrypt(const std::string *data, const byte *iv, unsigned ivlen, unsigned taglen, std::string *result); /** * @brief Authenticated symmetric encryption using AES in GCM mode. * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be encrypted. * @param iv Initialisation vector or nonce to use for encryption. Choose randomly * and never re-use. See note on size above. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. * @param result Encrypted data, including the authentication tag. * @return true if encryption proccess ends succesfully, otherwise returns false */ bool gcm_encrypt(const std::string *data, const byte *iv, unsigned ivlen, unsigned taglen, std::string *result); /** * @brief Authenticated symmetric encryption using AES in GCM mode with additional authenticated data. * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be encrypted. * @param datasize Size of the data to be encrypted. * @param additionalData Additional data for extra authentication * @param additionalDatalen Length of additional data * @param iv Initialisation vector or nonce to use for encryption. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. * @param result outputData data, including the authentication tag. * @param expectedSize expected size for the encrypted data * @return true if encryption proccess ends succesfully, otherwise returns false */ bool gcm_encrypt_add(const byte* data, const size_t datasize, const byte* additionalData, const size_t additionalDatalen, const byte* iv, const size_t ivlen, const size_t taglen, std::string& result, const size_t expectedSize); /** * @brief Authenticated symmetric decryption using AES in GCM mode. * * The size of the IV limits the maximum length of data. A length of 12 bytes * allows for up to 16.7 MB data size. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be decrypted. * @param iv Initialisation vector or nonce. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param taglen Length of expected authentication tag. Allowed sizes are 8 and 16 bytes. * @param result Decrypted data, not including the authentication tag. * @return true if decryption proccess ends succesfully, otherwise returns false */ bool gcm_decrypt(const std::string *data, const byte *iv, unsigned ivlen, unsigned taglen, std::string *result); /** * @brief Authenticated symmetric decryption using AES in GCM mode with additional authenticated data. * * The size of the IV limits the maximum length of data. Smaller IVs lead to larger maximum data sizes. * * @param data Data to be decrypted. * @param datalen Size of data to be decrypted. * @param additionalData Additional data for extra authentication * @param additionalDatalen Length of additional data * @param tag authentication tag * @param taglen Length of expected authentication tag. Allowed sizes are 4, 8 and 16 bytes. * @param iv Initialisation vector or nonce. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param result Decrypted data, not including the authentication tag. * @param resultSize size of Decrypted data, not including the authentication tag. * @return true if decryption proccess ends succesfully, otherwise returns false */ bool gcm_decrypt_add(const byte* data, const size_t datalen, const byte* additionalData, const size_t additionalDatalen, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize); // keep the alias with typo of the function above for backwards compatibility bool gcm_decrypt_aad(const byte* data, const size_t datalen, const byte* additionalData, const size_t additionalDatalen, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize) { return gcm_decrypt_add(data, datalen, additionalData, additionalDatalen, tag, taglen, iv, ivlen, result, resultSize); } /** * @brief Authenticated symmetric decryption using AES in GCM mode * * This method sets the decryption key and the initialization vector in the symetric cipher * The size of the IV limits the maximum length of data. Smaller IVs lead to larger maximum data * sizes. * * @param data Data to be decrypted. * @param datalen Size of data to be decrypted. * @param decryptionKey Decryption key * @param keylength Decryption key length * @param tag authentication tag * @param taglen Length of expected authentication tag. Allowed sizes are 4, 8 and 16 bytes. * @param iv Initialization vector or nonce. * @param ivlen Length of IV. Allowed sizes are 7, 8, 9, 10, 11, 12, and 13 bytes. * @param result Decrypted data, not including the authentication tag. * @param resultSize size of Decrypted data, not including the authentication tag. * @return true if decryption proccess ends succesfully, otherwise returns false */ bool gcm_decrypt_with_key(const byte* data, const size_t datalen, const byte* decryptionKey, const size_t keylength, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize); /** * @brief Serialize key for compatibility with the webclient * * The key is serialized to a JSON array like this one: * "[669070598,-250738112,2059051645,-1942187558]" * * @param d string that receives the serialized key */ void serializekeyforjs(std::string *); void ctr_crypt(byte *, unsigned, m_off_t, ctr_iv, byte *mac, bool encrypt, bool initmac = true); static void setint64(int64_t, byte*); static void xorblock(const byte*, byte*); static void xorblock(const byte*, byte*, int); static void incblock(byte*, unsigned = BLOCKSIZE); /** * @brief Check whether a key is a zerokey, or was generated with a zerokey * * This applies to keys whose length is: * SymmCipher:BLOCKSIZE (16) (used for generator keys, ex: Transfer::transferkey; also folder node keys and general node keys) * FILENODEKEYLENGTH (32 or 2*SymmCipher::BLOCKSIZE) -> AES32 file/node keys * * @param key Encryption/Decryption key * @param keySize Encryption/Decryption key length (expected BLOCKSIZE or BLOCKSIZE*2) * @return true if the key is a zero key or was generated with a zero key */ static bool isZeroKey(const byte* key, size_t keySize); SymmCipher() { } SymmCipher(const SymmCipher& ref); SymmCipher& operator=(const SymmCipher& ref); SymmCipher(const byte*); }; /** * @brief Asymmetric cryptography using RSA. */ class MEGA_API AsymmCipher { public: enum { PRIV_P, PRIV_Q, PRIV_D, PRIV_U }; enum { PUB_PQ, PUB_E }; static const int PRIVKEY = 4; static const int PRIVKEY_SHORT = 3; static const int PUBKEY = 2; using Key = CryptoPP::Integer[PRIVKEY]; static const int MAXKEYLENGTH = 1026; // in bytes, allows for RSA keys up // to 8192 bits /** * @brief Retrieve a reference to the specified key component. * * @param component Identifies the key component you want to reference. * Possible values: * - For private keys: PRIV_P, PRIV_Q, PRIV_D, PRIV_U * - For public keys: PUB_PQ, PUB_E * * @return A reference to the specified key component. */ const CryptoPP::Integer& getKey(unsigned component) const; /** * @brief Retrieve a reference to this cipher's key material. * * @return A reference to this cipher's key material. */ auto getKey() const -> const Key&; /** * @brief Sets a key from a buffer. * * @param numints Number of integers for key type (AsymmCipher::PRIVKEY * or AsymmCipher::PUBKEY). * @param data Buffer containing the serialised key. * @param len Length of data in buffer. * @return Number of bytes encrypted, 0 on failure. */ int setkey(int numints, const byte* data, int len); /** * @brief Reset the existing key */ void resetkey(); /** * @brief Simple check for validity of key pair. * * @param keytype Key type indication by number of integers for key type * (AsymmCipher::PRIVKEY or AsymmCipher::PUBKEY). * @return false on an invalid key pair, true if valid. */ bool isvalid(int keytype = PUBKEY) const; /** * @brief Encrypts a randomly padded plain text into a buffer. * * @param rng Reference to the random block generator * @param plain The plain text to encrypt. * @param plainlen Length of the plain text. * @param buf Buffer to take the cipher text.. * @param buflen Length of the cipher text. * @return Number of bytes encrypted, 0 on failure. */ int encrypt(PrnGen &rng, const byte* plain, size_t plainlen, byte* buf, size_t buflen) const; /** * @brief Decrypts a cipher text into a buffer and strips random padding. * * @param cipher The cipher text to encrypt. * @param cipherlen Length of the cipher text. * @param buf Buffer to take the plain text.. * @param buflen Length of the plain text. * @return Always returns 1. */ int decrypt(const byte* cipher, size_t cipherlen, byte* buf, size_t buflen) const; /** * @brief Encrypts a plain text into a buffer. * * @param plain The plain text to encrypt. * @param plainlen Length of the plain text. * @param buf Buffer to take the cipher text.. * @param buflen Length of the cipher text. * @return Number of bytes encrypted, 0 on failure. */ unsigned rawencrypt(const byte* plain, size_t plainlen, byte* buf, size_t buflen) const; /** * @brief Decrypts a cipher text into a buffer. * * @param cipher The cipher text to encrypt. * @param cipherlen Length of the cipher text. * @param buf Buffer to take the plain text.. * @param buflen Length of the plain text. * @return Always returns 1. */ unsigned rawdecrypt(const byte* cipher, size_t cipherlen, byte* buf, size_t buflen) const; static void serializeintarray(const CryptoPP::Integer*, int, std::string*, bool headers = true); /** * @brief Serialises a key to a string. * * @param d String to take the key. * @param keytype Key type indication by number of integers for key type * (AsymmCipher::PRIVKEY or AsymmCipher::PUBKEY). */ void serializekey(std::string* d, int keytype) const; /** * @brief Serialize public key for compatibility with the webclient. * * It also add padding (PUB_E size is forced to 4 bytes) in case the * of the key, at reception from server, indicates it has zero-padding. * * @param d String to take the serialized key without size-headers */ void serializekeyforjs(std::string& d) const; /** * @brief Generates an RSA key pair of a given key size. * * @param rng Reference to the random block generator * @param privk Private key. * @param pubk Public key. * @param size Size of key to generate in bits (key strength). */ static void genkeypair(PrnGen &rng, CryptoPP::Integer* privk, CryptoPP::Integer* pubk, int size); /** * @brief Generates an RSA key pair of a given key size and stores the new * private key in this object * * @param rng Reference to the random block generator * @param pubk Where should the public key be stored? * @param size Size of key to generate in bits (key strength). * * @note After this call, the cipher will contain the private key. */ void genkeypair(PrnGen &rng, CryptoPP::Integer* pubk, int size); private: enum Status : unsigned char { // Key's been checked and is invalid. S_INVALID, // Key has yet to be checked. S_UNKNOWN, // Key has been checked and is valid. S_VALID, }; // Status static int decodeintarray(CryptoPP::Integer*, int, const byte*, int); auto isvalid(const Key& keyToConfirm, int type) const -> Status; Key key; unsigned int padding; mutable Status status; }; class MEGA_API Hash { CryptoPP::SHA512 hash; public: void add(const byte*, unsigned); void get(std::string*); }; class MEGA_API HashSHA256 { CryptoPP::SHA256 hash; public: void add(const byte*, unsigned int); void get(std::string*); }; class MEGA_API HashCRC32 { CryptoPP::CRC32 hash; public: void add(const byte*, unsigned); void get(byte*); }; /** * @brief HMAC-SHA256 generator */ class MEGA_API HMACSHA256 { CryptoPP::HMAC< CryptoPP::SHA256 > hmac; public: /** * @brief Constructor * @param key HMAC key * @param length Key length */ HMACSHA256(const byte *key, size_t length); HMACSHA256(); /** * @brief Add data to the HMAC * @param data Data to add * @param len Data length */ void add(const byte* data, size_t len); /** * @brief Compute the HMAC for the current message * @param out The HMAC-SHA256 will be returned in the first 32 bytes of this buffer */ void get(byte *out); /** * @brief Set the HMAC's key. * * @param key HMAC key. * @param length Length of HMAC key. */ void setkey(const byte* key, const size_t length); }; /** * @brief PBKDF2 HMAC-SHA512 Key Derivation Function. */ class MEGA_API PBKDF2_HMAC_SHA512 { CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; public: PBKDF2_HMAC_SHA512(); bool deriveKey(byte* derivedkey, const size_t derivedkeyLen, const byte* pwd, const size_t pwdLen, const byte* salt, const size_t saltLen, const unsigned int iterations) const; }; } // namespace #endif #endif sdk-10.11.0/include/mega/crypto/sodium.h000066400000000000000000000147641516266226600200100ustar00rootroot00000000000000/** * @file sodium.h * @brief Crypto layer using libsodium. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef SODIUM_H #define SODIUM_H 1 #include #include namespace mega { class PrnGen; /** * @brief Asymmetric cryptographic signature using EdDSA with Edwards 25519. */ class MEGA_API EdDSA { public: static const int SEED_KEY_LENGTH = crypto_sign_SEEDBYTES; static const int PUBLIC_KEY_LENGTH = crypto_sign_PUBLICKEYBYTES; // TLV key to access to the corresponding value in the TLV records static const std::string TLV_KEY; bool initializationOK = false; EdDSA(PrnGen &rng, unsigned char* keySeed = NULL); ~EdDSA(); unsigned char keySeed[SEED_KEY_LENGTH]; unsigned char pubKey[PUBLIC_KEY_LENGTH]; /** * @brief Computes the signature of a message. * * @param msg The message to sign. * @param msglen Length of the message. * @param sig Buffer to take the signature. * @return Number of bytes for signed message (msg length + signature), * 0 on failure. */ int sign(const unsigned char* msg, const unsigned long long msglen, unsigned char* sig); /** * @brief Verifies the signature of a message. * * @param msg Text of the message. * @param msglen Length of message. * @param sig Signature of the message * @param pubKey Public key to check the signature. * @return 1 on a valid signature, 0 on a failed verification. */ static int verify(const unsigned char* msg, unsigned long long msglen, const unsigned char* sig, const unsigned char* pubKey); void signKey(const unsigned char* key, const unsigned long long keyLength, std::string *sigBuf, uint64_t ts = 0); static bool verifyKey(const unsigned char* pubk, const unsigned long long pubkLen, const std::string *sig, const unsigned char* singingPubKey); private: static const int PRIVATE_KEY_LENGTH = crypto_sign_SECRETKEYBYTES; unsigned char privKey[PRIVATE_KEY_LENGTH]; // don't use it externally, use keySeed instead }; /** * @brief Asymmetric cryptographic for chat messages encryptiong using * ECDH approach with x25519 key pair. */ class MEGA_API ECDH { public: static const int PRIVATE_KEY_LENGTH = crypto_box_SECRETKEYBYTES; static const int PUBLIC_KEY_LENGTH = crypto_box_PUBLICKEYBYTES; static const int DERIVED_KEY_LENGTH = crypto_scalarmult_BYTES; // TLV key to access to the corresponding value in the TLV records static const std::string TLV_KEY; bool initializationOK = false; ECDH(); // constructs an instance of ECDH and generates a new x25519 key pair ECDH(const std::string &privKey); // initialize the private key (and derive public key) ECDH(const unsigned char *privk, const std::string &pubk); // initialize the private key and public key ECDH(const ECDH& aux); ECDH* copy() const { return new ECDH(*this); } ECDH& operator=(const ECDH& aux) = delete; ECDH(ECDH&& aux) = delete; ECDH& operator=(ECDH&& aux) = delete; ~ECDH(); const unsigned char* getPrivKey() const { return mPrivKey; } const unsigned char* getPubKey() const { return mPubKey; } bool deriveSharedKeyWithSalt(const unsigned char* pubkey, const unsigned char* salt, size_t saltSize, std::string& output) const; /** * @brief Compute symetric key using the stored private and public keys * * @param output Generated symetric key * * @return 1 on success, 0 on failure. */ int computeSymmetricKey(std::string& output) const { return !doComputeSymmetricKey(mPrivKey, mPubKey, output); } /** * @brief encrypt Encrypt a message using the public key of recipient, the * private key of the sender and a nonce (number used once) * * @param encmsg Encrypted text after encryption. This function ensures that the * first crypto_box_ZEROBYTES bytes of the encrypted text msg are all 0. * @param msg Message to be encrypted. Caller must ensure that the first * crypto_box_ZEROBYTES bytes of the message msg are all 0. * @param msglen Lenght of the message to be encrypted. * @param nonce Number used once. The same nonce must never be used to encrypt another * packet from the sender's private key to this receiver's public key or viceversa. * @param pubKey Public key of the receiver. * @param privKey Private key of the sender. * * @return 1 on success, 0 on failure. */ int encrypt(unsigned char* encmsg, const unsigned char* msg, const unsigned long long msglen, const unsigned char* nonce, const unsigned char* pubKey, const unsigned char* privKey); /** * @brief decrypt Decrypt a message using the public key of recipient, the * private key of the sender and a nonce (number used once) * * @param msg Message in plain text after decryption. This function ensures that * the first crypto_box_ZEROBYTES bytes of the message msg are all 0. * @param encmsg Encrypted text to be decrypted. Caller must ensure that the first * crypto_box_ZEROBYTES bytes of the chipered text encmsg are all 0. * @param encmsglen Length of the encrypted text. * @param nonce Number used once. The same nonce must never be used to encrypt another * packet from the sender's private key to this receiver's public key or viceversa. * @param pubKey Public key of the sender. * @param privKey Private key of the receiver. * * @return 1 on success, 0 on failure. */ int decrypt(unsigned char* msg, const unsigned char* encmsg, const unsigned long long encmsglen, const unsigned char* nonce, const unsigned char* pubKey, const unsigned char* privKey); private: int doComputeSymmetricKey(const unsigned char* privk, const unsigned char* pubk, std::string& output) const; unsigned char mPrivKey[PRIVATE_KEY_LENGTH]; unsigned char mPubKey[PUBLIC_KEY_LENGTH]; }; } // namespace #endif sdk-10.11.0/include/mega/db.h000066400000000000000000000271351516266226600155510ustar00rootroot00000000000000/** * @file mega/db.h * @brief Database access interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_DB_H #define MEGA_DB_H 1 #include "filesystem.h" #include "logging.h" #include "node.h" #include #include namespace mega { // generic host transactional database access interface class DBTableTransactionCommitter; // Class to load serialized node from data base class NodeSerialized { public: std::string mNode; std::string mNodeCounter; }; enum class DBError { DB_ERROR_UNKNOWN = 0, DB_ERROR = 1, DB_ERROR_INTERNAL = 2, DB_ERROR_PERM = 3, DB_ERROR_ABORT = 4, DB_ERROR_BUSY = 5, DB_ERROR_LOCKED = 6, DB_ERROR_NOMEM = 7, DB_ERROR_READONLY = 8, DB_ERROR_INTERRUPT = 9, DB_ERROR_IO = 10, DB_ERROR_CORRUPT = 11, DB_ERROR_NOTFOUND = 12, DB_ERROR_FULL = 13, DB_ERROR_CANTOPEN = 14, DB_ERROR_PROTOCOL = 15, DB_ERROR_EMPTY = 16, DB_ERROR_SCHEMA = 17, DB_ERROR_TOOBIG = 18, DB_ERROR_CONSTRAINT = 19, DB_ERROR_MISMATCH = 20, DB_ERROR_MISUSE = 21, DB_ERROR_NOLFS = 22, DB_ERROR_AUTH = 23, DB_ERROR_FORMAT = 24, DB_ERROR_RANGE = 25, DB_ERROR_NOTADB = 26, DB_ERROR_INDEX_OVERFLOW = 100, // SDK internal error }; using DBErrorCallback = std::function; class MEGA_API DbTable { PrnGen &rng; protected: bool mCheckAlwaysTransacted = false; DBTableTransactionCommitter* mTransactionCommitter = nullptr; DBErrorCallback mDBErrorCallBack; friend class DBTableTransactionCommitter; void checkTransaction(); // should be called by the subclass' destructor void resetCommitter(); public: static const int IDSPACING = 16; // for a full sequential get: rewind to first record virtual void rewind() = 0; // get next record in sequence virtual bool next(uint32_t*, string*) = 0; bool next(uint32_t*, string*, SymmCipher*); // get specific record by key virtual bool get(uint32_t, string*) = 0; // update or add specific record virtual bool put(uint32_t, char*, unsigned) = 0; bool put(uint32_t, string*); bool put(uint32_t, Cacheable *, SymmCipher*); // delete specific record virtual bool del(uint32_t) = 0; // delete all records virtual void truncate() = 0; // begin transaction virtual void begin() = 0; // commit transaction virtual void commit() = 0; // abort transaction virtual void abort() = 0; // permanantly remove all database info virtual void remove() = 0; void checkCommitter(DBTableTransactionCommitter*); // autoincrement uint32_t nextid; DbTable(PrnGen &rng, bool alwaysTransacted, DBErrorCallback dBErrorCallBack); virtual ~DbTable() = default; DBTableTransactionCommitter *getTransactionCommitter() const; }; class NodeSearchFilter; class NodeSearchPage; struct NodeSearchLexicographicalOffset; class MEGA_API DBTableNodes { public: // add or update a node virtual bool put(Node* node) = 0; // remove one node from 'nodes' table virtual bool remove(NodeHandle nodehandle) = 0; // remove all nodes from 'nodes' table (truncate) virtual bool removeNodes() = 0; // get nodes and queries about nodes virtual bool getNode(NodeHandle nodehandle, NodeSerialized& nodeSerialized) = 0; virtual bool getNodesByOrigFingerprint(const std::string& fingerprint, std::vector>& nodes) = 0; virtual uint64_t getNumberOfChildren(NodeHandle parentHandle) = 0; virtual bool getChildren(const NodeSearchFilter& filter, int order, std::vector>& nodes, CancelToken cancelFlag, const NodeSearchPage& page) = 0; virtual bool listChildNodesLexicographically( const handle parenthandle, std::vector>& children, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset) = 0; virtual bool searchNodes(const NodeSearchFilter& filter, int order, std::vector>& nodes, CancelToken cancelFlag, const NodeSearchPage& page) = 0; /** * @brief * Returns a set of all distinct tags below a set of specified parents. * * @param cancelToken * A cancel token that can be used to abort the query. * * @param handle * Handle specifing the node we should search for tags below. * * @param pattern * A search pattern that can used to filter which tags are returned. * * An empty pattern specifies that there are no constraints as to what * tags should be returned. * * A non-empty pattern specifies that only the tags matching that * pattern should be returned. Wildcards are accepted. * * @return * A set of tags if successful. * nullopt if unsuccessful. */ virtual auto getNodeTagsBelow(CancelToken cancelToken, NodeHandle handle, const std::string& pattern) -> std::optional> = 0; virtual bool getRecentNodes(const NodeSearchPage& page, m_time_t since, std::vector>& nodes) = 0; virtual bool getNodeByFingerprint(const std::string& fingerprint, mega::NodeSerialized& node, NodeHandle& handle) = 0; virtual bool getNodesByFingerprintNoMtime(const std::string& fingerprint, std::vector>& nodes) = 0; virtual bool getRootNodes(std::vector>& nodes) = 0; virtual bool getNodesWithSharesOrLink(std::vector>&, ShareType_t shareType) = 0; virtual bool getFavouritesHandles(NodeHandle node, uint32_t count, std::vector& nodes) = 0; virtual bool childNodeByNameType(NodeHandle parentHandle, const std::string& name, nodetype_t nodeType, std::pair& node) = 0; virtual bool isAncestor(NodeHandle node, NodeHandle ancestror, CancelToken cancelFlag) = 0; // count of items in 'nodes' table. Returns 0 if error virtual uint64_t getNumberOfNodes() = 0; // count of children nodes of by type virtual uint64_t getNumberOfChildrenByType(NodeHandle parentHandle, nodetype_t nodeType) = 0; // -- get node properties -- virtual bool getNodeSizeTypeAndFlags(NodeHandle node, m_off_t& size, nodetype_t& nodeType, uint64_t& oldFlags) = 0; virtual void updateCounter(NodeHandle nodeHandle, const std::string& nodeCounterBlob) = 0; virtual void updateCounterAndFlags(NodeHandle nodeHandle, uint64_t flags, const std::string& nodeCounterBlob) = 0; virtual void createIndexes(bool enableIndexesForSearching, bool enableIndexesForLexicographicalList) = 0; virtual void dropSearchDBIndexes() = 0; virtual void dropLexicographicDBIndexes() = 0; }; class MEGA_API DBTableTransactionCommitter { DbTable* mTable; bool mStarted = false; std::thread::id threadId; public: void beginOnce() { if (mTable && !mStarted) { mTable->begin(); mStarted = true; } } void commitNow() { if (mTable) { if (mStarted) { mTable->commit(); mStarted = false; } } } void reset() { mTable = nullptr; } virtual ~DBTableTransactionCommitter() { if (mTable) { commitNow(); mTable->mTransactionCommitter = nullptr; } } explicit DBTableTransactionCommitter(unique_ptr& t) : mTable(t.get()), threadId(std::this_thread::get_id()) { if (mTable) { if (mTable->mTransactionCommitter) { assert(mTable->mTransactionCommitter->threadId == threadId); mTable = nullptr; // we are nested; this one does nothing. This can occur during eg. putnodes response when the core sdk and the intermediate layer both do db work. } else { mTable->mTransactionCommitter = this; } } } MEGA_DISABLE_COPY_MOVE(DBTableTransactionCommitter) }; class MEGA_API TransferDbCommitter : public DBTableTransactionCommitter { public: uint32_t addFileCount = 0; uint32_t addTransferCount = 0; uint32_t removeFileCount = 0; uint32_t removeTransferCount = 0; explicit TransferDbCommitter(unique_ptr& t) : DBTableTransactionCommitter(t) {} ~TransferDbCommitter() { if (addFileCount || addTransferCount || removeFileCount || removeTransferCount) { LOG_debug << "Committed transfer db with new transfers : " << addTransferCount << " and new transfer files: " << addFileCount << " removed transfers: " << removeTransferCount << " and removed transfer files: " << removeFileCount; } } }; enum DbOpenFlag { // Recycle legacy database, if present. DB_OPEN_FLAG_RECYCLE = 0x1, // Operations should always be transacted. DB_OPEN_FLAG_TRANSACTED = 0x2 }; // DbOpenFlag struct MEGA_API DbAccess { static const int LEGACY_DB_VERSION; static const int DB_VERSION; static const int LAST_DB_VERSION_WITHOUT_NOD; static const int LAST_DB_VERSION_WITHOUT_SRW; static const int LAST_DB_VERSION_WITHOUT_VFINGERPRINT; DbAccess(); virtual ~DbAccess() { } virtual bool checkDbFileAndAdjustLegacy(FileSystemAccess& fsAccess, const string& name, const int flags, LocalPath& dbPath) = 0; // Compute the path of a database with this name. virtual LocalPath databasePath(const FileSystemAccess& fsAccess, const string& name, const int version) const = 0; virtual DbTable* open(PrnGen &rng, FileSystemAccess& fsAccess, const string& name, const int flags, DBErrorCallback dBErrorCallBack) = 0; // use this method to get a `DbTable` that also implements `DbTableNodes` interface virtual DbTable* openTableWithNodes(PrnGen &rng, FileSystemAccess& fsAccess, const string& name, const int flags, DBErrorCallback dBErrorCallBack) = 0; // Check if the specified database exists on disk. virtual bool probe(FileSystemAccess& fsAccess, const string& name) const = 0; virtual std::optional getExistingDbPath(const FileSystemAccess& fsAccess, const std::string& fname) const = 0; // Where are we storing our databases? virtual const LocalPath& rootPath() const = 0; virtual bool renameDBFiles(FileSystemAccess& fsAccess, const LocalPath& legacyPath, const LocalPath& dbPath) = 0; int currentDbVersion; }; // Convenience. using DbAccessPtr = unique_ptr; using DbTablePtr = unique_ptr; } // namespace #endif sdk-10.11.0/include/mega/db/000077500000000000000000000000001516266226600153705ustar00rootroot00000000000000sdk-10.11.0/include/mega/db/sqlite.h000066400000000000000000000353031516266226600170460ustar00rootroot00000000000000/** * @file sqlite.h * @brief SQLite DB access layer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/types.h" #ifdef USE_SQLITE #ifndef DBACCESS_CLASS #define DBACCESS_CLASS SqliteDbAccess #include "mega/db.h" #include #include #include namespace mega { class MEGA_API SqliteDbTable : public DbTable { protected: sqlite3* db = nullptr; LocalPath dbfile; FileSystemAccess *fsaccess; sqlite3_stmt* pStmt = nullptr; sqlite3_stmt* mDelStmt = nullptr; sqlite3_stmt* mPutStmt = nullptr; // handler for DB errors ('interrupt' is true if caller can be interrupted by CancelToken) void errorHandler(int sqliteError, const std::string& operation, bool interrupt); // whether an unmatched begin() has been issued bool inTransaction() const; public: void rewind() override; bool next(uint32_t*, string*) override; bool get(uint32_t, string*) override; bool put(uint32_t, char*, unsigned) override; bool del(uint32_t) override; void truncate() override; void begin() override; void commit() override; void abort() override; void remove() override; SqliteDbTable(PrnGen &rng, sqlite3*, FileSystemAccess &fsAccess, const LocalPath &path, const bool checkAlwaysTransacted, DBErrorCallback dBErrorCallBack); ~SqliteDbTable() override; }; /** * This class implements DbTable iface (by deriving SqliteDbTable), and additionally * implements DbTableNodes iface too, so it allows to manage `nodes` table. */ class MEGA_API SqliteAccountState : public SqliteDbTable, public DBTableNodes { public: // Access to table `nodes` bool getNode(mega::NodeHandle nodehandle, NodeSerialized& nodeSerialized) override; bool getNodesByOrigFingerprint(const std::string& fingerprint, std::vector> &nodes) override; bool getRootNodes(std::vector>& nodes) override; bool getNodesWithSharesOrLink(std::vector>& nodes, ShareType_t shareType) override; uint64_t getNumberOfChildren(NodeHandle parentHandle) override; // If a cancelFlag is passed, it must be kept alive until this method returns. bool getChildren(const mega::NodeSearchFilter& filter, int order, std::vector>& children, CancelToken cancelFlag, const NodeSearchPage& page) override; bool listChildNodesLexicographically( const handle parenthandle, vector>& children, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset) override; bool searchNodes(const mega::NodeSearchFilter& filter, int order, std::vector>& nodes, CancelToken cancelFlag, const NodeSearchPage& page) override; /* * @brief * Get all node tags below a specified node. * * @param cancelToken * A token that can be used to terminate the query's execution prematurely. * * @param handle * A handle specifying which node we want to list tags below. * * If undefined, the query will list tags below all root nodes. * * @param pattern * An optional pattern that can be used to filter which tags we list. * * @returns * std::nullopt on failure. * std::set on success. */ auto getNodeTagsBelow(CancelToken cancelToken, NodeHandle handle, const std::string& pattern = "") -> std::optional> override; bool getNodesByFingerprintNoMtime( const std::string& fingerprint, std::vector>& nodes) override; bool getNodeByFingerprint(const std::string& fingerprint, mega::NodeSerialized& node, NodeHandle& handle) override; // getRecentNodes() is for the deprecated method MegaClient::getRecentActions without // excludeSensitives flag bool getRecentNodes(const NodeSearchPage& page, m_time_t since, std::vector>& nodes) override; bool getFavouritesHandles(NodeHandle node, uint32_t count, std::vector& nodes) override; bool childNodeByNameType(NodeHandle parentHanlde, const std::string& name, nodetype_t nodeType, std::pair& node) override; bool getNodeSizeTypeAndFlags(NodeHandle node, m_off_t& size, nodetype_t& nodeType, uint64_t &oldFlags) override; bool isAncestor(mega::NodeHandle node, mega::NodeHandle ancestor, CancelToken cancelFlag) override; uint64_t getNumberOfNodes() override; uint64_t getNumberOfChildrenByType(NodeHandle parentHandle, nodetype_t nodeType) override; bool put(Node* node) override; using SqliteDbTable::put; // for the other virtual overload bool remove(mega::NodeHandle nodehandle) override; bool removeNodes() override; void updateCounter(NodeHandle nodeHandle, const std::string& nodeCounterBlob) override; void updateCounterAndFlags(NodeHandle nodeHandle, uint64_t flags, const std::string& nodeCounterBlob) override; void createIndexes(bool enableIndexesForSearching, bool enableIndexesForLexicographicalList) override; void dropSearchDBIndexes() override; void dropLexicographicDBIndexes() override; void remove() override; SqliteAccountState(PrnGen &rng, sqlite3*, FileSystemAccess &fsAccess, const mega::LocalPath &path, const bool checkAlwaysTransacted, DBErrorCallback dBErrorCallBack); void finalise(); virtual ~SqliteAccountState(); // Callback registered by some long-time running queries, so they can be canceled // If the progress callback returns non-zero, the operation is interrupted static int progressHandler(void *); static void userRegexp(sqlite3_context* context, int argc, sqlite3_value** argv); // Method called when query uses 'getmimetype' // Gets the mimetype corresponding to the file extension static void userGetMimetype(sqlite3_context* context, int argc, sqlite3_value** argv); // Method called when query uses 'getFingerprintExcludingMtime' // Gets the node's fingerprint excluding mtime static void getFingerprintExcludingMtime(sqlite3_context* context, int argc, sqlite3_value** argv); // Method called when query uses 'getSizeFromNodeCounter' // Gets the node size from node counter (blob) static void getSizeFromNodeCounter(sqlite3_context* context, int argc, sqlite3_value** argv); /** * @brief This method is designed to apply all the filtering options in various methods that * perform a query to the database and use a NodeSearchFilter object. * * It is implemented trying to short-circuit unnecessary argument parsing. */ static void userMatchFilter(sqlite3_context* context, int argc, sqlite3_value** argv); private: // Iterate over a SQL query row by row and fill the map // Allow at least the following containers: bool processSqlQueryNodes(sqlite3_stmt *stmt, std::vector>& nodes); // if add a new sqlite3_stmt update finalise() sqlite3_stmt* mStmtPutNode = nullptr; sqlite3_stmt* mStmtUpdateNode = nullptr; sqlite3_stmt* mStmtUpdateNodeAndFlags = nullptr; sqlite3_stmt* mStmtTypeAndSizeNode = nullptr; sqlite3_stmt* mStmtGetNode = nullptr; /** @deprecated */ sqlite3_stmt* mStmtChildrenFromType = nullptr; sqlite3_stmt* mStmtNumChildren = nullptr; std::map mStmtGetChildren; sqlite3_stmt* mStmtGetChildrenLexi = nullptr; sqlite3_stmt* mStmtGetChildrenLexiNoOffset = nullptr; std::map mStmtSearchNodes; sqlite3_stmt* mStmtNodeTagsBelow = nullptr; sqlite3_stmt* mStmtNodesByFpNoMtime = nullptr; sqlite3_stmt* mStmtNodeByFp = nullptr; sqlite3_stmt* mStmtNodeByOrigFp = nullptr; sqlite3_stmt* mStmtNodesWithInshares = nullptr; sqlite3_stmt* mStmtNodesWithOutshares = nullptr; sqlite3_stmt* mStmtNodesWithPendingOutshares = nullptr; sqlite3_stmt* mStmtNodesWithPubLink = nullptr; sqlite3_stmt* mStmtChildNode = nullptr; sqlite3_stmt* mStmtIsAncestor = nullptr; sqlite3_stmt* mStmtNumChild = nullptr; sqlite3_stmt* mStmtRecents = nullptr; // For getRecentNodes() sqlite3_stmt* mStmtFavourites = nullptr; // how many SQLite instructions will be executed between callbacks to the progress handler // (tests with a value of 1000 results on a callback every 1.2ms on a desktop PC) static const int NUM_VIRTUAL_MACHINE_INSTRUCTIONS = 1000; // Helper method to drop index with the provided names void dropDBIndexes(const std::vector& indicesToDelete); }; class MEGA_API SqliteDbAccess : public DbAccess { LocalPath mRootPath; public: explicit SqliteDbAccess(const LocalPath& rootPath); ~SqliteDbAccess(); LocalPath databasePath(const FileSystemAccess& fsAccess, const string& name, const int version) const override; // Note: for proper adjustment of legacy versions, 'sctable' should be the first DB to be opened // In this way, when it's called with other DB (statusTable, tctable, ...), DbAccess::currentDbVersion has been // updated to new value bool checkDbFileAndAdjustLegacy(FileSystemAccess& fsAccess, const string& name, const int flags, LocalPath& dbPath) override; SqliteDbTable* open(PrnGen &rng, FileSystemAccess& fsAccess, const string& name, const int flags, DBErrorCallback dBErrorCallBack) override; DbTable* openTableWithNodes(PrnGen &rng, FileSystemAccess& fsAccess, const string& name, const int flags, DBErrorCallback dBErrorCallBack) override; bool probe(FileSystemAccess& fsAccess, const string& name) const override; std::optional getExistingDbPath(const FileSystemAccess& fsAccess, const std::string& fname) const override; const LocalPath& rootPath() const override; bool renameDBFiles(FileSystemAccess& fsAccess, const LocalPath& legacyPath, const LocalPath& dbPath) override; private: bool openDBAndCreateStatecache(sqlite3** db, FileSystemAccess& fsAccess, const string& name, mega::LocalPath& dbPath, const int flags); void removeDBFiles(mega::FileSystemAccess& fsAccess, mega::LocalPath& dbPath); // We should add new type for every new column that was added to DB // This new type have to inherit from `MigrateType` class MigrateType { public: virtual ~MigrateType() = default; virtual bool bindToDb(sqlite3_stmt* stmt, const std::map& lookupId) const = 0; virtual bool hasValidValue() const = 0; }; class MTimeType: public MigrateType { public: MTimeType(m_time_t value); bool bindToDb(sqlite3_stmt* stmt, const std::map& lookupId) const override; static std::unique_ptr fromNodeData(NodeData& nd); bool hasValidValue() const override; static constexpr auto COMPONENT = NodeData::COMPONENT_MTIME; private: m_time_t mValue; }; class LabelType: public MigrateType { public: LabelType(int value); bool bindToDb(sqlite3_stmt* stmt, const std::map& lookupId) const override; static std::unique_ptr fromNodeData(NodeData& nd); bool hasValidValue() const override; static constexpr auto COMPONENT = NodeData::COMPONENT_LABEL; private: int mValue; }; class DescriptionType: public MigrateType { public: DescriptionType(const std::string& value); bool bindToDb(sqlite3_stmt* stmt, const std::map& lookupId) const override; static std::unique_ptr fromNodeData(NodeData& nd); bool hasValidValue() const override; static constexpr auto COMPONENT = NodeData::COMPONENT_DESCRIPTION; private: std::string mValue; }; class TagsType: public MigrateType { public: TagsType(const std::string& value); bool bindToDb(sqlite3_stmt* stmt, const std::map& lookupId) const override; static std::unique_ptr fromNodeData(NodeData& nd); bool hasValidValue() const override; static constexpr auto COMPONENT = NodeData::COMPONENT_TAGS; private: std::string mValue; }; // functionality for adding columns to existing table, and copying data to them struct NewColumn { string name; string type; int migrationId; // Method to extract info from NodeData and add it to vector std::function>&)> migrateOperation; template static bool extractDataFromNodeData(NodeData& nd, std::vector>& newValues) { std::unique_ptr val = T::fromNodeData(nd); bool hasValidValue = val->hasValidValue(); newValues.push_back(std::move(val)); return hasValidValue; } }; bool addAndPopulateColumns(sqlite3* db, vector&& newCols); bool stripExistingColumns(sqlite3* db, vector& cols); bool addColumn(sqlite3* db, const string& name, const string& type); bool migrateDataToColumns(sqlite3* db, vector&& cols); }; class OrderByClause { public: static std::string get(int order); static size_t getId(int order); enum { DEFAULT_ASC = 1, DEFAULT_DESC, SIZE_ASC, SIZE_DESC, CTIME_ASC, CTIME_DESC, MTIME_ASC, MTIME_DESC, LABEL_ASC = 17, LABEL_DESC, FAV_ASC, FAV_DESC }; }; } // namespace #endif #endif sdk-10.11.0/include/mega/dns_lookup_pseudomessage.h000066400000000000000000000017331516266226600222610ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_DNS_LOOKUP_PSEUDOMESSAGE_H #define MEGA_DNS_LOOKUP_PSEUDOMESSAGE_H #include #include namespace mega { namespace dns_lookup_pseudomessage { std::string getForIPv4(uint64_t userId, uint16_t messageId); std::string getForIPv6(uint64_t userId, uint16_t messageId); } std::string userIdToHex(uint64_t userId); } // namespace mega #endif // MEGA_DNS_LOOKUP_PSEUDOMESSAGE_H sdk-10.11.0/include/mega/drivenotify.h000066400000000000000000000062501516266226600175210ustar00rootroot00000000000000/** * @file mega/drivenotify.h * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #ifdef USE_DRIVE_NOTIFICATIONS #include #include #include #include #include #include namespace mega { // Structure containing relevant Drive info. // Windows: information is provided by Windows Management Instrumentation (WMI), Microsoft's implementation of WBEM. struct DriveInfo // Local Removable/USB Network { using StringType = #ifdef _WIN32 std::wstring; #else std::string; #endif StringType mountPoint; // C: E: F: std::wstring location; // ""/null ""/null \\host\f std::wstring volumeSerialNumber; // EE82D138 0EEE1DE2 A01A541C // probably less useful std::wstring size; // 1005343207424 31020957696 843572047872 std::wstring description; // Local Fixed Disk Removable Disk Network Connection uint32_t driveType = 0; // 3 (Fixed) 2 (Removable) 4 (Network) uint32_t mediaType = 0; // 12 (Fixed HD) 0/null (Unknown) 0/null (Unknown) bool connected = false; }; // Interface for receiving drive [dis]connection events, and notifying futher. // // Platform specific implementations: // - DriveNotifyWin // - DriveNotifyPosix // - DriveNotifyOsx class DriveNotify { public: bool start(std::function notify); void stop(); bool enabled() { return mEventSinkThread.joinable(); } std::pair get(); virtual ~DriveNotify(); protected: bool shouldStop() { return mStop.load(); } bool startNotifier(); virtual bool notifierSetup() { return true; } void stopNotifier(); void add(DriveInfo&& info); virtual void doInThread() = 0; private: std::queue mInfoQueue; std::mutex mSyncAccessMutex; std::atomic_bool mStop{false}; std::thread mEventSinkThread; std::function mNotifyOnInfo; }; } // namespace mega #ifdef _WIN32 #include "mega/win32/drivenotifywin.h" namespace mega { using DriveInfoCollector = DriveNotifyWin; } #elif __APPLE__ #include "mega/osx/drivenotifyosx.h" namespace mega { using DriveInfoCollector = DriveNotifyOsx; } #else #include "mega/posix/drivenotifyposix.h" namespace mega { using DriveInfoCollector = DriveNotifyPosix; } #endif #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/include/mega/file.h000077500000000000000000000313111516266226600160750ustar00rootroot00000000000000/** * @file mega/file.h * @brief Classes for transferring files * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_FILE_H #define MEGA_FILE_H 1 #include "filesystem.h" #include #ifdef ENABLE_SYNC #include "syncinternals/mac_computation_state.h" #endif namespace mega { enum class CollisionResolution : uint8_t { Begin = 1, Overwrite = 1, RenameNewWithN = 2, RenameExistingToOldN = 3, End = 4, }; constexpr unsigned FILE_MAX_RETRIES = 16; constexpr unsigned FILE_IO_MAX_RETRIES = 6; constexpr unsigned FILE_SYNC_MAX_RETRIES = 8; class TransferDbCommitter; // Forward declaration // File is the base class for an upload or download, as managed by the SDK core. // Each Transfer consists of a list of File that all have the same content and fingerprint struct MEGA_API File: public FileFingerprint { // set localfilename in attached transfer virtual void prepare(FileSystemAccess&); // file transfer dispatched, expect updates/completion/failure virtual void start(); // progress update virtual void progress(); // transfer completion virtual void completed(Transfer*, putsource_t source); // transfer terminated before completion (cancelled, failed too many times) virtual void terminated(error e); // return true if the transfer should keep trying (limited to 16) // return false to delete the transfer virtual bool failed(error, MegaClient*); // update localname virtual void updatelocalname() {} void sendPutnodesOfUpload( MegaClient* client, UploadHandle fileAttrMatchHandle, std::string&& fileAttr, const UploadToken& ultoken, const FileNodeKey& newFileKey, putsource_t source, NodeHandle ovHandle, std::function&, bool targetOverride, int tag, const std::map& fileHandles)>&& completion, const m_time_t* overrideMtime, bool canChangeVault, std::optional pitag = std::nullopt); void sendPutnodesToCloneNode( MegaClient* client, Node* nodeToClone, putsource_t source, NodeHandle ovHandle, std::function&, bool targetOverride, int tag, const std::map& fileHandles)>&& completion, bool canChangeVault, Pitag pitag); void setCollisionResolution(CollisionResolution collisionResolution) { mCollisionResolution = collisionResolution; } CollisionResolution getCollisionResolution() const { return mCollisionResolution; } // generic filename for this transfer void displayname(string*); string displayname(); // normalized name (UTF-8 with unescaped special chars) string name; // local filename (must be set upon injection for uploads, can be set in start() for downloads) // now able to be updated from the syncs thread, should the nodes move during upload/download static mutex localname_mutex; LocalPath localname_multithreaded; LocalPath getLocalname() const; void setLocalname(const LocalPath&); // source/target node handle NodeHandle h; // previous node, if any std::shared_ptr previousNode; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { // source handle private? bool hprivate : 1; // source handle foreign? bool hforeign : 1; // is this part of a sync transfer? bool syncxfer : 1; // is the source file temporary? bool temporaryfile : 1; // remember if the sync is from an inshare bool fromInsycShare : 1; }; #ifdef _MSC_VER #pragma warning(pop) #endif VersioningOption mVersioningOption = NoVersioning; // private auth to access the node string privauth; // public auth to access the node string pubauth; // chat auth to access the node char *chatauth; // if !hprivate, filekey and size must be valid byte filekey[FILENODEKEYLENGTH]{}; // for remote file drops: uid or e-mail address of recipient string targetuser; // transfer linkage Transfer* transfer; file_list::iterator file_it{}; File(); virtual ~File(); // serialize the File object bool serialize(string*) const override; static File* unserialize(string*); // tag of the file transfer int tag; // set the token true to cause cancellation of this transfer (this file of the transfer) CancelToken cancelToken; // True if this is a FUSE transfer. virtual bool isFuseTransfer() const; // relevant only for downloads (GET); do not override anywhere else virtual bool undelete() const { return false; } // Set this file's logical path. void logicalPath(LocalPath logicalPath); // Retrieve this file's logical path. LocalPath logicalPath() const; std::optional getPitag() const { return mPitag; }; void setPitag(Pitag pitag) { mPitag = pitag; }; private: CollisionResolution mCollisionResolution; // The file's logical path. LocalPath mLogicalPath; std::optional mPitag; }; class SyncThreadsafeState; struct CloudNode; struct SyncTransfer_inClient: public File { // self-destruct after completion void completed(Transfer*, putsource_t) override; void terminated(error) override; // We will be passing a raw pointer to this object // into the tranfer system on the client thread. // this member prevents that becoming a dangling pointer // should the sync no longer require it. So we set this // member just before startxfer, and reset it on completed()/terminated() shared_ptr selfKeepAlive; shared_ptr syncThreadSafeState; // Why was the transfer failed/terminated? error mError = API_OK; std::atomic wasTerminated{false}; std::atomic wasFileTransferCompleted{false}; std::atomic wasRequesterAbandoned{false}; enum class AttributeOnlyUpdate : std::uint8_t { None = 0, MtimeOnly, CrcOnly, }; std::atomic attributeOnlyUpdate{AttributeOnlyUpdate::None}; // Whether the terminated SyncTransfer_inClient was already notified to the apps/in the logs std::atomic terminatedReasonAlreadyKnown{false}; std::optional mMetaMac{std::nullopt}; }; struct SyncDownload_inClient: public SyncTransfer_inClient { shared_ptr downloadDistributor; // set sync-specific temp filename, update treestate void prepare(FileSystemAccess&) override; bool failed(error, MegaClient*) override; SyncDownload_inClient(CloudNode& n, const LocalPath&, bool fromInshare, shared_ptr stss, const FileFingerprint& overwriteFF, const int64_t metamac, const AttributeOnlyUpdate attributeOnlyUpdate); ~SyncDownload_inClient(); // True if we could copy (or move) the download into place. bool wasDistributed = false; // Set in clientDownload when completing an mtime-only download. // True if setmtimelocal succeeded on the final target path. bool mtimeAppliedOnDisk{false}; FileFingerprint okToOverwriteFF; }; struct SyncUpload_inClient : SyncTransfer_inClient, std::enable_shared_from_this { // This class is part of the client's Transfer system (ie, works in the client's thread) // The sync system keeps a shared_ptr to it. Whichever system finishes with it last actually deletes it SyncUpload_inClient(NodeHandle targetFolder, const LocalPath& fullPath, const string& nodeName, const FileFingerprint& ff, shared_ptr stss, handle fsid, const LocalPath& localname, bool fromInshare, const int64_t metamac, const AttributeOnlyUpdate attributeOnlyUpdate); ~SyncUpload_inClient(); void prepare(FileSystemAccess&) override; void completed(Transfer*, putsource_t) override; void updateFingerprintMtime(const m_time_t newMtime); void updateFingerprint(const FileFingerprint& newFingerprint); /* UpSync operation can be one of the following: * - putnodes of upload * - put nodes of clone * - update mtime of node in cloud */ std::atomic upsyncStarted{false}; std::atomic upsyncFailed{false}; std::atomic wasStarted{false}; std::atomic wasUpsyncCompleted{false}; // Valid when wasUpsyncCompleted is true. NodeHandle upsyncResultHandle; handle sourceFsid = UNDEF; LocalPath sourceLocalname; // once the upload completes these are set. todo: should we dynamically allocate space for these, save RAM for mass transfer cases? UploadHandle uploadHandle; UploadToken uploadToken; FileNodeKey fileNodeKey; std::string fileAttr; void fullUpload(MegaClient& client, mega::TransferDbCommitter& committer, const VersioningOption vo, const bool queueFirst); void cloneNode(MegaClient& client, std::shared_ptr cloneNodeCandidate, const NodeHandle ovHandleIfShortcut); bool updateNodeMtime(MegaClient* client, std::shared_ptr node, const m_time_t newMtime, std::function&& completion); Pitag buildSyncUploadPitag() const; Pitag buildSyncClonePitag() const; void sendPutnodesOfUpload(MegaClient* client, NodeHandle ovHandle); void sendPutnodesToCloneNode(MegaClient* client, NodeHandle ovHandle, Node* nodeToClone, Pitag pitag); #ifdef ENABLE_SYNC /** * @brief State for async MAC computation when looking for clone candidates. * * Used to pre-compute MAC before calling findCloneNodeCandidate, avoiding * blocking the client thread for large files. */ std::shared_ptr macComputation; #endif }; /** * @struct DelayedSyncUpload * @brief Represents an upload task that is delayed for throttling purposes. * * This struct encapsulates the details of an upload task that is queued for later * processing due to throttling conditions. */ struct DelayedSyncUpload { /** * @brief Weak pointer to the upload client responsible for this task. * * This prevents holding a strong reference to the upload, allowing it to be safely * cleaned up if no longer valid before the task is processed. */ std::weak_ptr mWeakUpload; /** * @brief Versioning option for the upload task. */ VersioningOption mVersioningOption; /** * @brief Flag indicating if this upload should be queued first in the client. */ bool mQueueFirst; /** * @brief Node handle representing a shortcut for the upload. */ NodeHandle mOvHandleIfShortcut; /** * @brief Constructs a DelayedUpload instance. * * @param upload Shared pointer to the upload owned by the LocalNode. * For the other params, see LocalNode::queueClientUpload() */ DelayedSyncUpload(std::shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut): mWeakUpload(std::move(upload)), mVersioningOption(vo), mQueueFirst(queueFirst), mOvHandleIfShortcut(ovHandleIfShortcut) {} }; } // namespace #endif sdk-10.11.0/include/mega/file_service/000077500000000000000000000000001516266226600174425ustar00rootroot00000000000000sdk-10.11.0/include/mega/file_service/file.h000066400000000000000000000063201516266226600205330ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace mega { class LocalPath; class NodeHandle; namespace file_service { class File { // Logs instance lifetime. common::InstanceLogger mInstanceLogger; // What context is this instance wrapping? FileContextPtr mContext; public: File(FileServiceContextBadge badge, FileContextPtr context); File(const File& other); File(File&& other); ~File(); File& operator=(const File& rhs); File& operator=(File&& rhs); // Notify an observer when this file's information changes. FileEventObserverID addObserver(FileEventObserver observer); // Append data to the end of this file. void append(const void* buffer, FileAppendCallback callback, std::uint64_t length); // Fetch all of this file's data from the cloud. void fetch(FileFetchCallback callback); // Wait until all fetches in progress have completed. void fetchBarrier(FileFetchBarrierCallback callback); // Flush this file's local modifications to the cloud. void flush(FileFlushCallback callback); // Retrieve information about this file. FileInfo info() const; // Purge this file from the service. void purge(FilePurgeCallback callback); // What ranges of this file are currently in storage? FileRangeVector ranges() const; // Read data from this file. // // Be aware that this function does not guarantee that all data // requested will be provided in a single chunk. // // If you request 1MiB of data, you may get the entire 1MiB or you might // get much less. The information passed to the callback will specify // how much data you've received and it's your responsibility to request // the rest, if necessary. void read(FileReadCallback callback, std::uint64_t offset, std::uint64_t length); void read(FileReadCallback callback, const FileRange& range); // Reclaim this file's storage. void reclaim(FileReclaimCallback callback); // Remove the file. // // Like purge above but the cloud file is removed, too. void remove(FileRemoveCallback callback, bool replaced); // Remove a previously added observer. void removeObserver(FileEventObserverID id); // Update the file's modification time. void touch(FileTouchCallback callback, std::int64_t modified); // Truncate this file to a specified size. void truncate(FileTruncateCallback callback, std::uint64_t newSize); // Write data to this file. void write(const void* buffer, FileWriteCallback callback, std::uint64_t offset, std::uint64_t length); void write(const void* buffer, FileWriteCallback callback, const FileRange& range); }; // File } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_callbacks.h000066400000000000000000000017321516266226600225340ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace file_service { using FileAppendCallback = std::function; using FileFetchBarrierCallback = std::function; using FileFetchCallback = std::function; using FileFlushCallback = std::function; using FilePurgeCallback = std::function; using FileReadCallback = std::function)>; using FileReclaimCallback = std::function)>; using FileRemoveCallback = std::function; using FileTouchCallback = std::function; using FileTruncateCallback = std::function; using FileWriteCallback = std::function)>; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_context_badge_forward.h000066400000000000000000000003471516266226600251500ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { using FileContextBadge = common::Badge; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_context_forward.h000066400000000000000000000001511516266226600240170ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileContext; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_context_pointer.h000066400000000000000000000004121516266226600240330ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { using FileContextPtr = std::shared_ptr; using FileContextWeakPtr = std::weak_ptr; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event.h000066400000000000000000000005431516266226600217350ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include sdk-10.11.0/include/mega/file_service/file_event_emitter_forward.h000066400000000000000000000001561516266226600252120ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileEventEmitter; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event_forward.h000066400000000000000000000013341516266226600234600ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace mega { namespace file_service { using FileEvent = std::variant; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event_observer.h000066400000000000000000000004661516266226600236500ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { using FileEventObserver = std::function; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event_observer_id.h000066400000000000000000000004011516266226600243110ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { using FileEventObserverID = std::pair; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event_observer_result.h000066400000000000000000000004331516266226600252400ustar00rootroot00000000000000#pragma once #include namespace mega { namespace file_service { enum FileEventObserverResult : unsigned int { FILE_EVENT_OBSERVER_KEEP, FILE_EVENT_OBSERVER_REMOVE, }; // FileEventObserverResult } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_event_observer_result_forward.h000066400000000000000000000002031516266226600267570ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { enum FileEventObserverResult : unsigned int; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_flush_event.h000066400000000000000000000011431516266226600231330ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { struct FileFlushEvent { bool operator==(const FileFlushEvent& rhs) const { return mHandle == rhs.mHandle && mID == rhs.mID; } bool operator!=(const FileFlushEvent& rhs) const { return !(*this == rhs); } // The handle of this file's new cloud node. NodeHandle mHandle; // The ID of the file that has been flushed. FileID mID; }; // FileFlushEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_flush_event_forward.h000066400000000000000000000001551516266226600246610ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileFlushEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_forward.h000066400000000000000000000001421516266226600222530ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class File; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_id.h000066400000000000000000000022551516266226600212120ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { class NodeHandle; namespace common { template<> struct SerializationTraits { static file_service::FileID from(const Field& field); static void to(Parameter& parameter, file_service::FileID id); }; // SerializationTraits } // common namespace file_service { class FileID { explicit FileID(std::uint64_t id); std::uint64_t mID; public: FileID(); operator bool() const; bool operator==(const FileID& rhs) const; bool operator<(const FileID& rhs) const; bool operator!=(const FileID& rhs) const; bool operator!() const; static FileID from(NodeHandle handle); static FileID from(const std::string& string); static FileID from(const char* string); static FileID from(std::uint64_t u64); NodeHandle toHandle() const; std::string toString() const; std::uint64_t toU64() const; }; // FileID bool synthetic(FileID id); bool synthetic(std::uint64_t u64); std::string toString(FileID id); } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_id_forward.h000066400000000000000000000001441516266226600227310ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileID; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_info.h000066400000000000000000000046041516266226600215510ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace mega { class NodeHandle; namespace file_service { class FileInfo { // Logs instance lifetime. common::InstanceLogger mInstanceLogger; // What context does this instance wrap? FileInfoContextPtr mContext; public: FileInfo(FileContextBadge badge, FileInfoContextPtr context); FileInfo(FileServiceContextBadge badge, FileInfoContextPtr context); FileInfo(const FileInfo& other); ~FileInfo(); // Does rhs describe the same file as we do? bool operator==(const FileInfo& rhs) const { return mContext == rhs.mContext; } // Does rhs describe a different file than we do? bool operator!=(const FileInfo& rhs) const { return !operator==(rhs); } // Assign rhs to this instance. FileInfo& operator=(const FileInfo& rhs); // When was this file last accessed? std::int64_t accessed() const; // Notify an observer when this file's information changes. FileEventObserverID addObserver(FileEventObserver observer); // How much disk space has been allocated to this file? std::uint64_t allocatedSize() const; // Has this file been locally modified? bool dirty() const; // What node is this file associated with? NodeHandle handle() const; // What node is this file associated with? FileID id() const; // Where is this file located in the cloud? std::optional location() const; // When was this file last modified? std::int64_t modified() const; // Remove a previously added observer. void removeObserver(FileEventObserverID id); // Has this file been removed? bool removed() const; // How large does the filesystem say this file is? std::uint64_t reportedSize() const; // How large is this file conceptually? std::uint64_t size() const; }; // FileInfo } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_info_context_forward.h000066400000000000000000000001551516266226600250360ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileInfoContext; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_info_context_pointer.h000066400000000000000000000004211516266226600250460ustar00rootroot00000000000000#include #include namespace mega { namespace file_service { using FileInfoContextPtr = std::shared_ptr; using FileInfoContextWeakPtr = std::weak_ptr; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_info_forward.h000066400000000000000000000001461516266226600232720ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileInfo; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_location.h000066400000000000000000000013521516266226600224230ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace file_service { struct FileLocation { bool operator<(const FileLocation& rhs) const { return std::tie(mParentHandle, mName) < std::tie(rhs.mParentHandle, rhs.mName); } bool operator==(const FileLocation& rhs) const { return mParentHandle == rhs.mParentHandle && mName == rhs.mName; } bool operator!=(const FileLocation& rhs) const { return !(*this == rhs); } // What is this file's name? std::string mName; // Who is this file's parent? NodeHandle mParentHandle; }; // FileLocation } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_location_forward.h000066400000000000000000000001531516266226600241450ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileLocation; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_move_event.h000066400000000000000000000012521516266226600227610ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { struct FileMoveEvent { bool operator==(const FileMoveEvent& rhs) const { return mID == rhs.mID && mFrom == rhs.mFrom && mTo == rhs.mTo; } bool operator!=(const FileMoveEvent& rhs) const { return !(*this == rhs); } // Where the file was moved from. FileLocation mFrom; // Where the file was moved to. FileLocation mTo; // The ID of the file that was moved. FileID mID; }; // FileMoveEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_move_event_forward.h000066400000000000000000000001541516266226600245050ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileMoveEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_range.h000066400000000000000000000014111516266226600217030ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { struct FileRange { FileRange() = default; FileRange(std::uint64_t begin, std::uint64_t end); bool operator==(const FileRange& rhs) const { return mBegin == rhs.mBegin && mEnd == rhs.mEnd; } bool operator!=(const FileRange& rhs) const { return !(*this == rhs); } std::uint64_t mBegin; std::uint64_t mEnd; }; // FileRange std::ostream& operator<<(std::ostream& ostream, const FileRange& range); FileRange combine(const FileRange& lhs, const FileRange& rhs); FileRange extend(const FileRange& range, std::uint64_t adjustment); std::string toString(const FileRange& range); } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_range_forward.h000066400000000000000000000001501516266226600234260ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileRange; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_range_vector.h000066400000000000000000000002711516266226600232700ustar00rootroot00000000000000#pragma once #include namespace mega { namespace file_service { using FileRangeVector = std::vector; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_read_result.h000066400000000000000000000006261516266226600231270ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { struct FileReadResult { // Contains the data that we read. Source& mSource; // The offset in the file that we read from. std::uint64_t mOffset; // How much data we were able to read. std::uint64_t mLength; }; // FileReadResult } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_remove_event.h000066400000000000000000000011321516266226600233050ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { struct FileRemoveEvent { bool operator==(const FileRemoveEvent& rhs) const { return mID == rhs.mID && mReplaced == rhs.mReplaced; } bool operator!=(const FileRemoveEvent& rhs) const { return !(*this == rhs); } // The ID of the file that's been removed. FileID mID; // True if the file was removed because it was replaced. bool mReplaced; }; // FileRemoveEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_remove_event_forward.h000066400000000000000000000001561516266226600250360ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileRemoveEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_result.h000066400000000000000000000016461516266226600221370ustar00rootroot00000000000000#pragma once #include namespace mega { class Error; namespace file_service { #define DEFINE_FILE_RESULTS(expander) \ expander(INVALID_ARGUMENTS, "The file operation was passed invalid arguments") \ expander(CANCELLED, "The file operation has been cancelled") \ expander(FAILED, "The file operation has failed") \ expander(READONLY, "The operation has failed because the file is read only") \ expander(REMOVED, "The operation failed because the file has been removed") \ expander(SUCCESS, "The file operation has succeeded") enum FileResult : unsigned int { #define DEFINE_ENUMERANT(name, description) FILE_##name, DEFINE_FILE_RESULTS(DEFINE_ENUMERANT) #undef DEFINE_ENUMERANT }; // FileResult FileResult fileResultFromError(Error result); const char* toDescription(FileResult result); const char* toString(FileResult result); } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_result_forward.h000066400000000000000000000001661516266226600236570ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { enum FileResult : unsigned int; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_result_or.h000066400000000000000000000001561516266226600226320ustar00rootroot00000000000000#pragma once #include #include sdk-10.11.0/include/mega/file_service/file_result_or_forward.h000066400000000000000000000005061516266226600243550ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { template using FileResultOr = common::Expected; using common::unexpected; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service.h000066400000000000000000000060451516266226600222570ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace file_service { class FileService { // Logs instance lifetime. common::InstanceLogger mInstanceLogger; // What context does this instance wrap? FileServiceContextPtr mContext; // Serializes access to mContext; common::SharedMutex mContextLock; public: FileService(); ~FileService(); // Add a foreign file to the service. auto add(NodeHandle handle, const common::NodeKeyData& keyData, std::size_t size) -> FileServiceResultOr; // Notify observer when a file changes. auto addObserver(FileEventObserver observer) -> FileServiceResultOr; // Create a new file. auto create(NodeHandle parent, const std::string& name) -> FileServiceResultOr; // Deinitialize the file service. void deinitialize(); // Retrieve information about a file managed by the file service. auto info(FileID id) -> FileServiceResultOr; // Initialize the file service. auto initialize(common::Client& client, const FileServiceOptions& options) -> FileServiceResult; auto initialize(common::Client& client) -> FileServiceResult; // Open a file for reading or writing. auto open(NodeHandle parent, const std::string& name) -> FileServiceResultOr; auto open(FileID id) -> FileServiceResultOr; // Update the file service's options. auto options(const FileServiceOptions& options) -> FileServiceResult; // Retrieve the file service's current options. auto options() -> FileServiceResultOr; // Purge all files from storage. // // This function is intended to be used by integration tests. // // If you do happen to call it in a different context, be aware that // this function will block the caller until all file (or file info) // references have been dropped. auto purge() -> FileServiceResult; // Reclaim storage space. void reclaim(ReclaimCallback callback); // Remove a previously added file observer. auto removeObserver(FileEventObserverID id) -> FileServiceResult; // How much storage is the service using? auto storageUsed() -> FileServiceResultOr; }; // FileService } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_callbacks.h000066400000000000000000000003751516266226600242560ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { using ReclaimCallback = std::function)>; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_context_badge.h000066400000000000000000000001571516266226600251430ustar00rootroot00000000000000#pragma once #include #include sdk-10.11.0/include/mega/file_service/file_service_context_badge_forward.h000066400000000000000000000003751516266226600266710ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { using FileServiceContextBadge = common::Badge; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_context_forward.h000066400000000000000000000001601516266226600255370ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileServiceContext; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_context_pointer.h000066400000000000000000000003511516266226600255550ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { using FileServiceContextPtr = std::unique_ptr; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_forward.h000066400000000000000000000001511516266226600237730ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class FileService; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_options.h000066400000000000000000000024301516266226600240240ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace file_service { struct FileServiceOptions { // How many times will we try to download a range before we give up. std::uint64_t mMaximumRangeRetries = 5u; // Specifies the minimum distance between ranges before they are merged. std::uint64_t mMinimumRangeDistance = 1u << 17; // Specifies the unit of transfer from the cloud. std::uint64_t mMinimumRangeSize = 1u << 21; // How long should we wait between retries? common::deciseconds mRangeRetryBackoff{20}; // How long shouldn't we access a file before we can reclaim it? std::chrono::hours mReclaimAgeThreshold{3 * 24}; // How many files should we reclaim at a time? std::size_t mReclaimBatchSize = 4u; // How long after startup should we wait until we reclaim space? std::chrono::seconds mReclaimDelay{30 * 60}; // How often should we try to reclaim space? std::chrono::seconds mReclaimPeriod{2 * 60 * 60}; // How many bytes can the service store before it needs to reclaim space? std::uint64_t mReclaimSizeThreshold{0}; }; // FileServiceOptions } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_options_forward.h000066400000000000000000000001611516266226600255470ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileServiceOptions; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_result.h000066400000000000000000000026601516266226600236540ustar00rootroot00000000000000#pragma once #include namespace mega { namespace file_service { #define DEFINE_FILE_SERVICE_RESULTS(expander) \ expander(ALREADY_INITIALIZED, "The File Service has already been initialized") \ expander(FILE_ALREADY_EXISTS, "The file you're trying to create already exists") \ expander(FILE_DOESNT_EXIST, "The file you're trying to open doesn't exist") \ expander(FILE_IS_A_DIRECTORY, "The file you're trying to open is a directory") \ expander(INVALID_FILE_KEY, "The file key you've provided is invalid") \ expander(INVALID_NAME, "The name you've specified is invalid") \ expander(PARENT_DOESNT_EXIST, "The parent you've specified doesn't exist") \ expander(PARENT_IS_A_FILE, "The parent you've specified is a file") \ expander(SUCCESS, "The File Service completed the operation successfully") \ expander(UNEXPECTED, "The File Service encountered an unexpected error") \ expander(UNINITIALIZED, "The File Service has not been initialized") \ expander(UNKNOWN_FILE, "The specified file isn't known by the File Service") enum FileServiceResult : unsigned int { #define DEFINE_ENUMERANT(name, description) FILE_SERVICE_##name, DEFINE_FILE_SERVICE_RESULTS(DEFINE_ENUMERANT) #undef DEFINE_ENUMERANT }; // FileServiceResult const char* toDescription(FileServiceResult result); const char* toString(FileServiceResult result); } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_result_forward.h000066400000000000000000000001751516266226600253770ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { enum FileServiceResult : unsigned int; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_service_result_or.h000066400000000000000000000001561516266226600243520ustar00rootroot00000000000000#pragma once #include #include sdk-10.11.0/include/mega/file_service/file_service_result_or_forward.h000066400000000000000000000005341516266226600260760ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { template using FileServiceResultOr = common::Expected; using common::unexpected; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_touch_event.h000066400000000000000000000011371516266226600231370ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { struct FileTouchEvent { bool operator==(const FileTouchEvent& rhs) const { return mID == rhs.mID && mModified == rhs.mModified; } bool operator!=(const FileTouchEvent& rhs) const { return !(*this == rhs); } // The ID of the file that has been touched. FileID mID; // The file's new modification time. std::int64_t mModified; }; // FileTouchEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_touch_event_forward.h000066400000000000000000000001551516266226600246620ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileTouchEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_truncate_event.h000066400000000000000000000014251516266226600236420ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace file_service { struct FileTruncateEvent { bool operator==(const FileTruncateEvent& rhs) const { return mID == rhs.mID && mSize == rhs.mSize && mRange == rhs.mRange; } bool operator!=(const FileTruncateEvent& rhs) const { return !(*this == rhs); } // The portion of the file, if any, that has been "cut off." std::optional mRange; // The ID of the file that has been truncated. FileID mID; // The file's new size. std::uint64_t mSize; }; // FileTruncateEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_truncate_event_forward.h000066400000000000000000000001601516266226600253610ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileTruncateEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_write_event.h000066400000000000000000000010121516266226600231370ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { struct FileWriteEvent { bool operator==(const FileWriteEvent& rhs) const { return mID == rhs.mID && mRange == rhs.mRange; } // The portion of the file that was written. FileRange mRange; // The ID of the file that was written. FileID mID; }; // FileWriteEvent } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_write_event_forward.h000066400000000000000000000001551516266226600246720ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { struct FileWriteEvent; } // file_service } // mega sdk-10.11.0/include/mega/file_service/file_write_result.h000066400000000000000000000005151516266226600233430ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace file_service { struct FileWriteResult { // Where we wrote data to the file. std::uint64_t mOffset; // How data we wrote to the file. std::uint64_t mLength; }; // FileWriteResult } // file_service } // mega sdk-10.11.0/include/mega/file_service/sink.h000066400000000000000000000006631516266226600205640ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { class Sink { protected: Sink() = default; public: virtual ~Sink() = default; // Write data into the sink. virtual auto write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair = 0; }; // Sink } // file_service } // mega sdk-10.11.0/include/mega/file_service/sink_forward.h000066400000000000000000000001421516266226600223000ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class Sink; } // file_service } // mega sdk-10.11.0/include/mega/file_service/source.h000066400000000000000000000006751516266226600211230ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace file_service { class Source { protected: Source() = default; public: virtual ~Source() = default; // Read data from the source. virtual auto read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair = 0; }; // Source } // file_service } // mega sdk-10.11.0/include/mega/file_service/source_forward.h000066400000000000000000000001441516266226600226360ustar00rootroot00000000000000#pragma once namespace mega { namespace file_service { class Source; } // file_service } // mega sdk-10.11.0/include/mega/fileattributefetch.h000066400000000000000000000032731516266226600210360ustar00rootroot00000000000000/** * @file mega/fileattributefetch.h * @brief Classes for file attributes fetching * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_FILEATTRIBUTEFETCH_H #define MEGA_FILEATTRIBUTEFETCH_H 1 #include "backofftimer.h" #include "types.h" #include "http.h" namespace mega { // file attribute fetching for a specific source cluster struct MEGA_API FileAttributeFetchChannel { MegaClient *client; handle fahref; BackoffTimer bt; BackoffTimer timeout; HttpReq req; dstime urltime; string posturl; size_t inbytes; faf_map fafs[2]; error e; // dispatch new and retrying attributes by POSTing to existing URL void dispatch(); // parse fetch result and remove completed attributes from pending void parse(int, bool); // notify app of nodes that failed to receive their requested attribute void failed(); FileAttributeFetchChannel(MegaClient*); }; // pending individual attribute fetch struct MEGA_API FileAttributeFetch { handle nodehandle; string nodekey; fatype type; int retries; int tag; FileAttributeFetch(handle, string, fatype, int); }; } // namespace #endif sdk-10.11.0/include/mega/filefingerprint.h000066400000000000000000000074661516266226600203600ustar00rootroot00000000000000/** * @file mega/filefingerprint.h * @brief Sparse file fingerprint * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include #include "types.h" namespace mega { struct MEGA_API InputStreamAccess { virtual m_off_t size() = 0; virtual bool read(byte *, unsigned) = 0; virtual ~InputStreamAccess() { } }; // Tolerance threshold (in seconds) for filesystem modification time comparisons constexpr unsigned FS_MTIME_TOLERANCE_SECS = 2; /* IMPORTANT: * In case we want to perform a `mtime` update, the new value must be greater than * `FS_MTIME_TOLERANCE_SECS` respect the previous one, otherwise it won't be detected */ constexpr unsigned MIN_ALLOW_MTIME_DIFFERENCE = FS_MTIME_TOLERANCE_SECS + 1; // sparse file fingerprint, including size and mtime struct MEGA_API FileFingerprint : public Cacheable { m_off_t size = -1; m_time_t mtime = 0; FingerprintCrc crc{}; // if true, represents actual file data // if false, is constructed from node ctime/key bool isvalid = false; // Generates a fingerprint by iterating through`fa` bool genfingerprint(FileAccess* fa, bool ignoremtime = false); // Generates a fingerprint by iterating through `is` bool genfingerprint(InputStreamAccess* is, m_time_t cmtime, bool ignoremtime = false); // Includes CRC and mtime // Be wary that these must be used in pair; do not mix with serialize pair void serializefingerprint(string* d) const; int unserializefingerprint(const string* d); FileFingerprint() = default; FileFingerprint(const FileFingerprint&); FileFingerprint& operator=(const FileFingerprint& other); // Includes size, CRC, mtime, and isvalid // Be wary that these must be used in pair; do not mix with serializefingerprint pair bool serialize(string* d) const override; // Includes size, CRC, and isvalid (mtime is not included) // This method can be used to serialize a file fingerprint without mtime, that could be used to // find nodes with same content but only differs in mtime. // Note: This method should not be used to serialize fingerprints that will be stored on Db bool serializeExcludingMtime(string* d) const; static unique_ptr unserialize(const char*& ptr, const char* end); // convenience function for clear comparisons etc, referring to (this) base class const FileFingerprint& fingerprint() const { return *this; } string fingerprintDebugString() const; bool EqualExceptValidFlag(const FileFingerprint& rhs) const; bool equalExceptMtime(const FileFingerprint& rhs) const; bool equalExceptMtimeAndIsValid(const FileFingerprint& rhs) const; }; // orders transfers by file fingerprints, ordered by size / mtime / sparse CRC struct MEGA_API FileFingerprintCmp { bool operator()(const FileFingerprint* a, const FileFingerprint* b) const; bool operator()(const FileFingerprint& a, const FileFingerprint& b) const; }; struct MEGA_API FileFingerprintCmpNoMtime { bool operator()(const FileFingerprint* a, const FileFingerprint* b) const; bool operator()(const FileFingerprint& a, const FileFingerprint& b) const; }; bool operator==(const FileFingerprint& lhs, const FileFingerprint& rhs); bool operator!=(const FileFingerprint& lhs, const FileFingerprint& rhs); } // mega sdk-10.11.0/include/mega/filesystem.h000066400000000000000000001067611516266226600173530ustar00rootroot00000000000000/** * @file mega/filesystem.h * @brief Generic host filesystem access interfaces * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_FILESYSTEM_H #define MEGA_FILESYSTEM_H 1 #include "filefingerprint.h" #include "scoped_helpers.h" #include "types.h" #include "utils.h" #include "waiter.h" #include #include #include namespace mega { typedef void (*asyncfscallback)(void *); struct MEGA_API AsyncIOContext; // Opaque filesystem fingerprint. class fsfp_t { // Legacy filesystem fingerprint. // // Not necessarily unique or persistent. // // ntfs-3g or any FUSE filesystem would be an example of where // this value is not very meaningful. // // Legacy XFS would be an example of a filesystem where this // value while unique at runtime is not persistent. // // Maintained for backwards compatibility. std::uint64_t mFingerprint = 0; // Filesystem UUID. // // Should be unique and persistent across all systems. std::string mUUID; public: fsfp_t() = default; fsfp_t(std::uint64_t fingerprint, std::string uuid); fsfp_t(const fsfp_t& other) = default; fsfp_t(fsfp_t&& other) = default; operator bool() const; bool operator!() const { return !operator bool(); } fsfp_t& operator=(const fsfp_t& rhs) = default; fsfp_t& operator=(fsfp_t&& rhs) = default; bool operator==(const fsfp_t& rhs) const; bool operator<(const fsfp_t& rhs) const; bool operator!=(const fsfp_t& rhs) const { return !operator==(rhs); } bool equivalent(const fsfp_t& rhs) const; std::uint64_t fingerprint() const; void reset(); const std::string& uuid() const; std::string toString() const; }; // fsfp_t // Keeps track of known filesystem fingerprints. class fsfp_tracker_t { // Compare IDs by reference. struct Less { bool operator()(const fsfp_t* lhs, const fsfp_t* rhs) const; }; // Less // Fingerprint and reference count. using Entry = std::pair; // Maps fingerprints to fingerprint entry. using Map = std::map; // Tracks which fingerprints we're aware of. Map mFingerprints; public: fsfp_tracker_t() = default; fsfp_tracker_t(const fsfp_tracker_t&) = delete; fsfp_tracker_t& operator=(const fsfp_tracker_t&) = delete; // Add an ID to the tracker. fsfp_ptr_t add(const fsfp_t& id); // Retrieve an ID from the tracker. fsfp_ptr_t get(const fsfp_t& id) const; // Remove an ID from the tracker. bool remove(const fsfp_t& id); }; // fsfp_tracker_t // LocalPath represents a path in the local filesystem, and wraps up common operations in a convenient fashion. // On mac/linux, local paths are in utf8 but in windows local paths are utf16, that is wrapped up here. struct MEGA_API FileSystemAccess; class MEGA_API Sync; struct MEGA_API FSNode; extern CodeCounter::ScopeStats g_compareUtfTimings; inline std::ostream& operator<<(std::ostream& os, const LocalPath& p) { return os << p.toPath(false); } class RemotePath { public: // Create an empty path. RemotePath() = default; // Create a remote path from a string. RemotePath(const string& path); MEGA_DEFAULT_COPY_MOVE(RemotePath); // For convenience. RemotePath& operator=(const string& rhs); // Compare this path against another. bool operator==(const RemotePath& rhs) const; bool operator==(const string& rhs) const; // Return a string representing this path. operator const string&() const; // Add a component to the end of this path. void appendWithSeparator(const RemotePath& component, bool always); void appendWithSeparator(const string& component, bool always); // Query whether the path begins with a separator. bool beginsWithSeparator() const; // Clear the path. void clear(); // Query whether the path is empty. bool empty() const; // Query whether the path ends with a separator. bool endsInSeparator() const; // Locate the next path separator. bool findNextSeparator(size_t& index) const; // Query whether the path has any further components. bool hasNextPathComponent(size_t index) const; // Retrieve the next path component. bool nextPathComponent(size_t& index, RemotePath& component) const; // Add a path component to the start of this path. void prependWithSeparator(const RemotePath& component); // Return a string representing this path. const string& str() const; // Create a new path based on a portion of another. RemotePath subpathFrom(size_t index) const; RemotePath subpathTo(size_t index) const; // For compatibility with LocalPath. // // Useful when we're metaprogramming and don't knwo whether the type // provided by the caller is a local or remote path. const string &toName(const FileSystemAccess&) const; private: string mPath; }; // RemotePath // For convenience. first = leaf name only second = relative path using RemotePathPair = pair; // For metaprogramming. template struct IsPath : public std::false_type { }; // IsPath template<> struct IsPath : public std::true_type { }; // IsPath template<> struct IsPath : public std::true_type { }; // IsPath struct NameConflict { struct NameHandle { string name; NodeHandle handle; NameHandle(const string& s, NodeHandle h) : name(s), handle(h) {} }; string cloudPath; vector clashingCloud; LocalPath localPath; vector clashingLocalNames; }; void AddHiddenFileAttribute(mega::LocalPath& path); void RemoveHiddenFileAttribute(mega::LocalPath& path); /** * @brief * Checks whether a contains b. * * @return * True if a contains b. */ bool IsContainingLocalPathOf(const string& a, const string& b); bool IsContainingLocalPathOf(const string& a, const char* b, size_t bLength); bool IsContainingCloudPathOf(const string& a, const string& b); bool IsContainingCloudPathOf(const string& a, const char* b, size_t bLength); class FileNameGenerator { public: // It generates a new name suffixed with (n). It is not conflicted in the file system // always a new name is returned static LocalPath suffixWithN(FileAccess* fa, const LocalPath& localname); // It generates a new name suffixed with .oldn. It is not conflicted in the file system // always a new name is returned static LocalPath suffixWithOldN(FileAccess* fa, const LocalPath& localname); private: static LocalPath suffix(FileAccess* fa, const LocalPath& localname, std::function suffixF); }; class FileDistributor { // This class is to manage copying/moving a Transfer's downloaded file // To all the correct locations (one per File that the transfer owns) // Taking into account that the sync thread will do some of the moving/copying // So it has to operate properly across threads. // Note that the file is not always "tmp", one of the target locations // may actually be the place it was downloaded to, rather than a tmp location. recursive_mutex mMutex; LocalPath theFile; size_t numTargets; bool actualPathUsed = false; m_time_t mMtime; FileFingerprint confirmFingerprint; public: FileDistributor(const LocalPath& lp, size_t ntargets, m_time_t mtime, const FileFingerprint& confirm); ~FileDistributor(); enum TargetNameExistsResolution { OverwriteTarget, RenameWithBracketedNumber, MoveReplacedFileToSyncDebris, RenameExistingToOldN, }; bool distributeTo(LocalPath& lp, FileSystemAccess& fsaccess, TargetNameExistsResolution, bool& transient_error, bool& name_too_long, Sync* syncForDebris); // Remove a pending distribution target. void removeTarget(); // static functions used by distributeTo // these will be useful for other cases also LocalPath distributeFromPath() { lock_guard g(mMutex); return theFile; } private: static bool moveTo(const LocalPath& source, LocalPath& target, TargetNameExistsResolution, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, const FileFingerprint& confirmFingerprint); static bool moveToForMethod_RenameWithBracketedNumber(const LocalPath& source, LocalPath& target, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long); static bool moveToForMethod_RenameExistingToOldN(const LocalPath& source, LocalPath& target, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long); static bool copyTo(const LocalPath& source, LocalPath& target, m_time_t mtime, TargetNameExistsResolution, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, const FileFingerprint& confirmFingerprint); static bool copyToForMethod_RenameWithBracketedNumber(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long); static bool copyToForMethod_RenameExistingToOldN(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long); static bool copyToForMethod_OverwriteTarget(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long, const FileFingerprint& confirmFingerprint); #ifdef ENABLE_SYNC static bool moveToForMethod_MoveReplacedFileToSyncDebris(const LocalPath& source, LocalPath& target, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, const FileFingerprint& confirmFingerprint); static bool copyToForMethod_MoveReplacedFileToSyncDebris(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsaccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, const FileFingerprint& confirmFingerprint); #endif }; enum OpenFlag { OPEN_RDONLY = 0x00000000, OPEN_WRONLY = 0x00000001, OPEN_RDWR = 0x00000002, OPEN_ACCMODE = 0x00000003, /* mask for above modes */ }; constexpr bool openRead(OpenFlag flag) { return (flag & OPEN_ACCMODE) != OPEN_WRONLY; } constexpr bool openWrite(OpenFlag flag) { return (flag & OPEN_ACCMODE) != OPEN_RDONLY; } struct MEGA_API AsyncIOContext { enum { NONE, READ, WRITE, OPEN }; enum { ACCESS_NONE = 0x00, ACCESS_READ = 0x01, ACCESS_WRITE = 0x02 }; virtual ~AsyncIOContext(); virtual void finish(); static OpenFlag toOpenFlag(int access); // results asyncfscallback userCallback = nullptr; void *userData = nullptr; bool finished = false; bool failed = false; bool retry = false; // parameters int op = NONE; int access = ACCESS_NONE; m_off_t posOfBuffer = 0; unsigned pad = 0; LocalPath openPath; byte* dataBuffer = nullptr; unsigned dataBufferLen = 0; Waiter *waiter = nullptr; FileAccess *fa = nullptr; }; // map a request tag with pending paths of temporary files typedef map > pendingfiles_map; struct MEGA_API DirAccess; struct FSLogging { enum Setting { eNoLogging, eLogOnError, eLogExceptFileNotFound }; Setting setting; FSLogging(Setting s) : setting(s) {} bool doLog(int os_errorcode); static FSLogging noLogging; static FSLogging logOnError; static FSLogging logExceptFileNotFound; private: static bool isFileNotFound(int error); }; // generic host file/directory access interface struct MEGA_API FileAccess { // if true, then size and mtime are valid bool fopenSucceeded = false; // file size m_off_t size = 0; // mtime of a file opened for reading m_time_t mtime = 0; // local filesystem record id (survives renames & moves) handle fsid = 0; bool fsidvalid = false; // type of opened path nodetype_t type = TYPE_UNKNOWN; // if opened path is a symlink bool mIsSymLink = false; // if the open failed, retry indicates a potentially transient reason bool retry = false; // OS error code related to the last call to fopen() without parameters int errorcode = 0; // for files "opened" in nonblocking mode, the current local filename LocalPath nonblocking_localname; // waiter to notify on filesystem events Waiter *waiter; // blocking mode: open for reading, writing or reading and writing. // This one really does open the file, and openf(), closef() will have no effect // If iteratingDir is supplied, this fopen() call must be for the directory entry being iterated by dopen()/dnext() virtual bool fopen(const LocalPath&, OpenFlag flag, FSLogging, DirAccess* iteratingDir = nullptr, bool ignoreAttributes = false, bool skipcasecheck = false, LocalPath* actualLeafNameIfDifferent = nullptr) = 0; // nonblocking open: Only prepares for opening. Actually stats the file/folder, getting mtime, size, type. // Call openf() afterwards to actually open it if required. For folders, returns false with type==FOLDERNODE. bool fopen(const LocalPath&, FSLogging); // Open for MAC computation - allows file to be moved/deleted while open (FILE_SHARE_DELETE on // Windows). Returns true if file was successfully opened for reading. Default implementation // calls regular fopen. Windows overrides with FILE_SHARE_DELETE. virtual bool fopenForMacRead(const LocalPath& path, FSLogging fsl) { return fopen(path, OpenFlag::OPEN_RDONLY, fsl); } // check if a local path is a folder bool isfolder(const LocalPath& path); // check if local path is a file. bool isfile(const LocalPath& path); // update localname (only has an effect if operating in by-name mode) virtual void updatelocalname(const LocalPath&, bool force) = 0; // absolute position read, with NUL padding bool fread(string* buffer, unsigned long length, unsigned long padding, m_off_t offset, FSLogging logging, bool* retry = nullptr); // absolute position read to byte buffer virtual bool frawread(void* buffer, unsigned long length, m_off_t offset, bool alreadyOpened, FSLogging logging, bool* retry = nullptr); // After a successful nonblocking fopen(), call openf() to really open the file (by localname) // (this is a lazy-type approach in case we don't actually need to open the file after finding out type/size/mtime). // If the size or mtime changed, it will fail. virtual bool openf(FSLogging); // After calling openf(), make sure to close the file again quickly with closef(). virtual void closef(); // Close an already open file. virtual void fclose() = 0; // absolute position write virtual bool fwrite(const void* buffer, unsigned long length, m_off_t position, unsigned long* numWritten = nullptr, bool* retry = nullptr) = 0; // Stat an already open file. virtual bool fstat(m_time_t& modified, m_off_t& size) = 0; // Convenience version of the above. // Updates mtime and size. bool fstat(); // Truncate a file. virtual bool ftruncate(m_off_t size = 0) = 0; FileAccess(Waiter *waiter); virtual ~FileAccess(); virtual bool asyncavailable() { return false; } AsyncIOContext *asyncfopen(const LocalPath&, FSLogging); // non-locking ops: open/close temporary hFile bool asyncopenf(FSLogging fsl); void asyncclosef(); AsyncIOContext *asyncfopen(const LocalPath&, bool, bool, m_off_t = 0); AsyncIOContext* asyncfread(string*, unsigned, unsigned, m_off_t, FSLogging fsl); AsyncIOContext* asyncfwrite(const byte *, unsigned, m_off_t); // Mark this file as a sparse file. virtual bool setSparse() = 0; // Retrieve this file's allocated and reported size. virtual auto getFileSize() const -> std::optional> = 0; protected: virtual AsyncIOContext* newasynccontext(); static void asyncopfinished(void *param); bool isAsyncOpened; int numAsyncReads; // system-specific raw read/open/close to be provided by platform implementation. fopen / openf / fread etc are implemented by calling these. virtual bool sysread(void* buffer, unsigned long length, m_off_t offset, bool* retry) = 0; virtual bool sysstat(m_time_t*, m_off_t*, FSLogging) = 0; virtual bool sysopen(bool async, FSLogging) = 0; virtual void sysclose() = 0; virtual void asyncsysopen(AsyncIOContext*); virtual void asyncsysread(AsyncIOContext*); virtual void asyncsyswrite(AsyncIOContext*); }; class MEGA_API FileInputStream : public InputStreamAccess { FileAccess *fileAccess; m_off_t offset; public: FileInputStream(FileAccess *fileAccess); m_off_t size() override; bool read(byte *buffer, unsigned size) override; }; // generic host directory enumeration struct MEGA_API DirAccess { // open for scanning virtual bool dopen(LocalPath*, FileAccess*, bool) = 0; // get next record virtual bool dnext(LocalPath&, LocalPath&, bool = true, nodetype_t* = NULL) = 0; virtual ~DirAccess() { } }; #ifdef ENABLE_SYNC struct Notification { bool fromDebris(const Sync& sync) const; bool invalidated() const; enum ScanRequirement { NEEDS_PARENT_SCAN, // normal case. For a plain path (eg. file), we would scan the parent to see if the item at this path chagned FOLDER_NEEDS_SELF_SCAN, // But, if the notification means that we should scan the contents of the actual path rather than the parent, set this flag. FOLDER_NEEDS_SCAN_RECURSIVE // And sometimes we need to scan recursively from that point (eg. glitch in stream of incoming notifications) }; dstime timestamp; ScanRequirement scanRequirement = NEEDS_PARENT_SCAN; LocalPath path; LocalNode* localnode = nullptr; Notification() {} Notification(dstime ts, ScanRequirement sr, const LocalPath& p, LocalNode* ln) : timestamp(ts), scanRequirement(sr), path(p), localnode(ln) {} }; struct NotificationDeque : ThreadSafeDeque { void replaceLocalNodePointers(LocalNode* check, LocalNode* newvalue) { std::lock_guard g(m); for (auto& n : mNotifications) { if (n.localnode == check) { n.localnode = newvalue; } } } }; // filesystem change notification, highly coupled to Syncs and LocalNodes. struct MEGA_API DirNotify { // Thread safe so that a separate thread can listen for filesystem notifications (for windows for now, maybe more platforms later) NotificationDeque fsEventq; private: // these next few fields may be updated by notification-reading threads std::mutex mMutex; // set if no notification available on this platform or a permanent failure // occurred int mFailed; // reason of the permanent failure of filesystem notifications string mFailReason; public: // set if a temporary error occurred. May be set from a thread. std::atomic mErrorCount; // thread safe setter/getters void setFailed(int errCode, const string& reason); int getFailed(string& reason); // base path LocalPath localbasepath; void notify(NotificationDeque&, LocalNode *, Notification::ScanRequirement, LocalPath&&, bool = false); DirNotify(const LocalPath& rootPath); virtual ~DirNotify() {} bool empty(); }; #endif // For directoryScan(...). struct MEGA_API FSNode; // generic host filesystem access interface struct MEGA_API FileSystemAccess : public EventTrigger { // waiter to notify on filesystem events Waiter *waiter = nullptr; // indicate target_exists error logging is not necessary on this call as we may try something else for overall operation success bool skip_targetexists_errorreport = false; /** * @brief instantiate FileAccess object * @param followSymLinks whether symlinks should be followed when opening a path (default: true) * @return */ virtual std::unique_ptr newfileaccess(bool followSymLinks = true) = 0; // instantiate DirAccess object virtual unique_ptr newdiraccess() = 0; #ifdef ENABLE_SYNC // instantiate DirNotify object (default to periodic scanning handler if no // notification configured) with given root path virtual DirNotify* newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter); #endif // Extracts the character encoded by the escape sequence %ab at s, // if it is one, // which must be part of a null terminated c-style string bool decodeEscape(const char* s, char& escapedChar) const; bool islocalfscompatible(const int character, const FileSystemType type) const; void escapefsincompatible(string*, FileSystemType fileSystemType) const; static const char *fstypetostring(FileSystemType type); virtual bool getlocalfstype(const LocalPath& path, FileSystemType& type) const = 0; FileSystemType getlocalfstype(const LocalPath& path) const; void unescapefsincompatible(string*) const; // obtain local secondary name virtual bool getsname(const LocalPath&, LocalPath&) const = 0; // rename file, overwrite target virtual bool renamelocal(const LocalPath&, const LocalPath&, bool = true) = 0; // copy file, overwrite target, set mtime virtual bool copylocal(const LocalPath&, const LocalPath&, m_time_t) = 0; // delete file virtual bool unlinklocal(const LocalPath&) = 0; // delete empty directory virtual bool rmdirlocal(const LocalPath&) = 0; // create directory, optionally hidden virtual bool mkdirlocal(const LocalPath&, bool hidden, bool logAlreadyExistsError) = 0; // make sure that we stay within the range of timestamps supported by the server data structures (unsigned 32-bit) static void captimestamp(m_time_t*); // set mtime virtual bool setmtimelocal(const LocalPath&, m_time_t) = 0; // get mtime virtual std::pair getmtimelocal(const LocalPath&) = 0; // change working directory virtual bool chdirlocal(LocalPath&) const = 0; // obtain lowercased extension static bool getextension(const LocalPath& path, std::string& extension) { return path.extension(extension); } // check if synchronization is supported for a specific path virtual bool issyncsupported(const LocalPath&, bool&, SyncError&, SyncWarning&) = 0; // get the absolute path corresponding to a path virtual bool expanselocalpath(const LocalPath& path, LocalPath& absolutepath) = 0; // default permissions for new files virtual int getdefaultfilepermissions() { return 0600; } virtual void setdefaultfilepermissions(int) { } // default permissions for new folder virtual int getdefaultfolderpermissions() { return 0700; } virtual void setdefaultfolderpermissions(int) { } // convenience function for getting filesystem shortnames std::unique_ptr fsShortname(const LocalPath& localpath); // convenience function for testing file existence at a path bool fileExistsAt(const LocalPath&); // set whenever an operation fails due to a transient condition (e.g. locking violation) bool transient_error = false; // set whenever an operation fails because the target already exists bool target_exists = false; // Set when an operation fails because the target file name is too long. bool target_name_too_long = false; // append local operating system version information to string. // Set includeArchExtraInfo to know if the app is 32 bit running on 64 bit (on windows, that is via the WOW subsystem) virtual void osversion(string*, bool /*includeArchExtraInfo*/) const {} // append id for stats virtual void statsid(string*) const { } FileSystemAccess(); MEGA_DISABLE_COPY_MOVE(FileSystemAccess); virtual ~FileSystemAccess() { } // Get the current working directory. static bool cwd_static(LocalPath& path); virtual bool cwd(LocalPath& path) const = 0; // Retrieve the fingerprint of the filesystem containing the specified path. virtual fsfp_t fsFingerprint(const LocalPath& path) const; #ifdef ENABLE_SYNC // True if the filesystem indicated by the specified path has stable FSIDs. virtual bool fsStableIDs(const LocalPath& path) const = 0; virtual bool initFilesystemNotificationSystem(); #endif // ENABLE_SYNC virtual ScanResult directoryScan(const LocalPath& path, handle expectedFsid, map& known, std::vector& results, bool followSymLinks, unsigned& nFingerprinted) = 0; // Retrieve the FSID of the item at the specified path. // UNDEF is returned if we cannot determine the item's FSID. handle fsidOf(const LocalPath& path, bool follow, bool skipcasecheck, FSLogging); // Create a hard link from source to target. // Returns false if the link could not be created. virtual bool hardLink(const LocalPath& source, const LocalPath& target) = 0; // @brief // Retrieves the number of bytes available on the specified filesystem. // // @param drivePath // The path to the filesystem you'd like to query. // // @return // On success, the number of free bytes available to the caller. // On failure, zero. virtual m_off_t availableDiskSpace(const LocalPath& drivePath) = 0; // Specify the minimum permissions for new created directories. static void setMinimumDirectoryPermissions(int permissions); // Specify the minimum permissions for newly created files. static void setMinimumFilePermissions(int permissions); // return a description of OS error, // errno on unix. Defaults to the number itself. static std::string getErrorMessage(int error); // Check if the specified file is "hidden." // // On UNIX systems, this function will only return true if the // file specified by path begins with the period character. // // That is, "a" would not be hidden but ".a" would be. // // On Windows systems, this function will only return true if the // file specified by the path has its "hidden" attribute set. // // Returns: // >0 if the file is hidden. // =0 if the file is not hidden. // <0 if the file cannot be accessed. static int isFileHidden(const LocalPath& path, FSLogging logWhen = FSLogging::logOnError); // Mark the specified file as "hidden." // // On UNIX systems, this function is a no-op and always returns true. // // On Windows systems, this function will set the file's "hidden" // attribute. // // Returns: // True if the file's hidden attribute was set. static bool setFileHidden(const LocalPath& path, FSLogging logWhen = FSLogging::logOnError); // Retrieve a file's allocated and reported size. auto getFileSize(const LocalPath& path) -> std::optional>; protected: // Specifies the minimum permissions allowed for directories. static std::atomic mMinimumDirectoryPermissions; // Specifies the minimum permissions allowed for files. static std::atomic mMinimumFilePermissions; /** * @brief Expands a LocalPath to its canonical absolute form. * * Resolves relative paths against the current working directory, * normalizes the path, and resolves symbolic links. The path must exist. * * @param source Input path (relative or absolute - No URI). * @param destination Receives the canonical absolute path on success. * @return true on success, false if resolution fails. */ bool expandLocalPathFileSystem(const LocalPath& source, LocalPath& destination); }; int compareUtf(const string&, bool unescaping1, const string&, bool unescaping2, bool caseInsensitive); int compareUtf(const string&, bool unescaping1, const LocalPath&, bool unescaping2, bool caseInsensitive); int compareUtf(const LocalPath&, bool unescaping1, const string&, bool unescaping2, bool caseInsensitive); int compareUtf(const LocalPath&, bool unescaping1, const LocalPath&, bool unescaping2, bool caseInsensitive); // Same as above except case insensitivity is determined by build platform. int platformCompareUtf(const string&, bool unescape1, const string&, bool unescape2); int platformCompareUtf(const string&, bool unescape1, const LocalPath&, bool unescape2); int platformCompareUtf(const LocalPath&, bool unescape1, const string&, bool unescape2); int platformCompareUtf(const LocalPath&, bool unescape1, const LocalPath&, bool unescape2); struct MEGA_API FSNode { // A structure convenient for containing just the attributes of one item from the filesystem LocalPath localname; unique_ptr shortname; nodetype_t type = TYPE_UNKNOWN; mega::handle fsid = mega::UNDEF; bool isSymlink = false; bool isBlocked = false; FileFingerprint fingerprint; // includes size, mtime bool equivalentTo(const FSNode& n) const { if (type != n.type) return false; if (fsid != n.fsid) return false; if (isSymlink != n.isSymlink) return false; if (type == FILENODE && !(fingerprint == n.fingerprint)) return false; if (localname != n.localname) return false; return (!shortname && (!n.shortname || localname == *n.shortname)) || (shortname && n.shortname && *shortname == *n.shortname); } unique_ptr cloneShortname() const { return unique_ptr( shortname ? new LocalPath(*shortname) : nullptr); } FSNode clone() const { FSNode f; f.localname = localname; f.shortname = cloneShortname(); f.type = type; f.fsid = fsid; f.isSymlink = isSymlink; f.isBlocked = isBlocked; f.fingerprint = fingerprint; return f; } static unique_ptr fromFOpened(FileAccess&, const LocalPath& fullName, FileSystemAccess& fsa); // Same as the above but useful in situations where we don't have an FA handy. static unique_ptr fromPath(FileSystemAccess& fsAccess, const LocalPath& path, bool skipCaseCheck, FSLogging); // Use this in asserts() to check if race conditions may be occurring static bool debugConfirmOnDiskFingerprintOrLogWhy(FileSystemAccess& fsAccess, const LocalPath& path, const FileFingerprint& ff); const string& toName_of_localname(const FileSystemAccess& fsaccess) { // Although FSNode wouldn't naturally have a utf8 and normalized version of localname, // we need to compare such a string during sorting operations. // Using a caching mechanism like this avoids execessive conversions, normalization, and computing that if it's not used. if (toName_of_localname_cached.empty()) { toName_of_localname_cached = localname.toName(fsaccess); } return toName_of_localname_cached; } private: string toName_of_localname_cached; }; class MEGA_API ScanService { public: ScanService(); ~ScanService(); // Concrete representation of a scan request. class ScanRequest { public: ScanRequest(shared_ptr waiter, bool followSymlinks, LocalPath targetPath, handle expectedFsid, map&& priorScanChildren); MEGA_DISABLE_COPY_MOVE(ScanRequest); bool completed() const { return mScanResult != SCAN_INPROGRESS; }; ScanResult completionResult() const { return mScanResult; }; std::vector&& resultNodes() { return std::move(mResults); } handle fsidScanned() { return mExpectedFsid; } private: friend class ScanService; // Waiter to notify when done shared_ptr mWaiter; // Whether the scan request is complete. std::atomic mScanResult; // SCAN_INPROGRESS; // Whether we should follow symbolic links. const bool mFollowSymLinks; // Details the known children of mTarget. map mKnown; // Results of the scan. vector mResults; // Path to the target. const LocalPath mTargetPath; // fsid that the target path should still referene handle mExpectedFsid; }; // ScanRequest // For convenience. using RequestPtr = std::shared_ptr; // Issue a scan for the given target. RequestPtr queueScan(LocalPath targetPath, handle expectedFsid, bool followSymlinks, map&& priorScanChildren, shared_ptr waiter); // Track performance (debug only) static CodeCounter::ScopeStats syncScanTime; private: // Convenience. using ScanRequestPtr = std::shared_ptr; // Processes scan requests. class Worker { public: Worker(size_t numThreads = 1); ~Worker(); MEGA_DISABLE_COPY_MOVE(Worker); // Queues a scan request for processing. void queue(ScanRequestPtr request); private: // Thread entry point. void loop(); // Processes a scan request. ScanResult scan(ScanRequestPtr request, unsigned& nFingerprinted); // Filesystem access. std::unique_ptr mFsAccess; // Pending scan requests. std::deque mPending; // Guards access to the above. std::mutex mPendingLock; std::condition_variable mPendingNotifier; // Worker threads. std::vector mThreads; }; // Worker // How many services are currently active. static std::atomic mNumServices; // Worker shared by all services. static std::unique_ptr mWorker; // Synchronizes access to the above. static std::mutex mWorkerLock; }; // ScanService // True if type denotes a network filesystem. bool isNetworkFilesystem(FileSystemType type); } // namespace #endif sdk-10.11.0/include/mega/fuse/000077500000000000000000000000001516266226600157455ustar00rootroot00000000000000sdk-10.11.0/include/mega/fuse/common/000077500000000000000000000000001516266226600172355ustar00rootroot00000000000000sdk-10.11.0/include/mega/fuse/common/CMakeLists.txt000066400000000000000000000027631516266226600220050ustar00rootroot00000000000000target_sources(SDKlib PRIVATE any_lock.h any_lock_forward.h any_lock_set.h any_lock_set_forward.h client.h file_explorer_view.h file_move_flag.h file_move_flag_forward.h file_open_flag.h file_open_flag_forward.h inode_cache_flags.h inode_cache_flags_forward.h inode_id.h inode_id_forward.h inode_info.h inode_info_forward.h logger.h logging.h mount_event.h mount_event_forward.h mount_event_type.h mount_event_type_forward.h mount_flags.h mount_flags_forward.h mount_info.h mount_info_forward.h mount_inode_id.h mount_inode_id_forward.h mount_result.h mount_result_forward.h service.h service_callbacks.h service_context.h service_context_forward.h service_flags.h service_flags_forward.h service_forward.h ) sdk-10.11.0/include/mega/fuse/common/any_lock.h000066400000000000000000000073521516266226600212140ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace fuse { class AnyLock { struct OperationsBase { // Acquire this lock. virtual void lock(void* lock) const = 0; // Try and acquire this lock. virtual bool try_lock(void* lock) const = 0; // Release this lock. virtual void unlock(void* lock) const = 0; }; // OperationsBase template struct Operations : public OperationsBase { // Acquire this lock. void lock(void* lock) const override { to_lock(lock).lock(); } // Retrieve typed reference to specified lock. T& to_lock(void* lock) const { return *reinterpret_cast(lock); } // Try and acquire this lock. bool try_lock(void* lock) const override { return to_lock(lock).try_lock(); } // Release this lock. void unlock(void* lock) const override { to_lock(lock).unlock(); } }; // Operations // Retrieve operations for a specific lock type. template static const OperationsBase* operations() { static const Operations operations; return &operations; } // What lock are we currently wrapping? void* mLock; // How do we operate on that lock? const OperationsBase* mOperations; // Do we own this lock? bool mOwned; public: AnyLock() : mLock(nullptr) , mOperations(nullptr) , mOwned(false) { } template AnyLock(const T& lock, std::defer_lock_t defer) : AnyLock(const_cast(lock), defer) { } template AnyLock(T& lock, std::defer_lock_t) : mLock(reinterpret_cast(&lock)) , mOperations(operations()) , mOwned(false) { } template AnyLock(T& lock) : AnyLock(lock, std::defer_lock) { // Acquire the lock. this->lock(); } AnyLock(AnyLock&& other) : mLock(std::move(other.mLock)) , mOperations(std::move(other.mOperations)) , mOwned(other.mOwned) { other.mLock = nullptr; other.mOperations = nullptr; other.mOwned = false; } ~AnyLock() { if (mOwned) unlock(); } // Do we own this lock? operator bool() const { return owns_lock(); } AnyLock& operator=(AnyLock&& rhs) { AnyLock temp(std::move(rhs)); swap(temp); return *this; } // Acquire this lock. void lock() { assert(mLock); assert(mOperations); assert(!mOwned); mOperations->lock(mLock); mOwned = true; } // Do we currently own this lock? bool owns_lock() const { return mOwned; } // Release ownership of this lock. void release() { mLock = nullptr; mOperations = nullptr; mOwned = false; } // Swap this lock with another. void swap(AnyLock& other) { using std::swap; swap(mLock, other.mLock); swap(mOperations, other.mOperations); swap(mOwned, other.mOwned); } // Try and acquire this lock. bool try_lock() { assert(mLock); assert(mOperations); mOwned = mOperations->try_lock(mLock); return mOwned; } // Release this lock. void unlock() { assert(mLock); assert(mOperations); assert(mOwned); mOperations->unlock(mLock); mOwned = false; } }; // AnyLock } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/any_lock_forward.h000066400000000000000000000002261516266226600227310ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { class AnyLock; using AnyLockVector = std::vector; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/any_lock_set.h000066400000000000000000000064111516266226600220620ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace fuse { // Allows the user to treat multiple locks as if they were one. class AnyLockSet { // What locks does this set contain? AnyLockVector mLocks; // Do we own all the locks in the set? bool mOwned; public: AnyLockSet(); AnyLockSet(AnyLockSet&& other); AnyLockSet& operator=(AnyLockSet&& rhs); // Do we own all the locks in this set? operator bool() const; // Clear all locks from the set. void clear(); // Does this set contain any locks? bool empty() const; // Add a lock to the set. template void emplace(T& lock) { // You should never add a lock when locks are held. assert(!mOwned); // Add the lock to the set. mLocks.emplace_back(lock, std::defer_lock); } // Add a number of locks to the set. template void emplace(T& first, Ts&... rest) { emplace(first); emplace(rest...); } // Acquire each lock in the set. // // Control will not return to the caller until each lock in the set // has been acquired. If the method can't acquire a given lock, it // releases any locks it did acquire and retries. That is, locks are // acquired in a dead-lock free manner. void lock(); // Do we own all the locks in this set? bool owns_lock() const; // Release ownership of each lock in the set. void release(); // How many locks does this set contain? AnyLockVector::size_type size() const; // Swap this set with another. void swap(AnyLockSet& other); // Try and acquire each lock in the set. // // Control returns immediately to the caller in all cases. // // If the method is unable to acquire a lock in the set, it releases any // locks it was able to acquire and returns false to the caller. bool try_lock(); // Unlock each lock in the set. void unlock(); }; // AnyLockSet // Creates an unlocked set containing the specified locks. // // The behavior of this function is modeled after std::unique_lock: // // std::mutex ma; // std::mutex mb; // // std::unique_lock l0(ma, std::defer); // std::unique_lock l1(mb, std::defer); // // std::lock(l0, l1); // // Put differently: We want to group together a bunch of locks so // to make life a little more convenient but we may not want to // actually acquire those locks immediately. Maybe we want to pass them // somewhere else and acquire them later, or perhaps we want to acquire // them in combination with other locks via std::lock(...). template AnyLockSet deferredLockAll(T& first, Ts&... rest) { AnyLockSet locks; // Add locks to the set. locks.emplace(first, rest...); // Return set to caller. return locks; } // Creates a locked set containing the specified locks. template AnyLockSet lockAll(T& first, Ts&... rest) { // Add locks to a new set. auto locks = deferredLockAll(first, rest...); // Acquire locks. locks.lock(); // Return set to caller. return locks; } } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/any_lock_set_forward.h000066400000000000000000000001311516266226600235770ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { class AnyLockSet; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/client.h000066400000000000000000000003611516266226600206640ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace fuse { // Emit a FUSE event. void emitEvent(common::Client& client, const MountEvent& event); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/file_explorer_view.h000066400000000000000000000007721516266226600233050ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { #define DEFINE_FILE_EXPLORER_VIEWS(expander) \ expander(NONE) \ expander(LIST) enum FileExplorerView : int { #define DEFINE_FILE_EXPLORER_VIEW_ENUMERANT(name) FILE_EXPLORER_VIEW_##name, DEFINE_FILE_EXPLORER_VIEWS(DEFINE_FILE_EXPLORER_VIEW_ENUMERANT) #undef DEFINE_FILE_EXPLORER_VIEW_ENUMERANT }; FileExplorerView toFileExplorerView(const std::string& view); const char* toString(FileExplorerView view); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/file_move_flag.h000066400000000000000000000003541516266226600223460ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { enum FileMoveFlag : unsigned int { FILE_MOVE_EXCHANGE = 1, FILE_MOVE_NO_REPLACE = 2 }; // FileMoveFlag } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/file_move_flag_forward.h000066400000000000000000000002601516266226600240660ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { enum FileMoveFlag : unsigned int; using FileMoveFlags = unsigned int; bool valid(FileMoveFlags flags); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/file_open_flag.h000066400000000000000000000003621516266226600223400ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { enum FileOpenFlag : unsigned int { FOF_APPEND = 1, FOF_TRUNCATE = 2, FOF_WRITABLE = 4 }; // FileOpenFlag } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/file_open_flag_forward.h000066400000000000000000000002161516266226600240620ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { enum FileOpenFlag : unsigned int; using FileOpenFlags = unsigned int; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_cache_flags.h000066400000000000000000000012621516266226600230040ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace fuse { struct InodeCacheFlags { // Inodes older than this value are candidates for eviction. std::chrono::seconds mCleanAgeThreshold = std::chrono::seconds(5 * 60); // How often should the cache try to reduce its size? std::chrono::seconds mCleanInterval = std::chrono::seconds(5 * 60); // Inodes can be evicted when the cache stores more than this value. std::size_t mCleanSizeThreshold = 64u; // How many inodes is the cache allowed to store? std::size_t mMaxSize = 256u; }; // InodeCacheFlags } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_cache_flags_forward.h000066400000000000000000000001371516266226600245300ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { struct InodeCacheFlags; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_id.h000066400000000000000000000025551516266226600211670ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace mega { namespace common { template<> struct SerializationTraits { static fuse::InodeID from(const Field& field); static void to(Parameter& parameter, const fuse::InodeID& value); }; // SerializationTraits } // common namespace fuse { class InodeID { std::uint64_t mValue; public: InodeID(); explicit InodeID(MountInodeID id); explicit InodeID(NodeHandle handle); explicit InodeID(std::uint64_t value); InodeID(const InodeID& other) = default; explicit operator NodeHandle() const; operator bool() const; InodeID& operator=(const InodeID& rhs) = default; bool operator==(const InodeID& rhs) const; bool operator==(const NodeHandle& rhs) const; bool operator<(const InodeID& rhs) const; bool operator!=(const InodeID& rhs) const; bool operator!=(const NodeHandle& rhs) const; bool operator!() const; static InodeID fromFileName(const std::string& name); std::uint64_t get() const; bool synthetic() const; }; // InodeID std::string toFileName(InodeID id); std::string toString(InodeID id); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_id_forward.h000066400000000000000000000004361516266226600227070ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace fuse { class InodeID; template using FromInodeIDMap = std::map; using InodeIDSet = std::set; using InodeIDVector = std::vector; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_info.h000066400000000000000000000010721516266226600215170ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace fuse { struct InodeInfo { InodeInfo() = default; explicit InodeInfo(common::NodeInfo info); InodeInfo(InodeID id, common::NodeInfo info); InodeID mID; bool mIsDirectory; m_time_t mModified; std::string mName; InodeID mParentID; accesslevel_t mPermissions; m_off_t mSize; }; // InodeInfo } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/inode_info_forward.h000066400000000000000000000002351516266226600232430ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { struct InodeInfo; using InodeInfoVector = std::vector; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/logger.h000066400000000000000000000002251516266226600206640ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { common::SubsystemLogger& logger(); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/logging.h000066400000000000000000000016371516266226600210430ustar00rootroot00000000000000#pragma once #include #include #include #include // Emit a debug message. #define FUSEDebug1(format) \ LogDebug1(::mega::fuse::logger(), (format)) #define FUSEDebugF(format, ...) \ LogDebugF(::mega::fuse::logger(), (format), __VA_ARGS__) // Emit an info message. #define FUSEInfo1(format) \ LogInfo1(::mega::fuse::logger(), (format)) #define FUSEInfoF(format, ...) \ LogInfoF(::mega::fuse::logger(), (format), __VA_ARGS__) // Emit an error message. #define FUSEError1(format) \ LogError1(::mega::fuse::logger(), (format)) #define FUSEErrorF(format, ...) \ LogErrorF(::mega::fuse::logger(), (format), __VA_ARGS__) // Emit a warning message. #define FUSEWarning1(format) \ LogWarning1(::mega::fuse::logger(), (format)) #define FUSEWarningF(format, ...) \ LogWarningF(::mega::fuse::logger(), (format), __VA_ARGS__) sdk-10.11.0/include/mega/fuse/common/mount_event.h000066400000000000000000000007271516266226600217570ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace fuse { struct MountEvent { bool operator==(const MountEvent& rhs) const; bool operator!=(const MountEvent& rhs) const; std::string mName; MountResult mResult; MountEventType mType; }; // MountEvent } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_event_forward.h000066400000000000000000000002351516266226600234750ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { struct MountEvent; using MountEventQueue = std::deque; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_event_type.h000066400000000000000000000014231516266226600230120ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { #define DEFINE_MOUNT_EVENT_TYPES(expander) \ /* A mount was being added. */ \ expander(MOUNT_ADDED) \ /* A mount's flags were being changed. */ \ expander(MOUNT_CHANGED) \ /* A mount was being disabled. */ \ expander(MOUNT_DISABLED) \ /* A mount was being enabled. */ \ expander(MOUNT_ENABLED) \ /* A mount was be being removed. */ \ expander(MOUNT_REMOVED) enum MountEventType : unsigned int { #define DEFINE_MOUNT_EVENT_TYPE_ENUMERANT(name) name, DEFINE_MOUNT_EVENT_TYPES(DEFINE_MOUNT_EVENT_TYPE_ENUMERANT) #undef DEFINE_MOUNT_EVENT_TYPE_ENUMERANT }; // MountEventType const char* toString(MountEventType type); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_event_type_forward.h000066400000000000000000000001531516266226600245350ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { enum MountEventType : unsigned int; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_flags.h000066400000000000000000000014341516266226600217260ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace fuse { struct MountFlags { bool operator==(const MountFlags& rhs) const; bool operator!=(const MountFlags& rhs) const; static MountFlags deserialize(common::Query& query); static MountFlags deserialize(common::ScopedQuery& query); void serialize(common::Query& query) const; void serialize(common::ScopedQuery& query) const; std::string mName; bool mEnableAtStartup = false; bool mPersistent = false; bool mReadOnly = false; bool mAllowSelfAccess = false; // Only used by test to allow self process to access the mount. }; // MountFlags } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_flags_forward.h000066400000000000000000000002411516266226600234450ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { struct MountFlags; using MountFlagsPtr = std::unique_ptr; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_info.h000066400000000000000000000021121516266226600215570ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace fuse { struct MountInfo { bool operator==(const MountInfo& rhs) const; bool operator!=(const MountInfo& info) const; static MountInfo deserialize(common::Query& query); static MountInfo deserialize(common::ScopedQuery& query); // Convenience. void name(const std::string& name); const std::string& name() const; void serialize(common::Query& query) const; void serialize(common::ScopedQuery& query) const; MountFlags mFlags; NodeHandle mHandle; common::NormalizedPath mPath; }; // MountInfo struct MountInfoNameLess { bool operator()(const MountInfo& lhs, const MountInfo& rhs) const; }; // MountInfoNameLess struct MountInfoPathLess { bool operator()(const MountInfo& lhs, const MountInfo& rhs) const; }; // MountInfoPathLess } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_info_forward.h000066400000000000000000000005051516266226600233070ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace fuse { struct MountInfo; using MountInfoPtr = std::unique_ptr; template using MountInfoSet = std::set; using MountInfoVector = std::vector; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_inode_id.h000066400000000000000000000013241516266226600224020ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace fuse { class MountInodeID { std::uint64_t mValue; public: explicit MountInodeID(InodeID id); explicit MountInodeID(std::uint64_t value); MountInodeID(const MountInodeID& other) = default; MountInodeID& operator=(const MountInodeID& rhs) = default; bool operator==(const MountInodeID& rhs) const; bool operator<(const MountInodeID& rhs) const; bool operator!=(const MountInodeID& rhs) const; std::uint64_t get() const; }; // MountInodeID std::string toString(MountInodeID id); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_inode_id_forward.h000066400000000000000000000002341516266226600241250ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { class MountInodeID; using MountInodeIDSet = std::set; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_result.h000066400000000000000000000036161516266226600221540ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { #define DEFINE_MOUNT_RESULTS(expander) \ expander(MOUNT_ABORTED, "The operation was aborted due to client shutdown") \ expander(MOUNT_BACKEND_UNAVAILABLE, "FUSE is supported but the backend isn't installed") \ expander(MOUNT_BUSY, "The mount's busy and cannot be disabled") \ expander(MOUNT_FAILED, "A mount has encountered an expected failure and has been disabled") \ expander(MOUNT_LOCAL_EXISTS, "Mount target already exists") \ expander(MOUNT_LOCAL_FILE, "Mount target doesn't denote a directory") \ expander(MOUNT_LOCAL_SYNCING, "Mount target is being synchronized") \ expander(MOUNT_LOCAL_TAKEN, "A mount's already associated with the target path") \ expander(MOUNT_LOCAL_UNKNOWN, "Mount target doesn't exist") \ expander(MOUNT_NAME_TAKEN, "A mount already exists with a specified name") \ expander(MOUNT_NAME_TOO_LONG, "The mount's name is too long") \ expander(MOUNT_NAME_INVALID_CHAR, "The mount's name contains invalid character(s)") \ expander(MOUNT_NO_NAME, "No name has been specified for a mount") \ expander(MOUNT_REMOTE_FILE, "Mount source doesn't describe a directory") \ expander(MOUNT_REMOTE_UNKNOWN, "Mount source doesn't exist") \ expander(MOUNT_SUCCESS, "Mount was successful") \ expander(MOUNT_UNEXPECTED, "Encountered an unexpected error while mounting") \ expander(MOUNT_UNKNOWN, "No mount is associated with the specified handle or path") \ expander(MOUNT_UNSUPPORTED, "FUSE isn't supported on this platform") enum MountResult : unsigned int { #define DEFINE_MOUNT_RESULT_ENUMERANT(name, description) name, DEFINE_MOUNT_RESULTS(DEFINE_MOUNT_RESULT_ENUMERANT) #undef DEFINE_MOUNT_RESULT_ENUMERANT }; // MountResult const char* toDescription(MountResult result); const char* toString(MountResult result); } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/mount_result_forward.h000066400000000000000000000001501516266226600236660ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { enum MountResult : unsigned int; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service.h000066400000000000000000000101001516266226600210360ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace fuse { class Service final { public: Service(common::Client& client, const ServiceFlags& flags); explicit Service(common::Client& mClient); ~Service(); // Abort (and unmount) any mounts matching predicate. static MountResult abort(AbortPredicate predicate); // Add a mount to the database. MountResult add(const MountInfo& info); // Check if a file exists in the cache. bool cached(common::NormalizedPath path) const; // Called by the client when its view of the cloud is current. void current(); // Deinitialize the service. void deinitialize(); // Describe the inode representing the file at the specified path. common::ErrorOr describe(const common::NormalizedPath& path) const; // Disable an enabled mount. void disable(MountDisabledCallback callback, const std::string& name, bool remember); // Discard node events. MountResult discard(bool discard); // Downgrade the FUSE database to the specified version. MountResult downgrade(const common::NormalizedPath& path, std::size_t target); // Enable a disabled mount. MountResult enable(const std::string& name, bool remember); // Query whether a specified mount is enabled. bool enabled(const std::string& name) const; // Execute a function on some thread. common::Task execute(std::function function); // Update a mount's flags. MountResult flags(const std::string& name, const MountFlags& flags); // Query a mount's flags. MountFlagsPtr flags(const std::string& name) const; // Describe the mount associated with path. MountInfoPtr get(const std::string& name) const; // Describe all (enabled) mounts. MountInfoVector get(bool onlyEnabled) const; // Initialize the service. MountResult initialize(); // How verbose should our logging be? void logLevel(LogLevel level); // How verbose is our logging? LogLevel logLevel() const; // Retrieve the path the mount associated with name. common::NormalizedPath path(const std::string& name) const; // Remove a disabled mount from the database. MountResult remove(const std::string& name); // Update the service's flags. void serviceFlags(const ServiceFlags& flags); // Query the service's flags. ServiceFlags serviceFlags() const; // Is FUSE supported on this platform? bool supported() const; // Check whether the specified path is "syncable." // // A path is syncable if: // - It does not contain an active mount. // - It is not contained within an active mount. bool syncable(const common::NormalizedPath& path) const; // Called by the client when nodes have changed in the cloud. void updated(common::NodeEventQueue& events); // Update the FUSE database to the specified version. MountResult upgrade(const common::NormalizedPath& path, std::size_t target); // Who we call to learn about the cloud and transfer files. common::Client& mClient; private: // Platform-specific behavior and state. ServiceContextPtr mContext; // Customizes how the service functions. ServiceFlags mFlags; // Serializes access to mFlags. mutable std::mutex mFlagsLock; }; // Service } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_callbacks.h000066400000000000000000000004531516266226600230470ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace fuse { using AbortPredicate = std::function; using MountDisabledCallback = std::function; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_context.h000066400000000000000000000070161516266226600226160ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace fuse { class ServiceContext { protected: explicit ServiceContext(Service& service); public: virtual ~ServiceContext(); // Add a mount to the database. virtual MountResult add(const MountInfo& info) = 0; // Check if a file exists in the cache. virtual bool cached(common::NormalizedPath path) const = 0; // Retrieve the client that owns this context. common::Client& client() const; // Called by the client when its view of the cloud is current. virtual void current() = 0; // Describe the inode representing the file at the specified path. virtual common::ErrorOr describe(const common::NormalizedPath& path) const = 0; // Disable an enabled mount. virtual void disable(MountDisabledCallback callback, const std::string& name, bool remember) = 0; // Discard node events. virtual MountResult discard(bool discard) = 0; // Downgrade the FUSE database to the specified version. virtual MountResult downgrade(const LocalPath& path, std::size_t target) = 0; // Enable a disabled mount. virtual MountResult enable(const std::string& name, bool remember) = 0; // Query whether a specified mount is enabled. virtual bool enabled(const std::string& name) const = 0; // Execute a function on some thread. virtual common::Task execute(std::function function) = 0; // Update a mount's flags. virtual MountResult flags(const std::string& name, const MountFlags& flags) = 0; // Query a mount's flags. virtual MountFlagsPtr flags(const std::string& name) const = 0; // Describe the mount associated with name. virtual MountInfoPtr get(const std::string& name) const = 0; // Describe all (enabled) mounts. virtual MountInfoVector get(bool onlyEnabled) const = 0; // Retrieve the path the mount associated with this name. virtual common::NormalizedPath path(const std::string& name) const = 0; // Remove a disabled mount from the database. virtual MountResult remove(const std::string& name) = 0; // Update the service's flags. virtual void serviceFlags(const ServiceFlags& flags); // Query the service's flags. ServiceFlags serviceFlags() const; // Check whether the specified path is "syncable." virtual bool syncable(const common::NormalizedPath& path) const = 0; // Called by the client when nodes have been changed in the cloud. virtual void updated(common::NodeEventQueue& events) = 0; // Update the FUSE database to the specified version. virtual MountResult upgrade(const LocalPath& path, std::size_t target) = 0; Service& mService; }; // ServiceContext } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_context_forward.h000066400000000000000000000002361516266226600243370ustar00rootroot00000000000000#include namespace mega { namespace fuse { class ServiceContext; using ServiceContextPtr = std::unique_ptr; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_flags.h000066400000000000000000000017761516266226600222350ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include namespace mega { namespace fuse { struct ServiceFlags { // How long should we wait before we flush after a write? std::chrono::seconds mFlushDelay = std::chrono::seconds(4); // Controls how the service caches inodes. InodeCacheFlags mInodeCacheFlags; // How verbose should FUSE's logs be? LogLevel mLogLevel = logInfo; // Controls what view the file explorer should have FileExplorerView mFileExplorerView = FILE_EXPLORER_VIEW_LIST; // Specifies how mounts should manage their worker threads. common::TaskExecutorFlags mMountExecutorFlags; // Specifies how the service should manage its worker threads. common::TaskExecutorFlags mServiceExecutorFlags; }; // ServiceFlags } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_flags_forward.h000066400000000000000000000002471516266226600237510ustar00rootroot00000000000000#pragma once #include namespace mega { namespace fuse { struct ServiceFlags; using ServiceFlagsPtr = std::unique_ptr; } // fuse } // mega sdk-10.11.0/include/mega/fuse/common/service_forward.h000066400000000000000000000001261516266226600225710ustar00rootroot00000000000000#pragma once namespace mega { namespace fuse { class Service; } // fuse } // mega sdk-10.11.0/include/mega/gfx.h000066400000000000000000000155411516266226600157460ustar00rootroot00000000000000/** * @file gfx.h * @brief Bitmap graphics processing * * (c) 2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef GFX_H #define GFX_H 1 #include #include "mega/types.h" #include "mega/filesystem.h" #ifdef USE_IOS #include "mega/posix/megawaiter.h" #else #include "megawaiter.h" #endif #include "mega/thread/posixthread.h" #include "mega/thread/cppthread.h" namespace mega { class MEGA_API GfxJob { public: GfxJob(); // locally encoded path of the image LocalPath localfilename; // vector with the required image type vector imagetypes; // handle related to the image NodeOrUploadHandle h; // key related to the image byte key[SymmCipher::KEYLENGTH]; // resulting images vector images; }; class MEGA_API GfxJobQueue { protected: std::deque jobs; std::mutex mutex; public: GfxJobQueue(); void push(GfxJob *job); GfxJob *pop(); }; class MEGA_API GfxDimension { public: GfxDimension() = default; GfxDimension(int w, int h) : mWidth(w), mHeight(h) {} bool operator==(const GfxDimension& other) const { return mWidth == other.mWidth && mHeight == other.mHeight; } bool operator!=(const GfxDimension& other) const { return !(*this == other); } int w() const { return mWidth; } int h() const { return mHeight; } void setW(const int width) { mWidth = width; } void setH(const int height) { mHeight = height; } private: int mWidth = 0; int mHeight = 0; }; // Interface for graphic processor provider used by GfxProc class MEGA_API IGfxProvider { public: virtual ~IGfxProvider() = default; // It generates thumbnails for the file at localfilepath. The function will return // the same number of thumbnails as the size of dimensions vector. On error it will // return a vector of empty strings. virtual std::vector generateImages(const LocalPath& localfilepath, const std::vector& dimensions) = 0; // list of supported extensions (NULL if no pre-filtering is needed) virtual const char* supportedformats() = 0; // list of supported video extensions (NULL if no pre-filtering is needed) virtual const char* supportedvideoformats() = 0; static std::unique_ptr createInternalGfxProvider(); }; // Interface for the local graphic processor provider // Implementations should be able to allocate/deallocate and manipulate bitmaps, // No thread safety is required among the operations class MEGA_API IGfxLocalProvider : public IGfxProvider { public: // read and store bitmap virtual ~IGfxLocalProvider() = default; virtual std::vector generateImages(const LocalPath& localfilepath, const std::vector& dimensions) override; enum class Hint { NONE = 0, FORMAT_PNG = 1, // Format can be in PNG }; private: virtual bool readbitmap(const LocalPath&, int) = 0; // Resize stored bitmap and store result as JPEG by default or PNG if the bitmap has // transparency and the requested width and height is for the thumbnail. // // @param rw The requested width // @param rh The requested height // @param result The pointer to string where the resized bitmap is stored // @param hint Gives the hint about behaviour. virtual bool resizebitmap(int rw, int rh, string* result, Hint hint) = 0; // free stored bitmap virtual void freebitmap() = 0; int width() { return w; } int height() { return h; } protected: // coordinate transformation static void transform(int&, int&, int&, int&, int&, int&); int w, h; }; // bitmap graphics processor class MEGA_API GfxProc { std::atomic finished{false}; WAIT_CLASS waiter; std::mutex mutex; THREAD_CLASS thread; bool threadstarted = false; SymmCipher mCheckEventsKey; GfxJobQueue requests; GfxJobQueue responses; std::unique_ptr mGfxProvider; static void *threadEntryPoint(void *param); void loop(); std::vector getJobDimensions(GfxJob *job); // Caller should give dimensions from high resolution to low resolution std::vector generateImages(const LocalPath& localfilepath, const std::vector& dimensions); std::string generateOneImage(const LocalPath& localfilepath, const GfxDimension& dimension); public: // synchronously processes the results of gendimensionsputfa() (if any) in a thread safe manner int checkevents(Waiter*); // synchronously check whether the filename looks like a supported media type bool isgfx(const LocalPath&); // synchronously check whether the filename looks like a video bool isvideo(const LocalPath&); // synchronously generate all gfx sizes and returns the count // asynchronously write to metadata server and attach to PUT transfer or existing node, // upon finalization the job is stored in responses object in a thread safe manner, and client waiter is notified // The results can be processed by calling checkevents() // handle is uploadhandle or nodehandle // - must respect JPEG EXIF rotation tag // - must save at 85% quality (120*120 pixel result: ~4 KB) int gendimensionsputfa(const LocalPath&, NodeOrUploadHandle, SymmCipher*, int missingattr); // FIXME: read dynamically from API server typedef enum { THUMBNAIL, PREVIEW } meta_t; typedef enum { AVATAR250X250 } avatar_t; // synchronously generate and save a fa to a file bool savefa(const LocalPath& source, const GfxDimension& dimension, const LocalPath& destination); // - w*0: largest square crop at the center (landscape) or at 1/6 of the height above center (portrait) // - w*h: resize to fit inside w*h bounding box static const std::vector DIMENSIONS; static const std::vector DIMENSIONS_AVATAR; MegaClient* client = nullptr; // start a thread that will do the processing void startProcessingThread(); // The provided IGfxProvider implements library specific image processing // Thread safety among IGfxProvider methods is guaranteed by GfxProc GfxProc(std::unique_ptr); virtual ~GfxProc(); }; } // namespace #endif sdk-10.11.0/include/mega/gfx/000077500000000000000000000000001516266226600155675ustar00rootroot00000000000000sdk-10.11.0/include/mega/gfx/GfxProcCG.h000066400000000000000000000025161516266226600175260ustar00rootroot00000000000000/** * @file GfxProviderCG.h * @brief Graphics layer using Cocoa Touch * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_IOS #ifndef GFX_CLASS #define GFX_CLASS GfxProviderCG #include "mega.h" // bitmap graphics processor class MEGA_API GfxProviderCG : public mega::IGfxLocalProvider { dispatch_semaphore_t semaphore; CFURLRef sourceURL; private: // mega::GfxProc implementations const char* supportedformats() override; const char* supportedvideoformats() override; bool readbitmap(const mega::LocalPath&, int) override; bool resizebitmap(int, int, std::string*, Hint hint) override; void freebitmap() override; public: GfxProviderCG(); ~GfxProviderCG(); }; #endif void ios_statsid(std::string *statsid); void ios_appbasepath(std::string *appbasepath); #endif sdk-10.11.0/include/mega/gfx/external.h000066400000000000000000000026361516266226600175710ustar00rootroot00000000000000/** * @file gfx/external.h * @brief Graphics layer interface for an external implementation * * (c) 2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef GFX_PROC_EXTERNAL #define GFX_PROC_EXTERNAL #include #include "mega.h" #include "megaapi.h" #include "mega/gfx.h" namespace mega { // bitmap graphics processor class GfxProviderExternal : public IGfxLocalProvider { MegaGfxProcessor* processor{}; bool readbitmap(const LocalPath&, int) override; bool resizebitmap(int rw, int rh, std::string* imageOut, Hint hint) override; void freebitmap() override; const char* supportedformats() override; const char* supportedvideoformats() override; public: GfxProviderExternal() = default; GfxProviderExternal(MegaGfxProcessor* gfxProcessor): processor(gfxProcessor){}; void setProcessor(MegaGfxProcessor* gfxProcessor); }; } // namespace #endif sdk-10.11.0/include/mega/gfx/freeimage.h000066400000000000000000000044661516266226600176760ustar00rootroot00000000000000/** * @file freeimage.h * @brief Graphics layer implementation using FreeImage * * (c) 2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_FREEIMAGE #ifndef GFX_CLASS #define GFX_CLASS GfxProviderFreeImage #include #include #include #include #include "mega/gfx/gfx_pdfium.h" namespace mega { // Thread-safe RAII management of the FreeImage library. class FreeImageInstance { // Serializes access to mNumReferences. static std::mutex mLock; // How many providers are referencing FreeImage? static std::size_t mNumReferences; public: FreeImageInstance(); ~FreeImageInstance(); }; // FreeImageInstance // bitmap graphics processor class MEGA_API GfxProviderFreeImage : public IGfxLocalProvider { FreeImageInstance mLibraryInstance; #ifdef HAVE_PDFIUM bool pdfiumInitialized; #endif FIBITMAP* dib; public: bool readbitmap(const LocalPath&, int) override; bool resizebitmap(int, int, string*, Hint hint) override; void freebitmap() override; const char* supportedformats() override; const char* supportedvideoformats() override; GfxProviderFreeImage(); ~GfxProviderFreeImage(); protected: string sformats; bool readbitmapFreeimage(const LocalPath&, int); #if defined(HAVE_FFMPEG) || defined(HAVE_PDFIUM) static std::mutex gfxMutex; #endif #ifdef HAVE_FFMPEG const char* supportedformatsFfmpeg(); bool isFfmpegFile(const string &ext); bool readbitmapFfmpeg(const LocalPath&, int); #endif #ifdef HAVE_PDFIUM const char* supportedformatsPDF(); bool isPdfFile(const string &ext); bool readbitmapPdf(const LocalPath&, int); #endif #ifdef USE_MEDIAINFO bool readbitmapMediaInfo(const LocalPath& imagePath); #endif }; } // namespace #endif #endif sdk-10.11.0/include/mega/gfx/gfx_pdfium.h000066400000000000000000000037321516266226600200750ustar00rootroot00000000000000/** * @file gfx_pdfium.h * @brief class to get bitmaps from PDF files using pdfium * * (c) 2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef GFX_PDFIUM_H #define GFX_PDFIUM_H 1 #include "mega/types.h" #ifdef HAVE_PDFIUM #include "mega/filesystem.h" #include "mega/logging.h" #include namespace mega { class PdfiumReader { private: static unsigned initialized; public: // Initializes the library and increases the internal counter of initializations. See destroy(). // PdfiumReader member method calling init() is responsible for locking pdfMutex static void init(); #ifdef _WIN32 // BGRA format, 4 bytes per pixel (32bits), byte order: blue, green, red, alpha. // init() is called internally if library is not initialized. // workingDirFolder : Path to create a temporary file. static unique_ptr readBitmapFromPdf(int &w, int &h, int &orientation, const LocalPath &path, const LocalPath &workingDirFolder); #else // Returns a bitmap in BGRA format, 4 bytes per pixel (32bits), byte order: blue, green, red, alpha. // init() is called internally if library is not initialized. static unique_ptr readBitmapFromPdf(int &w, int &h, int &orientation, const LocalPath &path); #endif // It decreases the initializations internal counter and destroys the library once it reaches zero. static void destroy(); protected: static std::mutex pdfMutex; }; } // namespace #endif #endif sdk-10.11.0/include/mega/gfx/isolatedprocess.h000066400000000000000000000114571516266226600211530ustar00rootroot00000000000000#pragma once #include "mega.h" #include "mega/gfx.h" #include #include #include #include #include #include namespace mega { class Process; // a simple sleeper can be only cancelled for once and forever class CancellableSleeper { public: // true if sleeping is cancelled, otherwise, false bool sleep(const std::chrono::milliseconds& period); void cancel(); private: std::condition_variable mCv; std::mutex mMutex; bool mCancelled = false; }; class AutoStartLauncher: public std::enable_shared_from_this { public: AutoStartLauncher(const std::vector& argv, std::function shutdowner); bool start(); void stop(); private: bool startUntilSuccess(Process& process); bool exitLaunchLoopThread(); std::vector mArgv; std::thread mThread; std::atomic mShuttingDown{false}; std::atomic mThreadIsRunning{false}; CancellableSleeper mSleeper; // function to shutdown the started process if any std::function mShutdowner; const static std::chrono::milliseconds MAX_BACKOFF; const static std::chrono::milliseconds START_BACKOFF; }; // This class creates a thread sending hello command to // gfxworker in mPeriod interval. The purpose is to keep // the gfxworker running. class HelloBeater { public: HelloBeater(const std::chrono::seconds& period, const std::string& endpointName) : mPeriod(period) , mEndpointName(endpointName) { mThread = std::thread(&HelloBeater::beat, this); } ~HelloBeater(); void shutdownOnce(); private: void beat(); std::thread mThread; std::atomic mShuttingDown{false}; CancellableSleeper mSleeper; std::chrono::seconds mPeriod; std::string mEndpointName; }; // This lauches the process and keep it running using beater class GfxIsolatedProcess { public: class Params { public: // // keepAliveInSeconds default 60 seconds. It also ensure the minimum MIN_ALIVE_SECONDS // Params(const std::string& endpointName, const std::string& executable, std::chrono::seconds keepAliveInSeconds = std::chrono::seconds(60), const std::vector& rawArguments = {}); // Convert to args used to launch isolated process std::vector toArgs() const; bool isValid() const { return !endpointName.empty() && !executable.empty(); } private: friend class GfxIsolatedProcess; static constexpr std::chrono::seconds MIN_ALIVE_SECONDS{9}; // The pipe name in Windows or the unix domain socket name in UNIX std::string endpointName; // The executable file path std::string executable; // The number of seconds the server stays alive without receiving any requests. std::chrono::seconds keepAliveInSeconds; // The raw arguments that will be passed through as-is, without any modifications. std::vector rawArguments; }; GfxIsolatedProcess(const Params& params); ~GfxIsolatedProcess(); const std::string& endpointName() const { return mEndpointName; } private: std::string mEndpointName; std::shared_ptr mLauncher; HelloBeater mBeater; }; class GfxProviderIsolatedProcess : public IGfxProvider { public: GfxProviderIsolatedProcess(std::unique_ptr process); std::vector generateImages(const LocalPath& localfilepath, const std::vector& dimensions) override; const char* supportedformats() override; const char* supportedvideoformats() override; static std::unique_ptr create(const GfxIsolatedProcess::Params& params); private: // thread safe formats accessor class Formats { public: // whether has valid formats bool isValid() const; // return formats if it is valid and not empty, otherwise nullptr const char* formats() const; // return videoformats if it is valid and not empty, otherwise nullptr const char* videoformats() const; // set the formats and videoformats once void setOnce(const std::string& formats, const std::string& videoformats); private: std::string mFormats; std::string mVideoformats; std::atomic mIsValid{false}; std::mutex mMutex; }; const char* getformats(const char* (Formats::*formatsFunc)() const); Formats mFormats; std::unique_ptr mProcess; std::string mEndpointName; }; } sdk-10.11.0/include/mega/gfx/worker/000077500000000000000000000000001516266226600171005ustar00rootroot00000000000000sdk-10.11.0/include/mega/gfx/worker/client.h000066400000000000000000000042561516266226600205360ustar00rootroot00000000000000/** * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/gfx.h" #include "mega/gfx/worker/comms.h" #include "mega/gfx/worker/comms_client.h" #include #include #include #include namespace mega { namespace gfx { /** * @brief The GfxClient class. */ class GfxClient { public: /** * @brief GfxClient cotr * @param comms. The implementation of GfxCommunications to be used. GfxClient takes the * ownership of the provided object. * */ GfxClient(std::unique_ptr comms); bool runHello(const std::string& text); bool runShutDown(); bool runGfxTask(const std::string& localpath, const std::vector& dimensions, std::vector& images); bool runSupportFormats(std::string& formats, std::string& videoformats); static GfxClient create(const std::string& endpointName); private: bool isConnectRetryError(CommError error) const; // it retries on some errors std::unique_ptr connectWithRetry(std::chrono::milliseconds backoff, unsigned int maxRetries); std::unique_ptr connect(); template std::unique_ptr sendAndReceive(IEndpoint* endpoint, RequestT command, std::chrono::milliseconds sendTimeout = std::chrono::milliseconds{5000}, std::chrono::milliseconds receiveTimeout = std::chrono::milliseconds{5000}); std::unique_ptr mComms; }; } //namespace gfx } //namespace mega sdk-10.11.0/include/mega/gfx/worker/command_serializer.h000066400000000000000000000025041516266226600231210ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/commands.h" #include "mega/gfx/worker/comms.h" #include #include #include namespace mega { namespace gfx { class IReader; class IWriter; class ProtocolWriter { public: ProtocolWriter(IWriter* writer) : mWriter(writer) {} bool writeCommand(ICommand* command, std::chrono::milliseconds timeout) const; private: IWriter* mWriter; }; class ProtocolReader { public: ProtocolReader(IReader* reader) : mReader(reader) {} std::unique_ptr readCommand(std::chrono::milliseconds timeout) const; private: IReader* mReader; }; class CommandSerializer { public: static std::unique_ptr serialize(ICommand* command); static std::unique_ptr unserialize(IReader& reader, std::chrono::milliseconds timeout); private: static bool unserializeUInt32(IReader& reader, uint32_t& data, std::chrono::milliseconds timeout); static bool unserializeString(IReader& reader, std::string& data, std::chrono::milliseconds timeout); static std::unique_ptr unserializeCommand(CommandType type, const std::string& data); }; } } sdk-10.11.0/include/mega/gfx/worker/commands.h000066400000000000000000000105011516266226600210470ustar00rootroot00000000000000/** * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/gfx/worker/tasks.h" #include "mega/log_level.h" #include namespace mega { namespace gfx { enum class CommandType { BEGIN = 1, NEW_GFX = 1, NEW_GFX_RESPONSE = 2, ABORT = 3, SHUTDOWN = 4, SHUTDOWN_RESPONSE = 5, HELLO = 6, HELLO_RESPONSE = 7, SUPPORT_FORMATS = 8, SUPPORT_FORMATS_RESPONSE = 9, END = 10 // 1 more than the last valid one }; class ICommand { public: virtual ~ICommand() = default; virtual CommandType type() const = 0; virtual std::string typeStr() const = 0; virtual std::string serialize() const = 0; virtual bool unserialize(const std::string& data) = 0; static std::unique_ptr factory(CommandType type); virtual LogLevel logLevel() const { return logInfo; } }; struct CommandShutDown : public ICommand { CommandType type() const override { return CommandType::SHUTDOWN; } std::string typeStr() const override { return "SHUTDOWN"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; struct CommandShutDownResponse : public ICommand { CommandType type() const override { return CommandType::SHUTDOWN_RESPONSE; } std::string typeStr() const override { return "SHUTDOWN_RESPONSE"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; struct CommandNewGfx : public ICommand { GfxTask Task; CommandType type() const override { return CommandType::NEW_GFX; } std::string typeStr() const override { return "NEW_GFX"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; struct CommandNewGfxResponse : public ICommand { uint32_t ErrorCode; std::string ErrorText; std::vector Images; CommandType type() const override { return CommandType::NEW_GFX_RESPONSE; } std::string typeStr() const override { return "NEW_GFX_RESPONSE"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; struct CommandHello : public ICommand { std::string Text; CommandType type() const override { return CommandType::HELLO; } std::string typeStr() const override { return "HELLO"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; LogLevel logLevel() const override { return logVerbose; } }; struct CommandHelloResponse : public ICommand { std::string Text; CommandType type() const override { return CommandType::HELLO_RESPONSE; } std::string typeStr() const override { return "HELLO_RESPONSE"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; struct CommandSupportFormats : public ICommand { CommandType type() const override { return CommandType::SUPPORT_FORMATS; } std::string typeStr() const override { return "SUPPORT_FORMATS"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; LogLevel logLevel() const override { return logVerbose; } }; struct CommandSupportFormatsResponse : public ICommand { std::string formats; std::string videoformats; CommandType type() const override { return CommandType::SUPPORT_FORMATS_RESPONSE; } std::string typeStr() const override { return "SUPPORT_FORMATS_RESPONSE"; }; std::string serialize() const override; bool unserialize(const std::string& data) override; }; } //namespace gfx } //namespace mega sdk-10.11.0/include/mega/gfx/worker/comms.h000066400000000000000000000043211516266226600203670ustar00rootroot00000000000000/** * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/types.h" #include namespace mega { namespace gfx { class IReader { public: virtual ~IReader() = default; /** * @brief Attempts to read exactly 'n' bytes into the provided buffer unless an error, EOF, or * timeout occurs. * @param out The output buffer, which must have space for at least 'n' bytes. * @param n The number of bytes to read. * @param timeout The timeout duration in milliseconds. * A positive value waits up to the specified time, * 0 returns immediately without waiting, and a negative value waits * indefinitely. * @return true if 'n' bytes are successfully read, false if an error, EOF, or timeout occurs. */ bool read(void* out, size_t n, std::chrono::milliseconds timeout) { return doRead(out, n, timeout); }; private: virtual bool doRead(void* out, size_t n, std::chrono::milliseconds timeout) = 0; }; class IWriter { public: virtual ~IWriter() = default; bool write(const void* in, size_t n, std::chrono::milliseconds timeout) { return doWrite(in, n, timeout); }; private: virtual bool doWrite(const void* in, size_t n, std::chrono::milliseconds timeout) = 0; }; // // It represents a communication endpoint // class IEndpoint : public IReader, public IWriter { public: virtual ~IEndpoint() = default; }; enum class CommError: uint8_t { OK = 0, ERR = 1, // an generic error NOT_EXIST = 2, TIMEOUT = 3, CLOSED = 4, // The other side has closed }; } // namespace gfx } // namespace mega sdk-10.11.0/include/mega/gfx/worker/comms_client.h000066400000000000000000000003311516266226600217220ustar00rootroot00000000000000/** * Covenience, include this file instead of platform headers * * */ #pragma once #if defined(WIN32) #include "mega/win32/gfx/worker/comms_client.h" #else #include "mega/posix/gfx/worker/comms_client.h" #endif sdk-10.11.0/include/mega/gfx/worker/comms_client_common.h000066400000000000000000000004311516266226600232730ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/comms.h" #include namespace mega { namespace gfx { class IGfxCommunicationsClient { public: virtual ~IGfxCommunicationsClient() = default; virtual std::pair> connect() = 0; }; } } sdk-10.11.0/include/mega/gfx/worker/tasks.h000066400000000000000000000024701516266226600204010ustar00rootroot00000000000000/** * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/gfx.h" #include #include #include #include namespace mega { namespace gfx { struct GfxTask final { std::string Path; std::vector Dimensions; }; /** * @brief Defines the possible result status of GfxProcessor::process */ enum class GfxTaskProcessStatus { SUCCESS = 0, ERR = 1, }; struct GfxTaskResult final { GfxTaskResult(std::vector&& outputImages, const GfxTaskProcessStatus processStatus) : ProcessStatus(processStatus) , OutputImages(std::move(outputImages)) {} GfxTaskProcessStatus ProcessStatus; std::vector OutputImages; }; } //namespace gfx } //namespace mega sdk-10.11.0/include/mega/hashcash.h000066400000000000000000000071561516266226600167470ustar00rootroot00000000000000/** * @file mega/hashcash.h * @brief Mega SDK PoW for login */ #ifndef MEGA_HASHCASH_H #define MEGA_HASHCASH_H 1 #include "mega/canceller.h" #include #include #include #include #include namespace mega { static constexpr unsigned MAX_WORKERS_FOR_GENCASH = #if defined(__ANDROID__) || defined(USE_IOS) 2u; #else 8u; #endif struct RetryGencash { static constexpr auto kMaxRetries{2u}; unsigned mForceRetryCount{0}; uint8_t mEasiness{0}; std::chrono::milliseconds mBudget{0}; std::chrono::milliseconds mGencashTime{0}; }; /** * @brief Retrieve current RetryGencash data * * Attempt 1 (gencash execution) * Timeout -> Retry 1 (RetryGencash::mForceRetryCount == 1) * Attempt 2 (Retry 1) * Timeout -> Retry 2 (RetryGencash::mForceRetryCount == 2) * Attempt 3 (Retry 2) * Timeout -> No more retries (RetryGencash::mForceRetryCount == 0). * * return A valid RetryGencash object if: * -> There is a forced retry (RetryGencash::mForceRetryCount > 0) * -> This is the last retry and, despite not forcing a new retry anymore, * the time lapsed has exceeded the timeout that would have forced a new retry * if we didn't reach the maxRetries limit. RetryGencash::mForceRetryCount would be 0 * for this case. Nullopt otherwise. */ std::optional retryGencashData(); /** * @brief Multi threaded HashCash solver. * * Spawns workers threads (capped by maxWorkers and hardware_concurrency), * each running gencashWorker with a different stride. * * The first successful prefix is returned, all other workers are signalled to exit early. * * @param token Base64 token issued by the server. * @param easiness Target difficulty. * @param ttl Timeout to give up computing this hash - return empty string. * @param reqSnapshot Starting cancel_epoch_t to compare the global epoch to cancel the request * and stop computing immediately. If the canceller is triggered, this method will return an empty * string. * @param maxWorkers User cap: 8 for desktop, 2 for mobile, etc. * * @return Base64 encoded 4-byte prefix satisfying the difficulty target or empty string if the ttl * has been reached or the global cancel_epoch_t is greater than the reqSnapshot. */ std::string gencash(const std::string& token, const uint8_t easiness, const std::chrono::milliseconds ttl, const cancel_epoch_t reqSnapshot, const unsigned maxWorkers); /** * @brief Overload of gencash() with internal ttl (based on server's ttl). */ std::string gencash(const std::string& token, const uint8_t easiness, const cancel_epoch_t reqSnapshot, const unsigned maxWorkers); /** * @brief Overload of gencash() with internal ttl (based on server's ttl) and internal maxWorkers * based on the platform. */ std::string gencash(const std::string& token, const uint8_t easiness, const cancel_epoch_t reqSnapshot); /** * @brief Offline verifier for the hashcash calculated prefix. * * Rebuilds the 12MB message from token and prefixB64 (the calculated prefix), * hashes it once, and checks the leading 32bits against the threshold * for easiness. * * @return true if (prefix, token, easiness) constitute a valid proof, false otherwise. */ bool validateHashcash(const std::string& token, const uint8_t easiness, const std::string& prefixB64); } // namespace mega #endif // MEGA_HASHCASH_H sdk-10.11.0/include/mega/heartbeats.h000066400000000000000000000067411516266226600173060ustar00rootroot00000000000000/** * @file heartbeats.h * @brief Classes for heartbeating Backup configuration and status * * (c) 2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "types.h" #include #include #include "mega/command.h" namespace mega { #ifdef ENABLE_SYNC struct UnifiedSync; struct Syncs; /** * @brief The HeartBeatBackupInfo class * This class holds the information that will be heartbeated */ class HeartBeatBackupInfo { bool mModified = false; public: HeartBeatBackupInfo(); HeartBeatBackupInfo(HeartBeatBackupInfo&&) = delete; HeartBeatBackupInfo& operator=(HeartBeatBackupInfo&&) = delete; virtual ~HeartBeatBackupInfo() = default; virtual m_time_t lastAction() const; virtual handle lastItemUpdated() const; virtual m_time_t lastBeat() const; virtual void setLastBeat(const m_time_t &lastBeat); virtual void setLastAction(const m_time_t &lastAction); virtual void setLastSyncedItem(const handle &lastItemUpdated); std::atomic mSending{false}; protected: handle mLastItemUpdated = UNDEF; // handle of node most recently updated m_time_t mLastAction = -1; //timestamps of the last action m_time_t mLastBeat = -1; //timestamps of the last beat void updateLastActionTime(); friend class BackupMonitor; }; class HeartBeatSyncInfo : public HeartBeatBackupInfo { public: void updateSPHBStatus(UnifiedSync& us); using SPHBStatus = CommandBackupPutHeartBeat::SPHBStatus; SPHBStatus sphbStatus() { return mSPHBStatus; } SyncTransferCounts mSnapshotTransferCounts; SyncTransferCounts mResolvedTransferCounts; private: SPHBStatus mSPHBStatus = CommandBackupPutHeartBeat::STATE_NOT_INITIALIZED; }; class BackupInfoSync : public CommandBackupPut::BackupInfo { public: BackupInfoSync(const SyncConfig& config, const string& device, handle drive, CommandBackupPut::SPState calculatedState); BackupInfoSync(const UnifiedSync& us, bool pauseDown, bool pauseUp); static BackupType getSyncType(const SyncConfig& config); static CommandBackupPut::SPState getSyncState (const UnifiedSync &, bool pauseDown, bool pauseUp); static CommandBackupPut::SPState getSyncState(SyncError error, SyncRunState, bool pauseDown, bool pauseUp); static CommandBackupPut::SPState getSyncState(const SyncConfig& config, bool pauseDown, bool pauseUp); static handle getDriveId(const UnifiedSync&); bool operator==(const BackupInfoSync& o) const; bool operator!=(const BackupInfoSync& o) const; private: static CommandBackupPut::SPState calculatePauseActiveState(bool pauseDown, bool pauseUp); }; class BackupMonitor { public: explicit BackupMonitor(Syncs&); void beat(); // produce heartbeats! void updateOrRegisterSync(UnifiedSync&); private: static constexpr int MAX_HEARBEAT_SECS_DELAY = 60*30; // max time to wait before a heartbeat for unchanged backup Syncs& syncs; void beatBackupInfo(UnifiedSync& us); }; #endif } sdk-10.11.0/include/mega/http.h000066400000000000000000000572021516266226600161410ustar00rootroot00000000000000/** * @file mega/http.h * @brief Generic host HTTP I/O interfaces * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_HTTP_H #define MEGA_HTTP_H 1 #include "backofftimer.h" #include "canceller.h" #include "types.h" #include "utils.h" #include "waiter.h" #include #ifndef _WIN32 #include #include #include #else #pragma warning(push) #pragma warning( disable : 4459 ) // um\ws2tcpip.h(738,14): warning C4459: declaration of 'Error' hides global declaration // winrt\AsyncInfo.h(77,52): message : see declaration of 'Error' #include #pragma warning(pop) #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #include #endif namespace mega { #ifdef _WIN32 const char* mega_inet_ntop(int af, const void* src, char* dst, int cnt); #else #define mega_inet_ntop inet_ntop #endif // SSL public key pinning - active key #define APISSLMODULUS1 "\xb6\x61\xe7\xcf\x69\x2a\x84\x35\x05\xc3\x14\xbc\x95\xcf\x94\x33\x1c\x82\x67\x3b\x04\x35\x11" \ "\xa0\x8d\xc8\x9d\xbb\x9c\x79\x65\xe7\x10\xd9\x91\x80\xc7\x81\x0c\xf4\x95\xbb\xb3\x26\x9b\x97\xd2" \ "\x14\x0f\x0b\xca\xf0\x5e\x45\x7b\x32\xc6\xa4\x7d\x7a\xfe\x11\xe7\xb2\x5e\x21\x55\x23\x22\x1a\xca" \ "\x1a\xf9\x21\xe1\x4e\xb7\x82\x0d\xeb\x9d\xcb\x4e\x3d\x0b\xe4\xed\x4a\xef\xe4\xab\x0c\xec\x09\x69" \ "\xfe\xae\x43\xec\x19\x04\x3d\x5b\x68\x0f\x67\xe8\x80\xff\x9b\x03\xea\x50\xab\x16\xd7\xe0\x4c\xb4" \ "\x42\xef\x31\xe2\x32\x9f\xe4\xd5\xf4\xd8\xfd\x82\xcc\xc4\x50\xd9\x4d\xb5\xfb\x6d\xa2\xf3\xaf\x37" \ "\x67\x7f\x96\x4c\x54\x3d\x9b\x1c\xbd\x5c\x31\x6d\x10\x43\xd8\x22\x21\x01\x87\x63\x22\x89\x17\xca" \ "\x92\xcb\xcb\xec\xe8\xc7\xff\x58\xe8\x18\xc4\xce\x1b\xe5\x4f\x20\xa8\xcf\xd3\xb9\x9d\x5a\x7a\x69" \ "\xf2\xca\x48\xf8\x87\x95\x3a\x32\x70\xb3\x1a\xf0\xc4\x45\x70\x43\x58\x18\xda\x85\x29\x1d\xaf\x83" \ "\xc2\x35\xa9\xc1\x73\x76\xb4\x47\x22\x2b\x42\x9f\x93\x72\x3f\x9d\x3d\xa1\x47\x3d\xb0\x46\x37\x1b" \ "\xfd\x0e\x28\x68\xa0\xf6\x1d\x62\xb2\xdc\x69\xc7\x9b\x09\x1e\xb5\x47" // SSL public key pinning - backup key #define APISSLMODULUS2 "\xaf\xe6\x13\x63\xe6\x24\x7c\x6b\x3c\xfe\x61\x91\x58\x20\xf5\xb9\x91\xdb\x86\x4c\x8e\x0c\x2f" \ "\xdb\x78\x31\xac\xba\x48\x03\xcf\x07\x95\xc6\x09\xda\x5b\xf9\x7b\x60\xa2\x87\xfe\xa9\xa5\xa2\x8a" \ "\x8a\x2c\xb1\x48\xa7\x8e\x66\x24\x0a\xc7\x38\xcf\xba\xdb\x77\x1d\x0b\xe9\xbe\x00\x54\x7f\xe9\x0e" \ "\x56\xbd\xcf\x7c\x10\xf5\xc2\x5f\xc2\x2e\x8f\xbf\x36\xfe\xe0\x5e\x18\xef\xcb\x2f\x88\x95\x4d\xe2" \ "\x72\xed\xfe\x60\x58\x7c\xdf\x75\xb1\x88\x27\xf4\x1c\x9f\xea\x83\x1f\xc6\x34\xa7\x54\x3d\x59\x9d" \ "\x43\xd9\x75\xf4\x17\xcf\x99\x63\x02\xfd\xad\x0f\xc2\x8d\xe7\x0a\xcc\x0c\xda\xac\x99\xc6\xd3\xf5" \ "\xef\xa2\x1f\xd6\xdc\xdb\x98\x63\x2a\xac\x00\x94\x5f\x42\x33\x46\xb6\x10\x86\xcd\x03\x92\xb0\x23" \ "\x2f\x86\x30\x53\xf8\x04\x92\x89\x2e\x0a\x25\x3f\xfa\x4c\x69\xd6\xd7\xaf\x62\xee\xd6\xec\xf8\x96" \ "\xaf\x53\x1a\x13\x33\x38\x7e\xe1\xa9\xe0\x3f\x43\x2f\x17\x05\x90\xe1\x42\xaa\x47\x6d\xef\xdf\x75" \ "\x2e\x3c\xfd\xcf\xbb\x0b\x31\x21\xab\x81\x57\x95\xd3\x04\xf9\x52\x69\x2e\x30\xe5\x45\x2d\x23\x5f" \ "\x6f\x26\x76\x69\x7a\x12\x99\x78\xe0\x08\x87\x33\xd6\x94\xf0\x6c\x6d" // active and backup keys use the same exponent #define APISSLEXPONENTSIZE "\x03" #define APISSLEXPONENT "\x01\x00\x01" // chatd/presenced SSL public key pinning - chat key (used by MEGAchat) #define CHATSSLMODULUS "\xbe\x75\xfe\xe1\xff\xac\x69\x2b\xc8\x0c\x12\xe9\x9f\x78\x60\xc2\xa0\xe1\xf1\xf2\xec\x48\xc5" \ "\x8b\xb0\x94\xe9\x68\x02\xdd\xde\xe5\xc3\x15\x53\x55\x44\xc6\x5f\x71\xb3\xe5\x8f\xa3\x8a\x86\x75" \ "\x13\x79\x10\x25\xef\x8c\xc6\x4d\xf0\xbf\x8b\x4a\xfb\x49\x58\xae\xe7\x71\x21\xf4\x29\x58\x28\xb4" \ "\xbf\x41\xec\xa7\x81\xc8\xbe\x64\xd4\xf7\x44\xa2\x0c\x31\x6b\x7c\xfc\x33\x0a\x60\xa8\x36\x5a\xe8" \ "\xfd\xdb\x11\x44\xf8\x69\x12\x4f\x4c\x4a\x48\x2b\x4e\x0a\x44\x1b\xb7\x86\x08\xd9\x5d\x61\x2a\x8b" \ "\x51\x37\x51\x6d\x29\x8c\x4f\xfe\xc2\x84\x2d\x52\x94\xe0\xf4\x60\x5b\xdd\x8d\xda\x67\xe5\xfb\x37" \ "\x77\x51\xc3\x52\xb1\x24\x7f\x46\x3f\x3c\x62\xb5\x1e\xfa\x76\x0f\x39\xaf\x23\xd8\x93\xa9\x4a\x53" \ "\xdf\x38\x59\xde\x70\xbb\x1c\x66\xc8\xbc\xd4\xbc\x1e\xb9\x20\xa6\x62\x9a\x75\xd6\xc9\x94\x46\xcd" \ "\x09\x8f\xa3\x9e\xf9\x1f\xe8\x11\x73\x98\x66\x84\x04\x8f\x7c\xee\xc6\x28\xb3\x21\xa4\x9b\x42\xa3" \ "\xb1\x8f\x0f\xb9\x1a\x4d\xd6\xc0\x26\xa5\x42\x83\x6f\x64\xdf\x8e\x6a\x4e\xf9\x24\x50\x1f\x43\x74" \ "\x42\x43\x0d\x31\x69\xf5\xca\x47\xf8\x82\x8f\xf2\x8b\xc6\xa2\x57\x15" // chatd/presenced public key pinning - chat backup key (used by MEGAchat) #define CHATSSLMODULUS2 "\xb4\xf6\x5b\x5e\x17\x79\xd4\x65\xd0\x53\xe7\x2a\x80\x92\x0a\x67\xc3\xb7\xef\xd1\x96\x5c\x3e" \ "\x8f\x7c\xb2\x0f\xe7\xd1\x4a\x11\xb6\xcc\x35\x38\x73\xcd\x29\xf0\xc0\x83\x00\xad\xfb\xd2\x30\xf3" \ "\x5a\xdf\x6f\xd7\xc6\x41\x0e\xd2\xcd\xb4\xad\xfc\x62\x8a\xd2\x8f\x5a\x1d\x05\xb0\x58\x89\x2c\x78" \ "\xdf\xaf\xeb\xdc\xff\x97\x07\x7e\x79\x14\xe3\xea\x05\x2d\x23\x21\x53\xb1\xfd\xb2\xdf\x26\x7d\xa0" \ "\xce\xd7\x7a\x30\x18\x20\x9a\xa7\x13\x74\x13\x40\x3d\x3e\x30\x1c\x34\xf8\x47\xda\x77\xfc\xe2\x68" \ "\x63\x7f\xfa\xb5\x5e\x8c\x6f\x65\x1f\x78\x4e\x9b\x4f\x13\x4f\x35\x5d\x26\x9e\x02\xcd\x9b\x8d\xca" \ "\x56\x6f\x1b\x0a\x73\x2a\x03\x2b\x70\x16\x43\x11\xc3\xfd\xab\xde\xb9\xc5\x80\x4c\x1b\x1b\x94\x25" \ "\x7f\xb5\x0f\x5d\x7e\x89\x01\x73\x77\x93\x9c\x65\x98\xf5\x54\x22\x61\x6b\x9c\x1d\x21\xdc\xe5\x52" \ "\xaa\xcc\xd7\x57\x30\x87\xd4\x45\x33\x3f\xfd\xd9\x0b\xf6\x4e\x15\xe2\x3b\x0a\x0d\x84\xa0\x0a\x5b" \ "\x43\x46\xc1\x3b\x8a\xea\x07\xe9\xc6\xc8\x44\xa3\xa0\x2d\x30\xc7\xaf\xc3\xfb\x76\x28\x59\xad\xf3" \ "\xe4\x7b\x36\x9c\x86\xb9\x32\x5b\x21\x0d\xfc\x47\x01\xee\x4a\xd9\x59" #define CHATSSLEXPONENTSIZE "\x03" #define CHATSSLEXPONENT "\x01\x00\x01" // SFU SSL public key pinning - active key (used by MEGAchat) #define SFUSSLMODULUS "\xd5\x02\x43\xfa\x00\x9e\xc2\xe4\xbe\x74\xcc\x09\xe7\xa2\xac\x43\xfd\x8a\xa3\x21\xda\x47\x3d" \ "\x27\x0e\x8d\x2d\x0a\xfe\x07\xec\x46\xba\xb5\x07\x47\x54\x45\x05\x28\x46\x27\x43\xf1\x82\x7c\xd9" \ "\x14\x6c\x15\xce\x6e\x23\x46\x60\x4c\x06\x6d\x11\x5e\x86\x05\xd0\x33\x6b\x61\x5d\x6f\xcf\x86\x35" \ "\xbf\x1a\xdd\x85\xf1\xa2\xa3\x19\xe5\xf3\xe8\x24\x8c\x68\x10\x34\x7b\xf0\x52\x21\x56\x8a\x47\x23" \ "\x80\x56\xf2\x6f\xb1\x29\x27\x25\x9e\xe7\x45\x98\x5c\xe2\x31\x2a\x52\x71\x80\xab\xe9\x46\xe7\x71" \ "\x90\x39\x56\x9d\x0f\xf3\x99\x20\x2f\x3d\xac\xd0\xfc\x09\xa2\x69\x1b\xaa\x56\x4c\x4a\xca\xbc\xaf" \ "\x78\xde\xf0\x8e\x5b\x0e\x7b\xd2\xb8\x03\xe0\x1a\x65\xc1\xd8\x4b\x80\x5b\xee\x40\xea\x82\x06\x3b" \ "\xab\xca\x88\xb1\x8e\x57\x6a\xed\x92\x9c\x46\xd9\xbe\xed\xcb\x59\x08\xa1\x7f\x0b\x28\xb3\x61\xa6" \ "\x1d\x20\xe2\x0d\xd8\xcb\xc0\xe7\x94\xae\x8c\xa4\x1f\xab\x0a\x71\xd9\x41\xaa\x9f\x48\x6d\x7b\xd2" \ "\x2f\x5d\x3f\x1d\xd1\x14\x7d\x6c\xb0\xac\xa5\xf5\xba\xb8\xd5\xf2\xd7\x81\x0a\xf5\x4c\x54\x0b\xe9" \ "\x30\x3c\x4c\x77\x41\x30\x9b\xb6\xf0\x3b\xbf\x8c\xcf\xd3\x7f\x3b\xdb" // SFU SSL public key pinning - backup key (used by MEGAchat) #define SFUSSLMODULUS2 "\xe2\xc7\x18\x9e\x64\xd2\xe3\x04\x73\xcb\xd8\xa4\xcf\x46\xc2\xa9\x91\x0b\x5f\x83\x5f\x46\x40" \ "\x19\xe3\xd9\xf6\x6f\x28\x88\xa9\x4c\x35\x5e\x83\x20\xb5\x2e\xd3\xb6\x55\x3e\xfc\x7c\x42\x47\x4f" \ "\x20\x6b\x4c\x32\xc9\x25\x44\xf3\x62\x6c\x4d\xdf\x29\xd8\xcc\x99\x90\xfa\xbf\x76\x3b\xf8\x4e\xcb" \ "\x00\x3b\x01\xdd\x4f\x0d\xf6\x4f\xd8\xbd\x2a\x8c\xe0\xf9\x50\x69\x78\xe5\xc1\x4a\x53\x42\xe9\x67" \ "\xe6\xab\x16\xd7\x27\x4b\x95\x25\xec\xd0\x34\xcb\x52\x36\xa3\x74\xbb\xef\xbd\x9a\x95\x61\x27\x57" \ "\x66\xe5\xd0\x4e\x2a\x7a\x50\x68\x0b\x7e\x2a\x09\xee\xeb\x7f\xb3\x35\x75\x21\x36\x37\x2f\x36\xb4" \ "\x71\x11\x0f\x56\x57\xef\xb5\xeb\xb4\x65\xf2\x30\x2f\x33\x0b\x13\x9b\x79\x77\xb2\x69\x5b\x34\x9b" \ "\x59\x87\x14\xea\x92\xc8\x43\x99\x93\x5e\x3d\x6f\x8b\xba\x5f\xda\xd8\x39\xf0\x66\xba\x48\x29\xa2" \ "\x1e\xf4\x4e\xcb\xd6\x65\x6a\x34\x9c\xfa\x73\x64\x99\x43\xc9\x46\x73\x4c\x62\x5b\x78\x50\xbd\x41" \ "\xb1\xab\x0d\x62\xbf\x85\x70\x61\x09\x29\xf9\x67\x95\x13\xb9\xdc\xc3\x37\xde\xf0\x5f\x5e\x60\x17" \ "\x25\x30\x66\x28\x36\x60\x1e\xc0\x0f\x2d\x36\xd8\x6e\x90\xe2\xa9\xa1" // active and backup keys use the same exponent #define SFUSSLEXPONENTSIZE "\x03" #define SFUSSLEXPONENT "\x01\x00\x01" // SFU-stats SSL public key pinning - active key #define SFUSTATSSSLMODULUS "\xaf\x59\x51\xf0\x25\x45\x96\x7f\x49\x1e\x39\xdd\xc6\xd5\xeb\x0e\xc7\x8f\xa5\x38\x33\xf3\x54" \ "\x2e\x64\xf2\x6a\x67\xba\x11\xd7\xef\x64\x76\x4e\x7b\x5c\x97\xcb\x88\xf3\x40\x64\xb2\x37\x2e\xbe" \ "\x63\x98\x9c\xc0\x6d\xf8\x69\xfd\xb8\x63\xb1\x5d\x34\xcd\xf8\x1d\xf9\xf1\xa4\x56\x62\xfd\x20\x0d" \ "\x04\xbf\x30\xac\x71\x90\x89\x59\x4d\x51\x9f\x93\xae\xcd\xf4\x50\xd1\xfd\x69\x3f\xd7\xb7\x00\x98" \ "\x59\x98\x0a\xbe\xbc\x78\x6d\xee\x14\x32\x46\x6f\x58\x6f\xe4\x57\xe5\xf5\xe6\x2b\xb6\x50\xaf\x90" \ "\x19\x04\x29\x97\xc6\xba\x4c\x33\x87\x29\x23\xcc\xa2\xa5\x34\x01\x4f\xe7\xba\xbf\x81\x94\x7d\x39" \ "\xe0\x67\xb7\xbe\x6e\x10\x4e\x91\x64\x7b\x8a\x20\x10\xb9\x07\x77\x0b\xe5\xfb\x0d\x49\x51\xbb\x36" \ "\xed\x65\x06\x36\xe3\x64\xf3\x5f\x5f\x59\x0b\x4f\x49\x83\xc7\xf8\xe1\x6c\x79\x25\x91\xa0\xbc\x00" \ "\xda\xe1\x95\xed\x4c\xb0\xc5\x29\xba\xb4\xe0\xef\x6a\xb7\x2c\xeb\xa4\xbf\x2b\xac\xe3\x52\xe0\xd5" \ "\x81\xde\x4c\xba\x79\x9f\x45\x3b\x07\x3f\x55\xd2\xa1\xf3\x94\xaa\x9a\x5a\x5b\xb9\x17\x64\x2e\xbf" \ "\x2a\xb2\x3d\x4c\xa2\x95\x13\x9a\x57\xfd\xae\x69\x44\x77\x64\x12\x3d" // SFU-stats SSL public key pinning - backup key #define SFUSTATSSSLMODULUS2 "\x9f\x3a\xa7\x48\x3b\x71\xbf\x20\xc5\x32\x79\x46\xb1\xa3\x01\xb8\xd8\x07\x27\x0e\x6f\xe5\x2c" \ "\xb1\x0d\xd2\x3f\x6f\x92\x99\xb3\x7c\xb9\x4d\xf5\x7e\xbc\x21\x4b\x87\xbe\x93\x7d\xb9\xb2\x55\x5d" \ "\xd0\x9e\x1c\xd8\x19\x74\x68\x05\x90\x15\x93\x2b\x3d\x06\x0d\xeb\x5d\x52\xa7\xf9\x03\x33\x1f\x84" \ "\x52\x71\xe0\x05\x4d\x97\x36\x79\x9d\x29\x79\xb2\x79\x10\x64\x67\xb0\xdf\xa1\xda\x9e\x31\x92\x80" \ "\xaf\x36\x7d\x06\xae\x28\xac\xc9\x33\x9d\x1e\x82\xf2\xbe\x08\x7a\xa0\x35\x74\xd6\xb3\x94\xe3\x34" \ "\x0f\xc2\x69\x5a\xf3\xea\xee\x72\x78\xba\x46\xe2\x45\xde\x9a\x52\x9b\x8b\x54\xce\x71\xd8\x5b\x5b" \ "\x96\xbe\xce\xae\x0e\x58\x21\x1d\xa8\x01\x76\x87\xa0\x9e\x46\x61\xbe\x3d\xc6\xcc\xc3\x3d\x76\xf8" \ "\x61\xaa\xaf\x68\x8e\xf7\x50\xf4\x6e\xca\x1d\x4f\xf1\xc3\xbf\xb0\x3f\x50\x8b\x2d\x22\xbf\x95\x0a" \ "\x39\x8f\xd6\x9d\x3d\x42\xbe\x39\x65\xf2\xd9\xf4\x8c\xb5\x7c\x28\x0a\xf3\xe4\x88\xbb\x43\x21\x97" \ "\xfe\xbd\x27\x40\xea\xba\x08\xa6\x83\x60\x50\x1b\x06\xe1\x82\xb2\x4f\xc2\xee\xf5\x9e\xab\x43\xc7" \ "\xc7\x3b\xf6\xc6\xd3\xcc\xff\x9e\xd9\xa3\x3a\x7b\x18\x00\xd3\xca\xfd" // active and backup keys use the same exponent #define SFUSTATSSSLEXPONENTSIZE "\x03" #define SFUSTATSSSLEXPONENT "\x01\x00\x01" class MEGA_API SpeedController { public: // Size of the circular buffer used to calculate the circular mean speed (in seconds). static const dstime SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS = 5; // Constant representing the number of deciseconds in one second. static constexpr dstime DS_PER_SECOND = 10; SpeedController(); // Calculates and updates the circular mean speed and the total mean speed. // Returns the circular mean speed (same value as getMeanSpeed()). m_off_t calculateSpeed(m_off_t numBytes); // Retrieves the circular mean speed calculated over the time window defined by the circular buffer (SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS). m_off_t getCircularMeanSpeed() const; // Retrieves the total mean speed calculated over the entire data transfer duration. m_off_t getMeanSpeed() const; // Initialize the speed values. If called again, it increments the initial time by the time elapsed since the last update. void requestStarted(); // Calculates the speed (calls calculateSpeed) using "newPos" as the delta value, and returns the delta (newPos - mRequestPos). m_off_t requestProgressed(m_off_t newPos); // Get the last request speed. m_off_t lastRequestMeanSpeed() const; // Time elapsed since the request started in deciseconds. dstime requestElapsedDs() const; private: // Values for the circular mean speed std::array mCircularBuf{}; // Circular buffer of bytes received/transmitted per second. size_t mCircularCurrentIndex{}; // Index of the current entry in the circular buffer. dstime mCircularCurrentTime{}; // Current time for circular buffer updates. m_off_t mCircularCurrentSum{}; // Sum of bytes in the circular buffer. // Values for the total mean speed m_off_t mTotalSumBytes{}; // Total sum of bytes received/transmitted. dstime mInitialTime{}; // Initial time when the speed controller was started. m_off_t mMeanSpeed{}; // Total mean speed (retrieved with getMeanSpeed()). // Values for single requests m_off_t mRequestPos{}; // Position of the single request. dstime mRequestStart{}; // Start time of the single request. dstime mLastRequestUpdate{}; // Last time the single request was updated. // Calculate the total mean speed by aggregating progress (from deciseconds to seconds) over the total time period. // Helper method to be called within calculateSpeed(), so the value is assigned to mMeanSpeed, which can be retrieved with getMeanSpeed(). m_off_t calculateMeanSpeed(); // Helper method to update the circular buffer when the delta time from the previous call is within the circular buffer size (SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS) void updateCircularBufferWithinLimit(m_off_t delta, dstime deltaTimeFromPreviousCall); // Helper method to update the circular buffer when the delta time from the previous call exceeds the circular buffer size (SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS) void updateCircularBufferWithWeightedAverageForDeltaExceedingLimit(m_off_t& delta, dstime deltaTimeFromPreviousCall); // Calculate offset in deciseconds for the current second starting from the initial time. dstime calculateCurrentSecondOffsetInDs() const; // Calculate next buffer index. void nextIndex(size_t ¤tCircularBufIndex, size_t positionsToAdvance = 1) const; // Aggregate instantaneous delta values over a specified time subperiod to calculate a weighted average. m_off_t aggregateProgressForTimePeriod(dstime timePeriodToAggregate, dstime totalTime, m_off_t bytesToAggregate) const; }; extern std::mutex g_APIURL_default_mutex; extern string g_APIURL_default; extern bool g_disablepkp_default; // generic host HTTP I/O interface struct MEGA_API HttpIO : public EventTrigger { // set whenever a network request completes successfully bool success; // post request to target URL virtual void post(struct HttpReq*, const char* = NULL, unsigned = 0) = 0; // cancel request virtual void cancel(HttpReq*) = 0; // real-time POST progress information virtual m_off_t postpos(void*) = 0; // execute I/O operations virtual bool doio(void) = 0; // lock/unlock all in-flight HttpReqs virtual void lock() { } virtual void unlock() { } virtual void disconnect() { } // track Internet connectivity issues dstime noinetds; bool inetback; void inetstatus(bool); bool inetisback(); // timestamp of last data received (across all connections) dstime lastdata; // download speed SpeedController downloadSpeedController; m_off_t downloadSpeed; void updatedownloadspeed(m_off_t size = 0); // upload speed SpeedController uploadSpeedController; m_off_t uploadSpeed; void updateuploadspeed(m_off_t size = 0); // data receive timeout (ds) static const int NETWORKTIMEOUT; // request timeout (ds) static const int REQUESTTIMEOUT; // sc request timeout (ds) static const int SCREQUESTTIMEOUT; // connection timeout (ds) static const int CONNECTTIMEOUT; // root URL for API requests string APIURL; // disable public key pinning (for testing purposes) (determines if we check the public key from APIURL) bool disablepkp = false; // set useragent (must be called exactly once) virtual void setuseragent(string*) = 0; // get proxy settings from the system virtual Proxy *getautoproxy(); // set max download speed virtual bool setmaxdownloadspeed(m_off_t bpslimit); // set max upload speed virtual bool setmaxuploadspeed(m_off_t bpslimit); // get max download speed virtual m_off_t getmaxdownloadspeed(); // get max upload speed virtual m_off_t getmaxuploadspeed(); virtual int cacheresolvedurls(const std::vector&, const std::vector&) { return false; } HttpIO(); virtual ~HttpIO() { } virtual void setproxy(const Proxy&); virtual std::optional getproxy() const; }; // outgoing HTTP request struct MEGA_API HttpReq { std::atomic status; m_off_t pos; int httpstatus; unsigned mErrCode{0}; httpmethod_t method; contenttype_t type; int timeoutms; string posturl; bool protect; // check pinned public key bool minspeed; bool mExpectRedirect = false; bool mChunked = false; bool sslcheckfailed; string sslfakeissuer; string mRedirectURL; string* out; string in; size_t inpurge; size_t outpos; string outbuf; // if the out payload includes a fetch nodes command bool includesFetchingNodes = false; byte* buf; m_off_t buflen, bufpos, notifiedbufpos; // When did a post() start std::chrono::steady_clock::time_point postStartTime; // we assume that API responses are smaller than 4 GB m_off_t contentlength; // time left related to a bandwidth overquota m_time_t timeleft; // Content-Type of the response string contenttype; // Hashcash data extracted from X-Hashcash header of cs response, if any string mHashcashToken; uint8_t mHashcashEasiness{}; // If the request DNS resolution has failed bool mDnsFailure = false; // snapshot of the global cancel_epoch_t when the request is sent // use this to early exit from an ongoing request when cancel_epoch_bump() is called by the // application Note: currently used to early exit from gencash() computation. cancel_epoch_t mCancelSnapshot{}; // HttpIO implementation-specific identifier for this connection void* httpiohandle; // while this request is in flight, points to the application's HttpIO // object - NULL otherwise HttpIO* httpio; // set url and content type for subsequent requests void setreq(const char*, contenttype_t); // send POST request to the network void post(MegaClient*, const char* = NULL, unsigned = 0); // send GET request to the network void get(MegaClient*); // send a DNS request void dns(MegaClient*); // store chunk of incoming data with optional purging void put(void*, unsigned, bool = false); // start and size of unpurged data block - must be called with !buf and httpio locked char* data(); size_t size(); // a buffer that the HttpReq filled in. This struct owns the buffer (so HttpReq no longer has it). struct http_buf_t { byte* datastart() const; size_t datalen() const; size_t start; size_t end; http_buf_t(byte* b, size_t s, size_t e); // takes ownership of the byte*, which must have been allocated with new[] ~http_buf_t(); void swap(http_buf_t& other); bool isNull() const; private: byte* buf; }; // give up ownership of the buffer for client to use. The caller is the new owner of the http_buf_t, and the HttpReq no longer has the buffer or any info about it. http_buf_t* release_buf(); // set amount of purgeable data at 0 void purge(size_t); // set response content length void setcontentlength(m_off_t); // disconnect open HTTP connection void disconnect(); // progress information virtual m_off_t transferred(MegaClient*); // timestamp of last data sent or received dstime lastdata; // prevent raw data from being dumped in debug mode bool binary; HttpReq(bool = false); virtual ~HttpReq(); void init(); // get HTTP method as a static string const char* getMethodString(); // true if HTTP response status code is 3xx redirection bool isRedirection() const { return (httpstatus / 100) == 3; } /** * @brief Get an unique identifier for this object */ uint32_t getId() const { return reqId; } const std::string& getLogName() const { return logname; } void setLogName(const std::string& newLogName) { logname += newLogName; } private: static std::atomic_uint32_t nextReqId; const uint32_t reqId; // identify different channels from different MegaClients etc in the log std::string logname; void prepareMethod(HttpIO* clientHttpIo, const httpmethod_t reqMethod); }; struct MEGA_API GenericHttpReq : public HttpReq { GenericHttpReq(PrnGen &rng, bool = false); // tag related to the request int tag; // max number of retries, including the first attempt // 0 = infinite retries, 1 = no retries int maxretries; // current retry number int numretry; // backoff between retries BackoffTimer bt; // true when the backoff between retries is active bool isbtactive; // backoff to control the maximum allowed time for the request BackoffTimer maxbt; }; class MEGA_API EncryptByChunks { // this class allows encrypting a large buffer chunk by chunk, // or alternatively encrypting consecutive data by feeding it a piece at a time, // from separate buffers (the algorithm chooses the size though) public: // size (in bytes) of the CRC of uploaded chunks enum { CRCSIZE = 12 }; EncryptByChunks(SymmCipher* k, chunkmac_map* m, uint64_t iv); // encryption: data must be NULL-padded to SymmCipher::BLOCKSIZE // (so buffer allocation size must be rounded up too) // len must be < 2^31 virtual byte* nextbuffer(unsigned datasize) = 0; bool encrypt(m_off_t pos, m_off_t npos, string& urlSuffix); private: SymmCipher* key; chunkmac_map* macs; uint64_t ctriv; // initialization vector for CTR mode byte crc[CRCSIZE]; void updateCRC(byte* data, unsigned size, unsigned offset); }; class MEGA_API EncryptBufferByChunks : public EncryptByChunks { // specialisation for encrypting a whole contiguous buffer by chunks byte *chunkstart; byte* nextbuffer(unsigned bufsize) override; public: EncryptBufferByChunks(byte* b, SymmCipher* k, chunkmac_map* m, uint64_t iv); }; // file chunk I/O struct MEGA_API HttpReqXfer : public HttpReq { unsigned size; double mStartTransferTime{-1}; double mConnectTime{-1}; bool isLatencyProcessed{}; virtual void prepare(const char*, SymmCipher*, uint64_t, m_off_t, m_off_t) = 0; HttpReqXfer() : HttpReq(true), size(0) { } }; // file chunk upload struct MEGA_API HttpReqUL : public HttpReqXfer { chunkmac_map mChunkmacs; void prepare(const char*, SymmCipher*, uint64_t, m_off_t, m_off_t); m_off_t transferred(MegaClient*); ~HttpReqUL() { } }; // file chunk download struct MEGA_API HttpReqDL : public HttpReqXfer { m_off_t dlpos; bool buffer_released; void prepare(const char*, SymmCipher*, uint64_t, m_off_t, m_off_t); HttpReqDL(); ~HttpReqDL() { } }; // file attribute get struct MEGA_API HttpReqGetFA : public HttpReq { ~HttpReqGetFA() { } }; } // namespace #endif sdk-10.11.0/include/mega/json.h000066400000000000000000000241041516266226600161260ustar00rootroot00000000000000/** * @file mega/json.h * @brief Linear non-strict JSON scanner * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_JSON_H #define MEGA_JSON_H 1 #include "name_id.h" #include "types.h" namespace mega { // linear non-strict JSON scanner struct MEGA_API JSON { JSON() : pos(nullptr) { } explicit JSON(const string& data) : pos(data.c_str()) { } explicit JSON(const char* data) : pos(data) { } const char* pos; bool isnumeric(); void begin(const char*); m_off_t getint(); double getfloat(); const char* getvalue(); std::uint64_t getfsfp(); uint64_t getuint64(); nameid getnameid(); nameid getnameid(const char*) const; private: nameid getNameidSkipNull(bool skipnullvalues); public: nameid getnameidvalue(); string getname(); string getnameWithoutAdvance() const; bool is(const char*); int storebinary(byte*, int); bool storebinary(string*); // MegaClient::NODEHANDLE bool ishandle(int = 6); handle gethandle(int = 6); NodeHandle getNodeHandle(); bool enterarray(); bool leavearray(); bool enterobject(); bool leaveobject(); bool storeKeyValueFromObject(string& key, string& value); bool storeobject(string* = NULL); bool skipnullvalue(); static void unescape(string*); /** * @brief Extract a string value for a name in a JSON string * @param json JSON string to check * @param name Attribute name. * @param value Atribute value. * @return False if the JSON string doesn't contains the string attribute */ static bool extractstringvalue(const string & json, const string & name, string* value); // convenience functions, which avoid warnings and casts // Note: they should check for the value returned by the underlying function in case of error. inline int getint32() { return int(getint()); } inline unsigned getuint32() { return unsigned(getint()); } inline bool getbool() { return bool(getint()); } // Only advance the pointer if it's an error (0, -1, -2, -3, ...) bool isNumericError(error& e); // copy JSON-delimited string static void copystring(string*, const char*); // Strip whitspace from a string in a JSON-safe manner. static string stripWhitespace(const string& text); static string stripWhitespace(const char* text); }; class MEGA_API JSONWriter { public: JSONWriter(); MEGA_DEFAULT_COPY_MOVE(JSONWriter) void cmd(const char*); void notself(MegaClient*); void arg(const char*, const string&, int = 1); void arg(const char*, const char*, int = 1); void arg(const char*, handle, size_t); void arg(const char*, NodeHandle); void arg(const char*, const byte*, size_t); void arg(const char*, m_off_t); void arg_B64(const char*, const string&); void arg_fsfp(const char*, std::uint64_t); // These should only be used when producing JSON meant for human consumption. // If you're generating JSON meant to be consumed by our servers, you // should escape things using arg_B64 above. void arg_stringWithEscapes(const char*, const char*, int = 1); void arg_stringWithEscapes(const char*, const string&, int = 1); void addcomma(); void appendraw(const char*); void appendraw(const char*, int); void beginarray(); void beginarray(const char*); void endarray(); void beginobject(); void beginobject(const char*); void endobject(); void element(int); void element(handle, size_t = sizeof(handle)); void element(const byte*, size_t); void element(const char* data); void element(const string& data); void element_B64(const string&); void openobject(); void closeobject(); const byte* getbytes() const; const string& getstring() const; size_t size() const; void clear() { mJson.clear(); } protected: string escape(const char* data, size_t length) const; private: static const int MAXDEPTH = 8; int elements(); string mJson; std::array mLevels; signed char mLevel; }; // JSONWriter // Class to process JSON in streaming // For performance reasons, these objects don't own the memory of the JSON buffer being parsed // nor the map of filters used to trigger callbacks for the different JSON elements, so the caller // must ensure that the memory is alive during the processing of JSON chunks class MEGA_API JSONSplitter { public: JSONSplitter(); // Reinitializes the object to start parsing a new JSON stream void clear(); enum class CallbackResult { SUCCESS = 0, FAILED = -1, // PAUSED is only supported in KEY:VALUE filters. // For any other filter, PAUSED would be treated as FAILED PAUSED = 1, }; typedef std::function FilterCallback; typedef std::list*> FiltersChain; inline static CallbackResult ResultFromBool(bool result) { return result ? CallbackResult::SUCCESS : CallbackResult::FAILED; } // Process a new chunk of JSON data and triggers callbacks in the filters map. // Returns the number of consumed bytes. // // The "filters" map allows to process the different JSON elements when they are complete // // The keys can be composed of these elements: // { or [ -> unnamed object or array // {name or [name -> object or array with the name "name" // "name -> string value for an attribute with name "name" // These alements can be appended to specify full paths, for example: // {[f{ -> unnamed objects, inside an array with the name "f", inside an unnamed object // {[ipc -> array with the name "ipc" inside an unnamed object // // The JSON object passed to the callback will contain the whole requested element, // except if anything was filtered inside. In that case, only the remaining data // would be passed to the callback. // // There are also special keys for specific purposes: // (empty string) -> Called when the parsing starts. An empty string is passed to the callback. // E -> A parsing error was detected. The callback will receive the current data in the stream // # -> An error was received, either a number or an error object {"err":XXX} // { -> The end of a JSON object. This is a normal case, but with the exception that // if an error object is received, this callback won't be called. // // Callbacks in the map should return true on success and false if there was a parsing error, If // false is returned, the "E" callback will be triggered and the parsing will be aborted. // // "data" is the next chunk of JSON data to process. Initially it must be the beginning of the // JSON stream. The next chunk must start from the first non-consumed byte in the previous // call, which is at "data" + consumed_bytes (the return value of the previous call). // It is allowed to pass a different buffer for the next call, but it must // start with the same data that was not consumed during the previous call. m_off_t processChunk(const std::map* filters, const char* data); // Process a new chunk of JSON data and triggers callbacks in the list of filters map. // Returns the number of consumed bytes. // // The "filtersList" is a list of map that allows to process the different JSON elements when // they are complete m_off_t processChunk(const FiltersChain& filtersList, const char* data); // Check if the parsing has finished bool hasFinished(); // Check if the parsing has failed bool hasFailed(); // Check if the parsing has paused bool hasPaused(); // Check if the parsing is starting bool isStarting(); protected: // Returns the position (in bytes) to the end of the current JSON string, or -1 if it's not found int strEnd(); // Returns the position (in bytes) to the end of the current number, or -1 if it's not found int numEnd(); // Called when there is a parsing error void parseError(const FiltersChain& filters); // Check if there are any pending filter markers indicating that processing failed bool chunkProcessingFinishedSuccessfully(const FiltersChain& filters); // Find a filter callback for a given path in the filters chain const FilterCallback* findCallback(const FiltersChain& filtersChain, const std::string& path); // Position of the character being processed (not owned by this object) const char* mPos = nullptr; // Position after the last filtered JSON path (not owned by this object) const char *mLastPos = nullptr; // Name of the last JSON attribute name processed std::string mLastName; // Stack with accessed paths in the JSON stream std::vector mStack; // Current path in the processing of the JSON stream std::string mCurrentPath; // Bytes processed since the last discarded byte. // Despite those bytes were already processed, they are not discarded yet // because they belong to a JSON element that hasn't been totally // received nor filtered yet. m_off_t mProcessedBytes = 0; // 0: no value expected, 1: optional value expected, -1: compulsory value expected int mExpectValue = 1; // the parsing is starting bool mStarting = true; // the parsing has finished bool mFinished = false; // the parsing has failed bool mFailed = false; // the parsing has paused bool mPaused = false; }; // JSONSplitter } // namespace #endif sdk-10.11.0/include/mega/localpath.h000066400000000000000000000332341516266226600171300ustar00rootroot00000000000000/** * @brief Manage local paths (standars and URIs) */ #ifndef LOCALPATH_H #define LOCALPATH_H #include "mega/types.h" #include #include #include namespace mega { // Enumeration for filesystem families enum FileSystemType { FS_UNKNOWN = -1, FS_APFS = 0, FS_HFS = 1, FS_EXT = 2, FS_FAT32 = 3, FS_EXFAT = 4, FS_NTFS = 5, FS_FUSE = 6, FS_SDCARDFS = 7, FS_F2FS = 8, FS_XFS = 9, FS_CIFS = 10, FS_NFS = 11, FS_SMB = 12, FS_SMB2 = 13, FS_LIFS = 14, }; #ifdef WIN32 using string_type = std::wstring; #else using string_type = std::string; #endif class LocalPath; enum class PathType { ABSOLUTE_PATH, RELATIVE_PATH, URI_PATH, }; class AbstractLocalPath { public: virtual ~AbstractLocalPath() {} virtual auto asPlatformEncoded(const bool stripPrefix) const -> string_type = 0; virtual std::string platformEncoded() const = 0; virtual bool empty() const = 0; virtual void clear() = 0; virtual LocalPath leafName() const = 0; virtual std::string leafOrParentName() const = 0; virtual void append(const LocalPath& additionalPath) = 0; virtual void appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways) = 0; virtual void prependWithSeparator(const LocalPath& additionalPath) = 0; virtual LocalPath prependNewWithSeparator(const LocalPath& additionalPath) const = 0; virtual void trimNonDriveTrailingSeparator() = 0; virtual bool findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess& fsaccess) const = 0; virtual bool beginsWithSeparator() const = 0; virtual bool endsInSeparator() const = 0; virtual size_t getLeafnameByteIndex() const = 0; virtual LocalPath subpathFrom(const size_t bytePos) const = 0; virtual void changeLeaf(const LocalPath& newLeaf) = 0; virtual LocalPath parentPath() const = 0; virtual LocalPath insertFilenameSuffix(const std::string& suffix) const = 0; virtual bool isContainingPathOf(const LocalPath& path, size_t* subpathIndex = nullptr) const; virtual bool nextPathComponent(size_t& subpathIndex, LocalPath& component) const; virtual bool hasNextPathComponent(const size_t index) const; virtual std::string toPath(const bool normalize) const = 0; virtual std::string toName(const FileSystemAccess& fsaccess) const = 0; virtual bool isRootPath() const = 0; virtual bool extension(std::string& extension) const = 0; virtual std::string extension() const = 0; virtual bool related(const LocalPath& other) const = 0; virtual bool invariant() const = 0; virtual std::unique_ptr clone() const = 0; virtual PathType getPathType() const = 0; virtual std::string serialize() const = 0; virtual bool unserialize(const std::string& data) = 0; virtual string_type getRealPath() const = 0; private: virtual bool findNextSeparator(size_t& separatorBytePos) const = 0; }; /** * @brief Abstract base class providing platform-dependent URI handling * * Each platform should implement this interface to determine whether a given string * is recognized as a URI and to retrieve a representative name from that URI. */ class MEGA_API PlatformURIHelper { public: virtual ~PlatformURIHelper(){}; // Returns true if string is an URI virtual bool isURI(const string_type& URI) = 0; // Returns the name of file/directory pointed by the URI virtual std::optional getName(const string_type& uri) = 0; // Returns parent URI if it's available virtual std::optional getParentURI(const string_type& uri) = 0; virtual std::optional getPath(const string_type& uri) = 0; virtual std::optional getURI(const string_type& uri, const std::vector leaves) = 0; }; /** * @brief Provides an interface to handle URIs as an identifier for files and directories. * * The URIHandler class offers static methods to detect if a given string is a URI and to extract a * name from that URI. This functionality should be implemented by a platform specific * implementation PlatformURIHelper */ class MEGA_API URIHandler { public: // Check if a path is recognized as a URI static bool isURI(const string_type& uri); // Retrieve the name for a given path or URI static std::optional getName(const string_type& uri); // Retrieve the name for a given path or URI static std::optional getParentURI(const string_type& uri); static std::optional getPath(const string_type& uri); // Returns a new URI that point to the element pointed by uri + leaves static std::optional getURI(const string_type& uri, const std::vector leaves); // platformHelper should be kept alive during all program execution and ownership isn't taken static void setPlatformHelper(PlatformURIHelper* platformHelper); private: static PlatformURIHelper* mPlatformHelper; }; /** * @brief Class to manage device paths * * This class provide two implementations one for standard paths and other for URIs * For URI implementation works properly, an implementation for PlatformURIHelper should be provided * Standard path is implemented with a string * URI implementation has a string to store the URI and a vector of string to handle the leaves of * the tree */ class LocalPath { public: LocalPath(); LocalPath(LocalPath&&) noexcept; LocalPath& operator=(LocalPath&&) noexcept; LocalPath(const LocalPath& p); LocalPath& operator=(const LocalPath& p); virtual ~LocalPath() {} AbstractLocalPath* getImpl() const { return mImplementation.get(); } void setImpl(std::unique_ptr&& imp) { mImplementation = std::move(imp); } // path2local / local2path are much more natural here than in FileSystemAccess // convert MEGA path (UTF-8) to local format // there is still at least one use from outside this class static void path2local(const std::string*, std::string*); static void local2path(const std::string*, std::string*, const bool normalize); #if defined(_WIN32) static void local2path(const std::wstring*, std::string*, const bool normalize); static void path2local(const std::string*, std::wstring*); #endif // Create a Localpath from a utf8 string where no character conversions or escaping is // necessary. static LocalPath fromAbsolutePath(const std::string& path); static LocalPath fromRelativePath(const std::string& path); // Build LocalPath from URI, path can have following structure // URI#subFolde1#subFolder2#file // Example: // "content://com.android.externalstorage.documents/tree/primary%3Adescarga%2Fvarias/#F1#" static LocalPath fromURIPath(const string_type& path); static bool isURIPath(const std::string& path); // Create a LocalPath from a utf8 string, making any character conversions (escaping) necessary // for characters that are disallowed on that filesystem. fsaccess is used to do the // conversion. static LocalPath fromRelativeName(std::string path, const FileSystemAccess& fsaccess, const FileSystemType fsType); // Create a LocalPath from a string that was already converted to be appropriate for a local // file path. static LocalPath fromPlatformEncodedAbsolute(const std::string& localname); static LocalPath fromPlatformEncodedRelative(const std::string& localname); #ifdef WIN32 static LocalPath fromPlatformEncodedAbsolute(std::wstring&& localname); static LocalPath fromPlatformEncodedRelative(std::wstring&& localname); #endif // UTF-8 normalization static void utf8_normalize(std::string*); // Generates a name for a temporary file static LocalPath tmpNameLocal(); #ifdef _WIN32 typedef wchar_t separator_t; static constexpr separator_t localPathSeparator = L'\\'; static constexpr char localPathSeparator_utf8 = '\\'; #else typedef char separator_t; static constexpr separator_t localPathSeparator = '/'; static constexpr char localPathSeparator_utf8 = '/'; #endif static constexpr char uriPathSeparator_utf8 = '/'; bool isAbsolute() const { if (mImplementation) { return mImplementation->getPathType() == PathType::ABSOLUTE_PATH; } return false; } bool isURI() const { if (mImplementation) { return mImplementation->getPathType() == PathType::URI_PATH; } return false; } std::string serialize() const; static std::optional unserialize(const std::string& d); bool operator==(const LocalPath& p) const; bool operator!=(const LocalPath& p) const; bool operator<(const LocalPath& p) const; // Returns a string_type (wstring in windows) to the string's internal representation. // // Call this function with stripPrefix to false if you don't want any modification in string's // internal representation, otherwise prefix will be stripped in Windows (except for URI PATHS) auto asPlatformEncoded(const bool stripPrefix) const -> string_type; // Returns a string to internal representation // For URI path, returns a string with a URI that point to that LocalPath it it exists std::string platformEncoded() const; bool empty() const; void clear(); LocalPath leafName() const; /* * Return the last component of the path (internally uses absolute path, no matter how the * instance was initialized) that could be used as an actual name. * * Examples: * "D:\\foo\\bar.txt" "bar.txt" * "D:\\foo\\" "foo" * "D:\\foo" "foo" * "D:\\" "D" * "D:" "D" * "D" "D" * "D:\\.\\" "D" * "D:\\." "D" * ".\\foo\\" "foo" * ".\\foo" "foo" * ".\\" (as in "C:\\foo\\bar\\.\\") "bar" * "." (as in "C:\\foo\\bar\\.") "bar" * "..\\..\\" (as in "C:\\foo\\bar\\..\\..\\") "C" * "..\\.." (as in "C:\\foo\\bar\\..\\..") "C" * "..\\..\\.." (as in "C:\\foo\\bar\\..\\..\\..", thus too far back) "C" * "/" (*nix) "" */ std::string leafOrParentName() const; void append(const LocalPath& additionalPath); void appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways); void prependWithSeparator(const LocalPath& additionalPath); LocalPath prependNewWithSeparator(const LocalPath& additionalPath) const; void trimNonDriveTrailingSeparator(); bool findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess& fsaccess) const; bool beginsWithSeparator() const; // For URIS, returns true if it's only a URI without any appended leaf bool endsInSeparator() const; // get the index of the leaf name. A trailing separator is considered part of the leaf. size_t getLeafnameByteIndex() const; LocalPath subpathFrom(const size_t bytePos) const; void changeLeaf(const LocalPath& newLeaf); // Return a path denoting this path's parent. // // Result is undefined if this path is a "root." LocalPath parentPath() const; LocalPath insertFilenameSuffix(const std::string& suffix) const; bool isContainingPathOf(const LocalPath& path, size_t* subpathIndex = nullptr) const; bool nextPathComponent(size_t& subpathIndex, LocalPath& component) const; bool hasNextPathComponent(const size_t index) const; // Return a utf8 representation of the LocalPath // No escaping or unescaping is done. // If this function is called with normalize false, utf8 representation of the LocalPath won't // be modified. Otherwise utf8 representation will be normalized std::string toPath(const bool normalize) const; // Return a utf8 representation of the LocalPath, taking into account that the LocalPath // may contain escaped characters that are disallowed for the filesystem. // Those characters are converted back (unescaped). fsaccess is used to do the conversion. std::string toName(const FileSystemAccess& fsaccess) const; // Does this path represent a filesystem root? // // Relative paths are never considered to be a root path. // // On UNIX systems, this predicate returns true if and only if the // path denotes /. // // On Windows systems, this predicate returns true if and only if the // path specifies a drive such as C:\. // For URIs, root path is consider if LocalPath doesn't contain any leaf bool isRootPath() const; bool extension(std::string& extension) const; std::string extension() const; // Check if this path is "related" to another. // // Two paths are related if: // - They are effectively identical. // - One path contains another. bool related(const LocalPath& other) const; bool invariant() const; // Useful for path from type URI, it tries to convert URI in a path // For standard paths returns the as LocalPath::asPlatformEncoded string_type getRealPath() const; private: #ifdef _WIN32 static string_type toStringType(const std::wstring& path); #endif static string_type toStringType(const std::string& path); std::unique_ptr mImplementation; }; } // mega namespace #endif // LOCALPATH_H sdk-10.11.0/include/mega/log_level.h000066400000000000000000000010561516266226600171260ustar00rootroot00000000000000#pragma once #include #include namespace mega { #define DEFINE_LOG_LEVELS(expander) \ expander(Fatal) \ expander(Error) \ expander(Warning) \ expander(Info) \ expander(Debug) \ expander(Verbose) enum LogLevel : int { #define DEFINE_LOG_LEVEL_ENUMERANT(name) log ## name, DEFINE_LOG_LEVELS(DEFINE_LOG_LEVEL_ENUMERANT) #undef DEFINE_LOG_LEVEL_ENUMERANT logMax = logVerbose }; // LogLevel LogLevel toLogLevel(const std::string& level); const char* toString(LogLevel level); } // mega sdk-10.11.0/include/mega/log_level_forward.h000066400000000000000000000001011516266226600206400ustar00rootroot00000000000000#pragma once namespace mega { enum LogLevel : int; } // mega sdk-10.11.0/include/mega/logging.h000066400000000000000000000755021516266226600166130ustar00rootroot00000000000000/** * @file mega/logging.h * @brief Logging class * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ /* Usage example: 1) #include // for std::ofstream // output debug messages to file std::ofstream debugfile; debugfile.open("debug.log"); // use logDebug level SimpleLogger::setLogLevel(logDebug); // set output to stdout for all messages SimpleLogger::setAllOutputs(&std::cout); // add file for debug output only SimpleLogger::addOutput(logDebug, &debugfile); // print additional components for debug output SimpleLogger::setOutputSettings(logDebug, true, true, true); ... LOG_debug << "test"; // will print message on screen and append line to debugfile LOG_info << "informing"; // will only print message on screen 2) // set output class for all types of logs (for example - send logs to remote Log Server) class MyOutput: public Logger { public: void log(const char *time, int loglevel, const char *source, const char *message) { std::cout << "{" << time << "}" << " [" << source << "] " << message << std::endl; } }; ... MyOutput output; // let's output both Debug (verbose) and Info (just messages) logs // and send logs to remote Log Server SimpleLogger::setOutputSettings(logDebug, true, true, true); SimpleLogger::setOutputSettings(logInfo, false, false, false); SimpleLogger::setLogLevel(logDebug); SimpleLogger::setAllOutputs(&std::cout); SimpleLogger::setOutputClass(output); SimpleLogger::setOutputClass(&myOutput); LOG_debug << "test"; LOG_info << "informing"; 3) if MEGA_QT_LOGGING defined: QString a = QString::fromAscii("test1"); LOG_info << a; LOG_info << QString::fromAscii("test2"); 4) Performance mode can be enabled via defining ENABLE_LOG_PERFORMANCE at compile time. In performance mode, the `SimpleLogger` does not lock mutexes nor does it heap-allocate. Only `loglevel` and `message` of the `Logger` are populated where `message` will include file/line. It is assumed that the subclass of `Logger` provides timing information. In performance mode, only outputting to a logger assigned through `setOutputClass` is supported. Output streams are not supported. */ #pragma once #include #include #include #include #include #include #include #include #include #if __cplusplus >= 202002L #include #endif // define MEGA_QT_LOGGING to support QString #ifdef MEGA_QT_LOGGING #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #endif #include "mega/log_level.h" #include "mega/utils.h" namespace mega { // Output Log Interface class Logger { public: virtual ~Logger() = default; // Note: `time` and `source` are null in performance mode virtual void log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0 #endif ) = 0; }; const static size_t LOGGER_CHUNKS_SIZE = 1024; /** * @brief holds a const char * and its size to pass to SimpleLogger, to use the direct logging logic */ class DirectMessage{ private: const static size_t directMsgThreshold = 1024; //below this, the msg will be buffered as a normal message bool mForce = false; size_t mSize = 0; const char *mConstChar = nullptr; public: DirectMessage( const char *constChar, bool force = false) //force will set size as max, so as to stay above directMsgThreshold { mConstChar = constChar; mSize = strlen(constChar); mForce = force; } template::value>::type* = nullptr> DirectMessage( const char *constChar, T size, bool force = false) { mConstChar = constChar; mSize = static_cast(size); mForce = force; } size_t size() const { return mSize; } const char *constChar() const { return mConstChar; } }; class SimpleLogger { LogLevel level; #ifndef ENABLE_LOG_PERFORMANCE std::ostringstream ostr; std::string t; std::string fname; std::string getTime(); #else using Buffer = std::array; static inline thread_local Buffer mBuffer; static inline thread_local Buffer::iterator mBufferIt{mBuffer.begin()}; using DiffType = std::array::difference_type; using NumBuf = char[24]; const char* filenameStr; int lineNum; // True if there is a previous instance in the logging chain. // For example, the first LOG_debug below will have isChained == false: // the second LOG_debug inside f() below will have isChained == true: // int f() {LOG_DEBUG << "not first in chain"; return 2;} // LOG_debug << "1" << f(); bool isChained{false}; static inline thread_local std::vector mDirectMessages; static inline thread_local std::vector mCopiedParts; template void copyToBuffer(const DataIterator dataIt, DiffType currentSize) { if (mThreadLocalLoggingDisabled) return; DiffType start = 0; while (currentSize > 0) { const auto size = std::min(currentSize, std::distance(mBufferIt, mBuffer.end() - 1)); mBufferIt = std::copy(dataIt + start, dataIt + start + size, mBufferIt); if (mBufferIt == mBuffer.end() - 1) { outputBuffer(); } start += size; currentSize -= size; } } void outputBuffer(bool lastcall = false) { if (mThreadLocalLoggingDisabled) return; *mBufferIt = '\0'; if (!mDirectMessages.empty()) // some part has already been passed as direct, we'll do all directly { if (lastcall) //the mBuffer can be reused { mDirectMessages.push_back(DirectMessage(mBuffer.data(), std::distance(mBuffer.begin(), mBufferIt))); } else //reached LOGGER_CHUNKS_SIZE, we need to copy mBuffer contents { mCopiedParts.emplace_back(string(mBuffer.data())); mDirectMessages.push_back( DirectMessage{mCopiedParts.back().data(), mCopiedParts.back().size()}); } } else { Logger* currentLogger = logger.load(std::memory_order_acquire); if (currentLogger) { currentLogger->log(nullptr, level, nullptr, mBuffer.data()); } } mBufferIt = mBuffer.begin(); } template void logValue(const std::atomic& value) { logValue(value.load()); } template typename std::enable_if::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%d", static_cast(value)); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && !std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%p", reinterpret_cast(value)); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_signed::value && !std::is_same::value && !std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%d", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_signed::value && std::is_same::value && !std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%ld", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_signed::value && !std::is_same::value && std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%lld", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_unsigned::value && !std::is_same::value && !std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%u", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_unsigned::value && std::is_same::value && !std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%lu", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value && std::is_unsigned::value && !std::is_same::value && std::is_same::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%llu", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } template typename std::enable_if::value>::type logValue(const T value) { NumBuf buf; const auto size = snprintf(buf, sizeof(buf), "%g", value); copyToBuffer(buf, std::min(size, static_cast(sizeof(buf)) - 1)); } void logValue(const char* value) { copyToBuffer(value, static_cast(std::strlen(value))); } void logValue(const std::string& value) { copyToBuffer(value.begin(), static_cast(value.size())); } void logValue(const std::string_view value) { copyToBuffer(value.begin(), static_cast(value.size())); } void logValue(const ::mega::Error& value) // ::mega:: when building MEGAchat on windows, else ambiguity errors { logValue(error(value)); } void logValue(const std::error_code& value) { logValue(value.category().name()); logValue(":"); logValue(value.message()); } void logValue(const std::system_error& se) { logValue(se.code().category().name()); logValue(": "); logValue(se.what()); } #endif static std::atomic logger; static std::atomic logCurrentLevel; static constexpr size_t PAYLOAD_LOG_DEFAULT_SIZE = 10240; inline static std::atomic_size_t maxPayloadLogSize = PAYLOAD_LOG_DEFAULT_SIZE; public: // flag to turn off logging on the log-output thread, to prevent possible deadlock cycles. static inline thread_local bool mThreadLocalLoggingDisabled = false; SimpleLogger(const LogLevel ll, const char* filename, const int line, [[maybe_unused]] bool noChainAssert = false): level{ll} #ifdef ENABLE_LOG_PERFORMANCE , filenameStr(filename), lineNum(line), isChained(false) #endif { if (mThreadLocalLoggingDisabled) return; #ifdef ENABLE_LOG_PERFORMANCE // isChained if there are logs in the mBuffer or mDirectMessages // Note: such as if the first logger instance doesn't output anything and we treat // the second one as not in chain isChained = mBufferIt != mBuffer.begin() || !mDirectMessages.empty(); // logging chain is not well supported, change caller code to avoid it assert(!isChained || noChainAssert); #else Logger* currentLogger = logger.load(std::memory_order_acquire); if (!currentLogger) { return; } t = getTime(); std::ostringstream oss; oss << filename; if(line >= 0) { oss << ":" << line; } fname = oss.str(); #endif } ~SimpleLogger() { if (mThreadLocalLoggingDisabled) return; #ifdef ENABLE_LOG_PERFORMANCE if (filenameStr && lineNum != -1) { copyToBuffer(" [", 2); logValue(filenameStr); // put filename and line last, to keep the main text nicely column aligned copyToBuffer(":", 1); logValue(lineNum); copyToBuffer("]", 1); } // The last output if it is not chained const auto lastOutput = !isChained; outputBuffer(lastOutput); // Direct Messages are log on last output if (lastOutput) { if (!mDirectMessages.empty()) { Logger* currentLogger = logger.load(std::memory_order_acquire); if (currentLogger) { std::unique_ptr dm(new const char*[mDirectMessages.size()]); std::unique_ptr dms(new size_t[mDirectMessages.size()]); unsigned i = 0; for (const auto& d: mDirectMessages) { dm[i] = d.constChar(); dms[i] = d.size(); i++; } currentLogger->log(nullptr, level, nullptr, "", dm.get(), dms.get(), i); } } // Clear mDirectMessages.clear(); mCopiedParts.clear(); } #else Logger* currentLogger = logger.load(std::memory_order_acquire); if (currentLogger) { const auto message = ostr.str(); currentLogger ->log(/* */ t.c_str(), /* time stamp */ level, /* log level */ fname.c_str(), /* file name and line */ message.c_str()); /* the log message itself */ } #endif } SimpleLogger(const SimpleLogger&) = delete; SimpleLogger& operator=(const SimpleLogger&) = delete; static const char *toStr(LogLevel ll) { switch (ll) { case logDebug: return "debug"; case logInfo: return "info"; case logWarning: return "warn"; case logError: return "err"; case logFatal: return "FATAL"; case logVerbose: return "verbose"; } assert(false); return ""; } template SimpleLogger& operator<<(T* obj) { #ifdef ENABLE_LOG_PERFORMANCE if (obj) { logValue(obj); } else { copyToBuffer("(NULL)", 6); } #else if (obj) { ostr << obj; } else { ostr << "(NULL)"; } #endif return *this; } template ::value>::type> SimpleLogger& operator<<(const T obj) { static_assert(!std::is_same::value, "T cannot be nullptr_t"); #ifdef ENABLE_LOG_PERFORMANCE logValue(obj); #else ostr << obj; #endif return *this; } template typename std::enable_if::value #if defined(__cpp_char8_t) || __cplusplus >= 202002L && !std::is_same::value #endif #if __cplusplus >= 202002L && !std::is_same::value #endif , SimpleLogger&>::type operator<<(const T& obj) { #ifdef ENABLE_LOG_PERFORMANCE logValue(obj); #else ostr << obj; #endif return *this; } #ifdef MEGA_QT_LOGGING SimpleLogger& operator<<(const QString& s) { #ifdef ENABLE_LOG_PERFORMANCE logValue(s.toUtf8().constData()); #else ostr << s.toUtf8().constData(); #endif return *this; } #endif #if defined(__cpp_char8_t) || __cplusplus >= 202002L // Overload for std::u8string (char8_t string) - convert to std::string for streaming // Available when char8_t is supported SimpleLogger& operator<<(const std::u8string& s) { #ifdef ENABLE_LOG_PERFORMANCE logValue(std::string(reinterpret_cast(s.data()), s.size())); #else ostr << std::string(reinterpret_cast(s.data()), s.size()); #endif return *this; } #endif #if __cplusplus >= 202002L // Overload for std::filesystem::path - convert to std::string for streaming SimpleLogger& operator<<(const std::filesystem::path& p) { #ifdef ENABLE_LOG_PERFORMANCE logValue(p.string()); #else ostr << p.string(); #endif return *this; } #endif template SimpleLogger& operator<<(const std::unique_ptr& ptr) { #ifdef ENABLE_LOG_PERFORMANCE if (!ptr) { logValue(""); } else { logValue(*ptr.get()); } #else if (!ptr) { ostr << ""; } else { ostr << *ptr.get(); } #endif return *this; } template SimpleLogger& operator<<(const std::shared_ptr& ptr) { #ifdef ENABLE_LOG_PERFORMANCE if (!ptr) { logValue(""); } else { logValue(*ptr.get()); } #else if (!ptr) { ostr << ""; } else { ostr << *ptr.get(); } #endif return *this; } SimpleLogger& operator<<(const DirectMessage &obj) { #ifndef ENABLE_LOG_PERFORMANCE ostr.write(obj.constChar(), static_cast(obj.size())); #else // careful using constChar() without taking size() into account: *this << obj.constChar(); ended up with 2MB+ lines from fetchnodes. if (mBufferIt != mBuffer.begin()) //something was appended to the buffer before this direct msg { *mBufferIt = '\0'; mCopiedParts.emplace_back(string(mBuffer.data())); mDirectMessages.push_back( DirectMessage{mCopiedParts.back().data(), mCopiedParts.back().size()}); mBufferIt = mBuffer.begin(); } mDirectMessages.push_back(DirectMessage(obj.constChar(), obj.size())); #endif return *this; } // set output class static void setOutputClass(Logger *logger_class) { logger.store(logger_class, std::memory_order_release); } // set the current log level. all logs which are higher than this level won't be handled static void setLogLevel(LogLevel ll) { logCurrentLevel = ll; } inline static LogLevel getLogLevel() { return logCurrentLevel; } /** * @brief Sets the maximum size limit for request payload logging. * * When logging request payloads, messages exceeding this size will be truncated * with "[...]" to prevent extremely large log entries. * * @param size Maximum payload size in bytes. If set to 0, the max size limit of size_t is * applied and payloads will be logged in full regardless of size. Default is * PAYLOAD_LOG_DEFAULT_SIZE (10240 bytes). * */ static void setMaxPayloadLogSize(size_t size = PAYLOAD_LOG_DEFAULT_SIZE) { if (size == 0) maxPayloadLogSize = std::numeric_limits::max(); else maxPayloadLogSize = size; } inline static size_t getMaxPayloadLogSize() { return maxPayloadLogSize; } // Log messages forwarded from the client app though the configured logging mechanisms. // These do not go through the LOG_ macros. // // When ENABLE_LOG_PERFORMANCE is on, this must not be called during the lifetime of an // existing instance created in the same thread. Otherwise logs are mixed. That would be // a rare case, but could be avoided by writing: // LOG_info << "foo", SimpleLogger::postLog(logInfo, "bar", filename, line); static void postLog(LogLevel logLevel, const char *message, const char *filename, int line) { if (logCurrentLevel < logLevel) return; SimpleLogger simpleLogger(logLevel, filename ? filename : "", line); if (message) simpleLogger << message; } }; // source file leaf name - maybe to be compile time calculated one day template inline const char* log_file_leafname( const char (&fullpath)[N]) { for (auto i = N - 1; --i; ) { if (fullpath[i] == '/' || fullpath[i] == '\\') return &fullpath[i+1]; } return fullpath; } std::ostream& operator <<(std::ostream&, const std::system_error&); std::ostream& operator <<(std::ostream&, const std::error_code&); // Helper used in LOG_* macros below to make the right operand of ?: void to match the left one struct LoggerVoidify { void operator&(SimpleLogger&) {} }; // Note: If the argument to LOG_level includes any function call, it may be executed TWICE #define LOG_level(LOG_LEVEL) \ ::mega::SimpleLogger::getLogLevel() < (LOG_LEVEL) ? \ (void)0 : \ ::mega::LoggerVoidify() & \ ::mega::SimpleLogger((LOG_LEVEL), ::mega::log_file_leafname(__FILE__), __LINE__) #define LOG_verbose LOG_level(::mega::logVerbose) #define LOG_debug LOG_level(::mega::logDebug) #define LOG_info LOG_level(::mega::logInfo) #define LOG_warn LOG_level(::mega::logWarning) #define LOG_err LOG_level(::mega::logError) #define LOG_fatal \ ::mega::SimpleLogger(::mega::logFatal, ::mega::log_file_leafname(__FILE__), __LINE__) /** * @brief Checks if the current time is within an active time window. * * This function uses static timing to manage cycles of active and rest periods, determining if the * current call is made within an active time span. * * @tparam Duration The type of the chrono duration e.g. std::chrono::seconds, std::chrono::minutes. * @param sleepDuration The duration of the inactivity period. * @param activeDuration The duration of the active period. */ template inline bool isWithinActivePeriod(const TimeUnit sleepDuration, const TimeUnit activeDuration) { using namespace std::chrono; static const auto startTime = steady_clock::now(); const auto elapsed = duration_cast(steady_clock::now() - startTime); const TimeUnit period = sleepDuration + activeDuration; const TimeUnit currentPhase = elapsed % period; return currentPhase >= sleepDuration && currentPhase < period; } #define LOG_generic_timed(LOG_LEVEL, SLEEP_DUR, ACTIVE_DUR) \ ::mega::SimpleLogger::getLogLevel() < LOG_LEVEL || !isWithinActivePeriod(SLEEP_DUR, ACTIVE_DUR) ? \ (void)0 : \ ::mega::LoggerVoidify() & \ ::mega::SimpleLogger(LOG_LEVEL, ::mega::log_file_leafname(__FILE__), __LINE__) #define LOG_verbose_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logVerbose, SLEEP, ACTIVE) #define LOG_debug_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logDebug, SLEEP, ACTIVE) #define LOG_info_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logInfo, SLEEP, ACTIVE) #define LOG_warn_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logWarning, SLEEP, ACTIVE) #define LOG_err_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logError, SLEEP, ACTIVE) #define LOG_fatal_timed(SLEEP, ACTIVE) LOG_generic_timed(::mega::logFatal, SLEEP, ACTIVE) // moved from the intermediate layer class ExternalLogger : public Logger { public: typedef std::function< void(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages #endif )> LogCallback; ExternalLogger(); ~ExternalLogger(); void addMegaLogger(void* id, LogCallback); void removeMegaLogger(void* id); void setLogLevel(int logLevel); void setLogToConsole(bool enable); void log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages #endif ) override; // Do not use this unless you know what you are doing! // // This is an unfortunate workaround for cases when multiple connections/clients add // each its own logger to the same target (i.e. file), leading to duplicated messages // being logged consecutively. // This for example has happened in MegaChat's automated tests. void useOnlyFirstLogger(bool onlyFirst = true) { useOnlyFirstMegaLogger = onlyFirst; } private: std::recursive_mutex mutex; map megaLoggers; bool logToConsole; bool alreadyLogging = false; bool useOnlyFirstMegaLogger = false; }; class ExclusiveLogger : public Logger { // A lock-free adapter for loggers that require not to lock the mutex (e.g. RotativePerformanceLogger) // Note: we are using this being extra precautiuos: we don't let these loggers to work with any other external loggers. Hence the Exclusive. public: typedef std::function< void(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages #endif )> LogCallback; void log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, unsigned numberMessages #endif ) override; LogCallback exclusiveCallback; }; // This used to be a static member of MegaApi_impl // However, megacli could not use or test it from there since it // uses the SDK core directly, and not the intermediate layer // So, although globals and singletons are not ideal, moving it here // is one step forwards in tidying that up. // Update: Changing from Global variables to singleton is // for solving the race conditions in multi-threaded environment. // extern ExternalLogger g_externalLogger; // extern ExclusiveLogger g_exclusiveLogger; /* * @brief Get the External Logger object * * @return ExternalLogger& */ ExternalLogger& getExternalLogger(); /* * @brief Get the Exclusive Logger object * * @return ExclusiveLogger& */ ExclusiveLogger& getExclusiveLogger(); inline error logAndReturnError(const error e, const std::string_view msg) { LOG_err << msg; return e; } /** * @brief A generic version of `logAndReturnError` where you can specify how the return value is * instantiated from the error code * * @param returnGenerator A function that can be invoked with `const error` and generates the * returned value * @return The result from invoking returnGenerator(e) */ template auto logAndReturnErrorGeneric(const error e, const std::string_view msg, F&& returnGenerator) -> std::invoke_result_t { LOG_err << msg; return returnGenerator(e); } /** * @brief A helper factory function to generate callables with the same input parameters as * `logAndReturnError` but with a custom return type, generated by resGenerator. * * @example * const auto logAndError = generateLogAndReturnError( * [](const error e) -> std::pair> * { * return {e, {"", 0}}; * }); * return logAndError(API_EARGS, "Invalid handle"); // will return the pair */ template constexpr auto generateLogAndReturnError(F&& resGenerator) -> std::function, error>(error, std::string_view)> { return [gen = std::forward(resGenerator)](const error e, const std::string_view msg) { return logAndReturnErrorGeneric(e, msg, gen); }; } // Logging msg in full if its size msgSize is less than maxLogSize. Otherwise, logging first and // last parts of the msg based on maxLogSize/2. #define MaxDirectMessage(msg, msgSize, maxLogSize) \ ((msgSize) < (maxLogSize) ? DirectMessage((msg), (msgSize)) : \ DirectMessage((msg), (maxLogSize / 2))) \ << ((msgSize) < (maxLogSize) ? "" : "[...]") \ << ((msgSize) < (maxLogSize) ? \ "" : \ DirectMessage((msg) + (msgSize) - (maxLogSize / 2), (maxLogSize / 2))) class JSONLog { public: enum { NONE = 0, CHUNK_RECEIVED = 1, CHUNK_PROCESSING = 1 << 1, CHUNK_CONSUMED = 1 << 2, SENDING = 1 << 3, NONCHUNK_RECEIVED = 1 << 4, }; static uint32_t get() { return mJSONLog; } static void set(uint32_t value) { mJSONLog = value; } private: inline static std::atomic_uint32_t mJSONLog{CHUNK_CONSUMED | SENDING | NONCHUNK_RECEIVED}; }; #define JSON_CHUNK_RECEIVED \ if (JSONLog::get() & JSONLog::CHUNK_RECEIVED) \ LOG_debug #define JSON_CHUNK_PROCESSING \ if (JSONLog::get() & JSONLog::CHUNK_PROCESSING) \ LOG_debug #define JSON_CHUNK_CONSUMED \ if (JSONLog::get() & JSONLog::CHUNK_CONSUMED) \ LOG_debug #define JSON_SENDING \ if (JSONLog::get() & JSONLog::SENDING) \ LOG_debug #define JSON_NONCHUNK_RECEIVED \ if (JSONLog::get() & JSONLog::NONCHUNK_RECEIVED) \ LOG_debug } // namespace sdk-10.11.0/include/mega/mediafileattribute.h000066400000000000000000000141741516266226600210260ustar00rootroot00000000000000/** * @file mega/mediafileattribute.h * @brief Classes for file attributes fetching * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_MEDIAFILEATTRIBUTE_H #define MEGA_MEDIAFILEATTRIBUTE_H 1 #include "types.h" #include "json.h" #include "filesystem.h" #include namespace mega { enum fatype_ids { fa_media = 8, fa_mediaext = 9 }; void xxteaEncrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv = true); void xxteaDecrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv = true); struct MEGA_API MediaFileInfo; struct MEGA_API FileSystemAccess; struct MEGA_API MediaProperties { enum { UNKNOWN_FORMAT = 254, NOT_IDENTIFIED_FORMAT = 255 }; byte shortformat; uint32_t width; uint32_t height; uint32_t fps; uint32_t playtime; std::string containerName; std::string videocodecNames; std::string audiocodecNames; std::string containerFormat; std::string videocodecFormat; std::string audiocodecFormat; uint32_t containerid; uint32_t videocodecid; uint32_t audiocodecid; bool is_VFR; bool no_audio; MediaProperties(); bool operator==(const MediaProperties& o) const; bool isPopulated(); bool isIdentified(); // turn the structure into a string suitable for pfa command static std::string encodeMediaPropertiesAttributes(MediaProperties vp, uint32_t filekey[4]); // extract structure members back out of attributes static MediaProperties decodeMediaPropertiesAttributes(const std::string& attrs, uint32_t filekey[4]); #ifdef USE_MEDIAINFO static const char* supportedformatsMediaInfoAudio(); static const char* supportedformatsMediaInfo(); // return true if the filename extension is one that mediainfoLib can process static bool isMediaFilenameExtAudio(const std::string& ext); static bool isMediaFilenameExt(const std::string& ext); // Open the specified local file with mediainfoLib and get its video parameters. This function fills in the names but not the IDs void extractMediaPropertyFileAttributes(LocalPath& localFilename, FileSystemAccess* fa); // Look up the IDs of the codecs and container, and encode and encrypt all the info into a string with file attribute 8, and possibly file attribute 9. std::string convertMediaPropertyFileAttributes(uint32_t attributekey[4], MediaFileInfo& mediaInfo); // get binary data and synthetic extension ("jpg" or "png") for cover data in ID3v2 tag template static StringPair getCoverFromId3v2(const T& file); #endif std::string serialize(); MediaProperties(const std::string& deserialize); }; #ifdef USE_MEDIAINFO struct MEGA_API MediaFileInfo { struct MediaCodecs { struct shortformatrec { byte shortformatid; unsigned containerid; unsigned videocodecid; unsigned audiocodecid; }; std::map containers; std::map videocodecs; std::map audiocodecs; std::vector shortformats; }; // a set of codec <-> id mappings supplied by Mega bool mediaCodecsRequested; bool mediaCodecsReceived; bool mediaCodecsFailed; uint32_t downloadedCodecMapsVersion; MediaCodecs mediaCodecs; // look up IDs from the various maps unsigned Lookup(const std::string& name, std::map& data, unsigned notfoundvalue); byte LookupShortFormat(unsigned containerid, unsigned videocodecid, unsigned audiocodecid); // In case we don't have the MediaCodecs yet, remember the media attributes until we can add them to the file. struct queuedvp; std::vector< queuedvp > queuedForDownloadTranslation; std::map uploadFileAttributes; // request MediaCodecs from Mega. Only do this the first time we know we will need them. void requestCodecMappingsOneTime(MegaClient* client, const LocalPath& ifSuitableFilename); static void onCodecMappingsReceiptStatic(MegaClient* client, JSON& json, int codecListVersion); void onCodecMappingsReceipt(MegaClient* client, JSON& json, int codecListVersion); void ReadIdRecords(std::map& data, JSON& json); // get the cached media attributes for a file just before sending CommandPutNodes (for a newly uploaded file) void addUploadMediaFileAttributes(UploadHandle fh, std::string* s); // we figured out the properties, now attach them to a file. Queues the action if we don't have the MediaCodecs yet. Works for uploaded or downloaded files. unsigned queueMediaPropertiesFileAttributesForUpload(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, UploadHandle uploadHandle, Transfer*); void sendOrQueueMediaPropertiesFileAttributesForExistingFile(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, NodeHandle fileHandle); // Check if we should retry video property extraction, due to previous failure with older library bool timeToRetryMediaPropertyExtraction(const std::string& fileattributes, uint32_t fakey[4]); MediaFileInfo(); }; struct MediaFileInfo::queuedvp { // for a download it is the handle of the node of the file. For uploads that doesn't exist yet and it is the uploadHandle of the transfer NodeOrUploadHandle handle; // The properties to upload. These still need translation from strings to enums, plus file attribute encoding and encryption with XXTEA MediaProperties vp; // the key to use for XXTEA encryption (which is not the same as the file data key) uint32_t fakey[4]; }; #endif } // namespace #endif sdk-10.11.0/include/mega/megaapp.h000066400000000000000000000421231516266226600165700ustar00rootroot00000000000000/** * @file mega/megaapp.h * @brief Mega SDK callback interface * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_APP_H #define MEGA_APP_H 1 #include "mega/banner.h" #include #include #include // FUSE. #include namespace mega { struct Notification; struct UnifiedSync; class Set; class SetElement; struct PerSyncStats; struct AccountDetails; class MegaClient; class LocalPath; struct BusinessPlan; struct AchievementsDetails; class Sync; struct Product; // callback interface struct MEGA_API MegaApp { MegaClient* client; // a request-level error occurred (other than API_EAGAIN, which will lead to a retry) virtual void request_error(error) { } // request response progress virtual void request_response_progress(m_off_t, m_off_t) { } // prelogin result virtual void prelogin_result(int, string*, string*, error) { } // login result virtual void login_result(error) { } virtual void loggedInStateChanged(sessiontype_t, handle /*me*/, const string& /*email*/) { } // user data result virtual void userdata_result(string*, string*, string*, std::vector&&, Error) {} // user public key retrieval result virtual void pubkey_result(User *) { } // ephemeral session creation/resumption result virtual void ephemeral_result(error) { } virtual void ephemeral_result(handle, const byte*) { } virtual void cancelsignup_result(error) { } // check the reason of being blocked result virtual void whyamiblocked_result(int) { } // account creation virtual void sendsignuplink_result(error) { } virtual void confirmsignuplink2_result(handle, const char*, const char*, error) { } virtual void setkeypair_result(error) { } // account credentials, properties and history virtual void account_details(AccountDetails*, bool, bool, bool, bool, bool, bool) { } virtual void account_details(AccountDetails*, error) { } // query bandwidth quota result virtual void querytransferquota_result(int) { } // sessionid is undef if all sessions except the current were killed virtual void sessions_killed(handle /*sessionid*/, error) { } // node deletion failed (not invoked unless error != API_OK) virtual void unlink_result(handle, error) { } // remove versions result virtual void unlinkversions_result(error) { } // sets have been updated virtual void sets_updated(Set**, int) { } // set-elements have been updated virtual void setelements_updated(SetElement**, int) { } // nodes have been updated virtual void nodes_updated(sharedNode_vector*, int) { } // new actionpackets arrived with a new sequence tag virtual void sequencetag_update(const string&) { } // nodes have been updated virtual void pcrs_updated(PendingContactRequest**, int) { } // users have been added or updated virtual void users_updated(User**, int) { } // alerts have been added or updated virtual void useralerts_updated(UserAlert::Base**, int) { } // the account has been modified (upgraded/downgraded) virtual void account_updated() { } // password change result virtual void changepw_result(error) { } // user attribute update notification virtual void userattr_update(User*, int, const char*) { } // node fetch result virtual void fetchnodes_result(const Error&) { } // nodes now (nearly) current virtual void nodes_current() { } // up to date with API (regarding actionpackets) virtual void catchup_result() { } // notify about a modified key virtual void key_modified(handle, attr_t) { } // notify about cyptographyc security upgrade virtual void upgrading_security() { } // notify about detection of attempt to downgrade ^!keys virtual void downgrade_attack() { } #ifndef NDEBUG // So that tests can make a change as soon as a cloud node is moved. virtual void move_begin(const LocalPath&, const LocalPath&) { }; #endif // ! NDEBUG // node addition has failed virtual void putnodes_result(const Error&, targettype_t, vector&, bool /*targetOverride*/, int /*tag*/, const std::map& /*fileHandles*/ = {}) {} // outgoing pending contact result virtual void setpcr_result(handle, error, opcactions_t) { } // incoming pending contact result virtual void updatepcr_result(error, ipcactions_t) { } // file attribute fetch result virtual void fa_complete(handle, fatype, const char*, uint32_t) { } virtual int fa_failed(handle, fatype, int, error) { return 0; } // file attribute modification result virtual void putfa_result(handle, fatype, error) { } // purchase transactions virtual void enumeratequotaitems_result(const Product&) {} virtual void enumeratequotaitems_result(unique_ptr) {} virtual void enumeratequotaitems_result(error) { } virtual void additem_result(error) { } virtual void checkout_result(const char*, error) { } virtual void submitpurchasereceipt_result(error) { } virtual void creditcardstore_result(error) { } virtual void creditcardquerysubscriptions_result(int, error) {} virtual void creditcardcancelsubscriptions_result(error) {} virtual void getpaymentmethods_result(int, error) {} virtual void copysession_result(string*, error) { } // feedback from user/client virtual void userfeedbackstore_result(error) { } virtual void sendevent_result(error) { } virtual void supportticket_result(error) { } // user invites/attributes virtual void removecontact_result(error) { } virtual void putua_result(error) { } virtual void getua_result(error) { } virtual void getua_result(byte*, unsigned, attr_t) { } virtual void getua_result(unique_ptr, attr_t) {} #ifdef DEBUG virtual void delua_result(error) { } #endif // result of send dev subcommand's command virtual void senddevcommand_result(int) {} virtual void getuseremail_result(string *, error) { } // exported link access result virtual void openfilelink_result(const Error&) { } virtual void openfilelink_result(handle, const byte*, m_off_t, string*, string*, int) { } // pread result virtual dstime pread_failure(const Error&, int, void*, dstime) { return NEVER; } virtual bool pread_data(byte*, m_off_t, m_off_t, m_off_t, m_off_t, void*) { return false; } // event reporting result virtual void reportevent_result(error) { } // clean rubbish bin result virtual void cleanrubbishbin_result(error) { } // get account recovery link result virtual void getrecoverylink_result(error) {} // check account recovery link result virtual void queryrecoverylink_result(error) {} virtual void queryrecoverylink_result(int, const char *, const char *, time_t, handle, const vector *) {} // get private key from recovery link result virtual void getprivatekey_result(error, const byte * = NULL, const size_t = 0) {} // confirm recovery link result virtual void confirmrecoverylink_result(error) {} // convirm cancellation link result virtual void confirmcancellink_result(error) {} // validation of password virtual void validatepassword_result(error) {} // get change email link result virtual void getemaillink_result(error) {} // resend verification email virtual void resendverificationemail_result(error) {}; // reset the verified phone number virtual void resetSmsVerifiedPhoneNumber_result(error) {}; // confirm change email link result virtual void confirmemaillink_result(error) {} // get version info virtual void getversion_result(int, const char*, error) {} // get local SSL certificate virtual void getlocalsslcertificate_result(m_time_t, string*, error){ } #ifdef ENABLE_CHAT // chat-related command's result virtual void chatcreate_result(TextChat *, error) { } virtual void chatinvite_result(error) { } virtual void chatremove_result(error) { } virtual void chaturl_result(string*, error) { } virtual void chatgrantaccess_result(error) { } virtual void chatremoveaccess_result(error) { } virtual void chatupdatepermissions_result(error) { } virtual void chattruncate_result(error) { } virtual void chatsettitle_result(error) { } virtual void chatpresenceurl_result(string*, error) { } virtual void registerpushnotification_result(error) { } virtual void archivechat_result(error) { } virtual void setchatretentiontime_result(error){ } virtual void chats_updated(textchat_map *, int) { } virtual void richlinkrequest_result(string*, error) { } virtual void chatlink_result(handle, error) { } virtual void chatlinkurl_result(handle /*chatid*/, int /*shard*/, string* /*link*/, string* /*ct*/, int /*numPeers*/, m_time_t /*ts*/, bool /*meetingRoom*/, int /*chatOptions*/, const std::vector>* /*smList*/, handle /*callid*/, error) {} virtual void chatlinkclose_result(error) { } virtual void chatlinkjoin_result(error) { } #endif // get mega-achievements virtual void getmegaachievements_result(AchievementsDetails*, error) {} // codec-mappings received virtual void mediadetection_ready() {} // Locally calculated sum of sizes of files stored in cloud has changed virtual void storagesum_changed(int64_t /*newsum*/) {} // global transfer queue updates virtual void file_added(File*) {} virtual void file_removed(File*, const Error&) {} virtual void file_complete(File*) {} struct FileResumeData { File* file{nullptr}; NodeHandle sameNodeHandle; std::string remoteName; NodeHandle parentHandle; std::optional inboxTarget; }; /** * Restores a transfer from serialized data stored in the transfer database. * * This extended version of `file_resume` provides additional metadata through the * `FileResumeData` structure, allowing the client to determine whether the transfer * should be resumed normally or completed via a remote copy operation (when a node * with the same fingerprint already exists in the cloud). * Implementations are expected to: * - Deserialize the File object and its associated transfer information. * - Populate the `FileResumeData` fields (including `sameNodeHandle`, `remoteName`, * and `parentHandle` when a remote copy applies). * @param data Serialized record of the file/transfer from DB. * @param type Output parameter: transfer direction (GET or PUT). * @param dbid Database ID of the serialized entry. * @param outData Structure that receives the reconstructed File and metadata. */ virtual void file_resume(string*, direction_t*, uint32_t, FileResumeData&) {} virtual void transfer_added(Transfer*) { } virtual void transfer_removed(Transfer*) { } virtual void transfer_prepare(Transfer*) { } virtual void transfer_failed(Transfer*, const Error&, dstime = 0) { } virtual void transfer_update(Transfer*) { } virtual void transfer_complete(Transfer*) { } // ----- sync callbacks below, which occur on the syncs thread // ----- (other callbacks occur on the client thread) // sync status updates and events virtual void syncupdate_stateconfig(const SyncConfig&) {} virtual void syncupdate_stats(handle /*backupId*/, const PerSyncStats&) {} virtual void syncupdate_syncing(bool) { } virtual void syncupdate_scanning(bool) { } virtual void syncupdate_stalled(bool) { } virtual void syncupdate_conflicts(bool) { } virtual void syncupdate_totalstalls(bool) { } virtual void syncupdate_totalconflicts(bool) { } virtual void syncupdate_treestate(const SyncConfig &, const LocalPath&, treestate_t, nodetype_t) { } virtual bool isSyncStalledChanged() { return false; } // flag for syncupdate_totalstalls or syncupdate_totalstalls is set // after a root node of a sync changed its path virtual void syncupdate_remote_root_changed(const SyncConfig &) { } // after all sync configs have been loaded on startup virtual void syncs_restored(SyncError) { } // after all syncs have been disabled, eg due to overquota virtual void syncs_disabled(SyncError) { } // the sync could be auto-loaded on start, or one the user added virtual void sync_added(const SyncConfig&) {} // after a sync has been removed virtual void sync_removed(const SyncConfig&) {} // ----- that's the end of the sync callbacks, which occur on the syncs thread // ----- (other callbacks occur on the client thread) // Notify fatal errors (ie. DB, node unserialization, ...) to apps virtual void notifyError(const char*, ErrorReason) { } // reload forced automatically by server virtual void reloading() { } // wipe all users, nodes and shares virtual void clearing() { } // failed request retry notification virtual void notify_retry(dstime, retryreason_t) { } virtual void notify_dbcommit() { } virtual void notify_storage(int) { } virtual void notify_business_status(BizStatus) { } // account confirmation via signup link virtual void notify_confirmation(const char* /*email*/) { } // account confirmation after signup link -> user, email have been confirmed virtual void notify_confirm_user_email(handle /*user*/, const char* /*email*/) { } // network layer disconnected virtual void notify_disconnect() { } // HTTP request finished virtual void http_result(error, int, byte*, m_off_t) {} // Timer ended virtual void timer_result(error) { } // contact link create virtual void contactlinkcreate_result(error, handle) { } // contact link query virtual void contactlinkquery_result(error, handle, string*, string*, string*, string*) { } // contact link delete virtual void contactlinkdelete_result(error) { } // multi-factor authentication setup virtual void multifactorauthsetup_result(string*, error) { } // multi-factor authentication get virtual void multifactorauthcheck_result(int) { } // multi-factor authentication disable virtual void multifactorauthdisable_result(error) { } // fetch time zone virtual void fetchtimezone_result(error, vector*, vector*, int) { } // keep me alive command for mobile apps virtual void keepmealive_result (error) { } // get the current PSA virtual void getpsa_result (error, int, string*, string*, string*, string*, string*, string*) { } // result of the user alert acknowledge request virtual void acknowledgeuseralerts_result(error) { } // get info about a folder link virtual void folderlinkinfo_result(error, handle , handle, string*, string* , m_off_t, uint32_t , uint32_t , m_off_t , uint32_t) {} // result of sms verification commands virtual void smsverificationsend_result(error) { } virtual void smsverificationcheck_result(error, string*) { } // result of get country calling codes command virtual void getcountrycallingcodes_result(error, map>*) { } virtual void getmiscflags_result(error) { } virtual void backupput_result(const Error&, handle /*backup id*/) { } virtual void getbanners_result(error) { } virtual void getbanners_result(vector&&) {} virtual void dismissbanner_result(error) { } // provides the per mil progress of a long-running API operation or -1 if there isn't any operation in progress virtual void reqstat_progress(int) { } virtual void notify_creditCardExpiry() { } virtual ~MegaApp() { } // External drive notifications virtual void drive_presence_changed(bool /*appeared*/, const LocalPath& /*driveRoot*/) {} // Called when a mount has been added, disabled, enabled or removed. virtual void onFuseEvent(const fuse::MountEvent&) { } virtual void notify_network_activity(int /* networkActivityChannel */, int /* networkActivityType */, int /* code */) {} }; } // namespace #endif sdk-10.11.0/include/mega/megaclient.h000066400000000000000000004246621516266226600173020ustar00rootroot00000000000000/** * @file mega/megaclient.h * @brief Client access engine core logic * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGACLIENT_H #define MEGACLIENT_H 1 #include "account.h" #include "backofftimer.h" #include "canceller.h" #include "db.h" #include "drivenotify.h" #include "filefingerprint.h" #include "gfx.h" #include "http.h" #include "json.h" #include "mediafileattribute.h" #include "name_collision.h" #include "nodemanager.h" #include "pendingcontactrequest.h" #include "pubkeyaction.h" #include "pwm_file_parser.h" #include "request.h" #include "setandelement.h" #include "sharenodekeys.h" #include "sync.h" #include "transfer.h" #include "transferstats.h" #include "treeproc.h" #include "user.h" #include "useralerts.h" // FUSE support. #include #include #include #include namespace mega { class Logger; struct NetworkConnectivityTestResults; class MEGA_API FetchNodesStats { public: enum { MODE_DB = 0, MODE_API = 1, MODE_NONE = 2 }; enum { TYPE_ACCOUNT = 0, TYPE_FOLDER = 1, TYPE_NONE = 2 }; enum { API_CACHE = 0, API_NO_CACHE = 1, // use this for DB mode API_NONE = 2 }; FetchNodesStats(); void init(); void toJsonArray(string *json); ////////////////// // General info // ////////////////// int mode; // DB = 0, API = 1 int cache; // no-cache = 0, no-cache = 1 int type; // Account = 0, Folder = 1 dstime startTime; // startup time (ds) /** * \brief Number of nodes in the cached filesystem * * From DB: number on nodes in the local database * From API: number of nodes in the response to the fetchnodes command */ long long nodesCached; /** * @brief Number of nodes in the current filesystem, after the reception of action packets */ long long nodesCurrent; /** * @brief Number of action packets to complete the cached filesystem * * From DB: Number of action packets to complete the local cache * From API: Number of action packets to complete the server-side cache */ int actionPackets; //////////// // Errors // //////////// /** * @brief Number of error -3 or -4 received during the process (including cs and sc requests) */ int eAgainCount; /** * @brief Number of HTTP 500 errors received during the process (including cs and sc requests) */ int e500Count; /** * @brief Number of other errors received during the process (including cs and sc requests) * * The most common source of these errors are connectivity problems (no Internet, timeouts...) */ int eOthersCount; //////////////////////////////////////////////////////////////////// // Time elapsed until different steps since the startup time (ds) // //////////////////////////////////////////////////////////////////// /** * @brief Time until the first byte read * * From DB: time until the first record read from the database * From API: time until the first byte read in response to the fetchnodes command (errors excluded) */ dstime timeToFirstByte; /** * @brief Time until the last byte read * * From DB: time until the last record is read from the database * From API: time until the whole response to the fetchnodes command has been received */ dstime timeToLastByte; /** * @brief Time until the cached filesystem is ready * * From DB: time until the database has been read and processed * From API: time until the fetchnodes command is processed */ dstime timeToCached; /** * @brief Time until the filesystem is ready to be used * * From DB: this time is the same as timeToCached * From API: time until action packets have been processed * It's needed to wait until the reception of action packets due to * server-side caches. */ dstime timeToResult; /** * @brief Time until synchronizations have been resumed * * This involves the load of the local cache and the scan of known * files. Files that weren't cached are scanned later. */ dstime timeToSyncsResumed; /** * @brief Time until the filesystem is current * * From DB: time until action packets have been processed * From API: this time is the same as timeToResult */ dstime timeToCurrent; /** * @brief Time until the resumption of transfers has finished * * The resumption of transfers is done after the filesystem is current */ dstime timeToTransfersResumed; }; /** * @brief A helper class that keeps the SN (sequence number) members in sync and well initialized. * The server-client sequence number is updated along with every batch of actionpackets received from API * It is used to commit the open transaction in DB, so the account's local state is persisted. Upon resumption, * the scsn is sent to API, which provides the possible updates missing while the client was not running */ class SCSN { // scsn that we are sending in sc requests (ie, where we are up to with the persisted node data) char scsn[12]; // sc inconsistency: stop querying for action packets bool stopsc = false; public: bool setScsn(JSON*); void setScsn(handle); void stopScsn(); bool ready() const; bool stopped() const; const char* text() const; handle getHandle() const; friend std::ostream& operator<<(std::ostream& os, const SCSN& scsn); SCSN(); void clear(); }; std::ostream& operator<<(std::ostream &os, const SCSN &scsn); struct SyncdownContext { bool mBackupActionsPerformed = false; bool mBackupForeignChangeDetected = false; }; // SyncdownContext class ScDbStateRecord : public Cacheable { public: string seqTag; bool serialize(string* data) const override; static ScDbStateRecord unserialize(const std::string& data); }; // Class to help with upload of file attributes struct UploadWaitingForFileAttributes { struct FileAttributeValues { handle fileAttributeHandle = UNDEF; bool valueIsSet = false; }; mapWithLookupExisting pendingfa; // The transfer must always be known, so we can check for cancellation Transfer* transfer = nullptr; // This flag is set true if its data upload completes, and we removed it from transfers[] // In which case, this is now the "owning" object for the transfer bool uploadCompleted = false; }; // Class to help with upload of file attributes // One entry for each active upload that has file attribute involvement // Should the transfer be cancelled, this data structure is easily cleaned. struct FileAttributesPending : public mapWithLookupExisting { void setFileAttributePending(UploadHandle h, fatype type, Transfer* t, bool alreadyavailable = false) { auto& entry = operator[](h); entry.pendingfa[type].valueIsSet = alreadyavailable; assert(entry.transfer == t || entry.transfer == nullptr); entry.transfer = t; } }; class MegaClient; class MEGA_API KeyManager { public: KeyManager(MegaClient& client) : mClient(client) {} // it's called to initialize the ^!keys attribute, since it does not exist yet // prRSA is expected in base64 and 4 Ints format: pqdu void init(const string& prEd25519, const string& prCu25519, const string& prRSA); // it derives master key and sets mKey void setKey(const SymmCipher& masterKey); // decrypts and decodes the ^!keys attribute bool fromKeysContainer(const string& data); // encodes and encrypts the ^!keys attribute string toKeysContainer(); // --- Getters / Setters ---- uint32_t generation() const; string privEd25519() const; string privCu25519() const; void setPostRegistration(bool postRegistration); bool getPostRegistration() const; bool addPendingOutShare(handle sharehandle, std::string uid); bool addPendingInShare(std::string sharehandle, handle userHandle, std::string encrytedKey); bool removePendingOutShare(handle sharehandle, std::string uid); bool removePendingInShare(std::string shareHandle); bool addShareKey(handle sharehandle, std::string shareKey, bool sharedSecurely = false); string getShareKey(handle sharehandle) const; bool isShareKeyTrusted(handle sharehandle) const; bool isShareKeyInUse(handle sharehandle) const; void setSharekeyInUse(handle sharehandle, bool sent); // Clears, if set, the in-use bit of the sharekeys no longer used. void syncSharekeyInUseBit(); // return empty string if the user's credentials are not verified (or if fail to encrypt) std::string encryptShareKeyTo(handle userhandle, std::string shareKey); // return empty string if the user's credentials are not verified (or if fail to decrypt) std::string decryptShareKeyFrom(handle userhandle, std::string shareKey); void setAuthRing(std::string authring); void setAuthCU255(std::string authring); void setPrivRSA(std::string privRSA); std::string getPrivRSA(); bool promotePendingShares(); bool isUnverifiedOutShare(handle nodeHandle, const string& uid) const; bool isUnverifiedInShare(handle nodeHandle, handle userHandle); void loadShareKeys(); void commit(std::function applyChanges, std::function completion = nullptr); void reset(); // returns a formatted string, for logging purposes string toString() const; // Returns true if the warnings related to shares with non-verified contacts are enabled. bool getContactVerificationWarning(); // Enable/disable the warnings for shares with non-verified contacts. void setContactVerificationWarning(bool enabled); // this method allows to change the manual verification feature-flag for testing purposes void setManualVerificationFlag(bool enabled) { mManualVerification = enabled; } // query whether manual verification is required. bool getManualVerificationFlag() const { return mManualVerification; } protected: std::deque, std::function>> nextQueue; std::deque, std::function>> activeQueue; void nextCommit(); void tryCommit(Error e, std::function completion); void updateAttribute(std::function completion); private: // Tags used by TLV blob enum { TAG_VERSION = 1, TAG_CREATION_TIME = 2, TAG_IDENTITY = 3, TAG_GENERATION = 4, TAG_ATTR = 5, TAG_PRIV_ED25519 = 16, TAG_PRIV_CU25519 = 17, TAG_PRIV_RSA = 18, TAG_AUTHRING_ED25519 = 32, TAG_AUTHRING_CU25519 = 33, TAG_SHAREKEYS = 48, TAG_PENDING_OUTSHARES = 64, TAG_PENDING_INSHARES = 65, TAG_BACKUPS = 80, TAG_WARNINGS = 96, }; // Bit position for different flags for each sharekey. Bits 2 to 7 reserved for future usage. enum ShareKeyFlagsId { TRUSTED = 0, // If the sharekey is trusted INUSE = 1, // If there is an active outshare or folder-link using the sharekey }; // Bitmap with flags for each sharekey. The field is 1 byte size in the attribute. // See used bits and flag meaning in "ShareKeyFlagsId" enumeration. typedef std::bitset<8> ShareKeyFlags; static const uint8_t IV_LEN = 12; static const std::string SVCRYPTO_PAIRWISE_KEY; MegaClient& mClient; // key used to encrypt/decrypt the ^!keys attribute (derived from Master Key) SymmCipher mKey; // true if user needs to manually verify contact's credentials to encrypt/decrypt share keys bool mManualVerification = false; // enable / disable logs related to the contents of ^!keys static const bool mDebugContents = false; // true when the account is being created -> don't show warning to user "updading security", // false when the account is being upgraded to ^!keys -> show the warning bool mPostRegistration = false; // if the last known value of generation is greater than a value received in a ^!keys, // then a rogue API could be tampering with the attribute bool mDowngradeAttack = false; uint8_t mVersion = 0; uint32_t mCreationTime = 0; handle mIdentity = UNDEF; uint32_t mGeneration = 0; string mAttr; string mPrivEd25519, mPrivCu25519, mPrivRSA; string mAuthEd25519, mAuthCu25519; string mBackups; string mOther; // maps node handle of the shared folder to a pair of sharekey bytes and sharekey flags. map> mShareKeys; // maps node handle to the target users (where value can be a user's handle in B64 or the email address) map> mPendingOutShares; // maps base64 node handles to pairs of source user handle and share key map> mPendingInShares; // warnings as stored as a key-value map map mWarnings; // decode data from the decrypted ^!keys attribute and stores values in `km` // returns false in case of unserializatison isues static bool unserialize(KeyManager& km, const string& keysContainer); // prepares the header for a new serialized record of type 'tag' and 'len' bytes string tagHeader(const byte tag, size_t len) const; // Serialize pairs of tags and values as Length+Tag+Lengh+Value. // warnings and pending inshares are encoded like that when serialized. static bool deserializeFromLTLV(const string& blob, map& data); static string serializeToLTLV(const map& data); // encode data from the decrypted ^!keys attribute string serialize() const; string serializeShareKeys() const; static bool deserializeShareKeys(KeyManager& km, const string& blob); static string shareKeysToString(const KeyManager& km); string serializePendingOutshares() const; static bool deserializePendingOutshares(KeyManager& km, const string& blob); static string pendingOutsharesToString(const KeyManager& km); string serializePendingInshares() const; static bool deserializePendingInshares(KeyManager& km, const string& blob); static string pendingInsharesToString(const KeyManager& km); string serializeBackups() const; static bool deserializeBackups(KeyManager& km, const string& blob); string serializeWarnings() const; static bool deserializeWarnings(KeyManager& km, const string& blob); static string warningsToString(const KeyManager& km); std::string computeSymmetricKey(handle user); // validates data in `km`: ie. downgrade attack, tampered keys... bool isValidKeysContainer(const KeyManager& km); void updateValues(KeyManager& km); // decodes the RSA private key and sets it at MegaClient::asymkey // returns false if it doesn't match the current key or if failed to set the key bool decodeRSAKey(); // update the corresponding authring with `value`, both in KeyManager and MegaClient::mAuthrings void updateAuthring(attr_t at, std::string &value); // update sharekeys (incl. trust). It doesn't purge non-existing items void updateShareKeys(map > &shareKeys); // true if the credentials of this user require verification bool verificationRequired(handle userHandle); }; struct DynamicMessageNotification { int64_t id = 0; std::string title; std::string description; std::string imageName; // main notification image std::string iconName; std::string imagePath; int64_t start = 0; int64_t end = 0; bool showBanner = false; std::map callToAction1; std::map callToAction2; std::map> renderModes; }; class MEGA_API MegaClient { public: // own identity handle me; string uid; // all users user_map users; // encrypted master key string k; // version of the account int accountversion; // salt of the account (for v2 accounts) string accountsalt; // timestamp of the creation of the account m_time_t accountsince; // Global Multi-Factor Authentication enabled bool gmfa_enabled; // Server-Side Rubbish-bin Scheduler enabled (autopurging) bool ssrs_enabled; // Account has VOIP push enabled (only for Apple) bool aplvp_enabled; // Use new format to generate Mega links bool mNewLinkFormat = false; // Don't start showing the cookie banner until API says so bool mCookieBannerEnabled = false; // Consider an account as new if it was created less than X days earlier (right now it's 30days; received in "ug":"na") bool accountIsNew = false; // AB Test flags ThreadSafeKeyValue mABTestFlags; ThreadSafeKeyValue mFeatureFlags; private: // Pro Flexi plan is enabled bool mProFlexi = false; public: bool isProFlexi() const { return mProFlexi; } Error sendABTestActive(const char* flag, CommandABTestActive::Completion completion); // 2 = Opt-in and unblock SMS allowed 1 = Only unblock SMS allowed 0 = No SMS allowed -1 = flag was not received SmsVerificationState mSmsVerificationState; // the verified account phone number, filled in from 'ug' string mSmsVerifiedPhone; // pseudo-random number generator PrnGen rng; bool ephemeralSession = false; bool ephemeralSessionPlusPlus = false; static TypeOfLink validTypeForPublicURL(nodetype_t type); static string publicLinkURL(bool newLinkFormat, TypeOfLink type, handle ph, const char *key); string getWritableLinkAuthKey(handle node); // method to check if a timestamp (m_time_t) is valid or not static bool isValidMegaTimeStamp(m_time_t val) { return val > mega_invalid_timestamp; } #ifdef ENABLE_CHAT // all chats textchat_map chats; #endif // process API requests and HTTP I/O void exec(); // wait for I/O or other events int wait(); // splitted implementation of wait() for a better thread management int preparewait(); int dowait(); int checkevents(); // abort exponential backoff bool abortbackoff(bool = true); // ID tag of the next request int nextreqtag(); // corresponding ID tag of the currently executing callback int restag; // ephemeral session support void createephemeral(); void createephemeralPlusPlus(); void resumeephemeral(handle, const byte*, int = 0); void resumeephemeralPlusPlus(const std::string& session); void cancelsignup(); // full account confirmation/creation support string sendsignuplink2(const char*, const char *, const char*, int ctag = 0); void resendsignuplink2(const char*, const char *); void confirmsignuplink2(const byte*, unsigned); void setkeypair(); // prelogin: e-mail void prelogin(const char* email, CommandPrelogin::Completion completion = nullptr); // user login: e-mail, pwkey void login(const char*, const byte*, const char* = NULL, CommandLogin::Completion completion = nullptr); // user login: e-mail, password, salt void login2(const char*, const char*, const string *, const char* = NULL, CommandLogin::Completion completion = nullptr); // user login: e-mail, derivedkey, 2FA pin void login2(const char*, const byte*, const char* = NULL, CommandLogin::Completion completion = nullptr); // user login: e-mail, pwkey, emailhash void fastlogin(const char*, const byte*, uint64_t, CommandLogin::Completion completion = nullptr); // session login: binary session, bytecount void login(string session, CommandLogin::Completion completion = nullptr); // handle login result, and allow further actions when successful void loginResult(CommandLogin::Completion completion, error e, std::function onLoginOk = nullptr); // check password error validatepwd(const char* pswd); bool validatepwdlocally(const char* pswd); // get user data void getuserdata( int tag, std::function&&, error)> = nullptr); // get miscelaneous flags void getmiscflags(); // get the public key of an user void getpubkey(const char* user); // check if logged in (avoid repetitive calls <-- requires call to Cryptopp::InverseMod(), which is slow) sessiontype_t loggedin(); // provide state by change callback void reportLoggedInChanges(); sessiontype_t mLastLoggedInReportedState = NOTLOGGEDIN; handle mLastLoggedInMeHandle = UNDEF; string mLastLoggedInMyEmail; // check the reason of being blocked void whyamiblocked(); // sets block state: stops querying for action packets, pauses transfer & removes transfer slot availability void block(bool fromServerClientResponse = false); // unsets block state void unblock(); // dump current session int dumpsession(string&); // create a copy of the current session. EACCESS for not fully confirmed accounts error copysession(); // resend the verification email to the same email address as it was previously sent to void resendverificationemail(); // reset the verified phone number void resetSmsVerifiedPhoneNumber(); // get the data for a session transfer // the caller takes the ownership of the returned value string sessiontransferdata(const char*, string*); // Kill session id void killsession(handle session); void killallsessions(); // extract public handle and key from a public file/folder link error parsepubliclink(const char* link, handle& ph, byte* extractedKey, TypeOfLink type); // open the SC database and get the SCSN from it void checkForResumeableSCDatabase(); // set folder link: node, key. authKey is the authentication key to be able to write into the folder error folderaccess(const char* folderlink, const char* authKey, const bool tryToResumeFolderLinkFromCache = false); // open exported file link (op=0 -> download, op=1 fetch data) void openfilelink(handle ph, const byte* fileKey); // decrypt password-protected public link // the caller takes the ownership of the returned value in decryptedLink parameter error decryptlink(const char* link, const char* pwd, string *decryptedLink); // encrypt public link with password // the caller takes the ownership of the returned value error encryptlink(const char* link, const char* pwd, string *encryptedLink); // change login password error changepw(const char *password, const char *pin = NULL); // invoked at the moment we actually send `f` void resetScForFetchnodes(); // load all trees: nodes, shares, contacts void fetchnodes(bool nocache, bool loadSyncs, bool reloadingMidSession); // fetchnodes stats FetchNodesStats fnstats; // check existence and integrity of keys and signatures, initialize if missing void initializekeys(); // to be called after resumption from cache (user attributes loaded) void loadAuthrings(); // load cryptographic keys for contacts: RSA, Ed25519, Cu25519 void fetchContactsKeys(); // fetch keys related to authrings for a given contact void fetchContactKeys(User *user); // track a public key in the authring for a given user error trackKey(attr_t keyType, handle uh, const std::string &key); // track the signature of a public key in the authring for a given user error trackSignature(attr_t signatureType, handle uh, const std::string &signature); // update the authring if needed on the server and manage the deactivation of the temporal authring error updateAuthring(AuthRing *authring, attr_t authringType, bool temporalAuthring, handle updateduh); // set the Ed25519 public key as verified for a given user in the authring (done by user manually by comparing hash of keys) error verifyCredentials(handle uh, std::function); // reset the authentication method of Ed25519 key from Fingerprint-verified to Seen for a given user error resetCredentials(handle uh, std::function); // check credentials are verified for a given user bool areCredentialsVerified(handle uh); // retrieve user details void getaccountdetails(std::shared_ptr, bool, bool, bool, bool, bool, bool, int source = -1); // Get user storage information. void getstorageinfo(std::function completion); // check if the available bandwidth quota is enough to transfer an amount of bytes void querytransferquota(m_off_t size); static constexpr char NODE_ATTRIBUTE_DESCRIPTION[] = "des"; static constexpr char NODE_ATTRIBUTE_TAGS[] = "t"; static constexpr char NODE_ATTR_SEN[] = "sen"; static constexpr char NODE_ATTR_LABEL[] = "lbl"; static constexpr char TAG_DELIMITER = NodeSearchFilter::TAG_DELIMITER; static constexpr uint32_t MAX_NUMBER_TAGS = 10; static constexpr uint32_t MAX_TAGS_SIZE = 3000; // update node attributes error setattr(std::shared_ptr, attr_map&& updates, CommandSetAttr::Completion&& c, bool canChangeVault); // prefix and encrypt attribute json static void makeattr(SymmCipher*, string*, const char*, int = -1); // convenience version of the above (frequently we are passing a NodeBase's attrstring) static void makeattr(SymmCipher*, const std::unique_ptr&, const char*, int = -1); error addTagToNode(std::shared_ptr node, const std::string& tag, CommandSetAttr::Completion&& c); static std::vector getNodeTags(const std::string& delimitedTags); std::vector getNodeTags(std::shared_ptr node); /* * @brief * Get all node tags below a specified node. * * @param cancelToken * A token that can be used to terminate the query's execution prematurely. * * @param handle * A handle specifying which node we want to list tags below. * * If undefined, the query will list tags below all root nodes. * * @param pattern * An optional pattern that can be used to filter which tags we list. * * @returns * std::nullopt on failure. * std::set on success. */ auto getNodeTagsBelow(CancelToken cancelToken, NodeHandle handle, const std::string& pattern = {}) -> std::optional>; auto getNodeTagsBelow(NodeHandle handle, const std::string& pattern = {}) -> std::optional>; error removeTagFromNode(std::shared_ptr node, const std::string& tag, CommandSetAttr::Completion&& c); error updateTagNode(std::shared_ptr, const std::string& newTag, const std::string& oldTag, CommandSetAttr::Completion&& c); public: // check node access level int checkaccess(Node*, accesslevel_t); // check if a move operation would succeed error checkmove(Node*, Node*); // delete node error unlink(Node*, bool keepversions, int tag, bool canChangeVault, std::function&& resultFunction = nullptr); void unlinkOrMoveBackupNodes(NodeHandle backupRootNode, NodeHandle destination, std::function completion); // delete all versions void unlinkversions(); // move node to new parent folder error rename(std::shared_ptr, std::shared_ptr, syncdel_t, NodeHandle prevparenthandle, const char *newName, bool canChangeVault, CommandMoveNode::Completion&& c); // create folder node error createFolder(std::shared_ptr parent, const char* name, int rTag); // rename a node (i.e. change the node attribute 'n') error renameNode(NodeHandle nh, const char* newName, CommandSetAttr::Completion&& cbRequest); // remove node error removeNode(NodeHandle nh, bool keepVersions, int rTag); // Queue commands (if needed) to remvoe any outshares (or pending outshares) below the specified node void removeOutSharesFromSubtree(std::shared_ptr n, int tag); // start/stop/pause file transfer bool startxfer(direction_t, File*, TransferDbCommitter&, bool skipdupes, bool startfirst, bool donotpersist, VersioningOption, error* cause, int tag, m_off_t availableDiskSpace = 0); void stopxfer(File* f, TransferDbCommitter* committer); void pausexfers(direction_t, bool pause, bool hard, TransferDbCommitter& committer); error transferRemoteCopy(File* file, std::shared_ptr sameNode, const string& name, std::shared_ptr parent, int tag, std::optional overridenFp, std::optional inboxTarget); // maximum number of connections per transfer static const unsigned MAX_NUM_CONNECTIONS = 100; // set max connections per transfer void setmaxconnections(direction_t, int); // updates business status void setBusinessStatus(BizStatus newBizStatus); // updates block boolean void setBlocked(bool value); // enqueue/abort direct read void pread(Node* node, m_off_t offset, m_off_t count, DirectRead::Callback&& callback); void pread(handle handle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, DirectRead::Callback&& callback, bool isPublicHandle = true, const char* privateAuth = NULL, const char* publicAuth = NULL, const char* chatAuth = NULL); void pread(Node* node, m_off_t offset, m_off_t count, void* appData); void pread(handle handle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, void* appData, bool isPublicHandle = true, const char* privateAuth = NULL, const char* publicAuth = NULL, const char* chatAuth = NULL); void preadabort(Node* node, m_off_t offset = -1, m_off_t count = -1); void preadabort(handle handle, m_off_t offset = -1, m_off_t count = -1); // pause flags bool xferpaused[2]; MegaClientAsyncQueue mAsyncQueue; // number of parallel connections per transfer (PUT/GET) unsigned char connections[2]; // helpfer function for preparing a putnodes call for new node error putnodes_prepareOneFile(NewNode* newnode, Node* parentNode, const char* utf8Name, const UploadToken& binaryUploadToken, const byte* theFileKey, const char* megafingerprint, const char* fingerprintOriginal, std::function addNodeAttrsFunc = nullptr, std::function addFileAttrsFunc = nullptr); // helper function for preparing a putnodes call for new folders void putnodes_prepareOneFolder(NewNode* newnode, std::string foldername, bool canChangeVault, std::function addAttrs = nullptr); // static version to be used from worker threads, which cannot rely on the MegaClient::tmpnodecipher as SymCipher (not thread-safe)) static void putnodes_prepareOneFolder(NewNode* newnode, std::string foldername, PrnGen& rng, SymmCipher &tmpnodecipher, bool canChangeVault, std::function addAttrs = nullptr); // helper function for preparing a putnodes call for copy operations void putnodes_prepareCopy(std::vector& nn, unsigned& nc, const nodetype_t type, const handle nodehandle, const handle parenthandle, const string& nodekey, const AttrMap& attrs, const bool resetSensitive, const bool isPublic); /** * @brief Updates the modification time (mtime) of a node. * @param node Shared pointer to the Node to be modified * @param newMtime The new modification time * @param completion A callback function that will be invoked when the operation completes * @return `true` if the command was successfully sent to the API, `false` if an error occurred * before the command could be sent. */ error updateNodeMtime(std::shared_ptr node, const m_time_t newMtime, std::function&& completion); /** * @brief Updates the fingerprint attribute ('c') of a node. * * The fingerprint attribute encodes CRC and mtime. This can be used to fix metadata-only * mismatches without re-uploading file data. * * @param node Shared pointer to the Node to be modified * @param newFingerprint The fingerprint to serialize into the node's attribute * @param completion A callback function that will be invoked when the operation completes */ error updateNodeFingerprint(std::shared_ptr node, const FileFingerprint& newFingerprint, std::function&& completion); // add nodes to specified parent node (complete upload, copy files, make // folders) void putnodes(NodeHandle, VersioningOption vo, vector&&, const char*, int tag, bool canChangeVault, std::string customerIpPort = {}, CommandPutNodes::Completion&& completion = nullptr, std::optional pitag = std::nullopt); // send files/folders to user void putnodes(const char*, vector&&, int tag, CommandPutNodes::Completion&& completion = nullptr); void putFileAttributes(handle h, fatype t, const std::string& encryptedAttributes, int tag); // attach file attribute to upload or node handle error putfa(NodeOrUploadHandle, fatype, SymmCipher*, int tag, std::unique_ptr); // move as many as possible from pendingfa to activefa void activatefa(); // queue file attribute retrieval error getfa(handle h, string *fileattrstring, const string &nodekey, fatype, int = 0); // notify delayed upload completion subsystem about new file attribute void checkfacompletion(UploadHandle, Transfer* = NULL, bool uploadCompleted = false); // attach/update/delete a user attribute using encryption void putua(attr_t at, string_map&& records, int ctag = -1, handle lastPublicHandle = UNDEF, int phtype = 0, int64_t ts = 0, std::function completion = nullptr); // attach/update/delete a user attribute void putua(attr_t at, const byte* av = NULL, unsigned avl = 0, int ctag = -1, handle lastPublicHandle = UNDEF, int phtype = 0, int64_t ts = 0, std::function completion = nullptr); // attach/update multiple versioned user attributes at once void putua(userattr_map *attrs, int ctag = -1, std::function completion = nullptr); // queue a user attribute retrieval bool getua(User* u, const attr_t at = ATTR_UNKNOWN, int ctag = -1, CommandGetUA::CompletionErr completionErr = nullptr, CommandGetUA::CompletionBytes completionBytes = nullptr, CommandGetUA::CompletionTLV completionTLV = nullptr); // queue a user attribute retrieval (for non-contacts) void getua(const char* email_handle, const attr_t at = ATTR_UNKNOWN, const char *ph = NULL, int ctag = -1, CommandGetUA::CompletionErr ce = nullptr, CommandGetUA::CompletionBytes cb = nullptr, CommandGetUA::CompletionTLV ctlv = nullptr); // retrieve the email address of a user void getUserEmail(const char* userID); // Set email for an user void setEmail(User* u, const std::string& email); // // Account upgrade to V2 // public: void saveV1Pwd(const char* pwd); private: void upgradeAccountToV2(const string& pwd, int ctag, std::function completion); // temporarily stores v1 account password, to allow automatic upgrade to v2 after successful (full-)login unique_ptr> mV1PswdVault; // -------- end of Account upgrade to V2 public: #ifdef DEBUG // queue a user attribute removal void delua(const char* an); #endif // send dev command for testing void senddevcommand(const char* command, const char* email, long long q = 0, int bs = 0, int us = 0, const char* abs_c = nullptr); // delete or block an existing contact error removecontact(const char*, visibility_t = HIDDEN, CommandRemoveContact::Completion completion = nullptr); // Migrate the account to start using the new ^!keys attr. void upgradeSecurity(std::function completion); // Set the flag to enable/disable warnings when sharing with a non-verified contact. void setContactVerificationWarning(bool enabled, std::function completion = nullptr); // Creates a new share key for the node if there is no share key already created. void openShareDialog(Node* n, std::function completion); // add/remove/update outgoing share void setshare(std::shared_ptr, const char*, accesslevel_t, bool writable, const char*, int tag, std::function completion); void setShareCompletion(Node*, User*, accesslevel_t, bool writable, const char*, int tag, std::function completion); // Add/delete/remind outgoing pending contact request void setpcr(const char*, opcactions_t, const char* = NULL, const char* = NULL, handle = UNDEF, CommandSetPendingContact::Completion completion = nullptr); void updatepcr(handle, ipcactions_t, CommandUpdatePendingContact::Completion completion = nullptr); // export node link or remove existing exported link for this node error exportnode(std::shared_ptr, int, m_time_t, bool writable, bool megaHosted, int tag, std::function completion); void requestPublicLink(Node* n, int del, m_time_t ets, bool writable, bool megaHosted, int tag, CommandSetPH::CompletionType completion); // auxiliar method to add req // add timer error addtimer(TimerWithBackoff *twb); #ifdef ENABLE_SYNC /** * @brief Check if a given node is syncable * @param remotenode We want to validate if a sync can be enabled using this node as remote * root. * @param excludeSelf if true, we will asume the given node is already the root of a sync and we * want to validate if it can still be synced. In other words, while iterating active syncs we * will scape the one having remotenode as root. * @return A std::pair with information about the API and Sync errors. API_OK, NO_SYNC_ERROR if * the node is syncable. */ std::pair isnodesyncable(std::shared_ptr remotenode, const bool excludeSelf = false); /** * @brief Checks if the given remotenode is related with any active sync * * @param remotenode The node to check * @param excludeSelf if true, we will asume the given node is already the root of an active * sync. This sync will be skipped during the evaluation. * @return NO_SYNC_ERROR if the given node is not related with any of the active syncs. * Otherwise: * - ACTIVE_SYNC_SAME_PATH: If the given node is already the root of an active sync * - ACTIVE_SYNC_BELOW_PATH: If the given node contains an active sync * - ACTIVE_SYNC_ABOVE_PATH: If the given node is contained in an active sync */ SyncError anyActiveSyncAboveOrBelow(const Node& remotenode, const bool excludeSelf); /** * @brief Checks whether the current user has full access to the given node. * * This will be always the case for nodes that are not part of an inbound shared node. If the * node is or is contained in an inbound shared node, this function checks that the user has * full access to the node being shared. * * @param remotenode The node to check * @return true if the current user has full access to the node, false otherwise */ bool userHasRestrictedAccessToNode(const Node& remotenode); /** * @brief Checks if a local path is suitable for synchronization without conflicting with * existing syncs. * * This method determines whether the provided `newPath` can be used for a new synchronization * task. It ensures that the path does not overlap with any existing active sync configurations, * thereby preventing conflicts arising from nested or overlapping sync directories. * * @param newPath The local path to be checked for sync compatibility. * @param excludeBackupId A backup ID to exclude from the check. This is useful when * updating an existing sync, allowing it to bypass itself during the validation. Pass `UNDEF` * if you don't want to skip any sync during the validation. * * @return A `std::pair` containing: * - `error`: An error code indicating the result of the check. * - `SyncError`: A detailed sync error code providing more context. * * The possible returned error combinations are: * - `API_OK`: * + `NO_SYNC_ERROR` if the path is syncable and does not conflict with existing syncs. * - `API_EARGS`: * + `LOCAL_PATH_UNAVAILABLE` if the provided `newPath` is empty. * + `LOCAL_PATH_SYNC_COLLISION` if the path conflicts with an existing sync path. * * @warning * - The function does not verify the existence or accessibility of the `newPath`; it only * checks for path conflicts. * - Symbolic links are not resolved; the check is purely lexical based on the expanded paths. */ std::pair isLocalPathSyncable(const LocalPath& newPath, const handle excludeBackupId) const; /** * @brief Validates if the local path in a SyncConfig is suitable as a synchronization root. * * It performs several validations, including: * - Ensuring the path is not above or below a FUSE mount point. * - Verifying that the filesystem supports synchronization. * - Confirming the path exists, is accessible, and is a directory. * - Checking for conflicts with existing sync configurations to prevent overlapping sync areas. * * @param rootPath The path to validate * @param excludeBackupId A backup ID to exclude from the check done by the * `isLocalPathSyncable` method. This is useful when updating an existing sync, allowing it to * bypass itself during the validation. Pass `UNDEF` if you don't want to skip any sync during * the validation. * * @return A SyncErrorInfo, i.e., a `std::tuple` containing: * - `error`: An error code indicating the result of the check. * - `SyncError`: A detailed sync error code providing more context. * - `SyncWarning`: A sync warning code returned by `FileSystemAccess::issyncsupported`. * * The possible returned error combinations are: * - `API_OK`: * + `NO_SYNC_ERROR` if the path is a valid root for a sync. * - `API_EARGS`: * + `NO_SYNC_ERROR` if the given path is not absolute. * + `LOCAL_PATH_UNAVAILABLE` if the provided `newPath` is empty. * + `LOCAL_PATH_SYNC_COLLISION` if the path conflicts with an existing sync path. * - `API_EFAILED`: * + `LOCAL_PATH_MOUNTED` if the path is above or below a FUSE mount point * + `UNSUPPORTED_FILE_SYSTEM` if the filesystem is unsupported. * - `API_ETEMPUNAVAIL`: * + `LOCAL_PATH_TEMPORARY_UNAVAILABLE` if the local path is temporarily unavailable. * - `API_ENOENT`: * + `LOCAL_PATH_UNAVAILABLE` if the local path cannot be accessed. * - `API_EACCESS`: * + `INVALID_LOCAL_TYPE` if the path is not a directory. */ SyncErrorInfo isValidLocalSyncRoot(const LocalPath& rootPath, const handle backupIdToExclude); /** * @brief Check if a SyncConfig could be used to create a sync * * This function performs a series of checks to determine whether the provided `SyncConfig` is * valid and can be used to initiate synchronization. * * The validation includes: * - Ensuring the remote node exists and is suitable for syncing. This includes checks for * nested-syncs, which are not allowed. * - Verifying the local path is accessible, is a directory, and is on a supported filesystem. * Also nested syncs checks are carried out. * - Checking for account-related issues such as over-storage, expiration, or blockage. * - Handling specific conditions for backups, external drives, and periodic scanning modes. * * @param syncConfig The `SyncConfig` to validate. May be modified with error or warning * details. * * @return A SyncErrorInfo, i.e., a `std::tuple` containing: * - `error`: An error code indicating the result of the check. * - `SyncError`: A detailed sync error code providing more context. * - `SyncWarning`: A sync warning code returned by `FileSystemAccess::issyncsupported`. * * Possible error codes include: * - `API_ENOENT`: Remote node does not exist. * - `API_EARGS`: Invalid arguments, such as an invalid scan interval, mismatched drive paths or * given directory involved in other syncs. * - `API_EEXIST`: If there is an active sync already initiated with the given node, containing * the node or the given node contains a another sync's root. * - `API_EFAILED`: General failure due to account status or unsupported filesystem. * - `API_EACCESS`: Local path is not a directory or cannot be accessed. * - `API_ETEMPUNAVAIL`: Local path is temporarily unavailable. * - `API_EINTERNAL`: Internal error requiring account reload or restart. * * @note * - Symbolic links and mount points are considered during validation to prevent syncing * unsupported paths. */ SyncErrorInfo checkSyncConfig(const SyncConfig& syncConfig); /** * @brief add sync. Will fill syncError/syncWarning in the SyncConfig in case there are any. * It will persist the sync configuration if its call to checkSyncConfig succeeds * @param syncConfig the Config to attempt to add (takes ownership) * @param completion Completion function * @exludedPath: in sync rework, use this to specify a folder within the sync to exclude (eg, working folder with sync db in it) * @return API_OK if added to active syncs. (regular) error otherwise (with detail in syncConfig's SyncError field). * Completion is used to signal success/failure. That may occur during this call, or in future (after server request/reply etc) */ void addsync(SyncConfig&& syncConfig, std::function completion, const string& logname, const string& excludedPath); /** * @brief * Create the remote backup dir under //in/"My Backups"/`DEVICE_NAME`/. If `DEVICE-NAME` folder is missing, create that first. * * @param bkpName * The name of the remote backup dir (desired final outcome is //in/"My Backups"/`DEVICE_NAME`/bkpName) * * @param extDriveRoot * Drive root in case backup is from external drive; empty otherwise * * @param completion * Completion function * * @return * API_OK if remote backup dir has been successfully created. * API_EACCESS if "My Backups" handle could not be obtained from user attribute, or if `DEVICE_NAME` was not a dir, * or if remote backup dir already existed. * API_ENOENT if "My Backups" handle was invalid or its Node was missing. * API_EINCOMPLETE if device-id or device-name could not be obtained * Any error returned by readDriveId(), in case of external drive. * Registration occurs later, during addsync(), not in this function. * UndoFunction will be passed on completion, the caller can use it to remove the new backup cloud node if there is a later failure. */ typedef std::function continuation)> UndoFunction; void preparebackup(SyncConfig, std::function); /** * @brief * Import sync configs from JSON. * * @param configs * A JSON string encoding the sync configs to import. * * @param completion * The function to call when we've completed importing the configs. * * @see MegaApi::exportSyncConfigs * @see MegaApi::importSyncConfigs * @see Syncs::exportSyncConfig * @see Syncs::exportSyncConfigs * @see Syncs::importSyncConfig * @see Syncs::importSyncConfigs */ void importSyncConfigs(const char* configs, std::function completion); /** * @brief Changes the local/remote root of a sync to a different path/node * * @note This function only allows changing the local or the remote roots at once. To decide * which one would be changes, one of newRemoteRootNodeHandle or newLocalRootPath must be * UNDEF or nullptr respectively. Otherwise an API_EARGS will be returned * * @param backupId The backup id of the sync to be modified * @param newRemoteRootNodeHandle The handle of the node that will be used for the new root on * the cloud * @param newLocalRootPath The path to the new root of the sync locally * @param completion A completion function to be called once the logic is done, with the * corresponding error. */ void changeSyncRoot(const handle backupId, const handle newRemoteRootNodeHandle, const char* const newLocalRootPath, std::function&& completion); private: static constexpr std::chrono::seconds TIMEOUT_TO_SET_SYNC_UPLOAD_THROTTLE_PARAMS_FROM_API{ 86400}; std::chrono::steady_clock::time_point mSetSyncUploadThrottleParamsFromAPILastTime; std::chrono::seconds timeSinceLastSetSyncUploadThrottleParamsFromAPI() const { return std::chrono::duration_cast( std::chrono::steady_clock::now() - mSetSyncUploadThrottleParamsFromAPILastTime); } void setSyncUploadThrottleParamsFromAPIAfterTimeout(); void handleSetThrottleResult(const CommandSetThrottlingParams::ResultVariant& result); public: /** * @brief Sets the upload throttling configurable values retrieved directly from API. * * @see Syncs::setThrottleUpdateRate() * @see Syncs::setMaxUploadBeforeThrottle() */ void setSyncUploadThrottleParamsFromAPI(); /** * @brief Sets the upload throttling configurable value throttleUpdateRate. * * @see Syncs::setThrottleUpdateRate() */ void setSyncUploadThrottleUpdateRate(std::chrono::seconds throttleUpdateRate, std::function&& completion); /** * @brief Sets the upload throttling configurable value maxUploadsBeforeThrottle. * * @see Syncs::setMaxUploadsBeforeThrottle() */ void setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, std::function&& completion); /** * @brief Retrieves the upload throttling configurable values. */ void syncUploadThrottleValues( std::function&& completion); /** * @brief Retrieves the lower and upper upload throttling configurable values. */ void syncUploadThrottleValuesLimits(std::function&& completion); /** * @brief Checks whether there are delayed uploads pending to be processed. */ void checkSyncUploadsThrottled(std::function&& completion); /** * @brief Sets the IUploadThrottlingManager for Syncs. * * The Syncs object already constructs a IUploadThrottlingManager type object by default. * However, this method allows to change the object if needed. * * @param uploadThrottlingManager The shared_ptr to the IUploadThrottlingManager type object. * @param completion The completion function to be called after the operations * finishes. */ void setSyncUploadThrottlingManager( std::shared_ptr uploadThrottlingManager, std::function&& completion); public: #else void setSyncUploadThrottleParamsFromAPI() {} #endif // ENABLE_SYNC /** * @brief creates a tlv with one record and returns it encrypted with master key * @param name name of the record * @param text value of the record * @return encrypted base64 string with the tlv contents */ std::string cypherTLVTextWithMasterKey(const char* name, const std::string& text); std::string decypherTLVTextWithMasterKey(const char* name, const std::string& text); // close all open HTTP connections void disconnect(); // close server-client HTTP connection void catchup(); // abort lock request void abortlockrequest(); // abort session and free all state information void logout(bool keepSyncConfigsFile, CommandLogout::Completion completion = nullptr); // free all state information void locallogout(bool removecaches, bool keepSyncsConfigFile); // SDK version const char* version(); // get a local ssl certificate for communications with the webclient void getlocalsslcertificate(); // send a DNS request to resolve a hostname void dnsrequest(const char*); // send chat stats void sendchatstats(const char*, int port); // send chat logs with user's annonymous id void sendchatlogs(const char*, mega::handle forUserID, mega::handle callid, int port); // send a HTTP request void httprequest(const char*, int, bool = false, const char* = NULL, int = 1); // User-Agent header for HTTP requests string useragent; // Issuer of a detected fake SSL certificate string sslfakeissuer; // shopping basket handle_vector purchase_basket; // get the information about a discount code void getDiscountCodeInformation(const std::string& code, CommandDiscountCodeGetInfo::CompletionCallback completion); // enumerate Pro account purchase options // Optional countryCode to set the desired currency void purchase_enumeratequotaitems(const std::optional& countryCode = std::nullopt); // clear shopping basket void purchase_begin(); // add item to basket void purchase_additem(int, handle, unsigned, const char *, unsigned, const char *, handle = UNDEF, int = 0, int64_t = 0); // submit purchased products for payment void purchase_checkout(int); // submit purchase receipt for verification void submitpurchasereceipt(int, const char*, handle lph = UNDEF, int phtype = 0, int64_t ts = 0); // store credit card error creditcardstore(const char *); // get credit card subscriptions void creditcardquerysubscriptions(); // cancel credit card subscriptions void creditcardcancelsubscriptions( const CommandCreditCardCancelSubscriptions::CancelSubscription& cancelSubscription); // get payment methods void getpaymentmethods(); // store user feedback void userfeedbackstore(const char *); // send event void sendevent(int, const char *, const char* viewId = nullptr, bool addJourneyId = false); void sendevent(int, const char *, int tag, const char* viewId = nullptr, bool addJourneyId = false); void processHashcashSendevent(); // create support ticket void supportticket(const char *message, int type); // clean rubbish bin void cleanrubbishbin(); // process a received storage status value from API command and update the state if needed // returns true if the storagestatus_t arg is expected (STORAGE_GREEN, STORAGE_ORANGE, // STORAGE_RED), false otherwise. bool processStorageStatusFromCmd(const storagestatus_t); // change the storage status bool setstoragestatus(storagestatus_t); // get info about a folder link void getpubliclinkinfo(handle h); // send an sms to verificate a phone number (returns EARGS if phone number has invalid format) error smsverificationsend(const string& phoneNumber, bool reVerifyingWhitelisted = false); // check the verification code received by sms is valid (returns EARGS if provided code has invalid format) error smsverificationcheck(const string& verificationCode); #ifdef ENABLE_CHAT // create a new chat with multiple users and different privileges void createChat(bool group, bool publicchat, const userpriv_vector* userpriv = NULL, const string_map* userkeymap = NULL, const char* title = NULL, bool meetingRoom = false, int chatOptions = ChatOptions::kEmpty, const ScheduledMeeting* schedMeeting = nullptr); // invite a user to a chat void inviteToChat(handle chatid, handle uh, int priv, const char *unifiedkey = NULL, const char *title = NULL); // remove a user from a chat void removeFromChat(handle chatid, handle uh); // get the URL of a chat void getUrlChat(handle chatid); // set chat mode (public/private) void setChatMode(TextChat* chat, bool pubChat); // process object arrays by the API server (users + privileges) userpriv_vector * readuserpriv(JSON* j); // grant access to a chat peer to one specific node void grantAccessInChat(handle chatid, handle h, const char* peer); // revoke access to a chat peer to one specific node void removeAccessInChat(handle chatid, handle h, const char* peer); // update permissions of a peer in a chat void updateChatPermissions(handle chatid, handle uh, int priv); // truncate chat from message id void truncateChat(handle chatid, handle messageid); // set title of the chat void setChatTitle(handle chatid, const char *title = NULL); // get the URL of the presence server void getChatPresenceUrl(); // register a token device to route push notifications void registerPushNotification(int deviceType, const char *token = NULL); void archiveChat(handle chatid, bool archived); // request meta information from an url (title, description, icon) void richlinkrequest(const char*); // create/get or delete chat-link void chatlink(handle chatid, bool del, bool createifmissing); // get the URL for chat-link void chatlinkurl(handle publichandle); // convert public chat into private chat void chatlinkclose(handle chatid, const char *title); // auto-join publicchat void chatlinkjoin(handle publichandle, const char *unifiedkey); // set retention time for a chatroom in seconds, after which older messages in the chat are automatically deleted void setchatretentiontime(handle chatid, unsigned period); // parse scheduled meeting or scheduled meeting occurrences error parseScheduledMeetings(std::vector > &schedMeetings, bool parsingOccurrences, JSON *j, bool parseOnce = false, handle* originatingUser = nullptr, UserAlert::UpdatedScheduledMeeting::Changeset* cs = nullptr, handle_set* childMeetingsDeleted = nullptr); // report invalid scheduled meeting by sending an event to stats server void reportInvalidSchedMeeting(const ScheduledMeeting* sched = nullptr); #endif // get mega achievements void getaccountachievements(AchievementsDetails *details); // get mega achievements list (for advertising for unregistered users) void getmegaachievements(AchievementsDetails *details); public: // report an event to the API logger void reportevent(const char*, const char* = NULL); void reportevent(const char*, const char*, int tag); // set max download speed bool setmaxdownloadspeed(m_off_t bpslimit); // set max upload speed bool setmaxuploadspeed(m_off_t bpslimit); // get max download speed m_off_t getmaxdownloadspeed(); // get max upload speed m_off_t getmaxuploadspeed(); // get the handle of the older version for a NewNode std::shared_ptr getovnode(Node *parent, string *name); // Load from db node children at first level sharedNode_list getChildren(const Node* parent, CancelToken cancelToken = CancelToken(), bool includeVersions = false); // Get number of children from a node size_t getNumberOfChildren(NodeHandle parentHandle); // use an alternative port for downloads (8080) bool usealtdownport; // select the download port automatically bool autodownport; // use an alternative port for uploads (8080) bool usealtupport; // select the upload port automatically bool autoupport; // finish downloaded chunks in order bool orderdownloadedchunks; // retry API_ESSL errors bool retryessl; private: // The current request's status in millis. // // This member is maintained by procreqstat(...) whether request // monitoring is enabled or not, the idea being that we want an // application to be able to show a progress bar once any request // starts, without any delay. // // When the SDK starts a request, it checks to see if this member is // valid. If it is, it immediately notifies the application. // // If this member is not valid when a request is started, the SDK delays // notifying the application until progress information is received. std::optional mRequestProgress; // Tracks whether we've notified the application of request progress. bool mRequestProgressNotified = false; // flag to start / stop the request status monitor bool mReqStatEnabled = false; public: bool requestStatusMonitorEnabled() { return mReqStatEnabled; } void startRequestStatusMonitor() { mReqStatEnabled = true; } void stopRequestStatusMonitor() { mReqStatEnabled = false; } // timestamp until the bandwidth is overquota in deciseconds, related to Waiter::ds m_time_t overquotauntil; // storage status storagestatus_t ststatus; class CacheableStatusMap : private map { public: CacheableStatusMap(MegaClient *client) { mClient = client; } // returns the cached value for type, or defaultValue if not found int64_t lookup(CacheableStatus::Type type, int64_t defaultValue); // add/update cached status, both in memory and DB bool addOrUpdate(CacheableStatus::Type type, int64_t value); // adds a new item to the map. It also initializes dedicated vars in the client (used to load from DB) void loadCachedStatus(CacheableStatus::Type type, int64_t value); // for unserialize CacheableStatus *getPtr(CacheableStatus::Type type); void clear() { map::clear(); } private: MegaClient *mClient = nullptr; }; // cacheable status CacheableStatusMap mCachedStatus; // warning timestamps related to storage overquota in paywall mode vector mOverquotaWarningTs; // deadline timestamp related to storage overquota in paywall mode m_time_t mOverquotaDeadlineTs; // minimum bytes per second for streaming (0 == no limit, -1 == use default) int minstreamingrate; // user handle for customer support user static const string SUPPORT_USER_HANDLE; // root URL for chat stats static const string SFUSTATSURL; // root URL for reqstat requests static const string REQSTATURL; // root URL getter and setter static std::string getMegaURL(); static void setMegaURL(const std::string& url); // newsignup link URL prefix static const char* newsignupLinkPrefix(); // confirm link URL prefix static const char* confirmLinkPrefix(); // verify link URL prefix static const char* verifyLinkPrefix(); // recover link URL prefix static const char* recoverLinkPrefix(); // cancel link URL prefix static const char* cancelLinkPrefix(); // stats id std::string statsid; // number of ongoing asynchronous fopen int asyncfopens; // list of notifications to display to the user; includes items already seen UserAlerts useralerts; // true if user data is cached bool cachedug; // backoff for the expiration of cached user data BackoffTimer btugexpiration; // if logged into public folder (which might optionally be writable) bool loggedIntoFolder() const; // if logged into writable folder bool loggedIntoWritableFolder() const; // start receiving external drive [dis]connect notifications bool startDriveMonitor(); // stop receiving external drive [dis]connect notifications void stopDriveMonitor(); // returns true if drive monitor is started bool driveMonitorEnabled(); private: // root URL for Website static string MEGAURL; static std::shared_mutex megaUrlMutex; // possible root URLs static const std::string MEGAURL_NZ; static const std::string MEGAURL_APP; #ifdef USE_DRIVE_NOTIFICATIONS DriveInfoCollector mDriveInfoCollector; #endif BackoffTimer btcs; BackoffTimer mBackoffTimerLocklessCS; BackoffTimer btbadhost; BackoffTimer btworkinglock; BackoffTimer btreqstat; vector bttimers; // server-client command trigger connection std::unique_ptr pendingsc; std::unique_ptr pendingscUserAlerts; BackoffTimer btsc; int mPendingCatchUps = 0; bool mReceivingCatchUp = false; // account is blocked: stops querying for action packets, pauses transfer & removes transfer slot availability bool mBlocked = false; bool mBlockedSet = false; //value set in current execution bool pendingscTimedOut = false; // badhost report HttpReq* badhostcs; // Working lock unique_ptr workinglockcs; // Request status monitor unique_ptr mReqStatCS; // List of Notification IDs that should show in Notification Center std::vector mEnabledNotifications; public: // notify URL for new server-client commands string scnotifyurl; // lang URI component for API requests string lang; std::atomic mEnableSearchDBIndexes{true}; std::atomic mEnableLexicographicDBIndexes{false}; struct FolderLink { // public handle of the folder link ('&n=' param in the POST) handle mPublicHandle = UNDEF; // auth token that enables writing into the folder link (appended to the `n` param in POST) string mWriteAuth; // (optional, only for writable links) // auth token that relates the usage of the folder link to a user's session id ('&sid=' param in the POST) string mAccountAuth; // (optional, set by the app) }; FolderLink mFolderLink; // API response JSON object JSON response; // response record processing issue bool warned; // next local user record identifier to use int userid; // backoff for file attributes BackoffTimer btpfa; bool faretrying; // next internal upload handle (call UploadHandle::next() to update value) UploadHandle mUploadHandle; // maximum number of concurrent transfers (uploads + downloads) static const unsigned MAXTOTALTRANSFERS; // maximum number of concurrent transfers (uploads or downloads) static const unsigned MAXTRANSFERS; // minimum maximum number of concurrent transfers for dynamic calculation static const unsigned MIN_MAXTRANSFERS; // maximum number of concurrent raided transfers for mobile static const unsigned MAX_RAIDTRANSFERS_FOR_MOBILE; // meaningful portion of the maximum transfer queue size to consider raid representation // i.e., there must be at least this number of raid transfers to let us predict whether the next download transfer will be raided or non-raided static const unsigned MEANINGFUL_PORTION_OF_MAXTRANSFERS_QUEUE_FOR_RAID_PREDICTIVE_SYSTEM; // maximum number of queued putfa before halting the upload queue static const int MAXQUEUEDFA; // maximum number of concurrent putfa static const int MAXPUTFA; // update time at which next deferred transfer retry kicks in void nexttransferretry(direction_t d, dstime*); // a TransferSlot chunk failed bool chunkfailed; // fetch state serialize from local cache bool fetchsc(DbTable*); // fetch statusTable from local cache bool fetchStatusTable(DbTable*); // open/create status database table void doOpenStatusTable(); // remove old (2 days or more) transfers from cache, if they were not resumed void purgeOrphanTransfers(bool remove = false); // close the local transfer cache void closetc(bool remove = false); // server-client command processing void sc_storeSn(JSON& json); void sc_procEoo(std::unique_lock& nodeTreeIsChanging, bool originalAC); // process an action packet bool sc_procActionPacket(JSON& json, std::shared_ptr& lastAPDeletedNode); // process an action packet excluding a, i and st tags void sc_procActionPacketWithoutCommonTags(JSON& json, nameid name, bool isSelfOriginating, std::shared_ptr& lastAPDeletedNode); // evaluates if the sequence tag matches bool sc_checkSequenceTag(const string& tag); // check if it is ok to process the current action packet bool sc_checkActionPacket(JSON& json, const Node* lastAPDeletedNode); // check if it is ok to process the current action packet when there's no st bool sc_checkActionPacketWithoutSt(nameid cmd, const Node* lastAPDeletedNode); // enter json object to check the action packet, then restore json position bool sc_checkActionPacketPreservePos(JSON& json, const Node* lastAPDeletedNode); void sc_updatenode(JSON& json); std::shared_ptr sc_deltree(JSON& json, bool& moveOperation); handle sc_newnodes(JSON& json); void sc_contacts(JSON& json); void sc_fileattr(JSON& json); void sc_userattr(JSON& json); bool sc_shares(JSON& json); bool sc_upgrade(JSON& json, nameid paymentType); void sc_paymentreminder(JSON& json); void sc_opc(JSON& json); void sc_ipc(JSON& json); void sc_upc(JSON& json, bool incoming); void sc_ph(JSON& json); void sc_se(JSON& json); #ifdef ENABLE_CHAT void sc_chatupdate(JSON& json, bool readingPublicChat); void sc_chatnode(JSON& json); void sc_chatflags(JSON& json); void sc_scheduledmeetings(JSON& json); void sc_delscheduledmeeting(JSON& json); void createNewSMAlert(const handle&, handle chatid, handle schedId, handle parentSchedId, m_time_t startDateTime); void createDeletedSMAlert(const handle&, handle chatid, handle schedId); void createUpdatedSMAlert(const handle&, handle chatid, handle schedId, handle parentSchedId, m_time_t startDateTime, UserAlert::UpdatedScheduledMeeting::Changeset&& cs); static error parseScheduledMeetingChangeset(JSON*, UserAlert::UpdatedScheduledMeeting::Changeset*); void clearSchedOccurrences(TextChat& chat); #endif void sc_uac(JSON& json); void sc_uec(JSON& json); void sc_la(JSON& json); void sc_ub(JSON& json); void sc_sqac(JSON& json); void sc_pk(); void sc_cce(); void init(); // remove caches void removeCaches(); // add node to vector and return index unsigned addnode(sharedNode_vector* v, std::shared_ptr n) const; // read node tree from JSON object void readtree(JSON*); // converts UTF-8 to 32-bit word array static char* utf8_to_a32forjs(const char*, int*); // was the app notified of a retrying CS request? bool csretrying; // encode/query handle type void encodeHandleType(handle* h, bool isPublicHandle); // add direct read void queueread(handle handle, bool isPublicHandle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, DirectRead::Callback&& callback, const char* privateAuth = NULL, const char* publicAuth = NULL, const char* chatAuth = NULL); void queueread(handle handle, bool isPublicHandle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, void* appData, const char* privateAuth = NULL, const char* publicAuth = NULL, const char* chatAuth = NULL); // execute pending direct reads bool execdirectreads(); // maximum number parallel connections for the direct read subsystem static const int MAXDRSLOTS = 16; // abort queued direct read(s) void abortreads(handle handle, bool isPublicHandle, m_off_t offset, m_off_t count); // abort all queued direct reads. void abortreads(); static const char PAYMENT_PUBKEY[]; void dodiscarduser(User* u, bool discardnotified); void enabletransferresumption(); void disabletransferresumption(); void resumeTransfersForNotLoggedInInstance(); void resumeTransferFromDB(); // application callbacks struct MegaApp* app; // event waiter shared_ptr waiter; // HTTP access HttpIO* httpio; // directory change notification unique_ptr fsaccess; // bitmap graphics handling GfxProc* gfx; // enable / disable the gfx layer bool gfxdisabled; // DB access DbAccess* dbaccess = nullptr; // DbTable iface to handle "statecache" for logged in user (implemented at SqliteAccountState object) unique_ptr sctable; // NodeManager instance to wrap all access to Node objects NodeManager mNodeManager; // IMPORTANT: Please refer to the `mSyncVecMutex` definition for lock ordering rules (to avoid // deadlocks). recursive_mutex nodeTreeMutex; // transfer cache table unique_ptr tctable; // during processing of request responses, transfer table updates can be wrapped up in a single begin/commit TransferDbCommitter* mTctableRequestCommitter = nullptr; // status cache table for logged in user. For data pertaining status which requires immediate commits unique_ptr statusTable; // scsn as read from sctable handle cachedscsn; void handleDbError(DBError error); // notify the app about a fatal error (ie. DB critical error like disk is full) void fatalError(ErrorReason errorReason); // This method returns true when fatal failure has been detected // None actions has been taken yet (reload, restart app, ...) bool accountShouldBeReloadedOrRestarted() const; // This flag keeps the last fatal error detected. It's overwritten by new errors and reset upon // logout. It's cleaned after reload or other error is generated ErrorReason mLastFatalErrorDetected = ErrorReason::REASON_ERROR_NO_ERROR; // This flag keeps the last DB error detected. It's overwritten by new errors and reset upon // logout. It's cleaned after reload or other error is generated DBError mLastDBErrorDetected = DBError::DB_ERROR_UNKNOWN; // initial state load in progress? initial state can come from the database cache or via an 'f' command to the API. // Either way there can still be a lot of historic actionpackets to follow since that snaphot, especially if the user has not been online for a long time. bool fetchingnodes; int fetchnodestag; // set true after fetchnodes and catching up on actionpackets, stays true after that. std::atomic statecurrent; // actionpackets are up to date (similar to statecurrent but false if in the middle of spoonfeeding etc) std::atomic actionpacketsCurrent; // This flag is used to ensure we load Syncs just once per user session, even if a fetchnodes reload occurs after the first one bool syncsAlreadyLoadedOnStatecurrent = false; // subsequent fetchnodes should use the 'nocache' flag, so that we don't have difficulties with actionpackets getting to a later SCSN than we had before bool fetchnodesAlreadyCompletedThisSession = false; // File Attribute upload system. These can come from: // - upload transfers // - app requests to attach a thumbnail/preview to a node // - app requests for media upload (which return the fa handle) // initially added to queuedfa, and up to 10 moved to activefa. list> queuedfa; list> activefa; // API request queue double buffering: // reqs[r] is open for adding commands // reqs[r^1] is being processed on the API server HttpReq* pendingcs; // API lockless request std::unique_ptr mPendingLocklessCS; // When triggering an API Hashcash challenge, the HTTP response will contain // X-Hashcash header, with relevant data to be saved and used for the next retry. string mReqHashcashToken; uint8_t mReqHashcashEasiness{}; // Only queue the "Server busy" event once, until the current cs completes, otherwise we may DDOS // ourselves in cases where many clients get 500s for a while and then recover at the same time bool pendingcs_serverBusySent = false; // pending HTTP requests pendinghttp_map pendinghttp; // record type indicator for sctable // allways add new ones at the end of the enum, otherwise it will mess up the db! enum { CACHEDSCSN, CACHEDNODE, CACHEDUSER, CACHEDLOCALNODE, CACHEDPCR, CACHEDTRANSFER, CACHEDFILE, CACHEDCHAT, CACHEDSET, CACHEDSETELEMENT, CACHEDDBSTATE, CACHEDALERT } sctablerectype; void persistAlert(UserAlert::Base* a); // record type indicator for statusTable enum StatusTableRecType { CACHEDSTATUS }; // open/create "statecache" and "nodes" tables in DB void opensctable(); // opens (or creates if non existing) a status database table. // if loadFromCache is true, it will load status from the table. void openStatusTable(bool loadFromCache); // initialize/update state cache referenced sctable void initsc(); void updatesc(); void finalizesc(bool); // truncates status table void initStatusTable(); // flag to pause / resume the processing of action packets bool scpaused; // actionpacket sequence tags (current refers to the one expected by the Requests) string mCurrentSeqtag; string mPriorSeqTag; bool mCurrentSeqtagSeen = false; int mCurrentSeqtagCmdtag = 0; // sc received seqtags to report to app (not tied to requests in this client) string mLastReceivedScSeqTag; string mLargestEverSeenScSeqTag; // records last seqTag, with allowance for future fields also ScDbStateRecord mScDbStateRecord; // Server-MegaClient request JSON and processing state flag ("processing a element") JSON jsonsc; bool insca; bool insca_notlast; // no two interrelated client instances should ever have the same sessionid char sessionid[10]; // session key to protect local storage string sessionkey; // key protecting non-shareable GPS coordinates in nodes (currently used only by CUv2 in iOS) string unshareablekey; // incoming shares to be attached to a corresponding node newshare_list newshares; // maps the handle of the root of shares with their corresponding share key // out-shares: populated from 'ok0' element from `f` command // in-shares: populated from readnodes() for `f` command // map is cleared upon call to mergenewshares(), and used only temporary during `f` command. std::map> mNewKeyRepository; // current request tag int reqtag; // user maps: by handle and by case-normalized e-mail address uh_map uhindex; um_map umindex; // mapping of pending contact handles to their structure handlepcr_map pcrindex; // A record of which file attributes are needed (or now available) per upload transfer FileAttributesPending fileAttributesUploading; // file attribute fetch channels fafc_map fafcs; // generate attribute string based on the pending attributes for this upload void pendingattrstring(UploadHandle, string*); // active/pending direct reads handledrn_map hdrns; // DirectReadNodes, main ownership. One per file, each with one DirectRead per client request. dsdrn_map dsdrns; // indicates the time at which DRNs should be retried dr_list drq; // DirectReads that are in DirectReadNodes which have fectched URLs drs_list drss; // DirectReadSlot for each DR in drq, up to Max void removeAppData(void* t); // remove appdata (usually a MegaTransfer*) from every DirectRead // merge newly received share into nodes void mergenewshares(bool notify, bool skipWriteInDb = false); void mergenewshare(NewShare *s, bool notify, bool skipWriteInDb); // merge only the given share // return the list of incoming shared folder (only top level, nested inshares are skipped) sharedNode_vector getInShares(); template using IsIncomingShareIterator = std::conjunction>, std::is_same>, void>>; // Call function on each incoming share. template auto forEachIncomingShare(Function&& function) -> std::enable_if_t::value, void> { // Iterate over each user. for (const auto& user: users) { // And each node handle shared by that user. for (const auto& handle: user.second.sharing) { // Get our hands on the incoming shared node. if (auto node = nodebyhandle(handle); node && node->parent == nullptr) { // Pass the node to our iterator function. std::invoke(function, std::move(node)); } } } } // return the list of verified incoming shared folders (only top level, nested inshares are skipped) sharedNode_vector getVerifiedInShares(); // return the list of unverified incoming shared folders (only top level, nested inshares are skipped) sharedNode_vector getUnverifiedInShares(); // transfer queues (PUT/GET) transfer_multimap multi_transfers[2]; BackoffTimerGroupTracker transferRetryBackoffs[2]; uint32_t lastKnownCancelCount = 0; #ifdef ENABLE_SYNC // track puts that may need finishing if sync abandoned before putnodes happens TransferBackstop transferBackstop; #endif // transfer list to manage the priority of transfers TransferList transferlist; // cached transfers (PUT/GET) transfer_multimap multi_cachedtransfers[2]; // cached files and their dbids vector cachedfiles; vector cachedfilesdbids; // database IDs of cached files and transfers // waiting for the completion of a putnodes pendingdbid_map pendingtcids; // path of temporary files // waiting for the completion of a putnodes pendingfiles_map pendingfiles; // transfer tslots transferslot_list tslots; // raid transfers counter unsigned raidTransfersCounter{}; // keep track of next transfer slot timeout BackoffTimerGroupTracker transferSlotsBackoff; // next TransferSlot to doio() on transferslot_list::iterator slotit; // transfer statistics manager stats::TransferStatsManager mTransferStatsManager; // send updates to app when the storage size changes int64_t mNotifiedSumSize = 0; // asymmetric to symmetric key rewriting handle_vector nodekeyrewrite; static const char* const EXPORTEDLINK; // default number of seconds to wait after a bandwidth overquota static dstime DEFAULT_BW_OVERQUOTA_BACKOFF_SECS; // number of seconds to invalidate the cached user data static dstime USER_DATA_EXPIRATION_BACKOFF_SECS; // total number of Node objects std::atomic_ullong totalNodes; // server-client request sequence number SCSN scsn; // status of S4 std::atomic mIsS4Enabled; // node's handle of S4 container std::atomic mS4Container; // process an array of users from the API server bool readusers(JSON*, bool actionpackets); // process a JSON user object // possible results: // 0 -> no object found // 1 -> successful parsing // any other number -> parsing error int readuser(JSON*, bool actionpackets); user_vector usernotify; void notifyuser(User*); pcr_vector pcrnotify; void notifypcr(PendingContactRequest*); // update transfer in the persistent cache void transfercacheadd(Transfer*, TransferDbCommitter*); // remove a transfer from the persistent cache void transfercachedel(Transfer*, TransferDbCommitter* committer); // add a file to the persistent cache void filecacheadd(File*, TransferDbCommitter& committer); // remove a file from the persistent cache void filecachedel(File*, TransferDbCommitter* committer); #ifdef ENABLE_CHAT textchat_map chatnotify; void notifychat(TextChat *); // process mcsm array at fetchnodes void procmcsm(JSON*); #endif #ifdef USE_MEDIAINFO MediaFileInfo mediaFileInfo; #endif // write changed/added/deleted users to the DB cache and notify the // application void notifypurge(); // If it's necessary, load nodes from data base shared_ptr nodeByHandle(NodeHandle); shared_ptr nodebyhandle(handle); shared_ptr nodeByPath(const char* path, std::shared_ptr node = nullptr, nodetype_t type = TYPE_UNKNOWN); using TotpTokenResult = std::pair>; TotpTokenResult generateTotpTokenFromNode(const handle h); #if ENABLE_SYNC std::shared_ptr nodebyfingerprint(LocalNode*); #endif /* ENABLE_SYNC */ private: std::string getTransferDBName(); public: // get a vector of recent actions in the account recentactions_vector getRecentActions(unsigned maxcount, m_time_t since, bool excludeSensitives = true); // get a recent action bucket by its identifier. // Returns API_OK (recentaction in output), API_ENOENT (no match), or API_EARGS (invalid id). error getRecentActionById(const char* id, recentaction& output); // get a recent action bucket by its identifier, overriding the excludeSensitives flag in the // id. Returns API_OK (recentaction in output), API_ENOENT (no match), or API_EARGS (invalid // id). error getRecentActionById(const char* id, bool excludeSensitives, recentaction& output); // determine if the file is a video, photo, or media (video or photo). If the extension (with trailing .) is not precalculated, pass null bool nodeIsMedia(const Node*, bool *isphoto, bool *isvideo) const; // determine if the file is a photo. bool nodeIsPhoto(const Node *n, bool checkPreview) const; // determine if the file is a video. bool nodeIsVideo(const Node *n) const; // determine if the file is an audio. bool nodeIsAudio(const Node *n) const; // determine if the file is a document. bool nodeIsDocument(const Node *n) const; // determine if the file is a PDF. bool nodeIsPdf(const Node *n) const; // determine if the file is a presentation. bool nodeIsPresentation(const Node *n) const; // determine if the file is an archive. bool nodeIsArchive(const Node* n) const; // determine if the file is a program. bool nodeIsProgram(const Node* n) const; // determine if the file is miscellaneous. bool nodeIsMiscellaneous(const Node* n) const; // determine if the file is a spreadsheet. bool nodeIsSpreadsheet(const Node* n) const; // determine if the file is not in any of the other file types. bool nodeIsOtherType(const Node* n) const; // functions for determining whether we can clone a node instead of upload // or whether two files are the same so we can just upload/download the data once bool treatAsIfFileDataEqual(const FileFingerprint& nodeFingerprint, const LocalPath& file2, const std::string& filenameExtensionLowercaseNoDot) const; bool treatAsIfFileDataEqual(const FileFingerprint& fp1, const std::string& filenameExtensionLowercaseNoDot1, const FileFingerprint& fp2, const std::string& filenameExtensionLowercaseNoDot2) const; #ifdef ENABLE_SYNC // one unified structure for SyncConfigs, the Syncs that are running, and heartbeat data Syncs syncs; // indicates whether all startup syncs have been fully scanned bool syncsup; // sync debris folder name in //bin static const char* const SYNCDEBRISFOLDERNAME; // we are adding the //bin/SyncDebris/yyyy-mm-dd subfolder(s) bool syncdebrisadding; // minute of the last created folder in SyncDebris (don't attempt creation more frequently than once per minute) m_time_t syncdebrisminute; // move nodes to //bin/SyncDebris/yyyy-mm-dd/ or copy to //bin/SyncDebris/yyyy-mm-dd/ folder and unlink void movetosyncdebris(Node*, bool inshare, std::function&& completion, bool canChangeVault); // move queued nodes to SyncDebris (for syncing into the user's own cloud drive) void execmovetosyncdebris(Node* n, std::function&& completion, bool canChangeVault, bool isInshare); /** * @brief Get the daily SyncDebris folder name for a given timestamp. * * @param timestamp to generate daily SyncDebris folder name * @return String that contains the daily SyncDebris folder name */ std::string getDailyDebrisName(const m_time_t& ts) const; /** * @brief Get the daily SyncDebris folder for a given timestamp. * * This function locates the daily folder inside the SyncDebris folder for the specified * timestamp. * * @param binSyncDebrisNode Shared pointer to the //bin/SyncDebris node * @param dailyDebrisName Daily SyncDebris folder name * @return Shared pointer to the daily SyncDebris node (nullptr if not found) */ std::shared_ptr getSyncdebrisDaily(std::shared_ptr binSyncDebrisNode, const std::string& dailyDebrisName); /** * @brief Get the daily SyncDebris folder and all its ancestor nodes * * This function retrieves the complete path from the rubbish bin root to * the daily SyncDebris folder for the specified timestamp. It returns all * intermediate nodes in the hierarchy: rubbish bin, SyncDebris folder, * and the daily folder. * * @param ts Timestamp to generate the daily folder name from * @return Tuple containing: * - Shared pointer to the rubbish bin node (nullptr if not found) * - Shared pointer to the //bin/SyncDebris node (nullptr if not found) * - Shared pointer to the daily SyncDebris node (nullptr if not found) * - String with the daily folder name in yyyy-mm-dd format */ std::tuple, std::shared_ptr, std::shared_ptr, std::string> getSyncdebrisDailyAndAncestors(const m_time_t& ts); std::shared_ptr getOrCreateSyncdebrisFolder(); struct pendingDebrisRecord { NodeHandle nodeHandle; std::function completion; bool mIsInshare = false; bool mCanChangeVault = false; pendingDebrisRecord(NodeHandle h, std::function c, bool inshare, bool changeVault) : nodeHandle(h), completion(c), mIsInshare(inshare), mCanChangeVault(changeVault) {} }; list pendingDebris; #endif // determine if all transfer slots are full bool slotavail() const; // transfer queue dispatch/retry handling void dispatchTransfers(); void freeq(direction_t); // client-server request double-buffering RequestDispatcher reqs; // Lockless client-server request double-buffering RequestDispatcher mReqsLockless; // returns if the current pendingcs includes a fetch nodes command bool isFetchingNodesPendingCS(); // transfer chunk failed void setchunkfailed(string*); string badhosts; bool requestLock; dstime disconnecttimestamp; dstime nextDispatchTransfersDs = 0; #ifdef ENABLE_CHAT // SFU id to specify the SFU server where all chat calls will be started int mSfuid = sfu_invalid_id; #endif // process object arrays by the API server int readnodes(JSON*, int, putsource_t, vector*, bool modifiedByThisClient, bool applykeys); // process a JSON node object // possible results: // 0 -> no object found // 1 -> successful parsing // any other number -> parsing error int readnode(JSON*, int, putsource_t, vector*, bool modifiedByThisClient, bool applykeys, NodeManager::MissingParentNodes& missingParentNodes, handle& previousHandleForAlert, set* allParents); void readok(JSON*); void readokelement(JSON*); void readoutshares(JSON*); void readoutshareelement(JSON*); void readipc(JSON*); void readopc(JSON*); error readmiscflags(JSON*); bool procph(JSON*); // process a JSON ph object // possible results: // 0 -> no object found // 1 -> successful parsing // any other number -> parsing error int procphelement(JSON*); void procmcf(JSON*); void procmcna(JSON*); void setkey(SymmCipher*, const char*); bool decryptkey(const char*, byte*, int, SymmCipher*, int, handle); void handleauth(handle, byte*); // Entrance for server-client channel processing void handleScChannel(); // Process states and prepare data void handleScNonStreaming(); void handleScErrorInSuccessState(); void handleScInFailureState(); // Process actual data from the server-client channel void processScMessageNonStreaming(); bool procsc(JSON& json); size_t procreqstat(); // API warnings void warn(const char*); bool warnlevel(); std::shared_ptr childnodebyname(const Node *parent, const char* name, bool skipFolders = false); sharedNode_vector childnodesbyname(Node* parent, const char* name, bool skipFolders = false); std::shared_ptr childnodebynametype(Node* parent, const char* name, nodetype_t mustBeType); std::shared_ptr childnodebyattribute(Node* parent, nameid attrId, const char* attrValue); static void honorPreviousVersionAttrs(Node *previousNode, AttrMap &attrs); // purge account state and abort server-client connection void purgenodesusersabortsc(bool keepOwnUser); static const int USERHANDLE = 8; static const int PCRHANDLE = 8; static const int NODEHANDLE = 6; static const int CHATHANDLE = 8; static const int SESSIONHANDLE = 8; static const int PURCHASEHANDLE = 8; static const int BACKUPHANDLE = 8; static const int DRIVEHANDLE = 8; static const int CONTACTLINKHANDLE = 6; static const int CHATLINKHANDLE = 6; static const int SETHANDLE = Set::HANDLESIZE; static const int SETELEMENTHANDLE = SetElement::HANDLESIZE; static const int PUBLICSETHANDLE = Set::PUBLICHANDLESIZE; static const int SURVEYHANDLE = 8; // max new nodes per request static const int MAX_NEWNODES = 2000; // session ID length (binary) static const unsigned SIDLEN = 2 * SymmCipher::KEYLENGTH + USERHANDLE * 4 / 3 + 1; KeyManager mKeyManager; // account access: master key // folder link access: folder key SymmCipher key; // dummy key to obfuscate non protected cache SymmCipher tckey; // account access (full account): RSA private key AsymmCipher mPrivateRsaKey; string mSerializedPrivateRsaKey; // serialized version for apps // RSA public key AsymmCipher mPublicRsaKey; // EdDSA signing key (Ed25519 private key seed). EdDSA* mEd255Key; // ECDH key (x25519 private key). ECDH* mX255Key; // set when keys for every current contact have been checked AuthRingsMap mAuthRings; // used during initialization to accumulate required updates to authring (to send them all atomically) AuthRingsMap mAuthRingsTemp; // Pending contact keys during initialization std::map> mPendingContactKeys; // invalidate received keys (when fail to load) void clearKeys(); // delete chatkey and signing key void resetKeyring(); // binary session ID string sid; // distinguish activity from different MegaClients in logs string clientname; // number our http requests so we can distinguish them (and the curl debug logging for them) in logs unsigned transferHttpCounter = 0; // apply keys void applykeys(); // symmetric password challenge int checktsid(byte* sidbuf, unsigned len); // locate user by e-mail address or by handle User* finduser(const char*, int = 0); User* finduser(handle, int = 0); User* ownuser(); void mapuser(handle, const char*); void discarduser(handle, bool = true); void discarduser(const char*); void mappcr(handle, unique_ptr&&); bool discardnotifieduser(User *); PendingContactRequest* findpcr(handle); std::optional findpcr(const string&); // queue public key request for user User* getUserForSharing(const char* userID); void queuepubkeyreq(User*, std::unique_ptr); void queuepubkeyreq(const char*, std::unique_ptr); // rewrite foreign keys of the node (tree) void rewriteforeignkeys(std::shared_ptr n); // simple string hash static void stringhash(const char*, byte*, SymmCipher*); static uint64_t stringhash64(string*, SymmCipher*); // builds the authentication URI to be sent in POST requests string getAuthURI(bool supressSID = false, bool supressAuthKey = false); bool setlang(string *code); // Enable create DB indexes for queries used in search functionality // By default is true (reset to default value at locallogout) void enableSearchDBIndexes(bool enable); // Enable create DB indexes for queries listing nodes using lexicographical oreder // By default is false (reset to default value at locallogout) void enableLexicographicDBIndexes(bool enable); // Drop DB indexes for queries used in search functionality // It should be call just after open the DB void dropSearchDBIndexes(); // create a new folder with given name and stores its node's handle into the user's attribute ^!bak error setbackupfolder(const char* foldername, int tag, std::function addua_completion); // fetch backups and syncs from BC, search bkpId among them, pause/resume the backup or sync, update sds attribute void updateStateInBC(handle bkpId, CommandBackupPut::SPState newState, std::function f); // fetch backups and syncs from BC, search bkpId among them, disable the backup or sync, update sds attribute, for a backup move or delete its contents void removeFromBC(handle bkpId, handle bkpDest, std::function f); // fetch backups and syncs from BC void getBackupInfo(std::function&)> f); // sets the auth token to be used when logged into a folder link void setFolderLinkAccountAuth(const char *auth); // returns the public handle of the folder link if the account is logged into a public folder, otherwise UNDEF. handle getFolderLinkPublicHandle(); // check if end call reason is valid bool isValidEndCallReason(int reason); // check if there is a valid folder link (rootnode received and the valid key) bool isValidFolderLink(); //returns the top-level node for a node shared_ptr getrootnode(shared_ptr); //returns true if the node referenced by the handle belongs to the logged-in account bool isPrivateNode(NodeHandle h); //returns true if the node referenced by the handle belongs to other account than the logged-in account bool isForeignNode(NodeHandle h); // process node subtree void proctree(std::shared_ptr, TreeProc*, bool skipinshares = false, bool skipversions = false); // hash password error pw_key(const char*, byte*) const; // returns a pointer to tmptransfercipher, setting its key to the one provided; // tmptransfercipher key will change to be used right away; this is not a dedicated SymmCipher // for the transfer! SymmCipher* getRecycledTemporaryTransferCipher(const byte* newKey, int type = 1); // returns a pointer to tmpnodecipher, setting its key to the one provided; // tmpnodecipher key will change to be used right away; this is not a dedicated SymmCipher for // the node! SymmCipher* getRecycledTemporaryNodeCipher(const string* newKey); SymmCipher* getRecycledTemporaryNodeCipher(const byte* newKey); // request a link to recover account void getrecoverylink(const char *email, bool hasMasterkey); // query information about recovery link void queryrecoverylink(const char *link); // request private key for integrity checking the masterkey void getprivatekey(const char *code); // confirm a recovery link to restore the account void confirmrecoverylink(const char* code, const char* email, const char* password, const byte* masterkey = NULL, int ownAccountVersion = 1); // request a link to cancel the account void getcancellink(const char *email, const char* = NULL); // confirm a link to cancel the account void confirmcancellink(const char *code); // get a link to change the email address void getemaillink(const char *email, const char *pin = NULL); // confirm a link to change the email address void confirmemaillink(const char *code, const char *email, const byte *pwkey); // create contact link void contactlinkcreate(bool renew); // query contact link void contactlinkquery(handle); // delete contact link void contactlinkdelete(handle); // multi-factor authentication setup void multifactorauthsetup(const char* = NULL); // multi-factor authentication get void multifactorauthcheck(const char*); // multi-factor authentication disable void multifactorauthdisable(const char*); // fetch time zone void fetchtimezone(); void keepmealive(int, bool enable = true); void getpsa(bool urlSupport); // tells the API the user has seen existing alerts void acknowledgeuseralerts(); // manage overquota errors void activateoverquota(dstime timeleft, bool isPaywall); // achievements enabled for the account bool achievements_enabled; // non-zero if login with user+pwd was done (reset upon fetchnodes completion) bool isNewSession; // timestamp of the last login with user and password m_time_t tsLogin; // snapshot of the global cancel_epoch_t when a login request is sent cancel_epoch_t mLoginCancelSnapshot{}; // true if user has disabled fileversioning bool versions_disabled; // the SDK is trying to log out int loggingout = 0; bool executingLocalLogout = false; // the logout request succeeded, time to clean up localy once returned from CS response processing std::function mOnCSCompletion; // true if the account is a master business account, false if it's a sub-user account BizMode mBizMode; // -1: expired, 0: inactive (no business subscription), 1: active, 2: grace-period BizStatus mBizStatus; // list of handles of the Master business account/s std::set mBizMasters; // timestamp when a business account will enter into Grace Period m_time_t mBizGracePeriodTs; // timestamp when a business account will finally expire m_time_t mBizExpirationTs; // whether the destructor has started running yet bool destructorRunning = false; // Keep track of high level operation counts and times, for performance analysis struct PerformanceStats { CodeCounter::ScopeStats execFunction = { "MegaClient_exec" }; CodeCounter::ScopeStats transferslotDoio = { "TransferSlot_doio" }; CodeCounter::ScopeStats execdirectreads = { "execdirectreads" }; CodeCounter::ScopeStats transferComplete = { "transfer_complete" }; CodeCounter::ScopeStats megaapiSendPendingTransfers = { "megaapi_sendtransfers" }; CodeCounter::ScopeStats prepareWait = { "MegaClient_prepareWait" }; CodeCounter::ScopeStats doWait = { "MegaClient_doWait" }; CodeCounter::ScopeStats checkEvents = { "MegaClient_checkEvents" }; CodeCounter::ScopeStats applyKeys = { "MegaClient_applyKeys" }; CodeCounter::ScopeStats dispatchTransfers = { "dispatchTransfers" }; CodeCounter::ScopeStats csResponseProcessingTime = { "cs batch response processing" }; CodeCounter::ScopeStats csSuccessProcessingTime = { "cs batch received processing" }; CodeCounter::ScopeStats scProcessingTime = { "sc processing" }; #ifdef ENABLE_SYNC CodeCounter::ScopeStats recursiveSyncTime = { "recursiveSync" }; CodeCounter::ScopeStats computeSyncTripletsTime = { "computeSyncTriplets" }; CodeCounter::ScopeStats inferSyncTripletsTime = { "inferSyncTriplets" }; CodeCounter::ScopeStats syncItem = { "syncItem" }; CodeCounter::ScopeStats syncItemCheckMove = { "syncItemCheckMove" }; CodeCounter::ScopeStats syncItemXXX = { "syncItemXXX" }; CodeCounter::ScopeStats syncItemXXF = { "syncItemXXF" }; CodeCounter::ScopeStats syncItemXSX = { "syncItemXSX" }; CodeCounter::ScopeStats syncItemXSF = { "syncItemXSF" }; CodeCounter::ScopeStats syncItemCXX = { "syncItemCXX" }; CodeCounter::ScopeStats syncItemCXF = { "syncItemCXF" }; CodeCounter::ScopeStats syncItemCSX = { "syncItemCSX" }; CodeCounter::ScopeStats syncItemCSF = { "syncItemCSF" }; CodeCounter::ScopeStats clientThreadActions = { "clientThreadActions" }; #endif uint64_t transferStarts = 0, transferFinishes = 0; uint64_t transferTempErrors = 0, transferFails = 0; uint64_t prepwaitImmediate = 0, prepwaitZero = 0, prepwaitHttpio = 0, prepwaitFsaccess = 0, nonzeroWait = 0; CodeCounter::DurationSum csRequestWaitTime; CodeCounter::DurationSum transfersActiveTime; std::string report(bool reset, HttpIO* httpio, Waiter* waiter, const RequestDispatcher& reqs); } performanceStats; std::string getDeviceidHash(); /** * @brief This function calculates the time (in deciseconds) that a user * transfer request must wait for a retry. * * A pro user who has reached the limit must wait for the renewal or * an upgrade on the pro plan. * * @param req Pointer to HttpReq object * @note a 99408 event is sent for non-pro clients with a negative * timeleft in the request header * * @return returns the backoff time in dstime */ dstime overTransferQuotaBackoff(HttpReq* req); enum class ClientType { DEFAULT = 0, // same as MegaApi::CLIENT_TYPE_DEFAULT VPN, PASSWORD_MANAGER, }; MegaClient(MegaApp*, shared_ptr, HttpIO*, DbAccess*, GfxProc*, const char*, unsigned workerThreadCount, ClientType clientType = ClientType::DEFAULT); ~MegaClient(); struct MyAccountData { void setProLevel(AccountType prolevel) { mProLevel = prolevel; } AccountType getProLevel() { return mProLevel; }; void setProUntil(m_time_t prountil) { mProUntil = prountil; } // returns remaining time for the current pro-level plan // keep in mind that free plans do not have a remaining time; instead, the IP bandwidth is reset after a back off period m_time_t getTimeLeft(); private: AccountType mProLevel = AccountType::ACCOUNT_TYPE_UNKNOWN; m_time_t mProUntil = -1; } mMyAccount; // JourneyID for cs API requests and log events. Populated from "ug"/"gmf" commands response. // It is kept in memory and persisted in disk until a full logout. struct JourneyID { private: // The JourneyID value - a 16-char hex string (or an empty string if it hasn't been retrieved yet) string mJidValue; // The tracking flag: used to attach the JourneyID to cs requests bool mTrackValue; // Local cache file unique_ptr& mClientFsaccess; LocalPath mCacheFilePath; bool storeValuesToCache(bool storeJidValue, bool storeTrackValue) const; public: static constexpr size_t HEX_STRING_SIZE = 16; JourneyID(unique_ptr& clientFsaccess, const LocalPath& rootPath); // Updates the JourneyID and the tracking flag based on the provided jidValue, which must be a 16-char hex string. // When jidValue is not empty: // - Sets mJidValue to jidValue only if it is currently unset (empty). // - Sets mTrackValue if it is currently unset (false). // When jidValue is empty: // - Keeps mJidValue unchanged. // - Unsets mTrackValue if it is currently set (true). // Returns true if either the JourneyID (mJidValue) or the tracking flag (mTrackValue) have been updated. bool setValue(const string& jidValue); // Get the JourneyID (empty if still unset) string getValue() const; // Check if the tracking flag is set, i.e.: the JourneyID must be tracked (used in cs API reqs) bool isTrackingOn() const; // Load the JourneyID and the tracking flag stored in the cache file. bool loadValuesFromCache(); // Remove local cache file and reset the JourneyID so a new one can be set from the next "ug"/"gmf" command. bool resetCacheAndValues(); }; ClientType getClientType() const { return mClientType; } bool isClientType(const ClientType& t) const { return mClientType == t; } private: ClientType mClientType; // Since it's quite expensive to create a SymmCipher, this are provided to use for quick operations - just set the key and use. SymmCipher tmpnodecipher; // Since it's quite expensive to create a SymmCipher, this is provided to use for quick operation - just set the key and use. SymmCipher tmptransfercipher; error changePasswordV1(User* u, const char* password, const char* pin); error changePasswordV2(const char* password, const char* pin); void fillCypheredAccountDataV2(const char* password, vector& clientRandomValue, vector& encmasterkey, string& hashedauthkey, string& salt); static vector deriveKey(const char* password, const string& salt, size_t derivedKeySize); // // JourneyID and ViewID // // JourneyID for cs API requests and log events std::unique_ptr mJourneyId; public: // Checks if there is a valid JourneyID and tracking flag is set bool trackJourneyId() const; // Retrieves the JourneyID value, which is a 16-character hexadecimal string (for submission to the API) // If the JourneyID is still unset, it returns an empty string. string getJourneyId() const; // Load the JourneyID values from the local cache. bool loadJourneyIdCacheValues(); // Set the JourneyID value from a 16-character hexadecimal string (obtained from API commands "ug"/"gmf") // See JourneyID::setValue() for full doc bool setJourneyId(const string& jid); // Generates a unique ViewID that the caller should store and can optionally use in subsequent sendevent() calls. // ViewID is employed by apps for event logging. It is generated by the SDK to ensure consistent and shared logic across applications. static string generateViewId(PrnGen& rng); // // Sets and Elements // // generate "asp" command void putSet(Set&& s, std::function completion); // generate "asr" command void removeSet(handle setID, std::function completion); // generate "aft" command void fetchSetInPreviewMode(std::function completion); // generate "aepb" command void putSetElements(vector&& els, std::function*, const vector*)> completion); // generate "aep" command void putSetElement(SetElement&& el, std::function completion); // generate "aerb" command void removeSetElements(handle setID, vector&& eids, std::function*)> completion); // generate "aer" command void removeSetElement(handle setID, handle eid, std::function completion); // handle "aesp" parameter, part of 'f'/ "fetch nodes" response bool procaesp(JSON& j); // load Sets and Elements from json error readSetsAndElements(JSON& j, map& newSets, map& newElements); // return Set with given setID or nullptr if it was not found const Set* getSet(handle setID) const; // return all available Sets, indexed by id const map& getSets() const { return mSets; } // add new Set or replace exisiting one const Set* addSet(Set&& a); // search for Set with the same id, and update its members bool updateSet(Set&& s); // delete Set with setID from local memory; return true if found and deleted bool deleteSet(handle setID); // return Element count for Set with setID, or 0 if not found unsigned getSetElementCount(handle setID) const; // return Element with given eid from Set with setID, or nullptr if not found const SetElement* getSetElement(handle setID, handle eid) const; // return all available Elements in a Set, indexed by eid const elementsmap_t* getSetElements(handle setID) const; // add new SetElement or replace exisiting one const SetElement* addOrUpdateSetElement(SetElement&& el); // delete Element with eid from Set with setID in local memory; return true if found and deleted bool deleteSetElement(handle setID, handle eid); // return true if Set with given setID was exported (has a public link) bool isExportedSet(handle setID) const; void exportSet(handle setID, bool makePublic, std::function completion); // returns result of the operation and the link created pair getPublicSetLink(handle setID) const; // returns error code and public handle for the link provided as a param error fetchPublicSet(const char* publicSetLink, std::function); void stopSetPreview() { if (mPreviewSet) mPreviewSet.reset(); } bool inPublicSetPreview() const { return !!mPreviewSet; } const SetElement* getPreviewSetElement(handle eid) const { return isElementInPreviewSet(eid) ? &mPreviewSet->mElements[eid] : nullptr; } const Set* getPreviewSet() const { return inPublicSetPreview() ? &mPreviewSet->mSet : nullptr; } const elementsmap_t* getPreviewSetElements() const { return inPublicSetPreview() ? &mPreviewSet->mElements : nullptr; } private: error readSets(JSON& j, map& sets); error readSet(JSON& j, Set& s); error readElements(JSON& j, map& elements); error readElement(JSON& j, SetElement& el); error readAllNodeMetadata(JSON& j, map& nodes); error readSingleNodeMetadata(JSON& j, SetElement::NodeMetadata& node); bool decryptNodeMetadata(SetElement::NodeMetadata& nodeMeta, const string& encryptionKey); error readExportedSet(JSON& j, Set& s); error readSetsPublicHandles(JSON& j, map& sets); error readSetPublicHandle(JSON& j, map& sets); void fixSetElementWithWrongKey(const Set& set); size_t decryptAllSets(map& newSets, map& newElements, map* nodeData); error decryptSetData(Set& s); error decryptElementData(SetElement& el, const string& setKey); string decryptKey(const string& encryptedKey, SymmCipher& cipher) const; bool decryptAttrs(const string& attrs, const string& decrKey, string_map& output); string encryptAttrs(const string_map& attrs, const string& encryptionKey); void sc_asp(JSON& json); // AP after new or updated Set void sc_asr(JSON& json); // AP after removed Set void sc_aep(JSON& json); // AP after new or updated Set Element void sc_aer(JSON& json); // AP after removed Set Element void sc_ass(JSON& json); // AP after exported set update bool initscsets(); bool fetchscset(string* data, uint32_t id); bool updatescsets(); void notifypurgesets(); void notifyset(Set*); vector setnotify; map mSets; // indexed by Set id bool initscsetelements(); bool fetchscsetelement(string* data, uint32_t id); bool updatescsetelements(); void notifypurgesetelements(); void notifysetelement(SetElement*); void clearsetelementnotify(handle setID); vector setelementnotify; map mSetElements; // indexed by Set id, then Element id struct SetLink { handle mPublicId = UNDEF; // same as mSet.mPublicId once fetched string mPublicKey; string mPublicLink; Set mSet; elementsmap_t mElements; }; unique_ptr mPreviewSet; bool isElementInPreviewSet(handle eid) const { return mPreviewSet && (mPreviewSet->mElements.find(eid) != end(mPreviewSet->mElements)); } // -------- end of Sets and Elements // Generates a key pair (x25519 (Cu) key pair) to use for Vpn Credentials (MegaClient::putVpnCredential) StringKeyPair generateVpnKeyPair(); /** * @brief Stores json serialized `data` into the `attrs` `NODE_ATTR_PASSWORD_MANAGER` field * * @param attrs The node attribute map to write the serialized password data * @param data The attribute map containing password-related information. */ void preparePasswordManagerNodeData(attr_map& attrs, const AttrMap& data) const; /** * @brief Checks preconditions for node rename operations on the given node. * * @return Possible returned error codes: * - API_EPAYWALL: If over disk quota paywall * - API_EARGS: If the given node is nullptr * - API_EACCESS: If the node is not full accessible * - API_OK: No problems to rename the node */ error checkRenameNodePrecons(std::shared_ptr n); // Get a string to complete sc/wsc url and receive partial action packages // It's used from clients of type PWD and VPN std::string getPartialAPs(); public: /* Mega VPN methods */ // Call "vpnr" command. void getVpnRegions(CommandGetVpnRegions::Cb&& = nullptr /* Completion */); // Call "vpng" command. void getVpnCredentials(CommandGetVpnCredentials::Cb&& = nullptr /* Completion */); // Call "vpnp" command. void putVpnCredential(std::string&& /* VPN Region */, CommandPutVpnCredential::Cb&& = nullptr /* Completion */); // Call "vpnd" command. void delVpnCredential(int /* SlotID */, CommandDelVpnCredential::Cb&& = nullptr /* Completion */); // Call "vpnc" command. void checkVpnCredential(std::string&& /* User Public Key */, CommandCheckVpnCredential::Cb&& = nullptr /* Completion */); /** * @brief Generates a VPN credential string equivalent to the .conf file generated by the * webclient. This method is meant to be called with the credential details obtained from * CommandPutVpnCredential. * * Content: * [Interface] * PrivateKey = User Private Key * Address = IPv4, IPv6 of the Region * DNS = DNS of the Cluster * * [Peer] * PublicKey = Cluster Public Key * AllowedIPs = 0.0.0.0/0, ::/0 * Endpoint = host of the Cluster * * @note These VPN credential details are not the same than the data obtained from * MegaClient::getVpnCredentials() * @see MegaClient::putVpnCredential() * @return The string with the VPN credentials specified above. */ string generateVpnCredentialString( const std::string& /* host */, const std::vector& /* DNS */, std::string&& /* IPv4 */, std::string&& /* IPv6 */, StringKeyPair&& /* Peer Key Pair */); void getNetworkConnectivityTestServerInfo( CommandGetNetworkConnectivityTestServerInfo::Completion&& completion); void runNetworkConnectivityTest( std::function&& completion); void sendNetworkConnectivityTestEvent(const NetworkConnectivityTestResults& results); /* Mega VPN methods END */ void fetchCreditCardInfo(CommandFetchCreditCardCompletion completion); void setProFlexi(bool newProFlexi); // Password Manager static const char* const NODE_ATTR_PASSWORD_MANAGER; static constexpr std::string_view PWM_ATTR_NODE_TYPE{"t"}; static constexpr std::string_view PWM_ATTR_NODE_TYPE_CREDIT_CARD{"c"}; static constexpr std::string_view PWM_ATTR_CREDIT_CARD_NUMBER{"nu"}; static constexpr std::string_view PWM_ATTR_CREDIT_NOTES{"n"}; static constexpr std::string_view PWM_ATTR_CREDIT_CARD_HOLDER{"u"}; static constexpr std::string_view PWM_ATTR_CREDIT_CVV{"cvv"}; static constexpr std::string_view PWM_ATTR_CREDIT_EXP_DATE{"exp"}; static constexpr std::string_view PWM_ATTR_PASSWORD_NOTES{"n"}; static constexpr std::string_view PWM_ATTR_PASSWORD_URL{"url"}; static constexpr std::string_view PWM_ATTR_PASSWORD_USERNAME{"u"}; static constexpr std::string_view PWM_ATTR_PASSWORD_PWD{"pwd"}; static constexpr std::string_view PWM_ATTR_PASSWORD_TOTP{"totp"}; static constexpr std::string_view PWM_ATTR_PASSWORD_TOTP_SHSE{"shse"}; static constexpr std::string_view PWM_ATTR_PASSWORD_TOTP_EXPT{"t"}; static constexpr std::string_view PWM_ATTR_PASSWORD_TOTP_HASH_ALG{"alg"}; static constexpr std::string_view PWM_ATTR_PASSWORD_TOTP_NDIGITS{"nd"}; enum class PwmEntryType { PASSWORD, CREDIT_CARD, }; /** * @brief This method converts the value in the field PWM_ATTR_NODE_TYPE into a PwmEntryType. If * the value is not expected this method returns nullopt */ static std::optional toPwmEntryType(const std::optional& t) { if (!t) // Password entries have no PWM_ATTR_NODE_TYPE field return PwmEntryType::PASSWORD; if (*t == PWM_ATTR_NODE_TYPE_CREDIT_CARD) return PwmEntryType::CREDIT_CARD; return std::nullopt; } /** * @brief Checks if the given `data` stores the provided `type` of PWM node * @note `data` must be an AttrMap obtained from reading the NODE_ATTR_PASSWORD_MANAGER node * attribute. */ static bool isPwmDataOfType(const AttrMap& data, const PwmEntryType type) { return toPwmEntryType(data.getStringView(MegaClient::PWM_ATTR_NODE_TYPE)) == type; } NodeHandle getPasswordManagerBase(); void createPasswordManagerBase(int rtag, CommandCreatePasswordManagerBase::Completion cbRequest); /** @brief The same as updatePasswordNode but for updating a Credit Card Node */ error updateCreditCardNode(const NodeHandle nh, std::unique_ptr newData, CommandSetAttr::Completion&& cb); /** * @brief Updates the password node data stored in the node with the given `nh` with the * provided `newData` * * @param nh Handle of the node to update * @param newData Map with the new data. Notice that fields set to empty strings will cause the * data of that field to be removed. * @param cb The callback to forward to the `setattr` call. * @return An error code that can be: * - API_EARGS, if: * + newData is empty or nullptr * + The node to update is not a password node * + The updates leave the node in an invalid state * - All the possible codes returned by MegaClient::checkRenameNodePrecons * - All the possible codes returned by MegaClient::setattr */ error updatePasswordNode(const NodeHandle nh, std::unique_ptr newData, CommandSetAttr::Completion&& cb); // Data type to call putnodes and create password nodes using ValidPasswordData = std::map>; // Data type to handle wrongly formatted password info. Key: info, val: ErrCode using BadPasswordData = std::map; // Validator for data of a new PasswordManagerNode using PasswordDataValidator = std::function; // Check createPasswordEntries error createPasswordEntry(const char* name, std::unique_ptr data, MegaClient::PasswordDataValidator dataValidator, std::shared_ptr nParent, int rtag); /** * @brief Creates multiple PasswordManagerNode instances with a single putnodes call * * @note API_EARGS will be returned if: * - nParent is not a PasswordManagerNode folder * - If any of the given values in data is invalid, e.g., the password field is missing * * @param data A map with the name of the password entry to create as key and the information of * the password (AttrMap) as values. * @param dataValidator a std::function to validate the given data before creating a new * PasswordManagerNode. Note: The same validator will apply to all the entries in the data * parameter. * @param nParent The parent node that will contain the nodes to be created * @param rTag tag parameter for putnodes call * @return error code (API_OK if succeeded) */ error createPasswordEntries(ValidPasswordData&& data, PasswordDataValidator dataValidator, std::shared_ptr nParent, int rTag); using ImportPaswordResult = std::tuple; /** * @brief Import password nodes from a file * * @param filePath Path to the file with the entries to import * @param source Specifies the source of the input file (e.g. `FileSource::GOOGLE_PASSWORD`) * @param parentHandle The handle of the parent node that will contain the imported entries * @param rTag tag parameter for putnodes call * @return std::tuple with * - error: An error code indicating that the whole import operation failed. * + API_EARGS: * * The given parent handle doesn't map to a node * * Invalid file format for the given source * * No valid entries in the file * + API_EREAD: * * The input file cannot be opened * + API_EACCESS: * * Problem loading the file once opened, e.g. invalid header * + Errors returned by `MegaClient::createPasswordNodes` * - BadPasswordData: A vector of pairs with the lines that were not * successfully parsed (first) and an error code (second) to specify the reason. * - size_t: The number of successfully parsed entries that were sent to putnodes * @note This number can be 0, in that case, no call to putnodes will be done. */ ImportPaswordResult importPasswordsFromFile(const std::string& filePath, const pwm::import::FileSource source, const NodeHandle parentHandle, const int rTag); /** * @brief Validates the format of TOTP data stored in the given attribute map. * * This function checks the validity of essential TOTP fields: * - Shared secret (`PWM_ATTR_PASSWORD_TOTP_SHSE`) * - Number of digits (`PWM_ATTR_PASSWORD_TOTP_NDIGITS`) * - Expiry time (`PWM_ATTR_PASSWORD_TOTP_EXPT`) * - Hash algorithm (`PWM_ATTR_PASSWORD_TOTP_HASH`) * * If any field is ill-formed, an appropriate error code is returned. * * @param data The attribute map containing TOTP-related information. * @return PasswordEntryError An error code indicating validation status: * - `OK` if all fields are correctly formed. * - `INVALID_TOTP_SHARED_SECRET` if the shared secret is invalid. * - `INVALID_TOTP_NDIGITS` if the number of digits is invalid. * - `INVALID_TOTP_EXPT` if the expiration time is invalid. * - `INVALID_TOTP_ALG` if the hash algorithm is invalid. * * @note Uses `totp::validateFields` to check field validity. */ static PasswordEntryError validateTotpDataFormat(const AttrMap& data); /** * @brief Validates the TOTP data for a new node. * * This function checks if the TOTP shared secret (`PWM_ATTR_PASSWORD_TOTP_SHSE`) * is present in the provided attribute map. If it is missing, an error is returned. * Otherwise, it validates the rest of the fields by calling `validateTotpDataFormat`. * * @param data The attribute map containing TOTP data attr map. * @return PasswordEntryError An error code indicating validation status: * - `MISSING_TOTP_SHARED_SECRET` if the shared secret is missing. * - `MISSING_TOTP_NDIGITS` if the number of digits is missing. * - `MISSING_TOTP_EXPT` if the expiration time is missing. * - `MISSING_TOTP_HASH_ALG` if the hashing algorithm is missing. * - The result of `validateTotpDataFormat(data)`, which checks other fields. */ static PasswordEntryError validateNewNodeTotpData(const AttrMap& data); /** * @brief Ensures the given data can be used to create a new password node. * * For instance, the password field is mandatory and must be present. * * @param data The map with the password data * @return The error code for the validation */ static PasswordEntryError validateNewPasswordNodeData(const AttrMap& data); /** * @brief validateNewCreditCardNodeData * @param data * @return */ static PasswordEntryError validateNewCreditCardNodeData(const AttrMap& data); /** * @brief Processes the input password entries and splits them into two containers (bad, good). * * This method is designed to process the input entries from the mResults member of the * pwm::import::PassFileParseResult class. * * Entries that have any issues (e.g., problems parsing them, missing mandatory fields) are * returned in the .first member of the return value. This is a map with the original content * that caused the problem as the key and the error code as the value. * * Entries that are ready to create a new password node are placed in the .second container. * This is again a map with the final name of the node (ensuring no name conflicts) as the keys * and the data as the values. * * @param entries A container such as the mResults member of the * pwm::import::PassFileParseResult class. * @param nameValidator The functor that knows the state of the target parent node and handles * name clashing issues. Note: This can be generalized to any object that defines operator() * and takes and returns a std::string. * @return A pair of [badEntries, goodEntries]. */ static std::pair validatePasswordEntries(std::vector&& entries, ncoll::NameCollisionSolver& nameValidator); static std::string generatePasswordChars(const bool useUpper, const bool useDigits, const bool useSymbols, const unsigned int length); void setEnabledNotifications(std::vector&& notifs) { mEnabledNotifications = std::move(notifs); } const std::vector& getEnabledNotifications() const { return mEnabledNotifications; } void getNotifications(CommandGetNotifications::ResultFunc onResult); std::pair getFlag(const char* flagName); void getActiveSurveyTriggerActions( CommandGetActiveSurveyTriggerActions::Completion&& completion); void getSurvey(unsigned int triggerActionId, CommandGetSurvey::Completion&& completion); void answerSurvey(const CommandAnswerSurvey::Answer& answer, CommandAnswerSurvey::Completion&& completion); void getMyIp(CommandGetMyIP::Cb&& completion); void getSubscriptionCancellationDetails( const char* originalTransactionId, unsigned int gatewayId, CommandGetSubscriptionCancellationDetails::CompletionCallback&& completion); // Queue a CS command for transmission. // // This function takes ownership of its command parameter. void queueCommand(Command* command); // Client adapter. common::ClientAdapter mClientAdapter; // File Service. file_service::FileService mFileService; // FUSE service. fuse::Service mFuseService; private: #ifdef ENABLE_SYNC using GetJSCDataCallback = std::function; /** * @brief * This function will retrieve the user's JSCD user attributes and pass * them to the provided callback. * * If the user does not have any JSCD user attributes, this function * will create them and pass them to the provided callback. * * @param callback * The function that should receive the user's JSCD user attributes. */ void getJSCData(GetJSCDataCallback callback); /** * @brief * This function will create the user's JSCD user attributes. If * successful, the new attributes will be forwarded to the provided * callback. If not, the reason why the attributes couldn't be created * will be forwarded to the provided callback. * * @param callback * The function that should receive the user's JSCD user attributes or * the reason why those attributes couldn't be created. */ void createJSCData(GetJSCDataCallback callback); using InjectJSCDataCallback = std::function; /** * @brief * The purpose of this function is to be executed just after getting/setting * the keys but before enabling the syncs. * * One of these duties is to ensure that the user has a set of JSCD user * attributes, another is to safely inject these attributes along with * the client's master key into the sync engine. * * @param callback * The function that should be called when the task has completed. */ void injectSyncSensitiveData(InjectJSCDataCallback callback); /** * @brief * This function is called after the user's JSCD user attributes have * been created. If the attributes were created successfully, their * content is passed to the provided callback. If not, the error * received by this function is passed to the provided callback. * * @param callback * The function that should receive the user's JSCD user attributes or * the reason why those attributes couldn't be created. * * @param result * The result of our attempt to create the user's JSCD user attributes. */ void JSCDataCreated(GetJSCDataCallback& callback, Error result); /** * @brief * This function is called when the user's JSCD user attributes have * been retrieved. If successful, the JSCD user attributes are * destructured and passwed to the provided callback. If not, the reason * why the attributes could not be retrieved is passed to the provided * callback. * * @param callback * The function that receive the user's JSCD user attributes or the * reason why those attributes could not be retrieved. * * @param result * The result of our attempt to retrieve the user's JSCD user * attributes. * * @param store * The TLV store containing the user's JSCD user attributes. */ void JSCDataRetrieved(GetJSCDataCallback& callback, Error result, std::unique_ptr store); #endif // ENABLE_SYNC // Last known capacity retrieved from the cloud. m_off_t mLastKnownCapacity = -1; }; } // namespace #define char_is_not_digit [](unsigned char c) { return !::mega::is_digit(c); } #define char_is_not_space [](unsigned char c) { return !::mega::is_space(c); } #endif sdk-10.11.0/include/mega/name_collision.h000066400000000000000000000127101516266226600201500ustar00rootroot00000000000000/** * @file * @brief This file defines a set of utilities to deal with name collisions * * Namespace -> mega::ncoll(name collision) */ #ifndef MEGA_PWM_IMPORT_NAME_COLLISION_H #define MEGA_PWM_IMPORT_NAME_COLLISION_H 1 #include #include #include namespace mega::ncoll { using nameId_t = unsigned int; // Type for numbers attached to names when a collision is found enum class ENameType { baseNameOnly, // "test" withIdNoSpace, // "test(1)" withIdSpace, // "test (1)" }; /** * @brief Takes a name for a potential node and finds its base name and an identifier if * present. Also classifies it according to ENameType. * * The identifier is a number between parentheses placed at the end of the name. There can be a * space before it or not. * 0 is a valid identifier. * * Examples: * - "test" -> {"test", baseNameOnly, 0} * - "test (1)" -> {"test", withIdSpace, 1} * - "test (0)" -> {"test (0)", withIdSpace, 0} * - "test(2)" -> {"test(2)", withIdNoSpace, 0} * - "test (3)" -> {"test ", withIdSpace, 3} * * @param input The name to split * @return a tuple with [base name, the type of the input name, id] */ std::tuple getBaseNameKindId(const std::string& input); /** * @class NewFreeIndexProvider * @brief Helper class to keep track of the indices that are being used and also to retrieve from it * the next available index that should be used. * * The class manages two independent list of indices, the ones for names that fall into the * ENameType::withIdNoSpace and the ones for ENameType::withIdSpace. Also it tracks if the raw * base name is occupied. */ class NewFreeIndexProvider { public: /** * @brief Updates the internal occupied information * @note if kind is ENameType::baseNameOnly the index is not used */ void addOccupiedIndex(const ENameType kind, const nameId_t index); /** * @brief Returns the next available index and adds it to the list of occupied states * * This will return the lowest index that is not in the list of occupied but always greater or * equal to the given minimumAllowed */ nameId_t getNextFreeIndex(const ENameType kind, nameId_t minimumAllowed); /** * @brief Returns true if the index is not in the internal occupied information */ bool isFree(const ENameType kind, const nameId_t index) const; private: /** * @brief In the following lines members to keep track of the occupied state are defined. * Let's keep it simple for now and store all the occupied indices. * This could be optimized in the future if needed, for example, by storing occupied ranges * instead of individual indices. * * @note The elements in the vectors are sorted from lowest to highest */ std::vector occupiedIndicesNoSpace; std::vector occupiedIndicesSpace; bool baseNameOccupied{false}; }; /** * @class NameCollisionSolver * @brief A class to solve name clashing issues. * * The use case for this class is the following scenario: * - You have a location (destiny) where you want to put new elements (source). * - The elements have names. * - There can not be elements with the same name in the destiny. * - You need to process one by one the elements in source to know which is the name it must * have in the destiny to avoid name collisions. * * Usage example: * 1. Create an object. You can create it with a list of already existing names in a target * destiny. * 2. Iterate through the elements in your source list and call the operator() to obtain the * fixed name. * * Name resolution logic: * - If source name has no "(N)" in it, " (N)" is appended to the name. N is the lowest number * available (starting from 1) that avoids collision. * - If source name has "(N)" in it, that N gets increased until a free number is found. * */ class NameCollisionSolver { public: NameCollisionSolver() = default; NameCollisionSolver(const std::vector& existingNames); /** * @brief Returns the name that the element "nameToValidate" must have to avoid name collisions * in the destiny. * @note This is not a const method, i.e. it changes the internal state of the object. Be * careful calling this operator twice with the same source name. */ std::string operator()(const std::string& nameToValidate); private: /** * @brief For each base name (name without "(N)") we store an index provider */ std::map indexProviders; }; /** * @class FileNameCollisionSolver * @brief Same idea as NameCollisionSolver but to operate on names that may have an extension (such * as file names). * * The difference is that instead of adding " (N)" at the end of the name, it is added before the * extension (if it exists). Example: test.txt -> test (1).txt * * See NameCollisionSolver documentation for additional information * * @note This class shares the interface with NameCollisionSolver but this interface is not defined * to avoid vtable lookups. If you find it useful, consider defining it. */ class FileNameCollisionSolver { public: FileNameCollisionSolver() = default; FileNameCollisionSolver(const std::vector& existingNames); std::string operator()(const std::string& nameToValidate); private: std::map indexProviders; }; } #endif sdk-10.11.0/include/mega/name_id.h000066400000000000000000000046231516266226600165550ustar00rootroot00000000000000#ifndef MEGA_NAME_ID_H #define MEGA_NAME_ID_H #include #include #include namespace mega { // numeric representation of string (up to 8 chars) using nameid = uint64_t; constexpr nameid makeNameid(std::string_view name) { // name must have at most size 8 assert(name.size() <= 8); nameid id = 0; for (size_t n = 0; n < name.size(); ++n) { id = (id << 8) + static_cast(name[n]); } return id; } namespace name_id { static constexpr nameid ipc = makeNameid("ipc"); static constexpr nameid c = makeNameid("c"); static constexpr nameid upci = makeNameid("upci"); static constexpr nameid upco = makeNameid("upco"); static constexpr nameid share = makeNameid("share"); static constexpr nameid dshare = makeNameid("dshare"); static constexpr nameid put = makeNameid("put"); static constexpr nameid d = makeNameid("d"); static constexpr nameid u = makeNameid("u"); static constexpr nameid psts = makeNameid("psts"); static constexpr nameid psts_v2 = makeNameid("psts_v2"); static constexpr nameid pses = makeNameid("pses"); static constexpr nameid ph = makeNameid("ph"); static constexpr nameid ass = makeNameid("ass"); #ifdef ENABLE_CHAT static constexpr nameid mcsmp = makeNameid("mcsmp"); static constexpr nameid mcsmr = makeNameid("mcsmr"); #endif } // namespace name_id // convert 1...8 character ID to int64 integer (endian agnostic) // // @deprecated: Symbols below have been deprecated. Use the more generic functinality above to // obtain nameid-s #define MAKENAMEID1(a) (nameid)(a) #define MAKENAMEID2(a, b) (nameid)(((a) << 8) + (b)) #define MAKENAMEID3(a, b, c) (nameid)(((a) << 16) + ((b) << 8) + (c)) #define MAKENAMEID4(a, b, c, d) (nameid)(((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) #define MAKENAMEID5(a, b, c, d, e) \ (nameid)((((nameid)a) << 32) + ((b) << 24) + ((c) << 16) + ((d) << 8) + (e)) #define MAKENAMEID6(a, b, c, d, e, f) \ (nameid)((((nameid)a) << 40) + (((nameid)b) << 32) + ((c) << 24) + ((d) << 16) + ((e) << 8) + (f)) #define MAKENAMEID7(a, b, c, d, e, f, g) \ (nameid)((((nameid)a) << 48) + (((nameid)b) << 40) + (((nameid)c) << 32) + ((d) << 24) + \ ((e) << 16) + ((f) << 8) + (g)) #define MAKENAMEID8(a, b, c, d, e, f, g, h) \ (nameid)((((nameid)a) << 56) + (((nameid)b) << 48) + (((nameid)c) << 40) + (((nameid)d) << 32) + \ ((e) << 24) + ((f) << 16) + ((g) << 8) + (h)) } // namespace mega #endif // MEGA_NAME_ID_H sdk-10.11.0/include/mega/network_connectivity_test.h000066400000000000000000000043721516266226600225100ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_NETWORK_CONNECTIVITY_TEST_H #define MEGA_NETWORK_CONNECTIVITY_TEST_H #include "mega/network_connectivity_test_helpers.h" #include "mega/udp_socket_tester.h" #include namespace mega { /** * @brief Mechanism for running a network connectivity test and providing results in a predefined * format. * It includes: * - run the entire test suite on each socket; * - gather test results from all sockets; * - encapsulate the logic for further condensing and summarizing the results. */ class NetworkConnectivityTest { public: bool start(UdpSocketTester::TestSuite&& testSuite, const NetworkConnectivityTestServerInfo& serverInfo); const NetworkConnectivityTestResults& getResults(); private: NetworkConnectivityTestIpResults getTestResults(std::shared_ptr dnsTester, std::vector>& testers, char summaryPrefix); static void updateStatus(int error, NetworkConnectivityTestMessageStatus& status); static bool isNetworkUnreachable(int errorCode); std::string getSummary(char ipPrefix, const std::vector& results); std::shared_ptr mSocketTesterIPv4Dns; std::vector> mSocketTestersIPv4; std::shared_ptr mSocketTesterIPv6Dns; std::vector> mSocketTestersIPv6; uint16_t mTestsPerSocket{}; std::chrono::high_resolution_clock::time_point mTimeoutOfReceive; NetworkConnectivityTestResults mResults; }; } // namespace mega #endif // MEGA_NETWORK_CONNECTIVITY_TEST_H sdk-10.11.0/include/mega/network_connectivity_test_helpers.h000066400000000000000000000027201516266226600242250ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_NETWORK_CONNECTIVITY_TEST_HELPERS_H #define MEGA_NETWORK_CONNECTIVITY_TEST_HELPERS_H #include #include namespace mega { struct NetworkConnectivityTestServerInfo { std::string ipv4; std::string ipv6; std::vector ports; }; enum class NetworkConnectivityTestMessageStatus : int { NOT_RUN = -9999, PASS = 0, FAIL, NET_UNREACHABLE, }; struct NetworkConnectivityTestIpResults { NetworkConnectivityTestMessageStatus messages{NetworkConnectivityTestMessageStatus::NOT_RUN}; NetworkConnectivityTestMessageStatus dns{NetworkConnectivityTestMessageStatus::NOT_RUN}; std::string summary; std::vector socketErrors; }; struct NetworkConnectivityTestResults { NetworkConnectivityTestIpResults ipv4; NetworkConnectivityTestIpResults ipv6; }; } // namespace mega #endif // MEGA_NETWORK_CONNECTIVITY_TEST_HELPERS_H sdk-10.11.0/include/mega/node.h000066400000000000000000001352351516266226600161120ustar00rootroot00000000000000 /** * @file mega/node.h * @brief Classes for accessing local and remote nodes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_NODE_H #define MEGA_NODE_H 1 #include "attrmap.h" #include "backofftimer.h" #include "file.h" #include "filefingerprint.h" #include "syncfilter.h" #include "syncinternals/mac_computation_state.h" #include "syncinternals/syncuploadthrottlingfile.h" #include "utils.h" #include #include #include namespace mega { typedef map localnode_map; struct MEGA_API NodeCore { // node's own handle handle nodehandle = UNDEF; // inline convenience function to get a typed version that ensures we use the 6 bytes of a node handle, and not 8 NodeHandle nodeHandle() const { return NodeHandle().set6byte(nodehandle); } // parent node handle (in a Node context, temporary placeholder until parent is set) handle parenthandle = UNDEF; // inline convenience function to get a typed version that ensures we use the 6 bytes of a node handle, and not 8 NodeHandle parentHandle() const { return NodeHandle().set6byte(parenthandle); } // node type nodetype_t type = TYPE_UNKNOWN; // node attributes std::unique_ptr attrstring; }; struct CloudNode { // We can't use Node* directly on the sync thread,as such pointers // may be rendered dangling (and changes in Nodes thread-unsafe) // by actionpackets on the MegaClient thread. // So, we take temporary copies of the minimally needed aspects. // These are only used while recursing the LocalNode tree. string name; nodetype_t type = TYPE_UNKNOWN; NodeHandle handle; NodeHandle parentHandle; nodetype_t parentType = TYPE_UNKNOWN; FileFingerprint fingerprint; CloudNode() {} CloudNode(const Node& n); // Query whether this cloud node represents an ignore file. bool isIgnoreFile() const; }; // new node for putnodes() struct MEGA_API NewNode : public NodeCore { string nodekey; newnodesource_t source = NEW_NODE; NodeHandle ovhandle; UploadHandle uploadhandle; UploadToken uploadtoken; string fileattributes; // versioning used for this new node, forced at server's side regardless the account's value VersioningOption mVersioningOption = NoVersioning; bool added = false; // set true when the actionpacket arrives bool canChangeVault = false; handle mAddedHandle = UNDEF; // updated as actionpacket arrives error mError = API_OK; // per-node error (updated in cs response) bool hasZeroKey() const; }; struct MEGA_API PublicLink { handle ph; m_time_t cts; m_time_t ets; bool takendown; string mAuthKey; PublicLink(handle ph, m_time_t cts, m_time_t ets, bool takendown, const char *authKey = nullptr); PublicLink(const PublicLink& plink) = default; bool isExpired(); }; struct NodeCounter { m_off_t storage = 0; m_off_t versionStorage = 0; size_t files = 0; size_t folders = 0; size_t versions = 0; void operator += (const NodeCounter&); void operator -= (const NodeCounter&); std::string serialize() const; NodeCounter(const std::string& blob); NodeCounter() = default; }; typedef std::multiset fingerprintNoMtime_set; typedef fingerprintNoMtime_set::iterator FingerprintPosition; class NodeManagerNode { public: NodeManagerNode(NodeManager& nodeManager, NodeHandle nodeHandle); // Instances of this class cannot be copied std::unique_ptr> mChildren; bool mAllChildrenHandleLoaded = false; void setNode(shared_ptr node); shared_ptr getNodeInRam(bool updatePositionAtLRU = true); NodeHandle getNodeHandle() const; std::list >::const_iterator mLRUPosition; private: NodeHandle mNodeHandle; NodeManager& mNodeManager; weak_ptr mNode; }; typedef std::map::iterator NodePosition; struct CommandChain { // convenience functions, hides the unique_ptr aspect, removes it when empty bool empty() const { return !chain || chain->empty(); } void push_back(Command* c) { if (!chain) { chain.reset(new std::list); } chain->push_back(c); } void erase(Command* c) { if (chain) { for (auto i = chain->begin(); i != chain->end(); ++i) { if (*i == c) { chain->erase(i); if (chain->empty()) { chain.reset(); } return; } } } } void forEachCommand(const std::function& cmdFunction) const { if (chain) { for (auto& cmd : *chain) { cmdFunction(cmd); } } } private: // most nodes don't have commands in progress so keep representation super small std::unique_ptr> chain; }; // filesystem node struct MEGA_API Node : public NodeCore, FileFingerprint { // Define what shouldn't be logged enum LogCondition : uint32_t { LOG_CONDITION_NONE = 0, // NONE: all is logged LOG_CONDITION_DISABLE_NO_KEY = 1 // NO KEY is not logged }; static const std::string BLANK; static const std::string CRYPTO_ERROR; static const std::string NO_KEY; // It's important to not perform copies of this class. We use shared_ptr in // lot of places, so making a new copy may affect to the lifecycle of the instance and it may // have severel side-effects in different points of SDK. Node(const Node&) = delete; Node& operator=(const Node&) = delete; MegaClient* client = nullptr; // supplies the nodekey (which is private to ensure we track changes to it) const string& nodekey() const; // Also returns the key but does not assert that the key has been applied. Only use it where we don't need the node to be readable. const string& nodekeyUnchecked() const; // check if the key is present and is the correct size for this node bool keyApplied() const; // Check whether the node key is a zero key or was generated by a zero key (so it is a bad key and it will be rejected by the API). bool hasZeroKey() const; // Static version of the above for related node classes. static bool hasZeroKey(const string& nodekeydata); // change parent node association. updateNodeCounters is false when called from NodeManager::unserializeNode bool setparent(std::shared_ptr , bool updateNodeCounters = true); // follow the parent links all the way to the top const Node* firstancestor() const; // If this is a file, and has a file for a parent, it's not the latest version std::shared_ptr latestFileVersion() const; // Node's depth, counting from the cloud root. unsigned depth() const; // try to resolve node key string bool applykey(); // Returns false if the share key can't correctly decrypt the key and the // attributes of the node. Otherwise, it returns true. There are cases in // which it's not possible to check if the key is valid (for example when // the node is already decrypted). In those cases, this function returns // true, because it is intended to discard outdated share keys that could // make nodes undecryptable until the next full reload. That way, nodes // can be decrypted when the updated share key is received. bool testShareKey(const byte* shareKey); // set up nodekey in a static SymmCipher SymmCipher* nodecipher(); // decrypt attribute string, set fileattrs and save fingerprint void setattr(); // display name (UTF-8) const char* displayname(LogCondition log = LOG_CONDITION_NONE) const; // check if the name matches (UTF-8) bool hasName(const string&) const; // check if this node has a name. bool hasName() const; // display path from its root in the cloud (UTF-8) string displaypath() const; // match mimetype type // checkPreview flag is only compatible with MimeType_t::MIME_TYPE_PHOTO bool isIncludedForMimetype(MimeType_t mimetype, bool checkPreview = false) const; // node attributes AttrMap attrs; static const vector attributesToCopyIntoPreviousVersions; // 'sen' attribute bool isMarkedSensitive() const; bool isSensitiveInherited() const; // {backup-id, state} pairs received in "sds" node attribute vector> getSdsBackups() const; static nameid sdsId(); static string toSdsString(const vector>&); // track upcoming attribute changes for this node, so we can reason about current vs future state CommandChain mPendingChanges; // owner handle owner = mega::UNDEF; // actual time this node was created (cannot be set by user) m_time_t ctime = 0; // file attributes string fileattrstring; // check presence of file attribute int hasfileattribute(fatype) const; static int hasfileattribute(const string *fileattrstring, fatype); // decrypt node attribute string static byte* decryptattr(SymmCipher*, const char*, size_t); // parse node attributes from an incoming buffer, this function must be called after call decryptattr // fingerprint output param is a raw fingerprint (i.e. without App prefixes) static void parseattr(byte* bufattr, AttrMap& attrs, m_off_t size, m_time_t& mtime, string& fileName, string& fingerprint, FileFingerprint& ffp); // inbound share unique_ptr inshare; // outbound shares by user unique_ptr outshares; // outbound pending shares unique_ptr pendingshares; // incoming/outgoing share key unique_ptr sharekey; // app-private pointer void* appdata = nullptr; bool foreignkey = false; struct { bool removed : 1; bool attrs : 1; bool owner : 1; bool ctime : 1; bool fileattrstring : 1; bool inshare : 1; bool outshares : 1; bool pendingshares : 1; bool parent : 1; bool publiclink : 1; bool newnode : 1; bool name : 1; bool favourite : 1; #ifdef ENABLE_SYNC // this field is only used internally in syncdown() bool syncdown_node_matched_here : 1; #endif bool counter : 1; bool sensitive : 1; // this field also only used internally, for reporting new NO_KEY occurrences bool modifiedByThisClient : 1; bool pwd : 1; bool description : 1; bool tags : 1; } changed; void setKey(const string& key); void setkey(const byte*); void setkeyfromjson(const char*); void setfingerprint(); void faspec(string*); NodeCounter getCounter() const; void setCounter(const NodeCounter &counter); // to only be called by mNodeManger::setNodeCounter // parent shared_ptr parent; // own position in NodeManager::mFingerPrints (only valid for file nodes) // It's used for speeding up node removing at NodeManager::removeFingerprint FingerprintPosition mFingerPrintPosition; // own position in NodeManager::mNodes. The map can have an element of type NodeManagerNode // previously Node exists // It's used for speeding up get children when Node parent is known NodePosition mNodePosition; // check if node is below this node bool isbelow(const Node*) const; bool isbelow(NodeHandle) const; // handle of public link for the node unique_ptr plink; void setpubliclink(handle, m_time_t, m_time_t, bool, const string &authKey = {}); bool serialize(string*) const override; static std::shared_ptr unserialize(MegaClient& client, const string*, bool fromOldCache, std::list>& ownNewshares); Node(MegaClient&, NodeHandle, NodeHandle, nodetype_t, m_off_t, handle, const char*, m_time_t); ~Node(); int getShareType() const; using nodeCondition_t = std::function; /** * @brief Check if any of the ancestors of this node matches the give condition * * @param condition The condition to check on every ancestor. * @return Returns true if any of the ancestors of this node evaluates to true the given * condition, false otherwise. */ bool hasAncestorMatching(const nodeCondition_t& condition) const; /** * @brief Same as hasAncestorMatching but evaluates also the condition on this node. */ bool matchesOrHasAncestorMatching(const nodeCondition_t& condition) const { return condition(*this) || hasAncestorMatching(condition); } /** * @brief Check if any of the ancestors of this node has the given handle * * @param ancestorHandle The handle that is expected to match. * @return Returns true if any of the ancestors of this node has the given ancestorHandle */ bool isAncestor(const NodeHandle ancestorHandle) const { return hasAncestorMatching( [ancestorHandle](const Node& node) { return node.nodeHandle() == ancestorHandle; }); } /** * @brief Returns true if this node has the given nodeHandle or any of its ancestors have it. */ bool hasNHOrHasAncestorWithNH(const NodeHandle nh) const { return nodeHandle() == nh || isAncestor(nh); } // true for outshares, pending outshares and folder links (which are shared folders internally) bool isShared() const { return (outshares && !outshares->empty()) || (pendingshares && !pendingshares->empty()); } // Returns true if this node has a child with the given name. bool hasChildWithName(const string& name) const; // values that are used to populate the flags column in the database // for efficent searching enum { FLAGS_IS_VERSION = 0, // This bit is active if node is a version // i.e. the parent is a file not a folder FLAGS_IS_IN_RUBBISH = 1, // This bit is active if node is in rubbish bin // i.e. the root ansestor is the rubbish bin FLAGS_IS_MARKED_SENSTIVE = 2,// This bit is active if node is marked as sensitive // that is it and every descendent is to be considered // sensitive // i.e. the 'sen' attribute is set FLAGS_SIZE = 3 }; typedef std::bitset Flags; // check if any of the flags are set in any of the anesestors bool anyExcludeRecursiveFlag(Flags excludeRecursiveFlags) const; // should we keep the node // requiredFlags are flags that must be set // excludeFlags are flags that must not be set // excludeRecursiveFlags are flags that must not be set or set in a ansestor bool areFlagsValid(Flags requiredFlags, Flags excludeFlags, Flags excludeRecursiveFlags = Flags()) const; Flags getDBFlagsBitset() const; uint64_t getDBFlags() const; static uint64_t getDBFlags(uint64_t oldFlags, bool isInRubbish, bool isVersion, bool isSensitive); static bool getExtension(std::string& ext, const std::string& nodeName); static bool isPhoto(const std::string& ext); static bool isVideo(const std::string& ext); static bool isAudio(const std::string& ext); static bool isDocument(const std::string& ext); static bool isSpreadsheet(const std::string& ext); static bool isPdf(const std::string& ext); static bool isPresentation(const std::string& ext); static bool isArchive(const std::string& ext); static bool isProgram(const std::string& ext); static bool isMiscellaneous(const std::string& ext); static bool isOfMimetype(MimeType_t mimetype, const std::string& ext); static MimeType_t getMimetype(const std::string& ext); bool isPhotoWithFileAttributes(bool checkPreview) const; bool isCreditCardNode() const; bool isPasswordNode() const; bool isPasswordManagerNode() const; bool isPasswordManagerNodeFolder() const; private: // full folder/file key, symmetrically or asymmetrically encrypted // node crypto keys (raw or cooked - // cooked if size() == FOLDERNODEKEYLENGTH or FILEFOLDERNODEKEYLENGTH) string nodekeydata; // keeps track of counts of files, folder, versions, storage and version's storage NodeCounter mCounter; static nameid getExtensionNameId(const std::string& ext); }; inline const string& Node::nodekey() const { assert(keyApplied() || type == ROOTNODE || type == VAULTNODE || type == RUBBISHNODE); return nodekeydata; } inline const string& Node::nodekeyUnchecked() const { return nodekeydata; } inline bool Node::keyApplied() const { return nodekeydata.size() == size_t((type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); } inline bool Node::hasZeroKey() const { return keyApplied() && SymmCipher::isZeroKey(reinterpret_cast(nodekeydata.data()), nodekeydata.size()); } inline bool Node::hasZeroKey(const string& nodekeydata) { return ((nodekeydata.size() == FILENODEKEYLENGTH) || (nodekeydata.size() == FOLDERNODEKEYLENGTH)) && SymmCipher::isZeroKey(reinterpret_cast(nodekeydata.data()), nodekeydata.size()); } // END MEGA_API Node class NodeData { public: NodeData(const char* ptr, size_t size, int component) : mStart(ptr), mEnd(ptr + size), mComp(component) {} m_time_t getMtime(); int getLabel(); std::string getDescription(); std::string getTags(); handle getHandle(); std::shared_ptr createNode(MegaClient& client, bool fromOldCache, std::list>& ownNewshares); enum { COMPONENT_ALL = -1, COMPONENT_NONE = COMPONENT_ALL, // dummy symbol useful where "all" makes no sense (i.e. when no migration is required) COMPONENT_ATTRS, COMPONENT_MTIME, COMPONENT_LABEL, COMPONENT_DESCRIPTION, COMPONENT_TAGS, }; private: bool readComponents(); bool readFailed() { return (mReadAttempted && !mReadSucceeded) || (!mReadAttempted && !readComponents()); } const char* mStart; const char* mEnd; int mComp; m_off_t mSize = 0; nodetype_t mType = TYPE_UNKNOWN; handle mHandle = 0; handle mParentHandle = 0; handle mUserHandle = 0; m_time_t mCtime = 0; string mNodeKey; char mIsExported = '\0'; char mIsEncrypted = '\0'; string mFileAttributes; string mAuthKey; std::unique_ptr mShareKey; int mShareDirection = INT_MAX; // valid values are -1 (outshares) and 0 (inshare) std::list> mShares; AttrMap mAttrs; string mAttrString; // encrypted attrs handle mPubLinkHandle = 0; m_time_t mPubLinkEts = 0; m_time_t mPubLinkCts = 0; bool mPubLinkTakenDown = false; bool mReadAttempted = false; bool mReadSucceeded = false; }; #ifdef ENABLE_SYNC enum TreeState { TREE_RESOLVED = 0, TREE_DESCENDANT_FLAGGED = 1, TREE_ACTION_HERE = 2, // And also check if any children have flags set (ie, implicitly TREE_DESCENDANT_FLAGGED) TREE_ACTION_SUBTREE = 3 // overrides any children so the whole subtree is processed }; inline TreeState updateTreestateFromChild(TreeState oldFlag, TreeState childFlag) { return oldFlag == TREE_RESOLVED && childFlag != TREE_RESOLVED ? TREE_DESCENDANT_FLAGGED : oldFlag; } inline TreeState propagateSubtreeFlag(TreeState nodeFlag, TreeState childFlag) { return nodeFlag == TREE_ACTION_SUBTREE ? TREE_ACTION_SUBTREE : TreeState(childFlag); } struct SyncRow; struct SyncPath; struct MEGA_API LocalNode; struct MEGA_API LocalNodeCore : public Cacheable { // deserialize attributes from binary storage. bool read(const string& source, uint32_t& parentID); // serialize attributes to binary for storage. bool write(string& destination, uint32_t parentID) const; // local filesystem node ID (inode...) for rename/move detection handle fsid_lastSynced = ::mega::UNDEF; // The exact name of the file we are synced with, if synced // If not synced then it's the to-local (escaped) version of the CloudNode's name // This is also the key in the parent LocalNode's children map // (if this is the sync root node, it is an absolute path - otherwise just a leaf name) LocalPath localname; // for botched filesystems with legacy secondary ("short") names // Filesystem notifications could arrive with long or short names, and we need to recognise which LocalNode corresponds. // null means either the entry has no shortname or it's the same as the (normal) longname std::unique_ptr slocalname = nullptr; // whether this node knew its shortname (otherwise it was loaded from an old db) bool slocalname_in_db = false; // related cloud node, if any NodeHandle syncedCloudNodeHandle; // The fingerprint of the node and/or file we are synced with FileFingerprint syncedFingerprint; // The fingreprint scanned from file system // In Android scannedFingerprint is modified to set proper mtime value (from download) // (it isn't possible set mtime in file system) FileFingerprint realScannedFingerprint; // FILENODE or FOLDERNODE nodetype_t type = TYPE_UNKNOWN; // Once the local and remote names match exactly (taking into account escaping), we will keep them matching // This is so users can, for example, change uppercase/lowercase and have that synchronized. bool namesSynchronized = false; }; // LocalNodeCore struct MEGA_API LocalNode : public LocalNodeCore { class Sync* sync = nullptr; // UTF8 NFC version of LocalNodeCore::localname. // Not serialized. // Should be updated whenever localname is. // Does not match the corresponding Node's name, // as escapes/case may be involved. string toName_of_localname; // parent linkage LocalNode* parent = nullptr; // children by name localnode_map children; unique_ptr cloneShortname() const; localnode_map schildren; // The last scan of the folder (for folders). // Removed again when the folder is fully synced. std::unique_ptr> lastFolderScan; // If we can regenerate the filsystem data at this node, no need to store it, save some RAM void clearRegeneratableFolderScan(SyncPath& fullPath, vector& childRows); fsid_localnode_map::iterator fsid_lastSynced_it; // we also need to track what fsid corresponded to our FSNode last time, even if not synced (not serialized) // if it changes, we should rescan, in case of LocalNode pre-existing with no FSNode, then one appears. Or, now it's different handle fsid_asScanned = ::mega::UNDEF; fsid_localnode_map::iterator fsid_asScanned_it; // Fingerprint of the file as of the last scan. TODO: does this make LocalNode too large? FileFingerprint scannedFingerprint; // related cloud node, if any nodehandle_localnode_map::iterator syncedCloudNodeHandle_it; // using a per-Localnode scan delay prevents self-notifications delaying the whole sync dstime scanDelayUntil = 0; unsigned expectedSelfNotificationCount = 0; //dstime lastScanTime = 0; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { // Already-synced syncs on startup should not re-fingerprint files that match the synced fingerprint by fsid/size/mtime bool oneTimeUseSyncedFingerprintInScan : 1; // Determines whether we refingerprint a file when it is scanned. bool recomputeFingerprint : 1; // needs another recursiveSync for scanning at this level after pending changes TreeState scanAgain : 3; // needs another recursiveSync() to check moves at this level after pending changes // (can only be cleared if all scanAgain flags are clear) TreeState checkMovesAgain : 3; // needs another recursiveSync() for deletes/uploads/downloads at this level after pending changes // (can only be cleared if all checkMoveAgain flags are clear) TreeState syncAgain : 3; // whether any name conflicts have been detected. TreeState conflicts : 3; // fsids have been assigned in this node. bool unstableFsidAssigned : 1; // disappeared from local FS; we are moving the cloud node to the trash. bool deletedFS : 1; // we saw this node moved/renamed in the cloud, local move expected (or active) bool moveApplyingToLocal : 1; // todo: do we need these anymore? bool moveAppliedToLocal : 1; unsigned scanInProgress : 1; unsigned scanObsolete : 1; // When recursing the tree, sometimes we need a node to set a flag in its parent // but, on other runs we skip over some nodes (eg. syncHere flag false) // however, we still need to compute the required flags for the parent node. // these flags record what the node still needs its parent to do in case it's not visited unsigned parentSetScanAgain : 1; unsigned parentSetCheckMovesAgain : 1; unsigned parentSetSyncAgain : 1; unsigned parentSetContainsConflicts : 1; // Set when we've created a new directory (say, as part of downsync) // that has reused this node's FSID. unsigned fsidSyncedReused : 1; unsigned fsidScannedReused : 1; // we can't delete a node immediately in case it's involved in a move // that we haven't detected yet. So we increment this counter // Once it's big enough then we are sure and can delete the LocalNode. unsigned confirmDeleteCount : 2; // If we detected+actioned a move, and this is the old node // we can't delete it directly as there may be references on the stack unsigned certainlyOrphaned : 1; // track whether we have ever scanned this folder // folders never scanned can issue a second scan request for this sync unsigned neverScanned : 1; // if we write a file with this name, and then checking the filename given back, it's different // that makes it impossible to sync properly. The user must be informed. // eg. Synology SMB network drive from windows, and filenames with trailing spaces unsigned localFSCannotStoreThisName : 1; }; #ifdef _MSC_VER #pragma warning(pop) #endif // Fields which are hardly ever used. // We keep the average memory use by only alloating these when used. struct RareFields { shared_ptr scanRequest; struct ScanBlocked { BackoffTimer scanBlockedTimer; LocalPath scanBlockedLocalPath; bool folderUnreadable = false; bool filesUnreadable = false; // There is only one shared_ptr so if the node is gone, // we can't look this up by weak_ptr. So this ptr is not dangling LocalNode* localNode = nullptr; Sync* sync = nullptr; ScanBlocked(PrnGen &rng, const LocalPath& lp, LocalNode* ln, Sync* s); }; shared_ptr scanBlocked; struct BadlyFormedIgnore { LocalPath localPath; // There is only one shared_ptr so if the node is gone, // we can't look this up by weak_ptr. So this ptr is not dangling Sync* sync = nullptr; BadlyFormedIgnore(const LocalPath& lp, Sync* s) : localPath(lp), sync(s) {} }; shared_ptr badlyFormedIgnoreFilePath; struct MoveInProgress { bool succeeded = false; bool failed = false; bool syncCodeProcessedResult = false; bool inProgress() { return !succeeded && !failed; } fsfp_ptr_t sourceFsfp; handle sourceFsid = UNDEF; nodetype_t sourceType = FILENODE; FileFingerprint sourceFingerprint; NodeHandle movedHandle; const void* sourcePtr = nullptr; // for ptr comparison only - could be dangling (actually LocalNode*) map priorChildrenToRemove; }; struct MovePending { MovePending(LocalPath&& sourcePath) : sourcePath(std::move(sourcePath)) { } LocalPath sourcePath; }; struct CreateFolderInProgress { NodeHandle succeededHandle; handle originalFsid = UNDEF; bool failed = false; CreateFolderInProgress(handle fsid) : originalFsid(fsid) {} }; struct DeleteToDebrisInProgress { // (actually if it's an inshare, we unlink() as there's no debris string pathDeleting; bool failed = false; bool succeeded = false; }; struct UnlinkInProgress { bool failed = false; bool succeeded = false; handle sourceFsid = UNDEF; nodetype_t sourceType = FILENODE; FileFingerprint sourceFingerprint; LocalNode* sourcePtr = nullptr; }; /** * @brief Tracks an ongoing incremental MAC (Message Authentication Code) computation. * * When comparing local and cloud files that differ only in mtime, we need to compute * the MetaMAC to determine if contents actually match. This computation can be slow * for large files, so it runs incrementally across sync iterations using worker threads. * * Architecture: * - Sync thread reads file in large chunks (10-20MB) with FILE_SHARE_DELETE * - Worker thread (mAsyncQueue) computes MACs for chunks * - Progress tracked across sync iterations * - File opened/closed per chunk to minimize lock duration * * Thread safety: * - Context fields are immutable after construction * - chunkInProgress flag: sync thread sets true before queueing, worker clears when done * - completed flag: worker sets when all chunks processed * - partialMacs protected by mutex (worker writes, sync reads only when !chunkInProgress) * * Lifetime: * - Sync thread holds shared_ptr in RareFields * - Worker thread captures weak_ptr in lambda * - If LocalNode is deleted, weak_ptr.lock() returns nullptr and computation is abandoned * * Note: MacComputationState is defined in syncinternals/mac_computation_state.h * for reuse in both CSF (here) and clone candidate (SyncUpload_inClient) scenarios. */ shared_ptr macComputation; weak_ptr movePendingFrom; shared_ptr movePendingTo; shared_ptr moveFromHere; shared_ptr moveToHere; shared_ptr createFolderHere; shared_ptr removeNodeHere; weak_ptr unlinkHere; // Filter rules applicable below this node. unique_ptr filterChain; // If we can tell what the filesystem renamed a downloaded file to LocalPath localFSRenamedToThisName; }; bool hasRare() { return !!rareFields; } RareFields& rare(); void trimRareFields(); void resetMacComputationIfAny(); // use this one to skip the hasRare check, if it doesn't exist a reference to a blank one is returned const RareFields& rareRO() const; /** * @brief Marks the node and optionally its relatives for scanning again. * * This method sets the scanning flag on the current node, its parent, and/or its descendants, * indicating that they need to be scanned for filesystem changes during the next * synchronization cycle. Optionally, you can specify a delay before the scan occurs. * * @param doParent If `true`, marks the parent node for scanning. * @param doHere If `true`, marks the current node for scanning. * @param doBelow If `true`, marks all descendant nodes for scanning. * @param delayds The delay in deciseconds before the scan should occur. If zero, the scan is * addressed in the next syncLoop iteration. */ void setScanAgain(bool doParent, bool doHere, bool doBelow, dstime delayds); /** * @brief Marks the node and optionally its relatives to recheck for moved or renamed items. * * This method sets the move checking flag on the current node, its parent, and/or its * descendants, indicating that they need to be rechecked for any moves or renames within the * synchronization scope. * * @param doParent If `true`, marks the parent node for move checking. * @param doHere If `true`, marks the current node for move checking. * @param doBelow If `true`, marks all descendant nodes for move checking. */ void setCheckMovesAgain(bool doParent, bool doHere, bool doBelow); /** * @brief Marks the node and optionally its relatives to be resynchronized. * * This method sets the synchronization flag on the current node, its parent, and/or its * descendants, indicating that they need to be synchronized with the remote server during the * next syncLoop iteration. * * @param doParent If `true`, marks the parent node for synchronization. * @param doHere If `true`, marks the current node for synchronization. * @param doBelow If `true`, marks all descendant nodes for synchronization. */ void setSyncAgain(bool doParent, bool doHere, bool doBelow); void setContainsConflicts(bool doParent, bool doHere, bool doBelow); void initiateScanBlocked(bool folderBlocked, bool containsFingerprintBlocked); bool checkForScanBlocked(FSNode* fsnode); // True if this subtree requires scanning. bool scanRequired() const; // True if this subtree could contain move sources or targets bool mightHaveMoves() const; // True if this subtree requires syncing. bool syncRequired() const; // Pass any TREE_ACTION_SUBTREE flags on to child nodes, so we can clear the flag at this level void propagateAnySubtreeFlags(); // Queue a scan request for this node if needed, and if a slot is available (just one per sync) // Also receive the results if they are ready bool processBackgroundFolderScan(SyncRow& row, SyncPath& fullPath); void reassignUnstableFsidsOnceOnly(const FSNode* fsnode); // current subtree sync state as last notified to OS treestate_t mReportedSyncState = TREESTATE_NONE; // check the current state treestate_t checkTreestate(bool notifyChangeToApp); void recursiveSetAndReportTreestate(treestate_t ts, bool recurse, bool reportToApp); // timer to delay upload start dstime nagleds = 0; void bumpnagleds(); // build full local path to this node void getlocalpath(LocalPath&) const; LocalPath getLocalPath() const; // build full remote path to this node (might not exist anymore, of course) string getCloudPath(bool guessLeafName) const; // For debugging duplicate LocalNodes from older SDK versions string debugGetParentList(); // return child node by name (TODO: could this be ambiguous, especially with case insensitive filesystems) LocalNode* childbyname(LocalPath*); LocalNode* findChildWithSyncedNodeHandle(NodeHandle h); FSNode getLastSyncedFSDetails() const; FSNode getScannedFSDetails() const; // Each LocalNode can be either uploading or downloading a file. // These functions manage that /** * @brief Queues an upload task for processing, with throttling support. * * This method resets the transferSP shared pointer to the new SyncUpload_inClient, checks * throttling conditions, and queues the upload for processing. If throttling is required, the * upload is added to a global delayed queue owned by Syncs. Otherwise, the upload is sent to * the client to be processed immediately. * * @param upload Shared pointer to the upload task being processed. * @param vo Versioning option for the upload. * @param queueFirst Flag indicating if this upload should be prioritized. This is meant for the * queue client, not for the delayed queue. In case the upload is added to the delayed queue, * this param will be taken into account when sending it to the client. * @param ovHandleIfShortcut The origin version handle (source cloudNode) used if we find a * target node to clone. * @return True if the upload was queued for immediate processing, false if it was added to the * throttling delayed queue. */ bool queueClientUpload(shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut); void queueClientDownload(shared_ptr upload, bool queueFirst); void resetTransfer(shared_ptr p); void checkTransferCompleted(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); void updateTransferLocalname(); /** * @brief Determines whether a transfer associated with the local node should be reset, * based on transfer direction, fingerprint match and transfer state. * * This method checks if the current transfer matches the provided direction and fingerprint. * If not, the transfer is reset. Transfers that have been terminated (e.g., due to errors) * are only retried if they are retryable and unmatched. In case they are terminated with * specific errors (not retryable) the transfer must not be retried unless unmatched: the node * could have been replaced remotely (new version). * * @param dir The transfer direction (PUT for uploads or GET for downloads). * @param fingerprint The fingerprint of the file to match against the current transfer. * * @return This method returns false only if there is a failure or fingerprint mismatch but * putnodes was started, to reevaluate the row (trigger a new upload). For all the other * scenarios, it returns true. * * @details * - If there is no associated transfer (`!transferSP`), the method returns true. * - Checks if the current transfer's direction matches the given direction (`dir`) and * if the fingerprint matches the provided fingerprint. If both match, the transfer * remains active unless it was terminated and retryable. * - For upload transfers, additional throttling checks are performed using * UploadThrottlingFile::handleAbortUpload(). If the upload must not be canceled, * the method returns false. * * @todo Improve accuracy of the matching criteria, considering additional factors beyond * fingerprints. */ bool transferResetUnlessMatched(const direction_t, const FileFingerprint& fingerprint, const int64_t metamac); shared_ptr transferSP; /** * @brief Check if this node or any successors have any pending transfer (transferSP != nullptr) * * @return true if there are pending transfers, false otherwise */ bool hasPendingTransfers() const; void updateMoveInvolvement(); void setSyncedFsid(handle newfsid, fsid_localnode_map& fsidnodes, const LocalPath& fsName, std::unique_ptr newshortname); void setScannedFsid(handle newfsid, fsid_localnode_map& fsidnodes, const LocalPath& fsName, const FileFingerprint& scanfp); void setSyncedNodeHandle(NodeHandle h); void setnameparent(LocalNode*, const LocalPath& newlocalpath, std::unique_ptr); void moveContentTo(LocalNode*, LocalPath&, bool setScanAgain); void moveContentTo(LocalNode*, LocalPath&, bool setScanAgain, bool moveTransfer); LocalNode(Sync*); void init(nodetype_t, LocalNode*, const LocalPath&, std::unique_ptr); bool serialize(string*) const override; static unique_ptr unserialize(Sync& sync, const string& source, uint32_t& parentID); void deleteChildren(); ~LocalNode(); //// True if any name conflicts have been detected in this subtree. bool conflictsDetected() const; // Are we above other? bool isAbove(const LocalNode& other) const; // Are we below other? bool isBelow(const LocalNode& other) const; // Create a watch for this node if necessary. WatchResult watch(const LocalPath& path, handle fsid); void setSubtreeNeedsRefingerprint(); private: #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { // The node's exclusion state. ExclusionState mExclusionState; // Whether we're an ignore file. bool mIsIgnoreFile : 1; // Whether we need to reload this node's ignore file. bool mWaitingForIgnoreFileLoad : 1; }; #ifdef _MSC_VER #pragma warning(pop) #endif // Query whether a file is excluded by a name filter. ExclusionState calcExcluded(RemotePathPair namePath, nodetype_t applicableType, bool inherited) const; // Query whether a file is excluded by a size filter. ExclusionState calcExcluded(const RemotePathPair& namePath, m_off_t size) const; public: // Signal that LocalNodes in this subtree must recompute their exclusion state. void setRecomputeExclusionState(bool includingThisOne, bool scan); // Clears the filters defined by this node. void clearFilters(); // Returns a reference to this node's filter chain. const FilterChain& filterChainRO() const; // Load filters from the ignore file identified by path. bool loadFilters(const LocalPath& path); // Signal whether this node needs to load its ignore file. //void setWaitingForIgnoreFileLoad(bool waiting); // Query whether this node needs to load its ignore file. bool waitingForIgnoreFileLoad() const; // Query whether a file is excluded by this node or one of its parents. template typename std::enable_if::value, ExclusionState>::type exclusionState(const PathType& path, nodetype_t type, m_off_t size) const; // Specialization of above intended for cloud name queries. ExclusionState exclusionState(const string& name, nodetype_t applicableType, m_off_t size) const; // Query this node's exclusion state. ExclusionState exclusionState() const; // Query whether this node represents an ignore file. bool isIgnoreFile() const; // Recompute this node's exclusion state. bool recomputeExclusionState(); private: unique_ptr rareFields; #ifdef USE_INOTIFY class WatchHandle { public: WatchHandle(); ~WatchHandle(); MEGA_DISABLE_COPY_MOVE(WatchHandle); auto operator=(WatchMap::iterator entry) -> WatchHandle&; auto operator=(std::nullptr_t) -> WatchHandle&; bool operator==(handle fsid) const; void invalidate(); private: WatchMap::iterator mEntry; static WatchMap mSentinel; }; // WatchHandle public: WatchHandle mWatchHandle; #endif // USE_INOTIFY private: /** * @brief Member containing the state and operations for UploadThrottlingFile. */ UploadThrottlingFile mUploadThrottling; /** * @brief Timestamp of last completed mtime-only sync operation (upload or download). * * Used to throttle MAC computations for files with frequently changing mtime. * Files like .eml on Windows can have mtime updates every few seconds, causing * repeated expensive MAC computations. This timestamp allows us to skip MAC * computation if a recent mtime-only operation just completed. */ std::chrono::steady_clock::time_point mLastMtimeOnlyOpTime{}; public: /** * @brief Sets the mUploadThrottling flag to bypass throttling. * * @param maxUploadsBeforeThrottle Maximum number of allowed uploads before the next upload must * be throttled. * * @see UploadThrottlingFile */ void bypassThrottlingNextTime(const unsigned maxUploadsBeforeThrottle) { mUploadThrottling.bypassThrottlingNextTime(maxUploadsBeforeThrottle); } /** * @brief Increases the upload counter by 1. * * @see UploadThrottlingFile */ void increaseUploadCounter() { mUploadThrottling.increaseUploadCounter(); } /** * @brief Gets the upload counter. * * @see UploadThrottlingFile */ unsigned uploadCounter() const { return mUploadThrottling.uploadCounter(); } /** * @brief Records that an mtime-only sync operation completed. * * Called after successful mtime-only upload (clone/setattr) or download. * This timestamp is used to throttle MAC computations for files with * frequently changing mtime. */ void recordMtimeOnlyOperation() { mLastMtimeOnlyOpTime = std::chrono::steady_clock::now(); } /** * @brief Checks if MAC computation should be throttled due to recent mtime-only operation. * * @param throttleWindow Time window to throttle after last mtime-only operation. * @return true if MAC computation should be skipped for now (too recent). */ bool shouldThrottleMacComputation(std::chrono::seconds throttleWindow) const { if (mLastMtimeOnlyOpTime == std::chrono::steady_clock::time_point{}) { return false; } auto elapsed = std::chrono::steady_clock::now() - mLastMtimeOnlyOpTime; return elapsed < throttleWindow; } /** * @brief Clears the mtime-only operation timestamp. * * Called when actual content (not just mtime) changes, to reset throttling. */ void clearMtimeOnlyOpTime() { mLastMtimeOnlyOpTime = {}; } }; bool isDoNotSyncFileName(const string& name); #endif // ENABLE_SYNC bool isPhotoVideoAudioByName(const string& filenameExtensionLowercaseNoDot); } // namespace #endif sdk-10.11.0/include/mega/nodemanager.h000066400000000000000000000636101516266226600174420ustar00rootroot00000000000000/** * @file mega/nodemanager.h * @brief Client access engine core logic * * (c) 2013-2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include #ifndef NODEMANAGER_H #define NODEMANAGER_H 1 #include "node.h" #include "types.h" #include #include #include #include #include namespace mega { class DBTableNodes; struct FileFingerprint; class FingerprintContainer; class MegaClient; class NodeSerialized; class NodeSearchFilter { public: static constexpr char TAG_DELIMITER = ','; enum class BoolFilter { disabled = 0, onlyTrue, onlyFalse, }; enum class TextQueryJoiner { logicalAnd, logicalOr, }; void byAncestors(std::vector&& ancs) { assert(ancs.size() == 3); mLocationHandles.swap(ancs); } void setIncludedShares(ShareType_t s) { mIncludedShares = s; } void byName(const std::string& name) { mNameFilter = name; } void byNodeType(nodetype_t nodeType) { assert(nodeType >= nodetype_t::TYPE_UNKNOWN && nodeType <= nodetype_t::FOLDERNODE); mNodeType = nodeType; } void byCategory(const MimeType_t category) { mMimeCategory = category; } void bySensitivity(BoolFilter boolFilter) { mExcludeSensitive = boolFilter; } void byFavourite(const BoolFilter byFav) { mFavouriteFilterOption = byFav; } void byLocationHandle(const handle location) { mLocationHandles = {location, UNDEF, UNDEF}; } void byCreationTimeLowerLimitInSecs(int64_t creationLowerLimit) { mCreationLowerLimit = creationLowerLimit; } void byCreationTimeUpperLimitInSecs(int64_t creationUpperLimit) { mCreationUpperLimit = creationUpperLimit; } void byModificationTimeLowerLimitInSecs(int64_t modificationLowerLimit) { mModificationLowerLimit = modificationLowerLimit; } void byModificationTimeUpperLimitInSecs(int64_t modificationUpperLimit) { mModificationUpperLimit = modificationUpperLimit; } void byDescription(const std::string& description) { mDescriptionFilter = escapeWildCards(description); } void byTag(const std::string& tag) { mTagFilter = escapeWildCards(tag); mTagFilterContainsSeparator = mTagFilter.getText().find(TAG_DELIMITER) != std::string::npos; } void useAndForTextQuery(const bool useAnd) { mUseAndForTextQuery = useAnd; } const std::string& byName() const { return mNameFilter.getText(); } nodetype_t byNodeType() const { return mNodeType; } MimeType_t byCategory() const { return mMimeCategory; } BoolFilter byFavourite() const { return mFavouriteFilterOption; } BoolFilter bySensitivity() const { return mExcludeSensitive; } // recursive look-ups (searchNodes) const std::vector& byAncestorHandles() const { return mLocationHandles; } // non-recursive look-ups (getChildren) handle byParentHandle() const { assert(!mLocationHandles.empty()); return mLocationHandles[0]; } // recursive look-ups (searchNodes): type of shares to be included when searching; // non-recursive look-ups (getChildren): ignored. ShareType_t includedShares() const { return mIncludedShares; } int64_t byCreationTimeLowerLimit() const { return mCreationLowerLimit; } int64_t byCreationTimeUpperLimit() const { return mCreationUpperLimit; } int64_t byModificationTimeLowerLimit() const { return mModificationLowerLimit; } int64_t byModificationTimeUpperLimit() const { return mModificationUpperLimit; } const std::string& byDescription() const { return mDescriptionFilter.getText(); } const std::string& byTag() const { return mTagFilter.getText(); } bool useAndForTextQuery() const { return mUseAndForTextQuery; } bool hasNodeType() const { return mNodeType != TYPE_UNKNOWN; } bool hasCreationTimeLimits() const { return mCreationLowerLimit || mCreationUpperLimit; } bool hasModificationTimeLimits() const { return mModificationLowerLimit || mModificationUpperLimit; } bool hasCategory() const { return mMimeCategory != MIME_TYPE_UNKNOWN; } bool hasName() const { return !mNameFilter.getText().empty(); } bool hasDescription() const { return !mDescriptionFilter.getText().empty(); } bool hasTag() const { return !mTagFilter.getText().empty(); } bool hasFav() const { return mFavouriteFilterOption != BoolFilter::disabled; } bool hasSensitive() const { return mExcludeSensitive != BoolFilter::disabled; } void includeVersions(const bool includeVersions) { mIncludeVersions = includeVersions; } bool includeVersions() const { return mIncludeVersions; } bool isValidNodeType(const nodetype_t nodeType) const; bool isValidCreationTime(const int64_t time) const; bool isValidModificationTime(const int64_t time) const; bool isValidCategory(const MimeType_t category, const nodetype_t nodeType) const; bool isValidName(const uint8_t* testName) const; bool isValidDescription(const uint8_t* testDescription) const; bool isValidTagSequence(const uint8_t* tagSequence) const; bool isValidFav(const bool isNodeFav) const; bool isValidSensitivity(const bool isNodeSensitive) const; private: TextPattern mNameFilter; nodetype_t mNodeType = TYPE_UNKNOWN; MimeType_t mMimeCategory = MIME_TYPE_UNKNOWN; BoolFilter mFavouriteFilterOption = BoolFilter::disabled; BoolFilter mExcludeSensitive = BoolFilter::disabled; std::vector mLocationHandles {UNDEF, UNDEF, UNDEF}; // always contain 3 items ShareType_t mIncludedShares = NO_SHARES; int64_t mCreationLowerLimit = 0; int64_t mCreationUpperLimit = 0; int64_t mModificationLowerLimit = 0; int64_t mModificationUpperLimit = 0; TextPattern mDescriptionFilter; TextPattern mTagFilter; bool mTagFilterContainsSeparator{false}; bool mUseAndForTextQuery{true}; bool mIncludeVersions{false}; static bool isDocType(const MimeType_t t); static bool isVisualMediaType(const MimeType_t t); }; class NodeSearchPage { public: NodeSearchPage(size_t startingOffset, size_t size) : mOffset(startingOffset), mSize(size) {} const size_t& startingOffset() const { return mOffset; } const size_t& size() const { return mSize; } private: size_t mOffset; size_t mSize; }; struct NodeSearchLexicographicalOffset { std::string mLastName; std::optional mLastType; std::optional mLastHandle; }; /** * @brief The NodeManager class * * This class encapsulates the access to nodes. It hides the details to * access to the Node object: in case it's not loaded in RAM, it will * load it from the "nodes" DB table. * * The same DB file is used for the "statecache" and the "nodes" table, and * both tables need to follow the same domain for transactions: a commit is * triggered by the reception of a sequence-number in the actionpacket (scsn). */ class MEGA_API NodeManager { public: NodeManager(MegaClient& client); // set interface to access to "nodes" table void setTable(DBTableNodes *table); // set interface to access to "nodes" table to nullptr, it's called just after sctable.reset() void reset(); // Take node ownership typedef map>> MissingParentNodes; bool addNode(std::shared_ptr node, bool notify, bool isFetching, MissingParentNodes& missingParentNodes); bool updateNode(Node* node); // removeNode() --> it's done through notifypurge() // if node is not available in memory, it's loaded from DB std::shared_ptr getNodeByHandle(NodeHandle handle); // read children from DB and load them in memory sharedNode_list getChildren(const Node* parent, CancelToken cancelToken = CancelToken(), bool includeVersions = false); sharedNode_vector getChildren(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page); // get up to "maxcount" nodes, not older than "since", ordered by creation time // Note: nodes are read from DB and loaded in memory sharedNode_vector getRecentNodes(unsigned maxcount, m_time_t since, bool excludeSensitives = false); sharedNode_vector searchNodes(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page); sharedNode_vector listChildNodesLexicographically( const handle parenthandle, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset); sharedNode_vector listChildNodesLexicographically_internal( const handle parentHandle, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset); /* * @brief * Get all node tags below a specified node. * * @param cancelToken * A token that can be used to terminate the query's execution prematurely. * * @param handles * A set of handles specifying which nodes we want to list tags below. * * If undefined, the query will list tags below all root nodes. * * @param pattern * An optional pattern that can be used to filter which tags we list. * * @returns * std::nullopt on failure. * std::set on success. */ auto getNodeTagsBelow(CancelToken cancelToken, const std::set& handles, const std::string& pattern = {}) -> std::optional>; /** * @brief Retrieves all nodes matching the specified file fingerprint. * @param excludeMtime If true, ignores `mtime` when comparing fingerprints. * @return A vector of shared pointers to matching `Node` objects. */ sharedNode_vector getNodesByFingerprint(const FileFingerprint& fingerprint, const bool excludeMtime = false); sharedNode_vector getNodesByOrigFingerprint(const std::string& fingerprint, Node *parent); std::shared_ptr getNodeByFingerprint(FileFingerprint &fingerprint); // Return a first level child node whose name matches with 'name' // Valid values for nodeType: FILENODE, FOLDERNODE // Note: if not found among children loaded in RAM (and not all children are loaded), it will search in DB // Hint: ensure all children are loaded if this method is called for all children of a folder std::shared_ptr childNodeByNameType(const Node *parent, const std::string& name, nodetype_t nodeType); // Returns ROOTNODE, INCOMINGNODE, RUBBISHNODE (In case of logged into folder link returns only ROOTNODE) // Load from DB if it's necessary sharedNode_vector getRootNodes(); sharedNode_vector getNodesWithInShares(); // both, top-level and nested ones sharedNode_vector getNodesWithOutShares(); sharedNode_vector getNodesWithPendingOutShares(); sharedNode_vector getNodesWithLinks(); std::vector getFavouritesNodeHandles(NodeHandle node, uint32_t count); size_t getNumberOfChildrenFromNode(NodeHandle parentHandle); // Returns the number of children nodes of specific node type with a query to DB // Valid types are FILENODE and FOLDERNODE size_t getNumberOfChildrenByType(NodeHandle parentHandle, nodetype_t nodeType); // true if 'node' is a child node of 'ancestor', false otherwise. bool isAncestor(NodeHandle nodehandle, NodeHandle ancestor, CancelToken cancelFlag); // Clean 'changed' flag from all nodes void removeChanges(); // Remove all nodes from all caches void cleanNodes(); // Use blob received as parameter to generate a node // Used to generate nodes from old cache std::shared_ptr getNodeFromBlob(const string* nodeSerialized); // attempt to apply received keys to decrypt node's keys void applyKeys(); void addNodePendingApplykey(std::shared_ptr node); // add node to the notification queue void notifyNode(std::shared_ptr node, sharedNode_vector* nodesToReport = nullptr); // for consistently notifying when updating node counters void setNodeCounter(std::shared_ptr n, const NodeCounter &counter, bool notify, sharedNode_vector* nodesToReport); // process notified/changed nodes from 'mNodeNotify': dump changes to DB void notifyPurge(); size_t nodeNotifySize() const; // Returns if cache has been loaded bool hasCacheLoaded(); // Load rootnodes (ROOTNODE, INCOMING, RUBBISH), its first-level children // and root of incoming shares. Return true if success, false if error bool loadNodes(); // Returns total of nodes in the account (cloud+inbox+rubbish AND inshares), including versions uint64_t getNodeCount(); // return the counter for all root nodes (cloud+inbox+rubbish) NodeCounter getCounterOfRootNodes(); // update the counter of 'n' when its parent is updated (from 'oldParent' to 'n.parent') void updateCounter(std::shared_ptr n, std::shared_ptr oldParent); // true if 'h' is a rootnode: cloud, inbox or rubbish bin bool isRootNode(NodeHandle h) const; // Set values to mClient.rootnodes for ROOTNODE, INBOX and RUBBISH bool setrootnode(std::shared_ptr node); // Add fingerprint to mFingerprint. If node isn't going to keep in RAM // node isn't added FingerprintPosition insertFingerprint(Node* node); // Remove fingerprint from mFingerprint void removeFingerprint(Node* node, bool unloadNode = false); FingerprintPosition invalidFingerprintPos(); std::list>::const_iterator invalidCacheLRUPos() const; // Node has received last updates and it's ready to store in DB void saveNodeInDb(Node *node); // write all nodes into DB (used for migration from legacy to NOD DB schema) void dumpNodes(); // This method only can be used in Megacli for testing purposes uint64_t getNumberNodesInRam() const; // Return number of nodes have had a successful applykey() long long getNumNodesKeyApplied() const; // Add new relationship between parent and child void addChild(NodeHandle parent, NodeHandle child, Node *node); // remove relationship between parent and child void removeChild(Node *parent, NodeHandle child); // Returns the number of versions for a node (including the current version) int getNumVersions(NodeHandle nodeHandle); NodeHandle getRootNodeFiles() const; NodeHandle getRootNodeVault() const; NodeHandle getRootNodeRubbish() const; void setRootNodeFiles(NodeHandle h); void setRootNodeVault(NodeHandle h); void setRootNodeRubbish(NodeHandle h); // In case of orphans send an event void checkOrphanNodes(MissingParentNodes& nodesWithMissingParent); // This method is called when initial fetch nodes is finished // Initialize node counters and create indexes at DB void initCompleted(); // Drop indexes used for search funtionalities // These indexes aren't required in some apps (S4) void dropSearchDBIndexes(); void dropLexicographicDBIndexes(); std::shared_ptr getNodeFromNodeManagerNode(NodeManagerNode& nodeManagerNode); void insertNodeCacheLRU(std::shared_ptr node); void increaseNumNodesInRam(); void decreaseNumNodesInRam(); void increaseNumNodesAppliedKey(); void decreaseNumNodesAppliedKey(); uint64_t getCacheLRUMaxSize() const; void setCacheLRUMaxSize(uint64_t cacheLRUMaxSize); uint64_t getNumNodesAtCacheLRU() const; // true when the filesystem has been initialized // i.e., when nodes have been fully loaded from a fetchnodes or from cache bool ready(); bool isFromRootNodeType(const Node& node) const; void removeNodePendingApplyKeys(const Node* node); private: class NoKeyLogger { public: void log(const Node& node) const; private: // How many no key nodes has been counted for logging mutable std::atomic_int mCount{1}; }; MegaClient& mClient; #if defined(DEBUG) using MutexType = CheckableMutex; #else // DEBUG using MutexType = std::recursive_mutex; #endif // ! DEBUG using LockGuard = std::lock_guard; mutable MutexType mMutex; // interface to handle accesses to "nodes" table DBTableNodes* mTable = nullptr; // logger with rate limitting for no key static NoKeyLogger mNoKeyLogger; // root nodes (files, vault, rubbish) struct Rootnodes { NodeHandle files; NodeHandle vault; NodeHandle rubbish; std::map > mRootNodes; // minimum expected number of root nodes (min num of root nodes may vary depending on // client type i.e password manager) static constexpr uint8_t MIN_NUM_ROOT_NODES{3}; // returns true if the 'h' provided matches any of the rootnodes. // (when logged into folder links, the handle of the folder is set to 'files') bool isRootNode(NodeHandle h) const { return (h == files || h == vault || h == rubbish); } void clear(); } rootnodes; class FingerprintContainer: public fingerprintNoMtime_set { public: bool allFingerprintsAreLoaded(const FileFingerprint *fingerprint) const; void setAllFingerprintLoaded(const FileFingerprint *fingerprint); void removeAllFingerprintLoaded(const FileFingerprint *fingerprint); void clear(); private: // it stores all FileFingerprint that have been looked up in DB, so it // avoid the DB query for future lookups (includes non-existing (yet) fingerprints) std::set mAllFingerprintsLoaded; }; // Stores nodes that have been loaded in RAM from DB (not necessarily all of them) std::map mNodes; uint64_t mCacheLRUMaxSize = std::numeric_limits::max(); std::list > mCacheLRU; std::atomic mNodesInRam; // nodes that have changed and are pending to notify to app and dump to DB sharedNode_vector mNodeNotify; // Stores nodes pending key application std::unordered_map> mNodePendingApplyKeys; // tracks how many nodes have had a successful applykey() std::atomic mAppliedKeyNodeCount{0}; shared_ptr getNodeInRAM(NodeHandle handle); void saveNodeInRAM(std::shared_ptr node, bool isRootnode, MissingParentNodes& missingParentNodes); // takes ownership sharedNode_vector getNodesWithSharesOrLink_internal(ShareType_t shareType); enum OperationType { INCREASE = 0, DECREASE, }; // Update a node counter for 'origin' and its subtree (recursively) // If operationType is INCREASE, nc is added, in other case is decreased (ie. upon deletion) void updateTreeCounter(std::shared_ptr origin, NodeCounter nc, OperationType operation, sharedNode_vector* nodesToReport); // returns nullptr if there are unserialization errors. Also triggers a full reload (fetchnodes) shared_ptr getNodeFromNodeSerialized(const NodeSerialized& nodeSerialized); // reads from DB and loads the node in memory shared_ptr unserializeNode(const string*, bool fromOldCache); // returns the counter for the specified node, calculating it recursively and accessing to DB if it's neccesary NodeCounter calculateNodeCounter(const NodeHandle &nodehandle, nodetype_t parentType, std::shared_ptr node, bool isInRubbish); // Container storing FileFingerprint* (Node* in practice) ordered by fingerprint FingerprintContainer mFingerPrintsNoMtime; // Return a node from Data base, node shouldn't be in RAM previously shared_ptr getNodeFromDataBase(NodeHandle handle); // Returns root nodes without nested in-shares sharedNode_vector getRootNodesAndInshares(); // Process unserialized nodes read from DB // Avoid loading nodes whose ancestor is not ancestorHandle. If ancestorHandle is undef load all nodes // If a valid cancelFlag is passed and takes true value, this method returns without complete operation // If a valid object is passed, it must be kept alive until this method returns. sharedNode_vector processUnserializedNodes(const std::vector>& nodesFromTable, NodeHandle ancestorHandle = NodeHandle(), CancelToken cancelFlag = CancelToken()); sharedNode_vector searchNodes_internal(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page); sharedNode_vector processUnserializedNodes(const std::vector>& nodesFromTable, CancelToken cancelFlag); sharedNode_vector getChildren_internal(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page); sharedNode_vector getRecentNodes_internal(const NodeSearchPage& page, m_time_t since); // node temporary in memory, which will be removed upon write to DB std::shared_ptr mNodeToWriteInDb; // Stores (or updates) the node in the DB. It also tries to decrypt it for the last time before storing it. void putNodeInDb(Node* node) const; // true when the NodeManager has been inicialized and contains a valid filesystem bool mInitialized = false; // flag that determines if null root nodes error has already been reported bool mNullRootNodesReported{false}; // These are all the "internal" versions of the public interfaces. // This is to avoid confusion where public functions used to call other public functions // but that introudces confusion about where the mutex gets locked. // Now the public interfaces lock the mutex once, and call these internal functions // which have all the original code in them. // Internal functions only call other internal fucntions, and that keeps things simple // We would use a non-recursive mutex for more precise control, and to make sure we can unlock // it when we need to make callbacks to the app. // It's quite a verbose approach, but at least simple, easy to understand, and easy to get right. void setTable_internal(DBTableNodes *table); void reset_internal(); bool addNode_internal(std::shared_ptr node, bool notify, bool isFetching, MissingParentNodes& missingParentNodes); bool updateNode_internal(Node* node); /** * @brief Manages null root nodes error server event (just once in NodeManager lifetime) * This method sends an event to stats server and prints a log error to inform about this * scenario. */ void reportNullRootNodes(const size_t rootNodesSize); std::shared_ptr getNodeByHandle_internal(NodeHandle handle); sharedNode_list getChildren_internal(const Node* parent, CancelToken cancelToken = CancelToken(), bool includeVersions = false); sharedNode_vector getNodesByFingerprint_internal(const FileFingerprint& fingerprint, const bool excludeMtime); sharedNode_vector getNodesByOrigFingerprint_internal(const std::string& fingerprint, Node *parent); std::shared_ptr getNodeByFingerprint_internal(FileFingerprint &fingerprint); std::shared_ptr childNodeByNameType_internal(const Node *parent, const std::string& name, nodetype_t nodeType); sharedNode_vector getRootNodes_internal(); std::vector getFavouritesNodeHandles_internal(NodeHandle node, uint32_t count); size_t getNumberOfChildrenFromNode_internal(NodeHandle parentHandle); size_t getNumberOfChildrenByType_internal(NodeHandle parentHandle, nodetype_t nodeType); bool isAncestor_internal(NodeHandle nodehandle, NodeHandle ancestor, CancelToken cancelFlag); void removeChanges_internal(); void cleanNodes_internal(); std::shared_ptr getNodeFromBlob_internal(const string* nodeSerialized); void applyKeys_internal(); void notifyNode_internal(std::shared_ptr node, sharedNode_vector* nodesToReport); bool loadNodes_internal(); uint64_t getNodeCount_internal(); NodeCounter getCounterOfRootNodes_internal(); void updateCounter_internal(std::shared_ptr n, std::shared_ptr oldParent); bool setrootnode_internal(std::shared_ptr node); FingerprintPosition insertFingerprint_internal(Node* node); void removeFingerprint_internal(Node* node, bool unloadNode); void saveNodeInDb_internal(Node *node); void dumpNodes_internal(); void addChild_internal(NodeHandle parent, NodeHandle child, Node *node); void removeChild_internal(Node *parent, NodeHandle child); void setRootNodeFiles_internal(NodeHandle h); void setRootNodeVault_internal(NodeHandle h); void setRootNodeRubbish_internal(NodeHandle h); void initCompleted_internal(); void insertNodeCacheLRU_internal(std::shared_ptr node); void unLoadNodeFromCacheLRU(); void removeNodeCacheLRU_internal(Node* node); void removeNodePendingApplyKeys_internal(const Node* node); }; } // namespace #endif sdk-10.11.0/include/mega/osx/000077500000000000000000000000001516266226600156145ustar00rootroot00000000000000sdk-10.11.0/include/mega/osx/drivenotifyosx.h000066400000000000000000000254741516266226600210750ustar00rootroot00000000000000/** * @file mega/osx/drivenotifyosx.h * @brief Mega SDK various utilities and helper classes * * (c) 2013-2021 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #ifdef USE_DRIVE_NOTIFICATIONS // Include "mega/drivenotify.h" where needed. // This header cannot be used by itself. #include #include #include #include #include #include #include #include #include namespace mega { // Automatic memory management class template for "Create Rule" references to CoreFoundation types // See // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029 template class UniqueCFRef { private: // CoreFoundation types such as dict, array, etc are passed around as CFDictionaryRef, CFArrayRef. // These Ref types are type aliases to a pointer to implementation-defined "unutterable" types. // Thus we use the type alias below since unique_ptr holds a pointer to T, but we cannot name T. using Ptr = std::unique_ptr::type, decltype(&CFRelease)>; public: using pointer = typename Ptr::pointer; // Construction from return value of CoreFoundation "create" function UniqueCFRef(pointer p) noexcept : mPtr(p, &CFRelease) {} // Implicit conversion to underlying reference type for easy interaction with CF interfaces operator pointer() const noexcept { return mPtr.get(); } operator bool() const noexcept { return mPtr; } private: Ptr mPtr; }; class DriveNotifyOsx; // Encapsulate filtering and callbacks for different media types. // For the purpose of DriveNotify we are mainly concerned with the presence of Volume Path, and // within Disk Arbitration Framework (DAF), media types vary based on when in their lifetime the path // (and other info) are or are not available. // Specializations of this class may implement specific logic for Disk Arbitration callbacks, // which will be registered to a session. class MediaTypeCallbacks { public: static UniqueCFRef description(DADiskRef disk) { return UniqueCFRef(DADiskCopyDescription(disk)); } static CFURLRef volumePath(CFDictionaryRef diskDescription) { return reinterpret_cast( CFDictionaryGetValue(diskDescription, kDADiskDescriptionVolumePathKey)); } static CFUUIDRef volumeUUID(CFDictionaryRef diskDescription) { return reinterpret_cast( CFDictionaryGetValue(diskDescription, kDADiskDescriptionVolumeUUIDKey)); } // MediaTypeCallbacks lifetime is bound to a parent object to which it registers constructed DriveInfo MediaTypeCallbacks(DriveNotifyOsx& parent) : mParent(parent) {} // Register the disk appeared and disappeared callbacks. // Note: merely registers callbacks; does not start running them. void registerCallbacks(DASessionRef session); // Unregister all possible callbacks. void unregisterCallbacks(DASessionRef session); // The matching dictionary used to filter disk types in callbacks. // If this dictionary is used to register a callback then the callback only fires for disks that match // the dictionary's criteria. // All properties in the dictionary are checked with a logical-AND. virtual CFDictionaryRef matchingDict() const noexcept = 0; // Additional filtering step which takes place in callbacks after filtering by matchingDict. // Allows for finer-grained filtering based on traits not expressible through dictionary filtering. bool shouldNotify(DADiskRef disk) const noexcept { auto description = UniqueCFRef(DADiskCopyDescription(disk)); return shouldNotify(description); } virtual bool shouldNotify(CFDictionaryRef diskDescription) const noexcept = 0; protected: // Add a drive to the parent DriveNotifyOsx object. void addDrive(CFURLRef path, bool connected); // Callback for when a disk appears to DAF. // Disk appearance means when a DAF session becomes aware of a disk. This includes disks that were // connected before the DAF session began. static void onDiskAppeared(DADiskRef disk, void* context); // Callback for when a disk disappears to DAF. // This callback fires for disks that are ejected or also yanked without proper ejection/unmounting. static void onDiskDisappeared(DADiskRef disk, void* context); // Callback for when a disk's description changes, adding or removing keys from the description dict. // In practice we are only interested in the appearance or disappearance of Volume Path, but greater // filtering is possible with changedKeys. static void onDiskDescriptionChanged(DADiskRef disk, CFArrayRef changedKeys, void* context) { auto& self = castSelf(context); if (!self.shouldNotify(disk)) return; self.onDiskDescriptionChangedImpl(disk, changedKeys, context); } // Callback for approval to unmount a disk. // Note: Approval callbacks in Disk Arbitration framework allow for operations to be disapproved which is out of scope // for our application so this returns null unconditionally and the impl method is void static DADissenterRef onUnmountApproval(DADiskRef disk, void* context); private: static MediaTypeCallbacks& castSelf(void* context) { return *reinterpret_cast(context); } // Optional method to register additional callbacks other than appeared/disappeared virtual void registerAdditionalCallbacks(DASessionRef session) {} // Handle the appearance of a disk with no Volume Path key, if applicable virtual void handleNoPathAppeared(CFDictionaryRef diskDescription) {} // Perform additional processing on a disappearing disk, if applicable virtual void processDisappeared(CFDictionaryRef diskDescription) {} // The implementation of an onDiskDescriptionChanged callback. virtual void onDiskDescriptionChangedImpl(DADiskRef disk, CFArrayRef changedKeys, void* context) { } DriveNotifyOsx& mParent; }; // Callbacks for physical media such as USB Drives // Unlike network drives, there are various points in the lifetime of a DADiskRef object where its path // is null. The callbacks below handle various cases related to ejection or physical removal of disks, // and disks whose mounting occurs before or after program start. // Disk appearance: // - if the disk was already plugged in before session start, onDiskAppeared is called with a volume path present // - else, we have to wait for the path to appear in onDiskDescriptionChanged with a volume path // Disk disappearance: // - if the disk is yanked without ejecting, it appears in onDiskDisappeared with a volume path present // - else, we get the volume path in onUnmountApproval and mark it as removed there. class PhysicalMediaCallbacks : public MediaTypeCallbacks { public: PhysicalMediaCallbacks(DriveNotifyOsx& parent); // Filters removable and ejectable media corresponding to actual mounted partition CFDictionaryRef matchingDict() const noexcept override { return mMatchingDict; } // After filtration by matchingDict, ignore Virtual Interface drives bool shouldNotify(CFDictionaryRef diskDescription) const noexcept override; private: // Register unmount approval and description changed callbacks. void registerAdditionalCallbacks(DASessionRef session) override; // If a disk appears with no path, store it for later notification in the pending collection // Physical media plugged in after the start of a DAF session shows to onDiskAppeared with no // volume path, so we store it for later registration. void handleNoPathAppeared(CFDictionaryRef diskDescription) override; // If a disk disappears, remove it from the pending collection void processDisappeared(CFDictionaryRef diskDescription) override; // Check if description has changed to add a Volume Path for a disk that previously appeared with no path void onDiskDescriptionChangedImpl(DADiskRef disk, CFArrayRef changedKeys, void* context) override; UniqueCFRef mMatchingDict; // Monitor for changes in Volume Path for onDiskDescriptionChanged UniqueCFRef mKeysToMonitor; // Set of drives which appeared in onDiskAppeared with no Volume Path // Drives in this set are "in limbo" and their existence will not be // announced until their description is changed in onDiskDescriptionChanged // to have a volume path, at which point they are removed from this set. // Disks which disappear are also removed. std::set mDisksPendingPath; }; // Callbacks for Network Attached Storage // Unlike PhysicalMedia, for NetworkDrive storage the Volume Path is always known at time of onDiskAppeared // and onDiskDisappeared. Thus we do not override any of the specific helper methods or other callbacks, and // no additional callbacks are registered. class NetworkDriveCallbacks : public MediaTypeCallbacks { public: NetworkDriveCallbacks(DriveNotifyOsx& parent); // Matching dict for network drives CFDictionaryRef matchingDict() const noexcept override { return mMatchingDict; } // After filtration by matchingDict, ignore autofs network volumes in /System/Volumes // see https://apple.stackexchange.com/questions/367158/whats-system-volumes-data bool shouldNotify(CFDictionaryRef diskDescription) const noexcept override; private: UniqueCFRef mMatchingDict; }; class DriveNotifyOsx final : public DriveNotify { public: DriveNotifyOsx(); ~DriveNotifyOsx() override { stopNotifier(); } protected: // Provide access to add(DriveInfo&&) method friend MediaTypeCallbacks; bool notifierSetup() override; private: void doInThread() override; void notifierTeardown(); // don't make it virtual, it's called from destructor // Disk Arbitration framework session object UniqueCFRef mSession; PhysicalMediaCallbacks mPhysicalCbs; NetworkDriveCallbacks mNetworkCbs; }; } // namespace mega #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/include/mega/osx/megafs.h000066400000000000000000000057121516266226600172340ustar00rootroot00000000000000/** * @file mega/osx/megafs.h * @brief POSIX filesystem/directory access/notification * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_MAC_FS_H #define MEGA_MAC_FS_H #include #define FSACCESS_CLASS MacFileSystemAccess #include "mega/posix/megafs.h" namespace mega { class MEGA_API MacFileSystemAccess : public PosixFileSystemAccess { public: friend class MacDirNotify; MacFileSystemAccess(); ~MacFileSystemAccess(); void addevents(Waiter* waiter, int flags) override; int checkevents(Waiter* waiter) override; #ifdef ENABLE_SYNC bool initFilesystemNotificationSystem() override; DirNotify* newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter) override; #endif // ENABLE_SYNC private: // This function ensures that all tasks in the dispatch // queue are completed. void flushDispatchQueue(); // What queue executes our notification callbacks? dispatch_queue_t mDispatchQueue; // Tracks how many notifiers are active. std::atomic_size_t mNumNotifiers; }; // MacFileSystemAccess #ifdef ENABLE_SYNC class MEGA_API MacDirNotify : public DirNotify { public: MacDirNotify(MacFileSystemAccess& owner, LocalNode& root, const LocalPath& rootPath, Waiter& waiter); ~MacDirNotify(); private: // Invoked by the trampoline. void callback(const FSEventStreamEventFlags* flags, std::size_t numEvents, const char** paths); // Invoked by the run loop when it receives a filesystem event. static void trampoline(ConstFSEventStreamRef stream, void *context, std::size_t numPaths, void* paths, const FSEventStreamEventFlags* flags, const FSEventStreamEventId* ids); // Monitors for and dispatches filesystem events. FSEventStreamRef mEventStream; // The MFSA that we are associated with. MacFileSystemAccess& mOwner; // The root that events are relative to. LocalNode& mRoot; // How much of an event's path should we skip? std::size_t mRootPathLength; // How we tell the engine it has work to do. Waiter& mWaiter; }; // MacDirNotify #endif // ENABLE_SYNC } // mega #endif // ! MEGA_MAC_FS_H sdk-10.11.0/include/mega/osx/osxutils.h000066400000000000000000000003631516266226600176610ustar00rootroot00000000000000#ifndef OSXUTILS_H #define OSXUTILS_H #include "mega/proxy.h" void path2localMac(const std::string* path, std::string* local); #if defined(__APPLE__) && !(TARGET_OS_IPHONE) void getOSXproxy(mega::Proxy* proxy); #endif #endif // OSXUTILS_H sdk-10.11.0/include/mega/overloaded.h000066400000000000000000000011631516266226600173010ustar00rootroot00000000000000#pragma once namespace mega { /** * @brief helper type for std::visit * * @example Usage example (see https://en.cppreference.com/w/cpp/utility/variant/visit): * std::visit(overloaded{ * [](auto arg) { std::cout << arg << ' '; }, * [](double arg) { std::cout << std::fixed << arg << ' '; }, * [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; } * }, v); */ template struct overloaded: Ts... { using Ts::operator()...; }; // explicit deduction guide (not needed as of C++20) template overloaded(Ts...) -> overloaded; } // mega sdk-10.11.0/include/mega/pendingcontactrequest.h000066400000000000000000000041531516266226600215700ustar00rootroot00000000000000/** * @file mega/pendingcontactrequest.h * @brief Class for manipulating pending contact requests * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_PENDING_CONTACT_REQUEST_H #define MEGA_PENDING_CONTACT_REQUEST_H 1 #include "mega/utils.h" namespace mega { // pending contact request struct MEGA_API PendingContactRequest : public Cacheable { // id of the request handle id; // e-mail of request creator string originatoremail; // e-mail of the recipient (empty if to us) string targetemail; // creation timestamp m_time_t ts; // last update timestamp m_time_t uts; // message from originator string msg; // flag for ease of use identifying direction bool isoutgoing; // flag to notify that an incoming contact request is being autoaccepted bool autoaccepted; struct { bool accepted : 1; bool denied : 1; bool ignored : 1; bool deleted : 1; bool reminded : 1; } changed; bool serialize(string*) const override; static PendingContactRequest* unserialize(string*); PendingContactRequest(const handle id, const char *oemail, const char *temail, const m_time_t ts, const m_time_t uts, const char *msg, bool outgoing); PendingContactRequest(const handle id); // for dummy requests during gettree/fetchnodes void update(const char* oemail, const char* temail, const m_time_t newTs, const m_time_t newUts, const char* newMessage, bool outgoing); bool removed(); }; } //namespace #endif sdk-10.11.0/include/mega/posix/000077500000000000000000000000001516266226600161455ustar00rootroot00000000000000sdk-10.11.0/include/mega/posix/drivenotifyposix.h000066400000000000000000000032141516266226600217430ustar00rootroot00000000000000/** * @file mega/posix/drivenotifyposix.h * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #ifdef USE_DRIVE_NOTIFICATIONS // Include "mega/drivenotify.h" where needed. // This header cannot be used by itself. #include #include #include struct udev; struct udev_monitor; struct udev_device; namespace mega { // Posix: Platform specific definition // // Not implemented. class DriveNotifyPosix final : public DriveNotify { public: ~DriveNotifyPosix() override { stopNotifier(); } protected: bool notifierSetup() override; void doInThread() override; private: void notifierTeardown(); // don't make it virtual, it's called from destructor void cacheMountedPartitions(); bool isRemovable(udev_device* part); void evaluateDevice(udev_device* dev); // dev must Not be null std::string getMountPoint(const std::string& device); udev* mUdev = nullptr; udev_monitor* mUdevMon = nullptr; std::map mMounted; }; } // namespace #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/include/mega/posix/gfx/000077500000000000000000000000001516266226600167315ustar00rootroot00000000000000sdk-10.11.0/include/mega/posix/gfx/worker/000077500000000000000000000000001516266226600202425ustar00rootroot00000000000000sdk-10.11.0/include/mega/posix/gfx/worker/comms.h000066400000000000000000000013471516266226600215360ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/comms.h" #include namespace mega { namespace gfx { class Socket : public IEndpoint { public: Socket(int socket, const std::string& name) : mSocket(socket), mName(name) {} Socket(const Socket&) = delete; Socket(Socket&& other); ~Socket(); bool isValid() const { return mSocket >= 0; } int fd() const { return mSocket; } private: bool doWrite(const void* data, size_t n, std::chrono::milliseconds timeout) override; bool doRead(void* data, size_t n, std::chrono::milliseconds timeout) override; // File descriptor to the socket int mSocket{-1}; // A name describes the socket and is used in logs. std::string mName; }; } // namespace } sdk-10.11.0/include/mega/posix/gfx/worker/comms_client.h000066400000000000000000000006761516266226600231000ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/comms.h" #include "mega/gfx/worker/comms_client_common.h" namespace mega { namespace gfx { class GfxCommunicationsClient : public IGfxCommunicationsClient { public: GfxCommunicationsClient(const std::string& socketName); std::pair> connect() override; private: CommError toCommError(int error) const; std::string mSocketName; }; } // namespace }sdk-10.11.0/include/mega/posix/gfx/worker/socket_utils.h000066400000000000000000000070661516266226600231340ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace gfx { struct SocketUtils { /** * @brief Compose a valid path for the unix domain socket from the name * in the format /tmp/MegaLimited/name. is the real user id. * * @param name A valid file name * * @return A path */ static std::filesystem::path toSocketPath(const std::string& name); /** * @brief Remove the socket file from file system * * @param name A valid file name * * @return On success, 0 value is set in error_code. On failure, a none zero error_code is returned. * * @see toSocketPath about how the path is composed */ static std::error_code removeSocketFile(const std::string& name); /** * @brief Create a stream socket and connect it to a UNIX domain socket server. * * @param socketPath The socket path that the server is listening on * * @return A pair of an error_code and a file descriptior. On Success, 0 error_code and a valid file descriptor * pair is returned. On error, a non-zero error_code and -1 pair is returned. */ static std::pair connect(const std::filesystem::path& socketPath); /** * @brief Create a stream UNIX domain socket, bind it on the socketPath and listen on it. * * @param socketPath The socket path to bind on * * @return A pair of an error_code and a file descriptior. On Success, 0 error_code and a valid listening file descriptor * pair is returned. On error, a non-zero error_code and -1 pair is returned. */ static std::pair listen(const std::filesystem::path& socketPath); /** * @brief Accept on the listening file descriptor with timeout * * @param listeningFd The listening file descriptor * * @param timeout The maxiumum time to wait for a connection * * @return A pair of an error_code and a file descriptior. On Success, 0 error_code and a valid file descriptor for the * new established connection pair is returned. On error, a non-zero error_code and -1 pair is returned. */ static std::pair accept(int listeningFd, std::chrono::milliseconds timeout); /** * @brief Read the exact n bytes data from the file descriptor or timeout * * @param fd The file descriptor to read from * * @param buf The buffer to store the data. The buffer is at least n bytes and caller takes ownership. * * @param n The number of bytes to read * * @param timeout The maxiumum time to wait for reading * * @return On success, 0 error_code is returned and the n bytes data is read to buf. * On error, a non-zero error_code is returned. */ static std::error_code read(int fd, void* buf, size_t n, std::chrono::milliseconds timeout); /** * @brief Write the exact n bytes data to the file descriptor or timeout * * @param fd The file descriptor to write to * * @param buf The buffer stored the data. * * @param n The number of bytes to write * * @param timeout The maxiumum time to wait for writing * * @return On success, 0 error_code is returned and the count bytes data is written. * On error, a non-zero error_code is returned. */ static std::error_code write(int fd, const void* data, size_t n, std::chrono::milliseconds timeout); }; } // namespace } sdk-10.11.0/include/mega/posix/megaconsole.h000066400000000000000000000020131516266226600206060ustar00rootroot00000000000000/** * @file mega/posix/megaconsole.h * @brief POSIX console/terminal control * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef CONSOLE_CLASS #define CONSOLE_CLASS PosixConsole #include "mega/console.h" namespace mega { struct PosixConsole : public Console { tcflag_t oldlflag; cc_t oldvtime; struct termios term; void readpwchar(char*, int, int* pw_buf_pos, char**); void setecho(bool); PosixConsole(); ~PosixConsole(); }; } // namespace #endif sdk-10.11.0/include/mega/posix/megaconsolewaiter.h000066400000000000000000000016201516266226600220250ustar00rootroot00000000000000/** * @file mega/posix/megaconsolewaiter.h * @brief Posix event/timeout handling, listens for stdin input * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef CONSOLE_WAIT_CLASS #define CONSOLE_WAIT_CLASS PosixConsoleWaiter #include "megawaiter.h" namespace mega { struct PosixConsoleWaiter : public PosixWaiter { int wait(); }; } // namespace #endif sdk-10.11.0/include/mega/posix/megafs.h000066400000000000000000000176741516266226600175770ustar00rootroot00000000000000/** * @file mega/posix/megafs.h * @brief POSIX filesystem/directory access/notification * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_POSIX_FS_H #define MEGA_POSIX_FS_H #ifdef __APPLE__ // Apple calls it sendfile, but it isn't #undef HAVE_SENDFILE #define O_DIRECT 0 #include #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) #include #else #include #include #endif #ifdef HAVE_AIO_RT #include #endif #include "mega.h" #define DEBRISFOLDER ".debris" namespace mega { namespace detail { using AdjustBasePathResult = std::string; AdjustBasePathResult adjustBasePath(const LocalPath& path); } // detail struct MEGA_API PosixDirAccess : public DirAccess { DIR* dp; bool globbing; glob_t globbuf; unsigned globindex; struct stat currentItemStat; bool currentItemFollowedSymlink; bool dopen(LocalPath*, FileAccess*, bool) override; bool dnext(LocalPath&, LocalPath&, bool, nodetype_t*) override; PosixDirAccess(); virtual ~PosixDirAccess(); }; class MEGA_API PosixFileSystemAccess : public FileSystemAccess { public: using FileSystemAccess::getlocalfstype; int defaultfilepermissions; int defaultfolderpermissions; unique_ptr newfileaccess(bool followSymLinks = true) override; unique_ptr newdiraccess() override; bool getlocalfstype(const LocalPath& path, FileSystemType& type) const override; bool issyncsupported(const LocalPath& localpathArg, bool& isnetwork, SyncError& syncError, SyncWarning& syncWarning) override; bool getsname(const LocalPath&, LocalPath&) const override; bool renamelocal(const LocalPath&, const LocalPath&, bool) override; bool copylocal(const LocalPath&, const LocalPath&, m_time_t) override; bool rubbishlocal(string*); bool unlinklocal(const LocalPath&) override; bool rmdirlocal(const LocalPath&) override; bool mkdirlocal(const LocalPath&, bool hidden, bool logAlreadyExistsError) override; std::pair getmtimelocal(const LocalPath&) override; bool setmtimelocal(const LocalPath&, m_time_t) override; bool chdirlocal(LocalPath&) const override; bool expanselocalpath(const LocalPath& path, LocalPath& absolutepath) override; void osversion(string*, bool includeArchitecture) const override; void statsid(string*) const override; // Returns true if provided error as param is considered a transient error (an error lasting // only for a short period of time). Otherwise returns false static bool isTransient(const int e); static void emptydirlocal(const LocalPath&, dev_t = 0); int getdefaultfilepermissions() override; void setdefaultfilepermissions(int) override; int getdefaultfolderpermissions() override; void setdefaultfolderpermissions(int) override; PosixFileSystemAccess(); static bool cwd_static(LocalPath& path); bool cwd(LocalPath& path) const override; ScanResult directoryScan(const LocalPath& path, handle expectedFsid, map& known, std::vector& results, bool followSymLinks, unsigned& nFingerprinted) override; #ifdef ENABLE_SYNC bool fsStableIDs(const LocalPath& path) const override; #endif // ENABLE_SYNC bool hardLink(const LocalPath& source, const LocalPath& target) override; m_off_t availableDiskSpace(const LocalPath& drivePath) override; }; #ifdef HAVE_AIO_RT struct MEGA_API PosixAsyncIOContext : public AsyncIOContext { PosixAsyncIOContext(); ~PosixAsyncIOContext() override; void finish() override; struct aiocb *aiocb; }; #endif class MEGA_API PosixFileAccess : public FileAccess { private: int fd; public: int stealFileDescriptor(); int defaultfilepermissions; static bool mFoundASymlink; #ifndef HAVE_FDOPENDIR DIR* dp; #endif bool fopen(const LocalPath&, OpenFlag flag, FSLogging, DirAccess* iteratingDir = nullptr, bool ignoreAttributes = false, bool skipcasecheck = false, LocalPath* actualLeafNameIfDifferent = nullptr) override; void updatelocalname(const LocalPath&, bool force) override; bool fread(string *, unsigned, unsigned, m_off_t); void fclose() override; bool fwrite(const void* buffer, unsigned long length, m_off_t position, unsigned long* numWritten = nullptr, bool* retry = nullptr) override; bool fstat(m_time_t& modified, m_off_t& size) override; bool ftruncate(m_off_t size) override; bool sysread(void* buffer, unsigned long length, m_off_t offset, bool* retry) override; bool sysstat(m_time_t*, m_off_t*, FSLogging) override; bool sysopen(bool async, FSLogging) override; void sysclose() override; PosixFileAccess(Waiter *w, int defaultfilepermissions = 0600, bool followSymLinks = true); // async interface bool asyncavailable() override; void asyncsysopen(AsyncIOContext* context) override; void asyncsysread(AsyncIOContext* context) override; void asyncsyswrite(AsyncIOContext* context) override; ~PosixFileAccess(); // Mark this file as a sparse file. bool setSparse() override; // Retrieve this file's allocated and reported size. auto getFileSize() const -> std::optional> override; #ifdef HAVE_AIO_RT protected: AsyncIOContext* newasynccontext() override; static void asyncopfinished(union sigval sigev_value); #endif private: bool mFollowSymLinks = true; }; #ifdef __linux__ #ifndef __ANDROID__ #define FSACCESS_CLASS LinuxFileSystemAccess #else #define FSACCESS_CLASS AndroidFileSystemAccess #endif class LinuxFileSystemAccess : public PosixFileSystemAccess { public: friend class LinuxDirNotify; ~LinuxFileSystemAccess(); void addevents(Waiter* waiter, int flags) override; int checkevents(Waiter* waiter) override; #ifdef ENABLE_SYNC bool initFilesystemNotificationSystem() override; DirNotify* newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter) override; private: // Tracks which notifiers were created by this instance. list mNotifiers; // Inotify descriptor. int mNotifyFd = -EINVAL; // Tracks which nodes are associated with what inotify handle. WatchMap mWatches; #endif // ENABLE_SYNC }; // LinuxFileSystemAccess #ifdef ENABLE_SYNC // Convenience. using AddWatchResult = pair; class LinuxDirNotify : public DirNotify { public: LinuxDirNotify(LinuxFileSystemAccess& owner, LocalNode& root, const LocalPath& rootPath); ~LinuxDirNotify(); virtual AddWatchResult addWatch(LocalNode& node, const LocalPath& path, handle fsid); void removeWatch(WatchMapIterator entry); private: // The LFSA that we are associated with. LinuxFileSystemAccess& mOwner; // Our position in our owner's mNotifiers list. list::iterator mNotifiersIt; }; // LinuxDirNotify #endif // ENABLE_SYNC #endif // __linux__ } // namespace #endif sdk-10.11.0/include/mega/posix/meganet.h000066400000000000000000000215471516266226600177470ustar00rootroot00000000000000/** * @file mega/posix/meganet.h * @brief POSIX network access layer (using cURL) * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega.h" #ifdef USE_OPENSSL #include #endif #include namespace mega { extern std::atomic g_netLoggingOn; // Represents a DNS entry for a particular URI. struct DNSEntry { bool operator==(const DNSEntry& rhs) const { return ipv4 == rhs.ipv4 && ipv6 == rhs.ipv6; } bool operator!=(const DNSEntry& rhs) const { return !(*this == rhs); } // The URI's IPv4 address. std::string ipv4; // The URI's IPv6 address, if any. std::string ipv6; }; // DNSEntry struct MEGA_API SockInfo { enum { NONE = 0, READ = 1, WRITE = 2 }; #ifdef WIN32 SockInfo(HANDLE& sharedEvent) : mSharedEvent(sharedEvent) { } #else SockInfo() = default; #endif curl_socket_t fd = curl_socket_t(-1); int mode = NONE; #if defined(_WIN32) SockInfo(const SockInfo&) = delete; void operator=(const SockInfo&) = delete; SockInfo(SockInfo&& o); ~SockInfo(); // create the event and call WSAEventSelect, if it hasn't been done yet. bool createAssociateEvent(); // see if there is any work to be done on this socket (to be called after waiting, and a network event was triggered) bool checkEvent(bool& read, bool& write, bool logErr = true); // manually close the event (used when we know the socket is no longer active) void closeEvent(bool adjustSocket = true); // get the event handle, for waiting on HANDLE sharedEventHandle(); // Flag for dealing with windows write event signalling, where we only get signalled if the socket goes from unwriteable to writeable (but not if we wrote to it and didn't get it to the unwriteable state) bool signalledWrite = false; private: HANDLE& mSharedEvent; int associatedHandleEvents = 0; #endif }; struct MEGA_API CurlHttpContext; class CurlHttpIO: public HttpIO { protected: static std::mutex curlMutex; string useragent; CURLM* curlm[3]; CURLSH* curlsh; string proxyurl; string proxyschema; string proxyhost; int proxyport; int proxytype; string proxyip; string proxyusername; string proxypassword; int proxyinflight; std::queue pendingrequests; std::map dnscache; int pkpErrors; void send_pending_requests(); void drop_pending_requests(); static size_t read_data(void*, size_t, size_t, void*); static size_t write_data(void*, size_t, size_t, void*); static size_t check_header(const char*, size_t, size_t, void*); static int seek_data(void*, curl_off_t, int); static int socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp, direction_t d); static int sockopt_callback(void *clientp, curl_socket_t curlfd, curlsocktype purpose); static int api_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp); static int download_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp); static int upload_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp); static int timer_callback(CURLM *multi, long timeout_ms, void *userp, direction_t d); static int api_timer_callback(CURLM *multi, long timeout_ms, void *userp); static int download_timer_callback(CURLM *multi, long timeout_ms, void *userp); static int upload_timer_callback(CURLM *multi, long timeout_ms, void *userp); #if defined(USE_OPENSSL) && !defined(OPENSSL_IS_BORINGSSL) public: // so we can delete it on program end static std::recursive_mutex **sslMutexes; protected: static void locking_function(int mode, int lockNumber, const char *, int); #if OPENSSL_VERSION_NUMBER >= 0x10000000 static void id_function(CRYPTO_THREADID* id); #else static unsigned long id_function(); #endif #endif #ifdef USE_OPENSSL static CURLcode ssl_ctx_function(CURL*, void*, void*); static int cert_verify_callback(X509_STORE_CTX*, void*); #endif static void send_request(CurlHttpContext*); void request_proxy_ip(); static struct curl_slist* clone_curl_slist(struct curl_slist*); static int debug_callback(CURL*, curl_infotype, char*, size_t, void*); const char* pubkeyForUrl(const char* url) const; const char* pubkeyForUrl(const std::string& url) const { return pubkeyForUrl(url.c_str()); } bool reset; bool statechange; bool dnsok; string dnsservers; curl_slist* contenttypejson; curl_slist* contenttypebinary; WAIT_CLASS* waiter; bool disconnecting; typedef std::map SockInfoMap; void addcurlevents(Waiter* eventWaiter, direction_t d); int checkevents(Waiter*) override; void closecurlevents(direction_t d); void processcurlevents(direction_t d); SockInfoMap curlsockets[3]; m_time_t curltimeoutreset[3]; bool arerequestspaused[3]; int numconnections[3]; setpausedrequests[3]; m_off_t partialdata[2]; m_off_t maxspeed[2]; public: void post(HttpReq*, const char* = 0, unsigned = 0) override; void cancel(HttpReq*) override; m_off_t postpos(void*) override; bool doio(void) override; bool multidoio(CURLM *curlmhandle); void measureLatency(CURL* easy_handle, HttpReq* req); void addevents(Waiter*, int) override; void setuseragent(string*) override; void setproxy(const Proxy&) override; std::optional getproxy() const override; // It returns false if curl does not have a DNS backend supporting custom DNS lists. bool setdnsservers(const char*); void disconnect() override; // set max download speed bool setmaxdownloadspeed(m_off_t bpslimit) override; // set max upload speed bool setmaxuploadspeed(m_off_t bpslimit) override; // get max download speed m_off_t getmaxdownloadspeed() override; // get max upload speed m_off_t getmaxuploadspeed() override; int cacheresolvedurls(const std::vector& urls, const std::vector& ips) override; void addDnsResolution(CURL* curl, std::unique_ptr& dnsList, const string& host, const string& ips, const int port); CurlHttpIO(); ~CurlHttpIO(); #ifdef WIN32 HANDLE mSocketsWaitEvent; bool mSocketsWaitEvent_curl_call_needed = false; #endif // Retrieve a reference to this instance's cached DNS entries. const auto& getCachedDNSEntries() const { return dnscache; } private: static int instanceCount; friend class MegaClient; CodeCounter::ScopeStats countCurlHttpIOAddevents = {"curl-httpio-addevents"}; CodeCounter::ScopeStats countAddCurlEventsCode = {"curl-add-events"}; CodeCounter::ScopeStats countProcessCurlEventsCode = {"curl-process-events"}; }; struct MEGA_API CurlHttpContext { CURL* curl; direction_t d; HttpReq* req; CurlHttpIO* httpio; struct curl_slist* headers; string hostname; string schema; string hostip; int port; string hostheader; string posturl; unsigned len; const char* data; std::unique_ptr mCurlDnsList{nullptr, curl_slist_free_all}; }; // Separate a URI into its constituent pieces. bool crackURI(const string& uri, string& scheme, string& host, int& port); // True if string is a valid IPv4 address. bool isValidIPv4Address(const std::string& string); // True if string is a valid IPv6 address. bool isValidIPv6Address(const std::string& string); // Populates the specified DNS cache based on the provided URI and IPs. // // This function expects each URI to be associated with an IPv4 and an IPv6 // address. // // Entries will be added to the cache if and only if a URI is associated // with a valid IPv4 address. // // This function returns: // <0 - Too few or too many IPs vs. URIs. // 0 - Cache updated. // >0 - Cache updated but an invalid IP was detected. int populateDNSCache(std::map& cache, const std::vector& ips, const std::vector& uris); } // namespace sdk-10.11.0/include/mega/posix/megasys.h000066400000000000000000000046551516266226600200000ustar00rootroot00000000000000/** * @file mega/posix/megasys.h * @brief Mega SDK platform-specific includes (Posix) * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_POSIX_OS_H #define MEGA_POSIX_OS_H 1 // platform dependent constants #if defined(__ANDROID__) && !defined(HAVE_SDK_CONFIG_H) #include "mega/config-android.h" #else #ifndef MEGA_GENERATED_CONFIG_H #include "mega/config.h" #define MEGA_GENERATED_CONFIG_H #endif #endif #ifdef __APPLE__ #include #endif #include #include #include #include #include // the MEGA SDK assumes writable, contiguous string::data() #include #include #include #include #include #include #include #include #include #ifdef HAVE_STDDEF_H #include #endif #include #ifdef HAVE_STDLIB_H #include #endif #include #include #include #include #include #include #ifdef HAVE_STDBOOL_H #include #endif #include #include #include #include #include #include #include #ifdef HAVE_DIRENT_H #include #endif #include #include #include #include #if defined(__linux__) || defined(__OpenBSD__) #include #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__minix) || \ defined(__OpenBSD__) #include #endif #ifdef HAVE_SENDFILE #include #endif #ifdef USE_INOTIFY #include #endif #include #include #include #ifndef USE_POLL #ifndef FD_COPY #define FD_COPY(s, d) ( memcpy(( d ), ( s ), sizeof( fd_set ))) #endif #endif #endif // MEGA_POSIX_OS_H sdk-10.11.0/include/mega/posix/megawaiter.h000066400000000000000000000040771516266226600204530ustar00rootroot00000000000000/** * @file mega/posix/megawaiter.h * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef WAIT_CLASS #define WAIT_CLASS PosixWaiter #include "mega/waiter.h" #include #ifndef USE_POLL #define MEGA_FD_ZERO FD_ZERO #define MEGA_FD_SET FD_SET #define MEGA_FD_ISSET FD_ISSET typedef fd_set mega_fd_set_t; #else #define MEGA_FD_ZERO PosixWaiter::clear_fdset #define MEGA_FD_SET PosixWaiter::fdset #define MEGA_FD_ISSET PosixWaiter::fdisset #define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR) // Ready for reading #define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) // Ready for writing #define POLLEX_SET (POLLPRI) // Exceptional condition typedef std::set mega_fd_set_t ; #endif namespace mega { struct PosixWaiter : public Waiter { PosixWaiter(); ~PosixWaiter(); int maxfd; mega_fd_set_t rfds, wfds, efds; mega_fd_set_t ignorefds; #ifdef USE_POLL static void clear_fdset(mega_fd_set_t *s) { s->clear(); } static void fdset(int fd, mega_fd_set_t *s) { s->insert(fd); } static bool fdisset(int fd, mega_fd_set_t *s) { return s->find(fd) != s->end(); } #endif bool fd_filter(int nfds, mega_fd_set_t* fds, mega_fd_set_t* ignorefds) const; void init(dstime); int wait(); void bumpmaxfd(int); void notify(); protected: int m_pipe[2]; std::mutex mMutex; bool alreadyNotified = false; }; } // namespace #endif sdk-10.11.0/include/mega/process.h000066400000000000000000000224061516266226600166360ustar00rootroot00000000000000/** * @file mega/processs.h * @brief Mega SDK sun sub process * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_PROCESS_H #define MEGA_PROCESS_H 1 #include "mega/auto_file_handle.h" #include "mega/utils.h" #ifdef WIN32 #include #else #include #include #include #endif #include #include #include #include #include #include namespace mega { /** * @brief The Process class * * It allows to create child processes with its stdout, stderr (stdin is optional) redirected to * this object through a function of type DataReaderFunc / void(unsigned char *, size_t len). * * It also provides a buffer for writing to the child process's stdin. * * Intearnally, it uses execvp() instead of popen() and system(), as the latter have the following deficiencies: * - they can not be cross platform as the command string is interpreted bny the shell * a string variable argument can break a program, e.g. a " in an input string. * Process takes a vector of args * - the exit code or signal that killed the process is reported * - reads stdoutand stderr separately * flexible handling of stdout and stderr bytes via a function * * Additionally, execvp() provides the following features: * - can write to stdin * - can set env vars * - can set startup directory * * How to use (example): * Process::StringSink out; * Process::StringSink error; * Process p; * p.run({ "ls", "-l" }, "", {}, out.func(), error.func()); * if (p.wait()) return out; // completed ok * LOG_err << "command failed: " << p.getExitMessage() << ": " << error; */ class Process { public: typedef std::function DataReaderFunc; private: #ifdef WIN32 DWORD childPid = (DWORD)-1; #else // all values valid and may be up to a long pid_t childPid = -1; #endif bool launched = false; // reads stdout from the sub process AutoFileHandle readFd; // reads stderr from the sub process AutoFileHandle readErrorFd; #ifdef WIN32 DWORD writePipeLength = 0xFFFFFFFF; #endif // may be nullptr // if nullptr child stdout will be echoed to stdout DataReaderFunc stdoutReader = nullptr; // may be nullptr // if nullptr child stderr will be echoed to stderr DataReaderFunc stderrReader = nullptr; // undefined if !hasExited && !hasBeenSignalled // set in CheckExit() or Wait() // Unix: status set by waitpid() // Windows: exit code, may be 0xFFFFFFFF so we need a separate flag // encaptuated as should check with OS each time int status = -1; // 'status' contains an exit code from the subprocess bool hasExitStatus = false; // 'status' contains the signal that terminated the subvproceess bool hasSignalStatus = false; public: bool hasStatus() const { return hasExitStatus || hasSignalStatus; } // unsigned char as these are bytes not text // // a string that is adapted to be populated by Process class StringSink : public std::string { public: using std::string::operator=; DataReaderFunc func() { return [this](const unsigned char* data, size_t len) { append(reinterpret_cast(data), len); }; } }; bool hasExited() const { return hasExitStatus; } bool hasTerminateBySignal() const { return hasSignalStatus; } // need to flush() after exited bool hasExitedOk() const { return hasExitStatus && getExitCode() == 0; } // 0..255 on Unix, or -1 on internal error // DWORD on Windows int getExitCode() const { assert(hasExitStatus); return status; } int getTerminatingSignal() const { assert(hasSignalStatus); return status; } Process() {} ~Process(); /** * @brief run TODO * * @param args arguments, PATH is searched for argv[0] * @param directory Startup direcotry, "", the default for current directory * @param env Set enironemnt variables, value of "" to remove an environemnt variable * @param ireader To handle child stdout, defaults to writing to parent stdout * @param istderrReader To handle child stderr defaults to writing to parent stderr * @param iredirectStdin Allow write()ing to stdin of sub process * * @return TODO */ bool run(const std::vector& args, const std::unordered_map& env = std::unordered_map(), DataReaderFunc ireader = nullptr, DataReaderFunc istderrReader = nullptr); #ifdef WIN32 static std::string windowsQuoteArg(const std::string& str); #endif // produce string of command line // passed to OS on Windows, just used for trace on posix static std::string formCommandLine(const std::vector& args); // return true if read some bool poll(); // poll() until nothing is read nor written // return true if anything read or written bool flush(); // close the pipes but leave the process alive void close(); bool isOpen() const { return isStdOutOpen() || isStdErrOpen(); } bool isStdOutOpen() const { return readFd.isSet(); } bool isStdErrOpen() const { return readErrorFd.isSet(); } // return false if already terminated bool terminate(); // return true if child has status (exiited) // e.g. exited or signalled // fill in 'status' if get signal or exits bool checkStatus(); // wait for child to exit // returns true if exited ok // call hasExited()/getExitCode() or hasExitedBySignal() to work out why has terminated bool wait(); // return true if the processs is still running // a dead process may still have buffered output available in poll() // use hasExited() or hasTerminateBySignal() to determine the exit type // need to flush() after exited bool isAlive() { return !checkStatus(); } // Unix only // returns description std::string getExitSignalDescription() const; // return "SIGTERM - Termination Signal" static std::string describeSignal(int sig); // return description of exit // "Exited ok" // "Exited with status 3" // "Exited with signal: SIGTERM - Termination Signal" std::string getExitMessage() const; // Return the current process ID // Value returned is valid only if the process has been run int getPid() const { return childPid; } private: // internal methods void clearStatus() { status = -1; hasExitStatus = false; hasSignalStatus = false; } void setExitStatus(int istatus) { status = istatus; hasExitStatus = true; hasSignalStatus = false; } void setSignalledStatus(int istatus) { status = istatus; hasExitStatus = false; hasSignalStatus = true; } // called when we can not ascertain the shild process's status void setWaitFailureStatus() { // otherwise may spin forever setExitStatus(-1); } void setLaunchFailureStatus() { // otherwise may spin forever setExitStatus(-1); } // return true if read some bool readStdout(); // return true if read some bool readStderr(); // used to temporarily set environment variables // on Windows the API requires the entire environment to be specified // one implementation on both platforms class EnvironmentChanger { std::unordered_map saved; std::unordered_set unset; public: EnvironmentChanger(const std::unordered_map& env); // update environemnt // save variabled being overwritten ~EnvironmentChanger(); }; }; // print a progress value, bar and Estimated Time To Arrival class ConsoleProgressBar { size_t value = 0; size_t max = 0; ::mega::m_time_t start = 0; size_t barWidth = 40; std::string prefix; // write a new line after printing bool mWriteNewLine = false; // automatically print to cout when updated bool autoOutput = true; public: ConsoleProgressBar(size_t imax, bool writeNewLine); void add(size_t n); void inc(); std::ostream& put(std::ostream& os) const; // display in stdout void show() const; void setPrefix(const std::string& newPrefix); }; // must be outside mega namespace inline std::ostream& operator<<(std::ostream& os, const ConsoleProgressBar& bar) { return bar.put(os); } } // namespace mega #endif sdk-10.11.0/include/mega/proxy.h000066400000000000000000000036061516266226600163420ustar00rootroot00000000000000/** * @file mega/proxy.h * @brief Class for manipulating proxy data * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef PROXY_H #define PROXY_H #include "mega/types.h" #include namespace mega { #define DEFINE_PROXY_TYPES(expander) expander(NONE) expander(AUTO) expander(CUSTOM) struct MEGA_API Proxy { public: #define IDENTITY(name) name, enum ProxyType { DEFINE_PROXY_TYPES(IDENTITY) }; #undef IDENTITY Proxy(); void setProxyType(int newType); void setProxyURL(const std::string& newURL); void setCredentials(const std::string& newUsername, const std::string& newPassword); int getProxyType() const; std::string getProxyURL() const; bool credentialsNeeded() const; std::string getUsername() const; std::string getPassword() const; static Proxy parseFromURL(const std::string& url); bool operator==(const Proxy& other) const; protected: int proxyType; std::string proxyURL; std::string username; std::string password; }; int proxyTypeFromString(const std::string& type); const std::string* proxyTypeToString(int type); // This function retrieves the proxy settings from environment variables // (http_proxy, HTTP_PROXY, https_proxy or HTTPS_PROXY) in order. // If none is set, it returns a default proxy. void getEnvProxy(Proxy* proxy); } // namespace #endif // PROXY_H sdk-10.11.0/include/mega/pubkeyaction.h000066400000000000000000000031051516266226600176500ustar00rootroot00000000000000/** * @file mega/pubkeyaction.h * @brief Classes for manipulating user's public key * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_PUBKEYACTION_H #define MEGA_PUBKEYACTION_H 1 #include "mega/command.h" #include "mega/node.h" #include "mega/user.h" namespace mega { // action to be performed upon arrival of a user's public key // forward declaration to avoid cyclic include with megaclient.h class MegaClient; class MEGA_API PubKeyAction { public: int tag; CommandPubKeyRequest *cmd; virtual void proc(MegaClient*, User*) = 0; PubKeyAction(); virtual ~PubKeyAction() { } }; class MEGA_API PubKeyActionPutNodes : public PubKeyAction { vector nn; // nodes to add CommandPutNodes::Completion completion; public: void proc(MegaClient*, User*); PubKeyActionPutNodes(vector&&, int, CommandPutNodes::Completion&&); }; class MEGA_API PubKeyActionNotifyApp : public PubKeyAction { public: void proc(MegaClient*, User*); PubKeyActionNotifyApp(int); }; } // namespace #endif sdk-10.11.0/include/mega/pwm_file_parser.h000066400000000000000000000054351516266226600203410ustar00rootroot00000000000000#ifndef INCLUDE_MEGA_PWM_FILE_PARSER_H_ #define INCLUDE_MEGA_PWM_FILE_PARSER_H_ #include "mega/types.h" namespace mega::pwm::import { /** * @class PassEntryParseResult * @brief A helper struct to store the result from parsing an entry in a file with passwords to * import * */ struct PassEntryParseResult { enum class ErrCode : uint8_t { OK = 0, INVALID_NUM_OF_COLUMN, }; /** * @brief An error code associated to a problem that invalidates the parsing of the entry. */ ErrCode mErrCode = ErrCode::OK; /** * @brief The contents from the file that was used to create the entry. */ std::string mOriginalContent; /** * @brief The name that labels the password entry. * * @note This struct does not force any condition on this member, i.e. empty is a valid value */ std::string mName; /** * @brief Members for the different fields that can be found in an entry */ std::string mUrl; std::string mUserName; std::string mPassword; std::string mNote; }; /** * @class PassFileParseResult * @brief A helper struct to hold a full report of the parsing password file process. * */ struct PassFileParseResult { enum class ErrCode : uint8_t { OK = 0, NO_VALID_ENTRIES, FILE_NOT_FOUND, CANT_OPEN_FILE, MISSING_COLUMN, INVALID_HEADER, }; /** * @brief An error code associated to a problem that invalidates the file parsing as a whole. */ ErrCode mErrCode = ErrCode::OK; /** * @brief An error messages with additional information useful for logging. */ std::string mErrMsg; /** * @brief A vector with a PassEntryParseResult object with the parse information for each of the * rows found in the file. */ std::vector mResults; }; /** * @brief Reads the password entries listed in the input csv file exported from Google's Password * application. * * @param filePath The path to the csv file. * @return A container holding all the valid and invalid entries found together, with handy error * codes and messages. */ PassFileParseResult parseGooglePasswordCSVFile(const std::string& filePath); enum class FileSource : uint8_t { GOOGLE_PASSWORD = 0, }; /** * @brief Given the source of the file with passwords to import, decides the parser to apply and * returns the result from parsing the file * * @param filePath The path to the csv file. * @param source The app that was used to export the file * @return A container holding all the valid and invalid entries found together, with handy error * codes and messages. */ PassFileParseResult readPasswordImportFile(const std::string& filePath, const FileSource source); } #endif // INCLUDE_MEGA_PWM_FILE_PARSER_H_ sdk-10.11.0/include/mega/raid.h000066400000000000000000000352141516266226600161000ustar00rootroot00000000000000/** * @file mega/raid.h * @brief helper classes for managing cloudraid downloads * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_RAID_H #define MEGA_RAID_H 1 #define ISNEWRAID_DEFVALUE 1 #include "http.h" #include "utils.h" namespace mega { enum { RAIDPARTS = 6 }; enum { EFFECTIVE_RAIDPARTS = 5 // A file is divided by 5 parts plus parity part (to assemble the other // parts if one of them is missing) }; enum { RAIDSECTOR = 16 }; static constexpr int RAIDLINE = static_cast(EFFECTIVE_RAIDPARTS) * static_cast(RAIDSECTOR); // Holds the latest download data received. Raid-aware. Suitable for file transfers, or direct // streaming. For non-raid files, supplies the received buffer back to the same connection for // writing to file (having decrypted and mac'd it), effectively the same way it worked before raid. // For raid files, collects up enough input buffers until it can combine them to make a piece of the // output file. Once a piece of the output is reconstructed the caller can access it with // getAsyncOutputBufferPointer(). Once that piece is no longer needed, call bufferWriteCompleted to // indicate that it can be deallocated. class MEGA_API RaidBufferManager { public: struct FilePiece { m_off_t pos; HttpReq::http_buf_t buf; // owned here chunkmac_map chunkmacs; std::condition_variable finalizedCV; bool finalized = false; FilePiece(); FilePiece(m_off_t p, size_t len); // makes a buffer of the specified size (with extra space // for SymmCipher::ctr_crypt padding) FilePiece(m_off_t p, HttpReq::http_buf_t* b); // takes ownership of the buffer void swap(FilePiece& other); // decrypt & mac bool finalize(bool parallel, m_off_t filesize, int64_t ctriv, SymmCipher* cipher, chunkmac_map* source_chunkmacs); }; // Min last request chunk (to avoid small chunks to be requested) #if defined(__ANDROID__) || defined(USE_IOS) static constexpr size_t MIN_LAST_CHUNK = 512 * 1024; #else static constexpr size_t MIN_LAST_CHUNK = 10 * 1024 * 1024; #endif // Max last request chunk (otherwise split it in two) #if defined(__ANDROID__) || defined(USE_IOS) static constexpr size_t MAX_LAST_CHUNK = 1 * 1024 * 1024; #else static constexpr size_t MAX_LAST_CHUNK = 16 * 1024 * 1024; #endif // To be called within CloudRaid tests when a lower speed is needed or we need to trigger 403/404/timeout errors void disableAvoidSmallLastRequest(); // call this before starting a transfer. Extracts the vector content void setIsRaid(const std::vector& tempUrls, m_off_t resumepos, m_off_t readtopos, m_off_t filesize, m_off_t maxDownloadRequestSize, bool isNewRaid = ISNEWRAID_DEFVALUE); // indicate if the file is raid or not. Most variation due to raid/non-raid is captured in this class bool isRaid() const; // indicate if the file is new raid (CloudRaidProxy) or not bool isNewRaid() const; // Is this the connection we are not using bool isUnusedRaidConection(unsigned connectionNum) const; // Is this connection unable to continue currently because other connections are too far behind bool isRaidConnectionProgressBlocked(unsigned connectionNum) const; // in case URLs expire, use this to update them and keep downloading without wasting any data void updateUrlsAndResetPos(const std::vector& tempUrls); // pass a downloaded buffer to the manager, pre-decryption. Takes ownership of the FilePiece. May update the connection pos (for raid) void submitBuffer(unsigned connectionNum, FilePiece* piece); // get the file output data to write to the filesystem, on the asyncIO associated with a particular connection (or synchronously). Buffer ownership is retained here. std::shared_ptr getAsyncOutputBufferPointer(unsigned connectionNum); // indicate that the buffer written by asyncIO (or synchronously) can now be discarded. void bufferWriteCompleted(unsigned connectionNum, bool succeeded); // temp URL to use on a given connection. The same on all connections for a non-raid file. const std::string& tempURL(unsigned connectionNum); // reference to the tempurls. Useful for caching raid and non-raid const std::vector& tempUrlVector() const; // Track the progress of http requests sent. For raid download, tracks the parts. Otherwise, uses the position through the full file. virtual m_off_t& transferPos(unsigned connectionNum); // start this part off again (eg. after abandoning slowest connection) void resetPart(unsigned connectionNum); // Return the size of a particluar part of the file, for raid. Or for non-raid the size of the whole wile. m_off_t transferSize(unsigned connectionNum); // Get the file position to upload/download to on the specified connection std::pair nextNPosForConnection(unsigned connectionNum, bool& newBufferSupplied, bool& pauseConnectionForRaid); // calculate the exact size of each of the 6 parts of a raid file. Some may not have a full last sector static m_off_t raidPartSize(unsigned part, m_off_t fullfilesize); // calculates the module between a given offset and a RAIDLINE. static m_off_t offsetToRaidLine(const m_off_t offset); // report a failed connection. The function tries to switch to 5 connection raid or a different 5 connections. Two fails without progress and we should fail the transfer as usual bool tryRaidHttpGetErrorRecovery(unsigned errorConnectionNum, bool incrementErrors); // indicate that this connection has responded with headers, and see if we now know which is the slowest connection, and make that the unused one bool detectSlowestRaidConnection(unsigned thisConnection, unsigned& slowestConnection); // Set the unused raid connection [0 - RAIDPARTS) bool setUnusedRaidConnection(unsigned newUnusedRaidConnection); // Which raid connection is not being used for downloading unsigned getUnusedRaidConnection() const; // returns how far we are through the file on average, including uncombined data m_off_t progress() const; RaidBufferManager(); ~RaidBufferManager(); private: // parameters to control raid download enum { RaidMaxChunksPerRead = 5 }; enum { RaidReadAheadChunksPausePoint = 8 }; enum { RaidReadAheadChunksUnpausePoint = 4 }; bool is_raid{}; bool is_newRaid{}; bool raidKnown{}; m_off_t deliverlimitpos{}; // end of the data that the client requested m_off_t acquirelimitpos{}; // end of the data that we need to deliver that (can be up to the // next raidline boundary) m_off_t fullfilesize{}; // end of the file // controls buffer sizes used unsigned raidLinesPerChunk; // of the six raid URLs, which 5 are we downloading from unsigned unusedRaidConnection; // storage server access URLs. It either has 6 entries for a raid file, or 1 entry for a non-raid file, or empty if we have not looked up a tempurl yet. std::vector tempurls; std::string emptyReturnString; // a connection is paused if it reads too far ahead of others. This prevents excessive buffer usage bool connectionPaused[RAIDPARTS]; // for raid, how far through the raid part we are currently m_off_t raidrequestpartpos[RAIDPARTS]; // for raid, the http requested data before combining std::deque raidinputparts[RAIDPARTS]; // the data to output currently, per connection, raid or non-raid. Re-accessible in case retries are needed std::map> asyncoutputbuffers; // piece to carry over to the next combine operation, when we don't get pieces that match the chunkceil boundaries FilePiece leftoverchunk; // the point we are at in the raid input parts. raidinputparts buffers contain data from this point in their part. m_off_t raidpartspos; // the point we are at in the output file. asyncoutputbuffers contain data from this point. m_off_t outputfilepos; // the point we started at in the output file. m_off_t startfilepos; // In the case of resuming a file, the point we got to in the output might not line up nicely with a sector in an input part. // This field allows us to start reading on a sector boundary but skip outputting data until we match where we got to last time. size_t resumewastedbytes; // track errors across the connections. A successful fetch resets the error count for a connection. Stop trying to recover if we hit 3 total. unsigned raidHttpGetErrorCount[RAIDPARTS]; bool connectionStarted[RAIDPARTS]; // For test hooks, disable avoid small requests when we need a lower speed and trigger 404/403/timeout errors bool mDisableAvoidSmallLastRequest; // take raid input part buffers and combine to form the asyncoutputbuffers void combineRaidParts(unsigned connectionNum); FilePiece* combineRaidParts(size_t partslen, size_t bufflen, m_off_t filepos, FilePiece& prevleftoverchunk); void recoverSectorFromParity(byte* dest, byte* inputbufs[], unsigned offset); void combineLastRaidLine(byte* dest, size_t nbytes); void rollInputBuffers(size_t dataToDiscard); virtual void bufferWriteCompletedAction(FilePiece& r); // decrypt and mac downloaded chunk. virtual so Transfer and DirectNode derivations can be different // calcOutputChunkPos is used to figure out how much of the available data can be passed to it virtual void finalize(FilePiece& r) = 0; virtual m_off_t calcOutputChunkPos(m_off_t acquiredpos) = 0; friend class DebugTestHook; }; class MEGA_API TransferBufferManager: public RaidBufferManager { public: // call this before starting a transfer. Extracts the vector content void setIsRaid(Transfer* transfer, const std::vector& tempUrls, m_off_t resumepos, m_off_t maxDownloadRequestSize, bool isNewRaid = ISNEWRAID_DEFVALUE); // Track the progress of http requests sent. For raid download, tracks the parts. Otherwise, // uses the full file position in the Transfer object, as it used to prior to raid. m_off_t& transferPos(unsigned connectionNum) override; // Get the file position to upload/download to on the specified connection std::pair nextNPosForConnection(unsigned connectionNum, m_off_t maxDownloadRequestSize, unsigned connectionCount, bool& newBufferSupplied, bool& pauseConnectionForRaid, m_off_t uploadspeed); TransferBufferManager(); private: Transfer* transfer; // decrypt and mac downloaded chunk void finalize(FilePiece& r) override; m_off_t calcOutputChunkPos(m_off_t acquiredpos) override; void bufferWriteCompletedAction(FilePiece& r) override; friend class DebugTestHook; }; class MEGA_API DirectReadBufferManager : public RaidBufferManager { public: // Track the progress of http requests sent. For raid download, tracks the parts. Otherwise, uses the full file position in the Transfer object, as it used to prior to raid. m_off_t& transferPos(unsigned connectionNum) override; DirectReadBufferManager(DirectRead* dr); private: DirectRead* directRead; // decrypt and mac downloaded chunk void finalize(FilePiece& r) override; m_off_t calcOutputChunkPos(m_off_t acquiredpos) override; friend class DebugTestHook; }; class MEGA_API CloudRaid { private: class CloudRaidImpl; const CloudRaidImpl* mPimpl() const { return m_pImpl.get(); } CloudRaidImpl* mPimpl() { return m_pImpl.get(); } std::unique_ptr m_pImpl{}; bool mShown{}; public: CloudRaid(); CloudRaid(TransferSlot* tslot, MegaClient* client, int connections); ~CloudRaid(); /* Instance control functionality */ bool isShown() const; /* TransferSlot functionality for RaidProxy */ bool disconnect(const std::shared_ptr& req); bool prepareRequest(const std::shared_ptr& req, const string& tempURL, m_off_t pos, m_off_t npos); bool post(const std::shared_ptr& req); bool onRequestFailure(const std::shared_ptr& req, uint8_t part, dstime& backoff); bool setTransferFailure(::mega::error e = API_EAGAIN, dstime backoff = 0); std::pair<::mega::error, dstime> checkTransferFailure(); bool setUnusedRaidConnection(uint8_t part, bool addToFaultyServers); uint8_t getUnusedRaidConnection() const; m_off_t transferred(const std::shared_ptr& req) const; bool processRequestLatency(const std::shared_ptr& req); /* RaidProxy functionality for TransferSlot */ bool init(TransferSlot* tslot, MegaClient* client, int connections); bool balancedRequest(int connection, const std::vector& tempUrls, size_t cfilesize, m_off_t cstart, size_t creqlen); bool removeRaidReq(int connection); bool resumeAllConnections(); bool raidReqDoio(int connection); bool stop(); m_off_t progress() const; m_off_t readData(int connection, byte* buf, m_off_t len); }; } // namespace #endif sdk-10.11.0/include/mega/raidproxy.h000066400000000000000000000322101516266226600171730ustar00rootroot00000000000000#ifndef MEGA_RAIDPROXY_H #define MEGA_RAIDPROXY_H 1 #include #include #include #include "mega/types.h" #include "mega/http.h" #include "raid.h" namespace mega { namespace RaidProxy { #define MAX_NUMLINES 4096 // max number of lines for the RaidReq::data array #define MAXRETRIES 10 // max number of consecutive errors for a failing part #define LAGINTERVAL 256 // number of readdata() requests until the next interval check is conducted #define MAX_ERRORS_FOR_IDLE_GOOD_SOURCE 3 // Error tolerance to consider a source as a candidate to be switched with a hanging source #if defined(SUPPORTS_TI_EMULATION_MODE) typedef unsigned int uint128_t __attribute__((mode(TI))); #else // SUPPORTS_TI_EMULATION_MODE struct uint128_t { uint64_t parts[2]; uint128_t& operator=(const uint128_t& other) { parts[0] = other.parts[0]; parts[1] = other.parts[1]; return *this; } uint128_t& operator^=(const uint128_t& other) { parts[0] ^= other.parts[0]; parts[1] ^= other.parts[1]; return *this; } }; #endif // ! SUPPORTS_TI_EMULATION_MODE typedef uint128_t raidsector_t; using HttpReqType = HttpReqDL; using HttpReqPtr = std::shared_ptr; using HttpInputBuf = ::mega::HttpReq::http_buf_t; using raidTime = ::mega::dstime; class RaidReq; class RaidReqPool; class PartFetcher { friend class RaidReq; RaidReq* rr{nullptr}; // pointer to the underlying RaidReq std::string mUrl; // part tempURL std::unique_ptr mInbuf{nullptr}; // buffer containing the whole data for the HttpReq after successing (internal HttpReq buffer is released) m_off_t mPartStartPos{}; // starting position relative to the filesize (the underlying RaidReq can be requesting a part of the file) raidTime mDelayuntil{}; // delay before this part can be processed (checked in PartFetcher::io) uint8_t mConsecutiveErrors{}; // number of consecutive errors (related to 'errors') uint8_t part{}; // raid part index bool mConnected{}; // whether the part is considered as connected. A part is considered "connected" since it is prepared (REQ_PREPARED) for HttpReq::post bool mFinished{}; // whether this part is finished (there can be pending data to process like readahead, but all the requested HttpReq data is finished) uint16_t mErrors{}; // number of errors for this part (hanging, failures, etc.) raidTime lastdata; // last data for this part's request (keep the same naming as HttpReq, so no 'mLastData') std::chrono::time_point mPostStartTime{}; // starting time for HttpReq::post int64_t mTimeInflight{}; // total time in flight for this part m_off_t mReqBytesReceived{}; // total number of bytes received for this part bool mPostCompleted{}; // whether a HttpReq::post has been completed m_off_t mSourcesize{}; // full source size (which can be smaller than RaidReq::paddedpartsize) m_off_t mPos{}; // part current position m_off_t mRem{}; // remaining data for this part m_off_t mRemfeed{}; // active remaining read length (related to 'rem') map> mReadahead; // read-ahead data void setposrem(); // sets the next read position (pos) and the remaining read length (rem/remfeed) bool setremfeed(m_off_t); // sets the remfeed depending on the number of bytes param and the remaining (rem) part data int64_t onFailure(); // Handle request failures m_off_t getSocketSpeed() const; // Get part throughput in bytes per millisec public: static constexpr raidTime LASTDATA_DSTIME_FOR_HANGING_SOURCE = 300; PartFetcher(); ~PartFetcher(); bool setsource(const std::string&, RaidReq*, uint8_t); // Set URL for this source, part start pos and source size int64_t trigger(raidTime = 0, bool = false); // Add request for processing in RaidReqPool (with an optional // delay). Also checks if this part shouldn't be processed. bool directTrigger(bool = true); // Add request for direct processing in RaidReqPool (with no delay) void closesocket(bool = false); // reset part and optionally disconnect the HttpReq (depending on if it is going to be used) int64_t io(); // process HttpReq void cont(m_off_t); // request a further chunk of data from the open connection bool feedreadahead(); // Process available read ahead for this part and send it to RaidReq::procdata void resume(bool = false); // resume fetching on a parked source that has become eligible again m_off_t progress() const; // get part progress (data inflight, readahead...) }; class RaidReq { friend class PartFetcher; static constexpr raidTime LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK = 1000; static constexpr raidTime LASTDATA_DSTIME_FOR_TIMEOUT = LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK + (LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK / 2); static constexpr raidTime LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK_WITH_NO_HANGING_SOURCES = 3000; static constexpr raidTime LASTDATA_DSTIME_FOR_TIMEOUT_WITH_NO_HANGING_SOURCES = 6000; RaidReqPool& mPool; std::shared_ptr mCloudRaid; // CloudRaid controller std::vector mHttpReqs; // Download HttpReqs std::array mFetcher; m_off_t mNumLines; // dynamic NUMLINES for data and parity buffer sizes size_t mDataSize; // size of mData size_t mParitySize; // size of mParity std::array mPartpos{}; // incoming part positions relative to dataline std::array mFeedlag{}; // accumulated remfeed at shiftata() to identify slow sources std::unique_ptr mData; // always starts on a RAID line boundary std::unique_ptr mParity; // parity sectors std::unique_ptr mInvalid; // bitfield indicating which sectors have yet to be received m_off_t mDataline{}; // data's position relative to the file's beginning in RAID lines m_off_t mCompleted{}; // valid data RAID lines in data m_off_t mSkip{}; // bytes to skip from start of data m_off_t mRem; // bytes remaining for this request size_t mFilesize; // total file size m_off_t mReqStartPos; // RaidReq offset - starting pos (a RaidReq can request just a part of the whole file) m_off_t mPaddedpartsize; // the size of the biggest part (0) rounded up to the next RAIDSECTOR boundary int mLagrounds{}; // number of accumulated additions to feedlag[] raidTime lastdata; // timestamp of RaidReq creation or last data chunk forwarded to user bool mHaddata{}; // flag indicating whether any data was forwarded to user on this RaidReq bool mReported{}; // whether a feed stuck (RaidReq not progressing) has been already reported bool mMissingSource{}; // disable all-channel logic bool mFaultysourceadded{}; // whether a failing or hanging source has been added to faulty servers storage uint8_t mUnusedRaidConnection; // Unused connection or bad source void calculateNumLinesAndBufferSizes(); // Calculate the number of lines (NUMLINES) depending on the file size, and use that value to calculate mData and mParity buffer sizes void shiftdata(m_off_t); // shift already served data from the data array bool allconnected(uint8_t = RAIDPARTS) const; // whether all sources are connected, optionally excluding a RAIDPART (default value 'RAIDPARTS' won't exclude any part) uint8_t numPartsUnfinished() const; // how many parts are unfinished, the unused part will always count as "unfinished" uint8_t hangingSources(uint8_t*, uint8_t*); // how many sources are hanging (lastdata from the HttpReq exceeds the hanging time value 'LASTDATA_DSTIME_FOR_HANGING_SOURCE') void watchdog(); // check hanging sources bool differenceBetweenPartsSpeedIsSignificant(uint8_t part1, uint8_t part2) const; // return whether there is a significant difference between two parts based on a ratio (part1 should be faster than part2) bool getSlowestAndFastestParts(uint8_t&, uint8_t&, bool = true) const; // Get slowest and fastest parts (using feedlag) public: struct Params { std::vector tempUrls; size_t filesize; m_off_t reqStartPos; size_t reqlen; Params(const std::vector& tempUrls, size_t cfilesize, m_off_t creqStartPos, size_t creqlen) : tempUrls(tempUrls), filesize(cfilesize), reqStartPos(creqStartPos), reqlen(creqlen) {} }; RaidReq(const Params&, RaidReqPool&, const std::shared_ptr&); ~RaidReq(); void procdata(uint8_t, byte*, m_off_t, m_off_t); // process HttpReq data, either for read ahead or for assembled data buffer m_off_t readdata(byte*, m_off_t); // serve completed data to the external byte buffer param void dispatchio(const HttpReqPtr&); // add active requests to RaidReqPool for HttpReq processing void resumeall(uint8_t = RAIDPARTS); // resume part fetchers void procreadahead(); // process read ahead data void disconnect(); // disconnect all HttpReqs uint8_t processFeedLag(); // check slow sources m_off_t progress() const; // get the progress of the whole RaidReq (including part fetchers) uint8_t unusedPart() const; // inactive source (RAIDPARTS for no inactive source) std::pair<::mega::error, raidTime> checkTransferFailure(); // Check if CloudRaid transfer has failed (it could have happened in other RaidReq) bool setNewUnusedRaidConnection(uint8_t part, // set the shared unused raid connection in CloudRaid. Optionally add them to faulty servers persistent storage. bool addToFaultyServers = true); void processRequestLatency(const HttpReqPtr&); // process the request latency for a given source static size_t raidPartSize(uint8_t part, size_t fullfilesize); // calculate part size }; class RaidReqPool { bool mIsRunning{true}; // RaidReqPool loop control flag std::unique_ptr mRaidReq{nullptr}; // RaidReq owned by this RaidReqPool std::set mSetHttpReqs; // HttpReq set to avoid repetition in scheduledio std::set> mScheduledio; // HttpReqs to be processed in raidproxyio() at raidTime public: RaidReqPool(); ~RaidReqPool(); void raidproxyio(); // process HttpReqs from scheduledio void request(const RaidReq::Params& p, const std::shared_ptr&); // create and add RaidReq to RaidReqPool bool addScheduledio(raidTime, const HttpReqPtr&); // add HttpReq to scheduledio collection bool addDirectio(const HttpReqPtr&); // add HttpReq for immediate io processing bool lookupHttpReq(const HttpReqPtr& httpReq) { return mSetHttpReqs.find(httpReq) != mSetHttpReqs.end(); } bool removeio(const HttpReqPtr&); // remove HttpReq from scheduledio RaidReq* rr() { return mRaidReq.get(); } // returns RaidReq pointer }; } // namespace RaidProxy } // namespace mega #endif sdk-10.11.0/include/mega/recent_actions.h000066400000000000000000000127711516266226600201640ustar00rootroot00000000000000/** * @file recent_actions.h * @brief Business logic for getRecentActions and getRecentActionById * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/types.h" namespace mega { class MegaClient; class RecentActions { public: explicit RecentActions(MegaClient* client); /** * @brief Fetch recent action buckets for an account. * * Queries the node tree for recently changed files, then groups them into action * buckets (by owner, parent, time window, media/updated flags), sorts nodes within * each bucket and sorts buckets by time. * * @param maxcount Maximum number of nodes to consider. * @param since Only consider nodes changed after this timestamp. * @param excludeSensitives Exclude nodes marked as sensitive. * @return The resulting bucket vector, sorted most-recent-first. */ recentactions_vector getRecentActions(unsigned maxcount, m_time_t since, bool excludeSensitives) const; /** * @brief Retrieve a single recent action bucket by its string identifier. * * Parses the id, queries the node tree for matching nodes, applies all bucket * membership filters and fills @p output with the matching recentaction entry. * * @param id The bucket identifier returned by MegaRecentActionBucket::getId(). * @param output The recentaction struct to fill with the result. Only valid if the return value * is API_OK. * @return API_OK on success, API_EARGS if id is invalid, API_ENOENT if no nodes match. */ error getById(const char* id, recentaction& output) const; /** * @brief Retrieve a single recent action bucket by its string identifier, overriding the * excludeSensitives flag embedded in the id. * * Parses the id, queries the node tree for matching nodes using @p excludeSensitives * (ignoring the value encoded in the id), applies all bucket membership filters and fills * @p output with the matching recentaction entry. * * @param id The bucket identifier returned by MegaRecentActionBucket::getId(). * @param excludeSensitives If true, sensitive nodes are excluded regardless of the flag in * the id. If false, sensitive nodes are included. * @param output The recentaction struct to fill with the result. Only valid if the * return value is API_OK. * @return API_OK on success, API_EARGS if id is invalid, API_ENOENT if no nodes match. */ error getById(const char* id, bool excludeSensitives, recentaction& output) const; private: /** * @brief Build a recentactions_vector from a pre-fetched node vector. */ recentactions_vector buildFromNodes(sharedNode_vector&& v, bool excludeSensitives) const; /** * @brief Fetch candidate nodes for a recent action bucket. */ sharedNode_vector getBucketCandidates(m_time_t startTime, m_time_t endTime, handle parent, bool isMedia, bool excludeSensitives) const; /** * @brief Sort bucket nodes by creation time descending. */ void sortBucketNodes(sharedNode_vector& nodes) const; /** * @brief Core implementation shared by both public getById overloads. * * Parses @p id, then uses @p excludeSensitives to filter candidate nodes. * If @p excludeSensitives is std::nullopt the value encoded in the id is used; * otherwise the provided value overrides it. * * @param id Original bucket identifier string. * @param excludeSensitives Override for the sensitivity filter, or std::nullopt to use the * value embedded in @p id. * @param output Filled on API_OK. */ error getById(const char* id, std::optional excludeSensitives, recentaction& output) const; /** * @brief Filter candidate nodes to those that match the given bucket metadata and time window. * * Retains only nodes whose owner, ctime range, updated flag and media flag all match @p meta. * * @param candidates Nodes pre-fetched from the node tree. * @param meta Bucket identity (user, parent, media, updated). * @param startTime Inclusive lower bound of the time window (epoch seconds). * @param endTime Exclusive upper bound of the time window (epoch seconds). * @return Nodes that belong to the bucket. */ sharedNode_vector filterCandidatesByMeta(const sharedNode_vector& candidates, const RecentActionBucketMeta& meta, m_time_t startTime, m_time_t endTime) const; MegaClient* mClient; }; } // namespace mega sdk-10.11.0/include/mega/request.h000066400000000000000000000123741516266226600166530ustar00rootroot00000000000000/** * @file mega/request.h * @brief Generic request interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_REQUEST_H #define MEGA_REQUEST_H 1 #include "json.h" #include "types.h" #include namespace mega { // API request class MEGA_API Request { private: vector> cmds; string jsonresponse; JSON json; size_t processindex = 0; JSONSplitter mJsonSplitter; size_t mChunkedProgress = 0; // once we send the commands, any retry must be for exactly // the same JSON, or idempotence will not work properly mutable string cachedJSON; mutable string cachedIdempotenceId; mutable string cachedCounts; public: void add(Command*); size_t size() const; string get(MegaClient* client, char reqidCounter[10], string& idempotenceId) const; void serverresponse(string&& movestring, MegaClient*); void servererror(const std::string &e, MegaClient* client); void process(MegaClient* client); bool processCmdJSON(Command* cmd, bool couldBeError, JSON& jsonResponse); bool processSeqTag(Command* cmd, bool withJSON, bool& parsedOk, bool inSeqTagArray, JSON& processingJson); m_off_t processChunk(const char* chunk, MegaClient*); m_off_t totalChunkedProgress(); void clear(); bool empty() const; void swap(Request&); bool stopProcessing = false; // if contains only one command and that command is FetchNodes bool isFetchNodes() const; Command* getCurrentCommand(); }; class MEGA_API RequestDispatcher { // these ones have been sent to the server, but we haven't received the response yet Request inflightreq; retryreason_t inflightFailReason = RETRY_NONE; // client-server request double-buffering, in batches of up to MAX_COMMANDS deque nextreqs; // flags for dealing with resetting everything from a command in progress bool processing = false; bool clearWhenSafe = false; static const int MAX_COMMANDS = 10000; // unique request ID char reqid[10]; public: RequestDispatcher(PrnGen&); // Queue a command to be send to MEGA. Some commands must go in their own batch (in case other commands fail the whole batch), determined by the Command's `batchSeparately` field. void add(common::Badge badge, Command*); // Commands are waiting and could be sent (could be a retry if connection failed etc) (they are not already sent, not awaiting response) bool readyToSend() const; // True if we started sending a Request and haven't received a server response yet, // and stays true even through network errors, retries, etc until we get that response bool cmdsInflight() const; // True if there is a request that has failed and needs retry because the API returned // an error: -3 or -4 bool retryReasonIsApi() const; Command* getCurrentCommand(bool currSeqtagSeen); /** * @brief get the set of commands to be sent to the server (could be a retry) * @param includesFetchingNodes set to whether the commands include fetch nodes */ string serverrequest(bool& includesFetchingNodes, MegaClient* client, string& idempotenceId); // Once we get a successful reply from the server, call this to complete everything // Since we need to support idempotence, we cannot add anything more to the in-progress request void serverresponse(string&& movestring, MegaClient*); // Call this function when a chunk of data is received from the server for chunked requests // The return value is the number of bytes that must be discarded. The chunk in the next call // must start with the data that was not discarded in the previous call size_t serverChunk(const char *chunk, MegaClient*); // Amount of data consumed for chunked requests, 0 for non-chunked requests size_t chunkedProgress(); // If we need to retry (eg due to networking issue, abandoned req, server refusal etc) call this and we will abandon that attempt. // The req will be retried via the next serverrequest(), and idempotence takes care of avoiding duplicate actions void inflightFailure(retryreason_t reason); // If the server itself reports failure, use this one to resolve (commands are all failed) // and we will move to the next Request void servererror(const std::string &e, MegaClient*); void continueProcessing(MegaClient* client); void clear(); #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) Request deferredRequests; std::function deferRequests; void sendDeferred(); #endif #ifdef MEGA_MEASURE_CODE uint64_t csRequestsSent = 0, csRequestsCompleted = 0; uint64_t csBatchesSent = 0, csBatchesReceived = 0; #endif }; } // namespace #endif sdk-10.11.0/include/mega/scoped_helpers.h000066400000000000000000000077141516266226600201640ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { // Executes a user-provided function when destroyed. class ScopedDestructor { std::function mDestructor; public: ScopedDestructor(std::function destructor): mDestructor(std::move(destructor)) {} ScopedDestructor(ScopedDestructor&& other) noexcept { mDestructor = std::move(other.mDestructor); // final status of the source of the moved is not guaranteed by standard beyond staying in a // valid state, and Apple clang actually leaves the previous value other.mDestructor = nullptr; } ScopedDestructor(const ScopedDestructor& other) = delete; ~ScopedDestructor() { if (mDestructor != nullptr) mDestructor(); } ScopedDestructor& operator=(ScopedDestructor&& rhs) noexcept { mDestructor = std::move(rhs.mDestructor); // final status of the source of the moved is not guaranteed by standard beyond staying in a // valid state, and Apple clang actually leaves the previous value rhs.mDestructor = nullptr; return *this; } ScopedDestructor& operator=(const ScopedDestructor& rhs) = delete; }; // ScopedDestructor // Returns an object that executes function when destroyed. template auto makeScopedDestructor(Function function) -> std::enable_if_t, void>, ScopedDestructor> { return ScopedDestructor(std::move(function)); } // Returns an object that executes function when destroyed. // // This specialization differs from the above as it allows you to execute a // function that requires bound arguments. template>> auto makeScopedDestructor(Function function, Arguments&&... arguments) { auto destructor = std::bind(std::move(function), std::forward(arguments)...); return makeScopedDestructor(std::move(destructor)); } // Changes the length (size) of a sequence and returns an object that will // restore that sequence's original length when destroyed. template auto makeScopedSizeRestorer(T& instance, std::size_t newSize) { auto oldSize = SizeTraits::size(instance); ResizeTraits::resize(instance, newSize); return makeScopedDestructor( [&instance, oldSize]() { ResizeTraits::resize(instance, oldSize); }); } // Convenience specialization of the above. template auto makeScopedSizeRestorer(T& instance) { return makeScopedSizeRestorer(instance, SizeTraits::size(instance)); } // Changes the value of a specified location and returns an object that will // restore that location's original value when destroyed. template>> auto makeScopedValue(T& location, U&& value) { auto destructor = [oldValue = std::move(location), &location]() { location = std::move(oldValue); }; // destructor location = std::forward(value); return makeScopedDestructor(std::move(destructor)); } // Instantiates a shared_ptr from an existing raw pointer. template>, typename Tr = std::remove_pointer_t, typename Deleter = std::default_delete> auto makeSharedFrom(Tp pointer, Deleter deleter = Deleter()) { return std::shared_ptr(pointer, std::move(deleter)); } // Instantiates a unique_ptr from an existing raw pointer. template>, typename Tr = std::remove_pointer_t, typename Deleter = std::default_delete> auto makeUniqueFrom(Tp pointer, Deleter deleter = Deleter()) { return std::unique_ptr(pointer, std::move(deleter)); } } // mega sdk-10.11.0/include/mega/scoped_timer.h000066400000000000000000000007101516266226600176270ustar00rootroot00000000000000#pragma once #include namespace mega { // // T can be steady_clock, system_clock or high_resolution_clock // template class ScopedTimer { public: using time_point = typename T::time_point; using duration = typename T::duration; duration passedTime() const { return T::now() - mStart; } private: time_point mStart{T::now()}; }; using ScopedSteadyTimer = ScopedTimer; } sdk-10.11.0/include/mega/serialize64.h000066400000000000000000000017101516266226600173140ustar00rootroot00000000000000/** * @file mega/serialize64.h * @brief 64-bit int serialization/unserialization * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SERIALIZE64_H #define MEGA_SERIALIZE64_H 1 #include "types.h" namespace mega { // 64-bit int serialization/unserialization struct MEGA_API Serialize64 { static int serialize(byte *, uint64_t); static int unserialize(byte*, int, uint64_t*); }; } // namespace #endif sdk-10.11.0/include/mega/setandelement.h000066400000000000000000000442271516266226600200150ustar00rootroot00000000000000/** * @file mega/setandelement.h * @brief Class for manipulating Sets and their Elements * * (c) 2013-2022 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SET_AND_ELEMENT_H #define MEGA_SET_AND_ELEMENT_H 1 #include "types.h" #include #include #include #include #include namespace mega { /** * @brief Base class for common characteristics of Set and Element */ class CommonSE { public: // get own id const handle& id() const { return mId; } // get key used for encrypting attrs // SETNODEKEYLENGTH at types.h (128 = SymmCipher::KEYLENGTH) const std::string& key() const { return mKey; } // get timestamp const m_time_t& ts() const { return mTs; } // get own name const std::string& name() const { return getAttr(nameTag); } // set own id void setId(handle id) { mId = id; } // set key used for encrypting attrs void setKey(const std::string& key) { mKey = key; } void setKey(std::string&& key) { mKey = std::move(key); } // set timestamp void setTs(m_time_t ts) { mTs = ts; } // set own name void setName(std::string&& name); // test for attrs (including empty "" ones) bool hasAttrs() const { return !!mAttrs; } // test for encrypted attrs, that will need a call to decryptAttributes() bool hasEncrAttrs() const { return !!mEncryptedAttrs; } // set encrypted attrs, that will need a call to decryptAttributes() void setEncryptedAttrs(std::string&& eattrs) { mEncryptedAttrs.reset(new std::string(std::move(eattrs))); } // decrypt attributes set with setEncryptedAttrs(), and replace internal attrs bool decryptAttributes(std::function f); // encrypt internal attrs and return the result std::string encryptAttributes(std::function f) const; static const int HANDLESIZE = 8; static const int PUBLICHANDLESIZE = 6; protected: CommonSE() = default; CommonSE(handle id, std::string&& key, string_map&& attrs) : mId(id), mKey(std::move(key)), mAttrs(new string_map(std::move(attrs))) {} CommonSE(const CommonSE& src) { replaceCurrent(src); } CommonSE& operator=(const CommonSE& src) { replaceCurrent(src); return *this; } CommonSE(CommonSE&&) = default; CommonSE& operator=(CommonSE&&) = default; ~CommonSE() = default; handle mId = UNDEF; std::string mKey; std::unique_ptr mAttrs; m_time_t mTs = 0; // timestamp void setAttr(const std::string& tag, std::string&& value); // set any non-standard attr const std::string& getAttr(const std::string& tag) const; bool hasAttrChanged(const std::string& tag, const std::unique_ptr& otherAttrs) const; void rebaseCommonAttrsOn(const string_map* baseAttrs); static bool validChangeType(const uint64_t& typ, const uint64_t& typMax) { assert(typ < typMax); return typ < typMax; } std::unique_ptr mEncryptedAttrs; // "at": up to 65535 bytes of miscellaneous data, encrypted with mKey static const std::string nameTag; // "n", used for 'name' attribute private: void replaceCurrent(const CommonSE& src) { this->mId = src.mId; this->mKey = src.mKey; this->mAttrs.reset(src.mAttrs ? new string_map(*src.mAttrs) : nullptr); this->mTs = src.mTs; this->mEncryptedAttrs.reset(src.mEncryptedAttrs ? new std::string(*src.mEncryptedAttrs) : nullptr); } }; /** * @brief Internal representation of an Element */ class SetElement : public CommonSE, public Cacheable { public: SetElement() = default; SetElement(handle sid, handle node, handle elemId, std::string&& key, string_map&& attrs) : CommonSE(elemId, std::move(key), std::move(attrs)), mSetId(sid), mNodeHandle(node) {} SetElement(const SetElement& src) : CommonSE(src) { replaceCurrent(src); } SetElement& operator=(const SetElement& src) { CommonSE::operator=(src); replaceCurrent(src); return *this; } SetElement(SetElement&&) = default; SetElement& operator=(SetElement&&) = default; ~SetElement() = default; // return id of the set that owns this Element const handle& set() const { return mSetId; } // return handle of the node represented by this Element const handle& node() const { return mNodeHandle; } // return order of this Element int64_t order() const { return mOrder ? *mOrder : 0; } // set id of the set that owns this Element void setSet(handle s) { mSetId = s; } // set handle of the node represented by this Element void setNode(handle nh) { mNodeHandle = nh; mNodeMetadata.reset(); } // set order of this Element void setOrder(int64_t order); // return true if last change modified order of this Element // (useful for instances that only contain updates) bool hasOrder() const { return !!mOrder; } // replace internal parameters with the ones of 'el', and mark any CH_EL_XXX change bool updateWith(SetElement&& el); // apply attrs on top of the ones of 'el' (useful for instances that only contain updates) void rebaseAttrsOn(const SetElement& el) { rebaseCommonAttrsOn(el.mAttrs.get()); } // mark attrs being cleared by the last update (reason for internal container being null) // (useful for instances that only contain updates) void setAttrsClearedByLastUpdate(bool cleared) { mAttrsClearedByLastUpdate = cleared; } // return true if attrs have been cleared in the last update (reason for internal container being null) // (useful for instances that only contain updates) bool hasAttrsClearedByLastUpdate() const { return mAttrsClearedByLastUpdate; } // mark a change to internal parameters (useful for app notifications) void setChanged(int changeType); // reset changes of internal parameters (call after app has been notified) void resetChanges() { mChanges = 0; } // return changes to internal parameters (useful for app notifications) unsigned long changes() const { return mChanges.to_ulong(); } // return true if internal parameter pointed out by changeType has changed (useful for app // notifications) bool hasChanged(uint64_t changeType) const { return validChangeType(changeType, CH_EL_SIZE) ? mChanges[static_cast(changeType)] : false; } bool serialize(std::string*) const override; static std::unique_ptr unserialize(std::string* d); enum // match MegaSetElement::CHANGE_TYPE_ELEM_XXX values { CH_EL_NEW, // point out that this is a new Element CH_EL_NAME, // point out that 'name' attr has changed CH_EL_ORDER, // point out that order has changed CH_EL_REMOVED, // point out that this Element has been removed CH_EL_SIZE }; struct NodeMetadata { handle h = UNDEF; // node handle handle u = UNDEF; // owning user m_off_t s = 0; // size string at; // node attributes string fingerprint; string filename; string fa; // file attributes m_time_t ts; // timestamp }; // return node metadata in case of Element in preview, null otherwise const NodeMetadata* nodeMetadata() const { return mNodeMetadata.get(); } void setNodeMetadata(NodeMetadata&& nm) { assert(mNodeHandle == nm.h); mNodeMetadata.reset(new NodeMetadata(std::move(nm))); } private: handle mSetId = UNDEF; handle mNodeHandle = UNDEF; std::unique_ptr mNodeMetadata; std::unique_ptr mOrder; bool mAttrsClearedByLastUpdate = false; std::bitset mChanges; void replaceCurrent(const SetElement& src) { this->mSetId = src.mSetId; this->mNodeHandle = src.mNodeHandle; this->mNodeMetadata.reset(src.mNodeMetadata ? new NodeMetadata(*src.mNodeMetadata) : nullptr); this->mOrder.reset(src.mOrder ? new int64_t(*src.mOrder) : nullptr); this->mAttrsClearedByLastUpdate = src.mAttrsClearedByLastUpdate; this->mChanges = src.mChanges; } }; /** * @brief Internal representation of a public link from a set */ class PublicLinkSet { public: enum class LinkDeletionReason : uint8_t { NO_REMOVED = 0, BY_USER, DISPUTE, ETD, ATD, }; static constexpr uint64_t ETD_REMOVED_API_CODE = 4294967275; // Defined by API static constexpr uint64_t ATD_REMOVED_API_CODE = 4294967274; // Defined by API static constexpr uint64_t USER_REMOVED_API_CODE = 0; // Defined by API static LinkDeletionReason apiCodeToDeletionReason(const int64_t apiCode) { switch (apiCode) { case USER_REMOVED_API_CODE: return LinkDeletionReason::BY_USER; case ETD_REMOVED_API_CODE: return LinkDeletionReason::ETD; case ATD_REMOVED_API_CODE: return LinkDeletionReason::ATD; default: return LinkDeletionReason::DISPUTE; } } static std::string LinkDeletionReasonToString(const LinkDeletionReason reason) { switch (reason) { case LinkDeletionReason::NO_REMOVED: return "not removed"; case LinkDeletionReason::BY_USER: return "by user"; case LinkDeletionReason::DISPUTE: return "dispute"; case LinkDeletionReason::ETD: return "ETD"; case LinkDeletionReason::ATD: return "ATD"; } // Silent compilation warning return ""; } PublicLinkSet(): mPublicId(UNDEF) {} PublicLinkSet(handle publicId): mPublicId(publicId) {} PublicLinkSet* copy() const { return new PublicLinkSet(*this); } // set public id of the set (Set exported) void setPublicId(handle pid) { mPublicId = pid; } // set if link has been taken down void setTakeDown(bool takedown) { mTakedown = takedown; } // set the reason for link removal void setLinkDeletionReason(const LinkDeletionReason deletionReason) { mLinkDeletionReason = deletionReason; } // return public id of the set handle getPublicHandle() const { return mPublicId; } // returns true if link has been taken down bool isTakenDown() const { return mTakedown; } // returns the reason for link removal LinkDeletionReason getLinkDeletionReason() const { return mLinkDeletionReason; } protected: handle mPublicId{UNDEF}; bool mTakedown{}; LinkDeletionReason mLinkDeletionReason{LinkDeletionReason::NO_REMOVED}; PublicLinkSet(const PublicLinkSet& publicLink): mPublicId(publicLink.mPublicId), mTakedown(publicLink.mTakedown), mLinkDeletionReason(publicLink.mLinkDeletionReason) {} }; /** * @brief Internal representation of a Set */ class Set: public CommonSE, public Cacheable { public: using SetType = uint8_t; Set() = default; Set(handle id, std::string&& key, handle user, string_map&& attrs, SetType type = TYPE_ALBUM): CommonSE(id, std::move(key), std::move(attrs)), mUser(user), mType(type) {} Set(const Set& s): CommonSE(s), mUser(s.mUser), mCTs(s.mCTs), mType(s.mType), mChanges(s.mChanges) { if (s.getPublicLink()) { mPublicLink.reset(s.getPublicLink()->copy()); } } Set(Set&& s): CommonSE(s), mUser(s.mUser), mCTs(s.mCTs), mType(s.mType), mChanges(s.mChanges), mPublicLink(std::move(s.mPublicLink)) {} Set& operator=(const Set& src) { mId = src.mId; mKey = src.mKey; mAttrs.reset(src.mAttrs ? new string_map(*src.mAttrs) : nullptr); mTs = src.mTs; mEncryptedAttrs.reset(src.mEncryptedAttrs ? new std::string(*src.mEncryptedAttrs) : nullptr); mUser = src.mUser; mCTs = src.mCTs; mType = src.mType; mChanges = src.mChanges; if (src.getPublicLink()) { mPublicLink.reset(src.getPublicLink()->copy()); } return *this; } Set& operator=(Set&& src) { mId = src.mId; mKey = src.mKey; mAttrs = std::move(src.mAttrs); mTs = src.mTs; mEncryptedAttrs = std::move(src.mEncryptedAttrs); mUser = src.mUser; mCTs = src.mCTs; mType = src.mType; mChanges = src.mChanges; mPublicLink = std::move(src.mPublicLink); return *this; } // return public id of the set handle publicId() const { return mPublicLink ? mPublicLink->getPublicHandle() : UNDEF; } // return id of the user that owns this Set const handle& user() const { return mUser; } // return id of the Element that was set as cover, or UNDEF if none was set handle cover() const; // get creation timestamp const m_time_t& cts() const { return mCTs; } // get Set type SetType type() const { return mType; } // get public link info const PublicLinkSet* getPublicLink() const { return mPublicLink.get(); } // set id of the user that owns this Set void setUser(handle uh) { mUser = uh; } // set id of the Element that will act as cover; pass UNDEF to remove cover void setCover(handle h); // set creation timestamp void setCTs(m_time_t ts) { mCTs = ts; } // set Set type void setType(SetType t) { mType = t; } // set public link info (take ownership) void setPublicLink(std::unique_ptr&& publicLink) { mPublicLink = std::move(publicLink); } // set public link info (No take ownership) void setPublicLink(const PublicLinkSet* publicLink) { mPublicLink.reset(publicLink ? publicLink->copy() : nullptr); } // replace internal parameters with the ones of 's', and mark any CH_XXX change bool updateWith(Set&& s); // apply attrs on top of the ones of 's' (useful for instances that only contain updates) void rebaseAttrsOn(const Set& s) { rebaseCommonAttrsOn(s.mAttrs.get()); } // mark a change to internal parameters (useful for app notifications) void setChanged(int changeType); // reset changes of internal parameters (call after app has been notified) void resetChanges() { mChanges = 0; } // return changes to internal parameters (useful for app notifications) unsigned long changes() const { return mChanges.to_ulong(); } // return true if internal parameter pointed out by changeType has changed (useful for app // notifications) bool hasChanged(uint64_t changeType) const { return validChangeType(changeType, CH_SIZE) ? mChanges[static_cast(changeType)] : false; } // Returns true if Set is exported bool isExported() const { return publicId() != UNDEF; } bool serialize(std::string*) const override; static std::unique_ptr unserialize(std::string* d); enum // match MegaSet::CHANGE_TYPE_XXX values { CH_NEW, // point out that this is a new Set CH_NAME, // point out that 'name' attr has changed CH_COVER, // point out that 'cover' attr has changed CH_REMOVED, // point out that this Set has been removed CH_EXPORTED,// point out that this Set has been exported (shared) or disabled (stopped being shared) CH_SIZE }; enum : SetType { TYPE_ALBUM = 0, TYPE_PLAYLIST, TYPE_SIZE }; private: handle mUser = UNDEF; m_time_t mCTs = 0; // creation timestamp SetType mType = TYPE_ALBUM; std::bitset mChanges; std::unique_ptr mPublicLink; static const std::string coverTag; // "c", used for 'cover' attribute }; using elementsmap_t = std::map; } //namespace #endif sdk-10.11.0/include/mega/share.h000066400000000000000000000033351516266226600162620ustar00rootroot00000000000000/** * @file mega/share.h * @brief Classes for manipulating share credentials * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SHARE_H #define MEGA_SHARE_H 1 #include "types.h" #include "user.h" #include "megaclient.h" namespace mega { struct NewShare; // share credentials struct MEGA_API Share { accesslevel_t access; User* user; m_time_t ts; PendingContactRequest* pcr; void update(accesslevel_t, m_time_t, PendingContactRequest* = NULL); void serialize(string*); static NewShare* unserialize(int, handle, const byte *, const char**, const char*); Share(User*, accesslevel_t, m_time_t, PendingContactRequest* = NULL); }; // new share credentials (will be merged into node as soon as it appears) struct MEGA_API NewShare { handle h; int outgoing; handle peer; accesslevel_t access; m_time_t ts; handle pending; bool upgrade_pending_to_full; bool have_key, have_auth, remove_key; byte key[SymmCipher::BLOCKSIZE]; byte auth[SymmCipher::BLOCKSIZE]; NewShare(handle, int, handle, accesslevel_t, m_time_t, const byte*, const byte* = NULL, handle = UNDEF, bool = false, bool = false); }; } // namespace #endif sdk-10.11.0/include/mega/sharenodekeys.h000066400000000000000000000032321516266226600200200ustar00rootroot00000000000000/** * @file mega/sharenodekeys.h * @brief cr element share/node map key generator * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SHARENODEKEYS_H #define MEGA_SHARENODEKEYS_H 1 #include "types.h" namespace mega { // cr element share/node map key generator class MEGA_API ShareNodeKeys { sharedNode_vector shares; vector items; string keys; int addshare(std::shared_ptr); public: // a convenience function for calling the full add() below when working with Node* void add(std::shared_ptr, std::shared_ptr, bool); // Adds keys needed for sharing the node (specifed wtih nodekey/nodehandle) to the `keys` and `items` collections. // Each node may be in multiple shares so the parent chain is traversed and if this node is in multiple shares, then multiple keys are added. // The result is suitable for sending all the collected keys for each share, per Node, to the API. void add(const string& nodekey, handle nodehandle, std::shared_ptr, bool, const byte* = NULL, int = 0); void get(Command*, bool skiphandles = false); }; } // namespace #endif sdk-10.11.0/include/mega/sync.h000066400000000000000000003042501516266226600161340ustar00rootroot00000000000000/** * @file mega/sync.h * @brief Class for synchronizing local and remote trees * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SYNC_H #define MEGA_SYNC_H 1 #include "db.h" #include "waiter.h" #include #include #include #include #ifdef ENABLE_SYNC #include "node.h" #include "syncinternals/syncinternals.h" #include "syncinternals/synciuploadthrottlingmanager.h" namespace mega { struct MegaApp; class HeartBeatSyncInfo; class BackupInfoSync; class BackupMonitor; class MegaClient; struct JSON; class JSONWriter; using SyncIDtoConflictInfoMap = std::map>; // How should the sync engine detect filesystem changes? enum ChangeDetectionMethod { // Via filesystem event notifications. // // If the filesystem notification subsystem encounters an unrecoverable // error then all asssociated syncs will be failed unless the user has // specified a scan frequency. CDM_NOTIFICATIONS, // Via periodic rescanning. // // The user must specify a scan frequency in order to use this mode. CDM_PERIODIC_SCANNING, // Unknown change detection method. // // A possible result of importing a user-edited sync config. CDM_UNKNOWN }; // ChangeDetectionMethod ChangeDetectionMethod changeDetectionMethodFromString(const string& method); string changeDetectionMethodToString(const ChangeDetectionMethod method); class SyncConfig { public: enum Type { TYPE_UP = 0x01, // sync up from local to remote TYPE_DOWN = 0x02, // sync down from remote to local TYPE_TWOWAY = TYPE_UP | TYPE_DOWN, // Two-way sync TYPE_BACKUP, // special sync up from local to remote, automatically disabled when remote changed }; SyncConfig() = default; SyncConfig(LocalPath localPath, string syncName, NodeHandle remoteNode, const string& remotePath, const fsfp_t localFingerprint, const LocalPath& externalDrivePath, const bool enabled = true, const Type syncType = TYPE_TWOWAY, const SyncError error = NO_SYNC_ERROR, const SyncWarning warning = NO_SYNC_WARNING, handle hearBeatID = UNDEF ); // the local path of the sync root folder const LocalPath& getLocalPath() const; // returns the type of the sync Type getType() const; // If the sync is enabled, we will auto-start it bool getEnabled() const; void setEnabled(bool enabled); // Whether this is a backup sync. bool isBackup() const; // Whether this sync is backed by an external device. bool isExternal() const; bool isInternal() const; // check if we need to notify the App about error/enable flag changes bool stateFieldsChanged(); /** * @brief In case this is an external backup, returns true if this is a backup sync and the * given path belongs to the external drive. Always true for non-external backups. */ bool isGoodPathForExternalBackup(const LocalPath& path) const { return !isExternal() || (isBackup() && mExternalDrivePath.isContainingPathOf(path)); } string syncErrorToStr(); static string syncErrorToStr(SyncError errorCode); // enabled/disabled by the user bool mEnabled = true; // the local path of the sync LocalPath mLocalPath; // name of the sync (if localpath is not adequate) string mName; // the remote handle of the sync NodeHandle mRemoteNode; // the path to the remote node, as last known (not definitive) string mOriginalPathOfRemoteRootNode; // uniquely identifies the filesystem, we check this is unchanged. fsfp_t mFilesystemFingerprint; // uniquely identifies the local folder. This ID is also a component of the sync database's filename // so if it changes, we would lose sync state. So, we cannot allow that handle mLocalPathFsid = UNDEF; // type of the sync, defaults to bidirectional Type mSyncType; // failure cause (disable/failure cause). SyncError mError; // Warning if creation was successful but the user should know something SyncWarning mWarning; // Unique identifier. any other field can change (even remote handle), // and we want to keep disabled configurations saved: e.g: remote handle changed // id for heartbeating handle mBackupId; // Path to the volume containing this backup (only for external backups). // This one is not serialized LocalPath mExternalDrivePath; // If the database exists then its running/suspended. Not serialized. bool mDatabaseExists = false; // Maintained as we transition SyncRunState mRunState = SyncRunState::Pending; // not serialized. Prevent re-enabling sync after removal bool mSyncDeregisterSent = false; // not serialized. Prevent notifying the client app for this sync's state changes and prevent reentry removing sync by sds bool mRemovingSyncBySds = false; // not serialized. Prevent notifying the client app for this sync's state changes bool mFinishedInitialScanning = false; // Name of this sync's state cache. string getSyncDbStateCacheName(handle fsid, NodeHandle nh, handle userId) const; /** * @brief Checks if there is a file in the system with the database with local nodes * information and returns it if found. * * @param fsAccess The file system access needed to invoke the client's database * getExistingDbPath method * @param client The instance of MegaClient that has the information about the database * location. * @return std::nullopt if there is no database file, the path to the file otherwise. */ std::optional getSyncDbPath(const FileSystemAccess& fsAccess, const MegaClient& client) const; /** * @brief If the current config has a database file, this method renames it so it matches * what the target config expects. * * @param targetConfig The configuration that wants to take ownership of the local nodes * database * @param fsAccess The file system access needed to invoke the client's database * getExistingDbPath method * @param client The instance of MegaClient that has the information about the database * location. */ void renameDBToMatchTarget(const SyncConfig& targetConfig, FileSystemAccess& fsAccess, const MegaClient& client) const; // How should the engine detect filesystem changes? ChangeDetectionMethod mChangeDetectionMethod = CDM_NOTIFICATIONS; // Only meaningful when a sync is in CDM_PERIODIC_SCANNING mode. unsigned mScanIntervalSec = 0; // enum to string conversion static const char* synctypename(const Type type); static bool synctypefromname(const string& name, Type& type); SyncError knownError() const; // True if this sync is operating in scan-only mode. bool isScanOnly() const; private: // If mError or mEnabled have changed from these values, we need to notify the app. SyncError mKnownError = NO_SYNC_ERROR; bool mKnownEnabled = false; SyncRunState mKnownRunState = SyncRunState::Pending; }; std::pair buildSyncConfig(const SyncConfig::Type syncType, const std::string& localPath, const std::string& name, const std::string& drivePath, const handle nodeHandle, MegaClient& client); // Convenience. using SyncConfigVector = vector; struct Syncs; struct PerSyncStats { // Data that we report per running sync for display alongside the sync bool scanning = false; bool syncing = false; int32_t numFiles = 0; int32_t numFolders = 0; int32_t numUploads = 0; int32_t numDownloads = 0; bool operator==(const PerSyncStats&); bool operator!=(const PerSyncStats&); }; struct UnifiedSync { // Reference to containing Syncs object Syncs& syncs; // We always have a config SyncConfig mConfig; // If the config is good, the sync can be running unique_ptr mSync; // High level info about this sync, sent to backup centre std::unique_ptr mBackupInfo; // The next detail heartbeat to send to the backup centre std::shared_ptr mNextHeartbeat; // ctor/dtor UnifiedSync(Syncs&, const SyncConfig&); // Update state and signal to application void changeState(SyncError newSyncError, bool newEnableFlag, bool notifyApp, bool keepSyncDb); /** * @brief A wrapper around changeState that also makes sure mSync gets reset */ void suspendSync(); /** * @brief A wrapper around enableSyncByBackupId if the sync is not running * * @param completion callback to forward to enableSyncByBackupId */ void resumeSync(std::function&& completion); shared_ptr sdsUpdateInProgress; PerSyncStats lastReportedDisplayStats; /** * @brief Check if the associated sync should work with a database for the statecachetable * * @return true if it should, false otherwise */ bool shouldHaveDatabase() const; /** * @brief Changes all the necessary attributes in the sync config required to make effective the * change in the local path the sync is using as root. * * These changes include: * - Modify the mConfig member (specifically, its mLocalPath, mLocalPathFsid, * mFilesystemFingerprint members) * - Commit config changes into the database * - Rename the local nodes database to the new expected path * * @note This can only be called on suspended/disabled syncs. * * If an error different from NO_SYNC_ERROR is returned, it is guaranteed that no fields in the * config were changed and the database with local nodes was not renamed. * * @param newPath The new path to be set. * @return An error code indicating if the operation succeeded or the reason for the error: * - UNKNOWN_ERROR: If the sync is running when this method gets called. * - FILESYSTEM_ID_UNAVAILABLE: We can't get the file system id. * - LOCAL_FILESYSTEM_MISMATCH: When changing the root path we don't allow to change the file * system the new path is in. * - UNABLE_TO_RETRIEVE_ROOT_FSID: The new path cannot be opened. * - SYNC_CONFIG_WRITE_FAILURE: It failed to write the configuration into the database. * - NO_SYNC_ERROR: The operation succeeded. */ SyncError changeConfigLocalRoot(const LocalPath& newPath); private: friend class Sync; friend struct Syncs; void changedConfigState(bool save, bool notifyApp); }; enum SyncRowType : unsigned { SRT_XXX, SRT_XXF, SRT_XSX, SRT_XSF, SRT_CXX, SRT_CXF, SRT_CSX, SRT_CSF }; // C(cloud) S(sync) F(file) as elements of the triplet. X means that element of the triplet is missing struct SyncRow { SyncRow(CloudNode* node, LocalNode* syncNode, FSNode* fsNode) : cloudNode(node) , syncNode(syncNode) , fsNode(fsNode) { }; // needs to be move constructable/assignable for sorting (note std::list of non-copyable below) SyncRow(SyncRow&&) = default; SyncRow& operator=(SyncRow&&) = default; CloudNode* cloudNode; LocalNode* syncNode; FSNode* fsNode; NodeHandle cloudHandleOpt() { return cloudNode ? cloudNode->handle : NodeHandle(); } vector cloudClashingNames; vector fsClashingNames; // True if we've recorded any clashing names. bool hasClashes() const; // True if this row exists in the cloud. bool hasCloudPresence() const; // True if this row exists on disk. bool hasLocalPresence() const; bool suppressRecursion = false; bool itemProcessed = false; bool recurseBelowRemovedCloudNode = false; bool recurseBelowRemovedFsNode = false; vector* rowSiblings = nullptr; const LocalPath& comparisonLocalname() const; // This list stores "synthesized" FSNodes: That is, nodes that we // haven't scanned yet but we know must exist. // // An example would be when we download a directory from the cloud. // Here, we create directory locally and push an FSNode representing it // to this list so that we recurse into it immediately. list fsAddedSiblings; void inferOrCalculateChildSyncRows(bool wasSynced, vector& childRows, vector& fsInferredChildren, vector& fsChildren, vector& cloudChildren, bool belowRemovedFsNode, fsid_localnode_map& localnodeByScannedFsid); bool empty() { return !cloudNode && !syncNode && !fsNode && cloudClashingNames.empty() && fsClashingNames.empty(); } // What type of sync row is this? SyncRowType type() const; // Convenience specializations. ExclusionState exclusionState(const CloudNode& node) const; ExclusionState exclusionState(const FSNode& node) const; ExclusionState exclusionState(const LocalPath& name, nodetype_t type, m_off_t size) const; bool hasCaseInsensitiveLocalNameChange() const; bool hasCaseInsensitiveCloudNameChange() const; // Does this row represent an ignore file? bool isIgnoreFile() const; bool isLocalOnlyIgnoreFile() const; // Does this row represent a "no name" triplet? bool isNoName() const; void reassignFingerprints(); }; struct SyncPath { // Tracks both local and remote absolute paths (whether they really exist or not) as we recurse the sync nodes LocalPath localPath; string cloudPath; // this one purely from the sync root (using cloud name, to avoid escaped names) string syncPath; bool appendRowNames(const SyncRow& row, FileSystemType filesystemType); SyncPath(Syncs& s, const LocalPath& fs, const string& cloud) : localPath(fs), cloudPath(cloud), syncs(s) {} private: Syncs& syncs; }; struct SyncStatusInfo { handle mBackupID = UNDEF; string mName; size_t mTotalSyncedBytes = 0; size_t mTotalSyncedNodes = 0; SyncTransferCounts mTransferCounts; }; // SyncStatusInfo class SyncThreadsafeState { // This class contains things that are read/written from either the Syncs thread, // or the MegaClient thread. A mutex is used to keep the data consistent. // Referred to by shared_ptr so transfers etc don't have awkward lifetime issues. mutable mutex mMutex; // If we make a LocalNode for an upload we created ourselves, // it's because the local file is no longer at the matching position // and we need to set it up to be moved correspondingly map> mExpectedUploads; // Transfers update these from the client thread void adjustTransferCounts(bool upload, int32_t adjustQueued, int32_t adjustCompleted, m_off_t adjustQueuedBytes, m_off_t adjustCompletedBytes); // track uploads/downloads SyncTransferCounts mTransferCounts; int32_t mFolderCount = 0; int32_t mFileCount = 0; // know where the sync's tmp folder is LocalPath mSyncTmpFolder; MegaClient* mClient = nullptr; handle mBackupId = 0; public: const bool mCanChangeVault; // Remember which Nodes we created from upload, // until the corresponding LocalNodes are updated. void addExpectedUpload(NodeHandle parentHandle, const string& name, weak_ptr); virtual void removeExpectedUpload(NodeHandle parentHandle, const string& name); shared_ptr isNodeAnExpectedUpload(NodeHandle parentHandle, const string& name); virtual void transferBegin(direction_t direction, m_off_t numBytes); virtual void transferComplete(direction_t direction, m_off_t numBytes); virtual void transferFailed(direction_t direction, m_off_t numBytes); // Return a snapshot of this sync's current transfer counts. SyncTransferCounts transferCounts() const; void incrementSyncNodeCount(nodetype_t type, int32_t count); void getSyncNodeCounts(int32_t& files, int32_t& folders); std::atomic neverScannedFolderCount{}; LocalPath syncTmpFolder() const; void setSyncTmpFolder(const LocalPath&); SyncThreadsafeState(handle backupId, MegaClient* client, bool canChangeVault): mClient(client), mBackupId(backupId), mCanChangeVault(canChangeVault) {} virtual ~SyncThreadsafeState(){}; handle backupId() const { return mBackupId; } MegaClient* client() const { return mClient; } }; class MEGA_API Sync { public: // returns the sync config SyncConfig& getConfig(); const SyncConfig& getConfig() const; Syncs& syncs; // for logging string syncname; // sync-wide directory notification provider std::unique_ptr dirnotify; // track how recent the last received fs noticiation was dstime lastFSNotificationTime = 0; // root of local filesystem tree, holding the sync's root folder. Never null except briefly in the destructor (to ensure efficient db usage) unique_ptr localroot; // Cloud node to sync to CloudNode cloudRoot; string cloudRootPath; handle cloudRootOwningUser; FileSystemType mFilesystemType = FS_UNKNOWN; // We test the sync root folder, and assume the rest of the filesystem tree has the same case sensitivity bool mCaseInsensitive = false; // syncing to an inbound share? bool inshare = false; // insertion/update queue localnode_set insertq; // adds an entry to the delete queue - removes it from insertq void statecachedel(LocalNode*); // adds an entry to the insert queue - removes it from deleteq void statecacheadd(LocalNode*); // recursively add children void addstatecachechildren(uint32_t, idlocalnode_map*, LocalPath&, LocalNode*, int); // Caches all synchronized LocalNode void cachenodes(); // change state, signal to application void changestate(SyncError newSyncError, bool newEnableFlag, bool notifyApp, bool keepSyncDb); // process all outstanding filesystem notifications (mark sections of the sync tree to visit) dstime procscanq(); /** * @brief This function returns a value to be checked in order to prevent moving/uploading files * that may still being updated * * @note In case you call this method for the first time for a given path it will be * unitialized, so it will return nullopt. In that case this function needs to be called again * in the future. * * @param fsNode a reference to `FsNode` that represents the file you want to check * @param fullPath The absolute local path of the file that need to be checked * @return nullopt in case cache for this localPath is unitialized, `true` in case localPath is * still changing or changed too recently. `False` otherwise */ std::optional checkIfFileIsChanging(const FSNode& fsNode, const LocalPath& fullPath); // look up LocalNode relative to localroot LocalNode* localnodebypath(LocalNode*, const LocalPath&, LocalNode** parent, LocalPath* outpath, bool fromOutsideThreadAlreadyLocked); void combineTripletSet(vector::iterator a, vector::iterator b) const; vector computeSyncTriplets( vector& cloudNodes, const LocalNode& root, vector& fsNodes) const; bool inferRegeneratableTriplets( vector& cloudNodes, const LocalNode& root, vector& fsNodes, vector& inferredRows) const; struct PerFolderLogSummaryCounts { // in order to not swamp the logs, but still be able to diagnose. // mention one specfic item, then the count of the rest int alreadySyncedCount = 0; int alreadyUploadingCount = 0; int alreadyDownloadingCount = 0; bool report(string&); }; bool recursiveSync(SyncRow& row, SyncPath& fullPath, bool belowRemovedCloudNode, bool belowRemovedFsNode, unsigned depth); bool syncItem_checkMoves(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool belowRemovedCloudNode, bool belowRemovedFsNode); bool syncItem_checkFilenameClashes(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool syncItem_checkBackupCloudNameClash(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool syncItem_checkDownloadCompletion(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool syncItem(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc); static std::string logTriplet(const SyncRow& row, const SyncPath& fullPath); // resolve_* functions are to do with managing the various cases syncing a single item // they all return true/false depending on whether the node is now in sync // and therefore does not need to be visited again (until another change arrives). bool resolve_checkMoveDownloadComplete(SyncRow& row, SyncPath& fullPath); bool resolve_checkMoveComplete(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool resolve_rowMatched(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc); bool resolve_userIntervention(SyncRow& row, SyncPath& fullPath); bool resolve_makeSyncNode_fromFS(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced); bool resolve_makeSyncNode_fromCloud(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced); bool resolve_delSyncNode(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, unsigned deleteCounter); bool resolve_upsync(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc, const int64_t metamac, const SyncTransfer_inClient::AttributeOnlyUpdate attributeOnlyUpdate); bool resolve_downsync(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool alreadyExists, PerFolderLogSummaryCounts& pflsc, const int64_t metamac, const SyncTransfer_inClient::AttributeOnlyUpdate attributeOnlyUpdate); bool resolve_cloudNodeGone(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool resolve_fsNodeGone(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath); bool syncEqual(const CloudNode&, const LocalNode&); bool syncEqual(const FSNode&, const LocalNode&); bool checkSpecialFile(SyncRow& child, SyncRow& parent, SyncPath& path); bool checkLocalPathForMovesRenames(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult, bool belowRemovedCloudNode); bool checkCloudPathForMovesRenames(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult, bool belowRemovedFsNode); bool checkForCompletedCloudMoveToHere(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult); bool processCompletedUploadFromHere(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult, shared_ptr); bool checkForCompletedFolderCreateHere(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult); bool checkForCompletedCloudMovedToDebris(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult); // Whether the local root node has a scan required. bool isSyncScanning() const; // Check if the current sync is scanning, and set the scanningWasComplete depending on it. // Also sets scanningWasCompletePreviously if scanningWasComplete is true and it is not the initial pass for syncs. bool checkScanningWasComplete(); // Clear scanningWasComplete flag without any further checks. void unsetScanningWasComplete(); bool scanningWasComplete() const; // Sets movesWereComplete flag if: // mScanningWasCompletePreviously flag is false // The local root node does not have a scan required // The local root node does not have pending moves. bool checkMovesWereComplete(); bool movesWereComplete() const; void recursiveCollectNameConflicts(SyncRow& row, SyncPath& fullPath, list* ncs, size_t& count, size_t& limit); void recursiveCollectNameConflicts(list* conflicts, size_t* count = nullptr, size_t* limit = nullptr); void purgeStaleDownloads(); bool makeSyncNode_fromFS(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced); // debris path component relative to the base path string debris; LocalPath localdebris; LocalPath localdebrisname; // state cache table unique_ptr statecachetable; // move file or folder to localdebris bool movetolocaldebris(const LocalPath& localpath); bool movetolocaldebrisSubfolder(const LocalPath& localpath, const LocalPath& targetFolder, bool logFailReason, bool& failedDueToTargetExists); private: string mLastDailyDateTimeDebrisName; unsigned mLastDailyDateTimeDebrisCounter = 0; bool mScanningWasComplete{}; bool mScanningWasCompletePreviously{}; bool mMovesWereComplete{}; public: // does the filesystem have stable IDs? (FAT does not) bool fsstableids = false; // true if the local synced folder is a network folder bool isnetwork = false; // flag to optimize destruction by skipping calls to treestate() bool mDestructorRunning = false; // How deep is this sync's cloud root? unsigned mCurrentRootDepth = 0; /** * @brief Sync constructor * * @param[in,out] us the `UnifiedSync` object in which the new Sync will be stored. It is used * to read the configuration and instantiate the Sync accordingly. Also, non-const references to * some of its members (e.g. `syncs`) are also saved in the object. Additionally, some members * of the config (owned by the `UnifiedSync`) are also modified inside this constructor. * @param[in] logname A prefix to include in the logging messages involving this object. * @param[out] e An output error code. If the object is constructed successfully, NO_SYNC_ERROR */ Sync(UnifiedSync& us, const std::string& logname, SyncError& e); ~Sync(); /** * @brief Checks if this sync should have a database, in that case tries to open it if it * already exists and tries to create it if it doesn't * * Note: this method will reset the statecachetable member with a pointer to the DBTable if * everything went well. * * @param errorHandler a function to be called if something went wrong while opening the db * @return true if we ended up with an opened database, false otherwise */ bool openOrCreateDb(DBErrorCallback&& errorHandler); // Asynchronous scan request / result. std::shared_ptr mActiveScanRequestGeneral; // we can additionally be scanning one more yet-unscanned folder // in order to always be progressing even when downloads are // triggering rescans of their target folder std::shared_ptr mActiveScanRequestUnscanned; static const int SCANNING_DELAY_DS; static const int EXTRA_SCANNING_DELAY_DS; static const int FILE_UPDATE_DELAY_DS; static const int FILE_UPDATE_MAX_DELAY_SECS; static const dstime RECENT_VERSION_INTERVAL_SECS; // We don't officially support the synchronization of trees greater than this depth. // Note that the depth is from the cloud root, not from the sync root. static const unsigned MAX_CLOUD_DEPTH; // Whether this is a backup sync. bool isBackup() const; // True if this sync should have a state cache database. bool shouldHaveDatabase() const; /** * @brief Check if this sync has any pending transfer attached to any of its local nodes * * @return true if there are pending transfers, false otherwise */ bool hasPendingTransfers() const { return localroot != nullptr && localroot->hasPendingTransfers(); } bool hasPendingTransfersThreadSafeState() const; // What filesystem is this sync running on? const fsfp_t& fsfp() const; UnifiedSync& mUnifiedSync; // timer for whole-sync rescan in case of notifications failing or not being available BackoffTimer syncscanbt; shared_ptr threadSafeState; protected : void readstatecache(); private: const LocalPath& mLocalPath; // permanent lock on the debris/tmp folder void createDebrisTmpLockOnce(); // actually test the filesystem to be sure bool determineCaseInsenstivity(bool secondTry); // permanent lock on the debris/tmp folder unique_ptr tmpfa; LocalPath tmpfaPath; // Helper struct to handle new stall issues. struct ProgressingMonitor; /** * @brief Method to handle what to do when a download was terminated without completion. * * @param row The SyncRow object holding the nodes involved in the download. * @param fullPath Full path to the root of the sync. * @param downloadFile The SyncDownload_inClient object that has being terminated by the * corresponding Transfer. * @param monitor The ProgressingMonitor object that is used to notify stalls if needed. * @return true if it must continue with the syncItem logic false otherwise. NOTE: If false is * returned, the syncNode will be marked to sync again in the next iteration. */ bool handleTerminatedDownloads(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor); /** * @brief Method to handle what to do when a download was terminated due to a MAC verification * failure. * * A new Stall issue (of type SyncWaitReason::DownloadIssue) will be created and we will only * continue with the rest of the syncItem logic if the cloudNode doesn't exists or if it exists * but its handle is different from the one stored in downloadFile (i.e. if the remote node has * changed). * A new Stall issue will be created: * - Type: SyncWaitReason::DownloadIssue * - StallCloudPath: error of type PathProblem::MACVerificationFailure * - StallLocalPath1: pointing to the downloadFile.getLocalName() and error * PathProblem::MACVerificationFailure * - StallLocalPath2: pointing to the path of the sync (fullPath.localPath) * * @param row The SyncRow object holding the nodes involved in the download. * @param fullPath Full path to the root of the sync. * @param downloadFile The SyncDownload_inClient object that has being terminated by the * corresponding Transfer. * @param monitor The ProgressingMonitor object that is used to notify stalls if needed. * @return false if we want to keep stalling this issue. That will mean that the current * syncItem stops and the node gets synced again in the next iteration. True otherwise. */ bool handleTerminatedDownloadsDueMAC(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const; /** * @brief Method to handle what to do when a download was terminated because the remote file has * been taken down. * * A new Stall issue will be created: * - Type: SyncWaitReason::DownloadIssue * - StallCloudPath: error of type PathProblem::CloudNodeIsBlocked * - StallLocalPath: empty * * @param row The SyncRow object holding the nodes involved in the download. * @param fullPath Full path to the root of the sync. * @param downloadFile The SyncDownload_inClient object that has being terminated by the * corresponding Transfer. * @param monitor The ProgressingMonitor object that is used to notify stalls if needed. * @return Always true so we keep the transfer intact and let syncItem continue, the transfer * could be reset if the remote node is replaced */ bool handleTerminatedDownloadsDueBlocked(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const; /** * @brief Method to handle what to do when a download was terminated due to a write permissions * issue, e.g., we couldn't write the download to the temporary location. * * In this case, the temporary file access (tmpfa) gets reset so a new valid location is * recreated when restarting the transfer. This reset shouldn't be problematic even if there are * other sync-downloads in flight, because they use threadSafeState::mSyncTmpFolder (and this is * the same thread). * * @param row The SyncRow object holding the nodes involved in the download. * @param fullPath Full path to the root of the sync. * @param downloadFile The SyncDownload_inClient object that has being terminated by the * corresponding Transfer. * @return Always true so we re-evaluate what to do. */ bool handleTerminatedDownloadsDueWritePerms(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile); /** * @brief Method to handle what to do when a download was terminated without completion due to * an error that we was never found during development so we don't know how to handle it. * * @note: If this method gets called we know that it wasn't possible to download the file to the * temporary location but we don't know why. So, if this gets executed in a debug session, make * sure we understand how we get here and add a new case in the handleTerminatedDownloads method * to handle it properly. * * A new Stall issue will be created: * - Type: SyncWaitReason::DownloadIssue * - StallCloudPath: error of type PathProblem::UnknownDownloadIssue * - StallLocalPath: pointing to the temporary path where the download tried to write * * @param row The SyncRow object holding the nodes involved in the download. * @param fullPath Full path to the root of the sync. * @param downloadFile The SyncDownload_inClient object that has being terminated by the * corresponding Transfer. * @param monitor The ProgressingMonitor object that is used to notify stalls if needed. * @return false until file with a newer modification time is detected in the target local * location or the node in the cloud gets updated. Why false? Because terminated transfers with * unhandled error codes are reset inside transferResetUnlessMatched which then forces the * transfer to be created again and the download gets automatically restarted. We want to avoid * that in this unexpected scenarios. */ bool handleTerminatedDownloadsDueUnknown(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const; }; class SyncConfigIOContext; class SyncConfigStore { public: // How we compare drive paths. struct DrivePathComparator { bool operator()(const LocalPath& lhs, const LocalPath& rhs) const { return platformCompareUtf(lhs, false, rhs, false) < 0; } }; // DrivePathComparator using DriveSet = set; SyncConfigStore(const LocalPath& dbPath, SyncConfigIOContext& ioContext); ~SyncConfigStore(); // Remember whether we need to update the file containing configs on this drive. void markDriveDirty(const LocalPath& drivePath); // Retrieve a drive's unique backup ID. handle driveID(const LocalPath& drivePath) const; // Whether any config data has changed and needs to be written to disk bool dirty() const; // Reads a database from disk. error read(const LocalPath& drivePath, SyncConfigVector& configs, bool isExternal); // Write the configs with this drivepath to disk. error write(const LocalPath& drivePath, const SyncConfigVector& configs); // Check whether we read configs from a particular drive bool driveKnown(const LocalPath& drivePath) const; // What drives do we know about? vector knownDrives() const; // Remove a known drive. bool removeDrive(const LocalPath& drivePath); // update configs on disk for any drive marked as dirty auto writeDirtyDrives(const SyncConfigVector& configs) -> DriveSet; private: // Metadata regarding a given drive. struct DriveInfo { // Path to the drive itself. LocalPath drivePath; // The drive's unique backup ID. // Meaningful only for external backups. handle driveID = UNDEF; // Tracks which 'slot' we're writing to. unsigned int slot = 0; bool dirty = false; }; // DriveInfo using DriveInfoMap = map; // Checks whether two paths are equal. bool equal(const LocalPath& lhs, const LocalPath& rhs) const; // Computes a suitable DB path for a given drive. LocalPath dbPath(const LocalPath& drivePath) const; // Reads a database from the specified slot on disk. error read(DriveInfo& driveInfo, SyncConfigVector& configs, unsigned int slot, bool isExternal); // Where we store databases for internal syncs. const LocalPath mInternalSyncStorePath; // What drives are known to the store. DriveInfoMap mKnownDrives; // IO context used to read and write from disk. SyncConfigIOContext& mIOContext; }; // SyncConfigStore class MEGA_API SyncConfigIOContext { public: SyncConfigIOContext(FileSystemAccess& fsAccess, const string& authKey, const string& cipherKey, const string& name, PrnGen& rng); virtual ~SyncConfigIOContext(); MEGA_DISABLE_COPY_MOVE(SyncConfigIOContext) // Deserialize configs from JSON (with logging.) bool deserialize(const LocalPath& dbPath, SyncConfigVector& configs, JSON& reader, unsigned int slot, bool isExternal) const; bool deserialize(SyncConfigVector& configs, JSON& reader, bool isExternal) const; // Retrieve a drive's unique backup ID. virtual handle driveID(const LocalPath& drivePath) const; // Return a reference to this context's filesystem access. FileSystemAccess& fsAccess() const; // Determine which slots are present. virtual error getSlotsInOrder(const LocalPath& dbPath, vector& confSlots); // Read data from the specified slot. virtual error read(const LocalPath& dbPath, string& data, unsigned int slot); // Remove an existing slot from disk. virtual error remove(const LocalPath& dbPath, unsigned int slot); // Remove all existing slots from disk. virtual error remove(const LocalPath& dbPath); // Serialize configs to JSON. void serialize(const SyncConfigVector &configs, JSONWriter& writer) const; // Write data to the specified slot. virtual error write(const LocalPath& dbPath, const string& data, unsigned int slot); // Prefix applied to configuration database names. static const string NAME_PREFIX; private: // Generate complete database path. LocalPath dbFilePath(const LocalPath& dbPath, unsigned int slot) const; // Decrypt data. bool decrypt(const string& in, string& out); // Deserialize a config from JSON. bool deserialize(SyncConfig& config, JSON& reader, bool isExternal) const; // Encrypt data. string encrypt(const string& data); // Serialize a config to JSON. void serialize(const SyncConfig& config, JSONWriter& writer) const; // The cipher protecting the user's configuration databases. SymmCipher mCipher; // How we access the filesystem. FileSystemAccess& mFsAccess; // Name of this user's configuration databases. LocalPath mName; // Pseudo-random number generator. PrnGen& mRNG; // Hash used to authenticate configuration databases. HMACSHA256 mSigner; }; // SyncConfigIOContext /** * A Synchronization operation detected a problem and is * not able to continue (a stall) */ struct SyncStallEntry { // Gives an overall reason for the stall // There may be a more specific pathProblem in one of the paths SyncWaitReason reason = SyncWaitReason::NoReason; // Set this true if there's no way this stall will be resolved // automatically. That way, we can alert the user at the first // opportunity, instead of waiting until we run out of sync // activity that can occur. bool alertUserImmediately = false; // Indicates if we detected the issue from a user change in the cloud // otherwise, it was from a local change. // Showing it in the GUI helps the user understand what happened, especially for moves. bool detectionSideIsMEGA = false; struct StallCloudPath { PathProblem problem = PathProblem::NoProblem; string cloudPath; NodeHandle cloudHandle; StallCloudPath() {} StallCloudPath(NodeHandle h, const string& cp, PathProblem pp = PathProblem::NoProblem) : problem(pp), cloudPath(cp), cloudHandle(h) { } string debugReport() { string r = cloudPath; if (problem != PathProblem::NoProblem) r += " (" + string(syncPathProblemDebugString(problem)) + ")"; return r; } }; struct StallLocalPath { PathProblem problem = PathProblem::NoProblem; LocalPath localPath; StallLocalPath() {} StallLocalPath(const LocalPath& lp, PathProblem pp = PathProblem::NoProblem) : problem(pp), localPath(lp) { } string debugReport() { string r = localPath.toPath(false); if (problem != PathProblem::NoProblem) r += " (" + string(syncPathProblemDebugString(problem)) + ")"; return r; } }; // These are the paths involved with the stall case. // If a path is empty, it's irrelevant to the case // The problem might be local or remote, check the correpsonding PathProblem. // Typically we tried to do something on the problem side // The paths on the other side are what motivated the attempt. // Eg. Saw in the cloud cloudPath1 moved to cloudPath2 // Tried to move localPath1 to localPath2, got error localPath2Problem. StallCloudPath cloudPath1; StallCloudPath cloudPath2; StallLocalPath localPath1; StallLocalPath localPath2; SyncStallEntry(SyncWaitReason r, bool immediate, bool dueTocloudSideChange, StallCloudPath&& cp1, StallCloudPath&& cp2, StallLocalPath&& lp1, StallLocalPath&& lp2): reason(r), alertUserImmediately(immediate), detectionSideIsMEGA(dueTocloudSideChange), cloudPath1(std::move(cp1)), cloudPath2(std::move(cp2)), localPath1(std::move(lp1)), localPath2(std::move(lp2)) {} }; struct SyncStallInfo { using CloudStallInfoMap = map; using LocalStallInfoMap = map; struct StallInfoMaps { CloudStallInfoMap cloud; // Map with cloud-side stalls LocalStallInfoMap local; // Map with local-side stalls static const int MIN_NOPROGRESS_COUNT_FOR_LACK_OF_PROGRESS = 10; // used for hasProgressLack() to report non-immediate stalls static const int MAX_NOPROGRESS_COUNT = 1000000; // Prevent overflow // There is no progress. This is reset during syncLoop when all sync have completed the scanning round. bool noProgress{true}; // Count noProgress. This is used by non-immediate stalls. // When there is progress lack (hasProgressLack()), the non-immediate stalls will be reported to the app. // This counter is reset upon destruction of a ProgressMonitor, when no stalls have been added to it. int noProgressCount{}; // Need explicit defaults for the redefinition of operator= StallInfoMaps() = default; StallInfoMaps(StallInfoMaps&&) = default; // Move cloud and local maps, copy source noProgress flag and noProgressCount. void moveFromKeepingProgress(StallInfoMaps& source); // Use moveFromKeepingProgress() StallInfoMaps& operator=(StallInfoMaps&& other) noexcept; // Defaults needed in order to the operator redefinition above to work properly. StallInfoMaps(const StallInfoMaps& other) = default; StallInfoMaps& operator=(const StallInfoMaps& other) = default; // noProgress flag is set and noProgressCount is greater than MIN_NOPROGRESS_COUNT_FOR_LACK_OF_PROGRESS bool hasProgressLack() const; // Cloud and local maps are empty bool empty() const; // Full size - total number of stalls (cloud + local maps) size_t size() const; // Size taking into account only reportable stalls: // all of them (same as size() if hasProgressLack() is true, otherwise only immediate stalls) size_t reportableSize() const; // Update noProgressCount if noProgress is true and the count is smaller than MAX_NOPROGRESS_COUNT. void updateNoProgress(); // Set noProgress flag to true. void setNoProgress(); // Set noProgress flag to false and reset noProgressCount. void resetNoProgress(); // Clear cloud and local stall maps. Keep the noProgress flag and noProgressCount counters. void clearStalls(); }; // Map of syncID, struct of using SyncIDtoStallInfoMaps = std::map; SyncIDtoStallInfoMaps syncStallInfoMaps; // No stalls detected bool empty() const; // Add a cloud-side stall issue bool waitingCloud(handle backupId, const string& mapKeyPath, SyncStallEntry&& e); // Add a local-side stall issue bool waitingLocal(handle backupId, const LocalPath& mapKeyPath, SyncStallEntry&& e); // SyncID/BackupID is a key of syncStallInfoMaps bool isSyncStalled(handle backupId) const; // Requires user action to resolve - immediate stall (noProgress flag and noProgressCount does not have any effect on this stall) bool hasImmediateStallReason() const; // At least one StallInfoMaps entry has progress lack bool hasProgressLackStall() const; // Total stalls entries size_t size() const; // Total stalls entries that are either immediate or are part of a sync with progress lack size_t reportableSize() const; void updateNoProgress(); void setNoProgress(); /* Move all stalls from source, removing obsolete keys in source (no stalls entries) and removing keys not present in source */ void moveFromButKeepCountersAndClearObsoleteKeys(SyncStallInfo& source); private: void moveFromButKeepCounters(SyncStallInfo& other); void clearObsoleteKeys(SyncStallInfo& other); #ifndef NDEBUG public: void debug() const; #endif }; struct SyncProblems { SyncIDtoConflictInfoMap mConflictsMap; SyncStallInfo mStalls; bool mConflictsDetected = false; bool mStallsDetected = false; }; // SyncProblems struct SyncFlags { // we can only perform moves after scanning is complete std::atomic scanningWasComplete = false; // track whether all our reachable nodes have been scanned std::atomic reachableNodesAllScannedThisPass = true; std::atomic reachableNodesAllScannedLastPass = true; // true anytime we have just added a new sync, or unsuspended (unpaused) one std::atomic isInitialPass = true; // we can only delete/upload/download after moves are complete std::atomic movesWereComplete = false; // stall detection (for incompatible local and remote changes, eg file added locally in a folder removed remotely) std::atomic noProgress = true; std::atomic noProgressCount = 0; std::atomic earlyRecurseExitRequested{false}; // to help with slowing down retries in stall state std::atomic recursiveSyncLastCompletedDs = 0; // Access to this member is confined to sync_thread, so no mutex is required. // IMPORTANT: If this changes and other threads need access, it must be // protected with proper synchronization mechanism (e.g. mutex). SyncStallInfo stall; }; // This interface exists to give tests a little more control over how the // sync engine behaves. // // The basic idea is that the sync will will ask an outside entity whether // it should proceed with a certain action. Say, whether it should upload a // particular file or complete a putnodes request (binding.) // // This is necessary as some tests require very specific sequencing in order // to pass reliably. class SyncController { protected: SyncController(); public: virtual ~SyncController(); // Should we defer sending a putnodes for the specified file? virtual bool deferPutnode(const LocalPath& path) const; // Should we defer the completion of a putnodes sent for the specified file? virtual bool deferPutnodeCompletion(const LocalPath& path) const; // Should we defer uploading of the specified file? virtual bool deferUpload(const LocalPath& path) const; }; // SyncController // Convenience. using HasImmediateStallPredicate = std::function; using IsImmediateStallPredicate = std::function; using SyncControllerPtr = std::shared_ptr; using SyncControllerWeakPtr = std::weak_ptr; struct SyncSensitiveData { // Attributes necessary to manipulate the sync config database. JSCData jscData; // Key necessary to manipulate the sync's state cache. std::string stateCacheKey; }; // SyncSensitiveData struct Syncs { void injectSyncSensitiveData(SyncSensitiveData data); // Retrieve a copy of configured sync settings (thread safe) SyncConfigVector getConfigs(bool onlyActive) const; bool configById(handle backupId, SyncConfig&) const; SyncConfigVector configsForDrive(const LocalPath& drive) const; SyncConfigVector selectedSyncConfigs(std::function selector) const; handle getSyncIdContainingActivePath(const LocalPath& lp) const; // Add new sync setups void appendNewSync(const SyncConfig&, bool startSync, std::function completion, bool completionInClient, const string& logname, const string& excludedPath = string()); // only for use in tests; not really thread safe Sync* runningSyncByBackupIdForTests(handle backupId) const; void transferPauseFlagsUpdated(bool downloadsPaused, bool uploadsPaused); private: /** * @brief Searches for a SyncConfig with the given backupID and executes an optional action * callback. * * This method is expected to be called out of the sync thread. * * Acquires the lock on mSyncVec, searches for a SyncConfig with the given backupId, * and if found, invokes the provided callable under lock. * * @tparam SyncConfigCallable Callable type that accepts a SyncConfig type param. * @param backupId The backupID to search for. * @param completion A callable that is invoked with the SyncConfig if a match is found. * If set to nullptr, no callback is executed. * @return true if a matching SyncConfig was found, false otherwise. */ template bool ifFoundSyncConfigByBackupId(const handle backupId, SyncConfigCallable&& action) const; public: /** * @brief Checks whether a sync configuration with the specified backupID exists. * * @return true if a matching SyncConfig was found, false otherwise. * @see findSyncConfigByBackupId() */ bool hasSyncConfigByBackupId(const handle backupId) const; /** * @brief Retrieves a copy of the sync configuration if exists for the specified backupID. * * @param syncConfig Output parameter that receives the found configuration (copied for thread * safety). * @return true if a matching SyncConfig was found, false otherwise. * @see findSyncConfigByBackupId() */ bool syncConfigByBackupId(const handle backupId, SyncConfig& syncConfig) const; void purgeRunningSyncs(); void loadSyncConfigsOnFetchnodesComplete(bool resetSyncConfigStore); void resumeSyncsOnStateCurrent(); void enableSyncByBackupId(handle backupId, bool setOriginalPath, std::function completion, bool completionInClient, const string& logname); void disableSyncByBackupId(handle backupId, SyncError syncError, bool newEnabledFlag, bool keepSyncDb, std::function completion); // disable all active syncs. Cache is kept void disableSyncs(SyncError syncError, bool newEnabledFlag, bool keepSyncDb); // Called via MegaApi::removeSync - cache files are deleted and syncs unregistered. Synchronous (for now) void deregisterThenRemoveSync(handle backupId, std::function completion, std::function clientRemoveSdsEntryFunction); void deregisterThenRemoveSyncById(handle backupId, std::function&& completion); // async, callback on client thread void renameSync(handle backupId, const string& newname, std::function result); void prepareForLogout(bool keepSyncsConfigFile, std::function clientCompletion); void locallogout(bool removecaches, bool keepSyncsConfigFile, bool reopenStoreAfter); // get snapshots of the sync configs // synchronous for now as that's a constraint from the intermediate layer NodeHandle getSyncedNodeForLocalPath(const LocalPath&); // synchronous and requires first locking mLocalNodeChangeMutex treestate_t getSyncStateForLocalPath(handle backupId, const LocalPath&); // a variant for recovery after not being able to lock the mutex in time, all on the sync thread bool getSyncStateForLocalPath(const LocalPath& lp, treestate_t& ts, nodetype_t& nt, SyncConfig& sc); Syncs(MegaClient& mc); ~Syncs(); void getSyncProblems(std::function)> completion, bool completionInClient); // Retrieve status information about sync(s). using SyncStatusInfoCompletion = std::function)>; void getSyncStatusInfo(handle backupID, SyncStatusInfoCompletion completion, bool completionInClient); void getSyncStatusInfoInThread(handle backupID, SyncStatusInfoCompletion completion); /** * @brief * Removes previously opened backup databases from that drive from memory. * * Note that this function will: * - Flush any pending database changes. * - Remove all contained backup configs from memory. * - Remove the database itself from memory. * * @param drivePath * The drive containing the database to remove. */ void backupCloseDrive(const LocalPath& drivePath, std::function clientCallback); /** * @brief * Restores backups from an external drive. */ void backupOpenDrive(const LocalPath& drivePath, std::function clientCallback); // Add a config directly to the internal sync config DB. // // Note that configs added in this way bypass the usual sync mechanism. // That is, they are added directly to the JSON DB on disk. error syncConfigStoreAdd(const SyncConfig& config); // Move path to .debris folder associated to the backupId // Note: This method is thread safe. // completion is executed in sync thread, completionInClient is executed in client thread void moveToSyncDebrisByBackupID(const string& path, handle backupId, std::function completion, std::function completionInClient); private: // anything to do with loading/saving/storing configs etc is done on the sync thread // Returns a reference to this user's internal configuration database. SyncConfigStore* syncConfigStore(); // Whether the internal database has changes that need to be written to disk. bool syncConfigStoreDirty(); // Attempts to flush the internal configuration database to disk. bool syncConfigStoreFlush(); // Load internal sync configs from disk. error syncConfigStoreLoad(SyncConfigVector& configs); /** * @brief Ensures the specified external drive is opened and marks it as dirty in the sync * configuration store. * * This function checks whether the external drive at the given local path is known to the * synchronization configuration store. If the drive is not already known (i.e., the application * hasn't opened it yet), the function opens the drive to load any existing sync configurations. * After ensuring the drive is opened, it marks the drive as dirty in the sync configuration * store. * * @param externalDrivePath The local path to the external drive that needs to be opened and * marked as dirty. */ void ensureDriveOpenedAndMarkDirty(const LocalPath& externalDrivePath); /** * @brief Marks the externalDrivePath stored in the config as dirty and calls the * syncConfigStoreFlush method to ensure the configuration is written into the database. * * @param config The configuration to store * @return true if the operation succeed (the config was stored in the db), false otherwise */ bool commitConfigToDb(const SyncConfig& config); public: string exportSyncConfigs(const SyncConfigVector configs) const; string exportSyncConfigs() const; void importSyncConfigs(const char* data, std::function completion); // maps local fsid to corresponding LocalNode* (s) fsid_localnode_map localnodeBySyncedFsid; fsid_localnode_map localnodeByScannedFsid; /** * @brief Finds a LocalNode by its synced FSID. * * Searches for a LocalNode in the map of synced FSIDs. * This is used to detect moves, while avoiding mismatches caused by FSID reuse. * * @param fsid The FSID of the node to be searched on the localnode map. * @param targetNodeAttributes The necessary node attributes of the node looking for another * node matching by FSID. * @param originalPathForLogging The original path being processed for context in logs. * @param extraCheck An optional callable for additional filtering of LocalNodes. * @param onFingerprintMismatchDuringPutnodes Optional operation for a LocalNode that * has been excluded due to fingerprint mismatch, but the source node has a putnodes operation * ongoing for an upload which matches fingerprint with the target node. * The param is not const intentionally, in case it needs to be considered as a potential * source node, taking into account that there is a fingerprint match for the ongoing upload. * * @return A pair with: * bool - indicating whether an unknown exclusion was encountered. This may occur during * eg. the first pass of the tree after loading from Suspended state and the corresponding node * is later in the tree. The caller should decide whether to pospone the logic if an unknown * exclusion was found for some node. * LocalNode* - pointer to the matching LocalNode, or nullptr if no match is found. * * @see findLocalNodeByFsid() */ std::pair findLocalNodeBySyncedFsid( const handle fsid, const NodeMatchByFSIDAttributes& targetNodeAttributes, const LocalPath& originalPathForLogging, std::function extraCheck = nullptr, std::function onFingerprintMismatchDuringPutnodes = nullptr) const; /** * @brief Finds a LocalNode by its scanned FSID. * * Searches for a LocalNode in the map of scanned FSIDs. This is used to detect * moves and avoid mismatches caused by FSID reuse. * * @param fsid The FSID of the node to be searched on the localnode map. * @param targetNodeAttributes The necessary node attributes of the node looking for another * node matching by FSID. * @param originalPathForLogging The original path being processed for context in logs. * @param extraCheck An optional callable for additional filtering of LocalNodes. * * @return A pair with: * bool - indicating whether an unknown exclusion was encountered. This may occur during * eg. the first pass of the tree after loading from Suspended state and the corresponding node * is later in the tree. The caller should decide whether to pospone the logic if an unknown * exclusion was found for some node. * LocalNode* - pointer to the matching LocalNode, or nullptr if no match is found. * * @see findLocalNodeByFsid() */ std::pair findLocalNodeByScannedFsid( const handle fsid, const NodeMatchByFSIDAttributes& targetNodeAttributes, const LocalPath& originalPathForLogging, std::function extraCheck = nullptr) const; void setSyncedFsidReused(const fsfp_t& fsfp, const handle fsid); void setScannedFsidReused(const fsfp_t& fsfp, const handle fsid); // maps nodehandle to corresponding LocalNode* (s) nodehandle_localnode_map localnodeByNodeHandle; bool findLocalNodeByNodeHandle(NodeHandle h, LocalNode*& sourceSyncNodeOriginal, LocalNode*& sourceSyncNodeCurrent, bool& unsureDueToIncompleteScanning, bool& unsureDueToUnknownExclusionMoveSource); // manage syncdown flags inside the syncs // backupId of UNDEF to rescan all void setSyncsNeedFullSync(bool andFullScan, bool andReFingerprint, handle backupId); /** * @brief Detects name conflicts in the synchronizations and stores them in a Map. * * Additionally, this function updates the global conflict counter, and * disables sync conflicts update flag * * @note This function must be executed on the sync thread. * * @param conflicts Map (BackupId to list of conficts) where detected conflicts are stored. * * @return Returns true if conflicts were detected and stored in the map, otherwise returns * false. */ bool conflictsDetected(SyncIDtoConflictInfoMap& conflicts); size_t conflictsDetectedCount(size_t limit = 0) const; // limit 0 -> no limit // Get name conficts - pass UNDEF to collect for all syncs. void collectSyncNameConflicts(handle backupId, std::function&& nc)>, bool completionInClient); // retrieves information about any detected stalls. bool stallsDetected(SyncStallInfo& stallInfo); // This one resets syncupdate_totalstalls size_t stallsDetectedCount() const; bool syncStallDetected(SyncStallInfo& si) const; list> scanBlockedPaths; list> badlyFormedIgnoreFilePaths; typedef std::function QueuedClientFunc; ThreadSafeDeque clientThreadActions; typedef std::pair, string> QueuedSyncFunc; ThreadSafeDeque syncThreadActions; void syncRun(std::function, const string& actionName); void queueSync(std::function&&, const string& actionName); void queueClient(QueuedClientFunc&&, bool fromAnyThread = false); enum class FromAnyThread { yes, no }; /** * @brief Wraps the given callable inside another callable (same signature) that, once invoked, * instead of running it immediately, it gets enqueued to be executed in the MegaClient thread. * * @tparam Callable A type implementing operator(). The return type of the callable must be void * @param callable The callable to wrap * @param fromAnyThread If the callable can be enqueued from any thread (FromAnyThread::yes) or * if it should be done only from the sync thread. * @return A new callable that will enqueue the input parameter to the MegaClient thread. */ template auto wrapToRunInClientThread(Callable&& callable, const FromAnyThread fromAnyThread = FromAnyThread::no) { return [this, fromAnyThread = fromAnyThread == FromAnyThread::yes, callable = std::forward(callable)](auto&&... args) mutable { auto argsTuple = std::make_tuple(std::forward(args)...); queueClient( [callable = std::move(callable), argsTuple = std::move(argsTuple)](auto&&, auto&&) mutable { std::apply(callable, std::move(argsTuple)); }, fromAnyThread); }; } bool onSyncThread() const { return std::this_thread::get_id() == syncThreadId; } /** * @brief Checks if the new remote root path has changed. In that case, * config.mOriginalPathOfRemoteRootNode gets updated. * * Also ensures that the config.mRemoteNode is undefined if the remote root doesn't exist * anymore. * * @param config A configuration to read the previously stored remote root node * @param exists Whether the remote root node continues existing * @param cloudPath The current path of the remote root node * @return true if the remote mOriginalPathOfRemoteRootNode has changed, false otherwise */ bool checkSyncRemoteLocationChange(SyncConfig& config, const bool exists, const std::string& cloudPath); /** * @brief Change the root node in the cloud the sync with the given backupId is tracking * * @note This method must be called from the MegaClient thread. * * @param backupId Id of the sync to change the remote root node * @param newRootNode The new root's node handle * @param completionForClient The completion function to be called after the operations finishes * or if some error takes place. */ void changeSyncRemoteRoot(const handle backupId, std::shared_ptr&& newRootNode, std::function&& completionForClient); /** * @brief Same as changeSyncRemoteRoot but this must be called from the syncs thread. */ void changeSyncRemoteRootInThread(const handle backupId, std::shared_ptr&& newRootNode, std::function&& completion); /** * @brief Change the local path being used as the root of a sync * * @param backupId Id of the sync to change the remote root node * @param newValidLocalRootPath The path to the new local root. It is supposed to be valid, * i.e., all the requirements stated at MegaClient's level have been validated. * @param completionForClient The completion function to be called after the operations finishes * or if some error takes place. */ void changeSyncLocalRoot(const handle backupId, LocalPath&& newValidLocalRootPath, std::function&& completionForClient); /** * @brief Same as changeSyncLocalRoot but this must be called from the syncs thread. */ void changeSyncLocalRootInThread(const handle backupId, LocalPath&& newValidLocalRootPath, std::function&& completion); // Cause periodic-scan syncs to scan now (waiting for the next periodic scan is impractical for tests) std::future triggerPeriodicScanEarly(handle backupID); // mark nodes as needing to be checked for sync actions void triggerSync(NodeHandle, bool recurse = false); void triggerSync(const LocalPath& lp, bool scan); // set default permission for file system access void setdefaultfilepermissions(int permissions); void setdefaultfolderpermissions(int permissions); // ------ public data members (thread safe) // waiter for sync loop on thread shared_ptr waiter; std::atomic skipWait = false; // These rules are used to generate ignore files for newly added syncs. DefaultFilterChain mNewSyncFilterChain; // todo: move relevant code to this class later // this mutex protects the LocalNode trees while MEGAsync receives requests from the filesystem // browser for icon indicators needs to be locked when making changes on this thread; or when // accessing from another thread // // IMPORTANT: Please refer to the `mSyncVecMutex` definition for lock ordering rules (to avoid // deadlocks). std::timed_mutex mLocalNodeChangeMutex; // flags matching the state we have reported to the app via callbacks std::atomic syncscanstate{false}; std::atomic syncBusyState{false}; std::atomic syncStallState{false}; std::atomic syncConflictState{false}; std::atomic mSyncsLoaded{false}; std::atomic mSyncsResumed{false}; std::atomic totalSyncConflicts{0}; std::atomic totalSyncStalls{0}; std::chrono::steady_clock::time_point lastSyncConflictsCount{std::chrono::steady_clock::now()}; std::chrono::steady_clock::time_point lastSyncStallsCount{std::chrono::steady_clock::now()}; static const std::chrono::milliseconds MIN_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT; static const std::chrono::milliseconds MAX_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT; // Lock-free count of syncs currently active. std::atomic mNumSyncsActive{0u}; // directly accessed flag that makes sync-related logging a lot more detailed std::atomic mDetailedSyncLogging{true}; // total number of LocalNode objects (only updated by syncs thread) std::atomic totalLocalNodes{0}; // backup rework implies certain restrictions that can be skipped // by setting this flag bool mBackupRestrictionsEnabled = true; // Throttle for MAC computation to prevent resource exhaustion // Limits concurrent MAC computations and total data in flight MacComputationThrottle mMacComputationThrottle; std::atomic completedPassCount{0}; MacComputationThrottle& macComputationThrottle() { return mMacComputationThrottle; } const MacComputationThrottle& macComputationThrottle() const { return mMacComputationThrottle; } private: // functions for internal use on-thread only void stopSyncsInErrorState(); void processTriggerHandles(); void processTriggerLocalpaths(); void exportSyncConfig(JSONWriter& writer, const SyncConfig& config) const; bool importSyncConfig(JSON& reader, SyncConfig& config); bool importSyncConfigs(const string& data, SyncConfigVector& configs); // Returns a reference to this user's sync config IO context. SyncConfigIOContext* syncConfigIOContext(); void proclocaltree(LocalNode* n, LocalTreeProc* tp); bool checkSyncsMovesWereComplete(); // Iterate through syncs, calling Sync::checkMovesgWereComplete(). Returns false if any sync returns false. bool isAnySyncSyncing() const; bool isAnySyncScanning_inThread() const; /** * @brief Checks sync scanning completion status. * @return A pair where the first value indicates whether a full scan had completed in the * previous cycle (outside the initial pass), and the second whether all current syncs are fully * scanned. */ std::pair checkSyncsScanningWasComplete_inThread(); void unsetSyncsScanningWasComplete_inThread(); // Unset scanningWasComplete flag for every sync. /** * @brief Instantiates the Sync object and stores it in the given unified sync * * @param[in,out] us The UnifiedSync with the configuration needed to create the sync (mConfig). * It will be modified to store the new sync and to modify its state depending on the results. * @param completion A function to be called once the process finishes with or without errors. * The parameter passed to the callable are: * - `error`: An error code indicating the result of the initiation. * - `SyncError`: A detailed sync error code providing more context. * - `handle`: the backup id of the sync being initiated * @param logname The name to be passed to the Sync constructor. That will be stored in the * `Sync::syncname` member and used as prefix in the logged messages. */ void startSync_inThread(UnifiedSync& us, std::function completion, const string& logname); void prepareForLogout_inThread(bool keepSyncsConfigFile, std::function clientCompletion); void locallogout_inThread(bool removecaches, bool keepSyncsConfigFile, bool reopenStoreAfter); void loadSyncConfigsOnFetchnodesComplete_inThread(bool resetSyncConfigStore); void resumeSyncsOnStateCurrent_inThread(); void enableSyncByBackupId_inThread(handle backupId, bool setOriginalPath, std::function completion, const string& logname, const string& excludedPath = string()); void disableSyncByBackupId_inThread(handle backupId, SyncError syncError, bool newEnabledFlag, bool keepSyncDb, std::function completion); void appendNewSync_inThread(const SyncConfig&, bool startSync, std::function completion, const string& logname, const string& excludedPath = string()); void removeSyncAfterDeregistration_inThread(handle backupId, std::function clientCompletion, std::function clientRemoveSdsEntryFunction); void syncConfigStoreAdd_inThread(const SyncConfig& config, std::function completion); void clear_inThread(bool reopenStoreAfter); void purgeRunningSyncs_inThread(); void renameSync_inThread(handle backupId, const string& newname, std::function result); error backupOpenDrive_inThread(const LocalPath& drivePath); error backupCloseDrive_inThread(LocalPath drivePath); void getSyncProblems_inThread(SyncProblems& problems); std::function prepareSdsCleanupForBackup(UnifiedSync& us, const vector>& sds); bool processPauseResumeSyncBySds(UnifiedSync& us, vector>& sdsBackups); bool processRemovingSyncBySds(UnifiedSync& us, bool foundRootNode, vector>& sdsBackups); void deregisterThenRemoveSyncBySds(UnifiedSync& us, std::function clientRemoveSdsEntryFunction); void processSyncConflicts(); void processSyncStalls(); /** * @brief Returns a thread-safe copy of `mSyncVec`. * * This method returns a thread-safe (by locking `mSyncVecMutex`) copy of the current vector of * UnifiedSync shared pointers. * * @note The returned vector is a snapshot of the internal state * at the time of the call. Subsequent modifications to the * original container will not affect the returned copy. * * @note This method should be called only from sync thread to avoid issues */ std::vector> getSyncVecCopy() const; void syncLoop(); enum WhichCloudVersion { EXACT_VERSION, LATEST_VERSION, LATEST_VERSION_ONLY, FOLDER_ONLY }; bool lookupCloudNode(NodeHandle h, CloudNode& cn, string* cloudPath, bool* isInTrash, bool* nodeIsInActiveSync, bool* nodeIsDefinitelyExcluded, unsigned* depth, WhichCloudVersion, handle* owningUser = nullptr, vector>* sdsBackups = nullptr); /** * @brief Check if the given cloud node is an in-share * * @param cn The cloud node to check * @return true if the node is found and it is an inshare */ bool isCloudNodeInShare(const CloudNode& cn); bool lookupCloudChildren(NodeHandle h, vector& cloudChildren); // Query whether a specified child is definitely excluded. // // A node is definitely excluded if: // - The node itself is excluded. // - One of the node's parents are definitely excluded. // // It is the caller's responsibility to ensure that: // - Necessary locks are acquired such that this function has exclusive // access to both the local and remote node trees. // - The node specified by child is below root. bool isDefinitelyExcluded(const pair, Sync*>& root, std::shared_ptr child); template Sync* syncMatching(Predicate&& predicate) { // Sanity. assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& i : mSyncVec) { // Skip inactive syncs. if (!i->mSync) continue; // Have we found our lucky sync? if (predicate(*i)) return i->mSync.get(); } // No syncs match our search criteria. return nullptr; } Sync* syncContainingPath(const LocalPath& path); Sync* syncContainingPath(const string& path); // Signal that an ignore file failed to load. void ignoreFileLoadFailure(const Sync& sync, const LocalPath& path); // Records which ignore file failed to load and under which sync. struct IgnoreFileFailureContext { // Clear the context if the associated sync: // - Is disabled (or failed.) // - No longer exists. void reset(Syncs& syncs) { if (mBackupID == UNDEF) return; auto predicate = [&](const UnifiedSync& us) { return us.mConfig.mBackupId == mBackupID && !!us.mSync; }; if (syncs.syncMatching(std::move(predicate))) return; reset(); } // Clear the context. void reset() { mBackupID = UNDEF; mFilterChain.clear(); mPath.clear(); } // Report the load failure as a stall. void report(SyncStallInfo& stallInfo) { if (mBackupID == UNDEF) return; stallInfo.waitingLocal(mBackupID, mPath, SyncStallEntry( SyncWaitReason::FileIssue, true, false, {}, {}, {mPath, PathProblem::IgnoreFileMalformed}, {})); } // Has the ignore file failure been resolved? bool resolve(FileSystemAccess& fsAccess) { // No failures to resolve so we're all good. if (mBackupID == UNDEF) return true; // Try and load the ignore file. auto result = mFilterChain.load(fsAccess, mPath); // Resolved if the file's been deleted or corrected. if (result == FLR_FAILED) return false; // Clear the failure condition. reset(); // Let the caller know the situation's resolved. return true; } // Has an ignore file failure been signalled? bool signalled() const { return mBackupID != UNDEF; } // Used to load the ignore file specified below. FilterChain mFilterChain; // What ignore file failed to load? LocalPath mPath; // What sync contained the broken ignore file? handle mBackupID = UNDEF; }; // IgnoreFileFailureContext // Check if the sync described by config contains an ignore file. bool hasIgnoreFile(const SyncConfig& config); void confirmOrCreateDefaultMegaignore(unique_ptr& resultIfDfc, unique_ptr& resultIfMegaignoreDefault); /** * @brief Handles how to deal with a sync whose remote root node has been moved or renamed * * @note This method assumes that the location of the remote node has changed. * * @param sync The affected sync. Its state might be changed */ void manageRemoteRootLocationChange(Sync& sync) const; // ------ private data members MegaClient& mClient; // Syncs should have a separate fsaccess for thread safety unique_ptr fsaccess; // pseudo-random number generator PrnGen rng; // Track some state during and between recursiveSync runs unique_ptr mSyncFlags; // This user's internal sync configuration store. unique_ptr mSyncConfigStore; // Responsible for securely writing config databases to disk. unique_ptr mSyncConfigIOContext; // Sometimes the Client needs a list of the sync configs; we provide it as a copy (mutex for // thread safety, of course). // // IMPORTANT: Lock ordering rules (to avoid deadlocks) // // Note: Each group is independent of the others, and for each group it is not required to hold // all these mutexes simultaneously. The following rules define the valid lock ordering whenever // multiple of them need to be acquired. // // Group 1: Interaction between `nodeTreeMutex` and `mSyncVecMutex` // --------------------------------------------------------------------------------------------- // The required lock order is: // 1) `nodeTreeMutex` // 2) `mSyncVecMutex` // // Group 2: Interaction between `mLocalNodeChangeMutex` and `mSyncVecMutex` // --------------------------------------------------------------------------------------------- // The required lock order is: // 1) `mLocalNodeChangeMutex` // 2) `mSyncVecMutex` // // Group 3: Interaction between `mSyncVecMutex`, `stallReportMutex` and `mImmediateStallLock` // --------------------------------------------------------------------------------------------- // The required lock order is: // 1) `mSyncVecMutex` // 2) `stallReportMutex` // 3) `mImmediateStallLock` // // Group 4: Interaction between `mSyncVecMutex`, `triggerMutex` // --------------------------------------------------------------------------------------------- // The required lock order is: // 1) `mSyncVecMutex` // 2) `triggerMutex` mutable std::recursive_mutex mSyncVecMutex; vector> mSyncVec; // unload the Sync (remove from RAM and data structures), its config will be flushed to disk bool unloadSyncByBackupID(handle id, bool newEnabledFlag, SyncConfig&); // used to asynchronously perform scans. unique_ptr mScanService; // Separate key to avoid threading issues SymmCipher syncKey; // data structure with mutex to interchange stall info SyncStallInfo stallReport; // IMPORTANT: Please refer to the `mSyncVecMutex` definition for lock ordering rules (to avoid // deadlocks). mutable mutex stallReportMutex; // When the node tree changes, this structure lets the sync code know which LocalNodes need to be flagged map triggerHandles; map triggerLocalpaths; mutable std::mutex triggerMutex; public: bool triggerHandlesPending() const; bool triggerLocalpathsPending() const; // Keep track of files that we can't move yet because they are changing struct FileChangingState { // values related to possible files being updated m_off_t updatedfilesize = ~0; m_time_t updatedfilets = 0; m_time_t updatedfileinitialts = 0; bool isInitialized() const { return updatedfilesize != ~0 || updatedfilets != 0 || updatedfileinitialts != 0; } }; std::map mFileChangingCheckState; // Keep track of which LocalNodes are involved in moves // Sometimes we can't find the other end by fsid for example, if both Cloud and FSNode moved. std::set mMoveInvolvedLocalNodes; LocalNode* findMoveFromLocalNode(const shared_ptr&); // shutdown safety bool mExecutingLocallogout = false; // local record of client's state for thread safety std::atomic mDownloadsPaused {false}; std::atomic mUploadsPaused {false}; std::atomic mTransferPauseFlagsChanged {false}; // Local sds values for full-syncs using SyncsDesiredStates = std::vector>; SyncsDesiredStates mSdsBackupsFullSync; mutable std::mutex mSdsBackupsFullSyncMutex; /** * @brief Shared pointer to IUploadThrottlingManager, in charge of everything related to upload * throttling. * @see IUploadThrottlingManager. */ std::shared_ptr mThrottlingManager; // Responsible for tracking when to send sync/backup heartbeats unique_ptr mHeartBeatMonitor; // Tracks the last recorded ignore file failure. IgnoreFileFailureContext mIgnoreFileFailureContext; // for clarifying and confirming which functions run on which threads std::thread::id syncThreadId; // declared last; would be auto-destructed first. std::thread syncThread; // structs and classes that are private to the thread, and need access to some internals that should not be generally public friend struct LocalNode; friend class Sync; friend struct SyncPath; friend struct UnifiedSync; friend class BackupInfoSync; friend class BackupMonitor; friend struct ProgressingMonitor; // Helps guide the engine's activities. mutable SyncControllerWeakPtr mSyncController; // Serializes access to mSyncController. mutable std::mutex mSyncControllerLock; // Convenience helper. template bool defer(bool (SyncController::*predicate)(Parameters...) const, Arguments&&... arguments) const; // How does the engine know when an immediate stall has been reported? HasImmediateStallPredicate mHasImmediateStall; // How does the engine know that a specific stall is immediate? IsImmediateStallPredicate mIsImmediateStall; // Serializes access to *ImmediateStall predicates. // IMPORTANT: Please refer to the `mSyncVecMutex` definition for lock ordering rules (to avoid // deadlocks). mutable std::mutex mImmediateStallLock; // Check whether any immediate stalls have been reported. bool hasImmediateStall(const SyncStallInfo& stalls) const; // Check whether a specified stall is "immediate." bool isImmediateStall(const SyncStallEntry& entry) const; // What fingerprints does the engine know about? fsfp_tracker_t mFingerprintTracker; public: // Should we defer sending a putnodes for the specified file? bool deferPutnode(const LocalPath& path) const; // Should we defer the completion of a putnodes sent for the specified file? bool deferPutnodeCompletion(const LocalPath& path) const; // Should we defer uploading of the specified file? bool deferUpload(const LocalPath& path) const; // Check if the engine is being controlled by a sync controller. // // Pretty much the same as the syncController() function below but // better expresses intent. bool hasSyncController() const; // Specify how the engine should determine whether there are any immediate stalls. void setHasImmediateStall(HasImmediateStallPredicate predicate); // Specify how the engine can determine whether a given stall is "immediate." void setIsImmediateStall(IsImmediateStallPredicate predicate); // Specify a controller who should guide the engine's activities. void setSyncController(SyncControllerPtr controller); // Retrieve the engine's current controller. SyncControllerPtr syncController() const; bool isSyncStalled(handle backupId) const; // Check if any active syncs match the specified predicate. template bool anySyncMatching(Predicate&& predicate) { // Already on sync thread so just perform the query. if (onSyncThread()) return syncMatching(predicate) != nullptr; // So we can wait for the engine's result. std::promise notifier; // Ask the sync engine to perform our query. queueSync([&]() { // Check if any syncs match our predicate. notifier.set_value(syncMatching(predicate) != nullptr); }, "anySyncMatching"); // Let the caller know if any syncs match our predicate. return notifier.get_future().get(); } // Syncs Upload Throttling /** * @brief Processes the delayed uploads. * Expected to be called on the sync thread. * * When a delayed upload is processed after the required throttling time, it is enqueued as a * regular upload to be processed in the client. * * @see IUploadThrottlingManager::processDelayedUploads() * @see queueClient() */ void processDelayedUploads(); /** * @see IUploadThrottlingManager::addToDelayedUploads() * Expected to be called on the sync thread. */ void addToDelayedUploads(DelayedSyncUpload&& delayedUpload); /** * @see IUploadThrottlingManager::uploadCounterInactivityExpirationTime() * Expected to be called on the sync thread. */ std::chrono::seconds uploadCounterInactivityExpirationTime() const; /** * @see IUploadThrottlingManager::throttleUpdateRate() * Expected to be called on the sync thread. */ std::chrono::seconds throttleUpdateRate() const; /** * @see IUploadThrottlingManager::maxUploadsBeforeThrottle() * Expected to be called on the sync thread. */ unsigned maxUploadsBeforeThrottle() const; /** * @brief Sets the throttleUpdateRate configurable value for upload throttling. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param completion The completion function to be called after the operations finishes. * Error values: * - API_OK: Value was updated correctly. * - API_EARGS: Value was below or above throttleUpdateRate lower/upper limits. */ void setThrottleUpdateRate(std::chrono::seconds throttleUpdateRate, std::function&& completion); /** * @brief Sets the maxUploadsBeforeThrottle configurable value for upload throttling. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param completion The completion function to be called after the operations finishes. * Error values: * - API_OK: Value was updated correctly. * - API_EARGS: Value was below or above maxUploadsBeforeThrottle lower/upper limits. */ void setMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, std::function&& completion); /** * @brief Retrieves the upload throttling configurable values: throttleUpdateRate and * maxUploadsBeforeThrottle. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param completion The completion function to be called after the operations finishes. * @return a pair with throttleUpdateRate, maxUploadsBeforeThrottle. */ void uploadThrottleValues( std::function&& completion); /** * @brief Retrieves the lower/upper limits for the upload throttling configurable values. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param completion The completion function to be called after the operations finishes. */ void uploadThrottleValuesLimits(std::function&& completion); /** * @brief Checks whether or not there are delayed/throttled uploads waiting for processing. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param completion The completion function to be called after the operations finishes. */ void checkSyncUploadsThrottled(std::function&& completion); /** * @brief Sets the throttling manager object. * * Method to be executed out of the sync thread. The logic is enqueued to be later called within * the sync thread. * * @param uploadThrottlingManager A valid shared_ptr to IUploadThrottlingManager. * @param completion The completion function to be called after the operations finishes. * Error values: * - API_OK: New IUploadThrottlingManager was set correctly. * - API_EARGS: uploadThrottlingManager is not a valid pointer. */ void setThrottlingManager(std::shared_ptr uploadThrottlingManager, std::function&& completion); /** * @brief Sets the sds value for the syncs * * Method to be executed out of the sync thread. The value will be copied to be used later in * the sync thread, protected by the mSdsBackupsFullSyncMutex mutex. * * @param sds New *!sds user attribute value. It can be null. */ void setSdsBackupsFullSync(const std::unique_ptr& sds); private: /** * @brief Checks the throttling manager validity. * * The throttling manager is always expected to be valid since the constructor. * The method writes a log error and also performs an assert in case of the throttling manager * being nullptr. * * This method is expected to be called on the sync thread. It also asserts that. */ void assertThrottlingManagerIsValid() const; /** * @brief Gets the sds value for the full-sync with the sds node attribute format * * Internal method intended to be used in the sync thread. The returned values are set using * setSdsBackupsFullSync method. Protected by the mSdsBackupsFullSyncMutex * * @see setSdsBackupsFullSync * * @return vector with the latest known *!sds user attribute values. */ SyncsDesiredStates getSdsBackupsFullSync() const; public: bool anySyncHasPendingTransfersThreadSafeState() const; }; class OverlayIconCachedPaths { // This class is to help with reporting the status of synced files to the OS filesystem shell app // We can't always immediately look up the status instantly, due to mutex lock waits // and we don't want to stall the OS shell and make it wait. // So, // - remember the last (say) 512 paths the shell wanted to know about, and keep those up to date as we notify about them so we can reply instantly for those // - remember the last (say) 512 paths that we notified anyway, so that when the OS shell comes back to ask what the status of that notified path is, we can reply instantly // 512 should be enough as it would be tough to have that many files on screen at any one time typedef map Map; Map paths; deque recentOrder; size_t sizeLimit = 512; mutex mMutex; public: void addOrUpdate(const LocalPath& lp, int value) { lock_guard g(mMutex); auto it_bool = paths.insert(Map::value_type(lp, value)); if (it_bool.second) { recentOrder.push_back(it_bool.first); } else { it_bool.first->second = value; } if (recentOrder.size() > sizeLimit) { paths.erase(recentOrder.front()); recentOrder.pop_front(); } } void overwriteExisting(const LocalPath& lp, int value) { lock_guard g(mMutex); auto it = paths.find(lp); if (it != paths.end()) { it->second = value; } } bool lookup(const LocalPath& lp, int& value) { lock_guard g(mMutex); auto it = paths.find(lp); if (it == paths.end()) return false; value = it->second; return true; } void clear() { lock_guard g(mMutex); recentOrder.clear(); paths.clear(); } }; } // namespace #endif #endif sdk-10.11.0/include/mega/syncfilter.h000066400000000000000000000136141516266226600173430ustar00rootroot00000000000000/** * @file mega/syncfilter.h * @brief Classes representing file filters. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_SYNC_FILTER_H #define MEGA_SYNC_FILTER_H 1 #include #include "types.h" namespace mega { // Forward Declarations (from Filesystem) struct FileSystemAccess; class LocalPath; struct FileAccess; // Forward Declaration class SizeFilter; class StringFilter; // Convenience. using SizeFilterPtr = std::shared_ptr; using StringFilterPtr = std::shared_ptr; using StringFilterPtrVector = std::vector; class MEGA_API DefaultFilterChain { public: DefaultFilterChain(); DefaultFilterChain(DefaultFilterChain& other); DefaultFilterChain& operator=(DefaultFilterChain& rhs); // Creates a new ignore file in the target directory. // // Note that this function only writes an ignore file if one does not // already exist in the target directory. // // Returns true if: // - An ignore file already exists in the target directory. // - We successfully created an ignore file in the target directory. // // Returns false otherwise. bool create(const LocalPath& targetPath, bool appendName, FileSystemAccess& fsAccess, bool setSyncIgnoreFileFlag); // Specify that a given path should be excluded. void excludePath(const string& path); // Generates the content for an ignore file. string generate(const LocalPath& targetPath, FileSystemAccess& fsAccess, bool includeBOM, bool setSyncIgnoreFileFlag) const; // Specify the lower size limit. // // Zero is valid and represents "no lower limit." void lowerLimit(std::uint64_t lower); // Resets the chain to its default state. void reset(); // Specify the upper size limit. // // Zero is valid and represents "no upper limit." void upperLimit(std::uint64_t upper); private: // Computes a list of path exclusions applicable to targetPath. // // In order for an exclusion to be applicable to some target, the path // it excludes must be contained by that target. // // Note that the paths returned by this function are relative to target. vector applicablePaths(LocalPath targetPath) const; // Returns a copy of strings where each individual string has been // normalized. // // Strings that can not be normalized are not included in the result. string_vector normalize(const string_vector& strings) const; // Converts a relative local path to remote format. // // Path components are unescaped and are separated by '/'. RemotePath toRemotePath(const LocalPath& path, FileSystemAccess& fsAccess) const; // Converts a list of relative local paths to remote format. vector toRemotePaths(const vector& localPaths, FileSystemAccess& fsAccess) const; // Predefined name exclusions. static const string_vector mPredefinedNameExclusions; // Names that should be excluded. // // These names are stored in "cloud" format. // // Wildcard patterns are valid. vector mExcludedNames; // Absolute paths that should be excluded. // // These names are stored in "local" format. // // These names are translated into "cloud" format as necessary when // writing an ignore file for a specific sync root. vector mExcludedPaths; // How we synchronize access to instances of this class. mutable mutex mLock; // Lower size limit. // // Zero is a sentinel for "no limit." std::uint64_t mLowerLimit; // Upper size limit. // // Zero is a sentinel for "no limit." std::uint64_t mUpperLimit; }; // DefaultFilterChain enum FilterLoadResult { // The ignore file is no longer present. FLR_DELETED, // The ignore file failed to load. FLR_FAILED, // The ignore file was loaded successfully. FLR_SUCCESS }; // FilterLoadResult class MEGA_API FilterChain { public: // Removes all filters in this chain. void clear(); // Loads filters from a file. FilterLoadResult load(FileSystemAccess& fsAccess, const LocalPath& path); FilterLoadResult load(FileAccess& fileAccess); // Attempts to locate a match for the path pair p. ExclusionState match(const RemotePathPair& p, const nodetype_t type, const bool onlyInheritable) const; // Attempts to locate a match for the size s. ExclusionState match(const m_off_t s) const; // Fingerprint of the last loaded ignore file. FileFingerprint mFingerprint; bool mLoadSucceeded = false; bool mSyncThisMegaignore = false; private: // Name and/or path filters. StringFilterPtrVector mStringFilters; // File size filter. SizeFilterPtr mSizeFilter; }; /* FilterChain */ class IgnoreFileName { static const LocalPath mLocalName; static const RemotePath mRemoteName; public: operator const LocalPath&() const; operator const RemotePath&() const; operator const string&() const; bool operator==(const LocalPath& rhs) const; bool operator==(const RemotePath& rhs) const; bool operator==(const string& rhs) const; }; // IgnoreFileName template bool operator==(const T& lhs, const IgnoreFileName& rhs) { return rhs == lhs; } constexpr IgnoreFileName IGNORE_FILE_NAME; } /* mega */ #endif /* ! MEGA_SYNC_FILTER_H */ sdk-10.11.0/include/mega/syncinternals/000077500000000000000000000000001516266226600176775ustar00rootroot00000000000000sdk-10.11.0/include/mega/syncinternals/mac_computation_state.h000066400000000000000000000157611516266226600244440ustar00rootroot00000000000000/** * @file mac_computation_state.h * @brief State structure for asynchronous local file MAC computation. * * This struct handles the expensive part of MAC verification: computing * the local file's MetaMAC. The comparison with remote MAC is done separately * after local MAC is computed. * * Used for: * - CSF (Cloud+Sync+FS) case: mtime-only differences in synced files * - Clone candidates: verifying file content before cloning a node * * @copyright Simplified (2-clause) BSD License. */ #ifndef MEGA_MAC_COMPUTATION_STATE_H #define MEGA_MAC_COMPUTATION_STATE_H #ifdef ENABLE_SYNC #include "mega/filefingerprint.h" #include "mega/localpath.h" #include "mega/types.h" #include "mega/utils.h" #include #include #include #include #include namespace mega { class MacComputationThrottle; /** * @brief Context for CSF case validation and comparison. * * Used to detect if the file or cloud node changed during MAC computation. * Also stores the expected (remote) MAC for comparison when local MAC is ready. * Only needed for CSF case - clone candidates use upload object lifetime instead. */ struct MacComputationContext { FileFingerprint localFp; FileFingerprint cloudFp; NodeHandle cloudHandle; handle fsid{UNDEF}; int64_t expectedMac{INVALID_META_MAC}; // Remote MAC for comparison bool matches(const handle currentFsid, const NodeHandle currentCloudHandle, const FileFingerprint& currentLocalFp, const FileFingerprint& currentCloudFp) const { return currentFsid == fsid && currentCloudHandle == cloudHandle && currentLocalFp.equalExceptMtime(localFp) && currentCloudFp.equalExceptMtime(cloudFp); } }; /** * @brief State for asynchronous local file MAC computation. * * Tracks progress of incremental MAC computation across sync iterations. * Thread-safe: sync thread reads/writes, worker thread (mAsyncQueue) computes. * * Lifetime management: * - Owner (LocalNode::RareFields or SyncUpload_inClient) holds shared_ptr * - Worker thread captures weak_ptr in lambda * - If owner is destroyed, weak_ptr.lock() returns nullptr and computation is abandoned * * This struct focuses ONLY on computing the local file's MAC. * Comparison with remote MAC is done after local MAC is ready. */ struct MacComputationState { // File info (immutable after construction) const m_off_t totalSize; const LocalPath filePath; // Cipher params (from reference node - needed to compute MAC) std::array transferkey{}; int64_t ctriv = 0; // Progress tracking m_off_t currentPosition = 0; // Accumulated chunk MACs - protected by mutex mutable std::mutex macsMutex; chunkmac_map partialMacs; // Buffer size for reading chunks (10MB) static constexpr m_off_t BUFFER_SIZE = 10 * 1024 * 1024; // State flags (atomic for thread safety) std::atomic chunkInProgress{false}; // True while worker processing std::atomic completed{false}; // True when local MAC computed std::atomic failed{false}; // True if read/compute error // True when initialization is complete (first advanceMacComputation has returned). // Used to prevent checkPendingCloneMac from racing with initCloneCandidateMacComputation. // The sync thread (checkPendingCloneMac) should not proceed until this is true. std::atomic initializationComplete{false}; // Throttle tracking - true if we've acquired a slot from MacComputationThrottle bool throttleSlotAcquired{false}; // Result - the computed local file MAC (valid after completed=true) int64_t localMac{INVALID_META_MAC}; // Optional context for CSF case validation // Not used for clone candidates (they use upload object lifetime) std::optional context; // Clone candidate tracking (clone MAC computations only) NodeHandle cloneCandidateHandle; std::string cloneCandidateNodeKey; MacComputationState(const m_off_t totalSize_, const LocalPath& filePath_, MacComputationThrottle& throttle): totalSize(totalSize_), filePath(filePath_), mThrottle(throttle) {} ~MacComputationState(); /** * @brief Thread-safe: called by worker thread when chunk MAC is computed. */ void addChunkMacs(chunkmac_map&& chunkMacs, m_off_t newPosition) { std::lock_guard g(macsMutex); chunkMacs.copyEntriesTo(partialMacs); currentPosition = newPosition; } /** * @brief Thread-safe: called by worker thread when local MAC computation completes. */ void setComplete(const int64_t computedLocalMac) { localMac = computedLocalMac; chunkInProgress.store(false, std::memory_order_release); completed.store(true, std::memory_order_release); } /** * @brief Thread-safe: called by worker thread on error. */ void setFailed() { chunkInProgress.store(false, std::memory_order_release); failed.store(true, std::memory_order_release); } /** * @brief Thread-safe: check if a chunk is currently being processed. */ bool isChunkInProgress() const { return chunkInProgress.load(std::memory_order_acquire); } /** * @brief Thread-safe: check if local MAC is ready. */ bool isReady() const { return completed.load(std::memory_order_acquire); } /** * @brief Thread-safe: check if computation failed. */ bool hasFailed() const { return failed.load(std::memory_order_acquire); } /** * @brief Thread-safe: check if initialization is complete. * * Returns true after the initializing thread has finished setting up the computation * and the first advanceMacComputation call has returned. This prevents race conditions * where checkPendingCloneMac runs before initCloneCandidateMacComputation completes. */ bool isInitializationComplete() const { return initializationComplete.load(std::memory_order_acquire); } /** * @brief Thread-safe: mark initialization as complete. * * Called after advanceMacComputation returns in the initialization function. */ void setInitializationComplete() { initializationComplete.store(true, std::memory_order_release); } /** * @brief Check if stored context matches current state (CSF case only). */ bool contextMatches(const handle currentFsid, const NodeHandle currentCloudHandle, const FileFingerprint& currentLocalFp, const FileFingerprint& currentCloudFp) const { return context && context->matches(currentFsid, currentCloudHandle, currentLocalFp, currentCloudFp); } private: MacComputationThrottle& mThrottle; }; } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_MAC_COMPUTATION_STATE_H sdk-10.11.0/include/mega/syncinternals/syncinternals.h000066400000000000000000000570101516266226600227470ustar00rootroot00000000000000/** * @file syncinternals.h * @brief Class for internal operations of the sync engine. */ #ifndef MEGA_SYNCINTERNALS_H #define MEGA_SYNCINTERNALS_H 1 #ifdef ENABLE_SYNC #include "mega/db.h" #include "mega/node.h" #include "mega/syncinternals/mac_computation_state.h" #include #include namespace mega { /***************************\ * FIND LOCAL NODE BY FSID * \***************************/ /** * @brief Represents the result of a file system ID (FSID) node match operation. * * This enum provides detailed outcomes of comparing a source node to a target node * based on their FSID and associated attributes. */ enum class NodeMatchByFSIDResult { /** * @brief Nodes are equivalent. * * The source and target nodes match based on all criteria. */ Matched, /** * @brief Source and target nodes have different node types. * * Indicates that the nodes cannot be matched due to type mismatch (e.g., FILENODE vs. * FOLDERNODE). */ DifferentTypes, /** * @brief The source FSID has been reused. * * Suggests that the source node's parent dir FSID was reused, leading to potential * conflicts. */ SourceFsidReused, /** * @brief Source and target nodes are on different filesystems. */ DifferentFilesystems, /** * @brief Source and target nodes belong to different owners. * * We cannot move a node between cloud users (eg inshare to this account, or inshare to * inshare), so we avoid detecting those. */ DifferentOwners, /** * @brief The source node is explicitly excluded from synchronization. */ SourceIsExcluded, /** * @brief The exclusion state of the source node is unknown. */ SourceExclusionUnknown, /** * @brief File fingerprints differ. * * The source and target nodes have mismatching file fingerprints. */ DifferentFingerprint, /** * @brief File fingerprints differ only in mtime. * * The source and target nodes have mismatching file fingerprints but only in mtime (CRC, Size * and isValid match). */ DifferentFingerprintOnlyMtime, }; /** * @brief Represents the additional attributes needed to match a node by FSID. * * This structure encapsulates the attributes used to match a node * based on its file system ID (FSID) and related properties. */ struct NodeMatchByFSIDAttributes { /** * @brief The type of the node (e.g., FILENODE, FOLDERNODE). */ nodetype_t nodetype; /** * @brief The file system fingerprint. */ const fsfp_t& fsfp; /** * @brief The user handle of the node's owner. */ handle owningUser; // In Android we can't set mtime, then we have two fingerprints // fingerprint -> expected fingerprint (fingerprint of the file with modified mtime) // realFingerprint -> fingerprint from file system // In no Android systems, this values should be the same /** * @brief The file fingerprint for comparison. */ const FileFingerprint& fingerprint; /** * @brief The real file fingerprint for comparison. */ const FileFingerprint realFingerprint; }; /** * @brief Context for matching source nodes by file system ID. * * This structure provides contextual information when determining if * a file system ID (FSID) has been reused and its exclusion state. */ struct SourceNodeMatchByFSIDContext { /** * @brief Indicates whether the fsid is reused. * * true if the fsid has been reused, false otherwise. */ bool isFsidReused; /** * @brief The exclusion state of the node. * * Specifies whether the node is included/excluded from syncing. */ ExclusionState exclusionState; }; /** * @brief Indicates whether a LocalNode is part of a scanned or synced context. * * This is meant to be used to retrieve the corresponding scanned or synced values both * for the FSID and the FileFingerprint of the match candidate local node. * */ enum class ScannedOrSyncedContext { /** * @brief SYNCED node context. */ SYNCED, /** * @brief SCANNED node context. */ SCANNED }; /** * @brief Predicate class for finding a LocalNode by its File System ID (FSID). * * This class encapsulates the logic needed to determine whether a given LocalNode * matches the specified criteria for scanned or synced contexts. * It uses areNodesMatchedByFsidEquivalent() to encapsulate filtering logic and validate whether * a node is a valid match. Additional checks like node type, owning user, exclusion state, * and fingerprints ensure FSID reuse doesn't lead to incorrect matches. */ struct FindLocalNodeByFSIDPredicate { /** * @brief Constructs the predicate with necessary parameters. * * @param fsid The FSID to search for. * @param scannedOrSyncedType Indicates whether the search is in a synced or scanned context. * @param targetNodeAttributes Attributes of the target node to match against. * @param originalPathForLogging The original path being processed for context in logs. * @param extraCheck Additional optional checks to apply to matching nodes. * @param onFingerprintMismatchDuringPutnodes Callback for handling fingerprint mismatches while * there are ongoing putnodes operations. */ FindLocalNodeByFSIDPredicate( const handle fsid, const ScannedOrSyncedContext scannedOrSyncedCtxt, const NodeMatchByFSIDAttributes& targetNodeAttributes, const LocalPath& originalPathForLogging, std::function extraCheck = nullptr, std::function onFingerprintMismatchDuringPutnodes = nullptr): mFsid{fsid}, mScannedOrSyncedCtxt{scannedOrSyncedCtxt}, mTargetNodeAttributes{targetNodeAttributes}, mOriginalPathForLogging{originalPathForLogging}, mExtraCheck{extraCheck}, mOnFingerprintMismatchDuringPutnodes{onFingerprintMismatchDuringPutnodes} {}; /** * @brief Determines if a LocalNode matches the specified criteria. * * @param localNode The LocalNode to evaluate. * @return true if the node matches the criteria, false otherwise. */ bool operator()(LocalNode& localNode); /** * @brief Resets the early exit condition, preparing the predicate for reuse. */ void resetEarlyExit() { mEarlyExit = false; } /** * @brief Retrieves the FSID being searched for. * * @return The FSID handle. */ const handle& fsid() const { return mFsid; } /** * @brief Indicates if an unknown exclusion was encountered during the search. * * @return true if an unknown exclusion was found, false otherwise. */ bool foundExclusionUnknown() const { return mFoundExclusionUnknown; } private: /** * @brief Checks if the FSID has been reused for the given node. * * @param localNode The node to check. * @return true if the FSID has been reused, false otherwise. */ bool isFsidReused(const LocalNode& localNode) const { switch (mScannedOrSyncedCtxt) { case ScannedOrSyncedContext::SYNCED: return localNode.fsidSyncedReused; case ScannedOrSyncedContext::SCANNED: return localNode.fsidScannedReused; } assert(false && "Unexpected ScannedOrSyncedContext value"); return localNode.fsidScannedReused; // Fallback to silence compiler warning } /** * @brief Retrieves the fingerprint for the given node. * * @param localNode The node to retrieve the fingerprint from. * @return The file fingerprint of the node. */ const FileFingerprint& getFingerprint(const LocalNode& localNode) const { switch (mScannedOrSyncedCtxt) { case ScannedOrSyncedContext::SYNCED: return localNode.syncedFingerprint; case ScannedOrSyncedContext::SCANNED: return localNode.scannedFingerprint; } assert(false && "Unexpected ScannedOrSyncedContext value"); return localNode.scannedFingerprint; // Fallback to silence compiler warning } /** * @brief Logs a message with details about the search. * * @param msg The message to log. * @param checkingLocalPath The path of the node being checked. */ void logMsg(const std::string& msg, const LocalPath& checkingLocalPath) const; // MEMBERS /** * @brief The FSID being searched for. This is the primary key for matching two nodes. */ handle mFsid; /** * @brief Indicates whether the operation is performed in a scanned * or synced context. */ ScannedOrSyncedContext mScannedOrSyncedCtxt; /** * @brief Target node attributes for matching. * * Encapsulates details such as node type, file system fingerprint, * owner, and file fingerprint for the target node being matched. */ const NodeMatchByFSIDAttributes& mTargetNodeAttributes; /** * @brief Original path of the target node. * * Provides context for log messages during node matching. */ const LocalPath& mOriginalPathForLogging; /** * @brief Optional extra check for nodes. * * A user-defined function for applying additional filtering logic * to potential matches. */ std::function mExtraCheck; /** * @brief Callback for fingerprint mismatches during ongoing putnodes operations. * * Optional operation for a LocalNode that * has been excluded due to fingerprint mismatch, but the source node has a putnodes operation * ongoing for an upload which matches fingerprint with the target node. * The param is not const intentionally, in case it needs to be considered as a potential * source node, taking into account that there is a fingerprint match for the ongoing upload. */ std::function mOnFingerprintMismatchDuringPutnodes; /** * @brief Flag indicating if an unknown exclusion was encountered. */ bool mFoundExclusionUnknown{false}; /** * @brief Flag for early exit during search. * * Used to signal an early termination condition in the search loop * when certain criteria are met (e.g., mismatch during a putnodes operation detected while * meeting mOnFingerprintMismatchDuringPutnodes critera: no need to keep searching for a match). */ bool mEarlyExit{false}; }; /** * @brief Determines whether or not a source node and a target node matched by FSID can be * considered as equivalent. * * This method encapsulates the filtering logic for nodes matched by FSID. * It checks various properties (e.g., node type, filesystem fingerprint, exclusion state, FSID * reuse, file fingerprint) to ensure the node is a valid match. This method is designed for * decoupled logic without requiring access to Syncs instance attributes. * * @param NodeMatchByFSIDAttributes The necessary node attributes of the source node being * checked. * @param NodeMatchByFSIDAttributes The necessary node attributes of the target node being * checked. * @param SourceNodeMatchByFSIDContext Additional context of the source node being checked. * * @return A NodeMatchByFSIDResult enum value with the result. * * @warning about comparing fingerprints: * FSIDs (e.g., inodes on Linux) can be reused when files are deleted and new ones are * created. Also when files are updated by replacement. * To ensure that we are detecting a true move of the same file (and not incorrectly * matching two different files with reused FSIDs), we compare the file fingerprints. * The fingerprint provides a heuristic based on file properties like size and * modification time. While there is a small chance that a moved file with simultaneous * changes could mismatch (causing a reupload), this is far less harmful than mixing two * different files and losing data. * * This check is limited to FILENODE because fingerprints only exists for them. * Folder nodes (FOLDERNODE) generally do not have meaningful * fingerprints as their state is determined by their contents rather than intrinsic * properties. * Besides, for folders is much less common to have replacements or delete&create flows, * so the FSID alone is usually sufficient for detecting moves. */ NodeMatchByFSIDResult areNodesMatchedByFsidEquivalent(const NodeMatchByFSIDAttributes& source, const NodeMatchByFSIDAttributes& target, const SourceNodeMatchByFSIDContext& context); /** * @brief Finds a LocalNode by its File System ID (FSID) in a specified map. * * This method matches the provided FSID against cached FSIDs in the given map. It uses * FindLocalNodeByFSIDPredicate to encapsulate filtering logic and validate whether * a node is a valid match. Additional checks like node type, owning user, exclusion state, * and fingerprints ensure FSID reuse doesn't lead to incorrect matches. * * @param fsidLocalnodeMap The map of FSIDs to LocalNodes. * @param FindLocalNodeByFSIDPredicate The predicate with the necessary checks to consider the * searched node (already matched by FSID) a valid match, i.e., that the source and target nodes are * indeed equivalent. * * @return a const iterator to the fsid_localnode_map with the matched fsid or to the end if there * is no match. * * @see FindLocalNodeByFSIDPredicate */ fsid_localnode_map::const_iterator findLocalNodeByFsid_if(const fsid_localnode_map& fsidLocalnodeMap, FindLocalNodeByFSIDPredicate& predicate); /** * @brief Finds a LocalNode by its File System ID (FSID) in a specified map. * * This method matches the provided FSID against cached FSIDs in the given map. It uses * FindLocalNodeByFSIDPredicate to encapsulate filtering logic and validate whether * a node is a valid match. Additional checks like node type, owning user, exclusion state, * and fingerprints ensure FSID reuse doesn't lead to incorrect matches. * * @param fsidLocalnodeMap The map of FSIDs to LocalNodes. * @param FindLocalNodeByFSIDPredicate The rvalue predicate with the necessary checks to consider * the searched node (already matched by FSID) a valid match, i.e., that the source and target nodes * are indeed equivalent. * * @return A pair with: * bool - indicating whether an unknown exclusion was encountered. This may occur during * eg. the first pass of the tree after loading from Suspended state and the corresponding node * is later in the tree. The caller should decide whether to pospone the logic if an unknown * exclusion was found for some node. * LocalNode* - pointer to the matching LocalNode, or nullptr if no match is found. * * @see FindLocalNodeByFSIDPredicate */ std::pair findLocalNodeByFsid(const fsid_localnode_map& fsidLocalnodeMap, FindLocalNodeByFSIDPredicate&& predicate); /********************************\ * FIND NODE CANDIDATE TO CLONE * \********************************/ /** * @brief Finds a suitable node that can be cloned rather than triggering a new upload. * * This method prepares the local file extension and constructs a predicate to evaluate * candidate nodes based on their content and extension. It returns a pointer to a valid * clone node if found, or nullptr if no suitable node exists. * * A valid node to be cloned is a matched node that also has a valid key (no zero-key issue). * * @param mc Reference to the MegaClient managing the synchronization. * @param upload Const reference to the upload task being processed. * @return Shared pointer to a valid clone node if found, or nullptr otherwise. */ std::shared_ptr findCloneNodeCandidate(MegaClient& mc, const SyncUpload_inClient& upload, const bool excludeMtime); /****************\ * SYNC UPLOADS * \****************/ /** * @brief Manages the upload process for a file, with support for node cloning. * * This method attempts to find a clone node that matches the local file's content and * extension. If a valid node is found, it uses the node for cloning. Otherwise, it * proceeds with a normal upload process. * * @param mc Reference to the MegaClient. * @param committer Reference to the transfer database committer. * For the other params, see LocalNode::queueClientUpload() */ void clientUpload(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut); /** * @brief Compares a local file's CRC with a legacy buggy sparse CRC. * * @param expectedSize The expected size of the local file. * @param compareToCrc The CRC to compare the local file's CRC with. * @return True if the local file's CRC matches the legacy buggy sparse CRC, false otherwise. */ bool compareLegacyBuggySparseCrc(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, const FingerprintCrc& compareToCrc); /******************\ * SYNC DOWNLOADS * \******************/ void clientDownload(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr download, const bool queueFirst); /********************\ * SYNC COMPARISONS * \********************/ constexpr uint32_t kDefaultMaxConcurrentMacComputations = 8; /** * @brief Throttle for MAC computation to prevent resource exhaustion. * * Thread-safe class that tracks and limits concurrent MAC computations. * Prevents the sync engine from overwhelming the system with too many * simultaneous MAC calculations. * * We track FILES only (one chunk at a time per file). * * Usage: * - Call tryAcquireFile() before starting MAC computation for a new file * - Call releaseFile() when file computation completes */ class MacComputationThrottle { public: explicit MacComputationThrottle( uint32_t maxConcurrentFiles = kDefaultMaxConcurrentMacComputations): mMaxConcurrentFiles(maxConcurrentFiles) {} bool tryAcquireFile() { std::lock_guard lock(mMutex); if (mCurrentFiles >= mMaxConcurrentFiles) { return false; } ++mCurrentFiles; return true; } void releaseFile() { std::lock_guard lock(mMutex); if (mCurrentFiles == 0) { assert(false && "MacComputationThrottle: releaseFile called but no files are currently " "being processed"); return; } --mCurrentFiles; } uint32_t currentFiles() const { std::lock_guard lock(mMutex); return mCurrentFiles; } private: mutable std::mutex mMutex; uint32_t mMaxConcurrentFiles; uint32_t mCurrentFiles{0}; }; enum class FingerprintMismatch : std::uint8_t { None = 0, MtimeOnly, CrcOnly, Other, }; /** * @brief Result type for fingerprint/MAC comparisons. * * Returns `std::tuple` where: * - The first element is a `node_comparison_result` indicating: * + NODE_COMP_EARGS: Invalid arguments * + NODE_COMP_EREAD: Error reading the local file. * + NODE_COMP_PENDING: MAC computation initiated but not yet complete (async mode only) * + NODE_COMP_EQUAL: Fingerprints match including mtime * + NODE_COMP_DIFFERS_FP: Node types mismatch or fingerprints differ in something more * than mtime (CRC, Size, isValid). * + NODE_COMP_DIFFERS_MTIME: Fingerprints differ in mtime but METAMACs match. * + NODE_COMP_DIFFERS_MAC: Fingerprints differ in mtime and METAMACs also differ. * - The second element is the local MetaMAC, or INVALID_META_MAC if not computed. * - The third element is the remote MetaMAC, or INVALID_META_MAC if not computed. * - The fourth element categorizes the fingerprint mismatch (when determinable without MAC). */ using FsCloudComparisonResult = std::tuple; /** * @brief Quick fingerprint comparison without MAC computation. * * Compares type, size, CRC, and mtime. Returns a conclusive result if possible, * or std::nullopt if only mtime differs (indicating MAC computation is needed). * * @return std::optional with: * - NODE_COMP_EQUAL if fingerprints fully match (including mtime) * - NODE_COMP_DIFFERS_FP if fingerprints differ in type/size/CRC * - std::nullopt if only mtime differs (MAC needed to determine equality) */ std::optional quickFingerprintComparison(const CloudNode& cn, const FSNode& fs); /** * @brief Compares fsNode with cloudNode using async MAC computation. * * For synced files that have a LocalNode. If fingerprints match or differ in more than mtime, * returns immediately. If only mtime differs, initiates or checks async MAC computation * stored in LocalNode::RareFields. * * @param syncNode Reference to the LocalNode for storing async MAC computation state. * @return Comparison result. Returns NODE_COMP_PENDING if MAC computation in progress. * * @note METAMACs are only computed if fingerprints differ only in mtime. */ FsCloudComparisonResult syncEqualFsCloudExcludingMtimeAsync(MegaClient& mc, const CloudNode& cn, const FSNode& fs, const LocalPath& fsNodeFullPath, LocalNode& syncNode); /***********************************\ * CLONE CANDIDATE MAC COMPUTATION * \***********************************/ /** * @brief Status for clone MAC computation (init/check/compute). */ enum class CloneMacStatus { Pending, // Computing or throttled Ready, // Computed MAC available Failed, // Computation error / candidate invalidated NoCandidates, // No clone candidates or cannot start }; /** * @brief Check and advance pending clone candidate MAC computation. * * Called from resolve_upsync when upload exists. Advances computation and * returns status. When Ready, upload.mMetaMac contains the computed MAC * for use with findCloneNodeCandidate. * */ CloneMacStatus checkPendingCloneMac(MegaClient& mc, SyncUpload_inClient& upload); /** * @brief Process the result of clone candidate MAC computation. * * Processes the result of clone candidate MAC computation and decides the next action: * - If MAC is ready, proceeds with clone candidate search. * - If MAC failed, falls back to full upload. * - If no candidates, continues with full upload. */ void processCloneMacResult(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut, const CloneMacStatus macStatus); } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_SYNCINTERNALS_H sdk-10.11.0/include/mega/syncinternals/syncinternals_logging.h000066400000000000000000000015311516266226600244520ustar00rootroot00000000000000/** * @file syncinternals_logging.h * @brief Class for internal logging operations of the sync engine. */ #ifndef MEGA_SYNCINTERNALS_LOGGING_H #define MEGA_SYNCINTERNALS_LOGGING H 1 #ifdef ENABLE_SYNC #include "mega/logging.h" #include namespace mega { // Constants using namespace std::chrono_literals; const std::chrono::milliseconds MIN_DELAY_BETWEEN_SYNC_VERBOSE_TIMED{20s}; const std::chrono::milliseconds TIME_WINDOW_FOR_SYNC_VERBOSE_TIMED{1s}; // Macros #define SYNC_verbose \ if (syncs.mDetailedSyncLogging) \ LOG_verbose #define SYNC_verbose_timed \ if (syncs.mDetailedSyncLogging) \ SYNCS_verbose_timed #define SYNCS_verbose_timed \ LOG_verbose_timed(MIN_DELAY_BETWEEN_SYNC_VERBOSE_TIMED, TIME_WINDOW_FOR_SYNC_VERBOSE_TIMED) } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_SYNCINTERNALS_LOGGING_H sdk-10.11.0/include/mega/syncinternals/synciuploadthrottlingmanager.h000066400000000000000000000050501516266226600260540ustar00rootroot00000000000000/** * @file syncuploadthrottlingmanager.h * @brief Class for IUploadThrottlingManager. */ #ifndef MEGA_SYNCINTERNALS_IUPLOADTHROTTLINGMANAGER_H #define MEGA_SYNCINTERNALS_IUPLOADTHROTTLINGMANAGER_H 1 #ifdef ENABLE_SYNC #include "mega/file.h" #include "mega/types.h" #include namespace mega { /** * @struct ThrottleValueLimits * @brief Struct to contain the configurable values lower and upper limits. */ struct ThrottleValueLimits { std::chrono::seconds throttleUpdateRateLowerLimit; std::chrono::seconds throttleUpdateRateUpperLimit; unsigned maxUploadsBeforeThrottleLowerLimit; unsigned maxUploadsBeforeThrottleUpperLimit; }; /** * @class IUploadThrottlingManager * @brief Interface for the manager in charge of throttling and delayed processing of uploads. * * The IUploadThrottlingManager is meant to handle the collecting and processing of delayed uploads, * as well as owning and defining the configurable values to be used either from this manager or * from other components which are part of the throttling logic. * * The configurable values are: * throttleUpdateRate: delay to process next delayed upload. This one is meant to be used directly * within the internal process of delayed uploads. maxUploadsBeforeThrottle: number of uploads that * doesn't go through the throttling logic. This one is meant to be used by other components * handling the individual uploads and calling addToDelayedUploads when needed. * * Additionally, the uploadCounterInactivityExpirationTime is used to reset the individual upload * counters after some time, to avoid increasing them forever. */ class IUploadThrottlingManager { public: virtual ~IUploadThrottlingManager() = default; // Delayed uploads perations. virtual void addToDelayedUploads(DelayedSyncUpload&&) = 0; virtual void processDelayedUploads(std::function&&) = 0; // Setters. virtual bool setThrottleUpdateRate(const std::chrono::seconds) = 0; virtual bool setMaxUploadsBeforeThrottle(const unsigned) = 0; // Getters. virtual bool anyDelayedUploads() const = 0; virtual std::chrono::seconds uploadCounterInactivityExpirationTime() const = 0; virtual std::chrono::seconds throttleUpdateRate() const = 0; virtual unsigned maxUploadsBeforeThrottle() const = 0; virtual ThrottleValueLimits throttleValueLimits() const = 0; virtual std::chrono::seconds timeSinceLastProcessedUpload() const = 0; }; } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_SYNCINTERNALS_IUPLOADTHROTTLINGMANAGER_H sdk-10.11.0/include/mega/syncinternals/syncuploadthrottlingfile.h000066400000000000000000000101321516266226600252050ustar00rootroot00000000000000/** * @file syncuploadthrottlingfile.h * @brief Class for UploadThrottlingFile. */ #ifndef MEGA_SYNCINTERNALS_UPLOADTHROTTLINGFILE_H #define MEGA_SYNCINTERNALS_UPLOADTHROTTLINGFILE_H 1 #ifdef ENABLE_SYNC #include "mega/file.h" namespace mega { /** * @struct UploadThrottlingFile * @brief Handles upload throttling and abort handling for individual files. * * This struct encapsulates the logic for handling upload throttling and aborted uploads. * It tracks the number of uploads, manages timeouts, and * provides mechanisms for resetting counters and determining when throttling or upload * continuation should occur. * * @see UploadThrottlingManager */ struct UploadThrottlingFile { private: /** * @brief Counter for completed uploads. */ unsigned mUploadCounter{}; /** * @brief Timestamp of the last time the upload counter was processed. */ std::chrono::steady_clock::time_point mUploadCounterLastTime{std::chrono::steady_clock::now()}; /** * @brief Flag to bypass throttling logic. * This is meant for uncomplete uploads that were cancelled due to a change or failure. */ bool mBypassThrottlingNextTime{}; public: /** * @brief Gets the mUploadCounter. */ unsigned uploadCounter() const { return mUploadCounter; } /** * @brief Gets the mBypassThrottlingNextTime flag. */ unsigned willBypassThrottlingNextTime() const { return mBypassThrottlingNextTime; } /** * @brief Increases the upload counter by 1. * Also checks if the upload counter is going to reach the max for its type. * In that case the upload counter is reset. */ void increaseUploadCounter(); /** * @brief Checks throttling control logic for uploads. * * Checks the bypassThrottlingNextTime flag in case it needs bypassing, and whether the number * of uploads exceeds the configured maximum before throttling, always that the time lapsed * since last upload counter processing does not exceed the * uploadCounterInactivityExpirationTime. * * @param uploadCounterInactivityExpirationTime Timeout for resetting the upload counter. * @return True if throttling must be applied, otherwise false. */ bool checkUploadThrottling(const unsigned maxUploadsBeforeThrottle, const std::chrono::seconds uploadCounterInactivityExpirationTime); /** * @brief Handles the logic for aborting uploads due to fingerprint mismatch or termination. * * The upload can only be aborted if: * - The upload has already started (not in the throttling queue). Otherwise the * fingerprint of the upload is updated with the new one (unless the transfer direction needs to * change). No need to cancel the upload. * - The upload has not started the putnodes request. * * If the above conditions are met the upload must be aborted. * Additionally, bypassThrottlingNextTime() is called in case that the upload must be * aborted and the transfer direction does not need to change. * * @return True if the upload should be aborted, otherwise false. */ bool handleAbortUpload(SyncUpload_inClient& upload, const bool transferDirectionNeedsToChange, const FileFingerprint& fingerprint, const unsigned maxUploadsBeforeThrottle, const LocalPath& transferPath); /** * @brief Sets the mBypassThrottlingNextTime flag. * * The upload counter is not increased if the upload is not completed. However, the counter * could be greater than maxUploadsBeforeThrottle already, and the current upload has been * cancelled due to a fingerprint change or failure. * In that case, this method should be called to set the flag to true and bypass the throttling * logic upon the upload restart. */ void bypassThrottlingNextTime(const unsigned maxUploadsBeforeThrottle); }; } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_SYNCINTERNALS_UPLOADTHROTTLINGFILE_H sdk-10.11.0/include/mega/syncinternals/syncuploadthrottlingmanager.h000066400000000000000000000132621516266226600257070ustar00rootroot00000000000000/** * @file syncuploadthrottlingmanager.h * @brief Class for UploadThrottlingManager. */ #ifndef MEGA_SYNCINTERNALS_UPLOADTHROTTLINGMANAGER_H #define MEGA_SYNCINTERNALS_UPLOADTHROTTLINGMANAGER_H 1 #ifdef ENABLE_SYNC #include "mega/syncinternals/synciuploadthrottlingmanager.h" namespace mega { /** * @brief Calculates the adjusted throttle update rate depending on a given size. */ std::chrono::seconds calcDynamicThrottleUpdateRate(const std::chrono::seconds updateRateSeconds, const size_t delayedUploadsSize); /** * @class UploadThrottlingManager * @brief Manages throttling, delayed processing of uploads and configurable values. * * @see IUploadThrottlingManager */ class UploadThrottlingManager: public IUploadThrottlingManager { public: void addToDelayedUploads(DelayedSyncUpload&& delayedUpload) override { mDelayedQueue.emplace(std::move(delayedUpload)); } /** * @brief Processes the delayed upload queue. * * Processes the next delayed upload in the queue, ensuring that throttling conditions * are met before initiating uploads. * * If the next delayed upload is not valid (DelayedSyncUpload::weakUpload is not valid), it will * be skipped and the next delayed upload in the queue, if any, will be the one to be processed. * * If a valid delayed upload is processed, it will be passed to the completion function for * further processing (ex: enqueue the upload to the client). * * @see checkProcessDelayedUploads() */ void processDelayedUploads(std::function&& completion) override; /** * @brief Resets last processed time of a delayed upload from the queue. * This time will be the start point to process the next delayed upload after the * throttleUpdateRate. */ void resetLastProcessedTime() { mLastProcessedTime = std::chrono::steady_clock::now(); } // Setters /** * Sets the throttleUpdateRate configurable value. * @param interval The new throttle update rate or delay to process the next * delayed upload. It cannot be below THROTTLE_UPDATE_RATE_LOWER_LIMIT nor above * THROTTLE_UPDATE_RATE_UPPER_LIMIT. */ bool setThrottleUpdateRate(const std::chrono::seconds interval) override; /** * Sets the maxUploadsBeforeThrottle configurable value. * @param maxUploadsBeforeThrottle The maximum number of uploads that will be uploaded * unthrottled. It cannot be below MAX_UPLOADS_BEFORE_THROTTLE_LOWER_LIMIT nor above * MAX_UPLOADS_BEFORE_THROTTLE_UPPER_LIMIT. */ bool setMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle) override; // Getters bool anyDelayedUploads() const override { return !mDelayedQueue.empty(); } std::chrono::seconds uploadCounterInactivityExpirationTime() const override { return mUploadCounterInactivityExpirationTime; } std::chrono::seconds throttleUpdateRate() const override { return mThrottleUpdateRate; } unsigned maxUploadsBeforeThrottle() const override { return mMaxUploadsBeforeThrottle; } ThrottleValueLimits throttleValueLimits() const override { return {THROTTLE_UPDATE_RATE_LOWER_LIMIT, THROTTLE_UPDATE_RATE_UPPER_LIMIT, MAX_UPLOADS_BEFORE_THROTTLE_LOWER_LIMIT, MAX_UPLOADS_BEFORE_THROTTLE_UPPER_LIMIT}; } std::chrono::seconds timeSinceLastProcessedUpload() const override { return std::chrono::duration_cast(std::chrono::steady_clock::now() - mLastProcessedTime); } private: // Limits static constexpr std::chrono::seconds TIMEOUT_TO_RESET_UPLOAD_COUNTERS{ 86400}; // Timeout to reset upload counters due to inactivity. static constexpr std::chrono::seconds THROTTLE_UPDATE_RATE_LOWER_LIMIT{1}; static constexpr std::chrono::seconds THROTTLE_UPDATE_RATE_UPPER_LIMIT{ TIMEOUT_TO_RESET_UPLOAD_COUNTERS - std::chrono::seconds(1)}; static constexpr unsigned MAX_UPLOADS_BEFORE_THROTTLE_LOWER_LIMIT{1}; static constexpr unsigned MAX_UPLOADS_BEFORE_THROTTLE_UPPER_LIMIT{10000000}; // Default values static constexpr std::chrono::seconds DEFAULT_THROTTLE_UPDATE_RATE{1800}; static constexpr unsigned DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE{2}; // Members std::queue mDelayedQueue; std::chrono::steady_clock::time_point mLastProcessedTime{std::chrono::steady_clock::now()}; std::chrono::seconds mUploadCounterInactivityExpirationTime{TIMEOUT_TO_RESET_UPLOAD_COUNTERS}; // Configurable members std::chrono::seconds mThrottleUpdateRate{DEFAULT_THROTTLE_UPDATE_RATE}; unsigned mMaxUploadsBeforeThrottle{DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE}; /** * @brief Checks if the next delayed upload in the queue should be processed. * * Calculates a dynamic update rate taking into account: * 1. mDelayedQueue size. * 2. mThrottleUpdateRate (reference value). * 3. THROTTLE_UPDATE_RATE_LOWER_LIMIT. * The dynamic rate is the max between the THROTTLE_UPDATE_RATE_LOWER_LIMIT and the result of * mThrottleUpdateRate / sqrt(mDelayedQueue.size()) * * @return True if the next upload should be processed, otherwise false. */ bool checkProcessDelayedUploads() const; std::chrono::seconds dynamicThrottleUpdateRate() const { return calcDynamicThrottleUpdateRate(mThrottleUpdateRate, mDelayedQueue.size()); } }; } // namespace mega #endif // ENABLE_SYNC #endif // MEGA_SYNCINTERNALS_UPLOADTHROTTLINGMANAGER_H sdk-10.11.0/include/mega/testhooks.h000066400000000000000000000221061516266226600172000ustar00rootroot00000000000000/** * @file mega/testhooks.h * @brief helper classes for test cases to simulate various errors and special conditions * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_TESTHOOKS_H #define MEGA_TESTHOOKS_H 1 #include "types.h" #include namespace mega { // These hooks allow the sdk_test project to simulate some error / retry conditions, or cause smaller download block sizes for quicker tests // However they do require some (minimal) extra code in the SDK. // The preprocessor is used to ensure that code is not present for release builds, so it can't cause problems. // Additionally the hooks use std::function so a suitable compiler and library are needed to leverage those tests. #ifndef NDEBUG #define MEGASDK_DEBUG_TEST_HOOKS_ENABLED #endif #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED struct MEGA_API HttpReq; class MEGA_API RaidBufferManager; class DebugTestHook; struct Transfer; class TransferDbCommitter; struct MegaTestHooks { std::function onHttpReqPost; std::function onSetIsRaid; std::function onUploadChunkFailed; std::function onProgressCompletedUpdate; std::function onProgressContiguousUpdate; std::function onUploadChunkSucceeded; std::function onTransferReportProgress; std::function onDownloadFailed; std::function&)> interceptSCRequest; std::function onLimitMaxReqSize; std::function onHookNumberOfConnections; std::function onHookDownloadRequestSingleUrl; std::function onHookResetTransferLastAccessTime; std::function&)> interceptLocklessCSRequest; std::function< void(const int /*httpStatus*/, const unsigned /*curlCode*/, const bool /*failed*/)> onHttpReqFinish; // Allow tests to force legacy (buggy) sparse CRC offset computation in FileFingerprint. // When enabled, FileFingerprint uses `legacySparseOffset32Bug()` instead of the fixed // 64-bit math when sampling large files (sparse CRC). std::function onHookFileFingerprintUseLegacyBuggySparseCrc; // Allow to set device id to a specific value for testing purposes std::function onHookDeviceId; std::function onHashcashCalculationStarted; // Called during generateMetaMac after reading each chunk. Allows tests to modify/lock // the file mid-computation to trigger read errors. std::function onMacGenerationChunkRead; }; extern MegaTestHooks globalMegaTestHooks; // allow the test client to skip an actual http request, and set the results directly. The return statement, if activated, skips the http post() #define DEBUG_TEST_HOOK_HTTPREQ_POST(HTTPREQPTR) { if (globalMegaTestHooks.onHttpReqPost && globalMegaTestHooks.onHttpReqPost(HTTPREQPTR)) return; } // allow the test client to confirm raid/nonraid is happening, or adjust the parameters of a raid download for smaller chunks etc #define DEBUG_TEST_HOOK_RAIDBUFFERMANAGER_SETISRAID(RAIDBUFMGRPTR) { if (globalMegaTestHooks.onSetIsRaid) globalMegaTestHooks.onSetIsRaid(RAIDBUFMGRPTR); } // watch out for upload issues #define DEBUG_TEST_HOOK_UPLOADCHUNK_FAILED(X) { if (globalMegaTestHooks.onUploadChunkFailed) globalMegaTestHooks.onUploadChunkFailed(X); } // option to simulate something after an uploaded chunk #define DEBUG_TEST_HOOK_UPLOADCHUNK_SUCCEEDED(transfer, committer) { \ if (globalMegaTestHooks.onUploadChunkSucceeded) \ { \ if (!globalMegaTestHooks.onUploadChunkSucceeded(transfer, committer)) return; \ }} // get transfer progress completed updates #define DEBUG_TEST_HOOK_ON_PROGRESS_COMPLETED_UPDATE(p) \ { \ if (globalMegaTestHooks.onProgressCompletedUpdate) \ { \ globalMegaTestHooks.onProgressCompletedUpdate(p); \ } \ } // get transfer progress contiguous updates #define DEBUG_TEST_HOOK_ON_PROGRESS_CONTIGUOUS_UPDATE(p) \ { \ if (globalMegaTestHooks.onProgressContiguousUpdate) \ { \ globalMegaTestHooks.onProgressContiguousUpdate(p); \ } \ } // get reports counts updates #define DEBUG_TEST_HOOK_ON_TRANSFER_REPORT_PROGRESS(p, fp, pb) \ { \ if (globalMegaTestHooks.onTransferReportProgress) \ { \ globalMegaTestHooks.onTransferReportProgress(p, fp, pb); \ } \ } // watch out for download issues #define DEBUG_TEST_HOOK_DOWNLOAD_FAILED(X) { if (globalMegaTestHooks.onDownloadFailed) globalMegaTestHooks.onDownloadFailed(X); } // limit max request size for TransferBufferManager (non-raid) or new RaidReq #define DEBUG_TEST_HOOK_LIMIT_MAX_REQ_SIZE(X) { if (globalMegaTestHooks.onLimitMaxReqSize) globalMegaTestHooks.onLimitMaxReqSize(X); } // Ensure new RaidReq number of connections is taken from the client's number of connections #define DEBUG_TEST_HOOK_NUMBER_OF_CONNECTIONS(connectionsInOutVar, clientNumberOfConnections) { if (globalMegaTestHooks.onHookNumberOfConnections) globalMegaTestHooks.onHookNumberOfConnections(connectionsInOutVar, clientNumberOfConnections); } // For CommandGetFile, so a raided file can request the unraided copy. #define DEBUG_TEST_HOOK_DOWNLOAD_REQUEST_SINGLEURL(singleUrlFlag) \ { \ if (globalMegaTestHooks.onHookDownloadRequestSingleUrl) \ globalMegaTestHooks.onHookDownloadRequestSingleUrl(singleUrlFlag); \ } #define DEBUG_TEST_HOOK_RESET_TRANSFER_LASTACCESSTIME(lastAccessTime) \ { \ if (globalMegaTestHooks.onHookResetTransferLastAccessTime) \ globalMegaTestHooks.onHookResetTransferLastAccessTime(lastAccessTime); \ } #define DEBUG_TEST_HOOK_INTERCEPT_LOCKLESS_CS_REQUEST(pendingLocklessCS) \ { \ if (globalMegaTestHooks.interceptLocklessCSRequest) \ globalMegaTestHooks.interceptLocklessCSRequest(pendingLocklessCS); \ } #define DEBUG_TEST_HOOK_HTTPREQ_FINISH(HTTPSTATUS, CURLCODE, FAILED) \ { \ if (globalMegaTestHooks.onHttpReqFinish) \ globalMegaTestHooks.onHttpReqFinish((HTTPSTATUS), (CURLCODE), (FAILED)); \ } #define DEBUG_TEST_HOOK_FILEFINGERPRINT_USE_LEGACY_BUGGY_SPARSE_CRC(FLAG) \ { \ if (globalMegaTestHooks.onHookFileFingerprintUseLegacyBuggySparseCrc) \ { \ globalMegaTestHooks.onHookFileFingerprintUseLegacyBuggySparseCrc((FLAG)); \ } \ } #define DEBUG_TEST_HOOK_DEVICE_ID(DEVICEID) \ { \ if (globalMegaTestHooks.onHookDeviceId) \ { \ globalMegaTestHooks.onHookDeviceId((DEVICEID)); \ } \ } #define DEBUG_TEST_HOOK_HASHCASH_CALCULATION_STARTED \ { \ if (globalMegaTestHooks.onHashcashCalculationStarted) \ { \ globalMegaTestHooks.onHashcashCalculationStarted(); \ } \ } #define DEBUG_TEST_HOOK_MAC_GENERATION_CHUNK_READ(OFFSET) \ { \ if (globalMegaTestHooks.onMacGenerationChunkRead) \ { \ globalMegaTestHooks.onMacGenerationChunkRead((OFFSET)); \ } \ } #else #define DEBUG_TEST_HOOK_HTTPREQ_POST(x) #define DEBUG_TEST_HOOK_RAIDBUFFERMANAGER_SETISRAID(x) #define DEBUG_TEST_HOOK_UPLOADCHUNK_FAILED(X) #define DEBUG_TEST_HOOK_UPLOADCHUNK_SUCCEEDED(transfer, committer) #define DEBUG_TEST_HOOK_DOWNLOAD_FAILED(X) #define DEBUG_TEST_HOOK_LIMIT_MAX_REQ_SIZE(X) #define DEBUG_TEST_HOOK_NUMBER_OF_CONNECTIONS(connectionsInOutVar, clientNumberOfConnections) #define DEBUG_TEST_HOOK_ON_PROGRESS_COMPLETED_UPDATE(p) #define DEBUG_TEST_HOOK_ON_PROGRESS_CONTIGUOUS_UPDATE(p) #define DEBUG_TEST_HOOK_ON_TRANSFER_REPORT_PROGRESS(p, fp, pb) #define DEBUG_TEST_HOOK_DOWNLOAD_REQUEST_SINGLEURL(singleUrlFlag) #define DEBUG_TEST_HOOK_RESET_TRANSFER_LASTACCESSTIME(lastAccessTime) #define DEBUG_TEST_HOOK_INTERCEPT_LOCKLESS_CS_REQUEST(pendingLocklessCS) #define DEBUG_TEST_HOOK_HTTPREQ_FINISH(HTTPSTATUS, CURLCODE, FAILED) #define DEBUG_TEST_HOOK_FILEFINGERPRINT_USE_LEGACY_BUGGY_SPARSE_CRC(FLAG) #define DEBUG_TEST_HOOK_DEVICE_ID(DEVICEID) #define DEBUG_TEST_HOOK_HASHCASH_CALCULATION_STARTED #define DEBUG_TEST_HOOK_MAC_GENERATION_CHUNK_READ(OFFSET) #endif } // namespace #endif sdk-10.11.0/include/mega/textchat.h000066400000000000000000000336041516266226600170060ustar00rootroot00000000000000/** * @file mega/textchat.h * @brief Class for manipulating text chat * * (c) 2013-2022 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef TEXT_CHAT_H #define TEXT_CHAT_H 1 #include "types.h" #include #include #include namespace mega { using std::map; using std::vector; #ifdef ENABLE_CHAT typedef enum { PRIV_UNKNOWN = -2, PRIV_RM = -1, PRIV_RO = 0, PRIV_STANDARD = 2, PRIV_MODERATOR = 3 } privilege_t; typedef pair userpriv_pair; typedef vector< userpriv_pair > userpriv_vector; typedef map > attachments_map; class ScheduledFlags { public: typedef enum { FLAGS_SEND_EMAILS = 0, // API will send out calendar emails for this meeting if it's enabled FLAGS_SIZE = 1, // size in bits of flags bitmask } scheduled_flags_t; // 3 Bytes (maximum) static constexpr unsigned int schedEmptyFlags = 0; ScheduledFlags() = default; ScheduledFlags (const unsigned long numericValue) : mFlags(numericValue) {}; ScheduledFlags (const ScheduledFlags* flags) : mFlags(flags ? flags->getNumericValue() : schedEmptyFlags) {}; virtual ~ScheduledFlags() = default; ScheduledFlags(const ScheduledFlags&) = delete; ScheduledFlags(const ScheduledFlags&&) = delete; ScheduledFlags& operator=(const ScheduledFlags&) = delete; ScheduledFlags& operator=(const ScheduledFlags&&) = delete; void reset() { mFlags.reset(); } void setSendEmails(const bool enabled) { mFlags[FLAGS_SEND_EMAILS] = enabled; } void importFlagsValue(const unsigned long val) { mFlags = val; } bool sendEmails() const { return mFlags[FLAGS_SEND_EMAILS]; } unsigned long getNumericValue() const { return mFlags.to_ulong(); } bool isEmpty() const { return mFlags.none(); } bool equalTo(const ScheduledFlags* sf) const { return sf && (getNumericValue() == sf->getNumericValue()); } virtual ScheduledFlags* copy() const { return new ScheduledFlags(this); } bool serialize(string& out) const; static ScheduledFlags* unserialize(const string& in); protected: std::bitset mFlags = schedEmptyFlags; }; class ScheduledRules { public: typedef enum { FREQ_INVALID = -1, FREQ_DAILY = 0, FREQ_WEEKLY = 1, FREQ_MONTHLY = 2, } freq_type_t; // just for SDK core usage (matching mega::SmallInt{Vector|Map}) typedef vector rules_vector; typedef multimap rules_map; constexpr static int INTERVAL_INVALID = 0; ScheduledRules(const int freq, const int interval = INTERVAL_INVALID, const m_time_t until = mega_invalid_timestamp, const rules_vector* byWeekDay = nullptr, const rules_vector* byMonthDay = nullptr, const rules_map* byMonthWeekDay = nullptr); ScheduledRules(const ScheduledRules* rules); virtual ~ScheduledRules() = default; ScheduledRules(const ScheduledRules&) = delete; ScheduledRules(const ScheduledRules&&) = delete; ScheduledRules& operator=(const ScheduledRules&) = delete; ScheduledRules& operator=(const ScheduledRules&&) = delete; ScheduledRules::freq_type_t freq() const { return mFreq; } int interval() const { return mInterval; } m_time_t until() const { return mUntil;} const rules_vector* byWeekDay() const { return mByWeekDay.get(); } const rules_vector* byMonthDay() const { return mByMonthDay.get(); } const rules_map* byMonthWeekDay() const { return mByMonthWeekDay.get(); } virtual ScheduledRules* copy() const { return new ScheduledRules(this); } bool equalTo(const ScheduledRules*) const; const char* freqToString() const; static int stringToFreq (const char* freq); bool isValid() const { return isValidFreq(mFreq); } static bool isValidFreq(const int freq) { return (freq >= FREQ_DAILY && freq <= FREQ_MONTHLY); } static bool isValidInterval(const int interval) { return interval > INTERVAL_INVALID; } static bool isValidUntil(const m_time_t interval) { return interval > mega_invalid_timestamp; } bool serialize(string& out) const; static ScheduledRules* unserialize(const string& in); protected: // scheduled meeting frequency (DAILY | WEEKLY | MONTHLY), this is used in conjunction with interval to allow for a repeatable skips in the event timeline freq_type_t mFreq; // repetition interval in relation to the frequency int mInterval = 0; // specifies when the repetitions should end (unix timestamp) m_time_t mUntil = mega_invalid_timestamp; // allows us to specify that an event will only occur on given week day/s std::unique_ptr mByWeekDay; // allows us to specify that an event will only occur on a given day/s of the month std::unique_ptr mByMonthDay; // allows us to specify that an event will only occurs on a specific weekday offset of the month. For example, every 2nd Sunday of each month std::unique_ptr mByMonthWeekDay; }; class ScheduledMeeting { public: ScheduledMeeting(const handle chatid, const string& timezone, const m_time_t startDateTime, const m_time_t endDateTime, const string& title, const string& description, const handle organizerUserId, const handle schedId = UNDEF, const handle parentSchedId = UNDEF, const int cancelled = -1, const string& attributes = std::string(), const m_time_t overrides = mega_invalid_timestamp, const ScheduledFlags* flags = nullptr, const ScheduledRules* rules = nullptr); ScheduledMeeting(const ScheduledMeeting *scheduledMeeting); virtual ~ScheduledMeeting() = default; ScheduledMeeting(const ScheduledMeeting&) = delete; ScheduledMeeting(const ScheduledMeeting&&) = delete; ScheduledMeeting& operator=(const ScheduledMeeting&) = delete; ScheduledMeeting& operator=(const ScheduledMeeting&&) = delete; void setSchedId(const handle schedId) { mSchedId = schedId; } void setChatid(const handle chatid) { mChatid = chatid; } handle chatid() const { return mChatid; } handle organizerUserid() const { return mOrganizerUserId; } handle schedId() const { return mSchedId; } handle parentSchedId() const { return mParentSchedId; } const std::string &timezone() const { return mTimezone; } m_time_t startDateTime() const { return mStartDateTime; } m_time_t endDateTime() const { return mEndDateTime; } const std::string &title() const { return mTitle; } const std::string &description() const { return mDescription; } const std::string &attributes() const { return mAttributes; } m_time_t overrides() const { return mOverrides; } int cancelled() const { return mCancelled; } virtual const ScheduledFlags* flags() const { return mFlags.get(); } virtual const ScheduledRules* rules() const { return mRules.get(); } virtual ScheduledMeeting* copy() const { return new ScheduledMeeting(this); } bool equalTo(const ScheduledMeeting* sm) const; bool isValid() const; bool serialize(string& out) const; static ScheduledMeeting* unserialize(const std::string &in, const handle chatid); private: handle mChatid; handle mOrganizerUserId; handle mSchedId; handle mParentSchedId; std::string mTimezone; m_time_t mStartDateTime; // (unix timestamp) m_time_t mEndDateTime; // (unix timestamp) std::string mTitle; std::string mDescription; std::string mAttributes; // attributes to store any additional data m_time_t mOverrides; // start dateTime of the original meeting series event to be replaced (unix timestamp) int mCancelled; std::unique_ptr mFlags; // flags bitmask (used to store additional boolean settings as a bitmask) std::unique_ptr mRules; }; class TextChat : public Cacheable { public: enum { FLAG_OFFSET_ARCHIVE = 0 }; private: handle id = UNDEF; privilege_t priv = PRIV_UNKNOWN; int shard = -1; std::unique_ptr userpriv; bool group = false; string title; // byte array string unifiedKey; // byte array handle ou = UNDEF; m_time_t ts = 0; // creation time attachments_map attachedNodes; bool meeting = false; // chat is meeting room byte chatOptions = 0; // each chat option is represented in 1 bit (check ChatOptions struct at types.h) // maps a scheduled meeting id to a scheduled meeting // a scheduled meetings allows the user to specify an event that will occur in the future (check ScheduledMeeting class documentation) map> mScheduledMeetings; // list of scheduled meetings changed handle_set mSchedMeetingsChanged; // vector of scheduled meeting occurrences that needs to be notified std::vector> mUpdatedOcurrences; bool mPublicChat = false; // whether the chat is public or private byte flags = 0; // currently only used for "archive" flag at first bit void deleteSchedMeeting(const handle sm) { mScheduledMeetings.erase(sm); mSchedMeetingsChanged.insert(sm); } int tag = -1; // source tag, to identify own changes public: TextChat(const bool publicChat); ~TextChat() = default; bool serialize(string *d) const override; static TextChat* unserialize(class MegaClient *client, string *d); void setChatId(handle newId); handle getChatId() const; void setOwnPrivileges(privilege_t p); privilege_t getOwnPrivileges() const; void setShard(int sh); int getShard() const; void addUserPrivileges(handle uid, privilege_t p); bool updateUserPrivileges(handle uid, privilege_t p); bool removeUserPrivileges(handle uid); void setUserPrivileges(userpriv_vector* pvs); const userpriv_vector* getUserPrivileges() const; void setGroup(bool g); bool getGroup() const; void setTitle(const string& t); const string& getTitle() const; void setUnifiedKey(const string& uk); const string& getUnifiedKey() const; void setOwnUser(handle u); handle getOwnUser() const; void setTs(m_time_t t); m_time_t getTs() const; const attachments_map& getAttachments() const; handle_set getUsersOfAttachment(handle a) const; bool isUserOfAttachment(handle a, handle uid) const; void addUserForAttachment(handle a, handle uid); void setMeeting(bool m); bool getMeeting() const; byte getChatOptions() const; bool hasScheduledMeeting(handle smid) const; const handle_set& getSchedMeetingsChanged() const; void clearSchedMeetingsChanged(); const vector>& getUpdatedOcurrences() const; void setTag(int newTag); int getTag() const; void resetTag(); struct { bool attachments : 1; bool flags : 1; bool mode : 1; bool options : 1; bool schedOcurrReplace : 1; bool schedOcurrAppend : 1; } changed = {}; // return false if failed bool setNodeUserAccess(handle h, handle uh, bool revoke = false); bool addOrUpdateChatOptions(int speakRequest = -1, int waitingRoom = -1, int openInvite = -1); bool setFlag(bool value, uint8_t offset = 0xFF); bool setFlags(byte newFlags); bool isFlagSet(uint8_t offset) const; void clearUpdatedSchedMeetingOccurrences(); void addUpdatedSchedMeetingOccurrence(std::unique_ptr sm); ErrorCodes setMode(bool pubChat); bool publicChat() const; // add or update a scheduled meeting, SDK adquires the ownership of provided ScheduledMeeting bool addOrUpdateSchedMeeting(std::unique_ptr sm, bool notify = true); // add a scheduled meeting, SDK adquires the ownership of provided ScheduledMeeting bool addSchedMeeting(std::unique_ptr sm, bool notify = true); // removes a scheduled meeting given a scheduled meeting id bool removeSchedMeeting(handle schedId); // removes all scheduled meetings in provided list as param void removeSchedMeetingsList(const handle_set& schedList); // removes all scheduled meeting whose parent scheduled meeting id, is equal to parentSchedId provided // returns handle_set with the meeting id of the removed children handle_set removeChildSchedMeetings(handle parentSchedId); // updates scheduled meeting, SDK adquires the ownership of provided ScheduledMeeting bool updateSchedMeeting(std::unique_ptr sm); // returns a scheduled meeting (if any) whose schedId is equal to provided id. Otherwise returns nullptr const ScheduledMeeting* getSchedMeetingById(handle meetingID) const; // returns a map of schedId to ScheduledMeeting const map>& getSchedMeetings() const; }; #endif } //namespace #endif sdk-10.11.0/include/mega/thread.h000066400000000000000000000022661516266226600164310ustar00rootroot00000000000000/** * @file mega/thread.h * @brief Generic thread/mutex handling * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ #ifndef MEGA_THREAD_H #define MEGA_THREAD_H 1 namespace mega { class Thread { public: virtual void start(void *(*start_routine)(void*), void *parameter) = 0; virtual void join() = 0; virtual bool isCurrentThread() = 0; }; class Semaphore { public: virtual void release() = 0; virtual void wait() = 0; virtual int timedwait(int milliseconds) = 0; }; } // namespace #endif sdk-10.11.0/include/mega/thread/000077500000000000000000000000001516266226600162525ustar00rootroot00000000000000sdk-10.11.0/include/mega/thread/cppthread.h000066400000000000000000000032101516266226600203710ustar00rootroot00000000000000/** * @file mega/posix/megawaiter.h * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ #if defined USE_CPPTHREAD #ifndef THREAD_CLASS #define THREAD_CLASS CppThread #define SEMAPHORE_CLASS CppSemaphore #include "mega/thread.h" #include #include #include namespace mega { class CppThread : public Thread { public: CppThread(); virtual void start(void *(*start_routine)(void*), void *parameter); virtual void join(); virtual bool isCurrentThread(); virtual ~CppThread(); static unsigned long long currentThreadId(); protected: std::thread *thread; }; class CppSemaphore : public Semaphore { public: CppSemaphore(); virtual void release(); virtual void wait(); virtual int timedwait(int milliseconds); virtual ~CppSemaphore(); protected: std::mutex mtx; unsigned int count; std::condition_variable cv; }; } // namespace #endif #endif sdk-10.11.0/include/mega/thread/posixthread.h000066400000000000000000000035011516266226600207540ustar00rootroot00000000000000/** * @file mega/posix/megawaiter.h * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ // platform dependent constants #if defined(__ANDROID__) && !defined(HAVE_SDK_CONFIG_H) #include "mega/config-android.h" #else #ifndef MEGA_GENERATED_CONFIG_H #include "mega/config.h" #define MEGA_GENERATED_CONFIG_H #endif #endif #ifdef USE_PTHREAD #ifndef THREAD_CLASS #define THREAD_CLASS PosixThread #define SEMAPHORE_CLASS PosixSemaphore #include "mega/thread.h" #include #include namespace mega { class PosixThread : public Thread { public: PosixThread(); void start(void *(*start_routine)(void*), void *parameter); void join(); bool isCurrentThread(); virtual ~PosixThread(); static unsigned long long currentThreadId(); protected: pthread_t *thread; }; class PosixSemaphore : public Semaphore { public: PosixSemaphore(); virtual void release(); virtual void wait(); virtual int timedwait(int milliseconds); virtual ~PosixSemaphore(); protected: unsigned int count; pthread_mutex_t mtx; pthread_cond_t cv; }; } // namespace #endif #endif sdk-10.11.0/include/mega/tlv.h000066400000000000000000000134271516266226600157700ustar00rootroot00000000000000#ifndef MEGA_TLV_H #define MEGA_TLV_H #include #include #include #include namespace mega { class PrnGen; class SymmCipher; namespace tlv { /** * @brief Extract decrypted records from encrypted data * * @param container Binary byte array representing the encrypted data * @param key Key to decrypt the data * * @return Decrypted records */ std::unique_ptr> containerToRecords(const std::string& container, SymmCipher& key); /** * @brief Extract records from data * * @param container Binary byte array representing the encrypted data * * @return Records extracted from the received data * * @note Only used by MEGAchat implementation */ std::unique_ptr> containerToRecords(const std::string& container); /** * @brief Create container with encrypted data from decrypted records * * @param records Decrypted records * @param rng Random number generator used in data encryption * @param key Key to encrypt the data * * @return Encrypted data */ std::unique_ptr recordsToContainer(std::map&& records, PrnGen& rng, SymmCipher& key); /** * @brief Create container with data from received records * * @param records Records to pack into returned data * * @return Data packing the received records * * @note Only used by MEGAchat implementation */ std::unique_ptr recordsToContainer(std::map&& records); //========================================= // Old implementation // Direct use should be avoided in new code /** * @deprecated Please don't use this data type alias in new code. */ using TLV_map = std::map; enum encryptionsetting_t { AES_CCM_12_16 = 0x00, AES_CCM_10_16 = 0x01, AES_CCM_10_08 = 0x02, AES_GCM_12_16_BROKEN = 0x03, // Same as 0x00 (due to a legacy bug) AES_GCM_10_08_BROKEN = 0x04, // Same as 0x02 (due to a legacy bug) AES_GCM_12_16 = 0x10, AES_GCM_10_08 = 0x11 }; enum encryptionmode_t { AES_MODE_UNKNOWN, AES_MODE_CCM, AES_MODE_GCM }; /** * @deprecated Please don't use this class in new code. Instead use the equivalent * functions that directly convert between std data types. */ class TLVstore { public: /** * @brief Build a TLV object with records from an encrypted container * * @param data Binary byte array representing the encrypted container * @param key Master key to decrypt the container * * @return A new TLVstore object. You take the ownership of the object. */ static TLVstore* containerToTLVrecords(const std::string* data, SymmCipher* key); /** * @brief Build a TLV object with records from a container * * @param data Binary byte array representing the TLV records * * @return A new TLVstore object. You take the ownership of the object. */ static TLVstore* containerToTLVrecords(const std::string* data); /** * @brief Convert the TLV records into an encrypted byte array * * @param key Master key to encrypt the container * @param encSetting Block encryption mode to be used by AES * * @return A new string holding the encrypted byte array. You take the ownership of the string. */ std::string* tlvRecordsToContainer(PrnGen& rng, SymmCipher* key, encryptionsetting_t encSetting = AES_GCM_12_16); /** * @brief Convert the TLV records into a byte array * * @return A new string holding the byte array. You take the ownership of the string. */ std::string* tlvRecordsToContainer(); /** * @brief get Get the value for a given key * * In case the type is found, it will update value parameter and return true. * In case the type is not found, it will return false and value will not be changed. * * @param type Type of the value (without scope nor non-historic modifiers). * @param value Set to corresponding value if type was found. * * @return True if type was found, false otherwise. */ bool get(const std::string& type, std::string& value) const; /** * @brief Get a reference to the TLV_map associated to this TLVstore * * The TLVstore object retains the ownership of the returned object. It will be * valid until this TLVstore object is deleted. * * @return The TLV_map associated to this TLVstore */ const TLV_map* getMap() const; TLV_map moveMap(); /** * @brief Get a list of the keys contained in the TLV * * You take ownership of the returned value. * * @return A new vector with the keys included in the TLV */ std::vector* getKeys() const; /** * @brief Add a new record to the container * * @param type Type for the new value (without scope nor non-historic modifiers). * @param value New value to be set. */ void set(const std::string& type, const std::string& value); /** * @brief Replace all records in the container * * @param records New records to be set. */ void set(TLV_map&& records); /** * @brief Remove a record from the container * * @param type Type for the value to be removed (without scope nor non-historic modifiers). */ void reset(const std::string& type); size_t size() const; private: static unsigned getTaglen(int mode); static unsigned getIvlen(int mode); static encryptionmode_t getMode(int mode); TLV_map tlv; }; } // namespace tlv } // namespace mega #endif // MEGA_TLV_H sdk-10.11.0/include/mega/totp.h000066400000000000000000000137121516266226600161460ustar00rootroot00000000000000/** * @file * @brief Header defining utilities around TOTP token generation */ #ifndef INCLUDE_MEGA_TOTP_H_ #define INCLUDE_MEGA_TOTP_H_ #include #include #include #include #include namespace mega::totp { using namespace std::chrono_literals; /** * @brief Available algorithms for the hashing performed during otp generation. */ enum class HashAlgorithm { SHA1, SHA256, SHA512, }; constexpr unsigned NDIGITS_IN_MAX_INT32{10}; // int32_t::max -> 2147483647 constexpr unsigned MIN_ALLOWED_DIGITS_TOTP{6}; constexpr unsigned MAX_ALLOWED_DIGITS_TOTP{NDIGITS_IN_MAX_INT32}; constexpr unsigned DEF_NDIGITS{6}; constexpr auto DEF_EXP_TIME{30s}; constexpr HashAlgorithm DEF_ALG{HashAlgorithm::SHA1}; constexpr std::optional charTohashAlgorithm(const std::string_view alg) { if (alg == "sha1") return HashAlgorithm::SHA1; if (alg == "sha256") return HashAlgorithm::SHA256; if (alg == "sha512") return HashAlgorithm::SHA512; return std::nullopt; } constexpr std::string_view hashAlgorithmToStrView(const HashAlgorithm alg) { switch (alg) { case HashAlgorithm::SHA1: return "sha1"; case HashAlgorithm::SHA256: return "sha256"; case HashAlgorithm::SHA512: return "sha512"; } assert(false); return ""; } enum : uint32_t { INVALID_TOTP_SHARED_SECRET = 0, INVALID_TOTP_NDIGITS = 1, INVALID_TOTP_EXPT = 2, INVALID_TOTP_ALG = 3, NUM_TOTP_ERRORS = 4, }; using TotpValidationErrors = std::bitset; constexpr bool isValidNDigits(const unsigned nDigits) { return nDigits >= MIN_ALLOWED_DIGITS_TOTP && nDigits <= MAX_ALLOWED_DIGITS_TOTP; } /** * @brief Check that all the characters in the given string are contained in * "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" (base 32 specified at RFC 4648). Lowercase and uppercase are * both allowed. Padding characters ("=") are allowed only if they are placed at the end. */ bool isValidBase32Key(const std::string_view base32Key); /** * @brief Returns the number of valid Base32 characters in the input key */ size_t numberOfValidChars(const std::string_view base32Key); /** * @brief Validates the fields of a TOTP. * * This function checks the validity of various TOTP, including: * - Shared secret (`base32Key`): Must be a non-empty, valid Base32-encoded string. * - Number of digits (`nDigits`): Must be within the allowed range. * - Expiry time (`exptime`): Must be greater than zero. * - Hash algorithm (`alg`): Must be an expetected hashing algorithm. * * If any of these fields are invalid, the corresponding error flag is set in the returned * `TotpValidationErrors` bitset. * * @param base32Key The optional Base32-encoded shared secret key. * @param nDigits The optional number of digits for the TOTP code. * @param exptime The optional expiration time in seconds. * @param alg The optional hashing algorithm identifier. * @return TotpValidationErrors A bitmask containing validation errors, if any. */ TotpValidationErrors validateFields(const std::optional base32Key, const std::optional nDigits, const std::optional exptime, const std::optional alg); /** * @class TotpParameters * @brief Helper POD to group parameters for totp generation that are usually controlled by the * users */ struct TotpParameters { TotpParameters() = default; std::string base32Key; unsigned nDigits; std::chrono::seconds expirationTime; HashAlgorithm hashAlgo; }; /** * @brief Generates a TOTP following RFC-6238 (https://www.rfc-editor.org/rfc/rfc6238) * * @param base32Key The shared secret key. * Allowed characters (specified at RFC-4648 https://www.rfc-editor.org/rfc/rfc4648#section-6): * "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" * @param nDigits Number of digits expected in the output token. Required to be in [6, 10] * @param timeStep To count steps since t0. Required to be greater than 0 * @param t0 Time origin to count steps * @param tEval The time in which the totp wants to be calculated (usually now). Required to be * greater or equal than t0 * @param hashAlgo The algorithm to use for the hashing step * @returns A pair with: * - A string with nDigits characters representing the generated totp. * - The time remaining until the token becomes invalid * @note If the input parameters are not valid, the returned token will be an empty string. In that * case, the value of the returned expiration time isn't valid. */ std::pair generateTOTP( const std::string& base32Key, const unsigned nDigits = DEF_NDIGITS, const std::chrono::seconds timeStep = DEF_EXP_TIME, const HashAlgorithm hashAlgo = DEF_ALG, const std::chrono::system_clock::time_point t0 = {}, const std::chrono::system_clock::time_point tEval = std::chrono::system_clock::now()); /** * @brief Overloaded version that takes the first 4 parameters as a TotpParameters object */ std::pair generateTOTP( const TotpParameters& totpParams, const std::chrono::system_clock::time_point t0 = {}, const std::chrono::system_clock::time_point tEval = std::chrono::system_clock::now()); /** * @brief This overload accepts directly the number of seconds since the origin (t0 in the previous * version), i.e. `timeDelta`. This is very handy for testing purposes where `tEval` cannot be * represented as a time_point due to overflow issues. */ std::pair generateTOTP(const std::string& base32Key, const std::chrono::seconds timeDelta, const unsigned nDigits = DEF_NDIGITS, const std::chrono::seconds timeStep = DEF_EXP_TIME, const HashAlgorithm hashAlgo = DEF_ALG); } #endif // INCLUDE_MEGA_TOTP_H_ sdk-10.11.0/include/mega/traits.h000066400000000000000000000006241516266226600164640ustar00rootroot00000000000000#pragma once // For std::size_t. #include namespace mega { template struct ResizeTraits { static void resize(T& instance, std::size_t newSize) { instance.resize(newSize); } }; // ResizeTraits template struct SizeTraits { static std::size_t size(const T& instance) { return instance.size(); } }; // SizeTraits } // mega sdk-10.11.0/include/mega/transfer.h000066400000000000000000001331471516266226600170110ustar00rootroot00000000000000/** * @file mega/transfer.h * @brief pending/active up/download ordered by file fingerprint * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_TRANSFER_H #define MEGA_TRANSFER_H 1 #include "backofftimer.h" #include "command.h" #include "filefingerprint.h" #include "http.h" #include "raid.h" #include namespace mega { using namespace std::literals; // helper class for categorizing transfers for upload/download queues struct TransferCategory { direction_t direction = NONE; filesizetype_t sizetype = LARGEFILE; TransferCategory(direction_t d, filesizetype_t s); TransferCategory(Transfer*); unsigned index(); unsigned directionIndex(); }; class TransferDbCommitter; #ifdef ENABLE_SYNC class TransferBackstop { // A class to help track transfers that completed but haven't had // putnodes sent yet, and may be abandoned by the owning sync. If // that happens, we still need to inform the app about the transfer final state. mutex m; // map by transfer tag map> pendingPutnodes; public: void remember(int tag, shared_ptr wp) { lock_guard g(m); pendingPutnodes[tag] = std::move(wp); } void forget(int tag) { lock_guard g(m); pendingPutnodes.erase(tag); } vector> getAbandoned() { lock_guard g(m); vector> v; v.reserve(pendingPutnodes.size()); for (auto i = pendingPutnodes.begin(); i != pendingPutnodes.end(); ) { if (i->second.use_count() == 1) { v.push_back(i->second); i = pendingPutnodes.erase(i); } else ++i; } return v; } }; #endif // pending/active up/download ordered by file fingerprint (size - mtime - sparse CRC) struct MEGA_API Transfer : public FileFingerprint { // PUT or GET direction_t type; // transfer slot this transfer is active in (can be NULL if still queued) TransferSlot* slot; // files belonging to this transfer - transfer terminates upon its last // file is removed file_list files; shared_ptr downloadDistributor; // failures/backoff unsigned failcount; BackoffTimerTracked bt; // representative local filename for this transfer LocalPath localfilename; // progress completed m_off_t progresscompleted; m_off_t pos; // constructed from transferkey and the file's mac data, on upload completion FileNodeKey filekey; // CTR mode IV int64_t ctriv; // meta MAC int64_t metamac; // file crypto key and shared cipher std::array transferkey; // returns a pointer to MegaClient::tmptransfercipher setting its key to the transfer // tmptransfercipher key will change: to be used right away: this is not a dedicated SymmCipher for this transfer! SymmCipher *transfercipher(); chunkmac_map chunkmacs; // upload handle for file attribute attachment (only set if file attribute queued) UploadHandle uploadhandle; // When resuming on startup, we need to be sure we are downloading the same file as before (FileFingerprint match is not a guarantee) NodeHandle downloadFileHandle; // position in transfers[type] transfer_multimap::iterator transfers_it; // upload result unique_ptr ultoken; // backlink to base MegaClient* client; int tag; void setProgresscompleted(const m_off_t p, const bool append = false); // signal failure. Either the transfer's slot or the transfer itself (including slot) will be deleted. void failed(const Error&, TransferDbCommitter&, dstime = 0); // signal completion void complete(TransferDbCommitter&); // execute completion void completefiles(); // remove file from transfer including in cache void removeTransferFile(error, File* f, TransferDbCommitter* committer); void removeCancelledTransferFiles(TransferDbCommitter* committer); void removeAndDeleteSelf(transferstate_t finalState); // previous wrong fingerprint FileFingerprint badfp; // transfer state bool finished; // temp URLs for upload/download data. They can be cached. For uploads, a new url means any previously uploaded data is abandoned. // downloads can have 6 for raid, 1 for non-raid. Uploads always have 1 std::vector tempurls; uint8_t discardedTempUrlsSize{}; static constexpr m_time_t TEMPURL_TIMEOUT_TS{172500}; // context of the async fopen operation unique_ptr asyncopencontext; // timestamp of the start of the transfer m_time_t lastaccesstime; // priority of the transfer uint64_t priority; // state of the transfer transferstate_t state; bool skipserialization; Transfer(MegaClient*, direction_t); virtual ~Transfer(); // serialize the Transfer object bool serialize(string*) const override; // unserialize a Transfer and add it to the transfer map static Transfer* unserialize(MegaClient *, string*, transfer_multimap *); // examine a file on disk for video/audio attributes to attach to the file, on upload/download void addAnyMissingMediaFileAttributes(Node* node, LocalPath& localpath); // whether the Transfer needs to remove itself from the list it's in (for quick shutdown we can skip) bool mOptimizedDelete = false; // whether it is a Transfer for support (i.e., an upload for the Support team) bool isForSupport() const; // Add stats for this transfer to the MEGAclient. The client must be valid at this point. bool addTransferStats(); void collectAndPrintTransferStatsIfLimitReached(); void discardTempUrlsIfNoDataDownloadedOrTimeoutReached(const direction_t transferDirection, const m_time_t currentTime); void adjustNonRaidedProgressIfNowIsRaided(); private: FileDistributor::TargetNameExistsResolution toTargetNameExistsResolution(CollisionResolution resolution); }; struct LazyEraseTransferPtr { // This class enables us to relatively quickly and efficiently delete many items from the middle of std::deque // By being the class actualy stored in a mega::deque_with_lazy_bulk_erase. // Such builk deletion is done by marking the ones to delete, and finally performing those as a single remove_if. Transfer* transfer; uint64_t preErasurePriority = 0; bool erased = false; explicit LazyEraseTransferPtr(Transfer* t) : transfer(t) {} operator Transfer*&() { return transfer; } void erase() { preErasurePriority = transfer->priority; transfer = nullptr; erased = true; } bool isErased() const { return erased; } bool operator==(const LazyEraseTransferPtr& e) { return transfer && transfer == e.transfer; } }; class MEGA_API TransferList { public: static const uint64_t PRIORITY_START = 0x0000800000000000ull; static const uint64_t PRIORITY_STEP = 0x0000000000010000ull; typedef deque_with_lazy_bulk_erase transfer_list; TransferList(); void addtransfer(Transfer* transfer, TransferDbCommitter&, bool startFirst = false); void removetransfer(Transfer *transfer); void movetransfer(Transfer *transfer, Transfer *prevTransfer, TransferDbCommitter& committer); void movetransfer(Transfer *transfer, unsigned int position, TransferDbCommitter& committer); void movetransfer(Transfer *transfer, transfer_list::iterator dstit, TransferDbCommitter&); void movetransfer(transfer_list::iterator it, transfer_list::iterator dstit, TransferDbCommitter&); void movetofirst(Transfer *transfer, TransferDbCommitter& committer); void movetofirst(transfer_list::iterator it, TransferDbCommitter& committer); void movetolast(Transfer *transfer, TransferDbCommitter& committer); void movetolast(transfer_list::iterator it, TransferDbCommitter& committer); void moveup(Transfer *transfer, TransferDbCommitter& committer); void moveup(transfer_list::iterator it, TransferDbCommitter& committer); void movedown(Transfer *transfer, TransferDbCommitter& committer); void movedown(transfer_list::iterator it, TransferDbCommitter& committer); error pause(Transfer *transfer, bool enable, TransferDbCommitter& committer); transfer_list::iterator begin(direction_t direction); transfer_list::iterator end(direction_t direction); bool getIterator(Transfer *transfer, transfer_list::iterator&, bool canHandleErasedElements = false); std::array, 6> nexttransfers(std::function& continuefunction, std::function& directionContinuefunction, TransferDbCommitter& committer); Transfer *transferat(direction_t direction, unsigned int position); std::array transfers; MegaClient *client; uint64_t currentpriority; private: void prepareIncreasePriority(Transfer *transfer, transfer_list::iterator srcit, transfer_list::iterator dstit, TransferDbCommitter& committer); void prepareDecreasePriority(Transfer *transfer, transfer_list::iterator it, transfer_list::iterator dstit); bool isReady(Transfer *transfer); }; /** * @brief Represents unused connection in a Raided Streaming Transfer. * This struct stores the connection number and the reason why is unused * * @note A bandwidth overquota error (509) cannot affect only a specific raided part, it applies to * the entire transfer, so it will be considered as an invalid error in this struct. * */ struct UnusedConn { public: /** * @enum unusedReason * @brief Represents the reason why unused connection has been set as unused. * - INVALID REASON: Connection failed due 509, which should be managed by retrying entire * transfer with a backoff (DirectReadSlot::retryOnError should not be called with this error) * - UN_NOT_ERR: Unused connection has not failed yet, so it can be switched by another * connection if it's needed * - UN_DEFINITIVE_ERR: Unused connection has failed with a definitive error, so it cannot be * reused anymore. * - UN_TEMPORARY_ERR: Unused connection has failed by a temporary error (i.e HttpStatus 200 and * CURLcode: 56), so it can be switched by another connection if it's needed */ enum UnusedReason { UN_INVALID = 0, UN_NOT_ERR = 1, UN_DEFINITIVE_ERR = 2, UN_TEMPORARY_ERR = 3, }; /** * @enum connReplacementReason * @brief Represents the reason why a connection has been replaced by unused one. * - CONN_SPEED_SLOWEST_PART replaced part is the slowest one in comparison with the rest of * parts * * - TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD replaced part is the slowest one and transfer mean * speed is below minstreamingrate or replaced part speed is below min speed threshold * * - ON_RAIDED_ERROR replaced part has failed with an HTTP error. */ enum ConnReplacementReason { CONN_SPEED_SLOWEST_PART = 0, TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD = 1, ON_RAIDED_ERROR = 2, ON_RECOVERABLE_RAIDED_ERROR = 3, }; /** * @brief Returns an unusedReason given a HTTP status code. * * @return An unusedReason given a HTTP status code. */ static UnusedReason getReasonFromHttpStatus(const int httpstatus, const unsigned errCode) { constexpr unsigned CURLE_OK_CODE = 0; switch (httpstatus) { case 200: return errCode == CURLE_OK_CODE ? UN_NOT_ERR : UN_TEMPORARY_ERR; case 509: assert(false); return UN_INVALID; default: return UN_DEFINITIVE_ERR; } } /** * @brief Gets the number of the unused connection. * * @return The number of the unused connection. */ size_t getNum() const; /** * @brief Checks if unused connection can be reused (mReason is not an error reason) * @return true if unused connection can be reused, otherwise returns false. */ bool CanBeReused() const; /** * @brief Sets the unused connection info. * * This method sets the unused state of the connection * * @param num The number of connection * @param reason The reason for marking the connection as unused * * @return true if the reason is valid and the connection state was updated successfully, * false if the reason is invalid. */ bool setUnused(const size_t num, const UnusedReason reason); /** * @brief Resets the unused connection state. */ void clear(); /** * @brief Checks if reason provided by param is a valid unusedReason. * * @return true if the reason is valid, otherwise returns false. */ static bool isValidUnusedReason(const UnusedReason reason) { return reason == UN_NOT_ERR || reason == UN_DEFINITIVE_ERR || reason == UN_TEMPORARY_ERR; } private: UnusedReason mReason{UN_NOT_ERR}; size_t mNum{}; }; /** * @brief Direct Read Slot: slot for DirectRead for connections i/o operations * * Slot for DirectRead. * Holds the HttpReq objects for each connection. * Loops over every HttpReq to process data and send it to the client. * * @see DirectRead * @see DirectReadNode * @see RaidBufferManager * @see HttpReq */ struct MEGA_API DirectReadSlot { public: /* ===================*\ * Constants * \* ===================*/ /** * @brief Default unused connection index */ static constexpr unsigned DEFAULT_UNUSED_CONN_INDEX = 0; /** * @brief Time interval to recalculate speed and mean speed values. * * This value is used to watch over DirectRead performance in case it should be retried. * * @see DirectReadSlot::watchOverDirectReadPerformance() */ static constexpr int MEAN_SPEED_INTERVAL_DS = 100; /** * @brief Min speed value allowed for the transfer. * * @see DirectReadSlot::watchOverDirectReadPerformance() */ static constexpr int MIN_BYTES_PER_SECOND = 1024 * 15; /** * @brief Time interval allowed without request/connections updates before retrying DirectRead operations (from a new DirectReadSlot). * * @see DirectReadNode::schedule() */ static constexpr int TIMEOUT_DS = 100; /** * @brief Timeout value for retrying a completed DirectRead in case it doesn't finish properly. * * Timeout value when all the requests are done, and everything regarding DirectRead is cleaned up, * before retrying DirectRead operations. * * @see DirectReadNode::schedule() */ static constexpr int TEMPURL_TIMEOUT_DS = 3000; /** * @brief Min chunk size allowed to be sent to the server/consumer. * * Chunk size values (allowed to be submitted to the transfer buffer) will be multiple of this value. * For RAID files (or for any multi-connection approach) this value is used to calculate minChunk value, * having this value divided by the number of connections an padded to RAIDSECTOR * * @see DirectReadSlot::mMaxChunkSize */ #if defined(__ANDROID__) || defined(USE_IOS) static constexpr unsigned MAX_DELIVERY_CHUNK = 16 * 1024 * 1024; #else static constexpr unsigned MAX_DELIVERY_CHUNK = 33 * 1024 * 1024; #endif /** * @brief Min chunk size for a given connection to be throughput-comparable to another connection. * * @see DirectReadSlot::searchAndDisconnectSlowestConnection() */ static constexpr unsigned DEFAULT_MIN_COMPARABLE_THROUGHPUT = MAX_DELIVERY_CHUNK; /** * @brief Max times a DirectReadSlot is allowed to switch the unused connection for another * connection detected as slow with respect to the others. * * @see DirectReadSlot::searchAndDisconnectSlowestConnection() */ static constexpr unsigned MAX_CONN_SWITCHES_SLOWEST_PART = 6; /** * @brief Max times a DirectReadSlot is allowed switch unused connection by another connection * detected slower than min threshold * * @see DirectReadSlot::detectAndManageSlowRaidedConns() and * DirectReadSlot::onLowSpeedRaidedTransfer() */ static constexpr unsigned MAX_CONN_SWITCHES_BELOW_SPEED_THRESHOLD = 1; /** * @brief Max times a DirectReadSlot is allowed switch unused connection by another connection * when a recoverable error is received (i.e HttpStatus 200 and CURLcode: 56) * */ static constexpr unsigned MAX_CONN_SWITCHES_RECOVERABLE_ERR = 1; /** * @brief Requests are sent in batch, and no connection is allowed to request the next chunk until the other connections have finished fetching their current one. * * Flag value for waiting for all the connections to finish their current chunk requests (with status REQ_INFLIGHT) * before any finished connection can be allowed again to request the next chunk. * Warning: This value is needed to be true in order to gain fairness. * It should only set to false under special conditions or testing purposes with a very fast link. * * @see DirectReadSlot::waitForPartsInFlight() */ static constexpr bool WAIT_FOR_PARTS_IN_FLIGHT = true; /** * @brief Relation of X Y multiplying factor to consider connection A to be faster than connection B * * @param first X factor for connection A -> X * ConnectionA_throughput * @param second Y factor for connection B -> Y * ConnectionY_throughput * * @see DirectReadSlot::mThroughput * @see DirectReadSlot::searchAndDisconnectSlowestConnection() */ static constexpr m_off_t SLOWEST_TO_FASTEST_THROUGHPUT_RATIO[2] { 4, 5 }; /** * @brief Max simultaneus slow raided parts of a DirectRead allowed * * @see DirectReadSlot::watchOverDirectReadPerformance */ static constexpr unsigned MAX_SIMULTANEOUS_SLOW_RAIDED_CONNS = 1; /** * @brief Timeout to reset connection switches counters. * * During a streaming transfer, we may perform RAIDED parts replacements due to different * reasons (failed part, slow mean speed). These replacements can be done just a limited number * of times. However, for long streaming transfers, we need to reset those counters to discard * punctual connectivity issues. * * @see DirectReadSlot::doio */ static constexpr std::chrono::seconds CONNECTION_SWITCHES_LIMIT_RESET_TIME = 300s; /* ===================*\ * Methods * \* ===================*/ bool isRaidedTransfer() const; /** * @brief Retries the entire direct read transfer upon a failure. * * This function is called when a transfer has failed, and it is responsible for * resetting any failed parts and retrying the operation. * * @param err The error code that caused the failure * @param timeleft The time after which the transfer is retried */ void retryEntireTransfer(const Error& e, const dstime timeleft = 0); /** * @brief Identifies slow connections under minimum threshold and determines the slowest one. * * This function iterates through all active and completed connections, checks * their throughput against a minimum threshold, and identifies those that are * considered too slow. Additionally, it determines the slowest one among them. * * @return A pair containing: * - A set of indices representing connections that are too slow. * - The index of the slowest connection (or invalid index if no slow conns exist). */ std::pair, size_t> searchSlowConnsUnderThreshold(); void resetConnSwitchesCounters(const std::chrono::steady_clock::time_point& now); /** * @brief Reset all connection switches counters if timeout * (CONNECTION_SWITCHES_LIMIT_RESET_TIME) has expired */ void resetConnSwitchesCountersIfTimeoutExpired(); // Returns true if any raided Req has failed, otherwise returns false bool isAnyRaidedPartFailed() const; /** * @brief Main i/o loop (process every HTTP req from req vector). * * @return True if connection must be retried, False to continue as normal. */ bool doio(); /** * @brief Manages a HTTP req failure filtering by httpstatus and performing required action (i.e * retry HTTP req) */ void onFailure(std::unique_ptr& req, const size_t connectionNum, const unsigned errCode); /** * @brief Flag value getter to check if a given request is allowed to request a further chunk. * * Calling request should be in status REQ_READY. * If wait value is true, it will remain in that status before being allowed to POST. * * @return True for waiting, False otherwise. * @see DirectReadSlot::WAIT_FOR_PARTS_IN_FLIGHT * @see DirectReadSlot::mWaitForParts */ bool waitForPartsInFlight() const; /** * @brief Number of used connections (all conections but the unused one, if any). * * @return Count of used connections * @see mUnusedRaidConnection */ unsigned usedConnections() const; /** * @brief Disconnect and reset a connection, meant for connections with a request in REQ_INFLIGHT status. * * This method should be called every time a HttpReq should call its disconnect() method. * * @param connectionNum Connection index in mReq vector. * @return True if connectionNum is valid and connection has been reset successfuly. */ bool resetConnection(size_t connectionNum = 0); /** * @brief Retrieves the minimum speed per connection in Bytes per second. * * This method calculates the minimum allowed speed in Bytes per second for a connection, * taking into account whether it's a streaming RAID transfer, and the limits configured in the * client (check minstreamingrate). * * @return The minimum speed per connection in Bytes per second. */ unsigned getMinSpeedPerConnBytesPerSec() const; /** * @brief Calculate throughput for a given connection: relation of bytes per millisecond. * * Throughput is updated every time a new chunk is submitted to the transfer buffer. * Throughput values are reset when a new request starts. * * @param connectionNum Connection index in mReq vector. * @return Connection throughput: average number of bytes fetched per millisecond. * * @see DirectReadSlot::calcThroughput() * @see DirectReadSlot::mThroughPut */ m_off_t getThroughput(size_t connectionNum) const; /** * @brief Retries a DirectRead transfer, handling both RAIDED and non RAIDED transfers. * * This method attempts to retry a DirectRead transfer. If the transfer is non RAIDED, * it directly triggers a retry. If it's RAIDED, it replaces that part with unused RAID * connection (if possible), and retries only that part. * * @param connectionNum The connection number to retry. */ void retryOnError(const size_t connectionNum, const int httpstatus, const unsigned errCode); /** * @brief Check if there are in-flight requests * * @return True if there are in-flight requests, otherwise returns false */ bool exitDueReqsOnFlight() const; /** * @brief Determines if the unused connection can be reused. * * @return `true` if the unused connection can be reused, `false` otherwise. */ bool unusedConnectionCanBeReused(); /** * @brief Replace connectionNum by unused connection when there are requests in flight. * - This method decrements the number of requests in flight as necessary if the * newUnusedConnection can be replaced by the currently unused one. * * @note: this method internally calls DirectReadSlot::replaceConnectionByUnused to perform * connection replacement * * @param newUnusedConnection The connection number to be replaced by unused one * @param reason Reason of replacement * - UnusedConn::CONN_SPEED_SLOWEST_PART: replaced part is the slowest one * - UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD: replaced part is the slowest one AND * transfer mean speed is below minstreamingrate OR replaced part speed is below min speed * threshold * - UnusedConn::ON_RAIDED_ERROR replaced part has failed due a Http err * * @param unusedReason reason to be set at new unused connection. See UnusedReason enum * */ void replaceConnectionByUnusedInflight( const size_t newUnusedConnection, const UnusedConn::ConnReplacementReason replamecementReason, const UnusedConn::UnusedReason unusedReason); /** * @brief Replace connectionNum by unused connection * * @param newUnusedConnection The connection number to be replaced by unused one * @param reason Reason of replacement * - UnusedConn::CONN_SPEED_SLOWEST_PART: replaced part is the slowest one * - UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD: replaced part is the slowest one AND * transfer mean speed is below minstreamingrate OR replaced part speed is below min speed * threshold * - UnusedConn::ON_RAIDED_ERROR replaced part has failed due a Http err * * @param unusedReason reason to be set at new unused connection. See UnusedReason enum * * @return True if connection has been replaced by unused, false otherwise */ bool replaceConnectionByUnused(const size_t newUnusedConnection, const UnusedConn::ConnReplacementReason replamecementReason, const UnusedConn::UnusedReason unusedReason); /** * @brief Identifies the slowest and fastest connections (ignoring unused connection) * * @param connectionNum The index of the connection to compare others against. * @return A `std::pair` where: * - The first element is the index of the slowest connection. * - The second element is the index of the fastest connection. * If no valid comparison can be made, both values will be set to `mReqs.size()`. */ std::pair searchSlowestAndFastestConns(const size_t connectionNum) const; /** * @brief Determines if the slowest connection can be replaced by the unused connection. * * @param connectionNum The index of the connection being evaluated. * @param slowestConnection The index of the slowest connection identified. * @param fastestConnection The index of the fastest connection identified. * @return `true` if the slowest connection can be switched out for the unused connection, * `false` otherwise. */ bool slowestConnTooSlowVsFastest(const size_t connectionNum, const size_t slowestConnection, const size_t fastestConnection) const; /** * @brief Search for the slowest connection and switch it with the actual unused connection. * * This method is intented to select fastest 5 connections (after all 5 raided parts finished a * chunk) * * This method is called between requests: * If WAIT_FOR_PARTS_IN_FLIGHT is true, this ensures to compare among all the connections * before they POST again. If WAIT_FOR_PARTS_IN_FLIGHT is false, any connection with a * REQ_INFLIGHT status will be ignored for comparison purposes. * * @param connectionNum Connection index in mReq vector. * @return True if the slowest connection has been detected and switched with the actual * unused connection, False otherwise. * @see DirectReadSlot::DEFAULT_MIN_COMPARABLE_THROUGHPUT * @see DirectReadSlot::MAX_CONN_SWITCHES_BELOW_SPEED_THRESHOLD */ bool searchAndDisconnectSlowestConnection(const size_t connectionNum = 0); /** * @brief Checks if the minimum comparable throughput is met for a specific connection. * * @param connectionNum The index of the connection * @return true if the throughput for the specified connection meets the minimum comparable * threshold, otherwise returns false */ bool isMinComparableThroughputForThisConnection(const size_t connectionNum) const { return mThroughput[connectionNum].second && mThroughput[connectionNum].first >= mMinComparableThroughput; } /** * @brief Check if all requests are inflight or in ready status * @return True if all requests are inflight or in ready status */ bool areAllReqsReadyOrInFlight(); /** * @brief Decrease counter for requests with REQ_INFLIGHT status * * Valid only for 2+ connections * * @return True if counter was decreased, false otherwise (i.e.: if we only have one connection) * @see DirectReadSlot::WAIT_FOR_PARTS_IN_FLIGHT * @see DirectReadSlot:::mNumReqsInflight */ bool decreaseReqsInflight(); /** * @brief Increase counter for requests with REQ_INFLIGHT status * * Valid only for 2+ connections * * @return True if counter was increased, false otherwise (i.e.: if we only have one connection) * @see DirectReadSlot::WAIT_FOR_PARTS_IN_FLIGHT * @see DirectReadSlot::mNumReqsInflight */ bool increaseReqsInflight(); /** * @brief Returns a pair of [transfer min speed, transfer mean speed] * @param dsSinceLastWatch Ds since watchOverDirectReadPerformance was executed last time * @return a pair of [transfer min speed, transfer mean speed] */ std::pair getMinAndMeanSpeed(const dstime dsSinceLastWatch); /** * @brief Resets the watchdog associated variables that are used to perform some checkups based * on elapsed time since last check and received data */ void resetWatchdogPartialValues(); /** * @brief Calculate speed and mean speed for DirectRead aggregated operations. * * Controlling progress values are updated when an output piece is delivered to the client. * * @return true if Transfer must be retried, false otherwise * @see DirectReadSlot::MEAN_SPEED_INTERVAL_DS */ bool watchOverDirectReadPerformance(); /** * @brief Checks if connection is done * @param connectionNum The index of the connection * * @return true if connection is done, otherwise returns false */ bool isConnectionDone(const size_t connectionNum) const; /** * @brief Builds a DirectReadSlot attached to a DirectRead object. * * Insert DirectReadSlot object in MegaClient's DirectRead list to start fetching operations. */ DirectReadSlot(DirectRead*); /** * @brief Destroy DirectReadSlot and stop any pendant operation. * * Aborts DirectRead operations and remove iterator from MegaClient's DirectRead list. */ ~DirectReadSlot(); private: /* ===================*\ * Attributes * \* ===================*/ /** * @brief Actual position, updated after combined data is sent to the http server / streaming buffers. */ m_off_t mPos; /** * @brief DirectReadSlot iterator from MegaClient's DirectReadSlot list. * * @see mega::drs_list */ drs_list::iterator mDrs_it; /** * @brief Pointer to DirectRead (equivallent to Transfer for TransferSlot). * * @see DirectRead */ DirectRead* mDr; /** * @brief Vector for requests, each one corresponding to a different connection. * * For RAID files this value will be 6 (one for each part) * For NON-RAID files the default value is 1, but conceptually it could be greater than one * if a parallel-tcp-requests strategy is used or implemented. * * @see HttpReq */ std::vector> mReqs; /** * @brief Vector of pairs of and for throughput * calculations. * * Values are reset by default between different chunk requests. * * @see DirectReadSlot::DEFAULT_MIN_COMPARABLE_THROUGHPUT */ std::vector> mThroughput; /** * @brief Same pair of values than above, used to calculate the delivery speed. * * 'Delivery speed' is calculated from the time interval between new output pieces (combined if RAID) * are processed and ready to sent to the client. */ std::pair mSlotThroughput; /** * @brief Timestamp for DirectReadSlot start. Defined in DirectReadSlot constructor. */ std::chrono::steady_clock::time_point mSlotStartTime; /** * @brief Timeout to reset all connection switch counters. * * @see DirectReadSlot::resetConnSwitchesCountersIfTimeoutExpired */ std::chrono::steady_clock::time_point mConnectionSwitchesLimitLastReset; /** * @brief Unused connection due to slowness. * * This value is used for detecting the slowest start connection and further search and * disconnect new slowest connections. It must be synchronized with RaidBufferManager value, * which is the one to be cached (so we keep it if reseting the DirectReadSlot). */ UnusedConn mUnusedConn; /** * @brief Current total of switches due to performance, i.e., the slowest part being switched * with an unused connection (comparative logic among parts). * * @see DirectReadSlot::MAX_CONN_SWITCHES_SLOWEST_PART */ unsigned mNumConnSwitchesSlowestPart; /** * @brief Current total of switches due to slow connections, i.e., a connection performing below * the defined min speed threshold (minstrate). * * @see DirectReadSlot::MAX_CONN_SWITCHES_BELOW_SPEED_THRESHOLD */ unsigned mNumConnSwitchesBelowSpeedThreshold; /** * @brief Current total of switches due to recoverable errors (i.e HttpStatus 200 and CURLcode: * 56) * * @see DirectReadSlot::MAX_CONN_SWITCHES_RECOVERABLE_ERR */ unsigned mNumConnSwitchesRecoverableError; /** * @brief maps connection id (raided part id) to number of slow speed detections */ std::map mNumConnDetectedBelowSpeedThreshold; /** * @brief Current flag value for waiting for the other connects to finish their TCP requests before any other connection is allowed to request the next chunk. * * @see DirectReadSlot::WAIT_FOR_PARTS_IN_FLIGHT */ bool mWaitForParts; /** * @brief Current requests with status REQ_INFLIGHT. * * @see DirectReadSlot::mWaitForParts */ unsigned mNumReqsInflight; /** * @brief Flag that indicates whether the DirectReadSlot::mNumReqsInflight counter has been incremented after processing the unused connection. @see DirectReadSlot::increaseReqsInFlight */ bool mUnusedConnIncrementedInFlightReqs{false}; /** * @brief Speed controller instance. */ SpeedController mSpeedController; /** * @brief Calculated speed by speedController (different from the one calculated by throughput). */ m_off_t mSpeed; /** * @brief Calculated mean speed by speedController (different from the one calculated by throughput). */ m_off_t mMeanSpeed; /** * @brief Max chunk size allowed to submit the request data to the transfer buffer. * * This value is dynamically set depending on the average throughput of each connection, * so the DirectReadSlot will try to submit buffers as big as possible depending * on connection(s) capacity and general limits (memory, etc.). * * For NON-RAID files, the upper limit is defined by MAX_DELIVERY_CHUNK. * For RAID files, the upper limit is calculated from MAX_DELIVERY_CHUNK divided by the number of raid parts and padding it to RAIDSECTOR value. * * @see DirectReadSlot::MAX_DELIVERY_CHUNK */ unsigned mMaxChunkSize; /** * @brief Min submitted bytes needed for a connection to be throughput-comparable with others. * * This value is set on global delivery throughput. * Ex: * 1. Raid file, each connection submits 1MB. * 2. Delivery chunk size from combined data is 5MB -> min comparable throughtput until * next deliver will be 5MB. * * @see searchAndDisconnectSlowestConnection() * @see processAnyOutputPieces() */ m_off_t mMinComparableThroughput; /** * @brief Max chunk size submitted from one of the connections to the transfer buffer. * * For NON-RAID files, this value is got from MAX_DELIVERY_CHUNK (so submitting buffer size and delivering buffer size are the same). * For RAID files, this value is calculated from MAX_DELIVERY_CHUNK divided by the number of raid parts and padding it to RAIDSECTOR value. * * @see DirectReadSlot::MAX_DELIVERY_CHUNK */ unsigned mMaxChunkSubmitted; /* =======================*\ * Private aux methods * \* =======================*/ /** * @brief Checks if the maximum number of connection switches has been reached or exceeded * based on reason param. * * @param reason the reason for checking if we have reached max connection switched. * - if CONN_SPEED_SLOWEST_PART comparison will be done against * mNumConnSwitchesSlowestPart * - if TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD comparison will be * done against mNumConnSwitchesBelowSpeedThreshold * - if ON_RAIDED_ERROR we don't need to check any switch counter as any HTTP err * is considered as permanent, which means that unused connection cannot be reused anymore. See * unusedConnectionCanBeReused * - if ON_RECOVERABLE_RAIDED_ERROR comparison will be done against * mNumConnSwitchesRecoverableError * * @return `true` if the maximum number of connection switches has been reached or * exceeded, `false` otherwise. */ bool maxUnusedConnSwitchesReached(const UnusedConn::ConnReplacementReason reason) const { switch (reason) { case UnusedConn::CONN_SPEED_SLOWEST_PART: return mNumConnSwitchesSlowestPart >= DirectReadSlot::MAX_CONN_SWITCHES_SLOWEST_PART; case UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD: return mNumConnSwitchesBelowSpeedThreshold >= DirectReadSlot::MAX_CONN_SWITCHES_BELOW_SPEED_THRESHOLD; case UnusedConn::ON_RAIDED_ERROR: return false; case UnusedConn::ON_RECOVERABLE_RAIDED_ERROR: return mNumConnSwitchesRecoverableError >= DirectReadSlot::MAX_CONN_SWITCHES_RECOVERABLE_ERR; } return false; } /** * @brief increases counter for unused connection switches given a replacement reason * * @note: in case we need to replace unused conn by permanent failed raided part * (UnusedConn::ON_RAIDED_ERROR), we don't need to increase any switch counter, as any HTTP err * is considered as permanent, which means that unused connection cannot be reused anymore. See * unusedConnectionCanBeReused */ void increaseUnusedConnSwitches(const UnusedConn::ConnReplacementReason reason) { switch (reason) { case UnusedConn::CONN_SPEED_SLOWEST_PART: ++mNumConnSwitchesSlowestPart; return; case UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD: ++mNumConnSwitchesBelowSpeedThreshold; return; case UnusedConn::ON_RAIDED_ERROR: return; case UnusedConn::ON_RECOVERABLE_RAIDED_ERROR: ++mNumConnSwitchesRecoverableError; return; } } /** * @brief Adjust URL port in URL for streaming (8080). * * @param url Current URL. * @return New URL with updated port. */ std::string adjustURLPort(std::string url); /** * @brief Try processing new output pieces (generated by submitted buffers, fed by each connection request). * * - Combine output pieces for RAID files if needed. * - Deliver final combined chunks to the client. * * @return True if DirectReadSlot can continue, False if some delivery has failed. * @see DirectReadSlot::MAX_DELIVERY_CHUNK * @see MegaApiImpl::pread_data() */ bool processAnyOutputPieces(); /** * @brief Aux method to calculate the throughput: numBytes for 1 unit of timeCount. * * * @param numBytes Total numBytes received for timeCount period. * @param timeCount Time period spent for receiving numBytes. * @return Throughput: average number of bytes fetched for timeCount=1. * * @see DirectReadSlot::mThroughPut */ m_off_t calcThroughput(m_off_t numBytes, m_off_t timeCount) const; }; struct MEGA_API DirectRead { // Type for the callback when a data is recieved struct Data { Data(byte* buffer, m_off_t len, m_off_t offset, m_off_t speed, m_off_t meanSpeed): buffer{buffer}, len{len}, offset{offset}, speed{speed}, meanSpeed{meanSpeed} {} byte* buffer{nullptr}; m_off_t len{0}; m_off_t offset{0}; m_off_t speed{0}; m_off_t meanSpeed{0}; bool ret{false}; // Callback sets and tells a success or a failure }; // Type for the callback on a failure struct Failure { Failure(const Error& e, int retry, dstime timeLeft): e{e}, retry{retry}, timeLeft{timeLeft} {} Error e; int retry{0}; dstime timeLeft{0}; dstime ret{0}; // Callback sets and tells the interval for a retry }; // Type for the callback to revoke itself struct Revoke { Revoke(void* appData): appdata{appData} {} void* appdata{nullptr}; // appdata to match the callback bool ret{false}; // Callback sets and tells if it is revoked or not }; // Type for the callback to tell if it is still valid (not revoked) struct IsValid { bool ret{false}; // Callback sets }; using CallbackParam = std::variant; using Callback = std::function; m_off_t count; m_off_t offset; m_off_t progress; m_off_t nextrequestpos; DirectReadBufferManager drbuf; DirectReadNode* drn; DirectReadSlot* drs; dr_list::iterator reads_it; dr_list::iterator drq_it; int reqtag; Callback callback; void abort(); m_off_t drMaxReqSize() const; void revokeCallback(void* appData); bool onData(byte* buffer, m_off_t len, m_off_t theOffset, m_off_t speed, m_off_t meanSpeed); dstime onFailure(const Error& e, int retry, dstime timeLeft); bool hasValidCallback(); DirectRead(DirectReadNode*, m_off_t, m_off_t, int, Callback&& callback); ~DirectRead(); }; struct MEGA_API DirectReadNode { handle h; bool isPublicHandle; string publicauth; string privateauth; string chatauth; m_off_t partiallen; dstime partialstarttime; std::vector tempurls; m_off_t size; class CommandDirectRead* pendingcmd; int retries; int64_t ctriv; SymmCipher symmcipher; dr_list reads; MegaClient* client; handledrn_map::iterator hdrn_it; dsdrn_map::iterator dsdrn_it; // API command result void cmdresult(const Error&, dstime = 0); // enqueue new read DirectRead* enqueue(m_off_t, m_off_t, int, DirectRead::Callback&& callback); // dispatch all reads void dispatch(); // schedule next event void schedule(dstime); // report failure to app and abort or retry all reads void retry(const Error &, dstime = 0); DirectReadNode(MegaClient* client, handle h, bool isPublicHandle, SymmCipher* symmCipher, int64_t ctriv, const char* privateAuth, const char* publicAuth, const char* chatAuth); ~DirectReadNode(); // Convenience. template static constexpr auto IsDirectReadPredicateV = std::is_invocable_r_v; // Abort all reads satisfying a particular predicate. template auto abort(Predicate predicate) -> std::enable_if_t> { // Iterate over all active reads. for (auto i = reads.begin(); i != reads.end();) { // Get a pointer to the current read. auto* read = *i++; // Read doesn't satisfy our predicate. if (!std::invoke(predicate, *read)) continue; // Abort the read. read->onFailure(API_EINCOMPLETE, retries, 0); // And remove it from the list of active reads. delete read; } } }; } // namespace mega #endif sdk-10.11.0/include/mega/transferslot.h000066400000000000000000000166161516266226600177140ustar00rootroot00000000000000/** * @file mega/transferslot.h * @brief Class for active transfer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_TRANSFERSLOT_H #define MEGA_TRANSFERSLOT_H 1 #include "http.h" #include "node.h" #include "backofftimer.h" #include "raid.h" namespace mega { // Helper class: Automatically manage backoff timer enablement - if the slot is in progress and has an fa, the transfer's backoff timer should not be considered // (part of a performance upgrade, so we don't loop all the transfers, calling their bt.update() on every preparewait() ) class TransferSlotFileAccess { std::unique_ptr fa; Transfer* transfer; public: TransferSlotFileAccess(std::unique_ptr&& p, Transfer* t); ~TransferSlotFileAccess(); void reset(std::unique_ptr&& p = nullptr); inline operator bool() { return bool(fa); } inline FileAccess* operator->() { return fa.get(); } inline operator FileAccess* () { return fa.get(); } }; namespace stats { // Transfer stats struct TransferSlotStats { m_off_t mNumFailedRequests{}; m_off_t mNumTotalRequests{}; double mTotalStartTransferTime{}; double mTotalConnectTime{}; m_off_t mNumRequestsWithCalculatedLatency{}; // Ratio between failed requests and total requests. double failedRequestRatio() const; // Time (ms) taken to establish a connection // Includes DNS resolution, TCP handshake. double averageConnectTime() const; // Time (ms) taken to start receiving the first byte of data // Includes DNS resolution, TCP handshake, // SSL handshake, server-side processing. double averageStartTransferTime() const; }; } // namespace stats class TransferDbCommitter; // active transfer struct MEGA_API TransferSlot { // link to related transfer (never NULL) struct Transfer* transfer; // associated source/destination file TransferSlotFileAccess fa; // command in flight to obtain temporary URL Command* pendingcmd; // transfer attempts are considered failed after XFERTIMEOUT seconds // without data flow static const dstime XFERTIMEOUT; // max time without progress callbacks static const dstime PROGRESSTIMEOUT; // max request size for downloads and uploads static const m_off_t MAX_REQ_SIZE; // max request size per raid part for each connection static const m_off_t MAX_REQ_SIZE_NEW_RAID; // min file size to use new raid engine static const m_off_t UPPER_FILESIZE_LIMIT_FOR_SMALLER_CHUNKS; // min file size for multiple connections in transfer slot static const m_off_t MIN_FILESIZE_FOR_MULTIPLE_CONNECTIONS; // maximum gap between chunks for uploads static const m_off_t MAX_GAP_SIZE; m_off_t maxRequestSize; m_off_t progressreported; m_time_t lastprogressreport; dstime starttime, lastdata; // number of consecutive errors unsigned errorcount; // last error error lasterror; // maximum number of parallel connections and connection array. // shared_ptr for convenient coordination with the worker threads that do encrypt/decrypt on this data. int connections; vector> reqs; // Keep track of transfer network speed per channel, and overall vector mReqSpeeds; SpeedController mTransferSpeed; m_off_t speed, meanSpeed; // only swap channels twice for speed issues, to prevent endless non-progress (counter is reset if we make overall progress, ie data reassembled) unsigned mRaidChannelSwapsForSlowness = 0; // Manage download input buffers and file output buffers for file download. Raid-aware, and automatically performs decryption and mac. TransferBufferManager transferbuf; bool initCloudRaid(MegaClient* client); shared_ptr getcloudRaidPtr() { return cloudRaid; } // async IO operations AsyncIOContext** asyncIO; // handle I/O for this slot void doio(MegaClient*, TransferDbCommitter&); // Prepare an HTTP request void prepareRequest(const std::shared_ptr&, const string& tempURL, m_off_t pos, m_off_t npos); // Process HTTP POST void processRequestPost(MegaClient* client, const std::shared_ptr&); // Process a request failure // Return values: // Error: the ErrorCode. If different from API_OK, it means that the transfer is considered as failed and transfer->failed() should be called. // dstime: the backoff for the transfer->failed() call. This value doesn't shadow the backoff param, as it can be different and used on different parts of the code. std::pair processRequestFailure(MegaClient* client, const std::shared_ptr& httpReq, dstime& backoff, int channel); // Process the latency values for a given request. void processRequestLatency(const std::shared_ptr& req); // Process CloudRaid Request std::pair processRaidReq(size_t connection, m_off_t& raidReqProgress); // helper for doio to delay connection creation until we know if it's raid or non-raid bool createconnectionsonce(); // disconnect and reconnect all open connections for this transfer void disconnect(); // disconnect and reconnect one connection for this transfer void disconnect(unsigned connectionNum); void disconnect(const std::shared_ptr& req); // indicate progress void progress(); // Contiguous progress means that all the chunks are finished, from the start of the file up to (but not including) the file position returned. m_off_t updatecontiguousprogress(); // compute the meta MAC based on the chunk MACs int64_t macsmac(chunkmac_map*); int64_t macsmac_gaps(chunkmac_map*, size_t g1, size_t g2, size_t g3, size_t g4); // tslots list position transferslot_list::iterator slots_it; // slot operation retry timer bool retrying; BackoffTimerTracked retrybt; // transfer failure flag. MegaClient will increment the transfer->errorcount when it sees this set. bool failure; // transfer stats stats::TransferSlotStats tsStats; TransferSlot(Transfer*); ~TransferSlot(); private: // New CloudRaid Proxy std::shared_ptr cloudRaid; void toggleport(HttpReqXfer* req); bool checkDownloadTransferFinished(TransferDbCommitter& committer, MegaClient* client); bool checkMetaMacWithMissingLateEntries(); bool tryRaidRecoveryFromHttpGetError(unsigned i, bool incrementErrors); // returns true if connection haven't received data recently (set incrementErrors) or if slower than other connections (reset incrementErrors) bool testForSlowRaidConnection(unsigned connectionNum, bool& incrementErrors); // Log transfer stats and send to the client the specific transferslot stats that are used for // aggregated comparison. void processTransferStats(); }; } // namespace #endif sdk-10.11.0/include/mega/transferstats.h000066400000000000000000000323611516266226600200640ustar00rootroot00000000000000/** * @file mega/transferstats.h * @brief Calculate and collect transfer metrics * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include "mega/transfer.h" namespace mega::stats { /** * @brief A class to store and manage transfer statistics. * This class collects data for multiple transfers (uploads/downloads) and * provides methods to calculate and analyze metrics such as median size, * speed, latency, and failure ratios. */ class TransferStats { public: /** * @brief A pair representing: . */ using UncollectedTransfersCounters = std::pair; /** * @brief Structure that stores information about a single transfer. */ struct TransferData { m_off_t mSize{}; // Size of the transfer (in bytes). m_off_t mSpeed{}; // Speed of the transfer (in KBytes per second). double mLatency{}; // Latency of the transfer (in milliseconds). double mFailedRequestRatio{}; // Number of failed requests during the transfer. bool mIsRaided{}; // Flag indicating if the transfer is raided (true) or not (false). std::chrono::steady_clock::time_point mTimestamp{}; // Timestamp indicating when the transfer was added. // Check data fields validity. bool checkDataStateValidity() const; }; /** * @brief Structure that holds aggregated metrics for a set of transfers. * Metrics include statistical values like median and weighted average * size/speed, maximum speed, average latency, failed request ratio, and * raided transfer ratio. */ struct Metrics { direction_t mTransferType{}; // Type of transfer (upload or download). m_off_t mMedianSize{}; // Median size of transfers in the set (in bytes). m_off_t mContraharmonicMeanSize{}; // Contraharmonic mean (sizes weighted by size) of the // transfers (in bytes). m_off_t mMedianSpeed{}; // Median speed of the transfers (in KBytes per second). m_off_t mWeightedAverageSpeed{}; // Weighted average speed of the transfers (in KBytes per // second). m_off_t mMaxSpeed{}; // Maximum speed observed in the set of transfers (in KBytes per second). m_off_t mAvgLatency{}; // Average latency of the transfers (in milliseconds). double mFailedRequestRatio{}; // Ratio of failed requests to total requests, between 0 and 1. double mRaidedTransferRatio{}; // Ratio of raided transfers in the set, between 0 and 1. size_t mNumTransfers{}; // Number of transfers used to calculate these metrics. Informative, // not used for stats analysis. // Create a string to be used for debug and analyze contents. // The separator is meant for different lines with metrics data. std::string toString(const std::string& separator = "\n") const; // Create a string in JSON format with keys and values. std::string toJson() const; }; /** * @brief Constructs a new TransferStats object. * * @param mMaxEntries Maximum number of transfer entries to store. * @param mMaxAgeSeconds Maximum age (in seconds) before a transfer entry is discarded. */ TransferStats(const size_t maxEntries, const int64_t maxAgeSeconds): mMaxEntries(maxEntries), mMaxAgeSeconds(maxAgeSeconds) {} /** * @brief Adds a new transfer to the statistics collection. * * Removes the oldest transfers if the collection exceeds the maximum entries or age. * * Size, speed and latency must be positive values. * * There can be a speed 0 value for uploads whose nodes are cloned * due to the same file existing in the cloud (i.e., there is no real transfer). * Similarly, there can be a latency 0 if all the connections were reused. * None of them (transfers with speed 0 or latency 0) can be taken into account, * and they are discarded (not added) for transfer stats. * * These are the TransferData parameter fields that must be included. * * @param TransferData::mSize Size of the transfer (in bytes). * @param TransferData::mSpeed Speed of the transfer (in bytes per second). * @param TransferData::mLatency Latency of the transfer (in milliseconds). * @param TransferData::mFailedRequestRatio Number of failed requests * divided by the number of total requests. * @param TransferData::mIsRaided Boolean indicating whether the transfer was raided. * * The TransferData::timestamp field will be assigned during the operation. * * @return true if the params are valid as stated above, false otherwise. */ bool addTransferData(TransferData&& transferData); /** * @brief Collects metrics from the stored transfer data. * * @param type The type of transfer (upload or download). * @return Metrics A structure containing the aggregated metrics. */ Metrics collectMetrics(const direction_t type) const; /** * @brief Check the number of transfers included in the collection. * * @return The current number of transfers included. */ size_t size() const { return mTransfersData.size(); } /** * @brief Check the maximum number of transfers to store. * * @return The maximum number of transfers to store. */ size_t getMaxEntries() const { return mMaxEntries; } /** * @brief Check the max age of a transfer before it's removed (in seconds). * * Transfers above this age will only be removed when adding new transfers * to the collection. * * @return The max number of seconds for a transfer to be kept in the collection. */ int64_t getMaxAgeSeconds() const { return mMaxAgeSeconds; } /** * @brief Get the number of transfers and accumulated transfer size that has not been collected * and printed. */ UncollectedTransfersCounters getUncollectedAndPrintedTransferData() const { return mUncollectedAndPrintedTransferData; } /** * @brief Collect and print the metrics, and reset the uncollectedAndPrintedTransferData. */ TransferStats::Metrics collectAndPrintMetrics(const direction_t type, const std::string& separator = "\n"); private: std::deque mTransfersData; // Stores the recent transfer data. size_t mMaxEntries; // Maximum number of transfers to store. int64_t mMaxAgeSeconds; // Maximum age of a transfer before it's removed (in seconds). UncollectedTransfersCounters mUncollectedAndPrintedTransferData{}; }; /** * @brief TransferStatsManager handles separate statistics for uploads and downloads. * * It manages two instances of TransferStats—one for upload transfers and one for download * transfers. */ class TransferStatsManager { public: static constexpr size_t NUM_ENTRIES_FOR_LOGGING = 10; // Default number of entries in each stats collection to collect and log current // metrics. static constexpr m_off_t TOTAL_SIZE_FOR_LOGGING = 101 * 1024 * 1024; // Default accumulated size for all entries in each stats collection to // collect and log current metrics. /** * @brief Constructs a new TransferStatsManager object. * * Initializes statistics collections for uploads and downloads. * * @param maxEntries Maximum number of entries for uploads and downloads. * @param maxAgeSeconds Maximum age of transfers before being discarded. */ TransferStatsManager(const size_t maxEntries = MAX_ENTRIES, const int64_t maxAgeSeconds = MAX_AGE_SECONDS): mUploadStatistics(maxEntries, maxAgeSeconds), mDownloadStatistics(maxEntries, maxAgeSeconds) {} /** * @brief Adds a transfer to the appropriate statistics collection (upload or download). * * @param transfer Pointer to a Transfer object * @return true if the stats have been added, false if the transfer or slot are not valid. */ bool addTransferStats(const Transfer* const transfer); /** * @brief Create a string in JSON format for the type of transfer. * * @param type Specify whether to compress upload or download metrics. * @return A string containing the metrics in JSON format. */ std::string metricsToJsonForTransferType(const direction_t type) const; /** * @brief Collect and calculate metrics for either uploads or downloads. * * @param type Specify whether to collect upload or download metrics. * @return Metrics structure containing calculated statistics. */ TransferStats::Metrics collectMetrics(const direction_t type) const; /** * @brief Collect and print the metrics for either uploads or downloads. * * @param type Specify whether to print upload or download metrics. * @param separator for each line of metrics values. */ TransferStats::Metrics collectAndPrintMetrics(const direction_t type, const std::string& separator = "\n"); /** * @brief Call collectAndPrintMetrics if getUncollectedAndPrintedTransferData() reached either * NUM_ENTRIES_FOR_LOGGING or TOTAL_SIZE_FOR_LOGGING. */ std::optional collectAndPrintTransferStatsIfLimitReached(const direction_t type, const std::string& separator = "\n"); /** * @brief Check the number of transfers included in the collection. * * @param type PUT for uploads, GET for downloads. * @return The current number of transfers included. */ size_t size(const direction_t type) const; /** * @brief Check the maximum number of transfers to store. * * @param type PUT for uploads, GET for downloads. * @return The maximum number of transfers to store. */ size_t getMaxEntries(const direction_t type) const; /** * @brief Check the max age of a transfer before it's removed (in seconds). * * Transfers above this age will only be removed when adding new transfers * to the collection. * * @param type PUT for uploads, GET for downloads. * @return The max number of seconds for a transfer to be kept in the collection. */ int64_t getMaxAgeSeconds(const direction_t type) const; /** * @see TransferStats::getUncollectedAndPrintedTransferData() */ TransferStats::UncollectedTransfersCounters getUncollectedAndPrintedTransferData(const direction_t type) const; private: static constexpr size_t MAX_ENTRIES = 50; // Default maximum number of entries in each stats collection. static constexpr int64_t MAX_AGE_SECONDS = 3600; // Default maximum age (in seconds) before transfers are removed. mutable std::mutex mTransferStatsMutex; TransferStats mUploadStatistics; // Transfer statistics for uploads. TransferStats mDownloadStatistics; // Transfer statistics for downloads. }; // UTILS /** * @brief LOG the collected metrics. * * @param metrics The metrics to log. * @param separator Separator for each line of metrics values. */ void printMetrics(const TransferStats::Metrics& metrics, const std::string& separator = "\n"); /** * @brief Calculates the median of a vector of sorted values. * * @param sortedValues Vector of sorted values for which the median needs to be calculated. * @return m_off_t The median value. */ m_off_t calculateMedian(const std::vector& sortedValues); /** * @brief Calculates the weighted average of a vector of values. * * @param values Vector of values to average. * @param weights Corresponding vector of weights for each value. * @return m_off_t The weighted average value in bytes. */ m_off_t calculateWeightedAverage(const std::vector& values, const std::vector& weights); /** * @brief Check/assert valid transfer type. * * Only PUT (uploads) and GET (downloads) are allowed. */ void checkTransferTypeValidity(const direction_t type); /** * @brief Checks transfer state validity to add transfer stats. * * Error conditions are logged and asserts are triggered. * * The transfer object must be valid. * There must be a valid TransferSlot. * TempURLs must be defined and raidKnown (whether or not is raid) defined. * * @param transfer The transfer object to get the stats from. * * @returm true if the transfer state is valid and stats can be retrieved from it, false otherwise. */ bool checkTransferStateValidity(const Transfer* const transfer); } // namespace mega::stats sdk-10.11.0/include/mega/treeproc.h000066400000000000000000000046311516266226600170030ustar00rootroot00000000000000/** * @file mega/treeproc.h * @brief Node tree processor * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_TREEPROC_H #define MEGA_TREEPROC_H 1 #include "sharenodekeys.h" #include "node.h" #include "transfer.h" #include "sync.h" namespace mega { // node tree processor class MEGA_API TreeProc { public: virtual void proc(MegaClient*, std::shared_ptr) = 0; virtual ~TreeProc() { } }; class MEGA_API TreeProcDel : public TreeProc { public: void proc(MegaClient*, std::shared_ptr); void setOriginatingUser(const handle& handle); private: handle mOriginatingUser = mega::UNDEF; }; class MEGA_API TreeProcApplyKey : public TreeProc { public: void proc(MegaClient*, std::shared_ptr); }; class MEGA_API TreeProcCopy : public TreeProc { public: vector nn; unsigned nc = 0; bool allocated = false; bool resetSensitive = false; // Used when importing foreign nodes void allocnodes(void); void proc(MegaClient*, std::shared_ptr); }; class MEGA_API TreeProcShareKeys : public TreeProc { ShareNodeKeys snk; std::shared_ptr sn; bool includeParentChain; public: void proc(MegaClient*, std::shared_ptr); void get(Command*); TreeProcShareKeys(std::shared_ptr, bool); }; class MEGA_API TreeProcForeignKeys : public TreeProc { public: void proc(MegaClient*, std::shared_ptr); }; #ifdef ENABLE_SYNC class MEGA_API LocalTreeProc { public: virtual void proc(FileSystemAccess&, LocalNode*) = 0; virtual ~LocalTreeProc() { } }; class MEGA_API LocalTreeProcMove : public LocalTreeProc { Sync *newsync; public: LocalTreeProcMove(Sync*); void proc(FileSystemAccess&, LocalNode*); int nc; }; class MEGA_API LocalTreeProcUpdateTransfers : public LocalTreeProc { public: void proc(FileSystemAccess&, LocalNode*); }; #endif } // namespace #endif sdk-10.11.0/include/mega/types.h000066400000000000000000001716031516266226600163300ustar00rootroot00000000000000/** * @file mega/types.h * @brief Mega SDK types and includes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_TYPES_H #define MEGA_TYPES_H 1 #ifdef _MSC_VER #if MEGA_LINKED_AS_SHARED_LIBRARY #define MEGA_API __declspec(dllimport) #elif MEGA_CREATE_SHARED_LIBRARY #define MEGA_API __declspec(dllexport) #endif #endif #ifndef MEGA_API #define MEGA_API #endif // it needs to be reviewed that serialization/unserialization is not relying on this typedef char __static_check_01__[sizeof(bool) == sizeof(char) ? 1 : -1]; // if your build fails here, please contact MEGA developers // platform-specific includes and defines #ifdef _WIN32 #include "mega/win32/megasys.h" #else #include "mega/posix/megasys.h" #endif #ifdef USE_CRYPTOPP #include // so we can test CRYPTO_VERSION below #endif // signed 64-bit generic offset typedef int64_t m_off_t; namespace mega { // within ::mega namespace, byte is unsigned char (avoids ambiguity when std::byte from c++17 and perhaps other defined ::byte are available) using byte = unsigned char; } #ifdef USE_CRYPTOPP #include "mega/crypto/cryptopp.h" #else #include "megacrypto.h" #endif #include "mega/crypto/sodium.h" #include "mega/user_attribute_types.h" #include #include #include #include #include #include #include namespace mega { // import these select types into the namespace directly, to avoid adding std::byte from c++17 using std::string; using std::map; using std::set; using std::list; using std::vector; using std::pair; using std::multimap; using std::deque; using std::multiset; using std::queue; using std::streambuf; using std::tuple; using std::ostringstream; using std::unique_ptr; using std::shared_ptr; using std::weak_ptr; using std::move; using std::mutex; using std::recursive_mutex; using std::lock_guard; #ifdef WIN32 using std::wstring; #endif // forward declaration struct AccountDetails; struct AchievementsDetails; struct AttrMap; class BackoffTimer; struct BannerDetails; class Command; class CommandPubKeyRequest; struct BusinessPlan; struct CurrencyData; struct DirectRead; struct DirectReadNode; struct DirectReadSlot; struct FileAccess; struct FileSystemAccess; struct FileAttributeFetch; struct FileAttributeFetchChannel; struct FileFingerprint; struct FileFingerprintCmp; struct FileFingerprintCmpNoMtime; struct HttpReq; struct GenericHttpReq; struct LocalNode; class MegaClient; class NodeManager; struct NewNode; struct Node; struct NodeCore; class PubKeyAction; class Request; struct Transfer; class TreeProc; class LocalTreeProc; struct User; struct Waiter; struct Proxy; struct PendingContactRequest; class TransferList; struct Achievement; class SyncConfig; class LocalPath; namespace UserAlert { struct Base; } class AuthRing; #define EOO 0 // Our own version of time_t which we can be sure is 64 bit. // Utils.h has functions m_time() and so on corresponding to time() which help us to use this type and avoid arithmetic overflow when working with time_t on systems where it's 32-bit typedef int64_t m_time_t; constexpr m_time_t mega_invalid_timestamp = 0; inline bool isValidTimeStamp(m_time_t t) { return t != mega_invalid_timestamp; } // monotonously increasing time in deciseconds using dstime = int64_t; #define NEVER std::numeric_limits::max() #define EVER(ds) (ds != NEVER) #define STRINGIFY(x) # x #define TOSTRING(x) STRINGIFY(x) // HttpReq states typedef enum { REQ_READY, REQ_GET_URL, REQ_PREPARED, REQ_UPLOAD_PREPARED_BUT_WAIT, REQ_ENCRYPTING, REQ_DECRYPTING, REQ_DECRYPTED, REQ_INFLIGHT, REQ_SUCCESS, REQ_FAILURE, REQ_DONE, REQ_ASYNCIO, } reqstatus_t; typedef enum { USER_HANDLE, NODE_HANDLE } targettype_t; typedef enum { METHOD_POST, METHOD_GET, METHOD_NONE} httpmethod_t; typedef enum { REQ_BINARY, REQ_JSON } contenttype_t; // new node source types typedef enum { NEW_NODE, NEW_PUBLIC, NEW_UPLOAD } newnodesource_t; class chunkmac_map; /** * @brief Declaration of API error codes. */ typedef enum ErrorCodes : int { API_OK = 0, ///< Everything OK. API_EINTERNAL = -1, ///< Internal error. API_EARGS = -2, ///< Bad arguments. API_EAGAIN = -3, ///< Request failed, retry with exponential backoff. DAEMON_EFAILED = -4, ///< If returned from the daemon: EFAILED API_ERATELIMIT = -4, ///< If returned from the API: Too many requests, slow down. API_EFAILED = -5, ///< Request failed permanently. This one is only produced by the API, only per command (not batch level) API_ETOOMANY = -6, ///< Too many requests for this resource. API_ERANGE = -7, ///< Resource access out of range. API_EEXPIRED = -8, ///< Resource expired. API_ENOENT = -9, ///< Resource does not exist. API_ECIRCULAR = -10, ///< Circular linkage. API_EACCESS = -11, ///< Access denied. API_EEXIST = -12, ///< Resource already exists. API_EINCOMPLETE = -13, ///< Request incomplete. API_EKEY = -14, ///< Cryptographic error. API_ESID = -15, ///< Bad session ID. API_EBLOCKED = -16, ///< Resource administratively blocked. API_EOVERQUOTA = -17, ///< Quota exceeded. API_ETEMPUNAVAIL = -18, ///< Resource temporarily not available. API_ETOOMANYCONNECTIONS = -19, ///< Too many connections on this resource. API_EWRITE = -20, ///< File could not be written to (or failed post-write integrity check) API_EREAD = -21, ///< File could not be read from (or changed unexpectedly during reading) API_EAPPKEY = -22, ///< Invalid or missing application key. API_ESSL = -23, ///< SSL verification failed API_EGOINGOVERQUOTA = -24, ///< Not enough quota API_EROLLEDBACK = -25, ///< A strongly-grouped request was rolled back. API_EMFAREQUIRED = -26, ///< Multi-factor authentication required API_EMASTERONLY = -27, ///< Access denied for sub-users (only for business accounts) API_EBUSINESSPASTDUE = -28, ///< Business account expired API_EPAYWALL = -29, ///< Over Disk Quota Paywall API_ESUBUSERKEYMISSING = -30, ///< A business error where a subuser has not yet encrypted /// their master key for the admin user and tries to perform /// a disallowed command (currently u and p) LOCAL_ENOSPC = -1000, ///< Insufficient space LOCAL_ETIMEOUT = -1001, ///< A request timed out. LOCAL_ABANDONED = -1002, ///< Request abandoned due to local logout. LOCAL_ENETWORK = -1003, ///< Local network error (DNS resolution failure) LOCAL_LOGGED_OUT = -1004, ///< Client isn't logged in. API_FUSE_EBADF = -2000, API_FUSE_EISDIR = -2001, API_FUSE_ENAMETOOLONG = -2002, API_FUSE_ENOTDIR = -2003, API_FUSE_ENOTEMPTY = -2004, API_FUSE_ENOTFOUND = -2005, API_FUSE_EPERM = -2006, API_FUSE_EROFS = -2007, API_FUSE_EALREADY = -2008, API_FUSE_ECANCELLED = -2009, API_FUSE_EDUPLICATE = -2010 } error; class Error { public: typedef enum { USER_ETD_UNKNOWN = -1, USER_ENABLED = 0, USER_PENDINGCONFIRMATION = 1, USER_SUSPENDED_GENERIC = 2, USER_SUSPENDED_PAYMENT = 3, USER_COPYRIGHT_SUSPENSION = 4, USER_SUSPENDED_ADMIN_FULLDISABLE = 5, USER_SUSPENDED_ADMIN_PARTIALDISABLE = 6, USER_ETD_SUSPENSION = 7, USER_SUSPENDED_SMSVERIFICATIONREQUIRED = 8, USER_SUSPENDED_EMAILVERIFICATIONREQUIRED = 9, USER_SUBACCOUNT_PENDINGCONFIRMATION = 10, USER_SUBACCOUNT_DISABLED = 11, USER_SUBACCOUNT_DELETED = 12, USER_BUSINESSACCOUNT = 20, USER_SUSPENDED_PASSWORD_CHANGE_REQUIRED = 21, USER_EPHEMERAL_RESELLER_USER = 22, USER_SUSPENDED_NOUSER = 99, } UserErrorCode; typedef enum { LINK_UNKNOWN = -1, LINK_UNDELETED = 0, // Link is undeleted LINK_DELETED_DOWN = 1, // Link is deleted or down LINK_DOWN_ETD = 2, // Link is down due to an ETD specifically } LinkErrorCode; Error(error err = API_EINTERNAL) : mError(err) { } void setErrorCode(error err) { mError = err; } void setUserStatus(int64_t u) { mUserStatus = u; } void setLinkStatus(int64_t l) { mLinkStatus = l; } bool hasExtraInfo() const { return mUserStatus != USER_ETD_UNKNOWN || mLinkStatus != LINK_UNKNOWN; } int64_t getUserStatus() const { return mUserStatus; } int64_t getLinkStatus() const { return mLinkStatus; } operator error() const { return mError; } private: error mError = API_EINTERNAL; int64_t mUserStatus = USER_ETD_UNKNOWN; int64_t mLinkStatus = LINK_UNKNOWN; }; // returned by loggedin() typedef enum { NOTLOGGEDIN = 0, EPHEMERALACCOUNT, CONFIRMEDACCOUNT, FULLACCOUNT, EPHEMERALACCOUNTPLUSPLUS } sessiontype_t; enum class PasswordEntryError : uint8_t { OK = 0, PARSE_ERROR, MISSING_PASSWORD, MISSING_NAME, MISSING_TOTP_SHARED_SECRET, INVALID_TOTP_SHARED_SECRET, MISSING_TOTP_NDIGITS, INVALID_TOTP_NDIGITS, MISSING_TOTP_EXPT, INVALID_TOTP_EXPT, MISSING_TOTP_HASH_ALG, INVALID_TOTP_HASH_ALG, MISSING_CREDIT_CARD_NUMBER, INVALID_CREDIT_CARD_NUMBER, INVALID_CREDIT_CARD_CVV, INVALID_CREDIT_CARD_EXPIRATION_DATE, }; // Alias for `FileFingerprint` Crc using FingerprintCrc = std::array; /** * @brief Get a string representation from a PasswordEntryError */ std::string_view toString(const PasswordEntryError err); // node/user handles are 8-11 base64 characters, case sensitive, and thus fit // in a 64-bit int typedef uint64_t handle; class NodeHandle { // Handles of nodes are only 6 bytes. // This class helps avoid issues when we don't save/restore the top 2 bytes when using an 8 byte uint64 to represent it uint64_t h = 0xFFFFFFFFFFFFFFFF; public: bool isUndef() const { return (h & 0xFFFFFFFFFFFF) == 0xFFFFFFFFFFFF; } void setUndef() { h = 0xFFFFFFFFFFFFFFFF; } NodeHandle& set6byte(uint64_t n) { h = n; assert((n & 0xFFFF000000000000) == 0 || n == 0xFFFFFFFFFFFFFFFF); return *this; } NodeHandle& setImpossibleValue(uint64_t n) { h = n; return *this; } bool eq(NodeHandle b) const { return (h & 0xFFFFFFFFFFFF) == (b.h & 0xFFFFFFFFFFFF); } bool eq(handle b) const { return (h & 0xFFFFFFFFFFFF) == (b & 0xFFFFFFFFFFFF); } bool ne(handle b) const { return (h & 0xFFFFFFFFFFFF) != (b & 0xFFFFFFFFFFFF); } bool ne(NodeHandle b) const { return (h & 0xFFFFFFFFFFFF) != (b.h & 0xFFFFFFFFFFFF); } bool operator<(const NodeHandle& rhs) const { return h < rhs.h; } handle as8byte() const { return isUndef() ? 0xFFFFFFFFFFFFFFFF : (h & 0xFFFFFFFFFFFF); } }; inline bool operator==(NodeHandle a, NodeHandle b) { return a.eq(b); } inline bool operator==(NodeHandle a, handle b) { return a.eq(b); } inline bool operator!=(NodeHandle a, handle b) { return a.ne(b); } inline bool operator!=(NodeHandle a, NodeHandle b) { return a.ne(b); } std::ostream& operator<<(std::ostream&, NodeHandle h); struct UploadHandle { handle h = 0xFFFFFFFFFFFFFFFF; UploadHandle() {} UploadHandle(handle uh) : h(uh) { assert( (h & 0xFFFF000000000000) != 0 ); } // generate upload handle for the next upload UploadHandle next(); bool isUndef() const { return h == 0xFFFFFFFFFFFFFFFF; } bool eq(UploadHandle b) const { return h == b.h; } bool operator<(const UploadHandle& rhs) const { return h < rhs.h; } }; inline bool operator==(UploadHandle a, UploadHandle b) { return a.eq(b); } class NodeOrUploadHandle { handle h = 0xFFFFFFFFFFFFFFFF; bool mIsNodeHandle = true; public: NodeOrUploadHandle() {} explicit NodeOrUploadHandle(NodeHandle nh) : h(nh.as8byte()), mIsNodeHandle(true) {} explicit NodeOrUploadHandle(UploadHandle uh) : h(uh.h), mIsNodeHandle(false) {} NodeHandle nodeHandle() { return mIsNodeHandle ? NodeHandle().set6byte(h) : NodeHandle(); } UploadHandle uploadHandle() { return mIsNodeHandle ? UploadHandle() : UploadHandle(h); } bool isNodeHandle() { return mIsNodeHandle; } bool isUndef() const { return h == 0xFFFFFFFFFFFFFFFF; } bool eq(NodeOrUploadHandle b) const { return h == b.h && mIsNodeHandle == b.mIsNodeHandle; } bool operator<(const NodeOrUploadHandle& rhs) const { return h < rhs.h || (h == rhs.h && int(mIsNodeHandle) < int(rhs.mIsNodeHandle)); } handle as8byte() const { return h; } }; inline bool operator==(NodeOrUploadHandle a, NodeOrUploadHandle b) { return a.eq(b); } // (can use unordered_set if available) typedef set handle_set; // file attribute type typedef uint16_t fatype; // list of files typedef list file_list; // node types: typedef enum { TYPE_NESTED_MOUNT = -5, TYPE_SYMLINK = -4, TYPE_DONOTSYNC = -3, TYPE_SPECIAL = -2, // but not include SYMLINK TYPE_UNKNOWN = -1, FILENODE = 0, // FILE - regular file nodes FOLDERNODE, // FOLDER - regular folder nodes ROOTNODE, // ROOT - the cloud drive root node VAULTNODE, // VAULT - vault, for "My backups" and other special folders RUBBISHNODE, // RUBBISH - rubbish bin } nodetype_t; enum class TypeOfLink { FOLDER, FILE, SET, }; typedef enum { NO_SHARES = 0x00, IN_SHARES = 0x01, OUT_SHARES = 0x02, PENDING_OUTSHARES = 0x04, LINK = 0x08} ShareType_t; // MimeType_t maps to file extensionse declared at Node typedef enum { MIME_TYPE_UNKNOWN = 0, MIME_TYPE_PHOTO = 1, // photoExtensions, photoRawExtensions, photoImageDefExtension MIME_TYPE_AUDIO = 2, // audioExtensions longAudioExtension MIME_TYPE_VIDEO = 3, // videoExtensions MIME_TYPE_DOCUMENT = 4, // documentExtensions MIME_TYPE_PDF = 5, // pdfExtensions MIME_TYPE_PRESENTATION = 6, // presentationExtensions MIME_TYPE_ARCHIVE = 7, // archiveExtensions MIME_TYPE_PROGRAM = 8, // programExtensions MIME_TYPE_MISC = 9, // miscExtensions MIME_TYPE_SPREADSHEET = 10, // spreadsheetExtensions MIME_TYPE_ALL_DOCS = 11, // any of {document, pdf, presentation, spreadsheet} MIME_TYPE_OTHERS = 12, // any other file not included in previous types MIME_TYPE_ALL_VISUAL_MEDIA = 13, // any of {photo, video} } MimeType_t; typedef enum { LBL_UNKNOWN = 0, LBL_RED = 1, LBL_ORANGE = 2, LBL_YELLOW = 3, LBL_GREEN = 4, LBL_BLUE = 5, LBL_PURPLE = 6, LBL_GREY = 7, } nodelabel_t; // node type key lengths const int FILENODEKEYLENGTH = 32; const int FOLDERNODEKEYLENGTH = 16; const int SETNODEKEYLENGTH = SymmCipher::KEYLENGTH; // Max nodes per putnodes command const unsigned MAXNODESUPLOAD = 1000; typedef union { std::array bytes; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { std::array key; union { std::array iv_bytes; uint64_t iv_u64; }; union { std::array crc_bytes; uint64_t crc_u64; }; }; #ifdef _MSC_VER #pragma warning(pop) #endif } FileNodeKey; const int UPLOADTOKENLEN = 36; typedef std::array UploadToken; // persistent resource cache storage class Cacheable { public: virtual ~Cacheable() = default; virtual bool serialize(string*) const = 0; uint32_t dbid = 0; // exposed for active transfers via MegaTransfer::getUniqueId bool notified = false; }; // access levels: // RDONLY - cannot add, rename or delete // RDWR - cannot rename or delete // FULL - all operations that do not require ownership permitted // OWNER - node is in caller's ROOT, VAULT or RUBBISH trees typedef enum { ACCESS_UNKNOWN = -1, RDONLY = 0, RDWR, FULL, OWNER, OWNERPRELOGIN } accesslevel_t; // operations for outgoing pending contacts typedef enum { OPCA_ADD = 0, OPCA_DELETE, OPCA_REMIND} opcactions_t; // operations for incoming pending contacts typedef enum { IPCA_ACCEPT = 0, IPCA_DENY, IPCA_IGNORE} ipcactions_t; typedef vector > sharedNode_vector; // contact visibility: // HIDDEN - not shown // VISIBLE - shown typedef enum { VISIBILITY_UNKNOWN = -1, HIDDEN = 0, VISIBLE = 1, INACTIVE = 2, BLOCKED = 3 } visibility_t; typedef enum { PUTNODES_APP, PUTNODES_SYNC, PUTNODES_SYNCDEBRIS } putsource_t; // maps handle-index pairs to file attribute handle. map value is (file attribute handle, tag) typedef map, pair > fa_map; enum class SyncRunState { Pending, Loading, Run, Pause, /* do not use this state in new code; pausing a sync should actually use Suspend state */ Suspend, Disable }; enum ScanResult { SCAN_INPROGRESS, SCAN_SUCCESS, SCAN_FSID_MISMATCH, SCAN_INACCESSIBLE }; // ScanResult enum SyncError { UNLOADING_SYNC = -2, DECONFIGURING_SYNC = -1, NO_SYNC_ERROR = 0, UNKNOWN_ERROR = 1, UNSUPPORTED_FILE_SYSTEM = 2, // File system type is not supported INVALID_REMOTE_TYPE = 3, // Remote type is not a folder that can be synced INVALID_LOCAL_TYPE = 4, // Local path does not refer to a folder INITIAL_SCAN_FAILED = 5, // The initial scan failed LOCAL_PATH_TEMPORARY_UNAVAILABLE = 6, // Local path is temporarily unavailable: this is fatal when adding a sync LOCAL_PATH_UNAVAILABLE = 7, // Local path is not available (can't be open) REMOTE_NODE_NOT_FOUND = 8, // Remote node does no longer exists STORAGE_OVERQUOTA = 9, // Account reached storage overquota ACCOUNT_EXPIRED = 10, // Your plan has expired FOREIGN_TARGET_OVERSTORAGE = 11, // Sync transfer fails (upload into an inshare whose account is overquota) REMOTE_PATH_HAS_CHANGED = 12, // (obsolete -> changing remote path is not an error) // REMOTE_PATH_DELETED = 13, // (obsolete -> unified with REMOTE_NODE_NOT_FOUND) SHARE_NON_FULL_ACCESS = 14, // Existing inbound share sync or part thereof lost full access LOCAL_FILESYSTEM_MISMATCH = 15, // Filesystem fingerprint does not match the one stored for the synchronization PUT_NODES_ERROR = 16, // Error processing put nodes result ACTIVE_SYNC_BELOW_PATH = 17, // There's a synced node below the path to be synced ACTIVE_SYNC_ABOVE_PATH = 18, // There's a synced node above the path to be synced REMOTE_NODE_MOVED_TO_RUBBISH = 19, // Moved to rubbish REMOTE_NODE_INSIDE_RUBBISH = 20, // Attempted to be added in rubbish VBOXSHAREDFOLDER_UNSUPPORTED = 21, // Found unsupported VBoxSharedFolderFS LOCAL_PATH_SYNC_COLLISION = 22, // Local path includes a synced path or is included within one ACCOUNT_BLOCKED = 23, // Account blocked UNKNOWN_TEMPORARY_ERROR = 24, // Unknown temporary error TOO_MANY_ACTION_PACKETS = 25, // Too many changes in account, local state discarded LOGGED_OUT = 26, // Logged out //WHOLE_ACCOUNT_REFETCHED = 27, // obsolete. was: The whole account was reloaded, missed actionpacket changes could not have been applied //MISSING_PARENT_NODE = 28, // obsolete. was: Setting a new parent to a parent whose LocalNode is missing its corresponding Node crossref BACKUP_MODIFIED = 29, // Backup has been externally modified. BACKUP_SOURCE_NOT_BELOW_DRIVE = 30, // Backup source path not below drive path. SYNC_CONFIG_WRITE_FAILURE = 31, // Unable to write sync config to disk. ACTIVE_SYNC_SAME_PATH = 32, // There's a synced node at the path to be synced COULD_NOT_MOVE_CLOUD_NODES = 33, // rename() failed COULD_NOT_CREATE_IGNORE_FILE = 34, // Couldn't create a sync's initial ignore file. SYNC_CONFIG_READ_FAILURE = 35, // Couldn't read sync configs from disk. UNKNOWN_DRIVE_PATH = 36, // Sync's drive path isn't known. INVALID_SCAN_INTERVAL = 37, // The user's specified an invalid scan interval. NOTIFICATION_SYSTEM_UNAVAILABLE = 38, // Filesystem notification subsystem has encountered an unrecoverable error. UNABLE_TO_ADD_WATCH = 39, // Unable to add a filesystem watch. UNABLE_TO_RETRIEVE_ROOT_FSID = 40, // Unable to retrieve a sync root's FSID. UNABLE_TO_OPEN_DATABASE = 41, // Unable to open state cache database. INSUFFICIENT_DISK_SPACE = 42, // Insufficient space for download. FAILURE_ACCESSING_PERSISTENT_STORAGE = 43, // Failure accessing to persistent storage MISMATCH_OF_ROOT_FSID = 44, // The sync root's FSID changed. So this is a different folder. And, we can't identify the old sync db as the name depends on this FILESYSTEM_FILE_IDS_ARE_UNSTABLE = 45, // On MAC in particular, the FSID of a file in an exFAT drive can and does change spontaneously and frequently FILESYSTEM_ID_UNAVAILABLE = 46, // If we can't get a filesystem's id UNABLE_TO_RETRIEVE_DEVICE_ID = 47, // Unable to retrieve the ID of current device LOCAL_PATH_MOUNTED = 48, // The local path is a FUSE mount. }; enum SyncWarning { NO_SYNC_WARNING = 0, LOCAL_IS_FAT = 1, // Found FAT (not a failure per se) LOCAL_IS_HGFS = 2, // Found HGFS (not a failure per se) }; // Joins an error code (`error`) with more detailed error/warning codes specific for Syncs using SyncErrorInfo = std::tuple; typedef enum { SYNCDEL_NONE, SYNCDEL_DELETED, SYNCDEL_INFLIGHT, SYNCDEL_BIN, SYNCDEL_DEBRIS, SYNCDEL_DEBRISDAY, SYNCDEL_FAILED } syncdel_t; typedef vector localnode_vector; // fsid is not necessarily unique because multiple filesystems may be involved // Hence, we use a multimap and check other parameters too when looking for a match. typedef multimap fsid_localnode_map; // A similar type for looking up LocalNode by node handle, analagously // Keep the type separate by inheriting typedef multimap nodehandle_localnode_map; typedef set localnode_set; typedef multimap idlocalnode_map; #ifdef USE_INOTIFY using WatchEntry = pair; using WatchMap = multimap; using WatchMapIterator = WatchMap::iterator; #endif // USE_INOTIFY enum WatchResult { // Unable to add a watch due to bad path, etc. WR_FAILURE, // Unable to add a watch due to resource limits. WR_FATAL, // Successfully added a watch. WR_SUCCESS }; // WatchResult typedef set node_set; // enumerates a node's children typedef list > sharedNode_list; // undefined node handle const handle UNDEF = ~(handle)0; #define ISUNDEF(h) (!((h) + 1)) typedef list transferslot_list; // map a FileFingerprint to the transfer for that FileFingerprint typedef multimap transfer_multimap; template class deque_with_lazy_bulk_erase { // This is a wrapper class for deque. Erasing an element from the middle of a deque is not cheap since all the subsequent elements need to be shuffled back. // This wrapper intercepts the erase() calls for single items, and instead marks each one as 'erased'. // The supplied template class E contains the normal deque entry T, plus a flag or similar to mark an entry erased. // Any other operation on the deque performs all the gathered erases in a single std::remove_if for efficiency. // This makes an enormous difference when cancelling 100k transfers in MEGAsync's transfers window for example. deque mDeque; size_t nErased = 0; public: typedef typename deque::iterator iterator; void erase(iterator i) { assert(i != mDeque.end()); i->erase(); ++nErased; } void applyErase() { if (nErased) { // quite often the elements are at the front, no need to traverse the whole thing // removal from the front or back of a deque is cheap while (nErased && !mDeque.empty() && mDeque.front().isErased()) { mDeque.pop_front(); --nErased; } while (nErased && !mDeque.empty() && mDeque.back().isErased()) { mDeque.pop_back(); --nErased; } if (nErased) { auto newEnd = std::remove_if(mDeque.begin(), mDeque.end(), [](const E& e) { return e.isErased(); } ); mDeque.erase(newEnd, mDeque.end()); nErased = 0; } } } size_t size() { applyErase(); return mDeque.size(); } size_t empty() { applyErase(); return mDeque.empty(); } void clear() { mDeque.clear(); } iterator begin(bool canHandleErasedElements = false) { if (!canHandleErasedElements) applyErase(); return mDeque.begin(); } iterator end(bool canHandleErasedElements = false) { if (!canHandleErasedElements) applyErase(); return mDeque.end(); } void push_front(T t) { applyErase(); mDeque.push_front(E(t)); } void push_back(T t) { applyErase(); mDeque.push_back(E(t)); } void insert(iterator i, T t) { applyErase(); mDeque.insert(i, E(t)); } T& operator[](size_t n) { applyErase(); return mDeque[n]; } }; template class mapWithLookupExisting : public map { typedef map base; // helps older gcc public: T2* lookupExisting(T1 key) { auto it = base::find(key); if (it == base::end()) return nullptr; return &it->second; } }; // map a request tag with pending dbids of transfers and files typedef map > pendingdbid_map; // map a request tag with a pending dns request typedef map pendinghttp_map; // maps node handles to Node pointers typedef map> node_map; // maps node handles to Share pointers typedef map> share_map; // maps node handles NewShare pointers typedef list newshare_list; // generic handle vector typedef vector handle_vector; // node and user vectors typedef vector user_vector; typedef vector useralert_vector; typedef vector pcr_vector; // actual user data (indexed by userid) typedef map user_map; // maps user handles to userids typedef map uh_map; // maps lowercase user e-mail addresses to userids typedef map um_map; // file attribute fetch map typedef map faf_map; // file attribute fetch channel map typedef map fafc_map; // transfer type typedef enum { GET = 0, PUT, API, NONE } direction_t; typedef enum { LARGEFILE = 0, SMALLFILE } filesizetype_t; struct StringCmp { bool operator()(const string* a, const string* b) const { return *a < *b; } }; typedef map handledrn_map; typedef multimap dsdrn_map; typedef list dr_list; typedef list drs_list; // these correspond to MegaApi::STATE_SYNCED etc typedef enum { TREESTATE_NONE = 0, TREESTATE_SYNCED, TREESTATE_PENDING, TREESTATE_SYNCING, TREESTATE_IGNORED, } treestate_t; typedef enum { TRANSFERSTATE_NONE = 0, TRANSFERSTATE_QUEUED, TRANSFERSTATE_ACTIVE, TRANSFERSTATE_PAUSED, TRANSFERSTATE_RETRYING, TRANSFERSTATE_COMPLETING, TRANSFERSTATE_COMPLETED, TRANSFERSTATE_CANCELLED, TRANSFERSTATE_FAILED } transferstate_t; typedef map> handlepcr_map; // Type-Value (for user attributes) typedef vector string_vector; typedef map string_map; typedef multimap integer_map; typedef map userattr_map; typedef enum { RECOVER_WITH_MASTERKEY = 9, RECOVER_WITHOUT_MASTERKEY = 10, CANCEL_ACCOUNT = 21, CHANGE_EMAIL = 12 } recovery_t; typedef enum { EMAIL_REMOVED = 0, EMAIL_PENDING_REMOVED = 1, EMAIL_PENDING_ADDED = 2, EMAIL_FULLY_ACCEPTED = 3 } emailstatus_t; #define DEFINE_RETRY_REASONS(expander) \ expander(0, RETRY_NONE) \ expander(1, RETRY_CONNECTIVITY) \ expander(2, RETRY_SERVERS_BUSY) \ expander(3, RETRY_API_LOCK) \ expander(4, RETRY_RATE_LIMIT) \ expander(5, RETRY_LOCAL_LOCK) \ expander(6, RETRY_UNKNOWN) typedef enum { #define DEFINE_RETRY_REASON(index, name) name = index, DEFINE_RETRY_REASONS(DEFINE_RETRY_REASON) #undef DEFINE_RETRY_REASON } retryreason_t; typedef enum { STORAGE_UNKNOWN = -9, STORAGE_GREEN = 0, // there is storage is available STORAGE_ORANGE = 1, // storage is almost full STORAGE_RED = 2, // storage is full // STORAGE_CHANGE = 3, // obsolete (SDK fetches the state and notify directly) STORAGE_PAYWALL = 4, // storage is full and user didn't remedy despite of warnings } storagestatus_t; enum SmsVerificationState { // These values (except unknown) are delivered from the servers SMS_STATE_UNKNOWN = -1, // Flag was not received SMS_STATE_NOT_ALLOWED = 0, // No SMS allowed SMS_STATE_ONLY_UNBLOCK = 1, // Only unblock SMS allowed SMS_STATE_FULL = 2 // Opt-in and unblock SMS allowed }; typedef enum { END_CALL_REASON_REJECTED = 0x02, /// 1on1 call was rejected while ringing END_CALL_REASON_BY_MODERATOR = 0x06, /// group or meeting call has been ended by moderator } endCall_t; typedef unsigned int achievement_class_id; typedef map achievements_map; struct RecentActionBucketMeta { handle user = UNDEF; string userEmail; handle parent = UNDEF; bool updated = false; bool media = false; }; struct recentaction { RecentActionBucketMeta meta; m_time_t time = 0; string id; sharedNode_vector nodes; }; typedef vector recentactions_vector; typedef enum { BIZ_STATUS_UNKNOWN = -2, BIZ_STATUS_EXPIRED = -1, BIZ_STATUS_INACTIVE = 0, BIZ_STATUS_ACTIVE = 1, BIZ_STATUS_GRACE_PERIOD = 2 } BizStatus; typedef enum { BIZ_MODE_UNKNOWN = -1, BIZ_MODE_SUBUSER = 0, BIZ_MODE_MASTER = 1 } BizMode; typedef enum { ACCOUNT_TYPE_UNKNOWN = -1, ACCOUNT_TYPE_FREE = 0, ACCOUNT_TYPE_PROI = 1, ACCOUNT_TYPE_PROII = 2, ACCOUNT_TYPE_PROIII = 3, ACCOUNT_TYPE_LITE = 4, ACCOUNT_TYPE_STARTER = 11, ACCOUNT_TYPE_BASIC = 12, ACCOUNT_TYPE_ESSENTIAL = 13, ACCOUNT_TYPE_BUSINESS = 100, ACCOUNT_TYPE_PRO_FLEXI = 101, ACCOUNT_TYPE_FEATURE = 99999 } AccountType; typedef enum { ACTION_CREATE_ACCOUNT = 0, ACTION_RESUME_ACCOUNT = 1, ACTION_CANCEL_ACCOUNT = 2, ACTION_CREATE_EPLUSPLUS_ACCOUNT = 3, ACTION_RESUME_EPLUSPLUS_ACCOUNT = 4, } AccountActionType; typedef enum { AUTH_METHOD_UNKNOWN = -1, AUTH_METHOD_SEEN = 0, AUTH_METHOD_FINGERPRINT = 1, // used only for AUTHRING_ED255 AUTH_METHOD_SIGNATURE = 2, // used only for signed keys (RSA and Cu25519) } AuthMethod; typedef std::map AuthRingsMap; typedef enum { REASON_ERROR_UNKNOWN = -1, REASON_ERROR_NO_ERROR = 0, REASON_ERROR_UNSERIALIZE_NODE = 1, REASON_ERROR_DB_IO = 2, REASON_ERROR_DB_FULL = 3, REASON_ERROR_DB_INDEX_OVERFLOW = 4, REASON_ERROR_NO_JSCD = 5, REASON_ERROR_REGENERATE_JSCD = 6, REASON_ERROR_DB_CORRUPT = 7, REASON_ERROR_DB_UNKNOWN = 8, } ErrorReason; // enum matching 1:1 MegaEvent::NetworkActivityChannel enum NetworkActivityChannel : int { SC, CS, }; // enum matching 1:1 MegaEvent::NetworkActivityType enum NetworkActivityType : int { REQUEST_SENT, REQUEST_RECEIVED, REQUEST_ERROR, }; //#define MEGA_MEASURE_CODE // uncomment this to track time spent in major subsystems, and log it every 2 minutes, with extra control from megacli namespace CodeCounter { // Some classes that allow us to easily measure the number of times a block of code is called, and the sum of the time it takes. // Only enabled if MEGA_MEASURE_CODE is turned on. // Usage generally doesn't need to be protected by the macro as the classes and methods will be empty when not enabled. using namespace std::chrono; struct ScopeStats { #ifdef MEGA_MEASURE_CODE uint64_t count = 0; uint64_t starts = 0; uint64_t finishes = 0; high_resolution_clock::duration timeSpent{}; high_resolution_clock::duration longest{}; std::string name; ScopeStats(std::string s) : name(std::move(s)) {} inline string report(bool reset = false) { string s = " " + name + ": " + std::to_string(count) + " " + std::to_string(duration_cast(timeSpent).count()) + " " + std::to_string(duration_cast(longest).count()); if (reset) { count = 0; starts -= finishes; finishes = 0; timeSpent = high_resolution_clock::duration{}; longest = high_resolution_clock::duration{}; } return s; } #else ScopeStats(std::string) {} #endif }; struct DurationSum { #ifdef MEGA_MEASURE_CODE high_resolution_clock::duration sum{ 0 }; high_resolution_clock::time_point deltaStart; bool started = false; inline void start(bool b = true) { if (b && !started) { deltaStart = high_resolution_clock::now(); started = true; } } inline void stop(bool b = true) { if (b && started) { sum += high_resolution_clock::now() - deltaStart; started = false; } } inline bool inprogress() { return started; } inline string report(bool reset = false) { string s = std::to_string(std::chrono::duration_cast(sum).count()); if (reset) sum = high_resolution_clock::duration{ 0 }; return s; } #else inline void start(bool = true) { } inline void stop(bool = true) { } #endif }; struct ScopeTimer { #ifdef MEGA_MEASURE_CODE ScopeStats& scope; high_resolution_clock::time_point blockStart; high_resolution_clock::duration diff{}; bool done = false; ScopeTimer(ScopeStats& sm) : scope(sm), blockStart(high_resolution_clock::now()) { ++scope.starts; } ~ScopeTimer() { complete(); } high_resolution_clock::duration timeSpent() { return high_resolution_clock::now() - blockStart; } void complete() { // can be called early in which case the destructor's call is ignored if (!done) { ++scope.count; ++scope.finishes; diff = high_resolution_clock::now() - blockStart; scope.timeSpent += diff; if (diff > scope.longest) scope.longest = diff; done = true; } } #else ScopeTimer(ScopeStats&) {} void complete() {} #endif }; } // Hold the status of a status variable class CacheableStatus : public Cacheable { public: enum Type { STATUS_UNKNOWN = 0, STATUS_STORAGE = 1, STATUS_BUSINESS = 2, STATUS_BLOCKED = 3, STATUS_PRO_LEVEL = 4, STATUS_FEATURE_LEVEL = 5, STATUS_ROOT_FOLDER_LINK_HANDLE = 6, }; CacheableStatus(Type type, int64_t value); // serializes the object to a string bool serialize(string* data) const override; // deserializes the string to a SyncConfig object. Returns null in case of failure // returns a pointer to the unserialized value, owned by MegaClient passed as parameter static CacheableStatus* unserialize(MegaClient *client, const std::string& data); Type type() const; int64_t value() const; void setValue(const int64_t value); string typeToStr(); static string typeToStr(Type type); private: Type mType = STATUS_UNKNOWN; int64_t mValue = 0; }; typedef enum { INVALID = -1, TWO_WAY = 0, UP_SYNC = 1, DOWN_SYNC = 2, CAMERA_UPLOAD = 3, MEDIA_UPLOAD = 4, BACKUP_UPLOAD = 5 } BackupType; typedef mega::byte ChatOptions_t; struct ChatOptions { public: enum: ChatOptions_t { kEmpty = 0x00, kSpeakRequest = 0x01, kWaitingRoom = 0x02, kOpenInvite = 0x04, }; // update with new options added, to get the max value allowed, with regard to the existing options static constexpr ChatOptions_t maxValidValue = kSpeakRequest | kWaitingRoom | kOpenInvite; ChatOptions(): mChatOptions(ChatOptions::kEmpty){} ChatOptions(ChatOptions_t options): mChatOptions(options){} ChatOptions(bool speakRequest, bool waitingRoom , bool openInvite) : mChatOptions(static_cast((speakRequest ? kSpeakRequest : 0) | (waitingRoom ? kWaitingRoom : 0) | (openInvite ? kOpenInvite : 0))) { } // setters/modifiers void set(ChatOptions_t val) { mChatOptions = val; } void add(ChatOptions_t val) { mChatOptions = mChatOptions | val; } void remove(ChatOptions_t val) { mChatOptions = mChatOptions & static_cast(~val); } void updateSpeakRequest(bool enabled) { enabled ? add(kSpeakRequest) : remove(kSpeakRequest);} void updateWaitingRoom(bool enabled) { enabled ? add(kWaitingRoom) : remove(kWaitingRoom);} void updateOpenInvite(bool enabled) { enabled ? add(kOpenInvite) : remove(kOpenInvite);} // getters ChatOptions_t value() const { return mChatOptions; } bool areEqual(ChatOptions_t val) const { return mChatOptions == val; } bool speakRequest() const { return mChatOptions & kSpeakRequest; } bool waitingRoom() const { return mChatOptions & kWaitingRoom; } bool openInvite() const { return mChatOptions & kOpenInvite; } bool isValid() const { return static_cast(mChatOptions) <= static_cast(maxValidValue); } bool isEmpty() const { return mChatOptions == kEmpty; } protected: ChatOptions_t mChatOptions = kEmpty; }; enum VersioningOption { // In the cases where these options are specified for uploads, the `ov` flag will be // set if there is a pre-existing node in the target folder, with the same name. NoVersioning, // Node will be put directly to parent, with no versions, and no other node affected ClaimOldVersion, // The Node specified by `ov` (if any) will become the first version of the node put ReplaceOldVersion, // the Node specified by `ov` (if any) will be deleted, and this new node takes its place, retaining any version chain. UseLocalVersioningFlag, // One of the two above will occur, based on the versions_disabled flag UseServerVersioningFlag // One of those two will occur, based on the API's current state of that flag }; enum class SyncWaitReason { NoReason = 0, FileIssue, MoveOrRenameCannotOccur, DeleteOrMoveWaitingOnScanning, DeleteWaitingOnMoves, UploadIssue, DownloadIssue, CannotCreateFolder, CannotPerformDeletion, SyncItemExceedsSupportedTreeDepth, FolderMatchedAgainstFile, LocalAndRemoteChangedSinceLastSyncedState_userMustChoose, LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose, NamesWouldClashWhenSynced, SyncWaitReason_LastPlusOne }; enum class PathProblem : unsigned short { NoProblem = 0, FileChangingFrequently = 1, IgnoreRulesUnknown = 2, DetectedHardLink = 3, DetectedSymlink = 4, DetectedSpecialFile = 5, DifferentFileOrFolderIsAlreadyPresent = 6, ParentFolderDoesNotExist = 7, FilesystemErrorDuringOperation = 8, NameTooLongForFilesystem = 9, CannotFingerprintFile = 10, DestinationPathInUnresolvedArea = 11, MACVerificationFailure = 12, UnknownDownloadIssue = 13, DeletedOrMovedByUser = 14, FileFolderDeletedByUser = 15, MoveToDebrisFolderFailed = 16, IgnoreFileMalformed = 17, FilesystemErrorListingFolder = 18, // FilesystemErrorIdentifyingFolderContent = 19, -> obsolete WaitingForScanningToComplete = 20, WaitingForAnotherMoveToComplete = 21, SourceWasMovedElsewhere = 22, FilesystemCannotStoreThisName = 23, CloudNodeInvalidFingerprint = 24, CloudNodeIsBlocked = 25, PutnodeDeferredByController = 26, PutnodeCompletionDeferredByController = 27, PutnodeCompletionPending = 28, UploadDeferredByController = 29, DetectedNestedMount = 30, PathProblem_LastPlusOne }; const char* syncWaitReasonDebugString(SyncWaitReason r); const char* syncPathProblemDebugString(PathProblem r); class CancelToken { // A small item with representation shared between many objects // They can all be cancelled in one go by setting the token flag true shared_ptr flag; public: // invalid token, can't be cancelled. No storage CancelToken() {} // create with a token available to be cancelled explicit CancelToken(bool value) : flag(std::make_shared(value)) { if (value) { ++tokensCancelledCount; } } // cancel() can be invoked from any thread void cancel() { if (flag) { *flag = true; ++tokensCancelledCount; } } bool isCancelled() const { return !!flag && *flag; } bool exists() { return !!flag; } static std::atomic tokensCancelledCount; static bool haveAnyCancelsOccurredSince(uint32_t& lastKnownCancelCount) { if (lastKnownCancelCount == tokensCancelledCount.load()) { return false; } else { lastKnownCancelCount = tokensCancelledCount.load(); return true; } } }; typedef std::map nodePtr_map; enum ExclusionState : unsigned char { // Node's definitely excluded. ES_EXCLUDED, // Node's definitely included. ES_INCLUDED, // Node has an indeterminate exclusion state. ES_UNKNOWN, // No rule matched (so a higher level .megaignore should be checked) ES_UNMATCHED }; // ExclusionState struct StorageInfo { m_off_t mAvailable = 0; m_off_t mCapacity = 0; m_off_t mUsed = 0; }; // StorageInfo struct JSCData { // Verifies that the sync config database hasn't been tampered with. std::string authenticationKey; // Used to encipher the sync config database's content. std::string cipherKey; // The name of this user's sync config databases. std::string fileName; }; // JSCData #ifdef ENABLE_CHAT class ScheduledFlags; class ScheduledMeeting; class ScheduledRules; class TextChat; using textchat_map = map; using textchat_vector = vector; static constexpr int sfu_invalid_id = -1; #endif // ENABLE_CHAT // Opaque filesystem fingerprint. class fsfp_t; // Convenience. using fsfp_ptr_t = std::shared_ptr; // Convenience. using FileAccessPtr = std::unique_ptr; using FileAccessSharedPtr = std::shared_ptr; using FileAccessWeakPtr = std::weak_ptr; using FileSystemAccessPtr = std::unique_ptr; template using FromNodeHandleMap = std::map; using NodeHandleQueue = std::deque; using NodeHandleSet = std::set; using NodeHandleVector = std::vector; // For metaprogramming. template struct IsPath; // Convenience. template struct EnableIfPath : std::enable_if::value, T> { }; // EnableIfPath template using FromStringMap = std::map; } // namespace mega #define MEGA_DISABLE_COPY(class_name) \ class_name(const class_name&) = delete; \ class_name& operator=(const class_name&) = delete; #define MEGA_DISABLE_MOVE(class_name) \ class_name(class_name&&) = delete; \ class_name& operator=(class_name&&) = delete; #define MEGA_DISABLE_COPY_MOVE(class_name) \ MEGA_DISABLE_COPY(class_name) \ MEGA_DISABLE_MOVE(class_name) #define MEGA_DEFAULT_COPY(class_name) \ class_name(const class_name&) = default; \ class_name& operator=(const class_name&) = default; #define MEGA_DEFAULT_MOVE(class_name) \ class_name(class_name&&) = default; \ class_name& operator=(class_name&&) = default; #define MEGA_DEFAULT_COPY_MOVE(class_name) \ MEGA_DEFAULT_COPY(class_name) \ MEGA_DEFAULT_MOVE(class_name) typedef std::pair StringPair; struct StringKeyPair { std::string privKey; std::string pubKey; StringKeyPair(std::string&& privKey, std::string&& pubKey) : privKey(std::move(privKey)), pubKey(std::move(pubKey)) {} }; // A simple busy-wait lock for lightweight mutual exclusion. class Spinlock { // Is the spinlock currently locked? std::atomic_flag mLocked; public: Spinlock() : mLocked() { // Necessary until C++20. mLocked.clear(); } Spinlock(const Spinlock& other) = delete; Spinlock& operator=(const Spinlock& rhs) = delete; // Acquire exclusive ownership of this lock. void lock() { // Poll until the loop is acquired. while (!try_lock()) ; } // Try and acquire exclusive ownership of this lock. bool try_lock() { return !mLocked.test_and_set(); } // Release exclusive ownership of this lock. void unlock() { mLocked.clear(); } }; // Spinlock namespace detail { template struct IsRecursiveMutex : std::false_type { }; // IsRecursiveMutex template<> struct IsRecursiveMutex : std::true_type { }; // IsRecursiveMutex template::value> class CheckableMutex { T mMutex; std::atomic mOwner; public: CheckableMutex() : mMutex() , mOwner(std::thread::id()) { } CheckableMutex(const CheckableMutex& other) = delete; CheckableMutex& operator=(const CheckableMutex& rhs) = delete; void lock() { auto id = std::this_thread::get_id(); assert(mOwner != id); mMutex.lock(); mOwner = id; } bool owns_lock() const { return mOwner == std::this_thread::get_id(); } bool try_lock() { auto id = std::this_thread::get_id(); if (mMutex.try_lock()) { mOwner = id; return true; } return false; } void unlock() { assert(mOwner == std::this_thread::get_id()); mOwner = std::thread::id(); mMutex.unlock(); } }; // CheckableMutex template class CheckableMutex { std::uint32_t mCount; mutable Spinlock mLock; T mMutex; std::thread::id mOwner; public: CheckableMutex() : mCount(0) , mLock() , mMutex() , mOwner() { } CheckableMutex(const CheckableMutex& other) = delete; CheckableMutex& operator=(const CheckableMutex& rhs) = delete; void lock() { auto id = std::this_thread::get_id(); mMutex.lock(); std::lock_guard guard(mLock); mCount = mCount + 1; mOwner = id; } bool owns_lock() const { auto id = std::this_thread::get_id(); std::lock_guard guard(mLock); return mOwner == id && mCount > 0; } bool try_lock() { auto id = std::this_thread::get_id(); if (!mMutex.try_lock()) return false; std::lock_guard guard(mLock); mCount = mCount + 1; mOwner = id; return true; } void unlock() { std::lock_guard guard(mLock); assert(mCount); assert(mOwner == std::this_thread::get_id()); if (!--mCount) mOwner = std::thread::id(); mMutex.unlock(); } }; // CheckableMutex } // detail // API supports user/node attributes up to 16KB. This constant is used to restrict clients sending larger values static constexpr size_t MAX_NODE_ATTRIBUTE_SIZE = 64 * 1024; // 64kB static constexpr size_t MAX_FILE_ATTRIBUTE_SIZE = 16 * 1024 * 1024; // 16MB using detail::CheckableMutex; /** * @brief Thread safe access to shared resource - accessing with scoped locking. * * The owner thread must call to initOwnerThread() before using shared resource. * Methods to access to shared resource: * - getReadGuardOwnerThread(): owner thread read access, no mutex held. * - getReadGuardOtherThread(): non-owner thread read access, mutex held. * - getWriteGuardOwnerThread(): owner thread write access, mutex held (for mutation). * * Returned guards control the lifetime of access, references obtained from getData() * are only valid while the guard lives. * * Keep guard lifetimes as short as possible. * * @note This class does not provide `getWriteGuardOtherThread`, as all write access to * SharedResource must be performed from the owner thread. It is the caller's responsibility to * switch to the owner thread before performing any write operation. * * @tparam SharedData Templatized data type stored inside the critical section. * @tparam MutexType Mutex type used to protect cross-thread access. */ template class SharedResource { public: SharedResource() = default; /** * @brief Initializes the owner thread for this SharedResource instance. * * Must be called exactly once by the thread that will be considered the "owner". * The owner thread is allowed to call getReadGuardOwnerThread() and getWriteGuardOwnerThread(). * * @note This function must be called before calling other access method; otherwise it will be * considered as an error */ void initOwnerThread() { assert(!mOwnerThreadId.has_value() && "SharedResource::initOwnerThread can be called only once"); mOwnerThreadId = std::this_thread::get_id(); } /** * @brief Obtain read-only access on the owner thread without taking the mutex. * * Intended for quick access when running on the owner thread. No mutex is acquired, as write * access can be done only from ownder thread by contract. * * @return A read guard. The reference returned by the guard's getData() is only valid while * the returned guard object remains alive. */ [[nodiscard]] auto getReadGuardOwnerThread() const { ensureExpectedThread(true); return ReadGuard(mData, nullptr); } /** * @brief Obtain read-only access from a non-owner thread while holding the mutex. * * Intended for any thread other than the owner thread. A mutex lock is acquired and held * for the lifetime of the returned guard, preventing concurrent access protected by the * same mutex. * * @return A read guard holding the mutex. The reference returned by by the guard's getData() is * only valid while the returned guard object remains alive (and thus the lock is held). */ [[nodiscard]] auto getReadGuardOtherThread() const { ensureExpectedThread(false); return ReadGuard(mData, &mMutex); } /** * @brief Obtain mutable access on the owner thread while holding the mutex. * * Intended for performing mutations of the shared data. A mutex lock is acquired and held * for the lifetime of the returned guard to synchronize with other threads that access the * resource under the same mutex. * * @return A write guard holding the mutex. The reference returned by by the guard's getData() * is only valid while the returned guard object remains alive (and thus the lock is held). */ [[nodiscard]] auto getWriteGuardOwnerThread() { ensureExpectedThread(true); return WriteGuard(mData, &mMutex); } private: /** * @brief Validates that the calling thread matches the expected ownership relationship. * * @note If expectedOwnerThread is `true`, the current thread must be the owner thread, * otherwise the current thread must not be the owner thread * * @param expectedOwnerThread Whether the calling context expects the owner thread `true` * or a non-owner thread `false`. */ void ensureExpectedThread(bool expectedOwnerThread) const { assert(mOwnerThreadId.has_value() && "SharedResource used without init"); assert((expectedOwnerThread ? std::this_thread::get_id() == *mOwnerThreadId : std::this_thread::get_id() != *mOwnerThreadId) && "SharedResource unexpected threadId"); } /** * @brief Base class for RAII guards providing scoped access to the owned data. * * The guard optionally acquires and holds a mutex lock for the lifetime of the guard * instance. Access to the underlying data is provided via getData(). * * @tparam DataType Data type exposed by this guard (const SharedData for read guards, * SharedData for write guards). */ template class GuardBase { public: /** * @brief Constructs a guard over the provided data, optionally locking a mutex. * * If a non-null mutex pointer is provided, a lock is acquired and held for the guard's * lifetime. If the mutex pointer is null, no locking is performed. * * @param data Reference to the data being protected / exposed. * @param mutex Pointer to a mutex to lock, or nullptr to avoid locking. */ GuardBase(DataType& data, MutexType* mutex): mOwner(data) { if (mutex) { mLock = std::unique_lock(*mutex); } } /// @brief Non-copyable: guards represent unique scoped access. GuardBase(const GuardBase&) = delete; /// @brief Non-copyable: guards represent unique scoped access. GuardBase& operator=(const GuardBase&) = delete; /** * @brief Returns a const reference to the owned data. * * The returned reference remains valid only while this guard is alive. */ const DataType& getData() const { return mOwner; } /** * @brief Returns a mutable reference to the owned data (only for non-const DataType). * * The returned reference remains valid only while this guard is alive. */ DataType& getData() { return mOwner; } private: /// Reference to the data owned by the enclosing SharedResource. DataType& mOwner; /// Lock held for the lifetime of the guard when constructed with a mutex. std::unique_lock mLock; }; /** * @brief Read guard type: exposes const access to SharedData. * * Used by getReadGuardOwnerThread() and getReadGuardOtherThread(). */ using ReadGuard = GuardBase; /** * @brief Write guard type: exposes mutable access to SharedData. * * Used by getWriteGuardOwnerThread(). */ using WriteGuard = GuardBase; /// Mutex protecting cross-thread access paths that require synchronization. mutable MutexType mMutex; /// The shared data protected by the guard/mutex contract. SharedData mData; /// Thread id of the owner thread; set by initOwnerThread(). std::optional mOwnerThreadId{}; }; // For convenience. #ifdef USE_IOS #define IOS_ONLY(i) i #define IOS_OR_POSIX(i, p) i #else // USE_IOS #define IOS_ONLY(i) #define IOS_OR_POSIX(i, p) p #endif // ! USE_IOS /** * @brief A simple LRU cache implementation. * * @tparam Key * @tparam Value */ template class LRUCache { public: LRUCache(std::size_t capacity): mCapacity(capacity) {} /** * @brief Inserts or updates a value associated with the given key. * * - If the key already exists, its position is moved to the front of the list (most recently * used). * - If the key does not exist and the cache is at full capacity, the least recently used * element (at the back) is removed. * * @param key The key to insert or update. * @param value The value to associate with the key. */ void put(const Key& key, const Value& value) { if (auto it = mMap.find(key); it != mMap.end()) { // Update value it->second->second = value; mList.splice(mList.begin(), mList, it->second); return; } // Remove las element if (mList.size() == mCapacity) { if (mCapacity == 0) return; auto last = mList.end(); --last; // point to the last element mMap.erase(last->first); mList.pop_back(); } mList.emplace_front(key, value); mMap[key] = mList.begin(); } /** * @brief Retrieves the value associated with the given key, if it exists. * * - If the key is found, move the corresponding element to the front (most recently used). * - If the key is not found, return std::nullopt. * * @param key The key to search for in the cache. * @return std::optional The value if found, or std::nullopt if not. */ std::optional get(const Key& key) { const auto it = mMap.find(key); if (it == mMap.end()) { return std::nullopt; } mList.splice(mList.begin(), mList, it->second); return it->second->second; } /** * @brief Returns the current number of elements in the cache. */ std::size_t size() const { return mList.size(); } private: // The front of the list is the most recently used element. // The back of the list is the least recently used element. std::list> mList; // An unordered_map that maps a Key to an iterator pointing // to the element in the list. This allows O(1) average-time lookups. std::unordered_map>::iterator> mMap; std::size_t mCapacity{1}; }; enum class PitagPurpose : char { Unknown = '.', Upload = 'U', CreateFolder = 'F', Import = 'I', Copy = 'C', CopyInternal = 'c', Sync = 'S', Backup = 'B', Password = 'P', Fuse = 'f', Helpdesk = 'H' }; enum class PitagTrigger : char { NotApplicable = '.', Picker = 'p', DragAndDrop = 'd', Camera = 'c', Scanner = 's', SyncAlgorithm = 'a', ShareFromApp = 'S', CameraCapture = 'C', ExplorerExtension = 'e', VoiceRecorder = 'v' }; enum class PitagNodeType : char { NotApplicable = '.', Folder = 'F', File = 'f' }; enum class PitagTarget : char { NotApplicable = '.', CloudDrive = 'D', Chat1To1 = 'c', ChatGroup = 'C', NoteToSelf = 's', IncomingShare = 'i', MultipleChats = 'M' }; enum class PitagImportSource : char { NotApplicable = '.', FolderLink = 'F', FileLink = 'f', AlbumLink = 'A', CloudDrive = 'D', Chat1To1 = 'c', ChatGroup = 'C', NoteToSelf = 's', IncomingShare = 'i' }; struct Pitag { PitagPurpose purpose = PitagPurpose::Unknown; PitagTrigger trigger = PitagTrigger::NotApplicable; PitagNodeType nodeType = PitagNodeType::NotApplicable; PitagTarget target = PitagTarget::NotApplicable; PitagImportSource importSource = PitagImportSource::NotApplicable; }; #endif sdk-10.11.0/include/mega/udp_socket.h000066400000000000000000000034351516266226600173210ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_UDP_SOCKET_H #define MEGA_UDP_SOCKET_H #include #include #include #include #include struct sockaddr_in; struct sockaddr_in6; struct sockaddr; namespace mega { class UdpSocket final { public: UdpSocket(const std::string& remoteIP, int remotePort); ~UdpSocket(); bool isIPv4() const; struct Communication { int code{}; std::string message; }; Communication sendSyncMessage(const std::string& message); Communication receiveSyncMessage(const std::chrono::high_resolution_clock::time_point& timeout); private: bool createRemoteAddress(const std::string& remoteIP, int remotePort); sockaddr* getSockaddr(); int mInetType{}; std::unique_ptr> mRemoteAddress; int mSocket{}; // // OS-specific implementations // bool initializeSocketSupport(); void cleanupSocketSupport(); bool openNonblockingSocket(); intmax_t sendtoWrapper(const std::string& message); static bool noDataYet(); void closeSocket(); static Communication getSocketError(); static int getSocketErrorCode(); }; } // namespace mega #endif // MEGA_UDP_SOCKET_H sdk-10.11.0/include/mega/udp_socket_tester.h000066400000000000000000000067421516266226600207130ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_UDP_SOCKET_TESTER_H #define MEGA_UDP_SOCKET_TESTER_H #include "mega/udp_socket.h" #include #include #include #include #include namespace mega { /** * @brief Mechanism for running a network connectivity test, by sending multiple messages on a * single socket. * It includes: * - encapsulate the logic for building messages; * - send all messages on the required socket; * - receive and validate replies; * - provide the results after all communication has finished. */ class UdpSocketTester { public: UdpSocketTester(const std::string& ip, uint16_t port); ~UdpSocketTester(); struct TestSuite { TestSuite(uint16_t loops, uint16_t shorts, uint16_t longs, uint16_t dnss, uint64_t userId); const uint16_t loopCount{}; const uint16_t shortMessageCount{}; const uint16_t longMessageCount{}; const uint16_t dnsMessageCount{}; const std::string& getShortMessage() const { return mShortMessage; } const std::string& getLongMessage() const { return mLongMessage; } const std::string& getDnsIPv4Message() const { return mDnsIPv4Message; } const std::string& getDnsIPv6Message() const { return mDnsIPv6Message; } enum class MessageType : char { SHORT = 'S', LONG = 'L', DNS = 'D', }; uint16_t totalMessageCount() const { return static_cast(loopCount * (shortMessageCount + longMessageCount + dnsMessageCount)); } private: std::string mShortMessage; std::string mLongMessage; std::string mDnsIPv4Message; std::string mDnsIPv6Message; }; bool startSuite(const TestSuite& suite); struct MessageResult { TestSuite::MessageType messageType; int errorCode; }; struct SocketResults { const uint16_t port; std::vector messageResults; std::map log; // {log message, counter} }; // Return {port, {{messageType, error}, ...}, logged messages} SocketResults getSocketResults(const std::chrono::high_resolution_clock::time_point& timeout); private: void sendMessage(TestSuite::MessageType type, const std::string& message); static void sleepIfMultipleOf(uint16_t multiFactor, uint16_t factor); void confirmFirst(TestSuite::MessageType type); void log(std::string&& action, std::string&& error); std::unique_ptr mSocket; static constexpr int REPLY_NOT_RECEIVED = -1111; SocketResults mTestResults; bool mRunning{}; std::string mShortMessage; std::string mLongMessage; std::string mDnsMessage; }; } // namespace mega #endif // MEGA_UDP_SOCKET_TESTER_H sdk-10.11.0/include/mega/user.h000066400000000000000000000224251516266226600161370ustar00rootroot00000000000000/** * @file mega/user.h * @brief Class for manipulating user / contact data * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_USER_H #define MEGA_USER_H 1 #include "attrmap.h" namespace mega { class UserAttribute; class UserAttributeManager; // user/contact struct MEGA_API User : public Cacheable { // user handle handle userhandle; // string identifier for API requests (either e-mail address or ASCII user // handle) string uid; // e-mail address string email; // visibility status visibility_t show; // shares by this user handle_set sharing; // contact establishment timestamp m_time_t ctime; BizMode mBizMode = BIZ_MODE_UNKNOWN; struct { bool keyring : 1; // private keys bool authring : 1; // authentication information of the contact (signing key) bool authcu255 : 1; // authentication information of the contact (Cu25519 key) bool lstint : 1; // last interaction with the contact bool puEd255 : 1; // public key for Ed25519 bool puCu255 : 1; // public key for Cu25519 bool sigPubk : 1; // signature for RSA public key bool sigCu255 : 1; // signature for Cu255199 public key bool avatar : 1; // avatar image bool firstname : 1; bool lastname : 1; bool country : 1; bool birthday : 1; // wraps status of birthday, birthmonth, birthyear bool email : 1; bool language : 1; // preferred language code bool pwdReminder : 1; // password-reminder-dialog information bool disableVersions : 1; // disable fileversioning bool noCallKit : 1; // disable CallKit bool contactLinkVerification : 1; // Verify contact requests with contact links bool richPreviews : 1; // enable messages with rich previews bool lastPsa : 1; bool rubbishTime : 1; // days to keep nodes in rubbish bin before auto clean bool storageState : 1; // state of the storage (0 = green, 1 = orange, 2 = red) bool geolocation : 1; // enable send geolocations bool cameraUploadsFolder : 1; // target folder for Camera Uploads bool myChatFilesFolder : 1; // target folder for my chat files bool pushSettings : 1; // push notification settings bool alias : 1; // user's aliases bool unshareablekey : 1; // key to encrypt unshareable node attributes bool devicenames : 1; // device or external drive names bool myBackupsFolder : 1; // target folder for My Backups bool cookieSettings : 1; // bit map to indicate whether some cookies are enabled or not bool jsonSyncConfigData : 1; bool drivenames : 1; // drive names bool keys : 1; bool aPrefs : 1; // apps preferences bool ccPrefs : 1; // content consumption preferences bool enableTestNotifications : 1; // list of IDs for enabled notifications bool lastReadNotification : 1; // ID of last read notification bool lastActionedBanner : 1; // ID of last actioner banner bool enableTestSurveys: 1; // list of handles for enabled test surveys bool recentClearTimestamp: 1; // timestamp of clearing recent action history } changed; // user's public key AsymmCipher pubk; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { bool pubkrequested : 1; bool isTemporary : 1; }; #ifdef _MSC_VER #pragma warning(pop) #endif // actions to take after arrival of the public key deque> pkrs; private: std::unique_ptr mAttributeManager; // source tag int tag; public: void set(visibility_t, m_time_t); bool serialize(string*) const override; static User* unserialize(class MegaClient *, string*); bool unserializeAttributes(const char*& from, const char* upTo, char formatVersion); void removepkrs(MegaClient*); // attribute methods: set/get/expire... void setAttribute(attr_t at, const string& value, const string& version); bool updateAttributeIfDifferentVersion(attr_t at, const string& value, const string& version); void setAttributeExpired(attr_t at); const UserAttribute* getAttribute(attr_t at) const; void removeAttribute(attr_t at); void removeAttributeUpdateVersion(attr_t at, const string& version); // remove in up2/upv V3 ? // Set this to avoid requesting attributes already known to not exist from server. void cacheNonExistingAttributes(); static string attr2string(attr_t at); static string attr2longname(attr_t at); static attr_t string2attr(const char *name); static int needversioning(attr_t at); static char scope(attr_t at); static bool isAuthring(attr_t at); static size_t getMaxAttributeSize(attr_t at); enum { PWD_LAST_SUCCESS = 0x01, PWD_LAST_SKIPPED = 0x02, PWD_MK_EXPORTED = 0x04, PWD_DONT_SHOW = 0x08, PWD_LAST_LOGIN = 0x10 }; static const int PWD_SHOW_AFTER_ACCOUNT_AGE = 7 * 24 * 60 * 60; static const int PWD_SHOW_AFTER_LASTSUCCESS = 3 * 30 * 24 * 60 * 60; static const int PWD_SHOW_AFTER_LASTLOGIN = 14 * 24 * 60 * 60; static const int PWD_SHOW_AFTER_LASTSKIP = 3 * 30 * 24 * 60 * 60; static const int PWD_SHOW_AFTER_LASTSKIP_LOGOUT = 1 * 30 * 24 * 60 * 60; static bool mergePwdReminderData(int numDetails, const char *data, unsigned int size, string *newValue); static m_time_t getPwdReminderData(int numDetail, const char *data, unsigned int size); bool setChanged(attr_t at); void setTag(int newTag); int getTag(); void resetTag(); User(const char* = NULL); ~User() override; // merges the new values in the given destination. Returns true if it was changed. static bool mergeUserAttribute(attr_t type, const string_map& newValuesMap, string_map& destination); static string attributePrefixInTLV(attr_t type, bool modifier); }; class AuthRing { public: // create authring of 'type' from the encrypted data AuthRing(attr_t type, const string_map& authring = {}); // create authring of 'type' from the TLV value (undecrypted already, no Type nor Length) AuthRing(attr_t type, const string& authring); // return true if authring has changed (data can be pubKey or keySignature depending on authMethod) void add(handle uh, const std::string &fingerprint, AuthMethod authMethod); // assumes the key is already tracked for uh (otherwise, it will throw) void update(handle uh, AuthMethod authMethod); // return the authring as tlv container, ready to set as user's attribute [*!authring | *!authCu255 | *!authRSA] std::string *serialize(PrnGen &rng, SymmCipher &key) const; // return a binary buffer compatible with Webclient, to store authrings in user's attribute ^!keys std::string serializeForJS() const; // false if uh is not tracked in the authring bool isTracked(handle uh) const; // true for Cu25519 and RSA, false for Ed25519 bool isSignedKey() const; // true if key is tracked and authentication method is fingerprint/signature-verified bool areCredentialsVerified(handle uh) const; // returns AUTH_METHOD_UNKNOWN if no authentication is found for the given user AuthMethod getAuthMethod(handle uh) const; // returns the fingerprint of the public key for a given user, or empty string if user is not found string getFingerprint(handle uh) const; // returns the list of tracked users vector getTrackedUsers() const; // returns most significant 160 bits from SHA256, whether in binary or hexadecimal static string fingerprint(const string &pubKey, bool hexadecimal = false); // returns the authring type for a given attribute type associated to a public key static attr_t keyTypeToAuthringType(attr_t at); // returns the authring type for a given attribute type associated to a signature static attr_t signatureTypeToAuthringType(attr_t at); // returns the attribute type associated to the corresponding signature for a given authring type static attr_t authringTypeToSignatureType(attr_t at); // returns a human-friendly string for a given authentication method static string authMethodToStr(AuthMethod authMethod); static string toString(const AuthRing& authRing); bool needsUpdate() const { return mNeedsUpdate; } private: attr_t mType; map mFingerprint; map mAuthMethod; // indicates if the authring has changed and needs to update value in server bool mNeedsUpdate = false; bool deserialize(const std::string &authValue); }; } // namespace #endif sdk-10.11.0/include/mega/user_attribute.h000066400000000000000000000024031516266226600202140ustar00rootroot00000000000000#ifndef MEGA_USER_ATTRIBUTE_H #define MEGA_USER_ATTRIBUTE_H #include namespace mega { class UserAttributeDefinition; class UserAttribute { public: UserAttribute(const UserAttributeDefinition& def): mDefinition{def} {} void set(const std::string& value, const std::string& version); bool useVersioning() const; bool isValid() const { return mState == State::VALID; } void setValid() { mState = State::VALID; } void setExpired() { mState = State::EXPIRED; } bool isExpired() const { return mState == State::EXPIRED; } void setNotExisting() { mState = State::CACHED_NOT_EXISTING; mValue.clear(); } bool isNotExisting() const { return mState == State::CACHED_NOT_EXISTING; } const std::string& value() const { return mValue; } const std::string& version() const { return mVersion; } private: const UserAttributeDefinition& mDefinition; std::string mValue; std::string mVersion; enum class State { VALID, EXPIRED, CACHED_NOT_EXISTING, }; State mState = State::VALID; }; } // namespace #endif // MEGA_USER_ATTRIBUTE_H sdk-10.11.0/include/mega/user_attribute_definition.h000066400000000000000000000026061516266226600224310ustar00rootroot00000000000000#ifndef MEGA_USER_ATTRIBUTE_DEFINITION_H #define MEGA_USER_ATTRIBUTE_DEFINITION_H #include "user_attribute_types.h" #include #include namespace mega { class UserAttributeDefinition { public: static const UserAttributeDefinition* get(attr_t at); static attr_t getTypeForName(const std::string& name); static size_t getDefaultMaxSize() { return MAX_USER_ATTRIBUTE_SIZE; } char scope() const { return mScope; } const std::string& name() const { return mName; } const std::string& longName() const { return mLongName; } bool versioningEnabled() const { return mUseVersioning; } size_t maxSize() const { return mMaxSize; } private: UserAttributeDefinition(std::string&& name, std::string&& longName, int customOptions = NONE); // [*^+#%$][!~](actual name) static const std::unordered_map& getAllDefinitions(); const std::string mName; const std::string mLongName; char mScope = ATTR_SCOPE_UNKNOWN; bool mUseVersioning = true; size_t mMaxSize = 0; enum Opt { NONE, DISABLE_VERSIONING, MAKE_PROTECTED, MAKE_PRIVATE = 4, }; }; } // namespace #endif // MEGA_USER_ATTRIBUTE_DEFINITION_H sdk-10.11.0/include/mega/user_attribute_manager.h000066400000000000000000000031101516266226600217020ustar00rootroot00000000000000#ifndef MEGA_USER_ATTRIBUTE_MANAGER_H #define MEGA_USER_ATTRIBUTE_MANAGER_H #include "user_attribute.h" #include "user_attribute_types.h" #include #include namespace mega { class UserAttributeManager { public: void set(attr_t at, const std::string& value, const std::string& version); bool setIfNewVersion(attr_t at, const std::string& value, const std::string& version); bool setExpired(attr_t at); const UserAttribute* get(attr_t at) const; bool erase(attr_t at); bool eraseUpdateVersion(attr_t at, const std::string& version); void serializeAttributeFormatVersion(std::string& appendTo) const; static char unserializeAttributeFormatVersion(const char*& from); void serializeAttributes(std::string& appendTo) const; bool unserializeAttributes(const char*& from, const char* upTo, char formatVersion); static std::string getName(attr_t at); static std::string getLongName(attr_t at); static attr_t getType(const std::string& name); static char getScope(attr_t at); static int getVersioningEnabled(attr_t at); static size_t getMaxSize(attr_t at); void cacheNonExistingAttributes() { // used to avoid fetch from servers for attributes known to not exist mCacheNonExistingAttributes = true; } private: bool setNotExisting(attr_t at); bool isValid(attr_t at) const; // not Expired and not cached as Not Existing std::unordered_map mAttributes; bool mCacheNonExistingAttributes = false; }; } // namespace #endif // MEGA_USER_ATTRIBUTE_MANAGER_H sdk-10.11.0/include/mega/user_attribute_types.h000066400000000000000000000120201516266226600214340ustar00rootroot00000000000000#ifndef MEGA_USER_ATTRIBUTE_TYPES_H #define MEGA_USER_ATTRIBUTE_TYPES_H #include // required to define size_t namespace mega { enum attr_t { ATTR_UNKNOWN = -1, ATTR_AVATAR = 0, // public - byte array (image) or "none" magic value - non-versioned ATTR_FIRSTNAME = 1, // protected - char array - non-versioned ATTR_LASTNAME = 2, // protected - char array - non-versioned ATTR_AUTHRING = 3, // (deprecated) private - byte array - versioned ATTR_LAST_INT = 4, // private - byte array - versioned ATTR_ED25519_PUBK = 5, // public - byte array - versioned ATTR_CU25519_PUBK = 6, // public - byte array - versioned ATTR_KEYRING = 7, // (deprecated) private - byte array - versioned ATTR_SIG_RSA_PUBK = 8, // public - byte array - versioned ATTR_SIG_CU255_PUBK = 9, // public - byte array - versioned ATTR_COUNTRY = 10, // private - char array - non-versioned ATTR_BIRTHDAY = 11, // private - char array - non-versioned ATTR_BIRTHMONTH = 12, // private - char array - non-versioned ATTR_BIRTHYEAR = 13, // private - char array - non-versioned ATTR_LANGUAGE = 14, // private, non-encrypted - char array - non-versioned ATTR_PWD_REMINDER = 15, // private, non-encrypted - char array - non-versioned ATTR_DISABLE_VERSIONS = 16, // private, non-encrypted - char array - non-versioned ATTR_CONTACT_LINK_VERIFICATION = 17, // private, non-encrypted - char array - versioned ATTR_RICH_PREVIEWS = 18, // private - byte array - non-versioned ATTR_RUBBISH_TIME = 19, // private, non-encrypted - char array - non-versioned ATTR_LAST_PSA = 20, // private - char array - non-versioned ATTR_STORAGE_STATE = 21, // private, non-encrypted - char array - non-versioned ATTR_GEOLOCATION = 22, // private - byte array - non-versioned ATTR_CAMERA_UPLOADS_FOLDER = 23, // private - byte array - versioned ATTR_MY_CHAT_FILES_FOLDER = 24, // private - byte array - non-versioned ATTR_PUSH_SETTINGS = 25, // private, non-encrypted - char array - non-versioned ATTR_UNSHAREABLE_KEY = 26, // private - char array - versioned ATTR_ALIAS = 27, // private - byte array - versioned // ATTR_AUTHRSA = 28, // (deprecated) private - byte array ATTR_AUTHCU255 = 29, // (deprecated) private - byte array - versioned ATTR_DEVICE_NAMES = 30, // private - byte array - versioned ATTR_MY_BACKUPS_FOLDER = 31, // private, non-encrypted - char array - non-versioned // ATTR_BACKUP_NAMES = 32, // (deprecated) private - byte array - versioned ATTR_COOKIE_SETTINGS = 33, // private - byte array - non-versioned ATTR_JSON_SYNC_CONFIG_DATA = 34, // private - byte array - non-versioned // ATTR_DRIVE_NAMES = 35, // private - byte array - versioned (merged with ATTR_DEVICE_NAMES and // removed) ATTR_NO_CALLKIT = 36, // private, non-encrypted - char array - non-versioned ATTR_KEYS = 37, // private, non-encrypted (but encrypted to derived key from MK) - byte array - // versioned ATTR_APPS_PREFS = 38, // private - byte array - versioned ATTR_CC_PREFS = 39, // private - byte array - versioned ATTR_VISIBLE_WELCOME_DIALOG = 40, // private, non-encrypted - byte array - versioned ATTR_VISIBLE_TERMS_OF_SERVICE = 41, // private, non-encrypted - byte array - versioned ATTR_PWM_BASE = 42, // private, non-encrypted (controlled by API) - char array - non-versioned ATTR_ENABLE_TEST_NOTIFICATIONS = 43, // private, non-encrypted - char array - versioned ATTR_LAST_READ_NOTIFICATION = 44, // private, non-encrypted - char array - versioned ATTR_LAST_ACTIONED_BANNER = 45, // private, non-encrypted - char array - versioned ATTR_ENABLE_TEST_SURVEYS = 46, // private - non-encrypted - char array - non-versioned // ATTR_WELCOME_PDF_COPIED = 47, // (obsolete) private - non-encrypted - char array ATTR_SYNC_DESIRED_STATE = 48, // private - byte array - versioned ATTR_S4 = 49, // private - non-encrypted - char array (1/0) <-- TBD ATTR_S4_CONTAINER = 50, // private - non-encrypted - char array (B64 node's handle) <-- TBD ATTR_DEV_OPT = 51, // private - encrypted - byte array ATTR_RECENT_CLEAR_TIMESTAMP = 52, // private - encrypted - byte array - non-versioned }; enum UserAttributeScope : char { ATTR_SCOPE_UNKNOWN = '\0', ATTR_SCOPE_PUBLIC_UNENCRYPTED = '+', ATTR_SCOPE_PROTECTED_UNENCRYPTED = '#', // contacts can fetch it but not give it out // to non-contacts ATTR_SCOPE_PRIVATE_UNENCRYPTED = '^', // can only be fetched by you ATTR_SCOPE_PRIVATE_ENCRYPTED = '*', // can only be fetched by you, and API cannot read it ATTR_SCOPE_BUSINESS_UNENCRYPTED = '%', // probably not used ATTR_SCOPE_BUSINESS_ENCRYPTED = '$', // not used }; static constexpr size_t MAX_USER_VAR_SIZE = 16 * 1024 * 1024; // 16MB - User attributes whose second character is ! or ~ (per example // *!dn, ^!keys", ...) static constexpr size_t MAX_USER_ATTRIBUTE_SIZE = 64 * 1024; // 64kB - Other user attributes } // namespace #endif // MEGA_USER_ATTRIBUTE_TYPES_H sdk-10.11.0/include/mega/useralerts.h000066400000000000000000000613421516266226600173530ustar00rootroot00000000000000/** * @file mega/usernotifications.h * @brief additional megaclient code for user notifications * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGAUSERNOTIFICATIONS_H #define MEGAUSERNOTIFICATIONS_H 1 #include "json.h" #include "name_id.h" #include "setandelement.h" #include "utils.h" #include namespace mega { struct UserAlertRaw { // notifications have a very wide range of fields; so for most we interpret them once we know the type. map fields; struct handletype { handle h; // file/folder int t; // type }; nameid t; // notification type JSON field(nameid nid) const; bool has(nameid nid) const; int getint(nameid nid, int) const; int64_t getint64(nameid nid, int64_t) const; handle gethandle(nameid nid, int handlesize, handle) const; nameid getnameid(nameid nid, nameid) const; string getstring(nameid nid, const char*) const; bool gethandletypearray(nameid nid, vector& v) const; bool getstringarray(nameid nid, vector& v) const; UserAlertRaw(); }; struct UserAlertPendingContact { handle u; // user handle string m; // email vector m2; // email list string n; // name UserAlertPendingContact(); }; namespace UserAlert { enum userAlertsSubtype : uint8_t { subtype_invalid = 0, subtype_new_Sched = 1, subtype_upd_Sched = 2, }; struct Base; using handle_alerttype_map_t = map; struct Base : public Cacheable { // shared fields from the notification or action nameid type; const m_time_t& ts() const { return pst.timestamp; } const handle& user() const { return pst.userHandle; } const string& email() const { return pst.userEmail; } void setEmail(const string& eml) { pst.userEmail = eml; } // if false, not worth showing, eg obsolete payment reminder bool relevant() const { return pst.relevant; } void setRelevant(bool r) { pst.relevant = r; } // user already saw it (based on 'last notified' time) bool seen() const { return pst.seen; } void setSeen(bool s) { pst.seen = s; } int tag; // incremented for each new one. There will be gaps sometimes due to merging. unsigned int id; Base(UserAlertRaw& un, unsigned int id); Base(nameid t, handle uh, const string& email, m_time_t timestamp, unsigned int id); virtual ~Base(); // get the same text the webclient would show for this alert (in english) virtual void text(string& header, string& title, MegaClient* mc); // look up the userEmail again in case it wasn't available before (or was changed) virtual void updateEmail(MegaClient* mc); virtual bool checkprovisional(handle ou, MegaClient* mc); void setRemoved() { mRemoved = true; } bool removed() const { return mRemoved; } protected: struct Persistent // variables to be persisted { m_time_t timestamp = 0; handle userHandle = 0; string userEmail; bool relevant = true; bool seen = false; } pst; bool serialize(string*) const override; static unique_ptr readBase(CacheableReader& r); static unique_ptr unserialize(string*); private: bool mRemoved = false; // useful to know when to remove from persist db }; struct IncomingPendingContact : public Base { handle mPcrHandle = UNDEF; bool requestWasDeleted; bool requestWasReminded; IncomingPendingContact(UserAlertRaw& un, unsigned int id); IncomingPendingContact(m_time_t dts, m_time_t rts, handle p, const string& email, m_time_t timestamp, unsigned int id); void initTs(m_time_t dts, m_time_t rts); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static IncomingPendingContact* unserialize(string*, unsigned id); }; struct ContactChange : public Base { int action; ContactChange(UserAlertRaw& un, unsigned int id); ContactChange(int c, handle uh, const string& email, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; virtual bool checkprovisional(handle ou, MegaClient* mc) override; bool serialize(string*) const override; static ContactChange* unserialize(string*, unsigned id); }; struct UpdatedPendingContactIncoming : public Base { int action; UpdatedPendingContactIncoming(UserAlertRaw& un, unsigned int id); UpdatedPendingContactIncoming(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static UpdatedPendingContactIncoming* unserialize(string*, unsigned id); }; struct UpdatedPendingContactOutgoing : public Base { int action; UpdatedPendingContactOutgoing(UserAlertRaw& un, unsigned int id); UpdatedPendingContactOutgoing(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static UpdatedPendingContactOutgoing* unserialize(string*, unsigned id); }; struct NewShare : public Base { handle folderhandle; NewShare(UserAlertRaw& un, unsigned int id); NewShare(handle h, handle uh, const string& email, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static NewShare* unserialize(string*, unsigned id); }; struct DeletedShare : public Base { handle folderHandle; string folderPath; string folderName; handle ownerHandle; DeletedShare(UserAlertRaw& un, unsigned int id); DeletedShare(handle uh, const string& email, handle removerhandle, handle folderhandle, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; virtual void updateEmail(MegaClient* mc) override; bool serialize(string*) const override; static DeletedShare* unserialize(string*, unsigned id); }; struct NewSharedNodes : public Base { handle parentHandle; vector fileNodeHandles; vector folderNodeHandles; NewSharedNodes(UserAlertRaw& un, unsigned int id); NewSharedNodes(handle uh, handle ph, m_time_t timestamp, unsigned int id, vector&& fileHandles, vector&& folderHandles); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static NewSharedNodes* unserialize(string*, unsigned id); }; struct RemovedSharedNode : public Base { vector nodeHandles; RemovedSharedNode(UserAlertRaw& un, unsigned int id); RemovedSharedNode(handle uh, m_time_t timestamp, unsigned int id, vector&& handles); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static RemovedSharedNode* unserialize(string*, unsigned id); }; struct UpdatedSharedNode : public Base { vector nodeHandles; UpdatedSharedNode(UserAlertRaw& un, unsigned int id); UpdatedSharedNode(handle uh, m_time_t timestamp, unsigned int id, vector&& handles); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static UpdatedSharedNode* unserialize(string*, unsigned id); }; struct Payment : public Base { bool success; int planNumber; Payment(UserAlertRaw& un, unsigned int id); Payment(bool s, int plan, m_time_t timestamp, unsigned int id, nameid paymentType); virtual void text(string& header, string& title, MegaClient* mc) override; string getProPlanName(); bool serialize(string*) const override; static Payment* unserialize(string*, unsigned id, nameid paymentType); }; struct PaymentReminder : public Base { m_time_t expiryTime; PaymentReminder(UserAlertRaw& un, unsigned int id); PaymentReminder(m_time_t expiry, unsigned int id); PaymentReminder(m_time_t creation, m_time_t expiry, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static PaymentReminder* unserialize(string*, unsigned id); }; struct Takedown : public Base { bool isTakedown; bool isReinstate; handle nodeHandle; Takedown(UserAlertRaw& un, unsigned int id); Takedown(bool down, bool reinstate, int t, handle nh, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static Takedown* unserialize(string*, unsigned id); }; struct SetTakedown: public Base { bool isTakedown; bool isReinstate; PublicLinkSet::LinkDeletionReason reason; handle setId; SetTakedown(UserAlertRaw& un, unsigned int id); SetTakedown(bool down, bool reinstate, PublicLinkSet::LinkDeletionReason downReason, handle sId, m_time_t timestamp, unsigned int id); virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; static SetTakedown* unserialize(string*, unsigned id); }; #ifdef ENABLE_CHAT struct ScheduledMeetingAlert: public Base { handle mChatid = UNDEF; handle mSchedMeetingHandle = UNDEF; handle mParentSchedId = UNDEF; m_time_t mStartDateTime = mega_invalid_timestamp; // overrides param ScheduledMeetingAlert(UserAlertRaw& un, unsigned int id); ScheduledMeetingAlert(handle _ou, m_time_t _ts, unsigned int _id, handle _chatid, handle _sm, handle _parentSchedId, m_time_t _startDateTime): Base(name_id::mcsmp, _ou, string(), _ts, _id), mChatid(_chatid), mSchedMeetingHandle(_sm), mParentSchedId(_parentSchedId), mStartDateTime(_startDateTime) {} static UserAlert::Base* unserializeScheduledMeeting(string* d, unsigned id); string* serializeScheduledMeeting(const userAlertsSubtype& subType, string* d) const; }; std::ostream& operator<<(std::ostream& oss, const ScheduledMeetingAlert& sma); struct NewScheduledMeeting: public ScheduledMeetingAlert { NewScheduledMeeting(UserAlertRaw& un, unsigned int id): ScheduledMeetingAlert(un, id) {} NewScheduledMeeting(handle _ou, m_time_t _ts, unsigned int _id, handle _chatid, handle _sm, handle _parentSchedId, m_time_t _startDateTime): ScheduledMeetingAlert(_ou, _ts, _id, _chatid, _sm, _parentSchedId, _startDateTime) {} virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string* d) const override; }; struct UpdatedScheduledMeeting: public ScheduledMeetingAlert { class Changeset { public: enum { CHANGE_TYPE_TITLE = 0x01, CHANGE_TYPE_DESCRIPTION = 0x02, CHANGE_TYPE_CANCELLED = 0x04, CHANGE_TYPE_TIMEZONE = 0x08, CHANGE_TYPE_STARTDATE = 0x10, CHANGE_TYPE_ENDDATE = 0x20, CHANGE_TYPE_RULES = 0x40, CHANGE_TYPE_SIZE = 7 // remember to update this when adding new values }; struct StrChangeset { string oldValue, newValue; }; struct TsChangeset { m_time_t oldValue, newValue; }; const StrChangeset* getUpdatedTitle() const { return mUpdatedTitle.get(); } const StrChangeset* getUpdatedTimeZone() const { return mUpdatedTimeZone.get(); } const TsChangeset* getUpdatedStartDateTime() const { return mUpdatedStartDateTime.get(); } const TsChangeset* getUpdatedEndDateTime() const { return mUpdatedEndDateTime.get(); } uint64_t getChanges() const { return mUpdatedFields.to_ullong(); } bool hasChanged(uint64_t changeType) const { return getChanges() & changeType; } string changeToString(uint64_t changeType) const; void addChange(uint64_t changeType, StrChangeset* = nullptr, TsChangeset* = nullptr); Changeset() = default; Changeset(const std::bitset& _bs, unique_ptr& _titleCS, unique_ptr& _tzCS, unique_ptr& _sdCS, unique_ptr& _edCS); Changeset(const Changeset& src) { replaceCurrent(src); } Changeset& operator=(const Changeset& src) { replaceCurrent(src); return *this; } Changeset(Changeset&&) = default; Changeset& operator=(Changeset&&) = default; ~Changeset() = default; private: /* * invariant: * - bitset size must be the maximum types of changes allowed * - if title changed, there must be previous and new title string * - if timezone changed, there must be previous and new timezone * - if StartDateTime changed, there must be previous and new StartDateTime * - if EndDateTime changed, there must be previous and new EndDateTime */ bool invariant() const { const auto changes = getChanges(); return (mUpdatedFields.size() == CHANGE_TYPE_SIZE && (!(changes & CHANGE_TYPE_TITLE) || mUpdatedTitle) && (!(changes & CHANGE_TYPE_TIMEZONE) || mUpdatedTimeZone) && (!(changes & CHANGE_TYPE_STARTDATE) || mUpdatedStartDateTime) && (!(changes & CHANGE_TYPE_ENDDATE) || mUpdatedEndDateTime)); } void replaceCurrent(const Changeset& src) { mUpdatedFields = src.mUpdatedFields; if (src.mUpdatedTitle) { mUpdatedTitle.reset(new StrChangeset{src.mUpdatedTitle->oldValue, src.mUpdatedTitle->newValue}); } if (src.mUpdatedTimeZone) { mUpdatedTimeZone.reset(new StrChangeset{src.mUpdatedTimeZone->oldValue, src.mUpdatedTimeZone->newValue}); } if (src.mUpdatedStartDateTime) { mUpdatedStartDateTime.reset(new TsChangeset{src.mUpdatedStartDateTime->oldValue, src.mUpdatedStartDateTime->newValue}); } if (src.mUpdatedEndDateTime) { mUpdatedEndDateTime.reset(new TsChangeset{src.mUpdatedEndDateTime->oldValue, src.mUpdatedEndDateTime->newValue}); } } std::bitset mUpdatedFields; unique_ptr mUpdatedTitle; unique_ptr mUpdatedTimeZone; unique_ptr mUpdatedStartDateTime; unique_ptr mUpdatedEndDateTime; }; handle mChatid = UNDEF; handle mSchedMeetingHandle = UNDEF; handle mParentSchedId = UNDEF; m_time_t mStartDateTime = mega_invalid_timestamp; // overrides param Changeset mUpdatedChangeset; UpdatedScheduledMeeting(UserAlertRaw& un, unsigned int id); UpdatedScheduledMeeting(handle _ou, m_time_t _ts, unsigned int _id, handle _chatid, handle _sm, handle _parentSchedId, m_time_t _startDateTime, Changeset&& _cs): ScheduledMeetingAlert(_ou, _ts, _id, _chatid, _sm, _parentSchedId, _startDateTime), mUpdatedChangeset(_cs) {} virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string*) const override; }; struct DeletedScheduledMeeting : public Base { handle mChatid = UNDEF; handle mSchedMeetingHandle = UNDEF; DeletedScheduledMeeting(UserAlertRaw& un, unsigned int id); DeletedScheduledMeeting(handle _ou, m_time_t _ts, unsigned int _id, handle _chatid, handle _sm): Base(name_id::mcsmr, _ou, string(), _ts, _id), mChatid(_chatid), mSchedMeetingHandle(_sm) {} virtual void text(string& header, string& title, MegaClient* mc) override; bool serialize(string* d) const override; static DeletedScheduledMeeting* unserialize(string*, unsigned id); }; #endif }; struct UserAlertFlags { bool cloud_enabled; bool contacts_enabled; bool cloud_newfiles; bool cloud_newshare; bool cloud_delshare; bool contacts_fcrin; bool contacts_fcrdel; bool contacts_fcracpt; UserAlertFlags(); }; struct UserAlerts { private: MegaClient& mc; unsigned int nextid; public: typedef deque Alerts; Alerts alerts; // alerts created from sc (action packets) or received "raw" from sc50; newest go at the end // collect new/updated alerts to notify the app with; non-owning container of pointers owned by `alerts` useralert_vector useralertnotify; // alerts to be notified to the app (new/updated/removed) // set true after our initial query to MEGA to get the last 50 alerts on startup bool begincatchup; bool catchupdone; m_time_t catchup_last_timestamp; private: map pendingContactUsers; handle lsn, fsn; m_time_t lastTimeDelta; UserAlertFlags flags; bool provisionalmode; std::vector provisionals; struct ff { m_time_t timestamp = 0; UserAlert::handle_alerttype_map_t alertTypePerFileNode; UserAlert::handle_alerttype_map_t alertTypePerFolderNode; vector fileHandles() const { vector v; std::transform(alertTypePerFileNode.begin(), alertTypePerFileNode.end(), std::back_inserter(v), [](const pair& p) { return p.first; }); return v; } vector folderHandles() const { vector v; std::transform(alertTypePerFolderNode.begin(), alertTypePerFolderNode.end(), std::back_inserter(v), [](const pair& p) { return p.first; }); return v; } bool areNodeVersions() const { return mAreNodeVersions; } void areNodeVersions(const bool theyAre) { mAreNodeVersions = areNodeVersions() || theyAre; } void squash(const ff& rhs); private: bool mAreNodeVersions = false; }; using notedShNodesMap = map, ff>; // <,ff> notedShNodesMap notedSharedNodes; notedShNodesMap deletedSharedNodesStash; bool notingSharedNodes; handle ignoreNodesUnderShare; bool isUnwantedAlert(nameid type, int action); bool isConvertReadyToAdd(handle originatinguser) const; void convertNotedSharedNodes(bool added); void clearNotedSharedMembers(); void trimAlertsToMaxCount(); // mark as removed the excess from 200 void notifyAlert(UserAlert::Base* alert, bool seen, int tag); UserAlert::Base* findAlertToCombineWith(const UserAlert::Base* a, nameid t) const; bool containsRemovedNodeAlert(handle nh, const UserAlert::Base* a) const; // Returns param `a` downcasted if `nh` is found and erased; `nullptr` otherwise UserAlert::NewSharedNodes* eraseNodeHandleFromNewShareNodeAlert(handle nh, UserAlert::Base* a); // Returns param `a` downcasted if `nh` is found and erased; `nullptr` otherwise UserAlert::RemovedSharedNode* eraseNodeHandleFromRemovedSharedNode(handle nh, UserAlert::Base* a); pair findNotedSharedNodeIn(handle nodeHandle, const notedShNodesMap& notedSharedNodesMap) const; bool isSharedNodeNotedAsRemoved(handle nodeHandleToFind) const; bool isSharedNodeNotedAsRemovedFrom(handle nodeHandleToFind, const notedShNodesMap& notedSharedNodesMap) const; bool removeNotedSharedNodeFrom(notedShNodesMap::iterator itToNodeToRemove, Node* node, notedShNodesMap& notedSharedNodesMap); bool removeNotedSharedNodeFrom(Node* n, notedShNodesMap& notedSharedNodesMap); bool setNotedSharedNodeToUpdate(Node* n); public: // This is a separate class to encapsulate some MegaClient functionality // but it still needs to interact with other elements. UserAlerts(MegaClient&); ~UserAlerts(); unsigned int nextId(); // process notification response from MEGA bool procsc_useralert(JSON& jsonsc); // sc50 // add an alert - from alert reply or constructed from actionpackets void add(UserAlertRaw& un); // from sc50 void add(UserAlert::Base*); // from action packet or persistence // keep track of incoming nodes in shares, and convert to a notification void beginNotingSharedNodes(); void noteSharedNode(handle user, int type, m_time_t timestamp, Node* n, nameid alertType = name_id::d); void convertNotedSharedNodes(bool added, handle originatingUser); void ignoreNextSharedNodesUnder(handle h); // enter provisional mode, added items will be checked for suitability before actually adding void startprovisional(); void evalprovisional(handle originatinguser); // update node alerts management bool isHandleInAlertsAsRemoved(handle nodeHandleToFind) const; template void eraseAlertsFromContainer(T& container, const set& toErase) { container.erase( remove_if(begin(container), end(container), [&toErase](UserAlert::Base* a) { return toErase.find(a) != end(toErase); }) , end(container)); } void removeNodeAlerts(Node* n); void setNewNodeAlertToUpdateNodeAlert(Node* n); void initscalerts(); // persist alerts received from sc50 void purgescalerts(); // persist alerts from action packets bool unserializeAlert(string* d, uint32_t dbid); // stash removal-alert noted nodes void purgeNodeVersionsFromStash(); void convertStashedDeletedSharedNodes(); bool isDeletedSharedNodesStashEmpty() const; void stashDeletedNotedSharedNodes(handle originatingUser); // request from API to acknowledge all alerts void acknowledgeAll(); // marks all as seen, after API request has succeeded void acknowledgeAllSucceeded(); // the API notified us another client updated the last acknowleged void onAcknowledgeReceived(); // re-init eg. on logout void clear(); // drop stale payment reminders once we know the account is currently PRO void purgeStalePaymentReminders(); }; } // namespace #endif sdk-10.11.0/include/mega/utils.h000066400000000000000000001662421516266226600163270ustar00rootroot00000000000000/** * @file mega/utils.h * @brief Mega SDK various utilities and helper classes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_UTILS_H #define MEGA_UTILS_H 1 #include "overloaded.h" #include "types.h" #include #include #include #include #include #include #include #undef SSIZE_MAX #include #undef SSIZE_MAX // Include ICU headers #include namespace mega { std::string toNodeHandle(handle nodeHandle); std::string toNodeHandle(NodeHandle nodeHandle); NodeHandle toNodeHandle(const byte* data); // consider moving functionality to NodeHandle NodeHandle toNodeHandle(const std::string* data); std::string toHandle(handle h); handle stringToHandle(const std::string& b64String, const int handleSize); std::pair toTypeOfLink (nodetype_t type); #define LOG_NODEHANDLE(x) toNodeHandle(x) #define LOG_HANDLE(x) toHandle(x) class SimpleLogger; class LocalPath; SimpleLogger& operator<<(SimpleLogger&, NodeHandle h); SimpleLogger& operator<<(SimpleLogger&, UploadHandle h); SimpleLogger& operator<<(SimpleLogger&, NodeOrUploadHandle h); SimpleLogger& operator<<(SimpleLogger& s, const LocalPath& lp); typedef enum { FORMAT_SCHEDULED_COPY = 0, // 20221205123045 FORMAT_ISO8601 = 1, // 20221205T123045 } date_time_format_t; typedef enum { NODE_COMP_EREAD = -21, NODE_COMP_EARGS = -2, NODE_COMP_PENDING = -1, NODE_COMP_EQUAL = 0, NODE_COMP_DIFFERS_FP = 1, NODE_COMP_DIFFERS_MAC = 2, NODE_COMP_DIFFERS_MTIME = 3, } node_comparison_result; std::string nodeComparisonResultToStr(const node_comparison_result result); std::string backupTypeToStr(BackupType type); struct MEGA_API ChunkedHash { static const int SEGSIZE = 131072; static m_off_t chunkfloor(m_off_t); static m_off_t chunkceil(m_off_t, m_off_t limit = -1); }; /** * @brief Padded encryption using AES-128 in CBC mode. */ struct MEGA_API PaddedCBC { /** * @brief Encrypts a string after padding it to block length. * * Note: With an IV, only use the first 8 bytes. * * @param data Data buffer to be encrypted. Encryption is done in-place, * so cipher text will be in `data` afterwards as well. * @param key AES key for encryption. * @param iv Optional initialisation vector for encryption. Will use a * zero IV if not given. If `iv` is a zero length string, a new IV * for encryption will be generated and available through the reference. * @return true if encryption was successful. */ static bool encrypt(PrnGen &rng, string* data, SymmCipher* key, string* iv = NULL); /** * @brief Decrypts a string and strips the padding. * * Note: With an IV, only use the first 8 bytes. * * @param data Data buffer to be decrypted. Decryption is done in-place, * so plain text will be in `data` afterwards as well. * @param key AES key for decryption. * @param iv Optional initialisation vector for encryption. Will use a * zero IV if not given. * @return true if decryption was successful. */ static bool decrypt(string* data, SymmCipher* key, string* iv = NULL); }; class MEGA_API HashSignature { Hash* hash; public: // add data void add(const byte*, unsigned); // generate signature unsigned get(AsymmCipher*, byte*, unsigned); // verify signature bool checksignature(AsymmCipher*, const byte*, unsigned); HashSignature(Hash*); ~HashSignature(); }; /** * @brief Crypto functions related to payments */ class MEGA_API PayCrypter { /** * @brief Length of the AES key */ static const int ENC_KEY_BYTES = 16; /** * @brief Lenght of the key to generate the HMAC */ static const int MAC_KEY_BYTES = 32; /** * @brief Length of the IV for AES-CBC */ static const int IV_BYTES = 16; /** * @brief Buffer for the AES key and the HMAC key */ byte keys[ENC_KEY_BYTES+MAC_KEY_BYTES]; /** * @brief Pointer to the buffer with the AES key */ byte *encKey; /** * @brief Pointer to the buffer with the HMAC key */ byte *hmacKey; /** * @brief Buffer with the IV for AES-CBC */ byte iv[IV_BYTES]; /** * @brief Random blocks generator */ PrnGen &rng; public: /** * @brief Constructor. Initializes keys with random values. */ PayCrypter(PrnGen &rng); /** * @brief Updates the crypto keys (mainly for testing) * @param newEncKey New AES key (must contain ENC_KEY_BYTES bytes) * @param newHmacKey New HMAC key (must contain MAC_KEY_BYTES bytes) * @param newIv New IV for AES-CBC (must contain IV_BYTES bytes) */ void setKeys(const byte *newEncKey, const byte *newHmacKey, const byte *newIv); /** * @brief Encrypts the cleartext and returns the payload string. * * The clear text is encrypted with AES-CBC, then a HMAC-SHA256 is generated for (IV + ciphertext) * and finally returns (HMAC + IV + ciphertext) * * @param cleartext Clear text to generate the payload * @param result The function will fill this string with the generated payload * @return True if the funcion succeeds, otherwise false */ bool encryptPayload(const string *cleartext, string *result); /** * @brief Encrypts the cleartext using RSA with random padding. * * A 2-byte header is inserted just before the clear text with the size in bytes. * The result is padded with random bytes. Then RSA is applied and the result is returned * in the third parameter, with a 2-byte header that contains the size of the result of RSA. * * @param cleartext Clear text to encrypt with RSA * @param pubkdata Public key in binary format (result of AsymmCipher::serializekey) * @param pubkdatalen Size (in bytes) of pubkdata * @param result RSA encrypted text, with a 2-byte header with the size of the RSA buffer in bytes * @param randompadding Enables padding with random bytes. Otherwise, the cleartext is 0-padded * @return True if the funcion succeeds, otherwise false */ bool rsaEncryptKeys(const string *cleartext, const byte *pubkdata, int pubkdatalen, string *result, bool randompadding = true); /** * @brief Encrypts clear text data to an authenticated ciphertext, authenticated with an HMAC. * @param cleartext Clear text as byte string * @param pubkdata Public key in binary format (result of AsymmCipher::serializekey) * @param pubkdatalen Size (in bytes) of pubkdata * @param result Encrypted data block as byte string. * @param randompadding Enables padding with random bytes. Otherwise, the cleartext is 0-padded * @return True if the funcion succeeds, otherwise false */ bool hybridEncrypt(const string *cleartext, const byte *pubkdata, int pubkdatalen, string *result, bool randompadding = true); }; // read/write multibyte words struct MEGA_API MemAccess { #ifndef ALLOW_UNALIGNED_MEMORY_ACCESS template static T get(const char* ptr) { T val; memcpy(&val,ptr,sizeof(T)); return val; } template static void set(byte* ptr, T val) { memcpy(ptr,&val,sizeof val); } #else template static T get(const char* ptr) { return *(T*)ptr; } template static void set(byte* ptr, T val) { *(T*)ptr = val; } #endif }; #ifdef _WIN32 // get the Windows error message in UTF-8 std::string winErrorMessage(DWORD error); #endif class Utils { public: /** * @brief Converts a character string from UTF-8 to Unicode * This method is a workaround for a legacy bug where Webclient used to encode * each byte of the array in UTF-8, resulting in a wider string of variable length. * @note The UTF-8 string should only contain characters encoded as 1 or 2 bytes. * @param src Characters string encoded in UTF-8 * @param srclen Length of the string (in bytes) * @param result String holding the byte array of Unicode characters * @return True if success, false if the byte 'src' is not a valid UTF-8 string */ static bool utf8toUnicode(const uint8_t *src, unsigned srclen, string *result); /** * @brief Determines size in bytes of a valid UTF-8 sequence. * @param c first character of UTF-8 sequence * @return the size of UTF-8 sequence if its valid, otherwise returns 0 */ static size_t utf8SequenceSize(unsigned char c); /** * @brief This function is analogous to a32_to_str in js version. * Converts a vector of elements into a std::string * * @param data a vector of elements * @note this function has been initially designed to work with = uint32_t or = int32_t * This is a valid example: = uint32_t, data = [1952805748] => return_value = "test" * * @return returns a std::string */ template static std::string a32_to_str(std::vector data) { size_t size = data.size() * sizeof(T); std::unique_ptr result(new char[size]); for (size_t i = 0; i < size; ++i) { result[i] = static_cast((data[i >> 2] >> (24 - (i & 3) * 8)) & 255); } return std::string (result.get(), size); } /** * @brief This function is analogous to str_to_a32 in js version. * Converts a std::string into a vector of elements * * @param data a std::string * @note this function has been initially designed to work with = uint32_t or = int32_t * This is a valid example: = uint32_t, data = "test" => return_value = [1952805748] * * @return returns a vector of elements */ template static std::vector str_to_a32(std::string data) { std::vector data32((data.size() + 3) >> 2); for (size_t i = 0; i < data.size(); ++i) { data32[i >> 2] |= (data[i] & 255) << (24 - (i & 3) * 8); } return data32; } static std::string stringToHex(const std::string& input, bool spaceBetweenBytes = false); static std::string hexToString(const std::string& input); /** * @brief Converts a hexadecimal string to a uint64_t value. The input string may or may not have the hex prefix "0x". * * @param input The hexadecimal string to be converted (ex: "78b1bbbda5f32526", "0x10FF, "0x0001") * @return The uint64_t value corresponding to the input hexadecimal string. */ static uint64_t hexStringToUint64(const std::string &input); /** * @brief Converts a 8-byte numeric value to a 16-character lowercase hexadecimal string, with zero-padding if necessary. * * @param input The uint64_t value to be converted to a hexadecimal string. * @return A 16-character lowercase hexadecimal string representation of the input value (ex: "78b1bbbda5f32526"). * */ static std::string uint64ToHexString(uint64_t input); static int32_t toLower(const int32_t c) { return utf8proc_tolower(c); } static int32_t toUpper(const int32_t c) { return utf8proc_toupper(c); } static string toUpperUtf8(const string& text); static string toLowerUtf8(const string& text); // Platform-independent case-insensitive comparison. static int icasecmp(const std::string& lhs, const std::string& rhs); static int icasecmp(const char* lhs, const char* rhs); static int icasecmp(const std::wstring& lhs, const std::wstring& rhs); static int icasecmp(const wchar_t* lhs, const wchar_t* rhs); static int icasecmp(const std::string& lhs, const std::string& rhs, const size_t length); static int icasecmp(const std::wstring& lhs, const std::wstring& rhs, const size_t length); // Same as above but only case-insensitive on Windows. static int pcasecmp(const std::string& lhs, const std::string& rhs, const size_t length); static int pcasecmp(const std::wstring& lhs, const std::wstring& rhs, const size_t length); static std::string replace(const std::string& str, char search, char replace); static std::string replace(const std::string& str, const std::string& search, const std::string& replacement); // join({"a", "new", "loom"}, "; ") -> "a; new; loom" static std::string join(const std::vector& items, const std::string& with); template static bool startswith(const std::basic_string& str, const std::basic_string& start); template static bool startswith(const std::basic_string& str, T chr) { return str.length() >= 1 && chr == str.front(); } template static const T* startswith(const T* str, const T* start); template static const T* startswith(const std::basic_string& str, const T* start) { return startswith(str.c_str(), start); } template static bool endswith(const T* str, size_t strLen, const T* suffix, size_t suffixLen); static bool endswith(const std::string& str, char chr); static const std::string _trimDefaultChars; // return string with trimchrs removed from front and back of given string str static string trim(const string& str, const string& trimchars = _trimDefaultChars); // --- environment functions that work with Unicode UTF-8 on Windows (set/unset/get) --- static bool hasenv(const std::string& key); static std::pair getenv(const std::string& key); // return def if value not found static std::string getenv(const std::string& key, const std::string& def); static void setenv(const std::string& key, const std::string& value); static void unsetenv(const std::string& key); static std::string getIcuVersion(); }; extern m_time_t m_time(m_time_t* tt = NULL); extern struct tm* m_localtime(m_time_t, struct tm *dt); extern struct tm* m_gmtime(m_time_t, struct tm *dt); extern m_time_t m_mktime(struct tm*); extern dstime m_clock_getmonotonictimeDS(); // Similar behaviour to mktime but it receives a struct tm with a date in UTC and return mktime in UTC extern m_time_t m_mktime_UTC(const struct tm *src); /** * Converts a datetime from string format into a Unix timestamp * Allowed input formats: * + FORMAT_SCHEDULED_COPY => 20221205123045 => output format: Unix timestamp in deciseconds * + FORMAT_ISO8601 => 20221205T123045 => output format: Unix timestamp in seconds */ extern time_t stringToTimestamp(string stime, date_time_format_t format); std::string rfc1123_datetime( time_t time ); std::string webdavurlescape(const std::string &value); std::string escapewebdavchar(const char c); std::string webdavnameescape(const std::string &value); void tolower_string(std::string& str); #ifdef __APPLE__ int macOSmajorVersion(); #endif // file chunk macs class chunkmac_map { struct ChunkMAC { // do not change the size or layout, it is directly serialized to db from whatever the binary format is for this compiler/platform byte mac[SymmCipher::BLOCKSIZE]; // For a partially completed chunk, offset is the number of bytes processed (from the start of the chunk) // For a finished chunk, it's 0 // When we start consolidating from the front for macsmac calculation, it's -1 (and finished==true) unsigned int offset = 0; // True when the entire chunk has been processed. // For the special case of the first record being the macsmac calculation to this point, // finished == true and offset == -1, and mac == macsmac to the end of this block. bool finished = false; // True when the chunk is not entirely processed. // Offset is only increased by downloads, so (!offset) should always be true for uploads. bool notStarted() { return !finished && !offset; } // the very first record can be the macsmac calculation so far, from the start to some contiguous point bool isMacsmacSoFar() { return finished && offset == unsigned(-1); } }; map mMacMap; // we collapse the leading consecutive entries, for large files. // this is the map key for how far that collapsing has progressed m_off_t macsmacSoFarPos = -1; m_off_t progresscontiguous{0}; public: int64_t macsmac(SymmCipher *cipher); int64_t macsmac_gaps(SymmCipher *cipher, size_t g1, size_t g2, size_t g3, size_t g4); void serialize(string& d) const; bool unserialize(const char*& ptr, const char* end); void calcprogress(m_off_t size, m_off_t& chunkpos, m_off_t& completedprogress, m_off_t* sumOfPartialChunks = nullptr); m_off_t nextUnprocessedPosFrom(m_off_t pos); m_off_t expandUnprocessedPiece(m_off_t pos, m_off_t npos, m_off_t fileSize, m_off_t maxReqSize); m_off_t hasUnfinishedGap(m_off_t fileSize); void finishedUploadChunks(chunkmac_map& macs); bool finishedAt(m_off_t pos); m_off_t updateContiguousProgress(m_off_t fileSize); void updateMacsmacProgress(SymmCipher *cipher); void copyEntriesTo(chunkmac_map& other); m_off_t copyEntriesToUntilRaidlineBeforePos(m_off_t maxPos, chunkmac_map& other); void copyEntryTo(m_off_t pos, chunkmac_map& other); void debugLogOuputMacs(); void ctr_encrypt(m_off_t chunkid, SymmCipher *cipher, byte *chunkstart, unsigned chunksize, m_off_t startpos, int64_t ctriv, bool finishesChunk); void ctr_decrypt(m_off_t chunkid, SymmCipher *cipher, byte *chunkstart, unsigned chunksize, m_off_t startpos, int64_t ctriv, bool finishesChunk); void setProgressContiguous(const m_off_t p); void swap(chunkmac_map& other); size_t size() const { return mMacMap.size(); } void clear() { mMacMap.clear(); macsmacSoFarPos = -1; setProgressContiguous(0); } }; struct CacheableWriter { CacheableWriter(string& d); string& dest; void serializebinary(byte* data, size_t len); void serializecstr(const char* field, bool storeNull); // may store the '\0' also for backward compatibility. Only use for utf8! (std::string storing double byte chars will only store 1 byte) void serializepstr(const string* field); // uses string size() not strlen void serializestring(const string& field); void serializestring(const std::wstring& field); void serializestring_u32(const string& field); // use uint32_t for the size field void serializecompressedu64(uint64_t field); void serializecompressedi64(int64_t field) { serializecompressedu64(static_cast(field)); } // DO NOT add size_t or other types that are different sizes in different builds, eg 32/64 bit compilation void serializei8(int8_t field); void serializei32(int32_t field); void serializei64(int64_t field); void serializeu64(uint64_t field); void serializeu32(uint32_t field); void serializeu16(uint16_t field); void serializeu8(uint8_t field); void serializehandle(handle field); void serializenodehandle(handle field); void serializeNodeHandle(NodeHandle field); void serializebool(bool field); void serializebyte(byte field); void serializedouble(double field); void serializechunkmacs(const chunkmac_map& m); // Each class that might get extended should store expansion flags at the end // When adding new fields to an existing class, set the next expansion flag true to indicate they are present. // If you turn on the last flag, then you must also add another set of expansion flags (all false) after the new fields, for further expansion later. void serializeexpansionflags(bool b1 = false, bool b2 = false, bool b3 = false, bool b4 = false, bool b5 = false, bool b6 = false, bool b7 = false, bool b8 = false); }; struct CacheableReader { CacheableReader(const string& d); const char* ptr; const char* end; unsigned fieldnum; bool unserializebinary(byte* data, size_t len); bool unserializecstr(string& s, bool removeNull); // set removeNull if this field stores the terminating '\0' at the end bool unserializestring(std::wstring& s); bool unserializestring(string& s); bool unserializestring_u32(string& s); bool unserializecompressedu64(uint64_t& field); bool unserializecompressedi64(int64_t& field) { return unserializecompressedu64(reinterpret_cast(field)); } // DO NOT add size_t or other types that are different sizes in different builds, eg 32/64 bit compilation bool unserializei8(int8_t& s); bool unserializei32(int32_t& s); bool unserializei64(int64_t& s); bool unserializeu16(uint16_t &s); bool unserializeu32(uint32_t& s); bool unserializeu8(uint8_t& field); bool unserializeu64(uint64_t& s); bool unserializebyte(byte& s); bool unserializedouble(double& s); bool unserializehandle(handle& s); bool unserializenodehandle(handle& s); bool unserializeNodeHandle(NodeHandle& s); bool unserializebool(bool& s); bool unserializechunkmacs(chunkmac_map& m); bool unserializefingerprint(FileFingerprint& fp); bool unserializedirection(direction_t& field); // historic; size varies by compiler. todo: Remove when we next roll the transfer db version bool unserializeexpansionflags(unsigned char field[8], unsigned usedFlagCount); void eraseused(string& d); // must be the same string, unchanged bool hasdataleft() { return end > ptr; } }; struct FileAccess; struct InputStreamAccess; class SymmCipher; /*******************\ * CRC UTILS * \*******************/ constexpr std::uint64_t LEGACY_CRC_WINDOW_BYTES = 64; // 4 * sizeof(FileFingerprint::crc) constexpr unsigned LEGACY_CRC_LANES = 4; constexpr unsigned LEGACY_CRC_BLOCKS_PER_LANE = 32; constexpr std::uint32_t LEGACY_SPARSE_DENOM = static_cast(LEGACY_CRC_LANES * LEGACY_CRC_BLOCKS_PER_LANE - 1); // 127 // The old buggy 32-bit computation could only overflow once (size - windowBytes) * idx crosses // 2^32. constexpr std::uint64_t LEGACY_OVERFLOW_MIN_SIZE = (static_cast(std::numeric_limits::max()) / LEGACY_SPARSE_DENOM) + LEGACY_CRC_WINDOW_BYTES; [[nodiscard]] bool computeLegacyBuggySparseCrcFA(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, std::array& crcOut); [[nodiscard]] bool computeLegacyBuggySparseCrcIA(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, std::array& crcOut); [[nodiscard]] m_off_t legacySparseOffset32Bug(const m_off_t size, const unsigned lane, const unsigned block) noexcept; bool areCrcEqual(const FingerprintCrc& lhs, const FingerprintCrc& rhs); /*******************\ * METAMAC UTILS * \*******************/ static constexpr int64_t INVALID_META_MAC{0xFFFFFFFF}; /** * @brief Result of MAC comparison between local file and node. * * Used by CompareLocalFileMetaMacWithNodeKey to return extended information * for diagnostics and event reporting (e.g., event 800036). * @note errorCode: 0 = success, non-zero = OS error code during MAC generation * (e.g., EIO on POSIX, ERROR_HANDLE_EOF on Windows) */ struct MacComparisonResult { bool areEqualMacs{false}; int64_t localMac{INVALID_META_MAC}; int64_t remoteMac{INVALID_META_MAC}; int errorCode{0}; }; std::pair generateMetaMac(SymmCipher& cipher, FileAccess& ifAccess, const int64_t iv, std::optional pathStr); std::pair generateMetaMac(SymmCipher &cipher, InputStreamAccess &isAccess, const int64_t iv); /** * @brief Check if two file nodes are equal by comparing their MetaMACs. * * @param nodeKey_a The first node encryption key from which we will get the first MetaMAC. * @param nodeKey_b The second node encryption key from which we will get the second MetaMAC. * @return true if both MetaMACs, false otherwise. */ bool areEqualNodesByMetaMac(const std::string& nodeKey_a, const std::string& nodeKey_b); /** * @brief Compares local file MAC with node's MAC from its key. * * @param fa Pointer to FileAccess object for reading the local file. * @param nodeKey The node's encryption key containing the MAC. * @param type The node type for cipher initialization. * @param pathStr Optional path string for logging. * @return MacComparisonResult with comparison details including both MACs and error status. */ MacComparisonResult CompareLocalFileMetaMacWithNodeKey(FileAccess* fa, const std::string& nodeKey, int type, std::optional pathStr); bool CompareLocalFileMetaMacWithNode(FileAccess* fa, Node* node); /** * @brief Generates local METAMAC (from local file) and remote METAMAC (from a Node) * @param fa Pointer to FileAccess object for file operations * @param nodeKey The node's encryption key * @param type The node type * @return A pair containing the local and remote METAMACs */ std::pair genLocalAndRemoteMetaMac(FileAccess* fa, const std::string& nodeKey, int type, std::optional pathStr); /** * @brief Compares two fingerprints (excluding mtime) and two METAMACs. * * @param fp1 First Fingerprint to be compared. * @param fp2 Second Fingerprint to be compared. * @param metamac1 First MetaMac to be compared. * @param metamac2 Second MetaMac to be compared. * @note fpX and metamacX corresponds to the same item * @return `node_comparison_result` indicating the comparison result: * - NODE_COMP_EQUAL: Fingerprints (also mtime is equal) and METAMACs match * - NODE_COMP_DIFFERS_FP: Node types mismatch or fingerprints differ in something more than * mtime (CRC, Size, isValid). * - NODE_COMP_DIFFERS_MTIME: Fingerprints differ in mtime but METAMACs match. * - NODE_COMP_DIFFERS_MAC: METAMACs differ. */ node_comparison_result CompareMacAndFpExcludingMtime(const FileFingerprint& fp1, const FileFingerprint& fp2, const int64_t metamac1, const int64_t metamac2); /** * @brief Compares node's fingerprint and METAMAC with fingerprint and METAMAC provided as params * (Excluding mtime) * * @param node Pointer to the remote node to compare with. * @param fp Fingerprint to be compared. * @param precalcMetamac Precalculated METAMAC to be compared. * * @return A value of type `node_comparison_result` indicating the comparison result: * - NODE_COMP_EREAD: Error reading the local file. * - NODE_COMP_EQUAL: Fingerprints (also mtime is equal) and METAMACs match * - NODE_COMP_DIFFERS_FP: Node types mismatch or fingerprints differ in something more than * mtime (CRC, Size, isValid). * - NODE_COMP_DIFFERS_MTIME: Fingerprints differ in mtime but METAMACs match. * - NODE_COMP_DIFFERS_MAC: METAMACs differ. */ node_comparison_result CompareNodeWithProvidedMacAndFpExcludingMtime(const Node* node, const FileFingerprint& fp, const int64_t precalcMetamac); /** * @brief Compares a local file with a node based on fingerprint (Excluding mtime) and METAMAC. * * This function checks whether a local file matches a remote node by comparing: * 1. The fingerprint of the local file with the fingerprint of the node. * 2. The METAMAC (Message Authentication Code) of the local file with the METAMAC generated from node key. * * @param client reference to the MegaClient instance. * @param path Local path to the file to be compared. * @param fp Fingerprint of the local file to be compared. * @param node Pointer to the node to compare with. * @param excludeMtime If true, ignores mtime time during fingerprint comparison. * * @return A pair of {`node_comparison_result`, metamac}. * `node_comparison_result` indicates: * - NODE_COMP_EARGS: Invalid arguments * - NODE_COMP_EREAD: Error reading the local file. * - NODE_COMP_EQUAL: Fingerprints (also mtime is equal) and METAMACs match * - NODE_COMP_DIFFERS_FP: Node types mismatch or fingerprints differ in something more than mtime (CRC, Size, isValid). * - NODE_COMP_DIFFERS_MTIME: Fingerprints differ in mtime but METAMACs match. * - NODE_COMP_DIFFERS_MAC: METAMACs differ. */ std::pair CompareLocalFileWithNodeMacAndFpExludingMtime(MegaClient& client, const LocalPath& path, const FileFingerprint& fp, const Node* node); // Helper class for MegaClient. Suitable for expansion/templatizing for other use caes. // Maintains a small thread pool for executing independent operations such as encrypt/decrypt a block of data // The number of threads can be 0 (eg. for helper MegaApi that deals with public folder links) in which case something queued is // immediately executed synchronously on the caller's thread struct MegaClientAsyncQueue { void push(std::function f, bool discardable); void clearDiscardable(); MegaClientAsyncQueue(Waiter& w, unsigned threadCount); ~MegaClientAsyncQueue(); private: Waiter& mWaiter; std::mutex mMutex; std::condition_variable mConditionVariable; struct Entry { bool discardable = false; std::function f; Entry(bool disc, std::function&& func) : discardable(disc), f(func) {} }; std::deque mQueue; std::vector mThreads; SymmCipher mZeroThreadsCipher; void asyncThreadLoop(); }; template struct ThreadSafeDeque { // Just like a deque, but thread safe so that a separate thread can receive filesystem notifications as soon as they are available. // When we try to do that on the same thread, the processing of queued notifications is too slow so more notifications bulid up than // have been processed, so each time we get the outstanding ones from the buffer we gave to the OS, we need to give it an even // larger buffer to write into, otherwise it runs out of space before this thread is idle and can get the next batch from the buffer. protected: std::deque mNotifications; std::mutex m; public: bool peekFront(T& t) { std::lock_guard g(m); if (!mNotifications.empty()) { t = mNotifications.front(); return true; } return false; } bool popFront(T& t) { std::lock_guard g(m); if (!mNotifications.empty()) { t = std::move(mNotifications.front()); mNotifications.pop_front(); return true; } return false; } void unpopFront(const T& t) { std::lock_guard g(m); mNotifications.push_front(t); } void pushBack(T&& t) { std::lock_guard g(m); mNotifications.push_back(t); } bool empty() { std::lock_guard g(m); return mNotifications.empty(); } size_t size() { std::lock_guard g(m); return mNotifications.size(); } std::deque popAll() { std::lock_guard g(m); std::deque batch; batch.swap(mNotifications); return batch; } }; template class ThreadSafeKeyValue { // This is a thread-safe key-value container restricted to accepting only numeric values. // Only the needed interfaces were implemented. Add new ones as they become useful. public: std::unique_ptr get(const K& key) const { std::shared_lock lock(mMutex); auto it = mStorage.find(key); return it == mStorage.end() ? nullptr : std::make_unique(it->second); } void set(const K& key, const V& value) { std::unique_lock lock(mMutex); mStorage[key] = value; } void clear() { std::unique_lock lock(mMutex); return mStorage.clear(); } private: mutable std::shared_mutex mMutex; std::map mStorage; }; template struct UnicodeCodepointIteratorTraits; template<> struct UnicodeCodepointIteratorTraits { static ptrdiff_t get(int32_t& codepoint, const char* m, const char* n) { assert(m && n && m < n); return utf8proc_iterate(reinterpret_cast(m), n - m, &codepoint); } static size_t length(const char* s) { assert(s); return strlen(s); } }; // UnicodeCodepointIteratorTraits template<> struct UnicodeCodepointIteratorTraits { static ptrdiff_t get(int32_t& codepoint, const wchar_t* m, const wchar_t* n) { assert(m && n && m < n); // Are we looking at a high surrogate? if ((*m >> 10) == 0x36) { // Is it followed by a low surrogate? if (n - m < 2 || (m[1] >> 10) != 0x37) { // Nope, the string is malformed. return -1; } // Compute addend. const int32_t lo = m[1] & 0x3ff; const int32_t hi = *m & 0x3ff; const int32_t addend = (hi << 10) | lo; // Store effective code point. codepoint = 0x10000 + addend; return 2; } // Are we looking at a low surrogate? if ((*m >> 10) == 0x37) { // Then the string is malformed. return -1; } // Code point is encoded by a single code unit. codepoint = *m; return 1; } static size_t length(const wchar_t* s) { assert(s); return wcslen(s); } }; // UnicodeCodepointIteratorTraits template class UnicodeCodepointIterator { public: using traits_type = UnicodeCodepointIteratorTraits; UnicodeCodepointIterator(const CharT* s, size_t length) : mCurrent(s) , mEnd(s + length) { } explicit UnicodeCodepointIterator(const std::basic_string& s) : UnicodeCodepointIterator(s.data(), s.size()) { } explicit UnicodeCodepointIterator(const CharT* s) : UnicodeCodepointIterator(s, traits_type::length(s)) { } UnicodeCodepointIterator(const UnicodeCodepointIterator& other) : mCurrent(other.mCurrent) , mEnd(other.mEnd) { } UnicodeCodepointIterator() : mCurrent(nullptr) , mEnd(nullptr) { } UnicodeCodepointIterator& operator=(const UnicodeCodepointIterator& rhs) { if (this != &rhs) { mCurrent = rhs.mCurrent; mEnd = rhs.mEnd; } return *this; } bool operator==(const UnicodeCodepointIterator& rhs) const { return mCurrent == rhs.mCurrent && mEnd == rhs.mEnd; } bool operator!=(const UnicodeCodepointIterator& rhs) const { return !(*this == rhs); } bool end() const { return mCurrent == mEnd; } int32_t get() { int32_t result = 0; if (mCurrent < mEnd) { ptrdiff_t nConsumed = traits_type::get(result, mCurrent, mEnd); assert(nConsumed > 0); mCurrent += nConsumed; } return result; } bool match(const int32_t character) { if (peek() != character) { return false; } get(); return true; } int32_t peek() const { int32_t result = 0; if (mCurrent < mEnd) { #ifndef NDEBUG ptrdiff_t nConsumed = #endif traits_type::get(result, mCurrent, mEnd); assert(nConsumed > 0); } return result; } private: const CharT* mCurrent; const CharT* mEnd; }; // UnicodeCodepointIterator template UnicodeCodepointIterator unicodeCodepointIterator(const std::basic_string& s) { return UnicodeCodepointIterator(s); } template UnicodeCodepointIterator unicodeCodepointIterator(const CharT* s, size_t length) { return UnicodeCodepointIterator(s, length); } template UnicodeCodepointIterator unicodeCodepointIterator(const CharT* s) { return UnicodeCodepointIterator(s); } inline int hexval(const int c) { return ((c & 0xf) + (c >> 6)) | ((c >> 3) & 0x8); } bool islchex_high(const int c); bool islchex_low(const int c); // gets a safe url by replacing private parts to be used in logs std::string getSafeUrl(const std::string &posturl); bool readLines(FileAccess& ifAccess, string_vector& destination); bool readLines(InputStreamAccess& isAccess, string_vector& destination); bool readLines(const std::string& input, string_vector& destination); bool wildcardMatch(const string& text, const string& pattern); bool wildcardMatch(const char* text, const char* pattern); struct MEGA_API FileSystemAccess; // generate a new drive id handle generateDriveId(PrnGen& rng); // return API_OK if success and set driveID handle to the drive id read from the drive, // otherwise return error code and set driveId to UNDEF error readDriveId(FileSystemAccess& fsAccess, const char* pathToDrive, handle& driveId); error readDriveId(FileSystemAccess& fsAccess, const LocalPath& pathToDrive, handle& driveId); // return API_OK if success, otherwise error code error writeDriveId(FileSystemAccess& fsAccess, const char* pathToDrive, handle driveId); int platformGetRLimitNumFile(); bool platformSetRLimitNumFile(int newNumFileLimit = -1); void debugLogHeapUsage(); bool haveDuplicatedValues(const string_map& readableVals, const string_map& b64Vals); struct SyncTransferCount { bool operator==(const SyncTransferCount& rhs) const; bool operator!=(const SyncTransferCount& rhs) const; void operator-=(const SyncTransferCount& rhs); void clearPendingValues(); uint32_t mCompleted = 0; uint32_t mPending = 0; uint64_t mCompletedBytes = 0; uint64_t mPendingBytes = 0; }; struct SyncTransferCounts { bool operator==(const SyncTransferCounts& rhs) const; bool operator!=(const SyncTransferCounts& rhs) const; void operator-=(const SyncTransferCounts& rhs); // returns progress 0.0 to 1.0 double progress(m_off_t inflightProgress) const; m_off_t pendingTransferBytes() const; void clearPendingValues(); SyncTransferCount mDownloads; SyncTransferCount mUploads; }; // creates a new id filling `id` with random bytes, up to `length` void resetId(char* id, size_t length, PrnGen& rng); // write messsage and strerror(aerrno) to log as an error void reportError(const std::string& message, int aerrno = -1); #ifdef WIN32 // as per (non C library standard) unix API inline void sleep(int sec) { Sleep(sec * 1000); } // as per (non C library standard) unix API // sleep for given number of microseconds inline void usleep(int microsec) { Sleep(microsec / 1000); } // print messgae: error-num: error-description void reportWindowsError(const std::string& message, DWORD error = 0xFFFFFFFF); #endif // WIN32 // returns the direction type of a connection string connDirectionToStr(direction_t directionType); // Translate retry reason into a human-friendly string. const char* toString(retryreason_t reason); // Wrapper functions for std::isspace and std::isdigit // Not considering EOF values /** * @brief Checks if a character is a whitespace character. * * @param ch The character to check * @return true if the character is a space, otherwise returns false. */ bool is_space(unsigned int ch); /** * @brief Checks if a character is a digit. * * @param ch The character to check * @return true if the character is a digit (0-9), otherwise returns false. */ bool is_digit(unsigned int ch); template> Container splitString(const string& str, char delimiter) { Container tokens; std::string token; std::istringstream tokenStream(str); while (std::getline(tokenStream, token, delimiter)) { tokens.insert(tokens.end(), token); } return tokens; } template std::string joinStrings( const Iter begin, const Iter end, const std::string& separator, const std::function transform = [](const std::string& a) -> std::string { return a; }) { Iter position = begin; std::string result; if (position != end) { result += transform(*position++); } while (position != end) { result += separator + transform(*position++); } return result; } static constexpr char WILDCARD_MATCH_ONE = '?'; static constexpr char WILDCARD_MATCH_ALL = '*'; static constexpr char ESCAPE_CHARACTER = '\\'; std::string escapeWildCards(const std::string& pattern); /** * @class TextPattern * @brief Helper class to store a text that will be used in a regex like search * * It stores the original text and an associated pattern to be used directly in the search adding * wild cards in both sides of the original text if needed. Example: * - text: hello -> pattern: *hello* * - text: * -> pattern: * * */ class TextPattern { public: TextPattern(const std::string& text); TextPattern(const char* text); TextPattern() = default; ~TextPattern() = default; TextPattern(const TextPattern& other) = default; TextPattern& operator=(const TextPattern& other) = default; TextPattern(TextPattern&& other) noexcept = default; TextPattern& operator=(TextPattern&& other) noexcept = default; const std::string& getText() const { return mText; } const std::string& getPattern() const { return mPattern; } private: std::string mText; std::string mPattern; void recalcPattern(); static bool isOnlyWildCards(const std::string& text); }; std::set::iterator getTagPosition(std::set& tokens, const std::string& pattern, const bool stripAccents = true); /* * Compare two UTF-8 strings for equality where the first string is * a "LIKE" expression. It is case and aceent insensitive. * * @param pattern the like pattern * @param str the UFT-8 string to compare against * @param esc the escape character * @param stripAccents True if accents should be stripped before comparison. * * @return true if the are the same and false if they are different */ bool likeCompare(const char* pattern, const char* str, const UChar32 esc = static_cast(ESCAPE_CHARACTER), const bool stripAccents = true); // Get the current process ID unsigned long getCurrentPid(); // Convenience. template struct IsStringType : std::false_type { }; template<> struct IsStringType : std::true_type { }; template<> struct IsStringType : std::true_type { }; // Retrieve a file's extension. template auto extensionOf(const StringType& path, std::string& extension) -> typename std::enable_if::value, bool>::type; template auto extensionOf(const StringType& path) -> typename std::enable_if::value, std::string>::type; /** * @brief Remove the dot for a string beginning with '.' */ template StringType removeDot(StringType&& s) { if (!s.empty() && s.front() == '.') s.erase(0, 1); return s; } // Translate a character representing a hexadecimal digit to an integer. template auto fromHex(char character) -> typename std::enable_if::value, std::pair >::type { // Ensure the character's in lowercase. character |= ' '; // Character's a decimal digit. if (character >= '0' && character <= '9') return std::make_pair(static_cast(character - '0'), true); // Character's a hexadecimal digit. if (character >= 'a' && character <= 'f') return std::make_pair(static_cast(character - 'W'), true); // Character's not a valid hexadecimal digit. return std::make_pair(0, false); } // Translate a string of hexadecimal digits to an integer. // // NOTE: The string should be trimmed of any whitespace. template auto fromHex(const char* current, const char* end) -> typename std::enable_if::value, std::pair >::type { // What's the largest value that T can represent? constexpr auto maximum = std::numeric_limits::max(); // Convenience. constexpr auto undefined = std::make_pair(T{}, false); // An empty string doesn't contain a valid hex number. if (current == end) return undefined; // Our accumulated value. T value{}; for ( ; current != end; ++current) { // Try and convert the current character to an integer. auto result = fromHex(*current); // Character wasn't a valid hexadecimal digit. if (!result.second) return undefined; // Make sure we don't wrap. if (value && maximum / value < 16) return undefined; // Scale the value by 16. value *= 16; // Again, make sure we don't wrap. if (maximum - value < result.first) return undefined; // Include the new digit in our running total. value += result.first; } // Return value to caller. return std::make_pair(value, true); } template auto fromHex(const char* begin, std::size_t size) -> typename std::enable_if::value, std::pair >::type { return fromHex(begin, begin + size); } // Translate a string of hexadecimal digits to an integer. // // NOTE: The string should be trimmed of any whitespace. template auto fromHex(const std::string& value) -> typename std::enable_if::value, std::pair >::type { return fromHex(value.data(), value.size()); } // Convenience. using SplitFragment = std::pair; using SplitResult = std::pair; // Split a string into two halves around a specific delimiter. // // NOTE: The second half includes the delimiter, if present. SplitResult split(const char* begin, const char* end, char delimiter); SplitResult split(const char* begin, const std::size_t size, char delimiter); SplitResult split(const std::string& value, char delimiter); /** * @brief Compares two utf8 strings using natural sorting * * This function uses icu collator * * @param i Pointer to a null-terminated utf-8 string. * @param j Pointer to a null-terminated utf-8 string. * * @returns 0 if i == j, negative if i < j, otherwise positive */ [[nodiscard]] int naturalsorting_compare(const char* i, const char* j); /** * @brief Compares two utf8 strings using natural sorting * * This function uses icu collator * * @param i Pointer to a utf-8 string. * @param iSize Size of the first utf-8 string. * @param j Pointer to a utf-8 string. * @param jSize Size of the second utf-8 string. * * @returns 0 if i == j, negative if i < j, otherwise positive */ [[nodiscard]] int naturalsorting_compare(const char* i, int iSize, const char* j, int jSize); /** * @class NaturalSortingComparator * @brief A helper struct to be used in container templates such as std::set to force natural * sorting */ struct NaturalSortingComparator { bool operator()(const std::string& lhs, const std::string& rhs) const { return naturalsorting_compare(lhs.c_str(), rhs.c_str()) < 0; } }; /** * @class MrProper * * @brief Ensures execution of a cleanup function when the object goes out of scope. * * It accepts a std::function during construction, which it executes once upon destruction. * This is useful for resource management and ensuring cleanup in cases of exceptions. * * Example usage: * void function() { * MrProper cleaner([](){ std::cout << "Cleanup action executed.\n"; }); * // Any code here that might throw or return early * } * * The class is non-copyable and non-movable to ensure the cleanup action is tightly bound to the * scope where it is declared. */ struct MrProper { using CleanupFunction = std::function; CleanupFunction mOnRelease; ~MrProper() { mOnRelease(); } explicit MrProper(std::function f): mOnRelease(f) {} MrProper() = delete; MrProper(const MrProper&) = delete; MrProper(MrProper&&) = delete; MrProper& operator=(const MrProper&) = delete; MrProper& operator=(MrProper&&) = delete; }; /** * @brief Ensures the given string has an asterisk in front and back. If the string is empty, "*" is * returned. * * @note The input argument is passed by copy intentionally to operate on it. */ std::string ensureAsteriskSurround(std::string str); /** * @brief Returns the index where the last '.' can be found in the fileName * * If there is not '.' in the input string, fileName.size() is returned * * @note This index is intended to be used with std::string::substr like: * size_t dotPos = fileExtensionDotPosition(fileName); * std::stirng basename = fileName.substr(0, dotPos); * std::stirng extension = fileName.substr(dotPos); // It will contain the '.' if present */ size_t fileExtensionDotPosition(const std::string& fileName); // Helper function to combine hashes inline uint64_t hashCombine(uint64_t seed, uint64_t value) { // the magic number is the twos complement version of the golden ratio return seed ^ (value + 0x9e3779b97f4a7c15 + (seed << 12) + (seed >> 4)); } /** * @class Timer * @brief A very simple helper struct to measure performance of some code pieces while developing a * time consuming part or refactoring. * * Example usage: * void exampleFunction() { * { * Timer t("Elapsed time: ", " ms"); * // Code block whose execution time you want to measure * std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Simulate work * } // Timer destructs here, and the elapsed time is printed. * } */ template struct Timer { Timer(const std::string& prefixMsg = "", const std::string& posfixMsg = ""): mPreMsg{prefixMsg}, mPosMsg{posfixMsg}, mStartTime{std::chrono::steady_clock::now()} // Correct initialization {} ~Timer() { const auto end = std::chrono::steady_clock::now(); const auto dur = end - mStartTime; // Duration between start and end std::cout << mPreMsg << std::chrono::duration_cast(dur).count() << mPosMsg << "\n"; } private: std::string mPreMsg; std::string mPosMsg; std::chrono::time_point mStartTime; // Fixed time_point type }; /** * @brief Returns std::this_thread:get_id() converted to a string */ std::string getThisThreadIdStr(); /** * @brief Converts a number of any arithmetic type to its string representation. * * @tparam T The type of the number to be converted. It must be an arithmetic type (e.g., int, * float, double). * * @param number The number to be converted to a string. * @return A `std::string` representing the number. If conversion fails or the type is not * arithmetic, an empty string is returned. * * @note This function only supports arithmetic types. The function will return an empty string if * the number cannot be successfully converted. */ template std::string numberToString(T number) { static_assert(std::is_arithmetic_v, "invalid numeric type"); char buffer[64]; if (auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), number); ec == std::errc()) { return std::string(buffer, ptr); } return {}; } /** * @brief Converts an string to a number of any aritmetic type. * * @tparam T The type of the number to be converted to. It must be an arithmetic type (e.g., int, * float, double). * * @param sv The string to be converted to a number * @return The numeric value contained in a std::optional. std::nullpopt if the conversion fails. */ template std::optional stringToNumber(const std::string_view sv) { static_assert(std::is_arithmetic_v, "invalid numeric type"); T r; if (std::from_chars(sv.data(), sv.data() + sv.size(), r).ec == std::errc()) return r; return std::nullopt; } /** * @brief Represents a range of unsigned integers, providing an iterator-based interface for * iteration. * * The Range class allows to create a range of unsigned integers, which can be used in * for-each loops or other iteration contexts. * * The range is of type [start, end), i.e., "start" is inclusive, "end" is exclusive. * 'start' should be smaller than 'end'. Otherwise the 'start' value will be truncated to the 'end' * value, resulting in an empty Range. */ class Range { public: Range(const unsigned start, const unsigned end): mStart(start), mEnd(end) { if (mStart > mEnd) { mStart = mEnd; } } class Iterator { public: using value_type = unsigned; using difference_type = std::ptrdiff_t; using pointer = const unsigned*; using reference = const unsigned&; using iterator_category = std::input_iterator_tag; explicit Iterator(const unsigned current): mCurrent(current) {} unsigned operator*() const { return mCurrent; } Iterator& operator++() { ++mCurrent; return *this; } bool operator!=(const Iterator& other) const { return mCurrent != other.mCurrent; } private: unsigned mCurrent; }; Iterator begin() const { return Iterator(mStart); } Iterator end() const { return Iterator(mEnd); } size_t size() const { return mEnd - mStart; } bool empty() const { return !size(); } private: unsigned mStart; unsigned mEnd; }; /** * @brief Generates a Range object from a starting value to an ending value. * * This function provides a convenient way to create a Range without directly constructing it. * * Example: * @code * for (const auto i : range(2, 6)) // Iterates over 2, 3, 4, 5 * { * std::cout << i << "\n"; * } * @endcode * * @param start The starting value of the range (inclusive). * @param end The ending value of the range (exclusive). Expected to be greater than 'start'. * Otherwise the returned Range will start from 'end' (i.e., empty Range). * @return A Range object representing the specified range. */ inline Range range(const unsigned start, const unsigned end) { return Range(start, end); } /** * @brief Overload of range() that generates a Range from 0 to the specified ending value. * * Example: * @code * for (const auto i : range(5)) // Iterates over 0, 1, 2, 3, 4 * { * std::cout << i << "\n"; * } * @endcode * * @param end The ending value of the range (exclusive). * @return A Range object representing the range from 0 to end. */ template && !std::is_same_v, int> = 0> inline Range range(const T end) { if constexpr (std::is_signed_v) { if (end <= 0) { return Range(0u, 0u); } } return Range(0u, static_cast(end)); } /** * @brief Returns std::nullopt if opt is negative, std::optional{opt} otherwise */ template constexpr std::optional convertIfPositive(const int opt) { return opt < 0 ? std::nullopt : std::optional{opt}; } inline std::optional charPtrToStrOpt(const char* s) { return s ? std::optional{s} : std::nullopt; } inline std::optional charPtrToStrViewOpt(const char* s) { return s ? std::optional{s} : std::nullopt; } template T* getPtr(std::optional& opt) noexcept { return opt.has_value() ? std::addressof(opt.value()) : nullptr; } template const T* getPtr(const std::optional& opt) noexcept { return opt.has_value() ? std::addressof(opt.value()) : nullptr; } inline const char* getConstCharPtr(const std::optional& opt) { return opt.has_value() ? opt->c_str() : nullptr; } inline bool isAllDigits(const std::string_view s) { return std::all_of(begin(s), end(s), [](const auto c) { return std::isdigit(c); }); } storagestatus_t getStorageStatusFromString(const std::string& storageStateStr); /** * @brief Returns if path is in file system case insensitive * * The directory must not be empty in order to validate whether case-insensitive access (uppercase * or lowercase naming) is supported. If it can't be checked, it returns std::nullopt */ std::optional isCaseInsensitive(const LocalPath& path, FileSystemAccess* fsaccess); template constexpr std::underlying_type_t getEnumValue(E e) { static_assert(std::is_enum_v, "E must be an enum"); return static_cast>(e); } PitagPurpose pitagPurposeFromChar(char c); PitagTrigger pitagTriggerFromChar(char c); PitagNodeType pitagNodeTypeFromChar(char c); PitagTarget pitagTargetFromChar(char c); PitagImportSource pitagImportSourceFromChar(char c); std::string pitagToString(const Pitag& pitag); std::optional pitagFromString(const std::string& pitagString); } // namespace mega #endif // MEGA_UTILS_H sdk-10.11.0/include/mega/utils_optional.h000066400000000000000000000132201516266226600202170ustar00rootroot00000000000000/** * @file * @brief Utilities around std::optional */ #ifndef INCLUDE_MEGA_UTILS_OPTIONAL_H_ #define INCLUDE_MEGA_UTILS_OPTIONAL_H_ #include #include #include #include namespace mega { template struct is_std_optional: std::false_type {}; template struct is_std_optional>: std::true_type {}; template constexpr bool is_std_optional_v = is_std_optional::value; /** * @brief Helper struct to mimic std::optional monadic operations from C++23 * This documentation applies to: * - Transform (struct), transform (function) * - AndThen (struct), and_then (function) * - OrElse (struct), or_else (function) * * See usage examples at utils_optional_test.cpp * * These implementations replace the calls to methods with the pipe operators (|), similar to how * range views can be manipulated since C++20. * @example * std::string valPlus1 = (std::optional {"5"} * | or_else([]{ return std::optional{""s}; }) * | and_then(toIntOpt) * | transform([](int n) { return n + 1; }) * | transform([](int n) { return std::to_string(n); })) * .value_or("NaN"s); * * @note Remember to deprecate these monadic utilities in favor of std::optional methods once C++23 * is adopted in the project. */ template struct Transform { Transform(F&& t): transformOp{std::forward(t)} {} Transform(const F& t): transformOp{t} {} template auto operator()(OptT&& optionalT) -> std::optional< std::invoke_result_t::value_type>> { using InputOptType = std::remove_cv_t>; static_assert(is_std_optional_v, "Transform must be invoked with a std::optional"); using InputType = typename InputOptType::value_type; static_assert(std::is_invocable_v, "Transform callable must be invocable with the optional's value type."); if (!optionalT.has_value()) return std::nullopt; return std::optional{std::invoke(transformOp, std::forward(optionalT).value())}; } private: F transformOp; }; template Transform> transform(F&& transformOp) { return Transform>(std::forward(transformOp)); } template auto operator|(OptT&& optionalT, Transform&& op) -> decltype(auto) { return op(std::forward(optionalT)); } /** * @brief Helper struct to mimic std::optional::or_else from C++23 * See usage examples at utils_optional_test.cpp * @note Remember to deprecate this class in favor of std::optional::or_else once C++23 is used in * the project. */ template struct OrElse { OrElse(F&& t): transformOp{std::forward(t)} {} OrElse(const F& t): transformOp{t} {} template std::decay_t operator()(OptT&& optionalT) { using InputOptType = std::remove_cv_t>; static_assert(is_std_optional_v, "OrElse must be invoked with a std::optional"); using ResultType = std::invoke_result_t; static_assert(is_std_optional_v, "OrElse callable must return a std::optional."); static_assert(std::is_invocable_v, "OrElse callable must be invocable without parameters."); if (optionalT.has_value()) return std::forward(optionalT); return transformOp(); } private: F transformOp; }; template OrElse> or_else(F&& transformOp) { return OrElse>(std::forward(transformOp)); } template auto operator|(OptT&& optionalT, OrElse&& op) -> decltype(auto) { return op(std::forward(optionalT)); } /** * @brief Helper struct to mimic std::optional::and_then from C++23 * See usage examples at utils_optional_test.cpp * @note Remember to deprecate this class in favor of std::optional::and_then once C++23 is used in * the project. */ template struct AndThen { AndThen(F&& t): transformOp{std::forward(t)} {} AndThen(const F& t): transformOp{t} {} template auto operator()(OptT&& optionalT) -> std::invoke_result_t::value_type> { using InputOptType = std::remove_cv_t>; static_assert(is_std_optional_v, "AndThen must be invoked with a std::optional"); using InputType = typename InputOptType::value_type; using ResultType = std::invoke_result_t; static_assert(std::is_invocable_v, "AndThen callable must be invocable with the optional's value type."); static_assert(is_std_optional_v, "AndThen callable must return a std::optional."); if (optionalT.has_value()) return std::invoke(transformOp, std::forward(optionalT).value()); return std::nullopt; } private: F transformOp; }; template AndThen> and_then(F&& transformOp) { return AndThen>(std::forward(transformOp)); } template auto operator|(OptT&& optionalT, AndThen&& op) -> decltype(auto) { return op(std::forward(optionalT)); } } #endif // INCLUDE_MEGA_UTILS_OPTIONAL_H_ sdk-10.11.0/include/mega/version.h000066400000000000000000000002761516266226600166460ustar00rootroot00000000000000#ifndef MEGA_MAJOR_VERSION #define MEGA_MAJOR_VERSION 10 #endif #ifndef MEGA_MINOR_VERSION #define MEGA_MINOR_VERSION 11 #endif #ifndef MEGA_MICRO_VERSION #define MEGA_MICRO_VERSION 0 #endifsdk-10.11.0/include/mega/waiter.h000066400000000000000000000036141516266226600164530ustar00rootroot00000000000000/** * @file mega/waiter.h * @brief Generic waiter interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_WAITER_H #define MEGA_WAITER_H 1 #include #include "types.h" namespace mega { // interface enabling a class to add its wakeup criteria to the waiter struct MEGA_API EventTrigger { // add wakeup criterion virtual void addevents(Waiter*, int) = 0; // process events after wakeup virtual int checkevents(Waiter*) { return 0; } virtual ~EventTrigger() = default; }; // wait for events struct MEGA_API Waiter { // current time (processwide) static std::atomic ds; // set ds to current time static void bumpds(); // This mutex protects concurrent updates of the value stored by the atomic "ds" // when multiple threads are in bumpds() static std::mutex dsMutex; // wait ceiling std::atomic maxds; // begin waiting cycle with timeout virtual void init(dstime); // add wakeup events void wakeupby(EventTrigger*, int); // wait for all added wakeup criteria (plus the host app's own), up to the // specified number of deciseconds virtual int wait() = 0; // force a wakeup virtual void notify() = 0; static const int NEEDEXEC = 1; static const int HAVESTDIN = 2; virtual ~Waiter() { } }; } // namespace #endif sdk-10.11.0/include/mega/win32/000077500000000000000000000000001516266226600157455ustar00rootroot00000000000000sdk-10.11.0/include/mega/win32/drivenotifywin.h000066400000000000000000000025061516266226600212010ustar00rootroot00000000000000/** * @file mega/win32/drivenotifywin.h * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #ifdef USE_DRIVE_NOTIFICATIONS // Include "mega/drivenotify.h" where needed. // This header cannot be used by itself. #include #include #include namespace mega { // Windows: Platform specific definition // // Uses WMI. Use 'wbemtest' tool to run WQL queries, for testing and comparison. class DriveNotifyWin final : public DriveNotify { public: ~DriveNotifyWin() override { stopNotifier(); } protected: void doInThread() override; enum EventType { UNKNOWN_EVENT, DRIVE_CONNECTED_EVENT, DRIVE_DISCONNECTED_EVENT }; }; } // namespace #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/include/mega/win32/gfx/000077500000000000000000000000001516266226600165315ustar00rootroot00000000000000sdk-10.11.0/include/mega/win32/gfx/worker/000077500000000000000000000000001516266226600200425ustar00rootroot00000000000000sdk-10.11.0/include/mega/win32/gfx/worker/comms.h000066400000000000000000000032131516266226600213300ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/comms.h" #include "mega/types.h" #include namespace mega { namespace gfx { namespace win_utils { std::wstring toFullPipeName(const std::string& name); } class NamedPipe : public IEndpoint { public: NamedPipe(HANDLE h, const std::string& name) : mPipeHandle(h), mName(name) {} NamedPipe(const NamedPipe&) = delete; NamedPipe(NamedPipe&& other); ~NamedPipe(); bool isValid() const { return mPipeHandle != INVALID_HANDLE_VALUE; } protected: enum class Type { Client, Server }; HANDLE mPipeHandle; std::string mName; private: bool doWrite(const void* data, size_t n, std::chrono::milliseconds timeout) override; bool doRead(void* data, size_t n, std::chrono::milliseconds timeout) override; // // The common part of doing an overlapped I/O. // // Overlapped I/O is a name used for asynchoruous I/O in the Windows API. // See https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-server-using-overlapped-i-o // bool doOverlappedOperation(std::function op, std::chrono::milliseconds timeout, const std::string& opStr); virtual Type type() const = 0; }; class WinOverlapped final { public: WinOverlapped(); ~WinOverlapped(); OVERLAPPED* data(); bool isValid() const { return mOverlapped.hEvent != NULL; }; // Return an error code and error string on error std::pair waitForCompletion(DWORD mWaitMs); private: OVERLAPPED mOverlapped; }; } // namespace } sdk-10.11.0/include/mega/win32/gfx/worker/comms_client.h000066400000000000000000000015071516266226600226720ustar00rootroot00000000000000#pragma once #include "mega/gfx/worker/comms_client_common.h" namespace mega { namespace gfx { class GfxCommunicationsClient : public IGfxCommunicationsClient { public: GfxCommunicationsClient(const std::string& pipeName); // Connect to the server // On success, a CommError::OK and a valid endpoint pair is returned // On failure, a CommError error and nullptr pair is returned std::pair> connect() override; private: // Do connection to the named pipe server // On success, a CommError::OK and a valid handle pair is returned // On failure, a CommError error and INVALID_HANDLE_VALUE pair is returned std::pair doConnect(LPCTSTR pipeName); CommError toCommError(DWORD winError) const; std::string mPipeName; }; } // namespace } sdk-10.11.0/include/mega/win32/megaconsole.h000066400000000000000000000143351516266226600204200ustar00rootroot00000000000000/** * @file mega/win32/megaconsole.h * @brief Win32 console I/O * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef CONSOLE_CLASS #define CONSOLE_CLASS WinConsole #include #include #include #include namespace mega { #ifdef NO_READLINE struct MEGA_API Utf8Rdbuf; struct MEGA_API ConsoleModel { enum { MaxHistoryEntries = 20 }; enum lineEditAction { nullAction, CursorLeft, CursorRight, CursorStart, CursorEnd, WordLeft, WordRight, HistoryUp, HistoryDown, HistoryStart, HistoryEnd, HistorySearchForward, HistorySearchBackward, ClearLine, DeleteCharLeft, DeleteCharRight, DeleteWordLeft, DeleteWordRight, Paste, AutoCompleteForwards, AutoCompleteBackwards }; #ifdef HAVE_AUTOCOMPLETE // If using autocomplete, client to specify the syntax of commands here so we know what. Assign to this directly ::mega::autocomplete::ACN autocompleteSyntax; // If supplied, autocomplete will try to get additional completions from this function (eg for consulting MEGAcmd's server) std::function(string)> autocompleteFunction; #endif // a buffer to store characters received from keypresses. After a newline is received, we // don't check for keypresses anymore until that line is consumed. std::wstring buffer; // the point in the buffer that new characters get inserted (corresponds to cursor on screen) size_t insertPos = 0; // we can receive multiple newlines in a single key event. All these must be consumed before we check for more keypresses bool newlinesBuffered = false; // remember the last N commands executed std::deque inputHistory; // when using up and down arrow keys, or using history search, this is the history line selected size_t inputHistoryIndex = 0; // slightly different handling on the first history keypress bool enteredHistory = false; bool searchingHistory = false; bool searchingHistoryForward = false; std::wstring historySearchString; // if echo is on then edits appear on screen; if it is off then nothing appears and history is not added (suitable for passwords). bool echoOn = true; // we can do autocomplete like windows' cmd.exe or like unix. Start with the one matching the current platform #ifdef WIN32 bool unixCompletions = false; #else bool unixCompletions = true; #endif #ifdef HAVE_AUTOCOMPLETE // flags to indicate to the real console if redraws etc need to occur ::mega::autocomplete::CompletionTextOut redrawInputLineConsoleFeedback; #endif bool redrawInputLineNeeded = false; bool consoleNewlineNeeded = false; // real console tells us a key is pressed resulting in a character to add void addInputChar(wchar_t c); // real console has interpreted a key press as a special action needed void performLineEditingAction(lineEditAction action, unsigned consoleWidth); // client can check this after adding characters or performing actions to see if the user submitted the line for processing bool checkForCompletedInputLine(std::wstring& ws); std::wstring getInputLineToCursor(); private: #ifdef HAVE_AUTOCOMPLETE autocomplete::CompletionState autocompleteState; #endif void getHistory(int index, int offset); void searchHistory(bool forwards); void updateHistoryMatch(bool forwards, bool increment); void deleteHistorySearchChars(size_t n); void deleteCharRange(int start, int end); void redrawInputLine(int p); int detectWordBoundary(int start, bool forward); void autoComplete(bool forwards, unsigned consoleWidth); }; #endif struct MEGA_API WinConsole : public Console { void readpwchar(char*, int, int* pw_buf_pos, char**); void setecho(bool); WinConsole(); ~WinConsole(); #ifdef NO_READLINE HANDLE inputAvailableHandle(); // functions for native command editing (ie not using readline library) string getConsoleFont(COORD& xy); bool setShellConsole(UINT codepage = CP_UTF8, UINT failover_codepage = CP_UTF8); void getShellCodepages(UINT& codepage, UINT& failover_codepage); #ifdef HAVE_AUTOCOMPLETE void setAutocompleteSyntax(autocomplete::ACN); void setAutocompleteFunction(std::function(string)>); #endif void setAutocompleteStyle(bool unix); bool getAutocompleteStyle() const; bool consolePeek(); bool consolePeekNonBlocking(); bool consolePeekBlocking(); bool consoleGetch(wchar_t& c); void updateInputPrompt(const std::string& newprompt); char* checkForCompletedInputLine(); void clearScreen(); void outputHistory(); void retractPrompt(); std::wstring getInputLineToCursor(); enum logstyle { no_log, utf8_log, utf16_log, codepage_log }; bool log(const std::string& filename, logstyle logstyle); static std::string toUtf8String(const std::wstring& ws, UINT codepage = CP_UTF8); static std::wstring toUtf16String(const std::string& s, UINT codepage = CP_UTF8); bool blockingConsolePeek; private: std::deque irs; HANDLE hInput; HANDLE hOutput; bool promptRetracted = false; ConsoleModel model; Utf8Rdbuf *rdbuf = nullptr; streambuf *oldrb1 = nullptr, *oldrb2 = nullptr; bool logging = false; std::string currentPrompt; size_t inputLineOffset = 0; void redrawPromptIfLoggingOccurred(); #ifdef HAVE_AUTOCOMPLETE void redrawInputLine(::mega::autocomplete::CompletionTextOut* autocompleteFeedback); #else void redrawInputLine(); #endif ConsoleModel::lineEditAction interpretLineEditingKeystroke(INPUT_RECORD &ir); #endif }; } // namespace #endif sdk-10.11.0/include/mega/win32/megaconsolewaiter.h000066400000000000000000000017661516266226600216400ustar00rootroot00000000000000/** * @file mega/win32/megaconsolewaiter.h * @brief Win32 event/timeout handling, listens for console input * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef CONSOLE_WAIT_CLASS #define CONSOLE_WAIT_CLASS WinConsoleWaiter namespace mega { class MEGA_API WinConsoleWaiter : public WinWaiter { #ifdef NO_READLINE WinConsole* console; #else HANDLE hInput; #endif public: int wait(); WinConsoleWaiter(WinConsole*); }; } // namespace #endif sdk-10.11.0/include/mega/win32/megafs.h000066400000000000000000000161031516266226600173610ustar00rootroot00000000000000/** * @file mega/win32/megafs.h * @brief Win32 filesystem/directory access/notification (Unicode) * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef FSACCESS_CLASS #define FSACCESS_CLASS WinFileSystemAccess #include "mega.h" #define DEBRISFOLDER "Rubbish" namespace mega { class MEGA_API WinFileAccess; struct MEGA_API WinDirAccess : public DirAccess { bool ffdvalid; WIN32_FIND_DATAW ffd; HANDLE hFind; LocalPath globbase; WIN32_FIND_DATAW currentItemAttributes; friend class WinFileAccess; public: bool dopen(LocalPath*, FileAccess*, bool) override; bool dnext(LocalPath&, LocalPath&, bool, nodetype_t*) override; WinDirAccess(); virtual ~WinDirAccess(); }; struct MEGA_API WinDirNotify; class MEGA_API WinFileSystemAccess : public FileSystemAccess { public: using FileSystemAccess::getlocalfstype; std::unique_ptr newfileaccess(bool followSymLinks = true) override; bool getlocalfstype(const LocalPath& path, FileSystemType& type) const override; unique_ptr newdiraccess() override; #ifdef ENABLE_SYNC DirNotify* newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* notificationWaiter) override; #endif bool issyncsupported(const LocalPath&, bool&, SyncError&, SyncWarning&) override; bool getsname(const LocalPath&, LocalPath&) const override; bool renamelocal(const LocalPath&, const LocalPath&, bool) override; bool copylocal(const LocalPath&, const LocalPath&, m_time_t) override; bool unlinklocal(const LocalPath&) override; bool rmdirlocal(const LocalPath&) override; bool mkdirlocal(const LocalPath&, bool hidden, bool logAlreadyExistsError) override; std::pair getmtimelocal(const LocalPath&) override; bool setmtimelocal(const LocalPath&, m_time_t) override; bool chdirlocal(LocalPath&) const override; bool expanselocalpath(const LocalPath& path, LocalPath& absolutepath) override; void addevents(Waiter*, int) override; static bool istransient(DWORD); bool istransientorexists(DWORD); bool exists(const LocalPath& path) const; bool isPathError(DWORD error) const; void osversion(string*, bool includeArchExtraInfo) const override; void statsid(string*) const override; static void emptydirlocal(const LocalPath&, dev_t = 0); ScanResult directoryScan(const LocalPath& path, handle expectedFsid, map& known, std::vector& results, bool followSymlinks, unsigned& nFingerprinted) override; WinFileSystemAccess(); ~WinFileSystemAccess(); bool cwd(LocalPath& path) const override; #ifdef ENABLE_SYNC bool fsStableIDs(const LocalPath& path) const override; std::set dirnotifys; #endif bool hardLink(const LocalPath& source, const LocalPath& target) override; m_off_t availableDiskSpace(const LocalPath& drivePath) override; static bool checkForSymlink(const LocalPath& lp); }; #ifdef ENABLE_SYNC struct MEGA_API WinDirNotify : public DirNotify { private: WinFileSystemAccess* fsaccess; #ifdef ENABLE_SYNC LocalNode* localrootnode; #endif HANDLE hDirectory; std::atomic mOverlappedExit; std::atomic mOverlappedEnabled; Waiter* clientWaiter; string notifybuf; DWORD dwBytes; OVERLAPPED overlapped; static VOID CALLBACK completion(DWORD dwErrorCode, DWORD dwBytes, LPOVERLAPPED lpOverlapped); void process(DWORD bytesTransferred); void readchanges(); static std::atomic smNotifierCount; static std::mutex smNotifyMutex; static HANDLE smEventHandle; static std::deque> smQueue; static std::unique_ptr smNotifierThread; static void notifierThreadFunction(); public: WinDirNotify(LocalNode& root, const LocalPath& rootPath, WinFileSystemAccess* owner, Waiter* waiter); ~WinDirNotify(); }; #endif struct MEGA_API WinAsyncIOContext : public AsyncIOContext { WinAsyncIOContext(); virtual ~WinAsyncIOContext(); virtual void finish(); OVERLAPPED *overlapped; }; class MEGA_API WinFileAccess : public FileAccess { HANDLE hFile; public: HANDLE hFind; WIN32_FIND_DATAW ffd; bool fopen(const LocalPath&, OpenFlag flag, FSLogging, DirAccess* iteratingDir, bool ignoreAttributes, bool skipcasecheck, LocalPath* actualLeafNameIfDifferent) override; bool fopen_impl(const LocalPath&, OpenFlag flag, FSLogging, bool async, DirAccess* iteratingDir, bool ignoreAttributes, bool skipcasecheck, LocalPath* actualLeafNameIfDifferent, bool shareDelete = false); // Open for MAC computation - includes FILE_SHARE_DELETE so file can be moved/deleted while open bool fopenForMacRead(const LocalPath& path, FSLogging fsl) override; void updatelocalname(const LocalPath&, bool force) override; bool fread(string *, unsigned, unsigned, m_off_t); void fclose() override; bool fwrite(const void* buffer, unsigned long length, m_off_t position, unsigned long* numWritten = nullptr, bool* retry = nullptr) override; bool fstat(m_time_t& modified, m_off_t& fileSize) override; bool ftruncate(m_off_t newSize) override; bool sysread(void* buffer, unsigned long length, m_off_t offset, bool* retry) override; bool sysstat(m_time_t*, m_off_t*, FSLogging) override; bool sysopen(bool async, FSLogging) override; void sysclose() override; // async interface bool asyncavailable() override; void asyncsysopen(AsyncIOContext* context) override; void asyncsysread(AsyncIOContext* context) override; void asyncsyswrite(AsyncIOContext* context) override; static bool skipattributes(DWORD); WinFileAccess(Waiter *w); ~WinFileAccess(); // Mark this file as a sparse file. bool setSparse() override; // Retrieve this file's allocated and reported size. auto getFileSize() const -> std::optional> override; protected: AsyncIOContext* newasynccontext() override; static VOID CALLBACK asyncopfinished( DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped); }; } // namespace #endif sdk-10.11.0/include/mega/win32/meganet.h000066400000000000000000000013111516266226600175320ustar00rootroot00000000000000/** * @file mega/win32/meganet.h * @brief POSIX network access layer (using cURL) * * (c) 2013-2015 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/posix/meganet.h" sdk-10.11.0/include/mega/win32/megasys.h000066400000000000000000000041501516266226600175660ustar00rootroot00000000000000/** * @file mega/win32/megasys.h * @brief Mega SDK platform-specific includes (Win32) * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGA_WIN32_OS_H #define MEGA_WIN32_OS_H 1 // platform dependent constants #if defined(__ANDROID__) && !defined(HAVE_SDK_CONFIG_H) #include "mega/config-android.h" #else #include "mega/config.h" #endif // FIXME: move to autoconf #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // the MEGA SDK assumes writable, contiguous string::data() #include #if defined(USE_PTHREAD) && defined (__MINGW32__) #include #endif #include #pragma push_macro("NOMINMAX") #ifndef NOMINMAX #define NOMINMAX #endif #include #include #pragma pop_macro("NOMINMAX") #ifdef __MINGW32__ //#include // x509 define clashes with webrtc #endif //#include // x509 define clashes with webrtc #include #include #define atoll _atoi64 #define strncasecmp _strnicmp #define strtoull _strtoui64 #ifndef strcasecmp #define strcasecmp _stricmp #endif #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include #include #endif sdk-10.11.0/include/mega/win32/megawaiter.h000066400000000000000000000024761516266226600202540ustar00rootroot00000000000000/** * @file mega/win32/megawaiter.h * @brief Win32 event/timeout handling * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef WAIT_CLASS #define WAIT_CLASS WinWaiter typedef ULONGLONG (WINAPI * PGTC)(); extern PGTC pGTC; namespace mega { class MEGA_API WinWaiter : public Waiter { vector handles; vector flags; size_t index = 0; public: int wait(); bool addhandle(HANDLE handle, int); void notify(); WinWaiter(); ~WinWaiter(); #ifdef MEGA_MEASURE_CODE struct PerformanceStats { uint64_t waitTimedoutNonzero = 0; uint64_t waitTimedoutZero = 0; uint64_t waitIOCompleted = 0; uint64_t waitSignalled= 0; } performanceStats; #endif protected: HANDLE externalEvent; }; } // namespace #endif sdk-10.11.0/include/megaapi.h000066400000000000000000044553541516266226600156720ustar00rootroot00000000000000/** * @file megaapi.h * @brief Public header file of the intermediate layer for the MEGA C++ SDK. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGAAPI_H #define MEGAAPI_H #include #include #include #include #include #ifdef __APPLE__ #include #endif namespace mega { typedef uint64_t MegaHandle; typedef int64_t MegaTimeStamp; // unix timestamp struct MegaTotpTokenLifetime { std::string token; unsigned remainingLifeTimeSeconds; }; struct MegaTotpTokenGenResult { int errorCode; MegaTotpTokenLifetime result; }; struct MegaSearchLexicographicalOffset { std::string mLastName; std::optional mLastType{}; // eg. MegaNode::TYPE_FOLDER std::optional mLastHandle{}; }; #ifdef WIN32 const char MEGA_DEBRIS_FOLDER[] = "Rubbish"; #else const char MEGA_DEBRIS_FOLDER[] = ".debris"; #endif /** * @brief INVALID_HANDLE Invalid value for a handle * * This value is used to represent an invalid handle. Several MEGA objects can have * a handle but it will never be mega::INVALID_HANDLE * */ const MegaHandle INVALID_HANDLE = ~(MegaHandle)0; const MegaTimeStamp MEGA_INVALID_TIMESTAMP = 0; class MegaListener; class MegaRequestListener; class MegaTransferListener; class MegaScheduledCopyListener; class MegaGlobalListener; class MegaTreeProcessor; class MegaAccountDetails; class MegaAchievementsDetails; class MegaPricing; class MegaCurrency; class MegaNode; class MegaUser; class MegaUserAlert; class MegaContactRequest; class MegaShare; class MegaError; class MegaRequest; class MegaEvent; class MegaTransfer; class MegaScheduledCopy; class MegaSync; class MegaStringList; class MegaNodeList; class MegaUserList; class MegaUserAlertList; class MegaContactRequestList; class MegaShareList; class MegaTransferList; class MegaFolderInfo; class MegaTimeZoneDetails; class MegaPushNotificationSettings; class MegaBackgroundMediaUpload; class MegaCancelToken; class MegaUploadOptions; class MegaApi; class MegaSemaphore; class MegaScheduledMeeting; class MegaScheduledMeetingList; class MegaScheduledFlags; class MegaScheduledRules; class MegaIntegerMap; class MegaIntegerList; class MegaSyncStall; class MegaSyncStallList; class MegaSyncStallMap; class MegaFuseExecutorFlags; class MegaFuseFlags; class MegaFuseInodeCacheFlags; class MegaMount; class MegaMountFlags; class MegaMountList; class MegaVpnRegionList; class MegaVpnCredentials; class MegaNetworkConnectivityTestResults; class MegaNodeTree; class MegaCompleteUploadData; class MegaNotificationList; class MegaCancelSubscriptionReasonList; #if defined(SWIG) #define MEGA_DEPRECATED #else #define MEGA_DEPRECATED [[deprecated]] #endif /** * @brief Interface to provide an external GFX processor * * You can implement this interface to provide a graphics processor to the SDK * in the MegaApi::MegaApi constructor. That way, SDK will use your implementation to generate * thumbnails/previews when needed. * * The implementation will receive callbacks from an internal worker thread. * * Images will be sequentially processed. At first, the SDK will call MegaGfxProcessor::readBitmap * with the path of the file. Then, it will call MegaGfxProcessor::getWidth and * MegaGfxProcessor::getHeight to get the dimensions of the file (in pixels). After that, the SDK * will call MegaGfxProcessor::getBitmapDataSize and MegaGfxProcessor::getBitmapData in a loop to * get thumbnails/previews of different sizes. Finally, the SDK will call * MegaGfxProcessor::freeBitmap to let you free the resources required to process * the current file. * * If the image has EXIF data, it should be rotated/mirrored before doing any * other processing. MegaGfxProcessor::getWidth, MegaGfxProcessor::getHeight and all * other coordinates in this interface are expressed over the image after the required * transformation based on the EXIF data. * * Generated images can be in JPG or PNG format. * */ class MegaGfxProcessor { public: enum { GFX_HINT_NONE = 0, GFX_HINT_FORMAT_PNG = 1, // Format can be in PNG }; /** * @brief Read the image file and check if it can be processed * * This is the first function that will be called to process an image. No other * functions of this interface will be called before this one. * * The recommended implementation is to read the file, check if it's an image and * get its dimensions. If everything is OK, the function should return true. If the * file isn't an image or can't be processed, this function should return false. * * The SDK will call this function with all files so it's probably a good idea to * check the extension before trying to open them. * * @param path Path of the file that is going to be processed * @return True if the implementation is able to manage the file, false otherwise. */ virtual bool readBitmap(const char* path); /** * @brief Returns the width of the image * * This function must return the width of the image at the path provided in MegaGfxProcessor::readBitmap * If a number <= 0 is returned, the image won't be processed. * * @return The width of the image */ virtual int getWidth(); /** * @brief Returns the height of the image * * This function must return de width of the image at the path provided in MegaGfxProcessor::readBitmap * If a number <= 0 is returned, the image won't be processed. * * @return The height of the image */ virtual int getHeight(); /** * @brief Generates a thumbnail/preview image. * * This function provides the parameters of the thumbnail/preview that the SDK wants to * generate. If the implementation can create it, it has to provide the size of the buffer (in * bytes) that it needs to store the generated image. Otherwise, it should return a number <= 0. * * The implementation of this function has to scale the image to the size (width, height) and * then extract the rectangle starting at the point (px, py) with size (rw, rh). (px, py, rw and * rh) are expressed in pixels over the scaled image, being the point (0, 0) the upper-left * corner of the scaled image, with the X-axis growing to the right and the Y-axis growing to * the bottom. * * @param width Width of the scaled image from which the thumbnail/preview image will be * extracted * @param height Height of the scaled image from which the thumbnail/preview image will be * extracted * @param px X coordinate of the starting point of the desired image (in pixels over the scaled * image) * @param py Y coordinate of the starting point of the desired image (in pixels over the scaled * image) * @param rw Width of the desired image (in pixels over the scaled image) * @param rh Height of the desired image (in pixels over the scaled image) * @param hint The hint for thumbnail and preview generation: * - GFX_HINT_NONE * - GFX_HINT_FORMAT_PNG * * @return Size of the buffer required to store the image (in bytes) or a number <= 0 if it's * not possible to generate it. * */ virtual int getBitmapDataSize(int width, int height, int px, int py, int rw, int rh, int hint); /** * @brief Copy the thumbnail/preview data to a buffer provided by the SDK * * The SDK uses this function immediately after MegaGfxProcessor::getBitmapDataSize when that * fuction succeed. The implementation of this function must copy the data of the image in the * buffer provided by the SDK. The size of this buffer will be the same as the value returned * in the previous call to MegaGfxProcessor::getBitmapDataSize. That size is provided in the * second parameter for compatibility with SWIG and to help the implementation to prevent * buffer overflow errors. * * @param bitmapData Preallocated buffer in which the implementation must write the generated image * @param size Size of the buffer. It will be the same that the previous return value of * MegaGfxProcessor::getBitmapDataSize * * @return True in case of success, false otherwise. */ virtual bool getBitmapData(char *bitmapData, size_t size); /** * @brief Free resources associated with the processing of the current image * * With a call of this function, the processing of the image started with a call to * MegaGfxProcessor::readBitmap ends. No other functions will be called to continue processing * the current image, so you can free all related resources. * */ virtual void freeBitmap(); /** * @brief Indicate which file extensions (file/image types) are supported * * Return a string with all the supported extensions concatenated, with . separating * Make sure to include a trailing . eg. ".jpg.png.bmp.jpeg." * * The caller does not take ownership of the string. * * If not supplied, all relevant files will be attempted. */ virtual const char* supportedImageFormats(); /** * @brief Indicate which file extensions (file/image types) are supported * * Return a string with all the supported extensions concatenated, with . separating * Make sure to include a trailing . eg. ".mpeg.mp4.avi.mkv." * * The caller does not take ownership of the string. * * If not supplied, all relevant files will be attempted. */ virtual const char* supportedVideoFormats(); virtual ~MegaGfxProcessor(); }; /** * @brief Represents a GFX provider with a hidden implementation and interfaces for creating different GFX providers. * * There are three interfaces available for creating various GFX providers: * - @see MegaGfxProvider::createIsolatedInstance * - @see MegaGfxProvider::createExternalInstance * - @see MegaGfxProvider::createInternalInstance * * You can use one of these interfaces to create a GFX provider and provide it to the SDK within the MegaApi::MegaApi * constructor. Subsequently, the SDK will utilize the GFX provider for generating thumbnails and previews when needed. * For more details, refer to @see MegaApi::MegaApi. */ class MegaGfxProvider { public: virtual ~MegaGfxProvider(); /** * @brief Create a graphics processor that implemented and run in an isolated process. * * Note: Windows, Linux, macOS are supported. * * @param endpointName The unique name used for communicating with the isolated process. * @param executable The path to the executable file. * @param keepAliveInSeconds The amount of time (in seconds) the isolated process stays active * without receiving any requests. * @param extraArgs Additional arguments that will be passed directly to the isolated process. * * @note The created instance sends a hello request every keepAliveInSeconds / 3 seconds to * ensure the isolated process stays running. */ static MegaGfxProvider* createIsolatedInstance(const char* endpointName, const char* executable, unsigned int keepAliveInSeconds = 60, const MegaStringList* extraArgs = nullptr); /** * @brief Create a graphics processor that use your implementations @see MegaGfxProcessor. * * @param processor Your own implementation */ static MegaGfxProvider* createExternalInstance(MegaGfxProcessor* processor); /** * @brief Create a graphics processor that utilizes the SDK's implementation and runs in the same * process as the caller. */ static MegaGfxProvider* createInternalInstance(); }; /** * @brief Contains the information related to a proxy server. * * Pass an object of this class to MegaApi::setProxySettings to * start using a proxy server. * * Currently, only HTTP proxies are allowed. The proxy server * should support HTTP request and tunneling for HTTPS. * */ class MegaProxy { public: enum {PROXY_NONE = 0, PROXY_AUTO = 1, PROXY_CUSTOM = 2}; /** * @brief Creates a MegaProxy object with the default settings (PROXY_AUTO) */ MegaProxy(); virtual ~MegaProxy(); /** * @brief Sets the type of the proxy * * The allowed values in the current version are: * - PROXY_NONE means no proxy * - PROXY_AUTO means automatic detection (default) * - PROXY_CUSTOM means a proxy using user-provided data * * @param newProxyType Sets the type of the proxy */ void setProxyType(int newProxyType); /** * @brief Sets the URL of the proxy * * That URL must follow this format: "://:" * * This is a valid example: http://127.0.0.1:8080 * * @param newProxyURL URL of the proxy: "://:" */ void setProxyURL(const char* newProxyURL); /** * @brief Set the credentials needed to use the proxy * * If you don't need to use any credentials, do not use this function * or pass NULL in the first parameter. * * @param newUsername Username to access the proxy, or NULL if credentials aren't needed * @param newPassword Password to access the proxy */ void setCredentials(const char* newUsername, const char* newPassword); /** * @brief Returns the current proxy type of the object * * The allowed values in the current version are: * - PROXY_NONE means no proxy * - PROXY_AUTO means automatic detection (default) * - PROXY_CUSTOM means a proxy using user-provided data * * @return Current proxy type (PROXY_NONE, PROXY_AUTO or PROXY_CUSTOM) */ int getProxyType(); /** * @brief Returns the URL of the proxy, previously set with MegaProxy::setProxyURL. * * The MegaProxy object retains the ownership of the returned value. * It will be valid until the MegaProxy::setProxyURL is called (that will delete the previous value) * or until the MegaProxy object is deleted. * * @return URL of the proxy */ const char *getProxyURL(); /** * @brief Returns true if credentials are needed to access the proxy, false otherwise. * * The default value of this function is false. It will return true after calling * MegaProxy::setCredentials with a non NULL username. * * @return True if credentials are needed to access the proxy, false otherwise. */ bool credentialsNeeded(); /** * @brief Return the username required to access the proxy * * The MegaProxy object retains the ownership of the returned value. * It will be valid until the MegaProxy::setCredentials is called (that will delete the previous value) * or until the MegaProxy object is deleted. * * @return Username required to access the proxy */ const char *getUsername(); /** * @brief Return the username required to access the proxy * * The MegaProxy object retains the ownership of the returned value. * It will be valid until the MegaProxy::setCredentials is called (that will delete the previous value) * or until the MegaProxy object is deleted. * * @return Password required to access the proxy */ const char *getPassword(); private: int proxyType; const char *proxyURL; const char *username; const char *password; }; /** * @brief Interface to receive SDK logs * * You can implement this class and pass an object of your subclass to MegaApi::setLoggerClass * to receive SDK logs. You will have to use also MegaApi::setLogLevel to select the level of * the logs that you want to receive. * */ class MegaLogger { public: /** * @brief This function will be called with all logs with level <= your selected * level of logging (by default it is MegaApi::LOG_LEVEL_INFO) * * @param time Readable string representing the current time. * * The SDK retains the ownership of this string, it won't be valid after this funtion returns. * * @param loglevel Log level of this message * * Valid values are: * - MegaApi::LOG_LEVEL_FATAL = 0 * - MegaApi::LOG_LEVEL_ERROR = 1 * - MegaApi::LOG_LEVEL_WARNING = 2 * - MegaApi::LOG_LEVEL_INFO = 3 * - MegaApi::LOG_LEVEL_DEBUG = 4 * - MegaApi::LOG_LEVEL_VERBOSE = 5 * - MegaApi::LOG_LEVEL_MAX = 5 * * @param source Location where this log was generated * * For logs generated inside the SDK, this will contain the source file and the line of code. * The SDK retains the ownership of this string, it won't be valid after this funtion returns. * * @param message Log message * * The SDK retains the ownership of this string, it won't be valid after this funtion returns. * * @param directMessages: in ENABLE_LOG_PERFORMANCE MODE, this will indicate the logger that an array of const char* should * be written in the logs immediately without buffering the output. message can be discarded in that case. * * @param directMessagesSizes: size of the previous const char *. * */ virtual void log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, int numberMessages = 0 #endif ); virtual ~MegaLogger(){} }; /** * @brief Represents a node (file/folder) in the MEGA account * * It allows to get all data related to a file/folder in MEGA. It can be also used * to start SDK requests (MegaApi::renameNode, MegaApi::moveNode, etc.) * * Objects of this class aren't live, they are snapshots of the state of a node * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can inspect the MEGA filesystem and get these objects using * MegaApi::getChildren, MegaApi::getChildNode and other MegaApi functions. * */ class MegaNode { public: enum { TYPE_UNKNOWN = -1, TYPE_FILE = 0, TYPE_FOLDER = 1, TYPE_ROOT = 2, TYPE_VAULT = 3, TYPE_INCOMING = TYPE_VAULT, // kept for backwards-compatibility (renamed to Vault) TYPE_RUBBISH = 4 }; enum { NODE_LBL_UNKNOWN = 0, NODE_LBL_RED, NODE_LBL_ORANGE, NODE_LBL_YELLOW, NODE_LBL_GREEN, NODE_LBL_BLUE, NODE_LBL_PURPLE, NODE_LBL_GREY, }; enum { CHANGE_TYPE_REMOVED = 0x01, CHANGE_TYPE_ATTRIBUTES = 0x02, CHANGE_TYPE_OWNER = 0x04, CHANGE_TYPE_TIMESTAMP = 0x08, CHANGE_TYPE_FILE_ATTRIBUTES = 0x10, CHANGE_TYPE_INSHARE = 0x20, CHANGE_TYPE_OUTSHARE = 0x40, CHANGE_TYPE_PARENT = 0x80, CHANGE_TYPE_PENDINGSHARE = 0x100, CHANGE_TYPE_PUBLIC_LINK = 0x200, CHANGE_TYPE_NEW = 0x400, CHANGE_TYPE_NAME = 0x800, CHANGE_TYPE_FAVOURITE = 0x1000, CHANGE_TYPE_COUNTER = 0x2000, CHANGE_TYPE_SENSITIVE = 0x4000, CHANGE_TYPE_PWD = 0x8000, CHANGE_TYPE_DESCRIPTION = 0x10000, CHANGE_TYPE_TAGS = 0x20000, }; /** * @brief Pure Object Data for Credit Card Node attributes. Instances of this class are * returned by the function getCreditCardData on a Credit Card Node, as well as provided * as an argument for Credit Card Node updates in updateCreditCardNode function. */ class CreditCardNodeData { public: virtual ~CreditCardNodeData() = default; /** * @brief Creates a new instance of CreditCardNodeData. * * @param cardNumber Number of the card (All characters must be digits). This field * cannot be null nor empty when creating a new Credit card Node * @param notes Notes to attach to the Credit card node * @param cardHolderName Name of holder of Credit Card * @param cvv card verification Value of the Credit Card (All characters must be digits) * @param expirationDate expiration date of Credit card (Expected format `MM/YY` with MM * and YY digits) * * @note: nullptr can be used to specify that a field is not to be updated when calling * MegaApi::updateCreditCardNode. * * @note The caller takes the ownership of the returned pointer. * * @return A pointer to the newly created object which will be owned by the caller. */ static CreditCardNodeData* createInstance(const char* cardNumber, const char* notes, const char* cardHolderName, const char* cvv, const char* expirationDate); /** * @brief Set cardNumber attribute value. * * @note All characters must be digits * * @param cardNumber Value to set */ virtual void setCardNumber(const char* cardNumber) = 0; /** * @brief Set notes attribute value. * * @param notes Value to set */ virtual void setNotes(const char* notes) = 0; /** * @brief Set cardHolderName attribute value. * * @param cardHolderName Value to set */ virtual void setCardHolderName(const char* cardHolderName) = 0; /** * @brief Set cvv attribute value. * * @note All characters must be digits * * @param cvv Value to set */ virtual void setCvv(const char* cvv) = 0; /** * @brief Set expirationDate attribute value. * * @note Expected format `MM/YY` with MM and YY digits * * @param expirationDate Value to set */ virtual void setExpirationDate(const char* expirationDate) = 0; /** * @brief Get cardNumber attribute value. * * @return null-terminated string with the cardNumber value or nullptr/NULL if none */ virtual const char* cardNumber() const = 0; /** * @brief Get notes attribute value. * * @return null-terminated string with the notes value or nullptr/NULL if none */ virtual const char* notes() const = 0; /** * @brief Get cardHolderName attribute value. * * @return null-terminated string with the cardHolderName value or nullptr/NULL if none */ virtual const char* cardHolderName() const = 0; /** * @brief Get cvv attribute value. * * @return null-terminated string with the cvv value or nullptr/NULL if none */ virtual const char* cvv() const = 0; /** * @brief Get expiration date attribute value (in MM/YY format). * * @return null-terminated string with the expirationDate value or nullptr/NULL if none */ virtual const char* expirationDate() const = 0; protected: CreditCardNodeData() = default; }; /** * @brief Pure Object Data for Password Node attributes. Instances of this class are * returned by the function getPasswordData on a Password Node, as well as provided * as an argument for Password Node updates in updatePasswordNode function. */ class PasswordNodeData { public: /** * @brief Represents data related to TOTP (Time-based One-Time Password) token * generation. * * Example 1: Create a Password node with TOTP data. * 1) Create an instance of PasswordNodeData::TotpData: * * std::unique_ptr totpData{ * TotpData::createInstance("abcd", 20, TotpData::HASH_ALGO_SHA256, 8)}; * * 2) Create an instance of Password Node Data providing TOTP data created in previous * step: * * std::unique_ptr pwdData { * PasswordNodeData::createInstance("12},\" '34", * "notes", * "url", * "userName", * totpData.get())}; * * 3) Invoke MegaApi::createPasswordNode as usual providing pwdData created in previous * step * * Example 2: Update a TOTP field from an existing node: * 1) Get the password data from the node: * * std::unique_ptr pwdData{n->getPasswordData()}; * * 2) Create a new instance of TotpData as in step 1) in previous example with the new * information or get a copy of the data stored in the `pwdData` and modify the field * as follows: * * std::unique_ptr tData {pwdData->totpData()->copy()}; * tData->setSharedSecret("HQER2385"); * * 3) Set this new TotpData to the pwdData: * * pwdData->setTotpData(tData.get()); * * 4) Finally, call updatePasswordNode with this pwdData. * * Example 3: Remove TOTP data from a password node: * 1) Get the password data from the node as step 1) of example 2. * 2) Create a special instance of TotpData to remove the data: * * std::unique_ptr totpData{TotpData::createRemovalInstance()}; * * 3) Set this totpData to the pwdData as in step 3) in example 2. * 4) Finally, call updatePasswordNode with this pwdData. */ class TotpData { public: /** * @brief The Validation class represents the current validation status for a * TotpData instance * * Usage: * - Get a validation instance from your TotpData object using the * TotpData::getValidation() method * - If you are planning to use the object to update the existing TotpData of a * password node, invoke the Validation::isValidForUpdate() to check if the your * TotpData object contains valid data. * - If it returns true, you can safely use it to update the node. No errors will * be triggered due to the format of the totp data. * - If not (returns false), you can check the outputs from the following methods * to detect where the problem comes from: * - sharedSecretValid(): Must be a string only containing characters in * "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" * - algorithmValid(): Must be one of the enum entries `HASH_ALGO_*` * - nDigitsValid(): Between 6 and 10 both included * - expirationTimeValid(): A positive number greater than 0 * - If the instances is going to be used for creating a new password node with totp * data or to update a node that has no totp data stored, you should check the * output from Validation::isValidForCreate(). * - If it returns true, you can use the TotpData instance to create a new node or * to update an existing one without previous totp data. * - Else, in addition to the methods that must return true also for the update * (above), for creation, all the fields must be present. For that you can * check: * - sharedSecretExist() * - algorithmExist() * - nDigitsExist() * - expirationTimeExist() */ class Validation { public: virtual ~Validation() = default; /** * @brief Returns `true` if shared secret has a value, `false` otherwise */ virtual bool sharedSecretExist() const = 0; /** * @brief Returns `true` if shared secret is valid, `false` otherwise */ virtual bool sharedSecretValid() const = 0; /** * @brief Returns `true` if algorithm has a value different from TOTPNULLOPT, * `false` otherwise */ virtual bool algorithmExist() const = 0; /** * @brief Returns `true` if algorithm is valid, `false` otherwise */ virtual bool algorithmValid() const = 0; /** * @brief Returns `true` if expiration time has a value different from * TOTPNULLOPT, `false` otherwise */ virtual bool expirationTimeExist() const = 0; /** * @brief Returns `true` if expiration time is valid, `false` otherwise */ virtual bool expirationTimeValid() const = 0; /** * @brief Returns `true` if number of digits has a value different from * TOTPNULLOPT, `false` otherwise */ virtual bool nDigitsExist() const = 0; /** * @brief Returns `true` if number of digits is valid, `false` otherwise */ virtual bool nDigitsValid() const = 0; /** * @brief Returns `true` if TotpData instance is valid for initializing a new * totp data field in a password node, `false` otherwise * @note For initializing the totpData field in a password node, all the * mandatory fields (shared secret, expiration time, algorithm and n digits) * must be present in the TotpData instance. */ virtual bool isValidForCreate() const = 0; /** * @brief Returns `true` if TotpData instance is valid just for updating * existing totp data on a password node, `false` otherwise */ virtual bool isValidForUpdate() const = 0; protected: Validation() = default; }; enum { HASH_ALGO_SHA1 = 0, HASH_ALGO_SHA256 = 1, HASH_ALGO_SHA512 = 2, }; // Default values when not provided by the authentication provider static constexpr int DEFAULT_HASH_ALGO = 0; static constexpr int DEFAULT_EXPIRATION_TIME_SECS = 30; static constexpr int DEFAULT_NDIGITS = 6; // Use this constant to leave a field untouched static constexpr int TOTPNULLOPT = -1; virtual ~TotpData() = default; /** * @brief Creates a special instance of `TotpData` marked for removal. * This instance could be used to remove TOTP data from PasswordNodeData. * * @return A pointer to a `TotpData` instance marked for removal. * @note The caller takes the ownership of the returned pointer. */ static TotpData* createRemovalInstance(); /** * @brief Creates a new instance of `TotpData` with specified parameters. * * In an update operation, to leave values untouched, use nullptr for `sharedSecret` * and TOTPNULLOPT constant for the rest of the parameters. * * For creating a node with Totp data or for setting it in an existing node that has * no totp data, you are forced to pass all the parameters valid values. You can use * DEFAULT_EXPIRATION_TIME_SECS, DEFAULT_HASH_ALGO and DEFAULT_NDIGITS to set * default values. * * @param sharedSecret The shared secret key for TOTP. * @param expirationTimeSecs The expiration time in seconds. * @param hashAlgorithm The hashing algorithm to use. * @param ndigits The number of digits in the generated TOTP code. * @return A pointer to a newly created `TotpData` instance. * @note The caller takes the ownership of the returned pointer. */ static TotpData* createInstance(const char* sharedSecret, const int expirationTimeSecs, const int hashAlgorithm, const int ndigits); /** * @brief Returns the shared secret key for TOTP * * @return the null-terminated string with shared secret key for TOTP if any, * nullptr/NULL otherwise */ virtual const char* sharedSecret() const = 0; /** * @brief Returns the expiration time in seconds. * * @return the expiration time in seconds if any, TOTPNULLOPT otherwise. */ virtual int expirationTime() const = 0; /** * @brief Returns the hashing algorithm to use. * * @return the hashing algorithm to use if any, TOTPNULLOPT otherwise. */ virtual int hashAlgorithm() const = 0; /** * @brief Returns the number of digits in the generated TOTP code. * * @return true the number of digits in the generated TOTP code if any, TOTPNULLOPT * otherwise. */ virtual int nDigits() const = 0; /** * @brief Check if TOTP data instance is marked to be removed * * @return true if TOTP data instance is marked to be removed, otherwise false */ virtual bool markedToRemove() const = 0; /** * @brief Set shared secret attribute value. * * @param sharedSecret Value to set */ virtual void setSharedSecret(const char* sharedSecret) = 0; /** * @brief Set expiration time attribute value. * * @param expirationTimeSecs time Value to set */ virtual void setExpirationTime(const int expirationTimeSecs) = 0; /** * @brief Set Hash algorithm attribute value. * * @param algorithm Value to set */ virtual void setHashAlgorithm(const int algorithm) = 0; /** * @brief Set Ndigits attribute value. * * @param n Value to set */ virtual void setNdigits(const int n) = 0; /** * @brief Returns a copy of TOTP data. * * @return a pointer to the copy of TOTP data. */ virtual TotpData* copy() const = 0; /** * @brief Returns a Validation instance that can be used to check any error detected * in the TotpData object * * @return A pointer to a newly created `Validation` instance. * @note The caller takes the ownership of the returned pointer. * @see Validation class for more details */ virtual Validation* getValidation() const = 0; protected: TotpData() = default; }; /** * @brief Creates a new instance of PasswordNodeData * @return A pointer to the newly created object which will be owned by the caller */ static PasswordNodeData* createInstance(const char* pwd, const char* notes, const char* url, const char* userName, const TotpData* totpData); /** * @brief Set TOTP data attribute value. * * @param totpData Value to set */ virtual void setTotpData(const TotpData* totpData) = 0; /** * @brief Set password attribute value. * * @param pwd Value to set */ virtual void setPassword(const char* pwd) = 0; /** * @brief Set notes attribute value. * * @param n Value to set */ virtual void setNotes(const char* n) = 0; /** * @brief Set URL attribute value. * * @param u Value to set */ virtual void setUrl(const char* u) = 0; /** * @brief Set user name attribute value. * * @param un Value to set */ virtual void setUserName(const char* un) = 0; /** * @brief Get password attribute value. * * @return null-terminated string with the password value or nullptr/NULL if none */ virtual const char* password() const = 0; /** * @brief Get notes attribute value. * * @return null-terminated string with the password value or nullptr/NULL if none */ virtual const char* notes() const = 0; /** * @brief Get URL attribute value. * * @return null-terminated string with the password value or nullptr/NULL if none */ virtual const char* url() const = 0; /** * @brief Get user name attribute value. * * @return null-terminated string with the password value or nullptr/NULL if none */ virtual const char* userName() const = 0; /** * @brief Get TOTP data attribute value. * * The PasswordNodeData object retains the ownership of the TotpData. * You can get a copy of TotpData by calling TotpData::copy * * @return TOTP data attribute */ virtual const TotpData* totpData() const = 0; virtual ~PasswordNodeData() = default; protected: PasswordNodeData() = default; }; static const int INVALID_DURATION = -1; static const double INVALID_COORDINATE; virtual ~MegaNode(); /** * @brief Creates a copy of this MegaNode object. * * The resulting object is fully independent of the source MegaNode, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaNode object */ virtual MegaNode *copy(); /** * @brief Returns the type of the node * * Valid values are: * - TYPE_UNKNOWN = -1, * Unknown node type * * - TYPE_FILE = 0, * The MegaNode object represents a file in MEGA * * - TYPE_FOLDER = 1 * The MegaNode object represents a folder in MEGA * * - TYPE_ROOT = 2 * The MegaNode object represents root of the MEGA Cloud Drive * * - TYPE_VAULT = 3 * The MegaNode object represents root of the MEGA Vault * * - TYPE_RUBBISH = 4 * The MegaNode object represents root of the MEGA Rubbish Bin * * @return Type of the node */ virtual int getType() const; /** * @brief Returns the name of the node * * The name is only valid for nodes of type TYPE_FILE or TYPE_FOLDER. * For other MegaNode types, the name is undefined. * * The MegaNode object retains the ownership of the returned string. It will * be valid until the MegaNode object is deleted. * * @return Name of the node */ virtual const char* getName(); /** * @brief Returns the fingerprint (Base64-encoded) of the node * * Only files have a fingerprint, and there could be files without it. * If the node doesn't have a fingerprint, this funtion returns NULL * * The MegaNode object retains the ownership of the returned string. It will * be valid until the MegaNode object is deleted. * * @return Base64-encoded fingerprint of the node, or NULL it the node doesn't have a fingerprint. */ virtual const char* getFingerprint(); /** * @brief Returns the original fingerprint (Base64-encoded) of the node * * In the case where a file was modified before uploaded (eg. resized photo or gps coords removed), * it may have an original fingerprint set (by MegaApi::setOriginalFingerprint or * MegaApi::backgroundMediaUploadComplete), which is the fingerprint of the file before it was modified. * This can be useful on mobile devices to avoid uploading a file multiple times when only * the original file is kept on the device. * * The MegaNode object retains the ownership of the returned string. It will * be valid until the MegaNode object is deleted. * * @return Base64-encoded original fingerprint of the node, or NULL it the node doesn't have an original fingerprint. */ virtual const char* getOriginalFingerprint(); /** * @brief Returns true if the node has custom attributes * * Custom attributes can be set using MegaApi::setCustomNodeAttribute * * @return True if the node has custom attributes, otherwise false * @see MegaApi::setCustomNodeAttribute */ virtual bool hasCustomAttrs(); /** * @brief Returns the list with the names of the custom attributes of the node * * Custom attributes can be set using MegaApi::setCustomNodeAttribute * * You take the ownership of the returned value * * @return Names of the custom attributes of the node * @see MegaApi::setCustomNodeAttribute */ virtual MegaStringList *getCustomAttrNames(); /** * @brief Get a custom attribute of the node * * Custom attributes can be set using MegaApi::setCustomNodeAttribute * * The MegaNode object retains the ownership of the returned string. It will * be valid until the MegaNode object is deleted. * * @param attrName Name of the custom attribute * @return Custom attribute of the node * @see MegaApi::setCustomNodeAttribute */ virtual const char *getCustomAttr(const char* attrName); /** * @brief Get the attribute of the node representing its duration. * * The purpose of this attribute is to store the duration of audio/video files. * * @return The number of seconds, or -1 if this attribute is not set. */ virtual int getDuration(); /** * @brief Get the attribute of the node representing its width. * * @return The number of pixels for width, or -1 if this attribute is not set. */ virtual int getWidth(); /** * @brief Get the attribute of the node representing its height. * * @return The number of pixels for height, or -1 if this attribute is not set. */ virtual int getHeight(); /** * @brief Get the attribute of the node representing its shortformat. * * @return The shortformat, or -1 if this attribute is not set. */ virtual int getShortformat(); /** * @brief Get the attribute of the node representing its videocodecid. * * @return The videocodecid, or -1 if this attribute is not set. */ virtual int getVideocodecid(); /** * @brief Return if the node is marked as favourite. * * @return True if node is marked as favourite, otherwise return false (attribute is not set). */ virtual bool isFavourite(); /** * @brief Ascertain if the node is marked as sensitive * * see MegaApi::isSensitiveInherit to see if the node is marked sensitive * or as descendent of a node that is marked sensitive * * @param node node to inspect */ virtual bool isMarkedSensitive(); /** * @brief Get the attribute of the node representing its label. * * @return The label of the node, valid values are: * - MegaNode::NODE_LBL_UNKNOWN = 0 * - MegaNode::NODE_LBL_RED = 1 * - MegaNode::NODE_LBL_ORANGE = 2 * - MegaNode::NODE_LBL_YELLOW = 3 * - MegaNode::NODE_LBL_GREEN = 4 * - MegaNode::NODE_LBL_BLUE = 5 * - MegaNode::NODE_LBL_PURPLE = 6 * - MegaNode::NODE_LBL_GREY = 7 */ virtual int getLabel(); /** * @brief Get the attribute of the node representing the latitude. * * The purpose of this attribute is to store the coordinate where a photo was taken. * * @return The latitude coordinate in its decimal degree notation, or INVALID_COORDINATE * if this attribute is not set. */ virtual double getLatitude(); /** * @brief Get the attribute of the node representing the longitude. * * The purpose of this attribute is to store the coordinate where a photo was taken. * * @return The longitude coordinate in its decimal degree notation, or INVALID_COORDINATE * if this attribute is not set. */ virtual double getLongitude(); /** * @brief Get the attribute of the node representing the description * * The purpose of this attribute is to store the node description. * * The MegaNode object retains the ownership of the returned string. It will be valid until * the MegaNode object is deleted. * * @return Node description */ virtual const char* getDescription(); /** * @brief Get a list of tags from a node * * These tags are stored as a node attribute * * You take the ownership of the returned value. * * @return List of tags from the node */ virtual MegaStringList* getTags(); /** * @brief Returns the handle of this MegaNode in a Base64-encoded string * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Base64-encoded handle of the node */ virtual char* getBase64Handle(); /** * @brief Returns the size of the node * * The returned value is only valid for nodes of type TYPE_FILE. * * @return Size of the node */ virtual int64_t getSize(); /** * @brief Returns the creation time of the node in MEGA (in seconds since the epoch) * * The returned value is only valid for nodes of type TYPE_FILE or TYPE_FOLDER. * * @return Creation time of the node (in seconds since the epoch) */ virtual int64_t getCreationTime(); /** * @brief Returns the modification time of the file that was uploaded to MEGA (in seconds since the epoch) * * The returned value is only valid for nodes of type TYPE_FILE. * * @return Modification time of the file that was uploaded to MEGA (in seconds since the epoch) */ virtual int64_t getModificationTime(); /** * @brief Returns a handle to identify this MegaNode * * You can use MegaApi::getNodeByHandle to recover the node later. * * @return Handle that identifies this MegaNode */ virtual MegaHandle getHandle() const; /** * @brief Returns the handle of the previous parent of this node. * * This attribute is set when nodes are moved to the Rubbish Bin to * ease their restoration. If the attribute is not set for the node, * this function returns MegaApi::INVALID_HANDLE * * @return Handle of the previous parent of this node or MegaApi::INVALID_HANDLE * if the attribute is not set. */ virtual MegaHandle getRestoreHandle(); /** * @brief Returns the handle of the parent node * * You can use MegaApi::getNodeByHandle to recover the node later. * * @return Handle of the parent node (or INVALID_HANDLE for root nodes) */ virtual MegaHandle getParentHandle(); /** * @brief Returns the key of the node in a Base64-encoded string * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Returns the key of the node. */ virtual char* getBase64Key(); /** * @brief Returns the expiration time of a public link, if any * * @return The expiration time as an Epoch timestamp. Returns 0 for non-expire * links, and -1 if the MegaNode is not exported. */ virtual int64_t getExpirationTime(); /** * @brief Returns the public handle of a node * * Only exported nodes have a public handle. * * @return The public handle of an exported node. If the MegaNode * has not been exported, it returns UNDEF. */ virtual MegaHandle getPublicHandle(); /** * @brief Returns a public node corresponding to the exported MegaNode * * You take ownership of the returned value. * * @return Public node for the exported node. If the MegaNode has not been * exported or it has expired, then it returns NULL. */ virtual MegaNode* getPublicNode(); /** * @brief Returns the URL for the public link of the exported node. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param includeKey False if you want the link without the key. * @return The URL for the public link of the exported node. If the MegaNode * has not been exported, it returns NULL. */ virtual char * getPublicLink(bool includeKey = true); /** * @brief Returns the creation time for the public link of the exported node (in seconds since the epoch). * * @return Creation time for the public link of the node. Returns 0 if the creation time is not available * and -1 if the MegaNode has not been exported. */ virtual int64_t getPublicLinkCreationTime(); /** * @brief Returns authentication key for a writable folder. * * The MegaNode object retains the ownership of the returned string. It will * be valid until the MegaNode object is deleted. * * @return authentication key for a writable folder. If there is no authentication key, * nullptr shall be returned. */ virtual const char * getWritableLinkAuthKey(); /** * @brief Returns true if this node represents a file (type == TYPE_FILE) * @return true if this node represents a file, otherwise false */ virtual bool isFile(); /** * @brief Returns true this node represents a folder or a root node * * @return true this node represents a folder or a root node */ virtual bool isFolder(); /** * @brief Returns true if this node has been removed from the MEGA account * * This value is only useful for nodes notified by MegaListener::onNodesUpdate or * MegaGlobalListener::onNodesUpdate that can notify about deleted nodes. * * In other cases, the return value of this function will be always false. * * @return true if this node has been removed from the MEGA account */ virtual bool isRemoved(); /** * @brief Returns true if this node has an specific change * * This value is only useful for nodes notified by MegaListener::onNodesUpdate or * MegaGlobalListener::onNodesUpdate that can notify about node modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MegaNode::CHANGE_TYPE_REMOVED = 0x01 * Check if the node is being removed * * - MegaNode::CHANGE_TYPE_ATTRIBUTES = 0x02 * Check if an attribute of the node has changed, usually the namespace name * * - MegaNode::CHANGE_TYPE_OWNER = 0x04 * Check if the owner of the node has changed * * - MegaNode::CHANGE_TYPE_TIMESTAMP = 0x08 * Check if the modification time of the node has changed * * - MegaNode::CHANGE_TYPE_FILE_ATTRIBUTES = 0x10 * Check if file attributes have changed, usually the thumbnail or the preview for images * * - MegaNode::CHANGE_TYPE_INSHARE = 0x20 * Check if the node is a new or modified inshare * * - MegaNode:: CHANGE_TYPE_OUTSHARE = 0x40 * Check if the node is a new or modified outshare * * - MegaNode::CHANGE_TYPE_PARENT = 0x80 * Check if the parent of the node has changed * * - MegaNode::CHANGE_TYPE_PENDINGSHARE = 0x100 * Check if the pending share of the node has changed * * - MegaNode::CHANGE_TYPE_PUBLIC_LINK = 0x200 * Check if the public link of the node has changed * * - MegaNode::CHANGE_TYPE_NEW = 0x400 * Check if the node is new * * - MegaNode::CHANGE_TYPE_PWD = 0x8000 * Check if any Password Node Data value for this node changed * * - MegaNode::CHANGE_TYPE_DESCRIPTION = 0x10000 * Check if description for this node has changed * * - MegaNode::CHANGE_TYPE_TAGS = 0x20000 * Check if tags for this node have changed * * @return true if this node has an specific change */ virtual bool hasChanged(uint64_t changeType); /** * @brief Returns a bit field with the changes of the node * * This value is only useful for nodes notified by MegaListener::onNodesUpdate or * MegaGlobalListener::onNodesUpdate that can notify about node modifications. * * @return The returned value is an OR combination of these flags: * *- MegaNode::CHANGE_TYPE_REMOVED = 0x01 * The node is being removed * * - MegaNode::CHANGE_TYPE_ATTRIBUTES = 0x02 * An attribute of the node has changed, usually the namespace name * * - MegaNode::CHANGE_TYPE_OWNER = 0x04 * The owner of the node has changed * * - MegaNode::CHANGE_TYPE_TIMESTAMP = 0x08 * The modification time of the node has changed * * - MegaNode::CHANGE_TYPE_FILE_ATTRIBUTES = 0x10 * File attributes have changed, usually the thumbnail or the preview for images * * - MegaNode::CHANGE_TYPE_INSHARE = 0x20 * The node is a new or modified inshare * * - MegaNode::CHANGE_TYPE_OUTSHARE = 0x40 * The node is a new or modified outshare * * - MegaNode::CHANGE_TYPE_PARENT = 0x80 * The parent of the node has changed * * - MegaNode::CHANGE_TYPE_PENDINGSHARE = 0x100 * Check if the pending share of the node has changed * * - MegaNode::CHANGE_TYPE_PUBLIC_LINK = 0x200 * Check if the public link of the node has changed * * - MegaNode::CHANGE_TYPE_NEW = 0x400 * Check if the node is new * * - MegaNode::CHANGE_TYPE_NAME = 0x800 * Check if the node name has changed * * - MegaNode::CHANGE_TYPE_FAVOURITE = 0x1000 * Check if the node was added to or removed from favorites * * - MegaNode::CHANGE_TYPE_COUNTER = 0x2000 * Check if counter for this node (its subtree) has changed * * - MegaNode::CHANGE_TYPE_PWD = 0x8000 * Check if any Password Node Data value for this node changed * * - MegaNode::CHANGE_TYPE_DESCRIPTION = 0x10000 * Check if description for this node has changed * * - MegaNode::CHANGE_TYPE_TAGS = 0x20000 * Check if tags for this node have changed * */ virtual uint64_t getChanges(); /** * @brief Returns true if the node has an associated thumbnail * @return true if the node has an associated thumbnail */ virtual bool hasThumbnail(); /** * @brief Returns true if the node has an associated preview * @return true if the node has an associated preview */ virtual bool hasPreview(); /** * @brief Returns true if this is a public node * * Only MegaNode objects generated with MegaApi::getPublicMegaNode * will return true. * * @return true if this is a public node */ virtual bool isPublic(); /** * @brief Check if the MegaNode is being shared by/with your own user * * For nodes that are being shared, you can get a list of MegaShare * objects using MegaApi::getOutShares, or a list of MegaNode objects * using MegaApi::getInShares * * @return true is the MegaNode is being shared, otherwise false * @note Exported nodes (public link) are not considered to be shared nodes. */ virtual bool isShared(); /** * @brief Check if the MegaNode is being shared with other users * * For nodes that are being shared, you can get a list of MegaShare * objects using MegaApi::getOutShares * * @return true is the MegaNode is being shared, otherwise false */ virtual bool isOutShare(); /** * @brief Check if a MegaNode belong to another User, but it is shared with you * * For nodes that are being shared, you can get a list of MegaNode * objects using MegaApi::getInShares * * @return true is the MegaNode is being shared, otherwise false */ virtual bool isInShare(); /** * @brief Returns true if the node has been exported (has a public link) * * Public links are created by calling MegaApi::exportNode. * * @return true if this is an exported node */ virtual bool isExported(); /** * @brief Returns true if the node has been exported (has a temporal public link) * and the related public link has expired. * * Public links are created by calling MegaApi::exportNode. * * @return true if the public link has expired. */ virtual bool isExpired(); /** * @brief Returns true if this the node has been exported * and the related public link has been taken down. * * Public links are created by calling MegaApi::exportNode. * * @return true if the public link has been taken down. */ virtual bool isTakenDown(); /** * @brief Returns true if this MegaNode is a private node from a foreign account * * Only MegaNodes created with MegaApi::createForeignFileNode and MegaApi::createForeignFolderNode * returns true in this function. * * @return true if this node is a private node from a foreign account */ virtual bool isForeign(); /** * @brief Returns true if this MegaNode is a Credit Card Node * * @note: A Credit Card Node is a Password Manager Node with credit card information. * * Only MegaNodes created with MegaApi::createCreditCardNode return true in this function. * * @return true if this node is a Credit Card Node */ virtual bool isCreditCardNode() const; /** * @brief Returns true if this MegaNode is a Password Node * * @note: A Password Node is a Password Manager Node with login credential information. * * Only MegaNodes created with MegaApi::createPasswordNode return true in this function. * * @return true if this node is a Password Node */ virtual bool isPasswordNode() const; /** * @brief Returns true if this MegaNode is a Password Manager Node * * A node is considered a Password Manager Node if Password Manager Base is its * ancestor and it's not a Password Manager Node Folder. * * @return true if this node is a Password Manager Node, false otherwise. * In case node doesn't exists this method will also returns false. */ virtual bool isPasswordManagerNode() const; /** * @brief Gets the Credit Card Node value if the node is a Credit Card Node * * Non-set MegaNode::CreditCardNodeData members will return nullptr/NULL * * @note The caller takes the ownership of the returned pointer. * * @return Credit Card Node data. Caller receives ownership. */ virtual CreditCardNodeData* getCreditCardData() const; /** * @brief Gets the Password Node value if the node is a Password Node * * Non-set MegaNode::PasswordNodeData members will return nullptr/NULL * * You take ownership of the returned value. * * @return Password Node data. Caller receives ownership. */ virtual PasswordNodeData* getPasswordData() const; /** * @brief Returns a string that contains the decryption key of the file (in binary format) * * The MegaNode object retains the ownership of the returned pointer. It will be valid until the deletion * of the MegaNode object. * * @warning This method is not suitable for programming languages that require auto-generated bindings, * due to the lack of mapping of string pointers to objects in different languages. * * @return Decryption key of the file (in binary format) */ virtual std::string* getNodeKey(); /** * @brief Returns true if the node key is decrypted * * For nodes in shared folders, there could be missing keys. Also, faulty * clients might create invalid keys. In those cases, the node's key might * not be decrypted successfully. * * @return True if the node key is decrypted */ virtual bool isNodeKeyDecrypted(); /** * @brief Returns the file attributes related to the node * * The return value is only valid for nodes attached in a chatroom. In all other cases this function * will return NULL. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return File attributes related to the node */ virtual char *getFileAttrString(); /** * @brief Set an auth token to access this node * * This function is used to authenticate private nodes from a different account. * * @param privateAuth token to access the node */ virtual void setPrivateAuth(const char *privateAuth); /** * @brief Returns the child nodes of an authorized folder node * * This function always returns NULL, except for authorized folder nodes. * Authorized folder nodes are the ones returned by MegaApi::authorizeNode. * * The MegaNode object retains the ownership of the returned pointer. It will be valid until the deletion * of the MegaNode object. * * @return Child nodes of an authorized folder node, otherwise NULL */ virtual MegaNodeList *getChildren(); virtual MegaHandle getOwner() const; /** * @brief Returns the device id stored as a Node attribute. * It will be an empty string for other nodes than device folders related to backups. * * The MegaNode object retains the ownership of the returned string, it will be valid until * the MegaNode object is deleted. * * @return The device id associated with the Node of a Backup folder. */ virtual const char* getDeviceId() const; /** * @brief Returns the S4 metadata stored as a Node attribute. * * The MegaNode object retains the ownership of the returned string, it will be valid until * the MegaNode object is deleted. * * @return The s4 attribute associated with the Node. */ virtual const char* getS4() const; /** * @brief Provides a serialization of the MegaNode object * * @note This function is intended to use ONLY with MegaNode objects obtained from * attachment messages received in a chatroom (@see MegaChatMessage::getMegaNodeList()). * Using MegaNode objects returned by MegaNode::unserialize from a serialized * non-chat MegaNode object may cause undefined behavior. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Serialization of the MegaNode object, in Base64, or NULL if error. */ virtual char *serialize(); /** * @brief Returns a new MegaNode object from its serialization * * @note This function is intended to use ONLY with MegaNode objects obtained from * attachment messages received in a chatroom (@see MegaChatMessage::getMegaNodeList()). * Using MegaNode objects obtained by MegaNode::unserialize from a serialized * non-chat MegaNode object may cause undefined behavior. * * You take the ownership of the returned value. * * @param d Serialization of a MegaNode object obtained from a chat message (in Base64) * @return A new MegaNode object, or NULL if error. */ static MegaNode* unserialize(const char *d); }; class MegaBackupInfo; /** * @brief List of MegaBackupInfo objects * * A MegaBackupInfoList has the ownership of the MegaBackupInfo objects that it contains, so they * will be valid only until the MegaBackupInfoList is deleted. If you want to retain a MegaBackupInfo * returned by a MegaBackupInfoList, use MegaBackupInfo::copy(). * * Objects of this class are immutable. */ class MegaBackupInfoList { public: /** * @brief Returns the MegaBackupInfo at the position i in the MegaBackupInfoList * * The MegaBackupInfoList retains the ownership of any returned MegaBackupInfo. It will be valid * only until the MegaBackupInfoList is deleted. If you want to retain a MegaBackupInfo returned * by this function, use MegaBackupInfo::copy(). * * If the index is >= the size of the list, this function returns null. * * @param i Position of the instance that we want to get from the list * @return Instance at position i in the list */ virtual const MegaBackupInfo* get(unsigned int /*i*/) const { return nullptr; } /** * @brief Returns the number of MegaBackupInfo instances in the list * @return Number of MegaBackupInfo instances in the list */ virtual unsigned int size() const { return 0; } virtual MegaBackupInfoList* copy() const { return nullptr; } virtual ~MegaBackupInfoList() = default; }; /** * @brief Represents a Set in MEGA * * It allows to get all data related to a Set in MEGA. * * Objects of this class aren't live, they are snapshots of the state of a Set * in MEGA when the object is created, they are immutable. * */ class MegaSet { public: /** * @brief Returns id of current Set. * * @return Set id. */ virtual MegaHandle id() const { return INVALID_HANDLE; } /** * @brief Returns public id of current Set if it was exported. INVALID_HANDLE otherwise * * @return Public id of Set. */ virtual MegaHandle publicId() const { return INVALID_HANDLE; } /** * @brief Returns id of user that owns current Set. * * @return user id. */ virtual MegaHandle user() const { return INVALID_HANDLE; } /** * @brief Returns timestamp of latest changes to current Set (but not to its Elements). * * @return timestamp value. */ virtual int64_t ts() const { return 0; } /** * @brief Returns creation timestamp of current Set. * * @return timestamp value. */ virtual int64_t cts() const { return 0; } /** * @brief Returns type of current Set according to defined enum. * * @return type value. */ virtual int type() const { return SET_TYPE_INVALID; } /** * @brief Returns name of current Set. * * The MegaSet object retains the ownership of the returned string, it will be valid until * the MegaSet object is deleted. * * @return name of current Set. */ virtual const char* name() const { return nullptr; } /** * @brief Returns id of Element set as 'cover' for current Set. * * It will return INVALID_HANDLE if no cover was set or if the Element became invalid * (was removed) in the meantime. * * @return Element id. */ virtual MegaHandle cover() const { return INVALID_HANDLE; } /** * @brief Returns true if this Set has a specific change * * This value is only useful for Sets notified by MegaListener::onSetsUpdate or * MegaGlobalListener::onSetsUpdate that can notify about Set modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MegaSet::CHANGE_TYPE_NEW = 0x01 * Check if the Set was new * * - MegaSet::CHANGE_TYPE_NAME = 0x02 * Check if Set name has changed * * - MegaSet::CHANGE_TYPE_COVER = 0x04 * Check if Set cover has changed * * - MegaSet::CHANGE_TYPE_REMOVED = 0x08 * Check if the Set was removed * * - MegaSet::CHANGE_TYPE_EXPORT = 0x10 * Check if the Set was exported or disabled (i.e. exporting ended) * * @return true if this Set has a specific change */ virtual bool hasChanged(uint64_t /*changeType*/) const { return false; } /** * @brief Returns the addition / OR bit-operation of all the MegaSet::CHANGE_TYPE for * current MegaSet * * Note that the position of each bit matches the type of each according to the values * for MegaSet::CHANGE_TYPE_* * * @return value to check bitwise position according to MegaSet::CHANGE_TYPE_* options */ virtual uint64_t getChanges() const { return 0; } /** * @brief Returns true if this Set is exported (can be accessed via public link) * * Public link is retrieved when the Set becomes exported * * @return true if this Set is exported */ virtual bool isExported() const { return false; } /** * @brief Returns deletion reason for the link associated with the set * * Valid values are: * - DELETION_LINK_NO_REMOVED = 0 * - DELETION_LINK_BY_USER = 1 * - DELETION_LINK_DISPUTE = 2 * - DELETION_LINK_ETD = 3 * - DELETION_LINK_ATD = 4 * * @return reason for link has been removed */ virtual int getLinkDeletionReason() const { return false; } /** * @brief Returns true if this set has been exported * and the related public link has been taken down. * * Public links are created by calling MegaApi::exportSet. * * @return true if the public link has been taken down. */ virtual bool isTakenDown() const { return false; } virtual MegaSet* copy() const { return nullptr; } virtual ~MegaSet() = default; enum // 1:1 with Set::CH_XXX values { CHANGE_TYPE_NEW = 0x01, CHANGE_TYPE_NAME = 0x02, CHANGE_TYPE_COVER = 0x04, CHANGE_TYPE_REMOVED = 0x08, CHANGE_TYPE_EXPORT = 0x10, }; enum : int // 1:1 with existing Set::TYPE_YYY values (<255) { SET_TYPE_ALBUM = 0, SET_TYPE_PLAYLIST = 1, SET_TYPE_IGNORE = SET_TYPE_ALBUM, SET_TYPE_INVALID = -1, }; enum : int // 1:1 with existing Set::LinkDeletionReason:: { DELETION_LINK_NO_REMOVED = 0, DELETION_LINK_BY_USER = 1, DELETION_LINK_DISPUTE = 2, DELETION_LINK_ETD = 3, DELETION_LINK_ATD = 4, }; }; /** * @brief List of MegaSet objects * * A MegaSetList has the ownership of the MegaSet objects that it contains, so they will be * only valid until the MegaSetList is deleted. If you want to retain a MegaSet returned by * a MegaSetList, use MegaSet::copy(). * * Objects of this class are immutable. */ class MegaSetList { public: /** * @brief Returns the MegaSet at the position i in the MegaSetList * * The MegaSetList retains the ownership of the returned MegaSet. It will be only valid until * the MegaSetList is deleted. If you want to retain a MegaSet returned by this function, * use MegaSet::copy(). * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaSet that we want to get for the list * @return MegaSet at the position i in the list */ virtual const MegaSet* get(unsigned int /*i*/) const { return nullptr; } /** * @brief Returns the number of MegaSets in the list * @return Number of MegaSets in the list */ virtual unsigned int size() const { return 0; } virtual MegaSetList* copy() const { return nullptr; } virtual ~MegaSetList() = default; }; /** * @brief Represents an Element of a Set in MEGA * * It allows to get all data related to an Element of a Set in MEGA. * * Objects of this class aren't live, they are snapshots of the state of an Element of a Set * in MEGA when the object is created, they are immutable. * */ class MegaSetElement { public: /** * @brief Returns id of current Element. * * @return Element id. */ virtual MegaHandle id() const { return INVALID_HANDLE; } /** * @brief Returns handle of file-node represented by current Element. * * @return file-node handle. */ virtual MegaHandle node() const { return INVALID_HANDLE; } /** * @brief Returns id of MegaSet current MegaSetElement belongs to. * * @return MegaSet id. */ virtual MegaHandle setId() const { return INVALID_HANDLE; } /** * @brief Returns order of current Element. * * If not set explicitly, the API will typically set it to multiples of 1000. * * @return order of current Element. */ virtual int64_t order() const { return 0; } /** * @brief Returns timestamp of latest changes to current Element. * * @return timestamp value. */ virtual int64_t ts() const { return 0; } /** * @brief Returns name of current Element. * * The MegaSetElement object retains the ownership of the returned string, it will be valid until * the MegaSetElement object is deleted. * * @return name of current Element. */ virtual const char* name() const { return nullptr; } /** * @brief Returns true if this SetElement has a specific change * * This value is only useful for Sets notified by MegaListener::onSetElementsUpdate or * MegaGlobalListener::onSetElementsUpdate that can notify about SetElements modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MegaSetElement::CHANGE_TYPE_ELEM_NEW = 0x01 * Check if the SetElement was new * * - MegaSetElement::CHANGE_TYPE_ELEM_NAME = 0x02 * Check if SetElement name has changed * * - MegaSetElement::CHANGE_TYPE_ELEM_ORDER = 0x04 * Check if SetElement order has changed * * - MegaSetElement::CHANGE_TYPE_ELEM_REMOVED = 0x08 * Check if the SetElement was removed * * @return true if this Set has a specific change */ virtual bool hasChanged(uint64_t /*changeType*/) const { return false; } /** * @brief Returns the addition / OR bit-operation of all the MegaSetElement::CHANGE_TYPE for * current MegaSetElement * * Note that the position of each bit matches the type of each according to the values * for MegaSetElement::CHANGE_TYPE_ELEM_* * * @return value to check bitwise position according to MegaSetElement::CHANGE_TYPE_ELEM* options */ virtual uint64_t getChanges() const { return 0; } virtual MegaSetElement* copy() const { return nullptr; } virtual ~MegaSetElement() = default; enum // 1:1 with SetElement::CH_EL_XXX values { CHANGE_TYPE_ELEM_NEW = 0x01, CHANGE_TYPE_ELEM_NAME = 0x02, CHANGE_TYPE_ELEM_ORDER = 0x04, CHANGE_TYPE_ELEM_REMOVED = 0x08, }; }; /** * @brief List of MegaSetElement objects * * A MegaSetElementList has the ownership of the MegaSetElement objects that it contains, so they will be * only valid until the MegaSetElementList is deleted. If you want to retain a MegaSetElement returned by * a MegaSetElementList, use MegaSetElement::copy(). * * Objects of this class are immutable. */ class MegaSetElementList { public: /** * @brief Returns the MegaSetElement at the position i in the MegaSetElementList * * The MegaSetElementList retains the ownership of the returned MegaSetElement. It will be only * valid until the MegaSetElementList is deleted. If you want to retain a MegaSetElement * returned by this function, use MegaSetElement::copy(). * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaSetElement that we want to get for the list * @return MegaSetElement at the position i in the list */ virtual const MegaSetElement* get(unsigned int /*i*/) const { return nullptr; } /** * @brief Returns the number of MegaSetElements in the list * @return Number of MegaSetElements in the list */ virtual unsigned int size() const { return 0; } virtual MegaSetElementList* copy() const { return nullptr; } virtual ~MegaSetElementList() = default; }; /** * @brief Represents an user in MEGA * * It allows to get all data related to an user in MEGA. It can be also used * to start SDK requests (MegaApi::share MegaApi::removeContact, etc.) * * Objects of this class aren't live, they are snapshots of the state of an user * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can get the contacts of an account using * MegaApi::getContacts and MegaApi::getContact. * */ class MegaUser { public: enum { VISIBILITY_UNKNOWN = -1, // Unkown visibility VISIBILITY_HIDDEN = 0, // Passive Contact (ex-contacts, or user who owned files in an // incoming share from you) VISIBILITY_VISIBLE = 1, // Active contact which is visible for you VISIBILITY_INACTIVE = 2, // User account is Ex-users from MEGA (deleted account) VISIBILITY_BLOCKED = 3 // User account is blocked }; virtual ~MegaUser(); /** * @brief Creates a copy of this MegaUser object. * * The resulting object is fully independent of the source MegaUser, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaUser object */ virtual MegaUser *copy(); /** * @brief Returns the email associated with the contact. * * The email can be used to recover the MegaUser object later using MegaApi::getContact * * The MegaUser object retains the ownership of the returned string, it will be valid until * the MegaUser object is deleted. * * @return The email associated with the contact. */ virtual const char* getEmail(); /** * @brief Returns the handle associated with the contact. * * @return The handle associated with the contact. */ virtual MegaHandle getHandle(); /** * @brief Get the current visibility of the contact * * The returned value will be one of these: * * - VISIBILITY_UNKNOWN = -1 * The visibility of the contact isn't know * * - VISIBILITY_HIDDEN = 0 * The contact is currently hidden * * - VISIBILITY_VISIBLE = 1 * The contact is currently visible * * - VISIBILITY_INACTIVE = 2 * The contact is currently inactive * * - VISIBILITY_BLOCKED = 3 * The contact is currently blocked * * @note The visibility of your own user is undefined and shouldn't be used. * @return Current visibility of the contact */ virtual int getVisibility(); /** * @brief Returns the timestamp when the contact was added to the contact list (in seconds since the epoch) * @return Timestamp when the contact was added to the contact list (in seconds since the epoch) */ virtual int64_t getTimestamp(); enum : uint64_t { CHANGE_TYPE_AUTHRING = 0x01, CHANGE_TYPE_LSTINT = 0x02, CHANGE_TYPE_AVATAR = 0x04, CHANGE_TYPE_FIRSTNAME = 0x08, CHANGE_TYPE_LASTNAME = 0x10, CHANGE_TYPE_EMAIL = 0x20, CHANGE_TYPE_KEYRING = 0x40, CHANGE_TYPE_COUNTRY = 0x80, CHANGE_TYPE_BIRTHDAY = 0x100, CHANGE_TYPE_PUBKEY_CU255 = 0x200, CHANGE_TYPE_PUBKEY_ED255 = 0x400, CHANGE_TYPE_SIG_PUBKEY_RSA = 0x800, CHANGE_TYPE_SIG_PUBKEY_CU255 = 0x1000, CHANGE_TYPE_LANGUAGE = 0x2000, CHANGE_TYPE_PWD_REMINDER = 0x4000, CHANGE_TYPE_DISABLE_VERSIONS = 0x8000, CHANGE_TYPE_CONTACT_LINK_VERIFICATION = 0x10000, CHANGE_TYPE_RICH_PREVIEWS = 0x20000, CHANGE_TYPE_RUBBISH_TIME = 0x40000, CHANGE_TYPE_STORAGE_STATE = 0x80000, CHANGE_TYPE_GEOLOCATION = 0x100000, CHANGE_TYPE_CAMERA_UPLOADS_FOLDER = 0x200000, CHANGE_TYPE_MY_CHAT_FILES_FOLDER = 0x400000, CHANGE_TYPE_PUSH_SETTINGS = 0x800000, CHANGE_TYPE_ALIAS = 0x1000000, CHANGE_TYPE_UNSHAREABLE_KEY = 0x2000000, CHANGE_TYPE_DEVICE_NAMES = 0x4000000, CHANGE_TYPE_MY_BACKUPS_FOLDER = 0x8000000, CHANGE_TYPE_COOKIE_SETTINGS = 0x10000000, CHANGE_TYPE_NO_CALLKIT = 0x20000000, CHANGE_APPS_PREFS = 0x40000000, CHANGE_CC_PREFS = 0x80000000, CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP = 0x100000000ULL, }; /** * @brief Returns true if this user has an specific change * * This value is only useful for users notified by MegaListener::onUsersUpdate or * MegaGlobalListener::onUsersUpdate that can notify about user modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MegaUser::CHANGE_TYPE_AUTH = 0x01 * Check if the user has new or modified authentication information * * - MegaUser::CHANGE_TYPE_LSTINT = 0x02 * Check if the last interaction timestamp is modified * * - MegaUser::CHANGE_TYPE_AVATAR = 0x04 * Check if the user has a new or modified avatar image, or if the avatar was removed * * - MegaUser::CHANGE_TYPE_FIRSTNAME = 0x08 * Check if the user has new or modified firstname * * - MegaUser::CHANGE_TYPE_LASTNAME = 0x10 * Check if the user has new or modified lastname * * - MegaUser::CHANGE_TYPE_EMAIL = 0x20 * Check if the user has modified email * * - MegaUser::CHANGE_TYPE_KEYRING = 0x40 * Check if the user has new or modified keyring * * - MegaUser::CHANGE_TYPE_COUNTRY = 0x80 * Check if the user has new or modified country * * - MegaUser::CHANGE_TYPE_BIRTHDAY = 0x100 * Check if the user has new or modified birthday, birthmonth or birthyear * * - MegaUser::CHANGE_TYPE_PUBKEY_CU255 = 0x200 * Check if the user has new or modified public key for chat * * - MegaUser::CHANGE_TYPE_PUBKEY_ED255 = 0x400 * Check if the user has new or modified public key for signing * * - MegaUser::CHANGE_TYPE_SIG_PUBKEY_RSA = 0x800 * Check if the user has new or modified signature for RSA public key * * - MegaUser::CHANGE_TYPE_SIG_PUBKEY_CU255 = 0x1000 * Check if the user has new or modified signature for Cu25519 public key * * - MegaUser::CHANGE_TYPE_LANGUAGE = 0x2000 * Check if the user has modified the preferred language * * - MegaUser::CHANGE_TYPE_PWD_REMINDER = 0x4000 * Check if the data related to the password reminder dialog has changed * * - MegaUser::CHANGE_TYPE_DISABLE_VERSIONS = 0x8000 * Check if option for file versioning has changed * * - MegaUser::CHANGE_TYPE_CONTACT_LINK_VERIFICATION = 0x10000 * Check if option for automatic contact-link verification has changed * * - MegaUser::CHANGE_TYPE_RICH_PREVIEWS = 0x20000 * Check if option for rich links has changed * * - MegaUser::CHANGE_TYPE_RUBBISH_TIME = 0x40000 * Check if rubbish time for autopurge has changed * * - MegaUser::CHANGE_TYPE_STORAGE_STATE = 0x80000 * Check if the state of the storage has changed * * - MegaUser::CHANGE_TYPE_GEOLOCATION = 0x100000 * Check if option for geolocation messages has changed * * - MegaUser::CHANGE_TYPE_CAMERA_UPLOADS_FOLDER = 0x200000 * Check if "Camera uploads" folder has changed * * - MegaUser::CHANGE_TYPE_MY_CHAT_FILES_FOLDER = 0x400000 * Check if "My chat files" folder changed * * - MegaUser::CHANGE_TYPE_PUSH_SETTINGS = 0x800000 * Check if settings for push notifications have changed * * - MegaUser::CHANGE_TYPE_ALIAS = 0x1000000 * Check if aliases have changed * * - MegaUser::CHANGE_TYPE_UNSHAREABLE_KEY = 0x2000000 * (internal) The unshareable key has been created * * - MegaUser::CHANGE_TYPE_DEVICE_NAMES = 0x4000000 * Check if device names have changed * * - MegaUser::CHANGE_TYPE_MY_BACKUPS_FOLDER = 0x8000000 * Check if "My Backups" folder has changed * * - MegaUser::CHANGE_TYPE_COOKIE_SETTINGS = 0x10000000 * Check if option for cookie settings has changed * * - MegaUser::CHANGE_TYPE_NO_CALLKIT = 0x20000000 * Check if option for iOS CallKit has changed * * - MegaUser::CHANGE_APPS_PREFS = 0x40000000 * Check if apps prefs have changed * * - MegaUser::CHANGE_CC_PREFS = 0x80000000 * Check if content consumption prefs have changed * * - MegaUser::CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP = 0x100000000 * Check if the timestamp for clearing recent actions history has changed * * @return true if this user has an specific change */ virtual bool hasChanged(uint64_t changeType); /** * @brief Returns a bit field with the changes of the user * * This value is only useful for users notified by MegaListener::onUsersUpdate or * MegaGlobalListener::onUsersUpdate that can notify about user modifications. * * @return The returned value is an OR combination of these flags: * * - MegaUser::CHANGE_TYPE_AUTH = 0x01 * Check if the user has new or modified authentication information * * - MegaUser::CHANGE_TYPE_LSTINT = 0x02 * Check if the last interaction timestamp is modified * * - MegaUser::CHANGE_TYPE_AVATAR = 0x04 * Check if the user has a new or modified avatar image * * - MegaUser::CHANGE_TYPE_FIRSTNAME = 0x08 * Check if the user has new or modified firstname * * - MegaUser::CHANGE_TYPE_LASTNAME = 0x10 * Check if the user has new or modified lastname * * - MegaUser::CHANGE_TYPE_EMAIL = 0x20 * Check if the user has modified email * * - MegaUser::CHANGE_TYPE_KEYRING = 0x40 * Check if the user has new or modified keyring * * - MegaUser::CHANGE_TYPE_COUNTRY = 0x80 * Check if the user has new or modified country * * - MegaUser::CHANGE_TYPE_BIRTHDAY = 0x100 * Check if the user has new or modified birthday, birthmonth or birthyear * * - MegaUser::CHANGE_TYPE_PUBKEY_CU255 = 0x200 * Check if the user has new or modified public key for chat * * - MegaUser::CHANGE_TYPE_PUBKEY_ED255 = 0x400 * Check if the user has new or modified public key for signing * * - MegaUser::CHANGE_TYPE_SIG_PUBKEY_RSA = 0x800 * Check if the user has new or modified signature for RSA public key * * - MegaUser::CHANGE_TYPE_SIG_PUBKEY_CU255 = 0x1000 * Check if the user has new or modified signature for Cu25519 public key * * - MegaUser::CHANGE_TYPE_LANGUAGE = 0x2000 * Check if the user has modified the preferred language * * - MegaUser::CHANGE_TYPE_PWD_REMINDER = 0x4000 * Check if the data related to the password reminder dialog has changed * * - MegaUser::CHANGE_TYPE_DISABLE_VERSIONS = 0x8000 * Check if option for file versioning has changed * * - MegaUser::CHANGE_TYPE_CONTACT_LINK_VERIFICATION = 0x10000 * Check if option for automatic contact-link verification has changed * * - MegaUser::CHANGE_TYPE_RICH_PREVIEWS = 0x20000 * Check if option for rich links has changed * * - MegaUser::CHANGE_TYPE_RUBBISH_TIME = 0x40000 * Check if rubbish time for autopurge has changed * * - MegaUser::CHANGE_TYPE_STORAGE_STATE = 0x80000 * Check if the state of the storage has changed * * - MegaUser::CHANGE_TYPE_GEOLOCATION = 0x100000 * Check if option for geolocation messages has changed * * - MegaUser::CHANGE_TYPE_PUSH_SETTINGS = 0x800000 * Check if settings for push notifications have changed * * - MegaUser::CHANGE_TYPE_ALIAS = 0x1000000 * Check if aliases have changed * * - MegaUser::CHANGE_TYPE_UNSHAREABLE_KEY = 0x2000000 * (internal) The unshareable key has been created * * - MegaUser::CHANGE_TYPE_DEVICE_NAMES = 0x4000000 * Check if device names have changed * * - MegaUser::CHANGE_TYPE_BACKUP_NAMES = 0x8000000 * * - MegaUser::CHANGE_TYPE_COOKIE_SETTINGS = 0x10000000 * Check if option for cookie settings has changed * * - MegaUser::CHANGE_TYPE_NO_CALLKIT = 0x20000000 * Check if option for iOS CallKit has changed * * - MegaUser::CHANGE_APPS_PREFS = 0x40000000 * Check if apps prefs have changed * * - MegaUser::CHANGE_CC_PREFS = 0x80000000 * Check if content consumption prefs have changed * * Check if backup names have changed */ virtual uint64_t getChanges(); /** * @brief Indicates if the user is changed by yourself or by another client. * * This value is only useful for users notified by MegaListener::onUsersUpdate or * MegaGlobalListener::onUsersUpdate that can notify about user modifications. * * @return 0 if the change is external. >0 if the change is the result of an * explicit request, -1 if the change is the result of an implicit request * made by the SDK internally. */ virtual int isOwnChange(); }; /** * @brief Represents a user alert in MEGA. * Alerts are the notifictions appearing under the bell in the webclient * * Objects of this class aren't live, they are snapshots of the state * in MEGA when the object is created, they are immutable. * * MegaUserAlerts can be retrieved with MegaApi::getUserAlerts * */ class MegaUserAlert { public: enum { TYPE_INCOMINGPENDINGCONTACT_REQUEST, TYPE_INCOMINGPENDINGCONTACT_CANCELLED, TYPE_INCOMINGPENDINGCONTACT_REMINDER, TYPE_CONTACTCHANGE_DELETEDYOU, TYPE_CONTACTCHANGE_CONTACTESTABLISHED, TYPE_CONTACTCHANGE_ACCOUNTDELETED, TYPE_CONTACTCHANGE_BLOCKEDYOU, TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED, TYPE_UPDATEDPENDINGCONTACTINCOMING_ACCEPTED, TYPE_UPDATEDPENDINGCONTACTINCOMING_DENIED, TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED, TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED, TYPE_NEWSHARE, TYPE_DELETEDSHARE, TYPE_NEWSHAREDNODES, TYPE_REMOVEDSHAREDNODES, TYPE_UPDATEDSHAREDNODES, TYPE_PAYMENT_SUCCEEDED, TYPE_PAYMENT_FAILED, TYPE_PAYMENTREMINDER, TYPE_TAKEDOWN, TYPE_TAKEDOWN_REINSTATED, TYPE_SCHEDULEDMEETING_NEW, TYPE_SCHEDULEDMEETING_DELETED, TYPE_SCHEDULEDMEETING_UPDATED, TYPE_SET_TAKEDOWN, TYPE_SET_TAKEDOWN_REINSTATED, TOTAL_OF_ALERT_TYPES }; #ifdef ENABLE_CHAT enum { SM_CHANGE_TYPE_TITLE = 0x01, SM_CHANGE_TYPE_DESCRIPTION = 0x02, SM_CHANGE_TYPE_CANCELLED = 0x04, SM_CHANGE_TYPE_TIMEZONE = 0x08, SM_CHANGE_TYPE_STARTDATE = 0x10, SM_CHANGE_TYPE_ENDDATE = 0x20, SM_CHANGE_TYPE_RULES = 0x40, }; #endif virtual ~MegaUserAlert(); /** * @brief Creates a copy of this MegaUserAlert object. * * The resulting object is fully independent of the source MegaUserAlert, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaUserAlert object */ virtual MegaUserAlert *copy() const; /** * @brief Returns the id of the alert * * The ids are assigned to alerts sequentially from program start, * however there may be gaps. The id can be used to create an * association with a UI element in order to process updates in callbacks. * * @return Type of alert associated with the object */ virtual unsigned getId() const; /** * @brief Returns whether the alert has been acknowledged by this client or another * * @return Flag indicating whether the alert has been seen */ virtual bool getSeen() const; /** * @brief Returns whether the alert is still relevant to the logged in user. * * An alert may be relevant initially but become non-relevant, eg. payment reminder. * Alerts which are no longer relevant are usually removed from the visible list. * * @return Flag indicting whether the alert is still relevant */ virtual bool getRelevant() const; /** * @brief Returns the type of alert associated with the object * @return Type of alert associated with the object */ virtual int getType() const; /** * @brief Returns a readable string that shows the type of alert * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable string showing the type of alert */ virtual const char *getTypeString() const; /** * @brief Returns the handle of a user related to the alert * * This value is valid for user related alerts: * TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED, TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED, * TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED, * TYPE_CONTACTCHANGE_CONTACTESTABLISHED, TYPE_CONTACTCHANGE_ACCOUNTDELETED, * TYPE_CONTACTCHANGE_BLOCKEDYOU, TYPE_CONTACTCHANGE_DELETEDYOU, * TYPE_NEWSHARE, TYPE_DELETEDSHARE, TYPE_NEWSHAREDNODES, TYPE_REMOVEDSHAREDNODES, * TYPE_SCHEDULEDMEETING_NEW, TYPE_SCHEDULEDMEETING_UPDATED, TYPE_SCHEDULEDMEETING_DELETED * * @warning This value is still valid for user related alerts: * TYPE_INCOMINGPENDINGCONTACT_CANCELLED, TYPE_INCOMINGPENDINGCONTACT_REMINDER, * TYPE_INCOMINGPENDINGCONTACT_REQUEST * However, the returned value is the handle of the Pending Contact Request. There is no * user's handle associated to these type of alerts. Use MegaUserAlert::getPcrHandle. * * @return the associated user's handle, otherwise UNDEF */ virtual MegaHandle getUserHandle() const; /** * @brief Returns the handle of a node related to the alert * * This value is valid for alerts that relate to a single node. * TYPE_NEWSHARE (folder handle), TYPE_DELETEDSHARE (folder handle), TYPE_NEWSHAREDNODES * (parent handle), TYPE_TAKEDOWN (node handle), TYPE_TAKEDOWN_REINSTATED (node handle) * * This value is also valid for the following alerts: * TYPE_SCHEDULEDMEETING_NEW (chatid), TYPE_SCHEDULEDMEETING_DELETED (chatid), * TYPE_SCHEDULEDMEETING_UPDATED (chatid), TYPE_SET_TAKEDOWN (set id), * TYPE_SET_TAKEDOWN_REINSTATED (set id) * * @return the relevant node handle, or UNDEF if this alert does not have one. */ virtual MegaHandle getNodeHandle() const; /** * @brief Returns the handle of a Pending Contact Request related to the alert * * This value is valid for user related alerts: * TYPE_INCOMINGPENDINGCONTACT_CANCELLED, TYPE_INCOMINGPENDINGCONTACT_REMINDER, * TYPE_INCOMINGPENDINGCONTACT_REQUEST (PCR handle for all of these user alert types) * * This value is also valid for the following alerts: * TYPE_SCHEDULEDMEETING_NEW (parent scheduledMeetingId), * TYPE_SCHEDULEDMEETING_UPDATED (parent scheduledMeetingId) * * @return the relevant handle, or UNDEF if this alert does not have one. */ virtual MegaHandle getPcrHandle() const; /** * @brief Returns an email related to the alert * * This value is valid for alerts that relate to another user, provided the * user could be looked up at the time the alert arrived. If it was not available, * this function will return false and the client can request it via the userHandle. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaUserAlert object is deleted. * TYPE_CONTACTCHANGE_ACCOUNTDELETED,TYPE_CONTACTCHANGE_BLOCKEDYOU, * TYPE_CONTACTCHANGE_CONTACTESTABLISHED, TYPE_CONTACTCHANGE_DELETEDYOU, * TYPE_DELETEDSHARE, * TYPE_INCOMINGPENDINGCONTACT_CANCELLED, TYPE_INCOMINGPENDINGCONTACT_REMINDER, * TYPE_INCOMINGPENDINGCONTACT_REQUEST, * TYPE_NEWSHARE, TYPE_NEWSHAREDNODES, TYPE_REMOVEDSHAREDNODES * TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED, TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED, * TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED, * TYPE_SCHEDULEDMEETING_NEW, TYPE_SCHEDULEDMEETING_UPDATED, TYPE_SCHEDULEDMEETING_DELETED * * @return email string of the relevant user, or NULL if not available */ virtual const char* getEmail() const; /** * @brief Returns the path of a file, folder, or node related to the alert * * The SDK retains the ownership of the returned value. It will be valid until * the MegaUserAlert object is deleted. * * This value is valid for those alerts that relate to a single path, provided * it could be looked up from the cached nodes at the time the alert arrived. * Otherwise, it may be obtainable via the nodeHandle. * TYPE_DELETEDSHARE, TYPE_NEWSHARE?, TYPE_TAKEDOWN?, TYPE_TAKEDOWN_REINSTATED? * * @return the path string if relevant and available, otherwise NULL */ virtual const char* getPath() const; /** * @brief Returns the name of a file, folder, or node related to the alert * * The SDK retains the ownership of the returned value. It will be valid until * the MegaUserAlert object is deleted. * * This value is valid for those alerts that relate to a single name, provided * it could be looked up from the cached nodes at the time the alert arrived. * Otherwise, it may be obtainable via the nodeHandle. * TYPE_DELETEDSHARE, TYPE_NEWSHARE?, TYPE_TAKEDOWN?, TYPE_TAKEDOWN_REINSTATED?, * TYPE_SET_TAKEDOWN?, TYPE_SET_TAKEDOWN_REINSTATED? * * @return the name string if relevant and available, otherwise NULL */ virtual const char* getName() const; /** * @brief Returns the heading related to this alert * * The SDK retains the ownership of the returned value. They will be valid until * the MegaUserAlert object is deleted. * * This value is valid for all alerts, and similar to the strings displayed in the * webclient alerts. * * @return heading related to this alert. */ virtual const char* getHeading() const; /** * @brief Returns the title related to this alert * * The SDK retains the ownership of the returned value. They will be valid until * the MegaUserAlert object is deleted. * * This value is valid for all alerts, and similar to the strings displayed in the * webclient alerts. * * @return title related to this alert. */ virtual const char* getTitle() const; /** * @brief Returns a number related to this alert * * This value is valid for these alerts: * TYPE_DELETEDSHARE (index 0: value 1 if access for this user was removed by the share owner, otherwise * value 0 if someone left the folder) * TYPE_NEWSHAREDNODES (index 0: folder count 1: file count) * TYPE_REMOVEDSHAREDNODES (index 0: item count) * TYPE_UPDATEDSHAREDNODES (index 0: item count) * * This value is also valid for the following alerts: * TYPE_SCHEDULEDMEETING_NEW (index 0: value MEGA_INVALID_TIMESTAMP if there's no original startDateTime available for this user alert, otherwise * returns a value greater than MEGA_INVALID_TIMESTAMP) * * TYPE_SCHEDULEDMEETING_UPDATED (index 0: value MEGA_INVALID_TIMESTAMP if there's no original startDateTime available for this user alert, otherwise * returns a value greater than MEGA_INVALID_TIMESTAMP) * * @return Number related to this user alert, or -1 if the index is invalid */ virtual int64_t getNumber(unsigned index) const; /** * @brief Returns a timestamp related to this alert * * This value is valid for index 0 for all requests, indicating when the alert occurred. * Additionally TYPE_PAYMENTREMINDER index 1 is the timestamp of the expiry of the period. * * @return Timestamp related to this request, or -1 if the index is invalid */ virtual int64_t getTimestamp(unsigned index) const; /** * @brief Returns a handle related to this alert * * TYPE_NEWSHAREDNODES (folder and files) * * @return MegaHandle related to this request, or INVALID_HANDLE if the index is invalid */ virtual MegaHandle getHandle(unsigned index) const; /** * @brief Returns an additional string, related to the alert * * The SDK retains the ownership of the returned value. It will be valid until * the MegaUserAlert object is deleted. * * This value is currently only valid for * TYPE_PAYMENT_FAILED index 0: the plan name * TYPE_PAYMENT_SUCCEEDED index 0: the plan name * * @return a pointer to the string if index is valid; otherwise NULL */ virtual const char* getString(unsigned index) const; #ifdef ENABLE_CHAT /** * @brief Returns the MegaHandle that identifies the scheduled meeting id related to this alert * * This value is currently only valid for: * TYPE_SCHEDULEDMEETING_NEW * TYPE_SCHEDULEDMEETING_DELETED * TYPE_SCHEDULEDMEETING_UPDATED * * @return MegaHandle that identifies the scheduled meeting id related to this alert */ virtual MegaHandle getSchedId() const; /** * @brief Returns true if the scheduled meeting associated to this alert has an specific change * * This value is currently only valid for: * TYPE_SCHEDULEDMEETING_UPDATED * * @param changeType The type of change to check. It can be one of the following values: * - MegaUserAlert::SM_CHANGE_TYPE_TITLE 0x01 - Title has changed * - MegaUserAlert::SM_CHANGE_TYPE_DESCRIPTION 0x02 - Description has changed * - MegaUserAlert::SM_CHANGE_TYPE_CANCELLED 0x04 - Cancelled flag has changed * - MegaUserAlert::SM_CHANGE_TYPE_TIMEZONE 0x08 - Timezone has changed * - MegaUserAlert::SM_CHANGE_TYPE_STARTDATE 0x10 - Start date time has changed * - MegaUserAlert::SM_CHANGE_TYPE_ENDDATE 0x20 - End date time has changed * - MegaUserAlert::SM_CHANGE_TYPE_RULES 0x40 - Repetition rules have changed * * @return true if this scheduled meeting associated to this alert has an specific change */ virtual bool hasSchedMeetingChanged(uint64_t /*changeType*/) const; /** * @brief Returns a MegaStringList that contains old and new title for the scheduled meeting * * Note: This value is only valid if the following conditions are met: * - MegaUserAlert::getType == TYPE_SCHEDULEDMEETING_UPDATED * - MegaUserAlert::hasChanged(MegaUserAlert::SM_CHANGE_TYPE_TITLE) * * To retrieve old title you need to call: MegaStringList::get(0) * To retrieve new title you need to call: MegaStringList::get(1) * * @return MegaStringList that contains old and new title for the scheduled meeting */ virtual MegaStringList* getUpdatedTitle() const; /** * @brief Returns a MegaStringList that contains old and new TimeZone for the scheduled meeting * * Note: This value is only valid if the following conditions are met: * - MegaUserAlert::getType == TYPE_SCHEDULEDMEETING_UPDATED * - MegaUserAlert::hasChanged(MegaUserAlert::SM_CHANGE_TYPE_TIMEZONE) * * To retrieve old TimeZone you need to call: MegaStringList::get(0) * To retrieve new TimeZone you need to call: MegaStringList::get(1) * * @return MegaStringList that contains old and new TimeZone for the scheduled meeting */ virtual MegaStringList* getUpdatedTimeZone() const; /** * @brief Returns a MegaIntegerList that contains old and new StartDateTime for the scheduled meeting * * Note: This value is only valid if the following conditions are met: * - MegaUserAlert::getType == TYPE_SCHEDULEDMEETING_UPDATED * - MegaUserAlert::hasChanged(MegaUserAlert::SM_CHANGE_TYPE_STARTDATE) * * To retrieve old StartDateTime you need to call: MegaIntegerList::get(0) * To retrieve new StartDateTime you need to call: MegaIntegerList::get(1) * * @return MegaIntegerList that contains old and new StartDateTime for the scheduled meeting */ virtual MegaIntegerList* getUpdatedStartDate() const; /** * @brief Returns a MegaIntegerList that contains old and new EndDateTime for the scheduled meeting * * Note: This value is only valid if the following conditions are met: * - MegaUserAlert::getType == TYPE_SCHEDULEDMEETING_UPDATED * - MegaUserAlert::hasChanged(MegaUserAlert::SM_CHANGE_TYPE_ENDDATE) * * To retrieve old EndDateTime you need to call: MegaIntegerList::get(0) * To retrieve new EndDateTime you need to call: MegaIntegerList::get(1) * * @return MegaIntegerList that contains old and new EndDateTime for the scheduled meeting */ virtual MegaIntegerList* getUpdatedEndDate() const; #endif /** * @brief Indicates if the user alert is changed by yourself or by another client. * * This value is only useful for user alerts notified by MegaListener::onUserAlertsUpdate or * MegaGlobalListener::onUserAlertsUpdate that can notify about user alerts modifications. * * @return false if the change is external. true if the change is the result of a * request sent by this instance of the SDK. */ virtual bool isOwnChange() const; /** * @brief Indicates that the alert is about to be removed * * This value is useful to clean existing alerts that are not valid anymore. * In example, a TYPE_REMOVEDSHAREDNODES may become TYPE_UPDATEDSHAREDNODES. In * that case, the former will be notified as removed, and a new alert is added for * the latter. * * The SDK purge old alerts in order to keep the list limited to maximum amount * (currently up to 200). In result, the SDK may notify alerts as removed. * * @return True if the alert is about to be removed */ virtual bool isRemoved() const; }; /** * @brief List of MegaHandle objects * */ class MegaHandleList { protected: MegaHandleList(); public: /** * @brief Creates a new instance of MegaHandleList * @return A pointer the new object */ static MegaHandleList *createInstance(); virtual ~MegaHandleList(); /** * @brief Creates a copy of this MegaHandleList object * * The resulting object is fully independent of the source MegaHandleList, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaHandleList object */ virtual MegaHandleList *copy() const; /** * @brief Returns the MegaHandle at the position i in the MegaHandleList * * * If the index is >= the size of the list, this function returns MEGACHAT_INVALID_HANDLE. * * @param i Position of the MegaHandle that we want to get for the list * @return MegaHandle at the position i in the list */ virtual MegaHandle get(unsigned int i) const; /** * @brief Returns the number of MegaHandles in the list * @return Number of MegaHandles in the list */ virtual unsigned int size() const; /** * @brief Add new MegaHandle to list * @param megaHandle to be added */ virtual void addMegaHandle(MegaHandle megaHandle); }; class MegaIntegerList { public: virtual ~MegaIntegerList(); static MegaIntegerList* createInstance(); virtual MegaIntegerList *copy() const; /** * @brief Returns the integer at the position i in the MegaIntegerList * * If the index is >= the size of the list, this function returns -1. * * @param i Position of the integer that we want to get for the list * @return Integer at the position i in the list */ virtual int64_t get(int i) const; /** * @brief Add element to the MegaIntegerList * * @param value to add to list */ virtual void add(long long); /** * @brief Returns the number of integer values in the list * @return Number of integer values in the list */ virtual int size() const; }; /** * @brief Represents the outbound sharing of a folder with a user in MEGA * * It allows to get all data related to the sharing. You can start sharing a folder with * a contact or cancel an existing sharing using MegaApi::share. A public link of a folder * is also considered a sharing and can be cancelled. * * Objects of this class aren't live, they are snapshots of the state of the sharing * in MEGA when the object is created, they are immutable. * * Do not inherit from this class. You can get current active sharings using MegaApi::getOutShares * */ class MegaShare { public: enum { ACCESS_UNKNOWN = -1, ACCESS_READ = 0, ACCESS_READWRITE, ACCESS_FULL, ACCESS_OWNER }; virtual ~MegaShare(); /** * @brief Creates a copy of this MegaShare object * * The resulting object is fully independent of the source MegaShare, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaShare object */ virtual MegaShare *copy(); /** * @brief Returns the email of the user with whom we are sharing the folder * * For public shared folders, this function returns NULL * * The MegaShare object retains the ownership of the returned string, it will be valid until * the MegaShare object is deleted. * * @return The email of the user with whom we share the folder, or NULL if it's a public folder */ virtual const char *getUser(); /** * @brief Returns the handle of the folder that is being shared * @return The handle of the folder that is being shared */ virtual MegaHandle getNodeHandle(); /** * @brief Returns the access level of the sharing * * Possible return values are: * - ACCESS_UNKNOWN = -1 * It means that the access level is unknown * * - ACCESS_READ = 0 * The user can read the folder only * * - ACCESS_READWRITE = 1 * The user can read and write the folder * * - ACCESS_FULL = 2 * The user has full permissions over the folder * * - ACCESS_OWNER = 3 * The user is the owner of the folder * * @return The access level of the sharing */ virtual int getAccess(); /** * @brief Returns the timestamp when the sharing was created (in seconds since the epoch) * @return The timestamp when the sharing was created (in seconds since the epoch) */ virtual int64_t getTimestamp(); /** * @brief Returns true if the sharing is pending * * A sharing is pending when the folder has been shared with a user (or email) that * is not still a contact of this account. * * @return True if the sharing is pending, otherwise false. */ virtual bool isPending(); /** * @brief Returns true if the sharing is verified * * A sharing is verified when the keys have been shared with the other user after * verifying his credentials (see MegaApi::verifyCredentials). * * @return True if the sharing is pending, otherwise false. */ virtual bool isVerified(); }; #ifdef ENABLE_CHAT class MegaTextChatPeerList { protected: MegaTextChatPeerList(); public: enum { PRIV_UNKNOWN = -2, PRIV_RM = -1, PRIV_RO = 0, PRIV_STANDARD = 2, PRIV_MODERATOR = 3 }; /** * @brief Creates a new instance of MegaTextChatPeerList * @return A pointer to the superclass of the private object */ static MegaTextChatPeerList * createInstance(); virtual ~MegaTextChatPeerList(); /** * @brief Creates a copy of this MegaTextChatPeerList object * * The resulting object is fully independent of the source MegaTextChatPeerList, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaTextChatPeerList object */ virtual MegaTextChatPeerList *copy() const; /** * @brief addPeer Adds a new chat peer to the list * * @param h MegaHandle of the user to be added * @param priv Privilege level of the user to be added * Valid values are: * - MegaTextChatPeerList::PRIV_UNKNOWN = -2 * - MegaTextChatPeerList::PRIV_RM = -1 * - MegaTextChatPeerList::PRIV_RO = 0 * - MegaTextChatPeerList::PRIV_STANDARD = 2 * - MegaTextChatPeerList::PRIV_MODERATOR = 3 */ virtual void addPeer(MegaHandle h, int priv); /** * @brief Returns the MegaHandle of the chat peer at the position i in the list * * If the index is >= the size of the list, this function returns INVALID_HANDLE. * * @param i Position of the chat peer that we want to get from the list * @return MegaHandle of the chat peer at the position i in the list */ virtual MegaHandle getPeerHandle(int i) const; /** * @brief Returns the privilege of the chat peer at the position i in the list * * If the index is >= the size of the list, this function returns PRIV_UNKNOWN. * * @param i Position of the chat peer that we want to get from the list * @return Privilege level of the chat peer at the position i in the list. * Valid values are: * - MegaTextChatPeerList::PRIV_UNKNOWN = -2 * - MegaTextChatPeerList::PRIV_RM = -1 * - MegaTextChatPeerList::PRIV_RO = 0 * - MegaTextChatPeerList::PRIV_STANDARD = 2 * - MegaTextChatPeerList::PRIV_MODERATOR = 3 */ virtual int getPeerPrivilege(int i) const; /** * @brief Returns the number of chat peer in the list * @return Number of chat peers in the list */ virtual int size() const; }; class MegaTextChat { public: enum { CHANGE_TYPE_ATTACHMENT = 0x01, CHANGE_TYPE_FLAGS = 0x02, CHANGE_TYPE_MODE = 0x04, CHANGE_TYPE_CHAT_OPTIONS = 0x08, CHANGE_TYPE_SCHED_MEETING = 0x10, CHANGE_TYPE_SCHED_REPLACE_OCURR = 0x20, CHANGE_TYPE_SCHED_APPEND_OCURR = 0x40, }; virtual ~MegaTextChat(); /** * @brief Creates a copy of this MegaTextChat object * * The resulting object is fully independent of the source MegaTextChat, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaTextChat object */ virtual MegaTextChat *copy() const; /** * @brief getHandle Returns the MegaHandle of the chat. * @return MegaHandle of the chat. */ virtual MegaHandle getHandle() const; /** * @brief getOwnPrivilege Returns your privilege level in this chat * @return */ virtual int getOwnPrivilege() const; /** * @brief Returns the chat shard * @return The chat shard */ virtual int getShard() const; /** * @brief getPeerList Returns the full user list and privileges (excluding yourself). * * The MegaTextChat retains the ownership of the returned MetaTextChatPeerList. It will * be only valid until the MegaTextChat is deleted. * * @return The list of peers in the chat. */ virtual const MegaTextChatPeerList *getPeerList() const; /** * @brief Establish the list of peers participating on this chatroom * * If a peers list already exist, this function will delete it. * * The MegaTextChat does not take ownership of the list passed as parameter, it makes * a local copy. * * @param peers List of peers */ virtual void setPeerList(const MegaTextChatPeerList *peers); /** * @brief isGroup Returns whether this chat is a group chat or not * @return True if this chat is a group chat. Only chats with more than 2 peers are groupal chats. */ virtual bool isGroup() const; /** * @brief getOriginatingUser Returns the user that originated the chat notification * * @note This value is only relevant for new or updated chats notified by MegaGlobalListener::onChatsUpdate or * MegaListener::onChatsUpdate. * * @return The handle of the user who originated the chat notification. */ virtual MegaHandle getOriginatingUser() const; /** * @brief Returns the title of the chat, if any. * * The MegaTextChat retains the ownership of the returned string. It will * be only valid until the MegaTextChat is deleted. * * @return The title of the chat as a byte array encoded in Base64URL, or NULL if not available. */ virtual const char *getTitle() const; /** * @brief Returns the Unified key of the chat, if it's a public chat. * * The MegaTextChat retains the ownership of the returned string. It will * be only valid until the MegaTextChat is deleted. * * @return The Unified key [] of the chat as a byte array encoded in Base64URL, or NULL if not available. */ virtual const char *getUnifiedKey() const; /** * @brief Returns the chat options. * * The returned value contains the chat options represented in 1 Byte, where each individual option is stored in 1 bit. * Check ChatOptions struct at types.h * * @return The chat options in a numeric format */ virtual unsigned char getChatOptions() const; /** * @brief Returns true if this chat has an specific change * * This value is only useful for chats notified by MegaListener::onChatsUpdate or * MegaGlobalListener::onChatsUpdate that can notify about chat modifications. * * In other cases, the return value of this function will be always false. * * @param changeType The type of change to check. It can be one of the following values: * * - MegaTextChat::CHANGE_TYPE_ATTACHMENT = 0x01 * Check if the access to nodes have been granted/revoked * * - MegaTextChat::CHANGE_TYPE_FLAGS = 0x02 * Check if flags have changed (like archive flag) * * - MegaTextChat::CHANGE_TYPE_MODE = 0x04 * Check if operation mode has changed to private mode (from public mode) * * - MegaTextChat::CHANGE_TYPE_CHAT_OPTIONS = 0x08 * Check if chat options have changed * * - MegaTextChat::CHANGE_TYPE_SCHED_MEETING = 0x10 * Check if scheduled meetings have changed * * - MegaTextChat::CHANGE_TYPE_SCHED_OCURR = 0x20 * Check if scheduled meetings occurrences have changed * (current ones are automatically discarded) * * CHANGE_TYPE_SCHED_APPEND_OCURR * Check if we have received more scheduled meetings occurrences * * @return true if this chat has an specific change */ virtual bool hasChanged(uint64_t changeType) const; /** * @brief Returns a bit field with the changes of the chatroom * * This value is only useful for chats notified by MegaListener::onChatsUpdate or * MegaGlobalListener::onChatsUpdate that can notify about chat modifications. * * - MegaTextChat::CHANGE_TYPE_ATTACHMENT = 0x01 * Check if the access to nodes have been granted/revoked * * - MegaTextChat::CHANGE_TYPE_FLAGS = 0x02 * Check if flags have changed (like archive flag) * * - MegaTextChat::CHANGE_TYPE_MODE = 0x04 * Check if operation mode has changed to private mode (from public mode) * * - MegaTextChat::CHANGE_TYPE_CHAT_OPTIONS = 0x08 * Check if chat options have changed * * - MegaTextChat::CHANGE_TYPE_SCHED_MEETING = 0x10 * Check if scheduled meetings have changed * * - MegaTextChat::CHANGE_TYPE_SCHED_OCURR = 0x20 * Check if scheduled meetings occurrences have changed * (current ones are automatically discarded) * * CHANGE_TYPE_SCHED_APPEND_OCURR * Check if we have received more scheduled meetings occurrences * * @return The returned value is an OR combination of these flags */ virtual uint64_t getChanges() const; /** * @brief Indicates if the chat is changed by yourself or by another client. * * This value is only useful for chats notified by MegaListener::onChatsUpdate or * MegaGlobalListener::onChatsUpdate that can notify about chat modifications. * * @return 0 if the change is external. >0 if the change is the result of an * explicit request, -1 if the change is the result of an implicit request * made by the SDK internally. */ virtual int isOwnChange() const; /** * @brief Returns the scheduled meetings list. * * The MegaTextChat retains the ownership of the returned MegaScheduledMeetingList. It will * be only valid until the MegaTextChat is deleted. * * @return The list of the scheduled meetings. */ virtual const MegaScheduledMeetingList* getScheduledMeetingList() const; /** * @brief Returns a list with updated the scheduled meetings occurrences. * * The MegaTextChat retains the ownership of the returned MegaScheduledMeetingList. It will * be only valid until the MegaTextChat is deleted. * * The value returned by this method will only be valid when * MegaTextChat::hasChange(CHANGE_TYPE_SCHED_APPEND_OCURR) returns true * * @return The list of the updated scheduled meetings occurrences. */ virtual const MegaScheduledMeetingList* getUpdatedOccurrencesList() const; /** * @brief Returns a MegaHandleList with the handles of the scheduled meetings that have changed * * This method only returns a valid value when MegaTextChat::hasChange(CHANGE_TYPE_SCHED_MEETING) returns true * * The MegaTextChat retains the ownership of the returned MegaHandleList. It will * be only valid until the MegaTextChat is deleted. * * @return MegaHandleList with the handles of the scheduled meetings that have changed. */ virtual const MegaHandleList* getSchedMeetingsChanged() const; /** * @brief Returns the creation timestamp of the chat * * In seconds since the Epoch * * @return Creation date of the chat */ virtual int64_t getCreationTime() const; /** * @brief Returns whether this chat has been archived by the user or not * @return True if this chat is archived. */ virtual bool isArchived() const; /** * @brief Returns whether this chat is public or private * @return True if this chat is public */ virtual bool isPublicChat() const; /** * @brief Returns whether this chat is a meeting room * @return True if this chat is a meeting room */ virtual bool isMeeting() const; }; /** * @brief List of MegaTextChat objects * * A MegaTextChatList has the ownership of the MegaTextChat objects that it contains, so they will be * only valid until the MegaTextChatList is deleted. If you want to retain a MegaTextChat returned by * a MegaTextChatList, use MegaTextChat::copy. * * Objects of this class are immutable. */ class MegaTextChatList { public: virtual ~MegaTextChatList(); virtual MegaTextChatList *copy() const; /** * @brief Returns the MegaTextChat at the position i in the MegaTextChatList * * The MegaTextChatList retains the ownership of the returned MegaTextChat. It will be only valid until * the MegaTextChatList is deleted. If you want to retain a MegaTextChat returned by this function, * use MegaTextChat::copy. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaTextChat that we want to get for the list * @return MegaTextChat at the position i in the list */ virtual const MegaTextChat *get(unsigned int i) const; /** * @brief Returns the number of MegaTextChats in the list * @return Number of MegaTextChats in the list */ virtual int size() const; }; /** * @brief This class represents a scheduled meeting. Scheduled Meetings allows the user to specify an event that will occur in the future. * The user can also specify a set of rules for repetition, these rules enable an event to reoccur periodically. * * Important consideration: * A Chatroom only should have one root scheduled meeting associated, it means that just one scheduled meeting for a chatroom, * should have an invalid parent sched Id (MegaScheduledMeeting::parentSchedId) * */ class MegaScheduledMeeting { public: virtual ~MegaScheduledMeeting(); /** * @brief Creates a new instance of MegaScheduledMeeting * * @param chatid : chat handle * @param schedId : scheduled meeting handle * @param parentSchedId : parent scheduled meeting handle * @param cancelled : cancelled flag * @param timezone : timeZone * @param startDateTime : start dateTime (unix timestamp) * @param endDateTime : end dateTime (unix timestamp) * @param title : meeting title * @param description : meeting description * @param attributes : attributes to store any additional data * @param overrides : start dateTime of the original meeting series event to be replaced (unix timestamp) * @param flags : flags bitmask (used to store additional boolean settings as a bitmask) * @param rules : scheduled meetings rules * * @return A pointer to the superclass of the private object */ static MegaScheduledMeeting* createInstance(MegaHandle chatid, MegaHandle schedId, MegaHandle parentSchedId, MegaHandle organizerUserId, int cancelled, const char* timezone, MegaTimeStamp startDateTime, MegaTimeStamp endDateTime, const char* title, const char* description, const char* attributes, MegaTimeStamp overrides, MegaScheduledFlags* flags, MegaScheduledRules* rules); /** * @brief Creates a copy of this MegaScheduledMeeting object * * The resulting object is fully independent of the source MegaScheduledMeeting, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of the returned object * * @return Copy of the MegaScheduledMeeting object */ virtual MegaScheduledMeeting* copy() const; /** * @brief Returns if scheduled meeting is cancelled or not * * @return True if scheduled meeting is cancelled, otherwise returns false */ virtual int cancelled() const; /** * @brief Returns the MegaHandle of the chat * * @return MegaHandle of the chat */ virtual MegaHandle chatid() const; /** * @brief Returns the MegaHandle that identifies the scheduled meeting * * @return MegaHandle that identifies the scheduled meeting */ virtual MegaHandle schedId() const; /** * @brief Returns the MegaHandle of the organizer user of the scheduled meeting * * @return MegaHandle of the organizer user of the scheduled meeting */ virtual MegaHandle organizerUserid() const; /** * @brief Returns the MegaHandle that identifies the parent scheduled meeting * * @return MegaHandle that identifies the parent scheduled meeting */ virtual MegaHandle parentSchedId() const; /** * @brief Returns the time zone * * @return time zone */ virtual const char* timezone() const; /** * @brief Returns the start dateTime of the scheduled Meeting (unix timestamp) * * @return the start dateTime of the scheduled Meeting */ virtual MegaTimeStamp startDateTime() const; /** * @brief Returns the end dateTime of the scheduled Meeting (unix timestamp) * * @return the end dateTime of the scheduled Meeting */ virtual MegaTimeStamp endDateTime() const; /** * @brief Returns the scheduled meeting title * * @return The title of the scheduled meeting */ virtual const char* title() const; /** * @brief Returns the scheduled meeting description * * @return The description of the scheduled meeting */ virtual const char* description() const; /** * @brief Returns additional scheduled meetings attributes * * @return Additional scheduled meetings attributes */ virtual const char* attributes() const; /** * @brief Returns the start dateTime of the original meeting series event to be replaced (unix timestamp) * * @return the start dateTime of the original meeting series event to be replaced */ virtual MegaTimeStamp overrides() const; /** * @brief Returns a pointer to MegaScheduledFlags that contains the scheduled meetings flags * * You take ownership of the returned MegaScheduledFlags * * @return A pointer to MegaScheduledFlags that contains the scheduled meetings flags */ virtual MegaScheduledFlags* flags() const; /** * @brief Returns a pointer to MegaScheduledRules that contains the scheduled meetings rules * * You take ownership of the returned MegaScheduledRules * * @return A pointer to MegaScheduledRules that contains the scheduled meetings rules */ virtual MegaScheduledRules* rules() const; }; /** * @brief This class represents a set of meetings flags in a bit mask format, where every flag is represented by 1 bit */ class MegaScheduledFlags { public: enum { FLAGS_SEND_EMAILS = 0, // API will send out calendar emails for this meeting if it's enabled FLAGS_SIZE = 1, // size in bits of flags bitmask }; virtual ~MegaScheduledFlags(); /** * @brief Creates a new instance of MegaScheduledFlags * * @return A pointer to the superclass of the private object */ static MegaScheduledFlags* createInstance(); /** * @brief Imports scheduled meetings flags from numeric value */ virtual void importFlagsValue(unsigned long); /** * @brief Creates a copy of this virtual MegaScheduledFlags object * * The resulting object is fully independent of the source MegaScheduledFlags, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of the returned object * * @return Copy of the MegaScheduledFlags object */ virtual MegaScheduledFlags* copy() const; /** * @brief Reset the value of all options (to disabled) */ virtual void reset(); /** * @brief Returns the bistmask in a numeric value format * * @return The bistmask in a numeric value format */ virtual unsigned long getNumericValue() const; /** * @brief Returns true if all flags are disabled * * @return True if all flags are disabled, otherwise returns false. */ virtual bool isEmpty() const; }; /** * @brief This class represents a set of set of rules that can be defined for a Scheduled meeting. */ class MegaScheduledRules { public: enum { FREQ_INVALID = -1, FREQ_DAILY = 0, FREQ_WEEKLY = 1, FREQ_MONTHLY = 2, }; constexpr static int INTERVAL_INVALID = 0; virtual ~MegaScheduledRules(); /** * @brief Creates a new instance of MegaScheduledRules * * @param freq : scheduled meeting frequency (DAILY | WEEKLY | MONTHLY), this is used in conjunction with interval * @param interval : repetition interval in relation to the frequency * @param until : specifies when the repetitions should end * @param byWeekDay : allows us to specify that an event will only occur on given week day/s * @param byMonthDay : allows us to specify that an event will only occur on a given day/s of the month * @param byMonthWeekDay : allows us to specify that an event will only occurs on a specific weekday offset of the month. (i.e every 2nd Sunday of each month) * * @return A pointer to the superclass of the private object */ static MegaScheduledRules* createInstance(int freq, int interval = INTERVAL_INVALID, MegaTimeStamp until = MEGA_INVALID_TIMESTAMP, const ::mega::MegaIntegerList* byWeekDay = nullptr, const ::mega::MegaIntegerList* byMonthDay = nullptr, const ::mega::MegaIntegerMap* byMonthWeekDay = nullptr); /** * @brief Creates a copy of this MegaScheduledRules object * * The resulting object is fully independent of the source MegaScheduledRules, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of the returned object * * @return Copy of the MegaScheduledRules object */ virtual MegaScheduledRules* copy() const; /** * @brief Returns the frequency of the scheduled meeting: (DAILY | WEEKLY | MONTHLY) * @return The frequence of the scheduled meeting */ virtual int freq() const; /** * @brief Returns repetition interval in relation to the frequency * * @return The inverval in relation to the frequency of the scheduled meeting */ virtual int interval() const; /** * @brief Returns when the repetitions should end (unix timestamp) * * @return When the repetitions should end */ virtual MegaTimeStamp until() const; /** * @brief Returns a MegaIntegerList with the week days when the event will occur * * @return A MegaIntegerList with the week days when the event will occur */ virtual const mega::MegaIntegerList* byWeekDay() const; /** * @brief Returns a MegaIntegerList with the days of the month when the event will occur * * @return A MegaIntegerList with the days of the month when the event will occur */ virtual const mega::MegaIntegerList* byMonthDay() const; /** * @brief Returns a MegaIntegerMap that allows to specify one or multiple weekday offset * + Positive offset: (ie: [5,4] event will occur every 5th Thursday of each month) * + Negative offset: (ie: [-1,1] event will occur every last Monday of each month) * * @return A MegaIntegerMap that allows to specify one or multiple weekday offset */ virtual const mega::MegaIntegerMap* byMonthWeekDay() const; /** * @brief Returns if a given frequency is valid or not * * @return True if freq is valid, otherwise false */ static bool isValidFreq(int freq); /** * @brief Returns if a given interval is valid or not * * @return True if interval is valid, otherwise false */ static bool isValidInterval(int interval); }; /** * @brief List of MegaScheduledMeeting objects */ class MegaScheduledMeetingList { public: virtual ~MegaScheduledMeetingList(); /** * @brief Creates a new instance of MegaScheduledMeetingList * * You take the ownership of the returned object * * @return A pointer to the superclass of the private object */ static MegaScheduledMeetingList* createInstance(); /** * @brief Creates a copy of this MegaScheduledMeetingList object * * The resulting object is fully independent of the source MegaScheduledMeetingList, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of the returned object * * @return Copy of the MegaScheduledMeetingList object */ virtual MegaScheduledMeetingList *copy() const; /** * @brief Returns the number of elements in the list * @return Number of elements in the list */ virtual unsigned long size() const; /** * @brief Returns the MegaScheduledMeeting at the position i in the MegaScheduledMeetingList * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the element that we want to get for the list * @return MegaScheduledMeeting at the position i in the MegaScheduledMeetingList */ virtual MegaScheduledMeeting* at(unsigned long i) const; /** * @brief Get a MegaScheduledMeeting object in the list, that has a specific scheduled meeting id * If no scheduled meeting is found with the provided id, this method will returns NULL * * You take the ownership of the returned value * * @param h Scheduled meeting id * @return MegaScheduledMeeting with scheduled meeting id equal to h, or NULL */ virtual MegaScheduledMeeting* getBySchedId(MegaHandle h) const; /** * @brief Add element to the MegaScheduledMeetingList * * The SDK adquires the ownership of provided MegaScheduledMeeting * * @param sm MegaScheduledMeeting to add to list */ virtual void insert(MegaScheduledMeeting* sm); /** * @brief Clears the MegaScheduledMeetingList */ virtual void clear(); }; #endif /** * @brief Map of string values with string keys (map) * * A MegaStringMap has the ownership of the strings that it contains, so they will be * only valid until the MegaStringMap is deleted. If you want to retain a string returned by * a MegaStringMap, copy it. * * Objects of this class are immutable. */ class MegaStringMap { protected: MegaStringMap(); public: virtual ~MegaStringMap(); /** * @brief Creates a new instance of MegaStringMap * @return A pointer to the superclass of the private object */ static MegaStringMap *createInstance(); /** * @brief Creates a copy of this MegaStringMap object * * The resulting object is fully independent of the source MegaStringMap, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of the returned object * * @return Copy of the MegaStringMap object */ virtual MegaStringMap *copy() const; /** * @brief Returns the string at the position key in the MegaStringMap * * The returned value is a null-terminated char array. If the value in the map is an array of * bytes, then it will be a Base64-encoded string. * * The MegaStringMap retains the ownership of the returned string. It will be only valid until * the MegaStringMap is deleted. * * If the key is not found in the map, this function returns NULL. * * @param key Key of the string that you want to get from the map * @return String at the position key in the map */ virtual const char* get(const char* key) const; /** * @brief Returns the list of keys in the MegaStringMap * * You take the ownership of the returned value * * @return A MegaStringList containing the keys present in the MegaStringMap */ virtual MegaStringList *getKeys() const; /** * @brief Sets a value in the MegaStringMap for the given key. * * If the key already exists in the MegaStringMap, the value will be overwritten by the * new value. * * The MegaStringMap does not take ownership of the strings passed as parameter, it makes * a local copy. * * @param key Key for the new value in the map. It must be a NULL-terminated string. * @param value The new value for the key in the map. It must be a NULL-terminated string. */ virtual void set(const char* key, const char *value); /** * @brief Returns the number of strings in the map * @return Number of strings in the map */ virtual int size() const; }; /** * @brief Map (multimap) of integer values with integer keys (map) */ class MegaIntegerMap { public: /** * @brief Creates a new instance of MegaIntegerMap * @return A pointer to the superclass of the private object */ static MegaIntegerMap* createInstance(); virtual ~MegaIntegerMap(); virtual MegaIntegerMap* copy() const; /** * @brief Returns the list of keys in the MegaIntegerMap * * You take the ownership of the returned value * * @return A MegaIntegerList containing the keys present in the MegaIntegerMap */ virtual MegaIntegerList* getKeys() const; /** * @brief Returns a list of values for the provided key * * You take the ownership of the returned value * * @param key Key of the element that you want to get from the map * @return A MegaIntegerList containing the list of values for the provided key */ virtual MegaIntegerList* get(int64_t key) const; /** * @brief Sets a value in the map for the given key. * * If the key already exists, the value will be overwritten by the * new value. * * @param key The key in the map. * @param value The new value for the key in the map. */ virtual void set(int64_t key, int64_t value); /** * @brief Returns the number of (long long, long long) pairs in the map * @return Number of pairs in the map */ virtual int64_t size() const; }; /** * @brief Map of integer values with string keys (map) */ class MegaStringIntegerMap { public: virtual ~MegaStringIntegerMap() = default; virtual MegaStringIntegerMap* copy() const = 0; /** * @brief Returns the list of keys in the MegaStringIntegerMap * * You take the ownership of the returned value * * @return A MegaStringList containing the keys present in the MegaStringIntegerMap */ virtual MegaStringList* getKeys() const = 0; /** * @brief Returns a list with the value of the provided key * * You take the ownership of the returned value * * @param key Key of the element that you want to get from the map * @return A MegaIntegerList containing the list with the value for the provided key */ virtual MegaIntegerList* get(const char* key) const = 0; /** * @brief Sets a value in the map for the given key. * * If the key already exists, the value will be overwritten by the * new value. * * @param key The key in the map. * @param value The new value for the key in the map. */ virtual void set(const char* key, int64_t value) = 0; /** * @brief Returns the number of (string, int64_t) pairs in the map * @return Number of pairs in the map */ virtual int64_t size() const = 0; }; /** * @brief List of strings * * A MegaStringList has the ownership of the strings that it contains, so they will be * only valid until the MegaStringList is deleted. If you want to retain a string returned by * a MegaStringList, copy it. * * Objects of this class are immutable. */ class MegaStringList { public: /** * @brief Creates a new instance of MegaStringList * * @return A pointer to the superclass of the private object */ static MegaStringList *createInstance(); virtual ~MegaStringList(); virtual MegaStringList *copy() const; /** * @brief Returns the string at the position i in the MegaStringList * * The MegaStringList retains the ownership of the returned string. It will be only valid until * the MegaStringList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the string that we want to get for the list * @return string at the position i in the list */ virtual const char* get(int i) const; /** * @brief Returns the number of strings in the list * @return Number of strings in the list */ virtual int size() const; /** * @brief Add element to the list * * @param value String to add to list */ virtual void add(const char* value); }; /** * @brief A map of strings to string lists * * A MegaStringListMap takes owership of the MegaStringList objects passed to it. It does * NOT take ownership of the keys passed to it but makes a local copy. */ class MegaStringListMap { protected: MegaStringListMap(); public: virtual ~MegaStringListMap(); static MegaStringListMap* createInstance(); virtual MegaStringListMap* copy() const; /** * @brief Returns the string list at the given key in the map * * The MegaStringMap retains the ownership of the returned string list. It will be only * valid until the MegaStringMap is deleted. * * If the key is not found in the map, this function returns NULL. * * @param key Key to lookup in the map. Must be null-terminated * @return String list at the given key in the map */ virtual const MegaStringList* get(const char* key) const; /** * @brief Returns the list of keys in the MegaStringListMap * * You take the ownership of the returned value * * @return A MegaStringList containing the keys present in the MegaStringListMap */ virtual MegaStringList *getKeys() const; /** * @brief Sets a value in the map for the given key. * * If the key already exists, the value will be overwritten by the * new value. * * The map does not take ownership of the passed key, it makes * a local copy. However, it does take ownership of the passed value. * * @param key The key in the map. It must be a null-terminated string. * @param value The new value for the key in the map. */ virtual void set(const char* key, const MegaStringList* value); /** * @brief Returns the number of (string, string list) pairs in the map * @return Number of pairs in the map */ virtual int size() const; }; /** * @brief A list of string lists forming a table of strings. * * Each row can have a different number of columns. * However, ideally this class should be used as a table only. * * A MegaStringTable takes owership of the MegaStringList objects passed to it. */ class MegaStringTable { protected: MegaStringTable(); public: virtual ~MegaStringTable(); static MegaStringTable *createInstance(); virtual MegaStringTable* copy() const; /** * @brief Appends a new string list to the end of the table * * The table takes ownership of the passed value. * * @param value The string list to append */ virtual void append(const MegaStringList* value); /** * @brief Returns the string list at position i * * The table retains the ownership of the returned string list. It will be only valid until * the table is deleted. * * The returned pointer is null if i is out of range. * * @return The string list at position i */ virtual const MegaStringList* get(int i) const; /** * @brief Returns the number of string lists in the table * @return Number of string lists in the table */ virtual int size() const; }; /** * @brief List of MegaNode objects * * A MegaNodeList has the ownership of the MegaNode objects that it contains, so they will be * only valid until the NodeList is deleted. If you want to retain a MegaMode returned by * a MegaNodeList, use MegaNode::copy. * * Objects of this class are immutable. * * @see MegaApi::getChildren, MegaApi::search, MegaApi::getInShares */ class MegaNodeList { protected: MegaNodeList(); public: /** * @brief Creates a new instance of MegaNodeList * @return A pointer to the superclass of the private object */ static MegaNodeList * createInstance(); virtual ~MegaNodeList(); virtual MegaNodeList *copy() const; /** * @brief Returns the MegaNode at the position i in the MegaNodeList * * The MegaNodeList retains the ownership of the returned MegaNode. It will be only valid until * the MegaNodeList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaNode that we want to get for the list * @return MegaNode at the position i in the list */ virtual MegaNode* get(int i) const; /** * @brief Returns the number of MegaNode objects in the list * @return Number of MegaNode objects in the list */ virtual int size() const; /** * @brief Add new node to list * @param node MegaNode to be added. The node inserted is a copy from 'node' */ virtual void addNode(MegaNode* node); }; /** * @brief Lists of file and folder children MegaNode objects * * @obsolete This method is unused by app * * A MegaChildrenLists object has the ownership of the MegaNodeList objects that it contains, * so they will be only valid until the MegaChildrenLists is deleted. If you want to retain * a MegaNodeList returned by a MegaChildrenLists, use MegaNodeList::copy. * * Objects of this class are immutable. */ class MegaChildrenLists { public: virtual ~MegaChildrenLists(); virtual MegaChildrenLists *copy(); /** * @brief Get the list of folder MegaNode objects * @return List of MegaNode folders */ virtual MegaNodeList* getFolderList(); /** * @brief Get the list of file MegaNode objects * @return List of MegaNode files */ virtual MegaNodeList* getFileList(); }; /** * @brief List of MegaUser objects * * A MegaUserList has the ownership of the MegaUser objects that it contains, so they will be * only valid until the MegaUserList is deleted. If you want to retain a MegaUser returned by * a MegaUserList, use MegaUser::copy. * * Objects of this class are immutable. * * @see MegaApi::getContacts * */ class MegaUserList { public: virtual ~MegaUserList(); virtual MegaUserList *copy(); /** * @brief Returns the MegaUser at the position i in the MegaUserList * * The MegaUserList retains the ownership of the returned MegaUser. It will be only valid until * the MegaUserList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaUser that we want to get for the list * @return MegaUser at the position i in the list */ virtual MegaUser* get(int i); /** * @brief Returns the number of MegaUser objects in the list * @return Number of MegaUser objects in the list */ virtual int size(); }; /** * @brief List of MegaShare objects * * A MegaShareList has the ownership of the MegaShare objects that it contains, so they will be * only valid until the MegaShareList is deleted. If you want to retain a MegaShare returned by * a MegaShareList, use MegaShare::copy. * * Objects of this class are immutable. * * @see MegaApi::getOutShares */ class MegaShareList { public: virtual ~MegaShareList(); /** * @brief Returns the MegaShare at the position i in the MegaShareList * * The MegaShareList retains the ownership of the returned MegaShare. It will be only valid until * the MegaShareList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaShare that we want to get for the list * @return MegaShare at the position i in the list */ virtual MegaShare* get(int i); /** * @brief Returns the number of MegaShare objects in the list * @return Number of MegaShare objects in the list */ virtual int size(); }; /** * @brief List of MegaTransfer objects * * A MegaTransferList has the ownership of the MegaTransfer objects that it contains, so they will be * only valid until the MegaTransferList is deleted. If you want to retain a MegaTransfer returned by * a MegaTransferList, use MegaTransfer::copy. * * Objects of this class are immutable. * * @see MegaApi::getTransfers */ class MegaTransferList { public: virtual ~MegaTransferList(); /** * @brief Returns the MegaTransfer at the position i in the MegaTransferList * * The MegaTransferList retains the ownership of the returned MegaTransfer. It will be only valid until * the MegaTransferList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaTransfer that we want to get for the list * @return MegaTransfer at the position i in the list */ virtual MegaTransfer* get(int i); /** * @brief Returns the number of MegaTransfer objects in the list * @return Number of MegaTransfer objects in the list */ virtual int size(); }; /** * @brief List of MegaContactRequest objects * * A MegaContactRequestList has the ownership of the MegaContactRequest objects that it contains, so they will be * only valid until the MegaContactRequestList is deleted. If you want to retain a MegaContactRequest returned by * a MegaContactRequestList, use MegaContactRequest::copy. * * Objects of this class are immutable. * * @see MegaApi::getContactRequests */ class MegaContactRequestList { public: virtual ~MegaContactRequestList(); virtual MegaContactRequestList* copy() const; /** * @brief Returns the MegaContactRequest at the position i in the MegaContactRequestList * * The MegaContactRequestList retains the ownership of the returned MegaContactRequest. It * will be only valid until the MegaContactRequestList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaContactRequest that we want to get for the list * @return MegaContactRequest at the position i in the list */ MegaContactRequest* get(int i); /** * @brief Returns the MegaContactRequest at the position i in the MegaContactRequestList * * The MegaContactRequestList retains the ownership of the returned MegaContactRequest. It will be only valid until * the MegaContactRequestList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaContactRequest that we want to get for the list * @return MegaContactRequest at the position i in the list */ virtual const MegaContactRequest* get(int i) const; /** * @brief Returns the number of MegaContactRequest objects in the list * @return Number of MegaContactRequest objects in the list */ virtual int size() const; }; /** * @brief List of MegaUserAlert objects * * A MegaUserAlertList has the ownership of the MegaUserAlert objects that it contains, so they will be * only valid until the MegaUserAlertList is deleted. If you want to retain a MegaUserAlert returned by * a MegaUserAlertList, use MegaUserAlert::copy. * * Objects of this class are immutable. * * @see MegaApi::getUserAlerts * */ class MegaUserAlertList { public: virtual ~MegaUserAlertList(); virtual MegaUserAlertList *copy() const; /** * @brief Returns the MegaUserAlert at the position i in the MegaUserAlertList * * The MegaUserAlertList retains the ownership of the returned MegaUserAlert. It will be only valid until * the MegaUserAlertList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaUserAlert that we want to get for the list * @return MegaUserAlert at the position i in the list */ virtual MegaUserAlert* get(int i) const; /** * @brief Returns the number of MegaUserAlert objects in the list * @return Number of MegaUserAlert objects in the list */ virtual int size() const; /** * @brief Removes all MegaUserAlert objects from the list (does not delete them) */ virtual void clear(); }; /** * @brief Represents a set of files uploaded or updated in MEGA. * These are used to display the recent changes to an account. * * Objects of this class aren't live, they are snapshots of the state * in MEGA when the object is created, they are immutable. * * MegaRecentActionBuckets can be retrieved with MegaApi::getRecentActions * and MegaApi::getRecentActionsAsync. * */ class MegaRecentActionBucket { public: virtual ~MegaRecentActionBucket(); /** * @brief Creates a copy of this MegaRecentActionBucket object. * * The resulting object is fully independent of the source MegaRecentActionBucket, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaRecentActionBucket object */ virtual MegaRecentActionBucket *copy() const; /** * @brief Returns a timestamp reflecting when these changes occurred * * @return Timestamp indicating when the changes occurred (in seconds since the Epoch) */ virtual int64_t getTimestamp() const; /** * @brief Returns the email of the user who made the changes * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRecentActionBucket object is deleted. * * @return The associated user's email */ virtual const char* getUserEmail() const; /** * @brief Returns the handle of the parent folder these changes occurred in * * @return The handle of the parent folder for these changes. */ virtual MegaHandle getParentHandle() const; /** * @brief Returns whether the changes are updated files, or new files * * @return True if the changes are updates rather than newly uploaded files. */ virtual bool isUpdate() const; /** * @brief Returns whether the files are photos or videos * * @return True if the files in this change are media files. */ virtual bool isMedia() const; /** * @brief Returns the identifier for this bucket * * Format: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRecentActionBucket object is deleted. * * @return The bucket identifier string */ virtual const char* getId() const; /** * @brief Returns nodes representing the files changed in this bucket * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRecentActionBucket object is deleted. * * @return A MegaNodeList containing the files in the bucket */ virtual const MegaNodeList* getNodes() const; }; /** * @brief List of MegaRecentActionBucket objects * * A MegaRecentActionBucketList has the ownership of the MegaRecentActionBucket objects that it * contains, so they will be only valid until the MegaRecentActionBucketList is deleted. If you want * to retain a MegaRecentActionBucket returned by a MegaRecentActionBucketList, use * MegaRecentActionBucket::copy. * * Objects of this class are immutable. * * @see MegaApi::getRecentActions * @see MegaApi::getRecentActionsAsync * */ class MegaRecentActionBucketList { public: virtual ~MegaRecentActionBucketList(); /** * @brief Creates a copy of this MegaRecentActionBucketList object. * * The resulting object is fully independent of the source MegaRecentActionBucketList, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaRecentActionBucketList object */ virtual MegaRecentActionBucketList *copy() const; /** * @brief Returns the MegaRecentActionBucket at the position i in the MegaRecentActionBucketList * * The MegaRecentActionBucketList retains the ownership of the returned MegaRecentActionBucket. It will be only valid until * the MegaRecentActionBucketList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaRecentActionBucket that we want to get for the list * @return MegaRecentActionBucket at the position i in the list */ virtual MegaRecentActionBucket* get(int i) const; /** * @brief Returns the number of MegaRecentActionBucket objects in the list * @return Number of MegaRecentActionBucket objects in the list */ virtual int size() const; }; /** * @brief Represents a set of properties that define a SmartBanner. * These are used to display a banner in mobile apps. * * MegaBanner-s can be retrieved from MegaBannerList * */ class MegaBanner { public: virtual ~MegaBanner(); /** * @brief Creates a copy of this MegaBanner object. * * The resulting object is fully independent of the source MegaBanner, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaBanner object */ virtual MegaBanner* copy() const; /** * @brief Returns the id of the MegaBanner * * @return Id of this banner */ virtual int getId() const; /** * @brief Returns the title associated with the MegaBanner object * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return Title associated with the MegaBanner object */ virtual const char* getTitle() const; /** * @brief Returns the description associated with the MegaBanner object * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return Description associated with the MegaBanner object */ virtual const char* getDescription() const; /** * @brief Returns the filename of the image used by the MegaBanner object * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return Filename of the image used by the MegaBanner object */ virtual const char* getImage() const; /** * @brief Returns the URL associated with the MegaBanner object * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return URL associated with the MegaBanner object */ virtual const char* getUrl() const; /** * @brief Returns the filename of the background image used by the MegaBanner object * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return Filename of the background image used by the MegaBanner object */ virtual const char* getBackgroundImage() const; /** * @brief Returns the URL where images are located * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return URL where images are located */ virtual const char* getImageLocation() const; /** * @brief Returns the variant of the banner * * @return Variant of the banner */ virtual int getVariant() const; /** * @brief Returns the button of the banner * * The SDK retains the ownership of the returned value. It will be valid until * the MegaBanner object is deleted. * * @return Button of the banner */ virtual const char* getButton() const; protected: MegaBanner(); }; /** * @brief List of MegaBanner objects * * A MegaBannerList has the ownership of the MegaBanner objects that it contains, so they will be * only valid until the MegaBannerList is deleted. * */ class MegaBannerList { public: virtual ~MegaBannerList(); /** * @brief Creates a copy of this MegaBannerList object. * * The resulting object is fully independent of the source MegaBannerList, * it contains a copy of all internal objects, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaBannerList object */ virtual MegaBannerList* copy() const; /** * @brief Returns the MegaBanner at position i in the MegaBannerList * * The MegaBannerList retains the ownership of the returned MegaBanner. It will be only valid until * the MegaBannerList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaBanner that we want to get for the list * @return MegaBanner at position i in the list */ virtual const MegaBanner* get(int i) const; /** * @brief Returns the number of MegaBanner objects in the list * @return Number of MegaBanner objects in the list */ virtual int size() const; protected: MegaBannerList(); }; /** * @brief Represents a set of properties that define discount code */ class MegaDiscountCode { public: virtual ~MegaDiscountCode(); /** * @brief Creates a copy of this MegaDiscountCode object. * * The resulting object is fully independent of the source MegaDiscountCode, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaDiscountCode object */ virtual MegaDiscountCode* copy() const; /** * @brief Returns the discount code string * * The SDK retains the ownership of the returned value. It will be valid until * the MegaDiscountCode object is deleted. */ virtual const char* getCode() const; /** * @brief Returns the item associated with the discount code */ virtual int getItem() const; /** * @brief Returns the account level associated with the discount code */ virtual int getAccountLevel() const; /** * @brief Returns number of months (1 or 12), or 0 if code applies to any number of months */ virtual int getMonths() const; /** * @brief Returns the percentage discount associated with the discount code */ virtual int getPercentageDiscount() const; /** * @brief Returns the behavior type associated with the discount code */ virtual int getBehaviorType() const; protected: MegaDiscountCode(); }; /** * @brief List of MegaDiscountCode objects * * A MegaDiscountCodeList has the ownership of the MegaDiscountCode objects that it contains, so * they will be only valid until the MegaDiscountCodeList is deleted. If you want to retain a * MegaDiscountCode returned by a MegaDiscountCodeList, use MegaDiscountCode::copy. */ class MegaDiscountCodeList { public: virtual ~MegaDiscountCodeList(); /** * @brief Creates a copy of this MegaDiscountCodeList object. * * The resulting object is fully independent of the source MegaDiscountCodeList, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaDiscountCodeList object */ virtual MegaDiscountCodeList* copy() const; /** * @brief Returns the MegaDiscountCode at position i in the MegaDiscountCodeList * * The MegaDiscountCodeList retains the ownership of the returned MegaDiscountCode. It will be * only valid until the MegaDiscountCodeList is deleted. * * If the index is >= the size of the list, this function returns nullptr. * * @param i Position of the MegaDiscountCode that we want to get for the list * @return MegaDiscountCode at position i in the list */ virtual const MegaDiscountCode* get(int i) const; /** * @brief Returns the number of MegaDiscountCode objects in the list * @return Number of MegaDiscountCode objects in the list */ virtual int size() const; protected: MegaDiscountCodeList(); }; /** * @brief Represents a set of properties that define discount code information * These are used to display discount code details to the user. */ class MegaDiscountCodeInfo: public MegaDiscountCode { public: virtual ~MegaDiscountCodeInfo(); /** * @brief Creates a copy of this MegaDiscountCodeInfo object. * * The resulting object is fully independent of the source MegaDiscountCodeInfo, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You take the ownership of returned value * * @return Copy of the MegaDiscountCodeInfo object */ virtual MegaDiscountCodeInfo* copy() const; /* * @brief Returns the expiry time associated with the discount code info (seconds since epoch) */ virtual int getExpiry() const; /* * @brief Returns the compulsory subscription associated with the discount code info * Subscription will continue after discount period */ virtual int getCompulsorySubscription() const; /* * @brief Returns the multi discount associated with the discount code info * Turn flag on for using new Multi Discount system (alters UI appearance & behaviour) */ virtual int getMultiDiscount() const; /* * @brief Returns a MegaStringIntegerMap with all feature names and it's code: {{"vpn", 1}, * {"pwm", 2} ...} * You take the ownership of returned value */ virtual MegaStringIntegerMap* getFeatures() const; /* * @brief Returns the tax value associated with the discount code info */ virtual int getTaxValue() const; /* * @brief Returns if the user is tax exempt (0% tax) */ virtual bool isTaxExempt() const; /* * @brief Returns if the tax is applied on top of the base price (PPT) */ virtual bool isTaxAppliedOnTop() const; /* * @brief Returns the tax rate associated with the discount code info */ virtual int getTaxRate() const; /* * @brief Returns the tax name associated with the discount code info * * The SDK retains the ownership of the returned value. It will be valid until * the MegaDiscountCodeInfo object is deleted. */ virtual const char* getTaxName() const; /* * @brief Returns the tax country associated with the discount code info * * The SDK retains the ownership of the returned value. It will be valid until * the MegaDiscountCodeInfo object is deleted. */ virtual const char* getTaxCountry() const; /* * @brief Returns the euro total price associated with the discount code info */ virtual double getEuroTotalPrice() const; /* * @brief Returns the euro discount amount associated with the discount code info */ virtual double getEuroDiscountAmount() const; /* * @brief Returns the euro discounted total price associated with the discount code info */ virtual double getEuroDiscountedTotalPrice() const; /* * @brief Returns the euro discounted monthly price associated with the discount code info */ virtual double getEuroDiscountedMonthlyPrice() const; /* * @brief Returns the euro total price net associated with the discount code info */ virtual double getEuroTotalPriceNet() const; /* * @brief Returns the euro discount amount net associated with the discount code info */ virtual double getEuroDiscountAmountNet() const; /* * @brief Returns the euro discounted total price net associated with the discount code info */ virtual double getEuroDiscountedTotalPriceNet() const; /* * @brief Returns the euro discounted monthly price net associated with the discount code info */ virtual double getEuroDiscountedMonthlyPriceNet() const; /* * @brief Returns the local currency code associated with the discount code info * * The SDK retains the ownership of the returned value. It will be valid until * the MegaDiscountCodeInfo object is deleted. */ virtual const char* getLocalCurrencyCode() const; /* * @brief Returns the local currency symbol associated with the discount code info * * The SDK retains the ownership of the returned value. It will be valid until * the MegaDiscountCodeInfo object is deleted. */ virtual const char* getLocalCurrencySymbol() const; /* * @brief Returns the local total price associated with the discount code info */ virtual double getLocalTotalPrice() const; /* * @brief Returns the local discount amount associated with the discount code info */ virtual double getLocalDiscountAmount() const; /* * @brief Returns the local discounted total price associated with the discount code info */ virtual double getLocalDiscountedTotalPrice() const; /* * @brief Returns the local discounted monthly price associated with the discount code info */ virtual double getLocalDiscountedMonthlyPrice() const; /* * @brief Returns the local total price net associated with the discount code info */ virtual double getLocalTotalPriceNet() const; /* * @brief Returns the local discount amount net associated with the discount code info */ virtual double getLocalDiscountAmountNet() const; /* * @brief Returns the local discounted total price net associated with the discount code info */ virtual double getLocalDiscountedTotalPriceNet() const; /* * @brief Returns the local discounted monthly price net associated with the discount code info */ virtual double getLocalDiscountedMonthlyPriceNet() const; protected: MegaDiscountCodeInfo(); }; /** * @brief Provides information about an asynchronous request * * Most functions in this API are asynchronous, except the ones that never require to * contact MEGA servers. Developers can use listeners (MegaListener, MegaRequestListener) * to track the progress of each request. MegaRequest objects are provided in callbacks sent * to these listeners and allow developers to know the state of the request, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the request * when the object is created, they are immutable. * * These objects have a high number of 'getters', but only some of them return valid values * for each type of request. Documentation of each request specify which fields are valid. * */ class MegaRequest { public: enum { TYPE_LOGIN = 0, TYPE_CREATE_FOLDER = 1, TYPE_MOVE = 2, TYPE_COPY = 3, TYPE_RENAME = 4, TYPE_REMOVE = 5, TYPE_SHARE = 6, TYPE_IMPORT_LINK = 7, TYPE_EXPORT = 8, TYPE_FETCH_NODES = 9, TYPE_ACCOUNT_DETAILS = 10, TYPE_CHANGE_PW = 11, TYPE_UPLOAD = 12, TYPE_LOGOUT = 13, TYPE_GET_PUBLIC_NODE = 14, TYPE_GET_ATTR_FILE = 15, TYPE_SET_ATTR_FILE = 16, TYPE_GET_ATTR_USER = 17, TYPE_SET_ATTR_USER = 18, TYPE_RETRY_PENDING_CONNECTIONS = 19, TYPE_REMOVE_CONTACT = 20, TYPE_CREATE_ACCOUNT = 21, TYPE_CONFIRM_ACCOUNT = 22, TYPE_QUERY_SIGNUP_LINK = 23, TYPE_ADD_SYNC = 24, TYPE_REMOVE_SYNC = 25, //TYPE_DISABLE_SYNC = 26, //TYPE_ENABLE_SYNC = 27, TYPE_COPY_SYNC_CONFIG = 28, TYPE_COPY_CACHED_STATUS = 29, TYPE_IMPORT_SYNC_CONFIGS = 30, TYPE_REMOVE_SYNCS = 31, TYPE_PAUSE_TRANSFERS = 32, TYPE_CANCEL_TRANSFER = 33, TYPE_CANCEL_TRANSFERS = 34, TYPE_DELETE = 35, TYPE_REPORT_EVENT = 36, TYPE_CANCEL_ATTR_FILE = 37, TYPE_GET_PRICING = 38, TYPE_GET_PAYMENT_ID = 39, TYPE_GET_USER_DATA = 40, TYPE_LOAD_BALANCING = 41, TYPE_KILL_SESSION = 42, TYPE_SUBMIT_PURCHASE_RECEIPT = 43, TYPE_CREDIT_CARD_STORE = 44, TYPE_UPGRADE_ACCOUNT = 45, TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS = 46, TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS = 47, TYPE_GET_SESSION_TRANSFER_URL = 48, TYPE_GET_PAYMENT_METHODS = 49, TYPE_INVITE_CONTACT = 50, TYPE_REPLY_CONTACT_REQUEST = 51, TYPE_SUBMIT_FEEDBACK = 52, TYPE_SEND_EVENT = 53, TYPE_CLEAN_RUBBISH_BIN = 54, TYPE_SET_ATTR_NODE = 55, TYPE_CHAT_CREATE = 56, TYPE_CHAT_FETCH = 57, TYPE_CHAT_INVITE = 58, TYPE_CHAT_REMOVE = 59, TYPE_CHAT_URL = 60, TYPE_CHAT_GRANT_ACCESS = 61, TYPE_CHAT_REMOVE_ACCESS = 62, // TYPE_USE_HTTPS_ONLY = 63, (obsolete) TYPE_SET_PROXY = 64, TYPE_GET_RECOVERY_LINK = 65, TYPE_QUERY_RECOVERY_LINK = 66, TYPE_CONFIRM_RECOVERY_LINK = 67, TYPE_GET_CANCEL_LINK = 68, TYPE_CONFIRM_CANCEL_LINK = 69, TYPE_GET_CHANGE_EMAIL_LINK = 70, TYPE_CONFIRM_CHANGE_EMAIL_LINK = 71, TYPE_CHAT_UPDATE_PERMISSIONS = 72, TYPE_CHAT_TRUNCATE = 73, TYPE_CHAT_SET_TITLE = 74, TYPE_SET_MAX_CONNECTIONS = 75, TYPE_PAUSE_TRANSFER = 76, TYPE_MOVE_TRANSFER = 77, TYPE_CHAT_PRESENCE_URL = 78, TYPE_REGISTER_PUSH_NOTIFICATION = 79, TYPE_GET_USER_EMAIL = 80, TYPE_APP_VERSION = 81, TYPE_GET_LOCAL_SSL_CERT = 82, TYPE_SEND_SIGNUP_LINK = 83, TYPE_QUERY_DNS = 84, TYPE_QUERY_GELB = 85, // (obsolete) TYPE_CHAT_STATS = 86, TYPE_DOWNLOAD_FILE = 87, TYPE_QUERY_TRANSFER_QUOTA = 88, TYPE_PASSWORD_LINK = 89, TYPE_GET_ACHIEVEMENTS = 90, TYPE_RESTORE = 91, TYPE_REMOVE_VERSIONS = 92, TYPE_CHAT_ARCHIVE = 93, TYPE_WHY_AM_I_BLOCKED = 94, TYPE_CONTACT_LINK_CREATE = 95, TYPE_CONTACT_LINK_QUERY = 96, TYPE_CONTACT_LINK_DELETE = 97, TYPE_FOLDER_INFO = 98, TYPE_RICH_LINK = 99, TYPE_KEEP_ME_ALIVE = 100, TYPE_MULTI_FACTOR_AUTH_CHECK = 101, TYPE_MULTI_FACTOR_AUTH_GET = 102, TYPE_MULTI_FACTOR_AUTH_SET = 103, TYPE_ADD_SCHEDULED_COPY = 104, TYPE_REMOVE_SCHEDULED_COPY = 105, TYPE_TIMER = 106, TYPE_ABORT_CURRENT_SCHEDULED_COPY = 107, TYPE_GET_PSA = 108, TYPE_FETCH_TIMEZONE = 109, TYPE_USERALERT_ACKNOWLEDGE = 110, TYPE_CHAT_LINK_HANDLE = 111, TYPE_CHAT_LINK_URL = 112, TYPE_SET_PRIVATE_MODE = 113, TYPE_AUTOJOIN_PUBLIC_CHAT = 114, TYPE_CATCHUP = 115, TYPE_PUBLIC_LINK_INFORMATION = 116, TYPE_GET_BACKGROUND_UPLOAD_URL = 117, TYPE_COMPLETE_BACKGROUND_UPLOAD = 118, TYPE_GET_CLOUD_STORAGE_USED = 119, TYPE_SEND_SMS_VERIFICATIONCODE = 120, // (deprecated / obsolete) TYPE_CHECK_SMS_VERIFICATIONCODE = 121, // (deprecated / obsolete) // TYPE_GET_REGISTERED_CONTACTS = 122, (obsolete) TYPE_GET_COUNTRY_CALLING_CODES = 123, // (deprecated / obsolete) TYPE_VERIFY_CREDENTIALS = 124, TYPE_GET_MISC_FLAGS = 125, TYPE_RESEND_VERIFICATION_EMAIL = 126, TYPE_SUPPORT_TICKET = 127, TYPE_SET_RETENTION_TIME = 128, TYPE_RESET_SMS_VERIFIED_NUMBER = 129, // (deprecated / obsolete) TYPE_SEND_DEV_COMMAND = 130, TYPE_GET_BANNERS = 131, TYPE_DISMISS_BANNER = 132, TYPE_BACKUP_PUT = 133, TYPE_BACKUP_REMOVE = 134, TYPE_BACKUP_PUT_HEART_BEAT = 135, TYPE_FETCH_ADS = 136, TYPE_QUERY_ADS = 137, TYPE_GET_ATTR_NODE = 138, TYPE_LOAD_EXTERNAL_DRIVE_BACKUPS = 139, TYPE_CLOSE_EXTERNAL_DRIVE_BACKUPS = 140, TYPE_GET_DOWNLOAD_URLS = 141, TYPE_START_CHAT_CALL = 142, TYPE_JOIN_CHAT_CALL = 143, TYPE_END_CHAT_CALL = 144, TYPE_GET_FA_UPLOAD_URL = 145, TYPE_EXECUTE_ON_THREAD = 146, TYPE_SET_CHAT_OPTIONS = 147, TYPE_GET_RECENT_ACTIONS = 148, TYPE_CHECK_RECOVERY_KEY = 149, TYPE_SET_MY_BACKUPS = 150, TYPE_PUT_SET = 151, TYPE_REMOVE_SET = 152, TYPE_FETCH_SET = 153, TYPE_PUT_SET_ELEMENT = 154, TYPE_REMOVE_SET_ELEMENT = 155, TYPE_REMOVE_OLD_BACKUP_NODES = 156, TYPE_SET_SYNC_RUNSTATE = 157, TYPE_ADD_UPDATE_SCHEDULED_MEETING = 158, TYPE_DEL_SCHEDULED_MEETING = 159, TYPE_FETCH_SCHEDULED_MEETING = 160, TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES = 161, TYPE_OPEN_SHARE_DIALOG = 162, TYPE_UPGRADE_SECURITY = 163, TYPE_PUT_SET_ELEMENTS = 164, TYPE_REMOVE_SET_ELEMENTS = 165, TYPE_EXPORT_SET = 166, TYPE_GET_EXPORTED_SET_ELEMENT = 167, TYPE_GET_RECOMMENDED_PRO_PLAN = 168, TYPE_BACKUP_INFO = 169, TYPE_BACKUP_REMOVE_MD = 170, TYPE_AB_TEST_ACTIVE = 171, TYPE_GET_VPN_REGIONS = 172, TYPE_GET_VPN_CREDENTIALS = 173, TYPE_PUT_VPN_CREDENTIAL = 174, TYPE_DEL_VPN_CREDENTIAL = 175, TYPE_CHECK_VPN_CREDENTIAL = 176, TYPE_GET_SYNC_STALL_LIST = 177, TYPE_FETCH_CREDIT_CARD_INFO = 178, TYPE_MOVE_TO_DEBRIS = 179, TYPE_RING_INDIVIDUAL_IN_CALL = 180, TYPE_CREATE_NODE_TREE = 181, TYPE_CREATE_PASSWORD_MANAGER_BASE = 182, TYPE_CREATE_PASSWORD_NODE = 183, TYPE_UPDATE_PASSWORD_NODE = 184, TYPE_GET_NOTIFICATIONS = 185, TYPE_TAG_NODE = 186, TYPE_ADD_MOUNT = 187, TYPE_DISABLE_MOUNT = 188, TYPE_ENABLE_MOUNT = 189, TYPE_REMOVE_MOUNT = 190, TYPE_SET_MOUNT_FLAGS = 191, TYPE_DEL_ATTR_USER = 192, TYPE_BACKUP_PAUSE_MD = 194, TYPE_BACKUP_RESUME_MD = 195, TYPE_IMPORT_PASSWORDS_FROM_FILE = 196, TYPE_GET_ACTIVE_SURVEY_TRIGGER_ACTIONS = 197, TYPE_GET_SURVEY = 198, TYPE_ANSWER_SURVEY = 199, TYPE_CHANGE_SYNC_ROOT = 200, TYPE_GET_MY_IP = 201, TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES = 202, TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES = 203, TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS = 204, TYPE_CHECK_SYNC_UPLOAD_THROTTLED_ELEMENTS = 205, TYPE_RUN_NETWORK_CONNECTIVITY_TEST = 206, TYPE_ADD_SYNC_PREVALIDATION = 207, TYPE_GET_MAX_CONNECTIONS = 208, TYPE_GET_SUBSCRIPTION_CANCELLATION_DETAILS = 209, TYPE_GET_DISCOUNT_CODE_INFORMATION = 210, TYPE_GET_RECENT_ACTION_BY_ID = 211, TOTAL_OF_REQUEST_TYPES = 212, }; virtual ~MegaRequest(); /** * @brief Creates a copy of this MegaRequest object * * The resulting object is fully independent of the source MegaRequest, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaRequest object */ virtual MegaRequest *copy(); /** * @brief Returns the type of request associated with the object * @return Type of request associated with the object */ virtual int getType() const; /** * @brief Returns a readable string that shows the type of request * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable string showing the type of request */ virtual const char *getRequestString() const; /** * @brief Returns a readable string that shows the type of request * * This function provides exactly the same result as MegaRequest::getRequestString. * It's provided for a better Java compatibility * * @return Readable string showing the type of request */ virtual const char* toString() const; /** * @brief Returns the handle of a node related to the request * * This value is valid for these requests: * - MegaApi::moveNode - Returns the handle of the node to move * - MegaApi::copyNode - Returns the handle of the node to copy * - MegaApi::renameNode - Returns the handle of the node to rename * - MegaApi::remove - Returns the handle of the node to remove * - MegaApi::sendFileToUser - Returns the handle of the node to send * - MegaApi::share - Returns the handle of the folder to share * - MegaApi::getThumbnail - Returns the handle of the node to get the thumbnail * - MegaApi::getPreview - Return the handle of the node to get the preview * - MegaApi::cancelGetThumbnail - Return the handle of the node * - MegaApi::cancelGetPreview - Returns the handle of the node * - MegaApi::setThumbnail - Returns the handle of the node * - MegaApi::setPreview - Returns the handle of the node * - MegaApi::exportNode - Returns the handle of the node * - MegaApi::disableExport - Returns the handle of the node * - MegaApi::getPaymentId - Returns the handle of the product * - MegaApi::syncFolder - Returns the handle of the folder in MEGA * - MegaApi::removeSync - Returns the handle of the folder in MEGA * - MegaApi::upgradeAccount - Returns that handle of the product * - MegaApi::replyContactRequest - Returns the handle of the contact request * - MegaApi::inviteToChat - Returns the handle of the chat * - MegaApi::removeFromChat - Returns the handle of the chat * - MegaApi::getUrlChat - Returns the handle of the chat * - MegaApi::grantAccessInChat - Returns the handle of the node * - MegaApi::removeAccessInChat - Returns the handle of the node * - MegaApi::setScheduledCopy - Returns the target node of the backup * - MegaApi::updateBackup - Returns the target node of the backup * - MegaApi::sendBackupHeartbeat - Returns the last node backed up * - MegaApi::getChatLinkURL - Returns the public handle * - MegaApi::sendChatLogs - Returns the user handle * - MegaApi::fetchAds - Returns public handle that the user is visiting (optionally) * - MegaApi::queryAds - Returns public handle that the user is visiting (optionally) * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::createFolder - Returns the handle of the new folder * - MegaApi::copyNode - Returns the handle of the new node * - MegaApi::importFileLink - Returns the handle of the new node * - MegaApi::getPasswordManagerBase - Returns the handle of the base folder node * * @return Handle of a node related to the request */ virtual MegaHandle getNodeHandle() const; /** * @brief Returns a link related to the request * * This value is valid for these requests: * - MegaApi::querySignupLink - Returns the confirmation link * - MegaApi::confirmAccount - Returns the confirmation link * - MegaApi::loginToFolder - Returns the link to the folder * - MegaApi::importFileLink - Returns the link to the file to import * - MegaApi::getPublicNode - Returns the link to the file * - MegaApi::copySyncDataToCache - Returns the path of the remote folder * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::exportNode - Returns the public link * - MegaApi::getPaymentId - Returns the payment identifier * - MegaApi::getUrlChat - Returns the user-specific URL for the chat * - MegaApi::getChatPresenceURL - Returns the user-specific URL for the chat presence server * - MegaApi::getUploadURL - Returns the upload IPv4 * - MegaApi::getThumbnailUploadURL - Returns the upload IPv4 * - MegaApi::getPreviewUploadURL - Returns the upload IPv4 * - MegaApi::getDownloadUrl - Returns semicolon-separated IPv4 of the server in the URL(s) * - MegaApi::exportSet - Returns the public link * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Link related to the request */ virtual const char* getLink() const; /** * @brief Returns the handle of a parent node related to the request * * This value is valid for these requests: * - MegaApi::createFolder - Returns the handle of the parent folder * - MegaApi::moveNode - Returns the handle of the new parent for the node * - MegaApi::copyNode - Returns the handle of the parent for the new node * - MegaApi::importFileLink - Returns the handle of the node that receives the imported * file * - MegaApi::inviteToChat - Returns the handle of the user to be invited * - MegaApi::removeFromChat - Returns the handle of the user to be removed * - MegaApi::grantAccessInchat - Returns the chat identifier * - MegaApi::removeAccessInchat - Returns the chat identifier * - MegaApi::removeBackup - Returns the backupId * - MegaApi::updateBackup - Returns the backupId * - MegaApi::sendBackupHeartbeat - Returns the backupId * - MegaApi::syncFolder - Returns the backupId asociated with the sync * - MegaApi::copySyncDataToCache - Returns the backupId asociated with the sync * - MegaApi::getChatLinkURL - Returns the chatid * - MegaApi::sendChatLogs - Returns the callid (if exits) * - MegaApi::createPasswordNode - Returns parent folder for the new Password Node * - MegaApi::importPasswordsFromFile - Returns parent folder for the imported Password Node * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::syncFolder - Returns a fingerprint of the local folder * * @return Handle of a parent node related to the request */ virtual MegaHandle getParentHandle() const; /** * @brief Returns a session key related to the request * * This value is valid for these requests: * - MegaApi::fastLogin - Returns session key used to access the account * - MegaApi::sendEvent - Returns the ViewID key used to track the view * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Session key related to the request */ virtual const char* getSessionKey() const; /** * @brief Returns a name related to the request * * This value is valid for these requests: * - MegaApi::createAccount - Returns the name or the firstname of the user * - MegaApi::createFolder - Returns the name of the new folder * - MegaApi::renameNode - Returns the new name for the node * - MegaApi::syncFolder - Returns the name for the sync * - MegaApi::copySyncDataToCache - Returns the name for the sync * - MegaApi::setScheduledCopy - Returns the device id hash of the backup source device * - MegaApi::updateBackup - Returns the device id hash of the backup source device * - MegaApi::getUploadURL - Returns the upload URL * - MegaApi::getThumbnailUploadURL - Returns the upload URL * - MegaApi::getPreviewUploadURL - Returns the upload URL * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::querySignupLink - Returns the name of the user * - MegaApi::confirmAccount - Returns the name of the user * - MegaApi::getUserData - Returns the name of the user * - MegaApi::getDownloadUrl - Returns semicolon-separated download URL(s) to the file * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Name related to the request */ virtual const char* getName() const; /** * @brief Returns an email related to the request * * This value is valid for these requests: * - MegaApi::login - Returns the email of the account * - MegaApi::fastLogin - Returns the email of the account * - MegaApi::loginToFolder - Returns the string "FOLDER" * - MegaApi::createAccount - Returns the email for the account * - MegaApi::sendFileToUser - Returns the email of the user that receives the node * - MegaApi::share - Returns the email that receives the shared folder * - MegaApi::getUserAvatar - Returns the email of the user to get the avatar * - MegaApi::removeContact - Returns the email of the contact * - MegaApi::getUserData - Returns the email of the contact * - MegaApi::inviteContact - Returns the email of the contact * - MegaApi::grantAccessInChat -Returns the MegaHandle of the user in Base64 enconding * - MegaApi::removeAccessInChat -Returns the MegaHandle of the user in Base64 enconding * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::querySignupLink - Returns the email of the account * - MegaApi::confirmAccount - Returns the email of the account * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Email related to the request */ virtual const char* getEmail() const; /** * @brief Returns a password related to the request * * This value is valid for these requests: * - MegaApi::login - Returns the password of the account * - MegaApi::loginToFolder - Returns the authentication key to write in public folder * - MegaApi::changePassword - Returns the old password of the account (first parameter) * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getUserData - Returns the public RSA key of the contact, Base64-encoded * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Password related to the request */ virtual const char* getPassword() const; /** * @brief Returns a new password related to the request * * This value is valid for these requests: * - MegaApi::changePassword - Returns the new password for the account * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return New password related to the request */ virtual const char* getNewPassword() const; /** * @brief Returns a private key related to the request * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getUserData - Returns the private RSA key of the account, Base64-encoded * @return Private key related to the request */ virtual const char* getPrivateKey() const; /** * @brief Returns an access level related to the request * * This value is valid for these requests: * - MegaApi::share - Returns the access level for the shared folder * - MegaApi::exportNode - Returns true * - MegaApi::disableExport - Returns false * - MegaApi::inviteToChat - Returns the privilege level wanted for the user * - MegaApi::setScheduledCopy - Returns the backup state * - MegaApi::updateBackup - Returns the backup state * - MegaApi::sendBackupHeartbeat - Returns the backup state * * @return Access level related to the request */ virtual int getAccess() const; /** * @brief Returns the path of a file related to the request * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::getThumbnail - Returns the destination path for the thumbnail * - MegaApi::getPreview - Returns the destination path for the preview * - MegaApi::getUserAvatar - Returns the destination path for the avatar * - MegaApi::setThumbnail - Returns the source path for the thumbnail * - MegaApi::setPreview - Returns the source path for the preview * - MegaApi::setAvatar - Returns the source path for the avatar * - MegaApi::syncFolder - Returns the path of the local folder * - MegaApi::setScheduledCopy - Returns the path of the local folder * - MegaApi::updateBackup - Returns the path of the local folder * * @return Path of a file related to the request */ virtual const char* getFile() const; /** * @brief Return the number of times that a request has temporarily failed * @return Number of times that a request has temporarily failed * This value is valid for these requests: * - MegaApi::setScheduledCopy - Returns the maximun number of backups to keep */ virtual int getNumRetry() const; /** * @brief Returns a public node related to the request * * This value is valid for these requests: * - MegaApi::copyNode - Returns the node to copy (if it is a public node) * - MegaApi::getPreviewElementNode * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getPublicNode - Returns the public node * * You take ownership of the returned value. * * @return Public node related to the request */ virtual MegaNode *getPublicMegaNode() const; /** * @brief Returns the type of parameter related to the request * * This value is valid for these requests: * - MegaApi::getThumbnail - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaApi::getPreview - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaApi::cancelGetThumbnail - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaApi::cancelGetPreview - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaApi::setThumbnail - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaApi::setPreview - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaApi::reportDebugEvent - Returns MegaApi::EVENT_DEBUG * - MegaApi::cancelTransfers - Returns MegaTransfer::TYPE_DOWNLOAD if downloads are * cancelled or MegaTransfer::TYPE_UPLOAD if uploads are cancelled * - MegaApi::setUserAttribute - Returns the attribute type * - MegaApi::getUserAttribute - Returns the attribute type * - MegaApi::setMaxConnections - Returns the direction of transfers * - MegaApi::dismissBanner - Returns the id of the banner * - MegaApi::sendBackupHeartbeat - Returns the number of backup files uploaded * - MegaApi::getRecentActions - Returns the maximum number of nodes * - MegaApi::getRecentActionsAsync - Returns the maximum number of nodes * - MegaApi::importPasswordsFromFile - Returns source of the file provided as an argument * * @return Type of parameter related to the request */ virtual int getParamType() const; /** * @brief Returns a text relative to this request * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::submitFeedback - Returns the comment about the app * - MegaApi::reportDebugEvent - Returns the debug message * - MegaApi::setUserAttribute - Returns the new value for the attribute * - MegaApi::inviteContact - Returns the message appended to the contact invitation * - MegaApi::sendEvent - Returns the event message * - MegaApi::createAccount - Returns the lastname for the new account * - MegaApi::setScheduledCopy - Returns the cron like time string to define period * - MegaApi::getUploadURL - Returns the upload IPv6 * - MegaApi::getThumbnailUploadURL - Returns the upload IPv6 * - MegaApi::getPreviewUploadURL - Returns the upload IPv6 * - MegaApi::getDownloadUrl - Returns semicolon-separated IPv6 of the server in the URL(s) * - MegaApi::startChatCall - Returns the url sfu * - MegaApi::joinChatCall - Returns the url sfu * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getUserAttribute - Returns the value of the attribute * * @return Text relative to this request */ virtual const char *getText() const; /** * @brief Returns a number related to this request * * This value is valid for these requests: * - MegaApi::retryPendingConnections - Returns if transfers are retried * - MegaApi::submitFeedback - Returns the rating for the app * - MegaApi::pauseTransfers - Returns the direction of the transfers to pause/resume * - MegaApi::upgradeAccount - Returns the payment method * - MegaApi::replyContactRequest - Returns the action to do with the contact request * - MegaApi::inviteContact - Returns the action to do with the contact request * - MegaApi::sendEvent - Returns the event type * - MegaApi::moveTransferUp - Returns MegaTransfer::MOVE_TYPE_UP * - MegaApi::moveTransferUpByTag - Returns MegaTransfer::MOVE_TYPE_UP * - MegaApi::moveTransferDown - Returns MegaTransfer::MOVE_TYPE_DOWN * - MegaApi::moveTransferDownByTag - Returns MegaTransfer::MOVE_TYPE_DOWN * - MegaApi::moveTransferToFirst - Returns MegaTransfer::MOVE_TYPE_TOP * - MegaApi::moveTransferToFirstByTag - Returns MegaTransfer::MOVE_TYPE_TOP * - MegaApi::moveTransferToLast - Returns MegaTransfer::MOVE_TYPE_BOTTOM * - MegaApi::moveTransferToLastByTag - Returns MegaTransfer::MOVE_TYPE_BOTTOM * - MegaApi::moveTransferBefore - Returns the tag of the transfer with the target position * - MegaApi::moveTransferBeforeByTag - Returns the tag of the transfer with the target * position * - MegaApi::setScheduledCopy - Returns the period between backups in deciseconds (-1 if * cron time used) * - MegaApi::abortCurrentScheduledCopy - Returns the tag of the aborted backup * - MegaApi::removeScheduledCopy - Returns the tag of the deleted backup * - MegaApi::startTimer - Returns the selected period * - MegaApi::sendChatStats - Returns the connection port * - MegaApi::dismissBanner - Returns the timestamp of the request * - MegaApi::sendBackupHeartbeat - Returns the time associated with the request * - MegaApi::createPublicChat - Returns if chat room is a meeting room * - MegaApi::fetchAds - Returns a bitmap flag used to communicate with the API * - MegaApi::queryAds - Returns a bitmap flag used to communicate with the API * - MegaApi::clearRecentActionHistory - Returns the epoch time in seconds to set as the * recent action history clear timestamp * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::creditCardQuerySubscriptions - Returns the number of credit card subscriptions * - MegaApi::getPaymentMethods - Returns a bitfield with the available payment methods * - MegaApi::getCloudStorageUsed - Returns the sum of the sizes of file cloud nodes. * - MegaApi::getRecentActions - Returns the number of days since nodes will be considerated * - MegaApi::getRecentActionsAsync - Returns the number of days since nodes will be * considered * * @return Number related to this request */ virtual long long getNumber() const; /** * @brief Returns a flag related to the request * * This value is valid for these requests: * - MegaApi::retryPendingConnections - Returns if request are disconnected * - MegaApi::pauseTransfers - Returns true if transfers were paused, false if they were * resumed * - MegaApi::createChat - Creates a chat for one or more participants * - MegaApi::exportNode - Makes the folder writable * - MegaApi::fetchnodes - Return true if logged in into a folder and the provided key is * invalid. * - MegaApi::getPublicNode - Return true if the provided key along the link is invalid. * - MegaApi::pauseTransfer - Returns true if the transfer has to be pause or false if it * has to be resumed * - MegaApi::pauseTransferByTag - Returns true if the transfer has to be pause or false if * it has to be resumed * - MegaApi::moveTransferUp - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferUpByTag - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferDown - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferDownByTag - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferToFirst - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferToFirstByTag - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferToLast - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferToLastByTag - Returns true (it means that it's an automatic move) * - MegaApi::moveTransferBefore - Returns false (it means that it's a manual move) * - MegaApi::moveTransferBeforeByTag - Returns false (it means that it's a manual move) * - MegaApi::setBackup - Returns if backups that should have happen in the past should be * taken care of * - MegaApi::getChatLinkURL - Returns a vector with one element (callid), if call doesn't * exit it will be NULL * - MegaApi::setScheduledCopy - Returns if backups that should have happen in the past * should be taken care of * - MegaApi::sendEvent - Returns true if the JourneyID should be tracked * - MegaApi::getVisibleWelcomeDialog - Returns true if the Welcome dialog is visible * - MegaApi::getVisibleTermsOfService - Returns true if the Terms of Service need to be * displayed * - MegaApi::getRecentActionsAsync - Returns true if exclude sensitives * - MegaApi::getRecentActionById - Returns true if exclude sensitives * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::queryTransferQuota - True if it is expected to get an overquota error, * otherwise false * * @return Flag related to the request */ virtual bool getFlag() const; /** * @brief Returns the number of transferred bytes during the request * @return Number of transferred bytes during the request */ virtual long long getTransferredBytes() const; /** * @brief Returns the number of bytes that the SDK will have to transfer to finish the request * * In addition, this value is also valid for these requests: * - MegaApi::setScheduledCopy - Returns the backup type * - MegaApi::updateBackup - Returns the backup type * * @return Number of bytes that the SDK will have to transfer to finish the request */ virtual long long getTotalBytes() const; /** * @brief Return the MegaRequestListener associated with this request * * This function will return NULL if there isn't an associated request listener. * * @return MegaRequestListener associated with this request */ virtual MegaRequestListener *getListener() const; /** * @brief Returns details related to the MEGA account * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getAccountDetails - Details of the MEGA account * * You take the ownership of the returned value. * * @return Details related to the MEGA account */ virtual MegaAccountDetails *getMegaAccountDetails() const; /** * @brief Returns available pricing plans to upgrade a MEGA account * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getPricing - Returns the available pricing plans * * You take the ownership of the returned value. * * @return Available pricing plans to upgrade a MEGA account */ virtual MegaPricing *getPricing() const; /** * @brief Returns currency data related to prices * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getPricing - Returns available pricing plans to upgrade a MEGA account * * You take the ownership of the returned value. * * @return Currency data related to prices */ virtual MegaCurrency *getCurrency() const; /** * @brief Returns details related to the MEGA Achievements of this account * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getMegaAchievements - Details of the MEGA Achievements of this account * * You take the ownership of the returned value. * * @return Details related to the MEGA Achievements of this account */ virtual MegaAchievementsDetails *getMegaAchievementsDetails() const; /** * @brief Get details about timezones and the current default * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::fetchTimeZone - Details about timezones and the current default * * In any other case, this function returns NULL. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Details about timezones and the current default */ virtual MegaTimeZoneDetails *getMegaTimeZoneDetails() const; /** * @brief Returns the tag of a transfer related to the request * * This value is valid for these requests: * - MegaApi::cancelTransfer - Returns the tag of the cancelled transfer (MegaTransfer::getTag) * - MegaApi::pauseTransfer - Returns the tag of the request to pause or resume * - MegaApi::pauseTransferByTag - Returns the tag of the request to pause or resume * - MegaApi::moveTransferUp - Returns the tag of the transfer to move * - MegaApi::moveTransferUpByTag - Returns the tag of the transfer to move * - MegaApi::moveTransferDown - Returns the tag of the transfer to move * - MegaApi::moveTransferDownByTag - Returns the tag of the transfer to move * - MegaApi::moveTransferToFirst - Returns the tag of the transfer to move * - MegaApi::moveTransferToFirstByTag - Returns the tag of the transfer to move * - MegaApi::moveTransferToLast - Returns the tag of the transfer to move * - MegaApi::moveTransferToLastByTag - Returns the tag of the transfer to move * - MegaApi::moveTransferBefore - Returns the tag of the transfer to move * - MegaApi::moveTransferBeforeByTag - Returns the tag of the transfer to move * - MegaApi::setScheduledCopy - Returns the tag asociated with the backup * - MegaApi::sendBackupHeartbeat - Returns the number of backup files downloaded * * @return Tag of a transfer related to the request */ virtual int getTransferTag() const; /** * @brief Returns the number of details related to this request * * This value is valid for these requests: * - MegaApi::getAccountDetails * - MegaApi::getSpecificAccountDetails * - MegaApi::getExtendedAccountDetails * - MegaApi::disableSync * - MegaApi::removeSync * - MegaApi::enableSync * - MegaApi::syncFolder * - MegaApi::setScheduledCopy * - MegaApi::updateBackup * - MegaApi::sendBackupHeartbeat * * @return Number of details related to this request */ virtual int getNumDetails() const; /** * @brief Returns the tag that identifies this request * * The tag is unique for the MegaApi object that has generated it only * * @return Unique tag that identifies this request */ virtual int getTag() const; #ifdef ENABLE_CHAT /** * @brief Returns the list of peers in a chat. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::createChat - Returns the list of peers and their privilege level * * @return List of peers of a chat. Can be \c nullptr */ virtual MegaTextChatPeerList *getMegaTextChatPeerList() const; /** * @brief Returns the list of chats. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::createChat - Returns the new chat's information * * @return List of chats */ virtual MegaTextChatList *getMegaTextChatList() const; #endif /** * @brief Returns the string map * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getUserAttribute - Returns the attribute value * - MegaApi::fetchAds - Returns ads * * @return String map including the key-value pairs of the attribute */ virtual MegaStringMap* getMegaStringMap() const; /** * @brief Returns the string list map * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return String list map */ virtual MegaStringListMap* getMegaStringListMap() const; /** * @brief Returns the string table * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return String table */ virtual MegaStringTable* getMegaStringTable() const; /** * @brief Returns information about the contents of a folder * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getFolderInfo - Returns the information related to the folder * * @return Object with information about the contents of a folder */ virtual MegaFolderInfo *getMegaFolderInfo() const; /** * @brief Returns settings for push notifications * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getPushNotificationSettings - Returns settings for push notifications * * @return Object with settings for push notifications */ virtual const MegaPushNotificationSettings *getMegaPushNotificationSettings() const; /** * @brief Returns information about background uploads (used in iOS) * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for requests relating to background uploads. The returned * pointer is to the relevant background upload object. * * @return Object with information about the contents of a folder */ virtual MegaBackgroundMediaUpload* getMegaBackgroundMediaUploadPtr() const; /** * @brief Returns the list of all Smart Banners available for current user * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaApi::getBanners - Requests all Smart Banners available for current user * * @return List of all Smart Banners available for current user */ virtual MegaBannerList* getMegaBannerList() const; /** * @brief Returns the string list * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::fetchAds - A list of the adslot ids to fetch * * @return String list */ virtual MegaStringList* getMegaStringList() const; /** * @brief Returns a map with string as key and integer as value * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::importPasswordsFromFile - A map with problematic content as key and error code * as value * * @return Map with string as key and integer as value */ virtual MegaStringIntegerMap* getMegaStringIntegerMap() const; #ifdef ENABLE_CHAT /** * @brief Returns the scheduled meeting list * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return scheduled meeting list */ virtual MegaScheduledMeetingList* getMegaScheduledMeetingList() const; #endif /** * @brief Returns the MegaHandle list * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::getFavourites - A list of MegaHandle objects * - MegaApi::getChatLinkURL - Returns a vector with the callid (if call exits in other case it will be NULL) * * @return MegaHandle list */ virtual MegaHandleList* getMegaHandleList() const; /** * @brief Returns the recent actions bucket list * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::getRecentActionsAsync * * @return MegaRecentActionBucketList list */ virtual MegaRecentActionBucketList *getRecentActions() const; /** * @brief Returns a list of integers associated with this request * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::createSetElements -> error codes for all requested Elements * * @return list of integers associated with this request (can be null) */ virtual const MegaIntegerList* getMegaIntegerList() const; /** * @brief Returns a MegaSet explicitly fetched from online API (typically using 'aft' command) * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * This value is valid for these requests: * - MegaApi::fetchSet * * @return requested MegaSet or null if not found */ virtual MegaSet* getMegaSet() const; /** * @brief Returns the list of elements, part of the MegaSet explicitly fetched from online API (typically using 'aft' command) * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::fetchSet * * @return list of elements in the requested MegaSet, or null if Set not found */ virtual MegaSetElementList* getMegaSetElementList() const; /** * @brief Returns the list of all backups * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid for these requests: * - MegaApi::getBackupInfo * * @return list of all backups */ virtual MegaBackupInfoList* getMegaBackupInfoList() const; #ifdef ENABLE_SYNC /** * @brief * Returns a reference to this request's MegaSyncStallList instance. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid only for the following requests: * - MegaApi::getMegaSyncStallList * * @return * A reference to this request's MegaSyncStallList instance. */ virtual MegaSyncStallList* getMegaSyncStallList() const; /** * @brief * Returns a reference to this request's MegaSyncStallMap instance. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * This value is valid only for the following requests: * - MegaApi::getMegaSyncStallMap * * @return * A reference to this request's getMegaSyncStallMap instance. */ virtual MegaSyncStallMap* getMegaSyncStallMap() const; #endif // ENABLE_SYNC /** * @brief Provide all available VPN Regions, including their details. * * The data included for each Region is the following: * - Name (example: hMLKTUojS6o, 1MvzBCx1Uf4) * - Country Code (example: ES, LU) * - Country Name (example: Spain, Luxembourg) * - Region Name (optional) (example: Esch-sur-Alzette) * - Town Name (Optional) (example: Bettembourg) * - Map of {ClusterID, Cluster}. * - For each Cluster: * · Host. * · DNS IP list (as a MegaStringList). * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return not-null instance with available VPN Regions, if the relevant request was sent; * nullptr otherwise. */ virtual MegaVpnRegionList* getMegaVpnRegionsDetailed() const; /** * @brief Returns the VPN credentials registered by the user. * * Important consideration (as indicated on MegaApi::getVpnCredentials): * These credentials do NOT contain the User Private Key. * The credentials that include the User Private Key are generated by * MegaApi::putVpnCredential and cannot be retrieved afterwards. * * The data stored in MegaVpnCredentials is the following: * - List of occupied SlotIDs. * - For each SlotID: * · ClusterID. * · IPv4. * · IPv6. * . DeviceID. This value can be empty if there is no associated device ID. * The current device ID can be retrieved via MegaApi::getDeviceId * - For each ClusterID: * · Cluster Public Key. * - List of VPN regions (as a MegaStringList). * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return MegaVpnCredentials* if there are any VPN credentials for the user, nullptr otherwise. */ virtual MegaVpnCredentials* getMegaVpnCredentials() const; /** * @brief Returns the results of a network connectivity test. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return Valid pointer if this request was used for running a nework connectivity test, * nullptr otherwise. */ virtual MegaNetworkConnectivityTestResults* getMegaNetworkConnectivityTestResults() const; /** * @brief Get list of available notifications for Notification Center * * This value is valid only for the following requests: * - MegaApi::getNotifications * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return non-null pointer if a valid MegaApi functionality has been called, nullptr otherwise. */ virtual const MegaNotificationList* getMegaNotifications() const; /** * @brief Get node tree after its creation * * This value is valid only for the following requests: * - MegaApi::createNodeTree * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return non-null pointer if a valid MegaApi functionality has been called, null otherwise. */ virtual const MegaNodeTree* getMegaNodeTree() const; virtual const MegaCancelSubscriptionReasonList* getMegaCancelSubscriptionReasons() const; /** * @brief Get list of discount codes available for current user * * This value is valid only for the following requests: * - MegaApi::getDiscountCodes * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return non-null pointer if a valid MegaApi functionality has been called, null * otherwise. */ virtual MegaDiscountCodeList* getMegaDiscountCodeList() const; /** * @brief Get information about a discount code * * This value is valid only for the following requests: * - MegaApi::getDiscountCodeInformation * * The SDK retains the ownership of the returned value. It will be valid until * the MegaRequest object is deleted. * * @return non-null pointer if a valid MegaApi functionality has been called, null * otherwise. */ virtual const MegaDiscountCodeInfo* getMegaDiscountCodeInfo() const; }; /** * @brief Provides information about an event * * Objects of this class aren't live, they are snapshots of the state of the event * when the object is created, they are immutable. */ class MegaEvent { public: enum { EVENT_COMMIT_DB = 0, EVENT_ACCOUNT_CONFIRMATION = 1, // EVENT_CHANGE_TO_HTTPS = 2, (obsolete) EVENT_DISCONNECT = 3, EVENT_ACCOUNT_BLOCKED = 4, EVENT_STORAGE = 5, EVENT_NODES_CURRENT = 6, EVENT_MEDIA_INFO_READY = 7, EVENT_STORAGE_SUM_CHANGED = 8, EVENT_BUSINESS_STATUS = 9, EVENT_KEY_MODIFIED = 10, EVENT_MISC_FLAGS_READY = 11, #ifdef ENABLE_SYNC // EVENT_FIRST_SYNC_RESUMING = 12, // (obsolete) EVENT_SYNCS_DISABLED = 13, EVENT_SYNCS_RESTORED = 14, #endif EVENT_REQSTAT_PROGRESS = 15, EVENT_RELOADING = 16, EVENT_FATAL_ERROR = 17, EVENT_UPGRADE_SECURITY = 18, EVENT_DOWNGRADE_ATTACK = 19, EVENT_CONFIRM_USER_EMAIL = 20, EVENT_CREDIT_CARD_EXPIRY = 21, EVENT_NETWORK_ACTIVITY = 22, }; enum { REASON_ERROR_UNKNOWN = -1, // Unknown reason REASON_ERROR_NO_ERROR = 0, // No error REASON_ERROR_FAILURE_UNSERIALIZE_NODE = 1, // Failure when node is unserialized from DB REASON_ERROR_DB_IO_FAILURE = 2, // Input/output error at DB layer REASON_ERROR_DB_FULL = 3, // Failure at DB layer because disk is full REASON_ERROR_DB_INDEX_OVERFLOW = 4, // Index used to primary key at db overflow REASON_ERROR_NO_JSCD = 5, // No JSON Sync Config Data REASON_ERROR_REGENERATE_JSCD = 6, // JSON Sync Config Data has been regenerated REASON_ERROR_DB_CORRUPT = 7, // DB file is corrupted }; /** * @brief Direction of a network activity for EVENT_NETWORK_ACTIVITY. * * Maps 1:1 with the internal enum of the same name in types.h */ enum NetworkActivityChannel { SC = 0, // Server to client channel CS = 1, // Client to server channel }; /** * @brief Type of network activity for EVENT_NETWORK_ACTIVITY. * * Maps 1:1 with the internal enum of the same name in types.h */ enum NetworkActivityType { REQUEST_SENT = 0, REQUEST_RECEIVED = 1, REQUEST_ERROR = 2, }; virtual ~MegaEvent(); /** * @brief Creates a copy of this MegaEvent object * * The resulting object is fully independent of the source MegaEvent, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaEvent object */ virtual MegaEvent *copy(); /** * @brief Returns the type of event * * This method identifies the nature of the event being notified. Based on the event type, * other methods like getText(), getNumber(), or getHandle() may return meaningful values. * * To understand what data each method provides for a specific event type, * refer to the documentation of MegaEvent::getText(), MegaEvent::getNumber(), * MegaEvent::getHandle() and MegaEvent::getNumber(const std::string& key). * * Possible values: * * - EVENT_COMMIT_DB (0): * SDK has committed the ongoing DB transaction. Use getText() for the sequence number. * * - EVENT_ACCOUNT_CONFIRMATION (1): * A new account was confirmed. Use getText() for the email address. * * - EVENT_DISCONNECT (3): * SDK disconnected due to network change or invalid IPs. App should reset its connections. * * - EVENT_ACCOUNT_BLOCKED (4): * Account has been blocked. Use getText() for a user message and getNumber() for a reason * code. * * - EVENT_STORAGE (5): * Storage state has changed. Use getNumber() for the new storage state. * * - EVENT_NODES_CURRENT (6): * All external changes to nodes have been received. * * - EVENT_MEDIA_INFO_READY (7): * Codec-mapping information has been received and is ready. * * - EVENT_STORAGE_SUM_CHANGED (8): * Total storage usage has changed. Use getNumber() for the updated storage sum. * * - EVENT_BUSINESS_STATUS (9): * Business account status changed. Use getNumber() for the new status code. * * - EVENT_KEY_MODIFIED (10): * A user's key has changed. Use getHandle() for the user and getNumber() for the key type. * * - EVENT_MISC_FLAGS_READY (11): * Miscellaneous flags are now available or have been updated. * * - EVENT_FIRST_SYNC_RESUMING(12): * This event is obsolete. * * - MegaEvent::EVENT_SYNCS_DISABLED (13): * Syncs were bulk-disabled due to a situation like storage overquota. Use getNumber() to * retrieve the sync error code. * * - MegaEvent::EVENT_SYNCS_RESTORED (14): * Syncs have been restored after login and fetchnodes. Use getNumber() to retrieve the sync * error code. * * - EVENT_REQSTAT_PROGRESS (15): * Ongoing API request progress. Use getNumber() for progress (in per mil), or -1 if none. * * - EVENT_RELOADING (16): * Server forced a full reload. App should reinitialize data and UI accordingly. * * - EVENT_FATAL_ERROR (17): * A fatal error was encountered. Use getNumber() to retrieve the error code. * * - EVENT_UPGRADE_SECURITY (18): * Account was upgraded to use key attributes for improved security. * * - EVENT_DOWNGRADE_ATTACK (19): * Downgrade attack was detected. Removed shares might have reappeared. * * - EVENT_CONFIRM_USER_EMAIL (20): * User confirmed their email. Use getHandle() for user and getText() for email address. * * - EVENT_CREDIT_CARD_EXPIRY (21): * Credit card is expiring soon or a new one was added. * App should call `MegaApi::fetchCreditCardInfo()` to get updated details. * * - EVENT_NETWORK_ACTIVITY (22): * Network activity occurred on the SC or CS channel. * Use `getNumber("channel")`, `getNumber("activity_type")`, and `getNumber("error_code")` * to get more detail about the event. * * * @return Event type, from the MegaEvent::EventType enum. */ virtual int getType() const; /** * @brief Returns a text string relative to this event * * The SDK retains the ownership of the returned value. It will be valid until * the MegaEvent object is deleted. * * The meaning of the returned text depends on the event type (see MegaEvent::getType()). * * - EVENT_COMMIT_DB: * Returns the sequence number recorded by the SDK when the event happened. * * - EVENT_ACCOUNT_CONFIRMATION: * Returns the email address used to confirm the account. * * - EVENT_CONFIRM_USER_EMAIL: * Returns the email address used to confirm the account. * * - EVENT_ACCOUNT_BLOCKED: * Returns the message to show to the user explaining the reason for the block. * * @return Text relative to this event. */ virtual const char* getText() const; /** * @brief Returns a number relative to this event * * The meaning of the returned number depends on the event type (see MegaEvent::getType()). * * - EVENT_STORAGE: * Provides the current storage state of the account: * - MegaApi::STORAGE_STATE_GREEN = 0: No storage problems. * - MegaApi::STORAGE_STATE_ORANGE = 1: Account is almost full. * - MegaApi::STORAGE_STATE_RED = 2: Account is full; uploads are stopped. * - MegaApi::STORAGE_STATE_CHANGE = 3: Deprecated; current state is notified directly. * - MegaApi::STORAGE_STATE_PAYWALL = 4: Account full for a long time; most actions * disallowed. * * - EVENT_STORAGE_SUM_CHANGED: * Contains the new total storage sum used by the account. * * - EVENT_REQSTAT_PROGRESS: * Returns the progress of a long-running API operation in per mil (0–1000), * or -1 if no operation is in progress. * * - EVENT_FATAL_ERROR: * Provides the reason for a fatal error: * - REASON_ERROR_UNKNOWN = -1 * - REASON_ERROR_NO_ERROR = 0 * - REASON_ERROR_FAILURE_UNSERIALIZE_NODE = 1 * - REASON_ERROR_DB_IO_FAILURE = 2 * - REASON_ERROR_DB_FULL = 3 * - REASON_ERROR_DB_INDEX_OVERFLOW = 4 * - REASON_ERROR_NO_JSCD = 5 * - REASON_ERROR_REGENERATE_JSCD = 6 * - REASON_ERROR_DB_CORRUPT = 7 * * - EVENT_ACCOUNT_BLOCKED: * Indicates the reason for account blockage: * - MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT = 200 * - MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = 300 * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED = 400 * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED = 401 * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL = 700 * * - EVENT_BUSINESS_STATUS: * Indicates the business account status: * - BUSINESS_STATUS_EXPIRED = -1 * - BUSINESS_STATUS_INACTIVE = 0 * - BUSINESS_STATUS_ACTIVE = 1 * - BUSINESS_STATUS_GRACE_PERIOD = 2 * * - EVENT_KEY_MODIFIED: * Provides the type of key that was modified for a user (see getHandle() for the user * handle): * - 0: Public chat key (Cu25519) * - 1: Public signing key (Ed25519) * - 2: Public RSA key * - 3: Signature of chat key * - 4: Signature of RSA key * * @return Number relative to this event. */ virtual int64_t getNumber() const; /** * @brief Returns a handle relative to this event * * The meaning of the returned handle depends on the event type (see MegaEvent::getType()). * * - EVENT_CONFIRM_USER_EMAIL: * Returns the user handle for the confirmed account. * * - EVENT_KEY_MODIFIED: * Returns the handle of the user whose key has been modified. * * @return Handle relative to this event, or INVALID_HANDLE if not applicable. */ virtual MegaHandle getHandle() const; /** * @brief Returns a readable description of the event * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable description of the event */ virtual const char* getEventString() const; /** * @brief Returns a numeric value associated with the specified key for this event. * * This method allows accessing multiple named numeric values that may be associated * with the event. * * The meaning of the returned number depends on the event type (see MegaEvent::getType()). * * - EVENT_NETWORK_ACTIVITY: * This event uses multiple getNumber keys: * - getNumber("channel") returns the channel where the activity happened (See * MegaEvent::NetworkActivityChannel). * - getNumber("activity_type") returns the type of network activity (See * MegaEvent::NetworkActivityType). * - getNumber("error_code") returns the error code (See MegaError enum) or status (HTTP * status code) of the activity. * * @param key The key identifying the numeric data. * * @return An optional containing the numeric value corresponding to the provided key, * or an empty optional if not available. */ virtual std::optional getNumber(const std::string& key) const; }; /** * @brief Provides information about a transfer * * Developers can use listeners (MegaListener, MegaTransferListener) * to track the progress of each transfer. MegaTransfer objects are provided in callbacks sent * to these listeners and allow developers to know the state of the transfers, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the transfer * when the object is created, they are immutable. * */ class MegaTransfer { public: enum { TYPE_DOWNLOAD = 0, TYPE_UPLOAD = 1, TYPE_LOCAL_TCP_DOWNLOAD = 2, TYPE_LOCAL_HTTP_DOWNLOAD = 2 //kept for backwards compatibility }; enum { STATE_NONE = 0, STATE_QUEUED, STATE_ACTIVE, STATE_PAUSED, STATE_RETRYING, STATE_COMPLETING, STATE_COMPLETED, STATE_CANCELLED, STATE_FAILED }; enum { MOVE_TYPE_UP = 1, MOVE_TYPE_DOWN, MOVE_TYPE_TOP, MOVE_TYPE_BOTTOM }; enum { STAGE_NONE = 0, STAGE_SCAN, STAGE_CREATE_TREE, STAGE_TRANSFERRING_FILES, STAGE_MAX = STAGE_TRANSFERRING_FILES, }; enum { // Collision Check for same file COLLISION_CHECK_ASSUMESAME = 1, // assume files are the same COLLISION_CHECK_ALWAYSERROR = 2, // treat as an error COLLISION_CHECK_FINGERPRINT = 3, // Check fingerprint. Assume files are same if their fingerprint are same (quick) COLLISION_CHECK_METAMAC = 4, // Check MetaMac. Assume files are same if their meta mac are same (slow, a lot of disk + CPU) COLLISION_CHECK_ASSUMEDIFFERENT = 5, // assume files are different }; enum { // Defines how to handle name collisions when saving files. // For folders, the default behavior is to merge (i.e., do nothing) // unless the filesystem is case-insensitive and the collision is caused // by the same name with different capitalization. // In that case, we apply the selected collision resolution strategy. // Note: Overwrite always applies merge behavior for folders. COLLISION_RESOLUTION_OVERWRITE = 1, // Overwrite the existing one for files COLLISION_RESOLUTION_NEW_WITH_N = 2, // Rename the new one with suffix (1), (2), and etc. COLLISION_RESOLUTION_EXISTING_TO_OLDN = 3, // Rename the existing one with suffix .old1, old2, and etc. }; virtual ~MegaTransfer(); /** * @brief Creates a copy of this MegaTransfer object * * The resulting object is fully independent of the source MegaTransfer, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaTransfer object */ virtual MegaTransfer *copy(); /** * @brief Returns the type of the transfer (TYPE_DOWNLOAD, TYPE_UPLOAD) * @return The type of the transfer (TYPE_DOWNLOAD, TYPE_UPLOAD) */ virtual int getType() const; /** * @brief Returns a readable string showing the type of transfer (UPLOAD, DOWNLOAD) * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable string showing the type of transfer (UPLOAD, DOWNLOAD) */ virtual const char *getTransferString() const; /** * @brief Returns a readable string that shows the type of the transfer * * This function provides exactly the same result as MegaTransfer::getTransferString (UPLOAD, DOWNLOAD) * It's provided for a better Java compatibility * * @return Readable string showing the type of transfer (UPLOAD, DOWNLOAD) */ virtual const char* toString() const; /** * @brief Returns the starting time of the request (in deciseconds) * * The returned value is a monotonic time since some unspecified starting point expressed in * deciseconds. * * @return Starting time of the transfer (in deciseconds) */ virtual int64_t getStartTime() const; /** * @brief Returns the number of transferred bytes during this request * @return Transferred bytes during this transfer */ virtual long long getTransferredBytes() const; /** * @brief Returns the total bytes to be transferred to complete the transfer * @return Total bytes to be transferred to complete the transfer */ virtual long long getTotalBytes() const; /** * @brief Returns the local path related to this request * * For uploads, this function returns the path to the source file. For downloads, it * returns the path of the destination file. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaTransfer object is deleted. * * @return Local path related to this transfer */ virtual const char* getPath() const; /** * @brief Returns the parent path related to this request * * For uploads, this function returns the path to the folder containing the source file. * except when uploading files for support: it will return the support account then. * For downloads, it returns that path to the folder containing the destination file. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaTransfer object is deleted. * * @return Parent path related to this transfer */ virtual const char* getParentPath() const; /** * @brief Returns the handle related to this transfer * * For downloads, this function returns the handle of the source node. * * For uploads, it returns the handle of the new node in MegaTransferListener::onTransferFinish * and MegaListener::onTransferFinish when the error code is API_OK. Otherwise, it returns * mega::INVALID_HANDLE. * * @return The handle related to the transfer. */ virtual MegaHandle getNodeHandle() const; /** * @brief Returns the handle of the parent node related to this transfer * * For downloads, this function returns always mega::INVALID_HANDLE. For uploads, * it returns the handle of the destination node (folder) for the uploaded file. * * @return The handle of the destination folder for uploads, or mega::INVALID_HANDLE for downloads. */ virtual MegaHandle getParentHandle() const; /** * @brief Returns the starting position of the transfer for streaming downloads * * The return value of this fuction will be 0 if the transfer isn't a streaming * download (MegaApi::startStreaming) * * @return Starting position of the transfer for streaming downloads, otherwise 0 */ virtual long long getStartPos() const; /** * @brief Returns the end position of the transfer for streaming downloads * * The return value of this fuction will be 0 if the transfer isn't a streaming * download (MegaApi::startStreaming) * * @return End position of the transfer for streaming downloads, otherwise 0 */ virtual long long getEndPos() const; /** * @brief Returns the name of the file that is being transferred * * It's possible to upload a file with a different name (MegaApi::startUpload). In that case, * this function returns the destination name. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaTransfer object is deleted. * * @return Name of the file that is being transferred */ virtual const char* getFileName() const; /** * @brief Returns the MegaTransferListener object associated with this transfer * * MegaTransferListener objects can be associated with transfers at startup, if a listener * isn't associated, this function will return NULL * * @return Listener associated with this transfer */ virtual MegaTransferListener* getListener() const; /** * @brief Return the number of times that a transfer has temporarily failed * @return Number of times that a transfer has temporarily failed */ virtual int getNumRetry() const; /** * @brief Returns the maximum number of times that the transfer will be retried * @return Mmximum number of times that the transfer will be retried */ virtual int getMaxRetries() const; /** * @brief Returns a numeric value that can be used for different purposes: * * 1) In case MegaTransfer::isRecursive is true, it returns the current stage in case this * transfer represents a folder upload/download operation. Valid values are: * - MegaTransfer::STAGE_SCAN = 1 * - MegaTransfer::STAGE_CREATE_TREE = 2 * - MegaTransfer::STAGE_TRANSFERRING_FILES = 3 * Any other returned value in this scenario, must be ignored. * * In this scenario the value returned by this method, can only be considered as valid, when * we receive MegaTransferListener::onTransferUpdate or MegaListener::onTransferUpdate, and * the returned value is in between the range specified above. * * Note: any specific stage can only be notified once at most. * @deprecated use the stage in the onFolderTransferUpdate callback instead * * 2) In case of file transfer, MegaTransfer::getType is MegaTransfer::TYPE_UPLOAD and * MegaTransfer::isSourceFileTemporary is true, This method method returns a number greater * than 0 if temporary file could be removed from local filesystem, otherwise returns 0 * * In this scenario the value returned by this method, can only be considered as valid, when * we receive MegaTransferListener::onTransferFinish or MegaListener::onTransferFinish. * * @return A numeric value that can be used for different purposes */ virtual unsigned getStage() const; /** * @brief Returns an identifier of this transfer as long as it is in-flight (i.e. not * completed or cancelled). * * @note The identifiers may not be consecutive and can be reused once the transfer is * completed or cancelled. * * @note The identifiers are reset a after fresh login (without session) * * @return 32-bits unsigned integer that identifies this transfer */ virtual uint32_t getUniqueId() const; /** * @brief Returns an integer that identifies this transfer * @return Integer that identifies this transfer */ virtual int getTag() const; /** * @brief Returns the current speed of this transfer * @return Current speed of this transfer */ virtual long long getSpeed() const; /** * @brief Returns the average speed of this transfer * @return Average speed of this transfer */ virtual long long getMeanSpeed() const; /** * @brief Returns the number of bytes transferred since the previous callback * @return Number of bytes transferred since the previous callback * @see MegaListener::onTransferUpdate, MegaTransferListener::onTransferUpdate */ virtual long long getDeltaSize() const; /** * @brief Returns the timestamp when the last data was received (in deciseconds) * * This timestamp doesn't have a defined starting point. Use the difference between * the return value of this function and MegaTransfer::getStartTime to know how * much time the transfer has been running. * * @return Timestamp when the last data was received (in deciseconds) */ virtual int64_t getUpdateTime() const; /** * @brief Returns a public node related to the transfer * * The return value is only valid for downloads of public nodes. * * You take the ownership of the returned value. * * @return Public node related to the transfer */ virtual MegaNode *getPublicMegaNode() const; /** * @brief Returns true if this transfer belongs to the synchronization engine * * A single transfer can upload/download several files with exactly the same contents. If * some of these files are being transferred by the synchonization engine, but there is at * least one file started by the application, this function returns false. * * This data is important to know if the transfer is cancellable. Regular transfers are cancellable * but synchronization transfers aren't. * * @return true if this transfer belongs to the synchronization engine, otherwise false */ virtual bool isSyncTransfer() const; /** * @brief Returns true if this transfer belongs to the backups engine * * This data is important to know if the transfer will resume when enableTransferResumption is called. * Regular transfers are resumed, but backup transfers aren't. * * @return true if this transfer belongs to the backups engine, otherwise false */ virtual bool isBackupTransfer() const; /** * @brief Returns true if the transfer has failed with API_EOVERQUOTA * and the target is foreign. * * @return true if the transfer has failed with API_EOVERQUOTA and the target is foreign. */ virtual bool isForeignOverquota() const; /** * @brief Returns true if the transfer may force a new upload. * * @return true if the transfer can force a new upload. */ virtual bool isForceNewUpload() const; /** * @brief Returns true is this is a streaming transfer * @return true if this is a streaming transfer, false otherwise * @see MegaApi::startStreaming */ virtual bool isStreamingTransfer() const; /** * @brief Returns true is the transfer is at finished state (COMPLETED, CANCELLED OR FAILED) * @return true if this transfer is finished, false otherwise */ virtual bool isFinished() const; /** * @brief Returns the received bytes since the last callback * * The returned value is only valid for streaming transfers (MegaApi::startStreaming). * * @return Received bytes since the last callback */ virtual char *getLastBytes() const; /** * @brief Returns the last error related to the transfer with extra info * * The MegaTransfer object retains the ownership of the returned pointer. It will * be valid until the deletion of the MegaTransfer object. * * @return Last error related to the transfer, with extended info */ virtual const MegaError* getLastErrorExtended() const; /** * @brief Returns true if the transfer is a folder transfer * @return true if it's a folder transfer, otherwise (file transfer) it returns false */ virtual bool isFolderTransfer() const; /** * @brief Returns the identifier of the folder transfer associated to this transfer * * This function is only useful for transfers automatically started in the context of a folder transfer. * For folder transfers (the ones directly started with startUpload), it returns -1 * Otherwise, it returns 0 * * @return Tag of the associated folder transfer. */ virtual int getFolderTransferTag() const; /** * @brief Returns the application data associated with this transfer * * You can set the data returned by this function in MegaApi::startDownload * * The SDK retains the ownership of the returned value. It will be valid until * the MegaTransfer object is deleted. * * @return Application data associated with this transfer */ virtual const char* getAppData() const; /** * @brief Returns the state of the transfer * * It can be one of these values: * - STATE_NONE = 0 * Unknown state. This state should be never returned. * * - STATE_QUEUED = 1 * The transfer is queued. No data related to it is being transferred. * * - STATE_ACTIVE = 2 * The transfer is active. Its data is being transferred. * * - STATE_PAUSED = 3 * The transfer is paused. It won't be activated until it's resumed. * * - STATE_RETRYING = 4 * The transfer is waiting to be retried due to a temporary error. * * - STATE_COMPLETING = 5 * The transfer is being completed. All data has been transferred * but it's still needed to attach the resulting node to the * account (uploads), to attach thumbnails/previews to the * node (uploads of images) or to create the resulting local * file (downloads). The transfer should be completed in a short time. * * - STATE_COMPLETED = 6 * The transfer has beeing finished. * * - STATE_CANCELLED = 7 * The transfer was cancelled by the user. * * - STATE_FAILED = 8 * The transfer was cancelled by the SDK due to a fatal error or * after a high number of retries. * * @return State of the transfer */ virtual int getState() const; /** * @brief Returns the priority of the transfer * * This value is intended to keep the order of the transfer queue on apps. * * @return Priority of the transfer */ virtual unsigned long long getPriority() const; /** * @brief Returns the notification number of the SDK when this MegaTransfer was generated * * The notification number of the SDK is increased every time the SDK sends a callback * to the app. * * @return Notification number */ virtual long long getNotificationNumber() const; /** * @brief Returns whether the target folder of the transfer was overriden by the API server * * It may happen that the target folder fo a transfer is deleted by the time the node * is going to be added. Hence, the API will create the node in the rubbish bin. * * @return True if target folder was overriden (apps can check the final parent) */ virtual bool getTargetOverride() const; /** * @brief Returns a pointer to the cancel token associated to a MegaTransfer in case it exists. * * CancelToken can be used to cancel a batch of transfers (upload or download) that contains at least one folder. * * When user wants to upload/download a batch of items that at least contains one folder, SDK mutex will be partially * locked until: * - we have received onTransferStart for every file in the batch * - we have received onTransferUpdate with MegaTransfer::getStage == MegaTransfer::STAGE_TRANSFERRING_FILES * for every folder in the batch * * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by calling CancelToken::cancel(true). * This method will cancel all transfers(not finished yet). * * Important considerations: * - A cancel token instance can be shared by multiple transfers, and calling CancelToken::cancel(true) will affect all * of those transfers. * * @return A pointer to a cancelToken instance associated to the transfer in case it exists */ virtual MegaCancelToken* getCancelToken(); /** * @brief Returns a string that identify the recursive operation stage * * @return A string that identify the recursive operation stage */ static const char* stageToString(unsigned stage); }; /** * @brief Provides information about the contents of a folder * * This object is related to provide the results of the function MegaApi::getFolderInfo * * Objects of this class aren't live, they are snapshots of the state of the contents of the * folder when the object is created, they are immutable. * */ class MegaFolderInfo { public: virtual ~MegaFolderInfo(); /** * @brief Creates a copy of this MegaFolderInfo object * * The resulting object is fully independent of the source MegaFolderInfo, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaFolderInfo object */ virtual MegaFolderInfo *copy() const; /** * @brief Return the number of file versions inside the folder * * The current version of files is not taken into account for the return value of this function * * @return Number of file versions inside the folder */ virtual int getNumVersions() const; /** * @brief Returns the number of files inside the folder * * File versions are not counted for the return value of this function * * @return Number of files inside the folder */ virtual int getNumFiles() const; /** * @brief Returns the number of folders inside the folder * @return Number of folders inside the folder */ virtual int getNumFolders() const; /** * @brief Returns the total size of files inside the folder * * File versions are not taken into account for the return value of this function * * @return Total size of files inside the folder */ virtual long long getCurrentSize() const; /** * @brief Returns the total size of file versions inside the folder * * The current version of files is not taken into account for the return value of this function * * @return Total size of file versions inside the folder */ virtual long long getVersionsSize() const; }; /** * @brief Provides information about timezones and the current default * * This object is related to results of the function MegaApi::fetchTimeZone * * Objects of this class aren't live, they contain details about timezones and the * default when the object is created, they are immutable. * */ class MegaTimeZoneDetails { public: virtual ~MegaTimeZoneDetails(); /** * @brief Creates a copy of this MegaTimeZoneDetails object * * The resulting object is fully independent of the source MegaTimeZoneDetails, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaTimeZoneDetails object */ virtual MegaTimeZoneDetails *copy() const; /** * @brief Returns the number of timezones in this object * * @return Number of timezones in this object */ virtual int getNumTimeZones() const; /** * @brief Returns the timezone at an index * * The MegaTimeZoneDetails object retains the ownership of the returned string. * It will be only valid until the MegaTimeZoneDetails object is deleted. * * @param index Index in the list (it must be lower than MegaTimeZoneDetails::getNumTimeZones) * @return Timezone at an index */ virtual const char *getTimeZone(int index) const; /** * @brief Returns the current time offset of the time zone at an index, respect to UTC (in seconds, it can be negative) * * @param index Index in the list (it must be lower than MegaTimeZoneDetails::getNumTimeZones) * @return Current time offset of the time zone at an index, respect to UTC (in seconds, it can be negative) * @see MegaTimeZoneDetails::getTimeZone */ virtual int getTimeOffset(int index) const; /** * @brief Get the default time zone index * * If there isn't any good default known, this function will return -1 * * @return Default time zone index, or -1 if there isn't a good default known */ virtual int getDefault() const; }; /** * @brief Provides information about the notification settings * * The notifications can be configured: * * 1. Globally * 1.1. Mute all notifications * 1.2. Notify only during a schedule: from one time to another time of the day, specifying the timezone of reference * 1.3. Do Not Disturb for a period of time: it overrides the schedule, if any (no notification will be generated) * * 2. Chats: Mute for all chats notifications * * 3. Per chat: * 2.1. Mute all notifications from the specified chat * 2.2. Always notify for the specified chat * 2.3. Do Not Disturb for a period of time for the specified chat * * @note Notification settings per chat override any global notification setting. * @note The DND mode per chat is not compatible with the option to always notify and viceversa. * * 4. Contacts: new incoming contact request, outgoing contact request accepted... * 5. Shared folders: new shared folder, access removed... * */ class MegaPushNotificationSettings { protected: MegaPushNotificationSettings(); public: /** * @brief Creates a new instance of MegaPushNotificationSettings * @return A pointer to the superclass of the private object */ static MegaPushNotificationSettings *createInstance(); virtual ~MegaPushNotificationSettings(); /** * @brief Creates a copy of this MegaPushNotificationSettings object * * The resulting object is fully independent of the source MegaPushNotificationSettings, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaPushNotificationSettings object */ virtual MegaPushNotificationSettings *copy() const; /** * @brief Returns whether Do-Not-Disturb mode is enabled or not * @return True if enabled, false otherwise */ virtual bool isGlobalDndEnabled() const; /** * @brief Returns whether Do-Not-Disturb mode for chats is enabled or not * @return True if enabled, false otherwise */ virtual bool isGlobalChatsDndEnabled() const; /** * @brief Returns the timestamp until the DND mode is enabled * * This method returns a valid value only if MegaPushNotificationSettings::isGlobalDndEnabled * returns true. * * If there's no DND mode established, this function returns -1. * @note a DND value of 0 means the DND does not expire. * * @return Timestamp until DND mode is enabled (in seconds since the Epoch) */ virtual int64_t getGlobalDnd() const; /** * @brief Returns whether there is a schedule for notifications or not * @return True if enabled, false otherwise */ virtual bool isGlobalScheduleEnabled() const; /** * @brief Returns the time of the day when notifications start * * This method returns a valid value only if MegaPushNotificationSettings::isGlobalScheduleEnabled * returns true. * * @return Minutes counting from 00:00 (based on the configured timezone) */ virtual int getGlobalScheduleStart() const; /** * @brief Returns the time of the day when notifications stop * * This method returns a valid value only if MegaPushNotificationSettings::isGlobalScheduleEnabled * returns true. * * @return Minutes counting from 00:00 (based on the configured timezone) */ virtual int getGlobalScheduleEnd() const; /** * @brief Returns the timezone of reference for the notification schedule * * This method returns a valid value only if MegaPushNotificationSettings::isGlobalScheduleEnabled * returns true. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Minutes counting from 00:00 (based on the configured timezone) */ virtual const char *getGlobalScheduleTimezone() const; /** * @brief Returns whether Do-Not-Disturb mode for a chat is enabled or not * * @param chatid MegaHandle that identifies the chat room * @return True if enabled, false otherwise */ virtual bool isChatDndEnabled(MegaHandle chatid) const; /** * @brief Returns the timestamp until the Do-Not-Disturb mode for a chat * * This method returns a valid value only if MegaPushNotificationSettings::isChatDndEnabled * returns true. * * If there's no DND mode established for the specified chat, this function returns -1. * @note a DND value of 0 means the DND does not expire. * * @param chatid MegaHandle that identifies the chat room * @return Timestamp until DND mode is enabled (in seconds since the Epoch) */ virtual int64_t getChatDnd(MegaHandle chatid) const; /** * @brief Returns whether always notify for a chat or not * * This option overrides the global notification settings. * * @param chatid MegaHandle that identifies the chat room * @return True if enabled, false otherwise */ virtual bool isChatAlwaysNotifyEnabled(MegaHandle chatid) const; /** * @brief Returns whether notifications about Contacts are enabled or not * @return True if enabled, false otherwise */ virtual bool isContactsEnabled() const; /** * @brief Returns whether notifications about shared-folders are enabled or not * @return True if enabled, false otherwise */ virtual bool isSharesEnabled() const; /** * @brief Returns the timestamp until the chats DND mode is enabled * * This method returns a valid value only if MegaPushNotificationSettings::isGlobalChatsDndEnabled * returns true. * * If there's no DND mode established, this function returns -1. * @note a DND value of 0 means the DND does not expire. * * @return Timestamp until chats DND mode is enabled (in seconds since the Epoch) */ virtual int64_t getGlobalChatsDnd() const; /** * @brief Enable or disable notifications globally * * If notifications are globally disabled, the DND global setting will be * cleared and the specified schedule, if any, will have no effect. * * @note When notifications are globally disabled, settings per chat still apply. * * @param enable True to enable, false to disable */ virtual void enableGlobal(bool enable); /** * @brief Set the global DND mode for a period of time * * No notifications will be generated until the specified timestamp. * * If notifications were globally disabled, this function will enable them * back (but will not generate notification until the specified timestamp). * * @param timestamp Timestamp until DND mode is enabled (in seconds since the Epoch) */ virtual void setGlobalDnd(int64_t timestamp); /** * @brief Disable the globally specified DND mode */ virtual void disableGlobalDnd(); /** * @brief Set the schedule for notifications globally * * Notifications, if globally enabled, will be generated only from \c start * to \c end time, using the \c timezone as reference. * * The timezone should be one of the values returned by MegaTimeZoneDetails::getTimeZone. * @see MegaApi::fetchTimeZone for more details. * * @param start Minutes counting from 00:00 (based on the configured timezone) * @param end Minutes counting from 00:00 (based on the configured timezone) * @param timezone C-String representing the timezone */ virtual void setGlobalSchedule(int start, int end, const char *timezone); /** * @brief Disable the schedule for notifications globally */ virtual void disableGlobalSchedule(); /** * @brief Enable or disable notifications for a chat * * If notifications for this chat are disabled, the DND settings for this chat, * if any, will be cleared. * * @note Settings per chat override any global notification setting. * * @param chatid MegaHandle that identifies the chat room * @param enable True to enable, false to disable */ virtual void enableChat(MegaHandle chatid, bool enable); /** * @brief Set the DND mode for a chat for a period of time * * No notifications will be generated until the specified timestamp. * * This setting is not compatible with the "Always notify". If DND mode is * configured, the "Always notify" will be disabled. * * If chat notifications were totally disabled for the specified chat, this * function will enable them back (but will not generate notification until * the specified timestamp). * * @param timestamp Timestamp until DND mode is enabled (in seconds since the Epoch) */ virtual void setChatDnd(MegaHandle chatid, int64_t timestamp); /** * @brief Set the Global DND for chats for a period of time * * No chat notifications will be generated until the specified timestamp. * * @param timestamp Timestamp until DND mode is enabled (in seconds since the Epoch) */ virtual void setGlobalChatsDnd(int64_t timestamp); /** * @brief Enable or disable "Always notify" setting * * Notifications for this chat will always be generated, even if they are globally * disabled, out of the global schedule or a global DND mode is set. * * This setting is not compatible with the DND mode for the specified chat. In consequence, * if "Always notify" is enabled and the DND mode was configured, it will be disabled. * Also, if notifications were disabled for the specified chat, they will be enabled. * * @note Settings per chat override any global notification setting. * * @param chatid MegaHandle that identifies the chat room * @param enable True to enable, false to disable */ virtual void enableChatAlwaysNotify(MegaHandle chatid, bool enable); /** * @brief Enable or disable notifications related to contacts * @param enable True to enable, false to disable */ virtual void enableContacts(bool enable); /** * @brief Enable or disable notifications related to shared-folders * @param enable True to enable, false to disable */ virtual void enableShares(bool enable); /** * @brief Enable or disable notifications related to all chats * @param enable True to enable, false to disable */ virtual void enableChats(bool enable); }; /** * @brief Provides information about transfer queues * * This object is used as the return value of the function MegaApi::getTransferData * * Objects of this class aren't live, they are snapshots of the state of the transfer * queues when the object is created, they are immutable. * */ class MegaTransferData { public: virtual ~MegaTransferData(); /** * @brief Creates a copy of this MegaTransferData object * * The resulting object is fully independent of the source MegaTransferData, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaTransferData object */ virtual MegaTransferData *copy() const; /** * @brief Returns the number of downloads in the transfer queue * @return Number of downloads in the transfer queue */ virtual int getNumDownloads() const; /** * @brief Returns the number of uploads in the transfer queue * @return Number of uploads in the transfer queue */ virtual int getNumUploads() const; /** * @brief Returns the tag of the download at index i * @param i index of the selected download. It must be between 0 and MegaTransferData::getNumDownloads (not included) * @return Tag of the download at index i */ virtual int getDownloadTag(int i) const; /** * @brief Returns the tag of the upload at index i * @param i index of the selected upload. It must be between 0 and MegaTransferData::getNumUploads (not included) * @return Tag of the upload at index i */ virtual int getUploadTag(int i) const; /** * @brief Returns the priority of the download at index i * @param i index of the selected download. It must be between 0 and MegaTransferData::getNumDownloads (not included) * @return Priority of the download at index i */ virtual unsigned long long getDownloadPriority(int i) const; /** * @brief Returns the priority of the upload at index i * @param i index of the selected upload. It must be between 0 and MegaTransferData::getNumUploads (not included) * @return Priority of the upload at index i */ virtual unsigned long long getUploadPriority(int i) const; /** * @brief Returns the notification number of the SDK when this MegaTransferData was generated * * The notification number of the SDK is increased every time the SDK sends a callback * to the app. * * @return Notification number */ virtual long long getNotificationNumber() const; }; /** * @brief Provides information about a contact request * * Developers can use listeners (MegaListener, MegaGlobalListener) * to track the progress of each contact. MegaContactRequest objects are provided in callbacks sent * to these listeners and allow developers to know the state of the contact requests, their parameters * and their results. * * Objects of this class aren't live, they are snapshots of the state of the contact request * when the object is created, they are immutable. * */ class MegaContactRequest { public: enum { STATUS_UNRESOLVED = 0, STATUS_ACCEPTED, STATUS_DENIED, STATUS_IGNORED, STATUS_DELETED, STATUS_REMINDED }; enum { REPLY_ACTION_ACCEPT = 0, REPLY_ACTION_DENY, REPLY_ACTION_IGNORE }; enum { INVITE_ACTION_ADD = 0, INVITE_ACTION_DELETE, INVITE_ACTION_REMIND }; virtual ~MegaContactRequest(); /** * @brief Creates a copy of this MegaContactRequest object * * The resulting object is fully independent of the source MegaContactRequest, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaContactRequest object */ virtual MegaContactRequest *copy() const; /** * @brief Returns the handle of this MegaContactRequest object * @return Handle of the object */ virtual MegaHandle getHandle() const; /** * @brief Returns the email of the request creator * @return Email of the request creator */ virtual char* getSourceEmail() const; /** * @brief Return the message that the creator of the contact request has added * @return Message sent by the request creator */ virtual char* getSourceMessage() const; /** * @brief Returns the email of the recipient or NULL if the current account is the recipient * @return Email of the recipient or NULL if the request is for us */ virtual char* getTargetEmail() const; /** * @brief Returns the creation time of the contact request * @return Creation time of the contact request (in seconds since the Epoch) */ virtual int64_t getCreationTime() const; /** * @brief Returns the last update time of the contact request * @return Last update time of the contact request (in seconds since the Epoch) */ virtual int64_t getModificationTime() const; /** * @brief Returns the status of the contact request * * It can be one of the following values: * - STATUS_UNRESOLVED = 0 * The request is pending * * - STATUS_ACCEPTED = 1 * The request has been accepted * * - STATUS_DENIED = 2 * The request has been denied * * - STATUS_IGNORED = 3 * The request has been ignored * * - STATUS_DELETED = 4 * The request has been deleted * * - STATUS_REMINDED = 5 * The request has been reminded * * @return Status of the contact request */ virtual int getStatus() const; /** * @brief Direction of the request * @return True if the request is outgoing and false if it's incoming */ virtual bool isOutgoing() const; /** * @brief Returns true is the incoming contact request is being automatically accepted * @return True if the incoming contact request is being automatically accepted */ virtual bool isAutoAccepted() const; }; #ifdef ENABLE_SYNC /** * @brief Provides information about a synchronization */ class MegaSync { public: enum Error { NO_SYNC_ERROR = 0, UNKNOWN_ERROR = 1, UNSUPPORTED_FILE_SYSTEM = 2, //File system type is not supported INVALID_REMOTE_TYPE = 3, //Remote type is not a folder that can be synced INVALID_LOCAL_TYPE = 4, //Local path does not refer to a folder INITIAL_SCAN_FAILED = 5, //The initial scan failed LOCAL_PATH_TEMPORARY_UNAVAILABLE = 6, //Local path is temporarily unavailable: this is fatal when adding a sync LOCAL_PATH_UNAVAILABLE = 7, //Local path is not available (can't be open) REMOTE_NODE_NOT_FOUND = 8, //Remote node does no longer exists STORAGE_OVERQUOTA = 9, //Account reached storage overquota ACCOUNT_EXPIRED = 10, //Account expired (business or pro flexi) FOREIGN_TARGET_OVERSTORAGE = 11, //Sync transfer fails (upload into an inshare whose account is overquota) REMOTE_PATH_HAS_CHANGED = 12, // (obsolete -> changing remote path is not an error) // REMOTE_PATH_DELETED = 13, // (obsolete -> unified with REMOTE_NODE_NOT_FOUND) SHARE_NON_FULL_ACCESS = 14, //Existing inbound share sync or part thereof lost full access LOCAL_FILESYSTEM_MISMATCH = 15, //Filesystem fingerprint does not match the one stored for the synchronization PUT_NODES_ERROR = 16, // Error processing put nodes result ACTIVE_SYNC_BELOW_PATH = 17, // There's a synced node below the path to be synced ACTIVE_SYNC_ABOVE_PATH = 18, // There's a synced node above the path to be synced REMOTE_NODE_MOVED_TO_RUBBISH = 19, // Moved to rubbish REMOTE_NODE_INSIDE_RUBBISH = 20, // Attempted to be added in rubbish VBOXSHAREDFOLDER_UNSUPPORTED = 21, // Found unsupported VBoxSharedFolderFS LOCAL_PATH_SYNC_COLLISION = 22, //Local path includes a synced path or is included within one ACCOUNT_BLOCKED= 23, // Account blocked UNKNOWN_TEMPORARY_ERROR = 24, // unknown temporary error TOO_MANY_ACTION_PACKETS = 25, // Too many changes in account, local state discarded LOGGED_OUT = 26, // Logged out //WHOLE_ACCOUNT_REFETCHED = 27, // obsolete. was: The whole account was reloaded, missed actionpacket changes could not have been applied //MISSING_PARENT_NODE = 28, // obsolete. was: Setting a new parent to a parent whose LocalNode is missing its corresponding Node crossref BACKUP_MODIFIED = 29, // Backup has been externally modified. BACKUP_SOURCE_NOT_BELOW_DRIVE = 30, // Backup source path not below drive path. SYNC_CONFIG_WRITE_FAILURE = 31, // Unable to write sync config to disk. ACTIVE_SYNC_SAME_PATH = 32, // There's a synced node at the path to be synced COULD_NOT_MOVE_CLOUD_NODES = 33, // rename() failed COULD_NOT_CREATE_IGNORE_FILE = 34, // Couldn't create a sync's initial ignore file. SYNC_CONFIG_READ_FAILURE = 35, // Couldn't read sync configs from disk. UNKNOWN_DRIVE_PATH = 36, // Sync's drive path isn't known. INVALID_SCAN_INTERVAL = 37, // The user's specified an invalid scan interval. NOTIFICATION_SYSTEM_UNAVAILABLE = 38, // Filesystem notification subsystem has encountered an unrecoverable error. UNABLE_TO_ADD_WATCH = 39, // Unable to add a filesystem watch. UNABLE_TO_RETRIEVE_ROOT_FSID = 40, // Unable to retrieve a sync root's FSID. UNABLE_TO_OPEN_DATABASE = 41, // Unable to open state cache database. INSUFFICIENT_DISK_SPACE = 42, // Insufficient space for download. FAILURE_ACCESSING_PERSISTENT_STORAGE = 43, // Failure accessing to persistent storage MISMATCH_OF_ROOT_FSID = 44, // The sync root's FSID changed. So this is a different folder. And, we can't identify the old sync db as the name depends on this FILESYSTEM_FILE_IDS_ARE_UNSTABLE = 45, // On MAC in particular, the FSID of a file in an exFAT drive can and does change spontaneously and frequently FILESYSTEM_ID_UNAVAILABLE = 46, // Could not get the filesystem's id UNABLE_TO_RETRIEVE_DEVICE_ID = 47, // Unable to retrieve the ID of current device LOCAL_PATH_MOUNTED = 48, // The local path is a FUSE mount. }; enum Warning { NO_SYNC_WARNING = 0, LOCAL_IS_FAT = 1, // Found FAT (not a failure per se) LOCAL_IS_HGFS= 2, // Found HGFS (not a failure per se) }; enum SyncType { TYPE_UNKNOWN = 0, TYPE_TWOWAY = 3, // Two-way sync TYPE_BACKUP, // special sync up from local to remote, automatically disabled when remote changed }; enum SyncRunningState { RUNSTATE_PENDING = 0, // Sync config has loaded but we have not attempted to start it yet RUNSTATE_LOADING = 1, // Sync DB is in the process of loading from disk RUNSTATE_RUNNING = 2, // Sync DB is loaded and active // RUNSTATE_PAUSED = 3, (obsolete) Use RUNSTATE_SUSPENDED for paused syncs / backups RUNSTATE_SUSPENDED = 4, // Sync DB is not loaded, but it is on disk with the last known sync state. RUNSTATE_DISABLED = 5, // Sync DB does not exist. Starting it is like configuring a brand // new sync with those settings. }; virtual ~MegaSync(); /** * @brief Creates a copy of this MegaSync object * * The resulting object is fully independent of the source MegaSync, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaError object */ virtual MegaSync *copy(); /** * @brief Get the handle of the folder that is being synced * @return Handle of the folder that is being synced in MEGA */ virtual MegaHandle getMegaHandle() const; /** * @brief Get the path of the local folder that is being synced * * The SDK retains the ownership of the returned value. It will be valid until * the MegaSync object is deleted. * * @return Local folder that is being synced */ virtual const char* getLocalFolder() const; /** * @brief Get the name of the sync * * When the app did not provide an specific name, it will return the leaf * name of the local folder. * * The SDK retains the ownership of the returned value. It will be valid until * the MegaSync object is deleted. * * @return Name given to the sync */ virtual const char* getName() const; /** * @brief Get the last known path of the remote folder that is being synced * * The SDK retains the ownership of the returned value. It will be valid until * the MegaSync object is deleted. * * @return The path of the Remote folder from when it was last being synced */ virtual const char* getLastKnownMegaFolder() const; /** * @brief Returns the identifier of this synchronization * * Identifiers of synchronizations are always negative numbers. * * @return Identifier of the synchronization */ virtual MegaHandle getBackupId() const; /** * @brief Get the error of a synchronization * * Possible values are those in MegaSync::Error. Eg. * - NO_SYNC_ERROR = 0: No error * @return Error of a synchronization */ virtual int getError() const; /** * @brief Get the warning of a synchronization * * Possible values are: * - NO_SYNC_WARNING = 0: No warning * - LOCAL_IS_FAT = 1: Found FAT (not a failure per se) * - LOCAL_IS_HGFS = 2: Found HGFS (not a failure per se) * * @return Warning of a synchronization */ virtual int getWarning() const; /** * @brief Get the type of sync * * See possible values in MegaSync::SyncType. * * @return Type of sync */ virtual int getType() const; /** * @brief Returns the current run-state of the sync * * Returns one of the enum values from SyncRunningState * * @return one of the enum values from SyncRunningState. */ virtual int getRunState() const; /** * @brief Returns a readable description of the sync error * * Returns a text equivalent (in english) to getError() * * This gives the reason that the sync is in RUNSTATE_SUSPENDED * or RUNSTATE_DISABLED state. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Readable description of the error */ const char * getMegaSyncErrorCode(); /** * @brief Provides the error description associated with a sync error code * * You take ownership of the returned value. Use delete[] to release the memory. * * @param errorCode Error code for which the description will be returned * @return Description associated with the error code */ static const char* getMegaSyncErrorCode(int errorCode); /** * @brief Returns a readable description of the sync warning * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable description of the warning */ const char * getMegaSyncWarningCode(); /** * @brief Provides the warning description associated with a sync warning code * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @param warningCode Warning code for which the description will be returned * @return Description associated with the warning code */ static const char *getMegaSyncWarningCode(int warningCode); }; /** * @brief Counts of files/folders/uploads/downloads per Sync * * The sync is the one identified by the backupId. * The other fields are self-explanatory * * Objects of this class are immutable. */ class MegaSyncStats { public: /** @brief Get the backupId that identifies the Sync * @return The sync's BackupID */ virtual MegaHandle getBackupId() const = 0; /** @brief Indicates whether the sync is scanning currently * Scanning means reading the folder entries on local disks */ virtual bool isScanning() const = 0; /** @brief Indicates whether the sync is syncing currently * Syncing means comparing the two sides and bringing them in line */ virtual bool isSyncing() const = 0; /** @brief Indicates how many folders the sync contains */ virtual int getFolderCount() const = 0; /** @brief Indicates how many files the sync contains */ virtual int getFileCount() const = 0; /** @brief Indicates how many files are being uploaded */ virtual int getUploadCount() const = 0; /** @brief Indicates how many files are being downloaded */ virtual int getDownloadCount() const = 0; /** @brief Make a copy of this object * You take ownership of the result. */ virtual MegaSyncStats *copy() const = 0; virtual ~MegaSyncStats() = default; }; /** * @brief List of MegaSync objects * * A MegaSyncList has the ownership of the MegaSync objects that it contains, so they will be * only valid until the SyncList is deleted. If you want to retain a MegaMode returned by * a MegaSyncList, use MegaSync::copy. * * Objects of this class are immutable. * * @see MegaApi::getChildren, MegaApi::search, MegaApi::getInShares */ class MegaSyncList { protected: MegaSyncList(); public: /** * @brief Creates a new instance of MegaSyncList * @return A pointer to the superclass of the private object */ static MegaSyncList * createInstance(); virtual ~MegaSyncList(); virtual MegaSyncList *copy() const; /** * @brief Returns the MegaSync at the position i in the MegaSyncList * * The MegaSyncList retains the ownership of the returned MegaSync. It will be only valid until * the MegaSyncList is deleted. * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaSync that we want to get for the list * @return MegaSync at the position i in the list */ virtual MegaSync* get(int i) const; /** * @brief Returns the number of MegaSync objects in the list * @return Number of MegaSync objects in the list */ virtual int size() const; /** * @brief Add new sync to list * @param sync MegaSync to be added. The sync inserted is a copy from 'sync' */ virtual void addSync(MegaSync* sync); }; /** * @brief A synchronization conflict that requires user intervention to be solved */ class MegaSyncStall { public: MegaSyncStall() = default; virtual ~MegaSyncStall() = default; enum SyncStallReason { NoReason = 0, FileIssue, MoveOrRenameCannotOccur, DeleteOrMoveWaitingOnScanning, DeleteWaitingOnMoves, UploadIssue, DownloadIssue, CannotCreateFolder, CannotPerformDeletion, SyncItemExceedsSupportedTreeDepth, FolderMatchedAgainstFile, LocalAndRemoteChangedSinceLastSyncedState_userMustChoose, LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose, NamesWouldClashWhenSynced, SyncStallReason_LastPlusOne }; enum SyncPathProblem { NoProblem = 0, FileChangingFrequently = 1, IgnoreRulesUnknown = 2, DetectedHardLink = 3, DetectedSymlink = 4, DetectedSpecialFile = 5, DifferentFileOrFolderIsAlreadyPresent = 6, ParentFolderDoesNotExist = 7, FilesystemErrorDuringOperation = 8, NameTooLongForFilesystem = 9, CannotFingerprintFile = 10, DestinationPathInUnresolvedArea = 11, MACVerificationFailure = 12, UnknownDownloadIssue = 13, DeletedOrMovedByUser = 14, FileFolderDeletedByUser = 15, MoveToDebrisFolderFailed = 16, IgnoreFileMalformed = 17, FilesystemErrorListingFolder = 18, // FilesystemErrorIdentifyingFolderContent = 19, -> obsolete WaitingForScanningToComplete = 20, WaitingForAnotherMoveToComplete = 21, SourceWasMovedElsewhere = 22, FilesystemCannotStoreThisName = 23, CloudNodeInvalidFingerprint = 24, CloudNodeIsBlocked = 25, PutnodeDeferredByController = 26, PutnodeCompletionDeferredByController = 27, PutnodeCompletionPending = 28, UploadDeferredByController = 29, DetectedNestedMount = 30, SyncPathProblem_LastPlusOne }; /** * @brief Creates a copy of this MegaSyncStall object * * You are the owner of the returned object * * @return Copy of the MegaSyncStall object */ virtual MegaSyncStall* copy() const = 0; /** * @return reason for the sync stall */ virtual SyncStallReason reason() const = 0; /** * @return a human readable (english only) representation of the sync stall reason. @see SyncStallReason */ virtual const char* reasonDebugString() const = 0; /** * Retrieves a specific path involved in a sync stall. * * This method returns a string representing a path associated with the sync stall, aiding * in explaining the stall to the user. * * Notes: * - To retrieve all paths involved in the stall, iterate over `index` values starting from * `0` for both `cloudSide` values (`true` and `false`) until the function returns `NULL`. * - Empty paths (when the method returns `NULL`) should be ignored unless there is a * corresponding `pathProblem` for that path. * - The maximum number of paths is usually two. * * @param cloudSide true to retrieve the information for the cloud path, `false` for the * local. * @param index The index of the path; valid values are typically `0` or `1`. * @return A pointer to a character string containing the path, or `NULL` if no path is * associated with the specified `cloudSide` and `index`. */ virtual const char* path(bool cloudSide, int index) const = 0; /** * For cloud-side paths, call this function to get the * corresponding node handle (if any) */ virtual MegaHandle cloudNodeHandle(int index) const = 0; /** * To get the count of paths * * @return path count involved in the sync stall */ virtual unsigned int pathCount(bool cloudSide) const = 0; /** * Retrieves a code representing a problem associated with a specific path involved in a * sync stall. * * This method returns an integer corresponding to a `SyncPathProblem` enum value that * describes a condition of the specified path, helping to explain the stall to the user. * * Notes: * - The `-1` value acts as a sentinel indicating "no problem" or "not applicable." * - Some stall types may always return `-1` if path problems are not relevant for them. * * @param cloudSide true to retrieve the information for the cloud path, `false` for the * local. * @param index The index of the path; valid values are `0` or `1`. * @return An integer corresponding to a `SyncPathProblem` enum value, or `-1` if there is * no problem or the path is does not apply for this stall. */ virtual int pathProblem(bool cloudSide, int index) const = 0; /** * For some casess, it's likely that a sutiable course of action * for the user is to add the problematic path to .megaignore * This function advises if the GUI could/should offer a * shortcut button to do that. * The path in question would be the one from pathProblem with * the same arguments (but expressed as a cloud path from only the * sync root) * * @return local path involved in the sync stall */ virtual bool couldSuggestIgnoreThisPath(bool cloudSide, int index) const = 0; /** * Use this method for move problems to indicate which side * the move was detected on, and therefore the other side * is where the move could not be repliated. */ virtual bool detectedCloudSide() const = 0; /** * @brief Get an unique identifier for the MegaSyncStall object that takes into account all * the information it stores. */ virtual size_t getHash() const = 0; }; /** * @brief A list of synchronization stall conflicts @see MegaSyncStall */ class MegaSyncStallList { public: MegaSyncStallList() = default; virtual ~MegaSyncStallList() = default; virtual MegaSyncStallList* copy() const; /** * @param index of the request element in the list. * @return constant pointer to a MegaSyncStall stored in the container. */ virtual const MegaSyncStall* get(size_t index) const; /** * @return number of elements in the list. */ virtual size_t size() const; /** * @brief Get an unique identifier that is calculated combining the hashes of all the * elements in the container. The order of the elements also affects the final hash. */ virtual size_t getHash() const; }; /** * @brief A Map of BackupId to list of synchronization stall conflicts @see MegaSyncStall */ class MegaSyncStallMap { public: MegaSyncStallMap() = default; virtual ~MegaSyncStallMap() = default; virtual MegaSyncStallMap* copy() const; /** * @brief Returns the number of elements in the MegaSyncStallMap. * * @return The number of elements in the MegaSyncStallMap. */ virtual size_t size() const; /** * @brief Get an unique identifier that is calculated combining the hashes of all the * elements in the container. The order of the elements also affects the final hash. * * @return A combined hash value of all MegaSyncStall elements in the map. */ virtual size_t getHash() const; /** * @brief Retrieves a MegaSyncStallList object associated with the given key. * * The SDK retains the ownership of the MegaSyncStall object. * * @param key MegaHandle to look for in the stalls map. * @return A pointer to the MegaSyncStallList object associated with the key, or nullptr if not * found. */ virtual const MegaSyncStallList* get(const MegaHandle key) const; /** * @brief Retrieves a list of all keys present in the MegaSyncStallMap. * * This method creates and returns a MegaHandleList containing all the keys * currently present in the internal map of stalls. * * @return A MegaHandleList containing all keys(BackupId's) from the stalls map. */ virtual MegaHandleList* getKeys() const; }; #endif // ENABLE_SYNC /** * @brief Provides information about a backup * * Developers can use listeners (MegaListener, MegaScheduledCopyListener) * to track the progress of each backup. MegaScheduledCopy objects are provided in callbacks sent * to these listeners and allow developers to know the state of the backups and their parameters * and their results. * * The implementation will receive callbacks from an internal worker thread. * **/ class MegaScheduledCopyListener { public: virtual ~MegaScheduledCopyListener(); /** * @brief This function is called when the state of the backup changes * * The SDK calls this function when the state of the backup changes, for example * from 'active' to 'ongoing' or 'removing exceeding'. * * You can use MegaScheduledCopy::getState to get the new state. * * @param api MegaApi object that is backing up files * @param backup MegaScheduledCopy object that has changed the state */ virtual void onBackupStateChanged(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when a backup is about to start being processed * * The SDK retains the ownership of the backup parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the backup * @param backup Information about the backup */ virtual void onBackupStart(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when a backup has finished * * The SDK retains the ownership of the backup and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * There won't be more callbacks about this backup. * The last parameter provides the result of the backup: * If the backup finished without problems, * the error code will be API_OK. * If some transfer failed, the error code will be API_EINCOMPLETE. * If the backup has been skipped the error code will be API_EEXPIRED. * If the backup folder cannot be found, the error will be API_ENOENT. * * * @param api MegaApi object that started the backup * @param backup Information about the backup * @param error Error information */ virtual void onBackupFinish(MegaApi* api, MegaScheduledCopy *backup, MegaError* error); /** * @brief This function is called to inform about the progress of a backup * * The SDK retains the ownership of the backup parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the backup * @param backup Information about the backup * * @see MegaScheduledCopy::getTransferredBytes, MegaScheduledCopy::getSpeed */ virtual void onBackupUpdate(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when there is a temporary error processing a backup * * The backup continues after this callback, so expect more MegaScheduledCopyListener::onBackupTemporaryError or * a MegaScheduledCopyListener::onBackupFinish callback * * The SDK retains the ownership of the backup and error parameters. * Don't use them after this functions returns. * * @param api MegaApi object that started the backup * @param backup Information about the backup * @param error Error information */ virtual void onBackupTemporaryError(MegaApi *api, MegaScheduledCopy *backup, MegaError* error); }; /** * @brief Provides information about a backup */ class MegaScheduledCopy { public: enum { SCHEDULED_COPY_FAILED = -2, SCHEDULED_COPY_CANCELED = -1, SCHEDULED_COPY_INITIALSCAN = 0, SCHEDULED_COPY_ACTIVE, SCHEDULED_COPY_ONGOING, SCHEDULED_COPY_SKIPPING, SCHEDULED_COPY_REMOVING_EXCEEDING }; virtual ~MegaScheduledCopy(); /** * @brief Creates a copy of this MegaScheduledCopy object * * The resulting object is fully independent of the source MegaScheduledCopy, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaScheduledCopy object */ virtual MegaScheduledCopy *copy(); /** * @brief Get the handle of the folder that is being backed up * @return Handle of the folder that is being backed up in MEGA */ virtual MegaHandle getMegaHandle() const; /** * @brief Get the path of the local folder that is being backed up * * The SDK retains the ownership of the returned value. It will be valid until * the MegaScheduledCopy object is deleted. * * @return Local folder that is being backed up */ virtual const char* getLocalFolder() const; /** * @brief Returns the identifier of this backup * * @return Identifier of the backup */ virtual int getTag() const; /** * @brief Returns if backups that should have happen in the past should be taken care of * * @return Whether past backups should be taken care of */ virtual bool getAttendPastBackups() const; /** * @brief Returns the period of the backup * * @return The period of the backup in deciseconds */ virtual int64_t getPeriod() const; /** * @brief Returns the period string of the backup * Any of these 6 fields may be an asterisk (*). This would mean the entire range of possible values, i.e. each minute, each hour, etc. * * Period is formatted as follows * - - - - - - * | | | | | | * | | | | | | * | | | | | +---- Day of the Week (range: 1-7, 1 standing for Monday) * | | | | +------ Month of the Year (range: 1-12) * | | | +-------- Day of the Month (range: 1-31) * | | +---------- Hour (range: 0-23) * | +------------ Minute (range: 0-59) * +-------------- Second (range: 0-59) * * E.g: * - daily at 04:00:00 (UTC): "0 0 4 * * *" * - every 15th day at 00:00:00 (UTC) "0 0 0 15 * *" * - mondays at 04.30.00 (UTC): "0 30 4 * * 1" * * @return The period string of the backup */ virtual const char *getPeriodString() const; /** * @brief Returns the next absolute timestamp of the next backup. * @param oldStartTimeAbsolute Reference timestamp of the previous backup. If none provided it'll use current one. * * Successive nested calls to this functions will give you a full schedule of the next backups. * * Timestamp measures are given in number of seconds that elapsed since January 1, 1970 (midnight UTC/GMT), * not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). * * @return timestamp of the next backup. */ virtual long long getNextStartTime(long long oldStartTimeAbsolute = -1) const; /** * @brief Returns the number of backups to keep * * @return Maximun number of Backups to store */ virtual int getMaxBackups() const; /** * @brief Get the state of the backup * * Possible values are: * - SCHEDULED_COPY_FAILED = -2 * The backup has failed and has been disabled * * - SCHEDULED_COPY_CANCELED = -1, * The backup has failed and has been disabled * * - SCHEDULED_COPY_INITIALSCAN = 0, * The backup is doing the initial scan * * - SCHEDULED_COPY_ACTIVE * The backup is active * * - SCHEDULED_COPY_ONGOING * A backup is being performed * * - SCHEDULED_COPY_SKIPPING * A backup is being skipped * * - SCHEDULED_COPY_REMOVING_EXCEEDING * The backup is active and an exceeding backup is being removed * @return State of the backup */ virtual int getState() const; // Current backup data: /** * @brief Returns the number of folders created in the backup * @return number of folders created in the backup */ virtual long long getNumberFolders() const; /** * @brief Returns the number of files created in the backup * @return number of files created in the backup */ virtual long long getNumberFiles() const; /** * @brief Returns the number of files to be created in the backup * @return number of files to be created in the backup */ virtual long long getTotalFiles() const; /** * @brief Returns the starting time of the current backup being processed (in deciseconds) * * The returned value is a monotonic time since some unspecified starting point expressed in * deciseconds. * * @return Starting time of the backup (in deciseconds) */ virtual int64_t getCurrentBKStartTime() const; /** * @brief Returns the number of transferred bytes during last backup * @return Transferred bytes during this backup */ virtual long long getTransferredBytes() const; /** * @brief Returns the total bytes to be transferred to complete last backup * @return Total bytes to be transferred to complete the backup */ virtual long long getTotalBytes() const; /** * @brief Returns the current speed of last backup * @return Current speed of this backup */ virtual long long getSpeed() const; /** * @brief Returns the average speed of last backup * @return Average speed of this backup */ virtual long long getMeanSpeed() const; /** * @brief Returns the timestamp when the last data was received (in deciseconds) * * This timestamp doesn't have a defined starting point. Use the difference between * the return value of this function and MegaScheduledCopy::getCurrentBKStartTime to know how * much time the backup has been running. * * @return Timestamp when the last data was received (in deciseconds) */ virtual int64_t getUpdateTime() const; /** * @brief Returns the list with the transfers that have failed for during last backup * * You take the ownership of the returned value * * @return Names of the custom attributes of the node * @see MegaApi::setCustomNodeAttribute */ virtual MegaTransferList *getFailedTransfers(); }; /** * @brief Provides information about an error */ class MegaError { public: /** * @brief Declaration of API error codes. */ enum { API_OK = 0, ///< Everything OK API_EINTERNAL = -1, ///< Internal error. API_EARGS = -2, ///< Bad arguments. API_EAGAIN = -3, ///< Request failed, retry with exponential back-off. API_ERATELIMIT = -4, ///< Too many requests, slow down. API_EFAILED = -5, ///< Request failed permanently. API_ETOOMANY = -6, ///< Too many requests for this resource. API_ERANGE = -7, ///< Resource access out of range. API_EEXPIRED = -8, ///< Resource expired. API_ENOENT = -9, ///< Resource does not exist. API_ECIRCULAR = -10, ///< Circular linkage. API_EACCESS = -11, ///< Access denied. API_EEXIST = -12, ///< Resource already exists. API_EINCOMPLETE = -13, ///< Request incomplete. API_EKEY = -14, ///< Cryptographic error. API_ESID = -15, ///< Bad session ID. API_EBLOCKED = -16, ///< Resource administratively blocked. API_EOVERQUOTA = -17, ///< Quota exceeded. API_ETEMPUNAVAIL = -18, ///< Resource temporarily not available. API_ETOOMANYCONNECTIONS = -19, ///< Too many connections on this resource. API_EWRITE = -20, ///< File could not be written to (or failed post-write integrity check). API_EREAD = -21, ///< File could not be read from (or changed unexpectedly during reading). API_EAPPKEY = -22, ///< Invalid or missing application key. API_ESSL = -23, ///< SSL verification failed API_EGOINGOVERQUOTA = -24, ///< Not enough quota API_EROLLEDBACK = -25, ///< A strongly-grouped request was rolled back. API_EMFAREQUIRED = -26, ///< Multi-factor authentication required API_EMASTERONLY = -27, ///< Access denied for sub-users (only for business accounts) API_EBUSINESSPASTDUE = -28, ///< Business account expired API_EPAYWALL = -29, ///< Over Disk Quota Paywall API_ESUBUSERKEYMISSING = -30, ///< A business error where a subuser has not yet encrypted /// their master key for the admin user and tries to perform /// a disallowed command (currently u and p) PAYMENT_ECARD = -101, PAYMENT_EBILLING = -102, PAYMENT_EFRAUD = -103, PAYMENT_ETOOMANY = -104, PAYMENT_EBALANCE = -105, PAYMENT_EGENERIC = -106, LOCAL_ENOSPC = -1000, ///< Insufficient space. LOCAL_ETIMEOUT = -1001, ///< A request timed out. LOCAL_ENETWORK = -1003, ///< Local network error }; /** * @brief Api error code context. */ enum ErrorContexts { API_EC_DEFAULT = 0, ///< Default error code context API_EC_DOWNLOAD = 1, ///< Download transfer context. API_EC_IMPORT = 2, ///< Import context. API_EC_UPLOAD = 3, ///< Upload transfer context. }; /** * @brief User custom error details */ enum UserErrorCode { USER_ETD_UNKNOWN = -1, USER_ENABLED = 0, USER_PENDINGCONFIRMATION = 1, USER_SUSPENDED_GENERIC = 2, USER_SUSPENDED_PAYMENT = 3, USER_COPYRIGHT_SUSPENSION = 4, USER_SUSPENDED_ADMIN_FULLDISABLE = 5, USER_SUSPENDED_ADMIN_PARTIALDISABLE = 6, USER_ETD_SUSPENSION = 7, USER_SUSPENDED_SMSVERIFICATIONREQUIRED = 8, USER_SUSPENDED_EMAILVERIFICATIONREQUIRED = 9, USER_SUBACCOUNT_PENDINGCONFIRMATION = 10, USER_SUBACCOUNT_DISABLED = 11, USER_SUBACCOUNT_DELETED = 12, USER_BUSINESSACCOUNT = 20, USER_SUSPENDED_PASSWORD_CHANGE_REQUIRED = 21, USER_EPHEMERAL_RESELLER_USER = 22, USER_SUSPENDED_NOUSER = 99, }; /** * @brief Link custom error details */ enum LinkErrorCode { LINK_UNKNOWN = -1, ///< Unknown state LINK_UNDELETED = 0, ///< Link is undeleted LINK_DELETED_DOWN = 1, ///< Link is deleted or down LINK_DOWN_ETD = 2, ///< Link is down due to an ETD specifically }; virtual ~MegaError(); /** * @brief Creates a copy of this MegaError object * * The resulting object is fully independent of the source MegaError, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaError object */ virtual MegaError* copy() const; /** * @brief Returns the error code associated with this MegaError * * @return Error code, an Errors enum, associated with this MegaError */ virtual int getErrorCode() const; /** * @brief * Retrieve the result of the last mount operation. * * @return * An element of the MegaMount::Result enumeration. * * @note * This member is only relevant for the following request types: * - TYPE_ADD_MOUNT * - TYPE_DISABLE_MOUNT * - TYPE_ENABLE_MOUNT * - TYPE_REMOVE_MOUNT * - TYPE_SET_MOUNT_FLAGS */ virtual int getMountResult() const; /** * @brief Returns the sync error associated with this MegaError * * @return MegaSync::Error associated with this MegaError */ virtual int getSyncError() const; /** * @brief Returns a value associated with the error * * Currently, this value is only useful when it is related to an API_EOVERQUOTA * error related to a transfer. In that case, it's the number of seconds until * the more bandwidth will be available for the account. * * In any other case, this value will be 0 * * @return Value associated with the error */ virtual long long getValue() const; /** * @brief Returns true if error has extra info * * @note This method can return true for: * - MegaRequest::TYPE_FETCH_NODES with error ENOENT * - MegaRequest::TYPE_GET_PUBLIC_NODE with error ETOOMANY * - MegaRequest::TYPE_IMPORT_LINK with error ETOOMANY * - MegaTransferListener::onTransferFinish with error ETOOMANY * * @return True if error has extra info */ virtual bool hasExtraInfo() const; /** * @brief Returns the user status * * This method only returns a valid value when hasExtraInfo is true * Possible values are defined in MegaError::UserErrorCode * * Otherwise, it returns MegaError::UserErrorCode::USER_ETD_UNKNOWN * * @return user status */ virtual long long getUserStatus() const; /** * @brief Returns the link status * * This method only returns a valid value when hasExtraInfo is true * Possible values: * MegaError::LinkErrorCode::LINK_UNDELETED * MegaError::LinkErrorCode::LINK_DELETED_DOWN * MegaError::LinkErrorCode::LINK_DOWN_ETD * * Otherwise, it returns MegaError::LinkErrorCode::LINK_UNKNOWN * * @return link status */ virtual long long getLinkStatus() const; /** * @brief Returns a readable description of the error * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @return Readable description of the error */ virtual const char* getErrorString() const; /** * @brief Returns a readable description of the error * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * This function provides exactly the same result as MegaError::getErrorString. * It's provided for a better Java compatibility * * @return Readable description of the error */ virtual const char* toString() const; /** * @brief Provides the error description associated with an error code * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @param errorCode Error code for which the description will be returned * @return Description associated with the error code */ static const char *getErrorString(int errorCode); /** * @brief Provides the error description associated with an error code * given a certain context. * * This function returns a pointer to a statically allocated buffer. * You don't have to free the returned pointer * * @param errorCode Error code for which the description will be returned * @param context Context to provide a more accurate description (MegaError::ErrorContexts) * @return Description associated with the error code */ static const char *getErrorString(int errorCode, ErrorContexts context); protected: MegaError(int e); MegaError(int e, int se); //< 0 = API error code, > 0 = http error, 0 = No error // MegaError::Errors enum/ErrorCodes int errorCode; // SyncError/MegaSync::Error int syncError; friend class MegaTransfer; friend class MegaApiImpl; }; /** * @brief Interface to process node trees * * An implementation of this class can be used to process a node tree passing a pointer to * MegaApi::processMegaTree * * The implementation will receive callbacks from an internal worker thread. * */ class MegaTreeProcessor { public: /** * @brief Function that will be called for all nodes in a node tree * @param node Node to be processed * @return true to continue processing nodes, false to stop */ virtual bool processMegaNode(MegaNode* node); virtual ~MegaTreeProcessor(); }; /** * @brief Interface to receive information about requests * * All requests allows to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all requests using MegaApi::addRequestListener * * MegaListener objects can also receive information about requests * * This interface uses MegaRequest objects to provide information of requests. Take into account that not all * fields of MegaRequest objects are valid for all requests. See the documentation about each request to know * which fields contain useful information for each one. * * The implementation will receive callbacks from an internal worker thread. * */ class MegaRequestListener { public: /** * @brief This function is called when a request is about to start being processed * * The SDK retains the ownership of the request parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request */ virtual void onRequestStart(MegaApi* api, MegaRequest *request); /** * @brief This function is called when a request has finished * * There won't be more callbacks about this request. * The last parameter provides the result of the request. If the request finished without problems, * the error code will be API_OK * * The SDK retains the ownership of the request and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request * @param e Error information */ virtual void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError* e); /** * @brief This function is called to inform about the progres of a request * * Currently, this callback is only used for fetchNodes (MegaRequest::TYPE_FETCH_NODES) requests * * The SDK retains the ownership of the request parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * * @param api MegaApi object that started the request * @param request Information about the request * @see MegaRequest::getTotalBytes MegaRequest::getTransferredBytes */ virtual void onRequestUpdate(MegaApi*api, MegaRequest *request); /** * @brief This function is called when there is a temporary error processing a request * * The request continues after this callback, so expect more MegaRequestListener::onRequestTemporaryError or * a MegaRequestListener::onRequestFinish callback * * The SDK retains the ownership of the request and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request * @param error Error information */ virtual void onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError* error); virtual ~MegaRequestListener(); }; /** * @brief This class extendes the functionality of MegaRequestListener * allowing a synchronous behaviour * It can be used the same way as a MegaRequestListener by overriding doOnRequestFinish * instead of onRequestFinish. This function will be called * when onRequestFinish is called by the SDK. * * For a synchronous usage, a client for this listener may wait() until the request is finished and doOnRequestFinish is completed. * Alternatively a trywait function is included which waits for an amount of time or until the request is finished. * Then it can gather the MegaError and MegaRequest objects to process the outcome of the request. * * @see MegaRequestListener */ class SynchronousRequestListener : public MegaRequestListener { private: MegaSemaphore* semaphore; void onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *error); protected: MegaRequestListener *listener; MegaApi *megaApi; MegaRequest *megaRequest; MegaError *megaError; public: SynchronousRequestListener(); /** * @brief This function is called when a request has finished * * There won't be more callbacks about this request. * The last parameter provides the result of the request. If the request finished without problems, * the error code will be API_OK * * The SDK retains the ownership of the request and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request * @param error Error information */ virtual void doOnRequestFinish(MegaApi *api, MegaRequest *request, MegaError *error); /** * @brief Wait untill the request is finished. This means that the request has been processed and * doOnRequestFinish is completed. * After successfully waiting for the request to be finished, the caller can use getError() and getRequest() * to gather the output and errors produced by the request. Thus, implementing the callback doOnRequestFinish * is not required and the processing can be coded more linearly. * */ void wait(); /** * @brief Waits untill either the request is finished or the provided time is passed. * * After successfully waiting for the request to be finished, the caller can use getError() and getRequest() * to gather the output and errors produced by the request. Thus, implementing the callback doOnRequestFinish * is not required and the processing can be coded more linearly. * @param milliseconds Max number of milliseconds to wait. * @return returns 0 if the request had finished and a value different to 0 if timeout passed. */ int trywait(int milliseconds); /** * @brief Get the MegaError object produced by the request. * The RequestListener retains the ownership of the object and will delete upon its destruction * @return the error */ MegaError *getError() const; /** * @brief Get the MegaRequest object produced by the request. * The RequestListener retains the ownership of the object and will delete upon its destruction * @return the request */ MegaRequest *getRequest() const; /** * @brief Getter for the MegaApi object that started the request. * @return the MegaApi object that started the request. */ MegaApi *getApi() const; virtual ~SynchronousRequestListener(); }; /** * @brief Interface to receive information about transfers * * All transfers allows to pass a pointer to an implementation of this interface in the last parameter. * You can also get information about all transfers using MegaApi::addTransferListener * * MegaListener objects can also receive information about transfers * * The implementation will receive callbacks from an internal worker thread. * */ class MegaTransferListener { public: /** * @brief This function is called when a transfer is about to start being processed * * The SDK retains the ownership of the transfer parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer */ virtual void onTransferStart(MegaApi *api, MegaTransfer *transfer); /** * @brief This function is called when a transfer has finished * * The SDK retains the ownership of the transfer and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * There won't be more callbacks about this transfer. * The last parameter provides the result of the transfer. If the transfer finished without problems, * the error code will be API_OK * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param error Error information */ virtual void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* error); /** * @brief This function is called to inform about the progress of a transfer * * The SDK retains the ownership of the transfer parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * In case this transfer represents a recursive operation (folder upload/download) SDK will * notify apps about the stages transition. * * Current recursive operation stage can be retrieved with method MegaTransfer::getStage. * This method returns the following values: * - MegaTransfer::STAGE_SCAN = 1 * - MegaTransfer::STAGE_CREATE_TREE = 2 * - MegaTransfer::STAGE_TRANSFERRING_FILES = 3 * For more information about stages refer to MegaTransfer::getStage * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * * @see MegaTransfer::getTransferredBytes, MegaTransfer::getSpeed, MegaTransfer::getStage */ virtual void onTransferUpdate(MegaApi *api, MegaTransfer *transfer); /** * @brief This function is called to inform about the progress of a folder transfer * * The SDK retains the ownership of all parameters. * Don't use any after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * This callback is only made for folder transfers, and only to the listener for that * transfer, not for any globally registered listeners. The callback is only made * during the scanning phase. * * This function can be used to give feedback to the user as to how scanning is progressing, * since scanning may take a while and the application may be showing a modal dialog during * this time. * * Note that this function could be called from a variety of threads during the * overall operation, so proper thread safety should be observed. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @stage MegaTransfer::STAGE_SCAN or a later value in that enum * @param foldercount The count of folders scanned so far * @param foldercount The count of folders created so far (only relevant in MegaTransfer::STAGE_CREATE_TREE) * @param filecount The count of files scanned (and fingerprinted) so far. 0 if not in scanning stage * @param currentFolder The path of the folder currently being scanned (NULL except in the scan stage) * @param currentFileLeafname The leaft name of the file currently being fingerprinted (can be NULL for the first call in a new folder, and when not scanning anymore) */ virtual void onFolderTransferUpdate(MegaApi *api, MegaTransfer *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char* currentFolder, const char* currentFileLeafname); /** * @brief This function is called when there is a temporary error processing a transfer * * The transfer continues after this callback, so expect more MegaTransferListener::onTransferTemporaryError or * a MegaTransferListener::onTransferFinish callback * * The SDK retains the ownership of the transfer and error parameters. * Don't use them after this functions returns. * * If the error code is API_EOVERQUOTA we need to call to MegaTransfer::isForeignOverquota to determine if * our own storage, or a foreign storage is in overquota. If MegaTransfer::isForeignOverquota returns true * a foreign storage is in overquota, otherwise our own storage is in overquota. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param error Error information */ virtual void onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError* error); virtual ~MegaTransferListener(); /** * @brief This function is called to provide the last read bytes of streaming downloads * * This function won't be called for non streaming downloads. You can get the same buffer * provided by this function in MegaTransferListener::onTransferUpdate, using * MegaTransfer::getLastBytes MegaTransfer::getDeltaSize. * * The SDK retains the ownership of the transfer and buffer parameters. * Don't use them after this functions returns. * * This callback is mainly provided for compatibility with other programming languages. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param buffer Buffer with the last read bytes * @param size Size of the buffer * @return true to continue the transfer, false to cancel it * * @see MegaApi::startStreaming */ virtual bool onTransferData(MegaApi *api, MegaTransfer *transfer, char *buffer, size_t size); }; /** * @brief This class extendes the functionality of MegaTransferListener * allowing a synchronous behaviour * It can be used the same way as a MegaTransferListener by overriding doOnTransferFinish * instead of onTransferFinish. This function will be called * when onTransferFinish is called by the SDK. * * For a synchronous usage, a client for this listener may wait() until the transfer is finished and doOnTransferFinish is completed. * Alternatively a trywait function is included which waits for an amount of time or until the transfer is finished. * Then it can gather the MegaError and MegaTransfer objects to process the outcome of the transfer. * * @see MegaTransferListener */ class SynchronousTransferListener : public MegaTransferListener { private: MegaSemaphore* semaphore; void onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *error); protected: MegaTransferListener *listener; MegaApi *megaApi; MegaTransfer *megaTransfer; MegaError *megaError; public: SynchronousTransferListener(); /** * @brief This function is called when a transfer has finished * * There won't be more callbacks about this transfer. * The last parameter provides the result of the transfer. If the transfer finished without problems, * the error code will be API_OK * * The SDK retains the ownership of the transfer and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param error Error information */ virtual void doOnTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *error); /** * @brief Wait untill the transfer is finished. This means that the transfer has been processed and * doOnTransferFinish is completed. * After successfully waiting for the transfer to be finished, the caller can use getError() and getTransfer() * to gather the output and errors produced by the transfer. Thus, implementing the callback doOnTransferFinish * is not required and the processing can be coded more linearly. * */ void wait(); /** * @brief Waits untill either the transfer is finished or the provided time is passed. * * After successfully waiting for the transfer to be finished, the caller can use getError() and getTransfer() * to gather the output and errors produced by the transfer. Thus, implementing the callback doOnTransferFinish * is not required and the processing can be coded more linearly. * @param milliseconds Max number of milliseconds to wait. * @return returns 0 if the transfer had finished and a value different to 0 if timeout passed. */ int trywait(int milliseconds); /** * @brief Get the MegaError object produced by the transfer. * The TransferListener retains the ownership of the object and will delete upon its destruction * @return the error */ MegaError *getError() const; /** * @brief Get the MegaTransfer object produced by the transfer. * The TransferListener retains the ownership of the object and will delete upon its destruction * @return the transfer */ MegaTransfer *getTransfer() const; /** * @brief Getter for the MegaApi object that started the transfer. * @return the MegaApi object that started the transfer. */ MegaApi *getApi() const; virtual ~SynchronousTransferListener(); }; /** * @brief Interface to get information about global events * * You can implement this interface and start receiving events calling MegaApi::addGlobalListener * * MegaListener objects can also receive global events * * The implementation will receive callbacks from an internal worker thread. */ class MegaGlobalListener { public: /** * @brief This function is called when there are new or updated contacts in the account * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaUserList in the second parameter. The list and * all the MegaUser objects that it contains will be valid until this function returns. If * you want to save the list, use MegaUserList::copy. If you want to save only some of the * MegaUser objects, use MegaUser::copy for those objects. * * @param api MegaApi object connected to the account * @param users List that contains the new or updated contacts */ virtual void onUsersUpdate(MegaApi* api, MegaUserList *users); /** * @brief This function is called when there are new or updated user alerts in the account * * When there is a problem parsing the incoming information from the server or the full * account is reloaded or a large number of server notifications arrives at once, the second * parameter will be NULL. * * The SDK retains the ownership of the MegaUserAlertList in the second parameter. The list * and all the MegaUserAlert objects that it contains will be valid until this function * returns. If you want to save the list, use MegaUserAlertList::copy. If you want to save * only some of the MegaUserAlert objects, use MegaUserAlert::copy for those objects. * * @param api MegaApi object connected to the account * @param alerts List that contains the new or updated alerts */ virtual void onUserAlertsUpdate(MegaApi* api, MegaUserAlertList *alerts); /** * @brief This function is called when there are new or updated nodes in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be NULL. * * The SDK retains the ownership of the MegaNodeList in the second parameter. The list and all the * MegaNode objects that it contains will be valid until this function returns. If you want to save the * list, use MegaNodeList::copy. If you want to save only some of the MegaNode objects, use MegaNode::copy * for those nodes. * * @param api MegaApi object connected to the account * @param nodes List that contains the new or updated nodes */ virtual void onNodesUpdate(MegaApi* api, MegaNodeList *nodes); /** * @brief This function is called when the account has been updated (upgraded/downgraded) * * @param api MegaApi object connected to the account */ virtual void onAccountUpdate(MegaApi *api); /** * @brief This function is called when a Set has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaSetList in the second parameter. The list and * all the MegaSet objects that it contains will be valid until this function returns. If * you want to save the list, use MegaSetList::copy. If you want to save only some of the * MegaSet objects, use MegaSet::copy for them. * * @param api MegaApi object connected to the account * @param sets List that contains the new or updated Sets */ virtual void onSetsUpdate(MegaApi* api, MegaSetList* sets); /** * @brief This function is called when a Set-Element has been updated (created / updated / * removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaSetElementList in the second parameter. The list * and all the MegaSetElement objects that it contains will be valid until this function * returns. If you want to save the list, use MegaSetElementList::copy. If you want to save * only some of the MegaSetElement objects, use MegaSetElement::copy for them. * * @param api MegaApi object connected to the account * @param elements List that contains the new or updated Set-Elements */ virtual void onSetElementsUpdate(MegaApi* api, MegaSetElementList* elements); /** * @brief This function is called when there are new or updated contact requests in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be NULL. * * The SDK retains the ownership of the MegaContactRequestList in the second parameter. The list and all the * MegaContactRequest objects that it contains will be valid until this function returns. If you want to save the * list, use MegaContactRequestList::copy. If you want to save only some of the MegaContactRequest objects, use MegaContactRequest::copy * for them. * * @param api MegaApi object connected to the account * @param requests List that contains the new or updated contact requests */ virtual void onContactRequestsUpdate(MegaApi* api, MegaContactRequestList* requests); /** * @brief This function is called when seqTag updates. * * Used for synchronization of state between webclient and app on the same device * Subject to significatnt alterations in future as this is based on internal implementation details. * * @param api MegaApi object connected to the account * @param seqTag The string representing the sequence tag. (ownership stays with the SDK) */ virtual void onSeqTagUpdate(MegaApi* api, const std::string* seqTag); #ifdef ENABLE_SYNC /** * @brief This function is called with the state of the synchronization engine has changed * * You can call MegaApi::isScanning and MegaApi::isWaiting to know the global state * of the synchronization engine. * * @param api MegaApi object related to the event */ virtual void onGlobalSyncStateChanged(MegaApi* api); #endif #ifdef ENABLE_CHAT /** * @brief This function is called when there are new or updated chats * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * This callback is also used to initialize the list of chats available during the * fetchnodes request. * * The SDK retains the ownership of the MegaTextChatList in the second parameter. The list * and all the MegaTextChat objects that it contains will be valid until this function * returns. If you want to save the list, use MegaTextChatList::copy. If you want to save * only some of the MegaTextChat objects, use MegaTextChat::copy for those objects. * * @param api MegaApi object connected to the account * @param chats List that contains the new or updated chats */ virtual void onChatsUpdate(MegaApi* api, MegaTextChatList *chats); #endif /** * The details about the event, including the event type and any additional information, * are received in the \c params parameter. * * You can check the type of event by calling MegaEvent::getType. Refer to the method * documentation for details on the parameters that can be notified and how to access them. * * The SDK retains ownership of the event details (\c event). * Do not use them after this function returns. * * @param api MegaApi object connected to the account * @param event Details about the event */ virtual void onEvent(MegaApi* api, MegaEvent *event); /** * @brief This function is called when external drives are connected or disconnected * * The SDK retains the ownership of the char* in the third parameter, which will be valid until this function returns. * * @param api MegaApi object connected to the account * @param present Indicator of the drive status after this change (true: drive was connected; false: drive was disconnected) * @param rootPathInUtf8 Root path of the drive that determined this change (i.e. "D:", "/mnt/usbdrive") */ virtual void onDrivePresenceChanged(MegaApi* api, bool present, const char* rootPathInUtf8); virtual ~MegaGlobalListener(); }; /** * @brief Interface to get all information related to a MEGA account * * Implementations of this interface can receive all events (request, transfer, global) and two * additional events related to the synchronization engine. The SDK will provide a new interface * to get synchronization events separately in future updates- * * Multiple inheritance isn't used for compatibility with other programming languages * * The implementation will receive callbacks from an internal worker thread. * */ class MegaListener { public: /** * @brief This function is called when a request is about to start being processed * * The SDK retains the ownership of the request parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request */ virtual void onRequestStart(MegaApi* api, MegaRequest *request); /** * @brief This function is called when a request has finished * * There won't be more callbacks about this request. * The last parameter provides the result of the request. If the request finished without problems, * the error code will be API_OK * * The SDK retains the ownership of the request and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request * @param e Error information */ virtual void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError* e); /** * @brief This function is called to inform about the progres of a request * * Currently, this callback is only used for fetchNodes (MegaRequest::TYPE_FETCH_NODES) requests * * The SDK retains the ownership of the request parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * * @param api MegaApi object that started the request * @param request Information about the request * @see MegaRequest::getTotalBytes MegaRequest::getTransferredBytes */ virtual void onRequestUpdate(MegaApi*api, MegaRequest *request); /** * @brief This function is called when there is a temporary error processing a request * * The request continues after this callback, so expect more MegaRequestListener::onRequestTemporaryError or * a MegaRequestListener::onRequestFinish callback * * The SDK retains the ownership of the request and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param request Information about the request * @param error Error information */ virtual void onRequestTemporaryError(MegaApi *api, MegaRequest *request, MegaError* error); /** * @brief This function is called when a transfer is about to start being processed * * The SDK retains the ownership of the transfer parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the request * @param transfer Information about the transfer */ virtual void onTransferStart(MegaApi *api, MegaTransfer *transfer); /** * @brief This function is called when a transfer has finished * * The SDK retains the ownership of the transfer and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * There won't be more callbacks about this transfer. * The last parameter provides the result of the transfer. If the transfer finished without problems, * the error code will be API_OK * * In case that we are uploading a file into an incoming share, and our write permissions over the share * are revoked before the transfer has finished, API will put the node into our rubbish-bin. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param error Error information */ virtual void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* error); /** * @brief This function is called to inform about the progress of a transfer * * The SDK retains the ownership of the transfer parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * * In case this transfer represents a recursive operation (folder upload/download) SDK will * notify apps about the stages transition. * * Current recursive operation stage can be retrieved with method MegaTransfer::getStage. * This method returns the following values: * - MegaTransfer::STAGE_SCAN = 1 * - MegaTransfer::STAGE_CREATE_TREE = 2 * - MegaTransfer::STAGE_TRANSFERRING_FILES = 3 * For more information about stages refer to MegaTransfer::getStage * * @see MegaTransfer::getTransferredBytes, MegaTransfer::getSpeed, MegaTransfer::getStage */ virtual void onTransferUpdate(MegaApi *api, MegaTransfer *transfer); /** * @brief This function is called when there is a temporary error processing a transfer * * The transfer continues after this callback, so expect more MegaTransferListener::onTransferTemporaryError or * a MegaTransferListener::onTransferFinish callback * * The SDK retains the ownership of the transfer and error parameters. * Don't use them after this functions returns. * * If the error code is API_EOVERQUOTA we need to call to MegaTransfer::isForeignOverquota to determine if * our own storage, or a foreign storage is in overquota. If MegaTransfer::isForeignOverquota returns true * a foreign storage is in overquota, otherwise our own storage is in overquota. * * @param api MegaApi object that started the transfer * @param transfer Information about the transfer * @param error Error information */ virtual void onTransferTemporaryError(MegaApi *api, MegaTransfer *transfer, MegaError* error); /** * @brief This function is called when there are new or updated contacts in the account * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaUserList in the second parameter. The list and * all the MegaUser objects that it contains will be valid until this function returns. If * you want to save the list, use MegaUserList::copy. If you want to save only some of the * MegaUser objects, use MegaUser::copy for those objects. * * @param api MegaApi object connected to the account * @param users List that contains the new or updated contacts */ virtual void onUsersUpdate(MegaApi* api, MegaUserList *users); /** * @brief This function is called when there are new or updated user alerts in the account * * When there is a problem parsing the incoming information from the server or the full * account is reloaded or a large number of server notifications arrives at once, the second * parameter will be NULL. * * The SDK retains the ownership of the MegaUserAlertList in the second parameter. The list * and all the MegaUserAlert objects that it contains will be valid until this function * returns. If you want to save the list, use MegaUserAlertList::copy. If you want to save * only some of the MegaUserAlert objects, use MegaUserAlert::copy for those objects. * * @param api MegaApi object connected to the account * @param alerts List that contains the new or updated alerts */ virtual void onUserAlertsUpdate(MegaApi* api, MegaUserAlertList *alerts); /** * @brief This function is called when there are new or updated nodes in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be NULL. * * The SDK retains the ownership of the MegaNodeList in the second parameter. The list and all the * MegaNode objects that it contains will be valid until this function returns. If you want to save the * list, use MegaNodeList::copy. If you want to save only some of the MegaNode objects, use MegaNode::copy * for those nodes. * * @param api MegaApi object connected to the account * @param nodes List that contains the new or updated nodes */ virtual void onNodesUpdate(MegaApi* api, MegaNodeList *nodes); /** * @brief This function is called when the account has been updated (upgraded/downgraded) * * @param api MegaApi object connected to the account */ virtual void onAccountUpdate(MegaApi *api); /** * @brief This function is called when a Set has been updated (created / updated / removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaSetList in the second parameter. The list and * all the MegaSet objects that it contains will be valid until this function returns. If * you want to save the list, use MegaSetList::copy. If you want to save only some of the * MegaSet objects, use MegaSet::copy for them. * * @param api MegaApi object connected to the account * @param sets List that contains the new or updated Sets */ virtual void onSetsUpdate(MegaApi* api, MegaSetList* sets); /** * @brief This function is called when a Set-Element has been updated (created / updated / * removed) * * When the full account is reloaded or a large number of server notifications arrives at * once, the second parameter will be NULL. * * The SDK retains the ownership of the MegaSetElementList in the second parameter. The list * and all the MegaSetElement objects that it contains will be valid until this function * returns. If you want to save the list, use MegaSetElementList::copy. If you want to save * only some of the MegaSetElement objects, use MegaSetElement::copy for them. * * @param api MegaApi object connected to the account * @param elements List that contains the new or updated Set-Elements */ virtual void onSetElementsUpdate(MegaApi* api, MegaSetElementList* elements); /** * @brief This function is called when there are new or updated contact requests in the account * * When the full account is reloaded or a large number of server notifications arrives at once, the * second parameter will be NULL. * * The SDK retains the ownership of the MegaContactRequestList in the second parameter. The list and all the * MegaContactRequest objects that it contains will be valid until this function returns. If you want to save the * list, use MegaContactRequestList::copy. If you want to save only some of the MegaContactRequest objects, use MegaContactRequest::copy * for them. * * @param api MegaApi object connected to the account * @param requests List that contains the new or updated contact requests */ virtual void onContactRequestsUpdate(MegaApi* api, MegaContactRequestList* requests); #ifdef ENABLE_SYNC /** * @brief This function is called when the state of a synced file or folder changes * * Possible values for the state are: * - MegaApi::STATE_SYNCED = 1 * The file is synced with the MEGA account * * - MegaApi::STATE_PENDING = 2 * The file isn't synced with the MEGA account. It's waiting to be synced. * * - MegaApi::STATE_SYNCING = 3 * The file is being synced with the MEGA account * * The SDK retains the ownership of the sync and localPath parameters. * Don't use them after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object manages the file * @param localPath Local path of the file or folder * @param newState New state of the file */ virtual void onSyncFileStateChanged(MegaApi *api, MegaSync *sync, std::string *localPath, int newState); /** * @brief This callback will be called when a sync is added * * The SDK will call this after loading (and attempt to resume) syncs from cache or whenever a new * Synchronization is configured. * * Notice that adding a sync will not cause onSyncStateChanged to be called. * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param sync MegaSync object representing a sync * @param api MegaApi object that is synchronizing files * @param additionState conditions in which the sync is added */ virtual void onSyncAdded(MegaApi *api, MegaSync *sync); /** * @brief This callback will be called when a sync is removed. * * This entail that the sync is completely removed from cache * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object representing a sync */ virtual void onSyncDeleted(MegaApi *api, MegaSync *sync); /** * @brief This function is called when the state of the synchronization changes * * The SDK calls this function when the state of the synchronization changes. you can use * MegaSync::getRunState to get the new state of the synchronization * and MegaSync::getError to get the error if any. * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object that has changed its state */ virtual void onSyncStateChanged(MegaApi *api, MegaSync *sync); /** * @brief This function is called when there is an update on * the number of nodes or transfers in the sync * * The SDK retains the ownership of the MegaSyncStats. * Don't use it after this functions returns. But you can copy it * * @param api MegaApi object that is synchronizing files * @param syncStats Identifies the sync and provides the counts */ virtual void onSyncStatsUpdated(MegaApi *api, MegaSyncStats* syncStats); /** * @brief This function is called with the state of the synchronization engine has changed * * You can call MegaApi::isScanning and MegaApi::isWaiting to know the global state * of the synchronization engine. * * @param api MegaApi object related to the event */ virtual void onGlobalSyncStateChanged(MegaApi* api); /** * @brief This function is called when the root in the cloud of the sync has changed. * * You can use MegaSync::getLastKnownMegaFolder to get the new root in the cloud. * * @note This method will be called if the change doesn't imply a change in the state of the * sync or any additional errors. * * The SDK retains the ownership of the sync parameter. * Don't use it after this functions returns. * * @param api MegaApi object that is synchronizing files * @param sync MegaSync object that has changed its remote root node */ virtual void onSyncRemoteRootChanged(MegaApi* api, MegaSync* sync); #endif /** * @brief This function is called when the state of the backup changes * * The SDK calls this function when the state of the backup changes, for example * from 'active' to 'ongoing' or 'removing exceeding'. * * You can use MegaScheduledCopy::getState to get the new state. * * @param api MegaApi object that is backing up files * @param backup MegaScheduledCopy object that has changed the state */ virtual void onBackupStateChanged(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when a backup is about to start being processed * * The SDK retains the ownership of the backup parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the backup * @param backup Information about the backup */ virtual void onBackupStart(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when a backup has finished * * The SDK retains the ownership of the backup and error parameters. * Don't use them after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * There won't be more callbacks about this backup. * The last parameter provides the result of the backup. If the backup finished without problems, * the error code will be API_OK * * @param api MegaApi object that started the backup * @param backup Information about the backup * @param error Error information */ virtual void onBackupFinish(MegaApi* api, MegaScheduledCopy *backup, MegaError* error); /** * @brief This function is called to inform about the progress of a backup * * The SDK retains the ownership of the backup parameter. * Don't use it after this functions returns. * * The api object is the one created by the application, it will be valid until * the application deletes it. * * @param api MegaApi object that started the backup * @param backup Information about the backup * * @see MegaScheduledCopy::getTransferredBytes, MegaScheduledCopy::getSpeed */ virtual void onBackupUpdate(MegaApi *api, MegaScheduledCopy *backup); /** * @brief This function is called when there is a temporary error processing a backup * * The backup continues after this callback, so expect more MegaScheduledCopyListener::onBackupTemporaryError or * a MegaScheduledCopyListener::onBackupFinish callback * * The SDK retains the ownership of the backup and error parameters. * Don't use them after this functions returns. * * @param api MegaApi object that started the backup * @param backup Information about the backup * @param error Error information */ virtual void onBackupTemporaryError(MegaApi *api, MegaScheduledCopy *backup, MegaError* error); #ifdef ENABLE_CHAT /** * @brief This function is called when there are new or updated chats * * When the full account is reloaded or a large number of server notifications arrives at once, * the second parameter will be NULL. * * The SDK retains the ownership of the MegaTextChatList in the second parameter. The list and * all the MegaTextChat objects that it contains will be valid until this function returns. If * you want to save the list, use MegaTextChatList::copy. If you want to save only some of the * MegaTextChat objects, use MegaTextChat::copy for those objects. * * @param api MegaApi object connected to the account * @param chats List that contains the new or updated chats */ virtual void onChatsUpdate(MegaApi* api, MegaTextChatList* chats); #endif /** * The details about the event, including the event type and any additional information, * are received in the \c params parameter. * * You can check the type of event by calling MegaEvent::getType. Refer to the method * documentation for details on the parameters that can be notified and how to access them. * * The SDK retains ownership of the event details (\c event). * Do not use them after this function returns. * * @param api MegaApi object connected to the account * @param event Details about the event */ virtual void onEvent(MegaApi* api, MegaEvent* event); virtual ~MegaListener(); /** * @brief * Called when a mount is being added to the database. * * @param api * The API instance where the mount is being added. * * @param path * A path identifying the mount that was added. * * @param megaMountResult * An element of the MegaMount::Result enumeration. */ virtual void onMountAdded(MegaApi* api, const char* path, int megaMountResult); /** * @brief * Called when a mount's flags are being changed. * * @param api * The API instance where the mount is being added. * * @param path * A path identifying the mount that has changed. * * @param megaMountResult * An element of the MegaMount::Result enumeration. */ virtual void onMountChanged(MegaApi* api, const char* path, int megaMountResult); /** * @brief * Called when a mount is being disabled. * * @param api * The API instance where the mount is being added. * * @param path * A path identifying the mount that has been disabled. * * @param megaMountResult * An element of the MegaMount::Result enumeration. */ virtual void onMountDisabled(MegaApi* api, const char* path, int megaMountResult); /** * @brief * Called when a mount is being enabled. * * @param api * The API instance where the mount is being enabled. * * @param path * A path identifying the mount that has been enabled. * * @param megaMountResult * An element of the MegaMount::Result enumeration. */ virtual void onMountEnabled(MegaApi* api, const char* path, int megaMountResult); /** * @brief * Called when a mount is being removed from the database. * * @param api * The API instance where the mount is being removed. * * @param path * A path identifying the mount that has been removed. * * @param megaMountResult * An element of the MegaMount::Result enumeration. */ virtual void onMountRemoved(MegaApi* api, const char* path, int megaMountResult); }; /** * @brief Stores information about a background photo/video upload, used in iOS to take advantage of power saving features * * This object can be serialised so it can be stored in case your app is unloaded by its OS, and the background operation * completed afterward. * */ class MegaBackgroundMediaUpload { protected: MegaBackgroundMediaUpload(); public: /** * @brief Initial step to upload a photo/video via iOS low-power background upload feature * * Creates an object which can be used to encrypt a media file, and upload it outside of the SDK, * eg. in order to take advantage of a particular platform's low power background upload functionality. * * You take ownership of the returned value. * * @param api The MegaApi the new object will be used with. It must live longer than the new object. * @return A pointer to an object that keeps some needed state through the process of * uploading a media file via iOS low power background uploads (or similar). */ static MegaBackgroundMediaUpload* createInstance(MegaApi *api); /** * @brief Extract mediainfo information about the photo or video. * * Call this function once with the file to be uploaded. It uses mediainfo to extract information that will * help other clients to show or to play the files. The information is stored in this object until the whole * operation completes. * * Call MegaApi::ensureMediaInfo first in order prepare the library to attach file attributes * that enable videos to be identified and played in the web browser. * * @param inputFilepath The file to analyse with MediaInfo. * @return true if analysis was performed (and any relevant attributes stored ready for upload), false if mediainfo was not ready yet. */ virtual bool analyseMediaInfo(const char* inputFilepath); /** * @brief Encrypt the file or a portion of it * * Call this function once with the file to be uploaded. It uses mediainfo to extract information that will * help the webclient show or play the file in various browsers. The information is stored in this object * until the whole operation completes. The encrypted data is stored in a new file. * * In order to save space on mobile devices, this function can be called in such a way that the last portion * of the file is encrypted (to a new file), and then that last portion of the file is removed by file truncation. * That operation can be repeated until the file is completely encrypted, and only the encrypted version remains, * and takes up the same amount of space on the device. The size of the portions must first be calculated by using * the 'adjustsizeonly' parameter, and iterating from the start of the file, specifying the approximate sizes of the portions. * * Encryption is done by reading small pieces of the file, encrypting them, and outputting to the new file, * so that RAM usage is not excessive. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param inputFilepath The file to encrypt a portion of (and the one that is ultimately being uploaded). * @param startPos The index of the first byte of the file to encrypt * @param length The number of bytes of the file to encrypt. The function will round this value up by up to 1MB to fit the * MEGA internal chunking algorithm. The number of bytes actually encrypted and stored in the new file is the updated number. * You can supply -1 as input to request the remainder file (from startPos) be encrypted. * @param outputFilepath The name of the new file to create, and store the encrypted data in. * @param adjustsizeonly If this is set true, then encryption is not performed, and only the length parameter is adjusted. * This feature is to enable precalculating the exact sizes of the file portions for upload. * @return If the function tries to encrypt and succeeds, the return value is the suffix to append to the URL when uploading this enrypted chunk. * If adjustsizeonly was set, and the function succeeds, the return value will be non-NULL (and will need deallocation as usual). * If the function fails, the return value is NULL, and an error will have been logged. */ virtual char *encryptFile(const char* inputFilepath, int64_t startPos, int64_t* length, const char* outputFilepath, bool adjustsizeonly); /** * @brief Retrieves the value of the uploadURL once it has been successfully requested via MegaApi::backgroundMediaUploadRequestUploadURL * * You take ownership of the returned value. Use delete[] to release the memory. * * @return The URL to upload to (after appending the suffix), if one has been received. Otherwise NULL. */ virtual char *getUploadURL(); /** * @brief Attach a thumbnail by its file attribute handle. * * The thumbnail will implictly be attached to the node created as part of MegaApi::backgroundMediaUploadComplete. * The thumbnail file attibrute must have been obtained by MegaApi::putThumbnail. * If the result of MegaApi::putThumbnail is not available by the time MegaApi::backgroundMediaUploadComplete * is called, it can be attached to the node later using MegaApi::setThumbnailByHandle. * * @param h The handle obtained via MegaApi::putThumbnail */ virtual void setThumbnail(MegaHandle h); /** * @brief Attach a preview by its file attribute handle. * * The preview will implictly be attached to the node created as part of MegaApi::backgroundMediaUploadComplete. * The preview file attibrute must have been obtained by MegaApi::putPreview. * If the result of MegaApi::putPreview is not available by the time MegaApi::backgroundMediaUploadComplete * is called, it can be attached to the node later using MegaApi::setPreviewByHandle. * * @param h The handle obtained via MegaApi::putPreview */ virtual void setPreview(MegaHandle h); /** * @brief Sets the GPS coordinates for the node * * The node created via MegaApi::backgroundMediaUploadComplete will gain these coordinates as part of the * node creation. If the unshareable flag is set, the coodinates are encrypted in a way that even if the * node is later shared, the GPS coordinates cannot be decrypted by a different account. * * @param latitude The GPS latitude * @param longitude The GPS longitude * @param unshareable Set this true to prevent the coordinates being readable by other accounts. */ virtual void setCoordinates(double latitude, double longitude, bool unshareable); /** * @brief Turns the data stored in this object into a base 64 encoded string. * * The object can then be recreated via MegaBackgroundMediaUpload::unserialize and supplying the returned string. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return serialized version of this object (including URL, mediainfo attributes, and internal data suitable to resume uploading with in future) */ virtual char *serialize(); /** * @brief Get back the needed MegaBackgroundMediaUpload after the iOS app exited and restarted * * In case the iOS app exits while a background upload is going on, and the app is started again * to complete the operation, call this function to recreate the MegaBackgroundMediaUpload object * needed for a call to MegaApi::backgroundMediaUploadComplete. The object must have been serialised * before the app was unloaded by using MegaBackgroundMediaUpload::serialize. * * You take ownership of the returned value. * * @param data The string the object was serialized to previously. * @param api The MegaApi this object will be used with. It must live longer than this object. * @return A pointer to a new MegaBackgroundMediaUpload with all fields set to the data that was * stored in the serialized string. */ static MegaBackgroundMediaUpload* unserialize(const char* data, MegaApi* api); /** * @brief Destructor */ virtual ~MegaBackgroundMediaUpload(); }; class MegaInputStream { public: virtual int64_t getSize(); virtual bool read(char *buffer, size_t size); virtual ~MegaInputStream(); }; /** * @brief Store filtering options used in searches @see MegaApi::search, MegaApi::getChildren. * */ class MegaSearchFilter { protected: MegaSearchFilter(); public: // A helper enum for filtering boolean fields enum { BOOL_FILTER_DISABLED = 0, BOOL_FILTER_ONLY_TRUE, BOOL_FILTER_ONLY_FALSE, }; /** * @brief Creates a new instance of MegaSearchFilter * @return A pointer of current type, a superclass of the private object */ static MegaSearchFilter* createInstance(); /** * @brief Create a copy of this instance. * * The resulted instance is fully independent of the source instance, * it contains a copy of all internal attributes, so it will be valid after * the original instance was deleted. * * You are the owner of the returned instance * * @return Copy of the current instance */ virtual MegaSearchFilter* copy() const; virtual ~MegaSearchFilter(); /** * @brief Set option for filtering by name. * * @param searchString Contains a name or an expression using wildcards. */ virtual void byName(const char* searchString); /** * @brief Set option for filtering by predefined node types. * If not set, it will behave as MegaNode::TYPE_UNKNOWN was used. * * @param nodeType Type of nodes requested in the search * Valid values for this parameter are (invalid values will be ignored): * - MegaNode::TYPE_UNKNOWN = -1 --> all types * - MegaNode::TYPE_FILE = 0 * - MegaNode::TYPE_FOLDER = 1 */ virtual void byNodeType(int nodeType); /** * @brief Set option for filtering by predefined file categories. * If not set, it will behave as FILE_TYPE_DEFAULT was used. * When set to a valus different than FILE_TYPE_DEFAULT it will search only for files. * * @param mimeType Category of files requested in the search * Valid values for this parameter are (invalid values will be ignored): * - MegaApi::FILE_TYPE_DEFAULT = 0 --> no particular category, include folders too * - MegaApi::FILE_TYPE_PHOTO = 1 * - MegaApi::FILE_TYPE_AUDIO = 2 * - MegaApi::FILE_TYPE_VIDEO = 3 * - MegaApi::FILE_TYPE_DOCUMENT = 4 * - MegaApi::FILE_TYPE_PDF = 5 * - MegaApi::FILE_TYPE_PRESENTATION = 6 * - MegaApi::FILE_TYPE_ARCHIVE = 7 * - MegaApi::FILE_TYPE_PROGRAM = 8 * - MegaApi::FILE_TYPE_MISC = 9 * - MegaApi::FILE_TYPE_SPREADSHEET = 10 * - MegaApi::FILE_TYPE_ALL_DOCS = 11 --> any of {DOCUMENT, PDF, PRESENTATION, SPREADSHEET} * - MegaApi::FILE_TYPE_OTHERS = 12 * - MegaApi::FILE_TYPE_ALL_VISUAL_MEDIA = 13--> any of {PHOTO, VIDEO} */ virtual void byCategory(int mimeType); /** * @brief Set option for filtering out non favourite nodes. * If not set, it will behave as if BOOL_FILTER_DISABLED was used. * * @param boolFilterOption Kind of boolean filter to apply. * Valid values for this parameter are (invalid values will be ignored): * - MegaSearchFilter::BOOL_FILTER_DISABLED = 0 --> Both favourites and non favourites are considered * - MegaSearchFilter::BOOL_FILTER_ONLY_TRUE = 1 --> Only favourites * - MegaSearchFilter::BOOL_FILTER_ONLY_FALSE = 2 --> Only non favourites */ virtual void byFavourite(int boolFilterOption); /** * @brief Sets the filter option for excluding sensitive nodes. * If not set, it defaults to BOOL_FILTER_DISABLED. * * @note Due to compatibility reasons and the nature of the sensitive attribute, the behavior of * this filter may appear counter-intuitive, especially compared to byFavourite. Summary: * - Use BOOL_FILTER_ONLY_FALSE to get only nodes marked as sensitive. * - The union of results using BOOL_FILTER_ONLY_TRUE and BOOL_FILTER_ONLY_FALSE * differs from the results using BOOL_FILTER_DISABLED. * * @param boolFilterOption A tri-state variable determining the filter to apply. * Valid values are (invalid values will be ignored): * - MegaSearchFilter::BOOL_FILTER_DISABLED = 0 --> Considers all nodes. * - MegaSearchFilter::BOOL_FILTER_ONLY_TRUE = 1 --> Returns only nodes not marked as sensitive * and without any parent directory marked as sensitive. * - MegaSearchFilter::BOOL_FILTER_ONLY_FALSE = 2 --> Returns only nodes marked as sensitive, * i.e. node->isMarkedSensitive() == true. */ virtual void bySensitivity(int boolFilterOption); /** * @brief Set option for retrieving nodes below a particular ancestor. * If not set, nodes will not be restricted to a particular ancestor. * * @note When called, it will cancel any previous setting done by calling byLocation(). * * @param ancestorHandle Handle of an acestor to which the search will be restricted to */ virtual void byLocationHandle(MegaHandle ancestorHandle); /** * @brief Set option for searching nodes below predefined locations. * If not set, it will behave like using SEARCH_TARGET_ALL. * * @note When called, it will cancel any previous setting done by calling byLocationHandle(). * * @param locationType Location to which the search will be restricted to * Valid values for this parameter are (invalid values will be ignored): * - SEARCH_TARGET_INSHARE = 0 * - SEARCH_TARGET_OUTSHARE = 1 * - SEARCH_TARGET_PUBLICLINK = 2 * - SEARCH_TARGET_ROOTNODE = 3 --> search under Cloud and Vault rootnodes * - SEARCH_TARGET_ALL = 4 --> by default search under Cloud, Vault, Rubbish and among INSHARE-s; * if an ancestor was explicitly set via byLocationHandle(), search under that particular ancestor */ virtual void byLocation(int locationType); /** * @brief Set option for filtering out nodes created outside a defined time interval. * If any of the passed values is 0 it will be ignored, and no filtering will be * performed based on it. * * @param lowerLimit timestamp lower than any of the considered nodes. * @param upperLimit timestamp greater than any of the considered nodes. */ virtual void byCreationTime(int64_t lowerLimit, int64_t upperLimit); /** * @brief Set option for filtering out nodes modified outside a defined time interval. * If any of the passed values is 0 it will be ignored, and no filtering will be * performed based on it. * If any of the passed values is non-0, only nodes with valid modification time will * be included in the results. For now only File nodes have modification time so only * they will be included in the results. * * @param lowerLimit timestamp lower than any of the considered nodes. * @param upperLimit timestamp greater than any of the considered nodes. */ virtual void byModificationTime(int64_t lowerLimit, int64_t upperLimit); /** * @brief Set option for filtering by contains in description. * * @param searchString Contains string to be searched at nodes description */ virtual void byDescription(const char* searchString); /** * @brief Set option for filtering by tag * * @note ',' is an invalid character, it shouldn't be used as part of searchString. If used, * empty list will be returned * * @param searchString Contains string to be searched at nodes tags */ virtual void byTag(const char* searchString); /** * @brief Set the logical operator for filtering text related conditions * * @note This method sets the logical operator to be used between multiple search criteria * (name, tags and description). The operator can either be `AND` or `OR` based on the * input parameter. * * If not invoked, `AND` will be used as the default behavior. * * @param useAnd If true, the `AND` operator will be used between search criteria. * If false, the `OR` operator will be used. */ virtual void useAndForTextQuery(bool useAnd); /** * @brief Return the string used for filtering by name. * * @return string set for filtering by name, or empty string ("") if not set */ virtual const char* byName() const; /** * @brief Return predefined node type used for filtering. * * @return predefined node type set for filtering, or MegaNode::TYPE_UNKNOWN if not set */ virtual int byNodeType() const; /** * @brief Return predefined category used for filtering. * * @return predefined category set for filtering, or FILE_TYPE_DEFAULT if not set */ virtual int byCategory() const; /** * @brief Return the option for filtering out non favourite nodes. * * @return option set for filtering out favourite nodes, or BOOL_FILTER_DISABLED if not set */ virtual int byFavourite() const; /** * @brief Return the option for filtering out sensitive nodes. * * @return option set for filtering out sensitive nodes, or BOOL_FILTER_DISABLED if not set */ virtual int bySensitivity() const; /** * @brief Return ancestor handle set for restricting node search to. * * @return ancestor handle set for restricting node search to, or INVALID_HANDLE if not set */ virtual MegaHandle byLocationHandle() const; /** * @brief Return predefined location set for restricting node search to. * * @return predefined location set for restricting node search to, or SEARCH_TARGET_ALL if not set */ virtual int byLocation() const; /** * @brief Return lower limit creation timestamp set for restricting node search to. * * @return lower limit creation timestamp set for restricting node search to, or 0 if not set */ virtual int64_t byCreationTimeLowerLimit() const; /** * @brief Return upper limit creation timestamp set for restricting node search to. * * @return upper limit creation timestamp set for restricting node search to, or 0 if not set */ virtual int64_t byCreationTimeUpperLimit() const; /** * @brief Return lower limit modification timestamp set for restricting node search to. * * @return lower limit modification timestamp set for restricting node search to, or 0 if not set */ virtual int64_t byModificationTimeLowerLimit() const; /** * @brief Return upper limit modification timestamp set for restricting node search to. * * @return upper limit modification timestamp set for restricting node search to, or 0 if not set */ virtual int64_t byModificationTimeUpperLimit() const; /** * @brief Return the string used for filtering by description. * * @return string set for filtering by description, or empty string ("") if not set */ virtual const char* byDescription() const; /** * @brief Return the string used for filtering by tag. * * @return string set for filtering by tag, or empty string ("") if not set */ virtual const char* byTag() const; /** * @brief Get the current logical operator used for filtering text related conditions * * @return True if the `AND` operator is being used between search criteria, * false if the `OR` operator is being used. */ virtual bool useAndForTextQuery() const; }; /** * @brief Store pagination options used in searches @see MegaApi::search, MegaApi::getChildren. * */ class MegaSearchPage { protected: MegaSearchPage(); public: /** * @brief Creates a new instance of MegaSearchPage * * @param startingOffset The first position in the list of results to be included in the returned page (starts from 0). * @param size The maximum number of results included in the page, or 0 to return all (remaining) results * * @return A pointer of current type, a superclass of the private object */ static MegaSearchPage* createInstance(size_t startingOffset, size_t size); /** * @brief Create a copy of this instance. * * The resulted instance is fully independent of the source instance, * it contains a copy of all internal attributes, so it will be valid after * the original instance was deleted. * * You are the owner of the returned instance * * @return Copy of the current instance */ virtual MegaSearchPage* copy() const; virtual ~MegaSearchPage(); /** * @brief Return the first position in the list of results to be included in the returned page (starts from 0) * * @return first position in the list of results to be included in the returned page (starts from 0) */ virtual size_t startingOffset() const; /** * @brief Return the maximum number of results included in the page, or 0 to return all (remaining) results * * @return maximum number of results included in the page, or 0 to return all (remaining) results */ virtual size_t size() const; }; class MegaNodeTree { protected: MegaNodeTree() = default; public: virtual ~MegaNodeTree() = default; static MegaNodeTree* createInstance(const MegaNodeTree* nodeTreeChild, const char* name, const char* s4AttributeValue, const MegaCompleteUploadData* completeUploadData, MegaHandle sourceHandle = INVALID_HANDLE); virtual MegaNodeTree* getNodeTreeChild() const = 0; virtual MegaHandle getNodeHandle() const = 0; virtual MegaNodeTree* copy() const = 0; }; class MegaCompleteUploadData { protected: MegaCompleteUploadData() = default; public: virtual ~MegaCompleteUploadData() = default; static MegaCompleteUploadData* createInstance(const char* fingerprint, const char* string64UploadToken, const char* string64FileKey); virtual MegaCompleteUploadData* copy() const = 0; }; /** * @brief Store information of an A/B Test or a Feature flag * * @see MegaApi::getFlag. */ class MegaFlag { public: virtual ~MegaFlag() = default; /** * @brief Possible flag types. */ enum // 1:1 with enum values from internal implementation { FLAG_TYPE_INVALID = 0, FLAG_TYPE_AB_TEST = 1, FLAG_TYPE_FEATURE = 2, }; /** * @brief Get the type of the flag * * @return The type of the flag. Possible values are any of the FLAG_TYPE_x values. */ virtual uint32_t getType() const = 0; /** * @brief Get the group of the flag * * @return The group of the flag. Any value greater than 0 means the flag is active. */ virtual uint32_t getGroup() const = 0; }; class MegaApiImpl; /** * @brief Allows calling many synchronous operations on MegaApi without being blocked by SDK activity. * * Call MegaApi::getMegaApiLock() to get an instance of this class to use. */ class MegaApiLock { public: /** * @brief Lock the MegaApi if this instance does not currently have a lock on it yet. * * There is no harm in calling this more than once, the MegaApi will only be locked * once, and the first unlock() call will release it. Sometimes it is useful eg. * in a loop which may or may not need to use a locking function, or may need to use * many, to call lockOnce() before any such usage, and know that the MegaApi will * be locked once from that point, until the end of the loop (when unlockOnce() can * be called, or the MegaApiLock destroyed. */ void lockOnce(); /** * @brief Tries to lock the MegaApi if this instance does not currently have a lock on it yet. * * If the lock is succeeded in the expected time, the behaviour is the same as lockOnce(). * * @param time Milliseconds to wait for locking * * @return if the locking succeded */ bool tryLockFor(long long time); /** * @brief Release the lock on the MegaApi if one is still held by this instance * * The MegaApi will be unable to continue work until all MegaApiLock objects release * their locks. Only use multiple of these if you need nested locking. The destructor * of the object will release the lock, so it is sufficient to delete it when finished. * However, when using it from a garbage collected language it may be prudent to call unlock() directly. * * This function must be called from the same thread that called MegaApiLock::lockOnce(). */ void unlockOnce(); /** * @brief Destructor. This will call unlock() if the MegaApi is still locked by this instance. */ ~MegaApiLock(); private: MegaApiImpl* api; bool locked = false; MegaApiLock(MegaApiImpl*, bool lock); MegaApiLock(const MegaApiLock&) = delete; void operator=(const MegaApiLock&) = delete; friend class MegaApi; }; /** * @brief Optional parameters to customize an upload. */ class MegaUploadOptions { public: MegaUploadOptions() = default; static constexpr int64_t INVALID_CUSTOM_MOD_TIME = -1; static constexpr char PITAG_TRIGGER_NOT_APPLICABLE = '.'; static constexpr char PITAG_TARGET_NOT_APPLICABLE = '.'; /** * @brief Creates a new instance of MegaUploadOptions. * * The caller takes ownership of the returned pointer. */ static MegaUploadOptions* createInstance(); /** * Custom file or folder name in MEGA. * If empty, the name is taken from the local path. */ std::string fileName; /** * Custom modification time for files (seconds since epoch). * Use MegaUploadOptions::INVALID_CUSTOM_MOD_TIME to keep the local mtime. */ int64_t mtime = INVALID_CUSTOM_MOD_TIME; /** * Custom app data associated with the transfer. * Accessible via MegaTransfer::getAppData(). */ const char* appData = nullptr; /** * If true, the SDK deletes the local file when the upload finishes. * Intended for temporary files only. */ bool isSourceTemporary = false; /** * If true, the upload is put on top of the upload queue. */ bool startFirst = false; /** * One-byte upload trigger tag (see PITAG_TRIGGER_*). */ char pitagTrigger = PITAG_TRIGGER_NOT_APPLICABLE; /** * Indicate if the upload is done to a chat */ bool isChatUpload = false; /** * One-byte upload target tag (see PITAG_TARGET_*). * Allows specifying destinations such as chat uploads. * Apps uploading to chats should set the appropriate chat target (c, C, or s); * for other uploads keep the default value to avoid interfering with internal logic. */ char pitagTarget = PITAG_TARGET_NOT_APPLICABLE; }; /** * @brief Allows to control a MEGA account or a shared folder * * You can enable local node caching by passing a local path in the constructor of this class. That saves many data usage * and many time starting your app because the entire filesystem won't have to be downloaded each time. The persistent * node cache will only be loaded by logging in with a session key. To take advantage of this feature, apart of passing the * local path to the constructor, your application have to save the session key after login (MegaApi::dumpSession) and use * it to log in the next time. This is highly recommended also to enhance the security, because in this was the access password * doesn't have to be stored by the application. * * To access MEGA using this SDK, you have to create an object of this class and use one of the MegaApi::login options (to log in * to a MEGA account or a public folder). If the login request succeed, you must call MegaApi::fetchNodes to get the filesystem in MEGA. * After successfully completing that request, you can use all other functions, manage the files and start transfers. * * After using MegaApi::logout you can reuse the same MegaApi object to log in to another MEGA account or a public folder. * * Some functions in this class return a pointer and give you the ownership. In all of them, memory allocations * are made using new (for single objects) and new[] (for arrays) so you should use delete and delete[] to free them. */ class MegaApi { public: enum { CLIENT_TYPE_DEFAULT = 0, CLIENT_TYPE_VPN, CLIENT_TYPE_PASSWORD_MANAGER, }; enum { STATE_NONE = 0, STATE_SYNCED, STATE_PENDING, STATE_SYNCING, STATE_IGNORED }; enum { LOG_LEVEL_FATAL = 0, // Very severe error event that will presumably lead the application to abort. LOG_LEVEL_ERROR, // Error information but will continue application to keep running. LOG_LEVEL_WARNING, // Information representing errors in application but application will keep running LOG_LEVEL_INFO, // Mainly useful to represent current progress of application. LOG_LEVEL_DEBUG, // Informational logs, that are useful for developers. Only applicable if DEBUG is defined. LOG_LEVEL_VERBOSE, LOG_LEVEL_MAX = LOG_LEVEL_VERBOSE }; enum { ATTR_TYPE_THUMBNAIL = 0, ATTR_TYPE_PREVIEW = 1 }; enum { USER_ATTR_UNKNOWN = -1, USER_ATTR_AVATAR = 0, // public - char array USER_ATTR_FIRSTNAME = 1, // public - char array USER_ATTR_LASTNAME = 2, // public - char array USER_ATTR_AUTHRING = 3, // private - byte array USER_ATTR_LAST_INTERACTION = 4, // private - byte array USER_ATTR_ED25519_PUBLIC_KEY = 5, // public - byte array USER_ATTR_CU25519_PUBLIC_KEY = 6, // public - byte array USER_ATTR_KEYRING = 7, // private - byte array USER_ATTR_SIG_RSA_PUBLIC_KEY = 8, // public - byte array USER_ATTR_SIG_CU255_PUBLIC_KEY = 9, // public - byte array USER_ATTR_LANGUAGE = 14, // private - char array USER_ATTR_PWD_REMINDER = 15, // private - char array USER_ATTR_DISABLE_VERSIONS = 16, // private - byte array USER_ATTR_CONTACT_LINK_VERIFICATION = 17, // private - byte array USER_ATTR_RICH_PREVIEWS = 18, // private - byte array USER_ATTR_RUBBISH_TIME = 19, // private - byte array USER_ATTR_LAST_PSA = 20, // private - char array USER_ATTR_STORAGE_STATE = 21, // private - char array USER_ATTR_GEOLOCATION = 22, // private - byte array USER_ATTR_CAMERA_UPLOADS_FOLDER = 23,// private - byte array USER_ATTR_MY_CHAT_FILES_FOLDER = 24, // private - byte array USER_ATTR_PUSH_SETTINGS = 25, // private - char array // ATTR_UNSHAREABLE_KEY = 26 // it's internal for SDK, not exposed to apps USER_ATTR_ALIAS = 27, // private - byte array USER_ATTR_DEVICE_NAMES = 30, // private - byte array USER_ATTR_MY_BACKUPS_FOLDER = 31, // protected - char array in B64 - non-versioned // USER_ATTR_BACKUP_NAMES = 32, // (obsolete) private - byte array USER_ATTR_COOKIE_SETTINGS = 33, // private - byte array - non-versioned USER_ATTR_JSON_SYNC_CONFIG_DATA = 34,// private - byte array // USER_ATTR_DRIVE_NAMES = 35, // (obsolete, merged with USER_ATTR_DEVICE_NAMES) // private - byte array USER_ATTR_NO_CALLKIT = 36, // private - byte array USER_ATTR_APPS_PREFS = 38, // private - byte array - versioned USER_ATTR_CC_PREFS = 39, // private - byte array - versioned USER_ATTR_VISIBLE_WELCOME_DIALOG = 40, // private - non-encrypted - byte array - non-versioned USER_ATTR_VISIBLE_TERMS_OF_SERVICE = 41, // private - non-encrypted - byte array - non-versioned USER_ATTR_PWM_BASE = 42, // private non-encrypted (fully controlled by API) - char array in B64 USER_ATTR_ENABLE_TEST_NOTIFICATIONS = 43, // private - non-encrypted - char array - non-versioned USER_ATTR_LAST_READ_NOTIFICATION = 44, // private - non-encrypted - char array - non-versioned USER_ATTR_LAST_ACTIONED_BANNER = 45, // private - non-encrypted - char array - non-versioned USER_ATTR_ENABLE_TEST_SURVEYS = 46, // private - non-encrypted - char array in B64 - non-versioned // USER_ATTR_WELCOME_PDF_COPIED = 47, // (obsolete) private - non-encrypted - char array ATTR_SYNC_DESIRED_STATE = 48, // private - byte array - versioned USER_ATTR_S4 = 49, // private - non-encrypted - char array USER_ATTR_S4_CONTAINER = 50, // private - non-encrypted - char array USER_ATTR_DEV_OPT = 51, // private - encrypted - byte array USER_ATTR_RECENT_CLEAR_TIMESTAMP = 52, // private - encrypted - byte array - non-versioned }; enum { NODE_ATTR_DURATION = 0, NODE_ATTR_COORDINATES = 1, NODE_ATTR_ORIGINALFINGERPRINT = 2, NODE_ATTR_LABEL = 3, NODE_ATTR_FAV = 4, // "fav" NODE_ATTR_S4 = 5, NODE_ATTR_SEN = 6, // "sen" NODE_ATTR_DESCRIPTION = 7, }; enum { PAYMENT_METHOD_BALANCE = 0, PAYMENT_METHOD_PAYPAL = 1, PAYMENT_METHOD_ITUNES = 2, PAYMENT_METHOD_GOOGLE_WALLET = 3, PAYMENT_METHOD_BITCOIN = 4, PAYMENT_METHOD_UNIONPAY = 5, PAYMENT_METHOD_FORTUMO = 6, PAYMENT_METHOD_STRIPE = 7, // credit-card (stripe) PAYMENT_METHOD_CREDIT_CARD = 8, PAYMENT_METHOD_CENTILI = 9, PAYMENT_METHOD_PAYSAFE_CARD = 10, PAYMENT_METHOD_ASTROPAY = 11, PAYMENT_METHOD_RESERVED = 12, // TBD PAYMENT_METHOD_WINDOWS_STORE = 13, PAYMENT_METHOD_TPAY = 14, PAYMENT_METHOD_DIRECT_RESELLER = 15, PAYMENT_METHOD_ECP = 16, PAYMENT_METHOD_SABADELL = 17, PAYMENT_METHOD_HUAWEI_WALLET = 18, PAYMENT_METHOD_STRIPE2 = 19, // credit-card (stripe) PAYMENT_METHOD_WIRE_TRANSFER = 999 }; enum { TRANSFER_METHOD_NORMAL = 0, TRANSFER_METHOD_ALTERNATIVE_PORT = 1, TRANSFER_METHOD_AUTO = 2, TRANSFER_METHOD_AUTO_NORMAL = 3, TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 }; enum { PUSH_NOTIFICATION_ANDROID = 1, PUSH_NOTIFICATION_IOS_VOIP = 2, PUSH_NOTIFICATION_IOS_STD = 3, PUSH_NOTIFICATION_ANDROID_HUAWEI = 4 }; enum { PASSWORD_STRENGTH_VERYWEAK = 0, PASSWORD_STRENGTH_WEAK = 1, PASSWORD_STRENGTH_MEDIUM = 2, PASSWORD_STRENGTH_GOOD = 3, PASSWORD_STRENGTH_STRONG = 4 }; enum { RETRY_NONE = 0, RETRY_CONNECTIVITY = 1, RETRY_SERVERS_BUSY = 2, RETRY_API_LOCK = 3, RETRY_RATE_LIMIT = 4, // RETRY_LOCAL_LOCK = 5, // obsolete RETRY_UNKNOWN = 6 }; enum { KEEP_ALIVE_CAMERA_UPLOADS = 0 }; enum { STORAGE_STATE_UNKNOWN = -9, STORAGE_STATE_GREEN = 0, STORAGE_STATE_ORANGE = 1, STORAGE_STATE_RED = 2, STORAGE_STATE_CHANGE = 3, /** @deprecated not notified any more */ STORAGE_STATE_PAYWALL = 4, }; enum { BUSINESS_STATUS_EXPIRED = -1, BUSINESS_STATUS_INACTIVE = 0, // no business subscription BUSINESS_STATUS_ACTIVE = 1, BUSINESS_STATUS_GRACE_PERIOD = 2 }; enum { AFFILIATE_TYPE_INVALID = 0, // legacy mode AFFILIATE_TYPE_ID = 1, AFFILIATE_TYPE_FILE_FOLDER = 2, AFFILIATE_TYPE_CHAT = 3, AFFILIATE_TYPE_CONTACT = 4, }; enum { ACCOUNT_NOT_BLOCKED = 0, // ACCOUNT_BLOCKED_EXCESS_DATA_USAGE = 100, (obsolete) ACCOUNT_BLOCKED_TOS_COPYRIGHT = 200, // suspended due to copyright violations ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = 300, // suspended due to multiple breaches of MEGA ToS ACCOUNT_BLOCKED_SUBUSER_DISABLED = 400, // subuser disabled by business administrator ACCOUNT_BLOCKED_SUBUSER_REMOVED = 401, // subuser removed by business administrator /* SMS verification was deprecated. This symbol is deprecated. Please don't use it in * new code. */ ACCOUNT_BLOCKED_VERIFICATION_SMS = 500, // (deprecated) ACCOUNT_BLOCKED_VERIFICATION_EMAIL = 700, // temporary blocked, require email verification }; enum { BACKUP_TYPE_INVALID = -1, BACKUP_TYPE_TWO_WAY_SYNC = 0, BACKUP_TYPE_UP_SYNC = 1, BACKUP_TYPE_DOWN_SYNC = 2, BACKUP_TYPE_CAMERA_UPLOADS = 3, BACKUP_TYPE_MEDIA_UPLOADS = 4, // Android has a secondary CU BACKUP_TYPE_BACKUP_UPLOAD = 5, }; enum { ADS_DEFAULT = 0x0, // If you don't want to set any overrides/flags, then please provide 0 ADS_FORCE_ADS = 0x200, // Force enable ads regardless of any other factors. ADS_IGNORE_MEGA = 0x400, // Show ads even if the current user or file owner is a MEGA employee. ADS_IGNORE_COUNTRY = 0x800, // Show ads even if the user is not within an enabled country. ADS_IGNORE_IP = 0x1000, // Show ads even if the user is on a blacklisted IP (MEGA ips). ADS_IGNORE_PRO = 0x2000, // Show ads even if the current user or file owner is a PRO user. ADS_FLAG_IGNORE_ROLLOUT = 0x4000, // Ignore the rollout logic which only servers ads to 10% of users based on their IP. }; enum { CREATE_ACCOUNT = 0, RESUME_ACCOUNT = 1, CANCEL_ACCOUNT = 2, CREATE_EPLUSPLUS_ACCOUNT = 3, RESUME_EPLUSPLUS_ACCOUNT = 4, }; enum { CREATE_SET = (1 << 0), OPTION_SET_NAME = (1 << 1), OPTION_SET_COVER = (1 << 2), }; enum { CREATE_ELEMENT = (1 << 0), OPTION_ELEMENT_NAME = (1 << 1), OPTION_ELEMENT_ORDER = (1 << 2), }; enum { TAG_NODE_SET = 0, TAG_NODE_REMOVE = 1, TAG_NODE_UPDATE = 2, }; enum { CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO = 0, CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_YES = 1, }; enum { IMPORT_PASSWORD_SOURCE_GOOGLE = 0, }; enum { IMPORTED_PASSWORD_ERROR_PARSER = 1, IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD = 2, IMPORTED_PASSWORD_ERROR_MISSINGNAME = 3, IMPORTED_PASSWORD_ERROR_MISSING_TOTP_SHSE = 4, IMPORTED_PASSWORD_ERROR_INVALID_TOTP_SHSE = 5, IMPORTED_PASSWORD_ERROR_MISSING_TOTP_NDIGITS = 6, IMPORTED_PASSWORD_ERROR_INVALID_TOTP_NDIGITS = 7, IMPORTED_PASSWORD_ERROR_MISSING_TOTP_EXPTIME = 8, IMPORTED_PASSWORD_ERROR_INVALID_TOTP_EXPTIME = 9, IMPORTED_PASSWORD_ERROR_MISSING_TOTP_HASH_ALG = 10, IMPORTED_PASSWORD_ERROR_INVALID_TOTP_HASH_ALG = 11, IMPORTED_PASSWORD_ERROR_MISSING_CREDIT_CARD_NUMBER = 12, IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_NUMBER = 13, IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_CVV = 14, IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_EXPIRATION_DATE = 15, }; enum { TRANSFER_STATS_DOWNLOAD = 0, TRANSFER_STATS_UPLOAD = 1, TRANSFER_STATS_BOTH = 2, TRANSFER_STATS_MAX = TRANSFER_STATS_BOTH, }; /** * @enum ActionType * @brief Enumeration representing different types of trigger actions for surveys. * * This enum is used to define actions that will trigger specific surveys. * Each action is associated with a particular user activity. */ enum SurveyTriggerActionId { ACT_END_UPLOAD = 1, ACT_END_MEETING = 2, ACT_CLOSING_DOC = 3, ACT_END_VIDEO = 4, ACT_END_AUDIO = 5, ACT_INIT_BACKUP = 6, ACT_END_PHOTO_UPLOAD = 7, ACT_END_ALBUM_UPLOAD = 8, ACT_SHARE_FOLDER_FILE = 9, }; enum { PWM_NODE_TYPE_PASSWORD = 1, PWM_NODE_TYPE_CREDIT_CARD = 2, }; enum { JSON_LOG_NONE = 0, JSON_LOG_CHUNK_RECEIVED = 1, JSON_LOG_CHUNK_PROCESSING = 1 << 1, JSON_LOG_CHUNK_CONSUMED = 1 << 2, JSON_LOG_SENDING = 1 << 3, JSON_LOG_NONCHUNK_RECEIVED = 1 << 4, }; /** * @brief PITAG trigger codes exposed at API level. * * Maps 1:1 with PitagTrigger in types.h. */ static constexpr char PITAG_TRIGGER_NOT_APPLICABLE = '.'; static constexpr char PITAG_TRIGGER_PICKER = 'p'; static constexpr char PITAG_TRIGGER_DRAG_AND_DROP = 'd'; static constexpr char PITAG_TRIGGER_CAMERA = 'c'; static constexpr char PITAG_TRIGGER_SCANNER = 's'; static constexpr char PITAG_TRIGGER_SYNC_ALGORITHM = 'a'; static constexpr char PITAG_TRIGGER_SHARE_FROM_APP = 'S'; static constexpr char PITAG_TRIGGER_CAMERA_CAPTURE = 'C'; static constexpr char PITAG_TRIGGER_EXPLORER_EXTENSION = 'e'; static constexpr char PITAG_TRIGGER_VOICE_RECORDER = 'v'; /** * @brief PITAG target codes exposed at API level. * * Maps 1:1 with PitagTarget in types.h. * Apps uploading to chats should set the appropriate chat target (c, C, or s); * for other uploads keep the default value to avoid interfering with internal logic. */ static constexpr char PITAG_TARGET_NOT_APPLICABLE = '.'; static constexpr char PITAG_TARGET_CLOUD_DRIVE = 'D'; static constexpr char PITAG_TARGET_CHAT_1TO1 = 'c'; static constexpr char PITAG_TARGET_CHAT_GROUP = 'C'; static constexpr char PITAG_TARGET_NOTE_TO_SELF = 's'; static constexpr char PITAG_TARGET_INCOMING_SHARE = 'i'; static constexpr char PITAG_TARGET_MULTIPLE_CHATS = 'M'; /** * @brief Optional parameters to customize an upload. */ static constexpr int64_t INVALID_CUSTOM_MOD_TIME = -1; static constexpr int CHAT_OPTIONS_EMPTY = 0; static constexpr int MAX_NODE_DESCRIPTION_SIZE = 3000; /** * @brief Constructor suitable for most applications * * @param basePath Base path to store the local cache * If you pass NULL to this parameter, the SDK will use the current working directory. * * @param userAgent User agent to use in network requests * If you pass NULL to this parameter, a default user agent will be used * * @param workerThreadCount The number of worker threads for encryption or other operations * Using worker threads means that synchronous function calls on MegaApi will be blocked * less, and uploads and downloads can proceed more quickly on very fast connections. * * @param clientType Client type (default, VPN or Password Manager) enables SDK to function * differently * */ MegaApi(const char* appKey, const char* basePath = NULL, const char* userAgent = NULL, unsigned workerThreadCount = 1, int clientType = CLIENT_TYPE_DEFAULT); /** * @brief MegaApi Constructor that uses a given GFX provider * * The SDK attach thumbnails and previews to all uploaded images. To generate them, it needs * a graphics provider. * @see MegaGfxProvider * * @param provider Graphics processing provider. The SDK will use it to generate previews * and thumbnails. Once MegaApi returns, the provider couldn't be reused and the caller * should release it. * * @param basePath Base path to store the local cache * If you pass NULL to this parameter, the SDK will use the current working directory. * * @param userAgent User agent to use in network requests * If you pass NULL to this parameter, a default user agent will be used * * @param workerThreadCount The number of worker threads for encryption or other operations * Using worker threads means that synchronous function calls on MegaApi will be blocked * less, and uploads and downloads can proceed more quickly on very fast connections. * * @param clientType Client type (default, VPN or Password Manager) enables SDK to function * differently * */ MegaApi(const char* appKey, MegaGfxProvider* provider, const char* basePath = NULL, const char* userAgent = NULL, unsigned workerThreadCount = 1, int clientType = CLIENT_TYPE_DEFAULT); #ifdef HAVE_MEGAAPI_RPC MegaApi(); #endif virtual ~MegaApi(); /** * @brief Register a listener to receive all events (requests, transfers, global, synchronization) * * You can use MegaApi::removeListener to stop receiving events. * * @param listener Listener that will receive all events (requests, transfers, global, synchronization) */ void addListener(MegaListener* listener); /** * @brief Register a listener to receive all events about requests * * You can use MegaApi::removeRequestListener to stop receiving events. * * @param listener Listener that will receive all events about requests */ virtual void addRequestListener(MegaRequestListener* listener); /** * @brief Register a listener to receive all events about transfers * * You can use MegaApi::removeTransferListener to stop receiving events. * * @param listener Listener that will receive all events about transfers */ void addTransferListener(MegaTransferListener* listener); /** * @brief Register a listener to receive global events * * You can use MegaApi::removeGlobalListener to stop receiving events. * * @param listener Listener that will receive global events */ void addGlobalListener(MegaGlobalListener* listener); /** * @brief Add a listener for all events related to backups * @param listener Listener that will receive backup events */ void addScheduledCopyListener(MegaScheduledCopyListener *listener); /** * @brief * Unregister a backup listener * * @paramlistener * Object that will be unregistered * * @return * True if listener was found and unregistered. */ bool removeScheduledCopyListener(MegaScheduledCopyListener *listener); /** * @brief * Unregister a listener * * This listener won't receive more events. * * @param listener * Object that is unregistered * * @return * True if listener was found and unregistered. */ bool removeListener(MegaListener* listener); /** * @brief * Unregister a MegaRequestListener * * This listener won't receive more events. * * @param listener * Object that is unregistered * * @return * True if listener was found and unregistered. */ bool removeRequestListener(MegaRequestListener* listener); /** * @brief * Unregister a MegaTransferListener * * This listener won't receive more events. * * @param listener * Object that is unregistered * * @return * True if listener was found and unregistered. */ bool removeTransferListener(MegaTransferListener* listener); /** * @brief * Unregister a MegaGlobalListener * * This listener won't receive more events. * * @param listener * Object that is unregistered * * @return * True if listener was found and unregistered. */ bool removeGlobalListener(MegaGlobalListener* listener); /** * @brief Get internal timestamp used by the SDK * * This is a time used in certain internal operations. * * @return actual SDK time in deciseconds */ long long getSDKtime(); /** * @brief Get an URL to transfer the current session to the webclient * * This function creates a new session for the link so logging out in the web client won't * log out the current session. * * The associated request type is MegaRequest::TYPE_GET_SESSION_TRANSFER_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - URL to open the desired page with the same account * * If the client is logged in, but the account is not fully confirmed (ie. singup not * completed yet), this method will return API_EACCESS. * * If the client is not logged in, there won't be any session to transfer, but this method * will still return the MEGA's host (ie. https://mega.app) followed by /#. * * @param path Path inside the MEGA's host that we want to open with the current session * * For example, if you want to open https://mega.app/#pro, the parameter of this function * should be "pro". * * @param listener MegaRequestListener to track this request */ void getSessionTransferURL(const char *path, MegaRequestListener *listener = NULL); /** * @brief Converts a Base32-encoded user handle (JID) to a MegaHandle * * @param base32Handle Base32-encoded handle (JID) * @return User handle */ static MegaHandle base32ToHandle(const char* base32Handle); /** * @brief Converts a Base64-encoded node handle to a MegaHandle * * The returned value can be used to recover a MegaNode using MegaApi::getNodeByHandle * You can revert this operation using MegaApi::handleToBase64 * * @param base64Handle Base64-encoded node handle * @return Node handle */ static MegaHandle base64ToHandle(const char* base64Handle); /** * @brief Converts a Base64-encoded user handle to a MegaHandle * * You can revert this operation using MegaApi::userHandleToBase64 * * @param base64Handle Base64-encoded user handle * @return User handle */ static MegaHandle base64ToUserHandle(const char* base64Handle); /** * @brief * Converts a Base64-encoded backup ID to a MegaHandle. * * You can revert this operation using MegaApi::backupIdToBase64. * * @param backupId * Base64-encoded Backup ID. * * @return * Backup ID. */ static MegaHandle base64ToBackupId(const char* backupId); /** * @brief Converts the handle of a node to a Base64-encoded string * * You take ownership of the returned value. Use delete[] to release the memory. * You can revert this operation using MegaApi::base64ToHandle * * @param handle Node handle to be converted * @return Base64-encoded node handle */ static char* handleToBase64(MegaHandle handle); /** * @brief Converts a MegaHandle to a Base64-encoded string * * You take ownership of the returned value. Use delete[] to release the memory. * You can revert this operation using MegaApi::base64ToUserHandle * * @param handle User handle to be converted * @return Base64-encoded user handle */ static char* userHandleToBase64(MegaHandle handle); /** * @brief * Converts a Backup ID into a Base64-encoded string. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param backupId * Backup ID to be converted. * * @return * Base64-encoded backup ID. */ static const char* backupIdToBase64(MegaHandle backupId); /** * @brief Convert binary data to a base 64 encoded string * * For some operations such as background uploads, binary data must be converted to a format * suitable to be passed to the MegaApi interface. Use this function to do so. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param binaryData A pointer to the start of the binary data * @param length The number of bytes in the binary data * @return A newly allocated NULL-terminated string consisting of base64 characters. */ static char *binaryToBase64(const char* binaryData, size_t length); /** * @brief Convert data encoded in a base 64 string back to binary. * * This operation is the inverse of binaryToString64. * * You take ownership of the pointer assigned to *binary. * * @param base64string The base 64 encoded string to decode. * @param binary A pointer to a pointer to assign with a `new unsigned char[]` * allocated buffer containing the decoded binary data. * @param binarysize A pointer to a variable that will be assigned the size of the buffer allocated. */ static void base64ToBinary(const char *base64string, unsigned char **binary, size_t* binarysize); /** * @brief Add entropy to internal random number generators * * It's recommended to call this function with random data specially to * enhance security, * * @param data Byte array with random data * @param size Size of the byte array (in bytes) */ void addEntropy(char* data, unsigned int size); /** * @brief Retry all pending requests * * When requests fails they wait some time before being retried. That delay grows exponentially if the request * fails again. For this reason, and since this request is very lightweight, it's recommended to call it with * the default parameters on every user interaction with the application. This will prevent very big delays * completing requests. * * The associated request type with this request is MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * - MegaRequest::getNumber - Returns the second parameter * * If not possible to retrieve the DNS servers from the system, in iOS, this request will fail with * the error code MegaError::API_EACCESS in onRequestFinish(). * * @param disconnect true if you want to disconnect already connected requests * It's not recommended to set this flag to true if you are not fully sure about what are you doing. If you * send a request that needs some time to complete and you disconnect it in a loop without giving it enough time, * it could be retrying forever. * Using true in this parameter will trigger the callback MegaGlobalListener::onEvent and the callback * MegaListener::onEvent with the event type MegaEvent::EVENT_DISCONNECT. * * @param includexfers true to retry also transfers * It's not recommended to set this flag. Transfer has a retry counter and are aborted after a number of retries * MegaTransfer::getMaxRetries. Setting this flag to true, you will force more immediate retries and your transfers * could fail faster. * * @param listener MegaRequestListener to track this request */ void retryPendingConnections(bool disconnect = false, bool includexfers = false, MegaRequestListener* listener = NULL); /** * @brief Use custom DNS servers * * The SDK tries to automatically get and use DNS servers configured in the system at * startup. This function can be used to override that automatic detection and use a custom * list of DNS servers. It is also useful to provide working DNS servers to the SDK in * platforms in which it can't get them from the system. * * Since the usage of this function implies a change in DNS servers used by the SDK, all * connections are closed and restarted using the new list of new DNS servers, so calling * this function too often can cause many retries and problems to complete requests. Please * use it only at startup or when DNS servers need to be changed. * * To use this functionality the curl library should have been built with support for * c-ares. If there's no support for c-ares in the curl library, the error code provivded * in onRequestFinish is MegaError::API_ENOENT. * * The associated request type with this request is * MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS. Valid data in the MegaRequest object * received on callbacks: * - MegaRequest::getText - Returns the new list of DNS servers * * @param dnsServers New list of DNS servers. It must be a list of IPs separated by a comma * character ",". IPv6 servers are allowed (without brackets). An empty list can be used to * return the SDK to the default DNS resolution configuration. * * The usage of this function will trigger the callback MegaGlobalListener::onEvent and the * callback MegaListener::onEvent with the event type MegaEvent::EVENT_DISCONNECT. * * @param listener MegaRequestListener to track this request */ void setDnsServers(const char *dnsServers, MegaRequestListener* listener = NULL); /** * @brief Check if server-side Rubbish Bin autopurging is enabled for the current account * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * @return True if this feature is enabled. Otherwise false. */ bool serverSideRubbishBinAutopurgeEnabled(); /** * @brief Check if the account has VOIP push enabled * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * @return True if this feature is enabled. Otherwise false. */ bool appleVoipPushEnabled(); /** * @brief Check if the new format for public links is enabled * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if this feature is enabled. Otherwise, false. */ bool newLinkFormatEnabled(); /** * @brief Check if the logged in account is considered new * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * @return True if account is considered new. Otherwise, false. */ bool accountIsNew() const; /** * @brief Get the value of an A/B Test flag * * Any value greater than 0 means the flag is active. * * @param flag Name or key of the value to be retrieved. * * @return An unsigned integer with the value of the flag. */ unsigned int getABTestValue(const char* flag); /** * @brief Check if the opt-in or account unblocking SMS is allowed * * The result indicated whether the MegaApi::sendSMSVerificationCode function can be used. * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return 2 = Opt-in and unblock SMS allowed. 1 = Only unblock SMS allowed. 0 = No SMS * allowed * * @deprecated SMS verification was deprecated. This function is deprecated. Please don't * use it in new code. */ MEGA_DEPRECATED int smsAllowedState(); /** * @brief Get the verified phone number for the account logged in * * Returns the phone number previously confirmed with MegaApi::sendSMSVerificationCode * and MegaApi::checkSMSVerificationCode. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return NULL if there is no verified number, otherwise a string containing that phone * number. * * @deprecated SMS verification was deprecated. This function is deprecated. Please don't * use it in new code. */ MEGA_DEPRECATED char* smsVerifiedPhoneNumber(); /** * @brief Check if multi-factor authentication can be enabled for the current account. * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if multi-factor authentication can be enabled for the current account, otherwise false. */ bool multiFactorAuthAvailable(); /** * @brief Reset the verified phone number for the account logged in. * * The associated request type with this request is * MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER. * If there's no verified phone number associated for the account logged in, the error code * provided in onRequestFinish is MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request * * @deprecated SMS verification was deprecated. This function is deprecated. Please don't * use it in new code. */ MEGA_DEPRECATED void resetSmsVerifiedPhoneNumber(MegaRequestListener *listener = NULL); /** * @brief Check if multi-factor authentication is enabled for an account * * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email sent in the first parameter * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if multi-factor authentication is enabled or false if it's disabled. * * @param email Email to check * @param listener MegaRequestListener to track this request */ void multiFactorAuthCheck(const char *email, MegaRequestListener *listener = NULL); /** * @brief Get the secret code of the account to enable multi-factor authentication * The MegaApi object must be logged into an account to successfully use this function. * * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the Base32 secret code needed to configure multi-factor authentication. * * @param listener MegaRequestListener to track this request */ void multiFactorAuthGetCode(MegaRequestListener *listener = NULL); /** * @brief Enable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. * * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns true * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthEnable(const char *pin, MegaRequestListener *listener = NULL); /** * @brief Disable multi-factor authentication for the account * The MegaApi object must be logged into an account to successfully use this function. * * The associated request type with this request is MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false * - MegaRequest::getPassword - Returns the pin sent in the first parameter * * @param pin Valid pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthDisable(const char *pin, MegaRequestListener *listener = NULL); /** * @brief Log in to a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter * - MegaRequest::getText - Returns the third parameter * * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthLogin(const char* email, const char* password, const char* pin, MegaRequestListener *listener = NULL); /** * @brief Change the password of a MEGA account with multi-factor authentication enabled * * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * - MegaRequest::getText - Returns the pin code for multi-factor authentication * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthChangePassword(const char *oldPassword, const char *newPassword, const char* pin, MegaRequestListener *listener = NULL); /** * @brief Initialize the change of the email address associated to an account with multi-factor authentication enabled. * * The associated request type with this request is MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getText - Returns the pin code for multi-factor authentication * * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param email The new email to be associated to the account. * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthChangeEmail(const char *email, const char* pin, MegaRequestListener *listener = NULL); /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MegaRequest::TYPE_GET_CANCEL_LINK. * * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getText - Returns the pin code for multi-factor authentication * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @see MegaApi::confirmCancelAccount * * @param pin Pin code for multi-factor authentication * @param listener MegaRequestListener to track this request */ void multiFactorAuthCancelAccount(const char* pin, MegaRequestListener *listener = NULL); /** * @brief Fetch details related to time zones and the current default * * The associated request type with this request is MegaRequest::TYPE_FETCH_TIMEZONE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getFlag - Returns true to indicate that we want to force API request * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaTimeZoneDetails - Returns details about timezones and the current default * * @param listener MegaRequestListener to track this request */ void fetchTimeZone(MegaRequestListener* listener = NULL); /** * @brief Fetch details related to time zones and the current default. * * This method checks if we already have that information locally, to avoid an unnecessary API request, * otherwise we will request the information to API. * * The associated request type with this request is MegaRequest::TYPE_FETCH_TIMEZONE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getFlag - Returns false to indicate that we don't want to force API request * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaTimeZoneDetails - Returns details about timezones and the current default * * @param listener MegaRequestListener to track this request */ void fetchTimeZoneFromLocal(MegaRequestListener* listener = NULL); /** * @brief Log in to a MEGA account * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the first parameter * - MegaRequest::getPassword - Returns the second parameter * - MegaRequest::getTransferredBytes - Returns a relative epoch (global counter starting * from 0). This is used internally to early exit from expensive computations when a * cancellation request (ex: locallogout) needs to be executed quickly. * * If the email/password aren't valid the error code provided in onRequestFinish is * MegaError::API_ENOENT. * * @param email Email of the user * @param password Password * @param listener MegaRequestListener to track this request */ virtual void login(const char* email, const char* password, MegaRequestListener *listener = NULL); /** * @brief Log in to a public folder using a folder link * * After a successful login, you should call MegaApi::fetchNodes to get filesystem and * start working with the folder. * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Retuns the string "FOLDER" * - MegaRequest::getLink - Returns the public link to the folder * - MegaRequest::getTransferredBytes - See MegaApi::login() * * @param megaFolderLink Public link to a folder in MEGA * @param listener MegaRequestListener to track this request */ void loginToFolder(const char* megaFolderLink, MegaRequestListener *listener = NULL); /** * @brief Log in to a public folder using a folder link * * After a successful login, you should call MegaApi::fetchNodes to get filesystem and * start working with the folder. * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Retuns the string "FOLDER" * - MegaRequest::getLink - Returns the public link to the folder * - MegaRequest::getPassword - Returns the auth link used for writting * * If the provided authKey is not valid, onRequestFinish will * be called with the error code MegaError::API_EACCESS. * * @param megaFolderLink Public link to a folder in MEGA * @param authKey Authentication key to write into the folder link * @param listener MegaRequestListener to track this request */ void loginToFolder(const char* megaFolderLink, const char *authKey, MegaRequestListener *listener = NULL); /** * @brief Log in to a public folder using a folder link * * After a successful login, you should call MegaApi::fetchNodes to get filesystem and * start working with the folder. * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Retuns the string "FOLDER" * - MegaRequest::getLink - Returns the public link to the folder * - MegaRequest::getPassword - Returns the auth link used for writting * - MegaRequest::getFlag - Returns True if it will attempt to restore the folder link from * the cache, or False if it will fetch the nodes instead. * * If the provided authKey is not valid, onRequestFinish will * be called with the error code MegaError::API_EACCESS. * * @param megaFolderLink Public link to a folder in MEGA * @param authKey Authentication key to write into the folder link * @param tryToResumeFolderLinkFromCache If True and the folder link exists in the cache, * attempt to restore it; otherwise, fetch the nodes. * @param listener MegaRequestListener to track this request */ void loginToFolder(const char* megaFolderLink, const char* authKey, bool tryToResumeFolderLinkFromCache, MegaRequestListener* listener = nullptr); /** * @brief Log in to a MEGA account using a session key * * The associated request type with this request is MegaRequest::TYPE_LOGIN. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session key * - MegaRequest::getTransferredBytes - See MegaApi::login() * * @param session Session key previously dumped with MegaApi::dumpSession * @param listener MegaRequestListener to track this request */ void fastLogin(const char* session, MegaRequestListener *listener = NULL); /** * @brief Close a MEGA session * * All clients using this session will be automatically logged out. * * You can get session information using MegaApi::getExtendedAccountDetails. * Then use MegaAccountDetails::getNumSessions and MegaAccountDetails::getSession * to get session info. * MegaAccountSession::getHandle provides the handle that this function needs. * * If you use mega::INVALID_HANDLE, all sessions except the current one will be closed * * @param sessionHandle Handle of the session. Use mega::INVALID_HANDLE to cancel all sessions except the current one * @param listener MegaRequestListener to track this request */ void killSession(MegaHandle sessionHandle, MegaRequestListener *listener = NULL); /** * @brief Get data about the logged account * * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the name of the logged user * - MegaRequest::getPassword - Returns the the public RSA key of the account, Base64-encoded * - MegaRequest::getPrivateKey - Returns the private RSA key of the account, Base64-encoded * * @param listener MegaRequestListener to track this request */ void getUserData(MegaRequestListener *listener = NULL); /** * @brief Get data about a contact * * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the contact, Base64-encoded * * @param user Contact to get the data * @param listener MegaRequestListener to track this request */ void getUserData(MegaUser *user, MegaRequestListener *listener = NULL); /** * @brief Get information about a MEGA user * * The associated request type with this request is MegaRequest::TYPE_GET_USER_DATA. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email or the Base64 handle of the user, * depending on the value provided as user parameter * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the public RSA key of the user, Base64-encoded * * @param user Email or Base64 handle of the user * @param listener MegaRequestListener to track this request */ void getUserData(const char *user, MegaRequestListener *listener = NULL); /** * @brief Fetch miscellaneous flags when not logged in * * The associated request type with this request is MegaRequest::TYPE_GET_MISC_FLAGS. * * When onRequestFinish is called with MegaError::API_OK, the miscellaneous flags are available. * If you are logged in into an account, the error code provided in onRequestFinish is * MegaError::API_EACCESS. * * @see MegaApi::multiFactorAuthAvailable * @see MegaApi::newLinkFormatEnabled * @see MegaApi::smsAllowedState * * @param listener MegaRequestListener to track this request */ void getMiscFlags(MegaRequestListener *listener = NULL); /** * @brief Trigger special account state changes for own accounts, for testing * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the first parameter * - MegaRequest::getEmail - Returns the second parameter * * Possible errors are: * - EACCESS if the calling account is not allowed to perform this method (not a mega email account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid * - EBLOCKED if the target account is not allowed (this could also happen if the target account does not exist) * * Possible commands: * - "aodq" - Advance ODQ Warning State * If called, this will advance your ODQ warning state until the final warning state, * at which point it will turn on the ODQ paywall for your account. It requires an account lock on the target account. * This subcommand will return the 'step' of the warning flow you have advanced to - 1, 2, 3 or 4 * (the paywall is turned on at step 4) * * Valid data in the MegaRequest object received in onRequestFinish when the error code is MegaError::API_OK: * + MegaRequest::getNumber - Returns the number of warnings (1, 2, 3 or 4). * * Possible errors in addition to the standard dev ones are: * + EFAILED - your account is not in the RED stoplight state * * @param command The subcommand for the specific operation * @param email Optional email of the target email's account. If null, it will use the logged-in account * @param listener MegaRequestListener to track this request * @deprecated Use MegaApi::sendOdqDevCommand instead, for new API dev commands, a new public method will * be created for each one */ void sendDevCommand(const char *command, const char *email = nullptr, MegaRequestListener *listener = nullptr); /** * @brief Send dev API command, Advance ODQ Warning State for own accounts (for testing purposes) * * If called, this will advance your ODQ warning state until the final warning state, * at which point it will turn on the ODQ paywall for your account. It requires an account lock on the target account. * This subcommand will return the 'step' of the warning flow you have advanced to - 1, 2, 3 or 4 * (the paywall is turned on at step 4) * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the API dev command ("aodq") * - MegaRequest::getEmail - Returns the target email account, or NULL if target is the logged-in account * * Valid data in the MegaRequest object received in onRequestFinish when the error code is MegaError::API_OK: * - MegaRequest::getNumber - Returns the number of warnings (1, 2, 3 or 4). * * On the onRequestFinish error, the error code associated to the MegaError can be: * - EACCESS if the calling account is not allowed to perform this method (not a mega email account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid * - EBLOCKED if the target account is not allowed (this could also happen if the target account does not exist) * - EFAILED - your account is not in the RED stoplight state * * @param email Optional email of the target email's account. If null, it will use the logged-in account * @param listener MegaRequestListener to track this request */ void sendOdqDevCommand(const char *email = nullptr, MegaRequestListener *listener = nullptr); /** * @brief Send dev API command, Set used transfer quota for own accounts (for testing purposes) * * Sets the amount of transfer quota the target user has used from their PRO allocation. * This subcommand can only be run with PRO users. * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the API dev command ("tq") * - MegaRequest::getEmail - Returns the target email account, or NULL if target is the logged-in account * - MegaRequest::getTotalBytes - Returns the amount of transfer quota the target has used, in bytes * * On the onRequestFinish error, the error code associated to the MegaError can be: * - EACCESS if the calling account is not allowed to perform this method (not a mega email account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid, or if target account is not PRO * - EBLOCKED if the target account is not allowed (this could also happen if the target account does not exist) * - EMASTERONLY - if the target is the business internal account as they don't login * * @param quota The amount of transfer quota the target has used, in bytes * @param email Optional email of the target email's account. If null, it will use the logged-in account * @param listener MegaRequestListener to track this request */ void sendUsedTransferQuotaDevCommand(long long quota, const char *email = nullptr, MegaRequestListener *listener = nullptr); /** * @brief Send dev API command, Set business status for own accounts (for testing purposes) * * Sets the status of the business account. The target user can be the business internal account, * or a master or sub-user in the business. The status set by this method is permanent until removed, * it does not transition to grace period and expired over time. * * The following values are valid for business status: * - set the business expired = -1 * - clear the status override and set the business back to status of their payments = 0 * - set the business active = 1 * - set the business in grace period = 2 * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the API dev command ("bs") * - MegaRequest::getEmail - Returns the target email account, or NULL if target is the logged-in account * - MegaRequest::getAccess - Returns the business status * * On the onRequestFinish error, the error code associated to the MegaError can be: * - EACCESS if the calling account is not allowed to perform this method (not a mega email account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid, or if target account is not part of a business account * - EBLOCKED if the target account is not allowed (this could also happen if the target account does not exist) * * @param businessStatus The business status * @param email Optional email of the target email's account. If null, it will use the logged-in account * @param listener MegaRequestListener to track this request */ void sendBusinessStatusDevCommand(int businessStatus, const char *email = nullptr, MegaRequestListener *listener = nullptr); /** * @brief Send dev API command, set account status (for testing purposes) * * Modifies the status of a user's account. * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the API dev command ("sal") * - MegaRequest::getEmail - Returns the target email account, or NULL if target is the * logged-in account * - MegaRequest::getNumDetails - Returns the account level. * - MegaRequest::getTotalBytes - Returns the quota length in months. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - EACCESS if the calling account is not allowed to perform this method (not a mega email * account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid. * * @param accountLevel Desired account level. * @param quotaLengthInMonths Length of quota in months. * @param email Optional email of the target email's account. If null, it will use the * logged-in account * @param listener MegaRequestListener to track this request */ void sendSetAccountLevelDevCommand(int accountLevel, int quotaLengthInMonths, const char* email = nullptr, MegaRequestListener* listener = nullptr); /** * @brief Send dev API command, Set user status for own accounts (for testing purposes) * * Sets the status of a user. * * The following values are valid for user status: * - Enabled = 0 * - Suspended (generic) = 2 * - Suspended (for payment, but not used) = 3 * - Suspended (copyright complaint) = 4 * - Suspended (admin full disable) = 5 * - Suspended (admin partial disable) = 6 * - Suspended (Emergency Takedown) = 7 * - Suspended until SMS verified = 8 (obsolete) * - Suspended until Email verified = 9 * * Note that Action packets are not sent for a suspended user, but next command or action * packet request will give a EBLOCKED error. * * The associated request type with this request is MegaRequest::TYPE_SEND_DEV_COMMAND. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the API dev command ("us") * - MegaRequest::getEmail - Returns the target email account, or NULL if target is the * logged-in account * - MegaRequest::getNumDetails - Returns the user status * * On the onRequestFinish error, the error code associated to the MegaError can be: * - EACCESS if the calling account is not allowed to perform this method (not a mega email * account, not the right IP, etc). * - EARGS if the subcommand is not present or is invalid, if the status is out of range or * the target is a business internal account * - EBLOCKED if the target account is not allowed (this could also happen if the target * account does not exist) * * @param userStatus The user status * @param email Optional email of the target email's account. If null, it will use the * logged-in account * @param listener MegaRequestListener to track this request */ void sendUserStatusDevCommand(int userStatus, const char *email = nullptr, MegaRequestListener *listener = nullptr); /** * @brief Returns the current session key * * You have to be logged in to get a valid session key. Otherwise, * this function returns NULL. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Current session key */ char *dumpSession(); /** * @brief Returns the current sequence number * * The sequence number indicates the state of a MEGA account known by the SDK. * When external changes are received via actionpackets, the sequence number is * updated and changes are commited to the local cache. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return The current sequence number */ char *getSequenceNumber(); /** * @brief Returns the current sequence tag * * The sequence tag indicates the state of a MEGA account known by the SDK. * When external changes (*) are received via actionpackets, the sequence tag is * updated and changes are commited to the local cache. * Contrary to sequence numbers, sequence tags can be compared. * (*) external changes occurred in v3 enabled clients. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return The current sequence tag */ char *getSequenceTag(); /** * @brief Get an authentication token that can be used to identify the user account * * If this MegaApi object is not logged into an account, this function will return NULL * * The value returned by this function can be used in other instances of MegaApi * thanks to the function MegaApi::setAccountAuth. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Authentication token */ char *getAccountAuth(); /** * @brief Use an authentication token to identify an account while accessing public folders * * This function is useful to preserve the PRO status when a public folder is being * used. The identifier will be sent in all API requests made after the call to this function. * * To stop using the current authentication token, it's needed to explicitly call * this function with NULL as parameter. Otherwise, the value set would continue * being used despite this MegaApi object is logged in or logged out. * * It's recommended to call this function before the usage of MegaApi::loginToFolder * * @param auth Authentication token used to identify the account of the user. * You can get it using MegaApi::getAccountAuth with an instance of MegaApi logged into * an account. */ void setAccountAuth(const char* auth); /** * @brief Initialize the creation of a new MEGA account, with firstname and lastname * * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getPassword - Returns the password for the account * - MegaRequest::getName - Returns the firstname of the user * - MegaRequest::getText - Returns the lastname of the user * - MegaRequest::getParamType - Returns the value MegaApi::CREATE_ACCOUNT * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getSessionKey - Returns the session id to resume the process * * If this request succeeds, a new ephemeral account will be created for the new user * and a confirmation email will be sent to the specified email address. The app may * resume the create-account process by using MegaApi::resumeCreateAccount. * * If an account with the same email already exists, you will get the error code * MegaError::API_EEXIST in onRequestFinish * * @param email Email for the account * @param password Password for the account * @param firstname Firstname of the user * @param lastname Lastname of the user * @param listener MegaRequestListener to track this request */ void createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaRequestListener *listener = NULL); /** * @brief Create Ephemeral++ account * * This kind of account allows to join chat links and to keep the session in the device * where it was created. * * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the firstname of the user * - MegaRequest::getText - Returns the lastname of the user * - MegaRequest::getParamType - Returns the value MegaApi:CREATE_EPLUSPLUS_ACCOUNT * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getSessionKey - Returns the session id to resume the process * * If this request succeeds, a new ephemeral++ account will be created for the new user. * The app may resume the create-account process by using MegaApi::resumeCreateAccountEphemeralPlusPlus. * * @note This account should be confirmed in same device it was created * * @param firstname Firstname of the user * @param lastname Lastname of the user * @param listener MegaRequestListener to track this request */ void createEphemeralAccountPlusPlus(const char* firstname, const char* lastname, MegaRequestListener *listener = NULL); /** * @brief Resume a registration process * * When a user begins the account registration process by calling MegaApi::createAccount, * an ephemeral account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see MegaApi::sendSignupLink) and also to receive notifications in case the * user confirms the account using another client (MegaGlobalListener::onEvent or * MegaListener::onEvent, EVENT_ACCOUNT_CONFIRMATION). It is also possible to cancel the * registration process by MegaApi::cancelCreateAccount, which invalidates the signup link * associated to the ephemeral session (the session will be still valid). * * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session id to resume the process * - MegaRequest::getParamType - Returns the value MegaApi::RESUME_ACCOUNT * * In case the account is already confirmed, the associated request will fail with * error MegaError::API_EARGS. * * @param sid Session id valid for the ephemeral account (@see MegaApi::createAccount) * @param listener MegaRequestListener to track this request */ void resumeCreateAccount(const char* sid, MegaRequestListener *listener = NULL); /** * @brief Resume a registration process for an Ephemeral++ account * * When a user begins the account registration process by calling * MegaApi::createEphemeralAccountPlusPlus an ephemeral++ account is created. * * Until the user successfully confirms the signup link sent to the provided email address, * you can resume the ephemeral session in order to change the email address, resend the * signup link (@see MegaApi::sendSignupLink) and also to receive notifications in case the * user confirms the account using another client (MegaGlobalListener::onEvent or * MegaListener::onEvent, EVENT_ACCOUNT_CONFIRMATION). It is also possible to cancel the * registration process by MegaApi::cancelCreateAccount, which invalidates the signup link * associated to the ephemeral session (the session will be still valid). * * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getSessionKey - Returns the session id to resume the process * - MegaRequest::getParamType - Returns the value MegaApi::RESUME_EPLUSPLUS_ACCOUNT * * In case the account is already confirmed, the associated request will fail with * error MegaError::API_EARGS. * * @param sid Session id valid for the ephemeral++ account (@see * MegaApi::createEphemeralAccountPlusPlus) * @param listener MegaRequestListener to track this request */ void resumeCreateAccountEphemeralPlusPlus(const char* sid, MegaRequestListener *listener = NULL); /** * @brief Cancel a registration process * * If a signup link has been generated during registration process, call this function * to invalidate it. The ephemeral session will not be invalidated, only the signup link. * * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::CANCEL_ACCOUNT * * @param listener MegaRequestListener to track this request */ void cancelCreateAccount(MegaRequestListener *listener = NULL); /** * @brief Sends the confirmation email for a new account * * This function is useful to send the confirmation link again or to send it to a different * email address, in case the user mistyped the email at the registration form. It can only * be used after a successful call to MegaApi::createAccount or * MegaApi::resumeCreateAccount. * * The associated request type with this request is MegaRequest::TYPE_SEND_SIGNUP_LINK. * * @param email Email for the account * @param name Fullname of the user (firstname + lastname) * @param listener MegaRequestListener to track this request */ void resendSignupLink(const char* email, const char *name, MegaRequestListener *listener = NULL); /** * @brief Get information about a confirmation link or a new signup link * * The associated request type with this request is MegaRequest::TYPE_QUERY_SIGNUP_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the confirmation link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getName - Returns the name associated with the link (available only for confirmation links) * - MegaRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false * * If already logged-in into a different account, you will get the error code MegaError::API_EACCESS * in onRequestFinish. * If logged-in into the account that is attempted to confirm and the account is already confirmed, you * will get the error code MegaError::API_EEXPIRED in onRequestFinish. * In both cases, the MegaRequest::getEmail will return the email of the account that was attempted * to confirm, and the MegaRequest::getName will return the name. * * @param link Confirmation link (confirm) or new signup link (newsignup) * @param listener MegaRequestListener to track this request */ void querySignupLink(const char* link, MegaRequestListener *listener = NULL); /** * @brief Confirm a MEGA account using a confirmation link and the user password * * The associated request type with this request is MegaRequest::TYPE_CONFIRM_ACCOUNT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the confirmation link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Email of the account * - MegaRequest::getName - Name of the user * * As a result of a successful confirmation, the app will receive callbacks * MegaListener::onEvent and MegaGlobalListener::onEvent with events of type * MegaEvent::EVENT_ACCOUNT_CONFIRMATION. You can check the email used to confirm * the account by checking MegaEvent::getText. @see MegaListener::onEvent. * * If already logged-in into a different account, you will get the error code * MegaError::API_EACCESS in onRequestFinish. If logged-in into the account that is * attempted to confirm and the account is already confirmed, you will get the error code * MegaError::API_EEXPIRED in onRequestFinish. In both cases, the MegaRequest::getEmail will * return the email of the account that was attempted to confirm, and the * MegaRequest::getName will return the name. * * @param link Confirmation link * @param listener MegaRequestListener to track this request */ void confirmAccount(const char* link, MegaRequestListener* listener = NULL); /** * @deprecated Use the signature without the \c password parameter */ MEGA_DEPRECATED void confirmAccount(const char* link, const char *password, MegaRequestListener *listener = NULL); /** * @brief Initialize the reset of the existing password, with and without the Master Key. * * The associated request type with this request is MegaRequest::TYPE_GET_RECOVERY_LINK. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email for the account * - MegaRequest::getFlag - Returns whether the user has a backup of the master key or not. * * If this request succeeds, a recovery link will be sent to the user. * If no account is registered under the provided email, you will get the error code * MegaError::API_ENOENT in onRequestFinish * * @param email Email used to register the account whose password wants to be reset. * @param hasMasterKey True if the user has a backup of the master key. Otherwise, false. * @param listener MegaRequestListener to track this request */ void resetPassword(const char *email, bool hasMasterKey, MegaRequestListener *listener = NULL); /** * @brief Get information about a recovery link created by MegaApi::resetPassword. * * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getFlag - Return whether the link requires masterkey to reset password. * * @param link Recovery link (recover) * @param listener MegaRequestListener to track this request */ void queryResetPasswordLink(const char *link, MegaRequestListener *listener = NULL); /** * @brief Set a new password for the account pointed by the recovery link. * * Recovery links are created by calling MegaApi::resetPassword and may or may not * require to provide the Master Key. * * @see The flag of the MegaRequest::TYPE_QUERY_RECOVERY_LINK in MegaApi::queryResetPasswordLink. * * The associated request type with this request is MegaRequest::TYPE_CONFIRM_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link * - MegaRequest::getPassword - Returns the new password * - MegaRequest::getPrivateKey - Returns the Master Key, when provided * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getFlag - Return whether the link requires masterkey to reset password. * * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link The recovery link sent to the user's email address. * @param newPwd The new password to be set. * @param masterKey Base64-encoded string containing the master key (optional). * @param listener MegaRequestListener to track this request */ void confirmResetPassword(const char *link, const char *newPwd, const char *masterKey = NULL, MegaRequestListener *listener = NULL); /** * @brief Check that the provided recovery key (master key) is correct * * The associated request type with this request is MegaRequest::TYPE_CHECK_RECOVERY_KEY * No data in the MegaRequest object received on all callbacks * * @param link The recovery link sent to the user's email address. * @param recoveryKey Base64-encoded string containing the recoveryKey (masterKey). * @param listener MegaRequestListener to track this request */ void checkRecoveryKey(const char* link, const char* recoveryKey, MegaRequestListener* listener = NULL); /** * @brief Initialize the cancellation of an account. * * The associated request type with this request is MegaRequest::TYPE_GET_CANCEL_LINK. * * If this request succeeds, a cancellation link will be sent to the email address of the user. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @see MegaApi::confirmCancelAccount * * @param listener MegaRequestListener to track this request */ void cancelAccount(MegaRequestListener *listener = NULL); /** * @brief Get information about a cancel link created by MegaApi::cancelAccount. * * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the cancel link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * @param link Cancel link (cancel) * @param listener MegaRequestListener to track this request */ void queryCancelLink(const char *link, MegaRequestListener *listener = NULL); /** * @brief Effectively parks the user's account without creating a new fresh account. * * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * * The associated request type with this request is MegaRequest::TYPE_CONFIRM_CANCEL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the recovery link * - MegaRequest::getPassword - Returns the new password * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * @param link Cancellation link sent to the user's email address; * @param pwd Password for the account. * @param listener MegaRequestListener to track this request */ void confirmCancelAccount(const char *link, const char *pwd, MegaRequestListener *listener = NULL); /** * @brief Allow to resend the verification email for Weak Account Protection * * The verification email will be resent to the same address as it was previously sent to. * * This function can be called if the reason for being blocked is: * 700: the account is supended for Weak Account Protection. * * If the logged in account is not suspended or is suspended for some other reason, * onRequestFinish will be called with the error code MegaError::API_EACCESS. * * If the logged in account has not been sent the unlock email before, * onRequestFinish will be called with the error code MegaError::API_EARGS. * * If the logged in account has already sent the unlock email and until it's available again, * onRequestFinish will be called with the error code MegaError::API_ETEMPUNAVAIL. * * @param listener MegaRequestListener to track this request */ void resendVerificationEmail(MegaRequestListener *listener = NULL); /** * @brief Initialize the change of the email address associated to the account. * * The associated request type with this request is MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getEmail - Returns the email for the account * * If this request succeeds, a change-email link will be sent to the specified email address. * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param email The new email to be associated to the account. * @param listener MegaRequestListener to track this request */ void changeEmail(const char *email, MegaRequestListener *listener = NULL); /** * @brief Get information about a change-email link created by MegaApi::changeEmail. * * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link Change-email link (verify) * @param listener MegaRequestListener to track this request */ void queryChangeEmailLink(const char *link, MegaRequestListener *listener = NULL); /** * @brief Effectively changes the email address associated to the account. * * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). * * The associated request type with this request is MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link * - MegaRequest::getPassword - Returns the password * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * * If the account is logged-in into a different account than the account for which the link * was generated, onRequestFinish will be called with the error code MegaError::API_EACCESS. * * @param link Change-email link sent to the user's email address. * @param pwd Password for the account. * @param listener MegaRequestListener to track this request */ void confirmChangeEmail(const char *link, const char *pwd, MegaRequestListener *listener = NULL); /** * @brief Set proxy settings * * The SDK will start using the provided proxy settings as soon as this function returns. * * @param proxySettings Proxy settings. PROXY_AUTO is not supported and will be treated as * PROXY_NONE * @param listener MegaRequestListener to track this request * @see MegaProxy */ void setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener = NULL); /** * @brief Detect the system's proxy settings. * * This function attempts to automatically detect the system's proxy settings. * Proxy detection is currently supported on Windows, macOS, and Linux. * On unsupported platforms, it returns a `MegaProxy` object of type * `MegaProxy::PROXY_NONE`. * * - **Windows**: Retrieves the Internet Explorer proxy configuration for the current user. * - **macOS**: Retrieves the current internet proxy settings. * - **Linux**: Checks environment variables `http_proxy`, `HTTP_PROXY`, `https_proxy`, and * `HTTPS_PROXY` for proxy configuration. * * The caller takes ownership of the returned `MegaProxy` object. * * @return A `MegaProxy` object containing the detected proxy settings. */ MegaProxy *getAutoProxySettings(); /** * @brief Check if the MegaApi object is logged in * @return 0 if not logged in, Otherwise, a number > 0 */ int isLoggedIn(); /** * @brief Check if we are logged in into an Ephemeral account ++ * @return true if logged into an Ephemeral account ++, Otherwise return false */ bool isEphemeralPlusPlus(); /** * @brief Check the reason of being blocked. * * The associated request type with this request is MegaRequest::TYPE_WHY_AM_I_BLOCKED. * * This request can be sent internally at anytime (whenever an account gets blocked), so * a MegaGlobalListener should process the result, show the reason and logout. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the reason string (in English) * - MegaRequest::getNumber - Returns the reason code. Possible values: * - MegaApi::ACCOUNT_NOT_BLOCKED = 0 * Account is not blocked in any way. * * - MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT = 200 * Suspension only for multiple copyright violations. * * - MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT = 300 * Suspension message for any type of suspension, but copyright suspension. * * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED = 400 * Subuser of the business account has been disabled. * * - MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED = 401 * Subuser of business account has been removed. * * - MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL = 700 * The account is temporary blocked and needs to be verified by email (Weak Account Protection). * * If the error code in the MegaRequest object received in onRequestFinish * is MegaError::API_OK, the user is not blocked. */ void whyAmIBlocked(MegaRequestListener *listener = NULL); /** * @brief Create a contact link * * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_CREATE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getFlag - Returns the value of \c renew parameter * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Return the handle of the new contact link * * @param renew True to invalidate the previous contact link (if any). * @param listener MegaRequestListener to track this request */ void contactLinkCreate(bool renew = false, MegaRequestListener *listener = NULL); /** * @brief Get information about a contact link * * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_QUERY. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the userhandle of the contact * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getName - Returns the first name of the contact * - MegaRequest::getText - Returns the last name of the contact * - MegaRequest::getFile - Returns the avatar of the contact (JPG with Base64 encoding) * * @param handle Handle of the contact link to check * @param listener MegaRequestListener to track this request */ void contactLinkQuery(MegaHandle handle, MegaRequestListener *listener = NULL); /** * @brief Delete a contact link * * The associated request type with this request is MegaRequest::TYPE_CONTACT_LINK_DELETE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact link * * @param handle Handle of the contact link to delete * If the parameter is INVALID_HANDLE, the active contact link is deleted * * @param listener MegaRequestListener to track this request */ void contactLinkDelete(MegaHandle handle = INVALID_HANDLE, MegaRequestListener *listener = NULL); /** * @brief Command to keep mobile apps alive when needed * * When this feature is enabled, API servers will regularly send push notifications * to keep the application running. Before using this function, it's needed to register * a notification token using MegaApi::registerPushNotifications * * The associated request type with this request is MegaRequest::TYPE_KEEP_ME_ALIVE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getParamType - Returns the type send in the first parameter * - MegaRequest::getFlag - Returns true when the feature is being enabled, otherwise false * * @param type Type of keep alive desired * Valid values for this parameter: * - MegaApi::KEEP_ALIVE_CAMERA_UPLOADS = 0 * * @param enable True to enable this feature, false to disable it * @param listener MegaRequestListener to track this request * * @see MegaApi::registerPushNotifications */ void keepMeAlive(int type, bool enable, MegaRequestListener *listener = NULL); /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * * After the PSA has been accepted or dismissed by the user, app should * use MegaApi::setPSA to notify API servers about this event and * do not get the same PSA again in the next call to this function. * * The associated request type with this request is MegaRequest::TYPE_GET_PSA. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the id of the PSA (useful to call MegaApi::setPSA later) * - MegaRequest::getName - Returns the title of the PSA * - MegaRequest::getText - Returns the text of the PSA * - MegaRequest::getFile - Returns the URL of the image of the PSA * - MegaRequest::getPassword - Returns the text for the possitive button (or an empty string) * - MegaRequest::getLink - Returns the link for the possitive button (or an empty string) * * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request * @see MegaApi::setPSA */ void getPSA(MegaRequestListener *listener = NULL); /** * @brief Get the next PSA (Public Service Announcement) that should be shown to the user * * After the PSA has been accepted or dismissed by the user, app should * use MegaApi::setPSA to notify API servers about this event and * do not get the same PSA again in the next call to this function. * * The associated request type with this request is MegaRequest::TYPE_GET_PSA. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the id of the PSA (useful to call MegaApi::setPSA later) * Depending on the format of the PSA, the request may additionally return, for the new format: * - MegaRequest::getEmail - Returns the URL (or an empty string)) * - MegaRequest::getName - Returns the title of the PSA * - MegaRequest::getText - Returns the text of the PSA * - MegaRequest::getFile - Returns the URL of the image of the PSA * - MegaRequest::getPassword - Returns the text for the possitive button (or an empty string) * - MegaRequest::getLink - Returns the link for the possitive button (or an empty string) * * If there isn't any new PSA to show, onRequestFinish will be called with the error * code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request * @see MegaApi::setPSA */ void getPSAWithUrl(MegaRequestListener *listener = NULL); /** * @brief Notify API servers that a PSA (Public Service Announcement) has been already seen * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_LAST_PSA * - MegaRequest::getText - Returns the id passed in the first parameter (as a string) * * @param id Identifier of the PSA * @param listener MegaRequestListener to track this request * * @see MegaApi::getPSA */ void setPSA(int id, MegaRequestListener *listener = NULL); /** * @brief Command to acknowledge user alerts. * * Other clients will be notified that alerts to this point have been seen. * * The associated request type with this request is MegaRequest::TYPE_USERALERT_ACKNOWLEDGE. * * @param listener MegaRequestListener to track this request * * @see MegaApi::getUserAlerts */ void acknowledgeUserAlerts(MegaRequestListener *listener = NULL); /** * @brief Retuns the email of the currently open account * * If the MegaApi object isn't logged in or the email isn't available, * this function returns NULL * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Email of the account */ char* getMyEmail(); /** * @brief Get the timestamp when the account was created * @return Timestamp when the account was created */ int64_t getAccountCreationTs(); /** * @brief Returns the user handle of the currently open account * * If the MegaApi object isn't logged in, * this function returns NULL * * You take ownership of the returned value. Use delete[] to release the memory. * * @return User handle of the account */ char* getMyUserHandle(); /** * @brief Returns the user handle of the currently open account * * If the MegaApi object isn't logged in, * this function returns INVALID_HANDLE * * @return User handle of the account */ MegaHandle getMyUserHandleBinary(); /** * @brief Get the MegaUser of the currently open account * * If the MegaApi object isn't logged in, this function returns NULL. * * You take the ownership of the returned value * * @note The visibility of your own user is undefined and shouldn't be used. * @return MegaUser of the currently open account, otherwise NULL */ MegaUser* getMyUser(); /** * @brief Returns whether MEGA Achievements are enabled for the open account * @return True if enabled, false otherwise. */ bool isAchievementsEnabled(); /** * @brief Check if the account is a Pro Flexi account. * * @return returns true if it's a Pro Flexi account, otherwise false */ bool isProFlexiAccount(); /** * @brief Check if the account is a business account. * * For accounts under Pro Flexi plans, this method also returns true. * * @return returns true if it's a business account, otherwise false */ bool isBusinessAccount(); /** * @brief Check if the account is a master account. * * When a business account is a sub-user, not the master, some user actions will be blocked. * In result, the API will return the error code MegaError::API_EMASTERONLY. Some examples of * requests that may fail with this error are: * - MegaApi::cancelAccount * - MegaApi::changeEmail * - MegaApi::remove * - MegaApi::removeVersion * * @return returns true if it's a master account, false if it's a sub-user account */ bool isMasterBusinessAccount(); /** * @brief Check if the business account is active or not. * * When a business account is not active, some user actions will be blocked. In result, the API * will return the error code MegaError::API_EBUSINESSPASTDUE. Some examples of requests * that may fail with this error are: * - MegaApi::startDownload * - MegaApi::startUpload * - MegaApi::copyNode * - MegaApi::share * - MegaApi::cleanRubbishBin * * @return returns true if the account is active, otherwise false */ bool isBusinessAccountActive(); /** * @brief Get the status of a business account. * * @return Returns the business account status, possible values: * MegaApi::BUSINESS_STATUS_EXPIRED = -1 * MegaApi::BUSINESS_STATUS_INACTIVE = 0 * MegaApi::BUSINESS_STATUS_ACTIVE = 1 * MegaApi::BUSINESS_STATUS_GRACE_PERIOD = 2 */ int getBusinessStatus(); /** * @brief Returns the deadline to remedy the storage overquota situation * * This value is valid only when MegaApi::getUserData has been called after * receiving a callback MegaListener/MegaGlobalListener::onEvent of type * MegaEvent::EVENT_STORAGE, reporting STORAGE_STATE_PAYWALL. * The value will become invalid once the state of storage changes. * * @return Timestamp representing the deadline to remedy the overquota */ int64_t getOverquotaDeadlineTs(); /** * @brief Returns when the user was warned about overquota state * * This value is valid only when MegaApi::getUserData has been called after * receiving a callback MegaListener/MegaGlobalListener::onEvent of type * MegaEvent::EVENT_STORAGE, reporting STORAGE_STATE_PAYWALL. * The value will become invalid once the state of storage changes. * * You take the ownership of the returned value. * * @return MegaIntegerList with the timestamp corresponding to each warning */ MegaIntegerList *getOverquotaWarningsTs(); /** * @brief Check if the password is correct for the current account * @param password Password to check * @return True if the password is correct for the current account, otherwise false. */ bool checkPassword(const char *password); /** * @brief Returns the credentials of the currently open account * * If the MegaApi object isn't logged in or there's no signing key available, * this function returns NULL * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Fingerprint of the signing key of the current account */ char* getMyCredentials(); /** * Returns the credentials of a given user * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns MegaApi::USER_ATTR_ED25519_PUBLIC_KEY * - MegaRequest::getFlag - Returns true * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPassword - Returns the credentials in hexadecimal format * * @param user MegaUser of the contact (see MegaApi::getContact) to get the fingerprint * @param listener MegaRequestListener to track this request */ void getUserCredentials(MegaUser *user, MegaRequestListener *listener = NULL); /** * @brief Checks if credentials are verified for the given user * * @param user MegaUser of the contact whose credentiasl want to be checked * @return true if verified, false otherwise */ bool areCredentialsVerified(MegaUser *user); /** * @brief Verify credentials of a given user * * This function allow to tag credentials of a user as verified. It should be called when the * logged in user compares the fingerprint of the user (provided by an independent and secure * method) with the fingerprint shown by the app (@see MegaApi::getUserCredentials). * * The associated request type with this request is MegaRequest::TYPE_VERIFY_CREDENTIALS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns userhandle * * @param user MegaUser of the contact whose credentials want to be verified * @param listener MegaRequestListener to track this request */ void verifyCredentials(MegaUser *user, MegaRequestListener *listener = NULL); /** * @brief Reset credentials of a given user * * Call this function to undo the verification of credentials done by MegaApi::verifyCrendentials * * The associated request type with this request is MegaRequest::TYPE_VERIFY_CREDENTIALS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns userhandle * - MegaRequest::getFlag - Returns true * * @param user MegaUser of the contact whose credentials want to be reset * @param listener MegaRequestListener to track this request */ void resetCredentials(MegaUser *user, MegaRequestListener *listener = NULL); /** * @brief Set the active log level * * This function sets the log level of the logging system. Any log listener registered by * MegaApi::addLoggerObject will receive logs with the same or a lower level than * the one passed to this function. * * @param logLevel Active log level * * These are the valid values for this parameter: * - MegaApi::LOG_LEVEL_FATAL = 0 * - MegaApi::LOG_LEVEL_ERROR = 1 * - MegaApi::LOG_LEVEL_WARNING = 2 * - MegaApi::LOG_LEVEL_INFO = 3 * - MegaApi::LOG_LEVEL_DEBUG = 4 * - MegaApi::LOG_LEVEL_VERBOSE = 5 * - MegaApi::LOG_LEVEL_MAX = 5 */ static void setLogLevel(int logLevel); /** * @brief Turn on extra detailed logging for some modules * * Sometimes we need super detailed logging to investigate complicated issues * However for log size under normal conditions it's not practical to turn that on * This function allows that super detailed logging to be enabled just for * the module in question. * * @param networking Enable detailed extra logging for networking * @param syncs Enable detailed extra logging for syncs */ void setLogExtraForModules(bool networking, bool syncs); /** * @brief Set the maximum size limit for request payload logging * * This function controls the maximum size of request payloads that will be logged * in full. When a payload exceeds this limit, it will be truncated in the middle * with "[...]" inserted between the first and last portions to indicate truncation. * * @param maxSize Maximum payload size in bytes that will be logged without truncation. * Use 0 to use the max size limit. * */ static void setMaxPayloadLogSize(size_t maxSize); /** * @brief Enable log to console * * This function is only relevant if non-exclusive loggers are used. * For exclusive logging (ie, only one logger and no locking before it's called back) * the exclusive logger can easily output to console itself. * * By default, log to console is false. Logging to console is serialized via a mutex to * avoid interleaving by multiple threads, even in performance mode. * * @param enable True to show messages in console, false to skip them. */ static void setLogToConsole(bool enable); /** * @brief * Enable full JSON logging. * * This function allows an application to control whether JSON * requests and responses are logged in full. When enable is true, * JSON content will always be logged and will not be truncated. * When false, JSON may be logged in some situations but only if * the content is less than the logger's maximum payload size. * * @see MegaApi::setMaxPayloadLogSize * @see MegaApi::setLogJSON * * @deprecated This function is deprecated and will be removed in future releases. Use * setLogJSON and setMaxPlayloadLogSize instead. */ MEGA_DEPRECATED static void setLogJSONContent(bool enable); /** * @brief Configure JSON request and response logging options * * This function controls how JSON requests and responses are logged by the SDK. * Use bitwise OR to combine multiple logging options. * * Available logging flags: * - MegaApi::JSON_LOG_NONE = 0 * Disable all JSON logging if no other flags are set * * - MegaApi::JSON_LOG_CHUNK_RECEIVED = 1 * Enable logging of received JSON chunked data * @see MegaApi::setMaxPayloadLogSize for size limits * * - MegaApi::JSON_LOG_CHUNK_PROCESSING = 2 * Enable logging of JSON chunked data during processing * * - MegaApi::JSON_LOG_CHUNK_CONSUMED = 4 * Enable logging of consumed JSON chunked data (enabled by default) * * - MegaApi::JSON_LOG_SENDING = 8 * Enable logging of JSON data being sent to the server (enabled by default) * * - MegaApi::JSON_LOG_NONCHUNK_RECEIVED = 16 * Enable logging of received non-chunked JSON data (enabled by default) * * @param value Bitwise combination of logging flags * * Example usage: * @code * // Enable received and consumed logging * MegaApi::setLogJSON(MegaApi::JSON_LOG_CHUNK_RECEIVED | MegaApi::JSON_LOG_CHUNK_CONSUMED); * * // Disable all JSON logging * MegaApi::setLogJSON(MegaApi::JSON_LOG_NONE); * @endcode */ static void setLogJSON(uint32_t value); /** * @brief Get the current JSON logging configuration settings * * This function retrieves the currently active JSON logging flags that control * how JSON requests and responses are logged by the SDK. * * @return Current JSON logging configuration as a bitwise combination of flags * * @see MegaApi::setLogJSON for configuring these settings * @see MegaApi::setMaxPayloadLogSize for controlling log size limits */ static uint32_t getLogJSON(); /** * @brief Add a MegaLogger implementation to receive SDK logs * * Logs received by this objects depends on the active log level. * By default, it is MegaApi::LOG_LEVEL_INFO. You can change it * using MegaApi::setLogLevel. * * You can remove the existing logger by using MegaApi::removeLoggerObject. * * In performance mode, it is assumed that this is only called on startup and * not while actively logging. * * @param megaLogger MegaLogger implementation * @param singleExclusiveLogger If set, this is the only logger that will be called, and no mutexes will be locked before calling it. */ static void addLoggerObject(MegaLogger *megaLogger, bool singleExclusiveLogger = false); /** * @brief Remove a MegaLogger implementation to stop receiving SDK logs * * If the logger was registered in the past, it will stop receiving log * messages after the call to this function. * * In exclusive mode, it is assumed that this is only called on shutdown and * not while actively logging. There is no locking on the exclusive log callback pointer, * so there may already be threads deep in the logging functions. Clearing this * callback pointer won't stop those or wait for them to complete. So you can't * immediately delete the logger after calling this, unless you know for sure * that no threads are logging. Recommendation is to stop all other threads before calling this. * * @param megaLogger Previously registered MegaLogger implementation * @param singleExclusiveLogger If an exclusive logger was previously set, use this flag to remove it. */ static void removeLoggerObject(MegaLogger *megaLogger, bool singleExclusiveLogger = false); /** * @brief Send a log to the logging system * * This log will be received by the active logger object (MegaApi::setLoggerObject) if * the log level is the same or lower than the active log level (MegaApi::setLogLevel) * * The third and the fouth parameter are optional. You may want to use __FILE__ and __LINE__ * to complete them. * * In performance mode, only logging to console is serialized through a mutex. * Logging to `MegaLogger`s is not serialized and has to be done by the subclasses if needed. * * @param logLevel Log level for this message * @param message Message for the logging system * @param filename Origin of the log message * @param line Line of code where this message was generated */ static void log(int logLevel, const char* message, const char *filename = "", int line = -1); /** * @brief Differentiate MegaApi log output from different instances. * * If multiple MegaApi instances are used in a single application, it can be useful to * distinguish their activity in the log. Setting a name here for this instance will * cause some particularly relevant log lines to contain it. * A very short name is best to avoid increasing the log size too much. * * @param loggingName Name of this instance, to be output in log messages from this MegaApi * or NULL to clear a previous logging name. */ void setLoggingName(const char* loggingName); /** * @brief Create a folder in the MEGA account * * The associated request type with this request is MegaRequest::TYPE_CREATE_FOLDER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the handle of the parent folder * - MegaRequest::getName - Returns the name of the new folder * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new folder * - MegaRequest::getFlag - True if target folder (\c parent) was overriden * * If there already is a folder in target path with the same name, error code API_EEXIST is * returned and the existing folder MegaHandle included in MegaRequest::getNodeHandle. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param name Name of the new folder * @param parent Parent folder * @param listener MegaRequestListener to track this request */ void createFolder(const char* name, MegaNode *parent, MegaRequestListener *listener = NULL); /** * @brief Get Password Manager Base folder node from the MEGA account * * The associated request type with this request is * MegaRequest::TYPE_CREATE_PASSWORD_MANAGER_BASE Valid data in the MegaRequest object * received on callbacks: * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the folder * * A successfully completed fetchNodes request is required before calling getNodeByHandle * with the MegaHandle returned by this request. Otherwise, getNodeByHandle will return * NULL. * * @param listener MegaRequestListener to track this request */ void getPasswordManagerBase(MegaRequestListener *listener = NULL); /** * @brief Returns true if provided MegaHandle is of a Password Node Folder * * A folder is considered a Password Node Folder if Password Manager Base is its * ancestor, or if the node is the Password Manager Base folder itself. * * @param node MegaHandle of the node to check if it is a Password Node Folder * @return true if this node is a Password Node Folder, false otherwise. * In case node doesn't exists this method will also returns false. * * @deprecated Moved to isPasswordManagerNodeFolder. */ MEGA_DEPRECATED virtual bool isPasswordNodeFolder(MegaHandle node) const; /** * @brief Returns true if provided MegaHandle belongs to a Password Manager Node Folder * * A folder is considered a Password Manager Node Folder if Password Manager Base is its * ancestor, or if the node is the Password Manager Base folder itself. * * @param node MegaHandle of the node to check if it is a Password Manager Node Folder * @return true if this node is a Password Manager Node Folder, false otherwise. * In case node doesn't exists this method will also returns false. */ virtual bool isPasswordManagerNodeFolder(MegaHandle node) const; /** * @brief Create a new Credit Card Node in your Password Manager tree * * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Handle of the parent provided as an argument * - MegaRequest::getName - name for the new Password Node provided as an argument * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_CREDIT_CARD * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new Password Node * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `name` is nullptr or empty string * + If `data` is nullptr * + If `parent` does belong to a passwordNodeFolder * - MegaError::API_EEXIST: * + If there already is a Password Manager Node in the target path with the same name. In * that case, the existing Password Manager Node MegaHandle can be retrieved by * MegaRequest::getNodeHandle. * - MegaError::API_EAPPKEY: * + If the `data` is ill-formed. These are the format requirements for the data in the * CreditCardNodeData object: * - `cardNumber`: Mandatory (not nullptr nor empty string). Can only contain digits (no * spaces or other characters are allowed) * - `cvv`: Optional. If defined, must contain only digits (no spaces or other * characters are allowed) * - `expirationDate`: Optional. If defined must follow exactly the format: MM/YY, where * MM and YY are digits and MM must be between 01 and 12. Some examples: * + Valid inputs: 01/11, 12/25, 05/99. * + Invalid inputs: 13/25, 1/30, 03/5. * - `notes` and `cardHolderName`: Optionals and with no format restrictions. * * @param name Name for the new Credit Card Node * @param data Credit Card Node data for the Credit Card Node * @param parent Parent folder for the new Credit Card Node * @param listener MegaRequestListener to track this request */ virtual void createCreditCardNode(const char* name, const MegaNode::CreditCardNodeData* data, MegaHandle parent, MegaRequestListener* listener = NULL); /** * @brief Create a new Password Node in your Password Manager tree * * The associated request type with this request is MegaRequest::TYPE_CREATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Handle of the parent provided as an argument * - MegaRequest::getName - name for the new Password Node provided as an argument * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_PASSWORD * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new Password Node * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `name` is nullptr or empty string * + If `data` is nullptr * + If `parent` does belong to a passwordNodeFolder * - MegaError::API_EEXIST: * + If there already is a Password Manager Node in the target path with the same name. In * that case, the existing Password Manager Node MegaHandle can be retrieved by * MegaRequest::getNodeHandle. * - MegaError::API_EAPPKEY: * + If the `data` is ill-formed. These are the format requirements for the data in the * PasswordNodeData object: * - `password`: Mandatory (not nullptr nor empty string). * - `cvv`: Optional. If defined, must contain only digits (no spaces or other * characters are allowed) * - `totp`: Optional. If defined all their field must have valid values, i.e., the * `TotpData::Validation` object get from `TotpData::getValidation()` must return true * on `TotpData::Validation::isValidForCreate()`. See `TotpData::Validation` class * documentation for more information. * - `notes`, `url` and `userName`: Optionals and with no format restrictions. * * @param name Name for the new Password Node * @param data Password Node data for the Password Node * @param parent Parent folder for the new Password Node * @param listener MegaRequestListener to track this request */ void createPasswordNode(const char *name, const MegaNode::PasswordNodeData *data, MegaHandle parent, MegaRequestListener *listener = NULL); /** * @brief Update a Creadit Card Node in the MEGA account according to the parameters * * The associated request type with this request is MegaRequest::TYPE_UPDATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - handle provided of the Password Node to update * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_CREDIT_CARD * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `newData` is nullptr or empty * + If `node` does not exist or does not belong to a Credit Card Node * - MegaError::API_EAPPKEY: * + If the node ends up in an invalid state after applying the provided updates in * `newData`. See `MegaApi::createCreditCardNode` documentation for more details on the * expected format of each field if specified for the update. * * @param node Node to modify * @param newData New data for the Credit Card Node to update * @param listener MegaRequestListener to track this request */ void updateCreditCardNode(MegaHandle node, const MegaNode::CreditCardNodeData* newData, MegaRequestListener* listener = NULL); /** * @brief Update a Password Node in the MEGA account according to the parameters * * The associated request type with this request is MegaRequest::TYPE_UPDATE_PASSWORD_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - handle provided of the Password Node to update * - MegaRequest::getParamType - MegaApi::PWM_NODE_TYPE_PASSWORD * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EBUSINESSPASTDUE: * + If the MEGA account is a business account and it's status is expired * - MegaError::API_EARGS: * + If `newData` is nullptr or empty * + If `node` does not exist or does not belong to a password node * - MegaError::API_EAPPKEY: * + If the node ends up in an invalid state after applying the provided updates in * `newData`. See `MegaApi::createPasswordNode` documentation for more details on the * expected format of each field if specified for the update. * * @param node Node to modify * @param newData New data for the Password Node to update * @param listener MegaRequestListener to track this request */ void updatePasswordNode(MegaHandle node, const MegaNode::PasswordNodeData* newData, MegaRequestListener *listener = NULL); /** * @brief Import passwords from a file into your Password Manager tree * * The associated request type with this request is * MegaRequest::TYPE_IMPORT_PASSWORDS_FROM_FILE. Valid data in the MegaRequest object * received on callbacks: * - MegaRequest::getFile - Path of the file provided as an argument. * - MegaRequest::getParamType - Source of the file provided as an argument (see * fileSource documentation). * - MegaRequest::getParentHandle - Handle of the parent provided as an argument. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaHandleList - A list with all the handles for all the new imported * Password Nodes. * - MegaRequest::getMegaStringIntegerMap - A map with problematic content as key and error * code as value * Possible error codes are: * IMPORTED_PASSWORD_ERROR_PARSER = 1 * IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD = 2 * IMPORTED_PASSWORD_ERROR_MISSINGNAME = 3 * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS: * + Invalid parent (parent doesn't exist or isn't password node) * + Invalid fileSource * + NULL at filePath * + File with wrong format * - MegaError::API_EREAD: * + File can't be opened * - MegaError::API_EACESS * + File is empty * * @param filePath Path to the file containing the passwords to import. * @param fileSource Type for the source from where the file was exported. * Valid values are: * - IMPORT_PASSWORD_SOURCE_GOOGLE = 0 * @param parent Parent handle for node that will contain new nodes as children. * @param listener MegaRequestListener to track this request. */ void importPasswordsFromFile(const char* filePath, const int fileSource, MegaHandle parent, MegaRequestListener* listener = NULL); /** * @brief Create a new empty folder in your local file system * * @param localPath Path of the new folder * @return True if the local folder was successfully created, otherwise false. */ bool createLocalFolder(const char* localPath); /** * @brief Move a node in the MEGA account * * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node * @param listener MegaRequestListener to track this request */ void moveNode(MegaNode* node, MegaNode* newParent, MegaRequestListener *listener = NULL); /** * @brief Move a node in the MEGA account changing the file name * * The associated request type with this request is MegaRequest::TYPE_MOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to move * - MegaRequest::getParentHandle - Returns the handle of the new parent for the node * - MegaRequest::getName - Returns the name for the new node * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - True if target folder (\c newParent) was overriden * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to move * @param newParent New parent for the node * @param newName Name for the new node * @param listener MegaRequestListener to track this request */ void moveNode(MegaNode* node, MegaNode* newParent, const char* newName, MegaRequestListener *listener = NULL); /** * @brief Copy a node in the MEGA account * * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy (if it is a public node) * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node * - MegaRequest::getFlag - True if target folder (\c newParent) was overriden * * @note In case the target folder was overriden, the MegaRequest::getParentHandle still keeps * the handle of the original target folder. You can check the final parent by checking the * value returned by MegaNode::getParentHandle * * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @param listener MegaRequestListener to track this request */ void copyNode(MegaNode* node, MegaNode *newParent, MegaRequestListener *listener = NULL); /** * @brief Copy a node in the MEGA account changing the file name * * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to copy * - MegaRequest::getParentHandle - Returns the handle of the new parent for the new node * - MegaRequest::getPublicMegaNode - Returns the node to copy * - MegaRequest::getName - Returns the name for the new node * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node * * If the status of the business account is expired, onRequestFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to copy * @param newParent Parent for the new node * @param newName Name for the new node * @param listener MegaRequestListener to track this request */ void copyNode(MegaNode* node, MegaNode *newParent, const char* newName, MegaRequestListener *listener = NULL); /** * @brief Rename a node in the MEGA account * * The associated request type with this request is MegaRequest::TYPE_RENAME * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to rename * - MegaRequest::getName - Returns the new name for the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to modify * @param newName New name for the node * @param listener MegaRequestListener to track this request */ void renameNode(MegaNode* node, const char* newName, MegaRequestListener *listener = NULL); /** * @brief Remove a node from the MEGA account * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use MegaApi::moveNode * * If the node has previous versions, they will be deleted too * * The associated request type with this request is MegaRequest::TYPE_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to remove * - MegaRequest::getFlag - Returns false because previous versions won't be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param node Node to remove * @param listener MegaRequestListener to track this request */ void remove(MegaNode* node, MegaRequestListener *listener = NULL); /** * @brief Remove all versions from the MEGA account * * The associated request type with this request is MegaRequest::TYPE_REMOVE_VERSIONS * * When the request finishes, file versions might not be deleted yet. * Deletions are notified using onNodesUpdate callbacks. * * @param listener MegaRequestListener to track this request */ void removeVersions(MegaRequestListener *listener = NULL); /** * @brief Remove a version of a file from the MEGA account * * This function doesn't move the node to the Rubbish Bin, it fully removes the node. To move * the node to the Rubbish Bin use MegaApi::moveNode. * * If the node has previous versions, they won't be deleted. * * The associated request type with this request is MegaRequest::TYPE_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to remove * - MegaRequest::getFlag - Returns true because previous versions will be preserved * * If the MEGA account is a sub-user business account, onRequestFinish will * be called with the error code MegaError::API_EMASTERONLY. * * @param node Node to remove * @param listener MegaRequestListener to track this request */ void removeVersion(MegaNode* node, MegaRequestListener *listener = NULL); /** * @brief Restore a previous version of a file * * Only versions of a file can be restored, not the current version (because it's already current). * The node will be copied and set as current. All the version history will be preserved without changes, * being the old current node the previous version of the new current node, and keeping the restored * node also in its previous place in the version history. * * The associated request type with this request is MegaRequest::TYPE_RESTORE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to restore * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param version Node with the version to restore * @param listener MegaRequestListener to track this request */ void restoreVersion(MegaNode *version, MegaRequestListener *listener = NULL); /** * @brief Clean the Rubbish Bin in the MEGA account * * This function effectively removes every node contained in the Rubbish Bin. In order to * avoid accidental deletions, you might want to warn the user about the action. * * The associated request type with this request is MegaRequest::TYPE_CLEAN_RUBBISH_BIN. This * request returns MegaError::API_ENOENT if the Rubbish bin is already empty. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param listener MegaRequestListener to track this request */ void cleanRubbishBin(MegaRequestListener *listener = NULL); /** * @brief Send a node to the Inbox of another MEGA user using a MegaUser * * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to send * - MegaRequest::getEmail - Returns the email of the user that receives the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @obsolete The Inbox rootnode has been recycled for Vault and will no longer * accept to put nodes in user's Inbox. This method could be removed in the future. * * @param node Node to send * @param user User that receives the node * @param listener MegaRequestListener to track this request */ void sendFileToUser(MegaNode *node, MegaUser *user, MegaRequestListener *listener = NULL); /** * @brief Send a node to the Inbox of another MEGA user using his email * * The associated request type with this request is MegaRequest::TYPE_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to send * - MegaRequest::getEmail - Returns the email of the user that receives the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @obsolete The Inbox rootnode has been recycled for Vault and will no longer * accept to put nodes in user's Inbox. This method could be removed in the future. * * @param node Node to send * @param email Email of the user that receives the node * @param listener MegaRequestListener to track this request */ void sendFileToUser(MegaNode *node, const char* email, MegaRequestListener *listener = NULL); /** * @brief Upgrade cryptographic security * * This should be called only after MegaEvent::EVENT_UPGRADE_SECURITY event is received to effectively * proceed with the cryptographic upgrade process. * This should happen only once per account. * * The associated request type with this request is MegaRequest::TYPE_UPGRADE_SECURITY * * @param listener MegaRequestListener to track this request */ void upgradeSecurity(MegaRequestListener* listener = NULL); /** * @brief Get the contact verification warning flag status * * It returns if showing the warnings to verify contacts is enabled. * * @return True if showing the warnings are enabled, false otherwise. */ bool contactVerificationWarningEnabled(); /** * @brief Allows to change the hardcoded value of the "Manual Verification" flag * * With this feature flag set, the client will require manual verification of * contact credentials of users (both sharers AND sharees) in order to decrypt * shared folders correctly if the "secure" flag is set to true. * * The default value is "false". * * @note If the "secure" flag is disabled, "Manual Verification" flag has no * effect. * * @param enable New value of the flag */ void setManualVerificationFlag(bool enable); /** * @brief Creates a new share key for the node if there is no share key already created. * * Apps should call it before starting any new share (MegaApi::share). Otherwise, the * share request may fail. * * Note that it's safe to call this method for the same node multiple times. * * The associated request type with this request is MegaRequest::TYPE_OPEN_SHARE_DIALOG * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node to share * * @param node The folder to share. It must be a non-root folder * @param listener MegaRequestListener to track this request */ void openShareDialog(MegaNode *node, MegaRequestListener *listener = NULL); /** * @brief Share or stop sharing a folder in MEGA with another user using a MegaUser * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN * * Before calling this method, the app should call MegaApi::openShareDialog in order to * ensure that a share-key exists. If it doesn't exist, it will be created by the call to * MegaApi::openShareDialog. If the app doesn't call it in advance, this method will return * API_EKEY (unless there are other shares already for this node) * * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param user User that receives the shared folder * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user * * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 * * @param listener MegaRequestListener to track this request */ void share(MegaNode *node, MegaUser* user, int level, MegaRequestListener *listener = NULL); /** * @brief Share or stop sharing a folder in MEGA with another user using his email * * To share a folder with an user, set the desired access level in the level parameter. If you * want to stop sharing a folder use the access level MegaShare::ACCESS_UNKNOWN * * Before calling this method, the app should call MegaApi::openShareDialog in order to * ensure that a share-key exists. If it doesn't exist, it will be created by the call to * MegaApi::openShareDialog. If the app doesn't call it in advance, this method will return * API_EKEY (unless there are other shares already for this node) * * The associated request type with this request is MegaRequest::TYPE_SHARE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder to share * - MegaRequest::getEmail - Returns the email of the user that receives the shared folder * - MegaRequest::getAccess - Returns the access that is granted to the user * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node The folder to share. It must be a non-root folder * @param email Email of the user that receives the shared folder. If it doesn't have a MEGA account, the folder will be shared anyway * and the user will be invited to register an account. * * @param level Permissions that are granted to the user * Valid values for this parameter: * - MegaShare::ACCESS_UNKNOWN = -1 * Stop sharing a folder with this user * * - MegaShare::ACCESS_READ = 0 * - MegaShare::ACCESS_READWRITE = 1 * - MegaShare::ACCESS_FULL = 2 * - MegaShare::ACCESS_OWNER = 3 * * @param listener MegaRequestListener to track this request */ void share(MegaNode *node, const char* email, int level, MegaRequestListener *listener = NULL); /** * @brief Import a public link to the account * * The associated request type with this request is MegaRequest::TYPE_IMPORT_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file * - MegaRequest::getParentHandle - Returns the folder that receives the imported file * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Handle of the new node in the account * - MegaRequest::getFlag - True if target folder (\c parent) was overriden * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA * @param parent Parent folder for the imported file * @param listener MegaRequestListener to track this request */ void importFileLink(const char* megaFileLink, MegaNode* parent, MegaRequestListener *listener = NULL); /** * @brief Decrypt password-protected public link * * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the encrypted public link to the file/folder * - MegaRequest::getPassword - Returns the password to decrypt the link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Decrypted public link * * @param link Password/protected public link to a file/folder in MEGA * @param password Password to decrypt the link * @param listener MegaRequestListener to track this request */ void decryptPasswordProtectedLink(const char* link, const char* password, MegaRequestListener *listener = NULL); /** * @brief Encrypt public link with password * * The associated request type with this request is MegaRequest::TYPE_PASSWORD_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to be encrypted * - MegaRequest::getPassword - Returns the password to encrypt the link * - MegaRequest::getFlag - Returns true * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Encrypted public link * * @param link Public link to be encrypted, including encryption key for the link * @param password Password to encrypt the link * @param listener MegaRequestListener to track this request */ void encryptLinkWithPassword(const char* link, const char* password, MegaRequestListener *listener = NULL); /** * @brief Get a MegaNode from a public link to a file * * A public node can be imported using MegaApi::copyNode or downloaded using MegaApi::startDownload * * The associated request type with this request is MegaRequest::TYPE_GET_PUBLIC_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the public link to the file * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPublicMegaNode - Public MegaNode corresponding to the public link * - MegaRequest::getFlag - Return true if the provided key along the link is invalid. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param megaFileLink Public link to a file in MEGA * @param listener MegaRequestListener to track this request */ void getPublicNode(const char* megaFileLink, MegaRequestListener *listener = NULL); /** * @deprecated Use the new signature with forceSSL param */ MEGA_DEPRECATED void getDownloadUrl(MegaNode* node, bool singleUrl, MegaRequestListener* listener = nullptr); /** * @brief Get downloads urls for a node * * The associated request type with this request is MegaRequest::TYPE_GET_DOWNLOAD_URLS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns semicolon-separated download URL(s) to the file * - MegaRequest::getLink - Returns semicolon-separated IPv4 of the server in the URL(s) * - MegaRequest::getText - Returns semicolon-separated IPv6 of the server in the URL(s) * - MegaRequest::getMegaStringMap - Returns a {node handle, file handle} pair for the given * file node * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node to get the downloads URLs * @param singleUrl Always return one URL (even for raided files) * @param forceSSL Enforce getting a https URL * @param listener MegaRequestListener to track this request */ void getDownloadUrl(MegaNode* node, bool singleUrl, bool forceSSL, MegaRequestListener* listener = nullptr); /** * @brief Build the URL for a public link * * @note This function does not create the public link itself. It simply builds the URL * from the provided data. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param publicHandle Public handle of the link, in B64url encoding. * @param key Encryption key of the link. * @param isFolder True for folder links, false for file links. * @return The public link for the provided data */ const char *buildPublicLink(const char *publicHandle, const char *key, bool isFolder); /** * @brief Get the thumbnail of a node * * If the node doesn't have a thumbnail the request fails with the MegaError::API_ENOENT * error code * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the thumbnail * @param dstFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param listener MegaRequestListener to track this request */ void getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener = NULL); /** * @brief Get the thumbnail of a node by its handle * * If the node doesn't have a thumbnail, the request fails with the MegaError::API_ENOENT * error code. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param handle Handle of the node to get the thumbnail. * @param dstFilePath Destination path for the thumbnail. * If this path is a local folder, it must end with a '\' or '/' character and * (Base64-encoded handle + "0.jpg") will be used as the file name inside that folder. If * the path doesn't finish with one of these characters, the file will be downloaded to a * file in that path. * * @param listener MegaRequestListener to track this request */ void getThumbnail(MegaHandle handle, const char* dstFilePath, MegaRequestListener* listener = nullptr); /** * @brief Get the preview of a node * * If the node doesn't have a preview the request fails with the MegaError::API_ENOENT * error code * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the file attribute string if \c node is an attached node from chats. NULL otherwise * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * - MegaRequest::getBase64Key - Returns the nodekey in Base64 (only when node has file attributes) * - MegaRequest::getPrivateKey - Returns the file-attribute string (only when node has file attributes) * * @param node Node to get the preview * @param dstFilePath Destination path for the preview. * If this path is a local folder, it must end with a '\' or '/' character and (Base64-encoded handle + "1.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param listener MegaRequestListener to track this request */ void getPreview(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener = NULL); /** * @brief Get the avatar of a MegaUser * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param user MegaUser to get the avatar. If this parameter is set to NULL, the avatar is obtained * for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param listener MegaRequestListener to track this request * * @see MegaApi::setAvatar */ void getUserAvatar(MegaUser* user, const char *dstFilePath, MegaRequestListener *listener = NULL); /** * @brief Get the avatar of any user in MEGA * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) * * @param email_or_handle Email or user handle (Base64 encoded) to get the avatar. If this parameter is * set to NULL, the avatar is obtained for the active account * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param listener MegaRequestListener to track this request * * @see MegaApi::setAvatar */ void getUserAvatar(const char *email_or_handle, const char *dstFilePath, MegaRequestListener *listener = NULL); /** * @brief Get the avatar of the active account * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the destination path * - MegaRequest::getEmail - Returns the email of the user * * @param dstFilePath Destination path for the avatar. It has to be a path to a file, not to a folder. * If this path is a local folder, it must end with a '\' or '/' character and (email + "0.jpg") * will be used as the file name inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * * @param listener MegaRequestListener to track this request * * @see MegaApi::setAvatar */ void getUserAvatar(const char *dstFilePath, MegaRequestListener *listener = NULL); /** * @brief Get the default color for the avatar. * * This color should be used only when the user doesn't have an avatar. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param user MegaUser to get the color of the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ static char *getUserAvatarColor(MegaUser *user); /** * @brief Get the default color for the avatar. * * This color should be used only when the user doesn't have an avatar. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param userhandle User handle (Base64 encoded) to get the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ static char *getUserAvatarColor(const char *userhandle); /** * @brief Get the secondary color for the avatar. * * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from getUserAvatarColor. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param user MegaUser to get the color of the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ static char *getUserAvatarSecondaryColor(MegaUser *user); /** * @brief Get the secondary color for the avatar. * * This color should be used only when the user doesn't have an avatar, making a * gradient in combination with the color returned from getUserAvatarColor. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param userhandle User handle (Base64 encoded) to get the avatar. * @return The RGB color as a string with 3 components in hex: #RGB. Ie. "#FF6A19" */ static char *getUserAvatarSecondaryColor(const char *userhandle); /** * @brief Get an attribute of a MegaUser. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * - MegaRequest::getFlag - Returns true for external drive, in case attribute type was * USER_ATTR_DEVICE_NAMES * * @param user MegaUser to get the attribute. If this parameter is set to NULL, the * attribute is obtained for the active account * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Get the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Get the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Get the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Get the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Get whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION = 17 * Get whether user needs contact link verification (private) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Get number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_LAST_PSA = 20 * Get the ID of last PSA seen by the user (private) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Get the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Get the user geolocation (private) * MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER = 23 * Get the target folder for Camera Uploads (private) * MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER = 24 * Get the target folder for My chat files (private) * MegaApi::USER_ATTR_PUSH_SETTINGS = 25 * Get whether user has push settings enabled (private) * MegaApi::USER_ATTR_ALIAS = 27 * Get the list of the users's aliases (private) * MegaApi::USER_ATTR_DEVICE_NAMES = 30 * Get the list of device or external drive names (private) * MegaApi::USER_ATTR_MY_BACKUPS_FOLDER = 31 * Get the target folder for My Backups (private) * MegaApi::USER_ATTR_COOKIE_SETTINGS = 33 * Get whether user has Cookie Settings enabled * MegaApi::USER_ATTR_JSON_SYNC_CONFIG_DATA = 34 * Get name and key to cypher sync-configs file * MegaApi::USER_ATTR_NO_CALLKIT = 36 * Get whether user has iOS CallKit disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_APPS_PREFS = 38 * Get app preferences (private) * MegaApi::USER_ATTR_CC_PREFS = 39 * Get preferences for consumed content (private) * MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG = 40 * Get visibility for welcome dialog (private) * MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE = 41 * Get visibility for Terms of Service (private) * MegaApi::USER_ATTR_PWM_BASE = 42 * Get Password Manager Base (private) * MegaApi::USER_ATTR_LAST_READ_NOTIFICATION = 44 * Get last read notification (private) * MegaApi::USER_ATTR_LAST_ACTIONED_BANNER = 45 * Get last actioned banner (private) * MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP = 52 (private, encrypted) * Get the epoch time (in seconds) used as the recent actions history clear timestamp. * @param listener MegaRequestListener to track this request */ void getUserAttribute(MegaUser* user, int type, MegaRequestListener *listener = NULL); /** * @brief Get public attributes of participants of public chats during preview mode. * * Other's public attributes are retrievable by contacts and users who participates in your * chats. During a preview of a public chat, the user does not fullfil the above * requirements, so the public handle of the chat being previewed is required as * authorization. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one * as parameter) * - MegaRequest::getSessionKey - Returns the public handle of the chat * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * * @param email_or_handle Email or user handle (Base64 encoded) to get the attribute. * This parameter cannot be NULL. * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_AVATAR = 0 * Get the avatar of the user (public) * MegaApi::USER_ATTR_FIRSTNAME = 1 * Get the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Get the lastname of the user (public) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Get the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Get the public key Cu25519 of the user (public) * * @param ph Public handle of the chat link the user participates. * @param listener MegaRequestListener to track this request */ void getChatUserAttribute(const char *email_or_handle, int type, const char *ph, MegaRequestListener *listener = NULL); /** * @brief Get an attribute of any user in MEGA. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getEmail - Returns the email or the handle of the user (the provided one as parameter) * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param email_or_handle Email or user handle (Base64 encoded) to get the attribute. * If this parameter is set to NULL, the attribute is obtained for the active account. * @param type Attribute type * Valid values are the same as the ones in the previous overload. * * @param listener MegaRequestListener to track this request */ void getUserAttribute(const char *email_or_handle, int type, MegaRequestListener *listener = NULL); /** * @brief Get an attribute of the current account. * * User attributes can be private or public. Private attributes are accessible only by * your own user, while public ones are retrievable by any of your contacts. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the value for public attributes * - MegaRequest::getMegaStringMap - Returns the value for private attributes * * @param type Attribute type * Valid values are the same as the ones in the previous overload. * * @param listener MegaRequestListener to track this request */ void getUserAttribute(int type, MegaRequestListener *listener = NULL); /** * @brief Get the name associated to a user attribute * * You take ownership of the returned value. Use delete[] to release the memory. * * @param attr Attribute * @return name associated to the user attribute */ const char *userAttributeToString(int attr); /** * @brief Get the long descriptive name associated to a user attribute * * You take ownership of the returned value. Use delete[] to release the memory. * * @param attr Attribute * @return descriptive name associated to the user attribute */ const char *userAttributeToLongName(int attr); /** * @brief Get numeric value for user attribute given a string * @param name Name of the attribute * @return numeric value for user attribute */ int userAttributeFromString(const char *name); /** * @brief Get the email address of any user in MEGA. * * The associated request type with this request is MegaRequest::TYPE_GET_USER_EMAIL * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the user (the provided one as parameter) * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getEmail - Returns the email address * * @param handle Handle of the user to get the attribute. * @param listener MegaRequestListener to track this request */ void getUserEmail(MegaHandle handle, MegaRequestListener *listener = NULL); /** * @brief Cancel the retrieval of a thumbnail * * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node Node to cancel the retrieval of the thumbnail * @param listener MegaRequestListener to track this request * * @see MegaApi::getThumbnail */ void cancelGetThumbnail(MegaNode* node, MegaRequestListener *listener = NULL); /** * @brief Cancel the retrieval of a preview * * The associated request type with this request is MegaRequest::TYPE_CANCEL_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node Node to cancel the retrieval of the preview * @param listener MegaRequestListener to track this request * * @see MegaApi::getPreview */ void cancelGetPreview(MegaNode* node, MegaRequestListener *listener = NULL); /** * @brief Set the thumbnail of a MegaNode * * It is good practice to set the Preview file attribute (see MegaApi::setPreview) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Thumbnail you may call MegaApi::createThumbnail * and provide it's output image to this method. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node MegaNode to set the thumbnail * @param srcFilePath Source path of the file that will be set as thumbnail. This * image must be square (ratio) and it should contain the image's primary content for a * better UX * @param listener MegaRequestListener to track this request */ void setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener = NULL); /** * @brief Uploads a thumbnail as part of a background media file upload * * It is good practice to set the Preview file attribute (see MegaApi::setPreview) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Thumbnail you may call MegaApi::createThumbnail * and provide it's output image to this method. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getMegaBackgroundMediaUploadPtr - Returns the background upload object * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaRequest::getNodeHandle - The handle of the uploaded file attribute. * * Use the result in the MegaRequest::getNodeHandle as the thumbnail handle in the * call to MegaApi::backgroundMediaUploadComplete. * * @param bu the MegaBackgroundMediaUpload that the fingernail will be assoicated with * @param srcFilePath Source path of the file that will be set as thumbnail. This * image must be square (ratio) and it should contain the image's primary content for a * better UX * @param listener MegaRequestListener to track this request */ void putThumbnail(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener = NULL); /** * @brief Set the thumbnail of a MegaNode, via the result of MegaApi::putThumbnail * * It is good practice to set the Preview file attribute (see MegaApi::setPreview) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Thumbnail you may call MegaApi::createThumbnail * and provide it's output image to this method. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getNumber - Returns the attribute handle * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * @param node MegaNode to set the thumbnail * @param fileattribute The result handle from a previous call to MegaApi::putThumbnail * @param listener MegaRequestListener to track this request */ void setThumbnailByHandle(MegaNode* node, MegaHandle fileattribute, MegaRequestListener *listener = NULL); /** * @brief Set the preview of a MegaNode * * It is good practice to set the Thumbnail file attribute (see MegaApi::setThumbnail) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Preview you may call MegaApi::createPreview * and provide it's output image to this method. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node MegaNode to set the preview * @param srcFilePath Source path of the file that will be set as preview. This * image must be of the same ratio and contain completely the original image for a * better UX * @param listener MegaRequestListener to track this request */ void setPreview(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener = NULL); /** * @brief Uploads a preview as part of a background media file upload * * It is good practice to set the Thumbnail file attribute (see MegaApi::setThumbnail) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Preview you may call MegaApi::createPreview * and provide it's output image to this method. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getMegaBackgroundMediaUploadPtr - Returns the background upload object * - MegaRequest::getFile - Returns the source path * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_THUMBNAIL * * This value is valid for these requests in onRequestFinish when the * error code is MegaError::API_OK: * - MegaRequest::getNodeHandle - The handle of the uploaded file attribute. * * Use the result in the MegaRequest::getNodeHandle as the preview handle in the * call to MegaApi::backgroundMediaUploadComplete. * * @param bu the MegaBackgroundMediaUpload that the fingernail will be assoicated with * @param srcFilePath Source path of the file that will be set as preview. This * image must be of the same ratio and contain completely the original image for a * better UX * @param listener MegaRequestListener to track this request */ void putPreview(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener = NULL); /** * @brief Set the preview of a MegaNode, via the result of MegaApi::putPreview * * It is good practice to set the Thumbnail file attribute (see MegaApi::setThumbnail) * and this attribute with the same original image to keep consistency. In order to * ensure the correct ratio for the Preview you may call MegaApi::createPreview * and provide it's output image to this method. * * @note For consistence same source image must be used for both attributes (check * MegaApi::createPreview and MegaApi::createThumbnail). * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_FILE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getNumber - Returns the attribute handle * - MegaRequest::getParamType - Returns MegaApi::ATTR_TYPE_PREVIEW * * @param node MegaNode to set the preview of * @param fileattribute The result handle from a previous call to MegaApi::putPreview * @param listener MegaRequestListener to track this request */ void setPreviewByHandle(MegaNode* node, MegaHandle fileattribute, MegaRequestListener *listener = NULL); /** * @brief Set/Remove the avatar of the MEGA account * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the source path (optional) * * @param srcFilePath Source path of the file that will be set as avatar. * If NULL, the existing avatar will be removed (if any). * In case the avatar never existed before, removing the avatar returns * MegaError::API_ENOENT * @param listener MegaRequestListener to track this request * * @see MegaApi::getUserAvatar */ void setAvatar(const char *srcFilePath, MegaRequestListener *listener = NULL); enum { PRIVATE_KEY_ED25519 = 1, PRIVATE_KEY_CU25519, }; /** * @brief Returns private key from desired type in base64-encoded * * This method returns invalid value until fetch nodes has finished * * You take ownership of the returned value. Use delete[] to release the memory. * * @param type private key type * It can take this values: * - PRIVATE_KEY_ED25519 1 * - PRIVATE_KEY_CU25519 2 * @return Private key */ char* getPrivateKey(int type); /** * @brief Confirm available memory to avoid OOM situations * * Before queueing a thumbnail or preview upload (or other memory intensive task), * it may be useful on some devices to check if there is plenty of memory available * in the memory pool used by MegaApi (especially since some platforms may not have * the facility to check for themselves, and/or deallocation may need to wait on a GC) * and if not, delay until any current resource constraints (eg. other current operations, * or other RAM-hungry apps in the device), have finished. This function just * makes several memory allocations and then immediately releases them. If all allocations * succeeded, it returns true, indicating that memory is (probably) available. * Of course, another app or operation may grab that memory immediately so it's not a * guarantee. However it may help to reduce the frequency of OOM situations on phones for example. * * @param allocCount The number of allocations to make * @param allocSize The size of those memory allocations. * @return True if all the allocations succeeded */ bool testAllocation(unsigned allocCount, size_t allocSize); /** * @brief Set a public attribute of the current user * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getText - Returns the new value for the attribute * * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_FIRSTNAME = 1 * Set the firstname of the user (protected) * MegaApi::USER_ATTR_LASTNAME = 2 * Set the lastname of the user (protected) * MegaApi::USER_ATTR_LANGUAGE = 14 * Set the language for the user (private) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Set file versioning disabled for the user (private) * MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION = 17 * Set contact link verification for the user (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Set number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_LAST_PSA = 20 * Set last PSA for the user (private) * MegaApi::USER_PUSH_SETTINGS = 25 * Enable/disable push notifications for the user (private) * MegaApi::USER_ATTR_NO_CALLKIT = 36 * Set whether user has iOS CallKit disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG = 40 * Show/hide welcome dialog for the user (private) * MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE = 41 * Show/hide Terms of Service for the user (private) * MegaApi::USER_ATTR_LAST_READ_NOTIFICATION = 44 * Set last read notification for the user (private) * MegaApi::USER_ATTR_LAST_ACTIONED_BANNER = 45 * Set last actioned banner for the user (private) * * If the MEGA account is a sub-user business account, and the value of the parameter * type is equal to MegaApi::USER_ATTR_FIRSTNAME or MegaApi::USER_ATTR_LASTNAME * onRequestFinish will be called with the error code MegaError::API_EMASTERONLY. * * @param value New attribute value * @param listener MegaRequestListener to track this request */ void setUserAttribute(int type, const char* value, MegaRequestListener *listener = NULL); /** * @brief Set a private attribute of the current user * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * - MegaRequest::getMegaStringMap - Returns the new value for the attribute * * You can remove existing records/keypairs from the following attributes: * - MegaApi::USER_ATTR_ALIAS * - MegaApi::USER_ATTR_DEVICE_NAMES * - MegaApi::USER_ATTR_APPS_PREFS * - MegaApi::USER_ATTR_CC_PREFS * by adding a keypair into MegaStringMap with the key to remove and an empty C-string null * terminated as value. * * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_AUTHRING = 3 * Get the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Get the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_KEYRING = 7 * Get the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Get whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Set whether the user can send geolocation messages (private) * MegaApi::USER_ATTR_ALIAS = 27 * Set the list of users's aliases (private) * MegaApi::USER_ATTR_DEVICE_NAMES = 30 * Set the list of device names (private) * MegaApi::USER_ATTR_APPS_PREFS = 38 * Set the apps prefs (private) * MegaApi::USER_ATTR_CC_PREFS = 39 * Set the content consumption prefs (private) * * @param value New attribute value * @param listener MegaRequestListener to track this request */ void setUserAttribute(int type, const MegaStringMap *value, MegaRequestListener *listener = NULL); /** * @brief Set a custom attribute for the node * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getName - Returns the name of the custom attribute * - MegaRequest::getText - Returns the text for the attribute * - MegaRequest::getFlag - Returns false (not official attribute) * * The attribute name must be an UTF8 string with between 1 and 7 bytes * If the attribute already has a value, it will be replaced * If value is NULL, the attribute will be removed from the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the attribute * @param attrName Name of the custom attribute. * The length of this parameter must be between 1 and 7 UTF8 bytes * @param value Value for the attribute * @param listener MegaRequestListener to track this request */ void setCustomNodeAttribute(MegaNode *node, const char *attrName, const char* value, MegaRequestListener *listener = NULL); /** * @brief Set s4 attribute for the node * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getText - Returns the text for the attribute * - MegaRequest::getFlag - Returns true (official attribute) * * The attribute name must be an UTF8 string with between 1 and 7 bytes * If the attribute already has a value, it will be replaced * If value is NULL, the attribute will be removed from the node * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the attribute * @param value Value for the attribute * @param listener MegaRequestListener to track this request */ void setNodeS4(MegaNode *node, const char *value, MegaRequestListener *listener); /** * @brief Returns true if S4 object storage is enabled * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return True if enabled, false if disabled */ bool isS4Enabled(); /** * @brief Returns the node's handle of the S4 container * * S4 requires a folder in the root of the Cloud Drive to operate. * This method returns the handle of the related node, or INVALID_HANDLE if the * S4 service is disabled. * * This method doesn't need to block the SDK mutex: do not cache the value in the app. * * @return The node's handle, or INVALID_HANDLE if not set. */ MegaHandle getS4Container(); /** * @brief Set node label as a node attribute. * Valid values for label attribute are: * - MegaNode::NODE_LBL_RED = 1 * - MegaNode::NODE_LBL_ORANGE = 2 * - MegaNode::NODE_LBL_YELLOW = 3 * - MegaNode::NODE_LBL_GREEN = 4 * - MegaNode::NODE_LBL_BLUE = 5 * - MegaNode::NODE_LBL_PURPLE = 6 * - MegaNode::NODE_LBL_GREY = 7 * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns the label for the node * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. * @param label Label of the node * @param listener MegaRequestListener to track this request */ void setNodeLabel(MegaNode *node, int label, MegaRequestListener *listener = NULL); /** * @brief Remove node label * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_LABEL * * @param node Node that will receive the information. * @param listener MegaRequestListener to track this request */ void resetNodeLabel(MegaNode *node, MegaRequestListener *listener = NULL); /** * @brief Set node favourite as a node attribute. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as favourite, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_FAV * * @param node Node that will receive the information. * @param fav if true set node as favourite, otherwise remove the attribute * @param listener MegaRequestListener to track this request */ void setNodeFavourite(MegaNode *node, bool fav, MegaRequestListener *listener = NULL); /** * @brief Mark a node as sensitive * * @note Descendants will inherit the sensitive property. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getNumDetails - Returns 1 if node is set as sensitive, otherwise return 0 * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_SENSITIVE * * @param node Node that will receive the information. * @param sensitive if true set node as sensitive, otherwise remove the attribute * @param listener MegaRequestListener to track this request */ void setNodeSensitive(MegaNode* node, bool sensitive, MegaRequestListener* listener = NULL); /** * @brief Ascertain if the node is marked as sensitive or a descendent of such * * see MegaNode::isMarkedSensitive to see if the node is sensitive * * @param node node to inspect */ bool isSensitiveInherited(MegaNode* node); /** * @brief Get a list of favourite nodes. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node provided * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_FAV * - MegaRequest::getNumDetails - Returns the count requested * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaHandleList - List of handles of favourite nodes * * @param node Node and its children that will be searched for favourites. Search all nodes if null * @param count if count is zero return all favourite nodes, otherwise return only 'count' favourite nodes * @param listener MegaRequestListener to track this request * * @deprecated use alternatives instead: * - for recursive searches use search(const MegaSearchFilter* filter, int order, MegaCancelToken* cancelToken) * - for non-recursive searches use getChildren(const MegaSearchFilter* filter, int order, MegaCancelToken* cancelToken) * Remember to call the filter->byFavourite(true) to get only nodes marked as favourite */ void getFavourites(MegaNode* node, int count, MegaRequestListener* listener = nullptr); /** * @brief Set the GPS coordinates of image files as a node attribute. * * To remove the existing coordinates, set both the latitude and longitude to * the value MegaNode::INVALID_COORDINATE. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_COORDINATES * - MegaRequest::getNumDetails - Returns the longitude, scaled to integer in the range of [0, 2^24] * - MegaRequest::getTransferTag() - Returns the latitude, scaled to integer in the range of [0, 2^24) * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param latitude Latitude in signed decimal degrees notation * @param longitude Longitude in signed decimal degrees notation * @param listener MegaRequestListener to track this request */ void setNodeCoordinates(MegaNode *node, double latitude, double longitude, MegaRequestListener *listener = NULL); /** * @brief Set the GPS coordinates of media files as a node attribute that is private * * To remove the existing coordinates, set both the latitude and longitude to * the value MegaNode::INVALID_COORDINATE. * * Compared to MegaApi::setNodeCoordinates, this function stores the coordinates with an extra * layer of encryption which only this user can decrypt, so that even if this node is shared * with others, they cannot read the coordinates. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_COORDINATES * - MegaRequest::getNumDetails - Returns the longitude, scaled to integer in the range of [0, 2^24] * - MegaRequest::getTransferTag() - Returns the latitude, scaled to integer in the range of [0, 2^24) * * @param node Node that will receive the information. * @param latitude Latitude in signed decimal degrees notation * @param longitude Longitude in signed decimal degrees notation * @param listener MegaRequestListener to track this request */ void setUnshareableNodeCoordinates(MegaNode *node, double latitude, double longitude, MegaRequestListener *listener = NULL); /** * @brief Set the GPS coordinates of media files as a node attribute that is private * * To remove the existing coordinates, set both the latitude and longitude to * the value MegaNode::INVALID_COORDINATE. * * Compared to MegaApi::setNodeCoordinates, this function stores the coordinates with an * extra layer of encryption which only this user can decrypt, so that even if this node is * shared with others, they cannot read the coordinates. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that receive the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_COORDINATES * - MegaRequest::getNumDetails - Returns the longitude, scaled to integer in the range of * [0, 2^24] * - MegaRequest::getTransferTag() - Returns the latitude, scaled to integer in the range of * [0, 2^24) * * @param nodeHandle Node handle of the node that will receive the information. * @param latitude Latitude in signed decimal degrees notation * @param longitude Longitude in signed decimal degrees notation * @param listener MegaRequestListener to track this request */ void setUnshareableNodeCoordinates(MegaHandle nodeHandle, double latitude, double longitude, MegaRequestListener* listener = nullptr); /** * @brief Set node description as a node attribute * * To remove node description, set description to NULL * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the attribute * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_DESCRIPTION * - MegaRequest::getText - Returns node description * * If the size of the description is greater than MAX_NODE_DESCRIPTION_SIZE, onRequestFinish will be * called with the error code MegaError::API_EARGS. * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param description Node description * @param listener MegaRequestListener to track this request */ void setNodeDescription(MegaNode* node, const char* description, MegaRequestListener* listener = NULL); /** * @brief Add new tag stored as node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Remove tag, 2 - Update tag) * - MegaRequest::getText - Returns tag * * ',' is an invalid character to be used in a tag. If it is contained in the tag, * onRequestFinish will be called with the error code MegaError::API_EARGS. * * If the length of all tags is higher than 3000 onRequestFinish will be called with * the error code MegaError::API_EARGS * * If tag already exists, onRequestFinish will be called with the error code MegaError::API_EEXISTS * * If number of tags exceed the maximum number of tags (10), * onRequestFinish will be called with the error code MegaError::API_ETOOMANY * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param tag New tag * @param listener MegaRequestListener to track this request */ void addNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener = NULL); /** * @brief Remove a tag stored as a node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Temove tag, 2 - Update tag) * - MegaRequest::getText - Returns tag * * If tag doesn't exist, onRequestFinish will be called with the error code MegaError::API_ENOENT * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param tag Tag to be removed * @param listener MegaRequestListener to track this request */ void removeNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener = NULL); /** * @brief Update a tag stored as a node attribute * * The associated request type with this request is MegaRequest::TYPE_TAG_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node that received the tag * - MegaRequest::getParamType - Returns operation type (0 - Add tag, 1 - Temove tag, 2 - Update tag) * - MegaRequest::getText - Returns new tag * - MegaRequest::getName - Returns old tag * * ',' is an invalid character to be used in a tag. If it is contained in the tag, * onRequestFinish will be called with the error code MegaError::API_EARGS. * * If the length of all tags is higher than 3000 characters onRequestFinish will be called with * the error code MegaError::API_EARGS * * If newTag already exists, onRequestFinish will be called with the error code MegaError::API_EEXISTS * If oldTag doesn't exist, onRequestFinish will be called with the error code MegaError::API_ENOENT * * If the MEGA account is a business account and its status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node Node that will receive the information. * @param newTag New tag value * @param oldTag Old tag value * @param listener MegaRequestListener to track this request */ void updateNodeTag(MegaNode* node, const char* newTag, const char* oldTag, MegaRequestListener* listener = NULL); /** * @brief Retrieve all unique node tags present across all nodes in the account * * @note If the pattern contains invalid characters, such as ',', an empty list will be * returned. * * @note This function allows to cancel the processing at any time by passing a * MegaCancelToken and calling to MegaCancelToken::setCancelFlag(true). * * You take ownership of the returned value. * * @param pattern Optional parameter to filter the tags based on a specific search * string. If set to nullptr, all node tags will be retrieved. * * @param cancelToken MegaCancelToken to be able to cancel the processing at any time. * * @return All the unique node tags that match the search criteria. */ MegaStringList* getAllNodeTags(const char* pattern = nullptr, MegaCancelToken* cancelToken = nullptr); /** * @brief * Retrieve all unique node tags present at or below the specified node. * * You take ownership of the returned value. * * @param node * The node we want to search for tags below. * * @param pattern * An optional pattern to be used to filter which tags we retrieve. * * All tags will be retrieved if no pattern is specified. * * @param cancelToken * A token that can be used to cancel the query. * * @return * A string list containing all unique tags found at or below node. */ MegaStringList* getAllNodeTagsBelow(const MegaNode* node, const char* pattern = nullptr, MegaCancelToken* cancelToken = nullptr); /** * @brief * Retrieve all unique node tags present at or below the specified node. * * You take ownership of the returned value. * * @param handle * A handle specifying the node we want to search for tags below. * * When UNDEF, we will search for tags below each of this account's * root nodes. * * @param pattern * An optional pattern to be used to filter which tags we retrieve. * * All tags will be retrieved if no pattern is specified. * * @param cancelToken * A token that can be used to cancel the query. * * @return * A string list containing all unique tags found at or below node. */ MegaStringList* getAllNodeTagsBelow(MegaHandle handle, const char* pattern = nullptr, MegaCancelToken* cancelToken = nullptr); /** * @brief Generate a public link of a file/folder in MEGA * * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns true * - MegaRequest::getNumber - Returns expire time * - MegaRequest::getFlag - Returns true if writable * - MegaRequest::getTransferTag - Returns if share key is shared with mega * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Public link * - MegaRequest::getPrivateKey - Authentication token (only if writable=true) * - MegaRequest::getPassword - Returns base64 encryption key used for share-key (only if * writable=true and megaHosted=true) * * If the MEGA account is a business account and it's status is expired, onRequestFinish * will be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to get the public link * @param expireTime Unix timestamp until the public link will be valid * @param writable if the link should be writable. * @param megaHosted if the share key should be shared with MEGA * @param listener MegaRequestListener to track this request */ void exportNode(MegaNode *node, int64_t expireTime, bool writable, bool megaHosted, MegaRequestListener *listener = NULL); /** * @brief Stop sharing a file/folder * * The associated request type with this request is MegaRequest::TYPE_EXPORT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getAccess - Returns false * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param node MegaNode to stop sharing * @param listener MegaRequestListener to track this request */ void disableExport(MegaNode *node, MegaRequestListener *listener = NULL); /** * @brief Fetch the filesystem in MEGA and resumes syncs following a successful fetch * * The MegaApi object must be logged in in an account or a public folder * to successfully complete this request. * * The associated request type with this request is MegaRequest::TYPE_FETCH_NODES * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if logged in into a folder and the provided key is invalid. Otherwise, false. * - MegaRequest::getNodeHandle - Returns the public handle if logged into a public folder. Otherwise, INVALID_HANDLE * * @param listener MegaRequestListener to track this request */ void fetchNodes(MegaRequestListener *listener = NULL); /** * @brief Get the sum of sizes of all the files stored in the MEGA cloud. * * The SDK keeps a running total of the sum of the sizes of all the files stored in the cloud. * This function retrieves that sum, via listener in order to avoid any blocking when called * from a GUI thread. Provided the local state is caught up, the number will match the * storageUsed from MegaApi::getAccountDetails which requests data from the servers, and is much * quicker to retrieve. * * The MegaApi object must be logged in in an account or a public folder * to successfully complete this request. * * The associated request type with this request is MegaRequest::TYPE_GET_CLOUDSTORAGEUSED * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - returns the cloud storage bytes used (calculated locally from the node data structures) * * @param listener MegaRequestListener to track this request */ void getCloudStorageUsed(MegaRequestListener *listener = NULL); /** * @brief Get details about the MEGA account * * Only basic data will be available. If you can get more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. * * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags * * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) * * @param listener MegaRequestListener to track this request */ void getAccountDetails(MegaRequestListener *listener = NULL); /** * @brief Get details about the MEGA account * * Only basic data will be available. If you need more data (sessions, transactions, purchases), * use MegaApi::getExtendedAccountDetails. * * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS * * Use this version of the function to get just the details you need, to minimise server load * and keep the system highly available for all. At least one flag must be set. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags * * The available flags are: * - storage quota: (numDetails & 0x01) * - transfer quota: (numDetails & 0x02) * - pro level: (numDetails & 0x04) * * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param storage If true, account storage details are requested * @param transfer If true, account transfer details are requested * @param pro If true, pro level of account is requested * @param source code associated to trace the origin of storage requests, used for debugging purposes * @param listener MegaRequestListener to track this request */ void getSpecificAccountDetails(bool storage, bool transfer, bool pro, int source = -1, MegaRequestListener *listener = NULL); /** * @brief Get details about the MEGA account * * This function allows to optionally get data about sessions, transactions and purchases related to the account. * * The associated request type with this request is MegaRequest::TYPE_ACCOUNT_DETAILS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAccountDetails - Details of the MEGA account * - MegaRequest::getNumDetails - Requested flags * * The available flags are: * - transactions: (numDetails & 0x08) * - purchases: (numDetails & 0x10) * - sessions: (numDetails & 0x020) * * In case none of the flags are set, the associated request will fail with error MegaError::API_EARGS. * * @param sessions If true, sessions are requested * @param purchases If true, purchases are requested * @param transactions If true, transactions are requested * @param listener MegaRequestListener to track this request */ void getExtendedAccountDetails(bool sessions = false, bool purchases = false, bool transactions = false, MegaRequestListener *listener = NULL); /** * @brief Check if the available transfer quota is enough to transfer an amount of bytes * * The associated request type with this request is MegaRequest::TYPE_QUERY_TRANSFER_QUOTA * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the amount of bytes to be transferred * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - True if it is expected to get an overquota error, otherwise false * * @param size Amount of bytes to be transferred * @param listener MegaRequestListener to track this request */ void queryTransferQuota(long long size, MegaRequestListener *listener = NULL); /** * @brief Get the available pricing plans to upgrade a MEGA account * * You can get a payment ID for any of the pricing plans provided by this function * using MegaApi::getPaymentId * * The associated request type with this request is MegaRequest::TYPE_GET_PRICING * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPricing - MegaPricing object with all pricing plans * - MegaRequest::getCurrency - MegaCurrency object with currency data related to prices * * @param listener MegaRequestListener to track this request * * @see MegaApi::getPaymentId */ void getPricing(MegaRequestListener *listener = NULL); /** * @brief Get the available pricing plans to upgrade a MEGA account in a specifc currency. * * If you need the pricing plans in the default currency for the account, please use * the overload avobe. * * You can get a payment ID for any of the pricing plans provided by this function * using MegaApi::getPaymentId * * The associated request type with this request is MegaRequest::TYPE_GET_PRICING * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPricing - MegaPricing object with all pricing plans * - MegaRequest::getCurrency - MegaCurrency object with currency data related to prices * * @param countryCode Optional country code for which the currency and prices will be * localized * @param listener MegaRequestListener to track this request * * @see MegaApi::getPaymentId * @see MegaApi::getPricing */ void getPricing(const char* countryCode, MegaRequestListener* listener = nullptr); /** * @brief Get the recommended PRO level. The smallest plan that is an upgrade (free -> lite -> proi -> proii -> proiii) * and has enough space. * * The associated request type with this request is MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber: the recommended PRO level: * Valid values are (there are other account types): * - MegaAccountDetails::ACCOUNT_TYPE_PROI = 1 * - MegaAccountDetails::ACCOUNT_TYPE_PROII = 2 * - MegaAccountDetails::ACCOUNT_TYPE_PROIII = 3 * - MegaAccountDetails::ACCOUNT_TYPE_LITE = 4 * - MegaAccountDetails::ACCOUNT_TYPE_STARTER = 11 * - MegaAccountDetails::ACCOUNT_TYPE_BASIC = 12 * - MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL = 13 * * @param listener MegaRequestListener to track this request */ void getRecommendedProLevel(MegaRequestListener* listener = NULL); /** * @brief Get the payment URL for an upgrade * * The associated request type with this request is MegaRequest::TYPE_GET_PAYMENT_ID * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the product * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Payment ID * * @param productHandle Handle of the product (see MegaApi::getPricing) * @param listener MegaRequestListener to track this request * * @see MegaApi::getPricing */ void getPaymentId(MegaHandle productHandle, MegaRequestListener *listener = NULL); /** * @brief Upgrade an account * @param productHandle Product handle to purchase * * It's possible to get all pricing plans with their product handles using * MegaApi::getPricing * * The associated request type with this request is MegaRequest::TYPE_UPGRADE_ACCOUNT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the product * - MegaRequest::getNumber - Returns the payment method * * @param paymentMethod Payment method * Valid values are: * - MegaApi::PAYMENT_METHOD_BALANCE = 0 * Use the account balance for the payment * * - MegaApi::PAYMENT_METHOD_CREDIT_CARD = 8 * Complete the payment with your credit card. Use MegaApi::creditCardStore to add * a credit card to your account * * @param listener MegaRequestListener to track this request */ void upgradeAccount(MegaHandle productHandle, int paymentMethod, MegaRequestListener *listener = NULL); /** * @brief Submit a purchase receipt for verification * * The associated request type with this request is MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the payment gateway * - MegaRequest::getText - Returns the purchase receipt * * @param gateway Payment gateway * Currently supported payment gateways are: * - MegaApi::PAYMENT_METHOD_ITUNES = 2 * - MegaApi::PAYMENT_METHOD_GOOGLE_WALLET = 3 * - MegaApi::PAYMENT_METHOD_WINDOWS_STORE = 13 * * @param receipt Purchase receipt * @param listener MegaRequestListener to track this request */ void submitPurchaseReceipt(int gateway, const char* receipt, MegaRequestListener *listener = NULL); /** * @brief Submit a purchase receipt for verification * * The associated request type with this request is * MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the payment gateway * - MegaRequest::getText - Returns the purchase receipt * - MegaRequest::getNodeHandle - Returns the last public node handle accessed * * @param gateway Payment gateway * Currently supported payment gateways are: * - MegaApi::PAYMENT_METHOD_ITUNES = 2 * - MegaApi::PAYMENT_METHOD_GOOGLE_WALLET = 3 * - MegaApi::PAYMENT_METHOD_WINDOWS_STORE = 13 * * @param receipt Purchase receipt * @param lastPublicHandle Last public node handle accessed by the user in the last 24h * @param listener MegaRequestListener to track this request * * @deprecated This version of the function is deprecated. Please, use the non-deprecated * ones. */ MEGA_DEPRECATED void submitPurchaseReceipt(int gateway, const char* receipt, MegaHandle lastPublicHandle, MegaRequestListener *listener = NULL); /** * @brief Submit a purchase receipt for verification * * The associated request type with this request is * MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the payment gateway * - MegaRequest::getText - Returns the purchase receipt * - MegaRequest::getNodeHandle - Returns the last public node handle accessed * - MegaRequest::getParamType - Returns the type of lastPublicHandle * - MegaRequest::getTransferredBytes - Returns the timestamp of the last access * * @param gateway Payment gateway * Currently supported payment gateways are: * - MegaApi::PAYMENT_METHOD_ITUNES = 2 * - MegaApi::PAYMENT_METHOD_GOOGLE_WALLET = 3 * - MegaApi::PAYMENT_METHOD_WINDOWS_STORE = 13 * * @param receipt Purchase receipt * @param lastPublicHandle Last public node handle accessed by the user in the last 24h * @param lastPublicHandleType Indicates the type of lastPublicHandle, valid values are: * - MegaApi::AFFILIATE_TYPE_ID = 1 * - MegaApi::AFFILIATE_TYPE_FILE_FOLDER = 2 * - MegaApi::AFFILIATE_TYPE_CHAT = 3 * - MegaApi::AFFILIATE_TYPE_CONTACT = 4 * * @param lastAccessTimestamp Timestamp of the last access * @param listener MegaRequestListener to track this request * * @deprecated This version of the function is deprecated. Please, use the non-deprecated * one. */ MEGA_DEPRECATED void submitPurchaseReceipt(int gateway, const char *receipt, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener = NULL); /** * @brief Store a credit card * * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_STORE * * @param address1 Billing address * @param address2 Second line of the billing address (optional) * @param city City of the billing address * @param province Province of the billing address * @param country Contry of the billing address * @param postalcode Postal code of the billing address * @param firstname Firstname of the owner of the credit card * @param lastname Lastname of the owner of the credit card * @param creditcard Credit card number. Only digits, no spaces nor dashes * @param expire_month Expire month of the credit card. Must have two digits ("03" for example) * @param expire_year Expire year of the credit card. Must have four digits ("2010" for example) * @param cv2 Security code of the credit card (3 digits) * @param listener MegaRequestListener to track this request */ void creditCardStore(const char* address1, const char* address2, const char* city, const char* province, const char* country, const char *postalcode, const char* firstname, const char* lastname, const char* creditcard, const char* expire_month, const char* expire_year, const char* cv2, MegaRequestListener *listener = NULL); /** * @brief Get the credit card subscriptions of the account * * The associated request type with this request is MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Number of credit card subscriptions * * @param listener MegaRequestListener to track this request */ void creditCardQuerySubscriptions(MegaRequestListener *listener = NULL); /** * @brief Cancel the credit card subscriptions of the account * * The associated request type with this request is * MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS * @param reason The reason for the cancellation. It can be NULL. * @param id The subscription ID for the cancellation. If null or empty, all subscriptions * will be cancelled. * @param canContact Whether the user has permitted MEGA to contact them for the * cancellation. * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO = 0 * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_YES = 1 * @param listener MegaRequestListener to track this request */ void creditCardCancelSubscriptions(const char* reason, const char* id, int canContact, MegaRequestListener* listener = NULL); /** * @brief Cancel credit card subscriptions of the account * * The associated request type with this request is * MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS * * @param reasonList List of reasons for the cancellation. It can be null. * @param id The ID of the subscription to be cancelled. If null or empty, all subscriptions * will be cancelled. * @param canContact Whether the user has permitted MEGA to contact them about the * cancellation. * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO = 0 * - MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_YES = 1 * @param listener MegaRequestListener to track this request */ void creditCardCancelSubscriptions(const MegaCancelSubscriptionReasonList* reasonList, const char* id, int canContact, MegaRequestListener* listener = nullptr); /** * @brief Get the available payment methods * * The associated request type with this request is MegaRequest::TYPE_GET_PAYMENT_METHODS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Bitfield with available payment methods * * To know if a payment method is available, you can do a check like this one: * request->getNumber() & (1 << MegaApi::PAYMENT_METHOD_CREDIT_CARD) * * @param listener MegaRequestListener to track this request */ void getPaymentMethods(MegaRequestListener *listener = NULL); /** * @brief Export the master key of the account * * The returned value is a Base64-encoded string * * With the master key, it's possible to start the recovery of an account when the * password is lost: * - MegaApi::resetPassword() * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Base64-encoded master key */ char *exportMasterKey(); /** * @brief Notify the user has exported the master key * * This function should be called when the user exports the master key by * clicking on "Copy" or "Save file" options. * * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember the user has a backup of his/her master key. In consequence, * MEGA will not ask the user to remind the password for the account. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ void masterKeyExported(MegaRequestListener *listener = NULL); /** * @brief Notify the user has successfully checked his password * * This function should be called when the user demonstrates that he remembers * the password to access the account * * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ void passwordReminderDialogSucceeded(MegaRequestListener *listener = NULL); /** * @brief Notify the user has successfully skipped the password check * * This function should be called when the user skips the verification of * the password to access the account * * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not continue asking the user * to remind the password for the account in a short time. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ void passwordReminderDialogSkipped(MegaRequestListener *listener = NULL); /** * @brief Notify the user wants to totally disable the password check * * This function should be called when the user rejects to verify that he remembers * the password to access the account and doesn't want to see the reminder again. * * As result, the user attribute MegaApi::USER_ATTR_PWD_REMINDER will be updated * to remember this event. In consequence, MEGA will not ask the user * to remind the password for the account again. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * - MegaRequest::getText - Returns the new value for the attribute * * @param listener MegaRequestListener to track this request */ void passwordReminderDialogBlocked(MegaRequestListener *listener = NULL); /** * @brief Check if the app should show the password reminder dialog to the user * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if the password reminder dialog should be shown * * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT but the value of MegaRequest::getFlag will still * be valid. * * @param atLogout True if the check is being done just before a logout * @param listener MegaRequestListener to track this request */ void shouldShowPasswordReminderDialog(bool atLogout, MegaRequestListener *listener = NULL); /** * @brief Check if the master key has been exported * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getAccess - Returns true if the master key has been exported * * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ void isMasterKeyExported(MegaRequestListener *listener = NULL); #ifdef ENABLE_CHAT /** * @brief Enable or disable the generation of rich previews * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param enable True to enable the generation of rich previews * @param listener MegaRequestListener to track this request */ void enableRichPreviews(bool enable, MegaRequestListener *listener = NULL); /** * @brief Check if rich previews are automatically generated * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns zero * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if generation of rich previews is enabled * - MegaRequest::getMegaStringMap - Returns the raw content of the atribute: []* * * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (false). * * @param listener MegaRequestListener to track this request */ void isRichPreviewsEnabled(MegaRequestListener *listener = NULL); /** * @brief Check if the app should show the rich link warning dialog to the user * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * - MegaRequest::getNumDetails - Returns one * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getFlag - Returns true if it is necessary to show the rich link warning * - MegaRequest::getNumber - Returns the number of times that user has indicated that doesn't want * modify the message with a rich link. If number is bigger than three, the extra option "Never" * must be added to the warning dialog. * - MegaRequest::getMegaStringMap - Returns the raw content of the atribute: []* * * If the corresponding user attribute is not set yet, the request will fail with the * error code MegaError::API_ENOENT, but the value of MegaRequest::getFlag will still be valid (true). * * @param listener MegaRequestListener to track this request */ void shouldShowRichLinkWarning(MegaRequestListener *listener = NULL); /** * @brief Set the number of times "Not now" option has been selected in the rich link warning dialog * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RICH_PREVIEWS * * @param value Number of times "Not now" option has been selected * @param listener MegaRequestListener to track this request */ void setRichLinkWarningCounterValue(int value, MegaRequestListener *listener = NULL); /** * @brief Enable the sending of geolocation messages * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_GEOLOCATION * * @param listener MegaRequestListener to track this request */ void enableGeolocation(MegaRequestListener *listener = NULL); /** * @brief Check if the sending of geolocation messages is enabled * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_GEOLOCATION * * Sending a Geolocation message is enabled if the MegaRequest object, received in onRequestFinish, * has error code MegaError::API_OK. In other cases, send geolocation messages is not enabled and * the application has to answer before send a message of this type. * * @param listener MegaRequestListener to track this request */ void isGeolocationEnabled(MegaRequestListener *listener = NULL); #endif /** * @brief Set My Chat Files target folder. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER * - MegaRequest::getMegaStringMap - Returns a MegaStringMap. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param nodehandle MegaHandle of the node to be used as target folder * @param listener MegaRequestListener to track this request */ void setMyChatFilesFolder(MegaHandle nodehandle, MegaRequestListener *listener = NULL); /** * @brief Gets My chat files target folder. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the node where My Chat Files are stored * * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ void getMyChatFilesFolder(MegaRequestListener *listener = NULL); /** * @brief Set Camera Uploads primary target folder. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns false * - MegaRequest::getNodehandle - Returns the provided node handle * - MegaRequest::getMegaStringMap - Returns a MegaStringMap. * The key "h" in the map contains the nodehandle specified as parameter encoded in B64 * * @param nodehandle MegaHandle of the node to be used as primary target folder * @param listener MegaRequestListener to track this request */ void setCameraUploadsFolder(MegaHandle nodehandle, MegaRequestListener *listener = NULL); /** * @brief Set Camera Uploads for both primary and secondary target folder. * * If only one of the target folders wants to be set, simply pass a INVALID_HANDLE to * as the other target folder and it will remain untouched. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getNodehandle - Returns the provided node handle for primary folder * - MegaRequest::getParentHandle - Returns the provided node handle for secondary folder * * @param primaryFolder MegaHandle of the node to be used as primary target folder * @param secondaryFolder MegaHandle of the node to be used as secondary target folder * @param listener MegaRequestListener to track this request */ void setCameraUploadsFolders(MegaHandle primaryFolder, MegaHandle secondaryFolder, MegaRequestListener *listener = NULL); /** * @brief Gets Camera Uploads primary target folder. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns false * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the primary node where Camera Uploads files are stored * * If the folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ void getCameraUploadsFolder(MegaRequestListener *listener = NULL); /** * @brief Gets Camera Uploads secondary target folder. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER * - MegaRequest::getFlag - Returns true * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the handle of the secondary node where Camera Uploads files are stored * * If the secondary folder is not set, the request will fail with the error code MegaError::API_ENOENT. * * @param listener MegaRequestListener to track this request */ void getCameraUploadsFolderSecondary(MegaRequestListener *listener = NULL); /** * @brief Creates the special folder for backups ("My backups") * * It creates a new folder inside the Vault rootnode and later stores the node's * handle in a user's attribute, MegaApi::USER_ATTR_MY_BACKUPS_FOLDER. * * Apps should first check if this folder exists already, by calling * MegaApi::getUserAttribute for the corresponding attribute. * * The associated request type with this request is MegaRequest::TYPE_SET_MY_BACKUPS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the name provided as parameter * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodehandle - Returns the node handle of the folder created * * If no user was logged in, the request will fail with the error API_EACCESS. * If the folder for backups already existed, the request will fail with the error API_EEXIST. * * @param localizedName Localized name for "My backups" folder * @param listener MegaRequestListener to track this request */ void setMyBackupsFolder(const char *localizedName, MegaRequestListener *listener = nullptr); /** * @brief Gets the alias for an user * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_ALIAS * - MegaRequest::getNodeHandle - user handle in binary * - MegaRequest::getText - user handle encoded in B64 * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the user alias * * If the user alias doesn't exists the request will fail with the error code MegaError::API_ENOENT. * * @param uh handle of the user in binary * @param listener MegaRequestListener to track this request */ void getUserAlias(MegaHandle uh, MegaRequestListener *listener = NULL); /** * @brief Set or reset an alias for a user * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_ALIAS * - MegaRequest::getNodeHandle - Returns the user handle in binary * - MegaRequest::getText - Returns the user alias * * @param uh handle of the user in binary * @param alias the user alias, or null to reset the existing * @param listener MegaRequestListener to track this request */ void setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener = NULL); /** * @brief Get push notification settings * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PUSH_SETTINGS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaPushNotificationSettings - Returns settings for push notifications * * @see MegaPushNotificationSettings class for more details. * * @param listener MegaRequestListener to track this request */ void getPushNotificationSettings(MegaRequestListener *listener = NULL); /** * @brief Set push notification settings * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PUSH_SETTINGS * - MegaRequest::getMegaPushNotificationSettings - Returns settings for push notifications * * @see MegaPushNotificationSettings class for more details. You can prepare a new object by * calling MegaPushNotificationSettings::createInstance. * * @param settings MegaPushNotificationSettings with the new settings * @param listener MegaRequestListener to track this request */ void setPushNotificationSettings(MegaPushNotificationSettings *settings, MegaRequestListener *listener = NULL); /** * @brief Get the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RUBBISH_TIME * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the days for rubbish-bin cleaning scheduler. * Zero means that the rubbish-bin cleaning scheduler is disabled (only if the account is PRO) * Any negative value means that the configured value is invalid. * * @param listener MegaRequestListener to track this request */ void getRubbishBinAutopurgePeriod(MegaRequestListener *listener = NULL); /** * @brief Set the number of days for rubbish-bin cleaning scheduler * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_RUBBISH_TIME * - MegaRequest::getNumber - Returns the days for rubbish-bin cleaning scheduler passed as parameter * * @param days Number of days for rubbish-bin cleaning scheduler. It must be >= 0. * The value zero disables the rubbish-bin cleaning scheduler (only for PRO accounts). * * @param listener MegaRequestListener to track this request */ void setRubbishBinAutopurgePeriod(int days, MegaRequestListener *listener = NULL); /** * @brief Returns the id of this device * * You take ownership of the returned value. Use delete[] to release the memory. * * @return The id of this device */ const char* getDeviceId() const; /** * @brief Returns the name previously set for a device * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getText - Returns passed device id (or the value returned by getDeviceId() * if deviceId was initially passed as null). * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns device name. * * @param deviceId The id of the device to get the name for. If null, the value returned * by getDeviceId() will be used instead. * @param listener MegaRequestListener to track this request */ void getDeviceName(const char *deviceId, MegaRequestListener *listener = NULL); /** * @brief Sets name for specified device * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getName - Returns device name. * - MegaRequest::getText - Returns passed device id (or the value returned by getDeviceId() * if deviceId was initially passed as null). * * @param deviceId The id of the device to set the name for If null, the value returned * by getDeviceId() will be used instead. * @param deviceName String with device name * @param listener MegaRequestListener to track this request */ void setDeviceName(const char* deviceId, const char* deviceName, MegaRequestListener *listener = NULL); /** * @brief Returns the name set for this drive * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getFile - Returns the path to the drive * - MegaRequest::getFlag - Returns true * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns drive name. * * @param pathToDrive Path to the root of the external drive * @param listener MegaRequestListener to track this request */ void getDriveName(const char *pathToDrive, MegaRequestListener *listener = NULL); /** * @brief Sets drive name * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_DEVICE_NAMES * - MegaRequest::getName - Returns drive name. * - MegaRequest::getFile - Returns the path to the drive * - MegaRequest::getFlag - Returns true * * @param pathToDrive Path to the root of the external drive * @param driveName String with drive name * @param listener MegaRequestListener to track this request */ void setDriveName(const char* pathToDrive, const char *driveName, MegaRequestListener *listener = NULL); /** * @brief Change the password of the MEGA account * * The associated request type with this request is MegaRequest::TYPE_CHANGE_PW * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the old password (if it was passed as parameter) * - MegaRequest::getNewPassword - Returns the new password * * @param oldPassword Old password (optional, it can be NULL to not check the old password) * @param newPassword New password * @param listener MegaRequestListener to track this request */ void changePassword(const char *oldPassword, const char *newPassword, MegaRequestListener *listener = NULL); /** * @brief Invite another person to be your MEGA contact * * The user doesn't need to be registered on MEGA. If the email isn't associated with * a MEGA account, an invitation email will be sent with the text in the "message" parameter. * * The associated request type with this request is MegaRequest::TYPE_INVITE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getText - Returns the text of the invitation * - MegaRequest::getNumber - Returns the action * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MegaError::API_EACCESS. * * @param email Email of the new contact * @param message Message for the user (can be NULL) * @param action Action for this contact request. Valid values are: * - MegaContactRequest::INVITE_ACTION_ADD = 0 * - MegaContactRequest::INVITE_ACTION_DELETE = 1 * - MegaContactRequest::INVITE_ACTION_REMIND = 2 * * @param listener MegaRequestListener to track this request */ void inviteContact(const char* email, const char* message, int action, MegaRequestListener* listener = NULL); /** * @brief Invite another person to be your MEGA contact using a contact link handle * * The associated request type with this request is MegaRequest::TYPE_INVITE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * - MegaRequest::getText - Returns the text of the invitation * - MegaRequest::getNumber - Returns the action * - MegaRequest::getNodeHandle - Returns the contact link handle * * Sending a reminder within a two week period since you started or your last reminder will * fail the API returning the error code MegaError::API_EACCESS. * * @param email Email of the new contact * @param message Message for the user (can be NULL) * @param action Action for this contact request. Valid values are: * - MegaContactRequest::INVITE_ACTION_ADD = 0 * - MegaContactRequest::INVITE_ACTION_DELETE = 1 * - MegaContactRequest::INVITE_ACTION_REMIND = 2 * @param contactLink Contact link handle of the other account. This parameter is considered only if the * \c action is MegaContactRequest::INVITE_ACTION_ADD. Otherwise, it's ignored and it has no effect. * * @param listener MegaRequestListener to track this request */ void inviteContact(const char* email, const char* message, int action, MegaHandle contactLink, MegaRequestListener* listener = NULL); /** * @brief Reply to a contact request * @param request Contact request. You can get your pending contact requests using MegaApi::getIncomingContactRequests * @param action Action for this contact request. Valid values are: * - MegaContactRequest::REPLY_ACTION_ACCEPT = 0 * - MegaContactRequest::REPLY_ACTION_DENY = 1 * - MegaContactRequest::REPLY_ACTION_IGNORE = 2 * * The associated request type with this request is MegaRequest::TYPE_REPLY_CONTACT_REQUEST * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the contact request * - MegaRequest::getNumber - Returns the action * * @param listener MegaRequestListener to track this request */ void replyContactRequest(const MegaContactRequest* request, int action, MegaRequestListener* listener = NULL); /** * @brief Remove a contact to the MEGA account * * The associated request type with this request is MegaRequest::TYPE_REMOVE_CONTACT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getEmail - Returns the email of the contact * * @param user MegaUser of the contact (see MegaApi::getContact) * @param listener MegaRequestListener to track this request */ void removeContact(MegaUser *user, MegaRequestListener* listener = NULL); /** * @brief Logout of the MEGA account invalidating the session * * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the keepSyncConfigsFile * - MegaRequest::getFlag - Returns true * * Under certain circumstances, this request might return the error code * MegaError::API_ESID. It should not be taken as an error, since the reason * is that the logout action has been notified before the reception of the * logout response itself. * * In case of an automatic logout (ie. when the account become blocked by * ToS infringment), the MegaRequest::getParamType indicates the error that * triggered the automatic logout (MegaError::API_EBLOCKED for the example). * * @param keepSyncConfigsFile Allow sync configs to be recovered if the same user logs in again * The file containing sync configs is encrypted so there's no privacy issue. * This is provided for backward compatibility for MEGAsync. * @param listener MegaRequestListener to track this request */ #ifdef ENABLE_SYNC void logout(bool keepSyncConfigsFile, MegaRequestListener *listener); #else void logout(MegaRequestListener *listener = nullptr); #endif /** * @brief Logout of the MEGA account without invalidating the session * * The associated request type with this request is MegaRequest::TYPE_LOGOUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns false * * @param listener MegaRequestListener to track this request */ void localLogout(MegaRequestListener *listener = NULL); /** * @brief Invalidate the existing cache and create a fresh one */ void invalidateCache(); /** * @brief Estimate the strength of a password * * Possible return values are: * - PASSWORD_STRENGTH_VERYWEAK = 0 * - PASSWORD_STRENGTH_WEAK = 1 * - PASSWORD_STRENGTH_MEDIUM = 2 * - PASSWORD_STRENGTH_GOOD = 3 * - PASSWORD_STRENGTH_STRONG = 4 * * @param password Password to check * @return Estimated strength of the password */ int getPasswordStrength(const char *password); /** * @brief Generate a new pseudo-randomly characters-based password * * You take ownership of the returned value. Use delete[] to release the memory. * * @param t bool indicating if at least 1 upper case letter shall be included * @param t bool indicating if at least 1 digit shall be included * @param t bool indicating if at least 1 symbol from !@#$%^&*() shall be included * @param length unsigned int with the number of characters that will be included. * Minimum valid length is 8 and maximum valid is 64. * @return Null-terminated char string containing the newly generated password. */ static char* generateRandomCharsPassword(bool useUpper, bool useDigit, bool useSymbol, unsigned int length); /** * @brief Submit feedback about the app * * The associated request type with this request is MegaRequest::TYPE_SUBMIT_FEEDBACK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Retuns the comment about the app * - MegaRequest::getNumber - Returns the rating for the app * - MegaRequest::getFlag - Returns a flag used to differentiate feedback about transfers * from other types. In this case, it will always be false since we are not sending transfer * feedback. * * @param rating Integer to rate the app. Valid values: from 1 to 5. * @param comment Comment about the app * @param listener MegaRequestListener to track this request */ void submitFeedback(int rating, const char *comment, MegaRequestListener *listener = NULL); /** * @brief Submit feedback about transfers * * The associated request type with this request is MegaRequest::TYPE_SUBMIT_FEEDBACK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Retuns the comment about the app * - MegaRequest::getNumber - Returns the rating for the app * - MegaRequest::getFlag - Returns a flag used to differentiate feedback about transfers * from other types. In this case, it will always be true since we are sending transfer * feedback. * - MegaRequest::getParamType - Returns the type of transfer we want to give feedback * Valid values are: * + TRANSFER_STATS_DOWNLOAD = 0, * + TRANSFER_STATS_UPLOAD = 1, * + TRANSFER_STATS_BOTH = 2, * * @param rating Integer to rate the app. Valid values: from 1 to 5. * @param comment Comment about the app * @param comment transferType type of transfer we want to give feedback. Valid values are: * TRANSFER_STATS_DOWNLOAD (0) and TRANSFER_STATS_UPLOAD (1) TRANSFER_STATS_BOTH (2), * @param listener MegaRequestListener to track this request */ void submitFeedbackForTransfers(int rating, const char* comment, int transferType, MegaRequestListener* listener = NULL); /** * @brief Send events to the stats server * * The associated request type with this request is MegaRequest::TYPE_SEND_EVENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the event type * - MegaRequest::getText - Returns the event message * - MegaRequest::getFlag - Returns the addJourneyId flag * - MegaRequest::getSessionKey - Returns the ViewID * * @param eventType Event type * @param message Event message * @param addJourneyId True if JourneyID should be included. Otherwise, false. * @param viewId ViewID value (C-string null-terminated) to be sent with the event. * This value should have been generated with MegaApi::generateViewId method. * @param listener MegaRequestListener to track this request * * @devonly This function is for internal usage of MEGA apps for debug purposes. This info * is sent to MEGA servers. * * @note Event types are restricted to the following ranges: * - MEGAcmd: [98900, 99000) * - MEGAchat: [99000, 99199) * - Android: [99200, 99300) * - iOS: [99300, 99400) * - MEGA SDK: [99400, 99500) * - MEGAsync: [99500, 99600) * - Webclient: [99600, 99800] */ void sendEvent(int eventType, const char* message, bool addJourneyId, const char* viewId, MegaRequestListener *listener = NULL); /** * @brief Create a new ticket for support with attached description * * The associated request type with this request is MegaRequest::TYPE_SUPPORT_TICKET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the type of the ticket * - MegaRequest::getText - Returns the description of the issue * * @param message Description of the issue for support * @param type Ticket type. These are the available types: * 0 for General Enquiry * 1 for Technical Issue * 2 for Payment Issue * 3 for Forgotten Password * 4 for Transfer Issue * 5 for Contact/Sharing Issue * 6 for MEGAsync Issue * 7 for Missing/Invisible Data * 8 for help-centre clarifications * 9 for iOS issue * 10 for Android issue * @param listener MegaRequestListener to track this request */ void createSupportTicket(const char* message, int type = 1, MegaRequestListener *listener = NULL); /** * @brief Send a debug report * * The User-Agent is used to identify the app. It can be set in MegaApi::MegaApi * * The associated request type with this request is MegaRequest::TYPE_REPORT_EVENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns MegaApi::EVENT_DEBUG * - MegaRequest::getText - Retuns the debug message * * @param text Debug message * @param listener MegaRequestListener to track this request * * @devonly This function is for internal usage of MEGA apps. This feedback * is sent to MEGA servers. */ void reportDebugEvent(const char *text, MegaRequestListener *listener = NULL); /** * @deprecated HTTPS is always enabled */ MEGA_DEPRECATED void useHttpsOnly(bool httpsOnly, MegaRequestListener *listener = NULL); /** * @deprecated HTTPS is always enabled */ MEGA_DEPRECATED bool usingHttpsOnly(); /////////////////// TRANSFERS /////////////////// /** * @brief Upload a file to support * * If the status of the business account is expired, onTransferFinish will be called with * the error code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning * message similar to "Your business account is overdue, please contact your administrator." * * For folders, onTransferFinish will be called with error MegaError:API_EARGS; * * @param localPath Local path of the file * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it * when the upload finishes. This parameter is intended to automatically delete temporary * files that are only created to be uploaded. Use this parameter with caution. Set it to * true only if you are sure about what are you doing. * @param listener MegaTransferListener to track this transfer * * @note In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. */ void startUploadForSupport(const char* localPath, bool isSourceTemporary = false, MegaTransferListener *listener=NULL); /** * @brief Upload a file or a folder * * If the status of the business account is expired, onTransferFinish will be called with * the error code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning * message similar to "Your business account is overdue, please contact your administrator." * * When user wants to upload a batch of items that at least contains one folder, SDK mutex * will be partially locked until: * - we have received onTransferStart for every file in the batch * - we have received onTransferUpdate with MegaTransfer::getStage == * MegaTransfer::STAGE_TRANSFERRING_FILES for every folder in the batch * * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by * calling CancelToken::cancel(true). This method will cancel all transfers(not finished * yet). * * Important considerations: * - A cancel token instance can be shared by multiple transfers, and calling * CancelToken::cancel(true) will affect all of those transfers. * * - It's app responsibility, to keep cancel token instance alive until receive * MegaTransferListener::onTransferFinish for all MegaTransfers that shares the same cancel * token instance. * * For more information about MegaTransfer stages please refer to onTransferUpdate * documentation. * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param fileName Custom file name for the file or folder in MEGA * + If you don't need this param provide NULL as value * @param mtime Custom modification time for the file in MEGA (in seconds since the epoch) * + If you don't need this param provide MegaApi::INVALID_CUSTOM_MOD_TIME as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error API_EEXISTS and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * + If you don't need this param provide NULL as value * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it * when the upload finishes. This parameter is intended to automatically delete temporary * files that are only created to be uploaded. Use this parameter with caution. Set it to * true only if you are sure about what are you doing. * + If you don't need this param provide false as value * @param startFirst puts the transfer on top of the upload queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file upload process. * This param is required to be able to cancel the transfer safely. * App retains the ownership of this param. * @param listener MegaTransferListener to track this transfer * * @note In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. * * @deprecated This version of the function is deprecated. Please, use the non-deprecated * one. */ MEGA_DEPRECATED void startUpload(const char *localPath, MegaNode *parent, const char *fileName, int64_t mtime, const char *appData, bool isSourceTemporary, bool startFirst, MegaCancelToken *cancelToken, MegaTransferListener *listener=NULL); /** * @brief Upload a file or a folder * * This method should be used ONLY to share by chat a local file. In case the file * is already uploaded, but the corresponding node is missing the thumbnail and/or preview, * this method will force a new upload from the scratch (ensuring the file attributes are * set), instead of doing a remote copy. * * This method always puts the transfer on top of the upload queue. * * If the status of the business account is expired, onTransferFinish will be called with * the error code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning * message similar to "Your business account is overdue, please contact your administrator." * * @param localPath Local path of the file or folder * @param parent Parent node for the file or folder in the MEGA account * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. If a transfer is started with exactly the same data * (local path and target parent) as another one in the transfer queue, the new transfer * fails with the error API_EEXISTS and the appData of the new transfer is appended to * the appData of the old transfer, using a '!' separator if the old transfer had already * appData. * @param isSourceTemporary Pass the ownership of the file to the SDK, that will DELETE it * when the upload finishes. This parameter is intended to automatically delete temporary * files that are only created to be uploaded. Use this parameter with caution. Set it to * true only if you are sure about what are you doing. * @param fileName Custom file name for the file or folder in MEGA * @param listener MegaTransferListener to track this transfer * * @note In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. * * @deprecated Deprecated in favor of startUpload() with MegaUploadOptions passed via the * uploadOptions parameter. */ MEGA_DEPRECATED void startUploadForChat(const char *localPath, MegaNode *parent, const char *appData, bool isSourceTemporary, const char* fileName, MegaTransferListener *listener = NULL); /** * @brief Upload a file or a folder. * * This method starts an upload transfer for a local file or folder into the specified * parent node. * * Business account overdue: * If the status of the business account is expired/overdue, * MegaTransferListener::onTransferFinish() will be called with error code * MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar * to "Your business account is overdue, please contact your administrator." * * Folder batch deadlock considerations: * When uploading a batch of items that contains at least one folder, the SDK mutex will be * partially locked until: * - onTransferStart has been received for every file in the batch, and * - onTransferUpdate has been received with MegaTransfer::getStage() == * MegaTransfer::STAGE_TRANSFERRING_FILES for every folder in the batch. * * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by * calling CancelToken::cancel(true). This cancels all transfers (not finished yet) * associated with that cancel token instance. * * Important considerations about cancel tokens: * - A MegaCancelToken instance can be shared by multiple transfers. Calling cancel(true) * affects all transfers that share the token. * - It is the app responsibility to keep the MegaCancelToken instance alive until * MegaTransferListener::onTransferFinish() is received for all MegaTransfers that share * it. * * For more information about MegaTransfer stages please refer to * MegaTransferListener::onTransferUpdate documentation. * * @param localPath Local path of the file or folder to upload. * @param parent Parent node where the file/folder will be created in the MEGA account. * @param cancelToken MegaCancelToken used to cancel the upload process safely (required for * safe cancellation). App retains ownership and must keep it alive as described above. * @param options Optional upload customization parameters. * @param listener Optional MegaTransferListener to track this transfer. The app retains the * ownership of the object. It can be deleted after the call returns. * @note In case we find a node in cloud drive with the same content but a different mtime * than the file to be uploaded, this function will try to update it's mtime instead of * starting a new file upload. If setting the mtime fails, the transfer will fail with * API_EWRITE. */ void startUpload(const std::string& localPath, MegaNode* parent, MegaCancelToken* cancelToken, const MegaUploadOptions* options, MegaTransferListener* listener = nullptr); /** * @brief Download a file or a folder from MEGA, saving custom app data during the transfer * * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * If the node, or the account to which the node belongs, has been taken down or suspended, onTransferFinish will be called with * the error code MegaError::API_ETOOMANY. In this case, the application should show a warning message similar to * "The file that you are downloading (or the account it belongs to) has been suspended for violating our Terms of Service." * * When user wants to download a batch of items that at least contains one folder, SDK mutex will be partially * locked until: * - we have received onTransferStart for every file in the batch * - we have received onTransferUpdate with MegaTransfer::getStage == MegaTransfer::STAGE_TRANSFERRING_FILES * for every folder in the batch * * During this period, the only safe method (to avoid deadlocks) to cancel transfers is by calling CancelToken::cancel(true). * This method will cancel all transfers(not finished yet). * * Important considerations: * - A cancel token instance can be shared by multiple transfers, and calling CancelToken::cancel(true) will affect all * of those transfers. * * - It's app responsibility, to keep cancel token instance alive until receive MegaTransferListener::onTransferFinish for all MegaTransfers * that shares the same cancel token instance. * * For more information about MegaTransfer stages please refer to onTransferUpdate documentation. * * @param node MegaNode that identifies the file or folder * @param localPath Destination path for the file or folder * If this path is a local folder, it must end with a '\' or '/' character and the file name * in MEGA will be used to store a file inside that folder. If the path doesn't finish with * one of these characters, the file will be downloaded to a file in that path. * @param customName Custom file name for the file or folder in local destination * + If you don't need this param provide NULL as value * @param appData Custom app data to save in the MegaTransfer object * The data in this parameter can be accessed using MegaTransfer::getAppData in callbacks * related to the transfer. * + If you don't need this param provide NULL as value * @param startFirst puts the transfer on top of the download queue * + If you don't need this param provide false as value * @param cancelToken MegaCancelToken to be able to cancel a folder/file download process. * This param is required to be able to cancel transfers safely. * App retains the ownership of this param. * @param collisionCheck Indicates the collision check on same files, valid values are: * - MegaTransfer::COLLISION_CHECK_ASSUMESAME = 1, * - MegaTransfer::COLLISION_CHECK_ALWAYSERROR = 2, * - MegaTransfer::COLLISION_CHECK_FINGERPRINT = 3, * - MegaTransfer::COLLISION_CHECK_METAMAC = 4, * - MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT = 5, * * @param collisionResolution Indicates how to save same files, valid values are: * - MegaTransfer::COLLISION_RESOLUTION_OVERWRITE = 1, * - MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N = 2, * - MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN = 3, * * @param undelete Indicates a special request for a node that has been completely deleted * (even from Rubbish Bin); allowed only for accounts with PRO level * * @param listener MegaTransferListener to track this transfer */ void startDownload(MegaNode* node, const char* localPath, const char *customName, const char *appData, bool startFirst, MegaCancelToken *cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener *listener = NULL); /** * @brief Start an streaming download for a file in MEGA * * Streaming downloads don't save the downloaded data into a local file. It is provided * in MegaTransferListener::onTransferUpdate in a byte buffer. The pointer is returned by * MegaTransfer::getLastBytes and the size of the buffer in MegaTransfer::getDeltaSize * * The same byte array is also provided in the callback MegaTransferListener::onTransferData for * compatibility with other programming languages. Only the MegaTransferListener passed to this function * will receive MegaTransferListener::onTransferData callbacks. MegaTransferListener objects registered * with MegaApi::addTransferListener won't receive them for performance reasons * * If the status of the business account is expired, onTransferFinish will be called with the error * code MegaError::API_EBUSINESSPASTDUE. In this case, apps should show a warning message similar to * "Your business account is overdue, please contact your administrator." * * @param node MegaNode that identifies the file * @param startPos First byte to download from the file * @param size Size of the data to download * @param listener MegaTransferListener to track this transfer */ void startStreaming(MegaNode* node, int64_t startPos, int64_t size, MegaTransferListener *listener); /** * @brief Set the miniumum acceptable streaming speed for streaming transfers * * When streaming a file with startStreaming(), the SDK monitors the transfer rate. * After a few seconds grace period, the monitoring starts. If the average rate is below * the minimum rate specified (determined by this function, or by default a reasonable rate * for audio/video, then the streaming operation will fail with MegaError::API_EAGAIN. * * @param bytesPerSecond The minimum acceptable rate for streaming. * Use -1 to use the default built into the library. * Use 0 to prevent the check. */ void setStreamingMinimumRate(int bytesPerSecond); /** * @brief Cancel a transfer * * When a transfer is cancelled, it will finish and will provide the error code * MegaError::API_EINCOMPLETE in MegaTransferListener::onTransferFinish and * MegaListener::onTransferFinish * * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the cancelled transfer (MegaTransfer::getTag) * * @param transfer MegaTransfer object that identifies the transfer * You can get this object in any MegaTransferListener callback or any MegaListener callback * related to transfers. * * @param listener MegaRequestListener to track this request */ void cancelTransfer(MegaTransfer *transfer, MegaRequestListener *listener = NULL); /** * @brief Retry a transfer * * This function allows to start a transfer based on a MegaTransfer object. It can be used, * for example, to retry transfers that finished with an error. To do it, you can retain the * MegaTransfer object in onTransferFinish (calling MegaTransfer::copy to take the ownership) * and use it later with this function. * * If the transfer parameter is NULL or is not of type MegaTransfer::TYPE_DOWNLOAD or * MegaTransfer::TYPE_UPLOAD (transfers started with MegaApi::startDownload or * MegaApi::startUpload) the function returns without doing anything. * * Note that retried transfers will always start first, so the user see them progressing * immediately. * * @param transfer Transfer to be retried * @param listener MegaTransferListener to track this transfer */ void retryTransfer(MegaTransfer *transfer, MegaTransferListener *listener = NULL); /** * @brief Move a transfer one position up in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferUp(MegaTransfer *transfer, MegaRequestListener *listener = NULL); /** * @brief Move a transfer one position up in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_UP * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferUpByTag(int transferTag, MegaRequestListener *listener = NULL); /** * @brief Move a transfer one position down in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferDown(MegaTransfer *transfer, MegaRequestListener *listener = NULL); /** * @brief Move a transfer one position down in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_DOWN * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferDownByTag(int transferTag, MegaRequestListener *listener = NULL); /** * @brief Move a transfer to the top of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferToFirst(MegaTransfer *transfer, MegaRequestListener *listener = NULL); /** * @brief Move a transfer to the top of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_TOP * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferToFirstByTag(int transferTag, MegaRequestListener *listener = NULL); /** * @brief Move a transfer to the bottom of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transfer Transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferToLast(MegaTransfer *transfer, MegaRequestListener *listener = NULL); /** * @brief Move a transfer to the bottom of the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns true (it means that it's an automatic move) * - MegaRequest::getNumber - Returns MegaTransfer::MOVE_TYPE_BOTTOM * * @param transferTag Tag of the transfer to move * @param listener MegaRequestListener to track this request */ void moveTransferToLastByTag(int transferTag, MegaRequestListener *listener = NULL); /** * @brief Move a transfer before another one in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transfer Transfer to move * @param prevTransfer Transfer with the target position * @param listener MegaRequestListener to track this request */ void moveTransferBefore(MegaTransfer *transfer, MegaTransfer *prevTransfer, MegaRequestListener *listener = NULL); /** * @brief Move a transfer before another one in the transfer queue * * If the transfer is successfully moved, onTransferUpdate will be called * for the corresponding listeners of the moved transfer and the new priority * of the transfer will be available using MegaTransfer::getPriority * * The associated request type with this request is MegaRequest::TYPE_MOVE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to move * - MegaRequest::getFlag - Returns false (it means that it's a manual move) * - MegaRequest::getNumber - Returns the tag of the transfer with the target position * * @param transferTag Tag of the transfer to move * @param prevTransferTag Tag of the transfer with the target position * @param listener MegaRequestListener to track this request */ void moveTransferBeforeByTag(int transferTag, int prevTransferTag, MegaRequestListener *listener = NULL); /** * @brief Cancel the transfer with a specific tag * * When a transfer is cancelled, it will finish and will provide the error code * MegaError::API_EINCOMPLETE in MegaTransferListener::onTransferFinish and * MegaListener::onTransferFinish * * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the cancelled transfer (MegaTransfer::getTag) * * @param transferTag tag that identifies the transfer * You can get this tag using MegaTransfer::getTag * * @param listener MegaRequestListener to track this request */ void cancelTransferByTag(int transferTag, MegaRequestListener *listener = NULL); /** * @brief Cancel all transfers of the same type * * The associated request type with this request is MegaRequest::TYPE_CANCEL_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the first parameter * * @param type Type of transfers to cancel. * Valid values are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * * @param listener MegaRequestListener to track this request */ void cancelTransfers(int type, MegaRequestListener *listener = NULL); /** * @brief Pause/resume all transfers * * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * * @param pause true to pause all transfers / false to resume all transfers * @param listener MegaRequestListener to track this request */ void pauseTransfers(bool pause, MegaRequestListener* listener = NULL); /** * @brief Pause/resume all transfers in one direction (uploads or downloads) * * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFERS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns the first parameter * - MegaRequest::getNumber - Returns the direction of the transfers to pause/resume * * @param pause true to pause transfers / false to resume transfers * @param direction Direction of transfers to pause/resume * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * * @param listener MegaRequestListener to track this request */ void pauseTransfers(bool pause, int direction, MegaRequestListener* listener = NULL); /** * @brief Pause/resume a transfer * * The request finishes with MegaError::API_OK if the state of the transfer is the * desired one at that moment. That means that the request succeed when the transfer * is successfully paused or resumed, but also if the transfer was already in the * desired state and it wasn't needed to change anything. * * Resumed transfers don't necessarily continue just after the resumption. They * are tagged as queued and are processed according to its position on the request queue. * * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to pause or resume * - MegaRequest::getFlag - Returns true if the transfer has to be pause or false if it has to be resumed * * @param transfer Transfer to pause or resume * @param pause True to pause the transfer or false to resume it * @param listener MegaRequestListener to track this request */ void pauseTransfer(MegaTransfer *transfer, bool pause, MegaRequestListener* listener = NULL); /** * @brief Pause/resume a transfer * * The request finishes with MegaError::API_OK if the state of the transfer is the * desired one at that moment. That means that the request succeed when the transfer * is successfully paused or resumed, but also if the transfer was already in the * desired state and it wasn't needed to change anything. * * Resumed transfers don't necessarily continue just after the resumption. They * are tagged as queued and are processed according to its position on the request queue. * * The associated request type with this request is MegaRequest::TYPE_PAUSE_TRANSFER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTransferTag - Returns the tag of the transfer to pause or resume * - MegaRequest::getFlag - Returns true if the transfer has to be pause or false if it has to be resumed * * @param transferTag Tag of the transfer to pause or resume * @param pause True to pause the transfer or false to resume it * @param listener MegaRequestListener to track this request */ void pauseTransferByTag(int transferTag, bool pause, MegaRequestListener* listener = NULL); /** * @brief Returns the state (paused/unpaused) of transfers * @param direction Direction of transfers to check * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * * @return true if transfers on that direction are paused, false otherwise */ bool areTransfersPaused(int direction); /** * @brief Resume incomplete transfers started while not logged in * * This method resumes transfers that were cached while using a non-logged-in MegaApi * instance * * This method can be called when the app detects that there is no session to resume. * If a valid session exists, the app should proceed with resuming it, and calling * this method will have no effect. * * @note If there are transfers in progress and the app logs in, * any incomplete transfers will be aborted immediately. * * Please avoid calling this method when logged in. */ void resumeTransfersForNotLoggedInInstance(); /** * @deprecated This version of the function is deprecated. Please, use \c setMaxUploadSpeed. */ MEGA_DEPRECATED void setUploadLimit(int bpslimit); /** * @brief Set the maximum number of connections per transfer * * The maximum number of allowed connections is 100. If a higher number of connections is * passed to this function, it will fail with the error code API_ETOOMANY. * * The associated request type with this request is MegaRequest::TYPE_SET_MAX_CONNECTIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value for \c direction parameter * - MegaRequest::getNumber - Returns the number of \c connections * * @param direction Direction of transfers * Valid values for this parameter are: * - MegaTransfer::TYPE_DOWNLOAD = 0 * - MegaTransfer::TYPE_UPLOAD = 1 * @param connections Maximum number of connection (it should between 1 and 100) */ void setMaxConnections(int direction, int connections, MegaRequestListener* listener = NULL); /** * @brief Set the maximum number of connections per transfer for downloads and uploads * * The maximum number of allowed connections is 100. If a higher number of connections is * passed to this function, it will fail with the error code API_ETOOMANY. * * The associated request type with this request is MegaRequest::TYPE_SET_MAX_CONNECTIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of connections * * @param connections Maximum number of connection (it should between 1 and 100) */ void setMaxConnections(int connections, MegaRequestListener* listener = NULL); /** * @brief Get the maximum number of connections per upload transfer. * * The associated request type with this request is MegaRequest::TYPE_GET_MAX_CONNECTIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value for transfer direction (PUT) * - MegaRequest::getNumber - Returns the max number of connections for uploads. * * Possible return values for this function are: * - MegaError::API_OK if successfully retrieved the max number of connections for uploads * - MegaError::API_EINTERNAL if there was an internal issue when setting the transfer * direction. */ void getMaxUploadConnections(MegaRequestListener* const listener); /** * @brief Get the maximum number of connections per download transfer. * * The associated request type with this request is MegaRequest::TYPE_GET_MAX_CONNECTIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value for transfer direction (GET) * - MegaRequest::getNumber - Returns the max number of connections for downloads. * * Possible return values for this function are the same ones as getMaxUploadConnections() */ void getMaxDownloadConnections(MegaRequestListener* const listener); /** * @brief Set the transfer method for downloads * * Valid methods are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. * * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. * * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically * * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. * * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @param method Selected transfer method for downloads */ void setDownloadMethod(int method); /** * @brief Set the transfer method for uploads * * Valid methods are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. * * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. * * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically * * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. * * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @param method Selected transfer method for uploads */ void setUploadMethod(int method); /** * @brief Set the maximum download speed in bytes per second * * You can check if the function will have effect * by checking the return value. If it's true, the value will be applied. Otherwise, * this function returns false. * * A value <= 0 means unlimited speed * * @param bpslimit Download speed in bytes per second * @return true if the network layer allows to control the download speed, otherwise false */ bool setMaxDownloadSpeed(long long bpslimit); /** * @brief Set the maximum upload speed in bytes per second * * You can check if the function will have effect * by checking the return value. If it's true, the value will be applied. Otherwise, * this function returns false. * * A value <= 0 means unlimited speed * * @param bpslimit Upload speed in bytes per second * @return true if the network layer allows to control the upload speed, otherwise false */ bool setMaxUploadSpeed(long long bpslimit); /** * @brief Get the maximum download speed in bytes per second * * The value 0 means unlimited speed * * @return Download speed in bytes per second */ int getMaxDownloadSpeed(); /** * @brief Get the maximum upload speed in bytes per second * * The value 0 means unlimited speed * * @return Upload speed in bytes per second */ int getMaxUploadSpeed(); /** * @brief Return the current download speed * @return Download speed in bytes per second */ int getCurrentDownloadSpeed(); /** * @brief Return the current download speed * @return Download speed in bytes per second */ int getCurrentUploadSpeed(); /** * @brief Return the current transfer speed * @param type Type of transfer to get the speed. * Valid values are MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * @return Transfer speed for the transfer type, or 0 if the parameter is invalid */ int getCurrentSpeed(int type); /** * @brief Get the active transfer method for downloads * * Valid values for the return parameter are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. * * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. * * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically * * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. * * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @return Active transfer method for downloads */ int getDownloadMethod(); /** * @brief Get the active transfer method for uploads * * Valid values for the return parameter are: * - TRANSFER_METHOD_NORMAL = 0 * HTTP transfers using port 80. Data is already encrypted. * * - TRANSFER_METHOD_ALTERNATIVE_PORT = 1 * HTTP transfers using port 8080. Data is already encrypted. * * - TRANSFER_METHOD_AUTO = 2 * The SDK selects the transfer method automatically * * - TRANSFER_METHOD_AUTO_NORMAL = 3 * The SDK selects the transfer method automatically starting with port 80. * * - TRANSFER_METHOD_AUTO_ALTERNATIVE = 4 * The SDK selects the transfer method automatically starting with alternative port 8080. * * @return Active transfer method for uploads */ int getUploadMethod(); /** * @brief Get information about transfer queues * * You take the ownership of the returned value. * * @param listener MegaTransferListener to start receiving information about transfers * @return Information about transfer queues */ MegaTransferData *getTransferData(MegaTransferListener *listener = NULL); /** * @brief Get the first transfer in a transfer queue * * You take the ownership of the returned value. * * @param type Transfer queue to get the first transfer (MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD) * @return MegaTransfer object related to the first transfer in the queue or NULL if there isn't any transfer */ MegaTransfer *getFirstTransfer(int type); /** * @brief Force an onTransferUpdate callback for the specified transfer * * The callback will be received by transfer listeners registered to receive all * callbacks related to callbacks and additionally by the listener in the last * parameter of this function, if it's not NULL. * * @param transfer Transfer that will be provided in the onTransferUpdate callback * @param listener Listener that will receive the callback */ void notifyTransfer(MegaTransfer *transfer, MegaTransferListener *listener = NULL); /** * @brief Force an onTransferUpdate callback for the specified transfer * * The callback will be received by transfer listeners registered to receive all * callbacks related to callbacks and additionally by the listener in the last * parameter of this function, if it's not NULL. * * @param transferTag Tag of the transfer that will be provided in the onTransferUpdate callback * @param listener Listener that will receive the callback */ void notifyTransferByTag(int transferTag, MegaTransferListener *listener = NULL); /** * @brief Get all active transfers * * You take the ownership of the returned value * * @return List with all active transfers * @see MegaApi::startUpload, MegaApi::startDownload */ MegaTransferList *getTransfers(); /** * @brief Get all active streaming transfers * * You take the ownership of the returned value * * @return List with all active streaming transfers * @see MegaApi::startStreaming */ MegaTransferList *getStreamingTransfers(); /** * @brief Get the transfer with a unique id * * That unique identifier of a transfer can be retrieved using MegaTransfer::getUniqueId * * You take the ownership of the returned value * * @param transferUniqueId unique Id to find * @return MegaTransfer object with that unique Id, or nullptr if there isn't any * active transfer with it * */ MegaTransfer* getTransferByUniqueId(uint32_t transferUniqueId) const; /** * @brief Get the transfer with a transfer tag * * That tag can be got using MegaTransfer::getTag * * You take the ownership of the returned value * * @param transferTag tag to check * @return MegaTransfer object with that tag, or NULL if there isn't any * active transfer with it * */ MegaTransfer* getTransferByTag(int transferTag); /** * @brief Get all transfers of a specific type (downloads or uploads) * * If the parameter isn't MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * this function returns an empty list. * * You take the ownership of the returned value * * @param type MegaTransfer::TYPE_DOWNLOAD or MegaTransfer::TYPE_UPLOAD * @return List with transfers of the desired type */ MegaTransferList *getTransfers(int type); /** * @brief Get a list of transfers that belong to a folder transfer * * This function provides the list of transfers started in the context * of a folder transfer. * * If the tag in the parameter doesn't belong to a folder transfer, * this function returns an empty list. * * The transfers provided by this function are the ones that are added to the * transfer queue when this function is called. Finished transfers, or transfers * not added to the transfer queue yet (for example, uploads that are waiting for * the creation of the parent folder in MEGA) are not returned by this function. * * You take the ownership of the returned value * * @param transferTag Tag of the folder transfer to check * @return List of transfers in the context of the selected folder transfer * @see MegaTransfer::isFolderTransfer, MegaTransfer::getFolderTransferTag */ MegaTransferList *getChildTransfers(int transferTag); /** * @brief Returns the folder paths of a backup * * You take ownership of the returned value. * * @param backuptag backup tag * @return Folder paths that contain each of the backups or NULL if tag not found. */ MegaStringList *getBackupFolders(int backuptag) const; /** * @brief Starts a backup of a local folder into a remote location * * Determined by the selected period several backups will be stored in the selected location * If a backup with the same local folder and remote location exists, its parameters will be updated * * The associated request type with this request is MegaRequest::TYPE_ADD_SCHEDULED_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the period between backups in deciseconds (-1 if cron time used) * - MegaRequest::getText - Returns the cron like time string to define period * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getNumRetry - Returns the maximun number of backups to keep * - MegaRequest::getTransferTag - Returns the tag asociated with the backup * - MegaRequest::getFlag - Returns whether to attend past backups (ocurred while not running) * * * @param localPath Local path of the folder * @param parent MEGA folder to hold the backups * @param attendPastBackups attend backups that ought to have started before * @param period period between backups in deciseconds * @param periodstring cron like time string to define period * @param numBackups maximun number of backups to keep * @param listener MegaRequestListener to track this request * */ void setScheduledCopy(const char* localPath, MegaNode *parent, bool attendPastBackups, int64_t period, const char *periodstring, int numBackups, MegaRequestListener *listener=NULL); /** * @brief Remove a backup * * The backup will stop being performed. No files in the local nor in the remote folder * will be deleted due to the usage of this function. * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SCHEDULED_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the tag of the deleted backup * * @param tag tag of the backup to delete * @param listener MegaRequestListener to track this request */ void removeScheduledCopy(int tag, MegaRequestListener *listener=NULL); /** * @brief Aborts current ONGOING backup. * * This will cancell all current active backups. * * The associated request type with this request is MegaRequest::TYPE_ABORT_CURRENT_SCHEDULED_COPY * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the tag of the aborted backup * * Possible return values for this function are: * - MegaError::API_OK if successfully aborted an ongoing backup * - MegaError::API_ENOENT if backup could not be found or no ongoing backup found * * @param tag tag of the backup to delete */ void abortCurrentScheduledCopy(int tag, MegaRequestListener *listener=NULL); /** * @brief Starts a timer. * * This, besides the classic timer usage, can be used to enforce a loop of the SDK thread when the time passes * * The associated request type with this request is MegaRequest::TYPE_TIMER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the selected period * An OnRequestFinish will be caled when the time is passed * * @param period time to wait * @param listener MegaRequestListener to track this request * */ void startTimer(int64_t period, MegaRequestListener *listener = NULL); #ifdef ENABLE_SYNC /////////////////// SYNCHRONIZATION /////////////////// /** * @brief Get the synchronization state of a local file * @param Path of the local file * @return Synchronization state of the local file. * Valid values are: * - STATE_NONE = 0 * The file isn't inside a synced folder * * - MegaApi::STATE_SYNCED = 1 * The file is in sync with the MEGA account * * - MegaApi::STATE_PENDING = 2 * The file is pending to be synced with the MEGA account * * - MegaApi::STATE_SYNCING = 3 * The file is being synced with the MEGA account * * - MegaApi::STATE_IGNORED = 4 * The file is inside a synced folder, but it is ignored * by the selected exclusion filters * */ int syncPathState(std::string *path); /** * @brief Get the MegaNode associated with a local synced file * * You take ownership of the returned value. * * @param path Local path of the file * @return The same file in MEGA or NULL if the file isn't synced */ MegaNode *getSyncedNode(std::string *path); /** * @brief Start a Sync or Backup between a local folder and a folder in MEGA * * Check the syncFolder() function below for the full documentation. * * @param excludePath deprecated parameter, never used. * * @deprecated This function is deprecated. Please don't use it in new code. Use the one * below. It's the same one, but without the unused excludePath and with std types. */ void syncFolder(const MegaSync::SyncType syncType, const char* localSyncRootFolder, const char* name, const MegaHandle remoteSyncRootFolder, const char* driveRootIfExternal, MegaRequestListener* const listener, const char* excludePath = nullptr); /** * @brief Start a Sync or Backup between a local folder and a folder in MEGA * * This function should be used to add a new synchronization/backup task for the MegaApi. * To resume a previously configured task folder, use MegaApi::enableSync. * * Both TYPE_TWOWAY and TYPE_BACKUP are supported for the first parameter. * * The sync/backup's name is optional. If not provided, it will take the name of the leaf * folder of the local path. In example, for "/home/user/Documents", it will become * "Documents". * * The remote sync root folder should be INVALID_HANDLE for syncs of TYPE_BACKUP. The handle * of the remote node, which is created as part of this request, will be set to the * MegaRequest::getNodeHandle. * * The associated request type with this request is MegaRequest::TYPE_ADD_SYNC * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the folder in MEGA * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getName - Returns the name of the sync * - MegaRequest::getParamType - Returns the type of the sync * - MegaRequest::getLink - Returns the drive root if external backup * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * - MegaRequest::getNumDetails - If different than NO_SYNC_ERROR, it returns additional * info for the specific sync error (MegaSync::Error). It could happen both when the request * has succeeded (API_OK) and also in some cases of failure, when the request error is not * accurate enough. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is other than MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the sync backupId * * On the onRequestFinish error, the error code associated to the MegaError * (MegaError::getErrorCode()) can be: * - MegaError::API_EARGS - If the local folder was not set or is not a folder. * - MegaError::API_EACCESS - If the user was invalid, or did not have an attribute for "My * Backups" folder, or the attribute was invalid, or "My Backups"/`DEVICE_NAME` existed but * was not a folder, or it had the wrong 'dev-id'/'drv-id' tag. * - MegaError::API_EINTERNAL - If the user attribute for "My Backups" folder did not have a * record containing the handle. * - MegaError::API_ENOENT - If the handle of "My Backups" folder contained in the user * attribute was invalid * - or the node could not be found. * - MegaError::API_EINCOMPLETE - If device id was not set, or if current user did not have * an attribute for device name, or the attribute was invalid, or the attribute did not * contain a record for the device name, or device name was empty. * - MegaError::API_EEXIST - If this is a new device, but a folder with the same device-name * already exists. * * The MegaError can also contain a SyncError (MegaError::getSyncError()), with the same * value as MegaRequest::getNumDetails() See MegaApi::isNodeSyncableWithError() for specific * SyncError codes depending on the specific MegaError code. * * @param syncType Type of sync. Currently supported: TYPE_TWOWAY and TYPE_BACKUP. * @param localSyncRootFolder Path of the Local folder to sync/backup. * @param name Name given to the sync. You can pass NULL, and the folder name will be used * instead. * @param remoteSyncRootFolder Handle of MEGA folder. If you have a MegaNode for that * folder, use its getHandle() * @param driveRootIfExternal Only relevant for backups, and only if the backup is on an * external disk. Otherwise use an empty string. * @param listener MegaRequestListener to track this request */ void syncFolder(const MegaSync::SyncType syncType, const std::string& localSyncRootFolder, const std::string& name, const MegaHandle remoteSyncRootFolder, const std::string& driveRootIfExternal, MegaRequestListener* const listener); /** * @brief Prevalidates a Sync or Backup addition between a local folder and a folder in * MEGA. * * This function could be used to pre-check most of the typical validations that would take * place when calling MegaApi::syncFolder. This function does not create the sync, nor a * related sync config would exist afterwards. * * Most of the documentation of MegaApi::syncFolder applies to this method, with the * following differences: * * The associated request type with this request is MegaRequest::TYPE_ADD_SYNC_PREVALIDATION * * The handle of the remote node for syncs of TYPE_BACKUP is temporarily created as part of * this request for validation purposes, so it will NOT be set to the * MegaRequest::getNodeHandle. This method would return an undefined handle after the call. * Moreover, for syncs of TYPE_BACKUP, the device name is created as part of the validation * if it didn't exist before. It will not be cleared after the call. * * The final difference is that there is no additional data received in onRequestFinish: no * fingerprint or backupID, as the ID is never generated. */ void prevalidateSyncFolder(const MegaSync::SyncType syncType, const std::string& localSyncRootFolder, const std::string& name, const MegaHandle remoteSyncRootFolder, const std::string& driveRootIfExternal, MegaRequestListener* const listener); /** * @deprecated This version of the function is deprecated. It results on API_EINTERNAL. */ MEGA_DEPRECATED void copySyncDataToCache(const char *localFolder, const char *name, MegaHandle megaHandle, const char *remotePath, long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener *listener = NULL); /** * @deprecated This version of the function is deprecated. It results on API_EINTERNAL. */ MEGA_DEPRECATED void copySyncDataToCache(const char *localFolder, MegaHandle megaHandle, const char *remotePath, long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener *listener = NULL); /** * @deprecated This version of the function is deprecated. It results on API_EINTERNAL. */ MEGA_DEPRECATED void copyCachedStatus(int storageStatus, int blockStatus, int businessStatus, MegaRequestListener *listener = NULL); /** * @brief Check the external drive specified to see if it has any Backup Syncs set up on it. * * If any are present, their configurations will be loaded, in a disabled state. * External Backups will overwrite whatever is in the corrsponding cloud folder, making it the same on disk. * Therefore you must check with the user if they wish to resume any or all of them, otherwise they may lose data. * You can find the one added by iterating all syncs and checking the external drive path for each. * For those that the user wishes to resume, use MegaApi::enableSync() * * The associated request type with this request is MegaRequest::TYPE_LOAD_EXTERNAL_BACKUPS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the path of the drive root * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - The MegaSync records have been added. You can access them with MegaApi::getSyncs() * and check each one's drive path. * * @param externalDriveRoot The filesystem path to the root of the external drive. * @param listener MegaRequestListener to track this request * */ void loadExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener); /** * @brief Prepare any external backup syncs on the specified drive, for that drive be ejected/disconnected * * If any are present and active, the sync activity is stopped, any changes to sync/backup config for * those backups is flushed to disk,and all assoicated file/folder handles are closed, allowing the * drive to be ejected. All backup sync configs from that drive are removed from memory. * * This function may also be useful if the user just prefers not to have any sync/backup activity * going on ont that disk. * * The associated request type with this request is MegaRequest::TYPE_LOAD_EXTERNAL_BACKUPS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the path of the drive root * * @param externalDriveRoot The filesystem path to the root of the external drive. * @param listener MegaRequestListener to track this request * */ void closeExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener); /** * @brief De-configure the sync/backup of a folder * * The folder will stop being synced. No files in the local nor in the remote folder * will be deleted due to the usage of this function. * * The synchronization will stop and the local sync database will be deleted * The backupId of this sync will be invalid going forward. * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SYNC * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns sync backupId * - MegaRequest::getFlag - Returns true * - MegaRequest::getFile - Returns the path of the local folder (for active syncs only) * * @param backupId Identifier of the Sync (unique per user, provided by API) * @param listener MegaRequestListener to track this request */ void removeSync(MegaHandle backupId, MegaRequestListener *listener = NULL); /** * @brief Run/Pause/Suspend/Disable a synced folder * * Attempt to Start, Pause, Suspend, or Disable a sync. * * Specify the new state to try to move the sync to. In case there is * a problem, the cause will be returned in the listener callabck. * * The associated request type with this request is MegaRequest::TYPE_SET_SYNC_RUNSTATE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns sync backupId * * @param backupId Identifier of the Sync (unique per user, provided by API) * @param listener MegaRequestListener to track this request */ void setSyncRunState(MegaHandle backupId, MegaSync::SyncRunningState targetState, MegaRequestListener *listener = NULL); /** * @brief Cause one or all syncs' local folder tree to be rescanned * * The scanning will start, and the usual scanning callbacks notify * about when scanning is going on, or is resolved. * * @param backupId Identifier of the single Sync, or INVALID_HANDLE to rescan all. * @param reFingerprint If true, files on disk will be re-fingerprinted in case of changes not detectable by mtime etc */ void rescanSync(MegaHandle backupId, bool reFingerprint); /** * @brief * Imports internal sync configs from JSON. * * @param configs * A JSON string encoding the internal sync configs to import. * * @param listener * Listener to call back when the import has completed. * * @see exportSyncConfigs */ void importSyncConfigs(const char* configs, MegaRequestListener* listener); /** * @brief * Exports all internal sync configs to JSON. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return * A JSON string encoding all internal sync configs. * * @see importSyncConfigs */ const char* exportSyncConfigs(); /** * @brief Get all configured syncs * * You take the ownership of the returned value * * @return List of MegaSync objects with all syncs */ MegaSyncList* getSyncs(); /** * @brief Check if the synchronization engine is scanning files * @return true if it is scanning, otherwise false */ bool isScanning(); /** * @brief Check if any synchronization is in state syncing or pending * @return true if it is syncing, otherwise false */ bool isSyncing(); /** * @deprecated This function is deprecated and it will be removed in future updates. */ MEGA_DEPRECATED void setLegacyExcludedNames(std::vector* excludedNames); /** * @deprecated This function is deprecated and it will be removed in future updates. */ MEGA_DEPRECATED void setLegacyExcludedPaths(std::vector* excludedPaths); /** * @deprecated This function is deprecated and it will be removed in future updates. */ MEGA_DEPRECATED void setLegacyExclusionLowerSizeLimit(unsigned long long limit); /** * @deprecated This function is deprecated and it will be removed in future updates. */ MEGA_DEPRECATED void setLegacyExclusionUpperSizeLimit(unsigned long long limit); /** * @deprecated This function is deprecated and it will be removed in future updates. */ MEGA_DEPRECATED MegaError* exportLegacyExclusionRules(const char* absolutePath); /** * @brief Check if it's possible to start synchronizing a folder node. * * Possible return values for this function are: * - MegaError::API_OK if the folder is syncable * - MegaError::API_ENOENT if the node doesn't exist in the account * - MegaError::API_EARGS if the node is NULL or is not a folder * - MegaError::API_EACCESS if the node doesn't have full access * - MegaError::API_EEXIST if there is a conflicting synchronization (nodes can't be synced twice) * - MegaError::API_EINCOMPLETE if the SDK hasn't been built with support for synchronization * * @param Folder node to check * @return MegaError::API_OK if the node is syncable, otherwise it returns an error. */ int isNodeSyncable(MegaNode *node); /** * @brief Check if it's possible to start synchronizing a folder node. * * Return MegaError codes (MegaError::getErrorCode()) and SyncError codes (MegaError::getSyncError()). * * Possible return values for this function are: * - MegaError::API_OK if the folder is syncable * - MegaError::API_ENOENT if the node doesn't exist in the account * - MegaError::API_EARGS if the node is NULL or is not a folder * * - MegaError::API_EACCESS: * SyncError: SHARE_NON_FULL_ACCESS An ancestor node does not have full access * SyncError: REMOTE_NODE_INSIDE_RUBBISH * - MegaError::API_EEXIST if there is a conflicting synchronization (nodes can't be synced twice) * SyncError: ACTIVE_SYNC_BELOW_PATH - There's a synced node below the path to be synced * SyncError: ACTIVE_SYNC_ABOVE_PATH - There's a synced node above the path to be synced * SyncError: ACTIVE_SYNC_SAME_PATH - There's a synced node at the path to be synced * - MegaError::API_EINCOMPLETE if the SDK hasn't been built with support for synchronization * * You take the ownership of the returned value. * * @return API_OK if syncable. Error otherwise sets syncError in the returned MegaError * caller must free */ MegaError* isNodeSyncableWithError(MegaNode* node); /** * @brief Get the synchronization identified with a backupId * * You take the ownership of the returned value * * @param backupId Identifier of the Sync (unique per user, provided by API) * @return Synchronization identified by the backupId */ MegaSync *getSyncByBackupId(MegaHandle backupId); /** * @brief getSyncByNode Get the synchronization associated with a node * * You take the ownership of the returned value * * @param node Root node of the synchronization * @return Synchronization with the specified root node */ MegaSync *getSyncByNode(MegaNode *node); /** * @brief getSyncByPath Get the synchronization associated with a local path * * You take the ownership of the returned value * * @param localPath Root local path of the synchronization * @return Synchronization with the specified root local path */ MegaSync *getSyncByPath(const char *localPath); /** * @brief Get the total number of local nodes in the account * @return Total number of local nodes in the account */ long long getNumLocalNodes(); /** * @brief Query the sync engine to find out what is causing sync stalls * * The associated request type with this request is MegaRequest::TYPE_GET_SYNC_STALL_LIST. * * Valid data in the MegaRequest object received in onRequestFinish when the error code is * MegaError::API_OK: * - MegaRequest::getMegaSyncStallList - Returns a List of synchronization stall conflicts * * @param listener A MegaRequestListener with which to track the request. * */ void getMegaSyncStallList(MegaRequestListener* listener); /** * @brief Query the sync engine to find out what is causing sync stalls * * The associated request type with this request is MegaRequest::TYPE_GET_SYNC_STALL_LIST. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns true * * Valid data in the MegaRequest object received in onRequestFinish when the error code is * MegaError::API_OK: * - MegaRequest::getMegaSyncStallMap - Returns a Map of BackupId to synchronization stall * conflicts list * * @param listener A MegaRequestListener with which to track the request. * */ void getMegaSyncStallMap(MegaRequestListener* listener); /** * @brief * Clear a stall item in case the user Refreshes the list again * before the sync code has re-iterated the tree to generate a new list * * @param stallCloudPath * The stall item (or a copy of it) that was addressed but for which the * sync code might not have noticed yet. */ void clearStalledPath(MegaSyncStall* originalStall); /** * @brief Move local path to sync debris folder * * The associated request type with this request is MegaRequest::TYPE_MOVE_TO_DEBRIS. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns local path. * - MegaRequest::getNodeHandle - Returns sync backup Id * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Invalid path or backup id * - MegaError::API_ENOENT - There is no sync with this id * - MegaError::API_EINTERNAL - failure moving to sync debris * * @param path local path * @param syncBackupId handle to the sync * @param listener MegaRequestListener to track this request */ void moveToDebris(const char* path, MegaHandle syncBackupId, MegaRequestListener* listener = nullptr); /** * @brief Change the node that is being used as remote root for a sync. * * @important If the sync is active when executing this method, it will be temporary * suspended to perform the change, meaning that any ongoing transfer will be automatically * stopped. * * @note This operation is only allowed with syncs of TYPE_TWOWAY. * * The associated request type with this request is MegaRequest::TYPE_CHANGE_SYNC_ROOT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the handle of the new remote root in MEGA * - MegaRequest::getNodeHandle - Returns the affected sync backupId * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * - MegaRequest::getNumDetails - If different than NO_SYNC_ERROR, it returns additional * info about the specific sync error (MegaSync::Error). It could happen both when the * request has succeeded (API_OK) and also in some cases of failure, when the request error * is not accurate enough. * * On the onRequestFinish callback, the error code associated with the MegaError * (MegaError::getErrorCode()) and the SyncError (if relevant, MegaError::getSyncError()) * can be: * - MegaError::API_OK: * + SyncError::NO_SYNC_ERROR the new root has been changed successfully * - MegaError::API_EARGS: * + SyncError::NO_SYNC_ERROR The given syncBackupId or newRootNodeHandle are UNDEF * + SyncError::REMOTE_NODE_NOT_FOUND The given newRootNodeHandle does not map to an * existing node in the cloud * + SyncError::UNKNOWN_ERROR The given syncBackupId does not map to an existing two way * sync * - MegaError::API_EWRITE: * + SyncError::SYNC_CONFIG_WRITE_FAILURE We couldn't write into the database to commit * the change. * - MegaError::API_EEXISTS: * + SyncError::UNKNOWN_ERROR the given newRootNodeHandle matches with the one that is * already the root of the sync * * Additionally, error codes associated to the MegaApi::isNodeSyncableWithError() method * can also be reported by this method. See MegaApi::isNodeSyncableWithError() for specific * SyncError codes depending on the specific MegaError code. * * @param syncBackupId handle of the sync to change its remote root * @param newRootNodeHandle Handle of the MEGA node to set as new sync remote root * @param listener MegaRequestListener to track this request */ void changeSyncRemoteRoot(const MegaHandle syncBackupId, const MegaHandle newRootNodeHandle, MegaRequestListener* listener = nullptr); /** * @brief Change the local path that is being used as root for a sync. * * The associated request type with this request is MegaRequest::TYPE_CHANGE_SYNC_ROOT. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFile - Returns the path of the new local folder to use as root * - MegaRequest::getNodeHandle - Returns the affected sync backup ID. * - MegaRequest::getListener - Returns the MegaRequestListener to track this request. * - MegaRequest::getNumDetails - If different than NO_SYNC_ERROR, it returns additional * info for the specific sync error (MegaSync::Error). This can occur both when the * request has succeeded (API_OK) and in some cases of failure when the request error is * not sufficiently descriptive. * * On the onRequestFinish callback, the error code associated with the MegaError * (MegaError::getErrorCode()) and the SyncError (if relevant, MegaError::getSyncError()) * can be: * - MegaError::API_OK: * + SyncError::NO_SYNC_ERROR the new root has been changed successfully * - MegaError::API_EARGS: * + SyncError::LOCAL_PATH_UNAVAILABLE the given path is nullptr or is empty * + SyncError::UNKNOWN_ERROR The given backupId does not match any of the registered * syncs * + SyncError::LOCAL_PATH_SYNC_COLLISION The local path conflicts with existing * synchronization paths (nested syncs are not allowed) * + SyncError::FILESYSTEM_ID_UNAVAILABLE unable to get the file system fingerprint with * the given path * + SyncError::LOCAL_FILESYSTEM_MISMATCH The given path is in a different file system * comparing with the previous one. We don't allow this operation * + SyncError::UNABLE_TO_RETRIEVE_ROOT_FSID The new root directory cannot be opened * + SyncError::BACKUP_SOURCE_NOT_BELOW_DRIVE The new root directory is not contained in * the previous external path. That operation is not allowed. * - MegaError::API_EWRITE: * + SyncError::SYNC_CONFIG_WRITE_FAILURE We couldn't write into the database to commit * the change. * - MegaError::API_EFAILED: * + SyncError::LOCAL_PATH_MOUNTED trying to sync bellow a FUSE mount point * + SyncError::UNSUPPORTED_FILE_SYSTEM the given path is in a not supported file system * - MegaError::API_ETEMPUNAVAIL: * + SyncError::LOCAL_PATH_TEMPORARY_UNAVAILABLE the given new path is temporarily * unavailable * - MegaError::API_ENOENT: * + SyncError::LOCAL_PATH_UNAVAILABLE the given new path is not available * - MegaError::API_EACCESS: * + SyncError::INVALID_LOCAL_TYPE the given path is not a directory * * @param syncBackupId The handle (backup ID) of the sync whose local root is to be changed. * @param newLocalSyncRootPath The new local path to set as the sync root. * @param listener A MegaRequestListener to track this request. This parameter is optional. */ void changeSyncLocalRoot(const MegaHandle syncBackupId, const char* newLocalSyncRootPath, MegaRequestListener* listener = nullptr); /** * @brief Set the throttle update rate for sync-uploads. * * @note The values set by this method will be overwritten upon resuming/starting * application, and every 24-hours runtime (Those values will be received from API). We * highly recommend to rely on API values instead of setting custom ones. * * The associated request type with this request is * MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the throttle update rate in seconds. * MegaError: * - MegaError::API_OK if the update rate was updated correctly. * - MegaError::API_EARGS if the update rate is below the minimum or above the maximum. * * @param updateRateInSeconds The update rate for the throttling queue in seconds. * @param listener A MegaRequestListener to track this request. */ void setSyncUploadThrottleUpdateRate(const unsigned updateRateInSeconds, MegaRequestListener* const listener); /** * @brief Set the max number of sync uploads per file before applying throttling logic. * * @note The values set by this method will be overwritten upon resuming/starting * application, and every 24-hours runtime (Those values will be received from API). We * highly recommend to rely on API values instead of setting custom ones. * * The associated request type with this request is * MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTotalBytes - Returns the max number of uploads before throttle. * MegaError: * - MegaError::API_OK if the new max number of uploads before throttle was set correctly. * - MegaError::API_EARGS if the max uploads before throttle value is below the minimum or * above the maximum. * * @param maxUploadsBeforeThrottle The number of uploads that are allowed to go unthrottled. * @param listener A MegaRequestListener to track this request. */ void setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, MegaRequestListener* const listener); /** * @brief Get the configurable throttle values for sync-uploads. * * The associated request type with this request is * MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the throttle update rate in seconds. * - MegaRequest::getTotalBytes - Returns the max number of uploads before throttle. * * @param listener A MegaRequestListener to track this request. */ void getSyncUploadThrottleValues(MegaRequestListener* const listener); /** * @brief Get the lower limits of configurable throttle for sync-uploads. * * The associated request type with this request is * MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the minimum allowed for throttle update rate in * seconds. * - MegaRequest::getTotalBytes - Returns the minimum allowed for max number of uploads * before throttle. * - MegaRequest::getFlag - Returns false (this is set to false/0 for lower limits). * * @param listener A MegaRequestListener to track this request. */ void getSyncUploadThrottleLowerLimits(MegaRequestListener* const listener); /** * @brief Get the upper limits of configurable throttle values for sync-uploads. * * The associated request type with this request is * MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the maximum allowed for throttle update rate in * seconds. * - MegaRequest::getTotalBytes - Returns the maximum allowed for max number of uploads * before throttle. * - MegaRequest::getFlag - Returns true (this is set to true/1 for upper limits). * * @param listener A MegaRequestListener to track this request. */ void getSyncUploadThrottleUpperLimits(MegaRequestListener* const listener); /** * @brief Check if there are delayed/throttled uploads pending to be processed. * When delayed/throttled uploads are pending, those files are not fully synchronized. In * that case, the sync state would be Syncing. * * The associated request type with this request is * MegaRequest::TYPE_CHECK_SYNC_UPLOAD_THROTTLED_ELEMENTS * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns true if there are delayed uploads waiting for * processing, false otherwise. * * @param listener A MegaRequestListener to track this request. * * @warning It is not guaranteed that the delayed uploads pending to be processed are valid. * They could be invalid if the files have been removed, or if the upload transfer has been * reset for other reasons. This is not checked until they are processed after the * throttlingUpdateRate time. The caller should consider this method as an extra check: if * the sync is in Syncing state, there are no stall issues nor MegaTransfers being processed * nor other situations explaining the Syncing state, it could be expected that there are * delayed uploads pending to be processed. Note that, if the delayed uploads are not valid * anymore (for example, because the underlying files have been removed), even when this * method could return true, the sync shouldn't be in Syncing state. */ void checkSyncUploadsThrottled(MegaRequestListener* const listener); #endif // ENABLE_SYNC /** * @brief Move or Remove the nodes that used to be part of backup. * * The folder must be in folder Vault//, and will be moved, or permanently deleted. * Deletion is permanent (not to trash) and is selected with destination INVALID_HANDLE. * To move the nodes instead, specify the destination folder in backupDestination. * * These nodes cannot be deleted with the usual remove() function as they are in the Vault. * * The associated request type with this request is * MegaRequest::TYPE_REMOVE_OLD_BACKUP_NODES. Valid data in the MegaRequest object received * on callbacks: * - MegaRequest::getNodeHandle - Returns the deconfiguredBackupRoot handle * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - deconfiguredBackupRoot was not valid * - MegaError::API_EARGS - deconfiguredBackupRoot was not in the Vault, * or backupDestination was not in Files or Rubbish * - MegaError::API_EEXIST - The destination already contains a node with the same name. * * @param deconfiguredBackupRoot Identifier of the Sync (unique per user, provided by API) * @param backupDestination If INVALID_HANDLE, files will be permanently deleted, otherwise * files will be moved there. * @param listener MegaRequestListener to track this request */ void moveOrRemoveDeconfiguredBackupNodes(MegaHandle deconfiguredBackupRoot, MegaHandle backupDestination, MegaRequestListener* listener = NULL); /** * @brief Get the backup identified with a tag * * You take the ownership of the returned value * * @param tag Tag that identifies the backup * @return Backup identified by the tag */ MegaScheduledCopy *getScheduledCopyByTag(int tag); /** * @brief getScheduledCopyByNode Get the backup associated with a node * * You take the ownership of the returned value * Caveat: Two backups can have the same parent node, the first one encountered is returned * * @param node Root node of the backup * @return Backup with the specified root node */ MegaScheduledCopy *getScheduledCopyByNode(MegaNode *node); /** * @brief getScheduledCopyByPath Get the backup associated with a local path * * You take the ownership of the returned value * * @param localPath Root local path of the backup * @return Backup with the specified root local path */ MegaScheduledCopy *getScheduledCopyByPath(const char *localPath); /** * @brief Check if the SDK is waiting to complete a request and get the reason * @return State of SDK. * * Valid values are: * - MegaApi::RETRY_NONE = 0 * SDK is not waiting for the server to complete a request * * - MegaApi::RETRY_CONNECTIVITY = 1 * SDK is waiting for the server to complete a request due to connectivity issues * * - MegaApi::RETRY_SERVERS_BUSY = 2 * SDK is waiting for the server to complete a request due to a HTTP error 500 * * - MegaApi::RETRY_API_LOCK = 3 * SDK is waiting for the server to complete a request due to an API lock (API error -3) * * - MegaApi::RETRY_RATE_LIMIT = 4, * SDK is waiting for the server to complete a request due to a rate limit (API error -4) * * - MegaApi::RETRY_LOCAL_LOCK = 5 * SDK is waiting for a local locked file * * - MegaApi::RETRY_UNKNOWN = 6 * SDK is waiting for the server to complete a request with unknown reason * */ int isWaiting(); /** * @brief Find out if the syncs need User intervention for some files/folders * * use getMegaSyncStallList() to find out what needs attention. * * @return true if the User is needs to intervene. * */ bool isSyncStalled(); /** * @brief Find out if the stall/conflict list has changed (increased/decreased). * * Note that this is meant to be used while MegaApi::isSyncStalled() is true. * * use getMegaSyncStallList() to find out what needs attention. * * @return true if the stall/conflict list has changed. * */ bool isSyncStalledChanged(); /** * @brief Get the total number of nodes in the account * * @note This method doesn't lock SDK mutex, returned value may not be up to date * Nodes can be removed or added but this value is updated at the end of MegaClient::notityPurge * * @return Total number of nodes in the account */ unsigned long long getNumNodes(); /** @brief Get the total number of nodes in the account * * @note This method locks SDK mutex, returned value is up to date * This calls gets blocked if it's called between a node is added or removed and * nodes count is updated * * @return Total number of nodes in the account */ unsigned long long getAccurateNumNodes(); /** * @brief Set LRU cache size * * By default it's defined at unsigned long long max value * * @param size LRU cache size */ void setLRUCacheSize(unsigned long long size); /** * @brief Returns number of nodes stored at cache LRU * * @return Number of nodes at cache LRU */ unsigned long long getNumNodesAtCacheLRU() const; enum { ORDER_NONE = 0, ORDER_DEFAULT_ASC = 1, ORDER_DEFAULT_DESC = 2, ORDER_SIZE_ASC = 3, ORDER_SIZE_DESC = 4, ORDER_CREATION_ASC = 5, ORDER_CREATION_DESC = 6, ORDER_MODIFICATION_ASC = 7, ORDER_MODIFICATION_DESC = 8, // ORDER_ALPHABETICAL_ASC = 9, (obsolete) // ORDER_ALPHABETICAL_DESC = 10, (obsolete) // ORDER_PHOTO_ASC = 11, (obsolete) // ORDER_PHOTO_DESC = 12, (obsolete) // ORDER_VIDEO_ASC = 13, (obsolete) // ORDER_VIDEO_DESC = 14, (obsolete) ORDER_LINK_CREATION_ASC = 15, ORDER_LINK_CREATION_DESC = 16, ORDER_LABEL_ASC = 17, ORDER_LABEL_DESC = 18, ORDER_FAV_ASC = 19, ORDER_FAV_DESC = 20, ORDER_SHARE_CREATION_ASC = 21, ORDER_SHARE_CREATION_DESC = 22, }; enum { FILE_TYPE_DEFAULT = 0, // FILE_TYPE_UNKNOWN already exists at WinBase.h FILE_TYPE_PHOTO, FILE_TYPE_AUDIO, FILE_TYPE_VIDEO, FILE_TYPE_DOCUMENT, FILE_TYPE_PDF, FILE_TYPE_PRESENTATION, FILE_TYPE_ARCHIVE, FILE_TYPE_PROGRAM, FILE_TYPE_MISC, FILE_TYPE_SPREADSHEET, FILE_TYPE_ALL_DOCS, // any of {DOCUMENT, PDF, PRESENTATION, SPREADSHEET} FILE_TYPE_OTHERS, FILE_TYPE_ALL_VISUAL_MEDIA, // any of {PHOTO, VIDEO} FILE_TYPE_LAST = FILE_TYPE_ALL_VISUAL_MEDIA, }; enum { SEARCH_TARGET_INSHARE = 0, SEARCH_TARGET_OUTSHARE, SEARCH_TARGET_PUBLICLINK, SEARCH_TARGET_ROOTNODE, // search in Cloud and Vault rootnodes SEARCH_TARGET_ALL, }; /** * @brief Get the number of child nodes * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 * * This function doesn't search recursively, only returns the direct child nodes. * * @param parent Parent node * @return Number of child nodes */ int getNumChildren(MegaNode* parent); /** * @brief Get the number of child files of a node * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 * * This function doesn't search recursively, only returns the direct child files. * * @param parent Parent node * @return Number of child files */ int getNumChildFiles(MegaNode* parent); /** * @brief Get the number of child folders of a node * * If the node doesn't exist in MEGA or isn't a folder, * this function returns 0 * * This function doesn't search recursively, only returns the direct child folders. * * @param parent Parent node * @return Number of child folders */ int getNumChildFolders(MegaNode* parent); /** * @brief Get all children of a MegaNode * * If the parent node doesn't exist or it isn't a folder, this function * returns an empty list * * You take the ownership of the returned value * * This function allows to cancel the processing at any time by passing a MegaCancelToken * and calling to MegaCancelToken::setCancelFlag(true). * * @param parent Parent node * @param order Order for the returned list * * Note: First, the nodes are always sorted by type, being folders always first. Then, the * specified option is applied in each type block * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, older modification times first * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, older modification times last * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, nodes with colors first * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, nodes with colors last * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last * * @param cancelToken MegaCancelToken to be able to cancel the processing at any time. * @return List with all child MegaNode objects */ MegaNodeList* getChildren(MegaNode *parent, int order = 1, MegaCancelToken *cancelToken = nullptr); /** * @brief Get children of a particular parent or a predefined location, and allow filtering * the results. @see MegaSearchFilter * The look-up is case-insensitive. * For invalid filtering options, this function returns an empty list. * * You take the ownership of the returned value * * This function allows to cancel the processing at any time by passing a MegaCancelToken and calling * to MegaCancelToken::setCancelFlag(true). * * @param filter Container for filtering options. In order to be considered valid it must * - be not null * - have valid ancestor handle (different than INVALID_HANDLE) set by calling byLocationHandle(), * and in consequence it must have default value for location (SEARCH_TARGET_ALL) * @param order Order for the returned list * * Note: First, the nodes are always sorted by type, being folders always first. Then, the * specified option is applied in each type block * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, older modification times first * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, older modification times last * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, nodes with colors first * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, nodes with colors last * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last * * @param cancelToken MegaCancelToken to be able to cancel the processing at any time. * @param searchPage Container for pagination options; if null, all results will be returned * * @return List with found children as MegaNode objects */ MegaNodeList* getChildren(const MegaSearchFilter *filter, int order = ORDER_NONE, MegaCancelToken *cancelToken = nullptr, const MegaSearchPage* searchPage = nullptr); /** * @brief Get all children of a list of MegaNodes * * If any parent node doesn't exist or it isn't a folder, that parent * will be skipped. * * You take the ownership of the returned value * * @param parentNodes List of parent nodes * @param order Order for the returned list * * Note: First, the nodes are always sorted by type, being folders always first. Then, the * specified option is applied in each type block * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, older modification times first * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, older modification times last * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, nodes with colors first * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, nodes with colors last * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last * * @return List with all child MegaNode objects */ MegaNodeList* getChildren(MegaNodeList *parentNodes, int order = 1); /** * @brief List child nodes ordered lexicographically by name. * * Results are ordered by name, then node type (files before folders), then handle. * This makes paging deterministic even when there are duplicate names. * * @param parenthandle Parent node handle. * @param cancelToken Optional cancel token. * @param maxElements Maximum number of elements to return (0 means no limit). * @param offset Optional offset to resume from a previous page. Provide: * - mLastName to start strictly after all nodes with that name (regardless of type). * - mLastName + mLastType to start strictly after all nodes with that name and type. * - mLastName + mLastType + mLastHandle to resume after a specific node among duplicates. * * @return List with the MegaNode matching the query */ MegaNodeList* listChildNodesLexicographically( const MegaHandle parenthandle, MegaCancelToken* cancelToken = nullptr, const size_t maxElements = 0, const std::optional& offset = {}); /** * @brief Get all versions of a file * * You take ownership of the returned value. * * @param node Node to check * @return List with all versions of the node, including the current version */ MegaNodeList* getVersions(MegaNode *node); /** * @brief Get the number of versions of a file * @param node Node to check * @return Number of versions of the node, including the current version */ int getNumVersions(MegaNode *node); /** * @brief Check if a file has previous versions * @param node Node to check * @return true if the node has any previous version */ bool hasVersions(MegaNode *node); /** * @brief Get information about the contents of a folder * * The associated request type with this request is MegaRequest::TYPE_FOLDER_INFO * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaFolderInfo - MegaFolderInfo object with the information related to the folder * * @param node Folder node to inspect * @param listener MegaRequestListener to track this request */ void getFolderInfo(MegaNode *node, MegaRequestListener *listener = NULL); /** * @brief Returns true if the node has children * @return true if the node has children */ bool hasChildren(MegaNode *parent); /** * @brief Get the first child node with the provided name * * If the node doesn't exist, this function returns NULL * It's possible to have multiple nodes with the same name. * This function will return one of them. * Folder nodes take precedence over file nodes. * If you want a node of specific type, @see getChildNodeOfType * * You take the ownership of the returned value * * @param parent Parent node * @param name Name of the node * @return The MegaNode that has the selected parent and name */ MegaNode *getChildNode(MegaNode *parent, const char* name); /** * @brief Get the first child node with the name and type provided * * Allowed types for type parameter: MegaNode::TYPE_FILE, MegaNode::TYPE_FOLDER * * If the node doesn't exist, this function returns nullptr * It's possible to have multiple nodes with the same name. * This function will return one of them. * * You take the ownership of the returned value * * @param parent Parent node * @param name Name of the node * @param type Type of the node. * @return The MegaNode that has the selected parent, name and type */ MegaNode* getChildNodeOfType(MegaNode *parent, const char *name, int type); /** * @brief Get the parent node of a MegaNode * * If the node doesn't exist in the account or * it is a root node, this function returns NULL * * You take the ownership of the returned value. * * @param node MegaNode to get the parent * @return The parent of the provided node */ MegaNode *getParentNode(MegaNode *node); /** * @brief Get the path of a MegaNode * * If the node doesn't exist, this function returns NULL. * You can recover the node later using MegaApi::getNodeByPath * except if the path contains names with '/' or '\' characters. * * Note: inshare paths have following structure "email:path" * * You take ownership of the returned value. Use delete[] to release the memory. * * @param node MegaNode for which the path will be returned * @return The path of the node */ char* getNodePath(MegaNode *node); /** * @brief Get the path of a Node given its MegaHandle * * If the node doesn't exist, this function returns NULL. * You can recover the node later using MegaApi::getNodeByPath * except if the path contains names with '/' or '\' characters. * * Note: inshare paths have following structure "email:path" * * You take ownership of the returned value. Use delete[] to release the memory. * * @param handle MegaNode handle for which the path will be returned * @return The path of the node */ char* getNodePathByNodeHandle(MegaHandle handle); /** * @brief Get the MegaNode in a specific path in the MEGA account * * The path separator character is '/' * The Root node is / * The Vault root node is //in/ * The Rubbish root node is //bin/ * * Paths with names containing '/' or '\' aren't compatible * with this function. * * If the path name contains ':' it needs to be escaped before * calling this function. * * Note: inshare paths have following structure "email:path" * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. * * You take the ownership of the returned value * * @param path Path to check * @param n Base node if the path is relative * @return The MegaNode object in the path, otherwise NULL */ MegaNode *getNodeByPath(const char *path, MegaNode *n = NULL); /** * @brief Get the MegaNode of the specified type, in a specific path in the MEGA account * * The path separator character is '/' * The Root node is / * The Vault root node is //in/ * The Rubbish root node is //bin/ * * Paths with names containing '/' or '\' aren't compatible * with this function. * * If the path name contains ':' it needs to be escaped before * calling this function. * * Note: inshare paths have following structure "email:path" * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. * * You take the ownership of the returned value * * @param path Path to check * @param n Base node if the path is relative * @param type Type of the node to be looked up; valid values: TYPE_FILE, TYPE_FOLDER, * TYPE_UNKNOWN (any type, folder has precedence) * @return The MegaNode object in the path, otherwise NULL */ MegaNode* getNodeByPathOfType(const char *path, MegaNode *n = nullptr, int type = MegaNode::TYPE_UNKNOWN); /** * @brief Get the MegaNode that has a specific handle * * You can get the handle of a MegaNode using MegaNode::getHandle. The same handle * can be got in a Base64-encoded string using MegaNode::getBase64Handle. Conversions * between these formats can be done using MegaApi::base64ToHandle and MegaApi::handleToBase64 * * It is needed to be logged in and to have successfully completed a fetchNodes * request before calling this function. Otherwise, it will return NULL. * * You take the ownership of the returned value. * * @param h Node handle to check * @return MegaNode object with the handle, otherwise NULL */ MegaNode *getNodeByHandle(MegaHandle h); /** * @brief Generate a TOTP token and its lifetime with the data stored in the node with the * given handle. * * @note This performs a synchronous operation. * * @param handle The handle of the password node with the required totp data needed to * compute the totp token and its lifetime. * @return A MegaTotpTokenGenResult with: * - `errorCode`: An error code that can be one of: * + API_EARGS: The input handle is `UNDEF` * + API_ENOENT: The input handle does not correspond to a password node * + API_EKEY: The input handle corresponds to a password node with no TOTP data * + API_EINTERNAL: The TOTP data stored in the password node is ill-formed and cannot be * used to generate valid tokens. * + API_OK: the generation succeeded and the result can be retrieved from `second` * - `MegaTotpTokenLifetime`: * + `token`: The generated token * + `remainingLifeTimeSeconds`: The remaining time */ MegaTotpTokenGenResult generateTotpTokenFromNode(MegaHandle handle); /** * @brief Get the MegaContactRequest that has a specific handle * * You can get the handle of a MegaContactRequest using MegaContactRequest::getHandle. * * You take the ownership of the returned value. * * @param handle Contact request handle to check * @return MegaContactRequest object with the handle, otherwise NULL */ MegaContactRequest *getContactRequestByHandle(MegaHandle handle); /** * @brief Get all contacts of this MEGA account * * You take the ownership of the returned value * * @return List of MegaUser object with all contacts of this account */ MegaUserList* getContacts(); /** * @brief Get the MegaUser that has a specific email address * * You can get the email of a MegaUser using MegaUser::getEmail * * You take the ownership of the returned value * * @param user Email or Base64 handle of the user * @return MegaUser that has the email address, otherwise NULL */ MegaUser* getContact(const char *user); /** * @brief Get all MegaUserAlerts for the logged in user * * You take the ownership of the returned value * * @return List of MegaUserAlert objects */ MegaUserAlertList* getUserAlerts(); /** * @brief Get the number of unread user alerts for the logged in user * * @return Number of unread user alerts */ int getNumUnreadUserAlerts(); /** * @brief Get a list with all active inbound sharings from one MegaUser * * This method returns both verified and not verified shares. * * You take the ownership of the returned value * * @param user MegaUser sharing folders with this account * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * @return List of MegaNode objects that this user is sharing with this account */ MegaNodeList *getInShares(MegaUser* user, int order = ORDER_NONE); /** * @brief Get a list with all active inbound sharings * * This method returns both verified and not verified shares. * * You take the ownership of the returned value * * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * @return List of MegaNode objects that other users are sharing with this account */ MegaNodeList *getInShares(int order = ORDER_NONE); /** * @brief Get a list with all active inbound sharings * * This method returns verified shares. * * You take the ownership of the returned value * * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * @return List of MegaShare objects that other users are sharing with this account */ MegaShareList *getInSharesList(int order = ORDER_NONE); /** * @brief Get a list with all unverified inbound sharings * * You take the ownership of the returned value * * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * @return List of MegaShare objects that other users are sharing with this account */ MegaShareList *getUnverifiedInShares(int order = ORDER_NONE); /** * @brief Get the user relative to an incoming share * * This function will return NULL if the node is not found * * When recurse is true and the root of the specified node is not an incoming share, * this function will return NULL. * When recurse is false and the specified node doesn't represent the root of an * incoming share, this function will return NULL. * * You take the ownership of the returned value * * @param node Node to look for inshare user. * @param recurse use root node corresponding to the node passed * @return MegaUser relative to the incoming share */ MegaUser *getUserFromInShare(MegaNode *node, bool recurse = false); /** * @brief Check if a MegaNode is pending to be shared with another User. This situation * happens when a node is to be shared with a User which is not a contact yet. * * For nodes that are pending to be shared, you can get a list of MegaNode * objects using MegaApi::getPendingShares * * @param node Node to check * @return true is the MegaNode is pending to be shared, otherwise false */ bool isPendingShare(MegaNode *node); /** * @brief Get a list with all active and pending outbound sharings * * This method returns both, verified and unverified shares. * You take the ownership of the returned value * * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by node creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by node creation time in MEGA, older elements last * * - MegaApi::ORDER_SHARE_CREATION_ASC = 21 * Sort by share creation time in MEGA, older elements first * * - MegaApi::ORDER_SHARE_CREATION_DESC = 22 * Sort by share creation time in MEGA, older elements last * * @return List of MegaShare objects */ MegaShareList *getOutShares(int order = ORDER_NONE); /** * @brief Get a list with the active and pending outbound sharings for a MegaNode * * This method returns both, verified and unverified shares. * * If the node doesn't exist in the account, this function returns an empty list. * * You take the ownership of the returned value * * @param node MegaNode to check * @return List of MegaShare objects */ MegaShareList *getOutShares(MegaNode *node); /** * @brief Get a list with all pending outbound sharings * * This method returns both, verified and unverified shares. * * You take the ownership of the returned value * * @return List of MegaShare objects * @deprecated Use MegaNode::getOutShares instead of this function */ MegaShareList *getPendingOutShares(); /** * @brief Get a list with all pending outbound sharings * * This method returns both, verified and unverified shares. * * You take the ownership of the returned value * * @deprecated Use MegaNode::getOutShares instead of this function * @return List of MegaShare objects */ MegaShareList *getPendingOutShares(MegaNode *node); /** * @brief Get a list with all unverified sharings * * You take the ownership of the returned value * * @param order Sorting order to use * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by node creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by node creation time in MEGA, older elements last * * - MegaApi::ORDER_SHARE_CREATION_ASC = 21 * Sort by share creation time in MEGA, older elements first * * - MegaApi::ORDER_SHARE_CREATION_DESC = 22 * Sort by share creation time in MEGA, older elements last * * @return List of MegaShare objects */ MegaShareList *getUnverifiedOutShares(int order = ORDER_NONE); /** * @brief Check if a node belongs to your own cloud * @param handle Node to check * @return True if it belongs to your own cloud */ bool isPrivateNode(MegaHandle handle); /** * @brief Check if a node does NOT belong to your own cloud * * In example, nodes from incoming shared folders do not belong to your cloud. * * @param handle Node to check * @return True if it does NOT belong to your own cloud */ bool isForeignNode(MegaHandle handle); /** * @brief Get a list with all public links * * Valid value for order are: MegaApi::ORDER_NONE, MegaApi::ORDER_DEFAULT_ASC, * MegaApi::ORDER_DEFAULT_DESC, MegaApi::ORDER_LINK_CREATION_ASC, * MegaApi::ORDER_LINK_CREATION_DESC * * You take the ownership of the returned value * * @param order Sorting order to use * @return List of MegaNode objects that are shared with everyone via public link */ MegaNodeList *getPublicLinks(int order = ORDER_NONE); /** * @brief Get a list with all incoming contact requests * * You take the ownership of the returned value * * @return List of MegaContactRequest objects */ MegaContactRequestList* getIncomingContactRequests() const; /** * @brief Get a list with all outgoing contact requests * * You take the ownership of the returned value * * @return List of MegaContactRequest objects */ MegaContactRequestList* getOutgoingContactRequests() const; /** * @brief Get the access level of a MegaNode * @param node MegaNode to check * @return Access level of the node * Valid values are: * - MegaShare::ACCESS_OWNER * - MegaShare::ACCESS_FULL * - MegaShare::ACCESS_READWRITE * - MegaShare::ACCESS_READ * - MegaShare::ACCESS_UNKNOWN */ int getAccess(MegaNode* node); /** * @brief Get the access level of a node * @param nodeHandle MegaHandle of the node to check * @return Access level of the node * Valid values are: * - MegaShare::ACCESS_OWNER * - MegaShare::ACCESS_FULL * - MegaShare::ACCESS_READWRITE * - MegaShare::ACCESS_READ * - MegaShare::ACCESS_UNKNOWN */ int getAccess(MegaHandle nodeHandle); /** * @brief Get the size of a node tree * * If the MegaNode is a file, this function returns the size of the file. * If it's a folder, this fuction returns the sum of the sizes of all nodes * in the node tree. * * @param node Parent node * @return Size of the node tree */ long long getSize(MegaNode *node); /** * @brief Get a Base64-encoded fingerprint for a local file * * The fingerprint is created taking into account the modification time of the file * and file contents. This fingerprint can be used to get a corresponding node in MEGA * using MegaApi::getNodeByFingerprint * * If the file can't be found or can't be opened, this function returns NULL * * You take ownership of the returned value. Use delete[] to release the memory. * * @param filePath Local file path * @return Base64-encoded fingerprint for the file */ char* getFingerprint(const char *filePath); /** * @brief Get a Base64-encoded fingerprint from an input stream and a modification time * * If the input stream is NULL, has a negative size or can't be read, this function returns NULL * * You take ownership of the returned value. Use delete[] to release the memory. * * @param inputStream Input stream that provides the data to create the fingerprint * @param mtime Modification time that will be taken into account for the creation of the fingerprint * @return Base64-encoded fingerprint */ char* getFingerprint(MegaInputStream *inputStream, int64_t mtime); /** * @brief Returns a node with the provided fingerprint * * If there isn't any node in the account with that fingerprint, this function returns NULL. * * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return MegaNode object with the provided fingerprint */ MegaNode *getNodeByFingerprint(const char* fingerprint); /** * @brief Returns a node with the provided fingerprint * * If there isn't any node in the account with that fingerprint, this function returns NULL. * If there are several nodes with the same fingerprint, nodes in the preferred * parent folder take precedence. * * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @param parent Preferred parent node * @return MegaNode object with the provided fingerprint */ MegaNode *getNodeByFingerprint(const char *fingerprint, MegaNode* parent); /** * @brief Returns all nodes that have a fingerprint * * If there isn't any node in the account with that fingerprint, this function returns an empty MegaNodeList. * * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return List of nodes with the same fingerprint */ MegaNodeList *getNodesByFingerprint(const char* fingerprint); /** * @brief Returns all nodes that have a fingerprint (ignoring modification time) * * If there isn't any node in the account with that fingerprint, this function returns an * empty MegaNodeList. * * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @return List of nodes with the same fingerprint (ignoring modification time) */ MegaNodeList* getNodesByFingerprintIgnoringMtime(const char* fingerprint); /** * @brief Returns nodes that have an originalFingerprint equal to the supplied value * * Search the node tree and return a list of nodes that have an originalFingerprint, which * matches the supplied originalfingerprint. * * If the parent node supplied is not NULL, it only searches nodes below that parent folder, * otherwise all nodes are searched. If no nodes are found with that original fingerprint, * this function returns an empty MegaNodeList. * * You take the ownership of the returned value. * * @param originalFingerprint Original Fingerprint to check * @param parent Only return nodes below this specified parent folder. Pass NULL to consider all nodes. * @return List of nodes with the same original fingerprint */ MegaNodeList *getNodesByOriginalFingerprint(const char* originalFingerprint, MegaNode* parent); /** * @brief Returns a node with the provided fingerprint that can be exported * * If there isn't any node in the account with that fingerprint, this function returns NULL. * If a file name is passed in the second parameter, it's also checked if nodes with a matching * fingerprint has that name. If there isn't any matching node, this function returns NULL. * This function ignores nodes that are inside the Rubbish Bin because public links to those nodes * can't be downloaded. * * You take the ownership of the returned value. * * @param fingerprint Fingerprint to check * @param name Name that the node should have (optional) * @return Exportable node that meet the requirements */ MegaNode *getExportableNodeByFingerprint(const char *fingerprint, const char *name = NULL); /** * @brief Check if the account already has a node with the provided fingerprint * * A fingerprint for a local file can be generated using MegaApi::getFingerprint * * @param fingerprint Fingerprint to check * @return true if the account contains a node with the same fingerprint */ bool hasFingerprint(const char* fingerprint); /** * @brief getCRC Get the CRC of a file * * The CRC of a file is a hash of its contents. * If you need a more realiable method to check files, use fingerprint functions * (MegaApi::getFingerprint, MegaApi::getNodeByFingerprint) that also takes into * account the size and the modification time of the file to create the fingerprint. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param filePath Local file path * @return Base64-encoded CRC of the file */ char* getCRC(const char *filePath); /** * @brief Get the CRC from a fingerprint * * You take ownership of the returned value. Use delete[] to release the memory. * * @param fingerprint fingerprint from which we want to get the CRC * @return Base64-encoded CRC from the fingerprint */ char *getCRCFromFingerprint(const char *fingerprint); /** * @brief getCRC Get the CRC of a node * * The CRC of a node is a hash of its contents. * If you need a more realiable method to check files, use fingerprint functions * (MegaApi::getFingerprint, MegaApi::getNodeByFingerprint) that also takes into * account the size and the modification time of the node to create the fingerprint. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param node Node for which we want to get the CRC * @return Base64-encoded CRC of the node */ char* getCRC(MegaNode *node); /** * @brief getNodeByCRC Returns a node with the provided CRC * * If there isn't any node in the selected folder with that CRC, this function returns NULL. * If there are several nodes with the same CRC, anyone can be returned. * * You take the ownership of the returned value. * * @param crc CRC to check * @param parent Parent node to scan. It must be a folder. * @return Node with the selected CRC in the selected folder, or NULL * if it's not found. */ MegaNode* getNodeByCRC(const char *crc, MegaNode* parent); /** * @brief Check if a node has an access level * * You take the ownership of the returned value * * @param node Node to check * @param level Access level to check * Valid values for this parameter are: * - MegaShare::ACCESS_OWNER * - MegaShare::ACCESS_FULL * - MegaShare::ACCESS_READWRITE * - MegaShare::ACCESS_READ * * @return Pointer to MegaError with the result. * Valid values for the error code are: * - MegaError::API_OK - The node has the required access level * - MegaError::API_EACCESS - The node doesn't have the required access level * - MegaError::API_ENOENT - The node doesn't exist in the account * - MegaError::API_EARGS - Invalid parameters */ MegaError* checkAccessErrorExtended(MegaNode* node, int level); /** * @brief Check if a node can be moved to a target node * * You take the ownership of the returned value * * @param node Node to check * @param target Target for the move operation * @return MegaError object with the result: * Valid values for the error code are: * - MegaError::API_OK - The node can be moved to the target * - MegaError::API_EACCESS - The node can't be moved because of permissions problems * - MegaError::API_ECIRCULAR - The node can't be moved because that would create a circular linkage * - MegaError::API_ENOENT - The node or the target doesn't exist in the account * - MegaError::API_EARGS - Invalid parameters */ MegaError* checkMoveErrorExtended(MegaNode* node, MegaNode* target); /** * @brief Check if the MEGA filesystem is available in the local computer * * This function returns true after a successful call to MegaApi::fetchNodes, * otherwise it returns false * * @return True if the MEGA filesystem is available */ bool isFilesystemAvailable(); /** * @brief Returns the root node of the account * * You take the ownership of the returned value * * If you haven't successfully called MegaApi::fetchNodes before, * this function returns NULL * * @return Root node of the account */ MegaNode *getRootNode(); /** * @brief Returns the Vault node of the account * * You take the ownership of the returned value * * If you haven't successfully called MegaApi::fetchNodes before, * this function returns NULL * * @return Vault node of the account */ MegaNode *getVaultNode(); /** * @brief Returns the rubbish node of the account * * You take the ownership of the returned value * * If you haven't successfully called MegaApi::fetchNodes before, * this function returns NULL * * @return Rubbish node of the account */ MegaNode *getRubbishNode(); /** * @brief Returns the root node of one node * * You take the ownership of the returned value * * @param node Node to check * @return Root node for the \c node */ MegaNode *getRootNode(MegaNode *node); /** * @brief Check if a node is in the Cloud Drive tree * * @param node Node to check * @return True if the node is in the cloud drive */ bool isInCloud(MegaNode *node); /** * @brief Check if a node is in the Rubbish bin tree * * @param node Node to check * @return True if the node is in the Rubbish bin */ bool isInRubbish(MegaNode *node); /** * @brief Check if a node is in the Vault tree * * @param node Node to check * @return True if the node is in the Vault */ bool isInVault(MegaNode *node); /** * @brief Set default permissions for new files * * This function allows to change the permissions that will be received * by newly created files. * * It's possible to change group permissions, public permissions and the * executable permission for the user. "rw" permissions for the user will * be always granted to prevent synchronization problems. * * To check the effective permissions that will be applied, please use * MegaApi::getDefaultFilePermissions * * Currently, this function only works for OS X and Linux (or any other * platform using the Posix filesystem layer). On Windows, it doesn't have * any effect. * * @param permissions Permissions for new files in the same format accepted by chmod() (0755, for example) */ void setDefaultFilePermissions(int permissions); /** * @brief Get default permissions for new files * * This function returns the permissions that will be applied to new files. * * Currently, this function only works on OS X and Linux (or any other * platform using the Posix filesystem layer). On Windows it returns 0600 * * @return Permissions for new files in the same format accepted by chmod() (0755, for example) */ int getDefaultFilePermissions(); /** * @brief Set default permissions for new folders * * This function allows to change the permissions that will be received * by newly created folders. * * It's possible to change group permissions and public permissions. * "rwx" permissions for the user will be always granted to prevent * synchronization problems. * * To check the effective permissions that will be applied, please use * MegaApi::getDefaultFolderPermissions * * Currently, this function only works for OS X and Linux (or any other * platform using the Posix filesystem layer). On Windows, it doesn't have * any effect. * * @param permissions Permissions for new folders in the same format accepted by chmod() (0755, for example) */ void setDefaultFolderPermissions(int permissions); /** * @brief Get default permissions for new folders * * This function returns the permissions that will be applied to new folders. * * Currently, this function only works on OS X and Linux (or any other * platform using the Posix filesystem layer). On Windows, it returns 0700 * * @return Permissions for new folders in the same format accepted by chmod() (0755, for example) */ int getDefaultFolderPermissions(); /** * @brief Get the time (in seconds) during which transfers will be stopped due to a bandwidth overquota * @return Time (in seconds) during which transfers will be stopped, otherwise 0 */ long long getBandwidthOverquotaDelay(); /** * @brief Search nodes and allow filtering the results. * The search is case-insensitive. * * You take the ownership of the returned value. * * @param filter Container for filtering options, cannot be null * @param order Order for the returned list * * Note: First, the nodes are always sorted by type, being folders always first. Then, the * specified option is applied in each type block * * Valid values for this parameter are: * - MegaApi::ORDER_NONE = 0 * Undefined order * * - MegaApi::ORDER_DEFAULT_ASC = 1 * Alphabetical order, e.g. bar, Car, foo * * - MegaApi::ORDER_DEFAULT_DESC = 2 * Alphabetical inverse order, e.g. foo, Car, bar * * - MegaApi::ORDER_SIZE_ASC = 3 * Sort by size, small elements first * * - MegaApi::ORDER_SIZE_DESC = 4 * Sort by size, small elements last * * - MegaApi::ORDER_CREATION_ASC = 5 * Sort by creation time in MEGA, older elements first * * - MegaApi::ORDER_CREATION_DESC = 6 * Sort by creation time in MEGA, older elements last * * - MegaApi::ORDER_MODIFICATION_ASC = 7 * Sort by modification time of the original file, older modification times first * * - MegaApi::ORDER_MODIFICATION_DESC = 8 * Sort by modification time of the original file, older modification times last * * - MegaApi::ORDER_LABEL_ASC = 17 * Sort by color label, nodes with colors first * * - MegaApi::ORDER_LABEL_DESC = 18 * Sort by color label, nodes with colors last * * - MegaApi::ORDER_FAV_ASC = 19 * Sort nodes with favourite attr first * * - MegaApi::ORDER_FAV_DESC = 20 * Sort nodes with favourite attr last * * @param cancelToken MegaCancelToken to be able to cancel the search at any time. * @param searchPage Container for pagination options; if null, all results will be returned * * @return List with found nodes as MegaNode objects */ MegaNodeList* search(const MegaSearchFilter* filter, int order = ORDER_NONE, MegaCancelToken* cancelToken = nullptr, const MegaSearchPage* searchPage = nullptr); /** * @brief Get a list of buckets, each bucket containing a list of recently added/modified * nodes * * Each bucket contains files that were added/modified in a set, by a single user. * Buckets are grouped into fixed 6-hour windows in UTC: 0-6, 6-12, 12-18, 18-24. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes * * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns buckets with a list of recently added/modified * nodes * - MegaRequest::getFlag - Returns false, as sensitives are included by default. Use * getRecentActionsAsync with explicit excludeSensitives flag to search for sensitives and * filter them depending on the flag value * * The recommended values for the following * parameters are to consider interactions during the last 30 days and maximum 500 nodes. * * Note: Nodes sensitives are NOT excluded by default. Nodes are considered * sensitive if they have that property set, or one of their ancestors has it. * Use getRecentActionsAsync with explicit excludeSensitives flag * to search for sensitives and filter them depending on the flag value * * @deprecated use getRecentActionsAsync(days, maxnodes, excludeSensitives, listener) * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered * @param listener MegaRequestListener to track this request */ void getRecentActionsAsync(unsigned days, unsigned maxnodes, MegaRequestListener *listener = NULL); /** * @brief Get a list of buckets, each bucket containing a list of recently added/modified * nodes * * Each bucket contains files that were added/modified in a set, by a single user. * Buckets are grouped into fixed 6-hour windows in UTC: 0-6, 6-12, 12-18, 18-24. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the number of days since nodes will be considerated * - MegaRequest::getParamType - Returns the maximun number of nodes * - MegaRequest::getFlag - Returns true if sensitives are excluded * * The associated request type with this request is MegaRequest::TYPE_GET_RECENT_ACTIONS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns buckets with a list of recently added/modified * nodes * * The recommended values for the following parameters are to consider * interactions during the last 30 days and maximum 500 nodes. * * @param days Age of actions since added/modified nodes will be considered (in days) * @param maxnodes Maximum amount of nodes to be considered * @param excludeSensitives Set to true to filter out sensitive nodes (Nodes are considered * sensitive if they have that property set, or one of their ancestors has it) * @param listener MegaRequestListener to track this request */ void getRecentActionsAsync(unsigned days, unsigned maxnodes, bool excludeSensitives, MegaRequestListener* listener = NULL); /** * @brief Get a recent action bucket by its identifier * * The identifier format is: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the bucket identifier * - MegaRequest::getFlag - Returns false, as the excludeSensitives value is embedded in the * bucket identifier and can't be overridden in this function. Use getRecentActionById with * explicit excludeSensitives flag to override that value. * * The associated request type with this request is * MegaRequest::TYPE_GET_RECENT_ACTION_BY_ID * If the identifier is invalid (for example, invalid token count, invalid handle, * invalid boolean token, or parentHandle/userHandle is UNDEF), the request * finishes with MegaError::API_EARGS. * If the identifier is valid but there is no matching recent-action bucket, * the request finishes with MegaError::API_ENOENT. * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns a list with 1 bucket * * @param id Bucket identifier returned by MegaRecentActionBucket::getId * @param listener MegaRequestListener to track this request */ void getRecentActionById(const char* id, MegaRequestListener* listener = NULL); /** * @brief Get a recent action bucket by its identifier, overriding the excludeSensitives * flag embedded in the identifier. * * Behaves identically to getRecentActionById(id, listener) except that the * @p excludeSensitives parameter takes precedence over the flag encoded in the bucket * identifier (token 8 of the pipe-delimited id string). This lets callers re-query a * bucket with a different sensitivity filter than the one that was used when the id was * originally generated. * * The identifier format is: * dayStartTs|windowStartHour|windowEndHour|userHandle|parentHandle|isMedia|isUpdate|excludeSensitives * - dayStartTs is the UTC day start timestamp (seconds since Epoch). * - windowStartHour and windowEndHour are UTC hours for the time window boundaries. * - userHandle is base64-encoded and cannot be UNDEF. * - parentHandle is base64-encoded and cannot be UNDEF. * - isMedia, isUpdate and excludeSensitives are 0 or 1. * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the bucket identifier * - MegaRequest::getFlag - Returns the excludeSensitives value used for the lookup * * The associated request type with this request is * MegaRequest::TYPE_GET_RECENT_ACTION_BY_ID * If the identifier is invalid (for example, invalid token count, invalid handle, * invalid boolean token, or parentHandle/userHandle is UNDEF), the request * finishes with MegaError::API_EARGS. * If the identifier is valid but there is no matching recent-action bucket, * the request finishes with MegaError::API_ENOENT. * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getRecentActions - Returns a list with 1 bucket * * @param id Bucket identifier returned by MegaRecentActionBucket::getId * @param excludeSensitives If true, sensitive nodes are excluded from the result * regardless of the flag embedded in @p id. If false, sensitive nodes are included. * @param listener MegaRequestListener to track this request */ void getRecentActionById(const char* id, bool excludeSensitives, MegaRequestListener* listener = NULL); /** * @brief Clear the account’s recent actions history up to a given timestamp. * * This method clears the recent actions history on the account by setting a * “recent clear” timestamp. All actions that occurred at or before the given * timestamp are considered cleared. * * The associated request type for this operation is * MegaRequest::TYPE_SET_ATTR_USER. * * Valid data available in the MegaRequest object received in callbacks: * - MegaRequest::getParamType - Returns the user attribute type * MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP * - MegaRequest::getNumber - Returns the epoch time (in seconds) used as the recent * actions history clear timestamp. * * @param until Epoch time (in seconds). Recent actions up to this time will be cleared. * @param listener Optional MegaRequestListener to track this request. */ void clearRecentActionHistory(MegaTimeStamp until, MegaRequestListener* listener = nullptr); /** * @brief Process a node tree using a MegaTreeProcessor implementation * @param node The parent node of the tree to explore * @param processor MegaTreeProcessor that will receive callbacks for every node in the tree * @param recursive True if you want to recursively process the whole node tree. * False if you want to process the children of the node only * * @return True if all nodes were processed. False otherwise (the operation can be * cancelled by MegaTreeProcessor::processMegaNode()) */ bool processMegaTree(MegaNode* node, MegaTreeProcessor* processor, bool recursive = 1); /** * @brief Create a MegaNode that represents a file of a different account * * The resulting node can be used in MegaApi::startDownload and MegaApi::startStreaming but * can not be copied. * * At least the parameters handle, key, size, mtime and auth must be correct to be able to use the resulting node. * * You take the ownership of the returned value. * * @param handle Handle of the node * @param key Key of the node (Base64 encoded) * @param name Name of the node (Base64 encoded) * @param size Size of the node * @param mtime Modification time of the node * @param crc portion of the file's Fingerprint (base 64) * @param parentHandle Handle of the parent node * @param privateAuth Private authentication token to access the node * @param publicAuth Public authentication token to access the node * @param chatAuth Chat authentication token to access the node * @return MegaNode object */ MegaNode *createForeignFileNode(MegaHandle handle, const char *key, const char *name, int64_t size, int64_t mtime, const char* fingerprintCrc, MegaHandle parentHandle, const char *privateAuth, const char *publicAuth, const char *chatAuth); /** * @brief Create a MegaNode that represents a folder of a different account * * The resulting node can not be successfully used in any other function of MegaApi. * The resulting object is only useful to store the values passed as parameters. * * You take the ownership of the returned value. * @param handle Handle of the node * @param name Name of the node (Base64 encoded) * @param parentHandle Handle of the parent node * @param privateAuth Private authentication token to access the node * @param publicAuth Public authentication token to access the node * @return MegaNode object */ MegaNode *createForeignFolderNode(MegaHandle handle, const char *name, MegaHandle parentHandle, const char *privateAuth, const char *publicAuth); /** * @brief Returns a MegaNode that can be downloaded with any instance of MegaApi * * You can use MegaApi::startDownload with the resulting node with any instance * of MegaApi, even if it's logged into another account, a public folder, or not * logged in. * * If the first parameter is a public node or an already authorized node, this * function returns a copy of the node, because it can be already downloaded * with any MegaApi instance. * * If the node in the first parameter belongs to the account or public folder * in which the current MegaApi object is logged in, this funtion returns an * authorized node. * * If the first parameter is NULL or a node that is not a public node, is not * already authorized and doesn't belong to the current MegaApi, this function * returns NULL. * * You take the ownership of the returned value. * * @param node MegaNode to authorize * @return Authorized node, or NULL if the node can't be authorized */ MegaNode *authorizeNode(MegaNode *node); #ifdef ENABLE_CHAT /** * @brief Returns a MegaNode that can be downloaded/copied with a chat-authorization * * During preview of chat-links, you need to call this method to authorize the MegaNode * from a node-attachment message, so the API allows to access to it. The parameter to * authorize the access can be retrieved from MegaChatRoom::getAuthorizationToken when * the chatroom in in preview mode. * * You can use MegaApi::startDownload and/or MegaApi::copyNode with the resulting * node with any instance of MegaApi, even if it's logged into another account, * a public folder, or not logged in. * * You take the ownership of the returned value. * * @param node MegaNode to authorize * @param cauth Authorization token (public handle of the chatroom in B64url encoding) * @return Authorized node, or NULL if the node can't be authorized */ MegaNode *authorizeChatNode(MegaNode *node, const char *cauth); #endif /** * @brief Get the SDK version * * The returned string is an statically allocated array. * Do not delete it. * * @return SDK version */ const char *getVersion(); /** * @brief Get a string with the version of the operating system * * You take ownership of the returned string. Use delete[] to release the memory. * * @return Version of the operating system */ char *getOperatingSystemVersion(); /** * @deprecated */ MEGA_DEPRECATED void getLastAvailableVersion(const char *appKey = NULL, MegaRequestListener *listener = NULL); /** * @brief Get a SSL certificate for communications with the webclient * * The associated request type with this request is MegaRequest::TYPE_GET_LOCAL_SSL_CERT * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the expiration time of the certificate (in seconds since the Epoch) * - MegaRequest::getMegaStringMap - Returns the data of the certificate * * The data returned in the string map is encoded in PEM format. * The key "key" of the map contains the private key of the certificate. * The key "cert" of the map contains the certificate. * Intermediate certificates are provided in keys "intermediate_1" - "intermediate_X". * * @param listener MegaRequestListener to track this request */ void getLocalSSLCertificate(MegaRequestListener *listener = NULL); /** * @brief Get the IP of a MegaChat server * * This function allows to get the correct IP to connect to a MEGAchat server * using Websockets. * * The associated request type with this request is MegaRequest::TYPE_QUERY_DNS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the IP of the hostname. * IPv6 addresses are returned between brackets * * @param hostname Hostname to resolve * @param listener MegaRequestListener to track this request */ void queryDNS(const char *hostname, MegaRequestListener *listener = NULL); /** * @brief Download a file using a HTTP GET request * * The associated request type with this request is MegaRequest::TYPE_DOWNLOAD_FILE * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Return the HTTP status code from the server * - MegaRequest::getTotalBytes - Returns the number of bytes of the file * * If the request finishes with the error code MegaError::API_OK, the destination path * contains the downloaded file. If it's not possible to write in the destination path * the error code will be MegaError::API_EWRITE * * @param url URL of the file * @param dstpath Destination path for the downloaded file * @param listener MegaRequestListener to track this request */ void downloadFile(const char *url, const char *dstpath, MegaRequestListener *listener = NULL); /** * @brief Get the User-Agent header used by the SDK * * The SDK retains the ownership of the returned value. It will be valid until * the MegaApi object is deleted. * * @return User-Agent used by the SDK */ const char *getUserAgent(); /** * @brief Get the base path set during initialization * * The SDK retains the ownership of the returned value. It will be valid until * the MegaApi object is deleted. * * @return Base path */ const char *getBasePath(); /** * @brief Disable special features related to images and videos * * Disabling these features will avoid the upload of previews and thumbnails * for images and videos. * * It's only recommended to disable these features before uploading files * with image or video extensions that are not really images or videos, * or that are encrypted in the local drive so they can't be analyzed anyway. * * By default, graphic features are enabled if the SDK was built with a valid * graphic processor or a valid graphic processor was provided in the constructor * of MegaApi. * * @param disable True to disable special features related to images and videos */ void disableGfxFeatures(bool disable); /** * @brief Check if special graphic features are disabled * * By default, graphic features are enabled so this function will return false. * If graphic features were previously disabled, or the SDK wasn't built with * a valid graphic processor and it wasn't provided in the constructor on MegaApi, * this function will return true. * * @return True if special features related to images and videos are disabled */ bool areGfxFeaturesDisabled(); /** * @brief Change the API URL * * This function allows to change the API URL. * It's only useful for testing or debugging purposes. * * @param apiURL New API URL * @param disablepkp true to disable public key pinning for this URL */ void changeApiUrl(const char *apiURL, bool disablepkp = false); /** * @brief Set the language code used by the app * @param languageCode Language code used by the app * * @return True if the language code is known for the SDK, otherwise false */ bool setLanguage(const char* languageCode); /** * @brief Enables or disables database indexes used for search functionality. * * To optimize performance for applications that do not require search operations, * it is possible to disable specific database indexes that are only used for search * queries. This can reduce database overhead in apps where search is not used (S4). * * @note By default, this option is enabled (`true`). * * @note This method must be called before login and fetchnodes and its value is not reset * upon logout. If indexes already exist, they will be removed when the database is opened. * If indexes has been removed or never created, they won't be created * * @param enable Set to `true` to enable indexes for search functionality, or `false` to * disable them. * @return * - `API_OK` - Operation completed successfully. * - `API_EACCESS` - The operation could not be performed because the user is already logged * in. */ int enableSearchDBIndexes(bool enable); /** * @brief Enables or disables database indexes used by listChildNodesLexicographically. * * The lexicographical listing API uses a database index to speed up paging in large * folders. If your application doesn't call listChildNodesLexicographically, you can * keep these indexes disabled to reduce database overhead. When disabled, the query * still works but may be slower. * * @note By default, this option is disabled (`false`). * * @note This method must be called before login and fetchnodes and its value is not reset * upon logout. If indexes already exist, they will be removed when the database is opened. * If indexes have been removed or never created, they won't be created unless this option * is enabled before login. * * @param enable Set to `true` to enable indexes for lexicographical listing, or `false` * to disable them. * @return * - `API_OK` - Operation completed successfully. * - `API_EACCESS` - The operation could not be performed because the user is already logged * in. */ int enableLexicographicDBIndexes(bool enable); /** * @brief Generate an unique ViewID * * The caller gets the ownership of the object. Use delete[] to release the memory. * * A ViewID consists of a random generated id, encoded in hexadecimal as 16 characters of a null-terminated string. */ const char* generateViewId(); /** * @brief Set the preferred language of the user * * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - Return the language code * * If the language code is unknown for the SDK, the error code will be MegaError::API_ENOENT * * This attribute is automatically created by the server. Apps only need * to set the new value when the user changes the language. * * @param languageCode Language code to be set * @param listener MegaRequestListener to track this request */ void setLanguagePreference(const char* languageCode, MegaRequestListener *listener = NULL); /** * @brief Get the preferred language of the user * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Return the language code * * @param listener MegaRequestListener to track this request */ void getLanguagePreference(MegaRequestListener *listener = NULL); /** * @brief Enable or disable file versioning * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_DISABLE_VERSIONS * * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - "1" for disable, "0" for enable * * @param disable True to disable file versioning. False to enable it * @param listener MegaRequestListener to track this request */ void setFileVersionsOption(bool disable, MegaRequestListener *listener = NULL); /** * @brief Enable or disable the automatic approval of incoming contact requests using a * contact link * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value * MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION * * Valid data in the MegaRequest object received in onRequestFinish: * - MegaRequest::getText - "0" for disable, "1" for enable * * @param enable True to enable the automatic approval of incoming contact requests using a * contact link * @param listener MegaRequestListener to track this request */ void setContactLinksOption(bool enable, MegaRequestListener* listener = NULL); /** * @brief Check if file versioning is enabled or disabled * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_DISABLE_VERSIONS * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - "1" for disable, "0" for enable * - MegaRequest::getFlag - True if disabled, false if enabled * * If the option has never been set, the error code will be MegaError::API_ENOENT. * In that case, file versioning is enabled by default and MegaRequest::getFlag returns false. * * @param listener MegaRequestListener to track this request */ void getFileVersionsOption(MegaRequestListener *listener = NULL); /** * @brief Check if the automatic approval of incoming contact requests using contact links is enabled or disabled * * If the option has never been set, the error code will be MegaError::API_ENOENT. * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - "0" for disable, "1" for enable * - MegaRequest::getFlag - false if disabled, true if enabled * * @param listener MegaRequestListener to track this request */ void getContactLinksOption(MegaRequestListener *listener = NULL); /** * @brief Keep retrying when public key pinning fails * * By default, when the check of the MEGA public key fails, it causes an automatic * logout. Pass false to this function to disable that automatic logout and * keep the SDK retrying the request. * * Even if the automatic logout is disabled, a request of the type MegaRequest::TYPE_LOGOUT * will be automatically created and callbacks (onRequestStart, onRequestFinish) will * be sent. However, logout won't be really executed and in onRequestFinish the error code * for the request will be MegaError::API_EINCOMPLETE * * @param enable true to keep retrying failed requests due to a fail checking the MEGA public key * or false to perform an automatic logout in that case */ void retrySSLerrors(bool enable); /** * @brief Enable / disable the public key pinning * * Public key pinning is enabled by default for all sensible communications. * It is strongly discouraged to disable this feature. * * @param enable true to keep public key pinning enabled, false to disable it */ void setPublicKeyPinning(bool enable); /** * @brief Pause the reception of action packets * * This function is intended to help apps to initialize themselves * after the reception of nodes (MegaApi::fetchNodes) but before the reception * of action packets. * * For that purpose, this function can be called synchronously in the callback * onRequestFinish related to the fetchNodes request. * * After your initialization is finished, you can call MegaApi::resumeActionPackets * to start receiving external updates. * * If you forget to call MegaApi::resumeActionPackets after the usage of this function * the SDK won't work properly. Do not use this function for other purposes. */ void pauseActionPackets(); /** * @brief Resume the reception of action packets * @see MegaApi::pauseActionPackets */ void resumeActionPackets(); #ifdef _WIN32 /** * @brief Convert an UTF16 string to UTF8 (Windows only) * * If the conversion fails, the size of the string will be 0 * If the input string is empty, the size of the result will be also 0 * You can know that the conversion failed checking if the size of the input * is not 0 and the size of the output is zero * * @param utf16data UTF16 buffer * @param utf16size Size of the UTF16 buffer (in characters) * @param utf8string Pointer to a string that will be filled with UTF8 characters */ static void utf16ToUtf8(const wchar_t* utf16data, int utf16size, std::string* utf8string); /** * @brief Convert an UTF8 string to UTF16 (Windows only) * * The converted string will always be a valid UTF16 string. It will have a trailing null byte * added to the string, that along with the null character of the string itself forms a valid * UTF16 string terminator character. Thus, it's valid to pass utf16string->data() to any function * accepting a UTF16 string. * * If the conversion fails, the size of the string will be 1 (null character) * If the input string is empty, the size of the result will be also 1 (null character) * You can know that the conversion failed checking if the size of the input * is not 0 (or NULL) and the size of the output is zero * * @param utf8data NULL-terminated UTF8 character array * @param utf16string Pointer to a string that will be filled with UTF16 characters */ static void utf8ToUtf16(const char* utf8data, std::string* utf16string); #endif /** * @brief Make a name suitable for a file name in the local filesystem * * This function escapes (%xx) forbidden characters in the local filesystem if needed. * You can revert this operation using MegaApi::unescapeFsIncompatible * * If no dstPath is provided or filesystem type it's not supported this method will * escape characters contained in the following list: \/:?\"<>|* * Otherwise it will check forbidden characters for local filesystem type * * The input string must be UTF8 encoded. The returned value will be UTF8 too. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param filename Name to convert (UTF8) * @param dstPath Destination path * @return Converted name (UTF8) */ char* escapeFsIncompatible(const char *filename, const char *dstPath); /** * @brief Unescape a file name escaped with MegaApi::escapeFsIncompatible * * If no localPath is provided or filesystem type it's not supported, this method will * unescape those sequences that once has been unescaped results in any character * of the following list: \/:?\"<>|* * Otherwise it will unescape those characters forbidden in local filesystem type * * The input string must be UTF8 encoded. The returned value will be UTF8 too. * You take ownership of the returned value. Use delete[] to release the memory. * * @param name Escaped name to convert (UTF8) * @param localPath Local path * @return Converted name (UTF8) */ char* unescapeFsIncompatible(const char *name, const char *localPath); /** * @brief Create a thumbnail for an image * @param imagePath Image path * @param dstPath Destination path for the thumbnail (including the file name) * @return True if the thumbnail was successfully created, otherwise false. */ bool createThumbnail(const char *imagePath, const char *dstPath); /** * @brief Create a preview for an image * @param imagePath Image path * @param dstPath Destination path for the preview (including the file name) * @return True if the preview was successfully created, otherwise false. */ bool createPreview(const char *imagePath, const char *dstPath); /** * @brief Create an avatar from an image * @param imagePath Image path * @param dstPath Destination path for the avatar (including the file name) * @return True if the avatar was successfully created, otherwise false. */ bool createAvatar(const char *imagePath, const char *dstPath); /** * @brief Request the URL suitable for uploading a media file. * * This function requests the URL needed for uploading the file. The URL will need the urlSuffix * from the MegaBackgroundMediaUpload::encryptFile to be appended before actually sending. * * The associated request type with this request is MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBackgroundMediaUpload - The updated state of the upload with the URL in the MegaBackgroundMediaUpload::getUploadUrl * * Call this function just once (per file) to find out the URL to upload to, and upload all the pieces to the same * URL. If errors are encountered and the operation must be restarted from scratch, then a new URL should be requested. * A new URL could specify a different upload server for example. * * @param fullFileSize The size of the file * @param state A pointer to the MegaBackgroundMediaUpload object tracking this upload * @param listener MegaRequestListener to track this request */ void backgroundMediaUploadRequestUploadURL(int64_t fullFileSize, MegaBackgroundMediaUpload* state, MegaRequestListener *listener); /** * @brief Create the node after completing the upload of the file by the app. * * Note: added for the use of MEGAproxy and not otherwise supported * * Call this function after completing the upload of all the file data * The node representing the file will be created in the cloud, with all the suitable * attributes and file attributes attached. * * The associated request type with this request is MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getPassword - Returns the original fingerprint * - MegaRequest::getNewPassword - Returns the fingerprint * - MegaRequest::getName - Returns the name * - MegaRequest::getParentHandle - Returns the parent nodehandle * - MegaRequest::getSessionKey - Returns the upload token converted to B64url encoding * - MegaRequest::getPrivateKey - Returns the file key provided in B64url encoding * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Returns the handle of the uploaded node * - MegaRequest::getFlag - True if target folder (\c parent) was overriden * * @param utf8Name The leaf name of the file, utf-8 encoded * @param parent The folder node under which this new file should appear * @param fingerprint The fingerprint for the uploaded file (use MegaApi::getFingerprint to generate this) * @param fingerprintoriginal If the file uploaded is modified from the original, * pass the fingerprint of the original file here, otherwise NULL. * @param string64UploadToken The token returned from the upload of the last portion of the file, * which is exactly 36 binary bytes, converted to a base 64 string with MegaApi::binaryToString64. * @param string64FileKey file encryption key converted to a base 64 string with MegaApi::binaryToString64. * @param listener MegaRequestListener to track this request */ void completeUpload(const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, const char *string64FileKey, MegaRequestListener *listener); /** * @brief Request the URL suitable for uploading a file. * * Note: added for the use of MEGAproxy and not otherwise supported * * This function requests the base URL needed for uploading the file. * The URL will need the urlSuffix resulting from encryption. * * The associated request type with this request is * MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL Valid data in the MegaRequest object received * in onRequestFinish when the error code is MegaError::API_OK: * - MegaRequest::getName - The URL to use * - MegaRequest::getLink - The IPv4 of the upload server * - MegaRequest::getText - The IPv6 of the upload server * * Call this function just once (per file) to find out the URL to upload to, and upload all * the pieces to the same URL. If errors are encountered and the operation must be restarted * from scratch, then a new URL should be requested. A new URL could specify a different * upload server for example. * * @param fullFileSize The size of the file * @param forceSSL Enforce getting a https URL * @param listener MegaRequestListener to track this request */ void getUploadURL(int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener); /** * @brief Request the URL suitable for uploading a thubmnail for a node. * * Note: added for the use of MEGAproxy * * This function requests the base URL needed for uploading the thumbnail. * * The associated request type with this request is MegaRequest::TYPE_GET_FA_UPLOAD_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - The URL to use * - MegaRequest::getLink - The IPv4 of the upload server * - MegaRequest::getText - The IPv6 of the upload server * * Call this function just once (per file) to find out the URL to upload to, and upload all * the pieces to the same URL. If errors are encountered and the operation must be restarted * from scratch, then a new URL should be requested. A new URL could specify a different * upload server for example. * * @param nodehandle handle of the node * @param fullFileSize The size of the thumbnail * @param forceSSL Enforce getting a https URL * @param listener MegaRequestListener to track this request */ void getThumbnailUploadURL(MegaHandle nodehandle, int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener); /** * @brief Request the URL suitable for uploading a preview for a node. * * Note: added for the use of MEGAproxy * * This function requests the base URL needed for uploading the preview. * * The associated request type with this request is MegaRequest::TYPE_GET_FA_UPLOAD_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - The URL to use * - MegaRequest::getLink - The IPv4 of the upload server * - MegaRequest::getText - The IPv6 of the upload server * * Call this function just once (per file) to find out the URL to upload to, and upload all * the pieces to the same URL. If errors are encountered and the operation must be restarted * from scratch, then a new URL should be requested. A new URL could specify a different * upload server for example. * * @param nodehandle handle of the node * @param fullFileSize The size of the preview * @param forceSSL Enforce getting a https URL * @param listener MegaRequestListener to track this request */ void getPreviewUploadURL(MegaHandle nodehandle, int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener); /** * @brief Create the node after completing the background upload of the file. * * Call this function after completing the background upload of all the file data * The node representing the file will be created in the cloud, with all the suitable * attributes and file attributes attached. * * The associated request type with this request is MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getMegaBackgroundMediaUploadPtr() - Returns the provided state * - MegaRequest::getPassword - Returns the original fingerprint * - MegaRequest::getNewPassword - Returns the fingerprint * - MegaRequest::getName - Returns the name * - MegaRequest::getParentHandle - Returns the parent nodehandle * - MegaRequest::getSessionKey - Returns the upload token converted to B64url encoding * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Returns the handle of the uploaded node * - MegaRequest::getFlag - True if target folder (\c parent) was overriden * * @param state The MegaBackgroundMediaUpload object tracking this upload * @param utf8Name The leaf name of the file, utf-8 encoded * @param parent The folder node under which this new file should appear * @param fingerprint The fingerprint for the uploaded file (use MegaApi::getFingerprint to generate this) * @param fingerprintoriginal If the file uploaded is modified from the original, * pass the fingerprint of the original file here, otherwise NULL. * @param string64UploadToken The token returned from the upload of the last portion of the file, * which is exactly 36 binary bytes, converted to a base 64 string with MegaApi::binaryToString64. * @param listener MegaRequestListener to track this request */ void backgroundMediaUploadComplete(MegaBackgroundMediaUpload* state, const char *utf8Name, MegaNode *parent, const char *fingerprint, const char *fingerprintoriginal, const char *string64UploadToken, MegaRequestListener *listener); /** * @brief Call this to enable the library to attach media info attributes * * Those attributes allows to know if a file is a video, and play it with the correct codec. * * If media info is not ready, this function returns false and automatically retrieves the mappings for type names * and MEGA encodings, required to analyse media files. When media info is received, the callbacks * MegaListener::onEvent and MegaGlobalListener::onEvent are called with the MegaEvent::EVENT_MEDIA_INFO_READY. * * @return True if the library is ready, otherwise false (the request for media translation data is sent to MEGA) */ bool ensureMediaInfo(); /** * @brief Set the OriginalFingerprint of a node. * * Use this call to attach an originalFingerprint to a node. The fingerprint must * be generated from the file prior to modification, where this node is the modified file. * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_NODE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the node * - MegaRequest::getText - Returns the specified fingerprint * - MegaRequest::getFlag - Returns true (official attribute) * - MegaRequest::getParamType - Returns MegaApi::NODE_ATTR_ORIGINALFINGERPRINT * * @param node The node to attach the originalFingerprint to. * @param originalFingerprint The fingerprint of the file before modification * @param listener MegaRequestListener to track this request */ void setOriginalFingerprint(MegaNode* node, const char* originalFingerprint, MegaRequestListener *listener); /** * @brief Convert a Base64 string to Base32 * * If the input pointer is NULL, this function will return NULL. * If the input character array isn't a valid base64 string * the effect is undefined * * You take ownership of the returned value. Use delete[] to release the memory. * * @param base64 NULL-terminated Base64 character array * @return NULL-terminated Base32 character array */ static char *base64ToBase32(const char *base64); /** * @brief Convert a Base32 string to Base64 * * If the input pointer is NULL, this function will return NULL. * If the input character array isn't a valid base32 string * the effect is undefined * * You take ownership of the returned value. Use delete[] to release the memory. * * @param base32 NULL-terminated Base32 character array * @return NULL-terminated Base64 character array */ static char *base32ToBase64(const char *base32); /** * @brief Function to copy a buffer * * The new buffer is allocated by new[] so you should release * it with delete[]. * If the function is passed NULL, it will return NULL. * * @param buffer Character buffer to copy * @return Copy of the character buffer */ static char* strdup(const char* buffer); /** * @brief Recursively remove all local files/folders inside a local path * @param path Local path of a folder to start the recursive deletion * The folder itself is not deleted */ static void removeRecursively(const char *path); /** * @brief Check if the connection with MEGA servers is OK * * It can briefly return false even if the connection is good enough when * some storage servers are temporarily not available or the load of API * servers is high. * * @return true if the connection is perfectly OK, otherwise false */ bool isOnline(); #ifdef HAVE_LIBUV enum { TCP_SERVER_DENY_ALL = -1, TCP_SERVER_ALLOW_ALL = 0, TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1, TCP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 }; //kept for backwards compatibility enum { HTTP_SERVER_DENY_ALL = -1, HTTP_SERVER_ALLOW_ALL = 0, HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1, HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 }; /** * @brief Start an HTTP proxy server in specified port * * If this function returns true, that means that the server is * ready to accept connections. The initialization is synchronous. * * The server will serve files using this URL format: * http://127.0.0.1// * * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MegaNode using MegaApi::httpServerGetLocalLink * * If the node handle belongs to a folder node, a web with the list of files * inside the folder is returned. * * It's important to know that the HTTP proxy server has several configuration options * that can restrict the nodes that will be served and the connections that will be * accepted. * * These are the default options: * - The restricted mode of the server is set to * MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS (see MegaApi::httpServerSetRestrictedMode) * * - Folder nodes are NOT allowed to be served (see MegaApi::httpServerEnableFolderServer) * - File nodes are allowed to be served (see MegaApi::httpServerEnableFileServer) * - Subtitles support is disabled (see MegaApi::httpServerEnableSubtitlesSupport) * * The HTTP server will only stream a node if it's allowed by all configuration options. * * @param localOnly true to listen on 127.0.0.1 only, false to listen on all network * interfaces * @param port Port in which the server must accept connections. A free port is selected if * it is 0. * @param useTLS Use TLS (default false). * If the SDK compilation does not support TLS, * enabling this flag will cause the function to return false. * @param certificatepath path to certificate (PEM format) * @param keypath path to certificate key * @param useIPv6 true to use [::1] as host, false to use 127.0.0.1 * @return True if the server is ready, false if the initialization failed */ bool httpServerStart(bool localOnly = true, int port = 4443, bool useTLS = false, const char *certificatepath = NULL, const char * keypath = NULL, bool useIPv6 = false); /** * @brief Stop the HTTP proxy server * * When this function returns, the server is already shutdown. * If the HTTP proxy server isn't running, this functions does nothing */ void httpServerStop(); /** * @brief Check if the HTTP proxy server is running * @return 0 if the server is not running. Otherwise the port in which it's listening to */ int httpServerIsRunning(); /** * @brief Check if the HTTP proxy server is listening on all network interfaces * @return true if the HTTP proxy server is listening on 127.0.0.1 only, or it's not started. * If it's started and listening on all network interfaces, this function returns false */ bool httpServerIsLocalOnly(); /** * @brief Allow/forbid to serve files * * By default, files are served (when the server is running) * * Even if files are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @param enable true to allow to server files, false to forbid it */ void httpServerEnableFileServer(bool enable); /** * @brief Check if it's allowed to serve files * * This function can return true even if the HTTP proxy server is not running * * Even if files are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @return true if it's allowed to serve files, otherwise false */ bool httpServerIsFileServerEnabled(); /** * @brief Allow/forbid to serve folders * * By default, folders are NOT served * * Even if folders are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @param enable true to allow to server folders, false to forbid it */ void httpServerEnableFolderServer(bool enable); /** * @brief Check if it's allowed to serve folders * * This function can return true even if the HTTP proxy server is not running * * Even if folders are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerSetRestrictedMode) are still applied. * * @return true if it's allowed to serve folders, otherwise false */ bool httpServerIsFolderServerEnabled(); /** * @brief Stablish FILE_ATTRIBUTE_OFFLINE attribute * * By default, it is not enabled * * This is used when serving files in WEBDAV, it will cause windows clients to not load a file * when it is selected. It is intended to reduce unnecessary traffic. * * @param enable true to enable the FILE_ATTRIBUTE_OFFLINE attribute, false to disable it */ void httpServerEnableOfflineAttribute(bool enable); /** * @brief Check if FILE_ATTRIBUTE_OFFLINE it's enabled * * @return true if the FILE_ATTRIBUTE_OFFLINE attribute is enabled, otherwise false */ bool httpServerIsOfflineAttributeEnabled(); /** * @brief Enable/disable the restricted mode of the HTTP server * * This function allows to restrict the nodes that are allowed to be served. * For not allowed links, the server will return "407 Forbidden". * * Possible values are: * - HTTP_SERVER_DENY_ALL = -1 * All nodes are forbidden * * - HTTP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served * * - HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 (default) * Only links created with MegaApi::httpServerGetLocalLink are allowed to be served * * - HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::httpServerGetLocalLink is allowed to be served * * If a different value from the list above is passed to this function, it won't have any effect and the previous * state of this option will be preserved. * * The default value of this property is MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS * * The state of this option is preserved even if the HTTP server is restarted, but * the HTTP proxy server only remembers the generated links since the last call to * MegaApi::httpServerStart * * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerEnableFileServer, * MegaApi::httpServerEnableFolderServer) are still applied. * * @param mode Required state for the restricted mode of the HTTP proxy server */ void httpServerSetRestrictedMode(int mode); /** * @brief Check if the HTTP proxy server is working in restricted mode * * Possible return values are: * - HTTP_SERVER_DENY_ALL = -1 * All nodes are forbidden * * - HTTP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served * * - HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 * Only links created with MegaApi::httpServerGetLocalLink are allowed to be served * * - HTTP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::httpServerGetLocalLink is allowed to be served * * The default value of this property is MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS * * See MegaApi::httpServerEnableRestrictedMode and MegaApi::httpServerStart * * Even if nodes are allowed to be served by this function, restrictions related to * other configuration options (MegaApi::httpServerEnableFileServer, * MegaApi::httpServerEnableFolderServer) are still applied. * * @return State of the restricted mode of the HTTP proxy server */ int httpServerGetRestrictedMode(); /** * @brief Enable/disable the support for subtitles * * Subtitles support allows to stream some special links that otherwise wouldn't be valid. * For example, let's suppose that the server is streaming this video: * http://120.0.0.1:4443//MyHolidays.avi * * Some media players scan HTTP servers looking for subtitle files and request links like these ones: * http://120.0.0.1:4443//MyHolidays.txt * http://120.0.0.1:4443//MyHolidays.srt * * Even if a file with that name is in the same folder of the MEGA account, the node wouldn't be served because * the node handle wouldn't match. * * When this feature is enabled, the HTTP proxy server will check if there are files with that name * in the same folder as the node corresponding to the handle in the link. * * If a matching file is found, the name is exactly the same as the node with the specified handle * (except the extension), the node with that handle is allowed to be streamed and this feature is enabled * the HTTP proxy server will serve that file. * * This feature is disabled by default. * * @param enable True to enable subtitles support, false to disable it */ void httpServerEnableSubtitlesSupport(bool enable); /** * @brief Check if the support for subtitles is enabled * * See MegaApi::httpServerEnableSubtitlesSupport. * * This feature is disabled by default. * * @return true of the support for subtibles is enables, otherwise false */ bool httpServerIsSubtitlesSupportEnabled(); /** * @brief Add a listener to receive information about the HTTP proxy server * * This is the valid data that will be provided on callbacks: * - MegaTransfer::getType - It will be MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD * - MegaTransfer::getPath - URL requested to the HTTP proxy server * - MegaTransfer::getFileName - Name of the requested file (if any, otherwise NULL) * - MegaTransfer::getNodeHandle - Handle of the requested file (if any, otherwise NULL) * - MegaTransfer::getTotalBytes - Total bytes of the response (response headers + file, if required) * - MegaTransfer::getStartPos - Start position (for range requests only, otherwise -1) * - MegaTransfer::getEndPos - End position (for range requests only, otherwise -1) * * On the onTransferFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINCOMPLETE - If the whole response wasn't sent * (it's normal to get this error code sometimes because media players close connections when they have * the data that they need) * * - MegaError::API_EREAD - If the connection with MEGA storage servers failed * - MegaError::API_EAGAIN - If the download speed is too slow for streaming * - A number > 0 means an HTTP error code returned to the client * * @param listener Listener to receive information about the HTTP proxy server */ void httpServerAddListener(MegaTransferListener *listener); /** * @brief Stop the reception of callbacks related to the HTTP proxy server on this listener * @param listener Listener that won't continue receiving information */ void httpServerRemoveListener(MegaTransferListener *listener); /** * @brief Returns a URL to a node in the local HTTP proxy server * * The HTTP proxy server must be running before using this function, otherwise * it will return NULL. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param node Node to generate the local HTTP link * @return URL to the node in the local HTTP proxy server, otherwise NULL */ char *httpServerGetLocalLink(MegaNode *node); /** * @brief Returns a WEBDAV valid URL to a node in the local HTTP proxy server * * The HTTP proxy server must be running before using this function, otherwise * it will return NULL. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param node Node to generate the local HTTP link * @return URL to the node in the local HTTP proxy server, otherwise NULL */ char *httpServerGetLocalWebDavLink(MegaNode *node); /** * @brief Returns the list with the links of locations served via WEBDAV * * The HTTP server must be running before using this function, otherwise * it will return NULL. * * You take the ownership of the returned value * * @return URL to the node in the local HTTP server, otherwise NULL */ MegaStringList *httpServerGetWebDavLinks(); /** * @brief Returns the list of nodes served via WEBDAV * * The HTTP server must be running before using this function, otherwise * it will return NULL. * * You take the ownership of the returned value * * @return URL to the node in the local HTTP server, otherwise NULL */ MegaNodeList *httpServerGetWebDavAllowedNodes(); /** * @brief Stops serving a node via webdav. * The webdav link will no longer be valid. * * @param handle Handle of the node to stop serving */ void httpServerRemoveWebDavAllowedNode(MegaHandle handle); /** * @brief Stops serving all nodes served via webdav. * The webdav links will no longer be valid. * */ void httpServerRemoveWebDavAllowedNodes(); /** * @brief Set the maximum buffer size for the internal buffer and the size of packets * sent to clients (MaxOutputSize) * * Current policy is to set MaxOutputSize to 10% of the param passed in this function. * Be aware that calling this method will overwrite any previous value of MaxOutputSize. * Therefore, any call to httpServerSetMaxOutputSize should be performed after a call to * this method. * * The HTTP proxy server has an internal buffer to store the data received from MEGA * while it's being sent to clients. When the buffer is full, the connection with * the MEGA storage server is closed, when the buffer has few data, the connection * with the MEGA storage server is started again. * * Even with very fast connections, due to the possible latency starting new connections, * if this buffer is small the streaming can have problems due to the overhead caused by * the excessive number of POST requests. * * It's recommended to set this buffer at least to 1MB * * For connections that request less data than the buffer size, the HTTP proxy server * will only allocate the required memory to complete the request to minimize the * memory usage. * * The new value will be taken into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param bufferSize Maximum buffer size (in bytes) or a number <= 0 to use the * internal default value */ void httpServerSetMaxBufferSize(int bufferSize); /** * @brief Get the maximum size of the internal buffer size * * See MegaApi::httpServerSetMaxBufferSize * * @return Maximum size of the internal buffer size (in bytes) */ int httpServerGetMaxBufferSize(); /** * @brief Set the maximum size of packets sent to clients * * For each connection, the HTTP proxy server only sends one write to the underlying * socket at once. This parameter allows to set the size of that write. * * A small value could cause a lot of writes and would lower the performance. * * A big value could send too much data to the output buffer of the socket. That could * keep the internal buffer full of data that hasn't been sent to the client yet, * preventing the retrieval of additional data from the MEGA storage server. In that * circumstances, the client could read a lot of data at once and the HTTP server * could not have enough time to get more data fast enough. * * It's recommended to set this value to at least 8192 and no more than the 25% of * the maximum buffer size (MegaApi::httpServerSetMaxBufferSize). * * The new value will be taken into account since the next request received by * the HTTP proxy server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param outputSize Maximun size of data packets sent to clients (in bytes) or * a number <= 0 to use the internal default value */ void httpServerSetMaxOutputSize(int outputSize); /** * @brief Get the maximum size of the packets sent to clients * * See MegaApi::httpServerSetMaxOutputSize * * @return Maximum size of the packets sent to clients (in bytes) */ int httpServerGetMaxOutputSize(); /** * @brief Start an FTP server in specified port * * If this function returns true, that means that the server is * ready to accept connections. The initialization is synchronous. * * The server will serve files using this URL format: * ftp://127.0.0.1:PORT// * * The node name must be URL encoded and must match with the node handle. * You can generate a correct link for a MegaNode using MegaApi::ftpServerGetLocalLink * * It's important to know that the FTP server has several configuration options * that can restrict the nodes that will be served and the connections that will be * accepted. * * These are the default options: * - The restricted mode of the server is set to * MegaApi::FTP_SERVER_ALLOW_CREATED_LOCAL_LINKS (see MegaApi::ftpServerSetRestrictedMode) * * The FTP server will only stream a node if it's allowed by all configuration options. * * @param localOnly true to listen on 127.0.0.1 only, false to listen on all network * interfaces * @param port Port in which the server must accept connections. A free port is selected if * it is 0. * @param dataportBegin Initial port for FTP data channel * @param dataPortEnd Final port for FTP data channel (included) * @param useTLS Use TLS (default false) * @param certificatepath path to certificate (PEM format) * @param keypath path to certificate key * @return True if the server is ready, false if the initialization failed */ bool ftpServerStart(bool localOnly = true, int port = 22, int dataportBegin = 1500, int dataPortEnd = 1600, bool useTLS = false, const char *certificatepath = NULL, const char * keypath = NULL); /** * @brief Stop the FTP server * * When this function returns, the server is already shutdown. * If the FTP server isn't running, this functions does nothing */ void ftpServerStop(); /** * @brief Check if the FTP server is running * @return 0 if the server is not running. Otherwise the port in which it's listening to */ int ftpServerIsRunning(); /** * @brief Check if the FTP server is listening on all network interfaces * @return true if the FTP server is listening on 127.0.0.1 only, or it's not started. * If it's started and listening on all network interfaces, this function returns false */ bool ftpServerIsLocalOnly(); /** * @brief Enable/disable the restricted mode of the FTP server * * This function allows to restrict the nodes that are allowed to be served. * For not allowed links, the server will return a corresponding "550" error. * * Possible values are: * - TCP_SERVER_DENY_ALL = -1 * All nodes are forbidden * * - TCP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served * * - TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 (default) * Only links created with MegaApi::ftpServerGetLocalLink are allowed to be served * * - TCP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::ftpServerGetLocalLink is allowed to be served * * If a different value from the list above is passed to this function, it won't have any effect and the previous * state of this option will be preserved. * * The default value of this property is MegaApi::FTP_SERVER_ALLOW_CREATED_LOCAL_LINKS * * The state of this option is preserved even if the FTP server is restarted, but the * the FTP server only remembers the generated links since the last call to * MegaApi::ftpServerStart * * @param mode State for the restricted mode of the FTP server */ void ftpServerSetRestrictedMode(int mode); /** * @brief Check if the FTP server is working in restricted mode * * Possible return values are: * - TCP_SERVER_DENY_ALL = -1 * All nodes are forbidden * * - TCP_SERVER_ALLOW_ALL = 0 * All nodes are allowed to be served * * - TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS = 1 * Only links created with MegaApi::ftpServerGetLocalLink are allowed to be served * * - TCP_SERVER_ALLOW_LAST_LOCAL_LINK = 2 * Only the last link created with MegaApi::ftpServerGetLocalLink is allowed to be served * * The default value of this property is MegaApi::FTP_SERVER_ALLOW_CREATED_LOCAL_LINKS * * See MegaApi::ftpServerEnableRestrictedMode and MegaApi::ftpServerStart * * @return State of the restricted mode of the FTP server */ int ftpServerGetRestrictedMode(); /** * @brief Add a listener to receive information about the FTP server * * This is the valid data that will be provided on callbacks: * - MegaTransfer::getType - It will be MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD * - MegaTransfer::getPath - URL requested to the FTP server * - MegaTransfer::getFileName - Name of the requested file (if any, otherwise NULL) * - MegaTransfer::getNodeHandle - Handle of the requested file (if any, otherwise NULL) * - MegaTransfer::getTotalBytes - Total bytes of the response (response headers + file, if required) * - MegaTransfer::getStartPos - Start position (for range requests only, otherwise -1) * - MegaTransfer::getEndPos - End position (for range requests only, otherwise -1) * * On the onTransferFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINCOMPLETE - If the whole response wasn't sent * (it's normal to get this error code sometimes because media players close connections when they have * the data that they need) * * - MegaError::API_EREAD - If the connection with MEGA storage servers failed * - MegaError::API_EAGAIN - If the download speed is too slow for streaming * - A number > 0 means an FTP error code returned to the client * * @param listener Listener to receive information about the FTP server */ void ftpServerAddListener(MegaTransferListener *listener); /** * @brief Stop the reception of callbacks related to the FTP server on this listener * @param listener Listener that won't continue receiving information */ void ftpServerRemoveListener(MegaTransferListener *listener); /** * @brief Returns a URL to a node in the local FTP server * * The FTP server must be running before using this function, otherwise * it will return NULL. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param node Node to generate the local FTP link * @return URL to the node in the local FTP server, otherwise NULL */ char *ftpServerGetLocalLink(MegaNode *node); /** * @brief Returns the list with the links of locations served via FTP * * The FTP server must be running before using this function, otherwise * it will return NULL. * * You take the ownership of the returned value * * @return URL to the node in the local FTP server, otherwise NULL */ MegaStringList *ftpServerGetLinks(); /** * @brief Returns the list of nodes served via FTP * * The FTP server must be running before using this function, otherwise * it will return NULL. * * You take the ownership of the returned value * * @return URL to the node in the local FTP server, otherwise NULL */ MegaNodeList *ftpServerGetAllowedNodes(); /** * @brief Stops serving a node via ftp. * The ftp link will no longer be valid. * * @param handle Handle of the node to stop serving */ void ftpServerRemoveAllowedNode(MegaHandle handle); /** * @brief Stops serving all nodes served via ftp. * The ftp links will no longer be valid. * */ void ftpServerRemoveAllowedNodes(); /** * @brief Set the maximum buffer size for the internal buffer * * The FTP server has an internal buffer to store the data received from MEGA * while it's being sent to clients. When the buffer is full, the connection with * the MEGA storage server is closed, when the buffer has few data, the connection * with the MEGA storage server is started again. * * Even with very fast connections, due to the possible latency starting new connections, * if this buffer is small the streaming can have problems due to the overhead caused by * the excessive number of RETR/REST requests. * * It's recommended to set this buffer at least to 1MB * * For connections that request less data than the buffer size, the FTP server * will only allocate the required memory to complete the request to minimize the * memory usage. * * The new value will be taken into account since the next request received by * the FTP server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param bufferSize Maximum buffer size (in bytes) or a number <= 0 to use the * internal default value */ void ftpServerSetMaxBufferSize(int bufferSize); /** * @brief Get the maximum size of the internal buffer size * * See MegaApi::ftpServerSetMaxBufferSize * * @return Maximum size of the internal buffer size (in bytes) */ int ftpServerGetMaxBufferSize(); /** * @brief Set the maximum size of packets sent to clients * * For each connection, the FTP server only sends one write to the underlying * socket at once. This parameter allows to set the size of that write. * * A small value could cause a lot of writes and would lower the performance. * * A big value could send too much data to the output buffer of the socket. That could * keep the internal buffer full of data that hasn't been sent to the client yet, * preventing the retrieval of additional data from the MEGA storage server. In that * circumstances, the client could read a lot of data at once and the FTP server * could not have enough time to get more data fast enough. * * It's recommended to set this value to at least 8192 and no more than the 25% of * the maximum buffer size (MegaApi::ftpServerSetMaxBufferSize). * * The new value will be taken into account since the next request received by * the FTP server, not for ongoing requests. It's possible and effective * to call this function even before the server has been started, and the value * will be still active even if the server is stopped and started again. * * @param outputSize Maximun size of data packets sent to clients (in bytes) or * a number <= 0 to use the internal default value */ void ftpServerSetMaxOutputSize(int outputSize); /** * @brief Get the maximum size of the packets sent to clients * * See MegaApi::ftpServerSetMaxOutputSize * * @return Maximum size of the packets sent to clients (in bytes) */ int ftpServerGetMaxOutputSize(); #endif /** * @brief Get the MIME type associated with the extension * * You take ownership of the returned value. Use delete[] to release the memory. * * @param extension File extension (with or without a leading dot) * @return MIME type associated with the extension */ static char *getMimeType(const char* extension); #ifdef ENABLE_CHAT /** * @brief Creates a chat for one or more participants, allowing you to specify their * permissions and if the chat should be a group chat or not (when it is just for 2 * participants). * * There are two types of chat: permanent an group. A permanent chat is between two people, * or only with ourselves (note-to-self chat), and participants can not leave it. It's also * called 1on1 or 1:1. * * The creator of the chat will have moderator level privilege and should not be included in * the list of peers. * * On 1:1 chats with a peer, the other participant has also moderator level privilege, * regardless the privilege level specified. * * The associated request type with this request is MegaRequest::TYPE_CHAT_CREATE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Returns if the new chat is a group chat or permanent chat * - MegaRequest::getAccess - Returns zero (private mode) * - MegaRequest::getMegaTextChatPeerList - List of participants and their privilege level. * Note-to-self chats have no peers, so this function will return \c nullptr * - MegaRequest::getText - Returns the title of the chat. * - MegaRequest::getParamType - Returns a Bitmask with the chat options that will be * enabled in creation * - MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with a * MegaScheduledMeeting with data introduced by user) * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaTextChatList - Returns the new chat's information * - MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with * definitive ScheduledMeeting updated from API) * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If more than 1 peer is provided for a 1on1 chatroom. * - MegaError::API_EARGS - If chatOptions param is provided for a 1on1 chat * * @note If peers list contains only one person, group chat is not set and a permament chat * already exists with that person, then this call will return the information for the * existing chat, rather than a new chat. * * @param group Flag to indicate if the chat is a group chat or not * @param peers MegaTextChatPeerList including other users and their privilege level * @param title Byte array that contains the chat topic if exists. NULL if no custom title * is required. * @param chatOptions Bitmask that contains the chat options to create the chat * @param scheduledMeeting MegaScheduledMeeting with data introduced by user * @param listener MegaRequestListener to track this request */ void createChat(bool group, MegaTextChatPeerList* peers, const char* title = NULL, int chatOptions = CHAT_OPTIONS_EMPTY, const MegaScheduledMeeting* scheduledMeeting = NULL, MegaRequestListener* listener = NULL); /** * @brief Creates a public chatroom for multiple participants (groupchat) * * This function allows to create public chats, where the moderator can create chat links to share * the access to the chatroom via a URL (chat-link). In order to create a public chat-link, the * moderator needs to create / get a public handle for the chatroom by using \c MegaApi::chatLinkCreate. * * The resulting chat-link allows anyone (even users without an account in MEGA) to review the * history of the chatroom. The \c MegaApi::getChatLinkURL provides the chatd URL to connect. * * Users with an account in MEGA can freely join the room by themselves (the privilege * upon join will be standard / read-write) by using \c MegaApi::chatLinkJoin. * * The creator of the chat will have moderator level privilege and should not be included in the * list of peers. * * The associated request type with this request is MegaChatRequest::TYPE_CREATE_CHATROOM * Valid data in the MegaChatRequest object received on callbacks: * - MegaChatRequest::getFlag - Returns if the new chat is a group chat or permanent chat * - MegaRequest::getAccess - Returns one (public mode) * - MegaChatRequest::getMegaChatPeerList - List of participants and their privilege level * - MegaChatRequest::getMegaStringMap - MegaStringMap with handles and unified keys or each peer * - MegaRequest::getText - Returns the title of the chat. * - MegaRequest::getNumber - Returns if chat room is a meeting room * - MegaRequest::getParamType - Returns a Bitmask with the chat options that will be enabled in creation * - MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with a MegaScheduledMeeting with data introduced by user) * * Valid data in the MegaChatRequest object received in onRequestFinish when the error code * is MegaError::ERROR_OK: * - MegaChatRequest::getChatHandle - Returns the handle of the new chatroom * - MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with definitive ScheduledMeeting updated from API) * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the number of keys doesn't match the number of peers plus one (own user) * * @param peers MegaChatPeerList including other users and their privilege level * @param title Byte array that contains the chat topic if exists. NULL if no custom title is required. * @param userKeyMap MegaStringMap of user handles in B64 as keys, and unified keys in B64 as values. Own user included * @param meetingRoom Boolean indicating if room is a meeting room * @param chatOptions Bitmask that contains the chat options to create the chat * @param scheduledMeeting MegaScheduledMeeting with data introduced by user * @param listener MegaChatRequestListener to track this request */ void createPublicChat(MegaTextChatPeerList* peers, const MegaStringMap* userKeyMap, const char *title = NULL, bool meetingRoom = false, int chatOptions = CHAT_OPTIONS_EMPTY, const MegaScheduledMeeting* scheduledMeeting = NULL, MegaRequestListener* listener = NULL); /** * @brief Enable or disable a option for a chatroom * * This function allows to enable or disable one of the following chatroom options: * - 0x01: SpeakRequest: during calls non-operator users must request permission to speak. * - 0x02: WaitingRoom: during calls non-operator members will be placed into a waiting room, an operator level user must grant each user access to the call. * - 0x04: OpenInvite: when enabled allows non-operator level users to invite others into the chat room. * * The associated request type with this request is MegaRequest::TYPE_SET_CHAT_OPTIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::Access - Returns the chat option we want to enable disable * - MegaRequest::getFlag - Returns true if enabled was set true, otherwise it will return false * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the chatid is invalid * - MegaError::API_EARGS - If this method is called for a 1on1 chat * - MegaError::API_ENOENT - If the chatroom does not exists * * @param chatid MegaHandle that identifies the chat room * @param option Chat option that we want to enable/disable * @param enabled True if we want to enable the option, otherwise false. * @param listener MegaRequestListener to track this request */ void setChatOption(MegaHandle chatid, int option, bool enabled, MegaRequestListener* listener = NULL); /** * @brief Creates or updates a scheduled meeting * * @note If chatTitle is provided, this method will also update chatroom title. * * The associated request type with this request is MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with a MegaScheduledMeeting with data introduced by user) * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::ERROR_OK: * MegaRequest::getMegaScheduledMeetingList - returns a MegaScheduledMeetingList (with a MegaScheduledMeeting with definitive ScheduledMeeting updated from API) * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - if no scheduled meeting is provided * - MegaError::API_EARGS - if chatTitle length is zero * - MegaError::API_ENOENT - if the chatroom does not exists * * @param scheduledMeeting MegaScheduledMeeting with data introduced by user * @param chatTitle Byte array representing the chatroom title, already encrypted for all participants, * and converted to Base64url encoding. * @param listener MegaRequestListener to track this request */ void createOrUpdateScheduledMeeting(const MegaScheduledMeeting* scheduledMeeting, const char *chatTitle, MegaRequestListener* listener = NULL); /** * @brief Removes a scheduled meeting by scheduled meeting id and chatid * * The associated request type with this request is MegaRequest::TYPE_DEL_SCHEDULED_MEETING * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the chatroom * - MegaRequest::getParentHandle - Returns the scheduled meeting id * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom or scheduled meeting does not exists * * @param chatid MegaHandle that identifies a chat room * @param schedId MegaHandle that identifies a scheduled meeting * @param listener MegaRequestListener to track this request * * @deprecated This function is deprecated. Please don't use it in new code. * Use createOrUpdateScheduledMeeting and set cancelled flag `True` at MegaScheduledMeeting. * Note: You can use MegaScheduledMeeting::createInstance with cancelled param `True */ MEGA_DEPRECATED void removeScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener = NULL); /** * @brief Get a list of all scheduled meeting for a chatroom, or a specific scheduled meeting (given a scheduled meeting id) * * Important consideration: * For every chatroom there should only exist one root scheduled meeting associated, it means that for all scheduled meeting * returned by this method, there should be just one scheduled meeting, with an invalid parent sched Id (MegaScheduledMeeting::parentSchedId), * for every different chatid. * * The associated request type with this request is MegaRequest::TYPE_FETCH_SCHEDULED_MEETING * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the chatroom * - MegaRequest::getParentHandle - Returns the scheduled meeting id * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not exists * * @param chatid MegaHandle that identifies a chatroom * @param schedId MegaHandle that identifies a scheduled meeting * @param listener MegaRequestListener to track this request */ void fetchScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener = NULL); /** * @brief Get a list of scheduled meeting occurrences for a chatroom * * A scheduled meetings occurrence, is a call that will happen in the future * A scheduled meeting can produce one or multiple scheduled meeting occurrences * * The associated request type with this request is MegaRequest::TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the handle of the chatroom * - MegaRequest::getNumber - Returns the dateTime from which we want to fetch occurrences * - MegaRequest::getTotalBytes - Returns the dateTime until we want to fetch occurrences * - MegaRequest::getTransferredBytes - Returns the number of occurrences we want to fetch * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not exists * * @param chatid MegaHandle that identifies a chat room * @param since DateTime from which we want to fetch occurrences (unix timestamp) * @param until Datetime until we want to fetch occurrences (unix timestamp) * @param count Number of occurrences we want to fetch * @param listener MegaChatRequestListener to track this request */ void fetchScheduledMeetingEvents(MegaHandle chatid, MegaTimeStamp since, MegaTimeStamp until, unsigned int count, MegaRequestListener* listener = NULL); /** * @brief Adds a user to an existing chat. To do this you must have the * operator privilege in the chat, and the chat must be a group chat in private mode. * * In case the chat has a title already set, the title must be encrypted for the new * peer and passed to this function. Note that only participants with privilege level * MegaTextChatPeerList::PRIV_MODERATOR are allowed to set the title of a chat. * * The associated request type with this request is MegaRequest::TYPE_CHAT_INVITE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the MegaHandle of the user to be invited * - MegaRequest::getAccess - Returns the privilege level wanted for the user * - MegaRequest::getText - Returns the title of the chat * - MegaRequest::getFlag - Returns false (private/closed mode) * - MegaRequest::getSessionKey - Returns the unified key for the new peer * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If the logged in user doesn't have privileges to invite peers or the chatroom is in public mode. * - MegaError::API_EINCOMPLETE - If no valid title is provided and the chatroom has a custom title already. * - MegaError::API_ENOENT- If no valid chatid or user handle is provided, of if the chatroom does not exists. * * @param chatid MegaHandle that identifies the chat room * @param uh MegaHandle that identifies the user * @param privilege Privilege level for the new peers. Valid values are: * - MegaTextChatPeerList::PRIV_UNKNOWN = -2 * - MegaTextChatPeerList::PRIV_RM = -1 * - MegaTextChatPeerList::PRIV_RO = 0 * - MegaTextChatPeerList::PRIV_STANDARD = 2 * - MegaTextChatPeerList::PRIV_MODERATOR = 3 * @param title Byte array representing the title that wants to be set, already encrypted and * converted to Base64url encoding (optional). * @param listener MegaRequestListener to track this request */ void inviteToChat(MegaHandle chatid, MegaHandle uh, int privilege, const char *title = NULL, MegaRequestListener *listener = NULL); /** * @brief Adds a user to an existing chat. To do this you must have the * operator privilege in the chat, and the chat must be a group chat in public mode. * * The associated request type with this request is MegaRequest::TYPE_CHAT_INVITE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the MegaHandle of the user to be invited * - MegaRequest::getAccess - Returns the privilege level wanted for the user * - MegaRequest::getFlag - Returns true (open/public mode) * - MegaRequest::getSessionKey - Returns the unified key for the new peer * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If the logged in user doesn't have privileges to invite peers or the chatroom is in private mode. * - MegaError::API_EARGS - If there's a title and it's not Base64url encoded. * - MegaError::API_ENOENT- If no valid chatid or user handle is provided, of if the chatroom does not exists. * - MegaError::API_EINCOMPLETE - If no unified key is provided. * * @param chatid MegaHandle that identifies the chat room * @param uh MegaHandle that identifies the user * @param privilege Privilege level for the new peers. Valid values are: * - MegaTextChatPeerList::PRIV_UNKNOWN = -2 * - MegaTextChatPeerList::PRIV_RM = -1 * - MegaTextChatPeerList::PRIV_RO = 0 * - MegaTextChatPeerList::PRIV_STANDARD = 2 * - MegaTextChatPeerList::PRIV_MODERATOR = 3 * @param unifiedKey Byte array that contains the unified key, already encrypted and * converted to Base64url encoding. * @param listener MegaRequestListener to track this request */ void inviteToPublicChat(MegaHandle chatid, MegaHandle uh, int privilege, const char *unifiedKey = NULL, MegaRequestListener *listener = NULL); /** * @brief Remove yourself or another user from a chat. To remove a user other than * yourself you need to have the operator privilege. Only a group chat may be left. * * The associated request type with this request is MegaRequest::TYPE_CHAT_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the MegaHandle of the user to be removed * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT- If no valid chatid is provided or the chatroom does not exists. * - MegaError::API_EACCESS - If the chatroom is 1on1 or the caller is not operator or is not a * chat member, or the target is not a chat member. * * @param chatid MegaHandle that identifies the chat room * @param uh MegaHandle that identifies the user. If not provided (INVALID_HANDLE), the requester is removed * @param listener MegaRequestListener to track this request */ void removeFromChat(MegaHandle chatid, MegaHandle uh = INVALID_HANDLE, MegaRequestListener *listener = NULL); /** * @brief Get your current, user-specific url to connect to chatd with * * The associated request type with this request is MegaRequest::TYPE_CHAT_URL * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Returns the user-specific URL for the chat * * @param chatid MegaHandle that identifies the chat room * @param listener MegaRequestListener to track this request */ void getUrlChat(MegaHandle chatid, MegaRequestListener *listener = NULL); /** * @brief Grants another user access to download a file using MegaApi::startDownload like * a user would do so for their own file, rather than a public link. * * Currently, this method only supports files, not folders. * * The associated request type with this request is MegaRequest::TYPE_CHAT_GRANT_ACCESS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the node handle * - MegaRequest::getParentHandle - Returns the chat identifier * - MegaRequest::getEmail - Returns the MegaHandle of the user in Base64 enconding * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT- If the chatroom, the node or the target user don't exist. * - MegaError::API_EACCESS- If the target user is the same as caller, or if the target * user is anonymous but the chatroom is in private mode, or if caller is not an operator * or the target user is not a chat member. * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param chatid MegaHandle that identifies the chat room * @param n MegaNode that wants to be shared * @param uh MegaHandle that identifies the user * @param listener MegaRequestListener to track this request */ void grantAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener = NULL); /** * @brief Removes access to a node from a user you previously granted access to. * * The associated request type with this request is MegaRequest::TYPE_CHAT_REMOVE_ACCESS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the node handle * - MegaRequest::getParentHandle - Returns the chat identifier * - MegaRequest::getEmail - Returns the MegaHandle of the user in Base64 enconding * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT- If the chatroom, the node or the target user don't exist. * * @param chatid MegaHandle that identifies the chat room * @param n MegaNode whose access wants to be revokesd * @param uh MegaHandle that identifies the user * @param listener MegaRequestListener to track this request */ void removeAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener = NULL); /** * @brief Allows a logged in operator/moderator to adjust the permissions on any other user * in their group chat. This does not work for a 1:1 chat. * * The associated request type with this request is MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the MegaHandle of the user whose permission * is to be upgraded * - MegaRequest::getAccess - Returns the privilege level wanted for the user * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT- If the chatroom doesn't exist or if the user specified is not a participant. * - MegaError::API_EACCESS- If caller is not operator or the chatroom is 1on1. * * @param chatid MegaHandle that identifies the chat room * @param uh MegaHandle that identifies the user * @param privilege Privilege level for the existing peer. Valid values are: * - MegaTextChatPeerList::PRIV_RO = 0 * - MegaTextChatPeerList::PRIV_STANDARD = 2 * - MegaTextChatPeerList::PRIV_MODERATOR = 3 * @param listener MegaRequestListener to track this request */ void updateChatPermissions(MegaHandle chatid, MegaHandle uh, int privilege, MegaRequestListener *listener = NULL); /** * @brief Allows a logged in operator/moderator to truncate their chat, i.e. to clear * the entire chat history up to a certain message. All earlier messages are wiped, * but his specific message gets overridden with an API message. * * The associated request type with this request is MegaRequest::TYPE_CHAT_TRUNCATE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the message identifier to truncate from. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT- If the chatroom doesn't exist. * * @param chatid MegaHandle that identifies the chat room * @param messageid MegaHandle that identifies the message to truncate from * @param listener MegaRequestListener to track this request */ void truncateChat(MegaHandle chatid, MegaHandle messageid, MegaRequestListener *listener = NULL); /** * @brief Allows to set the title of a chat * * Only participants with privilege level MegaTextChatPeerList::PRIV_MODERATOR are allowed to * set the title of a chat. * * The associated request type with this request is MegaRequest::TYPE_CHAT_SET_TITLE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the title of the chat. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If the logged in user doesn't have privileges to invite peers. * - MegaError::API_EARGS - If there's a title and it's not Base64url encoded. * - MegaError::API_ENOENT- If the chatroom doesn't exist. * * @param chatid MegaHandle that identifies the chat room * @param title Byte array representing the title that wants to be set, already encrypted and * converted to Base64url encoding. * @param listener MegaRequestListener to track this request */ void setChatTitle(MegaHandle chatid, const char *title, MegaRequestListener *listener = NULL); /** * @brief Get your current URL to connect to the presence server * * The associated request type with this request is MegaRequest::TYPE_CHAT_PRESENCE_URL * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getLink - Returns the user-specific URL for the chat presence server * * @param listener MegaRequestListener to track this request */ void getChatPresenceURL(MegaRequestListener *listener = NULL); /** * @brief Register a token for push notifications * * This function attach a token to the current session, which is intended to get push notifications * on mobile platforms like Android and iOS. * * The push notification mechanism is platform-dependent. Hence, the app should indicate the * type of push notification to be registered. Currently, the different types are: * - MegaApi::PUSH_NOTIFICATION_ANDROID = 1 * - MegaApi::PUSH_NOTIFICATION_IOS_VOIP = 2 * - MegaApi::PUSH_NOTIFICATION_IOS_STD = 3 * - MegaApi::PUSH_NOTIFICATION_ANDROID_HUAWEI = 4 * * The associated request type with this request is MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the token provided. * - MegaRequest::getNumber - Returns the device type provided. * * @param deviceType Type of notification to be registered. * @param token Character array representing the token to be registered. * @param listener MegaRequestListener to track this request */ void registerPushNotifications(int deviceType, const char *token, MegaRequestListener *listener = NULL); /** * @brief Send data related to MEGAchat to the stats server * * The associated request type with this request is MegaRequest::TYPE_CHAT_STATS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the data provided. * - MegaRequest::getParamType - Returns number 1 * - MegaRequest::getNumber - Returns the connection port * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Return the HTTP status code from the stats server * - MegaRequest::getText - Returns the JSON response from the stats server * - MegaRequest::getTotalBytes - Returns the number of bytes in the response * * @param data JSON data to send to the stats server * @param port Server port to connect * @param listener MegaRequestListener to track this request */ void sendChatStats(const char *data, int port = 0, MegaRequestListener *listener = NULL); /** * @brief Send logs related to MEGAchat to the logs server * * The associated request type with this request is MegaRequest::TYPE_CHAT_STATS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the data provided. * - MegaRequest::getNodeHandle - Returns the userid * - MegaRequest::getParentHandle - Returns the provided callid * - MegaRequest::getParamType - Returns number 2 * - MegaRequest::getNumber - Returns the connection port * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Return the HTTP status code from the stats server * - MegaRequest::getText - Returns the JSON response from the stats server * - MegaRequest::getTotalBytes - Returns the number of bytes in the response * * @param data JSON data to send to the logs server * @param userid handle of the user * @param callid handle of the call * @param port Server port to connect * @param listener MegaRequestListener to track this request */ void sendChatLogs(const char *data, MegaHandle userid, MegaHandle callid = INVALID_HANDLE, int port = 0, MegaRequestListener *listener = NULL); /** * @brief Get the list of chatrooms for this account * * You take the ownership of the returned value * * @return A list of MegaTextChat objects with detailed information about each chatroom. */ MegaTextChatList *getChatList(); /** * @brief Get the list of users with access to the specified node * * You take the ownership of the returned value * * @param chatid MegaHandle that identifies the chat room * @param h MegaNode to check the access * * @return A list of user handles that have access to the node */ MegaHandleList *getAttachmentAccess(MegaHandle chatid, MegaHandle h); /** * @brief Check if the logged-in user has access to the specified node * * @param chatid MegaHandle that identifies the chat room * @param h MegaHandle that identifies the node to check the access * @param uh MegaHandle that identifies the user to check the access * * @return True the user has access to the node in that chat. Otherwise, it returns false */ bool hasAccessToAttachment(MegaHandle chatid, MegaHandle h, MegaHandle uh); /** * @brief Get files attributes from a node * You take ownership of the returned value. Use delete[] to release the memory. * @param h Handle from node * @return char array with files attributes from the node. */ const char* getFileAttribute(MegaHandle h); /** * @brief Archive a chat * * The associated request type with this request is MegaRequest::TYPE_CHAT_ARCHIVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getFlag - Returns chat desired state * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not exists. * * @param chatid MegaHandle that identifies the chat room * @param archive Desired chat state * @param listener MegaRequestListener to track this request */ void archiveChat(MegaHandle chatid, int archive, MegaRequestListener *listener = NULL); /** * @brief Set a retention timeframe after which older messages in the chat are automatically deleted. * * Allows a logged in operator/moderator to specify a message retention timeframe in seconds, * after which older messages in the chat are automatically deleted. * * The associated request type with this request is MegaRequest::TYPE_SET_RETENTION_TIME * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getTotalBytes - Returns the retention timeframe * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the chatid is invalid * - MegaError::API_ENOENT - If there isn't any chat with the specified chatid. * - MegaError::API_EACCESS - If the logged in user doesn't have operator privileges * * @param chatid MegaHandle that identifies the chat room * @param period retention timeframe in seconds, after which older messages in the chat are automatically deleted * @param listener MegaRequestListener to track this request */ void setChatRetentionTime(MegaHandle chatid, unsigned period, MegaRequestListener *listener = NULL); /** * @brief Request rich preview information for specified URL * * The associated request type with this request is MegaRequest::TYPE_RICH_LINK * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the requested URL * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns a JSON containing metadata from the URL * * @param url URL to request metadata (format: http://servername.domain) * @param listener MegaRequestListener to track this request */ void requestRichPreview(const char *url, MegaRequestListener *listener = NULL); /** * @brief Query if there is a chat link for this chatroom * * This function can be called by any chat member to check and retrieve the current * public handle for the specified chat without creating it. * * The associated request type with this request is MegaRequest::TYPE_CHAT_LINK_HANDLE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the public handle of the chat link, if any * * On the onTransferFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not have a valid chatlink, or the chatroom does not exists. * - MegaError::API_EACCESS - If caller is not operator or the chat is not a public chat or it's a 1on1 room. * * @param chatid MegaHandle that identifies the chat room * @param listener MegaRequestListener to track this request */ void chatLinkQuery(MegaHandle chatid, MegaRequestListener *listener = NULL); /** * @brief Create or retrieve the public handle of a chat link * * This function can be called by a chat operator to create or retrieve the current * public handle for the specified chat. It will create a management message. * * The associated request type with this request is MegaRequest::TYPE_CHAT_LINK_HANDLE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the public handle of the chat link * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not have a valid chatlink, or the chatroom does not exists. * - MegaError::API_EACCESS - If caller is not operator or the chat is not a public chat or it's a 1on1 room. * * @param chatid MegaHandle that identifies the chat room * @param listener MegaRequestListener to track this request */ void chatLinkCreate(MegaHandle chatid, MegaRequestListener *listener = NULL); /** * @brief Delete the public handle of a chat link * * This function can be called by a chat operator to remove the current public handle * for the specified chat. It will create a management message. * * The associated request type with this request is MegaRequest::TYPE_CHAT_LINK_HANDLE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not have a valid chatlink, or the chatroom does not exists. * - MegaError::API_EACCESS - If caller is not operator or the chat is not a public chat or it's a 1on1 room. * * @param chatid MegaHandle that identifies the chat room * @param listener MegaRequestListener to track this request */ void chatLinkDelete(MegaHandle chatid, MegaRequestListener *listener = NULL); /** * @brief Get the URL to connect to chatd for a chat link * * This function can be used by anonymous and registered users to request the URL to connect * to chatd, for a given public handle. @see \c MegaApi::chatLinkCreate. * It also returns the shard hosting the chatroom, the real chatid and the title (if any). * The chat-topic, for public chats, can be decrypted by using the unified-key, already * available as part of the link for previewers and available to participants as part of * the room's information. @see \c MegaTextChat::getUnifiedKey. * * The associated request type with this request is MegaRequest::TYPE_CHAT_LINK_URL * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the public handle of the chat link * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNodeHandle - Returns the public handle * - MegaRequest::getLink - Returns the URL to connect to chatd for the chat link * - MegaRequest::getParentHandle - Returns the chat identifier * - MegaRequest::getAccess - Returns the shard * - MegaRequest::getText - Returns the chat-topic (if any) * - MegaRequest::getNumDetails - Returns the current number of participants * - MegaRequest::getNumber - Returns the creation timestamp * - MegaRequest::getFlag - Returns if chatRoom is a meeting Room * - MegaRequest::getParamType - Returns 1 if chatRoom has waiting room option enabled * - MegaRequest::getMegaHandleList - Returns a vector with one element (callid), if call doesn't exit it will be NULL * - MegaRequest::getMegaScheduledMeetingList - Returns a MegaScheduledMeetingList (with a list of scheduled meetings associated to the chatroom) or nullptr if none. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the public handle is not valid or the chatroom does not exists. * * @note This function can be called without being logged in. In that case, the returned * URL will be different than for logged in users, so chatd knows whether user has a session. * * @param publichandle MegaHandle that represents the public handle of the chat link * @param listener MegaRequestListener to track this request */ void getChatLinkURL(MegaHandle publichandle, MegaRequestListener *listener = NULL); /** * @brief Convert an public chat into a private private mode chat * * This function allows a chat operator to convert an existing public chat into a private * chat (closed mode, key rotation enabled). It will create a management message. * * If the groupchat already has a customized title, it's required to provide the title encrypted * to a new key, so it becomes private for non-participants. * * The associated request type with this request is MegaRequest::TYPE_SET_PRIVATE_MODE. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getText - Returns the title of the chat * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the chatroom does not exists. * - MegaError::API_EACCESS - If caller is not operator or it's a 1on1 room. * - MegaError::API_EEXIST - If the chat is already in private mode. * - MegaError::API_EARGS - If custom title is set and no title is provided. * * @param chatid MegaHandle that identifies the chat room * @param title Byte array representing the title, already encrypted and converted to Base64url * encoding. If the chatroom doesn't have a title yet, this parameter should be NULL. * @param listener MegaRequestListener to track this request */ void chatLinkClose(MegaHandle chatid, const char *title, MegaRequestListener *listener = NULL); /** * @brief Allows to join a public chat * * This function allows any user with a MEGA account to join an open chat that has the * specified public handle. It will create a management message like any new user join. * * @see \c MegaApi::chatLinkCreate * * The associated request type with this request is MegaRequest::TYPE_AUTOJOIN_PUBLIC_CHAT * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the public handle of the chat link * - MegaRequest::getSessionKey - Returns the unified key of the chat link * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - If the public handle is not valid. * - MegaError::API_EINCOMPLETE - If the no unified key is provided. * * @param publichandle MegaHandle that represents the public handle of the chat link * @param unifiedKey Byte array that contains the unified key, already encrypted and * converted to Base64url encoding. * @param listener MegaRequestListener to track this request */ void chatLinkJoin(MegaHandle publichandle, const char *unifiedKey, MegaRequestListener *listener = NULL); /** * @brief Returns whether notifications about a chat have to be generated * * @param chatid MegaHandle that identifies the chat room * @return true if notification has to be created */ bool isChatNotifiable(MegaHandle chatid); /** * @brief Allows to start chat call in a chat room * * - Waiting room will be enabled for the call, just if waiting room flag is enabled for the chatroom. * + If notRinging param is false, all participants that answers the call, will bypass the waiting room. * + If notRinging param is true, all participants will be redirected to waiting room, * as soon as they answer the call * * - Call will ring or not depending on the value of notRinging param * * The associated request type with this request is MegaRequest::TYPE_START_CHAT_CALL * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaChatRequest::getParentHandle() - Returns the scheduled meeting id; * - MegaChatRequest::getAccess() - Returns the SFU id * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the sfu url * - MegaRequest::getNodeHandle - Returns the call identifier * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the chatid is invalid * - MegaError::API_EEXIST - If there is a call in the chatroom * * @param chatid MegaHandle that identifies the chat room * @param notRinging if true call won't ring for participants when it's started * @param listener MegaRequestListener to track this request */ void startChatCall(MegaHandle chatid, bool notRinging = false, MegaRequestListener* listener = nullptr); /** * @brief Allow to join chat call * * The associated request type with this request is MegaRequest::TYPE_JOIN_CHAT_CALL * * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the call identifier * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the sfu url * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the chatid or callid is invalid * * @param chatid MegaHandle that identifies the chat room * @param callid MegaHandle that identifies the call * @param listener MegaRequestListener to track this request */ void joinChatCall(MegaHandle chatid, MegaHandle callid, MegaRequestListener* listener = nullptr); /** * @brief Allow to end chat call * * The associated request type with this request is MegaRequest::TYPE_END_CHAT_CALL * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getNodeHandle - Returns the chat identifier * - MegaRequest::getParentHandle - Returns the call identifier * - MegaRequest::getAccess - Returns the reason to end call * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the chatid or callid is invalid * * @param chatid MegaHandle that identifies the chat room * @param callid MegaHandle that identifies the call * @param reason Reason to end call (Valid value END_CALL_REASON_REJECTED) * @param listener MegaRequestListener to track this request */ void endChatCall(MegaHandle chatid, MegaHandle callid, int reason = 0, MegaRequestListener *listener = nullptr); /** * @brief This function allows a user in an existing call, to send an incoming call push notification to another user in the chat * to notify that call is ringing.. * * When a call is started and one user doesn't pick it up, ringing stops for that user/participant after a given time. * This function can be used to force another ringing event at said user/participant. * * The associated request type with this request is MegaRequest::TYPE_RING_INDIVIDUAL_IN_CALL * Valid data in the MegaRequest object received on callbacks: * - MegaChatRequest::getNodeHandle - Returns the chat identifier * - MegaChatRequest::getParentHandle - Returns the user's id to ring again * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::ERROR_OK: * * The request will fail with MegaChatError::ERROR_ARGS * - if chat id provided as param is invalid * - if user id to ring again provided as param is invalid * * The request will fail with MegaChatError::ERROR_NOENT * - if the chatroom doesn't exists. * * @param chatId MegaChatHandle that identifies the chat room * @param userId MegaChatHandle that identifies the user to ring again * @param listener MegaRequestListener to track this request */ void ringIndividualInACall(MegaHandle chatid, MegaHandle userid, MegaRequestListener* listener = nullptr); /** * @brief Change the SFU id * * This function allows to set the SFU server where all chat calls will be started * It's only useful for testing or debugging purposes. * * @param sfuid New SFU id */ void setSFUid(int sfuid); #endif /** * @brief Returns whether notifications about incoming have to be generated * * @return true if notification has to be created */ bool isSharesNotifiable(); /** * @brief Returns whether notifications about pending contact requests have to be generated * * @return true if notification has to be created */ bool isContactsNotifiable(); /** * @brief Get the MEGA Achievements of the account logged in * * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always false * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the MEGA Achievements of this account * * @param listener MegaRequestListener to track this request */ void getAccountAchievements(MegaRequestListener *listener = NULL); /** * @brief Get the list of existing MEGA Achievements * * Similar to MegaApi::getAccountAchievements, this method returns only the base storage and * the details for the different achievement classes, but not awards or rewards related to the * account that is logged in. * This function can be used to give an indication of what is available for advertising * for unregistered users, despite it can be used with a logged in account with no difference. * * @note: if the IP address is not achievement enabled (it belongs to a country where MEGA * Achievements are not enabled), the request will fail with MegaError::API_EACCESS. * * The associated request type with this request is MegaRequest::TYPE_GET_ACHIEVEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getFlag - Always true * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaAchievementsDetails - Details of the list of existing MEGA Achievements * * @param listener MegaRequestListener to track this request */ void getMegaAchievements(MegaRequestListener *listener = NULL); /** * @brief Catch up with API for pending actionpackets * * The associated request type with this request is MegaRequest::TYPE_CATCHUP * * When onRequestFinish is called with MegaError::API_OK, the SDK is guaranteed to be * up to date (as for the time this function is called). * * @param listener MegaRequestListener to track this request */ void catchup(MegaRequestListener *listener = NULL); /** * @brief Send a verification code txt to the supplied phone number * * Sends a 6 digit code to the user's phone. The phone number is supplied in this function * call. The code is sent by SMS to the user. Once the user receives it, they can type it * into the app and the call MegaApi::checkSMSVerificationCode can be used to validate the * user did receive the verification code, so that really is their phone number. * * The frequency with which this call can be used is very limited (the API allows at most * two SMS mssages sent for phone number per 24 hour period), so it's important to get the * number right on the first try. The result will be MegaError::API_ETEMPUNAVAIL if it has * been tried too frequently. * * Make sure to test the result of MegaApi::smsAllowedState before calling this function. * * The associated request type with this request is * MegaRequest::TYPE_SEND_SMS_VERIFICATIONCODE. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - the phoneNumber as supplied to this function * * When the operation completes, onRequestFinish is called and the MegaError object can be: * - MegaError::API_ETEMPUNAVAIL if a limit is reached. * - MegaError::API_EACCESS if your account is already verified with an SMS number * - MegaError::API_EEXIST if the number is already verified for some other account. * - MegaError::API_EARGS if the phone number is badly formatted or invalid. * - MegaError::API_OK is returned upon success. * * @param phoneNumber The phone number to txt the code to, supplied by the user. * @param listener MegaRequestListener to track this request * @param reverifying_whitelisted debug usage only. May be removed in future. * * @deprecated SMS verification was deprecated. This function is deprecated. Please don't * use it in new code. */ MEGA_DEPRECATED void sendSMSVerificationCode(const char* phoneNumber, MegaRequestListener *listener = NULL, bool reverifying_whitelisted = false); /** * @brief Check a verification code that the user should have received via txt * * This function validates that the user received the verification code sent by * MegaApi::sendSMSVerificationCode. * * The associated request type with this request is * MegaRequest::TYPE_CHECK_SMS_VERIFICATIONCODE. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - the verificationCode as supplied to this function * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - the phone number that has been verified * * When the operation completes, onRequestFinish is called and the MegaError object can be: * - MegaError::API_EEACCESS if you have reached the verification limits. * - MegaError::API_EFAILED if the verification code does not match. * - MegaError::API_EEXPIRED if the phone number was verified on a different account. * - MegaError::API_OK is returned upon success. * * @param verificationCode A string supplied by the user, that they should have received via * txt. * @param listener MegaRequestListener to track this request * * @deprecated SMS verification was deprecated. This function is deprecated. Please don't * use it in new code. */ MEGA_DEPRECATED void checkSMSVerificationCode(const char* verificationCode, MegaRequestListener *listener = NULL); /** * @brief Requests the currently available country calling codes * * The response value is stored as a MegaStringListMap mapping from two-letter country code * to a list of calling codes. For instance: * { * "AD": ["376"], * "AE": ["971", "13"], * } * * The associated request type with this request is * MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES. * Valid data in the MegaRequest object received in onRequestFinish when the error code is * MegaError::API_OK: * - MegaRequest::getMegaStringListMap where the keys are two-letter country codes and the * values a list of calling codes. * * For this command, there are currently no command specific error codes returned by the * API. * * @param listener MegaRequestListener to track this request * * @deprecated This function is deprecated. Please don't use it in new code. */ MEGA_DEPRECATED void getCountryCallingCodes(MegaRequestListener *listener = NULL); /** * @brief Retrieve basic information about a folder link * * This function retrieves basic information from a folder link, like the number of files / folders * and the name of the folder. For folder links containing a lot of files/folders, * this function is more efficient than a fetchnodes. * * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink() - Returns the public link to the folder * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaFolderInfo() - Returns information about the contents of the folder * - MegaRequest::getNodeHandle() - Returns the public handle of the folder * - MegaRequest::getParentHandle() - Returns the handle of the owner of the folder * - MegaRequest::getText() - Returns the name of the folder. * If there's no name, it returns the special status string "CRYPTO_ERROR". * If the length of the name is zero, it returns the special status string "BLANK". * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - If the link is not a valid folder link * - MegaError::API_EKEY - If the public link does not contain the key or it is invalid * * @param megaFolderLink Public link to a folder in MEGA * @param listener MegaRequestListener to track this request */ void getPublicLinkInformation(const char *megaFolderLink, MegaRequestListener *listener = NULL); /** * @brief Get an object that can lock the MegaApi, allowing multiple quick synchronous calls. * * This object must be used very carefully. It is meant to be used when the application is about * to make a burst of synchronous calls (that return data immediately, without using a listener) * to the API over a very short time period, which could otherwise be blocked multiple times * interrupted by the MegaApi's operation. * * The MegaApiLock usual use is to request it already locked, and the caller must destroy it * when its sequence of operations are complete, which will allow the MegaApi to continue again. * However explicit lock and unlock calls can also be made on it, which are protected from * making more than one lock, and the destructor will make sure the lock is released. * * You take ownership of the returned value, and you must delete it when the sequence is complete. */ MegaApiLock* getMegaApiLock(bool lockNow); /** * @brief Call the low level function setrlimit() for NOFILE, needed for some platforms. * * Particularly on phones, the system default limit for the number of open files (and sockets) * is quite low. When the SDK can be working on many files and many sockets at once, * we need a higher limit. Those limits need to take into account the needs of the whole * app and not just the SDK, of course. This function is provided in order that the app * can make that call and set appropriate limits. * * @param newNumFileLimit The new limit of file and socket handles for the whole app. * * @return True when there were no errors setting the new limit (even when clipped to the maximum * allowed value). It returns false when setting a new limit failed. */ bool platformSetRLimitNumFile(int newNumFileLimit) const; /** * @brief Call the low level function getrlimit() for NOFILE, needed for some platforms. * * @return The current limit for the number of open files (and sockets) for the app, or -1 if error. */ int platformGetRLimitNumFile() const; /** * @brief Requests a list of all Smart Banners available for current user. * * The response value is stored as a MegaBannerList. * * The associated request type with this request is MegaRequest::TYPE_GET_BANNERS * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBannerList: the list of banners * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - If called with no user being logged in. * - MegaError::API_EINTERNAL - If the internally used user attribute exists but can't be decoded. * - MegaError::API_ENOENT if there are no banners to return to the user. * * @param listener MegaRequestListener to track this request */ void getBanners(MegaRequestListener *listener = nullptr); /** * @brief No longer show the Smart Banner with the specified id to the current user. */ void dismissBanner(int id, MegaRequestListener *listener = nullptr); /** * @brief Registers a backup to display in Backup Centre * * Apps should register backups, like CameraUploads, in order to be listed in the * BackupCentre. The client should send heartbeats to indicate the progress of the * backup (see \c MegaApi::sendBackupHeartbeats). * * Possible types of backups: * BACKUP_TYPE_CAMERA_UPLOADS = 3, * BACKUP_TYPE_MEDIA_UPLOADS = 4, // Android has a secondary CU * * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getNodeHandle - Returns the target node of the backup * - MegaRequest::getName - Returns the backup name of the remote location * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getTotalBytes - Returns the backup type * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getFlag - Returns true * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param backupType back up type requested for the service * @param targetNode MEGA folder to hold the backups * @param localFolder Local path of the folder * @param backupName Name of the backup * @param state state * @param subState subState * @param listener MegaRequestListener to track this request */ void setBackup(int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener = nullptr); /** * @brief Update the information about a registered backup for Backup Centre * * Possible types of backups: * BACKUP_TYPE_INVALID = -1, * BACKUP_TYPE_CAMERA_UPLOADS = 3, * BACKUP_TYPE_MEDIA_UPLOADS = 4, // Android has a secondary CU * * Params that keep the same value are passed with invalid value to avoid to send to the server * Invalid values: * - type: BACKUP_TYPE_INVALID * - nodeHandle: UNDEF * - backupName: nullptr * - localFolder: nullptr * - deviceId: nullptr * - state: -1 * - subState: -1 * * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getTotalBytes - Returns the backup type * - MegaRequest::getNodeHandle - Returns the target node of the backup * - MegaRequest::getName - Returns the backup name of the remote location * - MegaRequest::getFile - Returns the path of the local folder * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param backupId backup id identifying the backup to be updated * @param backupType back up type requested for the service * @param targetNode MEGA folder to hold the backups * @param localFolder Local path of the folder * @param backupName Name of the backup * @param state backup state * @param subState backup subState * @param listener MegaRequestListener to track this request */ void updateBackup(MegaHandle backupId, int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener = nullptr); /** * @brief Unregister a backup already registered for the Backup Centre * * This method allows to remove a backup from the list of backups displayed in the * Backup Centre. @see \c MegaApi::setScheduledCopy. * * The associated request type with this request is MegaRequest::TYPE_BACKUP_REMOVE * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param backupId backup id identifying the backup to be removed * @param listener MegaRequestListener to track this request */ void removeBackup(MegaHandle backupId, MegaRequestListener *listener = nullptr); /** * @brief Mark a backup already registered in Backup Centre, for removal, and * move or delete its contents. Other sync types will only be stopped. * * This method allows to remove a backup from the list of backups displayed in the * Backup Centre, and completely remove its contents, either by moving them to * moveDestination (when the latter has a valid value) or by deleting them (when * destination is INVALID_HANDLE). * * The associated request type with this request is MegaRequest::TYPE_BACKUP_REMOVE_MD * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backup id * - MegaRequest::getNodeHandle - Returns the node handle corresponding to the move * destination * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EEXIST - The destination already contains a node with the same name. * * @param backupId backup id of the backup to be removed * @param moveDestination node handle where backup contents will be moved; if * INVALID_HANDLE, backup contents will be deleted; for non-backup syncs it will be ignored * @param listener MegaRequestListener to track this request */ void removeFromBC(MegaHandle backupId, MegaHandle moveDestination, MegaRequestListener* listener = nullptr); /** * @brief Simulate a backup/sync being paused from the webclient. * * The associated request type with this request is MegaRequest::TYPE_BACKUP_PAUSE_MD * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backup id * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param backupId backup id of the backup to be paused * @param listener MegaRequestListener to track this request */ void pauseFromBC(MegaHandle backupId, MegaRequestListener* listener = nullptr); /** * @brief Simulate a backup/sync being resumed from the webclient. * * The associated request type with this request is MegaRequest::TYPE_BACKUP_RESUME_MD * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backup id * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param backupId backup id of the backup to be resumed * @param listener MegaRequestListener to track this request */ void resumeFromBC(MegaHandle backupId, MegaRequestListener* listener = nullptr); /** * @brief Fetch information about all registered backups for Backup Centre * * The associated request type with this request is MegaRequest::TYPE_BACKUP_INFO * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaBackupInfoList - Returns information about all registered backups * * @param listener MegaRequestListener to track this request */ void getBackupInfo(MegaRequestListener* listener = nullptr); /** * @brief Send heartbeat associated with an existing backup * * The client should call this method regularly for every registered backup, in order to * inform about the status of the backup. * * Progress, last timestamp and last node are not always meaningful (ie. when the Camera * Uploads starts a new batch, there isn't a last node, or when the CU up to date and * inactive for long time, the progress doesn't make sense). In consequence, these parameters * are optional. They will not be sent to API if they take the following values: * - lastNode = INVALID_HANDLE * - lastTs = -1 * - progress = -1 * * The associated request type with this request is MegaRequest::TYPE_BACKUP_PUT_HEART_BEAT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns the backupId * - MegaRequest::getAccess - Returns the backup state * - MegaRequest::getNumDetails - Returns the backup substate * - MegaRequest::getParamType - Returns the number of pending upload transfers * - MegaRequest::getTransferTag - Returns the number of pending download transfers * - MegaRequest::getNumber - Returns the last action timestamp * - MegaRequest::getNodeHandle - Returns the last node handle to be synced * * @param backupId backup id identifying the backup * @param status backup status * @param progress backup progress * @param ups Number of pending upload transfers * @param downs Number of pending download transfers * @param ts Last action timestamp * @param lastNode Last node handle to be synced * @param listener MegaRequestListener to track this request */ void sendBackupHeartbeat(MegaHandle backupId, int status, int progress, int ups, int downs, long long ts, MegaHandle lastNode, MegaRequestListener *listener = nullptr); /** * @brief Fetch ads * * The associated request type with this request is MegaRequest::TYPE_FETCH_ADS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber A bitmap flag used to communicate with the API * - MegaRequest::getMegaStringList List of the adslot ids to fetch * - MegaRequest::getNodeHandle Public handle that the user is visiting * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaStringMap: map with relationship between ids and ius * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - ADS_DEFAULT = 0x0 * - ADS_FORCE_ADS = 0x200 * - ADS_IGNORE_MEGA = 0x400 * - ADS_IGNORE_COUNTRY = 0x800 * - ADS_IGNORE_IP = 0x1000 * - ADS_IGNORE_PRO = 0x2000 * - ADS_FLAG_IGNORE_ROLLOUT = 0x4000 * @param adUnits A list of the adslot ids to fetch; it cannot be null nor empty * @param publicHandle Provide the public handle that the user is visiting * @param listener MegaRequestListener to track this request */ void fetchAds(int adFlags, MegaStringList *adUnits, MegaHandle publicHandle = INVALID_HANDLE, MegaRequestListener *listener = nullptr); /** * @brief Check if ads should show or not * * The associated request type with this request is MegaRequest::TYPE_QUERY_ADS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber A bitmap flag used to communicate with the API * - MegaRequest::getNodeHandle Public handle that the user is visiting * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumDetails Return if ads should be show or not * * @param adFlags A bitmap flag used to communicate with the API * Valid values are: * - ADS_DEFAULT = 0x0 * - ADS_FORCE_ADS = 0x200 * - ADS_IGNORE_MEGA = 0x400 * - ADS_IGNORE_COUNTRY = 0x800 * - ADS_IGNORE_IP = 0x1000 * - ADS_IGNORE_PRO = 0x2000 * - ADS_FLAG_IGNORE_ROLLOUT = 0x4000 * @param publicHandle Provide the public handle that the user is visiting * @param listener MegaRequestListener to track this request */ void queryAds(int adFlags, MegaHandle publicHandle = INVALID_HANDLE, MegaRequestListener *listener = nullptr); /** * @brief Set a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MegaRequest::TYPE_SET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getNumDetails - Return a bitmap with cookie settings * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * @param settings A bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * @param listener MegaRequestListener to track this request */ void setCookieSettings(int settings, MegaRequestListener *listener = nullptr); /** * @brief Get a bitmap to indicate whether some cookies are enabled or not * * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the value USER_ATTR_COOKIE_SETTINGS * - MegaRequest::getListener - Returns the MegaRequestListener to track this request * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumDetails Return the bitmap with cookie settings * Valid bits are: * - Bit 0: essential * - Bit 1: preference * - Bit 2: analytics * - Bit 3: ads * - Bit 4: thirdparty * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EINTERNAL - If the value for cookie settings bitmap was invalid * * @param listener MegaRequestListener to track this request */ void getCookieSettings(MegaRequestListener *listener = nullptr); /** * @brief Check if the app can start showing the cookie banner * * This function will NOT return a valid value until the callback onEvent with * type MegaApi::EVENT_MISC_FLAGS_READY is received. You can also rely on the completion of * a fetchnodes to check this value. * * For not logged-in mode, you need to call MegaApi::getMiscFlags first. * * @return True if this feature is enabled. Otherwise, false. */ bool cookieBannerEnabled(); /** * @brief Start receiving notifications for [dis]connected external drives, from the OS * * After a call to this function, and before another one, stopDriveMonitor() must be called, * otherwise it will fail. * * @return True when notifications have been started. * False when called while already receiving notifications, or * notifications could not have been started due to errors or missing implementation, */ bool startDriveMonitor(); /** * @brief Stop receiving notifications for [dis]connected external drives, from the OS */ void stopDriveMonitor(); /** * @brief Check if drive monitor is running * @return True if it is running, false otherwise. */ bool driveMonitorEnabled(); /** * @brief Request creation of a new Set * * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getText - Returns name of the Set * - MegaRequest::getParamType - Returns CREATE_SET, possibly combined with OPTION_SET_NAME * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - Returns either the new Set, or null if it was not created. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param name the name that should be given to the new Set * @param type the type of the Set (see MegaSet for possible types) * @param listener MegaRequestListener to track this request */ void createSet(const char* name = nullptr, int type = MegaSet::SET_TYPE_ALBUM, MegaRequestListener* listener = nullptr); /** * @brief Request to update the name of a Set * * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getText - Returns new name of the Set * - MegaRequest::getParamType - Returns OPTION_SET_NAME * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param name the new name that should be given to the Set * @param listener MegaRequestListener to track this request */ void updateSetName(MegaHandle sid, const char* name, MegaRequestListener* listener = nullptr); /** * @brief Request to update the cover of a Set * * The associated request type with this request is MegaRequest::TYPE_PUT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be updated * - MegaRequest::getNodeHandle - Returns Element id to be set as the new cover * - MegaRequest::getParamType - Returns OPTION_SET_COVER * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Given Element id was not part of the current Set; Malformed (from API). * - MegaError::API_ENOENT - Set with the given id could not be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be updated * @param eid the id of the Element to be set as cover * @param listener MegaRequestListener to track this request */ void putSetCover(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener = nullptr); /** * @brief Request to remove a Set * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Set to be removed * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set to be removed * @param listener MegaRequestListener to track this request */ void removeSet(MegaHandle sid, MegaRequestListener* listener = nullptr); /** * @brief Request creation of multiple Elements for a Set * * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getMegaHandleList - Returns a list containing the file handles corresponding to the new Elements * - MegaRequest::getMegaStringList - Returns a list containing the names corresponding to the new Elements * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Elements * - MegaRequest::getMegaIntegerList - Returns a list containing error codes for all requested Elements * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Elements * @param nodes the handles of the file-nodes that will be represented by the new Elements * @param names the names that should be given to the new Elements (param names must be either null or have * the same size() as param nodes) * @param listener MegaRequestListener to track this request */ void createSetElements(MegaHandle sid, const MegaHandleList* nodes, const MegaStringList* names, MegaRequestListener* listener = nullptr); /** * @brief Request creation of a new Element for a Set * * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns INVALID_HANDLE * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns CREATE_ELEMENT, possibly combined with OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSetElementList - Returns a list containing only the new Element * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found, or node could not be found. * - MegaError::API_EKEY - File-node had no key. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Element * @param node the handle of the file-node that will be represented by the new Element * @param name the name that should be given to the new Element * @param listener MegaRequestListener to track this request */ void createSetElement(MegaHandle sid, MegaHandle node, const char* name = nullptr, MegaRequestListener* listener = nullptr); /** * @brief Request to update the name of an Element * * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_NAME * - MegaRequest::getText - Returns name of the Element * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param name the new name that should be given to the Element * @param listener MegaRequestListener to track this request */ void updateSetElementName(MegaHandle sid, MegaHandle eid, const char* name, MegaRequestListener* listener = nullptr); /** * @brief Request to update the order of an Element * * The associated request type with this request is MegaRequest::TYPE_PUT_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be updated * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getParamType - Returns OPTION_ELEMENT_ORDER * - MegaRequest::getNumber - Returns order of the Element * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Element could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element that will be updated * @param order the new order of the Element * @param listener MegaRequestListener to track this request */ void updateSetElementOrder(MegaHandle sid, MegaHandle eid, int64_t order, MegaRequestListener* listener = nullptr); /** * @brief Request removal of multiple Elements from a Set * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET_ELEMENTS * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getTotalBytes - Returns the id of the Set * - MegaRequest::getMegaHandleList - Returns a list containing the handles of Elements to be removed * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaIntegerList - Returns a list containing error codes for all Elements intended for removal * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read or decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that will own the new Elements * @param eids the ids of Elements to be removed * @param listener MegaRequestListener to track this request */ void removeSetElements(MegaHandle sid, const MegaHandleList* eids, MegaRequestListener* listener = nullptr); /** * @brief Request to remove an Element * * The associated request type with this request is MegaRequest::TYPE_REMOVE_SET_ELEMENT * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParentHandle - Returns id of the Element to be removed * - MegaRequest::getTotalBytes - Returns the id of the Set * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - No Set or no Element with given ids could be found (before or after the request). * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * @param sid the id of the Set that owns the Element * @param eid the id of the Element to be removed * @param listener MegaRequestListener to track this request */ void removeSetElement(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener = nullptr); /** * @brief Get a list of all Sets available for current user. * * The response value is stored as a MegaSetList. * * You take the ownership of the returned value * * @return list of Sets */ MegaSetList* getSets(); /** * @brief Get the Set with the given id, for current user. * * The response value is stored as a MegaSet. * * You take the ownership of the returned value * * @param sid the id of the Set to be retrieved * * @return the requested Set, or null if not found */ MegaSet* getSet(MegaHandle sid); /** * @brief Get the cover (Element id) of the Set with the given id, for current user. * * @param sid the id of the Set to retrieve the cover for * * @return Element id of the cover, or INVALIDHANDLE if not set or invalid id */ MegaHandle getSetCover(MegaHandle sid); /** * @brief Get Element count of the Set with the given id, for current user. * * @param sid the id of the Set to get Element count for * @param includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * * @return Element count of requested Set, or 0 if not found */ unsigned getSetElementCount(MegaHandle sid, bool includeElementsInRubbishBin = true); /** * @brief Get all Elements in the Set with given id, for current user. * * The response value is stored as a MegaSetElementList. * * You take the ownership of the returned value * * @param sid the id of the Set owning the Elements * @param includeElementsInRubbishBin consider or filter out Elements in Rubbish Bin * * @return all Elements in that Set, or null if not found or none added */ MegaSetElementList* getSetElements(MegaHandle sid, bool includeElementsInRubbishBin = true); /** * @brief Get a particular Element in a particular Set, for current user. * * The response value is stored as a MegaSetElement. * * You take the ownership of the returned value * * @param sid the id of the Set owning the Element * @param eid the id of the Element to be retrieved * * @return requested Element, or null if not found */ MegaSetElement* getSetElement(MegaHandle sid, MegaHandle eid); /** * @brief Returns true if the Set has been exported (has a public link) * * Public links are created by calling MegaApi::exportSet * * @param sid the id of the Set to check * * @return true if param sid is an exported Set */ bool isExportedSet(MegaHandle sid); /** * @brief Generate a public link of a Set in MEGA * * The associated request type with this request is MegaRequest::TYPE_EXPORT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns id of the Set used as parameter * - MegaRequest::getFlag - Returns a boolean set to true representing the call was * meant to enable/create the export * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - MegaSet including the public id * - MegaRequest::getLink - Public link * * MegaError::API_OK results in onSetsUpdate being triggered as well * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param sid Set MegaHandle to get the public link * @param listener MegaRequestListener to track this request */ void exportSet(MegaHandle sid, MegaRequestListener *listener = nullptr); /** * @brief Stop sharing a Set * * The associated request type with this request is MegaRequest::TYPE_EXPORT_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns id of the Set used as parameter * - MegaRequest::getFlag - Returns a boolean set to false representing the call was * meant to disable the export * * MegaError::API_OK results in onSetsUpdate being triggered as well * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param sid Set MegaHandle to stop sharing * @param listener MegaRequestListener to track this request */ void disableExportSet(MegaHandle sid, MegaRequestListener *listener = nullptr); /** * @brief gets Set and Elements handle size * @return Set and Elements handle size */ static int getSetElementHandleSize(); /** * @brief Request to fetch a public/exported Set and its Elements. * * The associated request type with this request is MegaRequest::TYPE_FETCH_SET * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getLink - Returns the link used for the public Set fetch request * * In addition to fetching the Set (including Elements) and keeping a local/cached * copy in SDK instance, SDK's instance is set to preview mode for the public Set. * This mode allows downloading of foreign SetElements included in the public Set. * * To disable the preview mode and release resources cached by the preview Set, * use MegaApi::stopPublicSetPreview * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaSet - Returns the Set * - MegaRequest::getMegaSetElementList - Returns the list of Elements * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - Set could not be found. * - MegaError::API_EINTERNAL - Received answer could not be read. * - MegaError::API_EKEY - Received answer could not be decrypted. * - MegaError::API_EARGS - Malformed (from API). * - MegaError::API_EACCESS - Permissions Error (from API). * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param publicSetLink Public link to a Set in MEGA * @param listener MegaRequestListener to track this request */ void fetchPublicSet(const char* publicSetLink, MegaRequestListener* listener = nullptr); /** * @brief Stops public Set preview mode for current SDK instance * * MegaApi cached Set and SetElements will be released * */ void stopPublicSetPreview(); /** * @brief Returns if this MegaApi instance is in a public/exported Set preview mode * * @returns True if public Set preview mode is enabled * */ bool inPublicSetPreview(); /** * @brief Get currently cached public/exported Set in Preview mode * * The response value is stored as a MegaSet. * * You take the ownership of the returned value * * @return Current public/exported Set in preview mode or nullptr if there is none * */ MegaSet* getPublicSetInPreview(); /** * @brief Get currently cached public/exported SetElements in Preview mode * * The response value is stored as a MegaSetElementList. * * You take the ownership of the returned value * * @return Current public/exported SetElements in preview mode or nullptr if there is none * */ MegaSetElementList* getPublicSetElementsInPreview(); /** * @brief Gets a MegaNode for the foreign MegaSetElement that can be used to download the Element * * The associated request type with this request is MegaRequest::TYPE_GET_EXPORTED_SET_ELEMENT * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getPublicMegaNode - Returns the MegaNode (ownership transferred) * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - Public Set preview mode is not enabled * - MegaError::API_EARGS - MegaHandle for SetElement provided as param doesn't match any Element * in previewed Set * - MegaError::API_ENOENT - Node metadata was not available for the SetElement provided as param * * If the MEGA account is a business account and it's status is expired, onRequestFinish will * be called with the error code MegaError::API_EBUSINESSPASTDUE. * * @param eid MegaHandle of target SetElement from Set in preview mode * @param listener MegaRequestListener to track this request */ void getPreviewElementNode(MegaHandle eid, MegaRequestListener* listener = nullptr); /** * @brief Gets the public link / URL that can be used to fetch a public Set and its SetElements * * You take ownership of the returned value. Use delete[] to release the memory. * * @param sid MegaHandle of target Set to get its public link/URL * * @return const char* with the public URL if success, nullptr otherwise * In any case, one of the followings error codes with the result can be found in the log: * - API_OK on success * - API_ENOENT if sid doesn't match any owned Set or the Set is not exported * - API_EARGS if there was an internal error composing the URL */ const char* getPublicLinkForExportedSet(MegaHandle sid); /** * @brief Enable or disable the request status monitor * * When it's enabled, the request status monitor generates events of type * MegaEvent::EVENT_REQSTAT_PROGRESS with the per mille progress in * the field MegaEvent::getNumber(), or -1 if there isn't any operation in progress. * * @param enable True to enable the request status monitor, or false to disable it */ void enableRequestStatusMonitor(bool enable); /** * @brief Get the status of the request status monitor * @return True when the request status monitor is enabled, or false if it's disabled */ bool requestStatusMonitorEnabled(); /* MegaVpnCredentials */ /** * @brief Gets a list with the available regions for MEGA VPN. * * The associated request type with this request is MegaRequest::TYPE_GET_VPN_REGIONS. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaStringList - Returns the list with the VPN regions. * * @param listener MegaRequestListener to track this request. */ void getVpnRegions(MegaRequestListener* listener = nullptr); /** * @brief Gets the MEGA VPN credentials currently active for the user. * * Important consideration: * These credentials do NOT contain the User Private Key, which is required for VPN connection. * Credentials containing the User Private Key are generated by * MegaApi::putVpnCredential and cannot be retrieved afterwards. * * The associated request type with this request is MegaRequest::TYPE_GET_VPN_CREDENTIALS. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaVpnCredentials - Returns the MegaVpnCredentials object. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - The user has no credentials registered. * * @see MegaApi::MegaVpnCredentials * * @param listener MegaRequestListener to track this request. */ void getVpnCredentials(MegaRequestListener* listener = nullptr); /** * @brief Adds new MEGA VPN credentials on an empty slot. * * A pair of private and public keys are generated for the user during this request. * The User Public Key value is intented for use with MegaApi::checkVpnCredential. * The User Private Key value is included in the VPN credentials. * Once returned, neither of these keys can be retrieved, not even using MegaApi::getVpnCredentials. * * The user must be a PRO user and have unoccupied VPN slots in order to add new VPN credentials. * * The associated request type with this request is MegaRequest::TYPE_PUT_VPN_CREDENTIAL. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the VPN region used for the VPN credentials. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getNumber - Returns the SlotID attached to the new VPN credentials. * - MegaRequest::getPassword - Returns the User Public Key used to register the new VPN credentials. * - MegaRequest::getSessionKey - Returns a string with the new VPN credentials. * The content of this string is equivalent to the conf file generated by the webclient: * [Interface] * PrivateKey = User Private Key * Address = IPv4, IPv6 * DNS = IPv4, IPv6 * * [Peer] * PublicKey = Cluster Public Key * AllowedIPs = 0.0.0.0/0, ::/0 * Endpoint = host:port * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Public Key does not have a correct format/length. * - MegaError::API_EACCESS - User is not PRO. * - User is not logged in. * - Public Key is already taken. * - MegaError::API_ETOOMANY - User has too many registered credentials. * * @param region The VPN region to be used on the new VPN credential. * @param listener MegaRequestListener to track this request. */ void putVpnCredential(const char* region, MegaRequestListener* listener = nullptr); /** * @brief Delete the current MEGA VPN credentials used on a slot. * * The associated request type with this request is MegaRequest::TYPE_DEL_VPN_CREDENTIAL. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNumber - Returns the SlotID used as a parameter for credential removal. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - SlotID is not valid. * - MegaError::API_ENOENT - SlotID is not occupied. * * @param slotID The SlotID from which to remove the VPN credentials. * @param listener MegaRequestListener to track this request. */ void delVpnCredential(int slotID, MegaRequestListener* listener = nullptr); /** * @brief Check the current status of MEGA VPN credentials using the User Public Key. * * The User Public Key is obtained from MegaApi::putVpnCredential. * * The associated request type with this request is MegaRequest::TYPE_CHECK_VPN_CREDENTIAL. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getText - Returns the User Public Key used as a parameter to verify the status of the VPN credentials. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EACCESS - Public Key is not valid. * * @param userPubKey The User Public Key used to register the VPN credentials. * @param listener MegaRequestListener to track this request. */ void checkVpnCredential(const char* userPubKey, MegaRequestListener* listener = nullptr); /* MegaVpnCredentials END */ /** * @brief Fetch information about the registered credit card for the user * * The associated request type with this request is MegaRequest::TYPE_FETCH_CREDIT_CARD_INFO. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getMegaStringMap - map with following keys: * - gw (gateway) * - brand * - last4 * - exp_month * - exp_year * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::ENOENT - No Registered Card * * @param listener */ void fetchCreditCardInfo(MegaRequestListener* listener = nullptr); /** * @brief Get Welcome dialog visibility. * * The type associated with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG * - MegaRequest::getFlag - Returns the Welcome dialog visibility. * * If the corresponding user attribute is not set yet, the request will fail with the error * code MegaError::API_ENOENT and MegaRequest::getFlag will return the default value. * * @param listener MegaRequestListener to track this request. */ virtual void getVisibleWelcomeDialog(MegaRequestListener* listener); /** * @brief Set Welcome dialog visibility. * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG * * @param visible True to set the Welcome dialog visible, false otherwise. * @param listener MegaRequestListener to track this request. */ virtual void setVisibleWelcomeDialog(bool visible, MegaRequestListener* listener); /** * @brief Creates a node tree. * * The associated request type with this request is MegaRequest::TYPE_CREATE_NODE_TREE. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the node handle of the parent node in the tree * - MegaRequest::getMegaNodeTree - Returns the Node Tree updated after it was created * - MegaRequest::getMegaStringMap - Returns {node handle, file handle} pairs for all newly * created files. So far a single file gets created by this command. * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Parameters are incorrect. * * @param parentNode Parent node from which to create the node tree. * @param nodeTree Node tree to create which is fulfilled with the new node handles. * @param listener Listener to track the request. */ void createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, MegaRequestListener* listener); /** * @brief Creates a node tree. * * The associated request type with this request is MegaRequest::TYPE_CREATE_NODE_TREE. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParentHandle - Returns the node handle of the parent node in the tree * - MegaRequest::getMegaNodeTree - Returns the Node Tree updated after it was created * * On the onRequestFinish error, the error code associated to the MegaError can be: * - MegaError::API_EARGS - Parameters are incorrect. * * @param parentNode Parent node from which to create the node tree. * @param nodeTree Node tree to create which is fulfilled with the new node handles. * @param customerIpPort The IP and port number used by current customer. Valid format * for IPv4 is : (for example 0.0.0.0:12345) and for IPv6 is []: * (for example [2001:db8:3333::EEEE:FFFF]:12345). * @param listener Listener to track the request. */ void createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, const char* customerIpPort, MegaRequestListener* listener); /** * @brief Get Terms of Service for VPN visibility. * * The type associated with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE * - MegaRequest::getFlag - Returns Terms of Service for VPN visibility. * * If the corresponding user attribute is not set yet, the request will fail with the error * code MegaError::API_ENOENT and MegaRequest::getFlag will return the default value (true). * * @param listener MegaRequestListener to track this request. */ void getVisibleTermsOfService(MegaRequestListener* listener = nullptr); /** * @brief Set Terms of Service for VPN visibility. * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE * * @param visible True to set Terms of Service visibility on, false otherwise. * @param listener MegaRequestListener to track this request. */ void setVisibleTermsOfService(bool visible, MegaRequestListener* listener = nullptr); /** * @brief Get the list of IDs for enabled notifications * * You take the ownership of the returned value * * @return List of IDs for enabled notifications */ MegaIntegerList* getEnabledNotifications(); /** * @brief Enable test notifications * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_ENABLE_TEST_NOTIFICATIONS * - MegaRequest::getMegaIntegerList - Returns a list containing the notification IDs to be enabled * * @param notificationIds list of IDs for the notifications to be enabled * @param listener MegaRequestListener to track this request */ void enableTestNotifications(const MegaIntegerList* notificationIds, MegaRequestListener* listener = nullptr); /** * @brief Get list of available notifications for Notification Center * * The associated request type with this request is MegaRequest::TYPE_GET_NOTIFICATIONS. * * When onRequestFinish received MegaError::API_OK, valid data in the MegaRequest object is: * - MegaRequest::getMegaNotifications - Returns the list of notifications * * When onRequestFinish errored, the error code associated to the MegaError can be: * - MegaError::API_ENOENT - No such notifications exist, and MegaRequest::getMegaNotifications * will return a non-null, empty list. * - MegaError::API_EACCESS - No user was logged in. * - MegaError::API_EINTERNAL - Received answer could not be read. * * @param listener MegaRequestListener to track this request */ void getNotifications(MegaRequestListener* listener = nullptr); /** * @brief Set last read notification for Notification Center * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_READ_NOTIFICATION * - MegaRequest::getNumber - Returns the ID to be set as last read * * Note that any notifications with ID equal to or less than the given one will be marked as seen * in Notification Center. * * @param notificationId ID of the notification to be set as last read. Value `0` is an invalid ID. * Passing `0` will clear a previously set last read value. * @param listener MegaRequestListener to track this request */ void setLastReadNotification(uint32_t notificationId, MegaRequestListener* listener = nullptr); /** * @brief Get last read notification for Notification Center * * The type associated with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_READ_NOTIFICATION * * When onRequestFinish received MegaError::API_OK, valid data in the MegaRequest object is: * - MegaRequest::getNumber - Returns the ID of the last read Notification * Note that when the ID returned here was `0` it means that no ID was set as last read. * Note that the value returned here should be treated like a 32bit unsigned int. * * @param listener MegaRequestListener to track this request */ void getLastReadNotification(MegaRequestListener* listener = nullptr); /** * @brief Set last actioned banner for Notification Center * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_ACTIONED_BANNER * - MegaRequest::getNumber - Returns the ID to be set as last actioned banner * * @param notificationId ID for which the last banner was actioned. Value `0` is an invalid ID. * Passing `0` will clear a previously set last actioned banner. * @param listener MegaRequestListener to track this request */ void setLastActionedBanner(uint32_t notificationId, MegaRequestListener* listener = nullptr); /** * @brief Get last actioned banner for Notification Center * * The type associated with this request is MegaRequest::TYPE_GET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_LAST_ACTIONED_BANNER * * When onRequestFinish received MegaError::API_OK, valid data in the MegaRequest object is: * - MegaRequest::getNumber - Returns the ID of the last actioned banner * Note that when the ID returned here was `0` it means that no ID was set as last actioned banner. * Note that the value returned here should be treated like a 32bit unsigned int. * * @param listener MegaRequestListener to track this request */ void getLastActionedBanner(MegaRequestListener* listener = nullptr); /** * @brief * Add a new mount to the database. * * @param mount * A description of the new mount. * * @param listener * Who will be notified when the operation completes. * * @note * This call will issue a new request of the type TYPE_ADD_MOUNT. */ void addMount(const MegaMount* mount, MegaRequestListener* listener); /** * @brief * Disable an active mount. * * @path name * Identifies the mount to be disabled. * * @param listener * Who will be notified when the operation completes. * * @param remember * True if the mount should remain diasbled after application restart. * * If this parameter is true and path specifies a transient mount, * that mount will become persistent. * * This makes sense as remembering something about a mount implies * that it persists for more than a single session. * * @note * This call will issue a new request of the type TYPE_DISABLE_MOUNT. */ void disableMount(const char* name, MegaRequestListener* listener, bool remember); /** * @brief * Enable an inactive mount. * * @param name * Identifies the mount to be enabled. * * @param listener * Who will be notified when the operation completes. * * @param remember * True if the mount should remain enabled after application restart. * * If this parameter is true and path specifies a transient mount, * that mount will become persistent. * * This makes sense as remembering something about a mount implies * that it persists for more than a single session. * * @note * This call will issue a new request of the type TYPE_ENABLE_MOUNT. */ void enableMount(const char* name, MegaRequestListener* listener, bool remember); /** * @brief * Retrieve the FUSE subsystem's current flags. * * You take ownership of the returned value. * * @return * The FUSE subsystem's current flags. */ MegaFuseFlags* getFUSEFlags(); /** * @brief * Retrieve an existing mount's flags. * * You take ownership of the returned value. * * @param name * Identifies the mount we want to query. * * @return * NULL if no such mount exists. */ MegaMountFlags* getMountFlags(const char* name); /** * @brief * Retrieve a description of an existing mount. * * You take ownership of the returned value. * * @param name * Identifies the mount we want to describe. * * @return * NULL if no such mount exists. */ MegaMount* getMountInfo(const char* name); /** * @brief * Retrieve the path of the mount associated with name. * * You take ownership of the returned value. Use delete[] to release the memory. * * @param name * A name of a previously added mount. * * @return * The mounts path if any otherwise null. */ char* getMountPath(const char* name); /** * @brief * Retrieve a list of known mounts. * * You take ownership of the returned value. * * @param enabled * True if only enabled mounts should be returned. * * @return * A list of mount descriptions. */ MegaMountList* listMounts(bool enabled); /** * @brief * Check whether the specified file is in FUSE's cache. * * @param path * Identifies the file we want to check. * * @return * True if the file is in FUSE's cache. */ bool isCachedByPath(const char* path); /** * @brief * Query whether FUSE is supported on this platform. * * @return * True if FUSE is supported on this platform. */ bool isFUSESupported(); /** * @brief * Query whether a mount is enabled. * * @param path * Identifies the mount we want to query. * * @return * True if the mount is enabled. */ bool isMountEnabled(const char* path); /** * @brief * Remove an existing mount from the database. * * @param path * Identifies the mount to be removed. * * @param listener * Who will be notified when the operation completes. * * @note * This call will issue a request of the type TYPE_REMOVE_MOUNT. */ void removeMount(const char* path, MegaRequestListener* listener); /** * @brief * Update the FUSE subsystem's flags. * * @param flags * The FUSE subsystem's new flags. */ void setFUSEFlags(const MegaFuseFlags* flags); /** * @brief * Update an exisrting mount's flags. * * You can use this function to change properties such as a mount's * name or writability. * * @param flags * Specifies the new values of a mount's flags. * * @param path * Identifies the mount whose flags we want to update. * * @param listener * Who will be notified when the operation completes. * * @note * This call will issue a request of the type TYPE_SET_MOUNT_FLAGS. */ void setMountFlags(const MegaMountFlags* flags, const char* path, MegaRequestListener* listener); /** * You take ownership of the returned value. * @deprecated Use getFlag(const char* flagName, bool commit) instead. */ MegaFlag* getFlag(const char* flagName, bool commit, MegaRequestListener* listener); /** * @brief Get the type and value for the flag with the given name, if present among either * A/B Test or Feature flags. * * If found among A/B Test flags and commit was true, also inform the API that a user has become * relevant for that A/B Test flag (via a request of type MegaRequest::TYPE_AB_TEST_ACTIVE, * for which the response is not be relevant for the calling app) * * You take the ownership of the returned value * * @param flagName Name or key of the value to be retrieved (and possibly be sent to API as active). * @param commit Determine whether an A/B Test flag will be sent to API as active. * * @return A MegaFlag instance with the type and value of the flag. */ MegaFlag* getFlag(const char* flagName, bool commit = true); /** * @brief Delete a user attribute of the current user, for testing * This method is for developer use only and it requires to be logged-in into an * account under a MEGA email. Otherwise, it will fail with API_EACCESS (except for * attributes "gmk" and "promocode", which are not supported by SDK, but removed by * Webclient). * * The associated request type with this request is MegaRequest::TYPE_DEL_ATTR_USER * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the attribute type * * @param type Attribute type * * Valid values are: * * MegaApi::USER_ATTR_FIRSTNAME = 1 * Delete the firstname of the user (public) * MegaApi::USER_ATTR_LASTNAME = 2 * Delete the lastname of the user (public) * MegaApi::USER_ATTR_AUTHRING = 3 * Delete the authentication ring of the user (private) * MegaApi::USER_ATTR_LAST_INTERACTION = 4 * Delete the last interaction of the contacts of the user (private) * MegaApi::USER_ATTR_ED25519_PUBLIC_KEY = 5 * Delete the public key Ed25519 of the user (public) * MegaApi::USER_ATTR_CU25519_PUBLIC_KEY = 6 * Delete the public key Cu25519 of the user (public) * MegaApi::USER_ATTR_KEYRING = 7 * Delete the key ring of the user: private keys for Cu25519 and Ed25519 (private) * MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY = 8 * Delete the signature of RSA public key of the user (public) * MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY = 9 * Delete the signature of Cu25519 public key of the user (public) * MegaApi::USER_ATTR_LANGUAGE = 14 * Delete the preferred language of the user (private, non-encrypted) * MegaApi::USER_ATTR_PWD_REMINDER = 15 * Delete the password-reminder-dialog information (private, non-encrypted) * MegaApi::USER_ATTR_DISABLE_VERSIONS = 16 * Delete whether user has versions disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RICH_PREVIEWS = 18 * Delete whether user generates rich-link messages or not (private) * MegaApi::USER_ATTR_RUBBISH_TIME = 19 * Delete number of days for rubbish-bin cleaning scheduler (private non-encrypted) * MegaApi::USER_ATTR_STORAGE_STATE = 21 * Delete the state of the storage (private non-encrypted) * MegaApi::USER_ATTR_GEOLOCATION = 22 * Delete the user geolocation (private) * MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER = 23 * Delete the target folder for Camera Uploads (private) * MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER = 24 * Delete the target folder for My chat files (private) * MegaApi::USER_ATTR_PUSH_SETTINGS = 25 * Delete whether user has push settings enabled (private) * MegaApi::USER_ATTR_ALIAS = 27 * Delete the list of the users's aliases (private) * MegaApi::USER_ATTR_DEVICE_NAMES = 30 * Delete the list of device or external drive names (private) * MegaApi::USER_ATTR_MY_BACKUPS_FOLDER = 31 * Delete the target folder for My Backups (private) * MegaApi::USER_ATTR_COOKIE_SETTINGS = 33 * Delete whether user has Cookie Settings enabled * MegaApi::USER_ATTR_JSON_SYNC_CONFIG_DATA = 34 * Delete name and key to cypher sync-configs file * MegaApi::USER_ATTR_NO_CALLKIT = 36 * Delete whether user has iOS CallKit disabled or enabled (private, non-encrypted) * MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP = 52 * Delete the timestamp for recent actions history clearing (private, encrypted) * * @param listener MegaRequestListener to track this request */ void deleteUserAttribute(int type, MegaRequestListener* listener = NULL); /** * @brief Retrieve active survey trigger action IDs * * This function fetches all active survey trigger action IDs. * * The associated request type for this function is * MegaRequest::TYPE_GET_ACTIVE_SURVEY_TRIGGER_ACTIONS. * * On successful completion (MegaError::API_OK), the MegaRequest object received in * onRequestFinish contains: * - MegaRequest::getMegaIntegerList: Returns a list of active trigger action IDs. * * If the request fails, the MegaError code in onRequestFinish can be: * - ENOENT: No available trigger actions. * - EINTERNAL: Received response could not be read. * * @param listener MegaRequestListener to track this request */ void getActiveSurveyTriggerActions(MegaRequestListener* listener = nullptr); /** * @brief Get a survey * * This function retrieves the survey of the given trigger action. * * The associated request type for this function is MegaRequest::TYPE_GET_SURVEY. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - Returns the trigger action ID. * * On successful completion (MegaError::API_OK), the MegaRequest object received in * onRequestFinish contains: * - MegaRequest::getNodeHandle - Returns the survey handle. * - MegaRequest::getNumDetails - Returns the survey's maximum response value. * - MegaRequest::getFile - Returns the name of the image to be displayed. * - MegaRequest::getText - Returns the survey's question content. * * If the request fails, the MegaError code in onRequestFinish can be: * - EACCESS - Invalid user ID * - EARGS - Invalid trigger action * - ENOENT - No eligible survey * - EINTERNAL - Received answer could not be read * * @param triggerActionId The ID of the trigger action * @param listener MegaRequestListener to track this request */ void getSurvey(unsigned int triggerActionId, MegaRequestListener* listener = nullptr); /** * @brief Enable test surveys * * This function enables the specified surveys for testing purposes. Once enabled, these * surveys can be answered multiple times. * * The type associated with this request is MegaRequest::TYPE_SET_ATTR_USER * * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getParamType - The attribute type MegaApi::USER_ATTR_ENABLE_TEST_SURVEYS * - MegaRequest::getMegaHandleList - A list of the survey handles to be enabled * * @param surveyHandles The list of handles of the surveys to be enabled. * @param listener MegaRequestListener to track this request */ void enableTestSurveys(const MegaHandleList* surveyHandles, MegaRequestListener* listener = nullptr); /** * @brief Answer a survey * * This function answers a survey that the user has been asked to complete. * * @note: If triggerActionId is MegaApi::ACT_END_UPLOAD, response and comment params, must * be valid null terminated c-style strings, and response must contains a string with a * valid rating value between 1 and 5 * * The associated request type for this function is MegaRequest::TYPE_ANSWER_SURVEY. * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getNodeHandle - Returns the survey handle. * - MegaRequest::getParamType - Returns the trigger action ID. * - MegaRequest::getText - Returns the survey response. * - MegaRequest::getFile - Returns the response to tell us more. * * If the request fails, the MegaError code in onRequestFinish can be: * - EACCESS - Invalid user ID. * - EARGS - Invalid arguments such as invalid survey handle/invalid trigger action ID. * Also if triggerActionId is MegaApi::ACT_END_UPLOAD, and no valid rating value is provided * at response, or comment param is nullptr. * - ENOENT - Survey not found, trigger action not found, or survey disabled. * - EINTERNAL - Received answer could not be read. * * @param surveyHandle The survey handle * @param triggerActionId The trigger action ID. Valid values for this field are defined at * MegaApi::SurveyTriggerActionId * @param response The response to the survey * @param comment The response to tell us more * @param listener MegaRequestListener to track this request */ void answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const char* response, const char* comment = nullptr, MegaRequestListener* listener = nullptr); /** * @brief Gets the public IP address and country code. * * The associated request type with this request is MegaRequest::TYPE_GET_MY_IP. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getName - Returns the country code. * - MegaRequest::getText - Returns the public IP address. * * @param listener MegaRequestListener to track this request. */ void getMyIp(MegaRequestListener* listener = nullptr); /** * @brief Run a network connectivity test. * * The associated request type with this request is * MegaRequest::TYPE_RUN_NETWORK_CONNECTIVITY_TEST. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getMegaNetworkConnectivityTestResults - Returns the results of the test. * * If the network connectivity test server could not be retrieved the test will not run and * the request will fail with MegaError::API_ESID. * * @param listener MegaRequestListener to track this request. */ void runNetworkConnectivityTest(MegaRequestListener* listener = nullptr); /** * @brief Retrieve the cancellation details of a subscription * * This function requests information about the cancellation status of a subscription. * If the optional original transaction ID is not provided, the details of the most recent * subscription will be returned. * * The associated request type with this request is * MegaRequest::TYPE_GET_SUBSCRIPTION_CANCELLATION_DETAILS. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the original transaction ID * - MegaRequest::getNumber - Returns the subscription's expiration timestamp * - MegaRequest::getNumDetails - Returns the cancellation timestamp, or 0 if not cancelled * * Possible errors: * - MegaError::API_EARGS - If the gateway is not provided or not equal to 2, or if the * transaction ID is not a string * - MegaError::API_ENOENT - If the provided transaction ID is not valid, or the user does * not have a subscription via the specified gateway * * @param originalTransactionId Original transaction ID. Optional. If not provided, the last * subscription's details will be returned. * @param gatewayId Integer indicating the gateway. * @param listener MegaRequestListener to track this request, */ void getSubscriptionCancellationDetails(unsigned int gatewayId, const char* originalTransactionId = nullptr, MegaRequestListener* listener = nullptr); /** * @brief Retrieve information about a discount code * * The list of valid values for \c code can be retrieved from the * getter MegaDiscountCode::getCode. The list of available discounts * can be retrieved by calling MegaApi::getPricing. * * The associated request type with this request is * MegaRequest::TYPE_GET_DISCOUNT_CODE_INFORMATION. * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: * - MegaRequest::getText - Returns the discount code * - MegaRequest::getMegaDiscountCodeInfo - Returns the discount code information * * Possible errors: * - MegaError::API_EARGS - If the provided code is nullptr * - MegaError::API_EEXPIRED - If the discount has expired * - MegaError::API_ENOENT - If the provided code is not found * - MegaError::API_EACCESS - If the discount is for different user * - MegaError::API_EEXIST - If the discount has already been redeemed * * @param code Discount code to get information about * @param listener MegaRequestListener to track this request */ void getDiscountCodeInformation(const char* code, MegaRequestListener* listener = nullptr); protected: MegaApiImpl *pImpl = nullptr; friend class MegaApiImpl; }; /** * @brief Represents information of a Backup in MEGA * * It allows getting all information about a Backup. * * Objects of this class aren't live, they are snapshots of the state of a Backup * when the object was created. They are immutable. * */ class MegaBackupInfo { public: /** * @brief Returns Backup id. * * @return Backup id. */ virtual MegaHandle id() const { return INVALID_HANDLE; } /** * @brief Returns Backup type. * * It can be one of the MegaApi::BACKUP_TYPE_x values. * * @return Backup type. */ virtual int type() const { return MegaApi::BACKUP_TYPE_INVALID; } /** * @brief Returns handle of Backup root. * * @return Backup root handle. */ virtual MegaHandle root() const { return INVALID_HANDLE; } /** * @brief Returns the name of the backed up local folder. * * @return Name of the backed up local folder. */ virtual const char* localFolder() const { return nullptr; } /** * @brief Returns the id of the device where the backup originated. * * @return Id of the device where the backup originated. */ virtual const char* deviceId() const { return nullptr; } /** * @brief Returns the user-agent associated with the device where the backup originated. * * @return User-agent associated with the device where the backup originated. */ virtual const char* deviceUserAgent() const { return nullptr; } /** * @brief Possible sync state of a backup. */ enum // 1:1 with CommandBackupPut::SPState enum values { BACKUP_STATE_NOT_INITIALIZED = 0, BACKUP_STATE_ACTIVE = 1, // Working fine (enabled) BACKUP_STATE_FAILED = 2, // Failed (permanently disabled) BACKUP_STATE_TEMPORARY_DISABLED = 3, // Temporarily disabled due to a transient situation (e.g: account blocked). Will be resumed when the condition passes BACKUP_STATE_DISABLED = 4, // Disabled by the user BACKUP_STATE_PAUSE_UP = 5, // Active but upload transfers paused in the SDK BACKUP_STATE_PAUSE_DOWN = 6, // Active but download transfers paused in the SDK BACKUP_STATE_PAUSE_FULL = 7, // Active but transfers paused in the SDK BACKUP_STATE_DELETED = 8, // Sync needs to be deleted, as required by sync-desired-state received from BackupCenter (WebClient) }; /** * @brief Returns the sync state of the backup. * * It can be one of the BACKUP_STATE_x enum values. * * @return Sync state of the backup. */ virtual int state() const { return BACKUP_STATE_NOT_INITIALIZED; } /** * @brief Returns the sync substate of the backup. * * It can be one of the enum values defined at MegaSync::Error. * * @return Sync substate of the backup. */ virtual int substate() const { return 0; } /** * @brief Returns extra information, used as source for extracting other details. * * @return Extra information, used as source for extracting other details. */ virtual const char* extra() const { return nullptr; } /** * @brief Returns the name of the backup. * * @return Name of the backup. */ virtual const char* name() const { return nullptr; } /** * @brief Returns the timestamp of the backup, as reported by heartbeats. * * @return Timestamp of the backup, as reported by heartbeats. */ virtual uint64_t ts() const { return 0; } /** * @brief Possible status of a backup. */ enum // 1:1 with CommandBackupPutHeartBeat::SPHBStatus enum values { BACKUP_STATUS_NOT_INITIALIZED = 0, BACKUP_STATUS_UPTODATE = 1, // Up to date: local and remote paths are in sync BACKUP_STATUS_SYNCING = 2, // The sync engine is working, transfers are in progress BACKUP_STATUS_PENDING = 3, // The sync engine is working, e.g: scanning local folders BACKUP_STATUS_INACTIVE = 4, // Sync is not active. A state != ACTIVE should have been sent through 'sp' BACKUP_STATUS_UNKNOWN = 5, // Unknown status BACKUP_STATUS_STALLED = 6, // A folder is scan-blocked, or some contradictory changes occured between local and remote folders, user must pick one }; /** * @brief Returns the status of the backup, as reported by heartbeats. * * It can be one of the BACKUP_STATUS_x enum values. * * @return Status of the backup, as reported by heartbeats. */ virtual int status() const { return BACKUP_STATUS_NOT_INITIALIZED; } /** * @brief Returns the progress of the backup, as reported by heartbeats. * * @return Progress of the backup, as reported by heartbeats. */ virtual int progress() const { return 0; } /** * @brief Returns upload count. * * @return Upload count. */ virtual int uploads() const { return 0; } /** * @brief Returns download count. * * @return Download count. */ virtual int downloads() const { return 0; } /** * @brief Returns the last activity timestamp, as reported by heartbeats. * * @return Last activity timestamp, as reported by heartbeats. */ virtual uint64_t activityTs() const { return 0; } /** * @brief Returns handle of the last synced node. * * @return Handle of the last synced node. */ virtual MegaHandle lastSync() const { return INVALID_HANDLE; } virtual MegaBackupInfo* copy() const { return nullptr; } virtual ~MegaBackupInfo() = default; }; class MegaHashSignatureImpl; /** * @brief Class to check a digital signatures * * The typical usage of this class: * - Construct the object using a public key * - Add data using MegaHashSignature::add (it can be called many times to add more data) * - Call MegaHashSignature::check to know if the data matches a signature * - Call MegaHashSignature::init and reuse the object if needed */ class MegaHashSignature { public: /** * @brief Initialize the object with a public key to check digital signatures * @param base64Key Base64-encode public key. * * This is the public key used to distribute MEGAsync updates: * "EACTzXPE8fdMhm6LizLe1FxV2DncybVh2cXpW3momTb8tpzRNT833r1RfySz5uHe8gdoXN1W0eM5Bk8X-LefygYYDS9RyXrRZ8qXrr9ITJ4r8ATnFIEThO5vqaCpGWTVi5pOPI5FUTJuhghVKTyAels2SpYT5CmfSQIkMKv7YVldaV7A-kY060GfrNg4--ETyIzhvaSZ_jyw-gmzYl_dwfT9kSzrrWy1vQG8JPNjKVPC4MCTZJx9SNvp1fVi77hhgT-Mc5PLcDIfjustlJkDBHtmGEjyaDnaWQf49rGq94q23mLc56MSjKpjOR1TtpsCY31d1Oy2fEXFgghM0R-1UkKswVuWhEEd8nO2PimJOl4u9ZJ2PWtJL1Ro0Hlw9OemJ12klIAxtGV-61Z60XoErbqThwWT5Uu3D2gjK9e6rL9dufSoqjC7UA2C0h7KNtfUcUHw0UWzahlR8XBNFXaLWx9Z8fRtA_a4seZcr0AhIA7JdQG5i8tOZo966KcFnkU77pfQTSprnJhCfEmYbWm9EZA122LJBWq2UrSQQN3pKc9goNaaNxy5PYU1yXyiAfMVsBDmDonhRWQh2XhdV-FWJ3rOGMe25zOwV4z1XkNBuW4T1JF2FgqGR6_q74B2ccFC8vrNGvlTEcs3MSxTI_EKLXQvBYy7hxG8EPUkrMVCaWzzTQAFEQ" */ MegaHashSignature(const char *base64Key); ~MegaHashSignature(); /** * @brief Reinitialize the object */ void init(); /** * @brief Add data to calculate the signature * @param data Byte buffer with the data * @param size Size of the buffer */ void add(const char *data, unsigned size); /** * @brief Check if the introduced data matches a signature * @param base64Signature Base64-encoded digital signature * @return true if the signature is correct, otherwise false */ bool checkSignature(const char *base64Signature); private: MegaHashSignatureImpl *pImpl; }; /** * @brief Details about a MEGA balance */ class MegaAccountBalance { public: virtual ~MegaAccountBalance(); /** * @brief Get the amount of the balance * @return Amount */ virtual double getAmount() const; /** * @brief Get the currency of the amount * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Currency of the amount */ virtual char *getCurrency() const; }; /** * @brief Details about a MEGA session */ class MegaAccountSession { public: virtual ~MegaAccountSession(); /** * @brief Get the creation date of the session * * In seconds since the Epoch * * @return Creation date of the session */ virtual int64_t getCreationTimestamp() const; /** * @brief Get the timestamp of the most recent usage of the session * @return Timestamp of the most recent usage of the session (in seconds since the Epoch) */ virtual int64_t getMostRecentUsage() const; /** * @brief Get the User-Agent of the client that created the session * * You take ownership of the returned value. Use delete[] to release the memory. * * @return User-Agent of the creator of the session */ virtual char *getUserAgent() const; /** * @brief Get the IP address of the client that created the session * * You take ownership of the returned value. Use delete[] to release the memory. * * @return IP address of the creator of the session */ virtual char *getIP() const; /** * @brief Get the country of the client that created the session * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Country of the creator of the session */ virtual char *getCountry() const; /** * @brief Retuns true if the session is the current one * @return True if the session is the current one. Otherwise false. */ virtual bool isCurrent() const; /** * @brief Get the state of the session * @return True if the session is alive, false otherwise */ virtual bool isAlive() const; /** * @brief Get the handle of the session * @return Handle of the session */ virtual MegaHandle getHandle() const; /** * @brief Get the Device-id of the device where the session originated * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Device-id of the device where the session originated */ virtual char *getDeviceId() const; }; /** * @brief Details about a MEGA purchase */ class MegaAccountPurchase { public: virtual ~MegaAccountPurchase(); /** * @brief Get the timestamp of the purchase * @return Timestamp of the purchase (in seconds since the Epoch) */ virtual int64_t getTimestamp() const; /** * @brief Get the handle of the purchase * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Handle of the purchase */ virtual char *getHandle() const; /** * @brief Get the currency of the purchase * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Currency of the purchase */ virtual char* getCurrency() const; /** * @brief Get the amount of the purchase * @return Amount of the purchase */ virtual double getAmount() const; /** * @brief Get the method of the purchase * * These are the valid methods: * - MegaApi::PAYMENT_METHOD_BALANCE = 0, * - MegaApi::PAYMENT_METHOD_PAYPAL = 1, * - MegaApi::PAYMENT_METHOD_ITUNES = 2, * - MegaApi::PAYMENT_METHOD_GOOGLE_WALLET = 3, * - MegaApi::PAYMENT_METHOD_BITCOIN = 4, * - MegaApi::PAYMENT_METHOD_UNIONPAY = 5, * - MegaApi::PAYMENT_METHOD_FORTUMO = 6, * - MegaApi::PAYMENT_METHOD_CREDIT_CARD = 8 * - MegaApi::PAYMENT_METHOD_CENTILI = 9 * - MegaApi::PAYMENT_METHOD_WINDOWS_STORE = 13 * * @return Method of the purchase */ virtual int getMethod() const; }; /** * @brief Details about a MEGA transaction */ class MegaAccountTransaction { public: virtual ~MegaAccountTransaction(); /** * @brief Get the timestamp of the transaction * @return Timestamp of the transaction (in seconds since the Epoch) */ virtual int64_t getTimestamp() const; /** * @brief Get the handle of the transaction * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Handle of the transaction */ virtual char *getHandle() const; /** * @brief Get the currency of the transaction * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Currency of the transaction */ virtual char* getCurrency() const; /** * @brief Get the amount of the transaction * @return Amount of the transaction */ virtual double getAmount() const; }; /** * @brief Details about a MEGA feature */ class MegaAccountFeature { public: virtual ~MegaAccountFeature() = default; /** * @brief Get the expiry timestamp * * @return Expiry timestamp */ virtual int64_t getExpiry() const = 0; /** * @brief Get the ID of this feature * * You take ownership of the returned value. Use delete[] to release the memory. * * @return ID of this feature */ virtual char* getId() const = 0; }; /** * @brief Details about a MEGA subscription */ class MegaAccountSubscription { public: enum { SUBSCRIPTION_STATUS_NONE = 0, SUBSCRIPTION_STATUS_VALID = 1, SUBSCRIPTION_STATUS_INVALID = 2 }; virtual ~MegaAccountSubscription() = default; /** * @brief Get the ID of this subscription * * You take ownership of the returned value. Use delete[] to release the memory. * * @return ID of this subscription */ virtual char* getId() const = 0; /** * @brief Check if the subscription is active * * If this function returns MegaAccountDetails::SUBSCRIPTION_STATUS_VALID, * the subscription will be automatically renewed. * See MegaAccountSubscription::getRenewTime() * * @return Information about the subscription status * * Valid return values are: * - MegaAccountSubscription::SUBSCRIPTION_STATUS_NONE = 0 * There isn't any active subscription * * - MegaAccountSubscription::SUBSCRIPTION_STATUS_VALID = 1 * There is an active subscription * * - MegaAccountSubscription::SUBSCRIPTION_STATUS_INVALID = 2 * A subscription exists, but it uses a payment gateway that is no longer valid */ virtual int getStatus() const = 0; /** * @brief Get the subscription cycle * * The return value will show if the subscription will be montly or yearly renewed. * Example return values: "1 M", "1 Y". * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Subscription cycle */ virtual char* getCycle() const = 0; /** * @brief Get the subscription payment provider name * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Payment provider name */ virtual char* getPaymentMethod() const = 0; /** * @brief Get the subscription payment provider ID * * @return Payment provider ID */ virtual int32_t getPaymentMethodId() const = 0; /** * @brief Get the subscription renew timestamp * * @return Renewal timestamp (in seconds since epoch) */ virtual int64_t getRenewTime() const = 0; /** * @brief Get the subscription account level * * @return Subscription account level * Valid values for PRO plan subscriptions: * - MegaAccountDetails::ACCOUNT_TYPE_FREE = 0 * - MegaAccountDetails::ACCOUNT_TYPE_PROI = 1 * - MegaAccountDetails::ACCOUNT_TYPE_PROII = 2 * - MegaAccountDetails::ACCOUNT_TYPE_PROIII = 3 * - MegaAccountDetails::ACCOUNT_TYPE_LITE = 4 * - MegaAccountDetails::ACCOUNT_TYPE_STARTER = 11 * - MegaAccountDetails::ACCOUNT_TYPE_BASIC = 12 * - MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL = 13 * - MegaAccountDetails::ACCOUNT_TYPE_BUSINESS = 100 * - MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI = 101 * * Valid value for feature plan subscriptions: * - MegaAccountDetails::ACCOUNT_TYPE_FEATURE = 99999 */ virtual int32_t getAccountLevel() const = 0; /** * @brief Get the features granted by this subscription * * You take the ownership of the returned value * * @return Features granted by this subscription. */ virtual MegaStringList* getFeatures() const = 0; /** * @brief Return if the subscription is related to an active trial * * @return True if the subscription is related to an active trial, otherwise false. */ virtual bool isTrial() const = 0; }; class MegaAccountPlan { public: virtual ~MegaAccountPlan() = default; /** * @brief Check if the plan is a PRO plan or a feature plan. * * @return True if the plan is a PRO plan */ virtual bool isProPlan() const = 0; /** * @brief Get account level of the plan * * @return Plan level of the MEGA account. * Valid values for PRO plans are: * - MegaAccountDetails::ACCOUNT_TYPE_FREE = 0 * - MegaAccountDetails::ACCOUNT_TYPE_PROI = 1 * - MegaAccountDetails::ACCOUNT_TYPE_PROII = 2 * - MegaAccountDetails::ACCOUNT_TYPE_PROIII = 3 * - MegaAccountDetails::ACCOUNT_TYPE_LITE = 4 * - MegaAccountDetails::ACCOUNT_TYPE_STARTER = 11 * - MegaAccountDetails::ACCOUNT_TYPE_BASIC = 12 * - MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL = 13 * - MegaAccountDetails::ACCOUNT_TYPE_BUSINESS = 100 * - MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI = 101 * * Valid value for feature plans is: * - MegaAccountDetails::ACCOUNT_TYPE_FEATURE = 99999 */ virtual int32_t getAccountLevel() const = 0; /** * @brief Get the features granted by this plan * * You take the ownership of the returned value * * @return Features granted by this plan. */ virtual MegaStringList* getFeatures() const = 0; /** * @brief Get the expiration time for the plan * * @return The time the plan expires */ virtual int64_t getExpirationTime() const = 0; /** * @brief The type of plan. Why it was granted. * * Not available for Bussiness/Pro Flexi. * * @return Plan type */ virtual int32_t getType() const = 0; /** * @brief Get the relating subscription ID * * Only available if the plan relates to a subscription. * * You take ownership of the returned value. Use delete[] to release the memory. * * @return ID of this subscription */ virtual char* getId() const = 0; /** * @brief Return if the plan is related to an active trial * * @return True if the plan is related to an active trial, otherwise false. */ virtual bool isTrial() const = 0; }; /** * @brief Details about a MEGA account */ class MegaAccountDetails { public: enum { ACCOUNT_TYPE_FREE = 0, ACCOUNT_TYPE_PROI = 1, ACCOUNT_TYPE_PROII = 2, ACCOUNT_TYPE_PROIII = 3, ACCOUNT_TYPE_LITE = 4, ACCOUNT_TYPE_STARTER = 11, ACCOUNT_TYPE_BASIC = 12, ACCOUNT_TYPE_ESSENTIAL = 13, ACCOUNT_TYPE_BUSINESS = 100, ACCOUNT_TYPE_PRO_FLEXI = 101, // also known as PRO 4 ACCOUNT_TYPE_FEATURE = 99999 }; enum MEGA_DEPRECATED { SUBSCRIPTION_STATUS_NONE = 0, SUBSCRIPTION_STATUS_VALID = 1, SUBSCRIPTION_STATUS_INVALID = 2 }; virtual ~MegaAccountDetails(); /** * @brief Get the PRO level of the MEGA account * @return PRO level of the MEGA account. * Valid values are: * - MegaAccountDetails::ACCOUNT_TYPE_FREE = 0 * - MegaAccountDetails::ACCOUNT_TYPE_PROI = 1 * - MegaAccountDetails::ACCOUNT_TYPE_PROII = 2 * - MegaAccountDetails::ACCOUNT_TYPE_PROIII = 3 * - MegaAccountDetails::ACCOUNT_TYPE_LITE = 4 * - MegaAccountDetails::ACCOUNT_TYPE_STARTER = 11 * - MegaAccountDetails::ACCOUNT_TYPE_BASIC = 12 * - MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL = 13 * - MegaAccountDetails::ACCOUNT_TYPE_BUSINESS = 100 * - MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI = 101 */ virtual int getProLevel(); /** * @brief Get the expiration time of the latest PRO plan * * The expiration time could be higher than the expiration time of the active PRO plan * * @return Expiration time for the latest PRO plan (in seconds since the Epoch) */ virtual int64_t getProExpiration(); /** * @brief Check if there is a valid subscription * * If this function returns MegaAccountDetails::SUBSCRIPTION_STATUS_VALID, * the PRO account will be automatically renewed. * See MegaAccountDetails::getSubscriptionRenewTime * * @return Information about about the subscription status * * Valid return values are: * - MegaAccountDetails::SUBSCRIPTION_STATUS_NONE = 0 * There isn't any active subscription * * - MegaAccountDetails::SUBSCRIPTION_STATUS_VALID = 1 * There is an active subscription * * - MegaAccountDetails::SUBSCRIPTION_STATUS_INVALID = 2 * A subscription exists, but it uses a payment gateway that is no longer valid * */ MEGA_DEPRECATED virtual int getSubscriptionStatus(); /** * @brief Get the time when the the PRO account will be renewed * @return Renewal time (in seconds since the Epoch) */ MEGA_DEPRECATED virtual int64_t getSubscriptionRenewTime(); /** * @brief Get the subscription method * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Subscription method. For example "Credit Card". */ MEGA_DEPRECATED virtual char* getSubscriptionMethod(); /** * @brief Get the subscription method id * * @return Subscription method. For example 16. */ MEGA_DEPRECATED virtual int getSubscriptionMethodId(); /** * @brief Get the subscription cycle * * The return value will show if the subscription will be montly or yearly renewed. * Example return values: "1 M", "1 Y". * * You take ownership of the returned value. Use delete[] to release the memory. * * @return Subscription cycle */ MEGA_DEPRECATED virtual char* getSubscriptionCycle(); /** * @brief Get the maximum storage for the account (in bytes) * @return Maximum storage for the account (in bytes) */ virtual long long getStorageMax(); /** * @brief Get the used storage * @return Used storage (in bytes) */ virtual long long getStorageUsed(); /** * @brief Get the used storage by versions * @return Used storage by versions (in bytes) */ virtual long long getVersionStorageUsed(); /** * @brief Get the maximum available bandwidth for the account * @return Maximum available bandwidth (in bytes) */ virtual long long getTransferMax(); /** * @brief Get the used bandwidth for own user allowance * @see: MegaAccountDetails::getTransferUsed * @return Used bandwidth (in bytes) */ virtual long long getTransferOwnUsed(); /** * @brief Get the used bandwidth served to other users * @see: MegaAccountDetails::getTransferUsed * @return Used bandwidth (in bytes) */ virtual long long getTransferSrvUsed(); /** * @brief Get the used bandwidth allowance including own, free and served to other users * @see: MegaAccountDetails::getTransferOwnUsed, MegaAccountDetails::getTemporalBandwidth, MegaAccountDetails::getTransferSrvUsed * @return Used bandwidth (in bytes) */ virtual long long getTransferUsed(); /** * @brief Returns the number of nodes with account usage info * * You can get information about each node using MegaAccountDetails::getStorageUsed, * MegaAccountDetails::getNumFiles, MegaAccountDetails::getNumFolders * * This function can return: * - 0 (no info about any node) * - 3 (info about the root node, the vault node and the rubbish node) * Use MegaApi::getRootNode MegaApi::getVaultNode and MegaApi::getRubbishNode to get those nodes. * * - >3 (info about root, vault, rubbish and incoming shares) * Use MegaApi::getInShares to get the incoming shares * * @return Number of items with account usage info */ virtual int getNumUsageItems(); /** * @brief Get the used storage in for a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Used storage (in bytes) * @see MegaApi::getRootNode, MegaApi::getRubbishNode, MegaApi::getVaultNode */ virtual long long getStorageUsed(MegaHandle handle); /** * @brief Get the number of files in a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Number of files in the node * @see MegaApi::getRootNode, MegaApi::getRubbishNode, MegaApi::getVaultNode */ virtual long long getNumFiles(MegaHandle handle); /** * @brief Get the number of folders in a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Number of folders in the node * @see MegaApi::getRootNode, MegaApi::getRubbishNode, MegaApi::getVaultNode */ virtual long long getNumFolders(MegaHandle handle); /** * @brief Get the used storage by versions in for a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Used storage by versions (in bytes) * @see MegaApi::getRootNode, MegaApi::getRubbishNode, MegaApi::getVaultNode */ virtual long long getVersionStorageUsed(MegaHandle handle); /** * @brief Get the number of versioned files in a node * * Only root nodes are supported. * * @param handle Handle of the node to check * @return Number of versioned files in the node * @see MegaApi::getRootNode, MegaApi::getRubbishNode, MegaApi::getVaultNode */ virtual long long getNumVersionFiles(MegaHandle handle); /** * @brief Creates a copy of this MegaAccountDetails object. * * The resulting object is fully independent of the source MegaAccountDetails, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaAccountDetails object */ virtual MegaAccountDetails* copy(); /** * @brief Get the number of MegaAccountBalance objects associated with the account * * You can use MegaAccountDetails::getBalance to get those objects. * * @return Number of MegaAccountBalance objects */ virtual int getNumBalances() const; /** * @brief Returns the MegaAccountBalance object associated with an index * * You take the ownership of the returned value * * @param i Index of the object * @return MegaAccountBalance object */ virtual MegaAccountBalance* getBalance(int i) const; /** * @brief Get the number of MegaAccountSession objects associated with the account * * You can use MegaAccountDetails::getSession to get those objects. * * @return Number of MegaAccountSession objects */ virtual int getNumSessions() const; /** * @brief Returns the MegaAccountSession object associated with an index * * You take the ownership of the returned value * * @param i Index of the object * @return MegaAccountSession object */ virtual MegaAccountSession* getSession(int i) const; /** * @brief Get the number of MegaAccountPurchase objects associated with the account * * You can use MegaAccountDetails::getPurchase to get those objects. * * @return Number of MegaAccountPurchase objects */ virtual int getNumPurchases() const; /** * @brief Returns the MegaAccountPurchase object associated with an index * * You take the ownership of the returned value * * @param i Index of the object * @return MegaAccountPurchase object */ virtual MegaAccountPurchase* getPurchase(int i) const; /** * @brief Get the number of MegaAccountTransaction objects associated with the account * * You can use MegaAccountDetails::getTransaction to get those objects. * * @return Number of MegaAccountTransaction objects */ virtual int getNumTransactions() const; /** * @brief Returns the MegaAccountTransaction object associated with an index * * You take the ownership of the returned value * * @param i Index of the object * @return MegaAccountTransaction object */ virtual MegaAccountTransaction* getTransaction(int i) const; /** * @brief Get the number of hours that are taken into account to calculate the free bandwidth quota * * The number of bytes transferred in that time is provided using MegaAccountDetails::getTemporalBandwidth * * @return Number of hours taken into account to calculate the free bandwidth quota */ virtual int getTemporalBandwidthInterval(); /** * @brief Get the number of bytes that were recently transferred using free allowance * * The time interval in which those bytes were transferred * is provided (in hours) using MegaAccountDetails::getTemporalBandwidthInterval * * @see: MegaAccountDetails::getTransferUsed * @return Number of bytes that were recently transferred */ virtual long long getTemporalBandwidth(); /** * @brief Check if the temporal bandwidth usage is valid after an overquota error * @return True if the temporal bandwidth is valid, otherwise false */ virtual bool isTemporalBandwidthValid(); /** * @brief Get the number of active MegaAccountFeature-s in the account * * You can use MegaAccountDetails::getActiveFeature to get each of those objects. * * @return Number of MegaAccountFeature objects */ virtual int getNumActiveFeatures() const = 0; /** * @brief Returns the MegaAccountFeature object associated with an index * * You take the ownership of the returned value * * @param featureIndex Index of the object * @return MegaAccountFeature object */ virtual MegaAccountFeature* getActiveFeature(int featureIndex) const = 0; /** * @brief Get feature account level for feature related subscriptions * * @return Level for feature related subscriptions */ MEGA_DEPRECATED virtual int64_t getSubscriptionLevel() const = 0; /** * @brief Get subscription features for this account * * You take the ownership of the returned value * * @return Subscription features for this account. The value of each feature should be treated as a 32bit unsigned int */ MEGA_DEPRECATED virtual MegaStringIntegerMap* getSubscriptionFeatures() const = 0; /** * @brief Get the number of active subscriptions in the account. * * You can use MegaAccountDetails::getSubscription to get each of those objects. * * @return Number of active subscriptions */ virtual int getNumSubscriptions() const = 0; /** * @brief Returns the MegaAccountSubscription object associated with an index * * You take the ownership of the returned value * * @param subscriptionsIndex Index of the object * @return MegaAccountSubscription object */ virtual MegaAccountSubscription* getSubscription(int subscriptionsIndex) const = 0; /** * @brief Get the number of active plans in the account. * * You can use MegaAccountDetails::getPlan to get each of those objects. * * @return Number of active plans */ virtual int getNumPlans() const = 0; /** * @brief Returns the MegaAccountPlan object associated with an index * * You take the ownership of the returned value * * @param plansIndex Index of the object * @return MegaAccountPlan object */ virtual MegaAccountPlan* getPlan(int plansIndex) const = 0; }; class MegaCurrency { public: virtual ~MegaCurrency(); /** * @brief Creates a copy of this MegaCurrency object. * * The resulting object is fully independent of the source MegaCurrency, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaCurrency object */ virtual MegaCurrency *copy(); /** * @brief Get the currency symbol of prices * * The currency symbol is encoded in B64url, since it may be a UTF-8 char. * In example, for €, it returns "4oKs". * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @return currency symbol of price */ virtual const char* getCurrencySymbol(); /** * @brief Get the currency name of prices, ie. EUR * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @return currency name of price */ virtual const char* getCurrencyName(); /** * @brief Get the currency symbol of local prices * * The currency symbol is encoded in B64url, since it may be a UTF-8 char. * In example, for €, it returns "4oKs". * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @return currency symbol of local price */ virtual const char* getLocalCurrencySymbol(); /** * @brief Get the currency name of local prices, ie. NZD * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @return currency name of local price */ virtual const char* getLocalCurrencyName(); }; /** * @brief Details about pricing plans * * Use MegaApi::getPricing to get the pricing plans to upgrade MEGA accounts */ class MegaPricing { public: virtual ~MegaPricing(); /** * @brief Get the number of available products to upgrade the account * @return Number of available products */ virtual int getNumProducts(); /** * @brief Get the handle of a product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Handle of the product * @see MegaApi::getPaymentId */ virtual MegaHandle getHandle(int productIndex); /** * @brief Get the PRO level associated with the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return PRO level associated with the product: * Valid values are: * - MegaAccountDetails::ACCOUNT_TYPE_FREE = 0 * - MegaAccountDetails::ACCOUNT_TYPE_PROI = 1 * - MegaAccountDetails::ACCOUNT_TYPE_PROII = 2 * - MegaAccountDetails::ACCOUNT_TYPE_PROIII = 3 * - MegaAccountDetails::ACCOUNT_TYPE_LITE = 4 * - MegaAccountDetails::ACCOUNT_TYPE_STARTER = 11 * - MegaAccountDetails::ACCOUNT_TYPE_BASIC = 12 * - MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL = 13 * - MegaAccountDetails::ACCOUNT_TYPE_BUSINESS = 100 * - MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI = 101 */ virtual int getProLevel(int productIndex); /** * @brief Get the number of GB of storage associated with the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @note business plans have unlimited storage * @return number of GB of storage, zero if index is invalid, or -1 * if pricing plan is a business plan */ virtual int getGBStorage(int productIndex); /** * @brief Get the number of GB of bandwidth associated with the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @note business plans have unlimited bandwidth * @return number of GB of bandwidth, zero if index is invalid, or -1, * if pricing plan is a business plan */ virtual int getGBTransfer(int productIndex); /** * @brief Get the duration of the product (in months) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Duration of the product (in months) */ virtual int getMonths(int productIndex); /** * @brief Get the price of the product (in cents) * If you want the price in cents with decimals call MegaPricing::getAmountWithDecimals * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Price of the product (in cents) */ virtual int getAmount(int productIndex); /** * @brief Get the price in the local currency (in cents) * If you want the price in the local currency in cents with decimals call * MegaPricing::getLocalPriceWithDecimals * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Price of the product (in cents) */ virtual int getLocalPrice(int productIndex); /** * @brief Get the net base price of the product without tax (in cents, with decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Net base price of the product in cents, including decimal fraction, without tax */ virtual double getPriceNetWithDecimals(const int /*productIndex*/) const; /** * @brief Get the net base price of the product in local currency without tax (in cents, with * decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Net base price of the product in local currency in cents, including decimal fraction, * without tax */ virtual double getLocalPriceNetWithDecimals(const int /*productIndex*/) const; /** * @brief Get the price of the product (in cents, with decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Price of the product in cents, including decimal fraction */ virtual double getAmountWithDecimals(const int /*productIndex*/) const; /** * @brief Get the price in the local currency (in cents, with decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Price in the local currency of the product in cents, including decimal fraction */ virtual double getLocalPriceWithDecimals(const int /*productIndex*/) const; /** * @brief Get the monthly price of the product (in cents, with decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Monthly price of the product in cents, including decimal fraction */ virtual double getAmountMonthWithDecimals(const int /*productIndex*/) const; /** * @brief Get the net monthly base price of the product without tax (in cents, with decimals) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Net monthly base price of the product in cents, including decimal fraction, without * tax */ virtual double getMonthlyBasePriceNetWithDecimals(const int /*productIndex*/) const; /** * @brief Get a description of the product * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Description of the product */ virtual const char* getDescription(int productIndex); /** * @brief getIosID Get the iOS ID of the product * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return iOS ID of the product, NULL if index is invalid or an empty string * if pricing plan is a business plan. */ virtual const char* getIosID(int productIndex); /** * @brief Get the Android ID of the product * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Android ID of the product, NULL if index is invalid or an empty string * if pricing plan is a business plan. */ virtual const char* getAndroidID(int productIndex); /** * @brief Returns true if the pricing plan is a Business plan * * You can check if the plan is pure buiness or Pro Flexi by calling * the method MegaApi::getProLevel * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return true if the pricing plan is a Business plan, otherwise return false */ virtual bool isBusinessType(int productIndex); /** * @brief Returns true if the pricing plan is a Feature plan * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * * @return true if the pricing plan is a Feature plan, otherwise return false */ virtual bool isFeaturePlan(int productIndex) const; /** * @brief Get the monthly price of the product (in cents) * If you want the monthly price in cents with decimals call * MegaPricing::getAmountMonthWithDecimals * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Monthly price of the product (in cents) */ virtual int getAmountMonth(int productIndex); /** * @brief Creates a copy of this MegaPricing object. * * The resulting object is fully independent of the source MegaPricing, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaPricing object */ virtual MegaPricing *copy(); /** * @brief Get the number of GB of storage associated with the product, per user * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return number of GB of storage associated with the product, per user */ virtual int getGBStoragePerUser(int productIndex); /** * @brief Get the number of GB of transfer associated with the product, per user * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return number of GB of transfer associated with the product, per user */ virtual int getGBTransferPerUser(int productIndex); /** * @brief Get the minimum number of users to purchase the product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return minimum number of users to purchase the product */ virtual unsigned int getMinUsers(int productIndex); /** * @brief Get the monthly price of the product, per user (in cents) * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return monthly price of the product, per user (in cents) */ virtual unsigned int getPricePerUser(int productIndex); /** * @brief Get the monthly local price of the product, per user (in cents) * * Local prices are only available if the account will be charged in a different * currency than local. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return monthly local price of the product, per user (in cents) */ virtual unsigned int getLocalPricePerUser(int productIndex); /** * @brief Get the price per storage block * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return price per storage block */ virtual unsigned int getPricePerStorage(int productIndex); /** * @brief Get the local price per storage block * * Local prices are only available if the account will be charged in a different * currency than local. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return local price per storage block */ virtual unsigned int getLocalPricePerStorage(int productIndex); /** * @brief Get the number of GB of storage, per block * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return number of GB of storage, per block */ virtual int getGBPerStorage(int productIndex); /** * @brief Get the price per transfer block * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return price per transfer block */ virtual unsigned int getPricePerTransfer(int productIndex); /** * @brief Get the local price per transfer block * * Local prices are only available if the account will be charged in a different * currency than local. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return local price per storage block */ virtual unsigned int getLocalPricePerTransfer(int productIndex); /** * @brief Get the number of GB of transfer, per block * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return number of GB of transfer, per block */ virtual int getGBPerTransfer(int productIndex); /** * @brief Get the features of this product * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Features of this product. The value of each feature should be treated as a 32bit unsigned int */ virtual MegaStringIntegerMap* getFeatures(int productIndex) const; /** * @brief Get test category bitmap of a product * * The returned value must always be greater than 0 * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return test category bitmap */ virtual unsigned int getTestCategory(int productIndex) const; /** * @brief Check whether the product has a discount * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return True if the product has a discount, false otherwise */ virtual bool hasDiscount(int productIndex) const; /** * @brief Get the discount code for the product * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Discount code for the product, or nullptr if there is no discount * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. */ virtual const char* getDiscountCode(int productIndex) const; /** * @brief Get the discount name for the product * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Discount name for the product, or nullptr if there is no discount * * The SDK retains the ownership of the returned value. It will be valid until * the MegaPricing object is deleted. */ virtual const char* getDiscountName(int productIndex) const; /** * @brief Get the discount group for the product * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Discount group for the product, or 0 if there is no discount */ virtual int getDiscountGroup(int productIndex) const; /** * @brief Get the discount duration in months for the product * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Discount duration in months for the product, or 0 if there is no discount */ virtual int getDiscountMonths(int productIndex) const; /** * @brief Get the discount percentage for the product * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Discount percentage for the product, or 0 if there is no discount */ virtual int getDiscountPercentage(int productIndex) const; /** * @brief Get trial duration in days * * The returned value will be 0 if the plan is not elegible for trial. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return Trial duration in days */ virtual unsigned int getTrialDurationInDays(int productIndex) const = 0; /** * @brief Check whether the product has a mobile offer * * Determines if the specified product includes an associated mobile offer. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return True if the product has a mobile offer, false otherwise */ virtual bool hasMobileOffers(int productIndex) const = 0; /** * @brief Get the mobile offer identifier * * Returns the identifier of the mobile offer associated with the given * product. * * If the product does not have a mobile offer, this method returns a empty * string. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return A null-terminated string containing the mobile offer ID */ virtual std::string getMobileOfferId(int productIndex) const = 0; /** * @brief Check whether the mobile offer title should be used * * Possible values are: * - false: The mobile offer title should not be displayed. * - true: The mobile offer title should be displayed. * * If the product does not have a mobile offer, this method returns false. * * @param productIndex Product index (from 0 to MegaPricing::getNumProducts) * @return True if the mobile offer title should be displayed, false * otherwise */ virtual bool hasMobileOfferUat(int productIndex) const = 0; }; /** * @brief The MegaAchievementsDetails class * * There are several MEGA Achievements that a user can unlock, resulting in a * temporary extension of the storage and/or transfer quota during a period of * time. * * Currently there are 4 different classes of MEGA Achievements: * * - Welcome: Create your free account and get 35 GB of complimentary storage space, * valid for 30 days. * * - Invite: Invite as many friends or coworkers as you want. For every signup under the * invited email address, you will receive 10 GB of complimentary storage plus 20 GB * of transfer quota, both valid for 365 days, provided that the new user installs * either MEGAsync or a mobile app and starts using MEGA. * * - Desktop install: When you install MEGAsync you get 20 GB of complimentary storage * space plus 40 GB of transfer quota, both valid for 180 days. * * - Mobile install: When you install our mobile app you get 15 GB of complimentary * storage space plus 30 GB transfer quota, both valid for 180 days. * * When the user unlocks one of the achievements above, it unlocks an "Award". The award * includes a timestamps to indicate when it was unlocked, plus an expiration timestamp. * Afterwards, the award will not be active. Additionally, each award results in a "Reward". * The reward is linked to the corresponding award and includes the storage and transfer * quota obtained thanks to the unlocked award. * * @note It may take 2-3 days for achievements to show on the account after they have been completed. */ class MegaAchievementsDetails { public: enum { MEGA_ACHIEVEMENT_WELCOME = 1, MEGA_ACHIEVEMENT_INVITE = 3, MEGA_ACHIEVEMENT_DESKTOP_INSTALL = 4, MEGA_ACHIEVEMENT_MOBILE_INSTALL = 5, MEGA_ACHIEVEMENT_ADD_PHONE = 9, MEGA_ACHIEVEMENT_PWM_TRIAL = 10, MEGA_ACHIEVEMENT_VPN_TRIAL = 11 }; virtual ~MegaAchievementsDetails(); /** * @brief Get the base storage value for this account * @return The base storage value, in bytes */ virtual long long getBaseStorage(); /** * @brief Checks if the corresponding achievement is valid * * Some achievements are valid only for some users. * * The following classes are valid: * - MEGA_ACHIEVEMENT_WELCOME = 1 * - MEGA_ACHIEVEMENT_INVITE = 3 * - MEGA_ACHIEVEMENT_DESKTOP_INSTALL = 4 * - MEGA_ACHIEVEMENT_MOBILE_INSTALL = 5 * - MEGA_ACHIEVEMENT_ADD_PHONE = 9 * - MEGA_ACHIEVEMENT_PWM_TRIAL = 10 * - MEGA_ACHIEVEMENT_VPN_TRIAL = 11 * * @param class_id Id of the achievement. * @return True if it is valid, false otherwise */ virtual bool isValidClass(int class_id); /** * @brief Get the storage granted by a MEGA achievement class * * The following classes are valid: * - MEGA_ACHIEVEMENT_WELCOME = 1 * - MEGA_ACHIEVEMENT_INVITE = 3 * - MEGA_ACHIEVEMENT_DESKTOP_INSTALL = 4 * - MEGA_ACHIEVEMENT_MOBILE_INSTALL = 5 * - MEGA_ACHIEVEMENT_ADD_PHONE = 9 * - MEGA_ACHIEVEMENT_PWM_TRIAL = 10 * - MEGA_ACHIEVEMENT_VPN_TRIAL = 11 * * @param class_id Id of the MEGA achievement * @return Storage granted by this MEGA achievement class, in bytes */ virtual long long getClassStorage(int class_id); /** * @brief Get the transfer quota granted by a MEGA achievement class * * The following classes are valid: * - MEGA_ACHIEVEMENT_WELCOME = 1 * - MEGA_ACHIEVEMENT_INVITE = 3 * - MEGA_ACHIEVEMENT_DESKTOP_INSTALL = 4 * - MEGA_ACHIEVEMENT_MOBILE_INSTALL = 5 * - MEGA_ACHIEVEMENT_ADD_PHONE = 9 * - MEGA_ACHIEVEMENT_PWM_TRIAL = 10 * - MEGA_ACHIEVEMENT_VPN_TRIAL = 11 * * @param class_id Id of the MEGA achievement * @return Transfer quota granted by this MEGA achievement class, in bytes */ virtual long long getClassTransfer(int class_id); /** * @brief Get the duration of storage/transfer quota granted by a MEGA achievement class * * The following classes are valid: * - MEGA_ACHIEVEMENT_WELCOME = 1 * - MEGA_ACHIEVEMENT_INVITE = 3 * - MEGA_ACHIEVEMENT_DESKTOP_INSTALL = 4 * - MEGA_ACHIEVEMENT_MOBILE_INSTALL = 5 * - MEGA_ACHIEVEMENT_ADD_PHONE = 9 * - MEGA_ACHIEVEMENT_PWM_TRIAL = 10 * - MEGA_ACHIEVEMENT_VPN_TRIAL = 11 * * The storage and transfer quota resulting from a MEGA achievement may expire after * certain number of days. In example, the "Welcome" reward lasts for 30 days and afterwards * the granted storage and transfer quota is revoked. * * @param class_id Id of the MEGA achievement * @return Number of days for the storage/transfer quota granted by this MEGA achievement class */ virtual int getClassExpire(int class_id); /** * @brief Get the number of unlocked awards for this account * @return Number of unlocked awards */ virtual unsigned int getAwardsCount(); /** * @brief Get the MEGA achievement class of the award * @param index Position of the award in the list of unlocked awards * @return The achievement class associated to the award in position \c index */ virtual int getAwardClass(unsigned int index); /** * @brief Get the id of the award * @param index Position of the award in the list of unlocked awards * @return The id of the award in position \c index */ virtual int getAwardId(unsigned int index); /** * @brief Get the timestamp of the award (when it was unlocked) * @param index Position of the award in the list of unlocked awards * @return The timestamp of the award (when it was unlocked) in position \c index */ virtual int64_t getAwardTimestamp(unsigned int index); /** * @brief Get the expiration timestamp of the award * * After this moment, the storage and transfer quota granted as result of the award * will not be valid anymore. * * @note The expiration time may not be the \c getAwardTimestamp plus the number of days * returned by \c getClassExpire, since the award can be unlocked but not yet granted. It * typically takes 2 days from unlocking the award until the user is actually rewarded. * * If this function returns 0, it means the award is permanent (does not expire). * * @param index Position of the award in the list of unlocked awards * @return The expiration timestamp of the award in position \c index */ virtual int64_t getAwardExpirationTs(unsigned int index); /** * @brief Get the list of referred emails for the award * * This function is specific for the achievements of class MEGA_ACHIEVEMENT_INVITE. * * You take ownership of the returned value. * * @param index Position of the award in the list of unlocked awards * @return The list of invited emails for the award in position \c index */ virtual MegaStringList* getAwardEmails(unsigned int index); /** * @brief Get the number of active rewards for this account * @return Number of active rewards */ virtual int getRewardsCount(); /** * @brief Get the id of the award associated with the reward * @param index Position of the reward in the list of active rewards * @return The id of the award associated with the reward */ virtual int getRewardAwardId(unsigned int index); /** * @brief Get the storage rewarded by the award * @param index Position of the reward in the list of active rewards * @return The storage rewarded by the award */ virtual long long getRewardStorage(unsigned int index); /** * @brief Get the transfer quota rewarded by the award * @param index Position of the reward in the list of active rewards * @return The transfer quota rewarded by the award */ virtual long long getRewardTransfer(unsigned int index); /** * @brief Get the storage rewarded by the award_id * @param award_id The id of the award * @return The storage rewarded by the award_id */ virtual long long getRewardStorageByAwardId(int award_id); /** * @brief Get the transfer rewarded by the award_id * @param award_id The id of the award * @return The transfer rewarded by the award_id */ virtual long long getRewardTransferByAwardId(int award_id); /** * @brief Get the duration of the reward * @param index Position of the reward in the list of active rewards * @return The duration of the reward, in days */ virtual int getRewardExpire(unsigned int index); /** * @brief Creates a copy of this MegaAchievementsDetails object. * * The resulting object is fully independent of the source MegaAchievementsDetails, * it contains a copy of all internal attributes, so it will be valid after * the original object is deleted. * * You are the owner of the returned object * * @return Copy of the MegaAchievementsDetails object */ virtual MegaAchievementsDetails *copy(); /** * @brief Returns the actual storage achieved by this account * * This function considers all the storage granted to the logged in * account as result of the unlocked achievements. It does not consider * the expired achievements nor the permanent base storage. * * @return The achieved storage for this account */ virtual long long currentStorage(); /** * @brief Returns the actual transfer quota achieved by this account * * This function considers all the transfer quota granted to the logged * in account as result of the unlocked achievements. It does not consider * the expired achievements. * * @return The achieved transfer quota for this account */ virtual long long currentTransfer(); /** * @brief Returns the actual achieved storage due to referrals * * This function considers all the storage granted to the logged in account * as result of the successful invitations (referrals). It does not consider * the expired achievements. * * @return The achieved storage by this account as result of referrals */ virtual long long currentStorageReferrals(); /** * @brief Returns the actual achieved transfer quota due to referrals * * This function considers all the transfer quota granted to the logged * in account as result of the successful invitations (referrals). It * does not consider the expired achievements. * * @return The transfer achieved quota by this account as result of referrals */ virtual long long currentTransferReferrals(); }; class MegaCancelToken { protected: MegaCancelToken(); public: /** * @brief Creates an object which can be passed as parameter for some MegaApi methods in order to * request the cancellation of the processing associated to the function. @see MegaApi::search * * The instance of MegaCancelToken can be reset (@see cancel) for reuse for future calls, but * it should not be used for more than one operation at the same time. * * You take ownership of the returned value. * * @return A pointer to an object that allows to cancel the processing of some functions. */ static MegaCancelToken* createInstance(); virtual ~MegaCancelToken(); /** * @brief Allows to set the value of the flag */ virtual void cancel() = 0; /** * @brief Returns the state of the flag * @return The state of the flag */ virtual bool isCancelled() const = 0; }; /** * @brief Container to store information of a VPN Cluster. * * - Host * - DNS: list of IPs * * Instances of this class are immutable. */ class MegaVpnCluster { public: /** * @brief Get the host of this VPN Cluster. * * The caller does not take ownership of the returned const char*, which is valid as long as * current instance is valid. * * @return the host of this VPN Cluster, always not-null. */ virtual const char* getHost() const = 0; /** * @brief Get the list of IPs for current VPN Cluster * * You take the ownership of the returned value * * @return A list containing the IPs for current VPN Cluster, always not-null */ virtual MegaStringList* getDns() const = 0; /** * @brief Get the list of ad-blocking DNS IPs. * * You take the ownership of the returned value * * @return A list containing the IPs for ad blocking DNS, always not-null */ virtual MegaStringList* getAdBlockingDns() const = 0; virtual ~MegaVpnCluster() = default; virtual MegaVpnCluster* copy() const = 0; protected: MegaVpnCluster() = default; }; /** * @brief Container for MegaVpnCluster-s * * Instances of this class are immutable. */ class MegaVpnClusterMap { public: /** * @brief Get the list of keys in current instance * * You take the ownership of the returned value * * @return A list containing the keys in current intance, always not-null */ virtual MegaIntegerList* getKeys() const = 0; /** * @brief Get the payload for the provided key * * You take the ownership of the returned value * * @param key Key of the element that you want to get from the map * * @return The payload for the provided key, or null if not found */ virtual MegaVpnCluster* get(int64_t key) const = 0; /** * @brief Get the entry count for the container * * @return Entry count for the container */ virtual int64_t size() const = 0; virtual ~MegaVpnClusterMap() = default; virtual MegaVpnClusterMap* copy() const = 0; protected: MegaVpnClusterMap() = default; }; /** * @brief Container to store information of a VPN Region. * * - Name (example: hMLKTUojS6o, 1MvzBCx1Uf4) * - Country Code (example: ES, LU) * - Country Name (example: Spain, Luxembourg) * - Region Name (optional) (example: Esch-sur-Alzette) * - Town Name (Optional) (example: Bettembourg) * - Clusters (contain information like host, DNS list, possibly others) * * Instances of this class are immutable. */ class MegaVpnRegion { public: /** * @brief Get the name of this VPN Region. * * The caller does not take ownership of the returned value, which is valid as long as current * instance is valid. * * @return the name of this VPN Region, always not-null. */ virtual const char* getName() const = 0; /** * @brief Get the country code where the VPN Region is located. * * The caller does not take ownership of the returned value, which is valid as long as current * instance is valid. * * @return the country code for this VPN Region, always not-null. */ virtual const char* getCountryCode() const = 0; /** * @brief Get the name of the country where the VPN Region is located. * * The caller does not take ownership of the returned value, which is valid as long as current * instance is valid. * * @return the country name for this VPN Region, always not-null. */ virtual const char* getCountryName() const = 0; /** * @brief Get the name of the country region where this VPN Region is located. * * Optional value. It may be empty for certain VPN Regions * * The caller does not take ownership of the returned value, which is valid as long as current * instance is valid. * * @return the country region name for this VPN Region, always not-null. */ virtual const char* getRegionName() const = 0; /** * @brief Get the name name of the town where this VPN is located. * * Optional value. It may be empty for certain VPN Regions * * The caller does not take ownership of the returned value, which is valid as long as current * instance is valid. * * @return the name of the Town for this VPN Region, always not-null. */ virtual const char* getTownName() const = 0; /** * @brief Get a container with all Clusters of this VPN Region. * * The caller takes ownership of the returned instance. * * @return a container with all Clusters of this VPN Region, always not-null. */ virtual MegaVpnClusterMap* getClusters() const = 0; virtual MegaVpnRegion* copy() const = 0; virtual ~MegaVpnRegion() = default; protected: MegaVpnRegion() = default; }; /** * @brief List of MegaVpnRegion objects * * Instances of this class are immutable. */ class MegaVpnRegionList { public: /** * @brief Get the instance at position i in the list. * * The caller does not take ownership of the returned value, which will be valid until the list * is deleted. If you want to retain a copy of the instance returned by this function, use * MegaVpnRegion::copy(). * If the given index was out of range, this function will return null. * * @param i Position of the instance that we want to get from the list * * @return Instance at position i in the list, or null if given index was out of range */ virtual const MegaVpnRegion* get(unsigned i) const = 0; /** * @brief Get the instance count for the list * * @return Instance count for this list */ virtual unsigned size() const = 0; virtual MegaVpnRegionList* copy() const = 0; virtual ~MegaVpnRegionList() = default; protected: MegaVpnRegionList() = default; }; /** * @brief Container class to store and load Mega VPN credentials data. * * - SlotIDs occupied by VPN credentials. * - Full list of VPN regions. * - IPv4 and IPv6 used on each SlotID. * - ClusterID used on each SlotID. * - Cluster Public Key associated to each ClusterID. */ class MegaVpnCredentials { protected: MegaVpnCredentials(); public: virtual ~MegaVpnCredentials(); /** * @brief Get the SlotIDs occupied by the user. * * The caller takes the ownership of the MegaIntegerList object. * * @return A pointer to a MegaIntegerList with the SlotIDs. */ virtual MegaIntegerList* getSlotIDs() const = 0; /** * @brief Get the list of the available VPN regions. * * This object is a copy of the one owned by the MegaVpnCredentials object. * The caller takes the ownership of the MegaStringList object. * * @return A pointer to a MegaStringList with the VPN regions. */ virtual MegaStringList* getVpnRegions() const = 0; /** * @brief Get the list of the available VPN regions, including the clusters for each region. * * The caller takes the ownership of the returned object. * * @return A pointer to a list of detailed VPN regions. */ virtual MegaVpnRegionList* getVpnRegionsDetailed() const = 0; /** * @brief Get the IPv4 associated with the VPN credentials of a SlotID. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaVpnCredentials object is valid too. * * @param slotID The SlotID associated with the VPN credentials. * @return const char* with the IPv4 if the SlotID has a valid VPN credential, nullptr otherwise. */ virtual const char* getIPv4(int slotID) const = 0; /** * @brief Get the IPv6 associated with the VPN credentials of a SlotID. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaVpnCredentials object is valid too. * * @param slotID The SlotID associated with the VPN credentials. * @return const char* with the IPv6 if the SlotID has a valid VPN credential, nullptr otherwise. */ virtual const char* getIPv6(int slotID) const = 0; /** * @brief Get the DeviceID associated with the VPN credentials of a SlotID. * * The string value can be empty if there is no associated device ID. * The current device ID can be retrieved via MegaApi::getDeviceId * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaVpnCredentials object is valid too. * * @param slotID The SlotID associated with the VPN credentials. * @return const char* with the DeviceID if the SlotID has a valid VPN credential, nullptr otherwise. */ virtual const char* getDeviceID(int slotID) const = 0; /** * @brief Get the ClusterID associated with the VPN credentials of a SlotID. * * @param slotID The SlotID associated with the VPN credentials. * @return int with the ClusterID if the SlotID has a valid VPN credential, -1 otherwise. */ virtual int getClusterID(int slotID) const = 0; /** * @brief Get the Cluster Public Key associated with a ClusterID. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaVpnCredentials object is valid too. * * @param clusterID The ClusterID used on any of the VPN credentials. * @return const char* with the Cluster Public Key if the ClusterID exists, nullptr otherwise. */ virtual const char* getClusterPublicKey(int clusterID) const = 0; /** * @brief Copy the MegaVpnCredentials object. * * This copy is meant to be used from another scope which must survive the actual owner of this MegaVpnCredentials object. * The caller takes the ownership of the new MegaVpnCredentials object. * * @return MegaVpnCredentials* with the copied MegaVpnCredentials object. */ virtual MegaVpnCredentials* copy() const = 0; }; class MegaNetworkConnectivityTestResults { public: virtual ~MegaNetworkConnectivityTestResults() = default; /** * @brief Possible test results. */ enum // 1:1 with enum values from internal implementation { NETWORK_CONNECTIVITY_TEST_PASS = 0, NETWORK_CONNECTIVITY_TEST_FAIL = 1, NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE = 2, }; /** * @brief Get the result of testing communication over IPv4 * * @return The type of the flag. Possible values are any of the NETWORK_CONNECTIVITY_TEST_x * values. */ virtual int getIPv4UDP() const = 0; /** * @brief Get the result of testing DNS resolution over IPv4 * * @return The type of the flag. Possible values are any of the NETWORK_CONNECTIVITY_TEST_x * values. */ virtual int getIPv4DNS() const = 0; /** * @brief Get the result of testing communication over IPv6 * * @return The type of the flag. Possible values are any of the NETWORK_CONNECTIVITY_TEST_x * values. */ virtual int getIPv6UDP() const = 0; /** * @brief Get the result of testing DNS resolution over IPv6 * * @return The type of the flag. Possible values are any of the NETWORK_CONNECTIVITY_TEST_x * values. */ virtual int getIPv6DNS() const = 0; /** * @brief Copy this object. * * This copy is meant to be used from another scope which must survive the actual owner of this * object. The caller takes the ownership of the new object. * * @return Pointer with the copied object. */ virtual MegaNetworkConnectivityTestResults* copy() const = 0; }; /** * @brief Container class to store all information of a notification. * * - ID. * - Title. * - Description. * - Name of the main image for the notification. * - Name of the icon for the notification. * - Default static path for the notification image. * - Timestamp of when the notification became available to the user. * - Timestamp of when the notification will expire. * - Whether it should show a banner or only render a notification. * - Metadata for the first call-to-action ("link" and "text" attributes). * - Metadata for the second call-to-action ("link" and "text" attributes). * * Objects of this class are immutable. */ class MegaNotification { protected: MegaNotification() = default; public: virtual ~MegaNotification() = default; /** * @brief Get the ID associated with this notification. * * @return the ID associated with this notification. */ virtual int64_t getID() const = 0; /** * @brief Get the title of this notification. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaNotification object is valid too. * * @return the title of this notification, always not-null. */ virtual const char* getTitle() const = 0; /** * @brief Get the description for this notification. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaNotification object is valid too. * * @return the description for this notification, always not-null. */ virtual const char* getDescription() const = 0; /** * @brief Get the name of the main image for this notification. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaNotification object is valid too. * * @return the name of the main image for this notification, always not-null. */ virtual const char* getImageName() const = 0; /** * @brief Get the name of the icon for this notification. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaNotification object is valid too. * * @return the name of the icon for this notification, always not-null. */ virtual const char* getIconName() const = 0; /** * @brief Get the default static path of the image associated with this notification. * * The caller does not take the ownership of the const char* object. * The const char* object is valid as long as the current MegaNotification object is valid too. * * @return the default static path of the image associated with this notification, always not-null. */ virtual const char* getImagePath() const = 0; /** * @brief Get the timestamp of when the notification became available to the user. * * @return the timestamp of when the notification became available to the user. */ virtual int64_t getStart() const = 0; /** * @brief Get the timestamp of when the notification will expire. * * @return the timestamp of when the notification will expire. */ virtual int64_t getEnd() const = 0; /** * @brief Report whether it should show a banner or only render a notification. * * @return whether it should show a banner or only render a notification. */ virtual bool showBanner() const = 0; /** * @brief Get metadata for the first call to action, represented by attributes "link" and "text", * and their corresponding values. * * The caller does not take the ownership of the returned object. * The returned object is valid as long as the current MegaNotification object is valid too. * * @return metadata for the first call to action, always not-null. */ virtual const MegaStringMap* getCallToAction1() const = 0; /** * @brief Get metadata for the second call-to-action, represented by attributes "link" and "text", * and their corresponding values. * * The caller does not take the ownership of the returned object. * The returned object is valid as long as the current MegaNotification object is valid too. * * @return metadata for the second call-to-action, always not-null. */ virtual const MegaStringMap* getCallToAction2() const = 0; /** * @brief Get available rendering modes. * * The caller takes ownership of the returned object and is in charge of releasing the memory. * * @return available rendering modes, null if no rendering mode was supported. */ virtual MegaStringList* getRenderModes() const = 0; /** * @brief Get the fields of the received rendering mode. * * The caller takes ownership of the returned object and is in charge of releasing the memory. * * @param mode Rendering mode for which the fields will be returned * * @return fields of the received rendering mode, null if the mode was not supported. */ virtual MegaStringMap* getRenderModeFields(const char* mode) const = 0; /** * @brief Copy the MegaNotification object. * * This copy is meant to be used from another scope which must survive the actual owner of this MegaNotification object. * The caller takes the ownership of the new MegaNotification object. * * @return MegaNotification* of the copied object. */ virtual MegaNotification* copy() const = 0; }; /** * @brief List of MegaNotification objects * * A MegaNotificationList has the ownership of the MegaNotification objects that it contains, so they will be * only valid until the MegaNotificationList is deleted. If you want to retain a MegaNotification returned by * a MegaNotificationList, use MegaNotification::copy(). * * Objects of this class are immutable. */ class MegaNotificationList { public: /** * @brief Returns the MegaNotification at position i in the list * * The MegaNotificationList retains the ownership of the returned MegaNotification. It will be only valid until * the MegaNotificationList is deleted. If you want to retain a MegaNotification returned by this function, * use MegaNotification::copy(). * * If the index is >= the size of the list, this function returns NULL. * * @param i Position of the MegaNotification that we want to get from the list * @return MegaNotification at position i in the list */ virtual const MegaNotification* get(unsigned i) const = 0; /** * @brief Returns the number of MegaNotification-s in the list * @return number of MegaNotification-s in the list */ virtual unsigned size() const = 0; virtual MegaNotificationList* copy() const = 0; virtual ~MegaNotificationList() = default; }; class MegaFuseExecutorFlags { protected: MegaFuseExecutorFlags(); public: virtual ~MegaFuseExecutorFlags(); /** * @brief * How many threads is the executor allowed to spawn? * * @return * The maximum number of threads the executor is allowed to spawn. */ virtual size_t getMaxThreadCount() const = 0; /** * @brief * How long should idle threads be retained? * * @return * How long, in seconds, idle threads are retained. */ virtual size_t getMaxThreadIdleTime() const = 0; /** * @brief * How many idle threads is the executor allowed to retain. * * @return * The number of idle threads that the executor will retain. */ virtual size_t getMinThreadCount() const = 0; /** * @brief * Specify how many threads the executor is allowed to spawn. * * @param max * How many threads the executor is allowed to spawn. * * @return * True if max is a non-zero value, false otherwise. */ virtual bool setMaxThreadCount(size_t max) = 0; /** * @brief * Specify how long an idle thread is retained. * * @param seconds * How long an idle thread should be retained. */ virtual void setMaxThreadIdleTime(size_t seconds) = 0; /** * @brief * Specify how many idle threads the executor can retain. * * @param min * How many idle threads the executor can retain. */ virtual void setMinThreadCount(size_t min) = 0; }; // MegaFuseExecutorFlags class MegaFuseFlags { protected: MegaFuseFlags(); public: enum LogLevel { // Only emit error messages. LOG_LEVEL_ERROR, // Only emit error messages and warnings. LOG_LEVEL_WARNING, // Only emit error, warning and info messages. LOG_LEVEL_INFO, // Emit all messages. LOG_LEVEL_DEBUG }; // LogLevel enum FileExplorerView { // Do nothing FILE_EXPLORER_VIEW_NONE = 0, // Set file explorer view to list FILE_EXPLORER_VIEW_LIST = 1 }; // FileExplorerView virtual ~MegaFuseFlags(); /** * @brief * Create a copy of this instance. * * @return * A copy of this instance. */ virtual MegaFuseFlags* copy() const = 0; /** * @brief * Create a new instance. * * @return * A new instance. */ static MegaFuseFlags* create(); /** * @brief * How long should we wait until we upload a modified file? * * @return * How long, in seconds, before modified files are uploaded. */ virtual size_t getFlushDelay() const = 0; /** * @brief * Query the service's log level. * * @return * The service's current log level. */ virtual int getLogLevel() const = 0; /** * @brief * Query the service's file explorer view. * * @return * The service's current file explorer view. */ virtual int getFileExplorerView() const = 0; /** * @brief * Retrieve a reference to the inode cache's flags. * * @return * A reference to the inode cache's flags. */ virtual MegaFuseInodeCacheFlags* getInodeCacheFlags() = 0; /** * @brief * Retrieve a reference to the mount executor flags. * * These flags control how many threads an individual mount is allowed * to create and how long those threads should remain idle before they * are destroyed. * * @return * A reference to the mount executor flags. */ virtual MegaFuseExecutorFlags* getMountExecutorFlags() = 0; /** * @brief * Retrieve a reference to the subsystem's executor flags. * * These flags control how many threads the FUSE subsystem is allowed to * create for its own internal use and how long those threads should * remain before they are destroyed. * * @return * A reference to the subsystem's executor flags. */ virtual MegaFuseExecutorFlags* getSubsystemExecutorFlags() = 0; /** * @brief * Specify how long we should wait before uploading a modified file. * * @param seconds * How many seconds before a modified file is uploaded. */ virtual void setFlushDelay(size_t seconds) = 0; /** * @brief * Specify the service's log level. * * @param level * The service's new log level. */ virtual void setLogLevel(int level) = 0; /** * @brief * Specify the service's file explorer view. * * @param level * The service's new file explorer view. */ virtual void setFileExplorerView(int view) = 0; }; // MegaFuseFlags class MegaFuseInodeCacheFlags { protected: MegaFuseInodeCacheFlags(); public: virtual ~MegaFuseInodeCacheFlags(); virtual size_t getCleanAgeThreshold() const = 0; virtual size_t getCleanInterval() const = 0; virtual size_t getCleanSizeThreshold() const = 0; virtual size_t getMaxSize() const = 0; virtual void setCleanAgeThreshold(std::size_t seconds) = 0; virtual void setCleanInterval(std::size_t seconds) = 0; virtual void setCleanSizeThreshold(std::size_t size) = 0; virtual void setMaxSize(std::size_t size) = 0; }; // MegaFuseInodeCacheFlags class MegaMount { protected: MegaMount(); public: enum Result { // The operation was aborted due to client shutdown. ABORTED, // FUSE is supported but the backend is not installed. BACKEND_UNAVAILABLE, // The mount's busy and cannot be disabled. BUSY, // A mount has encountered an expected failure and has been disabled. FAILED, // Mount target already exists. LOCAL_EXISTS, // Mount target doesn't denote a directory. LOCAL_FILE, // Mount target is being synchronized. LOCAL_SYNCING, // A mount's already associated with the target path. LOCAL_TAKEN, // Mount target doesn't exist. LOCAL_UNKNOWN, // A mount already exists with a specified name. NAME_TAKEN, // The specified name is too long. NAME_TOO_LONG, // The specified name contains invalid character(s). NAME_INVALID_CHAR, // No name has been specified for a mount. NO_NAME, // Mount source doesn't describe a directory. REMOTE_FILE, // Mount source doesn't exist. REMOTE_UNKNOWN, // Mount was successful. SUCCESS, // Encountered an unexpected error while mounting. UNEXPECTED, // No mount is associated with the specified handle or path. UNKNOWN, // FUSE isn't supported on this platform. UNSUPPORTED }; // Result virtual ~MegaMount(); /** * @brief * Copies this instance. * * @return * A copy of this instance. */ virtual MegaMount* copy() const = 0; /** * @brief * Creates a new instance. * * @return * A new instance. */ static MegaMount* create(); /** * @brief * Retrieves a reference to this mount's flags. * * @return * A mutable reference to this mount's flags. */ virtual MegaMountFlags* getFlags() const = 0; /** * @brief * Retrieves the handle this mount is associated with. * * @return * The handle this mount is associated with. */ virtual MegaHandle getHandle() const = 0; /** * @brief * Retrieve this mount's local path. * * @return * This mount's local path. */ virtual const char* getPath() const = 0; /** * @brief * Translates a result code into a human readable description. * * @param result * The result you want to translate. * * @return * A description of the result. */ static const char* getResultDescription(int result); /** * @brief * Translates a result code into a human readable string. * * @param result * The result you want to translate. * * @return * A human-readable version of the result code. */ static const char* getResultString(int result); /** * @brief * Update this mount's flags. * * @param flags * The flags we want the mount to have. */ virtual void setFlags(const MegaMountFlags* flags) = 0; /** * @brief * Set the handle this mount should be associated with. * * @param handle * A handle identifying a cloud node. */ virtual void setHandle(MegaHandle handle) = 0; /** * @brief * Set this mount's local path. * * @param path * Where the mount should be present on the local filesystem. */ virtual void setPath(const char* path) = 0; }; // MegaMount class MegaMountFlags { protected: MegaMountFlags(); public: virtual ~MegaMountFlags(); /** * @brief * Copies this instance. * * @return * A copy of this instance. */ virtual MegaMountFlags* copy() const = 0; /** * @brief * Creates a new instance. * * @return * A new instance. */ static MegaMountFlags* create(); /** * @brief * Query whether the mount will be enabled on startup. * * @return * True if the mount will be enabled at startup. */ virtual bool getEnableAtStartup() const = 0; /** * @brief * Retrives a mount's name. * * @return * The mount's name. */ virtual const char* getName() const = 0; /** * @brief * Query whether a mount is persistent. * * @return * True if the mount is persistent. */ virtual bool getPersistent() const = 0; /** * @brief * Query whether a mount is read only. * * @return * True if the mount is read only. */ virtual bool getReadOnly() const = 0; /** * Specify whether a mount should be enabled at startup. * * @param enabled * True if the mount should be enabled at startup. * * @note * Altering the value of this flag will make a transient mount * persistent. This makes sense as stating how it should behave * on startup implies that the mount will exist for more than * a single session. */ virtual void setEnableAtStartup(bool enable) = 0; /** * @brief * Set the mount's name. * * @param name * The name of the mount. */ virtual void setName(const char* name) = 0; /** * @brief * Specify whether a mount is persistent. * * @param persistent * True if the mount should be persistent. */ virtual void setPersistent(bool persistent) = 0; /** * @brief * Specify whether a mount is read only. * * @param readOnly * True if the mount should be read only. */ virtual void setReadOnly(bool readOnly) = 0; }; // MegaMountFlags class MegaMountList { protected: MegaMountList(); public: virtual ~MegaMountList(); /** * @brief * Copies this instance. * * @return * A copy of this instance. */ virtual MegaMountList* copy() const = 0; /** * @brief * Retrieves an element from the list. * * @param index * The index of the element to retrieve. * * @return * NULL if index is out of range. */ virtual const MegaMount* get(size_t index) const = 0; /** * @brief * Query how many elements are in this list. * * @return * The number of elements in this list. */ virtual size_t size() const = 0; }; // MegaMountList /** * @brief Reason chosen from a multiple-choice by a user when canceling a subscription */ class MegaCancelSubscriptionReason { public: /** * @brief * Create a new instance. * * @param text * The actual text of the reason. * @param position * The rendered position of the selected reason (for example "1", "1.a", "2" etc.) * * @return * A new instance. */ static MegaCancelSubscriptionReason* create(const char* text, const char* position); /** * @brief * Get the text of the reason. * The returned value is owned by the current instances. * * @return * The text of the reason. */ virtual const char* text() const = 0; /** * @brief * Get the rendered position of the selected reason. * The returned value is owned by the current instances. * * @return * The rendered position of the selected reason. */ virtual const char* position() const = 0; virtual MegaCancelSubscriptionReason* copy() const = 0; virtual ~MegaCancelSubscriptionReason() = default; }; /** * @brief List of MegaCancelSubscriptionReason instances * * The list has the ownership of the instances that it contains, so they will be valid until the * list is deleted. If you want to retain a copy of a particular instance, use * MegaCancelSubscriptionReason::copy(). */ class MegaCancelSubscriptionReasonList { public: /** * @brief * Create a new instance. * * @return * A new instance. */ static MegaCancelSubscriptionReasonList* create(); /** * @brief * Add an element to the list. * * @param reason * The reason to be added to the list. */ virtual void add(const MegaCancelSubscriptionReason* reason) = 0; /** * @brief * Retrieve an element from the list. * * @param index * The index of the element to retrieve. * * @return * The element at the given index or NULL if index was out of range. */ virtual const MegaCancelSubscriptionReason* get(size_t index) const = 0; /** * @brief * Query how many elements are in this list. * * @return * The number of elements in this list. */ virtual size_t size() const = 0; virtual MegaCancelSubscriptionReasonList* copy() const = 0; virtual ~MegaCancelSubscriptionReasonList() = default; }; } #endif //MEGAAPI_H sdk-10.11.0/include/megaapi_impl.h000066400000000000000000010200261516266226600166700ustar00rootroot00000000000000/** * @file megaapi_impl.h * @brief Private header file of the intermediate layer for the MEGA C++ SDK. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGAAPI_IMPL_H #define MEGAAPI_IMPL_H #include "mega.h" #include "mega/command.h" #include "mega/filesystem.h" #include "mega/gfx/external.h" #include "mega/heartbeats.h" #include "mega/totp.h" #include "megaapi.h" #include #include #include #define CRON_USE_LOCAL_TIME 1 #include #ifdef HAVE_LIBUV #include "uv.h" #include #ifdef USE_OPENSSL #include #define ENABLE_EVT_TLS 1 #endif #endif #ifndef _WIN32 #include #include #endif #if TARGET_OS_IPHONE #include "mega/gfx/GfxProcCG.h" #endif #ifdef __ANDROID__ #include "mega/android/androidFileSystem.h" #endif #include "impl/share.h" // FUSE #include #include #include ////////////////////////////// SETTINGS ////////////////////////////// ////////// Support for threads and mutexes //Choose one of these options. //Otherwise, C++11 threads and mutexes will be used //#define USE_PTHREAD ////////// Support for thumbnails and previews. //If you selected QT for threads and mutexes, it will be also used for thumbnails and previews //You can create a subclass of MegaGfxProcessor and pass it to the constructor of MegaApi //#define USE_FREEIMAGE /////////////////////////// END OF SETTINGS /////////////////////////// namespace mega { #if USE_PTHREAD class MegaThread : public PosixThread {}; class MegaSemaphore : public PosixSemaphore {}; #else class MegaThread : public CppThread {}; class MegaSemaphore : public CppSemaphore {}; #endif #ifdef WIN32 class MegaHttpIO: public CurlHttpIO {}; class MegaWaiter: public WinWaiter {}; #else class MegaHttpIO: public CurlHttpIO {}; class MegaWaiter: public PosixWaiter {}; #endif #ifdef HAVE_LIBUV class MegaTCPServer; class MegaHTTPServer; class MegaFTPServer; class MegaFTPDataServer; #endif typedef std::vector MegaSmallIntVector; typedef std::multimap MegaSmallIntMap; class MegaDbAccess : public SqliteDbAccess { public: MegaDbAccess(const LocalPath& rootPath) : SqliteDbAccess(rootPath) { } }; // MegaDbAccess class MegaErrorPrivate : public MegaError { public: /** * @param errorCode: API MegaError API_* value or internal ErrorCodes enum */ MegaErrorPrivate(int errorCode = MegaError::API_OK); /** * @param errorCode: API MegaError API_* value or internal ErrorCodes enum */ MegaErrorPrivate(int errorCode, SyncError syncError); #ifdef ENABLE_SYNC /** * @param errorCode: API MegaError API_* value or internal ErrorCodes enum */ MegaErrorPrivate(int errorCode, MegaSync::Error syncError); #endif /** * @param errorCode: API MegaError API_* value or internal ErrorCodes enum */ MegaErrorPrivate(int errorCode, long long value); MegaErrorPrivate(const Error &err); MegaErrorPrivate(fuse::MountResult result); MegaErrorPrivate(const MegaError &megaError); ~MegaErrorPrivate() override; MegaError* copy() const override; int getErrorCode() const override; int getMountResult() const override; long long getValue() const override; bool hasExtraInfo() const override; long long getUserStatus() const override; long long getLinkStatus() const override; const char* getErrorString() const override; const char* toString() const override; private: long long mValue = 0; long long mUserStatus = MegaError::UserErrorCode::USER_ETD_UNKNOWN; long long mLinkStatus = MegaError::LinkErrorCode::LINK_UNKNOWN; fuse::MountResult mMountResult = fuse::MOUNT_SUCCESS; }; class MegaTransferPrivate; class MegaTreeProcCopy : public MegaTreeProcessor { public: vector nn; unsigned nc = 0; bool allocated = false; MegaTreeProcCopy(MegaClient *client); bool processMegaNode(MegaNode* node) override; void allocnodes(void); protected: MegaClient *client; }; class MegaSizeProcessor : public MegaTreeProcessor { protected: long long totalBytes; public: MegaSizeProcessor(); bool processMegaNode(MegaNode* node) override; long long getTotalBytes(); }; class ExecuteOnce { // An object to go on the requestQueue. // It could be completed early (eg on cancel()), in which case nothing happens when it's dequeued. // If not completed early, it executes on dequeue. // In either case the flag is set when executed, so it won't be executed in the other case. // An atomic type is used to make sure the flag is set and checked along with actual execution. // The objects referred to in the completion function must live until the first execution completes. // After that it doesn't matter if it contains dangling pointers etc as it won't be called anymore. std::function f; std::atomic_uint executed; public: ExecuteOnce(std::function fn) : f(fn), executed(0) {} bool exec() { if (++executed > 1) { return false; } f(); return true; // indicates that this call is the time it ran } }; class MegaRecursiveOperation : public MegaTransferListener { public: MegaRecursiveOperation(MegaClient* c) : mMegaapiThreadClient(c) {} virtual ~MegaRecursiveOperation() = default; virtual void start(MegaNode* node) = 0; void notifyStage(uint8_t stage); void ensureThreadStopped(); // check if user has cancelled recursive operation by using cancelToken of associated transfer bool isCancelledByFolderTransferToken() const; // check if we have received onTransferFinishCallback for every transfersTotalCount bool allSubtransfersResolved() { return transfersFinishedCount >= transfersTotalCount; } // setter/getter for transfersTotalCount void setTransfersTotalCount (size_t count) { transfersTotalCount = count; } size_t getTransfersTotalCount () { return transfersTotalCount; } // ---- MegaTransferListener methods --- void onTransferStart(MegaApi *, MegaTransfer *t) override; void onTransferUpdate(MegaApi *, MegaTransfer *t) override; void onTransferFinish(MegaApi*, MegaTransfer *t, MegaError *e) override; protected: MegaApiImpl *megaApi; MegaTransferPrivate *transfer; MegaTransferListener *listener; int recursive; int tag; // number of sub-transfers finished with an error uint64_t mIncompleteTransfers = 0; // number of sub-transfers expected to be transferred (size of TransferQueue provided to sendPendingTransfers) // in case we detect that user cancelled recursive operation (via cancel token) at sendPendingTransfers, // those sub-transfers not processed yet (startxfer not called) will be discounted from transfersTotalCount size_t transfersTotalCount = 0; // number of sub-transfers started, onTransferStart received (startxfer called, and file injected into SDK transfer subsystem) size_t transfersStartedCount = 0; // number of sub-transfers finished, onTransferFinish received size_t transfersFinishedCount = 0; // flag to notify STAGE_TRANSFERRING_FILES to apps, when all sub-transfers have been queued in SDK core already bool startedTransferring = false; // If the thread was started, it queues a completion before exiting // That will be executed when the queued request is procesed // We also keep a pointer to it here, so cancel() can execute it early. shared_ptr mCompletionForMegaApiThread; // worker thread std::atomic_bool mWorkerThreadStopFlag { false }; std::thread mWorkerThread; // thread id of MegaApiImpl thread std::thread::id mMainThreadId; // it's only safe to use this client ptr when on the MegaApiImpl's thread MegaClient* megaapiThreadClient(); // set node handle for root folder in transfer void setRootNodeHandleInTransfer(); // called from onTransferFinish for the last sub-transfer void complete(Error e, bool cancelledByUser = false); // return true if thread is stopped or canceled by transfer token bool isStoppedOrCancelled(const std::string& name) const; private: // client ptr to only be used from the MegaApiImpl's thread MegaClient* mMegaapiThreadClient; }; class TransferQueue; class MegaFolderUploadController : public MegaRecursiveOperation, public std::enable_shared_from_this { public: MegaFolderUploadController(MegaApiImpl *megaApi, MegaTransferPrivate *transfer); ~MegaFolderUploadController() override; // ---- MegaRecursiveOperation methods --- void start(MegaNode* node) override; protected: unique_ptr fsaccess; // Random number generator and cipher to avoid using client's which would cause threading corruption PrnGen rng; SymmCipher tmpnodecipher; // temporal nodeHandle for uploads from App handle mCurrUploadId = 1; // generates a temporal nodeHandle for uploads from App handle nextUploadId(); struct Tree { // represents the node name in case of folder type string folderName; // Only figure out the fs type per folder (and on the worker thread), as it is expensive FileSystemType fsType{FileSystemType::FS_UNKNOWN}; // If there is already a cloud node with this name for this parent, this is set // It also becomes set after we have created a cloud node for this folder unique_ptr megaNode; // true when children nodes of 'megaNode' are pre-loaded already bool childrenLoaded = false; // Otherwise this is the record we will send to create this folder NewNode newnode; // files to upload to this folder struct FileRecord { LocalPath lp; FileFingerprint fp; FileRecord(const LocalPath& a, const FileFingerprint& b) : lp(a), fp(b) {} }; vector files; // subfolders vector> subtrees; void recursiveCountFolders(unsigned& existing, unsigned& total) { total += 1; existing += megaNode ? 1 : 0; for (auto& n : subtrees) { n->recursiveCountFolders(existing, total); } } }; Tree mUploadTree; /* Scan entire tree recursively, and retrieve folder structure and files to be uploaded. * A putnodes command can only add subtrees under same target, so in case we need to add * subtrees under different targets, this method will generate a subtree for each one. * This happens on the worker thread. */ enum scanFolder_result { scanFolder_succeeded, scanFolder_cancelled, scanFolder_failed }; scanFolder_result scanFolder(Tree& tree, LocalPath& localPath, uint32_t& foldercount, uint32_t& filecount); // Gathers up enough (but not too many) newnode records that are all descendants of a single folder // and can be created in a single operation. // Called from the main thread just before we send the next set of folder creation commands. enum batchResult { batchResult_cancelled, batchResult_requestSent, batchResult_batchesComplete, batchResult_stillRecursing }; batchResult createNextFolderBatch(Tree& tree, vector& newnodes, uint32_t filecount, bool isBatchRootLevel); // Iterate through all pending files of each uploaded folder, and start all upload transfers bool genUploadTransfersForFiles(Tree& tree, TransferQueue& transferQueue); }; class MegaScheduledCopyController : public MegaScheduledCopy, public MegaRequestListener, public MegaTransferListener { public: MegaScheduledCopyController(MegaApiImpl *megaApi, int tag, int folderTransferTag, handle parenthandle, const char *filename, bool attendPastBackups, const char *speriod, int64_t period=-1, int maxBackups = 10); MegaScheduledCopyController(MegaScheduledCopyController *backup); ~MegaScheduledCopyController() override; void update(); void start(bool skip = false); void removeexceeding(bool currentoneOK); void abortCurrent(); // MegaScheduledCopy interface MegaScheduledCopy *copy() override; const char *getLocalFolder() const override; MegaHandle getMegaHandle() const override; int getTag() const override; int64_t getPeriod() const override; const char *getPeriodString() const override; int getMaxBackups() const override; int getState() const override; long long getNextStartTime(long long oldStartTimeAbsolute = -1) const override; bool getAttendPastBackups() const override; MegaTransferList *getFailedTransfers() override; // MegaScheduledCopy setters void setLocalFolder(const std::string &value); void setMegaHandle(const MegaHandle &value); void setTag(int value); void setPeriod(const int64_t &value); void setPeriodstring(const std::string &value); void setMaxBackups(int value); void setState(int value); void setAttendPastBackups(bool value); //getters&setters int64_t getStartTime() const; void setStartTime(const int64_t &value); std::string getBackupName() const; void setBackupName(const std::string &value); int64_t getOffsetds() const; void setOffsetds(const int64_t &value); int64_t getLastbackuptime() const; void setLastbackuptime(const int64_t &value); int getFolderTransferTag() const; void setFolderTransferTag(int value); //convenience methods bool isBackup(std::string localname, std::string backupname) const; int64_t getTimeOfBackup(std::string localname) const; protected: // common variables MegaApiImpl *megaApi; MegaClient *client; MegaScheduledCopyListener *backupListener; int state; int tag; int64_t lastwakeuptime; int64_t lastbackuptime; //ds absolute int pendingremovals; int folderTransferTag; //reused between backup instances std::string basepath; std::string backupName; handle parenthandle; int maxBackups; int64_t period; std::string periodstring; cron_expr ccronexpr; bool valid; int64_t offsetds; //times offset with epoch time? int64_t startTime; // when shall the next backup begin bool attendPastBackups; // backup instance related handle currentHandle; std::string currentName; std::list pendingFolders; std::vector failedTransfers; int recursive; int pendingTransfers; int pendingTags; // backup instance stats int64_t currentBKStartTime; int64_t updateTime; long long transferredBytes; long long totalBytes; long long speed; long long meanSpeed; long long numberFiles; //number of files successfully uploaded long long totalFiles; long long numberFolders; // internal methods void onFolderAvailable(MegaHandle handle); bool checkCompletion(); bool isBusy() const; int64_t getLastBackupTime(); long long getNextStartTimeDs(long long oldStartTimeds = -1) const; std::string epochdsToString(int64_t rawtimeds) const; void clearCurrentBackupData(); public: void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError *e) override; void onTransferStart(MegaApi *api, MegaTransfer *transfer) override; void onTransferUpdate(MegaApi *api, MegaTransfer *transfer) override; void onTransferTemporaryError(MegaApi *, MegaTransfer *t, MegaError* e) override; void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError *e) override; long long getNumberFolders() const override; void setNumberFolders(long long value); long long getNumberFiles() const override; void setNumberFiles(long long value); long long getMeanSpeed() const override; void setMeanSpeed(long long value); long long getSpeed() const override; void setSpeed(long long value); long long getTotalBytes() const override; void setTotalBytes(long long value); long long getTransferredBytes() const override; void setTransferredBytes(long long value); int64_t getUpdateTime() const override; void setUpdateTime(const int64_t &value); int64_t getCurrentBKStartTime() const override; void setCurrentBKStartTime(const int64_t &value); long long getTotalFiles() const override; void setTotalFiles(long long value); MegaScheduledCopyListener *getBackupListener() const; void setBackupListener(MegaScheduledCopyListener *value); cron_expr getCcronexpr() const; void setCcronexpr(const cron_expr &value); bool isValid() const; void setValid(bool value); }; class MegaFolderDownloadController : public MegaRecursiveOperation, public std::enable_shared_from_this { public: MegaFolderDownloadController(MegaApiImpl *megaApi, MegaTransferPrivate *transfer); ~MegaFolderDownloadController() override; // ---- MegaRecursiveOperation methods --- void start(MegaNode *node) override; protected: unique_ptr fsaccess; struct LocalTree { LocalTree(LocalPath lp) { localPath = lp; } LocalPath localPath; vector> childrenNodes; }; vector mLocalTree; // Scan entire tree recursively, and retrieve folder structure and files to be downloaded. enum scanFolder_result { scanFolder_succeeded, scanFolder_cancelled, scanFolder_failed }; scanFolder_result scanFolder(MegaNode *node, LocalPath& path, FileSystemType fsType, unsigned& fileAddedCount); // Create all local directories in one shot. This happens on the worker thread. std::unique_ptr createFolderGenDownloadTransfersForFiles(FileSystemType fsType, uint32_t fileCount, Error& e); // Iterate through all pending files, and adds all download transfers bool genDownloadTransfersForFiles(TransferQueue* transferQueue, LocalTree& folder, FileSystemType fsType, bool folderExists); }; namespace totp { constexpr std::optional getHashAlgorithm(const int alg) { using td = mega::MegaNode::PasswordNodeData::TotpData; switch (alg) { case td::HASH_ALGO_SHA1: return HashAlgorithm::SHA1; case td::HASH_ALGO_SHA256: return HashAlgorithm::SHA256; case td::HASH_ALGO_SHA512: return HashAlgorithm::SHA512; default: return std::nullopt; } } constexpr int getHashAlgorithmPublicId(const std::optional alg) { using td = mega::MegaNode::PasswordNodeData::TotpData; if (!alg) { return td::TOTPNULLOPT; } switch (*alg) { case HashAlgorithm::SHA1: return td::HASH_ALGO_SHA1; case HashAlgorithm::SHA256: return td::HASH_ALGO_SHA256; case HashAlgorithm::SHA512: return td::HASH_ALGO_SHA512; } return td::TOTPNULLOPT; } constexpr std::string_view hashAlgorithmPubToStrView(const int alg) { if (const auto optAlg = getHashAlgorithm(alg); optAlg) return totp::hashAlgorithmToStrView(*optAlg); return ""; } constexpr int charToPubhashAlgorithm(const std::string_view alg) { if (const auto optAlg = charTohashAlgorithm(alg)) return getHashAlgorithmPublicId(*optAlg); return MegaNode::PasswordNodeData::TotpData::TOTPNULLOPT; } } class MegaNodePrivate : public MegaNode, public Cacheable { public: class CCNDataPrivate: public MegaNode::CreditCardNodeData { public: CCNDataPrivate(const char* cardNumber, const char* notes, const char* cardHolderName, const char* cvv, const char* expirationDate): mCardNumber{charPtrToStrOpt(cardNumber)}, mNotes{charPtrToStrOpt(notes)}, mCardHolderName{charPtrToStrOpt(cardHolderName)}, mCvv{charPtrToStrOpt(cvv)}, mExpirationDate{charPtrToStrOpt(expirationDate)} {} void setCardNumber(const char* cardNumber) override { mCardNumber = charPtrToStrOpt(cardNumber); } void setNotes(const char* notes) override { mNotes = charPtrToStrOpt(notes); } void setCardHolderName(const char* cardHolderName) override { mCardHolderName = charPtrToStrOpt(cardHolderName); } void setCvv(const char* cvv) override { mCvv = charPtrToStrOpt(cvv); } void setExpirationDate(const char* expirationDate) override { mExpirationDate = charPtrToStrOpt(expirationDate); } const char* cardNumber() const override { return getConstCharPtr(mCardNumber); } const char* notes() const override { return getConstCharPtr(mNotes); } const char* cardHolderName() const override { return getConstCharPtr(mCardHolderName); } const char* cvv() const override { return getConstCharPtr(mCvv); } const char* expirationDate() const override { return getConstCharPtr(mExpirationDate); } private: std::optional mCardNumber, mNotes, mCardHolderName, mCvv, mExpirationDate; }; class PNDataPrivate : public MegaNode::PasswordNodeData { public: class TotpDataPrivate: public TotpData { public: class ValidationPrivate: public Validation { public: bool sharedSecretExist() const override { return mFieldsPresence[INDEX_SHSE]; } bool sharedSecretValid() const override { return !mValidationErrors[totp::INVALID_TOTP_SHARED_SECRET]; } bool algorithmExist() const override { return mFieldsPresence[INDEX_HASH]; } bool algorithmValid() const override { return !mValidationErrors[totp::INVALID_TOTP_ALG]; } bool expirationTimeExist() const override { return mFieldsPresence[INDEX_EXPT]; } bool expirationTimeValid() const override { return !mValidationErrors[totp::INVALID_TOTP_EXPT]; } bool nDigitsExist() const override { return mFieldsPresence[INDEX_NDIG]; } bool nDigitsValid() const override { return !mValidationErrors[totp::INVALID_TOTP_NDIGITS]; } bool isValidForCreate() const override { return mFieldsPresence.all() && isValidForUpdate(); } bool isValidForUpdate() const override { return mValidationErrors.none(); } ValidationPrivate(std::optional sharedSecret, std::optional expirationTimeSecs, std::optional hashAlgorithm, std::optional ndigits) { mFieldsPresence[INDEX_SHSE] = sharedSecret.has_value(); mFieldsPresence[INDEX_EXPT] = expirationTimeSecs.has_value(); mFieldsPresence[INDEX_HASH] = hashAlgorithm.has_value(); mFieldsPresence[INDEX_NDIG] = ndigits.has_value(); const auto alg = hashAlgorithm ? std::optional{totp::hashAlgorithmPubToStrView( static_cast(*hashAlgorithm))} : std::nullopt; mValidationErrors = totp::validateFields(sharedSecret, ndigits, expirationTimeSecs, alg); } protected: static constexpr size_t INDEX_SHSE{0}; static constexpr size_t INDEX_EXPT{1}; static constexpr size_t INDEX_HASH{2}; static constexpr size_t INDEX_NDIG{3}; std::bitset<4> mFieldsPresence{}; totp::TotpValidationErrors mValidationErrors{}; }; static TotpDataPrivate* createRemovalInstance() { TotpDataPrivate* data = new TotpDataPrivate(); data->mRemove = true; return data; } TotpDataPrivate(const TotpData& totpData): mSharedSecret{charPtrToStrOpt(totpData.sharedSecret())}, mExpirationTimeSecs( convertIfPositive(totpData.expirationTime())), mHashAlgorithm{convertIfPositive(totpData.hashAlgorithm())}, mNdigits(convertIfPositive(totpData.nDigits())), mRemove(totpData.markedToRemove()) {} TotpDataPrivate(const char* sharedSecret, const int expirationTimeSecs, const int hashAlgorithm, const int ndigits): mSharedSecret{charPtrToStrOpt(sharedSecret)}, mExpirationTimeSecs(convertIfPositive(expirationTimeSecs)), mHashAlgorithm{convertIfPositive(hashAlgorithm)}, mNdigits(convertIfPositive(ndigits)) {} bool markedToRemove() const override { return mRemove; } const char* sharedSecret() const override { return mSharedSecret ? mSharedSecret->c_str() : nullptr; } int expirationTime() const override { return mExpirationTimeSecs ? static_cast(mExpirationTimeSecs->count()) : TOTPNULLOPT; } int hashAlgorithm() const override { return mHashAlgorithm ? static_cast(*mHashAlgorithm) : TOTPNULLOPT; } int nDigits() const override { return mNdigits ? static_cast(*mNdigits) : TOTPNULLOPT; } void setSharedSecret(const char* sharedSecret) override { mSharedSecret = charPtrToStrOpt(sharedSecret); } void setExpirationTime(const int expirationTimeSecs) override { mExpirationTimeSecs = convertIfPositive(expirationTimeSecs); } void setHashAlgorithm(const int algorithm) override { mHashAlgorithm = convertIfPositive(algorithm); } void setNdigits(const int n) override { mNdigits = convertIfPositive(n); } static TotpDataPrivate fromMap(const AttrMap& m) { const char* shse = nullptr; if (const auto itShse = m.map.find(AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_SHSE)); itShse != m.map.end()) { shse = itShse->second.c_str(); } int expt = TOTPNULLOPT; if (const auto itExpt = m.map.find(AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_EXPT)); itExpt != m.map.end()) { expt = std::stoi(itExpt->second); } int alg = TOTPNULLOPT; if (const auto itAlg = m.map.find( AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_HASH_ALG)); itAlg != m.map.end()) { alg = totp::charToPubhashAlgorithm(itAlg->second); } int nDigits = TOTPNULLOPT; if (const auto itNDigits = m.map.find( AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_NDIGITS)); itNDigits != m.map.end()) { nDigits = std::stoi(itNDigits->second); } return TotpDataPrivate(shse, expt, alg, nDigits); } TotpData* copy() const override { return new TotpDataPrivate(*this); } Validation* getValidation() const override { return new ValidationPrivate(mSharedSecret, mExpirationTimeSecs, mHashAlgorithm, mNdigits); } protected: TotpDataPrivate() = default; std::optional mSharedSecret; std::optional mExpirationTimeSecs; std::optional mHashAlgorithm; std::optional mNdigits; bool mRemove = false; }; PNDataPrivate(const char* p, const char* n, const char* url, const char* un, const TotpData* totpData): mPwd{charPtrToStrOpt(p)}, mNotes{charPtrToStrOpt(n)}, mURL{charPtrToStrOpt(url)}, mUserName{charPtrToStrOpt(un)}, mTotpData{totpData ? std::optional{*totpData} : std::nullopt} {} void setTotpData(const TotpData* totpData) override { mTotpData = totpData ? std::optional{*totpData} : std::nullopt; } const TotpData* totpData() const override { return getPtr(mTotpData); } virtual void setPassword(const char* pwd) override { mPwd = charPtrToStrOpt(pwd); } virtual void setNotes(const char* n) override { mNotes = charPtrToStrOpt(n); } virtual void setUrl(const char* u) override { mURL = charPtrToStrOpt(u); } virtual void setUserName(const char* un) override { mUserName = charPtrToStrOpt(un); } virtual const char* password() const override { return mPwd ? mPwd->c_str() : nullptr; } virtual const char* notes() const override { return mNotes ? mNotes->c_str(): nullptr; } virtual const char* url() const override { return mURL ? mURL->c_str() : nullptr; } virtual const char* userName() const override { return mUserName ? mUserName->c_str() : nullptr; } private: std::optional mPwd, mNotes, mURL, mUserName; std::optional mTotpData; }; MegaNodePrivate(const char *name, int type, int64_t size, int64_t ctime, int64_t mtime, MegaHandle nodeMegaHandle, const std::string *nodekey, const std::string *fileattrstring, const char *fingerprint, const char *originalFingerprint, MegaHandle owner, MegaHandle parentHandle = INVALID_HANDLE, const char *privateauth = NULL, const char *publicauth = NULL, bool isPublic = true, bool isForeign = false, const char *chatauth = NULL, bool isNodeDecrypted = true); MegaNodePrivate(MegaNode *node); ~MegaNodePrivate() override; int getType() const override; const char* getName() override; const char* getFingerprint() override; const char* getOriginalFingerprint() override; bool hasCustomAttrs() override; MegaStringList *getCustomAttrNames() override; const char *getCustomAttr(const char* attrName) override; int getDuration() override; int getWidth() override; bool isFavourite() override; bool isMarkedSensitive() override; int getLabel() override; int getHeight() override; int getShortformat() override; int getVideocodecid() override; double getLatitude() override; double getLongitude() override; const char* getDescription() override; MegaStringList* getTags() override; char *getBase64Handle() override; int64_t getSize() override; int64_t getCreationTime() override; int64_t getModificationTime() override; MegaHandle getHandle() const override; MegaHandle getRestoreHandle() override; MegaHandle getParentHandle() override; std::string* getNodeKey() override; bool isNodeKeyDecrypted() override; char *getBase64Key() override; char* getFileAttrString() override; int64_t getExpirationTime() override; MegaHandle getPublicHandle() override; MegaNode* getPublicNode() override; char *getPublicLink(bool includeKey = true) override; int64_t getPublicLinkCreationTime() override; const char * getWritableLinkAuthKey() override; bool isNewLinkFormat(); bool isFile() override; bool isFolder() override; bool isRemoved() override; bool hasChanged(uint64_t changeType) override; uint64_t getChanges() override; bool hasThumbnail() override; bool hasPreview() override; bool isPublic() override; bool isExported() override; bool isExpired() override; bool isTakenDown() override; bool isForeign() override; bool isCreditCardNode() const override; bool isPasswordNode() const override; bool isPasswordManagerNode() const override; CreditCardNodeData* getCreditCardData() const override; PasswordNodeData* getPasswordData() const override; std::string* getPrivateAuth(); MegaNodeList *getChildren() override; void setPrivateAuth(const char* newPrivateAuth) override; void setPublicAuth(const char* newPublicAuth); void setChatAuth(const char* newChatAuth); void setForeign(bool isForeign); void setChildren(MegaNodeList* newChildren); void setName(const char *newName); std::string* getPublicAuth(); const char* getChatAuth(); bool isShared() override; bool isOutShare() override; bool isInShare() override; std::string* getSharekey(); MegaHandle getOwner() const override; const char* getDeviceId() const override; const char* getS4() const override; static MegaNode *fromNode(Node *node); MegaNode *copy() override; char *serialize() override; bool serialize(string*) const override; // only FILENODEs static MegaNodePrivate* unserialize(string*); // only FILENODEs static string removeAppPrefixFromFingerprint(const char* appFingerprint, m_off_t* nodeSize = nullptr); static string addAppPrefixToFingerprint(const string& fingerprint, const m_off_t nodeSize); protected: MegaNodePrivate(Node *node); const char* getAttrFrom(const char *attrName, const attr_map* attrMap) const; const char *getOfficialAttr(const char* attrName) const; int type; const char *name; const char *fingerprint; const char *originalfingerprint; attr_map *customAttrs; std::unique_ptr mOfficialAttrs; int64_t size; int64_t ctime; int64_t mtime; MegaHandle nodehandle; MegaHandle parenthandle; MegaHandle restorehandle = UNDEF; std::string nodekey; std::string fileattrstring; std::string privateAuth; std::string publicAuth; std::string mDeviceId; std::string mS4; const char* chatAuth = nullptr; uint64_t changed; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { bool thumbnailAvailable : 1; bool previewAvailable : 1; bool isPublicNode : 1; bool outShares : 1; bool inShare : 1; bool foreign : 1; }; #ifdef _MSC_VER #pragma warning(pop) #endif PublicLink *plink; bool mNewLinkFormat; std::string *sharekey; // for plinks of folders int duration; int width; int height; int shortformat; int videocodecid; double latitude; double longitude; MegaNodeList *children; MegaHandle owner; bool mFavourite; bool mMarkedSensitive = false; // sensitive attribute set on this node nodelabel_t mLabel; bool mIsNodeKeyDecrypted = false; }; class MegaBackupInfoPrivate : public MegaBackupInfo { public: MegaBackupInfoPrivate(const CommandBackupSyncFetch::Data& d) : mData(d) {} MegaHandle id() const override { return mData.backupId; } int type() const override { return mData.backupType; } MegaHandle root() const override { return mData.rootNode; } const char* localFolder() const override { return mData.localFolder.c_str(); } const char* deviceId() const override { return mData.deviceId.c_str(); } const char* deviceUserAgent() const override { return mData.deviceUserAgent.c_str(); } int state() const override { return mData.syncState; } int substate() const override { return mData.syncSubstate; } const char* extra() const override { return mData.extra.c_str(); } const char* name() const override { return mData.backupName.c_str(); } uint64_t ts() const override { return mData.hbTimestamp; } int status() const override { return mData.hbStatus; } int progress() const override { return mData.hbProgress; } int uploads() const override { return mData.uploads; } int downloads() const override { return mData.downloads; } uint64_t activityTs() const override { return mData.lastActivityTs; } MegaHandle lastSync() const override { return mData.lastSyncedNodeHandle; } MegaBackupInfoPrivate* copy() const override { return new MegaBackupInfoPrivate(*this); } private: const CommandBackupSyncFetch::Data mData; }; class MegaBackupInfoListPrivate : public MegaBackupInfoList { public: MegaBackupInfoListPrivate(const std::vector& d) { mBackups.reserve(d.size()); for (const auto& bd : d) { mBackups.emplace_back(bd); } } MegaBackupInfoListPrivate* copy() const override { return new MegaBackupInfoListPrivate(*this); } const MegaBackupInfo* get(unsigned i) const override { return i < size() ? &mBackups[i] : nullptr; } unsigned size() const override { return (unsigned)mBackups.size(); } private: vector mBackups; }; class MegaSetPrivate : public MegaSet { public: MegaSetPrivate(const Set& s): mId(s.id()), mPublicId(s.publicId()), mUser(s.user()), mTs(s.ts()), mCTs(s.cts()), mName(s.name()), mCover(s.cover()), mChanges(s.changes()), mType(s.type()) { if (s.getPublicLink()) { mLinkDeletionReason = s.getPublicLink()->getLinkDeletionReason(); mIsTakenDown = s.getPublicLink()->isTakenDown(); } } MegaHandle id() const override { return mId; } MegaHandle publicId() const override { return mPublicId; } MegaHandle user() const override { return mUser; } int64_t ts() const override { return mTs; } int64_t cts() const override { return mCTs; } int type() const override { return static_cast(mType); } const char* name() const override { return mName.c_str(); } MegaHandle cover() const override { return mCover; } bool hasChanged(uint64_t changeType) const override; uint64_t getChanges() const override { return mChanges.to_ullong(); } bool isExported() const override { return mPublicId != UNDEF; } int getLinkDeletionReason() const override { return static_cast(mLinkDeletionReason); } bool isTakenDown() const override { return mIsTakenDown; } MegaSet* copy() const override { return new MegaSetPrivate(*this); } private: MegaHandle mId; MegaHandle mPublicId; MegaHandle mUser; m_time_t mTs; m_time_t mCTs; string mName; MegaHandle mCover; std::bitset mChanges; Set::SetType mType; PublicLinkSet::LinkDeletionReason mLinkDeletionReason{ PublicLinkSet::LinkDeletionReason::NO_REMOVED}; bool mIsTakenDown{false}; }; class MegaSetListPrivate : public MegaSetList { public: MegaSetListPrivate(const Set *const* sets, int count); // ptr --> const ptr --> const Set MegaSetListPrivate(const map& sets); void add(MegaSetPrivate&& s); MegaSetList* copy() const override { return new MegaSetListPrivate(*this); } const MegaSet* get(unsigned i) const override { return i < size() ? &mSets[i] : nullptr; } unsigned size() const override { return (unsigned)mSets.size(); } private: vector mSets; }; class MegaSetElementPrivate : public MegaSetElement { public: MegaSetElementPrivate(const SetElement& el) : mId(el.id()), mNode(el.node()), mSetId(el.set()), mOrder(el.order()), mTs(el.ts()), mName(el.name()), mChanges(el.changes()) {} MegaHandle id() const override { return mId; } MegaHandle node() const override { return mNode; } MegaHandle setId() const override { return mSetId; } int64_t order() const override { return mOrder; } int64_t ts() const override { return mTs; } const char* name() const override { return mName.c_str(); } bool hasChanged(uint64_t changeType) const override; uint64_t getChanges() const override { return mChanges.to_ullong(); } virtual MegaSetElement* copy() const override { return new MegaSetElementPrivate(*this); } private: MegaHandle mId; MegaHandle mNode; MegaHandle mSetId; int64_t mOrder; m_time_t mTs; string mName; std::bitset mChanges; }; class MegaSetElementListPrivate : public MegaSetElementList { public: MegaSetElementListPrivate(const SetElement *const* elements, int count); // ptr --> const ptr --> const SetElement MegaSetElementListPrivate(const elementsmap_t* elements, const std::function& filterOut = nullptr); void add(MegaSetElementPrivate&& el); MegaSetElementList* copy() const override { return new MegaSetElementListPrivate(*this); } const MegaSetElement* get(unsigned i) const override { return i < size() ? &mElements[i] : nullptr; } unsigned size() const override { return (unsigned)mElements.size(); } private: vector mElements; }; class MegaUserPrivate : public MegaUser { public: MegaUserPrivate(User *user); MegaUserPrivate(MegaUser *user); static MegaUser *fromUser(User *user); MegaUser *copy() override; ~MegaUserPrivate() override; const char* getEmail() override; MegaHandle getHandle() override; int getVisibility() override; int64_t getTimestamp() override; bool hasChanged(uint64_t changeType) override; uint64_t getChanges() override; int isOwnChange() override; protected: const char *email; MegaHandle handle; int visibility; int64_t ctime; uint64_t changed; int tag; }; class MegaUserAlertPrivate : public MegaUserAlert { public: MegaUserAlertPrivate(UserAlert::Base* user, MegaClient* mc); //MegaUserAlertPrivate(const MegaUserAlertPrivate&); // default copy works for this type MegaUserAlert* copy() const override; unsigned getId() const override; bool getSeen() const override; bool getRelevant() const override; int getType() const override; const char *getTypeString() const override; MegaHandle getUserHandle() const override; MegaHandle getNodeHandle() const override; const char* getEmail() const override; const char* getPath() const override; const char* getName() const override; const char* getHeading() const override; const char* getTitle() const override; int64_t getNumber(unsigned index) const override; int64_t getTimestamp(unsigned index) const override; const char* getString(unsigned index) const override; MegaHandle getHandle(unsigned index) const override; #ifdef ENABLE_CHAT MegaHandle getSchedId() const override; bool hasSchedMeetingChanged(uint64_t changeType) const override; MegaStringList* getUpdatedTitle() const override; MegaStringList* getUpdatedTimeZone() const override; MegaIntegerList* getUpdatedStartDate() const override; MegaIntegerList* getUpdatedEndDate() const override; #endif bool isOwnChange() const override; bool isRemoved() const override; MegaHandle getPcrHandle() const override; protected: unsigned id; bool seen; bool relevant; int type; int tag; string heading; string title; handle userHandle; string email; handle nodeHandle; handle mPcrHandle = UNDEF; string nodePath; string nodeName; vector numbers; vector timestamps; vector extraStrings; vector handles; bool removed = false; handle schedMeetingId = UNDEF; #ifdef ENABLE_CHAT UserAlert::UpdatedScheduledMeeting::Changeset schedMeetingChangeset; #endif }; class MegaHandleListPrivate : public MegaHandleList { public: MegaHandleListPrivate(); MegaHandleListPrivate(const MegaHandleListPrivate *hList); ~MegaHandleListPrivate() override; MegaHandleListPrivate(const vector &handles); MegaHandleList *copy() const override; MegaHandle get(unsigned int i) const override; unsigned int size() const override; void addMegaHandle(MegaHandle megaHandle) override; private: std::vector mList; }; class MegaIntegerListPrivate : public MegaIntegerList { public: MegaIntegerListPrivate(); MegaIntegerListPrivate(const vector& bytesList); MegaIntegerListPrivate(const vector& integerList); MegaIntegerListPrivate(const vector& integerList); ~MegaIntegerListPrivate() override; MegaSmallIntVector* toByteList() const; MegaIntegerList *copy() const override; void add(long long i) override; int64_t get(int i) const override; int size() const override; const vector* getList() const; private: vector mIntegers; }; class MegaSharePrivate : public MegaShare { public: static MegaShare* fromShare(const impl::ShareData& data); MegaShare *copy() override; ~MegaSharePrivate() override; const char *getUser() override; MegaHandle getNodeHandle() override; int getAccess() override; int64_t getTimestamp() override; bool isPending() override; bool isVerified() override; protected: MegaSharePrivate(const impl::ShareData& data); MegaSharePrivate(MegaShare* share); MegaHandle nodehandle; const char* user; int access; int64_t ts; bool pending; bool mVerified; }; class MegaCancelTokenPrivate : public MegaCancelToken { public: // The default constructor leaves the token empty, so we don't waste space when it may not be needed (eg. a request object not related to transfers) MegaCancelTokenPrivate(); // Use this one to actually embed a token MegaCancelTokenPrivate(CancelToken); void cancel() override; bool isCancelled() const override; MegaCancelTokenPrivate* existencePtr() { return cancelFlag.exists() ? this : nullptr; } CancelToken cancelFlag; }; inline CancelToken convertToCancelToken(MegaCancelToken* mct) { if (!mct) return CancelToken(); return static_cast(mct)->cancelFlag; } class MegaVpnClusterPrivate: public MegaVpnCluster { public: MegaVpnClusterPrivate(const VpnCluster& cluster): mCluster{cluster} {} MegaVpnClusterPrivate* copy() const override { return new MegaVpnClusterPrivate(*this); } const char* getHost() const override { return mCluster.getHost().c_str(); } MegaStringList* getDns() const override; MegaStringList* getAdBlockingDns() const override; private: VpnCluster mCluster; }; class MegaVpnClusterMapPrivate: public MegaVpnClusterMap { public: MegaVpnClusterMapPrivate(const std::map& clusters): mClusters{clusters} {} MegaVpnClusterMapPrivate* copy() const override { return new MegaVpnClusterMapPrivate(*this); } MegaIntegerListPrivate* getKeys() const override; MegaVpnClusterPrivate* get(int64_t key) const override; int64_t size() const override { return static_cast(mClusters.size()); } private: std::map mClusters; }; class MegaVpnRegionPrivate: public MegaVpnRegion { public: MegaVpnRegionPrivate(const VpnRegion& region): mRegion{region} {} MegaVpnRegionPrivate* copy() const override { return new MegaVpnRegionPrivate(*this); } const char* getName() const override { return mRegion.getName().c_str(); } const char* getCountryCode() const override { return mRegion.getCountryCode().c_str(); } const char* getCountryName() const override { return mRegion.getCountryName().c_str(); } const char* getRegionName() const override { return mRegion.getRegionName().c_str(); } const char* getTownName() const override { return mRegion.getTownName().c_str(); } MegaVpnClusterMapPrivate* getClusters() const override; private: VpnRegion mRegion; }; class MegaVpnRegionListPrivate: public MegaVpnRegionList { public: MegaVpnRegionListPrivate(const std::vector& regions); MegaVpnRegionListPrivate* copy() const override { return new MegaVpnRegionListPrivate(*this); } const MegaVpnRegionPrivate* get(unsigned i) const override { return (static_cast(i) < mRegions.size()) ? &(mRegions[i]) : nullptr; } unsigned size() const override { return static_cast(mRegions.size()); } private: std::vector mRegions; }; class CollisionChecker { public: enum class Option { Begin = 1, AssumeSame = 1, AlwaysError = 2, Fingerprint = 3, Metamac = 4, AssumeDifferent = 5, End = 6, }; enum class Result { NotYet = 1, // Not checked yet Skip = 2, // Skip it ReportError = 3, // Report Error Download = 4, // Download it }; // Use faGetter instead of a FileAcccess instance which delays the access to the file system and only does it based // on demand by check. This helps in a network folder. static Result check(FileSystemAccess* fsaccess, const LocalPath &fileLocalPath, MegaNode* fileNode, Option option); static Result check(std::function faGetter, MegaNode* fileNode, Option option); static Result check(std::function faGetter, Node* node, Option option); private: static Result check(std::function fingerprintEqualF, std::function metamacEqualF, Option option); static bool CompareLocalFileMetaMac(FileAccess* fa, MegaNode* fileNode); static bool fingerprintEqualRelaxed(const FileFingerprint& lhs, const FileFingerprint& rhs); }; class MegaTransferPrivate : public MegaTransfer, public Cacheable { public: MegaTransferPrivate(int type, MegaTransferListener *listener = NULL); MegaTransferPrivate(const MegaTransferPrivate *transfer); ~MegaTransferPrivate() override; MegaTransfer *copy() override; Transfer *getTransfer() const; void setTransfer(Transfer* newTransfer); void setStartTime(int64_t newStartTime); void setTransferredBytes(long long newByteCount); void setTotalBytes(long long newByteCount); void setPath(const char* newPath); void setLocalPath(const LocalPath& newPath); void setParentPath(const char* newParentPath); void setNodeHandle(MegaHandle newNodeHandle); void setParentHandle(MegaHandle newParentHandle); void setStartPos(long long newStartPos); void setEndPos(long long newEndPos); void setNumRetry(int newRetryCount); void setStage(unsigned mStage); void setMaxRetries(int newMaxRetryCount); void setTime(int64_t newTime); void setFileName(const char* newFileName); void setTag(int newTag); void setSpeed(long long newSpeed); void setMeanSpeed(long long newMeanSpeed); void setDeltaSize(long long newDeltaSize); void setUpdateTime(int64_t newUpdateTime); void setPublicNode(MegaNode* newPublicNode, bool copyChildren = false); void setNodeToUndelete(MegaNode* toUndelete); void setSyncTransfer(bool isSyncTransfer); void setSourceFileTemporary(bool temporary); void setStartFirst(bool beFirst); void setBackupTransfer(bool isBackupTransfer); void setForeignOverquota(bool isForeignOverquota); void setForceNewUpload(bool isForceNewUpload); void setStreamingTransfer(bool isStreamingTransfer); void setLastBytes(char* newLastBytes); void setLastError(const MegaError *e); void setFolderTransferTag(int newFolderTag); void setNotificationNumber(long long notificationNumber); void setListener(MegaTransferListener* newTransferListener); void setTargetOverride(bool targetOverride); void setCancelToken(CancelToken); void setCollisionCheck(CollisionChecker::Option); void setCollisionCheck(int); void setCollisionCheckResult(CollisionChecker::Result); void setCollisionResolution(CollisionResolution); void setCollisionResolution(int); void setFileSystemType(FileSystemType fsType) { mFsType = fsType; } void setPitag(Pitag pitag) { mPitag = pitag; } int getType() const override; const char * getTransferString() const override; const char* toString() const override; virtual int64_t getStartTime() const override; long long getTransferredBytes() const override; long long getTotalBytes() const override; const char* getPath() const override; const char* getParentPath() const override; MegaHandle getNodeHandle() const override; MegaHandle getParentHandle() const override; long long getStartPos() const override; long long getEndPos() const override; const char* getFileName() const override; MegaTransferListener* getListener() const override; int getNumRetry() const override; int getMaxRetries() const override; unsigned getStage() const override; virtual int64_t getTime() const; uint32_t getUniqueId() const override; int getTag() const override; long long getSpeed() const override; long long getMeanSpeed() const override; long long getDeltaSize() const override; int64_t getUpdateTime() const override; virtual MegaNode *getPublicNode() const; MegaNode *getPublicMegaNode() const override; bool isSyncTransfer() const override; bool isStreamingTransfer() const override; bool isFinished() const override; virtual bool isSourceFileTemporary() const; virtual bool shouldStartFirst() const; bool isBackupTransfer() const override; bool isForeignOverquota() const override; bool isForceNewUpload() const override; char* getLastBytes() const override; const MegaError *getLastErrorExtended() const override; bool isFolderTransfer() const override; int getFolderTransferTag() const override; virtual void setAppData(const char *data); const char* getAppData() const override; virtual void setState(int newState); int getState() const override; virtual void setPriority(unsigned long long p); unsigned long long getPriority() const override; long long getNotificationNumber() const override; bool getTargetOverride() const override; bool serialize(string*) const override; static MegaTransferPrivate* unserialize(string*); void startRecursiveOperation(shared_ptr, MegaNode* node); // takes ownership of both void stopRecursiveOperationThread(); long long getPlaceInQueue() const; void setPlaceInQueue(long long value); MegaCancelToken* getCancelToken() override; bool isRecursive() const { return recursiveOperation.get() != nullptr; } size_t getTotalRecursiveOperation() const; CancelToken& accessCancelToken() { return mCancelToken.cancelFlag; } CollisionChecker::Option getCollisionCheck() const; CollisionChecker::Result getCollisionCheckResult() const; CollisionResolution getCollisionResolution() const; FileSystemType getFileSystemType() const { return mFsType; }; MegaNode* getNodeToUndelete() const; LocalPath getLocalPath() const; /** * @brief This method checks if the transfer destination corresponds to an Inbox upload * * @return std::optional The Inbox target path if the transfer * corresponds to an Inbox; otherwise, std::nullopt. */ std::optional getInboxTarget(); Pitag getPitag() const; // for uploads, we fingerprint the file before queueing // as that way, it can be done without the main mutex locked error fingerprint_error = API_OK; nodetype_t fingerprint_filetype = TYPE_UNKNOWN; FileFingerprint fingerprint_onDisk; protected: int type; int tag; int state; uint64_t priority; CollisionChecker::Option mCollisionCheck; CollisionResolution mCollisionResolution; CollisionChecker::Result mCollisionCheckResult; FileSystemType mFsType; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif struct { bool syncTransfer : 1; bool streamingTransfer : 1; bool temporarySourceFile : 1; bool startFirst : 1; bool backupTransfer : 1; bool foreignOverquota : 1; bool forceNewUpload : 1; }; #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4201) // nameless struct #endif int64_t startTime; int64_t updateTime; int64_t time; long long transferredBytes; long long totalBytes; long long speed; long long meanSpeed; long long deltaSize; long long notificationNumber; MegaHandle nodeHandle; MegaHandle parentHandle; const char* path; const char* parentPath; //used as targetUser for uploads const char* fileName; LocalPath mLocalPath; char *lastBytes; MegaNode *publicNode; std::unique_ptr nodeToUndelete; long long startPos; long long endPos; int retry; int maxRetries; long long placeInQueue = 0; MegaTransferListener *listener; Transfer *transfer = nullptr; std::unique_ptr lastError; MegaCancelTokenPrivate mCancelToken; // default-constructed with no actual token inside int folderTransferTag; const char* appData; uint8_t mStage; bool mTargetOverride; Pitag mPitag; void updateLocalPathInternal(const LocalPath& newPath); public: // use shared_ptr here so callbacks can use a weak_ptr // to protect against the operation being cancelled in the meantime shared_ptr recursiveOperation; }; class MegaTransferDataPrivate : public MegaTransferData { public: MegaTransferDataPrivate(TransferList *transferList, long long notificationNumber); MegaTransferDataPrivate(const MegaTransferDataPrivate *transferData); ~MegaTransferDataPrivate() override; MegaTransferData *copy() const override; int getNumDownloads() const override; int getNumUploads() const override; int getDownloadTag(int i) const override; int getUploadTag(int i) const override; unsigned long long getDownloadPriority(int i) const override; unsigned long long getUploadPriority(int i) const override; long long getNotificationNumber() const override; protected: int numDownloads; int numUploads; long long notificationNumber; vector downloadTags; vector uploadTags; vector downloadPriorities; vector uploadPriorities; }; class MegaFolderInfoPrivate : public MegaFolderInfo { public: MegaFolderInfoPrivate(int numFiles, int numFolders, int numVersions, long long currentSize, long long versionsSize); MegaFolderInfoPrivate(const MegaFolderInfoPrivate *folderData); ~MegaFolderInfoPrivate() override; MegaFolderInfo *copy() const override; int getNumVersions() const override; int getNumFiles() const override; int getNumFolders() const override; long long getCurrentSize() const override; long long getVersionsSize() const override; protected: int numFiles; int numFolders; int numVersions; long long currentSize; long long versionsSize; }; class MegaTimeZoneDetailsPrivate : public MegaTimeZoneDetails { public: MegaTimeZoneDetailsPrivate(vector* timeZones, vector *timeZoneOffsets, int defaultTimeZone); MegaTimeZoneDetailsPrivate(const MegaTimeZoneDetailsPrivate *timeZoneDetails); ~MegaTimeZoneDetailsPrivate() override; MegaTimeZoneDetails *copy() const override; int getNumTimeZones() const override; const char *getTimeZone(int index) const override; int getTimeOffset(int index) const override; int getDefault() const override; protected: int defaultTimeZone; vector timeZones; vector timeZoneOffsets; }; class MegaPushNotificationSettingsPrivate : public MegaPushNotificationSettings { public: MegaPushNotificationSettingsPrivate(const std::string &settingsJSON); MegaPushNotificationSettingsPrivate(); MegaPushNotificationSettingsPrivate(const MegaPushNotificationSettingsPrivate *settings); bool operator==(const MegaPushNotificationSettingsPrivate& other) const; std::string generateJson() const; bool isValid() const; ~MegaPushNotificationSettingsPrivate() override; MegaPushNotificationSettings *copy() const override; private: m_time_t mGlobalDND = -1; // defaults to -1 if not defined int mGlobalScheduleStart = -1; // defaults to -1 if not defined int mGlobalScheduleEnd = -1; // defaults to -1 if not defined std::string mGlobalScheduleTimezone; std::map mChatDND; std::map mChatAlwaysNotify; m_time_t mContactsDND = -1; // defaults to -1 if not defined m_time_t mSharesDND = -1; // defaults to -1 if not defined m_time_t mGlobalChatsDND = -1; // defaults to -1 if not defined bool mJsonInvalid = false; // true if ctor from JSON find issues public: // getters bool isGlobalDndEnabled() const override; bool isGlobalChatsDndEnabled() const override; int64_t getGlobalDnd() const override; int64_t getGlobalChatsDnd() const override; bool isGlobalScheduleEnabled() const override; int getGlobalScheduleStart() const override; int getGlobalScheduleEnd() const override; const char *getGlobalScheduleTimezone() const override; bool isChatDndEnabled(MegaHandle chatid) const override; int64_t getChatDnd(MegaHandle chatid) const override; bool isChatAlwaysNotifyEnabled(MegaHandle chatid) const override; bool isContactsEnabled() const override; bool isSharesEnabled() const override; // setters void enableGlobal(bool enable) override; void setGlobalDnd(int64_t timestamp) override; void disableGlobalDnd() override; void setGlobalSchedule(int start, int end, const char *timezone) override; void disableGlobalSchedule() override; void enableChat(MegaHandle chatid, bool enable) override; void setChatDnd(MegaHandle chatid, int64_t timestamp) override; void setGlobalChatsDnd(int64_t timestamp) override; void enableChatAlwaysNotify(MegaHandle chatid, bool enable) override; void enableContacts(bool enable) override; void enableShares(bool enable) override; void enableChats(bool enable) override; }; class MegaContactRequestPrivate : public MegaContactRequest { public: MegaContactRequestPrivate(PendingContactRequest *request); MegaContactRequestPrivate(const MegaContactRequest *request); ~MegaContactRequestPrivate() override; static MegaContactRequest *fromContactRequest(PendingContactRequest *request); MegaContactRequest *copy() const override; MegaHandle getHandle() const override; char* getSourceEmail() const override; char* getSourceMessage() const override; char* getTargetEmail() const override; int64_t getCreationTime() const override; int64_t getModificationTime() const override; int getStatus() const override; bool isOutgoing() const override; bool isAutoAccepted() const override; protected: MegaHandle handle; char* sourceEmail; char* sourceMessage; char* targetEmail; int64_t creationTime; int64_t modificationTime; int status; bool outgoing; bool autoaccepted; }; #ifdef ENABLE_SYNC class MegaSyncPrivate : public MegaSync { public: MegaSyncPrivate(const SyncConfig& config, MegaClient* client); MegaSyncPrivate(MegaSyncPrivate *sync); ~MegaSyncPrivate() override; MegaSync *copy() override; MegaHandle getMegaHandle() const override; void setMegaHandle(MegaHandle handle); const char* getLocalFolder() const override; void setLocalFolder(const char*path); const char* getName() const override; void setName(const char*name); const char* getLastKnownMegaFolder() const override; void setLastKnownMegaFolder(const char *path); MegaHandle getBackupId() const override; void setBackupId(MegaHandle backupId); int getError() const override; void setError(int error); int getWarning() const override; void setWarning(int warning); int getType() const override; void setType(SyncType type); int getRunState() const override; MegaSync::SyncRunningState mRunState = SyncRunningState::RUNSTATE_DISABLED; protected: MegaHandle megaHandle; char *localFolder; char *mName; char *lastKnownMegaFolder; SyncType mType = TYPE_UNKNOWN; //holds error cause int mError = NO_SYNC_ERROR; int mWarning = NO_SYNC_WARNING; handle mBackupId = UNDEF; }; class MegaSyncStatsPrivate : public MegaSyncStats { handle backupId; PerSyncStats stats; public: MegaSyncStatsPrivate(handle bid, const PerSyncStats& s) : backupId(bid), stats(s) {} MegaHandle getBackupId() const override { return backupId; } bool isScanning() const override { return stats.scanning; } bool isSyncing() const override { return stats.syncing; } int getFolderCount() const override { return stats.numFolders; } int getFileCount() const override { return stats.numFiles; } int getUploadCount() const override { return stats.numUploads; } int getDownloadCount() const override { return stats.numDownloads; } MegaSyncStatsPrivate *copy() const override { return new MegaSyncStatsPrivate(*this); } }; class MegaSyncListPrivate : public MegaSyncList { public: MegaSyncListPrivate(); MegaSyncListPrivate(MegaSyncPrivate **newlist, int size); MegaSyncListPrivate(const MegaSyncListPrivate *syncList); ~MegaSyncListPrivate() override; MegaSyncList *copy() const override; MegaSync* get(int i) const override; int size() const override; void addSync(MegaSync* sync) override; protected: MegaSync** list; int s; }; #endif // ENABLE_SYNC class MegaPricingPrivate; class MegaCurrencyPrivate; class MegaBannerListPrivate; class MegaRequestPrivate : public MegaRequest { public: MegaRequestPrivate(int type, MegaRequestListener *listener = NULL); MegaRequestPrivate(MegaRequestPrivate *request); // Set the function to be executed in sendPendingRequests() // instead of adding more code to the huge switch there std::function performRequest; std::function performTransferRequest; // perform fireOnRequestFinish in sendPendingReqeusts() // See fireOnRequestFinish std::function performFireOnRequestFinish; ~MegaRequestPrivate() override; MegaRequest *copy() override; void setNodeHandle(MegaHandle newNodeHandle); void setLink(const char* newLink); void setParentHandle(MegaHandle newParentHandle); void setSessionKey(const char* newSessionKey); void setName(const char* newName); void setEmail(const char* newEmail); void setPassword(const char* pass); void setNewPassword(const char* pass); void setPrivateKey(const char* newPrivateKey); void setAccess(int newAccess); void setNumRetry(int count); void setPublicNode(MegaNode* newPublicNode, bool copyChildren = false); void setNumDetails(int count); void setFile(const char* newFile); void setParamType(int newType); void setText(const char* newText); void setNumber(long long newNumber); void setFlag(bool newFlag); void setTransferTag(int newTag); void setListener(MegaRequestListener* newListener); void setTotalBytes(long long byteCount); void setTransferredBytes(long long byteCount); void setTag(int newTag); void addProduct(const Product& product); void setCurrency(std::unique_ptr currencyData); void setProxy(Proxy* newProxy); Proxy *getProxy(); void setTimeZoneDetails(MegaTimeZoneDetails* newDetails); int getType() const override; const char *getRequestString() const override; const char* toString() const override; MegaHandle getNodeHandle() const override; const char* getLink() const override; MegaHandle getParentHandle() const override; const char* getSessionKey() const override; const char* getName() const override; const char* getEmail() const override; const char* getPassword() const override; const char* getNewPassword() const override; const char* getPrivateKey() const override; int getAccess() const override; const char* getFile() const override; int getNumRetry() const override; MegaNode* getPublicNode() const; MegaNode *getPublicMegaNode() const override; int getParamType() const override; const char *getText() const override; long long getNumber() const override; bool getFlag() const override; long long getTransferredBytes() const override; long long getTotalBytes() const override; MegaRequestListener *getListener() const override; MegaAccountDetails *getMegaAccountDetails() const override; int getTransferTag() const override; int getNumDetails() const override; int getTag() const override; MegaPricing *getPricing() const override; MegaCurrency *getCurrency() const override; std::shared_ptr getAccountDetails() const; MegaAchievementsDetails *getMegaAchievementsDetails() const override; AchievementsDetails *getAchievementsDetails() const; MegaTimeZoneDetails *getMegaTimeZoneDetails () const override; MegaStringList *getMegaStringList() const override; MegaStringIntegerMap* getMegaStringIntegerMap() const override; MegaHandleList* getMegaHandleList() const override; #ifdef ENABLE_SYNC MegaSyncStallList* getMegaSyncStallList() const override; MegaSyncStallMap* getMegaSyncStallMap() const override; void setMegaSyncStallList(unique_ptr&& stalls); void setMegaSyncStallMap(std::unique_ptr&& sm); #endif // ENABLE_SYNC #ifdef ENABLE_CHAT MegaTextChatPeerList *getMegaTextChatPeerList() const override; void setMegaTextChatPeerList(MegaTextChatPeerList *chatPeers); MegaTextChatList *getMegaTextChatList() const override; void setMegaTextChatList(MegaTextChatList* newChatList); MegaScheduledMeetingList* getMegaScheduledMeetingList() const override; #endif MegaStringMap *getMegaStringMap() const override; void setMegaStringMap(const MegaStringMap *); void setMegaStringMap(const std::map&); MegaStringListMap *getMegaStringListMap() const override; void setMegaStringListMap(const MegaStringListMap *stringListMap); MegaStringTable *getMegaStringTable() const override; void setMegaStringTable(const MegaStringTable *stringTable); MegaFolderInfo *getMegaFolderInfo() const override; void setMegaFolderInfo(const MegaFolderInfo *); const MegaPushNotificationSettings *getMegaPushNotificationSettings() const override; void setMegaPushNotificationSettings(const MegaPushNotificationSettings* newSettings); MegaBackgroundMediaUpload *getMegaBackgroundMediaUploadPtr() const override; void setMegaBackgroundMediaUploadPtr(MegaBackgroundMediaUpload *); // non-owned pointer void setMegaStringList(const MegaStringList* stringList); void setMegaStringIntegerMap(const MegaStringIntegerMap* stringIntegerMap); void setMegaHandleList(const MegaHandleList* handles); void setMegaHandleList(const vector &handles); void setMegaScheduledMeetingList(const MegaScheduledMeetingList *schedMeetingList); MegaScheduledCopyListener *getBackupListener() const; void setBackupListener(MegaScheduledCopyListener *value); MegaBannerList* getMegaBannerList() const override; void setBanners(std::vector&& banners); MegaRecentActionBucketList *getRecentActions() const override; void setRecentActions(std::unique_ptr recentActionBucketList); MegaSet* getMegaSet() const override; void setMegaSet(std::unique_ptr s); MegaSetElementList* getMegaSetElementList() const override; void setMegaSetElementList(std::unique_ptr els); const MegaIntegerList* getMegaIntegerList() const override; void setMegaIntegerList(std::unique_ptr ints); MegaBackupInfoList* getMegaBackupInfoList() const override; void setMegaBackupInfoList(std::unique_ptr bkps); MegaVpnRegionList* getMegaVpnRegionsDetailed() const override; void setMegaVpnRegionsDetailed(MegaVpnRegionList* vpnRegions); MegaVpnCredentials* getMegaVpnCredentials() const override; void setMegaVpnCredentials(MegaVpnCredentials* megaVpnCredentials); MegaNetworkConnectivityTestResults* getMegaNetworkConnectivityTestResults() const override; void setMegaNetworkConnectivityTestResults( MegaNetworkConnectivityTestResults* networkConnectivityTestResults); const MegaNotificationList* getMegaNotifications() const override; void setMegaNotifications(MegaNotificationList* megaNotifications); const MegaNodeTree* getMegaNodeTree() const override; void setMegaNodeTree(MegaNodeTree* megaNodeTree); const MegaCancelSubscriptionReasonList* getMegaCancelSubscriptionReasons() const override; void setMegaCancelSubscriptionReasons(MegaCancelSubscriptionReasonList* cancelReasons); MegaDiscountCodeList* getMegaDiscountCodeList() const override; void setMegaDiscountCodes(std::vector&& discountCodes); const MegaDiscountCodeInfo* getMegaDiscountCodeInfo() const override; void setMegaDiscountCodeInfo(std::unique_ptr discountCodeInfo); static bool causesLocklessRequest(const int type); protected: std::shared_ptr accountDetails; MegaPricingPrivate *megaPricing; MegaCurrencyPrivate *megaCurrency; AchievementsDetails *achievementsDetails; MegaTimeZoneDetails *timeZoneDetails; int type; MegaHandle nodeHandle; const char* link; const char* name; MegaHandle parentHandle; const char* sessionKey; const char* email; const char* password; const char* newPassword; const char* privateKey; const char* text; long long number; int access; const char* file; int attrType; bool flag; long long totalBytes; long long transferredBytes; MegaRequestListener *listener; MegaScheduledCopyListener *backupListener; int transfer; int numDetails; MegaNode* publicNode; int numRetry; int tag; Proxy *proxy; #ifdef ENABLE_CHAT MegaTextChatPeerList *chatPeerList; MegaTextChatList *chatList; unique_ptr mScheduledMeetingList; #endif MegaStringMap *stringMap; MegaStringListMap *mStringListMap; MegaStringTable *mStringTable; MegaFolderInfo *folderInfo; MegaPushNotificationSettings *settings; MegaBackgroundMediaUpload* backgroundMediaUpload; // non-owned pointer unique_ptr mStringList; std::unique_ptr mStringIntegerMap; unique_ptr mHandleList; unique_ptr mRecentActions; private: unique_ptr mBannerList; unique_ptr mMegaSet; unique_ptr mMegaSetElementList; unique_ptr mMegaIntegerList; unique_ptr mMegaBackupInfoList; unique_ptr mMegaVpnRegions; unique_ptr mMegaVpnCredentials; unique_ptr mNetworkConnectivityTestResults; #ifdef ENABLE_SYNC unique_ptr mSyncStallList; unique_ptr mSyncStallMap; #endif // ENABLE_SYNC unique_ptr mMegaNotifications; unique_ptr mMegaNodeTree; unique_ptr mMegaCancelSubscriptionReasons; unique_ptr mMegaDiscountCodeList; unique_ptr mMegaDiscountCodeInfo; public: shared_ptr functionToExecute; }; class MegaEventPrivate : public MegaEvent { public: MegaEventPrivate(int atype); MegaEventPrivate(MegaEventPrivate *event); virtual ~MegaEventPrivate(); MegaEvent *copy() override; int getType() const override; const char *getText() const override; int64_t getNumber() const override; MegaHandle getHandle() const override; const char* getEventString() const override; std::optional getNumber(const std::string& key) const override; std::string getValidDataToString() const; static const char* getEventString(int type); void setText(const char* newText); void setNumber(int64_t newNumber); void setHandle(const MegaHandle& handle); void setNumber(const std::string& key, int64_t value); protected: int type; const char* text = nullptr; int64_t number = -1; std::map numberMap; MegaHandle mHandle = INVALID_HANDLE; }; class MegaAccountBalancePrivate : public MegaAccountBalance { public: static MegaAccountBalance *fromAccountBalance(const AccountBalance *balance); virtual ~MegaAccountBalancePrivate() ; virtual MegaAccountBalance* copy(); virtual double getAmount() const; virtual char* getCurrency() const; protected: MegaAccountBalancePrivate(const AccountBalance *balance); AccountBalance balance; }; class MegaAccountSessionPrivate : public MegaAccountSession { public: static MegaAccountSession *fromAccountSession(const AccountSession *session); ~MegaAccountSessionPrivate() override; MegaAccountSession* copy(); int64_t getCreationTimestamp() const override; int64_t getMostRecentUsage() const override; char *getUserAgent() const override; char *getIP() const override; char *getCountry() const override; bool isCurrent() const override; bool isAlive() const override; MegaHandle getHandle() const override; char *getDeviceId() const override; private: MegaAccountSessionPrivate(const AccountSession *session); AccountSession session; }; class MegaAccountPurchasePrivate : public MegaAccountPurchase { public: static MegaAccountPurchase *fromAccountPurchase(const AccountPurchase *purchase); ~MegaAccountPurchasePrivate() override; MegaAccountPurchase* copy(); int64_t getTimestamp() const override; char *getHandle() const override; char *getCurrency() const override; double getAmount() const override; int getMethod() const override; private: MegaAccountPurchasePrivate(const AccountPurchase *purchase); AccountPurchase purchase; }; class MegaAccountTransactionPrivate : public MegaAccountTransaction { public: static MegaAccountTransaction *fromAccountTransaction(const AccountTransaction *transaction); ~MegaAccountTransactionPrivate() override; MegaAccountTransaction* copy(); int64_t getTimestamp() const override; char *getHandle() const override; char *getCurrency() const override; double getAmount() const override; private: MegaAccountTransactionPrivate(const AccountTransaction *transaction); AccountTransaction transaction; }; class MegaAccountFeaturePrivate : public MegaAccountFeature { public: static MegaAccountFeaturePrivate* fromAccountFeature(const AccountFeature* feature); int64_t getExpiry() const override; char* getId() const override; private: MegaAccountFeaturePrivate(const AccountFeature* feature); AccountFeature mFeature; }; class MegaAccountSubscriptionPrivate: public MegaAccountSubscription { public: static MegaAccountSubscriptionPrivate* fromAccountSubscription(const AccountSubscription& subscription); char* getId() const override; int getStatus() const override; char* getCycle() const override; char* getPaymentMethod() const override; int32_t getPaymentMethodId() const override; int64_t getRenewTime() const override; int32_t getAccountLevel() const override; MegaStringList* getFeatures() const override; bool isTrial() const override; private: MegaAccountSubscriptionPrivate(const AccountSubscription& subscription); AccountSubscription mSubscription; }; class MegaAccountPlanPrivate: public MegaAccountPlan { public: static MegaAccountPlanPrivate* fromAccountPlan(const AccountPlan& plan); bool isProPlan() const override; int32_t getAccountLevel() const override; MegaStringList* getFeatures() const override; int64_t getExpirationTime() const override; int32_t getType() const override; char* getId() const override; bool isTrial() const override; private: MegaAccountPlanPrivate(const AccountPlan& plan); AccountPlan mPlan; }; class MegaAccountDetailsPrivate : public MegaAccountDetails { public: static MegaAccountDetails *fromAccountDetails(AccountDetails *details); ~MegaAccountDetailsPrivate() override; int getProLevel() override; int64_t getProExpiration() override; int getSubscriptionStatus() override; int64_t getSubscriptionRenewTime() override; char* getSubscriptionMethod() override; int getSubscriptionMethodId() override; char* getSubscriptionCycle() override; long long getStorageMax() override; long long getStorageUsed() override; long long getVersionStorageUsed() override; long long getTransferMax() override; long long getTransferOwnUsed() override; long long getTransferSrvUsed() override; long long getTransferUsed() override; int getNumUsageItems() override; long long getStorageUsed(MegaHandle handle) override; long long getNumFiles(MegaHandle handle) override; long long getNumFolders(MegaHandle handle) override; long long getVersionStorageUsed(MegaHandle handle) override; long long getNumVersionFiles(MegaHandle handle) override; MegaAccountDetails* copy() override; int getNumBalances() const override; MegaAccountBalance* getBalance(int i) const override; int getNumSessions() const override; MegaAccountSession* getSession(int i) const override; int getNumPurchases() const override; MegaAccountPurchase* getPurchase(int i) const override; int getNumTransactions() const override; MegaAccountTransaction* getTransaction(int i) const override; int getTemporalBandwidthInterval() override; long long getTemporalBandwidth() override; bool isTemporalBandwidthValid() override; int getNumActiveFeatures() const override; MegaAccountFeature* getActiveFeature(int featureIndex) const override; int64_t getSubscriptionLevel() const override; MegaStringIntegerMap* getSubscriptionFeatures() const override; int getNumSubscriptions() const override; MegaAccountSubscription* getSubscription(int subscriptionsIndex) const override; int getNumPlans() const override; MegaAccountPlan* getPlan(int plansIndex) const override; private: MegaAccountDetailsPrivate(AccountDetails *details); AccountDetails details; }; class MegaCurrencyPrivate : public MegaCurrency { public: ~MegaCurrencyPrivate() override; MegaCurrency *copy() override; const char *getCurrencySymbol() override; const char *getCurrencyName() override; const char *getLocalCurrencySymbol() override; const char *getLocalCurrencyName() override; void setCurrency(std::unique_ptr); // common for all products private: CurrencyData mCurrencyData; // reused for all plans }; class MegaPricingPrivate : public MegaPricing { public: ~MegaPricingPrivate() override; int getNumProducts() override; MegaHandle getHandle(int productIndex) override; int getProLevel(int productIndex) override; int getGBStorage(int productIndex) override; int getGBTransfer(int productIndex) override; int getMonths(int productIndex) override; int getAmount(int productIndex) override; int getLocalPrice(int productIndex) override; double getAmountWithDecimals(const int productIndex) const override; double getLocalPriceWithDecimals(const int productIndex) const override; double getAmountMonthWithDecimals(const int productIndex) const override; double getPriceNetWithDecimals(const int productIndex) const override; double getLocalPriceNetWithDecimals(const int productIndex) const override; double getMonthlyBasePriceNetWithDecimals(const int productIndex) const override; const char* getDescription(int productIndex) override; const char* getIosID(int productIndex) override; const char* getAndroidID(int productIndex) override; bool isBusinessType(int productIndex) override; bool isFeaturePlan(int productIndex) const override; int getAmountMonth(int productIndex) override; MegaPricing *copy() override; int getGBStoragePerUser(int productIndex) override; int getGBTransferPerUser(int productIndex) override; unsigned int getMinUsers(int productIndex) override; unsigned int getPricePerUser(int productIndex) override; unsigned int getLocalPricePerUser(int productIndex) override; unsigned int getPricePerStorage(int productIndex) override; unsigned int getLocalPricePerStorage(int productIndex) override; int getGBPerStorage(int productIndex) override; unsigned int getPricePerTransfer(int productIndex) override; unsigned int getLocalPricePerTransfer(int productIndex) override; int getGBPerTransfer(int productIndex) override; MegaStringIntegerMap* getFeatures(int productIndex) const override; unsigned int getTestCategory(int productIndex) const override; unsigned int getTrialDurationInDays(int productIndex) const override; bool hasMobileOffers(int productIndex) const override; std::string getMobileOfferId(int productIndex) const override; bool hasMobileOfferUat(int productIndex) const override; bool hasDiscount(int productIndex) const override; const char* getDiscountCode(int productIndex) const override; const char* getDiscountName(int productIndex) const override; int getDiscountGroup(int productIndex) const override; int getDiscountMonths(int productIndex) const override; int getDiscountPercentage(int productIndex) const override; void addProduct(const Product& product); private: enum PlanType : unsigned { PRO_LEVEL, BUSINESS, FEATURE, }; const InstantDiscounts* getInstantDiscounts(const int productIndex) const; bool isType(int productIndex, unsigned t) const; vector products; }; class MegaAchievementsDetailsPrivate : public MegaAchievementsDetails { public: static MegaAchievementsDetails *fromAchievementsDetails(AchievementsDetails *details); ~MegaAchievementsDetailsPrivate() override; MegaAchievementsDetails* copy() override; long long getBaseStorage() override; bool isValidClass(int class_id) override; long long getClassStorage(int class_id) override; long long getClassTransfer(int class_id) override; int getClassExpire(int class_id) override; unsigned int getAwardsCount() override; int getAwardClass(unsigned int index) override; int getAwardId(unsigned int index) override; int64_t getAwardTimestamp(unsigned int index) override; int64_t getAwardExpirationTs(unsigned int index) override; MegaStringList* getAwardEmails(unsigned int index) override; int getRewardsCount() override; int getRewardAwardId(unsigned int index) override; long long getRewardStorage(unsigned int index) override; long long getRewardTransfer(unsigned int index) override; long long getRewardStorageByAwardId(int award_id) override; long long getRewardTransferByAwardId(int award_id) override; int getRewardExpire(unsigned int index) override; long long currentStorage() override; long long currentTransfer() override; long long currentStorageReferrals() override; long long currentTransferReferrals() override; private: MegaAchievementsDetailsPrivate(AchievementsDetails *details); AchievementsDetails details; }; #ifdef ENABLE_CHAT class MegaTextChatPeerListPrivate : public MegaTextChatPeerList { public: MegaTextChatPeerListPrivate(); MegaTextChatPeerListPrivate(const userpriv_vector *); ~MegaTextChatPeerListPrivate() override; MegaTextChatPeerList *copy() const override; void addPeer(MegaHandle h, int priv) override; MegaHandle getPeerHandle(int i) const override; int getPeerPrivilege(int i) const override; int size() const override; // returns the list of user-privilege (this object keeps the ownership) const userpriv_vector * getList() const; void setPeerPrivilege(handle uh, privilege_t priv); private: userpriv_vector list; }; class MegaTextChatPrivate : public MegaTextChat { public: MegaTextChatPrivate(const MegaTextChat *); MegaTextChatPrivate(const TextChat *); ~MegaTextChatPrivate() override; MegaTextChat *copy() const override; MegaHandle getHandle() const override; int getOwnPrivilege() const override; int getShard() const override; const MegaTextChatPeerList *getPeerList() const override; void setPeerList(const MegaTextChatPeerList* newPeers) override; bool isGroup() const override; MegaHandle getOriginatingUser() const override; const char *getTitle() const override; const char *getUnifiedKey() const override; unsigned char getChatOptions() const override; int64_t getCreationTime() const override; bool isArchived() const override; bool isPublicChat() const override; bool isMeeting() const override; bool hasChanged(uint64_t changeType) const override; uint64_t getChanges() const override; int isOwnChange() const override; const MegaScheduledMeetingList* getScheduledMeetingList() const override; const MegaScheduledMeetingList* getUpdatedOccurrencesList() const override; const MegaHandleList* getSchedMeetingsChanged() const override; private: handle id; int priv; string url; int shard; MegaTextChatPeerList *peers; bool group; handle ou; string title; string unifiedKey; uint64_t changed; int tag; bool archived; bool publicchat; int64_t ts; bool meeting; ChatOptions_t chatOptions; // list of scheduled meetings std::unique_ptr mScheduledMeetings; // list of scheduled meetings Id's that have changed std::unique_ptr mSchedMeetingsChanged; // list of updated scheduled meetings occurrences (just in case app requested manually for more occurrences) std::unique_ptr mUpdatedOcurrences; }; class MegaTextChatListPrivate : public MegaTextChatList { public: MegaTextChatListPrivate(); MegaTextChatListPrivate(textchat_map *list); ~MegaTextChatListPrivate() override; MegaTextChatList *copy() const override; const MegaTextChat *get(unsigned int i) const override; int size() const override; void addChat(MegaTextChatPrivate*); private: MegaTextChatListPrivate(const MegaTextChatListPrivate*); vector list; }; #endif class MegaBannerPrivate : public MegaBanner { public: MegaBannerPrivate(BannerDetails&& details); MegaBanner* copy() const override; int getId() const override; const char* getTitle() const override; const char* getDescription() const override; const char* getImage() const override; const char* getUrl() const override; const char* getBackgroundImage() const override; const char* getImageLocation() const override; int getVariant() const override; const char* getButton() const override; private: BannerDetails mDetails; }; class MegaBannerListPrivate : public MegaBannerList { public: MegaBannerListPrivate* copy() const override; // "different" return type is Covariant const MegaBanner* get(int i) const override; int size() const override; void add(MegaBannerPrivate&&); private: std::vector mVector; }; class MegaStringMapPrivate : public MegaStringMap { public: MegaStringMapPrivate(); MegaStringMapPrivate(const string_map *map, bool toBase64 = false); ~MegaStringMapPrivate() override; MegaStringMap *copy() const override; const char *get(const char* key) const override; MegaStringList *getKeys() const override; void set(const char *key, const char *value) override; int size() const override; const string_map *getMap() const; protected: MegaStringMapPrivate(const MegaStringMapPrivate *megaStringMap); string_map strMap; }; class MegaIntegerMapPrivate : public MegaIntegerMap { public: MegaIntegerMapPrivate(); MegaIntegerMapPrivate(const std::multimap& bytesMap); MegaIntegerMapPrivate(const std::multimap& integerMap); ~MegaIntegerMapPrivate() override; MegaSmallIntMap* toByteMap() const; MegaIntegerMap* copy() const override; MegaIntegerList* getKeys() const override; MegaIntegerList* get(int64_t key) const override; int64_t size() const override; void set(int64_t key, int64_t value) override; const integer_map* getMap() const; private: MegaIntegerMapPrivate(const MegaIntegerMapPrivate &megaIntegerMap); integer_map mIntegerMap; }; class MegaStringListPrivate : public MegaStringList { public: MegaStringListPrivate() = default; MegaStringListPrivate(string_vector&&); // takes ownership MegaStringListPrivate(const string_vector&); ~MegaStringListPrivate() override = default; MegaStringList *copy() const override; const char* get(int i) const override; int size() const override; void add(const char* value) override; const string_vector& getVector() const; protected: MegaStringListPrivate(const MegaStringListPrivate& stringList) = default; string_vector mList; }; bool operator==(const MegaStringList& lhs, const MegaStringList& rhs); class MegaStringIntegerMapPrivate : public MegaStringIntegerMap { public: MegaStringIntegerMapPrivate() = default; ~MegaStringIntegerMapPrivate() override = default; MegaStringIntegerMapPrivate* copy() const override { return new MegaStringIntegerMapPrivate(*this); } MegaStringListPrivate* getKeys() const override; MegaIntegerListPrivate* get(const char* key) const override; void set(const char* key, int64_t value) override; void set(const std::string& key, int64_t value); int64_t size() const override { return static_cast(mStorage.size()); } protected: MegaStringIntegerMapPrivate(const MegaStringIntegerMapPrivate& stringIntMap) = default; std::map mStorage; }; class MegaStringListMapPrivate : public MegaStringListMap { public: MegaStringListMapPrivate() = default; MEGA_DISABLE_COPY_MOVE(MegaStringListMapPrivate) MegaStringListMap* copy() const override; const MegaStringList* get(const char* key) const override; MegaStringList *getKeys() const override; void set(const char* key, const MegaStringList* value) override; // takes ownership of value int size() const override; protected: struct Compare { bool operator()(const std::unique_ptr& rhs, const std::unique_ptr& lhs) const; }; map, std::unique_ptr, Compare> mMap; }; class MegaStringTablePrivate : public MegaStringTable { public: MegaStringTablePrivate() = default; MEGA_DISABLE_COPY_MOVE(MegaStringTablePrivate) MegaStringTable* copy() const override; void append(const MegaStringList* value) override; // takes ownership of value const MegaStringList* get(int i) const override; int size() const override; protected: vector> mTable; }; class MegaNodeListPrivate : public MegaNodeList { public: MegaNodeListPrivate(); MegaNodeListPrivate(Node** newlist, int size); MegaNodeListPrivate(const MegaNodeListPrivate *nodeList, bool copyChildren = false); MegaNodeListPrivate(sharedNode_vector& v); MegaNodeListPrivate(sharedNode_list& l); ~MegaNodeListPrivate() override; MegaNodeList *copy() const override; MegaNode* get(int i) const override; int size() const override; void addNode(MegaNode* node) override; //This ones takes the ownership of the given node void addNode(std::unique_ptr node); protected: MegaNode** list; int s; }; class MegaChildrenListsPrivate : public MegaChildrenLists { public: MegaChildrenListsPrivate(); MegaChildrenListsPrivate(MegaChildrenLists*); MegaChildrenListsPrivate(unique_ptr folderList, unique_ptr fileList); MegaChildrenLists *copy() override; MegaNodeList* getFolderList() override; MegaNodeList* getFileList() override; protected: unique_ptr folders; unique_ptr files; }; class MegaUserListPrivate : public MegaUserList { public: MegaUserListPrivate(); MegaUserListPrivate(User** newlist, int size); ~MegaUserListPrivate() override; MegaUserList *copy() override; MegaUser* get(int i) override; int size() override; protected: MegaUserListPrivate(MegaUserListPrivate *userList); MegaUser** list; int s; }; class MegaShareListPrivate : public MegaShareList { public: MegaShareListPrivate(); MegaShareListPrivate(const std::vector& shares); ~MegaShareListPrivate() override; MegaShare* get(int i) override; int size() override; protected: MegaShare** list; int s; }; class MegaTransferListPrivate : public MegaTransferList { public: MegaTransferListPrivate(); MegaTransferListPrivate(MegaTransfer** newlist, int size); ~MegaTransferListPrivate() override; MegaTransfer* get(int i) override; int size() override; protected: MegaTransfer** list; int s; }; class MegaContactRequestListPrivate : public MegaContactRequestList { public: MegaContactRequestListPrivate(); MegaContactRequestListPrivate(PendingContactRequest ** newlist, int size); ~MegaContactRequestListPrivate() override; MegaContactRequestList* copy() const override; const MegaContactRequest* get(int i) const override; int size() const override; protected: MegaContactRequestListPrivate(const MegaContactRequestListPrivate* requestList); MegaContactRequest** list; int s; }; class MegaUserAlertListPrivate : public MegaUserAlertList { public: MegaUserAlertListPrivate(); MegaUserAlertListPrivate(UserAlert::Base** newlist, int size, MegaClient* mc); MegaUserAlertListPrivate(const MegaUserAlertListPrivate &userList); ~MegaUserAlertListPrivate() override; MegaUserAlertList *copy() const override; MegaUserAlert* get(int i) const override; int size() const override; void clear() override; protected: MegaUserAlertListPrivate(MegaUserAlertListPrivate *userList); MegaUserAlert** list; int s; }; class MegaRecentActionBucketPrivate : public MegaRecentActionBucket { private: struct BucketData; public: MegaRecentActionBucketPrivate(recentaction&& ra); ~MegaRecentActionBucketPrivate() override; MegaRecentActionBucket *copy() const override; int64_t getTimestamp() const override; const char* getUserEmail() const override; MegaHandle getParentHandle() const override; bool isUpdate() const override; bool isMedia() const override; const char* getId() const override; const MegaNodeList* getNodes() const override; private: MegaRecentActionBucketPrivate(const BucketData& data); struct BucketData { RecentActionBucketMeta meta; int64_t timestamp = 0; string id; MegaNodeList* nodes = nullptr; }; BucketData mData; }; class MegaRecentActionBucketListPrivate : public MegaRecentActionBucketList { public: MegaRecentActionBucketListPrivate(); MegaRecentActionBucketListPrivate(recentactions_vector& v); MegaRecentActionBucketListPrivate(const MegaRecentActionBucketListPrivate &userList); ~MegaRecentActionBucketListPrivate() override; MegaRecentActionBucketList *copy() const override; MegaRecentActionBucket* get(int i) const override; int size() const override; protected: MegaRecentActionBucketPrivate** list; int s; }; class EncryptFilePieceByChunks : public EncryptByChunks { // specialisation for encrypting a piece of a file without using too much RAM FileAccess* fain; FileAccess* faout; m_off_t inpos, outpos; string buffer; unsigned lastsize; public: EncryptFilePieceByChunks(FileAccess* cFain, m_off_t cInPos, FileAccess* cFaout, m_off_t cOutPos, SymmCipher* cipher, chunkmac_map* chunkmacs, uint64_t ctriv); byte* nextbuffer(unsigned bufsize) override; }; class MegaBackgroundMediaUploadPrivate : public MegaBackgroundMediaUpload { public: MegaBackgroundMediaUploadPrivate(MegaApi* api); MegaBackgroundMediaUploadPrivate(const string& serialised, MegaApi* api); ~MegaBackgroundMediaUploadPrivate(); bool analyseMediaInfo(const char* inputFilepath) override; char *encryptFile(const char* inputFilepath, int64_t startPos, m_off_t* length, const char *outputFilepath, bool adjustsizeonly) override; char *getUploadURL() override; bool serialize(string* s); char *serialize() override; void setThumbnail(MegaHandle h) override; void setPreview(MegaHandle h) override; void setCoordinates(double lat, double lon, bool unshareable) override; SymmCipher* nodecipher(MegaClient*); MegaApiImpl* api; string url; chunkmac_map chunkmacs; byte filekey[FILENODEKEYLENGTH]; MediaProperties mediaproperties; double latitude = MegaNode::INVALID_COORDINATE; double longitude = MegaNode::INVALID_COORDINATE; bool unshareableGPS = false; handle thumbnailFA = INVALID_HANDLE; handle previewFA = INVALID_HANDLE; }; struct MegaFile : public File { MegaFile(); void setTransfer(MegaTransferPrivate* newTransfer); MegaTransferPrivate *getTransfer(); bool serialize(string*) const override; static MegaFile* unserialize(string*); protected: MegaTransferPrivate *megaTransfer; }; struct MegaFileGet : public MegaFile { void prepare(FileSystemAccess&) override; void updatelocalname() override; void progress() override; void completed(Transfer*, putsource_t source) override; void terminated(error e) override; bool undelete() const override { return mUndelete; } void setUndelete(bool u = true) { mUndelete = u; } MegaFileGet(MegaClient *client, Node* n, const LocalPath& dstPath, FileSystemType fsType, CollisionResolution collisionResolution); MegaFileGet(MegaClient *client, MegaNode* n, const LocalPath& dstPath, CollisionResolution collisionResolution); ~MegaFileGet() {} bool serialize(string*) const override; static MegaFileGet* unserialize(string*); private: MegaFileGet() {} bool mUndelete = false; }; struct MegaFilePut : public MegaFile { void completed(Transfer* t, putsource_t source) override; void terminated(error e) override; MegaFilePut(MegaClient *client, LocalPath clocalname, string *filename, NodeHandle ch, const char* ctargetuser, int64_t mtime = -1, bool isSourceTemporary = false, std::shared_ptr pvNode = nullptr); ~MegaFilePut() {} bool serialize(string*) const override; static MegaFilePut* unserialize(string*); protected: int64_t customMtime; private: MegaFilePut() {} }; //Thread safe request queue class RequestQueue { protected: class ScopedRetainingRequest; class RetainedRequest { MegaRequestPrivate* mRequest{nullptr}; void set(MegaRequestPrivate* request) { mRequest = request; } void clear() { mRequest = nullptr; } friend class ScopedRetainingRequest; public: void removeListener(MegaRequestListener* listener); void removeListener(MegaScheduledCopyListener* listener); }; class ScopedRetainingRequest { RetainedRequest& mRetainedRequest; public: ScopedRetainingRequest(RetainedRequest& retainedRequest, MegaRequestPrivate* request); ~ScopedRetainingRequest(); }; std::deque requests; RetainedRequest retainedRequest; std::mutex mutex; public: RequestQueue(); void push(MegaRequestPrivate *request); void push(std::unique_ptr request); void push_front(MegaRequestPrivate *request); MegaRequestPrivate * pop(); MegaRequestPrivate * front(); void removeListener(MegaRequestListener *listener); void removeListener(MegaScheduledCopyListener *listener); std::unique_ptr scopedRetainingRequest(MegaRequestPrivate* request); }; //Thread safe transfer queue class TransferQueue { protected: std::deque transfers; std::mutex mutex; int lastPushedTransferTag = 0; public: TransferQueue(); void push(MegaTransferPrivate *transfer); void push_front(MegaTransferPrivate *transfer); MegaTransferPrivate * pop(); bool empty(); size_t size(); void clear(); /** * @brief pops and returns transfer up to the designated one * @param lastQueuedTransfer position of the last transfer to pop * @param direction directio of transfers to pop * @return */ std::vector popUpTo(int lastQueuedTransfer, int direction); void removeWithFolderTag(int folderTag, std::function callback); void removeListener(MegaTransferListener *listener); int getLastPushedTag() const; void setAllCancelled(CancelToken t, int direction); }; #ifdef ENABLE_SYNC /** * Implementation for a Sync stall conflict (immutable) * It Could wrap a single synchronization conflict or a reference to it * if we know the MegaSyncStallList container is kept around. */ class MegaSyncStallPrivate : public MegaSyncStall { public: MegaSyncStallPrivate(const SyncStallEntry& e); MegaSyncStallPrivate* copy() const override; SyncStallReason reason() const override { return SyncStallReason(info.reason); } MegaHandle cloudNodeHandle(int index) const override { if (index == 0) return info.cloudPath1.cloudHandle.as8byte(); if (index == 1) return info.cloudPath2.cloudHandle.as8byte(); return UNDEF; } const char* path(bool cloudSide, int index) const override { if (cloudSide) { if (index == 0) return info.cloudPath1.cloudPath.c_str(); if (index == 1) return info.cloudPath2.cloudPath.c_str(); } else { if (lpConverted[0].empty() && lpConverted[1].empty()) { lpConverted[0] = info.localPath1.localPath.toPath(false); lpConverted[1] = info.localPath2.localPath.toPath(false); } if (index == 0) return lpConverted[0].c_str(); if (index == 1) return lpConverted[1].c_str(); } return nullptr; } unsigned int pathCount(bool cloudSide) const override { unsigned int count(0); if (cloudSide) { if(!info.cloudPath1.cloudPath.empty()) { count++; } if(!info.cloudPath2.cloudPath.empty()) { count++; } } else { if(!info.localPath1.localPath.empty()) { count++; } if(!info.localPath2.localPath.empty()) { count++; } } return count; } int pathProblem(bool cloudSide, int index) const override { if (cloudSide) { if (index == 0) return int(info.cloudPath1.problem); if (index == 1) return int(info.cloudPath2.problem); } else { if (index == 0) return int(info.localPath1.problem); if (index == 1) return int(info.localPath2.problem); } return -1; } bool couldSuggestIgnoreThisPath(bool cloudSide, int index) const override { if (info.reason != SyncWaitReason::FileIssue) return false; int problem = pathProblem(cloudSide, index); return problem == DetectedHardLink || problem == DetectedNestedMount || problem == DetectedSymlink || problem == DetectedSpecialFile || problem == FilesystemErrorListingFolder; } const char* reasonDebugString() const override { return reasonDebugString(reason()); } bool detectedCloudSide() const override { return info.detectionSideIsMEGA; } size_t getHash() const override; static const char* reasonDebugString(MegaSyncStall::SyncStallReason reason); static const char* pathProblemDebugString(MegaSyncStall::SyncPathProblem reason); const SyncStallEntry info; protected: mutable string lpConverted[2]; mutable std::pair hashCache{}; // an std::optional would be much better }; class MegaSyncNameConflictStallPrivate : public MegaSyncStall { public: MegaSyncNameConflictStallPrivate(const NameConflict& nc) : mConflict(nc) {} MegaSyncNameConflictStallPrivate* copy() const override { return new MegaSyncNameConflictStallPrivate(*this); } SyncStallReason reason() const override { return SyncStallReason(NamesWouldClashWhenSynced); } MegaHandle cloudNodeHandle(int index) const override { if (index >= 0 && index < int(mConflict.clashingCloud.size())) { return mConflict.clashingCloud[static_cast(index)].handle.as8byte(); } return UNDEF; } const char* path(bool cloudSide, int index) const override { if (cloudSide) { auto i = mCache1.find(index); if (i != mCache1.end()) return i->second.c_str(); if (index >= 0 && index < int(mConflict.clashingCloud.size())) { mCache1[index] = mConflict.cloudPath + "/" + mConflict.clashingCloud[static_cast(index)].name; return mCache1[index].c_str(); } } else { auto i = mCache2.find(index); if (i != mCache2.end()) return i->second.c_str(); if (index >= 0 && index < int(mConflict.clashingLocalNames.size())) { LocalPath lp = mConflict.localPath; lp.appendWithSeparator(mConflict.clashingLocalNames[static_cast(index)], true); mCache2[index] = lp.toPath(false); return mCache2[index].c_str(); } } return nullptr; } unsigned int pathCount(bool cloudSide) const override { if (cloudSide) { return static_cast(mConflict.clashingCloud.size()); } else { return static_cast(mConflict.clashingLocalNames.size()); } } int pathProblem(bool, int) const override { return -1; } bool couldSuggestIgnoreThisPath(bool /*cloudSide*/, int /*index*/) const override { return false; } const char* reasonDebugString() const override { return reasonDebugString(reason()); } bool detectedCloudSide() const override { return mCache1.size() > 1; } size_t getHash() const override; static const char* reasonDebugString(MegaSyncStall::SyncStallReason reason); static const char* pathProblemDebugString(MegaSyncStall::SyncPathProblem reason); const NameConflict mConflict; protected: mutable map mCache1, mCache2; mutable std::pair hashCache{}; // an std::optional would be much better }; class AddressedStallFilter { // Keeps track of which stalls the user addressed already // So we don't re-show them if the user presses Refresh // before the sync actually re-evaluates those nodes // in a complete new pass over the sync nodes mutex m; std::map addressedSyncCloudStalls; std::map addressedSyncLocalStalls; std::map addressedNameConflictCloudStalls; std::map addressedNameConflictLocalStalls; public: bool addressedNameConfict(const string& cloudPath, const LocalPath& localPath); bool addressedCloudStall(const string& cloudPath); bool addressedLocalStall(const LocalPath& localPath); void filterStallCloud(const string& cloudPath, int completedPassCount); void filterStallLocal(const LocalPath& localPath, int completedPassCount); void filterNameConfict(const string& cloudPath, const LocalPath& localPath, int completedPassCount); void removeOldFilters(int completedPassCount); void clear(); }; class MegaSyncStallListPrivate : public MegaSyncStallList { public: MegaSyncStallListPrivate() = default; MegaSyncStallListPrivate(SyncProblems&&, AddressedStallFilter& filter); MegaSyncStallListPrivate* copy() const override; const MegaSyncStall* get(size_t i) const override; size_t size() const override { return mStalls.size(); } void addStall(std::shared_ptr s) { assert(!!s); mStalls.push_back(s); } protected: std::vector> mStalls; }; class MegaSyncStallMapPrivate: public MegaSyncStallMap { public: MegaSyncStallMapPrivate(SyncProblems&& sp, AddressedStallFilter& filter); MegaSyncStallMapPrivate* copy() const override { return new MegaSyncStallMapPrivate(*this); } const MegaSyncStallList* get(const MegaHandle key) const override { if (const auto& it = mStallsMap.find(key); it != mStallsMap.end()) { return &it->second; } return nullptr; } size_t size() const override { return mStallsMap.size(); } MegaHandleList* getKeys() const override; protected: MegaSyncStallMapPrivate() = default; const std::map& getMap() const; std::map mStallsMap; }; #endif // ENABLE_SYNC class MegaSearchFilterPrivate : public MegaSearchFilter { public: MegaSearchFilterPrivate* copy() const override; void byName(const char* searchString) override; void byNodeType(int nodeType) override; void byCategory(int mimeType) override; void byFavourite(int boolFilterOption) override; void bySensitivity(int boolFilterOption) override; void byLocationHandle(MegaHandle ancestorHandle) override; void byLocation(int locationType) override; void byCreationTime(int64_t lowerLimit, int64_t upperLimit) override; void byModificationTime(int64_t lowerLimit, int64_t upperLimit) override; void byDescription(const char* searchString) override; void byTag(const char* searchString) override; void useAndForTextQuery(bool useAnd) override; const char* byName() const override { return mNameFilter.c_str(); } int byNodeType() const override { return mNodeType; } int byCategory() const override { return mMimeCategory; } int byFavourite() const override { return mFavouriteFilterOption; } int bySensitivity() const override { return mExcludeSensitive; } MegaHandle byLocationHandle() const override { return mLocationHandle; } int byLocation() const override { return mLocationType; } int64_t byCreationTimeLowerLimit() const override { return mCreationLowerLimit; } int64_t byCreationTimeUpperLimit() const override { return mCreationUpperLimit; } int64_t byModificationTimeLowerLimit() const override { return mModificationLowerLimit; } int64_t byModificationTimeUpperLimit() const override { return mModificationUpperLimit; } const char* byDescription() const override { return mDescriptionFilter.c_str(); } const char* byTag() const override { return mTag.c_str(); } bool useAndForTextQuery() const override { return mUseAndForTextQuery; } private: std::string mNameFilter; int mNodeType = MegaNode::TYPE_UNKNOWN; int mMimeCategory = MegaApi::FILE_TYPE_DEFAULT; int mFavouriteFilterOption = MegaSearchFilter::BOOL_FILTER_DISABLED; int mExcludeSensitive = MegaSearchFilter::BOOL_FILTER_DISABLED; MegaHandle mLocationHandle = INVALID_HANDLE; int mLocationType = MegaApi::SEARCH_TARGET_ALL; int64_t mCreationLowerLimit = 0; int64_t mCreationUpperLimit = 0; int64_t mModificationLowerLimit = 0; int64_t mModificationUpperLimit = 0; std::string mDescriptionFilter; std::string mTag; bool mUseAndForTextQuery = true; /** * @brief Checks if the input value is: * 0 -> MegaSearchFilter::BOOL_FILTER_DISABLED * 1 -> MegaSearchFilter::BOOL_FILTER_ONLY_TRUE * 2 -> MegaSearchFilter::BOOL_FILTER_ONLY_FALSE * * If it is out of range, 0 is returned and a warning message is logged */ static int validateBoolFilterOption(const int value); }; class MegaSearchPagePrivate : public MegaSearchPage { public: MegaSearchPagePrivate(size_t startingOffset, size_t size) : mOffset(startingOffset), mSize(size) {} MegaSearchPagePrivate* copy() const override { return new MegaSearchPagePrivate(*this); } size_t startingOffset() const override { return mOffset; } size_t size() const override { return mSize; } private: size_t mOffset; size_t mSize; }; class MegaGfxProviderPrivate : public MegaGfxProvider { public: explicit MegaGfxProviderPrivate(std::unique_ptr<::mega::IGfxProvider> provider) : mProvider(std::move(provider)) {} explicit MegaGfxProviderPrivate(MegaGfxProviderPrivate&& other) : mProvider(std::move(other.mProvider)) {} std::unique_ptr<::mega::IGfxProvider> releaseProvider() { return std::move(mProvider); } static std::unique_ptr createIsolatedInstance(const char* endpointName, const char* executable, unsigned int keepAliveInSeconds, const MegaStringList* extraArgs); static std::unique_ptr createExternalInstance(MegaGfxProcessor* processor); static std::unique_ptr createInternalInstance(); private: std::unique_ptr<::mega::IGfxProvider> mProvider; }; class MegaFlagPrivate : public MegaFlag { public: MegaFlagPrivate(uint32_t type, uint32_t group) : mType(type), mGroup(group) {} uint32_t getType() const override { return mType; } uint32_t getGroup() const override { return mGroup; } private: uint32_t mType; uint32_t mGroup; }; #ifdef ENABLE_SYNC /** * @brief Struct containing the necessary params for syncFolder() or prevalidateSyncFolder() * requests. * * @see MegaApiImpl::syncFolder() * @see MegaApiImpl::prevalidateSyncFolder() */ struct MegaRequestSyncFolderParams { std::string localFolder; std::string name; MegaHandle megaHandle{UNDEF}; SyncConfig::Type type{}; std::string driveRootIfExternal; }; #endif // ENABLE_SYNC class MegaApiImpl : public MegaApp { public: MegaApiImpl(MegaApi* api, MegaGfxProcessor* processor, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType); MegaApiImpl(MegaApi* api, MegaGfxProvider* provider, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType); virtual ~MegaApiImpl(); static MegaApiImpl* ImplOf(MegaApi*); /** * @brief Max children count to allow name-based search for nodes with same name than local * file to be uploaded, otherwise search nodes by fingerprint (to avoid penalization). */ static constexpr size_t MAX_CHILDREN_FOR_SAME_NAME_SEARCH{100}; //Multiple listener management. void addListener(MegaListener* listener); void addRequestListener(MegaRequestListener* listener); void addTransferListener(MegaTransferListener* listener); void addScheduledCopyListener(MegaScheduledCopyListener* listener); void addGlobalListener(MegaGlobalListener* listener); bool removeListener(MegaListener* listener); bool removeRequestListener(MegaRequestListener* listener); bool removeTransferListener(MegaTransferListener* listener); bool removeScheduledCopyListener(MegaScheduledCopyListener* listener); bool removeGlobalListener(MegaGlobalListener* listener); //Utils long long getSDKtime(); void getSessionTransferURL(const char *path, MegaRequestListener *listener); static MegaHandle base32ToHandle(const char* base32Handle); static handle base64ToHandle(const char* base64Handle); static handle base64ToUserHandle(const char* base64Handle); static handle base64ToBackupId(const char* backupId); static char *handleToBase64(MegaHandle handle); static char *userHandleToBase64(MegaHandle handle); static const char* backupIdToBase64(MegaHandle handle); static char *binaryToBase64(const char* binaryData, size_t length); static void base64ToBinary(const char *base64string, unsigned char **binary, size_t* binarysize); static const char* ebcEncryptKey(const char* encryptionKey, const char* plainKey); void retryPendingConnections(bool disconnect = false, bool includexfers = false, MegaRequestListener* listener = NULL); void setDnsServers(const char *dnsServers, MegaRequestListener* listener = NULL); void addEntropy(char* data, unsigned int size); static string userAttributeToString(int); static string userAttributeToLongName(int); static int userAttributeFromString(const char *name); static char userAttributeToScope(int); bool serverSideRubbishBinAutopurgeEnabled(); bool appleVoipPushEnabled(); bool newLinkFormatEnabled(); bool accountIsNew() const; unsigned int getABTestValue(const char* flag); void sendABTestActive(const char* flag, MegaRequestListener* listener); int smsAllowedState(); char* smsVerifiedPhoneNumber(); void resetSmsVerifiedPhoneNumber(MegaRequestListener *listener); bool multiFactorAuthAvailable(); void multiFactorAuthCheck(const char *email, MegaRequestListener *listener = NULL); void multiFactorAuthGetCode(MegaRequestListener *listener = NULL); void multiFactorAuthEnable(const char *pin, MegaRequestListener *listener = NULL); void multiFactorAuthDisable(const char *pin, MegaRequestListener *listener = NULL); void multiFactorAuthLogin(const char* email, const char* password, const char* pin, MegaRequestListener *listener = NULL); void multiFactorAuthChangePassword(const char *oldPassword, const char *newPassword, const char* pin, MegaRequestListener *listener = NULL); void multiFactorAuthChangeEmail(const char *email, const char* pin, MegaRequestListener *listener = NULL); void multiFactorAuthCancelAccount(const char* pin, MegaRequestListener *listener = NULL); void fetchTimeZone(bool forceApiFetch = true, MegaRequestListener *listener = NULL); //API requests void login(const char* email, const char* password, MegaRequestListener *listener = NULL); char *dumpSession(); char *getSequenceNumber(); char *getSequenceTag(); char *getAccountAuth(); void setAccountAuth(const char* auth); void fastLogin(const char* session, MegaRequestListener *listener = NULL); void killSession(MegaHandle sessionHandle, MegaRequestListener *listener = NULL); void getUserData(MegaRequestListener *listener = NULL); void getUserData(MegaUser *user, MegaRequestListener *listener = NULL); void getUserData(const char *user, MegaRequestListener *listener = NULL); void getMiscFlags(MegaRequestListener *listener = NULL); void sendDevCommand(const char *command, const char *email, long long quota, int businessStatus, int userStatus, MegaRequestListener *listener); void getCloudStorageUsed(MegaRequestListener *listener = NULL); void getAccountDetails(bool storage, bool transfer, bool pro, bool sessions, bool purchases, bool transactions, int source = -1, MegaRequestListener *listener = NULL); void queryTransferQuota(long long size, MegaRequestListener *listener = NULL); void createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener = NULL); void createEphemeralAccountPlusPlus(const char* firstname, const char* lastname, MegaRequestListener *listener = NULL); void resumeCreateAccount(const char* sid, MegaRequestListener *listener = NULL); void resumeCreateAccountEphemeralPlusPlus(const char* sid, MegaRequestListener *listener = NULL); void cancelCreateAccount(MegaRequestListener* listener = NULL); void resendSignupLink(const char* email, const char *name, MegaRequestListener *listener = NULL); void querySignupLink(const char* link, MegaRequestListener *listener = NULL); void confirmAccount(const char* link, MegaRequestListener* listener = NULL); void resetPassword(const char *email, bool hasMasterKey, MegaRequestListener *listener = NULL); void queryRecoveryLink(const char *link, MegaRequestListener *listener = NULL); void confirmResetPasswordLink(const char *link, const char *newPwd, const char *masterKey = NULL, MegaRequestListener *listener = NULL); void checkRecoveryKey(const char* link, const char* masterKey, MegaRequestListener* listener = NULL); void cancelAccount(MegaRequestListener *listener = NULL); void confirmCancelAccount(const char *link, const char *pwd, MegaRequestListener *listener = NULL); void resendVerificationEmail(MegaRequestListener *listener = NULL); void changeEmail(const char *email, MegaRequestListener *listener = NULL); void confirmChangeEmail(const char *link, const char *pwd, MegaRequestListener *listener = NULL); void setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener = NULL); MegaProxy *getAutoProxySettings(); int isLoggedIn(); void loggedInStateChanged(sessiontype_t, handle me, const string &email) override; bool isEphemeralPlusPlus(); void whyAmIBlocked(bool logout, MegaRequestListener *listener = NULL); char* getMyEmail(); int64_t getAccountCreationTs(); char* getMyUserHandle(); MegaHandle getMyUserHandleBinary(); MegaUser *getMyUser(); bool isAchievementsEnabled(); bool isProFlexiAccount(); bool isBusinessAccount(); bool isMasterBusinessAccount(); bool isBusinessAccountActive(); int getBusinessStatus(); int64_t getOverquotaDeadlineTs(); MegaIntegerList *getOverquotaWarningsTs(); bool checkPassword(const char *password); char* getMyCredentials(); void getUserCredentials(MegaUser *user, MegaRequestListener *listener = NULL); bool areCredentialsVerified(MegaUser *user); void verifyCredentials(MegaUser *user, MegaRequestListener *listener = NULL); void resetCredentials(MegaUser* user, MegaRequestListener* listener = NULL); void setLogExtraForModules(bool networking, bool syncs); static void setLogLevel(int logLevel); static void setMaxPayloadLogSize(size_t maxSize); static void addLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger); static void removeLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger); static void setLogToConsole(bool enable); static void setLogJSONContent(bool enable); static void setLogJSON(uint32_t value); static uint32_t getLogJSON(); static void log(int logLevel, const char* message, const char *filename = NULL, int line = -1); void setLoggingName(const char* loggingName); void createFolder(const char* name, MegaNode *parent, MegaRequestListener *listener = NULL); bool createLocalFolder(const char *path); static Error createLocalFolder_unlocked(LocalPath& localPath, FileSystemAccess& fsaccess, const CollisionResolution& collisionResolution); void moveNode(MegaNode* node, MegaNode* newParent, MegaRequestListener *listener = NULL); void moveNode(MegaNode* node, MegaNode* newParent, const char *newName, MegaRequestListener *listener = NULL); void copyNode(MegaNode* node, MegaNode *newParent, MegaRequestListener *listener = NULL); void copyNode(MegaNode* node, MegaNode *newParent, const char* newName, MegaRequestListener *listener = NULL); void renameNode(MegaNode* node, const char* newName, MegaRequestListener *listener = NULL); void remove(MegaNode* node, bool keepversions = false, MegaRequestListener *listener = NULL); void removeVersions(MegaRequestListener *listener = NULL); void restoreVersion(MegaNode *version, MegaRequestListener *listener = NULL); void cleanRubbishBin(MegaRequestListener *listener = NULL); void sendFileToUser(MegaNode *node, MegaUser *user, MegaRequestListener *listener = NULL); void sendFileToUser(MegaNode *node, const char* email, MegaRequestListener *listener = NULL); void upgradeSecurity(MegaRequestListener* listener = NULL); bool contactVerificationWarningEnabled(); void setManualVerificationFlag(bool enable); void openShareDialog(MegaNode *node, MegaRequestListener *listener = NULL); void share(MegaNode *node, MegaUser* user, int level, MegaRequestListener *listener = NULL); void share(MegaNode* node, const char* email, int level, MegaRequestListener *listener = NULL); void loginToFolder(const char* megaFolderLink, const char* authKey = nullptr, bool tryToResumeFolderLinkFromCache = false, MegaRequestListener* listener = nullptr); void importFileLink(const char* megaFileLink, MegaNode* parent, MegaRequestListener *listener = NULL); void decryptPasswordProtectedLink(const char* link, const char* password, MegaRequestListener *listener = NULL); void encryptLinkWithPassword(const char* link, const char* password, MegaRequestListener *listener = NULL); void getDownloadUrl(MegaNode* node, bool singleUrl, bool forceSSL, MegaRequestListener* listener); void getPublicNode(const char* megaFileLink, MegaRequestListener *listener = NULL); const char *buildPublicLink(const char *publicHandle, const char *key, bool isFolder); void getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener = NULL); void getThumbnail(MegaHandle handle, const char* dstFilePath, MegaRequestListener* listener = nullptr); void cancelGetThumbnail(MegaNode* node, MegaRequestListener* listener = NULL); void setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener = NULL); void putThumbnail(MegaBackgroundMediaUpload* node, const char *srcFilePath, MegaRequestListener *listener = NULL); void setThumbnailByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener = NULL); void getPreview(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener = NULL); void cancelGetPreview(MegaNode* node, MegaRequestListener *listener = NULL); void setPreview(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener = NULL); void putPreview(MegaBackgroundMediaUpload* node, const char *srcFilePath, MegaRequestListener *listener = NULL); void setPreviewByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener = NULL); void getUserAvatar(MegaUser* user, const char *dstFilePath, MegaRequestListener *listener = NULL); void setAvatar(const char *dstFilePath, MegaRequestListener *listener = NULL); void getUserAvatar(const char *email_or_handle, const char *dstFilePath, MegaRequestListener *listener = NULL); static char* getUserAvatarColor(MegaUser *user); static char *getUserAvatarColor(const char *userhandle); static char* getUserAvatarSecondaryColor(MegaUser *user); static char *getUserAvatarSecondaryColor(const char *userhandle); char* getPrivateKey(int type); bool testAllocation(unsigned allocCount, size_t allocSize); void getUserAttribute(MegaUser* user, int type, MegaRequestListener *listener = NULL); void getUserAttribute(const char* email_or_handle, int type, MegaRequestListener *listener = NULL); void getChatUserAttribute(const char* email_or_handle, int type, const char* ph, MegaRequestListener *listener = NULL); void getUserAttr(const char* email_or_handle, int type, const char *dstFilePath, int number = 0, MegaRequestListener *listener = NULL); void getChatUserAttr(const char* email_or_handle, int type, const char *dstFilePath, const char *ph = NULL, int number = 0, MegaRequestListener *listener = NULL); void setUserAttribute(int type, const char* value, MegaRequestListener *listener = NULL); void setUserAttribute(int type, const MegaStringMap* value, MegaRequestListener *listener = NULL); void getRubbishBinAutopurgePeriod(MegaRequestListener *listener = NULL); void setRubbishBinAutopurgePeriod(int days, MegaRequestListener *listener = NULL); const char* getDeviceId() const; void getDeviceName(const char* deviceId, MegaRequestListener *listener = NULL); void setDeviceName(const char* deviceId, const char* deviceName, MegaRequestListener *listener = NULL); void getDriveName(const char *pathToDrive, MegaRequestListener *listener = NULL); void setDriveName(const char* pathToDrive, const char *driveName, MegaRequestListener *listener = NULL); void getUserEmail(MegaHandle handle, MegaRequestListener *listener = NULL); void setCustomNodeAttribute(MegaNode *node, const char *attrName, const char *value, MegaRequestListener *listener = NULL); void setNodeS4(MegaNode* node, const char* value, MegaRequestListener* listener = NULL); bool isS4Enabled(); MegaHandle getS4Container(); void setNodeLabel(MegaNode *node, int label, MegaRequestListener *listener = NULL); void setNodeFavourite(MegaNode *node, bool fav, MegaRequestListener *listener = NULL); void getFavourites(MegaNode* node, int count, MegaRequestListener* listener = nullptr); void setNodeSensitive(MegaNode* node, bool sensitive, MegaRequestListener* listener); void setNodeCoordinates(std::variant nodeOrNodeHandle, bool unshareable, double latitude, double longitude, MegaRequestListener* listener = NULL); void setNodeDescription(MegaNode* node, const char* description, MegaRequestListener* listener = NULL); void addNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener = NULL); void removeNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener = NULL); void updateNodeTag(MegaNode* node, const char* newTag, const char* oldTag, MegaRequestListener* listener = NULL); MegaStringList* getAllNodeTagsBelow(MegaHandle handle, const std::string& pattern, CancelToken cancelToken); void exportNode(MegaNode *node, int64_t expireTime, bool writable, bool megaHosted, MegaRequestListener *listener = NULL); void disableExport(MegaNode *node, MegaRequestListener *listener = NULL); void fetchNodes(MegaRequestListener *listener = NULL); void getPricing(const std::optional& countryCode = std::nullopt, MegaRequestListener* listener = nullptr); void getRecommendedProLevel(MegaRequestListener* listener = NULL); void getPaymentId(handle productHandle, handle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener = NULL); void upgradeAccount(MegaHandle productHandle, int paymentMethod, MegaRequestListener *listener = NULL); void submitPurchaseReceipt(int gateway, const char *receipt, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener = NULL); void creditCardStore(const char* address1, const char* address2, const char* city, const char* province, const char* country, const char *postalcode, const char* firstname, const char* lastname, const char* creditcard, const char* expire_month, const char* expire_year, const char* cv2, MegaRequestListener *listener = NULL); void creditCardQuerySubscriptions(MegaRequestListener *listener = NULL); void creditCardCancelSubscriptions(const char* reason, const char* id, int canContact, MegaRequestListener* listener = NULL); void creditCardCancelSubscriptions(const MegaCancelSubscriptionReasonList* reasons, const char* id, int canContact, MegaRequestListener* listener); void getPaymentMethods(MegaRequestListener *listener = NULL); char *exportMasterKey(); void updatePwdReminderData(bool lastSuccess, bool lastSkipped, bool mkExported, bool dontShowAgain, bool lastLogin, MegaRequestListener *listener = NULL); void changePassword(const char *oldPassword, const char *newPassword, MegaRequestListener *listener = NULL); void inviteContact(const char* email, const char* message, int action, MegaHandle contactLink, MegaRequestListener* listener = NULL); void replyContactRequest(const MegaContactRequest* request, int action, MegaRequestListener* listener = NULL); void respondContactRequest(); void removeContact(MegaUser *user, MegaRequestListener* listener=NULL); void logout(bool keepSyncConfigsFile, MegaRequestListener *listener); void localLogout(MegaRequestListener *listener = NULL); void invalidateCache(); int getPasswordStrength(const char *password); static char* generateRandomCharsPassword(bool useUpper, bool useDigit, bool useSymbol, unsigned int length); void submitFeedback(int rating, const char* comment, bool transferFeedback, int transferType, MegaRequestListener* listener = nullptr); void reportEvent(const char *details = NULL, MegaRequestListener *listener = NULL); void sendEvent(int eventType, const char* message, bool addJourneyId, const char* viewId, MegaRequestListener *listener = NULL); void createSupportTicket(const char* message, int type = 1, MegaRequestListener *listener = NULL); //Backups MegaStringList *getBackupFolders(int backuptag); void setScheduledCopy(const char* localPath, MegaNode *parent, bool attendPastBackups, int64_t period, string periodstring, int numBackups, MegaRequestListener *listener=NULL); void removeScheduledCopy(int tag, MegaRequestListener *listener=NULL); void abortCurrentScheduledCopy(int tag, MegaRequestListener *listener=NULL); //Timer void startTimer( int64_t period, MegaRequestListener *listener=NULL); //Transfers void startUploadForSupport(const char* localPath, bool isSourceFileTemporary, FileSystemType fsType, MegaTransferListener* listener); struct MegaUploadOptionsPrivate { MegaUploadOptions mPublicOptions; int mFolderTransferTag = 0; bool mIsBackup = false; bool mForceNewUpload = false; FileSystemType mFsType = FS_UNKNOWN; PitagTarget mPitagTarget = PitagTarget::NotApplicable; std::string mTargetUser; }; void startUpload(const std::string localPath, MegaNode* parent, CancelToken cancelToken, const MegaUploadOptionsPrivate& options, MegaTransferListener* listener); MegaTransferPrivate* createUploadTransfer(const LocalPath& localPath, MegaNode* parent, const MegaUploadOptionsPrivate& options, CancelToken cancelToken, MegaTransferListener* listener, const FileFingerprint* preFingerprintedFile = nullptr); void startDownload (bool startFirst, MegaNode *node, const char* localPath, const char *customName, int folderTransferTag, const char *appData, CancelToken cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener *listener); MegaTransferPrivate* createDownloadTransfer(bool startFirst, MegaNode* node, const LocalPath& localPath, const char* customName, int folderTransferTag, const char* appData, CancelToken cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener* listener, FileSystemType fsType); void startStreaming(MegaNode* node, m_off_t startPos, m_off_t size, MegaTransferListener *listener); void setStreamingMinimumRate(int bytesPerSecond); void retryTransfer(MegaTransfer *transfer, MegaTransferListener *listener = NULL); void cancelTransfer(MegaTransfer *transfer, MegaRequestListener *listener=NULL); void cancelTransferByTag(int transferTag, MegaRequestListener *listener = NULL); void cancelTransfers(int direction, MegaRequestListener *listener=NULL); void pauseTransfers(bool pause, int direction, MegaRequestListener* listener=NULL); void pauseTransfer(int transferTag, bool pause, MegaRequestListener* listener = NULL); void moveTransferUp(int transferTag, MegaRequestListener *listener = NULL); void moveTransferDown(int transferTag, MegaRequestListener *listener = NULL); void moveTransferToFirst(int transferTag, MegaRequestListener *listener = NULL); void moveTransferToLast(int transferTag, MegaRequestListener *listener = NULL); void moveTransferBefore(int transferTag, int prevTransferTag, MegaRequestListener *listener = NULL); bool areTransfersPaused(int direction); void resumeTransfersForNotLoggedInInstance(); void setMaxConnections(int direction, int connections, MegaRequestListener* listener = NULL); private: void getMaxTransferConnections(const direction_t direction, MegaRequestListener* const listener); public: void getMaxUploadConnections(MegaRequestListener* const listener); void getMaxDownloadConnections(MegaRequestListener* const listener); void setDownloadMethod(int method); void setUploadMethod(int method); bool setMaxDownloadSpeed(m_off_t bpslimit); bool setMaxUploadSpeed(m_off_t bpslimit); int getMaxDownloadSpeed(); int getMaxUploadSpeed(); int getCurrentDownloadSpeed(); int getCurrentUploadSpeed(); int getCurrentSpeed(int type); int getDownloadMethod(); int getUploadMethod(); MegaTransferData *getTransferData(MegaTransferListener *listener = NULL); MegaTransfer *getFirstTransfer(int type); void notifyTransfer(int transferTag, MegaTransferListener *listener = NULL); MegaTransferList *getTransfers(); MegaTransferList *getStreamingTransfers(); MegaTransfer* getTransferByUniqueId(uint32_t transferUniqueId) const; MegaTransfer* getTransferByTag(int transferTag); MegaTransferList *getTransfers(int type); MegaTransferList *getChildTransfers(int transferTag); MegaTransferList *getTansfersByFolderTag(int folderTransferTag); // FUSE using FuseEventHandler = void (MegaListener::*)(MegaApi*, const char*, int); // Add a new mount. void addMount(const MegaMount* mount, MegaRequestListener* listener); // Disable an enabled mount. void disableMount(const char* path, MegaRequestListener* listener, bool remember); // Enable a disabled mount. void enableMount(const char* path, MegaRequestListener* listener, bool remember); // Retrieve FUSE flags. MegaFuseFlags* getFUSEFlags(); // Broadcast a mount event. void fireOnFuseEvent(FuseEventHandler handler, const fuse::MountEvent& event); // Retrieve a mount's flags. MegaMountFlags* getMountFlags(const char* path); // Retrieve a mount's description. MegaMount* getMountInfo(const char* path); // Retrieve the path of the mounts associated with name. char* getMountPath(const char* name); // Retrieve a list of all (enabled) mounts. MegaMountList* listMounts(bool enabled); // Called when FUSE wants to broadcast a mount event. void onFuseEvent(const fuse::MountEvent& event) override; // Query whether a file is in FUSE's file cache. bool isCached(const char* path); // Query whether FUSE is supported on this platform. bool isFUSESupported(); // Query whether a mount is enabled. bool isMountEnabled(const char* path); // Remove a disabled mount. void removeMount(const char* path, MegaRequestListener* listener); // Update FUSE flags. void setFUSEFlags(const MegaFuseFlags& flags); // Update a mount's flags. void setMountFlags(const MegaMountFlags* flags, const char* path, MegaRequestListener* listener); //Sets and Elements void putSet(MegaHandle sid, int optionFlags, const char* name, MegaHandle cover, int type, MegaRequestListener* listener = nullptr); void removeSet(MegaHandle sid, MegaRequestListener* listener = nullptr); void putSetElements(MegaHandle sid, const MegaHandleList* nodes, const MegaStringList* names, MegaRequestListener* listener = nullptr); void putSetElement(MegaHandle sid, MegaHandle eid, MegaHandle node, int optionFlags, int64_t order, const char* name, MegaRequestListener* listener = nullptr); void removeSetElements(MegaHandle sid, const MegaHandleList* eids, MegaRequestListener* listener = nullptr); void removeSetElement(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener = nullptr); void exportSet(MegaHandle sid, MegaRequestListener* listener = nullptr); void disableExportSet(MegaHandle sid, MegaRequestListener* listener = nullptr); static int getSetElementHandleSize() { return MegaClient::SETELEMENTHANDLE; } MegaSetList* getSets(); MegaSet* getSet(MegaHandle sid); MegaHandle getSetCover(MegaHandle sid); unsigned getSetElementCount(MegaHandle sid, bool includeElementsInRubbishBin); MegaSetElementList* getSetElements(MegaHandle sid, bool includeElementsInRubbishBin); MegaSetElement* getSetElement(MegaHandle sid, MegaHandle eid); const char* getPublicLinkForExportedSet(MegaHandle sid); void fetchPublicSet(const char* publicSetLink, MegaRequestListener* listener = nullptr); MegaSet* getPublicSetInPreview(); MegaSetElementList* getPublicSetElementsInPreview(); void getPreviewElementNode(MegaHandle eid, MegaRequestListener* listener = nullptr); void stopPublicSetPreview(); bool isExportedSet(MegaHandle sid); bool inPublicSetPreview(); // returns the Pro level based on the current plan and storage usage (MegaAccountDetails::ACCOUNT_TYPE_XYZ) static int calcRecommendedProLevel(MegaPricing& pricing, MegaAccountDetails& accDetails); private: bool nodeInRubbishCheck(handle) const; error checkCreateFolderPrecons(const char* name, std::shared_ptr parent, MegaRequestPrivate* request); void sendUserfeedback(const int rating, const char* comment, const bool transferFeedback, const int transferType); public: #ifdef ENABLE_SYNC //Sync OverlayIconCachedPaths mRecentlyNotifiedOverlayIconPaths; OverlayIconCachedPaths mRecentlyRequestedOverlayIconPaths; int syncPathState(string *path); MegaNode *getSyncedNode(const LocalPath& path); void syncFolder(MegaRequestSyncFolderParams&& params, MegaRequestListener* const listener = nullptr); void prevalidateSyncFolder(MegaRequestSyncFolderParams&&, MegaRequestListener* const listener = nullptr); void loadExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener); void closeExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener); void copySyncDataToCache(const char *localFolder, const char *name, MegaHandle megaHandle, const char *remotePath, long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener *listener = NULL); void copyCachedStatus(int storageStatus, int blockStatus, int businessStatus, MegaRequestListener *listener = NULL); void importSyncConfigs(const char* configs, MegaRequestListener* listener); const char* exportSyncConfigs(); void removeSyncById(handle backupId, MegaRequestListener *listener=NULL); void setSyncRunState(MegaHandle backupId, MegaSync::SyncRunningState targetState, MegaRequestListener *listener); void rescanSync(MegaHandle backupId, bool reFingerprint); MegaSyncList *getSyncs(); void setLegacyExcludedNames(vector *excludedNames); void setLegacyExcludedPaths(vector *excludedPaths); void setLegacyExclusionLowerSizeLimit(unsigned long long limit); void setLegacyExclusionUpperSizeLimit(unsigned long long limit); MegaError* exportLegacyExclusionRules(const char* absolutePath); long long getNumLocalNodes(); int isNodeSyncable(MegaNode *megaNode); MegaError *isNodeSyncableWithError(MegaNode* node); bool isScanning(); bool isSyncing(); std::atomic receivedStallFlag{false}; std::atomic receivedNameConflictsFlag{false}; std::atomic receivedTotalStallsFlag{false}; std::atomic receivedTotalNameConflictsFlag{false}; std::atomic receivedScanningStateFlag{false}; std::atomic receivedSyncingStateFlag{false}; MegaSync *getSyncByBackupId(mega::MegaHandle backupId); MegaSync *getSyncByNode(MegaNode *node); MegaSync *getSyncByPath(const char * localPath); void getMegaSyncStallList(MegaRequestListener* listener); void getMegaSyncStallMap(MegaRequestListener* listener); void clearStalledPath(MegaSyncStall*); void moveToDebris(const char* path, MegaHandle syncBackupId, MegaRequestListener* listener = nullptr); void changeSyncRemoteRoot(const MegaHandle syncBackupId, const MegaHandle newRootNodeHandle, MegaRequestListener* listener); void changeSyncLocalRoot(const MegaHandle syncBackupId, const char* newLocalSyncRootPath, MegaRequestListener* listener); void setSyncUploadThrottleUpdateRate(const unsigned updateRateInSeconds, MegaRequestListener* const listener); void setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, MegaRequestListener* const listener); void getSyncUploadThrottleValues(MegaRequestListener* const listener); void getSyncUploadThrottleLimits(const bool upperLimits, MegaRequestListener* const listener); void checkSyncUploadsThrottled(MegaRequestListener* const listener); AddressedStallFilter mAddressedStallFilter; #endif // ENABLE_SYNC void moveOrRemoveDeconfiguredBackupNodes(MegaHandle deconfiguredBackupRoot, MegaHandle backupDestination, MegaRequestListener* listener = NULL); MegaScheduledCopy *getScheduledCopyByTag(int tag); MegaScheduledCopy *getScheduledCopyByNode(MegaNode *node); MegaScheduledCopy *getScheduledCopyByPath(const char * localPath); int isWaiting(); bool isSyncStalled(); bool isSyncStalledChanged() override; void setLRUCacheSize(unsigned long long size); unsigned long long getNumNodesAtCacheLRU() const; unsigned long long getNumNodes(); unsigned long long getAccurateNumNodes(); //Filesystem int getNumChildren(MegaNode* parent); int getNumChildFiles(MegaNode* parent); int getNumChildFolders(MegaNode* parent); MegaNodeList* getChildren(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage); MegaNodeList* listChildNodesLexicographically( const handle parenthandle, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset); MegaNodeList* getChildren(const MegaNode *parent, int order, CancelToken cancelToken = CancelToken()); MegaNodeList* getChildren(MegaNodeList *parentNodes, int order); MegaNodeList* getVersions(MegaNode *node); int getNumVersions(MegaNode *node); bool hasVersions(MegaNode *node); void getFolderInfo(MegaNode *node, MegaRequestListener *listener); bool isSensitiveInherited(MegaNode* node); bool hasChildren(MegaNode *parent); MegaNode *getChildNode(MegaNode *parent, const char* name); MegaNode* getChildNodeOfType(MegaNode *parent, const char *name, int type = TYPE_UNKNOWN); MegaNode *getParentNode(MegaNode *node); char *getNodePath(MegaNode *node); char *getNodePathByNodeHandle(MegaHandle handle); MegaNode *getNodeByPath(const char *path, MegaNode *n = NULL); MegaNode *getNodeByPathOfType(const char* path, MegaNode* n, int type); MegaNode *getNodeByHandle(handle handler); MegaTotpTokenGenResult generateTotpTokenFromNode(const MegaHandle handle); MegaContactRequest *getContactRequestByHandle(MegaHandle handle); MegaUserList* getContacts(); MegaUser* getContact(const char* uid); MegaUserAlertList* getUserAlerts(); int getNumUnreadUserAlerts(); MegaNodeList *getInShares(MegaUser* user, int order); MegaNodeList *getInShares(int order); MegaShareList *getInSharesList(int order); MegaShareList *getUnverifiedInShares(int order); MegaUser *getUserFromInShare(MegaNode *node, bool recurse = false); bool isPendingShare(MegaNode *node); MegaShareList *getOutShares(int order); MegaShareList *getOutShares(MegaNode *node); private: sharedNode_vector getSharedNodes() const; public: MegaShareList *getPendingOutShares(); MegaShareList *getPendingOutShares(MegaNode *megaNode); MegaShareList *getUnverifiedOutShares(int order); bool isPrivateNode(MegaHandle h); bool isForeignNode(MegaHandle h); MegaNodeList *getPublicLinks(int order); MegaContactRequestList* getIncomingContactRequests() const; MegaContactRequestList* getOutgoingContactRequests() const; int getAccess(const std::variant& nodeOrNodeHandle); long long getSize(MegaNode *node); static void removeRecursively(const char *path); //Fingerprint char* getFingerprint(const char* filePath); char *getFingerprint(MegaInputStream *inputStream, int64_t mtime); MegaNode *getNodeByFingerprint(const char* fingerprint); MegaNodeList* getNodesByFingerprint(const char* fingerprint, const bool excludeMtime = false); MegaNodeList *getNodesByOriginalFingerprint(const char* originalfingerprint, MegaNode* parent); MegaNode *getExportableNodeByFingerprint(const char *fingerprint, const char *name = NULL); MegaNode *getNodeByFingerprint(const char *fingerprint, MegaNode* parent); bool hasFingerprint(const char* fingerprint); //CRC char *getCRC(const char *filePath); char *getCRCFromFingerprint(const char *fingerprint); char *getCRC(MegaNode *node); MegaNode* getNodeByCRC(const char *crc, MegaNode* parent); // Permissions MegaError* checkAccessErrorExtended(MegaNode* node, int level); MegaError* checkMoveErrorExtended(MegaNode* node, MegaNode* target); bool isFilesystemAvailable(); MegaNode *getRootNode(); MegaNode* getVaultNode(); MegaNode *getRubbishNode(); MegaNode *getRootNode(MegaNode *node); bool isInRootnode(MegaNode *node, int index); void setDefaultFilePermissions(int permissions); int getDefaultFilePermissions(); void setDefaultFolderPermissions(int permissions); int getDefaultFolderPermissions(); long long getBandwidthOverquotaDelay(); private: void getRecentActionsAsyncInternal(unsigned days, unsigned maxnodes, bool* optExcludeSensitives, MegaRequestListener* listener = NULL); void getRecentActionByIdInternal(const char* id, std::optional excludeSensitives, MegaRequestListener* listener = nullptr); MegaTimeStamp getRecentClearTimestamp(); MegaTimeStamp formatRecentClearTimestamp(string_map* records); public: void getRecentActionsAsync(unsigned days, unsigned maxnodes, MegaRequestListener* listener = NULL); void getRecentActionsAsync(unsigned days, unsigned maxnodes, bool excludeSensitives, MegaRequestListener* listener = NULL); void getRecentActionById(const char* id, MegaRequestListener* listener = nullptr); void getRecentActionById(const char* id, bool excludeSensitives, MegaRequestListener* listener = nullptr); void clearRecentActionHistory(MegaTimeStamp until, MegaRequestListener* listener = nullptr); MegaNodeList* search(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage); private: sharedNode_vector searchInNodeManager(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage); public: bool processMegaTree(MegaNode* node, MegaTreeProcessor* processor, bool recursive = 1); MegaNode *createForeignFileNode(MegaHandle handle, const char *key, const char *name, m_off_t size, m_off_t mtime, const char* fingerprintCrc, MegaHandle parentHandle, const char *privateauth, const char *publicauth, const char *chatauth); MegaNode *createForeignFolderNode(MegaHandle handle, const char *name, MegaHandle parentHandle, const char *privateauth, const char *publicauth); MegaNode *authorizeNode(MegaNode *node); void authorizeMegaNodePrivate(MegaNodePrivate *node); MegaNode *authorizeChatNode(MegaNode *node, const char *cauth); const char *getVersion(); char *getOperatingSystemVersion(); void getLastAvailableVersion(const char*, MegaRequestListener* listener = nullptr); void getLocalSSLCertificate(MegaRequestListener *listener = NULL); void queryDNS(const char *hostname, MegaRequestListener *listener = NULL); void downloadFile(const char *url, const char *dstpath, MegaRequestListener *listener = NULL); const char *getUserAgent(); const char *getBasePath(); void contactLinkCreate(bool renew = false, MegaRequestListener *listener = NULL); void contactLinkQuery(MegaHandle handle, MegaRequestListener *listener = NULL); void contactLinkDelete(MegaHandle handle, MegaRequestListener *listener = NULL); void keepMeAlive(int type, bool enable, MegaRequestListener *listener = NULL); void acknowledgeUserAlerts(MegaRequestListener *listener = NULL); void getPSA(bool urlSupported, MegaRequestListener *listener = NULL); void setPSA(int id, MegaRequestListener *listener = NULL); void disableGfxFeatures(bool disable); bool areGfxFeaturesDisabled(); void changeApiUrl(const char *apiURL, bool disablepkp = false); bool setLanguage(const char* languageCode); int enableSearchDBIndexes(bool enable); int enableLexicographicDBIndexes(bool enable); string generateViewId(); void setLanguagePreference(const char* languageCode, MegaRequestListener *listener = NULL); void getLanguagePreference(MegaRequestListener *listener = NULL); bool getLanguageCode(const char* languageCode, std::string* code); void setFileVersionsOption(bool disable, MegaRequestListener *listener = NULL); void getFileVersionsOption(MegaRequestListener *listener = NULL); void setContactLinksOption(bool enable, MegaRequestListener* listener = NULL); void getContactLinksOption(MegaRequestListener *listener = NULL); void retrySSLerrors(bool enable); void setPublicKeyPinning(bool enable); void pauseActionPackets(); void resumeActionPackets(); static std::functiongetComparatorFunction(int order, MegaClient& mc); static void sortByComparatorFunction(sharedNode_vector&v, int order, MegaClient& mc); static bool nodeNaturalComparatorASC(Node *i, Node *j); static bool nodeNaturalComparatorDESC(Node *i, Node *j); static bool nodeComparatorDefaultASC (Node *i, Node *j); static bool nodeComparatorDefaultDESC (Node *i, Node *j); static bool nodeComparatorSizeASC (Node *i, Node *j); static bool nodeComparatorSizeDESC (Node *i, Node *j); static bool nodeComparatorCreationASC (Node *i, Node *j); static bool nodeComparatorCreationDESC (Node *i, Node *j); static bool nodeComparatorModificationASC (Node *i, Node *j); static bool nodeComparatorModificationDESC (Node *i, Node *j); /*deprecated*/ static bool nodeComparatorPhotoASC(Node *i, Node *j, MegaClient& mc); /*deprecated*/ static bool nodeComparatorPhotoDESC(Node *i, Node *j, MegaClient& mc); /*deprecated*/ static bool nodeComparatorVideoASC(Node *i, Node *j, MegaClient& mc); /*deprecated*/ static bool nodeComparatorVideoDESC(Node *i, Node *j, MegaClient& mc); static bool nodeComparatorPublicLinkCreationASC(Node *i, Node *j); static bool nodeComparatorPublicLinkCreationDESC(Node *i, Node *j); static bool nodeComparatorLabelASC(Node *i, Node *j); static bool nodeComparatorLabelDESC(Node *i, Node *j); static bool nodeComparatorFavASC(Node *i, Node *j); static bool nodeComparatorFavDESC(Node *i, Node *j); static int typeComparator(Node *i, Node *j); static bool userComparatorDefaultASC (User *i, User *j); static m_off_t sizeDifference(Node *i, Node *j); char* escapeFsIncompatible(const char *filename, const char *dstPath); char* unescapeFsIncompatible(const char* name, const char *path); bool createThumbnail(const char* imagePath, const char *dstPath); bool createPreview(const char* imagePath, const char *dstPath); bool createAvatar(const char* imagePath, const char *dstPath); // these two: MEGA proxy use only void getUploadURL(int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener); void completeUpload(const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, const char *string64FileKey, MegaRequestListener *listener); void getFileAttributeUploadURL(MegaHandle nodehandle, int64_t fullFileSize, int faType, bool forceSSL, MegaRequestListener* listener); void backgroundMediaUploadRequestUploadURL(int64_t fullFileSize, MegaBackgroundMediaUpload* state, MegaRequestListener* listener); void backgroundMediaUploadComplete(MegaBackgroundMediaUpload* state, const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, MegaRequestListener *listener); bool ensureMediaInfo(); void setOriginalFingerprint(MegaNode* node, const char* originalFingerprint, MegaRequestListener *listener); bool isOnline(); #ifdef HAVE_LIBUV // start/stop bool httpServerStart(bool localOnly = true, int port = 4443, bool useTLS = false, const char *certificatepath = NULL, const char *keypath = NULL, bool useIPv6 = false); void httpServerStop(); int httpServerIsRunning(); // management char *httpServerGetLocalLink(MegaNode *node); char *httpServerGetLocalWebDavLink(MegaNode *node); MegaStringList *httpServerGetWebDavLinks(); MegaNodeList *httpServerGetWebDavAllowedNodes(); void httpServerRemoveWebDavAllowedNode(MegaHandle handle); void httpServerRemoveWebDavAllowedNodes(); void httpServerSetMaxBufferSize(int bufferSize); int httpServerGetMaxBufferSize(); void httpServerSetMaxOutputSize(int outputSize); int httpServerGetMaxOutputSize(); // permissions void httpServerEnableFileServer(bool enable); bool httpServerIsFileServerEnabled(); void httpServerEnableFolderServer(bool enable); bool httpServerIsFolderServerEnabled(); bool httpServerIsOfflineAttributeEnabled(); void httpServerSetRestrictedMode(int mode); int httpServerGetRestrictedMode(); bool httpServerIsLocalOnly(); void httpServerEnableOfflineAttribute(bool enable); void httpServerEnableSubtitlesSupport(bool enable); bool httpServerIsSubtitlesSupportEnabled(); void httpServerAddListener(MegaTransferListener *listener); void httpServerRemoveListener(MegaTransferListener *listener); void fireOnStreamingStart(MegaTransferPrivate *transfer); void fireOnStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr e); void fireOnStreamingFinish(MegaTransferPrivate *transfer, unique_ptr e); //FTP bool ftpServerStart(bool localOnly = true, int port = 4990, int dataportBegin = 1500, int dataPortEnd = 1600, bool useTLS = false, const char *certificatepath = NULL, const char *keypath = NULL); void ftpServerStop(); int ftpServerIsRunning(); // management char *ftpServerGetLocalLink(MegaNode *node); MegaStringList *ftpServerGetLinks(); MegaNodeList *ftpServerGetAllowedNodes(); void ftpServerRemoveAllowedNode(MegaHandle handle); void ftpServerRemoveAllowedNodes(); void ftpServerSetMaxBufferSize(int bufferSize); int ftpServerGetMaxBufferSize(); void ftpServerSetMaxOutputSize(int outputSize); int ftpServerGetMaxOutputSize(); // permissions void ftpServerSetRestrictedMode(int mode); int ftpServerGetRestrictedMode(); bool ftpServerIsLocalOnly(); void ftpServerAddListener(MegaTransferListener *listener); void ftpServerRemoveListener(MegaTransferListener *listener); void fireOnFtpStreamingStart(MegaTransferPrivate *transfer); void fireOnFtpStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr e); void fireOnFtpStreamingFinish(MegaTransferPrivate *transfer, unique_ptr e); #endif #ifdef ENABLE_CHAT void createChat(bool group, bool publicchat, MegaTextChatPeerList* peers, const MegaStringMap* userKeyMap = NULL, const char* title = NULL, bool meetingRoom = false, int chatOptions = MegaApi::CHAT_OPTIONS_EMPTY, const MegaScheduledMeeting* scheduledMeeting = nullptr, MegaRequestListener* listener = NULL); void setChatOption(MegaHandle chatid, int option, bool enabled, MegaRequestListener* listener = NULL); void inviteToChat(MegaHandle chatid, MegaHandle uh, int privilege, bool openMode, const char *unifiedKey = NULL, const char *title = NULL, MegaRequestListener *listener = NULL); void removeFromChat(MegaHandle chatid, MegaHandle uh = INVALID_HANDLE, MegaRequestListener *listener = NULL); void getUrlChat(MegaHandle chatid, MegaRequestListener *listener = NULL); void grantAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener = NULL); void removeAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener = NULL); void updateChatPermissions(MegaHandle chatid, MegaHandle uh, int privilege, MegaRequestListener *listener = NULL); void truncateChat(MegaHandle chatid, MegaHandle messageid, MegaRequestListener *listener = NULL); void setChatTitle(MegaHandle chatid, const char *title, MegaRequestListener *listener = NULL); void setChatUnifiedKey(MegaHandle chatid, const char *unifiedKey, MegaRequestListener *listener = NULL); void getChatPresenceURL(MegaRequestListener *listener = NULL); void registerPushNotification(int deviceType, const char *token, MegaRequestListener *listener = NULL); void sendChatStats(const char *data, int port, MegaRequestListener *listener = NULL); void sendChatLogs(const char *data, MegaHandle userid, MegaHandle callid = INVALID_HANDLE, int port = 0, MegaRequestListener *listener = NULL); MegaTextChatList *getChatList(); MegaHandleList *getAttachmentAccess(MegaHandle chatid, MegaHandle h); bool hasAccessToAttachment(MegaHandle chatid, MegaHandle h, MegaHandle uh); const char* getFileAttribute(MegaHandle h); void archiveChat(MegaHandle chatid, int archive, MegaRequestListener *listener = NULL); void setChatRetentionTime(MegaHandle chatid, unsigned int period, MegaRequestListener *listener = NULL); void requestRichPreview(const char *url, MegaRequestListener *listener = NULL); void chatLinkHandle(MegaHandle chatid, bool del, bool createifmissing, MegaRequestListener *listener = NULL); void getChatLinkURL(MegaHandle publichandle, MegaRequestListener *listener = NULL); void chatLinkClose(MegaHandle chatid, const char *title, MegaRequestListener *listener = NULL); void chatLinkJoin(MegaHandle publichandle, const char *unifiedkey, MegaRequestListener *listener = NULL); void enableRichPreviews(bool enable, MegaRequestListener *listener = NULL); void isRichPreviewsEnabled(MegaRequestListener *listener = NULL); void shouldShowRichLinkWarning(MegaRequestListener *listener = NULL); void setRichLinkWarningCounterValue(int value, MegaRequestListener *listener = NULL); void enableGeolocation(MegaRequestListener *listener = NULL); void isGeolocationEnabled(MegaRequestListener *listener = NULL); bool isChatNotifiable(MegaHandle chatid); void startChatCall(MegaHandle chatid, bool notRinging, MegaRequestListener* listener = nullptr); void joinChatCall(MegaHandle chatid, MegaHandle callid, MegaRequestListener* listener = nullptr); void endChatCall(MegaHandle chatid, MegaHandle callid, int reason = 0, MegaRequestListener *listener = nullptr); void ringIndividualInACall(MegaHandle chatid, MegaHandle userid, MegaRequestListener* listener = nullptr); void setSFUid(int sfuid); void createOrUpdateScheduledMeeting(const MegaScheduledMeeting* scheduledMeeting, const char* chatTitle, MegaRequestListener* listener = NULL); void removeScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener = NULL); void fetchScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener = NULL); void fetchScheduledMeetingEvents(MegaHandle chatid, MegaTimeStamp since, MegaTimeStamp until, unsigned int count, MegaRequestListener* listener = NULL); #endif void setMyChatFilesFolder(MegaHandle nodehandle, MegaRequestListener *listener = NULL); void getMyChatFilesFolder(MegaRequestListener *listener = NULL); void setCameraUploadsFolder(MegaHandle nodehandle, bool secondary, MegaRequestListener *listener = NULL); void setCameraUploadsFolders(MegaHandle primaryFolder, MegaHandle secondaryFolder, MegaRequestListener *listener); void getCameraUploadsFolder(bool secondary, MegaRequestListener *listener = NULL); void setMyBackupsFolder(const char *localizedName, MegaRequestListener *listener = nullptr); void getUserAlias(MegaHandle uh, MegaRequestListener *listener = NULL); void setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener = NULL); void getPushNotificationSettings(MegaRequestListener *listener = NULL); void setPushNotificationSettings(MegaPushNotificationSettings *settings, MegaRequestListener *listener = NULL); bool isSharesNotifiable(); bool isContactsNotifiable(); void getAccountAchievements(MegaRequestListener *listener = NULL); void getMegaAchievements(MegaRequestListener *listener = NULL); void catchup(MegaRequestListener *listener = NULL); void getPublicLinkInformation(const char *megaFolderLink, MegaRequestListener *listener); void sendSMSVerificationCode(const char* phoneNumber, MegaRequestListener *listener = NULL, bool reverifying_whitelisted = false); void checkSMSVerificationCode(const char* verificationCode, MegaRequestListener *listener = NULL); void getCountryCallingCodes(MegaRequestListener *listener = NULL); void getBanners(MegaRequestListener *listener); void dismissBanner(int id, MegaRequestListener *listener); void setBackup(int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener = nullptr); void updateBackup(MegaHandle backupId, int backupType, MegaHandle targetNode, const char* localFolder, const char *backupName, int state, int subState, MegaRequestListener* listener = nullptr); void removeBackup(MegaHandle backupId, MegaRequestListener *listener = nullptr); void removeFromBC(MegaHandle backupId, MegaHandle moveDestination, MegaRequestListener* listener = nullptr); void pauseFromBC(MegaHandle backupId, MegaRequestListener* listener); void resumeFromBC(MegaHandle backupId, MegaRequestListener* listener); void getBackupInfo(MegaRequestListener* listener = nullptr); void sendBackupHeartbeat(MegaHandle backupId, int status, int progress, int ups, int downs, long long ts, MegaHandle lastNode, MegaRequestListener *listener); void fetchAds(int adFlags, MegaStringList *adUnits, MegaHandle publicHandle, MegaRequestListener *listener = nullptr); void queryAds(int adFlags, MegaHandle publicHandle = INVALID_HANDLE, MegaRequestListener *listener = nullptr); void setCookieSettings(int settings, MegaRequestListener *listener = nullptr); void getCookieSettings(MegaRequestListener *listener = nullptr); bool cookieBannerEnabled(); bool startDriveMonitor(); void stopDriveMonitor(); bool driveMonitorEnabled(); void enableRequestStatusMonitor(bool enable); bool requestStatusMonitorEnabled(); /* MegaVpnCredentials */ void getVpnRegions(MegaRequestListener* listener = nullptr); void getVpnCredentials(MegaRequestListener* listener = nullptr); void putVpnCredential(const char* region, MegaRequestListener* listener = nullptr); void delVpnCredential(int slotID, MegaRequestListener* listener = nullptr); void checkVpnCredential(const char* userPubKey, MegaRequestListener* listener = nullptr); /* MegaVpnCredentials end */ // Password Manager void getPasswordManagerBase(MegaRequestListener *listener = nullptr); bool isPasswordManagerNodeFolder(MegaHandle node) const; void createCreditCardNode(const char* name, const MegaNode::CreditCardNodeData* ccData, const MegaHandle parentHandle, MegaRequestListener* listener = nullptr); void createPasswordNode(const char *name, const MegaNode::PasswordNodeData *data, MegaHandle parent, MegaRequestListener *listener = nullptr); void updateCreditCardNode(MegaHandle node, const MegaNode::CreditCardNodeData* ccData, MegaRequestListener* listener = nullptr); void updatePasswordNode(MegaHandle node, const MegaNode::PasswordNodeData* newData, MegaRequestListener *listener = NULL); void importPasswordsFromFile(const char* filePath, const int fileSource, MegaHandle parent, MegaRequestListener* listener = NULL); void fetchCreditCardInfo(MegaRequestListener* listener = nullptr); void fireOnTransferStart(MegaTransferPrivate *transfer); void fireOnTransferFinish(MegaTransferPrivate *transfer, unique_ptr e); // deletes `transfer` !! void fireOnTransferUpdate(MegaTransferPrivate *transfer); void fireOnFolderTransferUpdate(MegaTransferPrivate *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const LocalPath* currentFolder, const LocalPath* currentFileLeafname); void fireOnTransferTemporaryError(MegaTransferPrivate *transfer, unique_ptr e); map transferMap; MegaClient *getMegaClient(); static FileFingerprint *getFileFingerprintInternal(const char *fingerprint); error processAbortBackupRequest(MegaRequestPrivate *request); void fireOnBackupStateChanged(MegaScheduledCopyController *backup); void fireOnBackupStart(MegaScheduledCopyController *backup); void fireOnBackupFinish(MegaScheduledCopyController *backup, unique_ptr e); void fireOnBackupUpdate(MegaScheduledCopyController *backup); void fireOnBackupTemporaryError(MegaScheduledCopyController *backup, unique_ptr e); void yield(); void lockMutex(); void unlockMutex(); bool tryLockMutexFor(long long time); void getVisibleWelcomeDialog(MegaRequestListener* listener); void setVisibleWelcomeDialog(bool visible, MegaRequestListener* listener); void createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, const char* customerIpPort, MegaRequestListener* listener); void getVisibleTermsOfService(MegaRequestListener* listener = nullptr); void setVisibleTermsOfService(bool visible, MegaRequestListener* listener = nullptr); MegaIntegerList* getEnabledNotifications() const; void enableTestNotifications(const MegaIntegerList* notificationIds, MegaRequestListener* listener); void getNotifications(MegaRequestListener* listener); void setLastReadNotification(uint32_t notificationId, MegaRequestListener* listener); void getLastReadNotification(MegaRequestListener* listener); void setLastActionedBanner(uint32_t notificationId, MegaRequestListener* listener); void getLastActionedBanner(MegaRequestListener* listener); MegaFlagPrivate* getFlag(const char* flagName, bool commit, MegaRequestListener* listener = nullptr); void deleteUserAttribute(int type, MegaRequestListener* listener = NULL); void getActiveSurveyTriggerActions(MegaRequestListener* listener = NULL); void getSurvey(unsigned int triggerActionId, MegaRequestListener* listener = NULL); void enableTestSurveys(const MegaHandleList* surveyHandles, MegaRequestListener* listener = NULL); void answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const char* response, const char* comment, MegaRequestListener* listener); void getMyIp(MegaRequestListener* listener); void runNetworkConnectivityTest(MegaRequestListener* listener); void getSubscriptionCancellationDetails(const char* originalTransactionId, unsigned int gatewayId, MegaRequestListener* listener); void getDiscountCodeInformation(const char* discountCode, MegaRequestListener* listener); private: void init(MegaApi* publicApi, std::unique_ptr gfxproc, const char* newBasePath /*= NULL*/, const char* userAgent /*= NULL*/, unsigned clientWorkerThreadCount /*= 1*/, int clientType); static void *threadEntryPoint(void *param); MegaTransferPrivate* getMegaTransferPrivate(int tag); void fireOnRequestStart(MegaRequestPrivate *request); void fireOnRequestFinish(MegaRequestPrivate *request, unique_ptr e, bool callbackIsFromSyncThread = false); void fireOnRequestUpdate(MegaRequestPrivate *request); void fireOnRequestTemporaryError(MegaRequestPrivate *request, unique_ptr e); bool fireOnTransferData(MegaTransferPrivate *transfer); void fireOnUsersUpdate(MegaUserList *users); void fireOnUserAlertsUpdate(MegaUserAlertList *alerts); void fireOnNodesUpdate(MegaNodeList *nodes); void fireOnAccountUpdate(); void fireOnSetsUpdate(MegaSetList* sets); void fireOnSetElementsUpdate(MegaSetElementList* elements); void fireOnContactRequestsUpdate(MegaContactRequestList *requests); void fireOnEvent(MegaEventPrivate *event); #ifdef ENABLE_SYNC void fireOnGlobalSyncStateChanged(); void fireOnSyncStateChanged(MegaSyncPrivate *sync); void fireOnSyncStatsUpdated(MegaSyncStatsPrivate*); void fireOnSyncAdded(MegaSyncPrivate *sync); void fireOnSyncDeleted(MegaSyncPrivate *sync); void fireOnFileSyncStateChanged(MegaSyncPrivate *sync, string *localPath, int newState); void fireOnSyncRemoteRootChanged(MegaSyncPrivate* sync); #endif #ifdef ENABLE_CHAT void fireOnChatsUpdate(MegaTextChatList *chats); #endif void processTransferPrepare(Transfer *t, MegaTransferPrivate *transfer); void processTransferUpdate(Transfer *tr, MegaTransferPrivate *transfer); void processTransferComplete(Transfer *tr, MegaTransferPrivate *transfer); void processTransferFailed(Transfer *tr, MegaTransferPrivate *transfer, const Error &e, dstime timeleft); void processTransferRemoved(Transfer *tr, MegaTransferPrivate *transfer, const Error &e); bool isValidTypeNode(const Node *node, int type) const; MegaApi *api; std::thread thread; std::thread::id threadId; MegaClient *client; MegaHttpIO *httpio; shared_ptr waiter; unique_ptr fsAccess; MegaDbAccess *dbAccess; GfxProc *gfxAccess; string basePath; bool nocache; // for fingerprinting off-thread // one at a time is enough mutex fingerprintingFsAccessMutex; unique_ptr fingerprintingFsAccess; mutex mLastRecievedLoggedMeMutex; sessiontype_t mLastReceivedLoggedInState = NOTLOGGEDIN; handle mLastReceivedLoggedInMeHandle = UNDEF; string mLastReceivedLoggedInMyEmail; unique_ptr mLastKnownRootNode; unique_ptr mLastKnownVaultNode; unique_ptr mLastKnownRubbishNode; #ifdef HAVE_LIBUV MegaHTTPServer *httpServer; int httpServerMaxBufferSize; int httpServerMaxOutputSize; bool httpServerEnableFiles; bool httpServerEnableFolders; bool httpServerOfflineAttributeEnabled; int httpServerRestrictedMode; bool httpServerSubtitlesSupportEnabled; set httpServerListeners; MegaFTPServer *ftpServer; int ftpServerMaxBufferSize; int ftpServerMaxOutputSize; int ftpServerRestrictedMode; set ftpServerListeners; #endif map backupsMap; RequestQueue requestQueue; TransferQueue transferQueue; map requestMap; // sc requests to close existing wsc and immediately retrieve pending actionpackets RequestQueue scRequestQueue; long long notificationNumber; set requestListeners; set transferListeners; set backupListeners; #ifdef ENABLE_SYNC std::unique_ptr mHeartBeatMonitor; MegaSyncPrivate* cachedMegaSyncPrivateByBackupId(const SyncConfig&); unique_ptr mCachedMegaSyncPrivate; #endif set globalListeners; set listeners; retryreason_t waitingRequest; mutable std::recursive_timed_mutex sdkMutex; using SdkMutexGuard = std::unique_lock; // (equivalent to typedef) MegaTransferPrivate* currentTransfer; std::unique_ptr getMegaPushNotificationSetting(); // returns lastest-seen settings (to be able to filter notifications) MegaTimeZoneDetails *mTimezones; std::atomic syncPathStateLockTimeout{ false }; set syncPathStateDeferredSet; mutex syncPathStateDeferredSetMutex; // Track latest call to client->abortbackoff to avoid spamming. dstime latestAbortBackoffs{0}; int threadExit; void loop(); int maxRetries; // a request-level error occurred void request_error(error) override; void request_response_progress(m_off_t, m_off_t) override; // login result void prelogin_result(int, string*, string*, error) override; void login_result(error) override; void logout_result(error, MegaRequestPrivate*); void userdata_result(string*, string*, string*, std::vector&&, Error) override; void pubkey_result(User *) override; // ephemeral session creation/resumption result // check the reason of being blocked void ephemeral_result(error) override; void ephemeral_result(handle, const byte*) override; void cancelsignup_result(error) override; // check the reason of being blocked void whyamiblocked_result(int) override; // contact link management void contactlinkcreate_result(error, handle) override; void contactlinkquery_result(error, handle, string*, string*, string*, string*) override; void contactlinkdelete_result(error) override; // multi-factor authentication void multifactorauthsetup_result(string*, error) override; void multifactorauthcheck_result(int) override; void multifactorauthdisable_result(error) override; // fetch time zone void fetchtimezone_result(error, vector*, vector*, int) override; // keep me alive feature void keepmealive_result(error) override; void acknowledgeuseralerts_result(error) override; // account validation by txted verification code void smsverificationsend_result(error) override; void smsverificationcheck_result(error, std::string *phoneNumber) override; // get country calling codes void getcountrycallingcodes_result(error, map>*) override; // get the current PSA void getpsa_result (error, int, string*, string*, string*, string*, string*, string*) override; // account creation void sendsignuplink_result(error) override; void confirmsignuplink2_result(handle, const char*, const char*, error) override; void setkeypair_result(error) override; // account credentials, properties and history void account_details(AccountDetails*, bool, bool, bool, bool, bool, bool) override; void account_details(AccountDetails*, error) override; void querytransferquota_result(int) override; void unlink_result(handle, error) override; void unlinkversions_result(error) override; void nodes_updated(sharedNode_vector* nodes, int) override; void users_updated(User**, int) override; void useralerts_updated(UserAlert::Base**, int) override; void account_updated() override; void pcrs_updated(PendingContactRequest**, int) override; void sequencetag_update(const string&) override; void sets_updated(Set**, int) override; void setelements_updated(SetElement**, int) override; // password change result void changepw_result(error) override; // user attribute update notification void userattr_update(User*, int, const char*) override; void nodes_current() override; void catchup_result() override; void key_modified(handle, attr_t) override; void upgrading_security() override; void downgrade_attack() override; void fetchnodes_result(const Error&) override; void putnodes_result(const Error&, targettype_t, vector&, bool targetOverride, int tag, const std::map& fileHandles = {}) override; // contact request results void setpcr_result(handle, error, opcactions_t) override; void updatepcr_result(error, ipcactions_t) override; // file attribute fetch result void fa_complete(handle, fatype, const char*, uint32_t) override; int fa_failed(handle, fatype, int, error) override; // file attribute modification result void putfa_result(handle, fatype, error) override; #ifdef USE_DRIVE_NOTIFICATIONS // external drive [dis-]connected void drive_presence_changed(bool appeared, const LocalPath& driveRoot) override; #endif // purchase transactions void enumeratequotaitems_result(const Product& product) override; void enumeratequotaitems_result(unique_ptr) override; void enumeratequotaitems_result(error e) override; void additem_result(error) override; void checkout_result(const char*, error) override; void submitpurchasereceipt_result(error) override; void creditcardstore_result(error) override; void creditcardquerysubscriptions_result(int, error) override; void creditcardcancelsubscriptions_result(error) override; void getpaymentmethods_result(int, error) override; void copysession_result(string*, error) override; void userfeedbackstore_result(error) override; void sendevent_result(error) override; void supportticket_result(error) override; // user invites/attributes void removecontact_result(error) override; #ifdef DEBUG void delua_result(error) override; #endif void senddevcommand_result(int) override; void getuseremail_result(string *, error) override; // exported link access result void openfilelink_result(const Error&) override; void openfilelink_result(handle, const byte*, m_off_t, string*, string*, int) override; // retrieval of public link information void folderlinkinfo_result(error, handle, handle, string *, string*, m_off_t, uint32_t, uint32_t, m_off_t, uint32_t) override; // global transfer queue updates (separate signaling towards the queued objects) void file_added(File*) override; void file_removed(File*, const Error& e) override; void file_complete(File*) override; void transfer_complete(Transfer *) override; void transfer_removed(Transfer *) override; void file_resume(string* d, direction_t* type, uint32_t dbid, FileResumeData& data) override; void transfer_prepare(Transfer*) override; void transfer_failed(Transfer*, const Error& error, dstime timeleft) override; void transfer_update(Transfer*) override; dstime pread_failure(const Error&, int, void*, dstime) override; bool pread_data(byte*, m_off_t, m_off_t, m_off_t, m_off_t, void*) override; void reportevent_result(error) override; void sessions_killed(handle sessionid, error e) override; void cleanrubbishbin_result(error) override; void getrecoverylink_result(error) override; void queryrecoverylink_result(error) override; void queryrecoverylink_result(int type, const char *email, const char *ip, time_t ts, handle uh, const vector *emails) override; void getprivatekey_result(error, const byte *privk = NULL, const size_t len_privk = 0) override; void confirmrecoverylink_result(error) override; void confirmcancellink_result(error) override; void getemaillink_result(error) override; void resendverificationemail_result(error) override; void resetSmsVerifiedPhoneNumber_result(error) override; void confirmemaillink_result(error) override; void getversion_result(int, const char*, error) override; void getlocalsslcertificate_result(m_time_t, string *certdata, error) override; void getmegaachievements_result(AchievementsDetails*, error) override; void mediadetection_ready() override; void storagesum_changed(int64_t newsum) override; void getmiscflags_result(error) override; void getbanners_result(error e) override; void getbanners_result(std::vector&& banners) override; void dismissbanner_result(error e) override; void reqstat_progress(int permilprogress) override; // for internal use - for worker threads to run something on MegaApiImpl's thread, such as calls to onFire() functions void executeOnThread(shared_ptr); #ifdef ENABLE_CHAT // chat-related commandsresult void chatcreate_result(TextChat *, error) override; void chatinvite_result(error) override; void chatremove_result(error) override; void chaturl_result(string*, error) override; void chatgrantaccess_result(error) override; void chatremoveaccess_result(error) override; void chatupdatepermissions_result(error) override; void chattruncate_result(error) override; void chatsettitle_result(error) override; void chatpresenceurl_result(string*, error) override; void registerpushnotification_result(error) override; void archivechat_result(error) override; void setchatretentiontime_result(error) override; void chats_updated(textchat_map *, int) override; void richlinkrequest_result(string*, error) override; void chatlink_result(handle, error) override; void chatlinkurl_result(handle, int, string*, string*, int, m_time_t, bool, int, const std::vector>*, handle, error) override; void chatlinkclose_result(error) override; void chatlinkjoin_result(error) override; #endif #ifdef ENABLE_SYNC // sync status updates and events // calls fireOnSyncStateChanged void syncupdate_stateconfig(const SyncConfig& config) override; void syncupdate_stats(handle backupId, const PerSyncStats& stats) override; // this will fill syncMap with a new MegaSyncPrivate, and fire onSyncAdded void sync_added(const SyncConfig& config) override; // this will fire onSyncStateChange if remote path of the synced node has changed virtual void syncupdate_remote_root_changed(const SyncConfig &) override; // this will call will fire EVENT_SYNCS_RESTORED virtual void syncs_restored(SyncError syncError) override; // this will call will fire EVENT_SYNCS_DISABLED virtual void syncs_disabled(SyncError syncError) override; // removes the sync from syncMap and fires onSyncDeleted callback void sync_removed(const SyncConfig& config) override; void syncupdate_syncing(bool syncing) override; void syncupdate_scanning(bool scanning) override; void syncupdate_stalled(bool stalled) override; void syncupdate_conflicts(bool conflicts) override; void syncupdate_totalstalls(bool totalstalls) override; void syncupdate_totalconflicts(bool totalconflicts) override; void syncupdate_treestate(const SyncConfig &, const LocalPath&, treestate_t, nodetype_t) override; // for the exclusive use of sync_syncable unique_ptr mSyncable_fa; std::mutex mSyncable_fa_mutex; #endif void backupput_result(const Error&, handle backupId) override; // Notify sdk errors (DB, node serialization, ...) to apps void notifyError(const char*, ErrorReason errorReason) override; // reload forced automatically by server void reloading() override; // wipe all users, nodes and shares void clearing() override; // failed request retry notification void notify_retry(dstime, retryreason_t) override; // notify about db commit void notify_dbcommit() override; // notify about a storage event void notify_storage(int) override; // notify about account confirmation void notify_confirmation(const char*) override; // notify about account confirmation after signup link -> user, email have been confirmed void notify_confirm_user_email(handle /*user*/, const char* /*email*/) override; // network layer disconnected void notify_disconnect() override; // notify about a finished HTTP request void http_result(error, int, byte*, m_off_t) override; // notify about a business account status change void notify_business_status(BizStatus status) override; // notify about a finished timer void timer_result(error) override; // notify credit card Expiry void notify_creditCardExpiry() override; void sendPendingScRequest(); void sendPendingRequests(); unsigned sendPendingTransfers(TransferQueue *queue, MegaRecursiveOperation* = nullptr, m_off_t availableDiskSpace = 0); void updateBackups(); bool updateNodeMtime(std::shared_ptr node, MegaTransferPrivate* transfer, const m_time_t newMtime, const int nextTag); MegaFilePut* createMegaFileForRemoteCopyTransfer(MegaTransferPrivate& megaTransfer, std::shared_ptr prevNodeSameName, TransferDbCommitter& committer); void notify_network_activity(int networkActivityChannel, int networkActivityType, int code) override; //Internal std::shared_ptr getNodeByFingerprintInternal(const char *fingerprint); std::shared_ptr getNodeByFingerprintInternal(const char *fingerprint, Node *parent); void getNodeAttribute(std::variant nodeOrHandle, int type, const char* dstFilePath, MegaRequestListener* listener); void cancelGetNodeAttribute(MegaNode *node, int type, MegaRequestListener *listener = NULL); void setNodeAttribute(MegaNode* node, int type, const char *srcFilePath, MegaHandle attributehandle, MegaRequestListener *listener = NULL); void putNodeAttribute(MegaBackgroundMediaUpload* bu, int type, const char *srcFilePath, MegaRequestListener *listener = NULL); void setUserAttr(int type, const char *value, MegaRequestListener *listener = NULL); void setUserAttr(int type, const MegaStringMap* value, MegaRequestListener* listener = nullptr); void getUserAttr(User* user, attr_t type, MegaRequestPrivate* request); void getUserAttr(const std::string& email, attr_t type, const char* ph, MegaRequestPrivate* request); void getua_completion(error, MegaRequestPrivate* request); void getua_completion(byte*, unsigned, attr_t, MegaRequestPrivate* request); void getua_completion(unique_ptr, attr_t, MegaRequestPrivate* request); static char *getAvatarColor(handle userhandle); static char *getAvatarSecondaryColor(handle userhandle); bool isGlobalNotifiable(MegaPushNotificationSettingsPrivate* pushSettings); // return false if there's a schedule and it currently does not apply. Otherwise, true bool isScheduleNotifiable(MegaPushNotificationSettingsPrivate* pushSettings); // deletes backups, requests and transfers. Reset total stats for down/uploads void abortPendingActions(error preverror = API_OK); bool hasToForceUpload(const Node &node, const MegaTransferPrivate &transfer) const; void exportSet(MegaHandle sid, bool create, MegaRequestListener* listener = nullptr); // Password Manager - private void createPasswordManagerBase(MegaRequestPrivate*); std::unique_ptr toAttrMapCreditCard(const MegaNode::CreditCardNodeData* data) const; std::unique_ptr toAttrMapPassword(const MegaNode::PasswordNodeData* data) const; friend class MegaBackgroundMediaUploadPrivate; friend class MegaFolderDownloadController; friend class MegaFolderUploadController; friend class MegaRecursiveOperation; void setCookieSettings_sendPendingRequests(MegaRequestPrivate* request); error getCookieSettings_getua_result(byte* data, unsigned len, MegaRequestPrivate* request); error performRequest_backupPut(MegaRequestPrivate* request); error performRequest_verifyCredentials(MegaRequestPrivate* request); error performRequest_completeBackgroundUpload(MegaRequestPrivate* request); error performRequest_getBackgroundUploadURL(MegaRequestPrivate* request); error performRequest_getAchievements(MegaRequestPrivate* request); #ifdef ENABLE_CHAT error performRequest_chatStats(MegaRequestPrivate* request); #endif error performRequest_getUserData(MegaRequestPrivate* request); error performRequest_enumeratequotaitems(MegaRequestPrivate* request); error performRequest_getChangeEmailLink(MegaRequestPrivate* request); error performRequest_getCancelLink(MegaRequestPrivate* request); error performRequest_confirmAccount(MegaRequestPrivate* request); error performRequest_sendSignupLink(MegaRequestPrivate* request); error performRequest_createAccount(MegaRequestPrivate* request); error performRequest_retryPendingConnections(MegaRequestPrivate* request); error performRequest_setAttrNode(MegaRequestPrivate* request); error performRequest_setAttrFile(MegaRequestPrivate* request); error performRequest_setAttrUser(MegaRequestPrivate* request); error performRequest_getAttrUser(MegaRequestPrivate* request); error performRequest_logout(MegaRequestPrivate* request); error performRequest_changePw(MegaRequestPrivate* request); error performRequest_export(MegaRequestPrivate* request); error performRequest_passwordLink(MegaRequestPrivate* request); error performRequest_importLink_getPublicNode(MegaRequestPrivate* request); error performRequest_copy(MegaRequestPrivate* request); error copyTreeFromOwnedNode( shared_ptr node, const char* newName, shared_ptr target, vector& treeCopy, const std::optional& s4AttributeValue = std::nullopt); error performRequest_login(MegaRequestPrivate* request); error performRequest_tagNode(MegaRequestPrivate* request); void CRUDNodeTagOperation(MegaNode* node, int operationType, const char* tag, const char* oldTag, MegaRequestListener* listener); error performTransferRequest_cancelTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer); error performTransferRequest_moveTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer); void multiFactorAuthEnableOrDisable(const char* pin, bool enable, MegaRequestListener* listener); #ifdef ENABLE_SYNC /** * @brief A sync folder request completion function that should call one the specific * completeRequest methods. * * @see syncFolder() * @see prevalidateSyncFolder() */ using SyncFolderRequestCompletion = std::function< void(MegaRequestPrivate* const, SyncConfig&&, MegaClient::UndoFunction&&)>; /** * @brief Creates and enqueues a MegaRequestPrivate of the given requestType and populates * its fields with the MegaRequestSyncFolderParams data. * * @param megaRequestType The related request type: MegaRequest::TYPE_ADD_SYNC or * MegaRequest::TYPE_ADD_SYNC_PREVALIDATION. * @param params The MegaRequestSyncFolderParams passed from the public request method. * @param completion The SyncFolderRequestCompletion function passed from the public request * method. * * @see syncFolder() * @see prevalidateSyncFolder() */ void addRequest_syncFolder(const int megaRequestType, MegaRequestSyncFolderParams&& params, MegaRequestListener* const listener, SyncFolderRequestCompletion&& completion); /** * @brief Prepares the sync configuration using the related request fields and invokes the * completion function. * * If it is a backup it needs to be prepared by calling the corresponding client method. * This typically includes creating the deviceName if does not exist yet, as well as the * remote node used as root for the backup folder. * * @param completion The SyncFolderRequestCompletion function passed from the addRequest * step. */ error performRequest_syncFolder(MegaRequestPrivate* const request, SyncFolderRequestCompletion&& completion); /** * @brief Calls the related client method to add a new sync and finishes the request. * * @param syncConfig The initial sync config which should have been created when performing * the request. * @param revertForBackup An undo function to delete the remote backup root node in case it * was created but there was an error when adding the new backup. */ void completeRequest_syncFolder_AddSync(MegaRequestPrivate* const request, SyncConfig&& syncConfig, MegaClient::UndoFunction&& revertOnError); /** * @brief Calls the related client method to prevalidate a sync addition and finishes the * request. * * @param syncConfig The initial sync config which should have been created when performing * the request. * @param revertForBackup An undo function to delete the remote backup root node, if it was * created during the request for prevalidating a new backup. */ void completeRequest_syncFolder_PrevalidateAddSync( MegaRequestPrivate* const request, SyncConfig&& syncConfig, MegaClient::UndoFunction&& revertForBackup); #endif void CompleteFileDownloadBySkip(MegaTransferPrivate* transfer, m_off_t size, uint64_t nodehandle, int nextTag, const LocalPath& localPath); void performRequest_enableTestNotifications(MegaRequestPrivate* request); error performRequest_getNotifications(MegaRequestPrivate * request); void performRequest_setLastReadNotification(MegaRequestPrivate* request); error getLastReadNotification_getua_result(byte* data, unsigned len, MegaRequestPrivate* request); void performRequest_setLastActionedBanner(MegaRequestPrivate* request); error getLastActionedBanner_getua_result(byte* data, unsigned len, MegaRequestPrivate* request); void performRequest_enableTestSurveys(MegaRequestPrivate* request); error performRequest_getSyncStalls(MegaRequestPrivate* request); }; class MegaHashSignatureImpl { public: MegaHashSignatureImpl(const char *base64Key); ~MegaHashSignatureImpl(); void init(); void add(const char *data, unsigned size); bool checkSignature(const char *base64Signature); protected: HashSignature *hashSignature; AsymmCipher* asymmCypher; }; class ExternalInputStream : public InputStreamAccess { MegaInputStream *inputStream; public: ExternalInputStream(MegaInputStream *inputStream); m_off_t size() override; bool read(byte *buffer, unsigned size) override; }; #ifdef HAVE_LIBUV class StreamingBuffer { public: StreamingBuffer(const std::string& logName = {}); ~StreamingBuffer(); // Allocate buffer and reset class members void init(size_t newCapacity); // Reset positions for body writting ("forgets" buffered external data such as headers, which use the same buffer) [Default: 0 -> the whole buffer] void reset(bool freeData, size_t sizeToReset = 0); // Add data to the buffer. This will mainly come from the Transfer (or from a cache file if it's included someday). size_t append(const char *buf, size_t len); // Get buffered data size size_t availableData() const; // Get free space available in buffer size_t availableSpace() const; // Get total buffer capacity size_t availableCapacity() const; // Get the uv_buf_t for the consumer with as much buffered data as possible uv_buf_t nextBuffer(); // Increase the free data counter void freeData(size_t len); // Set upper bound limit for capacity void setMaxBufferSize(unsigned int bufferSize); // Set upper bound limit for chunk size to write to the consumer void setMaxOutputSize(unsigned int outputSize); // Set file size void setFileSize(m_off_t newFileSize); // Set media length in seconds void setDuration(int newDuration); // Rate between file size and its duration (only for media files) m_off_t getBytesPerSecond() const; // Get upper bound limit for capacity unsigned getMaxBufferSize(); // Get upper bound limit for chunk size to write to the consumer unsigned getMaxOutputSize(); // Get the actual buffer state for debugging purposes std::string bufferStatus() const; const std::string& getLogName() const { return logname; } static const unsigned int MAX_BUFFER_SIZE = 2097152; static const unsigned int MAX_OUTPUT_SIZE = MAX_BUFFER_SIZE / 10; private: // Rate between partial file size and its duration (only for media files) m_off_t partialDuration(m_off_t partialSize) const; // Recalculate maxBufferSize and maxOutputSize taking into accout the byteRate (for media files) and DirectReadSlot read chunk size. void calcMaxBufferAndMaxOutputSize(); std::string logname{}; protected: // Circular buffer to store data to feed the consumer char* buffer; // Total buffer size size_t capacity; // Buffered data size size_t size; // Available free space in buffer size_t free; // Index for last buffered data size_t inpos; // Index for last written data (to the consumer) size_t outpos; // Upper bound limit for capacity size_t maxBufferSize; // Upper bound limit for chunk size to write to the consumer size_t maxOutputSize; // File size m_off_t fileSize; // Media length in seconds (for media files) int duration; }; class MegaTCPContext : public MegaTransferListener, public MegaRequestListener { public: MegaTCPContext(); virtual ~MegaTCPContext(); // Connection management MegaTCPServer *server; // Client tcp connection handle uv_tcp_t tcphandle; // Async handle to perform writes uv_async_t asynchandle; uv_mutex_t mutex; MegaApiImpl *megaApi; m_off_t bytesWritten; m_off_t size; bool finished; #ifdef ENABLE_EVT_TLS //tls stuff: evt_tls_t *evt_tls; bool invalid; #endif std::list writePointers; }; class MegaTCPServer { protected: static void *threadEntryPoint(void *param); static http_parser_settings parsercfg; uv_loop_t uv_loop; set allowedHandles; handle lastHandle; list connections; uv_async_t exit_handle; MegaApiImpl *megaApi; bool semaphoresdestroyed; uv_sem_t semaphoreStartup; uv_sem_t semaphoreEnd; MegaThread *thread; uv_tcp_t server; int maxBufferSize; int maxOutputSize; int restrictedMode; bool localOnly; std::atomic_bool started; int port; bool closing; int remainingcloseevents; #ifdef ENABLE_EVT_TLS // TLS bool evtrequirescleaning; evt_ctx_t evtctx; std::string certificatepath; std::string keypath; #endif // libuv callbacks static void onNewClient(uv_stream_t* server_handle, int status); static void onDataReceived(uv_stream_t* tcp, ssize_t nread, const uv_buf_t * buf); static void allocBuffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t* buf); static void onClose(uv_handle_t* handle); #ifdef ENABLE_EVT_TLS //libuv tls static void onNewClient_tls(uv_stream_t* server_handle, int status); static void onWriteFinished_tls_async(uv_write_t* req, int status); static void on_tcp_read(uv_stream_t *stream, ssize_t nrd, const uv_buf_t *data); static int uv_tls_writer(evt_tls_t *evt_tls, void *bfr, int sz); static void on_evt_tls_close(evt_tls_t *evt_tls, int status); static void on_hd_complete( evt_tls_t *evt_tls, int status); static void evt_on_rd(evt_tls_t *evt_tls, char *bfr, int sz); #endif static void onAsyncEventClose(uv_handle_t* handle); static void onAsyncEvent(uv_async_t* handle); static void onExitHandleClose(uv_handle_t* handle); static void onCloseRequested(uv_async_t* handle); static void onWriteFinished(uv_write_t* req, int status); //This might need to go to HTTPServer #ifdef ENABLE_EVT_TLS static void onWriteFinished_tls(evt_tls_t *evt_tls, int status); #endif static void closeConnection(MegaTCPContext *tcpctx); static void closeTCPConnection(MegaTCPContext *tcpctx); void run(); void answer(MegaTCPContext* tcpctx, const char *rsp, size_t rlen); /** * @brief Process received data from a TCP connection. * * Called internally by libuv when data is received on a TCP connection. * Implementations should handle parsing and processing of the incoming data. * * @param tcpctx Pointer to the TCP context containing connection state and buffers * @param nread Number of bytes read. Negative values indicate errors: * - UV_EOF: End of stream * - Other negative values: libuv error codes * @param buf Buffer containing the received data (may be NULL if nread <= 0) * * @note This method is called from the libuv event loop thread * @see uv_read_start documentation for error handling details */ virtual void processReceivedData(MegaTCPContext* tcpctx, ssize_t nread, const uv_buf_t* buf); /** * @brief Process asynchronous write requests for a TCP connection. * * Called when the MegaTCPContext's async handle is triggered, typically * to initiate write operations or handle pending data transmission. * * @param tcpctx Pointer to the TCP context that triggered the async event * * @note This method runs in the libuv event loop thread * @note Implementations should check tcpctx->writePointers for pending data */ virtual void processAsyncEvent(MegaTCPContext* tcpctx); /** * @brief Allocate and initialize a new TCP context for an incoming connection. * * Called when a new client connects to the server. The implementation should * create an appropriate MegaTCPContext subclass and initialize it for the * specific server type (HTTP, FTP, etc.). * * @param server_handle libuv stream handle for the server socket that accepted the connection * @return Pointer to newly allocated MegaTCPContext, or NULL if allocation fails * * @note The returned context will be managed by the TCP server * @note Caller is responsible for proper initialization of the context */ virtual MegaTCPContext* initializeContext(uv_stream_t* server_handle) = 0; /** * @brief Handle completion of a write operation. * * Called as the libuv callback when a write operation completes (successfully or with error). * Implementations should handle cleanup, error reporting, and continuation of data * transmission. * * @param tcpctx Pointer to the TCP context that completed the write * @param status Write operation status: * - 0: Success * - Negative: libuv error code indicating failure * * @note This method runs in the libuv event loop thread * @note Implementations should check for connection closure on error */ virtual void processWriteFinished(MegaTCPContext* tcpctx, int status) = 0; /** * @brief Handle closure of the TCP context's async handle. * * Called when the MegaTCPContext's async handle is being closed as part * of connection cleanup. Used for final resource deallocation. * * @param tcpctx Pointer to the TCP context whose async handle is closing * * @note This is part of the connection teardown sequence * @note The context should not be used for async operations after this call */ virtual void processOnAsyncEventClose(MegaTCPContext* tcpctx); /** * @brief Determine if the server should start reading from a new connection. * * Called immediately after a new connection is accepted and initialized. * Some server types may need to send a response before reading (e.g., FTP servers * send a welcome message), while others start by reading client requests. * * @param tcpctx Pointer to the newly accepted TCP context * @return true if the server should immediately start reading from the connection * false if the server will handle the initial protocol exchange differently * * @note For HTTP servers, this typically returns true to read the HTTP request * @note For FTP servers, this typically returns false to send welcome message first */ virtual bool respondNewConnection(MegaTCPContext* tcpctx) = 0; /** * @brief Handle closure of the TCP server's exit handle. * * Called when the server is shutting down and its exit handle has been closed. * Used for final server cleanup and resource deallocation. * * @param tcpServer Pointer to the TCP server that is shutting down * * @note This is the final callback in the server shutdown sequence * @note Server should not accept new connections after this point */ virtual void processOnExitHandleClose(MegaTCPServer* tcpServer); public: const bool useIPv6; const bool useTLS; std::unique_ptr fsAccess; std::string basePath; MegaTCPServer(MegaApiImpl *megaApi, std::string basePath, bool useTLS = false, std::string certificatepath = std::string(), std::string keypath = std::string(), bool useIPv6 = false); virtual ~MegaTCPServer(); bool start(int newPort, bool newLocalOnly = true); void stop(bool doNotWait = false); int getPort(); bool isLocalOnly(); void setMaxBufferSize(int bufferSize); void setMaxOutputSize(int outputSize); int getMaxBufferSize(); int getMaxOutputSize(); void setRestrictedMode(int mode); int getRestrictedMode(); bool isHandleAllowed(handle h); void clearAllowedHandles(); char* getLink(MegaNode *node, std::string protocol = "http"); bool isCurrentThread() { return thread->isCurrentThread(); } set getAllowedHandles(); void removeAllowedHandle(MegaHandle handle); void readData(MegaTCPContext* tcpctx); }; class MegaHTTPContext : public MegaTCPContext { private: static std::atomic_uint32_t nextId; const uint32_t contextId; std::string logname; public: MegaHTTPContext(); ~MegaHTTPContext(); // Connection management StreamingBuffer streamingBuffer; std::unique_ptr transfer; http_parser parser; char *lastBuffer; size_t lastBufferLen; bool nodereceived; bool failed; bool pause; // Request information bool range; m_off_t rangeStart; m_off_t rangeEnd; m_off_t rangeWritten; MegaNode *node; std::string path; std::string nodehandle; std::string nodekey; std::string nodename; m_off_t nodesize; std::string nodepubauth; std::string nodeprivauth; std::string nodechatauth; int resultCode; // WEBDAV related int depth; std::string lastheader; std::string subpathrelative; const char *messageBody; size_t messageBodySize; std::string host; std::string destination; bool overwrite; std::unique_ptr tmpFileAccess; std::string tmpFileName; std::string newname; //newname for moved node MegaHandle nodeToMove; //node to be moved after delete MegaHandle newParentNode; //parent node for moved after delete uv_mutex_t mutex_responses; std::list responses; virtual void onTransferStart(MegaApi*, MegaTransfer* httpTransfer); virtual bool onTransferData(MegaApi*, MegaTransfer* httpTransfer, char* buffer, size_t dataSize); virtual void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError *e); virtual void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError *e); const std::string& getLogName() const { return logname; } }; class MegaHTTPServer: public MegaTCPServer { protected: set allowedWebDavHandles; bool fileServerEnabled; bool folderServerEnabled; bool offlineAttribute; bool subtitlesSupportEnabled; //virtual methods: virtual void processReceivedData(MegaTCPContext *ftpctx, ssize_t nread, const uv_buf_t * buf); virtual void processAsyncEvent(MegaTCPContext *ftpctx); virtual MegaTCPContext * initializeContext(uv_stream_t *server_handle); virtual void processWriteFinished(MegaTCPContext* tcpctx, int status); virtual void processOnAsyncEventClose(MegaTCPContext* tcpctx); virtual bool respondNewConnection(MegaTCPContext* tcpctx); virtual void processOnExitHandleClose(MegaTCPServer* tcpServer); // HTTP parser callback static int onMessageBegin(http_parser* parser); static int onHeadersComplete(http_parser* parser); static int onUrlReceived(http_parser* parser, const char* url, size_t length); static int onHeaderField(http_parser* parser, const char* at, size_t length); static int onHeaderValue(http_parser* parser, const char* at, size_t length); static int onBody(http_parser* parser, const char* at, size_t length); static int onMessageComplete(http_parser* parser); static void sendHeaders(MegaHTTPContext *httpctx, string *headers); static void sendNextBytes(MegaHTTPContext *httpctx); static int streamNode(MegaHTTPContext *httpctx); //Utility funcitons static std::string getHTTPMethodName(int httpmethod); static std::string getHTTPErrorString(int errorcode); static std::string getResponseForNode(MegaNode *node, MegaHTTPContext* httpctx); // WEBDAV related static std::string getWebDavPropFindResponseForNode(std::string baseURL, std::string subnodepath, MegaNode *node, MegaHTTPContext* httpctx); static std::string getWebDavProfFindNodeContents(MegaNode *node, std::string baseURL, bool offlineAttribute); static void returnHttpCodeBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e, bool synchronous = true); static void returnHttpCode(MegaHTTPContext* httpctx, int errorCode, std::string errorMessage = string(), bool synchronous = true); public: static void returnHttpCodeAsyncBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e); static void returnHttpCodeAsync(MegaHTTPContext* httpctx, int errorCode, std::string errorMessage = string()); MegaHTTPServer(MegaApiImpl *megaApi, string basePath, bool useTLS = false, std::string certificatepath = std::string(), std::string keypath = std::string(), bool useIPv6 = false); virtual ~MegaHTTPServer(); char *getWebDavLink(MegaNode *node); void clearAllowedHandles(); bool isHandleWebDavAllowed(handle h); set getAllowedWebDavHandles(); void removeAllowedWebDavHandle(MegaHandle handle); void enableFileServer(bool enable); void enableFolderServer(bool enable); bool isFileServerEnabled(); bool isFolderServerEnabled(); void enableOfflineAttribute(bool enable); bool isOfflineAttributeEnabled(); bool isSubtitlesSupportEnabled(); void enableSubtitlesSupport(bool enable); }; class MegaFTPContext : public MegaTCPContext { public: int command; std::string arg1; std::string arg2; int resultcode; int pasiveport; MegaFTPDataServer * ftpDataServer; std::string tmpFileName; MegaNode *nodeToDeleteAfterMove; uv_mutex_t mutex_responses; std::list responses; uv_mutex_t mutex_nodeToDownload; //status MegaHandle cwd; bool atroot; bool athandle; MegaHandle parentcwd; std::string cwdpath; MegaFTPContext(); ~MegaFTPContext(); virtual void onTransferStart(MegaApi *, MegaTransfer *transfer); virtual bool onTransferData(MegaApi *, MegaTransfer *transfer, char *buffer, size_t size); virtual void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError *e); virtual void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError *e); }; class MegaFTPServer: public MegaTCPServer { protected: enum{ FTP_CMD_INVALID = -1, FTP_CMD_USER = 1, FTP_CMD_PASS, FTP_CMD_ACCT, FTP_CMD_CWD, FTP_CMD_CDUP, FTP_CMD_SMNT, FTP_CMD_QUIT, FTP_CMD_REIN, FTP_CMD_PORT, FTP_CMD_PASV, FTP_CMD_TYPE, FTP_CMD_STRU, FTP_CMD_MODE, FTP_CMD_RETR, FTP_CMD_STOR, FTP_CMD_STOU, FTP_CMD_APPE, FTP_CMD_ALLO, FTP_CMD_REST, FTP_CMD_RNFR, FTP_CMD_RNTO, FTP_CMD_ABOR, FTP_CMD_DELE, FTP_CMD_RMD, FTP_CMD_MKD, FTP_CMD_PWD, FTP_CMD_LIST, FTP_CMD_NLST, FTP_CMD_SITE, FTP_CMD_SYST, FTP_CMD_STAT, FTP_CMD_HELP, FTP_CMD_FEAT, //rfc2389 FTP_CMD_SIZE, FTP_CMD_PROT, FTP_CMD_EPSV, //rfc2428 FTP_CMD_PBSZ, //rfc2228 FTP_CMD_OPTS, //rfc2389 FTP_CMD_NOOP }; std::string crlfout; MegaHandle nodeHandleToRename; int pport; int dataportBegin; int dataPortEnd; std::string getListingLineFromNode(MegaNode *child, std::string nameToShow = string()); MegaNode *getBaseFolderNode(std::string path); MegaNode *getNodeByFullFtpPath(std::string path); void getPermissionsString(int permissions, char *permsString); //virtual methods: virtual void processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf); virtual void processAsyncEvent(MegaTCPContext *tcpctx); virtual MegaTCPContext * initializeContext(uv_stream_t *server_handle); virtual void processWriteFinished(MegaTCPContext* tcpctx, int status); virtual void processOnAsyncEventClose(MegaTCPContext* tcpctx); virtual bool respondNewConnection(MegaTCPContext* tcpctx); virtual void processOnExitHandleClose(MegaTCPServer* tcpServer); public: std::string newNameAfterMove; MegaFTPServer(MegaApiImpl *megaApi, string basePath, int dataportBegin, int dataPortEnd, bool useTLS = false, std::string certificatepath = std::string(), std::string keypath = std::string()); virtual ~MegaFTPServer(); static std::string getFTPErrorString(int errorcode, std::string argument = string()); static void returnFtpCodeBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e); static void returnFtpCode(MegaFTPContext* ftpctx, int errorCode, std::string errorMessage = string()); static void returnFtpCodeAsyncBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e); static void returnFtpCodeAsync(MegaFTPContext* ftpctx, int errorCode, std::string errorMessage = string()); MegaNode * getNodeByFtpPath(MegaFTPContext* ftpctx, std::string path); std::string cdup(handle parentHandle, MegaFTPContext* ftpctx); std::string cd(string newpath, MegaFTPContext* ftpctx); std::string shortenpath(std::string path); }; class MegaFTPDataContext; class MegaFTPDataServer: public MegaTCPServer { protected: //virtual methods: virtual void processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf); virtual void processAsyncEvent(MegaTCPContext *tcpctx); virtual MegaTCPContext * initializeContext(uv_stream_t *server_handle); virtual void processWriteFinished(MegaTCPContext* tcpctx, int status); virtual void processOnAsyncEventClose(MegaTCPContext* tcpctx); virtual bool respondNewConnection(MegaTCPContext* tcpctx); virtual void processOnExitHandleClose(MegaTCPServer* tcpServer); void sendNextBytes(MegaFTPDataContext *ftpdatactx); public: MegaFTPContext *controlftpctx; std::string resultmsj; MegaNode *nodeToDownload; std::string remotePathToUpload; std::string newNameToUpload; MegaHandle newParentNodeHandle; m_off_t rangeStartREST; void sendData(); bool notifyNewConnectionRequired; MegaFTPDataServer(MegaApiImpl *megaApi, string basePath, MegaFTPContext * controlftpctx, bool useTLS = false, std::string certificatepath = std::string(), std::string keypath = std::string()); virtual ~MegaFTPDataServer(); string getListingLineFromNode(MegaNode *child); }; class MegaFTPDataContext : public MegaTCPContext { public: MegaFTPDataContext(); ~MegaFTPDataContext(); void setControlCodeUponDataClose(int code, std::string msg = string()); // Connection management StreamingBuffer streamingBuffer; MegaTransferPrivate *transfer; char *lastBuffer; size_t lastBufferLen; bool failed; int ecode; bool pause; MegaNode *node; m_off_t rangeStart; m_off_t rangeEnd; m_off_t rangeWritten; std::string nodename; std::string nodehandle; std::string tmpFileName; std::unique_ptr tmpFileAccess; size_t tmpFileSize; bool controlRespondedElsewhere; string controlResponseMessage; int controlResponseCode; virtual void onTransferStart(MegaApi*, MegaTransfer* ftpDataTransfer); virtual bool onTransferData(MegaApi*, MegaTransfer* ftpDataTransfer, char* buffer, size_t dataSize); virtual void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError *e); virtual void onRequestFinish(MegaApi* api, MegaRequest *request, MegaError *e); }; #endif #ifdef ENABLE_CHAT class MegaScheduledFlagsPrivate: public MegaScheduledFlags { public: MegaScheduledFlagsPrivate(); MegaScheduledFlagsPrivate(const unsigned long numericValue); MegaScheduledFlagsPrivate(const MegaScheduledFlagsPrivate* flags); MegaScheduledFlagsPrivate(const ScheduledFlags* flags); ~MegaScheduledFlagsPrivate() override = default; MegaScheduledFlagsPrivate(const MegaScheduledFlagsPrivate&) = delete; MegaScheduledFlagsPrivate(const MegaScheduledFlagsPrivate&&) = delete; MegaScheduledFlagsPrivate& operator=(const MegaScheduledFlagsPrivate&) = delete; MegaScheduledFlagsPrivate& operator=(const MegaScheduledFlagsPrivate&&) = delete; void reset() override; void setSendEmails(bool enabled); void importFlagsValue(unsigned long val) override; bool sendEmails() const; unsigned long getNumericValue() const override; MegaScheduledFlagsPrivate* copy() const override { return new MegaScheduledFlagsPrivate(this); } bool isEmpty() const override; unique_ptr getSdkScheduledFlags() const; private: unique_ptr mScheduledFlags; }; class MegaScheduledRulesPrivate : public MegaScheduledRules { public: MegaScheduledRulesPrivate(const int freq, const int interval = INTERVAL_INVALID, const MegaTimeStamp until = MEGA_INVALID_TIMESTAMP, const MegaIntegerList* byWeekDay = nullptr, const MegaIntegerList* byMonthDay = nullptr, const MegaIntegerMap* byMonthWeekDay = nullptr); MegaScheduledRulesPrivate(const MegaScheduledRulesPrivate* rules); MegaScheduledRulesPrivate(const ScheduledRules* rules); ~MegaScheduledRulesPrivate() override = default; MegaScheduledRulesPrivate(const MegaScheduledRulesPrivate&) = delete; MegaScheduledRulesPrivate(const MegaScheduledRulesPrivate&&) = delete; MegaScheduledRulesPrivate& operator=(const MegaScheduledRulesPrivate&) = delete; MegaScheduledRulesPrivate& operator=(const MegaScheduledRulesPrivate&&) = delete; int freq() const override; int interval() const override; MegaTimeStamp until() const override; const mega::MegaIntegerList* byWeekDay() const override; const mega::MegaIntegerList* byMonthDay() const override; const mega::MegaIntegerMap* byMonthWeekDay() const override; MegaScheduledRulesPrivate* copy() const override { return new MegaScheduledRulesPrivate(this); } unique_ptr getSdkScheduledRules() const; static bool isValidFreq(const int freq); static bool isValidInterval(const int interval); static bool isValidUntil(const m_time_t until); private: unique_ptr mScheduledRules; // temp memory must be held somewhere since there is a data transformation and ownership is not returned in the getters // (probably removed after checking MegaAPI redesign) mutable std::unique_ptr mTransformedByWeekDay; mutable std::unique_ptr mTransformedByMonthDay; mutable std::unique_ptr mTransformedByMonthWeekDay; }; class MegaScheduledMeetingPrivate: public MegaScheduledMeeting { public: MegaScheduledMeetingPrivate(const MegaHandle chatid, const char* timezone, const MegaTimeStamp startDateTime, const MegaTimeStamp endDateTime, const char* title, const char* description, const MegaHandle schedId = INVALID_HANDLE, const MegaHandle parentSchedId = INVALID_HANDLE, const MegaHandle organizerUserId = INVALID_HANDLE, const int cancelled = -1, const char* attributes = nullptr, const MegaTimeStamp overrides = MEGA_INVALID_TIMESTAMP, const MegaScheduledFlags* flags = nullptr, const MegaScheduledRules* rules = nullptr); MegaScheduledMeetingPrivate(const MegaScheduledMeetingPrivate *scheduledMeeting); MegaScheduledMeetingPrivate(const ScheduledMeeting* scheduledMeeting); ~MegaScheduledMeetingPrivate() override = default; MegaScheduledMeetingPrivate(const MegaScheduledMeetingPrivate&) = delete; MegaScheduledMeetingPrivate(const MegaScheduledMeetingPrivate&&) = delete; MegaScheduledMeetingPrivate& operator=(const MegaScheduledMeetingPrivate&) = delete; MegaScheduledMeetingPrivate& operator=(const MegaScheduledMeetingPrivate&&) = delete; MegaHandle chatid() const override; MegaHandle schedId() const override; MegaHandle parentSchedId() const override; MegaHandle organizerUserid() const override; const char* timezone() const override; MegaTimeStamp startDateTime() const override; MegaTimeStamp endDateTime() const override; const char* title() const override; const char* description() const override; const char* attributes() const override; MegaTimeStamp overrides() const override; int cancelled() const override; MegaScheduledFlags* flags() const override; // ownership returned MegaScheduledRules* rules() const override; // ownership returned MegaScheduledMeetingPrivate* copy() const override { return new MegaScheduledMeetingPrivate(this); } const ScheduledMeeting* scheduledMeeting() const { return mScheduledMeeting.get(); } private: unique_ptr mScheduledMeeting; }; class MegaScheduledMeetingListPrivate: public MegaScheduledMeetingList { public: MegaScheduledMeetingListPrivate(); MegaScheduledMeetingListPrivate(const MegaScheduledMeetingListPrivate &); ~MegaScheduledMeetingListPrivate(); MegaScheduledMeetingListPrivate *copy() const override; // getters unsigned long size() const override; MegaScheduledMeeting* at(unsigned long i) const override; // returns the first MegaScheduledMeeting, whose schedId matches with h // note that schedId is globally unique for all chats (in case of scheduled meetings), but this class // can be used to store scheduled meetings occurrences (it can contains multiple items with the same schedId) MegaScheduledMeeting* getBySchedId(MegaHandle h) const override; // setters void insert(MegaScheduledMeeting *sm) override; void clear() override; private: std::vector> mList; }; #endif class MegaVpnCredentialsPrivate : public MegaVpnCredentials { public: using MapSlotIDToCredentialInfo = CommandGetVpnCredentials::MapSlotIDToCredentialInfo; using MapClusterPublicKeys = CommandGetVpnCredentials::MapClusterPublicKeys; MegaVpnCredentialsPrivate(MapSlotIDToCredentialInfo&&, MapClusterPublicKeys&&, std::vector&&); MegaVpnCredentialsPrivate(const MegaVpnCredentialsPrivate&); ~MegaVpnCredentialsPrivate() override = default; MegaIntegerList* getSlotIDs() const override; MegaStringList* getVpnRegions() const override; MegaVpnRegionListPrivate* getVpnRegionsDetailed() const override; const char* getIPv4(int slotID) const override; const char* getIPv6(int slotID) const override; const char* getDeviceID(int slotID) const override; int getClusterID(int slotID) const override; const char* getClusterPublicKey(int clusterID) const override; MegaVpnCredentials* copy() const override; private: MapSlotIDToCredentialInfo mMapSlotIDToCredentialInfo; MapClusterPublicKeys mMapClusterPubKeys; std::vector mVpnRegions; }; class MegaNetworkConnectivityTestResultsPrivate: public MegaNetworkConnectivityTestResults { public: MegaNetworkConnectivityTestResultsPrivate(int ipv4, int ipv4dns, int ipv6, int ipv6dns): mIPv4(ipv4), mIPv4DNS(ipv4dns), mIPv6(ipv6), mIPv6DNS(ipv6dns) {} int getIPv4UDP() const override { return mIPv4; } int getIPv4DNS() const override { return mIPv4DNS; } int getIPv6UDP() const override { return mIPv6; } int getIPv6DNS() const override { return mIPv6DNS; } MegaNetworkConnectivityTestResultsPrivate* copy() const override; private: const int mIPv4; const int mIPv4DNS; const int mIPv6; const int mIPv6DNS; }; class MegaNodeTreePrivate: public MegaNodeTree { public: MegaNodeTreePrivate(const MegaNodeTree* nodeTreeChild, const std::string& name, const std::string& s4AttributeValue, const MegaCompleteUploadData* completeUploadData, MegaHandle sourceHandle, MegaHandle nodeHandle); ~MegaNodeTreePrivate() override = default; MegaNodeTree* getNodeTreeChild() const override; const std::string& getName() const; const std::string& getS4AttributeValue() const; const MegaCompleteUploadData* getCompleteUploadData() const; MegaHandle getNodeHandle() const override; void setNodeHandle(const MegaHandle& nodeHandle); const MegaHandle& getSourceHandle() const { return mSourceHandle; } MegaNodeTree* copy() const override; private: std::unique_ptr mNodeTreeChild; std::string mName; std::string mS4AttributeValue; // new leaf-file-node is created from upload-token or as a // copy of an existing node (cannot use both at the same time) // data to create node from upload-token std::unique_ptr mCompleteUploadData; // handle of an existing file node to be copied MegaHandle mSourceHandle; // output param: handle give to new node MegaHandle mNodeHandle; }; class MegaCompleteUploadDataPrivate: public MegaCompleteUploadData { public: MegaCompleteUploadDataPrivate(const std::string& fingerprint, const std::string& string64UploadToken, const std::string& string64FileKey); ~MegaCompleteUploadDataPrivate() override = default; const std::string& getFingerprint() const; const std::string& getString64UploadToken() const; const std::string& getString64FileKey() const; MegaCompleteUploadData* copy() const override; private: std::string mFingerprint; std::string mString64UploadToken; std::string mString64FileKey; }; class MegaNotificationPrivate : public MegaNotification { public: MegaNotificationPrivate(DynamicMessageNotification&& n) : mNotification{std::move(n)}, mCall1{&mNotification.callToAction1}, mCall2{&mNotification.callToAction2} {} MegaNotificationPrivate(const DynamicMessageNotification& n) : mNotification{n}, mCall1{&mNotification.callToAction1}, mCall2{&mNotification.callToAction2} {} int64_t getID() const override { return mNotification.id; } const char* getTitle() const override { return mNotification.title.c_str(); } const char* getDescription() const override { return mNotification.description.c_str(); } const char* getImageName() const override { return mNotification.imageName.c_str(); } const char* getIconName() const override { return mNotification.iconName.c_str(); } const char* getImagePath() const override { return mNotification.imagePath.c_str(); } int64_t getStart() const override { return mNotification.start; } int64_t getEnd() const override { return mNotification.end; } bool showBanner() const override { return mNotification.showBanner; } const MegaStringMap* getCallToAction1() const override { return &mCall1; } const MegaStringMap* getCallToAction2() const override { return &mCall2; } MegaStringList* getRenderModes() const override; MegaStringMap* getRenderModeFields(const char* mode) const override; MegaNotificationPrivate* copy() const override { return new MegaNotificationPrivate(*this); } private: const DynamicMessageNotification mNotification; const MegaStringMapPrivate mCall1; const MegaStringMapPrivate mCall2; }; class MegaNotificationListPrivate : public MegaNotificationList { public: MegaNotificationListPrivate(std::vector&& ns) { mNotifications.reserve(ns.size()); for (const auto& n : ns) { mNotifications.emplace_back(std::move(n)); } } MegaNotificationListPrivate* copy() const override { return new MegaNotificationListPrivate(*this); } const MegaNotification* get(unsigned i) const override { return i < size() ? &mNotifications[i] : nullptr; } unsigned size() const override { return static_cast(mNotifications.size()); } private: vector mNotifications; }; class MegaFuseExecutorFlagsPrivate : public MegaFuseExecutorFlags { common::TaskExecutorFlags& mFlags; public: MegaFuseExecutorFlagsPrivate(common::TaskExecutorFlags& flags); size_t getMinThreadCount() const override; size_t getMaxThreadCount() const override; size_t getMaxThreadIdleTime() const override; bool setMaxThreadCount(size_t max) override; void setMinThreadCount(size_t min) override; void setMaxThreadIdleTime(size_t max) override; }; // MegaFuseExecutorFlagsPrivate class MegaFuseInodeCacheFlagsPrivate : public MegaFuseInodeCacheFlags { fuse::InodeCacheFlags& mFlags; public: explicit MegaFuseInodeCacheFlagsPrivate(fuse::InodeCacheFlags& flags); size_t getCleanAgeThreshold() const override; size_t getCleanInterval() const override; size_t getCleanSizeThreshold() const override; size_t getMaxSize() const override; void setCleanAgeThreshold(std::size_t seconds) override; void setCleanInterval(std::size_t seconds) override; void setCleanSizeThreshold(std::size_t size) override; void setMaxSize(std::size_t size) override; }; // MegaFuseExecutorFlagsPrivate class MegaFuseFlagsPrivate : public MegaFuseFlags { fuse::ServiceFlags mFlags; MegaFuseInodeCacheFlagsPrivate mInodeCacheFlags; MegaFuseExecutorFlagsPrivate mMountExecutorFlags; MegaFuseExecutorFlagsPrivate mSubsystemExecutorFlags; public: MegaFuseFlagsPrivate(const fuse::ServiceFlags& flags); MegaFuseFlags* copy() const override; const fuse::ServiceFlags& getFlags() const; size_t getFlushDelay() const override; int getLogLevel() const override; int getFileExplorerView() const override; MegaFuseInodeCacheFlags* getInodeCacheFlags() override; MegaFuseExecutorFlags* getMountExecutorFlags() override; MegaFuseExecutorFlags* getSubsystemExecutorFlags() override; void setFlushDelay(size_t seconds) override; void setLogLevel(int level) override; void setFileExplorerView(int view) override; }; // MegaFuseFlagsPrivate using MegaMountFlagsPtr = std::unique_ptr; using MegaMountPtr = std::unique_ptr; using MegaMountPtrVector = std::vector; class MegaMountPrivate : public MegaMount { MegaMountFlagsPtr mFlags; MegaHandle mHandle; std::string mPath; public: MegaMountPrivate(); MegaMountPrivate(const fuse::MountInfo& info); MegaMountPrivate(const MegaMountPrivate& other); fuse::MountInfo asInfo() const; MegaMount* copy() const override; MegaMountFlags* getFlags() const override; MegaHandle getHandle() const override; const char* getPath() const override; void setFlags(const MegaMountFlags* flags) override; void setHandle(MegaHandle handle) override; void setPath(const char* path) override; }; // MegaMountPrivate class MegaMountFlagsPrivate : public MegaMountFlags { fuse::MountFlags mFlags; public: MegaMountFlagsPrivate() = default; MegaMountFlagsPrivate(const fuse::MountFlags& flags); MegaMountFlagsPrivate(const MegaMountFlagsPrivate& other) = default; MegaMountFlags* copy() const override; bool getEnableAtStartup() const override; const fuse::MountFlags& getFlags() const; const char* getName() const override; bool getPersistent() const override; bool getReadOnly() const override; void setEnableAtStartup(bool enable) override; void setName(const char* name) override; void setPersistent(bool persistent) override; void setReadOnly(bool readOnly) override; }; // MegaMountFlagsPrivate class MegaMountListPrivate : public MegaMountList { MegaMountPtrVector mMounts; public: MegaMountListPrivate(fuse::MountInfoVector&& mounts); MegaMountListPrivate(const MegaMountListPrivate& other); MegaMountList* copy() const override; const MegaMount* get(size_t index) const override; size_t size() const override; }; // MegaMountListPrivate class MegaCancelSubscriptionReasonPrivate: public MegaCancelSubscriptionReason { public: MegaCancelSubscriptionReasonPrivate(const char* reason, const char* position); const char* text() const override; const char* position() const override; MegaCancelSubscriptionReasonPrivate* copy() const override; private: std::string mText; std::string mPosition; }; class MegaCancelSubscriptionReasonListPrivate: public MegaCancelSubscriptionReasonList { public: void add(const MegaCancelSubscriptionReason* reason) override; const MegaCancelSubscriptionReason* get(size_t index) const override; size_t size() const override; MegaCancelSubscriptionReasonListPrivate* copy() const override; private: std::vector> mReasons; }; class MegaDiscountCodePrivate: public MegaDiscountCode { public: MegaDiscountCodePrivate(DiscountCode&& discountCode); ~MegaDiscountCodePrivate() override = default; MegaDiscountCode* copy() const override; const char* getCode() const override; int getItem() const override; int getAccountLevel() const override; int getMonths() const override; int getPercentageDiscount() const override; int getBehaviorType() const override; private: DiscountCode mDiscountCode; }; class MegaDiscountCodeListPrivate: public MegaDiscountCodeList { public: MegaDiscountCodeListPrivate(); ~MegaDiscountCodeListPrivate() override = default; MegaDiscountCodeList* copy() const override; int size() const override; const MegaDiscountCode* get(int i) const override; void add(MegaDiscountCodePrivate&& discountCode); private: std::vector mDiscountCodes; }; class MegaDiscountCodeInfoPrivate: public MegaDiscountCodeInfo { public: MegaDiscountCodeInfoPrivate(DiscountCodeInfoExtended&& discountCodeInfo); ~MegaDiscountCodeInfoPrivate() override = default; MegaDiscountCodeInfo* copy() const override; const char* getCode() const override; int getItem() const override; int getAccountLevel() const override; int getMonths() const override; int getPercentageDiscount() const override; int getBehaviorType() const override; int getExpiry() const override; int getCompulsorySubscription() const override; int getMultiDiscount() const override; int getTaxValue() const override; MegaStringIntegerMap* getFeatures() const override; bool isTaxExempt() const override; bool isTaxAppliedOnTop() const override; int getTaxRate() const override; const char* getTaxName() const override; const char* getTaxCountry() const override; double getEuroTotalPrice() const override; double getEuroDiscountAmount() const override; double getEuroDiscountedTotalPrice() const override; double getEuroDiscountedMonthlyPrice() const override; double getEuroTotalPriceNet() const override; double getEuroDiscountAmountNet() const override; double getEuroDiscountedTotalPriceNet() const override; double getEuroDiscountedMonthlyPriceNet() const override; const char* getLocalCurrencyCode() const override; const char* getLocalCurrencySymbol() const override; double getLocalTotalPrice() const override; double getLocalDiscountAmount() const override; double getLocalDiscountedTotalPrice() const override; double getLocalDiscountedMonthlyPrice() const override; double getLocalTotalPriceNet() const override; double getLocalDiscountAmountNet() const override; double getLocalDiscountedTotalPriceNet() const override; double getLocalDiscountedMonthlyPriceNet() const override; private: DiscountCodeInfoExtended mDiscountCodeInfo; }; std::unique_ptr createFSA(); } // Specializations of std::hash for custom Sync types namespace std { template<> struct hash<::mega::NodeHandle> { std::size_t operator()(const ::mega::NodeHandle& nh) const noexcept { return std::hash{}(nh.as8byte()); } }; template<> struct hash<::mega::LocalPath> { std::size_t operator()(const ::mega::LocalPath& lp) const noexcept { uint64_t seed = 0; seed = ::mega::hashCombine(seed, std::hash{}(lp.toPath(false))); seed = ::mega::hashCombine(seed, std::hash{}(lp.isAbsolute())); return static_cast(seed); } }; template<> struct hash<::mega::NameConflict::NameHandle> { std::size_t operator()(const ::mega::NameConflict::NameHandle& nh) const noexcept { uint64_t seed = 0; seed = ::mega::hashCombine(seed, std::hash{}(nh.name)); seed = ::mega::hashCombine(seed, std::hash<::mega::NodeHandle>{}(nh.handle)); return static_cast(seed); } }; template<> struct hash<::mega::NameConflict> { std::size_t operator()(const ::mega::NameConflict& nc) const noexcept { uint64_t seed = 0; const std::hash<::mega::LocalPath> lpHashGet{}; const std::hash<::mega::NameConflict::NameHandle> nhHashGet{}; seed = ::mega::hashCombine(seed, std::hash{}(nc.cloudPath)); seed = ::mega::hashCombine(seed, lpHashGet(nc.localPath)); std::for_each(std::begin(nc.clashingCloud), std::end(nc.clashingCloud), [&seed, &nhHashGet](const auto& cc) { seed = ::mega::hashCombine(seed, nhHashGet(cc)); }); std::for_each(std::begin(nc.clashingLocalNames), std::end(nc.clashingLocalNames), [&seed, &lpHashGet](const auto& lp) { seed = ::mega::hashCombine(seed, lpHashGet(lp)); }); return static_cast(seed); } }; #ifdef ENABLE_SYNC template<> struct hash<::mega::SyncStallEntry::StallCloudPath> { std::size_t operator()(const ::mega::SyncStallEntry::StallCloudPath& scp) const noexcept { uint64_t seed = 0; seed = ::mega::hashCombine(seed, std::hash{}(static_cast(scp.problem))); seed = ::mega::hashCombine(seed, std::hash{}(scp.cloudPath)); seed = ::mega::hashCombine(seed, std::hash<::mega::NodeHandle>{}(scp.cloudHandle)); return static_cast(seed); } }; template<> struct hash<::mega::SyncStallEntry::StallLocalPath> { std::size_t operator()(const ::mega::SyncStallEntry::StallLocalPath& slp) const noexcept { uint64_t seed = 0; seed = ::mega::hashCombine(seed, std::hash{}(static_cast(slp.problem))); seed = ::mega::hashCombine(seed, std::hash<::mega::LocalPath>{}(slp.localPath)); return static_cast(seed); } }; template<> struct hash<::mega::SyncStallEntry> { std::size_t operator()(const ::mega::SyncStallEntry& sse) const noexcept { using ::mega::SyncStallEntry; uint64_t seed = 0; seed = ::mega::hashCombine(seed, std::hash{}(static_cast(sse.reason))); seed = ::mega::hashCombine(seed, std::hash{}(sse.alertUserImmediately)); seed = ::mega::hashCombine(seed, std::hash{}(sse.detectionSideIsMEGA)); const std::hash cpHashGet{}; seed = ::mega::hashCombine(seed, cpHashGet(sse.cloudPath1)); seed = ::mega::hashCombine(seed, cpHashGet(sse.cloudPath2)); const std::hash lpHashGet{}; seed = ::mega::hashCombine(seed, lpHashGet(sse.localPath1)); seed = ::mega::hashCombine(seed, lpHashGet(sse.localPath2)); return static_cast(seed); } }; template<> struct hash<::mega::MegaSyncStallPrivate> { std::size_t operator()(const ::mega::MegaSyncStallPrivate& stall) const noexcept { return std::hash<::mega::SyncStallEntry>{}(stall.info); } }; template<> struct hash<::mega::MegaSyncNameConflictStallPrivate> { std::size_t operator()(const ::mega::MegaSyncNameConflictStallPrivate& stall) const noexcept { return std::hash<::mega::NameConflict>{}(stall.mConflict); } }; #endif } // namespace std #endif //MEGAAPI_IMPL_H sdk-10.11.0/include/megautils.h000066400000000000000000000041771516266226600162460ustar00rootroot00000000000000/** * @file megautils.h * @brief Utilities related with public objects from intermediate layer * * (c) 2024 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef MEGAUTILS_H #define MEGAUTILS_H #include #include #include #include #include #include namespace mega { /** * @brief Aux function to get a vector with the names of the nodes in a given MegaNodeList */ std::vector toNamesVector(const MegaNodeList& nodes); using ChildNameAndFingerprint = std::pair>; /** * @brief Aux function to get a vector with the names and fingerprints of the nodes in a given * MegaNodeList */ std::vector toNamesAndFingerprintVector(const MegaNodeList& nodes); /** * @brief Aux function to get a vector with the strings in a given MegaStringList */ std::vector stringListToVector(const MegaStringList& l); /** * @brief Aux function to get a map from a given MegaStringIntegerMap */ std::map stringIntegerMapToMap(const MegaStringIntegerMap& m); /** * @brief Aux function to get a vector with vector of strings in a given MegaRecentActionBucketList */ std::vector> bucketsToVector(const MegaRecentActionBucketList& buckets); #ifdef ENABLE_SYNC /** * @brief Convert a MegaSyncStallList into a vector of unique_ptr to its components. To own the * elements, the function copies each element in the list. */ std::vector> toSyncStallVector(const MegaSyncStallList& stallList); #endif } #endif // MEGAUTILS_H sdk-10.11.0/jenkinsfile/000077500000000000000000000000001516266226600147505ustar00rootroot00000000000000sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_android000066400000000000000000000540301516266226600212340ustar00rootroot00000000000000import groovy.json.JsonSlurperClassic // Uploads a file to a Gitlab project // Requires env.GITLAB_BASE_URL String uploadFileToGitLab(String fileName, String projectId) { String link = "" String response = "" withCredentials([string(credentialsId: 'gitlab-api-token', variable: 'TOKEN')]) { response = sh(script: "curl -s --request POST --header 'PRIVATE-TOKEN:\$TOKEN' --form file=@${fileName} ${env.GITLAB_BASE_URL}/api/v4/projects/${projectId}/uploads", returnStdout: true).trim() } link = new JsonSlurperClassic().parseText(response).markdown echo "Logs uploaded to: ${link}" return link } // Downloads the console log from this Jenkins build void downloadJenkinsConsoleLog(String fileName) { withCredentials([usernameColonPassword(credentialsId: 'jenkins-ro', variable: 'CREDENTIALS')]) { sh "curl -u \"\${CREDENTIALS}\" ${BUILD_URL}consoleText -o ${fileName}" } } // Downloads the logs of the build, uploads them to the gitlab project // And return the URL String getLogsUrl(String projectId) { String message = "" String fileName = "build.log" String logUrl = "" downloadJenkinsConsoleLog(fileName) return uploadFileToGitLab(fileName, projectId) } // Post a failure message in MR void commentFailedBuild() { logUrl = getLogsUrl(env.PROJECT_ID) addGitLabMRComment(comment: ":red_circle: ${currentBuild.projectName} :penguin: Android FAILURE :worried:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${env.GIT_COMMIT}
    Logs: ${logUrl}" ) updateGitlabCommitStatus(name: 'Build Android', state: 'failed') } pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { BUILD_OPTIONS = ' ' VCPKGPATH = "/opt/vcpkg" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } stages { stage('Update Gitlab commitStatus') { agent any steps { updateGitlabCommitStatus(name: 'Build Android', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build') { agent { label 'linux && amd64 && android' } stages { stage('clean previous runs'){ steps{ deleteDir() } } stage('Get build parameters'){ parallel{ stage('Get build options and run parameters'){ steps { script{ env.ARCH_TO_BUILD = sh(script: 'echo "$gitlabTriggerPhrase" | grep ARCH_TO_BUILD | awk -F "ARCH_TO_BUILD=" \'{print \$2}\' | cut -d\\" -f2', returnStdout: true).trim() if (env.ARCH_TO_BUILD == ""){ env.ARCH_TO_BUILD = "arm64" } // Map Jenkins arch -> Android ABI folder name env.ANDROID_ABI = sh(script: """ case "${env.ARCH_TO_BUILD}" in arm) echo "armeabi-v7a" ;; arm64) echo "arm64-v8a" ;; x86) echo "x86" ;; x64) echo "x86_64" ;; *) echo "" ;; esac """, returnStdout: true).trim() if (!env.ANDROID_ABI) { error "Unsupported ARCH_TO_BUILD='${env.ARCH_TO_BUILD}'" } BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\\"" -f2 || :', returnStdout: true).trim() println ARCH_TO_BUILD println BUILD_OPTIONS } } } stage('Get MEGAchat branch'){ steps { script{ env.MEGACHAT_BRANCH = sh(script: 'echo "$gitlabMergeRequestDescription" | grep MEGACHAT_BRANCH_TO_TEST | awk -F "MEGACHAT_BRANCH_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() if (MEGACHAT_BRANCH == ""){ echo "MEGACHAT_BRANCH was not found on description so develop will be used by default" env.MEGACHAT_BRANCH = "develop" } println MEGACHAT_BRANCH } } } stage('Get Android branch'){ steps { script{ env.ANDROID_BRANCH = sh(script: 'echo "$gitlabMergeRequestDescription" | grep ANDROID_BRANCH_TO_TEST | awk -F "ANDROID_BRANCH_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() if (ANDROID_BRANCH == ""){ echo "ANDROID_BRANCH was not found on description so develop will be used by default" env.ANDROID_BRANCH = "develop" } println ANDROID_BRANCH } } } } } stage('Build All'){ parallel{ stage('Example App'){ options{ timeout(time: 200, unit: 'MINUTES') } environment { SDK_OUTPUT_DIR = "${WORKSPACE}/output/android/${ARCH_TO_BUILD}" SDK_JNILIBS_DIR = "${WORKSPACE}/examples/android/ExampleApp/app/src/main/jniLibs/${ANDROID_ABI}" SDK_EXAMPLE_DIR = "${WORKSPACE}/examples/android/ExampleApp" } stages { stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) } } stage('Build Docker'){ steps{ dir("dockerfile"){ sh "docker build -t meganz/sdk-android-mr-build-env:${env.BUILD_NUMBER} -f ./android-cross-build.dockerfile ." } sh "echo ${ARCH_TO_BUILD}" sh "mkdir -p ${env.SDK_OUTPUT_DIR}" sh "mkdir -p ${env.SDK_JNILIBS_DIR}" sh "mkdir -p ${env.SDK_EXAMPLE_DIR}/app/src/main/java/nz/mega/sdk" } } stage("Build SDK for Example App"){ steps { sh "echo Build SDK for arch $ARCH_TO_BUILD" sh "docker run --name sdk-android-builder-$ARCH_TO_BUILD-${env.BUILD_NUMBER} \ --rm \ -v ${WORKSPACE}:/mega/sdk \ -v ${VCPKGPATH}:/mega/vcpkg \ -v ${env.SDK_OUTPUT_DIR}:/mega/buildAndroid \ -e ARCH=$ARCH_TO_BUILD \ -e VCPKG_BINARY_SOURCES \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_ENDPOINT_URL \ meganz/sdk-android-mr-build-env:${env.BUILD_NUMBER}" sh "cp -fv ${env.SDK_OUTPUT_DIR}/bindings/java/libmega.so ${env.SDK_JNILIBS_DIR}" sh "cp -fv ${env.SDK_OUTPUT_DIR}/bindings/java/nz/mega/sdk/*.java ${env.SDK_EXAMPLE_DIR}/app/src/main/java/nz/mega/sdk" } } stage('Build Example App'){ environment{ ANDROID_HOME = "/home/jenkins/android-cmdlinetools/" ANDROID_NDK_HOME ="/home/jenkins/android-ndk/" NDK_ROOT = "/home/jenkins/android-ndk/" VCPKG_ROOT="/opt/vcpkg" } steps{ dir("${env.SDK_EXAMPLE_DIR}") { script{ env.PATH="${env.PATH}:${env.ANDROID_HOME}/cmdline-tools/tools/bin/" } //Build Example App sh "mkdir -p ../java/nz/mega/sdk" sh "./gradlew --no-daemon --max-workers=2 build" sh "rm examples-*.tar.gz || :" sh "tar czf examples-${BUILD_ID}.tar.gz -C app/build/outputs apk" } } } } post{ always{ sh "docker image rm meganz/sdk-android-mr-build-env:${env.BUILD_NUMBER}" archiveArtifacts artifacts: 'examples/android/ExampleApp/examples-*.tar.gz', fingerprint: true } } } stage('Android app'){ agent { label 'linux && amd64 && android' } options{ timeout(time: 300, unit: 'MINUTES') } environment { SDK_OUTPUT_DIR = "${WORKSPACE}/output/android/${ARCH_TO_BUILD}" SDK_JNILIBS_DIR = "${WORKSPACE}/sdk/src/main/jniLibs/${ANDROID_ABI}" ANDROID_CHAT_DIR = "${WORKSPACE}/sdk/src/main/jni/megachat/sdk" ANDROID_SDK_DIR = "${WORKSPACE}/sdk/src/main/jni/mega/sdk" ANDROID_HOME = "/home/jenkins/android-cmdlinetools/" ANDROID_NDK_HOME = "/home/jenkins/android-ndk/" NDK_ROOT = "/home/jenkins/android-ndk/" VCPKG_ROOT = "/opt/vcpkg" DEFAULT_GOOGLE_MAPS_API_PATH = "/home/jenkins/android-default_google_maps_api" USE_PREBUILT_SDK = false ARTIFACTORY_BASE_URL = "${env.REPOSITORY_URL}" } stages { stage('Checkout SDK, MEGAchat and Android'){ steps { deleteDir() //Clone Android sh "echo Cloning android branch \"${ANDROID_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "${ANDROID_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_ANDROID}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) dir("${env.ANDROID_CHAT_DIR}"){ //Clone MEGAchat branch sh "echo Cloning megachat branch \"${MEGACHAT_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "${MEGACHAT_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_MEGACHAT}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script{ megachat_sources_workspace = WORKSPACE } } dir("${env.ANDROID_SDK_DIR}"){ //Clone SDK (with PreBuildMerge) checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) } } } stage('Build Docker'){ //Building sdk with MEGAchat-android-cross-build dockerfile, because it uses webrtc //(Needed for Android and unavailable from SDK dockerfile) steps{ dir("${env.ANDROID_CHAT_DIR}/dockerfile"){ sh "docker build -t meganz/megachat-android-mr-build-env:${env.BUILD_NUMBER} -f ./android-cross-build.dockerfile ." } sh "echo ${ARCH_TO_BUILD}" sh "mkdir -p ${env.SDK_OUTPUT_DIR}" sh "mkdir -p ${env.SDK_JNILIBS_DIR}" sh "mkdir -p ${WORKSPACE}/sdk/src/main/java/nz/mega/sdk" sh "mkdir -p ${WORKSPACE}/sdk/src/main/jni/megachat/webrtc" } } stage("Build SDK for Android App"){ steps { sh "echo Build SDK for arch $ARCH_TO_BUILD" sh "docker run --name megachat-android-builder-$ARCH_TO_BUILD-${env.BUILD_NUMBER} \ --rm \ -v ${env.ANDROID_CHAT_DIR}:/mega/MEGAchat \ -v ${env.ANDROID_SDK_DIR}:/mega/MEGAchat/third-party/mega \ -v ${VCPKGPATH}:/mega/vcpkg \ -v ${env.SDK_OUTPUT_DIR}:/mega/build-MEGAchat-mega-android \ -e ARCH=$ARCH_TO_BUILD \ -e BUILD_CORES=4 \ -e VCPKG_BINARY_SOURCES \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_ENDPOINT_URL \ meganz/megachat-android-mr-build-env:${env.BUILD_NUMBER}" sh """ cp -fv ${env.SDK_OUTPUT_DIR}/bindings/java/libmega.so ${env.SDK_JNILIBS_DIR} cp -fv ${env.SDK_OUTPUT_DIR}/bindings/java/nz/mega/sdk/*.java ${WORKSPACE}/sdk/src/main/java/nz/mega/sdk cp -fv `find ${env.SDK_OUTPUT_DIR} -name "libwebrtc.jar"` ${WORKSPACE}/sdk/src/main/jni/megachat/webrtc cp -r ${DEFAULT_GOOGLE_MAPS_API_PATH}/* app/src/ """ } } stage('Build Android App'){ steps{ script { withCredentials([ string(credentialsId: 'ARTIFACTORY_USER', variable: 'ARTIFACTORY_USER'), string(credentialsId: 'ARTIFACTORY_ACCESS_TOKEN', variable: 'ARTIFACTORY_ACCESS_TOKEN'), ]){ withEnv([ "ARTIFACTORY_USER=${ARTIFACTORY_USER}", "ARTIFACTORY_ACCESS_TOKEN=${ARTIFACTORY_ACCESS_TOKEN}" ]){ sh "./gradlew --no-daemon --max-workers=4 assembleGmsDebug" sh "./gradlew --no-daemon --max-workers=4 assembleGmsQa" } } } } } } post{ always{ sh "docker image rm meganz/megachat-android-mr-build-env:${env.BUILD_NUMBER}" } } } } } } post{ always { deleteDir() } failure { node('linux') { // We need and agent able to download logs from Jenkins. This one is allowed. script { commentFailedBuild() } } } } } } post { success { updateGitlabCommitStatus(name: 'Build Android', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :penguin: Android SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build Android', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: Android UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build Android', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: Android ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_clang_format000066400000000000000000000136611516266226600222550ustar00rootroot00000000000000def REBUILD_IMAGE = false pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') ansiColor('xterm') } stages { stage('Update Gitlab commit status') { agent any steps { updateGitlabCommitStatus(name: 'clang format', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build'){ agent { label 'docker' } stages { stage('Checkout'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) } } stage('Get parameters'){ steps { script{ // flag may be `--rebuild-docker` or empty `` def flag = sh(script: 'echo "$gitlabTriggerPhrase" | grep -o "\\-\\-rebuild-docker\\$" || :', returnStdout: true).trim() REBUILD_IMAGE = flag == "--rebuild-docker" echo "REBUILD_IMAGE set to ${REBUILD_IMAGE}" } } } stage('Rebuild docker image') { when { beforeAgent true expression { REBUILD_IMAGE } } steps { dir ("dockerfile") { sh """ docker build \ -f clang-format.dockerfile \ -t ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/clang-format-sdk:24.04_v18 \ . """ } withCredentials([usernamePassword(credentialsId: 'artifactory-jenkins-docker', usernameVariable: 'ART_USER', passwordVariable: 'ART_PASS')]) { sh """ echo \$ART_PASS | docker login \ -u \$ART_USER \ --password-stdin \ ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443 docker push ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/clang-format-sdk:24.04_v18 """ } } } stage('Clang format'){ agent { docker { image "${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/clang-format-sdk:24.04_v18" reuseNode true } } steps { sh """#!/bin/bash TARGET_REF="origin/${env.gitlabTargetBranch}" SOURCE_REF="origin/${env.gitlabSourceBranch}" if ! command -v clang-format-mr-check >/dev/null 2>&1; then echo "ERROR: clang-format-mr-check not found in Docker image." >&2 echo "This job is configured to run the check only inside the Docker image." >&2 echo "Rebuild/push the image (trigger with --rebuild-docker) and retry." >&2 exit 2 fi clang-format-mr-check "\${TARGET_REF}" "\${SOURCE_REF}" """ } } } post{ always { deleteDir() } } } } post { success { updateGitlabCommitStatus(name: 'clang format', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :page_with_curl: ClangFormat SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'clang format', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :page_with_curl: ClangFormat UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'clang format', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :page_with_curl: ClangFormat ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } failure { updateGitlabCommitStatus(name: 'clang format', state: 'failed') addGitLabMRComment(comment: ":red_circle: ${currentBuild.projectName} :page_with_curl: ClangFormat FAILURE :worried:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_iOS000066400000000000000000000262511516266226600203120ustar00rootroot00000000000000import groovy.json.JsonSlurperClassic // Uploads a file to a Gitlab project // Requires env.GITLAB_BASE_URL String uploadFileToGitLab(String fileName, String projectId) { String link = "" String response = "" withCredentials([string(credentialsId: 'gitlab-api-token', variable: 'TOKEN')]) { response = sh(script: "curl -s --request POST --header 'PRIVATE-TOKEN:\$TOKEN' --form file=@${fileName} ${env.GITLAB_BASE_URL}/api/v4/projects/${projectId}/uploads", returnStdout: true).trim() } link = new JsonSlurperClassic().parseText(response).markdown echo "Logs uploaded to: ${link}" return link } // Downloads the console log from this Jenkins build void downloadJenkinsConsoleLog(String fileName) { withCredentials([usernameColonPassword(credentialsId: 'jenkins-ro', variable: 'CREDENTIALS')]) { sh "curl -u \"\${CREDENTIALS}\" ${BUILD_URL}consoleText -o ${fileName}" } } // Downloads the logs of the build, uploads them to the gitlab project // And return the URL String getLogsUrl(String projectId) { String message = "" String fileName = "build.log" String logUrl = "" downloadJenkinsConsoleLog(fileName) return uploadFileToGitLab(fileName, projectId) } // Post a failure message in MR void commentFailedBuild() { logUrl = getLogsUrl(env.PROJECT_ID) addGitLabMRComment(comment: ":red_circle: ${currentBuild.projectName} :green_apple: SDK iOS FAILURE :worried:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${env.GIT_COMMIT}
    Logs: ${logUrl}" ) updateGitlabCommitStatus(name: 'Build iOS', state: 'failed') } pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') ansiColor('xterm') } parameters { booleanParam(name: 'IOS_REBUILD_3RDPARTY', defaultValue: false, description: 'Should we rebuild 3rdparty deps for iOS?') } environment { BUILD_OPTIONS = ' ' } stages { stage('Update Gitlab commitStatus') { agent any steps { updateGitlabCommitStatus(name: 'Build iOS', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build'){ agent { label 'osx && arm64' } stages { stage('clean previous runs'){ steps{ deleteDir() } } stage('Get build parameters'){ parallel{ stage('Get build options'){ when { allOf { expression { env.gitlabTriggerPhrase != null } expression { env.gitlabTriggerPhrase.contains('BUILD_OPTIONS') } } } steps { script{ BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\"" -f2', returnStdout: true).trim() println BUILD_OPTIONS } } } stage('Get MEGAchat branch'){ steps { script{ env.MEGACHAT_BRANCH = sh(script: 'echo "$gitlabMergeRequestDescription" | grep MEGACHAT_BRANCH_TO_TEST | awk -F "MEGACHAT_BRANCH_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() if (MEGACHAT_BRANCH == ""){ echo "MEGACHAT_BRANCH was not found on description so develop will be used by default" env.MEGACHAT_BRANCH = "develop" } println MEGACHAT_BRANCH } } } stage('Get iOS branch'){ steps { script{ env.IOS_BRANCH = sh(script: 'echo "$gitlabMergeRequestDescription" | grep IOS_BRANCH_TO_TEST | awk -F "IOS_BRANCH_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() if (IOS_BRANCH == ""){ echo "IOS_BRANCH was not found on description so develop will be used by default" env.IOS_BRANCH = "develop" } println IOS_BRANCH } } } } } stage('Checkout SDK MEGAchat and iOS'){ steps { //Clone iOS sh "echo Cloning iOS branch \"${IOS_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "${IOS_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_IOS}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) withCredentials([gitUsernamePassword(credentialsId: 'jenkins_sdk_token_with_user', gitToolName: 'Default')]) { sh "git submodule update --init --recursive" } script{ ios_sources_workspace = WORKSPACE sdk_sources_workspace = "${ios_sources_workspace}/Modules/DataSource/MEGASDK/Sources/MEGASDK" megachat_sources_workspace = "${ios_sources_workspace}/Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK" } dir('Modules/DataSource/MEGAChatSDK/Sources/MEGAChatSDK'){ //Clone MEGAchat sh "echo Cloning megachat branch \"${env.MEGACHAT_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "${env.MEGACHAT_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_MEGACHAT}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) } dir('Modules/DataSource/MEGASDK/Sources/MEGASDK'){ //Clone SDK (with PreBuildMerge) checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) } } } stage('Build SDK and iOS'){ environment { PATH = "/usr/local/bin:${env.PATH}" LIBTOOLIZE = "/usr/local/bin/glibtoolize" } options{ timeout(time: 200, unit: 'MINUTES') } steps{ sh "bundle config set --local path 'vendor/bundle'" sh "bundle install" dir("${megachat_sources_workspace}/src"){ sh "cmake -P genDbSchema.cmake" } script { if (params.IOS_REBUILD_3RDPARTY == "true") { dir("${sdk_sources_workspace}/bindings/ios/3rdparty"){ sh "sh build-all.sh --enable-chat" } } else { sh "bundle exec fastlane configure_sdk_and_chat_library use_cache:true" } } withCredentials([gitUsernamePassword(credentialsId: 'jenkins_sdk_token_with_user', gitToolName: 'Default')]) { sh "bundle exec fastlane update_plugins" sh "bundle exec fastlane build_simulator" } } post { always { sh "tar czvf buildlog.tar.gz buildlog/ || true" archiveArtifacts artifacts: 'buildlog.tar.gz', allowEmptyArchive: true, onlyIfSuccessful: false } } } } post{ always { deleteDir() } failure { node('linux') { // We need an agent able to download logs from Jenkins. script { commentFailedBuild() } } } } } } post { success { updateGitlabCommitStatus(name: 'Build iOS', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :green_apple: SDK iOS SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build iOS', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :green_apple: SDK iOS UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build iOS', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :green_apple: SDK iOS ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_linux_cmake000066400000000000000000000467121516266226600221230ustar00rootroot00000000000000def setCommitStatus(String pStatus, String errorHint = '') { switch (pStatus) { case 'running': pState = 'running' pMessage = "" break case 'success': pState = 'success' pMessage = ":white_check_mark: ${env.JOB_NAME} :penguin: Linux SUCCEEDED :muscle:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'aborted': pState = 'canceled' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: Linux ABORTED :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'build_failure': pState = 'failed' pMessage = ":red_circle: ${env.JOB_NAME} :penguin: Linux FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'tests_failure': pState = 'failed' pMessage = "🟠 ${env.JOB_NAME} :penguin: Linux FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'unstable': pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: Linux UNSTABLE :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break default: pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: Linux UNKNOWN :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" } if (pMessage) { if (errorHint) { pMessage += '
    ' + errorHint } addGitLabMRComment(comment: pMessage) } updateGitlabCommitStatus(name: "${PIPELINE_NAME}", state: pState) } def CUSTOM_TIMEOUT pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { PIPELINE_NAME = 'Build & test linux' BUILD_OPTIONS = '' TESTS_PARALLEL = '' APIURL_TO_TEST = '' GTEST_REPEAT = '' GTEST_FILTER = '' } stages { stage('Update Gitlab commit status') { agent any steps { updateGitlabCommitStatus(name: 'Build & test linux', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build'){ agent { label 'linux && amd64' } stages { stage('clean previous runs'){ steps{ deleteDir() } } stage('Get parameters'){ parallel{ stage('Get build and run paramters'){ steps { script{ BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\\"" -f2 || :', returnStdout: true).trim() TESTS_PARALLEL = sh(script: 'echo "$gitlabTriggerPhrase" | grep "\\-\\-sequence" >/dev/null 2>&1 && echo "" || echo "--INSTANCES:10"', returnStdout: true).trim() GTEST_REPEAT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_repeat=[^ ]*" | awk -F "gtest_repeat=" \'{print "--gtest_repeat="\$2}\'|| :', returnStdout: true).trim() GTEST_FILTER = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_filter=[^ ]*" | awk -F "gtest_filter=" \'{print "--gtest_filter="\$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-timeout=[^ ]*" | awk -F "timeout=" \'{print \$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = CUSTOM_TIMEOUT? CUSTOM_TIMEOUT : "250" println BUILD_OPTIONS println TESTS_PARALLEL println GTEST_REPEAT println GTEST_FILTER println CUSTOM_TIMEOUT } } } stage('Get API URL'){ steps { script{ APIURL_TO_TEST = sh(script: 'echo "$gitlabMergeRequestDescription" | grep USE_APIURL_TO_TEST | awk -F "USE_APIURL_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() println APIURL_TO_TEST if (APIURL_TO_TEST == ""){ APIURL_TO_TEST = "https://g.api.mega.co.nz/" echo "APIURL_TO_TEST was not found on description so ${APIURL_TO_TEST} will be used by default" } echo "APIURL_TO_TEST will be ${APIURL_TO_TEST}" } } } } } stage('Checkout Linux'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) script{ linux_sources_workspace = WORKSPACE //linux_build_agent = "${NODE_NAME}" } } post { failure { script { setCommitStatus('build_failure', ':warning: Checkout failed') } echo 'Error: Checkout stage failed' } } } stage('Build Linux'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ VCPKGPATH = "/opt/vcpkg" BUILD_DIR = "build_dir" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } steps{ dir("${linux_sources_workspace}") { sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" //Build SDK sh "echo Building SDK" sh "cmake -DUSE_LIBUV=ON -DENABLE_QT_BINDINGS=ON -DENABLE_JAVA_BINDINGS=ON -DENABLE_CHAT=ON -DENABLE_MEDIA_FILE_METADATA=ON -DWITH_FUSE=1 -DCMAKE_BUILD_TYPE=Debug -DVCPKG_ROOT=${VCPKGPATH} \ ${BUILD_OPTIONS} -DCMAKE_VERBOSE_MAKEFILE=ON -S ${linux_sources_workspace} -B ${linux_sources_workspace}/${BUILD_DIR}" sh "cmake --build ${linux_sources_workspace}/${BUILD_DIR} -j5" } } post { failure { script { setCommitStatus('build_failure', ':warning: Build failed. Tests won`t run') } echo "Error: Build stage failed. Tests won't run." } } } stage('Run Linux tests'){ environment { MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') BUILD_DIR = "build_dir" } steps{ timeout(time: "${CUSTOM_TIMEOUT}", unit: 'MINUTES') { script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ dir("${linux_sources_workspace}") { script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh "echo Running tests" sh """#!/bin/bash set -x ulimit -c unlimited ${env.BUILD_DIR}/tests/unit/test_unit || FAILED=1 if [ -z \"\$FAILED\" ]; then GFXWORKER_TEST_DIR=${env.BUILD_DIR}/tools/gfxworker/tests/integration/ GFXWORKER_TEST_NAME=gfxworker_test_integration GFXWORKER_TEST_PATH=\"\$GFXWORKER_TEST_DIR\$GFXWORKER_TEST_NAME\" if [ -f \"\$GFXWORKER_TEST_PATH\" ]; then \$GFXWORKER_TEST_PATH || FAILED=2 gzip -c gfxworker_test_integration.log > gfxworker_test_integration_${BUILD_ID}.log.gz || : rm gfxworker_test_integration.log || : fi fi if [ -z \"\$FAILED\" ]; then if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run ${env.BUILD_DIR}/tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} & pid=\$! wait \$pid || FAILED=3 if [ \"\$FAILED\" = \"3\" ]; then cp ${linux_sources_workspace}/pid_\$pid/test_integration.log . fi else # Parallel run ${env.BUILD_DIR}/tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${PIPESTATUS[0]}\" != \"0\" ] && FAILED=3 fi fi if [ -n \"\$FAILED\" ]; then if [ \"\$FAILED\" -le 2 ]; then coreFiles=core else # FAILED=3 if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run coreFiles=\"pid_\$pid/core\" else # Parallel run procFailed=`grep \"<< PROCESS\" tests.stdout | sed 's/.*PID:\\([0-9]*\\).*/\\1/'` if [ -n \"\$procFailed\" ]; then # Parallel run for i in \$procFailed; do coreFiles=\"\$coreFiles pid_\$i/core\" done fi fi fi if [ -n \"\$coreFiles\" ]; then maxTime=10 startTime=`date +%s` coresProcessed=0 coresTotal=`echo \$coreFiles | wc -w` # While there are pending cores while [ \$coresProcessed -lt \$coresTotal ] && [ \$( expr `date +%s` - \$startTime ) -lt \$maxTime ]; do echo "Waiting for core dumps..." sleep 1 for i in \$coreFiles; do if [ -e \"\$i\" ] && [ -z \"\$( lsof \$i 2>/dev/null )\" ]; then echo \"Processing core dump \$i :: \$(grep `echo \$i | sed 's#pid_\\([0-9].*\\)/core#\\1#'` tests.stdout)\" echo thread apply all bt > backtrace echo quit >> backtrace [ \"\$FAILED\" = \"1\" ] && gdb -q ${env.BUILD_DIR}/tests/unit/test_unit \$i -x backtrace [ \"\$FAILED\" = \"2\" ] && gdb -q \$GFXWORKER_TEST_PATH \$i -x backtrace [ \"\$FAILED\" = \"3\" ] && gdb -q ${env.BUILD_DIR}/tests/integration/test_integration \$i -x backtrace tar rf core.tar \$i coresProcessed=`expr \$coresProcessed + 1` coreFiles=`echo \$coreFiles | sed -e \"s#\$i##\"` fi done done if [ -e core.tar ]; then [ \"\$FAILED\" = \"1\" ] && tar rf core.tar -C ${env.BUILD_DIR}/tests/unit/ test_unit [ \"\$FAILED\" = \"2\" ] && tar rf core.tar -C \$GFXWORKER_TEST_DIR \$GFXWORKER_TEST_NAME [ \"\$FAILED\" = \"3\" ] && tar rf core.tar -C ${env.BUILD_DIR}/tests/integration/ test_integration gzip core.tar fi fi fi gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz || : rm test_integration.log || : if [ -n \"\$FAILED\" ]; then exit \"\$FAILED\" fi """ } } } } } post { always { dir("${env.WORKSPACE}/src/fuse/supported/platform/posix/scripts") { println "Attempting to clean up any stale MEGA-FS mounts" sh "./cleanup.sh '${env.WORKSPACE}'" } } failure { script { setCommitStatus('tests_failure', ':warning: Build succeed. Tests failed') } echo 'Error: Test stage failed' } } } } post{ always { dir("${env.WORKSPACE}/src/fuse/supported/platform/posix/scripts") { echo "Final cleanup of any stale MEGA-FS mounts" sh "./cleanup.sh '${env.WORKSPACE}'" } archiveArtifacts artifacts: '*test_integration*, core.tar.gz', fingerprint: true deleteDir() } } } } post { success { updateGitlabCommitStatus(name: 'Build & test linux', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :penguin: Linux SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build & test linux', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: Linux UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build & test linux', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: Linux ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_macOS_cmake000066400000000000000000000405341516266226600217620ustar00rootroot00000000000000def setCommitStatus(String pStatus, String errorHint = '') { switch (pStatus) { case 'running': pState = 'running' pMessage = "" break case 'success': pState = 'success' pMessage = ":white_check_mark: ${env.JOB_NAME} :green_apple: MacOS SUCCEEDED :muscle:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'aborted': pState = 'canceled' pMessage = ":interrobang: ${env.JOB_NAME} :green_apple: MacOS ABORTED :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'build_failure': pState = 'failed' pMessage = ":red_circle: ${env.JOB_NAME} :green_apple: MacOS FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'tests_failure': pState = 'failed' pMessage = "🟠 ${env.JOB_NAME} :green_apple: MacOS FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'unstable': pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :green_apple: MacOS UNSTABLE :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break default: pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :green_apple: MacOS UNKNOWN :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" } if (pMessage) { if (errorHint) { pMessage += '
    ' + errorHint } addGitLabMRComment(comment: pMessage) } updateGitlabCommitStatus(name: "${PIPELINE_NAME}", state: pState) } def CUSTOM_TIMEOUT pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { PIPELINE_NAME = 'Build & test macOS' BUILD_OPTIONS = '' TESTS_PARALLEL = '' APIURL_TO_TEST = '' } stages { stage('Update Gitlab CommitStatus') { agent any steps { updateGitlabCommitStatus(name: 'Build & test macOS', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build'){ agent { label 'osx && arm64' } stages{ stage('clean previous runs'){ steps{ deleteDir() } } stage('Get parameters'){ parallel{ stage('Get build and run paramters'){ steps { script{ BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\\"" -f2 || :', returnStdout: true).trim() TESTS_PARALLEL = sh(script: 'echo "$gitlabTriggerPhrase" | grep "\\-\\-sequence" >/dev/null 2>&1 && echo "" || echo "--INSTANCES:10"', returnStdout: true).trim() GTEST_REPEAT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_repeat=[^ ]*" | awk -F "gtest_repeat=" \'{print "--gtest_repeat="\$2}\'|| :', returnStdout: true).trim() GTEST_FILTER = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_filter=[^ ]*" | awk -F "gtest_filter=" \'{print "--gtest_filter="\$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-timeout=[^ ]*" | awk -F "timeout=" \'{print \$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = CUSTOM_TIMEOUT? CUSTOM_TIMEOUT : "450" println BUILD_OPTIONS println TESTS_PARALLEL println GTEST_REPEAT println GTEST_FILTER println CUSTOM_TIMEOUT } } } stage('Get API URL'){ steps { script{ APIURL_TO_TEST = sh(script: 'echo "$gitlabMergeRequestDescription" | grep USE_APIURL_TO_TEST | awk -F "USE_APIURL_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() println APIURL_TO_TEST if (APIURL_TO_TEST == ""){ APIURL_TO_TEST = "https://g.api.mega.co.nz/" echo "APIURL_TO_TEST was not found on description so ${APIURL_TO_TEST} will be used by default" } echo "APIURL_TO_TEST will be ${APIURL_TO_TEST}" } } } } } stage('Checkout macOS'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) script{ mac_sources_workspace = WORKSPACE sh "echo ${BUILD_OPTIONS}" } } post { failure { script { setCommitStatus('build_failure', ':warning: Checkout failed') } echo 'Error: Checkout stage failed' } } } stage('Build macOS'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ PATH = "/usr/local/bin:${env.PATH}" VCPKGPATH = "${env.HOME}/jenkins/vcpkg" BUILD_DIR = "build_dir" BUILD_DIR_X64 = "build_dir_x64" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" MEGAQTPATH = "${env.HOME}/Qt-build/5.15.16/5.15.16" COMMON_CMAKE_OPTIONS = "-DUSE_LIBUV=ON -DENABLE_QT_BINDINGS=ON -DENABLE_JAVA_BINDINGS=ON -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_CHAT=ON -DVCPKG_ROOT=${VCPKGPATH} ${BUILD_OPTIONS}" } steps{ dir(mac_sources_workspace){ //Build SDK for arm64 sh "echo Building SDK for arm64" sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" sh "cmake -DCMAKE_PREFIX_PATH=${MEGAQTPATH}/arm64 ${COMMON_CMAKE_OPTIONS} -S ${mac_sources_workspace} -B ${mac_sources_workspace}/${BUILD_DIR}" sh "cmake --build ${mac_sources_workspace}/${BUILD_DIR} -j3" //Build SDK for x64 sh "echo \"Building SDK for x64 (crosscompiling)\"" sh "rm -rf ${BUILD_DIR_X64}; mkdir ${BUILD_DIR_X64}" sh "cmake -DCMAKE_PREFIX_PATH=${MEGAQTPATH}/x86_64 ${COMMON_CMAKE_OPTIONS} -DCMAKE_OSX_ARCHITECTURES=x86_64 -S ${mac_sources_workspace} -B ${mac_sources_workspace}/${BUILD_DIR_X64}" sh "cmake --build ${mac_sources_workspace}/${BUILD_DIR_X64} -j3" } } post { failure { script { setCommitStatus('build_failure', ':warning: Build failed. Tests won`t run') } echo "Error: Build stage failed. Tests won't run." } } } stage('Run macOS tests'){ environment { MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') BUILD_DIR = "build_dir" } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ dir(mac_sources_workspace){ timeout(time: "${CUSTOM_TIMEOUT}", unit: 'MINUTES') { script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh "echo Running tests" sh """#!/bin/zsh setopt nonomatch cd ${env.BUILD_DIR} ./tests/unit/test_unit & pid=\$! wait \$pid || FAILED=1 if [ -z \"\$FAILED\" ]; then ./tools/gfxworker/tests/integration/gfxworker_test_integration & pid=\$! wait \$pid || FAILED=2 gzip -c gfxworker_test_integration.log > gfxworker_test_integration_${BUILD_ID}.log.gz || : rm gfxworker_test_integration.log || : fi if [ -z \"\$FAILED\" ]; then if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run ./tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} & pid=\$! wait \$pid || FAILED=3 if [ \"\$FAILED\" = \"3\" ]; then cp ${mac_sources_workspace}/pid_\$pid/test_integration.log . fi else # Parallel run ./tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${pipestatus[1]}\" != \"0\" ] && FAILED=3 fi fi # Process ips files in case some test crashes if [ -z \"${TESTS_PARALLEL}\" ]; then pid_list=\$pid else pid_list=`grep \"<< PROCESS\" tests.stdout | sed 's/.*PID:\\([0-9]*\\).*/\\1/' | tr '\\n' ' '` fi for ips_file in ~/Library/Logs/DiagnosticReports/*.ips; do ips_pid=`jq '.pid?, .PID?' \$ips_file | grep -iv null` if echo \$pid_list | grep -q -w "\$ips_pid" ; then cp -v \$ips_file . cat \$ips_file fi done gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz || : if [ -n "\${FAILED}" ]; then echo "FAILED: \${FAILED}" exit "\${FAILED}" fi """ } } } } } post { failure { script { setCommitStatus('tests_failure', ':warning: Build succeed. Tests failed') } echo 'Error: Test stage failed' } } } } post{ always { archiveArtifacts artifacts: 'build_dir/*log.gz, build_dir/examples-*.tar.gz', fingerprint: true, onlyIfSuccessful: false archiveArtifacts artifacts: 'build_dir/*.ips', allowEmptyArchive: true, onlyIfSuccessful: false deleteDir() } } } } post { success { updateGitlabCommitStatus(name: 'Build & test macOS', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :green_apple: macOS SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build & test macOS', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :green_apple: macOS UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build & test macOS', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :green_apple: macOS ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_megachat_cmake000066400000000000000000000435641516266226600225370ustar00rootroot00000000000000def setCommitStatus(String pStatus, String errorHint = '') { switch (pStatus) { case 'running': pState = 'running' pMessage = "" break case 'success': pState = 'success' pMessage = ":white_check_mark: ${env.JOB_NAME} :penguin: MEGAchat SUCCEEDED :muscle:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'aborted': pState = 'canceled' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: MEGAchat ABORTED :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'build_failure': pState = 'failed' pMessage = ":red_circle: ${env.JOB_NAME} :penguin: MEGAchat FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'tests_failure': pState = 'failed' pMessage = "🟠 ${env.JOB_NAME} :penguin: MEGAchat FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'unstable': pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: MEGAchat UNSTABLE :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break default: pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} :penguin: MEGAchat UNKNOWN :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" } if (pMessage) { if (errorHint) { pMessage += '
    ' + errorHint } addGitLabMRComment(comment: pMessage) } updateGitlabCommitStatus(name: "${PIPELINE_NAME}", state: pState) } def CUSTOM_TIMEOUT pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { PIPELINE_NAME = 'Build & test MEGAchat' BUILD_OPTIONS = ' ' APIURL_TO_TEST = ' ' } stages { stage('Update Gitlab commitStatus') { agent any steps { updateGitlabCommitStatus(name: 'Build & test MEGAchat', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build') { agent { label 'linux && amd64' } stages { stage('clean previous runs'){ steps{ deleteDir() } } stage('Get build parameters'){ parallel{ stage('Get build options'){ steps { script{ BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\\"" -f2 || :', returnStdout: true).trim() TESTS_PARALLEL = sh(script: 'echo "$gitlabTriggerPhrase" | grep "\\-\\-sequence" >/dev/null 2>&1 && echo "" || echo "--INSTANCES:10"', returnStdout: true).trim() GTEST_REPEAT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_repeat=[^ ]*" | awk -F "gtest_repeat=" \'{print "--gtest_repeat="\$2}\'|| :', returnStdout: true).trim() GTEST_FILTER = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_filter=[^ ]*" | awk -F "gtest_filter=" \'{print "--gtest_filter="\$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-timeout=[^ ]*" | awk -F "timeout=" \'{print \$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = CUSTOM_TIMEOUT? CUSTOM_TIMEOUT : "150" println BUILD_OPTIONS println TESTS_PARALLEL println GTEST_REPEAT println GTEST_FILTER println CUSTOM_TIMEOUT } } } stage('Get MEGAchat branch'){ steps { script{ env.MEGACHAT_BRANCH = sh(script: 'echo "$gitlabMergeRequestDescription" | grep MEGACHAT_BRANCH_TO_TEST | awk -F "MEGACHAT_BRANCH_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() if (MEGACHAT_BRANCH == ""){ echo "MEGACHAT_BRANCH was not found on description so develop will be used by default" env.MEGACHAT_BRANCH = "develop" } println MEGACHAT_BRANCH } } } stage('Get API URL'){ steps { script{ APIURL_TO_TEST = sh(script: 'echo "$gitlabMergeRequestDescription" | grep USE_APIURL_TO_TEST | awk -F "USE_APIURL_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() println APIURL_TO_TEST if (APIURL_TO_TEST == ""){ APIURL_TO_TEST = "https://g.api.mega.co.nz/" echo "APIURL_TO_TEST was not found on description so ${APIURL_TO_TEST} will be used by default" } echo "APIURL_TO_TEST will be ${APIURL_TO_TEST}" } } } } } stage('Checkout SDK and MEGAchat'){ steps { //Clone MEGAchat sh "echo Cloning MEGAchat branch \"${MEGACHAT_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "origin/${MEGACHAT_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_MEGACHAT}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) dir('third-party/mega'){ //Clone SDK (with PreBuildMerge) checkout([ $class: 'GitSCM', branches: [[name: "origin/${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) } script{ megachat_sources_workspace = WORKSPACE sdk_sources_workspace = "${megachat_sources_workspace}/third-party/mega" } } post { failure { script { setCommitStatus('build_failure', ':red_circle: Checkout failed') } echo 'Error: Checkout stage failed' } } } stage('Build MEGAchat'){ environment{ VCPKGPATH = "/opt/vcpkg" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" BUILD_DIR = "build_dir" } steps{ dir(megachat_sources_workspace){ sh "echo Building MEGAchat" sh "cmake --preset dev-unix -DCMAKE_BUILD_TYPE=Debug -DVCPKG_ROOT=${VCPKGPATH} ${BUILD_OPTIONS} -DCMAKE_VERBOSE_MAKEFILE=ON \ -S ${megachat_sources_workspace} -B ${megachat_sources_workspace}/${BUILD_DIR}" sh "cmake --build ${megachat_sources_workspace}/${BUILD_DIR} -j5" } } post { failure { script { setCommitStatus('build_failure', ':red_circle: Build failed. Tests won`t run') } echo "Error: Build stage failed. Tests won't run." } } } stage('Run MEGAchat tests'){ environment { MEGA_PWD0 = credentials('MEGA_PWD_DEFAULT') MEGA_PWD1 = credentials('MEGA_PWD_DEFAULT') MEGA_PWD2 = credentials('MEGA_PWD_DEFAULT') BUILD_DIR = "build_dir" } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ dir("${megachat_sources_workspace}"){ timeout(time: "${CUSTOM_TIMEOUT}", unit: 'MINUTES') { script{ env.MEGA_EMAIL0 = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh """#!/bin/bash set -x ulimit -c unlimited if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run ${env.BUILD_DIR}/tests/sdk_test/megachat_tests --USERAGENT:${env.USER_AGENT_TESTS_MEGACHAT} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} & pid=\$! wait \$pid || FAILED=1 else # Parallel run ${env.BUILD_DIR}/tests/sdk_test/megachat_tests --USERAGENT:${env.USER_AGENT_TESTS_MEGACHAT} --APIURL:${APIURL_TO_TEST} ${GTEST_FILTER} ${GTEST_REPEAT} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${PIPESTATUS[0]}\" != \"0\" ] && FAILED=1 fi if [ -n "\$FAILED" ]; then if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run coreFiles=\"test_pid_\$pid/core\" else # Parallel run procFailed=`grep \"<< PROCESS\" tests.stdout | sed 's/.*PID:\\([0-9]*\\).*/\\1/'` if [ -n \"\$procFailed\" ]; then for i in \$procFailed; do coreFiles=\"\$coreFiles test_pid_\$i/core\" done fi fi fi if [ -n \"\$coreFiles\" ]; then maxTime=10 startTime=`date +%s` coresProcessed=0 coresTotal=`echo \$coreFiles | wc -w` # While there are pending cores while [ \$coresProcessed -lt \$coresTotal ] && [ \$( expr `date +%s` - \$startTime ) -lt \$maxTime ]; do echo "Waiting for core dumps..." sleep 1 for i in \$coreFiles; do if [ -e \"\$i\" ] && [ -z \"\$( lsof \$i 2>/dev/null )\" ]; then echo echo echo \"Processing core dump \$i :: \$(grep `echo \$i | sed 's#test_pid_\\([0-9].*\\)/core#\\1#'` tests.stdout)\" echo thread apply all bt > backtrace echo quit >> backtrace gdb -q ${env.BUILD_DIR}/tests/sdk_test/megachat_tests \$i -x backtrace tar rf core.tar \$i coresProcessed=`expr \$coresProcessed + 1` coreFiles=`echo \$coreFiles | sed -e \"s#\$i##\"` fi done done if [ -e core.tar ]; then tar rf core.tar -C ${env.BUILD_DIR}/tests/sdk_test/ megachat_tests gzip core.tar fi fi gzip -c test.log > test_${BUILD_ID}.log.gz || : rm test.log || : if [ ! -z "\$FAILED" ]; then false fi """ } } } } } post { failure { script { setCommitStatus('tests_failure', ':warning: Build succeed. Tests failed') } echo 'Error: Test stage failed' } } } } post{ always { archiveArtifacts artifacts: 'test*.log*, core.tar.gz', fingerprint: true deleteDir() } } } } post { success { updateGitlabCommitStatus(name: 'Build & test MEGAchat', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} :penguin: MEGAchat SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build & test MEGAchat', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: MEGAchat UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build & test MEGAchat', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} :penguin: MEGAchat ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_MR_windows_cmake000066400000000000000000000417141516266226600224530ustar00rootroot00000000000000def setCommitStatus(String pStatus, String errorHint = '') { switch (pStatus) { case 'running': pState = 'running' pMessage = "" break case 'success': pState = 'success' pMessage = ":white_check_mark: ${env.JOB_NAME} Windows SUCCEEDED :muscle:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'aborted': pState = 'canceled' pMessage = ":interrobang: ${env.JOB_NAME} Windows ABORTED :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'build_failure': pState = 'failed' pMessage = ":red_circle: ${env.JOB_NAME} Windows FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'tests_failure': pState = 'failed' pMessage = "🟠 ${env.JOB_NAME} Windows FAILURE :worried:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break case 'unstable': pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} Windows UNSTABLE :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" break default: pState = 'failed' pMessage = ":interrobang: ${env.JOB_NAME} Windows UNKNOWN :confused:
    Build results: [Jenkins [${env.BUILD_DISPLAY_NAME}]](${env.RUN_DISPLAY_URL})
    Commit: ${env.GIT_COMMIT}" } if (pMessage) { if (errorHint) { pMessage += '
    ' + errorHint } addGitLabMRComment(comment: pMessage) } updateGitlabCommitStatus(name: "${PIPELINE_NAME}", state: pState) } def BUILD_OPTIONS def TESTS_PARALLEL def GTEST_REPEAT def GTEST_FILTER def WIN32_FLAG def CUSTOM_TIMEOUT pipeline { agent none options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { PIPELINE_NAME = 'Build & test windows' BUILD_OPTIONS = '' TESTS_PARALLEL = '' APIURL_TO_TEST = '' } stages { stage('Update Gitlab commitStatus') { agent any steps { updateGitlabCommitStatus(name: 'Build & test windows', state: 'running') script{ COMMIT_ID = env.GIT_COMMIT println GIT_COMMIT } } } stage('Build') { agent { label 'windows && amd64' } stages { stage('clean previous runs and update gitlab commit status'){ steps{ deleteDir() } } stage('Get parameters'){ parallel{ stage('Get build and run paramters'){ steps { script{ BUILD_OPTIONS = sh(script: 'echo "$gitlabTriggerPhrase" | grep BUILD_OPTIONS | awk -F "BUILD_OPTIONS=" \'{print \$2}\' | cut -d"\\"" -f2 || :', returnStdout: true).trim() TESTS_PARALLEL = sh(script: 'echo "$gitlabTriggerPhrase" | grep "\\-\\-sequence" >/dev/null 2>&1 && echo "" || echo "--INSTANCES:10"', returnStdout: true).trim() GTEST_REPEAT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_repeat=[^ ]*" | awk -F "gtest_repeat=" \'{print "--gtest_repeat="\$2}\'|| :', returnStdout: true).trim() GTEST_FILTER = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-gtest_filter=[^ ]*" | awk -F "gtest_filter=" \'{print "--gtest_filter="\$2}\'|| :', returnStdout: true).trim() WIN32_FLAG = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-windows-32bits" || :', returnStdout: true).trim() CUSTOM_TIMEOUT = sh(script: 'echo "$gitlabTriggerPhrase" | grep --only-matching "\\-\\-timeout=[^ ]*" | awk -F "timeout=" \'{print \$2}\'|| :', returnStdout: true).trim() CUSTOM_TIMEOUT = CUSTOM_TIMEOUT? CUSTOM_TIMEOUT : "250" println BUILD_OPTIONS println TESTS_PARALLEL println GTEST_REPEAT println GTEST_FILTER println WIN32_FLAG println CUSTOM_TIMEOUT } } } stage('Get API URL'){ steps { script{ APIURL_TO_TEST = sh(script: 'echo "$gitlabMergeRequestDescription" | grep USE_APIURL_TO_TEST | awk -F "USE_APIURL_TO_TEST=" \'{print \$2}\' | cut -d" " -f1', returnStdout: true).trim() println APIURL_TO_TEST if (APIURL_TO_TEST == ""){ APIURL_TO_TEST = "https://g.api.mega.co.nz/" echo "APIURL_TO_TEST was not found on description so ${APIURL_TO_TEST} will be used by default" } echo "APIURL_TO_TEST will be ${APIURL_TO_TEST}" } } } } } stage('Checkout Windows'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.gitlabSourceBranch}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], [$class: 'PreBuildMerge', options: [fastForwardMode: 'FF', mergeRemote: "origin", mergeStrategy: 'DEFAULT', mergeTarget: "${env.gitlabTargetBranch}"]] ] ]) script{ windows_sources_workspace = WORKSPACE } } post { failure { script { setCommitStatus('build_failure', ':warning: Checkout failed') } echo 'Error: Checkout stage failed' } } } stage('Build Windows'){ environment{ VCPKGPATH = "${windows_sources_workspace}\\..\\vcpkg" /* * VCPKG cache in S4 commented out because it takes too long time. * See CID-839 * VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' * AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') * AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') * AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" */ _MSPDBSRV_ENDPOINT_ = "${BUILD_TAG}" TMP = "${windows_sources_workspace}\\tmp" TEMP = "${windows_sources_workspace}\\tmp" TMPDIR = "${windows_sources_workspace}\\tmp" MEGAQTPATH_X64 = "C:\\Qt\\Qt5.15.16\\5.15.16\\x64" MEGAQTPATH_X86 = "C:\\Qt\\Qt5.15.16\\5.15.16\\x86" CMAKE_COMMON = "-DSWIG_EXECUTABLE='C:\\swigwin-4.0.2\\swig.exe' -DUSE_LIBUV=ON -DENABLE_QT_BINDINGS=ON -DENABLE_JAVA_BINDINGS=ON -DENABLE_MEDIA_FILE_METADATA=ON -DWITH_FUSE=1 -DCMAKE_VERBOSE_MAKEFILE=ON" } options{ timeout(time: 150, unit: 'MINUTES') } steps{ dir(windows_sources_workspace){ //Build SDK sh "echo Building SDK x64" sh "mkdir build_dir" sh "mkdir tmp" sh "cmake -DCMAKE_PREFIX_PATH='${MEGAQTPATH_X64}' -DENABLE_CHAT=ON -DVCPKG_ROOT='${VCPKGPATH}' ${CMAKE_COMMON} ${BUILD_OPTIONS} -DCMAKE_GENERATOR_PLATFORM=x64 -S '${windows_sources_workspace}' -B '${windows_sources_workspace}'\\\\build_dir\\\\" sh "cmake --build '${windows_sources_workspace}'\\\\build_dir\\\\ --config Debug -j 1" sh "echo Building SDK x86" sh "mkdir build_dir_x86" sh "cmake -DCMAKE_PREFIX_PATH='${MEGAQTPATH_X86}' -DENABLE_CHAT=ON -DVCPKG_ROOT='${VCPKGPATH}' ${CMAKE_COMMON} ${BUILD_OPTIONS} -DCMAKE_GENERATOR_PLATFORM=Win32 -S '${windows_sources_workspace}' -B '${windows_sources_workspace}'\\\\build_dir_x86\\\\" sh "cmake --build '${windows_sources_workspace}'\\\\build_dir_x86\\\\ --config Debug -j 1" } } post { failure { script { setCommitStatus('build_failure', ':warning: Build failed. Tests won`t run') } echo "Error: Build stage failed. Tests won't run." } } } stage('Run Windows tests'){ environment { PATH = "${windows_sources_workspace}\\\\vcpkg_installed\\\\x64-windows-mega\\\\debug\\\\bin;${env.PATH}" MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') SYMBOL_PATH = "srv*C:\\symcache*;http://msdl.microsoft.com/download/symbols;${WORKSPACE}\\build_dir;${WORKSPACE}\\build_dir_x86;${WORKSPACE}\\build_dir\\tests\\integration\\Debug;${WORKSPACE}\\build_dir_x86\\tests\\integration\\Debug" USER_AGENT_TESTS_SDK = "${USER_AGENT_TESTS_SDK}" APIURL_TO_TEST = "${APIURL_TO_TEST}" GTEST_FILTER = "${GTEST_FILTER}" GTEST_REPEAT = "${GTEST_REPEAT}" TESTS_PARALLEL = "${TESTS_PARALLEL}" } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } env.BUILD_DIR = WIN32_FLAG ? "build_dir_x86" : "build_dir" lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ dir("${windows_sources_workspace}") { timeout(time: "${CUSTOM_TIMEOUT}", unit: 'MINUTES') { script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } bat """ cd %BUILD_DIR% tests\\\\unit\\\\Debug\\\\test_unit.exe if %ERRORLEVEL% NEQ 0 exit 1 set gfxworkerTestIntegration=tools\\\\gfxworker\\\\tests\\\\integration\\\\Debug\\\\gfxworker_test_integration.exe if exist %gfxworkerTestIntegration% ( %gfxworkerTestIntegration% gzip -c gfxworker_test_integration.log > gfxworker_test_integration_${BUILD_ID}.log.gz rm gfxworker_test_integration.log ) if %ERRORLEVEL% NEQ 0 exit 1 """ def integration_tests_error = powershell script: 'jenkinsfile\\windows-integration-tests.ps1', returnStatus: true sh """ if [ -z "${TESTS_PARALLEL}" ]; then tar czf test_integration_${BUILD_ID}.tgz pid_*/test_integration*.log || true fi gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz || true """ if (integration_tests_error) { sh """ tar czf dumps.tgz dumps || true tar czf procdump.tgz procdump*.log || true tar czf pdbs.tgz $BUILD_DIR/Debug/*.pdb $BUILD_DIR/tests/integration/Debug/*.pdb exit ${integration_tests_error} """ } } } } } } post { failure { script { setCommitStatus('tests_failure', ':warning: Build succeed. Tests failed') } echo 'Error: Test stage failed' } } } } post{ always { archiveArtifacts artifacts: "build_dir/*.log", allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: "build_dir/*.log.gz", allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: "build_dir_x86/*.log", allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: "build_dir_x86/*.log.gz", allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: 'dumps.tgz', allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: 'pdbs.tgz', allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: 'procdump.tgz', allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: 'test_integration*.log.gz', allowEmptyArchive: true, fingerprint: true archiveArtifacts artifacts: 'test_integration*.tgz', allowEmptyArchive: true, fingerprint: true deleteDir() } } } } post { success { updateGitlabCommitStatus(name: 'Build & test windows', state: 'success') addGitLabMRComment(comment: ":white_check_mark: ${currentBuild.projectName} Windows SUCCEEDED :muscle:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } unstable { updateGitlabCommitStatus(name: 'Build & test windows', state: 'failed') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} Windows UNSTABLE :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } aborted { updateGitlabCommitStatus(name: 'Build & test windows', state: 'canceled') addGitLabMRComment(comment: ":interrobang: ${currentBuild.projectName} Windows ABORTED :confused:
    Build results: [Jenkins [${currentBuild.displayName}]](${currentBuild.absoluteUrl})
    Commit: ${COMMIT_ID}" ) } } } sdk-10.11.0/jenkinsfile/Jenkinsfile_Release_Management000066400000000000000000000206051516266226600227330ustar00rootroot00000000000000pipeline { agent { label 'docker' } options { buildDiscarder(logRotator(numToKeepStr: '25', daysToKeepStr: '30')) gitLabConnection('GitLabConnectionJenkins') } parameters { choice(name: 'ACTION', choices: ['Create_Release', 'Create_Release_Candidate', 'Close_Release', 'Patch_Release'], description: 'Pick an action') string(name: 'SLACK_THREAD_ANNOUNCE', defaultValue: '', description: 'Id of thread root message (only required for creating new releases or RCs).') string(name: 'TARGET_APPS', defaultValue: '', description: 'e.g. Android 1.0.1 / iOS 1.2 / MEGAsync 9.9.9 RC1 (only required for creating releases, not for closing)') string(name: 'RELEASE_VERSION', defaultValue: '', description: 'Define release version (ie. 8.5.0). Optional for Create Release, mandatory for other actions.') string(name: 'TICKETS', defaultValue: '', description: 'Comma separated tickets. Required for Patch release or Create RC.') password(name: 'GPG_PASSWORD', defaultValue: '', description: 'Enter the password. Only required for creating new releases and patched releases.') } environment { //JIRA_SDK_CREDS = credentials('SDK_JIRA') SLACK_TOKEN = credentials('sdk_slack_bot_releases') GITLAB_TOKEN = credentials('SDK_releases_gitlab_token') GPG_KEYGRIP = credentials('sdk_gpg_keygrip_release_management') gpg_key = credentials('sdk_gpg_key_release_management') GITHUB_TOKEN = credentials('sdk_github_token') GITHUB_USER = credentials('sdk_github_username') COMMIT_EMAIL = 'sdkdev+releases@mega.co.nz' COMMIT_NAME = 'SDK Releases' project_name = "SDK" slack_channel = "sdk" slack_channel_dev_requests = "sdk_devs_only" gitlab_url = "${env.GITLAB_BASE_URL}" jira_url = "${env.JIRA_BASE_URL}" confluence_url = "${env.CONFLUENCE_BASE_URL}" confluence_page_id = "3640225" GNUPGHOME = "${WORKSPACE}/.gnupg" JIRA_TOKEN = credentials('SDK_JIRA_PERSONAL_ACCESS_TOKEN') CONFLUENCE_TOKEN = credentials('SDK_CONFLUENCE_PERSONAL_ACCESS_TOKEN') } stages { stage('Replace config file'){ steps { dir("automation"){ sh 'cp config.toml.template config.toml' script { sh """ export github_push_remote_url="https://\${GITHUB_USER}:\${GITHUB_TOKEN}@github.com/meganz/sdk.git" sed -i 's|project_name = ""|project_name = "${env.project_name}"|' config.toml sed -i 's|target_apps = ""|target_apps = "${params.TARGET_APPS}"|' config.toml sed -i 's|gitlab_url = ""|gitlab_url = "${env.gitlab_url}"|' config.toml sed -i 's|jira_url = ""|jira_url = "${env.jira_url}"|' config.toml sed -i 's|slack_channel_dev_requests = ""|slack_channel_dev_requests = "${env.slack_channel_dev_requests}"|' config.toml sed -i 's|slack_channel_announce = ""|slack_channel_announce = "${env.slack_channel}"|' config.toml sed -i 's|slack_thread_announce = ""|slack_thread_announce = "${params.SLACK_THREAD_ANNOUNCE}"|' config.toml sed -i 's|release_version = ""|release_version = "${params.RELEASE_VERSION}"|' config.toml sed -i 's|github_push_remote_url = ""|github_push_remote_url = "'\${github_push_remote_url}'"|' config.toml sed -i 's|confluence_url = ""|confluence_url = "${env.confluence_url}"|' config.toml sed -i 's|tickets = ""|tickets = "${params.TICKETS}"|' config.toml """ } } // Configure GPG Agent sh """ mkdir -m 700 $GNUPGHOME touch $GNUPGHOME/gpg-agent.conf echo 'allow-preset-passphrase' >> $GNUPGHOME/gpg-agent.conf echo 'default-cache-ttl 3600' >> $GNUPGHOME/gpg-agent.conf echo 'max-cache-ttl 3600' >> $GNUPGHOME/gpg-agent.conf """ // Configure gitlab sh "git remote remove origin" sh """ set +x export gitlab_push_remote_url="https://gitlab-ci-token:\${GITLAB_TOKEN}@${GIT_URL_SDK_FOR_HTTPS_TOKEN}" git remote add origin \${gitlab_push_remote_url} """ } } stage('Build docker image') { steps { sh "cp automation/requirements.txt dockerfile/requirements.txt" dir ("dockerfile") { sh """ docker build \ --build-arg USER_ID=\$(id -u) \ --build-arg GROUP_ID=\$(id -g) \ -f release-management.dockerfile \ -t meganz/sdk-release-management:${env.BUILD_NUMBER} \ . """ } } } stage('Create Release'){ when { beforeAgent true expression { params.ACTION == "Create_Release" } } agent { docker { image "meganz/sdk-release-management:${env.BUILD_NUMBER}" reuseNode true } } steps { dir("automation"){ sh 'gpg --batch --import $gpg_key' sh 'gpg --list-secret-keys' sh 'set +x; /usr/lib/gnupg2/gpg-preset-passphrase --preset --passphrase $GPG_PASSWORD $GPG_KEYGRIP' sh """ git config --global user.email "${COMMIT_EMAIL}" git config --global user.name "${COMMIT_NAME}" """ sh 'python3 ./make_release.py config.toml' } } } stage ('Create Release Candidate'){ when { beforeAgent true expression { params.ACTION == "Create_Release_Candidate" } } agent { docker { image "meganz/sdk-release-management:${env.BUILD_NUMBER}" reuseNode true } } steps { dir("automation"){ sh 'gpg --batch --import $gpg_key' sh 'gpg --list-secret-keys' sh 'python3 ./make_another_rc.py config.toml' } } } stage ('Close Release'){ when { beforeAgent true expression { params.ACTION == "Close_Release" } } agent { docker { image "meganz/sdk-release-management:${env.BUILD_NUMBER}" reuseNode true } } steps { dir("automation"){ sh 'gpg --batch --import $gpg_key' sh 'gpg --list-secret-keys' sh 'python3 ./close_release.py config.toml' } } } stage ('Patch Release'){ when { beforeAgent true expression { params.ACTION == "Patch_Release" } } agent { docker { image "meganz/sdk-release-management:${env.BUILD_NUMBER}" reuseNode true } } steps { dir("automation"){ sh 'gpg --batch --import $gpg_key' sh 'gpg --list-secret-keys' sh 'set +x; /usr/lib/gnupg2/gpg-preset-passphrase --preset --passphrase $GPG_PASSWORD $GPG_KEYGRIP' sh """ git config --global user.email "${COMMIT_EMAIL}" git config --global user.name "${COMMIT_NAME}" """ sh 'python3 ./patch_release.py config.toml' } } } } post { always { sh "docker image rm meganz/sdk-release-management:${env.BUILD_NUMBER}" deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/cron_jobs/000077500000000000000000000000001516266226600167265ustar00rootroot00000000000000sdk-10.11.0/jenkinsfile/cron_jobs/Jenkinsfile_report_jira000066400000000000000000000077131516266226600235220ustar00rootroot00000000000000pipeline { agent {label "docker"} options { buildDiscarder(logRotator(numToKeepStr: '32', daysToKeepStr: '14')) ansiColor('xterm') } parameters { booleanParam( name: 'REBUILD_IMAGE', defaultValue: false, description: 'Rebuild docker image? If the report script has changed, the image should be rebuilt.' ) } stages { stage('Rebuild docker image') { when { beforeAgent true expression { return params.REBUILD_IMAGE } } steps { sh """ docker build \ -f dockerfile/report-jira-slack.dockerfile \ -t ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest \ . """ withCredentials([usernamePassword(credentialsId: 'artifactory-jenkins-docker', usernameVariable: 'ART_USER', passwordVariable: 'ART_PASS')]) { sh """ echo \$ART_PASS | docker login \ -u \$ART_USER \ --password-stdin \ ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443 docker push ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest """ } } } stage('Run report'){ steps { withCredentials([usernamePassword(credentialsId: 'artifactory-jenkins-docker', usernameVariable: 'ART_USER', passwordVariable: 'ART_PASS')]) { sh """ echo \$ART_PASS | docker login \ -u \$ART_USER \ --password-stdin \ ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443 docker pull ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest """ } withCredentials([ string(credentialsId: 'SDK_JIRA_PERSONAL_ACCESS_TOKEN', variable: 'SDK_JIRA_PERSONAL_ACCESS_TOKEN'), string(credentialsId: 'SDK_SLACK_BOT_TOKEN', variable: 'SDK_SLACK_BOT_TOKEN') ]){ // SDK report sh """ docker run \ --rm \ -e JIRA_PERSONAL_ACCESS_TOKEN=\${SDK_JIRA_PERSONAL_ACCESS_TOKEN} \ -e SLACK_BOT_TOKEN=\${SDK_SLACK_BOT_TOKEN} \ -e JIRA_URL=${env.JIRA_URL} \ -e JIRA_PROJECT_KEY=${env.SDK_JIRA_PROJECT_KEY} \ -e SLACK_CHANNEL=${env.SDK_SLACK_CHANNEL} \ ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest """ // CHT report sh """ docker run \ --rm \ -e JIRA_PERSONAL_ACCESS_TOKEN=\${SDK_JIRA_PERSONAL_ACCESS_TOKEN} \ -e SLACK_BOT_TOKEN=\${SDK_SLACK_BOT_TOKEN} \ -e JIRA_URL=${env.JIRA_URL} \ -e JIRA_PROJECT_KEY=${env.CHT_JIRA_PROJECT_KEY} \ -e SLACK_CHANNEL=${env.CHT_SLACK_CHANNEL} \ ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest """ } } post { always { deleteDir() sh """ docker rmi -f ${env.MEGA_INTERNAL_DOCKER_REGISTRY}:8443/report-jira-slack-sdk:latest """ } } } } } // vim: syntax=groovy tabstop=4 shiftwidth=4 sdk-10.11.0/jenkinsfile/specific-branches/000077500000000000000000000000001516266226600203205ustar00rootroot00000000000000sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-android-targets.groovy000066400000000000000000000244371516266226600270470ustar00rootroot00000000000000def failedTargets = [] pipeline { agent { label 'docker' } options { buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } parameters { booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') booleanParam(name: 'BUILD_ARM', defaultValue: true, description: 'Build for ARM') booleanParam(name: 'BUILD_ARM64', defaultValue: true, description: 'Build for ARM64') booleanParam(name: 'BUILD_X86', defaultValue: true, description: 'Build for X86') booleanParam(name: 'BUILD_X64', defaultValue: true, description: 'Build for X64') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } environment { VCPKGPATH = "/opt/vcpkg" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { sdk_sources_workspace = WORKSPACE } } } stage('Build Android docker image'){ steps{ dir("dockerfile"){ sh "docker build -t meganz/android-build-env:${env.BUILD_NUMBER} -f ./android-cross-build.dockerfile ." } } } stage('Get UID and GID') { steps { script { def uid = sh(script: 'id -u', returnStdout: true).trim() def gid = sh(script: 'id -g', returnStdout: true).trim() env.UID = uid env.GID = gid } } } stage('Build with docker'){ parallel { stage('Build arm'){ when { beforeAgent true expression { params.BUILD_ARM == true } } steps { sh "docker run --name android-builder-arm-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=arm -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" sh "docker run --name android-builder-arm-dynamiclib-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=arm -e BUILD_SHARED_LIBS=ON -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" } post{ aborted { sh "docker kill android-builder-arm-${env.BUILD_NUMBER}; docker kill android-builder-arm-dynamiclib-${env.BUILD_NUMBER}" script { failedTargets.add("arm") } } failure { script { failedTargets.add("arm") } } } } stage('Build arm64'){ when { beforeAgent true expression { params.BUILD_ARM64 == true } } steps { sh "docker run --name android-builder-arm64-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=arm64 -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" sh "docker run --name android-builder-arm64-dynamiclib-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=arm64 -e BUILD_SHARED_LIBS=ON -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" } post{ aborted { sh "docker kill android-builder-arm64-${env.BUILD_NUMBER}; docker kill android-builder-arm64-dynamiclib-${env.BUILD_NUMBER}" script { failedTargets.add("arm64") } } failure { script { failedTargets.add("arm64") } } } } stage('Build x86'){ when { beforeAgent true expression { params.BUILD_X86 == true } } steps { sh "docker run --name android-builder-x86-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=x86 -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" sh "docker run --name android-builder-x86-dynamiclib-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=x86 -e BUILD_SHARED_LIBS=ON -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" } post{ aborted { sh "docker kill android-builder-x86-${env.BUILD_NUMBER}; docker kill android-builder-x86-dynamiclib-${env.BUILD_NUMBER}" script { failedTargets.add("x86") } } failure { script { failedTargets.add("x86") } } } } stage('Build x64'){ when { beforeAgent true expression { params.BUILD_X64 == true } } steps { sh "docker run --name android-builder-x64-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=x64 -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" sh "docker run --name android-builder-x64-dynamiclib-${env.BUILD_NUMBER} --rm -v ${WORKSPACE}:/mega/sdk -v ${VCPKGPATH}:/mega/vcpkg -e ARCH=x64 -e BUILD_SHARED_LIBS=ON -e VCPKG_BINARY_SOURCES -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_ENDPOINT_URL meganz/android-build-env:${env.BUILD_NUMBER}" } post{ aborted { sh "docker kill android-builder-x64-${env.BUILD_NUMBER}; docker kill android-builder-x64-dynamiclib-${env.BUILD_NUMBER}" script { failedTargets.add("x64") } } failure { script { failedTargets.add("x64") } } } } } } } post { always { script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C ${sdk_sources_workspace} rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *Android* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() if (failedTargets.size() > 0) { message += "\nFailed targets: ${failedTargets.join(', ')}" } withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' ${SLACK_WEBHOOK_URL} """ } } } sh "docker image rm meganz/android-build-env:${env.BUILD_NUMBER}" deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-dsm-targets.groovy000066400000000000000000000176221516266226600262100ustar00rootroot00000000000000def failedTargets = [] pipeline { agent { label 'docker' } options { buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } parameters { booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') booleanParam(name: 'CUSTOM_PLATFORM', defaultValue: false, description: 'If true, will use PLATFORM_TO_BUILD. If false, will build for all platforms') string(name: 'PLATFORM_TO_BUILD', defaultValue: 'alpine', description: 'Only used if CUSTOM_PLATFORM is true') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } environment { VCPKGPATH = "/opt/vcpkg" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { sdk_sources_workspace = WORKSPACE build_agent = "${NODE_NAME}" } } } stage('Build DSM docker image'){ steps{ dir("dockerfile"){ sh "docker build -t meganz/dsm-build-env:${env.BUILD_NUMBER} -f ./dms-cross-build.dockerfile ." } } } stage ('Build custom platform'){ when { beforeAgent true expression { params.CUSTOM_PLATFORM == true } } steps { echo "Do Build for ${params.PLATFORM_TO_BUILD}" dir(sdk_sources_workspace){ sh """ docker run --name dsm-builder-${params.PLATFORM_TO_BUILD}-${env.BUILD_NUMBER} --rm \ -v ${sdk_sources_workspace}:/mega/sdk \ -v ${VCPKGPATH}:/mega/vcpkg \ -e PLATFORM=${params.PLATFORM_TO_BUILD} \ -e VCPKG_BINARY_SOURCES \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_ENDPOINT_URL \ meganz/dsm-build-env:${env.BUILD_NUMBER} """ } } post{ aborted { sh "docker kill android-builder-${params.PLATFORM_TO_BUILD}-${env.BUILD_NUMBER}" script { failedTargets.add("${params.PLATFORM_TO_BUILD}") } } failure { script { failedTargets.add("${params.PLATFORM_TO_BUILD}") } } } } stage ('Build all platforms'){ when { beforeAgent true expression { params.CUSTOM_PLATFORM == false } } matrix { axes { axis { name 'PLATFORM'; values 'alpine', 'alpine4k', 'apollolake', 'armada37xx', 'armada38x', 'avoton','braswell', 'broadwell', 'broadwellnk', 'broadwellnkv2', 'broadwellntbap', 'bromolow', 'denverton', 'epyc7002', 'geminilake', 'grantley', 'kvmx64', 'monaco', 'purley', 'r1000', 'rtd1296', 'rtd1619b', 'v1000' } } stages { stage('Build') { agent { label "${build_agent}" } steps { echo "Do Build for DSM - ${PLATFORM}" dir(sdk_sources_workspace){ sh """ docker run --name dsm-builder-${PLATFORM}-${env.BUILD_NUMBER} --rm \ -v ${sdk_sources_workspace}:/mega/sdk \ -v ${VCPKGPATH}:/mega/vcpkg \ -e VCPKG_BINARY_SOURCES \ -e AWS_ACCESS_KEY_ID \ -e AWS_SECRET_ACCESS_KEY \ -e AWS_ENDPOINT_URL \ -e PLATFORM=${PLATFORM} meganz/dsm-build-env:${env.BUILD_NUMBER} """ } } post{ aborted { sh "docker kill android-builder-${PLATFORM}-${env.BUILD_NUMBER}" script { failedTargets.add("${PLATFORM}") } } failure { script { failedTargets.add("${PLATFORM}") } } } } } } } } post { always { sh "docker image rm meganz/dsm-build-env:${env.BUILD_NUMBER}" script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C ${sdk_sources_workspace} rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *DSM* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() if (failedTargets.size() > 0) { message += "\nFailed targets: ${failedTargets.join(', ')}" } withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' \${SLACK_WEBHOOK_URL} """ } } } deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-ios-targets.groovy000066400000000000000000000111771516266226600262160ustar00rootroot00000000000000pipeline { agent { label 'osx && arm64' } options { buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } parameters { booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { ios_sources_workspace = WORKSPACE } } } stage('Build iOS'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ PATH = "/usr/local/bin:${env.PATH}" VCPKGPATH = "${env.HOME}/jenkins/vcpkg" BUILD_DIR_ARM64 = "build_dir_arm64" BUILD_DIR_ARM64_SIM = "build_dir_arm64_sim" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } steps{ //Build SDK for arm64-iphoneos sh "echo \"Building SDK for iOS arm64 (iphoneos SDK)\"" sh "cmake -DENABLE_LOG_PERFORMANCE=ON -DUSE_LIBUV=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_MEDIA_FILE_METADATA=ON -DVCPKG_ROOT=${VCPKGPATH} -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_SYSTEM_NAME=iOS -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR_ARM64}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR_ARM64} -j2" //Build SDK for arm64-iphonesimulator sh "echo \"Building SDK for iOS arm64 (iphonesimulator SDK)\"" sh "cmake -DENABLE_LOG_PERFORMANCE=ON -DUSE_LIBUV=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_MEDIA_FILE_METADATA=ON -DVCPKG_ROOT=${VCPKGPATH} -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_SYSROOT=iphonesimulator -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR_ARM64_SIM}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR_ARM64_SIM} -j2" } } } post { always { script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C ${ios_sources_workspace} rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *iOS* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' ${SLACK_WEBHOOK_URL} """ } } } deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-linux-builds-packages.groovy000066400000000000000000000306721516266226600301510ustar00rootroot00000000000000def failedDistros = [] pipeline { agent { label 'linux-testing-package-builder' } options { buildDiscarder(logRotator(numToKeepStr: '25', daysToKeepStr: '30')) gitLabConnection('GitLabConnectionJenkins') skipDefaultCheckout() } parameters { booleanParam(name: 'UPLOAD_TO_REPOSITORY', defaultValue: false, description: 'Should the package be uploaded to artifactory?') booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') booleanParam(name: 'CUSTOM_BUILD', defaultValue: false, description: 'If true, will use DISTRO_TO_BUILD and ARCH_TO_BUILD. If false, will build all distributions') choice(name: 'ARCH_TO_BUILD', choices: ['amd64', 'armhf','arm64'], description: 'Only used if CUSTOM_BUILD is true') string(name: 'DISTRO_TO_BUILD', defaultValue: 'xUbuntu_22.04', description: 'Only used if CUSTOM_BUILD is true') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } environment { SDK_BRANCH = "${params.SDK_BRANCH}" } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout linux'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { linux_sources_workspace = WORKSPACE } } } stage ('Build custom distribution'){ when { beforeAgent true expression { params.CUSTOM_BUILD == true } } steps { echo "Do Build for ${params.DISTRO_TO_BUILD}" dir(linux_sources_workspace) { lock(resource: "${params.DISTRO_TO_BUILD}-${params.ARCH_TO_BUILD}-megasdk-build", quantity: 1) { buildAndSignPackage("${params.DISTRO_TO_BUILD}", "${params.ARCH_TO_BUILD}", "4", "megasdk") } script{ if ( params.UPLOAD_TO_REPOSITORY == true) { //def SDK_VERSION = getVersionFromHeader("include/mega/version.h") def CURRENT_DATE = new Date().format('yyyyMMdd') withCredentials([string(credentialsId: 'MEGASDK_ARTIFACTORY_TOKEN', variable: 'MEGASDK_ARTIFACTORY_TOKEN')]) { dir("${env.INTERNAL_REPO_PATH}/repo/private/$DISTRO_TO_BUILD"){ sh """ jf rt upload \ --url ${REPO_URL} \ --access-token ${MEGASDK_ARTIFACTORY_TOKEN} \ --regexp '((x86_64|amd64)/megasdk.*deb\$|(x86_64|amd64)/megasdk.*rpm\$|(x86_64|amd64)/megasdk.*\\.pkg\\.tar\\.zst\$|(x86_64|amd64)/megasdk.*\\.pkg\\.tar\\.xz\$)' \ sdk/releases/$CURRENT_DATE/linux/$DISTRO_TO_BUILD/ """ } } echo "Packages successfully uploaded. URL: [${env.REPO_URL}/sdk/releases/$CURRENT_DATE/linux/$DISTRO_TO_BUILD/]" } } } } post { failure { script { failedDistros.add(params.DISTRO_TO_BUILD) } } } } stage ('Build all distributions'){ when { beforeAgent true expression { params.CUSTOM_BUILD == false } } matrix { axes { axis { name 'ARCHITECTURE'; values 'amd64','armhf','arm64' } axis { name 'DISTRO'; values 'xUbuntu_26.04', 'xUbuntu_25.10','xUbuntu_25.04','xUbuntu_24.04','xUbuntu_22.04', 'Debian_11','Debian_12','Debian_13','Debian_testing', 'DEB_Arch_Extra', 'Raspbian_12', 'Fedora_42','Fedora_43', 'AlmaLinux_9','CentOS_Stream_9', 'openSUSE_Leap_15.6', 'openSUSE_Leap_16.0', 'openSUSE_Tumbleweed' } } excludes { exclude { axis { name 'ARCHITECTURE'; values 'armhf' } axis { name 'DISTRO'; values 'xUbuntu_26.04', 'xUbuntu_25.10','xUbuntu_25.04','xUbuntu_24.04','xUbuntu_22.04', 'Debian_11','Debian_12','Debian_13','Debian_testing', 'DEB_Arch_Extra', 'Fedora_42','Fedora_43', 'AlmaLinux_9','CentOS_Stream_9', 'openSUSE_Leap_15.6', 'openSUSE_Leap_16.0', 'openSUSE_Tumbleweed' } } exclude { axis { name 'ARCHITECTURE'; values 'amd64' } axis { name 'DISTRO'; values 'Raspbian_12' } } exclude { axis { name 'ARCHITECTURE'; values 'arm64' } axis { name 'DISTRO'; values 'DEB_Arch_Extra','Debian_11','Debian_13', 'xUbuntu_24.04','xUbuntu_22.04', 'Fedora_42','Fedora_43', 'CentOS_Stream_9','openSUSE_Leap_16.0', 'Raspbian_12','openSUSE_Tumbleweed' } } } stages { stage('Build') { agent { label 'linux-testing-package-builder' } steps { echo "Do Build for ${DISTRO} - ${ARCHITECTURE}" dir(linux_sources_workspace) { lock(resource: "${DISTRO}-${ARCHITECTURE}-megasdk-build", quantity: 1) { buildAndSignPackage("${DISTRO}", "${ARCHITECTURE}", "2", "megasdk") } } } post { failure { script { failedDistros.add(DISTRO) } } } } stage('Upload packages') { when { beforeAgent true expression { params.UPLOAD_TO_REPOSITORY == true } } steps { dir(linux_sources_workspace) { script{ def CURRENT_DATE = new Date().format('yyyyMMdd') withCredentials([string(credentialsId: 'MEGASDK_ARTIFACTORY_TOKEN', variable: 'MEGASDK_ARTIFACTORY_TOKEN')]) { sh """ jf rt del \ --url ${REPO_URL} \ --access-token ${MEGASDK_ARTIFACTORY_TOKEN} \ sdk/releases/$CURRENT_DATE/linux/$DISTRO/ """ dir("${env.INTERNAL_REPO_PATH}/repo/private/$DISTRO"){ sh """ jf rt upload \ --url ${REPO_URL} \ --access-token ${MEGASDK_ARTIFACTORY_TOKEN} \ --regexp '((x86_64|amd64)/megasdk.*deb\$|(x86_64|amd64)/megasdk.*rpm\$|(x86_64|amd64)/megasdk.*\\.pkg\\.tar\\.zst\$|(x86_64|amd64)/megasdk.*\\.pkg\\.tar\\.xz\$)' \ sdk/releases/$CURRENT_DATE/linux/$DISTRO/ """ } } echo "Packages successfully uploaded. URL: [${env.REPO_URL}/sdk/releases/$CURRENT_DATE/linux/$DISTRO/]" } } } } } } } } post { always { script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C ${linux_sources_workspace} rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *Linux* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() if (failedDistros.size() > 0) { message += "\nFailed distributions: ${failedDistros.join(', ')}" } withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' \${SLACK_WEBHOOK_URL} """ } } } deleteDir() } } } def buildAndSignPackage(String distro, String architecture, String jobs, String packageName) { sh "${env.BUILDTOOLS_PATH}/build/buildManager.sh -a ${architecture} -j ${jobs} build ${distro} . ${packageName}" sh "${env.BUILDTOOLS_PATH}/repo/repoManager.sh add ${env.INTERNAL_REPO_PATH}/builder/results/${distro}/${architecture}/${packageName}/ ${distro}" sh "SIGN_KEY_PATH=${env.INTERNAL_REPO_PATH}/sign_test/ ${env.BUILDTOOLS_PATH}/repo/repoManager.sh build -n ${distro}" } def getVersionFromHeader(String versionFilePath) { return sh(script: """ awk '/#define MEGA_MAJOR_VERSION/ { MAJOR=\$3 }; \ /#define MEGA_MINOR_VERSION/ { MINOR=\$3 }; \ /#define MEGA_MICRO_VERSION/ { MICRO=\$3 }; \ END { print MAJOR"."MINOR"."MICRO }' \ $versionFilePath """ , returnStdout: true).trim() } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-macos-targets.groovy000066400000000000000000000113751516266226600265260ustar00rootroot00000000000000pipeline { agent { label 'osx && arm64' } options { buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } parameters { booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { macos_sources_workspace = WORKSPACE } } } stage('Build macOS'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ PATH = "/usr/local/bin:${env.PATH}" VCPKGPATH = "${env.HOME}/jenkins/vcpkg" BUILD_DIR = "build_dir" BUILD_DIR_X64 = "build_dir_x64" QTPATH = "${env.HOME}/Qt-build/5.15.16/5.15.16" COMMON_CMAKE_OPTIONS = "-DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_LOG_PERFORMANCE=ON -DENABLE_QT_BINDINGS=ON -DENABLE_JAVA_BINDINGS=ON -DUSE_LIBUV=ON -DENABLE_MEDIA_FILE_METADATA=ON -DVCPKG_ROOT=${VCPKGPATH}" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } steps{ //Build SDK for arm64 sh "echo Building SDK for Apple Silicon / arm64" sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" sh "cmake ${COMMON_CMAKE_OPTIONS} -DCMAKE_PREFIX_PATH=${QTPATH}/arm64 -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR} -j2" //Build SDK for x64 sh "echo \"Building SDK for intel / x64 (crosscompiling)\"" sh "rm -rf ${BUILD_DIR_X64}; mkdir ${BUILD_DIR_X64}" sh "cmake ${COMMON_CMAKE_OPTIONS} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_PREFIX_PATH=${QTPATH}/x86_64 -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR_X64}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR_X64} -j2" } } } post { always { script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C ${macos_sources_workspace} rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *MacOS* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' ${SLACK_WEBHOOK_URL} """ } } } deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-all-windows-targets.groovy000066400000000000000000000113531516266226600271120ustar00rootroot00000000000000//Build SDK for a given architecture def build_for_arch(String architecture) { def BUILD_DIR = "${WORKSPACE}\\build_dir_${architecture}" def QTPATH = "C:\\Qt\\Qt5.15.13\\5.15.13" def VCPKGPATH = "${WORKSPACE}\\..\\..\\vcpkg" def CMAKE_FLAGS = "-DVCPKG_ROOT='${VCPKGPATH}' -DSWIG_EXECUTABLE='C:\\swigwin-4.0.2\\swig.exe' -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_LOG_PERFORMANCE=ON -DENABLE_JAVA_BINDINGS=ON -DUSE_LIBUV=ON -DENABLE_MEDIA_FILE_METADATA=ON -S '${WORKSPACE}' -B '${BUILD_DIR}'" def CMAKE_PLATFORM = "-DCMAKE_GENERATOR_PLATFORM=${architecture}" // x64 and Win32 have QT bindings. arm64 does not. // Win32 is called x86 here def CMAKE_QT_FLAGS = "" switch (architecture) { case 'Win32': CMAKE_QT_FLAGS = "-DCMAKE_PREFIX_PATH='${QTPATH}\\x86' -DENABLE_QT_BINDINGS=ON" break case 'x64': CMAKE_QT_FLAGS = "-DCMAKE_PREFIX_PATH='${QTPATH}\\x64' -DENABLE_QT_BINDINGS=ON" } sh "rm -vrf '${BUILD_DIR}'; mkdir -v '${BUILD_DIR}'" sh "cmake ${CMAKE_PLATFORM} ${CMAKE_QT_FLAGS} ${CMAKE_FLAGS}" sh "cmake --build '${BUILD_DIR}' --config RelWithDebInfo -j 1" } pipeline { agent { label 'windows && amd64' } options { buildDiscarder(logRotator(numToKeepStr: '60', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } parameters { booleanParam(name: 'RESULT_TO_SLACK', defaultValue: true, description: 'Should the job result be sent to slack?') string(name: 'SDK_BRANCH', defaultValue: 'develop', description: 'Define a custom SDK branch.') } stages { stage('Clean previous runs'){ steps{ deleteDir() } } stage('Checkout SDK'){ steps { checkout([ $class: 'GitSCM', branches: [[name: "${env.SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) script { windows_sources_workspace = WORKSPACE } } } stage('Build Windows'){ options{ // timeout: 30 min per architecture timeout(time: 90, unit: 'MINUTES') } stages { stage("Build x64") { steps{ build_for_arch('x64') } } stage("Build Win32") { steps{ build_for_arch('Win32') } } stage("Build ARM64") { steps{ build_for_arch('ARM64') } } } } } post { always { script { if (params.RESULT_TO_SLACK) { sdk_commit = sh(script: "git -C '${windows_sources_workspace}' rev-parse HEAD", returnStdout: true).trim() messageStatus = currentBuild.currentResult messageColor = messageStatus == 'SUCCESS'? "#00FF00": "#FF0000" //green or red message = """ *Windows* <${BUILD_URL}|Build result>: '${messageStatus}'. SDK branch: `${SDK_BRANCH}` SDK commit: `${sdk_commit}` """.stripIndent() withCredentials([string(credentialsId: 'slack_webhook_sdk_report', variable: 'SLACK_WEBHOOK_URL')]) { sh """ curl -X POST -H 'Content-type: application/json' --data ' { "attachments": [ { "color": "${messageColor}", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": "${message}" } } ] } ] }' \${SLACK_WEBHOOK_URL} """ } } } deleteDir() } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-android-64-make-ExampleApp.groovy000066400000000000000000000021651516266226600300200ustar00rootroot00000000000000pipeline { agent { label 'amd64 && linux && android' } options { buildDiscarder(logRotator(numToKeepStr: '25', daysToKeepStr: '15')) } environment { ANDROID_HOME = '/home/jenkins/android-cmdlinetools/' ANDROID_NDK_HOME = '/home/jenkins/android-ndk' } stages { stage('build'){ steps{ sh "export PATH=\$PATH:\$ANDROID_HOME/cmdline-tools/tools/bin/" dir ("examples/android/ExampleApp/app/src/main/jni/") { sh "sed -i \"s#-j[0-9]##g\" build.sh" sh "sed -i 's#LOG_FILE=/dev/null#LOG_FILE=/dev/stdout#g' build.sh" sh "rm -rf ../java/nz/mega/sdk/" // Build libs and SDK. sh "bash -x ./build.sh clean" sh "bash -x ./build.sh all" } dir ( "examples/android/ExampleApp") { sh "./gradlew --no-daemon --max-workers=1 build" } } } } post { always { deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-debian64-gtests.groovy000066400000000000000000000143121516266226600261040ustar00rootroot00000000000000pipeline { agent { label 'linux && amd64' } options { buildDiscarder(logRotator(numToKeepStr: '25', daysToKeepStr: '15')) } stages { stage('Build Linux'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ VCPKGPATH = "/opt/vcpkg" BUILD_DIR = "build_dir" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } steps{ sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" sh "cmake -DENABLE_CHAT=ON -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DVCPKG_ROOT=${VCPKGPATH} -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_VERBOSE_MAKEFILE=ON -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR} -j1" } } stage('Run Linux tests'){ options{ timeout(time: 250, unit: 'MINUTES') } environment { MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') BUILD_DIR = "build_dir" } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh "echo Running tests" sh """#!/bin/bash set -x ulimit -c unlimited cd ${env.BUILD_DIR} ./tests/unit/test_unit || FAILED=1 if [ -z \"\$FAILED\" ]; then # Parallel run ./tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${PIPESTATUS[0]}\" != \"0\" ] && FAILED=2 fi if [ -n \"\$FAILED\" ]; then if [ \"\$FAILED\" -eq 1 ]; then coreFiles=core else # FAILED=2 # Parallel run procFailed=`grep \"<< PROCESS\" tests.stdout | sed 's/.*PID:\\([0-9]*\\).*/\\1/'` if [ -n \"\$procFailed\" ]; then # Parallel run for i in \$procFailed; do coreFiles=\"\$coreFiles pid_\$i/core\" done fi fi if [ -n \"\$coreFiles\" ]; then maxTime=10 startTime=`date +%s` coresProcessed=0 coresTotal=`echo \$coreFiles | wc -w` # While there are pending cores while [ \$coresProcessed -lt \$coresTotal ] && [ \$( expr `date +%s` - \$startTime ) -lt \$maxTime ]; do echo "Waiting for core dumps..." sleep 1 for i in \$coreFiles; do if [ -e \"\$i\" ] && [ -z \"\$( lsof \$i 2>/dev/null )\" ]; then echo \"Processing core dump \$i :: \$(grep `echo \$i | sed 's#pid_\\([0-9].*\\)/core#\\1#'` tests.stdout)\" echo thread apply all bt > backtrace echo quit >> backtrace [ \"\$FAILED\" = \"1\" ] && gdb -q ./tests/unit/test_unit \$i -x ${WORKSPACE}/backtrace [ \"\$FAILED\" = \"2\" ] && gdb -q ./tests/integration/test_integration \$i -x ${WORKSPACE}/backtrace tar rf core.tar \$i coresProcessed=`expr \$coresProcessed + 1` coreFiles=`echo \$coreFiles | sed -e \"s#\$i##\"` fi done done if [ -e core.tar ]; then [ \"\$FAILED\" = \"1\" ] && tar rf core.tar -C ./tests/unit/ test_unit [ \"\$FAILED\" = \"2\" ] && tar rf core.tar -C ./tests/integration/ test_integration gzip core.tar fi fi fi gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz || : rm test_integration.log || : if [ -n \"\$FAILED\" ]; then exit \"\$FAILED\" fi """ } } } } } post { always { archiveArtifacts artifacts: 'build_dir/*.log.gz', fingerprint: true deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-debian64-static.groovy000066400000000000000000000005121516266226600260570ustar00rootroot00000000000000pipeline { agent { label 'linux && amd64' } options { buildDiscarder(logRotator(numToKeepStr: '25', daysToKeepStr: '15')) } stages { stage('build'){ steps{ sh "./clean.sh" sh "./contrib/build_sdk.sh -g -e -v -u -q -a -f" } } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-macos-develop.groovy000066400000000000000000000135371516266226600257470ustar00rootroot00000000000000pipeline { agent { label 'osx && arm64' } options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } stages { stage('Build macOS'){ options{ timeout(time: 120, unit: 'MINUTES') } environment{ PATH = "/usr/local/bin:${env.PATH}" VCPKGPATH = "${env.HOME}/jenkins/vcpkg" BUILD_DIR = "build_dir" BUILD_DIR_X64 = "build_dir_x64" VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" } steps{ //Build SDK for arm64 sh "echo Building SDK for arm64" sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" sh "cmake -DENABLE_CHAT=ON -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DVCPKG_ROOT=${VCPKGPATH} -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_VERBOSE_MAKEFILE=ON -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR} -j1" //Build SDK for x64 sh "echo \"Building SDK for x64 (crosscompiling)\"" sh "rm -rf ${BUILD_DIR_X64}; mkdir ${BUILD_DIR_X64}" sh "cmake -DENABLE_CHAT=ON -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DVCPKG_ROOT=${VCPKGPATH} -DCMAKE_VERBOSE_MAKEFILE=ON -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_OSX_ARCHITECTURES=x86_64 -S ${WORKSPACE} -B ${WORKSPACE}/${BUILD_DIR_X64}" sh "cmake --build ${WORKSPACE}/${BUILD_DIR_X64} -j1" } } stage('Run macOS tests'){ options{ timeout(time: 250, unit: 'MINUTES') } environment { MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') BUILD_DIR = "build_dir" } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh "echo Running tests" sh """#!/bin/zsh set -x cd ${env.BUILD_DIR} ./tests/unit/test_unit & pid=\$! wait \$pid || FAILED=1 if [ -z \"\$FAILED\" ]; then if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run ./tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} & pid=\$! wait \$pid || FAILED=2 else # Parallel run ./tests/integration/test_integration --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${pipestatus[1]}\" != \"0\" ] && FAILED=2 fi fi if [ -n \"\$FAILED\" ]; then if [ \"\$FAILED\" -eq 1 ]; then procFailed=\$pid else # FAILED=2 if [ -z \"${TESTS_PARALLEL}\" ]; then # Sequential run procFailed=\$pid else # Parallel run procFailed=`grep \"<< PROCESS\" tests.stdout | sed 's/.*PID:\\([0-9]*\\).*/\\1/' | tr '\\n' ' '` fi fi if [ -n \"\$procFailed\" ]; then sleep 10 for i in `echo \$procFailed`; do last_core=`grep \"test_.*\$i\" -rn \$HOME/Library/Logs/DiagnosticReports | cut -d':' -f1` if [ -n \"\$last_core\" ]; then cat \"\$last_core\" rm \"\$last_core\" fi done fi fi gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz || : rm test_integration.log || : if [ -n \"\$FAILED\" ]; then exit \"\$FAILED\" fi """ } } } } } post { always { archiveArtifacts artifacts: 'build_dir/*.log.gz', fingerprint: true deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-megachat-test.groovy000066400000000000000000000142761516266226600257400ustar00rootroot00000000000000pipeline { agent { label 'linux && amd64 && webrtc' } options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } environment { MEGACHAT_BRANCH = "master" SDK_BRANCH = "master" } stages { stage('Checkout SDK and MEGAchat'){ steps { deleteDir() // Clean workspace //Clone MEGAchat sh "echo Cloning MEGAchat branch \"${MEGACHAT_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "origin/${MEGACHAT_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_MEGACHAT}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"] ] ]) dir('third-party/mega'){ //Clone SDK (with PreBuildMerge) sh "echo Cloning SDK branch \"${SDK_BRANCH}\"" checkout([ $class: 'GitSCM', branches: [[name: "origin/${SDK_BRANCH}"]], userRemoteConfigs: [[ url: "${env.GIT_URL_SDK}", credentialsId: "12492eb8-0278-4402-98f0-4412abfb65c1" ]], extensions: [ [$class: "UserIdentity",name: "jenkins", email: "jenkins@jenkins"], ] ]) } script{ megachat_sources_workspace = WORKSPACE sdk_sources_workspace = "${megachat_sources_workspace}/third-party/mega" } } } stage('Build MEGAchat'){ environment{ WEBRTC_SRC="/home/jenkins/webrtc/src" PATH = "/home/jenkins/tools/depot_tools:${env.PATH}" } options{ timeout(time: 150, unit: 'MINUTES') } steps{ dir(megachat_sources_workspace){ sh "sed -i \"s#MEGAChatTest#${env.USER_AGENT_TESTS_MEGACHAT}#g\" tests/sdk_test/sdk_test.h" sh "mkdir -p build" } dir(sdk_sources_workspace){ sh "./autogen.sh" sh "./configure --disable-tests --enable-chat --enable-shared --without-pdfium --without-ffmpeg" sh "sed -i \"s#nproc#echo 1#\" bindings/qt/build_with_webrtc.sh" sh "cd bindings/qt && bash build_with_webrtc.sh all withExamples" } } } stage('Run MEGAchat tests'){ environment { MEGA_PWD0 = credentials('MEGA_PWD_DEFAULT') MEGA_PWD1 = credentials('MEGA_PWD_DEFAULT') MEGA_PWD2 = credentials('MEGA_PWD_DEFAULT') } options{ timeout(time: 300, unit: 'MINUTES') } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ dir("${megachat_sources_workspace}/build/subfolder"){ script{ env.MEGA_EMAIL0 = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh """#!/bin/bash ulimit -c unlimited if [ -z \"${TESTS_PARALLEL}\" ]; then ${megachat_sources_workspace}/build/MEGAchatTests/megachat_tests --USERAGENT:${env.USER_AGENT_TESTS_MEGACHAT} --APIURL:${APIURL_TO_TEST} || FAILED=1 else # Parallel run ${megachat_sources_workspace}/build/MEGAchatTests/megachat_tests --USERAGENT:${env.USER_AGENT_TESTS_MEGACHAT} --APIURL:${APIURL_TO_TEST} ${TESTS_PARALLEL} 2>&1 | tee tests.stdout [ \"\${PIPESTATUS[0]}\" != \"0\" ] && FAILED=1 fi if [ -n \"\$FAILED\" ]; then echo "Test failed with status \$FAILED" maxTime=10 startTime=`date +%s` # Only a single core file can be handled, for either sequential or parallel run while [ \$( expr `date +%s` - \$startTime ) -lt \$maxTime ]; do if [ -e \"core\" ]; then echo "Processing core dump..." echo thread apply all bt > backtrace echo quit >> backtrace gdb -q ${megachat_sources_workspace}/build/MEGAchatTests/megachat_tests core -x ${megachat_sources_workspace}/build/subfolder/backtrace tar chvzf core.tar.gz core megachat_tests break fi sleep 1 done fi gzip -c test.log > test_${BUILD_ID}.log.gz || : rm test.log || : if [ ! -z \"\$FAILED\" ]; then false fi """ } } } } } } post { always { archiveArtifacts artifacts: '*.log.gz', fingerprint: true deleteDir() } } } sdk-10.11.0/jenkinsfile/specific-branches/megasdk-win-develop.groovy000066400000000000000000000077071516266226600254440ustar00rootroot00000000000000pipeline { agent { label 'windows && amd64' } options { buildDiscarder(logRotator(numToKeepStr: '135', daysToKeepStr: '21')) gitLabConnection('GitLabConnectionJenkins') } stages { stage('Build Windows'){ environment{ VCPKGPATH = "${WORKSPACE}\\..\\..\\vcpkg" BUILD_DIR = "build_dir" /* * VCPKG cache in S4 commented out because it takes too long time. * See CID-839 * VCPKG_BINARY_SOURCES = 'clear;x-aws,s3://vcpkg-cache/archives/,readwrite' * AWS_ACCESS_KEY_ID = credentials('s4_access_key_id_vcpkg_cache') * AWS_SECRET_ACCESS_KEY = credentials('s4_secret_access_key_vcpkg_cache') * AWS_ENDPOINT_URL = "https://s3.g.s4.mega.io" */ } options{ timeout(time: 150, unit: 'MINUTES') } steps{ //Build SDK sh "echo Building SDK x64" sh "rm -rf ${BUILD_DIR}; mkdir ${BUILD_DIR}" sh "cmake -DENABLE_CHAT=ON -DVCPKG_ROOT='${VCPKGPATH}' -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_GENERATOR_PLATFORM=x64 -S '${WORKSPACE}' -B '${WORKSPACE}'\\\\build_dir\\\\" sh "cmake --build '${WORKSPACE}'\\\\build_dir\\\\ --config ${BUILD_TYPE} -j 1" sh "echo Building SDK x86" sh "rm -rf build_dir_x86; mkdir build_dir_x86" sh "cmake -DENABLE_CHAT=ON -DVCPKG_ROOT='${VCPKGPATH}' -DENABLE_MEDIA_FILE_METADATA=ON -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_GENERATOR_PLATFORM=Win32 -S '${WORKSPACE}' -B '${WORKSPACE}'\\\\build_dir_x86\\\\" sh "cmake --build '${WORKSPACE}'\\\\build_dir_x86\\\\ --config ${BUILD_TYPE} -j 1" } } stage('Run Windows tests'){ options{ timeout(time: 250, unit: 'MINUTES') } environment { PATH = "${WORKSPACE}\\\\vcpkg_installed\\\\x64-windows-mega\\\\debug\\\\bin;${env.PATH}" MEGA_PWD = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX = credentials('MEGA_PWD_DEFAULT') MEGA_PWD_AUX2 = credentials('MEGA_PWD_DEFAULT') MEGA_REAL_PWD=credentials('MEGA_REAL_PWD_TEST') } steps{ script { def lockLabel = '' if ("${APIURL_TO_TEST}" == 'https://g.api.mega.co.nz/') { lockLabel = 'SDK_Concurrent_Test_Accounts' } else { lockLabel = 'SDK_Concurrent_Test_Accounts_Staging' } lock(label: lockLabel, variable: 'ACCOUNTS_COMBINATION', quantity: 1, resourceSelectStrategy: "random", resource: null){ script{ env.MEGA_EMAIL = "${env.ACCOUNTS_COMBINATION}" echo "${env.ACCOUNTS_COMBINATION}" } sh "echo Running tests" bat """ cd build_dir tests\\\\unit\\\\${BUILD_TYPE}\\\\test_unit.exe if %ERRORLEVEL% NEQ 0 exit 1 tests\\\\integration\\\\${BUILD_TYPE}\\\\test_integration.exe --FREEACCOUNTS --CI --USERAGENT:${env.USER_AGENT_TESTS_SDK} --APIURL:${APIURL_TO_TEST} ${TESTS_PARALLEL} if %ERRORLEVEL% NEQ 0 set ERROR_VAL=1 gzip -c test_integration.log > test_integration_${BUILD_ID}.log.gz rm test_integration.log exit %ERROR_VAL% """ } } } } } post { always { archiveArtifacts artifacts: 'build_dir/*.log.gz', fingerprint: true deleteDir() /* clean up our workspace */ } } } sdk-10.11.0/jenkinsfile/windows-integration-tests.ps1000066400000000000000000000055071516266226600225570ustar00rootroot00000000000000# This script runs Windows integration tests # And monitor tests PIDs with procdump to core dump when an exception happens # We'll use BUILD_ID and APIURL_TO_TEST variables for CI # Other CI env vars are optional If (-Not ((Test-Path Env:BUILD_ID) -Or (Test-Path Env:APIURL_TO_TEST))) { echo "Both BUILD_ID and APIRUL_TO_TEST env vars should be defined." exit 1 } # Build directory should be "build_dir" for amd64 and "build_dir_x86" for x86 # If not defined it will be "build_dir" If (-Not (Test-Path Env:BUILD_DIR)) { $Env:BUILD_DIR = "build_dir" } $procdump = "C:\Tools\procdump.exe" $cdb = "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" $dumpDir = "dumps" if (-not (Test-Path $dumpDir)) { New-Item -ItemType Directory -Path $dumpDir } # Monitor test PIDs to dump cores if necessary # This will be running in background Start-job -Name "childrenMonitor" -ScriptBlock { # Get the testing processes PIDs and run procdump on them $testPIDs = @{} # Redifinition of these variables are needed because of https://github.com/PowerShell/PowerShell/issues/4530 $procdump = "C:\Tools\procdump.exe" $dumpDir = "dumps" while ($true) { $processes = Get-Process -name test_integration_${using:Env:BUILD_ID} -ErrorAction SilentlyContinue foreach ($proc in $processes) { # If the process is not in the array, run procdump on it and add it to the array if (-not $testPIDs.ContainsKey($proc.Id)) { Start-Process $procdump -PassThru -NoNewWindow -RedirectStandardOutput procdump_$($proc.Id).log -RedirectStandardError procdump_$($proc.Id).error.log -ArgumentList "-accepteula -ma -e 1 $($proc.Id) $dumpDir" | Out-Null $testPIDs[$proc.Id] = $true } } # Get new test PIDs 10 times per second Start-Sleep -Milliseconds 100 } } # Start the tests with a unique name, based on BUILD_ID, so we can monitor their PIDs cp $Env:BUILD_DIR\tests\integration\Debug\test_integration.exe $Env:BUILD_DIR\tests\integration\Debug\test_integration_$Env:BUILD_ID.exe $testProcess = Start-Process "$Env:BUILD_DIR\tests\integration\Debug\test_integration_$Env:BUILD_ID.exe" -PassThru -NoNewWindow -Wait -ArgumentList "--FREEACCOUNTS --CI --USERAGENT:$Env:USER_AGENT_TESTS_SDK --APIURL:$Env:APIURL_TO_TEST $Env:GTEST_FILTER $Env:GTEST_REPEAT $Env:TESTS_PARALLEL" $testResult = $testProcess.ExitCode # Stop monitoring child processes Receive-Job -Name "childrenMonitor" Stop-job -Name "childrenMonitor" # Analyse the dumps, if there's any If ($testResult) { foreach ($dumpFile in Get-ChildItem -Path $dumpDir -Filter "*.dmp") { echo "" echo "" echo "Core dump analizys of $dumpDir\$dumpFile" echo "" echo "" & $cdb -z $dumpDir\$dumpFile -c ".lines -e;kv;!analyze -v;q" } } echo "" echo "Integration tests exit code: $testResult" echo "" exit $testResult sdk-10.11.0/patches/000077500000000000000000000000001516266226600140765ustar00rootroot00000000000000sdk-10.11.0/patches/webRtcSocketIosPatch.patch000066400000000000000000000147071516266226600211620ustar00rootroot00000000000000diff --git a/rtc_base/physical_socket_server.cc b/rtc_base/physical_socket_server.cc index 7c01815d30..c6464ee1dc 100644 --- a/rtc_base/physical_socket_server.cc +++ b/rtc_base/physical_socket_server.cc @@ -1072,6 +1072,10 @@ PhysicalSocketServer::PhysicalSocketServer() RTC_LOG_E(LS_WARNING, EN, errno) << "epoll_create"; // Note that -1 == INVALID_SOCKET, the alias used by later checks. } +#else + mKqueue = kqueue(); + RTC_LOG_E(LS_WARNING, EN, errno) << "kqueue"; + #endif // The `fWait_` flag to be cleared by the Signaler. signal_wakeup_ = new Signaler(this, fWait_); @@ -1265,6 +1269,9 @@ bool PhysicalSocketServer::WaitSelect(int cmsWait, bool process_io) { struct timeval* ptvWait = nullptr; struct timeval tvWait; + struct timespec timeOut; + struct timespec* ptvTimeout = nullptr; + int64_t stop_us; if (cmsWait != kForeverMs) { // Calculate wait timeval @@ -1274,26 +1281,21 @@ bool PhysicalSocketServer::WaitSelect(int cmsWait, bool process_io) { // Calculate when to return stop_us = rtc::TimeMicros() + cmsWait * 1000; + + timeOut.tv_sec = cmsWait / 1000; + timeOut.tv_nsec = (cmsWait % 1000) * 1000000; + + ptvTimeout = &timeOut; + } - fd_set fdsRead; - fd_set fdsWrite; -// Explicitly unpoison these FDs on MemorySanitizer which doesn't handle the -// inline assembly in FD_ZERO. -// http://crbug.com/344505 -#ifdef MEMORY_SANITIZER - __msan_unpoison(&fdsRead, sizeof(fdsRead)); - __msan_unpoison(&fdsWrite, sizeof(fdsWrite)); -#endif + std::vector fds; + fWait_ = true; while (fWait_) { - // Zero all fd_sets. Although select() zeros the descriptors not signaled, - // we may need to do this for dispatchers that were deleted while - // iterating. - FD_ZERO(&fdsRead); - FD_ZERO(&fdsWrite); + fds.clear(); int fdmax = -1; { CritScope cr(&crit_); @@ -1306,30 +1308,40 @@ bool PhysicalSocketServer::WaitSelect(int cmsWait, bool process_io) { continue; current_dispatcher_keys_.push_back(key); int fd = pdispatcher->GetDescriptor(); - // "select"ing a file descriptor that is equal to or larger than - // FD_SETSIZE will result in undefined behavior. - RTC_DCHECK_LT(fd, FD_SETSIZE); if (fd > fdmax) fdmax = fd; uint32_t ff = pdispatcher->GetRequestedEvents(); + struct kevent event; + uint32_t filter = 0; if (ff & (DE_READ | DE_ACCEPT)) - FD_SET(fd, &fdsRead); + { + filter = EVFILT_READ; + } if (ff & (DE_WRITE | DE_CONNECT)) - FD_SET(fd, &fdsWrite); + { + filter |= EVFILT_WRITE; + } + + if (filter) + { + EV_SET(&event, fd, filter, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, nullptr); + fds.push_back(event); + } } } + std::unique_ptr eventsReceived(new struct kevent[fds.size()]); // Wait then call handlers as appropriate // < 0 means error // 0 means timeout // > 0 means count of descriptors ready - int n = select(fdmax + 1, &fdsRead, &fdsWrite, nullptr, ptvWait); + int n = kevent(mKqueue, fds.data(), fds.size(), eventsReceived.get(), fds.size(), ptvTimeout); // If error, return error. if (n < 0) { if (errno != EINTR) { - RTC_LOG_E(LS_ERROR, EN, errno) << "select"; + RTC_LOG_E(LS_ERROR, EN, errno) << "kevent"; return false; } // Else ignore the error and keep going. If this EINTR was for one of the @@ -1352,19 +1364,24 @@ bool PhysicalSocketServer::WaitSelect(int cmsWait, bool process_io) { int fd = pdispatcher->GetDescriptor(); - bool readable = FD_ISSET(fd, &fdsRead); - if (readable) { - FD_CLR(fd, &fdsRead); + struct kevent* e = nullptr; + for (int i = 0; i < n; i++) + { + if (static_cast(fd) == eventsReceived[i].ident) + { + e = &eventsReceived[i]; + break; + } } - bool writable = FD_ISSET(fd, &fdsWrite); - if (writable) { - FD_CLR(fd, &fdsWrite); + if (e) + { + bool readable = e->filter & EVFILT_READ; + bool writable = e->filter & EVFILT_WRITE; + // The error code can be signaled through reads or writes. + ProcessEvents(pdispatcher, readable, writable, /*error_event=*/false, readable || writable); } - // The error code can be signaled through reads or writes. - ProcessEvents(pdispatcher, readable, writable, /*error_event=*/false, - readable || writable); } } @@ -1373,14 +1390,31 @@ bool PhysicalSocketServer::WaitSelect(int cmsWait, bool process_io) { if (ptvWait) { ptvWait->tv_sec = 0; ptvWait->tv_usec = 0; + timeOut.tv_sec = 0; + timeOut.tv_nsec = 0; + int64_t time_left_us = stop_us - rtc::TimeMicros(); if (time_left_us > 0) { ptvWait->tv_sec = time_left_us / rtc::kNumMicrosecsPerSec; ptvWait->tv_usec = time_left_us % rtc::kNumMicrosecsPerSec; + timeOut.tv_sec = ptvWait->tv_sec; + timeOut.tv_nsec = ptvWait->tv_usec * 1000; + } } } + std::vector fdsDelete; + for (unsigned int i = 0; i < fds.size(); i++) + { + struct kevent event; + EV_SET(&event, fds[i].ident, 0, EV_DELETE, 0, 0, nullptr); + fdsDelete.push_back(event); + } + + kevent(mKqueue, fdsDelete.data(), fdsDelete.size(), nullptr, 0, nullptr); + + return true; } diff --git a/rtc_base/physical_socket_server.h b/rtc_base/physical_socket_server.h index f97271f422..a2726139ed 100644 --- a/rtc_base/physical_socket_server.h +++ b/rtc_base/physical_socket_server.h @@ -15,6 +15,9 @@ #if defined(WEBRTC_POSIX) && defined(WEBRTC_LINUX) #include #define WEBRTC_USE_EPOLL 1 +#else +#include +#include #endif #include @@ -105,7 +108,10 @@ class RTC_EXPORT PhysicalSocketServer : public SocketServer { // to have to reset the sequence checker on Wait calls. std::array epoll_events_; const int epoll_fd_ = INVALID_SOCKET; -#endif // WEBRTC_USE_EPOLL +#else // WEBRTC_USE_EPOLL + int mKqueue = 0; +#endif + // uint64_t keys are used to uniquely identify a dispatcher in order to avoid // the ABA problem during the epoll loop (a dispatcher being destroyed and // replaced by one with the same address).sdk-10.11.0/src/000077500000000000000000000000001516266226600132365ustar00rootroot00000000000000sdk-10.11.0/src/android/000077500000000000000000000000001516266226600146565ustar00rootroot00000000000000sdk-10.11.0/src/android/androidFileSystem.cpp000066400000000000000000001464651516266226600210270ustar00rootroot00000000000000#include #include #include #include JavaVM* MEGAjvm = nullptr; jclass fileWrapper = nullptr; jclass integerClass = nullptr; jclass arrayListClass = nullptr; namespace mega { AndroidPlatformURIHelper AndroidPlatformURIHelper::mPlatformHelper; LRUCache AndroidFileWrapper::URIDataCache(LRUCacheSize); LRUCache AndroidFileWrapper::localPathURICache(LRUCacheSize); std::mutex AndroidFileWrapper::URIDataCacheLock; std::mutex AndroidFileWrapper::localPathURICacheLock; AndroidFileWrapper::AndroidFileWrapper(const std::string& path): mURI(path) { if (fileWrapper == nullptr) { LOG_err << "Error: AndroidFileWrapper::AndroidFileWrapper class not found"; return; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID getAndroidFileMethod = env->GetStaticMethodID( fileWrapper, GET_ANDROID_FILE, "(Ljava/lang/String;)Lmega/privacy/android/data/filewrapper/FileWrapper;"); if (getAndroidFileMethod == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::AndroidFileWrapper"; return; } jstring jPath = env->NewStringUTF(mURI.c_str()); jobject temporalObject = env->CallStaticObjectMethod(fileWrapper, getAndroidFileMethod, jPath); env->DeleteLocalRef(jPath); if (temporalObject != nullptr) { mJavaObject = std::make_shared(env->NewGlobalRef(temporalObject)); env->DeleteLocalRef(temporalObject); } } AndroidFileWrapper::AndroidFileWrapper(std::shared_ptr javaObject): mJavaObject(javaObject) {} AndroidFileWrapper::~AndroidFileWrapper() {} int AndroidFileWrapper::getFileDescriptor(bool write) { if (!exists()) { return -1; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, "getFileDescriptor", "(Z)Ljava/lang/Integer;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getFileDescriptor"; return -1; } jobject fileDescriptorObj = env->CallObjectMethod(mJavaObject->mObj, methodID, write); if (fileDescriptorObj && integerClass) { jmethodID intValueMethod = env->GetMethodID(integerClass, "intValue", "()I"); if (!intValueMethod) { return -1; } return env->CallIntMethod(fileDescriptorObj, intValueMethod); } return -1; } bool AndroidFileWrapper::isFolder() { if (!exists()) { return false; } auto data = getURIData(mURI); if (!data.has_value()) { data = URIData(); } else if (data->mIsFolder.has_value()) { return data->mIsFolder.value(); } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, IS_FOLDER, "()Z"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::isFolder"; return false; } data->mIsFolder = env->CallBooleanMethod(mJavaObject->mObj, methodID); setUriData(data.value()); return data->mIsFolder.value(); } std::string AndroidFileWrapper::getURI() const { return mURI; } bool AndroidFileWrapper::isURI() { auto data = getURIData(mURI); if (!data.has_value()) { data = URIData(); } else if (data->mIsURI.has_value()) { return data->mIsURI.value(); } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetStaticMethodID(fileWrapper, IS_PATH, "(Ljava/lang/String;)Z"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Critical error AndroidPlatformHelper::isURI"; return false; } data->mIsURI = !env->CallStaticBooleanMethod(fileWrapper, methodID, env->NewStringUTF(mURI.c_str())); setUriData(data.value()); return data->mIsURI.value(); } std::string AndroidFileWrapper::getName() { if (!exists()) { return std::string(); } auto data = getURIData(mURI); if (!data.has_value()) { data = URIData(); } else if (data->mName.has_value()) { return data->mName.value(); } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, GET_NAME, "()Ljava/lang/String;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getName"; return ""; } jstring name = static_cast(env->CallObjectMethod(mJavaObject->mObj, methodID)); const char* nameStr = env->GetStringUTFChars(name, nullptr); if (!nameStr) { return {}; } data->mName = nameStr; setUriData(data.value()); env->ReleaseStringUTFChars(name, nameStr); return data->mName.value(); } std::vector> AndroidFileWrapper::getChildren() { if (!exists()) { return {}; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, GET_CHILDREN_URIS, "()Ljava/util/List;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getchildren"; return {}; } jobject childrenUris = env->CallObjectMethod(mJavaObject->mObj, methodID); jclass listClass = env->FindClass("java/util/List"); jmethodID sizeMethod = env->GetMethodID(listClass, "size", "()I"); jmethodID getMethod = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); jint size = env->CallIntMethod(childrenUris, sizeMethod); std::vector> children; children.reserve(size); for (jint i = 0; i < size; ++i) { jstring element = (jstring)env->CallObjectMethod(childrenUris, getMethod, i); const char* elementStr = env->GetStringUTFChars(element, nullptr); if (!elementStr) { return {}; } children.push_back(AndroidFileWrapper::getAndroidFileWrapper(elementStr)); env->ReleaseStringUTFChars(element, elementStr); env->DeleteLocalRef(element); } return children; } std::shared_ptr AndroidFileWrapper::pathExists(const std::vector& subPaths) { std::shared_ptr child; for (const auto& childName: subPaths) { // First iteration child undef (check own children), rest iteration use matched child child = !child ? getChildByName(childName) : child->getChildByName(childName); if (!child) { return nullptr; } } return child; } jobject AndroidFileWrapper::vectorToJavaList(JNIEnv* env, const std::vector& vec) { jmethodID init = env->GetMethodID(arrayListClass, "", "()V"); jmethodID add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); jobject list = env->NewObject(arrayListClass, init); for (const std::string& str: vec) { jstring jstr = env->NewStringUTF(str.c_str()); env->CallBooleanMethod(list, add, jstr); env->DeleteLocalRef(jstr); } return list; } std::optional AndroidFileWrapper::createOrReturnElement(const std::string& element, bool create, bool isFolder) { if (!exists()) { return std::nullopt; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, CREATE_NESTED_PATH, "(Ljava/util/List;ZZ)Ljava/lang/String;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::createOrReturnElement"; return std::nullopt; } std::vector subPaths; subPaths.push_back(element); jobject list = vectorToJavaList(env, subPaths); jstring uriString = static_cast( env->CallObjectMethod(mJavaObject->mObj, methodID, list, create, isFolder)); env->DeleteLocalRef(list); if (uriString != nullptr) { const char* elementStr = env->GetStringUTFChars(uriString, nullptr); if (!elementStr) { return std::nullopt; } std::string uri{elementStr}; env->ReleaseStringUTFChars(uriString, elementStr); env->DeleteLocalRef(uriString); return uri; } return std::nullopt; } std::shared_ptr AndroidFileWrapper::createChild(const std::string& childName, bool isFolder) { if (!exists()) { return nullptr; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID( fileWrapper, CREATE_CHILD, "(Ljava/lang/String;Z)Lmega/privacy/android/data/filewrapper/FileWrapper;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::createChild"; return nullptr; } jstring jname{env->NewStringUTF(childName.c_str())}; jobject temporalObject{env->CallObjectMethod(mJavaObject->mObj, methodID, jname, isFolder)}; env->DeleteLocalRef(jname); jobject globalObject{nullptr}; if (temporalObject != nullptr) { globalObject = env->NewGlobalRef(temporalObject); env->DeleteLocalRef(temporalObject); } if (!globalObject) { return nullptr; } return std::shared_ptr( new AndroidFileWrapper(std::make_shared(globalObject))); } std::shared_ptr AndroidFileWrapper::getChildByName(const std::string& name) { if (!exists()) { return nullptr; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, GET_CHILD_BY_NAME, "(Ljava/lang/String;)Ljava/lang/String;"); if (!methodID) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getChildByName"; return nullptr; } jstring jname{env->NewStringUTF(name.c_str())}; jstring uriString = static_cast(env->CallObjectMethod(mJavaObject->mObj, methodID, jname)); env->DeleteLocalRef(jname); if (!uriString) { return nullptr; } const char* elementStr = env->GetStringUTFChars(uriString, nullptr); if (!elementStr) { return {}; } auto aux = AndroidFileWrapper::getAndroidFileWrapper(elementStr); env->ReleaseStringUTFChars(uriString, elementStr); env->DeleteLocalRef(uriString); return aux; } std::shared_ptr AndroidFileWrapper::getParent() const { if (!exists()) { return nullptr; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, GET_PARENT, "()Lmega/privacy/android/data/filewrapper/FileWrapper;"); if (methodID == nullptr) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getParent"; return nullptr; } jobject temporalObject = env->CallObjectMethod(mJavaObject->mObj, methodID); jobject globalObject{nullptr}; if (temporalObject != nullptr) { globalObject = env->NewGlobalRef(temporalObject); env->DeleteLocalRef(temporalObject); } if (!globalObject) { return nullptr; } return std::shared_ptr( new AndroidFileWrapper(std::make_shared(globalObject))); } std::optional AndroidFileWrapper::getPath() { if (!exists()) { return std::nullopt; } if (!isURI()) { return mURI; } auto data = getURIData(mURI); if (!data.has_value()) { data = URIData(); } else if (data->mPath.has_value()) { return data->mPath.value(); } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, GET_PATH, "()Ljava/lang/String;"); if (!methodID) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::getPath"; return std::nullopt; } jstring pathString = static_cast(env->CallObjectMethod(mJavaObject->mObj, methodID)); if (!pathString) { return std::nullopt; } const char* chars = env->GetStringUTFChars(pathString, nullptr); if (!chars) { return std::nullopt; } data->mPath = chars; setUriData(data.value()); env->ReleaseStringUTFChars(pathString, chars); env->DeleteLocalRef(pathString); return data->mPath.value(); } bool AndroidFileWrapper::deleteFile() { if (!exists()) { return false; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, DELETE_FILE, "()Z"); if (!methodID) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::deleteFile"; return false; } return env->CallBooleanMethod(mJavaObject->mObj, methodID); } bool AndroidFileWrapper::deleteEmptyFolder() { if (!exists()) { return false; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, DELETE_EMPTY_FOLDER, "()Z"); if (!methodID) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::deleteEmptyFolder"; return false; } return env->CallBooleanMethod(mJavaObject->mObj, methodID); } bool AndroidFileWrapper::rename(const std::string& parentPath, const std::string& newName, bool overwrite) { if (!exists()) { return false; } JNIEnv* env{nullptr}; MEGAjvm->AttachCurrentThread(&env, NULL); jmethodID methodID = env->GetMethodID(fileWrapper, RENAME_OVERRIDE, "(Ljava/lang/String;Ljava/lang/String;Z)Lmega/privacy/" "android/data/filewrapper/FileWrapper;"); if (!methodID) { env->ExceptionDescribe(); env->ExceptionClear(); LOG_err << "Error: AndroidFileWrapper::rename"; return false; } jstring jPathName = env->NewStringUTF(parentPath.c_str()); jstring jnewName = env->NewStringUTF(newName.c_str()); jobject temporalObject = env->CallObjectMethod(mJavaObject->mObj, methodID, jPathName, jnewName, overwrite); env->DeleteLocalRef(jnewName); env->DeleteLocalRef(jPathName); if (temporalObject != nullptr) { env->DeleteGlobalRef(mJavaObject->mObj); mJavaObject->mObj = env->NewGlobalRef(temporalObject); env->DeleteLocalRef(temporalObject); return true; } return false; } std::shared_ptr AndroidFileWrapper::getAndroidFileWrapper(const LocalPath& localPath, bool create, bool lastIsFolder) { if (localPath.isURI()) { return getAndroidFileWrapperFromURI(localPath, create, lastIsFolder); } return getAndroidFileWrapperFromPath(localPath, create, lastIsFolder); } void AndroidFileWrapper::setLocalPathURI(const std::string& path, const std::string& uri) { std::unique_lock lock(localPathURICacheLock); localPathURICache.put(path, uri); } std::optional AndroidFileWrapper::getLocalPathURI(const std::string& path) { std::unique_lock lock(localPathURICacheLock); return localPathURICache.get(path); } std::shared_ptr AndroidFileWrapper::getAndroidFileWrapperFromURI(const LocalPath& localPath, bool create, bool lastIsFolder) { // Attempt to resolve from URI cache if (auto cachedURI = getLocalPathURI(localPath.toPath(false)); cachedURI.has_value()) { auto fileWrapper = AndroidFileWrapper::getAndroidFileWrapper(cachedURI.value()); // Check if cached reference is still valid if (fileWrapper->exists()) { return fileWrapper; } } // Decompose URI path into segments std::vector pathSegments; LocalPath pathCursor = localPath; while (!pathCursor.isRootPath()) { auto name{pathCursor.leafOrParentName()}; if (name.empty()) { return {}; } pathSegments.insert(pathSegments.begin(), name); pathCursor = pathCursor.parentPath(); } std::shared_ptr currentWrapper = AndroidFileWrapper::getAndroidFileWrapper(pathCursor.toPath(false)); if (!currentWrapper->exists()) { return nullptr; } if (pathSegments.empty()) { return currentWrapper; } std::optional currentURI; for (auto it = pathSegments.begin(); it != pathSegments.end(); ++it) { const auto& segment = *it; LocalPath compositePath = pathCursor; compositePath.appendWithSeparator(LocalPath::fromRelativePath(segment), true); std::shared_ptr nextWrapper; if (auto cachedChildURI = getLocalPathURI(compositePath.toPath(false)); cachedChildURI.has_value()) { currentURI = cachedChildURI.value(); pathCursor = LocalPath::fromURIPath(currentURI.value()); nextWrapper = AndroidFileWrapper::getAndroidFileWrapper(pathCursor.toPath(false)); } // Create intermediate path if necessary if (!nextWrapper || !nextWrapper->exists()) { bool isLast = (std::next(it) == pathSegments.end()); currentURI = currentWrapper->createOrReturnElement(segment, create, !isLast || lastIsFolder); if (!currentURI.has_value()) { return nullptr; } pathCursor = LocalPath::fromURIPath(currentURI.value()); setLocalPathURI(compositePath.toPath(false), currentURI.value()); nextWrapper = AndroidFileWrapper::getAndroidFileWrapper(pathCursor.toPath(false)); } if (!nextWrapper->exists()) { return nullptr; } currentWrapper = nextWrapper; } setLocalPathURI(localPath.toPath(false), currentURI.value()); return currentWrapper; } std::shared_ptr AndroidFileWrapper::getAndroidFileWrapperFromPath(const LocalPath& localPath, bool create, bool lastIsFolder) { if (create) { LocalPath parentPath = localPath.parentPath(); auto parentFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(parentPath.toPath(false)); if (parentFileWrapper->exists()) { std::string name = localPath.leafName().toPath(false); auto wrapper = parentFileWrapper->getChildByName(name); if (wrapper || !create) { return wrapper; } return parentFileWrapper->createChild(localPath.leafName().toPath(false), lastIsFolder); } } else { return AndroidFileWrapper::getAndroidFileWrapper(localPath.toPath(false)); } return nullptr; } bool AndroidFileWrapper::exists() const { return mJavaObject && mJavaObject->mObj != nullptr; } std::shared_ptr AndroidFileWrapper::getAndroidFileWrapper(const std::string& uri) { std::shared_ptr androidFileWrapperNew{new AndroidFileWrapper(uri)}; assert(androidFileWrapperNew); // this method must return a valid AndroidFileWrapper ptr, // otherwise all usages of this method must be reviewed return androidFileWrapperNew; } void AndroidFileWrapper::setUriData(const URIData& uriData) { if (mURI.size()) { std::unique_lock lock(URIDataCacheLock); URIDataCache.put(mURI, uriData); } } std::optional AndroidFileWrapper::getURIData(const std::string& uri) const { std::unique_lock lock(URIDataCacheLock); return URIDataCache.get(uri); } AndroidPlatformURIHelper::AndroidPlatformURIHelper() { URIHandler::setPlatformHelper(this); } bool AndroidPlatformURIHelper::isURI(const std::string& uri) { auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(uri)}; if (androidFileWrapper->exists()) { return androidFileWrapper->isURI(); } return false; } std::optional AndroidPlatformURIHelper::getName(const std::string& uri) { auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(uri)}; if (androidFileWrapper->exists()) { return androidFileWrapper->getName(); } return std::nullopt; } std::optional AndroidPlatformURIHelper::getParentURI(const std::string& uri) { auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(uri)}; if (androidFileWrapper->exists()) { std::shared_ptr parentWrapper{androidFileWrapper->getParent()}; return parentWrapper ? std::optional{parentWrapper->getURI()} : std::nullopt; } return std::nullopt; } std::optional AndroidPlatformURIHelper::getPath(const std::string& uri) { auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(uri)}; if (androidFileWrapper->exists()) { return androidFileWrapper->getPath(); } return std::nullopt; } std::optional AndroidPlatformURIHelper::getURI(const string_type& uri, const std::vector leaves) { auto child{AndroidFileWrapper::getAndroidFileWrapper(uri)}; if (!child) { return std::nullopt; } for (const auto& childName: leaves) { child = child->getChildByName(childName); if (!child) { return std::nullopt; } } string_type aux; std::string newUri = child->getURI(); LocalPath::path2local(&newUri, &aux); return aux; } bool AndroidFileAccess::fopen(const LocalPath& f, OpenFlag flag, FSLogging, DirAccess*, bool, bool, LocalPath*) { fopenSucceeded = false; retry = false; assert(!mFileWrapper); const bool write = openWrite(flag); mFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(f, write, false); if (!mFileWrapper) { return false; } if (!mFileWrapper->exists()) { return false; } std::optional path{mFileWrapper->getPath()}; struct stat statbuf; const bool statCalculated = path.has_value() && stat(path->c_str(), &statbuf) != -1; if (statCalculated && S_ISDIR(statbuf.st_mode)) { type = FOLDERNODE; size = 0; mtime = statbuf.st_mtime; fsid = static_cast(statbuf.st_ino); fsidvalid = true; fopenSucceeded = true; return true; } assert(fd < 0 && "There should be no opened file descriptor at this point"); sysclose(); fd = mFileWrapper->getFileDescriptor(write); if (fd < 0) { LOG_err << "Error getting file descriptor"; errorcode = fd == -2 ? EACCES : ENOENT; return false; } if (!statCalculated && ::fstat(fd, &statbuf) == -1) { errorcode = errno; LOG_err << "Failled to call fstat: " << errorcode << " " << strerror(errorcode); close(fd); fd = -1; return false; } if (S_ISLNK(statbuf.st_mode)) { LOG_err << "Sym links aren't supported in Android"; return -1; } type = S_ISDIR(statbuf.st_mode) ? FOLDERNODE : FILENODE; size = (type == FILENODE || mIsSymLink) ? statbuf.st_size : 0; mtime = statbuf.st_mtime; fsid = static_cast(statbuf.st_ino); fsidvalid = true; FileSystemAccess::captimestamp(&mtime); fopenSucceeded = true; return true; } void AndroidFileAccess::fCloseInternal() { if (fd >= 0) { close(fd); } fd = -1; } void AndroidFileAccess::fclose() { fCloseInternal(); } bool AndroidFileAccess::fwrite(const void* buffer, unsigned long length, m_off_t offset, unsigned long* numWritten, bool* cretry) { // Sanity. assert(buffer || !length); assert(offset >= 0); // Keeps logic simple. if (!cretry) cretry = &retry; auto numWritten_ = 0ul; if (!numWritten) numWritten = &numWritten_; // Assume we can't write any data to file. *numWritten = 0; // Write failures are not retriable on POSIX systems. *cretry = false; // Try and perform the write. auto result = pwrite(fd, buffer, length, offset); // Couldn't perform the write. if (result < 0) return false; // Let the user know how many bytes were written. *numWritten = static_cast(result); // Write is successful if all bytes were be written. return length == *numWritten; } bool AndroidFileAccess::fstat(m_time_t& modified, m_off_t& size) { struct stat attributes; retry = false; if (::fstat(fd, &attributes)) { errorcode = errno; LOG_err << "Unable to stat descriptor: " << fd << ". Error was: " << errorcode; return false; } modified = attributes.st_mtime; size = static_cast(attributes.st_size); return true; } bool AndroidFileAccess::ftruncate(m_off_t size) { retry = false; // Truncate the file. if (::ftruncate(fd, size) == 0) { // Set the file pointer to the end. return lseek(fd, size, SEEK_SET) == size; } // Couldn't truncate the file. return false; } void AndroidFileAccess::updatelocalname(const LocalPath& name, bool force) { if (force || !nonblocking_localname.empty()) { nonblocking_localname = name; mFileWrapper.reset(); } } AndroidFileAccess::AndroidFileAccess(Waiter* w, int defaultfilepermissions, bool): FileAccess(w), mDefaultFilePermissions(defaultfilepermissions) {} AndroidFileAccess::~AndroidFileAccess() { fCloseInternal(); } std::shared_ptr AndroidFileAccess::stealFileWrapper() { sysclose(); return std::exchange(mFileWrapper, nullptr); } bool AndroidFileAccess::setSparse() { return true; } auto AndroidFileAccess::getFileSize() const -> std::optional> { // File isn't open. if (fd < 0) return std::nullopt; struct stat attributes; // Couldn't retrieve the file's attributes. if (::fstat(fd, &attributes) < 0) return std::nullopt; // Note that st_blocks is reported in units of 512B sectors. auto allocatedSize = static_cast(attributes.st_blocks) * 512ul; // st_size is reported in units of bytes. auto reportedSize = static_cast(attributes.st_size); return std::make_pair(allocatedSize, reportedSize); } bool AndroidFileAccess::sysread(void* buffer, unsigned long length, m_off_t offset, bool* cretry) { // Sanity. assert(buffer || !length); assert(offset >= 0); // Keeps logic simple. if (!cretry) cretry = &retry; // Reads are never retriable on POSIX systems. *cretry = false; // Perform the read. auto result = pread(fd, buffer, length, offset); // Read failed. if (result < 0) return false; // Read was successful if all bytes were read. return static_cast(result) == length; } bool AndroidFileAccess::sysstat(m_time_t* mtime, m_off_t* size, FSLogging) { if (!mFileWrapper) { mFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(nonblocking_localname, false, false); } else { assert(nonblocking_localname.asPlatformEncoded(false) == mFileWrapper->getName()); } if (!mFileWrapper) { return false; } if (!mFileWrapper->exists()) { return false; } // Try to calculate first with path, in case of failure, // get statbuf with the file descriptor std::optional path = mFileWrapper->getPath(); struct stat statbuf; if (path.has_value()) { if (stat(path->c_str(), &statbuf) != -1) { if (S_ISLNK(statbuf.st_mode)) { LOG_err << "Sym links aren't supported in Android"; return false; } *size = 0; type = S_ISDIR(statbuf.st_mode) ? FOLDERNODE : FILENODE; if (type == FILENODE) { *size = statbuf.st_size; *mtime = statbuf.st_mtime; FileSystemAccess::captimestamp(mtime); } return true; } } bool opened = false; if (fd < 0) { fd = mFileWrapper->getFileDescriptor(false); if (fd < 0) { errorcode = fd == -2 ? EACCES : ENOENT; LOG_err << "Error getting file descriptor"; return false; } opened = true; } if (::fstat(fd, &statbuf) == -1) { errorcode = errno; LOG_err << "Failled to call fstat: " << errorcode << " " << strerror(errorcode); if (opened) { close(fd); } return false; } if (S_ISLNK(statbuf.st_mode)) { LOG_err << "Sym links aren't supported in Android"; return false; } retry = false; type = TYPE_UNKNOWN; errorcode = 0; if (S_ISDIR(statbuf.st_mode)) { type = FOLDERNODE; if (opened) { close(fd); fd = -1; } return false; } type = FILENODE; *size = statbuf.st_size; *mtime = statbuf.st_mtime; FileSystemAccess::captimestamp(mtime); if (opened) { close(fd); fd = -1; } return true; } bool AndroidFileAccess::sysopen(bool, FSLogging) { assert(fd < 0 && "There should be no opened file descriptor at this point"); errorcode = 0; if (fd >= 0) { sysclose(); } mFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(nonblocking_localname, false, false); if (!mFileWrapper || !mFileWrapper->exists()) { errorcode = ENOENT; return false; } fd = mFileWrapper->getFileDescriptor(false); if (fd < 0) { LOG_err << "Error getting file descriptor"; errorcode = EACCES; } return fd >= 0; } void AndroidFileAccess::sysclose() { assert(nonblocking_localname.empty() || fd >= 0); if (fd >= 0) { close(fd); fd = -1; } } bool AndroidDirAccess::dopen(LocalPath* path, FileAccess* f, bool doglob) { if (doglob) { if (path->isURI()) { return false; } mGlobbing = std::make_unique(); return mGlobbing->dopen(path, f, doglob); } mGlobbing.reset(); mIndex = 0; if (f) { mFileWrapper = static_cast(f)->stealFileWrapper(); } else { assert(path); std::string fstr = path->asPlatformEncoded(false); assert(!mFileWrapper); mFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(fstr); } if (!mFileWrapper->exists()) { return false; } mChildren = mFileWrapper->getChildren(); return true; } bool AndroidDirAccess::dnext(LocalPath& path, LocalPath& name, bool followsymlinks, nodetype_t* type) { if (mGlobbing) { return mGlobbing->dnext(path, name, followsymlinks, type); } if (mChildren.size() <= mIndex) { return false; } auto& next = mChildren[mIndex]; assert(next.get()); path = LocalPath::fromPlatformEncodedAbsolute(next->getURI()); name = LocalPath::fromPlatformEncodedRelative(next->getName()); if (type) { *type = next->isFolder() ? FOLDERNODE : FILENODE; } mIndex++; return true; } std::unique_ptr AndroidFileSystemAccess::newfileaccess(bool followSymLinks) { return std::unique_ptr{ new AndroidFileAccess{waiter, LinuxFileSystemAccess::getdefaultfilepermissions(), followSymLinks}}; } std::unique_ptr AndroidFileSystemAccess::newdiraccess() { return std::unique_ptr(new AndroidDirAccess()); } #ifdef ENABLE_SYNC DirNotify* AndroidFileSystemAccess::newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter) { return new AndroidDirNotify(*this, root, rootPath); } #endif bool AndroidFileSystemAccess::getlocalfstype(const LocalPath& path, FileSystemType& type) const { return LinuxFileSystemAccess::getlocalfstype(getStandartPath(path), type); } bool AndroidFileSystemAccess::getsname(const LocalPath& p1, LocalPath& p2) const { p2 = getStandartPath(p2); return LinuxFileSystemAccess::getsname(getStandartPath(p1), p2); } bool AndroidFileSystemAccess::renamelocal(const LocalPath& oldname, const LocalPath& newname, bool overwrite) { if (oldname.isURI() || newname.isURI()) { auto oldNameWrapper = AndroidFileWrapper::getAndroidFileWrapper(oldname, false, false); if (!oldNameWrapper) { return false; } if (oldname.parentPath() == newname.parentPath()) { auto parent = AndroidFileWrapper::getAndroidFileWrapper(oldname.parentPath(), false, true); if (!parent) { return false; } bool success = oldNameWrapper->rename(parent->getURI(), newname.leafName().toPath(false), overwrite); target_exists = !overwrite && !success; return success; } else { if (copy(oldname, newname, overwrite)) { if (oldNameWrapper->isFolder()) { rmdirlocal(oldname); } else { unlinklocal(oldname); } return true; } return false; } } return LinuxFileSystemAccess::renamelocal(oldname, newname, overwrite); } bool AndroidFileSystemAccess::copylocal(const LocalPath& oldname, const LocalPath& newname, m_time_t time) { if (oldname.isURI() || newname.isURI()) { if (!copy(oldname, newname, true)) { return false; } setmtimelocal(newname, time); return true; } return LinuxFileSystemAccess::copylocal(oldname, newname, time); } bool AndroidFileSystemAccess::unlinklocal(const LocalPath& p1) { if (auto wrapper{AndroidFileWrapper::getAndroidFileWrapper(p1, false, false)}; wrapper && !wrapper->isFolder()) { return wrapper->deleteFile(); } return false; } bool AndroidFileSystemAccess::rmdirlocal(const LocalPath& p1) { emptydirlocal(p1); auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(p1, false, false)}; if (!androidFileWrapper || androidFileWrapper->getChildren().size()) { return false; } return androidFileWrapper->deleteEmptyFolder(); } bool AndroidFileSystemAccess::mkdirlocal(const LocalPath& name, bool, bool) { return AndroidFileWrapper::getAndroidFileWrapper(name, true, true) != nullptr; } bool AndroidFileSystemAccess::setmtimelocal(const LocalPath& path, m_time_t mtime) { auto standardPath = getStandartPath(path); if (standardPath.empty()) { return false; } return LinuxFileSystemAccess::setmtimelocal(standardPath, mtime); } std::pair AndroidFileSystemAccess::getmtimelocal(const LocalPath& path) { auto standardPath = getStandartPath(path); if (standardPath.empty()) { return {false, 0}; } return LinuxFileSystemAccess::getmtimelocal(standardPath); } bool AndroidFileSystemAccess::chdirlocal(LocalPath& path) const { path = getStandartPath(path); return LinuxFileSystemAccess::chdirlocal(path); } bool AndroidFileSystemAccess::issyncsupported(const LocalPath& path, bool& isnetwork, SyncError& syncError, SyncWarning& syncWarning) { return LinuxFileSystemAccess::issyncsupported(getStandartPath(path), isnetwork, syncError, syncWarning); } bool AndroidFileSystemAccess::expanselocalpath(const LocalPath& path, LocalPath& absolutepath) { if (path.isURI()) { absolutepath = path; return true; } return expandLocalPathFileSystem(path, absolutepath); } int AndroidFileSystemAccess::getdefaultfilepermissions() { return LinuxFileSystemAccess::getdefaultfilepermissions(); } void AndroidFileSystemAccess::setdefaultfilepermissions(int permissions) { LinuxFileSystemAccess::setdefaultfilepermissions(permissions); } int AndroidFileSystemAccess::getdefaultfolderpermissions() { return LinuxFileSystemAccess::getdefaultfolderpermissions(); } void AndroidFileSystemAccess::setdefaultfolderpermissions(int permissions) { LinuxFileSystemAccess::setdefaultfolderpermissions(permissions); } void AndroidFileSystemAccess::osversion(string* u, bool includeArchExtraInfo) const { LinuxFileSystemAccess::osversion(u, includeArchExtraInfo); } void AndroidFileSystemAccess::statsid(string* id) const { LinuxFileSystemAccess::statsid(id); } bool AndroidFileSystemAccess::cwd(LocalPath& path) const { path = getStandartPath(path); return LinuxFileSystemAccess::cwd(path); } #ifdef ENABLE_SYNC // True if the filesystem indicated by the specified path has stable FSIDs. bool AndroidFileSystemAccess::fsStableIDs(const LocalPath& path) const { return LinuxFileSystemAccess::fsStableIDs(getStandartPath(path)); } bool AndroidFileSystemAccess::initFilesystemNotificationSystem() { return LinuxFileSystemAccess::initFilesystemNotificationSystem(); } #endif ScanResult AndroidFileSystemAccess::directoryScan(const LocalPath& targetPath, handle expectedFsid, std::map& known, std::vector& results, bool followSymLinks, unsigned& nFingerprinted) { // Whether we can reuse an existing fingerprint. // I.e. Can we avoid computing the CRC? auto reuse = [](const FSNode& lhs, const FSNode& rhs) { return lhs.type == rhs.type && lhs.fsid == rhs.fsid && lhs.fingerprint.mtime == rhs.fingerprint.mtime && lhs.fingerprint.size == rhs.fingerprint.size; }; // Where we store file information. struct stat metadata; // Try and get information about the scan target. bool scanTarget_followSymLink = true; // Follow symlink for the parent directory, so we retrieve // the stats of the path that the symlinks points to std::shared_ptr targetWrapper = AndroidFileWrapper::getAndroidFileWrapper(targetPath, false, true); std::optional uriPath = targetWrapper ? targetWrapper->getPath() : std::nullopt; if (!uriPath.has_value() || stat(uriPath->c_str(), &metadata) == -1) { LOG_warn << "Failed to directoryScan: " << "Unable to stat(...) scan target: " << targetPath << ". Error code was: " << errno; return SCAN_INACCESSIBLE; } // Is the scan target a directory? if (!S_ISDIR(metadata.st_mode)) { LOG_warn << "Failed to directoryScan: " << "Scan target is not a directory: " << targetPath; return SCAN_INACCESSIBLE; } // Are we scanning the directory we think we are? if (expectedFsid != (handle)metadata.st_ino) { LOG_warn << "Failed to directoryScan: " << "Scan target mismatch on expected FSID: " << targetPath << " was " << expectedFsid << " now " << (handle)metadata.st_ino; return SCAN_FSID_MISMATCH; } // What device is this directory on? auto device = metadata.st_dev; auto children = targetWrapper->getChildren(); for (auto child: children) { auto& result = (results.emplace_back(), results.back()); result.localname = LocalPath::fromPlatformEncodedRelative(child->getName()); LocalPath newpath = LocalPath::fromURIPath((child->getURI())); std::optional childPath = child->getPath(); if (!childPath.has_value() || stat(childPath->c_str(), &metadata) == -1) { LOG_warn << "directoryScan: " << "Unable to stat(...) file: " << newpath << ". Error code was: " << errno; // Entry's unknown if we can't determine otherwise. result.type = TYPE_UNKNOWN; continue; } // result.fsid = (handle)entry->d_ino; (posix implementation) result.fsid = static_cast(metadata.st_ino); result.fingerprint.mtime = metadata.st_mtime; captimestamp(&result.fingerprint.mtime); // Are we dealing with a directory? if (S_ISDIR(metadata.st_mode)) { // Then no fingerprint is necessary. result.fingerprint.size = 0; // Assume this directory isn't a mount point. result.type = FOLDERNODE; // Directory's a mount point. if (device != metadata.st_dev) { // Mark directory as a mount so we can emit a stall. result.type = TYPE_NESTED_MOUNT; // Leave a trail for debuggers. LOG_warn << "directoryScan: " << "Encountered a nested mount: " << newpath; } continue; } if (!S_ISREG(metadata.st_mode)) { LOG_warn << "directoryScan: " << "Encountered a special file: " << newpath << ". Mode flags were: " << (metadata.st_mode & S_IFMT); result.isSymlink = S_ISLNK(metadata.st_mode); result.type = result.isSymlink ? TYPE_SYMLINK : TYPE_SPECIAL; continue; } // We're dealing with a regular file. result.type = FILENODE; auto it = known.find(result.localname); // Can we avoid recomputing this file's fingerprint? if (it != known.end() && reuse(result, it->second)) { result.fingerprint = std::move(it->second.fingerprint); continue; } AndroidFileAccess fAccess(nullptr); fAccess.updatelocalname(newpath, true); bool validOpen = fAccess.fopen(newpath, OPEN_RDONLY, FSLogging::logOnError); // Only fingerprint the file if we could actually open it. if (!validOpen) { LOG_warn << "directoryScan: " << "Unable to open file for fingerprinting: " << newpath << ". Error was: " << errno; continue; } // Fingerprint the file. result.fingerprint.genfingerprint(&fAccess); ++nFingerprinted; } return SCAN_SUCCESS; } bool AndroidFileSystemAccess::hardLink(const LocalPath&, const LocalPath&) { return false; } m_off_t AndroidFileSystemAccess::availableDiskSpace(const LocalPath& drivePath) { return LinuxFileSystemAccess::availableDiskSpace(getStandartPath(drivePath)); } void AndroidFileSystemAccess::addevents(Waiter* w, int flag) { LinuxFileSystemAccess::addevents(w, flag); } fsfp_t AndroidFileSystemAccess::fsFingerprint(const LocalPath& path) const { LocalPath auxPath{path}; if (auxPath.isURI()) { auto wrapper = AndroidFileWrapper::getAndroidFileWrapper(path, false, false); auto p = wrapper ? wrapper->getPath() : std::nullopt; if (p.has_value()) { auxPath = LocalPath::fromAbsolutePath(p.value()); } } return LinuxFileSystemAccess::fsFingerprint(auxPath); } void AndroidFileSystemAccess::emptydirlocal(const LocalPath& path, dev_t) { auto wrapper = AndroidFileWrapper::getAndroidFileWrapper(path, false, false); if (!wrapper || !wrapper->isFolder()) { return; } for (const auto& child: wrapper->getChildren()) { if (child->isFolder()) { LocalPath childPath = path; childPath.appendWithSeparator(LocalPath::fromRelativePath(child->getName()), false); emptydirlocal(childPath); child->deleteEmptyFolder(); } else { child->deleteFile(); } } } LocalPath AndroidFileSystemAccess::getStandartPath(const LocalPath& localPath) const { if (!localPath.isURI()) { return localPath; } auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(localPath, false, false)}; if (androidFileWrapper) { if (auto path = androidFileWrapper->getPath(); path.has_value()) { LocalPath auxPath = LocalPath::fromAbsolutePath(path->c_str()); return auxPath; } } return LocalPath{}; } bool AndroidFileSystemAccess::copy(const LocalPath& oldname, const LocalPath& newname, bool overwrite) { auto androidfileWrapper{AndroidFileWrapper::getAndroidFileWrapper(oldname, false, false)}; if (!androidfileWrapper) { return false; } if (androidfileWrapper->isFolder()) { if (mkdirlocal(newname, false, true)) { for (const auto& child: androidfileWrapper->getChildren()) { LocalPath childNewPath{newname}; childNewPath.appendWithSeparator(LocalPath::fromRelativePath(child->getName()), false); LocalPath childOldPath{oldname}; childOldPath.appendWithSeparator(LocalPath::fromRelativePath(child->getName()), false); copy(childOldPath, childNewPath, overwrite); } } return true; } unique_ptr oldFile{newfileaccess()}; if (!oldFile->fopen(oldname, OPEN_RDONLY, FSLogging::logOnError)) { LOG_warn << "Unable to open source file, copy failed"; return false; } // Check if destination exists if overwrite is false if (!overwrite) { if (auto exitingFileWrapper = AndroidFileWrapper::getAndroidFileWrapper(newname, false, false); exitingFileWrapper && exitingFileWrapper->exists()) { target_exists = true; LOG_info << "Destination file already exists and overwrite is false"; return false; } } unique_ptr newFile{newfileaccess()}; if (!newFile->fopen(newname, OPEN_RDWR, FSLogging::logOnError)) { LOG_warn << "Unable to open target file, copy failed"; return false; } { if (!newFile->ftruncate(0)) { LOG_warn << "Failed to truncate destination before copy: " << newname; } constexpr uint32_t BUFFER_SIZE{16384}; unsigned char buffer[BUFFER_SIZE]; size_t pos{0}; bool followRead = true; // Set true when last pacake isn't complete, bool moreData = true; do { unsigned bytesToRead{BUFFER_SIZE}; if (static_cast(pos + bytesToRead) > oldFile->size) { bytesToRead = static_cast(oldFile->size) - static_cast(pos); moreData = false; } followRead = oldFile->frawread(static_cast(buffer), bytesToRead, static_cast(pos), true, FSLogging::logOnError); newFile->fwrite(static_cast(buffer), bytesToRead, static_cast(pos)); pos += bytesToRead; } while (followRead && moreData); oldFile->closef(); newFile->closef(); LOG_verbose << "Copying via read/write"; return true; } LOG_warn << "Unable to copy file"; return false; } bool AndroidFileSystemAccess::isFileWrapperActive(const FileSystemAccess* fsa) { if (auto afsa = dynamic_cast(fsa); afsa) { return afsa->isFileWrapperActive(); } return false; } AndroidDirNotify::AndroidDirNotify(AndroidFileSystemAccess& owner, LocalNode& root, const LocalPath& rootPath): LinuxDirNotify(owner, root, rootPath) {} AddWatchResult AndroidDirNotify::addWatch(LocalNode& node, const LocalPath& path, handle fsid) { LocalPath auxPath{path}; if (auxPath.isURI()) { auto androidFileWrapper{AndroidFileWrapper::getAndroidFileWrapper(auxPath, false, false)}; if (!androidFileWrapper) { return make_pair(WatchMapIterator{}, WR_FAILURE); } auto pathStr{androidFileWrapper->getPath()}; if (pathStr.has_value()) { auxPath = LocalPath::fromAbsolutePath(pathStr.value()); } else { return make_pair(WatchMapIterator{}, WR_FAILURE); } } return LinuxDirNotify::addWatch(node, auxPath, fsid); } } // namespace sdk-10.11.0/src/arguments.cpp000066400000000000000000000026341516266226600157540ustar00rootroot00000000000000#include "mega/arguments.h" namespace mega { std::string Arguments::getValue(const std::string& name, const std::string& defaultValue) const { auto it = mValues.find(name); return it == mValues.end() ? defaultValue : it->second; } bool Arguments::empty() const { return mValues.empty(); } Arguments::size_type Arguments::size() const { return mValues.size(); } bool Arguments::contains(const std::string& name) const { return mValues.count(name) > 0; } std::ostream& operator<<(std::ostream& os, const Arguments& arguments) { for (auto& argument : arguments.mValues) { os << " " << argument.first << "=" << argument.second << std::endl; } return os; } Arguments ArgumentsParser::parse(int argc, char* argv[]) { std::vector argVec; std::copy(argv + 1, argv + argc, std::back_inserter(argVec)); Arguments arguments; for (const auto& arg : argVec) { // A argument wouldn't be emplaced (thus dropped) if it is duplicated with a previous one arguments.mValues.emplace(parseOneArgument(arg)); } return arguments; } std::pair ArgumentsParser::parseOneArgument(const std::string& argument) { const auto pos = argument.find('='); return pos == argument.npos ? std::make_pair(argument, "") : std::make_pair(argument.substr(0, pos), argument.substr(pos + 1)); } } sdk-10.11.0/src/attrmap.cpp000066400000000000000000000241051516266226600154140ustar00rootroot00000000000000/** * @file attrmap.cpp * @brief Class for manipulating node attributes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/attrmap.h" #include namespace mega { // approximate raw storage size of serialized AttrMap, not taking JSON escaping // or name length into account unsigned AttrMap::storagesize(int perrecord) const { assert(perrecord >= 0); unsigned t = 0; for (attr_map::const_iterator it = map.begin(); it != map.end(); it++) { t += static_cast(static_cast(perrecord) + it->second.size()); } return t; } bool AttrMap::getBool(const char* name) const { nameid id = string2nameid(name); attr_map::const_iterator iter = map.find(id); bool value = iter != map.end() && iter->second == "1"; return value; } std::optional AttrMap::getString(const std::string_view name) const { const auto id = AttrMap::string2nameid(name); if (const auto it = map.find(id); it != std::end(map)) return std::optional{std::in_place, it->second}; return std::nullopt; } std::optional AttrMap::getStringView(const std::string_view k) const { const auto name = AttrMap::string2nameid(k); if (const auto it = map.find(name); it != std::end(map)) return it->second; return std::nullopt; } int AttrMap::nameid2string(nameid id, char* buf) { char* ptr = buf; for (int i = 64; (i -= 8) >= 0;) { *ptr = static_cast((id >> i) & 0xff); if (*ptr) { ptr++; } } return static_cast(ptr - buf); } string AttrMap::nameid2string(nameid id) { string s; s.resize(10); s.resize(static_cast(nameid2string(id, const_cast(s.data())))); return s; } nameid AttrMap::string2nameid(const char* n) { if (!n) { return 0; } return makeNameid(n); } // generate binary serialize of attr_map name-value pairs void AttrMap::serialize(string* d) const { char buf[8]; unsigned char l; unsigned short ll; for (attr_map::const_iterator it = map.begin(); it != map.end(); it++) { l = (unsigned char)nameid2string(it->first, buf); if (l) { d->append((char*)&l, sizeof l); d->append(buf, l); ll = (unsigned short)it->second.size(); d->append((char*)&ll, sizeof ll); d->append(it->second.data(), ll); } } d->append("", 1); } // read binary serialize, return final offset const char* AttrMap::unserialize(const char* ptr , const char *end) { unsigned char l; unsigned short ll; nameid id; while ((ptr < end) && (l = static_cast(*ptr++)) != 0) { id = 0; if (ptr + l + sizeof ll > end) { return NULL; } while (l--) { id = (id << 8) + (unsigned char)*ptr++; } ll = static_cast(MemAccess::get(ptr)); ptr += sizeof ll; if (ptr + ll > end) { return NULL; } map[id].assign(ptr, ll); ptr += ll; } return ptr; } bool AttrMap::hasUpdate(nameid attrId, const attr_map& updates) const { auto curIt = map.find(attrId); auto updIt = updates.find(attrId); return updIt != updates.end() && // is present in updates AND ((curIt == map.end() && !updIt->second.empty()) || // is not present here and has non-empty value in updates OR (curIt != map.end() && curIt->second != updIt->second)); // is present here but has different value in updates } bool AttrMap::hasDifferentValue(nameid attrId, const attr_map& otherAttrs) const { auto curIt = map.find(attrId); auto otherIt = otherAttrs.find(attrId); return (curIt != map.end() && otherIt == otherAttrs.end()) || // present only here OR (curIt == map.end() && otherIt != otherAttrs.end()) || // present only in other attrs OR (curIt != map.end() && otherIt != otherAttrs.end() && curIt->second != otherIt->second); // have different values } void AttrMap::removeEmptyValues() { for (auto it = map.begin(); it != map.end();) { if (it->second.empty()) it = map.erase(it); else ++it; } } void AttrMap::applyUpdates(const attr_map& updates) { for (auto& u : updates) { if (u.second.empty()) map.erase(u.first); else map[u.first] = u.second; } } // generate JSON object containing attr_map void AttrMap::getjson(string* s) const { nameid id; char buf[8]; const char* ptr; const char* pptr; // reserve estimated size of final string s->erase(); s->reserve(storagesize(20)); for (attr_map::const_iterator it = map.begin(); it != map.end(); it++) { s->append(s->size() ? ",\"" : "\""); id = it->first; if (id) { // no JSON escaping here, as no escape characters are allowed in // attribute names s->append(buf, static_cast(nameid2string(id, buf))); s->append("\":\""); // JSON-escape value pptr = it->second.c_str(); ptr = it->second.c_str(); for (int i = static_cast(it->second.size()); i-- >= 0; ptr++) { if ((i < 0) || ((*(const signed char*)ptr >= 0) && (*ptr < ' ')) || (*ptr == '"') || (*ptr == '\\')) { if (ptr > pptr) { s->append(pptr, static_cast(ptr - pptr)); } if (i >= 0) { s->append("\\"); switch (*ptr) { case '"': s->append("\""); break; case '\\': s->append("\\"); break; case '\n': s->append("n"); break; case '\r': s->append("r"); break; case '\b': s->append("b"); break; case '\f': s->append("f"); break; case '\t': s->append("t"); break; default: s->append("u00"); snprintf(buf, sizeof(buf), "%02x", (unsigned char)*ptr); s->append(buf); } pptr = ptr + 1; } } } s->append("\""); } } } std::string AttrMap::getJsonObject() const { std::string result{"{"}; for (auto [k, v]: map) { result += "\"" + nameid2string(k) + "\":\"" + v + "\","; } result.pop_back(); if (!map.empty()) { result.append("}"); } return result; } std::string AttrMap::getjson() const { std::string result; getjson(&result); return result; } void AttrMap::fromjsonObject(const std::string& buf) { map.clear(); JSON json; json.begin(buf.c_str()); if (!json.enterobject()) { return; } nameid name; while ((name = json.getnameid()) != EOO) { if (!json.storeobject(&map[name])) { map.clear(); return; } } if (!json.leaveobject()) { map.clear(); return; } } void AttrMap::fromjson(const char* buf) { if (!buf) return; JSON json; json.begin(buf); nameid name; string* t; while ((name = json.getnameid()) != EOO && json.storeobject((t = &map[name]))) { JSON::unescape(t); } } std::optional AttrMap::getComplexNestedJsonObject(const std::string_view parentName, const std::string_view childName) const { const auto parentId = AttrMap::string2nameid(parentName); const auto it = map.find(parentId); if (it == std::end(map)) return std::nullopt; const char* buf = it->second.c_str(); const char* end = buf + it->second.size(); AttrMap aux; JSON json; json.begin(buf); nameid name; auto childId = AttrMap::string2nameid(childName); bool found = false; while (!found && (name = json.getnameid()) != EOO) { if (name != childId) { std::string auxConsum; json.storeobject(&auxConsum); continue; } found = true; const char* auxbuf = json.pos; while (auxbuf < end && *auxbuf != '}') { aux.map[name] += *auxbuf++; } if (auxbuf == end) { aux.map.clear(); // unexpected format } else { aux.map[name] += '}'; } } return aux.map.empty() ? std::optional{std::nullopt} : aux; } std::optional AttrMap::getNestedJsonObject(const std::string_view name) const { const auto id = AttrMap::string2nameid(name); const auto it = map.find(id); if (it == std::end(map)) return std::nullopt; AttrMap result; result.fromjson(it->second.c_str()); return result; } } // namespace sdk-10.11.0/src/autocomplete.cpp000066400000000000000000001156411516266226600164530ustar00rootroot00000000000000/** * @file autocomplete.cpp * @brief Win32 console I/O autocomplete support * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ // autocomplete to support platforms without readline (and complement readline where it is available) #include #include #include #include #if !defined(__MINGW32__) && !defined(__ANDROID__) && (!defined(__GNUC__) || (__GNUC__*100+__GNUC_MINOR__) >= 503) #define HAVE_FILESYSTEM #include namespace fs = std::filesystem; #endif namespace mega { namespace autocomplete { using namespace std; inline static bool icmp(char a, char b) { return(toupper(a) == toupper(b)); } ACState::quoting::quoting() { } ACState::quoting::quoting(std::string& s) { quoted = (!s.empty() && s[0] == '\"') || (s[0] == '\''); if (quoted) { quote_char = s[0]; s.erase(0, 1); if (!s.empty() && s.back() == quote_char) { s.pop_back(); } } } void ACState::quoting::applyQuotes(std::string& w) { if (quoted && quote_char != 0) { // reapply quotes as the user had them w.reserve(w.size() + 2); w.insert(0, 1, quote_char); w.push_back(quote_char); } else { // add quotes if it has a space in it now and doesn't already start with a quote if (w.find(' ') != std::string::npos) { w = "\"" + w + "\""; } } } ACState::quoted_word::quoted_word() { } ACState::quoted_word::quoted_word(const std::string &str) : s(str), q(s) { } ACState::quoted_word::quoted_word(const std::string &str, const quoting& quot) : s (str), q(quot) { } string ACState::quoted_word::getQuoted() { string qs = s; q.applyQuotes(qs); return qs; } bool ACState::extractflag(const string& flag) { for (auto it = words.begin(); it != words.end(); ++it) { if (it->s == flag && !it->q.quoted) { words.erase(it); return true; } } return false; }; bool ACState::extractflagparam(const string& flag, string& param) { for (auto it = words.begin(); it != words.end(); ++it) { if (it->s == flag) { auto j = it; ++j; if (j != words.end()) { param = j->s; words.erase(it, ++j); return true; } } } return false; } std::optional ACState::extractflagparam(const std::string& flag) { std::string value; if (extractflagparam(flag, value)) return std::optional(std::in_place, std::move(value)); return std::nullopt; } void ACState::addCompletion(const std::string& s, bool caseInsensitive, bool couldextend) { // add if it matches the prefix. Doing the check here keeps subclasses simple assert(atCursor()); const std::string& prefix = word().s; if (!s.empty() && s.size() >= prefix.size()) { bool equal; if (caseInsensitive) { equal = std::equal(prefix.begin(), prefix.end(), s.begin(), icmp); } else { equal = s.compare(0, prefix.size(), prefix) == 0; } if (equal) { // also only offer options when the user starts with "-", and not otherwise. if ((s[0] == '-' && !prefix.empty() && prefix[0] == '-') || (s[0] != '-' && (prefix.empty() || prefix[0] != '-'))) { completions.emplace_back(s, caseInsensitive, couldextend); } } } } void ACState::addPathCompletion(std::string&& f, const std::string& relativeRootPath, bool isFolder, char dir_sep, bool caseInsensitive) { if (f.size() > relativeRootPath.size() && f.compare(0, relativeRootPath.size(), relativeRootPath) == 0) { f.erase(0, relativeRootPath.size()); } if (dir_sep != '\\') { string from = "\\"; string to(1,dir_sep); size_t start_pos = 0; while (( start_pos = f.find(from, start_pos)) != std::string::npos) { f.replace(start_pos, from.length(), to); start_pos += to.length(); } } if (unixStyle && isFolder) { f.push_back(dir_sep); } addCompletion(f, caseInsensitive, isFolder); } std::ostream& operator<<(std::ostream& s, const ACNode& n) { return n.describe(s); } ACNode::~ACNode() { } Optional::Optional(ACN n) : subnode(n) { } bool Optional::addCompletions(ACState& s) { subnode->addCompletions(s); return s.i == s.words.size(); } bool Optional::match(ACState& s) const { auto i = s.i; if (!subnode->match(s)) s.i = i; return true; } std::ostream& Optional::describe(std::ostream& s) const { if (auto e = dynamic_cast(subnode.get())) { std::ostringstream s2; s2 << *e; std::string str = s2.str(); if (str.size() >= 2 && str.front() == '(' && str.back() == ')') { str.pop_back(); str.erase(0, 1); } return s << "[" << str << "]"; } else { return s << "[" << *subnode << "]"; } } Repeat::Repeat(ACN n) : subnode(n) { } bool Repeat::addCompletions(ACState& s) { unsigned n = s.i; while (s.i < s.words.size() && !subnode->addCompletions(s)) { if (s.i <= n) // not advancing break; n = s.i; } return s.i >= s.words.size(); } bool Repeat::match(ACState& s) const { for (;;) { if (s.i >= s.words.size()) break; auto i = s.i; if (!subnode->match(s)) { s.i = i; break; } } return true; } std::ostream& Repeat::describe(std::ostream& s) const { return s << *subnode << "*"; } Sequence::Sequence(ACN n1, ACN n2) : current(n1), next(n2) { } bool Sequence::addCompletions(ACState& s) { if (current->addCompletions(s)) { return true; } bool stop = s.i < s.words.size() ? next->addCompletions(s) : true; return stop; } bool Sequence::match(ACState& s) const { return current->match(s) && next->match(s); } std::ostream& Sequence::describe(std::ostream& s) const { return s << *current << " " << *next; } Text::Text(const std::string& s, bool isParam) : exactText(s) , param(isParam) { assert(!exactText.empty() && exactText[0] != '-'); } bool Text::addCompletions(ACState& s) { if (s.atCursor()) { s.addCompletion(param ? "<" + exactText + ">" : exactText); return true; } else { bool matches = param ? (!s.word().s.empty() && (s.word().s[0] != '-' || s.word().q.quoted)) : (s.word().s == exactText); s.i += matches ? 1 : 0; return !matches; } } bool Text::match(ACState& s) const { if (s.i < s.words.size() && (param ? !s.word().s.empty() && (s.word().s[0] != '-' || s.word().q.quoted) : s.word().s == exactText)) { s.i += 1; return true; } return false; } std::ostream& Text::describe(std::ostream& s) const { return s << (param ? "<" + exactText + ">" : exactText); } bool ExportedLink::isLink(const string& s, bool file, bool folder) { bool filestr = (s.find("https://mega.nz/#!") != string::npos || s.find("https://mega.nz/file/") != string::npos) || (s.find("https://mega.co.nz/#!") != string::npos || s.find("https://mega.co.nz/file/") != string::npos) || (s.find("https://mega.app/#!") != string::npos || s.find("https://mega.app/file/") != string::npos); bool folderstr = (s.find("https://mega.nz/#F!") != string::npos || s.find("https://mega.nz/folder/") != string::npos) || (s.find("https://mega.co.nz/#F!") != string::npos || s.find("https://mega.co.nz/folder/") != string::npos) || (s.find("https://mega.app/#F!") != string::npos || s.find("https://mega.app/folder/") != string::npos); if (file && !folder) { return filestr; } else if (!file && folder) { return folderstr; } return filestr || folderstr; } ExportedLink::ExportedLink(bool file, bool folder) : filelink(file), folderlink(folder) { } bool ExportedLink::addCompletions(ACState& s) { if (s.atCursor()) { if (filelink && !folderlink) { s.addCompletion(""); } else if (!filelink && folderlink) { s.addCompletion(""); } else { s.addCompletion(""); } return true; } else { bool matches = !s.word().s.empty() && s.word().s[0] != '-' && isLink(s.word().s, filelink, folderlink); s.i += matches ? 1 : 0; return !matches; } } bool ExportedLink::match(ACState& s) const { if (s.i < s.words.size() && (!s.word().s.empty() && s.word().s[0] != '-' && isLink(s.word().s, filelink, folderlink))) { s.i += 1; return true; } return false; } std::ostream& ExportedLink::describe(std::ostream& s) const { if (filelink && !folderlink) { return s << ""; } else if (!filelink && folderlink) { return s << ""; } else { return s << ""; } } Flag::Flag(const std::string& s) : flagText(s) { assert(!flagText.empty() && flagText[0] == '-'); } bool Flag::addCompletions(ACState& s) { if (s.atCursor()) { // only offer flag completions if the user requests it with "-" if (!s.word().s.empty() && s.word().s[0] == '-') { s.addCompletion(flagText); } return true; } else { bool matches = s.word().s == flagText; s.i += matches ? 1 : 0; return !matches; } } bool Flag::match(ACState& s) const { if (s.i < s.words.size() && s.word().s == flagText) { s.i += 1; return true; } return false; } std::ostream& Flag::describe(std::ostream& s) const { return s << flagText; } Either::Either(const std::string& prefix) : describePrefix(prefix) { } void Either::Add(ACN n) { if (n) { eithers.push_back(n); execFuncs.push_back(nullptr); } } void Either::Add(ExecFn f, ACN n) { if (n) { eithers.push_back(n); execFuncs.push_back(f); } } bool Either::addCompletions(ACState& s) { bool stop = true; unsigned n = s.i; unsigned best_s_i = s.i; for (auto& p : eithers) { s.i = static_cast(n); if (!p->addCompletions(s)) { stop = false; best_s_i = std::max(s.i, best_s_i); } } s.i = best_s_i; return stop; } bool Either::match(ACState& s) const { auto i = s.i; for (auto e : eithers) { s.i = i; if (e->match(s)) return true; // todo: address possible ambiguities } return false; } std::ostream& Either::describe(std::ostream& s) const { if (!describePrefix.empty()) { for (unsigned i = 0; i < eithers.size(); ++i) { s << describePrefix << *eithers[i] << std::endl; } } else { std::ostringstream s2; for (int i = 0; i < int(eithers.size() * 2) - 1; ++i) { (i & 1 ? s2 << "|" : s2 << *eithers[static_cast(i / 2)]); } std::string str = s2.str(); if (str.find(' ') == std::string::npos) { s << str; } else { s << "(" << str << ")"; } } return s; } WholeNumber::WholeNumber(const std::string& description, size_t defaultValue) : defaultvalue(defaultValue) , description(description) { } bool WholeNumber::addCompletions(ACState& s) { if (s.atCursor()) { s.addCompletion(to_string(defaultvalue)); return true; } else { for (char c : s.word().s) { if (!is_digit(static_cast(c))) { return true; } } s.i += 1; } return false; } bool WholeNumber::match(ACState& s) const { if (s.i < s.words.size()) { for (char c : s.word().s) { if (!is_digit(static_cast(c))) { return false; } } s.i += 1; return true; } return false; } std::ostream& WholeNumber::describe(std::ostream& s) const { return s << description; } LocalFS::LocalFS(bool files, bool folders, const std::string descriptionPrefix) : reportFiles(files) , reportFolders(folders) , descPref(descriptionPrefix) { } bool LocalFS::addCompletions(ACState& s) { if (s.atCursor()) { #ifdef HAVE_FILESYSTEM fs::path searchPath = fs::u8path(s.word().s + (s.word().s.empty() || (s.word().s.back() == '\\' || s.word().s.back() == '/' ) ? "*" : "")); #ifdef WIN32 char sep = (!s.word().s.empty() && s.word().s.find('/') != string::npos ) ?'/':'\\'; #else char sep = '/'; #endif bool relative = !searchPath.is_absolute(); searchPath = relative ? (fs::current_path() /= searchPath) : searchPath; std::string cp = relative ? fs::current_path().u8string() + sep : ""; if ((searchPath.filename() == ".." || searchPath.filename() == ".") && fs::exists(searchPath)) { s.addPathCompletion(searchPath.u8string(), cp, true, sep, true); } else { searchPath.remove_filename(); // iterate the whole directory, and filter #ifdef WIN32 std::string spath = searchPath.u8string(); if (spath.back() == ':') { searchPath = spath.append("\\"); } #endif if (fs::exists(searchPath) && fs::is_directory(searchPath)) { for (fs::directory_iterator iter(searchPath); iter != fs::directory_iterator(); ++iter) { if ((reportFolders && fs::is_directory(iter->status())) || (reportFiles && fs::is_regular_file(iter->status()))) { s.addPathCompletion(iter->path().u8string(), cp, fs::is_directory(iter->status()), sep, true); } } } } #else // todo: implement local directory listing for any platforms without std::filsystem, if it turns out to be needed #endif return true; } else { // don't let an option be misinterpreted as a filename. Files beginning with a '-' will need to be quoted bool stop = s.i >= s.words.size() || s.word().s.empty() || s.word().s.at(0) == '-'; s.i += stop ? 0 : 1; return stop; } } bool LocalFS::match(ACState& s) const { if (s.i < s.words.size()) { if (!s.word().s.empty() && s.word().s[0] != '-') { s.i += 1; return true; } } return false; } std::ostream& LocalFS::describe(std::ostream& s) const { return s << descPref << (descPref.size() < 10 ? (reportFiles ? (reportFolders ? "localpath" : "localfile") : "localfolder") : ""); } MegaFS::MegaFS(bool files, bool folders, MegaClient* c, ::mega::NodeHandle* curDirHandle, const std::string descriptionPrefix) : client(c) , cwd(curDirHandle) , reportFiles(files) , reportFolders(folders) , descPref(descriptionPrefix) { } shared_ptr addShareRootCompletions(ACState& s, MegaClient* client, string& pathprefix) { const string& path = s.word().s; string::size_type t = path.find_first_of(":/"); if (t == string::npos || path[t] == ':') { for (const user_map::value_type& u : client->users) { if (t == string::npos && !u.second.sharing.empty()) { string str; s.addCompletion(u.second.email + ":", true, true); } else if (u.second.email == path.substr(0, t)) { string::size_type pos = path.find_first_of("/", t + 1); for (handle h : u.second.sharing) { if (shared_ptr n = client->nodebyhandle(h)) { if (pos == string::npos) { s.addPathCompletion(path.substr(0, t + 1) + n->displayname(), "", n->type != FILENODE, '/', false); } else if (n->displayname() == path.substr(t + 1, pos - t - 1)) { pathprefix = path.substr(0, pos + 1); return n; } } } } } } return NULL; } bool MegaFS::addCompletions(ACState& s) { if (s.atCursor()) { if (client && cwd) { shared_ptr n; std::string pathprefix; if (!s.word().s.empty() && s.word().s[0] == '/') { if (s.word().s.size() >= 2 && s.word().s[1] == '/') { if (s.word().s.size() >= 5 && !strncmp(s.word().s.c_str(), "//in/", 5)) { pathprefix = "//in/"; n = client->nodeByHandle(client->mNodeManager.getRootNodeVault()); } else if (s.word().s.size() >= 6 && !strncmp(s.word().s.c_str(), "//bin/", 6)) { pathprefix = "//bin/"; n = client->nodeByHandle(client->mNodeManager.getRootNodeRubbish()); } else { s.addPathCompletion(string("//bin"), "", true, '/', false); s.addPathCompletion(string("//in"), "", true, '/', false); return true; } } else { pathprefix = "/"; n = client->nodeByHandle(client->mNodeManager.getRootNodeFiles()); } } else { n = addShareRootCompletions(s, client, pathprefix); if (!n && *cwd != UNDEF) { n = client->nodeByHandle(*cwd); pathprefix.clear(); } } // drill down folders size_t sepPos = 0; while (n && std::string::npos != (sepPos = s.word().s.find('/', pathprefix.size()))) { std::string folderName = s.word().s.substr(pathprefix.size(), sepPos - pathprefix.size()); pathprefix += folderName + "/"; if (folderName == ".") { } else if (folderName == "..") { n = n->parent; } else { shared_ptr nodematch = NULL; for (auto& subnode : client->getChildren(n.get())) { if (subnode->type == FOLDERNODE && subnode->displayname() == folderName) { nodematch = subnode; break; } } n = nodematch; } } std::string leaf = s.word().s.substr(pathprefix.size(), std::string::npos); if (n && (leaf == "." || (leaf == ".." && n->type != ROOTNODE))) { s.addPathCompletion(string(s.word().s), "", true, '/', false); } else { // iterate specified folder if (n) { for (auto& subnode : client->getChildren(n.get())) { if ((reportFolders && subnode->type == FOLDERNODE) || (reportFiles && subnode->type == FILENODE)) { s.addPathCompletion(pathprefix + subnode->displayname(), "", subnode->type == FOLDERNODE, '/', false); } } } } } return true; } else { // don't let an option be misinterpreted as a filename. Files beginning with a '-' will need to be quoted bool stop = s.word().s.empty() || s.word().s.at(0) == '-'; s.i += stop ? 0 : 1; return stop; } } bool MegaFS::match(ACState& s) const { if (s.i < s.words.size()) { if (!s.word().s.empty() && s.word().s[0] != '-' && !ExportedLink::isLink(s.word().s, true, true)) { s.i += 1; return true; } } return false; } std::ostream& MegaFS::describe(std::ostream& s) const { return s << descPref << (reportFiles ? (reportFolders ? "remotepath" : "remotefile") : "remotefolder"); } MegaContactEmail::MegaContactEmail(MegaClient* c) : client(c) { } bool MegaContactEmail::addCompletions(ACState& s) { if (s.atCursor()) { if (client) { for (const user_map::value_type& u : client->users) { if (u.second.show == VISIBLE) { s.addCompletion(u.second.email, true); } } } return true; } else { // don't let an option be misinterpreted as an email. Emails beginning with a '-' (prob not legal anyway) will need to be quoted bool stop = s.word().s.empty() || s.word().s.at(0) == '-'; s.i += stop ? 0 : 1; return stop; } } bool MegaContactEmail::match(ACState& s) const { if (s.i < s.words.size()) { if (!s.word().s.empty() && s.word().s[0] != '-') { s.i += 1; return true; } } return false; } #ifdef ENABLE_SYNC BackupID::BackupID(MegaClient& client, bool onlyActive) : mClient(client) , mOnlyActive(onlyActive) { } bool BackupID::addCompletions(ACState& state) { auto ids = backupIDs(); if (state.atCursor()) { for (auto& id : filter(ids, state)) state.addCompletion(std::move(id)); return true; } return match(ids, state); } std::ostream& BackupID::describe(std::ostream& ostream) const { return ostream << "BackupID"; } string_vector& BackupID::filter(string_vector& ids, const ACState& state) const { if (state.i >= state.words.size()) return ids; auto& word = state.words.back(); auto& prefix = word.s; if (prefix.empty()) return ids; auto predicate = [&prefix](const string& id) { return prefix.size() > id.size() || id.compare(0, prefix.size(), prefix); }; auto i = remove_if(ids.begin(), ids.end(), predicate); ids.erase(i, ids.end()); return ids; } bool BackupID::match(ACState& state) const { return state.i < state.words.size() && match(backupIDs(), state); } string_vector BackupID::backupIDs() const { string_vector result; handle_set seen; for (auto& config : mClient.syncs.getConfigs(mOnlyActive)) { if (seen.emplace(config.mBackupId).second) result.emplace_back(toHandle(config.mBackupId)); } return result; } bool BackupID::match(const string_vector& ids, ACState& state) const { auto& word = state.words[state.i]; if (word.s.empty() || (!word.q.quoted && word.s[0] == '-')) return false; auto i = find(ids.begin(), ids.end(), word.s); if (i != ids.end()) return ++state.i, true; return false; } ACN backupID(MegaClient& client, bool onlyActive) { return make_shared(client, onlyActive); } #endif // ENABLE_SYNC std::ostream& MegaContactEmail::describe(std::ostream& s) const { return s << ""; } std::pair identifyNextWord(const std::string& line, int startPos) { const char* strStart = line.c_str(); const char* ptr = strStart + startPos; while (*ptr > 0 && *ptr <= ' ') { ptr++; } std::pair ret(int(ptr - strStart), int(ptr - strStart)); if (!*ptr) { return ret; } // todo: yes the console recognises escapes on linux, but not on windows. Should we match the platform we are executing on (up to a reasonable point - as the rules on windows are pretty messy!)? https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cmd.mspx?mfr=true if (*ptr == '"') { for (++ptr; *ptr; ++ptr) { if (*ptr == '"') { ++ptr; break; } } } else if (*ptr == '\'') { for (++ptr; *ptr; ++ptr) { if (*ptr == '\'') { ++ptr; break; } } } else { for (; *ptr; ++ptr) { if (*ptr == ' ' || (*ptr == '\"') || (*ptr == '\'')) { break; } } } ret.second = int(ptr - strStart); return ret; } ACState prepACState(const std::string line, size_t insertPos, bool unixStyle) { if (insertPos == std::string::npos) { insertPos = line.size(); } // find where we're up to in the line and what syntax options are available at that point ACState acs; acs.unixStyle = unixStyle; std::pair linepos{ 0,0 }; bool last; do { linepos = identifyNextWord(line, linepos.second); std::string word = line.substr(static_cast(linepos.first), static_cast(linepos.second - linepos.first)); last = linepos.first == linepos.second; bool cursorInWord = linepos.first <= int(insertPos) && int(insertPos) <= linepos.second; if (cursorInWord) { last = true; word.erase(insertPos - static_cast(linepos.first), std::string::npos); linepos.second = int(insertPos); // keep everything to the right of the cursor } if (!acs.words.empty() && linepos.first == acs.wordPos.back().second) { // continuation, so combine into one word. eg "c:\prog files"\nextthing ACState::quoting q(word); acs.words.back().s += word; acs.wordPos.back().second = linepos.second; if (!acs.words.back().q.quoted) { acs.words.back().q = q; } } else { acs.wordPos.push_back(linepos); acs.words.emplace_back(word); } } while (!last); return acs; } CompletionState autoComplete(const std::string line, size_t insertPos, ACN syntax, bool unixStyle) { ACState acs = prepACState(line, insertPos, unixStyle); acs.i = 0; syntax->addCompletions(acs); CompletionState cs; cs.line = line; cs.wordPos = acs.wordPos.back(); cs.originalWord = acs.words.back(); cs.completions = acs.completions; cs.unixStyle = acs.unixStyle; cs.tidyCompletions(); return cs; } bool autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput, bool reportNoMatch) { ACState acs = prepACState(line, insertPos, unixStyle); while (!acs.words.empty() && acs.words.back().s.empty() && !acs.words.back().q.quoted) { acs.words.pop_back(); } if (!acs.words.empty()) { if (auto e = dynamic_cast(syntax.get())) { std::vector v; Either::ExecFn f; std::vector firstWordMatches; std::ostringstream conout; for (unsigned i = 0; i < e->eithers.size(); ++i) { acs.i = 0; if (e->eithers[i]->match(acs) && acs.i == acs.words.size()) { v.push_back(e->eithers[i]); f = e->execFuncs[i]; } acs.i = 0; if (auto seq = dynamic_cast(e->eithers[i].get())) { if (seq->current->match(acs)) { firstWordMatches.push_back(e->eithers[i]); } } } if (v.empty()) { if (!reportNoMatch) { return false; } conout << "Invalid syntax"; if (firstWordMatches.empty()) { conout << ", type 'help' for command syntax" << std::endl; } else { for (auto fwm : firstWordMatches) { conout << std::endl << e->describePrefix << *fwm << endl; } } } else if (v.size() == 1) { acs.i = 0; if (f) { f(acs); } else if (!reportNoMatch) { return false; } else { conout << "Operation not implemented yet" << std::endl; } } else { conout << "Ambiguous syntax" << std::endl; for (auto a : v) { conout << e->describePrefix << *a << std::endl; } } consoleOutput = conout.str(); } } return true; } unsigned utf8GlyphCount(const string &str) { int c, i, ix, q; for (q = 0, i = 0, ix = int(str.length()); i < ix; i++, q++) { c = (unsigned char)str[static_cast(i)]; if (c >= 0 && c <= 127) i += 0; else if ((c & 0xE0) == 0xC0) i += 1; else if ((c & 0xF0) == 0xE0) { i += 2; q++; } //these glyphs may occupy 2 characters! Problem: not always. Let's assume the worst else if ((c & 0xF8) == 0xF0) i += 3; else q++; // invalid utf8 - leave lots of space } return static_cast(q); } const string& CompletionState::unixColumnEntry(int row, int col, int rows) { static string emptyString; size_t index = unixListCount + static_cast(col * rows + row); return index < completions.size() ? completions[index].s : emptyString; } unsigned CompletionState::calcUnixColumnWidthInGlyphs(int col, int rows) { unsigned width = 0; for (int r = 0; r < rows; ++r) { width = std::max(width, utf8GlyphCount(unixColumnEntry(r, col, rows))); } return width; } void CompletionState::tidyCompletions() { // sort and eliminate duplicates std::sort(completions.begin(), completions.end(), [](const ACState::Completion& c1, const ACState::Completion& c2) { return c1.s < c2.s; }); completions.erase(std::unique(completions.begin(), completions.end(), [](const ACState::Completion& c1, const ACState::Completion& c2) { return c1.s == c2.s; }), completions.end()); } void applyCompletion(CompletionState& s, bool forwards, unsigned consoleWidth, CompletionTextOut& textOut) { if (!s.completions.empty()) { if (!s.unixStyle) { int index = ((!forwards && s.lastAppliedIndex == -1) ? -1 : (s.lastAppliedIndex + (forwards ? 1 : -1))) + (int)s.completions.size(); index = static_cast(static_cast(index) % s.completions.size()); // restore quotes if it had them already auto& c = s.completions[static_cast(index)]; std::string w = c.s; s.originalWord.q.applyQuotes(w); w += (s.completions.size() == 1 && !c.couldExtend) ? " " : ""; s.line.replace(static_cast(s.wordPos.first), static_cast(s.wordPos.second - s.wordPos.first), w); s.wordPos.second = int(w.size() + static_cast(s.wordPos.first)); s.lastAppliedIndex = index; if (s.completions.size()==1) { s.active = false; } } else { if (!s.firstPressDone) { // add characters that match all possibilities std::string exactChars = s.completions[0].s; // keep the uppercase/lowercase as specified by the user (for case sensitive they will match anyway) size_t commonLen = std::min(exactChars.size(), s.originalWord.s.size()); exactChars.replace(0, commonLen, s.originalWord.s.substr(0, commonLen)); for (auto& c : s.completions) { for (unsigned i = 0; i < exactChars.size(); ++i) { if (i == c.s.size() || (c.caseInsensitive ? !icmp(exactChars[i], c.s[i]) : exactChars[i] != c.s[i])) { exactChars.erase(i, std::string::npos); break; } } } s.originalWord.q.applyQuotes(exactChars); exactChars += (s.completions.size() == 1 && !s.completions[0].couldExtend) ? " " : ""; s.line.replace(static_cast(s.wordPos.first), static_cast(s.wordPos.second - s.wordPos.first), exactChars); s.wordPos.second = int(exactChars.size() + static_cast(s.wordPos.first)); s.firstPressDone = true; s.unixListCount = 0; if (s.completions.size() == 1) { s.active = false; } } else { // show remaining possibilities. Proper columns, unix order, alphabetical vertically then left-to-right unsigned rows = 1, cols = 0, sumwidth = 0; for (unsigned c = 0; ;) { unsigned width = s.calcUnixColumnWidthInGlyphs(static_cast(c), static_cast(rows)); if (width == 0) { cols = c; break; } else { sumwidth += width + 3; if (3 + sumwidth > consoleWidth) { if (rows == 5) { cols = c; break; } else { ++rows; c = 0; sumwidth = 0; } } else if (s.unixListCount + rows * (c + 1) >= s.completions.size()) { cols = c + 1; break; } else { ++c; } } } rows = std::max(rows, 1); cols = std::max(cols, 1); for (unsigned c = 0; c < cols; ++c) { textOut.columnwidths.push_back(static_cast( s.calcUnixColumnWidthInGlyphs(static_cast(c), static_cast(rows)) + (c == 0 ? 6 : 3))); } for (unsigned r = 0; r < rows; ++r) { textOut.stringgrid.push_back(vector()); for (unsigned c = 0; c < cols; ++c) { const string& entry = s.unixColumnEntry(static_cast(r), static_cast(c), static_cast(rows)); if (!entry.empty()) { textOut.stringgrid[r].push_back((c == 0 ? " " : "") + entry); } } } s.unixListCount += rows * cols; if (s.unixListCount < s.completions.size()) { textOut.stringgrid.push_back(vector(1, "")); } else { s.unixListCount = 0; s.firstPressDone = false; } } } } } ACN text(const std::string s) { return std::make_shared(s, false); } ACN param(const std::string s) { return std::make_shared(s, true); } ACN exportedLink(bool file, bool folder) { return std::make_shared(file, folder); } ACN flag(const std::string s) { return std::make_shared(s); } ACN opt(ACN n) { return std::make_shared(n); } ACN repeat(ACN n) { return std::make_shared(n); } ACN wholenumber(const std::string& description, size_t defaultValue) { return std::make_shared(description, defaultValue); } ACN wholenumber(size_t defaultValue) { return wholenumber("N", defaultValue); } ACN localFSPath(const std::string descriptionPrefix) { return ACN(new LocalFS(true, true, descriptionPrefix)); } ACN localFSFile(const std::string descriptionPrefix) { return ACN(new LocalFS(true, false, descriptionPrefix)); } ACN localFSFolder(const std::string descriptionPrefix) { return ACN(new LocalFS(false, true, descriptionPrefix)); } ACN remoteFSPath(MegaClient* client, ::mega::NodeHandle* cwd, const std::string descriptionPrefix) { return ACN(new MegaFS(true, true, client, cwd, descriptionPrefix)); } ACN remoteFSFile(MegaClient* client, ::mega::NodeHandle* cwd, const std::string descriptionPrefix) { return ACN(new MegaFS(true, false, client, cwd, descriptionPrefix)); } ACN remoteFSFolder(MegaClient* client, ::mega::NodeHandle* cwd, const std::string descriptionPrefix) { return ACN(new MegaFS(false, true, client, cwd, descriptionPrefix)); } ACN contactEmail(MegaClient* client) { return ACN(new MegaContactEmail(client)); } }}; //namespaces sdk-10.11.0/src/backofftimer.cpp000066400000000000000000000105711516266226600164020ustar00rootroot00000000000000/** * @file backofftimer.cpp * @brief Generic timer facility with exponential backoff * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/waiter.h" #include "mega/backofftimer.h" #include "mega/logging.h" namespace mega { // timer with capped exponential backoff BackoffTimer::BackoffTimer(PrnGen &rng) : rng(rng) { reset(); } void BackoffTimer::reset() { next = 0; delta = 1; base = 1; } void BackoffTimer::backoff() { next = Waiter::ds + delta; base <<= 1; if (base > 6000) { base = 6000; } delta = base + static_cast((static_cast(base) / 2.0) * (rng.genuint32(RAND_MAX) / (static_cast(RAND_MAX)))); } void BackoffTimer::backoff(dstime newdelta) { next = (newdelta == NEVER) ? NEVER : (Waiter::ds + newdelta); delta = newdelta; base = newdelta; } bool BackoffTimer::armed() const { return next <= 1 || Waiter::ds >= next; } bool BackoffTimer::arm() { if (next == NEVER || (next + delta) > Waiter::ds) { next = Waiter::ds; delta = 1; base = 1; return true; } return false; } void BackoffTimer::set(dstime newds) { if (newds < next) { next = newds; } } dstime BackoffTimer::retryin() const { if (armed()) { return 0; } return next - Waiter::ds; } dstime BackoffTimer::backoffdelta() { return delta; } dstime BackoffTimer::nextset() const { return next; } // event in the future: potentially updates waituntil // event in the past: zeros out waituntil and clears event void BackoffTimer::update(dstime* waituntil) { if (next) { assert(next != 1); if (next == 1) { LOG_warn << "Possible wrong management of timer"; } if (next <= Waiter::ds) { *waituntil = (next == 1) ? Waiter::ds + 1 : 0; next = 1; } else if (next < *waituntil) { *waituntil = next; } } } TimerWithBackoff::TimerWithBackoff(PrnGen &rng, int tag) : BackoffTimer(rng) { this->tag = tag; } void BackoffTimerGroupTracker::update(dstime* waituntil, bool transfers) { // This function performs a similar action as calling BackoffTimer::update for all the timers in the group, // which is to say, the `waituntil` parameter will be updated with the soonest time that we would need to // wake up from any of the timers in this group, should any of them be in a back-off state. // There are also some side-effects specfic to transfers which are preserved from the old system. vector v; v.reserve(timeouts.size()); if (transfers) { for (auto t : timeouts) { // put the ones to work on in a vector, as working on them changes their position in the map if (t.first <= Waiter::ds) { v.push_back(t.second); } else { break; } } for (auto t : v) { t->update(waituntil); if (t->armed()) { // fire the timer only once but keeping it armed t->set(0); LOG_debug << "Disabling armed transfer backoff"; } } } else { // put the ones to work on in a vector, as working on them changes their position in the map for (auto t : timeouts) { if (t.second->armed()) { v.push_back(t.second); } if (t.first > Waiter::ds) { break; } } for (auto t : v) { // update may set next=1 so we can't just call the first one. t->update(waituntil); } } } } // namespace sdk-10.11.0/src/base64.cpp000066400000000000000000000224461516266226600150360ustar00rootroot00000000000000/** * @file base64.cpp * @brief modified base64 encoding/decoding * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/base64.h" #include "mega/utils.h" namespace mega { // modified base64 conversion (no trailing '=' and '-_' instead of '+/') unsigned char Base64::to64(byte c) { c &= 63; if (c < 26) { return static_cast(c + 'A'); } if (c < 52) { return static_cast(c - 26 + 'a'); } if (c < 62) { return static_cast(c - 52 + '0'); } if (c == 62) { return '-'; } return '_'; } unsigned char Base64::from64(byte c) { if ((c >= 'A') && (c <= 'Z')) { return static_cast(c - 'A'); } if ((c >= 'a') && (c <= 'z')) { return static_cast(c - 'a' + 26); } if ((c >= '0') && (c <= '9')) { return static_cast(c - '0' + 52); } if (c == '-' || c == '+') { return static_cast(62); } if (c == '_' || c == '/') { return static_cast(63); } return static_cast(255); } int Base64::atob(const string &in, string &out) { out.resize(in.size() * 3 / 4 + 3); out.resize(static_cast(Base64::atob(in.data(), (byte*)out.data(), (int)out.size()))); return (int)out.size(); } std::string Base64::atob(const std::string &in) { string out; out.resize(in.size() * 3 / 4 + 3); out.resize(static_cast(Base64::atob(in.data(), (byte*)out.data(), (int)out.size()))); return out; } int Base64::atob(const char* a, byte* b, int blen) { byte c[4]={}; int i; int p = 0; for (;;) { for (i = 0; i < 4; i++) { if ((c[i] = from64(static_cast(*a++))) == 255) { break; } } if ((p >= blen) || !i) { return p; } b[p++] = static_cast((c[0] << 2) | ((c[1] & 0x30) >> 4)); if ((p >= blen) || (i < 3)) { return p; } b[p++] = static_cast((c[1] << 4) | ((c[2] & 0x3c) >> 2)); if ((p >= blen) || (i < 4)) { return p; } b[p++] = static_cast((c[2] << 6) | c[3]); } } void Base64::itoa(int64_t val, string *result) { byte c; int64_t rest; if (!result || val < 0) { return; } if (!val) { *result = "A"; return; } result->clear(); while (val) { rest = val % 64; val /= 64; c = to64(byte(rest)); result->insert(result->begin(), (char) c); } } int64_t Base64::atoi(string *val) { if (!val) { return -1; } size_t len = val->size(); if (len == 0) { return -1; } size_t pos = 0; int64_t res = 0; int valid = 0; while (pos < len) { byte b = from64(static_cast(val->at(pos))); if (b == 255) { pos++; continue; } valid++; res *= 64; res += b; pos++; } if (!valid || res < 0) { return -1; } return res; } size_t Base64::btoa(const string& in, string& out) { out.resize(in.size() * 4 / 3 + 4); out.resize(Base64::btoa((const byte*)in.data(), in.size(), (char*)out.data())); return out.size(); } std::string Base64::btoa(const string &in) { string out; out.resize(in.size() * 4 / 3 + 4); out.resize(Base64::btoa((const byte*)in.data(), in.size(), (char*)out.data())); return out; } size_t Base64::btoa(const byte* b, size_t blen, char* a) { size_t p = 0; for (;;) { if (blen == 0) { break; } a[p++] = static_cast(to64(static_cast(*b >> 2))); a[p++] = static_cast(to64(static_cast((*b << 4) | (((blen > 1) ? b[1] : 0) >> 4)))); if (blen < 2) { break; } a[p++] = static_cast(to64(static_cast(b[1] << 2 | (((blen > 2) ? b[2] : 0) >> 6)))); if (blen < 3) { break; } a[p++] = static_cast(to64(b[2])); blen -= 3; b += 3; } a[p] = 0; return p; } void Base64::toStandard(string& b64str) { std::replace(b64str.begin(), b64str.end(), '-', '+'); std::replace(b64str.begin(), b64str.end(), '_', '/'); // Calculate how many padding '=' chars are needed size_t padChars = 4 - (b64str.size() % 4); if (padChars < 4) { b64str.append(padChars, '='); } assert(b64str.size() % 4 == 0); } byte Base32::to32(byte c) { c &= 31; if (c < 26) { return static_cast(c + 'a'); } return static_cast(c - 26 + '2'); } byte Base32::from32(byte c) { if ((c >= 'a') && (c <= 'z')) { return static_cast(c - 'a'); } if ((c >= '2') && (c <= '9')) { return static_cast(c - '2' + 26); } return static_cast(255); } int Base32::btoa(const byte *b, int blen, char *a) { int p = 0; for (;;) { if (blen <= 0) { break; } a[p++] = static_cast(to32(static_cast(*b >> 3))); a[p++] = static_cast(to32(static_cast((*b << 2) | (((blen > 1) ? b[1] : 0) >> 6)))); if (blen < 2) { break; } a[p++] = static_cast(to32(static_cast(b[1] >> 1))); a[p++] = static_cast(to32(static_cast(b[1] << 4 | (((blen > 2) ? b[2] : 0) >> 4)))); if (blen < 3) { break; } a[p++] = static_cast( to32(static_cast((b[2] << 1) | (((blen > 3) ? b[3] : 0) >> 7)))); if (blen < 4) { break; } a[p++] = static_cast(to32(static_cast(b[3] >> 2))); a[p++] = static_cast(to32(static_cast(b[3] << 3 | (((blen > 4) ? b[4] : 0) >> 5)))); if (blen < 5) { break; } a[p++] = static_cast(to32(b[4])); blen -= 5; b += 5; } a[p] = 0; return p; } int Base32::atob(const char *a, byte *b, int blen) { byte c[8]={}; int i; int p = 0; for (;;) { for (i = 0; i < 8; i++) { if ((c[i] = from32(static_cast(*a++))) == 255) { break; } } if ((p >= blen) || !i) { return p; } b[p++] = static_cast((c[0] << 3) | ((c[1] & 0x1C) >> 2)); if ((p >= blen) || (i < 4)) { return p; } b[p++] = static_cast((c[1] << 6) | (c[2] << 1) | ((c[3] & 0x10) >> 4)); if ((p >= blen) || (i < 5)) { return p; } b[p++] = static_cast((c[3] << 4) | ((c[4] & 0x1E) >> 1)); if ((p >= blen) || (i < 7)) { return p; } b[p++] = static_cast((c[4] << 7) | (c[5] << 2) | ((c[6] & 0x18) >> 3)); if ((p >= blen) || (i < 8)) { return p; } b[p++] = static_cast((c[6] << 5) | c[7]); } } bool URLCodec::issafe(char c) { if (ishexdigit(c) || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '.' || c == '_' || c == '~') { return true; } return false; } bool URLCodec::ishexdigit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } void URLCodec::escape(string *plain, string *escaped) { if (!escaped || !plain) { return; } escaped->clear(); for (const char& c: *plain) { if (issafe(c)) { escaped->push_back(c); } else { char buf[4]; snprintf(buf, sizeof(buf), "%%%02x", (unsigned char)c); // cast it to avoid being sign-extended escaped->append(buf); } } } void URLCodec::unescape(string *escaped, string *plain) { if (!(escaped && plain)) { return; } plain->clear(); plain->reserve(escaped->size()); const char* m = escaped->c_str(); const char* n = m + escaped->size(); while (m < n) { if (*m == '%' && n - m > 2) { if (ishexdigit(m[1]) && ishexdigit(m[2])) { auto c = hexval(m[1]) << 4u | hexval(m[2]); plain->push_back(static_cast(c)); m += 3; continue; } } plain->push_back(*m++); } } } // namespace sdk-10.11.0/src/canceller.cpp000066400000000000000000000012441516266226600156730ustar00rootroot00000000000000#include "mega/canceller.h" namespace mega { namespace { std::atomic g_cancel_epoch{0}; } cancel_epoch_t cancel_epoch_snapshot() noexcept { return g_cancel_epoch.load(std::memory_order_relaxed); } void cancel_epoch_bump() noexcept { g_cancel_epoch.fetch_add(1, std::memory_order_relaxed); } bool cancel_epoch_triggered(const cancel_epoch_t snapshot) noexcept { return g_cancel_epoch.load(std::memory_order_relaxed) != snapshot; } ScopedCanceller::ScopedCanceller() noexcept { m_snapshot = cancel_epoch_snapshot(); } bool ScopedCanceller::triggered() const noexcept { return cancel_epoch_triggered(m_snapshot); } } // namespace mega sdk-10.11.0/src/command.cpp000066400000000000000000000236141516266226600153660ustar00rootroot00000000000000/** * @file command.cpp * @brief Request command component * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/command.h" #include "mega/base64.h" #include "mega/megaclient.h" #include "mega/tlv.h" namespace mega { Command::Command() { canceled = false; result = API_OK; client = NULL; tag = 0; batchSeparately = false; } Command::~Command() { } void Command::cancel() { canceled = true; } void Command::addToNodePendingCommands(Node* node) { node->mPendingChanges.push_back(this); } void Command::removeFromNodePendingCommands(NodeHandle h) { if (auto node = client->nodeByHandle(h)) { node->mPendingChanges.erase(this); } } bool Command::isLockless() const { return mLockless; } // returns completed command JSON string const char* Command::getJSON(MegaClient*) { return jsonWriter.getstring().c_str(); } //return true when the response is an error, false otherwise (in that case it doesn't consume JSON chars) bool Command::checkError(Error& errorDetails, JSON& json) { error e; bool errorDetected = false; if (json.isNumericError(e)) { // isNumericError already moved the pointer past the integer (name could imply this?) errorDetails.setErrorCode(e); errorDetected = true; } else { const char* ptr = json.pos; if (*ptr == ',') { ptr++; } if (strncmp(ptr, "{\"err\":", 7) == 0) { bool exit = false; json.enterobject(); while (!exit) { switch (json.getnameid()) { case makeNameid("err"): errorDetails.setErrorCode(static_cast(json.getint())); errorDetected = true; break; case makeNameid("u"): errorDetails.setUserStatus(json.getint()); break; case makeNameid("l"): errorDetails.setLinkStatus(json.getint()); break; case EOO: exit = true; break; default: json.storeobject(); break; } } json.leaveobject(); } } // generic handling of errors for all commands below if (errorDetected && errorDetails == API_EPAYWALL) { client->activateoverquota(0, true); } if (errorDetected && errorDetails == API_EBUSINESSPASTDUE) { client->setBusinessStatus(BIZ_STATUS_EXPIRED); } return errorDetected; } #ifdef ENABLE_CHAT void Command::createSchedMeetingJson(const ScheduledMeeting* schedMeeting) { if (!schedMeeting) { assert(false); return; } // note: we need to B64 encode the following params: timezone(tz), title(t), description(d), attributes(at) handle chatid = schedMeeting->chatid(); handle schedId = schedMeeting->schedId(); handle parentSchedId = schedMeeting->parentSchedId(); // chatid is only required when we create a scheduled meeting for an existing chat if (!ISUNDEF(chatid)) { arg("cid", (byte*)& chatid, MegaClient::CHATHANDLE); } // required params arg("tz", Base64::btoa(schedMeeting->timezone()).c_str()); arg("s", schedMeeting->startDateTime()); arg("e", schedMeeting->endDateTime()); arg("t", Base64::btoa(schedMeeting->title()).c_str()); arg("d", Base64::btoa(schedMeeting->description()).c_str()); // optional params if (!ISUNDEF(schedId)) { arg("id", (byte*)&schedId, MegaClient::CHATHANDLE); } // scheduled meeting ID if (!ISUNDEF(parentSchedId)) { arg("p", (byte*)&parentSchedId, MegaClient::CHATHANDLE); } // parent scheduled meeting ID if (schedMeeting->cancelled() >= 0) { arg("c", schedMeeting->cancelled()); } if (!schedMeeting->attributes().empty()) { arg("at", Base64::btoa(schedMeeting->attributes()).c_str()); } if (schedMeeting->flags() && !schedMeeting->flags()->isEmpty()) { arg("f", static_cast(schedMeeting->flags()->getNumericValue())); } if (MegaClient::isValidMegaTimeStamp(schedMeeting->overrides())) { arg("o", schedMeeting->overrides()); } // rules are not mandatory to create a scheduled meeting, but if provided, frequency is required if (schedMeeting->rules()) { const ScheduledRules* rules = schedMeeting->rules(); beginobject("r"); if (rules->isValidFreq(rules->freq())) { arg("f", rules->freqToString()); // required } if (rules->isValidInterval(rules->interval())) { arg("i", rules->interval()); } if (MegaClient::isValidMegaTimeStamp(rules->until())) { arg("u", rules->until()); } if (rules->byWeekDay() && !rules->byWeekDay()->empty()) { beginarray("wd"); for (auto i: *rules->byWeekDay()) { element(static_cast(i)); } endarray(); } if (rules->byMonthDay() && !rules->byMonthDay()->empty()) { beginarray("md"); for (auto i: *rules->byMonthDay()) { element(static_cast(i)); } endarray(); } if (rules->byMonthWeekDay() && !rules->byMonthWeekDay()->empty()) { beginarray("mwd"); for (auto i: *rules->byMonthWeekDay()) { beginarray(); element(static_cast(i.first)); element(static_cast(i.second)); endarray(); } endarray(); } endobject(); } } #endif // cache urls and ips given in response to avoid further waiting for dns resolution void Command::cacheresolvedurls(const std::string& command, const std::vector& urls, const std::vector& ips) { // Try and update the DNS cache. auto updated = client->httpio->cacheresolvedurls(urls, ips); // Not enough IPs for each URI. if (updated < 0) { LOG_err << "Unpaired IPs received for URLs in `" << command << "`. URLs: " << urls.size() << " IPs: " << ips.size(); return; } // Each URI was associated with two valid IP addresses. if (!updated) return; // One or more URIs were associated with an invalid IP address. std::ostringstream ostream; ostream << "Detected one or more invalid IPs while processing `" << command << "` command"; // Report that we've detected one or more invalid IP addresses. client->sendevent(800034, ostream.str().c_str()); } // Store ips from response in the vector passed bool Command::loadIpsFromJson(std::vector& ips, JSON& json) { if (json.enterarray()) // for each URL, there will be 2 IPs (IPv4 first, IPv6 second) { for (;;) { std::string ti; if (!json.storeobject(&ti)) { break; } ips.emplace_back(std::move(ti)); } json.leavearray(); return true; } return false; } // add opcode void Command::cmd(const char* cmd) { jsonWriter.cmd(cmd); commandStr = cmd; } void Command::notself(MegaClient* clientToIgnoreActionPackets) { jsonWriter.notself(clientToIgnoreActionPackets); } // add comma separator unless first element void Command::addcomma() { jsonWriter.addcomma(); } // add command argument name:value pair (FIXME: add proper JSON escaping) void Command::arg(const char* name, const char* value, int quotes) { jsonWriter.arg(name, value, quotes); } // binary data void Command::arg(const char* name, const byte* value, size_t len) { jsonWriter.arg(name, value, len); } void Command::arg(const char* name, NodeHandle h) { jsonWriter.arg(name, h); } // 64-bit signed integer void Command::arg(const char* name, m_off_t n) { jsonWriter.arg(name, n); } // raw JSON data void Command::appendraw(const char* s) { jsonWriter.appendraw(s); } // raw JSON data with length specifier void Command::appendraw(const char* s, int len) { jsonWriter.appendraw(s, len); } // begin array void Command::beginarray() { jsonWriter.beginarray(); } // begin array member void Command::beginarray(const char* name) { jsonWriter.beginarray(name); } // close array void Command::endarray() { jsonWriter.endarray(); } // begin JSON object void Command::beginobject() { jsonWriter.beginobject(); } void Command::beginobject(const char *name) { jsonWriter.beginobject(name); } // end JSON object void Command::endobject() { jsonWriter.endobject(); } // add integer void Command::element(int n) { jsonWriter.element(n); } // add handle (with size specifier) void Command::element(handle h, size_t len) { jsonWriter.element(h, len); } // add binary data void Command::element(const byte* data, size_t len) { jsonWriter.element(data, len); } void Command::element(const char *buf) { jsonWriter.element(buf); } // open object void Command::openobject() { jsonWriter.openobject(); } // close object void Command::closeobject() { jsonWriter.closeobject(); } } // namespace sdk-10.11.0/src/commands.cpp000066400000000000000000014547441516266226600155660ustar00rootroot00000000000000/** * @file commands.cpp * @brief Implementation of various commands * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/base64.h" #include "mega/command.h" #include "mega/fileattributefetch.h" #include "mega/heartbeats.h" #include "mega/json.h" #include "mega/mediafileattribute.h" #include "mega/megaapp.h" #include "mega/testhooks.h" #include "mega/tlv.h" #include "mega/transfer.h" #include "mega/transferslot.h" #include "mega/types.h" #include "mega/user.h" #include "mega/user_attribute.h" #include "mega/utils.h" #include #include namespace mega { CommandPutFA::CommandPutFA(NodeOrUploadHandle cth, fatype /*ctype*/, bool forceSSL, int ctag, size_t size, bool getIP, CommandPutFA::Cb&& completion): mCompletion(std::move(completion)) { cmd("ufa"); arg("s", static_cast(size)); if (cth.isNodeHandle()) { arg("h", cth.nodeHandle()); } if (forceSSL) { arg("ssl", 2); } if (getIP) { arg("v", 3); } tag = ctag; } HttpReqFA::HttpReqFA(NodeOrUploadHandle cth, fatype ctype, int ctag, std::unique_ptr cdata, bool getIP, MegaClient* client): data(std::move(cdata)) { tag = ctag; progressreported = 0; th = cth; type = ctype; binary = true; getURLForFACmd = [this, cth, ctype, ctag, getIP, client]() { std::weak_ptr weakSelf(shared_from_this()); return new CommandPutFA( cth, ctype, true, ctag, data->size(), getIP, [weakSelf, client](Error e, const std::string& url, const vector& /*ips*/) { auto self = weakSelf.lock(); if (!self) return; if (!self->data || self->data->empty()) { e = API_EARGS; LOG_err << "Data object is " << (!self->data ? "nullptr" : "empty"); } if (e == API_OK) { LOG_debug << "Sending file attribute data"; self->progressreported = 0; self->HttpReq::type = REQ_BINARY; self->posturl = url; // post sets the status for http processing state machine self->post(client, self->data->data(), static_cast(self->data->size())); } else { // jumping to REQ_SUCCESS, but with no handle in `in`, means we failed overall (and don't retry) self->status = REQ_SUCCESS; client->app->putfa_result(self->th.nodeHandle().as8byte(), self->type, e); } }); }; } bool CommandPutFA::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { assert(!r.wasError(API_EAGAIN)); // these would not occur here, we would retry after backoff assert(!r.wasError(API_ERATELIMIT)); if (r.wasError(API_EACCESS)) { // create a custom attribute indicating thumbnail can't be restored from this account shared_ptr n = client->nodeByHandle(th.nodeHandle()); char me64[12]; Base64::btoa((const byte*)&client->me, MegaClient::USERHANDLE, me64); if (n && client->checkaccess(n.get(), FULL) && (n->attrs.map.find('f') == n->attrs.map.end() || n->attrs.map['f'] != me64) ) { LOG_debug << "Restoration of file attributes is not allowed for current user (" << me64 << ")."; // 'canChangeVault' is false here because restoration of file attributes is triggered by // downloads, so it cannot be triggered by a Backup operation bool canChangeVault = false; client->setattr(n, attr_map('f', me64), nullptr, canChangeVault); } } mCompletion(r.errorOrOK(), {}, {}); return true; } else { const char* p = NULL; std::vector ips; for (;;) { switch (json.getnameid()) { case makeNameid("p"): p = json.getvalue(); break; case makeNameid("ip"): loadIpsFromJson(ips, json); break; case EOO: if (!p) { mCompletion(API_EINTERNAL, {}, {}); } else { string posturl; JSON::copystring(&posturl, p); // cache resolved URLs if received std::vector urls(1, posturl); std::vector ipsCopy = ips; cacheresolvedurls("ufa", urls, std::move(ips)); mCompletion(API_OK, posturl, ipsCopy); } return true; default: if (!json.storeobject()) { mCompletion(API_EINTERNAL, {}, {}); return false; } } } } } m_off_t HttpReqFA::transferred(MegaClient *client) { if (httpiohandle) { client->httpio->postpos(httpiohandle); return true; } return 0; } CommandGetFA::CommandGetFA(MegaClient* /*client*/, int p, handle fahref) { part = p; cmd("ufa"); arg("fah", (byte*)&fahref, sizeof fahref); arg("ssl", 2); arg("r", 1); arg("v", 3); } bool CommandGetFA::procresult(Result r, JSON& json) { fafc_map::iterator it = client->fafcs.find(part); if (r.wasErrorOrOK()) { if (it != client->fafcs.end()) { faf_map::iterator fafsit; for (fafsit = it->second->fafs[0].begin(); fafsit != it->second->fafs[0].end(); ) { // move from fresh to pending it->second->fafs[1][fafsit->first] = fafsit->second; it->second->fafs[0].erase(fafsit++); } it->second->e = r.errorOrOK(); it->second->req.status = REQ_FAILURE; } return true; } const char* p = NULL; std::vector ips; for (;;) { switch (json.getnameid()) { case makeNameid("p"): p = json.getvalue(); break; case makeNameid("ip"): loadIpsFromJson(ips, json); break; case EOO: if (it != client->fafcs.end()) { if (p) { JSON::copystring(&it->second->posturl, p); it->second->urltime = Waiter::ds; cacheresolvedurls("ufa", {it->second->posturl}, std::move(ips)); it->second->dispatch(); } else { faf_map::iterator fafsit; for (fafsit = it->second->fafs[0].begin(); fafsit != it->second->fafs[0].end(); ) { // move from fresh to pending it->second->fafs[1][fafsit->first] = fafsit->second; it->second->fafs[0].erase(fafsit++); } it->second->e = API_EINTERNAL; it->second->req.status = REQ_FAILURE; } } return true; default: if (!json.storeobject()) { faf_map::iterator fafsit; for (fafsit = it->second->fafs[0].begin(); fafsit != it->second->fafs[0].end(); ) { // move from fresh to pending it->second->fafs[1][fafsit->first] = fafsit->second; it->second->fafs[0].erase(fafsit++); } it->second->e = API_EINTERNAL; it->second->req.status = REQ_FAILURE; return false; } } } } CommandAttachFA::CommandAttachFA(MegaClient*, handle nh, fatype t, handle ah, int ctag) { mSeqtagArray = true; cmd("pfa"); arg("n", (byte*)&nh, MegaClient::NODEHANDLE); char buf[64]; snprintf(buf, sizeof(buf), "%u*", t); Base64::btoa((byte*)&ah, sizeof(ah), strchr(buf + 2, 0)); arg("fa", buf); h = nh; type = t; tag = ctag; } CommandAttachFA::CommandAttachFA(MegaClient*, handle nh, fatype t, const std::string& encryptedAttributes, int ctag) { mSeqtagArray = true; cmd("pfa"); arg("n", (byte*)&nh, MegaClient::NODEHANDLE); arg("fa", encryptedAttributes.c_str()); h = nh; type = t; tag = ctag; } bool CommandAttachFA::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->putfa_result(h, type, r.errorOrOK()); return true; } else { string fa; if (json.storeobject(&fa)) { #ifdef DEBUG shared_ptr n = client->nodebyhandle(h); assert(!n || n->fileattrstring == fa); #endif client->app->putfa_result(h, type, API_OK); return true; } } client->app->putfa_result(h, type, API_EINTERNAL); return false; } // request upload target URL CommandPutFile::CommandPutFile(MegaClient* client, TransferSlot* ctslot) { tslot = ctslot; cmd("u"); arg("ssl", 2); arg("v", 3); arg("s", tslot->fa->size); // send minimum set of different tree's roots for API to check overquota set targetRoots; bool begun = false; for (auto &file : tslot->transfer->files) { if (!file->h.isUndef()) { shared_ptr node = client->nodeByHandle(file->h); if (node) { assert(node->type != FILENODE); assert(!node->parent || node->parent->type != FILENODE); handle rootnode = client->getrootnode(node)->nodehandle; if (targetRoots.find(rootnode) != targetRoots.end()) { continue; } targetRoots.insert(rootnode); } if (!begun) { beginarray("t"); begun = true; } element((byte*)&file->h, MegaClient::NODEHANDLE); } } if (begun) { endarray(); } else { // Target user goes alone, not inside an array. Note: we are skipping this if a)more than two b)the array had been created for node handles for (auto &file : tslot->transfer->files) { if (file->h.isUndef() && file->targetuser.size()) { arg("t", file->targetuser.c_str()); break; } } } } void CommandPutFile::cancel() { Command::cancel(); tslot = NULL; } // set up file transfer with returned target URL bool CommandPutFile::procresult(Result r, JSON& json) { if (tslot) { tslot->pendingcmd = NULL; } else { canceled = true; } if (r.wasErrorOrOK()) { if (!canceled) { tslot->transfer->failed(r.errorOrOK(), *client->mTctableRequestCommitter); } return true; } std::vector tempurls; std::vector tempips; for (;;) { switch (json.getnameid()) { case makeNameid("p"): tempurls.push_back(""); json.storeobject(canceled ? NULL : &tempurls.back()); break; case makeNameid("ip"): loadIpsFromJson(tempips, json); break; case EOO: if (canceled) return true; if (tempurls.size() == 1) { cacheresolvedurls("u", tempurls, std::move(tempips)); tslot->transfer->tempurls = tempurls; tslot->transferbuf.setIsRaid(tslot->transfer, tempurls, tslot->transfer->pos, tslot->maxRequestSize); tslot->starttime = tslot->lastdata = client->waiter->ds; tslot->progress(); } else { tslot->transfer->failed(API_EINTERNAL, *client->mTctableRequestCommitter); } return true; default: if (!json.storeobject()) { if (!canceled) { tslot->transfer->failed(API_EINTERNAL, *client->mTctableRequestCommitter); } return false; } } } } // request upload target URL CommandGetPutUrl::CommandGetPutUrl(m_off_t size, bool forceSSL, bool getIP, CommandGetPutUrl::Cb completion): mCompletion(completion) { cmd("u"); if (forceSSL) { arg("ssl", 2); } if (getIP) { arg("v", 3); } else { arg("v", 2); } arg("s", size); } // set up file transfer with returned target URL bool CommandGetPutUrl::procresult(Result r, JSON& json) { string url; std::vector ips; if (r.wasErrorOrOK()) { if (!canceled) { mCompletion(r.errorOrOK(), url, ips); } return true; } for (;;) { switch (json.getnameid()) { case makeNameid("p"): json.storeobject(canceled ? nullptr : &url); break; case makeNameid("ip"): loadIpsFromJson(ips, json); break; case EOO: if (canceled) return true; mCompletion(API_OK, url, ips); return true; default: if (!json.storeobject()) { if (!canceled) { mCompletion(API_EINTERNAL, string(), {}); } return false; } } } } // request temporary source URL for DirectRead CommandDirectRead::CommandDirectRead(MegaClient* /*client*/, DirectReadNode* cdrn) { drn = cdrn; cmd("g"); arg(drn->isPublicHandle ? "p" : "n", (byte*)&drn->h, MegaClient::NODEHANDLE); arg("g", 1); // server will provide download URL(s)/token(s) (if skipped, only information about the file) arg("v", 2); // version 2: server can supply details for cloudraid files if (drn->privateauth.size()) { arg("esid", drn->privateauth.c_str()); } if (drn->publicauth.size()) { arg("en", drn->publicauth.c_str()); } if (drn->chatauth.size()) { arg("cauth", drn->chatauth.c_str()); } arg("ssl", 2); mLockless = true; } void CommandDirectRead::cancel() { Command::cancel(); drn = NULL; } bool CommandDirectRead::procresult(Result r, JSON& json) { if (drn) { drn->pendingcmd = NULL; } if (r.wasErrorOrOK()) { if (!canceled && drn) { drn->cmdresult(r.errorOrOK()); } return true; } else { Error e(API_EINTERNAL); dstime tl = 0; std::vector tempurls; for (;;) { switch (json.getnameid()) { case makeNameid("g"): if (json.enterarray()) // now that we are requesting v2, the reply will be an array of 6 URLs for a raid download, or a single URL for the original direct download { for (;;) { std::string tu; if (!json.storeobject(&tu)) { break; } tempurls.push_back(tu); } json.leavearray(); } else { std::string tu; if (json.storeobject(&tu)) { tempurls.push_back(tu); } } if (tempurls.size() == 1 || tempurls.size() == RAIDPARTS) { if (drn) { drn->tempurls.swap(tempurls); e.setErrorCode(API_OK); } } else { e.setErrorCode(API_EINCOMPLETE); } break; case makeNameid("s"): if (drn) { drn->size = json.getint(); } break; case makeNameid("d"): e = API_EBLOCKED; break; case makeNameid("e"): e = (error)json.getint(); break; case makeNameid("tl"): tl = dstime(json.getint()); break; case EOO: if (!canceled && drn) { if (e == API_EOVERQUOTA && !tl) { // default retry interval tl = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS; } drn->cmdresult(e, e == API_EOVERQUOTA ? tl * 10 : 0); } return true; default: if (!json.storeobject()) { if (!canceled && drn) { drn->cmdresult(e); } return false; } } } } } // request temporary source URL for full-file access (p == private node) CommandGetFile::CommandGetFile(MegaClient* /*client*/, const byte* key, size_t keySize, bool undelete, handle h, bool p, const char* privateauth, const char* publicauth, const char* chatauth, bool singleUrl, bool forceSSL, Cb&& completion) { if (undelete) { cmd("gd"); } else { cmd("g"); mLockless = true; } arg(p ? "n" : "p", (byte*)&h, MegaClient::NODEHANDLE); arg("g", 1); // server will provide download URL(s)/token(s) (if skipped, only information about the file) DEBUG_TEST_HOOK_DOWNLOAD_REQUEST_SINGLEURL(singleUrl); if (!singleUrl) { arg("v", 2); // version 2: server can supply details for cloudraid files } if (forceSSL) { arg("ssl", 2); } if (privateauth) { arg("esid", privateauth); } if (publicauth) { arg("en", publicauth); } if (chatauth) { arg("cauth", chatauth); } assert(key && "no key provided!"); if (key && keySize != SymmCipher::KEYLENGTH) { assert (keySize <= FILENODEKEYLENGTH); memcpy(filekey, key, keySize); mFileKeyType = FILENODE; } else if (key && keySize == SymmCipher::KEYLENGTH) { memcpy(filekey, key, SymmCipher::KEYLENGTH); mFileKeyType = 1; } mCompletion = std::move(completion); } void CommandGetFile::cancel() { Command::cancel(); } void CommandGetFile::callFailedCompletion(const Error &e) { assert(mCompletion); if (mCompletion) { mCompletion(e, -1, 0, nullptr, nullptr, nullptr, {}, {}, {}); } } // process file credentials bool CommandGetFile::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { if (!canceled) { callFailedCompletion(r.errorOrOK()); } return true; } const char* at = nullptr; Error e(API_EINTERNAL); m_off_t s = -1; dstime tl = 0; std::unique_ptr buf; // credentials relevant to a non-TransferSlot scenario (node query) string fileattrstring; string filenamestring; string filefingerprint; vector tempurls; vector tempips; string fileHandle; for (;;) { switch (json.getnameid()) { case makeNameid("g"): if (json.enterarray()) // now that we are requesting v2, the reply will be an array of 6 URLs for a raid download, or a single URL for the original direct download { for (;;) { std::string tu; if (!json.storeobject(&tu)) { break; } tempurls.push_back(tu); } json.leavearray(); } else { std::string tu; if (json.storeobject(&tu)) { tempurls.push_back(tu); } } e.setErrorCode(API_OK); break; case makeNameid("ip"): loadIpsFromJson(tempips, json); break; case makeNameid("s"): s = json.getint(); break; case makeNameid("at"): at = json.getvalue(); break; case makeNameid("fa"): json.storeobject(&fileattrstring); break; case makeNameid("e"): e = (error)json.getint(); break; case makeNameid("tl"): tl = dstime(json.getint()); break; case makeNameid("fh"): { json.storeobject(&fileHandle); break; } case EOO: { // defer code that steals the ips and stores them in the cache // thus we can use them before going out of scope std::shared_ptr deferThis( nullptr, [this, &tempurls, &tempips](...) { cacheresolvedurls("g", tempurls, std::move(tempips)); }); if (canceled) //do not proceed: SymmCipher may no longer exist { return true; } if (!at) { callFailedCompletion(API_EINTERNAL); return true; } // decrypt at and set filename SymmCipher * cipherer = client->getRecycledTemporaryTransferCipher(filekey, mFileKeyType); const char* eos = strchr(at, '"'); buf.reset(Node::decryptattr(cipherer, at, eos ? static_cast(eos - at) : strlen(at))); if (!buf) { callFailedCompletion(API_EKEY); return true; } // all good, lets parse the attribute string JSON attrJson; attrJson.begin((char*)buf.get() + 5); for (;;) { switch (attrJson.getnameid()) { case makeNameid("c"): if (!attrJson.storeobject(&filefingerprint)) { callFailedCompletion(API_EINTERNAL); return true; } break; case makeNameid("n"): if (!attrJson.storeobject(&filenamestring)) { callFailedCompletion(API_EINTERNAL); return true; } break; case EOO: // success, call completion function! return mCompletion ? mCompletion(e, s, tl, &filenamestring, &filefingerprint, &fileattrstring, tempurls, tempips, fileHandle) : false; default: if (!attrJson.storeobject()) { callFailedCompletion(API_EINTERNAL); return false; } } } } default: if (!json.storeobject()) { if (!canceled) { callFailedCompletion(API_EINTERNAL); } return false; } } } } CommandSetAttr::CommandSetAttr(MegaClient*, std::shared_ptr n, attr_map&& attrMapUpdates, Completion&& c, bool canChangeVault): mAttrMapUpdates(attrMapUpdates), mCanChangeVault(canChangeVault) { h = n->nodeHandle(); mNode = n; generationError = API_OK; completion = c; addToNodePendingCommands(n.get()); } const char* CommandSetAttr::getJSON(MegaClient* clientOfRequest) { // We generate the command just before sending, so it's up to date for any external changes that occured in the meantime // And we can also take into account any changes we have sent for this node that have not yet been applied by actionpackets jsonWriter.clear(); generationError = API_OK; cmd("a"); string at; if (shared_ptr n = clientOfRequest->nodeByHandle(h)) { assert(n == mNode); AttrMap m = n->attrs; // apply these changes for sending, but also any earlier changes that are ahead in the queue assert(!n->mPendingChanges.empty()); n->mPendingChanges.forEachCommand([&m, this](Command* cmd) { if (cmd == this) return; if (auto cmdSetAttr = dynamic_cast(cmd)) { cmdSetAttr->applyUpdatesTo(m); } }); m.applyUpdates(mAttrMapUpdates); if (SymmCipher* cipher = n->nodecipher()) { m.getjson(&at); clientOfRequest->makeattr(cipher, &at, at.c_str(), int(at.size())); } else { h.setUndef(); // dummy command to generate an error, with no effect mNode.reset(); generationError = API_EKEY; } if (at.size() > MAX_NODE_ATTRIBUTE_SIZE) { clientOfRequest->sendevent(99484, "Node attribute exceed maximun size"); LOG_err << "Node attribute exceed maximun size"; h.setUndef(); // dummy command to generate an error, with no effect mNode.reset(); generationError = API_EARGS; } } else { h.setUndef(); // dummy command to generate an error, with no effect mNode.reset(); generationError = API_ENOENT; } arg("n", (byte*)&h, MegaClient::NODEHANDLE); arg("at", (byte*)at.c_str(), at.size()); if (mCanChangeVault) { arg("vw", 1); } return jsonWriter.getstring().c_str(); } bool CommandSetAttr::procresult(Result r, JSON&) { removeFromNodePendingCommands(h); if (completion) completion(h, generationError ? Error(generationError) : r.errorOrOK()); return r.wasErrorOrOK(); } void CommandSetAttr::applyUpdatesTo(AttrMap& attrMap) const { attrMap.applyUpdates(mAttrMapUpdates); } // (the result is not processed directly - we rely on the server-client // response) CommandPutNodes::CommandPutNodes(MegaClient* client, NodeHandle th, const char* userhandle, VersioningOption vo, vector&& newnodes, int ctag, putsource_t csource, const char* cauth, Completion&& resultFunction, bool canChangeVault, const string& customerIpPort, std::optional pitag): mResultFunction(resultFunction) { byte key[FILENODEKEYLENGTH]; #ifndef NDEBUG assert(newnodes.size() > 0); for (auto& n : newnodes) assert(n.canChangeVault == canChangeVault); #endif nn = std::move(newnodes); type = userhandle ? USER_HANDLE : NODE_HANDLE; source = csource; mSeqtagArray = true; cmd("p"); arg("v", 4); // include file IDs/handles if (userhandle) { arg("t", userhandle); targethandle.setUndef(); } else { arg("t", (byte*)&th, MegaClient::NODEHANDLE); targethandle = th; } arg("sm", 1); if (pitag) { const auto pitagStr = pitagToString(*pitag); arg("p", pitagStr.c_str()); } if (cauth) { arg("cauth", cauth); } if (canChangeVault) { arg("vw", 1); } // "vb": when provided, it force to override the account-wide versioning behavior by the value indicated by client // vb:1 to force it on // vb:0 to force it off // Dont provide it at all to rely on the account-wide setting (as of the moment the command is processed). if (vo == UseLocalVersioningFlag && client->loggedIntoWritableFolder()) { // do not rely on local versioning flag when logged into writable folders // as the owner's ATTR_DISABLE_VERSIONS attribute is not received/updated in that case // and MegaClient::versions_disabled will alwas be false. // Instead, let the API server act according to user's settings vo = UseServerVersioningFlag; } switch (vo) { case NoVersioning: break; case ClaimOldVersion: arg("vb", 1); break; case ReplaceOldVersion: arg("vb", m_off_t(0)); break; case UseLocalVersioningFlag: arg("vb", !client->versions_disabled); vo = !client->versions_disabled ? ClaimOldVersion : ReplaceOldVersion; break; case UseServerVersioningFlag: break; } beginarray("n"); for (unsigned i = 0; i < nn.size(); i++) { beginobject(); NewNode* nni = &nn[i]; switch (nni->source) { case NEW_NODE: arg("h", (byte*)&nni->nodehandle, MegaClient::NODEHANDLE); break; case NEW_PUBLIC: arg("ph", (byte*)&nni->nodehandle, MegaClient::NODEHANDLE); break; case NEW_UPLOAD: arg("h", nni->uploadtoken.data(), sizeof nn[0].uploadtoken); // include pending file attributes for this upload string s; if (!nni->fileattributes.empty()) { // if attributes are set on the newnode then the app is not using the pendingattr mechanism s = std::move(nni->fileattributes); } else { client->pendingattrstring(nn[i].uploadhandle, &s); } if (s.size()) { arg("fa", s.c_str(), 1); } } if (!ISUNDEF(nn[i].parenthandle)) { arg("p", (byte*)&nn[i].parenthandle, MegaClient::NODEHANDLE); } if (vo != NoVersioning && nn[i].type == FILENODE && !nn[i].ovhandle.isUndef()) { arg("ov", (byte*)&nn[i].ovhandle, MegaClient::NODEHANDLE); } nn[i].mVersioningOption = vo; arg("t", nn[i].type); arg("a", (byte*)nn[i].attrstring->data(), nn[i].attrstring->size()); if (!client->loggedIntoWritableFolder()) { assert(!nn[i].hasZeroKey()); // Add this assert here to avoid extra checks in production -> we will have a check and logs if this fails within CommandPutNode::procresult if (nn[i].nodekey.size() <= sizeof key) { client->key.ecb_encrypt((byte*)nn[i].nodekey.data(), key, nn[i].nodekey.size()); assert(!SymmCipher::isZeroKey(key, FILENODEKEYLENGTH)); arg("k", key, nn[i].nodekey.size()); } else { arg("k", (const byte*)nn[i].nodekey.data(), nn[i].nodekey.size()); } } endobject(); } endarray(); if (!customerIpPort.empty()) { arg("cip", customerIpPort.c_str()); // "IPv4:port" or "[IPv6]:port" } // add cr element for new nodes, if applicable if (type == NODE_HANDLE) { shared_ptr tn; if ((tn = client->nodeByHandle(th))) { assert(tn->type != FILENODE); ShareNodeKeys snk; for (unsigned i = 0; i < nn.size(); i++) { switch (nn[i].source) { case NEW_PUBLIC: case NEW_NODE: snk.add(nn[i].nodekey, nn[i].nodehandle, tn, true); break; case NEW_UPLOAD: snk.add(nn[i].nodekey, nn[i].nodehandle, tn, true, nn[i].uploadtoken.data(), (int)sizeof nn[i].uploadtoken); break; } } snk.get(this, true); } } tag = ctag; } // add new nodes and handle->node handle mapping void CommandPutNodes::removePendingDBRecordsAndTempFiles() { pendingdbid_map::iterator it = client->pendingtcids.find(tag); if (it != client->pendingtcids.end()) { if (client->tctable) { client->mTctableRequestCommitter->beginOnce(); vector &ids = it->second; for (unsigned int i = 0; i < ids.size(); i++) { if (ids[i]) { client->tctable->del(ids[i]); } } } client->pendingtcids.erase(it); } pendingfiles_map::iterator pit = client->pendingfiles.find(tag); if (pit != client->pendingfiles.end()) { vector &pfs = pit->second; for (unsigned int i = 0; i < pfs.size(); i++) { client->fsaccess->unlinklocal(pfs[i]); } client->pendingfiles.erase(pit); } } void CommandPutNodes::performAppCallback(Error e, vector& newnodes, bool targetOverride, const map& fileHandles) { if (mResultFunction) mResultFunction(e, type, newnodes, targetOverride, tag, fileHandles); else client->app->putnodes_result(e, type, newnodes, targetOverride, tag, fileHandles); } bool CommandPutNodes::procresult(Result r, JSON& json) { removePendingDBRecordsAndTempFiles(); if (r.wasErrorOrOK()) { LOG_debug << "Putnodes error " << r.errorOrOK(); error e = r.errorOrOK(); if (e == API_EOVERQUOTA) { if (client->isPrivateNode(targethandle)) { client->activateoverquota(0, false); } } performAppCallback((e ? e : API_EINTERNAL), nn, false, {}); return true; } Error newNodeError(API_OK); map fileHandles; for (;;) { switch (json.getnameid()) { case makeNameid("e"): { // This element is a sparse array indicating the nodes that failed, and the // corresponding error codes. // If the first three nodes failed, the response would be e.g. [-9,-9,-9]. // Success is []. // If the second and third node failed, the response would change to // {"1":-9,"2":-9}. bool hasJsonArray = json.enterarray(); if (!hasJsonArray && !json.enterobject()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } unsigned arrayIndex = 0; for (;;) { if (hasJsonArray) { if (*json.pos == ']') { json.leavearray(); break; } if (!json.isnumeric()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } assert(arrayIndex < nn.size()); if (arrayIndex < nn.size()) { nn[arrayIndex].mError = error(json.getint()); if (nn[arrayIndex].mError != API_OK) { newNodeError = nn[arrayIndex].mError; LOG_debug << "[CommandPutNodes] New Node failed with " << newNodeError << " [newnode index = " << arrayIndex << ", NodeHandle = " << nn[arrayIndex].nodeHandle() << "]"; assert(((nn[arrayIndex].mError != API_EKEY) || !nn[arrayIndex].hasZeroKey()) && "New Node which failed with API_EKEY has a zerokey!!!!"); } arrayIndex++; } } else { string index, errorCode; if (json.storeobject(&index) && *json.pos == ':') { ++json.pos; if (json.storeobject(&errorCode)) { arrayIndex = unsigned(atoi(index.c_str())); if (arrayIndex < nn.size()) { nn[arrayIndex].mError = error(atoi(errorCode.c_str())); continue; } } } if (!json.leaveobject()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } break; } } break; } case makeNameid("fh"): // ["drEyXKKB:C6-OsdmLX2U",":"] if (!json.enterarray()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } for (std::string temp; json.storeobject(&temp);) { auto separator = temp.find(':'); if (separator != string::npos) { fileHandles[temp.substr(0, separator)] = temp.substr(separator + 1); } } if (!json.leavearray()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } break; default: if (!json.storeobject()) { performAppCallback(API_EINTERNAL, nn, false, fileHandles); return false; } break; case EOO: #ifdef DEBUG if (type != USER_HANDLE) { for (auto& n: nn) { // double check we got a node, or know the error why it didn't get created if (!((n.added && n.mAddedHandle != UNDEF && !n.mError) || (!n.added && n.mAddedHandle == UNDEF && n.mError))) { assert(false); } } } #endif // when the target has been removed, the API automatically adds the new node/s // into the rubbish bin shared_ptr tempNode = !nn.empty() ? client->nodebyhandle(nn.front().mAddedHandle) : nullptr; bool targetOverride = (tempNode.get() && NodeHandle().set6byte(tempNode->parenthandle) != targethandle); const Error& finalStatus = emptyResponse ? ((newNodeError != API_OK) ? // Add last new node error if there is any, otherwise API_ENOENT newNodeError : Error(API_ENOENT)) : Error(API_OK); performAppCallback(finalStatus, nn, targetOverride, fileHandles); return true; } } } CommandMoveNode::CommandMoveNode(MegaClient* client, std::shared_ptr n, std::shared_ptr t, syncdel_t csyncdel, NodeHandle prevparent, Completion&& c, bool canChangeVault) { h = n->nodeHandle(); syncdel = csyncdel; np = t->nodeHandle(); pp = prevparent; syncop = !pp.isUndef(); mCanChangeVault = canChangeVault; cmd("m"); // Special case for Move, we do set the 'i' field. // This is needed for backward compatibility, old versions used memcmp to detect if a 'd' actionpacket was followed by a 't' actionpacket with the same 'i' (ie, a move) // Additionally the servers can't deliver `st` in that packet for the same reason. And of course we will not ignore this `t` packet, despite setting 'i'. notself(client); if (mCanChangeVault) { arg("vw", 1); } arg("n", h); arg("t", t->nodeHandle()); assert(t->type != FILENODE); TreeProcShareKeys tpsk(t, true); client->proctree(n, &tpsk); tpsk.get(this); tag = client->reqtag; completion = std::move(c); } bool CommandMoveNode::procresult(Result r, JSON&) { if (r.wasErrorOrOK()) { if (r.wasError(API_EOVERQUOTA)) { client->activateoverquota(0, false); } // Movement of shares and pending shares into Rubbish should remove them if (r.wasStrictlyError() && syncdel == SYNCDEL_NONE) { client->sendevent(99439, "Unexpected move error", 0); } } if (completion) completion(h, r.errorOrOK()); return r.wasErrorOrOK(); } CommandDelNode::CommandDelNode(MegaClient*, NodeHandle th, bool keepversions, int cmdtag, std::function&& f, bool canChangeVault): mResultFunction(std::move(f)) { cmd("d"); arg("n", (byte*)&th, MegaClient::NODEHANDLE); if (keepversions) { arg("v", 1); } if (canChangeVault) { arg("vw", 1); } h = th; tag = cmdtag; } bool CommandDelNode::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mResultFunction) mResultFunction(h, r.errorOrOK()); else client->app->unlink_result(h.as8byte(), r.errorOrOK()); return true; } else { error e = API_OK; for (;;) { switch (json.getnameid()) { case makeNameid("r"): if (json.enterarray()) { if(json.isnumeric()) { e = (error)json.getint(); } json.leavearray(); } break; case EOO: if (mResultFunction) mResultFunction(h, e); else client->app->unlink_result(h.as8byte(), e); return true; default: if (!json.storeobject()) { if (mResultFunction) mResultFunction(h, API_EINTERNAL); else client->app->unlink_result(h.as8byte(), API_EINTERNAL); return false; } } } } } CommandDelVersions::CommandDelVersions(MegaClient* client) { cmd("dv"); tag = client->reqtag; } bool CommandDelVersions::procresult(Result r, JSON&) { client->app->unlinkversions_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandKillSessions::CommandKillSessions(MegaClient* client) { cmd("usr"); arg("ko", 1); // Request to kill all sessions except the current one h = UNDEF; tag = client->reqtag; } CommandKillSessions::CommandKillSessions(MegaClient* client, handle sessionid) { cmd("usr"); beginarray("s"); element(sessionid, MegaClient::USERHANDLE); endarray(); h = sessionid; tag = client->reqtag; } bool CommandKillSessions::procresult(Result r, JSON&) { client->app->sessions_killed(h, r.errorOrOK()); return r.wasErrorOrOK(); } CommandLogout::CommandLogout(MegaClient *client, Completion completion, bool keepSyncConfigsFile) : mCompletion(std::move(completion)) , mKeepSyncConfigsFile(keepSyncConfigsFile) { cmd("sml"); batchSeparately = true; tag = client->reqtag; } const char* CommandLogout::getJSON(MegaClient* clientOfRequest) { if (!incrementedCount) { // only set this once we are about to send the command, in case there are others ahead of it in the queue clientOfRequest->loggingout++; // only set it once in case of retries due to -3. incrementedCount = true; } return jsonWriter.getstring().c_str(); } bool CommandLogout::procresult(Result r, JSON&) { assert(r.wasErrorOrOK()); if (client->loggingout > 0) { client->loggingout--; } if(r.wasError(API_OK)) { // We are logged out, but we mustn't call locallogout until we exit this call // stack for processing CS batches, as it deletes data currently in use. Completion completion = std::move(mCompletion); bool keepSyncConfigsFile = mKeepSyncConfigsFile; LOG_debug << "setting mOnCSCompletion for final logout processing"; // track possible lack of logout callbacks client->mOnCSCompletion = [=](MegaClient* clientToLogout) { clientToLogout->locallogout(true, keepSyncConfigsFile); completion(API_OK); }; } else { mCompletion(r.errorOrOK()); } return true; } CommandPrelogin::CommandPrelogin(MegaClient* client, Completion completion, const char* email) : mCompletion(std::move(completion)) , email(email) { // Sanity. assert(mCompletion); cmd("us0"); arg("user", email); batchSeparately = true; // in case the account is blocked (we need to get a sid so we can issue whyamiblocked) tag = client->reqtag; } bool CommandPrelogin::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { mCompletion(0, NULL, NULL, r.errorOrOK()); return true; } assert(r.hasJsonObject()); int v = 0; string salt; for (;;) { switch (json.getnameid()) { case makeNameid("v"): v = int(json.getint()); break; case makeNameid("s"): json.storeobject(&salt); break; case EOO: if (v == 0) { LOG_err << "No version returned"; mCompletion(0, NULL, NULL, API_EINTERNAL); } else if (v > 2) { LOG_err << "Version of account not supported"; mCompletion(0, NULL, NULL, API_EINTERNAL); } else if (v == 2 && !salt.size()) { LOG_err << "No salt returned"; mCompletion(0, NULL, NULL, API_EINTERNAL); } else { client->accountversion = v; Base64::atob(salt, client->accountsalt); mCompletion(v, &email, &salt, API_OK); } return true; default: if (!json.storeobject()) { mCompletion(0, NULL, NULL, API_EINTERNAL); return false; } } } } // login request with user e-mail address and user hash CommandLogin::CommandLogin(MegaClient* client, Completion completion, const char* email, const byte* emailhash, size_t emailhashsize, const byte* sessionkey, int csessionversion, const char* pin): mCompletion(std::move(completion)) { // Sanity. assert(mCompletion); cmd("us"); batchSeparately = true; // in case the account is blocked (we need to get a sid so we can issue whyamiblocked) // are we just performing a session validation? checksession = !email; sessionversion = csessionversion; if (!checksession) { arg("user", email); arg("uh", emailhash, emailhashsize); if (pin) { arg("mfa", pin); } } else { if (client->sctable && client->dbaccess->currentDbVersion == DbAccess::LEGACY_DB_VERSION) { LOG_debug << "Requesting a local cache upgrade"; arg("fa", 1); } } if (sessionkey) { arg("sek", sessionkey, SymmCipher::KEYLENGTH); } if (client->cachedscsn != UNDEF) { arg("sn", (byte*)&client->cachedscsn, sizeof client->cachedscsn); } string deviceIdHash = client->getDeviceidHash(); if (!deviceIdHash.empty()) { arg("si", deviceIdHash.c_str()); } else { client->sendevent(99454, "Device-id not available at login"); } tag = client->reqtag; } // process login result bool CommandLogin::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->loginResult(std::move(mCompletion), r.errorOrOK()); return true; } assert(r.hasJsonObject()); byte hash[SymmCipher::KEYLENGTH]; byte sidbuf[AsymmCipher::MAXKEYLENGTH]; byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; byte sek[SymmCipher::KEYLENGTH]; int len_k = 0, len_privk = 0, len_csid = 0, len_tsid = 0, len_sek = 0; handle me = UNDEF; bool fa = false; bool ach = false; for (;;) { switch (json.getnameid()) { case makeNameid("k"): len_k = json.storebinary(hash, sizeof hash); break; case makeNameid("u"): me = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("sek"): len_sek = json.storebinary(sek, sizeof sek); break; case makeNameid("tsid"): len_tsid = json.storebinary(sidbuf, sizeof sidbuf); break; case makeNameid("csid"): len_csid = json.storebinary(sidbuf, sizeof sidbuf); break; case makeNameid("privk"): len_privk = json.storebinary(privkbuf, sizeof privkbuf); break; case makeNameid("fa"): fa = json.getbool(); break; case makeNameid("ach"): ach = json.getbool(); break; case makeNameid("sn"): if (!json.getint()) { // local state cache continuity rejected: read state from // server instead client->cachedscsn = UNDEF; } break; case EOO: if (!checksession) { if (ISUNDEF(me) || len_k != sizeof hash) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return true; } // decrypt and set master key client->key.ecb_decrypt(hash); client->key.setkey(hash); } else { if (fa && client->sctable) { client->sctable->remove(); client->sctable.reset(); client->mNodeManager.reset(); client->cachedscsn = UNDEF; client->dbaccess->currentDbVersion = DbAccess::DB_VERSION; client->sendevent(99404, "Local DB upgrade granted", 0); } } if (len_sek) { if (len_sek != SymmCipher::KEYLENGTH) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return true; } if (checksession && sessionversion) { byte k[SymmCipher::KEYLENGTH]; memcpy(k, client->key.key, sizeof(k)); client->key.setkey(sek); client->key.ecb_decrypt(k); client->key.setkey(k); } } if (len_tsid) { client->sid.assign((const char *)sidbuf, MegaClient::SIDLEN); // account does not have an RSA keypair set: verify // password using symmetric challenge if (!client->checktsid(sidbuf, static_cast(len_tsid))) { LOG_warn << "Error checking tsid"; client->loginResult(std::move(mCompletion), API_ENOENT); return true; } } else { // account has RSA keypair: decrypt server-provided session ID if (len_privk < 256) { if (!checksession) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return true; } } else { // decrypt and set private key client->key.ecb_decrypt(privkbuf, static_cast(len_privk)); client->mSerializedPrivateRsaKey.resize(AsymmCipher::MAXKEYLENGTH * 2); client->mSerializedPrivateRsaKey.resize( Base64::btoa(privkbuf, static_cast(len_privk), (char*)client->mSerializedPrivateRsaKey.data())); if (!client->mPrivateRsaKey.setkey(AsymmCipher::PRIVKEY, privkbuf, len_privk)) { LOG_warn << "Error checking private key"; client->loginResult(std::move(mCompletion), API_ENOENT); return true; } } if (!checksession) { if (len_csid < 32) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return true; } byte buf[sizeof me]; // decrypt and set session ID for subsequent API communication if (!client->mPrivateRsaKey.decrypt(sidbuf, static_cast(len_csid), sidbuf, MegaClient::SIDLEN) // additionally, check that the user's handle included in the session // matches the own user's handle (me) || (Base64::atob((char*)sidbuf + SymmCipher::KEYLENGTH, buf, sizeof buf) != sizeof buf) || (me != MemAccess::get((const char*)buf))) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return true; } client->sid.assign((const char *)sidbuf, MegaClient::SIDLEN); } } client->me = me; client->uid = Base64Str(client->me); client->achievements_enabled = ach; // Force to create own user client->finduser(me, 1); if (len_sek) { client->sessionkey.assign((const char *)sek, sizeof(sek)); } // Initialize the client adapter. client->mClientAdapter.initialize(); // Initialize File Service. client->mFileService.initialize(client->mClientAdapter); // Initialize FUSE subsystem. client->mFuseService.initialize(); client->openStatusTable(true); client->loadJourneyIdCacheValues(); client->setSyncUploadThrottleParamsFromAPI(); { // scope for local variable MegaClient* cl = client; // make a copy, because 'this' will be gone by the time lambda will execute client->loginResult(std::move(mCompletion), API_OK, [cl]() { cl->getaccountdetails(std::make_shared(), false, false, true, false, false, false); } ); } return true; default: if (!json.storeobject()) { client->loginResult(std::move(mCompletion), API_EINTERNAL); return false; } } } } // add/remove share; include node share keys if new share CommandSetShare::CommandSetShare(MegaClient* client, std::shared_ptr n, User* u, accesslevel_t a, bool newshare, const char* msg, bool writable, const char* personal_representation, int ctag, std::function f) { byte auth[SymmCipher::BLOCKSIZE]; byte key[SymmCipher::KEYLENGTH]; tag = ctag; sh = n->nodehandle; access = a; mWritable = writable; completion = std::move(f); assert(completion); mSeqtagArray = true; cmd("s2"); arg("n", (byte*)&sh, MegaClient::NODEHANDLE); // Only for inviting non-contacts if (personal_representation && personal_representation[0]) { this->personal_representation = personal_representation; arg("e", personal_representation); } if (msg && msg[0]) { this->msg = msg; arg("msg", msg); } if (a != ACCESS_UNKNOWN) { // TODO: dummy key/handleauth - FIXME: remove when the server allows it memset(key, 0, sizeof key); memset(auth, 0, sizeof auth); arg("ok", key, sizeof key); arg("ha", auth, sizeof auth); } beginarray("s"); beginobject(); arg("u", u ? ((u->show == VISIBLE) ? u->uid.c_str() : u->email.c_str()) : MegaClient::EXPORTEDLINK); // if the email is registered, the pubk request has returned the userhandle --> // sending the userhandle instead of the email makes the API to assume the user is already a contact if (a != ACCESS_UNKNOWN) { arg("r", a); } endobject(); endarray(); // only for a fresh share: add cr element with all node keys encrypted to // the share key if (newshare) { // the new share's nodekeys for this user: generate node list TreeProcShareKeys tpsk(n, false); client->proctree(n, &tpsk); tpsk.get(this); } } // process user element (email/handle pairs) bool CommandSetShare::procuserresult(MegaClient* ownClient, JSON& json) { while (json.enterobject()) { handle uh = UNDEF; const char* m = NULL; for (;;) { switch (json.getnameid()) { case makeNameid("u"): uh = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("m"): m = json.getvalue(); break; case EOO: if (!ISUNDEF(uh) && m) { ownClient->mapuser(uh, m); } return true; default: if (!json.storeobject()) { return false; } } } } return false; } // process result of share addition/modification bool CommandSetShare::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { completion(r.errorOrOK(), mWritable); return true; } for (;;) { switch (json.getnameid()) { case makeNameid("ok"): // an owner key response will only // occur if the same share was created // with a different key { // if the API has a different key, the only legit scenario is that // such owner key is invalid (ie. "AAAAA..."), set by a client with // secure=true completion(API_EKEY, mWritable); return true; } case makeNameid("u"): // user/handle confirmation if (json.enterarray()) { while (procuserresult(client, json)) {} json.leavearray(); } break; case makeNameid("r"): if (json.enterarray()) { while (json.isnumeric()) { // intermediate result updates, not final completion // we used to call share_result but it wasn't used json.getint(); } json.leavearray(); } break; case EOO: completion(API_OK, mWritable); return true; default: if (!json.storeobject()) { completion(API_EINTERNAL, mWritable); return false; } } } } CommandPendingKeys::CommandPendingKeys(MegaClient *client, CommandPendingKeysReadCompletion completion) { // Assume we've been passed a completion function. mReadCompletion = std::move(completion); cmd("pk"); tag = client->reqtag; } CommandPendingKeys::CommandPendingKeys(MegaClient *client, std::string lastcompleted, std::function completion) { // Assume we've been passed a completion function. mCompletion = std::move(completion); cmd("pk"); arg("d", lastcompleted.c_str()); tag = client->reqtag; } CommandPendingKeys::CommandPendingKeys(MegaClient *client, handle user, handle share, byte *key, std::function completion) { // Assume we've been passed a completion function. mCompletion = std::move(completion); cmd("pk"); arg("u", (byte*)&user, MegaClient::USERHANDLE); arg("h", (byte*)&share, MegaClient::NODEHANDLE); arg("k", key, SymmCipher::KEYLENGTH); tag = client->reqtag; } bool CommandPendingKeys::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mReadCompletion) { mReadCompletion(r.errorOrOK(), std::string(), nullptr); return true; } mCompletion(r.errorOrOK()); return true; } if (mCompletion) { mCompletion(API_EINTERNAL); return false; } // Response format: // {"peeruserhandle1":{"sharehandle1":"key1","sharehandle2":"key2"}, // "peeruserhandle2":{"sharehandle3":"key3"},... // "d":"lastcompleted"} (lastcompleted is a base64 string like oMl7nfj67Jw) // maps user's handles to a map of share's handles : share's keys std::shared_ptr>> keys = std::make_shared>>(); std::string lastcompleted; std::string name; name = json.getname(); while (name.size()) { if (name == "d") { json.storeobject(&lastcompleted); name = json.getname(); continue; } handle userhandle = 0; Base64::atob(name.c_str(), (byte*)&userhandle, MegaClient::USERHANDLE); if (!json.enterobject()) { mReadCompletion(API_EINTERNAL, std::string(), nullptr); return false; } handle sharehandle; while (!ISUNDEF(sharehandle = json.gethandle())) { string sharekey; JSON::copystring(&sharekey, json.getvalue()); (*keys)[userhandle][sharehandle] = Base64::atob(sharekey); } json.leaveobject(); name = json.getname(); } mReadCompletion(API_OK, lastcompleted, keys); return true; } CommandSetPendingContact::CommandSetPendingContact(MegaClient* client, const char* temail, opcactions_t action, const char* msg, const char* oemail, handle contactLink, Completion completion) { mSeqtagArray = true; cmd("upc"); if (oemail != NULL) { arg("e", oemail); } arg("u", temail); switch (action) { case OPCA_DELETE: arg("aa", "d"); break; case OPCA_REMIND: arg("aa", "r"); break; case OPCA_ADD: arg("aa", "a"); if (!ISUNDEF(contactLink)) { arg("cl", (byte*)&contactLink, MegaClient::CONTACTLINKHANDLE); } break; } if (msg != NULL) { arg("msg", msg); } tag = client->reqtag; this->action = action; this->temail = temail; // Assume we've been passed a completion function. mCompletion = std::move(completion); } bool CommandSetPendingContact::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { handle pcrhandle = UNDEF; if (r.wasError(API_OK)) // response for delete & remind actions is always numeric { // find the PCR by email PendingContactRequest *pcr = NULL; for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++) { if (it->second->targetemail == temail) { pcr = it->second.get(); pcrhandle = pcr->id; break; } } if (action == OPCA_DELETE && pcr) { LOG_err << "Deleted PCR is still present."; } if (action == OPCA_REMIND && !pcr) { LOG_err << "Reminded PCR not found"; } } doComplete(pcrhandle, r.errorOrOK(), this->action); return true; } // if the PCR has been added, the response contains full details // Validate returned values. PCR should have been added by the "opc" action packet. handle p = UNDEF; m_time_t ts = 0; m_time_t uts = 0; const char *eValue = NULL; const char *m = NULL; const char *msg = NULL; PendingContactRequest *pcr = NULL; for (;;) { switch (json.getnameid()) { case makeNameid("p"): p = json.gethandle(MegaClient::PCRHANDLE); break; case makeNameid("m"): m = json.getvalue(); break; case makeNameid("e"): eValue = json.getvalue(); break; case makeNameid("msg"): msg = json.getvalue(); break; case makeNameid("ts"): ts = json.getint(); break; case makeNameid("uts"): uts = json.getint(); break; case EOO: if (ISUNDEF(p)) { LOG_err << "Error in CommandSetPendingContact. Undefined handle"; doComplete(UNDEF, API_EINTERNAL, this->action); return true; } if (action != OPCA_ADD || !eValue || !m || ts == 0 || uts == 0) { LOG_err << "Error in CommandSetPendingContact. Wrong parameters"; doComplete(UNDEF, API_EINTERNAL, this->action); return true; } pcr = client->pcrindex.count(p) ? client->pcrindex[p].get() : nullptr; if (!pcr) { LOG_err << "Error in CommandSetPendingContact. Pending Contact Request " << toHandle(p) << " has not been added by the action packet."; } else { // Update the message if it was received empty in the action packet. // API may send it empty in the action packets to avoid spamming. if (msg && pcr->msg.empty()) { pcr->update(nullptr, nullptr, pcr->ts, pcr->uts, msg, pcr->isoutgoing); client->notifypcr(pcr); } } doComplete(p, API_OK, this->action); return true; default: if (!json.storeobject()) { LOG_err << "Error in CommandSetPendingContact. Parse error"; doComplete(UNDEF, API_EINTERNAL, this->action); return false; } } } } void CommandSetPendingContact::doComplete(handle handle, error e, opcactions_t actions) { if (!mCompletion) return client->app->setpcr_result(handle, e, actions); mCompletion(handle, e, actions); } CommandUpdatePendingContact::CommandUpdatePendingContact(MegaClient* client, handle p, ipcactions_t action, Completion completion) { cmd("upca"); arg("p", (byte*)&p, MegaClient::PCRHANDLE); switch (action) { case IPCA_ACCEPT: arg("aa", "a"); break; case IPCA_DENY: arg("aa", "d"); break; case IPCA_IGNORE: default: arg("aa", "i"); break; } tag = client->reqtag; this->action = action; // Assume we've been provided a completion function. mCompletion = std::move(completion); } bool CommandUpdatePendingContact::procresult(Result r, JSON&) { doComplete(r.errorOrOK(), this->action); return r.wasErrorOrOK(); } void CommandUpdatePendingContact::doComplete(error e, ipcactions_t actions) { if (!mCompletion) return client->app->updatepcr_result(e, actions); mCompletion(e, actions); } CommandEnumerateQuotaItems::CommandEnumerateQuotaItems( const std::optional& countryCode, MegaClient* client) { cmd("utqa"); arg("nf", 5); arg("b", 1); // support for Business accounts arg("p", 1); // support for Pro Flexi arg("ft", 1); // support for Feature plans if (countryCode && !countryCode->empty()) { arg("c", countryCode->c_str()); // Support for forcing the currency of a specific country } tag = client->reqtag; } bool CommandEnumerateQuotaItems::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->enumeratequotaitems_result(r.errorOrOK()); return true; } string currency; // common for all plans, populated from `l` object while (json.enterobject()) { handle product = UNDEF; int prolevel = -1, gbstorage = -1, gbtransfer = -1, months = -1, type = -1; double amount = 0.0, amountMonth = 0.0, localPrice = 0.0; unsigned int testCategory = CommandEnumerateQuotaItems::INVALID_TEST_CATEGORY; // Bitmap. Bit 0 set (int value 1) is standard plan, other bits are defined by API. unsigned int trialDays = CommandEnumerateQuotaItems::NO_TRIAL_DAYS; string description; map features; string ios_id; string android_id; double priceNet{0.0}; double localPriceNet{0.0}; double monthlyBasePriceNet{0.0}; unique_ptr bizPlan; unique_ptr currencyData; std::optional mobileOffer; std::optional instantDiscounts; bool finished = false; bool readingL = false; const char* buf = nullptr; while (!finished) { buf = nullptr; switch (json.getnameid()) { case makeNameid("l"): // currency localization { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `l` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } currencyData = std::make_unique(); readingL = true; while (!finished) { buf = nullptr; switch(json.getnameid()) { case makeNameid("c"): // currency, ie. EUR buf = json.getvalue(); JSON::copystring(¤cyData->currencyName, buf); currency = currencyData->currencyName; break; case makeNameid("cs"): // currency symbol, ie. € buf = json.getvalue(); JSON::copystring(¤cyData->currencySymbol, buf); currencyData->currencySymbol = Base64::atob(currencyData->currencySymbol); break; case makeNameid("lc"): // local currency, ie. NZD buf = json.getvalue(); JSON::copystring(¤cyData->localCurrencyName, buf); break; case makeNameid("lcs"): // local currency symbol, ie. $ buf = json.getvalue(); JSON::copystring(¤cyData->localCurrencySymbol, buf); break; case EOO: // sanity checks for received data if (currencyData->currencyName.empty() || currencyData->currencySymbol.empty()) { LOG_err << "Failed to parse Enumerate-quota-items response, `l` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return true; } finished = true; // exits from the outer loop too json.leaveobject(); // 'l' object break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, store `l` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } break; } case makeNameid("it"): // 0 -> for all Pro level plans; 1 -> for Business plan; // 2 -> for Feature plan type = static_cast(json.getint()); break; // case makeNameid("ib"): // for "it":1 (business plans), 0 -> Pro Flexi; 1 -> Business plan // { // bool isProFlexi = json.getbool(); // } // break; case makeNameid("id"): product = json.gethandle(8); break; case makeNameid("al"): prolevel = static_cast(json.getint()); break; case makeNameid("s"): gbstorage = static_cast(json.getint()); break; case makeNameid("t"): gbtransfer = static_cast(json.getint()); break; case makeNameid("m"): months = static_cast(json.getint()); break; case makeNameid("p"): // price (in cents) amount = json.getfloat(); break; case makeNameid("d"): buf = json.getvalue(); JSON::copystring(&description, buf); break; case makeNameid("f"): // e.g. "f": { "vpn": 1 } { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, enter `f` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } string key, value; while (json.storeKeyValueFromObject(key, value)) { features[key] = static_cast(std::stoul(value)); } if (!json.leaveobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, leave `f` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } case makeNameid("ios"): buf = json.getvalue(); JSON::copystring(&ios_id, buf); break; case makeNameid("google"): buf = json.getvalue(); JSON::copystring(&android_id, buf); break; case makeNameid("lpn"): // Local price net in local currency (without tax) localPriceNet = json.getfloat(); break; case makeNameid("pn"): // Price net (without tax) priceNet = json.getfloat(); break; case makeNameid("mbpn"): // Monthly base price net (without tax) monthlyBasePriceNet = json.getfloat(); break; case makeNameid("mbp"): // monthly price (in cents) amountMonth = json.getfloat(); break; case makeNameid("lp"): // local price (in cents) localPrice = json.getfloat(); break; case makeNameid("bd"): // BusinessPlan { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `bd` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } bizPlan = std::make_unique(); bool readingBd = true; while (readingBd) { switch (json.getnameid()) { case makeNameid("ba"): // base (-1 means unlimited storage or transfer) { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `ba` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } bool readingBa = true; while (readingBa) { switch (json.getnameid()) { case makeNameid("s"): bizPlan->gbStoragePerUser = static_cast(json.getint()); break; case makeNameid("t"): bizPlan->gbTransferPerUser = static_cast(json.getint()); break; case EOO: readingBa = false; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `ba` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } json.leaveobject(); break; } case makeNameid("us"): // price per user { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `us` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } bool readingUs = true; while (readingUs) { switch (json.getnameid()) { case makeNameid("p"): bizPlan->pricePerUser = static_cast(json.getint()); break; case makeNameid("lp"): bizPlan->localPricePerUser = static_cast(json.getint()); break; case EOO: readingUs = false; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `us` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } json.leaveobject(); break; } case makeNameid("sto"): // storage block { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `sto` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } bool readingSto = true; while (readingSto) { switch (json.getnameid()) { case makeNameid("s"): bizPlan->gbPerStorage = static_cast(json.getint()); break; case makeNameid("p"): bizPlan->pricePerStorage = static_cast(json.getint()); break; case makeNameid("lp"): bizPlan->localPricePerStorage = static_cast(json.getint()); break; case EOO: readingSto = false; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `sto` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } json.leaveobject(); break; } case makeNameid("trns"): // transfer block { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `trns` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } bool readingTrns = true; while (readingTrns) { switch (json.getnameid()) { case makeNameid("t"): bizPlan->gbPerTransfer = static_cast(json.getint()); break; case makeNameid("p"): bizPlan->pricePerTransfer = static_cast(json.getint()); break; case makeNameid("lp"): bizPlan->localPricePerTransfer = static_cast(json.getint()); break; case EOO: readingTrns = false; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `sto` data"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } json.leaveobject(); break; } case makeNameid("minu"): // minimum number of user required to purchase bizPlan->minUsers = json.getuint32(); break; case EOO: readingBd = false; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response, `bd` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } json.leaveobject(); break; } case makeNameid("tc"): testCategory = json.getuint32(); break; case makeNameid("trial"): { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "entering `trials` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } [[maybe_unused]] string key = json.getname(); assert(key == "days"); trialDays = json.getuint32(); if (!json.leaveobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "leaving `trials` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } } break; case makeNameid("mo"): // mobile offer { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "entering `mobile offer` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } MobileOffer temporalMobileOffer; bool readingMo{true}; while (readingMo) { switch (json.getnameid()) { case makeNameid("id"): temporalMobileOffer.id = json.getname(); break; case makeNameid("uat"): temporalMobileOffer.uat = json.getbool(); break; case EOO: readingMo = false; mobileOffer = std::move(temporalMobileOffer); break; default: if (!json.storeobject()) { LOG_err << "Failed to parse mobile offer sub objects"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } if (!json.leaveobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "leaving `mobile offer` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } case makeNameid("insdis"): { if (!json.enterobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "entering `Instant Discounts` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } InstantDiscounts tempInsdis; bool readingInsDis{true}; while (readingInsDis) { switch (json.getnameid()) { case makeNameid("dc"): json.storeobject(&tempInsdis.discountCode); break; case makeNameid("dn"): json.storeobject(&tempInsdis.discountName); break; case makeNameid("dp"): tempInsdis.discountPercentage = json.getint32(); break; case makeNameid("dg"): tempInsdis.discountGroup = json.getint32(); break; case makeNameid("dm"): tempInsdis.discountMonths = json.getint32(); break; case EOO: readingInsDis = false; instantDiscounts = std::move(tempInsdis); break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Instant Discounts sub objects"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } if (!json.leaveobject()) { LOG_err << "Failed to parse Enumerate-quota-items response," << "leaving `Instant Discounts` object"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } case EOO: if (type < 0 || ISUNDEF(product) || (prolevel < 0) || (months < 0) || currency.empty() || description.empty() || testCategory == CommandEnumerateQuotaItems::INVALID_TEST_CATEGORY // only available for Pro plans, not for Business || (!type && gbstorage < 0) || (!type && gbtransfer < 0) || (!type && amount == 0.0) || (!type && amountMonth == 0.0) || (!type && ios_id.empty()) || (!type && android_id.empty()) // only available for Business plan(s) || (type == 1 && !bizPlan)) { client->app->enumeratequotaitems_result(API_EINTERNAL); return true; } finished = true; break; default: if (!json.storeobject()) { LOG_err << "Failed to parse Enumerate-quota-items response"; client->app->enumeratequotaitems_result(API_EINTERNAL); return false; } break; } } // end while(!finished) json.leaveobject(); if (readingL) { // just read currency data, keep reading objects for each pro/business plan readingL = false; client->app->enumeratequotaitems_result(std::move(currencyData)); continue; } else { const Product productData = {static_cast(type), product, static_cast(prolevel), gbstorage, gbtransfer, static_cast(months), amount, amountMonth, localPrice, priceNet, localPriceNet, monthlyBasePriceNet, description.c_str(), std::move(features), ios_id.c_str(), android_id.c_str(), testCategory, std::move(bizPlan), trialDays, std::move(mobileOffer), std::move(instantDiscounts)}; client->app->enumeratequotaitems_result(productData); } } client->app->enumeratequotaitems_result(API_OK); return true; } CommandPurchaseAddItem::CommandPurchaseAddItem(MegaClient* client, int itemclass, handle item, unsigned price, const char* currency, unsigned /*tax*/, const char* /*country*/, handle lph, int phtype, int64_t ts) { string sprice; sprice.resize(128); snprintf(const_cast(sprice.data()), sprice.length(), "%.2f", price/100.0); replace( sprice.begin(), sprice.end(), ',', '.'); cmd("uts"); arg("it", itemclass); arg("si", (byte*)&item, 8); arg("p", sprice.c_str()); arg("c", currency); if (!ISUNDEF(lph)) { if (phtype == 0) // legacy mode { arg("aff", (byte*)&lph, MegaClient::NODEHANDLE); } else { beginobject("aff"); arg("id", (byte*)&lph, MegaClient::NODEHANDLE); arg("ts", ts); arg("t", phtype); // 1=affiliate id, 2=file/folder link, 3=chat link, 4=contact link endobject(); } } tag = client->reqtag; //TODO: Complete this (tax? country?) } bool CommandPurchaseAddItem::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->additem_result(r.errorOrOK()); return true; } handle item = json.gethandle(8); if (item != UNDEF) { client->purchase_basket.push_back(item); client->app->additem_result(API_OK); return true; } else { json.storeobject(); client->app->additem_result(API_EINTERNAL); return false; } } CommandPurchaseCheckout::CommandPurchaseCheckout(MegaClient* client, int gateway) { cmd("utc"); beginarray("s"); for (handle_vector::iterator it = client->purchase_basket.begin(); it != client->purchase_basket.end(); it++) { element((byte*)&*it, sizeof(handle)); } endarray(); arg("m", gateway); // empty basket client->purchase_begin(); tag = client->reqtag; } bool CommandPurchaseCheckout::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->checkout_result(NULL, r.errorOrOK()); return true; } //Expected response: "EUR":{"res":X,"code":Y}} json.getnameid(); if (!json.enterobject()) { LOG_err << "Parse error (CommandPurchaseCheckout)"; client->app->checkout_result(NULL, API_EINTERNAL); return false; } string errortype; Error e; for (;;) { switch (json.getnameid()) { case makeNameid("res"): if (json.isnumeric()) { e = (error)json.getint(); } else { json.storeobject(&errortype); if (errortype == "S") { errortype.clear(); e = API_OK; } } break; case makeNameid("code"): if (json.isnumeric()) { e = (error)json.getint(); } else { LOG_err << "Parse error in CommandPurchaseCheckout (code)"; } break; case EOO: json.leaveobject(); if (!errortype.size() || errortype == "FI" || e == API_OK) { client->app->checkout_result(NULL, e); } else { client->app->checkout_result(errortype.c_str(), e); } return true; default: if (!json.storeobject()) { client->app->checkout_result(NULL, API_EINTERNAL); return false; } } } } CommandRemoveContact::CommandRemoveContact(MegaClient* client, const char* m, visibility_t show, Completion completion) { mSeqtagArray = true; this->email = m ? m : ""; this->v = show; cmd("ur2"); arg("u", m); arg("l", (int)show); tag = client->reqtag; // Assume we've been given a completion function. mCompletion = std::move(completion); } bool CommandRemoveContact::procresult(Result r, JSON&) { assert(r.hasJsonObject() || r.wasStrictlyError()); if (r.hasJsonObject()) { // the object contains (userhandle + email string) - caller will leaveobject() automatically if (User *u = client->finduser(email.c_str())) { u->show = v; } doComplete(API_OK); return true; } doComplete(r.errorOrOK()); return r.wasErrorOrOK(); } void CommandRemoveContact::doComplete(error e) { if (!mCompletion) return client->app->removecontact_result(e); mCompletion(e); } CommandPutMultipleUAVer::CommandPutMultipleUAVer(MegaClient *client, const userattr_map *attrs, int ctag, std::function completion) { mSeqtagArray = true; this->attrs = *attrs; mCompletion = completion ? std::move(completion) : [this](Error e) { this->client->app->putua_result(e); }; cmd("upv"); for (userattr_map::const_iterator it = attrs->begin(); it != attrs->end(); it++) { attr_t type = it->first; beginarray(User::attr2string(type).c_str()); element((const byte*)it->second.data(), it->second.size()); const UserAttribute* attribute = client->ownuser()->getAttribute(type); if (attribute && !attribute->version().empty()) { element(attribute->version().c_str()); } endarray(); } tag = ctag; } bool CommandPutMultipleUAVer::procresult(Result r, JSON& json) { if (r.hasJsonObject()) { User *u = client->ownuser(); for(;;) // while there are more attrs to read... { if (*json.pos == '}') { client->notifyuser(u); mCompletion(API_OK); return true; } string key, value; if (!json.storeKeyValueFromObject(key, value)) { break; } attr_t type = User::string2attr(key.c_str()); userattr_map::iterator it = this->attrs.find(type); if (type == ATTR_UNKNOWN || value.empty() || (it == this->attrs.end())) { LOG_err << "Error in CommandPutMultipleUAVer. Undefined attribute or version: " << key; for (auto a : this->attrs) { LOG_err << " expected one of: " << User::attr2string(a.first); } break; } else { u->setAttribute(type, it->second, value); u->setTag(tag ? tag : -1); if (type == ATTR_KEYRING) { if (unique_ptr records{ tlv::containerToRecords(attrs[type], client->key)}) { string prEd255{std::move((*records)[EdDSA::TLV_KEY])}; if (prEd255.size() == EdDSA::SEED_KEY_LENGTH) { client->mEd255Key = new EdDSA(client->rng, (unsigned char*)prEd255.data()); } string prCu255{std::move((*records)[ECDH::TLV_KEY])}; if (prCu255.size() == ECDH::PRIVATE_KEY_LENGTH) { client->mX255Key = new ECDH(prCu255); } if (!client->mX255Key || !client->mX255Key->initializationOK || !client->mEd255Key || !client->mEd255Key->initializationOK) { client->resetKeyring(); client->sendevent(99418, "Failed to load attached keys", 0); } else { client->sendevent(99420, "Signing and chat keys attached OK", 0); } } else { LOG_warn << "Failed to decrypt keyring after putua"; } } else if (type == ATTR_KEYS) { if (!client->mKeyManager.fromKeysContainer(it->second)) { LOG_err << "Error processing new established value for the Key Manager (CommandPutMultipleUAVer)"; // We can't use a previous value here because CommandPutMultipleUAVer is only used to update ^!keys // during initialization } } } } } else if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK()); return true; } mCompletion(API_EINTERNAL); return false; } CommandPutUAVer::CommandPutUAVer(MegaClient* client, attr_t at, const byte* av, unsigned avl, int ctag, std::function completion) { mSeqtagArray = true; this->at = at; this->av.assign((const char*)av, avl); mCompletion = completion ? std::move(completion) : [this](Error e) { this->client->app->putua_result(e); }; cmd("upv"); beginarray(User::attr2string(at).c_str()); // if removing avatar, do not Base64 encode the attribute value if (at == ATTR_AVATAR && !strcmp((const char *)av, "none")) { element((const char*)av); } else { element(av, avl); } const UserAttribute* attribute = client->ownuser()->getAttribute(at); if (attribute && attribute->isValid() && !attribute->version().empty()) { element(attribute->version().c_str()); } endarray(); tag = ctag; } bool CommandPutUAVer::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { if (r.wasError(API_EEXPIRED)) { User *u = client->ownuser(); u->setAttributeExpired(at); } mCompletion(r.errorOrOK()); } else { const char* ptr; const char* end; ptr = json.getvalue(); if (!ptr) { mCompletion(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletion(API_EINTERNAL); return false; } attr_t attributeType = User::string2attr(string(ptr, static_cast(end - ptr)).c_str()); ptr = json.getvalue(); if (!ptr) { mCompletion(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletion(API_EINTERNAL); return false; } string v = string(ptr, static_cast(end - ptr)); if (attributeType == ATTR_UNKNOWN || v.empty() || (at != attributeType)) { LOG_err << "Error in CommandPutUAVer. Undefined attribute or version"; mCompletion(API_EINTERNAL); return false; } else { User *u = client->ownuser(); if (attributeType == ATTR_KEYS && !client->mKeyManager.fromKeysContainer(av)) { LOG_err << "Error processing new established value for the Key Manager"; // if there's a previous version, better keep that value in cache const UserAttribute* attribute = client->ownuser()->getAttribute(attributeType); if (attribute && !attribute->isNotExisting() && !attribute->version().empty()) { LOG_warn << "Replacing ^!keys value by previous version " << attribute->version() << ", current: " << v; assert(!attribute->value().empty()); av = attribute->value(); } } u->setAttribute(attributeType, av, v); u->setTag(tag ? tag : -1); if (attributeType == ATTR_UNSHAREABLE_KEY) { LOG_info << "Unshareable key successfully created"; client->unshareablekey.swap(av); } #ifdef ENABLE_SYNC else if (attributeType == ATTR_SYNC_DESIRED_STATE) { const std::unique_ptr records{tlv::containerToRecords(av, client->key)}; client->syncs.setSdsBackupsFullSync(records); } #endif client->notifyuser(u); mCompletion(API_OK); } } return true; } CommandPutUA::CommandPutUA(MegaClient* /*client*/, attr_t at, const byte* av, unsigned avl, int ctag, handle lph, int phtype, int64_t ts, std::function completion) { mSeqtagArray = true; this->at = at; this->av.assign((const char*)av, avl); mCompletion = completion ? std::move(completion) : [this](Error e){ client->app->putua_result(e); }; cmd("up2"); string an = User::attr2string(at); // if removing avatar, do not Base64 encode the attribute value if (at == ATTR_AVATAR && !strcmp((const char *)av, "none")) { arg(an.c_str(), (const char*)av, static_cast(avl)); } else { arg(an.c_str(), av, avl); } if (!ISUNDEF(lph)) { beginobject("aff"); arg("id", (byte*)&lph, MegaClient::NODEHANDLE); arg("ts", ts); arg("t", phtype); // 1=affiliate id, 2=file/folder link, 3=chat link, 4=contact link endobject(); } tag = ctag; } bool CommandPutUA::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK()); } else { const char* ptr; const char* end; ptr = json.getvalue(); if (!ptr) { mCompletion(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletion(API_EINTERNAL); return false; } attr_t attributeType = User::string2attr(string(ptr, static_cast(end - ptr)).c_str()); ptr = json.getvalue(); if (!ptr) { mCompletion(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletion(API_EINTERNAL); return false; } string v = string(ptr, static_cast(end - ptr)); if (attributeType == ATTR_UNKNOWN || v.empty() || (at != attributeType)) { LOG_err << "Error in CommandPutUA. Undefined attribute or version"; mCompletion(API_EINTERNAL); return false; } User *u = client->ownuser(); assert(u); if (!u) { LOG_err << "Own user not found when attempting to set user attributes"; mCompletion(API_EACCESS); return true; } u->setAttribute(attributeType, av, v); u->setTag(tag ? tag : -1); client->notifyuser(u); if (attributeType == ATTR_DISABLE_VERSIONS) { client->versions_disabled = (av == "1"); if (client->versions_disabled) { LOG_info << "File versioning is disabled"; } else { LOG_info << "File versioning is enabled"; } } else if (attributeType == ATTR_NO_CALLKIT) { LOG_info << "CallKit is " << ((av == "1") ? "disabled" : "enabled"); } mCompletion(API_OK); } return true; } CommandGetUA::CommandGetUA(MegaClient* /*client*/, const char* uid, attr_t at, const char* ph, int ctag, CompletionErr completionErr, CompletionBytes completionBytes, CompletionTLV completionTLV) { this->uid = uid; this->at = at; this->ph = ph ? string(ph) : ""; mCompletionErr = completionErr ? std::move(completionErr) : [this](error e) { client->app->getua_result(e); }; mCompletionBytes = completionBytes ? std::move(completionBytes) : [this](byte* b, unsigned l, attr_t e) { client->app->getua_result(b, l, e); }; mCompletionTLV = completionTLV ? std::move(completionTLV) : [this](unique_ptr t, attr_t e) { client->app->getua_result(std::move(t), e); }; if (ph && ph[0]) { cmd("mcuga"); arg("ph", ph); } else { cmd("uga"); } arg("u", uid); arg("ua", User::attr2string(at).c_str()); arg("v", 1); tag = ctag; } bool CommandGetUA::procresult(Result r, JSON& json) { User *u = client->finduser(uid.c_str()); if (r.wasErrorOrOK()) { if (r.wasError(API_ENOENT) && u) { u->removeAttribute(at); } mCompletionErr(r.errorOrOK()); if (isFromChatPreview()) // if `mcuga` was sent, no need to do anything else { return true; } if (u && !u->isTemporary && u->userhandle != client->me && r.wasError(API_ENOENT)) { if (at == ATTR_ED25519_PUBK || at == ATTR_CU25519_PUBK) { LOG_warn << "Missing public key " << User::attr2string(at) << " for user " << u->uid; attr_t authringType = AuthRing::keyTypeToAuthringType(at); auto it = client->mAuthRingsTemp.find(authringType); bool temporalAuthring = it != client->mAuthRingsTemp.end(); if (temporalAuthring) { client->updateAuthring(&it->second, authringType, true, u->userhandle); } } else if (at == ATTR_SIG_CU255_PUBK) { LOG_warn << "Missing signature " << User::attr2string(at) << " for user " << u->uid; attr_t authringType = AuthRing::signatureTypeToAuthringType(at); auto it = client->mAuthRingsTemp.find(authringType); bool temporalAuthring = it != client->mAuthRingsTemp.end(); if (temporalAuthring) { client->updateAuthring(&it->second, authringType, true, u->userhandle); } } } // if the attr does not exist, initialize it if (at == ATTR_DISABLE_VERSIONS && r.wasError(API_ENOENT)) { LOG_info << "File versioning is enabled"; client->versions_disabled = false; } else if (at == ATTR_NO_CALLKIT && r.wasError(API_ENOENT)) { LOG_info << "CallKit is enabled"; } return true; } else { const char* ptr; const char* end; string value, version, buf; for (;;) { switch (json.getnameid()) { case makeNameid("av"): { ptr = json.getvalue(); if (!ptr) { mCompletionErr(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletionErr(API_EINTERNAL); return false; } buf.assign(ptr, static_cast(end - ptr)); break; } case makeNameid("v"): { ptr = json.getvalue(); if (!ptr) { mCompletionErr(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { mCompletionErr(API_EINTERNAL); return false; } version.assign(ptr, static_cast(end - ptr)); break; } case EOO: { // if there's no avatar, the value is "none" (not Base64 encoded) if (u && at == ATTR_AVATAR && buf == "none") { u->updateAttributeIfDifferentVersion(ATTR_AVATAR, buf, // actual value will be ignored version); u->setTag(tag ? tag : -1); mCompletionErr(API_ENOENT); client->notifyuser(u); return true; } // convert from ASCII to binary the received data value.resize(buf.size() / 4 * 3 + 3); value.resize(static_cast( Base64::atob(buf.data(), (byte*)value.data(), int(value.size())))); // Some attributes don't keep historic records, ie. *!authring or *!lstint // (none of those attributes are used by the SDK yet) // bool nonHistoric = (attributename.at(1) == '!'); if (!u) // retrieval of attributes without contact-relationship { if (at == ATTR_AVATAR && buf == "none") { mCompletionErr(API_ENOENT); } else { mCompletionBytes((byte*) value.data(), unsigned(value.size()), at); } return true; } // handle attribute data depending on the scope switch (User::scope(at)) { case ATTR_SCOPE_PRIVATE_ENCRYPTED: { // decrypt the data std::unique_ptr records{ tlv::containerToRecords(value, client->key)}; if (records || (value.empty() && !version.empty())) { #ifdef ENABLE_SYNC if (at == ATTR_SYNC_DESIRED_STATE) { client->syncs.setSdsBackupsFullSync(records); } #endif u->updateAttributeIfDifferentVersion(at, value, version); mCompletionTLV(std::move(records), at); } else { LOG_err << "Cannot extract TLV records for private attribute " << User::attr2string(at); if (at == ATTR_JSON_SYNC_CONFIG_DATA) { // Store the attribute so we can update it later with valid // values u->updateAttributeIfDifferentVersion(at, value, version); mCompletionErr(API_EKEY); } else { mCompletionErr(API_EINTERNAL); } return true; } break; } case ATTR_SCOPE_PUBLIC_UNENCRYPTED: { u->updateAttributeIfDifferentVersion(at, value, version); mCompletionBytes((byte*) value.data(), unsigned(value.size()), at); if (!u->isTemporary && u->userhandle != client->me) { if (at == ATTR_ED25519_PUBK || at == ATTR_CU25519_PUBK) { client->trackKey(at, u->userhandle, value); } else if (at == ATTR_SIG_CU255_PUBK) { client->trackSignature(at, u->userhandle, value); } } break; } case ATTR_SCOPE_PROTECTED_UNENCRYPTED: { u->updateAttributeIfDifferentVersion(at, value, version); mCompletionBytes((byte*) value.data(), unsigned(value.size()), at); break; } case ATTR_SCOPE_PRIVATE_UNENCRYPTED: { if (at == ATTR_KEYS && !client->mKeyManager.fromKeysContainer(value)) { LOG_err << "Error processing new established value for the Key Manager upon init"; // if there's a previous version, better keep that value in cache const UserAttribute* attribute = client->ownuser()->getAttribute(at); if (attribute && !attribute->isNotExisting() && !attribute->version().empty()) { LOG_warn << "Replacing ^!keys value by previous version " << attribute->version() << " current: " << version; assert(!attribute->value().empty()); value = attribute->value(); } } // store the value in cache in binary format u->updateAttributeIfDifferentVersion(at, value, version); mCompletionBytes((byte*) value.data(), unsigned(value.size()), at); if (at == ATTR_DISABLE_VERSIONS) { client->versions_disabled = !strcmp(value.data(), "1"); if (client->versions_disabled) { LOG_info << "File versioning is disabled"; } else { LOG_info << "File versioning is enabled"; } } else if (at == ATTR_NO_CALLKIT) { LOG_info << "CallKit is " << ((!strcmp(value.data(), "1")) ? "disabled" : "enabled"); } else if (at == ATTR_STORAGE_STATE) { if (const auto storageStatus = getStorageStatusFromString(value); !client->processStorageStatusFromCmd(storageStatus)) { assert(false && "CommandGetUA: Storage status is unknown or invalid"); } } else if (at == ATTR_S4) { bool enabled = (value == "1"); LOG_info << "S4 is " << (enabled ? "enabled" : "disabled"); client->mIsS4Enabled.store(enabled); } else if (at == ATTR_S4_CONTAINER) { if (value.empty()) // it's been disabled { assert(client->mIsS4Enabled == false); client->mS4Container.store(NodeHandle()); } else { assert(client->mIsS4Enabled == true); assert(value.size() == MegaClient::NODEHANDLE); NodeHandle h = toNodeHandle(&value); client->mS4Container.store(h); } } break; } default: // legacy attributes without explicit scope or unknown attribute { LOG_err << "Unknown received attribute: " << User::attr2string(at); mCompletionErr(API_EINTERNAL); return true; } } // switch (scope) u->setTag(tag ? tag : -1); client->notifyuser(u); return true; } default: { if (!json.storeobject()) { LOG_err << "Error in CommandGetUA. Parse error"; mCompletionErr(API_EINTERNAL); return false; } } } // switch (nameid) } } #ifndef WIN32 return false; // unreachable code #endif } #ifdef DEBUG CommandDelUA::CommandDelUA(MegaClient *client, const char *an) { this->an = an; mSeqtagArray = true; cmd("upr"); arg("ua", an); arg("v", 1); // returns the new version for the (removed) null value tag = client->reqtag; } bool CommandDelUA::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->delua_result(r.errorOrOK()); } else { const char* ptr; const char* end; ptr = json.getvalue(); if (!ptr) { client->app->delua_result(API_EINTERNAL); return false; } end = strchr(ptr, '"'); if (!end) { client->app->delua_result(API_EINTERNAL); return false; } User *u = client->ownuser(); attr_t at = User::string2attr(an.c_str()); string version(ptr, static_cast(end - ptr)); // store version in order to avoid double users update from corresponding AP u->removeAttributeUpdateVersion(at, version); if (at == ATTR_KEYRING) { client->resetKeyring(); } client->notifyuser(u); client->app->delua_result(API_OK); } return true; } #endif // #ifdef DEBUG CommandSendDevCommand::CommandSendDevCommand(MegaClient* client, const char* command, const char* email, long long q, int bs, int us, const char* cp) { cmd("dev"); arg("aa", command); if (email) { arg("t", email); } if ((strcmp(command, "tq") == 0)) { arg("q", q); } else if ((strcmp(command, "bs") == 0)) { arg("s", bs); } else if ((strcmp(command, "us") == 0)) { arg("s", us); } else if ((strcmp(command, "abs") == 0)) { assert(cp); if (cp) arg("c", cp); arg("g", us); } else if (strcmp(command, "sal") == 0) { // Account level. arg("al", us); // Quota length in months. arg("m", q); } tag = client->reqtag; } bool CommandSendDevCommand::procresult(Result r, JSON&) { client->app->senddevcommand_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandGetUserEmail::CommandGetUserEmail(MegaClient *client, const char *uid) { mSeqtagArray = true; cmd("uge"); arg("u", uid); tag = client->reqtag; } bool CommandGetUserEmail::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { string email; if (json.storeobject(&email)) { client->app->getuseremail_result(&email, API_OK); return true; } } else if (r.wasErrorOrOK()) { assert(r.wasStrictlyError()); client->app->getuseremail_result(NULL, r.errorOrOK()); return true; } client->app->getuseremail_result(NULL, API_EINTERNAL); return false; } // set node keys (e.g. to convert asymmetric keys to symmetric ones) CommandNodeKeyUpdate::CommandNodeKeyUpdate(MegaClient* client, handle_vector* v) { byte nodekey[FILENODEKEYLENGTH]; cmd("k"); beginarray("nk"); for (size_t i = v->size(); i--;) { handle h = (*v)[i]; shared_ptr n; if ((n = client->nodebyhandle(h))) { client->key.ecb_encrypt((byte*)n->nodekey().data(), nodekey, n->nodekey().size()); assert(!n->hasZeroKey()); element(h, MegaClient::NODEHANDLE); element(nodekey, n->nodekey().size()); } } endarray(); } CommandSingleKeyCR::CommandSingleKeyCR(handle sh, handle nh, const byte* key, size_t keylen) { cmd("k"); beginarray("cr"); beginarray(); element(sh, MegaClient::NODEHANDLE); endarray(); beginarray(); element(nh, MegaClient::NODEHANDLE); endarray(); beginarray(); element(0); element(0); element(key, keylen); endarray(); endarray(); } CommandKeyCR::CommandKeyCR(MegaClient* /*client*/, sharedNode_vector* rshares, sharedNode_vector* rnodes, const char* keys) { cmd("k"); beginarray("cr"); beginarray(); for (int i = 0; i < (int)rshares->size(); i++) { element((*rshares)[static_cast(i)]->nodehandle, MegaClient::NODEHANDLE); } endarray(); beginarray(); for (int i = 0; i < (int)rnodes->size(); i++) { element((*rnodes)[static_cast(i)]->nodehandle, MegaClient::NODEHANDLE); } endarray(); beginarray(); appendraw(keys); endarray(); endarray(); } // a == ACCESS_UNKNOWN: request public key for user handle and respond with // share key for sn // otherwise: request public key for user handle and continue share creation // for node sn to user u with access a CommandPubKeyRequest::CommandPubKeyRequest(MegaClient* client, User* user) { cmd("uk"); arg("u", user->uid.c_str()); u = user; tag = client->reqtag; } bool CommandPubKeyRequest::procresult(Result r, JSON& json) { byte pubkbuf[AsymmCipher::MAXKEYLENGTH]; int len_pubk = 0; handle uh = UNDEF; unique_ptr cleanup(u && u->isTemporary ? u : nullptr); if (r.wasErrorOrOK()) { if (!r.wasError(API_ENOENT)) //API_ENOENT = unregistered users or accounts without a public key yet { LOG_err << "Unexpected error in CommandPubKeyRequest: " << error(r.errorOrOK()); } } else { bool finished = false; while (!finished) { switch (json.getnameid()) { case makeNameid("u"): uh = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("pubk"): len_pubk = json.storebinary(pubkbuf, sizeof pubkbuf); break; case EOO: if (!u) // user has cancelled the account { return true; } if (!ISUNDEF(uh)) { client->mapuser(uh, u->email.c_str()); if (u->isTemporary && u->uid == u->email) //update uid with the received USERHANDLE (will be used as target for putnodes) { u->uid = Base64Str(uh); } } if (len_pubk && !u->pubk.setkey(AsymmCipher::PUBKEY, pubkbuf, len_pubk)) { len_pubk = 0; } finished = true; break; default: if (json.storeobject()) { continue; } len_pubk = 0; finished = true; break; } } } if (!u) // user has cancelled the account, or HIDDEN user was removed { return true; } // satisfy all pending PubKeyAction requests for this user while (u->pkrs.size()) { client->restag = tag; u->pkrs[0]->proc(client, u); u->pkrs.pop_front(); } if (len_pubk && !u->isTemporary) { client->notifyuser(u); } return true; } void CommandPubKeyRequest::invalidateUser() { u = NULL; } CommandGetUserData::CommandGetUserData( MegaClient*, int tag, std::function&& discountCodes, error)> completion) { cmd("ug"); arg("v", 1); this->tag = tag; mCompletion = completion ? std::move(completion) : [this](string* name, string* pubk, string* privk, std::vector&& discountCodes, error e) { this->client->app->userdata_result(name, pubk, privk, std::move(discountCodes), e); }; } bool CommandGetUserData::updatePrivateEncryptedUserAttribute(User* u, const std::string& value, const std::string& version, mega::attr_t at) { // Empty private encrypted attribute with empty version means removal; non-empty version // indicates it was explicitly emptied. if (!version.empty()) { if (value.empty() || tlv::containerToRecords(value, client->key)) // validation { return u->updateAttributeIfDifferentVersion(at, value, version); } else { LOG_err << "Cannot extract TLV records for " << User::attr2string(at); } } else { u->removeAttribute(at); } return false; } bool CommandGetUserData::procresult(Result r, JSON& json) { string name; string pubk; string privk; string k; byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; int len_privk = 0; byte pubkbuf[AsymmCipher::MAXKEYLENGTH]; int len_pubk = 0; m_time_t since = 0; int v = 0; string salt; string smsv; string lastname; string versionLastname; string firstname; string versionFirstname; string language; string versionLanguage; string pwdReminderDialog; string versionPwdReminderDialog; string pushSetting; string versionPushSetting; string contactLinkVerification; string versionContactLinkVerification; #ifndef NDEBUG handle me = UNDEF; #endif string chatFolder; string versionChatFolder; string cameraUploadFolder; string versionCameraUploadFolder; string aliases; string versionAliases; string disableVersions; string versionDisableVersions; string noCallKit; string versionNoCallKit; string country; string versionCountry; string birthday; string versionBirthday; string birthmonth; string versionBirthmonth; string birthyear; string versionBirthyear; string email; string unshareableKey; string versionUnshareableKey; string deviceNames; string versionDeviceNames; string versionDriveNames; string myBackupsFolder; string versionMyBackupsFolder; string versionBackupNames; string cookieSettings; string versionCookieSettings; string appPrefs; string versionAppPrefs; string ccPrefs; string versionCcPrefs; string enabledTestNotifications, versionEnabledTestNotifications; string lastReadNotification, versionLastReadNotification; string lastActionedBanner, versionLastActionedBanner; string enabledTestSurveys, versionEnabledTestSurveys; #ifdef ENABLE_SYNC string jsonSyncConfigData; string jsonSyncConfigDataVersion; #endif string keys, keysVersion; string keyring, versionKeyring; string pubEd255, versionPubEd255; string pubCu255, versionPubCu255; string sigPubk, versionSigPubk; string sigCu255, versionSigCu255; string authringEd255, versionAuthringEd255; string authringCu255, versionAuthringCu255; string visibleWelcomeDialog; string versionVisibleWelcomeDialog; string visibleTermsOfService; string versionVisibleTermsOfService; string pwmh, pwmhVersion; vector notifs; std::string sds, sdsVersion; string s4, s4Version; string s4container, s4containerVersion; string devOpt, devOptVersion; string rcts, rctsVersion; bool uspw = false; string userStorageLevel, versionUserStorageLevel; vector warningTs; m_time_t deadlineTs = -1; bool b = false; BizMode m = BIZ_MODE_UNKNOWN; BizStatus s = BIZ_STATUS_UNKNOWN; std::set masters; std::vector> sts; std::vector dciList; if (r.wasErrorOrOK()) { mCompletion(NULL, NULL, NULL, {}, r.wasError(API_OK) ? Error(API_ENOENT) : r.errorOrOK()); return true; } for (;;) { string attributeName = json.getnameWithoutAdvance(); switch (json.getnameid()) { case makeNameid("aav"): // account authentication version v = (int)json.getint(); break; case makeNameid("aas"): // account authentication salt json.storeobject(&salt); break; case makeNameid("name"): json.storeobject(&name); break; case makeNameid("k"): // master key k.resize(SymmCipher::KEYLENGTH); json.storebinary((byte *)k.data(), int(k.size())); break; case makeNameid("since"): since = json.getint(); break; case makeNameid("pubk"): // RSA public key json.storeobject(&pubk); len_pubk = Base64::atob(pubk.c_str(), pubkbuf, sizeof pubkbuf); break; case makeNameid("privk"): // RSA private key (encrypted to MK) len_privk = json.storebinary(privkbuf, sizeof privkbuf); break; case makeNameid("flags"): if (json.enterobject()) { if (client->readmiscflags(&json) != API_OK) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } json.leaveobject(); } break; case makeNameid("na"): client->accountIsNew = bool(json.getint()); break; case makeNameid("u"): #ifndef NDEBUG me = #endif json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("lastname"): parseUserAttribute(json, lastname, versionLastname); break; case makeNameid("^!lang"): parseUserAttribute(json, language, versionLanguage); break; case makeNameid("birthday"): parseUserAttribute(json, birthday, versionBirthday); break; case makeNameid("country"): parseUserAttribute(json, country, versionCountry); break; case makeNameid("^!ps"): parseUserAttribute(json, pushSetting, versionPushSetting); break; case makeNameid("^!prd"): parseUserAttribute(json, pwdReminderDialog, versionPwdReminderDialog); break; case makeNameid("^clv"): parseUserAttribute(json, contactLinkVerification, versionContactLinkVerification); break; case makeNameid("^!dv"): parseUserAttribute(json, disableVersions, versionDisableVersions); break; case makeNameid("^!nokit"): parseUserAttribute(json, noCallKit, versionNoCallKit); break; case makeNameid("*!cf"): parseUserAttribute(json, chatFolder, versionChatFolder); break; case makeNameid("*!cam"): parseUserAttribute(json, cameraUploadFolder, versionCameraUploadFolder); break; case makeNameid("*!>alias"): parseUserAttribute(json, aliases, versionAliases); break; case makeNameid("email"): json.storeobject(&email); break; case makeNameid("*~usk"): parseUserAttribute(json, unshareableKey, versionUnshareableKey, false); break; case makeNameid("*!dn"): parseUserAttribute(json, deviceNames, versionDeviceNames); break; case makeNameid("^!bak"): parseUserAttribute(json, myBackupsFolder, versionMyBackupsFolder); break; case makeNameid("*!aPrefs"): parseUserAttribute(json, appPrefs, versionAppPrefs); break; case makeNameid("*!ccPref"): parseUserAttribute(json, ccPrefs, versionCcPrefs); break; #ifdef ENABLE_SYNC case makeNameid("*~jscd"): parseUserAttribute(json, jsonSyncConfigData, jsonSyncConfigDataVersion); break; #endif case makeNameid("^!keys"): parseUserAttribute(json, keys, keysVersion); break; case makeNameid("*keyring"): parseUserAttribute(json, keyring, versionKeyring); break; case makeNameid("+puEd255"): parseUserAttribute(json, pubEd255, versionPubEd255); break; case makeNameid("+puCu255"): parseUserAttribute(json, pubCu255, versionPubCu255); break; case makeNameid("+sigPubk"): parseUserAttribute(json, sigPubk, versionSigPubk); break; case makeNameid("^!devopt"): parseUserAttribute(json, devOpt, devOptVersion); break; case makeNameid("mkt"): if (json.enterobject()) { bool endobject = false; while (!endobject) { switch (json.getnameid()) { case makeNameid("dc"): if (!parseDiscountCodes(json, dciList)) { json.leaveobject(); mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } break; case EOO: if (!json.leaveobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } endobject = true; break; default: if (!json.storeobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } } } } break; case makeNameid("pf"): // Pro Flexi plan (similar to business) client->setProFlexi(true); [[fallthrough]]; case makeNameid("b"): // business account's info assert(!b); b = true; if (json.enterobject()) { bool endobject = false; while (!endobject) { switch (json.getnameid()) { case makeNameid("s"): // status // -1: expired, 1: active, 2: grace-period s = BizStatus(json.getint32()); break; case makeNameid("m"): // mode m = BizMode(json.getint32()); break; case makeNameid("mu"): if (json.enterarray()) { for (;;) { handle uh = json.gethandle(MegaClient::USERHANDLE); if (!ISUNDEF(uh)) { masters.emplace(uh); } else { break; } } json.leavearray(); } break; case makeNameid("sts"): // status timestamps // ie. "sts":[{"s":-1,"ts":1566182227},{"s":1,"ts":1563590227}] json.enterarray(); while (json.enterobject()) { BizStatus status = BIZ_STATUS_UNKNOWN; m_time_t ts = 0; bool exit = false; while (!exit) { switch (json.getnameid()) { case makeNameid("s"): status = BizStatus(json.getint()); break; case makeNameid("ts"): ts = json.getint(); break; case EOO: if (status != BIZ_STATUS_UNKNOWN && isValidTimeStamp(ts)) { sts.push_back(std::make_pair(status, ts)); } else { LOG_warn << "Unpaired/missing business status-ts in b.sts"; } exit = true; break; default: if (!json.storeobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); json.leavearray(); return false; } } } json.leaveobject(); } json.leavearray(); break; case EOO: endobject = true; break; default: if (!json.storeobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } } } json.leaveobject(); } break; case makeNameid("smsv"): // SMS verified phone number if (!json.storeobject(&smsv)) { LOG_err << "Invalid verified phone number (smsv)"; assert(false); } break; case makeNameid("uspw"): // user paywall data { uspw = true; if (json.enterobject()) { bool endobject = false; while (!endobject) { switch (json.getnameid()) { case makeNameid("dl"): // deadline timestamp deadlineTs = json.getint(); break; case makeNameid("wts"): // warning timestamps // ie. "wts":[1591803600,1591813600,1591823600 if (json.enterarray()) { m_time_t ts; while (json.isnumeric() && (ts = json.getint()) != -1) { warningTs.push_back(ts); } json.leavearray(); } break; case EOO: endobject = true; break; default: if (!json.storeobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } } } json.leaveobject(); } break; } case makeNameid("^!usl"): parseUserAttribute(json, userStorageLevel, versionUserStorageLevel); break; case makeNameid("^!csp"): parseUserAttribute(json, cookieSettings, versionCookieSettings); break; // case makeNameid("p"): // plan: 101 for Pro Flexi // { // int proPlan = json.getint32(); // } // break; case makeNameid("^!weldlg"): { parseUserAttribute(json, visibleWelcomeDialog, versionVisibleWelcomeDialog); break; } case makeNameid("^!tos"): { parseUserAttribute(json, visibleTermsOfService, versionVisibleTermsOfService); break; } case makeNameid("pwmh"): parseUserAttribute(json, pwmh, pwmhVersion); break; case makeNameid("notifs"): { if (json.enterarray()) { while (json.isnumeric()) { notifs.push_back(json.getuint32()); } json.leavearray(); } break; } case makeNameid("^!tnotif"): { parseUserAttribute(json, enabledTestNotifications, versionEnabledTestNotifications); break; } case makeNameid("^!lnotif"): { parseUserAttribute(json, lastReadNotification, versionLastReadNotification); break; } case makeNameid("^!lbannr"): { parseUserAttribute(json, lastActionedBanner, versionLastActionedBanner); break; } case makeNameid("^!tsur"): { parseUserAttribute(json, enabledTestSurveys, versionEnabledTestSurveys); break; } case makeNameid("*!sds"): { parseUserAttribute(json, sds, sdsVersion); break; } case makeNameid("s4"): { parseUserAttribute(json, s4, s4Version); break; } case makeNameid("s4c"): { parseUserAttribute(json, s4container, s4containerVersion); break; } case makeNameid("*!rcts"): { parseUserAttribute(json, rcts, rctsVersion); break; } case EOO: { assert(me == client->me); if (len_privk) { client->key.ecb_decrypt(privkbuf, static_cast(len_privk)); privk.resize(AsymmCipher::MAXKEYLENGTH * 2); privk.resize( Base64::btoa(privkbuf, static_cast(len_privk), (char*)privk.data())); // RSA private key should be already assigned at login assert(privk == client->mSerializedPrivateRsaKey); if (client->mSerializedPrivateRsaKey.empty()) { client->mSerializedPrivateRsaKey = privk; LOG_warn << "Private key not set by login, setting at `ug` response..."; if (!client->mPrivateRsaKey.setkey(AsymmCipher::PRIVKEY, privkbuf, len_privk)) { LOG_warn << "Error checking private key at `ug` response"; } } } if (len_pubk) { client->mPublicRsaKey.setkey(AsymmCipher::PUBKEY, pubkbuf, len_pubk); } if (v) { client->accountversion = v; } if (salt.size()) { Base64::atob(salt, client->accountsalt); } client->accountsince = since; client->mSmsVerifiedPhone = smsv; client->k = k; client->btugexpiration.backoff(MegaClient::USER_DATA_EXPIRATION_BACKOFF_SECS * 10); client->cachedug = true; // pre-load received user attributes into cache User* u = client->ownuser(); if (u) { bool changes = false; if (email.size()) { client->setEmail(u, email); } if (firstname.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_FIRSTNAME, firstname, versionFirstname); } if (lastname.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_LASTNAME, lastname, versionLastname); } if (language.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_LANGUAGE, language, versionLanguage); } else { u->removeAttribute(ATTR_LANGUAGE); } if (birthday.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_BIRTHDAY, birthday, versionBirthday); } else { u->removeAttribute(ATTR_BIRTHDAY); } if (birthmonth.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_BIRTHMONTH, birthmonth, versionBirthmonth); } else { u->removeAttribute(ATTR_BIRTHMONTH); } if (birthyear.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_BIRTHYEAR, birthyear, versionBirthyear); } else { u->removeAttribute(ATTR_BIRTHYEAR); } if (country.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_COUNTRY, country, versionCountry); } else { u->removeAttribute(ATTR_COUNTRY); } if (pwdReminderDialog.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_PWD_REMINDER, pwdReminderDialog, versionPwdReminderDialog); } else { u->removeAttribute(ATTR_PWD_REMINDER); } if (pushSetting.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_PUSH_SETTINGS, pushSetting, versionPushSetting); } else { u->removeAttribute(ATTR_PUSH_SETTINGS); } if (contactLinkVerification.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_CONTACT_LINK_VERIFICATION, contactLinkVerification, versionContactLinkVerification); } else { u->removeAttribute(ATTR_CONTACT_LINK_VERIFICATION); } if (disableVersions.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_DISABLE_VERSIONS, disableVersions, versionDisableVersions); // initialize the status of file-versioning for the client client->versions_disabled = (disableVersions == "1"); if (client->versions_disabled) { LOG_info << "File versioning is disabled"; } else { LOG_info << "File versioning is enabled"; } } else // attribute does not exists { LOG_info << "File versioning is enabled"; client->versions_disabled = false; u->removeAttribute(ATTR_DISABLE_VERSIONS); } if (noCallKit.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_NO_CALLKIT, noCallKit, versionNoCallKit); LOG_info << "CallKit is " << ((noCallKit == "1") ? "disabled" : "enabled"); } else { LOG_info << "CallKit is enabled [noCallKit.size() == 0]"; u->removeAttribute(ATTR_NO_CALLKIT); } changes |= updatePrivateEncryptedUserAttribute(u, deviceNames, versionDeviceNames, ATTR_DEVICE_NAMES); changes |= updatePrivateEncryptedUserAttribute(u, chatFolder, versionChatFolder, ATTR_MY_CHAT_FILES_FOLDER); changes |= updatePrivateEncryptedUserAttribute(u, cameraUploadFolder, versionCameraUploadFolder, ATTR_CAMERA_UPLOADS_FOLDER); changes |= updatePrivateEncryptedUserAttribute(u, appPrefs, versionAppPrefs, ATTR_APPS_PREFS); changes |= updatePrivateEncryptedUserAttribute(u, ccPrefs, versionCcPrefs, ATTR_CC_PREFS); #ifdef ENABLE_SYNC changes |= updatePrivateEncryptedUserAttribute(u, jsonSyncConfigData, jsonSyncConfigDataVersion, ATTR_JSON_SYNC_CONFIG_DATA); #endif // ENABLE_SYNC // `ug` response excludes alias if empty, even with a version. Use `uga` to // differentiate between removal and explicit emptying. if (aliases.size()) { if (tlv::containerToRecords(aliases, client->key)) // validation { changes |= u->updateAttributeIfDifferentVersion(ATTR_ALIAS, aliases, versionAliases); } else { LOG_err << "Cannot extract TLV records for ATTR_ALIAS"; } } if (!myBackupsFolder.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_MY_BACKUPS_FOLDER, myBackupsFolder, versionMyBackupsFolder); } else { u->removeAttribute(ATTR_MY_BACKUPS_FOLDER); } if (unshareableKey.size() == Base64Str::STRLEN) { changes |= u->updateAttributeIfDifferentVersion(ATTR_UNSHAREABLE_KEY, unshareableKey, versionUnshareableKey); client->unshareablekey.swap(unshareableKey); } else if (client->loggedin() == EPHEMERALACCOUNTPLUSPLUS) { // cannot configure CameraUploads, so it's not needed at this stage. // It will be created when the account gets confirmed. // (motivation: speed up the E++ account's setup) LOG_info << "Skip creation of unshareable key for E++ account"; } else if (unshareableKey.empty()) // it has not been created yet { LOG_info << "Creating unshareable key..."; byte newunshareablekey[SymmCipher::BLOCKSIZE]; client->rng.genblock(newunshareablekey, sizeof(newunshareablekey)); client->putua(ATTR_UNSHAREABLE_KEY, newunshareablekey, sizeof(newunshareablekey), 0); } else { LOG_err << "Unshareable key wrong length"; } if (!cookieSettings.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_COOKIE_SETTINGS, cookieSettings, versionCookieSettings); } else { u->removeAttribute(ATTR_COOKIE_SETTINGS); } client->setEnabledNotifications(std::move(notifs)); if (!enabledTestNotifications.empty() || !versionEnabledTestNotifications.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_ENABLE_TEST_NOTIFICATIONS, enabledTestNotifications, versionEnabledTestNotifications); } else { u->removeAttribute(ATTR_ENABLE_TEST_NOTIFICATIONS); } if (!lastReadNotification.empty() || !versionLastReadNotification.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_LAST_READ_NOTIFICATION, lastReadNotification, versionLastReadNotification); } else { u->removeAttribute(ATTR_LAST_READ_NOTIFICATION); } if (!lastActionedBanner.empty() || !versionLastActionedBanner.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_LAST_ACTIONED_BANNER, lastActionedBanner, versionLastActionedBanner); } else { u->removeAttribute(ATTR_LAST_ACTIONED_BANNER); } if (!enabledTestSurveys.empty() || !versionEnabledTestSurveys.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_ENABLE_TEST_SURVEYS, enabledTestSurveys, versionEnabledTestSurveys); } else { u->removeAttribute(ATTR_ENABLE_TEST_SURVEYS); } if (keys.size()) { client->mKeyManager.setKey(client->key); if (!client->mKeyManager.fromKeysContainer(keys)) { LOG_err << "Error processing new received values for the Key Manager (ug command)"; // if there's a previous version, better keep that value in cache const UserAttribute* attribute = client->ownuser()->getAttribute(ATTR_KEYS); if (attribute && !attribute->isNotExisting() && !attribute->version().empty()) { LOG_warn << "Replacing ^!keys value by previous version " << attribute->version() << " current: " << keysVersion; assert(!attribute->value().empty()); keys = attribute->value(); } } changes |= u->updateAttributeIfDifferentVersion(ATTR_KEYS, keys, keysVersion); } else if (client->mKeyManager.generation()) { // once the KeyManager is initialized, a future `ug` should always // include the user's attribute client->sendevent(99465, "KeyMgr / Setup failure"); } else { // Process the following ones only when there is no ^!keys yet in the account. // If ^!keys exists, they are all already in it. if (keyring.size()) // priv Ed255 and Cu255 keys { changes |= u->updateAttributeIfDifferentVersion(ATTR_KEYRING, keyring, versionKeyring); } if (authringEd255.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_AUTHRING, authringEd255, versionAuthringEd255); } if (authringCu255.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_AUTHCU255, authringCu255, versionAuthringCu255); } } if (pubEd255.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_ED25519_PUBK, pubEd255, versionPubEd255); } if (pubCu255.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_CU25519_PUBK, pubCu255, versionPubCu255); } if (sigPubk.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_SIG_RSA_PUBK, sigPubk, versionSigPubk); } if (sigCu255.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_SIG_CU255_PUBK, sigCu255, versionSigCu255); } if (!pwmh.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_PWM_BASE, pwmh, pwmhVersion); } else { u->removeAttribute(ATTR_PWM_BASE); } if (!sds.empty() || !sdsVersion.empty()) { changes |= updatePrivateEncryptedUserAttribute(u, sds, sdsVersion, ATTR_SYNC_DESIRED_STATE); #ifdef ENABLE_SYNC const std::unique_ptr records{ tlv::containerToRecords(sds, client->key)}; client->syncs.setSdsBackupsFullSync(records); #endif } else { u->removeAttribute(ATTR_SYNC_DESIRED_STATE); } if (!userStorageLevel.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_STORAGE_STATE, userStorageLevel, versionUserStorageLevel); } else { LOG_debug << "[CommandGetUserData] userStorageLevel is empty"; u->removeAttribute(ATTR_STORAGE_STATE); } if (!s4.empty() && !s4Version.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_S4, s4, s4Version); bool enabled = (s4 == "1"); LOG_info << "S4 is " << (enabled ? "enabled" : "disabled"); client->mIsS4Enabled.store(enabled); } else { u->removeAttribute(ATTR_S4); LOG_info << "S4 has been disabled"; client->mIsS4Enabled.store(false); } if (!s4container.empty() && !s4containerVersion.empty()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_S4_CONTAINER, s4container, s4containerVersion); assert(client->mIsS4Enabled == true); assert(s4container.size() == MegaClient::NODEHANDLE); NodeHandle h = toNodeHandle(&s4container); client->mS4Container.store(h); } else { u->removeAttribute(ATTR_S4_CONTAINER); client->mS4Container.store(NodeHandle()); } if (devOpt.size() && devOptVersion.size()) { changes |= u->updateAttributeIfDifferentVersion(ATTR_DEV_OPT, devOpt, devOptVersion); } else { u->removeAttribute(ATTR_DEV_OPT); } changes |= updatePrivateEncryptedUserAttribute(u, rcts, rctsVersion, ATTR_RECENT_CLEAR_TIMESTAMP); if (changes) { u->setTag(tag ? tag : -1); client->notifyuser(u); } } if (b) // business account { // integrity checks if ((s < BIZ_STATUS_EXPIRED || s > BIZ_STATUS_GRACE_PERIOD) // status not received or invalid || (m == BIZ_MODE_UNKNOWN && !client->isProFlexi())) // master flag not received or invalid (or Pro Flexi, not business) { std::string err = "GetUserData: invalid business status / account mode"; LOG_err << err; client->sendevent(99450, err.c_str(), 0); client->mBizMode = BIZ_MODE_SUBUSER; client->mBizExpirationTs = client->mBizGracePeriodTs = 0; client->setBusinessStatus(BIZ_STATUS_EXPIRED); } else { for (auto it : sts) { BizStatus status = it.first; m_time_t ts = it.second; if (status == BIZ_STATUS_EXPIRED) { client->mBizExpirationTs = ts; } else if (status == BIZ_STATUS_GRACE_PERIOD) { client->mBizGracePeriodTs = ts; } else { LOG_warn << "Unexpected status in b.sts. Status: " << status << "ts: " << ts; } } client->mBizMode = m; // subusers must receive the list of master users assert(m != BIZ_MODE_SUBUSER || !masters.empty()); client->mBizMasters = masters; client->setBusinessStatus(s); // if current business status will expire sooner than the scheduled `ug`, update the // backoff to a shorter one in order to refresh the business status asap m_time_t auxts = 0; m_time_t now = m_time(nullptr); if (client->mBizGracePeriodTs && client->mBizGracePeriodTs > now) { auxts = client->mBizGracePeriodTs; } else if (client->mBizExpirationTs && client->mBizExpirationTs > now) { auxts = client->mBizExpirationTs; } if (auxts) { dstime diff = static_cast((auxts - now) * 10); dstime current = client->btugexpiration.backoffdelta(); // diff < 0 grace period has already expired, ug update is requested one per // day if (diff > 0 && current > diff) { client->btugexpiration.backoff(diff); } } // TODO: check if type of account has changed and notify with new event (not yet supported by API) } } else { client->mBizMode = BIZ_MODE_UNKNOWN; client->mBizMasters.clear(); client->mBizExpirationTs = client->mBizGracePeriodTs = 0; client->setBusinessStatus(BIZ_STATUS_INACTIVE); } const auto storageStatus = getStorageStatusFromString(userStorageLevel); if (uspw) { if (deadlineTs == -1 || warningTs.empty()) { LOG_err << "uspw received with missing timestamps"; } else { client->mOverquotaWarningTs = std::move(warningTs); client->mOverquotaDeadlineTs = deadlineTs; client->activateoverquota(0, true); } if (storageStatus != STORAGE_RED) { client->sendevent(99492, "Paywall enabled, but storage level not red"); assert(false && "Paywall enabled, but storage level not red"); } } client->processStorageStatusFromCmd(storageStatus); mCompletion(&name, &pubk, &privk, std::move(dciList), API_OK); return true; } default: switch (User::string2attr(attributeName.c_str())) { case ATTR_FIRSTNAME: parseUserAttribute(json, firstname, versionFirstname); break; case ATTR_BIRTHMONTH: parseUserAttribute(json, birthmonth, versionBirthmonth); break; case ATTR_BIRTHYEAR: parseUserAttribute(json, birthyear, versionBirthyear); break; case ATTR_SIG_CU255_PUBK: parseUserAttribute(json, sigCu255, versionSigCu255); break; case ATTR_AUTHRING: parseUserAttribute(json, authringEd255, versionAuthringEd255); break; case ATTR_AUTHCU255: parseUserAttribute(json, authringCu255, versionAuthringCu255); break; default: if (!json.storeobject()) { mCompletion(NULL, NULL, NULL, {}, API_EINTERNAL); return false; } break; } break; } } } void CommandGetUserData::parseUserAttribute(JSON& json, std::string &value, std::string &version, bool asciiToBinary) { string info; if (!json.storeobject(&info)) { LOG_err << "Failed to parse user attribute from the array"; return; } string buf; JSON infoJson; infoJson.pos = info.c_str() + 1; for (;;) { switch (infoJson.getnameid()) { case makeNameid("av"): // value { infoJson.storeobject(&buf); break; } case makeNameid("v"): // version { infoJson.storeobject(&version); break; } case EOO: { value = asciiToBinary ? Base64::atob(buf) : buf; return; } default: { if (!infoJson.storeobject()) { version.clear(); LOG_err << "Failed to parse user attribute inside the array"; return; } } } } } bool CommandGetUserData::parseDiscountCodes(JSON& json, std::vector& dciList) { if (!json.enterarray()) { return false; } while (json.enterobject()) { DiscountCode dci; bool exit = false; while (!exit) { switch (json.getnameid()) { case makeNameid("dc"): json.storeobject(&dci.alfanumDiscountCode); break; case makeNameid("it"): dci.item = json.getint32(); break; case makeNameid("al"): dci.accountLevel = json.getint32(); break; case makeNameid("bt"): dci.behaviourType = json.getint32(); break; case makeNameid("pd"): dci.percentageDiscount = json.getint32(); break; case makeNameid("m"): dci.numMonths = json.getint32(); break; case EOO: if (dci.isValidFormat() && dci.hasAlfanumCode()) { dciList.push_back(dci); } else { LOG_warn << "Ill-formed DiscountCode"; } exit = true; if (!json.leaveobject()) { return false; } break; default: if (!json.storeobject()) { json.leaveobject(); return false; } } } } json.leavearray(); return true; } CommandGetMiscFlags::CommandGetMiscFlags(MegaClient *client) { cmd("gmf"); // this one can get the smsve flag when the account is blocked (if it's in a batch by itself) batchSeparately = true; tag = client->reqtag; } bool CommandGetMiscFlags::procresult(Result r, JSON& json) { Error e; if (r.wasErrorOrOK()) { e = r.errorOrOK(); if (!e) { LOG_err << "Unexpected response for gmf: no flags, but no error"; e = API_ENOENT; } LOG_err << "gmf failed: " << e; } else { e = client->readmiscflags(&json); } client->app->getmiscflags_result(e); return error(e) != API_EINTERNAL; } CommandABTestActive::CommandABTestActive(MegaClient *client, const string& flag, Completion completion) : mCompletion(completion) { cmd("abta"); arg("c", flag.c_str()); tag = client->reqtag; } bool CommandABTestActive::procresult(Result r, JSON&) { assert(r.wasErrorOrOK()); if (mCompletion) { mCompletion(r.errorOrOK()); } return r.wasErrorOrOK(); } CommandGetUserQuota::CommandGetUserQuota(MegaClient* client, std::shared_ptr ad, bool storage, bool transfer, bool pro, int source, std::function, Error)> completion) : details(ad), mStorage(storage), mTransfer(transfer), mPro(pro), mCompletion(std::move(completion)) { cmd("uq"); if (storage) { arg("strg", "1", 0); } if (transfer) { arg("xfer", "1", 0); } if (pro) { arg("pro", "1", 0); } arg("src", source); arg("v", 2); tag = client->reqtag; } bool CommandGetUserQuota::procresult(Result r, JSON& json) { m_off_t td; #ifndef NDEBUG bool got_storage = false; bool got_storage_used = false; #endif if (r.wasErrorOrOK()) { client->app->account_details(details.get(), r.errorOrOK()); if(mCompletion) { mCompletion(details, r.errorOrOK()); } return true; } details->pro_until = 0; details->subscriptions.clear(); details->plans.clear(); details->storage_used = 0; details->storage_max = 0; details->transfer_max = 0; details->transfer_own_used = 0; details->transfer_srv_used = 0; details->srv_ratio = 0; details->transfer_hist_starttime = 0; details->transfer_hist_interval = 3600; details->transfer_hist.clear(); details->transfer_hist_valid = true; details->transfer_reserved = 0; details->transfer_own_reserved = 0; details->transfer_srv_reserved = 0; storagestatus_t userStorageLevel{STORAGE_UNKNOWN}; for (;;) { switch (json.getnameid()) { case makeNameid("bt"): // "Base time age", this is number of seconds since the start of the current quota buckets // age of transfer // window start td = json.getint(); if (td != -1) { details->transfer_hist_starttime = m_time() - td; } break; case makeNameid("tah"): // The free IP-based quota buckets, 6 entries for 6 hours if (json.enterarray()) { m_off_t t; while (json.isnumeric() && (t = json.getint()) != -1) { details->transfer_hist.push_back(t); } json.leavearray(); } break; case makeNameid("tar"): // IP transfer reserved details->transfer_reserved = json.getint(); break; case makeNameid("rua"): // Actor reserved quota details->transfer_own_reserved += json.getint(); break; case makeNameid("ruo"): // Owner reserved quota details->transfer_srv_reserved += json.getint(); break; case makeNameid("cstrg"): // Your total account storage usage details->storage_used = json.getint(); #ifndef NDEBUG got_storage_used = true; #endif break; case makeNameid("cstrgn"): // Storage breakdown of root nodes and shares for your account // [bytes, numFiles, numFolders, versionedBytes, numVersionedFiles] if (json.enterobject()) { handle h; NodeStorage* ns; while (!ISUNDEF(h = json.gethandle()) && json.enterarray()) { ns = &details->storage[h]; ns->bytes = json.getint(); ns->files = uint32_t(json.getint()); ns->folders = uint32_t(json.getint()); ns->version_bytes = json.getint(); ns->version_files = static_cast(json.getint32()); #ifdef _DEBUG // TODO: remove this debugging block once local count is confirmed to work correctly 100% // verify the new local storage counters per root match server side (could fail if actionpackets are pending) shared_ptr node = client->nodebyhandle(h); if (node) { NodeCounter counter = node->getCounter(); const auto displayPath = node->displaypath(); LOG_debug << displayPath << " " << counter.storage << " " << ns->bytes << " " << counter.files << " " << ns->files << " " << counter.folders << " " << ns->folders << " " << counter.versionStorage << " " << ns->version_bytes << " " << counter.versions << " " << ns->version_files << (counter.storage == ns->bytes && counter.files == ns->files && counter.folders == ns->folders && counter.versionStorage == ns->version_bytes && counter.versions == ns->version_files ? "" : " ******************************************* mismatch " "*******************************************"); } #endif while(json.storeobject()); json.leavearray(); } json.leaveobject(); } break; case makeNameid("mstrg"): // maximum storage allowance details->storage_max = json.getint(); #ifndef NDEBUG got_storage = true; #endif break; case makeNameid("caxfer"): // PRO transfer quota consumed by yourself details->transfer_own_used += json.getint(); break; case makeNameid("tuo"): // Transfer usage by the owner on quotad which hasn't yet been committed back to the API DB. Supplements caxfer details->transfer_own_used += json.getint(); break; case makeNameid("csxfer"): // PRO transfer quota served to others details->transfer_srv_used += json.getint(); break; case makeNameid("tua"): // Transfer usage served to other users which hasn't yet been committed back to the API DB. Supplements csxfer details->transfer_srv_used += json.getint(); break; case makeNameid("mxfer"): // maximum transfer allowance details->transfer_max = json.getint(); break; case makeNameid("srvratio"): // The ratio of your PRO transfer quota that is able to be served to others details->srv_ratio = json.getfloat(); break; case makeNameid("rtt"): details->transfer_hist_valid = !json.getint(); break; case makeNameid("suntil"): // Expiration time of the latest PRO plan. // This expiration time could be higher than the currently active PRO plan details->pro_until = json.getint(); break; case makeNameid("balance"): // Balance of your account if (json.enterarray()) { const char* cur; double amount; while (json.enterarray()) { amount = json.getfloat(); cur = json.getvalue(); if (cur) { size_t t = details->balances.size(); details->balances.resize(t + 1); details->balances[t].amount = amount; memcpy(details->balances[t].currency, cur, 3); details->balances[t].currency[3] = 0; } json.leavearray(); } json.leavearray(); } break; case makeNameid("uslw"): // The percentage (in 100s) indicating the limit at which you are 'nearly' over. // Currently 98% for PRO, 90% for free. if (const auto uslw = json.getint(); uslw >= 0) { LOG_debug << "[CommandGetUserQuota] Percentage of storage above which the " "storage status is set to STORAGE_ORANGE (meaning the used " "storage is close to the limit): >" << (uslw / 100) << "%"; } break; case makeNameid("features"): if (!json.enterarray()) { LOG_err << "Failed to parse GetUserQuota response, enter `features` object"; client->app->account_details(details.get(), API_EINTERNAL); return false; } while (json.enterarray()) { int64_t expiryTimestamp = json.getint(); string featureId; json.storeobject(&featureId); details->activeFeatures.push_back({expiryTimestamp, featureId}); json.leavearray(); } if (!json.leavearray()) { LOG_err << "Failed to parse GetUserQuota response, leave `features` object"; client->app->account_details(details.get(), API_EINTERNAL); return false; } break; case makeNameid("subs"): { if (!readSubscriptions(&json)) { LOG_err << "Failed to parse `subs` array in GetUserQuota response"; client->app->account_details(details.get(), API_EINTERNAL); return false; } } break; case makeNameid("plans"): { if (!readPlans(&json)) { LOG_err << "Failed to parse `plans` array in GetUserQuota response"; client->app->account_details(details.get(), API_EINTERNAL); return false; } } break; case makeNameid("usl"): userStorageLevel = static_cast(json.getint()); break; case EOO: assert(!mStorage || (got_storage && got_storage_used) || client->loggedIntoFolder()); if (mStorage && !client->processStorageStatusFromCmd(userStorageLevel)) { assert(false && "CommandGetUserQuota: Storage status is unknown or invalid"); } if (mPro) { processPlans(); } client->app->account_details(details.get(), mStorage, mTransfer, mPro, false, false, false); if(mCompletion) { mCompletion(details, API_OK); } return true; default: if (!json.storeobject()) { client->app->account_details(details.get(), API_EINTERNAL); if(mCompletion) { mCompletion(details, API_EINTERNAL); } return false; } } } } bool CommandGetUserQuota::readSubscriptions(JSON* j) { vector& subs = details->subscriptions; if (!j->enterarray()) { return false; } while (j->enterobject()) { AccountSubscription sub; bool finishedSubscription = false; while (!finishedSubscription) { switch (j->getnameid()) { case makeNameid("id"): // Encrypted subscription ID if (!j->storeobject(&sub.id)) { return false; } break; case makeNameid("type"): // 'S' for active payment provider, 'R' otherwise { const char* ptr; ptr = j->getvalue(); if (ptr) { sub.type = *ptr; } } break; case makeNameid("cycle"): // Subscription billing period if (!j->storeobject(&sub.cycle)) { return false; } break; case makeNameid("gw"): // Payment provider name if (!j->storeobject(&sub.paymentMethod)) { return false; } break; case makeNameid("gwid"): // Payment provider ID sub.paymentMethodId = j->getint32(); break; case makeNameid("next"): // Renewal time sub.renew = j->getint(); break; case makeNameid("al"): // Account level sub.level = j->getint32(); break; case makeNameid("features"): // List of features the subscription grants { if (!j->enterobject()) { return false; } string key, value; while (j->storeKeyValueFromObject(key, value)) { // Check if enabled (value = 1). Disabled features are usually not // present. if (std::stoi(value)) { sub.features.push_back(std::move(key)); } } if (!j->leaveobject()) { return false; } } break; case makeNameid("is_trial"): // Is an active trial sub.isTrial = j->getbool(); break; case EOO: subs.push_back(std::move(sub)); finishedSubscription = true; break; default: if (!j->storeobject()) { return false; } } } } return j->leavearray(); } bool CommandGetUserQuota::readPlans(JSON* j) { vector& plans = details->plans; if (!j->enterarray()) { return false; } while (j->enterobject()) { AccountPlan plan; bool finishedPlan = false; while (!finishedPlan) { switch (j->getnameid()) { case makeNameid("al"): // Account level plan.level = j->getint32(); break; case makeNameid("features"): // List of features the plan grants { if (!j->enterobject()) { return false; } string key, value; while (j->storeKeyValueFromObject(key, value)) { // Check if enabled (value = 1). // Disabled features are usually not present. if (std::stoi(value)) { plan.features.push_back(std::move(key)); } } if (!j->leaveobject()) { return false; } } break; case makeNameid("expires"): // The time the plan expires plan.expiration = j->getint(); break; case makeNameid("type"): // Why the plan was granted: payment, achievement, etc. // Not included for Bussiness/Pro Flexi plan.type = j->getint32(); break; // Encrypted subscription ID case makeNameid("subid"): if (!j->storeobject(&plan.subscriptionId)) { return false; } break; case makeNameid("is_trial"): // Is an active trial plan.isTrial = j->getbool(); break; case EOO: plans.push_back(std::move(plan)); finishedPlan = true; break; default: if (!j->storeobject()) { return false; } } } } return j->leavearray(); } void CommandGetUserQuota::processPlans() { // Inspect plans to detect changes in the account. bool proPlanReceived = false; bool featurePlanReceived = false; bool changed = false; for (const auto& plan: details->plans) { if (plan.isProPlan()) { changed |= client->mCachedStatus.addOrUpdate(CacheableStatus::STATUS_PRO_LEVEL, plan.level); client->mMyAccount.setProLevel(static_cast(plan.level)); client->mMyAccount.setProUntil(static_cast(plan.expiration)); proPlanReceived = true; } else // Feature plans { changed |= client->mCachedStatus.addOrUpdate(CacheableStatus::STATUS_FEATURE_LEVEL, plan.level); featurePlanReceived = true; } } if (!proPlanReceived) { // Check if the PRO plan is no longer active. changed |= client->mCachedStatus.addOrUpdate(CacheableStatus::STATUS_PRO_LEVEL, AccountType::ACCOUNT_TYPE_FREE); if (client->mMyAccount.getProLevel() != AccountType::ACCOUNT_TYPE_FREE) { client->mMyAccount.setProLevel(AccountType::ACCOUNT_TYPE_FREE); client->mMyAccount.setProUntil(-1); } } else { client->useralerts.purgeStalePaymentReminders(); } if (!featurePlanReceived) { // Check if the feature plan is no longer active. changed |= client->mCachedStatus.addOrUpdate(CacheableStatus::STATUS_FEATURE_LEVEL, ACCOUNT_TYPE_UNKNOWN); } // Account level (PRO and features) can change without a payment (ie. with // coupons or by helpdesk) and in those cases, the `psts` packages // are not triggered. However, the SDK should notify the app and resume // transfers, etc. if (changed) { client->app->account_updated(); client->abortbackoff(true); } } CommandQueryTransferQuota::CommandQueryTransferQuota(MegaClient* client, m_off_t size) { cmd("qbq"); arg("s", size); tag = client->reqtag; } bool CommandQueryTransferQuota::procresult(Result r, JSON& json) { if (!r.wasErrorOrOK()) { LOG_err << "Unexpected response: " << json.pos; json.storeobject(); // Returns 0 to not alarm apps and don't show overquota pre-warnings // if something unexpected is received, following the same approach as // in the webclient client->app->querytransferquota_result(0); return false; } client->app->querytransferquota_result(r.errorOrOK()); return true; } CommandGetUserTransactions::CommandGetUserTransactions(MegaClient* client, std::shared_ptr ad) { cmd("utt"); details = ad; tag = client->reqtag; } bool CommandGetUserTransactions::procresult(Result, JSON& json) { details->transactions.clear(); while (json.enterarray()) { const char* handle = json.getvalue(); m_time_t ts = json.getint(); double delta = json.getfloat(); const char* cur = json.getvalue(); if (handle && (ts > 0) && cur) { size_t t = details->transactions.size(); details->transactions.resize(t + 1); memcpy(details->transactions[t].handle, handle, 11); details->transactions[t].handle[11] = 0; details->transactions[t].timestamp = ts; details->transactions[t].delta = delta; memcpy(details->transactions[t].currency, cur, 3); details->transactions[t].currency[3] = 0; } if (!json.leavearray()) { client->app->account_details(details.get(), API_EINTERNAL); return false; } } client->app->account_details(details.get(), false, false, false, false, true, false); return true; } CommandGetUserPurchases::CommandGetUserPurchases(MegaClient* client, std::shared_ptr ad) { cmd("utp"); details = ad; tag = client->reqtag; } bool CommandGetUserPurchases::procresult(Result, JSON& json) { client->restag = tag; details->purchases.clear(); while (json.enterarray()) { const char* handle = json.getvalue(); const m_time_t ts = json.getint(); double amount = json.getfloat(); const char* cur = json.getvalue(); int method = (int)json.getint(); if (handle && (ts > 0) && cur && (method >= 0)) { size_t t = details->purchases.size(); details->purchases.resize(t + 1); memcpy(details->purchases[t].handle, handle, 11); details->purchases[t].handle[11] = 0; details->purchases[t].timestamp = ts; details->purchases[t].amount = amount; memcpy(details->purchases[t].currency, cur, 3); details->purchases[t].currency[3] = 0; details->purchases[t].method = method; } if (!json.leavearray()) { client->app->account_details(details.get(), API_EINTERNAL); return false; } } client->app->account_details(details.get(), false, false, false, true, false, false); return true; } CommandGetUserSessions::CommandGetUserSessions(MegaClient* client, std::shared_ptr ad) { cmd("usl"); arg("x", 1); // Request the additional id and alive information arg("d", 1); // Request the additional device-id details = ad; tag = client->reqtag; } bool CommandGetUserSessions::procresult(Result, JSON& json) { details->sessions.clear(); while (json.enterarray()) { size_t t = details->sessions.size(); details->sessions.resize(t + 1); details->sessions[t].timestamp = json.getint(); details->sessions[t].mru = json.getint(); json.storeobject(&details->sessions[t].useragent); json.storeobject(&details->sessions[t].ip); const char* country = json.getvalue(); memcpy(details->sessions[t].country, country ? country : "\0\0", 2); details->sessions[t].country[2] = 0; details->sessions[t].current = (int)json.getint(); details->sessions[t].id = json.gethandle(8); details->sessions[t].alive = (int)json.getint(); json.storeobject(&details->sessions[t].deviceid); if (!json.leavearray()) { client->app->account_details(details.get(), API_EINTERNAL); return false; } } client->app->account_details(details.get(), false, false, false, false, false, true); return true; } CommandSetPH::CommandSetPH(MegaClient* client, Node* n, int del, m_time_t cets, bool writable, bool megaHosted, int ctag, CompletionType f) { mSeqtagArray = true; h = n->nodehandle; ets = cets; tag = ctag; mCompletion = std::move(f); assert(mCompletion); cmd("l"); arg("n", (byte*)&n->nodehandle, MegaClient::NODEHANDLE); if (del) { mDeleting = true; arg("d", 1); } if (ets) { arg("ets", ets); } if (writable) { mWritable = true; arg("w", "1"); if (megaHosted) { assert(n->sharekey && "attempting to share a key that was not set"); // generate AES-128 encryption key byte encryptionKeyForShareKey[SymmCipher::KEYLENGTH]; client->rng.genblock(encryptionKeyForShareKey, SymmCipher::KEYLENGTH); // encrypt share key with it SymmCipher* encrypter = client->getRecycledTemporaryNodeCipher(encryptionKeyForShareKey); byte encryptedShareKey[SymmCipher::KEYLENGTH] = {}; encrypter->ecb_encrypt(n->sharekey->key, encryptedShareKey, SymmCipher::KEYLENGTH); // send encrypted share key arg("sk", encryptedShareKey, SymmCipher::KEYLENGTH); // keep the encryption key until the command has succeeded mEncryptionKeyForShareKey = Base64::btoa(std::string(reinterpret_cast(encryptionKeyForShareKey), SymmCipher::KEYLENGTH)); } } } void CommandSetPH::completion(Error error, handle nodeHandle, handle publicHandle) { mCompletion(error, nodeHandle, publicHandle, std::move(mEncryptionKeyForShareKey)); } bool CommandSetPH::procresult(Result r, JSON& json) { // depending on 'w', the response can be [{"ph":"XXXXXXXX","w":"YYYYYYYYYYYYYYYYYYYYYY"}] or simply [XXXXXXXX] if (r.hasJsonObject()) { assert(mWritable); assert(!mDeleting); handle ph = UNDEF; std::string authKey; bool exit = false; while (!exit) { switch (json.getnameid()) { case makeNameid("w"): json.storeobject(&authKey); break; case makeNameid("ph"): ph = json.gethandle(); break; case EOO: { if (!authKey.empty() && !ISUNDEF(ph)) { completion(API_OK, h, ph); return true; } exit = true; break; } default: if (!json.storeobject()) { exit = true; break; } } } } else if (r.hasJsonItem()) // format: [XXXXXXXX] { assert(!mWritable); assert(!mDeleting); handle ph = json.gethandle(); if (!ISUNDEF(ph)) { completion(API_OK, h, ph); return true; } } else if (r.wasError(API_OK)) { assert(mDeleting); // link removal is done by actionpacket in this case completion(r.errorOrOK(), h, UNDEF); return true; } else if (r.wasStrictlyError()) { completion(r.errorOrOK(), h, UNDEF); return true; } completion(API_EINTERNAL, UNDEF, UNDEF); return false; } CommandGetPH::CommandGetPH(MegaClient* client, handle cph, const byte* ckey, int cop) { cmd("g"); arg("p", (byte*)&cph, MegaClient::NODEHANDLE); ph = cph; havekey = ckey ? true : false; if (havekey) { memcpy(key, ckey, sizeof key); } mLockless = true; tag = client->reqtag; op = cop; } bool CommandGetPH::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->openfilelink_result(r.errorOrOK()); return true; } m_off_t s = -1; string a, fa; for (;;) { switch (json.getnameid()) { case makeNameid("s"): s = json.getint(); break; case makeNameid("at"): json.storeobject(&a); break; case makeNameid("fa"): json.storeobject(&fa); break; case EOO: // we want at least the attributes if (s >= 0) { a.resize(static_cast( Base64::atob(a.c_str(), (byte*)a.data(), int(a.size())))); if (havekey) { client->app->openfilelink_result(ph, key, s, &a, &fa, op); } else { client->app->openfilelink_result(ph, NULL, s, &a, &fa, op); } } else { client->app->openfilelink_result(API_EINTERNAL); } return true; default: if (!json.storeobject()) { client->app->openfilelink_result(API_EINTERNAL); return false; } } } } CommandSetMasterKey::CommandSetMasterKey(MegaClient* client, const byte* newkey, const byte* hash, size_t hashsize, const byte* clientrandomvalue, const char* pin, string* salt) { mSeqtagArray = true; memcpy(this->newkey, newkey, SymmCipher::KEYLENGTH); cmd("up"); arg("k", newkey, SymmCipher::KEYLENGTH); if (clientrandomvalue) { arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); } arg("uh", hash, hashsize); if (pin) { arg("mfa", pin); } if (salt) { this->salt = *salt; } tag = client->reqtag; } bool CommandSetMasterKey::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { // update encrypted MK and salt for further checkups client->k.assign((const char *) newkey, SymmCipher::KEYLENGTH); client->accountsalt = salt; json.storeobject(); client->app->changepw_result(API_OK); return true; } else if (r.wasErrorOrOK()) { client->app->changepw_result(r.errorOrOK()); return true; } client->app->changepw_result(API_EINTERNAL); return false; } CommandAccountVersionUpgrade::CommandAccountVersionUpgrade(vector&& clRandValue, vector&& encMKey, string&& hashedAuthKey, string&& salt, int ctag, std::function completion) : mEncryptedMasterKey(std::move(encMKey)), mSalt(std::move(salt)), mCompletion(completion) { cmd("avu"); arg("emk", mEncryptedMasterKey.data(), mEncryptedMasterKey.size()); arg("hak", reinterpret_cast(hashedAuthKey.c_str()), hashedAuthKey.size()); arg("crv", clRandValue.data(), clRandValue.size()); tag = ctag; } bool CommandAccountVersionUpgrade::procresult(Result r, JSON&) { bool goodJson = r.wasErrorOrOK(); error e = goodJson ? error(r.errorOrOK()) : API_EINTERNAL; if (goodJson) { if (r.errorOrOK() == API_OK) { client->accountversion = 2; client->k.assign(reinterpret_cast(mEncryptedMasterKey.data()), mEncryptedMasterKey.size()); client->accountsalt = std::move(mSalt); } } if (e == API_OK) { client->sendevent(99473, "Account successfully upgraded to v2"); } else { const string& msg = "Account upgrade to v2 has failed (" + std::to_string(e) + ')'; client->sendevent(99474, msg.c_str()); } if (mCompletion) { mCompletion(e); } return goodJson; } CommandCreateEphemeralSession::CommandCreateEphemeralSession(MegaClient* client, const byte* key, const byte* cpw, const byte* ssc) { mSeqtagArray = true; memcpy(pw, cpw, sizeof pw); cmd("up"); arg("k", key, SymmCipher::KEYLENGTH); arg("ts", ssc, 2 * SymmCipher::KEYLENGTH); tag = client->reqtag; } bool CommandCreateEphemeralSession::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { client->me = json.gethandle(MegaClient::USERHANDLE); client->uid = Base64Str(client->me); client->resumeephemeral(client->me, pw, tag); return true; } else if (r.wasErrorOrOK()) { client->ephemeralSession = false; client->ephemeralSessionPlusPlus = false; client->app->ephemeral_result(r.errorOrOK()); return true; } client->app->ephemeral_result(API_EINTERNAL); return false; } CommandResumeEphemeralSession::CommandResumeEphemeralSession(MegaClient*, handle cuh, const byte* cpw, int ctag) { memcpy(pw, cpw, sizeof pw); uh = cuh; cmd("us"); arg("user", (byte*)&uh, MegaClient::USERHANDLE); tag = ctag; } bool CommandResumeEphemeralSession::procresult(Result r, JSON& json) { byte keybuf[SymmCipher::KEYLENGTH]; byte sidbuf[MegaClient::SIDLEN]; int havek = 0, havecsid = 0; if (r.wasErrorOrOK()) { client->app->ephemeral_result(r.errorOrOK()); return true; } for (;;) { switch (json.getnameid()) { case makeNameid("k"): havek = json.storebinary(keybuf, sizeof keybuf) == sizeof keybuf; break; case makeNameid("tsid"): havecsid = json.storebinary(sidbuf, sizeof sidbuf) == sizeof sidbuf; break; case EOO: if (!havek || !havecsid) { client->app->ephemeral_result(API_EINTERNAL); return false; } client->sid.assign((const char *)sidbuf, sizeof sidbuf); client->key.setkey(pw); client->key.ecb_decrypt(keybuf); client->key.setkey(keybuf); if (!client->checktsid(sidbuf, sizeof sidbuf)) { client->app->ephemeral_result(API_EKEY); return true; } client->me = uh; client->uid = Base64Str(client->me); client->openStatusTable(true); client->loadJourneyIdCacheValues(); client->app->ephemeral_result(uh, pw); return true; default: if (!json.storeobject()) { client->app->ephemeral_result(API_EINTERNAL); return false; } } } } CommandCancelSignup::CommandCancelSignup(MegaClient *client) { cmd("ucr"); tag = client->reqtag; } bool CommandCancelSignup::procresult(Result r, JSON&) { client->app->cancelsignup_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandWhyAmIblocked::CommandWhyAmIblocked(MegaClient *client) { cmd("whyamiblocked"); batchSeparately = true; // don't let any other commands that might get batched with it cause the whole batch to fail tag = client->reqtag; } bool CommandWhyAmIblocked::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { if (r.wasError(API_OK)) //unblocked { client->unblock(); } client->app->whyamiblocked_result(r.errorOrOK()); return true; } else if (json.isnumeric()) { int response = int(json.getint()); client->app->whyamiblocked_result(response); return true; } json.storeobject(); client->app->whyamiblocked_result(API_EINTERNAL); return false; } CommandSendSignupLink2::CommandSendSignupLink2(MegaClient* client, const char* email, const char* name) { cmd("uc2"); arg("n", (byte*)name, strlen(name)); arg("m", (byte*)email, strlen(email)); arg("v", 2); tag = client->reqtag; } CommandSendSignupLink2::CommandSendSignupLink2(MegaClient*, const char* email, const char* name, byte* clientrandomvalue, byte* encmasterkey, byte* hashedauthkey, int ctag) { cmd("uc2"); arg("n", (byte*)name, strlen(name)); arg("m", (byte*)email, strlen(email)); arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); arg("hak", hashedauthkey, SymmCipher::KEYLENGTH); arg("k", encmasterkey, SymmCipher::KEYLENGTH); arg("v", 2); tag = ctag; } bool CommandSendSignupLink2::procresult(Result r, JSON&) { client->app->sendsignuplink_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandConfirmSignupLink2::CommandConfirmSignupLink2(MegaClient* client, const byte* code, unsigned len) { mSeqtagArray = true; cmd("ud2"); arg("c", code, len); tag = client->reqtag; } bool CommandConfirmSignupLink2::procresult(Result r, JSON& json) { string name; string email; handle uh = UNDEF; int version = 0; if (r.wasErrorOrOK()) { client->app->confirmsignuplink2_result(UNDEF, NULL, NULL, r.errorOrOK()); return true; } assert(r.hasJsonArray()); if (json.storebinary(&email) && json.storebinary(&name)) { uh = json.gethandle(MegaClient::USERHANDLE); version = int(json.getint()); } while (json.storeobject()); if (!ISUNDEF(uh) && version == 2) { client->ephemeralSession = false; client->app->confirmsignuplink2_result(uh, name.c_str(), email.c_str(), API_OK); return true; } else { client->app->confirmsignuplink2_result(UNDEF, NULL, NULL, API_EINTERNAL); return false; } } CommandSetKeyPair::CommandSetKeyPair(MegaClient* client, const byte* privk, unsigned privklen, const byte* pubk, unsigned pubklen) { mSeqtagArray = true; cmd("up"); arg("privk", privk, privklen); arg("pubk", pubk, pubklen); tag = client->reqtag; } bool CommandSetKeyPair::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { json.storeobject(); client->app->setkeypair_result(API_OK); return true; } else if (r.wasErrorOrOK()) { // clear local value, since it failed to set client->mPrivateRsaKey.resetkey(); client->mPublicRsaKey.resetkey(); client->mSerializedPrivateRsaKey.clear(); client->app->setkeypair_result(r.errorOrOK()); return true; } client->app->setkeypair_result(API_EINTERNAL); return false; } // fetch full node tree CommandFetchNodes::CommandFetchNodes(MegaClient* client, int tag, bool nocache, bool loadSyncs, const NodeHandle partialFetchRoot) { assert(client); cmd("f"); // The servers are more efficient with this command when it's the only one in the batch batchSeparately = true; this->tag = tag; if (client->isClientType(MegaClient::ClientType::VPN)) { arg("mc", 1); // the only arg supported by VPN return; } arg("c", 1); arg("r", 1); if (!nocache) { arg("ca", 1); } if (client->isClientType(MegaClient::ClientType::PASSWORD_MANAGER)) { arg("n", partialFetchRoot); arg("part", 1); } // Whether we should (re)load the sync config database on request completion. mLoadSyncs = loadSyncs; /////////////////////////////////// // Filters for parsing in streaming // Parsing of chunk started mFilters.emplace("<", [this, client](JSON *) { if (!mFirstChunkProcessed) { mScsn = 0; mSt.clear(); mPreviousHandleForAlert = UNDEF; mMissingParentNodes.clear(); // make sure the syncs don't see Nodes disappearing // they should only look at the nodes again once // everything is reloaded and caught up // (in case we are reloading mid-session) client->statecurrent = false; client->actionpacketsCurrent = false; #ifdef ENABLE_SYNC // this just makes sure syncs exit any current tree iteration client->syncs.syncRun([]{}, "fetchnodes ready"); #endif assert(!mNodeTreeIsChanging.owns_lock()); mNodeTreeIsChanging = std::unique_lock(client->nodeTreeMutex); client->purgenodesusersabortsc(true); if (client->sctable) { // reset sc database for brand new node tree (note that we may be reloading mid-session) LOG_debug << "Resetting sc database"; client->sctable->truncate(); client->sctable->commit(); client->sctable->begin(); } mFirstChunkProcessed = true; } else { assert(!mNodeTreeIsChanging.owns_lock()); mNodeTreeIsChanging = std::unique_lock(client->nodeTreeMutex); } return JSONSplitter::CallbackResult::SUCCESS; }); // Parsing of chunk finished mFilters.emplace(">", [this](JSON*) { assert(mNodeTreeIsChanging.owns_lock()); mNodeTreeIsChanging.unlock(); return JSONSplitter::CallbackResult::SUCCESS; }); // Node objects (one by one) auto f = mFilters.emplace( "{[f{", [this, client](JSON* json) { if (client->readnode(json, 0, PUTNODES_APP, nullptr, false, true, mMissingParentNodes, mPreviousHandleForAlert, nullptr // allParents disabled because Syncs::triggerSync // does nothing when MegaClient::fetchingnodes is true ) != 1) { return JSONSplitter::CallbackResult::FAILED; } return JSONSplitter::ResultFromBool(json->leaveobject()); }); // Node versions (one by one) mFilters.emplace("{[f2{", f.first->second); // End of node array f = mFilters.emplace("{[f", [this, client](JSON *json) { client->mergenewshares(0); client->mNodeManager.checkOrphanNodes(mMissingParentNodes); // No need to call Syncs::triggerSync here like in MegaClient::readnodes // because it does nothing when MegaClient::fetchingnodes is true mPreviousHandleForAlert = UNDEF; mMissingParentNodes.clear(); // This is intended to consume the '[' character if the array // was empty and an empty array arrives here "[]". // If the array was not empty, we receive here only the remaining // character ']' and this call doesn't have any effect. json->enterarray(); return JSONSplitter::ResultFromBool(json->leavearray()); }); // End of node versions array mFilters.emplace("{[f2", f.first->second); // Legacy keys (one by one) mFilters.emplace("{[ok0{", [client](JSON *json) { if (!json->enterobject()) { return JSONSplitter::CallbackResult::FAILED; } client->readokelement(json); return JSONSplitter::ResultFromBool(json->leaveobject()); }); // Outgoing shares (one by one) f = mFilters.emplace("{[s{", [client](JSON *json) { if (!json->enterobject()) { return JSONSplitter::CallbackResult::FAILED; } client->readoutshareelement(json); return JSONSplitter::ResultFromBool(json->leaveobject()); }); // Pending shares (one by one) mFilters.emplace("{[ps{", f.first->second); // End of outgoing shares array f = mFilters.emplace("{[s", [client](JSON *json) { client->mergenewshares(0); json->enterarray(); return JSONSplitter::ResultFromBool(json->leavearray()); }); // End of pending shares array mFilters.emplace("{[ps", f.first->second); // Users (one by one) mFilters.emplace("{[u{", [client](JSON *json) { if (client->readuser(json, false) != 1) { return JSONSplitter::CallbackResult::FAILED; } return JSONSplitter::ResultFromBool(json->leaveobject()); }); // sn tag mFilters.emplace("{\"sn", [this](JSON* json) { // Not applying the scsn until the end of the parsing // because it could arrive before nodes // (despite at the moment it is arriving at the end) return JSONSplitter::ResultFromBool( json->storebinary((byte*)&mScsn, sizeof mScsn) == sizeof mScsn); }); // st tag mFilters.emplace("{\"st", [this](JSON* json) { return JSONSplitter::ResultFromBool(json->storeobject(&mSt)); }); // Incoming contact requests mFilters.emplace("{[ipc", [client](JSON *json) { client->readipc(json); return JSONSplitter::CallbackResult::SUCCESS; }); // Outgoing contact requests mFilters.emplace("{[opc", [client](JSON *json) { client->readopc(json); return JSONSplitter::CallbackResult::SUCCESS; }); // Public links (one by one) mFilters.emplace("{[ph{", [client](JSON *json) { if (client->procphelement(json) == 1) { json->leaveobject(); } return JSONSplitter::CallbackResult::SUCCESS; }); // Sets and Elements mFilters.emplace("{{aesp", [client](JSON *json) { client->procaesp(*json); // continue even if it failed, it's not critical return JSONSplitter::CallbackResult::SUCCESS; }); // Parsing finished mFilters.emplace("{", [this, client](JSON *) { WAIT_CLASS::bumpds(); client->fnstats.timeToLastByte = Waiter::ds - client->fnstats.startTime; assert(mScsn && "scsn must be received in response to `f` command always"); if (mScsn) { client->scsn.setScsn(mScsn); } if (!mSt.empty()) { client->app->sequencetag_update(mSt); client->mScDbStateRecord.seqTag = mSt; } return JSONSplitter::ResultFromBool(parsingFinished()); }); // Numeric error, either a number or an error object {"err":XXX} mFilters.emplace("#", [this, client](JSON *json) { // like CommandFetchNodes::procresult when r.wasErrorOrOK() is true but // parsing the specific error code here instead of directly receiving it WAIT_CLASS::bumpds(); client->fnstats.timeToLastByte = Waiter::ds - client->fnstats.startTime; Error e; checkError(e, *json); client->fetchingnodes = false; client->app->fetchnodes_result(e); return JSONSplitter::CallbackResult::SUCCESS; }); // Parsing error mFilters.emplace("E", [client](JSON*) { WAIT_CLASS::bumpds(); client->fnstats.timeToLastByte = Waiter::ds - client->fnstats.startTime; client->purgenodesusersabortsc(true); client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return JSONSplitter::CallbackResult::SUCCESS; }); #ifdef ENABLE_CHAT // Chat-related callbacks mFilters.emplace("{{mcf", [client](JSON *json) { // List of chatrooms client->procmcf(json); return JSONSplitter::CallbackResult::SUCCESS; }); f = mFilters.emplace("{[mcpna", [client](JSON *json) { // nodes shared in chatrooms client->procmcna(json); return JSONSplitter::CallbackResult::SUCCESS; }); mFilters.emplace("{[mcna", f.first->second); mFilters.emplace("{[mcsm", [client](JSON *json) { // scheduled meetings client->procmcsm(json); return JSONSplitter::CallbackResult::SUCCESS; }); #endif } CommandFetchNodes::~CommandFetchNodes() { assert(!mNodeTreeIsChanging.owns_lock()); } const char* CommandFetchNodes::getJSON(MegaClient* clientOfRequest) { // reset all the sc channel state, prevent sending sc requests while fetchnodes is sent // we wait until this moment, because when `f` is queued, there may be // other commands queued ahead of it, and those may need sc responses in order // to fully complete, and so we can't reset these members at that time. clientOfRequest->resetScForFetchnodes(); return Command::getJSON(clientOfRequest); } // purge and rebuild node/user tree bool CommandFetchNodes::procresult(Result r, JSON& json) { WAIT_CLASS::bumpds(); client->fnstats.timeToLastByte = Waiter::ds - client->fnstats.startTime; if (r.wasErrorOrOK()) { client->fetchingnodes = false; client->app->fetchnodes_result(r.errorOrOK()); return true; } // make sure the syncs don't see Nodes disappearing // they should only look at the nodes again once // everything is reloaded and caught up // (in case we are reloading mid-session) client->statecurrent = false; client->actionpacketsCurrent = false; #ifdef ENABLE_SYNC // this just makes sure syncs exit any current tree iteration client->syncs.syncRun([&](){}, "fetchnodes ready"); #endif std::unique_lock nodeTreeIsChanging(client->nodeTreeMutex); client->purgenodesusersabortsc(true); if (client->sctable) { // reset sc database for brand new node tree (note that we may be reloading mid-session) LOG_debug << "Resetting sc database"; client->sctable->truncate(); client->sctable->commit(); client->sctable->begin(); } for (;;) { switch (json.getnameid()) { case makeNameid("f"): // nodes if (!client->readnodes(&json, 0, PUTNODES_APP, nullptr, false, true)) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } break; case makeNameid("f2"): // old versions if (!client->readnodes(&json, 0, PUTNODES_APP, nullptr, false, true)) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } break; case makeNameid("ok0"): // outgoing sharekeys client->readok(&json); break; case makeNameid("s"): // Fall through case makeNameid("ps"): // outgoing or pending shares client->readoutshares(&json); break; case makeNameid("u"): // users/contacts if (!client->readusers(&json, false)) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } break; case makeNameid("sn"): // sequence number if (!client->scsn.setScsn(&json)) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } break; case makeNameid("st"): { string st; if (!json.storeobject(&st)) return false; client->app->sequencetag_update(st); client->mScDbStateRecord.seqTag = st; } break; case makeNameid("ipc"): // Incoming pending contact client->readipc(&json); break; case makeNameid("opc"): // Outgoing pending contact client->readopc(&json); break; case makeNameid("ph"): // Public links handles client->procph(&json); break; case makeNameid("aesp"): // Sets and Elements client->procaesp(json); // continue even if it failed, it's not critical break; #ifdef ENABLE_CHAT case makeNameid("mcf"): // List of chatrooms client->procmcf(&json); break; case makeNameid("mcpna"): // fall-through case makeNameid("mcna"): // nodes shared in chatrooms client->procmcna(&json); break; case makeNameid("mcsm"): // scheduled meetings client->procmcsm(&json); break; #endif case EOO: { return parsingFinished(); } default: if (!json.storeobject()) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } } } } bool CommandFetchNodes::parsingFinished() { if (!client->scsn.ready()) { client->fetchingnodes = false; client->mNodeManager.cleanNodes(); client->app->fetchnodes_result(API_EINTERNAL); return false; } client->mergenewshares(0); client->mNodeManager.initCompleted(); // (nodes already written into DB) client->initsc(); client->fetchnodestag = tag; WAIT_CLASS::bumpds(); client->fnstats.timeToCached = Waiter::ds - client->fnstats.startTime; client->fnstats.nodesCached = static_cast(client->mNodeManager.getNodeCount()); #ifdef ENABLE_SYNC if (mLoadSyncs) client->syncs.loadSyncConfigsOnFetchnodesComplete(true); #endif return true; } CommandSubmitPurchaseReceipt::CommandSubmitPurchaseReceipt(MegaClient *client, int type, const char *receipt, handle lph, int phtype, int64_t ts) { cmd("vpay"); arg("t", type); if(receipt) { arg("receipt", receipt); } if(type == 2 && client->loggedin() == FULLACCOUNT) { arg("user", client->finduser(client->me)->uid.c_str()); } if (!ISUNDEF(lph)) { if (phtype == 0) // legacy mode { arg("aff", (byte*)&lph, MegaClient::NODEHANDLE); } else { beginobject("aff"); arg("id", (byte*)&lph, MegaClient::NODEHANDLE); arg("ts", ts); arg("t", phtype); // 1=affiliate id, 2=file/folder link, 3=chat link, 4=contact link endobject(); } } tag = client->reqtag; } bool CommandSubmitPurchaseReceipt::procresult(Result r, JSON&) { client->app->submitpurchasereceipt_result(r.errorOrOK()); return r.wasErrorOrOK(); } // Credit Card Store CommandCreditCardStore::CommandCreditCardStore(MegaClient* client, const char *cc, const char *last4, const char *expm, const char *expy, const char *hash) { cmd("ccs"); arg("cc", cc); arg("last4", last4); arg("expm", expm); arg("expy", expy); arg("hash", hash); tag = client->reqtag; } bool CommandCreditCardStore::procresult(Result r, JSON&) { client->app->creditcardstore_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandCreditCardQuerySubscriptions::CommandCreditCardQuerySubscriptions(MegaClient* client) { cmd("ccqns"); tag = client->reqtag; } bool CommandCreditCardQuerySubscriptions::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->creditcardquerysubscriptions_result(0, r.errorOrOK()); return true; } else if (json.isnumeric()) { int number = int(json.getint()); client->app->creditcardquerysubscriptions_result(number, API_OK); return true; } else { json.storeobject(); client->app->creditcardquerysubscriptions_result(0, API_EINTERNAL); return false; } } CommandCreditCardCancelSubscriptions::CancelSubscription::CancelSubscription(const char* reason, const char* id, int canContact): mReasoning{reason ? reason : ""}, mId{id ? id : ""}, mCanContact{canContact == static_cast(CanContact::Yes) ? CanContact::Yes : CanContact::No} {} CommandCreditCardCancelSubscriptions::CancelSubscription::CancelSubscription( vector>&& reasons, const char* id, int canContact): mReasoning{std::move(reasons)}, mId{id ? id : ""}, mCanContact{canContact == static_cast(CanContact::Yes) ? CanContact::Yes : CanContact::No} {} CommandCreditCardCancelSubscriptions::CommandCreditCardCancelSubscriptions( MegaClient* client, const CancelSubscription& cancelSubscription) { cmd("cccs"); // Cancel Reason(s) if (const string* reason = cancelSubscription.getReasoning()) { if (!reason->empty()) { arg("r", reason->c_str()); } } else if (const auto* reasons = cancelSubscription.getReasoning>>()) { if (!reasons->empty()) { beginarray("r"); for (const auto& r: *reasons) { beginobject(); arg("r", r.first.c_str()); arg("p", r.second.c_str()); endobject(); } endarray(); } } // The user can be contacted or not if (cancelSubscription.canContact()) { arg("cc", static_cast(CanContact::Yes)); } // Specific subscription ID if (!cancelSubscription.getId().empty()) { arg("sub", cancelSubscription.getId().c_str()); } tag = client->reqtag; } bool CommandCreditCardCancelSubscriptions::procresult(Result r, JSON&) { client->app->creditcardcancelsubscriptions_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandCopySession::CommandCopySession(MegaClient *client) { cmd("us"); arg("c", 1); batchSeparately = true; // don't let any other commands that might get batched with it cause the whole batch to fail when blocked tag = client->reqtag; } // for ephemeral accounts, it returns "tsid" instead of "csid" -> not supported, will return API_EINTERNAL bool CommandCopySession::procresult(Result r, JSON& json) { string session; byte sidbuf[AsymmCipher::MAXKEYLENGTH]; int len_csid = 0; if (r.wasErrorOrOK()) { assert(r.errorOrOK() != API_OK); // API shouldn't return OK, but a session client->app->copysession_result(NULL, r.errorOrOK()); return true; } for (;;) { switch (json.getnameid()) { case makeNameid("csid"): len_csid = json.storebinary(sidbuf, sizeof sidbuf); break; case EOO: if (len_csid < 32) { client->app->copysession_result(NULL, API_EINTERNAL); return false; } if (!client->mPrivateRsaKey.decrypt(sidbuf, static_cast(len_csid), sidbuf, MegaClient::SIDLEN)) { client->app->copysession_result(NULL, API_EINTERNAL); return false; } session.resize(MegaClient::SIDLEN * 4 / 3 + 4); session.resize(Base64::btoa(sidbuf, MegaClient::SIDLEN, (char*)session.data())); client->app->copysession_result(&session, API_OK); return true; default: if (!json.storeobject()) { client->app->copysession_result(NULL, API_EINTERNAL); return false; } } } } CommandGetPaymentMethods::CommandGetPaymentMethods(MegaClient *client) { cmd("ufpq"); tag = client->reqtag; } bool CommandGetPaymentMethods::procresult(Result r, JSON& json) { int methods = 0; int64_t value; if (r.wasErrorOrOK()) { if (!r.wasError(API_OK)) { client->app->getpaymentmethods_result(methods, r.errorOrOK()); //Consume remaining values if they exist while(json.isnumeric()) { json.getint(); } return true; } value = static_cast(error(r.errorOrOK())); } else if (json.isnumeric()) { value = json.getint(); } else { LOG_err << "Parse error in ufpq"; client->app->getpaymentmethods_result(methods, API_EINTERNAL); return false; } methods |= 1 << value; while (json.isnumeric()) { value = json.getint(); if (value < 0) { client->app->getpaymentmethods_result(methods, static_cast(value)); //Consume remaining values if they exist while(json.isnumeric()) { json.getint(); } return true; } methods |= 1 << value; } client->app->getpaymentmethods_result(methods, API_OK); return true; } CommandSendReport::CommandSendReport(MegaClient *client, const char *type, const char *blob, const char *uid) { cmd("clog"); arg("t", type); if (blob) { arg("d", blob); } if (uid) { arg("id", uid); } tag = client->reqtag; } bool CommandSendReport::procresult(Result r, JSON&) { client->app->userfeedbackstore_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandSendEvent::CommandSendEvent(MegaClient *client, int type, const char *desc, bool addJourneyId, const char *viewId) { cmd("log"); arg("e", type); arg("m", desc); // Attach JourneyID if (addJourneyId) { string journeyId = client->getJourneyId(); if (!journeyId.empty()) { arg("j", journeyId.c_str()); m_off_t currentms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); arg("ms", currentms); } else { LOG_warn << "[CommandSendEvent::CommandSendEvent] Add JourneyID flag is ON, but there is no JourneyID value set"; } } // Attach ViewID (generated by the SDK for the client, handled by the client) if (viewId && *viewId) // Cannot be empty { arg("v", viewId); } tag = client->reqtag; } bool CommandSendEvent::procresult(Result r, JSON&) { client->app->sendevent_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandSupportTicket::CommandSupportTicket(MegaClient *client, const char *message, int type) { cmd("sse"); arg("t", type); arg("b", 1); // base64 encoding for `msg` arg("m", (const byte*)message, strlen(message)); tag = client->reqtag; } bool CommandSupportTicket::procresult(Result r, JSON&) { client->app->supportticket_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandCleanRubbishBin::CommandCleanRubbishBin(MegaClient *client) { cmd("dr"); tag = client->reqtag; } bool CommandCleanRubbishBin::procresult(Result r, JSON&) { client->app->cleanrubbishbin_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandGetRecoveryLink::CommandGetRecoveryLink(MegaClient *client, const char *email, int type, const char *pin) { cmd("erm"); arg("m", email); arg("t", type); if (type == CANCEL_ACCOUNT && pin) { arg("mfa", pin); } tag = client->reqtag; } bool CommandGetRecoveryLink::procresult(Result r, JSON&) { client->app->getrecoverylink_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandQueryRecoveryLink::CommandQueryRecoveryLink(MegaClient *client, const char *linkcode) { cmd("erv"); arg("c", linkcode); tag = client->reqtag; } bool CommandQueryRecoveryLink::procresult(Result r, JSON& json) { // [,"","",,"",[""]] (and we are already in the array) string email; string ip; m_time_t ts; handle uh; if (r.wasStrictlyError()) { client->app->queryrecoverylink_result(r.errorOrOK()); return true; } if (!json.isnumeric()) { client->app->queryrecoverylink_result(API_EINTERNAL); return false; } int type = static_cast(json.getint()); if (!json.storeobject(&email) || !json.storeobject(&ip)) { client->app->queryrecoverylink_result(API_EINTERNAL); return false; } ts = json.getint(); uh = json.gethandle(MegaClient::USERHANDLE); if (ts == -1 || !uh) { client->app->queryrecoverylink_result(API_EINTERNAL); return false; } string tmp; vector emails; // read emails registered for this account json.enterarray(); while (json.storeobject(&tmp)) { emails.push_back(tmp); if (*json.pos == ']') { break; } } json.leavearray(); // emails array if (!emails.size()) // there should be at least one email { client->app->queryrecoverylink_result(API_EINTERNAL); return false; } if (client->loggedin() == FULLACCOUNT && uh != client->me) { client->app->queryrecoverylink_result(API_EACCESS); return true; } client->app->queryrecoverylink_result(type, email.c_str(), ip.c_str(), time_t(ts), uh, &emails); return true; } CommandGetPrivateKey::CommandGetPrivateKey(MegaClient *client, const char *code) { mSeqtagArray = true; cmd("erx"); arg("r", "gk"); arg("c", code); tag = client->reqtag; } bool CommandGetPrivateKey::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) // error { client->app->getprivatekey_result(r.errorOrOK()); return true; } else { byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; int len_privk = json.storebinary(privkbuf, sizeof privkbuf); // account has RSA keypair: decrypt server-provided session ID if (len_privk < 256) { client->app->getprivatekey_result(API_EINTERNAL); return false; } else { client->app->getprivatekey_result((error)API_OK, privkbuf, static_cast(len_privk)); return true; } } } CommandConfirmRecoveryLink::CommandConfirmRecoveryLink(MegaClient* client, const char* code, const byte* hash, size_t hashsize, const byte* clientrandomvalue, const byte* encMasterKey, const byte* initialSession) { cmd("erx"); mSeqtagArray = true; if (!initialSession) { arg("r", "sk"); } arg("c", code); arg("x", encMasterKey, SymmCipher::KEYLENGTH); if (!clientrandomvalue) { arg("y", hash, hashsize); } else { beginobject("y"); arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); arg("hak", hash, hashsize); //hashed authentication key endobject(); } if (initialSession) { arg("z", initialSession, 2 * SymmCipher::KEYLENGTH); } tag = client->reqtag; } bool CommandConfirmRecoveryLink::procresult(Result r, JSON&) { client->app->confirmrecoverylink_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandConfirmCancelLink::CommandConfirmCancelLink(MegaClient *client, const char *code) { cmd("erx"); arg("c", code); tag = client->reqtag; } bool CommandConfirmCancelLink::procresult(Result r, JSON&) { MegaApp *app = client->app; app->confirmcancellink_result(r.errorOrOK()); if (r.wasError(API_OK)) { app->request_error(API_ESID); } return r.wasErrorOrOK(); } CommandResendVerificationEmail::CommandResendVerificationEmail(MegaClient *client) { cmd("era"); batchSeparately = true; // don't let any other commands that might get batched with it cause the whole batch to fail tag = client->reqtag; } bool CommandResendVerificationEmail::procresult(Result r, JSON&) { client->app->resendverificationemail_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandResetSmsVerifiedPhoneNumber::CommandResetSmsVerifiedPhoneNumber(MegaClient *client) { cmd("smsr"); tag = client->reqtag; } bool CommandResetSmsVerifiedPhoneNumber::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { client->mSmsVerifiedPhone.clear(); } client->app->resetSmsVerifiedPhoneNumber_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandValidatePassword::CommandValidatePassword(MegaClient *client, const char *email, const vector& authKey) { cmd("us"); arg("user", email); arg("uh", authKey.data(), authKey.size()); tag = client->reqtag; } bool CommandValidatePassword::procresult(Result r, JSON&) { if (r.wasErrorOrOK()) { client->app->validatepassword_result(r.errorOrOK()); return true; } else { assert(r.hasJsonObject()); // we don't use the object contents, and will exit the object automatically client->app->validatepassword_result(API_OK); return r.hasJsonObject(); } } CommandGetEmailLink::CommandGetEmailLink(MegaClient *client, const char *email, int add, const char *pin) { cmd("se"); if (add) { arg("aa", "a"); // add } else { arg("aa", "r"); // remove } arg("e", email); if (pin) { arg("mfa", pin); } tag = client->reqtag; } bool CommandGetEmailLink::procresult(Result r, JSON&) { client->app->getemaillink_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandConfirmEmailLink::CommandConfirmEmailLink(MegaClient *client, const char *code, const char *email, const byte *newLoginHash, bool replace) { this->email = email; this->replace = replace; cmd("sec"); arg("c", code); arg("e", email); if (newLoginHash) { arg("uh", newLoginHash, sizeof(uint64_t)); } if (replace) { arg("r", 1); // replace the current email address by this one } notself(client); tag = client->reqtag; } bool CommandConfirmEmailLink::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { User *u = client->finduser(client->me); if (replace) { LOG_debug << "Email changed from `" << u->email << "` to `" << email << "`"; client->setEmail(u, email); } // TODO: once we manage multiple emails, add the new email to the list of emails } client->app->confirmemaillink_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandGetLocalSSLCertificate::CommandGetLocalSSLCertificate(MegaClient *client) { this->client = client; cmd("lc"); arg("v", 1); tag = client->reqtag; } bool CommandGetLocalSSLCertificate::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->getlocalsslcertificate_result(0, NULL, r.errorOrOK()); return true; } assert(r.hasJsonObject()); string certdata; m_time_t ts = 0; int numelements = 0; for (;;) { switch (json.getnameid()) { case makeNameid("t"): { ts = json.getint(); break; } case makeNameid("d"): { string data; json.enterarray(); while (json.storeobject(&data)) { if (numelements) { certdata.append(";"); } numelements++; certdata.append(data); } json.leavearray(); break; } case EOO: { if (numelements < 2) { client->app->getlocalsslcertificate_result(0, NULL, API_EINTERNAL); return false; } client->app->getlocalsslcertificate_result(ts, &certdata, API_OK); return true; } default: if (!json.storeobject()) { client->app->getlocalsslcertificate_result(0, NULL, API_EINTERNAL); return false; } } } } #ifdef ENABLE_CHAT CommandChatCreate::CommandChatCreate(MegaClient* client, bool group, bool publicchat, const userpriv_vector* upl, const string_map* ukm, const char* title, bool meetingRoom, int chatOptions, const ScheduledMeeting* schedMeeting) { this->client = client; this->chatPeers = upl ? new userpriv_vector(*upl) : nullptr; this->mPublicChat = publicchat; this->mTitle = title ? string(title) : ""; this->mUnifiedKey = ""; mMeeting = meetingRoom; cmd("mcc"); arg("g", (group) ? 1 : 0); if (group && title) { arg("ct", title); } if (publicchat) { arg("m", 1); char ownHandleB64[12]; Base64::btoa((byte *)&client->me, MegaClient::USERHANDLE, ownHandleB64); ownHandleB64[11] = '\0'; string_map::const_iterator it = ukm->find(ownHandleB64); if (it != ukm->end()) { mUnifiedKey = it->second; arg("ck", mUnifiedKey.c_str()); } } if (meetingRoom) { arg("mr", 1); } if (group) { mChatOptions.set(static_cast(chatOptions)); if (mChatOptions.speakRequest()) {arg("sr", 1);} if (mChatOptions.waitingRoom()) {arg("w", 1);} if (mChatOptions.openInvite()) {arg("oi", 1);} } beginarray("u"); if (chatPeers) { for (const auto& peer: *chatPeers) { beginobject(); handle uh = peer.first; arg("u", (byte*)&uh, MegaClient::USERHANDLE); arg("p", peer.second); if (publicchat) { char uid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid); uid[11] = '\0'; string_map::const_iterator ituk = ukm->find(uid); if (ituk != ukm->end()) { arg("ck", ituk->second.c_str()); } } endobject(); } } endarray(); // create a scheduled meeting along with chatroom if (schedMeeting) { mSchedMeeting.reset(schedMeeting->copy()); // can avoid copy by mooving check from where we are calling beginobject("sm"); arg("a", "mcsmp"); createSchedMeetingJson(mSchedMeeting.get()); endobject(); } arg("v", 1); notself(client); tag = client->reqtag; } bool CommandChatCreate::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->chatcreate_result(NULL, r.errorOrOK()); return true; } else { handle chatid = UNDEF; handle schedId = UNDEF; int shard = -1; bool group = false; m_time_t ts = -1; std::vector> schedMeetings; UserAlert::UpdatedScheduledMeeting::Changeset cs; bool exit = false; bool addSchedMeeting = false; while (!exit) { switch (json.getnameid()) { case makeNameid("id"): chatid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("cs"): shard = int(json.getint()); break; case makeNameid("g"): group = json.getbool(); break; case makeNameid("ts"): // actual creation timestamp ts = json.getint(); break; case makeNameid("sm"): { addSchedMeeting = !json.isnumeric(); if (addSchedMeeting) { schedId = json.gethandle(MegaClient::CHATHANDLE); } else { LOG_err << "Error creating a scheduled meeting along with chat. chatId [" << Base64Str(chatid) << "]"; assert(false); } break; } case EOO: exit = true; break; default: if (!json.storeobject()) { client->app->chatcreate_result(NULL, API_EINTERNAL); return false; } } } if (chatid != UNDEF && shard != -1) { if (addSchedMeeting) { if (mSchedMeeting) { mSchedMeeting->setSchedId(schedId); mSchedMeeting->setChatid(chatid); if (!mSchedMeeting->isValid()) { client->reportInvalidSchedMeeting(mSchedMeeting.get()); addSchedMeeting = false; } } else { LOG_err << "Scheduled meeting id received upon mcc command, but there's no local " "scheduled meeting data. chatId [" << toHandle(chatid) << "]"; addSchedMeeting = false; assert(false); } } TextChat* chat = nullptr; if (client->chats.find(chatid) == client->chats.end()) { chat = new TextChat(mPublicChat); client->chats[chatid] = chat; } else { chat = client->chats[chatid]; client->setChatMode(chat, mPublicChat); } chat->setChatId(chatid); chat->setOwnPrivileges(PRIV_MODERATOR); chat->setShard(shard); chat->setUserPrivileges(chatPeers); chat->setGroup(group); chat->setTs(ts != -1 ? ts : 0); chat->setMeeting(mMeeting); // no need to fetch scheduled meetings as we have just created the chat, so it doesn't have any if (group) // we are creating a chat, so we need to initialize all chat options enabled/disabled { chat->addOrUpdateChatOptions(mChatOptions.speakRequest(), mChatOptions.waitingRoom(), mChatOptions.openInvite()); } chat->setTag(tag ? tag : -1); if (chat->getGroup() && !mTitle.empty()) { chat->setTitle(mTitle); } if (mPublicChat) { chat->setUnifiedKey(mUnifiedKey); } if (addSchedMeeting && !chat->addOrUpdateSchedMeeting(std::move(mSchedMeeting))) { LOG_err << "Error adding a new scheduled meeting with schedId [" << Base64Str(schedId) << "]"; } client->notifychat(chat); client->app->chatcreate_result(chat, API_OK); chatPeers = nullptr; } else { client->app->chatcreate_result(NULL, API_EINTERNAL); } return true; } } CommandChatCreate::~CommandChatCreate() { if (chatPeers) { delete chatPeers; } } CommandSetChatOptions::CommandSetChatOptions(MegaClient* client, handle chatid, int option, bool enabled, CommandSetChatOptionsCompletion completion) : mCompletion(completion) { this->client = client; mChatid = chatid; mOption = option; mEnabled = enabled; cmd("mco"); arg("cid", (byte*)&chatid, MegaClient::CHATHANDLE); switch (option) { case ChatOptions::kOpenInvite: arg("oi", enabled); break; case ChatOptions::kSpeakRequest: arg("sr", enabled); break; case ChatOptions::kWaitingRoom: arg("w", enabled); break; default: break; } notself(client); // set i param to ignore action packet generated by our own action tag = client->reqtag; } bool CommandSetChatOptions::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { auto it = client->chats.find(mChatid); if (it == client->chats.end()) { mCompletion(API_EINTERNAL); return true; } // chat options: [-1 (not updated) | 0 (remove) | 1 (add)] int speakRequest = mOption == ChatOptions::kSpeakRequest ? mEnabled : -1; int waitingRoom = mOption == ChatOptions::kWaitingRoom ? mEnabled : -1; int openInvite = mOption == ChatOptions::kOpenInvite ? mEnabled : -1; TextChat* chat = it->second; chat->addOrUpdateChatOptions(speakRequest, waitingRoom, openInvite); chat->setTag(tag ? tag : -1); client->notifychat(chat); } mCompletion(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatInvite::CommandChatInvite(MegaClient *client, handle chatid, handle uh, privilege_t priv, const char *unifiedkey, const char* title) { this->client = client; this->chatid = chatid; this->uh = uh; this->priv = priv; this->title = title ? string(title) : ""; cmd("mci"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("u", (byte *)&uh, MegaClient::USERHANDLE); arg("p", priv); arg("v", 1); if (title) { arg("ct", title); } if (unifiedkey) { arg("ck", unifiedkey); } notself(client); tag = client->reqtag; } bool CommandChatInvite::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { // the invitation succeed for a non-existing chatroom client->app->chatinvite_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; chat->addUserPrivileges(uh, priv); if (!title.empty()) // only if title was set for this chatroom, update it { chat->setTitle(title); } chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatinvite_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatRemove::CommandChatRemove(MegaClient *client, handle chatid, handle uh) { this->client = client; this->chatid = chatid; this->uh = uh; cmd("mcr"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); if (uh != client->me) { arg("u", (byte *)&uh, MegaClient::USERHANDLE); } arg("v", 1); notself(client); tag = client->reqtag; } bool CommandChatRemove::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { // the invitation succeed for a non-existing chatroom client->app->chatremove_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; if (!chat->removeUserPrivileges(uh)) { if (uh != client->me) { // the removal succeed, but the list of peers is empty client->app->chatremove_result(API_EINTERNAL); return true; } } if (uh == client->me) { chat->setOwnPrivileges(PRIV_RM); // clear the list of peers (if re-invited, peers will be re-added) chat->setUserPrivileges(nullptr); } chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatremove_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatURL::CommandChatURL(MegaClient *client, handle chatid) { mSeqtagArray = true; this->client = client; cmd("mcurl"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("v", 1); tag = client->reqtag; } bool CommandChatURL::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { string url; if (json.storeobject(&url)) { client->app->chaturl_result(&url, API_OK); return true; } } else if (r.wasErrorOrOK()) { client->app->chaturl_result(NULL, r.errorOrOK()); return true; } client->app->chaturl_result(NULL, API_EINTERNAL); return false; } CommandChatGrantAccess::CommandChatGrantAccess(MegaClient *client, handle chatid, handle h, const char *uid) { this->client = client; this->chatid = chatid; this->h = h; Base64::atob(uid, (byte*)&uh, MegaClient::USERHANDLE); cmd("mcga"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("n", (byte*)&h, MegaClient::NODEHANDLE); arg("u", uid); arg("v", 1); notself(client); tag = client->reqtag; } bool CommandChatGrantAccess::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { // the action succeed for a non-existing chatroom?? client->app->chatgrantaccess_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; chat->setNodeUserAccess(h, uh); chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatgrantaccess_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatRemoveAccess::CommandChatRemoveAccess(MegaClient *client, handle chatid, handle h, const char *uid) { this->client = client; this->chatid = chatid; this->h = h; Base64::atob(uid, (byte*)&uh, MegaClient::USERHANDLE); cmd("mcra"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("n", (byte*)&h, MegaClient::NODEHANDLE); arg("u", uid); arg("v", 1); notself(client); tag = client->reqtag; } bool CommandChatRemoveAccess::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { client->app->chatremoveaccess_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; chat->setNodeUserAccess(h, uh, true); chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatremoveaccess_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatUpdatePermissions::CommandChatUpdatePermissions(MegaClient *client, handle chatid, handle uh, privilege_t priv) { this->client = client; this->chatid = chatid; this->uh = uh; this->priv = priv; cmd("mcup"); arg("v", 1); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("u", (byte *)&uh, MegaClient::USERHANDLE); arg("p", priv); notself(client); tag = client->reqtag; } bool CommandChatUpdatePermissions::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { client->app->chatupdatepermissions_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; if (uh != client->me) { if (!chat->updateUserPrivileges(uh, priv)) { // the update succeed, but that peer is not included in the chatroom client->app->chatupdatepermissions_result(API_EINTERNAL); return true; } } else { chat->setOwnPrivileges(priv); } chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatupdatepermissions_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatTruncate::CommandChatTruncate(MegaClient *client, handle chatid, handle messageid) { this->client = client; this->chatid = chatid; cmd("mct"); arg("v", 1); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("m", (byte*)&messageid, MegaClient::CHATHANDLE); notself(client); tag = client->reqtag; } bool CommandChatTruncate::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { // the truncation succeed for a non-existing chatroom client->app->chattruncate_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chattruncate_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatSetTitle::CommandChatSetTitle(MegaClient *client, handle chatid, const char *title) { this->client = client; this->chatid = chatid; this->title = title ? string(title) : ""; cmd("mcst"); arg("v", 1); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("ct", title); notself(client); tag = client->reqtag; } bool CommandChatSetTitle::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { if (client->chats.find(chatid) == client->chats.end()) { // the invitation succeed for a non-existing chatroom client->app->chatsettitle_result(API_EINTERNAL); return true; } TextChat *chat = client->chats[chatid]; chat->setTitle(title); chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatsettitle_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatPresenceURL::CommandChatPresenceURL(MegaClient *client) { mSeqtagArray = true; this->client = client; cmd("pu"); tag = client->reqtag; } bool CommandChatPresenceURL::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { string url; if (json.storeobject(&url)) { client->app->chatpresenceurl_result(&url, API_OK); return true; } } else if (r.wasErrorOrOK()) { client->app->chatpresenceurl_result(NULL, r.errorOrOK()); return true; } client->app->chatpresenceurl_result(NULL, API_EINTERNAL); return false; } CommandRegisterPushNotification::CommandRegisterPushNotification(MegaClient *client, int deviceType, const char *token) { this->client = client; cmd("spt"); arg("p", deviceType); arg("t", token); tag = client->reqtag; } bool CommandRegisterPushNotification::procresult(Result r, JSON&) { client->app->registerpushnotification_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandArchiveChat::CommandArchiveChat(MegaClient *client, handle chatid, bool archive) { this->mChatid = chatid; this->mArchive = archive; cmd("mcsf"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("m", 1); arg("f", archive); notself(client); tag = client->reqtag; } bool CommandArchiveChat::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { textchat_map::iterator it = client->chats.find(mChatid); if (it == client->chats.end()) { LOG_err << "Archive chat succeeded for a non-existing chatroom"; client->app->archivechat_result(API_ENOENT); return true; } TextChat *chat = it->second; chat->setFlag(mArchive, TextChat::FLAG_OFFSET_ARCHIVE); chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->archivechat_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandSetChatRetentionTime::CommandSetChatRetentionTime(MegaClient *client, handle chatid, unsigned period) { mChatid = chatid; cmd("mcsr"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); arg("d", period); arg("ds", 1); tag = client->reqtag; } bool CommandSetChatRetentionTime::procresult(Result r, JSON&) { client->app->setchatretentiontime_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandRichLink::CommandRichLink(MegaClient *client, const char *url) { cmd("erlsd"); arg("url", url); tag = client->reqtag; } bool CommandRichLink::procresult(Result r, JSON& json) { // error format: [{"error":}] // result format: [{"result":{ // "url":"", // "t":"", // "d":"<description>", // "ic":"<format>:<icon_B64>", // "i":"<format>:<image>"}}] if (r.wasErrorOrOK()) { client->app->richlinkrequest_result(NULL, r.errorOrOK()); return true; } string res; int errCode = 0; string metadata; for (;;) { switch (json.getnameid()) { case makeNameid("error"): errCode = int(json.getint()); break; case makeNameid("result"): json.storeobject(&metadata); break; case EOO: { error e = API_EINTERNAL; if (!metadata.empty()) { client->app->richlinkrequest_result(&metadata, API_OK); return true; } else if (errCode) { switch(errCode) { case 403: e = API_EACCESS; break; case 404: e = API_ENOENT; break; default: e = API_EINTERNAL; break; } } client->app->richlinkrequest_result(NULL, e); return true; } default: if (!json.storeobject()) { client->app->richlinkrequest_result(NULL, API_EINTERNAL); return false; } } } } CommandChatLink::CommandChatLink(MegaClient *client, handle chatid, bool del, bool createifmissing) { mSeqtagArray = true; mDelete = del; cmd("mcph"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); if (del) { arg("d", 1); } if (!createifmissing) { arg("cim", (m_off_t)0); } tag = client->reqtag; } bool CommandChatLink::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { assert(!mDelete); handle h = json.gethandle(MegaClient::CHATLINKHANDLE); if (!ISUNDEF(h)) { client->app->chatlink_result(h, API_OK); return true; } } else if (r.wasErrorOrOK()) { client->app->chatlink_result(UNDEF, r.errorOrOK()); return true; } LOG_err << "Unexpected response for create/get chatlink"; client->app->chatlink_result(UNDEF, API_EINTERNAL); return false; } CommandChatLinkURL::CommandChatLinkURL(MegaClient *client, handle publichandle) { cmd("mcphurl"); arg("ph", (byte*)&publichandle, MegaClient::CHATLINKHANDLE); tag = client->reqtag; } bool CommandChatLinkURL::procresult(Result r, JSON& json) { if (r.wasStrictlyError()) { client->app->chatlinkurl_result(UNDEF, -1, NULL, NULL, -1, 0, false, ChatOptions::kEmpty, nullptr, UNDEF, r.errorOrOK()); return true; } else { handle chatid = UNDEF; int shard = -1; int numPeers = -1; string url; string ct; m_time_t ts = 0; bool meetingRoom = false; bool waitingRoom = false; bool openInvite = false; bool speakRequest = false; std::vector<std::unique_ptr<ScheduledMeeting>> schedMeetings; handle callid = UNDEF; for (;;) { switch (json.getnameid()) { case makeNameid("id"): // chatid chatid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("cs"): // shard shard = int(json.getint()); break; case makeNameid("ct"): // chat-title json.storeobject(&ct); break; case makeNameid("url"): // chaturl json.storeobject(&url); break; case makeNameid("ncm"): // number of members in the chat numPeers = int(json.getint()); break; case makeNameid("ts"): // chat creation timestamp ts = json.getint(); break; case makeNameid("callId"): // callId if there is an active call (just if mr == 1) callid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("mr"): // meeting room meetingRoom = json.getbool(); break; case makeNameid("w"): // waiting room waitingRoom = json.getbool(); break; case makeNameid("sr"): speakRequest = json.getbool(); break; case makeNameid("oi"): openInvite = json.getbool(); break; case makeNameid("sm"): // scheduled meetings { if (json.enterarray()) { error err = client->parseScheduledMeetings(schedMeetings, false, &json); if (!json.leavearray() || err) { LOG_err << "Failed to parse mcphurl respone. Error: " << err; client->app->chatlinkurl_result(UNDEF, -1, NULL, NULL, -1, 0, false, false, nullptr, UNDEF, API_EINTERNAL); return false; } } break; } case EOO: if (chatid != UNDEF && shard != -1 && !url.empty() && !ct.empty() && numPeers != -1) { client->app->chatlinkurl_result(chatid, shard, &url, &ct, numPeers, ts, meetingRoom, ChatOptions(speakRequest, waitingRoom, openInvite).value(), &schedMeetings, callid, API_OK); } else { client->app->chatlinkurl_result(UNDEF, -1, NULL, NULL, -1, 0, false, ChatOptions::kEmpty, nullptr, UNDEF, API_EINTERNAL); } return true; default: if (!json.storeobject()) { client->app->chatlinkurl_result(UNDEF, -1, NULL, NULL, -1, 0, false, ChatOptions::kEmpty, nullptr, UNDEF, API_EINTERNAL); return false; } } } } } CommandChatLinkClose::CommandChatLinkClose(MegaClient *client, handle chatid, const char *title) { mChatid = chatid; mTitle = title ? string(title) : ""; cmd("mcscm"); arg("id", (byte*)&chatid, MegaClient::CHATHANDLE); if (title) { arg("ct", title); } notself(client); tag = client->reqtag; } bool CommandChatLinkClose::procresult(Result r, JSON&) { if (r.wasError(API_OK)) { textchat_map::iterator it = client->chats.find(mChatid); if (it == client->chats.end()) { LOG_err << "Chat link close succeeded for a non-existing chatroom"; client->app->chatlinkclose_result(API_ENOENT); return true; } TextChat *chat = it->second; client->setChatMode(chat, false); if (!mTitle.empty()) { chat->setTitle(mTitle); } chat->setTag(tag ? tag : -1); client->notifychat(chat); } client->app->chatlinkclose_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandChatLinkJoin::CommandChatLinkJoin(MegaClient *client, handle publichandle, const char *unifiedkey) { cmd("mciph"); arg("ph", (byte*)&publichandle, MegaClient::CHATLINKHANDLE); arg("ck", unifiedkey); tag = client->reqtag; } bool CommandChatLinkJoin::procresult(Result r, JSON&) { client->app->chatlinkjoin_result(r.errorOrOK()); return r.wasErrorOrOK(); } #endif CommandGetMegaAchievements::CommandGetMegaAchievements(MegaClient *client, AchievementsDetails *details, bool registered_user) { this->details = details; if (registered_user) { cmd("maf"); } else { cmd("mafu"); } arg("v", (m_off_t)0); tag = client->reqtag; } bool CommandGetMegaAchievements::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->getmegaachievements_result(details, r.errorOrOK()); return true; } details->permanent_size = 0; details->achievements.clear(); details->awards.clear(); details->rewards.clear(); for (;;) { switch (json.getnameid()) { case makeNameid("s"): details->permanent_size = json.getint(); break; case makeNameid("u"): if (json.enterobject()) { for (;;) { string idStr = json.getname(); if (idStr.empty()) { break; // no more "id"s to read } achievement_class_id id = achievement_class_id(std::stoi(idStr)); if (json.enterarray()) { Achievement achievement; achievement.storage = json.getint(); achievement.transfer = json.getint(); const char *exp_ts = json.getvalue(); char *pEnd = NULL; achievement.expire = int(strtol(exp_ts, &pEnd, 10)); if (*pEnd == 'm') { achievement.expire *= 30; } else if (*pEnd == 'y') { achievement.expire *= 365; } details->achievements[id] = achievement; while(json.storeobject()); json.leavearray(); } } json.leaveobject(); } else { LOG_err << "Failed to parse Achievements of MEGA achievements"; json.storeobject(); client->app->getmegaachievements_result(details, API_EINTERNAL); return false; } break; case makeNameid("a"): if (json.enterarray()) { while (json.enterobject()) { Award award; award.achievement_class = 0; award.award_id = 0; award.ts = 0; award.expire = 0; bool finished = false; while (!finished) { switch (json.getnameid()) { case makeNameid("a"): award.achievement_class = achievement_class_id(json.getint()); break; case makeNameid("r"): award.award_id = int(json.getint()); break; case makeNameid("ts"): award.ts = json.getint(); break; case makeNameid("e"): award.expire = json.getint(); break; case makeNameid("m"): if (json.enterarray()) { string email; while(json.storeobject(&email)) { award.emails_invited.push_back(email); } json.leavearray(); } break; case EOO: finished = true; break; default: json.storeobject(); break; } } details->awards.push_back(award); json.leaveobject(); } json.leavearray(); } else { LOG_err << "Failed to parse Awards of MEGA achievements"; json.storeobject(); client->app->getmegaachievements_result(details, API_EINTERNAL); return false; } break; case makeNameid("r"): if (json.enterobject()) { for (;;) { string idStr = json.getname(); if (idStr.empty()) { break; // no more "id"s to read } Reward reward; reward.award_id = std::stoi(idStr); // convert to number json.enterarray(); reward.storage = json.getint(); reward.transfer = json.getint(); const char *exp_ts = json.getvalue(); char *pEnd = NULL; reward.expire = int(strtol(exp_ts, &pEnd, 10)); if (*pEnd == 'm') { reward.expire *= 30; } else if (*pEnd == 'y') { reward.expire *= 365; } while(json.storeobject()); json.leavearray(); details->rewards.push_back(reward); } json.leaveobject(); } else { LOG_err << "Failed to parse Rewards of MEGA achievements"; json.storeobject(); client->app->getmegaachievements_result(details, API_EINTERNAL); return false; } break; case EOO: client->app->getmegaachievements_result(details, API_OK); return true; default: if (!json.storeobject()) { LOG_err << "Failed to parse MEGA achievements"; client->app->getmegaachievements_result(details, API_EINTERNAL); return false; } break; } } } CommandMediaCodecs::CommandMediaCodecs(MegaClient* c, Callback cb) { cmd("mc"); client = c; callback = cb; } bool CommandMediaCodecs::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { LOG_err << "mc result: " << error(r.errorOrOK()); return true; } if (!json.isnumeric()) { // It's wrongly formatted, consume this one so the next command can be processed. LOG_err << "mc response badly formatted"; return false; } int version = static_cast<int>(json.getint()); callback(client, json, version); return true; } CommandContactLinkCreate::CommandContactLinkCreate(MegaClient *client, bool renew) { mSeqtagArray = true; if (renew) { cmd("clr"); } else { cmd("clc"); } tag = client->reqtag; } bool CommandContactLinkCreate::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { handle h = json.gethandle(MegaClient::CONTACTLINKHANDLE); client->app->contactlinkcreate_result(API_OK, h); return true; } else if (r.wasErrorOrOK()) { client->app->contactlinkcreate_result(r.errorOrOK(), UNDEF); return true; } client->app->contactlinkcreate_result(API_EINTERNAL, UNDEF); return false; } CommandContactLinkQuery::CommandContactLinkQuery(MegaClient *client, handle h) { cmd("clg"); arg("cl", (byte*)&h, MegaClient::CONTACTLINKHANDLE); arg("b", 1); // return firstname/lastname in B64 tag = client->reqtag; } bool CommandContactLinkQuery::procresult(Result r, JSON& json) { handle h = UNDEF; string email; string firstname; string lastname; string avatar; if (r.wasErrorOrOK()) { client->app->contactlinkquery_result(r.errorOrOK(), h, &email, &firstname, &lastname, &avatar); return true; } for (;;) { switch (json.getnameid()) { case makeNameid("h"): h = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("e"): json.storeobject(&email); break; case makeNameid("fn"): json.storeobject(&firstname); break; case makeNameid("ln"): json.storeobject(&lastname); break; case makeNameid("+a"): json.storeobject(&avatar); break; case EOO: client->app->contactlinkquery_result(API_OK, h, &email, &firstname, &lastname, &avatar); return true; default: if (!json.storeobject()) { LOG_err << "Failed to parse query contact link response"; client->app->contactlinkquery_result(API_EINTERNAL, h, &email, &firstname, &lastname, &avatar); return false; } break; } } } CommandContactLinkDelete::CommandContactLinkDelete(MegaClient *client, handle h) { cmd("cld"); if (!ISUNDEF(h)) { arg("cl", (byte*)&h, MegaClient::CONTACTLINKHANDLE); } tag = client->reqtag; } bool CommandContactLinkDelete::procresult(Result r, JSON&) { client->app->contactlinkdelete_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandKeepMeAlive::CommandKeepMeAlive(MegaClient *client, int type, bool enable) { if (enable) { cmd("kma"); } else { cmd("kmac"); } arg("t", type); tag = client->reqtag; } bool CommandKeepMeAlive::procresult(Result r, JSON&) { client->app->keepmealive_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandMultiFactorAuthSetup::CommandMultiFactorAuthSetup(MegaClient *client, const char *pin) { mSeqtagArray = true; cmd("mfas"); if (pin) { arg("mfa", pin); } tag = client->reqtag; } bool CommandMultiFactorAuthSetup::procresult(Result r, JSON& json) { // don't call storeobject unless we are sure we should, as it could consume a top level `,` if (r.hasJsonItem()) { // code string is returned when mfa is not supplied in the request string code; if (json.storeobject(&code)) { client->app->multifactorauthsetup_result(&code, API_OK); return true; } } else if (r.wasErrorOrOK()) //[0] is valid response (returned when mfa is supplied in the request) { client->app->multifactorauthsetup_result(NULL, r.errorOrOK()); return true; } // if anything went wrong client->app->multifactorauthsetup_result(NULL, API_EINTERNAL); return false; // caller will reevaluate json to get to the next command } CommandMultiFactorAuthCheck::CommandMultiFactorAuthCheck(MegaClient *client, const char *email) { cmd("mfag"); arg("e", email); tag = client->reqtag; } bool CommandMultiFactorAuthCheck::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->multifactorauthcheck_result(r.errorOrOK()); return true; } if (json.isnumeric()) { client->app->multifactorauthcheck_result(static_cast<int>(json.getint())); return true; } else { client->app->multifactorauthcheck_result(API_EINTERNAL); return false; } } CommandMultiFactorAuthDisable::CommandMultiFactorAuthDisable(MegaClient *client, const char *pin) { cmd("mfad"); arg("mfa", pin); tag = client->reqtag; } bool CommandMultiFactorAuthDisable::procresult(Result r, JSON&) { client->app->multifactorauthdisable_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandGetPSA::CommandGetPSA(bool urlSupport, MegaClient *client) { cmd("gpsa"); if (urlSupport) { arg("w", 1); } tag = client->reqtag; } bool CommandGetPSA::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->getpsa_result(r.errorOrOK(), 0, NULL, NULL, NULL, NULL, NULL, NULL); return true; } int id = 0; string temp; string title, text, imagename, imagepath; string buttonlink, buttontext, url; for (;;) { switch (json.getnameid()) { case makeNameid("id"): id = int(json.getint()); break; case makeNameid("t"): json.storeobject(&temp); Base64::atob(temp, title); break; case makeNameid("d"): json.storeobject(&temp); Base64::atob(temp, text); break; case makeNameid("img"): json.storeobject(&imagename); break; case makeNameid("l"): json.storeobject(&buttonlink); break; case makeNameid("url"): json.storeobject(&url); break; case makeNameid("b"): json.storeobject(&temp); Base64::atob(temp, buttontext); break; case makeNameid("dsp"): json.storeobject(&imagepath); break; case EOO: imagepath.append(imagename); imagepath.append(".png"); client->app->getpsa_result(API_OK, id, &title, &text, &imagepath, &buttontext, &buttonlink, &url); return true; default: if (!json.storeobject()) { LOG_err << "Failed to parse get PSA response"; client->app->getpsa_result(API_EINTERNAL, 0, NULL, NULL, NULL, NULL, NULL, NULL); return false; } break; } } } CommandFetchTimeZone::CommandFetchTimeZone(MegaClient *client, const char *timezone, const char* timeoffset) { cmd("ftz"); arg("utz", timezone); arg("uo", timeoffset); tag = client->reqtag; } bool CommandFetchTimeZone::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->fetchtimezone_result(r.errorOrOK(), NULL, NULL, -1); return true; } string currenttz; int currentto; vector<string> timezones; vector<int> timeoffsets; string defaulttz; int defaulttzindex = -1; for (;;) { switch (json.getnameid()) { case makeNameid("choices"): if (json.enterobject()) { while (json.storeobject(¤ttz)) { currentto = int(json.getint()); timezones.push_back(currenttz); timeoffsets.push_back(currentto); } json.leaveobject(); } else if (!json.storeobject()) { LOG_err << "Failed to parse fetch time zone response"; client->app->fetchtimezone_result(API_EINTERNAL, NULL, NULL, -1); return false; } break; case makeNameid("default"): if (json.isnumeric()) { json.getint(); } else { json.storeobject(&defaulttz); } break; case EOO: if (!defaulttz.empty()) // default received as string { for (int i = 0; i < (int)timezones.size(); i++) { if (timezones[static_cast<size_t>(i)] == defaulttz) { defaulttzindex = i; break; } } } client->app->fetchtimezone_result(API_OK, &timezones, &timeoffsets, defaulttzindex); return true; default: if (!json.storeobject()) { LOG_err << "Failed to parse fetch time zone response"; client->app->fetchtimezone_result(API_EINTERNAL, NULL, NULL, -1); return false; } break; } } } CommandSetLastAcknowledged::CommandSetLastAcknowledged(MegaClient* client) { cmd("sla"); tag = client->reqtag; } bool CommandSetLastAcknowledged::procresult(Result r, JSON&) { if (r.succeeded()) { client->useralerts.acknowledgeAllSucceeded(); } client->app->acknowledgeuseralerts_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandSMSVerificationSend::CommandSMSVerificationSend(MegaClient* client, const string& phoneNumber, bool reVerifyingWhitelisted) { cmd("smss"); batchSeparately = true; // don't let any other commands that might get batched with it cause the whole batch to fail assert(isPhoneNumber(phoneNumber)); arg("n", phoneNumber.c_str()); if (reVerifyingWhitelisted) { arg("to", 1); // test override } tag = client->reqtag; } bool CommandSMSVerificationSend::isPhoneNumber(const string& s) { for (auto i = s.size(); i--; ) { if (!(is_digit(static_cast<unsigned int>(s[i])) || (i == 0 && s[i] == '+'))) { return false; } } return s.size() > 6; } bool CommandSMSVerificationSend::procresult(Result r, JSON&) { client->app->smsverificationsend_result(r.errorOrOK()); return r.wasErrorOrOK(); } CommandSMSVerificationCheck::CommandSMSVerificationCheck(MegaClient* client, const string& verificationcode) { mSeqtagArray = true; cmd("smsv"); batchSeparately = true; // don't let any other commands that might get batched with it cause the whole batch to fail if (isVerificationCode(verificationcode)) { arg("c", verificationcode.c_str()); } tag = client->reqtag; } bool CommandSMSVerificationCheck::isVerificationCode(const string& s) { for (const char c : s) { if (!is_digit(static_cast<unsigned int>(c))) { return false; } } return s.size() == 6; } bool CommandSMSVerificationCheck::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { string phoneNumber; if (json.storeobject(&phoneNumber)) { assert(CommandSMSVerificationSend::isPhoneNumber(phoneNumber)); client->mSmsVerifiedPhone = phoneNumber; client->app->smsverificationcheck_result(API_OK, &phoneNumber); return true; } } else if (r.wasErrorOrOK()) { client->app->smsverificationcheck_result(r.errorOrOK(), nullptr); return true; } client->app->smsverificationcheck_result(API_EINTERNAL, nullptr); return false; } CommandGetCountryCallingCodes::CommandGetCountryCallingCodes(MegaClient* client) { cmd("smslc"); batchSeparately = true; tag = client->reqtag; } bool CommandGetCountryCallingCodes::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->getcountrycallingcodes_result(r.errorOrOK(), nullptr); return true; } map<string, vector<string>> countryCallingCodes; bool success = true; while (json.enterobject()) { bool exit = false; string countryCode; vector<string> callingCodes; while (!exit) { switch (json.getnameid()) { case makeNameid("cc"): { json.storeobject(&countryCode); break; } case makeNameid("l"): { if (json.enterarray()) { std::string code; while (json.storeobject(&code)) { callingCodes.emplace_back(std::move(code)); } json.leavearray(); } break; } case EOO: { if (countryCode.empty() || callingCodes.empty()) { LOG_err << "Missing or empty fields when parsing 'get country calling codes' response"; success = false; } else { countryCallingCodes.emplace(make_pair(std::move(countryCode), std::move(callingCodes))); } exit = true; break; } default: { if (!json.storeobject()) { LOG_err << "Failed to parse 'get country calling codes' response"; client->app->getcountrycallingcodes_result(API_EINTERNAL, nullptr); return false; } } } } json.leaveobject(); } if (success) { client->app->getcountrycallingcodes_result(API_OK, &countryCallingCodes); return true; } else { client->app->getcountrycallingcodes_result(API_EINTERNAL, nullptr); return false; } } CommandFolderLinkInfo::CommandFolderLinkInfo(MegaClient* client, handle publichandle) { ph = publichandle; cmd("pli"); arg("ph", (byte*)&publichandle, MegaClient::NODEHANDLE); tag = client->reqtag; } bool CommandFolderLinkInfo::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->folderlinkinfo_result(r.errorOrOK(), UNDEF, UNDEF, NULL, NULL, 0, 0, 0, 0, 0); return true; } string attr; string key; handle owner = UNDEF; handle parentHandle = 0; m_off_t currentSize = 0; m_off_t versionsSize = 0; int numFolders = 0; int numFiles = 0; int numVersions = 0; for (;;) { switch (json.getnameid()) { case makeNameid("attrs"): json.storeobject(&attr); break; case makeNameid("ph"): parentHandle = json.gethandle(MegaClient::NODEHANDLE); break; case makeNameid("u"): owner = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("s"): if (json.enterarray()) { currentSize = json.getint(); numFiles = int(json.getint()); numFolders = int(json.getint()); versionsSize = json.getint(); numVersions = int(json.getint()); json.leavearray(); } break; case makeNameid("k"): json.storeobject(&key); break; case EOO: if (attr.empty()) { LOG_err << "The folder link information doesn't contain the attr string"; client->app->folderlinkinfo_result(API_EINCOMPLETE, UNDEF, UNDEF, NULL, NULL, 0, 0, 0, 0, 0); return false; } if (key.size() <= 9 || key.find(":") == string::npos) { LOG_err << "The folder link information doesn't contain a valid decryption key"; client->app->folderlinkinfo_result(API_EKEY, UNDEF, UNDEF, NULL, NULL, 0, 0, 0, 0, 0); return false; } if (parentHandle != ph) { LOG_err << "Folder link information: public handle doesn't match"; client->app->folderlinkinfo_result(API_EINTERNAL, UNDEF, UNDEF, NULL, NULL, 0, 0, 0, 0, 0); return false; } client->app->folderlinkinfo_result(API_OK, owner, parentHandle, &attr, &key, currentSize, static_cast<uint32_t>(numFiles), static_cast<uint32_t>(numFolders), versionsSize, static_cast<uint32_t>(numVersions)); return true; default: if (!json.storeobject()) { LOG_err << "Failed to parse folder link information response"; client->app->folderlinkinfo_result(API_EINTERNAL, UNDEF, UNDEF, NULL, NULL, 0, 0, 0, 0, 0); return false; } break; } } } CommandBackupPut::CommandBackupPut(MegaClient* client, const BackupInfo& fields, std::function<void(Error, handle /*backup id*/)> completion) : mCompletion(completion) { mSeqtagArray = true; cmd("sp"); if (!ISUNDEF(fields.backupId)) { arg("id", (byte*)&fields.backupId, MegaClient::BACKUPHANDLE); } if (fields.type != BackupType::INVALID) { arg("t", fields.type); } if (!fields.nodeHandle.isUndef()) { arg("h", fields.nodeHandle); } if (!fields.localFolder.empty()) { string localFolderEncrypted(client->cypherTLVTextWithMasterKey("lf", fields.localFolder.toPath(false))); arg("l", localFolderEncrypted.c_str()); } if (!fields.deviceId.empty()) { arg("d", fields.deviceId.c_str()); } if (!ISUNDEF(fields.driveId)) { arg("dr", (byte*)&fields.driveId, MegaClient::DRIVEHANDLE); } if (fields.state >= 0) { arg("s", fields.state); } if (fields.subState >= 0) { arg("ss", fields.subState); } if (!fields.backupName.empty()) { string edEncrypted(client->cypherTLVTextWithMasterKey("bn", fields.backupName)); arg("e", edEncrypted.c_str()); } tag = client->reqtag; } bool CommandBackupPut::procresult(Result r, JSON& json) { if (r.hasJsonItem()) { handle backupId = json.gethandle(MegaClient::BACKUPHANDLE); if (mCompletion) mCompletion(API_OK, backupId); client->app->backupput_result(API_OK, backupId); return true; } else if (r.wasErrorOrOK()) { assert(r.errorOrOK() != API_EARGS); // if this happens, the API rejected the request because it wants more fields supplied if (mCompletion) mCompletion(r.errorOrOK(), UNDEF); client->app->backupput_result(r.errorOrOK(), UNDEF); return true; } if (mCompletion) mCompletion(API_EINTERNAL, UNDEF); client->app->backupput_result(API_EINTERNAL, UNDEF); return false; } CommandBackupPutHeartBeat::CommandBackupPutHeartBeat(MegaClient* client, handle backupId, SPHBStatus status, int8_t progress, uint32_t uploads, uint32_t downloads, m_time_t ts, handle lastNode, std::function<void(Error)> f) : mCompletion(f) { cmd("sphb"); arg("id", (byte*)&backupId, MegaClient::BACKUPHANDLE); arg("s", uint8_t(status)); if (status == SPHBStatus::SYNCING || status == SPHBStatus::UPTODATE) { // so don't send 0 out of 0 0% initially assert(progress >= 0); assert(progress <= 100); arg("p", progress); } arg("qu", uploads); arg("qd", downloads); if (ts != -1) { arg("lts", ts); } if (!ISUNDEF(lastNode)) { arg("lh", (byte*)&lastNode, MegaClient::NODEHANDLE); } tag = client->reqtag; } bool CommandBackupPutHeartBeat::procresult(Result r, JSON&) { if (mCompletion) mCompletion(r.errorOrOK()); return r.wasErrorOrOK(); } CommandBackupRemove::CommandBackupRemove(MegaClient* client, handle backupId, std::function<void(Error)> completion) { cmd("sr"); arg("id", (byte*)&backupId, MegaClient::BACKUPHANDLE); tag = client->reqtag; mCompletion = completion; } bool CommandBackupRemove::procresult(Result r, JSON&) { if (mCompletion) { mCompletion(r.errorOrOK()); } return r.wasErrorOrOK(); } CommandBackupSyncFetch::CommandBackupSyncFetch(std::function<void(const Error&, const vector<Data>&)> f) : completion(std::move(f)) { cmd("sf"); } bool CommandBackupSyncFetch::procresult(Result r, JSON& json) { vector<Data> data; if (!r.hasJsonArray()) { completion(r.errorOrOK(), data); } else { auto skipUnknownField = [&]() -> bool { if (!json.storeobject()) { completion(API_EINTERNAL, data); return false; } return true; }; auto cantLeaveObject = [&]() -> bool { if (!json.leaveobject()) { completion(API_EINTERNAL, data); return true; } return false; }; while (json.enterobject()) { data.push_back(Data()); for (;;) { auto& d = data.back(); auto nid = json.getnameid(); if (nid == EOO) break; switch (nid) { case makeNameid("id"): d.backupId = json.gethandle(sizeof(handle)); break; case makeNameid("t"): d.backupType = static_cast<BackupType>(json.getint32()); break; case makeNameid("h"): d.rootNode = json.gethandle(MegaClient::NODEHANDLE); break; case makeNameid("l"): json.storeobject(&d.localFolder); d.localFolder = client->decypherTLVTextWithMasterKey("lf", d.localFolder); break; case makeNameid("d"): json.storeobject(&d.deviceId); break; case makeNameid("dua"): json.storeobject(&d.deviceUserAgent); break; case makeNameid("s"): d.syncState = json.getint32(); break; case makeNameid("ss"): d.syncSubstate = json.getint32(); break; case makeNameid("e"): json.storeobject(&d.extra); d.backupName = client->decypherTLVTextWithMasterKey("bn", d.extra); break; case makeNameid("hb"): { if (json.enterobject()) { for (;;) { nid = json.getnameid(); if (nid == EOO) break; switch (nid) { case makeNameid("ts"): d.hbTimestamp = json.getuint64(); break; case makeNameid("s"): d.hbStatus = json.getint32(); break; case makeNameid("p"): d.hbProgress = json.getint32(); break; case makeNameid("qu"): d.uploads = json.getint32(); break; case makeNameid("qd"): d.downloads = json.getint32(); break; case makeNameid("lts"): d.lastActivityTs = json.getuint64(); break; case makeNameid("lh"): d.lastSyncedNodeHandle = json.gethandle(MegaClient::NODEHANDLE); break; default: if (!skipUnknownField()) return false; } } if (cantLeaveObject()) return false; } } break; default: if (!skipUnknownField()) return false; } } if (cantLeaveObject()) return false; } completion(API_OK, data); } return true; } CommandGetBanners::CommandGetBanners(MegaClient* client) { cmd("gban"); constexpr int GBAN_VERSION = 2; arg("v", GBAN_VERSION); // version tag = client->reqtag; } bool CommandGetBanners::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { client->app->getbanners_result(r.errorOrOK()); return true; // because parsing didn't fail } /* * [{ * "id": 2, ///The banner id * "t": "R2V0IFZlcmlmaWVk", ///Banner title * "d": "TWFrZSBpdCBlYXNpZXIgZm9yIHlvdXIgY29udGFjdHMgdG8gZmluZCB5b3Ugb24gTUVHQS4", * ///Banner description. * "img": "Verified_image.png", ///Image name. * "l": "", ///URL * "bimg": "Verified_BG.png", ///background image name. * "dsp": "https://domain/path" ///Where to get the image. * "v": 0, ///variant * "b": "" ///button * }, {"id":3, ...}, ... ] */ vector<BannerDetails> banners; // loop array elements while (json.enterobject()) { BannerDetails banner; bool exit = false; // loop and read object members while (!exit) { switch (json.getnameid()) { case makeNameid("id"): banner.id = json.getint32(); break; case makeNameid("t"): json.storeobject(&banner.title); banner.title = Base64::atob(banner.title); break; case makeNameid("d"): json.storeobject(&banner.description); banner.description = Base64::atob(banner.description); break; case makeNameid("img"): json.storeobject(&banner.image); break; case makeNameid("l"): json.storeobject(&banner.url); break; case makeNameid("bimg"): json.storeobject(&banner.backgroundImage); break; case makeNameid("dsp"): json.storeobject(&banner.imageLocation); break; case makeNameid("v"): banner.variant = json.getint32(); break; case makeNameid("b"): json.storeobject(&banner.button); banner.button = Base64::atob(banner.button); break; case EOO: if (!banner.id || banner.title.empty() || banner.description.empty()) { LOG_err << "Missing id, title or description in response to gban"; client->app->getbanners_result(API_EINTERNAL); return false; } exit = true; break; default: if (!json.storeobject()) // skip unknown member { LOG_err << "Failed to parse banners response"; client->app->getbanners_result(API_EINTERNAL); return false; } break; } } banners.emplace_back(std::move(banner)); json.leaveobject(); } client->app->getbanners_result(std::move(banners)); return true; } CommandDismissBanner::CommandDismissBanner(MegaClient* client, int id, m_time_t timestamp) { cmd("dban"); arg("id", id); // id of the Smart Banner arg("ts", timestamp); tag = client->reqtag; } bool CommandDismissBanner::procresult(Result r, JSON&) { client->app->dismissbanner_result(r.errorOrOK()); return r.wasErrorOrOK(); } // // Sets and Elements // bool CommandSE::procjsonobject(JSON& json, handle& id, m_time_t& ts, handle* u, m_time_t* cts, handle* s, int64_t* o, mega::PublicLinkSet* publicLinkSet, uint8_t* t) const { for (;;) { switch (json.getnameid()) { case makeNameid("id"): id = json.gethandle(MegaClient::SETHANDLE); break; case makeNameid("u"): { const auto buf = json.gethandle(MegaClient::USERHANDLE); if (u) *u = buf; } break; case makeNameid("s"): { const auto buf = json.gethandle(MegaClient::SETHANDLE); if (s) *s = buf; } break; case makeNameid("ts"): ts = json.getint(); break; case makeNameid("cts"): { const auto buf = json.getint(); if (cts) *cts = buf; } break; case makeNameid("o"): { const auto buf = json.getint(); if (o) *o = buf; } break; case makeNameid("ph"): { const auto buf = json.gethandle(MegaClient::PUBLICSETHANDLE); if (publicLinkSet) { publicLinkSet->setPublicId(buf); } } break; case makeNameid("t"): { const auto setType = static_cast<uint8_t>(json.getint()); if (t) *t = setType; } break; default: if (!json.storeobject()) { return false; } break; case EOO: return true; } } } bool CommandSE::procresultid(JSON& json, const Result& r, handle& id, m_time_t& ts, handle* u, m_time_t* cts, handle* s, int64_t* o, PublicLinkSet* publicLinkSet, uint8_t* t) const { return r.hasJsonObject() && procjsonobject(json, id, ts, u, cts, s, o, publicLinkSet, t); } bool CommandSE::procerrorcode(const Result& r, Error& e) const { if (r.wasErrorOrOK()) { e = r.errorOrOK(); return true; } return false; } bool CommandSE::procExtendedError(JSON& json, int64_t& errCode, handle& eid) const { int maxJsonAttrToCheck = 2; // shortcut to avoid processing the whole json object bool isErr = false; while (maxJsonAttrToCheck--) { switch (json.getnameid()) { case makeNameid("err"): { isErr = true; errCode = json.getint(); break; } case makeNameid("eid"): { eid = json.gethandle(MegaClient::SETELEMENTHANDLE); break; } default: return false; } } return isErr; } CommandPutSet::CommandPutSet(MegaClient* cl, Set&& s, unique_ptr<string> encrAttrs, string&& encrKey, std::function<void(Error, const Set*)> completion) : mSet(new Set(std::move(s))), mCompletion(completion) { mSeqtagArray = true; cmd("asp"); if (mSet->id() == UNDEF) // create new { arg("k", (byte*)encrKey.c_str(), encrKey.size()); arg("t", static_cast<m_off_t>(mSet->type())); } else // update { arg("id", (byte*)&mSet->id(), MegaClient::SETHANDLE); } if (encrAttrs) { arg("at", (byte*)encrAttrs->c_str(), encrAttrs->size()); } notself(cl); // don't process its Action Packet after sending this } bool CommandPutSet::procresult(Result r, JSON& json) { handle sId = 0; handle user = 0; m_time_t ts = 0; m_time_t cts = 0; const Set* s = nullptr; Error e = API_OK; bool parsedOk = procerrorcode(r, e) || procresultid(json, r, sId, ts, &user, &cts); if (!parsedOk || (mSet->id() == UNDEF && !user)) { e = API_EINTERNAL; } else if (e == API_OK) { mSet->setTs(ts); if (mSet->id() == UNDEF) // add new { mSet->setId(sId); mSet->setUser(user); mSet->setCTs(cts); mSet->setChanged(Set::CH_NEW); s = client->addSet(std::move(*mSet)); } else // update existing { assert(mSet->id() == sId); if (!client->updateSet(std::move(*mSet))) { LOG_warn << "Sets: command 'asp' succeed, but Set was not found"; e = API_ENOENT; } } } if (mCompletion) { mCompletion(e, s); } return parsedOk; } CommandRemoveSet::CommandRemoveSet(MegaClient* cl, handle id, std::function<void(Error)> completion) : mSetId(id), mCompletion(completion) { cmd("asr"); arg("id", (byte*)&id, MegaClient::SETHANDLE); notself(cl); // don't process its Action Packet after sending this } bool CommandRemoveSet::procresult(Result r, JSON&) { Error e = API_OK; bool parsedOk = procerrorcode(r, e); if (parsedOk && e == API_OK) { if (!client->deleteSet(mSetId)) { LOG_err << "Sets: Failed to remove Set in `asr` command response"; e = API_ENOENT; } } if (mCompletion) { mCompletion(e); } return parsedOk; } CommandFetchSet::CommandFetchSet(MegaClient* cl, std::function<void(Error, Set*, elementsmap_t*)> completion) : mCompletion(completion) { cmd("aft"); arg("v", 2); // version 2: server can supply node metadata if(!cl->inPublicSetPreview()) { LOG_err << "Sets: CommandFetchSet only available for Public Set in Preview Mode"; assert(cl->inPublicSetPreview()); } } bool CommandFetchSet::procresult(Result r, JSON& json) { Error e = API_OK; if (procerrorcode(r, e)) { if (mCompletion) { mCompletion(e, nullptr, nullptr); } return true; } map<handle, Set> sets; map<handle, elementsmap_t> elements; e = client->readSetsAndElements(json, sets, elements); if (e != API_OK) { LOG_err << "Sets: Failed to parse \"aft\" response"; if (mCompletion) { mCompletion(e, nullptr, nullptr); } return false; } assert(sets.size() <= 1); if (mCompletion) { if (sets.empty()) { LOG_err << "Sets: Failed to decrypt data from \"aft\" response"; mCompletion(API_EKEY, nullptr, nullptr); } else { Set* s = new Set(std::move(sets.begin()->second)); elementsmap_t* els = elements.empty() ? new elementsmap_t() : new elementsmap_t(std::move(elements.begin()->second)); mCompletion(API_OK, s, els); } } return true; } CommandPutSetElements::CommandPutSetElements(MegaClient* cl, vector<SetElement>&& els, vector<StringPair>&& encrDetails, std::function<void(Error, const vector<const SetElement*>*, const vector<int64_t>*)> completion) : mElements(new vector<SetElement>(std::move(els))), mCompletion(completion) { mSeqtagArray = true; cmd("aepb"); const byte* setHandleBytes = reinterpret_cast<const byte*>(&mElements->front().set()); arg("s", setHandleBytes, MegaClient::SETHANDLE); beginarray("e"); for (size_t i = 0; i < mElements->size(); ++i) { beginobject(); const byte* nodeHandleBytes = reinterpret_cast<const byte*>(&mElements->at(i).node()); arg("h", nodeHandleBytes, MegaClient::NODEHANDLE); auto& ed = encrDetails[i]; const byte* keyBytes = reinterpret_cast<const byte*>(ed.second.c_str()); arg("k", keyBytes, ed.second.size()); if (!ed.first.empty()) { const byte* attrBytes = reinterpret_cast<const byte*>(ed.first.c_str()); arg("at", attrBytes, ed.first.size()); } endobject(); } endarray(); notself(cl); // don't process its Action Packets after sending this } bool CommandPutSetElements::procresult(Result r, JSON& json) { Error e = API_OK; if (procerrorcode(r, e)) { if (mCompletion) { mCompletion(e, nullptr, nullptr); } return true; } else if (!r.hasJsonArray()) { LOG_err << "Sets: failed to parse `aepb` response"; if (mCompletion) { mCompletion(API_EINTERNAL, nullptr, nullptr); } return false; } bool allOk = true; vector<const SetElement*> addedEls; vector<int64_t> errs(mElements->size(), API_OK); for (size_t elCount = 0u; elCount < mElements->size(); ++elCount) { if (json.isnumeric()) { // there was an error while adding this element errs[elCount] = json.getint(); } else if (json.enterobject()) { const auto posAux = json.pos; handle errEid = UNDEF; if (procExtendedError(json, errs[elCount], errEid)) { if (errEid == UNDEF) LOG_warn << "Sets: Extended error missing Element id"; } else { json.pos = posAux; handle elementId = 0; m_time_t ts = 0; int64_t order = 0; if (!procjsonobject(json, elementId, ts, nullptr, nullptr, nullptr, &order)) { LOG_err << "Sets: failed to parse Element object in `aepb` response"; allOk = false; break; } SetElement& el = mElements->at(elCount); el.setId(elementId); el.setTs(ts); el.setOrder(order); addedEls.push_back(client->addOrUpdateSetElement(std::move(el))); } if (!json.leaveobject()) { LOG_err << "Sets: failed to leave Element object in `aepb` response"; allOk = false; break; } } else { LOG_err << "Sets: failed to parse Element array in `aepb` response"; allOk = false; break; } } if (mCompletion) { mCompletion(e, &addedEls, &errs); } return allOk; } CommandPutSetElement::CommandPutSetElement(MegaClient* cl, SetElement&& el, unique_ptr<string> encrAttrs, string&& encrKey, std::function<void(Error, const SetElement*)> completion) : mElement(new SetElement(std::move(el))), mCompletion(completion) { mSeqtagArray = true; cmd("aep"); bool createNew = mElement->id() == UNDEF; if (createNew) { arg("s", (byte*)&mElement->set(), MegaClient::SETHANDLE); arg("h", (byte*)&mElement->node(), MegaClient::NODEHANDLE); arg("k", (byte*)encrKey.c_str(), encrKey.size()); } else // update { arg("id", (byte*)&mElement->id(), MegaClient::SETELEMENTHANDLE); } // optionals if (mElement->hasOrder()) { arg("o", mElement->order()); } if (encrAttrs) { arg("at", (byte*)encrAttrs->c_str(), encrAttrs->size()); } notself(cl); // don't process its Action Packet after sending this } bool CommandPutSetElement::procresult(Result r, JSON& json) { handle elementId = 0; m_time_t ts = 0; int64_t order = 0; Error e = API_OK; #ifndef NDEBUG bool isNew = mElement->id() == UNDEF; #endif const SetElement* el = nullptr; bool parsedOk = procerrorcode(r, e) || procresultid(json, r, elementId, ts, nullptr, nullptr, nullptr, &order); // 'aep' does not return 's' if (!parsedOk) { e = API_EINTERNAL; } else if (e == API_OK) { mElement->setTs(ts); mElement->setOrder(order); // this is now present in all 'aep' responses assert(isNew || mElement->id() == elementId); mElement->setId(elementId); el = client->addOrUpdateSetElement(std::move(*mElement)); } if (mCompletion) { mCompletion(e, el); } return parsedOk; } CommandRemoveSetElements::CommandRemoveSetElements(MegaClient* cl, handle sid, vector<handle>&& eids, std::function<void(Error, const vector<int64_t>*)> completion) : mSetId(sid), mElemIds(std::move(eids)), mCompletion(completion) { cmd("aerb"); arg("s", reinterpret_cast<const byte*>(&sid), MegaClient::SETHANDLE); beginarray("e"); for (auto& eh : mElemIds) { element(reinterpret_cast<const byte*>(&eh), MegaClient::SETELEMENTHANDLE); } endarray(); notself(cl); // don't process its Action Packet after sending this } bool CommandRemoveSetElements::procresult(Result r, JSON& json) { Error e = API_OK; if (procerrorcode(r, e)) { if (mCompletion) { mCompletion(e, nullptr); } return true; } else if (!r.hasJsonArray()) { LOG_err << "Sets: failed to parse `aerb` response"; if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return false; } bool jsonOk = true; vector<int64_t> errs(mElemIds.size()); for (size_t elCount = 0u; elCount < mElemIds.size(); ++elCount) { if (json.isnumeric()) { errs[elCount] = json.getint(); } else if (json.enterobject()) { handle errEid = UNDEF; if (procExtendedError(json, errs[elCount], errEid)) { if (errEid == UNDEF) LOG_warn << "Sets: Extended error missing Element id in `aerb`"; } else { jsonOk = false; } if (!json.leaveobject()) { LOG_err << "Sets: failed to parse Element object in `aerb` response"; jsonOk = false; } } else { LOG_err << "Sets: failed to parse Element removal response in `aerb` command response"; jsonOk = false; } if (!jsonOk) break; if (errs[elCount] == API_OK && !client->deleteSetElement(mSetId, mElemIds[elCount])) { LOG_err << "Sets: Failed to remove Element in `aerb` command response"; errs[elCount] = API_ENOENT; } } if (mCompletion) { mCompletion(e, &errs); } return jsonOk; } CommandRemoveSetElement::CommandRemoveSetElement(MegaClient* cl, handle sid, handle eid, std::function<void(Error)> completion) : mSetId(sid), mElementId(eid), mCompletion(completion) { mSeqtagArray = true; cmd("aer"); arg("id", (byte*)&eid, MegaClient::SETELEMENTHANDLE); notself(cl); // don't process its Action Packet after sending this } bool CommandRemoveSetElement::procresult(Result r, JSON& json) { handle elementId = 0; m_time_t ts = 0; Error e = API_OK; bool parsedOk = procerrorcode(r, e) || procresultid(json, r, elementId, ts, nullptr); if (parsedOk && e == API_OK) { if (!client->deleteSetElement(mSetId, mElementId)) { LOG_err << "Sets: Failed to remove Element in `aer` command response"; e = API_ENOENT; } } if (mCompletion) { mCompletion(e); } return parsedOk; } CommandExportSet::CommandExportSet(MegaClient* cl, Set&& s, bool makePublic, std::function<void(Error)> completion) : mSet(new Set(std::move(s))), mCompletion(completion) { mSeqtagArray = true; cmd("ass"); arg("id", (byte*)&mSet->id(), MegaClient::SETHANDLE); if (!makePublic) arg("d", 1); notself(cl); } bool CommandExportSet::procresult(Result r, JSON& json) { handle sid = mSet->id(); m_time_t ts = m_time(nullptr); // made it up for case that API returns [0] (like for "d":1) Error e = API_OK; PublicLinkSet publicLinkSet; const bool parsedOk = procerrorcode(r, e) || procresultid(json, r, sid, ts, nullptr, nullptr, nullptr, nullptr, &publicLinkSet); if (sid != mSet->id()) { LOG_err << "Sets: command 'ass' in processing result. Received Set id " << toHandle(sid) << " expected Set id " << toHandle(mSet->id()); assert(false); } if ((parsedOk) && e == API_OK) { mSet->setPublicLink(&publicLinkSet); mSet->setTs(ts); mSet->setChanged(Set::CH_EXPORTED); if (!client->updateSet(std::move(*mSet))) { LOG_warn << "Sets: command 'ass' succeeded, but Set was not found"; e = API_ENOENT; } } if (mCompletion) mCompletion(e); return parsedOk; } // -------- end of Sets and Elements #ifdef ENABLE_CHAT bool CommandMeetingStart::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), "", UNDEF); return true; } handle callid = UNDEF; string sfuUrl; for (;;) { switch (json.getnameid()) { case makeNameid("callId"): callid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("sfu"): json.storeobject(&sfuUrl); break; case EOO: mCompletion(API_OK, sfuUrl, callid); return true; break; default: if (!json.storeobject()) { mCompletion(API_EINTERNAL, "", UNDEF); return false; } } } } CommandMeetingStart::CommandMeetingStart(MegaClient* client, const handle chatid, const bool notRinging, CommandMeetingStartCompletion completion) : mCompletion(completion) { cmd("mcms"); arg("cid", (byte*)&chatid, MegaClient::CHATHANDLE); if (client->mSfuid != sfu_invalid_id) { arg("sfu", client->mSfuid); } if (notRinging) { arg("nr", 1); } tag = client->reqtag; } bool CommandMeetingJoin::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), ""); return true; } string sfuUrl; for (;;) { switch (json.getnameid()) { case makeNameid("url"): json.storeobject(&sfuUrl); break; case EOO: mCompletion(API_OK, sfuUrl); return true; break; default: if (!json.storeobject()) { mCompletion(API_EINTERNAL, ""); return false; } } } } CommandMeetingJoin::CommandMeetingJoin(MegaClient *client, handle chatid, handle callid, CommandMeetingJoinCompletion completion) : mCompletion(completion) { cmd("mcmj"); arg("cid", (byte*)&chatid, MegaClient::CHATHANDLE); arg("mid", (byte*)&callid, MegaClient::CHATHANDLE); tag = client->reqtag; } bool CommandMeetingEnd::procresult(Command::Result r, JSON&) { mCompletion(r.errorOrOK()); return r.wasErrorOrOK(); } CommandMeetingEnd::CommandMeetingEnd(MegaClient *client, handle chatid, handle callid, int reason, CommandMeetingEndCompletion completion) : mCompletion(completion) { cmd("mcme"); arg("cid", (byte*)&chatid, MegaClient::CHATHANDLE); arg("mid", (byte*)&callid, MegaClient::CHATHANDLE); // At meeting first version, only valid reason is 0x02 (REJECTED) arg("r", reason); tag = client->reqtag; } bool CommandRingUser::procresult(Command::Result r, JSON&) { mCompletion(r.errorOrOK()); return r.wasErrorOrOK(); } CommandRingUser::CommandRingUser(MegaClient* client, handle chatid, handle userid, CommandRingUserCompletion completion) : mCompletion(completion) { cmd("mcru"); arg("u", reinterpret_cast<byte*>(&userid), MegaClient::CHATHANDLE); arg("cid", reinterpret_cast<byte*>(&chatid), MegaClient::CHATHANDLE); tag = client->reqtag; } CommandScheduledMeetingAddOrUpdate::CommandScheduledMeetingAddOrUpdate(MegaClient* client, const ScheduledMeeting *schedMeeting, const char* chatTitle, CommandScheduledMeetingAddOrUpdateCompletion completion) : mScheduledMeeting(schedMeeting->copy()), mCompletion(completion) { assert(schedMeeting); cmd("mcsmp"); arg("v", 1); // add version to receive cmd array // this one does produce an `st`, with a json {object} after mSeqtagArray = true; if (chatTitle && strlen(chatTitle)) { // update chatroom title along with sm title mChatTitle.assign(chatTitle, strlen(chatTitle)); arg("ct", mChatTitle.c_str()); } createSchedMeetingJson(mScheduledMeeting.get()); notself(client); // set i param to ignore action packet generated by our own action tag = client->reqtag; } bool CommandScheduledMeetingAddOrUpdate::procresult(Command::Result r, JSON& json) { assert(mScheduledMeeting); if (r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK(), nullptr); } return true; } bool exit = false; handle schedId = UNDEF; handle_set childMeetingsDeleted; while (!exit) { switch (json.getnameid()) { case makeNameid("cmd"): { if (json.enterarray()) { while(json.ishandle(MegaClient::CHATHANDLE)) { childMeetingsDeleted.insert(json.gethandle()); } json.leavearray(); } else { if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return false; } break; } case makeNameid("id"): schedId = json.gethandle(MegaClient::CHATHANDLE); mScheduledMeeting->setSchedId(schedId); break; case EOO: exit = true; break; default: if (!json.storeobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return false; } } } // sanity checks for scheduled meeting if (!mScheduledMeeting || !mScheduledMeeting->isValid()) { if (mScheduledMeeting) { client->reportInvalidSchedMeeting(mScheduledMeeting.get()); } if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return true; } auto it = client->chats.find(mScheduledMeeting->chatid()); if (it == client->chats.end()) { if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return true; } TextChat* chat = it->second; // remove child scheduled meetings in cmd (child meetings deleted) array chat->removeSchedMeetingsList(childMeetingsDeleted); // clear scheduled meeting occurrences for the chat client->clearSchedOccurrences(*chat); // update chat title if (!mChatTitle.empty()) { chat->setTitle(mChatTitle.c_str()); } // add scheduled meeting const bool added = chat->addOrUpdateSchedMeeting(std::unique_ptr<ScheduledMeeting>(mScheduledMeeting->copy())); // add or update scheduled meeting if already exists // notify chat chat->setTag(tag ? tag : -1); client->notifychat(chat); if (mCompletion) { mCompletion(added ? API_OK : API_EINTERNAL, mScheduledMeeting.get()); } return true; } CommandScheduledMeetingRemove::CommandScheduledMeetingRemove(MegaClient* client, handle chatid, handle schedMeeting, CommandScheduledMeetingRemoveCompletion completion) : mChatId(chatid), mSchedId(schedMeeting), mCompletion(completion) { cmd("mcsmr"); arg("id", (byte*) &schedMeeting, MegaClient::CHATHANDLE); // scheduled meeting handle notself(client); // set i param to ignore action packet generated by our own action tag = client->reqtag; } bool CommandScheduledMeetingRemove::procresult(Command::Result r, JSON&) { if (!r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK()); } return false; } if (r.wasError(API_OK)) { auto it = client->chats.find(mChatId); if (it == client->chats.end()) { if (mCompletion) { mCompletion(API_EINTERNAL); } return true; } // remove scheduled meeting and all it's children TextChat* chat = it->second; if (chat->removeSchedMeeting(mSchedId)) { // remove children scheduled meetings (API requirement) chat->removeChildSchedMeetings(mSchedId); } client->clearSchedOccurrences(*chat); chat->setTag(tag ? tag : -1); client->notifychat(chat); } if (mCompletion) { mCompletion(r.errorOrOK()); } return true; } CommandScheduledMeetingFetch::CommandScheduledMeetingFetch( MegaClient* client, handle chatid, handle schedMeeting, CommandScheduledMeetingFetchCompletion completion): mCompletion(completion) { cmd("mcsmf"); if (schedMeeting != UNDEF) { arg("id", (byte*) &schedMeeting, MegaClient::CHATHANDLE); } if (chatid != UNDEF) { arg("cid", (byte*) &chatid, MegaClient::CHATHANDLE); } tag = client->reqtag; } bool CommandScheduledMeetingFetch::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK(), nullptr); } return true; } std::vector<std::unique_ptr<ScheduledMeeting>> schedMeetings; error err = client->parseScheduledMeetings(schedMeetings, false /*parsingOccurrences*/, &json); if (mCompletion) { mCompletion(err, err == API_OK ? &schedMeetings : nullptr); } return err == API_OK; } CommandScheduledMeetingFetchEvents::CommandScheduledMeetingFetchEvents(MegaClient* client, handle chatid, m_time_t since, m_time_t until, unsigned int count, bool byDemand, CommandScheduledMeetingFetchEventsCompletion completion) : mChatId(chatid), mByDemand(byDemand), mCompletion(completion ? completion : [](Error, const std::vector<std::unique_ptr<ScheduledMeeting>>*){}) { cmd("mcsmfo"); arg("cid", (byte*) &chatid, MegaClient::CHATHANDLE); if (isValidTimeStamp(since)) { arg("cf", since); } if (isValidTimeStamp(until)) { arg("ct", until); } if (count) { arg("cc", count); } tag = client->reqtag; } bool CommandScheduledMeetingFetchEvents::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK(), nullptr); } return true; } std::vector<std::unique_ptr<ScheduledMeeting>> schedMeetings; error err = client->parseScheduledMeetings(schedMeetings, true /*parsingOccurrences*/, &json); if (err) { if (mCompletion) { mCompletion(err, nullptr); } return false; } auto it = client->chats.find(mChatId); if (it == client->chats.end()) { if (mCompletion) { mCompletion(API_EINTERNAL, nullptr); } return true; } TextChat* chat = it->second; // clear list in case it contains any element chat->clearUpdatedSchedMeetingOccurrences(); // add received scheduled meetings occurrences from API into mUpdatedOcurrences to be notified for (auto& schedMeeting: schedMeetings) { chat->addUpdatedSchedMeetingOccurrence(std::unique_ptr<ScheduledMeeting>(schedMeeting->copy())); } // set the change type although we haven't received any occurrences (but there's no error and json proccessing has been succesfull) if (mByDemand) { chat->changed.schedOcurrAppend = true; } else { chat->changed.schedOcurrReplace = true; } // just notify once, for all ocurrences received for the same chat chat->setTag(tag ? tag : -1); client->notifychat(chat); if (mCompletion) { mCompletion(API_OK, &schedMeetings); } return true; } #endif bool CommandFetchAds::procresult(Command::Result r, JSON& json) { string_map ads; if (r.wasStrictlyError()) { mCompletion(r.errorOrOK(), ads); return true; } bool error = false; for (auto adUnit = std::begin(mAdUnits); adUnit != std::end(mAdUnits) && !error; ++adUnit) { if (json.isnumeric()) { // -9 or any other error for the provided ad unit (error results order must match) ads[*adUnit] = std::to_string(json.getint()); } else if (json.enterobject()) { std::string id; std::string iu; bool exit = false; while (!exit) { switch (json.getnameid()) { case makeNameid("id"): json.storeobject(&id); break; case makeNameid("src"): json.storeobject(&iu); break; case EOO: exit = true; if (!id.empty() && !iu.empty()) { assert(id == *adUnit); ads[id] = iu; } else { error = true; ads.clear(); } break; default: if (!json.storeobject()) { ads.clear(); mCompletion(API_EINTERNAL, ads); return false; } break; } } json.leaveobject(); } else { ads.clear(); error = true; } } mCompletion((error ? API_EINTERNAL : API_OK), ads); return !error; } CommandFetchAds::CommandFetchAds(MegaClient* client, int adFlags, const std::vector<std::string> &adUnits, handle publicHandle, CommandFetchAdsCompletion completion) : mCompletion(completion), mAdUnits(adUnits) { cmd("adf"); arg("ad", adFlags); arg("af", 1); // ad format: URL if (!ISUNDEF(publicHandle)) { arg("ph", NodeHandle().set6byte(publicHandle)); } beginarray("au"); for (const std::string& adUnit : adUnits) { element(adUnit.c_str()); } endarray(); tag = client->reqtag; } bool CommandQueryAds::procresult(Command::Result r, JSON &json) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), 0); return true; } if (!json.isnumeric()) { // It's wrongly formatted, consume this one so the next command can be processed. LOG_err << "Command response badly formatted"; mCompletion(API_EINTERNAL, 0); return false; } int value = json.getint32(); mCompletion(API_OK, value); return true; } CommandQueryAds::CommandQueryAds(MegaClient* client, int adFlags, handle publicHandle, CommandQueryAdsCompletion completion) : mCompletion(completion) { cmd("ads"); arg("ad", adFlags); if (!ISUNDEF(publicHandle)) { arg("ph", NodeHandle().set6byte(publicHandle)); } tag = client->reqtag; } /* MegaVPN Commands BEGIN */ CommandGetVpnRegions::CommandGetVpnRegions(MegaClient* client, Cb&& completion) : mCompletion(std::move(completion)) { cmd("vpnr"); arg("v", 5); // include the DNS targets for each cluster in each region in response arg("t", 1); // Retrieve server location information tag = client->reqtag; } bool CommandGetVpnRegions::parseRegions(JSON& json, vector<VpnRegion>* vpnRegions) { string buffer; string* pBuffer = vpnRegions ? &buffer : nullptr; for (; json.storeobject(pBuffer);) // Iterate over regions { if (*json.pos == ':') // work around lack of functionality in enterobject() ++json.pos; if (!json.enterobject()) return false; std::optional<VpnRegion> region{vpnRegions ? std::make_optional(std::move(buffer)) : std::nullopt}; buffer.clear(); for (bool finishedRegion = false; !finishedRegion;) { switch (json.getnameid()) { case makeNameid("c"): // Clusters list if (!json.enterobject()) // Enter clusters object { return false; } if (!CommandGetVpnRegions::parseClusters(json, region ? ®ion.value() : nullptr)) { return false; } if (!json.leaveobject()) // leave clusters object { return false; } break; case makeNameid("cc"): // Country code { string countryCode; json.storeobject(&countryCode); if (region) { region->setCountryCode(std::move(countryCode)); } } break; case makeNameid("cn"): // Country name { string countryName; json.storeobject(&countryName); if (region) { region->setCountryName(std::move(countryName)); } } break; case makeNameid("rn"): // Region name (optional) { string regionName; json.storeobject(®ionName); if (region) { region->setRegionName(std::move(regionName)); } } break; case makeNameid("tn"): // Town name (optional) { string townName; json.storeobject(&townName); if (region) { region->setTownName(std::move(townName)); } } break; default: if (!json.storeobject()) { return false; } break; case EOO: // Mandatory values can't be empty. if (region && (region->getCountryCode().empty() || region->getCountryName().empty())) { return false; } finishedRegion = true; break; } } // Leave region object if (!json.leaveobject()) return false; if (region) { vpnRegions->emplace_back(std::move(region.value())); } } return true; } bool CommandGetVpnRegions::parseClusters(JSON& json, VpnRegion* vpnRegion) { string buffer; string* pBuffer = vpnRegion ? &buffer : nullptr; for (; json.storeobject(pBuffer);) // cluster ID { int clusterID{}; if (vpnRegion) { auto [ptr, ec] = std::from_chars(buffer.c_str(), buffer.c_str() + buffer.size(), clusterID); if (ec != std::errc()) return false; } if (*json.pos == ':') ++json.pos; if (!json.enterobject()) return false; std::optional<string> host{vpnRegion ? std::make_optional<string>() : std::nullopt}; std::optional<vector<string>> dns{vpnRegion ? std::make_optional<vector<string>>() : std::nullopt}; std::optional<vector<string>> afdns{vpnRegion ? std::make_optional<vector<string>>() : std::nullopt}; for (bool hasData = true; hasData;) // host, dns { switch (json.getnameid()) { case makeNameid("h"): if (!json.storeobject(pBuffer)) return false; if (host) { host = std::move(buffer); buffer.clear(); } break; case makeNameid("dns"): if (!json.enterarray()) return false; while (json.storeobject(pBuffer)) { if (dns) { dns->emplace_back(std::move(buffer)); buffer.clear(); } } if (!json.leavearray()) return false; break; case makeNameid("afdns"): if (!json.enterarray()) return false; while (json.storeobject(pBuffer)) { if (afdns) { afdns->emplace_back(std::move(buffer)); buffer.clear(); } } if (!json.leavearray()) return false; break; case EOO: hasData = false; break; default: if (!json.storeobject()) return false; } } if (!json.leaveobject()) return false; if (host && dns && afdns) { vpnRegion->addCluster( clusterID, {std::move(host.value()), std::move(dns.value()), std::move(afdns.value())}); } } return true; } bool CommandGetVpnRegions::procresult(Command::Result r, JSON& json) { if (!r.hasJsonObject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}); } return false; } // Parse regions vector<VpnRegion> vpnRegions; if (parseRegions(json, &vpnRegions)) { mCompletion(API_OK, std::move(vpnRegions)); return true; } else { mCompletion(API_EINTERNAL, {}); return false; } } CommandGetVpnCredentials::CommandGetVpnCredentials(MegaClient* client, Cb&& completion) : mCompletion(std::move(completion)) { cmd("vpng"); arg("v", 5); // include the DNS targets for each cluster in each region in response tag = client->reqtag; } bool CommandGetVpnCredentials::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK(), {}, {}, {}); } return true; } Error e(API_EINTERNAL); MapSlotIDToCredentialInfo mapSlotIDToCredentialInfo; MapClusterPublicKeys mapClusterPubKeys; { // Parse ClusterID and IPs if (json.enterobject()) { string slotIDStr; bool parsedOk = true; while (parsedOk) { slotIDStr = json.getname(); if (slotIDStr.empty()) { break; } int slotID = -1; try { slotID = std::stoi(slotIDStr); } catch (std::exception const &ex) { LOG_err << "[CommandGetVpnCredentials] Could not convert param SlotID(" << slotIDStr << ") to integer. Exception: " << ex.what(); parsedOk = false; } if (parsedOk && json.enterarray()) { CredentialInfo credentialInfo; credentialInfo.clusterID = static_cast<int>(json.getint()); parsedOk = credentialInfo.clusterID != -1; parsedOk = parsedOk && json.storeobject(&credentialInfo.ipv4); parsedOk = parsedOk && json.storeobject(&credentialInfo.ipv6); parsedOk = parsedOk && json.storeobject(&credentialInfo.deviceID); if (parsedOk) { mapSlotIDToCredentialInfo.emplace(std::make_pair(slotID, std::move(credentialInfo))); } json.leavearray(); } } if (!parsedOk) { // There were credentials, but something was wrong with the JSON if (mCompletion) { mCompletion(e, {}, {}, {}); } return false; } json.leaveobject(); } else { // There should be a valid object at this point if (mCompletion) { mCompletion(e, {}, {}, {}); } return false; } // Parse Cluster Public Keys if (json.enterobject()) { bool parsedOk = true; while (parsedOk) { std::string clusterIDStr = json.getname(); if (clusterIDStr.empty()) { break; } int clusterID = -1; try { clusterID = std::stoi(clusterIDStr); } catch (std::exception const &ex) { LOG_err << "[CommandGetVpnCredentials] Could not convert param ClusterID(" << clusterIDStr << ") to integer. Exception: " << ex.what(); parsedOk = false; } if (parsedOk) { std::string clusterPubKey; if (!json.storeobject(&clusterPubKey)) { parsedOk = false; break; } mapClusterPubKeys.emplace(std::make_pair(clusterID, clusterPubKey)); } } if (!parsedOk) { // There were credentials and a valid ClusterID, but something was wrong with the Cluster Public Key value if (mCompletion) { mCompletion(e, {}, {}, {}); } return false; } json.leaveobject(); } else { // There were credentials, but there were no information regarding the Cluster Public Key(s) if (mCompletion) { mCompletion(e, {}, {}, {}); } return false; } } // Finally, parse VPN regions vector<VpnRegion> vpnRegions; if (json.enterobject() && CommandGetVpnRegions::parseRegions(json, &vpnRegions) && json.leaveobject()) { mCompletion(API_OK, std::move(mapSlotIDToCredentialInfo), std::move(mapClusterPubKeys), std::move(vpnRegions)); return true; } else { mCompletion(API_EINTERNAL, {}, {}, {}); return false; } } CommandPutVpnCredential::CommandPutVpnCredential(MegaClient* client, std::string&& region, StringKeyPair&& userKeyPair, Cb&& completion) : mRegion(std::move(region)), mUserKeyPair(std::move(userKeyPair)), mCompletion(std::move(completion)) { cmd("vpnp"); arg("k", (byte*)mUserKeyPair.pubKey.c_str(), mUserKeyPair.pubKey.size()); arg("v", 5); // include the DNS targets for each cluster in each region in response tag = client->reqtag; } bool CommandPutVpnCredential::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { if (mCompletion) { mCompletion(r.errorOrOK(), -1, {}, {}); } return true; } if (!r.hasJsonArray()) { if (mCompletion) { mCompletion(API_EINTERNAL, -1, {}, {}); } return false; } // We receive directly one array here (like in CommandGetVpnRegions), so we are inside the array already // Parse SlotID int slotID = static_cast<int>(json.getint()); // Parse ClusterID int clusterID = static_cast<int>(json.getint()); // Parse IPv4 std::string ipv4; if (!json.storeobject(&ipv4)) { if (mCompletion) { mCompletion(API_EINTERNAL, -1, {}, {}); } return false; } // Parse IPv6 std::string ipv6; if (!json.storeobject(&ipv6)) { if (mCompletion) { mCompletion(API_EINTERNAL, -1, {}, {}); } return false; } // Parse Cluster Public Key std::string clusterPubKey; if (!json.storeobject(&clusterPubKey)) { if (mCompletion) { mCompletion(API_EINTERNAL, -1, {}, {}); } return false; } // Parse VPN regions vector<VpnRegion> vpnRegions; if (!json.enterobject() || !CommandGetVpnRegions::parseRegions(json, &vpnRegions) || !json.leaveobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, -1, {}, {}); } return false; } if (mCompletion) { std::string userPubKey = Base64::btoa(mUserKeyPair.pubKey); std::string newCredential; const auto itRegion = std::find_if(vpnRegions.begin(), vpnRegions.end(), [&name = mRegion](const VpnRegion& r) { return r.getName() == name; }); if (itRegion != vpnRegions.end()) { const auto& clusters = itRegion->getClusters(); const auto itCluster = clusters.find(clusterID); if (itCluster != clusters.end()) { auto peerKeyPair = StringKeyPair(std::move(mUserKeyPair.privKey), std::move(clusterPubKey)); newCredential = client->generateVpnCredentialString(itCluster->second.getHost(), itCluster->second.getDns(), std::move(ipv4), std::move(ipv6), std::move(peerKeyPair)); } } if (newCredential.empty()) { LOG_err << "[CommandPutVpnCredentials] Could not generate VPN credential string"; mCompletion(API_ENOENT, -1, {}, {}); } else { mCompletion(API_OK, slotID, std::move(userPubKey), std::move(newCredential)); } } return true; } CommandDelVpnCredential::CommandDelVpnCredential(MegaClient* client, int slotID, Cb&& completion) : mCompletion(std::move(completion)) { cmd("vpnd"); arg("s", slotID); // SlotID to remove the credentials tag = client->reqtag; } bool CommandDelVpnCredential::procresult(Command::Result r, JSON&) { if (mCompletion) { mCompletion(r.errorOrOK()); } return r.wasErrorOrOK(); } CommandCheckVpnCredential::CommandCheckVpnCredential(MegaClient* client, string&& userPubKey, Cb&& completion) { cmd("vpnc"); arg("k", userPubKey.c_str()); // User Public Key is already in B64 format tag = client->reqtag; mCompletion = std::move(completion); } bool CommandCheckVpnCredential::procresult(Command::Result r, JSON&) { if (mCompletion) { mCompletion(r.errorOrOK()); } return r.wasErrorOrOK(); } CommandGetNetworkConnectivityTestServerInfo::CommandGetNetworkConnectivityTestServerInfo( MegaClient* client, Completion&& completion) { cmd("vpnv"); tag = client->reqtag; mCompletion = std::move(completion); } bool CommandGetNetworkConnectivityTestServerInfo::procresult(Command::Result r, JSON& json) { if (r.wasErrorOrOK()) { assert(r.wasStrictlyError()); if (mCompletion) mCompletion(r.errorOrOK(), {}); return true; } if (!r.hasJsonObject()) { onParseFailure(); return false; } NetworkConnectivityTestServerInfo info; for (bool finished = false; !finished;) { switch (json.getnameid()) { case makeNameid("t"): // IPv4 if (!json.storeobject(&info.ipv4)) { onParseFailure(); return false; } break; case makeNameid("t6"): // IPv6 if (!json.storeobject(&info.ipv6)) { onParseFailure(); return false; } break; case makeNameid("p"): // ports list if (!json.enterarray()) { onParseFailure(); return false; } while (json.isnumeric()) { info.ports.push_back(json.getint32()); } if (!json.leavearray()) { onParseFailure(); return false; } break; default: if (!json.storeobject()) { onParseFailure(); return false; } break; case EOO: finished = true; break; } } if (mCompletion) { mCompletion(API_OK, std::move(info)); } return true; } /* MegaVPN Commands END*/ CommandFetchCreditCard::CommandFetchCreditCard(MegaClient* client, CommandFetchCreditCardCompletion completion) : mCompletion(std::move(completion)) { assert(mCompletion); cmd("cci"); tag = client->reqtag; } bool CommandFetchCreditCard::procresult(Command::Result r, JSON& json) { string_map creditCardInfo; if (r.wasStrictlyError()) { mCompletion(r.errorOrOK(), creditCardInfo); return true; } if (r.hasJsonObject()) { for (;;) { string name = json.getnameWithoutAdvance(); switch (json.getnameid()) { case makeNameid("gw"): creditCardInfo[name] = std::to_string(json.getint()); break; case makeNameid("brand"): creditCardInfo[name] = json.getname(); break; case makeNameid("last4"): creditCardInfo[name] = json.getname(); break; case makeNameid("exp_year"): creditCardInfo[name] = std::to_string(json.getint()); break; case EOO: assert(creditCardInfo.size() == 5); mCompletion(API_OK, creditCardInfo); return true; default: if (name == "exp_month") { creditCardInfo[name] = std::to_string(json.getint()); } else if (!json.storeobject()) { creditCardInfo.clear(); mCompletion(API_EINTERNAL, creditCardInfo); return false; } break; } } } else { mCompletion(API_EINTERNAL, creditCardInfo); } return false; } CommandCreatePasswordManagerBase::CommandCreatePasswordManagerBase(MegaClient* cl, std::unique_ptr<NewNode> nn, int ctag, CommandCreatePasswordManagerBase::Completion&& cb) : mNewNode(std::move(nn)), mCompletion(std::move(cb)) { mSeqtagArray = true; cmd("pwmp"); // APs "t" (for the new node/folder) and "ua" (for the new user attribute) triggered assert(mNewNode); arg("k", reinterpret_cast<const byte*>(mNewNode->nodekey.data()), mNewNode->nodekey.size()); if (mNewNode->attrstring) { arg("at", reinterpret_cast<const byte*>(mNewNode->attrstring->data()), mNewNode->attrstring->size()); } // although these won't be used, they are updated for integrity tag = ctag; client = cl; }; bool CommandCreatePasswordManagerBase::procresult(Result r, JSON &json) { // APs will update user data (wait for APs before returning) if (r.wasErrorOrOK()) { if (mCompletion) mCompletion(r.errorOrOK(), nullptr); return true; } NodeHandle folderHandle; std::string key; std::unique_ptr<std::string> attrString; // optionals are C++17 m_off_t t = 0; for (;;) { // not interested in already-known "k" (user:key), "t", "at", "u", "ts" switch (json.getnameid()) { case makeNameid("h"): folderHandle.set6byte(json.gethandle(MegaClient::NODEHANDLE)); break; case makeNameid("k"): json.storeobject(&key); break; case makeNameid("a"): attrString = std::make_unique<std::string>(); json.storeobject(attrString.get()); break; case makeNameid("t"): { t = json.getint(); break; } case EOO: { bool sanityChecksFailed = false; const std::string msg {"Password Manager: wrong node type received in command response. Received "}; if (FOLDERNODE != static_cast<nodetype_t>(t)) { LOG_err << msg << "type " << t << " expected " << FOLDERNODE; sanityChecksFailed = true; } const auto keySeparatorPos = key.find(":"); auto keyBeginning = keySeparatorPos + 1; if (keySeparatorPos == std::string::npos) { LOG_warn << msg << "unexpected key field value |" << key << "| missing separator ':'." << " Attempting key value format without separator ':'"; keyBeginning = 0; } const std::string aux {Base64::btoa(mNewNode->nodekey)}; key = key.substr(keyBeginning); if (key != aux) { LOG_err << "node key value |" << key << "| different than expected |" << aux << "|"; sanityChecksFailed = true; } const auto& at = mNewNode->attrstring; const std::string atAux = at ? Base64::btoa(*(at.get())) : ""; if ((!at && attrString) || (at && !attrString) || // if only 1 exists (at && attrString && atAux != *attrString)) // or both exist and are different { LOG_err << "node attributes |" << (attrString ? *attrString : "") << "| different than expected |" << atAux << "|"; sanityChecksFailed = true; } if (sanityChecksFailed) { if (mCompletion) mCompletion(API_EINTERNAL, nullptr); return true; } mNewNode->nodehandle = folderHandle.as8byte(); if (mCompletion) mCompletion(API_OK, std::move(mNewNode)); return true; } default: if (!json.storeobject()) { LOG_err << "Password Manager: error parsing param"; if (mCompletion) mCompletion(API_EINTERNAL, nullptr); return false; } } } } CommandGetNotifications::CommandGetNotifications(MegaClient* client, ResultFunc onResult) : mOnResult(onResult) { cmd("gnotif"); tag = client->reqtag; if (!mOnResult) { mOnResult = [](const Error&, vector<DynamicMessageNotification>&&) { LOG_err << "The result of 'gnotif' will be lost"; }; } } bool CommandGetNotifications::procresult(Result r, JSON& json) { if (r.wasErrorOrOK()) { LOG_err << "Unexpected response of 'gnotif' command"; mOnResult(r.errorOrOK(), {}); return true; } vector<DynamicMessageNotification> notifications; while (json.enterobject()) { notifications.emplace_back(); DynamicMessageNotification& notification = notifications.back(); for (nameid nid = json.getnameid(); nid != EOO; nid = json.getnameid()) { switch (nid) { case makeNameid("id"): notification.id = json.getint(); break; case makeNameid("t"): json.storeobject(¬ification.title); notification.title = Base64::atob(notification.title); break; case makeNameid("d"): json.storeobject(¬ification.description); notification.description = Base64::atob(notification.description); break; case makeNameid("img"): json.storeobject(¬ification.imageName); break; case makeNameid("icon"): json.storeobject(¬ification.iconName); break; case makeNameid("dsp"): json.storeobject(¬ification.imagePath); break; case makeNameid("s"): notification.start = json.getint(); break; case makeNameid("e"): notification.end = json.getint(); break; case makeNameid("sb"): notification.showBanner = json.getbool(); break; case makeNameid("cta1"): { if (!readCallToAction(json, notification.callToAction1)) { LOG_err << "Unable to read 'cta1' in 'gnotif' response"; mOnResult(API_EINTERNAL, {}); return false; } break; } case makeNameid("cta2"): { if (!readCallToAction(json, notification.callToAction2)) { LOG_err << "Unable to read 'cta2' in 'gnotif' response"; mOnResult(API_EINTERNAL, {}); return false; } break; } case makeNameid("m"): if (!readRenderModes(json, notification.renderModes)) { LOG_err << "Unable to read 'm' in 'gnotif' response"; mOnResult(API_EINTERNAL, {}); return false; } break; default: if (!json.storeobject()) { LOG_err << "Failed to parse 'gnotif' response"; mOnResult(API_EINTERNAL, {}); return false; } break; } } if (!json.leaveobject()) { LOG_err << "Unable to leave json object in 'gnotif' response"; mOnResult(API_EINTERNAL, {}); return false; } } mOnResult(API_OK, std::move(notifications)); return true; } bool CommandGetNotifications::readCallToAction(JSON& json, map<string, string>& action) { if (!json.enterobject()) { return false; } for (nameid nid = json.getnameid(); nid != EOO; nid = json.getnameid()) { switch (nid) { case makeNameid("link"): { json.storeobject(&action["link"]); break; } case makeNameid("text"): { string& t = action["text"]; json.storeobject(&t); t = Base64::atob(t); break; } default: if (!json.storeobject()) { return false; } } } return json.leaveobject(); } bool CommandGetNotifications::readRenderModes(JSON& json, map<string, map<string, string>>& modes) { if (!json.enterobject()) { return false; } for (string renderMode = json.getname(); !renderMode.empty(); renderMode = json.getname()) { if (!json.enterobject()) { return false; } auto& fields = modes[std::move(renderMode)]; for (string f, v; json.storeKeyValueFromObject(f, v);) { fields.emplace(std::move(f), std::move(v)); // Clear moved-from strings to avoid "Use of a moved from object" warning // (false-positive in current implementation) f.clear(); v.clear(); } if (!json.leaveobject()) { return false; } } return json.leaveobject(); } CommandGetActiveSurveyTriggerActions::CommandGetActiveSurveyTriggerActions(MegaClient* client, Completion&& completion) { cmd("gsur"); mCompletion = std::move(completion); tag = client->reqtag; } std::vector<uint32_t> CommandGetActiveSurveyTriggerActions::parseTriggerActionIds(JSON& json) { std::vector<uint32_t> ids; // Trigger action ID is a small positive integer int id = 0; while (json.isnumeric() && (id = json.getint32()) > 0) { ids.push_back(static_cast<uint32_t>(id)); } return ids; } bool CommandGetActiveSurveyTriggerActions::procresult(Result r, JSON& json) { std::vector<uint32_t> ids; if (r.wasErrorOrOK()) { // Preventive: convert API_OK to API_ENOENT Error e = r.wasError(API_OK) ? Error{API_ENOENT} : r.errorOrOK(); onCompletion(e, ids); return true; } if (!r.hasJsonArray()) { // Not expect to happen assert(r.hasJsonArray() && "Unexpected response for gsur command"); onCompletion(API_EINTERNAL, ids); return false; } // Inside Json array and parse ids = parseTriggerActionIds(json); Error err = ids.empty() ? API_ENOENT : API_OK; onCompletion(err, ids); return true; } CommandGetSurvey::CommandGetSurvey(MegaClient* client, unsigned int triggerActionId, Completion&& completion) { cmd("ssur"); arg("t", static_cast<m_off_t>(triggerActionId)); mCompletion = std::move(completion); tag = client->reqtag; } // // Returns true if parsing was successful, false otherwise. // bool CommandGetSurvey::parseSurvey(JSON& json, Survey& survey) { for (;;) { switch (json.getnameid()) { case makeNameid("s"): if ((survey.h = json.gethandle(MegaClient::SURVEYHANDLE)) == UNDEF) return false; break; case makeNameid("m"): if (auto value = json.getint32(); value < 0) return false; else survey.maxResponse = static_cast<unsigned int>(value); break; case makeNameid("i"): if (!json.storeobject(&survey.image)) return false; break; case makeNameid("c"): if (!json.storeobject(&survey.content)) return false; break; case EOO: return true; break; default: if (!json.storeobject()) return false; break; } } } bool CommandGetSurvey::procresult(Result r, JSON& json) { Survey survey{}; if (r.wasErrorOrOK()) { // Preventive: convert API_OK to API_ENOENT const Error e = r.wasError(API_OK) ? Error{API_ENOENT} : r.errorOrOK(); onCompletion(e, survey); return true; } const bool parsedOk = parseSurvey(json, survey); const Error e = parsedOk && survey.isValid() ? API_OK : API_EINTERNAL; onCompletion(e, survey); return parsedOk; } CommandAnswerSurvey::CommandAnswerSurvey(MegaClient* client, const Answer& answer, Completion&& completion) { cmd("asur"); arg("s", Base64Str<MegaClient::SURVEYHANDLE>(answer.mHandle)); arg("t", static_cast<m_off_t>(answer.mTriggerActionId)); if (!answer.mResponse.empty()) arg("r", answer.mResponse.c_str()); if (!answer.mComment.empty()) arg("c", answer.mComment.c_str()); mCompletion = std::move(completion); tag = client->reqtag; } bool CommandAnswerSurvey::procresult(Result r, JSON&) { if (r.wasErrorOrOK()) { onCompletion(r.errorOrOK()); return true; } return false; } CommandGetMyIP::CommandGetMyIP(MegaClient* client, Cb&& completion) { assert(completion); cmd("wmip"); tag = client->reqtag; mCompletion = std::move(completion); } bool CommandGetMyIP::procresult(Command::Result r, JSON& json) { if (!r.hasJsonObject()) { if (mCompletion) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), {}, {}); } else { mCompletion(API_EINTERNAL, {}, {}); } } return true; } string countryCode; string ipAddress; // Parse country code and IP address for (bool finished = false; !finished;) { switch (json.getnameid()) { case makeNameid("cc"): // Country code json.storeobject(&countryCode); break; case makeNameid("ip"): // My public IP address json.storeobject(&ipAddress); break; default: if (!json.storeobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}, {}); } return false; } break; case EOO: { finished = true; } break; } } if (mCompletion) { mCompletion((countryCode.empty() || ipAddress.empty()) ? API_EINTERNAL : API_OK, std::move(countryCode), std::move(ipAddress)); } return true; } CommandSetThrottlingParams::CommandSetThrottlingParams(const MegaClient& client, Completion&& completion): mCompletion(std::move(completion)) { assert(mCompletion); cmd("stp"); tag = client.reqtag; } std::pair<bool, std::optional<CommandSetThrottlingParams::ThrottlingParamsFromAPI>> CommandSetThrottlingParams::parseJson(JSON& json) { std::optional<m_off_t> updateRateInSeconds; std::optional<m_off_t> maxUploadsBeforeThrottle; std::optional<m_off_t> uploadCounterInactivityTime; for (bool finished = false; !finished;) { switch (json.getnameid()) { case makeNameid("ur"): { updateRateInSeconds = json.getint(); break; } case makeNameid("uc"): { maxUploadsBeforeThrottle = json.getint(); break; } case makeNameid("ucr"): { uploadCounterInactivityTime = json.getint(); break; } case EOO: { finished = true; break; } default: { if (!json.storeobject()) { LOG_err << "[CommandSetThrottlingParams::procresult] Failed to parse throttling " "parameters inside the array"; return {false, {}}; } break; } } } if (!updateRateInSeconds || !maxUploadsBeforeThrottle || !uploadCounterInactivityTime) { return {true, {}}; } return {true, CommandSetThrottlingParams::ThrottlingParamsFromAPI{*updateRateInSeconds, *maxUploadsBeforeThrottle, *uploadCounterInactivityTime}}; } bool CommandSetThrottlingParams::procresult(Result r, JSON& json) { if (!r.hasJsonObject()) { const auto wasErrorOrOk = r.wasErrorOrOK(); mCompletion(wasErrorOrOk ? r.errorOrOK() : static_cast<Error>(API_EINTERNAL)); return wasErrorOrOk; } auto [parseOk, throttlingParams] = parseJson(json); if (!parseOk || !throttlingParams) { mCompletion(parseOk ? API_EARGS : API_EINTERNAL); return parseOk; } mCompletion(*throttlingParams); return true; } CommandGetSubscriptionCancellationDetails::CommandGetSubscriptionCancellationDetails( MegaClient* client, const char* originalTransactionId, unsigned int gatewayId, CompletionCallback&& completion) { assert(completion); cmd("gsc"); if (originalTransactionId) { arg("id", originalTransactionId); } arg("gw", gatewayId); tag = client->reqtag; mCompletion = std::move(completion); } bool CommandGetSubscriptionCancellationDetails::procresult(Command::Result r, JSON& json) { if (!r.hasJsonObject()) { if (mCompletion) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), {}, {}, {}); } else { mCompletion(API_EINTERNAL, {}, {}, {}); } } return true; } std::string originalTransactionId; int expiresDate = 0; int cancelledDate = -1; for (bool finished = false; !finished;) { switch (json.getnameid()) { case makeNameid("id"): json.storeobject(&originalTransactionId); break; case makeNameid("expires"): expiresDate = json.getint32(); break; case makeNameid("canceled"): cancelledDate = json.getint32(); break; default: if (!json.storeobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}, {}, {}); } return false; } break; case EOO: { finished = true; } break; } } if (mCompletion) { mCompletion((originalTransactionId.empty() || expiresDate == 0) ? API_EINTERNAL : API_OK, std::move(originalTransactionId), std::move(expiresDate), std::move(cancelledDate)); } return true; } CommandDiscountCodeGetInfo::CommandDiscountCodeGetInfo(MegaClient* client, const string& discountCode, CompletionCallback&& completion) { assert(completion); cmd("dci"); arg("dc", discountCode.c_str()); // [TODO]: For now this will be always true, but in case we want to send false, we may need to // start parsing some fields that will only be received in case extra is false. arg("extra", 1); // request extended info tag = client->reqtag; mCompletion = std::move(completion); } bool CommandDiscountCodeGetInfo::procresult(Command::Result r, JSON& json) { if (!r.hasJsonObject()) { if (mCompletion) { if (r.wasErrorOrOK()) { mCompletion(r.errorOrOK(), {}); } else { mCompletion(API_EINTERNAL, {}); } } return true; } DiscountCodeInfoExtended dci; for (bool finished = false; !finished;) { switch (json.getnameid()) { case makeNameid("dc"): json.storeobject(&dci.alfanumDiscountCode); break; case makeNameid("it"): dci.item = json.getint32(); break; case makeNameid("al"): dci.accountLevel = json.getint32(); break; case makeNameid("m"): dci.numMonths = json.getint32(); break; case makeNameid("ex"): dci.expiry = json.getint32(); break; case makeNameid("cs"): dci.compulsorySubscription = json.getint32(); break; case makeNameid("pd"): dci.percentageDiscount = json.getint32(); break; case makeNameid("bt"): dci.behaviourType = json.getint32(); break; case makeNameid("f"): { if (!json.enterobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}); } return false; } string key, value; while (json.storeKeyValueFromObject(key, value)) { dci.features[key] = static_cast<int>(std::stoul(value)); } if (!json.leaveobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}); } return false; } break; } case makeNameid("txva"): dci.txva = json.getint32(); break; case makeNameid("txe"): dci.taxExempt = json.getint32(); break; case makeNameid("tx"): dci.taxRate = json.getint32(); break; case makeNameid("txn"): json.storeobject(&dci.taxName); break; case makeNameid("txcc"): json.storeobject(&dci.taxCountry); break; case makeNameid("md"): dci.multiDiscount = json.getint32(); break; case makeNameid("etp"): dci.euroTotalPrice = json.getfloat(); break; case makeNameid("eda"): dci.euroDiscountAmount = json.getfloat(); break; case makeNameid("edtp"): dci.euroDiscountedTotalPrice = json.getfloat(); break; case makeNameid("edmp"): dci.euroDiscountedMonthlyPrice = json.getfloat(); break; case makeNameid("etpn"): dci.euroTotalPriceNet = json.getfloat(); break; case makeNameid("edan"): dci.euroDiscountAmountNet = json.getfloat(); break; case makeNameid("edtpn"): dci.euroDiscountedTotalPriceNet = json.getfloat(); break; case makeNameid("edmpn"): dci.euroDiscountedMonthlyPriceNet = json.getfloat(); break; case makeNameid("lcc"): json.storeobject(&dci.localCurrencyCode); break; case makeNameid("lcs"): json.storeobject(&dci.localCurrencySymbol); break; case makeNameid("ltp"): dci.localTotalPrice = json.getfloat(); break; case makeNameid("lda"): dci.localDiscountAmount = json.getfloat(); break; case makeNameid("ldtp"): dci.localDiscountedTotalPrice = json.getfloat(); break; case makeNameid("ldmp"): dci.localDiscountedMonthlyPrice = json.getfloat(); break; case makeNameid("ltpn"): dci.localTotalPriceNet = json.getfloat(); break; case makeNameid("ldan"): dci.localDiscountAmountNet = json.getfloat(); break; case makeNameid("ldtpn"): dci.localDiscountedTotalPriceNet = json.getfloat(); break; case makeNameid("ldmpn"): dci.localDiscountedMonthlyPriceNet = json.getfloat(); break; default: if (!json.storeobject()) { if (mCompletion) { mCompletion(API_EINTERNAL, {}); } return false; } break; case EOO: { finished = true; } break; } } if (mCompletion) { mCompletion(dci.isValidFormat() ? API_OK : API_EINTERNAL, std::move(dci)); } return true; } } // namespace ����������������������������sdk-10.11.0/src/common/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014526�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/activity_monitor.cpp���������������������������������������������������������0000664�0000000�0000000�00000003420�15162662266�0020634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <utility> #include <mega/common/activity_monitor.h> namespace mega { namespace common { Activity::Activity(ActivityMonitor& monitor) : mMonitor(&monitor) { std::lock_guard<std::mutex> guard(monitor.mLock); ++monitor.mProcessing; assert(monitor.mProcessing); } Activity::Activity() : mMonitor(nullptr) { } Activity::Activity(const Activity& other) : mMonitor(other.mMonitor) { if (!mMonitor) return; std::lock_guard<std::mutex> guard(mMonitor->mLock); ++mMonitor->mProcessing; assert(mMonitor->mProcessing); } Activity::Activity(Activity&& other) : mMonitor(std::move(other.mMonitor)) { other.mMonitor = nullptr; } Activity::~Activity() { if (!mMonitor) return; std::lock_guard<std::mutex> guard(mMonitor->mLock); assert(mMonitor->mProcessing); --mMonitor->mProcessing; if (!mMonitor->mProcessing) mMonitor->mCompleted.notify_all(); } Activity& Activity::operator=(const Activity& rhs) { Activity temp(rhs); swap(temp); return *this; } Activity& Activity::operator=(Activity&& rhs) { Activity temp(std::move(rhs)); swap(temp); return *this; } void Activity::swap(Activity& other) { using std::swap; swap(mMonitor, other.mMonitor); } ActivityMonitor::ActivityMonitor() : mCompleted() , mLock() , mProcessing(0u) { } ActivityMonitor::~ActivityMonitor() { waitUntilIdle(); } bool ActivityMonitor::active() const { std::lock_guard<std::mutex> guard(mLock); return mProcessing > 0; } Activity ActivityMonitor::begin() { return Activity(*this); } void ActivityMonitor::waitUntilIdle() { std::unique_lock<std::mutex> lock(mLock); mCompleted.wait(lock, [&]() { return !mProcessing; }); } } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/client.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000024461�15162662266�0016517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <condition_variable> #include <future> #include <mutex> #include <mega/common/client.h> #include <mega/common/error_or.h> #include <mega/common/logging.h> #include <mega/common/node_info.h> #include <mega/common/task_queue.h> #include <mega/common/utility.h> #include <mega/filesystem.h> #include <mega/types.h> namespace mega { namespace common { Client::Client(Logger& logger): mEventObservers(), mEventObserversLock(), mLogger(logger) { } Client::~Client() { } void Client::addEventObserver(NodeEventObserver& observer) { std::lock_guard guard(mEventObserversLock); mEventObservers.emplace(&observer); } ErrorOr<bool> Client::isDirectory(NodeHandle handle) const { // Check if the node's a file. auto result = isFile(handle); // Couldn't get information about the node. if (!result) return result; // The node can't be a directory if it's a file. return !*result; } template<typename T> auto Client::lookup(const T& path, NodeHandle parent) -> typename EnableIfPath<T, ErrorOr<NodeInfo>>::type { // Clarity. using Path = T; // Convenience. const auto& fsAccess = this->fsAccess(); // Make sure the parent exists. auto info = get(parent); // Parent doesn't exist. if (!info) return unexpected(info.error()); // Parent isn't a directory. if (!info->mIsDirectory) return unexpected(API_FUSE_ENOTDIR); Path name; std::size_t index = 0; // Traverse tree fragment by fragment. while (path.nextPathComponent(index, name)) { // Node doesn't exist. if (!info) return unexpected(info.error()); // Node isn't a directory. if (!info->mIsDirectory) return unexpected(API_FUSE_ENOTDIR); // Try and locate the next node in the path. info = get(info->mHandle, name.toName(fsAccess)); } // Node doesn't exist. if (!info) return unexpected(info.error()); // Node exists. return info; } Logger& Client::logger() const { return mLogger; } ErrorOr<NodeInfo> Client::makeDirectory(const std::string& name, NodeHandle parent) { auto notifier = makeSharedPromise<ErrorOr<NodeInfo>>(); // Called when our directory has been created. auto created = [notifier](ErrorOr<NodeInfo> result) { notifier->set_value(std::move(result)); }; // created // Try and create the directory. makeDirectory(std::move(created), name, parent); // Return result to caller. return waitFor(notifier->get_future()); } void Client::move(MoveCallback callback, const std::string& name, NodeHandle source, NodeHandle target) { // Sanity. assert(callback); assert(!name.empty()); assert(!source.isUndef()); assert(!target.isUndef()); // Called when source has been renamed. auto renamed = [=](MoveCallback& callback, Error result) { // Couldn't rename source to name. if (result != API_OK) return callback(result); // Ask the client to move source to target. move(std::move(callback), source, target); }; // renamed // Ask the client to rename source to name. rename(std::bind(std::move(renamed), std::move(callback), std::placeholders::_1), name, source); } Error Client::move(const std::string& name, NodeHandle source, NodeHandle target) { // Sanity. assert(!name.empty()); assert(!source.isUndef()); assert(!target.isUndef()); // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Called when source has been renamed and moved to target. auto moved = [notifier](Error result) { notifier->set_value(result); }; // moved // Ask the client to rename source and move it to target. move(std::move(moved), name, source, target); // Return the client's result to our caller. return waitFor(notifier->get_future()); } Error Client::move(NodeHandle source, NodeHandle target) { // Sanity. assert(!source.isUndef()); assert(!target.isUndef()); // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Transmits the client's result to our notifier. auto moved = [notifier](Error result) { notifier->set_value(result); }; // moved // Ask the client to move source to target. move(std::move(moved), source, target); // Return the client's result to our caller. return waitFor(notifier->get_future()); } Error Client::remove(NodeHandle handle) { // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Transmits the client's result to our notifier. auto removed = [notifier](Error result) { notifier->set_value(result); }; // removed // Ask the client to remove the specified node. remove(std::move(removed), handle); // Return the result to the caller. return waitFor(notifier->get_future()); } Error Client::removeAll(NodeHandle handle) { // Bundle necessary state for convenience. struct Context { // So we can wait for the client's result. std::condition_variable mCV; // Serializes access to mNumOutstanding and mResult. std::mutex mLock; // Tracks how many removals are in progress. std::size_t mNumOutstanding; // Representative result of removal. Error mResult = API_OK; }; // Context // Instantiate context. auto context = std::make_shared<Context>(); // Executed when a node has been removed. RemoveCallback removed = [context](Error result) { // Acquire lock. std::lock_guard<std::mutex> guard(context->mLock); // Update result. if (context->mResult == API_OK) context->mResult = result; // Sanity. assert(context->mNumOutstanding); // Decrement counter. --context->mNumOutstanding; // Notify any waiters. context->mCV.notify_one(); }; // removed // Try and remove all of this node's children. each([&](NodeInfo info) { // Increment counter. { std::lock_guard<std::mutex> guard(context->mLock); ++context->mNumOutstanding; } // Try and remove this child. remove(removed, info.mHandle); }, handle); // Check whether: // - Any removals have failed. // - All removals have completed. auto completed = [&]() { return context->mResult != API_OK || !context->mNumOutstanding; }; // completed // Acquire lock. std::unique_lock<std::mutex> lock(context->mLock); // Wait the removals to complete. auto result = context->mCV.wait_for(lock, defaultTimeout(), completed); // One or more of our removals timed out. if (!result) return LOCAL_ETIMEOUT; // Return the result to our caller. return context->mResult; } void Client::removeEventObserver(NodeEventObserver& observer) { std::lock_guard guard(mEventObserversLock); mEventObservers.erase(&observer); } Error Client::rename(const std::string& name, NodeHandle handle) { // Sanity. assert(!name.empty()); assert(!handle.isUndef()); // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Called when handle has been renamed. auto renamed = [notifier](Error result) { // Transmit client's result to our notifier. notifier->set_value(result); }; // renamed // Ask the client to rename the node. rename(std::move(renamed), name, handle); // Return the result to the caller. return waitFor(notifier->get_future()); } Error Client::replace(NodeHandle source, NodeHandle target) { // Sanity. assert(!source.isUndef()); assert(!target.isUndef()); // Try and get our hands on target's description. auto target_ = get(target); // Couldn't get our hands on target's description. if (!target_) return target_.error(); // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Called when source has been renamed and moved. auto moved = [=](Error result) { // Couldn't rename or move source. if (result != API_OK) return notifier->set_value(result); // Called when target has been removed. auto removed = [=](Error result) { notifier->set_value(result); }; // removed // Ask the client to remove target. remove(std::move(removed), target); }; // moved // Ask the client to rename and move source. move(std::move(moved), target_->mName, source, target_->mParentHandle); // Return the client's result to the caller. return waitFor(notifier->get_future()); } ErrorOr<StorageInfo> Client::storageInfo() { // So we can wait for the client's result. auto notifier = makeSharedPromise<ErrorOr<StorageInfo>>(); // Called when we've retrieved the user's storage info. auto retrieved = [notifier](ErrorOr<StorageInfo> result) { notifier->set_value(std::move(result)); }; // retrieved // Ask the client to retrieve the user's storage info. storageInfo(std::move(retrieved)); // Return the client's result to our caller. return waitFor(notifier->get_future()); } Error Client::touch(NodeHandle handle, m_time_t modified) { // Sanity. assert(!handle.isUndef()); // So we can wait for the client's result. auto notifier = makeSharedPromise<Error>(); // Called when the node's timestamp has been updated. auto updated = [notifier](Error result) { notifier->set_value(result); }; // updated // Ask the client to update the node's timestamp. touch(std::move(updated), handle, modified); // Return the client's result to the caller. return waitFor(notifier->get_future()); } template ErrorOr<NodeInfo> Client::lookup(const LocalPath&, NodeHandle); template ErrorOr<NodeInfo> Client::lookup(const RemotePath&, NodeHandle); } // common } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/client_adapter.cpp�����������������������������������������������������������0000664�0000000�0000000�00000173564�15162662266�0020230�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/base64.h> #include <mega/common/client_adapter.h> #include <mega/common/error_or.h> #include <mega/common/logging.h> #include <mega/common/node_event.h> #include <mega/common/node_event_observer.h> #include <mega/common/node_event_queue.h> #include <mega/common/node_event_type.h> #include <mega/common/node_info.h> #include <mega/common/node_key_data.h> #include <mega/common/normalized_path.h> #include <mega/common/partial_download.h> #include <mega/common/partial_download_callback.h> #include <mega/common/status_flag.h> #include <mega/common/upload.h> #include <mega/common/utility.h> #include <mega/crypto/cryptopp.h> #include <mega/file.h> #include <mega/megaapp.h> #include <mega/megaclient.h> #include <mega/node.h> #include <mega/scoped_helpers.h> #include <atomic> #include <cassert> #include <functional> #include <memory> #include <mutex> #include <sstream> namespace mega { namespace common { // Convenience. using NewNodeVector = std::vector<NewNode>; using NewNodeVectorPtr = std::shared_ptr<NewNodeVector>; class ClientTransfer : public File { protected: ClientTransfer() = default; ~ClientTransfer() = default; // Constantly true. bool isFuseTransfer() const override; }; // ClientTransfer class ClientDownload : public ClientTransfer { // Called when the download has completed. void completed(Transfer* transfer, putsource_t source) override; // Called when the download has terminated (cancellation, failure.) void terminated(mega::error result) override; // Who do we call when we've completed? std::function<void(Error)> mCallback; public: ClientDownload(std::function<void(Error)> callback, const LocalPath& logicalPath, const Node& node, const LocalPath& physicalPath); // Begin the download. bool begin(MegaClient& client); // Called when the download has completed. // // Forwards result to callback and deletes the instance. void completed(Error result); }; // ClientDownload class ClientNodeEvent : public NodeEvent { public: ClientNodeEvent(sharedNode_vector::const_iterator position); // Is this node a directory? bool isDirectory() const override; // What is this node's handle? NodeHandle handle() const override; // Retrieve this node's description. NodeInfo info() const override; // What is this node's name? const std::string& name() const override; // Who is this node's parent? NodeHandle parentHandle() const override; // What kind of event is this? NodeEventType type() const override; // What node does this event represent? sharedNode_vector::const_iterator mPosition; }; // ClientNodeEvent class ClientNodeEventQueue : public NodeEventQueue { ClientNodeEvent mCurrent; const sharedNode_vector& mEvents; public: ClientNodeEventQueue(const sharedNode_vector& events); // Is the queue empty? bool empty() const override; // Return a reference to the first event in the queue. const ClientNodeEvent& front() const override; // Pop an event from the queue. void pop_front() override; // Restore any popped events. void restore(); // How many events are in the queue? std::size_t size() const override; }; // ClientNodeEventQueue class ClientPartialDownload: public PartialDownload, public std::enable_shared_from_this<ClientPartialDownload> { // Convenience. using Abort = PartialDownloadCallback::Abort; using Data = DirectRead::Data; using Event = DirectRead::CallbackParam; using Failure = DirectRead::Failure; // Called when the download's been completed. void completed(Error result); // Called when the SDK wants to feed us file content. void data(Data& data); // Called when the SDK wants to report a download failure. void failure(Failure& failure); // Signal that the download is now in progress. // // Returns false if: // - The download has already begun. // - The download has already completed. bool inProgress(); // Called when the SDK wants to notify us about our download. static void notify(PartialDownloadWeakPtr cookie, Event& event); // The callback that will receive file content. PartialDownloadCallback& mCallback; // The client that will perform our download. ClientAdapter& mClient; // The file that we're downloading. const NodeHandle mHandle; // The file's decryption key, IV and authentication tokens. NodeKeyData mKeyData; // Serializes access to instance members. mutable std::recursive_mutex mLock; // At what location in the file should we start downloading? std::uint64_t mOffset; // How many bytes of content do we still have to download? std::uint64_t mRemaining; // Tracks the status of the download. StatusFlags mStatus; public: ClientPartialDownload(PartialDownloadCallback& callback, ClientAdapter& client, NodeHandle handle, const NodeKeyData& keyData, std::uint64_t length, std::uint64_t offset); ~ClientPartialDownload(); // Begin the partial download. void begin() override; // Cancel the partial download. bool cancel() override; // Is this download cancellable? bool cancellable() const override; // Has the download been cancelled? bool cancelled() const override; // Has this download completed? bool completed() const override; }; // ClientPartialDownload class ClientUpload; // Convenience. using ClientUploadPtr = std::shared_ptr<ClientUpload>; using ClientUploadWeakPtr = std::weak_ptr<ClientUpload>; class ClientUpload : public ClientTransfer { // Bind our uploaded data to a name. void bind(BoundCallback callback, FileNodeKey fileKey, NodeHandle lastHandle, ClientUploadPtr self, UploadHandle uploadHandle, std::string fileAttr, UploadToken uploadToken); // Called when our upload has been bound to a name. void bound(BoundCallback callback, NewNodeVector& nodes, bool overriden, Error result); // Called when the upload has completed. void completed(Transfer* transfer, putsource_t source) override; // Called when the upload has been terminated (cancellation, failure.) void terminated(mega::error result) override; // Who do we tell when our data is uploaded? UploadCallback mCallback; // Which client is responsible for us? ClientAdapter& mClient; // Records the result of the upload. Error mResult; // A strong reference to ourselves. // // Dropped when the upload has completed. ClientUploadPtr mSelf; // Tracks the status of the upload. std::atomic<StatusFlags> mStatus; public: ClientUpload(ClientAdapter& client, const LocalPath& logicalPath, NodeHandle parentHandle, const std::string& name, const LocalPath& physicalPath); ~ClientUpload(); // Begin the upload. void begin(UploadCallback callback); // Cancel the upload. bool cancel(); // Has this upload been cancelled? bool cancelled() const; // Has this upload completed? bool completed() const; // Inject a reference to ourselves. void inject(ClientUploadPtr self); // How did this upload complete? Error result() const; }; // ClientUpload class ClientUploadAdapter : public Upload { // The actual upload. ClientUploadPtr mUpload; public: ClientUploadAdapter(ClientUploadPtr upload); // Begin the upload. void begin(UploadCallback callback) override; // Cancel the upload. bool cancel() override; // Has the upload been cancelled? bool cancelled() const override; // Has the upload completed? bool completed() const override; // Did the upload succeed? Error result() const override; }; // ClientUploadAdapter // Retrieves a reference to a specific child. static auto child(MegaClient& client, NodeHandle parent, const std::string& name) -> ErrorOr<std::shared_ptr<Node>>; // Translates a node into a description. static void describe(NodeInfo& destination, accesslevel_t permissions, Node& source); static NodeInfo describe(Node& node); ClientAdapter::ClientAdapter(MegaClient& client) : Client(common::logger()) , mActivities() , mClient(client) , mDeinitialized{false} , mLock() , mPendingCallbacks() , mTaskQueue() , mThreadID(std::this_thread::get_id()) { } ClientAdapter::~ClientAdapter() { deinitialize(); } MegaApp& ClientAdapter::application() { assert(mClient.app); return *mClient.app; } ErrorOr<std::set<std::string>> ClientAdapter::childNames(NodeHandle parent) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate specified parent. auto parent_ = mClient.nodeByHandle(parent); // Parent doesn't exist. if (!parent_) return unexpected(API_ENOENT); // Parent isn't a directory. if (parent_->type == FILENODE) return unexpected(API_FUSE_ENOTDIR); // Keeps track of duplicate names. std::set<const std::string*> duplicates; std::set<std::string> names; // Collect child names. for (auto child : mClient.getChildren(parent_.get())) { // Add the child's name to the set. auto result = names.emplace(child->displayname()); // Name's already been seen. if (!result.second) duplicates.emplace(&*result.first); } // Prune duplicate names. while (!duplicates.empty()) { // Get an iterator to the duplicate we have to remove. auto duplicate = duplicates.begin(); // Get an iterator to the name we have to remove. auto name = names.find(**duplicate); // Remove the duplicate from our duplicates set. duplicates.erase(duplicate); // Remove the duplicated name from our names set. names.erase(name); } // Return names to caller. return names; } MegaClient& ClientAdapter::client() const { return mClient; } LocalPath ClientAdapter::dbPath(const std::string& name) const { // FUSE requires database support. assert(mClient.dbaccess); // Where should FUSE store its database? return mClient.dbaccess->databasePath(fsAccess(), name, DbAccess::DB_VERSION); } LocalPath ClientAdapter::dbRootPath() const { // FUSE requires database support. assert(mClient.dbaccess); // Where is the client storing its databases? return mClient.dbaccess->rootPath(); } void ClientAdapter::deinitialize() { // Remember that we've been deinitialized. mDeinitialized = true; // Wait for any calls to complete. mActivities.waitUntilIdle(); // Cancel any pending callbacks. mPendingCallbacks.cancel(); // Acquire lock. std::unique_lock<std::mutex> lock(mLock); // Cancel outstanding tasks. while (!mTaskQueue.empty()) { std::deque<Task> tasks; // Dequeue all tasks. mTaskQueue.dequeue(tasks, tasks.max_size()); // Release lock. lock.unlock(); // Cancel tasks. while (!tasks.empty()) { tasks.back().cancel(); tasks.pop_back(); } // Reacquire lock. lock.lock(); } } void ClientAdapter::dispatch() { // Acquire lock. std::unique_lock<std::mutex> lock(mLock); // Execute ready tasks. while (mTaskQueue.ready()) { // Pop a task from the queue. auto task = mTaskQueue.dequeue(); // Release lock. lock.unlock(); // Complete the task. task.complete(); // Reacquire lock. lock.lock(); } } void ClientAdapter::download(DownloadCallback callback, NodeHandle handle, const LocalPath& logicalPath, const LocalPath& physicalPath) { // Sanity. assert(callback); assert(!handle.isUndef()); assert(!physicalPath.empty()); // Asks the client to download the file. auto download = [this](DownloadCallback& callback, NodeHandle handle, LocalPath& logicalPath, LocalPath& physicalPath, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(API_EINCOMPLETE); // Try and locate the node to be downloaded. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return callback(API_ENOENT); // Node's not a file. if (node->type != FILENODE) return callback(API_EARGS); // Instantiate a download for the file. auto download = std::make_unique<ClientDownload>(std::move(callback), logicalPath, *node, physicalPath); // Couldn't start the download. if (!download->begin(mClient)) return; // Download's now owned by the client. static_cast<void>(download.release()); }; // download // Ask the client to download the file. execute(std::bind(std::move(download), std::move(callback), handle, logicalPath, physicalPath, std::placeholders::_1)); } void ClientAdapter::each(std::function<void(NodeInfo)> function, NodeHandle handle) const { // Sanity. assert(function); // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return; // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Couldn't locate the specified node. if (!node) return; // Node isn't a directory. if (node->type == FILENODE) return; // Assume directory is read-only. auto permissions = RDONLY; // Directory's actually writable. if (mClient.checkaccess(node.get(), FULL)) permissions = FULL; // Enumerate over the node's children. for (auto child : mClient.getChildren(node.get())) { NodeInfo info; // Latch the child's description. describe(info, permissions, *child); // Pass description to user callback. function(std::move(info)); } } Task ClientAdapter::execute(std::function<void(const Task&)> function) { // Sanity. assert(function); // Instantiate a new task. auto task = Task(std::move(function), mLogger); // Acquire lock. std::unique_lock<std::mutex> lock(mLock); // Client's being deinitialized. if (mDeinitialized) { // Release lock. lock.unlock(); // Cancel the task. task.cancel(); // Return task to caller. return task; } // Queue the task for execution. mTaskQueue.queue(task); // Release lock. lock.unlock(); // Let the client know it has work to do. mClient.waiter->notify(); // Return task to caller. return task; } ErrorOr<bool> ClientAdapter::exists(NodeHandle handle) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Check if the node exists. return !!mClient.nodeByHandle(handle); } FileSystemAccess& ClientAdapter::fsAccess() const { return *mClient.fsaccess; } ErrorOr<NodeInfo> ClientAdapter::get(NodeHandle handle) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return unexpected(API_ENOENT); // Return description to caller. return describe(*node); } ErrorOr<NodeInfo> ClientAdapter::get(NodeHandle parent, const std::string& name) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and retrieve a reference to the named child. auto node = child(mClient, parent, name); // Couldn't get a reference to the named child. if (!node) return unexpected(node.error()); // Return the child's description. return describe(**node); } ErrorOr<NodeHandle> ClientAdapter::handle(NodeHandle parent, const std::string& name) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and retrive a reference to the child. auto node = child(mClient, parent, name); // Couldn't retrieve child reference. if (!node) return unexpected(node.error()); // Return the child's handle. return (*node)->nodeHandle(); } ErrorOr<bool> ClientAdapter::hasChildren(NodeHandle parent) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate the specified parent. auto parent_ = mClient.nodeByHandle(parent); // Parent doesn't exist. if (!parent_) return API_ENOENT; // How many children does the parent contain? return mClient.getNumberOfChildren(parent) > 0; } void ClientAdapter::initialize() { // Clear deinitialization flag. mDeinitialized = false; } ErrorOr<bool> ClientAdapter::isFile(NodeHandle handle) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return unexpected(API_ENOENT); // Let the caller know if the node's a file. return node->type == FILENODE; } ErrorOr<NodeKeyData> ClientAdapter::keyData(NodeHandle handle, bool authorize) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Couldn't locate the specified node. if (!node) return unexpected(API_ENOENT); // Node's decryption key isn't available. if (!node->keyApplied()) return unexpected(API_EKEY); // Instantiate and populate node key data. NodeKeyData keyData; keyData.mKeyAndIV = node->nodekey(); keyData.mIsPublicHandle = false; // Caller doesn't want to authorize the node. if (!authorize) return keyData; // Node's a file. if (node->type == FILENODE) { // Private authentication token is the master key. keyData.mPrivateAuth = Base64::btoa(mClient.sid); // Return the node's key data to our caller. return keyData; } // Public authentication token is the account's root handle. auto rootHandle = mClient.mNodeManager.getRootNodeFiles(); // Encoded as Base64. keyData.mPublicAuth = Base64Str(&rootHandle); // Return the node's key data to our caller. return keyData; } void ClientAdapter::makeDirectory(MakeDirectoryCallback callback, const std::string& name, NodeHandle parent) { // Responsible for making our new directory. auto make = [](MakeDirectoryCallback& callback, MegaClient& client, const std::string& name, NodeHandle parent, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(unexpected(API_EINCOMPLETE)); NewNodeVector nodes(1); // Describe our new node. client.putnodes_prepareOneFolder(&nodes[0], name, false); // Forwards result to our notiifer. auto created = [](MakeDirectoryCallback& callback, MegaClient& client, NewNodeVector& nodes, Error result) { // Couldn't make the directory. if (result != API_OK) return callback(unexpected(result)); // Convenience. auto handle = NodeHandle().set6byte(nodes[0].mAddedHandle); // Get our hands on the new node. auto node = client.nodeByHandle(handle); // Node doesn't exist. if (!node) return callback(unexpected(API_EINTERNAL)); // Transmit node's description to caller. callback(describe(*node)); }; // created // Ask the client to create our new directory. client.putnodes(parent, UseLocalVersioningFlag, std::move(nodes), nullptr, 0, false, {}, // customerIpPort std::bind(std::move(created), std::move(callback), std::ref(client), std::placeholders::_3, std::placeholders::_1)); }; // make // Ask the client to make our new directory. execute(std::bind(std::move(make), wrap(std::move(callback)), std::ref(mClient), name, parent, std::placeholders::_1)); } void ClientAdapter::move(MoveCallback callback, NodeHandle source, NodeHandle target) { // Sanity. assert(callback); assert(!source.isUndef()); assert(!target.isUndef()); // Actually moves the node. auto move = [=](MoveCallback& callback, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(API_EINCOMPLETE); // Get our hands on the nodes in question. auto source_ = mClient.nodeByHandle(source); auto target_ = mClient.nodeByHandle(target); // Either node no longer exists. if (!source_ || !target_) return callback(API_ENOENT); // Ask the client to move the node. auto result = mClient.rename(source_, target_, SYNCDEL_NONE, NodeHandle(), nullptr, false, std::bind(callback, std::placeholders::_2)); // Client error. if (result != API_OK) callback(result); }; // move execute(std::bind(std::move(move), wrap(std::move(callback)), std::placeholders::_1)); } bool ClientAdapter::isClientThread() const { return std::this_thread::get_id() == mThreadID; } ErrorOr<NodeHandle> ClientAdapter::parentHandle(NodeHandle handle) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Locate specified node. auto node = mClient.nodeByHandle(handle); // Node exists. if (node) return node->parentHandle(); // Node doesn't exist. return unexpected(API_ENOENT); } auto ClientAdapter::partialDownload(PartialDownloadCallback& callback, NodeHandle handle, std::uint64_t length, std::uint64_t offset) -> ErrorOr<PartialDownloadPtr> { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Couldn't locate the specified node. if (!node) return unexpected(API_ENOENT); // Node isn't a file. if (node->type != FILENODE) return unexpected(API_FUSE_EISDIR); // Node's decryption key isn't available. if (!node->keyApplied()) return unexpected(API_EKEY); // Instantiate and populate node key data. NodeKeyData keyData; keyData.mKeyAndIV = node->nodekey(); keyData.mIsPublicHandle = false; // Convenience. auto size = static_cast<std::uint64_t>(node->size); // Sanitize length and offset. offset = std::min(offset, size); length = std::min(length, size - offset); // Return partial download to our caller. return partialDownload(callback, handle, keyData, length, offset); } auto ClientAdapter::partialDownload(PartialDownloadCallback& callback, NodeHandle handle, const NodeKeyData& keyData, std::uint64_t length, std::uint64_t offset) -> ErrorOr<PartialDownloadPtr> { // No handle? No download. if (handle.isUndef()) return unexpected(API_EARGS); // Caller's passed us a bogus key. if (keyData.mKeyAndIV.size() != FILENODEKEYLENGTH) return unexpected(API_EKEY); // Return explicit partial download to caller. return std::make_shared<ClientPartialDownload>(callback, *this, handle, keyData, length, offset); } ErrorOr<accesslevel_t> ClientAdapter::permissions(NodeHandle handle) const { // Make sure deinitialize(...) waits for this call to complete. auto activity = mActivities.begin(); // Client's being torn down. if (mDeinitialized) return unexpected(LOCAL_LOGGED_OUT); // Acquire RNT lock. std::lock_guard<std::recursive_mutex> guard(mClient.nodeTreeMutex); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return unexpected(API_ENOENT); // Do we have full access to this node? if (mClient.checkaccess(node.get(), FULL)) return FULL; // Node's effectively read-only. return RDONLY; } void ClientAdapter::remove(RemoveCallback callback, NodeHandle handle) { // Actually removes the node. auto remove = [=](RemoveCallback& callback, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(API_EINCOMPLETE); // Locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return callback(API_ENOENT); // Ask the client to remove the node. auto result = mClient.unlink(node.get(), false, 0, false, std::bind(callback, std::placeholders::_2)); // Client error. if (result != API_OK) callback(result); }; // task // Ask the client to remove the node. execute(std::bind(std::move(remove), wrap(std::move(callback)), std::placeholders::_1)); } void ClientAdapter::rename(RenameCallback callback, const std::string& name, NodeHandle handle) { // Sanity. assert(callback); assert(!name.empty()); assert(!handle.isUndef()); // Actually renames the node. auto rename = [=](RenameCallback& callback, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(API_EINCOMPLETE); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return callback(API_ENOENT); // Node already has the specified name. if (node->hasName(name)) return callback(API_OK); // Ask the client to rename the node. auto result = mClient.setattr(node, attr_map('n', name), std::bind(callback, std::placeholders::_2), false); // Client error. if (result != API_OK) return callback(result); }; // rename // Ask the client to rename the node. execute(std::bind(std::move(rename), wrap(std::move(callback)), std::placeholders::_1)); } std::string ClientAdapter::sessionID() const { // Sanity. assert(mClient.sid.size() >= MegaClient::SIDLEN); // Extract session ID. auto id = mClient.sid.substr(sizeof(mClient.key.key)); // Return ID to caller. return Base64::btoa(id); } void ClientAdapter::storageInfo(StorageInfoCallback callback) { // Actually retrieves the user's storage info. auto getStorageInfo = [=](StorageInfoCallback& callback, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(unexpected(API_EINCOMPLETE)); // Forward result to user callback. auto retrieved = [=](StorageInfoCallback& callback, const StorageInfo& info, Error result) { if (result != API_OK) return callback(unexpected(result)); callback(info); }; // retrieved // Ask the client for our storage statistics. mClient.getstorageinfo( std::bind(std::move(retrieved), std::move(callback), std::placeholders::_1, std::placeholders::_2)); }; // getStorageInfo // Ask the client to retrieve our storage statistics. execute(std::bind(std::move(getStorageInfo), wrap(std::move(callback)), std::placeholders::_1)); } void ClientAdapter::touch(TouchCallback callback, NodeHandle handle, m_time_t modified) { // Sanity. assert(callback); assert(!handle.isUndef()); // Actually updates the node's timestamp. auto touch = [=](TouchCallback& callback, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(API_EINCOMPLETE); // Try and locate the specified node. auto node = mClient.nodeByHandle(handle); // Node doesn't exist. if (!node) return callback(API_ENOENT); // Node doesn't describe a file. if (node->type != FILENODE) return callback(API_FUSE_EISDIR); // Node's modification time hasn't changed. if (node->mtime == modified) return callback(API_OK); // Compute the node's new fingerprint attribute. auto attribute = ([&]() { // Grab the node's current fingerprint. auto fingerprint = node->fingerprint(); // Update the modification time. fingerprint.mtime = modified; std::string attribute; // Serialize the fingerprint into an attribute. fingerprint.serializefingerprint(&attribute); // Return attribute to caller. return attribute; })(); // Ask the client to update the node's attribute. auto result = mClient.setattr(node, attr_map('c', std::move(attribute)), std::bind(std::move(callback), std::placeholders::_2), false); // Can't update the node's fingerprint attribute. if (result != API_OK) callback(result); }; // touch // Ask the client to update the node's modification time. execute(std::bind(std::move(touch), wrap(std::move(callback)), std::placeholders::_1)); } void ClientAdapter::updated(const sharedNode_vector& nodes) { // Should never happen. if (nodes.empty()) return; std::lock_guard guard(mEventObserversLock); // No observers? Nothing to do! if (mEventObservers.empty()) return; // Translate node vector into event queue. ClientNodeEventQueue events(nodes); // Broadcast events to our observers. for (auto* observer: mEventObservers) { // Let the observer know what's changed. observer->updated(events); // Restore any popped events. events.restore(); } } UploadPtr ClientAdapter::upload(const LocalPath& logicalPath, const std::string& name, NodeHandle parent, const LocalPath& physicalPath) { // Sanity. assert(!name.empty()); assert(!parent.isUndef()); assert(!physicalPath.empty()); // Instantiate an object to perform our upload. auto upload = std::make_shared<ClientUpload>(*this, logicalPath, parent, name, physicalPath); // Let the upload know about itself. upload->inject(upload); // Return the upload to the caller. return std::make_shared<ClientUploadAdapter>(std::move(upload)); } bool ClientTransfer::isFuseTransfer() const { return true; } void ClientDownload::completed(Transfer*, putsource_t) { // Latch the callback. auto callback = std::move(mCallback); // Tell waiter that we've completed. callback(API_OK); // Delete ourselves. delete this; } void ClientDownload::terminated(mega::error result) { // A terminated download should always be an error. if (result == API_OK) result = API_EINCOMPLETE; // Latch the callback. auto callback = std::move(mCallback); // Tell waiter that we encountered an error. callback(result); // Delete ourselves. delete this; } ClientDownload::ClientDownload(std::function<void(Error)> callback, const LocalPath& logicalPath, const Node& node, const LocalPath& physicalPath) : ClientTransfer() , mCallback(std::move(callback)) { // What node do we want to download? h = node.nodeHandle(); // Where should the user think we've downloaded the file? this->logicalPath(logicalPath); // What is the name of the file we're downloading? name = node.displayname(); // Where do we want to save the node's content? setLocalname(physicalPath); // What are the file's current attributes? static_cast<FileFingerprint&>(*this) = node; } bool ClientDownload::begin(MegaClient& client) { TransferDbCommitter committer(client.tctable); auto result = API_OK; // Try and start the transfer. client.startxfer(GET, this, committer, false, false, true, NoVersioning, &result, client.nextreqtag()); // Transfer's been started. if (result == API_OK) return client.waiter->notify(), true; // Latch callback. auto callback = std::move(mCallback); // Couldn't start the transfer. callback(result); return false; } auto child(MegaClient& client, NodeHandle parent, const std::string& name) -> ErrorOr<std::shared_ptr<Node>> { // Locate specified parent. auto parent_ = client.nodeByHandle(parent); // Parent doesn't exist. if (!parent_) return unexpected(API_ENOENT); // Parent isn't a directory. if (parent_->type == FILENODE) return unexpected(API_FUSE_ENOTDIR); std::shared_ptr<Node> candidate; // Try and locate unique child. for (auto child : client.getChildren(parent_.get())) { // Child's name isn't a match. if (!child->hasName(name)) continue; // Already seen an instance of this name. if (candidate) return unexpected(API_FUSE_EDUPLICATE); // Remember that we've seen this name. candidate = child; } // Couldn't find the named child. if (!candidate) return unexpected(API_FUSE_ENOTFOUND); // Return child (if any) to caller. return candidate; } void describe(NodeInfo& destination, accesslevel_t permissions, Node& source) { // Populate description of node. destination.mIsDirectory = source.type != FILENODE; destination.mHandle = source.nodeHandle(); destination.mModified = source.ctime; destination.mName = source.displayname(); destination.mParentHandle = source.parentHandle(); destination.mPermissions = permissions; destination.mSize = 4096; // Directories don't have a sane mtime or size. if (destination.mIsDirectory) return; destination.mModified = source.mtime; destination.mSize = source.size; } NodeInfo describe(Node& node) { // Assume node is read-only. auto permissions = RDONLY; // Node's actually writable. if (node.client->checkaccess(&node, FULL)) permissions = FULL; NodeInfo info; // Populate description of node. describe(info, permissions, node); // Return description to caller. return info; } ClientNodeEvent::ClientNodeEvent(sharedNode_vector::const_iterator position) : mPosition(position) { } bool ClientNodeEvent::isDirectory() const { return (*mPosition)->type != FILENODE; } NodeHandle ClientNodeEvent::handle() const { return (*mPosition)->nodeHandle(); } NodeInfo ClientNodeEvent::info() const { return describe(**mPosition); } const std::string& ClientNodeEvent::name() const { if (!(*mPosition)->hasName()) return Node::CRYPTO_ERROR; auto& name = (*mPosition)->attrs.map.at('n'); if (name.empty()) return Node::BLANK; return name; } NodeHandle ClientNodeEvent::parentHandle() const { return (*mPosition)->parentHandle(); } NodeEventType ClientNodeEvent::type() const { // Convenience. const auto& node = **mPosition; // Node's been added. if (node.changed.newnode) return NODE_EVENT_ADDED; // Node's been removed. if (node.changed.removed) return NODE_EVENT_REMOVED; // Node's been moved or renamed. if (node.changed.parent || node.changed.name) return NODE_EVENT_MOVED; // A share's permissions have changed. if (node.changed.inshare) return NODE_EVENT_PERMISSIONS; // Node's been modified in some unspecified way. return NODE_EVENT_MODIFIED; } ClientNodeEventQueue::ClientNodeEventQueue(const sharedNode_vector& events) : mCurrent(events.begin()) , mEvents(events) { } bool ClientNodeEventQueue::empty() const { return mCurrent.mPosition == mEvents.end(); } const ClientNodeEvent& ClientNodeEventQueue::front() const { return mCurrent; } void ClientNodeEventQueue::restore() { mCurrent = mEvents.begin(); } void ClientNodeEventQueue::pop_front() { ++mCurrent.mPosition; } std::size_t ClientNodeEventQueue::size() const { return mEvents.size(); } void ClientPartialDownload::completed(Error result) { // Check and update download status. { std::lock_guard guard(mLock); // Download's already been completed. if (mStatus & SF_COMPLETED) return; // Convenience. auto mask = SF_CANCELLABLE | SF_IN_PROGRESS; // Update the download's status. mStatus = (mStatus & ~mask) | SF_COMPLETED; // Downloads been effectively cancelled. if (result == API_EINCOMPLETE) mStatus |= SF_CANCELLED; } // Let the user know their download has completed. mCallback.completed(result); } void ClientPartialDownload::data(Data& data) { // Assume the download should be terminated. data.ret = false; // Download's been cancelled. if (cancelled()) return completed(API_EINCOMPLETE); // Convenience. auto buffer = reinterpret_cast<const void*>(data.buffer); auto offset = static_cast<std::uint64_t>(data.offset); auto length = static_cast<std::uint64_t>(data.len); // Clamp length. length = std::min(length, mRemaining); // Pass data to the user callback. auto result = mCallback.data(buffer, offset, length); // Figure out how many bytes we still have to download. mRemaining -= length; // The user's callback cancelled the download. if (std::holds_alternative<Abort>(result)) return completed(API_EINCOMPLETE); // Download's been cancelled. if (cancelled()) return completed(API_EINCOMPLETE); // We've got all the data we asked for. if (!mRemaining) return completed(API_OK); // Continue the download. data.ret = true; } void ClientPartialDownload::failure(Failure& failure) { // Assume the download will be terminated. failure.ret = NEVER; // Client's being torn down or the read's been aborted. if (failure.e == API_EINCOMPLETE) return completed(API_EINCOMPLETE); // Dispatch the callback. auto result = mCallback.failed(failure.e, failure.retry); // Download's been cancelled. if (cancelled()) return completed(API_EINCOMPLETE); // Convenience. using Retry = PartialDownloadCallback::Retry; // Let the SDK know if it should abort the download or retry. std::visit(overloaded{[&](const Abort&) { // Let the user know why the read has completed. completed(failure.e); }, [&](const Retry& retry) { // Retry in mWhen ds. failure.ret = retry.mWhen.count(); }}, result); } bool ClientPartialDownload::inProgress() { std::lock_guard guard(mLock); // The download's already begun or already completed. if (mStatus != SF_CANCELLABLE) return false; // Mark the download as in progress. mStatus |= SF_IN_PROGRESS; // Let the caller know the download's in progress. return true; } void ClientPartialDownload::notify(PartialDownloadWeakPtr cookie, Event& event) { // Convenience. using Revoke = DirectRead::Revoke; using Valid = DirectRead::IsValid; // Try and get a reference to ourselves. auto download = std::static_pointer_cast<ClientPartialDownload>(cookie.lock()); // Download's been destroyed. if (!download) { // Let the SDK know it can terminate the download. return std::visit(overloaded{[&](Data& data) { data.ret = false; }, [&](Failure& failure) { failure.ret = NEVER; }, [&](Revoke& revoke) { revoke.ret = true; }, [&](Valid& valid) { valid.ret = false; }}, event); } // Make sure other threads don't cancel the download while we are // executing callbacks. std::lock_guard guard(download->mLock); // Dispatch the event. std::visit(overloaded{[&](Data& data) { // Delegate. download->data(data); }, [&](Failure& failure) { // Delegate. download->failure(failure); }, [&](Revoke& revoke) { // Never revoked by intermediate layer. revoke.ret = false; }, [&](Valid& valid) { // We're valid if the download hasn't been cancelled. valid.ret = !download->cancelled(); }}, event); } ClientPartialDownload::ClientPartialDownload(PartialDownloadCallback& callback, ClientAdapter& client, NodeHandle handle, const NodeKeyData& keyData, std::uint64_t length, std::uint64_t offset): PartialDownload(), enable_shared_from_this(), mCallback(callback), mClient(client), mHandle(handle), mKeyData(keyData), mLock(), mOffset(offset), mRemaining(length), mStatus{SF_CANCELLABLE} {} ClientPartialDownload::~ClientPartialDownload() { // Let the user know the download's been completed. completed(API_EINCOMPLETE); } void ClientPartialDownload::begin() { // Download's already begun or already been completed. if (!inProgress()) return; // So we can test later whether the user's destroyed this instance. auto cookie = weak_from_this(); // Try and begin the download. mClient.execute( [=](const Task& task) mutable { // Check whether this download is still alive. auto download = cookie.lock(); // Download's been destroyed. if (!download) return; // Make sure cancellation status doesn't change during setup. std::lock_guard guard(mLock); // Client's being torn down. if (task.cancelled()) return completed(API_EINCOMPLETE); // Download's been cancelled. if (cancelled()) return completed(API_EINCOMPLETE); // Sanitized length is zero so complete the download early. if (!mRemaining) return completed(API_OK); // Convenience. auto& keyAndIV = mKeyData.mKeyAndIV; // Sanity. assert(keyAndIV.size() == FILENODEKEYLENGTH); // Populate cipher. SymmCipher cipher; cipher.setkey(&keyAndIV); // Latch IV for convenience. auto iv = MemAccess::get<std::int64_t>(&keyAndIV[SymmCipher::KEYLENGTH]); // Begin the download. mClient.client().pread( mHandle.as8byte(), &cipher, iv, static_cast<std::int64_t>(mOffset), static_cast<std::int64_t>(mRemaining), std::bind(&ClientPartialDownload::notify, std::move(cookie), std::placeholders::_1), mKeyData.mIsPublicHandle, toCharPointer(mKeyData.mPrivateAuth), toCharPointer(mKeyData.mPublicAuth), toCharPointer(mKeyData.mChatAuth)); }); } bool ClientPartialDownload::cancel() { // Check and update the download's status. { std::lock_guard guard(mLock); // Download's already been cancelled or completed. if (!(mStatus & SF_CANCELLABLE)) return false; // Compute the download's new status. mStatus = (mStatus ^ SF_CANCELLABLE) | SF_CANCELLED | SF_COMPLETED; } // Let the user know their download has been cancelled. mCallback.completed(API_EINCOMPLETE); // Let the caller know the download has been cancelled. return true; } bool ClientPartialDownload::cancellable() const { std::lock_guard guard(mLock); // Check if we can be cancelled. return (mStatus & SF_CANCELLABLE) > 0; } bool ClientPartialDownload::cancelled() const { std::lock_guard guard(mLock); // Check if the download's been cancelled. return (mStatus & SF_CANCELLED) > 0; } bool ClientPartialDownload::completed() const { std::lock_guard guard(mLock); // Check if we've been completed. return (mStatus & SF_COMPLETED) > 0; } void ClientUpload::bind(BoundCallback callback, FileNodeKey fileKey, NodeHandle lastHandle, ClientUploadPtr self, UploadHandle uploadHandle, std::string fileAttr, UploadToken uploadToken) { static NewNodeVector empty; // Sanity. assert(callback); StatusFlags expected = SF_CANCELLABLE; // The upload's been cancelled. if (!mStatus.compare_exchange_weak(expected, 0u)) return bound(std::move(callback), empty, false, mResult); // Binds our data to a name. auto bind = [this](BoundCallback& callback, FileNodeKey& fileKey, NodeHandle lastHandle, const Task& task, ClientUploadPtr self, UploadHandle& uploadHandle, std::string& fileAttr, UploadToken& uploadToken) { // Client's being torn down. if (task.cancelled()) return bound(std::move(callback), empty, false, API_EINCOMPLETE); // Ask the client to bind a name to our data. sendPutnodesOfUpload(&mClient.client(), std::move(uploadHandle), std::move(fileAttr), std::move(uploadToken), std::move(fileKey), PUTNODES_APP, lastHandle, std::bind(&ClientUpload::bound, std::move(self), mClient.wrap(std::move(callback)), std::placeholders::_3, std::placeholders::_4, std::placeholders::_1), nullptr, false, getPitag()); }; // bind // Called when our content has been bound to a name. auto bound = [this](BoundCallback& callback, ErrorOr<NodeHandle> result) { // Mark upload as having been completed. mStatus.store(SF_COMPLETED); // Forward result to user callback. callback(result); }; // bound // Wrap user callback. callback = std::bind(std::move(bound), std::move(callback), std::placeholders::_1); // Ask the client to bind a name to our data. mClient.execute(std::bind(std::move(bind), std::move(callback), std::move(fileKey), lastHandle, std::placeholders::_1, std::move(self), std::move(uploadHandle), std::move(fileAttr), std::move(uploadToken))); } void ClientUpload::bound(BoundCallback callback, NewNodeVector& nodes, bool overridden, Error result) { // Assume we couldn't bind the content to a name. ErrorOr<NodeHandle> handle = unexpected(result); // Mark upload as having been completed. mStatus.store(SF_COMPLETED); // Content was actually bound to a name. if (result == API_OK) handle = NodeHandle().set6byte(nodes.front().mAddedHandle); // Forward result to user callback. callback(handle); // Notify application directly if we're on the client thread. if (mClient.isClientThread()) return mClient.client().app->putnodes_result(result, NODE_HANDLE, nodes, overridden, tag); // Invokes application callback. auto wrapper = [](MegaApp& application, NewNodeVectorPtr& nodes, bool overridden, Error result, int ownTag, const Task&) { application.putnodes_result(result, NODE_HANDLE, *nodes, overridden, ownTag); }; // wrapper // Wrapper takes ownership of the new nodes. auto nodes_ = std::make_shared<NewNodeVector>(std::move(nodes)); // Notify application on the client thread. mClient.execute(std::bind(std::move(wrapper), std::ref(*mClient.client().app), std::move(nodes_), overridden, result, tag, std::placeholders::_1)); } void ClientUpload::completed(Transfer* upload, putsource_t) { // Sanity. assert(upload); std::string fileAttr; mClient.client().pendingattrstring(upload->uploadhandle, &fileAttr); // Instantiate bind callback. BindCallback bind = std::bind(&ClientUpload::bind, this, std::placeholders::_1, upload->filekey, std::placeholders::_2, std::move(mSelf), upload->uploadhandle, std::move(fileAttr), *upload->ultoken); // Latch callback. // // The reason we're moving the callback into a local here is to ensure // that the callback's closure is destroyed when this function returns. // // This is necessary to prevent reference cycles. // // That is, the callback might reference this upload which itself // references the callback. auto callback = std::move(mCallback); // Let the user know they can bind a name to their data. callback(std::move(bind)); } void ClientUpload::terminated(mega::error result) { // Make sure we always have a sane result. mResult = result == API_OK ? API_EINCOMPLETE : result; // Signal that the upload has completed. mStatus |= SF_COMPLETED; // Latch callback. // // See completed(...) as to why this is necessary. auto callback = std::move(mCallback); // Let the user know the upload failed. callback(unexpected(mResult)); // Let ourselves be destroyed. mSelf.reset(); } ClientUpload::ClientUpload(ClientAdapter& client, const LocalPath& logicalPath, NodeHandle parentHandle, const std::string& name, const LocalPath& physicalPath) : ClientTransfer() , mCallback() , mClient(client) , mResult(API_OK) , mSelf() , mStatus{SF_CANCELLABLE} { // Sanity. assert(!parentHandle.isUndef()); assert(!name.empty()); assert(!physicalPath.empty()); // Who will be the parent of our new node? h = parentHandle; // What file should we say we are uploading? this->logicalPath(logicalPath); // What file are we uploading? setLocalname(physicalPath); // What shall our new node be called? this->name = name; setPitag(Pitag{PitagPurpose::Fuse, PitagTrigger::NotApplicable, PitagNodeType::NotApplicable, PitagTarget::NotApplicable, PitagImportSource::NotApplicable}); } ClientUpload::~ClientUpload() { static NewNodeVector empty; // File's been uploaded but hasn't been bound. if (!mStatus) bound([](ErrorOr<NodeHandle>) { }, empty, false, API_EINCOMPLETE); } void ClientUpload::begin(UploadCallback callback) { // Make sure the upload hasn't already been started. assert(!mCallback); // Squirrel away the upload callback. mCallback = std::move(callback); // Ask the client to begin the upload. mClient.execute([this](const Task& task) { // Client's being torn down. if (task.cancelled()) return terminated(API_EINCOMPLETE); // We've been cancelled. if (cancelled()) return terminated(API_EINCOMPLETE); // Convenience. auto& client = mClient.client(); // So we can alter the transfer database. TransferDbCommitter committer(client.tctable); auto result = API_OK; // Try and begin the upload. client.startxfer(PUT, this, committer, false, false, true, UseServerVersioningFlag, &result, client.nextreqtag()); // Couldn't begin the upload. if (result != API_OK) return terminated(result); // Let the client know it has work to do. client.waiter->notify(); }); } bool ClientUpload::cancel() { StatusFlags desired = SF_CANCELLED | SF_COMPLETED; StatusFlags expected = SF_CANCELLABLE; // Upload's not in a cancellable state. // // Say, the upload's been completed or we're in the // process of binding the uploaded data to a name. if (!mStatus.compare_exchange_weak(expected, desired)) return false; auto terminate = [](ClientUploadWeakPtr cookie, const Task& task) { // Client's being torn down. if (task.cancelled()) return; // Make sure we're still alive. auto self = cookie.lock(); // We've been released. if (!self) return; // Get our hands on the real client. auto& client = self->mClient.client(); // So we can modify the transfer database. TransferDbCommitter committer(client.tctable); // Stop the upload. client.stopxfer(self.get(), &committer); }; // terminate // Ask the client to terminate the upload. mClient.execute( std::bind(std::move(terminate), ClientUploadWeakPtr(mSelf), std::placeholders::_1)); // Let the caller know the upload's been cancelled. return true; } bool ClientUpload::cancelled() const { return (mStatus & SF_CANCELLED) > 0; } bool ClientUpload::completed() const { return (mStatus & SF_COMPLETED) > 0; } void ClientUpload::inject(ClientUploadPtr self) { assert(self); assert(self.get() == this); mSelf = std::move(self); } Error ClientUpload::result() const { return mResult; } ClientUploadAdapter::ClientUploadAdapter(ClientUploadPtr upload) : Upload() , mUpload(std::move(upload)) { } void ClientUploadAdapter::begin(UploadCallback callback) { return mUpload->begin(std::move(callback)); } bool ClientUploadAdapter::cancel() { return mUpload->cancel(); } bool ClientUploadAdapter::cancelled() const { return mUpload->cancelled(); } bool ClientUploadAdapter::completed() const { return mUpload->completed(); } Error ClientUploadAdapter::result() const { return mUpload->result(); } } // common } // mega ��������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/client_adapter_with_sync.cpp�������������������������������������������������0000664�0000000�0000000�00000006112�15162662266�0022277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <future> #include <mega/common/client_adapter.h> #include <mega/common/normalized_path.h> #include <mega/megaclient.h> namespace mega { namespace common { void ClientAdapter::desynchronize(mega::handle id) { // So we can wait for the client's result. std::promise<void> notifier; // Remove the sync on the client thread. execute([&](const Task& task) { // Client's being torn down. if (task.cancelled()) return notifier.set_value(); // So we can use set_value(...) as our completion function. auto completion = std::bind(&std::promise<void>::set_value, ¬ifier); // Ask the client to remove our sync. mClient.syncs.deregisterThenRemoveSyncById(id, std::move(completion)); }); // Wait for the client to process our request. notifier.get_future().get(); } bool ClientAdapter::mountable(const NormalizedPath& path) const { // Empty target path on Windows is allowed and mean the system to allocate the path. It is // mountable if (path.empty()) return true; // Check if the sync described by us is related to path. auto isRelated = [&path](const UnifiedSync& us) { return path.related(us.mConfig.mLocalPath); }; // isRelated // Are any syncs related to path? auto anyRelated = mClient.syncs.anySyncMatching(isRelated); // Path is only mountable if it is unrelated to any active sync. return !anyRelated; } auto ClientAdapter::synchronize(const NormalizedPath& path, NodeHandle target) -> std::tuple<mega::handle, Error, SyncError> { // Convenience. using Result = decltype(synchronize(NormalizedPath(), NodeHandle())); std::promise<Result> notifier; // Transmit a result to our notifier. auto notify = [&](mega::handle handle, Error error, SyncError syncError) { return notifier.set_value({handle, error, syncError}); }; // notify // Add the new sync on the client thread. execute([&](const Task& task) { // Client's being torn down. if (task.cancelled()) return notify(UNDEF, API_EINCOMPLETE, NO_SYNC_ERROR); // So we can use notify as our completion function. auto completion = std::bind(std::move(notify), std::placeholders::_3, std::placeholders::_1, std::placeholders::_2); // Populate sync config object. auto config = SyncConfig(path, std::string(), target, std::string(), fsfp_t(), LocalPath()); // Ask the client to add our new sync. mClient.addsync(std::move(config), std::move(completion), std::string(), std::string()); }); // Return the client's result to our caller. return notifier.get_future().get(); } } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/client_adapter_without_sync.cpp����������������������������������������������0000664�0000000�0000000�00000000642�15162662266�0023031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/client_adapter.h> namespace mega { namespace common { void ClientAdapter::desynchronize(mega::handle) { } bool ClientAdapter::mountable(const NormalizedPath&) const { return true; } auto ClientAdapter::synchronize(const NormalizedPath&, NodeHandle) -> std::tuple<mega::handle, Error, SyncError> { return std::make_tuple(UNDEF, API_EFAILED, NO_SYNC_ERROR); } } // common } // mega ����������������������������������������������������������������������������������������������sdk-10.11.0/src/common/database.cpp�����������������������������������������������������������������0000664�0000000�0000000�00000005633�15162662266�0017005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <cstddef> #include <mutex> #include <utility> #include <sqlite3.h> #include <mega/common/badge.h> #include <mega/common/database.h> #include <mega/common/logger.h> #include <mega/common/logging.h> #include <mega/common/query.h> #include <mega/common/transaction.h> #include <mega/filesystem.h> namespace mega { namespace common { std::string Database::execute(const char* statement) { assert(mDB); assert(statement); char* message = nullptr; auto result = sqlite3_exec(mDB, statement, nullptr, nullptr, &message); if (result == SQLITE_OK) return std::string(); std::string temp = message; sqlite3_free(message); return temp; } Database::Database(Logger& logger, const LocalPath& path) : Lockable() , mDB(nullptr) , mLogger(&logger) , mPath(path.toPath(false)) { // Log a suitable error message and return an exception. auto failed = [this](const std::string& message) { // Ensure the database has been closed. sqlite3_close(mDB); // Log the failure and return a suitable exception. return LogErrorF(*mLogger, "Unable to open database: %s: %s", mPath.c_str(), message.c_str()); }; // failed // Keeps the open call itself simple. constexpr auto flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE; // Try and open the database. auto result = sqlite3_open_v2(mPath.c_str(), &mDB, flags, nullptr); // Couldn't open the database. if (result != SQLITE_OK) throw failed(sqlite3_errstr(result)); // Try and enable journalling. auto message = execute("pragma journal_mode = WAL"); // Try and enable foreign keys. if (message.empty()) message = execute("pragma foreign_keys = ON"); // Couldn't enable journalling or foreign key support. if (!message.empty()) throw failed(sqlite3_errmsg(mDB)); // Database has been opened successfully. LogDebugF(*mLogger, "Database opened: %s", mPath.c_str()); } Database::Database(Database&& other) : mDB() , mLogger() , mPath() { DatabaseLock guard(other); mDB = other.mDB; mLogger = other.mLogger; mPath = std::move(other.mPath); other.mDB = nullptr; other.mLogger = nullptr; } Database::~Database() { sqlite3_close(mDB); if (mLogger) LogDebugF(*mLogger, "Database closed: %s", mPath.c_str()); } sqlite3* Database::get(Badge<Query>) { return mDB; } Logger& Database::logger() const { assert(mLogger); return *mLogger; } Query Database::query() { return Query({}, *this); } Transaction Database::transaction() { return Transaction({}, *this); } } // common } // mega �����������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/database_builder.cpp���������������������������������������������������������0000664�0000000�0000000�00000007356�15162662266�0020517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <limits> #include <mega/common/database_builder.h> #include <mega/common/database_utilities.h> #include <mega/common/logging.h> namespace mega { namespace common { static std::size_t currentVersion(Query& query); void DatabaseBuilder::downgrade(const DatabaseVersionVector& versions, std::size_t target) { withQuery(mDatabase, [&](Query&& query) { // What version are we at? auto current = currentVersion(query); // Already at or below the target version. if (current <= target) return; // Downgrade to the target version. for (; current > target; --current) { const auto& version = versions[current - 1]; // Sanity. assert(version.mDowngrade); LogDebugF(query.logger(), "Downgrading database to version %zu", current - 1); version.mDowngrade(query); query = "delete from version where version = :version"; query.param(":version").set(current); query.execute(); } }); } void DatabaseBuilder::upgrade(const DatabaseVersionVector& versions, std::size_t target) { target = std::min(target, versions.size()); withQuery(mDatabase, [&](Query&& query) { // What version are we at? auto current = currentVersion(query); // Already at or above the target version. if (current >= target) return; // Make sure the database has a version table. if (!current) { // Tracks which datahbase upgrades have been performed. query = "create table if not exists version ( " " version integer " " constraint nn_version_version " " not null, " " constraint pk_version " " primary key (version) " ")"; query.execute(); } // Upgrade to the target version. while (current < target) { auto& version = versions[current++]; // Sanity. assert(version.mUpgrade); LogDebugF(query.logger(), "Upgrading database to version %zu", current); version.mUpgrade(query); query = "insert into version values (:version)"; query.param(":version").set(current); query.execute(); } }); } DatabaseBuilder::DatabaseBuilder(Database& database) : mDatabase(database) { } void DatabaseBuilder::build() { upgrade(versions(), std::numeric_limits<std::size_t>::max()); } void DatabaseBuilder::downgrade(std::size_t target) { downgrade(versions(), target); } void DatabaseBuilder::upgrade(std::size_t target) { upgrade(versions(), target); } std::size_t currentVersion(Query& query) { // Check if the "version" table exists. query = "select name " " from sqlite_master " " where name = :name " " and type = :type"; query.param(":name").set("version"); query.param(":type").set("table"); query.execute(); // Table doesn't exist: Must be the initial version. if (!query) return 0u; // Determine the database's current version. query = " select version " " from version " " order by version desc " " limit 1"; query.execute(); // There's at least one version in the table. if (query) return query.field("version").get<std::size_t>(); // No versions yet: Must be the initial version. return 0u; } } // common } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/date_time.cpp����������������������������������������������������������������0000664�0000000�0000000�00000001134�15162662266�0017164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/date_time.h> #include <mega/utils.h> #include <iomanip> #include <sstream> namespace mega { namespace common { namespace detail { bool DateTime::operator==(const DateTime& rhs) const { return mValue == rhs.mValue; } bool DateTime::operator!=(const DateTime& rhs) const { return mValue != rhs.mValue; } std::string toString(const DateTime& value) { std::ostringstream ostream; struct tm tm; m_localtime(value.asValue<m_time_t>(), &tm); ostream << std::put_time(&tm, "%Y/%m/%d %H:%M:%S"); return ostream.str(); } } // detail } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/directory.cpp����������������������������������������������������������������0000664�0000000�0000000�00000001541�15162662266�0017237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/directory.h> #include <mega/common/logging.h> #include <mega/filesystem.h> namespace mega { namespace common { Directory::Directory(FileSystemAccess& filesystem, Logger& logger, const std::string& name, const LocalPath& rootPath): mFilesystem(filesystem), mPath(rootPath) { mPath.appendWithSeparator(LocalPath::fromRelativePath(name), true); if (!mFilesystem.mkdirlocal(mPath, false, false) && !mFilesystem.target_exists) throw LogErrorF(logger, "Couldn't create directory: %s", mPath.toPath(false).c_str()); LogDebugF(logger, "Created directory: %s", mPath.toPath(false).c_str()); } Directory::operator const LocalPath&() const { return path(); } auto Directory::path() const -> const LocalPath& { return mPath; } } // common } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/logger.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000005201�15162662266�0016507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <sstream> #include <stdexcept> #include <thread> #include <mega/log_level.h> #include <mega/common/logger.h> #include <mega/common/utility.h> #include <mega/logging.h> namespace mega { namespace common { Logger::Logger(const char* subsystemName) : mSubsystemName(subsystemName) { } std::runtime_error Logger::error(const char* filename, const char* format, unsigned int line, ...) const { std::va_list arguments; // Compute log message. va_start(arguments, line); auto message = formatv(arguments, format); va_end(arguments); // Instantiate exception. std::runtime_error exception(message); // Emit log message. log(filename, message, line, logError); // Return exception to caller. return exception; } void Logger::log(const char* filename, const std::string& message, unsigned int line, int severity) const { // Sanity. assert(filename); assert(severity < logMax); // Last minute severity check. if (masked(severity)) return; std::ostringstream ostream; // Add subsystem name if necessary. if (mSubsystemName) ostream << mSubsystemName << ": "; // Compute log message. ostream << std::this_thread::get_id() << ": " << message; // Emit log message. SimpleLogger::postLog(static_cast<LogLevel>(severity), ostream.str().c_str(), filename, static_cast<int>(line)); } void Logger::log(const char* filename, const char* format, unsigned int line, int severity, ...) const { // Sanity. assert(filename); assert(format); std::va_list arguments; va_start(arguments, severity); // Emit log message. logv(arguments, filename, format, line, severity); va_end(arguments); } void Logger::logv(std::va_list arguments, const char* filename, const char* format, unsigned int line, int severity) const { // Sanity. assert(format); // Last minute severity check. if (masked(severity)) return; // Emit log message. log(filename, formatv(arguments, format), line, severity); } bool Logger::masked(int severity) const { return SimpleLogger::getLogLevel() < severity; } Logger& logger() { static Logger logger; return logger; } } // common } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/node_event_type.cpp����������������������������������������������������������0000664�0000000�0000000�00000000634�15162662266�0020424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/node_event_type.h> namespace mega { namespace common { const char* toString(NodeEventType type) { switch (type) { #define DEFINE_NODE_EVENT_TYPE_CLAUSE(name) case NODE_EVENT_ ## name: return #name; DEFINE_NODE_EVENT_TYPES(DEFINE_NODE_EVENT_TYPE_CLAUSE); #undef DEFINE_NODE_EVENT_TYPE_CLAUSE } // Silence the compiler. return "N/A"; } } // common } // mega ����������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/normalized_path.cpp����������������������������������������������������������0000664�0000000�0000000�00000000514�15162662266�0020412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/normalized_path.h> namespace mega { namespace common { NormalizedPath::NormalizedPath(const LocalPath& other) : LocalPath(other) { trimNonDriveTrailingSeparator(); } NormalizedPath& NormalizedPath::operator=(const LocalPath& rhs) { return operator=(NormalizedPath(rhs)); } } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/pending_callbacks.cpp��������������������������������������������������������0000664�0000000�0000000�00000002314�15162662266�0020655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <utility> #include <mega/common/pending_callbacks.h> namespace mega { namespace common { PendingCallbacks::Context::Context(PendingCallbacks& pendingCallbacks) : mPendingCallbacks(pendingCallbacks) { } PendingCallbacks::Context::~Context() = default; bool PendingCallbacks::Context::remove(const ContextPtr& context) { // Acquire lock. std::lock_guard<std::mutex> guard(mPendingCallbacks.mLock); // Try and remove this context. return mPendingCallbacks.mContexts.erase(context) > 0; } PendingCallbacks::PendingCallbacks() : mContexts() , mLock() { } PendingCallbacks::~PendingCallbacks() { // Cancel any outstanding contexts. cancel(); } void PendingCallbacks::cancel() { // Cancel contexts until we hit a steady state. while (true) { ContextSet contexts; // Take ownership of context set. { std::lock_guard<std::mutex> guard(mLock); std::swap(contexts, mContexts); } // No contexts left to cancel. if (contexts.empty()) return; // Cancel each wrapped callback in turn. for (auto& context : contexts) context->cancel(); } } } // common } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016352�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017514�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/mega/���������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020425�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/mega/common/��������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021715�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/mega/common/platform/�����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023541�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/mega/common/platform/date_time.h������������������������������0000664�0000000�0000000�00000001157�15162662266�0025651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #if defined(__ANDROID__) && !defined(HAVE_SDK_CONFIG_H) #include "mega/config-android.h" #else #include "mega/config.h" #endif #include <mega/common/date_time.h> #if HAVE_DISTINCT_TIME_T #include <ctime> namespace mega { namespace common { namespace detail { template<> struct TimeValueTraits<time_t> { static std::uint64_t from(time_t value) { return static_cast<std::uint64_t>(value); } static time_t to(std::uint64_t value) { return static_cast<time_t>(value); } }; // TimeValueTraits<time_t> } // detail } // common } // mega #endif // HAVE_DISTINCT_TIME_T �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/posix/mega/common/platform/folder_locker.h��������������������������0000664�0000000�0000000�00000000514�15162662266�0026524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <string> namespace mega { namespace common { namespace platform { // Dummy implementation class FolderLocker { public: FolderLocker() = default; FolderLocker(const std::string&) {} FolderLocker& operator=(FolderLocker&&) = default; void reset(){}; }; } // platform } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020044�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/�������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020755�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022245�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/���������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024071�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/date_time.h����������������������������0000664�0000000�0000000�00000002226�15162662266�0026177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/date_time.h> #include <mega/common/platform/windows.h> namespace mega { namespace common { namespace detail { template<> struct TimeValueTraits<UINT64> { static const UINT64 offset = 11644473600ull; static const UINT64 scale = 10000000ull; static std::uint64_t from(const UINT64 value) { return (value / scale) - offset; } static UINT64 to(std::uint64_t value) { return (value + offset) * scale; } }; // TimeValueTraits<UINT64> template<> struct TimeValueTraits<FILETIME> { static std::uint64_t from(const FILETIME& value) { UINT64 low = static_cast<UINT64>(value.dwLowDateTime); UINT64 high = static_cast<UINT64>(value.dwHighDateTime); return TimeValueTraits<UINT64>::from((high << 32) | low); } static FILETIME to(std::uint64_t value) { auto temp = TimeValueTraits<UINT64>::to(value); FILETIME result; result.dwLowDateTime = static_cast<DWORD>(temp); result.dwHighDateTime = static_cast<DWORD>(temp >> 32); return result; } }; // TimeValueTraits<FILETIME> } // detail } // common } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/folder_locker.cpp����������������������0000664�0000000�0000000�00000002103�15162662266�0027403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/common/platform/folder_locker.h" #include "mega/logging.h" namespace mega { namespace common { namespace platform { FolderLocker::FolderLocker(const std::wstring& path) { mHandle.reset(CreateFile(path.c_str(), // folder path GENERIC_READ, // desired access (must request at least read) 0, // share mode = 0 (deny all sharing) NULL, // security attributes OPEN_EXISTING, // must exist FILE_FLAG_BACKUP_SEMANTICS, // required for opening directories NULL)); if (mHandle.get() == INVALID_HANDLE_VALUE) LOG_warn << "Exclusive open folder failed " << GetLastError(); else LOG_info << "Exclusive open folder OK"; } FolderLocker& FolderLocker::operator=(FolderLocker&& other) { mHandle = std::move(other.mHandle); return *this; } FolderLocker::~FolderLocker() { reset(); } void FolderLocker::reset() { mHandle.reset(); } } // platform } // common } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/folder_locker.h������������������������0000664�0000000�0000000�00000001200�15162662266�0027045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/platform/handle.h> #include <mega/common/platform/windows.h> #include <string> namespace mega { namespace common { namespace platform { // Open the path and keep opening exclusively during the lifetime of the object // Note: If the folder has been opened by others, the exclusive opening fails. class FolderLocker { Handle<DefaultHandleDeleter> mHandle{}; public: FolderLocker() = default; // path is a folder FolderLocker(const std::wstring& path); FolderLocker& operator=(FolderLocker&& other); ~FolderLocker(); void reset(); }; } // platform } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/handle.h�������������������������������0000664�0000000�0000000�00000004166�15162662266�0025504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/platform/handle_forward.h> #include <mega/common/platform/windows.h> #include <utility> namespace mega { namespace common { namespace platform { struct DefaultHandleDeleter { void operator()(HANDLE handle) { CloseHandle(handle); } }; // DefaultHandleDeleter template<typename Deleter> class Handle { Deleter mDeleter; HANDLE mHandle; public: Handle(const Deleter& deleter = Deleter()): Handle(INVALID_HANDLE_VALUE, deleter) {} explicit Handle(HANDLE handle, const Deleter& deleter = Deleter()): mDeleter(deleter), mHandle(handle) {} Handle(Handle&& other): mDeleter(std::move(other.mDeleter)), mHandle(std::move(other.mHandle)) { other.mDeleter = Deleter(); other.mHandle = INVALID_HANDLE_VALUE; } ~Handle() { if (mHandle != INVALID_HANDLE_VALUE) mDeleter(mHandle); } Handle& operator=(Handle&& rhs) { if (this == &rhs) return *this; Handle temp(std::move(rhs)); swap(temp); return *this; } operator bool() const { return mHandle != INVALID_HANDLE_VALUE; } bool operator==(const Handle& rhs) const { return mHandle == rhs.mHandle; } bool operator!() const { return mHandle == INVALID_HANDLE_VALUE; } bool operator!=(const Handle& rhs) const { return mHandle != rhs.mHandle; } HANDLE get() const { return mHandle; } HANDLE release() { auto handle = mHandle; mHandle = INVALID_HANDLE_VALUE; return handle; } void reset(HANDLE other = INVALID_HANDLE_VALUE) { if (mHandle != INVALID_HANDLE_VALUE) mDeleter(mHandle); mHandle = other; } void swap(Handle& other) { using std::swap; swap(mDeleter, other.mDeleter); swap(mHandle, other.mHandle); } }; // Handle template<typename Deleter> void swap(Handle<Deleter>& lhs, Handle<Deleter>& rhs) { lhs.swap(rhs); } } // platform } // common } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/handle_forward.h�����������������������0000664�0000000�0000000�00000000313�15162662266�0027216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace common { namespace platform { struct DefaultHandleDeleter; template<typename Deleter = DefaultHandleDeleter> class Handle; } // platform } // common } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/platform/windows/mega/common/platform/windows.h������������������������������0000664�0000000�0000000�00000000433�15162662266�0025734�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #pragma push_macro("NOMINMAX") #pragma push_macro("WIN32_LEAN_AND_MEAN") #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #pragma pop_macro("NOMINMAX") #pragma pop_macro("WIN32_LEAN_AND_MEAN") �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/query.cpp��������������������������������������������������������������������0000664�0000000�0000000�00000030275�15162662266�0016406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/common/database.h> #include <mega/common/logging.h> #include <mega/common/query.h> #include <mega/filesystem.h> #include <mega/types.h> #include <cassert> #include <chrono> #include <sqlite3.h> #include <tuple> #include <utility> namespace mega { namespace common { class RetryTimer { // How much time should we wait, maximum, between retries? static constexpr auto MaxRetryInterval = std::chrono::seconds(2); // How much time should we spend trying to retry? static constexpr auto MaxRetryTime = std::chrono::minutes(1); // Current interval between retries. std::chrono::milliseconds mRetryInterval; // Total time taken trying to retry. std::chrono::milliseconds mRetryTime; public: RetryTimer(): mRetryInterval(8), mRetryTime(0) {} // Wait until we can perform another retry. // // Returns false if no further retries should be attempted. bool wait() { // We've already waited long enough. if (mRetryTime >= MaxRetryTime) return false; // Convenience. using std::chrono::duration_cast; using std::chrono::milliseconds; // Avoid having to cast many times. constexpr auto MaxInterval = duration_cast<milliseconds>(MaxRetryInterval); // Wait for a little while. std::this_thread::sleep_for(mRetryInterval); // Remember how long we waited. mRetryTime += mRetryInterval; // Exponentially increase retry interval. mRetryInterval = std::min(mRetryInterval * 2, MaxInterval); // Make sure our wait time never exceeds MaxTime. mRetryInterval = std::min(mRetryInterval, MaxRetryTime - mRetryTime); // Let the caller know they should retry the operation. return true; } }; // RetryTimer void Field::match(const int requested) const { #define NAME(type) {type, #type} static const std::map<int, const char*> names = { NAME(SQLITE_BLOB), NAME(SQLITE_INTEGER), NAME(SQLITE_FLOAT), NAME(SQLITE_NULL), NAME(SQLITE_TEXT) }; // names #undef NAME auto computed = type(); if (computed == requested) return; throw LogErrorF(mQuery.logger(), "Field type mismatch: Requested an %s, got a %s", names.at(requested), names.at(computed)); } std::string Field::string() const { match(SQLITE_TEXT); auto value = sqlite3_column_text(mQuery.mStatement, mIndex); if (!value) throw LogErrorF(mQuery.logger(), "Unable to extract field value: %s", sqlite3_errmsg(mQuery.database())); auto length = sqlite3_column_bytes(mQuery.mStatement, mIndex); return std::string(reinterpret_cast<const char*>(value), static_cast<std::size_t>(length)); } int Field::type() const { return sqlite3_column_type(mQuery.mStatement, mIndex); } Field::Field(const int index, Query& query) : mIndex(index) , mQuery(query) { } bool Field::null() const { return type() == SQLITE_NULL; } std::uint64_t Field::uint64() const { match(SQLITE_INTEGER); auto value = sqlite3_column_int64(mQuery.mStatement, mIndex); return static_cast<std::uint64_t>(value); } auto Parameter::null() -> Parameter& { auto result = sqlite3_bind_null(mQuery.mStatement, mIndex); if (result == SQLITE_OK) return *this; throw LogErrorF(mQuery.logger(), "Unable to bind parameter: %s", sqlite3_errmsg(mQuery.database())); } auto Parameter::string(const char* data, std::size_t length) -> Parameter& { auto result = sqlite3_bind_text(mQuery.mStatement, mIndex, data, static_cast<int>(length), SQLITE_TRANSIENT); if (result == SQLITE_OK) return *this; throw LogErrorF(mQuery.logger(), "Unable to bind parameter: %s", sqlite3_errmsg(mQuery.database())); } auto Parameter::uint64(const std::uint64_t value) -> Parameter& { auto result = sqlite3_bind_int64(mQuery.mStatement, mIndex, static_cast<sqlite3_int64>(value)); if (result == SQLITE_OK) return *this; throw LogErrorF(mQuery.logger(), "Unable to bind parameter: %s", sqlite3_errmsg(mQuery.database())); } Parameter::Parameter(const int index, Query& query) : mIndex(index) , mQuery(query) { } Query::Query(Query&& other): mDB(std::exchange(other.mDB, nullptr)), mHasNext(std::exchange(other.mHasNext, false)), mFields(std::move(other.mFields)), mParameters(std::move(other.mParameters)), mStatement(std::exchange(other.mStatement, nullptr)) {} Query::~Query() { sqlite3_finalize(mStatement); } Query& Query::operator=(Query&& rhs) { Query temp(std::move(rhs)); swap(temp); return *this; } Query& Query::operator=(const std::string& rhs) { return operator=(rhs.c_str()); } Query& Query::operator=(const char* rhs) { assert(rhs); sqlite3_stmt* statement = nullptr; auto result = sqlite3_prepare_v2(database(), rhs, -1, &statement, nullptr); if (result != SQLITE_OK) { auto exception = LogErrorF(logger(), "Unable to prepare query: %s", sqlite3_errmsg(database())); assert(false); throw exception; } std::map<std::string, int> fields; for (auto i = 0, j = sqlite3_column_count(statement); i < j; ++i) { auto* name = sqlite3_column_name(statement, i); if (!name) throw LogError1(logger(), "Unable to prepare query: " "Couldn't extract field name"); fields.emplace(name, i); } std::map<std::string, int> parameters; for (auto i = 0, j = sqlite3_bind_parameter_count(statement); i < j; ++i) { auto* name = sqlite3_bind_parameter_name(statement, i + 1); if (!name) throw LogError1(logger(), "Unable to prepare query: " "Couldn't extract parameter name"); parameters.emplace(name, i + 1); } mHasNext = false; mFields = std::move(fields); mParameters = std::move(parameters); sqlite3_finalize(mStatement); mStatement = statement; return *this; } Query::operator bool() const { return mHasNext; } Query& Query::operator++() { auto* prefix = "Unable to retrieve row"; if (!mHasNext) throw LogErrorF(logger(), "%s: No further rows available", prefix); execute(prefix); return *this; } bool Query::operator!() const { return !mHasNext; } std::uint64_t Query::changed() const { if (mStatement) return static_cast<std::uint64_t>(sqlite3_changes(database())); return 0; } void Query::clear() { auto* prefix = "Unable to clear query parameters"; if (!mStatement) throw LogErrorF(logger(), "%s: No statement has been prepared", prefix); auto result = sqlite3_clear_bindings(mStatement); if (result != SQLITE_OK) throw LogErrorF(logger(), "%s: %s", prefix, sqlite3_errmsg(database())); } bool Query::execute() { return execute("Unable to execute query"); } auto Query::field(const std::string& name) -> Field { auto i = mFields.find(name); if (i != mFields.end()) return Field(i->second, *this); throw LogErrorF(logger(), "Query has no field named \"%s\"", name.c_str()); } auto Query::field(const char* name) -> Field { return field(std::string(name)); } std::uint64_t Query::lastID() const { assert(mDB); return static_cast<std::uint64_t>(sqlite3_last_insert_rowid(database())); } Logger& Query::logger() const { assert(mDB); return mDB->logger(); } auto Query::param(const std::string& name) -> Parameter { auto i = mParameters.find(name); if (i != mParameters.end()) return Parameter(i->second, *this); auto exception = LogErrorF(logger(), "Query has no parameter named \"%s\"", name.c_str()); assert(false); throw exception; } auto Query::param(const char* name) -> Parameter { return param(std::string(name)); } void Query::reset() { // Convenience. auto* prefix = "Unable to reset query"; // Can't reset a statement that hasn't been prepared. if (!mStatement) throw LogErrorF(logger(), "%s: No statement has been prepared", prefix); // There will never be any results after the statement is reset. mHasNext = false; // Repeatedly try and reset the query. for (RetryTimer timer;;) { // Try and reset the query. auto result = sqlite3_reset(mStatement); // Query was reset successfully. if (result == SQLITE_OK) return; // Convenience. const auto* reason = sqlite3_errmsg(database()); // Couldn't reset the query because: // - We encountered a nontransient error. // - We spent too long retrying. if ((result != SQLITE_BUSY && result != SQLITE_LOCKED) || !timer.wait()) throw LogErrorF(logger(), "%s: %s", prefix, reason); // So we know when reset fails due to locks. LogWarningF(logger(), "%s: %s", prefix, reason); } } void Query::swap(Query& other) { using std::swap; swap(other.mDB, other.mDB); swap(other.mFields, other.mFields); swap(other.mParameters, other.mParameters); swap(other.mStatement, other.mStatement); } sqlite3* Query::database() const { assert(mDB); return mDB->get(Badge<Query>()); } bool Query::execute(const char* prefix) { // Sanity. assert(prefix); // Can't execute a query that hasn't been prepared. if (!mStatement) throw LogErrorF(logger(), "%s: No statement has been prepared", prefix); // Repeatedly attempt to execute the query. for (RetryTimer timer;;) { // Try and execute the query. auto result = sqlite3_step(mStatement); // Does the query have any further rows to return? mHasNext = result == SQLITE_ROW; // Query executed successfully. if (mHasNext || result == SQLITE_DONE) return mHasNext; // Reset the query. // // This is necessary for two reasons. // // First, we will want to retry the query if we were not able to // acquire a necessary file or table lock. // // Second, we want to clear the latest error set on our statement so // that later reset() calls do not fail spuriously. sqlite3_reset(mStatement); // Convenience. const auto* reason = sqlite3_errmsg(database()); // Query failed because: // - We encountered a nontransient error. // - We spent too long retrying the query. if ((result != SQLITE_BUSY && result != SQLITE_LOCKED) || !timer.wait()) throw LogErrorF(logger(), "%s: %s", prefix, reason); // So we know when queries fail due to locks. LogWarningF(logger(), "%s: %s", prefix, reason); } } Query::Query(Badge<Database>, Database& db) : mDB(&db) , mHasNext(false) , mFields() , mParameters() , mStatement(nullptr) { } LocalPath SerializationTraits<LocalPath>::from(const Field& field) { return LocalPath::fromAbsolutePath(field.get<std::string>()); } void SerializationTraits<LocalPath>::to(Parameter& parameter, const LocalPath& value) { parameter.set(value.toPath(false)); } NodeHandle SerializationTraits<NodeHandle>::from(const Field& field) { return NodeHandle().set6byte(field.get<std::uint64_t>()); } void SerializationTraits<NodeHandle>::to(Parameter& parameter, const NodeHandle& value) { parameter.set(value.as8byte()); } } // common } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/scoped_query.cpp�������������������������������������������������������������0000664�0000000�0000000�00000004220�15162662266�0017732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/common/query.h> #include <mega/common/scoped_query.h> #include <cassert> #include <stdexcept> namespace mega { namespace common { ScopedQuery::ScopedQuery() : mQuery(nullptr) { } ScopedQuery::ScopedQuery(Badge<Transaction>, Query& query): mQuery(&query) { query.clear(); } ScopedQuery::ScopedQuery(ScopedQuery&& other) : mQuery(other.mQuery) { other.mQuery = nullptr; } ScopedQuery::~ScopedQuery() { try { if (mQuery) mQuery->reset(); } catch (std::runtime_error&) { // reset() would've alreaddy logged any generated exception. } } ScopedQuery& ScopedQuery::operator=(ScopedQuery&& rhs) { ScopedQuery temp(std::move(rhs)); swap(temp); return *this; } ScopedQuery::operator bool() const { assert(mQuery); return *mQuery; } ScopedQuery& ScopedQuery::operator++() { assert(mQuery); ++*mQuery; return *this; } bool ScopedQuery::operator!() const { assert(mQuery); return !*mQuery; } std::uint64_t ScopedQuery::changed() const { assert(mQuery); return mQuery->changed(); } void ScopedQuery::clear() { assert(mQuery); mQuery->clear(); } bool ScopedQuery::execute() { assert(mQuery); return mQuery->execute(); } Field ScopedQuery::field(const std::string& name) { assert(mQuery); return mQuery->field(name); } Field ScopedQuery::field(const char* name) { assert(mQuery); return mQuery->field(name); } std::uint64_t ScopedQuery::lastID() const { assert(mQuery); return mQuery->lastID(); } Parameter ScopedQuery::param(const std::string& name) { assert(mQuery); return mQuery->param(name); } Parameter ScopedQuery::param(const char* name) { assert(mQuery); return mQuery->param(name); } Query& ScopedQuery::query() { assert(mQuery); return *mQuery; } void ScopedQuery::reset() { assert(mQuery); mQuery->clear(); mQuery->reset(); } void ScopedQuery::swap(ScopedQuery& other) { using std::swap; swap(mQuery, other.mQuery); } void swap(ScopedQuery& lhs, ScopedQuery& rhs) { lhs.swap(rhs); } } // common } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/shared_mutex.cpp�������������������������������������������������������������0000664�0000000�0000000�00000011135�15162662266�0017723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <chrono> #include <mega/common/logging.h> #include <mega/common/shared_mutex.h> #include <mega/common/shared_mutex.h> namespace mega { namespace common { using std::chrono::steady_clock; // Convenience. using steady_time = steady_clock::time_point; bool SharedMutex::try_lock_shared_until(steady_clock::time_point time, [[maybe_unused]] bool validate) { std::unique_lock<std::mutex> lock(mLock); // What thread is trying to acquire this mutex? [[maybe_unused]] auto id = std::this_thread::get_id(); // Make sure the thread doesn't already hold a write lock. assert(!validate || mWriterID != id); // Wait for the mutex to be available. auto result = mReaderCV.wait_until(lock, time, [&]() { return mCounter >= 0; }); // Couldn't acquire the mutex. if (!result) return false; // Remember that we're holding this lock. assert((++mReaders[std::this_thread::get_id()], true)); // We've acquired the mutex. ++mCounter; return true; } void SharedMutex::lock_shared() { while (!try_lock_shared_until(steady_time::max(), true)) ; } void SharedMutex::lock() { while (!try_lock_until(steady_time::max())) ; } bool SharedMutex::try_lock_shared() { return try_lock_shared_until(steady_clock::now()); } bool SharedMutex::try_lock() { return try_lock_until(steady_clock::now()); } bool SharedMutex::try_lock_until(steady_clock::time_point time) { std::unique_lock<std::mutex> lock(mLock); // What thread wants to acquire this mutex? auto id = std::this_thread::get_id(); // Make sure this thread doesn't already hold a read lock. assert(!mReaders.count(id)); // This thread doesn't own the mutex. if (mWriterID != id) { // Wait for the mutex to become available. auto result = mWriterCV.wait_until(lock, time, [&]() { return !mCounter; }); // Couldn't acquire the mutex. if (!result) return false; // Remember that this thread owns the mutex. mWriterID = id; } // Mutex has been acquired. --mCounter; // Let our caller know the mutex was acquired. return true; } SharedMutex& SharedMutex::unique_to_shared() { // Convenience. [[maybe_unused]] auto id = std::this_thread::get_id(); // Make sure no one else is touching this lock. std::lock_guard<std::mutex> guard(mLock); // Make sure this thread currently holds the lock. assert(mWriterID == id); // Make sure this thread doesn't hold any recursive lock. assert(mCounter == -1); // Translate our writer to a reader. mCounter = 1; mWriterID = std::thread::id(); // Remember how many read locks this thread has. assert(++mReaders[id]); // Let any waiting readers know they can acquire the lock. mReaderCV.notify_all(); // Return a reference to ourselves to our caller. return *this; } void SharedMutex::unlock() { std::int64_t counter; { std::lock_guard<std::mutex> guard(mLock); // Make sure the lock is held. assert(mCounter < 0); // And that we own this mutex. assert(mWriterID == std::this_thread::get_id()); // Release the mutex. counter = ++mCounter; if (!counter) mWriterID = std::thread::id(); } // Mutex isn't available. if (counter < 0) return; // Notify waiting readers. mReaderCV.notify_all(); // Notify waiting writers. mWriterCV.notify_one(); } void SharedMutex::unlock_shared() { std::int64_t counter; { std::lock_guard<std::mutex> guard(mLock); // Make sure the lock is held. assert(mCounter > 0); // And that we own this mutex. auto id = std::this_thread::get_id(); // Make sure we know about this thread. assert(mReaders.count(id)); // Verify this thread actually owns this mutex. assert(mReaders[id]-- > 0); // Remove thread from our set of readers if necessary. assert((!mReaders[id] && mReaders.erase(id)) || true); // Release the mutex. counter = --mCounter; // Silence compiler. static_cast<void>(id); } // Mutex is held by one or more readers. if (counter > 0) return; // Mutex is available. mWriterCV.notify_one(); } } // common } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/subsystem_logger.cpp���������������������������������������������������������0000664�0000000�0000000�00000000735�15162662266�0020634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/subsystem_logger.h> #include <mega/log_level.h> namespace mega { namespace common { SubsystemLogger::SubsystemLogger(const char* subsystemName) : Logger(subsystemName) , mLogLevel(logInfo) { } void SubsystemLogger::logLevel(LogLevel level) { mLogLevel = level; } LogLevel SubsystemLogger::logLevel() const { return mLogLevel; } bool SubsystemLogger::masked(int severity) const { return mLogLevel < severity; } } // common } // mega �����������������������������������sdk-10.11.0/src/common/task_executor.cpp������������������������������������������������������������0000664�0000000�0000000�00000015132�15162662266�0020114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <chrono> #include <thread> #include <mega/common/logging.h> #include <mega/common/task_executor.h> #include <mega/common/utility.h> #include <mega/types.h> namespace mega { namespace common { class TaskExecutor::Worker { // Executes tasks when appropriate. void loop(); // Which executor hired us? TaskExecutor& mExecutor; // What logger should we use? Logger& mLogger; // Where are we in the executor's list of workers? WorkerList::iterator mPosition; // Where do we do our processing? std::thread mThread; public: Worker(TaskExecutor& executor, Logger& logger, WorkerList::iterator position); ~Worker(); }; // Worker TaskExecutor::TaskExecutor(const TaskExecutorFlags& flags, Logger& logger) : mAvailableWorkers(0u) , mCV() , mFlags(flags) , mLock() , mLogger(logger) , mTaskQueue() , mTerminating(false) , mWorkers() { LogDebug1(mLogger, "Executor constructed"); } TaskExecutor::~TaskExecutor() { // Acquire executor lock. std::unique_lock<std::mutex> lock(mLock); // Let the workers know that it's time to call it quits. mTerminating = true; // Wake up all the workers. mCV.notify_all(); // Wait for the workers to quit. while (!mWorkers.empty()) { // Select a worker to wait on. auto worker = std::move(mWorkers.front()); // Remove worker from the list. mWorkers.pop_front(); // Release the lock. lock.unlock(); // Wait for the worker to quit. worker.reset(); // Reacquire lock. lock.lock(); } LogDebug1(mLogger, "Executor destroyed"); } Task TaskExecutor::execute(std::function<void(const Task&)> function, std::chrono::steady_clock::time_point when, bool spawnWorker) { // Sanity. assert(function); // Instantiate a new task. auto task = Task(std::move(function), mLogger, when); // Acquire executor lock. std::unique_lock<std::mutex> lock(mLock); // Executor's being terminated. if (mTerminating) { // Release the lock. lock.unlock(); // Cancel the task. task.cancel(); // Return task to caller. return task; } // Only spawn a new worker if requested and if none are available. spawnWorker = spawnWorker && !mAvailableWorkers; // Always spawn a worker if there are none present. spawnWorker |= mWorkers.empty(); // But only spawn so many. spawnWorker &= mWorkers.size() < mFlags.mMaxWorkers; // Spawn a new worker if necessary. if (spawnWorker) { // Allocate a position for the worker. auto position = mWorkers.emplace(mWorkers.end(), nullptr); // Instantiate the worker. *position = std::make_unique<Worker>(*this, mLogger, position); // We now have at least one worker available. ++mAvailableWorkers; } // Sanity. assert(!mWorkers.empty()); // Queue the task for execution. mTaskQueue.queue(task); // Release executor lock. lock.unlock(); // Let a worker know there's something to do. mCV.notify_one(); // Return task to caller. return task; } void TaskExecutor::flags(const TaskExecutorFlags& flags) { std::lock_guard<std::mutex> guard(mLock); mFlags = flags; mCV.notify_all(); } TaskExecutorFlags TaskExecutor::flags() const { std::lock_guard<std::mutex> guard(mLock); return mFlags; } void TaskExecutor::Worker::loop() { // Acquire executor lock. std::unique_lock<std::mutex> lock(mExecutor.mLock); // Convenience. auto& availableWorkers = mExecutor.mAvailableWorkers; auto& cv = mExecutor.mCV; auto& flags = mExecutor.mFlags; auto& taskQueue = mExecutor.mTaskQueue; auto& terminating = mExecutor.mTerminating; auto& workers = mExecutor.mWorkers; // When should we wake up? auto nextWakeup = [&]() { if (!taskQueue.empty()) return taskQueue.when(); using std::chrono::steady_clock; return steady_clock::now() + flags.mIdleTime; }; // nextWakeup // Should we wake up? auto shouldWake = [&]() { return terminating || taskQueue.ready(); }; // shouldWake auto threadId = std::this_thread::get_id(); mExecutor.workerStarted(threadId); LogDebug1(mLogger, "Worker thread started"); // Execute queued tasks. while (true) { // Release excess workers. if (workers.size() > flags.mMaxWorkers) break; // Sleep until there's something to do. auto hasWork = cv.wait_until(lock, nextWakeup(), shouldWake); // We haven't had any work in awhile. if (!hasWork) { // Keep at least this many workers alive. if (flags.mMinWorkers >= workers.size()) continue; // Keep at least a single worker alive if there tasks pending. if (!taskQueue.empty() && workers.size() < 2) continue; // So we don't block on our own removal. mThread.detach(); // Leave a trail of what's going on. LogDebug1(mLogger, "Worker thread stopped"); // Let the executor know it has one less worker. --availableWorkers; workers.erase(mPosition); // We're all done. return; } // Executor's closing up shop. if (mExecutor.mTerminating) break; // Pop a task from the queue. auto task = taskQueue.dequeue(); // Sanity. assert(task); // Let the executor know we're busy. --availableWorkers; // Release the lock so other workers can proceed. lock.unlock(); // Complete the task. task.complete(); // Reacquire lock. lock.lock(); // Let the executor know we're available. ++availableWorkers; } // Let the executor know it has one less worker. --availableWorkers; mExecutor.workerStopped(threadId); LogDebug1(mLogger, "Worker thread stopped"); } TaskExecutor::Worker::Worker(TaskExecutor& executor, Logger& logger, WorkerList::iterator position) : mExecutor(executor) , mLogger(logger) , mPosition(position) , mThread(&Worker::loop, this) { LogDebug1(mLogger, "Worker constructed"); } TaskExecutor::Worker::~Worker() { if (mThread.joinable()) mThread.join(); LogDebug1(mLogger, "Worker destroyed"); } } // common } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/task_queue.cpp���������������������������������������������������������������0000664�0000000�0000000�00000014655�15162662266�0017413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <algorithm> #include <atomic> #include <cassert> #include <stdexcept> #include <mega/common/logging.h> #include <mega/common/task_queue.h> namespace mega { namespace common { class TaskContext { enum StatusFlag : unsigned int { // The task has been aborted. SF_ABORTED = 1, // The task can be cancelled. SF_CANCELLABLE = 2, // The task has been cancelled. SF_CANCELLED = 4, // The task has been executed. SF_COMPLETED = 8 }; // StatusFlag using StatusFlags = unsigned int; // Execute mFunction if the task has not already completed. bool execute(StatusFlags desired, const Task& task); // What will the task do when executed? std::function<void(const Task&)> mFunction; // How should this task log exceptions? Logger& mLogger; // What is the task's status? std::atomic<StatusFlags> mStatus; // When is the task to be executed? std::chrono::steady_clock::time_point mWhen; public: TaskContext(std::function<void(const Task&)> function, Logger& logger, std::chrono::steady_clock::time_point when); // Orders by ascending deadline. bool operator<(const TaskContext& rhs) const; // Try and abort the task. bool abort(const Task& task); // Try and cancel the task. bool cancel(const Task& task); // Has the task been aborted? bool aborted() const; // Has the task been cancelled? bool cancelled() const; // Try and complete the task. bool complete(const Task& task); // Has the task been completed? bool completed() const; // When is the task due to execution? std::chrono::steady_clock::time_point when() const; }; // TaskContext Task::Task(std::function<void(const Task&)> function, Logger& logger, std::chrono::steady_clock::time_point when) : mContext(std::make_shared<TaskContext>(std::move(function), logger, when)) { } Task::operator bool() const { return !!mContext; } bool Task::operator!() const { return !mContext; } bool Task::abort() { if (mContext) return mContext->abort(*this); return false; } bool Task::cancel() { if (mContext) return mContext->cancel(*this); return false; } bool Task::aborted() const { if (mContext) return mContext->aborted(); return false; } bool Task::cancelled() const { if (mContext) return mContext->cancelled(); return false; } bool Task::complete() { if (mContext) return mContext->complete(*this); return false; } bool Task::completed() const { if (mContext) return mContext->completed(); return false; } void Task::reset() { mContext.reset(); } bool TaskQueue::earlier(const Task& lhs, const Task& rhs) { return *lhs.mContext < *rhs.mContext; } TaskQueue::TaskQueue() : mTasks() { } TaskQueue::~TaskQueue() { // Cancel any outstanding tasks. while (!mTasks.empty()) { mTasks.back().abort(); mTasks.pop_back(); } } void TaskQueue::dequeue(std::deque<Task>& tasks, std::size_t count) { using std::swap; // Caller wants all tasks. if (count >= mTasks.size()) return swap(mTasks, tasks), mTasks.clear(); // Caller only wants so many tasks. while (count-- && !mTasks.empty()) { // Pop the next due task from the queue. std::pop_heap(mTasks.begin(), mTasks.end(), earlier); auto task = std::move(mTasks.back()); mTasks.pop_back(); // Transfer ownership of task to caller. tasks.emplace_back(std::move(task)); } } Task TaskQueue::dequeue() { // No tasks have been queued. if (mTasks.empty()) return Task(); // Pop the next due task from the queue. std::pop_heap(mTasks.begin(), mTasks.end(), earlier); auto task = std::move(mTasks.back()); mTasks.pop_back(); // Return task to caller. return task; } bool TaskQueue::empty() const { return mTasks.empty(); } Task TaskQueue::queue(Task task) { // Task doesn't reference anything. if (!task) return task; // Task's already been completed. if (task.completed()) return task; // Queue the task for execution. mTasks.emplace_back(task); // Order tasks by ascending due time. std::push_heap(mTasks.begin(), mTasks.end(), earlier); // Return task to caller. return task; } bool TaskQueue::ready() const { return std::chrono::steady_clock::now() >= when(); } std::chrono::steady_clock::time_point TaskQueue::when() const { if (mTasks.empty()) return std::chrono::steady_clock::time_point::max(); return mTasks.front().mContext->when(); } bool TaskContext::execute(StatusFlags desired, const Task& task) { StatusFlags expected = SF_CANCELLABLE; // Task's already been completed. if (!mStatus.compare_exchange_strong(expected, desired)) return false; // Safely execute the task. try { mFunction(task); } catch (std::exception& exception) { LogErrorF(mLogger, "Exception encountered executing task: %s", exception.what()); } // Release the closure. mFunction = nullptr; // Task's been executed. return true; } TaskContext::TaskContext(std::function<void(const Task&)> function, Logger& logger, std::chrono::steady_clock::time_point when): mFunction(std::move(function)), mLogger(logger), mStatus{SF_CANCELLABLE}, mWhen(when) {} bool TaskContext::operator<(const TaskContext& rhs) const { return mWhen > rhs.mWhen; } bool TaskContext::abort(const Task& task) { return execute(SF_ABORTED | SF_CANCELLED | SF_COMPLETED, task); } bool TaskContext::cancel(const Task& task) { return execute(SF_CANCELLED | SF_COMPLETED, task); } bool TaskContext::aborted() const { return (mStatus & SF_ABORTED); } bool TaskContext::cancelled() const { return (mStatus & SF_CANCELLED); } bool TaskContext::complete(const Task& task) { return execute(SF_COMPLETED, task); } bool TaskContext::completed() const { return (mStatus & SF_COMPLETED); } std::chrono::steady_clock::time_point TaskContext::when() const { return mWhen; } bool compare(const TaskContextPtr& lhs, const TaskContextPtr& rhs) { return *lhs < *rhs; } } // common } // mega �����������������������������������������������������������������������������������sdk-10.11.0/src/common/transaction.cpp��������������������������������������������������������������0000664�0000000�0000000�00000010021�15162662266�0017551�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <stdexcept> #include <utility> #include <sqlite3.h> #include <mega/common/badge.h> #include <mega/common/database.h> #include <mega/common/logging.h> #include <mega/common/query.h> #include <mega/common/scoped_query.h> #include <mega/common/transaction.h> namespace mega { namespace common { Transaction::Transaction() : mDB(nullptr) , mInProgress(false) { } Transaction::Transaction(Badge<Database>, Database& database) : mDB(&database) , mInProgress(false) { try { // Instantiate a query so we can manipulate the database. auto query = database.query(); // Try and start a new transaction. query = "savepoint txn"; query.execute(); // Transaction's now in progress. mInProgress = true; // Let debuggers know the transaction was started. LogDebug1(logger(), "Transaction started"); } catch (std::runtime_error& exception) { // Log the reason why we couldn't start a transaction. throw LogErrorF(logger(), "Unable to start transaction: %s", exception.what()); } } Transaction::Transaction(Transaction&& other): mDB(std::exchange(other.mDB, nullptr)), mInProgress(std::exchange(other.mInProgress, false)) {} Transaction::~Transaction() { try { // Try and roll back any transction in progress. if (mInProgress) rollback(); } catch (std::runtime_error&) { // Any exception would've been logged in rollback(). } } Transaction& Transaction::operator=(Transaction&& rhs) { Transaction temp(std::move(rhs)); swap(temp); return *this; } void Transaction::commit() try { // Sanity. assert(mDB); // Can't commit a transaction that hasn't been started. if (!mInProgress) throw LogError1(logger(), "Can't commit an inactive transaction"); // So we can manipulate the database. auto query = mDB->query(); // Try and release the transaction. query = "release savepoint txn"; query.execute(); // Transaction's been committed. mInProgress = false; // Let debuggers know the transaction was committed. LogDebug1(logger(), "Transaction committed"); } catch (std::runtime_error& exception) { // Let debuggers know why we couldn't commit the transaction. throw LogErrorF(logger(), "Unable to commit transaction: %s", exception.what()); } bool Transaction::inProgress() const { return mInProgress; } Logger& Transaction::logger() const { assert(mDB); return mDB->logger(); } Query Transaction::Transaction::query() { // Sanity. assert(mDB); // Queries must be guarded by a transaction. if (!mInProgress) throw LogError1(logger(), "Queries require an active transaction"); return mDB->query(); } ScopedQuery Transaction::query(Query& query) { // Sanity. assert(mDB); // Queries must be guarded by a transaction. if (!mInProgress) throw LogError1(logger(), "Queries require an active transaction"); return ScopedQuery({}, query); } void Transaction::rollback() try { // Sanity. assert(mDB); // Can't roll back a transaction that never began. if (!mInProgress) throw LogError1(logger(), "Can't rollback an inactive transaction"); auto query = mDB->query(); // Try and roll back the transaction. query = "rollback transaction to savepoint txn"; query.execute(); // Try and release the transaction. query = "release savepoint txn"; query.execute(); // Transaction's been rolled back. mInProgress = false; // Let debuggers know the transaction was rolled back. LogDebug1(logger(), "Transaction rolled back"); } catch (std::runtime_error& exception) { // Let debuggers know why we couldn't roll back the transaction. throw LogErrorF(logger(), "Unable to rollback transaction: %s", exception.what()); } void Transaction::swap(Transaction& other) { using std::swap; swap(mDB, other.mDB); swap(mInProgress, other.mInProgress); } } // common } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/common/upload.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000001674�15162662266�0016526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/upload.h> namespace mega { namespace common { void Upload::begin(BoundCallback callback) { // Sanity. assert(callback); // Called when the file's content has been uploaded. auto uploaded = [](BoundCallback& bound, ErrorOr<UploadResult> result) { // Couldn't upload the file's content. if (!result) return bound(unexpected(result.error())); // Extract bind callback. auto bind = std::move(*result); // Sanity. assert(bind); // Try and bind a name to our uploaded content. bind(std::move(bound), NodeHandle()); }; // uploaded UploadCallback wrapper = std::bind(std::move(uploaded), std::move(callback), std::placeholders::_1); // Try and upload the file's content. return begin(std::move(wrapper)); } } // common } // mega ��������������������������������������������������������������������sdk-10.11.0/src/common/utility.cpp������������������������������������������������������������������0000664�0000000�0000000�00000003134�15162662266�0016736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <mega/common/utility.h> namespace mega { namespace common { std::chrono::minutes defaultTimeout() { return std::chrono::minutes(5); } std::string format(const char* format, ...) { assert(format); std::va_list arguments; va_start(arguments, format); auto result = formatv(arguments, format); va_end(arguments); return result; } std::string formatv(std::va_list arguments, const char* format) { assert(format); std::va_list temp; va_copy(temp, arguments); auto required = std::vsnprintf(nullptr, 0, format, temp); va_end(temp); std::string buffer; buffer.resize(static_cast<std::size_t>(required) + 1); va_copy(temp, arguments); std::vsnprintf(&buffer[0], buffer.size(), format, temp); buffer.pop_back(); va_end(temp); return buffer; } std::optional<std::string> fromCharPointer(const char* maybeString) { if (maybeString) return std::string(maybeString); return std::nullopt; } std::int64_t now() { // Convenience. using std::chrono::system_clock; // Get our hands on the current time. auto now = system_clock::now(); // Return the current time to our caller as a time_t value. return system_clock::to_time_t(now); } const char* toCharPointer(const std::optional<std::string>& maybeString) { if (maybeString) return maybeString->data(); return nullptr; } } // common } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/crypto/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014556�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/crypto/cryptopp.cpp�����������������������������������������������������������������0000664�0000000�0000000�00000103334�15162662266�0017146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file cryptopp.cpp * @brief Crypto layer using Crypto++ * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" namespace mega { #ifndef htobe64 #define htobe64(x) (((uint64_t)htonl((uint32_t)((x) >> 32))) | (((uint64_t)htonl((uint32_t)x)) << 32)) #endif using namespace CryptoPP; // cryptographically strong random byte sequence void PrnGen::genblock(byte* buf, size_t len) { GenerateBlock(buf, len); } // random number from 0 ... max-1 uint32_t PrnGen::genuint32(uint64_t max) { uint32_t t; genblock((byte*)&t, sizeof t); return (uint32_t)(((uint64_t)t) / ((((uint64_t)(~(uint32_t)0)) + 1) / max)); } std::string PrnGen::genstring(const size_t len) { std::string result(len, '\0'); // Necessary as data() returns const until C++17. void *buffer = const_cast<char*>(result.data()); genblock(reinterpret_cast<byte*>(buffer), len); return result; } SymmCipher::SymmCipher(const byte* key) { setkey(key); } byte SymmCipher::zeroiv[BLOCKSIZE] = {}; void SymmCipher::setkey(const byte* newkey, int type) { memcpy(key, newkey, KEYLENGTH); if (!type) { xorblock(newkey + KEYLENGTH, key); } aesecb_e.SetKey(key, KEYLENGTH); aesecb_d.SetKey(key, KEYLENGTH); mAescbc_e.reset(); mAescbc_d.reset(); mAesccm8_e.reset(); mAesccm8_d.reset(); mAesccm16_e.reset(); mAesccm16_d.reset(); mAesgcm_e.reset(); mAesgcm_d.reset(); } bool SymmCipher::setkey(const string* newKey) { if (newKey->size() == FILENODEKEYLENGTH || newKey->size() == FOLDERNODEKEYLENGTH) { setkey((const byte*)newKey->data(), (newKey->size() == FOLDERNODEKEYLENGTH) ? FOLDERNODE : FILENODE); return true; } return false; } bool SymmCipher::cbc_encrypt_with_key(const std::string& plain, std::string& cipher, const byte* encryptionKey, const size_t keylen, const byte* iv) { try { auto& aescbc_e = prepareCipher(mAescbc_e, iv, BLOCKSIZE, encryptionKey, keylen); StringSource ss(plain, true, new StreamTransformationFilter(aescbc_e, new StringSink(cipher))); return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC encryption" << e.what(); return false; } } bool SymmCipher::cbc_decrypt_with_key(const std::string& cipher, std::string& plain, const byte* decryptionKey, const size_t keylen, const byte* iv) { try { auto& aescbc_d = prepareCipher(mAescbc_d, iv, BLOCKSIZE, decryptionKey, keylen); StringSource ss(cipher, true, new StreamTransformationFilter(aescbc_d, new StringSink(plain))); return true; } catch(const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC decryption" << e.what(); return false; } } bool SymmCipher::cbc_encrypt(byte* data, size_t len, const byte* iv) { try { auto& aescbc_e = prepareCipher(mAescbc_e, iv, BLOCKSIZE); aescbc_e.ProcessData(data, data, len); return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC encryption " << e.what(); return false; } } bool SymmCipher::cbc_decrypt(byte* data, size_t len, const byte* iv) { try { auto& aescbc_d = prepareCipher(mAescbc_d, iv, BLOCKSIZE); aescbc_d.ProcessData(data, data, len); return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC decryption " << e.what(); return false; } } bool SymmCipher::cbc_encrypt_pkcs_padding(const string *data, const byte *iv, string *result) { if (!data || !result) { assert(data && result); return false; } using Transformation = StreamTransformationFilter; try { // Update IV. auto& aescbc_e = prepareCipher(mAescbc_e, iv, BLOCKSIZE); // Create sink. unique_ptr<StringSink> sink = std::make_unique<StringSink>(*result); // Create transform. unique_ptr<Transformation> xfrm = std::make_unique<Transformation>(aescbc_e, sink.get(), Transformation::PKCS_PADDING); // Transform now owns sink. static_cast<void>(sink.release()); // Encrypt. StringSource ss(*data, true, xfrm.release()); return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC pkcs encryption " << e.what(); return false; } } bool SymmCipher::cbc_decrypt_pkcs_padding(const std::string* data, const byte* iv, string* result) { if (!data || !result) { assert(data && result); return false; } try { using Transformation = StreamTransformationFilter; // Update IV. auto& aescbc_d = prepareCipher(mAescbc_d, iv, BLOCKSIZE); // Create sink. unique_ptr<StringSink> sink = std::make_unique<StringSink>(*result); // Create transform. unique_ptr<Transformation> xfrm = std::make_unique<Transformation>(aescbc_d, sink.get(), Transformation::PKCS_PADDING); // Transform now owns sink. static_cast<void>(sink.release()); // Attempt decrypt. StringSource ss(*data, true, xfrm.release()); // Decrypt had correct padding. return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC pkcs decryption " << e.what(); return false; } } bool SymmCipher::cbc_decrypt_pkcs_padding(const byte* data, const size_t dataLength, const byte* iv, std::string* result) { if (!result) { assert(result); return false; } try { using Transformation = StreamTransformationFilter; // Update IV. auto& aescbc_d = prepareCipher(mAescbc_d, iv, BLOCKSIZE); // Create sink. unique_ptr<StringSink> sink = std::make_unique<StringSink>(*result); // Create transform. unique_ptr<Transformation> xfrm = std::make_unique<Transformation>(aescbc_d, sink.get(), Transformation::PKCS_PADDING); // Transform now owns sink. sink.release(); // Attempt decrypt. ArraySource(data, dataLength, true, xfrm.release()); // Decrypt had correct padding. return true; } catch (const CryptoPP::Exception& e) { LOG_err << "Failed AES-CBC pkcs decryption " << e.what(); return false; } } void SymmCipher::ecb_encrypt(byte* data, byte* dst, size_t len) { aesecb_e.ProcessData(dst ? dst : data, data, len); } void SymmCipher::ecb_decrypt(byte* data, size_t len) { aesecb_d.ProcessData(data, data, len); } bool SymmCipher::ccm_encrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result) { if (!data || !result) { assert(data && result); return false; } try { if (taglen == 16) { auto& aesccm16_e = prepareCipher(mAesccm16_e, iv, static_cast<size_t>(ivlen)); aesccm16_e.SpecifyDataLengths(0, data->size(), 0); StringSource ss(*data, true, new AuthenticatedEncryptionFilter(aesccm16_e, new StringSink(*result))); return true; } else if (taglen == 8) { auto& aesccm8_e = prepareCipher(mAesccm8_e, iv, static_cast<size_t>(ivlen)); aesccm8_e.SpecifyDataLengths(0, data->size(), 0); StringSource ss(*data, true, new AuthenticatedEncryptionFilter(aesccm8_e, new StringSink(*result))); return true; } } catch (CryptoPP::Exception const& e) { LOG_err << "Failed AES-CCM encryption: " << e.GetWhat(); } return false; } bool SymmCipher::ccm_decrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result) { if (!data || !result) { assert(data && result); return false; } try { if (taglen == 16) { auto& aesccm16_d = prepareCipher(mAesccm16_d, iv, static_cast<size_t>(ivlen)); aesccm16_d.SpecifyDataLengths(0, data->size() - taglen, 0); StringSource ss(*data, true, new AuthenticatedDecryptionFilter(aesccm16_d, new StringSink(*result))); return true; } else if (taglen == 8) { auto& aesccm8_d = prepareCipher(mAesccm8_d, iv, static_cast<size_t>(ivlen)); aesccm8_d.SpecifyDataLengths(0, data->size() - taglen, 0); StringSource ss(*data, true, new AuthenticatedDecryptionFilter(aesccm8_d, new StringSink(*result))); return true; } } catch (HashVerificationFilter::HashVerificationFailed const &e) { result->clear(); LOG_err << "Failed AES-CCM decryption: " << e.GetWhat(); } return false; } bool SymmCipher::gcm_encrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result) { if (!data || !result) { assert(data && result); return false; } try { auto& aesgcm_e = prepareCipher(mAesgcm_e, iv, static_cast<size_t>(ivlen)); StringSource ss(*data, true, new AuthenticatedEncryptionFilter(aesgcm_e, new StringSink(*result), false, static_cast<int>(taglen))); } catch (CryptoPP::Exception const &e) { LOG_err << "Failed AES-GCM encryption: " << e.GetWhat(); return false; } return true; } bool SymmCipher::gcm_encrypt_add(const byte* data, const size_t datasize, const byte* additionalData, const size_t additionalDatalen, const byte* iv, const size_t ivlen, const size_t taglen, std::string& result, const size_t expectedSize) { if (!additionalData || !additionalDatalen) { LOG_err << "Failed AES-GCM encryption with additional authenticated data. Invalid additional data"; return false; } return gcm_encrypt(data, datasize, nullptr /*key*/, 0 /*keylen*/, additionalData, additionalDatalen, iv, ivlen, taglen, result, expectedSize); } bool SymmCipher::gcm_encrypt(const byte* data, const size_t datasize, const byte* encryptionKey, const size_t keylen, const byte* additionalData, const size_t additionalDatalen, const byte* iv, const size_t ivlen, const size_t taglen, std::string& result, const size_t expectedSize) { std::string err; if (!data || !datasize) {err = "Invalid plain text";} if (!iv || !ivlen) {err = "Invalid IV";} if (!taglen) { err = "Invalid taglen"; } if (!err.empty()) { LOG_err << "Failed AES-GCM encryption with additional authenticated data: " << err; return false; } try { const auto* keyPtr = (encryptionKey && keylen) ? encryptionKey : nullptr; auto& aesgcm_e = prepareCipher(mAesgcm_e, iv, ivlen, keyPtr, keylen); AuthenticatedEncryptionFilter ef (aesgcm_e, new StringSink(result), false, static_cast<int>(taglen)); // add additionalData to channel for additional authenticated data if (additionalData && additionalDatalen) { ef.ChannelPut(AAD_CHANNEL, additionalData, additionalDatalen, true); } ef.ChannelMessageEnd(AAD_CHANNEL); // add plain text to DEFAULT_CHANNEL in order to be encrypted ef.ChannelPut(DEFAULT_CHANNEL, reinterpret_cast<const byte*>(data), datasize, true); ef.ChannelMessageEnd(DEFAULT_CHANNEL); if (expectedSize && expectedSize != result.size()) { LOG_err << "Failed AES-GCM encryption with additional authenticated data, invalid encrypted data size"; return false; } } catch (CryptoPP::Exception const &e) { LOG_err << "Failed AES-GCM encryption with additional authenticated data: " << e.GetWhat(); return false; } return true; } bool SymmCipher::gcm_decrypt(const string *data, const byte *iv, unsigned ivlen, unsigned taglen, string *result) { if (!data || !result) { assert(data && result); return false; } try { auto& aesgcm_d = prepareCipher(mAesgcm_d, iv, static_cast<size_t>(ivlen)); StringSource ss( *data, true, new AuthenticatedDecryptionFilter(aesgcm_d, new StringSink(*result), AuthenticatedDecryptionFilter::DEFAULT_FLAGS, static_cast<int>(taglen))); } catch (CryptoPP::Exception const& e) { result->clear(); LOG_err << "Failed AES-GCM decryption: " << e.GetWhat(); return false; } return true; } bool SymmCipher::gcm_decrypt(const byte* data, const size_t datalen, const byte* additionalData, const size_t additionalDatalen, const byte* decryptionKey, const size_t keylength, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize) { std::string err; if (!data || !datalen) {err = "Invalid data";} if (!tag || !taglen) {err = "Invalid tag";} if (!iv || !ivlen) {err = "Invalid IV";} if (!result || !resultSize) { err = "Invalid result"; } if (!err.empty()) { LOG_err << "Failed AES-GCM decryption with additional authenticated data: " << err; return false; } try { const auto* keyPtr = (decryptionKey && keylength) ? decryptionKey : nullptr; auto& aesgcm_d = prepareCipher(mAesgcm_d, iv, ivlen, keyPtr, keylength); unsigned int flags = AuthenticatedDecryptionFilter::MAC_AT_BEGIN | AuthenticatedDecryptionFilter::THROW_EXCEPTION; AuthenticatedDecryptionFilter df(aesgcm_d, nullptr, flags, static_cast<int>(taglen)); // add tag (GCM authentication tag) to DEFAULT_CHANNEL to check message hash or MAC df.ChannelPut(DEFAULT_CHANNEL, tag, taglen); if (additionalData && additionalDatalen) { // add additionalData to AAD_CHANNEL for additional authenticated data df.ChannelPut(AAD_CHANNEL, additionalData, additionalDatalen); } // add encrypted data to DEFAULT_CHANNEL in order to be decrypted df.ChannelPut(DEFAULT_CHANNEL, data, datalen); df.ChannelMessageEnd(AAD_CHANNEL); df.ChannelMessageEnd(DEFAULT_CHANNEL); // check data's integrity assert(df.GetLastResult()); if (!df.GetLastResult()) { LOG_err << "Failed AES-GCM decryption with additional authenticated data: integrity check failure"; return false; } // retrieve decrypted data from channel df.SetRetrievalChannel(DEFAULT_CHANNEL); uint64_t maxRetrievable = df.MaxRetrievable(); std::string retrieved; if (maxRetrievable <= 0 || maxRetrievable > resultSize) { LOG_err << "Failed AES-GCM decryption with additional authenticated data: output size mismatch"; return false; } df.Get((byte*)result, static_cast<size_t>(maxRetrievable)); } catch (CryptoPP::Exception const &e) { LOG_err << "Failed AES-GCM decryption with additional authenticated data: " << e.GetWhat(); return false; } return true; } bool SymmCipher::gcm_decrypt_add(const byte* data, const size_t datalen, const byte* additionalData, const size_t additionalDatalen, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize) { if (!additionalData || !additionalDatalen) { LOG_err << "Failed AES-GCM decryption with additional authenticated data. Invalid additional data"; return false; } return gcm_decrypt(data, datalen, additionalData, additionalDatalen, nullptr /*key*/, 0 /*keylength*/, tag, taglen, iv, ivlen, result, resultSize); } bool SymmCipher::gcm_decrypt_with_key(const byte* data, const size_t datalen, const byte* decryptionKey, const size_t keylength, const byte* tag, const size_t taglen, const byte* iv, const size_t ivlen, byte* result, const size_t resultSize) { if (!decryptionKey || !keylength) { LOG_err << "Failed AES-GCM decryption. Invalid decryption key"; return false; } return gcm_decrypt(data, datalen, nullptr /*additionalData*/, 0 /*additionalDatalen*/, decryptionKey, keylength, tag, taglen, iv, ivlen, result, resultSize); } void SymmCipher::serializekeyforjs(string *d) { unsigned char invertedkey[BLOCKSIZE]; std::stringstream ss; ss << "["; for (int i=0; i<BLOCKSIZE; i++) { invertedkey[i] = key[BLOCKSIZE - i - 1]; } int32_t *k = (int32_t *)invertedkey; for (int i = 3; i >= 0; i--) { ss << k[i]; if (i) { ss << ","; } } ss << "]"; *d = ss.str(); } void SymmCipher::setint64(int64_t value, byte* data) { #if __BYTE_ORDER == __LITTLE_ENDIAN value = static_cast<int64_t>(htobe64(value)); #else #if __BYTE_ORDER != __BIG_ENDIAN #error "Unknown or unsupported endianness" #endif #endif memcpy(data, (char*)&value, sizeof value); } void SymmCipher::xorblock(const byte* src, byte* dst) { if ((reinterpret_cast<uintptr_t>(src) & (sizeof(long) - 1)) == 0 && (reinterpret_cast<uintptr_t>(dst) & (sizeof(long) - 1)) == 0) { // src and dst aligned to machine word long* lsrc = (long*)src; long* ldst = (long*)dst; for (int i = BLOCKSIZE / sizeof(long); i--;) { ldst[i] ^= lsrc[i]; } } else { xorblock(src, dst, BLOCKSIZE); } } void SymmCipher::xorblock(const byte* src, byte* dst, int len) { while (len--) { dst[len] ^= src[len]; } } void SymmCipher::incblock(byte* dst, unsigned len) { while (len) { if (++dst[--len]) { break; } } } bool SymmCipher::isZeroKey(const byte* key, size_t keySize) { if (!key) { // Invalid key pointer, consider it non-zero LOG_warn << "[SymmCipher::isZeroKey] invalid key pointer"; assert(false && "[SymmCipher::isZeroKey] invalid key pointer"); return false; } if (keySize == FILENODEKEYLENGTH) // 32 (filekey, nodekey, etc) { static_assert(FILENODEKEYLENGTH == SymmCipher::BLOCKSIZE * 2); // Check if the lower 16 bytes (0-15) are equal to the higher 16 bytes (16-31) // This will be true either if the key is all zeros or it was generated with a 16-byte zero key (for example, the transferkey was a zerokey). return std::memcmp(key /*key[0-15]*/, key + SymmCipher::BLOCKSIZE /*key[16-31]*/, SymmCipher::BLOCKSIZE) == 0; } else if (keySize == SymmCipher::BLOCKSIZE) // 16 (transfer key, client master key, etc) { // Check if all bytes are zero (zerokey) static const byte zeroKey[SymmCipher::BLOCKSIZE] = {}; return std::memcmp(key, zeroKey, SymmCipher::BLOCKSIZE) == 0; } // Invalid key size, consider it non-zero LOG_warn << "[SymmCipher::isZeroKey] used a keySize(" << keySize << ") different from 32 and 16 -> function will return false"; assert(false && "SymmCipher::isZeroKey used a keySize different from 32 and 16"); return false; } SymmCipher::SymmCipher(const SymmCipher &ref) { setkey(ref.key); } SymmCipher& SymmCipher::operator=(const SymmCipher& ref) { setkey(ref.key); return *this; } // encryption: data must be NUL-padded to BLOCKSIZE // decryption: data must be padded to BLOCKSIZE // len must be < 2^31 void SymmCipher::ctr_crypt(byte* data, unsigned len, m_off_t pos, ctr_iv ctriv, byte* mac, bool encrypt, bool initmac) { assert(!(pos & (KEYLENGTH - 1))); byte ctr[BLOCKSIZE], tmp[BLOCKSIZE]; MemAccess::set<int64_t>(ctr, static_cast<int64_t>(ctriv)); setint64(pos / BLOCKSIZE, ctr + sizeof ctriv); if (mac && initmac) { memcpy(mac, ctr, sizeof ctriv); memcpy(mac + sizeof ctriv, ctr, sizeof ctriv); } while ((int)len > 0) { if (encrypt) { if(mac) { xorblock(data, mac); ecb_encrypt(mac); } ecb_encrypt(ctr, tmp); xorblock(tmp, data); } else { ecb_encrypt(ctr, tmp); xorblock(tmp, data); if (mac) { if (len >= (unsigned)BLOCKSIZE) { xorblock(data, mac); } else { xorblock(data, mac, static_cast<int>(len)); } ecb_encrypt(mac); } } len -= BLOCKSIZE; data += BLOCKSIZE; incblock(ctr); } } static void rsaencrypt(const Integer* key, Integer* m) { *m = a_exp_b_mod_c(*m, key[AsymmCipher::PUB_E], key[AsymmCipher::PUB_PQ]); } unsigned AsymmCipher::rawencrypt(const byte* plain, size_t plainlen, byte* buf, size_t buflen) const { Integer t(plain, plainlen); rsaencrypt(key, &t); unsigned i = t.ByteCount(); if (i > buflen) { return 0; } while (i--) { *buf++ = t.GetByte(i); } return t.ByteCount(); } int AsymmCipher::encrypt(PrnGen &rng, const byte* plain, size_t plainlen, byte* buf, size_t buflen) const { if (key[PUB_PQ].ByteCount() + 2 > buflen) { return 0; } if (buf != plain) { memcpy(buf, plain, plainlen); } // add random padding rng.genblock(buf + plainlen, key[PUB_PQ].ByteCount() - plainlen - 2); Integer t(buf, key[PUB_PQ].ByteCount() - 2); rsaencrypt(key, &t); unsigned int i = t.BitCount(); byte* ptr = buf; *ptr++ = (byte)(i >> 8); *ptr++ = (byte)i; i = t.ByteCount(); while (i--) { *ptr++ = t.GetByte(i); } return int(ptr - buf); } static void rsadecrypt(const Integer* key, Integer* m) { Integer xp = a_exp_b_mod_c(*m % key[AsymmCipher::PRIV_P], key[AsymmCipher::PRIV_D] % (key[AsymmCipher::PRIV_P] - Integer::One()), key[AsymmCipher::PRIV_P]); Integer xq = a_exp_b_mod_c(*m % key[AsymmCipher::PRIV_Q], key[AsymmCipher::PRIV_D] % (key[AsymmCipher::PRIV_Q] - Integer::One()), key[AsymmCipher::PRIV_Q]); if (xp > xq) { *m = key[AsymmCipher::PRIV_Q] - (((xp - xq) * key[AsymmCipher::PRIV_U]) % key[AsymmCipher::PRIV_Q]); } else { *m = ((xq - xp) * key[AsymmCipher::PRIV_U]) % key[AsymmCipher::PRIV_Q]; } *m = *m * key[AsymmCipher::PRIV_P] + xp; } unsigned AsymmCipher::rawdecrypt(const byte* cipher, size_t cipherlen, byte* buf, size_t buflen) const { Integer m(cipher, cipherlen); rsadecrypt(key, &m); unsigned i = m.ByteCount(); if (i > buflen) { return 0; } while (i--) { *buf++ = m.GetByte(i); } return m.ByteCount(); } int AsymmCipher::decrypt(const byte* cipher, size_t cipherlen, byte* out, size_t numbytes) const { Integer m; if (!decodeintarray(&m, 1, cipher, int(cipherlen))) { return 0; } rsadecrypt(key, &m); size_t l = key[AsymmCipher::PRIV_P].ByteCount() + key[AsymmCipher::PRIV_Q].ByteCount() - 2; if (m.ByteCount() > l) { l = m.ByteCount(); } l -= numbytes; while (numbytes--) { out[numbytes] = m.GetByte(l++); } return 1; } const Integer& AsymmCipher::getKey(unsigned component) const { assert(component < PRIVKEY); return key[component]; } auto AsymmCipher::getKey() const -> const Key& { return key; } int AsymmCipher::setkey(int numints, const byte* data, int len) { // Assume key material is invalid. padding = 0; status = S_UNKNOWN; // Deserialize key material. auto result = decodeintarray(key, numints, data, len); if (!result) return result; // We've been provided a private key. if (numints == PRIVKEY || numints == PRIVKEY_SHORT) return isvalid(numints) ? result : 0; // Convenience. auto e = key[PUB_E].ByteCount(); auto pq = key[PUB_PQ].ByteCount(); // Compute number of padding bytes. padding = static_cast<unsigned>(len) - pq - e - 4; // Return result to caller. return result; } void AsymmCipher::resetkey() { std::fill(std::begin(key), std::end(key), Integer::Zero()); padding = 0; status = S_INVALID; } void AsymmCipher::serializekeyforjs(string& d) const { unsigned sizePQ = key[PUB_PQ].ByteCount(); unsigned sizeE = key[PUB_E].ByteCount(); char c; d.clear(); d.reserve(sizePQ + sizeE + padding); for (unsigned int j = key[PUB_PQ].ByteCount(); j--;) { c = static_cast<char>(key[PUB_PQ].GetByte(j)); d.append(&c, sizeof c); } // accounts created by webclient use 4 bytes for serialization of exponent // --> add left-padding up to 4 bytes for compatibility reasons c = 0; for (unsigned j = 0; j < padding; j++) { d.append(&c, sizeof c); } for (unsigned int j = sizeE; j--;) { c = static_cast<char>(key[PUB_E].GetByte(j)); // returns 0 if out-of-range d.append(&c, sizeof c); } } void AsymmCipher::serializekey(string* d, int keytype) const { serializeintarray(key, keytype, d); } void AsymmCipher::serializeintarray(const Integer* t, int numints, string* d, bool headers) { unsigned size = 0; unsigned char c; for (int i = numints; i--;) { size += t[i].ByteCount(); if (headers) { size += 2; } } d->reserve(d->size() + size); for (int i = 0; i < numints; i++) { if (headers) { unsigned int bitCount = t[i].ByteCount() * 8; c = static_cast<unsigned char>((bitCount & 0x0000FF00) >> 8); d->append((char*)(&c), sizeof c); c = bitCount & 0x000000FF; d->append((char*)&c, sizeof c); } for (unsigned j = t[i].ByteCount(); j--;) { c = t[i].GetByte(j); d->append((char*)&c, sizeof c); } } } int AsymmCipher::decodeintarray(Integer* t, int numints, const byte* data, int len) { int p, i, n; p = 0; for (i = 0; i < numints; i++) { if (p + 2 > len) { break; } n = ((data[p] << 8) + data[p + 1] + 7) >> 3; p += 2; if (p + n > len) { break; } t[i] = Integer(data + p, static_cast<size_t>(n)); p += n; } // If u is not present, calculate it. if (numints == PRIVKEY_SHORT) { t[PRIV_U] = t[PRIV_P].InverseMod(t[PRIV_Q]); } return i == numints && len - p < 16; } bool AsymmCipher::isvalid(int type) const { if (status == S_UNKNOWN) status = isvalid(key, type); return status == S_VALID; } auto AsymmCipher::isvalid(const Key& keyToConfirm, int type) const -> Status { assert(type >= PUBKEY && type <= PRIVKEY); if (type == PUBKEY) { if (keyToConfirm[PUB_E].BitCount() && keyToConfirm[PUB_PQ].BitCount()) return S_VALID; return S_INVALID; } // Convenience. auto& d = keyToConfirm[PRIV_D]; auto& p = keyToConfirm[PRIV_P]; auto& u = keyToConfirm[PRIV_U]; auto& q = keyToConfirm[PRIV_Q]; // detect private key blob corruption. // prevent API-exploitable RSA oracle requiring 500+ logins if (d.BitCount() <= 2000 || p.BitCount() <= 1000 || q.BitCount() <= 1000 || u.BitCount() <= 1000 || u != p.InverseMod(q)) return S_INVALID; return S_VALID; } // adapted from CryptoPP, rsa.cpp class RSAPrimeSelector : public PrimeSelector { Integer m_e; public: RSAPrimeSelector(const Integer &e) : m_e(e) { } bool IsAcceptable(const Integer &candidate) const { return RelativelyPrime(m_e, candidate - Integer::One()); } }; // generate RSA keypair void AsymmCipher::genkeypair(PrnGen &rng, Integer* privk, Integer* pubk, int size) { pubk[PUB_E] = 17; RSAPrimeSelector selector(pubk[PUB_E]); AlgorithmParameters primeParam = MakeParametersForTwoPrimesOfEqualSize( static_cast<unsigned>(size))(Name::PointerToPrimeSelector(), selector.GetSelectorPointer()); privk[PRIV_P].GenerateRandom(rng, primeParam); privk[PRIV_Q].GenerateRandom(rng, primeParam); privk[PRIV_D] = pubk[PUB_E].InverseMod(LCM(privk[PRIV_P] - Integer::One(), privk[PRIV_Q] - Integer::One())); pubk[PUB_PQ] = privk[PRIV_P] * privk[PRIV_Q]; privk[PRIV_U] = privk[PRIV_P].InverseMod(privk[PRIV_Q]); } void AsymmCipher::genkeypair(PrnGen &rng, Integer* pubk, int size) { assert(pubk); genkeypair(rng, key, pubk, size); // Consider the keys we generate as valid. status = S_VALID; } void Hash::add(const byte* data, unsigned len) { hash.Update(data, len); } void Hash::get(string* out) { out->resize(hash.DigestSize()); hash.Final((byte*)out->data()); } void HashSHA256::add(const byte *data, unsigned int len) { hash.Update(data, len); } void HashSHA256::get(std::string *retStr) { retStr->resize(hash.DigestSize()); hash.Final((byte*)retStr->data()); } void HashCRC32::add(const byte* data, unsigned len) { hash.Update(data, len); } void HashCRC32::get(byte* out) { hash.Final(out); } HMACSHA256::HMACSHA256(const byte *key, size_t length) : hmac(key, length) { } HMACSHA256::HMACSHA256() { } void HMACSHA256::add(const byte *data, size_t len) { hmac.Update(data, len); } void HMACSHA256::get(byte *out) { hmac.Final(out); } void HMACSHA256::setkey(const byte* key, const size_t length) { assert(key || length == 0); hmac.SetKey(key, length); } PBKDF2_HMAC_SHA512::PBKDF2_HMAC_SHA512() { } bool PBKDF2_HMAC_SHA512::deriveKey(byte* derivedkey, const size_t derivedkeyLen, const byte* pwd, const size_t pwdLen, const byte* salt, const size_t saltLen, const unsigned int iterations) const { assert(derivedkey); assert(derivedkeyLen > 0); assert(pwd); assert(pwdLen > 0); assert(salt); assert(saltLen > 0); assert(iterations > 0); try { pbkdf2.DeriveKey( // buffer that holds the derived key derivedkey, derivedkeyLen, // purpose byte. unused by this PBKDF implementation. 0x00, // password bytes. careful to be consistent with encoding... pwd, pwdLen, // salt bytes salt, saltLen, // iteration count. See SP 800-132 for details. You want this as large as you can tolerate. // make sure to use the same iteration count on both sides... iterations ); return true; } catch (const CryptoPP::Exception& e) { // DeriveKey() should throw CryptoPP::InvalidDerivedLength, however that is not present in all // versions of the lib, i.e. Linux system lib LOG_err << "PKCS5_PBKDF2_HMAC<T>::DeriveKey() exception: " << e.what(); return false; } } } // namespace ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/crypto/sodium.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000015474�15162662266�0016575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sodium.cpp * @brief Crypto layer using libsodium. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include <cryptopp/hkdf.h> namespace mega { const std::string EdDSA::TLV_KEY = "prEd255"; EdDSA::EdDSA(PrnGen &rng, unsigned char *keySeed) { if (sodium_init() == -1) { LOG_err << "Cannot initialize sodium library."; return; } if (keySeed) // then use the value { memcpy(this->keySeed, keySeed, EdDSA::SEED_KEY_LENGTH); } else // make the new key seed. { rng.genblock(this->keySeed, EdDSA::SEED_KEY_LENGTH); } // derive public and private keys from the seed if (crypto_sign_seed_keypair(this->pubKey, this->privKey, this->keySeed) != 0) { LOG_err << "Error generating an Ed25519 key pair."; return; } initializationOK = true; } EdDSA::~EdDSA() { } // Computes the signature of a message. int EdDSA::sign(const unsigned char* msg, const unsigned long long msglen, unsigned char* sig) { if (!sig || !msg) { return 0; } int result = crypto_sign_detached(sig, NULL, msg, msglen, (const unsigned char*)privKey); return int( (result == 0) ? (crypto_sign_BYTES + msglen) : 0 ); } // Verifies the signature of a message. int EdDSA::verify(const unsigned char* msg, unsigned long long msglen, const unsigned char* sig, const unsigned char* pubKey) { if (!sig || !msg) { return 0; } return !crypto_sign_verify_detached(sig, msg, msglen, pubKey); } void EdDSA::signKey(const unsigned char *key, const unsigned long long keyLength, string *result, uint64_t ts) { if (!ts) { ts = (uint64_t) m_time(); } string tsstr; unsigned char digit; for (int i = 0; i < 8; i++) { digit = ts & 0xFF; tsstr.insert(0, 1, static_cast<char>(digit)); ts >>= 8; } string keyString = "keyauth"; keyString.append(tsstr); keyString.append((char*)key, size_t(keyLength)); byte sigBuf[crypto_sign_BYTES]; sign((unsigned char *)keyString.data(), keyString.size(), sigBuf); result->resize(crypto_sign_BYTES + sizeof(ts)); // 8 --> timestamp prefix result->assign(tsstr.data(), sizeof(ts)); result->append((const char*)sigBuf, crypto_sign_BYTES); } bool EdDSA::verifyKey(const unsigned char *pubk, const unsigned long long pubkLen, const string *sig, const unsigned char* signingPubKey) { if (sig->size() < 72) { return false; } uint64_t ts; memcpy(&ts, sig->substr(0, 8).data(), sizeof(ts)); string message = "keyauth"; message.append(sig->data(), 8); message.append((char*)pubk, size_t(pubkLen)); string signature = sig->substr(8); return verify((unsigned char*)message.data(), message.length(), (unsigned char*)signature.data(), signingPubKey) != 0; } const std::string ECDH::TLV_KEY= "prCu255"; ECDH::ECDH() { if (sodium_init() == -1) { LOG_err << "Cannot initialize sodium library."; return; } // create a new key pair crypto_box_keypair(mPubKey, mPrivKey); initializationOK = true; } ECDH::ECDH (const ECDH& aux) { std::copy(aux.getPrivKey(), aux.getPrivKey() + ECDH::PRIVATE_KEY_LENGTH, mPrivKey); std::copy(aux.getPubKey(), aux.getPubKey() + ECDH::PUBLIC_KEY_LENGTH, mPubKey); } ECDH::ECDH(const string& privKey) { if (sodium_init() == -1) { LOG_err << "Cannot initialize sodium library."; return; } if (privKey.size() != PRIVATE_KEY_LENGTH) { LOG_err << "Invalid size of private Cu25519 key"; return; } memcpy(mPrivKey, privKey.data(), PRIVATE_KEY_LENGTH); // derive public key from privKey crypto_scalarmult_base(mPubKey, mPrivKey); initializationOK = true; } ECDH::~ECDH() { } ECDH::ECDH(const unsigned char *privk, const std::string &pubk) { assert(privk); if (!privk) return; std::copy(privk, privk + PRIVATE_KEY_LENGTH, mPrivKey); std::copy(pubk.data(), pubk.data() + PUBLIC_KEY_LENGTH, mPubKey); } int ECDH::doComputeSymmetricKey(const unsigned char* privk, const unsigned char* pubk, std::string& output) const { assert(privk && pubk); if (!privk || !pubk) return -1; // return some non-0 value output.resize(DERIVED_KEY_LENGTH); unsigned char* outputPtr = reinterpret_cast<unsigned char*>(const_cast<char*>(output.data())); int ret = crypto_scalarmult(outputPtr, privk, pubk); // 0: success return ret; } int ECDH::encrypt(unsigned char *encmsg, const unsigned char *msg, const unsigned long long msglen, const unsigned char *nonce, const unsigned char *pubKey, const unsigned char *privKey) { return !crypto_box(encmsg, msg, msglen, nonce, pubKey, privKey); } int ECDH::decrypt(unsigned char *msg, const unsigned char *encmsg, const unsigned long long encmsglen, const unsigned char *nonce, const unsigned char *pubKey, const unsigned char *privKey) { return !crypto_box_open(msg, encmsg, encmsglen, nonce, pubKey, privKey); } bool ECDH::deriveSharedKeyWithSalt(const unsigned char* pubkey, const unsigned char* salt, size_t saltSize, std::string& output) const { if (!pubkey || !salt || ! saltSize) { LOG_err << "derivePrivKeyWithSalt: eargs check input params"; return false; } if (!getPrivKey()) { LOG_err << "derivePrivKeyWithSalt: invalid private key"; return false; } std::string sharedSecret; int err = doComputeSymmetricKey(mPrivKey, pubkey, sharedSecret); if (err) { LOG_err << "derivePrivKeyWithSalt: crypto_scalarmult err: " << err; return false; } try { output.resize(::mega::ECDH::DERIVED_KEY_LENGTH); auto outPtr = reinterpret_cast<const unsigned char *>(output.data()); CryptoPP::HKDF<CryptoPP::SHA256> hkdf; hkdf.DeriveKey(const_cast<unsigned char*>(outPtr), output.size(), reinterpret_cast<const unsigned char*>(sharedSecret.data()), sharedSecret.size(), salt, saltSize, nullptr, 0); return true; } catch (std::invalid_argument const& e) { LOG_err << "derivePrivKeyWithSalt: Invalid argument err: " << e.what(); return false; } } } // namespace ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/db.cpp������������������������������������������������������������������������������0000664�0000000�0000000�00000010036�15162662266�0014327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file db.cpp * @brief Database access interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/db.h" #include "mega/utils.h" #include "mega/logging.h" namespace mega { DbTable::DbTable(PrnGen &rng, bool checkAlwaysTransacted, DBErrorCallback dBErrorCallBack) : rng(rng), mCheckAlwaysTransacted(checkAlwaysTransacted) , mDBErrorCallBack(std::move(dBErrorCallBack)) { nextid = 0; } // add or update record from string bool DbTable::put(uint32_t index, string* data) { return put(index, (char*)data->data(), unsigned(data->size())); } // add or update record with padding and encryption bool DbTable::put(uint32_t type, Cacheable* record, SymmCipher* key) { string data; if (!record->dbid) { uint32_t previousNextid = nextid; record->dbid = (nextid += IDSPACING) | type; if (nextid < previousNextid) { LOG_err << "Overflow at nextid " << type; if (mDBErrorCallBack) { mDBErrorCallBack(DBError::DB_ERROR_INDEX_OVERFLOW); } assert(nextid >= previousNextid); } } if (!record->serialize(&data)) { // Don't return false if there are errors in the serialization // to let the SDK continue and save the rest of records LOG_warn << "Serialization failed: " << type; return true; } if (!PaddedCBC::encrypt(rng, &data, key)) { LOG_err << "Failed to CBC encrypt data"; // continue with unencrypted data intentionally } return put(record->dbid, &data); } // get next record, decrypt and unpad bool DbTable::next(uint32_t* type, string* data, SymmCipher* key) { if (next(type, data)) { if (!*type) { return true; } if (*type > nextid) { nextid = *type & ~(static_cast<unsigned>(IDSPACING) - 1); } return PaddedCBC::decrypt(data, key); } return false; } DBTableTransactionCommitter *DbTable::getTransactionCommitter() const { return mTransactionCommitter; } void DbTable::checkTransaction() { if (mCheckAlwaysTransacted) { assert(mTransactionCommitter); // if this fails, we should have started a DBTableTransactionCommitter higher in the call stack if (mTransactionCommitter) { mTransactionCommitter->beginOnce(); } } } void DbTable::resetCommitter() { if (mTransactionCommitter) { mTransactionCommitter->reset(); mCheckAlwaysTransacted = false; mTransactionCommitter = nullptr; } } void DbTable::checkCommitter(DBTableTransactionCommitter*) { // This is to alert us if we haven't put any committer on the stack because // then we are probably taking much longer than needed to make db changes. // Nested committers are allowed; the outermost one will actually commmit. // Committer function parameters are there to remind us to put one on the stack // but are not actually needed - if there is only one on the stack then // the incoming committer == mTransactionCommitter (unless we are being called via a destructor) assert(mTransactionCommitter); } const int DbAccess::LEGACY_DB_VERSION = 14; const int DbAccess::DB_VERSION = DbAccess::LEGACY_DB_VERSION + 1; const int DbAccess::LAST_DB_VERSION_WITHOUT_NOD = 12; const int DbAccess::LAST_DB_VERSION_WITHOUT_SRW = 13; const int DbAccess::LAST_DB_VERSION_WITHOUT_VFINGERPRINT = 14; DbAccess::DbAccess() { currentDbVersion = LEGACY_DB_VERSION; } } // namespace ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/db/���������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0013623�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/db/sqlite.cpp�����������������������������������������������������������������������0000664�0000000�0000000�00000312161�15162662266�0015634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sqlite.cpp * @brief SQLite DB access layer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include <limits> #include <numeric> #ifdef USE_SQLITE namespace mega { static const char* NodeSearchFilterPtrStr = "NodeSearchFilterPtrStr"; SqliteDbAccess::SqliteDbAccess(const LocalPath& rootPath) : mRootPath(rootPath) { LOG_debug << "sqlite version: " << sqlite3_libversion(); assert(mRootPath.isAbsolute()); } SqliteDbAccess::~SqliteDbAccess() { } LocalPath SqliteDbAccess::databasePath(const FileSystemAccess&, const string& name, const int version) const { ostringstream osstream; osstream << "megaclient_statecache" << version << "_" << name << ".db"; LocalPath path = mRootPath; path.appendWithSeparator( LocalPath::fromRelativePath(osstream.str()), false); return path; } bool SqliteDbAccess::checkDbFileAndAdjustLegacy(FileSystemAccess& fsAccess, const string& name, const int flags, LocalPath& dbPath) { dbPath = databasePath(fsAccess, name, DB_VERSION); auto upgraded = true; { auto legacyPath = databasePath(fsAccess, name, LEGACY_DB_VERSION); auto fileAccess = fsAccess.newfileaccess(); if (fileAccess->fopen(legacyPath, FSLogging::logExceptFileNotFound)) { LOG_debug << "Found legacy database at: " << legacyPath; // if current version is legacy, use that one... unless migration to NoD, DB with // virtual fingerprint column in Nodes table, or renaming to adapt the version to SRW // are required if (currentDbVersion == LEGACY_DB_VERSION && LEGACY_DB_VERSION != LAST_DB_VERSION_WITHOUT_NOD && LEGACY_DB_VERSION != LAST_DB_VERSION_WITHOUT_SRW && LEGACY_DB_VERSION != LAST_DB_VERSION_WITHOUT_VFINGERPRINT) { LOG_debug << "Using a legacy database."; dbPath = std::move(legacyPath); upgraded = false; } else if ((flags & DB_OPEN_FLAG_RECYCLE)) { LOG_debug << "Trying to recycle a legacy database."; // if DB_VERSION already exist, let's get rid of it first // (it could happen if downgrade is executed and come back to newer version) removeDBFiles(fsAccess, dbPath); if (renameDBFiles(fsAccess, legacyPath, dbPath)) { LOG_debug << "Legacy database recycled."; } else { LOG_err << "Unable to recycle database, deleting..."; assert(false); removeDBFiles(fsAccess, legacyPath); } } else { LOG_debug << "Deleting outdated legacy database."; removeDBFiles(fsAccess, legacyPath); } } } if (upgraded) { LOG_debug << "Using an upgraded DB: " << dbPath; currentDbVersion = DB_VERSION; } return fsAccess.fileExistsAt(dbPath); } SqliteDbTable *SqliteDbAccess::open(PrnGen &rng, FileSystemAccess &fsAccess, const string &name, const int flags, DBErrorCallback dBErrorCallBack) { sqlite3 *db = nullptr; auto dbPath = databasePath(fsAccess, name, DB_VERSION); if (!openDBAndCreateStatecache(&db, fsAccess, name, dbPath, flags)) { return nullptr; } return new SqliteDbTable(rng, db, fsAccess, dbPath, (flags & DB_OPEN_FLAG_TRANSACTED) > 0, std::move(dBErrorCallBack)); } // An adapter around naturalsorting_compare static int sqlite_naturalsorting_compare(void*, int size1, const void* data1, int size2, const void* data2) { return naturalsorting_compare(static_cast<const char*>(data1), size1, static_cast<const char*>(data2), size2); } DbTable *SqliteDbAccess::openTableWithNodes(PrnGen &rng, FileSystemAccess &fsAccess, const string &name, const int flags, DBErrorCallback dBErrorCallBack) { /** * Deprecated columns (WARNING: do not use these names anymore for new columns): * - size: file/folder size in Bytes (replaced by sizeVirtual, calculated from nodeCounter) * - mimetype: node mimetype (replaced by mimetypeVirtual, calculated from node name) */ sqlite3 *db = nullptr; auto dbPath = databasePath(fsAccess, name, DB_VERSION); if (!openDBAndCreateStatecache(&db, fsAccess, name, dbPath, flags)) { return nullptr; } if (sqlite3_create_function(db, u8"getmimetype", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0, &SqliteAccountState::userGetMimetype, 0, 0) != SQLITE_OK) { LOG_err << "Data base error(sqlite3_create_function userGetMimetype): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } if (sqlite3_create_function(db, u8"getFingerprintExcludingMtime", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0, &SqliteAccountState::getFingerprintExcludingMtime, 0, 0) != SQLITE_OK) { LOG_err << "Data base error(sqlite3_create_function getFingerprintExcludingMtime): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } if (sqlite3_create_function(db, u8"getSizeFromNodeCounter", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0, &SqliteAccountState::getSizeFromNodeCounter, 0, 0) != SQLITE_OK) { LOG_err << "Data base error(sqlite3_create_function getSizeFromNodeCounter): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } if (sqlite3_create_collation(db, "NATURALNOCASE", SQLITE_UTF8, nullptr, sqlite_naturalsorting_compare)) { LOG_err << "Data base error(sqlite3_create_collation NATURALNOCASE): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } // Create specific table for handle nodes std::string sql = "CREATE TABLE IF NOT EXISTS nodes (nodehandle int64 PRIMARY KEY NOT NULL, " "parenthandle int64, name text, fingerprint BLOB, origFingerprint BLOB, " "type tinyint, mimetypeVirtual tinyint AS (getmimetype(name)) VIRTUAL, " "fingerprintVirtual BLOB AS (getFingerprintExcludingMtime(fingerprint)) VIRTUAL, " "sizeVirtual int64 AS (getSizeFromNodeCounter(counter)) VIRTUAL," "share tinyint, fav tinyint, ctime int64, mtime int64 DEFAULT 0, " "flags int64, counter BLOB NOT NULL, " "node BLOB NOT NULL, label tinyint DEFAULT 0, description text, tags text)"; int result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error: " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } // Add following columns to existing 'nodes' table that might not have them, and populate them // if needed: vector<NewColumn> newCols{ {"mtime", "int64 DEFAULT 0", NodeData::COMPONENT_MTIME, NewColumn::extractDataFromNodeData<MTimeType>}, {"label", "tinyint DEFAULT 0", NodeData::COMPONENT_LABEL, NewColumn::extractDataFromNodeData<LabelType>}, {"mimetypeVirtual", "tinyint AS (getmimetype(name)) VIRTUAL", NodeData::COMPONENT_NONE, nullptr}, {"fingerprintVirtual", "BLOB AS (getFingerprintExcludingMtime(fingerprint)) VIRTUAL", NodeData::COMPONENT_NONE, nullptr}, {"description", "text", NodeData::COMPONENT_DESCRIPTION, NewColumn::extractDataFromNodeData<DescriptionType>}, {"tags", "text", NodeData::COMPONENT_TAGS, NewColumn::extractDataFromNodeData<TagsType>}, {"sizeVirtual", "int64 AS (getSizeFromNodeCounter(counter)) VIRTUAL", NodeData::COMPONENT_NONE, nullptr}, }; if (!addAndPopulateColumns(db, std::move(newCols))) { sqlite3_close(db); return nullptr; } #if __ANDROID__ // Android doesn't provide a temporal directory -> change default policy for temp // store (FILE=1) to avoid failures on large queries, so it relies on MEMORY=2 result = sqlite3_exec(db, "PRAGMA temp_store=2;", nullptr, nullptr, nullptr); if (result) { LOG_err << "PRAGMA temp_store error " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } #endif result = sqlite3_create_function(db, "regexp", 2, SQLITE_ANY,0, &SqliteAccountState::userRegexp, 0, 0); if (result) { LOG_err << "Data base error(sqlite3_create_function userRegexp): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } result = sqlite3_create_function(db, "matchFilter", 10, SQLITE_ANY, 0, &SqliteAccountState::userMatchFilter, 0, 0); if (result) { LOG_err << "Data base error(sqlite3_create_function userMatchFilter): " << sqlite3_errmsg(db); sqlite3_close(db); return nullptr; } return new SqliteAccountState(rng, db, fsAccess, dbPath, (flags & DB_OPEN_FLAG_TRANSACTED) > 0, std::move(dBErrorCallBack)); } bool SqliteDbAccess::probe(FileSystemAccess& fsAccess, const string& name) const { auto fileAccess = fsAccess.newfileaccess(); LocalPath dbPath = databasePath(fsAccess, name, DB_VERSION); if (fileAccess->isfile(dbPath)) { return true; } dbPath = databasePath(fsAccess, name, LEGACY_DB_VERSION); return fileAccess->isfile(dbPath); } std::optional<std::filesystem::path> SqliteDbAccess::getExistingDbPath(const FileSystemAccess& fsAccess, const std::string& fname) const { auto expectedPath = databasePath(fsAccess, fname, DbAccess::DB_VERSION).asPlatformEncoded(false); if (std::filesystem::exists(expectedPath)) return expectedPath; expectedPath = databasePath(fsAccess, fname, DbAccess::LEGACY_DB_VERSION).asPlatformEncoded(false); if (std::filesystem::exists(expectedPath)) return expectedPath; return {}; } const LocalPath& SqliteDbAccess::rootPath() const { return mRootPath; } bool SqliteDbAccess::openDBAndCreateStatecache(sqlite3 **db, FileSystemAccess &fsAccess, const string &name, LocalPath &dbPath, const int flags) { checkDbFileAndAdjustLegacy(fsAccess, name, flags, dbPath); int result = sqlite3_open_v2(dbPath.toPath(false).c_str(), db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE // The database is opened for reading and writing, and is created if it does not already exist. This is the behavior that is always used for sqlite3_open() and sqlite3_open16(). | SQLITE_OPEN_FULLMUTEX // The new database connection will use the "Serialized" threading mode. This means that multiple threads can be used withou restriction. , nullptr); if (result) { if (db) { sqlite3_close(*db); } return false; } #if !(TARGET_OS_IPHONE) result = sqlite3_exec(*db, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr); if (result) { sqlite3_close(*db); return false; } #endif /* ! TARGET_OS_IPHONE */ string sql = "CREATE TABLE IF NOT EXISTS statecache (id INTEGER PRIMARY KEY ASC NOT NULL, content BLOB NOT NULL)"; result = sqlite3_exec(*db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { string err = string(" Error: ") + (sqlite3_errmsg(*db) ? sqlite3_errmsg(*db) : std::to_string(result)); LOG_debug << "Failed to create table 'statecache'" << err; sqlite3_close(*db); return false; } return true; } bool SqliteDbAccess::renameDBFiles(FileSystemAccess& fsAccess, const LocalPath& legacyPath, const LocalPath& dbPath) { // Main DB file should exits if (!fsAccess.renamelocal(legacyPath, dbPath)) { return false; } std::unique_ptr<FileAccess> fileAccess = fsAccess.newfileaccess(); #if !(TARGET_OS_IPHONE) auto suffix = LocalPath::fromRelativePath("-shm"); auto from = legacyPath; from.append(suffix); auto to = dbPath; to.append(suffix); // -shm could or couldn't be present if (fileAccess->fopen(from, FSLogging::logExceptFileNotFound) && !fsAccess.renamelocal(from, to)) { // Exists origin and failure to rename LOG_debug << "Failure to rename -shm file"; return false; } suffix = LocalPath::fromRelativePath("-wal"); from = legacyPath; from.append(suffix); to = dbPath; to.append(suffix); // -wal could or couldn't be present if (fileAccess->fopen(from, FSLogging::logExceptFileNotFound) && !fsAccess.renamelocal(from, to)) { // Exists origin and failure to rename LOG_debug << "Failure to rename -wall file"; return false; } #else // iOS doesn't use WAL mode, but Journal auto suffix = LocalPath::fromRelativePath("-journal"); auto from = legacyPath; from.append(suffix); auto to = dbPath; to.append(suffix); // -journal could or couldn't be present if (fileAccess->fopen(from, FSLogging::logExceptFileNotFound) && !fsAccess.renamelocal(from, to)) { // Exists origin and failure to rename LOG_debug << "Failure to rename -journal file"; return false; } #endif return true; } void SqliteDbAccess::removeDBFiles(FileSystemAccess& fsAccess, mega::LocalPath& dbPath) { fsAccess.unlinklocal(dbPath); #if !(TARGET_OS_IPHONE) auto fileToRemove = dbPath; fileToRemove.insertFilenameSuffix("-shm"); fsAccess.unlinklocal(fileToRemove); fileToRemove = dbPath; fileToRemove.insertFilenameSuffix("-wal"); fsAccess.unlinklocal(fileToRemove); #else // iOS doesn't use WAL mode, but Journal auto suffix = LocalPath::fromRelativePath("-journal"); auto fileToRemove = dbPath; fileToRemove.append(suffix); fsAccess.unlinklocal(fileToRemove); #endif } bool SqliteDbAccess::addAndPopulateColumns(sqlite3* db, vector<NewColumn>&& newCols) { // skip existing columns if (!stripExistingColumns(db, newCols)) { return false; } // add missing columns for (const auto& c : newCols) { if (!addColumn(db, c.name, c.type)) { return false; } } return migrateDataToColumns(db, std::move(newCols)); } bool SqliteDbAccess::stripExistingColumns(sqlite3* db, vector<NewColumn>& cols) { string query = "SELECT name, COUNT(name) FROM pragma_table_xinfo('nodes') WHERE name IN ( "; std::for_each(cols.begin(), cols.end(), [&query](const NewColumn& c) { query += '\'' + c.name + "',"; }); query.pop_back(); // drop trailing ',' query += " ) GROUP BY name"; sqlite3_stmt* stmt = nullptr; if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { LOG_err << "Db error while preparing to search for existing cols: " << sqlite3_errmsg(db); return false; } while (sqlite3_step(stmt) == SQLITE_ROW) { int existing = sqlite3_column_int(stmt, 1); const char* n = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); if (existing) { assert(n); // "Strings returned by sqlite3_column_text(), even empty strings, are always zero-terminated." cols.erase(std::remove_if(cols.begin(), cols.end(), [n](const NewColumn& c) { return c.name == n; }), cols.end()); } } sqlite3_finalize(stmt); return true; } bool SqliteDbAccess::addColumn(sqlite3* db, const string& name, const string& type) { string query("ALTER TABLE nodes ADD COLUMN '" + name + "' " + type); if (sqlite3_exec(db, query.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK) { LOG_err << "Db error while adding 'nodes." << name << ' ' << type << "' column: " << sqlite3_errmsg(db); return false; } return true; } bool SqliteDbAccess::migrateDataToColumns(sqlite3* db, vector<NewColumn>&& cols) { if (cols.empty()) return true; // identify data pieces to copy to new columns cols.erase(std::remove_if(cols.begin(), cols.end(), [](const NewColumn& c) { return c.migrationId == NodeData::COMPONENT_NONE; }), cols.end()); if (cols.empty()) return true; LOG_info << "Migrating Data base - populating new columns"; // get existing data sqlite3_stmt* stmt = nullptr; if (sqlite3_prepare_v2(db, "SELECT nodehandle, node FROM nodes", -1, &stmt, nullptr) != SQLITE_OK) { LOG_err << "Db error while preparing to extract data to migrate: " << sqlite3_errmsg(db); return false; } // extract values to be copied map<handle, std::vector<std::unique_ptr<MigrateType>>> newValues; uint64_t numRows = 0; uint64_t affectedRows = 0; while (sqlite3_step(stmt) == SQLITE_ROW) { const char* blob = static_cast<const char*>(sqlite3_column_blob(stmt, 1)); int blobSize = sqlite3_column_bytes(stmt, 1); handle nh = static_cast<handle>(sqlite3_column_int64(stmt, 0)); NodeData nd(blob, static_cast<size_t>(blobSize), NodeData::COMPONENT_ATTRS); std::vector<std::unique_ptr<MigrateType>> migrateElement; migrateElement.reserve(cols.size()); bool hasValues = std::transform_reduce( cols.begin(), cols.end(), false, std::logical_or{}, [&migrateElement, &nd](const NewColumn& c) -> bool { assert(c.migrateOperation); return c.migrateOperation(nd, migrateElement); }); ++numRows; // Only update row in DB if some column has valid data if (hasValues) { assert(migrateElement.size() == cols.size()); newValues[nh] = std::move(migrateElement); ++affectedRows; } } LOG_info << "Migrating Data base - affected rows: " << affectedRows << " from total rows: " << numRows; sqlite3_finalize(stmt); if (newValues.empty()) { return true; } // Calculate index for query parameters std::map<int, int> dataToMigrate; int bindIdx = 0; for (const auto& c: cols) { assert(c.migrationId > NodeData::COMPONENT_NONE); dataToMigrate[c.migrationId] = ++bindIdx; } if (sqlite3_exec(db, "BEGIN", nullptr, nullptr, nullptr) != SQLITE_OK) { LOG_debug << "Db error during migration for " << "BEGIN: " << sqlite3_errmsg(db); return false; } // build update query string query{"UPDATE nodes SET "}; for (const NewColumn& c : cols) { query += c.name + "= ?" + std::to_string(dataToMigrate[c.migrationId]) + ','; } query.pop_back(); // drop trailing ',' query += " WHERE nodehandle = ?" + std::to_string(cols.size() + 1); // identifier for 'nodehandle' if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { LOG_err << "Db error while preparing to populate new columns: " << sqlite3_errmsg(db); return false; } // run update query for each data entry for (const auto& update : newValues) { assert(update.second.size() == cols.size()); for (const auto& values : update.second) { assert(values); if (!values->bindToDb(stmt, dataToMigrate)) return false; } int stepResult; if (sqlite3_bind_int64(stmt, static_cast<int>(cols.size()) + 1, static_cast<sqlite3_int64>(update.first)) != SQLITE_OK || // nodehandle ((stepResult = sqlite3_step(stmt)) != SQLITE_DONE && stepResult != SQLITE_ROW) || sqlite3_reset(stmt) != SQLITE_OK) { LOG_err << "Db error during migration while updating columns: " << sqlite3_errmsg(db); sqlite3_finalize(stmt); return false; } } sqlite3_finalize(stmt); if (sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr) != SQLITE_OK) { LOG_debug << "Db error during migration for " << "COMMIT: " << sqlite3_errmsg(db); return false; } return true; } SqliteDbTable::SqliteDbTable(PrnGen &rng, sqlite3* db, FileSystemAccess &fsAccess, const LocalPath &path, const bool checkAlwaysTransacted, DBErrorCallback dBErrorCallBack) : DbTable(rng, checkAlwaysTransacted, dBErrorCallBack) , db(db) , dbfile(path) , fsaccess(&fsAccess) { } SqliteDbTable::~SqliteDbTable() { resetCommitter(); if (!db) { return; } sqlite3_finalize(pStmt); sqlite3_finalize(mDelStmt); sqlite3_finalize(mPutStmt); if (inTransaction()) { SqliteDbTable::abort(); // fully qualify virtual function } sqlite3_close(db); LOG_debug << "Database closed " << dbfile; } bool SqliteDbTable::inTransaction() const { return sqlite3_get_autocommit(db) == 0; } // set cursor to first record void SqliteDbTable::rewind() { if (!db) { return; } int result = SQLITE_OK; if (pStmt) { result = sqlite3_reset(pStmt); } else { result = sqlite3_prepare_v2(db, "SELECT id, content FROM statecache", -1, &pStmt, NULL); } errorHandler(result, "Rewind", false); } // retrieve next record through cursor bool SqliteDbTable::next(uint32_t* index, string* data) { if (!db) { return false; } if (!pStmt) { return false; } int rc = sqlite3_step(pStmt); if (rc != SQLITE_ROW) { sqlite3_finalize(pStmt); pStmt = NULL; errorHandler(rc, "Get next record", false); return false; } *index = static_cast<uint32_t>(sqlite3_column_int(pStmt, 0)); data->assign(static_cast<const char*>(sqlite3_column_blob(pStmt, 1)), static_cast<size_t>(sqlite3_column_bytes(pStmt, 1))); return true; } // retrieve record by index bool SqliteDbTable::get(uint32_t index, string* data) { if (!db) { return false; } sqlite3_stmt *stmt = nullptr; int rc; rc = sqlite3_prepare_v2(db, "SELECT content FROM statecache WHERE id = ?", -1, &stmt, NULL); if (rc == SQLITE_OK) { rc = sqlite3_bind_int(stmt, 1, static_cast<int>(index)); if (rc == SQLITE_OK) { rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { data->assign(static_cast<const char*>(sqlite3_column_blob(stmt, 0)), static_cast<size_t>(sqlite3_column_bytes(stmt, 0))); } } } errorHandler(rc, "Get record statecache", false); sqlite3_finalize(stmt); return rc == SQLITE_ROW; } // add/update record by index bool SqliteDbTable::put(uint32_t index, char* data, unsigned len) { if (!db) { return false; } // First bits at index are reserved for the type assert((index & (DbTable::IDSPACING - 1)) != MegaClient::CACHEDNODE); // nodes must be stored in DbTableNodes ('nodes' table, not 'statecache' table) checkTransaction(); int sqlResult = SQLITE_OK; if (!mPutStmt) { sqlResult = sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO statecache (id, content) VALUES (?, ?)", -1, &mPutStmt, nullptr); } if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_bind_int(mPutStmt, 1, static_cast<int>(index)); if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_bind_blob(mPutStmt, 2, data, static_cast<int>(len), SQLITE_STATIC); if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_step(mPutStmt); } } } errorHandler(sqlResult, "Put record", false); sqlite3_reset(mPutStmt); return sqlResult == SQLITE_DONE; } // delete record by index bool SqliteDbTable::del(uint32_t index) { if (!db) { return false; } checkTransaction(); int sqlResult = SQLITE_OK; if (!mDelStmt) { sqlResult = sqlite3_prepare_v2(db, "DELETE FROM statecache WHERE id = ?", -1, &mDelStmt, nullptr); } if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_bind_int(mDelStmt, 1, static_cast<int>(index)); if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_step(mDelStmt); // tipically SQLITE_DONE, but could be SQLITE_ROW if implementation returned removed row count } } errorHandler(sqlResult, "Delete record", false); sqlite3_reset(mDelStmt); return sqlResult == SQLITE_DONE || sqlResult == SQLITE_ROW; } // truncate table void SqliteDbTable::truncate() { if (!db) { return; } checkTransaction(); assert(inTransaction()); int rc = sqlite3_exec(db, "DELETE FROM statecache", 0, 0, NULL); errorHandler(rc, "Truncate ", false); } // begin transaction void SqliteDbTable::begin() { if (!db) { return; } assert(!inTransaction()); LOG_debug << "DB transaction BEGIN " << dbfile; int rc = sqlite3_exec(db, "BEGIN", 0, 0, NULL); errorHandler(rc, "Begin transaction", false); } // commit transaction void SqliteDbTable::commit() { if (!db) { return; } LOG_debug << "DB transaction COMMIT " << dbfile; int rc = sqlite3_exec(db, "COMMIT", 0, 0, NULL); errorHandler(rc, "Commit transaction", false); } // abort transaction void SqliteDbTable::abort() { if (!db) { return; } LOG_debug << "DB transaction ROLLBACK " << dbfile; int rc = sqlite3_exec(db, "ROLLBACK", 0, 0, NULL); errorHandler(rc, "Rollback", false); } void SqliteDbTable::remove() { if (!db) { return; } sqlite3_finalize(pStmt); pStmt = nullptr; sqlite3_finalize(mDelStmt); mDelStmt = nullptr; sqlite3_finalize(mPutStmt); mPutStmt = nullptr; if (inTransaction()) { abort(); } sqlite3_close(db); db = NULL; fsaccess->unlinklocal(dbfile); } void SqliteDbTable::errorHandler(int sqliteError, const string& operation, bool interrupt) { DBError dbError = DBError::DB_ERROR_UNKNOWN; switch (sqliteError) { case SQLITE_OK: case SQLITE_ROW: case SQLITE_DONE: return; case SQLITE_ERROR: dbError = DBError::DB_ERROR; break; case SQLITE_INTERNAL: dbError = DBError::DB_ERROR_INTERNAL; break; case SQLITE_PERM: dbError = DBError::DB_ERROR_PERM; break; case SQLITE_ABORT: dbError = DBError::DB_ERROR_ABORT; break; case SQLITE_BUSY: dbError = DBError::DB_ERROR_BUSY; break; case SQLITE_LOCKED: dbError = DBError::DB_ERROR_LOCKED; break; case SQLITE_NOMEM: dbError = DBError::DB_ERROR_NOMEM; break; case SQLITE_READONLY: dbError = DBError::DB_ERROR_READONLY; break; case SQLITE_INTERRUPT: if (interrupt) { // SQLITE_INTERRUPT isn't handle as an error if caller can be interrupted LOG_debug << operation << ": interrupted"; return; } dbError = DBError::DB_ERROR_INTERRUPT; break; case SQLITE_IOERR: dbError = DBError::DB_ERROR_IO; break; case SQLITE_CORRUPT: dbError = DBError::DB_ERROR_CORRUPT; break; case SQLITE_NOTFOUND: dbError = DBError::DB_ERROR_NOTFOUND; break; case SQLITE_FULL: dbError = DBError::DB_ERROR_FULL; break; case SQLITE_CANTOPEN: dbError = DBError::DB_ERROR_CANTOPEN; break; case SQLITE_PROTOCOL: dbError = DBError::DB_ERROR_PROTOCOL; break; case SQLITE_EMPTY: dbError = DBError::DB_ERROR_EMPTY; break; case SQLITE_SCHEMA: dbError = DBError::DB_ERROR_SCHEMA; break; case SQLITE_TOOBIG: dbError = DBError::DB_ERROR_TOOBIG; break; case SQLITE_CONSTRAINT: dbError = DBError::DB_ERROR_CONSTRAINT; break; case SQLITE_MISMATCH: dbError = DBError::DB_ERROR_MISMATCH; break; case SQLITE_MISUSE: dbError = DBError::DB_ERROR_MISUSE; break; case SQLITE_NOLFS: dbError = DBError::DB_ERROR_NOLFS; break; case SQLITE_AUTH: dbError = DBError::DB_ERROR_AUTH; break; case SQLITE_FORMAT: dbError = DBError::DB_ERROR_FORMAT; break; case SQLITE_RANGE: dbError = DBError::DB_ERROR_RANGE; break; case SQLITE_NOTADB: dbError = DBError::DB_ERROR_NOTADB; break; default: dbError = DBError::DB_ERROR_UNKNOWN; break; } string err = string(" Error: ") + (sqlite3_errmsg(db) ? sqlite3_errmsg(db) : std::to_string(sqliteError)); LOG_err << operation << ": " << dbfile << err; assert(!operation.c_str()); if (mDBErrorCallBack) { // Only notify DB errors related to disk-is-full and input/output failures mDBErrorCallBack(dbError); } } SqliteAccountState::SqliteAccountState(PrnGen &rng, sqlite3 *pdb, FileSystemAccess &fsAccess, const LocalPath &path, const bool checkAlwaysTransacted, DBErrorCallback dBErrorCallBack) : SqliteDbTable(rng, pdb, fsAccess, path, checkAlwaysTransacted, dBErrorCallBack) { } SqliteAccountState::~SqliteAccountState() { finalise(); } int SqliteAccountState::progressHandler(void *param) { CancelToken* cancelFlag = static_cast<CancelToken*>(param); return cancelFlag->isCancelled(); } bool SqliteAccountState::processSqlQueryNodes(sqlite3_stmt *stmt, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>& nodes) { assert(stmt); int sqlResult = SQLITE_ERROR; while ((sqlResult = sqlite3_step(stmt)) == SQLITE_ROW) { NodeHandle nodeHandle; nodeHandle.set6byte(static_cast<uint64_t>(sqlite3_column_int64(stmt, 0))); NodeSerialized node; // Blob node counter const void* data = sqlite3_column_blob(stmt, 1); int size = sqlite3_column_bytes(stmt, 1); if (data && size) { node.mNodeCounter = std::string(static_cast<const char*>(data), static_cast<size_t>(size)); } // blob node data = sqlite3_column_blob(stmt, 2); size = sqlite3_column_bytes(stmt, 2); if (data && size) { node.mNode = std::string(static_cast<const char*>(data), static_cast<size_t>(size)); nodes.insert(nodes.end(), std::make_pair(nodeHandle, std::move(node))); } } errorHandler(sqlResult, "Process sql query", true); return sqlResult == SQLITE_DONE; } bool SqliteAccountState::remove(NodeHandle nodehandle) { if (!db) { return false; } checkTransaction(); char buf[64]; snprintf(buf, sizeof(buf), "DELETE FROM nodes WHERE nodehandle = %" PRId64, nodehandle.as8byte()); int sqlResult = sqlite3_exec(db, buf, 0, 0, NULL); errorHandler(sqlResult, "Delete node", false); return sqlResult == SQLITE_OK; } bool SqliteAccountState::removeNodes() { if (!db) { return false; } checkTransaction(); int sqlResult = sqlite3_exec(db, "DELETE FROM nodes", 0, 0, NULL); errorHandler(sqlResult, "Delete nodes", false); return sqlResult == SQLITE_OK; } void SqliteAccountState::updateCounter(NodeHandle nodeHandle, const std::string& nodeCounterBlob) { if (!db) { return; } checkTransaction(); int sqlResult = SQLITE_OK; if (!mStmtUpdateNode) { sqlResult = sqlite3_prepare_v2(db, "UPDATE nodes SET counter = ? WHERE nodehandle = ?", -1, &mStmtUpdateNode, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_blob(mStmtUpdateNode, 1, nodeCounterBlob.data(), static_cast<int>(nodeCounterBlob.size()), SQLITE_STATIC)) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64( mStmtUpdateNode, 2, static_cast<sqlite3_int64>(nodeHandle.as8byte()))) == SQLITE_OK) { sqlResult = sqlite3_step(mStmtUpdateNode); } } } errorHandler(sqlResult, "Update counter", false); sqlite3_reset(mStmtUpdateNode); } void SqliteAccountState::updateCounterAndFlags(NodeHandle nodeHandle, uint64_t flags, const std::string& nodeCounterBlob) { if (!db) { return; } checkTransaction(); int sqlResult = SQLITE_OK; if (!mStmtUpdateNodeAndFlags) { sqlResult = sqlite3_prepare_v2(db, "UPDATE nodes SET counter = ?, flags = ? WHERE nodehandle = ?", -1, &mStmtUpdateNodeAndFlags, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_blob(mStmtUpdateNodeAndFlags, 1, nodeCounterBlob.data(), static_cast<int>(nodeCounterBlob.size()), SQLITE_STATIC)) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtUpdateNodeAndFlags, 2, static_cast<sqlite3_int64>(flags))) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64( mStmtUpdateNodeAndFlags, 3, static_cast<sqlite3_int64>(nodeHandle.as8byte()))) == SQLITE_OK) { sqlResult = sqlite3_step(mStmtUpdateNodeAndFlags); } } } } errorHandler(sqlResult, "Update counter and flags", false); sqlite3_reset(mStmtUpdateNodeAndFlags); } void SqliteAccountState::createIndexes(bool enableIndexesForSearching, bool enableIndexesForLexicographicalList) { if (!db) { return; } // Create index for column that is not primary key (which already has an index by default) std::string sql = "CREATE INDEX IF NOT EXISTS parenthandleindex on nodes (parenthandle, type, name)"; int result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (parenthandleindex): " << sqlite3_errmsg(db); } sql = "CREATE INDEX IF NOT EXISTS fingerprintindex on nodes (fingerprint)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (fingerprintindex): " << sqlite3_errmsg(db); } sql = "CREATE INDEX IF NOT EXISTS fingerprintvirtualindex on nodes (fingerprintVirtual)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (fingerprintvirtualindex): " << sqlite3_errmsg(db); } #if defined( __ANDROID__) || defined(USE_IOS) sql = "CREATE INDEX IF NOT EXISTS origFingerprintindex on nodes (origFingerprint)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (origFingerprintindex): " << sqlite3_errmsg(db); } #endif if (enableIndexesForSearching) { sql = "CREATE INDEX IF NOT EXISTS shareindex on nodes (share)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (shareindex): " << sqlite3_errmsg(db); } sql = "CREATE INDEX IF NOT EXISTS favindex on nodes (fav)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (favindex): " << sqlite3_errmsg(db); } sql = "CREATE INDEX IF NOT EXISTS ctimeindex on nodes (type, ctime DESC)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (ctimeindex): " << sqlite3_errmsg(db); } } if (enableIndexesForLexicographicalList) { sql = "CREATE INDEX IF NOT EXISTS lexicopraphicindex on nodes (parenthandle, name, type, " "nodehandle)"; result = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, nullptr); if (result) { LOG_err << "Data base error while creating index (lexicopraphicindex): " << sqlite3_errmsg(db); } } } void SqliteAccountState::dropSearchDBIndexes() { dropDBIndexes({"shareindex", "favindex", "ctimeindex"}); } void SqliteAccountState::dropLexicographicDBIndexes() { dropDBIndexes({"lexicopraphicindex"}); } void SqliteAccountState::dropDBIndexes(const std::vector<std::string>& indicesToDelete) { if (!db) { return; } assert(!inTransaction()); // Finalise all statements finalise(); begin(); for (const auto& indexName: indicesToDelete) { sqlite3_stmt* stmt = nullptr; const std::string query = "SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?"; if (sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr) == SQLITE_OK) { sqlite3_bind_text(stmt, 1, indexName.c_str(), -1, SQLITE_TRANSIENT); if (sqlite3_step(stmt) == SQLITE_ROW) { sqlite3_finalize(stmt); const std::string dropStmt = "DROP INDEX " + indexName + ";"; if (int sqlResult = sqlite3_exec(db, dropStmt.c_str(), nullptr, nullptr, nullptr); sqlResult != SQLITE_OK) { errorHandler(sqlResult, "Error while dropping index (" + indexName + ")", false); } } else { sqlite3_finalize(stmt); } } } commit(); } void SqliteAccountState::remove() { finalise(); SqliteDbTable::remove(); } void SqliteAccountState::finalise() { sqlite3_finalize(mStmtPutNode); mStmtPutNode = nullptr; sqlite3_finalize(mStmtUpdateNode); mStmtUpdateNode = nullptr; sqlite3_finalize(mStmtUpdateNodeAndFlags); mStmtUpdateNodeAndFlags = nullptr; sqlite3_finalize(mStmtTypeAndSizeNode); mStmtTypeAndSizeNode = nullptr; sqlite3_finalize(mStmtGetNode); mStmtGetNode = nullptr; sqlite3_finalize(mStmtChildrenFromType); mStmtChildrenFromType = nullptr; sqlite3_finalize(mStmtNumChildren); mStmtNumChildren = nullptr; for (auto& s : mStmtGetChildren) { sqlite3_finalize(s.second); } mStmtGetChildren.clear(); sqlite3_finalize(mStmtGetChildrenLexi); mStmtGetChildrenLexi = nullptr; sqlite3_finalize(mStmtGetChildrenLexiNoOffset); mStmtGetChildrenLexiNoOffset = nullptr; for (auto& s : mStmtSearchNodes) { sqlite3_finalize(s.second); } mStmtSearchNodes.clear(); sqlite3_finalize(mStmtNodeTagsBelow); mStmtNodeTagsBelow = nullptr; sqlite3_finalize(mStmtNodesByFpNoMtime); mStmtNodesByFpNoMtime = nullptr; sqlite3_finalize(mStmtNodeByFp); mStmtNodeByFp = nullptr; sqlite3_finalize(mStmtNodeByOrigFp); mStmtNodeByOrigFp = nullptr; sqlite3_finalize(mStmtNodesWithInshares); mStmtNodesWithInshares = nullptr; sqlite3_finalize(mStmtNodesWithOutshares); mStmtNodesWithOutshares = nullptr; sqlite3_finalize(mStmtNodesWithPendingOutshares); mStmtNodesWithPendingOutshares = nullptr; sqlite3_finalize(mStmtNodesWithPubLink); mStmtNodesWithPubLink = nullptr; sqlite3_finalize(mStmtChildNode); mStmtChildNode = nullptr; sqlite3_finalize(mStmtIsAncestor); mStmtIsAncestor = nullptr; sqlite3_finalize(mStmtNumChild); mStmtNumChild = nullptr; sqlite3_finalize(mStmtRecents); mStmtRecents = nullptr; sqlite3_finalize(mStmtFavourites); mStmtFavourites = nullptr; } bool SqliteAccountState::put(Node *node) { if (!db) { return false; } checkTransaction(); int sqlResult = SQLITE_OK; if (!mStmtPutNode) { sqlResult = sqlite3_prepare_v2(db, "INSERT OR REPLACE INTO nodes (nodehandle, parenthandle, " "name, fingerprint, origFingerprint, type, share, fav, ctime, " "mtime, flags, counter, node, label, description, tags) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", -1, &mStmtPutNode, NULL); } if (sqlResult == SQLITE_OK) { string nodeSerialized; node->serialize(&nodeSerialized); assert(nodeSerialized.size()); sqlite3_bind_int64(mStmtPutNode, 1, static_cast<sqlite3_int64>(node->nodehandle)); sqlite3_bind_int64(mStmtPutNode, 2, static_cast<sqlite3_int64>(node->parenthandle)); std::string name = node->displayname(Node::LOG_CONDITION_DISABLE_NO_KEY); sqlite3_bind_text(mStmtPutNode, 3, name.c_str(), static_cast<int>(name.length()), SQLITE_STATIC); string fp; node->FileFingerprint::serialize(&fp); sqlite3_bind_blob(mStmtPutNode, 4, fp.data(), static_cast<int>(fp.size()), SQLITE_STATIC); std::string origFingerprint; attr_map::const_iterator attrIt = node->attrs.map.find(makeNameid("c0")); if (attrIt != node->attrs.map.end()) { origFingerprint = attrIt->second; } sqlite3_bind_blob(mStmtPutNode, 5, origFingerprint.data(), static_cast<int>(origFingerprint.size()), SQLITE_STATIC); sqlite3_bind_int(mStmtPutNode, 6, node->type); int shareType = node->getShareType(); sqlite3_bind_int(mStmtPutNode, 7, shareType); // node->attrstring has value => node is encrypted nameid favId = AttrMap::string2nameid("fav"); auto favIt = node->attrs.map.find(favId); bool fav = (favIt != node->attrs.map.end() && favIt->second == "1"); // test 'fav' attr value (only "1" is valid) sqlite3_bind_int(mStmtPutNode, 8, fav); sqlite3_bind_int64(mStmtPutNode, 9, node->ctime); sqlite3_bind_int64(mStmtPutNode, 10, node->mtime); sqlite3_bind_int64(mStmtPutNode, 11, static_cast<sqlite3_int64>(node->getDBFlags())); std::string nodeCountersBlob = node->getCounter().serialize(); sqlite3_bind_blob(mStmtPutNode, 12, nodeCountersBlob.data(), static_cast<int>(nodeCountersBlob.size()), SQLITE_STATIC); sqlite3_bind_blob(mStmtPutNode, 13, nodeSerialized.data(), static_cast<int>(nodeSerialized.size()), SQLITE_STATIC); static nameid labelId = AttrMap::string2nameid("lbl"); auto labelIt = node->attrs.map.find(labelId); int label = (labelIt == node->attrs.map.end()) ? LBL_UNKNOWN : std::atoi(labelIt->second.c_str()); sqlite3_bind_int(mStmtPutNode, 14, label); nameid descriptionId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_DESCRIPTION); if (auto descriptionIt = node->attrs.map.find(descriptionId); descriptionIt != node->attrs.map.end()) { const std::string& description = descriptionIt->second; sqlite3_bind_text(mStmtPutNode, 15, description.c_str(), static_cast<int>(description.length()), SQLITE_STATIC); } else { sqlite3_bind_null(mStmtPutNode, 15); } nameid tagId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); if (auto tagIt = node->attrs.map.find(tagId); tagIt != node->attrs.map.end()) { const std::string& tag = tagIt->second; sqlite3_bind_text(mStmtPutNode, 16, tag.c_str(), static_cast<int>(tag.length()), SQLITE_STATIC); } else { sqlite3_bind_null(mStmtPutNode, 16); } sqlResult = sqlite3_step(mStmtPutNode); } errorHandler(sqlResult, "Put node", false); sqlite3_reset(mStmtPutNode); return sqlResult == SQLITE_DONE; } bool SqliteAccountState::getNode(NodeHandle nodehandle, NodeSerialized &nodeSerialized) { bool success = false; if (!db) { return success; } nodeSerialized.mNode.clear(); int sqlResult = SQLITE_OK; if (!mStmtGetNode) { sqlResult = sqlite3_prepare_v2(db, "SELECT counter, node FROM nodes WHERE nodehandle = ?", -1, &mStmtGetNode, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtGetNode, 1, static_cast<sqlite3_int64>(nodehandle.as8byte()))) == SQLITE_OK) { if((sqlResult = sqlite3_step(mStmtGetNode)) == SQLITE_ROW) { const void* dataNodeCounter = sqlite3_column_blob(mStmtGetNode, 0); int sizeNodeCounter = sqlite3_column_bytes(mStmtGetNode, 0); const void* dataNodeSerialized = sqlite3_column_blob(mStmtGetNode, 1); int sizeNodeSerialized = sqlite3_column_bytes(mStmtGetNode, 1); if (dataNodeCounter && sizeNodeCounter && dataNodeSerialized && sizeNodeSerialized) { nodeSerialized.mNodeCounter.assign(static_cast<const char*>(dataNodeCounter), static_cast<size_t>(sizeNodeCounter)); nodeSerialized.mNode.assign(static_cast<const char*>(dataNodeSerialized), static_cast<size_t>(sizeNodeSerialized)); success = true; } } } } if (sqlResult != SQLITE_ROW && sqlResult != SQLITE_DONE) { errorHandler(sqlResult, "Get node", false); } sqlite3_reset(mStmtGetNode); return success; } bool SqliteAccountState::getNodesByOrigFingerprint(const std::string &fingerprint, std::vector<std::pair<NodeHandle, NodeSerialized>> &nodes) { if (!db) { return false; } int sqlResult = SQLITE_OK; if (!mStmtNodeByOrigFp) { sqlResult = sqlite3_prepare_v2(db, "SELECT nodehandle, counter, node FROM nodes WHERE origfingerprint = ?", -1, &mStmtNodeByOrigFp, NULL); } bool result = false; if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_blob(mStmtNodeByOrigFp, 1, fingerprint.data(), (int)fingerprint.size(), SQLITE_STATIC)) == SQLITE_OK) { result = processSqlQueryNodes(mStmtNodeByOrigFp, nodes); } } errorHandler(sqlResult, "Get node by orig fingerprint", false); sqlite3_reset(mStmtNodeByOrigFp); return result; } bool SqliteAccountState::getRootNodes(std::vector<std::pair<NodeHandle, NodeSerialized>> &nodes) { if (!db) { return false; } sqlite3_stmt *stmt = nullptr; bool result = false; int sqlResult = sqlite3_prepare_v2(db, "SELECT nodehandle, counter, node FROM nodes WHERE type >= ? AND type <= ?", -1, &stmt, NULL); if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int(stmt, 1, nodetype_t::ROOTNODE)) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int(stmt, 2, nodetype_t::RUBBISHNODE)) == SQLITE_OK) { result = processSqlQueryNodes(stmt, nodes); } } } errorHandler(sqlResult, "Get root nodes", false); sqlite3_finalize(stmt); return result; } bool SqliteAccountState::getNodesWithSharesOrLink(std::vector<std::pair<NodeHandle, NodeSerialized>> &nodes, ShareType_t shareType) { if (!db) { return false; } sqlite3_stmt* stmt = nullptr; int sqlResult = SQLITE_OK; // The integers for the expresion "share IN (x, y, z,...)" are the decimal representation of // the possible combinations of ShareType_t binary map values. // For example 10 coresponds to IN_SHARES = 0x01 + LINK = 0x08 and 12 corresponds to // PENDING_OUTSHARES = 0x04 + LINK = 0x08. static constexpr auto sqlQueryInshares = "SELECT nodehandle, counter, node FROM nodes WHERE share IN (1,3,5,7,9,11,13,15)"; static constexpr auto sqlQueryOutshares = "SELECT nodehandle, counter, node FROM nodes WHERE share IN (2,3,6,7,10,11,14,15)"; static constexpr auto sqlQueryPendingOutshares = "SELECT nodehandle, counter, node FROM nodes WHERE share IN (4,5,6,7,12,13,14,15)"; static constexpr auto sqlQueryPubLink = "SELECT nodehandle, counter, node FROM nodes WHERE share IN (8,9,10,11,12,13,14,15)"; switch (shareType) { case ShareType_t::IN_SHARES: if (!mStmtNodesWithInshares) { sqlResult = sqlite3_prepare_v2(db, sqlQueryInshares, -1, &mStmtNodesWithInshares, nullptr); } stmt = mStmtNodesWithInshares; break; case ShareType_t::OUT_SHARES: if (!mStmtNodesWithOutshares) { sqlResult = sqlite3_prepare_v2(db, sqlQueryOutshares, -1, &mStmtNodesWithOutshares, nullptr); } stmt = mStmtNodesWithOutshares; break; case ShareType_t::PENDING_OUTSHARES: if (!mStmtNodesWithPendingOutshares) { sqlResult = sqlite3_prepare_v2(db, sqlQueryPendingOutshares, -1, &mStmtNodesWithPendingOutshares, nullptr); } stmt = mStmtNodesWithPendingOutshares; break; case ShareType_t::LINK: if (!mStmtNodesWithPubLink) { sqlResult = sqlite3_prepare_v2(db, sqlQueryPubLink, -1, &mStmtNodesWithPubLink, nullptr); } stmt = mStmtNodesWithPubLink; break; default: return false; } bool result = false; if (sqlResult == SQLITE_OK && stmt) { result = processSqlQueryNodes(stmt, nodes); } errorHandler(sqlResult, "Get nodes with shares or link", false); if (stmt) { sqlite3_reset(stmt); } return result; } uint64_t SqliteAccountState::getNumberOfChildren(NodeHandle parentHandle) { if (!db) { return false; } uint64_t numChildren = 0; int sqlResult = SQLITE_OK; if (!mStmtNumChildren) { sqlResult = sqlite3_prepare_v2(db, "SELECT count(*) FROM nodes WHERE parenthandle = ?", -1, &mStmtNumChildren, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtNumChildren, 1, static_cast<sqlite3_int64>(parentHandle.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_step(mStmtNumChildren)) == SQLITE_ROW) { numChildren = static_cast<uint64_t>(sqlite3_column_int64(mStmtNumChildren, 0)); } } } errorHandler(sqlResult, "Get number of children", false); sqlite3_reset(mStmtNumChildren); return numChildren; } namespace { /** * @class QueryTagId * @brief Helper struct to deal with sqlite statement place holders (e.g. ?10) * */ struct QueryTagId { explicit QueryTagId(int id): mId{id} {} operator int() const { return mId; } operator std::string() const { return "?" + std::to_string(mId); } std::string operator+(const std::string& other) { return std::string(*this) + other; } friend std::string operator+(const std::string& lhs, const QueryTagId& rhs) { return lhs + std::string(rhs); } private: int mId; }; // Helper function for binding values like int, int64 template<typename T, typename V> int bindValue(int& sqlResult, sqlite3_stmt* stmt, int index, V value, int (*bindFunc)(sqlite3_stmt*, int, T)) { if constexpr (std::is_integral_v<T> && std::is_integral_v<V>) { // Allow casting between integral types static_assert(sizeof(T) >= sizeof(V), "Target type T must be able to hold value of type V without truncation"); } if (sqlResult == SQLITE_OK) { sqlResult = bindFunc(stmt, index, static_cast<T>(value)); } return sqlResult; } // Helper function for binding text int bindText(int& sqlResult, sqlite3_stmt* stmt, int index, const std::string& text) { if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_bind_text(stmt, index, text.c_str(), static_cast<int>(text.size()), SQLITE_STATIC); } return sqlResult; } // Helper function for binding pointers int bindPointer(int& sqlResult, sqlite3_stmt* stmt, int index, void* ptr, const char* type) { if (sqlResult == SQLITE_OK) { sqlResult = sqlite3_bind_pointer(stmt, index, ptr, type, nullptr); } return sqlResult; } } bool SqliteAccountState::getChildren(const mega::NodeSearchFilter& filter, int order, vector<pair<NodeHandle, NodeSerialized>>& children, CancelToken cancelFlag, const NodeSearchPage& page) { if (!db) return false; if (cancelFlag.exists()) sqlite3_progress_handler(db, NUM_VIRTUAL_MACHINE_INSTRUCTIONS, SqliteAccountState::progressHandler, static_cast<void*>(&cancelFlag)); // There are multiple criteria used in ORDER BY clause. // For every order type a new statement is created const size_t cacheId = OrderByClause::getId(order); sqlite3_stmt*& stmt = mStmtGetChildren[cacheId]; int sqlResult = SQLITE_OK; static const QueryTagId idParentHand{1}; static const QueryTagId idPageSize{2}; static const QueryTagId idPageOff{3}; static const QueryTagId idFilter{4}; if (!stmt) { // Inherited sensitivity is not a concern here. When filtering out sensitive nodes, the // parent of all children would be checked before getting here. There's no point in making // this query recursive just because of that. using namespace std::string_literals; // Disabling format for query readability // clang-format off const std::string sqlQuery = "SELECT nodehandle, counter, node "s + "FROM nodes " "WHERE (parenthandle = " + idParentHand + ") " // Versions aren't taken in consideration "AND matchFilter(" + idFilter + ", flags, type, ctime, mtime, mimetypeVirtual, name, description, tags, fav)" "ORDER BY \n" + OrderByClause::get(order) + " \n" + "LIMIT " + idPageSize + " OFFSET " + idPageOff; // clang-format on sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &stmt, NULL); } bool result = false; const sqlite3_int64 pageSize = page.size() ? static_cast<sqlite3_int64>(page.size()) : -1; NodeSearchFilter filterCopy = filter; bindPointer(sqlResult, stmt, idFilter, &filterCopy, NodeSearchFilterPtrStr); bindValue(sqlResult, stmt, idParentHand, filter.byParentHandle(), sqlite3_bind_int64); bindValue(sqlResult, stmt, idPageSize, pageSize, sqlite3_bind_int64); bindValue(sqlResult, stmt, idPageOff, page.startingOffset(), sqlite3_bind_int64); if (sqlResult == SQLITE_OK) result = processSqlQueryNodes(stmt, children); // unregister the handler (no-op if not registered) sqlite3_progress_handler(db, -1, nullptr, nullptr); errorHandler(sqlResult, "Get children with filter", true); sqlite3_reset(stmt); return result; } bool SqliteAccountState::listChildNodesLexicographically( const handle parenthandle, std::vector<std::pair<NodeHandle, NodeSerialized>>& children, CancelToken cancelFlag, const size_t maxElements, const std::optional<NodeSearchLexicographicalOffset>& offset) { if (!db) return false; if (cancelFlag.exists()) sqlite3_progress_handler(db, NUM_VIRTUAL_MACHINE_INSTRUCTIONS, SqliteAccountState::progressHandler, static_cast<void*>(&cancelFlag)); // There are multiple criteria used in ORDER BY clause. // For every order type a new statement is created int sqlResult = SQLITE_OK; static const QueryTagId idParentHand{1}; static const QueryTagId idPageOffName{2}; static const QueryTagId idPageOffType{3}; static const QueryTagId idPageSize{4}; static const QueryTagId idPageOffHandle{5}; sqlite3_stmt*& stmt = offset ? mStmtGetChildrenLexi : mStmtGetChildrenLexiNoOffset; if (!stmt) { // clang-format off const std::string offsetWhere = offset ? "AND ((name > "s + idPageOffName + ") OR " + "(name = " + idPageOffName + " AND type > " + idPageOffType + ") OR " + "(name = " + idPageOffName + " AND type = " + idPageOffType + " AND nodehandle > " + idPageOffHandle + "))" : ""; const std::string sqlQuery = "SELECT nodehandle, counter, node "s + "FROM nodes " "WHERE (parenthandle = " + idParentHand + ") " // Versions aren't taken in consideration + offsetWhere + "ORDER BY name, type, nodehandle\n" "LIMIT " + idPageSize; // clang-format on sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &stmt, NULL); } const sqlite3_int64 pageSize = maxElements == 0 ? -1 : static_cast<sqlite3_int64>(maxElements); bindValue(sqlResult, stmt, idPageSize, pageSize, sqlite3_bind_int64); bindValue(sqlResult, stmt, idParentHand, parenthandle, sqlite3_bind_int64); if (offset) { const auto lastType = offset->mLastType.value_or(std::numeric_limits<int>::max()); const auto lastHandle = offset->mLastHandle ? static_cast<sqlite3_int64>(*offset->mLastHandle) : std::numeric_limits<sqlite3_int64>::max(); bindValue(sqlResult, stmt, idPageOffType, lastType, sqlite3_bind_int64); bindValue(sqlResult, stmt, idPageOffHandle, lastHandle, sqlite3_bind_int64); bindText(sqlResult, stmt, idPageOffName, offset->mLastName); } bool result = false; if (sqlResult == SQLITE_OK) result = processSqlQueryNodes(stmt, children); // unregister the handler (no-op if not registered) sqlite3_progress_handler(db, -1, nullptr, nullptr); errorHandler(sqlResult, "List child nodes with Lexicographical order", true); sqlite3_reset(stmt); return result; } auto SqliteAccountState::getNodeTagsBelow(CancelToken cancelToken, NodeHandle handle, const std::string& pattern) -> std::optional<std::set<std::string>> { // Convenience. static auto failed = [](const std::string& message) { LOG_err << "SqliteAccountState::getNodeTagsBelow: " << message; return std::nullopt; }; // failed static auto couldntBindParameter = [](auto index) { static const std::string message = "Couldn't bind parameter ?"; return failed(message + std::to_string(index)); }; // couldntBindParameter // Our database isn't in a usable state. if (!db) return failed("Invalid database"); // Transmit to global error handler on return. auto result = SQLITE_OK; // Transmits our result to the global error handler on return. auto cleanup = makeScopedDestructor( [&]() { // Remove any active progress handler. sqlite3_progress_handler(db, -1, nullptr, nullptr); // Transmit result to global error handler. errorHandler(result, "Get node tags below", true); // Make sure our statement's in a reusable state. sqlite3_reset(mStmtNodeTagsBelow); }); // cleanup // Caller wants to be able to abort the query. if (cancelToken.exists()) { sqlite3_progress_handler(db, NUM_VIRTUAL_MACHINE_INSTRUCTIONS, SqliteAccountState::progressHandler, static_cast<void*>(&cancelToken)); } // Statement needs to be instantiated. if (!mStmtNodeTagsBelow) { // This query retrieves all the tags below some particular node or // below all root nodes in the user's account by performing a // breadth-first traversal from those nodes. // // The way the query works is basically by populating a virtual // table repeatedly. For instance, in the first step, we ask which // nodes are below a particular node. That'll produce a result set // containing that node's direct children. // // The engine then performs the same query on that exact result set // which will produce a result set containing the descendants of // those children. // // The process repeats until there are no more directories to // traverse into. // // Note that this query does not descend down file version chains. auto query = "with recursive tags (nodehandle, tags, type) as ( " "select n.nodehandle " " , n.tags " " , n.type " " from nodes as n " " where ((?1 = 0 and n.parenthandle = -1) " " or (?1 = 1 and n.nodehandle = ?2)) " " and n.type != 0 " " union " "select n.nodehandle " " , n.tags " " , n.type " " from tags as t " " inner join nodes as n " " on n.parenthandle = t.nodehandle " " and t.type != 0 " " where n.type != 0 " " or n.tags is not null " " and n.tags != '' " ") " "select distinct " " tags " " from tags " " where tags is not null " " and tags != '' " " and (?3 = 0 or tags regexp ?4)"; // Try and instantiate our statement. result = sqlite3_prepare_v2(db, query, -1, &mStmtNodeTagsBelow, nullptr); // Couldn't instantiate statement. if (result != SQLITE_OK) return failed("Couldn't prepare query"); } // Clarity. enum ParameterIndex { PARAM_HAS_NODE_HANDLE = 1, PARAM_NODE_HANDLE, PARAM_HAS_PATTERN, PARAM_PATTERN }; // ParameterIndex // Let the query know if we have a search root. result = sqlite3_bind_int64(mStmtNodeTagsBelow, PARAM_HAS_NODE_HANDLE, !handle.isUndef()); // Couldn't bind parameter. if (result != API_OK) return couldntBindParameter(PARAM_HAS_NODE_HANDLE); // Let the query know which node we're searching below. result = sqlite3_bind_int64(mStmtNodeTagsBelow, PARAM_NODE_HANDLE, static_cast<std::int64_t>(handle.as8byte())); // Couldn't bind parameter. if (result != API_OK) return couldntBindParameter(PARAM_NODE_HANDLE); // Generate effective pattern. auto effectivePattern = ensureAsteriskSurround(pattern); // Let the query know if the caller's provided a pattern. result = sqlite3_bind_int(mStmtNodeTagsBelow, PARAM_HAS_PATTERN, !pattern.empty()); // Couldn't bind parameter. if (result != API_OK) return couldntBindParameter(PARAM_HAS_PATTERN); // Let the query know what the pattern is. result = sqlite3_bind_text(mStmtNodeTagsBelow, PARAM_PATTERN, effectivePattern.c_str(), static_cast<int>(effectivePattern.size()), SQLITE_STATIC); // Couldn't bind parameter. if (result != API_OK) return couldntBindParameter(PARAM_PATTERN); std::set<std::string> tags; // Process each result row. while (result != SQLITE_DONE) { // Try and retrieve a row from the database. result = sqlite3_step(mStmtNodeTagsBelow); // Couldn't get a row from the database. if (result != SQLITE_DONE && result != SQLITE_ROW) return failed("Couldn't retrieve row from database"); // Get our hands on this node's delimited list of tags. auto* data = reinterpret_cast<const char*>(sqlite3_column_blob(mStmtNodeTagsBelow, 0)); // How large is the node's delimited list of tags? auto size = static_cast<std::size_t>(sqlite3_column_bytes(mStmtNodeTagsBelow, 0)); // Delimited list of tags is null or empty. if (!data || !size) continue; // Separate individual tags. auto individualTags = splitString<decltype(tags)>(std::string(data, size), MegaClient::TAG_DELIMITER); // Collect the tags that satisfy our pattern. for (auto i = individualTags.begin(); i != individualTags.end();) { // Convenience. auto j = i++; // Not interested in this node. if (!pattern.empty() && !likeCompare(effectivePattern.c_str(), j->c_str(), false)) continue; // Move tag into tags set. tags.insert(individualTags.extract(j)); } } // Return tags to caller. return std::optional<decltype(tags)>(std::in_place, std::move(tags)); } bool SqliteAccountState::searchNodes(const NodeSearchFilter& filter, int order, vector<pair<NodeHandle, NodeSerialized>>& nodes, CancelToken cancelFlag, const NodeSearchPage& page) { if (!db) return false; if (cancelFlag.exists()) sqlite3_progress_handler(db, NUM_VIRTUAL_MACHINE_INSTRUCTIONS, SqliteAccountState::progressHandler, static_cast<void*>(&cancelFlag)); // There are multiple criteria used in ORDER BY clause. // For every order type a new statement is created size_t cacheId = OrderByClause::getId(order); sqlite3_stmt*& stmt = mStmtSearchNodes[cacheId]; static const QueryTagId idVerFlag{1}; static const QueryTagId idName{2}; static const QueryTagId idAncestor1{3}; static const QueryTagId idAncestor2{4}; static const QueryTagId idAncestor3{5}; static const QueryTagId idPageSize{6}; static const QueryTagId idPageOff{7}; static const QueryTagId idSens{8}; static const QueryTagId idSensFlag{9}; static const QueryTagId idIncShares{10}; static const QueryTagId idFilter{11}; int sqlResult = SQLITE_OK; if (!stmt) { // Handful string conversions static const std::string undefStr{std::to_string(static_cast<sqlite3_int64>(UNDEF))}; static const std::string noShareStr{std::to_string(NO_SHARES)}; static const std::string onlyTrueStr = std::to_string(static_cast<int>(NodeSearchFilter::BoolFilter::onlyTrue)); static const std::string filenodeStr = std::to_string(FILENODE); // Columns for the SELECT static const std::vector<std::string> columnsForNodeAndFiltersVec = {"nodehandle", "parenthandle", "flags", "name", "type", "counter", "node", "sizeVirtual", "ctime", "mtime", "share", "mimetypeVirtual", "fav", "label", "description", "tags"}; // Output: "nodehandle, parenthandle, flags, ..." static const std::string columnsForNodeAndFilters = joinStrings(std::cbegin(columnsForNodeAndFiltersVec), std::cend(columnsForNodeAndFiltersVec), ", "); // Output: "N.nodehandle, N.parenthandle, N.flags, ..." static const std::string columnsForNodeAndFiltersPrefixN = joinStrings(std::cbegin(columnsForNodeAndFiltersVec), std::cend(columnsForNodeAndFiltersVec), ", ", [](const std::string& n) -> std::string { return "N." + n; }); static const std::string columnsForNodeAndOrderBy = "nodehandle, counter, node, " // for nodes "type, sizeVirtual, ctime, mtime, name, label, fav"; // for ORDER BY only using namespace std::string_literals; // Disabling format for query readability // clang-format off static const std::string ancestors = "ancestors(nodehandle) \n"s "AS (SELECT nodehandle FROM nodes \n" "WHERE (" + idAncestor1 + " != " + undefStr + " AND nodehandle = " + idAncestor1 + ") " "OR (" + idAncestor2 + " != " + undefStr + " AND nodehandle = " + idAncestor2 + ") " "OR (" + idAncestor3 + " != " + undefStr + " AND nodehandle = " + idAncestor3 + ") " "OR (" + idIncShares + " != " + noShareStr + " AND type != " + filenodeStr + " AND share & " + idIncShares + " != 0))"; static const std::string nodesOfShares = "nodesOfShares(" + columnsForNodeAndFilters + ") \n" "AS (SELECT " + columnsForNodeAndFilters + " \n" "FROM nodes \n" "WHERE parenthandle NOT IN (SELECT nodehandle FROM ancestors) AND " + idIncShares + " != " + noShareStr + " AND share & " + idIncShares + " != 0)"; static const std::string nodesCTE = "nodesCTE(" + columnsForNodeAndFilters + ") \n" "AS (SELECT " + columnsForNodeAndFilters + " \n" "FROM nodes \n" "WHERE parenthandle IN (SELECT nodehandle FROM ancestors) \n" "UNION ALL \n" "SELECT " + columnsForNodeAndFiltersPrefixN + " \n" "FROM nodes AS N \n" "INNER JOIN nodesCTE AS P \n" "ON (N.parenthandle = P.nodehandle \n" "AND (P.flags & " + idVerFlag + " = 0) \n" // Versions aren't taken in consideration "AND (" + idSens + " != " + onlyTrueStr + // Sensitive nodes " OR " + idSens + " = " + onlyTrueStr + " AND (P.flags & " + idSensFlag + ") = 0) " "AND P.type != " + filenodeStr + "))"; static const std::string whereClause = "matchFilter("s + idFilter + ", flags, type, ctime, mtime, mimetypeVirtual, name, description, tags, fav)"; static const std::string nodesAfterFilters = "nodesAfterFilters (" + columnsForNodeAndOrderBy + ") \n" "AS (SELECT " + columnsForNodeAndOrderBy + " \n" "FROM nodesOfShares \n" "WHERE " + whereClause + " \n" "UNION ALL \n" "SELECT " + columnsForNodeAndOrderBy + " \n" "FROM nodesCTE \n" "WHERE " + whereClause + // avoid duplicates (should be faster than SELECT DISTINCT, but possibly require more memory) "GROUP BY nodehandle)"; /// recursive query considering ancestors const std::string query = "WITH \n\n" + ancestors + ", \n\n" + nodesOfShares + ", \n\n" + nodesCTE + ", \n\n" + nodesAfterFilters + "\n\n" + "SELECT " + columnsForNodeAndOrderBy + " \n" "FROM nodesAfterFilters GROUP BY nodehandle\n" // Avoid duplicates after union of nodesOfShares and nodesCTE "ORDER BY \n" + OrderByClause::get(order) + " \n" + "LIMIT " + idPageSize + " OFFSET " + idPageOff; // clang-format on sqlResult = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, NULL); } constexpr uint64_t versionFlag = (1 << Node::FLAGS_IS_VERSION); // exclude file versions constexpr uint64_t senstivityFlag = 1 << Node::FLAGS_IS_MARKED_SENSTIVE; // by sensitivity const auto& ancestors = filter.byAncestorHandles(); const sqlite3_int64 pageSize = page.size() ? static_cast<sqlite3_int64>(page.size()) : -1; assert(ancestors.size() >= 3); // support at least 3 ancestors NodeSearchFilter filterCopy = filter; bindValue(sqlResult, stmt, idVerFlag, versionFlag, sqlite3_bind_int64); bindValue(sqlResult, stmt, idIncShares, filter.includedShares(), sqlite3_bind_int); bindText(sqlResult, stmt, idName, filter.byName()); bindValue(sqlResult, stmt, idAncestor1, ancestors[0], sqlite3_bind_int64); bindValue(sqlResult, stmt, idAncestor2, ancestors[1], sqlite3_bind_int64); bindValue(sqlResult, stmt, idAncestor3, ancestors[2], sqlite3_bind_int64); bindValue(sqlResult, stmt, idPageSize, pageSize, sqlite3_bind_int64); bindValue(sqlResult, stmt, idPageOff, page.startingOffset(), sqlite3_bind_int64); bindPointer(sqlResult, stmt, idFilter, &filterCopy, NodeSearchFilterPtrStr); bindValue(sqlResult, stmt, idSens, filter.bySensitivity(), sqlite3_bind_int); bindValue(sqlResult, stmt, idSensFlag, senstivityFlag, sqlite3_bind_int64); const bool result = (sqlResult == SQLITE_OK) && processSqlQueryNodes(stmt, nodes); // unregister the handler (no-op if not registered) sqlite3_progress_handler(db, -1, nullptr, nullptr); errorHandler(sqlResult, "Search nodes with filter", true); sqlite3_reset(stmt); return result; } bool SqliteAccountState::getNodesByFingerprintNoMtime( const std::string& fingerprint, std::vector<std::pair<NodeHandle, NodeSerialized>>& nodes) { if (!db) { return false; } int sqlResult = SQLITE_OK; if (!mStmtNodesByFpNoMtime) { sqlResult = sqlite3_prepare_v2( db, "SELECT nodehandle, counter, node FROM nodes WHERE fingerprintVirtual = ?", -1, &mStmtNodesByFpNoMtime, NULL); } bool result = false; if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_blob(mStmtNodesByFpNoMtime, 1, fingerprint.data(), (int)fingerprint.size(), SQLITE_STATIC)) == SQLITE_OK) { result = processSqlQueryNodes(mStmtNodesByFpNoMtime, nodes); } } if (sqlResult != SQLITE_OK) { errorHandler(sqlResult, "get nodes by getNodesByFingerprintNoMtime", false); } sqlite3_reset(mStmtNodesByFpNoMtime); return result; } bool SqliteAccountState::getNodeByFingerprint(const std::string &fingerprint, mega::NodeSerialized &node, NodeHandle& handle) { if (!db) { return false; } int sqlResult = SQLITE_OK; if (!mStmtNodeByFp) { sqlResult = sqlite3_prepare_v2(db, "SELECT nodehandle, counter, node FROM nodes WHERE fingerprint = ? LIMIT 1", -1, &mStmtNodeByFp, NULL); } bool result = false; if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_blob(mStmtNodeByFp, 1, fingerprint.data(), (int)fingerprint.size(), SQLITE_STATIC)) == SQLITE_OK) { std::vector<std::pair<NodeHandle, NodeSerialized>> nodes; result = processSqlQueryNodes(mStmtNodeByFp, nodes); if (nodes.size()) { node = nodes.begin()->second; handle = nodes.begin()->first; } } } if (sqlResult != SQLITE_OK) { errorHandler(sqlResult, "Get node by fingerprint", false); } sqlite3_reset(mStmtNodeByFp); return result; } bool SqliteAccountState::getRecentNodes(const NodeSearchPage& page, m_time_t since, std::vector<std::pair<NodeHandle, NodeSerialized>>& nodes) { if (!db) { return false; } constexpr uint64_t excludeFlags = (1 << Node::FLAGS_IS_VERSION | 1 << Node::FLAGS_IS_IN_RUBBISH); static const std::string filenode = std::to_string(FILENODE); static const std::string sqlQuery = "SELECT n1.nodehandle, n1.counter, n1.node " "FROM nodes n1 " "WHERE n1.flags & " + std::to_string(excludeFlags) + " = 0 AND n1.ctime >= ?1 AND n1.type = " + filenode + " " "ORDER BY n1.ctime DESC LIMIT ?2 OFFSET ?3"; int sqlResult = SQLITE_OK; if (!mStmtRecents) { sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &mStmtRecents, NULL); } bool stepResult = false; const int64_t nodeCount = page.size() ? static_cast<int64_t>(page.size()) : -1; const int64_t offset = static_cast<int64_t>(page.startingOffset()); if (sqlResult == SQLITE_OK && sqlResult == sqlite3_bind_int64(mStmtRecents, 1, since) && sqlResult == sqlite3_bind_int64(mStmtRecents, 2, nodeCount) && sqlResult == sqlite3_bind_int64(mStmtRecents, 3, offset)) { stepResult = processSqlQueryNodes(mStmtRecents, nodes); } if (sqlResult != SQLITE_OK) { errorHandler(sqlResult, "Get recent nodes", false); } sqlite3_reset(mStmtRecents); return stepResult; } bool SqliteAccountState::getFavouritesHandles(NodeHandle node, uint32_t count, std::vector<mega::NodeHandle> &nodes) { if (!db) { return false; } int sqlResult = SQLITE_OK; if (!mStmtFavourites) { // exclude previous versions <- P.type != FILENODE // this is 1.6x faster than using the flags std::string sqlQuery = "WITH nodesCTE(nodehandle, parenthandle, fav, type) AS (SELECT nodehandle, parenthandle, fav, type " "FROM nodes WHERE parenthandle = ? UNION ALL SELECT N.nodehandle, N.parenthandle, N.fav, N.type " "FROM nodes AS N INNER JOIN nodesCTE AS P ON (N.parenthandle = P.nodehandle AND P.type != " + std::to_string(FILENODE) + ")) SELECT node.nodehandle " "FROM nodesCTE AS node WHERE node.fav = 1"; sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &mStmtFavourites, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtFavourites, 1, static_cast<sqlite3_int64>(node.as8byte()))) == SQLITE_OK) { while ((sqlResult = sqlite3_step(mStmtFavourites)) == SQLITE_ROW && (nodes.size() < count || count == 0)) { nodes.push_back(NodeHandle().set6byte( static_cast<uint64_t>(sqlite3_column_int64(mStmtFavourites, 0)))); } } } if (sqlResult != SQLITE_DONE && sqlResult != SQLITE_ROW) { errorHandler(sqlResult, "Get favourites handles", false); } sqlite3_reset(mStmtFavourites); return sqlResult == SQLITE_DONE || sqlResult == SQLITE_ROW; } bool SqliteAccountState::childNodeByNameType(NodeHandle parentHandle, const std::string& name, nodetype_t nodeType, std::pair<NodeHandle, NodeSerialized> &node) { bool success = false; if (!db) { return success; } std::string sqlQuery = "SELECT nodehandle, counter, node FROM nodes WHERE parenthandle = ? AND name = ? AND type = ? limit 1"; int sqlResult = SQLITE_OK; if (!mStmtChildNode) { sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &mStmtChildNode, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtChildNode, 1, static_cast<sqlite3_int64>(parentHandle.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_text(mStmtChildNode, 2, name.c_str(), static_cast<int>(name.length()), SQLITE_STATIC)) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtChildNode, 3, nodeType)) == SQLITE_OK) { std::vector<std::pair<NodeHandle, NodeSerialized>> nodes; processSqlQueryNodes(mStmtChildNode, nodes); if (nodes.size()) { node.first = nodes.begin()->first; node.second = nodes.begin()->second; success = true; } } } } } if (sqlResult != SQLITE_OK) { errorHandler(sqlResult, "Get nodes by name and type", false); } sqlite3_reset(mStmtChildNode); return success; } bool SqliteAccountState::getNodeSizeTypeAndFlags(NodeHandle node, m_off_t& size, nodetype_t& nodeType, uint64_t& oldFlags) { if (!db) { return false; } int sqlResult = SQLITE_OK; if (!mStmtTypeAndSizeNode) { sqlResult = sqlite3_prepare_v2(db, "SELECT type, sizeVirtual, flags FROM nodes WHERE nodehandle = ?", -1, &mStmtTypeAndSizeNode, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtTypeAndSizeNode, 1, static_cast<sqlite3_int64>(node.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_step(mStmtTypeAndSizeNode)) == SQLITE_ROW) { nodeType = (nodetype_t)sqlite3_column_int(mStmtTypeAndSizeNode, 0); size = sqlite3_column_int64(mStmtTypeAndSizeNode, 1); oldFlags = static_cast<uint64_t>(sqlite3_column_int64(mStmtTypeAndSizeNode, 2)); } } } if (sqlResult != SQLITE_ROW && sqlResult != SQLITE_DONE) { errorHandler(sqlResult, "Get nodes by name, type and flags", false); } sqlite3_reset(mStmtTypeAndSizeNode); return sqlResult == SQLITE_ROW; } bool SqliteAccountState::isAncestor(NodeHandle node, NodeHandle ancestor, CancelToken cancelFlag) { bool result = false; if (!db) { return result; } std::string sqlQuery = "WITH nodesCTE(nodehandle, parenthandle) " "AS (SELECT nodehandle, parenthandle FROM nodes WHERE nodehandle = ? " "UNION ALL SELECT A.nodehandle, A.parenthandle FROM nodes AS A INNER JOIN nodesCTE " "AS E ON (A.nodehandle = E.parenthandle)) " "SELECT * FROM nodesCTE WHERE parenthandle = ?"; if (cancelFlag.exists()) { sqlite3_progress_handler(db, NUM_VIRTUAL_MACHINE_INSTRUCTIONS, SqliteAccountState::progressHandler, static_cast<void*>(&cancelFlag)); } int sqlResult = SQLITE_OK; if (!mStmtIsAncestor) { sqlResult = sqlite3_prepare_v2(db, sqlQuery.c_str(), -1, &mStmtIsAncestor, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtIsAncestor, 1, static_cast<sqlite3_int64>(node.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtIsAncestor, 2, static_cast<sqlite3_int64>(ancestor.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_step(mStmtIsAncestor)) == SQLITE_ROW) { result = true; } } } } // unregister the handler (no-op if not registered) sqlite3_progress_handler(db, -1, nullptr, nullptr); if (sqlResult != SQLITE_ROW && sqlResult != SQLITE_DONE) { errorHandler(sqlResult, "Is ancestor", true); } sqlite3_reset(mStmtIsAncestor); return result; } uint64_t SqliteAccountState::getNumberOfNodes() { uint64_t count = 0; if (!db) { return count; } sqlite3_stmt *stmt = nullptr; int sqlResult = sqlite3_prepare_v2(db, "SELECT count(*) FROM nodes", -1, &stmt, NULL); if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_step(stmt)) == SQLITE_ROW) { count = static_cast<uint64_t>(sqlite3_column_int64(stmt, 0)); } } if (sqlResult != SQLITE_ROW) { errorHandler(sqlResult, "Get number of nodes", false); } sqlite3_finalize(stmt); return count; } uint64_t SqliteAccountState::getNumberOfChildrenByType(NodeHandle parentHandle, nodetype_t nodeType) { uint64_t count = 0; if (!db) { return count; } int sqlResult = SQLITE_OK; if (!mStmtNumChild) { sqlResult = sqlite3_prepare_v2(db, "SELECT count(*) FROM nodes where parenthandle = ? AND type = ?", -1, &mStmtNumChild, NULL); } if (sqlResult == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int64(mStmtNumChild, 1, static_cast<sqlite3_int64>(parentHandle.as8byte()))) == SQLITE_OK) { if ((sqlResult = sqlite3_bind_int(mStmtNumChild, 2, nodeType)) == SQLITE_OK) { if ((sqlResult = sqlite3_step(mStmtNumChild)) == SQLITE_ROW) { count = static_cast<uint64_t>(sqlite3_column_int64(mStmtNumChild, 0)); } } } } if (sqlResult != SQLITE_ROW) { errorHandler(sqlResult, "Get number of children by type", false); } sqlite3_reset(mStmtNumChild); return count; } void SqliteAccountState::userRegexp(sqlite3_context* context, int argc, sqlite3_value** argv) { if (argc != 2) { LOG_err << "Invalid parameters for user Regexp"; assert(false); return; } auto pattern = reinterpret_cast<const char*>(sqlite3_value_text(argv[0])); auto nameFromDataBase = reinterpret_cast<const char*>(sqlite3_value_text(argv[1])); if (nameFromDataBase && pattern) { // C++ standard, true to 1, false to 0 int result = static_cast<int>(likeCompare(pattern, nameFromDataBase, 0)); sqlite3_result_int(context, result); } } void SqliteAccountState::getSizeFromNodeCounter(sqlite3_context* context, int argc, sqlite3_value** argv) { if (argc != 1) { LOG_err << "getSizeFromNodeCounter: Invalid parameters for getSizeFromNodeCounter"; assert(argc == 1); sqlite3_result_int64(context, -1); return; } const auto blob = sqlite3_value_blob(argv[0]); if (!blob) { LOG_err << "getSizeFromNodeCounter: invalid FromNodeCounter blob"; sqlite3_result_int64(context, -1); return; } const auto blobSize = sqlite3_value_bytes(argv[0]); const std::string nodeCounter(static_cast<const char*>(blob), static_cast<size_t>(blobSize)); const NodeCounter nc(nodeCounter); sqlite3_result_int64(context, nc.storage); } void SqliteAccountState::userGetMimetype(sqlite3_context* context, int argc, sqlite3_value** argv) { if (argc != 1) { LOG_err << "Invalid parameters for userGetMimetype"; assert(argc == 1); sqlite3_result_int(context, MimeType_t::MIME_TYPE_UNKNOWN); return; } const char* fileName = reinterpret_cast<const char*>(sqlite3_value_text(argv[0])); string ext; int result = (fileName && *fileName && Node::getExtension(ext, fileName) && !ext.empty()) ? Node::getMimetype(ext) : MimeType_t::MIME_TYPE_OTHERS; sqlite3_result_int(context, result); } void SqliteAccountState::getFingerprintExcludingMtime(sqlite3_context* context, int argc, sqlite3_value** argv) { if (argc != 1) { LOG_err << "Invalid parameters for getFingerprintExcludingMtime (argc=" << argc << ")"; sqlite3_result_null(context); return; } const unsigned char* input = static_cast<const unsigned char*>(sqlite3_value_blob(argv[0])); const int len = sqlite3_value_bytes(argv[0]); if (!input) { sqlite3_result_null(context); return; } if (len < 33) { LOG_err << "getFingerprintExcludingMtime: invalid fingerprint blob size (len=" << len << ")"; assert(false && "getFingerprintExcludingMtime(): invalid fingerprint blob size"); sqlite3_result_null(context); return; } std::array<uint8_t, 25> result; std::copy_n(input, 8, result.begin()); // Copy size std::copy_n(input + 16, 16, result.begin() + 8); // Ignore mtime and copy CRC std::copy_n(input + 32, 1, result.begin() + 24); // Copy isValid sqlite3_result_blob(context, result.data(), static_cast<int>(result.size()), SQLITE_TRANSIENT); } void SqliteAccountState::userMatchFilter(sqlite3_context* context, int argc, sqlite3_value** argv) { bool result = false; const MrProper cleanUp{[&context, &result]() { sqlite3_result_int(context, result); }}; if (argc != 10) { LOG_err << "Invalid parameters for userMatchFilter. Expected (in this order): filter*, " "flags, type, ctime, mtime, mimetypeVirtual, name, description, tags, fav"; assert(false); return; } auto filter = static_cast<const NodeSearchFilter*>( sqlite3_value_pointer(argv[0], NodeSearchFilterPtrStr)); const int64_t flags = sqlite3_value_int64(argv[1]); // Do not include versions if (!filter->includeVersions()) { constexpr int64_t versionFlag = 1 << Node::FLAGS_IS_VERSION; if ((flags & versionFlag) != 0) return; } // type const nodetype_t type = static_cast<nodetype_t>(sqlite3_value_int(argv[2])); if (filter->hasNodeType() && !filter->isValidNodeType(type)) return; // ctime if (filter->hasCreationTimeLimits() && !filter->isValidCreationTime(sqlite3_value_int64(argv[3]))) return; // mtime if (filter->hasModificationTimeLimits() && !filter->isValidModificationTime(sqlite3_value_int64(argv[4]))) return; // mimetype if (filter->hasCategory() && !filter->isValidCategory(static_cast<MimeType_t>(sqlite3_value_int(argv[5])), type)) return; // Fav if (filter->hasFav() && !filter->isValidFav(static_cast<bool>(sqlite3_value_int(argv[9])))) return; // sensitive constexpr int64_t sensitivityFlag = 1 << Node::FLAGS_IS_MARKED_SENSTIVE; if (!filter->isValidSensitivity((flags & sensitivityFlag) == sensitivityFlag)) return; //// This block defines conditions to be combined by OR or AND operations if present in filter // Define a vector with all the conditions to combine std::vector<std::function<bool()>> conditionEvals; if (filter->hasName()) conditionEvals.emplace_back( [&filter, &argv]() { return filter->isValidName(sqlite3_value_text(argv[6])); }); if (filter->hasDescription()) conditionEvals.emplace_back( [&filter, &argv]() { return filter->isValidDescription(sqlite3_value_text(argv[7])); }); if (filter->hasTag()) conditionEvals.emplace_back( [&filter, &argv]() { return filter->isValidTagSequence(sqlite3_value_text(argv[8])); }); // Condition combination if (conditionEvals.empty()) { result = true; } else if (filter->useAndForTextQuery()) { result = std::all_of(std::begin(conditionEvals), std::end(conditionEvals), [](auto&& f) -> bool { return f(); }); } else { result = std::any_of(std::begin(conditionEvals), std::end(conditionEvals), [](auto&& f) -> bool { return f(); }); } } std::string OrderByClause::get(int order) { static const std::string nameSort = "name COLLATE NATURALNOCASE"; static const std::string typeSort = " type DESC"; switch (order) { case DEFAULT_ASC: return typeSort + ", " + nameSort; case DEFAULT_DESC: return typeSort + ", " + nameSort + " DESC"; case SIZE_ASC: return typeSort + ", " + "sizeVirtual, " + nameSort; case SIZE_DESC: return typeSort + ", " + "sizeVirtual DESC, " + nameSort + " DESC"; case CTIME_ASC: return typeSort + ", " + "ctime, " + nameSort; case CTIME_DESC: return typeSort + ", " + "ctime DESC, " + nameSort + " DESC"; case MTIME_ASC: return typeSort + ", " + "mtime, " + nameSort; case MTIME_DESC: return typeSort + ", " + "mtime DESC, " + nameSort + " DESC"; case LABEL_ASC: return "CASE WHEN label = 0 THEN 1 ELSE 0 END ASC, label ASC, " + typeSort + ", " + nameSort; case LABEL_DESC: return "label DESC, " + typeSort + ", " + nameSort; // fav have inverse order case FAV_ASC: return "fav DESC," + typeSort + ", " + nameSort; case FAV_DESC: return "fav, " + typeSort + ", " + nameSort; default: return typeSort + ", " + nameSort; } } size_t OrderByClause::getId(int order) { return static_cast<size_t>(order); } SqliteDbAccess::MTimeType::MTimeType(mega::m_time_t value): mValue(value) {} bool SqliteDbAccess::MTimeType::bindToDb(sqlite3_stmt* stmt, const std::map<int, int>& lookupId) const { if (sqlite3_bind_int64(stmt, lookupId.at(COMPONENT), mValue) != SQLITE_OK) { LOG_err << "Db error during migration while binding mTime value to column: "; sqlite3_finalize(stmt); return false; } return true; } std::unique_ptr<SqliteDbAccess::MigrateType> SqliteDbAccess::MTimeType::fromNodeData(NodeData& nd) { return std::make_unique<MTimeType>(nd.getMtime()); } bool SqliteDbAccess::MTimeType::hasValidValue() const { return mValue != 0; } SqliteDbAccess::LabelType::LabelType(int value): mValue(value) {} bool SqliteDbAccess::LabelType::bindToDb(sqlite3_stmt* stmt, const std::map<int, int>& lookupId) const { if (sqlite3_bind_int64(stmt, lookupId.at(COMPONENT), mValue) != SQLITE_OK) { LOG_err << "Db error during migration while binding label value to column: "; sqlite3_finalize(stmt); return false; } return true; } std::unique_ptr<SqliteDbAccess::MigrateType> SqliteDbAccess::LabelType::fromNodeData(NodeData& nd) { return std::make_unique<LabelType>(nd.getLabel()); } bool SqliteDbAccess::LabelType::hasValidValue() const { return mValue != LBL_UNKNOWN; } SqliteDbAccess::DescriptionType::DescriptionType(const string& value): mValue(value) {} bool SqliteDbAccess::DescriptionType::bindToDb(sqlite3_stmt* stmt, const std::map<int, int>& lookupId) const { if (mValue.size()) { if (sqlite3_bind_text(stmt, lookupId.at(COMPONENT), mValue.c_str(), static_cast<int>(mValue.length()), SQLITE_STATIC) != SQLITE_OK) { LOG_err << "Db error during migration while binding description value to column: "; sqlite3_finalize(stmt); return false; } } else { sqlite3_bind_null(stmt, lookupId.at(COMPONENT)); } return true; } std::unique_ptr<SqliteDbAccess::MigrateType> SqliteDbAccess::DescriptionType::fromNodeData(NodeData& nd) { return std::make_unique<DescriptionType>(nd.getDescription()); } bool SqliteDbAccess::DescriptionType::hasValidValue() const { return mValue.size() > 0; } SqliteDbAccess::TagsType::TagsType(const string& value): mValue(value) {} bool SqliteDbAccess::TagsType::bindToDb(sqlite3_stmt* stmt, const std::map<int, int>& lookupId) const { if (mValue.size()) { if (sqlite3_bind_text(stmt, lookupId.at(COMPONENT), mValue.c_str(), static_cast<int>(mValue.length()), SQLITE_STATIC) != SQLITE_OK) { LOG_err << "Db error during migration while binding tags value to column: "; sqlite3_finalize(stmt); return false; } } else { sqlite3_bind_null(stmt, lookupId.at(COMPONENT)); } return true; } std::unique_ptr<SqliteDbAccess::MigrateType> SqliteDbAccess::TagsType::fromNodeData(NodeData& nd) { return std::make_unique<TagsType>(nd.getTags());; } bool SqliteDbAccess::TagsType::hasValidValue() const { return mValue.size() > 0; } } // namespace #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/dns_lookup_pseudomessage.cpp��������������������������������������������������������0000664�0000000�0000000�00000005353�15162662266�0021051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/dns_lookup_pseudomessage.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include <winsock2.h> #else #include <arpa/inet.h> #endif #include <array> #include <sstream> namespace mega { namespace dns_lookup_pseudomessage { enum DnsType : uint16_t { IPV4 = 0x01, // a.k.a. DNS type A (1) IPV6 = 0x1C, // a.k.a. DNS type AAAA (28) }; /** * @brief Build a string like a DNS lookup message, considering network byte order * * @param userId user id to be used for building the pseudo-domain * @param messageId id to be set to the DNS lookup message * @param dnsType specify what type of IP to resolve the name to * * @return a string like a DNS lookup message */ static std::string get(uint64_t userId, uint16_t messageId, DnsType dnsType) { // DNS message header static const std::array<uint16_t, 6> header{ htons(messageId), // random id htons(256), // flags: standard query, recursion desired htons(1), // question count: 1 since it's a query 0, // answer count: 0 since it's a query 0, // authority record count 0}; // additional record count const std::array<uint16_t, 2> dnsOptions{htons(dnsType), // DNS type htons(1)}; // additional record count // Build message std::string hexId{userIdToHex(userId)}; std::stringstream m; m.write(reinterpret_cast<const char*>(header.data()), header.size() * sizeof(decltype(header)::value_type)); m << char{static_cast<char>(hexId.size())} << hexId << char{4} << "test" << char{4} << "mega" << char{2} << "nz" << '\0'; m.write(reinterpret_cast<const char*>(dnsOptions.data()), dnsOptions.size() * sizeof(decltype(dnsOptions)::value_type)); return m.str(); } std::string getForIPv4(uint64_t userId, uint16_t messageId) { return get(userId, messageId, DnsType::IPV4); } std::string getForIPv6(uint64_t userId, uint16_t messageId) { return get(userId, messageId, DnsType::IPV6); } } // namespace dns_lookup_pseudomessage std::string userIdToHex(uint64_t userId) { std::stringstream s; s << std::hex << userId; return s.str(); } } // namespace mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/drivenotify.cpp���������������������������������������������������������������������0000664�0000000�0000000�00000004665�15162662266�0016317�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file drivenotify.cpp * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_DRIVE_NOTIFICATIONS #include "mega/drivenotify.h" #include <assert.h> using namespace std; namespace mega { bool DriveNotify::start(function<void()> notify) { lock_guard<mutex> lock(mSyncAccessMutex); // start the notifier bool started = startNotifier(); if (started) { mNotifyOnInfo = notify; } return started; } void DriveNotify::stop() { stopNotifier(); decltype(mInfoQueue) temp; mInfoQueue.swap(temp); // clear the container } bool DriveNotify::startNotifier() { if (mEventSinkThread.joinable() || mStop.load()) return false; if (!notifierSetup()) return false; mEventSinkThread = thread(&DriveNotify::doInThread, this); return true; } void DriveNotify::stopNotifier() { if (!mEventSinkThread.joinable()) return; mStop.store(true); mEventSinkThread.join(); mStop.store(false); } std::pair<DriveInfo::StringType, bool> DriveNotify::get() { // sync access lock_guard<mutex> lock(mSyncAccessMutex); // no entry, return invalid data if (mInfoQueue.empty()) return pair<DriveInfo::StringType, bool>(); // get the oldest entry const DriveInfo& drive = mInfoQueue.front(); pair<DriveInfo::StringType, bool> info(std::move(drive.mountPoint), drive.connected); mInfoQueue.pop(); return info; } DriveNotify::~DriveNotify() { // thread, if running, should have been stopped by the derived class assert(!shouldStop() && !enabled()); } void DriveNotify::add(DriveInfo&& info) { // sync access { lock_guard<mutex> lock(mSyncAccessMutex); // save the new info mInfoQueue.emplace(std::move(info)); } // notify that new info was received mNotifyOnInfo(); } } // namespace mega #endif // USE_DRIVE_NOTIFICATIONS ���������������������������������������������������������������������������sdk-10.11.0/src/file.cpp����������������������������������������������������������������������������0000664�0000000�0000000�00000107076�15162662266�0014674�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file file.cpp * @brief Classes for transferring files * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/file.h" #include "mega/command.h" #include "mega/heartbeats.h" #include "mega/logging.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/sync.h" #include "mega/transfer.h" #include "mega/transferslot.h" namespace mega { mutex File::localname_mutex; File::File() :mCollisionResolution(CollisionResolution::RenameNewWithN) { transfer = NULL; chatauth = NULL; hprivate = true; hforeign = false; syncxfer = false; fromInsycShare = false; temporaryfile = false; tag = 0; } File::~File() { // if transfer currently running, stop if (transfer) { transfer->client->stopxfer(this, nullptr); } delete [] chatauth; } LocalPath File::getLocalname() const { lock_guard<mutex> g(localname_mutex); return localname_multithreaded; } void File::setLocalname(const LocalPath& ln) { lock_guard<mutex> g(localname_mutex); localname_multithreaded = ln; } bool File::serialize(string *d) const { char type = char(transfer->type); d->append((const char*)&type, sizeof(type)); if (!FileFingerprint::serialize(d)) { LOG_err << "Error serializing File: Unable to serialize FileFingerprint"; return false; } unsigned short ll; bool flag; ll = (unsigned short)name.size(); d->append((char*)&ll, sizeof(ll)); d->append(name.data(), ll); auto tmpstr = getLocalname().serialize(); ll = (unsigned short)tmpstr.size(); d->append((char*)&ll, sizeof(ll)); d->append(tmpstr.data(), ll); ll = (unsigned short)targetuser.size(); d->append((char*)&ll, sizeof(ll)); d->append(targetuser.data(), ll); ll = (unsigned short)privauth.size(); d->append((char*)&ll, sizeof(ll)); d->append(privauth.data(), ll); ll = (unsigned short)pubauth.size(); d->append((char*)&ll, sizeof(ll)); d->append(pubauth.data(), ll); d->append((const char*)&h, sizeof(h)); d->append((const char*)filekey, sizeof(filekey)); flag = hprivate; d->append((const char*)&flag, sizeof(flag)); flag = hforeign; d->append((const char*)&flag, sizeof(flag)); flag = syncxfer; d->append((const char*)&flag, sizeof(flag)); flag = temporaryfile; d->append((const char*)&flag, sizeof(flag)); char hasChatAuth = (chatauth && chatauth[0]) ? 1 : 0; d->append((char *)&hasChatAuth, 1); d->append((char*)&mCollisionResolution, 1); bool pathSerializedAsLocalPath = true; d->append(reinterpret_cast<const char*>(&pathSerializedAsLocalPath), sizeof(bool)); bool hasPitag = mPitag.has_value(); d->append(reinterpret_cast<const char*>(&hasPitag), sizeof(bool)); d->append("\0\0\0\0\0", 6); if (hasChatAuth) { ll = (unsigned short) strlen(chatauth); d->append((char*)&ll, sizeof(ll)); d->append(chatauth, ll); } if (hasPitag) { const auto pitagStr = pitagToString(*mPitag); ll = static_cast<unsigned short>(pitagStr.size()); d->append((char*)&ll, sizeof(ll)); d->append(pitagStr.data(), ll); } return true; } File *File::unserialize(string *d) { if (!d->size()) { LOG_err << "Error unserializing File: Empty string"; return NULL; } d->erase(0, 1); const char* ptr = d->data(); const char* end = ptr + d->size(); auto fp = FileFingerprint::unserialize(ptr, end); if (!fp) { LOG_err << "Error unserializing File: Unable to unserialize FileFingerprint"; return NULL; } if (ptr + sizeof(unsigned short) > end) { LOG_err << "File unserialization failed - serialized string too short"; return NULL; } // read name unsigned short namelen = MemAccess::get<unsigned short>(ptr); if (ptr + namelen + sizeof(unsigned short) > end) { LOG_err << "File unserialization failed - name too long"; return NULL; } ptr += sizeof(namelen); const char *name = ptr; ptr += namelen; // read localname unsigned short localnamelen = MemAccess::get<unsigned short>(ptr); if (ptr + localnamelen + sizeof(unsigned short) > end) { LOG_err << "File unserialization failed - localname too long"; return NULL; } ptr += sizeof(localnamelen); const char *localname = ptr; ptr += localnamelen; // read targetuser unsigned short targetuserlen = MemAccess::get<unsigned short>(ptr); if (ptr + targetuserlen + sizeof(unsigned short) > end) { LOG_err << "File unserialization failed - targetuser too long"; return NULL; } ptr += sizeof(targetuserlen); const char *targetuser = ptr; ptr += targetuserlen; // read private auth unsigned short privauthlen = MemAccess::get<unsigned short>(ptr); if (ptr + privauthlen + sizeof(unsigned short) > end) { LOG_err << "File unserialization failed - private auth too long"; return NULL; } ptr += sizeof(privauthlen); const char *privauth = ptr; ptr += privauthlen; unsigned short pubauthlen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(pubauthlen); if (ptr + pubauthlen + sizeof(handle) + FILENODEKEYLENGTH + sizeof(bool) //hprivate + sizeof(bool) //hforeign + sizeof(bool) //syncxfer + sizeof(bool) //temporaryfile + sizeof(char) //hasChatAuth + sizeof(uint8_t) //collisionResolution + 8 //8 '0' > end) { LOG_err << "File unserialization failed - public auth too long"; return NULL; } const char *pubauth = ptr; ptr += pubauthlen; File *file = new File(); *(FileFingerprint *)file = *fp; fp.reset(); file->name.assign(name, namelen); file->targetuser.assign(targetuser, targetuserlen); file->privauth.assign(privauth, privauthlen); file->pubauth.assign(pubauth, pubauthlen); file->h.set6byte(MemAccess::get<handle>(ptr)); ptr += sizeof(handle); memcpy(file->filekey, ptr, FILENODEKEYLENGTH); ptr += FILENODEKEYLENGTH; file->hprivate = MemAccess::get<bool>(ptr); ptr += sizeof(bool); file->hforeign = MemAccess::get<bool>(ptr); ptr += sizeof(bool); file->syncxfer = MemAccess::get<bool>(ptr); ptr += sizeof(bool); file->temporaryfile = MemAccess::get<bool>(ptr); ptr += sizeof(bool); char hasChatAuth = MemAccess::get<char>(ptr); ptr += sizeof(char); uint8_t collisionResolutionUint8 = MemAccess::get<uint8_t>(ptr); ptr += sizeof(uint8_t); if (collisionResolutionUint8 < static_cast<uint8_t>(CollisionResolution::Begin) || collisionResolutionUint8 >= static_cast<uint8_t>(CollisionResolution::End)) { LOG_err << "File unserialization failed - collision resolution " << collisionResolutionUint8 << " not valid"; delete file; return NULL; } file->setCollisionResolution(static_cast<CollisionResolution>(collisionResolutionUint8)); bool pathSerializedAsLocalPath = MemAccess::get<bool>(ptr); ptr += sizeof(bool); if (pathSerializedAsLocalPath) { auto localPath = LocalPath::unserialize(std::string(localname, localnamelen)); if (!localPath.has_value()) { LOG_err << "File unserialization failed - invalid LocalPath"; delete file; return NULL; } file->setLocalname(localPath.value()); } else { file->setLocalname( LocalPath::fromPlatformEncodedAbsolute(std::string(localname, localnamelen))); } char hasPitag = MemAccess::get<char>(ptr); ptr += sizeof(char); if (memcmp(ptr, "\0\0\0\0\0", 6)) { LOG_err << "File unserialization failed - invalid version"; delete file; return NULL; } ptr += 6; if (hasChatAuth) { if (ptr + sizeof(unsigned short) <= end) { unsigned short chatauthlen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(chatauthlen); if (!chatauthlen || ptr + chatauthlen > end) { LOG_err << "File unserialization failed - incorrect size of chat auth"; delete file; return NULL; } file->chatauth = new char[chatauthlen + 1]; memcpy(file->chatauth, ptr, chatauthlen); file->chatauth[chatauthlen] = '\0'; ptr += chatauthlen; } else { LOG_err << "File unserialization failed - chat auth not found"; delete file; return NULL; } } if (hasPitag) { unsigned short pitagLength = MemAccess::get<unsigned short>(ptr); if (ptr + sizeof(unsigned short) + pitagLength > end) { LOG_err << "File unserialization failed - pitag too long"; return nullptr; } ptr += sizeof(pitagLength); const char* pitag = ptr; auto pitagOpt{pitagFromString(std::string(pitag, pitagLength))}; if (!pitagOpt.has_value()) { LOG_err << "File unserialization failed - incorrect pitag"; return nullptr; } file->setPitag(pitagOpt.value()); ptr += pitagLength; } d->erase(0, static_cast<size_t>(ptr - d->data())); return file; } bool File::isFuseTransfer() const { return false; } void File::logicalPath(LocalPath logicalPath) { std::lock_guard<std::mutex> guard(localname_mutex); mLogicalPath = std::move(logicalPath); } LocalPath File::logicalPath() const { std::lock_guard<std::mutex> guard(localname_mutex); if (mLogicalPath.empty()) return localname_multithreaded; return mLogicalPath; } void File::prepare(FileSystemAccess&) { transfer->localfilename = getLocalname(); assert(transfer->localfilename.isAbsolute()); } void File::start() { } void File::progress() { } void File::completed(Transfer* t, putsource_t source) { assert(!transfer || t == transfer); assert(source == PUTNODES_APP); // derived class for sync doesn't use this code path if (t->type == PUT) { sendPutnodesOfUpload(t->client, t->uploadhandle, "", *t->ultoken, t->filekey, source, NodeHandle(), nullptr, nullptr, false); } } void File::sendPutnodesOfUpload(MegaClient* client, UploadHandle fileAttrMatchHandle, std::string&& fileAttr, const UploadToken& ultoken, const FileNodeKey& newFileKey, putsource_t source, NodeHandle ovHandle, CommandPutNodes::Completion&& completion, const m_time_t* overrideMtime, bool canChangeVault, std::optional<Pitag> pitag) { vector<NewNode> newnodes(1); NewNode* newnode = &newnodes[0]; // build new node newnode->source = NEW_UPLOAD; newnode->canChangeVault = canChangeVault; // upload handle required to retrieve/include pending file attributes // or file attribute value if it is not empty newnode->uploadhandle = fileAttrMatchHandle; newnode->fileattributes = std::move(fileAttr); // reference to uploaded file newnode->uploadtoken = ultoken; // file's crypto key static_assert(sizeof(newFileKey) == FILENODEKEYLENGTH, "File completed: filekey size doesn't match with FILENODEKEYLENGTH"); newnode->nodekey.assign((char*)&newFileKey, FILENODEKEYLENGTH); newnode->type = FILENODE; newnode->parenthandle = UNDEF; AttrMap attrs; MegaClient::honorPreviousVersionAttrs(previousNode.get(), attrs); // store filename attrs.map['n'] = name; // store fingerprint auto oldMtime = mtime; if (overrideMtime) mtime = *overrideMtime; serializefingerprint(&attrs.map['c']); if (overrideMtime) mtime = oldMtime; string tattrstring; attrs.getjson(&tattrstring); newnode->attrstring.reset(new string); MegaClient::makeattr( client->getRecycledTemporaryTransferCipher(newFileKey.bytes.data(), FILENODE), newnode->attrstring, tattrstring.c_str()); if (targetuser.size()) { // drop file into targetuser's inbox (obsolete feature, kept for sending logs to helpdesk) client->putnodes(targetuser.c_str(), std::move(newnodes), tag, std::move(completion)); } else { NodeHandle th = h; std::shared_ptr<Node> parentNode = client->nodeByHandle(th); if (syncxfer) { newnode->ovhandle = ovHandle; } else if (mVersioningOption != NoVersioning) // todo: resolve clash with mVersioningOption vs fixNameConflicts { // for manual upload, let the API apply the `ov` according to the global versions_disabled flag. // with versions on, the API will make the ov the first version of this new node // with versions off, the API will permanently delete `ov`, replacing it with this (and attaching the ov's old versions) if (auto ovNode = client->getovnode(parentNode.get(), &name)) { newnode->ovhandle = ovNode->nodeHandle(); } } if (pitag.has_value() && pitag->target == PitagTarget::NotApplicable) { const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); pitag->target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } client->queueCommand(new CommandPutNodes(client, th, NULL, mVersioningOption, std::move(newnodes), tag, source, nullptr, std::move(completion), canChangeVault, {}, // customerIpPort pitag)); } } void File::sendPutnodesToCloneNode(MegaClient* client, Node* nodeToClone, putsource_t source, NodeHandle ovHandle, CommandPutNodes::Completion&& completion, bool canChangeVault, Pitag pitag) { vector<NewNode> newnodes(1); NewNode* newnode = &newnodes[0]; // build new node newnode->source = NEW_NODE; newnode->canChangeVault = canChangeVault; newnode->nodehandle = nodeToClone->nodehandle; // file's crypto key newnode->nodekey = nodeToClone->nodekey(); assert(newnode->nodekey.size() == FILENODEKEYLENGTH); // copy attrs AttrMap tmpAttrs; tmpAttrs.map = nodeToClone->attrs.map; // Serialize fsNode fp, overriding nodeToClone’s attrs. serializefingerprint(&tmpAttrs.map['c']); attr_map::iterator it = tmpAttrs.map.find(AttrMap::string2nameid("rr")); if (it != tmpAttrs.map.end()) { LOG_debug << "Removing rr attribute for clone"; tmpAttrs.map.erase(it); } newnode->type = FILENODE; newnode->parenthandle = UNDEF; // store filename tmpAttrs.map['n'] = name; string tattrstring; tmpAttrs.getjson(&tattrstring); newnode->attrstring.reset(new string); MegaClient::makeattr(client->getRecycledTemporaryTransferCipher((byte*)newnode->nodekey.data(), FILENODE), newnode->attrstring, tattrstring.c_str()); if (targetuser.size()) { // drop file into targetuser's inbox (obsolete feature, kept for sending logs to helpdesk) client->putnodes(targetuser.c_str(), std::move(newnodes), tag, std::move(completion)); } else { NodeHandle th = h; assert(syncxfer); newnode->ovhandle = ovHandle; if (pitag.target == PitagTarget::NotApplicable) { const std::shared_ptr<Node> parentNode = client->nodeByHandle(th); const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); pitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } client->queueCommand(new CommandPutNodes(client, th, NULL, UseLocalVersioningFlag, std::move(newnodes), tag, source, nullptr, std::move(completion), canChangeVault, {}, // customerIpPort pitag)); } } void File::terminated(error) { } // do not retry crypto errors or administrative takedowns bool File::failed(error e, MegaClient*) { if (e == API_EKEY) { return false; // mac error; do not retry } return // Non fatal errors, up to FILE_MAX_RETRIES retries ((e != API_EBLOCKED && e != API_ENOENT && e != API_EINTERNAL && e != API_EACCESS && e != API_ETOOMANY && transfer->failcount < FILE_MAX_RETRIES) // I/O errors up to FILE_IO_MAX_RETRIES retries && !((e == API_EREAD || e == API_EWRITE) && transfer->failcount > FILE_IO_MAX_RETRIES)) // Retry sync transfers up to FILE_SYNC_MAX_RETRIES times for erros that doesn't have a // specific management to prevent immediate retries triggered by the sync engine || (syncxfer && e != API_EBLOCKED && e != API_EKEY && transfer->failcount <= FILE_SYNC_MAX_RETRIES) // Infinite retries for storage overquota errors || e == API_EOVERQUOTA || e == API_EGOINGOVERQUOTA; } void File::displayname(string* dname) { if (name.size()) { *dname = name; } else { shared_ptr<Node> n; if ((n = transfer->client->nodeByHandle(h))) { *dname = n->displayname(); } else { *dname = "DELETED/UNAVAILABLE"; } } } string File::displayname() { string result; displayname(&result); return result; } #ifdef ENABLE_SYNC void SyncTransfer_inClient::terminated(error e) { mError = e; File::terminated(e); if (e == API_EOVERQUOTA) { syncThreadSafeState->client()->syncs.disableSyncByBackupId(syncThreadSafeState->backupId(), FOREIGN_TARGET_OVERSTORAGE, false, true, nullptr); } // We shouldn't call terminated twice but, if we do, all the calls must be done before notifying // the apps assert(!wasTerminated || !terminatedReasonAlreadyKnown); wasTerminated = true; selfKeepAlive.reset(); // deletes this object! (if abandoned by sync) } void SyncTransfer_inClient::completed(Transfer*, [[maybe_unused]] putsource_t source) { assert(source == PUTNODES_SYNC); // do not allow the base class to submit putnodes immediately //File::completed(t, source); assert(!wasFileTransferCompleted); wasFileTransferCompleted = true; selfKeepAlive.reset(); // deletes this object! (if abandoned by sync) } void SyncUpload_inClient::completed(Transfer* t, putsource_t source) { // Keep the info required for putnodes and wait for // the sync thread to validate and activate the putnodes uploadHandle = t->uploadhandle; uploadToken = *t->ultoken; fileNodeKey = t->filekey; if (auto c = t->client) c->pendingattrstring(uploadHandle, &fileAttr); SyncTransfer_inClient::completed(t, source); } void SyncUpload_inClient::fullUpload(MegaClient& client, TransferDbCommitter& committer, const VersioningOption vo, const bool queueFirst) { // Reset flags that signal transfer stage status wasFileTransferCompleted = false; upsyncStarted = false; tag = client.nextreqtag(); selfKeepAlive = shared_from_this(); client.startxfer(PUT, this, committer, false, queueFirst, false, vo, nullptr, tag); } void SyncUpload_inClient::cloneNode(MegaClient& client, std::shared_ptr<Node> cloneNodeCandidate, const NodeHandle ovHandleIfShortcut) { // We have found a candidate node to clone with a valid key, call putNodesToCloneNode. const auto displayPath = cloneNodeCandidate->displaypath(); LOG_debug << "Cloning node rather than sync uploading: " << displayPath << " for " << sourceLocalname; // completion function is supplied to putNodes command sendPutnodesToCloneNode(&client, ovHandleIfShortcut, cloneNodeCandidate.get(), buildSyncClonePitag()); // Set `true` even though no actual data transfer occurred, we're sending putnodes to clone // node instead wasFileTransferCompleted = true; upsyncStarted = true; } bool SyncUpload_inClient::updateNodeMtime(MegaClient* client, std::shared_ptr<Node> node, const m_time_t newMtime, std::function<void(NodeHandle, Error)>&& completion) { return client->updateNodeMtime(node, newMtime, std::move(completion)); } Pitag SyncUpload_inClient::buildSyncUploadPitag() const { Pitag pitag{syncThreadSafeState->mCanChangeVault ? PitagPurpose::Backup : PitagPurpose::Sync, PitagTrigger::SyncAlgorithm, PitagNodeType::File, PitagTarget::NotApplicable, PitagImportSource::NotApplicable}; return pitag; } Pitag SyncUpload_inClient::buildSyncClonePitag() const { Pitag pitag{PitagPurpose::CopyInternal, PitagTrigger::SyncAlgorithm, PitagNodeType::File, PitagTarget::NotApplicable, PitagImportSource::NotApplicable}; return pitag; } void SyncUpload_inClient::sendPutnodesOfUpload(MegaClient* client, NodeHandle ovHandle) { // Always called from the client thread weak_ptr<SyncThreadsafeState> stts = syncThreadSafeState; // So we know whether it's safe to update putnodesCompleted. weak_ptr<SyncUpload_inClient> self = shared_from_this(); // since we are now sending putnodes, no need to remember puts to inform the client on abandonment syncThreadSafeState->client()->transferBackstop.forget(tag); if (sourceLocalname.toPath(false) == ".gitignore") { ovHandle.isUndef() ? client->sendevent(99493, "New .gitignore file synced up") : client->sendevent(99494, "Existing .gitignore file modified"); } File::sendPutnodesOfUpload( client, uploadHandle, std::move(fileAttr), uploadToken, fileNodeKey, PUTNODES_SYNC, ovHandle, [self, stts, client](const Error& e, targettype_t t, vector<NewNode>& nn, bool targetOverride, int ownTag, const map<string, string>& fileHandles) { // Is the originating transfer still alive? if (auto s = self.lock()) { // Then track the result of its putnodes request. s->upsyncFailed = e != API_OK; // Capture the handle if the putnodes was successful. if (!s->upsyncFailed) { assert(!nn.empty()); s->upsyncResultHandle.set6byte(nn.front().mAddedHandle); } // Let the engine know the putnodes has completed. s->wasUpsyncCompleted.store(true); } if (auto s = stts.lock()) { if (e == API_EACCESS) { client->sendevent(99402, "API_EACCESS putting node in sync transfer", 0); } else if (e == API_EOVERQUOTA) { client->syncs.disableSyncByBackupId(s->backupId(), FOREIGN_TARGET_OVERSTORAGE, false, true, nullptr); } } // since we used a completion function, putnodes_result is not called. // but the intermediate layer still needs that in order to call the client app back: client->app->putnodes_result(e, t, nn, targetOverride, ownTag, fileHandles); }, nullptr, syncThreadSafeState->mCanChangeVault, buildSyncUploadPitag()); } void SyncUpload_inClient::sendPutnodesToCloneNode(MegaClient* client, NodeHandle ovHandle, Node* nodeToClone, Pitag pitag) { // Always called from the client thread weak_ptr<SyncThreadsafeState> stts = syncThreadSafeState; if (sourceLocalname.toPath(false) == ".gitignore") { ovHandle.isUndef() ? client->sendevent(99493, "New .gitignore file synced up") : client->sendevent(99494, "Existing .gitignore file modified"); } // So we know whether it's safe to update putnodesCompleted. weak_ptr<SyncUpload_inClient> self = shared_from_this(); File::sendPutnodesToCloneNode( client, nodeToClone, PUTNODES_SYNC, ovHandle, [self, stts, client](const Error& e, targettype_t /*t*/, vector<NewNode>& nn, bool /*targetOverride*/, int /*tag*/, const map<string, string>& /*fileHandles*/) { // Is the originating transfer still alive? if (auto s = self.lock()) { // Then track the result of its putnodes request. s->upsyncFailed = e != API_OK; // Capture the handle if the putnodes was successful. if (!s->upsyncFailed) { assert(!nn.empty()); s->upsyncResultHandle.set6byte(nn.front().mAddedHandle); } // Let the engine know the putnodes has completed. s->wasUpsyncCompleted.store(true); } if (auto s = stts.lock()) { if (e == API_EACCESS) { client->sendevent(99402, "API_EACCESS putting node in sync transfer", 0); } else if (e == API_EOVERQUOTA) { client->syncs.disableSyncByBackupId(s->backupId(), FOREIGN_TARGET_OVERSTORAGE, false, true, nullptr); } } }, syncThreadSafeState->mCanChangeVault, pitag); } SyncUpload_inClient::SyncUpload_inClient(NodeHandle targetFolder, const LocalPath& fullPath, const string& nodeName, const FileFingerprint& ff, shared_ptr<SyncThreadsafeState> stss, handle fsid, const LocalPath& localname, bool fromInshare, const int64_t metamac, const AttributeOnlyUpdate attributeOnlyUpdate) { *static_cast<FileFingerprint*>(this) = ff; // normalized name (UTF-8 with unescaped special chars) // todo: we did unescape them though? name = nodeName; // setting the full path means it works like a normal non-sync transfer setLocalname(fullPath); h = targetFolder; hprivate = false; hforeign = false; syncxfer = true; fromInsycShare = fromInshare; temporaryfile = false; chatauth = nullptr; transfer = nullptr; tag = 0; syncThreadSafeState = std::move(stss); syncThreadSafeState->transferBegin(PUT, size); sourceFsid = fsid; sourceLocalname = localname; this->attributeOnlyUpdate = attributeOnlyUpdate; if (metamac != INVALID_META_MAC) { mMetaMac.emplace(metamac); } LOG_debug << "[SyncUpload_inClient()] Name: '" << getLocalname() << "'. Source local name: '" << sourceLocalname.toPath(false) << "'. Source fsid: " << fsid << ". Fingerprint: " << fingerprintDebugString(); } SyncUpload_inClient::~SyncUpload_inClient() { if (!wasTerminated && !wasFileTransferCompleted) { assert(wasRequesterAbandoned); transfer = nullptr; // don't try to remove File from Transfer from the wrong thread } if (wasFileTransferCompleted && wasUpsyncCompleted) { syncThreadSafeState->transferComplete(PUT, size); } else { syncThreadSafeState->transferFailed(PUT, size); } if (upsyncStarted) { syncThreadSafeState->removeExpectedUpload(h, name); } LOG_debug << "[~SyncUpload_inClient()] Name: '" << getLocalname() << "'. Source local name: '" << sourceLocalname.toPath(false) << "'. Source fsid: " << sourceFsid << ". Fingerprint: " << fingerprintDebugString(); } void SyncUpload_inClient::prepare(FileSystemAccess&) { transfer->localfilename = getLocalname(); // is this transfer in progress? update file's filename. if (transfer->slot && transfer->slot->fa && !transfer->slot->fa->nonblocking_localname.empty()) { transfer->slot->fa->updatelocalname(transfer->localfilename, false); } //todo: localNode.treestate(TREESTATE_SYNCING); } void SyncUpload_inClient::updateFingerprintMtime(const m_time_t newMtime) { if (wasStarted) { assert(false && "Trying to update fingerprint with the upload alredy started"); return; } LOG_debug << "[SyncUpload_inClient::updateFingerprintMtime] Name: '" << getLocalname() << "'. Source fsid: " << sourceFsid << ". Prev mTime: " << mtime << ". New mTime: " << newMtime; mtime = newMtime; } void SyncUpload_inClient::updateFingerprint(const FileFingerprint& newFingerprint) { if (wasStarted) { assert(false && "Trying to update fingerprint with the upload alredy started"); return; } if (size != newFingerprint.size) { // Reset transfer tracking values syncThreadSafeState->transferFailed(PUT, size); syncThreadSafeState->transferBegin(PUT, newFingerprint.size); } LOG_debug << "[SyncUpload_inClient::updateFingerprint] Name: '" << getLocalname() << "'. Source fsid: " << sourceFsid << ". Prev Fingerprint: " << fingerprintDebugString() << ". New Fingerprint: " << newFingerprint.fingerprintDebugString(); FileFingerprint::operator=(newFingerprint); } SyncDownload_inClient::SyncDownload_inClient(CloudNode& n, const LocalPath& clocalname, bool fromInshare, shared_ptr<SyncThreadsafeState> stss, const FileFingerprint& overwriteFF, const int64_t metamac, const AttributeOnlyUpdate attributeOnlyUpdate) { h = n.handle; *(FileFingerprint*)this = n.fingerprint; okToOverwriteFF = overwriteFF; syncxfer = true; fromInsycShare = fromInshare; setLocalname(clocalname); syncThreadSafeState = std::move(stss); syncThreadSafeState->transferBegin(GET, size); this->attributeOnlyUpdate = attributeOnlyUpdate; mMetaMac = metamac; LOG_debug << "[SyncDownload_inClient()] Name: '" << getLocalname() << "'. Handle: " << h << ". Cloud Fingerprint: " << fingerprintDebugString() << ". Local Fingerprint (overwrite): " << overwriteFF.fingerprintDebugString(); } SyncDownload_inClient::~SyncDownload_inClient() { if (!wasTerminated && !wasFileTransferCompleted) { assert(wasRequesterAbandoned); transfer = nullptr; // don't try to remove File from Transfer from the wrong thread } if (!wasDistributed && downloadDistributor) downloadDistributor->removeTarget(); if (wasFileTransferCompleted) { syncThreadSafeState->transferComplete(GET, size); } else { syncThreadSafeState->transferFailed(GET, size); } LOG_debug << "[~SyncDownload_inClient()] Name: '" << getLocalname() << "'. Handle: " << h << ". Cloud Fingerprint: " << fingerprintDebugString() << ". Local Fingerprint (overwrite): " << okToOverwriteFF.fingerprintDebugString(); } void SyncDownload_inClient::prepare(FileSystemAccess&) { if (transfer->localfilename.empty()) { // set unique filename in sync-specific temp download directory transfer->localfilename = syncThreadSafeState->syncTmpFolder(); transfer->localfilename.appendWithSeparator(LocalPath::tmpNameLocal(), true); } } bool SyncDownload_inClient::failed(error e, MegaClient* mc) { // Squirrel away the error for later use. mError = e; // Should we retry the download? if (File::failed(e, mc)) return true; // MAC validation error? if (e == API_EKEY) mc->sendevent(99433, "Undecryptable file", 0); return false; } #endif } // namespace ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015675�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/buffer.cpp�������������������������������������������������������������0000664�0000000�0000000�00000000263�15162662266�0017653�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/buffer.h> namespace mega { namespace file_service { bool Buffer::isMemoryBuffer() const { return !isFileBuffer(); } } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/database_builder.cpp���������������������������������������������������0000664�0000000�0000000�00000011612�15162662266�0021654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/query.h> #include <mega/common/utility.h> #include <mega/file_service/database_builder.h> namespace mega { namespace file_service { using namespace common; static void downgrade10(Query& query); static void downgrade21(Query& query); static void upgrade01(Query& query); static void upgrade12(Query& query); const DatabaseVersionVector& DatabaseBuilder::versions() const { static const DatabaseVersionVector versions = {{&downgrade10, &upgrade01}, {&downgrade21, &upgrade12}}; // versions return versions; } DatabaseBuilder::DatabaseBuilder(Database& database): common::DatabaseBuilder(database) {} void downgrade10(Query& query) { static const char* tables[] = {"file_id", "file_ids", "file_ranges", "files"}; // tables for (const auto* table: tables) { query = format("drop table %s", table); query.execute(); } } void downgrade21(Query& query) { query = "drop table file_key_data"; query.execute(); } void upgrade01(Query& query) { query = "create table files ( " " accessed integer " " constraint nn_files_accessed " " not null, " " allocated_size integer " " constraint nn_files_allocated_size " " not null, " " dirty integer " " constraint nn_files_dirty " " not null, " " handle integer " " constraint uq_files_handle " " unique, " " id integer " " constraint nn_files_id " " not null, " " modified integer " " constraint nn_files_modified " " not null, " " name text, " " parent_handle integer, " " removed integer " " constraint nn_files_removed " " not null, " " reported_size integer " " constraint nn_files_reported_size " " not null, " " size integer " " constraint nn_files_size " " not null, " " constraint pk_files " " primary key (id), " " constraint uq_files_handle " " unique (handle), " " constraint uq_files_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); query = "create table file_ranges ( " " begin integer " " constraint nn_file_ranges_begin " " not null, " " end integer " " constraint nn_file_ranges_end " " not null, " " id integer " " constraint nn_file_ranges_id " " not null, " " constraint fk_file_ranges_files " " foreign key (id) " " references files (id) " " on delete cascade, " " constraint pk_file_ranges " " primary key (begin, id), " " constraint uq_file_ranges_end_id " " unique (end, id) " ")"; query.execute(); query = "create table file_ids ( " " id integer " " constraint nn_file_ids_id " " not null, " " constraint pk_file_ids " " primary key (id) " ")"; query.execute(); query = "create table file_id ( " " id integer " " constraint nn_file_id_id " " not null, " " next integer " " constraint nn_file_id_next " " not null, " " constraint pk_file_id " " primary key (id) " ")"; query.execute(); query = "insert into file_id values (0, 0)"; query.execute(); } void upgrade12(Query& query) { query = "create table if not exists file_key_data ( " " chat_auth text, " " id integer " " constraint nn_file_key_data_id " " not null, " " is_public integer " " constraint nn_file_key_data_is_public " " not null, " " key_and_iv text " " constraint nn_file_key_data_key_and_iv " " not null, " " public_auth text, " " private_auth text, " " constraint fk_file_key_data_files " " foreign key (id) " " references files (id) " " on delete cascade, " " constraint pk_file_key_data " " primary key (id) " ")"; query.execute(); } } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/displaced_buffer.cpp���������������������������������������������������0000664�0000000�0000000�00000003626�15162662266�0021671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/displaced_buffer.h> #include <cassert> namespace mega { namespace file_service { DisplacedBuffer::DisplacedBuffer(BufferPtr buffer, std::uint64_t displacement): Buffer(), mBuffer(std::move(buffer)), mDisplacement(displacement) { // Sanity. assert(mBuffer); } auto DisplacedBuffer::copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { assert(mBuffer); // No buffer to delegate to. if (!mBuffer) return std::make_pair(0u, false); // Delegate transfer. return mBuffer->copy(target, mDisplacement + sourceOffset, targetOffset, length); } void DisplacedBuffer::displacement(std::uint64_t displacement) { mDisplacement = displacement; } std::uint64_t DisplacedBuffer::displacement() const { return mDisplacement; } bool DisplacedBuffer::isFileBuffer() const { assert(mBuffer); return mBuffer->isFileBuffer(); } auto DisplacedBuffer::read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { assert(mBuffer); // No buffer to delegate to. if (!mBuffer) return std::make_pair(0u, false); // Delegate read. return mBuffer->read(buffer, mDisplacement + offset, length); } auto DisplacedBuffer::write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { assert(mBuffer); // No buffer to delegate to. if (!mBuffer) return std::make_pair(0u, false); // Delegate write. return mBuffer->write(buffer, mDisplacement + offset, length); } DisplacedBufferPtr displace(BufferPtr buffer, std::uint64_t displacement) { return std::make_shared<DisplacedBuffer>(std::move(buffer), displacement); } } // file_service } // mega ����������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/documentation/���������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020546�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/documentation/uml/�����������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021343�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/documentation/uml/high_level_detailed.plantuml�������������������������0000664�0000000�0000000�00000007750�15162662266�0027073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@startuml ' Use :: as a package separator instead of . set separator :: ' Hide all members by default. hide members ' Define toplevel classes. class mega::MegaClient class mega::NodeHandle class std::int64_t class std::string class std::uint64_t ' Define common classes. abstract class mega::common::Client class mega::common::ClientAdapter class mega::common::Expected<T, E> ' ClientAdapter implements Client. mega::common::Client <|.. mega::common::ClientAdapter ' ClientAdapter delegates (wraps) MegaClient. mega::MegaClient <-- mega::common::ClientAdapter ' Define public file service classes. class mega::file_service::File class mega::file_service::FileContext class mega::file_service::FileID class mega::file_service::FileInfo class mega::file_service::FileInfoContext class mega::file_service::FileRange class mega::file_service::FileReadCallback class mega::file_service::FileReadRequest class mega::file_service::FileResultOr<T> class mega::file_service::FileService class mega::file_service::FileServiceContext class mega::file_service::FileServiceResultOr<T> class mega::file_service::FileWriteCallback class mega::file_service::FileWriteRequest enum mega::file_service::FileResult enum mega::file_service::FileServiceResult ' FileService accesses cloud functionality via Client. mega::common::Client <-- mega::file_service::FileServiceContext ' Every File instance references a FileContext instance. mega::file_service::File o-- mega::file_service::FileContext ' Every FileInfo instance references a FileInfoContext instance. mega::file_service::FileInfo o-- mega::file_service::FileInfoContext ' Every FileContext instance references a FileInfoContext instance. mega::file_service::FileContext o-- mega::file_service::FileInfoContext ' The FileService may reference a FileServiceContext instance. mega::file_service::FileService *-- mega::file_service::FileServiceContext ' FileServiceContext manages zero or more FileContexts. mega::file_service::FileServiceContext *-- mega::file_service::FileContext ' FileServiceContext manages zero or more FileInfoContexts. mega::file_service::FileServiceContext *-- mega::file_service::FileInfoContext ' Show members of the following classes. show members class mega::file_service::File { void read(FileReadCallback callback, void* buffer, std::uint64_t offset, std::uint64_t length) void read(FileReadCallback callback, void* buffer, const FileRange& range) void write(FileWriteCallback callback, const void* buffer, std::uint64_t offset, std::uint64_t length) void write(FileWriteCallback callback, const void* buffer, const FileRange& range) } class mega::file_service::FileContext { void read(FileReadRequest request) void write(FileWriteRequest request) } class mega::file_service::FileInfo { NodeHandle handle() const FileID id() const std::int64_t modified() const std::uint64_t size() const } class mega::file_service::FileInfoContext { void handle(NodeHandle handle) NodeHandle handle() const FileID id() const void modified(std::int64_t modified) std::int64_t modified() const void size(std::uint64_t size) std::uint64_t size() const } class mega::file_service::FileRange { std::uint64_t mBegin std::uint64_t mEnd } class mega::file_service::FileReadRequest { void* mBuffer FileReadCallback mCallback FileRange mRange } class mega::file_service::FileService { FileServiceResultOr<File> create(NodeHandle parentHandle, const std::string& name) void deinitialize() FileServiceResult initialize(common::Client& client) FileServiceResultOr<FileInfo> info(FileID id) FileServiceResultOr<File> open(FileID id) } class mega::file_service::FileServiceContext { FileServiceResultOr<File> create(NodeHandle parentHandle, const std::string& name) FileServiceResultOr<FileInfo> info(FileID id) FileServiceResultOr<File> open(FileID id) } class mega::file_service::FileWriteRequest { const void* mBuffer FileWriteCallback mCallback FileRange mRange } @enduml ������������������������sdk-10.11.0/src/file_service/documentation/uml/high_level_simplified.plantuml�����������������������0000664�0000000�0000000�00000005174�15162662266�0027443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������@startuml ' Use :: as a package separator rather than . set separator :: ' As this is a simplified diagram, don't show members. hide members ' Define toplevel classes. class mega::MegaClient ' Define common classes. abstract class mega::common::Client class mega::common::ClientAdapter ' Define file service classes. class mega::file_service::File class mega::file_service::FileInfo class mega::file_service::FileService ' ClientAdapter implement Client. mega::common::Client <|.. mega::common::ClientAdapter ' By abstracting functionality provided by MegaClient. mega::MegaClient <-- mega::common::ClientAdapter ' The FileService manipulates the cloud via the Client interface. mega::common::Client <-- mega::file_service::FileService ' The FileService manages zero or more File(Info)? instances. mega::file_service::FileService \ "1" *-- "*" mega::file_service::File : manages mega::file_service::FileService \ "1" *-- "*" mega::file_service::FileInfo : manages ' Every File instance references a FileInfo instance. mega::file_service::FileInfo o-- mega::file_service::File ' Add some notes to make things a little clearer. note top of mega::common::Client : \ Provides an abstract high-level interface to client functionality. note right of mega::common::ClientAdapter : \ Delegate (or wraps) MegaClient functionality.\n\ \n\ Ensures that accessing such functionality is thread-safe and that\n\ callbacks for any asynchronous operations are always executed. note top of mega::file_service::FileService : \ Provides high-level access to files contained in the cloud.\n\ \n\ It allows a consumer to manipulate files in the cloud without concern\n\ for how a given file's content is retrieved or uploaded.\n\ \n\ It also allows a consumer to retrieve information about a file and to\n\ be notified when that file's information has changed. note top of mega::file_service::FileInfo : \ Instantiated on demand by the FileService.\n\ \n\ It allows the consumer to determine what a file's modification time\n\ and size are along with what node in the cloud that file is related to if \ any.\n\ \n\ Consumers can choose to be notified when any of these attributes change. note bottom of mega::file_service::File : \ Instantiated on demand by the FileService.\n\ \n\ Allows a consumer to read content from or write content to a file in the cloud.\n\ \n\ File content is streamed as necessary to satisfy reads.\n\ File content is uploaded when requested by the consumer.\n\ \n\ Every File instance contains a reference to a matching FileInfo instance so\n\ that consumers can access current values for attributes such as file's\n\ modification time or size. @enduml ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file.cpp���������������������������������������������������������������0000664�0000000�0000000�00000010654�15162662266�0017326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file.h> #include <mega/file_service/file_append_request.h> #include <mega/file_service/file_context.h> #include <mega/file_service/file_fetch_request.h> #include <mega/file_service/file_flush_request.h> #include <mega/file_service/file_info.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_read_request.h> #include <mega/file_service/file_read_result.h> #include <mega/file_service/file_remove_request.h> #include <mega/file_service/file_result.h> #include <mega/file_service/file_result_or.h> #include <mega/file_service/file_service_context_badge.h> #include <mega/file_service/file_touch_request.h> #include <mega/file_service/file_truncate_request.h> #include <mega/file_service/file_write_request.h> #include <mega/file_service/logger.h> #include <utility> namespace mega { namespace file_service { File::File(FileServiceContextBadge, FileContextPtr context): mInstanceLogger("File", *this, logger()), mContext(std::move(context)) {} File::~File() = default; File::File(const File& other): mInstanceLogger("File", *this, logger()), mContext(other.mContext) {} File::File(File&& other): mInstanceLogger("File", *this, logger()), mContext(std::exchange(other.mContext, nullptr)) {} File& File::operator=(const File& rhs) { if (this != &rhs) mContext = rhs.mContext; return *this; } File& File::operator=(File&& rhs) { using std::swap; if (this != &rhs) swap(mContext, rhs.mContext); return *this; } FileEventObserverID File::addObserver(FileEventObserver observer) { assert(mContext); return mContext->addObserver(std::move(observer)); } void File::append(const void* buffer, FileAppendCallback callback, std::uint64_t length) { assert(buffer || !length); assert(callback); assert(mContext); return mContext->append(FileAppendRequest{buffer, std::move(callback), length}); } void File::fetch(FileFetchCallback callback) { assert(callback); assert(mContext); mContext->fetch(FileFetchRequest{std::move(callback)}); } void File::fetchBarrier(FileFetchBarrierCallback callback) { assert(callback); assert(mContext); mContext->fetchBarrier(std::move(callback)); } void File::flush(FileFlushCallback callback) { assert(callback); assert(mContext); return mContext->flush(FileFlushRequest{std::move(callback)}); } FileInfo File::info() const { assert(mContext); return mContext->info(); } void File::purge(FilePurgeCallback callback) { assert(callback); assert(mContext); return mContext->remove(FileRemoveRequest{std::move(callback), false, true}); } FileRangeVector File::ranges() const { assert(mContext); return mContext->ranges(); } void File::read(FileReadCallback callback, std::uint64_t offset, std::uint64_t length) { assert(callback); assert(mContext); read(std::move(callback), FileRange(offset, offset + length)); } void File::read(FileReadCallback callback, const FileRange& range) { assert(callback); assert(mContext); mContext->read(FileReadRequest{std::move(callback), range}); } void File::reclaim(FileReclaimCallback callback) { assert(callback); assert(mContext); mContext->reclaim(std::move(callback)); } void File::remove(FileRemoveCallback callback, bool replaced) { assert(callback); assert(mContext); mContext->remove(FileRemoveRequest{std::move(callback), replaced, false}); } void File::removeObserver(FileEventObserverID id) { assert(mContext); mContext->removeObserver(id); } void File::touch(FileTouchCallback callback, std::int64_t modified) { assert(callback); assert(mContext); mContext->touch(FileTouchRequest{std::move(callback), modified}); } void File::truncate(FileTruncateCallback callback, std::uint64_t newSize) { assert(callback); assert(mContext); mContext->truncate(FileTruncateRequest{std::move(callback), newSize}); } void File::write(const void* buffer, FileWriteCallback callback, std::uint64_t offset, std::uint64_t length) { write(buffer, std::move(callback), FileRange(offset, offset + length)); } void File::write(const void* buffer, FileWriteCallback callback, const FileRange& range) { assert(buffer || range.mEnd - range.mBegin == 0); assert(callback); assert(mContext); mContext->write(FileWriteRequest{buffer, std::move(callback), range}); } } // file_service } // mega ������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_access.cpp��������������������������������������������������������0000664�0000000�0000000�00000006017�15162662266�0020645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_access.h> #include <mega/filesystem.h> #include <cassert> namespace mega { namespace file_service { // Maximum read or write length. constexpr std::uint64_t maximum = std::numeric_limits<unsigned long>::max(); auto read(FileAccess& file, void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { // Sanity. assert(buffer); // Caller didn't pass us a valid buffer. if (!buffer) return std::make_pair(0u, false); // Convenience. auto* buffer_ = static_cast<std::uint8_t*>(buffer); // How many bytes we have left to read. auto remaining = length; // To avoid data races in FileAccess. auto retry = false; // Read as much data from the file as possible. while (remaining) { // Figure out how much data we can read in this iteration. auto count = std::min(maximum, remaining); // Couldn't read data from the file. if (!file.frawread(buffer_, static_cast<unsigned long>(count), static_cast<m_off_t>(offset), true, FSLogging::logOnError, &retry)) break; // Bump pointers. buffer_ += count; offset += count; remaining -= count; } // Let the caller know how much we could read. return std::make_pair(length - remaining, !remaining); } auto write(FileAccess& file, const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { // Sanity. assert(buffer); // Caller didn't pass us a valid buffer. if (!buffer) return std::make_pair(0u, false); // Convenience. auto* buffer_ = static_cast<const std::uint8_t*>(buffer); // How many bytes we have left to write. auto remaining = length; // To avoid data races in FileAccess. auto retry = false; // Write as much data to the file as possible. while (remaining) { // How much data should we write in this iteration? auto count = std::min(remaining, maximum); // Track how much data we wrote in this iteration. auto written = 0ul; // Try and write the data to file. auto result = file.fwrite(buffer_, static_cast<unsigned long>(count), static_cast<m_off_t>(offset), &written, &retry); // Update count of remaining bytes. remaining -= written; // Couldn't write data to the file. if (!result) break; // Bump pointers. buffer_ += written; offset += written; } // Let the caller know how much data we wrote. return std::make_pair(length - remaining, !remaining); } bool truncate(FileAccess& file, std::uint64_t newSize) { return file.ftruncate(static_cast<m_off_t>(newSize)); } } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_buffer.cpp��������������������������������������������������������0000664�0000000�0000000�00000007650�15162662266�0020661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_access.h> #include <mega/file_service/file_buffer.h> #include <cassert> #include <memory> #include <tuple> namespace mega { namespace file_service { FileBuffer::FileBuffer(FileAccess& file): Buffer(), mFile(file) {} auto FileBuffer::copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { assert(this != &target); // Can't copy to the same buffer. if (this == &target) return std::make_pair(0u, false); // Caller doesn't actually want to transfer any data. if (!length) return std::make_pair(0u, true); // Maximum length allowed for on-stack buffer. constexpr std::uint64_t threshold = 1u << 12; // Buffer used when length < threshold. std::uint8_t stackBuffer[threshold]; // Assume we'll use the on-stack buffer. auto buffer = &stackBuffer[0]; auto size = threshold; // Buffer used when length >= threshold. std::unique_ptr<std::uint8_t[]> memoryBuffer; // Need to use an in-memory buffer. if (threshold < length) { // A 128KiB block should be enough for our needs. size = std::min<std::uint64_t>(length, 1u << 17); // Instantiate buffer. memoryBuffer.reset(new std::uint8_t[static_cast<std::size_t>(size)]); // Use the in-memory buffer. buffer = memoryBuffer.get(); } // How much data have we copied? std::uint64_t copied = 0u; // Transfer data to the target buffer. while (length > size) { // Try and read data from storage. auto [count, success] = read(buffer, sourceOffset + copied, size); // Couldn't read data from storage. if (!success) return std::make_pair(copied, false); // Try and write data to the target buffer. std::tie(count, success) = target.write(buffer, targetOffset + copied, size); // Bump counters. copied += count; length -= count; // Couldn't write data to the target buffer. if (!success) return std::make_pair(copied, false); } // Try and read data from storage. auto [count, success] = read(buffer, sourceOffset + copied, length); // Couldn't read data from storage. if (!success) return std::make_pair(copied, false); // Try and write data to the target buffer. std::tie(count, success) = target.write(buffer, targetOffset + copied, length); // Let the caller know how much data was copied. return std::make_pair(count + copied, success); } bool FileBuffer::isFileBuffer() const { return true; } auto FileBuffer::read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { // Caller doesn't want to read anything. if (!length) return std::make_pair(0u, true); assert(buffer); // Caller gave us a bad buffer. if (!buffer) return std::make_pair(0, false); // Disambiguate. using file_service::read; // Let the caller know how much data we read from the file. return read(mFile, buffer, offset, length); } auto FileBuffer::write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { // Caller doesn't actually want to write anything. if (!length) return std::make_pair(0u, true); assert(buffer); // Caller didn't give us a valid buffer. if (!buffer) return std::make_pair(0u, false); // Disambiguate. using file_service::write; // Let the caller know how much data we wrote to the file. return write(mFile, buffer, offset, length); } bool FileBuffer::truncate(std::uint64_t newSize) { // Disambiguate. using file_service::truncate; // Truncate the file. return truncate(mFile, newSize); } } // file_service } // mega ����������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_context.cpp�������������������������������������������������������0000664�0000000�0000000�00000176111�15162662266�0021073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/client.h> #include <mega/common/database.h> #include <mega/common/lock.h> #include <mega/common/node_info.h> #include <mega/common/partial_download.h> #include <mega/common/scoped_query.h> #include <mega/common/task_queue.h> #include <mega/common/transaction.h> #include <mega/common/upload.h> #include <mega/common/utility.h> #include <mega/file_service/displaced_buffer.h> #include <mega/file_service/file_access.h> #include <mega/file_service/file_append_request.h> #include <mega/file_service/file_context.h> #include <mega/file_service/file_context_badge.h> #include <mega/file_service/file_fetch_request.h> #include <mega/file_service/file_flush_request.h> #include <mega/file_service/file_id.h> #include <mega/file_service/file_info.h> #include <mega/file_service/file_info_context.h> #include <mega/file_service/file_location.h> #include <mega/file_service/file_range_context.h> #include <mega/file_service/file_read_request.h> #include <mega/file_service/file_reclaim_request.h> #include <mega/file_service/file_remove_request.h> #include <mega/file_service/file_result.h> #include <mega/file_service/file_service_context.h> #include <mega/file_service/file_touch_request.h> #include <mega/file_service/file_truncate_request.h> #include <mega/file_service/file_write_request.h> #include <mega/file_service/logging.h> #include <mega/file_service/sparse_file_buffer.h> #include <mega/file_service/type_traits.h> #include <mega/filesystem.h> #include <chrono> #include <iterator> #include <limits> #include <type_traits> #include <variant> namespace mega { namespace file_service { using namespace common; class FileContext::FetchContext { // Called when the fetch has been completed. void completed(FileResult result); // Logs instance lifetime. common::InstanceLogger<FetchContext> mInstanceLogger; // Keep mContext alive as long as we are alive. Activity mActivity; // What file are we fetching? FileContext& mContext; // What fetch requests are we executing? std::vector<FileFetchRequest> mRequests; public: FetchContext(FileContext& context, FileFetchRequest request); // Called when we've received file data. void operator()(FetchContextPtr& context, FileResultOr<FileReadResult> result); // Queue a fetch request for execution. void queue(FileFetchRequest request); }; // FetchContext class FileContext::FlushContext { // Called when the file's content has been uploaded. void bound(FlushContextPtr& context, ErrorOr<NodeHandle> result); // Called when the flush has been completed. template<typename Lock> void completed(FlushContextPtr context, Lock&& lock, FileResult result); // Called to check that our upload target is valid. // // Populates mHandle, mName and mParentHandle. Error resolve(Client& client); // Called when the file's data has been uploaded. void uploaded(FlushContextPtr& context, ErrorOr<UploadResult> result); // Logs instance lifetime. common::InstanceLogger<FlushContext> mInstanceLogger; // Keep mContext alive as long as we are alive. Activity mActivity; // What file are we flushing? FileContext& mContext; // The file's current node handle. NodeHandle mHandle; // Where is this file stored in the cloud? FileLocation mLocation; // What flush requests are we executing? std::vector<FileFlushRequest> mRequests; // The upload that's pushing our content to the cloud. UploadPtr mUpload; public: FlushContext(FileContext& context, FileFlushRequest request); // Called when we've retrieved all of this file's content. void operator()(FlushContextPtr& context, FileResult result); // Cancel the flush. template<typename Lock> static void cancel(FlushContextPtr context, Lock&& lock); // Queue a flush request for execution. void queue(FileFlushRequest request); }; // FlushContext class FileContext::ReclaimContext { // Called when the reclaim request has completed. template<typename Lock> void completed(ReclaimContextPtr context, Lock&& lock, FileResultOr<std::uint64_t> result); // Logs instance lifetime. common::InstanceLogger<ReclaimContext> mInstanceLogger; // Keep mContext alive as long as we are alive. Activity mActivity; // How much space was the file taking when we started reclaiming? std::uint64_t mAllocatedSize; // Who should we call when the reclaim completes? std::vector<FileReclaimCallback> mCallbacks; // What file are we reclaiming? FileContext& mContext; public: ReclaimContext(FileContext& context); // Cancel the reclamation. template<typename Lock> static void cancel(ReclaimContextPtr& context, Lock&& lock); // Called when the file's data has been flushed to the cloud. void flushed(ReclaimContextPtr& context, FileResult result); // Queue a callback for execution when the reclaim completes. void queue(FileReclaimCallback callback); }; // ReclaimContext // Retrieve an instance of a request's type tag. template<typename Request> auto tag(const Request& request) -> std::enable_if_t<IsFileRequestV<Request>, typename Request::Type>; // Wrap a callback to ensure that exceptions are always handled. template<typename Callback> Callback swallow(Callback callback, const char* name); void FileContext::addRange(const FileRange& range, Transaction& transaction) { auto query = transaction.query(mService.queries().mAddFileRange); query.param(":begin").set(range.mBegin); query.param(":end").set(range.mEnd); query.param(":id").set(mInfo->id()); query.execute(); } void FileContext::cancel(const FileRange& range) { // Make sure we have exclusive access to mRanges. std::unique_lock lock(mRangesLock); // What ranges does range intersect? auto [begin, end] = mRanges.find(range); // No ranges intersect range. if (begin == end) return; // Tracks any downloads in progress. std::vector<FileRangeContextPtr> contexts; // Tracks how many downloads are still in progress. std::atomic<std::size_t> count{0}; // So we know when all downloads have completed. std::promise<void> notifier; // Called when a download has completed. auto completed = [&](auto) { // All downloads have completed. if (count.fetch_sub(1) == 1) notifier.set_value(); }; // completed // Figure out which range downloads are in progress. for (; begin != end; ++begin) { // Convenience. auto context = begin->second; // Range doesn't have a download in progress. if (!context) continue; // Invoke our callback when the range's download completes. context->queue(completed); // Latch the context so we can cancel its download later. contexts.emplace_back(context); } // No range downloads need to be cancelled. if (contexts.empty()) return; // Track how many downloads are in progress. count += contexts.size(); // Release ranges lock. // // Any ranges that were waiting on the lock will now complete. // // NOTE: As this function is only called while processing a write // request, we can be assured that no ranges will be added after this // lock is released. lock.unlock(); // Cancel any range downloads still in progress. for (auto& context: contexts) context->cancel(); // Wait for range downloads to complete. notifier.get_future().get(); } void FileContext::cancel(FileRequest& request) { // Cancel the request. std::visit( [&](auto& request) { completed(std::move(request), FILE_CANCELLED); }, request); } void FileContext::cancel() { // When we execute this function, we know that no live references to // this instance can exist. We know this because this function is only // called from the instance's destructor. // // This doesn't mean that the instance is idle, however, as it is // possible that one or more downloads may still be in progress which // means that the client servicing those downloads may be executing // within us or about to execute within us. // Get a snapshot of our ranges. auto ranges = [&]() { std::lock_guard guard(mRangesLock); return mRanges; }(); // Cancel any downloads in progress. for (const auto& [_, context]: ranges) { if (context) context->cancel(); } // Cancel the flush if necessary. { std::unique_lock lock(mFlushContextLock); if (mFlushContext) mFlushContext->cancel(mFlushContext, std::move(lock)); } // Cancel reclamation if necessary. { std::unique_lock lock(mReclaimContextLock); if (mReclaimContext) mReclaimContext->cancel(mReclaimContext, std::move(lock)); } // Latch the request queue. auto requests = [this]() { // Make sure no one else is messing with our request queue. std::lock_guard guard(mRequestsLock); // Latch the request queue. auto requests = std::exchange(mRequests, FileRequestList()); // Make sure the queue's in a sane state. mRequests.clear(); // Return queue to caller. return requests; }(); // Cancel any pending requests. // // We know this won't cause any other requests to be queued as we know // there are no live references to this instance. while (!requests.empty()) { // Cancel the request. cancel(requests.front()); // Remove the request from our queue. requests.pop_front(); } } void FileContext::completed(Buffer& buffer, FileRangeContextPtrMap::Iterator iterator, FileRange range) try { // Convenience. auto offset = range.mBegin; auto length = range.mEnd - offset; // No data for this range was downloaded. if (!length) return mRanges.remove(iterator), void(); // Flush this range's data to storage if necessary. if (buffer.isMemoryBuffer()) std::tie(length, std::ignore) = buffer.copy(*mBuffer, 0, offset, length); // Couldn't flush any of this range's data to storage. if (!length) return mRanges.remove(iterator), void(); // Compute the range's actual end point. range.mEnd = range.mBegin + length; // Figure out what ranges we can coalesce with. auto begin = [&]() { // We don't have a left neighbor. if (iterator == mRanges.begin()) return iterator; // Get an iterator to our left neighbor. auto candidate = std::prev(iterator); // Neighbor hasn't completed downloading. if (candidate->second) return iterator; // Neighbor isn't contiguous. if (candidate->first.mEnd != range.mBegin) return iterator; // Update range. range.mBegin = candidate->first.mBegin; // Return iterator to caller. return candidate; }(); auto end = [&]() { // Get an iterator to our right neighbor. auto candidate = std::next(iterator); // We don't have a right neighbor. if (candidate == mRanges.end()) return candidate; // Neighbor hasn't completed downloading. if (candidate->second) return candidate; // Neighbor isn't contiguous. if (candidate->first.mBegin != range.mEnd) return candidate; // Update range. range.mEnd = candidate->first.mEnd; // Return iterator to caller. return std::next(candidate); }(); // Mark range as present. iterator->second.reset(); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // Start transaction so we can safely access the database. auto transaction = database.transaction(); // Remove obsolete ranges from the database. removeRanges(range, transaction); // Add our new range to the database. addRange(range, transaction); // Update the file's size. updateSize(mInfo->size(), transaction); // Remove obsolete ranges from memory. mRanges.remove(begin, end); // Add our new range to memory. mRanges.add(range, nullptr); // Persist our changes. transaction.commit(); } catch (std::runtime_error& exception) { // Let debuggers know what went wrong. FSWarningF("Unable to complete file range download: %s: %s: %s", toString(mInfo->id()).c_str(), toString(range).c_str(), exception.what()); // Consider the range absent. mRanges.remove(iterator); } void FileContext::completed(BufferPtr buffer, FileReadRequest&& request) { // Sanity. assert(buffer); // Convenience. auto [begin, end] = request.mRange; // Complete the user's request. completed(std::move(request), FileReadResult{*buffer, begin, end - begin}, std::move(buffer)); } template<typename Request, typename Result, typename... Captures> auto FileContext::completed(Request&& request, Result result, Captures&&... captures) -> std::enable_if_t<IsFileRequestV<Request>> { // Sanity. assert(request.mCallback); // Make sure request has been passed by rvalue reference. static_assert(std::is_rvalue_reference_v<decltype(request)>); // Called to complete the user's request. auto callback = [=](auto& callback, auto& cookie, auto&, auto& tag, auto&&...) { // Are we passing a file result? if constexpr (std::is_same_v<FileResult, Result>) { // Determine the callback's concrete type. using Callback = decltype(callback); // Check if we have to pass result as an unexpected. if constexpr (std::is_invocable_v<Callback, FileResult>) callback(result); else callback(unexpected(result)); } else { // Pass the result as is. callback(result); } // Check if our context is still alive. auto context = cookie.lock(); // Context isn't alive. if (!context) return; // Let the context know the request has completed. executed(tag); // See if we can't execute any queued requests. context->execute(); }; // callback // Queue the user's request for completion. mService.execute(std::bind(std::move(callback), swallow(std::move(request.mCallback), request.name()), weak_from_this(), std::placeholders::_1, tag(request), std::forward<Captures>(captures)...)); } void FileContext::completed(FileWriteRequest&& request) { // Convenience. auto [begin, end] = request.mRange; // Complete the user's request. completed(std::move(request), FileWriteResult{begin, end - begin}); } void FileContext::dequeued([[maybe_unused]] std::unique_lock<std::mutex> lock, FileReadRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); } void FileContext::dequeued([[maybe_unused]] std::unique_lock<std::mutex> lock, FileWriteRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); // Sanity. assert(mNumPendingWriteRequests); --mNumPendingWriteRequests; } void FileContext::dequeued(std::unique_lock<std::mutex> lock, const FileRequest& request) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); std::visit( [&lock, this](auto&& request) { this->dequeued(std::move(lock), tag(request)); }, request); } bool FileContext::executable(std::unique_lock<std::mutex>& lock, bool queuing, const FileRequest& request) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); return std::visit( [&lock, queuing, this](auto&& request) { return this->executable(lock, queuing, tag(request)); }, request); } bool FileContext::executable([[maybe_unused]] std::unique_lock<std::mutex>& lock, bool queuing, FileReadRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); if (queuing && mNumPendingWriteRequests) return false; return mReadWriteState.read(); } bool FileContext::executable([[maybe_unused]] std::unique_lock<std::mutex>& lock, bool, FileWriteRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); return mReadWriteState.write(); } void FileContext::execute(std::function<void()> function) { // Sanity. assert(function); // Wrap the caller's function. auto wrapper = [function = std::move(function)](auto&) { // Execute the caller's function. function(); }; // wrapper // Queue function for execution on the service's thread pool. mService.execute(std::move(wrapper)); } void FileContext::execute(FileAppendRequest& request) { // Convenience. auto size = mInfo->size(); // Assume there's no range for us to grow. FileRange range(size, size + request.mLength); // Acquire ranges lock. std::unique_lock rangesLock(mRangesLock); // Assume we can grow the last range. auto candidate = mRanges.rbegin(); // Can grow the last range. if (!mRanges.empty() && candidate->first.mEnd == size) range.mBegin = candidate->first.mBegin; else candidate = mRanges.end(); // Disambiguate. using file_service::write; // Try and write the user's data to disk. auto [length, _] = mBuffer->write(request.mBuffer, size, request.mLength); // Couldn't write all of the user's data to disk. if (length < request.mLength) return completed(std::move(request), FILE_FAILED); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // Start a transaction so we can safely modify the database. auto transaction = database.transaction(); // Remove obsolete ranges from the database. removeRanges(range, transaction); // Add a new range to the database. addRange(range, transaction); // Compute the file's new modification time. auto modified = now(); // Update the file's access and modification time. updateAccessAndModificationTimes(modified, modified, transaction); // Update the file's size. updateSize(range.mEnd, transaction); // Remove obsolete ranges from memory. mRanges.remove(candidate, mRanges.end()); // Add new range to memory. mRanges.add(range, nullptr); // Persist our changes. transaction.commit(); // Tweak range. range.mBegin = size; // Update the file's attributes. mInfo->written(modified, range); // Queue the user's request for execution. completed(std::move(request), FILE_SUCCESS); } void FileContext::execute(FileFetchRequest& request) { // Acquire fetch context lock. std::unique_lock lock(mFetchContextLock); // A fetch is already in progress. if (mFetchContext) return mFetchContext->queue(std::move(request)); // Instantiate a context for our fetch. mFetchContext = std::make_shared<FetchContext>(*this, std::move(request)); // Release flush context lock. lock.unlock(); // Try and read all of the file's data. read(FileReadRequest{std::bind(&FetchContext::operator(), mFetchContext.get(), mFetchContext, std::placeholders::_1), FileRange(0, mInfo->size())}); } void FileContext::execute(FileFlushRequest request) { // The file hasn't been modified. if (!mInfo->dirty()) return completed(std::move(request), FILE_SUCCESS); // Acquire flush context lock. std::unique_lock lock(mFlushContextLock); // A flush is already in progress. if (mFlushContext) return mFlushContext->queue(std::move(request)); // Instantiate a new flush context. mFlushContext = std::make_shared<FlushContext>(*this, std::move(request)); // Unlock flush context lock. lock.unlock(); // Fetch all of this file's data. fetch(FileFetchRequest{std::bind(&FlushContext::operator(), mFlushContext.get(), mFlushContext, std::placeholders::_1)}); } // This function is pretty complex as it handles a lot of cases. // // The basic idea is that we want to get the most value out of any download // from the cloud we perform. // // For instance, if the user wants to read only 2KiB and we can't satisfy // that request, we might as well download 2MiB because it will take the // same time for the servers. That is, if it takes the same amount of time // to download 2KiB and 2MiB, we might as well download 2MiB because it's // better value. // // We also want to remove holes that surround a new range when it's // economical to do so. For instance, imagine the user is performing a read // and that the read is surrounded by ranges on either side. If those ranges // are not too far away, we might as well extend the read so that the range // that we wind up creating fills the space between the surrounding ranges // completely. // // Note that there are really two ranges we're dealing with when we are // executing a user's read: there's the range the user gave us directly and // then there's the effective range, the one that we actually use for // downloading. The effective range will always be the same or larger than // the range the user provided. // // The cases are roughly as follows: // // - A user's request can be completely satisfied by an existing range. // - The existing range has already been downloaded. // - Execute the user's request and do no further processing. // - The existing range is still being downloaded. // - Queue the user's request for later completion. // // - A user's request can be partly satisfied by an existing range. // - The range contains the beginning of the user's read. // - The range has already been downloaded. // - Execute the user's request immediately. // - The range is still being downloaded. // - Queue the user's request for later completion. // - Extend the user's read so that we download a sane amount. // - The sane amount being at least mMinimumRangeSize. // - Check if there are any ranges before or after our extended range. // - If they are close enough, extend the range again. // - Close enough being mMinimumRangeDistance. // - That is, fill the holes if it's cheap enough to do so. // - Fill all of the holes in our extended range. // - That is, download all of the subranges we don't have. // // - A user's request cannot be satisfied by any existing range. // - This case is handled pretty much the same as the case above. // - It differs in that the user's request will be executed when the // first hole is downloaded. void FileContext::execute(FileReadRequest& request) { // The service's current options. auto options = mService.options(); // The range the user wants to read. auto range = request.mRange; // The file's current size. auto size = mInfo->size(); // Convenience. auto& [begin, end] = range; // Make sure the user's read doesn't extend beyond the end of the file. end = std::min(end, size); // Make sure the user's read doesn't end before it begins. end = std::max(begin, end); // Make sure the request's range has been clamped. request.mRange = range; // The user doesn't actually need to read anything. if (begin == end) return completed(mBuffer, std::move(request)); // Update the file's access time. mInfo->accessed(now()); // Make sure we have exclusive access to mRanges. std::unique_lock lock(mRangesLock); // Try and locate the range that either: // - Contains the beginning of our read. // - Contains the read completely. // - Preceeds the read. auto i = [&range, this]() { // Search for the first range that ends at or after our read begins. auto i = mRanges.endsAfter(range.mBegin); // No ranges end at or after our read begins. // // Assume a range exists before our read. if (i == mRanges.end()) return mRanges.last(); // The range preceeds our read. if (i->first.mEnd <= range.mBegin) { // Does this range have a successor? auto j = std::next(i); // Range's successor contains either: // - The beginning of our read. // - All of our read. if (j != mRanges.end() && j->first.mBegin <= range.mBegin) return j; // Range preceeds our read. return i; } // The range contains: // - The beginning of our read. // - All of our read. if (i->first.mBegin <= range.mBegin) return i; // No ranges contain or preceed our read. if (i == mRanges.begin()) return mRanges.end(); // Assume a range exists before our read. return std::prev(i); }(); // We found a range that either contains or preceeds our read. while (i != mRanges.end()) { // The range preceeds our read. if (i->first.mEnd <= begin) { // Compute the distance between the range and our read. auto distance = begin - i->first.mEnd; // Begin the read earlier if the distance is small enough. if (distance <= options.mMinimumRangeDistance) begin = i->first.mEnd; break; } // The range contains all or part of our read. // // Clamp the request as necessary. request.mRange.mEnd = std::min(i->first.mEnd, request.mRange.mEnd); // Is the range still being downloaded? if (i->second) { // Queue the read as the range is still being downloaded. i->second->queue(std::move(request)); } else { // Range has been downloaded so complete the read now. completed(displace(mBuffer, range.mBegin), std::move(request)); } // The range only partially contained our read. if (i->first.mEnd < end) { // Bump our range's beginning. range.mBegin = i->first.mEnd; break; } // Nothing more to do as the range completely contained our read. return; } // Add a range to our map and return a reference to its context. auto add = [this](const FileRange& range) { // Add the range to the map. auto [iterator, added] = mRanges.tryAdd(range, nullptr); // Adding should always succeed as we're filling holes. assert(added); // Convenience. auto& context = iterator->second; // Instantiate a context to track the range's download. context.reset(new FileRangeContext(mActivities.begin(), iterator, *this)); // Return a reference to the context to our caller. return context.get(); }; // add // Extend the read if necessary so that the download is worthwhile. end = begin + std::max(end - begin, options.mMinimumRangeSize); // Make sure the read doesn't extend past the end of the file. end = std::min(end, size); // Try and find the first range that begins after our read begins. i = mRanges.beginsAfter(begin); // Try and find the first range that begins after our read ends. auto j = mRanges.beginsAfter(end); // Extend the read if it's worthwhile to do so. if (j != mRanges.end() && j->first.mBegin - end <= options.mMinimumRangeDistance) end = j++->first.mBegin; // Tracks the ranges that we need to download. std::vector<FileRangeContext*> ranges; // Keeps track of a hole's range. auto scratch = range; // Iterate over the holes, creating ranges as needed. for (; i != j; ++i) { // The range begins after scratch. if (i->first.mBegin > scratch.mBegin) { // Tweak scratch so that it represents the hole. scratch.mEnd = i->first.mBegin; // Add a new range and keep track of its context. ranges.emplace_back(add(scratch)); } // Bump scratch's beginning. scratch.mBegin = i->first.mEnd; } // A final hole still remains. if (scratch.mBegin < range.mEnd) ranges.emplace_back(add(FileRange(scratch.mBegin, end))); // Sanity. assert(!ranges.empty() || !request.mCallback); // No holes need to be filled. if (ranges.empty()) return; // Queue the request if it hasn't already been done. if (request.mCallback) ranges.front()->queue(std::move(request)); // Keep track of the downloads we need to begin. std::vector<PartialDownloadPtr> downloads; // We know how many downloads we need to begin. downloads.reserve(ranges.size()); // Convenience. auto& client = mService.client(); // The handle of the node we're downloading. auto handle = mInfo->handle(); // Try and create downloads for our ranges. for (auto* range_: ranges) { if (auto download = range_->download(client, mBuffer, handle, mKeyData)) downloads.emplace_back(std::move(download)); } // Release our mRanges lock so we can safely begin the downloads. lock.unlock(); // Begin the downloads. for (auto& download: downloads) download->begin(); } // When this request is executed, any pending downloads will have completed. void FileContext::execute(FileReclaimRequest& request) { // Make sure no one else is modifying mRanges. std::lock_guard rangesLock(mRangesLock); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // So we can safely modify the database. auto transaction = database.transaction(); // Represents the entire file. FileRange range(0, mInfo->size()); // Remove all of this file's ranges from the database. removeRanges(range, transaction); // Couldn't reduce the file's size. if (!mBuffer->truncate(0)) return completed(std::move(request), FILE_FAILED); // Remove all of the ranges from memory. mRanges.clear(); // Update the file's size. updateSize(mInfo->size(), transaction); // Persist our changes. transaction.commit(); // How much space did we reclaim? auto reclaimed = request.mAllocatedSize - mInfo->allocatedSize(); // Let waiters know how much space we reclaimed. completed(std::move(request), reclaimed); } void FileContext::execute(FileRemoveRequest& request) { // File's already been removed. if (mInfo->removed()) return completed(std::move(request), FILE_SUCCESS); // Cancel any pending downloads. cancel(FileRange(0, mInfo->size())); // Convenience. auto handle = mInfo->handle(); auto replaced = request.mReplaced; auto serviceOnly = request.mServiceOnly; // We only need to remove the file from the service. if (handle.isUndef() || serviceOnly) return completed(std::move(request), setRemoved(replaced)); // Called when the file's been removed. auto removed = [replaced, this](auto&&, auto&& request, auto result) { // File was removed from the cloud. if (result == API_OK) return completed(std::move(request), setRemoved(replaced)); // Couldn't remove the file from the cloud. completed(std::move(request), fileResultFromError(result)); }; // removed // Ask the client to remove our file. mService.client().remove(std::bind(std::move(removed), mActivities.begin(), std::move(request), std::placeholders::_1), handle); } void FileContext::execute(FileTouchRequest& request) { // Compute the file's new access time. auto accessed = now(); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // Start a transaction so we can safely modify the database. auto transaction = database.transaction(); // Update the file's access and modification time. updateAccessAndModificationTimes(accessed, request.mModified, transaction); // Persist our changes. transaction.commit(); // Update file attributes. mInfo->modified(accessed, request.mModified); // Queue the user's request for completion. completed(std::move(request), FILE_SUCCESS); } void FileContext::execute(FileTruncateRequest& request) { // Convenience. auto newSize = request.mSize; auto oldSize = mInfo->size(); // User isn't changing this file's size. if (newSize == oldSize) return completed(std::move(request), FILE_SUCCESS); // Grow or shrink the file as necessary. auto [databaseLock, transaction] = newSize > oldSize ? grow(newSize, oldSize) : shrink(newSize, oldSize); // Compute the file's new modification time. auto modified = now(); // Update the file's access and modification times in the database. updateAccessAndModificationTimes(modified, modified, transaction); // Update the file's size in the database. updateSize(newSize, transaction); // Persist our changes. transaction.commit(); // Update the file's attributes. mInfo->truncated(modified, newSize); // Queue the user's request to for completion. completed(std::move(request), FILE_SUCCESS); } void FileContext::execute(FileWriteRequest& request) { // Convenience. auto& range = request.mRange; auto length = range.mEnd - range.mBegin; // Caller doesn't actually want to write anything. if (!length) return completed(std::move(request)); // Caller hasn't passed us a valid buffer. if (!request.mBuffer) return completed(std::move(request), FILE_INVALID_ARGUMENTS); // Cancel any downloads in progress that intersect our write. cancel(range); // Get exclusive access to mRanges. std::unique_lock rangesLock(mRangesLock); // Disambiguate. using file_service::write; // Try and write the caller's content to storage. std::tie(length, std::ignore) = mBuffer->write(request.mBuffer, range.mBegin, length); // Couldn't write any content to storage. if (!length) return completed(std::move(request), FILE_FAILED); // Compute actual end of the written range. range.mEnd = range.mBegin + length; // Convenience. using Iterator = decltype(mRanges.begin()); Iterator begin; Iterator end; // Compute initial effective range. FileRange effectiveRange = {std::min(mInfo->size(), range.mBegin), range.mEnd}; // Find out which ranges we've touched. std::tie(begin, end) = mRanges.find(extend(effectiveRange, 1)); // Refine our effective range. effectiveRange = [&]() { // Assume range has no contiguous siblings. auto from = effectiveRange.mBegin; auto to = effectiveRange.mEnd; // Range has no siblings. if (begin == mRanges.end()) return FileRange(from, to); // Range has a sibling. from = std::min(begin->first.mBegin, from); to = std::max(begin->first.mEnd, to); // Range has a right sibling. if (end != mRanges.end()) { // Clarity. auto sibling = std::prev(end); // Recompute range's end point. to = std::max(sibling->first.mEnd, to); // Return effective range to caller. return FileRange(from, to); } // Range may have a right sibling. auto candidate = mRanges.crbegin(); // Range doesn't have a right sibling. if (candidate == mRanges.crend()) return FileRange(from, to); // Recompute range's end point. to = std::max(candidate->first.mEnd, to); // Return effective range to caller. return FileRange(from, to); }(); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // Start a transaction so we can safely modify the database. auto transaction = database.transaction(); // Remove obsolete ranges from the database. removeRanges(effectiveRange, transaction); // Add a new range to the database. addRange(effectiveRange, transaction); // Compute the file's new modification time. auto modified = now(); // Update the file's access and modification times in the database. updateAccessAndModificationTimes(modified, modified, transaction); // Update the file's size in the database. updateSize(std::max(mInfo->size(), effectiveRange.mEnd), transaction); // Remove obsolete ranges from memory. mRanges.remove(begin, end); // Add our new range to memory. mRanges.add(effectiveRange, nullptr); // Persist our changes. transaction.commit(); // Update the file's attributes. mInfo->written(modified, range); // Queue the user's request for completion. completed(std::move(request)); } void FileContext::execute(FileRequest& request) { // Executes a user's request. auto execute = [this](auto& request) { try { // Sanity. assert(request.mCallback); // Immediately reject the request if necessary. if (auto result = reject(request); result != FILE_SUCCESS) return completed(std::move(request), result); // Try and execute the request. this->execute(request); } catch (std::exception& exception) { // Threw an exception while executing request. FSErrorF("Unable to execute %s request: %s", request.name(), exception.what()); // Try and fail the request. completed(std::move(request), FILE_FAILED); } }; // execute // Execute the user's request. std::visit(std::move(execute), request); } void FileContext::execute() { // Execute as many requests as we can. while (true) { // Acquire lock. std::unique_lock lock(mRequestsLock); // There are no requests waiting to execute. if (mRequests.empty()) return; // Request isn't executable. if (!executable(lock, false, mRequests.front())) return; // Pop the request off the queue. auto request = std::move(mRequests.front()); mRequests.pop_front(); // Perform post dequeue actions. dequeued(std::unique_lock(std::move(lock)), request); // Execute the request. execute(request); } } template<typename Request> auto FileContext::executeOrQueue(Request&& request) -> std::enable_if_t<IsFileRequestV<Request>> { // Sanity. assert(request.mCallback); // Make sure the request's been passed by rvalue reference. static_assert(std::is_rvalue_reference_v<decltype(request)>); // Request isn't executable so queue it for later execution. // // If executable(...) returns true, request will have acquired a read (or write) lock. if (std::unique_lock lock(mRequestsLock); !executable(lock, true, request)) return queue(std::move(lock), std::forward<Request>(request)); // Immediately reject the request if necessary. // // completed(...) needs to be called here as it expects a request to hold some lock. if (auto result = reject(request); result != FILE_SUCCESS) return completed(std::forward<Request>(request), result); // Otherwise execute the request. execute(request); } void FileContext::executed(FileReadRequestTag) { mReadWriteState.readCompleted(); } void FileContext::executed(FileWriteRequestTag) { mReadWriteState.writeCompleted(); } void FileContext::failed(FileReadRequest&& request, FileResult result) { // Delegate to template function. completed<>(std::move(request), result); } auto FileContext::grow(std::uint64_t newSize, std::uint64_t oldSize) -> std::pair<UniqueLock<Database>, Transaction> { // Make sure we have exclusive access to mRanges. std::lock_guard rangesLock(mRangesLock); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // So we can safely modify the database. auto transaction = database.transaction(); // Get our hands on this file's last range. auto last = mRanges.rbegin(); // Assume the file has no range for us to enlarge. FileRange range(oldSize, newSize); // File has a range we can enlarge. if (last != mRanges.rend() && last->first.mEnd == oldSize) { // Remove the range from the database. removeRanges(last->first, transaction); // Tweak our range. range.mBegin = last->first.mBegin; // Remove the range from memory. mRanges.remove(last); } // (Re)?add the range to the database. addRange(range, transaction); // (Re)?add the range to memory. mRanges.add(range, nullptr); // Return the transaction to our caller. return std::make_pair(std::move(databaseLock), std::move(transaction)); } std::unique_lock<std::recursive_mutex> FileContext::lock() const { return std::unique_lock(mRangesLock); } std::recursive_mutex& FileContext::mutex() const { return mRangesLock; } FileServiceOptions FileContext::options() const { return mService.options(); } template<typename Request> auto FileContext::queue(std::unique_lock<std::mutex> lock, Request&& request) -> std::enable_if_t<IsFileRequestV<Request>> { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); // Convenience. using Type = std::remove_reference_t<Request>; using Tag = std::in_place_type_t<Type>; // Push all but reclaim requests onto the end of our queue. if constexpr (IsFileReclaimRequestV<Type>) mRequests.emplace_front(Tag(), std::forward<Request>(request)); else mRequests.emplace_back(Tag(), std::forward<Request>(request)); // Perform post-queue actions. queued(std::move(lock), tag(request)); } void FileContext::queued([[maybe_unused]] std::unique_lock<std::mutex> lock, FileReadRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); } void FileContext::queued([[maybe_unused]] std::unique_lock<std::mutex> lock, FileWriteRequestTag) { assert(lock.mutex() == &mRequestsLock); assert(lock.owns_lock()); ++mNumPendingWriteRequests; } template<typename Request> auto FileContext::reject([[maybe_unused]] const Request& request) -> std::enable_if_t<IsFileRequestV<Request>, FileResult> { if constexpr (!IsFileReclaimRequestV<Request> && IsFileWriteRequestV<Request>) { if constexpr (IsFileRemoveRequestV<Request>) { if (request.mServiceOnly) return FILE_SUCCESS; } if (mKeyData) return FILE_READONLY; } return FILE_SUCCESS; } void FileContext::removeRanges(const FileRange& range, Transaction& transaction) { auto query = transaction.query(mService.queries().mRemoveFileRanges); query.param(":begin").set(range.mBegin); query.param(":end").set(range.mEnd); query.param(":id").set(mInfo->id()); query.execute(); } FileResult FileContext::setRemoved(bool replaced) try { // Convenience. auto& database = mService.database(); auto& queries = mService.queries(); // Acquire database lock. std::lock_guard lock(database); // Mark the file as removed in the database. auto transaction = database.transaction(); auto query = transaction.query(queries.mSetFileRemoved); query.param(":id").set(mInfo->id()); query.execute(); // Persist our changes. transaction.commit(); // Mark the file as removed in memory. mInfo->removed(replaced); // Let the caller know the file was removed. return FILE_SUCCESS; } catch (std::runtime_error& exception) { // Let debuggers know why we couldn't remove the file. FSErrorF("Unable to mark file %s as removed: %s", toString(mInfo->id()).c_str(), exception.what()); // Let our caller know we couldn't remove the file. return FILE_FAILED; } auto FileContext::shrink(std::uint64_t newSize, std::uint64_t oldSize) -> std::pair<UniqueLock<Database>, Transaction> { // Cancel any downloads in progress that would be "cut off." cancel(FileRange(oldSize, newSize)); // So we have exclusive access to mRanges. std::lock_guard rangesLock(mRangesLock); // Convenience. auto& database = mService.database(); // Acquire database lock. UniqueLock databaseLock(database); // So we can safely modify the database. auto transaction = database.transaction(); // Couldn't reduce the file's size. if (!mBuffer->truncate(newSize)) throw FSError1("Couldn't reduce file size"); // What ranges end at or after our file's new size? auto begin = mRanges.endsAfter(newSize); // No ranges end at or after our new size. if (begin == mRanges.end()) return std::make_pair(std::move(databaseLock), std::move(transaction)); // Convenience. FileRange range(begin->first.mBegin, oldSize); // Remove affected ranges from the database. removeRanges(range, transaction); // Remove affected ranges from memory. mRanges.remove(begin, mRanges.end()); // First range has been "cut" by the file's new size. if (range.mBegin < newSize) { // Adjust the range's end point. range.mEnd = newSize; // Readd the range to the database. addRange(range, transaction); // Readd the range to memory. mRanges.add(range, nullptr); } // Return the transaction to our caller. return std::make_pair(std::move(databaseLock), std::move(transaction)); } void FileContext::updateAccessAndModificationTimes(std::int64_t accessed, std::int64_t modified, Transaction& transaction) { auto query = transaction.query(mService.queries().mSetFileModificationTime); query.param(":accessed").set(accessed); query.param(":modified").set(modified); query.param(":id").set(mInfo->id()); query.execute(); } void FileContext::updateSize(std::uint64_t size, Transaction& transaction) { auto query = transaction.query(mService.queries().mSetFileSize); query.param(":allocated_size").set(mInfo->allocatedSize()); query.param(":id").set(mInfo->id()); query.param(":reported_size").set(mInfo->reportedSize()); query.param(":size").set(size); query.execute(); } FileContext::FileContext(Activity activity, FileAccessPtr file, FileInfoContextPtr info, std::optional<NodeKeyData> keyData, const FileRangeVector& ranges, FileServiceContext& service): FileRangeContextManager(), enable_shared_from_this(), mInstanceLogger("FileContext", *this, logger()), mActivity(std::move(activity)), mBuffer(std::make_shared<SparseFileBuffer>(*file, *info)), mInfo(std::move(info)), mFetchContext(), mFetchContextLock(), mFile(std::move(file)), mFlushContext(), mFlushContextLock(), mKeyData(std::move(keyData)), mNumPendingWriteRequests(0u), mRanges(), mRangesLock(), mReadWriteState(), mReclaimContext(), mReclaimContextLock(), mRequests(), mRequestsLock(), mService(service), mActivities() { for (auto& range: ranges) mRanges.add(range, nullptr); } FileContext::~FileContext() { // Cancel any downloads or pending requests. cancel(); // Remove ourselves from our service's index. mService.removeFromIndex(FileContextBadge(), mInfo->id()); } FileEventObserverID FileContext::addObserver(FileEventObserver observer) { return mInfo->addObserver(std::move(observer)); } void FileContext::append(FileAppendRequest request) { executeOrQueue(std::move(request)); } void FileContext::fetch(FileFetchRequest request) { executeOrQueue(std::move(request)); } void FileContext::fetchBarrier(FileFetchBarrierCallback callback) { // Sanity. assert(callback); // Acquire range lock. std::unique_lock lock(mRangesLock); // True if a download is in progress. auto fetching = [](const auto& entry) { return entry.second != nullptr; }; // fetching // How many fetches are in progress? auto count = std::count_if(mRanges.begin(), mRanges.end(), fetching); // No fetches are in progress. if (!count) { // Release range lock. lock.unlock(); // Invoke user callback. return callback(); } // To avoid copying state needlessly. struct BarrierContext { BarrierContext(FileFetchBarrierCallback callback, std::size_t count): mCallback(std::move(callback)), mCount{count} {} FileFetchBarrierCallback mCallback; std::atomic<std::size_t> mCount; }; // BarrierContext // Instantiate barrier context. auto context = std::make_shared<BarrierContext>(std::move(callback), count); // Called when a fetch has completed. auto fetched = [context](FileResult) { // Invoke the user's callback when all fetches have completed. if (context->mCount.fetch_sub(1) == 1) context->mCallback(); }; // fetched // Make sure fetched is called when each fetch has completed. for (const auto& entry: mRanges) { if (entry.second) entry.second->queue(fetched); } } void FileContext::flush(FileFlushRequest request) { executeOrQueue(std::move(request)); } FileInfo FileContext::info() const { return FileInfo(FileContextBadge(), mInfo); } FileRangeVector FileContext::ranges() const { // Will store the ranges we'll return our caller. FileRangeVector ranges; // Get exclusive access to mRanges. std::lock_guard guard(mRangesLock); // Populate our range vector. std::transform(mRanges.begin(), mRanges.end(), std::back_inserter(ranges), SelectFirst()); // Return ranges to our caller. return ranges; } void FileContext::read(FileReadRequest request) { executeOrQueue(std::move(request)); } void FileContext::reclaim(FileReclaimCallback callback) { // Make sure we have exclusive access to mReclaimContext. std::lock_guard lock(mReclaimContextLock); // A reclaim request is already in progress. if (mReclaimContext) return mReclaimContext->queue(std::move(callback)); // Create a new reclaim context. mReclaimContext = std::make_shared<ReclaimContext>(*this); // Queue our callback for later execution. mReclaimContext->queue(std::move(callback)); // So we can use the context's flushed method as a callback. FileFlushCallback flushed = std::bind(&ReclaimContext::flushed, mReclaimContext.get(), mReclaimContext, std::placeholders::_1); // Make sure this file's data has been flushed to the cloud. flush(FileFlushRequest{std::move(flushed)}); } void FileContext::remove(FileRemoveRequest request) { executeOrQueue(std::move(request)); } void FileContext::removeObserver(FileEventObserverID id) { mInfo->removeObserver(id); } bool FileContext::removed() const { return mInfo->removed(); } void FileContext::touch(FileTouchRequest request) { executeOrQueue(std::move(request)); } void FileContext::truncate(FileTruncateRequest request) { executeOrQueue(std::move(request)); } void FileContext::write(FileWriteRequest request) { executeOrQueue(std::move(request)); } void FileContext::FetchContext::completed(FileResult result) { // Acquire fetch context lock. std::unique_lock lock(mContext.mFetchContextLock); // Clear fetch context. mContext.mFetchContext = nullptr; // Steal queued requests. auto requests = std::exchange(mRequests, decltype(mRequests)()); // Release fetch context lock. lock.unlock(); // Execute queued requests. for (auto& request: requests) mContext.completed(std::move(request), result); } FileContext::FetchContext::FetchContext(FileContext& context, FileFetchRequest request): mInstanceLogger("FetchContext", *this, logger()), mActivity(context.mActivities.begin()), mContext(context), mRequests() { // Queue the request. queue(std::move(request)); } void FileContext::FetchContext::operator()(FetchContextPtr& context, FileResultOr<FileReadResult> result) { // Couldn't read this file's data. if (!result) return completed(result.error()); // No more content to read. if (!result->mLength) return completed(FILE_SUCCESS); // Convenience. auto offset = result->mOffset + result->mLength; auto length = mContext.mInfo->size() - offset; // Try and read the rest of the file's data. mContext.read(FileReadRequest{ std::bind(&FetchContext::operator(), this, std::move(context), std::placeholders::_1), FileRange(offset, offset + length)}); } void FileContext::FetchContext::queue(FileFetchRequest request) { // Acquire fetch context lock. std::lock_guard guard(mContext.mFetchContextLock); // Queue the request. mRequests.emplace_back(std::move(request)); } void FileContext::FlushContext::bound(FlushContextPtr& context, ErrorOr<NodeHandle> result) { // Acquire flush context lock. std::unique_lock lock(mContext.mFlushContextLock); // Couldn't flush the file's content. if (!result) return completed(std::move(context), std::move(lock), fileResultFromError(result.error())); // Try and update the file's handle. try { // Convenience. auto& info = *mContext.mInfo; auto& service = mContext.mService; auto& database = service.database(); // Acquire database lock. UniqueLock databaseLock(database); // Try and update the file's handle. auto transaction = database.transaction(); auto query = transaction.query(service.queries().mSetFileHandle); query.param(":handle").set(*result); query.param(":id").set(info.id()); query.execute(); // Persist our changes. transaction.commit(); // Update the file's node handle. info.flushed(*result); // File's flushed. completed(std::move(context), std::move(lock), FILE_SUCCESS); } catch (std::exception& exception) { // Let debuggers know why the flush failed. FSErrorF("Couldn't update file handle: %s: %s", toString(mContext.mInfo->id()).c_str(), exception.what()); // Couldn't flush the file's content. completed(std::move(context), std::move(lock), FILE_FAILED); } } template<typename Lock> void FileContext::FlushContext::completed(FlushContextPtr context, Lock&& lock, FileResult result) { // Sanity. assert(lock.mutex() == &mContext.mFlushContextLock); assert(lock.owns_lock()); // Make sure we're still the file's current flush context. if (mContext.mFlushContext == context) { // We are. Let our file know this flush has completed. mContext.mFlushContext = nullptr; } // Steal queued requests. auto requests = std::exchange(mRequests, decltype(mRequests)()); // Release lock. lock.unlock(); // Execute queued requests. for (auto& request: requests) mContext.completed(std::move(request), result); } Error FileContext::FlushContext::resolve(Client& client) { // File's never been flushed before. if (mHandle.isUndef()) return client.get(mLocation.mParentHandle).errorOr(API_OK); // Check if the file's node still exists. auto node = client.get(mHandle); // File's node no longer exists. if (!node) return node.error(); // Latch the node's current name and parent. mLocation.mName = std::move(node->mName); mLocation.mParentHandle = node->mParentHandle; // Let the caller know the node still exists. return API_OK; } void FileContext::FlushContext::uploaded(FlushContextPtr& context, ErrorOr<UploadResult> result) { // Acquire flush context lock. std::unique_lock lock(mContext.mFlushContextLock); // Couldn't upload the file's data. if (!result) return completed(std::move(context), std::move(lock), fileResultFromError(result.error())); // The file's been removed. if (mContext.mInfo->removed()) return completed(std::move(context), std::move(lock), FILE_REMOVED); // Upload's been cancelled. if (mRequests.empty()) return; // Release the lock. lock.unlock(); // So we can use our bound method as a callback. BoundCallback bound = std::bind(&FlushContext::bound, this, std::move(context), std::placeholders::_1); // Bind a name to our file's uploaded data. (*result)(std::move(bound), mHandle); } FileContext::FlushContext::FlushContext(FileContext& context, FileFlushRequest request): mInstanceLogger("FlushContext", *this, logger()), mActivity(context.mActivities.begin()), mContext(context), mHandle(context.mInfo->handle()), mLocation(context.mInfo->location().value()), mRequests(), mUpload() { mRequests.emplace_back(std::move(request)); } void FileContext::FlushContext::operator()(FlushContextPtr& context, FileResult result) { // Convenience. auto& mutex = mContext.mFlushContextLock; // Couldn't retrieve this file's content. if (result != FILE_SUCCESS) return completed(std::move(context), std::unique_lock(mutex), result); // Convenience. auto& service = mContext.mService; auto& client = service.client(); auto& info = *mContext.mInfo; // Check whether the file or its intended parent still exists. result = fileResultFromError(resolve(client)); // Acquire context lock. std::unique_lock lock(mutex); // File or its intended parent no longer exists. if (result != FILE_SUCCESS) return completed(std::move(context), std::move(lock), result); // No requests? Flush must have been cancelled. if (mRequests.empty()) return; // Where is this file's data stored? auto path = service.path(info.id()); // Convenience. auto& [name, parent] = mLocation; // Instantiate an upload. mUpload = client.upload(path, name, parent, path); // So we can use our uploaded method as a callback. UploadCallback callback = std::bind(&FlushContext::uploaded, this, std::move(context), std::placeholders::_1); // Begin the upload. mUpload->begin(std::move(callback)); } template<typename Lock> void FileContext::FlushContext::cancel(FlushContextPtr context, Lock&& lock) { assert(context); assert(lock.mutex() == &context->mContext.mFlushContextLock); assert(lock.owns_lock()); auto upload = std::exchange(context->mUpload, nullptr); // No upload's in progress. if (!upload) return context->completed(context, std::move(lock), FILE_CANCELLED); // Release the lock. lock.unlock(); // Cancel the upload. upload->cancel(); } void FileContext::FlushContext::queue(FileFlushRequest request) { // Acquire flush context lock. std::lock_guard guard(mContext.mFlushContextLock); // Queue the request. mRequests.emplace_back(std::move(request)); } template<typename Lock> void FileContext::ReclaimContext::completed(ReclaimContextPtr context, Lock&& lock, FileResultOr<std::uint64_t> result) { // Sanity. assert(lock.mutex() == &mContext.mReclaimContextLock); assert(lock.owns_lock()); // Make sure we're still the current reclaim context. if (mContext.mReclaimContext == context) { // We are so let the file know we've completed. mContext.mReclaimContext = nullptr; } // Steal queued callbacks. auto callbacks = std::exchange(mCallbacks, decltype(mCallbacks)()); // Release reclaim context lock. lock.unlock(); // Execute queued callbacks. for (auto& callback: callbacks) callback(result); } FileContext::ReclaimContext::ReclaimContext(FileContext& context): mInstanceLogger("ReclaimContext", *this, logger()), mActivity(context.mActivities.begin()), mAllocatedSize(context.mInfo->allocatedSize()), mCallbacks(), mContext(context) {} template<typename Lock> void FileContext::ReclaimContext::cancel(ReclaimContextPtr& context, Lock&& lock) { // Sanity. assert(context); assert(lock.mutex() == &context->mContext.mReclaimContextLock); assert(lock.owns_lock()); context->completed(context, std::forward<Lock>(lock), FILE_CANCELLED); } void FileContext::ReclaimContext::flushed(ReclaimContextPtr& context, FileResult result) { // Acquire reclaim context lock. std::unique_lock lock(mContext.mReclaimContextLock); // Couldn't flush this file's data to the cloud. if (result != FILE_SUCCESS) return completed(std::move(context), std::move(lock), result); // Reclamation has been cancelled. if (mCallbacks.empty()) return; // Release reclaim context lock. lock.unlock(); // So we can use this context's completed method as a callback. auto callback = [context = std::move(context), this](auto result) mutable { completed(std::move(context), std::unique_lock(mContext.mReclaimContextLock), result); }; // callback // Queue the reclaim request for execution. mContext.queue(std::unique_lock(mContext.mRequestsLock), FileReclaimRequest{mAllocatedSize, std::move(callback)}); } void FileContext::ReclaimContext::queue(FileReclaimCallback callback) { // Sanity. assert(callback); // Queue the callback for later execution. mCallbacks.emplace_back(swallow(std::move(callback), "reclaim")); } template<typename Callback> Callback swallow(Callback callback, const char* name) { return [callback = std::move(callback), name](auto&&... arguments) { try { // Try and execute the user's callback. std::invoke(callback, std::forward<decltype(arguments)>(arguments)...); } catch (std::exception& exception) { // User's callback threw an exception we can log. FSErrorF("User %s callback threw an exception: %s", name, exception.what()); } }; } template<typename Request> auto tag(const Request&) -> std::enable_if_t<IsFileRequestV<Request>, typename Request::Type> { return typename Request::Type(); } } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_event_emitter.cpp�������������������������������������������������0000664�0000000�0000000�00000003255�15162662266�0022257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_event.h> #include <mega/file_service/file_event_emitter.h> #include <mega/file_service/file_event_observer_result.h> #include <cassert> namespace mega { namespace file_service { FileEventObserverID FileEventEmitter::addObserver(FileEventObserver observer) { // Make sure no one else is modifying our map of observers. std::lock_guard guard(mObserversLock); // Add the observer to our map. auto [iterator, _] = mObservers.emplace(mNextID++, std::move(observer)); // Return the observer's ID to our caller. return std::make_pair(this, iterator->first); } void FileEventEmitter::notify(const FileEvent& event) { // Make sure no threads are modifying our map of observers. std::lock_guard guard(mObserversLock); // Transmit event to each observer. for (auto i = mObservers.begin(); i != mObservers.end();) { // Just in case the observer removes itself. auto j = i++; // Transmit event to our observer. auto result = j->second(event); // Observer wants to be removed. if (result == FILE_EVENT_OBSERVER_REMOVE) mObservers.erase(j); } } void FileEventEmitter::removeObserver(FileEventObserverID id) { // Convenience. auto* emitter = std::get<FileEventEmitter*>(id); // For debugging purposes. assert(emitter == this); // ID was generated by some other event emitter. if (emitter != this) return; // Make sure no one else is modifying our map of observer. std::lock_guard guard(mObserversLock); // Remove the the observer from the map. mObservers.erase(std::get<std::uint64_t>(id)); } } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_id.cpp������������������������������������������������������������0000664�0000000�0000000�00000004537�15162662266�0020005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/base64.h> #include <mega/common/query.h> #include <mega/file_service/file_id.h> #include <mega/types.h> #include <cinttypes> #include <limits> namespace mega { namespace common { using namespace file_service; FileID SerializationTraits<FileID>::from(const Field& field) { auto value = field.get<std::uint64_t>(); if (synthetic(value)) return FileID::from(value); return FileID::from(NodeHandle().set6byte(value)); } void SerializationTraits<FileID>::to(Parameter& parameter, FileID id) { parameter.set(id.toU64()); } } // common namespace file_service { static constexpr auto kSynthetic = UINT64_C(1) << 63u; static constexpr auto kUndefined = std::numeric_limits<std::uint64_t>::max(); FileID::FileID(std::uint64_t id): mID(id) {} FileID::FileID(): FileID(kUndefined) {} FileID::operator bool() const { return mID != kUndefined; } bool FileID::operator==(const FileID& rhs) const { return mID == rhs.mID; } bool FileID::operator<(const FileID& rhs) const { return mID < rhs.mID; } bool FileID::operator!=(const FileID& rhs) const { return !(*this == rhs); } bool FileID::operator!() const { return !operator bool(); } FileID FileID::from(NodeHandle handle) { if (!handle.isUndef()) return FileID(handle.as8byte()); return FileID(); } FileID FileID::from(const std::string& string) { if (!string.empty()) return from(string.c_str()); return FileID(); } FileID FileID::from(const char* string) { FileID id; if (!string) return id; auto length = Base64::atob(string, reinterpret_cast<byte*>(&id.mID), sizeof(id.mID)); if (static_cast<std::size_t>(length) < sizeof(id.mID)) return FileID(); return id; } FileID FileID::from(std::uint64_t u64) { return FileID(kSynthetic | u64); } NodeHandle FileID::toHandle() const { assert(!synthetic(mID)); if (*this) return NodeHandle().set6byte(mID); return NodeHandle(); } std::string FileID::toString() const { return std::string(Base64Str<sizeof(mID)>(&mID)); } std::uint64_t FileID::toU64() const { return mID; } bool synthetic(FileID id) { return synthetic(id.toU64()); } bool synthetic(std::uint64_t u64) { return u64 != kUndefined && u64 >= kSynthetic; } std::string toString(FileID id) { return id.toString(); } } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_info.cpp����������������������������������������������������������0000664�0000000�0000000�00000003623�15162662266�0020337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_context_badge.h> #include <mega/file_service/file_info.h> #include <mega/file_service/file_info_context.h> #include <mega/file_service/file_location.h> #include <mega/file_service/file_service_context_badge.h> #include <mega/file_service/logger.h> namespace mega { namespace file_service { FileInfo::FileInfo(FileContextBadge, FileInfoContextPtr context): mInstanceLogger("FileInfo", *this, logger()), mContext(std::move(context)) {} FileInfo::FileInfo(FileServiceContextBadge, FileInfoContextPtr context): mInstanceLogger("FileInfo", *this, logger()), mContext(std::move(context)) {} FileInfo::FileInfo(const FileInfo& other): mInstanceLogger("FileInfo", *this, logger()), mContext(other.mContext) {} FileInfo::~FileInfo() = default; FileEventObserverID FileInfo::addObserver(FileEventObserver observer) { return mContext->addObserver(std::move(observer)); } FileInfo& FileInfo::operator=(const FileInfo& rhs) { if (this != &rhs) mContext = rhs.mContext; return *this; } std::int64_t FileInfo::accessed() const { return mContext->accessed(); } std::uint64_t FileInfo::allocatedSize() const { return mContext->allocatedSize(); } bool FileInfo::dirty() const { return mContext->dirty(); } NodeHandle FileInfo::handle() const { return mContext->handle(); } FileID FileInfo::id() const { return mContext->id(); } std::optional<FileLocation> FileInfo::location() const { return mContext->location(); } std::int64_t FileInfo::modified() const { return mContext->modified(); } void FileInfo::removeObserver(FileEventObserverID id) { mContext->removeObserver(id); } bool FileInfo::removed() const { return mContext->removed(); } std::uint64_t FileInfo::reportedSize() const { return mContext->reportedSize(); } std::uint64_t FileInfo::size() const { return mContext->size(); } } // file_service } // mega �������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_info_context.cpp��������������������������������������������������0000664�0000000�0000000�00000016113�15162662266�0022101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/lock.h> #include <mega/common/utility.h> #include <mega/file_service/file_event.h> #include <mega/file_service/file_info_context.h> #include <mega/file_service/file_info_context_badge.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_service_context.h> #include <mega/file_service/logger.h> #include <mega/filesystem.h> #include <utility> namespace mega { namespace file_service { using namespace common; template<typename T> auto FileInfoContext::get(T FileInfoContext::* const property) const { SharedLock guard(mLock); return this->*property; } template<typename T, typename U> void FileInfoContext::set(T FileInfoContext::*property, U&& value) { std::lock_guard guard(mLock); this->*property = std::forward<U>(value); } void FileInfoContext::notify(const FileEvent& event) { // Notify observers interested in this particular file. FileEventEmitter::notify(event); // Notify observers interested in all files. mService.notify(event); } FileInfoContext::FileInfoContext(std::int64_t accessed, Activity activity, std::uint64_t allocatedSize, bool dirty, NodeHandle handle, FileID id, std::optional<FileLocation> location, std::int64_t modified, std::uint64_t reportedSize, FileServiceContext& service, std::uint64_t size): FileEventEmitter(), mInstanceLogger("FileInfoContext", *this, logger()), mAccessed(accessed), mActivity(std::move(activity)), mAllocatedSize(allocatedSize), mDirty(dirty), mHandle(handle), mID(id), mLocation(std::move(location)), mLock(), mModified(modified), mRemoved(false), mReportedSize(reportedSize), mService(service), mSize(size) {} FileInfoContext::~FileInfoContext() { mService.removeFromIndex(FileInfoContextBadge(), *this); } void FileInfoContext::accessed(std::int64_t accessed) { std::lock_guard guard(mLock); mAccessed = std::max(accessed, mAccessed); } std::int64_t FileInfoContext::accessed() const { return get(&FileInfoContext::mAccessed); } void FileInfoContext::allocatedSize(std::uint64_t allocatedSize) { set(&FileInfoContext::mAllocatedSize, allocatedSize); } std::uint64_t FileInfoContext::allocatedSize() const { return get(&FileInfoContext::mAllocatedSize); } bool FileInfoContext::dirty() const { return get(&FileInfoContext::mDirty); } void FileInfoContext::flushed(NodeHandle handle) { // Sanity. assert(!handle.isUndef()); // Let observers know the file's been flushed. notify( [=]() { // Make sure no one else is changing our information. std::lock_guard guard(mLock); // Flushed files are never dirty. mDirty = false; // Update the file's node handle. mHandle = handle; return FileFlushEvent{handle, mID}; }()); } NodeHandle FileInfoContext::handle() const { return get(&FileInfoContext::mHandle); } FileID FileInfoContext::id() const { return mID; } void FileInfoContext::location(const FileLocation& to) { notify( [&]() { // Make sure no one else is modifying the location. std::lock_guard guard(mLock); // Sanity. assert(mLocation); // Latch the file's current location. auto from = std::move(*mLocation); // Update the file's current location. mLocation = to; // Return a moved event. return FileMoveEvent{std::move(from), to, mID}; }()); } std::optional<FileLocation> FileInfoContext::location() const { return get(&FileInfoContext::mLocation); } void FileInfoContext::modified(std::int64_t accessed, std::int64_t modified) { // Update the file's modification time and return a new event. notify( [accessed, modified, this]() { // Make sure no one else is changing this file's information. UniqueLock guard(mLock); // Mark file as having been locally modified. mDirty = true; // Update the file's access time. mAccessed = std::max(accessed, mAccessed); // Update the file's modification time. mModified = modified; // Return an event to our caller. return FileTouchEvent{mID, mModified}; }()); } std::int64_t FileInfoContext::modified() const { return get(&FileInfoContext::mModified); } void FileInfoContext::removed(bool replaced) { set(&FileInfoContext::mRemoved, true); notify(FileRemoveEvent{mID, replaced}); } bool FileInfoContext::removed() const { return get(&FileInfoContext::mRemoved); } void FileInfoContext::reportedSize(std::uint64_t reportedSize) { set(&FileInfoContext::mReportedSize, reportedSize); } std::uint64_t FileInfoContext::reportedSize() const { return get(&FileInfoContext::mReportedSize); } std::uint64_t FileInfoContext::size() const { return get(&FileInfoContext::mSize); } void FileInfoContext::truncated(std::int64_t modified, std::uint64_t size) { // Update the file's information and return an event for notification. notify( [modified, size, this]() mutable { // Convenience. using std::swap; // Make sure no one else changes this file's information. UniqueLock guard(mLock); // Mark file as having been locally modified. mDirty = true; // Update the file's access time. mAccessed = std::max(mAccessed, modified); // Update the file's modification time. mModified = modified; // Update the file's size. swap(mSize, size); // Assume the file's size hasn't decreased. FileTruncateEvent event{std::nullopt, mID, mSize}; // File's size has decreased. if (mSize < size) event.mRange.emplace(mSize, size); // Return event to our caller. return event; }()); } void FileInfoContext::written(std::int64_t modified, const FileRange& range) { // Update the file's information and return an event for notification. notify( [modified, &range, this]() { // Make sure we have exclusive access to our information. UniqueLock guard(mLock); // Mark file as having been locally modified. mDirty = true; // Update the file's access time. mAccessed = std::max(mAccessed, modified); // Update the file's modification time. mModified = modified; // Update the file's size. mSize = std::max(mSize, range.mEnd); // Return a suitable event. return FileWriteEvent{range, mID}; }()); } } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_range.cpp���������������������������������������������������������0000664�0000000�0000000�00000001774�15162662266�0020505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/utility.h> #include <mega/file_service/file_range.h> #include <cassert> #include <cinttypes> #include <utility> namespace mega { namespace file_service { using namespace common; FileRange::FileRange(std::uint64_t begin, std::uint64_t end): mBegin(std::min(begin, end)), mEnd(std::max(begin, end)) {} FileRange combine(const FileRange& lhs, const FileRange& rhs) { auto begin = std::min(lhs.mBegin, rhs.mBegin); auto end = std::max(lhs.mEnd, rhs.mEnd); return FileRange(begin, end); } FileRange extend(const FileRange& range, std::uint64_t adjustment) { auto begin = range.mBegin - std::min(adjustment, range.mBegin); auto end = range.mEnd + adjustment; return FileRange(begin, end); } std::string toString(const FileRange& range) { return format("[0x%" PRIx64 "-0x%" PRIx64 "]", range.mBegin, range.mEnd); } std::ostream& operator<<(std::ostream& ostream, const FileRange& range) { return ostream << toString(range); } } // file_service } // mega ����sdk-10.11.0/src/file_service/file_range_context.cpp�������������������������������������������������0000664�0000000�0000000�00000022015�15162662266�0022240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/client.h> #include <mega/common/expected.h> #include <mega/common/partial_download.h> #include <mega/file_service/buffer.h> #include <mega/file_service/displaced_buffer.h> #include <mega/file_service/file_buffer.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_range_context.h> #include <mega/file_service/file_range_context_manager.h> #include <mega/file_service/file_read_request.h> #include <mega/file_service/file_result.h> #include <mega/file_service/file_service_options.h> #include <mega/file_service/memory_buffer.h> #include <mega/types.h> #include <cassert> namespace mega { namespace file_service { using namespace common; constexpr std::uint64_t MinimumLength = 1u << 18; // Check if result is a retryable error. static bool retryable(const Error& result); template<typename Lock> void FileRangeContext::completed([[maybe_unused]] Lock&& lock, Error result) { // Sanity. assert(lock.owns_lock()); assert(lock.mutex() == &mManager.mutex()); // Convenience. FileRange range(mIterator->first.mBegin, mEnd); // Let the manager know this download has completed. mManager.completed(*mBuffer, mIterator, range); // Complete as many requests as we can. dispatch(std::move(mBuffer), range.mBegin, 1); // Translate SDK result. auto result_ = fileResultFromError(result); // Download didn't complete successfully. if (result_ != FILE_SUCCESS) { // Fail any remaining requests. for (auto i = mRequests.begin(); i != mRequests.end();) { // Convenience. auto& request = const_cast<FileReadRequest&>(*i); // Fail the request. mManager.failed(std::move(request), result_); // Remove the request from our set. i = mRequests.erase(i); } } // Let any waiters know this range's download has completed. for (auto& callback: mCallbacks) mManager.execute(std::bind(std::move(callback), result_)); } void FileRangeContext::completed(Error result) { // Get a reference to our context. // // We're doing this here for two reasons: // // 1. We want to make sure this instance is kept alive until we've // finished processing this donwload's completion. // // 2. We want to make sure that the lock we acquire immediately below is // released before this instance itself is destroyed. [[maybe_unused]] auto context = std::move(mIterator->second); // Complete the download. completed(mManager.lock(), result); } auto FileRangeContext::data(const void* buffer, std::uint64_t, std::uint64_t length) -> std::variant<Abort, Continue> { // Convenience. auto offset = mEnd - mIterator->first.mBegin; // Try and write data to our buffer. auto [count, success] = mBuffer->write(buffer, offset, length); // Lock our manager. auto lock = mManager.lock(); // Bump our buffer iterator. mEnd += count; // Couldn't write all of the data to our buffer. if (!success) return Abort(); // Don't dispatch any requests here if this is the last piece of the // file. Instead, dispatch them when the download is completed. // // This is necessary to stabilize the integration tests as they expect // all necessary processing to have completed by the time any final read // callbacks have been executed. if (mEnd == mIterator->first.mEnd) return Continue(); // Dispatch what requests we can. dispatch(mBuffer, mIterator->first.mBegin, MinimumLength); // Let the caller know the download should continue. return Continue(); } void FileRangeContext::dispatch(BufferPtr buffer, const std::uint64_t begin, std::uint64_t minimumLength) { // What requests might we be able to satisfy? auto i = mRequests.begin(); auto j = mRequests.upper_bound(mEnd); // Dispatch as many requests as we can. while (i != j) { // Copying the iterator keeps logic clean. auto k = i++; // Evil but necessary. auto& request = const_cast<FileReadRequest&>(*k); // Can't dispatch this request. if (!dispatchable(request, minimumLength)) continue; // Convenience. auto& range = request.mRange; // Tweak the request. range.mEnd = std::min(mEnd, range.mEnd); // Dispatch the request. mManager.completed( [&]() -> BufferPtr { if (auto displacement = range.mBegin - begin) return displace(buffer, displacement); return buffer; }(), std::move(request)); // Remove the request from our set. mRequests.erase(k); } } bool FileRangeContext::dispatchable(const FileReadRequest& request, std::uint64_t minimumLength) const { // Convenience. auto& [m, n] = request.mRange; // Request is dispatchable if: // - We have enough data to fully satisfy the read. // - We have enough data to provide minimumLength bytes of data. return n <= mEnd || mEnd - std::min(m, mEnd) >= minimumLength; } auto FileRangeContext::failed(Error result, int retries) -> std::variant<Abort, Retry> { // Failure isn't due to a retryable error. if (!retryable(result)) return Abort(); // Convenience. auto options = mManager.options(); // Or if we've already retried the download too many times. if (static_cast<std::uint64_t>(retries) >= options.mMaximumRangeRetries) return Abort(); // Retry the download. return options.mRangeRetryBackoff; } FileRangeContext::FileRangeContext(Activity activity, FileRangeContextPtrMap::Iterator iterator, FileRangeContextManager& manager): PartialDownloadCallback(), mInstanceLogger("FileRangeContext", *this, logger()), mActivity(std::move(activity)), mBuffer(), mCallbacks(), mDownload(), mEnd(iterator->first.mBegin), mIterator(iterator), mManager(manager), mRequests() {} FileRangeContext::~FileRangeContext() { // No requests should be queued at this point. assert(mRequests.empty()); } void FileRangeContext::cancel() { // Download's alive so cancel it. if (auto download = mDownload) download->cancel(); } auto FileRangeContext::download(Client& client, FileBufferPtr buffer, NodeHandle handle, const std::optional<NodeKeyData>& keyData) -> PartialDownloadPtr { // Sanity. assert(buffer); assert(!mBuffer); assert(!mDownload); // Convenience. auto offset = mIterator->first.mBegin; auto length = mIterator->first.mEnd - offset; // Create a suitable buffer for this range. mBuffer = [&]() -> BufferPtr { // How much data can we store directly in memory? constexpr std::uint64_t memoryThreshold = 1u << 22; // Range is small enough that it can fit entirely in memory. if (length <= memoryThreshold) return std::make_shared<MemoryBuffer>(length); // Range is displaced. if (offset) return std::make_shared<DisplacedBuffer>(std::move(buffer), offset); // Range isn't displaced. return std::move(buffer); }(); // Try and create a partial download. auto download = keyData ? client.partialDownload(*this, handle, *keyData, length, offset) : client.partialDownload(*this, handle, length, offset); // Couldn't create the download. if (!download) return completed(download.error()), nullptr; // Grab download. mDownload = std::move(*download); // Return the download to our caller. return mDownload; } void FileRangeContext::queue(FileFetchCallback callback) { // Queue the callback for later execution. mCallbacks.emplace_back(std::move(callback)); } void FileRangeContext::queue(FileReadRequest request) { // Request isn't dispatchable so queue it for later execution. if (!dispatchable(request, MinimumLength)) return mRequests.emplace(std::move(request)), void(); // Assume the request requires no displacement. auto buffer = mBuffer; // What is the request's displacement? auto displacement = request.mRange.mBegin - mIterator->first.mBegin; // Buffer needs to be displaced. if (displacement) buffer = displace(std::move(buffer), displacement); // Dispatch the request. mManager.completed(std::move(buffer), std::move(request)); } bool retryable(const Error& result) { // Client's being torn down or the download has been cancelled. if (result == API_EINCOMPLETE) return false; // File's been taken down because it breached our terms and conditions. if (result == API_ETOOMANY && result.hasExtraInfo()) return false; // Retry all other failures. return true; } } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_read_request_set.cpp����������������������������������������������0000664�0000000�0000000�00000001107�15162662266�0022735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_read_request.h> #include <mega/file_service/file_read_request_set.h> namespace mega { namespace file_service { bool FileReadRequestLess::operator()(const FileReadRequest& lhs, const FileReadRequest& rhs) const { // Convenience. auto [i, j] = lhs.mRange; auto [m, n] = rhs.mRange; if (i < m) return true; if (i > m) return false; return j < n; } bool FileReadRequestLess::operator()(std::uint64_t lhs, const FileReadRequest& rhs) const { return lhs < rhs.mRange.mBegin; } } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_read_write_state.cpp����������������������������������������������0000664�0000000�0000000�00000002206�15162662266�0022725�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_read_write_state.h> #include <cassert> #include <limits> namespace mega { namespace file_service { bool FileReadWriteState::read() { std::lock_guard guard(mLock); // Convenience. auto min = std::numeric_limits<long>::min(); // Check if we can perform the read. auto adjustment = static_cast<long>(mState <= 0 && mState > min); // Adjust state. mState -= adjustment; // Let the caller know if the read can be performed. return adjustment; } void FileReadWriteState::readCompleted() { std::lock_guard guard(mLock); // Sanity. assert(mState < 0); // Adjust state. ++mState; } bool FileReadWriteState::write() { std::lock_guard guard(mLock); // Check if we can perform the write. auto adjustment = static_cast<long>(mState == 0); // Adjust state. mState += adjustment; // Let the caller know if the write can be performed. return adjustment; } void FileReadWriteState::writeCompleted() { std::lock_guard guard(mLock); // Sanity. assert(mState == 1); // Adjust state. --mState; } } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_result.cpp��������������������������������������������������������0000664�0000000�0000000�00000002373�15162662266�0020723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_result.h> #include <mega/types.h> #include <cassert> namespace mega { namespace file_service { FileResult fileResultFromError(Error result) { switch (result) { case API_EINCOMPLETE: return FILE_CANCELLED; case API_ENOENT: return FILE_REMOVED; case API_OK: return FILE_SUCCESS; case LOCAL_LOGGED_OUT: return FILE_CANCELLED; default: return FILE_FAILED; } } const char* toDescription(FileResult result) { static const char* descriptions[] = { #define DEFINE_DESCRIPTION(name, description) description, DEFINE_FILE_RESULTS(DEFINE_DESCRIPTION) #undef DEFINE_DESCRIPTION }; // descriptions if (result < sizeof(descriptions)) return descriptions[result]; assert(false && "Unhandled file result enumerant"); return "N/A"; } const char* toString(FileResult result) { static const char* names[] = { #define DEFINE_NAME(name, description) #name, DEFINE_FILE_RESULTS(DEFINE_NAME) #undef DEFINE_NAME }; // names if (result < sizeof(names)) return names[result]; assert(false && "Unhandled file result enumerant"); return "N/A"; } } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_service.cpp�������������������������������������������������������0000664�0000000�0000000�00000010652�15162662266�0021044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/lock.h> #include <mega/file_service/file.h> #include <mega/file_service/file_id.h> #include <mega/file_service/file_info.h> #include <mega/file_service/file_service.h> #include <mega/file_service/file_service_context.h> #include <mega/file_service/file_service_options.h> #include <mega/file_service/file_service_result.h> #include <mega/file_service/file_service_result_or.h> #include <mega/file_service/logging.h> #include <stdexcept> namespace mega { namespace file_service { using namespace common; FileService::FileService(): mInstanceLogger("FileService", *this, logger()), mContext(), mContextLock() {} FileService::~FileService() = default; auto FileService::add(NodeHandle handle, const NodeKeyData& keyData, std::size_t size) -> FileServiceResultOr<FileID> { SharedLock guard(mContextLock); if (mContext) return mContext->add(handle, keyData, size); return unexpected(FILE_SERVICE_UNINITIALIZED); } auto FileService::addObserver(FileEventObserver observer) -> FileServiceResultOr<FileEventObserverID> { SharedLock guard(mContextLock); if (mContext) return mContext->addObserver(std::move(observer)); return unexpected(FILE_SERVICE_UNINITIALIZED); } auto FileService::create(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); return mContext->create(parent, name); } void FileService::deinitialize() { UniqueLock guard(mContextLock); mContext.reset(); } auto FileService::info(FileID id) -> FileServiceResultOr<FileInfo> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); if (!id) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); return mContext->info(id); } auto FileService::open(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); return mContext->open(parent, name); } auto FileService::open(FileID id) -> FileServiceResultOr<File> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); if (!id) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); return mContext->open(id); } auto FileService::options(const FileServiceOptions& options) -> FileServiceResult { SharedLock guard(mContextLock); if (!mContext) return FILE_SERVICE_UNINITIALIZED; mContext->options(options); return FILE_SERVICE_SUCCESS; } auto FileService::options() -> FileServiceResultOr<FileServiceOptions> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); return mContext->options(); } auto FileService::initialize(Client& client, const FileServiceOptions& options) -> FileServiceResult try { UniqueLock guard(mContextLock); if (mContext) { FSError1("File Service has already been initialized"); return FILE_SERVICE_ALREADY_INITIALIZED; } mContext = std::make_unique<FileServiceContext>(client, options); FSInfo1("File Service initialized"); return FILE_SERVICE_SUCCESS; } catch (std::runtime_error& exception) { FSErrorF("Unable to initialize File Service: %s", exception.what()); return FILE_SERVICE_UNEXPECTED; } auto FileService::initialize(Client& client) -> FileServiceResult { return initialize(client, FileServiceOptions()); } auto FileService::purge() -> FileServiceResult { SharedLock guard(mContextLock); if (!mContext) return FILE_SERVICE_UNINITIALIZED; return mContext->purge(); } void FileService::reclaim(ReclaimCallback callback) { SharedLock guard(mContextLock); if (!mContext) return callback(FILE_SERVICE_UNINITIALIZED); return mContext->reclaim(std::move(callback)); } auto FileService::removeObserver(FileEventObserverID id) -> FileServiceResult { SharedLock guard(mContextLock); if (!mContext) return FILE_SERVICE_UNINITIALIZED; mContext->removeObserver(id); return FILE_SERVICE_SUCCESS; } auto FileService::storageUsed() -> FileServiceResultOr<std::uint64_t> { SharedLock guard(mContextLock); if (!mContext) return unexpected(FILE_SERVICE_UNINITIALIZED); return mContext->storageUsed(); } } // file_service } // mega ��������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_service_context.cpp�����������������������������������������������0000664�0000000�0000000�00000162333�15162662266�0022614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/client.h> #include <mega/common/lock.h> #include <mega/common/node_event.h> #include <mega/common/node_event_queue.h> #include <mega/common/node_event_type.h> #include <mega/common/node_info.h> #include <mega/common/scoped_query.h> #include <mega/common/transaction.h> #include <mega/common/utility.h> #include <mega/file_service/database_builder.h> #include <mega/file_service/file.h> #include <mega/file_service/file_context.h> #include <mega/file_service/file_context_badge.h> #include <mega/file_service/file_event.h> #include <mega/file_service/file_id.h> #include <mega/file_service/file_info.h> #include <mega/file_service/file_info_context.h> #include <mega/file_service/file_info_context_badge.h> #include <mega/file_service/file_move_event.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_remove_event.h> #include <mega/file_service/file_result.h> #include <mega/file_service/file_service_context.h> #include <mega/file_service/file_service_result.h> #include <mega/file_service/file_service_result_or.h> #include <mega/file_service/logging.h> #include <mega/filesystem.h> #include <chrono> #include <stdexcept> namespace mega { namespace file_service { // Convenience. using namespace common; using namespace std::chrono; class FileServiceContext::EventProcessor { // Called when a new node has been added. // // If the event describes a node we added, ignore it. // // Otherwise, check if the node would "replace" a file we manage. // If so, remove the file. // // Note that a directory with the same name and parent as some file that // we manage will "replace" the file that we manage. void added(const NodeEvent& event); // Called to dispatch an event. void dispatch(const NodeEvent& event); // Retrieve a file's info context. FileInfoContextPtr info(FileID id); // Mark an in-memory file as removed. // // Returns true iff the file was in memory. bool mark(FileID id, bool replaced); // Called when a node has been moved or renamed. // // If the event describes a node that would "replace" a file we manage, // remove the file. // // If the event describe a file that we manage and that file has been // superseded by a new version in the cloud, remove it. // // Otherwise, update the file's location to match the cloud. void moved(const NodeEvent& event); // Remove a file from the database and from storage. void remove(FileID id, bool replaced); // Called when a node has been removed. // // If event describes a directory, delegate to removedDirectory(...). // // If event describes a file we manage, remove it. // Otherwise, ignore the event. void removed(const NodeEvent& event); // Called when a directory node has been removed. // // Remove any files associated with the directory described by event. // // This is necessary as the directory may conceptually contain one or // more local files. void removedDirectory(const NodeEvent& event); // The service we're processing events for. FileServiceContext& mService; // Ensures we have exclusive access to the service. UniqueLock<SharedMutex> mServiceLock; // Ensures we have exclusive access to the database. UniqueLock<Database> mDatabaseLock; // Provides convenient access to the service's queries. FileServiceQueries& mQueries; // Provides access to the database while we process events. Transaction mTransaction; public: EventProcessor(FileServiceContext& service); // Process zero or more node events. void operator()(NodeEventQueue& events); }; // EventProcessor class FileServiceContext::ReclaimContext { // Reclaim a single file. void reclaim(ReclaimContextPtr context, FileID id); // Reclaim zero or more files in a batch. template<typename Lock> void reclaimBatch(ReclaimContextPtr context, Lock&& lock); // Called when a file has been reclaimed. void reclaimed(ReclaimContextPtr context, FileResultOr<std::uint64_t> result); // Logs instance lifetime. common::InstanceLogger<ReclaimContext> mInstanceLogger; // Make sure our service stays alive as long as we do. Activity mActivity; // Who should we call when reclamation completes? std::vector<ReclaimCallback> mCallbacks; // What files are we reclaiming? FileIDVector mIDs; // Serializes access to mCount, mReclaimed and mResult. std::recursive_mutex mLock; // Tracks how many files are currently being reclaimed. std::size_t mNumPending; // Tracks how much space we've recovered. std::uint64_t mReclaimed; // Tracks whether we encountered any failures. FileServiceResult mResult; // What service are we reclaiming storage for? FileServiceContext& mService; public: ReclaimContext(FileServiceContext& service); // Called when the reclamation has completed. void completed(FileServiceResultOr<std::uint64_t> result); // Queue a callback for later execution. void queue(ReclaimCallback callback); // Reclaim zero or more files. void reclaim(ReclaimContextPtr context); }; // ReclaimContext static Database createDatabase(const LocalPath& databasePath); static bool reclamationEnabled(const FileServiceOptions& options); template<typename Lock> FileID FileServiceContext::allocateID([[maybe_unused]] Lock&& lock, Transaction& transaction) { assert(lock.mutex() == &mDatabase); assert(lock.owns_lock()); // Check if we need to generate a new file ID. auto query = transaction.query(mQueries.mGetFreeFileID); query.execute(); // We need to generate a new file ID. if (!query) { // Determine the next allocable ID. query = transaction.query(mQueries.mGetNextFileID); query.execute(); // Latch the next available ID. auto next = query.field("next").get<std::uint64_t>(); // Make sure we haven't exhausted the space of synthetic IDs. assert(!synthetic(next)); if (synthetic(next)) throw std::runtime_error("Exhausted space of synthetic IDs"); // Note that this ID has been allocated. query = transaction.query(mQueries.mSetNextFileID); query.param(":next").set(next + 1); query.execute(); // Return the ID to our caller. return FileID::from(next); } // We can recycle a previously allocated ID. auto id = query.field("id").get<std::uint64_t>(); // id is no longer available for allocation. query = transaction.query(mQueries.mRemoveFileID); query.param(":id").set(id); query.execute(); // Return the ID to our caller. return FileID::from(id); } template<typename Lock> void FileServiceContext::deallocateID(FileID id, [[maybe_unused]] Lock&& lock, Transaction& transaction) { assert(synthetic(id)); assert(lock.mutex() == &mDatabase); assert(lock.owns_lock()); // Add this id to our free list. auto query = transaction.query(mQueries.mAddFileID); query.param(":id").set(id); query.execute(); } auto FileServiceContext::fileContextFromCloud(FileID id) -> FileServiceResultOr<FileContextPtr> { // Synthetic IDs are never a valid node handle. if (synthetic(id)) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Check if a node exists in the cloud with this ID. auto node = mClient.get(id.toHandle()); // Couldn't get a reference to the node. if (!node) { // Because it doesn't exist in the cloud. if (node.error() == API_ENOENT) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Because we hit some unexpected error. return unexpected(FILE_SERVICE_UNEXPECTED); } // You can't open a directory as a file. if (node->mIsDirectory) return unexpected(FILE_SERVICE_FILE_IS_A_DIRECTORY); // Make sure no one's changing our indexes. UniqueLock lockContexts(mLock); // Make sure no one's changing the database. UniqueLock lockDatabase(mDatabase); // Check if another thread's opened (or removed) this file. auto maybeFile = fileContextFromIndex(id, lockContexts); // Another thread opened (or removed) this file. if (!maybeFile || *maybeFile) return maybeFile; // Compute the file's access time. auto accessed = now(); // Latch the file's size. auto size = static_cast<std::uint64_t>(node->mSize); // Add the file to the database. auto transaction = mDatabase.transaction(); auto query = transaction.query(mQueries.mAddFile); query.param(":accessed").set(accessed); query.param(":allocated_size").set(0u); query.param(":dirty").set(false); query.param(":handle").set(node->mHandle); query.param(":id").set(id); query.param(":modified").set(node->mModified); query.param(":name").set(node->mName); query.param(":parent_handle").set(node->mParentHandle); query.param(":removed").set(false); query.param(":reported_size").set(0u); query.param(":size").set(size); query.execute(); // Add the file to storage. auto file = mStorage.addFile(id); // Persist our database changes. transaction.commit(); // Clarity. auto allocatedSize = 0u; auto dirty = false; auto location = FileLocation{std::move(node->mName), node->mParentHandle}; auto reportedSize = 0u; // Create a context to represent this file's information. auto info = std::make_shared<FileInfoContext>(accessed, mActivities.begin(), allocatedSize, dirty, node->mHandle, id, location, node->mModified, reportedSize, *this, size); // Make sure this file's info is in our index. mInfoContexts.emplace(id, info); // Create a context to represent the file itself. auto context = std::make_shared<FileContext>(mActivities.begin(), std::move(file), std::move(info), std::nullopt, FileRangeVector(), *this); // Make sure the file is in our index. mFileContexts.emplace(id, context); // Return the file to our caller. return context; } auto FileServiceContext::fileContextFromDatabase(FileID id) -> FileServiceResultOr<FileContextPtr> { // Try and get our hands on the file's information. auto maybeInfo = infoContext(id); // File's been removed. if (!maybeInfo) return unexpected(maybeInfo.error()); // Clarity. auto& info = *maybeInfo; // File isn't in storage so open it from the cloud. if (!info) return fileContextFromCloud(id); // Acquire lock. UniqueLock lock(mLock); // File's been removed. if (info->removed()) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Check if another thread opened the file. auto maybeFile = fileContextFromIndex(info->id(), lock); // File was opened (or removed) by another thread. if (!maybeFile || *maybeFile) return maybeFile; // Make sure we're using the file's canonical ID. id = info->id(); // Retrieve this file's key data and ranges from the database. std::optional<NodeKeyData> keyData; FileRangeVector ranges; { // Acquire database lock. UniqueLock lockDatabase(mDatabase); // So we can safely access the database. auto transaction = mDatabase.transaction(); // Retrieve the file's key data from the database. keyData = this->keyData(id, transaction); // Retrieve the file's ranges from the database. ranges = this->ranges(id, transaction); } // Instantiate a new file context. auto context = std::make_shared<FileContext>(mActivities.begin(), mStorage.getFile(id), std::move(info), std::move(keyData), ranges, *this); // Add the context to our index. mFileContexts.emplace(id, context); // Return the context to our caller. return context; } template<typename Lock> auto FileServiceContext::fileContextFromIndex(FileID id, Lock&& lock) -> FileServiceResultOr<FileContextPtr> { // Check if the file's in memory. auto file = getFromIndex(id, std::forward<Lock>(lock), mFileContexts); // File isn't in memory. if (!file) return nullptr; // File's been marked as removed. if (file->removed()) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Return file to caller. return file; } template<typename Lock, typename T> auto FileServiceContext::getFromIndex(FileID id, [[maybe_unused]] Lock&& lock, FromFileIDMap<std::weak_ptr<T>>& map) -> std::shared_ptr<T> { assert(lock.mutex() == &mLock); assert(lock.owns_lock()); // Try and find the entry in the map. auto entry = map.find(id); // No entry in the map. if (entry == map.end()) return nullptr; // Try and get a strong reference to the entry's instance. auto instance = entry->second.lock(); // Entry references a dead instance. if (!instance) return map.erase(entry), nullptr; // Return instance to caller. return instance; } auto FileServiceContext::infoContextFromDatabase(FileID id) -> FileInfoContextPtr { // Make sure no one is changing our indexes. UniqueLock lockContexts(mLock); // Make sure no one is changing our database. UniqueLock lockDatabase(mDatabase); // Check if another thread loaded this file's info. auto info = infoContextFromIndex(id, lockContexts); // Info was loaded by another thread. if (info) return info; // Check if this file exists in the database. auto transaction = mDatabase.transaction(); auto query = transaction.query(mQueries.mGetFile); query.param(":handle").set(nullptr); query.param(":id").set(id); query.param(":removed").set(false); // The caller's looking up the file by a node handle. if (!synthetic(id)) query.param(":handle").set(id.toHandle()); query.execute(); // We know nothing about this file. if (!query) return nullptr; // Latch the file's attributes from the database. auto accessed = query.field("accessed").get<std::int64_t>(); auto allocatedSize = query.field("allocated_size").get<std::uint64_t>(); auto dirty = query.field("dirty").get<bool>(); auto handle = NodeHandle(); auto modified = query.field("modified").get<std::int64_t>(); auto name = query.field("name").get<std::optional<std::string>>(); auto parent = query.field("parent_handle").get<std::optional<NodeHandle>>(); auto reportedSize = query.field("reported_size").get<std::uint64_t>(); auto size = query.field("size").get<std::uint64_t>(); if (!query.field("handle").null()) handle = query.field("handle").get<NodeHandle>(); id = query.field("id").get<FileID>(); std::optional<FileLocation> location; // File only has a location if its name and parent are set. if (name && parent) location = FileLocation{std::move(*name), *parent}; // Instantiate a context to represent this file's information. info = std::make_shared<FileInfoContext>(accessed, mActivities.begin(), allocatedSize, dirty, handle, id, std::move(location), modified, reportedSize, *this, size); // Add the context to our index. mInfoContexts.emplace(id, info); // Return the file's information to our caller. return info; } template<typename Lock> auto FileServiceContext::infoContextFromIndex(FileID id, Lock&& lock) -> FileInfoContextPtr { // Check if this file's information is in the index. return getFromIndex(id, std::forward<Lock>(lock), mInfoContexts); } auto FileServiceContext::infoContext(FileID id) -> FileServiceResultOr<FileInfoContextPtr> { // Check if the file's in memory. auto info = infoContextFromIndex(id, SharedLock(mLock)); // File's not in memory. if (!info) return infoContextFromDatabase(id); // File's been marked as removed. if (info->removed()) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Return the file's information to our caller. return info; } template<typename Transaction> auto FileServiceContext::keyData(FileID id, Transaction&& transaction) -> std::optional<NodeKeyData> { assert(transaction.inProgress()); auto query = transaction.query(mQueries.mGetFileKeyData); query.param(":id").set(id); if (!query.execute()) return std::nullopt; using MaybeString = std::optional<std::string>; NodeKeyData keyData; keyData.mChatAuth = query.field("chat_auth").template get<MaybeString>(); keyData.mIsPublicHandle = query.field("is_public").template get<bool>(); keyData.mKeyAndIV = query.field("key_and_iv").template get<std::string>(); keyData.mPrivateAuth = query.field("private_auth").template get<MaybeString>(); keyData.mPublicAuth = query.field("public_auth").template get<MaybeString>(); // Sanity. assert(keyData.mKeyAndIV.size() == FILENODEKEYLENGTH); return keyData; } template<typename Transaction> auto FileServiceContext::ranges(FileID id, Transaction&& transaction) -> FileRangeVector { assert(transaction.inProgress()); auto query = transaction.query(mQueries.mGetFileRanges); auto ranges = FileRangeVector(); query.param(":id").set(id); for (query.execute(); query; ++query) { auto begin = query.field("begin").template get<std::uint64_t>(); auto end = query.field("end").template get<std::uint64_t>(); ranges.emplace_back(begin, end); } return ranges; } void FileServiceContext::reclaimTaskCallback(Activity& activity, steady_clock::time_point when, const Task& task) { // Exchange mReclaimTask with task. auto exchange = [this](Task task) { // Acquire task lock. std::lock_guard guard(mReclaimTaskLock); // For ADL lookup. using std::swap; // Exchange the task. swap(mReclaimTask, task); }; // exchange // Client's shutting down or reclamation has been disabled. if (task.aborted()) return exchange(Task()); // Schedule another reclamation in the future. auto reschedule = [activity = std::move(activity), exchange, this]() { // Get the service's current options. auto options = this->options(); // Reclamation has been disabled. if (!reclamationEnabled(options)) return exchange(Task()); // When should the next reclamation occur? auto when = steady_clock::now() + options.mReclaimPeriod; // Keep exchange below simple. auto callback = std::bind(&FileServiceContext::reclaimTaskCallback, this, std::move(activity), when, std::placeholders::_1); // Schedule another reclamation for the future. exchange(mExecutor.execute(std::move(callback), when, false)); }; // reschedule // No reclamation needed at this time. if (steady_clock::now() < when) return reschedule(); // Called when reclamation has completed. auto reclaimed = [reschedule = std::move(reschedule)](auto) { // Schedule another reclamation in the future. reschedule(); }; // reclaimed // Try and reclaim storage. reclaim(std::move(reclaimed)); } auto FileServiceContext::reclaimable() -> FileServiceResultOr<FileIDVector> try { // Get our hands on our current options. auto options = this->options(); // Convenience. auto sizeThreshold = options.mReclaimSizeThreshold; // No quota? No need to reclaim anything. if (!sizeThreshold) return FileIDVector(); // So we have exclusive access to the database. UniqueLock lock(mDatabase); // So we can safely access the database. auto transaction = mDatabase.transaction(); // Figure out how much storage we're currently using. auto used = storageUsed(lock, transaction); // No need to reclaim any storage. if (sizeThreshold >= used) return FileIDVector(); // Get the allocated size and ID of all files in storage. auto query = transaction.query(mQueries.mGetReclaimableFiles); // Retrieve access time threshold. auto threshold = options.mReclaimAgeThreshold; // Compute maximum reclaimable access time. auto accessed = system_clock::now() - threshold; // Specify maximum reclaimable access time. query.param(":accessed").set(system_clock::to_time_t(accessed)); // Tracks the IDs of the files we can reclaim. FileIDVector ids; // Collect as many IDs for reclamation as necessary. for (query.execute(); query && used > sizeThreshold; ++query) { // Latch the file's ID and allocated size. auto id = query.field("id").get<FileID>(); auto size = query.field("allocated_size").get<std::uint64_t>(); // Add the ID to our vector. ids.emplace_back(id); // Decrease amount of used storage. used -= std::min(size, used); } // Return vector of reclaimable IDs to our caller. return ids; } catch (std::runtime_error& exception) { FSErrorF("Unable to determine which files can be reclaimed: %s", exception.what()); return FILE_SERVICE_UNEXPECTED; } // No lock necessary as we're called directly from the constructor. void FileServiceContext::purgeRemovedFiles() try { // Tracks synthetic file IDs that we need to deallocate. FileIDVector ids; // So we can safely access the database. auto transaction = mDatabase.transaction(); // Retrieve the ID of each file marked for removal. auto query = transaction.query(mQueries.mGetFileIDs); query.param(":removed").set(true); // Iterate over each file, purging its data from storage. for (query.execute(); query; ++query) { // Latch the file's ID. auto id = query.field("id").get<FileID>(); // File's ID is synthetic and needs to be deallocated. if (synthetic(id)) ids.emplace_back(id); // Remove the file's data from storage. mStorage.removeFile(id); } // Deallocate synthetic file IDs. query = transaction.query(mQueries.mAddFileID); for (auto id: ids) { query.param(":id").set(id); query.execute(); } // Removed removed files from the database. query = transaction.query(mQueries.mRemoveFiles); query.param(":removed").set(true); query.execute(); // Persist database changes. transaction.commit(); } catch (std::runtime_error& exception) { // Leave a trail so we know something went wrong. FSErrorF("Unable to purge removed files: %s", exception.what()); } template<typename ContextLock, typename DatabaseLock> void FileServiceContext::remove([[maybe_unused]] ContextLock&& contextLock, DatabaseLock&& databaseLock, FileID id, common::Transaction& transaction) { // Sanity. assert(contextLock.mutex() == &mLock); assert(contextLock.owns_lock()); assert(databaseLock.mutex() == &mDatabase); assert(databaseLock.owns_lock()); // Remove the file from the database. removeFromDatabase(id, databaseLock, transaction); // Remove the file from storage. mStorage.removeFile(id); } template<typename Lock> void FileServiceContext::removeFromDatabase(FileID id, [[maybe_unused]] Lock&& lock, Transaction& transaction) { assert(lock.mutex() == &mDatabase); assert(lock.owns_lock()); // Remove this file from the database. auto query = transaction.query(mQueries.mRemoveFile); query.param(":id").set(id); query.execute(); // Deallocate the file's ID if necessary. if (synthetic(id)) deallocateID(id, std::forward<Lock>(lock), transaction); } template<typename Lock, typename T> bool FileServiceContext::removeFromIndex(FileID id, [[maybe_unused]] Lock&& lock, FromFileIDMap<T>& map) { assert(lock.mutex() == &mLock); assert(lock.owns_lock()); // Is id in the map? auto entry = map.find(id); // id's not in the map. if (entry == map.end()) return false; // id's in the map but refers a different instance. if (!entry->second.expired()) return false; // Remove id from the map. map.erase(id); // Let the caller know id was removed. return true; } template<typename T> bool FileServiceContext::removeFromIndex(FileID id, FromFileIDMap<T>& map) { return removeFromIndex(id, UniqueLock(mLock), map); } template<typename Lock, typename Transaction> auto FileServiceContext::storageUsed([[maybe_unused]] Lock&& lock, Transaction&& transaction) -> std::uint64_t { // Sanity. assert(lock.mutex() == &mDatabase); assert(lock.owns_lock()); assert(transaction.inProgress()); // Compute the storage used by all files in storage. auto query = transaction.query(mQueries.mGetStorageUsed); query.execute(); // Sanity. assert(query); // Return the total allocated size of all files in storage. return query.field("total_allocated_size").template get<std::uint64_t>(); } void FileServiceContext::updated(NodeEventQueue& events) { // Process the latest changes from the cloud. EventProcessor (*this)(events); } FileServiceContext::FileServiceContext(Client& client, const FileServiceOptions& options): NodeEventObserver(), FileEventEmitter(), mInstanceLogger("FileServiceContext", *this, logger()), mClient(client), mStorage(mClient), mDatabase(createDatabase(mStorage.databasePath())), mQueries(mDatabase), mFileContexts(), mInfoContextRemoved(), mInfoContexts(), mLock(), mOptions(options), mOptionsLock(), mReclaimContext(), mReclaimContextLock(), mReclaimTask(), mReclaimTaskLock(), mActivities(), mExecutor(TaskExecutorFlags(), logger()) { // Let the client know we want to receive node change events. mClient.addEventObserver(*this); // Purge any lingering removed files. purgeRemovedFiles(); // User hasn't specified any storage quota. if (!mOptions.mReclaimSizeThreshold) return; // Assume user's specified an initial reclamation delay. auto delay = mOptions.mReclaimDelay; // User hasn't specified an initial reclamation delay. if (!delay.count()) delay = mOptions.mReclaimPeriod; // User hasn't specified a reclamation period. if (!delay.count()) return; // When should we perform the reclamation? auto when = steady_clock::now() + delay; // Schedule initial reclamation for later execution. mReclaimTask = mExecutor.execute(std::bind(&FileServiceContext::reclaimTaskCallback, this, mActivities.begin(), when, std::placeholders::_1), when, true); } FileServiceContext::~FileServiceContext() { // Let the client know we're no longer interested in node events. mClient.removeEventObserver(*this); } auto FileServiceContext::add(NodeHandle handle, const NodeKeyData& keyData, std::size_t size) -> FileServiceResultOr<FileID> try { // Caller's given us a bogus key. if (keyData.mKeyAndIV.size() != FILENODEKEYLENGTH) return unexpected(FILE_SERVICE_INVALID_FILE_KEY); // Acquire context lock. std::lock_guard lockContexts(mLock); // Acquire database lock. std::lock_guard lockDatabase(mDatabase); // So we can safely access the database. auto transaction = mDatabase.transaction(); // Check if this file's already in the database. auto query = transaction.query(mQueries.mGetFile); query.param(":id").set(handle); // File's already in the database. if (query.execute()) return unexpected(FILE_SERVICE_FILE_ALREADY_EXISTS); // Convenience. auto accessed = now(); auto id = FileID::from(handle); // Add the file to the database. query = transaction.query(mQueries.mAddFile); query.param(":accessed").set(accessed); query.param(":allocated_size").set(0u); query.param(":dirty").set(false); query.param(":handle").set(handle); query.param(":id").set(id); query.param(":modified").set(accessed); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.param(":removed").set(false); query.param(":reported_size").set(0u); query.param(":size").set(size); query.execute(); // Add the file's key data to the database. query = transaction.query(mQueries.mAddFileKeyData); query.param(":chat_auth").set(keyData.mChatAuth); query.param(":id").set(id); query.param(":is_public").set(keyData.mIsPublicHandle); query.param(":key_and_iv").set(keyData.mKeyAndIV); query.param(":private_auth").set(keyData.mPrivateAuth); query.param(":public_auth").set(keyData.mPublicAuth); query.execute(); // Add the file to storage. mStorage.addFile(id); // Persist database changes. transaction.commit(); // Let the caller know the foreign file was added successfully. return id; } catch (std::runtime_error& exception) { FSErrorF("Unable to add foreign file to service: %s", exception.what()); return unexpected(FILE_SERVICE_UNEXPECTED); } Client& FileServiceContext::client() { return mClient; } auto FileServiceContext::create(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File> try { // The caller's passed us an invalid name. if (name.empty()) return unexpected(FILE_SERVICE_INVALID_NAME); // Check if the parent already contains a child with this name. switch (auto node = mClient.get(parent, name); node.errorOr(API_OK)) { case API_ENOENT: // Parent doesn't exist. return unexpected(FILE_SERVICE_PARENT_DOESNT_EXIST); case API_FUSE_ENOTDIR: // Parent isn't a directory. return unexpected(FILE_SERVICE_PARENT_IS_A_FILE); case API_FUSE_ENOTFOUND: // Parent doesn't contain a child with this name. break; case API_OK: // Parent already contains a child with this name. return unexpected(FILE_SERVICE_FILE_ALREADY_EXISTS); default: // Encountered an unknown failure. return unexpected(FILE_SERVICE_UNEXPECTED); } // Acquire context and database locks. UniqueLock lockContexts(mLock); UniqueLock lockDatabase(mDatabase); // Initiate a transaction so we can safely modify the database. auto transaction = mDatabase.transaction(); // Check if parent already contains a local child with this name. auto query = transaction.query(mQueries.mGetFileByNameAndParentHandle); query.param(":parent_handle").set(parent); query.param(":name").set(name); query.execute(); // Parent already contains a local child with this name. if (query) return unexpected(FILE_SERVICE_FILE_ALREADY_EXISTS); // Try and allocate a new file ID. auto id = allocateID(lockDatabase, transaction); // Compute the new file's modification time. auto modified = now(); // Add a new file to the database. query = transaction.query(mQueries.mAddFile); query.param(":accessed").set(modified); query.param(":allocated_size").set(0u); query.param(":dirty").set(true); query.param(":handle").set(nullptr); query.param(":id").set(id); query.param(":modified").set(modified); query.param(":name").set(name); query.param(":parent_handle").set(parent); query.param(":removed").set(false); query.param(":reported_size").set(0u); query.param(":size").set(0u); query.execute(); // Clarity. auto allocatedSize = 0u; auto dirty = true; auto location = FileLocation{name, parent}; auto reportedSize = 0u; auto size = 0u; // Instantiate an info context to describe our new file. auto info = std::make_shared<FileInfoContext>(modified, mActivities.begin(), allocatedSize, dirty, NodeHandle(), id, location, modified, reportedSize, *this, size); // Instantiate a file context to manipulate our new file. auto file = std::make_shared<FileContext>(mActivities.begin(), mStorage.addFile(id), info, std::nullopt, FileRangeVector(), *this); // Persist our changes. transaction.commit(); // Add both contexts to our index. mFileContexts.emplace(id, file); mInfoContexts.emplace(id, std::move(info)); // Return a file instance to our caller. return File(FileServiceContextBadge{}, std::move(file)); } catch (std::runtime_error& exception) { FSErrorF("Unable to create a new file: %s", exception.what()); return unexpected(FILE_SERVICE_UNEXPECTED); } Database& FileServiceContext::database() { return mDatabase; } auto FileServiceContext::execute(std::function<void(const Task&)> function) -> Task { return mExecutor.execute(std::move(function), true); } auto FileServiceContext::info(FileID id) -> FileServiceResultOr<FileInfo> try { // Try and get our hands on this file's info. auto maybeInfo = infoContext(id); // Couldn't get information. if (!maybeInfo) { // Because the file's been removed. if (maybeInfo.error() == FILE_SERVICE_FILE_DOESNT_EXIST) return unexpected(FILE_SERVICE_UNKNOWN_FILE); // Because we encountered some kind of failure. return unexpected(maybeInfo.error()); } // Clarity. auto& info = *maybeInfo; // File's in storage. if (info) return FileInfo(FileServiceContextBadge(), std::move(info)); // File isn't in storage. return unexpected(FILE_SERVICE_UNKNOWN_FILE); } catch (std::runtime_error& exception) { FSErrorF("Unable to get file information: %s: %s", toString(id).c_str(), exception.what()); return unexpected(FILE_SERVICE_UNEXPECTED); } auto FileServiceContext::open(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File> try { // Caller's given us a bogus name. if (name.empty()) return unexpected(FILE_SERVICE_INVALID_NAME); // Check if the specified child exists in the cloud. switch (auto node = mClient.get(parent, name); node.errorOr(API_OK)) { case API_ENOENT: // Parent doesn't exist. return unexpected(FILE_SERVICE_PARENT_DOESNT_EXIST); case API_FUSE_ENOTDIR: // Parent isn't a directory. return unexpected(FILE_SERVICE_PARENT_IS_A_FILE); case API_FUSE_ENOTFOUND: // Child doesn't exist in the cloud. break; case API_OK: // Child exists but denotes a directory. if (node->mIsDirectory) return unexpected(FILE_SERVICE_FILE_IS_A_DIRECTORY); // Open file by node handle. return open(FileID::from(node->mHandle)); default: // Encountered an unknown failure. return unexpected(FILE_SERVICE_UNEXPECTED); } FileID id; // Try and determine this child's file ID. { // Acquire context lock. UniqueLock lockContexts(mLock); // Acquire database lock. UniqueLock lockDatabase(mDatabase); // So we can safely access the database. auto transaction = mDatabase.transaction(); // Check if this child exists in the database. auto query = transaction.query(mQueries.mGetFileByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parent); // Child doesn't exist in the database. if (!query.execute()) return unexpected(FILE_SERVICE_FILE_DOESNT_EXIST); // Latch the child's ID. id = query.field("id").get<FileID>(); } // Try and open the child. return open(id); } catch (std::runtime_error& exception) { // Leave a trail for debuggers. FSErrorF("Unable to open file %s under %s: %s", name.c_str(), toNodeHandle(parent).c_str(), exception.what()); // Let the caller know we couldn't open the file. return unexpected(FILE_SERVICE_UNEXPECTED); } auto FileServiceContext::open(FileID id) -> FileServiceResultOr<File> try { // Check if the file's already been opened. auto maybeFile = fileContextFromIndex(id, SharedLock(mLock)); // File's been marked as removed. if (!maybeFile) return unexpected(maybeFile.error()); // File isn't in memory. if (!*maybeFile) maybeFile = fileContextFromDatabase(id); // Couldn't open the file. if (!maybeFile) return unexpected(maybeFile.error()); // Return file to our caller. return File({}, std::move(*maybeFile)); } catch (std::runtime_error& exception) { FSErrorF("Unable to open file: %s: %s", toString(id).c_str(), exception.what()); return unexpected(FILE_SERVICE_UNEXPECTED); } void FileServiceContext::options(const FileServiceOptions& options) { // Set later to prevent modification to our options. SharedLock readLock(mOptionsLock, std::defer_lock); // Keeps track of our original reclamation period. seconds oldPeriod; // Update our options. { // Make sure no one else is modifying our options. UniqueLock writeLock(mOptionsLock); // Latch current reclamation period. oldPeriod = mOptions.mReclaimPeriod; // Update our options. mOptions = options; // Let other threads read our options. readLock = writeLock.to_shared_lock(); } // Acquire task lock. UniqueLock taskLock(mReclaimTaskLock); // Caller wants to disable periodic reclamation. if (!reclamationEnabled(options)) return mReclaimTask.abort(), void(); // Convenience. auto newPeriod = options.mReclaimPeriod; // Caller isn't changing reclamation period. if (newPeriod == oldPeriod) return; // Periodic reclamation is already scheduled. // // Send it a cancellation so it reschedules itself. if (mReclaimTask) return mReclaimTask.cancel(), void(); // When should we perform the reclamation? auto when = steady_clock::now() + newPeriod; // Schedule a reclamation for some time in the future. mReclaimTask = mExecutor.execute(std::bind(&FileServiceContext::reclaimTaskCallback, this, mActivities.begin(), when, std::placeholders::_1), when, true); } FileServiceOptions FileServiceContext::options() { // Make sure no one else is modifying our options. SharedLock guard(mOptionsLock); // Return current options to our caller. return mOptions; } LocalPath FileServiceContext::path(FileID id) const { return mStorage.userFilePath(id); } FileServiceQueries& FileServiceContext::queries() { return mQueries; } auto FileServiceContext::purge() -> FileServiceResult try { // True when there are no info contexts in memory. auto idle = [this]() { return mInfoContexts.empty(); }; // idle // Make sure we have exclusive access to the context. UniqueLock lockContexts(mLock); // Wait until all info contexts have been removed from memory. mInfoContextRemoved.wait(lockContexts, idle); // Make sure we have exclusive access to the database. UniqueLock lockDatabase(mDatabase); // Retrieve the ID of each file in storage. auto transaction = mDatabase.transaction(); auto query = transaction.query(mQueries.mGetFileIDs); // Purge each file's data from storage. for (query.execute(); query; ++query) mStorage.removeFile(query.field("id").get<FileID>()); // Remove all the files from the database. query = transaction.query(mQueries.mRemoveFiles); query.execute(); // Remove any synthetic IDs saved for reuse. query = transaction.query(mQueries.mRemoveFileIDs); query.execute(); // Reset the ID generator to its initial state. query = transaction.query(mQueries.mSetNextFileID); query.param(":next").set(0u); query.execute(); // Persist database changes. transaction.commit(); // Let the caller know the service has been purged. return FILE_SERVICE_SUCCESS; } catch (std::runtime_error& exception) { // Leave a hint as to why we couldn't purge files from the service. FSErrorF("Unable to purge files from storage: %s", exception.what()); // Let the caller know we couldn't purge files from storage. return FILE_SERVICE_UNEXPECTED; } void FileServiceContext::reclaim(ReclaimCallback callback) { // Acquire reclaim context lock. std::unique_lock lock(mReclaimContextLock); // Reclamation is already in progress. if (mReclaimContext) return mReclaimContext->queue(std::move(callback)); // Instantiate reclaim context. auto context = std::make_shared<ReclaimContext>(*this); // Make context visible to other callers. mReclaimContext = context; // Queue callback for later execution. mReclaimContext->queue(std::move(callback)); // Release reclaim context lock. lock.unlock(); // Reclaim storage space. mReclaimContext->reclaim(mReclaimContext); } void FileServiceContext::removeFromIndex(FileContextBadge, FileID id) { removeFromIndex(id, mFileContexts); } void FileServiceContext::removeFromIndex(FileInfoContextBadge, FileInfoContext& context) try { // Latch the file's ID. auto id = context.id(); // Make sure we have exclusive access to mInfoContexts. UniqueLock lockContexts(mLock); // mInfoContexts contains a distinct info instance for this file. if (!removeFromIndex(id, lockContexts, mInfoContexts)) return; // Make sure we have exclusive access to mDatabase. UniqueLock lockDatabase(mDatabase); // So we can safely modify the database. auto transaction = mDatabase.transaction(); // File hasn't been removed. if (!context.removed()) { // Update the file's access time. auto query = transaction.query(mQueries.mSetFileAccessTime); query.param(":accessed").set(context.accessed()); query.param(":id").set(id); query.execute(); // Persist database changes. transaction.commit(); // Let waiters know the context's been removed. return mInfoContextRemoved.notify_all(); } // Remove the file. remove(lockContexts, lockDatabase, id, transaction); // Persist our changes. transaction.commit(); // Let waiters know the context's been removed. mInfoContextRemoved.notify_all(); } catch (std::runtime_error& exception) { FSWarningF("Unable to purge %s from storage: %s", toString(context.id()).c_str(), exception.what()); } auto FileServiceContext::storageUsed() -> FileServiceResultOr<std::uint64_t> try { // Make sure no one else is changing the database. UniqueLock lock(mDatabase); // So we can safely access the database. auto transaction = mDatabase.transaction(); // Let the caller know how much storage space we're using. return storageUsed(lock, transaction); } catch (std::runtime_error& exception) { FSErrorF("Unable to determine storage footprint: %s", exception.what()); return unexpected(FILE_SERVICE_UNEXPECTED); } FileServiceContext::EventProcessor::EventProcessor(FileServiceContext& service): mService(service), mServiceLock(service.mLock), mDatabaseLock(service.mDatabase), mQueries(service.mQueries), mTransaction(service.mDatabase.transaction()) {} void FileServiceContext::EventProcessor::added(const NodeEvent& event) { // Does this node replace a file managed by the service? auto query = mTransaction.query(mQueries.mGetFileByNameAndParentHandle); query.param(":name").set(event.name()); query.param(":parent_handle").set(event.parentHandle()); query.execute(); // Node doesn't replace any file managed by the service. if (!query) return; // Latch the file's handle, if any. auto handle = query.field("handle").get<std::optional<NodeHandle>>(); // Node describes a file managed by the service. if (handle && event.handle() == *handle) return; // Latch the file's ID. auto id = query.field("id").get<FileID>(); // The file's not in memory so purge it from the service. if (!mark(id, true)) remove(id, true); } void FileServiceContext::EventProcessor::dispatch(const NodeEvent& event) try { switch (event.type()) { case NODE_EVENT_ADDED: added(event); break; case NODE_EVENT_MOVED: moved(event); break; case NODE_EVENT_REMOVED: removed(event); break; default: break; } } catch (std::runtime_error& exception) { FSErrorF("Unable to dispatch node event: %s", exception.what()); } FileInfoContextPtr FileServiceContext::EventProcessor::info(FileID id) { // Check if the file's information is in memory. auto info = mService.infoContextFromIndex(id, mServiceLock); // File's in memory but has been marked as removed. if (info && info->removed()) return nullptr; // Return the file's information to our caller. return info; } bool FileServiceContext::EventProcessor::mark(FileID id, bool replaced) { // Check if the file's in memory. auto info = this->info(id); // File's not in memory. if (!info) return false; // Mark the file as removed in the database. auto query = mTransaction.query(mQueries.mSetFileRemoved); query.param(":id").set(id); query.execute(); // Mark the file as removed in memory. info->removed(replaced); // Let our caller know we marked an in-memory file. return true; } void FileServiceContext::EventProcessor::moved(const NodeEvent& event) { // Mark or remove a file managed by the service. auto remove = [this](FileID id) { // File's not in memory so remove it immediately. if (!mark(id, true)) this->remove(id, true); }; // remove // Convenience. auto name = event.name(); auto parentHandle = event.parentHandle(); // Check if this node would replace a file managed by the service. auto query = mTransaction.query(mQueries.mGetFileByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); // Node may replace a file managed by the service. if (query.execute()) { // Latch the file's handle, if any. auto handle = query.field("handle").get<std::optional<NodeHandle>>(); // File's location is already up to date. if (handle && event.handle() == *handle) return; // Mark or remove the file. remove(query.field("id").get<FileID>()); } // Node's a directory so it can't be managed by the service. if (event.isDirectory()) return; // Check if this node *is* a file managed by the service. query = mTransaction.query(mQueries.mGetFile); query.param(":handle").set(event.handle()); // Node isn't a file managed by the service. if (!query.execute()) return; // Latch the file's ID and current location. auto id = query.field("id").get<FileID>(); auto oldName = query.field("name").get<std::string>(); auto oldParentHandle = query.field("parent_handle").get<NodeHandle>(); // Node's been superseded by another version. if (auto parent = mService.mClient.get(parentHandle); parent && !parent->mIsDirectory) return remove(id); // Update the file's location in the database. query = mTransaction.query(mQueries.mSetFileLocation); query.param(":id").set(id); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // File's in memory so update it's in-memory location. if (auto info = this->info(id)) return info->location(FileLocation{name, parentHandle}); // Let observers know the file's been moved. mService.notify(FileMoveEvent{FileLocation{std::move(oldName), oldParentHandle}, FileLocation{std::move(name), parentHandle}, id}); } void FileServiceContext::EventProcessor::remove(FileID id, bool replaced) { // Remove a file from the database and from storage. mService.remove(mServiceLock, mDatabaseLock, id, mTransaction); // Let observers know the file's been removed. mService.notify(FileRemoveEvent{id, replaced}); } void FileServiceContext::EventProcessor::removed(const NodeEvent& event) { // Directories are not managed by the service. if (event.isDirectory()) return removedDirectory(event); // Is this node managed by the service? auto query = mTransaction.query(mQueries.mGetFile); query.param(":handle").set(event.handle()); query.param(":id").set(nullptr); query.param(":removed").set(false); // Node isn't managed by the service. if (!query.execute()) return; // Convenience. auto id = query.field("id").get<FileID>(); // File's not in memory so purge it from the service. if (!mark(id, false)) remove(id, false); } void FileServiceContext::EventProcessor::removedDirectory(const NodeEvent& event) { // IDs of children we should mark as removed. FileIDVector pendingMark; // IDs of children we should remove immediately. FileIDVector pendingRemove; // Retrieve the ID of each child under this directory. auto query = mTransaction.query(mQueries.mGetFileIDsByParentHandle); query.param(":parent_handle").set(event.handle()); query.param(":removed").set(false); // Iterate over this directory's children. for (query.execute(); query; ++query) { // Latch the child's ID. auto id = query.field("id").get<FileID>(); // Check if the child's in memory. auto info = this->info(id); // Child's in memory. if (info) { // Mark the file as removed in memory. info->removed(false); // Remember to mark this child in the database. pendingMark.emplace_back(id); // Move onto the next child. continue; } // Child's not in memory: remember to remove it. pendingRemove.emplace_back(id); } query = mTransaction.query(mQueries.mSetFileRemoved); // Mark in-memory children as removed in the database. for (auto id: pendingMark) { query.param(":id").set(id); query.execute(); } // Remove out-of-memory children from the service. for (auto id: pendingRemove) remove(id, false); } void FileServiceContext::EventProcessor::operator()(NodeEventQueue& events) try { // Process each event in turn. for (; !events.empty(); events.pop_front()) dispatch(events.front()); // Persist any database changes. mTransaction.commit(); } catch (std::runtime_error& exception) { FSErrorF("Unable to dispatch node events: %s", exception.what()); } void FileServiceContext::ReclaimContext::reclaim(ReclaimContextPtr context, FileID id) { // Sanity. assert(context); // Try and open the file. auto file = mService.open(id); // Couldn't open the file. if (!file) return reclaimed(std::move(context), FILE_FAILED); // So we can use our reclaimed function as a callback. auto reclaimed = std::bind(&ReclaimContext::reclaimed, this, std::move(context), std::placeholders::_1); // Try and reclaim the file. file->reclaim(std::move(reclaimed)); } template<typename Lock> void FileServiceContext::ReclaimContext::reclaimBatch(ReclaimContextPtr context, [[maybe_unused]] Lock&& lock) { // Sanity. assert(context); assert(lock.mutex() == &mLock); assert(lock.owns_lock()); // There are no files left to reclaim. if (mIDs.empty()) { // We were able to reclaim some space or encountered no failures. if (mReclaimed || mResult == FILE_SERVICE_SUCCESS) return completed(mReclaimed); // We encountered some kind of failure. return completed(mResult); } // How many files should we reclaim at once? auto batchSize = mService.options().mReclaimBatchSize; // Reclaim one or more files. while (mNumPending < batchSize && !mIDs.empty()) { // Grab the ID of a file waiting to be reclaimed. auto id = mIDs.back(); mIDs.pop_back(); // Increment number of pending reclamations. ++mNumPending; // Try and reclaim the file. reclaim(context, id); } } void FileServiceContext::ReclaimContext::reclaimed(ReclaimContextPtr context, FileResultOr<std::uint64_t> result) { // Make sure no one else changes our members. std::unique_lock lock(mLock); // Sanity. assert(context); assert(mNumPending); // Reduce number of pending reclamations. --mNumPending; // Update total amount of reclaimed space. mReclaimed += result.valueOr(0ul); // Remember if we encountered any failures. if (!result) mResult = FILE_SERVICE_UNEXPECTED; // Reclaim remaning files if any. reclaimBatch(std::move(context), std::move(lock)); } FileServiceContext::ReclaimContext::ReclaimContext(FileServiceContext& service): mInstanceLogger("ReclaimContext", *this, logger()), mActivity(service.mActivities.begin()), mCallbacks(), mIDs(), mNumPending(0), mReclaimed(0), mResult(FILE_SERVICE_SUCCESS), mService(service) {} void FileServiceContext::ReclaimContext::completed(FileServiceResultOr<std::uint64_t> result) { // Let the service know the reclamation has completed. { std::lock_guard guard(mService.mReclaimContextLock); mService.mReclaimContext = nullptr; } // Execute queued callbacks. for (auto& callback: mCallbacks) callback(result); } void FileServiceContext::ReclaimContext::queue(ReclaimCallback callback) { // Queue the caller's callback for later execution. mCallbacks.emplace_back(std::move(callback)); } void FileServiceContext::ReclaimContext::reclaim(ReclaimContextPtr context) { // Try and figure out what files we can reclaim. auto ids = mService.reclaimable(); // Couldn't determine how many files to reclaim. if (!ids) return completed(ids.error()); // Remember what file's we're reclaiming. mIDs = std::move(*ids); // Reclaim zero or more files in a batch. reclaimBatch(std::move(context), std::unique_lock(mLock)); } Database createDatabase(const LocalPath& databasePath) { Database database(logger(), databasePath); DatabaseBuilder(database).build(); return database; } bool reclamationEnabled(const FileServiceOptions& options) { return options.mReclaimBatchSize && options.mReclaimPeriod.count() && options.mReclaimSizeThreshold; } } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_service_queries.cpp�����������������������������������������������0000664�0000000�0000000�00000013526�15162662266�0022604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/database.h> #include <mega/file_service/file_service_queries.h> namespace mega { namespace file_service { using namespace common; FileServiceQueries::FileServiceQueries(Database& database): mAddFile(database.query()), mAddFileID(database.query()), mAddFileKeyData(database.query()), mAddFileRange(database.query()), mGetFile(database.query()), mGetFileByNameAndParentHandle(database.query()), mGetFileIDs(database.query()), mGetFileIDsByParentHandle(database.query()), mGetFileKeyData(database.query()), mGetFileRanges(database.query()), mGetFreeFileID(database.query()), mGetNextFileID(database.query()), mGetReclaimableFiles(database.query()), mGetStorageUsed(database.query()), mRemoveFile(database.query()), mRemoveFileID(database.query()), mRemoveFileIDs(database.query()), mRemoveFileRanges(database.query()), mRemoveFiles(database.query()), mSetFileAccessTime(database.query()), mSetFileHandle(database.query()), mSetFileLocation(database.query()), mSetFileModificationTime(database.query()), mSetFileRemoved(database.query()), mSetFileSize(database.query()), mSetNextFileID(database.query()) { mAddFile = "insert into files values ( " " :accessed, " " :allocated_size, " " :dirty, " " :handle, " " :id, " " :modified, " " :name, " " :parent_handle, " " :removed, " " :reported_size, " " :size " ")"; mAddFileID = "insert into file_ids values (:id)"; mAddFileKeyData = "insert into file_key_data values ( " " :chat_auth, " " :id, " " :is_public, " " :key_and_iv, " " :private_auth, " " :public_auth " ")"; mAddFileRange = "insert into file_ranges values ( " " :begin, " " :end, " " :id " ")"; mGetFile = "select * " " from files " " where ((:handle is not null and handle = :handle) " " or (:id is not null and id = :id)) " " and (:removed is null or removed = :removed)"; mGetFileByNameAndParentHandle = "select * " " from files " " where name = :name and parent_handle = :parent_handle"; mGetFileIDs = "select id " " from files " " where (:removed is null or removed = :removed)"; mGetFileIDsByParentHandle = "select id " " from files " " where parent_handle = :parent_handle " " and (:removed is null or removed = :removed)"; mGetFileKeyData = "select * from file_key_data where id = :id"; mGetFileRanges = "select begin " " , end " " from file_ranges " " where id = :id"; mGetFreeFileID = "select id " " from file_ids " " limit 1"; mGetNextFileID = "select next from file_id"; // Files marked for removal will be purged when closed. mGetReclaimableFiles = "select allocated_size " " , id " " from files " " where allocated_size <> 0 " " and accessed <= :accessed " " and removed = 0 " " order by accessed desc"; // ifnull(...) is necessary as there may be no files to sum. mGetStorageUsed = "select ifnull(sum(allocated_size), 0) as total_allocated_size " " , ifnull(sum(reported_size), 0) as total_reported_size " " , ifnull(sum(size), 0) as total_size " " from files"; mRemoveFile = "delete from files " " where id = :id"; mRemoveFileID = "delete from file_ids " " where id = :id"; mRemoveFileIDs = "delete from file_ids"; mRemoveFileRanges = "delete from file_ranges " " where begin >= :begin " " and end <= :end " " and id = :id"; mRemoveFiles = "delete from files " " where (:removed is null or removed = :removed)"; mSetFileAccessTime = "update files " " set accessed = :accessed " " where id = :id"; mSetFileHandle = "update files " " set handle = :handle " " where id = :id"; mSetFileLocation = "update files " " set name = :name " " , parent_handle = :parent_handle " " where id = :id"; mSetFileModificationTime = "update files " " set accessed = :accessed " " , dirty = 1 " " , modified = :modified " " where id = :id"; mSetFileRemoved = "update files " " set name = null " " , parent_handle = null " " , removed = 1 " " where id = :id"; mSetFileSize = "update files " " set allocated_size = :allocated_size " " , reported_size = :reported_size " " , size = :size " " where id = :id"; mSetNextFileID = "update file_id " " set next = :next"; } } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_service_result.cpp������������������������������������������������0000664�0000000�0000000�00000001643�15162662266�0022442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_service_result.h> #include <cassert> namespace mega { namespace file_service { const char* toDescription(FileServiceResult result) { static const char* descriptions[] = { #define DEFINE_DESCRIPTION(name, description) description, DEFINE_FILE_SERVICE_RESULTS(DEFINE_DESCRIPTION) #undef DEFINE_DESCRIPTION }; // descriptions if (result < sizeof(descriptions)) return descriptions[result]; assert(false && "Unhandled file service result enumerant"); return "N/A"; } const char* toString(FileServiceResult result) { static const char* names[] = { #define DEFINE_NAME(name, description) #name, DEFINE_FILE_SERVICE_RESULTS(DEFINE_NAME) #undef DEFINE_NAME }; // names if (result < sizeof(names)) return names[result]; assert(false && "Unhandled file service result enumerant"); return "N/A"; } } // file_service } // mega ���������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/file_storage.cpp�������������������������������������������������������0000664�0000000�0000000�00000004710�15162662266�0021046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/client.h> #include <mega/common/node_info.h> #include <mega/file_service/file_id.h> #include <mega/file_service/file_storage.h> #include <mega/file_service/logging.h> #include <megafs.h> namespace mega { namespace file_service { using namespace common; FileAccessPtr FileStorage::openFile(const LocalPath& path, bool mustCreate) { // So we can access the filesystem. auto file = mFilesystem->newfileaccess(false); // Sanity. assert(file); // Should never happen but hey, just in case. if (!file) throw FSError1("Couldn't create file access instance"); // Vulnerable to TOCTOU race. if (file->isfile(path) != !mustCreate || !file->fopen(path, OPEN_RDWR, FSLogging::noLogging)) throw FSErrorF("Couldn't %s file: %s", mustCreate ? "create" : "open", path.toPath(false).c_str()); // Mark the file as a sparse file if supported. file->setSparse(); // Return the file to our caller. return file; } FileStorage::FileStorage(const Client& client): mFilesystem(std::make_unique<FSACCESS_CLASS>()), mStorageDirectory(*mFilesystem, logger(), "file-service", client.dbRootPath()), mUserStorageDirectory(*mFilesystem, logger(), client.sessionID(), mStorageDirectory), mUserCacheDirectory(*mFilesystem, logger(), "cache", mUserStorageDirectory), mFolderLocker(mUserCacheDirectory.path().asPlatformEncoded(true)) {} FileStorage::~FileStorage() = default; FileAccessPtr FileStorage::addFile(FileID id) { return openFile(userFilePath(id), true); } LocalPath FileStorage::databasePath() const { static const auto name = LocalPath::fromRelativePath("metadata"); auto path = mUserStorageDirectory.path(); path.appendWithSeparator(name, true); return path; } FileAccessPtr FileStorage::getFile(FileID id) { return openFile(userFilePath(id), false); } void FileStorage::removeFile(FileID id) { // Compute the file's path. auto path = userFilePath(id); // File was removed from storage. if (mFilesystem->unlinklocal(path)) return; // Couldn't remove the file from storage. throw FSErrorF("Couldn't remove file: %s", path.toPath(false).c_str()); } LocalPath FileStorage::userFilePath(FileID id) const { auto name = LocalPath::fromRelativePath(toString(id)); auto path = mUserCacheDirectory.path(); path.appendWithSeparator(name, false); return path; } } // file_service } // mega ��������������������������������������������������������sdk-10.11.0/src/file_service/logger.cpp�������������������������������������������������������������0000664�0000000�0000000�00000000410�15162662266�0017653�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/logger.h> #include <mega/log_level.h> namespace mega { namespace file_service { using namespace common; SubsystemLogger& logger() { static SubsystemLogger logger("FileService"); return logger; } } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016606�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/�����������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021245�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/avl_tree.h�������������������������������������������0000664�0000000�0000000�00000044603�15162662266�0023226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree_iterator.h> #include <mega/file_service/avl_tree_node.h> #include <mega/file_service/avl_tree_traits.h> #include <mega/file_service/type_traits.h> #include <cassert> #include <cmath> #include <cstddef> #include <utility> namespace mega { namespace file_service { namespace detail { template<typename Traits, auto IsEqualityComparable = ValueIsEqualityComparableV<Traits>> class AVLTree { // Convenience. using KT = detail::KeyTraits<Traits>; using LT = detail::LinkTraits<Traits>; using MT = detail::MetadataTraits<Traits>; // Check if T contains a NodeType type. template<typename T> using DetectNodeType = typename T::NodeType; public: // Might be useful for generic code. using KeyTraits = KT; using KeyType = typename KT::KeyType; using LinkTraits = LT; // Try and determine the user's concrete node type. // // This is necessary as it's possible that the user's node type is // actually a subclass of some other node type. // // In this case, it'd be possible for two different classes to be // reported by our traits. For instance, mValuePointer might be // referencing something in NodeClassA and mMetadataPointer might be // referencing something in NodeClass B. // // To resolve this issue, we consider the user's node type to be the // most specific of the node types that we have detected. using NodeType = MostSpecificClassT<typename KT::NodeType, typename LT::NodeType, DetectedOrT<typename KT::NodeType, DetectNodeType, MT>>; // Make sure the node types we've detected are actually related. static_assert(IsNotNoneSuchV<NodeType>); private: // Rebalance a node (subtree) if necessary. NodeType* maybeRebalance(NodeType& node) { // How imbalanced is this node? auto balance = LT::balance(node); // Node's critically imbalanced. if (std::abs(balance) > 1) return rebalance(node, balance > 0); // Update this node's height and metadata. update(node); // This subtree's structure hasn't been changed so return it as is. return &node; } // Perform a left or right rebalance on the specified node. // // If direction is true, perform a right rebalance. // Otherwise, perform a left rebalance. NodeType* rebalance(NodeType& node, bool direction) { static const int balances[] = {+1, -1}; auto& child = LT::child(node, direction); assert(child); auto balance = LT::balance(*child); // Double rotation case (left-right or right-left.) if (balance == balances[direction]) child = rotate(*child, direction); return rotate(node, !direction); } // Rebalance the tree, traversing upwards from node. void rebalance(NodeType* node) { assert(node); while (true) { // Try and get our hands on this node's parent. auto* parent = LT::parent(*node); // No parent so we've reached the root node. if (!parent) break; // Is node parent's left or right child? auto* left = LT::left(*parent); auto& link = LT::child(*parent, left != node); // Rebalance (restructure) this subtree if necessary. link = maybeRebalance(*node); // Move one level up the tree. node = parent; } // Rebalance (restructure) the root if necessary. mRoot = maybeRebalance(*node); } // Remove a node from the tree. NodeType* remove(NodeType** link, NodeType* parent) { // Make sure we've been passed a valid node link. assert(link); // Make sure the node link actually references something. assert(*link); // Get a reference to the node we are removing. auto& node = **link; // Get a reference to the node's left child, if any. auto* replacement = LT::left(node); // Reduce tree's size as we're removing a node. --mSize; // Node has a left child. if (replacement) { // Check if node also has a right child. if (LT::right(node)) { // Node's replacement will be its inorder predecessor. auto* replacementLink = <::left(node); while (LT::right(*replacement)) { replacementLink = <::right(*replacement); replacement = *replacementLink; } // Replacement is node's left child. if ((parent = LT::parent(*replacement)) == &node) { // Make sure rebalancing starts from the replacement. parent = replacement; } // Don't lose replacement's left child, if any. *replacementLink = LT::left(*replacement); // Replacement takes node's place in the tree. *link = replacement; // Replacement inherits node's children and parent. LT::link(*replacement) = LT::link(node); // If replacement had a left child, update its parent link. if (*replacementLink) { // Needed when replacemnt is not node's left child. LT::parent(**replacementLink) = parent; } // Make sure replacement's new children know who their parent is. if (auto* left = LT::left(*replacement)) LT::parent(*left) = replacement; if (auto* right = LT::right(*replacement)) LT::parent(*right) = replacement; rebalance(parent); // Return a reference to the node we've removed. return &node; } } else { // Check if node has a right child. replacement = LT::right(node); } // If replacement is not null, node is a left or right branch. // // replacement will take node's place in the tree. *link = replacement; // Update the replacement's parent if necessary. if (replacement) LT::parent(*replacement) = parent; // Rebalance the tree starting from node's parent, if any. if (parent) rebalance(parent); // Return a reference to the node we removed. return &node; } // Rotate a node left or right. // // If direction is true, perform a right rotation. // Otherwise, perform a left rotation. NodeType* rotate(NodeType& node, bool direction) { auto* child = LT::child(node, !direction); assert(child); LT::parent(*child) = LT::parent(node); LT::parent(node) = child; auto* grandchild = LT::child(*child, direction); if (grandchild) LT::parent(*grandchild) = &node; LT::child(*child, direction) = &node; LT::child(node, !direction) = grandchild; // Update node invariants. update(node); // Make sure child's invariants are updated last. update(*child); return child; } // Update a node's height and metadata. void update(NodeType& node) { // Assume the node has no children. AVLTreeHeight height = 0; // Node has a left child so latch its height. if (auto* left = LT::left(node)) height = LT::height(*left); // Node has a right child. Latch its height if it is higher. if (auto* right = LT::right(node)) height = std::max(height, LT::height(*right)); // Update the node's height. // // The +1 is because the height includes the node itself. LT::height(node) = height + 1; // Update any metadata associated with this node. MT::template update<ConstIterator>(node); } // Points to the tree's root node, if any. NodeType* mRoot{}; // How many nodes does this tree contain? std::size_t mSize{}; public: using Iterator = AVLTreeIterator<NodeType, LinkTraits, false, false>; using ReverseIterator = ToReverseIteratorT<Iterator>; using ConstIterator = ToConstIteratorT<Iterator>; using ConstReverseIterator = ToReverseIteratorT<ConstIterator>; AVLTree() = default; // Allow move construction since we are just moving the root pointer. AVLTree(AVLTree&& other): mRoot(other.mRoot), mSize(other.mSize) { other.mRoot = nullptr; other.mSize = 0; } // Disallow move assignment as we don't know how to deallocate nodes. AVLTree& operator=(AVLTree&& rhs) = delete; // Add a node to the tree. auto add(NodeType** link, NodeType& node, NodeType* parent) -> std::pair<Iterator, bool> { // Sanity. assert(link); // A node in the tree's already associated with this key. if (auto* child = *link) return std::make_pair(child, false); // Link in the user's node. *link = &node; // Make sure the user's node knows who its parent is. LT::parent(node) = parent; // Increment node counter. ++mSize; // Rebalance the tree, updating node metadata as needed. rebalance(&node); // Let the user know the node was added. return std::make_pair(&node, true); } // Add a node to the tree. auto add(NodeType& node) -> std::pair<Iterator, bool> { // Where should we link in the user's node? auto [parent, link] = findLink(KT::key(node)); // Try and add the node to the tree. return add(link, node, parent); } // Return an iterator to the first node in the tree. Iterator begin() { // No nodes? No iterator. if (!mRoot) return {}; auto* node = mRoot; // Locate the tree's smallest key. while (LT::left(*node)) node = LT::left(*node); return node; } // Return an iterator to the first node in the tree. ConstIterator begin() const { return const_cast<AVLTree&>(*this).begin(); } ConstIterator cbegin() const { return begin(); } // Return an iterator to the end of the tree. ConstIterator cend() const { return end(); } // Return a reverse iterator to the last node in the tree. ConstReverseIterator crbegin() const { return rbegin(); } // Return a reverse iterator to the end of the tree. ConstReverseIterator crend() const { return rend(); } // Does the tree contain any nodes? bool empty() const { return !mSize; } // Return an iterator to the end of the tree. Iterator end() { return {}; } // Return an iterator to the end of the tree. ConstIterator end() const { return {}; } // Try and locate key in the tree. // // This function returns two values, one directly and one through the // link parameter. // // The value returned directly by this function will be a pointer to the // last node that was traversed when searching for key. Put differently, // it will point to the parent of the node that does (or would) contain // key. // // The link parameter will point to the last child link that was taken // before traversal terminated. If key is already in the tree, *link // will reference the node that contains it. If key isn't in the tree // then we can use *link to attach a new directly to the appropriate // parent. auto findLink(const KeyType& key) -> std::pair<NodeType*, NodeType**> { // Start the search from the root. // // Note that link is a reference to a node pointer. auto link = &mRoot; // The root node has no parent. NodeType* parent{}; while (*link) { // Convenience. auto* child = *link; // How does the user's key relate to the child's? auto relationship = KT::compare(key, KT::key(*child)); // User's key is equivalent to the child's. if (!relationship) break; // Which child are we going to traverse into? // // If relationship is >0, traverse into the right child. // Otherwise, traverse into the left child. link = <::child(*child, relationship > 0); // This child is the parent of the next. parent = child; } return std::make_pair(parent, link); } // Return an iterator to the node associated with key. Iterator find(const KeyType& key) { // Try and locate the node associated with key. auto [_, link] = findLink(key); return *link; } // Return an iterator to the node associated with key. ConstIterator find(const KeyType& key) const { return const_cast<AVLTree&>(*this).find(key); } // Return a reference to the first node not less than key. Iterator lower_bound(const KeyType& key) { NodeType* candidate = nullptr; // Search the tree for key. for (auto* node = mRoot; node;) { // How does key relate to this node's key? auto relationship = KT::compare(key, KT::key(*node)); // Key's equivalent to this node's key. if (!relationship) return node; // Key's less than this node's key. if (relationship < 0) candidate = node; // Continue the search down the tree. node = LT::child(*node, relationship > 0); } // If candidate's not null, it'll always reference the node with the // smallest key greater than key. return candidate; } // Return a reference to the first node not less than key. ConstIterator lower_bound(const KeyType& key) const { return const_cast<AVLTree&>(*this).lower_bound(key); } // Return a reverse iterator to the last node in the tree. ReverseIterator rbegin() { // No nodes? No iterator. if (!mRoot) return {}; auto* node = mRoot; // Locate the tree's largest key. while (LT::right(*node)) node = LT::right(*node); return node; } ReverseIterator rbegin() const { return const_cast<AVLTree&>(*this).rbegin(); } // Remove the node associated with the specified key. NodeType* remove(const KeyType& key) { // Try and locate the node in the tree. auto [parent, link] = findLink(key); // Key is associated with some node in the tree. if (*link) return remove(link, parent); // Key isn't associated with any node in the tree. return nullptr; } // Remove the node identified by this iterator from the tree. NodeType* remove(Iterator iterator) { // Make sure our iterator is valid. assert(iterator); // What node is the iterator referencing? auto& node = *iterator; // Who is our node's parent? auto* parent = LT::parent(node); // We're removing the root node. if (!parent) return remove(&mRoot, nullptr); // Get a reference to our parent's child links. auto* link = <::left(*parent); // Is node parent's left or right child? link = &link[*link != &node]; // Remove node from the tree. return remove(link, parent); } // Return a reverse iterator to the end of the tree. ReverseIterator rend() { return {}; } ConstReverseIterator rend() const { return {}; } // Return an iterator to this tree's root node. Iterator root() { return mRoot; } // Return a const iterator to this tree's root node. ConstIterator root() const { return const_cast<AVLTree&>(*this).root(); } // How many nodes does this tree contain? std::size_t size() const { return mSize; } // Swap the contents of this tree with another. void swap(AVLTree& other) { using std::swap; swap(mRoot, other.mRoot); swap(mSize, other.mSize); } // Return a reference to the first node greater than key. Iterator upper_bound(const KeyType& key) { NodeType* candidate = nullptr; // Search the tree for key. for (auto* node = mRoot; node;) { // How does key relate to this node's key? auto relationship = KT::compare(key, KT::key(*node)); // Key's less than this node's key. if (relationship < 0) candidate = node; // Continue the search down the tree. node = LT::child(*node, relationship >= 0); } // If candidate's not null, it'll be the first node greater than key. return candidate; } ConstIterator upper_bound(const KeyType& key) const { return const_cast<AVLTree&>(*this).upper_bound(key); } }; // AVLTree<Traits, false> template<typename Traits> class AVLTree<Traits, true>: public AVLTree<Traits, false> { public: bool operator==(const AVLTree& rhs) const { // A tree's always equal to itself. if (this == &rhs) return true; // Can't be equal if the trees differ in size. if (this->size() != rhs.size()) return false; const auto end = this->end(); auto i = this->begin(); // Convenience. using KT = typename AVLTree::KeyTraits; // Iterate over the tree comparing values as we go. for (auto m = rhs.begin(); i != end && KT::value(*i) == KT::value(*m); ++i, ++m) ; // Make sure we compared every value in the tree. return i == end; } bool operator!=(const AVLTree& rhs) const { return !(*this == rhs); } }; // AVLTree<Traits, true> // Swap the contents of lhs with rhs. template<typename Traits, auto IsEqualityComparable> void swap(AVLTree<Traits, IsEqualityComparable>& lhs, AVLTree<Traits, IsEqualityComparable>& rhs) { lhs.swap(rhs); } } // detail using detail::AVLTree; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/avl_tree_forward.h�����������������������������������0000664�0000000�0000000�00000000177�15162662266�0024750�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { template<typename Traits> class AVLTree; } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/avl_tree_iterator.h����������������������������������0000664�0000000�0000000�00000012033�15162662266�0025127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree_traits.h> #include <cassert> #include <cstdint> #include <iterator> #include <type_traits> namespace mega { namespace file_service { template<typename BaseNodeType, typename LinkTraits, auto IsConstIterator, auto IsReverseIterator> class AVLTreeIterator { // Convenience. template<auto IsConst, auto IsReverse> using CompatibleIteratorType = AVLTreeIterator<BaseNodeType, LinkTraits, IsConst, IsReverse>; // Determine our actual node type. using NodeType = std::conditional_t<IsConstIterator, const BaseNodeType, BaseNodeType>; // Move the iterator forward one node. AVLTreeIterator& next() { assert(mNode); if (auto* node = LinkTraits::right(*mNode)) { mNode = node; node = LinkTraits::left(*node); for (; node; node = LinkTraits::left(*node)) mNode = node; return *this; } while (true) { auto* node = mNode; mNode = LinkTraits::parent(*node); if (!mNode) break; if (LinkTraits::right(*mNode) != node) break; } return *this; } // Move the iterator backwards one node. AVLTreeIterator& previous() { assert(mNode); if (auto* node = LinkTraits::left(*mNode)) { mNode = node; node = LinkTraits::right(*node); for (; node; node = LinkTraits::right(*node)) mNode = node; return *this; } while (true) { auto* node = mNode; mNode = LinkTraits::parent(*node); if (!mNode) break; if (LinkTraits::left(*mNode) != node) break; } return *this; } // Where we are in the tree. NodeType* mNode{}; public: // Minimal STL support. using difference_type = std::ptrdiff_t; using iterator_category = std::bidirectional_iterator_tag; using pointer = NodeType*; using reference = NodeType&; using value_type = NodeType; AVLTreeIterator() = default; AVLTreeIterator(NodeType* node): mNode(node) {} template<auto IsConst, auto IsReverse> AVLTreeIterator(const CompatibleIteratorType<IsConst, IsReverse>& other): mNode(const_cast<NodeType*>(other.nodePointer())) {} explicit operator bool() const { return mNode != nullptr; } NodeType& operator*() const { assert(mNode); return *mNode; } NodeType* operator->() const { assert(mNode); return mNode; } bool operator==(const AVLTreeIterator& rhs) const { return mNode == rhs.mNode; } bool operator!=(const AVLTreeIterator& rhs) const { return !(*this == rhs); } bool operator!() const { return !mNode; } AVLTreeIterator& operator++() { if constexpr (IsReverseIterator) { return previous(); } else { return next(); } } AVLTreeIterator operator++(int) { AVLTreeIterator result = *this; ++(*this); return result; } AVLTreeIterator& operator--() { if constexpr (IsReverseIterator) { return next(); } else { return previous(); } } AVLTreeIterator operator--(int) { AVLTreeIterator result = *this; --(*this); return result; } AVLTreeIterator left() const { assert(mNode); return LinkTraits::left(*mNode); } NodeType* nodePointer() const { return mNode; } AVLTreeIterator parent() const { assert(mNode); return LinkTraits::parent(*mNode); } AVLTreeIterator right() const { assert(mNode); return LinkTraits::right(*mNode); } }; // AVLTreeIterator<BaseNodeType, LinkTraits, IsConstIterator, IsReverseIterator> // Convenience. template<typename Type> struct ToConstIterator; template<typename BaseNodeType, typename LinkTraits, auto IsConstIterator, auto IsReverseIterator> struct ToConstIterator< AVLTreeIterator<BaseNodeType, LinkTraits, IsConstIterator, IsReverseIterator>> { using Type = AVLTreeIterator<BaseNodeType, LinkTraits, true, IsReverseIterator>; }; // ToConstIterator<AVLTreeIterator<...>> template<typename Type> using ToConstIteratorT = typename ToConstIterator<Type>::Type; template<typename Type> struct ToReverseIterator; template<typename BaseNodeType, typename LinkTraits, auto IsConstIterator, auto IsReverseIterator> struct ToReverseIterator< AVLTreeIterator<BaseNodeType, LinkTraits, IsConstIterator, IsReverseIterator>> { using Type = AVLTreeIterator<BaseNodeType, LinkTraits, IsConstIterator, true>; }; // ToReverseIterator<AVLTreeIterator<...>> template<typename Type> using ToReverseIteratorT = typename ToReverseIterator<Type>::Type; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/avl_tree_node.h��������������������������������������0000664�0000000�0000000�00000002135�15162662266�0024225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <cstdint> namespace mega { namespace file_service { using AVLTreeHeight = std::uint8_t; template<typename NodeType> struct AVLTreeNode { // Reference to this node's left and right children. // // The reason the child links are defined as an array rather // than as separate mLeft and mRight members is that it enables // us to reduce duplication when implementing the tree later. // // For instance, we can replace something like this: // // NodeType* next; // // if (compare(key, node.mKey) < 0) // next = node.mLeft; // else if (compare(node.mKey, key) >= 0) // next = node.mRight; // // ... // // With this: // // auto relationship = compare(key, node.mKey); // auto* next = node.mChildren[relationship > 0]; NodeType* mChildren[2]; // Reference to this node's parent. NodeType* mParent; // Tracks how "tall" or "deep" this subtree is. // // We use this member to compute a node's balance. AVLTreeHeight mHeight; }; // AVLTreeNode<NodeType> } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/avl_tree_traits.h������������������������������������0000664�0000000�0000000�00000021223�15162662266�0024605�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree_node.h> #include <mega/file_service/type_traits.h> #include <cassert> #include <cmath> #include <functional> namespace mega { namespace file_service { namespace detail { // Check whether Traits contains a Compare type. template<typename Traits> using DetectCompareType = typename Traits::Compare; // Check whether Traits contains a key functor. template<typename Traits> using DetectKeyFunction = typename Traits::KeyFunction; // Check whether Traits contains an mLinkPointer member. template<typename Traits> using DetectLinkPointer = decltype(Traits::mLinkPointer); // Check whether Traits contains an update member. template<typename Traits> using DetectUpdate = typename Traits::Update; // Check whether Traits contains an mValuePointer member. template<typename Traits> using DetectValuePointer = decltype(Traits::mValuePointer); template<typename Traits> class KeyTraits { // Check whether Traits contains a mValuePointer member and if it does, // whether that member is a class member pointer. // // DetectedT<DetectValuePointer, Traits> will be yield NoneSuch if // Traits doesn't contain a mValuePointer member. using ValuePointerTraits = MemberPointerTraits<DetectedT<DetectValuePointer, Traits>>; // Ensure Traits::mValuePointer is present and is a class member pointer. static_assert(ValuePointerTraits::value); public: // Default to Identity if Traits::KeyFunction isn't defined. using KeyFunction = DetectedOrT<Identity, DetectKeyFunction, Traits>; // Convenience. using ValueType = typename ValuePointerTraits::MemberType; // Make sure the user's provided a sane key functor. static_assert(std::is_invocable_v<KeyFunction, ValueType>); // Determine the tree's key type. using KeyType = RemoveCVRefT<std::invoke_result_t<KeyFunction, ValueType>>; // Determine the tree's node type. using NodeType = typename ValuePointerTraits::ClassType; // Default to std::less<KeyType> if Traits::Compare isn't defined. using Compare = DetectedOrT<std::less<KeyType>, DetectCompareType, Traits>; // Make sure our comparator is sane. static_assert(std::is_invocable_r_v<bool, Compare const&, const KeyType&, const KeyType&>); // Compare lhs and rhs. // // Returns // <0 if lhs < rhs // =0 if lhs == rhs // >0 if lhs > rhs static auto compare(const KeyType& lhs, const KeyType& rhs) { Compare compare{}; // lhs is less than rhs. if (compare(lhs, rhs)) return -1; // lhs is greater than rhs. if (compare(rhs, lhs)) return +1; // lhs is equal to rhs. return 0; } // Return a reference to the key contained by node. template<typename NodeType> static auto& key(NodeType& node) { return KeyFunction()(value(node)); } // Return a reference to the value contained by node. template<typename NodeType> static auto& value(NodeType& node) { return node.*Traits::mValuePointer; } }; // KeyTraits<Traits> template<typename Traits> class LinkTraits { // Same technique as for KeyPointerTraits above. using LinkPointerTraits = MemberPointerTraits<DetectedT<DetectLinkPointer, Traits>>; static_assert(LinkPointerTraits::value); public: using LinkType = typename LinkPointerTraits::MemberType; using NodeType = typename LinkPointerTraits::ClassType; // Make sure mLinkPointer actually refers to an AVL node instance. static_assert(std::is_base_of_v<AVLTreeNode<NodeType>, LinkType>); // Compute this node's balance. // // <0 if the node's left subtree is taller than its right subtree. // =0 if the node's subtrees are the same height. // >0 if the node's right subtree is taller than its left subtree. // // We use this value to determine whether we have to rebalance the // subtree rooted at this node. template<typename NodeType> static auto balance(NodeType& node) { auto balance = 0; if (auto* right = LinkTraits::right(node)) balance = height(*right); if (auto* left = LinkTraits::left(node)) balance -= height(*left); // Ensure the node's balance is within [-2, +2]. assert(std::abs(balance) < 3); return balance; } // Return a reference to one of the node's child pointers. // // direction specifies which child pointer we want to reference. // // When true, return a reference to the node's right child pointer. // Otherwise, return a reference to the node's left child pointer. template<typename NodeType> static auto& child(NodeType& node, bool direction) { return link(node).mChildren[direction]; } // Return a reference to the node's height member. template<typename NodeType> static auto& height(NodeType& node) { return link(node).mHeight; } // Return a reference to the node's left child pointer. template<typename NodeType> static auto& left(NodeType& node) { return child(node, 0); } // Return a reference to this node's node link member. template<typename NodeType> static auto& link(NodeType& node) { return node.*Traits::mLinkPointer; } // Return a reference to the node's parent pointer. template<typename NodeType> static auto& parent(NodeType& node) { return link(node).mParent; } // Return a reference to the node's right child pointer. template<typename NodeType> static auto& right(NodeType& node) { return child(node, 1); } }; // LinkTraits<Traits> // Used when a client isn't using an augmented tree. template<typename Traits, typename = void> struct MetadataTraits { // Provide a dummy method so our AVL code doesn't care whether the // user's tree is augmented or not. template<typename IteratorType, typename NodeType> static void update(NodeType&) {} }; // MetadataTraits<Traits, void> // Traits has a mMetadataPointer member. template<typename Traits> class MetadataTraits<Traits, std::void_t<decltype(Traits::mMetadataPointer)>> { // Same technique as for KeyPointerTraits above. using MetadataPointerTraits = MemberPointerTraits<decltype(Traits::mMetadataPointer)>; // If mMetadataPointer is present, make sure it's a class member pointer. static_assert(MetadataPointerTraits::value); using MetadataType = typename MetadataPointerTraits::MemberType; public: using NodeType = typename MetadataPointerTraits::ClassType; // If Traits::mMetadataPointer exists so much Traits::update(...). // // This function is called when a node's metadata needs to be updated. // // As an example, imagine the user's defining an augmented tree that // where each node knows how many children it has. // // Let's add the keys 0, 1, 2 in that order. // // Notation is this: key(size). // // Add 0: // 0(0) // // When 0 is added, update(...) is called on the new node to compute the // sizes of its children, if any. // // Add 1: // 0(1) -. // 1(0) // // When 1 is added, we update its size as the case above but note that // the tree's structure has changed as 0 now has a right child. So, we // traverse up the tree and update 0's metadata, too. // // Add 2: // 0 -. .--- 1(2) ---. // 1 -. -> rebalance -> 0(0) 2(0) // 2 // // When 2 is added, the tree becomes imbalanced so the structure of the // tree is altered to restore that balance. Since the tree's structure // has changed, we need to update the metadata of each node that has // been altered. The result is what you'd expect. using Update = DetectedT<DetectUpdate, Traits>; static_assert(IsNotNoneSuchV<Update>); // Return a reference to a node's metadata. template<typename NodeType> static auto& metadata(NodeType& node) { return node.*Traits::mMetadataPointer; } // Update a node's metadata based on that of its children. template<typename IteratorType, typename NodeType> static void update(NodeType& node) { // Make sure our update functor accepts an iterator. static_assert(std::is_invocable_r_v<MetadataType, Update, IteratorType>); IteratorType iterator(&node); // Recompute this node's metadata. metadata(node) = Update()(iterator); } }; // MetadataTraits<Traits, void> // Convenience. template<typename Traits> constexpr auto ValueIsEqualityComparableV = IsEqualityComparableV<typename KeyTraits<Traits>::ValueType>; } // detail } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/buffer.h���������������������������������������������0000664�0000000�0000000�00000001447�15162662266�0022675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer_pointer.h> #include <mega/file_service/sink.h> #include <mega/file_service/source.h> namespace mega { struct FileAccess; namespace file_service { class Buffer: public Sink, public Source { protected: Buffer() = default; public: virtual ~Buffer() = default; // Copy data from this buffer to another. virtual auto copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> = 0; // Check if this buffer is a file buffer. virtual bool isFileBuffer() const = 0; // Check if this buffer is a memory buffer. bool isMemoryBuffer() const; }; // Buffer } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/buffer_forward.h�������������������������������������0000664�0000000�0000000�00000000144�15162662266�0024412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class Buffer; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/buffer_pointer.h�������������������������������������0000664�0000000�0000000�00000000303�15162662266�0024423�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer_forward.h> #include <memory> namespace mega { namespace file_service { using BufferPtr = std::shared_ptr<Buffer>; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/database_builder.h�����������������������������������0000664�0000000�0000000�00000000561�15162662266�0024672�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/database_builder.h> namespace mega { class LocalPath; namespace file_service { class DatabaseBuilder: public common::DatabaseBuilder { auto versions() const -> const common::DatabaseVersionVector& override; public: explicit DatabaseBuilder(common::Database& database); }; // DatabaseBuilder } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/displaced_buffer.h�����������������������������������0000664�0000000�0000000�00000002612�15162662266�0024700�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer.h> #include <mega/file_service/buffer_pointer.h> #include <mega/file_service/displaced_buffer_forward.h> #include <mega/file_service/displaced_buffer_pointer.h> #include <cstdint> namespace mega { namespace file_service { class DisplacedBuffer: public Buffer { BufferPtr mBuffer; std::uint64_t mDisplacement; public: DisplacedBuffer(BufferPtr buffer, std::uint64_t displacement); // Copy data from this buffer to another. auto copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Update our displacement. void displacement(std::uint64_t displacement); // What is our displacement? std::uint64_t displacement() const; // Check if this buffer is a file buffer. bool isFileBuffer() const override; // Read data from the buffer. auto read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Write data into the buffer. auto write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> override; }; // DisplacedBuffer // Displace a buffer. DisplacedBufferPtr displace(BufferPtr buffer, std::uint64_t displacement); } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/displaced_buffer_forward.h���������������������������0000664�0000000�0000000�00000000155�15162662266�0026424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class DisplacedBuffer; } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/displaced_buffer_pointer.h���������������������������0000664�0000000�0000000�00000000337�15162662266�0026442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/displaced_buffer_forward.h> #include <memory> namespace mega { namespace file_service { using DisplacedBufferPtr = std::shared_ptr<DisplacedBuffer>; } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_access.h����������������������������������������0000664�0000000�0000000�00000000715�15162662266�0023661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <cstdint> #include <utility> namespace mega { struct FileAccess; namespace file_service { auto read(FileAccess& file, void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool>; auto write(FileAccess& file, const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool>; bool truncate(FileAccess& file, std::uint64_t newSize); } // file_service } // mega ���������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_append_request.h��������������������������������0000664�0000000�0000000�00000001356�15162662266�0025441�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_append_request_forward.h> #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_request_tags.h> #include <cstdint> namespace mega { namespace file_service { struct FileAppendRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "append"; } // What data does the user want to append to the file? const void* mBuffer; // Who should we call when the append completes? FileAppendCallback mCallback; // How much data does the user want to append? std::uint64_t mLength; }; // FileAppendRequest } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_append_request_forward.h������������������������0000664�0000000�0000000�00000000160�15162662266�0027155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileAppendRequest; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_buffer.h����������������������������������������0000664�0000000�0000000�00000002120�15162662266�0023661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer.h> #include <mega/file_service/file_buffer_forward.h> #include <cstdint> namespace mega { struct FileAccess; namespace file_service { class FileBuffer: public Buffer { protected: FileAccess& mFile; public: explicit FileBuffer(FileAccess& file); // Copy data from this buffer to another. auto copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Check if this buffer is a file buffer. bool isFileBuffer() const override; // Read data from the buffer. auto read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Write data into the buffer. auto write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> override; // Truncate the file's size. virtual bool truncate(std::uint64_t newSize); }; // FileBuffer } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_buffer_forward.h��������������������������������0000664�0000000�0000000�00000000150�15162662266�0025406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class FileBuffer; } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_buffer_pointer.h��������������������������������0000664�0000000�0000000�00000000320�15162662266�0025421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_buffer_forward.h> #include <memory> namespace mega { namespace file_service { using FileBufferPtr = std::shared_ptr<FileBuffer>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_context.h���������������������������������������0000664�0000000�0000000�00000025556�15162662266�0024116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/database_forward.h> #include <mega/common/instance_logger.h> #include <mega/common/lock_forward.h> #include <mega/common/node_key_data.h> #include <mega/common/transaction_forward.h> #include <mega/file_service/buffer_pointer.h> #include <mega/file_service/file_append_request_forward.h> #include <mega/file_service/file_buffer_pointer.h> #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_context_forward.h> #include <mega/file_service/file_context_pointer.h> #include <mega/file_service/file_event_observer.h> #include <mega/file_service/file_event_observer_id.h> #include <mega/file_service/file_fetch_request_forward.h> #include <mega/file_service/file_flush_request_forward.h> #include <mega/file_service/file_forward.h> #include <mega/file_service/file_info_context_pointer.h> #include <mega/file_service/file_info_forward.h> #include <mega/file_service/file_range_context_manager.h> #include <mega/file_service/file_range_context_pointer_map.h> #include <mega/file_service/file_range_forward.h> #include <mega/file_service/file_range_vector.h> #include <mega/file_service/file_read_request_forward.h> #include <mega/file_service/file_read_write_state.h> #include <mega/file_service/file_reclaim_request_forward.h> #include <mega/file_service/file_remove_request_forward.h> #include <mega/file_service/file_request_list.h> #include <mega/file_service/file_request_traits.h> #include <mega/file_service/file_service_context_forward.h> #include <mega/file_service/file_service_options_forward.h> #include <mega/file_service/file_touch_request_forward.h> #include <mega/file_service/file_truncate_request.h> #include <mega/file_service/file_write_request_forward.h> #include <mega/types.h> #include <functional> #include <list> #include <memory> #include <mutex> #include <type_traits> #include <variant> namespace mega { namespace file_service { class FileContext final: FileRangeContextManager, public std::enable_shared_from_this<FileContext> { // Tracks state necessary for fetch. class FetchContext; // Tracks state necessary for flush. class FlushContext; // Tracks state necessary for reclaim. class ReclaimContext; // Convenience. using FetchContextPtr = std::shared_ptr<FetchContext>; using FlushContextPtr = std::shared_ptr<FlushContext>; using FlushContextWeakPtr = std::weak_ptr<FlushContext>; using ReclaimContextPtr = std::shared_ptr<ReclaimContext>; // Add a range to the database. void addRange(const FileRange& range, common::Transaction& transaction); // Cancel any reads intersect the specified range. void cancel(const FileRange& range); // Cancel a pending request. void cancel(FileRequest& request); // Cancel any downloads and pending requests. void cancel(); // Called when a file range has been downloaded. void completed(Buffer& buffer, FileRangeContextPtrMap::Iterator iterator, FileRange range) override; // Called when a file read request has been completed. void completed(BufferPtr buffer, FileReadRequest&& request) override; // Called when a file request has been completed. template<typename Request, typename Result, typename... Captures> auto completed(Request&& request, Result result, Captures&&... captures) -> std::enable_if_t<IsFileRequestV<Request>>; // Called when a file write request has been completed. void completed(FileWriteRequest&& request); // Called when a request of a particular class is dequeued. void dequeued(std::unique_lock<std::mutex> lock, FileReadRequestTag tag); void dequeued(std::unique_lock<std::mutex> lock, FileWriteRequestTag tag); // Called when a request has been dequeued. void dequeued(std::unique_lock<std::mutex> lock, const FileRequest& request); // Check if a request can be executed. bool executable(std::unique_lock<std::mutex>& lock, bool queuing, const FileRequest& request); // Check if a particular class of request can be executed. bool executable(std::unique_lock<std::mutex>& lock, bool queuing, FileReadRequestTag tag); bool executable(std::unique_lock<std::mutex>& lock, bool queuing, FileWriteRequestTag tag); // Called to execute an arbitrary function on the service's thread pool. void execute(std::function<void()> function) override; // Try and execute an append request. void execute(FileAppendRequest& request); // Try and execute a fetch request. void execute(FileFetchRequest& request); // Try and execute a flush request. void execute(FileFlushRequest request); // Try and execute a read request. void execute(FileReadRequest& request); // Try and execute a reclaim request. void execute(FileReclaimRequest& request); // Try and execute a remove request. void execute(FileRemoveRequest& request); // Try and execute a touch request. void execute(FileTouchRequest& request); // Try and execute a truncate request. void execute(FileTruncateRequest& request); // Try and execute a write request. void execute(FileWriteRequest& request); // Try and execute a request. void execute(FileRequest& request); // Execute zero or more queued requests. void execute(); // Execute a request if possible otherwise queue it for later execution. template<typename Request> auto executeOrQueue(Request&& request) -> std::enable_if_t<IsFileRequestV<Request>>; // Called when a request of a particular class has executed. void executed(FileReadRequestTag tag); void executed(FileWriteRequestTag tag); // Called when a file read request has failed. void failed(FileReadRequest&& request, FileResult result) override; // Increase this file's size. auto grow(std::uint64_t newSize, std::uint64_t oldSize) -> std::pair<common::UniqueLock<common::Database>, common::Transaction>; // Acquire a lock on this manager. std::unique_lock<std::recursive_mutex> lock() const override; // Return a reference to the mutex protecting this manager. std::recursive_mutex& mutex() const override; // Retrieve a copy of the service's current options. FileServiceOptions options() const override; // Queue a request for later execution. template<typename Request> auto queue(std::unique_lock<std::mutex> lock, Request&& request) -> std::enable_if_t<IsFileRequestV<Request>>; // Called when a request of a particular class has been queued. void queued(std::unique_lock<std::mutex> lock, FileReadRequestTag tag); void queued(std::unique_lock<std::mutex> lock, FileWriteRequestTag tag); // Return an error if this request should be rejected. template<typename Request> auto reject(const Request& request) -> std::enable_if_t<IsFileRequestV<Request>, FileResult>; // Remove zero or more ranges from the database. void removeRanges(const FileRange& range, common::Transaction& transaction); // Mark the file as removed. FileResult setRemoved(bool replaced); // Decrease this file's size. auto shrink(std::uint64_t newSize, std::uint64_t oldSize) -> std::pair<common::UniqueLock<common::Database>, common::Transaction>; // Update this file's access and modification time in the database. void updateAccessAndModificationTimes(std::int64_t accessed, std::int64_t modified, common::Transaction& transaction); // Update the file's sizes in the database. void updateSize(std::uint64_t size, common::Transaction& transaction); // Logs instance lifetime. common::InstanceLogger<FileContext> mInstanceLogger; // Keep our service alive until we're dead. common::Activity mActivity; // Wraps mFile and unifies logic. FileBufferPtr mBuffer; // How we get and set our file's attributes. FileInfoContextPtr mInfo; // Tracks any fetch in progress. FetchContextPtr mFetchContext; // Serializes access to mFetchContext. std::recursive_mutex mFetchContextLock; // The file storing our data. FileAccessPtr mFile; // Tracks any flush in progress. FlushContextPtr mFlushContext; // Serializes access to mFlushContext. std::recursive_mutex mFlushContextLock; // The file's decryption key, IV and authentication tokens. const std::optional<common::NodeKeyData> mKeyData; // How many write requests are pending? std::size_t mNumPendingWriteRequests; // What ranges of the file do we have? FileRangeContextPtrMap mRanges; // Serializes access to mRanges. mutable std::recursive_mutex mRangesLock; // Tracks whether any reads or writes are in progress. FileReadWriteState mReadWriteState; // Tracks any reclaim in progress. ReclaimContextPtr mReclaimContext; // Serializes access to mReclaimContext. std::mutex mReclaimContextLock; // Tracks pending requests. FileRequestList mRequests; // Serializes access to mRequests. std::mutex mRequestsLock; // The service that manages this context. FileServiceContext& mService; // Keeps us alive until all of our ranges have died. common::ActivityMonitor mActivities; public: FileContext(common::Activity activity, FileAccessPtr file, FileInfoContextPtr info, std::optional<common::NodeKeyData> keyData, const FileRangeVector& ranges, FileServiceContext& service); ~FileContext(); // Notify an observer when this file's information changes. FileEventObserverID addObserver(FileEventObserver observer); // Append data to the end of this file. void append(FileAppendRequest request); // Fetch all of this file's data from the cloud. void fetch(FileFetchRequest request); // Wait until all fetches in progress have completed. void fetchBarrier(FileFetchBarrierCallback callback); // Flush this file's local modifications to the cloud. void flush(FileFlushRequest request); // Retrieve information about this file. FileInfo info() const; // What ranges of this file are currently in storage? FileRangeVector ranges() const; // Read data from this file. void read(FileReadRequest request); // Reclaim this file's storage. void reclaim(FileReclaimCallback callback); // Remove this file. void remove(FileRemoveRequest request); // Remove a previously added observer. void removeObserver(FileEventObserverID id); // Check if this file has been removed. bool removed() const; // Update the file's modification time. void touch(FileTouchRequest request); // Truncate this file to a specified size. void truncate(FileTruncateRequest request); // Write data to this file. void write(FileWriteRequest request); }; // FileContext } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_context_badge.h���������������������������������0000664�0000000�0000000�00000000147�15162662266�0025225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/badge.h> #include <mega/file_service/file_context_badge_forward.h> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_event_emitter.h���������������������������������0000664�0000000�0000000�00000002124�15162662266�0025266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_event_emitter_forward.h> #include <mega/file_service/file_event_forward.h> #include <mega/file_service/file_event_observer.h> #include <mega/file_service/file_event_observer_id.h> #include <map> #include <mutex> namespace mega { namespace file_service { class FileEventEmitter { // Convenience. using FileEventObserverMap = std::map<std::uint64_t, FileEventObserver>; // Next available observer ID. std::uint64_t mNextID = 0u; // Who should we notify when an event is emitted? FileEventObserverMap mObservers; // Serializes access to mNextID and mObservers. std::mutex mObserversLock; protected: FileEventEmitter() = default; ~FileEventEmitter() = default; public: // Notify observer when a file changes. FileEventObserverID addObserver(FileEventObserver observer); // Transmit event to all registered observers. void notify(const FileEvent& event); // Remove a previously added observer. void removeObserver(FileEventObserverID id); }; // FileEventEmitter } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_event_vector.h����������������������������������0000664�0000000�0000000�00000000304�15162662266�0025115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_event.h> #include <vector> namespace mega { namespace file_service { using FileEventVector = std::vector<FileEvent>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_fetch_request.h���������������������������������0000664�0000000�0000000�00000001057�15162662266�0025261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_fetch_request_forward.h> #include <mega/file_service/file_request_tags.h> namespace mega { namespace file_service { struct FileFetchRequest { // What kind of request is this? using Type = FileReadRequestTag; // This request's human readable name. static const char* name() { return "fetch"; } // Who should we call when the fetch completes? FileFetchCallback mCallback; }; // FileFetchRequest } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_fetch_request_forward.h�������������������������0000664�0000000�0000000�00000000157�15162662266�0027005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileFetchRequest; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_flush_request.h���������������������������������0000664�0000000�0000000�00000001113�15162662266�0025302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_flush_request_forward.h> #include <mega/file_service/file_request_tags.h> #include <mega/localpath.h> namespace mega { namespace file_service { struct FileFlushRequest { // What kind of request is this? using Type = FileReadRequestTag; // This request's human readable name. static const char* name() { return "flush"; } // Who should we call when the flush completes? FileFlushCallback mCallback; }; // FileFlushRequest } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_flush_request_forward.h�������������������������0000664�0000000�0000000�00000000157�15162662266�0027035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileFlushRequest; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_id_vector.h�������������������������������������0000664�0000000�0000000�00000000303�15162662266�0024367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_id_forward.h> #include <vector> namespace mega { namespace file_service { using FileIDVector = std::vector<FileID>; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_info_context.h����������������������������������0000664�0000000�0000000�00000011427�15162662266�0025121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/activity_monitor.h> #include <mega/common/instance_logger.h> #include <mega/common/shared_mutex.h> #include <mega/file_service/file_event_emitter.h> #include <mega/file_service/file_event_observer.h> #include <mega/file_service/file_event_observer_id.h> #include <mega/file_service/file_id.h> #include <mega/file_service/file_info_context_forward.h> #include <mega/file_service/file_location.h> #include <mega/file_service/file_range_forward.h> #include <mega/file_service/file_service_context_forward.h> #include <mega/file_service/file_size_info.h> #include <mega/types.h> #include <mutex> #include <optional> #include <type_traits> namespace mega { namespace file_service { class FileInfoContext: FileEventEmitter, public FileSizeInfo { // Maps observer IDs to observer callbacks. using FileEventObserverMap = std::map<FileEventObserverID, FileEventObserver>; // Retrieve one of our properties in a thread-safe manner. template<typename T> auto get(T FileInfoContext::* const property) const; // Set one of our properties in a thread-safe manner. template<typename T, typename U> void set(T FileInfoContext::*property, U&& value); // Transmit an event to all registered observers. void notify(const FileEvent& event); // Logs instance lifetime. common::InstanceLogger<FileInfoContext> mInstanceLogger; // When was the file last accessed? std::int64_t mAccessed; // Makes sure mService isn't destroyed until we are. common::Activity mActivity; // How much disk space has been allocated to this file? std::uint64_t mAllocatedSize; // Whether this file's been locally modified. bool mDirty; // The node in the cloud this file is associated with, if any. NodeHandle mHandle; // The unique identifier for this file. const FileID mID; // Where is this file located in the cloud? std::optional<FileLocation> mLocation; // Serializes access to our members. mutable common::SharedMutex mLock; // The time the file was last modified. std::int64_t mModified; // Has this file been removed? bool mRemoved; // How large does the filesystem say this file is? std::uint64_t mReportedSize; // The service managing this instance. FileServiceContext& mService; // How large is this file conceptually? std::uint64_t mSize; public: FileInfoContext(std::int64_t accessed, common::Activity activity, std::uint64_t allocatedSize, bool dirty, NodeHandle handle, FileID id, std::optional<FileLocation> location, std::int64_t modified, std::uint64_t reportedSize, FileServiceContext& service, std::uint64_t size); ~FileInfoContext(); // Set the file's last access time. void accessed(std::int64_t accessed); // When was this file last accessed? std::int64_t accessed() const; // Add an observer. using FileEventEmitter::addObserver; // Update this file's allocated size. void allocatedSize(std::uint64_t allocatedSize) override; // How much disk space has been allocated to this file? std::uint64_t allocatedSize() const override; // Has the file been locally modified? bool dirty() const; // Signal that this file has been flushed to the cloud. void flushed(NodeHandle handle); // What node is this file associated with? auto handle() const -> NodeHandle; // What is this file's identifier? auto id() const -> FileID; // Specify where this file is located in the cloud. void location(const FileLocation& location); // Where is this file located in the cloud? std::optional<FileLocation> location() const; // Update the file's access and modification time. void modified(std::int64_t accessed, std::int64_t modified); // When was this file last modified? auto modified() const -> std::int64_t; // Remove an observer. using FileEventEmitter::removeObserver; // Specify whether this file has been removed. void removed(bool replaced); // Has this file been removed? bool removed() const; // Update this file's reported size. void reportedSize(std::uint64_t reportedSize) override; // How large does the filesystem say this file is? std::uint64_t reportedSize() const override; // How large is this file conceptually? std::uint64_t size() const override; // Signal that the file has been truncated. void truncated(std::int64_t modified, std::uint64_t size); // Signal that data has been written to the file. void written(std::int64_t modified, const FileRange& range); }; // FileInfoContext } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_info_context_badge.h����������������������������0000664�0000000�0000000�00000000136�15162662266�0026236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/file_service/file_info_context_badge_forward.h> ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_info_context_badge_forward.h��������������������0000664�0000000�0000000�00000000364�15162662266�0027765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/badge_forward.h> #include <mega/file_service/file_info_context_forward.h> namespace mega { namespace file_service { using FileInfoContextBadge = common::Badge<FileInfoContext>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context.h���������������������������������0000664�0000000�0000000�00000006321�15162662266�0025257�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/client_forward.h> #include <mega/common/instance_logger.h> #include <mega/common/node_key_data.h> #include <mega/common/partial_download_callback.h> #include <mega/common/partial_download_forward.h> #include <mega/file_service/buffer_pointer.h> #include <mega/file_service/file_buffer_pointer.h> #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_range_context_forward.h> #include <mega/file_service/file_range_context_manager_forward.h> #include <mega/file_service/file_range_context_pointer_map.h> #include <mega/file_service/file_read_request_forward.h> #include <mega/file_service/file_read_request_set.h> #include <cstdint> #include <mutex> namespace mega { class Error; class NodeHandle; struct FileAccess; namespace file_service { class FileRangeContext: private common::PartialDownloadCallback { // Called when the file range has been downloaded. template<typename Lock> void completed(Lock&& lock, Error result); void completed(Error result) override; // Called repeatedly as data is donwloaded from the cloud. auto data(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::variant<Abort, Continue> override; // Dispatch zero or more read requests. void dispatch(BufferPtr buffer, std::uint64_t begin, std::uint64_t minimumLength); // Check if a request can be dispatched. bool dispatchable(const FileReadRequest& request, std::uint64_t minimumLength) const; // Called when our download's encountered a failure. virtual auto failed(Error result, int retries) -> std::variant<Abort, Retry> override; // Logs instance lifetime. common::InstanceLogger<FileRangeContext> mInstanceLogger; // Keeps our manager alive until we're dead. common::Activity mActivity; // The buffer containing our downloaded data. BufferPtr mBuffer; // Callbacks to execute when this range's fetch completes. std::vector<FileFetchCallback> mCallbacks; // The download that's retrieving this file range's data. common::PartialDownloadPtr mDownload; // Where does our downloaded data currently end? std::uint64_t mEnd; // Where are we in our manager's map of contexts? FileRangeContextPtrMap::Iterator mIterator; // Who's responsible for this context? FileRangeContextManager& mManager; // Requests pending completion. FileReadRequestSet mRequests; public: FileRangeContext(common::Activity activity, FileRangeContextPtrMap::Iterator iterator, FileRangeContextManager& manager); ~FileRangeContext(); // Cancel this range's download. void cancel(); // Create a download this range. auto download(common::Client& client, FileBufferPtr buffer, NodeHandle handle, const std::optional<common::NodeKeyData>& keyData) -> common::PartialDownloadPtr; // Queue a callback for execution when this range has downloaded. void queue(FileFetchCallback callback); // Queue a file read request for later completion. void queue(FileReadRequest request); }; // FileRangeContext } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context_forward.h�������������������������0000664�0000000�0000000�00000000156�15162662266�0027003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class FileRangeContext; } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context_manager.h�������������������������0000664�0000000�0000000�00000003151�15162662266�0026747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer_forward.h> #include <mega/file_service/file_range_context_manager_forward.h> #include <mega/file_service/file_range_context_pointer_map.h> #include <mega/file_service/file_range_forward.h> #include <mega/file_service/file_read_request_forward.h> #include <mega/file_service/file_result_forward.h> #include <mega/file_service/file_service_options_forward.h> #include <functional> #include <mutex> namespace mega { namespace file_service { class FileRangeContextManager { protected: FileRangeContextManager() = default; ~FileRangeContextManager() = default; public: // Called when a file range has been downloaded. virtual void completed(Buffer& buffer, FileRangeContextPtrMap::Iterator iterator, FileRange range) = 0; // Called when a file read request has been completed. virtual void completed(BufferPtr buffer, FileReadRequest&& request) = 0; // Called to execute an arbitrary function on the manager's thread pool. virtual void execute(std::function<void()> function) = 0; // Called when a file read request has failed. virtual void failed(FileReadRequest&& request, FileResult result) = 0; // Acquire a lock on this manager. virtual std::unique_lock<std::recursive_mutex> lock() const = 0; // Return a reference to the mutex protecting this manager. virtual std::recursive_mutex& mutex() const = 0; // Retrieve a copy of the service's current options. virtual FileServiceOptions options() const = 0; }; // FileRangeContextManager } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context_manager_forward.h�����������������0000664�0000000�0000000�00000000165�15162662266�0030475�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class FileRangeContextManager; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context_pointer.h�������������������������0000664�0000000�0000000�00000000343�15162662266�0027015�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_range_context_forward.h> #include <memory> namespace mega { namespace file_service { using FileRangeContextPtr = std::shared_ptr<FileRangeContext>; } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_context_pointer_map.h���������������������0000664�0000000�0000000�00000000401�15162662266�0027645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_range_context_pointer.h> #include <mega/file_service/file_range_map.h> namespace mega { namespace file_service { using FileRangeContextPtrMap = FileRangeMap<FileRangeContextPtr>; } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_map.h�������������������������������������0000664�0000000�0000000�00000000454�15162662266�0024351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_range_tree.h> #include <mega/file_service/type_traits.h> #include <utility> namespace mega { namespace file_service { template<typename ValueType> using FileRangeMap = FileRangeTree<SelectFirst, std::pair<const FileRange, ValueType>>; } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_set.h�������������������������������������0000664�0000000�0000000�00000000342�15162662266�0024363�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_range_tree.h> #include <mega/file_service/type_traits.h> namespace mega { namespace file_service { using FileRangeSet = FileRangeTree<Identity, const FileRange>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_traits.h����������������������������������0000664�0000000�0000000�00000000517�15162662266�0025102�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_range_forward.h> #include <type_traits> namespace mega { namespace file_service { template<typename Type> using IsFileRange = std::is_same<std::remove_cv_t<Type>, FileRange>; template<typename Type> constexpr auto IsFileRangeV = IsFileRange<Type>::value; } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_tree.h������������������������������������0000664�0000000�0000000�00000036615�15162662266�0024543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_range_tree_traits.h> #include <mega/file_service/type_traits.h> #include <cassert> #include <memory> #include <type_traits> #include <utility> namespace mega { namespace file_service { template<typename KeyFunctionType, typename ValueType, auto Comparable = IsEqualityComparableV<ValueType>, auto Copyable = std::is_copy_constructible_v<ValueType>> class FileRangeTree { // Convenience. using ByRangeBeginTree = AVLTree<detail::IndexByRangeBegin<KeyFunctionType, ValueType>>; using ByRangeEndTree = AVLTree<detail::IndexByRangeEnd<KeyFunctionType, ValueType>>; template<typename IteratorType> class IteratorAdapter: public IteratorType { public: // Minimal STL support; using pointer = ValueType*; using reference = ValueType&; using value_type = ValueType; // Inherit constructors from IteratorType. using IteratorType::IteratorType; // Allow construction from an IteratorType instance. IteratorAdapter(const IteratorType& iterator): IteratorType(iterator) {} // Return a reference to our value, preserving constness from the underlying iterator. decltype(auto) operator*() const { return IteratorType::operator*().mValue; } // Return a pointer to our value, not our node. auto operator->() const -> decltype(std::addressof(IteratorType::operator*().mValue)) { return std::addressof(IteratorType::operator*().mValue); } IteratorAdapter& operator++() { IteratorType::operator++(); return *this; } IteratorAdapter operator++(int) { return static_cast<IteratorType&>(*this)++; } IteratorAdapter& operator--() { IteratorType::operator--(); return *this; } IteratorAdapter operator--(int) { return static_cast<IteratorType&>(*this)--; } }; // IteratorBase using NodeType = typename ByRangeBeginTree::NodeType; // Construct a new node. template<typename Parameter, typename... Parameters> auto construct(const FileRange& range, Parameter&& argument, Parameters&&... arguments) { return std::make_unique<NodeType>( std::piecewise_construct, std::forward_as_tuple(range), std::forward_as_tuple(std::forward<Parameter>(argument), std::forward<Parameters>(arguments)...)); } template<typename Parameter, typename... Parameters> auto construct(std::piecewise_construct_t, Parameter&& argument, Parameters&&... arguments) { return std::make_unique<NodeType>(std::piecewise_construct, std::forward<Parameter>(argument), std::forward<Parameters>(arguments)...); } template<typename Parameter, typename... Parameters> auto construct(Parameter&& argument, Parameters&&... arguments) { return std::make_unique<NodeType>(std::forward<Parameter>(argument), std::forward<Parameters>(arguments)...); } // Indexes nodes by the end of their range. ByRangeEndTree mByRangeEnd; protected: // Indexes nodes by the beginning of their range. ByRangeBeginTree mByRangeBegin; public: using ConstIterator = IteratorAdapter<typename ByRangeBeginTree::ConstIterator>; using ConstReverseIterator = IteratorAdapter<typename ByRangeBeginTree::ConstReverseIterator>; using Iterator = IteratorAdapter<typename ByRangeBeginTree::Iterator>; using ReverseIterator = IteratorAdapter<typename ByRangeBeginTree::ReverseIterator>; FileRangeTree() = default; FileRangeTree(FileRangeTree&& other) = default; ~FileRangeTree() { // Remove all the ranges from the tree. clear(); } FileRangeTree& operator=(FileRangeTree&& rhs) { FileRangeTree temp(std::move(rhs)); swap(temp); return *this; } // Add a range into the tree. // // NOTE: This function will always allocate a new node regardless of // of whether the range described by that node is already present in // some form in the tree. // // If some overlapping range is already present in the tree, the node we // eagerly allocated will be deallocated and an iterator to the first // overlapping range in the tree will be returned. // // If you want to add a range to the tree and you really don't want to // allocate anything unless the addition actually happens, you should // call tryAdd(...) below instead. template<typename Parameter, typename... Parameters> auto add(Parameter&& argument, Parameters&&... arguments) -> std::pair<Iterator, bool> { // Construt a node to represent our range in the tree. auto node = construct(std::forward<Parameter>(argument), std::forward<Parameters>(arguments)...); // Convenience. KeyFunctionType key; // Get a reference to our node's range. auto& range = const_cast<FileRange&>(key(node->mValue)); // Some range ends after our range begins. if (auto iterator = mByRangeEnd.upper_bound(range.mBegin)) { // Convenience. auto& other = key(iterator->mValue); // Other range contains the leading part of our range. if (other.mBegin <= range.mBegin) return std::make_pair<Iterator>(&*iterator, false); // Our range may contain the leading part of the other range. range.mEnd = std::min(range.mEnd, other.mBegin); } // Make sure the range is sane. assert(range.mEnd > range.mBegin); // Add our range to the "by begin" index. auto [iterator, added] = mByRangeBegin.add(*node); // Sanity. assert(added); // Add our range to the "by end" index. added = mByRangeEnd.add(*node).second; // Sanity. assert(added); // The tree now owns our node. node.release(); // Let the caller know their range is now in the tree. return std::make_pair<Iterator>(iterator, true); } // Return an iterator to the first node in the tree. Iterator begin() { return mByRangeBegin.begin(); } ConstIterator begin() const { return mByRangeBegin.begin(); } // Find the first range that begins at or after position. Iterator beginsAfter(std::uint64_t position) { return mByRangeBegin.lower_bound(position); } ConstIterator beginsAfter(std::uint64_t position) const { return const_cast<FileRangeTree&>(*this).beginsAfter(position); } // Return an iterator to the first node in the tree. ConstIterator cbegin() const { return begin(); } // Remove all ranges from the tree. void clear() { // Remove all ranges from the tree. remove(begin(), end()); } // Return an iterator to the end of the tree. ConstIterator cend() const { return end(); } // Return a reverse iterator to the last node in the tree. ConstReverseIterator crbegin() const { return rbegin(); } // Return a reverse iterator to the end of the tree. ConstReverseIterator crend() const { return rend(); } // Does this tree contain any ranges? bool empty() const { return mByRangeBegin.empty(); } // Return an iterator to the end of the tree. Iterator end() { return mByRangeBegin.end(); } ConstIterator end() const { return mByRangeBegin.end(); } // Find the first range that ends at or after position. Iterator endsAfter(std::uint64_t position) { return mByRangeEnd.lower_bound(position).nodePointer(); } auto endsAfter(std::uint64_t position) const { return const_cast<FileRangeTree&>(*this).endsAfter(position); } // Return an iterator to the last range in the tree. Iterator last() { return rbegin(); } ConstIterator last() const { return crbegin(); } // Find all ranges that overlap range. auto find(const FileRange& range) -> std::pair<Iterator, Iterator> { // Are there any ranges that end after we begin? auto i = mByRangeEnd.upper_bound(range.mBegin); // No ranges end after we begin. if (!i) return {}; // Range begins after we end. if (KeyFunctionType()(i->mValue).mBegin >= range.mEnd) return {}; // Are there any ranges that begin after (or when) we end? auto j = mByRangeBegin.lower_bound(range.mEnd); // Return range of overlapping ranges to our caller. return std::make_pair<Iterator>(&*i, j); } auto find(const FileRange& range) const -> std::pair<ConstIterator, ConstIterator> { return const_cast<FileRangeTree&>(*this).find(range); } // Return a reverse iterator to the last node in the tree. ReverseIterator rbegin() { return mByRangeBegin.rbegin(); } ConstReverseIterator rbegin() const { return mByRangeBegin.rbegin(); } // Remove all ranges contained in the specified range. Iterator remove(const FileRange& range) { // Find the first range, if any, contained by range. auto begin = mByRangeBegin.lower_bound(range.mBegin); // No range begins after the specified range. if (!begin) return {}; // Begin isn't contained within the specified range. if (KeyFunctionType()(begin->mValue).mEnd > range.mEnd) return {}; // Find the first range outside of range. auto end = mByRangeBegin.lower_bound(range.mEnd); // And remove them from the tree. return remove(begin, end); } // Remove all ranges between two iterators from the tree. Iterator remove(Iterator begin, Iterator end) { while (begin != end) begin = remove(begin); return begin; } // Remove a specific range from the tree. Iterator remove(Iterator iterator) { // Sanity. assert(iterator); // Remove the node from our "by begin" index. auto* node = mByRangeBegin.remove(iterator++); // Remove the node from our "by end" index. mByRangeEnd.remove(node); // Destroy the node. delete node; // Return an iterator to the next range in the tree. return iterator; } // Return a reverse iterator to the end of the tree. ReverseIterator rend() { return mByRangeBegin.rend(); } ConstReverseIterator rend() const { return mByRangeBegin.rend(); } // Get an iterator to the tree's root node. Iterator root() { return mByRangeBegin.root(); } ConstIterator root() const { return mByRangeBegin.root(); } // How many ranges does this tree contain? std::size_t size() const { return mByRangeBegin.size(); } // Swap the contents of this tree with another. void swap(FileRangeTree& other) { using std::swap; swap(mByRangeBegin, other.mByRangeBegin); swap(mByRangeEnd, other.mByRangeEnd); } // Try and add a new range to the tree. // // tryAdd(...) is a more restrictive and performant version of add(...). // // Unlike add(...), it'll allocate a new node if and only if no other // ranges in the tree overlap the range provided. // // If some ranges overlap the range provided by the caller, this // function will return an iterator to the first such range. // // If no ranges overlap the range provided by the caller, a new node // will be created based on that range and the specified arguments. template<typename... Parameters> auto tryAdd(FileRange range, Parameters&&... arguments) -> std::pair<Iterator, bool> { // Some range ends after our range begins. if (auto iterator = mByRangeEnd.upper_bound(range.mBegin)) { // Convenience. auto tempFunctor = KeyFunctionType(); auto& other = tempFunctor(iterator->mValue); // The other range contains the leading part of our range. if (other.mBegin <= range.mBegin) return std::make_pair<Iterator>(&*iterator, false); // Our range may contain the leading part of the other range. range.mEnd = std::min(range.mEnd, other.mBegin); } // Make sure the range is sane. assert(range.mEnd > range.mBegin); // Construct a node to represent our range in the tree. auto node = construct(range, std::forward<Parameters>(arguments)...); // Add our node to the tree's "by begin" index. auto [iterator, added] = mByRangeBegin.add(*node); // Sanity. assert(added); // Add our node to the tree's "by end" index. added = mByRangeEnd.add(*node).second; // Sanity. assert(added); // The tree now owns the node. node.release(); // Let the caller know their range has been added to the tree. return std::make_pair<Iterator>(iterator, added); } }; // FileRangeTree<KeyFunctionType, ValueType, Comparable, Copyable> template<typename KeyFunctionType, typename ValueType> class FileRangeTree<KeyFunctionType, ValueType, true, false>: public FileRangeTree<KeyFunctionType, ValueType, false, false> { // Convenience. using BaseType = FileRangeTree<KeyFunctionType, ValueType, false, false>; public: // Inherit constructors. using BaseType::BaseType; bool operator==(const FileRangeTree& rhs) const { return this->mByRangeBegin == rhs.mByRangeBegin; } bool operator!=(const FileRangeTree& rhs) const { return !(*this == rhs); } }; // FileRangeTree<KeyFunctionType, ValueType, true, false> template<typename KeyFunctionType, typename ValueType, auto Comparable> class FileRangeTree<KeyFunctionType, ValueType, Comparable, true>: public FileRangeTree<KeyFunctionType, ValueType, Comparable, false> { // Convenience. using BaseType = FileRangeTree<KeyFunctionType, ValueType, Comparable, false>; public: // Inherit constructors from base File Range tree. using BaseType::BaseType; // Allow copy construction. FileRangeTree(const FileRangeTree& other): BaseType() { for (auto i = other.begin(); i != other.end(); ++i) this->add(*i); } FileRangeTree(FileRangeTree&& other) = default; // And copy assignment. FileRangeTree& operator=(const FileRangeTree& rhs) { FileRangeTree temp(rhs); this->swap(temp); return *this; } FileRangeTree& operator=(FileRangeTree&& rhs) = default; }; // FileRangeTree<KeyFunctionType, ValueType, Comparable, true> // Swap the contents of lhs with rhs. template<typename KeyFunctionType, typename ValueType, auto Comparable, auto Copyable> void swap(FileRangeTree<KeyFunctionType, ValueType, Comparable, Copyable>& lhs, FileRangeTree<KeyFunctionType, ValueType, Comparable, Copyable>& rhs) { lhs.swap(rhs); } } // file_service } // mega �������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_tree_node.h�������������������������������0000664�0000000�0000000�00000002044�15162662266�0025535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree_node.h> namespace mega { namespace file_service { namespace detail { template<typename ValueType> struct FileRangeTreeNode { FileRangeTreeNode() = default; // Allows us to construct mValue any way we please. template<typename T, typename... Ts> FileRangeTreeNode(T&& first, Ts&&... rest): mByRangeBegin(), mByRangeEnd(), mValue(std::forward<T>(first), std::forward<Ts>(rest)...) {} // Nodes are never copied or moved. FileRangeTreeNode(const FileRangeTreeNode& other) = delete; FileRangeTreeNode& operator=(const FileRangeTreeNode& rhs) = delete; // Convenience. using LinkType = AVLTreeNode<FileRangeTreeNode<ValueType>>; // So we can index this node by the beginning of its range. LinkType mByRangeBegin; // So we can index this node by the end of its range. LinkType mByRangeEnd; // The value carried by this node. ValueType mValue; }; // FileRangeTreeNode<ValueType> } // detail } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_range_tree_traits.h�����������������������������0000664�0000000�0000000�00000004471�15162662266�0026124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree.h> #include <mega/file_service/file_range_forward.h> #include <mega/file_service/file_range_traits.h> #include <mega/file_service/file_range_tree_node.h> #include <mega/file_service/file_range_tree_traits.h> #include <functional> namespace mega { namespace file_service { namespace detail { template<typename KeyFunction, typename ValueType> using IsValidKeyFunction = std::is_invocable_r<const FileRange&, KeyFunction, const ValueType&>; template<typename KeyFunction, typename ValueType> constexpr auto IsValidKeyFunctionV = IsValidKeyFunction<KeyFunction, ValueType>::value; template<typename ValueType> struct IsValidValueType: IsFileRange<ValueType> {}; // IsValidValueType<ValueType> template<typename FirstType, typename SecondType> struct IsValidValueType<std::pair<FirstType, SecondType>>: IsFileRange<FirstType> {}; // IsValidValueType<std::pair<FirstType, SecondType>> template<typename ValueType> constexpr auto IsValidValueTypeV = IsValidValueType<ValueType>::value; template<typename KeyFunctionType, typename ValueType> struct IndexByRangeBegin { // Sanity. static_assert(IsValidValueTypeV<ValueType>); static_assert(IsValidKeyFunctionV<KeyFunctionType, ValueType>); struct KeyFunction { auto& operator()(const ValueType& value) const { return KeyFunctionType()(value).mBegin; } }; // KeyFunction // Convenenience. using NodeType = FileRangeTreeNode<ValueType>; static constexpr auto mLinkPointer = &NodeType::mByRangeBegin; static constexpr auto mValuePointer = &NodeType::mValue; }; // IndexByRangeBegin<KeyFunction, ValueType> template<typename KeyFunctionType, typename ValueType> struct IndexByRangeEnd { // Sanity. static_assert(IsValidValueTypeV<ValueType>); static_assert(IsValidKeyFunctionV<KeyFunctionType, ValueType>); struct KeyFunction { auto& operator()(const ValueType& value) const { return KeyFunctionType()(value).mEnd; } }; // KeyFunction // Convenenience. using NodeType = FileRangeTreeNode<ValueType>; static constexpr auto mLinkPointer = &NodeType::mByRangeEnd; static constexpr auto mValuePointer = &NodeType::mValue; }; // IndexByRangeEnd<KeyFunction, ValueType> } // detail } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_read_request.h����������������������������������0000664�0000000�0000000�00000001221�15162662266�0025074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_read_request_forward.h> #include <mega/file_service/file_request_tags.h> namespace mega { namespace file_service { struct FileReadRequest { // What kind of request is this? using Type = FileReadRequestTag; // This request's human readable name. static const char* name() { return "read"; } // The callback the user wants us to call. FileReadCallback mCallback; // The content the user wants to read. FileRange mRange; }; // FileReadRequest } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_read_request_forward.h��������������������������0000664�0000000�0000000�00000000156�15162662266�0026626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileReadRequest; } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_read_request_set.h������������������������������0000664�0000000�0000000�00000000773�15162662266�0025762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_read_request_forward.h> #include <cstdint> #include <set> namespace mega { namespace file_service { struct FileReadRequestLess { using is_transparent = void; bool operator()(const FileReadRequest& lhs, const FileReadRequest& rhs) const; bool operator()(std::uint64_t lhs, const FileReadRequest& rhs) const; }; // FileReadRequestLess using FileReadRequestSet = std::multiset<FileReadRequest, FileReadRequestLess>; } // file_service } // mega �����sdk-10.11.0/src/file_service/mega/file_service/file_read_write_state.h������������������������������0000664�0000000�0000000�00000001625�15162662266�0025746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mutex> namespace mega { namespace file_service { class FileReadWriteState { // Serializes access to mState. std::mutex mLock; // Tracks whether we have any active read or write operations. // // Value is interpreted as follows: // <0 - One or more read operations are in progress. // 0 - No operations are in progress. // =1 - A write operation is in progress. // >1 - Invalid state. long mState{0}; public: // Try and begin a read operation. // // Returns false if the read must be queued. bool read(); // Signal that a read operation has completed. void readCompleted(); // Try and begin a write operation. // // Returns false if the write must be queued. bool write(); // Signal that a write operation has completed. void writeCompleted(); }; // FileReadWriteState } // file_service } // mega �����������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_reclaim_request.h�������������������������������0000664�0000000�0000000�00000001300�15162662266�0025573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_reclaim_request_forward.h> #include <mega/file_service/file_request_tags.h> #include <cstdint> namespace mega { namespace file_service { struct FileReclaimRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "reclaim"; } // How much space was the file taking when this request was queued? std::uint64_t mAllocatedSize; // Who should we call when this request has completed? FileReclaimCallback mCallback; }; // FileReclaimRequest } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_reclaim_request_forward.h�����������������������0000664�0000000�0000000�00000000161�15162662266�0027323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileReclaimRequest; } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_remove_request.h��������������������������������0000664�0000000�0000000�00000001331�15162662266�0025460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_remove_request_forward.h> #include <mega/file_service/file_request_tags.h> namespace mega { namespace file_service { struct FileRemoveRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "remove"; } // Who should we call when the file's been removed? FileRemoveCallback mCallback; // Is this file being removed because it was replaced? bool mReplaced; // Should we only remove the file from the service? bool mServiceOnly; }; // FileRemoveRequest } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_remove_request_forward.h������������������������0000664�0000000�0000000�00000000160�15162662266�0027203�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileRemoveRequest; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_request.h���������������������������������������0000664�0000000�0000000�00000002115�15162662266�0024104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_append_request_forward.h> #include <mega/file_service/file_fetch_request_forward.h> #include <mega/file_service/file_flush_request_forward.h> #include <mega/file_service/file_read_request_forward.h> #include <mega/file_service/file_reclaim_request_forward.h> #include <mega/file_service/file_remove_request_forward.h> #include <mega/file_service/file_touch_request_forward.h> #include <mega/file_service/file_truncate_request_forward.h> #include <mega/file_service/file_write_request_forward.h> #include <variant> namespace mega { namespace file_service { using FileRequest = std::variant<FileAppendRequest, FileFetchRequest, FileFlushRequest, FileReadRequest, FileReclaimRequest, FileRemoveRequest, FileTouchRequest, FileTruncateRequest, FileWriteRequest>; } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_request_list.h����������������������������������0000664�0000000�0000000�00000000304�15162662266�0025135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_request.h> #include <list> namespace mega { namespace file_service { using FileRequestList = std::list<FileRequest>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_request_tags.h����������������������������������0000664�0000000�0000000�00000000301�15162662266�0025115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileReadRequestTag {}; // FileReadRequestTag struct FileWriteRequestTag {}; // FileWriteRequestTag } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_request_traits.h��������������������������������0000664�0000000�0000000�00000003537�15162662266�0025503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_request.h> #include <mega/file_service/file_request_tags.h> #include <mega/file_service/type_traits.h> #include <type_traits> namespace mega { namespace file_service { namespace detail { template<typename T> using DetectTypeType = typename T::Type; template<typename T, typename U> using HasTypeOf = std::is_same<DetectedT<DetectTypeType, T>, U>; template<typename T> using IsFileFlushRequest = std::is_base_of<FileFlushRequest, T>; template<typename T> constexpr auto IsFileFlushRequestV = IsFileFlushRequest<T>::value; template<typename T> using IsFileReadRequest = HasTypeOf<T, FileReadRequestTag>; template<typename T> constexpr auto IsFileReadRequestV = IsFileReadRequest<T>::value; template<typename T> using IsFileReclaimRequest = std::is_same<FileReclaimRequest, T>; template<typename T> constexpr auto IsFileReclaimRequestV = IsFileReclaimRequest<T>::value; template<typename T> using IsFileRemoveRequest = std::is_same<FileRemoveRequest, T>; template<typename T> constexpr auto IsFileRemoveRequestV = IsFileRemoveRequest<T>::value; template<typename T> using IsFileRequest = std::is_constructible<FileRequest, T>; template<typename T> constexpr auto IsFileRequestV = IsFileRequest<T>::value; template<typename T> using IsFileWriteRequest = HasTypeOf<T, FileWriteRequestTag>; template<typename T> constexpr auto IsFileWriteRequestV = IsFileWriteRequest<T>::value; } // detail using detail::IsFileFlushRequest; using detail::IsFileFlushRequestV; using detail::IsFileReadRequest; using detail::IsFileReadRequestV; using detail::IsFileReclaimRequest; using detail::IsFileReclaimRequestV; using detail::IsFileRemoveRequest; using detail::IsFileRemoveRequestV; using detail::IsFileRequest; using detail::IsFileRequestV; using detail::IsFileWriteRequest; using detail::IsFileWriteRequestV; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_service_context.h�������������������������������0000664�0000000�0000000�00000017401�15162662266�0025624�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/client_forward.h> #include <mega/common/database.h> #include <mega/common/instance_logger.h> #include <mega/common/node_event_observer.h> #include <mega/common/node_key_data.h> #include <mega/common/shared_mutex.h> #include <mega/common/task_executor.h> #include <mega/common/task_queue.h> #include <mega/file_service/file_context_badge_forward.h> #include <mega/file_service/file_context_pointer.h> #include <mega/file_service/file_event_emitter.h> #include <mega/file_service/file_forward.h> #include <mega/file_service/file_id_forward.h> #include <mega/file_service/file_id_vector.h> #include <mega/file_service/file_info_context_badge_forward.h> #include <mega/file_service/file_info_context_pointer.h> #include <mega/file_service/file_info_forward.h> #include <mega/file_service/file_range_vector.h> #include <mega/file_service/file_service_callbacks.h> #include <mega/file_service/file_service_context_forward.h> #include <mega/file_service/file_service_options.h> #include <mega/file_service/file_service_queries.h> #include <mega/file_service/file_service_result_or_forward.h> #include <mega/file_service/file_storage.h> #include <mega/file_service/from_file_id_map.h> #include <chrono> #include <condition_variable> #include <memory> #include <optional> #include <vector> namespace mega { class LocalPath; namespace file_service { class FileServiceContext: common::NodeEventObserver, public FileEventEmitter { // Processes client node events. class EventProcessor; // Returned from fileContextFrom(Cloud|Database|Index). using FileContextResult = FileServiceResultOr<FileContextPtr>; // Tracks state necessary for reclaim. class ReclaimContext; // Convenience. using ReclaimContextPtr = std::shared_ptr<ReclaimContext>; template<typename Lock> FileID allocateID(Lock&& lock, common::Transaction& transaction); template<typename Lock> void deallocateID(FileID id, Lock&& lock, common::Transaction& transaction); auto fileContextFromCloud(FileID id) -> FileContextResult; auto fileContextFromDatabase(FileID id) -> FileContextResult; template<typename Lock> auto fileContextFromIndex(FileID id, Lock&& lock) -> FileContextResult; template<typename Lock, typename T> auto getFromIndex(FileID id, Lock&& lock, FromFileIDMap<std::weak_ptr<T>>& map) -> std::shared_ptr<T>; auto infoContextFromDatabase(FileID id) -> FileInfoContextPtr; template<typename Lock> auto infoContextFromIndex(FileID id, Lock&& lock) -> FileInfoContextPtr; auto infoContext(FileID id) -> FileServiceResultOr<FileInfoContextPtr>; template<typename Transaction> auto keyData(FileID id, Transaction&& transaction) -> std::optional<common::NodeKeyData>; template<typename Transaction> auto ranges(FileID id, Transaction&& transaction) -> FileRangeVector; void reclaimTaskCallback(common::Activity& activity, std::chrono::steady_clock::time_point when, const common::Task& task); auto reclaimable() -> FileServiceResultOr<FileIDVector>; template<typename ContextLock, typename DatabaseLock> void remove(ContextLock&& contextLock, DatabaseLock&& databaseLock, FileID id, common::Transaction& transaction); template<typename Lock> void removeFromDatabase(FileID id, Lock&& lock, common::Transaction& transaction); template<typename Lock, typename T> bool removeFromIndex(FileID id, Lock&& lock, FromFileIDMap<T>& map); template<typename T> bool removeFromIndex(FileID id, FromFileIDMap<T>& map); void purgeRemovedFiles(); template<typename Lock, typename Transaction> auto storageUsed(Lock&& lock, Transaction&& transaction) -> std::uint64_t; void updated(common::NodeEventQueue& events) override; // Logs instance lifetime. common::InstanceLogger<FileServiceContext> mInstanceLogger; common::Client& mClient; // No locks are needed in order to make use of this member. // // As far as invariants are concerned, the member is sane as soon as it // completes its initialization. // // As for different threads making concurrent calls, that should also be // safe although we will be relying on the operating system itself to // synchronize calls to the filesystem. FileStorage mStorage; common::Database mDatabase; FileServiceQueries mQueries; FromFileIDMap<FileContextWeakPtr> mFileContexts; std::condition_variable_any mInfoContextRemoved; FromFileIDMap<FileInfoContextWeakPtr> mInfoContexts; // This lock serializes access to the context's members. // // Note that if we want to run some query on the database, we must // explicitly lock mDatabase, too. common::SharedMutex mLock; // Specifies various metrics that control how the service behaves. FileServiceOptions mOptions; // Serializes access to mOptions. common::SharedMutex mOptionsLock; // Tracks any reclaim in progress. ReclaimContextPtr mReclaimContext; // Serializes access to mReclaimContext. std::mutex mReclaimContextLock; // Tracks any scheduled reclamation. common::Task mReclaimTask; // Serializes access to mReclaimTask. std::recursive_mutex mReclaimTaskLock; // This member will ensure the context isn't destroyed until any related // activities have been completed. // // Since each File(Info)?Context is passed an activity when they are // instantiated, this means that this member's destructor will wait // until all File(Info)?Contexts that refer to this context have been // destroyed before allowing this context itself to be destroyed. common::ActivityMonitor mActivities; // Lets us execute tasks on a thread pool. common::TaskExecutor mExecutor; public: FileServiceContext(common::Client& client, const FileServiceOptions& options); ~FileServiceContext(); // Add a foreign file to the service. auto add(NodeHandle handle, const common::NodeKeyData& keyData, std::size_t size) -> FileServiceResultOr<FileID>; // Retrieve a reference to this service's client. common::Client& client(); // Create a new file. auto create(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File>; // Retrieve a reference to this service's database. common::Database& database(); // Execute a task on this service's thread pool. auto execute(std::function<void(const common::Task&)> function) -> common::Task; // Retrieve information about a file managed by this service. auto info(FileID id) -> FileServiceResultOr<FileInfo>; // Open a file for reading or writing. auto open(NodeHandle parent, const std::string& name) -> FileServiceResultOr<File>; auto open(FileID id) -> FileServiceResultOr<File>; // Update the file service's options. void options(const FileServiceOptions& options); // Retrieve the file service's current options. FileServiceOptions options(); // Find out where the service is storing the specified file. LocalPath path(FileID id) const; // Return a reference to this service's queries. FileServiceQueries& queries(); // Purge all files from storage. auto purge() -> FileServiceResult; // Reclaim storage space. void reclaim(ReclaimCallback callback); // Remove a file context from our index. void removeFromIndex(FileContextBadge badge, FileID id); // Remove a file info context from our index. void removeFromIndex(FileInfoContextBadge badge, FileInfoContext& context); // How much storage space is the service using? auto storageUsed() -> FileServiceResultOr<std::uint64_t>; }; // FileServiceContext } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_service_queries.h�������������������������������0000664�0000000�0000000�00000002242�15162662266�0025612�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/database_forward.h> #include <mega/common/query.h> namespace mega { namespace file_service { struct FileServiceQueries { explicit FileServiceQueries(common::Database& database); common::Query mAddFile; common::Query mAddFileID; common::Query mAddFileKeyData; common::Query mAddFileRange; common::Query mGetFile; common::Query mGetFileByNameAndParentHandle; common::Query mGetFileIDs; common::Query mGetFileIDsByParentHandle; common::Query mGetFileKeyData; common::Query mGetFileRanges; common::Query mGetFreeFileID; common::Query mGetNextFileID; common::Query mGetReclaimableFiles; common::Query mGetStorageUsed; common::Query mRemoveFile; common::Query mRemoveFileID; common::Query mRemoveFileIDs; common::Query mRemoveFileRanges; common::Query mRemoveFiles; common::Query mSetFileAccessTime; common::Query mSetFileHandle; common::Query mSetFileLocation; common::Query mSetFileModificationTime; common::Query mSetFileRemoved; common::Query mSetFileSize; common::Query mSetNextFileID; }; // FileServiceQueries } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_size_info.h�������������������������������������0000664�0000000�0000000�00000001456�15162662266�0024410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_size_info_forward.h> #include <cstdint> namespace mega { namespace file_service { class FileSizeInfo { protected: FileSizeInfo() = default; public: virtual ~FileSizeInfo() = default; // Update this file's allocated size. virtual void allocatedSize(std::uint64_t allocatedSize) = 0; // How much disk space has been allocated to this file? virtual std::uint64_t allocatedSize() const = 0; // Update this file's reported size. virtual void reportedSize(std::uint64_t reportedSize) = 0; // How large does the filesystem say this file is? virtual std::uint64_t reportedSize() const = 0; // How large is this file conceptually? virtual std::uint64_t size() const = 0; }; // FileSizeInfo } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_size_info_forward.h�����������������������������0000664�0000000�0000000�00000000152�15162662266�0026124�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { class FileSizeInfo; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_storage.h���������������������������������������0000664�0000000�0000000�00000003634�15162662266�0024067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/client_forward.h> #include <mega/common/directory.h> #include <mega/common/node_info_forward.h> #include <mega/common/platform/folder_locker.h> #include <mega/types.h> #include <optional> namespace mega { namespace file_service { class FileStorage { // This function exists solely to reduce duplication. // // It is called by both addFile(...) and getFile(...). // // mustCreate specifies whether we need to create a new file for id. // // It's an error if mustCreate is true and a file for id already exists. // It's an error if mustCreate is false but no file for id exists. FileAccessPtr openFile(const LocalPath& path, bool mustCreate); // How we interact with the host filesystem. FileSystemAccessPtr mFilesystem; // Where the service is storing its metadata. common::Directory mStorageDirectory; // Where the service is storing this user's metadata. common::Directory mUserStorageDirectory; // Where the service is storing this user's cached files common::Directory mUserCacheDirectory; // On Windows, prevent others, especially file explorer, from opening files under the folder, // generating thumbnail while we're running. We have seen we're blocked to open files forever // due to this. common::platform::FolderLocker mFolderLocker; public: explicit FileStorage(const common::Client& client); ~FileStorage(); // Add a new file to our storage area. FileAccessPtr addFile(FileID id); // Where is the service storing this user's database? LocalPath databasePath() const; // Get a file from our storage area. FileAccessPtr getFile(FileID id); // Remove a file from our storage area. void removeFile(FileID id); // Find out where the service is storing a particular file. LocalPath userFilePath(FileID id) const; }; // FileStorage } // file_service } // mega ����������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_touch_request.h���������������������������������0000664�0000000�0000000�00000001266�15162662266�0025314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_request_tags.h> #include <mega/file_service/file_touch_request_forward.h> #include <cstdint> namespace mega { namespace file_service { struct FileTouchRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "touch"; } // Who should we call when the file's modification time has been updated? FileTouchCallback mCallback; // What should we set the file's modification time to? std::int64_t mModified; }; // FileTouchRequest } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_touch_request_forward.h�������������������������0000664�0000000�0000000�00000000157�15162662266�0027036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileTouchRequest; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_truncate_request.h������������������������������0000664�0000000�0000000�00000001245�15162662266�0026014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_request_tags.h> #include <mega/file_service/file_truncate_request_forward.h> #include <cstdint> namespace mega { namespace file_service { struct FileTruncateRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "truncate"; } // Who should we call when the file's been truncated? FileTruncateCallback mCallback; // What size should the file be truncated to? std::uint64_t mSize; }; // FileTruncateRequest } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_truncate_request_forward.h����������������������0000664�0000000�0000000�00000000162�15162662266�0027535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileTruncateRequest; } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_write_request.h���������������������������������0000664�0000000�0000000�00000001345�15162662266�0025322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_callbacks.h> #include <mega/file_service/file_range.h> #include <mega/file_service/file_request_tags.h> #include <mega/file_service/file_write_request_forward.h> namespace mega { namespace file_service { struct FileWriteRequest { // What kind of request is this? using Type = FileWriteRequestTag; // This request's human readable name. static const char* name() { return "write"; } // The content the user wants to write. const void* mBuffer; // The callback the user wants us to invoke. FileWriteCallback mCallback; // Where the user wants us to write content. FileRange mRange; }; // FileWriteRequest } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/file_write_request_forward.h�������������������������0000664�0000000�0000000�00000000157�15162662266�0027046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace file_service { struct FileWriteRequest; } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/from_file_id_map.h�����������������������������������0000664�0000000�0000000�00000000326�15162662266�0024672�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_id_forward.h> #include <map> namespace mega { namespace file_service { template<typename T> using FromFileIDMap = std::map<FileID, T>; } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/logger.h���������������������������������������������0000664�0000000�0000000�00000000226�15162662266�0022675�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/subsystem_logger.h> namespace mega { namespace file_service { common::SubsystemLogger& logger(); } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/logging.h��������������������������������������������0000664�0000000�0000000�00000001570�15162662266�0023047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/logging.h> #include <mega/file_service/logger.h> // Emit a debug message. #define FSDebug1(format) LogDebug1(::mega::file_service::logger(), (format)) #define FSDebugF(format, ...) LogDebugF(::mega::file_service::logger(), (format), __VA_ARGS__) // Emit an info message. #define FSInfo1(format) LogInfo1(::mega::file_service::logger(), (format)) #define FSInfoF(format, ...) LogInfoF(::mega::file_service::logger(), (format), __VA_ARGS__) // Emit an error message. #define FSError1(format) LogError1(::mega::file_service::logger(), (format)) #define FSErrorF(format, ...) LogErrorF(::mega::file_service::logger(), (format), __VA_ARGS__) // Emit a warning message. #define FSWarning1(format) LogWarning1(::mega::file_service::logger(), (format)) #define FSWarningF(format, ...) LogWarningF(::mega::file_service::logger(), (format), __VA_ARGS__) ����������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/memory_buffer.h��������������������������������������0000664�0000000�0000000�00000001766�15162662266�0024271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/buffer.h> #include <cstdint> #include <memory> namespace mega { namespace file_service { class MemoryBuffer: public Buffer { std::shared_ptr<std::uint8_t[]> mBuffer; std::uint64_t mLength; public: explicit MemoryBuffer(std::uint64_t length); // Copy data from this buffer to another. auto copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Check if this buffer is a file buffer. bool isFileBuffer() const override; // Read data from the buffer. auto read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Write data into the buffer. auto write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> override; }; // MemoryBuffer } // file_service } // mega ����������sdk-10.11.0/src/file_service/mega/file_service/sparse_file_buffer.h���������������������������������0000664�0000000�0000000�00000001445�15162662266�0025247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/file_buffer.h> #include <mega/file_service/file_size_info_forward.h> namespace mega { namespace file_service { class SparseFileBuffer: public FileBuffer { // Describes the file we're accessing. FileSizeInfo& mInfo; public: SparseFileBuffer(FileAccess& file, FileSizeInfo& info); // Read data from the buffer. auto read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> override; // Write data into the buffer. auto write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> override; // Truncate the file's size. bool truncate(std::uint64_t newSize) override; }; // SparseFileBuffer } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/mega/file_service/type_traits.h����������������������������������������0000664�0000000�0000000�00000011433�15162662266�0023767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <type_traits> #include <utility> namespace mega { namespace file_service { enum class NoneSuch {}; // Check if Type is NoneSuch. template<typename Type> using IsNoneSuch = std::is_same<NoneSuch, Type>; template<typename Type> constexpr auto IsNoneSuchV = IsNoneSuch<Type>::value; // Check if Type is not NoneSuch. template<typename Type> using IsNotNoneSuch = std::negation<IsNoneSuch<Type>>; template<typename Type> constexpr auto IsNotNoneSuchV = IsNotNoneSuch<Type>::value; namespace detail { template<typename DefaultType, typename Enabler, template<typename> typename Predicate, typename... Parameters> struct Detected: std::false_type { using Type = DefaultType; }; // Detected<DefaultType, Enabler, Predicate, Parameters...> template<typename DefaultType, template<typename> typename Predicate, typename... Parameters> struct Detected<DefaultType, std::void_t<Predicate<Parameters...>>, Predicate, Parameters...>: std::true_type { using Type = Predicate<Parameters...>; }; // DefaultType<DefaultType, Predicate, Parameters...> template<typename T> using EqualityCompareResultType = decltype(std::declval<T>() == std::declval<T>()); template<typename T, typename = void> struct IsEqualityComparable: std::false_type {}; // IsEqualityComparable<T, void> template<typename T> struct IsEqualityComparable<T, std::void_t<EqualityCompareResultType<T>>>: std::is_convertible<EqualityCompareResultType<T>, bool> {}; // IsEqualityComparable<T, void> template<typename T, typename U> struct IsEqualityComparable<std::pair<T, U>, void>: std::conjunction<IsEqualityComparable<T>, IsEqualityComparable<U>> {}; // IsEqualityComparable<std::pair<T, U>, void> template<typename T> const auto IsEqualityComparableV = IsEqualityComparable<T>::value; } // detail template<template<typename> typename Predicate, typename... Parameters> using Detected = detail::Detected<NoneSuch, void, Predicate, Parameters...>; template<template<typename> typename Predicate, typename... Parameters> using DetectedT = typename Detected<Predicate, Parameters...>::Type; template<template<typename> typename Predicate, typename... Parameters> constexpr auto DetectedV = Detected<Predicate, Parameters...>::value; template<typename DefaultType, template<typename> typename Predicate, typename... Parameters> using DetectedOr = detail::Detected<DefaultType, void, Predicate, Parameters...>; template<typename DefaultType, template<typename> typename Predicate, typename... Parameters> using DetectedOrT = typename DetectedOr<DefaultType, Predicate, Parameters...>::Type; template<typename DefaultType, template<typename> typename Predicate, typename... Parameters> constexpr auto DetectedOrV = DetectedOr<DefaultType, Predicate, Parameters...>::value; // Return value as-is. struct Identity { template<typename T> auto&& operator()(T&& value) const { return std::forward<T>(value); } }; // Identity using detail::IsEqualityComparable; using detail::IsEqualityComparableV; template<typename Class0, typename Class1, typename... Classes> struct MostSpecificClass: MostSpecificClass<typename MostSpecificClass<Class0, Class1>::Type, Classes...> {}; // MostSpecificClass<Class0, Class1, Classes...> template<typename Class, typename... Classes> struct MostSpecificClass<NoneSuch, Class, Classes...> { using Type = NoneSuch; }; // MostSpecificClass<NoneSuch, Class, Classes...> template<typename Class0, typename Class1> struct MostSpecificClass<Class0, Class1> { using Type = std::conditional_t<std::is_base_of_v<Class0, Class1>, Class1, std::conditional_t<std::is_base_of_v<Class1, Class0>, Class0, NoneSuch>>; }; // MostSpecificClass template<typename Class0, typename Class1, typename... Classes> using MostSpecificClassT = typename MostSpecificClass<Class0, Class1, Classes...>::Type; template<typename Type> struct MemberPointerTraits: std::false_type {}; // MemberPointerTraits<Type> template<typename Class, typename Member> struct MemberPointerTraits<Member Class::*>: std::true_type { using ClassType = Class; using MemberType = Member; }; // MemberPointerTraits<Class Member::*> template<typename Class, typename Member> struct MemberPointerTraits<Member Class::* const>: std::true_type { using ClassType = Class; using MemberType = Member; }; // MemberPointerTraits<Class Member::* const> template<typename Type> using RemoveCVRef = std::remove_cv<std::remove_reference_t<Type>>; template<typename Type> using RemoveCVRefT = typename RemoveCVRef<Type>::type; // Return first value from a pair. struct SelectFirst { template<typename T> auto&& operator()(T&& value) const { return std::get<0>(std::forward<T>(value)); } }; // SelectFirst } // file_service } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/memory_buffer.cpp������������������������������������������������������0000664�0000000�0000000�00000005145�15162662266�0021247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/memory_buffer.h> #include <algorithm> #include <cassert> #include <cstring> namespace mega { namespace file_service { MemoryBuffer::MemoryBuffer(std::uint64_t length): Buffer(), mBuffer(new std::uint8_t[static_cast<std::size_t>(length)]), mLength(length) {} auto MemoryBuffer::copy(Buffer& target, std::uint64_t sourceOffset, std::uint64_t targetOffset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { assert(this != &target); // Can't copy to the same buffer. if (this == &target) return std::make_pair(0u, false); // Clamp length as necessary. length = std::min(length, std::max(mLength, sourceOffset) - sourceOffset); // Caller doesn't actually want to transfer any data. if (!length) return std::make_pair(0u, true); // Try and transfer our data to the target. return target.write(mBuffer.get() + sourceOffset, targetOffset, length); } bool MemoryBuffer::isFileBuffer() const { return false; } auto MemoryBuffer::read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { assert(buffer); // Caller gave us a bad buffer. if (!buffer) return std::make_pair(0u, false); // Clamp length. length = std::min(length, std::max(mLength, offset) - offset); // Caller doesn't actually want to read anything. if (!length) return std::make_pair(0u, true); // Convenenience. auto* destination = static_cast<std::uint8_t*>(buffer); auto* source = mBuffer.get() + offset; // Copy data into the caller's buffer. std::copy(source, source + length, destination); // Let the user know how much data was read. return std::make_pair(length, true); } auto MemoryBuffer::write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { assert(buffer); // Caller gave us a bad buffer. if (!buffer) return std::make_pair(0u, false); // Clamp length as necessary. length = std::min(length, std::max(mLength, offset) - offset); // Caller doesn't actually want to write anything. if (!length) return std::make_pair(0u, true); // Convenience. auto* destination = mBuffer.get() + offset; auto* source = static_cast<const std::uint8_t*>(buffer); // Copy data into our buffer. std::copy(source, source + length, destination); // Let the user know how many bytes were written. return std::make_pair(length, true); } } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/file_service/sparse_file_buffer.cpp�������������������������������������������������0000664�0000000�0000000�00000006706�15162662266�0022237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_size_info.h> #include <mega/file_service/sparse_file_buffer.h> #include <mega/filesystem.h> #include <algorithm> #include <cassert> #include <tuple> namespace mega { namespace file_service { SparseFileBuffer::SparseFileBuffer(FileAccess& file, FileSizeInfo& info): FileBuffer(file), mInfo(info) {} auto SparseFileBuffer::read(void* buffer, std::uint64_t offset, std::uint64_t length) const -> std::pair<std::uint64_t, bool> { auto reportedSize = mInfo.reportedSize(); auto size = mInfo.size(); // Clamp the caller's length. length = std::min(length, std::max(offset, size) - offset); // Caller doesn't actually need to read any data. if (!length) return std::make_pair(length, true); // How much data can we actually read from disk? auto count = std::min(length, std::max(offset, reportedSize) - offset); // So we can tie. auto success = false; // Try and populate the caller's buffer. std::tie(count, success) = FileBuffer::read(buffer, offset, count); // Retrieve the file's current file sizes. auto sizes = mFile.getFileSize(); // If getFileSize(...) failed so should've read. assert(sizes || (!count && !success)); // Couldn't populate the caller's buffer. if (!sizes || !success) return std::make_pair(count, success); std::uint64_t allocatedSize; // Clarity. std::tie(allocatedSize, reportedSize) = *sizes; // Update the file's allocated and reported sizes. mInfo.allocatedSize(allocatedSize); mInfo.reportedSize(reportedSize); // Convenience. auto* buffer_ = static_cast<std::uint8_t*>(buffer) + count; // How many zeros do we need to write to the caller's buffer? count = length - count; // Zero the remainder of the caller's buffer if necessary. std::fill(buffer_, buffer_ + count, 0); // Let the caller know the read was successful. return std::make_pair(length, true); } auto SparseFileBuffer::write(const void* buffer, std::uint64_t offset, std::uint64_t length) -> std::pair<std::uint64_t, bool> { // Caller doesn't actually want to write any data. if (!length) return std::make_pair(0u, true); // Try and write the caller's data to disk. auto [count, success] = FileBuffer::write(buffer, offset, length); // Retrieve the file's updated sizes. auto sizes = mFile.getFileSize(); // If getFileSize(...) failed so shoud've write(...). assert(sizes || (!count && !success)); // Couldn't retrieve the file's updated sizes. if (!sizes) return std::make_pair(count, success); // Clarity. auto [allocatedSize, reportedSize] = *sizes; // Update the file's sizes. mInfo.allocatedSize(allocatedSize); mInfo.reportedSize(reportedSize); // Let the caller know how much data was written. return std::make_pair(count, success); } bool SparseFileBuffer::truncate(std::uint64_t newSize) { // Couldn't truncate the file. if (!FileBuffer::truncate(newSize)) return false; // Try and retrieve the file's updated sizes. auto sizes = mFile.getFileSize(); // This should never fail. assert(sizes); // Destructure sizes for clarity. auto [allocatedSize, reportedSize] = *sizes; // Update the file's sizes. mInfo.allocatedSize(allocatedSize); mInfo.reportedSize(reportedSize); // Let the caller know the file was truncated. return true; } } // file_service } // mega ����������������������������������������������������������sdk-10.11.0/src/fileattributefetch.cpp��������������������������������������������������������������0000664�0000000�0000000�00000011500�15162662266�0017614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file fileattributefetch.cpp * @brief Classes for file attributes fetching * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/fileattributefetch.h" #include "mega/megaclient.h" #include "mega/megaapp.h" #include "mega/logging.h" namespace mega { FileAttributeFetchChannel::FileAttributeFetchChannel(MegaClient* client) : client(client), bt(client->rng), timeout(client->rng) { req.binary = true; req.status = REQ_READY; urltime = 0; fahref = UNDEF; inbytes = 0; e = API_EINTERNAL; } FileAttributeFetch::FileAttributeFetch(handle h, string key, fatype t, int ctag) { nodehandle = h; nodekey = key; type = t; retries = 0; tag = ctag; } void FileAttributeFetchChannel::dispatch() { faf_map::iterator it; // reserve space req.outbuf.clear(); req.outbuf.reserve((fafs[0].size() + fafs[1].size()) * sizeof(handle)); for (int i = 2; i--; ) { for (it = fafs[i].begin(); it != fafs[i].end(); ) { req.outbuf.append((char*)&it->first, sizeof(handle)); if (!i) { // move from fresh to pending fafs[1][it->first] = it->second; fafs[0].erase(it++); } else { it++; } } } if (req.outbuf.size()) { LOG_debug << "Getting file attribute"; e = API_EFAILED; inbytes = 0; req.in.clear(); req.posturl = posturl; req.post(client); timeout.backoff(150); } else { timeout.reset(); req.status = REQ_PREPARED; } } // communicate received file attributes to the application void FileAttributeFetchChannel::parse(int /*fac*/, bool final) { #pragma pack(push,1) // structure of data on the wire // do not read fields directly struct FaHeader { handle h; uint32_t len; }; #pragma pack(pop) const char* ptr = req.data(); const char* endptr = ptr + req.size(); faf_map::iterator it; uint32_t falen = 0; // data is structured as (handle.8.le / position.4.le) + attribute data // attributes are CBC-encrypted with the file's key for (;;) { if (ptr == endptr) break; if (ptr + sizeof(FaHeader) > endptr || ptr + sizeof(FaHeader) + (falen = ((FaHeader*)ptr)->len) > endptr) { if (final || falen > 16*1048576) { break; } else { req.purge(static_cast<size_t>(ptr - req.data())); } break; } // read aligned properly handle h; memcpy(&h, &((FaHeader*)ptr)->h, sizeof(h)); it = fafs[1].find(h); ptr += sizeof(FaHeader); // locate fetch request (could have been deleted by the application in the meantime) if (it != fafs[1].end()) { client->restag = it->second->tag; if (!(falen & (SymmCipher::BLOCKSIZE - 1))) { SymmCipher *cipher = client->getRecycledTemporaryNodeCipher(&it->second->nodekey); if (cipher) { if (!cipher->cbc_decrypt((byte*)ptr, falen)) { LOG_err << "Failed to CBC decrypt file attributes"; } client->app->fa_complete(it->second->nodehandle, it->second->type, ptr, falen); } delete it->second; fafs[1].erase(it); } } ptr += falen; } } // notify the application of the request failure and remove records no longer needed void FileAttributeFetchChannel::failed() { for (faf_map::iterator it = fafs[1].begin(); it != fafs[1].end(); ) { client->restag = it->second->tag; if (client->app->fa_failed(it->second->nodehandle, it->second->type, it->second->retries, e)) { // no retry desired delete it->second; fafs[1].erase(it++); } else { // retry it->second->retries++; // move from pending to fresh fafs[0][it->first] = it->second; fafs[1].erase(it++); req.status = REQ_PREPARED; } } } } // namespace ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/filefingerprint.cpp�����������������������������������������������������������������0000664�0000000�0000000�00000033041�15162662266�0017132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file filefingerprint.cpp * @brief Sparse file fingerprint * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/base64.h" #include "mega/filesystem.h" #include "mega/logging.h" #include "mega/serialize64.h" #include "mega/testhooks.h" #include "mega/utils.h" namespace { constexpr int MAXFULL = 8192; } // anonymous namespace mega { bool operator==(const FileFingerprint& lhs, const FileFingerprint& rhs) { // size differs - cannot be equal if (lhs.size != rhs.size) { return false; } // mtime differs - cannot be equal if (abs(lhs.mtime - rhs.mtime) > FS_MTIME_TOLERANCE_SECS) { return false; } // FileFingerprints not fully available - we can't ensure that they are equal if (!lhs.isvalid || !rhs.isvalid) { return false; } return !memcmp(lhs.crc.data(), rhs.crc.data(), sizeof lhs.crc); } bool operator!=(const FileFingerprint& lhs, const FileFingerprint& rhs) { return !(lhs == rhs); } bool FileFingerprint::EqualExceptValidFlag(const FileFingerprint& rhs) const { // same as == but not checking valid if (size != rhs.size) return false; if (abs(mtime - rhs.mtime) > FS_MTIME_TOLERANCE_SECS) return false; return !memcmp(crc.data(), rhs.crc.data(), sizeof crc); } bool FileFingerprint::equalExceptMtime(const FileFingerprint& rhs) const { if (this->size != rhs.size) { return false; } if (!this->isvalid || !rhs.isvalid) { return false; } return !memcmp(this->crc.data(), rhs.crc.data(), sizeof this->crc); } bool FileFingerprint::equalExceptMtimeAndIsValid(const FileFingerprint& rhs) const { return (memcmp(this->crc.data(), rhs.crc.data(), sizeof rhs.crc) == 0 && this->size == rhs.size); } bool FileFingerprint::serialize(string *d) const { d->append((const char*)&size, sizeof(size)); d->append((const char*)&mtime, sizeof(mtime)); d->append((const char*)crc.data(), sizeof(crc)); d->append((const char*)&isvalid, sizeof(isvalid)); return true; } bool FileFingerprint::serializeExcludingMtime(string* d) const { d->reserve(sizeof(size) + sizeof(crc) + sizeof(isvalid)); d->append((const char*)&size, sizeof(size)); d->append((const char*)crc.data(), sizeof(crc)); d->append((const char*)&isvalid, sizeof(isvalid)); return true; } unique_ptr<FileFingerprint> FileFingerprint::unserialize(const char*& ptr, const char* end) { if (ptr + sizeof(m_off_t) + sizeof(m_time_t) + 4 * sizeof(int32_t) + sizeof(bool) > end) { LOG_err << "FileFingerprint unserialization failed - serialized string too short"; return NULL; } unique_ptr<FileFingerprint> fp(new FileFingerprint()); fp->size = MemAccess::get<m_off_t>(ptr); ptr += sizeof(m_off_t); fp->mtime = MemAccess::get<m_time_t>(ptr); ptr += sizeof(m_time_t); memcpy(fp->crc.data(), ptr, sizeof(fp->crc)); ptr += sizeof(fp->crc); fp->isvalid = MemAccess::get<bool>(ptr); ptr += sizeof(bool); return fp; } FileFingerprint::FileFingerprint(const FileFingerprint& other) : size{other.size} , mtime{other.mtime} , crc(other.crc) , isvalid{other.isvalid} {} FileFingerprint& FileFingerprint::operator=(const FileFingerprint& other) { assert(this != &other); size = other.size; mtime = other.mtime; crc = other.crc; isvalid = other.isvalid; return *this; } // Helper: compute sparse window offset with 64-bit math and clamp to [0, size - blockBytes] static inline m_off_t computeSparseOffset64(const m_off_t size, const unsigned lane_i, const unsigned block_j, const unsigned blocks, const size_t crcCount, const size_t blockBytes) { #ifndef NDEBUG bool useLegacyBuggySparseCrc = false; DEBUG_TEST_HOOK_FILEFINGERPRINT_USE_LEGACY_BUGGY_SPARSE_CRC(useLegacyBuggySparseCrc); LOG_warn << "computeSparseOffset64: useLegacyBuggySparseCrc = " << useLegacyBuggySparseCrc; if (useLegacyBuggySparseCrc) { // For test-only simulations: emulate the historical 32-bit overflow bug. // This is equivalent to using the buggy math for the sparse offset calculation. return legacySparseOffset32Bug(size, lane_i, block_j); } #endif const auto sz = static_cast<uint64_t>(size); const auto idx64 = static_cast<uint64_t>(lane_i) * static_cast<uint64_t>(blocks) + static_cast<uint64_t>(block_j); // 0..(crcCount*blocks-1) const auto numer = (sz - static_cast<uint64_t>(blockBytes)) * idx64; // 64-bit multiply const auto denom = static_cast<uint64_t>(crcCount) * static_cast<uint64_t>(blocks) - 1; // e.g. 127 const auto off64 = denom ? (numer / denom) : 0; const auto clampMax = sz - static_cast<uint64_t>(blockBytes); return static_cast<m_off_t>(off64 > clampMax ? clampMax : off64); } bool FileFingerprint::genfingerprint(FileAccess* fa, bool ignoremtime) { bool changed = false; decltype(crc) newcrc; int32_t crcval; if (mtime != fa->mtime) { mtime = fa->mtime; changed = !ignoremtime; } if (size != fa->size) { size = fa->size; changed = true; } if (!fa->openf(FSLogging::logOnError)) { size = -1; return true; } if (size <= (m_off_t)sizeof crc) { // tiny file: read verbatim, NUL pad if (!fa->frawread((byte*)newcrc.data(), static_cast<unsigned>(size), 0, true, FSLogging::logOnError)) { size = -1; fa->closef(); return true; } if (size < (m_off_t)sizeof(crc)) { memset((byte*)newcrc.data() + size, 0, sizeof(crc) - static_cast<size_t>(size)); } } else if (size <= MAXFULL) { // small file: full coverage, four full CRC32s HashCRC32 crc32; byte buf[MAXFULL]; if (!fa->frawread(buf, static_cast<unsigned>(size), 0, true, FSLogging::logOnError)) { size = -1; fa->closef(); return true; } for (unsigned i = 0; i < crc.size(); i++) { int begin = int(i * static_cast<size_t>(size) / crc.size()); int end = int((i + 1) * static_cast<size_t>(size) / crc.size()); crc32.add(buf + begin, static_cast<unsigned>(end - begin)); crc32.get((byte*)&crcval); newcrc[i] = static_cast<int32_t>(htonl(static_cast<uint32_t>(crcval))); } } else { // large file: sparse coverage, four sparse CRC32s HashCRC32 crc32; byte block[4 * sizeof crc]; const unsigned blocks = MAXFULL / unsigned(sizeof block * crc.size()); for (unsigned i = 0; i < crc.size(); i++) { for (unsigned j = 0; j < blocks; j++) { const auto offset = computeSparseOffset64(size, i, j, blocks, crc.size(), sizeof block); if (!fa->frawread(block, sizeof block, offset, true, FSLogging::logOnError)) { size = -1; fa->closef(); return true; } crc32.add(block, sizeof block); } crc32.get((byte*)&crcval); newcrc[i] = static_cast<int32_t>(htonl(static_cast<uint32_t>(crcval))); } } if (crc != newcrc) { crc = newcrc; changed = true; } if (!isvalid) { isvalid = true; changed = true; } fa->closef(); LOG_debug << "[FileFingerprint::genfingerprint] FA debug fp: " << fingerprintDebugString(); return changed; } bool FileFingerprint::genfingerprint(InputStreamAccess *is, m_time_t cmtime, bool ignoremtime) { bool changed = false; decltype(crc) newcrc; int32_t crcval; if (mtime != cmtime) { mtime = cmtime; changed = !ignoremtime; } if (size != is->size()) { size = is->size(); changed = true; } if (size < 0) { size = -1; return true; } if (size <= (m_off_t)sizeof crc) { // tiny file: read verbatim, NUL pad if (!is->read((byte*)newcrc.data(), (unsigned int)size)) { size = -1; return true; } if (size < (m_off_t)sizeof(crc)) { memset((byte*)newcrc.data() + size, 0, sizeof(crc) - static_cast<size_t>(size)); } } else if (size <= MAXFULL) { // small file: full coverage, four full CRC32s HashCRC32 crc32; byte buf[MAXFULL]; if (!is->read(buf, static_cast<unsigned>(size))) { size = -1; return true; } for (unsigned i = 0; i < crc.size(); i++) { int begin = int(i * static_cast<size_t>(size) / crc.size()); int end = int((i + 1) * static_cast<size_t>(size) / crc.size()); crc32.add(buf + begin, static_cast<unsigned>(end - begin)); crc32.get((byte*)&crcval); newcrc[i] = static_cast<int32_t>(htonl(static_cast<uint32_t>(crcval))); } } else { // large file: sparse coverage, four sparse CRC32s HashCRC32 crc32; byte block[4 * sizeof crc]; const unsigned blocks = MAXFULL / unsigned(sizeof block * crc.size()); m_off_t current = 0; for (unsigned i = 0; i < crc.size(); i++) { for (unsigned j = 0; j < blocks; j++) { const auto offset = computeSparseOffset64(size, i, j, blocks, crc.size(), sizeof block); //Seek for (m_off_t fullstep = offset - current; fullstep > 0; ) // 500G or more and the step doesn't fit in 32 bits { unsigned step = fullstep > UINT_MAX ? UINT_MAX : unsigned(fullstep); if (!is->read(NULL, step)) { size = -1; return true; } fullstep -= (uint64_t)step; } current += (offset - current); if (!is->read(block, sizeof block)) { size = -1; return true; } current += sizeof block; crc32.add(block, sizeof block); } crc32.get((byte*)&crcval); newcrc[i] = static_cast<int32_t>(htonl(static_cast<uint32_t>(crcval))); } } if (crc != newcrc) { crc = newcrc; changed = true; } if (!isvalid) { isvalid = true; changed = true; } LOG_debug << "[FileFingerprint::genfingerprint] IA debug fp: " << fingerprintDebugString(); return changed; } // convert this FileFingerprint to string void FileFingerprint::serializefingerprint(string* d) const { byte buf[sizeof crc + 1 + sizeof mtime]; int l; memcpy(buf, crc.data(), sizeof crc); l = Serialize64::serialize(buf + sizeof crc, static_cast<uint64_t>(mtime)); d->resize((sizeof crc + static_cast<size_t>(l)) * 4 / 3 + 4); d->resize(Base64::btoa(buf, sizeof crc + static_cast<unsigned long>(l), (char*)d->c_str())); } // decode and set base64-encoded fingerprint int FileFingerprint::unserializefingerprint(const string* d) { byte buf[sizeof crc + sizeof mtime + 1]; unsigned l; uint64_t t; if ((l = static_cast<unsigned>(Base64::atob(d->c_str(), buf, sizeof buf))) < sizeof crc + 1) { return 0; } if (Serialize64::unserialize(buf + sizeof crc, static_cast<int>(l - sizeof crc), &t) < 0) { return 0; } memcpy(crc.data(), buf, sizeof crc); mtime = static_cast<m_time_t>(t); isvalid = true; return 1; } string FileFingerprint::fingerprintDebugString() const { return std::to_string(size) + ":" + std::to_string(mtime) + ":" + (const char*)Base64Str<sizeof(crc)>((byte*)crc.data()) + (isvalid ? ":1" : ":0"); } bool FileFingerprintCmp::operator()(const FileFingerprint* a, const FileFingerprint* b) const { if (a->size < b->size) { return true; } if (a->size > b->size) { return false; } if (a->mtime < b->mtime) { return true; } if (a->mtime > b->mtime) { return false; } return memcmp(a->crc.data(), b->crc.data(), sizeof a->crc) < 0; } bool FileFingerprintCmp::operator()(const FileFingerprint &a, const FileFingerprint &b) const { return operator()(&a, &b); } bool FileFingerprintCmpNoMtime::operator()(const FileFingerprint* a, const FileFingerprint* b) const { if (a->size < b->size) return true; if (a->size > b->size) return false; return memcmp(a->crc.data(), b->crc.data(), sizeof a->crc) < 0; } bool FileFingerprintCmpNoMtime::operator()(const FileFingerprint& a, const FileFingerprint& b) const { return operator()(&a, &b); } } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/filesystem.cpp����������������������������������������������������������������������0000664�0000000�0000000�00000155571�15162662266�0016144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file filesystem.cpp * @brief Generic host filesystem access interfaces * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/filesystem.h" #include "mega.h" #include "mega/base64.h" #include "mega/localpath.h" #include "mega/logging.h" #include "mega/megaclient.h" #include "mega/node.h" #include "mega/sync.h" #include "megafs.h" #include <utf8proc/utf8proc.h> #include <cassert> #include <cctype> #include <regex> #include <tuple> #ifdef TARGET_OS_MAC #include "mega/osx/osxutils.h" #endif #ifdef __ANDROID__ #include "mega/android/androidFileSystem.h" #endif namespace mega { std::atomic<int> FileSystemAccess::mMinimumDirectoryPermissions{0700}; std::atomic<int> FileSystemAccess::mMinimumFilePermissions{0600}; CodeCounter::ScopeStats g_compareUtfTimings("compareUtfTimings"); FSLogging FSLogging::noLogging(eNoLogging); FSLogging FSLogging::logOnError(eLogOnError); FSLogging FSLogging::logExceptFileNotFound(eLogExceptFileNotFound); bool FSLogging::doLog(int os_errorcode) { return setting == eLogOnError || (setting == eLogExceptFileNotFound && !isFileNotFound(os_errorcode)); } namespace detail { const int escapeChar = '%'; template<typename CharT> int decodeEscape(UnicodeCodepointIterator<CharT>& it) { // only call when we already consumed an escapeChar. auto tmpit = it; auto c1 = tmpit.get(); auto c2 = tmpit.get(); if (islchex_high(c1) && islchex_low(c2)) { it = tmpit; return hexval(c1) << 4 | hexval(c2); } else return -1; } int identity(const int c) { return c; } #ifdef _WIN32 template<typename CharT> UnicodeCodepointIterator<CharT> skipPrefix(const UnicodeCodepointIterator<CharT>& it) { auto i = it; // Match leading \\. if (!(i.match('\\') && i.match('\\'))) { return it; } // Match . or ? switch (i.peek()) { case '.': case '?': i.get(); break; default: return it; } // Match \. if (!i.match('\\')) { return it; } auto j = i; // Match drive letter. if (j.get() && j.match(':')) { return i; } return it; } #endif // _WIN32 // the case when the strings are over different character types (just uses match()) template<typename CharT, typename CharU, typename UnaryOperation> int compareUtf(UnicodeCodepointIterator<CharT> first1, bool unescaping1, UnicodeCodepointIterator<CharU> first2, bool unescaping2, UnaryOperation transform) { CodeCounter::ScopeTimer rst(g_compareUtfTimings); #ifdef _WIN32 first1 = skipPrefix(first1); first2 = skipPrefix(first2); #endif // _WIN32 while (!(first1.end() || first2.end())) { int c1 = first1.get(); if (c1 != escapeChar && first2.match(c1)) { continue; } int c2 = first2.get(); if (unescaping1 || unescaping2) { int c1e = -1; int c2e = -1; auto first1e = first1; auto first2e = first2; if (unescaping1 && c1 == escapeChar) { c1e = decodeEscape(first1e); } if (unescaping2 && c2 == escapeChar) { c2e = decodeEscape(first2e); } // so we have preferred to consume the escape if it's a match (even if there is a match before considering escapes) if (c1e != -1 && c2e != -1) { if (transform(c1e) == transform(c2e)) { first1 = first1e; first2 = first2e; c1 = c1e; c2 = c2e; } } else if (c1e != -1) { if (transform(c1e) == transform(c2) || transform(c1) != transform(c2)) { // even if it's not a match, still consume the escape if the other is not a match, for sorting purposes first1 = first1e; c1 = c1e; } } else if (c2e != -1) { if (transform(c2e) == transform(c1) || transform(c2) != transform(c1)) { // even if it's not a match, still consume the escape if the other is not a match, for sorting purposes first2 = first2e; c2 = c2e; } } } if (c1 != c2) { c1 = transform(c1); c2 = transform(c2); if (c1 != c2) { return c1 - c2; } } } if (first1.end() && first2.end()) { return 0; } if (first1.end()) { return -1; } return 1; } } // detail fsfp_t::fsfp_t(std::uint64_t fingerprint, std::string uuid) : mFingerprint(fingerprint) , mUUID(std::move(uuid)) { } fsfp_t::operator bool() const { return mFingerprint != 0 || !mUUID.empty(); } bool fsfp_t::operator==(const fsfp_t& rhs) const { return mFingerprint == rhs.mFingerprint && mUUID == rhs.mUUID; } bool fsfp_t::operator<(const fsfp_t& rhs) const { return std::tie(mFingerprint, mUUID) < std::tie(rhs.mFingerprint, rhs.mUUID); } bool fsfp_t::equivalent(const fsfp_t& rhs) const { // Only compare legacy fingerprints if UUIDs are unavailable. if (mUUID.empty() || rhs.mUUID.empty()) { return mFingerprint == rhs.mFingerprint; } return mUUID == rhs.mUUID; } std::uint64_t fsfp_t::fingerprint() const { return mFingerprint; } void fsfp_t::reset() { operator=(fsfp_t()); } const std::string& fsfp_t::uuid() const { return mUUID; } std::string fsfp_t::toString() const { std::ostringstream ostream; ostream << "(fingerprint: " << mFingerprint << ", uuid: " << (mUUID.empty() ? "undefined" : mUUID.c_str()) << ")"; return ostream.str(); } bool fsfp_tracker_t::Less::operator()(const fsfp_t* lhs, const fsfp_t* rhs) const { return lhs != rhs && *lhs < *rhs; } fsfp_ptr_t fsfp_tracker_t::add(const fsfp_t& id) { // Do we already know about this ID? auto i = mFingerprints.find(&id); // IDs already tracked. if (i != mFingerprints.end()) { // Increment reference count. ++i->second.second; // Return reference to caller. return i->second.first; } // Instantiate ID. auto ptr = std::make_shared<fsfp_t>(id); // Add ID to map. mFingerprints.emplace(std::piecewise_construct, std::forward_as_tuple(ptr.get()), std::forward_as_tuple(ptr, 1)); // Return reference to caller. return ptr; } fsfp_ptr_t fsfp_tracker_t::get(const fsfp_t& id) const { // Do we know about this ID? auto i = mFingerprints.find(&id); // Don't know about this ID. if (i == mFingerprints.end()) return nullptr; // Return reference to caller. return i->second.first; } bool fsfp_tracker_t::remove(const fsfp_t& id) { // Do we know about this ID? auto i = mFingerprints.find(&id); // Don't know about this ID. if (i == mFingerprints.end()) return false; // Remove ID if reference count drops to zero. if (!--i->second.second) mFingerprints.erase(i); // Let caller know we removed an ID reference. return true; } int compareUtf(const string& s1, bool unescaping1, const string& s2, bool unescaping2, bool caseInsensitive) { return detail::compareUtf( unicodeCodepointIterator(s1), unescaping1, unicodeCodepointIterator(s2), unescaping2, caseInsensitive ? Utils::toUpper: detail::identity); } int compareUtf(const string& s1, bool unescaping1, const LocalPath& s2, bool unescaping2, bool caseInsensitive) { return detail::compareUtf(unicodeCodepointIterator(s1), unescaping1, unicodeCodepointIterator(s2.toPath(false)), unescaping2, caseInsensitive ? Utils::toUpper : detail::identity); } int compareUtf(const LocalPath& s1, bool unescaping1, const string& s2, bool unescaping2, bool caseInsensitive) { return detail::compareUtf(unicodeCodepointIterator(s1.toPath(false)), unescaping1, unicodeCodepointIterator(s2), unescaping2, caseInsensitive ? Utils::toUpper : detail::identity); } int compareUtf(const LocalPath& s1, bool unescaping1, const LocalPath& s2, bool unescaping2, bool caseInsensitive) { return detail::compareUtf(unicodeCodepointIterator(s1.toPath(false)), unescaping1, unicodeCodepointIterator(s2.toPath(false)), unescaping2, caseInsensitive ? Utils::toUpper : detail::identity); } RemotePath::RemotePath(const string& path) : mPath(path) { } RemotePath& RemotePath::operator=(const string& rhs) { return mPath = rhs, *this; } bool RemotePath::operator==(const RemotePath& rhs) const { return mPath == rhs.mPath; } bool RemotePath::operator==(const string& rhs) const { return mPath == rhs; } RemotePath::operator const string&() const { return mPath; } void RemotePath::appendWithSeparator(const RemotePath& component, bool always) { appendWithSeparator(component.mPath, always); } void RemotePath::appendWithSeparator(const string& component, bool always) { // Only add a separator if necessary. while (always || !mPath.empty()) { // Does the path already end with a separator? if (endsInSeparator()) break; // Does the component begin with a separator? if (component.empty() || component.front() == '/') break; // Add the separator. mPath.append(1, '/'); break; } // Add the component. mPath.append(component); } bool RemotePath::beginsWithSeparator() const { return !mPath.empty() && mPath.front() == '/'; } void RemotePath::clear() { mPath.clear(); } bool RemotePath::empty() const { return mPath.empty(); } bool RemotePath::endsInSeparator() const { return !mPath.empty() && mPath.back() == '/'; } bool RemotePath::findNextSeparator(size_t& index) const { index = std::min(mPath.find('/', index), mPath.size()); return index < mPath.size(); } bool RemotePath::hasNextPathComponent(size_t index) const { return index < mPath.size(); } bool RemotePath::nextPathComponent(size_t& index, RemotePath& component) const { // Skip leading separators. while (index < mPath.size() && mPath[index] == '/') ++index; // Have we hit the end of the string? if (index >= mPath.size()) return component.clear(), false; // Start of component. auto i = index; // Locate next separator. findNextSeparator(index); // Extract component. component.mPath.assign(mPath, i, index - i); return true; } void RemotePath::prependWithSeparator(const RemotePath& component) { // Add a separator only if necessary. if (!beginsWithSeparator() && !component.endsInSeparator()) mPath.insert(0, 1, '/'); // Prepend the component. mPath.insert(0, component.mPath); } const string& RemotePath::str() const { return mPath; } RemotePath RemotePath::subpathFrom(size_t index) const { RemotePath path; path.mPath = mPath.substr(index, string::npos); return path; } RemotePath RemotePath::subpathTo(size_t index) const { RemotePath path; path.mPath.assign(mPath, 0, index); return path; } const string& RemotePath::toName(const FileSystemAccess&) const { return mPath; } bool IsContainingPathOf(const string& a, const char* b, size_t bLength, char sep) { // a's longer than b so a can't contain b. if (bLength < a.size()) return false; // b's longer than a so there should be a separator. if (bLength > a.size() && b[a.size()] != sep) return false; // a and b must share a common prefix. return !a.compare(0, a.size(), b, a.size()); } bool IsContainingCloudPathOf(const string& a, const string& b) { return IsContainingPathOf(a, b.c_str(), b.size(), '/'); } bool IsContainingCloudPathOf(const string& a, const char* b, size_t bLength) { return IsContainingPathOf(a, b, bLength, '/'); } bool IsContainingLocalPathOf(const string& a, const string& b) { #ifdef _WIN32 return IsContainingPathOf(a, b.c_str(), b.size(), '\\'); #else return IsContainingPathOf(a, b.c_str(), b.size(), '/'); #endif } bool IsContainingLocalPathOf(const string& a, const char* b, size_t bLength) { #ifdef _WIN32 return IsContainingPathOf(a, b, bLength, '\\'); #else return IsContainingPathOf(a, b, bLength, '/'); #endif } FileSystemAccess::FileSystemAccess() { } void FileSystemAccess::captimestamp(m_time_t* t) { // FIXME: remove upper bound before the year 2100 and upgrade server-side timestamps to BIGINT if (*t > (uint32_t)-1) *t = (uint32_t)-1; else if (*t < 0) *t = 0; } bool FileSystemAccess::decodeEscape(const char* s, char& escapedChar) const { // s must be part of a null terminated c-style string if (s && *s == '%' && islchex_high(s[1]) // must be 0..127 && islchex_low(s[2])) { escapedChar = char((hexval(s[1]) << 4) | hexval(s[2])); return true; } return false; } const char *FileSystemAccess::fstypetostring(FileSystemType type) { switch (type) { case FS_NTFS: return "NTFS"; case FS_EXFAT: return "EXFAT"; case FS_FAT32: return "FAT32"; case FS_EXT: return "EXT"; case FS_HFS: return "HFS"; case FS_APFS: return "APFS"; case FS_FUSE: return "FUSE"; case FS_SDCARDFS: return "SDCARDFS"; case FS_F2FS: return "F2FS"; case FS_XFS: return "XFS"; case FS_CIFS: return "CIFS"; case FS_NFS: return "NFS"; case FS_SMB: return "SMB"; case FS_SMB2: return "SMB2"; case FS_LIFS: return "LIFS"; case FS_UNKNOWN: // fall through return "UNKNOWN FS"; } return "UNKNOWN FS"; } FileSystemType FileSystemAccess::getlocalfstype(const LocalPath& path) const { // Not enough information to determine path. if (path.empty()) { return FS_UNKNOWN; } FileSystemType type; // Try and get the type from the path we were given. if (getlocalfstype(path, type)) { // Path exists. return type; } // Try and get the type based on our parent's path. LocalPath parentPath(path); // Remove trailing separator, if any. parentPath.trimNonDriveTrailingSeparator(); // Did the path consist solely of that separator? if (parentPath.empty()) { return FS_UNKNOWN; } parentPath = parentPath.parentPath(); if (getlocalfstype(parentPath, type)) { return type; } return FS_UNKNOWN; } bool FileSystemAccess::islocalfscompatible(const int character, const FileSystemType type) const { // NUL is always escaped. if (!character) { return false; } // it turns out that escaping the escape % character doesn't interact well with the // existing sync code, should an older megasync etc be running in the same account // so let's leave this aspect the same as the old system, for now at least. // Filesystem-specific policies. switch (type) { case FS_HFS: return character != ':' && character != '/'; case FS_APFS: case FS_EXT: case FS_F2FS: case FS_XFS: return character != '/'; case FS_EXFAT: case FS_FAT32: case FS_FUSE: case FS_NTFS: case FS_SDCARDFS: case FS_LIFS: case FS_UNKNOWN: default: return !(std::iscntrl(character) || strchr("\\/:?\"<>|*", character)); } } // replace characters that are not allowed in local fs names with a %xx escape sequence void FileSystemAccess::escapefsincompatible(string* name, FileSystemType fileSystemType) const { if (!name->compare("..")) { name->replace(0, 2, "%2e%2e"); return; } if (!name->compare(".")) { name->replace(0, 1, "%2e"); return; } char buf[4]; size_t utf8seqsize = 0; size_t i = 0; unsigned char c = '0'; while (i < name->size()) { c = static_cast<unsigned char>((*name)[i]); utf8seqsize = Utils::utf8SequenceSize(c); assert(utf8seqsize); if (utf8seqsize == 1 && !islocalfscompatible(c, fileSystemType)) { snprintf(buf, sizeof(buf), "%%%02x", c); name->replace(i, 1, buf); // Logging these at such a low level is too frequent and verbose //LOG_debug << "Escape incompatible character for filesystem type " // << fstypetostring(fileSystemType) // << ", replace '" << char(c) << "' by '" << buf << "'\n"; } i += utf8seqsize; } } void FileSystemAccess::unescapefsincompatible(string *name) const { if (!name->compare("%2e%2e")) { name->replace(0, 6, ".."); return; } if (!name->compare("%2e")) { name->replace(0, 3, "."); return; } for (size_t i = 0; i < name->size(); ++i) { char c; if (decodeEscape(name->c_str() + i, c)) // it must be a null terminated c-style string passed here { // Substitute in the decoded character. name->replace(i, 3, 1, c); } } } std::unique_ptr<LocalPath> FileSystemAccess::fsShortname(const LocalPath& localname) { LocalPath s; if (getsname(localname, s)) { return std::make_unique<LocalPath>(std::move(s)); } return nullptr; } handle FileSystemAccess::fsidOf(const LocalPath& path, bool follow, bool skipcasecheck, FSLogging fsl) { auto fileAccess = newfileaccess(follow); if (fileAccess->fopen(path, OPEN_RDONLY, fsl, nullptr, false, skipcasecheck)) return fileAccess->fsid; return UNDEF; } #ifdef ENABLE_SYNC bool FileSystemAccess::initFilesystemNotificationSystem() { return true; } #endif // ENABLE_SYNC bool FileSystemAccess::fileExistsAt(const LocalPath& path) { auto fa = newfileaccess(false); return fa->isfile(path); } #ifdef ENABLE_SYNC // default DirNotify: no notification available DirNotify::DirNotify(const LocalPath& rootPath) { assert(!rootPath.empty()); localbasepath = rootPath; mFailed = 1; mFailReason = "Not initialized"; mErrorCount = 0; } void DirNotify::setFailed(int errCode, const string& reason) { std::lock_guard<std::mutex> g(mMutex); mFailed = errCode; mFailReason = reason; } int DirNotify::getFailed(string& reason) { if (mFailed) { reason = mFailReason; } return mFailed; } bool DirNotify::empty() { return fsEventq.empty(); } // notify base LocalNode + relative path/filename void DirNotify::notify(NotificationDeque& q, LocalNode* l, Notification::ScanRequirement sr, LocalPath&& path, bool immediate) { // We may be executing on a thread here so we can't access the LocalNode data structures. Queue everything, and // filter when the notifications are processed. Also, queueing it here is faster than logging the decision anyway. Notification n(immediate ? 0 : Waiter::ds.load(), sr, std::move(path), l); q.pushBack(std::move(n)); } DirNotify* FileSystemAccess::newdirnotify(LocalNode&, const LocalPath& rootPath, Waiter*) { return new DirNotify(rootPath); } #endif // ENABLE_SYNC FileAccess::FileAccess(Waiter *waiter) { this->waiter = waiter; this->isAsyncOpened = false; this->numAsyncReads = 0; } FileAccess::~FileAccess() { // All AsyncIOContext objects must be deleted before assert(!numAsyncReads && !isAsyncOpened); } // open file for reading bool FileAccess::fopen(const LocalPath& name, FSLogging fsl) { updatelocalname(name, true); fopenSucceeded = sysstat(&mtime, &size, FSLogging::noLogging); if (!fopenSucceeded && fsl.doLog(errorcode)) { LOG_err << "Unable to FileAccess::fopen('" << name << "'): sysstat() failed: error code: " << errorcode << ": " << FileSystemAccess::getErrorMessage(errorcode); } return fopenSucceeded; } bool FileAccess::isfile(const LocalPath& path) { auto name = std::move(nonblocking_localname); updatelocalname(path, true); sysstat(&mtime, &size, FSLogging::noLogging); updatelocalname(name, true); return type == FILENODE; } bool FileAccess::isfolder(const LocalPath& path) { auto name = std::move(nonblocking_localname); updatelocalname(path, true); sysstat(&mtime, &size, FSLogging::noLogging); updatelocalname(name, true); return type == FOLDERNODE; } // check if size and mtime are unchanged, then open for reading bool FileAccess::openf(FSLogging fsl) { if (nonblocking_localname.empty()) { // file was not opened in nonblocking mode return true; } m_time_t curr_mtime; m_off_t curr_size; if (!sysstat(&curr_mtime, &curr_size, FSLogging::noLogging)) { if (fsl.doLog(errorcode)) { LOG_err << "Error opening file handle (sysstat) '" << nonblocking_localname << "': errorcode " << errorcode << ": " << FileSystemAccess::getErrorMessage(errorcode); } return false; } if (curr_mtime != mtime || curr_size != size) { mtime = curr_mtime; size = curr_size; retry = false; return false; } bool r = sysopen(false, FSLogging::noLogging); if (!r && fsl.doLog(errorcode)) { // file may have been deleted just now LOG_err << "Error opening file handle (sysopen) '" << nonblocking_localname << "': errorcode " << errorcode << ": " << FileSystemAccess::getErrorMessage(errorcode); } return r; } void FileAccess::closef() { if (!nonblocking_localname.empty()) { sysclose(); } } bool FileAccess::fstat() { return fstat(mtime, size); } void FileAccess::asyncopfinished(void *param) { Waiter *waiter = (Waiter *)param; if (waiter) { waiter->notify(); } } AsyncIOContext *FileAccess::asyncfopen(const LocalPath& f, FSLogging fsl) { updatelocalname(f, true); LOG_verbose << "Async open start"; AsyncIOContext *context = newasynccontext(); context->op = AsyncIOContext::OPEN; context->access = AsyncIOContext::ACCESS_READ; context->openPath = f; context->waiter = waiter; context->userCallback = asyncopfinished; context->userData = waiter; context->posOfBuffer = size; context->fa = this; context->failed = !sysstat(&mtime, &size, fsl); context->retry = this->retry; context->finished = true; context->userCallback(context->userData); return context; } bool FileAccess::asyncopenf(FSLogging fsl) { numAsyncReads++; if (nonblocking_localname.empty()) { return true; } if (isAsyncOpened) { return true; } m_time_t curr_mtime = 0; m_off_t curr_size = 0; if (!sysstat(&curr_mtime, &curr_size, FSLogging::noLogging)) { if (fsl.doLog(errorcode)) { LOG_err << "Error opening async file handle (sysstat): '" << nonblocking_localname << "': " << errorcode << ": " << FileSystemAccess::getErrorMessage(errorcode); } return false; } if (curr_mtime != mtime || curr_size != size) { mtime = curr_mtime; size = curr_size; retry = false; return false; } LOG_debug << "Opening async file handle for reading"; bool result = sysopen(true, FSLogging::noLogging); if (result) { isAsyncOpened = true; } else if (fsl.doLog(errorcode)) { LOG_err << "Error opening async file handle (sysopen): '" << nonblocking_localname << "': " << errorcode << ": " << FileSystemAccess::getErrorMessage(errorcode); } return result; } void FileAccess::asyncclosef() { numAsyncReads--; if (isAsyncOpened && !numAsyncReads) { LOG_debug << "Closing async file handle"; isAsyncOpened = false; sysclose(); } } AsyncIOContext *FileAccess::asyncfopen(const LocalPath& f, bool read, bool write, m_off_t pos) { LOG_verbose << "Async open start"; AsyncIOContext *context = newasynccontext(); context->op = AsyncIOContext::OPEN; context->access = AsyncIOContext::ACCESS_NONE | (read ? AsyncIOContext::ACCESS_READ : 0) | (write ? AsyncIOContext::ACCESS_WRITE : 0); context->openPath = f; context->waiter = waiter; context->userCallback = asyncopfinished; context->userData = waiter; context->posOfBuffer = pos; context->fa = this; asyncsysopen(context); return context; } void FileAccess::asyncsysopen(AsyncIOContext *context) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } } AsyncIOContext *FileAccess::asyncfread(string *dst, unsigned len, unsigned pad, m_off_t pos, FSLogging fsl) { LOG_verbose << "Async read start"; dst->resize(len + pad); AsyncIOContext *context = newasynccontext(); context->op = AsyncIOContext::READ; context->posOfBuffer = pos; context->pad = pad; context->dataBuffer = (byte*)dst->data(); context->dataBufferLen = len; context->waiter = waiter; context->userCallback = asyncopfinished; context->userData = waiter; context->fa = this; if (!asyncopenf(fsl)) { LOG_err << "Error in asyncopenf"; context->failed = true; context->retry = this->retry; context->finished = true; context->userCallback(context->userData); return context; } asyncsysread(context); return context; } void FileAccess::asyncsysread(AsyncIOContext *context) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } } AsyncIOContext *FileAccess::asyncfwrite(const byte* data, unsigned len, m_off_t pos) { LOG_verbose << "Async write start"; AsyncIOContext *context = newasynccontext(); context->op = AsyncIOContext::WRITE; context->posOfBuffer = pos; context->dataBufferLen = len; context->dataBuffer = const_cast<byte*>(data); context->waiter = waiter; context->userCallback = asyncopfinished; context->userData = waiter; context->fa = this; asyncsyswrite(context); return context; } void FileAccess::asyncsyswrite(AsyncIOContext *context) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } } AsyncIOContext *FileAccess::newasynccontext() { return new AsyncIOContext(); } bool FileAccess::fread(string* buffer, unsigned long length, unsigned long padding, m_off_t offset, FSLogging logging, bool* cretry) { // Sanity. assert(buffer); // Make sure the file's been opened. if (!openf(logging)) return false; // Make sure our buffer is large enough. buffer->resize(length + padding); // Try and perform the read. auto result = sysread(buffer->data(), length, offset, cretry); // Read was successful so zero pad bytes. if (result && padding) std::memset(buffer->data() + length, 0, padding); // Close the file if necessary. closef(); // Let the caller know if the read was successful. return result; } bool FileAccess::frawread(void* buffer, unsigned long length, m_off_t offset, bool alreadyOpened, FSLogging logging, bool* cretry) { // Sanity. assert(buffer || !length); // Couldn't open the file. if (!alreadyOpened && !openf(logging)) return false; // Try and perform the read. auto result = sysread(buffer, length, offset, cretry); // Close the file if necessary. if (!alreadyOpened) closef(); // Let the caller know if the read was successful. return result; } AsyncIOContext::~AsyncIOContext() { finish(); // AsyncIOContext objects must be deleted before the FileAccess object if (op == AsyncIOContext::READ) { fa->asyncclosef(); } } void AsyncIOContext::finish() { if (!finished) { while (!finished) { waiter->init(NEVER); waiter->wait(); } // We could have been consumed and external event waiter->notify(); } } OpenFlag AsyncIOContext::toOpenFlag(int access) { bool read = access & AsyncIOContext::ACCESS_READ; bool write = access & AsyncIOContext::ACCESS_WRITE; if (read && write) { return OPEN_RDWR; } else if (write) { return OPEN_WRONLY; } else { // Includes (read && !write) and (!read && !write) return OPEN_RDONLY; } } FileInputStream::FileInputStream(FileAccess *fileAccess) { this->fileAccess = fileAccess; this->offset = 0; } m_off_t FileInputStream::size() { return fileAccess->size; } bool FileInputStream::read(byte *buffer, unsigned size) { if (!buffer) { if ((offset + size) <= fileAccess->size) { offset += size; return true; } LOG_warn << "Invalid seek on FileInputStream"; return false; } if (fileAccess->frawread(buffer, size, offset, true, FSLogging::logOnError)) { offset += size; return true; } LOG_warn << "Invalid read on FileInputStream"; return false; } #ifdef ENABLE_SYNC bool Notification::fromDebris(const Sync& sync) const { // Must not be the root. if (path.empty()) return false; // Must have an associated local node. if (!localnode) return false; // Assume this filtering has been done at a higher level. assert(!invalidated()); // Emitted from sync root? if (localnode->parent) return false; // Contained with debris? return sync.localdebrisname.isContainingPathOf(path); } bool Notification::invalidated() const { return localnode == (LocalNode*)~0; } #endif LocalPath FileNameGenerator::suffixWithN(FileAccess* fa, const LocalPath& localname) { return suffix(fa, localname, [ ](unsigned num) { return " (" + std::to_string(num) + ")"; }); } LocalPath FileNameGenerator::suffixWithOldN(FileAccess* fa, const LocalPath& localname) { LocalPath currentLeafName; // We need to get current name, in case of unsensitive case systems, suffix is added to current // name without any change case fa->fopen(localname, OPEN_RDONLY, FSLogging::logExceptFileNotFound, nullptr, false, false, ¤tLeafName); LocalPath path = localname; if (!currentLeafName.empty() && !currentLeafName.isAbsolute() && !currentLeafName.isURI()) { path.changeLeaf(currentLeafName); } return suffix(fa, path, [](unsigned num) { return ".old" + std::to_string(num); }); } LocalPath FileNameGenerator::suffix(FileAccess* fa, const LocalPath& localname, std::function<std::string(unsigned)> suffixF) { LocalPath localnewname; unsigned num = 0; do { num++; localnewname = localname.insertFilenameSuffix(suffixF(num)); } while (fa->fopen(localnewname, FSLogging::logExceptFileNotFound) || fa->type == FOLDERNODE); return localnewname; } FileDistributor::FileDistributor(const LocalPath& lp, size_t ntargets, m_time_t mtime, const FileFingerprint& confirm) : theFile(lp) , numTargets(ntargets) , mMtime(mtime) , confirmFingerprint(confirm) { } FileDistributor::~FileDistributor() { // the last operation clears the name lock_guard<recursive_mutex> g(mMutex); assert(theFile.empty()); // todo: if we haven't cleared the name, delete the file. But we need an fsaccess for this thread which could be sync or client... maybe queue to client thread? assert(numTargets == 0); } bool FileDistributor::moveTo(const LocalPath& source, LocalPath& target, TargetNameExistsResolution method, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long, [[maybe_unused]] Sync* syncForDebris, [[maybe_unused]] const FileFingerprint& confirmFingerprint) { assert (!!syncForDebris == (method == MoveReplacedFileToSyncDebris)); // Try and move the source to the target. assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, source, confirmFingerprint)); if (fsAccess.renamelocal(source, target, method == OverwriteTarget)) { assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, target, confirmFingerprint)); return true; } transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; // the destination path already exists if method is not OverwriteTarget switch (method) { #ifdef ENABLE_SYNC case MoveReplacedFileToSyncDebris: return moveToForMethod_MoveReplacedFileToSyncDebris(source, target, fsAccess, transient_error, name_too_long, syncForDebris, confirmFingerprint); #endif case RenameWithBracketedNumber: return moveToForMethod_RenameWithBracketedNumber(source, target, fsAccess, transient_error, name_too_long); case RenameExistingToOldN: return moveToForMethod_RenameExistingToOldN(source, target, fsAccess, transient_error, name_too_long); default: { LOG_debug << "File move failed even with overwrite set. Target name: " << target; return false; } } } bool FileDistributor::moveToForMethod_RenameWithBracketedNumber(const LocalPath& source, LocalPath& target, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long) { // add an (x) suffix until there's no clash auto fa = fsAccess.newfileaccess(); auto changedName = FileNameGenerator::suffixWithN(fa.get(), target); LOG_debug << "The move destination file path exists already. Updated name: " << changedName; // Try and move the source to the changed name. if (fsAccess.renamelocal(source, changedName, false)) { target = changedName; return true; } else { LOG_debug << "File move failed even after renaming with (N) to avoid a clash. Updated name: " << changedName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } } bool FileDistributor::moveToForMethod_RenameExistingToOldN(const LocalPath& source, LocalPath& target, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long) { // rename the existing with an .oldN suffix until there's no clash auto fa = fsAccess.newfileaccess(); auto newName = FileNameGenerator::suffixWithOldN(fa.get(), target); LOG_debug << "The move destination file path exists already. renamed it to: " << newName; // Try rename the target to the new name. if (!fsAccess.renamelocal(target, newName, false)) { LOG_debug << "Existing File renamed failed even after renaming with .oldN to avoid a clash. renamed name: " << newName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } // Try and move the source to the target. if (fsAccess.renamelocal(source, target, false)) { return true; } else { LOG_debug << "File move failed even after renaming the existing with .oldN to avoid a clash. renamed name: " << newName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } } #ifdef ENABLE_SYNC bool FileDistributor::moveToForMethod_MoveReplacedFileToSyncDebris( const LocalPath& source, LocalPath& target, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, [[maybe_unused]] const FileFingerprint& confirmFingerprint) { // Move the obstruction to the local debris. if (!syncForDebris->movetolocaldebris(target)) { return false; } auto result = fsAccess.renamelocal(source, target, false); if (!result) { transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; LOG_warn << "File move failed even after moving the obstruction to local debris. Target name: " << target; } else { assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, target, confirmFingerprint)); } return result; } bool FileDistributor::copyToForMethod_MoveReplacedFileToSyncDebris( const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long, Sync* syncForDebris, [[maybe_unused]] const FileFingerprint& confirmFingerprint) { // Move the obstruction to the local debris. if (!syncForDebris->movetolocaldebris(target)) { return false; } auto result = fsAccess.copylocal(source, target, mtime); if (!result) { transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; LOG_debug << "File copy failed even after moving the obstruction to local debris. Target name: " << target; } else { assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, target, confirmFingerprint)); } return result; } #endif //ENABLE_SYNC bool FileDistributor::copyToForMethod_RenameWithBracketedNumber(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long) { // add an (x) suffix until there's no clash auto fa = fsAccess.newfileaccess(); auto changedName = FileNameGenerator::suffixWithN(fa.get(), target); LOG_debug << "The copy destination file path exists already. Updated name: " << changedName; // copy the source to the changed name. if (fsAccess.copylocal(source, changedName, mtime)) { target = changedName; return true; } else { LOG_debug << "File copy failed even after renaming with (N) to avoid a clash. Updated name: " << changedName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } } bool FileDistributor::copyToForMethod_RenameExistingToOldN(const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long) { // rename the existing with an .oldN suffix until there's no clash auto fa = fsAccess.newfileaccess(); auto newName = FileNameGenerator::suffixWithOldN(fa.get(), target); LOG_debug << "The copy destination file path exists already. renamed it to: " << newName; // Try rename the target to the new name. if (!fsAccess.renamelocal(target, newName, false)) { LOG_debug << "Existing File renamed failed even after renaming with .oldN to avoid a clash. renamed name: " << newName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } // Try and copy the source to the target. if (fsAccess.copylocal(source, target, mtime)) { return true; } else { LOG_debug << "File copy failed even after renaming the existing with .oldN to avoid a clash. Updated name: " << newName; transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } } bool FileDistributor::copyToForMethod_OverwriteTarget( const LocalPath& source, LocalPath& target, m_time_t mtime, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long, [[maybe_unused]] const FileFingerprint& confirmFingerprint) { if (fsAccess.copylocal(source, target, mtime))//copylocal is implemented as always overwrite { assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, target, confirmFingerprint)); return true; } else { transient_error = fsAccess.transient_error; name_too_long = fsAccess.target_name_too_long; return false; } } bool FileDistributor::copyTo(const LocalPath& source, LocalPath& target, m_time_t mtime, TargetNameExistsResolution method, FileSystemAccess& fsAccess, bool& transient_error, bool& name_too_long, [[maybe_unused]] Sync* syncForDebris, const FileFingerprint& confirmFingerprint) { assert (!!syncForDebris == (method == MoveReplacedFileToSyncDebris)); assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, source, confirmFingerprint)); // copy the source to the target if target is not there. if (!fsAccess.fileExistsAt(target)) { return copyToForMethod_OverwriteTarget(source, target, mtime, fsAccess, transient_error, name_too_long, confirmFingerprint); } // the destination path already exists if method is not OverwriteTarget switch (method) { #ifdef ENABLE_SYNC case MoveReplacedFileToSyncDebris: return copyToForMethod_MoveReplacedFileToSyncDebris(source, target, mtime, fsAccess, transient_error, name_too_long, syncForDebris, confirmFingerprint); #endif case OverwriteTarget: return copyToForMethod_OverwriteTarget(source, target, mtime, fsAccess, transient_error, name_too_long, confirmFingerprint); case RenameWithBracketedNumber: return copyToForMethod_RenameWithBracketedNumber(source, target, mtime, fsAccess, transient_error, name_too_long); case RenameExistingToOldN: return copyToForMethod_RenameExistingToOldN(source, target, mtime, fsAccess, transient_error, name_too_long); default: { LOG_debug << "File copy failed as invalid method: " << method; return false; } } } bool FileDistributor::distributeTo(LocalPath& lp, FileSystemAccess& fsaccess, TargetNameExistsResolution method, bool& transient_error, bool& name_too_long, Sync* syncForDebris) { transient_error = false; name_too_long = false; lock_guard<recursive_mutex> g(mMutex); if (lp == theFile) { actualPathUsed = true; removeTarget(); return true; } else { if (numTargets == 1 && !actualPathUsed) { // the last one can be a rename (if we haven't already renamed to a final location) LOG_debug << "Renaming temporary file to target path"; if (moveTo(theFile, lp, method, fsaccess, transient_error, name_too_long, syncForDebris, confirmFingerprint)) { actualPathUsed = true; removeTarget(); return true; } else { // maybe multiple Files were part of a single Transfer, and this last one is on a different disk LOG_debug << "Moving instead of renaming temporary file to target path"; if (copyTo(theFile, lp, mMtime, method, fsaccess, transient_error, name_too_long, syncForDebris, confirmFingerprint)) { if (!fsaccess.unlinklocal(theFile)) { LOG_debug << "Could not remove temp file after final destination copy: " << theFile; } removeTarget(); return true; } } } else { // otherwise copy if (copyTo(theFile, lp, mMtime, method, fsaccess, transient_error, name_too_long, syncForDebris, confirmFingerprint)) { removeTarget(); return true; } } return false; } } void FileDistributor::removeTarget() { lock_guard<recursive_mutex> guard(mMutex); // Call isn't meaningful if the distributor has no targets. assert(numTargets && !theFile.empty()); // Decrement the count and clear theFile if there are no more targets. if (!--numTargets) theFile.clear(); } bool isNetworkFilesystem(FileSystemType type) { return type == FS_CIFS || type == FS_NFS || type == FS_SMB || type == FS_SMB2; } std::atomic<size_t> ScanService::mNumServices(0); std::unique_ptr<ScanService::Worker> ScanService::mWorker; std::mutex ScanService::mWorkerLock; ScanService::ScanService() { // Locking here, rather than in the if statement, ensures that the // worker is fully constructed when control leaves the constructor. std::lock_guard<std::mutex> lock(mWorkerLock); if (++mNumServices == 1) { mWorker.reset(new Worker()); } } ScanService::~ScanService() { if (--mNumServices == 0) { std::lock_guard<std::mutex> lock(mWorkerLock); mWorker.reset(); } } auto ScanService::queueScan(LocalPath targetPath, handle expectedFsid, bool followSymlinks, map<LocalPath, FSNode>&& priorScanChildren, shared_ptr<Waiter> waiter) -> RequestPtr { // Create a request to represent the scan. auto request = std::make_shared<ScanRequest>(std::move(waiter), followSymlinks, targetPath, expectedFsid, std::move(priorScanChildren)); // Queue request for processing. mWorker->queue(request); return request; } ScanService::ScanRequest::ScanRequest(shared_ptr<Waiter> waiter, bool followSymLinks, LocalPath targetPath, handle expectedFsid, map<LocalPath, FSNode>&& priorScanChildren) : mWaiter(waiter) , mScanResult(SCAN_INPROGRESS) , mFollowSymLinks(followSymLinks) , mKnown(std::move(priorScanChildren)) , mResults() , mTargetPath(std::move(targetPath)) , mExpectedFsid(expectedFsid) { } ScanService::Worker::Worker(size_t numThreads) : mFsAccess(new FSACCESS_CLASS()) , mPending() , mPendingLock() , mPendingNotifier() , mThreads() { // Always at least one thread. assert(numThreads > 0); LOG_debug << "Starting ScanService worker..."; // Start the threads. while (numThreads--) { try { mThreads.emplace_back([this]() { loop(); }); } catch (std::system_error& e) { LOG_err << "Failed to start worker thread: " << e.what(); } } LOG_debug << mThreads.size() << " worker thread(s) started."; LOG_debug << "ScanService worker started."; } ScanService::Worker::~Worker() { LOG_debug << "Stopping ScanService worker..."; // Queue the 'terminate' sentinel. { std::unique_lock<std::mutex> lock(mPendingLock); mPending.emplace_back(); } // Wake any sleeping threads. mPendingNotifier.notify_all(); LOG_debug << "Waiting for worker thread(s) to terminate..."; // Wait for the threads to terminate. for (auto& thread : mThreads) { thread.join(); } LOG_debug << "ScanService worker stopped."; } void ScanService::Worker::queue(ScanRequestPtr request) { // Queue the request. { std::unique_lock<std::mutex> lock(mPendingLock); mPending.emplace_back(std::move(request)); } // Tell the lucky thread it has something to do. mPendingNotifier.notify_one(); } void ScanService::Worker::loop() { // We're ready when we have some work to do. auto ready = [this]() { return !mPending.empty(); }; for ( ; ; ) { ScanRequestPtr request; { // Wait for something to do. std::unique_lock<std::mutex> lock(mPendingLock); mPendingNotifier.wait(lock, ready); assert(ready()); // condition variable should have taken care of this // Are we being told to terminate? if (!mPending.front()) { // Bail, don't deque the sentinel. return; } request = std::move(mPending.front()); mPending.pop_front(); } LOG_verbose << "Directory scan begins: " << request->mTargetPath; using namespace std::chrono; auto scanStart = high_resolution_clock::now(); // Process the request. unsigned nFingerprinted = 0; auto result = scan(request, nFingerprinted); auto scanEnd = high_resolution_clock::now(); if (result == SCAN_SUCCESS) { LOG_verbose << "Directory scan complete for: " << request->mTargetPath << " entries: " << request->mResults.size() << " taking " << duration_cast<milliseconds>(scanEnd - scanStart).count() << "ms" << " fingerprinted: " << nFingerprinted; } else { LOG_verbose << "Directory scan FAILED (" << result << "): " << request->mTargetPath; } request->mScanResult = result; request->mWaiter->notify(); } } // Really we only have one worker despite the vector of threads - maybe we should just have one // regardless of multiple clients too - there is only one filesystem after all (but not singleton!!) CodeCounter::ScopeStats ScanService::syncScanTime = { "folderScan" }; auto ScanService::Worker::scan(ScanRequestPtr request, unsigned& nFingerprinted) -> ScanResult { CodeCounter::ScopeTimer rst(syncScanTime); auto result = mFsAccess->directoryScan(request->mTargetPath, request->mExpectedFsid, request->mKnown, request->mResults, request->mFollowSymLinks, nFingerprinted); // No need to keep this data around anymore. request->mKnown.clear(); return result; } unique_ptr<FSNode> FSNode::fromFOpened(FileAccess& fa, const LocalPath& fullPath, FileSystemAccess& fsa) { unique_ptr<FSNode> result(new FSNode); result->type = fa.type; result->fsid = fa.fsidvalid ? fa.fsid : UNDEF; result->isSymlink = fa.mIsSymLink; result->fingerprint.mtime = fa.mtime; result->fingerprint.size = fa.size; result->localname = fullPath.leafName(); if (auto sn = fsa.fsShortname(fullPath)) { if (*sn != result->localname) { result->shortname = std::move(sn); } } return result; } unique_ptr<FSNode> FSNode::fromPath(FileSystemAccess& fsAccess, const LocalPath& path, bool skipCaseCheck, FSLogging fsl) { auto fileAccess = fsAccess.newfileaccess(false); LocalPath actualLeafNameIfDifferent; if (!fileAccess->fopen(path, OPEN_RDONLY, fsl, nullptr, false, skipCaseCheck, &actualLeafNameIfDifferent)) return nullptr; auto fsNode = fromFOpened(*fileAccess, path, fsAccess); if (!actualLeafNameIfDifferent.empty()) { fsNode->localname = actualLeafNameIfDifferent; } if (fsNode->type != FILENODE) return fsNode; if (!fsNode->fingerprint.genfingerprint(fileAccess.get())) return nullptr; return fsNode; } bool FSNode::debugConfirmOnDiskFingerprintOrLogWhy(FileSystemAccess& fsAccess, const LocalPath& path, const FileFingerprint& ff) { if (unique_ptr<FSNode> od = fromPath(fsAccess, path, false, FSLogging::logOnError)) { if (od->fingerprint == ff) return true; #ifdef __ANDROID__ if (od->fingerprint.equalExceptMtimeAndIsValid(ff)) { return true; } #endif LOG_debug << "fingerprint mismatch at path: " << path; LOG_debug << "size: " << od->fingerprint.size << " should have been " << ff.size; LOG_debug << "mtime: " << od->fingerprint.mtime << " should have been " << ff.mtime; LOG_debug << "crc: " << Base64Str<sizeof(FileFingerprint::crc)>((byte*)&od->fingerprint.crc) << " should have been " << Base64Str<sizeof(FileFingerprint::crc)>((byte*)&ff.crc); } else { LOG_debug << "failed to get fingerprint for path " << path; } return false; } auto FileSystemAccess::getFileSize(const LocalPath& path) -> std::optional<std::pair<std::uint64_t, std::uint64_t>> { // Create an FA so we can access the file at path. auto fileAccess = newfileaccess(false); // Couldn't create an FA. if (!fileAccess) return std::nullopt; // Couldn't open the file. if (!fileAccess->fopen(path, OPEN_RDONLY, FSLogging::logOnError)) return std::nullopt; // Return the file's allocated and reported size to our caller. return fileAccess->getFileSize(); } bool FileSystemAccess::expandLocalPathFileSystem(const LocalPath& source, LocalPath& destination) { namespace fs = std::filesystem; assert(!source.empty()); assert(!LocalPath::isURIPath(source.toPath(false))); // At worst, the destination mirrors the source. destination = source; fs::path p(source.asPlatformEncoded(false)); std::error_code ec; fs::path c = fs::canonical(p, ec); if (ec) { destination = source; return false; } #ifdef WIN32 destination = LocalPath::fromPlatformEncodedAbsolute(c.wstring()); #else destination = LocalPath::fromAbsolutePath(c.string()); #endif return true; } } // namespace ���������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014200�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/CMakeLists.txt�����������������������������������������������������������������0000664�0000000�0000000�00000000620�15162662266�0016736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Let the user know that they can build with FUSE support. option(WITH_FUSE "Build with FUSE support." OFF) # Add sources required by all backends. add_subdirectory(common) # Does the user want FUSE support built in? if (WITH_FUSE) # Yep so build platform-specific backend. add_subdirectory(supported) else() # Nope so build the dummy backend. add_subdirectory(unsupported) endif() ����������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015470�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/CMakeLists.txt����������������������������������������������������������0000664�0000000�0000000�00000001440�15162662266�0020227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../../../include/mega/fuse/common ${CMAKE_CURRENT_BINARY_DIR}/../../../include/mega/fuse/common ) # Add sources required by all backends. target_sources(SDKlib PRIVATE any_lock_set.cpp client.cpp file_explorer_view.cpp file_move_flag.cpp inode_id.cpp inode_info.cpp logger.cpp mount_event.cpp mount_event_type.cpp mount_flags.cpp mount_info.cpp mount_inode_id.cpp mount_result.cpp service.cpp service_context.cpp ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/any_lock_set.cpp��������������������������������������������������������0000664�0000000�0000000�00000011305�15162662266�0020646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <functional> #include <utility> #include <mega/fuse/common/any_lock_set.h> #include <mega/fuse/common/any_lock.h> namespace mega { namespace fuse { struct Locker { // Try and acquire the specified lock. std::function<bool(AnyLock&)> lock; // Should we abort locking if we can't acquire a single lock? bool shouldAbort; }; /* Locker */ static const Locker blocking = { [](AnyLock& lock) { return lock.lock(), true; }, false }; /* blocking */ static const Locker nonblocking = { [](AnyLock& lock) { return lock.try_lock(); }, true }; /* nonblocking */ // Tries to acquire all locks in a set. static bool tryLockAll(AnyLockVector& locks, const Locker& locker); AnyLockSet::AnyLockSet() : mLocks() , mOwned(false) { } AnyLockSet::AnyLockSet(AnyLockSet&& other) : mLocks(std::move(other.mLocks)) , mOwned(std::move(other.mOwned)) { other.mOwned = false; } AnyLockSet& AnyLockSet::operator=(AnyLockSet&& rhs) { AnyLockSet temp(std::move(rhs)); swap(temp); return *this; } AnyLockSet::operator bool() const { return owns_lock(); } void AnyLockSet::clear() { mLocks.clear(); } bool AnyLockSet::empty() const { return mLocks.empty(); } void AnyLockSet::lock() { assert(!mLocks.empty()); assert(!mOwned); mOwned = tryLockAll(mLocks, blocking); } bool AnyLockSet::owns_lock() const { return mOwned; } void AnyLockSet::release() { if (mOwned) { while (!mLocks.empty()) { mLocks.back().release(); mLocks.pop_back(); } } mOwned = false; } AnyLockVector::size_type AnyLockSet::size() const { return mLocks.size(); } void AnyLockSet::swap(AnyLockSet& other) { using std::swap; swap(mLocks, other.mLocks); swap(mOwned, other.mOwned); } bool AnyLockSet::try_lock() { assert(!mLocks.empty()); assert(!mOwned); mOwned = tryLockAll(mLocks, nonblocking); return mOwned; } void AnyLockSet::unlock() { assert(mOwned); for (auto& lock : mLocks) lock.unlock(); mOwned = false; } bool lock(AnyLock& lock) { return lock.lock(), true; } bool tryLock(AnyLock& lock) { return lock.try_lock(); } // This function's responsible for acquiring a vector of locks in a // dead-lock safe manner. // // The way it works is pretty straight forward: Try to acquire each lock in // turn, remembering which locks were successfully acquired. If any lock // couldn't be acquired, release the locks that we have acquired and repeat // the process starting from the lock we couldn't acquire. // // The first lock is acquired is a special way, depending on how this // function was called. If the user wants to block until all locks have been // acquired, the call to acquire the first lock is itself blocking. Each // subsequent lock is acquired in a nonblocking manner so that we can // release any held locks if we're unable to acquire a particular lock. // // If the user doesn't want to block and we couldn't acquire the first lock, // we just return false to the caller immediately. bool tryLockAll(AnyLockVector& locks, const Locker& locker) { using Index = AnyLockVector::size_type; // Sanity. assert(!locks.empty()); assert(locker.lock); // What lock should we acquire first? Index first = 0; // How many locks have we acquired? Index count = 0; // How many locks do we have to acquire? Index num = locks.size(); try { do { // Try and acquire the first lock. if (!locker.lock(locks[first])) return false; // Try and acquire the rest of the locks. for (count = 1; count < num; ++count) { // Calculate index of next lock. auto index = (first + count) % num; // Try and acquire the next lock. if (locks[index].try_lock()) continue; // Release acquired locks. while (count--) locks[(first + count) % num].unlock(); // Should we fail immediately? if (locker.shouldAbort) return false; // Start the next iteration by acquiring the failing lock. first = index; // Try and acquire the locks again. break; } } while (!locks[first].owns_lock()); } catch (...) { // Release acquired locks. while (count--) locks[(first + count) & num].unlock(); // Rethrow exception. throw; } // We've acquired all the locks in the set. return true; } } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/client.cpp��������������������������������������������������������������0000664�0000000�0000000�00000001303�15162662266�0017447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_queue.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/megaapp.h> namespace mega { namespace fuse { using namespace common; void emitEvent(Client& client, const MountEvent& event) { // Emit the event on the client thread. client.execute([&client, event](const Task& task) { // Client's being torn down. if (task.cancelled()) return; FUSEDebugF("Emitting %s event", toString(event.mType)); // Emit the event. client.application().onFuseEvent(event); }); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/file_explorer_view.cpp��������������������������������������������������0000664�0000000�0000000�00000002020�15162662266�0022057�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/file_explorer_view.h> #include <map> namespace mega { namespace fuse { FileExplorerView toFileExplorerView(const std::string& view) { static const std::map<std::string, FileExplorerView> views = { #define DEFINE_FILE_EXPLORER_VIEW_ENTRY(name) {#name, FILE_EXPLORER_VIEW_##name}, DEFINE_FILE_EXPLORER_VIEWS(DEFINE_FILE_EXPLORER_VIEW_ENTRY) #undef DEFINE_FILE_EXPLORER_VIEW_ENTRY }; auto i = views.find(view); if (i != views.end()) return i->second; // Assume some sane default. return FILE_EXPLORER_VIEW_LIST; } const char* toString(FileExplorerView view) { static const std::map<FileExplorerView, std::string> strings = { #define DEFINE_FILE_EXPLORER_VIEW_ENTRY(name) {FILE_EXPLORER_VIEW_##name, #name}, DEFINE_FILE_EXPLORER_VIEWS(DEFINE_FILE_EXPLORER_VIEW_ENTRY) #undef DEFINE_FILE_EXPLORER_VIEW_ENTRY }; // strings if (auto i = strings.find(view); i != strings.end()) return i->second.c_str(); return "N/A"; } } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/file_move_flag.cpp������������������������������������������������������0000664�0000000�0000000�00000000344�15162662266�0021133�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/file_move_flag.h> namespace mega { namespace fuse { bool valid(FileMoveFlags flags) { // Make sure only a single flag has been set. return (flags & (flags - 1)) == 0; } } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/inode_id.cpp������������������������������������������������������������0000664�0000000�0000000�00000006140�15162662266�0017747�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <iomanip> #include <limits> #include <sstream> #include <mega/common/query.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/base64.h> #include <mega/utils.h> namespace mega { constexpr auto Synthetic = std::uint64_t(1) << 63; namespace common { using fuse::InodeID; InodeID SerializationTraits<InodeID>::from(const Field& field) { auto value = field.get<std::uint64_t>(); if (value < Synthetic) return InodeID(NodeHandle().set6byte(value)); return InodeID(value); } void SerializationTraits<InodeID>::to(Parameter& parameter, const InodeID& value) { parameter.set(value.get()); } } // common namespace fuse { constexpr auto Undefined = std::numeric_limits<std::uint64_t>::max(); InodeID::InodeID() : mValue(Undefined) { } InodeID::InodeID(MountInodeID id) : mValue(id.get()) { } InodeID::InodeID(NodeHandle handle) : mValue(handle.as8byte()) { } InodeID::InodeID(std::uint64_t value) : mValue(value | Synthetic) { assert(value != Undefined); } InodeID::operator NodeHandle() const { if (mValue == Undefined) return NodeHandle(); NodeHandle handle; // Sanity. assert(!(mValue & Synthetic)); handle.set6byte(mValue); return handle; } InodeID::operator bool() const { return mValue != Undefined; } bool InodeID::operator==(const InodeID& rhs) const { return mValue == rhs.mValue; } bool InodeID::operator==(const NodeHandle& rhs) const { return rhs.as8byte() == mValue; } bool InodeID::operator<(const InodeID& rhs) const { return mValue < rhs.mValue; } bool InodeID::operator!=(const InodeID& rhs) const { return mValue != rhs.mValue; } bool InodeID::operator!=(const NodeHandle& rhs) const { assert(!rhs.isUndef()); return rhs.as8byte() != mValue; } bool InodeID::operator!() const { return mValue == Undefined; } InodeID InodeID::fromFileName(const std::string& filename) { // Name's not longer enough to contain a valid Inode ID. if (filename.size() < 16) return InodeID(); // Convenience. SplitFragment name; SplitFragment extension; // Split the name into two chunks: name and extension. std::tie(name, extension) = split(filename, '.'); // Filename's nothing but a giant extension. if (!name.second) return InodeID(); InodeID id; bool result; // Try and decode the inode's ID. std::tie(id.mValue, result) = fromHex<std::uint64_t>(name.first, name.second); // Successfully decoded the inode's ID. if (result) return id; // Couldn't decode the inode's ID. return InodeID(); } std::uint64_t InodeID::get() const { return mValue; } bool InodeID::synthetic() const { return (mValue & Synthetic) > 0; } std::string toFileName(InodeID id) { std::ostringstream ostream; ostream << std::hex << std::setfill('0') << std::setw(16) << id.get(); return ostream.str(); } std::string toString(InodeID id) { return toHandle(id.get()); } } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/inode_info.cpp����������������������������������������������������������0000664�0000000�0000000�00000001267�15162662266�0020313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/node_info.h> #include <mega/fuse/common/inode_info.h> namespace mega { namespace fuse { using namespace common; InodeInfo::InodeInfo(NodeInfo info) : mID(InodeID(info.mHandle)) , mIsDirectory(info.mIsDirectory) , mModified(info.mModified) , mName(std::move(info.mName)) , mParentID(InodeID(info.mParentHandle)) , mPermissions(info.mPermissions) , mSize(info.mSize) { } InodeInfo::InodeInfo(InodeID id, NodeInfo info) : mID(id) , mIsDirectory(info.mIsDirectory) , mModified(info.mModified) , mName(std::move(info.mName)) , mParentID(InodeID(info.mParentHandle)) , mPermissions(info.mPermissions) , mSize(info.mSize) { } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/logger.cpp��������������������������������������������������������������0000664�0000000�0000000�00000000325�15162662266�0017453�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logger.h> namespace mega { namespace fuse { using namespace common; SubsystemLogger& logger() { static SubsystemLogger logger("FUSE"); return logger; } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_event.cpp���������������������������������������������������������0000664�0000000�0000000�00000000537�15162662266�0020544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_event.h> namespace mega { namespace fuse { bool MountEvent::operator==(const MountEvent& rhs) const { return mResult == rhs.mResult && mType == rhs.mType && mName == rhs.mName; } bool MountEvent::operator!=(const MountEvent& rhs) const { return !(*this == rhs); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_event_type.cpp����������������������������������������������������0000664�0000000�0000000�00000000623�15162662266�0021601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_event_type.h> namespace mega { namespace fuse { const char* toString(MountEventType type) { switch (type) { #define DEFINE_MOUNT_EVENT_TYPE_CLAUSE(name) case name: return #name; DEFINE_MOUNT_EVENT_TYPES(DEFINE_MOUNT_EVENT_TYPE_CLAUSE); #undef DEFINE_MOUNT_EVENT_TYPE_CLAUSE } // Silence the compiler. return "N/A"; } } // fuse } // mega �������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_flags.cpp���������������������������������������������������������0000664�0000000�0000000�00000003362�15162662266�0020516�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <stdexcept> #include <mega/common/query.h> #include <mega/common/scoped_query.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_flags.h> namespace mega { namespace fuse { using namespace common; bool MountFlags::operator==(const MountFlags& rhs) const { return mName == rhs.mName && mEnableAtStartup == rhs.mEnableAtStartup && mPersistent == rhs.mPersistent && mReadOnly == rhs.mReadOnly; } bool MountFlags::operator!=(const MountFlags& rhs) const { return !(*this == rhs); } MountFlags MountFlags::deserialize(Query& query) try { MountFlags flags; flags.mEnableAtStartup = query.field("enable_at_startup").get<bool>(); flags.mName = query.field("name").get<std::string>(); flags.mPersistent = query.field("persistent").get<bool>(); flags.mReadOnly = query.field("read_only").get<bool>(); // Sanity. assert(!flags.mEnableAtStartup || flags.mPersistent); return flags; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to deserialize mount flags: %s", exception.what()); throw; } MountFlags MountFlags::deserialize(ScopedQuery& query) { return deserialize(query.query()); } void MountFlags::serialize(Query& query) const try { // Sanity. assert(!mEnableAtStartup || mPersistent); query.param(":enable_at_startup").set(mEnableAtStartup); query.param(":name").set(mName); query.param(":persistent").set(mPersistent); query.param(":read_only").set(mReadOnly); } catch (std::runtime_error& exception) { FUSEErrorF("Unable to serialize mount flags: %s", exception.what()); throw; } void MountFlags::serialize(ScopedQuery& query) const { serialize(query.query()); } } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_info.cpp����������������������������������������������������������0000664�0000000�0000000�00000003567�15162662266�0020364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <stdexcept> #include <mega/common/query.h> #include <mega/common/scoped_query.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_info.h> namespace mega { namespace fuse { using namespace common; bool MountInfoNameLess::operator()(const MountInfo& lhs, const MountInfo& rhs) const { return lhs.name() < rhs.name(); } bool MountInfoPathLess::operator()(const MountInfo& lhs, const MountInfo& rhs) const { return lhs.mPath < rhs.mPath; } bool MountInfo::operator==(const MountInfo& rhs) const { return mPath == rhs.mPath && mFlags == rhs.mFlags && mHandle == rhs.mHandle; } bool MountInfo::operator!=(const MountInfo& rhs) const { return !(*this == rhs); } void MountInfo::name(const std::string& name) { mFlags.mName = name; } const std::string& MountInfo::name() const { return mFlags.mName; } MountInfo MountInfo::deserialize(Query& query) try { MountInfo info; info.mFlags = MountFlags::deserialize(query); info.mHandle = query.field("id").get<NodeHandle>(); info.mPath = NormalizedPath(); if (!query.field("path").null()) info.mPath = query.field("path").get<LocalPath>(); return info; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to deserialize mount info: %s", exception.what()); throw; } MountInfo MountInfo::deserialize(ScopedQuery& query) { return deserialize(query.query()); } void MountInfo::serialize(Query& query) const try { mFlags.serialize(query); query.param(":id").set(mHandle); query.param(":path").set(nullptr); if (!mPath.empty()) query.param(":path").set<LocalPath>(mPath); } catch (std::runtime_error& exception) { FUSEErrorF("Unable to serialize mount info: %s", exception.what()); throw; } void MountInfo::serialize(ScopedQuery& query) const { serialize(query.query()); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_inode_id.cpp������������������������������������������������������0000664�0000000�0000000�00000001427�15162662266�0021174�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/common/inode_id.h> #include <mega/utils.h> namespace mega { namespace fuse { MountInodeID::MountInodeID(InodeID id) : mValue(id.get()) { assert(id); } MountInodeID::MountInodeID(std::uint64_t value) : mValue(value) { assert(mValue); } bool MountInodeID::operator==(const MountInodeID& rhs) const { return mValue == rhs.mValue; } bool MountInodeID::operator<(const MountInodeID& rhs) const { return mValue < rhs.mValue; } bool MountInodeID::operator!=(const MountInodeID& rhs) const { return mValue != rhs.mValue; } std::uint64_t MountInodeID::get() const { return mValue; } std::string toString(MountInodeID id) { return toHandle(id.get()); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/mount_result.cpp��������������������������������������������������������0000664�0000000�0000000�00000001656�15162662266�0020744�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <mega/fuse/common/mount_result.h> namespace mega { namespace fuse { const char* toDescription(MountResult result) { #define DEFINE_MOUNT_RESULT_DESCRIPTIONS(name, description) description, static const char * const descriptions[] = { DEFINE_MOUNT_RESULTS(DEFINE_MOUNT_RESULT_DESCRIPTIONS) }; // descriptions #undef DEFINE_MOUNT_RESULT_DESCRIPTIONS if (result < sizeof(descriptions)) return descriptions[result]; assert(false && "Unknown mount result type"); return "N/A"; } const char* toString(MountResult result) { #define DEFINE_MOUNT_RESULT_NAMES(name, _) #name, static const char * const names[] = { DEFINE_MOUNT_RESULTS(DEFINE_MOUNT_RESULT_NAMES) }; // names #undef DEFINE_MOUNT_RESULT_NAMES if (result < sizeof(names)) return names[result]; assert(false && "Unknown mount result type"); return "N/A"; } } // fuse } // mega ����������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/service.cpp�������������������������������������������������������������0000664�0000000�0000000�00000013361�15162662266�0017640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cassert> #include <stdexcept> #include <mega/common/error_or.h> #include <mega/log_level.h> #include <mega/common/normalized_path.h> #include <mega/common/task_queue.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> #include <mega/fuse/platform/service_context.h> namespace mega { namespace fuse { using namespace common; Service::Service(Client& client, const ServiceFlags& flags) : mClient(client) , mContext() , mFlags(flags) , mFlagsLock() { FUSEDebug1("Service constructed"); } Service::Service(Client& client) : Service(client, ServiceFlags()) { } Service::~Service() { FUSEDebug1("Service destroyed"); } MountResult Service::add(const MountInfo& info) { MountEvent event; event.mName = info.name(); event.mResult = MOUNT_UNEXPECTED; event.mType = MOUNT_ADDED; if (mContext) event.mResult = mContext->add(info); emitEvent(mClient, event); return event.mResult; } bool Service::cached(NormalizedPath path) const { return mContext && mContext->cached(path); } void Service::current() { if (mContext) mContext->current(); } ErrorOr<InodeInfo> Service::describe(const NormalizedPath& path) const { if (mContext) return mContext->describe(path); return unexpected(API_ENOENT); } void Service::disable(MountDisabledCallback callback, const std::string& name, bool remember) { assert(callback); if (mContext) return mContext->disable(std::move(callback), name, remember); MountEvent event; event.mName = name; event.mResult = MOUNT_UNKNOWN; event.mType = MOUNT_DISABLED; emitEvent(mClient, event); callback(event.mResult); } MountResult Service::discard(bool discard) { if (mContext) return mContext->discard(discard); return MOUNT_UNEXPECTED; } void Service::deinitialize() { mContext.reset(); FUSEDebug1("Service deinitialized"); } MountResult Service::downgrade(const NormalizedPath& path, std::size_t target) { if (mContext) return mContext->downgrade(path, target); return MOUNT_UNSUPPORTED; } MountResult Service::enable(const std::string& name, bool remember) { MountEvent event; event.mName = name; event.mResult = MOUNT_UNKNOWN; event.mType = MOUNT_ENABLED; if (mContext) event.mResult = mContext->enable(name, remember); if (event.mResult != MOUNT_SUCCESS) emitEvent(mClient, event); return event.mResult; } bool Service::enabled(const std::string& name) const { return mContext && mContext->enabled(name); } Task Service::execute(std::function<void(const Task&)> function) { if (mContext) return mContext->execute(std::move(function)); Task task(std::move(function), logger()); task.cancel(); return task; } MountResult Service::flags(const std::string& name, const MountFlags& flags) { MountEvent event; event.mName = name; event.mResult = MOUNT_UNKNOWN; event.mType = MOUNT_CHANGED; if (mContext) event.mResult = mContext->flags(name, flags); emitEvent(mClient, event); return event.mResult; } MountFlagsPtr Service::flags(const std::string& name) const { if (mContext) return mContext->flags(name); return nullptr; } MountInfoPtr Service::get(const std::string& name) const { if (mContext) return mContext->get(name); return nullptr; } MountInfoVector Service::get(bool onlyEnabled) const { if (mContext) return mContext->get(onlyEnabled); return MountInfoVector(); } MountResult Service::initialize() try { mContext = std::make_unique<platform::ServiceContext>(ServiceFlags(), *this); FUSEDebug1("Service initialized"); return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to initialize service: %s", exception.what()); return MOUNT_UNEXPECTED; } void Service::logLevel(LogLevel level) { std::lock_guard<std::mutex> guard(mFlagsLock); mFlags.mLogLevel = level; logger().logLevel(level); } LogLevel Service::logLevel() const { return logger().logLevel(); } NormalizedPath Service::path(const std::string& name) const { if (mContext) return mContext->path(name); return NormalizedPath(); } MountResult Service::remove(const std::string& name) { MountEvent event; event.mName = name; event.mResult = MOUNT_UNKNOWN; event.mType = MOUNT_REMOVED; if (mContext) event.mResult = mContext->remove(name); emitEvent(mClient, event); return event.mResult; } void Service::serviceFlags(const ServiceFlags& flags) { std::lock_guard<std::mutex> guard(mFlagsLock); mFlags = flags; logger().logLevel(mFlags.mLogLevel); if (mContext) mContext->serviceFlags(mFlags); } ServiceFlags Service::serviceFlags() const { std::lock_guard<std::mutex> guard(mFlagsLock); return mFlags; } void Service::updated(NodeEventQueue& events) { if (mContext) mContext->updated(events); } bool Service::syncable(const NormalizedPath& path) const { if (mContext) return mContext->syncable(path); return true; } MountResult Service::upgrade(const NormalizedPath& path, std::size_t target) { if (mContext) return mContext->upgrade(path, target); return MOUNT_UNSUPPORTED; } } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/common/service_context.cpp�����������������������������������������������������0000664�0000000�0000000�00000001045�15162662266�0021400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/service_context.h> #include <mega/fuse/common/service_flags.h> #include <mega/fuse/common/service.h> namespace mega { namespace fuse { using namespace common; ServiceContext::ServiceContext(Service& service) : mService(service) { } ServiceContext::~ServiceContext() { } Client& ServiceContext::client() const { return mService.mClient; } void ServiceContext::serviceFlags(const ServiceFlags&) { } ServiceFlags ServiceContext::serviceFlags() const { return mService.serviceFlags(); } } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/���������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016225�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/CMakeLists.txt�������������������������������������������������������0000664�0000000�0000000�00000000207�15162662266�0020764�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Add sources required by all concrete backends. add_subdirectory(common) # Add platform-specific sources. add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017515�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/CMakeLists.txt������������������������������������������������0000664�0000000�0000000�00000001136�15162662266�0022256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE database_builder.cpp directory_inode.cpp file_cache.cpp file_extension_db.cpp file_info.cpp file_inode.cpp file_io_context.cpp inode_cache.cpp inode.cpp inode_db.cpp mount.cpp mount_db.cpp ) add_subdirectory(mega) add_subdirectory(testing) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/database_builder.cpp������������������������������������������0000664�0000000�0000000�00000036320�15162662266�0023477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/database.h> #include <mega/common/database_utilities.h> #include <mega/common/logging.h> #include <mega/common/query.h> #include <mega/common/transaction.h> #include <mega/fuse/common/database_builder.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/mount_info.h> #include <algorithm> #include <cassert> #include <map> #include <vector> namespace mega { namespace fuse { using namespace common; static void downgrade10(Query& query); static void downgrade21(Query& query); static void downgrade32(Query& query); static void downgrade43(Query& query); static void downgrade54(Query& query); static void upgrade01(Query& query); static void upgrade12(Query& query); static void upgrade23(Query& query); static void upgrade34(Query& query); static void upgrade45(Query& query); const DatabaseVersionVector& DatabaseBuilder::versions() const { static const DatabaseVersionVector versions = { {&downgrade10, &upgrade01}, {&downgrade21, &upgrade12}, {&downgrade32, &upgrade23}, {&downgrade43, &upgrade34}, {&downgrade54, &upgrade45}, }; // versions return versions; } DatabaseBuilder::DatabaseBuilder(Database& database): common::DatabaseBuilder(database) {} void downgrade10(Query& query) { query = "drop table inode_id"; query.execute(); query = "drop table inodes"; query.execute(); query = "drop table mounts"; query.execute(); } void downgrade21(Query& query) { // Inode table loses its bind_handle field. query = "create table inodes_new ( " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Migrate existing inode data to our new table. query = "insert into inodes_new " "select handle " " , id " " , modified " " , name " " , parent_handle " " from inodes"; query.execute(); // Replace the old inodes table with our new one. query = "drop table inodes"; query.execute(); query = "alter table inodes_new rename to inodes"; query.execute(); } void downgrade32(Query& query) { query = "alter table inodes drop column extension"; query.execute(); } void downgrade43(Query& query) { // Tracks which mounts we're migrating. MountInfoSet<MountInfoPathLess> mounts; // Mounts become keyed on path rather than name. // // Mounts with the same path will be dropped. query = "create table mounts_new ( " " enable_at_startup integer " " constraint nn_mounts_enable_at_startup " " not null, " " id integer " " constraint nn_mounts_id " " not null, " " name text " " constraint nn_mounts_name " " not null, " " path text " " constraint nn_mounts_path " " not null, " " persistent integer " " constraint nn_mounts_persistent " " not null, " " read_only integer " " constraint nn_mounts_read_only " " not null, " " constraint pk_mounts " " primary key (path) " ")"; query.execute(); // Figure out which mounts we want to migrate. query = "select * from mounts"; for (query.execute(); query; ++query) { // Add a mount to the set only if it has a path. if (!query.field("path").null()) mounts.emplace(MountInfo::deserialize(query)); } // Add mounts back into the database. query = "insert into mounts_new values ( " " :enable_at_startup, " " :id, " " :name, " " :path, " " :persistent, " " :read_only " ")"; for (auto i = mounts.begin(); i != mounts.end(); ++i) { // Reset any parameter bindings. query.reset(); // Write mount into the database. i->serialize(query); // Add the mount. query.execute(); } // Replace the old mounts table with our new one. query = "drop table mounts"; query.execute(); query = "alter table mounts_new rename to mounts"; query.execute(); } void downgrade54(Query& query) { // Add a new inode table with a bind_handle column. query = "create table inodes_new ( " " bind_handle text " " constraint uq_inodes_bind_handle " " unique, " " extension text, " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Migrate inodes over to the new table. query = "insert into inodes_new " "select null " " , extension" " , handle " " , id " " , modified " " , name " " , parent_handle " " from inodes"; query.execute(); // Replace the old inodes table with our new one. query = "drop table inodes"; query.execute(); query = "alter table inodes_new rename to inodes"; query.execute(); } void upgrade01(Query& query) { // Tracks all inodes with local state. query = "create table inodes ( " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Records what the next synthetic inode ID should be. query = "create table inode_id ( " " next integer " " constraint nn_inode_id_next " " not null, " " constraint pk_inode_id " " primary key (next) " ")"; query.execute(); // Initially, no IDs have been allocated. query = "insert into inode_id values (:value)"; query.param(":value").set(InodeID(0)); query.execute(); // Tracks all mounts known by FUSE. query = "create table mounts ( " " enable_at_startup integer " " constraint nn_mounts_enable_at_startup " " not null, " " id integer " " constraint nn_mounts_id " " not null, " " name text " " constraint nn_mounts_name " " not null, " " path text " " constraint nn_mounts_path " " not null, " " persistent integer " " constraint nn_mounts_persistent " " not null, " " read_only integer " " constraint nn_mounts_read_only " " not null, " " constraint pk_mounts " " primary key (path) " ")"; query.execute(); } void upgrade12(Query& query) { // Add a new inodes table with a bind_handle column. query = "create table inodes_new ( " " bind_handle text " " constraint uq_inodes_bind_handle " " unique, " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Migrate existing inode data to our new table. query = "insert into inodes_new " "select null " " , handle " " , id " " , modified " " , name " " , parent_handle " " from inodes"; query.execute(); // Replace the old inodes table with our new one. query = "drop table inodes"; query.execute(); query = "alter table inodes_new rename to inodes"; query.execute(); } void upgrade23(Query& query) { // Add a new inodes table with an extension column. query = "create table inodes_new ( " " bind_handle text " " constraint uq_inodes_bind_handle " " unique, " " extension text, " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Migrate existing inode data to our new table. query = "insert into inodes_new " "select bind_handle " " , null " " , handle " " , id " " , modified " " , name " " , parent_handle " " from inodes"; query.execute(); // Replace the old inodes table with our new one. query = "drop table inodes"; query.execute(); query = "alter table inodes_new rename to inodes"; query.execute(); } void upgrade34(Query& query) { // The mounts table will now be keyed on name. query = "create table mounts_new ( " " enable_at_startup integer " " constraint nn_mounts_enable_at_startup " " not null, " " id integer " " constraint nn_mounts_id " " not null, " " name text " " constraint nn_mounts_name " " not null, " " path text, " " persistent integer " " constraint nn_mounts_persistent " " not null, " " read_only integer " " constraint nn_mounts_read_only " " not null, " " constraint pk_mounts " " primary key (name) " ")"; // Create the new table. query.execute(); // Tracks the mounts we are migrating. std::vector<MountInfo> mounts; // Tracks how many times a given name has been used. std::map<std::string, std::size_t> occurances; // Migrate existing mounts, resolving name conflicts as necessary. query = "select * from mounts"; for (query.execute(); query; ++query) { // Latch the mount's description. mounts.emplace_back(MountInfo::deserialize(query)); // Convenience. auto& name = mounts.back().mFlags.mName; // Remember that we've seen this mount's name. auto i = occurances.emplace(name, 0u); // We've seen this name before. while (!i.second) { // generate a new name by adding a counter. name += " (" + std::to_string(++i.first->second) + ")"; // make sure our new name is unique. i = occurances.emplace(name, 0u); } } // Add mounts back into the database. query = "insert into mounts_new values ( " " :enable_at_startup, " " :id, " " :name, " " :path, " " :persistent, " " :read_only " ")"; while (!mounts.empty()) { // Reset any bound query parameters. query.reset(); // Write the mount's info to the database. mounts.back().serialize(query); // Add the mount. query.execute(); // Mount's been added. mounts.pop_back(); } // Replace the old mounts table with our new one. query = "drop table mounts"; query.execute(); query = "alter table mounts_new rename to mounts"; query.execute(); } void upgrade45(Query& query) { // Add a new inodes table without a bind_handle column. query = "create table inodes_new ( " " extension text, " " handle integer " " constraint uq_inodes_handle " " unique, " " id integer " " constraint nn_inodes_id " " not null, " " modified integer " " constraint nn_inodes_modified " " not null, " " name text, " " parent_handle integer, " " constraint pk_inodes " " primary key (id), " " constraint uq_inodes_name_parent_handle " " unique (name, parent_handle) " ")"; query.execute(); // Mark inodes that were being bound as modified. query = "update inodes " " set modified = 1 " " where bind_handle is not null"; query.execute(); // Migrate existing inodes over to our new table. query = "insert into inodes_new " "select extension" " , handle " " , id " " , modified" " , name " " , parent_handle " " from inodes"; query.execute(); // Replace the old inodes table with our new one. query = "drop table inodes"; query.execute(); query = "alter table inodes_new rename to inodes"; query.execute(); } } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/directory_inode.cpp�������������������������������������������0000664�0000000�0000000�00000023052�15162662266�0023405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/fuse/common/any_lock.h> #include <mega/fuse/common/any_lock_set.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/constants.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/file_move_flag.h> #include <mega/fuse/common/inode_badge.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/ref.h> #include <cassert> #include <mutex> namespace mega { namespace fuse { using namespace common; template<typename Maker> ErrorOr<MakeInodeResult> DirectoryInode::make(Maker&& maker, const std::string& name) { InodeLock guard(*this); // Invalid name. if (name.empty()) return unexpected(API_EARGS); // Name's too long. if (name.size() > MaxNameLength) return unexpected(API_FUSE_ENAMETOOLONG); auto permissions = this->permissions(); // Parent doesn't exist. if (permissions == ACCESS_UNKNOWN) return unexpected(API_ENOENT); // Parent's read only. if (permissions != FULL) return unexpected(API_FUSE_EROFS); // Parent already has a child with this name. if (hasChild(name)) return unexpected(API_EEXIST); // Try and make the new child. return maker(name); } void DirectoryInode::remove(RefBadge, InodeDBLock lock) { // Remove this directory from the database. mInodeDB.remove(*this, std::move(lock)); } DirectoryInode::DirectoryInode(InodeID id, const NodeInfo& info, InodeDB& inodeDB): Inode(id, info, inodeDB) {} DirectoryInode::~DirectoryInode() {} bool DirectoryInode::cached() const { return false; } InodeRefVector DirectoryInode::children() const { InodeLock guard(*this); // Ask the Inode DB what children we contain. return mInodeDB.children(*this); } DirectoryInodeRef DirectoryInode::directory() { return DirectoryInodeRef(this); } InodeRef DirectoryInode::get(const std::string& name) const { InodeLock guard(*this); // Ask the Inode DB if we have a child with this name. return mInodeDB.child(*this, name); } NodeHandle DirectoryInode::handle() const { return static_cast<NodeHandle>(mID); } bool DirectoryInode::hasChild(const std::string& name) const { InodeLock guard(*this); // Ask the Inode DB if we contain a child with this name. return mInodeDB.hasChild(*this, name); } ErrorOr<bool> DirectoryInode::hasChildren() const { InodeLock guard(*this); // Ask the Inode DB if we contain any children. return mInodeDB.hasChildren(*this); } void DirectoryInode::info(const NodeInfo& info) { InodeDBLock guard(mInodeDB); // Sanity. assert(!info.mHandle.isUndef()); assert(info.mIsDirectory); // Update this directory's cached description. Inode::info(info, guard); } InodeInfo DirectoryInode::info() const { // Acquire lock. InodeDBLock lock(mInodeDB); // No special gymnastics are needed if we've been removed. if (removed()) { InodeInfo info; // Just return cached information. info.mID = mID; info.mIsDirectory = true; info.mModified = mModified; info.mName = mName; info.mParentID = InodeID(mParentHandle); info.mPermissions = mPermissions; info.mSize = 4096; return info; } // Retrieve info and update cached state. while (true) { // Latch cached state. auto lastModified = mModified; auto lastName = mName; auto lastParentHandle = mParentHandle; auto lastPermissions = mPermissions; // Release lock so we can call the client. lock.unlock(); // Try and retrieve the latest info on ourselves. auto info = mInodeDB.client().get(handle()); // Reacquire lock. lock.lock(); // Another thread's updated our cached state. if (lastModified != mModified || lastParentHandle != mParentHandle || lastPermissions != mPermissions || lastName != mName) continue; // We no longer exist. if (!info) { InodeInfo info_; // Populate info based on last known state. info_.mID = mID; info_.mIsDirectory = true; info_.mModified = mModified; info_.mName = std::move(lastName); info_.mParentID = InodeID(mParentHandle); info_.mPermissions = mPermissions; info_.mSize = 4096; // Mark ourselves as removed. removed(true); // Return our description to the caller. return info_; } // Update cached state. const_cast<DirectoryInode*>(this)->Inode::info(*info, lock); // Return info to caller. return InodeInfo(mID, std::move(*info)); } } ErrorOr<MakeInodeResult> DirectoryInode::makeDirectory(const platform::Mount& mount, const std::string& name) { return make( [&](const std::string& name) { return mInodeDB.makeDirectory(mount, name, DirectoryInodeRef(this)); }, name); } ErrorOr<MakeInodeResult> DirectoryInode::makeFile(const platform::Mount& mount, const std::string& name) { return make( [&](const std::string& name) { return mInodeDB.makeFile(mount, name, DirectoryInodeRef(this)); }, name); } Error DirectoryInode::move(const std::string& name, const std::string& newName, DirectoryInodeRef newParent, FileMoveFlags flags) { // Sanity. assert(newParent); // Invalid names. if (name.size() > MaxNameLength || newName.size() > MaxNameLength) return API_FUSE_ENAMETOOLONG; // Easy management of multiple locks. AnyLockSet locks; // Lock parents. locks.emplace(*this); locks.emplace(*newParent); locks.lock(); // Does the child we're moving exist? auto source = get(name); // Child doesn't exist. if (!source) return API_ENOENT; // Does our new parent already have a child with our desired name? auto target = newParent->get(newName); // Release parent locks. locks.unlock(); locks.emplace(*source); // New parent contains a child with the desired name. if (target) { // But the caller doesn't want to replace it. if ((flags & FILE_MOVE_NO_REPLACE)) return API_EEXIST; locks.emplace(*target); // Reacquire locks. locks.lock(); // Perform replace. return source->replace(InodeBadge(), std::move(target), newName, newParent); } // The caller wanted to replace the target. if ((flags & FILE_MOVE_EXCHANGE)) return API_ENOENT; locks.lock(); // Perform move. return source->move(InodeBadge(), newName, std::move(newParent)); } Error DirectoryInode::move(InodeBadge, const std::string& name, DirectoryInodeRef parent) { // Sanity. assert(parent); return mInodeDB.move(InodeRef(this), name, std::move(parent)); } Error DirectoryInode::replace(InodeBadge, InodeRef other, const std::string& otherName, DirectoryInodeRef otherParent) { // Sanity. assert(other); assert(otherParent); // Are we replacing a directory? auto otherDirectory = other->directory(); // Directories can't replace files. if (!otherDirectory) return API_FUSE_ENOTDIR; // Is the directory we're replacing empty? auto result = otherDirectory->hasChildren(); // Other directory has been removed. if (!result) return API_ENOENT; // Other directory isn't empty. if (*result) return API_FUSE_ENOTEMPTY; // Perform the replacement. return mInodeDB.replace(DirectoryInodeRef(this), std::move(otherDirectory), otherName, std::move(otherParent)); } Error DirectoryInode::unlink(const std::string& name, std::function<Error(InodeRef)> predicate) { // Invalid name. if (name.size() > MaxNameLength) return API_FUSE_ENAMETOOLONG; // Do we have a child with this name? auto child = get(name); // No child with this name. if (!child) return API_ENOENT; // Lock the child. InodeLock childLock(*child); auto permissions = child->permissions(); // Child no longer exists. if (permissions == ACCESS_UNKNOWN) return API_ENOENT; // Child's read only. if (permissions != FULL) return API_FUSE_EROFS; auto result = API_OK; // Perform extra checks, if necessary. if (predicate) result = predicate(child); // Unlink the child. if (result == API_OK) result = child->unlink(InodeBadge()); // Return result to caller. return result; } Error DirectoryInode::unlink(InodeBadge) { auto result = hasChildren(); // Can't unlink a directory that doesn't exist. if (!result) return API_ENOENT; // Can't unlink a directory that isn't empty. if (*result) return API_FUSE_ENOTEMPTY; // Unlink the directory. return mInodeDB.unlink(InodeRef(this)); } void doRef(RefBadge badge, DirectoryInode& inode) { doRef(badge, static_cast<Inode&>(inode)); } void doUnref(RefBadge badge, DirectoryInode& inode) { doUnref(badge, static_cast<Inode&>(inode)); } } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/file_cache.cpp������������������������������������������������0000664�0000000�0000000�00000030422�15162662266�0022264�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/client.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_info.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/service_context.h> #include <mega/scoped_helpers.h> #include <atomic> #include <cassert> #include <chrono> #include <mutex> #include <stdexcept> #include <tuple> #include <utility> namespace mega { namespace fuse { using namespace common; static LocalPath cachePath(const Client& client); static void ensureCachePathExists(Client& client, const LocalPath& path); ErrorOr<FileInfoRef> FileCache::create(const FileExtension& extension, const LocalPath& path, InodeID id, FileAccessSharedPtr* fileAccess, bool create) { // Sanity. assert(!path.empty()); assert(id); // Acquire cache lock. FileCacheLock guard(*this); // More sanity. assert(!mInfoByID.count(id)); // So we can access the filesystem. auto fileAccess_ = client().fsAccess().newfileaccess(false); // Can't create a file to store the file's content. // // Open for reading only if create is false. // Open for writing only if create is true. const auto flag = create ? OPEN_WRONLY : OPEN_RDONLY; if (!fileAccess_->fopen(path, flag, FSLogging::logOnError)) return unexpected(API_EWRITE); // Make sure the file's attributes have been loaded. if (!fileAccess_->fstat()) return unexpected(API_EWRITE); // Create and populate a new file description. auto info = this->info(extension, *fileAccess_, id); // Caller isn't interested in the file access object. if (!fileAccess) return info; // Close file. fileAccess_->fclose(); // Couldn't reopen the file for reading and writing. if (!fileAccess_->fopen(path, OPEN_RDWR, FSLogging::logOnError)) return unexpected(API_EWRITE); // Transfer ownership of file access to caller. fileAccess->reset(fileAccess_.release()); // Return file description to caller. return info; } FileInfoRef FileCache::info(const FileExtension& extension, const FileAccess& fileAccess, InodeID id) { FileCacheLock guard(*this); // Does the cache already know about this inode? auto i = mInfoByID.find(id); // Cache already knows about this inode. if (i != mInfoByID.end()) return FileInfoRef(i->second.get()); // Instantiate a file info representing this inode. auto info = std::make_unique<FileInfo>(extension, fileAccess, *this, id); // Add the info to the index. i = mInfoByID.emplace(id, std::move(info)).first; // Return a reference to the caller. return FileInfoRef(i->second.get()); } void FileCache::purge() { // Convenience. auto& fsAccess = client().fsAccess(); auto dirAccess = fsAccess.newdiraccess(); auto path = mCachePath; // Try and open the cache directory for iteration. if (!dirAccess->dopen(&path, nullptr, false)) return; LocalPath name; nodetype_t type; // Iterate over each file the cache. while (dirAccess->dnext(path, name, false, &type)) { // Entry isn't a file. if (type != FILENODE) continue; // Convert file name to inode ID. auto id = InodeID::fromFileName(name.toPath(false)); // Invalid ID. if (!id) continue; // Inode's still present in the database. if (mContext.mInodeDB.exists(id)) continue; LocalPath newPath{path}; newPath.appendWithSeparator(name, true); // Try and remove the file. if (!fsAccess.unlinklocal(newPath)) FUSEWarningF("Couldn't remove stale cache file: %s", newPath.toPath(false).c_str()); } } void FileCache::remove(const FileIOContext& context, FileCacheLock lock) { // Get our hands on the context's pointer. auto i = mContextByID.find(context.id()); // Sanity. assert(i != mContextByID.end()); // Latch the context's pointer. auto ptr = std::move(i->second); // Remove the context from the index. mContextByID.erase(i); // Let any waiters know that a context's been removed. mRemoved.notify_all(); // Release the lock. lock.unlock(); // Release the context. ptr.reset(); } void FileCache::remove(const FileInfo& info, FileCacheLock lock) { // Get our hands on the info's pointer. auto i = mInfoByID.find(info.id()); // Sanity. assert(i != mInfoByID.end()); // Latch the info's pointer. auto ptr = std::move(i->second); // Remove the info from the index. mInfoByID.erase(i); // Release the lock. lock.unlock(); // Release the info. ptr.reset(); } FileCache::FileCache(platform::ServiceContext& context): Lockable(), mContextByID(), mInfoByID(), mRemoved(), mCachePath(cachePath(context.client())), mContext(context) { FUSEDebug1("File Cache constructed"); ensureCachePathExists(client(), mCachePath); } FileCache::~FileCache() { assert(mContextByID.empty()); assert(mInfoByID.empty()); FUSEDebug1("File Cache destroyed"); } void FileCache::cancel() { // What contexts currently exist? auto contexts = ([this]() { // Acquire lock. FileCacheLock guard(*this); FileIOContextRefVector contexts; // Reserve space for the contexts. contexts.reserve(mContextByID.size()); // Latch contexts. for (auto& c : mContextByID) contexts.emplace_back(c.second.get()); // Return contexts to caller. return contexts; })(); FUSEDebug1("Waiting for file IO contexts to be purged from memory"); // Iterate over contexts, cancelling any pending flushes. while (!contexts.empty()) { contexts.back()->cancel(true); contexts.pop_back(); } // True when all of the contexts have been destroyed. auto empty = [this]() { return mContextByID.empty(); }; // empty FileCacheLock lock(*this); // Wait for all of the contexts to be destroyed. mRemoved.wait(lock, empty); FUSEDebug1("File IO contexts have been purged from memory"); } Client& FileCache::client() const { return mContext.client(); } FileIOContextRef FileCache::context(FileInodeRef file, bool inMemoryOnly) const { assert(file); // Convenience. auto extension = file->extension(); auto id = file->id(); // Determine whether the file was previously modified. auto modified = file->wasModified(); // Acquire lock. FileCacheLock guard(*this); // Do we already have a context for this file in memory? auto c = mContextByID.find(id); // Context's already in memory. if (c != mContextByID.end()) return FileIOContextRef(c->second.get()); // Context's not in memory and we don't want to create it. if (inMemoryOnly) return FileIOContextRef(); // Instantiate a context to represent this file. auto ptr = std::make_unique<FileIOContext>(const_cast<FileCache&>(*this), std::move(file), info(extension, id, true), modified); // Add the context to our index. c = mContextByID.emplace(id, std::move(ptr)).first; // Return context to caller. return FileIOContextRef(c->second.get()); } ErrorOr<FileInfoRef> FileCache::create(const FileExtension& extension, const LocalPath& path, InodeID id, FileAccessSharedPtr* fileAccess) { // Sanity. assert(id); // Try and create a new description based on a file in the cache. return create(extension, path, id, fileAccess, false); } ErrorOr<FileInfoRef> FileCache::create(const FileExtension& extension, InodeID id, FileAccessSharedPtr* fileAccess, LocalPath* filePath) { // Sanity. assert(id); // Compute to the path to the inode's content. auto path = this->path(extension, id); // Try and create a new file to contain the inode's content. auto result = create(extension, path, id, fileAccess, true); // Couldn't create the file. if (!result) return result; // Latch the file's path if requested. if (filePath) *filePath = std::move(path); // Return description to caller. return result; } void FileCache::current() { // Preventive mFolderLocker.reset(); // Purge any unreferenced files in the cache. purge(); // Prevent others, especially file explorer, from opening files under the folder, generating // thumbnail while we're running. We have seen we're blocked to open files forever due to this. mFolderLocker = common::platform::FolderLocker{mCachePath.asPlatformEncoded(true)}; } TaskExecutor& FileCache::executor() const { return mContext.mExecutor; } void FileCache::flush(const Mount& mount, FileInodeRefVector inodes) { // Flush each inode to the cloud. while (!inodes.empty()) { // Grab the inode. auto inode = std::move(inodes.back()); // Pop the stack. inodes.pop_back(); // Get our hands on a suitable IO context. auto context = this->context(std::move(inode)); // Queue the inode for upload. context->modified(mount); } } FileInfoRef FileCache::info(const FileExtension& extension, InodeID id, bool inMemoryOnly) const { FileCacheLock guard(*this); // Is info about this inode already in memory? auto i = mInfoByID.find(id); // Info's already in memory. if (i != mInfoByID.end()) return FileInfoRef(i->second.get()); // Info isn't in memory and we don't want to create it. if (inMemoryOnly) return FileInfoRef(); auto fileAccess = client().fsAccess().newfileaccess(false); auto filePath = path(extension, id); // File doesn't exist or couldn't be accessed. if (!fileAccess->fopen(filePath, OPEN_RDONLY, FSLogging::eNoLogging)) return FileInfoRef(); // File isn't actually a file. assert(fileAccess->type == FILENODE); // Track the file's info. auto info = std::make_unique<FileInfo>(extension, *fileAccess, const_cast<FileCache&>(*this), id); // Add info to the index. i = mInfoByID.emplace(id, std::move(info)).first; // Return info to the caller. return FileInfoRef(i->second.get()); } LocalPath FileCache::path(const FileExtension& extension, InodeID id) const { auto name = LocalPath::fromRelativePath(toFileName(id)); auto path = mCachePath; path.appendWithSeparator(name, false); path.append(LocalPath::fromRelativePath(extension)); // path.append(LocalPath::fromRelativePath(".3b7a")); return path; } void FileCache::remove(const FileExtension& extension, InodeID id) { // Sanity. assert(id); // Acquire lock. FileCacheLock guard(*this); // Is the file being manipulated? auto c = mContextByID.find(id); // Can't remove the file if it's being manipulated. if (c != mContextByID.end()) return c->second->cancel(); // Can't remove the file if its description is in memory. if (mInfoByID.count(id)) return; // Try and remove the file. client().fsAccess().unlinklocal(path(extension, id)); } LocalPath cachePath(const Client& client) { auto name = LocalPath::fromRelativePath("fuse-cache"); auto path = client.dbRootPath(); path.appendWithSeparator(name, false); return path; } void ensureCachePathExists(Client& client, const LocalPath& path) { auto& fsAccess = client.fsAccess(); // Directory's been created or already existed. if (fsAccess.mkdirlocal(path, false, false) || fsAccess.target_exists) return; throw FUSEErrorF("Unable to create file cache directory: %s", path.toPath(false).c_str()); } } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/file_extension_db.cpp�����������������������������������������0000664�0000000�0000000�00000005011�15162662266�0023676�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/file_extension_db.h> #include <mega/utils.h> namespace mega { namespace fuse { FileExtension::FileExtension(FileExtensionDB& db, FromStringMap<std::size_t>::iterator iterator): mDB(&db), mIterator(iterator) {} FileExtension::FileExtension(): mDB(nullptr), mIterator() {} FileExtension::FileExtension(const FileExtension& other): mDB(other.mDB), mIterator(other.mIterator) { if (mDB) mDB->ref(mIterator); } FileExtension::FileExtension(FileExtension&& other): mDB(other.mDB), mIterator(other.mIterator) { other.mDB = nullptr; } FileExtension::~FileExtension() { if (mDB) mDB->unref(mIterator); } FileExtension::operator const std::string&() const { return get(); } FileExtension& FileExtension::operator=(const FileExtension& rhs) { FileExtension temp(rhs); swap(temp); return *this; } FileExtension& FileExtension::operator=(FileExtension&& rhs) { FileExtension temp(std::move(rhs)); swap(temp); return *this; } const std::string& FileExtension::get() const { static const std::string empty; if (mDB) return mIterator->first; return empty; } void FileExtension::swap(FileExtension& other) { using std::swap; swap(mDB, other.mDB); swap(mIterator, other.mIterator); } void swap(FileExtension& lhs, FileExtension& rhs) { lhs.swap(rhs); } void FileExtensionDB::ref(FromStringMap<std::size_t>::iterator iterator) { std::lock_guard<std::mutex> guard(mLock); ++iterator->second; } void FileExtensionDB::unref(FromStringMap<std::size_t>::iterator iterator) { std::lock_guard<std::mutex> guard(mLock); if (!--iterator->second) mExtensions.erase(iterator); } FileExtension FileExtensionDB::get(const std::string& extension) { // Empty extension. if (extension.empty()) return FileExtension(); std::lock_guard<std::mutex> guard(mLock); // Does our index already contain this extension? auto i = mExtensions.lower_bound(extension); // Add extension as necessary. if (i == mExtensions.end() || i->first != extension) i = mExtensions.emplace_hint(i, extension, 0); // Increment extension's reference count. ++i->second; // Return extension to caller. return FileExtension(*this, i); } FileExtension FileExtensionDB::getFromPath(const std::string& path) { // Empty path. if (path.empty()) return FileExtension(); // Add extension to index. return get(extensionOf(path)); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/file_info.cpp�������������������������������������������������0000664�0000000�0000000�00000005603�15162662266�0022157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_info.h> #include <cassert> namespace mega { namespace fuse { using namespace common; FileInfo::FileInfo(const FileExtension& extension, const FileAccess& fileAccess, FileCache& fileCache, InodeID id): mExtension(extension), mFileCache(fileCache), mID(id), mLock(), mModified(fileAccess.mtime), mReferences(0), mSize(fileAccess.size) {} FileInfo::~FileInfo() = default; FileExtension FileInfo::extension() const { return mExtension; } void FileInfo::get(m_time_t& modified, m_off_t& size) const { std::lock_guard<std::mutex> guard(mLock); modified = mModified; size = mSize; } InodeID FileInfo::id() const { return mID; } m_time_t FileInfo::modified() const { std::lock_guard<std::mutex> guard(mLock); return mModified; } ErrorOr<FileAccessSharedPtr> FileInfo::open(LocalPath& path) const { // Convenience. auto& fsAccess = mFileCache.client().fsAccess(); // Create a new file access object. auto fileAccess = fsAccess.newfileaccess(false); // Compute the file's path. auto path_ = this->path(); // Couldn't open the file for reading and writing. if (!fileAccess->fopen(path_, OPEN_RDWR, FSLogging::logOnError)) return unexpected(API_EREAD); // Make sure the file's attributes have been loaded. if (!fileAccess->fstat()) return unexpected(API_EREAD); // Sanity check. { std::lock_guard<std::mutex> guard(mLock); assert(fileAccess->mtime == mModified); assert(fileAccess->size == mSize); } // Pass path to caller. path = std::move(path_); // Return file access to caller. return fileAccess.release(); } LocalPath FileInfo::path() const { return mFileCache.path(mExtension, mID); } void FileInfo::ref(RefBadge) { // Make sure no one else touches our counter. FileCacheLock lock(mFileCache); // Increment our reference count. ++mReferences; // Sanity. assert(mReferences); } void FileInfo::unref(RefBadge) { // Make sure no one else touches our counter. FileCacheLock lock(mFileCache); // Sanity. assert(mReferences); // Decrement the counter. --mReferences; // Remove the info as all references have been dropped. if (!mReferences) mFileCache.remove(*this, std::move(lock)); } m_off_t FileInfo::size() const { std::lock_guard<std::mutex> guard(mLock); return mSize; } void FileInfo::set(m_time_t modified, m_off_t size) { std::lock_guard<std::mutex> guard(mLock); mModified = modified; mSize = size; } void doRef(RefBadge badge, FileInfo& info) { info.ref(badge); } void doUnref(RefBadge badge, FileInfo& info) { info.unref(badge); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/file_inode.cpp������������������������������������������������0000664�0000000�0000000�00000023027�15162662266�0022322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_info.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/file_open_flag.h> #include <mega/fuse/common/inode_badge.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/file_context.h> #include <cassert> namespace mega { namespace fuse { using namespace common; void FileInode::remove(RefBadge, InodeDBLock lock) { // Remove this file from the inode database. mInodeDB.remove(*this, std::move(lock)); } FileInode::FileInode(InodeID id, const NodeInfo& info, InodeDB& inodeDB): Inode(id, info, inodeDB), mHandle(info.mHandle), mInfo(), mInfoLock() {} FileInode::~FileInode() {} bool FileInode::cached() const { return fileInfo(); } FileExtension FileInode::extension() const { // Retrieve extension from file info, if present. { std::lock_guard<std::mutex> guard(mInfoLock); if (mInfo) return mInfo->extension(); } // Compute extension based on current file name. return mInodeDB.fileExtensionDB().getFromPath(name()); } FileInodeRef FileInode::file() { return FileInodeRef(this); } void FileInode::fileInfo(FileInfoRef info) { // Sanity. assert(info); std::lock_guard<std::mutex> guard(mInfoLock); // A file's info should only be injected once. assert(!mInfo); mInfo = std::move(info); } FileInfoRef FileInode::fileInfo() const { std::lock_guard<std::mutex> guard(mInfoLock); return mInfo; } void FileInode::handle(NodeHandle handle) { InodeDBLock guard(mInodeDB); // Sanity. assert(!handle.isUndef()); // Update the database. mInodeDB.handle(*this, mHandle, handle); } NodeHandle FileInode::handle() const { InodeDBLock guard(mInodeDB); return mHandle; } void FileInode::info(const NodeInfo& info) { InodeDBLock guard(mInodeDB); // Sanity. assert(!info.mHandle.isUndef()); assert(!info.mIsDirectory); // Update this inode's cached description. Inode::info(info, guard); // Update this inode's handle. mInodeDB.handle(*this, mHandle, info.mHandle); } InodeInfo FileInode::info() const { // Retrieve a reference to this inode's local state, if any. auto fileInfo = this->fileInfo(); // Acquire lock. InodeDBLock lock(mInodeDB); // Return cached information if we've been removed. if (removed()) { InodeInfo info; info.mID = mID; info.mIsDirectory = false; info.mName = mName; info.mModified = mModified; info.mParentID = InodeID(mParentHandle); info.mPermissions = mPermissions; // Latch local state, if any. if (fileInfo) fileInfo->get(info.mModified, info.mSize); return info; } // Convenience. auto& client = mInodeDB.client(); while (true) { // Where we will store our description. auto info = NodeInfo(); // Latch cached attributes. info.mHandle = mHandle; info.mModified = mModified; info.mName = mName; info.mParentHandle = mParentHandle; info.mPermissions = mPermissions; info.mIsDirectory = false; // We existed in the cloud. if (!info.mHandle.isUndef()) { // Release the lock so we can call the client. lock.unlock(); // Retrieve this inode's local state, if any. if (!fileInfo) fileInfo = this->fileInfo(); // Try and retrieve our description. auto info_ = client.get(info.mHandle); // Reacquire lock. lock.lock(); // Another thread's altered our attributes. if (info.mHandle != mHandle || info.mModified != mModified || info.mParentHandle != mParentHandle || info.mPermissions != mPermissions || info.mName != mName) continue; // We no longer exist. if (!info_) { // Latch local state, if any. if (fileInfo) fileInfo->get(info.mModified, info.mSize); // Mark ourselves as removed if necessary. if (info_.error() == API_ENOENT) removed(true); // Return our description to the caller. return InodeInfo(mID, std::move(info)); } // Latch latest description from cloud. info = std::move(*info_); // Latch local state, if any. if (fileInfo) fileInfo->get(info.mModified, info.mSize); // Update our cached attributes. const_cast<FileInode*>(this)->Inode::info(info, lock); // Return description to caller. return InodeInfo(mID, std::move(info)); } // We don't exist in the cloud. // Release the lock so we can call the client. lock.unlock(); // Try and retrieve our parent's permissions. auto permissions = client.permissions(info.mParentHandle).valueOr(ACCESS_UNKNOWN); // Reacquire lock. lock.lock(); // We've been uploaded by another thread. if (info.mHandle != mHandle) continue; // Another thread's changed our attributes. if (info.mModified != mModified || info.mParentHandle != mParentHandle || info.mPermissions != mPermissions || info.mName != mName) continue; // Sanity. assert(fileInfo); // Latch local state. fileInfo->get(info.mModified, info.mSize); // Latch current permissions. if (permissions != ACCESS_UNKNOWN) info.mPermissions = permissions; // Update cached attributes. mModified = info.mModified; mPermissions = info.mPermissions; // Return description to caller. return InodeInfo(mID, std::move(info)); } } void FileInode::modified(bool modified) { mInodeDB.modified(id(), modified); } Error FileInode::move(InodeBadge, const std::string& name, DirectoryInodeRef parent) { // Sanity. assert(parent); // Ask the Inode DB to move us into parent. return mInodeDB.move(FileInodeRef(this), name, std::move(parent)); } ErrorOr<platform::FileContextPtr> FileInode::open(Mount& mount, FileOpenFlags flags) { // Are we opening the file for writing? auto writable = (flags & FOF_WRITABLE) > 0; // File's read-only. if (writable && permissions() != FULL) return unexpected(API_FUSE_EROFS); // Get a reference to ourself. auto ref = FileInodeRef(this); // Get a reference to our file context. auto context = mInodeDB.fileCache().context(std::move(ref)); // Does the user want to truncate the file? auto truncate = (flags & FOF_TRUNCATE) > 0; // Sanity. assert(!truncate || writable); // Try and open the file for IO. auto result = context->open(mount, truncate); // Couldn't open the file. if (result != API_OK) return unexpected(result); // File's open. return std::make_unique<platform::FileContext>(std::move(context), mount, flags); } Error FileInode::replace(InodeBadge, InodeRef other, const std::string& otherName, DirectoryInodeRef otherParent) { assert(other); assert(otherParent); // Are we replacing a file? auto otherFile = other->file(); // A file can't replace a directory. if (!otherFile) return API_FUSE_EISDIR; // Ask the Inode DB to replace the other file with us. return mInodeDB.replace(FileInodeRef(this), std::move(otherFile), otherName, std::move(otherParent)); } Error FileInode::touch(const Mount& mount, m_time_t modified) { // Lock the file so no one else can alter us. InodeLock lock(*this); // Get our hands on our context. auto context = mInodeDB.fileCache().context(FileInodeRef(this)); // Try and open the file for IO. auto result = context->open(mount, false); // Try and update the file's modification time. if (result == API_OK) result = context->touch(mount, modified); // Return result to caller. return result; } Error FileInode::truncate(const Mount& mount, m_off_t size, bool dontGrow) { // Lock the file so no one else can alter us. InodeLock lock(*this); // Get our hands on our context. auto context = mInodeDB.fileCache().context(FileInodeRef(this)); // Open the file for IO. auto result = context->open(mount, !size); // Couldn't open the file for IO. if (result != API_OK) return result; // Truncate the file if necessary. if (size) result = context->truncate(mount, size, dontGrow); // Return result to caller. return result; } Error FileInode::unlink(InodeBadge) { return mInodeDB.unlink(FileInodeRef(this)); } bool FileInode::wasModified() const { return mInodeDB.modified(id()); } void doRef(RefBadge badge, FileInode& inode) { doRef(badge, static_cast<Inode&>(inode)); } void doUnref(RefBadge badge, FileInode& inode) { doUnref(badge, static_cast<Inode&>(inode)); } } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/file_io_context.cpp�������������������������������������������0000664�0000000�0000000�00000063333�15162662266�0023403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/lock.h> #include <mega/common/node_info.h> #include <mega/common/task_executor.h> #include <mega/common/upload.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_info.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/service_flags.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/service_context.h> #include <cassert> #include <chrono> #include <condition_variable> #include <future> #include <utility> namespace mega { namespace common { using fuse::FileIOContext; using fuse::toString; void LockableTraits<FileIOContext>::acquired(const FileIOContext& context) { FUSEDebugF("Acquired lock on file IO context %s", toString(context.id()).c_str()); } void LockableTraits<FileIOContext>::acquiring(const FileIOContext& context) { FUSEDebugF("Acquiring lock on file IO context %s", toString(context.id()).c_str()); } void LockableTraits<FileIOContext>::couldntAcquire(const FileIOContext& context) { FUSEDebugF("Couldn't acquire lock on file IO context %s", toString(context.id()).c_str()); } void LockableTraits<FileIOContext>::released(const FileIOContext& context) { FUSEDebugF("Released lock on file IO context %s", toString(context.id()).c_str()); } void LockableTraits<FileIOContext>::tryAcquire(const FileIOContext& context) { FUSEDebugF("Trying to acquire lock on file IO context %s", toString(context.id()).c_str()); } } // common namespace fuse { using namespace common; class FileIOContext::FlushContext { // The client which will perform our upload. Client& client() const; // The InodeDB that describes the inode we are uploading. InodeDB& inodeDB() const; // Called when our content has been uploaded to the cloud. void uploaded(ErrorOr<UploadResult> result); // Wakes any threads waiting for the upload's result. mutable std::condition_variable mCV; // The context whose content we are uploading. FileIOContext& mContext; // Serializes access to instance members. mutable std::mutex mLock; // The actual upload used to send content to the cloud. UploadPtr mUpload; public: FlushContext(FileIOContext& context, LocalPath logicalPath); // Try and cancel any upload in progress. bool cancel(); // Retrieve the upload's result. Error result() const; }; // FlushContext ErrorOr<FileAccessSharedPtr> FileIOContext::create() { // Sanity. assert(mFileAccess.expired()); assert(!mFileInfo); assert(mFilePath.empty()); FileAccessSharedPtr fileAccess; // Create an empty file in the cache. auto info = mFileCache.create(mFile->extension(), mFile->id(), &fileAccess, &mFilePath); // Couldn't create the file. if (!info) return unexpected(info.error()); // File's been successfully created. mFileInfo = std::move(*info); // Inject description into inode. mFile->fileInfo(mFileInfo); // Inode has a local presence: Make sure it's in the database. inodeDB().add(*mFile); // Make file access object visible to other threads. mFileAccess = fileAccess; // Return file access object to caller. return fileAccess; } ErrorOr<FileAccessSharedPtr> FileIOContext::download(const Mount& mount) { // Sanity. assert(!mFile->handle().isUndef()); assert(mFileAccess.expired()); assert(!mFileInfo); assert(mFilePath.empty()); // Convenience. auto& client = mFileCache.client(); // So we can wait for the client's result. std::promise<Error> waiter; // Transmits the client's result to our waiter. auto wrapper = [&waiter](Error result) { waiter.set_value(result); }; // wrapper // Convenience. auto extension = mFile->extension(); auto id = mFile->id(); // What node is this file associated with? auto handle = mFile->handle(); // Where should we say we downloaded the file's content? auto logicalPath = ([&]() { // Compute the file's path relative to the mount. auto logicalPath = mFile->path(mount.handle()); // File's present under the mount. if (logicalPath) logicalPath->prependWithSeparator(mount.path()); // Return logical path to caller. return logicalPath; })(); // File's no longer present under the mount. if (!logicalPath) return unexpected(API_EREAD); // Where should we download the file's content? auto path = mFileCache.path(extension, id); // Ask the client to download our content. client.download(std::move(wrapper), handle, std::move(*logicalPath), path); // Wait for the file to be downloaded. auto result = waiter.get_future().get(); // Couldn't download the file. if (result != API_OK) return unexpected(result); FileAccessSharedPtr fileAccess; // Try and retrieve this file's description. auto info = mFileCache.create(extension, path, id, &fileAccess); // Couldn't retrieve this file's description. if (!info) return unexpected(info.error()); // File's been successfully downloaded. mFileInfo = std::move(*info); mFilePath = std::move(path); // Inject description into inode. mFile->fileInfo(mFileInfo); // Inode has a local presence: Make sure it's in the database. inodeDB().add(*mFile); // Make file access object visible to other threads. mFileAccess = fileAccess; // Return file access object to caller. return fileAccess; } std::chrono::seconds FileIOContext::flushDelay() const { return mFileCache.mContext.serviceFlags().mFlushDelay; } InodeDB& FileIOContext::inodeDB() const { return mFileCache.mContext.mInodeDB; } Error FileIOContext::manualFlush([[maybe_unused]] FileIOContextSharedLock& contextLock, std::unique_lock<std::mutex>& flushLock, NodeHandle mountHandle, LocalPath mountPath) { // Sanity. assert(contextLock.owns_lock()); assert(flushLock.owns_lock()); // Content's already been flushed. if (!mFlushNeeded) return API_OK; // Flush context doesn't exist. if (!mFlushContext) { // Compute the inode's path relative to the mount. auto filePath = mFile->path(mountHandle); // File's no longer below this mount. if (!filePath) return API_OK; // Add the inode's path to the mount's. mountPath.appendWithSeparator(*filePath, false); // Instantiate a new flush context. mFlushContext = std::make_shared<FlushContext>(*this, std::move(mountPath)); } // Retrieve flush context. auto context = mFlushContext; // Release flush lock. flushLock.unlock(); // Wait for the flush to complete. auto result = context->result(); // Reacquire flush lock. flushLock.lock(); // Another thread completed the flush. // // This is possible because several threads might be in this function at // the exact same time. One way that this could happen is if the user // executes the sync(...) system call, at the same time, via several // different programs. // // Note mFlushLock above: We acquire it, only as long as necessary to // create (or retrieve) the mFlushContext. Once we have that context, we // release the lock and then wait for the flush's result. // // When we get that result, we then try to reacquire the lock but by the // time we've actually acquired the lock, another thread - also waiting // on the flush's result - may have already completed flush processing. // // When we want to clear the flush context, we need to make sure that // any context that is live, is exactly the same as the one we're trying // to release because it's possible that the flush we were waiting has // failed and been replaced by another flush that's in progress. // // That's the rationale for the below :) if (mFlushContext != context) return result; // Clear flush context. mFlushContext.reset(); // We were unable to flush the content to the cloud. if (result != API_OK) return result; // Content's been flushed to the cloud. if (mFlushNeeded) mFile->modified(false); mFlushNeeded = false; // Return result to caller. return result; } void FileIOContext::onPeriodicFlush(FileIOContextRef& context, m_time_t lastModified, NodeHandle mountHandle, LocalPath& mountPath, const Task& task) { // Sanity. assert(context); // Acquire context lock. FileIOContextSharedLock contextLock(*this); // Acquire flush lock. std::unique_lock<std::mutex> flushLock(mFlushLock); // Flush has been cancelled or is no longer necessary. if (task.cancelled() || !mFlushNeeded) return; // When was our content last modified? auto modified = mFileInfo->modified(); // Content was modified since the flush was queued. if (lastModified != modified) { // Another task will flush the modifications. if (mPeriodicFlushTask != task) return; // Reschedule the flush for later. mPeriodicFlushTask = mFileCache.executor().execute(std::bind(&FileIOContext::onPeriodicFlush, this, std::move(context), modified, mountHandle, std::move(mountPath), std::placeholders::_1), flushDelay(), true); // We're all done for now. return; } // Perform the flush. manualFlush(contextLock, flushLock, mountHandle, std::move(mountPath)); // Sanity. assert(contextLock.owns_lock()); assert(flushLock.owns_lock()); // Clear flush task if necessary. if (mPeriodicFlushTask == task) mPeriodicFlushTask.reset(); } auto FileIOContext::open([[maybe_unused]] FileIOContextLock& lock, const Mount& mount, m_off_t hint) -> ErrorOr<FileAccessSharedPtr> { // Sanity. assert(lock.owns_lock()); // Inode's already open. if (auto fileAccess = mFileAccess.lock()) return fileAccess; // Inode has no local file. if (!mFileInfo) { // Don't download a file just to truncate it. if (!hint) return create(); // Can't download a file that doesn't exist. if (mFile->removed()) return create(); // File's content must be downloaded. return download(mount); } // Inode has a local file. // // Try and open the file for reading and writing. auto fileAccess = mFileInfo->open(mFilePath); // Couldn't open the file. if (!fileAccess) return unexpected(fileAccess.error()); // Make file visible to other threads. mFileAccess = *fileAccess; // Let the caller know the file's open. return std::move(*fileAccess); } auto FileIOContext::open(FileIOContextSharedLock& shared, const Mount& mount, m_off_t hint) -> ErrorOr<FileAccessSharedPtr> { // Sanity. assert(shared.owns_lock()); // Inode's already open. if (auto fileAccess = mFileAccess.lock()) return fileAccess; // Release shared lock. shared.unlock(); // Try and download file. auto result = ([&]() { // Acquire exclusive lock. auto exclusive = FileIOContextLock(*this); // Try and download the file. return open(exclusive, mount, hint); })(); // Reacquire shared lock. shared.lock(); // Return result to caller. return result; } FileIOContext::FileIOContext(FileCache& cache, FileInodeRef file, FileInfoRef info, bool modified): Lockable(), mFile(std::move(file)), mFileAccess(), mFileCache(cache), mFileInfo(std::move(info)), mFilePath(), mFlushContext(), mFlushLock(), mFlushNeeded(modified), mPeriodicFlushTask(), mReferences(0u) { assert(mFile); FUSEDebugF("File Context constructed: %s", toString(mFile->id()).c_str()); } FileIOContext::~FileIOContext() { FUSEDebugF("File Context destroyed: %s", toString(mFile->id()).c_str()); } void FileIOContext::cancel(bool pendingFlush) { Task flushTask; // Cancel upload and latch periodic flush. { // Acquire flush lock. std::lock_guard<std::mutex> guard(mFlushLock); // Cancel any upload in progress. if (mFlushContext && mFlushContext->cancel()) mFlushContext.reset(); // Latch periodic flush. if (pendingFlush) flushTask = std::move(mPeriodicFlushTask); } // Cancel periodic flush, if any. flushTask.cancel(); } void FileIOContext::cancel() { auto cancel = [&](FileIOContextRef&, const Task& task) { if (!task.cancelled()) this->cancel(true); }; // cancel mFileCache.executor().execute( std::bind(std::move(cancel), FileIOContextRef(this), std::placeholders::_1), true); } InodeID FileIOContext::id() const { // Sanity. assert(mFile); return mFile->id(); } FileInodeRef FileIOContext::file() const { return mFile; } Error FileIOContext::manualFlush(const Mount& mount) { // Acquire context lock. FileIOContextSharedLock contextLock(*this); // Can't flush a file that couldn't have been modified. if (!mFileInfo) return API_OK; // Acquire flush lock. std::unique_lock<std::mutex> flushLock(mFlushLock); // Try and perform the flush. auto result = manualFlush(contextLock, flushLock, mount.handle(), mount.path()); // Try and cancel any periodic flush. auto flushTask = std::move(mPeriodicFlushTask); // Release flush lock. flushLock.unlock(); // Cancel periodic flush. flushTask.cancel(); // Return result to caller. return result; } void FileIOContext::modified(const Mount& mount) { // Acquire flush lock. std::lock_guard<std::mutex> guard(mFlushLock); // Mark file as having been modified. if (!mFlushNeeded) mFile->modified(true); mFlushNeeded = true; // A flush has already been queued. if (mPeriodicFlushTask && !mPeriodicFlushTask.completed()) return; // Queue a periodic flush. mPeriodicFlushTask = mFileCache.executor().execute(std::bind(&FileIOContext::onPeriodicFlush, this, FileIOContextRef(this), mFileInfo->modified(), mount.handle(), mount.path(), std::placeholders::_1), flushDelay(), true); } Error FileIOContext::open(const Mount& mount, bool truncate) { // Truncate existing content. if (truncate || mFile->removed()) return this->truncate(mount, 0, false); // Update file's access time. mFile->accessed(); // File's open. return API_OK; } ErrorOr<std::string> FileIOContext::read(const Mount& mount, m_off_t offset, unsigned int size) { assert(offset >= 0); assert(size); // Update file's access time. mFile->accessed(); // Make sure nothing else is touching this file. FileIOContextSharedLock guard(*this); // Make sure the file's present and open. auto result = open(guard, mount); // Couldn't download (or open) the file. if (!result) return unexpected(result.error()); auto fileAccess = std::move(*result); // Sanity. assert(fileAccess); assert(mFileInfo); // Clamp offset. offset = std::min(offset, fileAccess->size); // How much data can actually be read? auto remaining = fileAccess->size - offset; // Clamp size. size = std::min(static_cast<unsigned int>(remaining), size); // No data available for reading. if (!size) return std::string(); std::string buffer; // Couldn't read from the file. if (!fileAccess->fread(&buffer, size, 0, offset, FSLogging::logOnError)) return unexpected(API_EREAD); // Return result to caller. return buffer; } void FileIOContext::ref(RefBadge) { // Make sure nothing else touches our reference count. FileCacheLock lock(mFileCache); // Increase the number of references. ++mReferences; // Sanity. assert(mReferences); } m_off_t FileIOContext::size() const { FileIOContextLock guard(*this); if (mFileInfo) return mFileInfo->size(); return mFile->info().mSize; } Error FileIOContext::touch(const Mount& mount, m_time_t modified) { // Update file's access time. mFile->accessed(); // Try and cancel any pending flush. cancel(false); // Make sure no one else is altering this file. FileIOContextLock guard(*this); // Convenience. auto& client = mFileCache.client(); // File exists only in the cloud. if (!mFileInfo) return client.touch(mFile->handle(), modified); // File's modification time hasn't changed. if (mFileInfo->modified() == modified) return API_OK; // Open the file if necessary. auto result = open(guard, mount); // Couldn't download or open the file. if (!result) return result.error(); auto fileAccess = std::move(*result); // Couldn't update the file's modification time. if (!client.fsAccess().setmtimelocal(mFilePath, modified)) return API_EWRITE; // Couldn't get the file's info. if (!fileAccess->fstat()) return API_EWRITE; // Update the file's info. mFileInfo->set(fileAccess->mtime, fileAccess->size); // Invalidate the file's attributes. mFileCache.mContext.mMountDB.each( [&](Mount& mount) { mount.invalidateAttributes(mFile->id()); }); // Mark the file as having been modified. this->modified(mount); // File's modification time has been updated. return API_OK; } Error FileIOContext::truncate(const Mount& mount, m_off_t size, bool dontGrow) { // Sanity. assert(size >= 0); // Update file's access time. mFile->accessed(); // Try and cancel any pending flush. cancel(false); // Make sure no one else is altering this file. FileIOContextLock lock(*this); // Make sure the file's been opened. auto result = open(lock, mount, size); // Couldn't open the file. if (!result) return result.error(); auto fileAccess = std::move(*result); // Sanity. assert(fileAccess); assert(mFileInfo); // Caller doesn't want to extend the file's size. if (dontGrow) size = std::min(fileAccess->size, size); // Couldn't truncate the file. if (!fileAccess->ftruncate(size)) return API_EWRITE; // Couldn't get the file's info. if (!fileAccess->fstat()) return API_EWRITE; // Latch the file's previous size. auto previousSize = mFileInfo->size(); // Update the file's info. mFileInfo->set(fileAccess->mtime, fileAccess->size); // For capture. auto id = mFile->id(); // Invalidate the file's attributes and data. mFileCache.mContext.mMountDB.each( [=](Mount& mount) { mount.invalidateAttributes(id); // Inavlidate data only if necessary. if (size < previousSize) mount.invalidateData(id, size, previousSize - size); }); // Mark the file as having been modified. modified(mount); // We're all done. return API_OK; } void FileIOContext::unref(RefBadge) { // Make sure nothing else touches our reference counter. FileCacheLock lock(mFileCache); // Decrement the number of references. --mReferences; // All references have been dropped. if (!mReferences) mFileCache.remove(*this, std::move(lock)); } ErrorOr<std::size_t> FileIOContext::write(const Mount& mount, const void* data, m_off_t length, m_off_t offset, bool noGrow) { // Update file's access time. mFile->accessed(); // Try and cancel any pending flush. cancel(false); // Make sure no one else touches the file. FileIOContextLock guard(*this); // Make sure the file exists and has been opened. auto result = open(guard, mount); // Couldn't download or open the file. if (!result) return result.error(); auto fileAccess = std::move(*result); // Sanity. assert(fileAccess); assert(mFileInfo); // Retrieve the file's current size. auto size = mFileInfo->size(); // Data's being appended to the end of the file. if (offset < 0) offset = size; // Caller doesn't want the file's size to change. if (noGrow) { offset = std::min(offset, size); length = std::min(offset + length, size) - offset; } // Writing nothing is always successful. if (!length) return 0u; // Convenience. auto data_ = reinterpret_cast<const byte*>(data); auto length_ = static_cast<unsigned int>(length); // Couldn't write the data to disk. if (!fileAccess->fwrite(data_, length_, offset)) return API_EWRITE; // Couldn't get the file's info. if (!fileAccess->fstat()) return API_EWRITE; // Update the file's info. mFileInfo->set(fileAccess->mtime, fileAccess->size); // Invalidate the file's attributes and data. mFileCache.mContext.mMountDB.each( [&](Mount& mount) { mount.invalidateAttributes(mFile->id()); mount.invalidateData(mFile->id(), offset, length); }); // Mark the file as having been modified. modified(mount); // Let the user know whether the write succeeded. return static_cast<std::size_t>(length); } void doRef(RefBadge badge, FileIOContext& entry) { entry.ref(badge); } void doUnref(RefBadge badge, FileIOContext& entry) { entry.unref(badge); } Client& FileIOContext::FlushContext::client() const { return mContext.mFileCache.client(); } InodeDB& FileIOContext::FlushContext::inodeDB() const { return mContext.inodeDB(); } void FileIOContext::FlushContext::uploaded(ErrorOr<UploadResult> result) { // Acquire lock. std::lock_guard<std::mutex> guard(mLock); // Couldn't upload the file's content. if (!result) return mCV.notify_all(); // The file we were uploading has been removed. if (mContext.mFile->removed()) { mUpload->cancel(); mCV.notify_all(); return; } // Extract bind callback and bind handle. auto bind = std::move(*result); // Sanity. assert(bind); // Called when we've bound a name to our uploaded content. auto bound = [this](ErrorOr<NodeHandle> result) { // A name's been bound to our content. if (result) { // Get the file's updated information. auto info = client().get(*result); // Sanity. assert(info.errorOr(API_OK) == API_OK); // Update the file's information. mContext.mFile->info(*info); } // Let waiters know the upload's complete. mCV.notify_all(); }; // bound // Try and bind a name to our uploaded content. bind(std::move(bound), mContext.mFile->handle()); } FileIOContext::FlushContext::FlushContext(FileIOContext& context, LocalPath logicalPath): mCV(), mContext(context), mLock(), mUpload() { // Sanity. assert(mContext.mFile); // Retrieve the content's current name and parent. auto info = mContext.mFile->info(); // Compute the file's path if necessary. auto filePath = mContext.mFilePath; // Path hasn't been generated yet. if (filePath.empty()) filePath = mContext.mFileInfo->path(); // Sanity. assert(!filePath.empty()); assert(!logicalPath.empty()); // Create our upload. mUpload = client().upload(std::move(logicalPath), info.mName, static_cast<NodeHandle>(info.mParentID), std::move(filePath)); // Wrap our uploaded(...) method so we can use it as a callback. UploadCallback uploaded = std::bind(&FlushContext::uploaded, this, std::placeholders::_1); // Try and upload our content. mUpload->begin(std::move(uploaded)); } bool FileIOContext::FlushContext::cancel() { // Acquire lock. std::lock_guard<std::mutex> guard(mLock); // Try and cancel the upload. mUpload->cancel(); // Let the caller know if the upload was cancelled. return mUpload->cancelled(); } Error FileIOContext::FlushContext::result() const { // Acquire lock. std::unique_lock<std::mutex> lock(mLock); // Waits for the upload to complete. auto uploaded = [&]() { return mUpload->completed(); }; // completed // Wait for the upload to complete. mCV.wait(lock, std::move(uploaded)); // Return the upload's result to the caller. return mUpload->result(); } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/inode.cpp�����������������������������������������������������0000664�0000000�0000000�00000026065�15162662266�0021330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/fuse/common/any_lock.h> #include <mega/fuse/common/any_lock_set.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_badge.h> #include <mega/fuse/common/inode_cache.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/mount.h> #include <cassert> namespace mega { namespace common { using fuse::Inode; using fuse::toString; void LockableTraits<Inode>::acquiring(const Inode& inode) { FUSEDebugF("Acquiring lock on inode %s", toString(inode.id()).c_str()); } void LockableTraits<Inode>::acquired(const Inode& inode) { FUSEDebugF("Acquired lock on inode %s", toString(inode.id()).c_str()); } void LockableTraits<Inode>::couldntAcquire(const Inode& inode) { FUSEDebugF("Couldn't acquire lock on inode %s", toString(inode.id()).c_str()); } void LockableTraits<Inode>::released(const Inode& inode) { FUSEDebugF("Releasing lock on inode %s", toString(inode.id()).c_str()); } void LockableTraits<Inode>::tryAcquire(const Inode& inode) { FUSEDebugF("Trying to acquire lock on inode %s", toString(inode.id()).c_str()); } } // common namespace fuse { using namespace common; void Inode::moved([[maybe_unused]] InodeDBLock& lock, const std::string& name, NodeHandle parentHandle) { // Sanity. assert(lock.owns_lock()); // The node's been marked as removed. if (mRemoved) { // So just update the node's name and parent. mName = name; mParentHandle = parentHandle; return; } // Node's no longer present under mParentHandle as mName; mInodeDB.childRemoved(*this, mName, mParentHandle); // Update the node's name and parent. mName = name; mParentHandle = parentHandle; // Node's now visible as mName under mParentHandle. mInodeDB.childAdded(*this, mName, mParentHandle); } Inode::Inode(InodeID id, const NodeInfo& info, InodeDB& inodeDB): Lockable(), mReferences(0), mRemoved(false), mID(id), mInodeDB(inodeDB), mModified(info.mModified), mName(info.mName), mParentHandle(info.mParentHandle), mPermissions(info.mPermissions) { // We're claiming mName under mParentHandle. mInodeDB.childAdded(*this, mName, mParentHandle); } void Inode::info(const NodeInfo& info, InodeDBLock& lock) { // Inode's name or parent has changed. if (mParentHandle != info.mParentHandle || mName != info.mName) moved(lock, info.mName, info.mParentHandle); // Latch modification time and permissions. mModified = info.mModified; mPermissions = info.mPermissions; } Inode::~Inode() {} Inode* Inode::accessed() const { // The cache should only contain inodes that haven't been removed. if (!removed()) mInodeDB.cache().add(*this); // Return a reference to this inode in the name of convenience. return const_cast<Inode*>(this); } DirectoryInodeRef Inode::directory() { return DirectoryInodeRef(); } FileInodeRef Inode::file() { return FileInodeRef(); } InodeID Inode::id() const { return mID; } Error Inode::move(const std::string& name, DirectoryInodeRef targetParent) { // Sanity. assert(!name.empty()); assert(targetParent); while (true) { DirectoryInodeRef sourceParent; // For easy management of multiple locks. AnyLockSet locks; // Lock ourselves. locks.emplace(*this); locks.lock(); // Get a reference to our current parent. sourceParent = parent(); // Release locks. locks.unlock(); // Lock our parent. if (sourceParent) locks.emplace(*sourceParent); // Lock the target parent. locks.emplace(*targetParent); locks.lock(); // Make sure our parent hasn't changed. if (sourceParent != parent()) continue; // Target already has a child with this name. if (targetParent->get(name)) return API_EEXIST; // Try and move ourselves. return move(InodeBadge(), name, std::move(targetParent)); } } void Inode::moved(const std::string& name, NodeHandle parentHandle) { InodeDBLock lock(mInodeDB); // Sanity. assert(!mRemoved); // Update this inode's name and parent. moved(lock, name, parentHandle); } const std::string& Inode::name(CachedOnlyTag) const { InodeDBLock lock(mInodeDB); return mName; } std::string Inode::name() const { return info().mName; } DirectoryInodeRef Inode::parent() const { // Who is this inode's parent? auto parentHandle = this->parentHandle(); // We have no parent. if (parentHandle.isUndef()) return DirectoryInodeRef(); // Try and get a reference to our parent. auto ref = mInodeDB.get(parentHandle); // Parent exists. if (ref) return ref->directory(); // Either we have no parent or it has been removed. return DirectoryInodeRef(); } NodeHandle Inode::parentHandle(CachedOnlyTag) const { InodeDBLock lock(mInodeDB); return mParentHandle; } NodeHandle Inode::parentHandle() const { return NodeHandle(info().mParentID); } accesslevel_t Inode::permissions() const { return info().mPermissions; } ErrorOr<LocalPath> Inode::path(NodeHandle parentHandle) const { // For convenience. return path(static_cast<InodeID>(parentHandle)); } ErrorOr<LocalPath> Inode::path(InodeID parentID) const { // Stores the description of the current inode. auto info = InodeInfo(); // Stores the computed path of this inode. auto path = LocalPath(); // Ascend from this inode. info.mParentID = mID; // Ascend until we find parentID or hit the cloud root. while (true) { // We've reached parentID. if (info.mParentID == parentID) return path; // We've hit the cloud root. if (!info.mParentID) return unexpected(API_ENOENT); // Retrieve a reference to this inode's parent. auto ref = mInodeDB.get(info.mParentID); // Couldn't get a reference to this inode's parent. if (!ref) return unexpected(API_ENOENT); // Retrieve the parent's description. info = ref->info(); // Translate the parent's name to a local path. auto name = LocalPath::fromRelativePath(info.mName); // Prepend the parent's name to our path. path.prependWithSeparator(name); } } void Inode::ref(RefBadge) { // Make sure nothing else can mess with our counter. InodeDBLock guard(mInodeDB); // Increment the counter. ++mReferences; // Make sure our counter hasn't wrapped around. assert(mReferences); } void Inode::removed(bool removed) const { InodeDBLock guard(mInodeDB); // Nothing to do if our removal state has changed. if (mRemoved == removed) return; // Update our removal state. mRemoved = removed; // We've been removed. if (mRemoved) { FUSEDebugF("Setting removed flag on %s [%s] (%s)", mName.c_str(), toString(mID).c_str(), toString(InodeID(mParentHandle)).c_str()); // Remove the inode from the cache. mInodeDB.cache().remove(*this); // We no longer claim mName under mParentHandle. return mInodeDB.childRemoved(*this, mName, mParentHandle); } // We've been added. FUSEDebugF("Clearing removed flag on %s [%s] (%s)", mName.c_str(), toString(mID).c_str(), toString(InodeID(mParentHandle)).c_str()); // Add the inode to the cache. mInodeDB.cache().add(*this); // Claim mName under mParentHandle. mInodeDB.childAdded(*this, mName, mParentHandle); } bool Inode::removed() const { InodeDBLock guard(mInodeDB); return mRemoved; } Error Inode::replace(InodeRef target, bool replaceDirectories) { assert(target); // Is our target a directory? auto targetDirectory = target->directory(); // Is it legal for us to replace target? if (targetDirectory) { // We're a directory but the target's a file. if (file()) return API_FUSE_EISDIR; // Caller doesn't want to replace directories. if (!replaceDirectories) return API_EEXIST; } else if (directory()) { // We're a directory but the target's a file. return API_FUSE_ENOTDIR; } while (true) { auto sourceParent = parent(); auto targetParent = target->parent(); AnyLockSet locks; // Lock the parents. locks.emplace(*sourceParent); locks.emplace(*targetParent); // Lock ourselves and our target. locks.emplace(*this); locks.emplace(*target); // Parent's have changed. if (sourceParent != parent() || targetParent != target->parent()) continue; locks.lock(); // Our target's a directory. if (targetDirectory) { // And it must be empty. auto hasChildren = targetDirectory->hasChildren(); // Target's been removed. if (!hasChildren) return API_ENOENT; // Target's not empty. if (*hasChildren) return API_FUSE_ENOTEMPTY; } // Latch the target's name. auto targetName = target->name(); // Try and replace the target. return replace(InodeBadge(), target, std::move(targetName), targetParent); } } Error Inode::unlink() { while (true) { DirectoryInodeRef parent; // Easy management of multiple locks. AnyLockSet locks; // Lock ourselves. locks.emplace(*this); locks.lock(); // Lock our parent if necessary. if ((parent = this->parent())) { // Release locks. locks.unlock(); // Lock our parent. locks.emplace(*parent); locks.lock(); // Parent's changed. if (this->parent() != parent) continue; } // Try and unlink ourselves. return unlink(InodeBadge()); } } void Inode::unref(RefBadge badge) { // Make sure nothing else can mess with our counter. InodeDBLock lock(mInodeDB); // Make sure the counter's sane. assert(mReferences); // Decrement the counter. --mReferences; // Inode still has some references. if (mReferences) return; // All references to this inode have been dropped. // Inode's no longer associated with this name or parent. if (!mRemoved) mInodeDB.childRemoved(*this, mName, mParentHandle); // Remove the inode from the database. remove(badge, std::move(lock)); } void doRef(RefBadge badge, Inode& inode) { inode.ref(badge); } void doUnref(RefBadge badge, Inode& inode) { inode.unref(badge); } } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/inode_cache.cpp�����������������������������������������������0000664�0000000�0000000�00000013405�15162662266�0022445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/utility.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_cache.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/ref.h> #include <chrono> #include <functional> namespace mega { namespace fuse { // Convenience. using std::chrono::steady_clock; struct InodeCache::Entry { Entry(InodeRef&& inode): mAccessed(steady_clock::now()), mInode(std::move(inode)), mPosition() {} // When was the inode last accessed? steady_clock::time_point mAccessed; // What inode has been cached? InodeRef mInode; // Where is the inode in the cache's position map? EntryPositionMapIterator mPosition; }; // Entry; void InodeCache::loop() { // Convenience. auto& ageThreshold = mFlags.mCleanAgeThreshold; auto& interval = mFlags.mCleanInterval; auto& sizeThreshold = mFlags.mCleanSizeThreshold; FUSEDebug1("Inode Cache Cleaner thread started"); while (true) { // Stores references to any evicted inodes. auto evicted = InodeRefVector(); // Acquire lock. Lock lock(mLock); // Are we shutting down? if (mTerminate) break; // Wait until interval has passed or until we're notified. mCV.wait_for(lock, interval); // Try and reduce the cache's size. evicted = reduce(ageThreshold, lock, sizeThreshold); } FUSEDebug1("Inode Cache Cleaner thread stopped"); } InodeRefVector InodeCache::reduce(std::chrono::seconds age, Lock&, std::size_t size) { // For debugging. FUSEDebugF("Cleaning inode cache: age >= %lus, size > %lu", age.count(), size); // Which inodes are to be evicted? auto evicted = InodeRefVector(); // Convenience. auto now = steady_clock::now(); // How many inodes are in the cache? auto num = mEntries.size(); // Evict inodes until we store less than size. while (mEntries.size() > size) { // Select oldest inode in the cache. auto& entry = mEntries.back(); // We've evicted all inodes older than age. if (age.count() && age > now - entry.mAccessed) break; FUSEDebugF("Removing inode %s from inode cache", toString(entry.mInode->id()).c_str()); // Remove inode from the position map. mPositions.erase(entry.mPosition); // Take ownership of the entry's inode. evicted.emplace_back(std::move(entry.mInode)); // Remove inode from the cache. mEntries.pop_back(); } FUSEDebugF("Removed %lu/%lu inode(s) from the inode cache", evicted.size(), num); return evicted; } InodeCache::InodeCache(const InodeCacheFlags& flags): mCV(), mEntries(), mFlags(flags), mLock(), mPositions(), mTerminate{false}, mThread(&InodeCache::loop, this) { FUSEDebug1("Inode Cache constructed"); } InodeCache::~InodeCache() { // Let the cleaner know it should terminate. mTerminate = true; // Wake the cleaner if necessary. mCV.notify_one(); // Wait for the cleaner to terminate. mThread.join(); // We're done. FUSEDebug1("Inode Cache destroyed"); } bool InodeCache::add(const Inode& inode) { // Acquire a reference to this inode. InodeRef ref(const_cast<Inode*>(&inode)); // Lock the cache so no one else can modify it. Lock guard(mLock); // Convenience. auto e = mEntries.begin(); auto id = inode.id(); // Is the inode already in the cache? auto p = mPositions.find(id); // Inode's already in the cache. if (p != mPositions.end()) { // Mark inode as most recently accessed if necessary. if (e != p->second) mEntries.splice(e, mEntries, p->second); // Update inode's access time. p->second->mAccessed = steady_clock::now(); // Inode's been updated. return false; } // Add inode to the cache. e = mEntries.emplace(e, std::move(ref)); // For debugging. FUSEDebugF("Adding inode %s to inode cache", toString(id).c_str()); // Add inode to the position map. e->mPosition = mPositions.emplace(id, e).first; // Inode's been added to the cache. return true; } void InodeCache::clear() { EntryList entries; EntryPositionMap positions; // Acquire ownership of mEntries and mPositions. { // Acquire lock. std::lock_guard<std::mutex> guard(mLock); // Take ownership of mEntries and mPositions. entries = std::move(mEntries); positions = std::move(mPositions); } } void InodeCache::flags(const InodeCacheFlags& flags) { Lock guard(mLock); // Update the cache's flags. mFlags = flags; // Make sure the cleaner doesn't poll. if (!mFlags.mCleanInterval.count()) mFlags.mCleanInterval = std::chrono::seconds::max(); // Wake the cleaner so the flags take effect. mCV.notify_one(); } InodeCacheFlags InodeCache::flags() const { Lock guard(mLock); return mFlags; } bool InodeCache::remove(const Inode& inode) { // Lock the cache so no one else can modify it. Lock lock(mLock); // Convenience. auto id = inode.id(); // Is this inode in the cache? auto p = mPositions.find(id); // Inode isn't in the cache. if (p == mPositions.end()) return false; // For debugging. FUSEDebugF("Removing inode %s from inode cache", toString(id).c_str()); // Latch the inode reference. InodeRef ref(std::move(p->second->mInode)); // Remove the inode from the cache. mEntries.erase(p->second); // Remove inode from the position map. mPositions.erase(p); // Release the lock. lock.unlock(); // Drop the inode reference. ref.reset(); // Inode's been removed from the cache. return true; } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/inode_db.cpp��������������������������������������������������0000664�0000000�0000000�00000172140�15162662266�0021771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/database.h> #include <mega/common/error_or.h> #include <mega/common/node_event.h> #include <mega/common/node_event_queue.h> #include <mega/common/node_event_type.h> #include <mega/common/node_info.h> #include <mega/common/query.h> #include <mega/common/scoped_query.h> #include <mega/common/transaction.h> #include <mega/fuse/common/any_lock.h> #include <mega/fuse/common/any_lock_set.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_info.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_db.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/path_adapter.h> #include <mega/fuse/platform/service_context.h> #include <mega/utils.h> #include <cassert> #include <chrono> #include <map> #include <tuple> namespace mega { namespace fuse { using namespace common; class InodeDB::EventObserver { // Called when a node's been added. void added(const NodeEvent& event); // Retrieve a reference to the database. Database& database() const; // Retrieve a reference to the file cache. FileCache& fileCache() const; // Retrieve a reference to the file extension DB. FileExtensionDB& fileExtensionDB() const; // Called when a node's been modified. void modified(const NodeEvent& event); // Retrieve a reference to the mount DB. MountDB& mountDB() const; // Called when a node's been moved or renamed. void moved(const NodeEvent& event); // Called when a node's permissions have changed. void permissions(const NodeEvent& event); // Retrieve a reference to the InodeDB's queries. auto queries() const -> Queries&; // Called when a node's been removed. void removed(const NodeEvent& event); // The InodeDB we're processing events for. InodeDB& mInodeDB; // Ensures exclusive access to the database. DatabaseLock mDatabaseLock; // Ensures exclusive access to the inode DB. InodeDBLock mInodeDBLock; // So we can perform queries. Transaction mTransaction; public: EventObserver(InodeDB& inodeDB); ~EventObserver(); // Process the specified node events. void operator()(NodeEventQueue& events); }; // EventObserver InodeDB::Queries::Queries(Database& database): mAddInode(database.query()), mGetChildrenByParentHandle(database.query()), mGetExtensionAndInodeIDByHandle(database.query()), mGetExtensionAndInodeIDByNameAndParentHandle(database.query()), mGetHandleByID(database.query()), mGetInodeByHandle(database.query()), mGetInodeByID(database.query()), mGetInodeIDByHandle(database.query()), mGetInodeIDByNameAndParentHandle(database.query()), mGetInodeIDByParentHandle(database.query()), mGetModifiedByID(database.query()), mGetModifiedInodes(database.query()), mGetNextInodeID(database.query()), mIncrementNextInodeID(database.query()), mRemoveInodeByID(database.query()), mSetHandleNameParentHandleByID(database.query()), mSetModifiedByID(database.query()), mSetNameParentHandleByID(database.query()) { mAddInode = "insert into inodes values ( " " :extension, " " :handle, " " :id, " " :modified, " " :name, " " :parent_handle " ")"; mGetChildrenByParentHandle = "select extension " " , handle " " , id " " , name " " from inodes " " where parent_handle = :parent_handle"; mGetExtensionAndInodeIDByHandle = "select extension " " , id " " from inodes " " where handle = :handle"; mGetExtensionAndInodeIDByNameAndParentHandle = "select extension " " , id " " from inodes " " where name = :name " " and parent_handle = :parent_handle"; mGetHandleByID = "select handle from inodes where id = :id"; mGetInodeByHandle = "select * " " from inodes " " where handle = :handle"; mGetInodeByID = "select * " " from inodes " " where id = :id"; mGetInodeIDByHandle = "select id " " from inodes " " where handle = :handle;"; mGetInodeIDByNameAndParentHandle = "select id " " from inodes " " where name = :name " " and parent_handle = :parent_handle"; mGetInodeIDByParentHandle = "select id " " from inodes " " where parent_handle = :parent_handle"; mGetModifiedByID = "select modified from inodes where id = :id"; mGetModifiedInodes = "select * " " from inodes " " where modified = 1 " " and (handle is not null " " or parent_handle is not null)"; mGetNextInodeID = "select next from inode_id"; mIncrementNextInodeID = "update inode_id set next = next + 1"; mRemoveInodeByID = "delete from inodes where id = :id"; mSetHandleNameParentHandleByID = "update inodes " " set handle = :handle " " , name = :name " " , parent_handle = :parent_handle " " where id = :id"; mSetModifiedByID = "update inodes " " set modified = :modified" " where id = :id"; mSetNameParentHandleByID = "update inodes " " set name = :name " " , parent_handle = :parent_handle " " where id = :id"; } InodeRef InodeDB::add(InodePtr (InodeDB::*build)(const NodeInfo&), const NodeInfo& info) { // Convenience. auto id = InodeID(info.mHandle); auto handle = info.mHandle; // Sanity. assert(build); assert(!handle.isUndef()); assert(!mByHandle.count(handle)); assert(!mByID.count(id)); // Instantiate a new inode. auto ptr = (this->*build)(info); // Add the inode to the index. auto h = mByHandle.emplace(handle, ptr.get()).first; mByID.emplace(id, std::move(ptr)); // Return inode to caller. return InodeRef(h->second->accessed()); } InodeID InodeDB::addFile(const FileExtension& extension, const std::string& name, NodeHandle parentHandle, Transaction& transaction) { auto query = transaction.query(mQueries.mGetNextInodeID); // Allocate a new inode ID. query.execute(); auto id = query.field("next").get<InodeID>(); // Make sure we haven't wrapped around. assert(id); // Mark ID as having been allocated. query = transaction.query(mQueries.mIncrementNextInodeID); query.execute(); // Add the file to the database. query = transaction.query(mQueries.mAddInode); query.param(":extension").set(extension.get()); query.param(":handle").set(nullptr); query.param(":id").set(id); query.param(":modified").set(false); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // Return new ID to caller. return id; } InodePtr InodeDB::buildDirectory(const NodeInfo& info) { return std::make_unique<DirectoryInode>(InodeID(info.mHandle), info, *this); } InodePtr InodeDB::buildFile(const NodeInfo& info) { return std::make_unique<FileInode>(InodeID(info.mHandle), info, *this); } InodeRef InodeDB::child(const DirectoryInode& parent, const std::string& name) const { // Sanity. assert(!name.empty()); // Check if the parent contains a child with this name. auto childID = hasChild(parent, name); // Parent has a child with this name. if (childID) return get(childID); // Parent doesn't have a child with this name. return InodeRef(); } InodeRef InodeDB::child(const std::string& name, NodeHandle parentHandle, MemoryOnlyTag) const { InodeDBLock guard(*this); // Is there an inode in memory at this location? auto p = std::make_pair(parentHandle, &name); auto i = mByParentHandleAndName.find(p); // An inode's in memory at this location. if (i != mByParentHandleAndName.end()) return InodeRef(i->second->accessed()); // No inode's in memory at this location. return InodeRef(); } void InodeDB::childAdded(const Inode& inode, const std::string& name, NodeHandle parentHandle) { auto pair = std::make_pair(parentHandle, &name); // Does a child with this name already exist under this parent? auto i = mByParentHandleAndName.find(pair); // Child already exists: consider it replaced. if (i != mByParentHandleAndName.end()) i->second->removed(true); // Add ourselves to the map. auto result = mByParentHandleAndName.emplace(std::piecewise_construct, std::forward_as_tuple(parentHandle, &name), std::forward_as_tuple(const_cast<Inode*>(&inode))); // Make sure the name isn't already occupied under this parent. assert(result.second); // Silence the compiler. static_cast<void>(result); } void InodeDB::childRemoved([[maybe_unused]] const Inode& inode, const std::string& name, NodeHandle parentHandle) { // Convenience. auto pair = std::make_pair(parentHandle, &name); // What entry are we in the map? auto i = mByParentHandleAndName.find(pair); // Does this entry describe us? assert(i != mByParentHandleAndName.end() && i->second == &inode); // Remove ourselves from the map. mByParentHandleAndName.erase(i); } InodeRefVector InodeDB::children(const DirectoryInode& parent) const { // Convenience. using StringToNodeInfoPtrMap = std::map<std::string, NodeInfoList::iterator>; // Stores the description of each of this node's children. NodeInfoList storage; // Maps a child's name to its description. StringToNodeInfoPtrMap descriptions; // Insert dummy for purposes of duplicate detection. storage.emplace_back(); // What children are present in the cloud? client().each( [&](NodeInfo description) { // Have we seen a child with this name before? auto i = descriptions.find(description.mName); // Haven't seen a child with this name before. if (i == descriptions.end()) { auto j = storage.end(); // Latch the child's description. j = storage.emplace(j, std::move(description)); // Add child to index. descriptions[j->mName] = j; // Process the next child. return; } // We've already detected a duplicate with this name. if (i->second == storage.begin()) return; // Remove existing child's description. storage.erase(i->second); // Mark name as a duplicate. i->second = storage.begin(); }, parent.handle()); auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetChildrenByParentHandle); // What children are present on disk? query.param(":parent_handle").set(parent.handle()); query.execute(); // What children need to be loaded? struct PendingChild { PendingChild(FileExtension extension, std::string name, InodeID id): mExtension(std::move(extension)), mName(std::move(name)), mID(id) {} // The child's extension. FileExtension mExtension; // The child's name. std::string mName; // The child's ID. InodeID mID; }; // PendingChild using PendingChildVector = std::vector<PendingChild>; // Children waiting to be loaded. PendingChildVector pending; // What children have been removed or replaced? InodeIDVector removed; for (; query; ++query) { auto id = query.field("id").get<InodeID>(); auto name = query.field("name").get<std::string>(); // Have we already seen a cloud child with this name? auto i = descriptions.find(name); // A child with this name is present in the cloud. if (i != descriptions.end()) { // Child was a duplicate. if (i->second == storage.begin()) continue; // Cloud child has replaced this local child. removed.emplace_back(id); // Process next local child. continue; } // Child exists only locally. pending.emplace_back(fileExtensionDB().get(query.field("extension").get<std::string>()), std::move(name), id); } InodeRefVector children; // Convenience. auto& self = const_cast<InodeDB&>(*this); // Pop dummy marker. storage.pop_front(); // Prepare query. query = transaction.query(mQueries.mGetExtensionAndInodeIDByHandle); // Instantiate cloud children. while (!storage.empty()) { // Latch child's description. auto info = std::move(storage.front()); // Pop description. storage.pop_front(); // Instantiate child. children.emplace_back(( [&]() { // Is the child already in memory? auto h = mByHandle.find(info.mHandle); // Child's already in memory. if (h != mByHandle.end()) return InodeRef(h->second->accessed()); // Child's a directory. if (info.mIsDirectory) return self.add(&InodeDB::buildDirectory, info); query.reset(); // Check if child's in the file cache. query.param(":handle").set(info.mHandle); query.execute(); // Child's not in the file cache. if (!query) return self.add(&InodeDB::buildFile, info); // Convenience. auto extension = fileExtensionDB().get(query.field("extension").get<std::string>()); auto id = query.field("id").get<InodeID>(); // Try and get our hands on the file's info. auto fileInfo = fileCache().info(extension, id); // File's been removed from the cache. if (!fileInfo) { // Remember to purge the stale record. removed.emplace_back(id); // Return new child instance. return self.add(&InodeDB::buildFile, info); } // Instantiate child. auto ptr = std::make_unique<FileInode>(id, info, self); // Inject file info. ptr->fileInfo(std::move(fileInfo)); // Establish reference to new child. auto ref = InodeRef(ptr->accessed()); // Add child to index. mByHandle.emplace(info.mHandle, ptr.get()); mByID.emplace(id, std::move(ptr)); // Return new child instance. return ref; })()); } // Instantiate local children. while (!pending.empty()) { // Convenience. auto& child = pending.back(); // Pop child's name and ID. auto extension = std::move(child.mExtension); auto name = std::move(child.mName); auto id = child.mID; pending.pop_back(); // Is the child already in memory? auto i = mByID.find(id); // Child's already in memory. if (i != mByID.end()) { // Add child to vector. children.emplace_back(i->second->accessed()); // Process next child. continue; } // Try and get our hands on the file's info. auto fileInfo = fileCache().info(extension, id); // File's been removed from the cache. if (!fileInfo) { // Remember to purge stale record. removed.emplace_back(id); // Process next child. continue; } NodeInfo info; // Populate dummy description. info.mName = std::move(name); info.mParentHandle = parent.handle(); // Instantiate child. auto ptr = std::make_unique<FileInode>(id, info, self); // Inject file info. ptr->fileInfo(std::move(fileInfo)); // Add child to vector. children.emplace_back(ptr.get()); // Add child to index. mByID.emplace(id, std::move(ptr)); } query = transaction.query(mQueries.mRemoveInodeByID); // Prune stale database records. for (auto id: removed) { query.param(":id").set(id); query.execute(); query.reset(); } // Commit transaction. transaction.commit(); // Return children to caller. return children; } void InodeDB::current() { // No-op for now but left in case we need it later. } bool InodeDB::discard() const { InodeDBLock guard(*this); return mDiscard; } InodeRef InodeDB::get(Client& client, NodeHandle handle) const { // Sanity. assert(!handle.isUndef()); // Does the client know anything about this node? auto info = client.get(handle); // Client knows nothing. if (!info) return InodeRef(); // Acquire lock. InodeDBLock guard(*this); // Has another thread loaded this inode? auto h = mByHandle.find(handle); // Inode was loaded by another thread. if (h != mByHandle.end()) return InodeRef(h->second->accessed()); // Assume we're instantiating a file inode. auto build = &InodeDB::buildFile; // Actually instantiating a directory. if (info->mIsDirectory) build = &InodeDB::buildDirectory; // Instantiate new inode and add it to the index. auto ref = const_cast<InodeDB&>(*this).add(build, *info); // Return new inode to caller. return ref; } InodeRef InodeDB::get(FileCache& fileCache, NodeHandle handle, AnyLockSet locks) const { // Make sure we're holding the lock. assert(locks.owns_lock()); // Is this inode present in the file cache? auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetInodeByHandle); query.param(":handle").set(handle); query.execute(); // Inode isn't present in the cache. if (!query) return InodeRef(); return get(fileCache, std::move(locks), std::move(query), std::move(transaction)); } InodeRef InodeDB::get(FileCache& fileCache, InodeID id, AnyLockSet locks) const { // Make sure we're holding the lock. assert(locks.owns_lock()); // Is this inode present in the file cache? auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetInodeByID); query.param(":id").set(id); query.execute(); // Inode isn't present in the cache. if (!query) return InodeRef(); return get(fileCache, std::move(locks), std::move(query), std::move(transaction)); } InodeRef InodeDB::get(FileCache& fileCache, AnyLockSet locks, ScopedQuery query, Transaction transaction) const { // Make sure the lock's held. assert(locks.owns_lock()); // Make sure we actually found an entry in the database. assert(query); // Convenience. auto extension = fileExtensionDB().get(query.field("extension").get<std::string>()); auto id = query.field("id").get<InodeID>(); // If the inode's in the cache, it must be a file. // // Try and determine the file's local state. auto fileInfo = fileCache.info(extension, id); // The file's state is no longer accessible. if (!fileInfo) { // So prune the stale cache entry. query = transaction.query(mQueries.mRemoveInodeByID); query.param(":id").set(id); query.execute(); transaction.commit(); return InodeRef(); } NodeInfo info; // Latch information from the database. if (!query.field("handle").null()) info.mHandle = query.field("handle").get<NodeHandle>(); if (!query.field("name").null()) info.mName = query.field("name").get<std::string>(); if (!query.field("parent_handle").null()) info.mParentHandle = query.field("parent_handle").get<NodeHandle>(); // Release query, transaction and lock so we can call the client. query.reset(); transaction.rollback(); locks.unlock(); // File's local state is present. do { // Convenience. auto& client = this->client(); // File was present in the cloud. if (!info.mHandle.isUndef()) { // Is the file still present in the cloud? auto info_ = client.get(info.mHandle); // Couldn't get info about the file. if (!info_) { // Because the file's been removed from the cloud. if (info_.error() == API_ENOENT) break; // Because the client's been logged out. return InodeRef(); } // Latch current name and parent. info = std::move(*info_); } // File was never present in the cloud. if (info.mHandle.isUndef()) { // Check if the node's parent exists. auto exists = client.exists(info.mParentHandle); // Couldn't get info about the parent. if (!exists) { // Because the parent's been removed. if (exists.error() == API_ENOENT) break; // Because the client's been logged out. return InodeRef(); } } // Reacquire lock. locks.lock(); // Has another thread loaded this inode? auto i = mByID.find(id); // Another thread loaded the inode. if (i != mByID.end()) return InodeRef(i->second->accessed()); // Instantiate the inode. auto ptr = std::make_unique<FileInode>(id, std::move(info), const_cast<InodeDB&>(*this)); // Inject local file state. ptr->fileInfo(std::move(fileInfo)); // Add the inode to the index. i = mByID.emplace(id, std::move(ptr)).first; // File doesn't exist in the cloud. if (info.mHandle.isUndef()) return InodeRef(i->second->accessed()); // File's present in the cloud. // Sanity. assert(!mByHandle.count(info.mHandle)); // We don't record the name or parent of a file in the cloud. transaction = mContext.mDatabase.transaction(); query = transaction.query(mQueries.mSetHandleNameParentHandleByID); query.param(":handle").set(info.mHandle); query.param(":id").set(id); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.execute(); transaction.commit(); // Add handle to the index. mByHandle.emplace(info.mHandle, i->second.get()); // Return inode to the caller. return InodeRef(i->second->accessed()); } while (0); // Reacquire lock. locks.lock(); // Prune stale entry from file cache. transaction = mContext.mDatabase.transaction(); query = transaction.query(mQueries.mRemoveInodeByID); query.param(":id").set(id); query.execute(); transaction.commit(); return InodeRef(); } void InodeDB::handle(FileInode& file, NodeHandle& oldHandle, NodeHandle newHandle) { // Sanity. assert(!newHandle.isUndef()); // Handle hasn't changed. if (oldHandle == newHandle) return; // Acquire locks. auto guard = lockAll(mContext.mDatabase, *this); // Swap new handle into place. std::swap(oldHandle, newHandle); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mSetHandleNameParentHandleByID); // Record the inode's new handle in the database. query.param(":handle").set(oldHandle); query.param(":id").set(file.id()); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.execute(); // Sanity. assert(!mByHandle.count(oldHandle)); // Add new assocation. mByHandle.emplace(oldHandle, &file); // Remove any old association. auto count = mByHandle.erase(newHandle); assert(!count == newHandle.isUndef()); // Silence compiler. static_cast<void>(count); // Persist database changes. transaction.commit(); } InodeID InodeDB::hasChild(const DirectoryInode& parent, const std::string& name) const { // Sanity. assert(!name.empty()); // Convenience. auto parentHandle = parent.handle(); // Does the child exist in the cloud? auto childHandle = client().handle(parentHandle, name); // Acquire locks. auto guard = lockAll(mContext.mDatabase, *this); // Child exists in the cloud. if (childHandle) { // Assume the child's inode ID is its node handle. auto id = InodeID(*childHandle); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetInodeIDByHandle); // Check if the inode is known locally. query.param(":handle").set(*childHandle); query.execute(); // Child's known locally (under some parent.) if (query) { // Latch the child's actual inode ID. id = query.field("id").get<InodeID>(); // We don't track a cloud node's name or parent. query = transaction.query(mQueries.mSetHandleNameParentHandleByID); query.param(":handle").set(*childHandle); query.param(":id").set(id); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.execute(); } // Check if the child is known locally under *this* parent. query = transaction.query(mQueries.mGetInodeIDByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // No other child exists with this name. if (!query) return transaction.commit(), id; // Another child exists with this name. auto otherID = query.field("id").get<InodeID>(); // Consider it "replaced." query = transaction.query(mQueries.mSetNameParentHandleByID); query.param(":id").set(otherID); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.execute(); transaction.commit(); // Return inode ID to caller. return id; } // Does the child exist locally? auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetInodeIDByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // Child exists locally. if (query) return query.field("id").get<InodeID>(); // No child exists locally. return InodeID(); } ErrorOr<bool> InodeDB::hasChildren(const DirectoryInode& directory) const { // Convenience. auto parentHandle = directory.handle(); // Does this directory have any children in the cloud? auto result = client().hasChildren(parentHandle); // Parent no longer exists in the cloud. if (!result) return result; // Parent has children in the cloud. if (*result) return result; // Acquire lock. auto guard = lockAll(mContext.mDatabase, *this); // Does this directory contain any local children? auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetInodeIDByParentHandle); query.param(":parent_handle").set(parentHandle); query.execute(); return query; } ErrorOr<MakeInodeResult> InodeDB::makeDirectory(const platform::Mount&, const std::string& name, DirectoryInodeRef parent) { // Sanity. assert(parent); // Try and make the new directory. auto result = client().makeDirectory(name, parent->handle()); // Couldn't create the directory. if (!result) return unexpected(result.error()); // Convenience. auto info = std::move(*result); // Lock the database. InodeDBLock guard(*this); // Has another thread instantiated an inode for this directory? auto ref = get(info.mHandle, true); // No inode has been instantiated yet. if (!ref) ref = add(&InodeDB::buildDirectory, info); // Return result to caller. return std::make_tuple(std::move(ref), std::move(info)); } ErrorOr<MakeInodeResult> InodeDB::makeFile(const platform::Mount& mount, const std::string& name, DirectoryInodeRef parent) { // Sanity. assert(parent); // Lock the database. auto lock = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); // Add the new file to the database. auto extension = fileExtensionDB().getFromPath(name); auto id = addFile(extension, name, parent->handle(), transaction); // Add a new file to the cache. auto fileInfo = fileCache().create(extension, id); // Couldn't add a new file to the cache. if (!fileInfo) return unexpected(fileInfo.error()); // Create a description of our new file. NodeInfo info; info.mIsDirectory = false; info.mName = name; info.mModified = (*fileInfo)->modified(); info.mParentHandle = parent->handle(); info.mPermissions = FULL; info.mSize = 0; // Make an inode to represent the new file. auto ref = ([&]() { // Instantiate a new inode to represent the file. auto ptr = std::make_unique<FileInode>(id, info, const_cast<InodeDB&>(*this)); // Sanity. assert(!mByID.count(id)); // Add the inode to our index. auto i = mByID.emplace(id, std::move(ptr)).first; // Return a reference to our new inode. return i->second->file(); })(); // Let the inode know about its attributes. ref->fileInfo(*fileInfo); // Persist database changes. transaction.commit(); // Release lock. lock.unlock(); // Make sure our new file is flushed to the cloud. fileCache().context(ref)->modified(mount); // Let the mounts know a new file has been created. mContext.mMountDB.each( [&](Mount& mount) { mount.invalidateEntry(name, parent->id()); }); // Return result to caller. return std::make_tuple(std::move(ref), InodeInfo(id, std::move(info))); } auto InodeDB::modified() const -> NodeHandleInodeIDPairVector { // Acquire locks. auto lock = lockAll(mContext.mDatabase, *this); // Instantiate transaction and query. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetModifiedInodes); NodeHandleInodeIDPairVector modified; // Collect the ID of each modified inode. for (query.execute(); query; ++query) { // Compute the inode's effective node handle. auto handle = ([&]() -> NodeHandle { // Search for the inode's parent if it hasn't been uploaded. if (query.field("handle").null()) return query.field("parent_handle").get<NodeHandle>(); // Otherwise search for the inode itself. return query.field("handle").get<NodeHandle>(); })(); // Retrieve the inode's ID. auto id = query.field("id").get<InodeID>(); // Add the inode's information to our vector. modified.emplace_back(handle, id); } // Pass IDs to our caller. return modified; } Error InodeDB::move(InodeRef source, const std::string& targetName, DirectoryInodeRef targetParent) { // Sanity. assert(source); assert(!targetName.empty()); assert(targetParent); // Ask the client to move the child. return client().move(targetName, source->handle(), targetParent->handle()); } Error InodeDB::move(FileInodeRef source, const std::string& targetName, DirectoryInodeRef targetParent) { // Sanity. assert(source); assert(!targetName.empty()); assert(targetParent); // Child exists in the cloud. if (!source->handle().isUndef()) return move(InodeRef(std::move(source)), targetName, std::move(targetParent)); // Convenience. auto id = source->id(); auto sourceName = source->name(); auto sourceParent = source->parent(); auto targetParentHandle = targetParent->handle(); // Child (may) exist locally. auto guard = lockAll(mContext.mDatabase, *this); // Update the database. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mSetNameParentHandleByID); query.param(":id").set(id); query.param(":name").set(targetName); query.param(":parent_handle").set(targetParentHandle); query.execute(); transaction.commit(); // Let the mounts know the file's been moved. mContext.mMountDB.each( [&](Mount& mount) { // Invalidate source directory entry. mount.invalidateEntry(sourceName, sourceParent->id()); // Invalidate target directory entry. mount.invalidateEntry(targetName, targetParent->id()); }); // Let the child know that it's been moved. source->moved(targetName, targetParentHandle); // Return result to caller. return API_OK; } void InodeDB::remove(const DirectoryInode& inode, InodeDBLock) { // Leave a record of what we've done. FUSEDebugF("Removing inode %s from memory", toString(inode.id()).c_str()); // Directories are always present in both maps. auto count = mByHandle.erase(inode.handle()); assert(count); count = mByID.erase(inode.id()); assert(count); static_cast<void>(count); // Let any waiters know an inode's been removed. mCV.notify_all(); } void InodeDB::remove(const FileInode& inode, InodeDBLock lock) { // Convenience. auto id = inode.id(); // Leave some clues for debuggers. FUSEDebugF("Removing inode %s from memory", toString(inode.id()).c_str()); InodePtr ptr; // Remove the inode from the index. do { auto guard = std::move(lock); // Convenience. auto handle = inode.handle(); // Get our hands on the inode's pointer. auto i = mByID.find(id); // Sanity. assert(i != mByID.end()); // Latch the pointer for release. ptr = std::move(i->second); // Remove the inode from the ID index. mByID.erase(i); // Remove the inode from the other indexes. auto count = mByHandle.erase(handle); assert(!!count == !handle.isUndef()); // Silence the compiler. static_cast<void>(count); } while (0); // Had the file been removed? auto removed = inode.removed(); // File hasn't been removed. if (!removed) { // Let any waiters know an inode's been removed from memory. return mCV.notify_all(); } // Purge the file from the cache if needed. if (auto info = inode.fileInfo()) fileCache().remove(info->extension(), id); // Purge inode from the database. auto locks = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mRemoveInodeByID); query.param(":id").set(id); query.execute(); transaction.commit(); // Let any waiters know an inode's been removed from memory. mCV.notify_all(); } Error InodeDB::replace(FileInodeRef source, FileInodeRef target, const std::string& targetName, DirectoryInodeRef targetParent) { assert(source); assert(target); assert(targetParent); // Convenience. auto sourceHandle = source->handle(); auto sourceName = source->name(); auto sourceParent = source->parent(); auto targetHandle = target->handle(); auto targetParentHandle = targetParent->handle(); // Perform cloud processing if required. auto result = ([&]() { // Source exists in the cloud. if (!sourceHandle.isUndef()) { // Target exists in the cloud. if (!targetHandle.isUndef()) return client().replace(sourceHandle, targetHandle); // Only source exists in the cloud. return client().move(targetName, sourceHandle, targetParentHandle); } // Only target exists in the cloud. if (!targetHandle.isUndef()) return client().remove(targetHandle); // Neither source nor target exist in the cloud. return Error(API_OK); })(); // Couldn't perform move/remove/replace in the cloud. if (result != API_OK) return result; // Lock database. auto lock = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mRemoveInodeByID); // Remove target from the database. query.param(":id").set(target->id()); query.execute(); // Source is a local file. if (sourceHandle.isUndef()) { query = transaction.query(mQueries.mSetNameParentHandleByID); // Move source in the database. query.param(":id").set(source->id()); query.param(":name").set(targetName); query.param(":parent_handle").set(targetParentHandle); query.execute(); } // Persist database changes. transaction.commit(); // Let the mounts know the target has been replaced. mContext.mMountDB.each( [&](Mount& mount) { // Source is a local file. if (sourceHandle.isUndef()) mount.invalidateEntry(sourceName, sourceParent->id()); // Target is a local file. if (targetHandle.isUndef()) mount.invalidateEntry(targetName, targetParent->id()); }); // Let the target know it's been removed. if (targetHandle.isUndef()) target->removed(true); // Let the source know it's been moved. if (sourceHandle.isUndef()) source->moved(targetName, targetParentHandle); // Target's been replaced. return API_OK; } Error InodeDB::replace(DirectoryInodeRef source, DirectoryInodeRef target, const std::string&, DirectoryInodeRef) { assert(source); assert(target); // Ask the client to replace target with source. return client().replace(source->handle(), target->handle()); } Error InodeDB::unlink(InodeRef inode) { // Sanity. assert(inode); // Try and remove the node associated with this inode. auto result = client().remove(inode->handle()); // Couldn't remove the node. if (result != API_OK) return result; // Mark inode as removed. inode->removed(true); // Let caller know whether the inode was removed. return result; } Error InodeDB::unlink(FileInodeRef file) { assert(file); // Convenience. auto handle = file->handle(); auto name = file->name(); auto parent = file->parent(); auto result = API_OK; // File exists in the cloud. if (!handle.isUndef()) { // Ask the client to remove the file. result = client().remove(handle); // Couldn't remove the file. if (result != API_OK) return result; } // Remove the file from the database. { auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mRemoveInodeByID); query.param(":id").set(file->id()); query.execute(); transaction.commit(); } // Mark the file as having been removed. file->removed(true); // Try and cancel any pending uploads. fileCache().remove(file->extension(), file->id()); // Let the mounts know the file's been removed. mContext.mMountDB.each( [&](Mount& mount) { mount.invalidateEntry(name, parent->id()); }); // File's been removed. return API_OK; } InodeDB::InodeDB(platform::ServiceContext& context): Lockable(), mByHandle(), mByID(), mByParentHandleAndName(), mCV(), mContext(context), mDiscard(false), mQueries(context.mDatabase) { FUSEDebug1("Inode DB constructed"); } InodeDB::~InodeDB() { FUSEDebug1("Inode DB destroyed"); } void InodeDB::add(const FileInode& inode) { // Convenience. auto extension = inode.extension(); auto handle = inode.handle(); auto id = inode.id(); // Acquire locks. auto guard = lockAll(mContext.mDatabase, *this); // Establish transaction and query. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mAddInode); // Add the inode to the database. query.param(":extension").set(extension.get()); query.param(":handle").set(handle); query.param(":id").set(id); query.param(":modified").set(false); query.param(":name").set(nullptr); query.param(":parent_handle").set(nullptr); query.execute(); // Persist changes. transaction.commit(); } InodeCache& InodeDB::cache() const { return mContext.mInodeCache; } void InodeDB::clear() { // True when there are no inodes in memory. auto empty = [this]() { return mByID.empty(); }; // empty // True when all inodes have been purged from memory. auto purged = false; FUSEDebug1("Waiting for inodes to be purged from memory"); // Wait for the inodes to be purged from memory. while (!purged) { // Convenience. constexpr auto timeout = std::chrono::milliseconds(500); // Evict all inodes from the cache. cache().clear(); // Acquire lock. InodeDBLock lock(*this); // Wait for the inodes to be purged from memory. purged = mCV.wait_for(lock, timeout, empty); } FUSEDebug1("Inodes have been purged from memory"); // Acquire lock. InodeDBLock guard(*this); // Sanity. assert(mByID.empty()); assert(mByHandle.empty()); assert(mByParentHandleAndName.empty()); } Client& InodeDB::client() const { return mContext.client(); } void InodeDB::discard(bool discard) { InodeDBLock guard(*this); mDiscard = discard; } bool InodeDB::exists(InodeID id) const { // Check if the inode's in memory. auto ref = get(id, true); // Inode exists if it hasn't been removed. if (ref) return !ref->removed(); auto guard = lockAll(mContext.mDatabase, *this); // Check the database. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetHandleByID); query.param(":id").set(id); query.execute(); return !!query; } FileCache& InodeDB::fileCache() const { return mContext.mFileCache; } FileExtensionDB& InodeDB::fileExtensionDB() const { return mContext.mFileExtensionDB; } InodeRef InodeDB::get(NodeHandle handle, bool inMemoryOnly) const { assert(!handle.isUndef()); // Acquire database lock. auto lock = lockAll(mContext.mDatabase, *this); // Check if the inode's already in memory. auto h = mByHandle.find(handle); // Inode's in memory (match on current handle.) if (h != mByHandle.end()) return InodeRef(h->second->accessed()); // Inode's not in memory and we don't want to load it. if (inMemoryOnly) return InodeRef(); // Check if the inode's in the file cache. if (auto ptr = get(fileCache(), handle, std::move(lock))) return ptr; // Inode's not in memory and not in the cache. return get(client(), handle); } InodeRef InodeDB::get(InodeID id, bool inMemoryOnly) const { assert(id); // Acquire database lock. auto lock = lockAll(mContext.mDatabase, *this); // Check if the inode's already in memory. auto i = mByID.find(id); // Inode's already in memory. if (i != mByID.end()) return InodeRef(i->second->accessed()); // Inode's not in memory and we don't want to load it. if (inMemoryOnly) return InodeRef(); // Check if the inode's in the file cache. if (auto ptr = get(fileCache(), id, std::move(lock))) return ptr; // A synthetic inode should've been in the file cache. if (id.synthetic()) return InodeRef(); // See if the inode exists in the cloud. return get(client(), NodeHandle(id)); } template<typename Path> auto InodeDB::lookup(const Path& path, NodeHandle parent, std::string* name) const -> typename EnableIfPath<Path, LookupResult>::type { // For convenience. auto& fsAccess = client().fsAccess(); // Keeps logic simple. std::string dummy; if (!name) name = &dummy; // Try and get our hands on the root node. auto ref = get(parent); // Couldn't locate the root node. if (!ref) return name->clear(), std::make_pair(ref, API_ENOENT); Path component; std::size_t index = 0; // Traverse tree, component by component. while (path.nextPathComponent(index, component)) { // Is the current node a directory? auto directoryRef = ref->directory(); // Current node isn't a directory. if (!directoryRef) return std::make_pair(ref, API_FUSE_ENOTDIR); // Extract component name. *name = component.toName(fsAccess); // Try and traverse down the tree. ref = directoryRef->get(*name); // We've found a child with this name. if (ref) continue; // No child exists with this name. auto result = API_ENOENT; // Indicate when the final component couldn't be found. if (!path.hasNextPathComponent(index)) result = API_FUSE_ENOTFOUND; return std::make_pair(directoryRef, result); } // Effectively empty path, return root to caller. return std::make_pair(ref, API_OK); } void InodeDB::modified(InodeID id, bool modified) { auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mSetModifiedByID); query.param(":id").set(id); query.param(":modified").set(modified); query.execute(); transaction.commit(); } bool InodeDB::modified(InodeID id) const { auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetModifiedByID); query.param(":id").set(id); query.execute(); return query && query.field("modified").get<bool>(); } FileInodeRefVector InodeDB::modified(NodeHandle parent) const { // Convenience. const auto& client = this->client(); // Stores a reference to each modified inode. FileInodeRefVector modified; // Tracks which nodes are under parent. NodeHandleSet related; // Tracks which nodes are *not* under parent. NodeHandleSet unrelated; // Makes logic simpler. related.emplace(parent); unrelated.emplace(NodeHandle()); // Collect each modified inode that's a descendent of parent. for (auto& entry: this->modified()) { // Keeps track of this node's ancestors; NodeHandleSet ancestors; // Convenience. auto handle = entry.first; auto id = entry.second; // Climb up the tree until we hit parent or the root. while (true) { // We've hit the root (or an unrelated node.) if (unrelated.count(handle)) { // Add ancestors to our unrelated set. unrelated.insert(ancestors.begin(), ancestors.end()); // Process the next inode. break; } // We've hit parent (or a related node.) if (related.count(handle)) { // Add ancestors to our related set. related.insert(ancestors.begin(), ancestors.end()); // Add the inode to our vector of modified inodes. if (auto ref = get(id)) modified.emplace_back(ref->file()); // Process the next inode. break; } // Add ourselves to the ancestors map. ancestors.emplace(handle); // Climb up into our parent. handle = client.parentHandle(handle).valueOr(NodeHandle()); } } // Return modified inodes to the caller. return modified; } void InodeDB::updated(NodeEventQueue& events) { // Processing node events. if (!discard()) return EventObserver(*this)(events); // Discarding node events. FUSEDebugF("Discarding %zu node event(s)", events.size()); } void InodeDB::EventObserver::added(const NodeEvent& event) { // Convenience. auto handle = event.handle(); auto& name = event.name(); auto parentHandle = event.parentHandle(); FUSEDebugF("Node added: %s (%s) [%s]", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str()); // Node replaces an in-memory inode. if (auto ref = mInodeDB.child(name, parentHandle, MemoryOnly)) { // Node is a new version of this inode. if (ref->handle() == handle) return; // Node replaces the inode. FUSEDebugF("%s (%s) [%s] replaces warm inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(ref->id()).c_str()); // Mark the inode as removed. ref->removed(true); // Invalidate any associated directory entries. return mountDB().each( [&](Mount& mount) { mount.invalidatePin(ref->id()); }); } // Does the node replace an inode that's not in memory? auto query = mTransaction.query(queries().mGetExtensionAndInodeIDByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // Node replaces an inode that's not in memory. if (query) { // Convenience. auto extension = fileExtensionDB().get(query.field("extension").get<std::string>()); auto id = query.field("id").get<InodeID>(); FUSEDebugF("%s (%s) [%s] replaces cold inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(id).c_str()); // Purge the inode from the database. query = mTransaction.query(queries().mRemoveInodeByID); query.param(":id").set(id); query.execute(); // Purge the inode's content from the cache. fileCache().remove(extension, id); } // Invalidate any associated directory entries. mountDB().each( [&](Mount& mount) { mount.invalidateEntry(name, InodeID(parentHandle)); }); } Database& InodeDB::EventObserver::database() const { return mInodeDB.mContext.mDatabase; } FileCache& InodeDB::EventObserver::fileCache() const { return mInodeDB.fileCache(); } FileExtensionDB& InodeDB::EventObserver::fileExtensionDB() const { return mInodeDB.fileExtensionDB(); } void InodeDB::EventObserver::modified(const NodeEvent& event) { // Convenience. auto handle = event.handle(); auto& name = event.name(); auto parentHandle = event.parentHandle(); FUSEDebugF("Node modified: %s (%s) [%s]", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str()); // Has an inode in memory been updated in the cloud? auto ref = mInodeDB.get(handle, true); // No inode in memory has been updated in the cloud. if (!ref) return; // Invalidate the inode's attributes. mountDB().each( [&](Mount& mount) { mount.invalidateAttributes(ref->id()); }); } void InodeDB::EventObserver::moved(const NodeEvent& event) { // Convenience. auto handle = event.handle(); auto& name = event.name(); auto parentHandle = event.parentHandle(); FUSEDebugF("Node moved: %s (%s) [%s]", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str()); // Node replaces an in-memory inode at this location. if (auto ref = mInodeDB.child(name, parentHandle, MemoryOnly)) { FUSEDebugF("%s (%s) [%s] replaces warm inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(ref->id()).c_str()); // Mark inode as removed. ref->removed(true); } // Is an inode at this location in the database? auto query = mTransaction.query(queries().mGetExtensionAndInodeIDByNameAndParentHandle); query.param(":name").set(name); query.param(":parent_handle").set(parentHandle); query.execute(); // Node replaces an inode that's not in memory. if (query) { // Convenience. auto extension = fileExtensionDB().get(query.field("extension").get<std::string>()); auto id = query.field("id").get<InodeID>(); FUSEDebugF("%s (%s) [%s] replaces cold inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(id).c_str()); // Purge the inode from the database. query = mTransaction.query(queries().mRemoveInodeByID); query.param(":id").set(id); query.execute(); // Purge the inode from the cache. fileCache().remove(extension, id); } // Has an inode changed location? if (auto ref = mInodeDB.get(handle, true)) { // Convenience. auto id_ = ref->id(); auto& name_ = ref->name(CachedOnly); auto parentID_ = InodeID(ref->parentHandle(CachedOnly)); FUSEDebugF("%s (%s) [%s] has moved to %s [%s]", name_.c_str(), toString(id_).c_str(), toString(parentID_).c_str(), name.c_str(), toNodeHandle(parentHandle).c_str()); // Update the inode's description. ref->info(event.info()); // Invalidate associated directory entries. return mountDB().each( [&](Mount& mount) { // Invalidate source. mount.invalidatePin(ref->id()); // Invalidate target. mount.invalidateEntry(name, InodeID(parentHandle)); }); } // Invalidate any negative directory entries. mountDB().each( [&](Mount& mount) { mount.invalidateEntry(name, InodeID(parentHandle)); }); } MountDB& InodeDB::EventObserver::mountDB() const { return mInodeDB.mContext.mMountDB; } void InodeDB::EventObserver::permissions(const NodeEvent&) { // TODO: To be improved later. } auto InodeDB::EventObserver::queries() const -> Queries& { return mInodeDB.mQueries; } void InodeDB::EventObserver::removed(const NodeEvent& event) { // Convenience. auto handle = event.handle(); auto& name = event.name(); auto parentHandle = event.parentHandle(); FUSEDebugF("Node removed: %s (%s) [%s]", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str()); // Disable any mounts that might be associated with this node. if (event.isDirectory()) mountDB().disable(event.handle()); // Node matches an inode that's currently in memory. if (auto ref = mInodeDB.get(handle, true)) { // Convenience. auto id = ref->id(); FUSEDebugF("%s (%s) [%s] matches warm inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(id).c_str()); // Mark inode as removed. ref->removed(true); // Invalidate any associated directory entries. return mountDB().each( [&](Mount& mount) { mount.invalidateEntry(name, id, InodeID(parentHandle)); }); } auto query = mTransaction.query(queries().mGetExtensionAndInodeIDByHandle); query.param(":handle").set(handle); query.execute(); // Node matches an inode that's not in memory. if (query) { // Convenience. auto extension = fileExtensionDB().get(query.field("extension").get<std::string>()); auto id = query.field("id").get<InodeID>(); FUSEDebugF("%s (%s) [%s] matches cold inode %s", name.c_str(), toNodeHandle(handle).c_str(), toNodeHandle(parentHandle).c_str(), toString(id).c_str()); // Purge the inode from the database. query = mTransaction.query(queries().mRemoveInodeByID); query.param(":id").set(id); query.execute(); // Purge the inode's content from the cache. fileCache().remove(extension, id); } // Invalidate any associated directory entries. mountDB().each( [&](Mount& mount) { mount.invalidateEntry(name, InodeID(parentHandle)); }); } InodeDB::EventObserver::EventObserver(InodeDB& inodeDB): mInodeDB(inodeDB), mDatabaseLock(database(), std::defer_lock), mInodeDBLock(mInodeDB, std::defer_lock), mTransaction() { // Acquire necessary locks. std::lock(mDatabaseLock, mInodeDB); // Establish a transaction for future queries. mTransaction = database().transaction(); } InodeDB::EventObserver::~EventObserver() { mInodeDB.unlock(); mDatabaseLock.unlock(); } void InodeDB::EventObserver::operator()(NodeEventQueue& events) { // Convenience. using EventHandler = void (EventObserver::*)(const NodeEvent&); using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::milliseconds; // For quick dispatch. static EventHandler handlers[NUM_NODE_EVENT_TYPES] = {&EventObserver::added, &EventObserver::modified, &EventObserver::moved, &EventObserver::permissions, &EventObserver::removed}; // handlers FUSEDebugF("Processing %zu node event(s)", events.size()); auto began = high_resolution_clock::now(); // Process each event. for (; !events.empty(); events.pop_front()) { // What's the next event in the queue? auto& event = events.front(); // What is the event's type? auto type = event.type(); // Sanity. assert(type < NUM_NODE_EVENT_TYPES); // Which handler should we dispatch to? auto handler = handlers[type]; // Sanity. assert(handler); // Handle the event. (this->*handler)(event); } // Persist database changes. mTransaction.commit(); // Log some helpful statistics. auto elapsed = high_resolution_clock::now() - began; FUSEDebugF("%zu node event(s) processed in %lu ms", events.size(), duration_cast<milliseconds>(elapsed).count()); } template auto InodeDB::lookup(const platform::PathAdapter&, NodeHandle, std::string*) const -> LookupResult; template auto InodeDB::lookup(const LocalPath&, NodeHandle, std::string*) const -> LookupResult; template auto InodeDB::lookup(const RemotePath&, NodeHandle, std::string*) const -> LookupResult; } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/���������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020426�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/CMakeLists.txt�������������������������������������������0000664�0000000�0000000�00000000027�15162662266�0023165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/����������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021370�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/CMakeLists.txt��������������������������������������0000664�0000000�0000000�00000000031�15162662266�0024122�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(common) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/���������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022660�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/CMakeLists.txt�������������������������������0000664�0000000�0000000�00000002372�15162662266�0025424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE constants.h database_builder.h directory_inode.h directory_inode_forward.h directory_inode_results.h file_cache.h file_cache_forward.h file_extension_db.h file_extension_db_forward.h file_info.h file_info_forward.h file_inode.h file_inode_forward.h file_io_context.h file_io_context_forward.h inode.h inode_badge.h inode_badge_forward.h inode_cache.h inode_cache_forward.h inode_db.h inode_db_forward.h inode_forward.h mount.h mount_db.h mount_db_forward.h mount_forward.h path_adapter.h path_adapter_forward.h ref.h ref_forward.h tags.h ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/constants.h����������������������������������0000664�0000000�0000000�00000000271�15162662266�0025045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { constexpr auto FilesystemID = 0x4d454741ul; constexpr auto BlockSize = 4096u; constexpr auto MaxNameLength = 255u; } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/database_builder.h���������������������������0000664�0000000�0000000�00000000636�15162662266�0026310�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/database_builder.h> #include <mega/common/database_forward.h> namespace mega { namespace fuse { class DatabaseBuilder: public common::DatabaseBuilder { // What versions exist for this database? const common::DatabaseVersionVector& versions() const override; public: explicit DatabaseBuilder(common::Database& database); }; // DatabaseBuilder } // fuse } // mega ��������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/directory_inode.h����������������������������0000664�0000000�0000000�00000005511�15162662266�0026215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/error_or_forward.h> #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/directory_inode_results.h> #include <mega/fuse/common/file_move_flag_forward.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/types.h> #include <cstdint> #include <functional> #include <string> namespace mega { namespace fuse { class DirectoryInode final: public Inode { // Make a new child. template<typename Maker> common::ErrorOr<MakeInodeResult> make(Maker&& maker, const std::string& name); // Removes this directory from the inode database. void remove(RefBadge badge, InodeDBLock lock) override; public: DirectoryInode(InodeID id, const common::NodeInfo& info, InodeDB& inodeDB); ~DirectoryInode(); // Is this inode in the file cache? bool cached() const override; // Retrieve a list of this directory's children. InodeRefVector children() const; // Return a specialized reference to this directory. DirectoryInodeRef directory() override; // Try and retrieve a reference to the specified child. InodeRef get(const std::string& name) const; // What cloud node does this directory represent? NodeHandle handle() const override; // Does this directory contain the specified child? bool hasChild(const std::string& name) const; // Does this directory contain any children? common::ErrorOr<bool> hasChildren() const; // Update this directory's cached description. void info(const common::NodeInfo& info) override; // Retrieve a description of this directory. InodeInfo info() const override; // Make a subdirectory with the specified name. common::ErrorOr<MakeInodeResult> makeDirectory(const platform::Mount& mount, const std::string& name); // Make a file with the specified name. common::ErrorOr<MakeInodeResult> makeFile(const platform::Mount& mount, const std::string& name); // Move a child to a new directory. Error move(const std::string& name, const std::string& newName, DirectoryInodeRef newParent, FileMoveFlags flags); // Move (or rename) this directory. Error move(InodeBadge badge, const std::string& name, DirectoryInodeRef parent) override; // Replace other with this directory. Error replace(InodeBadge badge, InodeRef other, const std::string& otherName, DirectoryInodeRef otherParent) override; // Unlink a child. Error unlink(const std::string& name, std::function<Error(InodeRef)> predicate); // Unlink this directory. Error unlink(InodeBadge badge) override; }; // DirectoryInode } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/directory_inode_forward.h��������������������0000664�0000000�0000000�00000000656�15162662266�0027746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/badge_forward.h> #include <mega/fuse/common/ref_forward.h> #include <memory> namespace mega { namespace fuse { class DirectoryInode; using DirectoryInodeBadge = common::Badge<DirectoryInode>; using DirectoryInodeRef = Ref<DirectoryInode>; // Interface to Ref<T>. void doRef(RefBadge badge, DirectoryInode& inode); void doUnref(RefBadge badge, DirectoryInode& inode); } // fuse } // mega ����������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/directory_inode_results.h��������������������0000664�0000000�0000000�00000000357�15162662266�0030001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <tuple> namespace mega { namespace fuse { using MakeInodeResult = std::tuple<InodeRef, InodeInfo>; } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_cache.h���������������������������������0000664�0000000�0000000�00000011464�15162662266�0025101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/client_forward.h> #include <mega/common/error_or_forward.h> #include <mega/common/lockable.h> #include <mega/common/platform/folder_locker.h> #include <mega/common/task_executor_forward.h> #include <mega/filesystem.h> #include <mega/fuse/common/file_cache_forward.h> #include <mega/fuse/common/file_extension_db_forward.h> #include <mega/fuse/common/file_info_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/file_io_context_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/mount_forward.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/fuse/platform/utility.h> #include <condition_variable> namespace mega { namespace common { template<> struct LockableTraits<fuse::FileCache>: public LockableTraitsCommon<fuse::FileCache, std::recursive_mutex> {}; // LockableTraits<fuse::FileCache> } // common namespace fuse { class FileCache: public common::Lockable<FileCache> { friend class FileIOContext; friend class FileInfo; // Create a new file description based on the file at the specified path. // // If create is false, this function will return a description only if // the file already exists at the specified location. // // If create is true, a new file will be created at the specified location. common::ErrorOr<FileInfoRef> create(const FileExtension& extension, const LocalPath& path, InodeID id, FileAccessSharedPtr* fileAccess, bool create); // Get a reference to an inode's file info. // // If no info is currently associated with the specified inode, // a new file info instance will be created based on the values // contained in the specified file access instance. FileInfoRef info(const FileExtension& extension, const FileAccess& fileAccess, InodeID id); // Purge unreferenced files from the cache. void purge(); // Remove context from the index. void remove(const FileIOContext& context, FileCacheLock lock); // Remove info from the index. void remove(const FileInfo& info, FileCacheLock lock); // Tracks which context is associated with what inode. mutable ToFileIOContextPtrMap<InodeID> mContextByID; // Tracks which info is associated with what inode. mutable ToFileInfoPtrMap<InodeID> mInfoByID; // Signalled when a context or info instance is removed. std::condition_variable_any mRemoved; common::platform::FolderLocker mFolderLocker; public: explicit FileCache(platform::ServiceContext& context); ~FileCache(); // Cancel pending uploads and wait for contexts to drain. void cancel(); // What client are we using to transfer data? common::Client& client() const; // Retrieve a reference to an inode's file context. FileIOContextRef context(FileInodeRef file, bool inMemoryOnly = false) const; // Create a new file description based on a file already in the cache. common::ErrorOr<FileInfoRef> create(const FileExtension& extension, const LocalPath& path, InodeID id, FileAccessSharedPtr* fileAccess = nullptr); // Create an empty file and return its description. common::ErrorOr<FileInfoRef> create(const FileExtension& extension, InodeID id, FileAccessSharedPtr* fileAccess = nullptr, LocalPath* filePath = nullptr); // Called by the client when its view of the cloud is current. void current(); // Who do we call when we want to execute something on another thread? common::TaskExecutor& executor() const; // Flush zero or more modified inodes to the cloud. void flush(const Mount& mount, FileInodeRefVector inodes); // Get a reference to an inode's file info. // // If inMemoryOnly is false and no info is currently associated with the // specified inode, a new file info instance will be created based on // the file representing this inode's cached content. FileInfoRef info(const FileExtension& extension, InodeID id, bool inMemoryOnly = false) const; // Where is an inode's local state located? LocalPath path(const FileExtension& extension, InodeID id) const; // Remove an inode's content from the cache. void remove(const FileExtension& extension, InodeID id); // Where is the cache storing its data? const LocalPath mCachePath; // Which context owns this cache? platform::ServiceContext& mContext; }; // FileCache } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_cache_forward.h�������������������������0000664�0000000�0000000�00000000243�15162662266�0026616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mutex> namespace mega { namespace fuse { class FileCache; using FileCacheLock = std::unique_lock<const FileCache>; } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_extension_db.h��������������������������0000664�0000000�0000000�00000003556�15162662266�0026522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/file_extension_db_forward.h> #include <mega/types.h> #include <mutex> namespace mega { namespace fuse { class FileExtensionDB; class FileExtension { friend class FileExtensionDB; FileExtension(FileExtensionDB& db, FromStringMap<std::size_t>::iterator iterator); // The DB that contains this extension. FileExtensionDB* mDB; // Our position in the DB's extensions map. FromStringMap<std::size_t>::iterator mIterator; public: FileExtension(); FileExtension(const FileExtension& other); FileExtension(FileExtension&& other); ~FileExtension(); operator const std::string&() const; FileExtension& operator=(const FileExtension& rhs); FileExtension& operator=(FileExtension&& rhs); const std::string& get() const; void swap(FileExtension& other); }; // FileExtension class FileExtensionDB { friend class FileExtension; // Add a reference to an existing extension. void ref(FromStringMap<std::size_t>::iterator iterator); // Remove a reference from an existing extension. void unref(FromStringMap<std::size_t>::iterator iterator); // Serializes access to mExtensions. std::mutex mLock; // Tracks the extensions we know about along with their reference count. FromStringMap<std::size_t> mExtensions; public: FileExtensionDB() = default; FileExtensionDB(const FileExtensionDB& other) = delete; ~FileExtensionDB() = default; FileExtensionDB& operator=(const FileExtensionDB& rhs) = delete; // Add a new (or reference an existing) extension to the DB. FileExtension get(const std::string& extension); // The same as the above but determines the extension from the path. FileExtension getFromPath(const std::string& path); }; // FileExtensionDB void swap(FileExtension& lhs, FileExtension& rhs); } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_extension_db_forward.h������������������0000664�0000000�0000000�00000000162�15162662266�0030234�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { class FileExtension; class FileExtensionDB; } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_info.h����������������������������������0000664�0000000�0000000�00000004215�15162662266�0024765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/error_or_forward.h> #include <mega/filesystem.h> #include <mega/fuse/common/file_cache_forward.h> #include <mega/fuse/common/file_extension_db.h> #include <mega/fuse/common/file_info_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/inode_id.h> #include <cstddef> #include <mutex> namespace mega { namespace fuse { class FileInfo { // The file's extension. FileExtension mExtension; // The cache this file belongs to. FileCache& mFileCache; // The inode that this file represents. const InodeID mID; // Serializes access to our members. mutable std::mutex mLock; // When was the file last modified? m_time_t mModified; // Tracks how many actors are referencing this instance. unsigned long mReferences; // What is the file's size? m_off_t mSize; public: FileInfo(const FileExtension& extension, const FileAccess& fileAccess, FileCache& fileCache, InodeID id); ~FileInfo(); // Retrieve this file's extension. FileExtension extension() const; // Retrieve this file's current attributes. void get(m_time_t& modified, m_off_t& size) const; // What inode does this file represent? InodeID id() const; // Retrieve this file's current modification time. m_time_t modified() const; // Open this file for writing. // // If successful, path will be updated to contain the concrete // location where this file's content is stored. common::ErrorOr<FileAccessSharedPtr> open(LocalPath& path) const; // Where is this file's cached content stored? LocalPath path() const; // Increment this instance's reference counter. void ref(RefBadge badge); // Retrieve this file's current size. m_off_t size() const; // Set this file's current attributes. void set(m_time_t modified, m_off_t size); // Decrements this inode's reference counter. // // If the instance's reference counter drops to zero, // the instance is removed from the file cache. void unref(RefBadge badge); }; // FileInfo } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_info_forward.h��������������������������0000664�0000000�0000000�00000000663�15162662266�0026514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/ref_forward.h> #include <map> #include <memory> namespace mega { namespace fuse { class FileInfo; using FileInfoPtr = std::unique_ptr<FileInfo>; using FileInfoRef = Ref<FileInfo>; template<typename T> using ToFileInfoPtrMap = std::map<T, FileInfoPtr>; // Interface to Ref<T>. void doRef(RefBadge badge, FileInfo& info); void doUnref(RefBadge badge, FileInfo& info); } // fuse } // mega �����������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_inode.h���������������������������������0000664�0000000�0000000�00000005343�15162662266�0025133�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/error_or_forward.h> #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/file_extension_db_forward.h> #include <mega/fuse/common/file_info_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/file_open_flag_forward.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/mount_forward.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/file_context_forward.h> namespace mega { namespace fuse { // Represents an individual file. class FileInode final: public Inode { // Removes this file from the inode database. void remove(RefBadge badge, InodeDBLock lock) override; // Tracks which cloud node we're associated with, if any. NodeHandle mHandle; // Tracks the local state of this file. FileInfoRef mInfo; // Serializes access to mInfo. mutable std::mutex mInfoLock; public: FileInode(InodeID id, const common::NodeInfo& info, InodeDB& inodeDB); ~FileInode(); // Is this inode in the file cache? bool cached() const override; // Retrieve this file's extension. FileExtension extension() const; // Return a specialized reference to this file. FileInodeRef file() override; // Set this file's file info. void fileInfo(FileInfoRef info); // Retrieve a reference to this file's file info. FileInfoRef fileInfo() const; // Specify which cloud node this file is associatd with. void handle(NodeHandle handle); // What cloud node, if any, is associated with this file? NodeHandle handle() const override; // Update this file's cached description. void info(const common::NodeInfo& info) override; // Retrieve a description of this file. InodeInfo info() const override; // Specify whether this file has been modified. void modified(bool modified); // Move (or rename) this file. Error move(InodeBadge badge, const std::string& name, DirectoryInodeRef parent) override; // Open this file for reading or writing. common::ErrorOr<platform::FileContextPtr> open(Mount& mount, FileOpenFlags flags); // Replace other with this file. Error replace(InodeBadge badge, InodeRef other, const std::string& otherName, DirectoryInodeRef otherParent) override; // Truncate the file to the specified size. Error truncate(const Mount& mount, m_off_t size, bool dontGrow); // Update the file's modification time. Error touch(const Mount& mount, m_time_t modified); // Unlink this file. Error unlink(InodeBadge badge) override; // Query whether this file has been modified. bool wasModified() const; }; // FileInode } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_inode_forward.h�������������������������0000664�0000000�0000000�00000000573�15162662266�0026657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/ref_forward.h> #include <memory> #include <vector> namespace mega { namespace fuse { class FileInode; using FileInodeRef = Ref<FileInode>; using FileInodeRefVector = std::vector<FileInodeRef>; // Interface to Ref<T>. void doRef(RefBadge badge, FileInode& inode); void doUnref(RefBadge badge, FileInode& inode); } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_io_context.h����������������������������0000664�0000000�0000000�00000011657�15162662266�0026215�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/client_callbacks.h> #include <mega/common/error_or.h> #include <mega/common/lock_forward.h> #include <mega/common/lockable.h> #include <mega/common/shared_mutex.h> #include <mega/common/task_queue.h> #include <mega/common/utility.h> #include <mega/filesystem.h> #include <mega/fuse/common/file_cache_forward.h> #include <mega/fuse/common/file_info_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/file_io_context_forward.h> #include <mega/fuse/common/inode_db_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/mount_forward.h> #include <mega/fuse/common/ref.h> #include <mega/types.h> #include <cstddef> #include <mutex> #include <string> namespace mega { namespace common { template<> struct LockableTraits<fuse::FileIOContext> { using LockType = SharedMutex; static void acquired(const fuse::FileIOContext& context); static void acquiring(const fuse::FileIOContext& context); static void couldntAcquire(const fuse::FileIOContext& context); static void released(const fuse::FileIOContext& context); static void tryAcquire(const fuse::FileIOContext& context); }; // LockableTraits<fuse::FileIOContext> } // common namespace fuse { class FileIOContext: public common::Lockable<FileIOContext> { // Bundles up state required to perform a flush. class FlushContext; // Convenience. using FlushContextPtr = std::shared_ptr<FlushContext>; // Create the file. common::ErrorOr<FileAccessSharedPtr> create(); // Download the file from the cloud. common::ErrorOr<FileAccessSharedPtr> download(const Mount& mount); // How long should we wait before we flush modifications? std::chrono::seconds flushDelay() const; // Retrieve a reference to the inode DB. InodeDB& inodeDB() const; // Flush this context's content to the cloud. Error manualFlush(FileIOContextSharedLock& contextLock, std::unique_lock<std::mutex>& flushLock, NodeHandle mountHandle, LocalPath mountPath); // Called when it's time to perform a queued flush. void onPeriodicFlush(FileIOContextRef& context, m_time_t lastModified, NodeHandle mountHandle, LocalPath& mountPath, const common::Task& task); // Open the file for IO. auto open(FileIOContextLock& lock, const Mount& mount, m_off_t hint = -1) -> common::ErrorOr<FileAccessSharedPtr>; auto open(FileIOContextSharedLock& lock, const Mount& mount, m_off_t hint = -1) -> common::ErrorOr<FileAccessSharedPtr>; // What file does this entry represent? FileInodeRef mFile; // How we manipulate the file on disk. FileAccessWeakPtr mFileAccess; // What cache contains this context? FileCache& mFileCache; // Where is that file's local info stored? FileInfoRef mFileInfo; // Where is the file stored on disk? LocalPath mFilePath; // State required for the current flush, if any. FlushContextPtr mFlushContext; // Serializes access to mFlush* members. std::mutex mFlushLock; // True if we need to flush this file's content to the cloud. bool mFlushNeeded; // Represents a queued periodic flush, if any. common::Task mPeriodicFlushTask; // Tracks how many actors reference this instance. unsigned long mReferences; public: FileIOContext(FileCache& cache, FileInodeRef file, FileInfoRef info, bool modified); ~FileIOContext(); // Cancel pending flush and/or upload. void cancel(bool pendingFlush); // The same as above but performed off-thread. void cancel(); // What ID is this context associated with? InodeID id() const; // What file does this context represent? FileInodeRef file() const; // Flush any modifications to the cloud. Error manualFlush(const Mount& mount); // Called when the file's been modified. // // Responsible for queuing a flush if necessary. void modified(const Mount& mount); // Open the file for manipulation. Error open(const Mount& mount, bool truncate); // Read data from the file. common::ErrorOr<std::string> read(const Mount& mount, m_off_t offset, unsigned int size); // Increment this instance's reference count. void ref(RefBadge badge); // Retrieve the file's current size. m_off_t size() const; // Set the file's modification time. Error touch(const Mount& mount, m_time_t modified); // Truncate the file to a specified size. Error truncate(const Mount& mount, m_off_t size, bool dontGrow); // Decrement this instance's reference count. void unref(RefBadge badge); // Write data to the file. common::ErrorOr<std::size_t> write(const Mount& mount, const void* data, m_off_t length, m_off_t offset, bool noGrow); }; // FileIOContext } // fuse } // mega ���������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/file_io_context_forward.h��������������������0000664�0000000�0000000�00000001672�15162662266�0027735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/lock_forward.h> #include <mega/fuse/common/ref_forward.h> #include <map> #include <memory> #include <mutex> #include <vector> namespace mega { namespace fuse { class FileIOContext; using FileIOContextLock = common::UniqueLock<const FileIOContext>; using FileIOContextSharedLock = common::SharedLock<const FileIOContext>; using FileIOContextPtr = std::unique_ptr<FileIOContext>; using FileIOContextRef = Ref<FileIOContext>; using FileIOContextRefVector = std::vector<FileIOContextRef>; template<typename T> using ToFileIOContextPtrMap = std::map<T, FileIOContextPtr>; template<typename T> using ToFileIOContextRawPtrMap = std::map<T, FileIOContext*>; template<typename T> using ToFileIOContextRawPtrMapIterator = typename ToFileIOContextRawPtrMap<T>::iterator; // Interface to Ref<T>. void doRef(RefBadge badge, FileIOContext& entry); void doUnref(RefBadge badge, FileIOContext& entry); } // fuse } // mega ����������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode.h��������������������������������������0000664�0000000�0000000�00000012641�15162662266�0024133�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/error_or_forward.h> #include <mega/common/lockable.h> #include <mega/common/node_info_forward.h> #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/inode_badge_forward.h> #include <mega/fuse/common/inode_db_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/tags.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/types.h> #include <cstddef> #include <cstdint> #include <mutex> #include <string> namespace mega { namespace common { template<> struct LockableTraits<fuse::Inode> { using LockType = std::recursive_mutex; static void acquiring(const fuse::Inode& inode); static void acquired(const fuse::Inode& inode); static void couldntAcquire(const fuse::Inode& inode); static void released(const fuse::Inode& inode); static void tryAcquire(const fuse::Inode& inode); }; // LockableTraits<fuse::Inode> } // common namespace fuse { // Represents a filesystem entity. class Inode: public common::Lockable<Inode> { // Update this inode's name and parent. void moved(InodeDBLock& lock, const std::string& name, NodeHandle parentHandle); // Removes this instance from the inode database. virtual void remove(RefBadge badge, InodeDBLock lock) = 0; // Tracks how many actors reference this instance. unsigned long mReferences; // Has this inode been removed? mutable bool mRemoved; protected: Inode(InodeID id, const common::NodeInfo& info, InodeDB& inodeDB); // Update an inode's cached description. void info(const common::NodeInfo& info, InodeDBLock& lock); // The inode's identifier. const InodeID mID; // The database that contains this inode. InodeDB& mInodeDB; // Last known modification time. mutable m_time_t mModified; // Last known name. mutable std::string mName; // Last known parent. mutable NodeHandle mParentHandle; // Last known permissions. mutable accesslevel_t mPermissions; public: virtual ~Inode(); // Update this inode's access time. Inode* accessed() const; // Is this inode in the file cache? virtual bool cached() const = 0; // Check if this inode represents a directory. // // If the inode does represent a directory, a more specialized // reference to the inode is returned. // // If the inode doesn't represent a directory, a null reference // is returned. virtual DirectoryInodeRef directory(); // Check if this inode represents a file. // // If the inode does represent a file, a more specialized // reference to the inode is returned. // // If the inode doesn't represent a file, a null reference is // returned. virtual FileInodeRef file(); // What cloud node, if any, is associated with this inode? virtual NodeHandle handle() const = 0; // What is this inode's identifier? InodeID id() const; // Update an inode's cached description. virtual void info(const common::NodeInfo& info) = 0; // Retrieve a description of the entity this inode represents. virtual InodeInfo info() const = 0; // Move (or rename) this inode (assuming locks are held.) virtual Error move(InodeBadge badge, const std::string& name, DirectoryInodeRef parent) = 0; // Move (or rename) this inode. Error move(const std::string& name, DirectoryInodeRef parent); // Signal that this inode has been moved (or renamed.) void moved(const std::string& name, NodeHandle parentHandle); // What is this inode's name? const std::string& name(CachedOnlyTag) const; std::string name() const; // Retrieve a reference to this inode's parent. DirectoryInodeRef parent() const; // What cloud node is the parent of this inode? NodeHandle parentHandle(CachedOnlyTag) const; NodeHandle parentHandle() const; // Determine what permissions are applicable to this inode. accesslevel_t permissions() const; // Compute this inode's path relative to the specified node. common::ErrorOr<LocalPath> path(NodeHandle parentHandle) const; common::ErrorOr<LocalPath> path(InodeID parentID) const; // Increment this instance's reference counter. void ref(RefBadge badge); // Signal whether this inode has been removed. // // Typically called when an inode has been overwritten or unlinked. void removed(bool removed) const; // Query whether this inode has been removed. bool removed() const; // Replace other with this inode (assuming locks are held.) virtual Error replace(InodeBadge badge, InodeRef other, const std::string& otherName, DirectoryInodeRef otherParent) = 0; // Replace other with this inode (assuming locks are not held.) Error replace(InodeRef other, bool replaceDirectories); // Unlink this inode (without taking any locks.) virtual Error unlink(InodeBadge badge) = 0; // Unlink this inode (taking appriopriate locks.) Error unlink(); // Decrements this inode's reference counter. // // If the instance's referene counter drops to zero, // the instance is removed from the inode database. void unref(RefBadge badge); }; // Inode } // fuse } // mega �����������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_badge.h��������������������������������0000664�0000000�0000000�00000000774�15162662266�0025261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/inode_badge_forward.h> #include <mega/fuse/common/inode_forward.h> namespace mega { namespace fuse { class InodeBadge { friend class DirectoryInode; friend class FileInode; friend class Inode; InodeBadge() = default; public: InodeBadge(const InodeBadge& other) = default; ~InodeBadge() = default; }; // InodeBadge } // fuse } // mega ����sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_badge_forward.h������������������������0000664�0000000�0000000�00000000130�15162662266�0026767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { class InodeBadge; } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_cache.h��������������������������������0000664�0000000�0000000�00000003701�15162662266�0025253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/inode_cache_flags.h> #include <mega/fuse/common/inode_cache_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <atomic> #include <chrono> #include <condition_variable> #include <list> #include <map> #include <mutex> #include <thread> namespace mega { namespace fuse { class InodeCache { // Describes an inode in the cache. struct Entry; using EntryList = std::list<Entry>; using EntryListIterator = EntryList::iterator; using EntryPositionMap = std::map<InodeID, EntryListIterator>; using EntryPositionMapIterator = EntryPositionMap::iterator; using Lock = std::unique_lock<std::mutex>; // Periodically tries to reduce the cache's size. void loop(); // Reduce the cache to the specified size. // // Only entries age or older are evicted. InodeRefVector reduce(std::chrono::seconds age, Lock& lock, std::size_t size); // Wakes up the cleaner thread. std::condition_variable mCV; // Describes each inode in the cache. EntryList mEntries; // Dictates how we behave. InodeCacheFlags mFlags; // Serializes access to class members. mutable std::mutex mLock; // Tracks where each inode can be found in the cache. EntryPositionMap mPositions; // Signals the cleaner thread to terminate. std::atomic<bool> mTerminate; // Responsible for periodically cleaning the cache. std::thread mThread; public: explicit InodeCache(const InodeCacheFlags& flags); ~InodeCache(); // Add an inode to the cache. bool add(const Inode& inode); // Evict all inodes from the cache. void clear(); // Update this cache's flags. void flags(const InodeCacheFlags& flags); // Retrieve this cache's flags. InodeCacheFlags flags() const; // Remove an inode from the cache. bool remove(const Inode& inode); }; // InodeCache } // fuse } // mega ���������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_cache_forward.h������������������������0000664�0000000�0000000�00000000130�15162662266�0026770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { class InodeCache; } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_db.h�����������������������������������0000664�0000000�0000000�00000030547�15162662266�0024605�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/client_forward.h> #include <mega/common/database_forward.h> #include <mega/common/error_or_forward.h> #include <mega/common/lockable.h> #include <mega/common/node_event_forward.h> #include <mega/common/node_event_observer.h> #include <mega/common/node_event_queue_forward.h> #include <mega/common/node_info_forward.h> #include <mega/common/query.h> #include <mega/common/query_forward.h> #include <mega/common/scoped_query_forward.h> #include <mega/common/transaction_forward.h> #include <mega/fuse/common/any_lock_set_forward.h> #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/directory_inode_results.h> #include <mega/fuse/common/file_cache_forward.h> #include <mega/fuse/common/file_extension_db_forward.h> #include <mega/fuse/common/file_inode_forward.h> #include <mega/fuse/common/inode_cache_forward.h> #include <mega/fuse/common/inode_db_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/tags.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/types.h> #include <condition_variable> #include <map> #include <mutex> #include <string> #include <utility> namespace mega { namespace common { template<> struct LockableTraits<fuse::InodeDB>: public LockableTraitsCommon<fuse::InodeDB, std::recursive_mutex> {}; // LockableTraits<fuse::InodeDB> } // common namespace fuse { // Manages all inodes that are exposed to userspace. // // Every filesystem entity that is exposed to userspace is // represented by an "inode." // // An inode can represent either a directory or a file. // // Every inode has a unique identifier known as its "inode ID." // Once assigned, this identifier is never changed. // // The ID of a directory is the same as that directory's // node handle in the cloud. This is reasonable as directories // are not versioned. // // The ID of a file depends on whether that file existed in // the cloud. If the file does exist in the cloud then the file's // ID is same as that file's node handle. // // Files that don't exist in the cloud are assigned a unique // identifier that is generated in some fashion. // // In either case, it's important to note that the ID of a file // is not necessarily always the same as the cloud node that the // file represents. // // The reason for this is that when we update a file in the cloud, // we're not really updating that file in place. Instead, a new // version of that file is created. // // When a new version of a file is uploaded, the file's node handle // is updated but the file's ID remains unchanged. // // Once a file has been exposed to userspace under some ID, that file // can continue be accessed via that ID until it has been removed. class InodeDB final: public common::Lockable<InodeDB>, public common::NodeEventObserver { // So they can remove themselves from the database. friend class DirectoryInode; friend class FileInode; friend class Inode; // Clarity. class EventObserver; // So we can use an inode's name and parent handle as a key. using NodeHandleStringPtrPair = std::pair<NodeHandle, const std::string*>; // So we compare a pair's name by value rather than address. struct NodeHandleStringPtrPairLess { bool operator()(const NodeHandleStringPtrPair& lhs, const NodeHandleStringPtrPair& rhs) const { if (lhs.first < rhs.first) return true; if (rhs.first < lhs.first) return false; // Sanity. assert(lhs.second); assert(rhs.second); return *lhs.second < *rhs.second; } }; // NodeHandleStringPtrPairLess // For convenience. template<typename T> using FromNodeHandleStringPtrPairMap = std::map<NodeHandleStringPtrPair, T, NodeHandleStringPtrPairLess>; // What queries does the InodeDB perform? struct Queries { Queries(common::Database& database); // Add an inode to the database. common::Query mAddInode; // What inodes are present under the specified node handle? common::Query mGetChildrenByParentHandle; // What extension and ID is associated with the given node handle? common::Query mGetExtensionAndInodeIDByHandle; // Get an inode's extension and ID based on a name and parent handle. common::Query mGetExtensionAndInodeIDByNameAndParentHandle; // What inode is associated with the specified inode? common::Query mGetHandleByID; // What inode is associated with a given node handle? common::Query mGetInodeByHandle; // What inode is associated with a given ID? common::Query mGetInodeByID; // What ID is associated with the given node handle? common::Query mGetInodeIDByHandle; // Get an inode's ID based on name and parent handle. common::Query mGetInodeIDByNameAndParentHandle; // What inode are present under the specified node handle? common::Query mGetInodeIDByParentHandle; // Has a specific inode been modified? common::Query mGetModifiedByID; // What inodes have been modified? common::Query mGetModifiedInodes; // What is the next free inode ID? common::Query mGetNextInodeID; // Increment the next free inode ID. common::Query mIncrementNextInodeID; // Remove an inode specified by ID. common::Query mRemoveInodeByID; // Set an inode's handle, name and parent handle. common::Query mSetHandleNameParentHandleByID; // Specify whether an inode has been modified. common::Query mSetModifiedByID; // Set an inode's name and parent handle. common::Query mSetNameParentHandleByID; }; // Queries // Add a new inode to the index. InodeRef add(InodePtr (InodeDB::*build)(const common::NodeInfo&), const common::NodeInfo& info); // Add a new file to the database. InodeID addFile(const FileExtension& extension, const std::string& name, NodeHandle parentHandle, common::Transaction& transaction); // Instantiate a new directory inode. InodePtr buildDirectory(const common::NodeInfo& info); // Instantiate a new file inode. InodePtr buildFile(const common::NodeInfo& info); // Try and retrieve a reference to a parent's child. InodeRef child(const DirectoryInode& parent, const std::string& name) const; // Try and retrieve a reference to the specified child. InodeRef child(const std::string& name, NodeHandle parentHandle, MemoryOnlyTag) const; // Called with a child has been added under some parent. void childAdded(const Inode& indode, const std::string& name, NodeHandle parentHandle); // Called when a child has been removed from some parent. void childRemoved(const Inode& inode, const std::string& name, NodeHandle parentHandle); // Retrieve a reference to a directory's children. InodeRefVector children(const DirectoryInode& parent) const; // Are we discarding node events? bool discard() const; // Load an inode from the client. InodeRef get(common::Client& client, NodeHandle handle) const; // Load an inode from the file cache by handle. InodeRef get(FileCache& fileCache, NodeHandle handle, AnyLockSet locks) const; // Load an inode from the file cache by ID. InodeRef get(FileCache& fileCache, InodeID id, AnyLockSet locks) const; // Load an inode from the file cache. InodeRef get(FileCache& fileCache, AnyLockSet locks, common::ScopedQuery query, common::Transaction transaction) const; // Specify what cloud node is associated with the specified file. void handle(FileInode& file, NodeHandle& oldHandle, NodeHandle newHandle); // Check if parent contains the named child. InodeID hasChild(const DirectoryInode& parent, const std::string& name) const; // Check if a directory contains any children. common::ErrorOr<bool> hasChildren(const DirectoryInode& directory) const; // Make a new directory below parent. common::ErrorOr<MakeInodeResult> makeDirectory(const platform::Mount& mount, const std::string& name, DirectoryInodeRef parent); // Make a new file below parent. common::ErrorOr<MakeInodeResult> makeFile(const platform::Mount& mount, const std::string& name, DirectoryInodeRef parent); // Retrieve a list of all the modified inodes. using NodeHandleInodeIDPair = std::pair<NodeHandle, InodeID>; using NodeHandleInodeIDPairVector = std::vector<NodeHandleInodeIDPair>; auto modified() const -> NodeHandleInodeIDPairVector; // Move (or rename) an inode. Error move(InodeRef source, const std::string& targetName, DirectoryInodeRef targetParent); // Move (or rename) a file. Error move(FileInodeRef source, const std::string& targetName, DirectoryInodeRef targetParent); // Remove a directory inode from the index. void remove(const DirectoryInode& inode, InodeDBLock lock); // Remove a file inode from the index. void remove(const FileInode& inode, InodeDBLock lock); // Replace a file. Error replace(FileInodeRef source, FileInodeRef target, const std::string& targetName, DirectoryInodeRef targetParent); // Replace an inode. Error replace(DirectoryInodeRef source, DirectoryInodeRef target, const std::string& targetName, DirectoryInodeRef targetParent); // Unlink an inode. Error unlink(InodeRef inode); // Unlink a file. Error unlink(FileInodeRef file); // Tracks which inode is associated with what node handle. mutable ToInodeRawPtrMap<NodeHandle> mByHandle; // Tracks which inode is associated with what ID. mutable ToInodePtrMap<InodeID> mByID; // Tracks which inode is visible under what parent with what name. mutable FromNodeHandleStringPtrPairMap<InodeRawPtr> mByParentHandleAndName; // Signalled when an inode is purged from memory. std::condition_variable_any mCV; // The context this database is associated with. platform::ServiceContext& mContext; // Whether we should discard node events. bool mDiscard; // What queries do we perform? mutable Queries mQueries; public: InodeDB(platform::ServiceContext& context); ~InodeDB(); // Add a memory-only inode to the database. void add(const FileInode& inode); // Retrieve the cache associated with this database. InodeCache& cache() const; // Wait for all inodes to be cleared from memory. void clear(); // Retrieve the client associated with this database. common::Client& client() const; // Called by the client when its view of the cloud is current. void current(); // Discard node events. void discard(bool discard); // Check if an inode is in the database. bool exists(InodeID id) const; // Retrieve the file cache associated with this database. FileCache& fileCache() const; // Retrieve the file extension DB associated with this database. FileExtensionDB& fileExtensionDB() const; // Retrieve an inode by handle. InodeRef get(NodeHandle handle, bool inMemoryOnly = false) const; // Retrieve an inode by ID. InodeRef get(InodeID id, bool inMemoryOnly = false) const; // Locate an inode based on a path relative to some parent. using LookupResult = std::pair<InodeRef, Error>; template<typename Path> auto lookup(const Path& path, NodeHandle parent, std::string* name = nullptr) const -> typename EnableIfPath<Path, LookupResult>::type; // Specify whether a file has been modified. void modified(InodeID id, bool modified); // Query whether a file has been modified. bool modified(InodeID id) const; // Return a reference to all modified inodes under the specified parent. FileInodeRefVector modified(NodeHandle parent) const; // Called when nodes have been updated in the cloud. void updated(common::NodeEventQueue& events) override; }; // InodeDB } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_db_forward.h���������������������������0000664�0000000�0000000�00000000235�15162662266�0026320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mutex> namespace mega { namespace fuse { class InodeDB; using InodeDBLock = std::unique_lock<const InodeDB>; } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/inode_forward.h������������������������������0000664�0000000�0000000�00000001456�15162662266�0025661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/ref_forward.h> #include <map> #include <memory> #include <mutex> #include <set> #include <vector> namespace mega { namespace fuse { class Inode; using InodeLock = std::unique_lock<const Inode>; using InodeLockPtr = std::shared_ptr<InodeLock>; using InodePtr = std::unique_ptr<Inode>; using InodeRawPtr = Inode*; using InodeRef = Ref<Inode>; using InodeRefSet = std::set<InodeRef>; using InodeRefVector = std::vector<InodeRef>; template<typename T> using ToInodePtrMap = std::map<T, InodePtr>; template<typename T> using FromInodeRefMap = std::map<InodeRef, T>; template<typename T> using ToInodeRawPtrMap = std::map<T, InodeRawPtr>; // Interface to Ref<T>. void doRef(RefBadge badge, Inode& inode); void doUnref(RefBadge badge, Inode& inode); } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/mount.h��������������������������������������0000664�0000000�0000000�00000007751�15162662266�0024205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/task_executor_flags_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount_db_forward.h> #include <mega/fuse/common/mount_flags.h> #include <mega/fuse/common/mount_forward.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_inode_id_forward.h> #include <mega/fuse/platform/context_forward.h> #include <mega/fuse/platform/mount_db_forward.h> #include <future> #include <mutex> namespace mega { namespace fuse { // Represents an individual mapping between the cloud and local disk. class Mount: public std::enable_shared_from_this<Mount> { // Records information about a pinned inode. struct PinnedInodeInfo; // Invalidate a pinned inode. void invalidatePin(PinnedInodeInfo& info, std::unique_lock<std::mutex>& lock); // What directories (or files) are currently open? platform::ContextRawPtrSet mContexts; // Serializes access to mContexts. std::mutex mContextsLock; // Signalled when the mount is destroyed. std::promise<void> mDisabled; // Specifies how the mount should behave. MountFlags mFlags; // Protects access to this mount's flags. mutable std::mutex mLock; // What cloud node are we mapping to? const NodeHandle mHandle; // Used to keep (pin) inodes in memory. FromInodeIDMap<PinnedInodeInfo> mPins; // Protects access to mPins. std::mutex mPinsLock; protected: Mount(const MountInfo& info, platform::MountDB& mountDB); ~Mount(); // Try and retrieve a reference to the specified inode. InodeRef get(MountInodeID id, bool memoryOnly = false) const; // Pin an inode in memory. void pin(InodeRef inode, const InodeInfo& info); // Unpin a pinned inode. void unpin(InodeRef inode, std::uint64_t num); public: // Add a context to our context set. void contextAdded(platform::ContextBadge badge, platform::Context& context); // Remove a context from our context set. void contextRemoved(platform::ContextBadge badge, platform::Context& context); // Retrieve a reference to this mount's disabled event. std::future<void> disabled(); // Called when the mount has been enabled. void enabled(); // Update this mount's executor flags. virtual void executorFlags(const common::TaskExecutorFlags& flags); // Update this mount's flags. void flags(const MountFlags& flags); // Retrieve this mount's flags. MountFlags flags() const; // Which cloud node is this mount mapping to? NodeHandle handle() const; // Retrieve this mount's description. MountInfo info() const; // Invalidate an inode's attributes. virtual void invalidateAttributes(InodeID id) = 0; // Invalidate an inode's data. virtual void invalidateData(InodeID id, m_off_t offset, m_off_t size) = 0; virtual void invalidateData(InodeID id) = 0; // Invalidate a directory entry. virtual void invalidateEntry(const std::string& name, InodeID child, InodeID parent) = 0; virtual void invalidateEntry(const std::string& name, InodeID parent) = 0; // Invalidate a pinned inode. void invalidatePin(InodeID id); // Invalidate any pinned inodes. void invalidatePins(InodeRefSet& invalidated); // Translate a mount-speicifc inode ID to a system-wide inode ID. virtual InodeID map(MountInodeID id) const = 0; // Translate a system-wide inode ID to a mount-specific inode ID. virtual MountInodeID map(InodeID id) const = 0; // What is this mount's name? std::string name() const; // What local path is this mount mapping from? virtual common::NormalizedPath path() const = 0; // Is this mount writable? bool writable() const; // Does this mount allow accessing from self process? bool allowSelfAccess() const; // Which database contains this mount? platform::MountDB& mMountDB; }; // Mount } // fuse } // mega �����������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/mount_db.h�����������������������������������0000664�0000000�0000000�00000015236�15162662266�0024647�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/client_forward.h> #include <mega/common/lockable.h> #include <mega/common/normalized_path_forward.h> #include <mega/common/query.h> #include <mega/common/task_executor_flags_forward.h> #include <mega/fuse/common/file_cache_forward.h> #include <mega/fuse/common/file_explorer_view.h> #include <mega/fuse/common/inode_db_forward.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/mount_db_forward.h> #include <mega/fuse/common/mount_flags_forward.h> #include <mega/fuse/common/mount_info_forward.h> #include <mega/fuse/common/service_callbacks.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/types.h> #include <functional> namespace mega { namespace common { template<> struct LockableTraits<fuse::MountDB>: public LockableTraitsCommon<fuse::MountDB, std::mutex> {}; // LockableTraits<fuse::MountDB> } // common namespace fuse { // Manages mappings between the cloud and the local disk. // // Each mapping is like a one-way portal: They can manipulate // entities in the cloud through that mapping's local path. class MountDB: public common::Lockable<MountDB> { friend class platform::Mount; // Bundles up all of the MountDB's queries. struct Queries { Queries(common::Database& database); // Add a mount to the database. common::Query mAddMount; // Get a mount by name. common::Query mGetMountByName; // What are a mount's flags? common::Query mGetMountFlagsByName; // What inode is the mount associated with? common::Query mGetMountInodeByName; // What path is associated with a given name? common::Query mGetMountPathByName; // Get a mount's startup state. common::Query mGetMountStartupStateByName; // Get a list of all known mounts. common::Query mGetMounts; // What mounts should be enabled at startup? common::Query mGetMountsEnabledAtStartup; // Remove a specified mount. common::Query mRemoveMountByName; // Remove transient mounts. common::Query mRemoveTransientMounts; // Set a mount's flags. common::Query mSetMountFlagsByName; // Set a mount's startup state. common::Query mSetMountStartupStateByName; }; // Queries // Checks whether info is a valid description of a mount. MountResult check(const MountInfo& info); // Checks whether a mount's local path is valid. virtual MountResult check(const common::Client& client, const MountInfo& info) const = 0; // Checks whether a mount's name is valid. virtual MountResult checkName(const std::string& name) const = 0; // Perform platform-specific deinitialization. virtual void doDeinitialize(); // Enable all persistent mounts. void enable(); // Invalidate information cached by all mounts. void invalidate(); // Query which mount is associated with a name. platform::MountPtr mount(const std::string& name) const; // Query which mount is associated with a path. platform::MountPtr mount(const LocalPath& path) const; // What cloud nodes are currently mounted? NodeHandleVector mounted() const; // Removes the specified mount from our indexes. platform::MountPtr remove(platform::Mount& mount); // Tracks which mounts are associated with what handle. platform::ToMountPtrSetMap<NodeHandle> mByHandle; // Tracks which mount is associated with what name. platform::ToMountPtrMap<std::string> mByName; // Tracks which mount is associated with what path. platform::ToMountPtrMap<LocalPath> mByPath; // How should we handle the "nodes current" event? void (MountDB::*mOnCurrent)(); // What queries do we perform? mutable Queries mQueries; // What file explorer view flags std::atomic<FileExplorerView> mFileExplorerView{FILE_EXPLORER_VIEW_LIST}; protected: MountDB(platform::ServiceContext& context); ~MountDB(); // Disable all enabled mounts. void disable(); // Tracks whether we have any callbacks in progress. common::ActivityMonitor mActivities; public: // Add a new mount to the database. MountResult add(const MountInfo& info); // Retrieve the client that contains this Mount DB. common::Client& client() const; // What mount contains the specified path? MountInfoPtr contains(const LocalPath& path, bool enabled, LocalPath* relativePath = nullptr) const; // Called by the client when its view of the cloud is current. void current(); // Prepare the Mount DB for destruction. void deinitialize(); // Disable an enabled mount. void disable(MountDisabledCallback callback, const std::string& name, bool remember); // Disable all mounts associated with the specified node. void disable(NodeHandle handle); // Execute a function on each enabled mount. void each(std::function<void(platform::Mount&)> function); // Enable a disabled mount. MountResult enable(const std::string& name, bool remember); // Query whether the specified mount is enabled. bool enabled(const std::string& name) const; // Update executor flags. void executorFlags(const common::TaskExecutorFlags& flags); // Query executor flags. common::TaskExecutorFlags executorFlags() const; // Update file explorer view. void fileExplorerView(FileExplorerView view); // Query file explorer view. FileExplorerView fileExplorerView() const; // Retrieve a reference to the file cache. FileCache& fileCache(); // Update an existing mount's flags. MountResult flags(const std::string& name, const MountFlags& flags); // Query an existing mount's flags. MountFlagsPtr flags(const std::string& name) const; // Retrieve a description of an existing mount. MountInfoPtr get(const std::string& name) const; // Retrieve a list of known mounts. MountInfoVector get(bool onlyEnabled) const; // Retrieve a reference to the inode DB. InodeDB& inodeDB(); // Query which path a named mount is associated with. common::NormalizedPath path(const std::string& name) const; // Prune stale mount entries from the database. MountResult prune(); // Remove a disabled mount from the database. MountResult remove(const std::string& name); // Check whether the specified path is "syncable." bool syncable(const common::NormalizedPath& path) const; // The context this database belongs to. platform::ServiceContext& mContext; }; // MountDB } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/mount_db_forward.h���������������������������0000664�0000000�0000000�00000000235�15162662266�0026364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mutex> namespace mega { namespace fuse { class MountDB; using MountDBLock = std::unique_lock<const MountDB>; } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/mount_forward.h������������������������������0000664�0000000�0000000�00000000123�15162662266�0025713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { class Mount; } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/path_adapter.h�������������������������������0000664�0000000�0000000�00000006531�15162662266�0025472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/type_traits.h> #include <mega/filesystem.h> #include <mega/fuse/common/path_adapter_forward.h> #include <algorithm> #include <string> namespace mega { template<typename Traits> struct IsPath<fuse::detail::PathAdapter<Traits>>: std::true_type {}; // IsPath<fuse::detail::PathAdapter<Traits>> namespace fuse { namespace detail { template<typename TraitsType> class PathAdapter { using SizeType = typename TraitsType::SizeType; using StringType = typename TraitsType::StringType; using ValueType = typename TraitsType::ValueType; const ValueType* mPath = nullptr; SizeType mLength = 0; public: PathAdapter() = default; explicit PathAdapter(const StringType& path): mPath(path.data()), mLength(path.size()) {} PathAdapter(const PathAdapter& other) = default; PathAdapter& operator=(const PathAdapter& rhs) = default; // Clear the path. void clear() { operator=(PathAdapter()); } // Query whether the path is empty. bool empty() const { return !mPath || !mLength; } // Locate the next path separator. bool findNextSeparator(SizeType& index) const { auto* current = mPath + index; auto* end = mPath + mLength; // Index is at, or beyond, the end of this path. if (current >= end) return false; // Search for the next separator. auto* next = std::find(current, end, TraitsType::separator()); // Couldn't find another separator. if (next == end) return false; // Compute index of separator. index = static_cast<SizeType>(next - mPath); // Let the caller know we've found another separator. return true; } // Query whether the path has any further components. bool hasNextPathComponent(SizeType index) const { // Not strictly correct but good enough. return index < mLength; } // Retrieve the next path component. bool nextPathComponent(SizeType& index, PathAdapter& component) const { auto* current = mPath + index; auto* end = mPath + mLength; // Index is at, or beyond, the end of this path. if (current >= end) return component.clear(), false; // For convenience. auto separator = [](const ValueType& character) { return character == TraitsType::separator(); }; // separator // Skip any leading separators. current = std::find_if_not(current, end, separator); // Path is effectively empty. if (current == end) return component.clear(), false; // Find the end of this component. end = std::find(current, end, TraitsType::separator()); // Populate component. component.mPath = current; component.mLength = static_cast<SizeType>(end - current); // Compute index of next separator. index = static_cast<SizeType>(end - mPath); // Let the caller know we've extracted a component. return true; } // Translate component into a cloud-friendly form. const std::string toName(const FileSystemAccess&) const { // Return transcoded path to caller. return TraitsType::toUTF8(mPath, mLength); } }; // PathAdapter<T> } // detail } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/path_adapter_forward.h�����������������������0000664�0000000�0000000�00000000222�15162662266�0027205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace detail { template<typename Traits> class PathAdapter; } // detail } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/ref.h����������������������������������������0000664�0000000�0000000�00000006674�15162662266�0023622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/ref_forward.h> #include <cassert> #include <utility> namespace mega { namespace fuse { // Represents a reference to some reference-counted instance. template<typename T> class Ref { template<typename U> friend class Ref; // What instance are we referencing? T* mInstance; public: Ref(): mInstance(nullptr) {} template<typename U> explicit Ref(U* instance): mInstance(instance) { if (mInstance) doRef(RefBadge(), *mInstance); } template<typename U> Ref(U* instance, AdoptRefTag): mInstance(instance) { assert(instance); } Ref(const Ref& other): Ref(other.mInstance) {} template<typename U> Ref(const Ref<U>& other): Ref(other.mInstance) {} Ref(Ref&& other) noexcept: mInstance(std::move(other.mInstance)) { other.mInstance = nullptr; } template<typename U> Ref(Ref<U>&& other) noexcept: mInstance(std::move(other.mInstance)) { other.mInstance = nullptr; } ~Ref() { if (mInstance) doUnref(RefBadge(), *mInstance); } operator bool() const { return mInstance != nullptr; } Ref& operator=(const Ref& rhs) { Ref temp(rhs); swap(temp); return *this; } template<typename U> Ref& operator=(const Ref<U>& rhs) { Ref temp(rhs); swap(temp); return *this; } Ref& operator=(Ref&& rhs) noexcept { Ref temp(std::move(rhs)); swap(temp); return *this; } template<typename U> Ref& operator=(Ref<U>&& rhs) noexcept { Ref temp(std::move(rhs)); swap(temp); return *this; } T& operator*() { assert(mInstance); return *mInstance; } const T& operator*() const { assert(mInstance); return *mInstance; } T* operator->() { assert(mInstance); return mInstance; } const T* operator->() const { assert(mInstance); return mInstance; } bool operator==(const Ref& rhs) const { return mInstance == rhs.mInstance; } template<typename U> bool operator==(const Ref<U>& rhs) const { return mInstance == rhs.mInstance; } template<typename U> bool operator<(const Ref<U>& rhs) const { return mInstance < rhs.mInstance; } bool operator!=(const Ref& rhs) const { return mInstance != rhs.mInstance; } template<typename U> bool operator!=(const Ref<U>& rhs) const { return mInstance != rhs.mInstance; } bool operator!() const { return !mInstance; } T* get() { return mInstance; } T* release() { auto* instance = mInstance; mInstance = nullptr; return instance; } template<typename U> void reset(U* instance) { Ref temp(instance); swap(temp); } void reset(std::nullptr_t) { T* dummy{}; reset(dummy); } void reset() { reset(nullptr); } template<typename U> void swap(Ref<U>& other) { using std::swap; swap(mInstance, other.mInstance); } }; // Ref<T> template<typename T> void swap(Ref<T>& lhs, Ref<T>& rhs) { lhs.swap(rhs); } } // fuse } // mega ��������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/ref_forward.h��������������������������������0000664�0000000�0000000�00000001030�15162662266�0025323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { // Specify that a Ref should "steal" an existing reference. struct AdoptRefTag {}; // Forward declaration for all Ref types. template<typename T> class Ref; // Ensures that only Ref<T> instances can call certain methods. class RefBadge { template<typename T> friend class Ref; RefBadge() = default; public: RefBadge(const RefBadge& other) = default; ~RefBadge() = default; }; // RefBadge // For convenience. constexpr AdoptRefTag AdoptRef; } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mega/fuse/common/tags.h���������������������������������������0000664�0000000�0000000�00000000302�15162662266�0023762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { struct CachedOnlyTag {}; struct MemoryOnlyTag {}; constexpr CachedOnlyTag CachedOnly; constexpr MemoryOnlyTag MemoryOnly; } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mount.cpp�����������������������������������������������������0000664�0000000�0000000�00000015176�15162662266�0021375�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/service_context.h> #include <cassert> #include <tuple> #include <utility> namespace mega { namespace fuse { using namespace common; struct Mount::PinnedInodeInfo { PinnedInodeInfo(InodeRef inode, const InodeInfo& info): mInode(std::move(inode)), mName(info.mName), mParentID(info.mParentID), mPinCount(0) {} // The inode that we're pinning in memory. InodeRef mInode; // The name of the inode when it was pinned. const std::string mName; // The parent of the inode when it was pinned. const InodeID mParentID; // How many times the inode has been pinned. std::uint64_t mPinCount; }; /* PinnedInodeInfo */ void Mount::invalidatePin(PinnedInodeInfo& info, std::unique_lock<std::mutex>&) { // Convenience. auto& inode = info.mInode; // Invalidate this inode's data if it's a file. if (inode->file()) invalidateData(inode->id()); // Invalidate the inode's attributes. invalidateAttributes(inode->id()); // Inode has no parent. if (!info.mParentID) return; // Invalidate the inode's directory entry. invalidateEntry(info.mName, info.mParentID); } Mount::Mount(const MountInfo& info, platform::MountDB& mountDB): enable_shared_from_this(), mContexts(), mContextsLock(), mDisabled(), mFlags(info.mFlags), mHandle(info.mHandle), mPins(), mPinsLock(), mMountDB(mountDB) {} Mount::~Mount() { // Release dangling contexts. auto contexts = ([this]() { std::lock_guard<std::mutex> guard(mContextsLock); return std::move(mContexts); })(); for (auto* context: contexts) delete context; // Broadcast a mount disabled event. emitEvent(mMountDB.client(), {name(), MOUNT_SUCCESS, MOUNT_DISABLED}); // Let any waiters know we've been disabled. mDisabled.set_value(); } InodeRef Mount::get(MountInodeID id, bool memoryOnly) const { return mMountDB.mContext.mInodeDB.get(map(id), memoryOnly); } void Mount::pin(InodeRef inode, const InodeInfo& info) { // Sanity. assert(inode); assert(inode->id() == info.mID); // Convenience. auto id = info.mID; std::lock_guard<std::mutex> guard(mPinsLock); FUSEDebugF("Pinning inode %s", toString(id).c_str()); // Where should we insert this inode's pin record? auto position = mPins.lower_bound(id); // Inode hasn't already been pinned. if (position == mPins.end() || position->first != id) { position = mPins.emplace_hint(position, std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(std::move(inode), info)); } // Increase pin count. position->second.mPinCount++; FUSEDebugF("Inode %s now has %zu reference(s)", toString(id).c_str(), position->second.mPinCount); } void Mount::unpin(InodeRef inode, std::uint64_t num) { assert(inode); // The inode to be unpinned, if any. InodeRef ref; // Decrease pin count and latch inode if needed. { std::lock_guard<std::mutex> guard(mPinsLock); FUSEDebugF("Unpinning inode %s", toString(inode->id()).c_str()); // Has this indoe been pinned? auto i = mPins.find(inode->id()); // Sanity. assert(i != mPins.end()); assert(i->second.mPinCount >= num); // Decrement pin count. i->second.mPinCount -= num; FUSEDebugF("Inode %s now has %zu reference(s)", toString(inode->id()).c_str(), i->second.mPinCount); // Pin still has references. if (i->second.mPinCount) return; // Latch inode so we can release it outside of mPinsLock. ref = std::move(i->second.mInode); // Inode's no longer pinned. mPins.erase(i); } } void Mount::contextAdded(platform::ContextBadge, platform::Context& context) { std::lock_guard<std::mutex> guard(mContextsLock); auto result = mContexts.emplace(&context); assert(result.second); static_cast<void>(result); } void Mount::contextRemoved(platform::ContextBadge, platform::Context& context) { std::lock_guard<std::mutex> guard(mContextsLock); mContexts.erase(&context); } std::future<void> Mount::disabled() { return mDisabled.get_future(); } void Mount::enabled() { emitEvent(mMountDB.client(), {name(), MOUNT_SUCCESS, MOUNT_ENABLED}); } void Mount::executorFlags(const TaskExecutorFlags&) {} void Mount::flags(const MountFlags& flags) { std::lock_guard<std::mutex> guard(mLock); mFlags = flags; } MountFlags Mount::flags() const { std::lock_guard<std::mutex> guard(mLock); return mFlags; } NodeHandle Mount::handle() const { return mHandle; } MountInfo Mount::info() const { std::lock_guard<std::mutex> guard(mLock); MountInfo info; info.mFlags = mFlags; info.mHandle = mHandle; info.mPath = path(); return info; } void Mount::invalidatePin(InodeID id) { std::unique_lock<std::mutex> guard(mPinsLock); // Has this inode been pinned? auto i = mPins.find(id); // Inode's been pinned: Invalidate it. if (i != mPins.end()) invalidatePin(i->second, guard); } void Mount::invalidatePins(InodeRefSet& invalidated) { std::unique_lock<std::mutex> guard(mPinsLock); // Iterate over pinned inodes, invalidating each in turn. for (auto i = mPins.begin(); i != mPins.end();) { // Get our hands on the pin's info. auto& pin = i++->second; // Let the caller know we invalidated this inode. invalidated.emplace(pin.mInode); // Invalidate the inode. invalidatePin(pin, guard); } } std::string Mount::name() const { std::lock_guard<std::mutex> guard(mLock); return mFlags.mName; } bool Mount::writable() const { std::lock_guard<std::mutex> guard(mLock); return !mFlags.mReadOnly; } bool Mount::allowSelfAccess() const { std::lock_guard<std::mutex> guard(mLock); return mFlags.mAllowSelfAccess; } } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/mount_db.cpp��������������������������������������������������0000664�0000000�0000000�00000070716�15162662266�0022043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/scoped_query.h> #include <mega/common/transaction.h> #include <mega/filesystem.h> #include <mega/fuse/common/any_lock.h> #include <mega/fuse/common/any_lock_set.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_db.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/service_context.h> #include <atomic> #include <cassert> #include <stdexcept> namespace mega { namespace fuse { using namespace common; MountDB::Queries::Queries(Database& mDatabase): mAddMount(mDatabase.query()), mGetMountByName(mDatabase.query()), mGetMountFlagsByName(mDatabase.query()), mGetMountInodeByName(mDatabase.query()), mGetMountPathByName(mDatabase.query()), mGetMountStartupStateByName(mDatabase.query()), mGetMounts(mDatabase.query()), mGetMountsEnabledAtStartup(mDatabase.query()), mRemoveMountByName(mDatabase.query()), mRemoveTransientMounts(mDatabase.query()), mSetMountFlagsByName(mDatabase.query()), mSetMountStartupStateByName(mDatabase.query()) { mAddMount = "insert into mounts values ( " " :enable_at_startup, " " :id, " " :name, " " :path, " " :persistent, " " :read_only " ")"; mGetMountByName = "select * from mounts where name = :name"; mGetMountFlagsByName = "select enable_at_startup " " , name " " , persistent " " , read_only " " from mounts " " where name = :name"; mGetMountInodeByName = "select id from mounts where name = :name"; mGetMountPathByName = "select path from mounts where name = :name"; mGetMountStartupStateByName = "select enable_at_startup " " , persistent " " from mounts " " where name = :name"; mGetMounts = "select * from mounts"; mGetMountsEnabledAtStartup = "select name " " from mounts " " where enable_at_startup = true " " and persistent = true"; mRemoveMountByName = "delete from mounts where name = :name"; mRemoveTransientMounts = "delete from mounts where persistent = false"; mSetMountFlagsByName = "update mounts " " set enable_at_startup = :enable_at_startup " " , name = :name " " , persistent = :persistent " " , read_only = :read_only " " where name = :current_name"; mSetMountStartupStateByName = "update mounts " " set enable_at_startup = :enable_at_startup " " , persistent = :persistent " " where name = :name"; } MountResult MountDB::check(const MountInfo& info) { // Convenience. auto& handle = info.mHandle; // User's specified a bogus node handle. if (handle.isUndef()) { FUSEErrorF("Invalid cloud handle specified", toNodeHandle(handle).c_str()); return MOUNT_REMOTE_UNKNOWN; } // Check that the specified cloud node exists. auto info_ = client().get(handle); // Cloud node doesn't exist. if (!info_) { FUSEErrorF("Cloud node doesn't exist: %s", toNodeHandle(handle).c_str()); return MOUNT_REMOTE_UNKNOWN; } // Cloud node isn't a directory. if (!info_->mIsDirectory) { FUSEErrorF("Cloud node is not a directory: %s", toNodeHandle(handle).c_str()); return MOUNT_REMOTE_FILE; } // Make sure the target path isn't claimed by a sync. if (!client().mountable(info.mPath)) { FUSEErrorF("Local path is being synchronized: %s", info.mPath.toPath(false).c_str()); return MOUNT_LOCAL_SYNCING; } // Check local path. return check(client(), info); } void MountDB::doDeinitialize() {} void MountDB::enable() try { inodeDB().current(); fileCache().current(); // What mounts should we try and enable? std::vector<std::string> mounts; // Compute list of mounts to enable. { auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountsEnabledAtStartup); // What mounts should be enabled at startup? query.execute(); // Collect names of enabled mounts. for (; query; ++query) mounts.emplace_back(query.field("name").get<std::string>()); } // Try and enable each mount. for (auto& name: mounts) { MountEvent event; event.mName = name; event.mType = MOUNT_ENABLED; // Try and enable the mount. event.mResult = enable(name, false); // Couldn't enable the mount. if (event.mResult != MOUNT_SUCCESS) { FUSEWarningF("Unable to enable persistent mount \"%s\" due to error: %s", name.c_str(), toString(event.mResult)); emitEvent(client(), event); // Try and enable the next mount. continue; } // Mount's been enabled. FUSEInfoF("Successfully enabled persistent mount \"%s\"", name.c_str()); } } catch (std::runtime_error& exception) { FUSEErrorF("Unable to enable persistent mounts: %s", exception.what()); } void MountDB::invalidate() { // Tracks which inodes have been invalidated. InodeRefSet invalidated; // Tracks which nodes have been mounted. NodeHandleSet mounted; // Iterate over each mount, invalidating any pinned inodes. each( [&](platform::Mount& mount) { // Invalidate any inodes pinned by this mount. mount.invalidatePins(invalidated); // Remember that this node was mounted. mounted.emplace(mount.handle()); }); // Mark inodes whose node has been removed. for (auto ref: invalidated) { auto handle = ref->handle(); // Inode was never present in the cloud. if (handle.isUndef()) continue; // Mark inode as removed if necessary. if (auto exists = client().exists(handle)) ref->removed(!*exists); } // Try and disable each mount associated with a removed node. for (auto handle: mounted) { if (!client().exists(handle)) disable(handle); } } platform::MountPtr MountDB::mount(const std::string& name) const { // Locate mount by name. auto i = mByName.find(name); if (i != mByName.end()) return i->second; return nullptr; } platform::MountPtr MountDB::mount(const LocalPath& path) const { // Locate mount by path. auto i = mByPath.find(path); if (i != mByPath.end()) return i->second; return nullptr; } platform::MountPtr MountDB::remove(platform::Mount& mount) { MountDBLock guard(*this); // Is the mount in the index? auto p = mByPath.find(mount.path()); // Mount's not in the index. if (p == mByPath.end()) return nullptr; // Latch a reference to the mount. auto ptr = std::move(p->second); // Sanity. assert(ptr.get() == &mount); // Remove the mount from the handle index. auto h = mByHandle.find(mount.handle()); // Sanity. auto count = h->second.erase(ptr); assert(count); // No other mounts are associated with this handle. if (h->second.empty()) mByHandle.erase(h); // Remove the mount from the path index. mByPath.erase(p); // Remove the mount from the name index. count = mByName.erase(mount.name()); // Sanity. assert(count); static_cast<void>(count); // Return mount to caller. return ptr; } MountDB::MountDB(platform::ServiceContext& context): Lockable(), mByHandle(), mByName(), mByPath(), mOnCurrent(&MountDB::enable), mQueries(context.mDatabase), mActivities(), mContext(context) {} MountDB::~MountDB() { assert(!mActivities.active()); assert(mByHandle.empty()); assert(mByName.empty()); assert(mByPath.empty()); FUSEDebug1("Mount DB destroyed"); } void MountDB::disable() { MountDBLock lock(*this); // Latch mounts. auto byHandle = std::move(mByHandle); auto byName = std::move(mByName); auto byPath = std::move(mByPath); mByHandle.clear(); mByName.clear(); mByPath.clear(); // Release lock. lock.unlock(); // Release mounts. byHandle.clear(); byName.clear(); byPath.clear(); } MountResult MountDB::add(const MountInfo& info) try { // Check that the mount's description is sane. // // We're not holding the lock as check(...) calls the client. auto result = check(info); // Description isn't sane. if (result != MOUNT_SUCCESS) return result; auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountInodeByName); // Make sure the name isn't already associated with a mount. query.param(":name").set(info.name()); query.execute(); // A mount's already associated with this name. if (query) return MOUNT_NAME_TAKEN; // Add the description to the database. query = transaction.query(mQueries.mAddMount); info.serialize(query); query.execute(); transaction.commit(); // Mount's been added to the database. return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { // Unexpected error while adding the mount. FUSEErrorF("Unable to add mount: %s", exception.what()); return MOUNT_UNEXPECTED; } Client& MountDB::client() const { return mContext.client(); } void MountDB::current() { // Acquire lock. MountDBLock guard(*this); // Calls our current handler. auto current = [this](Activity&, void (MountDB::*onCurrent)(), const Task& task) { // Task hasn't been cancelled. if (!task.cancelled()) (this->*onCurrent)(); }; // current // Queue current handler for execution. mContext.mExecutor.execute( std::bind(std::move(current), mActivities.begin(), mOnCurrent, std::placeholders::_1), true); // Subsequent events cause us to invalidate all mounts. mOnCurrent = &MountDB::invalidate; } void MountDB::deinitialize() { FUSEDebug1("Deinitializating Mount DB"); // Tear down any enabled mounts. disable(); // Wait for any callbacks to complete. mActivities.waitUntilIdle(); // Perform platform-specific deinitialization. doDeinitialize(); FUSEDebug1("Mount DB deinitialized"); } MountInfoPtr MountDB::contains(const LocalPath& path, bool enabled, LocalPath* relativePath) const { // Records where the mount's path ends. std::size_t index = 0; // Keeps track of the most specific match, if any. const MountInfo* mount = nullptr; // Retrieve a list of all (enabled) mounts. auto mounts = get(enabled); // Search for a matching mount. for (auto& m: mounts) { // Convenience. auto& path_ = m.mPath; // Path isn't within this mount. if (!path_.isContainingPathOf(path, &index)) continue; // This mount is a better match. if (!mount || path_.isContainingPathOf(mount->mPath, &index)) mount = &m; } // No mount contains this path. if (!mount) return nullptr; // Latch relative path, if requested. if (relativePath) *relativePath = path.subpathFrom(index); // Return description to caller. return std::make_unique<MountInfo>(*mount); } void MountDB::disable(MountDisabledCallback callback, const std::string& name, bool remember) { auto guard = lockAll(mContext.mDatabase, *this); MountEvent event; event.mName = name; event.mType = MOUNT_DISABLED; // Emits the event and queues callback for execution. auto emitEvent = [&](MountResult result) { // Latch result. event.mResult = result; // Emit the event. fuse::emitEvent(client(), event); // Forward result to callback. auto wrapper = [result](MountDisabledCallback& callback, const Task&) { callback(result); }; // wrapper // Queue callback for execution. client().execute(std::bind(std::move(wrapper), std::move(callback), std::placeholders::_1)); }; // emitEvent try { auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountStartupStateByName); // Query the mount's startup state. query.param(":name").set(name); query.execute(); // No mount associated with specified path. if (!query) { FUSEErrorF("No mount associated with name: %s", name.c_str()); return emitEvent(MOUNT_UNKNOWN); } // Latch startup state. bool enableAtStartup = query.field("enable_at_startup").get<bool>(); bool persistent = query.field("persistent").get<bool>(); enableAtStartup = enableAtStartup && !remember; persistent = persistent || remember; // Update the mount's startup state. query = transaction.query(mQueries.mSetMountStartupStateByName); query.param(":enable_at_startup").set(enableAtStartup); query.param(":name").set(name); query.param(":persistent").set(persistent); query.execute(); transaction.commit(); // Is the mount currently enabled? auto mount = this->mount(name); // Mount's not enabled: We're done. if (!mount) return emitEvent(MOUNT_SUCCESS); auto flags = mount->flags(); // Keep mount consistent with the database. flags.mEnableAtStartup = enableAtStartup; flags.mPersistent = persistent; // Update the mount's flags. mount->flags(flags); // Unmount the mount. mContext.mUnmounter.unmount(std::move(callback), std::move(mount)); } catch (std::runtime_error& exception) { // Unexpected error while disabling mount. FUSEErrorF("Unable to disable mount %s: %s", name.c_str(), exception.what()); emitEvent(MOUNT_UNEXPECTED); } } void MountDB::disable(NodeHandle handle) { // Acquire lock. MountDBLock guard(*this); // Are any mounts associated with this handle? auto h = mByHandle.find(handle); // No mounts are associated with this handle. if (h == mByHandle.end()) return; // Sanity. assert(!h->second.empty()); // Called when a mount has been disabled. auto disabled = [](const LocalPath& path, MountResult result) { // Couldn't disable the mount. if (result != MOUNT_SUCCESS) { FUSEWarningF("Unable to disable mount \"%s\": %s", path.toPath(false).c_str(), toString(result)); return; } // Mount's been disabled. FUSEInfoF("Successfully disabled mount \"%s\"", path.toPath(false).c_str()); }; // disabled FUSEInfoF("Attempting to disable mounts associated with %s", toNodeHandle(handle).c_str()); // Disable mounts associated with handle. for (auto mount: h->second) { // Try and disable the mount. mContext.mUnmounter.unmount(std::bind(disabled, mount->path(), std::placeholders::_1), mount); } } void MountDB::each(std::function<void(platform::Mount&)> function) { // Sanity. assert(function); // Acquire vector of active mounts. auto mounts = ([this]() { // Acquire lock. MountDBLock guard(*this); // Instantiate vector. platform::MountPtrVector mounts; // Reserve necessary space. mounts.reserve(mByPath.size()); // Populate vector with active mounts. for (auto& m : mByPath) mounts.emplace_back(m.second); // Return vector to caller. return mounts; })(); // Execute the function on each mount. for (auto& mount: mounts) function(*mount); } MountResult MountDB::enable(const std::string& name, bool remember) try { auto lock = lockAll(mContext.mDatabase, *this); // The mount associated with this path is already enabled. // // NOTE: We're calling enabled() manually to force the generation of a // MOUNT_ENABLED event. This is necessary because we want to generate // this event whenever enabling a mount succeeds but normally, a mount // will only generate the event when it becomes functional. // // That is, when you enable a mount that's already enabled, it's not // transitioning into a functional state so, we have to generate the // event manually. if (auto mount = this->mount(name)) return mount->enabled(), MOUNT_SUCCESS; auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountByName); // Check if the mount's present in the database. query.param(":name").set(name); query.execute(); // Mount's not in the database. if (!query) { FUSEErrorF("No mount associated with name: %s", name.c_str()); return MOUNT_UNKNOWN; } // Deserialize the mount's description. auto info = MountInfo::deserialize(query); auto& flags = info.mFlags; // Compute the mount's new startup state. flags.mEnableAtStartup |= remember; flags.mPersistent |= remember; // Update the mount's startup state. query = transaction.query(mQueries.mSetMountStartupStateByName); query.param(":enable_at_startup").set(flags.mEnableAtStartup); query.param(":name").set(name); query.param(":persistent").set(flags.mPersistent); query.execute(); transaction.commit(); // Make sure the transaction's truly dead. query.reset(); // Release the lock so we can call the client. lock.unlock(); // Check that the mount's description is still sane. auto result = check(info); // Description's no longer sane. // // The reason we don't bail on LOCAL_EXISTS is that this // error will always be signaled when you try to enable // multiple mounts that use the same path. It'd also be // generated in some cases if multiple threads tried to // enable the same mount at the same time. // // So, instead of bailing here, bail later if needed. That // way, we can check if the mount's already been enabled and // return success. Or, if two distinct mounts with the same // path are being enabled, we can return a more meaningful // LOCAL_TAKEN error below. if (result != MOUNT_SUCCESS && result != MOUNT_LOCAL_EXISTS) return result; // Reacquire the lock. lock.lock(); // Another thread's enabled the mount. if (auto mount = this->mount(name)) return MOUNT_SUCCESS; // Another thread's enabled a mount with the same path. if (auto mount = this->mount(info.mPath)) { FUSEErrorF("Path %s already taken by mount: %s", info.mPath.toPath(false).c_str(), mount->name().c_str()); return MOUNT_LOCAL_TAKEN; } // Mount path might already exist on disk. if (result != MOUNT_SUCCESS) return result; // Convenience. auto& self = static_cast<platform::MountDB&>(*this); // Instantiate the mount. auto mount = std::make_shared<platform::Mount>(info, self); // Add the mount to the index. mByHandle[info.mHandle].emplace(mount); mByName[name] = mount; mByPath[mount->path()] = mount; // Release lock. lock.unlock(); // Flush any files modified by this mount. fileCache().flush(*mount, inodeDB().modified(mount->handle())); // Mount's enabled. return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { // Unexpected error while enabling mount. FUSEErrorF("Unable to enable mount %s: %s", name.c_str(), exception.what()); return MOUNT_UNEXPECTED; } bool MountDB::enabled(const std::string& name) const { // Acquire lock. MountDBLock guard(*this); // Is a mount with this path enabled? return mByName.count(name) > 0; } void MountDB::executorFlags(const TaskExecutorFlags& flags) { each( [&](platform::Mount& mount) { mount.executorFlags(flags); }); } TaskExecutorFlags MountDB::executorFlags() const { return mContext.serviceFlags().mMountExecutorFlags; } void MountDB::fileExplorerView(FileExplorerView view) { mFileExplorerView = view; } FileExplorerView MountDB::fileExplorerView() const { return mFileExplorerView; } FileCache& MountDB::fileCache() { return mContext.mFileCache; } MountResult MountDB::flags(const std::string& currentName, const MountFlags& flags) try { auto guard = lockAll(mContext.mDatabase, *this); // User's specified an invalid name. if (const auto ret = checkName(flags.mName); ret != MOUNT_SUCCESS) return ret; auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountByName); // Make sure the mount's new name, if any, is unique. if (currentName != flags.mName) { query.param(":name").set(flags.mName); query.execute(); // Mount's new name isn't unique. if (query) { FUSEErrorF("Name \"%s\" already taken by another mount.", flags.mName.c_str()); return MOUNT_NAME_TAKEN; } } // Update the mount's flags. query = transaction.query(mQueries.mSetMountFlagsByName); flags.serialize(query); query.param(":current_name").set(currentName); query.execute(); // No mount is associated with this path. if (!query.changed()) return MOUNT_UNKNOWN; // Mount's enabled. if (auto mount = this->mount(currentName)) { // Keep mount consistent with the database. mount->flags(flags); // Keep the by-name index up to date. mByName.erase(currentName); mByName.emplace(flags.mName, mount); } transaction.commit(); // Mount's flags have been updated. return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { // Unexpected error while updating the mount's flags. FUSEErrorF("Unable to update flags for mount %s: %s", currentName.c_str(), exception.what()); return MOUNT_UNEXPECTED; } MountFlagsPtr MountDB::flags(const std::string& name) const try { auto guard = lockAll(mContext.mDatabase, *this); // Fast path: Mount's enabled and in memory. if (auto mount = this->mount(name)) return std::make_unique<MountFlags>(mount->flags()); // Retrieve mount's flags from the database. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountFlagsByName); query.param(":name").set(name); query.execute(); // No mount's associated with the specified path. if (!query) return nullptr; // Deserialize the mount's flags. auto flags = MountFlags::deserialize(query); // And return them to the caller. return std::make_unique<MountFlags>(std::move(flags)); } catch (std::runtime_error& exception) { // Unexpected error while retrieving the mount's flags. FUSEErrorF("Unable to retrieve flags for mount %s: %s", name.c_str(), exception.what()); return nullptr; } MountInfoPtr MountDB::get(const std::string& name) const try { auto guard = lockAll(mContext.mDatabase, *this); // Fast path: Mount's enabled and in memory. if (auto mount = this->mount(name)) return std::make_unique<MountInfo>(mount->info()); // Retrieve the mount's description from the database. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountByName); query.param(":name").set(name); query.execute(); // No mount associated with the specified path. if (!query) return nullptr; // Deserialize the mount's description. auto info = MountInfo::deserialize(query); // And return it to the caller. return std::make_unique<MountInfo>(std::move(info)); } catch (std::runtime_error& exception) { // Unexpected error while retrieving the mount's description. FUSEErrorF("Unable to retrieve information for mount %s: %s", name.c_str(), exception.what()); return nullptr; } MountInfoVector MountDB::get(bool onlyEnabled) const try { auto guard = lockAll(mContext.mDatabase, *this); MountInfoSet<MountInfoNameLess> mounts; // Copy descriptions of enabled mounts. // // Note that we do this even when onlyEnabled is false. // // The reason is that some mounts may only have a defined path when they // are enabled and we want to provide that path to the caller when // possible. // // We rely on the set above to prevent entries from the database from // overwriting what we've retrieved from memory. for (auto& i: mByPath) mounts.emplace(i.second->info()); // Caller's interested only in mounts that are enabled. if (onlyEnabled) return MountInfoVector(mounts.begin(), mounts.end()); // Retrieve descriptions of all known mounts from the database. auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMounts); query.execute(); // Deserialize each description. for (; query; ++query) mounts.emplace(MountInfo::deserialize(query)); // And return them all to the caller. return MountInfoVector(mounts.begin(), mounts.end()); } catch (std::runtime_error& exception) { // Unepected error while retrieving mount descriptions. FUSEErrorF("Unable to retrieve mount information: %s", exception.what()); return MountInfoVector(); } InodeDB& MountDB::inodeDB() { return mContext.mInodeDB; } NormalizedPath MountDB::path(const std::string& name) const try { auto guard = lockAll(mContext.mDatabase, *this); if (auto mount = this->mount(name)) return mount->path(); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mGetMountPathByName); query.param(":name").set(name); query.execute(); if (query && !query.field("path").null()) return query.field("path").get<LocalPath>(); return NormalizedPath(); } catch (std::runtime_error& exception) { FUSEErrorF("Unable to retrieve paths of mounts with name %s: %s", name.c_str(), exception.what()); return NormalizedPath(); } MountResult MountDB::prune() try { auto guard = lockAll(mContext.mDatabase, *this); auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mRemoveTransientMounts); query.execute(); transaction.commit(); return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to prune transient mounts: %s", exception.what()); return MOUNT_UNEXPECTED; } MountResult MountDB::remove(const std::string& name) try { auto guard = lockAll(mContext.mDatabase, *this); if (mByName.count(name)) { FUSEErrorF("Can't remove an enabled mount: %s", name.c_str()); return MOUNT_BUSY; } auto transaction = mContext.mDatabase.transaction(); auto query = transaction.query(mQueries.mRemoveMountByName); query.param(":name").set(name); query.execute(); transaction.commit(); return MOUNT_SUCCESS; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to remove mount %s: %s", name.c_str(), exception.what()); return MOUNT_UNEXPECTED; } bool MountDB::syncable(const NormalizedPath& path) const { // Acquire lock. MountDBLock guard(*this); // Check if any active mount is related to path. for (auto& m: mByPath) { if (path.related(m.second->path())) return false; } // Path isn't related to any mount. return true; } } // fuse } // mega ��������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021172�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/CMakeLists.txt����������������������������������������0000664�0000000�0000000�00000000036�15162662266�0023731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(integration) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023515�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/CMakeLists.txt����������������������������0000664�0000000�0000000�00000001442�15162662266�0026256�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if(NOT TARGET test_integration) return() endif() add_subdirectory(mega) target_include_directories(test_integration PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(test_integration PRIVATE client.cpp common_tests.cpp mount_event_observer.cpp mount_tests.cpp parameters.cpp printers.cpp real_client.cpp sync_tests.cpp test.cpp test_base.cpp utility.cpp ) if(ENABLE_SYNC) target_sources(test_integration PRIVATE sync_tests.cpp) endif() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/client.cpp��������������������������������0000664�0000000�0000000�00000014773�15162662266�0025513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/normalized_path.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_flags.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <future> namespace mega { namespace fuse { namespace testing { using common::ErrorOr; using common::NormalizedPath; using common::testing::Path; Client::Client(const std::string& clientName, const Path& databasePath, const Path& storagePath): common::testing::Client(clientName, databasePath, storagePath), mMountEventObservers(), mMountEventObserversLock() {} Client::~Client() {} void Client::mountEvent(const MountEvent& event) { std::lock_guard<std::mutex> guard(mMountEventObserversLock); // Inform observers that an event has been emitted. auto i = mMountEventObservers.begin(); while (i != mMountEventObservers.end()) { // Check if the observer's still alive. auto observer = i->lock(); // Observer's dead. if (!observer) { i = mMountEventObservers.erase(i); continue; } // Let the observer know an event has been emitted. observer->emitted(event); // Move to the next observer. ++i; } } MountResult Client::addMount(const MountInfo& info) { return fuseService().add(info); } ErrorOr<InodeInfo> Client::describe(const Path& path) const { return fuseService().describe(path.localPath()); } MountResult Client::disableMount(const std::string& name, bool remember) { // So we can wait for the mount to be disabled. std::promise<MountResult> notifier; // Called when the mount has been disabled. auto disabled = [¬ifier](MountResult result) { notifier.set_value(result); }; // disabled // Try and disable the mount. fuseService().disable(std::move(disabled), name, remember); // Wait for the mount to be disabled. auto result = notifier.get_future().get(); // Couldn't disable the mount. if (result != MOUNT_SUCCESS) FUSEErrorF("Couldn't disable mount: %s: %s", name.c_str(), toString(result)); // Return the result to the caller. return result; } MountResult Client::disableMounts(bool remember) { // What mounts are currently enabled? auto mounts = this->mounts(true); // No mounts are enabled. if (mounts.empty()) return MOUNT_SUCCESS; // How long should we wait for a mount to become idle? constexpr auto idleTime = std::chrono::seconds(4); // How many times should we try and disable a mount? constexpr auto numAttempts = 4; // Try and disable each mount. while (!mounts.empty()) { // Pick a mount to disable. auto& mount = mounts.back(); // Keep trying to disable the mount if necessary. auto disabled = [](MountResult result) { return result == MOUNT_UNKNOWN || result == MOUNT_SUCCESS; }; // disabled // Try and disable the mount. auto result = disableMount(mount.name(), remember); // Keep trying to disable the mount if necessary. for (auto attempts = 0; !disabled(result) && attempts < numAttempts; ++attempts) { // Give the mount a little time to become idle. std::this_thread::sleep_for(idleTime); // Try and disable the mount again. result = disableMount(mount.name(), remember); } // Try and disable the next mount. mounts.pop_back(); } // We weren't able to disable all the mounts. if (!mounts.empty()) return MOUNT_BUSY; // All mounts have been disabled. return MOUNT_SUCCESS; } MountResult Client::discard(bool discard) { return fuseService().discard(discard); } MountResult Client::enableMount(const std::string& name, bool remember) { const auto ret = fuseService().enable(name, remember); // Tell FUSE mount that we need to access mount if (const auto flags = fuseService().flags(name); flags) { flags->mAllowSelfAccess = true; fuseService().flags(name, *flags); } return ret; } bool Client::isCached(const Path& path) const { return fuseService().cached(path.localPath()); } MountEventObserverPtr Client::mountEventObserver() { auto observer = MountEventObserver::create(); std::lock_guard<std::mutex> guard(mMountEventObserversLock); mMountEventObservers.emplace(observer); return observer; } bool Client::mountEnabled(const std::string& name) const { return fuseService().enabled(name); } MountResult Client::mountFlags(const std::string& name, const MountFlags& flags) { return fuseService().flags(name, flags); } MountFlagsPtr Client::mountFlags(const std::string& name) const { return fuseService().flags(name); } MountInfoPtr Client::mountInfo(const std::string& name) const { return fuseService().get(name); } NormalizedPath Client::mountPath(const std::string& name) const { return fuseService().path(name); } MountInfoVector Client::mounts(bool onlyEnabled) const { return fuseService().get(onlyEnabled); } MountResult Client::removeMount(const std::string& name) { // Try and remove the mount. auto result = fuseService().remove(name); // Couldn't remove the mount. if (result != MOUNT_SUCCESS) FUSEErrorF("Unable to remove mount: %s: %s", name.c_str(), toString(result)); // Return result to caller. return result; } MountResult Client::removeMounts(bool disable) { auto result = MOUNT_SUCCESS; // Disable enabled mounts if requested. if (disable) result = disableMounts(true); // Mounts couldn't be disabled. if (result != MOUNT_SUCCESS) return result; // What mounts are known to us? auto mounts = this->mounts(false); // Try and remove each mount. while (!mounts.empty()) { // Select a mount to remove. auto& mount = mounts.back(); // Try and remove the mount. auto result = removeMount(mount.name()); // Couldn't remove the mount. if (result != MOUNT_SUCCESS) return result; // Mount's been removed. mounts.pop_back(); } // All mounts have been removed. return result; } } // testing } // fuse } // mega �����sdk-10.11.0/src/fuse/supported/common/testing/integration/common_tests.cpp��������������������������0000664�0000000�0000000�00000053003�15162662266�0026734�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "megafs.h" #include "sdk_test_data_provider.h" #include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/testing/cloud_path.h> #include <mega/common/testing/file.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/printers.h> #include <mega/fuse/common/testing/test_base.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/testing/wrappers.h> #include <filesystem> #include <fstream> namespace mega { namespace fuse { namespace testing { struct FUSECommonTests: TestBase {}; // FUSECommonTests using namespace common; using common::testing::File; using common::testing::Path; using common::testing::randomBytes; using common::testing::randomName; using common::testing::waitFor; static handle fsidOf(const Path& path); static bool makeFile(const Path& path, const std::string& data); static bool makeFile(const Path& path, std::size_t size); static std::string readFile(const Path& path); TEST_F(FUSECommonTests, cached_false_when_directory) { ASSERT_FALSE(ClientW()->isCached(MountPathW())); ASSERT_FALSE(ClientW()->isCached(MountPathW() / "sd0")); } TEST_F(FUSECommonTests, cached_false_when_not_cached) { ASSERT_FALSE(ClientW()->isCached(MountPathW() / "sf0")); } TEST_F(FUSECommonTests, cached_false_when_unknown) { ASSERT_FALSE(ClientW()->isCached(MountPathW() / "sfx")); ASSERT_FALSE(ClientW()->isCached(MountPathW() / "sf0" / "sd0")); } TEST_F(FUSECommonTests, cached_true_when_cached) { ASSERT_FALSE(ClientW()->isCached(MountPathW() / "sf0")); auto data = readFile(MountPathW() / "sf0"); ASSERT_FALSE(data.empty()); ASSERT_TRUE(ClientW()->isCached(MountPathW() / "sf0")); std::error_code error; EXPECT_TRUE(fs::remove(MountPathW() / "sf0", error)); ASSERT_FALSE(error); ASSERT_TRUE(waitFor( [&]() { return !ClientW()->isCached(MountPathW() / "sf0"); }, mDefaultTimeout)); } TEST_F(FUSECommonTests, cloud_add) { auto handle = ClientW()->makeDirectory("sdx", "/x/s"); ASSERT_TRUE(handle); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return fs::is_directory(MountPathW() / "sdx", error) && fsidOf(MountPathW() / "sdx") == handle->as8byte(); }, mDefaultTimeout)); EXPECT_TRUE(fs::is_directory(MountPathW() / "sdx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sdx"), handle->as8byte()); } TEST_F(FUSECommonTests, cloud_add_replace) { ASSERT_TRUE(makeFile(MountPathW() / "sfx", 32)); auto handle = ClientW()->makeDirectory("sfx", "/x/s"); ASSERT_TRUE(handle); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return fs::is_directory(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == handle->as8byte(); }, mDefaultTimeout)); EXPECT_TRUE(fs::is_directory(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), handle->as8byte()); } TEST_F(FUSECommonTests, cloud_move) { auto id = fsidOf(MountPathW() / "sf0"); ASSERT_NE(id, UNDEF); ASSERT_EQ(ClientW()->move("sf0", "/x/s/sf0", "/x/s/sd0"), API_OK); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathW() / "sf0", error) && fs::is_regular_file(MountPathW() / "sd0" / "sf0", error) && fsidOf(MountPathW() / "sd0" / "sf0") == id; }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathW() / "sf0", error)); EXPECT_FALSE(error); EXPECT_TRUE(fs::is_regular_file(MountPathW() / "sd0" / "sf0", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sd0" / "sf0"), id); } TEST_F(FUSECommonTests, cloud_move_rename_replace) { ASSERT_TRUE(makeFile(MountPathW() / "sfx", 32)); auto info = ClientW()->get("/x/s/sd0/sd0d0"); ASSERT_TRUE(info); ASSERT_EQ(ClientW()->move("sfx", "/x/s/sd0/sd0d0", "/x/s"), API_OK); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathW() / "sd0" / "sd0d0", error) && fs::is_directory(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == info->mHandle.as8byte(); }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathW() / "sd0" / "sd0d0", error)); EXPECT_FALSE(error); EXPECT_TRUE(fs::is_directory(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), info->mHandle.as8byte()); } TEST_F(FUSECommonTests, cloud_move_replace) { ASSERT_TRUE(makeFile(MountPathW() / "sfx", 32)); auto info = ClientW()->get("/x/s/sd0"); ASSERT_TRUE(info); ASSERT_EQ(ClientW()->move("sfx", "/x/s/sd0", "/x/s"), API_OK); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return fs::is_directory(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == info->mHandle.as8byte(); }, mDefaultTimeout)); EXPECT_TRUE(fs::is_directory(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), info->mHandle.as8byte()); } TEST_F(FUSECommonTests, cloud_remove) { ASSERT_EQ(ClientW()->remove("/x/s/sf0"), API_OK); ASSERT_TRUE(waitFor( [&]() { std::error_code error; return !fs::exists(MountPathW() / "sf0", error); }, mDefaultTimeout)); } TEST_F(FUSECommonTests, cloud_rename) { auto info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); ASSERT_EQ(ClientW()->move("sfx", "/x/s/sf0", "/x/s"), API_OK); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathW() / "sf0", error) && fs::exists(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == info->mHandle.as8byte(); }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathW() / "sf0", error)); EXPECT_FALSE(error); EXPECT_TRUE(fs::exists(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), info->mHandle.as8byte()); } TEST_F(FUSECommonTests, cloud_rename_replace) { ASSERT_TRUE(makeFile(MountPathW() / "sfx", 64)); auto info = ClientW()->get("/x/s/sd0"); ASSERT_TRUE(info); ASSERT_EQ(ClientW()->move("sfx", "/x/s/sd0", "/x/s"), API_OK); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathW() / "sd0", error) && fs::is_directory(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == info->mHandle.as8byte(); }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); EXPECT_TRUE(fs::is_directory(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), info->mHandle.as8byte()); } TEST_F(FUSECommonTests, cloud_replace) { ASSERT_TRUE(makeFile(MountPathW() / "sfx", 32)); auto handle = ClientW()->makeDirectory("sfx", "/x/s"); ASSERT_TRUE(handle); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return fs::is_directory(MountPathW() / "sfx", error) && fsidOf(MountPathW() / "sfx") == handle->as8byte(); }, mDefaultTimeout)); EXPECT_TRUE(fs::is_directory(MountPathW() / "sfx", error)); EXPECT_FALSE(error); EXPECT_EQ(fsidOf(MountPathW() / "sfx"), handle->as8byte()); } TEST_F(FUSECommonTests, duplicate_names) { // Add a few duplicate directories. ASSERT_EQ(ClientW()->makeDirectory("sd0", "/x/s").errorOr(API_OK), API_OK); ASSERT_EQ(ClientW()->makeDirectory("sd0", "/x/s").errorOr(API_OK), API_OK); // Sanity. auto names = ClientW()->childNames("/x/s"); ASSERT_EQ(names.errorOr(API_OK), API_OK); EXPECT_EQ(names->count("sd0"), 0u); // Wait for the directory to become inaccessible. std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathW() / "sd0", error); }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); } TEST_F(FUSECommonTests, file_cache_load) { // Create a new client so not to interfere with later tests. auto client = CreateClient("filecache_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(1), API_OK); auto handle = client->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Add a new mount. MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mFlags.mPersistent = true; mount.mPath = client->storagePath() / "s"; UNIX_ONLY(ASSERT_TRUE(fs::create_directories(Path(mount.mPath)))); ASSERT_EQ(client->addMount(mount), MOUNT_SUCCESS); // Enable the mount. ASSERT_EQ(client->enableMount(mount.name(), false), MOUNT_SUCCESS); // Create a new file. auto sfxData = randomBytes(32); auto sfxPath = client->storagePath() / "s" / "sfx"; ASSERT_TRUE(makeFile(sfxPath, sfxData)); // Modify an existing file. auto sf0Path = client->storagePath() / "s" / "sf0"; ASSERT_TRUE(makeFile(sf0Path, 32)); // Capture the file's ID. auto id = fsidOf(sfxPath); ASSERT_NE(id, UNDEF); // Disable the mount. auto observer = client->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_EQ(client->disableMounts(false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); // Log the client out and back in again (to prevent file upload.) { auto sessionToken = client->sessionToken(); ASSERT_EQ(sessionToken.errorOr(API_OK), API_OK); ASSERT_EQ(client->logout(true), API_OK); ASSERT_EQ(client->login(*sessionToken), API_OK); } // Re-enable the mount. ASSERT_EQ(client->enableMount(mount.name(), false), MOUNT_SUCCESS); // Try and read the file's data back. ASSERT_EQ(readFile(sfxPath), sfxData); // Erase sf0. ASSERT_EQ(client->remove("/x/s/sf0"), API_OK); // Make sure we can't access sf0. std::error_code error; ASSERT_FALSE(fs::exists(sf0Path, error)); ASSERT_FALSE(error); // Disable the mount. observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_EQ(client->disableMounts(false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); // Log out the client. ASSERT_EQ(client->logout(false), API_OK); std::filebuf x; } TEST_F(FUSECommonTests, reload) { // Create a new client so not to interfere with future tests. auto client = CreateClient("reload_" + randomName()); // Log the client in. ASSERT_EQ(client->login(1), API_OK); auto handle = client->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Add a new mount. MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = client->storagePath() / "s"; UNIX_ONLY(ASSERT_TRUE(fs::create_directories(Path(mount.mPath)))); ASSERT_EQ(client->addMount(mount), MOUNT_SUCCESS); // Enable the mount. ASSERT_EQ(client->enableMount(mount.name(), false), MOUNT_SUCCESS); // Bring a few inodes into memory. // sd0 will be renamed to sdx. auto sd0i = fsidOf(client->storagePath() / "s" / "sd0"); ASSERT_NE(sd0i, UNDEF); // sd1 will be removed. ASSERT_NE(fsidOf(client->storagePath() / "s" / "sd1"), UNDEF); // sd2 will be added. ASSERT_EQ(fsidOf(client->storagePath() / "s" / "sd2"), UNDEF); // sf0 will be moved to sdx/sf0. auto sf0i = fsidOf(client->storagePath() / "s" / "sf0"); ASSERT_NE(sf0i, UNDEF); // sf2 will be added. ASSERT_EQ(fsidOf(client->storagePath() / "s" / "sf2"), UNDEF); // Tell FUSE to ignore node events. ASSERT_EQ(client->discard(true), MOUNT_SUCCESS); // Rename sd0 to sdx. ASSERT_EQ(client->move("sdx", "/x/s/sd0", "/x/s"), API_OK); // Remove sd1. ASSERT_EQ(client->remove("/x/s/sd1"), API_OK); // Add sd2. ASSERT_EQ(client->makeDirectory("sd2", "/x/s").errorOr(API_OK), API_OK); // Move sf0 to sdx/sf0. ASSERT_EQ(client->move("sf0", "/x/s/sf0", "/x/s/sdx"), API_OK); // Add sf2. { // Create a file to be uploaded. File sf2("sf2", "sf2", mScratchPath); // Upload the file. ASSERT_EQ(client->upload("/x/s", sf2.path()).errorOr(API_OK), API_OK); } // Simulate ETOOMANY by reloading the cloud tree. ASSERT_EQ(client->reload(), API_OK); EXPECT_TRUE(waitFor( [&]() { // sd0 should no longer be visible. if (fsidOf(client->storagePath() / "s" / "sd0") != UNDEF) return false; // sd1 should no longer be visible. if (fsidOf(client->storagePath() / "s" / "sd1") != UNDEF) return false; // sd2 should be visible. if (fsidOf(client->storagePath() / "s" / "sd2") == UNDEF) return false; // sf0 should no longer be visible. if (fsidOf(client->storagePath() / "s" / "sf0") != UNDEF) return false; // sf2 should now be visible. if (fsidOf(client->storagePath() / "s" / "sf2") == UNDEF) return false; // sd0 should now be known as sdx. if (fsidOf(client->storagePath() / "s" / "sdx") != sd0i) return false; // sf0 should be visible under sdx. return fsidOf(client->storagePath() / "s" / "sdx" / "sf0") == sf0i; }, mDefaultTimeout)); EXPECT_EQ(fsidOf(client->storagePath() / "s" / "sd0"), UNDEF); EXPECT_EQ(fsidOf(client->storagePath() / "s" / "sd1"), UNDEF); EXPECT_NE(fsidOf(client->storagePath() / "s" / "sd2"), UNDEF); EXPECT_EQ(fsidOf(client->storagePath() / "s" / "sf0"), UNDEF); EXPECT_NE(fsidOf(client->storagePath() / "s" / "sf2"), UNDEF); EXPECT_EQ(fsidOf(client->storagePath() / "s" / "sdx"), sd0i); EXPECT_EQ(fsidOf(client->storagePath() / "s" / "sdx" / "sf0"), sf0i); } TEST_F(FUSECommonTests, share_changes_permissions) { // Convenience. constexpr auto U_R = UNIX_OR_WINDOWS(fs::perms::owner_read, fs::perms::_File_attribute_readonly); UNIX_ONLY(constexpr auto U_W = fs::perms::owner_write); constexpr auto U_X = fs::perms::owner_exec; constexpr auto U_RW = UNIX_OR_WINDOWS(U_R | U_W, fs::perms::all); constexpr auto U_RWX = U_RW | U_X; constexpr auto U_RX = UNIX_OR_WINDOWS(U_R | U_X, U_RWX); // Conveniently queries a file's permissions. auto permissions = [](const Path& path) -> ErrorOr<fs::perms> { auto error = std::error_code(); auto status = fs::status(path, error); if (!error) return status.permissions(); return unexpected(API_EREAD); }; // permissions // Verify initial permissions are as we expect. auto perms = permissions(MountPathRS() / "sd0"); ASSERT_EQ(perms.errorOr(API_OK), API_OK); EXPECT_EQ(perms.value(), U_RX); perms = permissions(MountPathRS() / "sf0"); ASSERT_EQ(perms.errorOr(API_OK), API_OK); EXPECT_EQ(perms.value(), U_R); perms = permissions(MountPathWS() / "sd0"); ASSERT_EQ(perms.errorOr(API_OK), API_OK); EXPECT_EQ(perms.value(), U_RWX); perms = permissions(MountPathWS() / "sf0"); ASSERT_EQ(perms.errorOr(API_OK), API_OK); EXPECT_EQ(perms.value(), U_RW); // Change permissions of shares. { auto email = ClientS()->email(); auto rs = ClientR()->handle("/x/s"); ASSERT_EQ(rs.errorOr(API_OK), API_OK); auto ws = ClientW()->handle("/x/s"); ASSERT_EQ(ws.errorOr(API_OK), API_OK); // Make read-only share writable. ASSERT_EQ(ClientR()->share(email, *rs, FULL), API_OK); // Make writalbe share read-only. ASSERT_EQ(ClientW()->share(email, *ws, RDONLY), API_OK); // Wait for sharee to recognize permission changes. ASSERT_TRUE(waitFor( [&]() { auto rs_ = ClientS()->get(*rs); auto ws_ = ClientS()->get(*ws); return (rs_ && rs_->mPermissions == FULL) && (ws_ && ws_->mPermissions == RDONLY); }, mDefaultTimeout)); } // Wait for mounts to recognize new permissions. ASSERT_TRUE(waitFor( [&]() { auto perms = permissions(MountPathRS() / "sd0"); if (!perms || *perms != U_RWX) return false; perms = permissions(MountPathRS() / "sf0"); if (!perms || *perms != U_RW) return false; perms = permissions(MountPathWS() / "sd0"); if (!perms || *perms != U_RX) return false; perms = permissions(MountPathWS() / "sf0"); return perms && *perms == U_R; }, mDefaultTimeout)); } TEST_F(FUSECommonTests, supports_entities_with_international_names) { static const std::string directoryName = "測試目錄"; static const std::string fileName = "測試文件"; // Convenience. auto sd0 = ClientW()->handle("x/s/sd0"); ASSERT_EQ(sd0.errorOr(API_OK), API_OK); auto sd0f0 = ClientW()->handle("x/s/sd0/sd0f0"); ASSERT_EQ(sd0f0.errorOr(API_OK), API_OK); // Give some cloud entities an internationalized name. ASSERT_EQ(ClientW()->move(fileName, "x/s/sd0/sd0f0", "x/s/sd0"), API_OK); ASSERT_EQ(ClientW()->move(directoryName, "x/s/sd0", "x/s"), API_OK); // Wait for our changes to be recognized by the SDK. ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get(*sd0); if (!info || info->mName != directoryName) return false; info = ClientW()->get(*sd0f0); return info && info->mName == fileName; }, mDefaultTimeout)); // Wait for our changes to be visible via our mount. ASSERT_TRUE(waitFor( [&]() { // Convenience. auto dp = MountPathW() / directoryName; auto fp = dp / fileName; // Check for presence of directory and file. std::error_code error; return fs::is_directory(dp, error) && !error && fs::is_regular_file(fp, error) && !error; }, mDefaultTimeout)); // Make sure we can actually operate on these entities. ASSERT_EQ(readFile(MountPathW() / directoryName / fileName), "sd0f0"); // Restore original names. std::error_code error; fs::rename(MountPathW() / directoryName / fileName, MountPathW() / directoryName / "sd0f0", error); ASSERT_FALSE(error); fs::rename(MountPathW() / directoryName, MountPathW() / "sd0", error); ASSERT_FALSE(error); // Wait for changes to be visible in the cloud. ASSERT_TRUE(waitFor( [&]() { return ClientW()->handle("x/s/sd0/sd0f0") == sd0f0 && ClientW()->handle("x/s/sd0") == sd0; }, mDefaultTimeout)); } TEST_F(FUSECommonTests, adds_thumbnails_to_images) { constexpr fatype THUMBNAIL = 0; constexpr fatype PREVIEW = 1; static constexpr const char* imageName = "logo.png"; static constexpr const char* imagePath = "/x/s/logo.png"; // Download a test image from artifactory. auto mountPath = MountPathW() / imageName; ASSERT_TRUE(getFileFromArtifactory(std::string{"test-data/"} + imageName, mountPath)); // Make sure the file is flushed to the cloud. ASSERT_TRUE(flushFile(mountPath)); EXPECT_TRUE(waitFor( [&]() { const auto h = ClientW()->handle(imagePath); // File isn't in the cloud. if (!h) return false; // File doesn't have expected attributes. if (!ClientW()->hasFileAttribute(*h, THUMBNAIL)) return false; if (!ClientW()->hasFileAttribute(*h, PREVIEW)) return false; return true; }, mDefaultTimeout)); } handle fsidOf(const Path& path) { static FSACCESS_CLASS fsAccess; return fsAccess.fsidOf(path.localPath(), false, true, FSLogging::logOnError); } bool makeFile(const Path& path, const std::string& data) { std::ofstream ostream(path.string(), std::ios::binary | std::ios::trunc); if (!ostream) return false; ostream.write(data.data(), static_cast<std::streamsize>(data.size())); if (!ostream) return false; ostream.flush(); if (!ostream) return false; ostream.close(); return !!ostream; } bool makeFile(const Path& path, std::size_t size) { return makeFile(path, randomBytes(size)); } std::string readFile(const Path& path) { std::error_code error; auto size = fs::file_size(path, error); if (error) return std::string(); std::ifstream istream(path.path(), std::ios::binary); if (!istream) return std::string(); std::string buffer(static_cast<std::size_t>(size), '\0'); istream.read(&buffer[0], static_cast<std::streamsize>(size)); buffer.resize(static_cast<std::size_t>(istream.gcount())); return buffer; } } // testing } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/�������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024426�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/CMakeLists.txt�����������������������0000664�0000000�0000000�00000000027�15162662266�0027165�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/��������������������������������0000775�0000000�0000000�00000000000�15162662266�0025370�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/CMakeLists.txt������������������0000664�0000000�0000000�00000000031�15162662266�0030122�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(common) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/�������������������������0000775�0000000�0000000�00000000000�15162662266�0026660�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/CMakeLists.txt�����������0000664�0000000�0000000�00000000032�15162662266�0031413�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(testing) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/�����������������0000775�0000000�0000000�00000000000�15162662266�0030335�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/CMakeLists.txt���0000664�0000000�0000000�00000001215�15162662266�0033074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(test_integration PRIVATE client.h client_forward.h mount_event_observer.h mount_event_observer_forward.h mount_tests.h parameters.h parameters_forward.h printers.h real_client.h sync_tests.h test.h test_base.h utility.h ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/client.h���������0000664�0000000�0000000�00000005647�15162662266�0032000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/normalized_path_forward.h> #include <mega/common/testing/client.h> #include <mega/common/testing/path_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount_event_forward.h> #include <mega/fuse/common/mount_flags_forward.h> #include <mega/fuse/common/mount_info_forward.h> #include <mega/fuse/common/mount_result_forward.h> #include <mega/fuse/common/service_forward.h> #include <mega/fuse/common/testing/client_forward.h> #include <mega/fuse/common/testing/mount_event_observer_forward.h> #include <mutex> #include <string> namespace mega { namespace fuse { namespace testing { class Client: public virtual common::testing::Client { // What observers are monitoring mount events? MountEventObserverWeakPtrSet mMountEventObservers; // Serializes access to mMountEventObservers. std::mutex mMountEventObserversLock; protected: Client(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); // Called when a mount event has been emitted. void mountEvent(const MountEvent& event); public: virtual ~Client(); // Add a new mount to the database. MountResult addMount(const MountInfo& info); // Describe the inode associated with the specified path. common::ErrorOr<InodeInfo> describe(const common::testing::Path& path) const; // Disable a previously enabled mount. MountResult disableMount(const std::string& name, bool remember); // Disable all enabled mounts. MountResult disableMounts(bool remember); // Discard node events. MountResult discard(bool discard); // Enable a previously added mount. MountResult enableMount(const std::string& name, bool remember); // Get our hands on the client's FUSE interface. virtual Service& fuseService() const = 0; // Check if a file is cached. bool isCached(const common::testing::Path& path) const; // Return a reference to a new mount event observer. MountEventObserverPtr mountEventObserver(); // Query whether a mount is enabled. bool mountEnabled(const std::string& name) const; // Update a mount's flags. MountResult mountFlags(const std::string& name, const MountFlags& flags); // Retrieve a mount's flags. MountFlagsPtr mountFlags(const std::string& name) const; // Retrieve a mount's description. MountInfoPtr mountInfo(const std::string& name) const; // Retrieve the path associated with the specified name. common::NormalizedPath mountPath(const std::string& name) const; // Retrieve a description of each (enabled) mount. MountInfoVector mounts(bool onlyEnabled) const; // Remove a mount from the database. MountResult removeMount(const std::string& name); // Remove all mounts from the database. MountResult removeMounts(bool disable); }; // Client } // testing } // fuse } // mega �����������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/client_forward.h�0000664�0000000�0000000�00000000264�15162662266�0033512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <memory> namespace mega { namespace fuse { namespace testing { class Client; using ClientPtr = std::unique_ptr<Client>; } // testing } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������mount_event_observer.h������������������������������������������������������������������������������0000664�0000000�0000000�00000001551�15162662266�0034703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing�������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/mount_event_forward.h> #include <mega/fuse/common/mount_event_type_forward.h> #include <mega/fuse/common/testing/mount_event_observer_forward.h> #include <chrono> #include <condition_variable> #include <list> #include <thread> namespace mega { namespace fuse { namespace testing { class MountEventObserver { std::condition_variable mCV; std::list<MountEvent> mEvents; std::mutex mLock; public: static MountEventObserverPtr create(); void emitted(const MountEvent& event); void expect(MountEvent event); bool wait(std::chrono::steady_clock::time_point when); template<typename Rep, typename Period> bool wait(std::chrono::duration<Rep, Period> timeout) { return wait(std::chrono::steady_clock::now() + timeout); } }; // MountEventObserver } // testing } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������mount_event_observer_forward.h����������������������������������������������������������������������0000664�0000000�0000000�00000000647�15162662266�0036434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing�������������������������������������������������������������������������������������#pragma once #include <memory> #include <set> namespace mega { namespace fuse { namespace testing { class MountEventObserver; using MountEventObserverPtr = std::shared_ptr<MountEventObserver>; using MountEventObserverWeakPtr = std::weak_ptr<MountEventObserver>; using MountEventObserverWeakPtrSet = std::set<MountEventObserverWeakPtr, std::owner_less<MountEventObserverWeakPtr>>; } // testing } // fuse } // mega �����������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/mount_tests.h����0000664�0000000�0000000�00000000305�15162662266�0033070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/testing/test.h> namespace mega { namespace fuse { namespace testing { struct FUSEMountTests: Test {}; // FUSEMountTests } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/parameters.h�����0000664�0000000�0000000�00000001757�15162662266�0032663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/testing/path_forward.h> #include <mega/fuse/common/testing/client_forward.h> #include <mega/fuse/common/testing/parameters_forward.h> #include <iosfwd> #include <string> namespace mega { namespace fuse { namespace testing { struct Parameters { struct { using Accessor = ClientPtr& (*)(); Accessor mReadOnly; Accessor mReadWrite; } mClients; std::string mName; struct { using Accessor = const common::testing::Path& (*)(); Accessor mObserver; Accessor mReadOnly; Accessor mReadWrite; } mPaths; bool mUseVersioning; }; // Parameters extern const Parameters SHARED_UNVERSIONED; extern const Parameters SHARED_VERSIONED; extern const Parameters STANDARD_UNVERSIONED; extern const Parameters STANDARD_VERSIONED; std::ostream& operator<<(std::ostream& ostream, const Parameters& parameters); std::string toString(const Parameters& parameters); } // testing } // fuse } // mega �����������������parameters_forward.h��������������������������������������������������������������������������������0000664�0000000�0000000�00000000172�15162662266�0034316�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing�������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace testing { struct Parameters; } // testing } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/printers.h�������0000664�0000000�0000000�00000001266�15162662266�0032361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include "utility.h" #include <mega/common/node_info_forward.h> #include <mega/common/testing/printers.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount_event_type_forward.h> #include <mega/fuse/common/mount_result_forward.h> #include <mega/fuse/common/testing/utility.h> #include <mega/types.h> #include <iosfwd> namespace mega { namespace fuse { template<typename T> auto operator<<(std::ostream& ostream, const T& value) -> typename testing::EnableIfInfoLike<T, std::ostream>::type&; void PrintTo(const MountEventType type, std::ostream* ostream); void PrintTo(const MountResult result, std::ostream* ostream); } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/real_client.h����0000664�0000000�0000000�00000001505�15162662266�0032770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/testing/path_forward.h> #include <mega/common/testing/real_client.h> #include <mega/fuse/common/mount_event_forward.h> #include <mega/fuse/common/service_forward.h> #include <mega/fuse/common/testing/client.h> #include <string> namespace mega { namespace fuse { namespace testing { class RealClient: public Client, public common::testing::RealClient { // Called when the client emits a mount event. void onFuseEvent(const MountEvent& event) override; public: RealClient(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); ~RealClient(); // Get our hands on the client's FUSE interface. Service& fuseService() const override; }; // RealClient } // testing } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/sync_tests.h�����0000664�0000000�0000000�00000002561�15162662266�0032710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/testing/cloud_path_forward.h> #include <mega/common/testing/path_forward.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/test.h> namespace mega { namespace fuse { namespace testing { struct FUSESyncTests: Test {}; // FUSESyncTests class ScopedMount { Client& mClient; std::string mName; MountResult mResult; public: ScopedMount(ClientPtr& client, const std::string& name, common::testing::Path sourcePath, common::testing::CloudPath targetPath); ScopedMount(ClientPtr& client, common::testing::Path sourcePath, common::testing::CloudPath targetPath); ScopedMount(const ScopedMount& other) = delete; ~ScopedMount(); ScopedMount& operator=(const ScopedMount& rhs) = delete; MountResult result() const; }; // ScopedMount class ScopedSync { Client& mClient; std::tuple<handle, Error, SyncError> mContext; public: ScopedSync(ClientPtr& client, common::testing::Path sourcePath, common::testing::CloudPath targetPath); ScopedSync(const ScopedSync& other) = delete; ~ScopedSync(); ScopedSync& operator=(const ScopedSync& rhs) = delete; Error error() const; SyncError syncError() const; }; // ScopedSync } // testing } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/test.h�����������0000664�0000000�0000000�00000010307�15162662266�0031466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <gmock/gmock.h> #include <mega/common/testing/model.h> #include <mega/common/testing/path.h> #include <mega/common/testing/test.h> #include <mega/common/testing/watchdog.h> #include <mega/fuse/common/testing/client_forward.h> #include <mega/fuse/common/testing/parameters_forward.h> #include <mega/fuse/common/testing/real_client.h> #include <chrono> namespace mega { namespace fuse { namespace testing { struct TestTraits { using AbstractClient = Client; using ConcreteClient = RealClient; static constexpr const char* mName = "fuse"; }; // TestTraits class Test: public common::testing::Test<TestTraits> { // For clarity. enum ClientType : std::size_t { // Client for read-only access. CT_READ_ONLY, // Client for read-write access. CT_READ_WRITE, // Client for share access. CT_SHAREE, // How many client types there are. NUM_CLIENT_TYPES }; // ClientType enum PathType : std::size_t { // Path for observing changes. PT_OBSERVER, // Path for observing share changes. PT_OBSERVER_SHARE, // Path for read-only accesses. PT_READ_ONLY, // Path for read-only share accesses. PT_READ_ONLY_SHARE, // Path for read-write accesses. PT_READ_WRITE, // Path for read-write share accesses. PT_READ_WRITE_SHARE, // Number of path types. NUM_PATH_TYPES }; // PathType // Convenience. using ClientPtrArray = std::array<ClientPtr, NUM_CLIENT_TYPES>; using PathArray = std::array<common::testing::Path, NUM_PATH_TYPES>; // Regenerate cloud content if necessary. static Error regenerate(Client& client, Client& sharee, const common::testing::Model& model, accesslevel_t permissions); // What clients should we use for testing? static ClientPtrArray mClients; // Expected contents of the cloud. static common::testing::Model mModel; // Where should we mount cloud entities? static PathArray mMountPaths; // Where are our sentinels? static PathArray mSentinelPaths; // Makes sure our tests don't run forever. static common::testing::Watchdog mWatchdog; protected: // Perform fixture-specific setup. virtual bool DoSetUp(const Parameters& parameters); // Prform fixture-specific teardown. virtual bool DoTearDown(); public: // Defines a client accessor method. #define DEFINE_CLIENT_ACCESSOR(name, slot) \ static ClientPtr& Client##name() \ { \ return mClients[(slot)]; \ } // Define client accessor methods. DEFINE_CLIENT_ACCESSOR(R, CT_READ_ONLY); DEFINE_CLIENT_ACCESSOR(W, CT_READ_WRITE); DEFINE_CLIENT_ACCESSOR(S, CT_SHAREE); // Remove macro from scope. #undef DEFINE_CLIENT_ACCESSOR // Defines a path accesor method. #define DEFINE_PATH_ACCESSOR(name, scope, slot) \ static const common::testing::Path& scope##name() \ { \ return m##scope##s[(slot)]; \ } // Define mount path accessors. DEFINE_PATH_ACCESSOR(O, MountPath, PT_OBSERVER); DEFINE_PATH_ACCESSOR(OS, MountPath, PT_OBSERVER_SHARE); DEFINE_PATH_ACCESSOR(R, MountPath, PT_READ_ONLY); DEFINE_PATH_ACCESSOR(RS, MountPath, PT_READ_ONLY_SHARE); DEFINE_PATH_ACCESSOR(W, MountPath, PT_READ_WRITE); DEFINE_PATH_ACCESSOR(WS, MountPath, PT_READ_WRITE_SHARE); // Define sentinel path accessors. DEFINE_PATH_ACCESSOR(O, SentinelPath, PT_OBSERVER); DEFINE_PATH_ACCESSOR(OS, SentinelPath, PT_OBSERVER_SHARE); DEFINE_PATH_ACCESSOR(R, SentinelPath, PT_READ_ONLY); DEFINE_PATH_ACCESSOR(RS, SentinelPath, PT_READ_ONLY_SHARE); DEFINE_PATH_ACCESSOR(W, SentinelPath, PT_READ_WRITE); DEFINE_PATH_ACCESSOR(WS, SentinelPath, PT_READ_WRITE_SHARE); // Remove macro from scope. #undef DEFINE_PATH_ACCESSOR // Perform instance-specific setup. void SetUp() override; // Perform fixture-wide setup. static void SetUpTestSuite(); // Perform instance-specific teardown. void TearDown() override; // Perform fixture-wide teardown. static void TearDownTestSuite(); }; // Test } // testing } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/test_base.h������0000664�0000000�0000000�00000000453�15162662266�0032461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/testing/test.h> namespace mega { namespace fuse { namespace testing { class TestBase: public Test { protected: // Perform fixture-specific setup. bool DoSetUp(const Parameters& parameters) override; }; // TestBase } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mega/fuse/common/testing/utility.h��������0000664�0000000�0000000�00000001614�15162662266�0032213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/node_info_forward.h> #include <mega/common/testing/utility.h> #include <mega/common/type_traits.h> #include <mega/fuse/common/inode_id_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/testing/client_forward.h> namespace mega { class Error; class NodeHandle; namespace fuse { namespace testing { // Convenience. template<typename T> using IsInfoLike = common::IsOneOf<T, InodeInfo, common::NodeInfo>; template<typename I, typename T> using EnableIfInfoLike = std::enable_if<IsInfoLike<I>::value, T>; NodeHandle id(const common::NodeInfo& info); InodeID id(const InodeInfo& info); NodeHandle parentID(const common::NodeInfo& info); InodeID parentID(const InodeInfo& info); std::string toString(NodeHandle handle); std::uint64_t toUint64(InodeID id); std::uint64_t toUint64(NodeHandle handle); } // testing } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mount_event_observer.cpp������������������0000664�0000000�0000000�00000002104�15162662266�0030470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <algorithm> namespace mega { namespace fuse { namespace testing { MountEventObserverPtr MountEventObserver::create() { return std::make_shared<MountEventObserver>(); } void MountEventObserver::emitted(const MountEvent& event) { std::lock_guard<std::mutex> guard(mLock); auto i = std::find(mEvents.begin(), mEvents.end(), event); if (i == mEvents.end()) return; mEvents.erase(i); if (mEvents.empty()) mCV.notify_all(); } void MountEventObserver::expect(MountEvent event) { std::lock_guard<std::mutex> guard(mLock); mEvents.emplace_back(std::move(event)); } bool MountEventObserver::wait(std::chrono::steady_clock::time_point when) { std::unique_lock<std::mutex> lock(mLock); return mCV.wait_until(lock, when, [&]() { return mEvents.empty(); }); } } // testing } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/mount_tests.cpp���������������������������0000664�0000000�0000000�00000104762�15162662266�0026617�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/testing/cloud_path.h> #include <mega/common/testing/directory.h> #include <mega/common/testing/file.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/mount_tests.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/platform.h> namespace mega { namespace fuse { namespace testing { using common::testing::Directory; using common::testing::File; using common::testing::Path; using common::testing::randomName; using common::testing::waitFor; TEST_F(FUSEMountTests, add_fails_when_name_isnt_specified) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_NO_NAME, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_NO_NAME); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_fails_when_source_is_file) { auto handle = ClientW()->handle("/x/s/sf0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("sf0"); info.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_REMOTE_FILE, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_REMOTE_FILE); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_fails_when_source_is_unknown) { MountInfo info; info.name("bogus"); info.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_REMOTE_UNKNOWN, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_REMOTE_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_fails_when_target_is_file) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); File sf0("sf0", "sf0", mScratchPath); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = sf0.path(); auto expected = UNIX_OR_WINDOWS(MOUNT_LOCAL_FILE, MOUNT_LOCAL_EXISTS); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), expected, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), expected); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(false), MountInfoVector(1, info)); } TEST_F(FUSEMountTests, add_fails_when_name_is_not_unique) { auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("d"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(mounts.back()); mounts.back().mHandle = *handle; mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_NAME_TAKEN, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_NAME_TAKEN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); mounts.pop_back(); ASSERT_EQ(ClientW()->mounts(false), mounts); } TEST_F(FUSEMountTests, add_succeeds_when_node_is_read_only_share) { auto handle = ClientR()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = MountPathR(); auto observer = ClientS()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientS()->addMount(info), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientS()->mounts(false), MountInfoVector(1, info)); } TEST_F(FUSEMountTests, add_succeeds_when_node_is_read_write_share) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = MountPathW(); auto observer = ClientS()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientS()->addMount(info), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientS()->mounts(false), MountInfoVector(1, info)); } TEST_F(FUSEMountTests, add_succeeds_when_target_is_not_unique) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("s0"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); mounts.emplace_back(mounts.back()); mounts.back().name("s1"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(false), mounts); } TEST_F(FUSEMountTests, disable_fails_when_mount_unknown) { auto observer = ClientW()->mountEventObserver(); observer->expect({std::string(), MOUNT_UNKNOWN, MOUNT_DISABLED}); ASSERT_EQ(ClientW()->disableMount(std::string(), false), MOUNT_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, disable_succeeds_when_mount_disabled) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_EQ(ClientW()->disableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, disable_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_EQ(ClientW()->mounts(true), MountInfoVector(1, mount)); std::error_code error; ASSERT_TRUE(waitFor( [&]() { return fs::exists(SentinelPathW(), error); }, mDefaultTimeout)); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_TRUE(waitFor( [&]() { return ClientW()->disableMount(mount.name(), false) == MOUNT_SUCCESS; }, mDefaultTimeout)); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(true).empty()); EXPECT_FALSE(fs::exists(SentinelPathW(), error)); EXPECT_FALSE(error); } TEST_F(FUSEMountTests, disable_when_source_removed) { auto result = ClientW()->remove("/t"); ASSERT_TRUE(result == API_FUSE_ENOTFOUND || result == API_OK); auto handle = ClientW()->makeDirectory("t", "/"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); ASSERT_EQ(ClientW()->makeDirectory("sentinel", "/t").errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("t"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({ mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED, }); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); std::error_code error; ASSERT_TRUE(waitFor( [&]() { return fs::exists(SentinelPathW(), error); }, mDefaultTimeout)); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_EQ(ClientW()->remove(*handle), API_OK); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(true).empty()); EXPECT_FALSE(fs::exists(SentinelPathW(), error)); EXPECT_FALSE(error); } TEST_F(FUSEMountTests, enable_fails_when_mount_unknown) { auto observer = ClientW()->mountEventObserver(); observer->expect({std::string(), MOUNT_UNKNOWN, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(std::string(), false), MOUNT_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(true).empty()); } TEST_F(FUSEMountTests, enable_fails_when_source_is_unknown) { auto result = ClientW()->remove("/t"); ASSERT_TRUE(result == API_FUSE_ENOTFOUND || result == API_OK); auto handle = ClientW()->makeDirectory("t", "/"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("t"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); ASSERT_EQ(ClientW()->remove(*handle), API_OK); observer->expect({mount.name(), MOUNT_REMOTE_UNKNOWN, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_REMOTE_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(true).empty()); } TEST_F(FUSEMountTests, enable_fails_when_target_is_file) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; auto observer = ClientW()->mountEventObserver(); { UNIX_ONLY(Directory sd0("sd0", mScratchPath)); mount.name("s"); mount.mHandle = *handle; mount.mPath = mScratchPath / "sd0"; observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); } File sd0("sd0", "sd0", mScratchPath); auto expected = UNIX_OR_WINDOWS(MOUNT_LOCAL_FILE, MOUNT_LOCAL_EXISTS); observer->expect({mount.name(), expected, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), expected); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(true).empty()); std::error_code error; EXPECT_FALSE(fs::exists(SentinelPathW(), error)); EXPECT_FALSE(error); } TEST_F(FUSEMountTests, enable_fails_when_target_is_not_unique) { auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("s"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(mounts.back()); mounts.back().mHandle = *handle; mounts.back().name("t"); mounts.back().mPath = MountPathW(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); observer->expect({mounts.front().name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mounts.front().name(), false), MOUNT_SUCCESS); observer->expect({mounts.back().name(), MOUNT_LOCAL_TAKEN, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mounts.back().name(), false), MOUNT_LOCAL_TAKEN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(true), MountInfoVector(1, mounts.front())); ASSERT_EQ(ClientW()->removeMounts(true), MOUNT_SUCCESS); } TEST_F(FUSEMountTests, enable_succeeds_when_target_is_unique) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("s"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); mounts.emplace_back(mounts.back()); mounts.back().name("t"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); observer->expect({ mounts.back().name(), MOUNT_SUCCESS, MOUNT_ENABLED, }); ASSERT_EQ(ClientW()->enableMount(mounts.back().name(), false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(true), MountInfoVector(1, mounts.back())); ASSERT_TRUE(waitFor( [&]() { std::error_code error; return fs::exists(SentinelPathO(), error); }, mDefaultTimeout)); ASSERT_EQ(ClientW()->removeMounts(true), MOUNT_SUCCESS); } TEST_F(FUSEMountTests, enable_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(true), MountInfoVector(1, mount)); ASSERT_TRUE(waitFor( [&]() { std::error_code error; return fs::exists(SentinelPathW(), error); }, mDefaultTimeout)); } TEST_F(FUSEMountTests, enable_succeeds_when_mount_enabled) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.name("s"); mount.mHandle = *handle; mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, enables_enabled_persisent_mounts_after_login) { auto client = CreateClient("enable_" + randomName()); ASSERT_TRUE(client); ASSERT_EQ(client->login(1), API_OK); auto handle = client->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.mFlags.mEnableAtStartup = true; mount.name("s"); mount.mFlags.mPersistent = true; mount.mPath = client->storagePath() / "s"; UNIX_ONLY(ASSERT_TRUE(fs::create_directories(Path(mount.mPath)))); auto observer = client->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(client->addMount(mount), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto sessionToken = client->sessionToken(); ASSERT_EQ(sessionToken.errorOr(API_OK), API_OK); ASSERT_EQ(client->logout(true), API_OK); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(client->login(*sessionToken), API_OK); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, enabled_false_when_disabled) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); ASSERT_FALSE(ClientW()->mountEnabled(mount.name())); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, enabled_false_when_unknown) { ASSERT_FALSE(ClientW()->mountEnabled(std::string())); } TEST_F(FUSEMountTests, enabled_true_when_enabled) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_TRUE(ClientW()->mountEnabled(mount.name())); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, flags_fails_when_enabled_name_not_unique) { auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd0"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mounts.back().name(), false), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd1"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mounts.back().name(), false), MOUNT_SUCCESS); auto flags = ClientW()->mountFlags(mounts.back().name()); ASSERT_TRUE(flags); ASSERT_EQ(mounts.back().mFlags, *flags); flags->mName = mounts.front().name(); observer->expect({mounts.back().name(), MOUNT_NAME_TAKEN, MOUNT_CHANGED}); ASSERT_EQ(ClientW()->mountFlags(mounts.back().name(), *flags), MOUNT_NAME_TAKEN); flags = ClientW()->mountFlags(mounts.back().name()); ASSERT_TRUE(flags); ASSERT_EQ(mounts.back().mFlags, *flags); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, flags_fails_when_mount_unknown) { ASSERT_FALSE(ClientW()->mountFlags(MountPathW())); auto observer = ClientW()->mountEventObserver(); observer->expect({std::string(), MOUNT_UNKNOWN, MOUNT_CHANGED}); MountFlags flags; flags.mName = "x"; ASSERT_EQ(ClientW()->mountFlags(std::string(), flags), MOUNT_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, flags_fails_when_name_isnt_specified) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); auto flags = mount.mFlags; flags.mName.clear(); observer->expect({mount.name(), MOUNT_NO_NAME, MOUNT_CHANGED}); ASSERT_EQ(ClientW()->mountFlags(mount.name(), flags), MOUNT_NO_NAME); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, flags_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); auto flags0 = ClientW()->mountFlags(mount.name()); ASSERT_TRUE(flags0); ASSERT_EQ(mount.mFlags, *flags0); flags0->mEnableAtStartup = true; flags0->mName = "t"; flags0->mReadOnly = true; flags0->mPersistent = true; observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_CHANGED}); ASSERT_EQ(ClientW()->mountFlags(mount.name(), *flags0), MOUNT_SUCCESS); auto flags1 = ClientW()->mountFlags(flags0->mName); ASSERT_TRUE(flags1); ASSERT_EQ(*flags0, *flags1); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, info_fails_unknown_mount) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); ASSERT_FALSE(ClientW()->mountInfo("bogus")); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, info_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); auto info = ClientW()->mountInfo(mount.name()); ASSERT_TRUE(info); ASSERT_EQ(mount, *info); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, list_all) { ASSERT_TRUE(ClientW()->mounts(false).empty()); auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd0"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd1"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto mounts_ = ClientW()->mounts(false); ASSERT_EQ(mounts_.size(), 2u); ASSERT_EQ(mounts_, mounts); } TEST_F(FUSEMountTests, list_enabled) { auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd0"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd1"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); ASSERT_TRUE(ClientW()->mounts(true).empty()); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mounts.back().name(), false), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto mounts_ = ClientW()->mounts(true); ASSERT_EQ(mounts_.size(), 1u); ASSERT_EQ(mounts_.back(), mounts.back()); } TEST_F(FUSEMountTests, path_distinct_names) { auto handle = ClientW()->handle("/x/s/sd0"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfoVector mounts; mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd0"); mounts.back().mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); handle = ClientW()->handle("/x/s/sd1"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); mounts.emplace_back(); mounts.back().mHandle = *handle; mounts.back().name("sd1"); mounts.back().mPath = MountPathO(); observer->expect({mounts.back().name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mounts.back()), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto path = ClientW()->mountPath(mounts.back().name()); EXPECT_EQ(path, mounts.back().mPath); mounts.pop_back(); path = ClientW()->mountPath(mounts.back().name()); EXPECT_EQ(path, mounts.back().mPath); } TEST_F(FUSEMountTests, path_unused_name) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mountPath("t").empty()); } TEST_F(FUSEMountTests, persistent_mounts_are_persistent) { auto client = CreateClient("persistent_" + randomName()); ASSERT_TRUE(client); ASSERT_EQ(client->login(1), API_OK); auto handle = client->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mFlags.mPersistent = true; mount.mPath = client->storagePath() / "s"; UNIX_ONLY(ASSERT_TRUE(fs::create_directories(Path(mount.mPath)))); auto observer = client->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(client->addMount(mount), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto sessionToken = client->sessionToken(); ASSERT_EQ(sessionToken.errorOr(API_OK), API_OK); ASSERT_EQ(client->logout(true), API_OK); ASSERT_EQ(client->login(*sessionToken), API_OK); auto mount_ = client->mountInfo(mount.name()); ASSERT_TRUE(mount_); ASSERT_EQ(*mount_, mount); } TEST_F(FUSEMountTests, remember_disable_implies_persistence) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED}); ASSERT_EQ(ClientW()->disableMount(mount.name(), true), MOUNT_SUCCESS); EXPECT_TRUE(observer->wait(mDefaultTimeout)); auto flags = ClientW()->mountFlags(mount.name()); ASSERT_TRUE(flags); ASSERT_TRUE(flags->mPersistent); } TEST_F(FUSEMountTests, remember_enable_implies_persistence) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), true), MOUNT_SUCCESS); auto flags = ClientW()->mountFlags(mount.name()); ASSERT_TRUE(flags); ASSERT_TRUE(flags->mEnableAtStartup); ASSERT_TRUE(flags->mPersistent); EXPECT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, remove_fails_when_mount_enabled) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); ASSERT_TRUE(waitFor( [&]() { std::error_code error; return fs::exists(SentinelPathW(), error); }, mDefaultTimeout)); observer->expect({mount.name(), MOUNT_BUSY, MOUNT_REMOVED}); ASSERT_EQ(ClientW()->removeMount(mount.name()), MOUNT_BUSY); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_EQ(ClientW()->mounts(true), MountInfoVector(1, mount)); } TEST_F(FUSEMountTests, remove_succeeds) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); ASSERT_FALSE(ClientW()->mounts(false).empty()); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_REMOVED}); ASSERT_EQ(ClientW()->removeMount(mount.name()), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, remove_succeeds_when_mount_unknown) { auto observer = ClientW()->mountEventObserver(); observer->expect({std::string(), MOUNT_SUCCESS, MOUNT_REMOVED}); ASSERT_EQ(ClientW()->removeMount(std::string()), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); } TEST_F(FUSEMountTests, temporary_disable_is_not_remembered) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.mFlags.mEnableAtStartup = true; mount.name("s"); mount.mFlags.mPersistent = true; mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({ mount.name(), MOUNT_SUCCESS, MOUNT_DISABLED, }); ASSERT_EQ(ClientW()->disableMount(mount.name(), false), MOUNT_SUCCESS); EXPECT_TRUE(observer->wait(mDefaultTimeout)); auto flags = ClientW()->mountFlags(mount.name()); ASSERT_TRUE(flags); ASSERT_EQ(mount.mFlags, *flags); } TEST_F(FUSEMountTests, temporary_enable_is_not_remembered) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = MountPathW(); auto observer = ClientW()->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); observer->expect({ mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED, }); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_SUCCESS); EXPECT_TRUE(observer->wait(mDefaultTimeout)); auto flags = ClientW()->mountFlags(mount.name()); ASSERT_TRUE(flags); ASSERT_EQ(mount.mFlags, *flags); } TEST_F(FUSEMountTests, transient_mounts_are_transient) { auto client = CreateClient("transient_" + randomName()); ASSERT_TRUE(client); ASSERT_EQ(client->login(1), API_OK); auto handle = client->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; mount.mHandle = *handle; mount.name("s"); mount.mPath = client->storagePath() / "s"; UNIX_ONLY(ASSERT_TRUE(fs::create_directories(Path(mount.mPath)))); auto observer = client->mountEventObserver(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(client->addMount(mount), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); auto sessionToken = client->sessionToken(); ASSERT_EQ(sessionToken.errorOr(API_OK), API_OK); ASSERT_EQ(client->logout(true), API_OK); ASSERT_EQ(client->login(*sessionToken), API_OK); ASSERT_FALSE(client->mountInfo(mount.name())); } } // testing } // fuse } // mega ��������������sdk-10.11.0/src/fuse/supported/common/testing/integration/parameters.cpp����������������������������0000664�0000000�0000000�00000002442�15162662266�0026366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/testing/parameters.h> #include <mega/fuse/common/testing/test.h> #include <sstream> namespace mega { namespace fuse { namespace testing { static Parameters sharedSuite(bool useVersioning); static Parameters standardSuite(bool useVersioning); const Parameters SHARED_UNVERSIONED = sharedSuite(false); const Parameters SHARED_VERSIONED = sharedSuite(true); const Parameters STANDARD_UNVERSIONED = standardSuite(false); const Parameters STANDARD_VERSIONED = standardSuite(true); std::ostream& operator<<(std::ostream& ostream, const Parameters& parameters) { return ostream << toString(parameters); } std::string toString(const Parameters& parameters) { std::ostringstream ostream; ostream << parameters.mName << (parameters.mUseVersioning ? "_" : "_un") << "versioned"; return ostream.str(); } Parameters sharedSuite(bool useVersioning) { return {{&Test::ClientS, &Test::ClientS}, "shared", {&Test::MountPathOS, &Test::MountPathRS, &Test::MountPathWS}, useVersioning}; } Parameters standardSuite(bool useVersioning) { return {{&Test::ClientW, &Test::ClientW}, "standard", {&Test::MountPathO, &Test::MountPathR, &Test::MountPathW}, useVersioning}; } } // testing } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/printers.cpp������������������������������0000664�0000000�0000000�00000004164�15162662266�0026074�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/date_time.h> #include <mega/common/node_info.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/printers.h> #include <mega/fuse/common/testing/utility.h> #include <ostream> namespace mega { namespace fuse { using namespace common; static std::ostream& operator<<(std::ostream& ostream, accesslevel_t permissions); static const std::string indent = std::string(6, ' '); template<typename T> auto operator<<(std::ostream& ostream, const T& value) -> typename testing::EnableIfInfoLike<T, std::ostream>::type& { using testing::id; using testing::parentID; using testing::toString; return ostream << "\n" << indent << "mID: " << toString(id(value)) << "\n" << indent << "mIsDirectory: " << (value.mIsDirectory ? "TRUE" : "FALSE") << "\n" << indent << "mModified: " << DateTime(value.mModified) << "\n" << indent << "mName: " << value.mName << "\n" << indent << "mParentID: " << toString(parentID(value)) << "\n" << indent << "mPermissions: " << value.mPermissions << "\n" << indent << "mSize: " << value.mSize; } void PrintTo(const MountEventType type, std::ostream* ostream) { *ostream << toString(type); } void PrintTo(const MountResult result, std::ostream* ostream) { *ostream << toString(result); } std::ostream& operator<<(std::ostream& ostream, accesslevel_t permissions) { switch (permissions) { case RDONLY: return ostream << "RDONLY"; case RDWR: return ostream << "RDWR"; case FULL: return ostream << "FULL"; case OWNER: return ostream << "OWNER"; case OWNERPRELOGIN: return ostream << "OWNER-PRELOGIN"; default: break; } return ostream << "UNKNOWN"; } template std::ostream& operator<<(std::ostream&, const InodeInfo&); template std::ostream& operator<<(std::ostream&, const NodeInfo&); } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/real_client.cpp���������������������������0000664�0000000�0000000�00000001505�15162662266�0026503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/testing/real_client.h> namespace mega { namespace fuse { namespace testing { using common::testing::Path; void RealClient::onFuseEvent(const MountEvent& event) { mountEvent(event); } RealClient::RealClient(const std::string& clientName, const Path& databasePath, const Path& storagePath): common::testing::Client(clientName, databasePath, storagePath), Client(clientName, databasePath, storagePath), common::testing::RealClient(clientName, databasePath, storagePath) { // Make sure FUSE logs *everything*. mClient->mFuseService.logLevel(logDebug); } RealClient::~RealClient() {} Service& RealClient::fuseService() const { return mClient->mFuseService; } } // testing } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/sync_tests.cpp����������������������������0000664�0000000�0000000�00000011157�15162662266�0026424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/normalized_path.h> #include <mega/common/testing/cloud_path.h> #include <mega/common/testing/directory.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/sync_tests.h> #include <mega/fuse/common/testing/test.h> #include <mega/fuse/platform/platform.h> namespace mega { namespace fuse { namespace testing { using namespace common::testing; TEST_F(FUSESyncTests, cant_mount_above_sync) { Directory s("s", mScratchPath); Directory sd0("sd0", s.path()); // Sync s/sd0. ScopedSync ssd0(ClientW(), sd0.path(), "x/s/sd0"); ASSERT_EQ(ssd0.error(), API_OK); ASSERT_EQ(ssd0.syncError(), NO_SYNC_ERROR); // Try and mount s. ScopedMount ms(ClientW(), s.path(), "x/s"); // Attempted mount should fail. ASSERT_EQ(ms.result(), MOUNT_LOCAL_SYNCING); } TEST_F(FUSESyncTests, cant_mount_below_sync) { Directory s("s", mScratchPath); // Sync s. ScopedSync ssd0(ClientW(), s.path(), "x/s"); ASSERT_EQ(ssd0.error(), API_OK); ASSERT_EQ(ssd0.syncError(), NO_SYNC_ERROR); UNIX_ONLY(Directory sd0("sd0", s.path())); // Try and mount s/sd0. ScopedMount msd0(ClientW(), s.path() / "sd0", "x/s/sd0"); // Attempted mount should fail. ASSERT_EQ(msd0.result(), MOUNT_LOCAL_SYNCING); } TEST_F(FUSESyncTests, cant_sync_above_mount) { Directory s("s", mScratchPath); UNIX_ONLY(Directory sd0("sd0", s.path())); // Try and mount s/sd0. ScopedMount msd0(ClientW(), s.path() / "sd0", "x/s/sd0"); ASSERT_EQ(msd0.result(), MOUNT_SUCCESS); // Try and sync s. ScopedSync ss(ClientW(), s.path(), "x/s"); EXPECT_EQ(ss.error(), API_EFAILED); EXPECT_EQ(ss.syncError(), LOCAL_PATH_MOUNTED); } TEST_F(FUSESyncTests, cant_sync_below_mount) { UNIX_ONLY(Directory s("s", mScratchPath)); // Try and mount s. ScopedMount ms(ClientW(), mScratchPath / "s", "x/s"); ASSERT_EQ(ms.result(), MOUNT_SUCCESS); // Try and sync s/sd0. ScopedSync ssd0(ClientW(), mScratchPath / "s" / "sd0", "x/s/sd0"); EXPECT_EQ(ssd0.error(), API_EFAILED); EXPECT_EQ(ssd0.syncError(), LOCAL_PATH_MOUNTED); } ScopedMount::ScopedMount(ClientPtr& client, const std::string& name, Path sourcePath, CloudPath targetPath): mClient(*client), mName(name), mResult() { MountInfo info; // Describe our new mount. info.name(mName); info.mHandle = targetPath.resolve(mClient).valueOr(NodeHandle()); info.mPath = sourcePath; // Try and add our mount. mResult = mClient.addMount(info); // Couldn't add the mount. if (mResult != MOUNT_SUCCESS) return; // So we can wait until the mount is actually active. auto observer = mClient.mountEventObserver(); observer->expect({mName, MOUNT_SUCCESS, MOUNT_ENABLED}); // Try and enable mount. mResult = mClient.enableMount(mName, false); // Couldn't enable the mount. if (mResult != MOUNT_SUCCESS) return; // Never received enabled event. if (!observer->wait(Test::mDefaultTimeout)) mResult = MOUNT_UNEXPECTED; } ScopedMount::ScopedMount(ClientPtr& client, Path sourcePath, CloudPath targetPath): ScopedMount(client, sourcePath.localPath().leafName().toPath(false), sourcePath.localPath(), targetPath) {} ScopedMount::~ScopedMount() { // Mount was never added or enabled. if (mResult != MOUNT_SUCCESS) return; // Try and disable mount. mResult = mClient.disableMount(mName, false); // Couldn't disable mount. if (mResult != MOUNT_SUCCESS) return; // Try and remove mount. mResult = mClient.removeMount(mName); } MountResult ScopedMount::result() const { return mResult; } ScopedSync::ScopedSync(ClientPtr& client, Path sourcePath, CloudPath targetPath): mClient(*client), mContext(mClient.synchronize(sourcePath, std::move(targetPath))) {} ScopedSync::~ScopedSync() { // Get our hands on the sync's backup ID. auto id = std::get<0>(mContext); // Remove the sync if it was enabled. if (id != UNDEF) mClient.desynchronize(id); } Error ScopedSync::error() const { return std::get<1>(mContext); } SyncError ScopedSync::syncError() const { return std::get<2>(mContext); } } // testing } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/test.cpp����������������������������������0000664�0000000�0000000�00000023577�15162662266�0025216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "integration/test.h" #include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/testing/cloud_path.h> #include <mega/common/testing/directory.h> #include <mega/common/testing/model.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/testing/parameters.h> #include <mega/fuse/common/testing/real_client.h> #include <mega/fuse/common/testing/test.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/platform.h> namespace mega { namespace fuse { namespace testing { using namespace common::testing; Error Test::regenerate(Client& client, Client& sharee, const Model& model, accesslevel_t permissions) { // Convenience. using ::testing::AnyOf; // Make sure our two clients are friends. auto befriended = befriend(client, sharee, mDefaultTimeout); EXPECT_EQ(befriended, API_OK); // Clients don't want to be friends. if (HasFailure()) return befriended; // Check if the test root is present. auto handle = client.handle("/x"); EXPECT_THAT(handle.errorOr(API_OK), AnyOf(API_FUSE_ENOTFOUND, API_OK)); if (HasFailure()) return handle.error(); // Create the test root if necessary. if (!handle) { // Try and create the test root. auto created = client.makeDirectory("x", "/"); // Make sure the test root was created. EXPECT_EQ(created.errorOr(API_OK), API_OK); // Couldn't create test root. if (HasFailure()) return created.error(); // Latch the test root's handle. handle = *created; } // Try and share the test root with our friend. auto shared = client.share(sharee.email(), *handle, permissions); // Make sure the test root was shared. EXPECT_EQ(shared, API_OK); // Couldn't share the test root with our friend. if (HasFailure()) return shared; // Cloud hasn't changed state. if (Model::from(client, "/x/s").match(model)) return API_OK; // Clear current cloud content. auto removed = client.removeAll("/x"); // Make sure cloud content was removed. EXPECT_THAT(removed, AnyOf(API_FUSE_ENOTFOUND, API_OK)); // Couldn't clear cloud content. if (HasFailure()) return removed; // Create scratch directory. Directory directory(randomName(), mScratchPath); // Populate scratch directory. model.populate(directory.path()); // Upload new cloud content. auto uploaded = client.upload("/x", directory.path() / "s"); // Make sure content was uploaded. EXPECT_EQ(uploaded.errorOr(API_OK), API_OK); // Couldn't upload cloud content. if (HasFailure()) return uploaded.error(); // Wait until our friend sees our new content. EXPECT_TRUE(waitFor( [&]() { return Model::from(sharee, *uploaded).match(model); }, mDefaultTimeout)); // Friend never saw our uploaded content. if (HasFailure()) return LOCAL_ETIMEOUT; // Let the caller know that everything's set up. return API_OK; } Model Test::mModel; Watchdog Test::mWatchdog(logger()); Test::ClientPtrArray Test::mClients; Test::PathArray Test::mMountPaths; Test::PathArray Test::mSentinelPaths; // Most tests run for less than 10 seconds so these limits should be fine. static constexpr auto MaxTestCleanupTime = std::chrono::minutes(15); static constexpr auto MaxTestRunTime = std::chrono::minutes(15); static constexpr auto MaxTestSetupTime = std::chrono::minutes(15); bool Test::DoSetUp(const Parameters& parameters) { // Arm the watchdog. ScopedWatch watch(mWatchdog, MaxTestRunTime); // Make sure the clients are set up. allOf(mClients, [&](const ClientPtr& client) { auto exists = false; // The client should exist. EXPECT_TRUE((exists = !!client)); if (!exists) return false; auto loggedIn = false; // The client should be logged in. EXPECT_TRUE((loggedIn = client->loggedIn() == FULLACCOUNT)); if (!loggedIn) return false; auto idle = false; // The client should contain no mounts. EXPECT_TRUE((idle = client->mounts(false).empty())); // Specify whether files should be versioned. client->useVersioning(parameters.mUseVersioning); return idle; }); // One or more clients isn't set up. if (HasFailure()) return false; // Verify that the sentinels are no longer visible. allOf(mSentinelPaths, [](const Path& path) { auto error = std::error_code(); auto result = false; // Sentinel shouldn't exist. EXPECT_TRUE((result = !fs::exists(path, error) && !error)); return result; }); // One or more sentinels exist. if (HasFailure()) return false; using ClientEntry = std::pair<Client*, accesslevel_t>; ClientEntry entries[] = {{ClientR().get(), RDONLY}, {ClientW().get(), FULL}}; // entries // Regenerate clients as necessary. allOf(entries, [](ClientEntry& entry) { auto populated = API_OK; auto sharee = ClientS().get(); auto client = entry.first; auto permissions = entry.second; // Regenerate state if necessary. EXPECT_EQ((populated = regenerate(*client, *sharee, mModel, permissions)), API_OK); return populated == API_OK; }); // Couldn't regenerate read-write state. if (HasFailure()) return false; // Don't disarm the watchdog. watch.release(); // We're done. return true; } bool Test::DoTearDown() { // Make sure all mounts have been removed. auto result = allOf(mClients, [](ClientPtr& client) { // Client doesn't exist. if (!client) return false; auto emptied = MOUNT_SUCCESS; // Make sure all mounts have been removed. EXPECT_EQ((emptied = client->removeMounts(true)), MOUNT_SUCCESS); // Couldn't remove all mounts. if (emptied != MOUNT_SUCCESS) return false; auto empty = false; // Verify that all mounts have been removed. EXPECT_TRUE((empty = client->mounts(false).empty())); return empty; }); // Disarm the watchdog. mWatchdog.disarm(); return result; } void Test::SetUp() { ASSERT_TRUE(DoSetUp(STANDARD_VERSIONED)); } void Test::SetUpTestSuite() { common::testing::Test<TestTraits>::SetUpTestSuite(); // Arm the watchdog. ScopedWatch watch(mWatchdog, MaxTestSetupTime); ClientPtrArray clients; // Create clients and log them in. { std::vector<std::string> names = {"sharee", "read-write", "read-only"}; // names // Create clients and log them in. allOf(clients, [&names](ClientPtr& client) { // Try and create client. EXPECT_TRUE((client = CreateClient(names.back()))); // Couldn't create client. if (!client) return false; // Pop name. names.pop_back(); auto loggedIn = API_OK; // Try and log the client in. EXPECT_EQ((loggedIn = client->login(names.size())), API_OK); // Couldn't log in. if (loggedIn != API_OK) return false; return true; }); // Couldn't create or log in a client. ASSERT_FALSE(HasFailure()); } // Prepare model. auto model = Model::generate("s", 3, 2, 2); model.add(Model::directory("sentinel"), "s"); // Compute mount paths PathArray mountPaths = {/* o */ clients[CT_READ_WRITE]->storagePath() / "observer", /* os */ clients[CT_SHAREE]->storagePath() / "read-write-observer", /* r */ clients[CT_READ_ONLY]->storagePath() / "actor", /* rs */ clients[CT_SHAREE]->storagePath() / "read-only-actor", /* w */ clients[CT_READ_WRITE]->storagePath() / "actor", /* ws */ clients[CT_SHAREE]->storagePath() / "read-write-actor"}; // mountPaths // Make sure mount paths exist. UNIX_ONLY({ // Make sure mount paths exist. allOf(mountPaths, [](const Path& path) { std::error_code error; EXPECT_TRUE(fs::create_directories(path, error) || !error); return !HasFailure(); }); // Couldn't create mount paths. ASSERT_FALSE(HasFailure()); }); PathArray sentinelPaths = mountPaths; // Compute sentinel paths. for (auto& path: sentinelPaths) path /= "sentinel"; // Persist clients, model and paths. mClients = std::move(clients); mModel = std::move(model); mMountPaths = std::move(mountPaths); mSentinelPaths = std::move(sentinelPaths); } void Test::TearDown() { ASSERT_TRUE(DoTearDown()); } void Test::TearDownTestSuite() { // Arm watchdog. ScopedWatch watch(mWatchdog, MaxTestCleanupTime); // Destroy clients. for (auto& client: mClients) client.reset(); } } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/test_base.cpp�����������������������������0000664�0000000�0000000�00000007736�15162662266�0026207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/parameters.h> #include <mega/fuse/common/testing/test_base.h> #include <mega/fuse/common/testing/utility.h> namespace mega { namespace fuse { namespace testing { using namespace common::testing; bool TestBase::DoSetUp(const Parameters& parameters) { // Make sure the basic stuff is up and running. EXPECT_TRUE(Test::DoSetUp(parameters)); // Basic stuff isn't up and running. if (HasFailure()) return false; // Adds and enables a mount. auto setupMount = [&](Client& client, const MountInfo& mount) { // So we can check if events are emitted. auto observer = client.mountEventObserver(); // Try and add the mount. observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); EXPECT_EQ(client.addMount(mount), MOUNT_SUCCESS); // Couldn't add the mount. if (HasFailure()) return false; // Try and enable the mount. observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); EXPECT_EQ(client.enableMount(mount.name(), false), MOUNT_SUCCESS); // Couldn't enable the mount. if (HasFailure()) return false; // Convenience. constexpr auto timeout = std::chrono::seconds(8); // Wait for events to be emitted. EXPECT_TRUE(observer->wait(timeout)); // Events were never emitted. if (HasFailure()) return false; // Get the mount's path. auto path = client.mountPath(mount.name()); // Sanity. EXPECT_FALSE(path.empty()); if (HasFailure()) return false; // Wait for sentinel to be visible. auto sentinel = Path(path).path() / "sentinel"; EXPECT_TRUE(waitFor( [&]() { std::error_code error; return fs::exists(sentinel, error); }, timeout)); // Sentinels were never visible. if (HasFailure()) return false; // Mount should be up and running. return true; }; // setupMount auto handle = ClientW()->handle("/x/s"); EXPECT_EQ(handle.errorOr(API_OK), API_OK); if (HasFailure()) return false; MountInfo mount; // Direct mounts. // Establish read-only mount. mount.name("sr"); mount.mFlags.mReadOnly = true; mount.mHandle = *handle; mount.mPath = MountPathR(); EXPECT_TRUE(setupMount(*ClientW(), mount)); if (HasFailure()) return false; // Establish read-write observer mount. mount.name("so"); mount.mFlags.mReadOnly = false; mount.mPath = MountPathO(); EXPECT_TRUE(setupMount(*ClientW(), mount)); if (HasFailure()) return false; // Establish read-write actor mount. mount.name("sw"); mount.mPath = MountPathW(); EXPECT_TRUE(setupMount(*ClientW(), mount)); if (HasFailure()) return false; // Share mounts. // Read-write observer mount. mount.mHandle = *handle; mount.name("So"); mount.mPath = MountPathOS(); EXPECT_TRUE(setupMount(*ClientS(), mount)); if (HasFailure()) return false; // Read-write mount. mount.name("Sw"); mount.mPath = MountPathWS(); EXPECT_TRUE(setupMount(*ClientS(), mount)); if (HasFailure()) return false; handle = ClientR()->handle("/x/s"); EXPECT_EQ(handle.errorOr(API_OK), API_OK); if (HasFailure()) return false; // Read-only mount. mount.mHandle = *handle; mount.name("Sr"); mount.mPath = MountPathRS(); EXPECT_TRUE(setupMount(*ClientS(), mount)); return !HasFailure(); } } // testing } // fuse } // mega ����������������������������������sdk-10.11.0/src/fuse/supported/common/testing/integration/utility.cpp�������������������������������0000664�0000000�0000000�00000001400�15162662266�0025717�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/node_info.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/inode_info.h> #include <mega/types.h> #include <mega/utils.h> namespace mega { namespace fuse { namespace testing { using namespace common; NodeHandle id(const NodeInfo& info) { return info.mHandle; } InodeID id(const InodeInfo& info) { return info.mID; } NodeHandle parentID(const NodeInfo& info) { return info.mParentHandle; } InodeID parentID(const InodeInfo& info) { return info.mParentID; } std::string toString(NodeHandle handle) { return toNodeHandle(handle); } std::uint64_t toUint64(InodeID id) { return id.get(); } std::uint64_t toUint64(NodeHandle handle) { return handle.as8byte(); } } // testing } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020051�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/CMakeLists.txt����������������������������������������������0000664�0000000�0000000�00000001076�15162662266�0022615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Add sources required by all platforms. target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE context.cpp file_context.cpp service.cpp service_context.cpp unmounter.cpp ) # Add headers required by all platforms. add_subdirectory(mega) # Add tests common to all platforms. add_subdirectory(testing) # Add backend-specific sources. if (UNIX) add_subdirectory(posix) else() add_subdirectory(windows) endif() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/context.cpp�������������������������������������������������0000664�0000000�0000000�00000001373�15162662266�0022245�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/badge.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/mount.h> namespace mega { namespace fuse { namespace platform { Context::Context(fuse::Mount& mount): mMount(&mount) { mMount->contextAdded(ContextBadge(), *this); } Context::~Context() { mMount->contextRemoved(ContextBadge(), *this); } DirectoryContext* Context::directory() { return nullptr; } FileContext* Context::file() { return nullptr; } InodeInfo Context::info() const { return inode()->info(); } fuse::Mount& Context::mount() const { assert(mMount); return *mMount; } } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/file_context.cpp��������������������������������������������0000664�0000000�0000000�00000003301�15162662266�0023235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/file_open_flag.h> #include <mega/fuse/common/mount.h> #include <mega/fuse/platform/file_context.h> #include <mega/fuse/platform/mount.h> namespace mega { namespace fuse { namespace platform { using namespace common; FileContext::FileContext(FileIOContextRef context, fuse::Mount& mount, FileOpenFlags flags): Context(mount), mContext(std::move(context)), mFlags(flags) { FUSEDebugF("File Context %s created", toString(mContext->id()).c_str()); } FileContext::~FileContext() { FUSEDebugF("File Context %s destroyed", toString(mContext->id()).c_str()); } FileContext* FileContext::file() { return this; } Error FileContext::flush() { return mContext->manualFlush(mount()); } InodeRef FileContext::inode() const { return mContext->file(); } ErrorOr<std::string> FileContext::read(m_off_t offset, unsigned int size) { return mContext->read(mount(), offset, size); } Error FileContext::touch(m_time_t modified) { return mContext->touch(mount(), modified); } Error FileContext::truncate(m_off_t size, bool dontGrow) { if ((mFlags & FOF_WRITABLE)) return mContext->truncate(mount(), size, dontGrow); return API_FUSE_EROFS; } ErrorOr<std::size_t> FileContext::write(const void* data, m_off_t length, m_off_t offset, bool noGrow) { // File's only open for reading. if (!(mFlags & FOF_WRITABLE)) return API_FUSE_EBADF; // File's open for appending. if ((mFlags & FOF_APPEND)) offset = -1; // Perform the write. return mContext->write(mount(), data, length, offset, noGrow); } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/�������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020762�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/CMakeLists.txt�����������������������������������������0000664�0000000�0000000�00000000027�15162662266�0023521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/��������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021724�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/CMakeLists.txt������������������������������������0000664�0000000�0000000�00000000033�15162662266�0024460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/�����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023550�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/CMakeLists.txt���������������������������0000664�0000000�0000000�00000000742�15162662266�0026313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE context.h context_forward.h directory_context_forward.h file_context.h file_context_forward.h mount_db_forward.h mount_forward.h path_adapter_forward.h service_context.h service_context_forward.h unmounter.h ) ������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/context.h��������������������������������0000664�0000000�0000000�00000002012�15162662266�0025400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount_forward.h> #include <mega/fuse/platform/context_forward.h> #include <mega/fuse/platform/directory_context_forward.h> #include <mega/fuse/platform/file_context_forward.h> namespace mega { namespace fuse { namespace platform { // Represents the context of an arbitrary filesystem entity. class Context { fuse::Mount* mMount; protected: Context(fuse::Mount& mount); public: virtual ~Context(); // Check if this context represents a directory. virtual DirectoryContext* directory(); // Check if this context represents a file. virtual FileContext* file(); // Retrieve a description of the entity this context represents. InodeInfo info() const; // What inode does this context represent? virtual InodeRef inode() const = 0; // What mount created this context? fuse::Mount& mount() const; }; // Context } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/context_forward.h������������������������0000664�0000000�0000000�00000000511�15162662266�0027126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/badge_forward.h> #include <memory> #include <set> namespace mega { namespace fuse { namespace platform { class Context; using ContextBadge = common::Badge<Context>; using ContextPtr = std::unique_ptr<Context>; using ContextRawPtrSet = std::set<Context*>; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/directory_context_forward.h��������������0000664�0000000�0000000�00000000324�15162662266�0031214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <memory> namespace mega { namespace fuse { namespace platform { class DirectoryContext; using DirectoryContextPtr = std::unique_ptr<DirectoryContext>; } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/file_context.h���������������������������0000664�0000000�0000000�00000002534�15162662266�0026410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/error_or_forward.h> #include <mega/fuse/common/file_io_context_forward.h> #include <mega/fuse/common/file_open_flag_forward.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/file_context_forward.h> #include <mega/types.h> namespace mega { namespace fuse { namespace platform { class FileContext: public Context { // How we actually perform IO operations. FileIOContextRef mContext; // Controls how we perform IO. FileOpenFlags mFlags; public: FileContext(FileIOContextRef context, fuse::Mount& mount, FileOpenFlags flags); ~FileContext(); // Check if this context represents a file. FileContext* file() override; // Flush any modifications to the cloud. Error flush(); // What inode does this context represent? InodeRef inode() const override; // Read data from the file. common::ErrorOr<std::string> read(m_off_t offset, unsigned int size); // Update the file's modification time. Error touch(m_time_t modified); // Truncate the file to a specific size. Error truncate(m_off_t size, bool dontGrow); // Write data to the file. common::ErrorOr<std::size_t> write(const void* data, m_off_t length, m_off_t offset, bool noGrow); }; // FileContext } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/file_context_forward.h�������������������0000664�0000000�0000000�00000000305�15162662266�0030126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <memory> namespace mega { namespace fuse { namespace platform { class FileContext; using FileContextPtr = std::unique_ptr<FileContext>; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/mount_db_forward.h�����������������������0000664�0000000�0000000�00000000170�15162662266�0027252�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class MountDB; } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/mount_forward.h��������������������������0000664�0000000�0000000�00000001054�15162662266�0026607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <map> #include <memory> #include <mutex> #include <set> #include <vector> namespace mega { namespace fuse { namespace platform { class Mount; using MountLock = std::unique_lock<Mount>; using MountPtr = std::shared_ptr<Mount>; using MountPtrVector = std::vector<MountPtr>; using MountPtrSet = std::set<MountPtr>; using MountWeakPtr = std::weak_ptr<Mount>; template<typename T> using ToMountPtrMap = std::map<T, MountPtr>; template<typename T> using ToMountPtrSetMap = std::map<T, MountPtrSet>; } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/path_adapter_forward.h�������������������0000664�0000000�0000000�00000000442�15162662266�0030101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/path_adapter_forward.h> namespace mega { namespace fuse { namespace platform { namespace detail { struct PathAdapterTraits; } // detail using PathAdapter = fuse::detail::PathAdapter<detail::PathAdapterTraits>; } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/service_context.h������������������������0000664�0000000�0000000�00000006603�15162662266�0027132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/database.h> #include <mega/common/task_executor.h> #include <mega/fuse/common/file_cache.h> #include <mega/fuse/common/file_extension_db.h> #include <mega/fuse/common/inode_cache.h> #include <mega/fuse/common/inode_db.h> #include <mega/fuse/common/service_context.h> #include <mega/fuse/common/service_flags.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/fuse/platform/unmounter.h> #include <mega/types.h> namespace mega { namespace fuse { namespace platform { class ServiceContext: public fuse::ServiceContext { public: using fuse::ServiceContext::serviceFlags; ServiceContext(const ServiceFlags& flags, Service& service); ~ServiceContext(); // Add a mount to the database. MountResult add(const MountInfo& info) override; // Check if a file exists in the cache. bool cached(common::NormalizedPath path) const override; // Called by the client when its view of the cloud is current. void current() override; // Describe the inode representing the file at the specified path. common::ErrorOr<InodeInfo> describe(const common::NormalizedPath& path) const override; // Disable an enabled mount. void disable(MountDisabledCallback callback, const std::string& name, bool remember) override; // Discard node events. MountResult discard(bool discard) override; // Downgrade the FUSE database to the specified version. MountResult downgrade(const LocalPath& path, std::size_t target) override; // Enable a disabled mount. MountResult enable(const std::string& name, bool remember) override; // Query whether a specified mount is enabled. bool enabled(const std::string& name) const override; // Execute a function on some thread. common::Task execute(std::function<void(const common::Task&)> function) override; // Update a mount's flags. MountResult flags(const std::string& name, const MountFlags& flags) override; // Query a mount's flags. MountFlagsPtr flags(const std::string& name) const override; // Get our hands on the client's filesystem access instance. FileSystemAccess& fsAccess() const; // Describe the mount associated with path. MountInfoPtr get(const std::string& name) const override; // Describe all (enabled) mounts. MountInfoVector get(bool onlyEnabled) const override; // Retrieve the path of the mounts associated with name. common::NormalizedPath path(const std::string& name) const override; // Remove a disabled mount from the database. MountResult remove(const std::string& name) override; // Update the service's flags. void serviceFlags(const ServiceFlags& flags) override; // Check whether the specified path is "syncable." bool syncable(const common::NormalizedPath& path) const override; // Called by the client when nodes have been changed in the cloud. void updated(common::NodeEventQueue& events) override; // Update the FUSE database to the specified version. MountResult upgrade(const LocalPath& path, std::size_t target) override; common::Database mDatabase; common::TaskExecutor mExecutor; FileExtensionDB mFileExtensionDB; InodeDB mInodeDB; FileCache mFileCache; InodeCache mInodeCache; Unmounter mUnmounter; MountDB mMountDB; }; // ServiceContext } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/service_context_forward.h����������������0000664�0000000�0000000�00000000177�15162662266�0030656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class ServiceContext; } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/mega/fuse/platform/unmounter.h������������������������������0000664�0000000�0000000�00000002352�15162662266�0025757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/fuse/common/mount_result_forward.h> #include <mega/fuse/common/service_callbacks.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/types.h> namespace mega { namespace fuse { namespace platform { class Unmounter final { // Report the result of an unmount operation. void emitEvent(MountDisabledCallback callback, const std::string& name, MountResult result); // Try and unmount the specified mount. void unmount(MountDisabledCallback callback, MountWeakPtr mount, const std::string& name, const LocalPath& path); // Unmount the specified mount. MountResult unmount(Mount& mount, const std::string& path, bool abort); // Tracks whether we have any unmounts in progress. common::ActivityMonitor mActivities; // Which context contains our mounts? platform::ServiceContext& mContext; public: explicit Unmounter(platform::ServiceContext& context); ~Unmounter(); // Unmount the specified mount. void unmount(MountDisabledCallback callback, MountPtr mount); }; // Unmounter } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021213�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/CMakeLists.txt����������������������������������������0000664�0000000�0000000�00000001755�15162662266�0023763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Let the SDK know where it can find our headers. target_include_directories(SDKlib PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # Add sources required by libfuse backend. target_sources(SDKlib PRIVATE constants.cpp directory_context.cpp file_descriptor.cpp inode_invalidator.cpp mount.cpp mount_db.cpp process.cpp request.cpp service.cpp session_base.cpp signal.cpp unmounter.cpp utility.cpp ) # Add libfuse sources. add_subdirectory(libfuse) # Add libfuse headers. add_subdirectory(mega) # Add platform-specific sources. if(APPLE) add_subdirectory(darwin) else() add_subdirectory(linux) endif() # Add libfuse tests. add_subdirectory(testing) �������������������sdk-10.11.0/src/fuse/supported/platform/posix/constants.cpp�����������������������������������������0000664�0000000�0000000�00000000263�15162662266�0023734�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/constants.h> namespace mega { namespace fuse { namespace platform { const std::string FilesystemName = "megafs"; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/�����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022477�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/CMakeLists.txt���������������������������������0000664�0000000�0000000�00000000212�15162662266�0025232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE utility.cpp) add_subdirectory(mega) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023410�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/CMakeLists.txt����������������������������0000664�0000000�0000000�00000000027�15162662266�0026147�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/fuse/�������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024352�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/fuse/CMakeLists.txt�����������������������0000664�0000000�0000000�00000000033�15162662266�0027106�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/fuse/platform/����������������������������0000775�0000000�0000000�00000000000�15162662266�0026176�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/fuse/platform/CMakeLists.txt��������������0000664�0000000�0000000�00000000052�15162662266�0030733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE platform.h) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/mega/fuse/platform/platform.h������������������0000664�0000000�0000000�00000000252�15162662266�0030172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #define LINUX_ONLY(l) #define LINUX_OR_POSIX(l, p) p #define POSIX_ONLY(p) p #define UNIX_ONLY(u) u #define UNIX_OR_WINDOWS(u, w) u #define WINDOWS_ONLY(w) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/darwin/utility.cpp������������������������������������0000664�0000000�0000000�00000003732�15162662266�0024713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/utility.h> #include <sys/mount.h> #include <sys/param.h> #include <sys/ucred.h> #include <cerrno> #include <cstring> #include <vector> namespace mega { namespace fuse { namespace platform { PathVector filesystems(FilesystemPredicate predicate) { // How many filesystems are mounted? auto count = getfsstat(nullptr, 0, MNT_NOWAIT); // Couldn't determine how many filesystems were mounted. if (count < 0) { FUSEWarningF("Couldn't retrieve number of mounted filesystems: %s", std::strerror(errno)); return PathVector(); } // Will contain a description of each mounted filesystem. std::vector<struct statfs> filesystems; // Allocate enough memory for our descriptions. filesystems.resize(static_cast<std::size_t>(count)); // Convenience. count *= sizeof(struct statfs); // Retrieve a description of each mounted filesystem. auto result = getfsstat(&filesystems[0], count, MNT_NOWAIT); // Couldn't retrieve filesystem descriptions. if (result < 0) { FUSEWarningF("Couldn't retrieve filesystem descriptions: %s", std::strerror(errno)); return PathVector(); } PathVector matches; // Iterate over filesystems. for (const auto& filesystem: filesystems) { // Latch each filesystem's path and type. std::string path = filesystem.f_mntonname; std::string type = filesystem.f_fstypename; // Collect path if our predicate is satisfied. if (!predicate || predicate(path, type)) matches.emplace_back(std::move(path)); } // Pass matches to our caller. return matches; } MountResult unmount(const std::string& path, bool) { if (!::unmount(path.c_str(), MNT_FORCE)) return MOUNT_SUCCESS; if (errno == EBUSY) return MOUNT_BUSY; return MOUNT_UNEXPECTED; } } // platform } // fuse } // mega ��������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/directory_context.cpp���������������������������������0000664�0000000�0000000�00000004320�15162662266�0025466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/directory_context.h> namespace mega { namespace fuse { namespace platform { void DirectoryContext::populate() const { std::lock_guard<std::mutex> guard(mLock); // Retrieve children if necessary. if (!mPopulated) mChildren = mDirectory->children(); // Remember that we've retrieved this directory's children. mPopulated = true; } DirectoryContext::DirectoryContext(DirectoryInodeRef directory, fuse::Mount& mount): Context(mount), mChildren(), mDirectory(std::move(directory)), mLock(), mParent(mDirectory->parent()), mPopulated(false) { FUSEDebugF("Directory Context %s created", toString(mDirectory->id()).c_str()); // Directory has no parent but one must be reported. if (!mParent) mParent = mDirectory; } DirectoryContext::~DirectoryContext() { FUSEDebugF("Directory Context %s destroyed", toString(mDirectory->id()).c_str()); } DirectoryContext* DirectoryContext::directory() { return this; } InodeInfo DirectoryContext::get(std::size_t index) const { assert(index < size()); // Populate entries if necessary. populate(); // Assume the caller's interested in this directory. InodeRef child = mDirectory; if (index >= 2) child = mChildren[index - 2]; else if (index) child = mParent; // Child no longer exists. if (!child || child->removed()) return InodeInfo(); // Get our hands on the child's description. auto info = child->info(); // Child's no longer below this directory. if (index >= 2 && info.mParentID != mDirectory->id()) return InodeInfo(); // Tweak filename for symbolic links. if (index < 2) info.mName.assign(index + 1, '.'); // Return description to caller. return info; } InodeRef DirectoryContext::inode() const { return mDirectory; } std::size_t DirectoryContext::size() const { // Populate entries if necessary. populate(); // Two extra for . and .. return mChildren.size() + 2; } } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/file_descriptor.cpp�����������������������������������0000664�0000000�0000000�00000014117�15162662266�0025100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/platform/file_descriptor.h> #include <cerrno> #include <cstring> #include <fcntl.h> #include <mutex> #include <set> #include <unistd.h> #include <utility> namespace mega { namespace fuse { namespace platform { FileDescriptor::FileDescriptor(int descriptor, bool closeOnFork): mDescriptor(descriptor) { if (mDescriptor >= 0) this->closeOnFork(closeOnFork); } FileDescriptor::FileDescriptor(FileDescriptor&& other): mDescriptor(std::move(other.mDescriptor)) { other.mDescriptor = -1; } FileDescriptor::~FileDescriptor() { if (mDescriptor < 0) return; auto result = close(mDescriptor); while (result < 0 && errno == EINTR) result = close(mDescriptor); if (result < 0) FUSEErrorF("Unable to close descriptor: %d: %s", mDescriptor, std::strerror(errno)); } FileDescriptor::operator bool() const { return mDescriptor >= 0; } bool FileDescriptor::operator!() const { return mDescriptor < 0; } FileDescriptor& FileDescriptor::operator=(FileDescriptor&& rhs) { FileDescriptor temp(std::move(rhs)); swap(temp); return *this; } void FileDescriptor::closeOnFork(bool closeOnFork) { auto flags = this->flags() | FD_CLOEXEC; if (closeOnFork) return this->flags(flags | FD_CLOEXEC); this->flags(flags & ~FD_CLOEXEC); } bool FileDescriptor::closeOnFork() const { return (flags() & FD_CLOEXEC) > 0; } void FileDescriptor::flags(int flags) { errno = 0; if (fcntl(mDescriptor, F_SETFD, flags) < 0) throw FUSEErrorF("Unable to set descriptor flags: %d: %s", mDescriptor, std::strerror(errno)); } int FileDescriptor::flags() const { errno = 0; auto flags = fcntl(mDescriptor, F_GETFD); if (flags < 0) throw FUSEErrorF("Unable to retrieve descriptor flags: %d: %s", mDescriptor, std::strerror(errno)); return flags; } int FileDescriptor::get() const { return mDescriptor; } std::size_t FileDescriptor::read(void* buffer, std::size_t length) { auto* m = reinterpret_cast<char*>(buffer); std::size_t numRead = 0; while (numRead < length) { auto result = ::read(mDescriptor, m + numRead, length - numRead); if (result < 0) { if (errno == EINTR) continue; throw FUSEErrorF("Unable to read from descriptor: %d: %s", mDescriptor, std::strerror(errno)); } if (!result) return numRead; numRead += static_cast<std::size_t>(result); } return numRead; } std::size_t FileDescriptor::read(void* buffer, std::size_t length, m_off_t offset) { auto* m = reinterpret_cast<char*>(buffer); std::size_t numRead = 0; while (numRead < length) { auto result = pread(mDescriptor, m + numRead, length - numRead, offset); if (result < 0) { if (errno == EINTR) continue; throw FUSEErrorF("Unable to read from descriptor: %d: %s", mDescriptor, std::strerror(errno)); } if (!result) return numRead; numRead += static_cast<std::size_t>(result); offset += result; } return numRead; } std::string FileDescriptor::readAll() { for (std::string buffer;;) { constexpr std::size_t BLOCK_SIZE = 4096; // Latch buffer's current size. auto size = buffer.size(); // Allocate additional buffer space. buffer.resize(size + BLOCK_SIZE); // Read bytes into buffer. auto numRead = read(&buffer[size], BLOCK_SIZE); // Read an entire block. if (numRead == BLOCK_SIZE) continue; // Shrink buffer to size. buffer.resize(size + numRead); buffer.shrink_to_fit(); // Return buffer to caller. return buffer; } } void FileDescriptor::redirect(const FileDescriptor& other) { while (true) { if (dup2(mDescriptor, other.mDescriptor) >= 0) return; if (errno == EINTR) continue; throw FUSEErrorF("Unable to redirect descriptor %d to %d: %s", mDescriptor, other.mDescriptor, std::strerror(errno)); } } int FileDescriptor::release() { auto descriptor = mDescriptor; mDescriptor = -1; return descriptor; } void FileDescriptor::reset(int descriptor) { operator=(FileDescriptor(descriptor)); } void FileDescriptor::swap(FileDescriptor& other) { using std::swap; swap(mDescriptor, other.mDescriptor); } std::size_t FileDescriptor::write(const void* buffer, std::size_t length) { auto* m = reinterpret_cast<const char*>(buffer); std::size_t numWritten = 0; while (numWritten < length) { auto result = ::write(mDescriptor, m + numWritten, length - numWritten); if (result > 0) { numWritten += static_cast<std::size_t>(result); continue; } if (errno == EINTR) continue; throw FUSEErrorF("Unable to write to descriptor: %d: %s", mDescriptor, std::strerror(errno)); } return numWritten; } std::size_t FileDescriptor::write(const void* buffer, std::size_t length, m_off_t offset) { auto* m = static_cast<const char*>(buffer); std::size_t numWritten = 0; while (numWritten < length) { auto result = pwrite(mDescriptor, m + numWritten, length - numWritten, offset); if (result > 0) { numWritten += static_cast<std::size_t>(result); offset += result; continue; } if (errno == EINTR) continue; throw FUSEErrorF("Unable to write to descriptor: %d: %s", mDescriptor, std::strerror(errno)); } return numWritten; } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/inode_invalidator.cpp���������������������������������0000664�0000000�0000000�00000015623�15162662266�0025420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/activity_monitor.h> #include <mega/common/utility.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/inode_invalidator.h> #include <mega/fuse/platform/mount.h> #include <cassert> #include <stdexcept> #include <utility> namespace mega { namespace fuse { namespace platform { enum InvalidationFlag : unsigned int { // Invalidating an inode's attributes. IF_ATTRIBUTES = 0x1, // Invalidating an inode's data. IF_DATA = 0x2 }; // InvalidationFlag using InvalidationFlags = unsigned int; using namespace common; struct InodeInvalidator::Invalidation { Invalidation(ActivityMonitor& activities, MountInodeID id): mActivity(activities.begin()), mEntries(), mID(id), mFlags(0) {} void invalidate(Session& session) try { // Invalidate the inode's attributes. if ((mFlags & IF_ATTRIBUTES)) session.invalidateAttributes(mID); // Invalidate the inode's data. if ((mFlags & IF_DATA)) session.invalidateData(mID, mData.mBegin, mData.mEnd - mData.mBegin); // Invalidate the inode's directory entries. for (auto& entry: mEntries) { for (auto& child: entry.second) session.invalidateEntry(entry.first, child, mID); session.invalidateEntry(entry.first, mID); } } catch (std::runtime_error& exception) { FUSEWarningF("Unable to invalidate inode %s: %s", toString(mID).c_str(), exception.what()); } // What data do we need to invalidate? struct { m_off_t mBegin; m_off_t mEnd; } mData; // Lets our mount know its still being used. Activity mActivity; // What entries need to be invalidated? std::map<std::string, MountInodeIDSet> mEntries; // What inode are we invalidating? MountInodeID mID; // What invalidations do we need to perform? InvalidationFlags mFlags; }; // Invalidation auto InodeInvalidator::invalidation(ActivityMonitor& activities, MountInodeID id) -> Invalidation& { // Has an invalidation already been queued for this inode? auto i = mInvalidationByID.find(id); // An invalidation's already been queued. if (i != mInvalidationByID.end()) return i->second; // Add an invalidation request for this inode. auto result = mInvalidationByID.emplace(std::piecewise_construct, std::forward_as_tuple(id), std::forward_as_tuple(activities, id)); // Sanity. assert(result.second); // Remember when the inode should be invalidated. mInvalidationByOrder.emplace_back(result.first); // Let the worker know there's an invalidation to perform. mCV.notify_one(); // Return a reference to the new invalidation. return result.first->second; } void InodeInvalidator::loop() { FUSEDebug1("Inode Invalidator Worker thread started"); std::unique_lock<std::mutex> lock(mLock); auto hasWork = [&]() { return mTerminate || !mInvalidationByID.empty(); }; // hasWork while (true) { // Wait until we have some work to do. mCV.wait(lock, hasWork); // We're being terminated. if (mTerminate) return; // Pop an invalidation from the queue. auto invalidation = ([&]() { assert(!mInvalidationByOrder.empty()); // What invalidation are we going to perform? auto i = mInvalidationByOrder.front(); // Pop the invalidation. mInvalidationByOrder.pop_front(); // Latch the invalidation request. auto invalidation = std::move(i->second); // Remove the request from our map. mInvalidationByID.erase(i); // Return invalidation request to caller. return invalidation; })(); // Release the lock so pending invalidations can be modified. lock.unlock(); // Invalidate the inode. invalidation.invalidate(mSession); // Reacquire lock. lock.lock(); } FUSEDebug1("Inode Invalidator Worker thread stopped"); } InodeInvalidator::InodeInvalidator(Session& session): mCV(), mInvalidationByID(), mInvalidationByOrder(), mLock(), mSession(session), mTerminate{false}, mWorker(&InodeInvalidator::loop, this) { FUSEDebug1("Inode Invalidator constructed"); } InodeInvalidator::~InodeInvalidator() { // Let the worker know it has to terminate. mTerminate = true; // Wake up the worker if's sleeping. mCV.notify_one(); // Wait for the worker to terminate. mWorker.join(); FUSEDebug1("Inode Invalidator destroyed"); } void InodeInvalidator::invalidateAttributes(ActivityMonitor& activities, MountInodeID id) { std::lock_guard<std::mutex> guard(mLock); // Get our hands on this inode's invalidation. auto& invalidation = this->invalidation(activities, id); // Signal that we need to invalidate this inode's attributes. invalidation.mFlags |= IF_ATTRIBUTES; } void InodeInvalidator::invalidateEntry(ActivityMonitor& activities, MountInodeID child, const std::string& name, MountInodeID parent) { // Sanity. assert(!name.empty()); std::lock_guard<std::mutex> guard(mLock); // Get our hands on this inode's invalidation. auto& invalidation = this->invalidation(activities, parent); // Record which entry we want to invalidate. invalidation.mEntries[name].emplace(child); } void InodeInvalidator::invalidateEntry(ActivityMonitor& activities, MountInodeID id, const std::string& name) { // Sanity. assert(!name.empty()); std::lock_guard<std::mutex> guard(mLock); // Get our hands on this inode's invalidation. auto& invalidation = this->invalidation(activities, id); // Record which entry we want to invalidate. invalidation.mEntries[name]; } void InodeInvalidator::invalidateData(ActivityMonitor& activities, MountInodeID id, m_off_t offset, m_off_t size) { // Sanity. assert(offset >= 0); assert(size >= 0); std::lock_guard<std::mutex> guard(mLock); // Get our hands on this inode's invalidation. auto& invalidation = this->invalidation(activities, id); // Signal that we want to invalidate this inode's data. invalidation.mFlags |= IF_DATA; // Convenience. auto& begin = invalidation.mData.mBegin; auto& end = invalidation.mData.mEnd; // Specify what data we want to invalidate. begin = std::min(begin, offset); end = std::max(end, offset + size); } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022644�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/��������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023005�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/CMakeLists.txt������������������������������0000664�0000000�0000000�00000000234�15162662266�0025544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(mega) target_include_directories(SDKlib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE session.cpp session_base.cpp) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/���������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023716�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/CMakeLists.txt�������������������������0000664�0000000�0000000�00000000027�15162662266�0026455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/����������������������������������0000775�0000000�0000000�00000000000�15162662266�0024660�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/CMakeLists.txt��������������������0000664�0000000�0000000�00000000033�15162662266�0027414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/platform/�������������������������0000775�0000000�0000000�00000000000�15162662266�0026504�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/platform/CMakeLists.txt�����������0000664�0000000�0000000�00000000063�15162662266�0031243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE library.h session.h) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/platform/library.h����������������0000664�0000000�0000000�00000000112�15162662266�0030313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #define FUSE_USE_VERSION 26 #include <fuse/fuse_lowlevel.h> ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/mega/fuse/platform/session.h����������������0000664�0000000�0000000�00000003026�15162662266�0030341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/session_base.h> namespace mega { namespace fuse { namespace platform { // How we communicate with FUSE. class Session: public SessionBase { class ChannelDeleter { Mount* mMount; public: ChannelDeleter(Mount& mount); void operator()(fuse_chan* channel); }; // ChannelDeleter using ChannelPtr = std::unique_ptr<fuse_chan, ChannelDeleter>; static void forget(fuse_req_t request, fuse_ino_t inode, unsigned long num); // Retrieve the next request from FUSE. std::string nextRequest(); void populateOperations(fuse_lowlevel_ops& operations) override; static void rename(fuse_req_t request, fuse_ino_t sourceParent, const char* sourceName, fuse_ino_t targetParent, const char* targetName); ChannelPtr mChannel; public: Session(Mount& mount); ~Session() = default; // What descriptor is the session using to communicate with FUSE? int descriptor() const override; // Dispatch a request received from FUSE. void dispatch() override; // Invalidate an inode's data. void invalidateData(MountInodeID id, off_t offset, off_t size) override; // Invalidate a specific directory entry. void invalidateEntry(const std::string& name, MountInodeID child, MountInodeID parent) override; void invalidateEntry(const std::string& name, MountInodeID parent) override; }; // Session } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/session.cpp���������������������������������0000664�0000000�0000000�00000014536�15162662266�0025205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_executor.h> #include <mega/common/utility.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/request.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/session.h> #include <mega/fuse/platform/utility.h> #include <cassert> #include <cstring> #include <vector> namespace mega { namespace fuse { namespace platform { Session::ChannelDeleter::ChannelDeleter(Mount& mount): mMount(&mount) {} void Session::ChannelDeleter::operator()(fuse_chan* channel) { if (!channel) return; fuse_session_remove_chan(channel); fuse_unmount(mMount->path().toPath(false).c_str(), channel); } void Session::forget(fuse_req_t request, fuse_ino_t inode, unsigned long num) { SessionBase::forget(request, inode, num); } std::string Session::nextRequest() { assert(mChannel); assert(mSession); auto channel = mChannel.get(); std::string buffer(fuse_chan_bufsize(channel), '\0'); while (true) { auto result = fuse_chan_recv(&channel, &buffer[0], buffer.size()); if (!result) return std::string(); if (result > 0) { buffer.resize(static_cast<std::size_t>(result)); return buffer; } // We can hit this case when poll(...) tells us that our channel // has data available for reading but it really doesn't. // // Put differently, this is here to guard against spurious wakeups. if (result == -EAGAIN) return std::string(); if (result == -EINTR) continue; throw FUSEErrorF("Unable to read request from session: %s", std::strerror(-result)); } } void Session::populateOperations(fuse_lowlevel_ops& operations) { SessionBase::populateOperations(operations); operations.forget = &Session::forget; operations.rename = &Session::rename; } void Session::rename(fuse_req_t request, fuse_ino_t parent, const char* name, fuse_ino_t newParent, const char* newName) { MountInodeID parent_(parent); MountInodeID newParent_(newParent); FUSEDebugF("rename: parent: %s, name: %s, newParent: %s, " "newName: %s, request: %p", toString(parent_).c_str(), name, toString(newParent_).c_str(), newName, request); mount(request).execute(&Mount::rename, true, Request(request), parent_, std::string(name), newParent_, std::string(newName), 0); } Session::Session(Mount& mount): SessionBase(mount), mChannel(nullptr, mount) { auto arguments = Arguments(mount.name()); auto path = mMount.path().toPath(false); ChannelPtr channel(fuse_mount(path.c_str(), arguments.get()), mount); if (!channel) throw FUSEErrorF("Unable to construct channel: %s", path.c_str()); nonblocking(fuse_chan_fd(channel.get()), true); SessionPtr session( fuse_lowlevel_new(arguments.get(), &operations(), sizeof(fuse_lowlevel_ops), this)); if (!session) throw FUSEErrorF("Unable to construct session: %s", path.c_str()); fuse_session_add_chan(session.get(), channel.get()); mChannel = std::move(channel); mSession = std::move(session); FUSEDebugF("Session constructed: %s", path.c_str()); } int Session::descriptor() const { assert(mChannel); return fuse_chan_fd(mChannel.get()); } void Session::dispatch() { // Sanity. assert(mChannel); assert(mSession); // Dispatch the request. if (auto request = nextRequest(); !request.empty()) fuse_session_process(mSession.get(), request.data(), request.size(), mChannel.get()); } void Session::invalidateData(MountInodeID id, off_t offset, off_t length) { assert(mChannel); assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_inval_inode(mChannel.get(), id.get(), offset, length); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate inode: %s: %s", toString(id).c_str(), std::strerror(-result)); } } void Session::invalidateEntry(const std::string& name, MountInodeID child, MountInodeID parent) { assert(!name.empty()); assert(mChannel); assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_delete(mChannel.get(), parent.get(), child.get(), name.c_str(), name.size()); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate entry: %s %s %s: %s", toString(child).c_str(), toString(parent).c_str(), name.c_str(), std::strerror(-result)); } } void Session::invalidateEntry(const std::string& name, MountInodeID parent) { assert(!name.empty()); assert(mChannel); assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_inval_entry(mChannel.get(), parent.get(), name.c_str(), name.size()); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate entry: %s %s: %s", toString(parent).c_str(), name.c_str(), std::strerror(-result)); } } } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/2/session_base.cpp����������������������������0000664�0000000�0000000�00000000411�15162662266�0026162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/session_base.h> namespace mega { namespace fuse { namespace platform { void SessionBase::SessionDeleter::operator()(fuse_session* session) { if (session) fuse_session_destroy(session); } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/��������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023006�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/CMakeLists.txt������������������������������0000664�0000000�0000000�00000000234�15162662266�0025545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(mega) target_include_directories(SDKlib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE session.cpp session_base.cpp) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/���������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023717�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/CMakeLists.txt�������������������������0000664�0000000�0000000�00000000027�15162662266�0026456�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/����������������������������������0000775�0000000�0000000�00000000000�15162662266�0024661�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/CMakeLists.txt��������������������0000664�0000000�0000000�00000000033�15162662266�0027415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/platform/�������������������������0000775�0000000�0000000�00000000000�15162662266�0026505�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/platform/CMakeLists.txt�����������0000664�0000000�0000000�00000000063�15162662266�0031244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE library.h session.h) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/platform/library.h����������������0000664�0000000�0000000�00000000113�15162662266�0030315�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #define FUSE_USE_VERSION 35 #include <fuse3/fuse_lowlevel.h> �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/mega/fuse/platform/session.h����������������0000664�0000000�0000000�00000002331�15162662266�0030340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/session_base.h> namespace mega { namespace fuse { namespace platform { // How we communicate with FUSE. class Session: public SessionBase { void populateCapabilities(fuse_conn_info* connection) override; void populateOperations(fuse_lowlevel_ops& operations) override; static void rename(fuse_req_t request, fuse_ino_t sourceParent, const char* sourceName, fuse_ino_t targetParent, const char* targetName, unsigned int flags); public: Session(Mount& mount); ~Session() = default; // What descriptor is the session using to communicate with FUSE? int descriptor() const override; // Dispatch a request received from FUSE. void dispatch() override; // Invalidate an inode's data. void invalidateData(MountInodeID id, off_t offset, off_t size) override; // Invalidate a specific directory entry. void invalidateEntry(const std::string& name, MountInodeID child, MountInodeID parent) override; void invalidateEntry(const std::string& name, MountInodeID parent) override; }; // Session } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/session.cpp���������������������������������0000664�0000000�0000000�00000014531�15162662266�0025201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_executor.h> #include <mega/common/utility.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/request.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/session.h> #include <mega/fuse/platform/utility.h> #include <mega/scoped_helpers.h> #include <cassert> #include <cstring> #include <vector> namespace mega { namespace fuse { namespace platform { void Session::populateCapabilities(fuse_conn_info* connection) { SessionBase::populateCapabilities(connection); connection->want |= FUSE_CAP_EXPLICIT_INVAL_DATA; #ifdef FUSE_CAP_NO_EXPORT connection->want |= FUSE_CAP_NO_EXPORT; #endif // FUSE_CAP_NO_EXPORT } void Session::populateOperations(fuse_lowlevel_ops& operations) { SessionBase::populateOperations(operations); operations.forget = &Session::forget; operations.rename = &Session::rename; } void Session::rename(fuse_req_t request, fuse_ino_t parent, const char* name, fuse_ino_t newParent, const char* newName, unsigned int flags) { #define ENTRY(name) \ { \ #name, name \ } static const std::map<std::string, int> names = {ENTRY(RENAME_EXCHANGE), ENTRY(RENAME_NOREPLACE)}; // names #undef ENTRY MountInodeID parent_(parent); MountInodeID newParent_(newParent); FUSEDebugF("rename: parent: %s, name: %s, newParent: %s, " "newName: %s, request: %p", toString(parent_).c_str(), name, toString(newParent_).c_str(), newName, request); for (const auto& i: names) { if ((flags & i.second)) FUSEDebugF("rename: flag: %s", i.first.c_str()); } mount(request).execute(&Mount::rename, true, Request(request), parent_, std::string(name), newParent_, std::string(newName), flags); } Session::Session(Mount& mount): SessionBase(mount) { auto arguments = Arguments(mount.name()); auto path = mMount.path().toPath(false); SessionPtr session( fuse_session_new(arguments.get(), &operations(), sizeof(fuse_lowlevel_ops), this)); if (!session) throw FUSEErrorF("Unable to construct session: %s", path.c_str()); auto result = fuse_session_mount(session.get(), path.c_str()); if (result < 0) throw FUSEErrorF("Unable to bind session to mount point: %s: %s", path.c_str(), std::strerror(-result)); nonblocking(fuse_session_fd(session.get()), true); mSession = std::move(session); FUSEDebugF("Session constructed: %s", path.c_str()); } int Session::descriptor() const { assert(mSession); return fuse_session_fd(mSession.get()); } void Session::dispatch() { // Sanity. assert(mSession); struct fuse_buf buffer {}; // Make sure our buffer is always released. auto releaser = makeScopedDestructor( [&buffer]() { free(buffer.mem); }); // releaser // Try and read a request from libfuse. while (true) { auto result = fuse_session_receive_buf(mSession.get(), &buffer); // Ignore zero length requests. if (!result) return; // We've got a request. if (result > 0) break; // Spurious wakeup. if (result == -EAGAIN) return; // Call was interrupted, retry. if (result == -EINTR) continue; // Couldn't get a request. throw FUSEErrorF("Unable to read request from session: %s", std::strerror(-result)); } // Dispatch the request. fuse_session_process_buf(mSession.get(), &buffer); } void Session::invalidateData(MountInodeID id, off_t offset, off_t length) { assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_inval_inode(mSession.get(), id.get(), offset, length); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate inode: %s: %s", toString(id).c_str(), std::strerror(-result)); } } void Session::invalidateEntry(const std::string& name, MountInodeID child, MountInodeID parent) { assert(!name.empty()); assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_delete(mSession.get(), parent.get(), child.get(), name.c_str(), name.size()); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate entry: %s %s %s: %s", toString(child).c_str(), toString(parent).c_str(), name.c_str(), std::strerror(-result)); } } void Session::invalidateEntry(const std::string& name, MountInodeID parent) { assert(!name.empty()); assert(mSession); while (!exited()) { auto result = fuse_lowlevel_notify_inval_entry(mSession.get(), parent.get(), name.c_str(), name.size()); if (!result || result == -ENOENT || result == -ENOTCONN) return; if (result == -EINTR) continue; throw FUSEErrorF("Unable to invalidate entry: %s %s: %s", toString(parent).c_str(), name.c_str(), std::strerror(-result)); } } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/3/session_base.cpp����������������������������0000664�0000000�0000000�00000000472�15162662266�0026172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/session_base.h> namespace mega { namespace fuse { namespace platform { void SessionBase::SessionDeleter::operator()(fuse_session* session) { if (!session) return; fuse_session_unmount(session); fuse_session_destroy(session); } } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/CMakeLists.txt��������������������������������0000664�0000000�0000000�00000001255�15162662266�0025407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Let CMake know where it can find our FindFUSE module. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # Make sure libfuse is present. find_package(FUSE REQUIRED) # Make sure 32bit UNIX platforms are building against libfuse3. if (CMAKE_SIZEOF_VOID_P EQUAL 4 AND FUSE_VERSION_MAJOR LESS 3) message(FATAL_ERROR "32bit UNIX platforms must link against libfuse3.") endif() # Make sure dependent applications support 64bit inodes. target_compile_definitions(SDKlib PUBLIC _FILE_OFFSET_BITS=64) # Make sure the SDKs linked against libfuse. target_link_libraries(SDKlib PRIVATE FUSE) # Add version-specific libfuse sources. add_subdirectory(${FUSE_VERSION_MAJOR}) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/cmake/����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023724�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/libfuse/cmake/FindFUSE.cmake��������������������������0000664�0000000�0000000�00000004506�15162662266�0026276�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������find_path(FUSE_INCLUDE_DIR fuse_common.h HINTS $ENV{FUSE_PREFIX} PATH_SUFFIXES include/fuse3 include/fuse ) find_library(FUSE_LIBRARY libfuse3.so libfuse.dylib libfuse.so HINTS $ENV{FUSE_PREFIX} PATH_SUFFIXES lib ) if (FUSE_INCLUDE_DIR AND FUSE_LIBRARY) find_package(Threads) set(FUSE_DEFINITIONS -D_FILE_OFFSET_BITS=64) if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.20.0") cmake_path(GET FUSE_INCLUDE_DIR PARENT_PATH FUSE_INCLUDE_DIRS) else() get_filename_component(FUSE_INCLUDE_DIRS "${FUSE_INCLUDE_DIR}" DIRECTORY) endif() set(FUSE_LIBRARIES ${CMAKE_THREAD_LIBS_INIT} ${FUSE_LIBRARY}) if (NOT TARGET FUSE) add_library(FUSE UNKNOWN IMPORTED) set_target_properties( FUSE PROPERTIES IMPORTED_LOCATION ${FUSE_LIBRARY} ) if (Threads_FOUND) target_link_libraries(FUSE INTERFACE Threads::Threads) endif() target_compile_definitions(FUSE INTERFACE ${FUSE_DEFINITIONS}) target_include_directories(FUSE INTERFACE ${FUSE_INCLUDE_DIRS}) endif() # Assume we've found libfuse 3.x. set(FUSE_VERSION_PATH "${FUSE_INCLUDE_DIR}/libfuse_config.h") # We've actually found libfuse 2.x. if (NOT EXISTS "${FUSE_VERSION_PATH}") set(FUSE_VERSION_PATH "${FUSE_INCLUDE_DIR}/fuse_common.h") endif() # Read the version file. file(READ "${FUSE_VERSION_PATH}" CONTENT) # Parse version. string(REGEX REPLACE ".*#define FUSE_MAJOR_VERSION +([0-9]+).*$" "\\1" FUSE_VERSION_MAJOR ${CONTENT} ) string(REGEX REPLACE ".*define FUSE_MINOR_VERSION +([0-9]+).*$" "\\1" FUSE_VERSION_MINOR ${CONTENT} ) # Latch full version. set(FUSE_VERSION "${FUSE_VERSION_MAJOR}.${FUSE_VERSION_MINOR}") # Cleanup after ourselves. unset(CONTENT) unset(FUSE_VERSION_PATH) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( FUSE REQUIRED_VARS FUSE_INCLUDE_DIR FUSE_LIBRARY VERSION_VAR FUSE_VERSION ) mark_as_advanced(FUSE_INCLUDE_DIR FUSE_LIBRARY) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022352�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/CMakeLists.txt����������������������������������0000664�0000000�0000000�00000001057�15162662266�0025115�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Linux definitely has the O_PATH flag. target_compile_definitions(SDKlib PUBLIC HAS_OPEN_PATH) # Linux may or may not have the RENAME_(EXCHANGE|NOREPLACE) flags. try_compile(HAS_RENAME_FLAGS "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/checks/has_rename_flags.cpp" ) if (HAS_RENAME_FLAGS) target_compile_definitions(SDKlib PUBLIC HAS_RENAME_FLAGS) endif() target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(SDKlib PRIVATE utility.cpp) add_subdirectory(mega) #add_subdirectory(testing) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/checks/�����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023612�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/checks/has_rename_flags.cpp���������������������0000664�0000000�0000000�00000000311�15162662266�0027567�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <cstdio> #include <cstdlib> int main(int, char**) { [[maybe_unused]] auto exchange = RENAME_EXCHANGE; [[maybe_unused]] auto no_replace = RENAME_NOREPLACE; return EXIT_SUCCESS; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/�������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023263�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/CMakeLists.txt�����������������������������0000664�0000000�0000000�00000000027�15162662266�0026022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/fuse/��������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024225�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/fuse/CMakeLists.txt������������������������0000664�0000000�0000000�00000000033�15162662266�0026761�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/fuse/platform/�����������������������������0000775�0000000�0000000�00000000000�15162662266�0026051�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/fuse/platform/CMakeLists.txt���������������0000664�0000000�0000000�00000000052�15162662266�0030606�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE platform.h) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/mega/fuse/platform/platform.h�������������������0000664�0000000�0000000�00000000252�15162662266�0030045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #define LINUX_ONLY(l) l #define LINUX_OR_POSIX(l, p) l #define POSIX_ONLY(p) #define UNIX_ONLY(u) u #define UNIX_OR_WINDOWS(u, w) u #define WINDOWS_ONLY(w) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/testing/����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024027�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/testing/CMakeLists.txt��������������������������0000664�0000000�0000000�00000000036�15162662266�0026566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(integration) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/testing/integration/����������������������������0000775�0000000�0000000�00000000000�15162662266�0026352�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/testing/integration/CMakeLists.txt��������������0000664�0000000�0000000�00000000163�15162662266�0031112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if (NOT TARGET test_integration) return() endif() target_sources(test_integration PRIVATE platform_tests.cpp) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/testing/integration/platform_tests.cpp����������0000664�0000000�0000000�00000011655�15162662266�0032134�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/testing/cloud_path.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/testing/platform_tests.h> #include <mega/fuse/platform/testing/wrappers.h> namespace mega { namespace fuse { namespace testing { using namespace common::testing; using platform::FileDescriptor; static int futimesat(const FileDescriptor& descriptor, const Path& path, struct timeval (×)[2]); static int mknod(const Path& path, mode_t mode, dev_t dev); static int mknodat(const FileDescriptor& descriptor, const Path& path, mode_t mode, dev_t dev); TEST_P(FUSEPlatformTests, mknod_at_fails_when_already_exists) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_TRUE(mknodat(s, "sf0", S_IFREG | 0644, 0)); ASSERT_EQ(errno, EEXIST); } TEST_P(FUSEPlatformTests, mknod_at_fails_when_below_file) { auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_TRUE(mknodat(sf0, "x", S_IFREG | 0644, 0)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, mknod_at_fails_when_not_regular_file) { static const std::vector<mode_t> types = { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFSOCK, }; // types auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); for (auto type: types) { ASSERT_TRUE(mknodat(s, "sfx", type | 0644, 0)); ASSERT_EQ(errno, EPERM); ASSERT_TRUE(accessat(s, "sfx", F_OK)); ASSERT_EQ(errno, ENOENT); } } TEST_P(FUSEPlatformTests, mknod_at_fails_when_read_only) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); ASSERT_TRUE(mknodat(s, "sfx", S_IFREG | 0644, 0)); ASSERT_EQ(errno, EROFS); ASSERT_TRUE(accessat(s, "sfx", F_OK)); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mknod_at_fails_when_unknown) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_TRUE(mknodat(s, Path("sdx") / "sfx", S_IFREG | 0644, 0)); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mknod_at_succeeds) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_FALSE(mknodat(s, "sfx", S_IFREG | 0644, 0)); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathO() / "sfx", F_OK); }, mDefaultTimeout)); auto sfx = openat(s, "sfx", O_RDWR); ASSERT_TRUE(sfx); ASSERT_FALSE(fsync(sfx)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sfx"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, mknod_fails_when_already_exists) { ASSERT_TRUE(mknod(MountPathW() / "sf0", S_IFREG | 0644, 0)); ASSERT_EQ(errno, EEXIST); } TEST_P(FUSEPlatformTests, mknod_fails_when_below_file) { ASSERT_TRUE(mknod(MountPathW() / "sf0" / "x", S_IFREG | 0644, 0)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, mknod_fails_when_not_regular_file) { static const std::vector<mode_t> types = { S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFSOCK, }; // types for (auto type: types) { ASSERT_TRUE(mknod(MountPathW() / "sfx", type | 0644, 0)); ASSERT_EQ(errno, EPERM); ASSERT_TRUE(access(MountPathW() / "sfx", F_OK)); ASSERT_EQ(errno, ENOENT); } } TEST_P(FUSEPlatformTests, mknod_fails_when_read_only) { ASSERT_TRUE(mknod(MountPathR() / "sfx", S_IFREG | 0644, 0)); ASSERT_EQ(errno, EROFS); ASSERT_TRUE(access(MountPathR() / "sfx", F_OK)); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mknod_fails_when_unknown) { ASSERT_TRUE(mknod(MountPathW() / "sdx" / "sfx", S_IFREG | 0644, 0)); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mknod_succeeds) { ASSERT_FALSE(mknod(MountPathW() / "sfx", S_IFREG | 0644, 0)); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathO() / "sfx", F_OK); }, mDefaultTimeout)); auto sfx = open(MountPathW() / "sfx", O_RDWR); ASSERT_TRUE(sfx); ASSERT_FALSE(fsync(sfx)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sfx"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); } int futimesat(const FileDescriptor& descriptor, const Path& path, struct timeval (×)[2]) { return ::futimesat(descriptor.get(), path.string().c_str(), times); } int mknod(const Path& path, mode_t mode, dev_t dev) { return ::mknod(path.string().c_str(), mode, dev); } int mknodat(const FileDescriptor& descriptor, const Path& path, mode_t mode, dev_t dev) { return ::mknodat(descriptor.get(), path.string().c_str(), mode, dev); } } // testing } // fuse } // mega �����������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/linux/utility.cpp�������������������������������������0000664�0000000�0000000�00000011033�15162662266�0024557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/utility.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/process.h> #include <mega/fuse/platform/utility.h> #include <sys/stat.h> #include <sys/sysmacros.h> #include <cerrno> #include <fcntl.h> #include <mntent.h> #include <unistd.h> namespace mega { namespace fuse { namespace platform { using namespace common; bool abort(const std::string& path) { // Clarity. constexpr auto flags = AT_STATX_DONT_SYNC; constexpr auto mask = STATX_BASIC_STATS; // Try and retrieve information about path. struct statx attributes; // Couldn't retrieve information about path. if (statx(0, path.c_str(), flags, mask, &attributes)) { FUSEErrorF("Couldn't retrieve information about %s: %s", path.c_str(), std::strerror(errno)); return false; } // Compute absolute device number. auto device = makedev(attributes.stx_dev_major, attributes.stx_dev_minor); // Compute abort file path. auto abortPath = format("/sys/fs/fuse/connections/%lu/abort", device); // Try and open abort file for writing. FileDescriptor abortFile(open(abortPath.c_str(), O_CLOEXEC | O_SYNC | O_WRONLY)); // Couldn't open abort file. if (!abortFile) { FUSEErrorF("Couldn't open abort file for writing: %s: %s", abortPath.c_str(), std::strerror(errno)); return false; } // Try and write the abort file. try { static const char one[] = "1\n"; // Might throw. abortFile.write(one, sizeof(one)); } catch (std::runtime_error& exception) { FUSEErrorF("Couldn't write abort file: %s: %s", abortPath.c_str(), std::strerror(errno)); return false; } return true; } PathVector filesystems(FilesystemPredicate predicate) { // Convenience. using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>; // Where should we search for a suitable mtab? static const std::vector<std::string> paths = {"/proc/mounts", "/etc/mtab"}; // paths FilePtr mounts(nullptr, std::fclose); // Try and locate a suitable mtab. for (auto& path: paths) { // Try and open this mtab. mounts.reset(setmntent(path.c_str(), "r")); // Found a suitable mtab. if (!!mounts) break; // Leave a trail of what we've done. FUSEDebugF("Unable to open mtab: %s: %s", path.c_str(), std::strerror(errno)); } // Couldn't find a suitable mtab. if (!mounts) { FUSEWarning1("Unable to locate a suitable mtab"); return PathVector(); } PathVector matches; // Iterate over the mtab's entries. for (auto* entry = getmntent(mounts.get()); entry; errno = 0, entry = getmntent(mounts.get())) { // Latch mount's path and type. std::string path = entry->mnt_dir; std::string type = entry->mnt_type; // Collect path if our predicate is satisfied. if (!predicate || predicate(path, type)) matches.emplace_back(std::move(path)); } // Couldn't iterate over all of the mtab's entries. if (errno) { FUSEWarningF("Unable to iterate over mtab entries: %s", std::strerror(errno)); return PathVector(); } // Pass matches to our caller. return matches; } MountResult unmount(const std::string& path, bool abort) try { // Try and abort the mount. if (abort && !platform::abort(path)) FUSEWarningF("Unable to abort mount: %s", path.c_str()); // Provided by libfuse. static const std::string command = "/usr/bin/fusermount"; // Populate arguments. std::vector<std::string> arguments; // Unmount. arguments.emplace_back("-u"); // This mount. arguments.emplace_back(path); // Execute fusermount. auto process = run(command, arguments); // Read fusermount's output. auto output = process.output().readAll(); // Wait for fusermount to terminate. auto result = process.wait(); // Mount was successfully unmounted. if (!result) return MOUNT_SUCCESS; // Mount was busy. if (output.find("busy") != std::string::npos) return MOUNT_BUSY; // Couldn't unmount for some unknown reason. return MOUNT_UNEXPECTED; } catch (std::runtime_error& exception) { FUSEErrorF("Unable to unmount %s: %s", path.c_str(), exception.what()); return MOUNT_UNEXPECTED; } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/�������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022124�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/CMakeLists.txt�����������������������������������0000664�0000000�0000000�00000000027�15162662266�0024663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/��������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023066�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/CMakeLists.txt������������������������������0000664�0000000�0000000�00000000033�15162662266�0025622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/�����������������������������������0000775�0000000�0000000�00000000000�15162662266�0024712�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/CMakeLists.txt���������������������0000664�0000000�0000000�00000003645�15162662266�0027462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE constants.h directory_context.h file_descriptor.h file_descriptor_forward.h inode_invalidator.h mount.h mount_db.h path_adapter.h process.h process_forward.h request.h request_forward.h session_base.h session_forward.h signal.h utility.h ) # Convenience. set(CAPABILITY_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}") set(CAPABILITY_HEADER_FILE "${CAPABILITY_HEADER_PATH}/capability_flags.i") # Do we need to generate a list of known capability flags? if (NOT EXISTS "${CAPABILITY_HEADER_FILE}") # Convenience. set(CAPABILITY_PATTERN "^#define (FUSE_CAP_[^ \t]+).*$") # Extract capability flag definitions. file(STRINGS "${FUSE_INCLUDE_DIR}/fuse_common.h" CAPABILITY_DEFINITIONS REGEX "${CAPABILITY_PATTERN}") # Create mega/fuse/platform if necessary. file(MAKE_DIRECTORY "${CAPABILITY_HEADER_PATH}") # Write capability flag entries to a header. foreach (CAPABILITY_DEFINITION ${CAPABILITY_DEFINITIONS}) # Generate an entry for this capability flag. string(REGEX REPLACE "${CAPABILITY_PATTERN}" "ENTRY(\\1)\n" CAPABILITY_ENTRY "${CAPABILITY_DEFINITION}") # Write the entry to our header. file(APPEND "${CAPABILITY_HEADER_FILE}" "${CAPABILITY_ENTRY}") endforeach() # Cleanup. unset(CAPABILITY_ENTRY) unset(CAPABILITY_DEFINITION) unset(CAPABILITY_DEFINITIONS) unset(CAPABILITY_PATTERN) endif() # Cleanup. unset(CAPABILITY_HEADER_FILE) unset(CAPABILITY_HEADER_PATH) �������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/constants.h������������������������0000664�0000000�0000000�00000000435�15162662266�0027101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/constants.h> #include <string> namespace mega { namespace fuse { namespace platform { constexpr auto AttributeTimeout = 120.0; constexpr auto EntryTimeout = 120.0; extern const std::string FilesystemName; } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/directory_context.h����������������0000664�0000000�0000000�00000002442�15162662266�0030635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/directory_context_forward.h> namespace mega { namespace fuse { namespace platform { class DirectoryContext: public Context { // Retrieve this directory's children. void populate() const; // The directory's (last known) children. mutable InodeRefVector mChildren; // The directory we're iterating. DirectoryInodeRef mDirectory; // Serializes access to instance members. mutable std::mutex mLock; // The parent of the directory we're iterating. DirectoryInodeRef mParent; // Have we retrieved all of this directory's children? mutable bool mPopulated; public: DirectoryContext(DirectoryInodeRef directory, fuse::Mount& mount); ~DirectoryContext(); // Check if this context represents a directory. DirectoryContext* directory() override; // Retrieve information about a specific directory entry. InodeInfo get(std::size_t index) const; // What inode does this context represent? InodeRef inode() const override; // How many entries does this directory contain? std::size_t size() const; }; // DirectoryContext } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/file_descriptor.h������������������0000664�0000000�0000000�00000002173�15162662266�0030243�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/file_descriptor_forward.h> #include <mega/types.h> #include <cstddef> #include <string> namespace mega { namespace fuse { namespace platform { class FileDescriptor { int mDescriptor; public: explicit FileDescriptor(int descriptor = -1, bool closeOnFork = true); FileDescriptor(FileDescriptor&& other); ~FileDescriptor(); operator bool() const; bool operator!() const; FileDescriptor& operator=(FileDescriptor&& rhs); void closeOnFork(bool closeOnFork); bool closeOnFork() const; void flags(int flags); int flags() const; int get() const; std::size_t read(void* buffer, std::size_t length); std::size_t read(void* buffer, std::size_t length, m_off_t offset); std::string readAll(); void redirect(const FileDescriptor& other); int release(); void reset(int descriptor = -1); void swap(FileDescriptor& other); std::size_t write(const void* buffer, std::size_t length); std::size_t write(const void* buffer, std::size_t length, m_off_t offset); }; // FileDescriptor } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/file_descriptor_forward.h����������0000664�0000000�0000000�00000000332�15162662266�0031762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <utility> namespace mega { namespace fuse { namespace platform { class FileDescriptor; using FileDescriptorPair = std::pair<FileDescriptor, FileDescriptor>; } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/inode_invalidator.h����������������0000664�0000000�0000000�00000004650�15162662266�0030562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor_forward.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/session_forward.h> #include <mega/types.h> #include <atomic> #include <condition_variable> #include <deque> #include <map> #include <mutex> #include <string> #include <thread> namespace mega { namespace fuse { namespace platform { class InodeInvalidator { // Describes an invalidation to be performed. struct Invalidation; // Associates an inode with an invalidation to be performed. using InvalidationMap = std::map<MountInodeID, Invalidation>; // Maintains the order in which invalidations should be performed. using InvalidationQueue = std::deque<InvalidationMap::iterator>; // Get the invalidation associated with an inode. auto invalidation(common::ActivityMonitor& activities, MountInodeID id) -> Invalidation&; // Processes invalidation requests. void loop(); // Signalled when an inode needs to be invalidated. std::condition_variable mCV; // What kind of invalidation does an inode require? InvalidationMap mInvalidationByID; // When should an inode be invalidated? InvalidationQueue mInvalidationByOrder; // Serializes access to instance members. std::mutex mLock; // What session are we invalidating inodes on? Session& mSession; // Signals the worker that it's time to terminate. std::atomic<bool> mTerminate; // The thread on which inodes will be invalidated. std::thread mWorker; public: explicit InodeInvalidator(Session& session); ~InodeInvalidator(); // Invalidate the attributes of a specific inode. void invalidateAttributes(common::ActivityMonitor& activities, MountInodeID id); // Invalidate the data of a specific inode. void invalidateData(common::ActivityMonitor& activities, MountInodeID id, m_off_t offset, m_off_t size); // Invalidate a directory entry in a specific inode. void invalidateEntry(common::ActivityMonitor& activities, MountInodeID child, const std::string& name, MountInodeID parent); void invalidateEntry(common::ActivityMonitor& activities, MountInodeID id, const std::string& name); }; // InodeInvalidator } // platform } // fuse } // mega ����������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/mount.h����������������������������0000664�0000000�0000000�00000013110�15162662266�0026221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/normalized_path.h> #include <mega/common/task_executor.h> #include <mega/common/task_executor_flags_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/logger.h> #include <mega/fuse/common/mount.h> #include <mega/fuse/common/mount_inode_id_forward.h> #include <mega/fuse/common/tags.h> #include <mega/fuse/platform/inode_invalidator.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/fuse/platform/request_forward.h> #include <mega/fuse/platform/session.h> #include <functional> #include <memory> #include <vector> namespace mega { namespace fuse { namespace platform { class Mount final: public fuse::Mount { friend class Session; friend class SessionBase; void access(Request request, MountInodeID inode, int mask); void destroy(); void doUnlink(Request request, MountInodeID parent, std::function<Error(InodeRef)> predicate, const std::string& name); template<typename Callback, typename... Arguments> static constexpr auto IsMountCallbackV = std::is_invocable_r_v<void, Callback, Mount*, Arguments...>; template<typename... Arguments, typename Callback> auto execute(Callback callback, bool spawnWorker, Arguments&&... arguments) -> std::enable_if_t<IsMountCallbackV<Callback, Arguments...>> { std::function<void()> callback_ = std::bind(callback, this, std::forward<Arguments>(arguments)...); auto wrapper = [](common::Activity, auto& callback, const common::Task&) { callback(); }; // wrapper std::function<void(const common::Task&)> wrapper_ = std::bind(std::move(wrapper), mActivities.begin(), std::move(callback_), std::placeholders::_1); mExecutor.execute(std::move(wrapper_), spawnWorker); } void lookup(Request request, MountInodeID parent, const std::string& name); void flush(Request request, MountInodeID inode, fuse_file_info& info); void forget(Request request, MountInodeID inode, std::size_t num); void forget_multi(Request request, const std::vector<fuse_forget_data>& forgets); void fsync(Request request, MountInodeID inode, bool onlyData, fuse_file_info& info); void getattr(Request request, MountInodeID inode); void mkdir(Request request, MountInodeID parent, const std::string& name, mode_t mode); void mknod(Request request, MountInodeID parent, const std::string& name, mode_t mode); void open(Request request, MountInodeID inode, fuse_file_info& info); void opendir(Request request, MountInodeID inode, fuse_file_info& info); void read(Request request, MountInodeID inode, std::size_t size, off_t offset, fuse_file_info& info); void readdir(Request request, MountInodeID inode, std::size_t size, off_t offset, fuse_file_info& info); void release(Request request, MountInodeID inode, fuse_file_info& info); void releasedir(Request request, MountInodeID inode, fuse_file_info& info); void rename(Request request, MountInodeID sourceParent, const std::string& sourceName, MountInodeID targetParent, const std::string& targetName, unsigned int flags); void rmdir(Request request, MountInodeID parent, const std::string& name); void setattr(Request request, MountInodeID inode, struct stat& attributes, int changes); void statfs(Request request, MountInodeID inode); void unlink(Request request, MountInodeID parent, const std::string& name); void write(Request request, MountInodeID inode, const std::string& data, off_t offset, fuse_file_info& info); bool isSelfForbidden(const Request& request) const; // Tracks whether any requests are in progress. common::ActivityMonitor mActivities; // Responsible for performing requests. common::TaskExecutor mExecutor; // Where is the mount mounted? common::NormalizedPath mPath; // How this mount communicates with libfuse. Session mSession; // Responsible for invalidating inodes. InodeInvalidator mInvalidator; public: Mount(const MountInfo& info, MountDB& mountDB); ~Mount(); // Update this mount's executor flags. void executorFlags(const common::TaskExecutorFlags& flags) override; // Invalidate an inode's attributes. void invalidateAttributes(InodeID id) override; // Invalidate an inode's data. void invalidateData(InodeID id, m_off_t offset, m_off_t size) override; void invalidateData(InodeID id) override; // Invalidate a directory entry. void invalidateEntry(const std::string& name, InodeID child, InodeID parent) override; void invalidateEntry(const std::string& name, InodeID parent) override; // Translate a mount-speicifc inode ID to a system-wide inode ID. InodeID map(MountInodeID id) const override; // Translate a system-wide inode ID to a mount-specific inode ID. MountInodeID map(InodeID id) const override; // What local path is this mount mapping from? common::NormalizedPath path() const override; }; // Mount } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/mount_db.h�������������������������0000664�0000000�0000000�00000002124�15162662266�0026671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/mount_db.h> #include <mega/fuse/platform/mount_db_forward.h> #include <mega/fuse/platform/service_context_forward.h> #include <mega/fuse/platform/session_forward.h> #include <mega/fuse/platform/signal.h> #include <future> #include <mutex> #include <set> #include <thread> namespace mega { namespace fuse { namespace platform { class MountDB final: public fuse::MountDB { MountResult check(const common::Client& client, const MountInfo& info) const override; MountResult checkName(const std::string& name) const override; void dispatch(); void doDeinitialize() override; void loop(); std::mutex mLock; Signal mPendingAdd; FromSessionRawPtrMap<std::promise<void>> mPendingAdds; Signal mPendingRemove; FromSessionRawPtrMap<std::promise<void>> mPendingRemoves; SessionRawPtrSet mSessions; Signal mTerminate; std::thread mThread; public: MountDB(ServiceContext& context); void sessionAdded(Session& session); void sessionRemoved(Session& session); }; // MountDB } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/path_adapter.h���������������������0000664�0000000�0000000�00000001156�15162662266�0027522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/path_adapter.h> #include <mega/fuse/platform/path_adapter_forward.h> #include <string> namespace mega { namespace fuse { namespace platform { namespace detail { struct PathAdapterTraits { using SizeType = std::string::size_type; using StringType = std::string; using ValueType = std::string::value_type; static ValueType separator() { return '/'; } static std::string toUTF8(const ValueType* data, SizeType length) { return std::string(data, length); } }; // PathAdapterTraits } // detail } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/process.h��������������������������0000664�0000000�0000000�00000004470�15162662266�0026546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/process_forward.h> #include <functional> #include <string> #include <vector> namespace mega { namespace fuse { namespace platform { // Convenience. using ProcessCallback = std::function<void(FileDescriptor input, FileDescriptor output)>; class Process { // A descriptor representing this process's standard input. FileDescriptor mInput; // A descriptor representing this process's standard output. FileDescriptor mOutput; // The process's ID. long mID; public: Process(); // Instantiate a new process that will execute callback. explicit Process(ProcessCallback callback); // Take ownership of an existing process. Process(Process&& other); // Destroy process, aborting child if necessary. ~Process(); // True if this instance represents a process. operator bool() const; // True if this instance doesn't represent a process. bool operator!() const; // Move one process to another. Process& operator=(Process&& rhs); // Retrieve a descriptor you can use to read data from the child. FileDescriptor& input(); // Retrive a descriptor you can use to send data to the child. FileDescriptor& output(); // Read some data from the process. std::size_t read(void* buffer, std::size_t length); // Swap this process with another. void swap(Process& other); // Wait for the child to terminate. int wait(); // Write some data to the process. std::size_t write(const void* buffer, std::size_t length); }; // Process // Run the specified command as a new process. Process run(const std::string& command, const std::vector<std::string>& arguments); // Swap lhs with rhs. void swap(Process& lhs, Process& rhs); // Executes callback in an environment where: // - stderr and stdout can be read on the parent's input. // - The parent's output can be read on the child's stdin. ProcessCallback withRedirects(std::function<void()> callback); // Convenience. template<typename Callback> ProcessCallback withRedirects(Callback&& callback) { // Adapt callback to needed interface. std::function<void()> wrapper(std::forward<Callback>(callback)); // Wrap callback. return withRedirects(std::move(wrapper)); } } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/process_forward.h������������������0000664�0000000�0000000�00000000170�15162662266�0030263�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class Process; } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/request.h��������������������������0000664�0000000�0000000�00000002154�15162662266�0026555�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/request_forward.h> #include <cstddef> #include <string> namespace mega { namespace fuse { namespace platform { class Request { template<typename T> void reply(T&& responder); const fuse_req_t mRequest; public: explicit Request(fuse_req_t request); bool addDirEntry(const struct stat& attributes, std::string& buffer, const std::string& name, const std::size_t offset, const std::size_t size); gid_t group() const; uid_t owner() const; pid_t process() const; void replyAttributes(const struct statvfs& attributes); void replyAttributes(const struct stat& attributes, double timeout); void replyBuffer(const std::string& buffer); void replyEntry(const struct fuse_entry_param& entry); void replyError(int error); void replyNone(); void replyOk(); void replyOpen(const fuse_file_info& info); void replyWritten(std::size_t numBytes); }; // Request } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/request_forward.h������������������0000664�0000000�0000000�00000000170�15162662266�0030275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class Request; } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/session_base.h���������������������0000664�0000000�0000000�00000010543�15162662266�0027543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/mount_inode_id_forward.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount_forward.h> #include <mutex> #include <string> #include <vector> namespace mega { namespace fuse { namespace platform { class SessionBase { static fuse_lowlevel_ops mOperations; static std::once_flag mOperationsInitialized; protected: class Arguments { fuse_args mArguments; std::vector<char*> mPointers; std::vector<std::string> mStrings; public: Arguments(const std::string& name); fuse_args* get(); }; // Arguments struct SessionDeleter { void operator()(fuse_session* session); }; // SessionDeleter using SessionPtr = std::unique_ptr<fuse_session, SessionDeleter>; SessionBase(Mount& mount); ~SessionBase(); static void access(fuse_req_t request, fuse_ino_t inode, int mask); static void lookup(fuse_req_t request, fuse_ino_t parent, const char* name); static void flush(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); static void forget(fuse_req_t request, fuse_ino_t inode, std::uint64_t num); static void forget_multi(fuse_req_t request, std::size_t count, fuse_forget_data* forgets); static void fsync(fuse_req_t request, fuse_ino_t inode, int onlyData, fuse_file_info* info); static void getattr(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); static void init(void* context, fuse_conn_info* connection); static void mkdir(fuse_req_t request, fuse_ino_t parent, const char* name, mode_t mode); static void mknod(fuse_req_t request, fuse_ino_t parent, const char* name, mode_t mode, dev_t device); static Mount& mount(fuse_req_t request); static Mount& mount(void* context); static void open(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); static void opendir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); const fuse_lowlevel_ops& operations(); virtual void populateCapabilities(fuse_conn_info* connection); virtual void populateOperations(fuse_lowlevel_ops& operations); static void read(fuse_req_t request, fuse_ino_t inode, size_t size, off_t offset, fuse_file_info* info); static void readdir(fuse_req_t request, fuse_ino_t inode, std::size_t size, off_t offset, fuse_file_info* info); static void release(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); static void releasedir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info); static void rmdir(fuse_req_t request, fuse_ino_t parent, const char* name); static SessionBase& session(fuse_req_t request); static SessionBase& session(void* context); static void setattr(fuse_req_t request, fuse_ino_t inode, struct stat* attributes, int changes, fuse_file_info* info); static void statfs(fuse_req_t request, fuse_ino_t inode); static void unlink(fuse_req_t request, fuse_ino_t parent, const char* name); static void write(fuse_req_t request, fuse_ino_t inode, const char* data, size_t size, off_t offset, fuse_file_info* info); Mount& mMount; SessionPtr mSession; public: // What descriptor is the session using to communicate with FUSE? virtual int descriptor() const = 0; // Dispatch a request received from FUSE. virtual void dispatch() = 0; // Destroy the mount associated with this session. void destroy(); // Has this session exited? bool exited() const; // Invalidate an inode's attributes. void invalidateAttributes(MountInodeID id); // Invalidate an inode's data. virtual void invalidateData(MountInodeID id, off_t offset, off_t size) = 0; void invalidateData(MountInodeID id); // Invalidate a specific directory entry. virtual void invalidateEntry(const std::string& name, MountInodeID child, MountInodeID parent) = 0; virtual void invalidateEntry(const std::string& name, MountInodeID parent) = 0; }; // SessionBase } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/session_forward.h������������������0000664�0000000�0000000�00000000417�15162662266�0030274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <map> #include <set> namespace mega { namespace fuse { namespace platform { class Session; template<typename T> using FromSessionRawPtrMap = std::map<Session*, T>; using SessionRawPtrSet = std::set<Session*>; } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/signal.h���������������������������0000664�0000000�0000000�00000001077�15162662266�0026345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/file_descriptor.h> #include <string> namespace mega { namespace fuse { namespace platform { class Signal { std::string mName; FileDescriptor mReader; FileDescriptor mWriter; public: Signal(const std::string& name); Signal(Signal&& other) = default; ~Signal() = default; Signal& operator=(Signal&& rhs) = default; void clear(); int descriptor() const; const std::string& name() const; void raise(); void swap(Signal& other); }; // Signal } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mega/fuse/platform/utility.h��������������������������0000664�0000000�0000000�00000001770�15162662266�0026573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount_inode_id_forward.h> #include <mega/fuse/common/mount_result_forward.h> #include <mega/fuse/platform/file_descriptor_forward.h> #include <mega/fuse/platform/library.h> #include <mega/types.h> namespace mega { namespace fuse { namespace platform { // Clarity. using FilesystemPredicate = std::function<bool(const std::string& path, const std::string& type)>; using PathVector = std::vector<std::string>; bool abort(const std::string& path); PathVector filesystems(FilesystemPredicate predicate = nullptr); void nonblocking(int descriptor, bool enabled); FileDescriptorPair pipe(bool closeReaderOnFork, bool closeWriterOnFork); void translate(struct stat& attributes, MountInodeID id, const InodeInfo& info); void translate(fuse_entry_param& entry, MountInodeID id, const InodeInfo& info); int translate(Error result); MountResult unmount(const std::string& path, bool abort); } // platform } // fuse } // mega ��������sdk-10.11.0/src/fuse/supported/platform/posix/mount.cpp���������������������������������������������0000664�0000000�0000000�00000063325�15162662266�0023072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_io_context.h> #include <mega/fuse/common/file_move_flag.h> #include <mega/fuse/common/file_open_flag.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/directory_context.h> #include <mega/fuse/platform/file_context.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/request.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/utility.h> #include <cassert> #include <chrono> namespace mega { namespace fuse { namespace platform { // For compatibility with libfuse2. #ifndef HAS_RENAME_FLAGS #define RENAME_EXCHANGE 0 #define RENAME_NOREPLACE 0 #endif // !HAS_RENAME_FLAGS using namespace common; void Mount::access(Request request, MountInodeID inode, int mask) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Mask is invalid. if (mask != F_OK && mask > (R_OK | W_OK | X_OK)) return request.replyError(EINVAL); // Try and locate the specified inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Existence check has been satisfied. if (mask == F_OK) return request.replyOk(); // What are we allowed to do with this inode? auto permissions = ref->permissions(); // Only directories are executable. if ((mask & X_OK) && ref->file()) return request.replyError(EACCES); // Inodes are always readable. if (!(mask & W_OK)) return request.replyOk(); // Inode's writable. if (permissions == FULL && writable()) return request.replyOk(); // Inode or mount is read-only. request.replyError(EROFS); } void Mount::destroy() { // Destroy the mount. auto destroy = [&](Activity& activity, const Task&) { // Remove the mount from the database. auto ptr = ([&](Activity) { return mMountDB.remove(*this); })(std::move(activity)); // Destroy the mount. ptr.reset(); }; // destroy // Schedule the mount for destruction. mMountDB.mContext.mExecutor.execute( std::bind(std::move(destroy), mActivities.begin(), std::placeholders::_1), true); } void Mount::doUnlink(Request request, MountInodeID parent, std::function<Error(InodeRef)> predicate, const std::string& name) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the parent. auto ref = get(parent); // Parent doesn't exist. if (!ref) return request.replyError(ENOENT); auto directoryRef = ref->directory(); // Parent's not a directory. if (!directoryRef) return request.replyError(ENOTDIR); // Mount's read-only. if (!writable()) return request.replyError(EROFS); // Try and unlink the specified child. auto result = directoryRef->unlink(name, std::move(predicate)); // Reply to FUSE. request.replyError(translate(result)); } void Mount::lookup(Request request, MountInodeID parent, const std::string& name) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Look up the parent. auto ref = get(parent); // Parent doesn't exist. if (!ref) return request.replyError(ENOENT); auto directoryRef = ref->directory(); // Parent isn't a directory. if (!directoryRef) return request.replyError(ENOTDIR); // Specified name is way too long. if (name.size() > MaxNameLength) return request.replyError(ENAMETOOLONG); auto childRef = directoryRef->get(name); // Child doesn't exist. if (!childRef) return request.replyError(ENOENT); auto info = childRef->info(); // Mount's not writable. if (!writable()) info.mPermissions = RDONLY; pin(childRef, info); auto entry = fuse_entry_param(); std::memset(&entry, 0, sizeof(entry)); entry.attr_timeout = AttributeTimeout; entry.entry_timeout = EntryTimeout; translate(entry, map(info.mID), info); request.replyEntry(entry); } void Mount::flush(Request request, MountInodeID, fuse_file_info&) { request.replyOk(); } void Mount::forget(Request request, MountInodeID inode, std::size_t num) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Locate the specified inode. auto ref = get(inode, true); // Pinned inodes are *always* in memory. assert(ref); // Unpin the inode. unpin(std::move(ref), num); request.replyNone(); } void Mount::forget_multi(Request request, const std::vector<fuse_forget_data>& forgets) { assert(!forgets.empty()); // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); for (auto& forget: forgets) { // Lookup the specified inode. auto ref = get(MountInodeID(forget.ino), true); // Pinned inodes are always in memory. assert(ref); // Unpin the inode. unpin(std::move(ref), forget.nlookup); } request.replyNone(); } void Mount::fsync(Request request, MountInodeID, bool, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the file's context. auto* context = reinterpret_cast<FileContext*>(info.fh); // Sanity. assert(context); // Try and flush any modifications made to the file. auto result = context->flush(); // Let FUSE know if the file was flushed. request.replyError(translate(result)); } void Mount::getattr(Request request, MountInodeID inode) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Try and get our hands on the inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Retrieve a description of this inode. auto info = ref->info(); // Mount's not writable. if (!writable()) info.mPermissions = RDONLY; struct stat attributes; translate(attributes, inode, info); request.replyAttributes(attributes, AttributeTimeout); } void Mount::mkdir(Request request, MountInodeID parent, const std::string& name, mode_t mode) { mknod(request, parent, std::move(name), mode | S_IFDIR); } void Mount::mknod(Request request, MountInodeID parent, const std::string& name, mode_t mode) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Try and locate the parent. auto ref = get(parent); // Parent doesn't exist. if (!ref) return request.replyError(ENOENT); auto directoryRef = ref->directory(); // Parent isn't a directory. if (!directoryRef) return request.replyError(ENOTDIR); // Mount isn't writable. if (!writable()) return request.replyError(EROFS); // Only directories and regular files are supported. if (!S_ISDIR(mode) && !S_ISREG(mode)) return request.replyError(EPERM); // Try and create the new inode. auto result = S_ISDIR(mode) ? directoryRef->makeDirectory(*this, name) : directoryRef->makeFile(*this, name); // Couldn't create the inode. if (!result) return request.replyError(translate(result.error())); // Extract description of new inode. auto info = std::move(std::get<1>(*result)); // Translate description into something meaningful to FUSE. auto entry = fuse_entry_param(); translate(entry, MountInodeID(info.mID), info); // Pin inode in memory. pin(std::move(std::get<0>(*result)), info); // Respond to FUSE. request.replyEntry(entry); } void Mount::open(Request request, MountInodeID inode, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Check for invalid flags. if (info.direct_io) return request.replyError(EINVAL); // Try and get a reference to the specified inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Does the inode represent a file? auto fileRef = ref->file(); // Inode represents a directory. if (!fileRef) return request.replyError(EISDIR); // Compute flags. FileOpenFlags flags = 0; // User wants to open the file for writing. if ((info.flags & (O_RDWR | O_WRONLY))) { // Mount is read only. if (!writable()) return request.replyError(EROFS); // File's read only. if (fileRef->permissions() != FULL) return request.replyError(EROFS); flags |= FOF_WRITABLE; // User wants to append data to the file. if ((info.flags & O_APPEND)) flags |= FOF_APPEND; // User wants to truncate existing content. if ((info.flags & O_TRUNC)) flags |= FOF_TRUNCATE; } // Try and open the file. auto result = fileRef->open(*this, flags); // Couldn't open the file. if (!result) return request.replyError(translate(result.error())); // Convenience. auto context = std::move(*result); // Populate FUSE's file context. info.direct_io = false; info.fh = reinterpret_cast<std::uint64_t>(context.get()); info.keep_cache = !(flags & FOF_TRUNCATE); info.nonseekable = false; // Let FUSE know the file's open. request.replyOpen(info); // FUSE now owns the file context. static_cast<void>(context.release()); } void Mount::opendir(Request request, MountInodeID inode, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Try and get a reference to the specified inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Does the inode represent a directory? auto directoryRef = ref->directory(); // Inode isn't a directory. if (!directoryRef) return request.replyError(ENOTDIR); // Instantiate directory iterator context. auto context = std::make_unique<DirectoryContext>(std::move(directoryRef), *this); // Pass context to FUSE. info.fh = reinterpret_cast<std::uint64_t>(context.get()); request.replyOpen(info); // Context is in FUSE's hands now. static_cast<void>(context.release()); } void Mount::read(Request request, MountInodeID, std::size_t size, off_t offset, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the file's context. auto* context = reinterpret_cast<FileContext*>(info.fh); // Sanity. assert(context); // Try and read the file. auto result = context->read(offset, static_cast<unsigned int>(size)); // Couldn't read the file. if (!result) return request.replyError(translate(result.error())); // Pass read data to FUSE. request.replyBuffer(std::move(*result)); } void Mount::readdir(Request request, MountInodeID, std::size_t size, off_t offset, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Retrieve directory context. auto* context = reinterpret_cast<DirectoryContext*>(info.fh); // Sanity. assert(context); assert(offset >= 0); // Where we'll be storing directory entries. std::string buffer; // Type safety. auto m = static_cast<std::size_t>(offset); auto n = context->size(); // Collect directory entries. // // NOTE: The first two directory entries are always symlinks to the // directory itself (.) and to its immediate parent (..). while (m < n) { // Get information about the current child. auto info = context->get(m); // Child no longer exists. if (!info.mID) { // Either we or our parent no longer exist. if (m++ < 2) return request.replyBuffer(std::string()); // Process the next child. continue; } struct stat attributes; // Translate info into something meaningful. translate(attributes, map(info.mID), info); // Try and add the entry to our buffer. if (!request.addDirEntry(attributes, buffer, info.mName, ++m, size - buffer.size())) break; } // Report directory entries to FUSE. request.replyBuffer(std::move(buffer)); } void Mount::release(Request request, MountInodeID, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the context. auto* context = reinterpret_cast<FileContext*>(info.fh); // Make sure the context is properly released. delete context; // Let FUSE know that the context's been released. request.replyOk(); } void Mount::releasedir(Request request, MountInodeID, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Retrieve directory context. auto* context = reinterpret_cast<DirectoryContext*>(info.fh); // Sanity. assert(context); // Release context. delete context; // Let FUSE know we're done. request.replyOk(); } void Mount::rename(Request request, MountInodeID sourceParent, const std::string& sourceName, MountInodeID targetParent, const std::string& targetName, unsigned int flags) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the parents. auto sourceRef = get(sourceParent); auto targetRef = get(targetParent); // Either parent doesn't exist. if (!sourceRef || !targetRef) return request.replyError(ENOENT); auto sourceDirectoryRef = sourceRef->directory(); auto targetDirectoryRef = targetRef->directory(); // Either parent isn't a directory. if (!sourceDirectoryRef || !targetDirectoryRef) return request.replyError(ENOTDIR); // Mount isn't writable. if (!writable()) return request.replyError(EROFS); FileMoveFlags moveFlags = 0; // Caller doesn't want to replace any existing file. if ((flags & RENAME_NOREPLACE)) moveFlags |= FILE_MOVE_NO_REPLACE; // Caller wants to atomically exchange two files. if ((flags & RENAME_EXCHANGE)) moveFlags |= FILE_MOVE_EXCHANGE; // Make sure only a single flag has been set. if (!valid(moveFlags)) return request.replyError(EINVAL); // Perform the move. auto result = sourceDirectoryRef->move(sourceName, targetName, std::move(targetDirectoryRef), moveFlags); // Reply to FUSE. request.replyError(translate(result)); } void Mount::rmdir(Request request, MountInodeID parent, const std::string& name) { auto predicate = [](InodeRef ref) { return ref->file() ? API_FUSE_ENOTDIR : API_OK; }; // predicate doUnlink(request, parent, std::move(predicate), name); } void Mount::setattr(Request request, MountInodeID inode, struct stat& attributes, int changes) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Mount's read-only. if (!writable()) return request.replyError(EROFS); // Inode's read-only. if (ref->permissions() != FULL) return request.replyError(EROFS); // Clarity; constexpr auto EOK = 0; auto ownership = [&]() { auto group = changes & FUSE_SET_ATTR_GID; auto owner = changes & FUSE_SET_ATTR_UID; // User's not changing ownership. if (!(group | owner)) return EOK; // Can't transfer ownership to another group. if (group && attributes.st_gid != getgid()) return EPERM; // Can't transfer ownership to another user. if (owner && attributes.st_uid != geteuid()) return EPERM; // It's okay to transfer ownership to yourself. return EOK; }; // ownership auto size = [&]() { // User's not changing the file's size. if (!(changes & FUSE_SET_ATTR_SIZE)) return EOK; auto fileRef = ref->file(); // Can't change the size of a directory. if (!fileRef) return EISDIR; // Try and truncate the file. auto result = fileRef->truncate(*this, attributes.st_size, false); // Translate result. return translate(result); }; // size auto time = [&]() { // Convenience. using std::chrono::system_clock; // Not changing modification time. // // NOTE: If you're wondering why we don't handle ATIME, the reasons // are pretty straight forward. First, MEGA itself doesn't have any // concept of a file access time so if we did want to implement it, // we'd have to record extra data in FUSE's database. // // Second, we don't want to store any information in FUSE's database // unless we really have to. That is, if a user's doing nothing but // listing files, we shouldn't have to add anything to our database // since we aren't recording any local changes. // // That explains why we don't implement ATIME but it doesn't explain // why we don't just return an error to userspace, to let the system // know that we don't support it. The reason we pretend as if the // ATIME succeeded is that if we don't, many tools will issue // warnings to the user and QA felt this was scary, even though the // warnings themselves are benign. // // So for now, we just pretend that we've set the ATIME. if (!(changes & FUSE_SET_ATTR_MTIME)) return EOK; auto fileRef = ref->file(); // Directories don't have a modification time. if (!fileRef) return EOK; // Assume the user has a specific time in mind. m_time_t modified = attributes.st_mtime; // User wants to set the modification time to now. if ((changes & FUSE_SET_ATTR_MTIME_NOW)) modified = system_clock::to_time_t(system_clock::now()); // Try and set the file's modification time. auto result = fileRef->touch(*this, modified); // Translate result. return translate(result); }; // time // Update ownership if necessary. if (auto result = ownership()) return request.replyError(result); // Update size if necessary. if (auto result = size()) return request.replyError(result); // Update time if necessary. if (auto result = time()) return request.replyError(result); // Retrieve a current description of this node. translate(attributes, inode, ref->info()); // Forward description to userspace. request.replyAttributes(attributes, AttributeTimeout); } void Mount::statfs(Request request, MountInodeID inode) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the inode. auto ref = get(inode); // Inode doesn't exist. if (!ref) return request.replyError(ENOENT); // Try and retrieve our storage statistics. auto info = mMountDB.client().storageInfo(); // Couldn't retrieve our storage statistics. if (!info) return request.replyError(translate(info.error())); struct statvfs attributes; // Make sure unset members have a well defined state. std::memset(&attributes, 0, sizeof(attributes)); // Convenience. auto available = static_cast<fsblkcnt_t>(info->mAvailable); auto capacity = static_cast<fsblkcnt_t>(info->mCapacity); // Populate filesystem statistics. // // NOTE: We only really set the attributes that make sense to us here. // // For instance, we don't set f_files or f_free because there isn't any // kind of limit on how many inodes the user can create unlike a real // filesystem such as ext2 which must explicitly reserve space for such // structures. // // This behavior is permitted by the statvfs(...) documentation. attributes.f_bavail = available / BlockSize; attributes.f_bfree = attributes.f_bavail; attributes.f_blocks = capacity / BlockSize; attributes.f_bsize = BlockSize; attributes.f_frsize = BlockSize; attributes.f_fsid = FilesystemID; attributes.f_namemax = MaxNameLength; // Forward statistics to FUSE. request.replyAttributes(attributes); } void Mount::unlink(Request request, MountInodeID parent, const std::string& name) { auto predicate = [](InodeRef ref) { if (ref->directory()) return LINUX_OR_POSIX(API_FUSE_EISDIR, API_FUSE_EPERM); return API_OK; }; // predicate doUnlink(request, parent, std::move(predicate), name); } void Mount::write(Request request, MountInodeID, const string& data, off_t offset, fuse_file_info& info) { // Reject if the originating process is self if (isSelfForbidden(request)) return request.replyError(EPERM); // Get our hands on the context. auto* context = reinterpret_cast<FileContext*>(info.fh); // Sanity. assert(context); assert(offset >= 0); // Try and write the file. auto result = context->write(data.c_str(), static_cast<m_off_t>(data.length()), offset, false); // Couldn't write the file. if (!result) return request.replyError(translate(result.error())); // Let FUSE know whether the data was written. request.replyWritten(*result); } // Check if the request's originating process is this process and forbidden. // Don't allow SDK to access the mount if the request is from itself as it will have deadlock issues // due to single-threaded execution loop of the SDK. // @return true is self and is forbidden, otherwise false // bool Mount::isSelfForbidden(const Request& request) const { if (allowSelfAccess()) return false; const auto originatingPid = request.process(); return originatingPid != 0 && originatingPid == getpid(); } Mount::Mount(const MountInfo& info, MountDB& mountDB): fuse::Mount(info, mountDB), mActivities(), mExecutor(mountDB.executorFlags(), logger()), mPath(info.mPath), mSession(*this), mInvalidator(mSession) { // Let the database know a new session has been added. mMountDB.sessionAdded(mSession); FUSEDebugF("Mount constructed: %s", path().toPath(false).c_str()); } Mount::~Mount() { // Let the database know that a session is being removed. mMountDB.sessionRemoved(mSession); // Wait for all outstanding requests to complete. mActivities.waitUntilIdle(); // It's safe for us to be destroyed. FUSEDebugF("Mount destroyed: %s", path().toPath(false).c_str()); } void Mount::executorFlags(const TaskExecutorFlags& flags) { // Updates this mount's executor flags. auto update = [flags, this](Activity&, const Task&) { mExecutor.flags(flags); }; // update mExecutor.execute(std::bind(std::move(update), mActivities.begin(), std::placeholders::_1), true); } void Mount::invalidateAttributes(InodeID id) { mInvalidator.invalidateAttributes(mActivities, map(id)); } void Mount::invalidateData(InodeID id, m_off_t offset, m_off_t size) { mInvalidator.invalidateData(mActivities, map(id), offset, size); } void Mount::invalidateData(InodeID id) { invalidateData(id, 0, 0); } void Mount::invalidateEntry(const std::string& name, InodeID child, InodeID parent) { assert(child); assert(parent); mInvalidator.invalidateEntry(mActivities, map(child), name, map(parent)); } void Mount::invalidateEntry(const std::string& name, InodeID parent) { assert(parent); mInvalidator.invalidateEntry(mActivities, map(parent), name); } InodeID Mount::map(MountInodeID id) const { if (id.get() != FUSE_ROOT_ID) return InodeID(id); return InodeID(handle()); } MountInodeID Mount::map(InodeID id) const { if (id == handle()) return MountInodeID(FUSE_ROOT_ID); return MountInodeID(id); } NormalizedPath Mount::path() const { return mPath; } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/mount_db.cpp������������������������������������������0000664�0000000�0000000�00000022542�15162662266�0023533�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/utility.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/session.h> #include <cassert> #include <cerrno> #include <cstring> #include <functional> #include <poll.h> #include <stdexcept> #include <tuple> #include <utility> #include <vector> namespace mega { namespace fuse { namespace platform { using namespace common; // Makes dealing with fd_set a little more convenient. class DescriptorSet { // Compares a pollfd instance against a descriptor. static bool less(const struct pollfd& lhs, int rhs); // Which descriptors are we monitoring? std::vector<struct pollfd> mDescriptors; public: DescriptorSet(); // Add a descriptor to the set. template<typename T> void add(const T& descriptor); // Remove all descriptors from the set. void clear(); // Remove a descriptor from the set. template<typename T> void remove(const T& descriptor); // Check if a descriptor has any activity. template<typename T> bool set(const T& descriptor) const; // Wait for activity on some descriptor. void wait(); }; // DescriptorSet MountResult MountDB::check(const Client& client, const MountInfo& info) const { // Convenience. auto& name = info.name(); auto& path = info.mPath; // Check the mount's name. if (const auto ret = checkName(name); ret != MOUNT_SUCCESS) return ret; // User's specified a bogus path. if (path.empty()) { FUSEError1("Invalid local path specified"); return MOUNT_LOCAL_UNKNOWN; } // Try and retrieve the local path's type. auto fileAccess = client.fsAccess().newfileaccess(false); fileAccess->fopen(path, FSLogging::noLogging); // Path isn't accessible. if (fileAccess->type == TYPE_UNKNOWN) { FUSEErrorF("Local path doesn't exist: %s", path.toPath(false).c_str()); return MOUNT_LOCAL_UNKNOWN; } // Path denotes a file. if (fileAccess->type != FOLDERNODE) { FUSEErrorF("Local path is not a directory: %s", path.toPath(false).c_str()); return MOUNT_LOCAL_FILE; } // Path's okay. return MOUNT_SUCCESS; } MountResult MountDB::checkName(const std::string& name) const { if (name.empty()) { FUSEError1("No name specified"); return MOUNT_NO_NAME; } return MOUNT_SUCCESS; } void MountDB::dispatch() { DescriptorSet descriptors; // Wake up when a session has been added. descriptors.add(mPendingAdd); // Wake up when a session has been removed. descriptors.add(mPendingRemove); // Wake up when we need to terminate. descriptors.add(mTerminate); // Dispatch incoming FUSE requests. while (true) { // Wait for some event to wake us up. descriptors.wait(); // Acquire lock. std::unique_lock<std::mutex> lock(mLock); // Some sessions have been added. if (descriptors.set(mPendingAdd)) { mPendingAdd.clear(); // Add each new session to our map. for (auto& a: mPendingAdds) { // Add the session to our set. mSessions.emplace(a.first); // Monitor the session's descriptor for activity. descriptors.add(*a.first); // Let waiter know the session's been added. a.second.set_value(); } mPendingAdds.clear(); } // Some sessions have been removed. if (descriptors.set(mPendingRemove)) { mPendingRemove.clear(); // Remove each session from our map. for (auto& r: mPendingRemoves) { // We're no longer interested in this session's activity. descriptors.remove(*r.first); // Remove the session from our set. mSessions.erase(r.first); // Let waiter know the session's been removed. r.second.set_value(); } mPendingRemoves.clear(); } // We've been asked to terminate. if (descriptors.set(mTerminate)) { // Sanity. assert(mPendingAdds.empty()); assert(mPendingRemoves.empty()); return; } // Release lock so work can proceed while we dispatch requests. lock.unlock(); // Dispatch incoming requests. for (auto i = mSessions.begin(); i != mSessions.end();) { // Convenience. auto j = i++; auto session = *j; // Session's been closed. if (session->exited()) { // Remove the session from our set. mSessions.erase(j); // We're no longer interested in this session's activity. descriptors.remove(*session); // Destroy the mount associated with this session. session->destroy(); // Dispatch the next request. continue; } // Session hasn't received a request. if (!descriptors.set(*session)) continue; // Dispatch the request. session->dispatch(); } } } void MountDB::doDeinitialize() { // Let the dispatcher know it's time to terminate. mTerminate.raise(); // Wait for the dispatcher to terminate. mThread.join(); } void MountDB::loop() { FUSEDebug1("Mount Request Dispatcher started"); dispatch(); FUSEDebug1("Mount Request Dispatcher stopped"); } MountDB::MountDB(ServiceContext& context): fuse::MountDB(context), mLock(), mPendingAdd("PendingAdd"), mPendingAdds(), mPendingRemove("PendingRemove"), mPendingRemoves(), mSessions(), mTerminate("Terminate"), mThread(&MountDB::loop, this) { FUSEDebug1("Mount DB constructed"); } void MountDB::sessionAdded(Session& session) { std::unique_lock<std::mutex> lock(mLock); assert(!mPendingAdds.count(&session)); assert(!mPendingRemoves.count(&session)); assert(!mSessions.count(&session)); auto promise = std::promise<void>(); auto future = promise.get_future(); mPendingAdds.emplace(std::piecewise_construct, std::forward_as_tuple(&session), std::forward_as_tuple(std::move(promise))); mPendingAdd.raise(); lock.unlock(); future.get(); } void MountDB::sessionRemoved(Session& session) { std::unique_lock<std::mutex> lock(mLock); if (!mSessions.count(&session)) return; assert(!mPendingAdds.count(&session)); assert(!mPendingRemoves.count(&session)); auto promise = std::promise<void>(); auto future = promise.get_future(); mPendingRemoves.emplace(std::piecewise_construct, std::forward_as_tuple(&session), std::forward_as_tuple(std::move(promise))); mPendingRemove.raise(); lock.unlock(); future.get(); } bool DescriptorSet::less(const struct pollfd& lhs, int rhs) { return lhs.fd < rhs; } DescriptorSet::DescriptorSet(): mDescriptors() {} template<typename T> void DescriptorSet::add(const T& descriptor) { // Populate a new poll record. struct pollfd record = {descriptor.descriptor(), POLLIN, 0}; // record // Where should we add our new record? auto i = std::lower_bound(mDescriptors.begin(), mDescriptors.end(), record.fd, less); // The set should never contain any duplicates. assert(i == mDescriptors.end() || i->fd != record.fd); // Add the new record to our set. mDescriptors.insert(i, record); } template<typename T> void DescriptorSet::remove(const T& descriptor) { // Locate this descriptor's record. auto i = std::lower_bound(mDescriptors.begin(), mDescriptors.end(), descriptor.descriptor(), less); // The descriptor must be in the set. assert(i != mDescriptors.end() && i->fd == descriptor.descriptor()); // Remove the record from our set. mDescriptors.erase(i); } void DescriptorSet::clear() { mDescriptors.clear(); } template<typename T> bool DescriptorSet::set(const T& descriptor) const { // Locate this descriptor's record. auto i = std::lower_bound(mDescriptors.begin(), mDescriptors.end(), descriptor.descriptor(), less); // The descriptor must be in the set. assert(i != mDescriptors.end() && i->fd == descriptor.descriptor()); // Let the caller know if the descriptor's readable. return i->revents > 0; } void DescriptorSet::wait() { while (true) { // Wait for one of our descriptors to become readable. auto result = poll(mDescriptors.data(), static_cast<nfds_t>(mDescriptors.size()), -1); // No descriptors were readable. if (!result) continue; // Some descriptors were readable. if (result > 0) return; // Call was interrupted. if (errno == EAGAIN || errno == EINTR) continue; // Encounterd some unexpected error waiting for activity. throw FUSEErrorF("Unexpected error waiting for requests: %s", std::strerror(errno)); } } } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/process.cpp�������������������������������������������0000664�0000000�0000000�00000013062�15162662266�0023377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/platform/process.h> #include <mega/fuse/platform/utility.h> #include <sys/wait.h> #include <cassert> #include <unistd.h> namespace mega { namespace fuse { namespace platform { // Close descriptors in the range [begin, SC_OPEN_MAX). static void closeFrom(int begin); Process::Process(): mInput(), mOutput(), mID(-1) {} Process::Process(ProcessCallback callback): mInput(), mOutput(), mID(-1) { FileDescriptor fromChild; FileDescriptor fromParent; FileDescriptor toChild; FileDescriptor toParent; // So we can read data from the process. std::tie(fromChild, toParent) = pipe(true, false); // So we can write data to the process. std::tie(fromParent, toChild) = pipe(false, true); // Fork a new process. auto id = fork(); // Couldn't fork a new process. if (id < 0) throw FUSEErrorF("Unable to fork process: %s", std::strerror(errno)); // We're in the child process. if (!id) { // Execute user callback. callback(std::move(fromParent), std::move(toParent)); // We should never get here. exit(0); } // We're in the parent. mID = id; mInput = std::move(toChild); mOutput = std::move(fromChild); } Process::Process(Process&& other): mInput(std::move(other.mInput)), mOutput(std::move(other.mOutput)), mID(other.mID) { other.mID = -1; } Process::~Process() { // We have no child. if (mID < 0) return; // Abort the child. kill(static_cast<pid_t>(mID), SIGKILL); // Wait for the child to terminate. wait(); } Process::operator bool() const { return mID >= 0; } bool Process::operator!() const { return mID < 0; } Process& Process::operator=(Process&& rhs) { Process temp(std::move(rhs)); swap(temp); return *this; } FileDescriptor& Process::input() { return mInput; } FileDescriptor& Process::output() { return mOutput; } std::size_t Process::read(void* buffer, std::size_t length) { return mOutput.read(buffer, length); } void Process::swap(Process& other) { using std::swap; swap(mInput, other.mInput); swap(mOutput, other.mOutput); swap(mID, other.mID); } int Process::wait() { for (auto status = 0;;) { // Wait for the child to terminate. auto id = waitpid(static_cast<pid_t>(mID), &status, 0); // Child's terminated. if (id >= 0) { // We no longer have any child. mID = -1; // Clean up resources. Process().swap(*this); // Child wasn't aborted. if (WIFEXITED(status)) return WEXITSTATUS(status); // Child was aborted. return -1; } // System call was interrupted. if (errno == EINTR) continue; // Couldn't wait for child process. throw FUSEErrorF("Couldn't wait for child process: %s", std::strerror(errno)); } } std::size_t Process::write(const void* buffer, std::size_t length) { return mInput.write(buffer, length); } Process run(const std::string& command, const std::vector<std::string>& arguments) { // Executes command in the child process. static const auto execute = [](std::string& command, std::vector<std::string>& arguments) { // Instantiate argument vector. std::vector<char*> argv; // Reserve space (add 2 for the commmand path and terminator.) argv.reserve(arguments.size() + 2); // Add command path. argv.push_back(&command[0]); // Add arguments. for (auto& argument: arguments) argv.push_back(&argument[0]); // Add terminator. argv.push_back(nullptr); // Try and execute command. auto result = execvp(command.c_str(), argv.data()); // Sanity. assert(result < 0); // Silence compiler. static_cast<void>(result); // Couldn't execute command. exit(errno); }; // execute // Execute command in a standard environment. auto wrapper = withRedirects(std::bind(execute, command, arguments)); // Return proess to caller. return Process(std::move(wrapper)); } void swap(Process& lhs, Process& rhs) { lhs.swap(rhs); } ProcessCallback withRedirects(std::function<void()> callback) { // Set's up standard redirects. static const auto wrapper = [](std::function<void()>& callback, FileDescriptor fromParent, FileDescriptor toParent) { FileDescriptor stderr(STDERR_FILENO, false); FileDescriptor stdin(STDIN_FILENO, false); FileDescriptor stdout(STDOUT_FILENO, false); // Send stderr and stdout to our parent. toParent.redirect(stderr); toParent.redirect(stdout); toParent.reset(); // stdin reads from our parent. fromParent.redirect(stdin); fromParent.reset(); // Close unneeded descriptors. closeFrom(STDERR_FILENO + 1); // Execute user callback. callback(); }; // wrapper // Return wrapper to caller. return std::bind(wrapper, std::move(callback), std::placeholders::_1, std::placeholders::_2); } void closeFrom(int current) { // Sanity. assert(current >= 0); // What is the maximum number of open descriptors? auto max = sysconf(_SC_OPEN_MAX); // Close descriptors. for (; current < max; ++current) { // Close the descriptor. while (close(current) < 0 && errno == EINTR) ; } } } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/request.cpp�������������������������������������������0000664�0000000�0000000�00000006412�15162662266�0023412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/platform/request.h> namespace mega { namespace fuse { namespace platform { template<typename T> void Request::reply(T&& responder) { while (true) { auto result = responder(mRequest); if (result == -EINTR) continue; if (!result) { FUSEDebugF("Response sent for request: %p", mRequest); return; } throw FUSEErrorF("Unable to send response for request: %p", mRequest); } } Request::Request(fuse_req_t request): mRequest(request) {} bool Request::addDirEntry(const struct stat& attributes, std::string& buffer, const std::string& name, const std::size_t offset, const std::size_t size) { // How much have we written to the buffer? auto current = buffer.size(); // How much space does this entry need? auto required = fuse_add_direntry(mRequest, nullptr, 0, name.c_str(), nullptr, 0); // Don't have enough space for this entry. if (current + required > size) return false; // Expand the buffer. buffer.resize(current + required); // Add the entry to the buffer. fuse_add_direntry(mRequest, &buffer[current], required, name.c_str(), &attributes, static_cast<off_t>(offset)); // Let the caller know the entry's been added. return true; } gid_t Request::group() const { return fuse_req_ctx(mRequest)->gid; } uid_t Request::owner() const { return fuse_req_ctx(mRequest)->uid; } pid_t Request::process() const { return fuse_req_ctx(mRequest)->pid; } void Request::replyAttributes(const struct statvfs& attributes) { reply( [&](fuse_req_t request) { return fuse_reply_statfs(request, &attributes); }); } void Request::replyAttributes(const struct stat& attributes, const double timeout) { reply( [&](fuse_req_t request) { return fuse_reply_attr(request, &attributes, timeout); }); } void Request::replyBuffer(const std::string& buffer) { reply( [&](fuse_req_t request) { return fuse_reply_buf(request, buffer.data(), buffer.size()); }); } void Request::replyEntry(const struct fuse_entry_param& entry) { reply( [&](fuse_req_t request) { return fuse_reply_entry(request, &entry); }); } void Request::replyError(const int error) { reply( [=](fuse_req_t request) { return fuse_reply_err(request, error); }); } void Request::replyNone() { reply( [&](fuse_req_t request) { return fuse_reply_none(request), 0; }); } void Request::replyOk() { replyError(0); } void Request::replyOpen(const fuse_file_info& info) { reply( [&](fuse_req_t request) { return fuse_reply_open(request, &info); }); } void Request::replyWritten(const std::size_t numBytes) { reply( [&](fuse_req_t request) { return fuse_reply_write(request, numBytes); }); } } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/scripts/����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022702�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/scripts/cleanup.sh������������������������������������0000775�0000000�0000000�00000011121�15162662266�0024664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env bash # A pipeline only succeeds if all of its parts succeed. set -o pipefail # User didn't specify where we should search for mounts. ERROR_BAD_ARGUMENTS=1 # Couldn't unmount one or more filesystems. ERROR_COULDNT_UNMOUNT_FILESYSTEM=2 # Couldn't locate (or access) a directory we need. ERROR_DIRECTORY_NOT_FOUND=3 # Couldn't locate a program that we need. ERROR_PROGRAM_NOT_FOUND=4 # Everything executed successfully. ERROR_SUCCESS=0 # Where can we can find libfuse's control filesystem? FUSE_CONTROL_PREFIX=/sys/fs/fuse/connections # die(message, result) # # Print MESSAGE to the standard error output and terminate the # program, returning RESULT to the shell. die() { local message="$1" local result=$2 printf "%s: %s\n" ERROR "$message" >&2 exit $result } # ensure_directory(path) # # Checks that PATH exists and denotes a directory. If not, this function # terminates the program and returns the error ERROR_DIRECTORY_NOT_FOUND to # the shell. ensure_directory() { local path="$1" test -d "$path" \ || die "Couldn't access directory \"$path\"." \ ${ERROR_DIRECTORY_NOT_FOUND} } # ensure_program(program) # # Checks that PROGRAM is present in the shell's search path and if not, # terminates the program and returns the error ERROR_PROGRAM_NOT_FOUND to # the shell. ensure_program() { local program="$1" command -v "$program" &> /dev/null \ || die "Couldn't find required program \"$program.\"" \ ${ERROR_PROGRAM_NOT_FOUND} } # ensure_programs() # # Checks that all programs required by this script are present in the # shell's search path. If any program we need isn't present, this function # will terminate the program and return the error ERROR_PROGRAM_NOT_FOUND to # the shell. ensure_programs() { # So we can parse mount's output. ensure_program cut ensure_program grep # So we can determine which FUSE mounts are active on the system. ensure_program mount # So we can unmount a FUSE mount. ensure_program fusermount # So we can determine which FUSE device is associated with a given mount. ensure_program stat } # main(arguments) # # Main entry point. main() { # Make sure the programs we need are in the shell's search path. ensure_programs # Make sure libfuse's control filesystem is mounted. ensure_directory "$FUSE_CONTROL_PREFIX" # Clarity local path="$1" # The user hasn't specified where we should search for mounts. test $# -eq 1 -a -n "$path" \ || die "You didn't specify where we should search for mounts." \ ${ERROR_BAD_ARGUMENTS} # Make sure we can access the path the user's specified. ensure_directory "$path" # Try and umount all MEGA-FS filesystems under PATH. unmount_all "$path" } # mounts(path) # # Retrieve a list of all MEGA-FS mounts under PATH. mounts() { local path="$1" # Retrieve a list of all active mounts. # Filter that list to contain only MEGA-FS filesystems. # Filter that list to consider only those below PATH. # Return the paths of those filesystems. mount | grep megafs | grep -F "$path" | cut -d ' ' -f 3 } # unmount(path) # # Try and unmount the MEGA-FS filesystem mounted at PATH. unmount() { local path="$1" # Compute the filesystem's device number. local device_no=$(stat --cached=always --format=%d "$path") # Couldn't determine the filesystem's device number. test -z "$device_no" && return # Compute filesystem's control path. local control_path="$FUSE_CONTROL_PREFIX/$device_no/abort" # Try and unmount the filesystem. while test -f "$control_path"; do # We were able to unmount the filesystem. fusermount -u "$path" &> /dev/null && return # Filesystems busy: try and abort the mount. echo 1 > "$control_path" 2> /dev/null || true # Give the system a little time to process the abort. sleep 0.25 done # Make sure the filesystem's been unmounted. ! test -d "$path" } # unmount_all(path) # # Try and unmount all MEGA-FS filesystems under PATH. unmount_all() { local path="$1" local result=$ERROR_SUCCESS # Try and unmount all MEGA-FS filesystems under PATH. mounts "$path" | while read path; do if unmount "$path"; then printf "Successfully unmounted \"%s\".\n" "$path" else printf "Unable to unmount \"%s\"\." "$path" result=$ERROR_COULDNT_UNMOUNT_FILESYSTEM fi done # Let the caller know if all the filesystems have been unmounted. return $result } # Transfer control to our entry point. main "$@" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/service.cpp�������������������������������������������0000664�0000000�0000000�00000003021�15162662266�0023353�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/utility.h> namespace mega { namespace fuse { // Convenience. using platform::FilesystemName; using platform::FilesystemPredicate; // Wrap an abort predicate in a filesystem predicate. static FilesystemPredicate wrap(AbortPredicate predicate); MountResult Service::abort(AbortPredicate predicate) { // Convenience. using platform::filesystems; using platform::unmount; // What mounts should we abort? auto matches = filesystems(wrap(std::move(predicate))); auto result = MOUNT_SUCCESS; // (Try to) abort and unmount each match. for (const auto& match: matches) { // Abort and unmount match. auto result_ = unmount(match, true); // Latch first failure, if any. if (result == MOUNT_SUCCESS) result = result_; } // Let caller know if we were successful. return result; } FilesystemPredicate wrap(AbortPredicate predicate) { static const auto wrapper = [](AbortPredicate& next, const std::string& path, const std::string& type) { // Type looks like a megafs mount. if (type.find(FilesystemName) != type.npos) return next(path); // Type doesn't look like megafs. return false; }; // wrapper // Wrap user callback. return std::bind(wrapper, std::move(predicate), std::placeholders::_1, std::placeholders::_2); } } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/session_base.cpp��������������������������������������0000664�0000000�0000000�00000032414�15162662266�0024400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_executor.h> #include <mega/common/utility.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/request.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/session_base.h> #include <cassert> #include <cstring> #include <vector> namespace mega { namespace fuse { namespace platform { using namespace common; SessionBase::Arguments::Arguments([[maybe_unused]] const std::string& name): mArguments(), mPointers(), mStrings() { mStrings.emplace_back("mega-fuse"); // So it's easier to identify which FUSE mounts we created. mStrings.emplace_back(format("-ofsname=%s", FilesystemName.c_str())); mStrings.emplace_back(format("-osubtype=%s", FilesystemName.c_str())); POSIX_ONLY(mStrings.emplace_back(format("-ovolname=%s", name.c_str()))); // Translate strings into a form meaningful to libfuse. for (auto& string: mStrings) mPointers.emplace_back(&string[0]); mPointers.emplace_back(nullptr); // Populate libfuse argument block. mArguments.allocated = 0; mArguments.argc = static_cast<int>(mStrings.size()); mArguments.argv = &mPointers[0]; } fuse_args* SessionBase::Arguments::get() { return &mArguments; } fuse_lowlevel_ops SessionBase::mOperations{}; std::once_flag SessionBase::mOperationsInitialized; void SessionBase::access(fuse_req_t request, fuse_ino_t inode, int mask) { MountInodeID inode_(inode); FUSEDebugF("access: inode: %s, mask: %x, request: %p", toString(inode_).c_str(), mask, request); mount(request).execute(&Mount::access, true, Request(request), inode_, mask); } void SessionBase::lookup(fuse_req_t request, fuse_ino_t parent, const char* name) { MountInodeID parent_(parent); FUSEDebugF("lookup: parent: %s, name: %s, request: %p", toString(parent_).c_str(), name, request); mount(request).execute(&Mount::lookup, true, Request(request), parent_, std::string(name)); } void SessionBase::flush(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("flush: inode: %s, request: %p", toString(inode_).c_str(), request); mount(request).execute(&Mount::flush, true, Request(request), inode_, *info); } void SessionBase::forget(fuse_req_t request, fuse_ino_t inode, std::uint64_t num) { MountInodeID inode_(inode); FUSEDebugF("forget: inode: %s, num: %lu, request: %p", toString(inode_).c_str(), num, request); mount(request).execute(&Mount::forget, false, Request(request), inode_, num); } void SessionBase::forget_multi(fuse_req_t request, std::size_t count, fuse_forget_data* forgets) { FUSEDebugF("forget_multi: count: %zu, forgets: %p, request: %p", count, forgets, request); std::vector<fuse_forget_data> forgets_(forgets, forgets + count); mount(request).execute(&Mount::forget_multi, false, Request(request), std::move(forgets_)); } void SessionBase::fsync(fuse_req_t request, fuse_ino_t inode, int onlyData, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("fsync: inode: %s, onlyData: %d, request: %p", toString(inode_).c_str(), onlyData, request); mount(request).execute(&Mount::fsync, true, Request(request), inode_, onlyData, *info); } void SessionBase::getattr(fuse_req_t request, fuse_ino_t inode, fuse_file_info*) { MountInodeID inode_(inode); FUSEDebugF("getattr: inode: %s, request: %p", toString(inode_).c_str(), request); mount(request).execute(&Mount::getattr, true, Request(request), inode_); } void SessionBase::init(void* context, fuse_conn_info* connection) { #define ENTRY(name) {#name, name}, static const std::map<std::string, unsigned int> capabilities = { #include <mega/fuse/platform/capability_flags.i> }; // capabilities #undef ENTRY session(context).populateCapabilities(connection); for (auto& entry: capabilities) { auto capable = (connection->capable & entry.second) > 0; auto wanted = (connection->want & entry.second) > 0; FUSEDebugF("init: %u%u %s", capable, wanted, entry.first.c_str()); } mount(context).execute(&Mount::enabled, true); } void SessionBase::mkdir(fuse_req_t request, fuse_ino_t parent, const char* name, mode_t mode) { MountInodeID parent_(parent); FUSEDebugF("mkdir: mode: %jo, name: %s, parent: %s, request: %p", mode, name, toString(parent_).c_str(), request); mount(request).execute(&Mount::mkdir, true, Request(request), parent_, std::string(name), static_cast<std::uintmax_t>(mode)); } void SessionBase::mknod(fuse_req_t request, fuse_ino_t parent, const char* name, mode_t mode, dev_t) { MountInodeID parent_(parent); FUSEDebugF("mknod: mode: %jo, name: %s, parent: %s, request: %p", mode, name, toString(parent_).c_str(), request); mount(request).execute(&Mount::mknod, true, Request(request), parent_, std::string(name), static_cast<std::uintmax_t>(mode)); } Mount& SessionBase::mount(fuse_req_t request) { return mount(fuse_req_userdata(request)); } Mount& SessionBase::mount(void* context) { return session(context).mMount; } void SessionBase::open(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("open: inode: %s, request: %p", toString(inode_).c_str(), request); mount(request).execute(&Mount::open, true, Request(request), inode_, *info); } void SessionBase::opendir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("opendir: info: %p, inode: %s, request: %p", info, toString(inode_).c_str(), request); mount(request).execute(&Mount::opendir, true, Request(request), inode_, *info); } const fuse_lowlevel_ops& SessionBase::operations() { // Make sure all operations have been populated. std::call_once(mOperationsInitialized, [this]() { populateOperations(mOperations); }); // Return operations to caller. return mOperations; } void SessionBase::populateCapabilities(fuse_conn_info* connection) { connection->want |= FUSE_CAP_ATOMIC_O_TRUNC; } void SessionBase::populateOperations(fuse_lowlevel_ops& operations) { operations.access = &SessionBase::access; operations.flush = &SessionBase::flush; operations.forget_multi = &SessionBase::forget_multi; operations.fsync = &SessionBase::fsync; operations.getattr = &SessionBase::getattr; operations.init = &SessionBase::init; operations.lookup = &SessionBase::lookup; operations.mkdir = &SessionBase::mkdir; operations.mknod = &SessionBase::mknod; operations.open = &SessionBase::open; operations.opendir = &SessionBase::opendir; operations.read = &SessionBase::read; operations.readdir = &SessionBase::readdir; operations.release = &SessionBase::release; operations.releasedir = &SessionBase::releasedir; operations.rmdir = &SessionBase::rmdir; operations.setattr = &SessionBase::setattr; operations.statfs = &SessionBase::statfs; operations.unlink = &SessionBase::unlink; operations.write = &SessionBase::write; } void SessionBase::read(fuse_req_t request, fuse_ino_t inode, size_t size, off_t offset, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("read: inode: %s, offset: %ld, request: %p, size: %zu", toString(inode_).c_str(), offset, request, size); mount(request).execute(&Mount::read, true, Request(request), inode_, size, offset, *info); } void SessionBase::readdir(fuse_req_t request, fuse_ino_t inode, std::size_t size, off_t offset, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("readdir: info: %p, inode: %s, offset: %d, size: %zu, request: %p", info, toString(inode_).c_str(), offset, size, request); mount(request).execute(&Mount::readdir, true, Request(request), inode_, size, offset, *info); } void SessionBase::release(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("release: inode: %s, request: %p", toString(inode_).c_str(), request); mount(request).execute(&Mount::release, true, Request(request), inode_, *info); } void SessionBase::releasedir(fuse_req_t request, fuse_ino_t inode, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("releasedir: info: %p, inode: %s, request: %p", info, toString(inode_).c_str(), request); mount(request).execute(&Mount::releasedir, true, Request(request), inode_, *info); } void SessionBase::rmdir(fuse_req_t request, fuse_ino_t parent, const char* name) { MountInodeID parent_(parent); FUSEDebugF("rmdir: name: %s, parent: %s, request: %p", name, toString(parent_).c_str(), request); mount(request).execute(&Mount::rmdir, true, Request(request), parent_, std::string(name)); } SessionBase& SessionBase::session(fuse_req_t request) { return session(fuse_req_userdata(request)); } SessionBase& SessionBase::session(void* context) { assert(context); return *static_cast<SessionBase*>(context); } void SessionBase::setattr(fuse_req_t request, fuse_ino_t inode, struct stat* attributes, int changes, fuse_file_info*) { #define ENTRY(name) \ { \ #name, name \ } static std::map<std::string, int> names = {ENTRY(FUSE_SET_ATTR_ATIME), ENTRY(FUSE_SET_ATTR_ATIME_NOW), ENTRY(FUSE_SET_ATTR_GID), ENTRY(FUSE_SET_ATTR_MODE), ENTRY(FUSE_SET_ATTR_MTIME), ENTRY(FUSE_SET_ATTR_MTIME_NOW), ENTRY(FUSE_SET_ATTR_SIZE), ENTRY(FUSE_SET_ATTR_UID)}; // names #undef ENTRY MountInodeID inode_(inode); FUSEDebugF("setattr: changes: %x, inode: %s, request: %p", changes, toString(inode_).c_str(), request); for (auto& i: names) { if ((changes & i.second)) FUSEDebugF("setattr: attribute %s", i.first.c_str()); } mount(request).execute(&Mount::setattr, true, Request(request), inode_, *attributes, changes); } void SessionBase::statfs(fuse_req_t request, fuse_ino_t inode) { MountInodeID inode_(inode); FUSEDebugF("statfs: inode: %s, request: %p", toString(inode_).c_str(), request); mount(request).execute(&Mount::statfs, true, Request(request), inode_); } void SessionBase::unlink(fuse_req_t request, fuse_ino_t parent, const char* name) { MountInodeID parent_(parent); FUSEDebugF("unlink: name: %s, parent: %s, request: %p", name, toString(parent_).c_str(), request); mount(request).execute(&Mount::unlink, true, Request(request), parent_, std::string(name)); } void SessionBase::write(fuse_req_t request, fuse_ino_t inode, const char* data, size_t size, off_t offset, fuse_file_info* info) { MountInodeID inode_(inode); FUSEDebugF("write: inode: %s, offset: %ld, request: %p, size: %zu", toString(inode_).c_str(), offset, request, size); mount(request).execute(&Mount::write, true, Request(request), inode_, std::string(data, size), offset, *info); } SessionBase::SessionBase(Mount& mount): mMount(mount), mSession() {} SessionBase::~SessionBase() { FUSEDebugF("Session destroyed: %s", mMount.path().toPath(false).c_str()); } bool SessionBase::exited() const { assert(mSession); return fuse_session_exited(mSession.get()); } void SessionBase::destroy() { // Sanity. assert(exited()); mMount.destroy(); } void SessionBase::invalidateAttributes(MountInodeID id) { return invalidateData(id, -1, 0); } void SessionBase::invalidateData(MountInodeID id) { return invalidateData(id, 0, 0); } } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/signal.cpp��������������������������������������������0000664�0000000�0000000�00000002011�15162662266�0023166�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/platform/signal.h> #include <mega/fuse/platform/utility.h> #include <cassert> #include <cstring> #include <utility> namespace mega { namespace fuse { namespace platform { Signal::Signal(const std::string& name) try : mName(name), mReader(), mWriter() { // Create pipe. std::tie(mReader, mWriter) = pipe(true, true); // Make sure writer is closed on fork. mWriter.closeOnFork(true); } catch (std::runtime_error& exception) { throw FUSEErrorF("Unable to create signal: %s", exception.what()); } void Signal::clear() { char dummy; FUSEDebugF("Clearing signal %s", mName.c_str()); mReader.read(&dummy, 1); } int Signal::descriptor() const { return mReader.get(); } void Signal::raise() { FUSEDebugF("Raising signal %s", mName.c_str()); mWriter.write("", 1); } void Signal::swap(Signal& other) { using std::swap; swap(mReader, other.mReader); swap(mWriter, other.mWriter); } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022670�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/CMakeLists.txt��������������������������������0000664�0000000�0000000�00000000036�15162662266�0025427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(integration) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/����������������������������������0000775�0000000�0000000�00000000000�15162662266�0025213�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/CMakeLists.txt��������������������0000664�0000000�0000000�00000000607�15162662266�0027756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if(NOT TARGET test_integration) return() endif() target_include_directories(test_integration PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(test_integration PRIVATE mount_tests.cpp platform_tests.cpp printers.cpp wrappers.cpp ) add_subdirectory(mega) �������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/�����������������������������0000775�0000000�0000000�00000000000�15162662266�0026124�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/CMakeLists.txt���������������0000664�0000000�0000000�00000000027�15162662266�0030663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/������������������������0000775�0000000�0000000�00000000000�15162662266�0027066�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/CMakeLists.txt����������0000664�0000000�0000000�00000000033�15162662266�0031622�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/���������������0000775�0000000�0000000�00000000000�15162662266�0030712�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/CMakeLists.txt�0000664�0000000�0000000�00000000032�15162662266�0033445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(testing) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/testing/�������0000775�0000000�0000000�00000000000�15162662266�0032367�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CMakeLists.txt��������������������������������������������������������������������������������������0000664�0000000�0000000�00000000077�15162662266�0035054�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/testing���������������������������������������������������������������������������target_sources(test_integration PRIVATE printers.h wrappers.h) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������printers.h������������������������������������������������������������������������������������������0000664�0000000�0000000�00000000423�15162662266�0034326�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/testing���������������������������������������������������������������������������#pragma once #include <mega/fuse/common/testing/printers.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> #include <ostream> void PrintTo(const struct dirent& entry, std::ostream* ostream); void PrintTo(const struct stat& stat, std::ostream* ostream); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������wrappers.h������������������������������������������������������������������������������������������0000664�0000000�0000000�00000007561�15162662266�0034335�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mega/fuse/platform/testing���������������������������������������������������������������������������#pragma once #include <mega/common/testing/path_forward.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/file_descriptor_forward.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/types.h> #include <dirent.h> #include <memory> #include <ostream> #include <string> // Convenience. struct DirectoryDeleter { void operator()(DIR* directory) { if (directory) closedir(directory); } }; // DirectoryDeleter using DirectoryIterator = std::unique_ptr<DIR, DirectoryDeleter>; using Stat = struct stat; bool operator==(const struct dirent& lhs, const struct dirent& rhs); bool operator!=(const struct dirent& lhs, const struct dirent& rhs); bool operator==(const Stat& lhs, const Stat& rhs); bool operator!=(const Stat& lhs, const Stat& rhs); template<typename T> auto operator==(const Stat& lhs, const T& rhs) -> typename mega::fuse::testing::EnableIfInfoLike<T, bool>::type { return rhs == lhs; } template<typename T> auto operator!=(const Stat& lhs, const T& rhs) -> typename mega::fuse::testing::EnableIfInfoLike<T, bool>::type { return rhs != lhs; } namespace mega { namespace common { bool operator==(const NodeInfo& lhs, const Stat& rhs); bool operator!=(const NodeInfo& lhs, const Stat& rhs); } // common namespace fuse { template<typename T> auto operator==(const T& lhs, const Stat& rhs) -> typename testing::EnableIfInfoLike<T, bool>::type; template<typename T> auto operator!=(const T& lhs, const Stat& rhs) -> typename testing::EnableIfInfoLike<T, bool>::type { return !(lhs == rhs); } namespace testing { int access(const common::testing::Path& path, int mode); int accessat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, int mode); DirectoryIterator fdopendir(platform::FileDescriptor descriptor); bool flushFile(const common::testing::Path& path); int fstat(const platform::FileDescriptor& descriptor, Stat& buffer); int fsync(const platform::FileDescriptor& descriptor); int ftruncate(const platform::FileDescriptor& descriptor, off_t length); int futimes(const platform::FileDescriptor& descriptor, struct timeval (×)[2]); int mkdir(const common::testing::Path& path, mode_t mode); int mkdirat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, mode_t mode); platform::FileDescriptor open(const common::testing::Path& path, int flags); platform::FileDescriptor open(const common::testing::Path& path, int flags, mode_t mode); platform::FileDescriptor openat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, int flags); platform::FileDescriptor openat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, int flags, mode_t mode); DirectoryIterator opendir(const common::testing::Path& path); int rename(const common::testing::Path& before, const common::testing::Path& after); int renameat(const platform::FileDescriptor& sourceParent, const common::testing::Path& sourcePath, const platform::FileDescriptor& targetParent, const common::testing::Path& targetPath); int rmdir(const common::testing::Path& path); int stat(const common::testing::Path& path, Stat& buffer); int statat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, Stat& buffer); int statvfs(const common::testing::Path& path, struct statvfs& buffer); int truncate(const common::testing::Path& path, off_t length); int unlink(const common::testing::Path& path); int unlinkat(const platform::FileDescriptor& descriptor, const common::testing::Path& path, int flags); } // testing } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/mount_tests.cpp�������������������0000664�0000000�0000000�00000004706�15162662266�0030312�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/testing/directory.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/mount_tests.h> namespace mega { namespace fuse { namespace testing { using namespace common; using namespace common::testing; TEST_F(FUSEMountTests, add_fails_when_target_is_unknown) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = Path(MountPathW().path() / "bogus"); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_LOCAL_UNKNOWN, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_LOCAL_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_fails_when_target_is_unspecified) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); info.mPath = NormalizedPath(); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_LOCAL_UNKNOWN, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_LOCAL_UNKNOWN); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, enable_fails_when_target_is_unknown) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo mount; auto observer = ClientW()->mountEventObserver(); { Directory sd0("sd0", mScratchPath); mount.name("s"); mount.mHandle = *handle; mount.mPath = sd0.path(); observer->expect({mount.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(mount), MOUNT_SUCCESS); } observer->expect({mount.name(), MOUNT_LOCAL_UNKNOWN, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(mount.name(), false), MOUNT_LOCAL_UNKNOWN); ASSERT_TRUE(ClientW()->mounts(true).empty()); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_FALSE(fs::exists(SentinelPathW())); } } // testing } // fuse } // mega ����������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/platform_tests.cpp����������������0000664�0000000�0000000�00000124336�15162662266�0030776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/testing/cloud_path.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/testing/platform_tests.h> #include <mega/fuse/platform/testing/printers.h> #include <mega/fuse/platform/testing/wrappers.h> #include <mega/logging.h> #include <sys/stat.h> #include <sys/types.h> #include <algorithm> #include <chrono> #include <map> #include <thread> namespace mega { namespace fuse { namespace testing { #ifndef HAS_OPEN_PATH #define O_PATH O_RDONLY #endif // !HAS_OPEN_PATH using namespace common::testing; TEST_P(FUSEPlatformTests, access_at_fails_when_below_file) { auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_LT(accessat(sf0, "x", F_OK), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, access_at_fails_when_read_only) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(accessat(s, "sd0", W_OK), 0); ASSERT_EQ(errno, EROFS); ASSERT_LT(accessat(s, "sf0", W_OK), 0); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, access_at_fails_when_not_executable) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(accessat(s, "sf0", X_OK), 0); ASSERT_EQ(errno, EACCES); } TEST_P(FUSEPlatformTests, access_at_fails_when_unknown) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(accessat(s, "sfx", F_OK), 0); ASSERT_EQ(errno, ENOENT); ASSERT_LT(accessat(s, "sfx", W_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, access_at_succeeds) { auto sr = open(MountPathR(), O_PATH); ASSERT_TRUE(sr); auto sw = open(MountPathW(), O_PATH); ASSERT_TRUE(sw); // Should be able to test for existence. ASSERT_EQ(accessat(sr, "sd0", F_OK), 0); ASSERT_EQ(accessat(sw, "sf0", F_OK), 0); // Readable directories should have 0500 permissions. ASSERT_EQ(accessat(sr, "sd0", R_OK | X_OK), 0); // Readable files should have 0400 permissions. ASSERT_EQ(accessat(sr, "sf0", R_OK), 0); // Writable directories should have 0700 permissions. ASSERT_EQ(accessat(sw, "sd0", R_OK | W_OK | X_OK), 0); // Writable files should have 0600 permissions. ASSERT_EQ(accessat(sw, "sf0", R_OK | W_OK), 0); } TEST_P(FUSEPlatformTests, access_fails_when_below_file) { ASSERT_LT(access(MountPathW() / "sf0" / "x", F_OK), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_LT(access(MountPathW() / "sf0" / "x", W_OK), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, access_fails_when_read_only) { ASSERT_LT(access(MountPathR() / "sd0", W_OK), 0); ASSERT_EQ(errno, EROFS); ASSERT_LT(access(MountPathR() / "sf0", W_OK), 0); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, access_fails_when_not_executable) { ASSERT_LT(access(MountPathW() / "sf0", X_OK), 0); ASSERT_EQ(errno, EACCES); } TEST_P(FUSEPlatformTests, access_fails_when_unknown) { ASSERT_LT(access(MountPathW() / "x", F_OK), 00); ASSERT_EQ(errno, ENOENT); ASSERT_LT(access(MountPathW() / "x", W_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, access_succeeds) { // Should be able to test for existence. ASSERT_EQ(access(MountPathW(), F_OK), 0); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); // Readable directories should have 0500 permissions. ASSERT_EQ(access(MountPathR(), R_OK | X_OK), 0); // Readable files should have 0400 permissions. ASSERT_EQ(access(MountPathR() / "sf0", R_OK), 0); // Writable directories should have 0700 permissions. ASSERT_EQ(access(MountPathW(), R_OK | W_OK | X_OK), 0); // Writable files should have 0600 permissions. ASSERT_EQ(access(MountPathW() / "sf0", R_OK | W_OK), 0); } TEST_P(FUSEPlatformTests, fchown_fails_when_read_only) { auto sf0 = open(MountPathR() / "sf0", O_RDONLY); ASSERT_TRUE(sf0); ASSERT_TRUE(fchown(sf0.get(), 0, 0)); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, fchown_fails_when_other_user) { auto sf0 = open(MountPathW() / "sf0", O_RDWR); ASSERT_TRUE(sf0); ASSERT_TRUE(fchown(sf0.get(), getuid(), getgid() + 1)); ASSERT_EQ(errno, EPERM); ASSERT_TRUE(fchown(sf0.get(), getuid() + 1, getgid())); ASSERT_EQ(errno, EPERM); } TEST_P(FUSEPlatformTests, fchown_succeeds) { auto sf0 = open(MountPathW() / "sf0", O_RDWR); ASSERT_TRUE(sf0); ASSERT_FALSE(fchown(sf0.get(), getuid(), getgid())); } TEST_P(FUSEPlatformTests, fstat_succeeds_after_directory_removed) { auto info = ClientW()->get("/x/s/sd0/sd0d0"); ASSERT_TRUE(info); auto sd0d0 = open(MountPathW() / "sd0" / "sd0d0", O_PATH); ASSERT_TRUE(sd0d0); auto buffer0 = Stat(); ASSERT_EQ(fstat(sd0d0, buffer0), 0); ASSERT_EQ(buffer0, *info); ASSERT_EQ(ClientW()->remove(info->mHandle), API_OK); ASSERT_TRUE(waitFor( [&]() { return access(MountPathO() / "sd0" / "sd0d0", F_OK) < 0 && errno == ENOENT && access(MountPathW() / "sd0" / "sd0d0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); LINUX_ONLY(buffer0.st_nlink = 0); auto buffer1 = Stat(); ASSERT_EQ(fstat(sd0d0, buffer1), 0); ASSERT_EQ(buffer0, buffer1); } TEST_P(FUSEPlatformTests, fstat_succeeds_after_file_removed) { auto info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); auto buffer0 = Stat(); ASSERT_EQ(fstat(sf0, buffer0), 0); ASSERT_EQ(buffer0, *info); ASSERT_EQ(ClientW()->remove(info->mHandle), API_OK); ASSERT_TRUE(waitFor( [&]() { return access(MountPathO() / "sf0", F_OK) < 0 && errno == ENOENT && access(MountPathW() / "sf0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); LINUX_ONLY(buffer0.st_nlink = 0); auto buffer1 = Stat(); ASSERT_EQ(fstat(sf0, buffer1), 0); ASSERT_EQ(buffer0, buffer1); } TEST_P(FUSEPlatformTests, fstat_succeeds) { auto info = ClientW()->get("/x/s/sd0"); ASSERT_TRUE(info); auto sd0 = open(MountPathW() / "sd0", O_PATH); ASSERT_TRUE(sd0); auto buffer = Stat(); ASSERT_EQ(fstat(sd0, buffer), 0); ASSERT_EQ(buffer, *info); auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); ASSERT_EQ(fstat(sf0, buffer), 0); ASSERT_EQ(buffer, *info); } TEST_P(FUSEPlatformTests, ftruncate_fails_when_directory) { auto s = open(MountPathW(), O_RDONLY); ASSERT_TRUE(s); ASSERT_TRUE(ftruncate(s, 0)); ASSERT_EQ(errno, EINVAL); } TEST_P(FUSEPlatformTests, ftruncate_fails_when_read_only) { auto sf0 = open(MountPathR() / "sf0", O_RDONLY); ASSERT_TRUE(sf0); ASSERT_TRUE(ftruncate(sf0, 0)); ASSERT_EQ(errno, EINVAL); } TEST_P(FUSEPlatformTests, ftruncate_succeeds) { auto sf0 = open(MountPathW() / "sf0", O_RDWR); ASSERT_TRUE(sf0); ASSERT_FALSE(ftruncate(sf0, 64)); Stat buffer; ASSERT_FALSE(fstat(sf0, buffer)); ASSERT_EQ(buffer.st_size, 64); ASSERT_FALSE(ftruncate(sf0, 0)); ASSERT_FALSE(fstat(sf0, buffer)); ASSERT_EQ(buffer.st_size, 0); ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sf0", buffer) && !buffer.st_size; }, mDefaultTimeout)); ASSERT_FALSE(fsync(sf0)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, futimes_fails_when_read_only) { auto sf0 = open(MountPathR() / "sf0", O_RDONLY); ASSERT_TRUE(sf0); struct timeval times[] = {{0, 0}, {0, 0}}; ASSERT_TRUE(futimes(sf0, times)); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, futimes_success) { // Open file for writing. auto sf0 = open(MountPathW() / "sf0", O_RDWR); ASSERT_TRUE(sf0); for (auto i = 0; i < 2; ++i) { struct timeval times[] = {{i, i}, {i, i}}; // times // Try and set the modification time. ASSERT_FALSE(futimes(sf0, times)); // Make sure the modification time was set. Stat buffer; ASSERT_FALSE(fstat(sf0, buffer)); ASSERT_EQ(buffer.st_mtime, i); // Make sure the new time is visible via observer. ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sf0", buffer) && buffer.st_mtime == i; }, mDefaultTimeout)); // Truncate the file. ASSERT_FALSE(ftruncate(sf0, 0)); } } TEST_P(FUSEPlatformTests, mkdir_at_fails_when_already_exists) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(mkdirat(s, "sd0", 0700), 0); ASSERT_EQ(errno, EEXIST); } TEST_P(FUSEPlatformTests, mkdir_at_fails_when_below_file) { auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_LT(mkdirat(sf0, "x", 0700), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, mkdir_at_fails_when_read_only) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(mkdirat(s, "x", 0700), 0); ASSERT_EQ(errno, EROFS); ASSERT_LT(accessat(s, "x", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mkdir_at_fails_when_unknown) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(mkdirat(s, Path("sdx") / "x", 0700), 0); ASSERT_EQ(errno, ENOENT); ASSERT_LT(accessat(s, "sdx", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mkdir_at_succeeds) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_EQ(mkdirat(s, "sd2", 0700), 0); ASSERT_TRUE(waitFor( [&]() { // Make sure directory exists in the cloud. auto info = ClientW()->get("/x/s/sd2"); // Directory isn't in the cloud. if (!info) return false; // Wrong name or type. if (info->mName != "sd2" || !info->mIsDirectory) return false; // Make sure cache has been invalidated in observer. return !access(MountPathO() / "sd2", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, mkdir_fails_when_already_exists) { ASSERT_LT(mkdir(MountPathW() / "sd0", 0700), 0); ASSERT_EQ(errno, EEXIST); } TEST_P(FUSEPlatformTests, mkdir_fails_when_below_file) { ASSERT_LT(mkdir(MountPathW() / "sf0" / "x", 0700), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, mkdir_fails_when_read_only) { ASSERT_LT(mkdir(MountPathR() / "x", 0700), 0); ASSERT_EQ(errno, EROFS); ASSERT_LT(access(MountPathR() / "x", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mkdir_fails_when_unknown) { ASSERT_LT(mkdir(MountPathW() / "sdx" / "x", 0700), 0); ASSERT_EQ(errno, ENOENT); ASSERT_LT(access(MountPathW() / "sdx", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, mkdir_succeeds) { ASSERT_EQ(mkdir(MountPathW() / "sd2", 0700), 0); ASSERT_TRUE(waitFor( [&]() { // Make sure directory exists in the cloud. auto info = ClientW()->get("/x/s/sd2"); // Directory isn't in the cloud. if (!info) return false; // Wrong name or type. if (info->mName != "sd2" || !info->mIsDirectory) return false; // Make sure cache has been invalidated in observer. return !access(MountPathO() / "sd2", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, move_local_file_succeeds) { ASSERT_GE(open(MountPathW() / "sfx", O_CREAT | O_TRUNC | O_WRONLY), 0); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathO() / "sfx", F_OK); }, mDefaultTimeout)); Stat sfxo; Stat sfxw; ASSERT_FALSE(stat(MountPathO() / "sfx", sfxo)); ASSERT_FALSE(stat(MountPathW() / "sfx", sfxw)); ASSERT_FALSE(rename(MountPathW() / "sfx", MountPathW() / "sfy")); Stat sfy; ASSERT_FALSE(stat(MountPathW() / "sfy", sfy)); ASSERT_EQ(sfxw, sfy); ASSERT_TRUE(waitFor( [&]() { return access(MountPathO() / "sfx", F_OK) && errno == ENOENT && !stat(MountPathO() / "sfy", sfy) && sfxo == sfy; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, open_at_create_succeeds) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); auto sfx = openat(s, "sfx", O_CREAT | O_WRONLY, 0644); ASSERT_TRUE(sfx); Stat buffer; ASSERT_FALSE(fstat(sfx, buffer)); ASSERT_EQ(buffer.st_size, 0); ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sfx", buffer) && !buffer.st_size; }, mDefaultTimeout)); ASSERT_FALSE(fsync(sfx)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sfx"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); ASSERT_FALSE(unlink(MountPathW() / "sfx")); } TEST_P(FUSEPlatformTests, open_at_fails_when_below_file) { auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_FALSE(openat(sf0, "x", O_RDWR)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, open_at_fails_when_not_directory) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_FALSE(openat(s, "sf0", O_DIRECTORY)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, open_at_fails_when_not_file) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_FALSE(openat(s, "sd0", O_RDWR)); ASSERT_EQ(errno, EISDIR); } TEST_P(FUSEPlatformTests, open_at_fails_when_read_only) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); ASSERT_FALSE(openat(s, "sf0", O_RDWR)); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, open_at_fails_when_unknown) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_FALSE(openat(s, "x", O_RDWR)); ASSERT_EQ(errno, ENOENT); ASSERT_LT(accessat(s, "x", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, open_at_succeeds) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); // Should be able to open a directory for reading. ASSERT_TRUE(openat(s, "sd0", O_RDONLY | O_DIRECTORY)); // Should be able to open a file for reading. ASSERT_TRUE(openat(s, "sf0", O_RDONLY)); s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); // Should be able to open a file for read/write. ASSERT_TRUE(openat(s, "sf0", O_RDWR)); // Should be able to open a file for writing. ASSERT_TRUE(openat(s, "sf0", O_WRONLY)); // Should be able to open a file for appended writes. ASSERT_TRUE(openat(s, "sf0", O_APPEND | O_RDWR)); } TEST_P(FUSEPlatformTests, open_at_truncate_succeeds) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); auto sf0 = openat(s, "sf0", O_TRUNC | O_WRONLY); ASSERT_TRUE(sf0); Stat buffer; ASSERT_FALSE(fstat(sf0, buffer)); ASSERT_EQ(buffer.st_size, 0); ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sf0", buffer) && !buffer.st_size; }, mDefaultTimeout)); ASSERT_FALSE(fsync(sf0)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, opendir_fails_when_below_file) { auto iterator = opendir(MountPathW() / "sf0" / "x"); ASSERT_FALSE(iterator); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, opendir_fails_when_not_directory) { auto iterator = opendir(MountPathW() / "sf0"); ASSERT_FALSE(iterator); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, opendir_fails_when_unknown) { auto iterator = opendir(MountPathW() / "x"); ASSERT_FALSE(iterator); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, opendir_succeeds) { auto iterator = opendir(MountPathW() / "sd0"); ASSERT_TRUE(iterator); } TEST_P(FUSEPlatformTests, open_create_succeeds) { auto sfx = open(MountPathW() / "sfx", O_CREAT | O_WRONLY, 0644); ASSERT_TRUE(sfx); Stat buffer; ASSERT_FALSE(fstat(sfx, buffer)); ASSERT_EQ(buffer.st_size, 0); ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sfx", buffer) && !buffer.st_size; }, mDefaultTimeout)); ASSERT_FALSE(fsync(sfx)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sfx"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); ASSERT_FALSE(unlink(MountPathW() / "sfx")); } TEST_P(FUSEPlatformTests, open_fails_when_below_file) { ASSERT_FALSE(open(MountPathW() / "sf0" / "x", O_RDWR)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, open_fails_when_not_directory) { ASSERT_FALSE(open(MountPathW() / "sf0", O_DIRECTORY)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, open_fails_when_not_file) { ASSERT_FALSE(open(MountPathW() / "sd0", O_RDWR)); ASSERT_EQ(errno, EISDIR); } TEST_P(FUSEPlatformTests, open_fails_when_read_only) { ASSERT_FALSE(open(MountPathR() / "sf0", O_RDWR)); ASSERT_EQ(errno, EROFS); } TEST_P(FUSEPlatformTests, open_fails_when_unknown) { ASSERT_FALSE(open(MountPathW() / "x", O_RDWR)); ASSERT_EQ(errno, ENOENT); ASSERT_LT(access(MountPathW() / "x", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, open_succeeds) { // Should be able to open a directory for reading. ASSERT_TRUE(open(MountPathR() / "sd0", O_RDONLY | O_DIRECTORY)); // Should be able to open a file for reading. ASSERT_TRUE(open(MountPathR() / "sf0", O_RDONLY)); // Should be able to open a file for read/write. ASSERT_TRUE(open(MountPathW() / "sf0", O_RDWR)); // Should be able to open a file for writing. ASSERT_TRUE(open(MountPathW() / "sf0", O_WRONLY)); // Should be able to open a file for appended writes. ASSERT_TRUE(open(MountPathW() / "sf0", O_APPEND | O_RDWR)); } TEST_P(FUSEPlatformTests, open_truncate_succeeds) { auto sf0 = open(MountPathW() / "sf0", O_TRUNC | O_WRONLY); ASSERT_TRUE(sf0); Stat buffer; ASSERT_FALSE(fstat(sf0, buffer)); ASSERT_EQ(buffer.st_size, 0); ASSERT_TRUE(waitFor( [&]() { return !stat(MountPathO() / "sf0", buffer) && !buffer.st_size; }, mDefaultTimeout)); ASSERT_FALSE(fsync(sf0)); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, read_fails_when_directory) { auto sd0 = open(MountPathW() / "sd0", O_RDONLY); ASSERT_TRUE(sd0); char buffer; ASSERT_LT(read(sd0.get(), &buffer, sizeof(buffer)), 0); ASSERT_EQ(errno, EISDIR); } TEST_P(FUSEPlatformTests, read_fails_when_write_only) { auto sf0 = open(MountPathW() / "sf0", O_WRONLY); ASSERT_TRUE(sf0); char buffer; ASSERT_LT(read(sf0.get(), &buffer, sizeof(buffer)), 0); ASSERT_EQ(errno, EBADF); } TEST_P(FUSEPlatformTests, read_succeeds) { auto sf0 = open(MountPathR() / "sf0", O_RDONLY); ASSERT_TRUE(sf0); std::string buffer(32, '\0'); buffer.resize(sf0.read(&buffer[0], buffer.size())); ASSERT_EQ(buffer.size(), 3u); ASSERT_EQ(buffer, "sf0"); } TEST_P(FUSEPlatformTests, read_write_succeeds) { constexpr auto BYTES_PER_THREAD = 4u; constexpr auto NUM_ITERATIONS = 128u; constexpr auto NUM_THREADS = 4u; auto w = open(MountPathW() / "sfx", O_CREAT | O_WRONLY); ASSERT_TRUE(w); auto r = open(MountPathW() / "sfx", O_RDONLY); ASSERT_TRUE(r); // Tells our threads to terminate if there's a mismatch. std::atomic<bool> terminate{false}; // Writes data to w, reads it back on r. auto loop = [&](std::size_t id) { // Where should this thread write its data? auto offset = static_cast<m_off_t>(id * BYTES_PER_THREAD); // Write data to w, read it back on r. for (auto i = 0u; !terminate && i < NUM_ITERATIONS; ++i) { // Generate some data to write to w. auto written = randomBytes(BYTES_PER_THREAD); // Write the data to w. w.write(&written[0], written.size(), offset); auto read = std::string(written.size(), '\0'); // Try and read the data back. read.resize(r.read(&read[0], read.size(), offset)); // Terminate if we couldn't read back what we wrote. if (read != written) terminate = true; } }; // loop std::vector<std::thread> threads; // Kick off a bunch of threads. for (auto i = 0u; i < NUM_THREADS; ++i) threads.emplace_back(std::bind(loop, i)); // Wait for the threads to terminate. while (!threads.empty()) { threads.back().join(); threads.pop_back(); } ASSERT_FALSE(unlink(MountPathW() / "sfx")); // Make sure there were no failures. ASSERT_FALSE(terminate); } TEST_P(FUSEPlatformTests, readdir_succeeds_when_changing) { auto iterator = opendir(MountPathW()); ASSERT_TRUE(iterator); auto entries = [&]() { std::map<std::string, ino_t> entries; rewinddir(iterator.get()); auto* entry = readdir(iterator.get()); EXPECT_TRUE(entry); while (entry) { entries.emplace(entry->d_name, entry->d_ino); errno = 0; entry = readdir(iterator.get()); } EXPECT_EQ(errno, 0); return entries; }; // entries auto before = entries(); ASSERT_EQ(before.size(), 7u); { auto sf0 = open(MountPathW() / "sf0", O_TRUNC | O_WRONLY); ASSERT_TRUE(sf0); ASSERT_EQ(fsync(sf0), 0); } ASSERT_EQ(ClientW()->remove("/x/s/sd0"), API_OK); ASSERT_EQ(ClientW()->move("sdx", "/x/s/sd1", "/x/s"), API_OK); ASSERT_EQ(ClientW()->move("sf1", "/x/s/sf1", "/x/s/sdx"), API_OK); ASSERT_TRUE(waitFor( [&]() { return access(MountPathW() / "sd0", F_OK) && errno == ENOENT && !access(MountPathW() / "sdx" / "sf1", F_OK); }, mDefaultTimeout)); auto after = entries(); ASSERT_EQ(after.size(), 5u); ASSERT_EQ(after.count("sdx"), 1u); ASSERT_EQ(after.count("sf0"), 1u); ASSERT_EQ(after["sdx"], before["sd1"]); ASSERT_EQ(after["sf0"], before["sf0"]); } TEST_P(FUSEPlatformTests, readdir_succeeds_random_access) { std::vector<struct dirent> entries; std::vector<long> indices; auto iterator = opendir(MountPathW() / "sd0"); ASSERT_TRUE(iterator); while (true) { auto index = telldir(iterator.get()); errno = 0; auto* entry = readdir(iterator.get()); if (!entry) break; entries.emplace_back(*entry); indices.emplace_back(index); } while (std::next_permutation(indices.begin(), indices.end())) { for (auto index: indices) { seekdir(iterator.get(), index); auto* entry = readdir(iterator.get()); ASSERT_TRUE(entry); auto index_ = static_cast<std::size_t>(index); ASSERT_EQ(entries[index_], *entry); } } } TEST_P(FUSEPlatformTests, readdir_succeeds) { auto sd0 = open(MountPathW() / "sd0", O_RDONLY); ASSERT_TRUE(sd0); std::map<std::string, Stat> expectations; ASSERT_EQ(fstat(sd0, expectations["."]), 0); ASSERT_EQ(statat(sd0, "..", expectations[".."]), 0); auto names = ClientW()->childNames("/x/s/sd0"); ASSERT_EQ(names.errorOr(API_OK), API_OK); for (const auto& child: *names) ASSERT_EQ(statat(sd0, child, expectations[child]), 0); auto iterator = fdopendir(std::move(sd0)); ASSERT_TRUE(iterator); auto* entry = readdir(iterator.get()); ASSERT_TRUE(entry); while (entry) { auto e = expectations.find(entry->d_name); ASSERT_NE(e, expectations.end()); ASSERT_EQ(entry->d_ino, e->second.st_ino); expectations.erase(e); errno = 0; entry = readdir(iterator.get()); } ASSERT_EQ(errno, 0); ASSERT_TRUE(expectations.empty()); } TEST_P(FUSEPlatformTests, rename_fails_when_below_file) { ASSERT_TRUE(rename(MountPathW() / "sf0" / "x", MountPathW() / "x")); ASSERT_EQ(errno, ENOTDIR); ASSERT_TRUE(rename(MountPathW() / "sf0", MountPathW() / "sf1" / "x")); ASSERT_EQ(errno, ENOTDIR); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, rename_fails_when_read_only) { ASSERT_TRUE(rename(MountPathR() / "sf0", MountPathR() / "sfx")); ASSERT_EQ(errno, EROFS); ASSERT_EQ(access(MountPathR() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, rename_fails_when_source_and_target_types_dont_match) { ASSERT_TRUE(rename(MountPathW() / "sf0", MountPathW() / "sd0")); ASSERT_EQ(errno, EISDIR); ASSERT_FALSE(access(MountPathW() / "sf0", F_OK)); ASSERT_TRUE(rename(MountPathW() / "sd0", MountPathW() / "sf0")); ASSERT_EQ(errno, ENOTDIR); ASSERT_FALSE(access(MountPathW() / "sd0", F_OK)); } TEST_P(FUSEPlatformTests, rename_fails_when_target_directory_is_not_empty) { ASSERT_TRUE(rename(MountPathW() / "sd0", MountPathW() / "sd1")); ASSERT_EQ(errno, ENOTEMPTY); } TEST_P(FUSEPlatformTests, rename_fails_when_unknown) { ASSERT_TRUE(rename(MountPathW() / "sdx", MountPathW() / "sdy")); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, rename_move_rename_succeeds) { Stat before; ASSERT_FALSE(stat(MountPathW() / "sd0", before)); ASSERT_FALSE(rename(MountPathW() / "sd0", MountPathW() / "sd1" / "sdx")); Stat after; ASSERT_FALSE(stat(MountPathW() / "sd1" / "sdx", after)); ASSERT_EQ(after, before); ASSERT_TRUE(waitFor( [&]() { return ClientW()->get("/x/s/sd1/sdx/sd0d0") && !access(MountPathO() / "sd1" / "sdx" / "sd0d0", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rename_move_succeeds) { Stat before; ASSERT_FALSE(stat(MountPathW() / "sd0", before)); ASSERT_FALSE(rename(MountPathW() / "sd0", MountPathW() / "sd1" / "sd0")); Stat after; ASSERT_FALSE(stat(MountPathW() / "sd1" / "sd0", after)); ASSERT_EQ(after, before); ASSERT_TRUE(waitFor( [&]() { return ClientW()->get("/x/s/sd1/sd0/sd0d0") && !access(MountPathO() / "sd1" / "sd0" / "sd0d0", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rename_rename_succeeds) { Stat before; ASSERT_FALSE(stat(MountPathW() / "sf0", before)); ASSERT_FALSE(rename(MountPathW() / "sf0", MountPathW() / "sfx")); Stat after; ASSERT_FALSE(stat(MountPathW() / "sfx", after)); ASSERT_EQ(after, before); ASSERT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && !access(MountPathO() / "sfx", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rename_replace_directory_succeeds) { Stat before; ASSERT_FALSE(stat(MountPathW() / "sd0", before)); ASSERT_FALSE(rename(MountPathW() / "sd0", MountPathW() / "sd1" / "sd1d0")); Stat after; ASSERT_FALSE(stat(MountPathW() / "sd1" / "sd1d0", after)); ASSERT_EQ(after, before); EXPECT_TRUE(waitFor( [&]() { return ClientW()->get("/x/s/sd1/sd1d0/sd0d0") && !access(MountPathW() / "sd1" / "sd1d0" / "sd0d0", F_OK); }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rename_replace_file_cloud_local_succeeds) { Stat sf0o; Stat sf0w; ASSERT_FALSE(stat(MountPathO() / "sf0", sf0o)); ASSERT_FALSE(stat(MountPathW() / "sf0", sf0w)); ASSERT_GE(open(MountPathW() / "sfx", O_CREAT | O_TRUNC | O_WRONLY), 0); ASSERT_FALSE(rename(MountPathW() / "sf0", MountPathW() / "sfx")); Stat sfx; ASSERT_TRUE(access(MountPathW() / "sf0", F_OK)); ASSERT_EQ(errno, ENOENT); ASSERT_FALSE(stat(MountPathW() / "sfx", sfx)); ASSERT_EQ(sf0w, sfx); EXPECT_TRUE(waitFor( [&]() { if (ClientW()->get("/x/s/sf0")) return false; if (!ClientW()->get("/x/s/sfx")) return false; return access(MountPathO() / "sf0", F_OK) && errno == ENOENT && !stat(MountPathO() / "sfx", sfx) && sf0o == sfx; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rename_replace_file_local_cloud_succeeds) { ASSERT_GE(open(MountPathW() / "sfx", O_CREAT | O_TRUNC | O_WRONLY), 0); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathO() / "sfx", F_OK); }, mDefaultTimeout)); Stat sfxo; Stat sfxw; ASSERT_FALSE(stat(MountPathO() / "sfx", sfxo)); ASSERT_FALSE(stat(MountPathW() / "sfx", sfxw)); ASSERT_FALSE(rename(MountPathW() / "sfx", MountPathW() / "sf0")); Stat sf0; ASSERT_FALSE(stat(MountPathW() / "sf0", sf0)); ASSERT_EQ(sfxw, sf0); ASSERT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && access(MountPathO() / "sfx", F_OK) && errno == ENOENT && !stat(MountPathO() / "sf0", sf0) && sfxo == sf0; }, mDefaultTimeout)); ASSERT_FALSE(unlink(MountPathW() / "sf0")); } TEST_P(FUSEPlatformTests, rename_replace_file_local_local_succeeds) { ASSERT_GE(open(MountPathW() / "sfx", O_CREAT | O_TRUNC | O_WRONLY), 0); ASSERT_GE(open(MountPathW() / "sfy", O_CREAT | O_TRUNC | O_WRONLY), 0); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathO() / "sfx", F_OK) && !access(MountPathO() / "sfy", F_OK); }, mDefaultTimeout)); Stat sfxo; Stat sfxw; ASSERT_FALSE(stat(MountPathO() / "sfx", sfxo)); ASSERT_FALSE(stat(MountPathW() / "sfx", sfxw)); ASSERT_FALSE(rename(MountPathW() / "sfx", MountPathW() / "sfy")); Stat sfy; ASSERT_FALSE(stat(MountPathW() / "sfy", sfy)); ASSERT_EQ(sfxw, sfy); ASSERT_TRUE(waitFor( [&]() { return access(MountPathO() / "sfx", F_OK) && errno == ENOENT && !stat(MountPathO() / "sfy", sfy) && sfxo == sfy; }, mDefaultTimeout)); ASSERT_FALSE(unlink(MountPathW() / "sfy")); } TEST_P(FUSEPlatformTests, rename_replace_file_succeeds) { Stat beforeO; Stat beforeW; ASSERT_FALSE(stat(MountPathO() / "sf0", beforeO)); ASSERT_FALSE(stat(MountPathW() / "sf0", beforeW)); ASSERT_FALSE(rename(MountPathW() / "sf0", MountPathW() / "sd0" / "sd0f0")); Stat after; ASSERT_FALSE(stat(MountPathW() / "sd0" / "sd0f0", after)); ASSERT_EQ(after, beforeW); EXPECT_TRUE(waitFor( [&]() { if (ClientW()->get("/x/s/sf0")) return false; if (!access(MountPathO() / "sf0", F_OK)) return false; return !stat(MountPathO() / "sd0" / "sd0f0", after) && after == beforeO; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, rmdir_fails_when_below_file) { ASSERT_LT(rmdir(MountPathW() / "sf0" / "x"), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, rmdir_fails_when_file) { ASSERT_LT(rmdir(MountPathW() / "sf0"), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, rmdir_fails_when_not_empty) { ASSERT_LT(rmdir(MountPathW() / "sd0"), 0); ASSERT_EQ(errno, ENOTEMPTY); ASSERT_EQ(access(MountPathW() / "sd0" / "sd0d0", F_OK), 0); } TEST_P(FUSEPlatformTests, rmdir_fails_when_read_only) { ASSERT_LT(rmdir(MountPathR() / "sd0"), 0); ASSERT_EQ(errno, EROFS); ASSERT_EQ(access(MountPathR() / "sd0" / "sd0d0", F_OK), 0); } TEST_P(FUSEPlatformTests, rmdir_fails_when_unknown) { ASSERT_LT(rmdir(MountPathW() / "sdx"), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, rmdir_succeeds) { ASSERT_EQ(rmdir(MountPathW() / "sd0" / "sd0d0"), 0); ASSERT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sd0/sd0d0"); // Directory should no longer be visible in the cloud. if (info.errorOr(API_OK) != API_FUSE_ENOTFOUND) return false; // Directory should no longer be visible to observer. return access(MountPathW() / "sd0" / "sd0d0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, stat_at_fails_when_below_file) { Stat buffer; auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_LT(statat(sf0, "x", buffer), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, stat_at_fails_when_unknown) { Stat buffer; auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(statat(s, "x", buffer), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, stat_at_succeeds) { auto buffer = Stat(); auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); auto info = ClientW()->get("/x/s/sd0"); ASSERT_TRUE(info); ASSERT_EQ(statat(s, "sd0", buffer), 0); EXPECT_EQ(buffer, *info); info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); ASSERT_EQ(statat(s, "sf0", buffer), 0); EXPECT_EQ(buffer, *info); } TEST_P(FUSEPlatformTests, stat_fails_when_below_file) { Stat buffer; ASSERT_LT(stat(MountPathW() / "sf0" / "x", buffer), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, stat_fails_when_unknown) { Stat buffer; ASSERT_LT(stat(MountPathW() / "x", buffer), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, stat_succeeds) { auto buffer = Stat(); auto info = ClientW()->get("/x/s/sd0"); ASSERT_TRUE(info); ASSERT_EQ(stat(MountPathW() / "sd0", buffer), 0); EXPECT_EQ(buffer, *info); info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); ASSERT_EQ(stat(MountPathW() / "sf0", buffer), 0); EXPECT_EQ(buffer, *info); } TEST_P(FUSEPlatformTests, statvfs_fails_when_below_file) { struct statvfs buffer; ASSERT_NE(statvfs(MountPathW() / "sf0" / "bogus", buffer), 0); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, statvfs_fails_when_unknown) { struct statvfs buffer; ASSERT_NE(statvfs(MountPathW() / "bogus", buffer), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, statvfs_succeeds) { struct statvfs buffer; ASSERT_EQ(statvfs(MountPathW() / "sf0", buffer), 0); EXPECT_EQ(buffer.f_bsize, BlockSize); EXPECT_EQ(buffer.f_namemax, MaxNameLength); // The MEGA API doesn't allow us to query how much storage space one of // our contact is using so testing the fields below is not meaningfulfor // shares. if (isShareTest()) return; auto info = ClientW()->storageInfo(); ASSERT_EQ(info.errorOr(API_OK), API_OK); auto available = static_cast<fsblkcnt_t>(info->mAvailable) / BlockSize; EXPECT_EQ(buffer.f_bavail, available); EXPECT_EQ(buffer.f_bfree, buffer.f_bavail); LINUX_ONLY({ auto capacity = static_cast<fsblkcnt_t>(info->mCapacity) / BlockSize; EXPECT_EQ(buffer.f_blocks, capacity); EXPECT_EQ(buffer.f_frsize, buffer.f_bsize); }) } TEST_P(FUSEPlatformTests, truncate_fails_when_below_file) { ASSERT_TRUE(truncate(MountPathW() / "sf0" / "x", 0)); ASSERT_EQ(errno, ENOTDIR); } TEST_P(FUSEPlatformTests, truncate_fails_when_directory) { ASSERT_TRUE(truncate(MountPathW() / "sd0", 0)); ASSERT_EQ(errno, EISDIR); ASSERT_FALSE(access(MountPathW() / "sd0", F_OK)); } TEST_P(FUSEPlatformTests, truncate_fails_when_read_only) { ASSERT_TRUE(truncate(MountPathR() / "sf0", 0)); ASSERT_EQ(errno, EROFS); Stat buffer; ASSERT_FALSE(stat(MountPathR() / "sf0", buffer)); ASSERT_NE(buffer.st_size, 0); } TEST_P(FUSEPlatformTests, truncate_fails_when_unknown) { ASSERT_TRUE(truncate(MountPathW() / "sfx", 0)); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, truncate_succeeds) { ASSERT_FALSE(truncate(MountPathW() / "sf0", 0)); ASSERT_FALSE(truncate(MountPathW() / "sf1", 32)); ASSERT_TRUE(waitFor( [&]() { Stat buffer; return !stat(MountPathO() / "sf0", buffer) && !buffer.st_size && !stat(MountPathO() / "sf1", buffer) && buffer.st_size == 32; }, mDefaultTimeout)); auto descriptor = open(MountPathW() / "sf0", O_RDONLY); ASSERT_FALSE(fsync(descriptor)); descriptor = open(MountPathW() / "sf1", O_RDONLY); ASSERT_FALSE(fsync(descriptor)); EXPECT_TRUE(waitFor( [&]() { auto sf0 = ClientW()->get("/x/s/sf0"); auto sf1 = ClientW()->get("/x/s/sf1"); return (sf0 && !sf0->mIsDirectory && !sf0->mSize) && (sf1 && !sf1->mIsDirectory && sf1->mSize == 32); }, mDefaultTimeout)); auto sf0 = ClientW()->get("/x/s/sf0"); auto sf1 = ClientW()->get("/x/s/sf1"); ASSERT_TRUE(sf0); ASSERT_TRUE(sf1); EXPECT_FALSE(sf0->mIsDirectory); EXPECT_FALSE(sf1->mIsDirectory); EXPECT_EQ(sf0->mSize, 0); EXPECT_EQ(sf1->mSize, 32); } TEST_P(FUSEPlatformTests, unlink_at_fails_when_below_file) { auto sf0 = open(MountPathW() / "sf0", O_PATH); ASSERT_TRUE(sf0); ASSERT_LT(unlinkat(sf0, "x", 0), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_at_fails_when_directory) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(unlinkat(s, "sd0", 0), 0); ASSERT_EQ(errno, LINUX_OR_POSIX(EISDIR, EPERM)); ASSERT_EQ(access(MountPathW() / "sd0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_at_fails_when_read_only) { auto s = open(MountPathR(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(unlinkat(s, "sf0", 0), 0); ASSERT_EQ(errno, EROFS); ASSERT_EQ(access(MountPathR() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_at_fails_when_unknown) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_LT(unlinkat(s, "sdx", 0), 0); ASSERT_EQ(errno, ENOENT); ASSERT_LT(accessat(s, "sdx", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, unlink_at_directory_succeeds) { auto sd0 = open(MountPathW() / "sd0", O_PATH); ASSERT_TRUE(sd0); ASSERT_EQ(unlinkat(sd0, "sd0d0", AT_REMOVEDIR), 0); ASSERT_TRUE(waitFor( [&]() { // File should no longer be visible in the cloud. auto info = ClientW()->get("/x/s/sd0/sd0d0"); // File's still visible in the cloud. if (info.errorOr(API_OK) != API_FUSE_ENOTFOUND) return false; // File should no longer be visible by observer. return accessat(sd0, "sd0d0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, unlink_at_file_succeeds) { auto s = open(MountPathW(), O_PATH); ASSERT_TRUE(s); ASSERT_EQ(unlinkat(s, "sf0", 0), 0); ASSERT_TRUE(waitFor( [&]() { // File should no longer be visible in the cloud. auto info = ClientW()->get("/x/s/sf0"); // File's still visible in the cloud. if (info.errorOr(API_OK) != API_FUSE_ENOTFOUND) return false; // File should no longer be visible by observer. return accessat(s, "sf0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, unlink_fails_when_below_file) { ASSERT_LT(unlink(MountPathW() / "sf0" / "x"), 0); ASSERT_EQ(errno, ENOTDIR); ASSERT_EQ(access(MountPathW() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_fails_when_directory) { ASSERT_LT(unlink(MountPathW() / "sd0"), 0); ASSERT_EQ(errno, LINUX_OR_POSIX(EISDIR, EPERM)); ASSERT_EQ(access(MountPathW() / "sd0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_fails_when_read_only) { ASSERT_LT(unlink(MountPathR() / "sf0"), 0); ASSERT_EQ(errno, EROFS); ASSERT_EQ(access(MountPathR() / "sf0", F_OK), 0); } TEST_P(FUSEPlatformTests, unlink_fails_when_unknown) { ASSERT_LT(unlink(MountPathW() / "sdx"), 0); ASSERT_EQ(errno, ENOENT); ASSERT_LT(access(MountPathW() / "sdx", F_OK), 0); ASSERT_EQ(errno, ENOENT); } TEST_P(FUSEPlatformTests, unlink_succeeds) { ASSERT_EQ(unlink(MountPathW() / "sf0"), 0); ASSERT_TRUE(waitFor( [&]() { // File should no longer be visible in the cloud. auto info = ClientW()->get("/x/s/sf0"); // File's still visible in the cloud. if (info.errorOr(API_OK) != API_FUSE_ENOTFOUND) return false; // File should no longer be visible by observer. return access(MountPathW() / "sf0", F_OK) < 0 && errno == ENOENT; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, write_fails_when_read_only) { auto sf0 = open(MountPathW() / "sf0", O_RDONLY); ASSERT_TRUE(sf0); ASSERT_LT(write(sf0.get(), "", 1), 0); ASSERT_EQ(errno, EBADF); } TEST_P(FUSEPlatformTests, write_succeeds) { auto sfx = open(MountPathW() / "sfx", O_CREAT | O_RDWR); ASSERT_TRUE(sfx); ASSERT_TRUE(waitFor( [&]() { return !access(MountPathW() / "sfx", F_OK); }, mDefaultTimeout)); auto written = randomBytes(32); ASSERT_EQ(sfx.write(&written[0], written.size()), written.size()); auto read = std::string(written.size(), '\0'); read.resize(sfx.read(&read[0], read.size(), 0)); ASSERT_EQ(read, written); auto sfxO = open(MountPathO() / "sfx", O_RDONLY); ASSERT_TRUE(sfxO); written = randomBytes(64); ASSERT_EQ(sfx.write(&written[0], written.size(), 0), written.size()); read.resize(written.size()); ASSERT_TRUE(waitFor( [&]() { return sfxO.read(&read[0], read.size(), 0) == read.size() && read == written; }, mDefaultTimeout)); ASSERT_FALSE(unlink(MountPathW() / "sfx")); } } // testing } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/printers.cpp����������������������0000664�0000000�0000000�00000003330�15162662266�0027564�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/date_time.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/testing/printers.h> #include <iomanip> #include <sstream> #include <string> struct Mode { Mode(mode_t mode): mMode(mode) {} mode_t mMode; }; // Mode std::ostream& operator<<(std::ostream& ostream, const Mode& mode); static const auto indent = std::string(6, ' '); void PrintTo(const struct dirent& entry, std::ostream* ostream) { using namespace mega::fuse; *ostream << "\n" << indent << "d_ino: " << toString(MountInodeID(entry.d_ino)) << "\n" << indent << "d_name: " << entry.d_name; } void PrintTo(const struct stat& stat, std::ostream* ostream) { using namespace mega::common; using namespace mega::fuse; *ostream << "\n" << indent << "st_ino: " << toString(MountInodeID(stat.st_ino)) << "\n" << indent << "st_mode: " << Mode(stat.st_mode) << "\n" << indent << "st_nlink: " << stat.st_nlink << "\n" << indent << "st_uid: " << stat.st_uid << "\n" << indent << "st_gid: " << stat.st_gid << "\n" << indent << "st_size: " << stat.st_size << "\n" << indent << "st_blksize: " << stat.st_blksize << "\n" << indent << "st_blocks: " << stat.st_blocks << "\n" << indent << "st_atime: " << DateTime(stat.st_atime) << "\n" << indent << "st_mtime: " << DateTime(stat.st_mtime) << "\n" << indent << "st_ctime: " << DateTime(stat.st_ctime); } std::ostream& operator<<(std::ostream& ostream, const Mode& mode) { std::ostringstream osstream; osstream << std::oct << mode.mMode; return ostream << osstream.str(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/testing/integration/wrappers.cpp����������������������0000664�0000000�0000000�00000012731�15162662266�0027566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/node_info.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/constants.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/platform.h> #include <mega/fuse/platform/testing/wrappers.h> #include <cstring> #include <fcntl.h> #include <unistd.h> bool operator==(const struct dirent& lhs, const struct dirent& rhs) { return lhs.d_ino == rhs.d_ino && !std::strcmp(lhs.d_name, rhs.d_name); } bool operator!=(const struct dirent& lhs, const struct dirent& rhs) { return !(lhs == rhs); } bool operator==(const Stat& lhs, const Stat& rhs) { return !std::memcmp(&lhs, &rhs, sizeof(lhs)); } bool operator!=(const Stat& lhs, const Stat& rhs) { return !(lhs == rhs); } namespace mega { namespace common { bool operator==(const NodeInfo& lhs, const Stat& rhs) { return fuse::operator==(lhs, rhs); } bool operator!=(const NodeInfo& lhs, const Stat& rhs) { return fuse::operator!=(lhs, rhs); } } // common namespace fuse { // Convenience. using testing::EnableIfInfoLike; using testing::id; using testing::toUint64; template<typename T> auto operator==(const T& lhs, const Stat& rhs) -> typename EnableIfInfoLike<T, bool>::type { mode_t permissions = S_IRUSR; mode_t type = S_IFREG; if (lhs.mIsDirectory) { permissions |= S_IXUSR; type = S_IFDIR; } if (lhs.mPermissions == FULL) permissions |= S_IWUSR; auto size = std::max<m_off_t>(lhs.mSize, BlockSize); auto blocks = (size + 511) / 512; constexpr auto mask = S_IRWXG | S_IRWXO | S_IRWXU; return rhs.st_blocks == blocks && LINUX_OR_POSIX(rhs.st_blksize == BlockSize, true) && rhs.st_gid == getgid() && rhs.st_ino == toUint64(id(lhs)) && (rhs.st_mode & mask) == permissions && (rhs.st_mode & S_IFMT) == type && rhs.st_mtime == lhs.mModified && rhs.st_uid == getuid(); } template bool operator==(const InodeInfo&, const Stat&); template bool operator==(const common::NodeInfo&, const Stat&); namespace testing { using namespace common::testing; using namespace platform; int access(const Path& path, int mode) { return ::access(path.string().c_str(), mode); } int accessat(const FileDescriptor& descriptor, const Path& path, int mode) { return faccessat(descriptor.get(), path.string().c_str(), mode, 0); } DirectoryIterator fdopendir(FileDescriptor descriptor) { auto* iterator = ::fdopendir(descriptor.get()); if (iterator) descriptor.release(); return DirectoryIterator(iterator); } bool flushFile(const Path& path) { auto descriptor = open(path, O_RDONLY); return !fsync(descriptor); } int fstat(const FileDescriptor& descriptor, Stat& buffer) { return ::fstat(descriptor.get(), &buffer); } int fsync(const FileDescriptor& descriptor) { return ::fsync(descriptor.get()); } int ftruncate(const FileDescriptor& descriptor, off_t length) { return ::ftruncate(descriptor.get(), length); } int futimes(const FileDescriptor& descriptor, struct timeval (×)[2]) { return ::futimes(descriptor.get(), times); } int mkdir(const Path& path, mode_t mode) { return ::mkdir(path.string().c_str(), mode); } int mkdirat(const FileDescriptor& descriptor, const Path& path, mode_t mode) { return ::mkdirat(descriptor.get(), path.string().c_str(), mode); } FileDescriptor open(const Path& path, int flags) { auto descriptor = ::open(path.string().c_str(), flags); return FileDescriptor(descriptor); } FileDescriptor open(const Path& path, int flags, mode_t mode) { auto descriptor = ::open(path.string().c_str(), flags, mode); return FileDescriptor(descriptor); } FileDescriptor openat(const FileDescriptor& descriptor, const Path& path, int flags) { auto descriptor_ = ::openat(descriptor.get(), path.string().c_str(), flags); return FileDescriptor(descriptor_); } FileDescriptor openat(const FileDescriptor& descriptor, const Path& path, int flags, mode_t mode) { auto descriptor_ = ::openat(descriptor.get(), path.string().c_str(), flags, mode); return FileDescriptor(descriptor_); } DirectoryIterator opendir(const Path& path) { return DirectoryIterator(::opendir(path.string().c_str())); } int rename(const Path& before, const Path& after) { return ::rename(before.string().c_str(), after.string().c_str()); } int renameat(const FileDescriptor& sourceParent, const Path& sourcePath, const FileDescriptor& targetParent, const Path& targetPath) { return ::renameat(sourceParent.get(), sourcePath.string().c_str(), targetParent.get(), targetPath.string().c_str()); } int rmdir(const Path& path) { return ::rmdir(path.string().c_str()); } int stat(const Path& path, Stat& buffer) { return ::stat(path.string().c_str(), &buffer); } int statat(const platform::FileDescriptor& descriptor, const Path& path, Stat& buffer) { return ::fstatat(descriptor.get(), path.string().c_str(), &buffer, 0); } int statvfs(const Path& path, struct statvfs& buffer) { return ::statvfs(path.string().c_str(), &buffer); } int truncate(const Path& path, off_t length) { return ::truncate(path.string().c_str(), length); } int unlink(const Path& path) { return ::unlink(path.string().c_str()); } int unlinkat(const FileDescriptor& descriptor, const Path& path, int flags) { return ::unlinkat(descriptor.get(), path.string().c_str(), flags); } } // testing } // fuse } // mega ���������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/unmounter.cpp�����������������������������������������0000664�0000000�0000000�00000000524�15162662266�0023754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/unmounter.h> #include <mega/fuse/platform/utility.h> namespace mega { namespace fuse { namespace platform { MountResult Unmounter::unmount(Mount&, const std::string& path, bool abort) { return platform::unmount(path, abort); } } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/posix/utility.cpp�������������������������������������������0000664�0000000�0000000�00000007626�15162662266�0023435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/utility.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/file_descriptor.h> #include <mega/fuse/platform/utility.h> #include <cerrno> #include <cstring> #include <fcntl.h> #include <unistd.h> namespace mega { namespace fuse { namespace platform { FileDescriptorPair pipe(bool closeReaderOnFork, bool closeWriterOnFork) { int descriptors[2]; // Try and create our pipe. auto result = ::pipe(descriptors); // Couldn't create the pipe. if (result < 0) throw FUSEErrorF("Unable to create pipe: %s", std::strerror(errno)); FileDescriptor r(descriptors[0], closeReaderOnFork); FileDescriptor w(descriptors[1], closeWriterOnFork); // Return pipe to caller. return std::make_pair(std::move(r), std::move(w)); } void nonblocking(int descriptor, bool enabled) { // Sanity. assert(descriptor >= 0); // Try and get the descriptor's current status flags. auto flags = fcntl(descriptor, F_GETFL); // Couldn't get the descriptor's status flags. if (flags < 0) throw FUSEErrorF("Couldn't get descriptor status flags: %d: %s", descriptor, std::strerror(errno)); // Assume the user wants to enable nonblocking operation. flags |= O_NONBLOCK; // User really wants to disable nonblocking operation. if (!enabled) flags &= ~O_NONBLOCK; // Couldn't update the descriptor's status flags. if (fcntl(descriptor, F_SETFL, flags) < 0) throw FUSEErrorF("Couldn't update descriptor status flags: %d: %s", descriptor, std::strerror(errno)); } void translate(struct stat& attributes, MountInodeID id, const InodeInfo& info) { std::memset(&attributes, 0, sizeof(attributes)); // Raw permissions for this entity. // // Directories use these permissions verbatim. // Files mask these permission as they are never executable. const mode_t permissions = info.mPermissions == FULL ? 0700 : 0500; attributes.st_atime = static_cast<time_t>(info.mModified); attributes.st_blksize = BlockSize; attributes.st_ctime = attributes.st_atime; attributes.st_gid = getgid(); attributes.st_ino = id.get(); attributes.st_mode = S_IFDIR | permissions; attributes.st_mtime = attributes.st_atime; attributes.st_nlink = 1; attributes.st_size = static_cast<off_t>(info.mSize); attributes.st_uid = getuid(); // All files require at least a single "block." auto size = std::max<off_t>(BlockSize, attributes.st_size); // Always align size to a 512B boundary. attributes.st_blocks = (size + 511) / 512; if (info.mIsDirectory) return; // Files are never executable. attributes.st_mode = S_IFREG | (permissions & 0600); } void translate(fuse_entry_param& entry, MountInodeID id, const InodeInfo& info) { entry.attr_timeout = AttributeTimeout; entry.entry_timeout = EntryTimeout; entry.generation = 0; entry.ino = id.get(); translate(entry.attr, id, info); } int translate(Error result) { switch (result) { case API_EACCESS: return EROFS; case API_EEXIST: return EEXIST; case API_ENOENT: return ENOENT; case API_FUSE_EBADF: return EBADF; case API_FUSE_EISDIR: return EISDIR; case API_FUSE_ENAMETOOLONG: return ENAMETOOLONG; case API_FUSE_ENOTDIR: return ENOTDIR; case API_FUSE_ENOTEMPTY: return ENOTEMPTY; case API_FUSE_EPERM: return EPERM; case API_FUSE_EROFS: return EROFS; case API_OK: return 0; default: break; } return EIO; } } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/service.cpp�������������������������������������������������0000664�0000000�0000000�00000000224�15162662266�0022213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/service.h> namespace mega { namespace fuse { bool Service::supported() const { return true; } } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/service_context.cpp�����������������������������������������0000664�0000000�0000000�00000014174�15162662266�0023770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/utility.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/database_builder.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/common/service.h> #include <mega/fuse/platform/service_context.h> #include <algorithm> #include <cassert> namespace mega { namespace fuse { namespace platform { using namespace common; static Database dbInit(const Client& client); static MountResult dbOperation(void (DatabaseBuilder::*op)(std::size_t), const LocalPath& path, const std::size_t target); static LocalPath dbPath(const Client& client); ServiceContext::ServiceContext(const ServiceFlags& flags, Service& service): fuse::ServiceContext(service), mDatabase(dbInit(service.mClient)), mExecutor(flags.mServiceExecutorFlags, logger()), mFileExtensionDB(), mInodeDB(*this), mFileCache(*this), mInodeCache(flags.mInodeCacheFlags), mUnmounter(*this), mMountDB(*this) { // Inject InodeDB as an event observer. service.mClient.addEventObserver(mInodeDB); // Prune lingering transient mounts from the database. mMountDB.prune(); } ServiceContext::~ServiceContext() { // Detach InodeDB as event observer. client().removeEventObserver(mInodeDB); // Tear down any enabled mounts. mMountDB.deinitialize(); // Cancel any pending uploads. mFileCache.cancel(); // Wait for all inodes to be purged from memory. mInodeDB.clear(); } MountResult ServiceContext::add(const MountInfo& info) { return mMountDB.add(info); } bool ServiceContext::cached(NormalizedPath path) const { LocalPath relativePath; // What mount contains this path? auto mount = mMountDB.contains(path, false, &relativePath); // No mount contains this path. if (!mount) return false; // Try to locate the inode associated with this path. auto result = mInodeDB.lookup(relativePath, mount->mHandle); // Couldn't find the inode. if (result.second != API_OK) return false; // Let the caller know whether the inode is cached or not. return result.first->cached(); } void ServiceContext::current() { mMountDB.current(); } ErrorOr<InodeInfo> ServiceContext::describe(const NormalizedPath& path) const { LocalPath relativePath; // What mount contains this path? auto mount = mMountDB.contains(path, true, &relativePath); // No enabled mount contains this path. if (!mount) return unexpected(API_ENOENT); // Try and locate the inode associated with this path. auto result = mInodeDB.lookup(relativePath, mount->mHandle); // We couldn't find the inode. if (result.second != API_OK) return unexpected(result.second); // Retrieve the inode's description. auto info = result.first->info(); // Adjust permissions as necessary. if (mount->mFlags.mReadOnly) info.mPermissions = RDONLY; // Return description to caller. return info; } void ServiceContext::disable(MountDisabledCallback callback, const std::string& name, bool remember) { mMountDB.disable(std::move(callback), name, remember); } MountResult ServiceContext::discard(bool discard) { // Let the inode DB know whether it should process node events. mInodeDB.discard(discard); // Let the caller know the discard was completed. return MOUNT_SUCCESS; } MountResult ServiceContext::downgrade(const LocalPath& path, std::size_t target) { return dbOperation(&DatabaseBuilder::downgrade, path, target); } MountResult ServiceContext::enable(const std::string& name, bool remember) { return mMountDB.enable(name, remember); } bool ServiceContext::enabled(const std::string& name) const { return mMountDB.enabled(name); } Task ServiceContext::execute(std::function<void(const Task&)> function) { return mExecutor.execute(std::move(function), true); } MountResult ServiceContext::flags(const std::string& name, const MountFlags& flags) { return mMountDB.flags(name, flags); } MountFlagsPtr ServiceContext::flags(const std::string& name) const { return mMountDB.flags(name); } FileSystemAccess& ServiceContext::fsAccess() const { return client().fsAccess(); } MountInfoPtr ServiceContext::get(const std::string& name) const { return mMountDB.get(name); } MountInfoVector ServiceContext::get(bool onlyEnabled) const { return mMountDB.get(onlyEnabled); } NormalizedPath ServiceContext::path(const std::string& name) const { return mMountDB.path(name); } MountResult ServiceContext::remove(const std::string& name) { return mMountDB.remove(name); } void ServiceContext::serviceFlags(const ServiceFlags& flags) { // Update the inode cache's flags. mInodeCache.flags(flags.mInodeCacheFlags); // Update executor flags for existing mounts. mMountDB.executorFlags(flags.mMountExecutorFlags); // Update file explorer view flags mMountDB.fileExplorerView(flags.mFileExplorerView); } bool ServiceContext::syncable(const NormalizedPath& path) const { return mMountDB.syncable(path); } void ServiceContext::updated(NodeEventQueue& events) { mInodeDB.updated(events); } MountResult ServiceContext::upgrade(const LocalPath& path, std::size_t target) { return dbOperation(&DatabaseBuilder::upgrade, path, target); } Database dbInit(const Client& client) { Database database(logger(), dbPath(client)); DatabaseBuilder(database).build(); return database; } MountResult dbOperation(void (DatabaseBuilder::*op)(std::size_t), const LocalPath& path, const std::size_t target) try { assert(op); Database database(logger(), path); DatabaseBuilder builder(database); (builder.*op)(target); return MOUNT_SUCCESS; } catch (...) { return MOUNT_UNEXPECTED; } LocalPath dbPath(const Client& client) { return client.dbPath(format("fuse00_%s", client.sessionID().c_str())); } } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/����������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021526�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/CMakeLists.txt��������������������������������������0000664�0000000�0000000�00000000036�15162662266�0024265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(integration) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/����������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024051�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/CMakeLists.txt��������������������������0000664�0000000�0000000�00000000334�15162662266�0026611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if(NOT TARGET test_integration) return() endif() add_subdirectory(mega) target_include_directories(test_integration PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(test_integration PRIVATE platform_tests.cpp) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/�����������������������������������0000775�0000000�0000000�00000000000�15162662266�0024762�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/CMakeLists.txt���������������������0000664�0000000�0000000�00000000027�15162662266�0027521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/������������������������������0000775�0000000�0000000�00000000000�15162662266�0025724�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/CMakeLists.txt����������������0000664�0000000�0000000�00000000033�15162662266�0030460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/platform/���������������������0000775�0000000�0000000�00000000000�15162662266�0027550�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/platform/CMakeLists.txt�������0000664�0000000�0000000�00000000032�15162662266�0032303�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(testing) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/platform/testing/�������������0000775�0000000�0000000�00000000000�15162662266�0031225�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CMakeLists.txt��������������������������������������������������������������������������������������0000664�0000000�0000000�00000000072�15162662266�0033705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/platform/testing���������������������������������������������������������������������������������target_sources(test_integration PRIVATE platform_tests.h) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������platform_tests.h������������������������������������������������������������������������������������0000664�0000000�0000000�00000002265�15162662266�0034372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/testing/integration/mega/fuse/platform/testing���������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/testing/parameters.h> #include <mega/fuse/common/testing/test_base.h> namespace mega { namespace fuse { namespace testing { struct FUSEPlatformTests: TestBase, ::testing::WithParamInterface<Parameters> { #define DEFINE_CLIENT_ACCESSOR(accessor, name) \ static ClientPtr& Client##name() \ { \ return (*GetParam().mClients.m##accessor)(); \ } DEFINE_CLIENT_ACCESSOR(ReadOnly, RS); DEFINE_CLIENT_ACCESSOR(ReadWrite, WS); #undef DEFINE_CLIENT_ACCESSOR #define DEFINE_MOUNT_PATH_ACCESSOR(accessor, name) \ static const common::testing::Path& MountPath##name() \ { \ return (*GetParam().mPaths.m##accessor)(); \ } DEFINE_MOUNT_PATH_ACCESSOR(Observer, O); DEFINE_MOUNT_PATH_ACCESSOR(ReadOnly, R); DEFINE_MOUNT_PATH_ACCESSOR(ReadWrite, W); #undef DEFINE_MOUNT_PATH_ACCESSOR // Perform instance-specific setup. void SetUp() override { ASSERT_TRUE(DoSetUp(GetParam())); } // Are we performing a test with shares? bool isShareTest() const { return GetParam().mClients.mReadWrite == &Test::ClientS; } }; // FUSEPlatformTests } // testing } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/testing/integration/platform_tests.cpp����������������������0000664�0000000�0000000�00000000677�15162662266�0027635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/testing/platform_tests.h> namespace mega { namespace fuse { namespace testing { using ::testing::TestParamInfo; using ::testing::Values; INSTANTIATE_TEST_SUITE_P( , FUSEPlatformTests, Values(SHARED_UNVERSIONED, SHARED_VERSIONED, STANDARD_UNVERSIONED, STANDARD_VERSIONED), [](const TestParamInfo<Parameters>& info) { return toString(info.param); }); } // testing } // fuse } // mega �����������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/unmounter.cpp�����������������������������������������������0000664�0000000�0000000�00000006775�15162662266�0022630�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_executor.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/unmounter.h> #include <mega/fuse/platform/utility.h> namespace mega { namespace fuse { namespace platform { using namespace common; void Unmounter::emitEvent(MountDisabledCallback callback, const std::string& name, MountResult result) { // Convenience. auto& client = mContext.client(); // Emit event. if (result != MOUNT_SUCCESS) { MountEvent event; event.mName = name; event.mResult = result; event.mType = MOUNT_DISABLED; fuse::emitEvent(client, event); } // Forward result to user callback. auto wrapper = [result](MountDisabledCallback& callback, const Task&) { callback(result); }; // wrapper // Queue callback for execution. client.execute(std::bind(std::move(wrapper), std::move(callback), std::placeholders::_1)); } void Unmounter::unmount(MountDisabledCallback callback, MountWeakPtr mount, const std::string& name, const LocalPath& path) { FUSEDebugF("Attempting to unmount mount: %s", name.c_str()); auto mount_ = mount.lock(); if (!mount_) { FUSEDebugF("Mount no longer exists: %s", name.c_str()); return emitEvent(std::move(callback), name, MOUNT_UNKNOWN); } auto result = this->unmount(*mount_, path.toPath(false), false); if (result != MOUNT_SUCCESS) return emitEvent(std::move(callback), name, result); auto disabled = mount_->disabled(); mount_.reset(); disabled.get(); FUSEDebugF("Mount %s has been unmounted", name.c_str()); emitEvent(std::move(callback), name, MOUNT_SUCCESS); } Unmounter::Unmounter(platform::ServiceContext& context): mActivities(), mContext(context) { FUSEDebug1("Unmounter constructed"); } Unmounter::~Unmounter() { FUSEDebug1("Unmounter destroyed"); } void Unmounter::unmount(MountDisabledCallback callback, MountPtr mount) { auto wrapper = [this](Activity&, MountDisabledCallback& callback, MountWeakPtr mount, const std::string& name, const LocalPath& path, const Task& task) { // Don't bother unmounting as we're being torn down. if (task.cancelled()) return emitEvent(std::move(callback), name, MOUNT_ABORTED); // Try and unmount the specified mount. unmount(std::move(callback), std::move(mount), name, path); }; // wrapper auto name = mount->name(); FUSEDebugF("Queuing unmount of mount %s", name.c_str()); // Queue unmount for execution. mContext.mExecutor.execute(std::bind(std::move(wrapper), mActivities.begin(), std::move(callback), MountWeakPtr(mount), std::move(name), mount->path(), std::placeholders::_1), true); } } // platform } // fuse } // mega ���sdk-10.11.0/src/fuse/supported/platform/windows/����������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021543�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/CMakeLists.txt��������������������������������������0000664�0000000�0000000�00000002054�15162662266�0024304�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Let CMake know where it can find our FindWinFsP module. list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # Make sure WinFSP is present. find_package(WinFSP REQUIRED) # Make sure the SDK links against WinFSP. target_link_libraries(SDKlib PUBLIC WinFSP) # Let the SDK know where it can find our headers. target_include_directories(SDKlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # Add sources required by WinFSP backend. target_sources(SDKlib PRIVATE constants.cpp directory_context.cpp dispatcher.cpp file_explorer_setter.cpp local_pointer.cpp mount.cpp mount_db.cpp security_descriptor.cpp security_identifier.cpp service.cpp shell.cpp unmounter.cpp utility.cpp ) # Add WinFSP backend headers. add_subdirectory(mega) # Add WinFSP tests. add_subdirectory(testing) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/cmake/����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022623�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/cmake/FindWinFSP.cmake������������������������������0000664�0000000�0000000�00000003137�15162662266�0025540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Assume the user hasn't specified where to find WinFSP. if (NOT DEFINED WinFSP_PREFIX) # Search WinFSP's default installation path. set(WinFSP_PREFIX "C:/Program Files (x86)/WinFSP" CACHE PATH "The path where WinFSP is installed" FORCE) endif() find_path( WinFSP_INCLUDE_DIR winfsp.h HINTS ${WinFSP_PREFIX} PATH_SUFFIXES inc/winfsp ) # Assume we're targeting 64bit machines. set(WinFSP_LIBRARY_SUFFIX x64) # Really targeting 32bit machines. if (CMAKE_VS_PLATFORM_NAME MATCHES "Win32") set(WinFSP_LIBRARY_SUFFIX x86) endif() set(WinFSP_LIBRARY_NAME "winfsp-${WinFSP_LIBRARY_SUFFIX}") find_library( WinFSP_LIBRARY ${WinFSP_LIBRARY_NAME}.lib HINTS ${WinFSP_PREFIX} PATH_SUFFIXES lib ) if (WinFSP_INCLUDE_DIR AND WinFSP_LIBRARY) add_library(WinFSP STATIC IMPORTED) set_target_properties( WinFSP PROPERTIES IMPORTED_LOCATION ${WinFSP_LIBRARY} ) if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.20.0") cmake_path(GET WinFSP_INCLUDE_DIR PARENT_PATH WinFSP_INCLUDE_DIRS) else() get_filename_component(WinFSP_INCLUDE_DIRS "${WinFSP_INCLUDE_DIR}" DIRECTORY) endif() target_include_directories(WinFSP INTERFACE ${WinFSP_INCLUDE_DIRS}) target_link_libraries(WinFSP INTERFACE delayimp) target_link_options(WinFSP INTERFACE /DELAYLOAD:${WinFSP_LIBRARY_NAME}.dll) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args( WinFSP REQUIRED_VARS WinFSP_INCLUDE_DIR WinFSP_LIBRARY ) mark_as_advanced(WinFSP_INCLUDE_DIR WinFSP_LIBRARY) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/constants.cpp���������������������������������������0000664�0000000�0000000�00000000262�15162662266�0024263�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/platform/constants.h> namespace mega { namespace fuse { namespace platform { const std::wstring UNCPrefix = L"\\MEGA\\"; } // platform } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/directory_context.cpp�������������������������������0000664�0000000�0000000�00000010165�15162662266�0026022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/platform/directory_context.h> #include <mega/fuse/platform/utility.h> namespace mega { namespace fuse { namespace platform { void DirectoryContext::populate() const { std::lock_guard<std::mutex> guard(mLock); // Have already retrieved this directory's children. if (mPopulated) return; // Who are this directory's children? mChildren = mDirectory->children(); // Only non-root directories report uplinks. if (!mIsRoot) { // Add link to self. mChildrenByName.emplace_back(".", mChildren.size()); mChildren.emplace_back(mDirectory); // Add link to parent. mChildrenByName.emplace_back("..", mChildren.size()); mChildren.emplace_back(mDirectory->parent()); } // Remember where legitimate children begin. mOffset = mChildrenByName.size(); // Add mappings for legitimate children. for (auto m = 0u; m < mChildren.size() - mOffset; ++m) mChildrenByName.emplace_back(mChildren[m]->name(), m); // Sort children. auto less = [](const NameIndexPair& lhs, const NameIndexPair& rhs) { return lhs.first < rhs.first; }; // less std::sort(mChildrenByName.begin() + mOffset, mChildrenByName.end(), std::move(less)); // Children have been retrieved. mPopulated = true; } DirectoryContext::DirectoryContext(DirectoryInodeRef directory, fuse::Mount& mount, bool isRoot): Context(mount), mChildren(), mChildrenByName(), mDirectory(std::move(directory)), mLock(), mOffset(0), mIsRoot(isRoot), mPopulated(false) { FUSEDebugF("Directory Context %s created", toString(mDirectory->id()).c_str()); } DirectoryContext::~DirectoryContext() { FUSEDebugF("Directory Context %s destroyed", toString(mDirectory->id()).c_str()); } DirectoryContext* DirectoryContext::directory() { return this; } InodeRef DirectoryContext::get(const std::string& name) const { return mDirectory->get(name); } void DirectoryContext::get(const std::string& marker, PVOID buffer, ULONG length, const Mount& mount, ULONG& numWritten) const { // Populate children if necessary. populate(); // Assume that we're listing all children. auto i = mChildrenByName.begin(); auto j = i; auto k = mChildrenByName.end(); // Caller's continuing a previous listing. if (!marker.empty()) { auto less = [](const std::string& lhs, const NameIndexPair& rhs) { return lhs < rhs.first; }; // less j = std::upper_bound(i, k, marker, std::move(less)); } // Temporary storage. std::vector<std::uint8_t> storage; // Populate buffer. for (; j != k; ++j) { // Get a reference to the current child. auto& child = mChildren[j->second]; // Compute the child's position in the list of entries. auto index = static_cast<std::size_t>(j - i); // Child's been removed. if (!child || child->removed()) { // This directory or its parent no longer exists. if (index < mOffset) { numWritten = 0; break; } // Check the next child. continue; } // Latch this child's description. auto info = child->info(); // Child no longer exists below this directory. if (index >= mOffset && info.mParentID != mDirectory->id()) continue; // Try and add this child's description to the buffer. if (!FspFileSystemAddDirInfo(translate(storage, mount, j->first, info), buffer, length, &numWritten)) return; } // No further children to describe. FspFileSystemAddDirInfo(nullptr, buffer, length, &numWritten); } InodeRef DirectoryContext::inode() const { return mDirectory; } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/dispatcher.cpp��������������������������������������0000664�0000000�0000000�00000064337�15162662266�0024412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/normalized_path.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/dispatcher.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/utility.h> #include <cassert> #include <cstring> namespace mega { namespace fuse { namespace platform { using namespace common; static Dispatcher& dispatcher(FSP_FILE_SYSTEM& filesystem); static void logAccess(const char* function, UINT32 mask); static void logAttributes(const char* function, UINT32 mask); static void logOptions(const char* function, UINT32 mask); const FSP_FILE_SYSTEM_INTERFACE Dispatcher::mOperations = { /* GetVolumeInfo */ &Dispatcher::getVolumeInfo, /* SetVolumeLabel */ nullptr, /* GetSecurityByName */ &Dispatcher::getSecurityByName, /* Create */ &Dispatcher::create, /* Open */ &Dispatcher::open, /* Overwrite */ &Dispatcher::overwrite, /* Cleanup */ &Dispatcher::cleanup, /* Close */ &Dispatcher::close, /* Read */ &Dispatcher::read, /* Write */ &Dispatcher::write, /* Flush */ &Dispatcher::flush, /* GetFileInfo */ &Dispatcher::getFileInfo, /* SetBasicInfo */ &Dispatcher::setBasicInfo, /* SetFileSize */ &Dispatcher::setFileSize, /* CanDelete */ &Dispatcher::canDelete, /* Rename */ &Dispatcher::rename, /* GetSecurity */ &Dispatcher::getSecurity, /* SetSecurity */ &Dispatcher::setSecurity, /* ReadDirectory */ &Dispatcher::readDirectory, /* ResolveReparsePoints */ nullptr, /* GetReparsePoint */ nullptr, /* SetReparsePoint */ nullptr, /* DeleteReparsePoint */ nullptr, /* GetStreamInfo */ nullptr, /* GetDirInfoByName */ &Dispatcher::getDirInfoByName, /* Control */ nullptr, /* SetDelete */ nullptr, /* CreateEx */ nullptr, /* OverwriteEx */ nullptr, /* GetEa */ nullptr, /* SetEa */ nullptr, /* Obsolete0 */ nullptr, /* DispatcherStopped */ nullptr, /* Reserved00 */ nullptr, /* Reserved01 */ nullptr, /* Reserved02 */ nullptr, /* Reserved03 */ nullptr, /* Reserved04 */ nullptr, /* Reserved05 */ nullptr, /* Reserved06 */ nullptr, /* Reserved07 */ nullptr, /* Reserved08 */ nullptr, /* Reserved09 */ nullptr, /* Reserved0a */ nullptr, /* Reserved0b */ nullptr, /* Reserved0c */ nullptr, /* Reserved0d */ nullptr, /* Reserved0e */ nullptr, /* Reserved0f */ nullptr, /* Reserved10 */ nullptr, /* Reserved11 */ nullptr, /* Reserved12 */ nullptr, /* Reserved13 */ nullptr, /* Reserved14 */ nullptr, /* Reserved15 */ nullptr, /* Reserved16 */ nullptr, /* Reserved17 */ nullptr, /* Reserved18 */ nullptr, /* Reserved19 */ nullptr, /* Reserved1a */ nullptr, /* Reserved1b */ nullptr, /* Reserved1c */ nullptr, /* Reserved1d */ nullptr, /* Reserved1e */ nullptr}; // mOperations NTSTATUS Dispatcher::canDelete(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path) { assert(filesystem); assert(context); assert(path); auto& dispatcher = platform::dispatcher(*filesystem); auto path_ = normalize(path); FUSEDebugF("canDelete: context: %p, path: %s", context, fromWideString(path_).c_str()); auto result = dispatcher.mMount.canDelete(context); FUSEDebugF("canDelete: context: %p, result: %lu", context, result); return result; } void Dispatcher::cleanup(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path, ULONG flags) { assert(filesystem); assert(context); auto& dispatcher = platform::dispatcher(*filesystem); std::wstring path_; if (path) path_ = normalize(&path[1]); FUSEDebugF("cleanup: context: %p, path: %s, flags: %lx", context, path_.empty() ? "NULL" : fromWideString(path_).c_str(), flags); dispatcher.mMount.cleanup(context, path_, flags); } void Dispatcher::close(FSP_FILE_SYSTEM* filesystem, PVOID context) { assert(filesystem); assert(context); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("close: context: %p", context); dispatcher.mMount.close(context); } NTSTATUS Dispatcher::create(FSP_FILE_SYSTEM* filesystem, PWSTR path, UINT32 options, UINT32 access, UINT32 attributes, [[maybe_unused]] PSECURITY_DESCRIPTOR descriptor, UINT64, PVOID* context, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(path); assert(descriptor); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); auto path_ = normalize(&path[1]); FUSEDebugF("create: path: %s", fromWideString(path_).c_str()); logAccess("create", access); logAttributes("create", attributes); logOptions("create", options); auto result = dispatcher.mMount.create(path_, options, access, *context, *info); FUSEDebugF("create: result: %lu", result); return result; } NTSTATUS Dispatcher::flush(FSP_FILE_SYSTEM* filesystem, PVOID context, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("flush: context: %p, info: %p", context, info); auto result = dispatcher.mMount.flush(context, *info); FUSEDebugF("flush: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::getDirInfoByName(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path, FSP_FSCTL_DIR_INFO* info) { assert(filesystem); assert(context); assert(path); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); auto path_ = normalize(&path[1]); FUSEDebugF("getDirInfoByName: context: %p, path: %s, info: %p", context, fromWideString(path_).c_str(), info); auto result = dispatcher.mMount.getDirInfoByName(context, path_, *info); FUSEDebugF("getDirInfoByName: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::getFileInfo(FSP_FILE_SYSTEM* filesystem, PVOID context, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("getFileInfo: context: %p, info: %p", context, info); auto result = dispatcher.mMount.getFileInfo(context, *info); FUSEDebugF("getFileInfo: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::getSecurity(FSP_FILE_SYSTEM* filesystem, PVOID context, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength) { assert(filesystem); assert(context); assert(descriptor); assert(descriptorLength); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("getSecurity: context: %p", context); auto result = dispatcher.mMount.getSecurity(context, descriptor, *descriptorLength); FUSEDebugF("getSecurity: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::getSecurityByName(FSP_FILE_SYSTEM* filesystem, PWSTR path, PUINT32 attributes, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength) { assert(filesystem); assert(path); assert(!!descriptor == !!descriptorLength); auto& dispatcher = platform::dispatcher(*filesystem); auto path_ = normalize(&path[1]); FUSEDebugF("getSecurityByName: path: %s", fromWideString(path_).c_str()); auto result = dispatcher.mMount.getSecurityByName(path_, attributes, descriptor, descriptorLength); FUSEDebugF("getSecurityByName: path: %s, result: %lu", fromWideString(path_).c_str(), result); return result; } NTSTATUS Dispatcher::getVolumeInfo(FSP_FILE_SYSTEM* filesystem, FSP_FSCTL_VOLUME_INFO* info) { assert(filesystem); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebug1("getVolumeInfo"); return dispatcher.mMount.getVolumeInfo(*info); } NTSTATUS Dispatcher::open(FSP_FILE_SYSTEM* filesystem, PWSTR path, UINT32 options, UINT32 access, PVOID* context, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(path); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); auto& mount = dispatcher.mMount; auto path_ = normalize(&path[1]); FUSEDebugF("open: path: %s", fromWideString(path_).c_str()); logAccess("open", access); logOptions("open", options); auto result = mount.open(path_, options, access, *context, *info); FUSEDebugF("open: context: %p, path: %s, result: %lu", *context, fromWideString(path_).c_str(), result); return result; } NTSTATUS Dispatcher::overwrite(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT32 attributes, BOOLEAN replaceAttributes, UINT64 allocation, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("overwrite: allocation: %lu, attributes: %lx, context: %p, replace: %u", allocation, attributes, context, replaceAttributes); auto result = dispatcher.mMount.overwrite(context, *info); FUSEDebugF("overwrite: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::read(FSP_FILE_SYSTEM* filesystem, PVOID context, PVOID buffer, UINT64 offset, ULONG length, PULONG numRead) { assert(filesystem); assert(context); assert(buffer); assert(numRead); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("read: buffer: %p, context: %p, offset: %lu, size: %lu", buffer, context, offset, length); auto result = dispatcher.mMount.read(context, buffer, offset, length, *numRead); FUSEDebugF("read: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::readDirectory(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR pattern, PWSTR marker, PVOID buffer, ULONG length, PULONG numWritten) { assert(filesystem); assert(context); assert(buffer); assert(numWritten); auto& dispatcher = platform::dispatcher(*filesystem); std::string marker_; std::string pattern_ = "*"; if (marker) marker_ = fromWideString(marker); if (pattern) pattern_ = fromWideString(pattern); FUSEDebugF("readDirectory: context: %p, length: %lu marker: %s, pattern: %s", context, length, marker_.c_str(), pattern_.c_str()); auto result = dispatcher.mMount.readDirectory(context, pattern_, marker_, buffer, length, *numWritten); FUSEDebugF("readDirectory: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::rename(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR sourcePath, PWSTR targetPath, BOOLEAN replace) { assert(filesystem); assert(context); assert(sourcePath); assert(targetPath); auto& dispatcher = platform::dispatcher(*filesystem); auto source_ = normalize(&sourcePath[1]); auto target_ = normalize(&targetPath[1]); FUSEDebugF("rename: context: %p, replace: %u, source: %s, target: %s", context, replace, fromWideString(source_).c_str(), fromWideString(target_).c_str()); auto result = dispatcher.mMount.rename(context, target_, replace); FUSEDebugF("rename: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::setBasicInfo(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT32 attributes, UINT64 created, UINT64 accessed, UINT64 written, UINT64 changed, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("setBasicInfo: context: %p", context); auto result = dispatcher.mMount .setBasicInfo(context, attributes, created, accessed, written, changed, *info); FUSEDebugF("setBasicInfo: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::setFileSize(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT64 size, BOOLEAN allocated, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("setFileSize: allocated: %u, context: %p, size: %lu", allocated, context, size); auto result = dispatcher.mMount.setFileSize(context, size, allocated, *info); FUSEDebugF("setFileSize: context: %p, result: %lu", context, result); return result; } NTSTATUS Dispatcher::setSecurity(FSP_FILE_SYSTEM* filesystem, PVOID context, SECURITY_INFORMATION security, PSECURITY_DESCRIPTOR descriptor) { assert(filesystem); assert(context); assert(descriptor); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("setSecurity: context: %p, security: %lx", context, security); auto result = dispatcher.mMount.setSecurity(context, security, descriptor); FUSEDebugF("setSecurity: context: %p, result: %lu", context, result); return result; } void Dispatcher::stopped(FSP_FILE_SYSTEM* filesystem, BOOLEAN normally) { assert(filesystem); dispatcher(*filesystem).mMount.stopped(normally); } NTSTATUS Dispatcher::write(FSP_FILE_SYSTEM* filesystem, PVOID context, PVOID buffer, UINT64 offset, ULONG length, BOOLEAN append, BOOLEAN noGrow, PULONG numWritten, FSP_FSCTL_FILE_INFO* info) { assert(filesystem); assert(context); assert(buffer); assert(numWritten); assert(info); auto& dispatcher = platform::dispatcher(*filesystem); FUSEDebugF("write: buffer: %p, context: %p, offset: %lu, size: %lu", buffer, context, offset, length); auto result = dispatcher.mMount .write(context, buffer, offset, length, append, noGrow, *numWritten, *info); FUSEDebugF("write: context: %p, result: %lu", context, result); return result; } Dispatcher::Dispatcher(Mount& mount, const NormalizedPath& path): mFilesystem(nullptr), mMount(mount), mPath() { FSP_FSCTL_VOLUME_PARAMS parameters; // Make sure parameters are in a well-defined state. std::memset(¶meters, 0, sizeof(parameters)); // Populate parameters. parameters.CasePreservedNames = true; parameters.CaseSensitiveSearch = true; parameters.FlushAndPurgeOnCleanup = true; parameters.MaxComponentLength = MaxNameLength; parameters.PersistentAcls = true; parameters.ReadOnlyVolume = !mount.writable(); parameters.SectorSize = 512; parameters.FileInfoTimeout = 128; parameters.SectorsPerAllocationUnit = BlockSize / parameters.SectorSize; parameters.UmFileContextIsUserContext2 = true; parameters.UnicodeOnDisk = true; // Assume we're mounting as as "disk-based" filesystem. std::wstring type = L"" FSP_FSCTL_DISK_DEVICE_NAME; // Actually mounting as a "network" filesystem. if (path.empty() || path.isRootPath()) { // Compute UNC prefix. auto prefix = UNCPrefix + toWideString(mount.name()); // Sanity. assert(prefix.size() <= MaxVolumePrefixLength); // Populate UNC prefix. std::wmemcpy(parameters.Prefix, prefix.data(), prefix.size()); // Mount as a "network filesystem." type = L"" FSP_FSCTL_NET_DEVICE_NAME; } // Try and create the filesystem. auto result = FspFileSystemCreate(&type[0], ¶meters, &mOperations, &mFilesystem); // Couldn't create filesystem. if (!NT_SUCCESS(result)) throw FUSEErrorF("Couldn't create dispatcher: %lx", result); // Ask WinFSP to log *everything*. FspFileSystemSetDebugLog(mFilesystem, std::numeric_limits<UINT32>::max()); // Allow concurrent file operations. FspFileSystemSetOperationGuardStrategy(mFilesystem, FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY_FINE); // Make sure WinFSP logs everything. mFilesystem->DebugLog = std::numeric_limits<UINT32>::max(); // Make sure we know which dispatcher is processing what requests. mFilesystem->UserContext = this; FUSEDebugF("Dispatcher constructed: %s", mMount.name().c_str()); } Dispatcher::~Dispatcher() { FspFileSystemDelete(mFilesystem); FUSEDebugF("Dispatcher destructed: %s", mMount.name().c_str()); } const NormalizedPath& Dispatcher::path() const { return mPath; } void Dispatcher::reply(FSP_FSCTL_TRANSACT_RSP& response, Error result) { reply(response, translate(result)); } void Dispatcher::reply(FSP_FSCTL_TRANSACT_RSP& response, NTSTATUS result) { response.IoStatus.Status = result; FspFileSystemSendResponse(mFilesystem, &response); } FSP_FSCTL_TRANSACT_REQ& Dispatcher::request() const { auto* context = FspFileSystemGetOperationContext(); assert(context); return *context->Request; } void Dispatcher::start(const NormalizedPath& path) { // Try and start the dispatcher. auto result = FspFileSystemStartDispatcher(mFilesystem, 0); // Couldn't start the dispatcher. if (!NT_SUCCESS(result)) throw FUSEErrorF("Couldn't start dispatcher: %s: %lx", mMount.name().c_str(), result); // Assume the mount's writable. auto* descriptor = &mMount.mMountDB.mReadWriteSecurityDescriptor; // Mount's actually raed-only. if (!mMount.writable()) descriptor = &mMount.mMountDB.mReadOnlySecurityDescriptor; // Assume the user wants us to allocate a drive letter. auto path_ = path.asPlatformEncoded(true); // Try and make the mount visible on the local filesystem. result = FspFileSystemSetMountPointEx(mFilesystem, path_.empty() ? nullptr : path_.data(), descriptor->get()); // Couldn't make the mount visible. if (!NT_SUCCESS(result)) throw FUSEErrorF("Couldn't set volume mount point: %s: %lx", mMount.name().c_str(), result); // Latch the mount's actual path. mPath = [this]() { std::wstring path = FspFileSystemMountPoint(mFilesystem); return LocalPath::fromPlatformEncodedAbsolute(std::move(path)); }(); } void Dispatcher::stop() { FspFileSystemStopDispatcher(mFilesystem); } Dispatcher& dispatcher(FSP_FILE_SYSTEM& filesystem) { auto* context = filesystem.UserContext; assert(context); return *reinterpret_cast<Dispatcher*>(context); } #define ENTRY(enumerant) \ { \ #enumerant, enumerant \ } void logAccess(const char* function, UINT32 mask) { static const std::map<const char*, UINT32> names = {ENTRY(DELETE), ENTRY(FILE_APPEND_DATA), ENTRY(FILE_EXECUTE), ENTRY(FILE_LIST_DIRECTORY), ENTRY(FILE_READ_ATTRIBUTES), ENTRY(FILE_READ_DATA), ENTRY(FILE_READ_EA), ENTRY(FILE_TRAVERSE), ENTRY(FILE_WRITE_ATTRIBUTES), ENTRY(FILE_WRITE_DATA), ENTRY(FILE_WRITE_EA), ENTRY(READ_CONTROL), ENTRY(SYNCHRONIZE), ENTRY(WRITE_DAC), ENTRY(WRITE_OWNER)}; // names for (auto& n: names) { if ((mask & n.second)) FUSEDebugF("%s: access: %s", function, n.first); } } void logAttributes(const char* function, UINT32 mask) { static const std::map<const char*, UINT32> names = {ENTRY(FILE_ATTRIBUTE_ARCHIVE), ENTRY(FILE_ATTRIBUTE_COMPRESSED), ENTRY(FILE_ATTRIBUTE_DEVICE), ENTRY(FILE_ATTRIBUTE_DIRECTORY), ENTRY(FILE_ATTRIBUTE_EA), ENTRY(FILE_ATTRIBUTE_ENCRYPTED), ENTRY(FILE_ATTRIBUTE_HIDDEN), ENTRY(FILE_ATTRIBUTE_INTEGRITY_STREAM), ENTRY(FILE_ATTRIBUTE_NORMAL), ENTRY(FILE_ATTRIBUTE_NOT_CONTENT_INDEXED), ENTRY(FILE_ATTRIBUTE_NO_SCRUB_DATA), ENTRY(FILE_ATTRIBUTE_OFFLINE), ENTRY(FILE_ATTRIBUTE_PINNED), ENTRY(FILE_ATTRIBUTE_READONLY), ENTRY(FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS), ENTRY(FILE_ATTRIBUTE_RECALL_ON_OPEN), ENTRY(FILE_ATTRIBUTE_REPARSE_POINT), ENTRY(FILE_ATTRIBUTE_SPARSE_FILE), ENTRY(FILE_ATTRIBUTE_SYSTEM), ENTRY(FILE_ATTRIBUTE_TEMPORARY), ENTRY(FILE_ATTRIBUTE_UNPINNED), ENTRY(FILE_ATTRIBUTE_VIRTUAL)}; // names for (auto& n: names) { if ((mask & n.second)) FUSEDebugF("%s: attribute: %s", function, n.first); } } void logOptions(const char* function, UINT32 mask) { static const std::map<const char*, UINT32> names = {ENTRY(FILE_COMPLETE_IF_OPLOCKED), ENTRY(FILE_CREATE_TREE_CONNECTION), ENTRY(FILE_DELETE_ON_CLOSE), ENTRY(FILE_DIRECTORY_FILE), ENTRY(FILE_NON_DIRECTORY_FILE), ENTRY(FILE_NO_EA_KNOWLEDGE), ENTRY(FILE_NO_INTERMEDIATE_BUFFERING), ENTRY(FILE_OPEN_BY_FILE_ID), ENTRY(FILE_OPEN_FOR_BACKUP_INTENT), ENTRY(FILE_OPEN_REPARSE_POINT), ENTRY(FILE_OPEN_REQUIRING_OPLOCK), ENTRY(FILE_RANDOM_ACCESS), ENTRY(FILE_RESERVE_OPFILTER), ENTRY(FILE_SEQUENTIAL_ONLY), ENTRY(FILE_SYNCHRONOUS_IO_ALERT), ENTRY(FILE_SYNCHRONOUS_IO_NONALERT), ENTRY(FILE_WRITE_THROUGH)}; // names for (auto& n: names) { if ((mask & n.second)) FUSEDebugF("%s: option: %s", function, n.first); } } #undef ENTRY } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/file_explorer_setter.cpp����������������������������0000664�0000000�0000000�00000004503�15162662266�0026476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/task_executor.h> #include <mega/common/task_executor_flags.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/file_explorer_setter.h> #include <thread> namespace mega { namespace fuse { namespace platform { using namespace common; // One worker running forever // This is required by FileExplorerSetter::Executor static TaskExecutorFlags executorFlags() { TaskExecutorFlags flags; flags.mMinWorkers = 1; flags.mMaxWorkers = 1; return flags; } class FileExplorerSetter::Executor: public common::TaskExecutor { friend class FileExplorerSetter; bool mInitialized{false}; void workerStarted(std::thread::id id) override; void workerStopped(std::thread::id id) override; public: Executor(); }; FileExplorerSetter::Executor::Executor(): common::TaskExecutor(executorFlags(), logger()) {} void FileExplorerSetter::Executor::workerStarted(std::thread::id) { mInitialized = shell::initialize(); } void FileExplorerSetter::Executor::workerStopped(std::thread::id) { if (mInitialized) shell::deinitialize(); } FileExplorerSetter::FileExplorerSetter(): mExecutor(std::make_unique<FileExplorerSetter::Executor>()) { // start the worker for workerStarted initialization mExecutor->execute([](const Task&) {}, false); } FileExplorerSetter::~FileExplorerSetter() = default; void FileExplorerSetter::notify(std::function<shell::Prefixes()> getPrefixes) { if (!mExecutor->mInitialized) return; auto setView = [getPrefixes = std::move(getPrefixes)](const Task& task) { if (task.cancelled()) return; if (auto prefixes = getPrefixes(); !prefixes.empty()) { shell::setView(prefixes); } }; // There is a small chance that the notification is sent too early and File Explorer misses it. // In most cases, the first attempt will succeed. // The second attempt is scheduled after 30ms—a short enough delay to avoid visible UI flicker. // The third attempt is after 100ms as a final fallback, hoping it is long enough for the system // to be ready mExecutor->execute(setView, false); mExecutor->execute(setView, std::chrono::milliseconds{30}, false); mExecutor->execute(setView, std::chrono::milliseconds{100}, false); } } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/local_pointer.cpp�����������������������������������0000664�0000000�0000000�00000000400�15162662266�0025073�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/windows.h> #include <mega/fuse/platform/local_pointer.h> namespace mega { namespace fuse { namespace platform { void LocalDeleter::operator()(void* instance) { LocalFree(instance); } } // platform } // fuse } // megea ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/�����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022454�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/CMakeLists.txt���������������������������������0000664�0000000�0000000�00000000027�15162662266�0025213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023416�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/CMakeLists.txt����������������������������0000664�0000000�0000000�00000000033�15162662266�0026152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/���������������������������������0000775�0000000�0000000�00000000000�15162662266�0025242�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/CMakeLists.txt�������������������0000664�0000000�0000000�00000001260�15162662266�0030001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE constants.h directory_context.h dispatcher.h dispatcher_forward.h file_explorer_setter.h library.h local_pointer.h mount.h mount_db.h path_adapter.h platform.h security_descriptor.h security_descriptor_forward.h security_identifier.h security_identifier_forward.h shell.h utility.h ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/constants.h����������������������0000664�0000000�0000000�00000000564�15162662266�0027434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/constants.h> #include <mega/fuse/platform/library.h> #include <string> namespace mega { namespace fuse { namespace platform { constexpr auto MaxMountNameLength = 32u; constexpr auto MaxVolumePrefixLength = FSP_FSCTL_VOLUME_PREFIX_SIZE / sizeof(wchar_t); extern const std::wstring UNCPrefix; } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/directory_context.h��������������0000664�0000000�0000000�00000003673�15162662266�0031174�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/directory_inode_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/directory_context_forward.h> #include <mega/fuse/platform/library.h> #include <mutex> #include <string> #include <utility> #include <vector> namespace mega { namespace fuse { namespace platform { class DirectoryContext: public Context { // Translates a name to a child index. using NameIndexPair = std::pair<std::string, std::size_t>; // Tracks all known name-index mappings. using NameIndexVector = std::vector<NameIndexPair>; // Retrieve this directory's children if necessary. void populate() const; // Who are this directory's children? mutable InodeRefVector mChildren; // Maps child names to child indices. mutable NameIndexVector mChildrenByName; // What directory are we iterating over? DirectoryInodeRef mDirectory; // Serializes access to instance members. mutable std::mutex mLock; // Offset of first non-link child. mutable NameIndexVector::size_type mOffset; // Is this a root directory? bool mIsRoot; // How we retrieved this directory's children? mutable bool mPopulated; public: DirectoryContext(DirectoryInodeRef directory, fuse::Mount& mount, bool isRoot); ~DirectoryContext(); // Check if this context represents a directory. DirectoryContext* directory() override; // Retrieve a reference to the specified child. InodeRef get(const std::string& name) const; // Retrieve a listing of this directory's children. void get(const std::string& marker, PVOID buffer, ULONG length, const Mount& mount, ULONG& numWritten) const; // What inode does this context represent? InodeRef inode() const override; }; // DirectoryContext } // platform } // fuse } // mega ���������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/dispatcher.h���������������������0000664�0000000�0000000�00000013237�15162662266�0027547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/normalized_path.h> #include <mega/fuse/platform/dispatcher_forward.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount_forward.h> #include <memory> namespace mega { namespace fuse { namespace platform { // Responsible for receiving and dispatching filesystem requests. class Dispatcher { static NTSTATUS canDelete(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path); static void cleanup(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path, ULONG flags); static void close(FSP_FILE_SYSTEM* filesystem, PVOID context); static NTSTATUS create(FSP_FILE_SYSTEM* filesystem, PWSTR path, UINT32 options, UINT32 access, UINT32 attributes, PSECURITY_DESCRIPTOR descriptor, UINT64 allocation, PVOID* context, FSP_FSCTL_FILE_INFO* info); static NTSTATUS flush(FSP_FILE_SYSTEM* filesystem, PVOID context, FSP_FSCTL_FILE_INFO* info); static NTSTATUS getDirInfoByName(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR path, FSP_FSCTL_DIR_INFO* info); static NTSTATUS getFileInfo(FSP_FILE_SYSTEM* filesystem, PVOID context, FSP_FSCTL_FILE_INFO* info); static NTSTATUS getSecurity(FSP_FILE_SYSTEM* filesystem, PVOID context, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength); static NTSTATUS getSecurityByName(FSP_FILE_SYSTEM* filesystem, PWSTR path, PUINT32 attributes, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength); static NTSTATUS getVolumeInfo(FSP_FILE_SYSTEM* filesystem, FSP_FSCTL_VOLUME_INFO* info); static NTSTATUS open(FSP_FILE_SYSTEM* filesystem, PWSTR path, UINT32 options, UINT32 access, PVOID* context, FSP_FSCTL_FILE_INFO* info); static NTSTATUS overwrite(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT32 attributes, BOOLEAN replaceAttributes, UINT64 allocation, FSP_FSCTL_FILE_INFO* info); static NTSTATUS read(FSP_FILE_SYSTEM* filesystem, PVOID context, PVOID buffer, UINT64 offset, ULONG Length, PULONG numRead); static NTSTATUS readDirectory(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR pattern, PWSTR marker, PVOID buffer, ULONG length, PULONG numWritten); static NTSTATUS rename(FSP_FILE_SYSTEM* filesystem, PVOID context, PWSTR sourceName, PWSTR targetName, BOOLEAN replace); static NTSTATUS setBasicInfo(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT32 attributes, UINT64 created, UINT64 accessed, UINT64 written, UINT64 changed, FSP_FSCTL_FILE_INFO* info); static NTSTATUS setFileSize(FSP_FILE_SYSTEM* filesystem, PVOID context, UINT64 size, BOOLEAN allocated, FSP_FSCTL_FILE_INFO* info); static NTSTATUS setSecurity(FSP_FILE_SYSTEM* filesystem, PVOID context, SECURITY_INFORMATION security, PSECURITY_DESCRIPTOR descriptor); static void stopped(FSP_FILE_SYSTEM* filesystem, BOOLEAN normally); static NTSTATUS write(FSP_FILE_SYSTEM* filesystem, PVOID context, PVOID buffer, UINT64 offset, ULONG length, BOOLEAN append, BOOLEAN noGrow, PULONG numWritten, FSP_FSCTL_FILE_INFO* info); // The filesystem we're dispatching requests for. FSP_FILE_SYSTEM* mFilesystem; // What mount are dispatching requests to? Mount& mMount; // Where is this mount, mounted? common::NormalizedPath mPath; // Who should be called for what requests? static const FSP_FILE_SYSTEM_INTERFACE mOperations; public: explicit Dispatcher(Mount& mount, const common::NormalizedPath& path); ~Dispatcher(); const common::NormalizedPath& path() const; void reply(FSP_FSCTL_TRANSACT_RSP& response, Error result); void reply(FSP_FSCTL_TRANSACT_RSP& response, NTSTATUS result); FSP_FSCTL_TRANSACT_REQ& request() const; void start(const common::NormalizedPath& path); void stop(); }; // Dispatcher } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/dispatcher_forward.h�������������0000664�0000000�0000000�00000000173�15162662266�0031266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class Dispatcher; } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/file_explorer_setter.h�����������0000664�0000000�0000000�00000000712�15162662266�0031640�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/shell.h> #include <atomic> #include <functional> #include <memory> namespace mega { namespace fuse { namespace platform { // Sets running file explorer's view class FileExplorerSetter { class Executor; std::unique_ptr<Executor> mExecutor; public: FileExplorerSetter(); ~FileExplorerSetter(); void notify(std::function<shell::Prefixes()> getPrefixes); }; } // platform } // fuse } // mega ������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/library.h������������������������0000664�0000000�0000000�00000000216�15162662266�0027056�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once // clang-format off #include <mega/common/platform/windows.h> #include <bcrypt.h> #include <winfsp/winfsp.h> // clang-format on����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/local_pointer.h������������������0000664�0000000�0000000�00000000423�15162662266�0030244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <memory> namespace mega { namespace fuse { namespace platform { struct LocalDeleter { void operator()(void* instance); }; // LocalDeleter template<typename T> using LocalPtr = std::unique_ptr<T, LocalDeleter>; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/mount.h��������������������������0000664�0000000�0000000�00000011126�15162662266�0026556�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/activity_monitor.h> #include <mega/common/error_or_forward.h> #include <mega/common/task_executor.h> #include <mega/fuse/common/inode_db_forward.h> #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/mount.h> #include <mega/fuse/common/mount_result_forward.h> #include <mega/fuse/platform/context_forward.h> #include <mega/fuse/platform/dispatcher.h> #include <mega/fuse/platform/library.h> namespace mega { namespace fuse { namespace platform { class Mount: public fuse::Mount { // So the dispatcher can invoke our callbacks. friend class Dispatcher; NTSTATUS canDelete(PVOID context); void cleanup(PVOID context, const std::wstring& path, ULONG flags); void close(PVOID context); NTSTATUS create(const std::wstring& path, UINT32 options, UINT32 access, PVOID& context, FSP_FSCTL_FILE_INFO& info); NTSTATUS flush(PVOID context, FSP_FSCTL_FILE_INFO& info); NTSTATUS getDirInfoByName(PVOID context, const std::wstring& path, FSP_FSCTL_DIR_INFO& info); NTSTATUS getFileInfo(PVOID context, FSP_FSCTL_FILE_INFO& info); NTSTATUS getSecurity(PVOID context, PSECURITY_DESCRIPTOR descriptor, SIZE_T& descriptorLength); NTSTATUS getSecurity(PSECURITY_DESCRIPTOR descriptor, SIZE_T& descriptorLength, InodeInfo info); NTSTATUS getSecurityByName(const std::wstring& path, PUINT32 attributes, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength); NTSTATUS getVolumeInfo(FSP_FSCTL_VOLUME_INFO& info); // For convenience. InodeDB& inodeDB() const; NTSTATUS open(const std::wstring& path, UINT32 options, UINT32 access, PVOID& context, FSP_FSCTL_FILE_INFO& info); NTSTATUS overwrite(PVOID context, FSP_FSCTL_FILE_INFO& info); NTSTATUS read(PVOID context, PVOID buffer, UINT64 offset, ULONG length, ULONG& numRead); NTSTATUS readDirectory(PVOID context, const std::string& pattern, const std::string& marker, PVOID buffer, ULONG length, ULONG& numWritten); NTSTATUS rename(PVOID context, const std::wstring& targetPath, BOOLEAN replace); NTSTATUS setBasicInfo(PVOID context, UINT32 attributes, UINT64 created, UINT64 accessed, UINT64 written, UINT64 changed, FSP_FSCTL_FILE_INFO& info); NTSTATUS setFileSize(PVOID context, UINT64 size, BOOLEAN allocated, FSP_FSCTL_FILE_INFO& info); NTSTATUS setSecurity(PVOID context, SECURITY_INFORMATION security, PSECURITY_DESCRIPTOR descriptor); void stopped(BOOLEAN normally); NTSTATUS write(PVOID context, PVOID buffer, UINT64 offset, ULONG length, BOOLEAN append, BOOLEAN noGrow, ULONG& numWritten, FSP_FSCTL_FILE_INFO& info); bool isSelfForbidden() const; // Tracks whether any requests are in progress. common::ActivityMonitor mActivities; // Responsible for receiving requests from WinFSP. Dispatcher mDispatcher; // Responsible for performing select requests. common::TaskExecutor mExecutor; public: Mount(const MountInfo& info, MountDB& mountDB); ~Mount(); // Invalidate an inode's attributes. void invalidateAttributes(InodeID id) override; // Invalidate an inode's data. void invalidateData(InodeID id, m_off_t offset, m_off_t size) override; void invalidateData(InodeID id) override; // Invalidate a directory entry. void invalidateEntry(const std::string& name, InodeID child, InodeID parent) override; void invalidateEntry(const std::string& name, InodeID parent) override; // Translate a mount-speicifc inode ID to a system-wide inode ID. InodeID map(MountInodeID id) const override; // Translate a system-wide inode ID to a mount-specific inode ID. MountInodeID map(InodeID id) const override; // What local path is this mount mapping from? common::NormalizedPath path() const override; // Remove the mount from memory. MountResult remove(); }; // Mount } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/mount_db.h�����������������������0000664�0000000�0000000�00000001761�15162662266�0027227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/inode_forward.h> #include <mega/fuse/common/mount_db.h> #include <mega/fuse/common/service_context_forward.h> #include <mega/fuse/platform/file_explorer_setter.h> #include <mega/fuse/platform/security_descriptor.h> namespace mega { namespace fuse { namespace platform { class MountDB: public fuse::MountDB { FileExplorerSetter mFileExplorerSetter{}; // Checks whether a mount's local path is valid. MountResult check(const common::Client& client, const MountInfo& info) const override; // Checks whether a mount's name is valid. MountResult checkName(const std::string& name) const override; public: MountDB(ServiceContext& context); void notifyFileExplorerSetter(); // Security descriptor for read-only inodes. const SecurityDescriptor mReadOnlySecurityDescriptor; // Security descriptor for read-write inodes. const SecurityDescriptor mReadWriteSecurityDescriptor; }; // MountDB } // platform } // fuse } // mega ���������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/path_adapter.h�������������������0000664�0000000�0000000�00000001236�15162662266�0030051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/path_adapter.h> #include <mega/fuse/platform/path_adapter_forward.h> #include <mega/fuse/platform/utility.h> #include <string> namespace mega { namespace fuse { namespace platform { namespace detail { struct PathAdapterTraits { using SizeType = std::wstring::size_type; using StringType = std::wstring; using ValueType = std::wstring::value_type; static ValueType separator() { return L'\\'; } static std::string toUTF8(const ValueType* data, SizeType length) { return fromWideString(data, length); } }; // PathAdapterTraits } // detail } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/platform.h�����������������������0000664�0000000�0000000�00000000246�15162662266�0027241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #define LINUX_ONLY(l) #define LINUX_OR_POSIX(l, p) #define POSIX_ONLY(p) #define UNIX_ONLY(u) #define UNIX_OR_WINDOWS(u, w) w #define WINDOWS_ONLY(w) w ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/security_descriptor.h������������0000664�0000000�0000000�00000003030�15162662266�0031514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/local_pointer.h> #include <mega/fuse/platform/security_descriptor_forward.h> #include <cstddef> #include <cstdint> #include <string> namespace mega { namespace fuse { namespace platform { class SecurityDescriptor { LocalPtr<void> mDescriptor; public: SecurityDescriptor(); explicit SecurityDescriptor(LocalPtr<void> descriptor); SecurityDescriptor(const SecurityDescriptor& other); SecurityDescriptor(SecurityDescriptor&& other) = default; operator bool() const; bool operator!() const; SecurityDescriptor& operator=(const SecurityDescriptor& rhs); SecurityDescriptor& operator=(SecurityDescriptor&& rhs) = default; bool operator==(const SecurityDescriptor& rhs) const; bool operator!=(const SecurityDescriptor& rhs) const; static SecurityDescriptor fromString(const std::string& text); static SecurityDescriptor fromString(const char* text); void* get() const; std::size_t length() const; std::uint32_t modify(const SecurityDescriptor& modifications, std::uint32_t mask); std::uint32_t modify(void* modifications, std::uint32_t mask); void* release(); void reset(void* descriptor = nullptr); void swap(SecurityDescriptor& other); }; // SecurityDescriptor SecurityDescriptor readOnlySecurityDescriptor(); SecurityDescriptor readWriteSecurityDescriptor(); void swap(SecurityDescriptor& lhs, SecurityDescriptor& rhs); std::string toString(const SecurityDescriptor& descriptor); } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/security_descriptor_forward.h����0000664�0000000�0000000�00000000203�15162662266�0033237�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class SecurityDescriptor; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/security_identifier.h������������0000664�0000000�0000000�00000002347�15162662266�0031472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/platform/local_pointer.h> #include <mega/fuse/platform/security_identifier_forward.h> #include <cstddef> #include <cstdint> #include <string> namespace mega { namespace fuse { namespace platform { class SecurityIdentifier { LocalPtr<void> mIdentifier; public: SecurityIdentifier(); explicit SecurityIdentifier(void* identifier); SecurityIdentifier(const SecurityIdentifier& other); SecurityIdentifier(SecurityIdentifier&& other) = default; operator bool() const; bool operator!() const; SecurityIdentifier& operator=(const SecurityIdentifier& rhs); SecurityIdentifier& operator=(SecurityIdentifier&& rhs) = default; static SecurityIdentifier fromString(const std::string& text); static SecurityIdentifier fromString(const char* text); void* get() const; static SecurityIdentifier group(); std::size_t length() const; void* release(); void reset(void* identifier = nullptr); void swap(SecurityIdentifier& other); static SecurityIdentifier user(); }; // SecurityIdentifier void swap(SecurityIdentifier& lhs, SecurityIdentifier& rhs); std::string toString(const SecurityIdentifier& identifier); } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/security_identifier_forward.h����0000664�0000000�0000000�00000000203�15162662266�0033203�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once namespace mega { namespace fuse { namespace platform { class SecurityIdentifier; } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/shell.h��������������������������0000664�0000000�0000000�00000001263�15162662266�0026524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <string> #include <vector> namespace mega { namespace fuse { namespace platform { namespace shell { using Prefixes = std::vector<std::wstring>; // Init for the calling thread once and before use setView. Caller can call this once per thread. bool initialize(); // Sets the view mode of running File Explorer windows to "List View" for whose open folder // paths started with one of given prefix in the set. This affects only currently open File Explorer // instances. // @param prefixes The folder path prefixes set to match void setView(const Prefixes& prefixes); // Uninit once. Pair with init. void deinitialize(); } // shell } // platform } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mega/fuse/platform/utility.h������������������������0000664�0000000�0000000�00000002162�15162662266�0027117�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/common/utility.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount_forward.h> #include <mega/types.h> #include <string> namespace mega { namespace fuse { namespace platform { DWORD attributes(const InodeInfo& info, const Mount& mount); std::string fromWideString(const wchar_t* value, std::size_t length); std::string fromWideString(const std::wstring& value); std::wstring normalize(const std::wstring& value); std::wstring toWideString(const std::string& value); FSP_FSCTL_DIR_INFO* translate(FSP_FSCTL_DIR_INFO& destination, const Mount& mount, const InodeInfo& source); FSP_FSCTL_DIR_INFO* translate(std::vector<uint8_t>& destination, const Mount& mount, const std::string& name, const InodeInfo& source); void translate(FSP_FSCTL_FILE_INFO& destination, const Mount& mount, const InodeInfo& source); NTSTATUS translate(Error result); } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/mount.cpp�������������������������������������������0000664�0000000�0000000�00000064776�15162662266�0023435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "megafs.h" #include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/platform/date_time.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/file_inode.h> #include <mega/fuse/common/file_open_flag.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/inode_id.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/context.h> #include <mega/fuse/platform/directory_context.h> #include <mega/fuse/platform/file_context.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/path_adapter.h> #include <mega/fuse/platform/service_context.h> #include <mega/fuse/platform/utility.h> #include <cstring> namespace mega { namespace fuse { namespace platform { using namespace common; static bool newAttributeIsAllowed(UINT32 newValue, UINT32 currentValue) { // What attributes are allowed to change // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesa // We excludes FILE_ATTRIBUTE_OFFLINE as it apprently doesn't apply constexpr UINT32 allowed = FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_TEMPORARY; // What bits is changed const UINT32 changed = newValue ^ currentValue; // Nothing is changed or changed bits are allowed // Below is equal as changed == 0 || (changed & allowed) == changed) return (changed & allowed) == changed; } NTSTATUS Mount::canDelete(PVOID context) { // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // Get our hands on the entity's context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Get our hands on the entity's inode. auto ref = context_->inode(); // Inode isn't writable. if (ref->permissions() != FULL) return STATUS_ACCESS_DENIED; // Does the inode represent a directory? if (auto directoryRef = ref->directory()) { // Is the directory empty? auto hasChildren = directoryRef->hasChildren(); // Can't determine whether the directory's empty. if (!hasChildren) return STATUS_UNSUCCESSFUL; // Directory isn't empty. if (*hasChildren) return STATUS_DIRECTORY_NOT_EMPTY; } // Entity can be deleted. return STATUS_SUCCESS; } void Mount::cleanup(PVOID context, const std::wstring& path, ULONG flags) { // Nothing to do if we're not deleting the entity. if (!(flags & FspCleanupDelete)) return; // Get our hands on the context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Try and delete the entity. auto result = context_->inode()->unlink(); // Entity's been deleted. if (result == API_OK) return; // Couldn't delete the entity. FUSEWarningF("Couldn't delete entity: %s: %d", fromWideString(path).c_str(), static_cast<int>(result)); } void Mount::close(PVOID context) { // Get our hands on the context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Delete the context. delete context_; } NTSTATUS Mount::create(const std::wstring& path, UINT32 options, UINT32 access, PVOID& context, FSP_FSCTL_FILE_INFO& info) { // Reject if the originating process is self if (isSelfForbidden()) return STATUS_ACCESS_DENIED; // Try and locate the specified node. auto name = std::string(); auto result = inodeDB().lookup(PathAdapter(path), handle(), &name); // Node already exists. if (result.second == API_OK) return STATUS_OBJECT_NAME_COLLISION; // Some parent doesn't exist. if (result.second != API_FUSE_ENOTFOUND) return translate(result.second); auto directoryRef = result.first->directory(); // Sanity. assert(directoryRef); // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // Directory isn't writable. if (directoryRef->permissions() != FULL) return STATUS_ACCESS_DENIED; // Assume caller wants to create a new file. auto create = &DirectoryInode::makeFile; // Caller wants to create a new directory; if ((options & FILE_DIRECTORY_FILE)) create = &DirectoryInode::makeDirectory; // Try and create the new node. auto created = (*directoryRef.*create)(*this, name); // Couldn't create the new node. if (!created) return translate(created.error()); // Latch the new node's description. translate(info, *this, std::get<1>(*created)); // Get our hands on the newly created node. auto ref = std::move(std::get<0>(*created)); // Caller's created a directory. if ((directoryRef = ref->directory())) { // Create a context to represent this directory. auto context_ = std::make_unique<DirectoryContext>(directoryRef, *this, false); // Caller now owns the context. context = context_.release(); // Directory's been created. return STATUS_SUCCESS; } // Caller's created a file. FileOpenFlags flags = 0; // Caller wants to append to the file. if ((access & FILE_APPEND_DATA)) flags = FOF_APPEND | FOF_WRITABLE; // Caller wants to write data to the file. if ((access & FILE_WRITE_DATA)) flags = FOF_WRITABLE; // Try and open the file for writing. auto opened = ref->file()->open(*this, flags); // Couldn't open the file. if (!opened) return translate(opened.error()); // Caller now owns file context. context = opened->release(); // File's been created. return STATUS_SUCCESS; } NTSTATUS Mount::flush(PVOID context, FSP_FSCTL_FILE_INFO& info) { // Caller wants to flush the entire volume. if (!context) return STATUS_UNSUCCESSFUL; // Get our hands on this file's context. auto* context_ = reinterpret_cast<Context*>(context)->file(); // Sanity. assert(context); // Try and flush any modifications to the cloud. auto result = context_->flush(); // Latch the file's description. translate(info, *this, context_->info()); // Let the caller know if the flush was successful. return translate(result); } NTSTATUS Mount::getDirInfoByName(PVOID context, const std::wstring& name, FSP_FSCTL_DIR_INFO& info) { // Sanity. assert(context); // Get our hands on the directory's context. auto* context_ = reinterpret_cast<Context*>(context)->directory(); // Sanity. assert(context_); // Try and locate the specified child. auto ref = context_->get(fromWideString(name)); // Couldn't locate the child. if (!ref) return STATUS_OBJECT_NAME_NOT_FOUND; // Latch the child's description. translate(info, *this, ref->info()); // Let the caller know the request was successful. return STATUS_SUCCESS; } NTSTATUS Mount::getFileInfo(PVOID context, FSP_FSCTL_FILE_INFO& info) { // Sanity. assert(context); // Get our hands on the entity's context. auto* context_ = static_cast<Context*>(context); // Desrcibe the entity to the caller. translate(info, *this, context_->info()); // Let the caller know their request was successful. return STATUS_SUCCESS; } NTSTATUS Mount::getSecurity(PVOID context, PSECURITY_DESCRIPTOR descriptor, SIZE_T& descriptorLength) { // Get our hands on the file's context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Retrieve the inode's security descriptor. return getSecurity(descriptor, descriptorLength, context_->inode()->info()); } NTSTATUS Mount::getSecurity(PSECURITY_DESCRIPTOR descriptor, SIZE_T& descriptorLength, InodeInfo info) { // Sanity. assert(descriptor); // Assume the inode is read-write. auto* descriptor_ = &mMountDB.mReadWriteSecurityDescriptor; // Mount or inode is read-only. if (!writable() || info.mPermissions != FULL) descriptor_ = &mMountDB.mReadOnlySecurityDescriptor; // Compute the descriptor's length. SIZE_T descriptorLength_ = descriptor_->length(); // Let the caller know the descriptor's length. std::swap(descriptorLength, descriptorLength_); // Caller hasn't allocated enough buffer space. if (descriptorLength > descriptorLength_) return STATUS_BUFFER_OVERFLOW; // Copy descriptor to the caller's buffer. std::memcpy(descriptor, descriptor_->get(), descriptorLength); // Let the caller know they've got the descriptor. return STATUS_SUCCESS; } NTSTATUS Mount::getSecurityByName(const std::wstring& path, PUINT32 attributes, PSECURITY_DESCRIPTOR descriptor, SIZE_T* descriptorLength) { // Reject if the originating process is self if (isSelfForbidden()) return STATUS_ACCESS_DENIED; // Try and locate the specified inode. auto result = inodeDB().lookup(PathAdapter(path), handle()); // Couldn't locate the inode. if (result.second != API_OK) return translate(result.second); // Latch this inode's description. auto info = result.first->info(); // Caller wants to know the inode's file attributes. if (attributes) *attributes = platform::attributes(info, *this); // Caller wants the inode's security descriptor. if (descriptor) return getSecurity(descriptor, *descriptorLength, std::move(info)); // Let the caller know the request was successful. return STATUS_SUCCESS; } NTSTATUS Mount::getVolumeInfo(FSP_FSCTL_VOLUME_INFO& info) { // Convenience. auto& client = mMountDB.client(); // Ask the client how much storage we've used. auto storageInfo = client.storageInfo(); // Couldn't determine how much storage we've used. if (!storageInfo) return STATUS_UNSUCCESSFUL; // How long can a mount's name be? constexpr auto maxLength = sizeof(info.VolumeLabel) / sizeof(WCHAR); // Get our hands on the mount's name. auto name = toWideString(this->name()); // Truncate the name as necessary. name.resize(std::min(maxLength, name.size())); // Populate usage statistics. info.FreeSize = static_cast<UINT64>(storageInfo->mAvailable); info.TotalSize = static_cast<UINT64>(storageInfo->mCapacity); // Populate volume label. info.VolumeLabelLength = static_cast<UINT16>(name.size()); std::memcpy(info.VolumeLabel, name.c_str(), info.VolumeLabelLength); // Return control to caller. return STATUS_SUCCESS; } InodeDB& Mount::inodeDB() const { return mMountDB.mContext.mInodeDB; } NTSTATUS Mount::open(const std::wstring& path, UINT32 options, UINT32 access, PVOID& context, FSP_FSCTL_FILE_INFO& info) { // Reject if the originating process is self if (isSelfForbidden()) return STATUS_ACCESS_DENIED; // Try and locate the specified inode. auto result = inodeDB().lookup(PathAdapter(path), handle()); // Couldn't locate the inode. if (result.second != API_OK) return translate(result.second); auto ref = std::move(result.first); // Inode describes a directory. if (auto directoryRef = ref->directory()) { // Caller's only interested in files. if ((options & FILE_NON_DIRECTORY_FILE)) return STATUS_FILE_IS_A_DIRECTORY; // Latch the directory's description. translate(info, *this, directoryRef->info()); // Create a context to represent this directory. auto context_ = std::make_unique<DirectoryContext>(directoryRef, *this, path.empty()); // Caller now owns the directory's context. context = context_.release(); // Let the caller know the directory's opened. return STATUS_SUCCESS; } // Inode describes a file but the caller wants a directory. if ((options & FILE_DIRECTORY_FILE)) return STATUS_NOT_A_DIRECTORY; FileOpenFlags flags = 0; // Compute open flags. if ((access & FILE_APPEND_DATA)) flags = FOF_APPEND | FOF_WRITABLE; if ((access & FILE_WRITE_DATA)) flags = FOF_WRITABLE; // Get our hands on the file's inode. auto fileRef = ref->file(); // Try and open the file. auto opened = fileRef->open(*this, flags); // Couldn't open the file. if (!opened) return translate(opened.error()); // Latch the file's description. translate(info, *this, fileRef->info()); // Caller know owns the file's context. context = opened->release(); // Let the caller know the file's opened. return STATUS_SUCCESS; } NTSTATUS Mount::overwrite(PVOID context, FSP_FSCTL_FILE_INFO& info) { // Sanity. assert(context); // Get our hands on the file's context. auto* context_ = reinterpret_cast<Context*>(context)->file(); // Sanity. assert(context_); // Try and truncate the file. auto result = context_->truncate(0, false); // Can't truncate the file. if (result != API_OK) return translate(result); // Latch the file's description. translate(info, *this, context_->info()); // File's been overwritten. return STATUS_SUCCESS; } NTSTATUS Mount::read(PVOID context, PVOID buffer, UINT64 offset, ULONG length, ULONG& /*numRead*/) { // Sanity. assert(context); // Get our hands on the context. auto* context_ = reinterpret_cast<Context*>(context)->file(); // Sanity. assert(context_); // Get our hands on the request's "hint." auto hint = mDispatcher.request().Hint; // Actually reads the file. auto read = [=](Activity&, const Task&) { auto response = std::make_unique<FSP_FSCTL_TRANSACT_RSP>(); std::memset(response.get(), 0, sizeof(response)); // Prepare for response. response->Hint = hint; response->Kind = FspFsctlTransactReadKind; response->Size = sizeof(*response); // Try and read the file. auto result = context_->read(static_cast<m_off_t>(offset), length); // Couldn't read the file. if (!result) return mDispatcher.reply(*response, result.error()); // Let the caller know how much data was read. response->IoStatus.Information = static_cast<ULONG>(result->size()); // Caller's hit the end of the file. if (!response->IoStatus.Information) return mDispatcher.reply(*response, STATUS_END_OF_FILE); // Populate user's buffer. std::memcpy(buffer, result->c_str(), result->size()); // Let the caller know their read has been successful. mDispatcher.reply(*response, STATUS_SUCCESS); }; // read // Schedule the read for execution. mExecutor.execute(std::bind(std::move(read), mActivities.begin(), std::placeholders::_1), true); // Let the caller know their read is underway. return STATUS_PENDING; } NTSTATUS Mount::readDirectory(PVOID context, const std::string& /*pattern*/, const std::string& marker, PVOID buffer, ULONG length, ULONG& /*numWritten*/) { // Get our hands on the directory's context. auto* context_ = reinterpret_cast<DirectoryContext*>(context); // Get our hands on the request's "hint." auto hint = mDispatcher.request().Hint; // Actually reads the directory. auto read = [=](Activity&, const Task&) { auto numWritten = 0ul; auto response = std::make_unique<FSP_FSCTL_TRANSACT_RSP>(); // Populate directory entries. context_->get(marker, buffer, length, *this, numWritten); // Populate response. std::memset(response.get(), 0, sizeof(*response)); response->Hint = hint; response->Kind = FspFsctlTransactQueryDirectoryKind; response->Size = sizeof(*response); response->IoStatus.Information = static_cast<UINT32>(numWritten); // Notify File Explorer // // NOTE: This is called here (after directory entries are read) because: // - It ensures the folder is actually opened before notification. // - Placing this in open() (for both files and directories, it has timing // problem if only called for openning directories) increases CPU usage (~2%+) // when opening large folders, so this is more efficient. mMountDB.notifyFileExplorerSetter(); // Send response to caller. mDispatcher.reply(*response, STATUS_SUCCESS); }; // read // Schedule the read for execution. mExecutor.execute(std::bind(std::move(read), mActivities.begin(), std::placeholders::_1), true); // Let the caller know their request is in progress. return STATUS_PENDING; } NTSTATUS Mount::rename(PVOID context, const std::wstring& targetPath, BOOLEAN replace) { // Get our hands on this inode's context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // Convenience. auto name = std::string(); // Try and locate the target inode. auto located = inodeDB().lookup(PathAdapter(targetPath), handle(), &name); // Convenience. auto source = context_->inode(); auto target = std::move(located.first); // Target was found. if (located.second == API_OK) { // But the caller doesn't want to replace it. if (!replace) return STATUS_OBJECT_NAME_COLLISION; // Try and replace target with source. auto result = source->replace(std::move(target), false); // Let the caller know if source replaced target. return translate(result); } // Target directory wasn't found. if (located.second != API_FUSE_ENOTFOUND) return translate(located.second); // Try and move source to target. auto result = source->move(std::move(name), target->directory()); // Let the caller know if source was moved to target. return translate(result); } NTSTATUS Mount::setBasicInfo(PVOID context, UINT32 attributes, UINT64 created, UINT64 /*accessed*/, UINT64 written, UINT64 /*changed*/, FSP_FSCTL_FILE_INFO& info) { // Get our hands on this inode's context. auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Get our hands on the inode. auto ref = context_->inode(); // Latch the inode's current description translate(info, *this, ref->info()); // User wants to modify the file's attributes. if (attributes != INVALID_FILE_ATTRIBUTES) { // Normalize attributes for comparison. if (!attributes) attributes = FILE_ATTRIBUTE_NORMAL; // Deny if any change isn't in allowed if (!newAttributeIsAllowed(attributes, info.FileAttributes)) return STATUS_ACCESS_DENIED; } // Caller isn't allowed to change creation time. if (created && created != info.CreationTime) return STATUS_ACCESS_DENIED; // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // Inode isn't writable. if (ref->permissions() != FULL) return STATUS_ACCESS_DENIED; // Caller doesn't want to change the inode's modification time. if (!written) return STATUS_SUCCESS; auto fileRef = ref->file(); // Directories don't have a mutable modification time. if (!fileRef) return STATUS_SUCCESS; // Try and change the file's modification time. auto result = fileRef->touch(*this, DateTime(written)); // Latch the file's current description. translate(info, *this, ref->info()); // Let the caller know if the modification time was changed. return translate(result); } NTSTATUS Mount::setFileSize(PVOID context, UINT64 size, BOOLEAN allocated, FSP_FSCTL_FILE_INFO& info) { // Sanity. assert(context); // Get our hands on the file's context. auto* context_ = reinterpret_cast<Context*>(context)->file(); // Sanity. assert(context_); // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // File isn't writable. if (context_->inode()->permissions() != FULL) return STATUS_ACCESS_DENIED; // Try and set the file's size. auto result = context_->truncate(static_cast<m_off_t>(size), allocated); // Couldn't set the file's size. if (result != API_OK) return translate(result); // Latch the file's description. translate(info, *this, context_->info()); // Let the caller know the file's size has been changed. return STATUS_SUCCESS; } NTSTATUS Mount::setSecurity(PVOID context, SECURITY_INFORMATION security, PSECURITY_DESCRIPTOR desired) { auto* context_ = reinterpret_cast<Context*>(context); // Sanity. assert(context_); // Mount isn't writable. if (!writable()) return STATUS_ACCESS_DENIED; // Inode isn't writable. if (context_->inode()->permissions() != FULL) return STATUS_ACCESS_DENIED; // Create a mutable copy of this inode's security descriptor. auto descriptor = mMountDB.mReadWriteSecurityDescriptor; // Try and perform the requested updates. auto result = descriptor.modify(desired, security); // Couldn't update the descriptor. if (result != ERROR_SUCCESS) return FspNtStatusFromWin32(result); // Make sure the descriptor hasn't changed. if (descriptor != mMountDB.mReadWriteSecurityDescriptor) return STATUS_ACCESS_DENIED; return STATUS_SUCCESS; } void Mount::stopped(BOOLEAN /*normally*/) {} NTSTATUS Mount::write(PVOID context, PVOID buffer, UINT64 offset, ULONG length, BOOLEAN append, BOOLEAN noGrow, ULONG& /*numWritten*/, FSP_FSCTL_FILE_INFO& /*info*/) { // Sanity. assert(context); // Get our hands on the file's context. auto* context_ = reinterpret_cast<Context*>(context)->file(); // Sanity. assert(context_); // Get our hands on the request's "hint." auto hint = mDispatcher.request().Hint; // Convenience. auto length_ = static_cast<m_off_t>(length); auto offset_ = static_cast<m_off_t>(offset); // Caller wants to write to the end of the file. if (append) offset_ = -1; // Actually perform the write. auto write = [=](Activity&, const Task&) { auto response = std::make_unique<FSP_FSCTL_TRANSACT_RSP>(); // Prepare for response. std::memset(response.get(), 0, sizeof(*response)); response->Hint = hint; response->Kind = FspFsctlTransactWriteKind; response->Size = sizeof(*response); // Try and write the data to the file. auto result = context_->write(buffer, length_, offset_, noGrow); // Couldn't write the data to the file. if (!result) return mDispatcher.reply(*response, result.error()); // Let the caller know how much data was written. response->IoStatus.Information = static_cast<UINT32>(*result); // Let the caller know the write was successful. mDispatcher.reply(*response, STATUS_SUCCESS); }; // write // Schedule write for execution. mExecutor.execute(std::bind(std::move(write), mActivities.begin(), std::placeholders::_1), true); // Let the caller know the write is underway. return STATUS_PENDING; } // Check if the request's originating process is this process and forbidden // Don't allow SDK to access the mount if the request is from itself as it will have deadlock issues // due to single-threaded execution loop of the SDK. // // @return true is self and is forbidden, otherwise false // // Note: FspFileSystemOperationProcessId only provides valid information for // getSecurityByName, open, and create operations. This may change in future // versions of the FSP bool Mount::isSelfForbidden() const { if (allowSelfAccess()) return false; const auto originatingPid = FspFileSystemOperationProcessId(); assert(originatingPid); return originatingPid != 0 && GetCurrentProcessId() == originatingPid; } Mount::Mount(const MountInfo& info, MountDB& mountDB): fuse::Mount(info, mountDB), mActivities(), mDispatcher(*this, info.mPath), mExecutor(mountDB.executorFlags(), logger()) { mDispatcher.start(info.mPath); // Let observers know the mount's functional. enabled(); FUSEDebugF("Mount constructed: %s", path().toPath(false).c_str()); } Mount::~Mount() { // Wait for all outstanding requests to complete. mActivities.waitUntilIdle(); // Shut down the dispatcher. mDispatcher.stop(); FUSEDebugF("Mount destroyed: %s", path().toPath(false).c_str()); } void Mount::invalidateAttributes(InodeID) {} void Mount::invalidateData(InodeID, m_off_t /*offset*/, m_off_t /*size*/) {} void Mount::invalidateData(InodeID) {} void Mount::invalidateEntry(const std::string& /*name*/, InodeID /*child*/, InodeID /*parent*/) {} void Mount::invalidateEntry(const std::string& /*name*/, InodeID /*parent*/) {} InodeID Mount::map(MountInodeID id) const { return InodeID(id); } MountInodeID Mount::map(InodeID id) const { return MountInodeID(id); } NormalizedPath Mount::path() const { return mDispatcher.path(); } MountResult Mount::remove() { // Remove the mount from memory. mMountDB.remove(*this); // Let the caller know the mount's been removed. return MOUNT_SUCCESS; } } // platform } // fuse } // mega ��sdk-10.11.0/src/fuse/supported/platform/windows/mount_db.cpp����������������������������������������0000664�0000000�0000000�00000007447�15162662266�0024072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/filesystem.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/directory_inode.h> #include <mega/fuse/common/inode.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/ref.h> #include <mega/fuse/platform/constants.h> #include <mega/fuse/platform/library.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/mount_db.h> #include <mega/fuse/platform/security_identifier.h> #include <mega/fuse/platform/service_context.h> #include <sstream> namespace mega { namespace fuse { namespace platform { using namespace common; MountResult MountDB::check(const Client& client, const MountInfo& info) const { // Convenience. auto& name = info.name(); auto& path = info.mPath; // Check if WinFSP is actually available. if (FspLoad(nullptr) != STATUS_SUCCESS) return MOUNT_BACKEND_UNAVAILABLE; // Check the mount's name. if (const auto ret = checkName(name); ret != MOUNT_SUCCESS) return ret; // An unspecified path signals we should assign a drive letter. if (path.empty()) return MOUNT_SUCCESS; // Make sure nothing exists at the path. auto fileAccess = client.fsAccess().newfileaccess(true); // Check if something already exists at the path. fileAccess->fopen(path, FSLogging::noLogging); // Convenience. auto result = fileAccess->errorcode; // Something already exists at the path. if (result == ERROR_SUCCESS) { FUSEErrorF("Local path is already occupied: %s", path.toPath(false).c_str()); return MOUNT_LOCAL_EXISTS; } // Some parent doesn't exist. if (result == ERROR_PATH_NOT_FOUND) { FUSEErrorF("Local path doesn't exist: %s", path.toPath(false).c_str()); return MOUNT_LOCAL_UNKNOWN; } // Nothing exists at the path. We're all good. if (result == ERROR_FILE_NOT_FOUND) return MOUNT_SUCCESS; // Couldn't determine whether anything exists at the path. FUSEErrorF("Couldn't determine status of path: %s: %d", path.toPath(false).c_str(), result); return MOUNT_UNEXPECTED; } MountResult MountDB::checkName(const std::string& name) const { if (name.empty()) { FUSEError1("No name specified"); return MOUNT_NO_NAME; } // Make sure the mount's name is within limits. if (name.size() > MaxMountNameLength) { FUSEErrorF("Name too long: %s (%lu > %lu)", name.c_str(), name.size(), MaxMountNameLength); return MOUNT_NAME_TOO_LONG; } // Make sure the mount's name contains no invalid characters // Refer https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file constexpr const char* invalidChars = "<>:\"/\\|?*"; if (name.find_first_of(invalidChars) != std::string::npos) { FUSEErrorF("Name contains invalid character(s): %s", name.c_str()); return MOUNT_NAME_INVALID_CHAR; } return MOUNT_SUCCESS; } MountDB::MountDB(ServiceContext& context): fuse::MountDB(context), mReadOnlySecurityDescriptor(readOnlySecurityDescriptor()), mReadWriteSecurityDescriptor(readWriteSecurityDescriptor()) { FUSEDebug1("Mount DB constructed"); } void MountDB::notifyFileExplorerSetter() { auto getPrefixes = [this]() { auto mounts = get(true); std::vector<std::wstring> prefixes; std::transform(mounts.begin(), mounts.end(), std::back_inserter(prefixes), [](const MountInfo& mount) { return mount.mPath.asPlatformEncoded(true); }); return prefixes; }; if (fileExplorerView() != FILE_EXPLORER_VIEW_NONE) mFileExplorerSetter.notify(getPrefixes); } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/security_descriptor.cpp�����������������������������0000664�0000000�0000000�00000014242�15162662266�0026357�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/windows.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/security_descriptor.h> #include <mega/fuse/platform/security_identifier.h> #include <cassert> #include <memory> #include <sddl.h> #include <sstream> #include <utility> namespace mega { namespace fuse { namespace platform { SecurityDescriptor::SecurityDescriptor(): mDescriptor() {} SecurityDescriptor::SecurityDescriptor(LocalPtr<void> descriptor): mDescriptor(std::move(descriptor)) {} SecurityDescriptor::SecurityDescriptor(const SecurityDescriptor& other): mDescriptor() { if (!other.mDescriptor) return; auto length = other.length(); mDescriptor.reset(LocalAlloc(LMEM_FIXED, length)); if (!mDescriptor) throw FUSEErrorF("Couldn't allocate security descriptor: %u", GetLastError()); std::memcpy(mDescriptor.get(), other.mDescriptor.get(), length); } SecurityDescriptor::operator bool() const { return !!mDescriptor; } bool SecurityDescriptor::operator!() const { return !mDescriptor; } SecurityDescriptor& SecurityDescriptor::operator=(const SecurityDescriptor& rhs) { if (this == &rhs) return *this; SecurityDescriptor temp(rhs); swap(temp); return *this; } bool SecurityDescriptor::operator==(const SecurityDescriptor& rhs) const { if (mDescriptor && rhs.mDescriptor) return EqualSid(mDescriptor.get(), rhs.mDescriptor.get()); return !mDescriptor == !rhs.mDescriptor; } bool SecurityDescriptor::operator!=(const SecurityDescriptor& rhs) const { return !(*this == rhs); } SecurityDescriptor SecurityDescriptor::fromString(const std::string& text) { return fromString(text.c_str()); } SecurityDescriptor SecurityDescriptor::fromString(const char* text) { assert(text); PSECURITY_DESCRIPTOR descriptor; ULONG length; auto result = ConvertStringSecurityDescriptorToSecurityDescriptorA(text, SDDL_REVISION_1, &descriptor, &length); if (!result) throw FUSEErrorF("Couldn't deserialize security descriptor: %u", GetLastError()); return SecurityDescriptor(LocalPtr<void>(descriptor)); } void* SecurityDescriptor::get() const { return mDescriptor.get(); } std::size_t SecurityDescriptor::length() const { if (mDescriptor) return GetSecurityDescriptorLength(mDescriptor.get()); return 0u; } std::uint32_t SecurityDescriptor::modify(const SecurityDescriptor& modifications, std::uint32_t mask) { return modify(modifications.get(), mask); } std::uint32_t SecurityDescriptor::modify(void* modifications, std::uint32_t mask) { static GENERIC_MAPPING mapping = {FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS}; // mapping assert(modifications); // Get our hands on the process' heap. auto heap = GetProcessHeap(); // How large is this descriptor? auto length = this->length(); // Create a mutable copy of this descriptor. auto* modified = HeapAlloc(heap, 0, length); // Couldn't create a mutable copy of this descriptor. if (!modified) return GetLastError(); std::memcpy(modified, mDescriptor.get(), length); // Can't update the descriptor. if (!SetPrivateObjectSecurity(mask, modifications, &modified, &mapping, nullptr)) { // Release the mutable copy. HeapFree(heap, 0, modified); // Let the caller know why we failed. return GetLastError(); } // How large is the updated descriptor? length = GetSecurityDescriptorLength(modified); // Allocate a permanent home for the updated descriptor. LocalPtr<void> descriptor(LocalAlloc(LMEM_FIXED, length)); // Couldn't allocate permanent home. if (!descriptor) { // Release updated descriptor. DestroyPrivateObjectSecurity(&modified); // Let the caller knwo why we failed. return GetLastError(); } // Copy updated descriptor to new home. std::memcpy(descriptor.get(), modified, length); // Release updated descriptor. DestroyPrivateObjectSecurity(&modified); // Swap descriptor. mDescriptor = std::move(descriptor); // Let the caller know the update was successful. return ERROR_SUCCESS; } void* SecurityDescriptor::release() { return mDescriptor.release(); } void SecurityDescriptor::reset(void* descriptor) { mDescriptor.reset(descriptor); } void SecurityDescriptor::swap(SecurityDescriptor& other) { using std::swap; swap(mDescriptor, other.mDescriptor); } SecurityDescriptor readOnlySecurityDescriptor() { std::ostringstream ostream; ostream << "O:" << toString(SecurityIdentifier::user()) << "G:" << toString(SecurityIdentifier::group()) << "D:P" << "(A;;FRFX;;;WD)"; return SecurityDescriptor::fromString(ostream.str()); } SecurityDescriptor readWriteSecurityDescriptor() { auto user = toString(SecurityIdentifier::user()); std::ostringstream ostream; ostream << "O:" << user << "G:" << toString(SecurityIdentifier::group()) << "D:P" << "(A;;FA;;;" << user << ")" << "(A;;FRFX;;;WD)"; return SecurityDescriptor::fromString(ostream.str()); } void swap(SecurityDescriptor& lhs, SecurityDescriptor& rhs) { lhs.swap(rhs); } std::string toString(const SecurityDescriptor& descriptor) { if (!descriptor) return std::string(); PSTR text; auto result = ConvertSecurityDescriptorToStringSecurityDescriptorA( descriptor.get(), SDDL_REVISION_1, DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, &text, nullptr); if (!result) throw FUSEErrorF("Couldn't serialize security descriptor: %u", GetLastError()); LocalPtr<CHAR> text_(text); return std::string(text_.get()); } } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/security_identifier.cpp�����������������������������0000664�0000000�0000000�00000011121�15162662266�0026314�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/windows.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/security_identifier.h> #include <cassert> #include <cstring> #include <sddl.h> namespace mega { namespace fuse { namespace platform { static LocalPtr<void> get(TOKEN_INFORMATION_CLASS type); SecurityIdentifier::SecurityIdentifier(): mIdentifier() {} SecurityIdentifier::SecurityIdentifier(void* identifier): mIdentifier(identifier) {} SecurityIdentifier::SecurityIdentifier(const SecurityIdentifier& other): mIdentifier() { if (!other) return; auto length = GetLengthSid(other.mIdentifier.get()); mIdentifier.reset(LocalAlloc(LMEM_FIXED, length)); if (!mIdentifier) throw FUSEErrorF("Couldn't allocate security identifier: %u", GetLastError()); if (!CopySid(length, mIdentifier.get(), other.mIdentifier.get())) throw FUSEErrorF("Couldn't copy security identifier: %u", GetLastError()); } SecurityIdentifier::operator bool() const { return !!mIdentifier; } bool SecurityIdentifier::operator!() const { return !mIdentifier; } SecurityIdentifier& SecurityIdentifier::operator=(const SecurityIdentifier& rhs) { if (this == &rhs) return *this; SecurityIdentifier temp(rhs); swap(temp); return *this; } SecurityIdentifier SecurityIdentifier::fromString(const std::string& text) { return fromString(text.c_str()); } SecurityIdentifier SecurityIdentifier::fromString(const char* text) { assert(text); PSID sid; if (!ConvertStringSidToSidA(text, &sid)) throw FUSEErrorF("Couldn't deserialize security identifier: %u", GetLastError()); return SecurityIdentifier(sid); } void* SecurityIdentifier::get() const { return mIdentifier.get(); } SecurityIdentifier SecurityIdentifier::group() { auto identifier = platform::get(TokenPrimaryGroup); if (!identifier) throw FUSEErrorF("Couldn't retrieve group security identifier: %u", GetLastError()); return SecurityIdentifier(identifier.release()); } std::size_t SecurityIdentifier::length() const { assert(mIdentifier); return GetLengthSid(mIdentifier.get()); } void* SecurityIdentifier::release() { return mIdentifier.release(); } void SecurityIdentifier::reset(void* identifier) { mIdentifier.reset(identifier); } void SecurityIdentifier::swap(SecurityIdentifier& other) { using std::swap; swap(mIdentifier, other.mIdentifier); } SecurityIdentifier SecurityIdentifier::user() { auto identifier = platform::get(TokenOwner); if (!identifier) throw FUSEErrorF("Couldn't retrieve user security identifier: %u", GetLastError()); return SecurityIdentifier(identifier.release()); } void swap(SecurityIdentifier& lhs, SecurityIdentifier& rhs) { lhs.swap(rhs); } std::string toString(const SecurityIdentifier& identifier) { assert(identifier); PSTR text; if (!ConvertSidToStringSidA(identifier.get(), &text)) throw FUSEErrorF("Couldn't serialize security identifier: %u", GetLastError()); LocalPtr<CHAR> text_(text); return std::string(text_.get()); } LocalPtr<void> get(TOKEN_INFORMATION_CLASS type) { // Sanity. assert(type == TokenOwner || type == TokenPrimaryGroup); // Get our hands on this process's handle. auto handle = GetCurrentProcess(); // Try and get our hands on the thread's token. if (!OpenProcessToken(handle, TOKEN_QUERY, &handle)) return nullptr; auto required = 0ul; // Try and determine how much buffer space we need. auto result = GetTokenInformation(handle, type, nullptr, 0, &required); // Couldn't determine buffer requirement. if (!result && GetLastError() != ERROR_INSUFFICIENT_BUFFER) return nullptr; // Try and allocate memory for our buffer. LocalPtr<void*> temp(static_cast<void**>(LocalAlloc(LMEM_FIXED, required))); // Couldn't allocate memory for buffer. if (!temp) return nullptr; // Try and retrieve requested information. result = GetTokenInformation(handle, type, temp.get(), required, &required); // Couldn't retrieve information. if (!result) return nullptr; // How large is the SID we've retrieved? required = GetLengthSid(*temp); // Try and allocate buffer for SID. LocalPtr<void> identifier(LocalAlloc(LMEM_FIXED, required)); // Couldn't allocate buffer for SID. if (!identifier) return nullptr; // Copy SID from temporary buffer. if (!CopySid(required, identifier.get(), *temp)) return nullptr; // Return SID to caller. return identifier; } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/service.cpp�����������������������������������������0000664�0000000�0000000�00000000323�15162662266�0023705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> namespace mega { namespace fuse { MountResult Service::abort(AbortPredicate) { return MOUNT_SUCCESS; } } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/shell.cpp�������������������������������������������0000664�0000000�0000000�00000007146�15162662266�0023366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/windows.h> #include <mega/fuse/platform/shell.h> #include <mega/scoped_helpers.h> #include <mega/utils.h> // Disable for the including order // clang-format off #include <ExDisp.h> #include <ShlObj.h> #include <ShlObj_core.h> #include <ShObjIdl.h> // clang-format on namespace mega { namespace fuse { namespace platform { namespace shell { template<typename T> class ComPtr { public: ComPtr() = default; ComPtr(const ComPtr&) = delete; ComPtr& operator=(ComPtr&) = delete; bool operator!() const { return p == nullptr; } T* operator->() const { assert(p); return p; } // Avoid misuse. Take its address only when p is nullptr T** operator&() { assert(p == nullptr); return &p; } T* get() const { return p; } ~ComPtr() { if (p) p->Release(); } private: T* p{nullptr}; }; bool isMatchedShell(IShellView* shellView, const Prefixes& prefixes) { ComPtr<IFolderView> folderView; if (FAILED(shellView->QueryInterface(IID_PPV_ARGS(&folderView)))) return false; ComPtr<IPersistFolder2> persistFolder; if (FAILED(folderView->GetFolder(IID_PPV_ARGS(&persistFolder)))) return false; LPITEMIDLIST idl = nullptr; if (FAILED(persistFolder->GetCurFolder(&idl))) return false; const auto idlReleaser = makeScopedDestructor( [&idl]() { CoTaskMemFree(idl); idl = nullptr; }); WCHAR szPath[MAX_PATH]; if (!SHGetPathFromIDListW(idl, szPath)) return false; const auto p = std::wstring(szPath); auto isStartedWith = [&p](const std::wstring& prefix) { return Utils::startswith(p, prefix); }; return std::any_of(prefixes.begin(), prefixes.end(), isStartedWith); } void setToListView(IShellView* shellView) { // Query IFolderView2 ComPtr<IFolderView2> folderView2; auto hr = shellView->QueryInterface(IID_PPV_ARGS(&folderView2)); if (FAILED(hr)) return; // Set view mode to List View folderView2->SetViewModeAndIconSize(FVM_LIST, -1); } bool initialize() { return !FAILED(CoInitialize(NULL)); } void setView(const Prefixes& prefixes) { // Get the desktop Shell windows interface ComPtr<IShellWindows> windows; auto hr = CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&windows)); if (FAILED(hr)) return; // Iterate through the shell windows to find one that's a folder long count = 0; windows->get_Count(&count); for (long i = 0; i < count; i++) { // Get the window's IDispath interface ComPtr<IDispatch> disp; VARIANT v = {{{VT_I4}}}; v.lVal = i; windows->Item(v, &disp); if (!disp) continue; // Get the IServiceProvider interface from the window ComPtr<IServiceProvider> serviceProvider; if (FAILED(disp->QueryInterface(IID_PPV_ARGS(&serviceProvider)))) continue; // Get IShellBrowser ComPtr<IShellBrowser> shellBrowser; if (FAILED( serviceProvider->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&shellBrowser)))) continue; // Get the active IShellView ComPtr<IShellView> shellView; if (FAILED(shellBrowser->QueryActiveShellView(&shellView))) continue; if (!isMatchedShell(shellView.get(), prefixes)) continue; setToListView(shellView.get()); } } void deinitialize() { CoUninitialize(); } } // shell } // platform } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/��������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0023220�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/CMakeLists.txt������������������������������0000664�0000000�0000000�00000000036�15162662266�0025757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(integration) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/��������������������������������0000775�0000000�0000000�00000000000�15162662266�0025543�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/CMakeLists.txt������������������0000664�0000000�0000000�00000000754�15162662266�0030311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������if(NOT TARGET test_integration) return() endif() target_include_directories(test_integration PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(test_integration PRIVATE directory_monitor.cpp mount_tests.cpp platform_tests.cpp printers.cpp sync_tests.cpp wrappers.cpp ) add_subdirectory(mega) ��������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/directory_monitor.cpp�����������0000664�0000000�0000000�00000013167�15162662266�0032032�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/windows.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/testing/directory_monitor.h> #include <mega/fuse/platform/testing/wrappers.h> #include <mega/fuse/platform/utility.h> #include <algorithm> #include <cassert> #include <cstring> #include <functional> namespace mega { namespace fuse { namespace testing { using namespace common::testing; using namespace common::platform; using namespace platform; constexpr auto BUFFER_LENGTH = 32768; struct DirectoryMonitor::Buffer { OVERLAPPED mOverlapped; char mStorage[BUFFER_LENGTH]; }; // DirectoryMonitor::Buffer void DirectoryMonitor::emit(const DirectoryEvent& event) { std::lock_guard<std::mutex> guard(mLock); auto i = std::find(mExpectations.begin(), mExpectations.end(), event); if (i == mExpectations.end()) return; mExpectations.erase(i); mCV.notify_all(); } void DirectoryMonitor::loop() { // Make sure overlapped buffer is initialized. std::memset(&mBuffer->mOverlapped, 0, sizeof(mBuffer->mOverlapped)); // Convenience. auto failed = [this]() { FUSEErrorF("Couldn't retrieve directory notifications: %u", GetLastError()); }; // failed for (std::wstring from;;) { // Convenience. constexpr auto filter = FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SECURITY | FILE_NOTIFY_CHANGE_SIZE; // Ask the system for a list of directory notifications. auto result = ReadDirectoryChangesW(mDirectory.get(), mBuffer->mStorage, sizeof(mBuffer->mStorage), true, filter, nullptr, &mBuffer->mOverlapped, nullptr); // Couldn't retrieve directory notifications. if (!result) return failed(); ULONG_PTR key; DWORD num; OVERLAPPED* overlapped; // Wait for the system to post our result. result = GetQueuedCompletionStatus(mPort.get(), &num, &key, &overlapped, INFINITE); // Couldn't wait for the result. if (!result) return failed(); // We've been asked to terminate. if (key == 'T') return; // Sanity. assert(key == 'F'); // There were too many changes for the system to report. if (!num) continue; auto* position = mBuffer->mStorage; auto* info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(position); // Process directory notifications. while (true) { auto length = info->FileNameLength / sizeof(wchar_t); // Sanity. assert(length); auto to = std::wstring(info->FileName, length); if (info->Action != FILE_ACTION_RENAMED_OLD_NAME) { DirectoryEvent event; event.mFrom = fromWideString(from); event.mTo = fromWideString(to); event.mType = info->Action; emit(event); from.clear(); } else { from = std::move(to); } if (!info->NextEntryOffset) break; position += info->NextEntryOffset; info = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(position); } } } DirectoryMonitor::DirectoryMonitor(const Path& path): mBuffer(new Buffer()), mCV(), mDirectory(), mExpectations(), mLock(), mPort(), mWorker() { // Try and open specified directory. auto directory = CreateFileP(path, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, Handle<>()); // Couldn't open specified directory. if (!directory) throw FUSEErrorF("Couldn't open directory: %s: %u", path.path().u8string().c_str(), GetLastError()); // Try and create an IO completion port. Handle<> port(CreateIoCompletionPort(directory.get(), nullptr, 'F', 0)); // Couldn't create IO completion port. if (!port) throw FUSEErrorF("Couldn't create IO completion port: %u", GetLastError()); // Spawn worker thread. mWorker = std::thread(std::bind(&DirectoryMonitor::loop, this)); // Latch directory and completion port. mDirectory = std::move(directory); mPort = std::move(port); } DirectoryMonitor::~DirectoryMonitor() { // This should never fail. if (!PostQueuedCompletionStatus(mPort.get(), 0, 'T', nullptr)) FUSEErrorF("Couldn't notify completion port: %u", GetLastError()); // Wait for the worker to terminate. mWorker.join(); } void DirectoryMonitor::expect(DirectoryEvent event) { std::lock_guard<std::mutex> guard(mLock); mExpectations.emplace_back(std::move(event)); } bool DirectoryMonitor::wait(std::chrono::steady_clock::time_point until) { std::unique_lock lock(mLock); return mCV.wait_until(lock, until) == std::cv_status::no_timeout; } } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/���������������������������0000775�0000000�0000000�00000000000�15162662266�0026454�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/CMakeLists.txt�������������0000664�0000000�0000000�00000000027�15162662266�0031213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/����������������������0000775�0000000�0000000�00000000000�15162662266�0027416�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/CMakeLists.txt��������0000664�0000000�0000000�00000000033�15162662266�0032152�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/�������������0000775�0000000�0000000�00000000000�15162662266�0031242�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CMakeLists.txt��������������������������������������������������������������������������������������0000664�0000000�0000000�00000000032�15162662266�0033716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform���������������������������������������������������������������������������������add_subdirectory(testing) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/testing/�����0000775�0000000�0000000�00000000000�15162662266�0032717�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������CMakeLists.txt��������������������������������������������������������������������������������������0000664�0000000�0000000�00000000264�15162662266�0035402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/testing�������������������������������������������������������������������������target_sources(test_integration PRIVATE directory_monitor.h printers.h wrappers.h ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������directory_monitor.h���������������������������������������������������������������������������������0000664�0000000�0000000�00000002502�15162662266�0036563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/testing�������������������������������������������������������������������������#pragma once #include <mega/common/platform/handle.h> #include <mega/common/testing/path_forward.h> #include <atomic> #include <chrono> #include <condition_variable> #include <list> #include <memory> #include <mutex> #include <thread> namespace mega { namespace fuse { namespace testing { struct DirectoryEvent { bool operator==(const DirectoryEvent& rhs) const { return mType == rhs.mType && mFrom == rhs.mFrom && mTo == rhs.mTo; } common::testing::Path mFrom; common::testing::Path mTo; unsigned long mType; }; // DirectoryEvent class DirectoryMonitor { struct Buffer; void emit(const DirectoryEvent& event); void loop(); std::unique_ptr<Buffer> mBuffer; std::condition_variable mCV; common::platform::Handle<> mDirectory; std::list<DirectoryEvent> mExpectations; std::mutex mLock; common::platform::Handle<> mPort; std::thread mWorker; public: DirectoryMonitor(const common::testing::Path& path); ~DirectoryMonitor(); void expect(DirectoryEvent event); bool wait(std::chrono::steady_clock::time_point until); template<typename Rep, typename Period> bool wait(std::chrono::duration<Rep, Period> delay) { return wait(std::chrono::steady_clock::now() + delay); } }; // DirectoryMonitor } // testing } // fuse } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������printers.h������������������������������������������������������������������������������������������0000664�0000000�0000000�00000001264�15162662266�0034662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/testing�������������������������������������������������������������������������#pragma once #include <mega/common/platform/windows.h> #include <mega/fuse/common/testing/printers.h> #include <mega/fuse/platform/security_descriptor_forward.h> #include <mega/fuse/platform/testing/wrappers.h> void PrintTo(const BY_HANDLE_FILE_INFORMATION& info, std::ostream* ostream); void PrintTo(const FILETIME& value, std::ostream* ostream); void PrintTo(const WIN32_FILE_ATTRIBUTE_DATA& info, std::ostream* ostream); namespace mega { namespace fuse { namespace platform { void PrintTo(const SecurityDescriptor& descriptor, std::ostream* ostream); } // platform namespace testing { void PrintTo(const FileTimes& value, std::ostream* ostream); } // testing } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������wrappers.h������������������������������������������������������������������������������������������0000664�0000000�0000000�00000010711�15162662266�0034654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mega/fuse/platform/testing�������������������������������������������������������������������������#pragma once #include <mega/common/node_info_forward.h> #include <mega/common/platform/handle_forward.h> #include <mega/common/platform/windows.h> #include <mega/common/testing/path_forward.h> #include <mega/common/type_traits.h> #include <mega/fuse/common/inode_info_forward.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/security_descriptor_forward.h> #include <mega/fuse/platform/utility.h> #include <optional> template<typename T> using IsNativeInfoLike = ::mega::common::IsOneOf<T, BY_HANDLE_FILE_INFORMATION, WIN32_FILE_ATTRIBUTE_DATA>; template<typename T> using IsAnyInfoLike = std::integral_constant<bool, ::mega::fuse::testing::IsInfoLike<T>::value || IsNativeInfoLike<T>::value>; template<typename T, typename... Ts> using AreAnyInfoLike = ::mega::common::AllOf<IsAnyInfoLike, T, Ts...>; template<typename T, typename... Ts> using AreNativeInfoLike = ::mega::common::AllOf<IsNativeInfoLike, T, Ts...>; bool operator==(const BY_HANDLE_FILE_INFORMATION& lhs, const BY_HANDLE_FILE_INFORMATION& rhs); bool operator==(const BY_HANDLE_FILE_INFORMATION& lhs, const WIN32_FILE_ATTRIBUTE_DATA& rhs); bool operator==(const WIN32_FILE_ATTRIBUTE_DATA& lhs, const BY_HANDLE_FILE_INFORMATION& rhs); template<typename T, typename U> auto operator==(const T& lhs, const U& rhs) -> typename std:: enable_if<IsNativeInfoLike<T>::value&& ::mega::fuse::testing::IsInfoLike<U>::value, bool>::type { return rhs == lhs; } template<typename T, typename U> auto operator!=(const T& lhs, const U& rhs) -> typename std::enable_if<AreAnyInfoLike<T, U>::value, bool>::type { return !(lhs == rhs); } namespace mega { namespace common { template<typename T> auto operator==(const NodeInfo& lhs, const T& rhs) -> std::enable_if_t<IsNativeInfoLike<T>::value, bool>; } // common namespace fuse { template<typename T> auto operator==(const T& lhs, const BY_HANDLE_FILE_INFORMATION& rhs) -> typename std::enable_if<testing::IsInfoLike<T>::value, bool>::type; template<typename T> auto operator==(const T& lhs, const WIN32_FILE_ATTRIBUTE_DATA& rhs) -> typename std::enable_if<testing::IsInfoLike<T>::value, bool>::type; namespace testing { struct FileTimes { FILETIME mAccessed; FILETIME mCreated; FILETIME mWritten; }; // FileTimes struct FindHandleDeleter { void operator()(HANDLE handle) { FindClose(handle); } }; // FindHandleDeleter struct VolumeInfo { std::string mVolumeName; std::string mFilesystemName; }; // VolumeInfo using FindHandle = common::platform::Handle<FindHandleDeleter>; bool operator==(const FileTimes& lhs, const FileTimes& rhs); bool operator!=(const FileTimes& lhs, const FileTimes& rhs); BOOL CreateDirectoryP(const common::testing::Path& path, LPSECURITY_ATTRIBUTES securityAttributes); common::platform::Handle<> CreateFileP(const common::testing::Path& path, DWORD desiredAccess, DWORD shareMode, LPSECURITY_ATTRIBUTES securityAttributes, DWORD creationDisposition, DWORD flagsAndAttributes, const common::platform::Handle<>& templateFile); BOOL DeleteFileP(const common::testing::Path& path); FindHandle FindFirstFileP(const common::testing::Path& path, LPWIN32_FIND_DATAW info); DWORD GetFileAttributesP(const common::testing::Path& path); BOOL GetFileAttributesExP(const common::testing::Path& path, GET_FILEEX_INFO_LEVELS level, LPVOID info); BOOL GetFileInformationByPath(const common::testing::Path& path, BY_HANDLE_FILE_INFORMATION& info); platform::SecurityDescriptor GetFileSecurityP(const common::testing::Path& path); long GetLastError(); std::optional<VolumeInfo> GetVolumeInformationByPath(const common::testing::Path& path); BOOL MoveFileExP(const common::testing::Path& source, const common::testing::Path& target, DWORD flags); BOOL RemoveDirectoryP(const common::testing::Path& path); BOOL SetFileAttributesP(const common::testing::Path& path, DWORD attributes); BOOL SetFileSecurityP(const common::testing::Path& path, const platform::SecurityDescriptor& descriptor); bool flushFile(const common::testing::Path& path); } // testing } // fuse } // mega �������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/mount_tests.cpp�����������������0000664�0000000�0000000�00000006577�15162662266�0030652�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/testing/directory.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/mount_event_observer.h> #include <mega/fuse/common/testing/mount_tests.h> #include <mega/fuse/platform/constants.h> namespace mega { namespace fuse { namespace testing { using platform::MaxMountNameLength; TEST_F(FUSEMountTests, add_fails_when_name_contains_illegal_characters) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s|a"); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_NAME_INVALID_CHAR, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_NAME_INVALID_CHAR); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_fails_when_name_is_too_long) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.mFlags.mName = std::string(MaxMountNameLength + 1, 'a'); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_NAME_TOO_LONG, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_NAME_TOO_LONG); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_TRUE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, add_succeeds_when_target_is_unspecified) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); ASSERT_FALSE(ClientW()->mounts(false).empty()); } TEST_F(FUSEMountTests, enable_succeeds_with_long_name) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.mFlags.mName = std::string(MaxMountNameLength, 'a'); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_SUCCESS); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(info.name(), false), MOUNT_SUCCESS); } TEST_F(FUSEMountTests, enable_succeeds_when_target_is_empty) { auto handle = ClientW()->handle("/x/s"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); MountInfo info; info.mHandle = *handle; info.name("s"); auto observer = ClientW()->mountEventObserver(); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ADDED}); ASSERT_EQ(ClientW()->addMount(info), MOUNT_SUCCESS); ASSERT_TRUE(observer->wait(mDefaultTimeout)); observer->expect({info.name(), MOUNT_SUCCESS, MOUNT_ENABLED}); ASSERT_EQ(ClientW()->enableMount(info.name(), false), MOUNT_SUCCESS); auto path = ClientW()->mountPath(info.name()); ASSERT_FALSE(path.empty()); } } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/platform_tests.cpp��������������0000664�0000000�0000000�00000141046�15162662266�0031323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/node_info.h> #include <mega/common/platform/date_time.h> #include <mega/common/platform/handle.h> #include <mega/common/testing/cloud_path.h> #include <mega/common/testing/directory.h> #include <mega/common/testing/model.h> #include <mega/common/testing/path.h> #include <mega/common/testing/utility.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/testing/client.h> #include <mega/fuse/common/testing/utility.h> #include <mega/fuse/platform/security_descriptor.h> #include <mega/fuse/platform/security_identifier.h> #include <mega/fuse/platform/testing/directory_monitor.h> #include <mega/fuse/platform/testing/platform_tests.h> #include <mega/fuse/platform/testing/printers.h> #include <mega/fuse/platform/testing/wrappers.h> #include <mega/fuse/platform/utility.h> namespace mega { namespace fuse { namespace testing { using common::DateTime; using common::platform::Handle; using common::testing::Directory; using common::testing::Model; using common::testing::randomBytes; using common::testing::randomName; using common::testing::waitFor; using platform::fromWideString; using platform::readOnlySecurityDescriptor; using platform::readWriteSecurityDescriptor; using platform::SecurityDescriptor; using platform::SecurityIdentifier; using namespace common; union FileInfo { BY_HANDLE_FILE_INFORMATION mByHandle; WIN32_FILE_ATTRIBUTE_DATA mByPath; }; // FileInfo TEST_P(FUSEPlatformTests, create_directory_fails_when_below_file) { EXPECT_FALSE(CreateDirectoryP(MountPathW() / "sf0" / "sdx", nullptr)); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, create_directory_fails_when_read_only) { EXPECT_FALSE(CreateDirectoryP(MountPathR() / "sdx", nullptr)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_FALSE(fs::exists(MountPathR() / "sdx", error)); } TEST_P(FUSEPlatformTests, create_directory_fails_when_unknown) { EXPECT_FALSE(CreateDirectoryP(MountPathW() / "sdx" / "sdy", nullptr)); EXPECT_EQ(GetLastError(), ERROR_PATH_NOT_FOUND); } TEST_P(FUSEPlatformTests, create_directory_succeeds) { EXPECT_TRUE(CreateDirectoryP(MountPathW() / "sdx", nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { // Make sure the new directory's visible in the cloud. auto info = ClientW()->get("/x/s/sdx"); // Directory isn't in the cloud. if (!info) return false; // Wrong name or type. if (info->mName != "sdx" || !info->mIsDirectory) return false; std::error_code error; // Make sure the new directory is visible under observer. return fs::is_directory(MountPathO() / "sdx", error) && !error; }, mDefaultTimeout)); } TEST_P(FUSEPlatformTests, create_file_fails_when_below_file) { EXPECT_FALSE(CreateFileP(MountPathW() / "sf0" / "sfy", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, create_file_fails_when_read_only) { EXPECT_FALSE( CreateFileP(MountPathR() / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); } TEST_P(FUSEPlatformTests, create_file_fails_when_unknown) { EXPECT_FALSE(CreateFileP(MountPathW() / "sdx" / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_PATH_NOT_FOUND); } TEST_P(FUSEPlatformTests, create_file_succeeds) { auto handle = CreateFileP(MountPathW() / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); FileInfo fileInfo; EXPECT_TRUE(GetFileInformationByHandle(handle.get(), &fileInfo.mByHandle)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeLow); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeHigh); EXPECT_TRUE(waitFor( [&]() { return GetFileAttributesExP(MountPathO() / "sfx", GetFileExInfoStandard, &fileInfo.mByPath) && GetLastError() == ERROR_SUCCESS && !fileInfo.mByPath.nFileSizeLow && !fileInfo.mByPath.nFileSizeHigh; }, mDefaultTimeout)); EXPECT_TRUE( GetFileAttributesExP(MountPathO() / "sfx", GetFileExInfoStandard, &fileInfo.mByPath)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(fileInfo.mByPath.nFileSizeLow); EXPECT_FALSE(fileInfo.mByPath.nFileSizeHigh); EXPECT_TRUE(FlushFileBuffers(handle.get())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); handle.reset(); EXPECT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sfx"); return info && !info->mIsDirectory && !info->mSize; }, mDefaultTimeout)); EXPECT_TRUE(DeleteFileP(MountPathW() / "sfx")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, delete_file_fails_when_below_file) { EXPECT_FALSE(DeleteFileP(MountPathW() / "sf0" / "sfx")); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, delete_file_fails_when_directory) { EXPECT_FALSE(DeleteFileP(MountPathW() / "sd0")); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, delete_file_fails_when_read_only) { EXPECT_FALSE(DeleteFileP(MountPathR() / "sf0")); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_TRUE(fs::exists(MountPathR() / "sf0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, delete_file_fails_when_unknown) { EXPECT_FALSE(DeleteFileP(MountPathW() / "sfx")); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, delete_file_succeeds) { EXPECT_TRUE(DeleteFileP(MountPathW() / "sf0")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && !fs::exists(MountPathO() / "sf0", error); }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sf0")); EXPECT_FALSE(fs::exists(MountPathO() / "sf0")); } TEST_P(FUSEPlatformTests, find_first_file_fails_when_no_match) { auto info = WIN32_FIND_DATAW(); auto handle = FindFirstFileP(MountPathW() / "x*", &info); EXPECT_FALSE(handle); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, find_first_file_succeeds_when_singular) { auto sf0 = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(sf0); auto info = WIN32_FIND_DATAW(); auto handle = FindFirstFileP(MountPathW() / "sf0", &info); EXPECT_TRUE(handle); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); auto& info_ = reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA&>(info); EXPECT_EQ(info_, *sf0); } TEST_P(FUSEPlatformTests, find_first_file_succeeds) { std::map<std::string, NodeInfo> expectations; { auto s = ClientW()->get("/x/s"); ASSERT_EQ(s.errorOr(API_OK), API_OK); expectations[".."] = *s; auto sd0 = ClientW()->get("/x/s/sd0"); ASSERT_EQ(sd0.errorOr(API_OK), API_OK); expectations["."] = *sd0; auto names = ClientW()->childNames(sd0->mHandle); ASSERT_EQ(names.errorOr(API_OK), API_OK); for (const auto& name: *names) { auto child = ClientW()->get(sd0->mHandle, name); ASSERT_TRUE(child); expectations[child->mName] = *child; } ASSERT_GT(expectations.size(), 2u); } auto info = WIN32_FIND_DATAW(); auto handle = FindFirstFileP(MountPathW() / "sd0" / "*", &info); EXPECT_TRUE(handle); while (GetLastError() == ERROR_SUCCESS) { auto name = fromWideString(info.cFileName); EXPECT_FALSE(name.empty()); if (name.empty()) continue; auto i = expectations.find(name); EXPECT_NE(i, expectations.end()) << "Couldn't locate directory entry for: " << name; if (i == expectations.end()) continue; auto& attributes = reinterpret_cast<WIN32_FILE_ATTRIBUTE_DATA&>(info); ASSERT_EQ(attributes, i->second); if (attributes != i->second) continue; expectations.erase(i); FindNextFileW(handle.get(), &info); } EXPECT_EQ(GetLastError(), ERROR_NO_MORE_FILES); EXPECT_TRUE(expectations.empty()); } TEST_P(FUSEPlatformTests, get_file_attributes_fails_when_below_file) { auto buffer = WIN32_FILE_ATTRIBUTE_DATA(); EXPECT_FALSE( GetFileAttributesExP(MountPathW() / "sf0" / "sdx", GetFileExInfoStandard, &buffer)); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, get_file_attributes_fails_when_unknown) { auto buffer = WIN32_FILE_ATTRIBUTE_DATA(); EXPECT_FALSE(GetFileAttributesExP(MountPathW() / "sdx", GetFileExInfoStandard, &buffer)); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, get_file_attributes_succeeds) { auto buffer = WIN32_FILE_ATTRIBUTE_DATA(); auto info = ClientRS()->describe(MountPathR() / "sd0"); ASSERT_TRUE(info); EXPECT_TRUE(GetFileAttributesExP(MountPathR() / "sd0", GetFileExInfoStandard, &buffer)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(buffer, *info); info = ClientWS()->describe(MountPathW() / "sf0"); ASSERT_TRUE(info); EXPECT_TRUE(GetFileAttributesExP(MountPathW() / "sf0", GetFileExInfoStandard, &buffer)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(buffer, *info); info = ClientRS()->describe(MountPathR() / "sf0"); ASSERT_TRUE(info); EXPECT_TRUE(GetFileAttributesExP(MountPathR() / "sf0", GetFileExInfoStandard, &buffer)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(buffer, *info); } TEST_P(FUSEPlatformTests, get_file_security_fails_when_below_file) { EXPECT_FALSE(GetFileSecurityP(MountPathW() / "sf0" / "x")); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, get_file_security_fails_when_unknown) { EXPECT_FALSE(GetFileSecurityP(MountPathW() / "x")); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, get_file_security_succeeds) { auto expected = toString(platform::readOnlySecurityDescriptor()); auto computed = GetFileSecurityP(MountPathR() / "sd0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(toString(computed), expected); computed = GetFileSecurityP(MountPathR() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(toString(computed), expected); expected = toString(platform::readWriteSecurityDescriptor()); computed = GetFileSecurityP(MountPathW() / "sd0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(toString(computed), expected); computed = GetFileSecurityP(MountPathW() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(toString(computed), expected); } TEST_P(FUSEPlatformTests, get_volume_information_succeeds) { auto info = GetVolumeInformationByPath(MountPathW()); EXPECT_TRUE(info); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); if (HasFailure()) return; EXPECT_EQ(info->mFilesystemName, "WinFsp"); using ::testing::StrCaseEq; EXPECT_THAT(info->mVolumeName, StrCaseEq("s")); } TEST_P(FUSEPlatformTests, move_fails_when_below_file) { EXPECT_FALSE(MoveFileExP(MountPathW() / "sd0", MountPathW() / "sf0" / "sd0", 0)); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); EXPECT_FALSE(MoveFileExP(MountPathW() / "sf0" / "sd0", MountPathW() / "sd0", 0)); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, move_fails_when_read_only) { EXPECT_FALSE(MoveFileExP(MountPathR() / "sf0", MountPathR() / "sd0" / "sf0", 0)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_TRUE(fs::exists(MountPathR() / "sf0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, move_fails_when_target_exists) { EXPECT_FALSE(MoveFileExP(MountPathW() / "sf0", MountPathW() / "sf1", 0)); EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sf0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, move_fails_when_unknown) { EXPECT_FALSE(MoveFileExP(MountPathW() / "sfx", MountPathW() / "sd0" / "sfx", 0)); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, move_move_succeeds) { BY_HANDLE_FILE_INFORMATION before; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sd0", before)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sd0", MountPathW() / "sd1" / "sd0", 0)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION after; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sd1" / "sd0", after)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sd0/sd0d0") && ClientW()->get("/x/s/sd1/sd0/sd0d0") && !fs::exists(MountPathO() / "sd0" / "sd0d0", error) && fs::exists(MountPathO() / "sd1" / "sd0" / "sd0d0", error); }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sd0/sd0d0")); EXPECT_TRUE(ClientW()->get("/x/s/sd1/sd0/sd0d0")); EXPECT_FALSE(fs::exists(MountPathO() / "sd0" / "sd0d0", error)); EXPECT_TRUE(fs::exists(MountPathO() / "sd1" / "sd0" / "sd0d0", error)); } TEST_P(FUSEPlatformTests, move_rename_succeeds) { BY_HANDLE_FILE_INFORMATION before; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sf0", before)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sf0", MountPathW() / "sfx", 0)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION after; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sfx", after)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && ClientW()->get("/x/s/sfx") && !fs::exists(MountPathO() / "sf0", error) && fs::exists(MountPathO() / "sfx", error); }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sf0")); EXPECT_TRUE(ClientW()->get("/x/s/sfx")); EXPECT_FALSE(fs::exists(MountPathO() / "sf0", error)); EXPECT_TRUE(fs::exists(MountPathO() / "sfx", error)); } TEST_P(FUSEPlatformTests, move_replace_directory_fails) { EXPECT_FALSE(MoveFileExP(MountPathW() / "sd0", MountPathW() / "sd1" / "sd1d0", MOVEFILE_REPLACE_EXISTING)); EXPECT_EQ(GetLastError(), ERROR_ALREADY_EXISTS); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, move_replace_file_fails_when_target_is_directory) { EXPECT_FALSE(MoveFileExP(MountPathW() / "sf0", MountPathW() / "sd0" / "sd0d0", MOVEFILE_REPLACE_EXISTING)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sf0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, move_replace_file_cloud_local_succeeds) { BY_HANDLE_FILE_INFORMATION sf0o; BY_HANDLE_FILE_INFORMATION sf0w; EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sf0", sf0o)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sf0", sf0w)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE( CreateFileP(MountPathW() / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sf0", MountPathW() / "sfx", MOVEFILE_REPLACE_EXISTING)); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_FALSE(fs::exists(MountPathW() / "sf0", error)); BY_HANDLE_FILE_INFORMATION sfx; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sfx", sfx)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sf0w, sfx); EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && ClientW()->get("/x/s/sfx") && !fs::exists(MountPathO() / "sf0", error) && GetFileInformationByPath(MountPathO() / "sfx", sfx) && GetLastError() == ERROR_SUCCESS && sf0o == sfx; }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sf0")); EXPECT_TRUE(ClientW()->get("/x/s/sfx")); EXPECT_FALSE(fs::exists(MountPathO() / "sf0", error)); EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sfx", sfx)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sf0o, sfx); } TEST_P(FUSEPlatformTests, move_replace_file_local_local_succeeds) { EXPECT_TRUE( CreateFileP(MountPathW() / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE( CreateFileP(MountPathW() / "sfy", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION sfxo; BY_HANDLE_FILE_INFORMATION sfxw; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sfx", sfxw)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { return GetFileInformationByPath(MountPathO() / "sfx", sfxo) && GetLastError() == ERROR_SUCCESS; }, mDefaultTimeout)); EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sfx", sfxo)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sfx", MountPathW() / "sfy", MOVEFILE_REPLACE_EXISTING)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_FALSE(fs::exists(MountPathW() / "sfx", error)); BY_HANDLE_FILE_INFORMATION sfy; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sfy", sfy)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sfxw, sfy); EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathO() / "sfx", error) && GetFileInformationByPath(MountPathO() / "sfy", sfy) && sfxo == sfy; }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathO() / "sfx", error)); EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sfy", sfy)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sfxo, sfy); EXPECT_TRUE(DeleteFileP(MountPathW() / "sfy")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, move_replace_file_local_cloud_succeeds) { // Quick hack to make sure the cloud is regenerated. ASSERT_EQ(ClientW()->remove("/x/s/sf1"), API_OK); EXPECT_TRUE( CreateFileP(MountPathW() / "sfx", GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION sfxo; BY_HANDLE_FILE_INFORMATION sfxw; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sfx", sfxw)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { return GetFileInformationByPath(MountPathO() / "sfx", sfxo); }, mDefaultTimeout)); EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sfx", sfxo)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sfx", MountPathW() / "sf0", MOVEFILE_REPLACE_EXISTING)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_FALSE(fs::exists(MountPathW() / "sfx", error)); BY_HANDLE_FILE_INFORMATION sf0; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sf0", sf0)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sfxw, sf0); EXPECT_TRUE(waitFor( [&]() { return !fs::exists(MountPathO() / "sfx", error) && GetFileInformationByPath(MountPathO() / "sf0", sf0) && sfxo == sf0; }, mDefaultTimeout)); EXPECT_FALSE(fs::exists(MountPathO() / "sfx", error)); EXPECT_TRUE(GetFileInformationByPath(MountPathO() / "sf0", sf0)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(sfxo, sf0); EXPECT_TRUE(DeleteFileP(MountPathW() / "sf0")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, move_replace_file_succeeds) { BY_HANDLE_FILE_INFORMATION before; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sf0", before)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(MoveFileExP(MountPathW() / "sf0", MountPathW() / "sf1", MOVEFILE_REPLACE_EXISTING)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION after; EXPECT_TRUE(GetFileInformationByPath(MountPathW() / "sf1", after)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sf0") && ClientW()->get("/x/s/sf1") && !fs::exists(MountPathO() / "sf0", error) && fs::exists(MountPathO() / "sf1", error); }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sf0")); EXPECT_TRUE(ClientW()->get("/x/s/sf1")); EXPECT_FALSE(fs::exists(MountPathO() / "sf0", error)); EXPECT_TRUE(fs::exists(MountPathO() / "sf1", error)); } TEST_P(FUSEPlatformTests, open_file_fails_when_below_file) { EXPECT_FALSE(CreateFileP(MountPathR() / "sf0" / "sfx", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, open_file_fails_when_unknown) { EXPECT_FALSE( CreateFileP(MountPathR() / "sfx", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, open_file_succeeds) { // Should be able to open a directory. EXPECT_TRUE(CreateFileP(MountPathR() / "sd0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); // Should be able to open a file for reading. EXPECT_TRUE( CreateFileP(MountPathR() / "sf0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); // Should be able to open a file for reading and writing. EXPECT_TRUE(CreateFileP(MountPathW() / "sf0", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); // Should be able to open a file for writing. EXPECT_TRUE( CreateFileP(MountPathW() / "sf0", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); // Should be able to open a file for appending. EXPECT_TRUE(CreateFileP(MountPathW() / "sf0", FILE_APPEND_DATA, 0, nullptr, OPEN_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, open_file_truncate_fails_when_read_only) { EXPECT_FALSE(CreateFileP(MountPathR() / "sf0", GENERIC_READ | GENERIC_WRITE, 0, nullptr, TRUNCATE_EXISTING, 0, Handle<>())); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); } TEST_P(FUSEPlatformTests, open_file_truncate_succeeds) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_WRITE, 0, nullptr, TRUNCATE_EXISTING, 0, Handle<>()); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); ASSERT_TRUE(handle); FileInfo fileInfo; EXPECT_TRUE(GetFileInformationByHandle(handle.get(), &fileInfo.mByHandle)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeLow); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeHigh); EXPECT_TRUE(waitFor( [&]() { return GetFileAttributesExP(MountPathO() / "sf0", GetFileExInfoStandard, &fileInfo.mByPath) && GetLastError() == ERROR_SUCCESS && !fileInfo.mByPath.nFileSizeLow && !fileInfo.mByPath.nFileSizeHigh; }, mDefaultTimeout)); EXPECT_TRUE( GetFileAttributesExP(MountPathO() / "sf0", GetFileExInfoStandard, &fileInfo.mByPath)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(fileInfo.mByPath.nFileSizeLow); EXPECT_FALSE(fileInfo.mByPath.nFileSizeHigh); EXPECT_TRUE(FlushFileBuffers(handle.get())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && !info->mSize; }, mDefaultTimeout)); auto info = ClientW()->get("/x/s/sf0"); ASSERT_EQ(info.errorOr(API_OK), API_OK); ASSERT_EQ(info->mSize, 0); } TEST_P(FUSEPlatformTests, read_directory_changes_succeeds) { Directory directory(randomName(), mScratchPath); { Model model; model.generate("x/s", 2, 2, 2); model.populate(directory.path()); } DirectoryMonitor monitor(directory.path()); } TEST_P(FUSEPlatformTests, read_fails_when_write_only) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); char buffer; EXPECT_FALSE(ReadFile(handle.get(), &buffer, sizeof(buffer), nullptr, nullptr)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); } TEST_P(FUSEPlatformTests, read_succeeds) { auto handle = CreateFileP(MountPathR() / "sf0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); std::string buffer(32, '\x0'); DWORD numRead = 0u; EXPECT_TRUE( ReadFile(handle.get(), &buffer[0], static_cast<DWORD>(buffer.size()), &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); buffer.resize(numRead); EXPECT_EQ(buffer, "sf0"); } TEST_P(FUSEPlatformTests, read_write_succeeds) { constexpr auto BYTES_PER_THREAD = 4u; constexpr auto NUM_ITERATIONS = 128u; constexpr auto NUM_THREADS = 4u; std::atomic<bool> terminate{false}; auto loop = [&](std::size_t index) { auto handle = CreateFileP(MountPathW() / "sfx", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, 0, Handle<>()); if (!handle) return terminate.store(true); auto offset = static_cast<DWORD>(BYTES_PER_THREAD * index); for (auto i = 0u; !terminate && i < NUM_ITERATIONS; ++i) { if (SetFilePointer(handle.get(), offset, nullptr, FILE_BEGIN) != offset) return terminate.store(true); auto written = randomBytes(BYTES_PER_THREAD); auto numWritten = 0ul; if (!WriteFile(handle.get(), &written[0], BYTES_PER_THREAD, &numWritten, nullptr)) return terminate.store(true); if (numWritten != BYTES_PER_THREAD) return terminate.store(true); if (SetFilePointer(handle.get(), offset, nullptr, FILE_BEGIN) != offset) return terminate.store(true); auto read = std::string(BYTES_PER_THREAD, '\x0'); auto numRead = 0ul; if (!ReadFile(handle.get(), &read[0], BYTES_PER_THREAD, &numRead, nullptr)) return terminate.store(true); if (numRead != BYTES_PER_THREAD || read != written) return terminate.store(true); } }; // loop std::vector<std::thread> threads; for (auto i = 0u; i < NUM_THREADS; ++i) threads.emplace_back(std::thread(std::bind(loop, i))); while (!threads.empty()) { threads.back().join(); threads.pop_back(); } EXPECT_FALSE(terminate); EXPECT_TRUE(DeleteFileP(MountPathW() / "sfx")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, remove_directory_fails_when_below_file) { EXPECT_FALSE(RemoveDirectoryP(MountPathW() / "sf0" / "sdx")); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, remove_directory_fails_when_file) { EXPECT_FALSE(RemoveDirectoryP(MountPathW() / "sf0")); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, remove_directory_fails_when_not_empty) { EXPECT_FALSE(RemoveDirectoryP(MountPathW() / "sd0")); EXPECT_EQ(GetLastError(), ERROR_DIR_NOT_EMPTY); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sd0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, remove_directory_fails_when_read_only) { EXPECT_FALSE(RemoveDirectoryP(MountPathR() / "sd0" / "sd0d0")); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); std::error_code error; EXPECT_TRUE(fs::exists(MountPathW() / "sd0" / "sd0d0", error)); EXPECT_FALSE(error); } TEST_P(FUSEPlatformTests, remove_directory_fails_when_unknown) { EXPECT_FALSE(RemoveDirectoryP(MountPathW() / "sdx")); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, remove_directory_succeeds) { EXPECT_TRUE(RemoveDirectoryP(MountPathW() / "sd0" / "sd0d0")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return !ClientW()->get("/x/s/sd0/sd0d0") && !fs::exists(MountPathW() / "sd0" / "sd0d0", error); }, mDefaultTimeout)); EXPECT_FALSE(ClientW()->get("/x/s/sd0/sd0d0")); EXPECT_FALSE(fs::exists(MountPathW() / "sd0" / "sd0d0", error)); } TEST_P(FUSEPlatformTests, set_attributes_fails_when_unsupported_attributes_changed) { auto before = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_NE(before, INVALID_FILE_ATTRIBUTES); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(SetFileAttributesP(MountPathW() / "sf0", FILE_ATTRIBUTE_OFFLINE)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); SetLastError(ERROR_SUCCESS); auto after = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_attributes_on_folder) { auto before = GetFileAttributesP(MountPathW() / "sd0"); EXPECT_NE(before, INVALID_FILE_ATTRIBUTES); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE( SetFileAttributesP(MountPathW() / "sd0", FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetFileAttributesP(MountPathW() / "sd0", FILE_ATTRIBUTE_NORMAL)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(SetFileAttributesP(MountPathW() / "sd0", FILE_ATTRIBUTE_OFFLINE)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); SetLastError(ERROR_SUCCESS); auto after = GetFileAttributesP(MountPathW() / "sd0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_attributes_succeeds_but_attributes_not_changed) { auto before = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_NE(before, INVALID_FILE_ATTRIBUTES); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetFileAttributesP(MountPathW() / "sf0", FILE_ATTRIBUTE_READONLY)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetFileAttributesP(MountPathW() / "sf0", FILE_ATTRIBUTE_HIDDEN)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE( SetFileAttributesP(MountPathW() / "sf0", FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_ARCHIVE)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetFileAttributesP(MountPathW() / "sf0", FILE_ATTRIBUTE_NORMAL)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_attributes_fails_when_read_only) { auto before = GetFileAttributesP(MountPathR() / "sf0"); EXPECT_NE(before, INVALID_FILE_ATTRIBUTES); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(SetFileAttributesP(MountPathR() / "sf0", before)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); SetLastError(ERROR_SUCCESS); auto after = GetFileAttributesP(MountPathR() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_attributes_succeeds) { auto before = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_NE(before, INVALID_FILE_ATTRIBUTES); ASSERT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetFileAttributesP(MountPathW() / "sf0", before)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = GetFileAttributesP(MountPathW() / "sf0"); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_security_fails_when_below_file) { auto descriptor = readOnlySecurityDescriptor(); EXPECT_FALSE(SetFileSecurityP(MountPathW() / "sf0" / "x", descriptor)); EXPECT_EQ(GetLastError(), ERROR_DIRECTORY); } TEST_P(FUSEPlatformTests, set_file_security_fails_when_changed) { auto before = GetFileSecurityP(MountPathW() / "sf0"); EXPECT_TRUE(before); auto after = readOnlySecurityDescriptor(); EXPECT_FALSE(SetFileSecurityP(MountPathW() / "sf0", after)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); after = GetFileSecurityP(MountPathW() / "sf0"); EXPECT_TRUE(after); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_security_fails_when_read_only) { auto before = GetFileSecurityP(MountPathR() / "sf0"); EXPECT_TRUE(before); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = readWriteSecurityDescriptor(); EXPECT_FALSE(SetFileSecurityP(MountPathR() / "sf0", after)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); after = GetFileSecurityP(MountPathR() / "sf0"); EXPECT_TRUE(after); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_security_fails_when_unknown) { auto descriptor = readWriteSecurityDescriptor(); EXPECT_FALSE(SetFileSecurityP(MountPathW() / "sfx", descriptor)); EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND); } TEST_P(FUSEPlatformTests, set_file_security_succeeds) { auto before = GetFileSecurityP(MountPathW() / "sf0"); EXPECT_TRUE(before); EXPECT_TRUE(SetFileSecurityP(MountPathW() / "sf0", before)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = GetFileSecurityP(MountPathW() / "sf0"); EXPECT_TRUE(after); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_time_fails_when_changing_creation_time) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); FileTimes before; EXPECT_TRUE(GetFileTime(handle.get(), &before.mCreated, &before.mAccessed, &before.mWritten)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = before; auto next = std::chrono::system_clock::now() + std::chrono::minutes(5); after.mCreated = DateTime(next); EXPECT_FALSE(SetFileTime(handle.get(), &after.mCreated, &after.mAccessed, &after.mWritten)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); SetLastError(ERROR_SUCCESS); EXPECT_TRUE(GetFileTime(handle.get(), &after.mCreated, &after.mAccessed, &after.mWritten)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_time_fails_when_read_only) { auto handle = CreateFileP(MountPathR() / "sf0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); FileTimes before; EXPECT_TRUE(GetFileTime(handle.get(), &before.mCreated, &before.mAccessed, &before.mWritten)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto after = before; auto next = std::chrono::system_clock::now() + std::chrono::minutes(5); after.mWritten = DateTime(next); EXPECT_FALSE(SetFileTime(handle.get(), &after.mCreated, &after.mAccessed, &after.mWritten)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); SetLastError(ERROR_SUCCESS); EXPECT_TRUE(GetFileTime(handle.get(), &after.mCreated, &after.mAccessed, &after.mWritten)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(after, before); } TEST_P(FUSEPlatformTests, set_file_time_succeeds) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); BY_HANDLE_FILE_INFORMATION expected; EXPECT_TRUE(GetFileInformationByHandle(handle.get(), &expected)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto next = std::chrono::system_clock::now() + std::chrono::minutes(5); expected.ftCreationTime = DateTime(next); expected.ftLastAccessTime = DateTime(next); expected.ftLastWriteTime = DateTime(next); EXPECT_TRUE(SetFileTime(handle.get(), nullptr, nullptr, &expected.ftLastWriteTime)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); FileInfo computed; EXPECT_TRUE(GetFileInformationByHandle(handle.get(), &computed.mByHandle)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(computed.mByHandle, expected); EXPECT_TRUE(waitFor( [&]() { return GetFileAttributesExP(MountPathO() / "sf0", GetFileExInfoStandard, &computed.mByPath) && GetLastError() == ERROR_SUCCESS && computed.mByPath == expected; }, mDefaultTimeout)); EXPECT_TRUE( GetFileAttributesExP(MountPathO() / "sf0", GetFileExInfoStandard, &computed.mByPath)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(computed.mByPath, expected); // Make sure our changes have hit the cloud. EXPECT_TRUE(FlushFileBuffers(handle.get())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && expected == *info; }, mDefaultTimeout)); auto info = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(info); EXPECT_EQ(expected, *info); } TEST_P(FUSEPlatformTests, truncate_fails_when_read_only) { auto sf0 = ClientW()->get("/x/s/sf0"); ASSERT_TRUE(sf0); sf0->mPermissions = RDONLY; auto handle = CreateFileP(MountPathR() / "sf0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); ASSERT_TRUE(handle); EXPECT_FALSE(SetFilePointer(handle.get(), 0, nullptr, FILE_BEGIN)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(SetEndOfFile(handle.get())); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); WIN32_FILE_ATTRIBUTE_DATA info; SetLastError(ERROR_SUCCESS); EXPECT_TRUE(GetFileAttributesExP(MountPathR() / "sf0", GetFileExInfoStandard, &info)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(info, *sf0); } TEST_P(FUSEPlatformTests, truncate_succeeds) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); ASSERT_TRUE(handle); EXPECT_FALSE(SetFilePointer(handle.get(), 0, nullptr, FILE_BEGIN)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(SetEndOfFile(handle.get())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); FileInfo fileInfo; EXPECT_TRUE(GetFileInformationByHandle(handle.get(), &fileInfo.mByHandle)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeLow); EXPECT_FALSE(fileInfo.mByHandle.nFileSizeHigh); EXPECT_TRUE(waitFor( [&]() { return GetFileAttributesExP(MountPathO() / "sf0", GetFileExInfoStandard, &fileInfo.mByPath) && GetLastError() == ERROR_SUCCESS && !fileInfo.mByPath.nFileSizeLow && !fileInfo.mByPath.nFileSizeHigh; }, mDefaultTimeout)); EXPECT_TRUE(FlushFileBuffers(handle.get())); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(waitFor( [&]() { auto info = ClientW()->get("/x/s/sf0"); return info && !info->mSize; }, mDefaultTimeout)); auto info = ClientW()->get("/x/s/sf0"); ASSERT_EQ(info.errorOr(API_OK), API_OK); ASSERT_EQ(info->mSize, 0); } TEST_P(FUSEPlatformTests, write_fails_when_read_only) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); char dummy; DWORD numWritten = 0u; EXPECT_FALSE(WriteFile(handle.get(), &dummy, sizeof(dummy), &numWritten, nullptr)); EXPECT_EQ(GetLastError(), ERROR_ACCESS_DENIED); EXPECT_FALSE(numWritten); } TEST_P(FUSEPlatformTests, write_append_succeeds) { auto handle = CreateFileP(MountPathW() / "sf0", GENERIC_READ | FILE_APPEND_DATA, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(handle); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto written = randomBytes(32); auto numWritten = 0ul; EXPECT_TRUE(WriteFile(handle.get(), &written[0], static_cast<DWORD>(written.size()), &numWritten, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numWritten, static_cast<DWORD>(written.size())); SetFilePointer(handle.get(), 0, nullptr, FILE_BEGIN); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); written.insert(0, "sf0"); auto read = std::string(written.size(), '\x0'); auto numRead = 0ul; EXPECT_TRUE( ReadFile(handle.get(), &read[0], static_cast<DWORD>(written.size()), &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numRead, static_cast<DWORD>(written.size())); EXPECT_EQ(read, written); SetFilePointer(handle.get(), 0, nullptr, FILE_BEGIN); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_TRUE(WriteFile(handle.get(), &written[0], static_cast<DWORD>(written.size()), &numWritten, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numWritten, static_cast<DWORD>(written.size())); written.append(read); read.resize(written.size()); SetFilePointer(handle.get(), 0, nullptr, FILE_BEGIN); EXPECT_TRUE( ReadFile(handle.get(), &read[0], static_cast<DWORD>(written.size()), &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numRead, static_cast<DWORD>(written.size())); EXPECT_EQ(read, written); handle.reset(); EXPECT_TRUE(DeleteFileP(MountPathW() / "sf0")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } TEST_P(FUSEPlatformTests, write_succeeds) { auto sfxW = CreateFileP(MountPathW() / "sfx", GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, Handle<>()); EXPECT_TRUE(sfxW); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); std::error_code error; EXPECT_TRUE(waitFor( [&]() { return fs::exists(MountPathO() / "sfx", error); }, mDefaultTimeout)); auto sfxO = CreateFileP(MountPathO() / "sfx", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, Handle<>()); EXPECT_TRUE(sfxO); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto written = randomBytes(32); auto numWritten = 0ul; EXPECT_TRUE(WriteFile(sfxW.get(), &written[0], static_cast<DWORD>(written.size()), &numWritten, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numWritten, static_cast<DWORD>(written.size())); SetFilePointer(sfxW.get(), 0, nullptr, FILE_BEGIN); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); auto read = std::string(numWritten, '\x0'); auto numRead = 0ul; EXPECT_TRUE(ReadFile(sfxW.get(), &read[0], numWritten, &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numRead, numWritten); EXPECT_EQ(read, written); EXPECT_TRUE(waitFor( [&]() { return GetFileSize(sfxO.get(), nullptr) == numWritten; }, mDefaultTimeout)); read.assign(32, '\x0'); EXPECT_TRUE(ReadFile(sfxO.get(), &read[0], numWritten, &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numRead, numWritten); EXPECT_EQ(read, written); SetFilePointer(sfxW.get(), 0, nullptr, FILE_BEGIN); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); written = randomBytes(64); EXPECT_TRUE(WriteFile(sfxW.get(), &written[0], static_cast<DWORD>(written.size()), &numWritten, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numWritten, static_cast<DWORD>(written.size())); EXPECT_TRUE(waitFor( [&]() { return GetFileSize(sfxO.get(), nullptr) == numWritten; }, mDefaultTimeout)); SetFilePointer(sfxO.get(), 0, nullptr, FILE_BEGIN); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); read.resize(numWritten); EXPECT_TRUE(ReadFile(sfxO.get(), &read[0], numWritten, &numRead, nullptr)); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); EXPECT_EQ(numRead, numWritten); EXPECT_EQ(read, written); sfxO.reset(); sfxW.reset(); EXPECT_TRUE(DeleteFileP(MountPathW() / "sfx")); EXPECT_EQ(GetLastError(), ERROR_SUCCESS); } } // testing } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/printers.cpp��������������������0000664�0000000�0000000�00000005661�15162662266�0030125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/date_time.h> #include <mega/common/testing/printers.h> #include <mega/fuse/common/mount_inode_id.h> #include <mega/fuse/platform/security_descriptor.h> #include <mega/fuse/platform/testing/printers.h> #include <mega/fuse/platform/utility.h> #include <iomanip> #include <string> using namespace mega::common; using namespace mega::fuse; static UINT64 toUint64(DWORD low, DWORD high); static const std::string indent = std::string(6, ' '); void PrintTo(const BY_HANDLE_FILE_INFORMATION& info, std::ostream* ostream) { auto fill = ostream->fill(); auto flags = ostream->flags(); auto index = MountInodeID(toUint64(info.nFileIndexLow, info.nFileIndexHigh)); auto width = ostream->width(); *ostream << "\n" << indent << "accessed: " << DateTime(info.ftLastAccessTime) << "\n" << indent << "attributes: " << std::hex << std::setfill('0') << std::setw(8) << info.dwFileAttributes << "\n" << indent << "created: " << DateTime(info.ftCreationTime) << "\n" << indent << "index: " << toString(index) << "\n" << indent << "size: " << std::dec << std::setfill(fill) << std::setw(width) << toUint64(info.nFileSizeLow, info.nFileSizeHigh) << "\n" << indent << "written: " << DateTime(info.ftLastWriteTime); ostream->flags(flags); } void PrintTo(const FILETIME& value, std::ostream* ostream) { *ostream << DateTime(value); } void PrintTo(const WIN32_FILE_ATTRIBUTE_DATA& info, std::ostream* ostream) { auto fill = ostream->fill(); auto flags = ostream->flags(); auto width = ostream->width(); *ostream << "\n" << indent << "accessed: " << info.ftLastAccessTime << "\n" << indent << "attributes: " << std::hex << std::setfill('0') << std::setw(8) << info.dwFileAttributes << "\n" << indent << "created: " << DateTime(info.ftCreationTime) << "\n" << indent << "size: " << std::dec << std::setfill(fill) << std::setw(width) << toUint64(info.nFileSizeLow, info.nFileSizeHigh) << "\n" << indent << "written: " << DateTime(info.ftLastWriteTime); ostream->flags(flags); } UINT64 toUint64(DWORD low, DWORD high) { auto low_ = static_cast<UINT64>(low); auto high_ = static_cast<UINT64>(high); return (high_ << 32) | low_; } namespace mega { namespace fuse { namespace platform { void PrintTo(const SecurityDescriptor& descriptor, std::ostream* ostream) { *ostream << "\n" << indent << toString(descriptor); } } // platform namespace testing { void PrintTo(const FileTimes& value, std::ostream* ostream) { using common::operator<<; *ostream << "\n" << indent << "accessed: " << DateTime(value.mAccessed) << "\n" << indent << "created: " << DateTime(value.mCreated) << "\n" << indent << "written: " << DateTime(value.mWritten); } } // testing } // fuse } // mega �������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/sync_tests.cpp������������������0000664�0000000�0000000�00000002277�15162662266�0030455�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/testing/cloud_path.h> #include <mega/common/testing/directory.h> #include <mega/fuse/common/testing/sync_tests.h> namespace mega { namespace fuse { namespace testing { using namespace common::testing; TEST_F(FUSESyncTests, can_sync_when_an_empty_path_mount_is_on) { Directory empty(""); Directory sd0("sd0", mScratchPath); // Try and mount s. ScopedMount ms(ClientW(), "empty", empty.path(), "x/s"); // Mount should succeed. ASSERT_EQ(ms.result(), MOUNT_SUCCESS); // Try sync s/sd0. ScopedSync ssd0(ClientW(), sd0.path(), "x/s/sd0"); // Attempted sync should succeed ASSERT_EQ(ssd0.error(), API_OK); ASSERT_EQ(ssd0.syncError(), NO_SYNC_ERROR); } TEST_F(FUSESyncTests, can_mount_empty_path_when_sync_is_on) { Directory empty(""); Directory sd0("sd0", mScratchPath); // Sync s/sd0. ScopedSync ssd0(ClientW(), sd0.path(), "x/s/sd0"); ASSERT_EQ(ssd0.error(), API_OK); ASSERT_EQ(ssd0.syncError(), NO_SYNC_ERROR); // Try and mount s. ScopedMount ms(ClientW(), "empty", empty.path(), "x/s"); // Attempted mount should succeed. ASSERT_EQ(ms.result(), MOUNT_SUCCESS); } } // testing } // fuse } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/testing/integration/wrappers.cpp��������������������0000664�0000000�0000000�00000021344�15162662266�0030116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/node_info.h> #include <mega/common/platform/date_time.h> #include <mega/common/platform/handle.h> #include <mega/common/testing/path.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/platform/local_pointer.h> #include <mega/fuse/platform/security_descriptor.h> #include <mega/fuse/platform/testing/wrappers.h> bool operator==(const BY_HANDLE_FILE_INFORMATION& lhs, const BY_HANDLE_FILE_INFORMATION& rhs) { return !std::memcmp(&lhs, &rhs, sizeof(lhs)); } bool operator==(const BY_HANDLE_FILE_INFORMATION& lhs, const WIN32_FILE_ATTRIBUTE_DATA& rhs) { using mega::common::DateTime; return lhs.dwFileAttributes == rhs.dwFileAttributes && DateTime(lhs.ftCreationTime) == rhs.ftCreationTime && DateTime(lhs.ftLastAccessTime) == rhs.ftLastAccessTime && DateTime(lhs.ftLastWriteTime) == rhs.ftLastWriteTime && lhs.nFileSizeLow == rhs.nFileSizeLow && lhs.nFileSizeHigh == rhs.nFileSizeHigh; } bool operator==(const WIN32_FILE_ATTRIBUTE_DATA& lhs, const BY_HANDLE_FILE_INFORMATION& rhs) { return rhs == lhs; } namespace mega { namespace common { template<typename T> auto operator==(const NodeInfo& lhs, const T& rhs) -> std::enable_if_t<IsNativeInfoLike<T>::value, bool> { return fuse::operator==(lhs, rhs); } template bool operator==(const NodeInfo&, const BY_HANDLE_FILE_INFORMATION&); template bool operator==(const NodeInfo&, const WIN32_FILE_ATTRIBUTE_DATA&); } // common namespace fuse { using namespace common; using namespace common::platform; using namespace platform; template<typename T> auto operator==(const T& lhs, const BY_HANDLE_FILE_INFORMATION& rhs) -> typename std::enable_if<testing::IsInfoLike<T>::value, bool>::type { using testing::id; using testing::toUint64; DWORD attributes = FILE_ATTRIBUTE_NORMAL; auto handle = toUint64(id(lhs)); DWORD handleLo = static_cast<DWORD>(handle); DWORD handleHi = static_cast<DWORD>(handle >> 32); DWORD sizeLo = static_cast<DWORD>(lhs.mSize); DWORD sizeHi = static_cast<DWORD>(lhs.mSize >> 32); if (lhs.mPermissions != FULL) attributes = FILE_ATTRIBUTE_READONLY; if (lhs.mIsDirectory) { attributes = FILE_ATTRIBUTE_DIRECTORY; sizeLo = 0; sizeHi = 0; } return attributes == rhs.dwFileAttributes && handleLo == rhs.nFileIndexLow && handleHi == rhs.nFileIndexHigh && DateTime(lhs.mModified) == rhs.ftLastWriteTime && sizeLo == rhs.nFileSizeLow && sizeHi == rhs.nFileSizeHigh; } template bool operator==(const InodeInfo&, const BY_HANDLE_FILE_INFORMATION&); template bool operator==(const NodeInfo&, const BY_HANDLE_FILE_INFORMATION&); template<typename T> auto operator==(const T& lhs, const WIN32_FILE_ATTRIBUTE_DATA& rhs) -> typename std::enable_if<testing::IsInfoLike<T>::value, bool>::type { DWORD attributes = FILE_ATTRIBUTE_NORMAL; DWORD sizeLo = static_cast<DWORD>(lhs.mSize); DWORD sizeHi = static_cast<DWORD>(lhs.mSize >> 32); auto written = DateTime(lhs.mModified); if (lhs.mPermissions != FULL) attributes = FILE_ATTRIBUTE_READONLY; if (lhs.mIsDirectory) { attributes = FILE_ATTRIBUTE_DIRECTORY; sizeLo = 0; sizeHi = 0; } return attributes == rhs.dwFileAttributes && sizeLo == rhs.nFileSizeLow && sizeHi == rhs.nFileSizeHigh && written == rhs.ftLastWriteTime; } template bool operator==(const InodeInfo&, const WIN32_FILE_ATTRIBUTE_DATA&); template bool operator==(const NodeInfo&, const WIN32_FILE_ATTRIBUTE_DATA&); namespace testing { using namespace common::testing; bool operator==(const FileTimes& lhs, const FileTimes& rhs) { return !std::memcmp(&lhs, &rhs, sizeof(lhs)); } bool operator!=(const FileTimes& lhs, const FileTimes& rhs) { return !(lhs == rhs); } BOOL CreateDirectoryP(const Path& path, LPSECURITY_ATTRIBUTES securityAttributes) { return CreateDirectoryW(path.path().c_str(), securityAttributes); } Handle<> CreateFileP(const Path& path, DWORD desiredAccess, DWORD shareMode, LPSECURITY_ATTRIBUTES securityAttributes, DWORD creationDisposition, DWORD flagsAndAttributes, const Handle<>& templateFile) { return Handle<>(CreateFileW(path.path().c_str(), desiredAccess, shareMode, securityAttributes, creationDisposition, flagsAndAttributes, templateFile.get())); } BOOL DeleteFileP(const Path& path) { return DeleteFileW(path.path().c_str()); } FindHandle FindFirstFileP(const Path& path, LPWIN32_FIND_DATAW info) { return FindHandle(FindFirstFileW(path.path().c_str(), info)); } bool flushFile(const Path& path) { auto handle = CreateFileP(path, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, Handle<>()); if (!handle) return false; return FlushFileBuffers(handle.get()); } DWORD GetFileAttributesP(const Path& path) { return GetFileAttributesW(path.path().c_str()); } BOOL GetFileAttributesExP(const Path& path, GET_FILEEX_INFO_LEVELS level, LPVOID info) { return GetFileAttributesExW(path.path().c_str(), level, info); } BOOL GetFileInformationByPath(const Path& path, BY_HANDLE_FILE_INFORMATION& info) { auto handle = CreateFileP(path, GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, Handle<>()); if (!handle) return false; if (!GetFileInformationByHandle(handle.get(), &info)) return false; SetLastError(ERROR_SUCCESS); return true; } SecurityDescriptor GetFileSecurityP(const Path& path) { constexpr auto requested = DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION; auto required = 0ul; GetFileSecurityW(path.path().c_str(), requested, nullptr, required, &required); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return SecurityDescriptor(); LocalPtr<void> descriptor(LocalAlloc(LMEM_FIXED, required)); if (!descriptor) return SecurityDescriptor(); SetLastError(ERROR_SUCCESS); if (!GetFileSecurityW(path.path().c_str(), requested, descriptor.get(), required, &required)) return SecurityDescriptor(); return SecurityDescriptor(std::move(descriptor)); } long GetLastError() { return static_cast<long>(::GetLastError()); } std::optional<VolumeInfo> GetVolumeInformationByPath(const Path& path) { // Add trailing separator as necessary. auto path_ = toWideString(path.string()); if (!path_.empty() && path_.back() != L'\\') path_.push_back(L'\\'); // Preallocate maximum necessary space for names. WCHAR filesystemName[MAX_PATH + 1]; WCHAR volumeName[MAX_PATH + 1]; // So we know how long the names actually are. DWORD filesystemNameLength = 0u; DWORD volumeNameLength = 0u; // Couldn't get information about the specified volume. if (!GetVolumeInformationW(path_.c_str(), volumeName, std::size(volumeName), nullptr, nullptr, nullptr, filesystemName, std::size(filesystemName))) return std::nullopt; // Make sure the names are null terminated. filesystemName[MAX_PATH] = L'\0'; volumeName[MAX_PATH] = L'\0'; VolumeInfo info; // Populate info instance. info.mFilesystemName = fromWideString(filesystemName); info.mVolumeName = fromWideString(volumeName); // Return volume info to our caller. return info; } BOOL MoveFileExP(const Path& source, const Path& target, DWORD flags) { return MoveFileExW(source.path().c_str(), target.path().c_str(), flags); } BOOL RemoveDirectoryP(const Path& path) { return RemoveDirectoryW(path.path().c_str()); } BOOL SetFileAttributesP(const Path& path, DWORD attributes) { return SetFileAttributesW(path.path().c_str(), attributes); } BOOL SetFileSecurityP(const Path& path, const SecurityDescriptor& descriptor) { constexpr auto flags = DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION; return SetFileSecurityW(path.path().c_str(), flags, descriptor.get()); } } // testing } // fuse } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/unmounter.cpp���������������������������������������0000664�0000000�0000000�00000000610�15162662266�0024300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/logging.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/unmounter.h> namespace mega { namespace fuse { namespace platform { MountResult Unmounter::unmount(Mount& mount, const std::string&, bool) { // Remove the mount from memory. return mount.remove(); } } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/supported/platform/windows/utility.cpp�����������������������������������������0000664�0000000�0000000�00000017015�15162662266�0023756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/platform/date_time.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logging.h> #include <mega/fuse/platform/mount.h> #include <mega/fuse/platform/utility.h> #include <cstring> namespace mega { namespace fuse { namespace platform { static FSP_FSCTL_DIR_INFO* translate(FSP_FSCTL_DIR_INFO& destination, const Mount& mount, const std::wstring& name, std::size_t size, const InodeInfo& source); DWORD attributes(const InodeInfo& info, const Mount& mount) { // Inode's a directory. if (info.mIsDirectory) return FILE_ATTRIBUTE_DIRECTORY; // Inode's a read-only file. if (!mount.writable() || info.mPermissions != FULL) return FILE_ATTRIBUTE_READONLY; // Inode's a writable file. return FILE_ATTRIBUTE_NORMAL; } std::string fromWideString(const wchar_t* value, std::size_t length) { // String's empty. if (!length) return std::string(); // How much buffer space do we need? auto required = WideCharToMultiByte(CP_UTF8, 0, value, static_cast<int>(length), nullptr, 0, nullptr, nullptr); // Couldn't determine buffer requirements. if (!required) throw FUSEErrorF("Couldn't compute length of wide-string: %u", GetLastError()); std::string buffer(required, '\x0'); // Translate the wide-string to UTF-8. if (!WideCharToMultiByte(CP_UTF8, 0, value, static_cast<int>(length), &buffer[0], static_cast<int>(required), nullptr, nullptr)) throw FUSEErrorF("Couldn't translate wide-string to UTF-8: %u", GetLastError()); // Return buffer to caller. return buffer; } std::string fromWideString(const std::wstring& value) { return fromWideString(value.c_str(), value.size()); } std::wstring normalize(const std::wstring& value) { // String's empty. if (value.empty()) return std::wstring(); // How much space does our buffer need? auto required = NormalizeString(NormalizationC, value.c_str(), static_cast<int>(value.size()), nullptr, 0); // Couldn't determine length of buffer. if (required <= 0) throw FUSEErrorF("Couldn't compute length of normalized wide-string: %u", GetLastError()); std::wstring buffer(required, L'\x0'); // Try and normalize the string. required = NormalizeString(NormalizationC, value.c_str(), static_cast<int>(value.size()), &buffer[0], static_cast<int>(buffer.size())); if (required <= 0) throw FUSEErrorF("Couldn't normalize wide-string: %u", GetLastError()); // Trim the buffer down to size. buffer.resize(required); // Return normalized string to caller. return buffer; } std::wstring toWideString(const std::string& value) { // String's empty. if (value.empty()) return std::wstring(); // How large does our buffer need to be? auto required = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0); // Couldn't determine buffer length. if (!required) throw FUSEErrorF("Couldn't compute length of UTF-8 string: %u", GetLastError()); std::wstring buffer(required, L'\x0'); // Translate our UTF-8 string into a wide-string. if (!MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), &buffer[0], static_cast<int>(buffer.size()))) throw FUSEErrorF("Couldn't translate UTF-8 string to wide-string: %u", GetLastError()); // Return wide-string to caller. return buffer; } void translate(FSP_FSCTL_FILE_INFO& destination, const Mount& mount, const InodeInfo& source) { using common::DateTime; // Make sure destination's in a well-defined state. std::memset(&destination, 0, sizeof(destination)); destination.ChangeTime = DateTime(source.mModified); destination.CreationTime = destination.ChangeTime; destination.FileAttributes = attributes(source, mount); destination.IndexNumber = source.mID.get(); destination.LastAccessTime = destination.ChangeTime; destination.LastWriteTime = destination.ChangeTime; if (source.mIsDirectory) return; destination.AllocationSize = static_cast<UINT64>(source.mSize); destination.FileSize = destination.AllocationSize; } FSP_FSCTL_DIR_INFO* translate(FSP_FSCTL_DIR_INFO& destination, const Mount& mount, const InodeInfo& source) { // Translate name into a wide string. auto name = toWideString(source.mName); // Compute buffer's length. auto size = sizeof(destination) + sizeof(wchar_t) * name.size(); // Populate buffer and return reference to caller. return translate(destination, mount, name, size, source); } FSP_FSCTL_DIR_INFO* translate(std::vector<uint8_t>& destination, const Mount& mount, const std::string& name, const InodeInfo& source) { // Convenience. FSP_FSCTL_DIR_INFO* buffer; // Translate the inode's name into a wide-string. auto name_ = toWideString(name); // Make sure our buffer is large enough. destination.resize(sizeof(*buffer) + sizeof(wchar_t) * name_.size()); // So we can easily populate our buffer. buffer = reinterpret_cast<FSP_FSCTL_DIR_INFO*>(&destination[0]); // Populate buffer and return reference to caller. return translate(*buffer, mount, name_, destination.size(), source); } NTSTATUS translate(Error result) { switch (result) { case API_EEXIST: return STATUS_OBJECT_NAME_COLLISION; case API_ENOENT: return STATUS_OBJECT_PATH_NOT_FOUND; case API_FUSE_ENOTDIR: return STATUS_NOT_A_DIRECTORY; case API_FUSE_EISDIR: return STATUS_FILE_IS_A_DIRECTORY; case API_FUSE_ENOTFOUND: return STATUS_OBJECT_NAME_NOT_FOUND; case API_OK: return STATUS_SUCCESS; default: return STATUS_UNSUCCESSFUL; } } FSP_FSCTL_DIR_INFO* translate(FSP_FSCTL_DIR_INFO& destination, const Mount& mount, const std::wstring& name, std::size_t size, const InodeInfo& source) { // Specify buffer's length. destination.Size = static_cast<UINT16>(size); // Make sure reserved bytes are zero. std::memset(destination.Padding, 0, sizeof(destination.Padding)); // Populate file name. std::memcpy(destination.FileNameBuf, name.c_str(), destination.Size - sizeof(destination)); // Populate file attributes. translate(destination.FileInfo, mount, source); // Return buffer's address to caller. return &destination; } } // platform } // fuse } // mega �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016570�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/CMakeLists.txt�����������������������������������������������������0000664�0000000�0000000�00000000447�15162662266�0021335�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Let the SDK know where it can find our headers. target_include_directories(SDKlib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) # Add sources required by dummy backend. target_sources(SDKlib PRIVATE service_context.cpp service.cpp ) add_subdirectory(mega) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017501�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/CMakeLists.txt������������������������������������������������0000664�0000000�0000000�00000000027�15162662266�0022240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(fuse) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/fuse/���������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020443�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/fuse/CMakeLists.txt�������������������������������������������0000664�0000000�0000000�00000000033�15162662266�0023177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(platform) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/fuse/platform/������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022267�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/fuse/platform/CMakeLists.txt����������������������������������0000664�0000000�0000000�00000000061�15162662266�0025024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_sources(SDKlib PRIVATE service_context.h) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/mega/fuse/platform/service_context.h�������������������������������0000664�0000000�0000000�00000005222�15162662266�0025645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/fuse/common/service_context.h> #include <mega/fuse/common/service_flags_forward.h> namespace mega { namespace fuse { namespace platform { class ServiceContext : public fuse::ServiceContext { public: ServiceContext(const ServiceFlags&, Service& service); ~ServiceContext(); // Add a mount to the database. MountResult add(const MountInfo& info) override; // Check if a file exists in the cache. bool cached(common::NormalizedPath path) const override; // Called by the client when its view of the cloud is current. void current() override; // Describe the inode representing the file at the specified path. common::ErrorOr<InodeInfo> describe(const common::NormalizedPath& path) const override; // Disable an enabled mount. void disable(MountDisabledCallback callback, const std::string& name, bool remember) override; // Discard node events. MountResult discard(bool discard) override; // Downgrade the FUSE database to the specified version. MountResult downgrade(const LocalPath& path, std::size_t target) override; // Enable a disabled mount. MountResult enable(const std::string& name, bool remember) override; // Query whether a specified mount is enabled. bool enabled(const std::string& name) const override; // Execute a function on some task. common::Task execute(std::function<void(const common::Task&)> function) override; // Update a mount's flags. MountResult flags(const std::string& name, const MountFlags& flags) override; // Query a mount's flags. MountFlagsPtr flags(const std::string& name) const override; // Describe the mount associated with name. MountInfoPtr get(const std::string& name) const override; // Describe all (enabled) mounts. MountInfoVector get(bool onlyEnabled) const override; // Retrieve the path of the mount associated with name. common::NormalizedPath path(const std::string& name) const override; // Remove a disabled mount from the database. MountResult remove(const std::string& path) override; // Check whether the specified path is "syncable." bool syncable(const common::NormalizedPath& path) const override; // Called by the client when nodes have been changed in the cloud. void updated(common::NodeEventQueue& events) override; // Update the FUSE database to the specified version. MountResult upgrade(const LocalPath& path, std::size_t target) override; }; // ServiceContext } // platform } // fuse } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/service.cpp��������������������������������������������������������0000664�0000000�0000000�00000000413�15162662266�0020732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> namespace mega { namespace fuse { MountResult Service::abort(AbortPredicate) { return MOUNT_SUCCESS; } bool Service::supported() const { return false; } } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/fuse/unsupported/service_context.cpp������������������������������������������������0000664�0000000�0000000�00000005165�15162662266�0022507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/common/error_or.h> #include <mega/common/normalized_path.h> #include <mega/common/task_queue.h> #include <mega/fuse/common/client.h> #include <mega/fuse/common/inode_info.h> #include <mega/fuse/common/logger.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_info.h> #include <mega/fuse/common/mount_result.h> #include <mega/fuse/common/service.h> #include <mega/fuse/platform/service_context.h> namespace mega { namespace fuse { namespace platform { using namespace common; ServiceContext::ServiceContext(const ServiceFlags&, Service& service) : fuse::ServiceContext(service) { } ServiceContext::~ServiceContext() { } MountResult ServiceContext::add(const MountInfo&) { return MOUNT_UNSUPPORTED; } bool ServiceContext::cached(NormalizedPath) const { return false; } void ServiceContext::current() { } ErrorOr<InodeInfo> ServiceContext::describe(const NormalizedPath&) const { return unexpected(API_ENOENT); } void ServiceContext::disable(MountDisabledCallback callback, const std::string& name, bool) { callback(MOUNT_UNKNOWN); MountEvent event; event.mName = name; event.mResult = MOUNT_UNKNOWN; event.mType = MOUNT_DISABLED; emitEvent(client(), event); } MountResult ServiceContext::discard(bool) { return MOUNT_UNSUPPORTED; } MountResult ServiceContext::downgrade(const LocalPath&, std::size_t) { return MOUNT_UNSUPPORTED; } MountResult ServiceContext::enable(const std::string&, bool) { return MOUNT_UNKNOWN; } bool ServiceContext::enabled(const std::string&) const { return false; } Task ServiceContext::execute(std::function<void(const Task&)> function) { Task task(std::move(function), logger()); task.cancel(); return task; } MountResult ServiceContext::flags(const std::string&, const MountFlags&) { return MOUNT_UNKNOWN; } MountFlagsPtr ServiceContext::flags(const std::string&) const { return nullptr; } MountInfoPtr ServiceContext::get(const std::string&) const { return nullptr; } MountInfoVector ServiceContext::get(bool) const { return MountInfoVector(); } NormalizedPath ServiceContext::path(const std::string&) const { return NormalizedPath(); } MountResult ServiceContext::remove(const std::string&) { return MOUNT_UNKNOWN; } bool ServiceContext::syncable(const NormalizedPath&) const { return true; } void ServiceContext::updated(NodeEventQueue&) { } MountResult ServiceContext::upgrade(const LocalPath&, std::size_t) { return MOUNT_UNSUPPORTED; } } // platform } // fuse } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx.cpp�����������������������������������������������������������������������������0000664�0000000�0000000�00000027620�15162662266�0014535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file gfx.cpp * @brief Platform-independent bitmap graphics transformation functionality * * (c) 2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/gfx.h" #include "mega/logging.h" #include "mega/gfx/GfxProcCG.h" #include <numeric> #include <tuple> namespace mega { // from low resolution to high resolution. gendimensionsputfa relies on this order. const std::vector<GfxDimension> GfxProc::DIMENSIONS = { { 200, 0 }, // THUMBNAIL: square thumbnail, cropped from near center { 1000, 1000 } // PREVIEW: scaled version inside 1000x1000 bounding square }; const std::vector<GfxDimension> GfxProc::DIMENSIONS_AVATAR = { { 250, 0 } // AVATAR250X250: square thumbnail, cropped from near center }; std::unique_ptr<IGfxProvider> IGfxProvider::createInternalGfxProvider() { #if USE_FREEIMAGE return std::make_unique<::mega::GfxProviderFreeImage>(); #elif USE_IOS return std::make_unique<GfxProviderCG>(); #else return nullptr; #endif } bool GfxProc::isgfx(const LocalPath& localfilename) { const char* supported = nullptr; supported = mGfxProvider->supportedformats(); if (!supported) { // We don't have supported formats, so the build was without FREEIMAGE or other graphics processing libraries // Therefore we cannot graphics process any file, so return false so that we don't try. return false; } if (0 == strcmp(supported, "all")) { // special case for client app provided MegaGfxProcessor // and for our Android app. If they don't supply a list // of extensions, then we don't filter. return true; } string ext; if (client->fsaccess->getextension(localfilename, ext)) { const char* ptr; // FIXME: use hash ptr = strstr(supported, ext.c_str()); if (ptr && ptr[ext.size()] == '.') { return true; } } return false; } bool GfxProc::isvideo(const LocalPath& localfilename) { const char* supported = nullptr; supported = mGfxProvider->supportedvideoformats(); if (!supported) { return false; } if (0 == strcmp(supported, "all")) { // special case for client app provided MegaGfxProcessor // and for our Android app. If they don't supply a list // of extensions, then we don't filter. return true; } string ext; if (client->fsaccess->getextension(localfilename, ext)) { const char* ptr; // FIXME: use hash ptr = strstr(supported, ext.c_str()); if (ptr && ptr[ext.size()] == '.') { return true; } } return false; } void *GfxProc::threadEntryPoint(void *param) { GfxProc* gfxProcessor = (GfxProc*)param; gfxProcessor->loop(); return NULL; } std::vector<GfxDimension> GfxProc::getJobDimensions(GfxJob *job) { std::vector<GfxDimension> jobDimensions; for (auto i : job->imagetypes) { assert(i < DIMENSIONS.size()); jobDimensions.push_back(DIMENSIONS[i]); } return jobDimensions; } void GfxProc::loop() { GfxJob *job = NULL; while (!finished) { waiter.init(NEVER); waiter.wait(); while ((job = requests.pop()) != nullptr) { if (finished) { delete job; break; } LOG_debug << "Processing media file: " << job->h; auto images = generateImages(job->localfilename, getJobDimensions(job)); for (auto& image : images) { job->images.push_back(image.empty() ? nullptr : new string(std::move(image))); } responses.push(job); client->waiter->notify(); } } while ((job = requests.pop()) != nullptr) { delete job; } while ((job = responses.pop()) != nullptr) { for (unsigned i = 0; i < job->imagetypes.size(); i++) { delete job->images[i]; } delete job; } } int GfxProc::checkevents(Waiter *) { if (!client) { return 0; } GfxJob *job = NULL; bool needexec = false; while ((job = responses.pop()) != nullptr) { for (unsigned i = 0; i < job->images.size(); i++) { if (job->images[i]) { LOG_debug << "Media file correctly processed. Attaching file attribute: " << job->h; // thumbnail/preview has been extracted from the main image. // Now we upload those to the file attribute servers // The file attribute will either be added to an existing node // or added to the eventual putnodes if this was started as part of an upload transfer mCheckEventsKey.setkey(job->key); if (client->putfa(job->h, job->imagetypes[i], &mCheckEventsKey, 0, std::unique_ptr<string>(job->images[i])) != API_OK) { continue; // no needexec for this one } } else { LOG_debug << "Unable to process media file: " << job->h; if (job->h.isNodeHandle()) { // This case is for the automatic "Restoring missing attributes" case (from syncup() or from download completion). // It doesn't matter much if we can't do it // App requests don't come by this route (they supply already generated preview/thumnnail) so no need to make any callbacks LOG_warn << "Media file processing failed for existing Node"; } else { if (auto it = client->fileAttributesUploading.lookupExisting(job->h.uploadHandle())) { // reduce the number of required attributes to let the upload continue it->pendingfa.erase(job->imagetypes[i]); client->checkfacompletion(job->h.uploadHandle()); } else { LOG_debug << "Transfer related to media file not found: " << job->h; } } } needexec = true; } delete job; } return needexec ? Waiter::NEEDEXEC : 0; } std::vector<std::string> IGfxLocalProvider::generateImages(const LocalPath& localfilepath, const std::vector<GfxDimension>& dimensions) { std::vector<std::string> images(dimensions.size()); int maxDimension = std::accumulate( dimensions.begin(), dimensions.end(), 0, [](int max, const GfxDimension& d) { return std::max(max, std::max(d.w(), d.h())); }); if (readbitmap(localfilepath, maxDimension)) { for (unsigned int i = 0; i < dimensions.size(); ++i) { int targetWidth = dimensions[i].w(), targetHeight = dimensions[i].h(); if (width() < targetWidth && height() < targetHeight) { LOG_debug << "Skipping upsizing of local preview"; targetWidth = width(); targetHeight = height(); } // LOG_verbose << "resizebitmap w/h: " << targetWidth << "/" << targetHeight; // For thumbnail, PNG is allowed images with transparency const auto hint = (dimensions[i] == GfxProc::DIMENSIONS[GfxProc::THUMBNAIL]) ? Hint::FORMAT_PNG : Hint::NONE; string image; if (resizebitmap(targetWidth, targetHeight, &image, hint)) { images[i] = std::move(image); } } freebitmap(); } else { LOG_err << "Error reading bitmap for " << localfilepath; } return images; } void IGfxLocalProvider::transform(int& w, int& h, int& rw, int& rh, int& px, int& py) { if (rh) { // rectangular rw*rh bounding box if (h*rw > w*rh) { w = w * rh / h; h = rh; } else { h = h * rw / w; w = rw; } px = 0; py = 0; rw = w; rh = h; } else { // square rw*rw crop thumbnail if (w < h) { h = h * rw / w; w = rw; } else { w = w * rw / h; h = rw; } px = (w - rw) / 2; py = (h - rw) / 3; rh = rw; } } // load bitmap image, generate all designated sizes, attach to specified upload/node handle int GfxProc::gendimensionsputfa(const LocalPath& localfilename, NodeOrUploadHandle th, SymmCipher* key, int missing) { LOG_debug << "Creating thumb/preview for " << localfilename; GfxJob *job = new GfxJob(); job->h = th; memcpy(job->key, key->key, SymmCipher::KEYLENGTH); job->localfilename = localfilename; int generatingAttrs = 0; for (fatype i = static_cast<fatype>(DIMENSIONS.size()); i--; ) { if (missing & (1 << i)) { job->imagetypes.push_back(i); generatingAttrs += 1 << i; } } if (!generatingAttrs) { delete job; return 0; } requests.push(job); waiter.notify(); return generatingAttrs; } std::vector<std::string> GfxProc::generateImages(const LocalPath& localfilepath, const std::vector<GfxDimension>& dimensions) { std::lock_guard<std::mutex> g(mutex); return mGfxProvider->generateImages(localfilepath, dimensions); } std::string GfxProc::generateOneImage(const LocalPath& localfilepath, const GfxDimension& dimension) { std::lock_guard<std::mutex> g(mutex); auto images = mGfxProvider->generateImages(localfilepath, std::vector<GfxDimension>{ dimension }); return images[0]; } bool GfxProc::savefa(const LocalPath& localfilepath, const GfxDimension& dimension, const LocalPath& localdstpath) { if (!isgfx(localfilepath)) { return false; } string image = generateOneImage(localfilepath, dimension); if (image.empty()) { return false; } auto f = client->fsaccess->newfileaccess(); client->fsaccess->unlinklocal(localdstpath); if (!f->fopen(localdstpath, OPEN_WRONLY, FSLogging::logOnError)) { return false; } if (!f->fwrite((const byte*)image.data(), unsigned(image.size()), 0)) { return false; } return true; } GfxProc::GfxProc(std::unique_ptr<IGfxProvider> middleware) : mGfxProvider(std::move(middleware)) { } void GfxProc::startProcessingThread() { thread.start(threadEntryPoint, this); threadstarted = true; } GfxProc::~GfxProc() { finished = true; waiter.notify(); assert(threadstarted); if (threadstarted) { thread.join(); } } GfxJobQueue::GfxJobQueue() { } void GfxJobQueue::push(GfxJob *job) { mutex.lock(); jobs.push_back(job); mutex.unlock(); } GfxJob *GfxJobQueue::pop() { mutex.lock(); if (jobs.empty()) { mutex.unlock(); return NULL; } GfxJob *job = jobs.front(); jobs.pop_front(); mutex.unlock(); return job; } GfxJob::GfxJob() { } } // namespace ����������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/��������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014022�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/GfxProcCG.mm��������������������������������������������������������������������0000664�0000000�0000000�00000031303�15162662266�0016137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file GfxProviderCG.mm * @brief Graphics layer using Cocoa Touch * * (c) 2013-2015 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #import <AVFoundation/AVFoundation.h> #if TARGET_OS_IPHONE #import <UIKit/UIImage.h> #else #import <AppKit/NSImage.h> #endif #import <QuickLookThumbnailing/QuickLookThumbnailing.h> const CGFloat COMPRESSION_QUALITY = 0.8f; const int THUMBNAIL_MIN_SIZE = 200; const int64_t WAIT_60_SECONDS = 60; using namespace mega; #ifndef USE_FREEIMAGE GfxProviderCG::GfxProviderCG(): sourceURL(NULL) { w = h = 0; semaphore = dispatch_semaphore_create(0); } GfxProviderCG::~GfxProviderCG() { freebitmap(); if (sourceURL) { CFRelease(sourceURL); sourceURL = NULL; } } const char* GfxProviderCG::supportedformats() { return ".bmp.cr2.crw.cur.dng.gif.heic.ico.j2c.jp2.jpf.jpeg.jpg.nef.orf.pbm.pdf.pgm.png.pnm.ppm.psd.raf.rw2.rwl.tga.tif.tiff.3g2.3gp.avi.m4v.mov.mp4.mqv.qt.webp.jxl.avif."; } const char* GfxProviderCG::supportedvideoformats() { return NULL; } bool GfxProviderCG::readbitmap(const LocalPath& path, int size) { // Convenience. using mega::detail::AdjustBasePathResult; using mega::detail::adjustBasePath; // Ensure provided path is absolute. AdjustBasePathResult absolutePath = adjustBasePath(path); // Make absolute path usable to Cocoa. NSString* sourcePath = [NSString stringWithCString: absolutePath.c_str() encoding:NSUTF8StringEncoding]; // Couldn't create a Cocoa-friendly path. if (sourcePath == nil) { return false; } sourceURL = (CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:sourcePath isDirectory:NO]); if (sourceURL == NULL) { return false; } w = h = 0; NSString *fileExtension = [sourcePath pathExtension]; if (fileExtension) { UTType *type = [UTType typeWithFilenameExtension:fileExtension]; if ([type conformsToType:UTTypeMovie]) { AVAsset *asset = [AVAsset assetWithURL:(__bridge NSURL *)sourceURL]; AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject]; CGSize naturalSize = videoTrack.naturalSize; w = naturalSize.width; h = naturalSize.height; } else if ([type conformsToType:UTTypeImage]) { CFMutableDictionaryRef imageOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(imageOptions, kCGImageSourceShouldCache, kCFBooleanFalse); CGImageSourceRef imageSource = CGImageSourceCreateWithURL(sourceURL, imageOptions); if (imageSource) { CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, imageOptions); if (imageProperties) { CFNumberRef width = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth); CFNumberRef height = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight); if (width && height) { CGFloat value; if (CFNumberGetValue(width, kCFNumberCGFloatType, &value)) { w = value; } if (CFNumberGetValue(height, kCFNumberCGFloatType, &value)) { h = value; } } CFRelease(imageProperties); } CFRelease(imageSource); } if (imageOptions) { CFRelease(imageOptions); } } } if (!(w && h)) { w = h = size; } return w && h; } static inline CGRect tileRect(size_t w, size_t h) { CGRect res; // square rw*rw crop thumbnail res.size.width = res.size.height = std::min(w, h); if (w < h) { res.origin.x = 0; res.origin.y = (h - w) / 2; } else { res.origin.x = (w - h) / 2; res.origin.y = 0; } return res; } #if !(TARGET_OS_IPHONE) static inline NSData* dataForImage(CGImageRef image) { NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCGImage:image]; NSDictionary *properties = @{NSImageCompressionFactor : @(COMPRESSION_QUALITY)}; return [bitmap representationUsingType:NSBitmapImageFileTypeJPEG properties:properties]; } #endif bool GfxProviderCG::resizebitmap(int rw, int rh, string* imageOut, Hint) { imageOut->clear(); bool isThumbnail = !rh; if (isThumbnail) { if (w > h) { rh = THUMBNAIL_MIN_SIZE; rw = THUMBNAIL_MIN_SIZE * w / h; } else if (h > w) { rh = THUMBNAIL_MIN_SIZE * h / w; rw = THUMBNAIL_MIN_SIZE; } else { rw = rh = THUMBNAIL_MIN_SIZE; } } CGSize size = CGSizeMake(rw, rh); __block NSData *data = nil; QLThumbnailGenerationRequest *request = [[QLThumbnailGenerationRequest alloc] initWithFileAtURL:(__bridge NSURL *)sourceURL size:size scale:1.0 representationTypes:QLThumbnailGenerationRequestRepresentationTypeThumbnail]; [QLThumbnailGenerator.sharedGenerator generateBestRepresentationForRequest:request completionHandler:^(QLThumbnailRepresentation * _Nullable thumbnail, NSError * _Nullable error) { if (error) { LOG_err << "Error generating best representation for a request: " << error.localizedDescription; #if TARGET_OS_IPHONE NSString *path = ((__bridge NSURL *)sourceURL).path; if ([NSFileManager.defaultManager fileExistsAtPath:path]) { UIImage *image = [UIImage imageWithContentsOfFile:path]; UIImage *thumbnail = [image imageByPreparingThumbnailOfSize:size]; if (thumbnail) { if (isThumbnail) { CGImageRef newImage = CGImageCreateWithImageInRect(thumbnail.CGImage, tileRect(CGImageGetWidth(thumbnail.CGImage), CGImageGetHeight(thumbnail.CGImage))); data = UIImageJPEGRepresentation([UIImage imageWithCGImage:newImage], COMPRESSION_QUALITY); if (newImage) { CFRelease(newImage); } } else { data = UIImageJPEGRepresentation(thumbnail, COMPRESSION_QUALITY); } if (!data) { LOG_err << "Could not convert image to data for image for path: " << path; } } else { LOG_err << "Could not generate thumbnail for image at path: " << path; } } else { LOG_err << "Could not find the image at path: " << path; } #endif } else { if (isThumbnail) { CGImageRef newImage = CGImageCreateWithImageInRect(thumbnail.CGImage, tileRect(CGImageGetWidth(thumbnail.CGImage), CGImageGetHeight(thumbnail.CGImage))); #if TARGET_OS_IPHONE data = UIImageJPEGRepresentation([UIImage imageWithCGImage:newImage], COMPRESSION_QUALITY); if (newImage) { CFRelease(newImage); } #else data = dataForImage(newImage); #endif } else { #if TARGET_OS_IPHONE data = UIImageJPEGRepresentation(thumbnail.UIImage, COMPRESSION_QUALITY); #else data = dataForImage(thumbnail.CGImage); #endif } } if (this->semaphore) { dispatch_semaphore_signal(this->semaphore); } }]; dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, WAIT_60_SECONDS * NSEC_PER_SEC); dispatch_semaphore_wait(semaphore, waitTime); imageOut->assign((char*) data.bytes, data.length); return data; } void GfxProviderCG::freebitmap() { w = h = 0; } #endif void ios_statsid(std::string *statsid) { NSMutableDictionary *queryDictionary = [[NSMutableDictionary alloc] init]; [queryDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; [queryDictionary setObject:@"statsid" forKey:(__bridge id)kSecAttrAccount]; [queryDictionary setObject:@"MEGA" forKey:(__bridge id)kSecAttrService]; [queryDictionary setObject:(__bridge id)(kSecAttrSynchronizableAny) forKey:(__bridge id)(kSecAttrSynchronizable)]; [queryDictionary setObject:@YES forKey:(__bridge id)kSecReturnData]; [queryDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; [queryDictionary setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible]; CFTypeRef result = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, &result); switch (status) { case errSecSuccess: { NSString *uuidString = [[NSString alloc] initWithData:(__bridge_transfer NSData *)result encoding:NSUTF8StringEncoding]; statsid->append([uuidString UTF8String]); break; } case errSecItemNotFound: { NSString *uuidString = [[[NSUUID alloc] init] UUIDString]; NSData *uuidData = [uuidString dataUsingEncoding:NSUTF8StringEncoding]; [queryDictionary setObject:uuidData forKey:(__bridge id)kSecValueData]; [queryDictionary setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible]; [queryDictionary removeObjectForKey:(__bridge id)kSecReturnData]; [queryDictionary removeObjectForKey:(__bridge id)kSecMatchLimit]; status = SecItemAdd((__bridge CFDictionaryRef)queryDictionary, NULL); switch (status) { case errSecSuccess: { statsid->append([uuidString UTF8String]); break; } case errSecDuplicateItem: { [queryDictionary removeObjectForKey:(__bridge id)kSecAttrAccessible]; [queryDictionary setObject:@YES forKey:(__bridge id)kSecReturnData]; [queryDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, &result); switch (status) { case errSecSuccess: { NSString *uuidString = [[NSString alloc] initWithData:(__bridge_transfer NSData *)result encoding:NSUTF8StringEncoding]; statsid->append([uuidString UTF8String]); break; } } [queryDictionary removeObjectForKey:(__bridge id)kSecReturnData]; [queryDictionary removeObjectForKey:(__bridge id)kSecMatchLimit]; NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init]; [attributesToUpdate setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible]; status = SecItemUpdate((__bridge CFDictionaryRef)queryDictionary, (__bridge CFDictionaryRef)attributesToUpdate); switch (status) { case errSecSuccess: LOG_debug << "Update statsid keychain item to allow access it after first unlock"; break; default: LOG_err << "SecItemUpdate failed with error code " << status; break; } break; } default: { LOG_err << "SecItemAdd failed with error code " << status; break; } } break; } default: { LOG_err << "SecItemCopyMatching failed with error code " << status; break; } } } void ios_appbasepath(std::string *appbasepath) { appbasepath->assign([NSHomeDirectory() UTF8String]); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/external.cpp��������������������������������������������������������������������0000664�0000000�0000000�00000004130�15162662266�0016346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file gfx/external.cpp * @brief Graphics layer interface for an external implementation * * (c) 2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/gfx/external.h" namespace mega { void GfxProviderExternal::setProcessor(MegaGfxProcessor* gfxProcessor) { processor = gfxProcessor; } bool GfxProviderExternal::readbitmap(const LocalPath& localname, int /*size*/) { if(!processor) return false; bool result = processor->readBitmap(localname.platformEncoded().c_str()); if(!result) return false; w = processor->getWidth(); if(w <= 0) { return false; } h = processor->getHeight(); if(h <= 0) { return false; } return true; } bool GfxProviderExternal::resizebitmap(int rw, int rh, string* imageOut, Hint hint) { if(!processor) return false; int px, py; if (!w || !h) return false; transform(w, h, rw, rh, px, py); if (!w || !h) return false; int size = processor->getBitmapDataSize(w, h, px, py, rw, rh, static_cast<int>(hint)); if(size <= 0) return false; imageOut->resize(static_cast<size_t>(size)); return processor->getBitmapData((char*)imageOut->data(), imageOut->size()); } void GfxProviderExternal::freebitmap() { if(!processor) return; processor->freeBitmap(); } const char *GfxProviderExternal::supportedformats() { return processor ? processor->supportedImageFormats() : nullptr; } const char *GfxProviderExternal::supportedvideoformats() { return processor ? processor->supportedVideoFormats() : nullptr; } } // namespace ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/freeimage.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000074352�15162662266�0016465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file freeimage.cpp * @brief Graphics layer using FreeImage * * (c) 2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/gfx/freeimage.h" #include "mega.h" #include "mega/logging.h" #include "mega/scoped_helpers.h" #include <cmath> #include <optional> #ifdef USE_FREEIMAGE #ifdef _WIN32 #define FreeImage_GetFileTypeX FreeImage_GetFileTypeU #define FreeImage_LoadX FreeImage_LoadU typedef const wchar_t freeimage_filename_char_t; #else #define FreeImage_GetFileTypeX FreeImage_GetFileType #define FreeImage_LoadX FreeImage_Load typedef const char freeimage_filename_char_t; #endif #ifdef HAVE_FFMPEG extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> #include <libavutil/avutil.h> #include <libavutil/mathematics.h> #include <libavutil/display.h> #include <libavutil/imgutils.h> } #endif namespace { struct FIBITMAPDeleter { void operator()(FIBITMAP* dib) const { FreeImage_Unload(dib); } }; using FIBITMAPPtr = std::unique_ptr<FIBITMAP, FIBITMAPDeleter>; // Rescale the image and convert it to standard FIT_BITMAP // @ return nullptr if it fails, otherwise the pointer to the new rescaled image FIBITMAPPtr rescale(FIBITMAP* dib, int width, int height) { const FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib); auto thumbnail = [image_type, dib, width, height]() -> FIBITMAPPtr { switch (image_type) { case FIT_BITMAP: case FIT_UINT16: case FIT_RGB16: case FIT_RGBA16: case FIT_FLOAT: case FIT_RGBF: case FIT_RGBAF: return FIBITMAPPtr{FreeImage_Rescale(dib, width, height, FILTER_BILINEAR)}; default: // Other types are not supported by freeimage return nullptr; } }(); if (!thumbnail || image_type == FIT_BITMAP) return thumbnail; // Convert these non standard bitmap to a standard bitmap FIT_BITMAP switch (image_type) { case FIT_UINT16: return FIBITMAPPtr{FreeImage_ConvertTo8Bits(thumbnail.get())}; case FIT_RGB16: return FIBITMAPPtr{FreeImage_ConvertTo24Bits(thumbnail.get())}; case FIT_RGBA16: return FIBITMAPPtr{FreeImage_ConvertTo32Bits(thumbnail.get())}; case FIT_FLOAT: return FIBITMAPPtr{FreeImage_ConvertToStandardType(thumbnail.get(), TRUE)}; case FIT_RGBF: return FIBITMAPPtr{FreeImage_ToneMapping(thumbnail.get(), FITMO_DRAGO03)}; case FIT_RGBAF: { FIBITMAPPtr rgbf{FreeImage_ConvertToRGBF(thumbnail.get())}; return FIBITMAPPtr{FreeImage_ToneMapping(rgbf.get(), FITMO_DRAGO03)}; } default: return nullptr; }; } using FITAGPtr = std::unique_ptr<FITAG, decltype(&FreeImage_DeleteTag)>; FITAGPtr getTagClone(FREE_IMAGE_MDMODEL model, FIBITMAP* dib, const char* key) { FITAG* searchedTag = nullptr; FreeImage_GetMetadata(model, dib, key, &searchedTag); return ::mega::makeUniqueFrom(FreeImage_CloneTag(searchedTag), &FreeImage_DeleteTag); } FITAG* getTag(FREE_IMAGE_MDMODEL model, FIBITMAP* dib, const char* key) { FITAG* searchedTag = nullptr; FreeImage_GetMetadata(model, dib, key, &searchedTag); return searchedTag; } void removeAllMetadata(FIBITMAP* dib) { if (!dib) return; FreeImage_SetMetadata(FIMD_COMMENTS, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_MAIN, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_EXIF, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_GPS, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_MAKERNOTE, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_INTEROP, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_IPTC, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_XMP, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_GEOTIFF, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_ANIMATION, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_CUSTOM, dib, nullptr, nullptr); FreeImage_SetMetadata(FIMD_EXIF_RAW, dib, nullptr, nullptr); } std::pair<FREE_IMAGE_FORMAT, int> decideSaveFormatFlag(FIBITMAP* dib, bool pngIsAllowed) { return FreeImage_IsTransparent(dib) && pngIsAllowed ? std::make_pair(FIF_PNG, PNG_DEFAULT) : std::make_pair(FIF_JPEG, JPEG_BASELINE | JPEG_OPTIMIZE | 85); } /** * @brief Prepares a standard bitmap image for saving to the target format. * * For JPEG target format, It performs the following steps: * 1. Fills any transparent areas with black. * 2. Converts the image to 24-bit format (required for JPEG). * * @param dib The original standard bitmap image to be processed. * @param format The target format * * @return A pair containing: * - The converted image (nullptr if no conversion was needed). * - A boolean indicating if an error occurred: * - true: An error occurred during processing. * - false: No error occurred. */ std::pair<FIBITMAPPtr, bool> prepareImageForSave(FIBITMAP* dib, FREE_IMAGE_FORMAT format) { assert(format == FIF_PNG || format == FIF_JPEG); // No preparation is needed for PNG if (format != FIF_JPEG) return {nullptr, false}; // Prepare for JPEG FIBITMAPPtr converted{}; if (FreeImage_IsTransparent(dib)) { RGBQUAD black{0, 0, 0, 0}; converted.reset(FreeImage_Composite(dib, FALSE, &black)); if (!converted) { LOG_err << "FreeImage Composite error"; return {nullptr, true}; } dib = converted.get(); } if (FreeImage_GetBPP(dib) != 24) { converted.reset(FreeImage_ConvertTo24Bits(dib)); if (!converted) { LOG_err << "FreeImage ConvertTo24Bits error"; return {nullptr, true}; } } return {std::move(converted), false}; } std::string saveToMemory(FIBITMAP* dib, FREE_IMAGE_FORMAT format, int flag) { // Allocate Memory and Save const auto hmem = ::mega::makeUniqueFrom(FreeImage_OpenMemory(), [](FIMEMORY* p) { FreeImage_CloseMemory(p); }); if (!hmem) return {}; if (!FreeImage_SaveToMemory(format, dib, hmem.get(), flag)) return {}; // To string BYTE* tdata; DWORD tlen; FreeImage_AcquireMemory(hmem.get(), &tdata, &tlen); return std::string{reinterpret_cast<char*>(tdata), tlen}; } // Save the standard bitmap image in jpeg or png format. std::string saveToJpegOrPng(FIBITMAP* dib, bool pngIsAllowed) { if (!dib) return {}; assert(FreeImage_GetImageType(dib) == FIT_BITMAP); const auto [format, flag] = decideSaveFormatFlag(dib, pngIsAllowed); const auto [converted, hasError] = prepareImageForSave(dib, format); if (hasError) return {}; // Point to the converted image if (converted) dib = converted.get(); return saveToMemory(dib, format, flag); } std::optional<WORD> getOrientation(FIBITMAP* dib) { FITAG* tag = getTag(FIMD_EXIF_MAIN, dib, "Orientation"); // Invalid if (!tag || (FreeImage_GetTagID(tag) != 0x112) || (FreeImage_GetTagType(tag) != FIDT_SHORT)) { return std::nullopt; } return *((const WORD*)FreeImage_GetTagValue(tag)); } // // It may rotate the image based on EXIF orientation tag. It may release the original image and // update the pointer to the new rotated image. // FIBITMAPPtr rotateOnExifOrientation(FIBITMAPPtr dib) { const auto orientation = getOrientation(dib.get()); if (!orientation.has_value()) return dib; // Helper: rotate, release the original, and update the pointer to the new rotated one auto rotate = [](FIBITMAPPtr dib, double degree) { if (auto rotated = FreeImage_Rotate(dib.get(), degree); rotated) { dib.reset(rotated); } return dib; }; switch (orientation.value()) { case 1: // Normal break; case 2: // Mirror horizontal FreeImage_FlipHorizontal(dib.get()); break; case 3: // Rotate 180 dib = rotate(std::move(dib), 180); break; case 4: // Mirror vertical FreeImage_FlipVertical(dib.get()); break; case 5: // Mirror horizontal and rotate 270 CW dib = rotate(std::move(dib), 90); FreeImage_FlipVertical(dib.get()); break; case 6: // Rotate 90 CW dib = rotate(std::move(dib), -90); break; case 7: // Mirror horizontal and rotate 90 CW dib = rotate(std::move(dib), -90); FreeImage_FlipVertical(dib.get()); break; case 8: // Rotate 270 CW dib = rotate(std::move(dib), 90); break; default: break; } return dib; } } namespace mega { #if defined(HAVE_FFMPEG) || defined(HAVE_PDFIUM) std::mutex GfxProviderFreeImage::gfxMutex; #endif std::mutex FreeImageInstance::mLock; std::size_t FreeImageInstance::mNumReferences = 0; FreeImageInstance::FreeImageInstance() { std::lock_guard<std::mutex> guard(mLock); // Make sure reference count doesn't overflow. assert(mNumReferences < std::numeric_limits<std::size_t>::max()); // Increment reference counter. ++mNumReferences; #ifdef FREEIMAGE_LIB // Iniitalize if necessary. if (mNumReferences == 1) { FreeImage_Initialise(TRUE); } #endif // FREEIMAGE_LIB } FreeImageInstance::~FreeImageInstance() { std::lock_guard<std::mutex> guard(mLock); // Make sure reference count doesn't borrow. assert(mNumReferences > 0); // Decrement reference counter. --mNumReferences; #ifdef FREEIMAGE_LIB // Deinitialize if necessary. if (mNumReferences == 0) { FreeImage_DeInitialise(); } #endif // FREEIMAGE_LIB } GfxProviderFreeImage::GfxProviderFreeImage() { dib = NULL; w = 0; h = 0; #ifdef HAVE_PDFIUM pdfiumInitialized = false; #endif #ifdef HAVE_FFMPEG // av_log_set_level(AV_LOG_VERBOSE); #endif } GfxProviderFreeImage::~GfxProviderFreeImage() { #ifdef HAVE_PDFIUM gfxMutex.lock(); if (pdfiumInitialized) { PdfiumReader::destroy(); } gfxMutex.unlock(); #endif } #ifdef USE_MEDIAINFO bool GfxProviderFreeImage::readbitmapMediaInfo(const LocalPath& imagePath) { string_type imgPathStr = imagePath.asPlatformEncoded(false); const StringPair& cover = MediaProperties::getCoverFromId3v2(imgPathStr); if (cover.first.empty()) { return false; } FREE_IMAGE_FORMAT format = FIF_UNKNOWN; int flags = 0; if (cover.second == "jpg") { format = FIF_JPEG; flags = JPEG_EXIFROTATE | JPEG_FAST; } else if (cover.second == "png") { format = FIF_PNG; } if (format == FIF_UNKNOWN) { // It either didn't have a cover, or there was a problem reading it, // in which case it should have been already logged by now. return false; } BYTE* dataBytes = (BYTE*)cover.first.c_str(); FIMEMORY* dataMem = FreeImage_OpenMemory(dataBytes, (DWORD)cover.first.size()); dib = FreeImage_LoadFromMemory(format, dataMem, flags); FreeImage_CloseMemory(dataMem); if (!dib) { LOG_warn << "Error converting raw MediaInfo bitmap from memory."; return false; } w = static_cast<int>(FreeImage_GetWidth(dib)); h = static_cast<int>(FreeImage_GetHeight(dib)); return true; } #endif bool GfxProviderFreeImage::readbitmapFreeimage(const LocalPath& imagePath, int size) { // FIXME: race condition, need to use open file instead of filename FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeX(imagePath.asPlatformEncoded(false).c_str()); if (fif == FIF_UNKNOWN) { return false; } // Load flag const auto flag = [fif, size]() { switch (fif) { case FIF_JPEG: return JPEG_FAST | (size << 16); case FIF_RAW: return RAW_PREVIEW; default: return 0; } }(); // Load dib = FreeImage_LoadX(fif, imagePath.asPlatformEncoded(false).c_str(), flag); if (!dib) { return false; } // Rotate first dib = rotateOnExifOrientation(FIBITMAPPtr{dib}).release(); // Remove Metadata after removeAllMetadata(dib); w = static_cast<int>(FreeImage_GetWidth(dib)); h = static_cast<int>(FreeImage_GetHeight(dib)); return w > 0 && h > 0; } #ifdef HAVE_FFMPEG #if LIBAVCODEC_VERSION_MAJOR < 60 #ifdef AV_CODEC_CAP_TRUNCATED #define CAP_TRUNCATED AV_CODEC_CAP_TRUNCATED #else #define CAP_TRUNCATED CODEC_CAP_TRUNCATED #endif #endif const char *GfxProviderFreeImage::supportedformatsFfmpeg() { return ".264.265.3g2.3gp.3gpa.3gpp.3gpp2.mp3" ".avi.dde.divx.evo.f4v.flv.gvi.h261.h263.h264.h265.hevc" ".ismt.ismv.ivf.jpm.k3g.m1v.m2p.m2s.m2t.m2v.m4s.m4t.m4v.mac.mkv.mk3d" ".mks.mov.mp1v.mp2v.mp4.mp4v.mpeg.mpg.mpgv.mpv.mqv.ogm.ogv" ".qt.sls.tmf.trp.ts.ty.vc1.vob.vr.webm.wmv."; } bool GfxProviderFreeImage::isFfmpegFile(const string& ext) { const char* ptr; ptr = strstr(supportedformatsFfmpeg(), ext.c_str()); if (ptr && ptr[ext.size()] == '.') { return true; } return false; } bool GfxProviderFreeImage::readbitmapFfmpeg(const LocalPath& imagePath, int /*size*/) { #ifndef DEBUG av_log_set_level(AV_LOG_PANIC); #endif // Open video file AVFormatContext* formatContext = nullptr; #if defined(LIBAVFORMAT_VERSION_MAJOR) && LIBAVFORMAT_VERSION_MAJOR < 58 // deprecated/no longer required in FFMPEG 4.0: av_register_all(); #endif if (avformat_open_input(&formatContext, imagePath.toPath(false).c_str(), NULL, NULL)) { LOG_warn << "Error opening video: " << imagePath; return false; } auto fmtContextGuard = makeUniqueFrom(&formatContext, avformat_close_input); // Get stream information if (avformat_find_stream_info(formatContext, NULL)) { LOG_warn << "Stream info not found: " << imagePath; return false; } // Find first video stream type AVStream *videoStream = NULL; int videoStreamIdx = 0; for (unsigned i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar && formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStream = formatContext->streams[i]; videoStreamIdx = static_cast<int>(i); break; } } if (!videoStream) { LOG_warn << "Video stream not found: " << imagePath; return false; } // Get codec params to determine video frame dimensions AVCodecParameters *codecParm = videoStream->codecpar; int width = codecParm->width; int height = codecParm->height; if (width <= 0 || height <= 0) { LOG_warn << "Invalid video dimensions: " << width << ", " << height; return false; } if (codecParm->format == AV_PIX_FMT_NONE) { LOG_warn << "Invalid pixel format: " << codecParm->format; return false; } // Find decoder for video stream AVCodecID codecId = codecParm->codec_id; auto decoder = avcodec_find_decoder(codecId); if (!decoder) { LOG_warn << "Codec not found: " << codecId; return false; } AVCodecContext *codecContext = avcodec_alloc_context3(decoder); auto codecContextGuard = makeUniqueFrom(&codecContext, avcodec_free_context); if (!codecContext || avcodec_parameters_to_context(codecContext, codecParm) < 0) { LOG_warn << "Could not copy codec parameters to context"; return false; } // Force seeking to key frames formatContext->seek2any = false; #if LIBAVCODEC_VERSION_MAJOR < 60 if (decoder->capabilities & CAP_TRUNCATED) { codecContext->flags |= CAP_TRUNCATED; } #endif AVPixelFormat sourcePixelFormat = static_cast<AVPixelFormat>(codecParm->format); AVPixelFormat targetPixelFormat = AV_PIX_FMT_BGR24; //raw data expected by freeimage is in this format SwsContext* swsContext = sws_getContext(width, height, sourcePixelFormat, width, height, targetPixelFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL); auto swsContextGuard = makeUniqueFrom(swsContext, sws_freeContext); if (!swsContext) { LOG_warn << "SWS Context not found: " << sourcePixelFormat; return false; } // Open codec if (avcodec_open2(codecContext, decoder, NULL) < 0) { LOG_warn << "Error opening codec: " << codecId; return false; } //Allocate video frames AVFrame* videoFrame = av_frame_alloc(); auto videoFrameGuard = makeUniqueFrom(&videoFrame, av_frame_free); AVFrame* targetFrame = av_frame_alloc(); auto targetFrameGuard = makeUniqueFrom(&targetFrame, av_frame_free); if (!videoFrame || !targetFrame) { LOG_warn << "Error allocating video frames"; return false; } targetFrame->format = targetPixelFormat; targetFrame->width = width; targetFrame->height = height; if (av_image_alloc(targetFrame->data, targetFrame->linesize, targetFrame->width, targetFrame->height, targetPixelFormat, 32) < 0) { LOG_warn << "Error allocating frame"; return false; } auto targetFrameDataGuard = makeUniqueFrom(&targetFrame->data[0], av_freep); // Calculation of seeking point. We need to rescale time units (seconds) to AVStream.time_base units to perform the seeking // Timestamp in streams are measured in frames rather than seconds //int64_t frametimestamp = (int64_t)(5 * AV_TIME_BASE); // Seek five seconds from the beginning int64_t seek_target = 0; if (videoStream->duration != AV_NOPTS_VALUE) { seek_target = videoStream->duration / 5; } else { seek_target = av_rescale_q(formatContext->duration / 5, av_get_time_base_q(), videoStream->time_base); } string extension = imagePath.extension(); if (!extension.empty() && strcmp(extension.c_str(),".mp3") && seek_target > 0 && av_seek_frame(formatContext, videoStreamIdx, seek_target, AVSEEK_FLAG_BACKWARD) < 0) { LOG_warn << "Error seeking video"; return false; } AVPacket* p = av_packet_alloc(); std::unique_ptr<AVPacket*, std::function<void(AVPacket**)>> pCleanup{ &p, [](AVPacket** pkt) { av_packet_free(pkt); } }; AVPacket& packet = *p; packet.data = NULL; packet.size = 0; // Compute the video's rotation. auto rotation = [&]() { uint8_t* matrix = nullptr; #if LIBAVCODEC_VERSION_MAJOR < 60 // Retrieve the video's display matrix. matrix = av_stream_get_side_data(videoStream, AV_PKT_DATA_DISPLAYMATRIX, nullptr); #else const AVPacketSideData* packet_side_data = av_packet_side_data_get(videoStream->codecpar->coded_side_data, videoStream->codecpar->nb_coded_side_data, AV_PKT_DATA_DISPLAYMATRIX); if (packet_side_data) { matrix = packet_side_data->data; } #endif // No display matrix? No rotation. if (!matrix) { return 0; } // Retrieve the video's rotation. const auto rotationValue = av_display_rotation_get((int32_t*)matrix); // is NaN? No rotation if (isnan(rotationValue)) { return 0; } // Round to nearest integer and return return (int)rotationValue; }(); int scalingResult; int actualNumFrames = 0; int result = 0; // Read frames until succesfull decodification or reach limit of 220 frames while (actualNumFrames < 220 && result != AVERROR_EOF) { // Try and read a packet from the file. result = av_read_frame(formatContext, &packet); // Couldn't read a packet from the file due to some (hard) error. // // Note that we don't break here if we couldn't read a packet // because we hit the end of the file. This is because in this case, // the packet will be a dummy which we'll feed into the codec below. if (result < 0 && result != AVERROR_EOF) { break; } // Make sure any data contained in the packet is released at the end // of this iteration. auto avPacketGuard = makeUniqueFrom(&packet, av_packet_unref); // We're only interested in video packets. if (packet.stream_index == videoStream->index) { // Feed the packet we retrieved from the file into our codec. // // Note that the packet we feed into the codec will be a dummy // if we hit the end of the file. The reason we still need to // process this dummy packet is that doing so will put the codec // into "drain mode." // // This is necessary because even though we've hit the end of // the file, the codec may still contain frames for us to // process. result = avcodec_send_packet(codecContext, &packet); // Encountered a hard error passing the packet to the codec. if (result < 0 && result != AVERROR(EAGAIN) && result != AVERROR_EOF) { break; } // Keep extracting decoded frames from the codec for as long as // we can. If we haven't hit the end of the file, this function // will return EAGAIN which means it needs us to feed the codec // more packets. If the function returns EOF, it means that the // codec has been drained and no further decoded frames are // possible. // // Note that this function can only return EOF if the codec had // entered "draining mode." That is, it'll only happen if // av_read_frame above also returned EOF. while ((result = avcodec_receive_frame(codecContext, videoFrame)) >= 0) { if (sourcePixelFormat != codecContext->pix_fmt) { LOG_warn << "Error: pixel format changed from " << sourcePixelFormat << " to " << codecContext->pix_fmt; return false; } scalingResult = sws_scale(swsContext, videoFrame->data, videoFrame->linesize, 0, codecParm->height, targetFrame->data, targetFrame->linesize); if (scalingResult > 0) { const int legacy_align = 1; int imagesize = av_image_get_buffer_size(targetPixelFormat, width, height, legacy_align); FIMEMORY fmemory; fmemory.data = malloc(static_cast<size_t>(imagesize)); if (!fmemory.data) { LOG_warn << "Error allocating image copy buffer"; return false; } auto fmemoryDataGuard = makeUniqueFrom(fmemory.data, free); if (av_image_copy_to_buffer((uint8_t *)fmemory.data, imagesize, targetFrame->data, targetFrame->linesize, targetPixelFormat, width, height, legacy_align) <= 0) { LOG_warn << "Error copying frame"; return false; } int pitch = width * 3; // Assume we can't generate the image from our raw frame. w = 0; h = 0; // Try and generate an image from our raw frame. (dib = FreeImage_ConvertFromRawBits((BYTE*)fmemory.data, width, height, pitch, 24, FI_RGBA_RED_SHIFT, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK | 0xFFFF, TRUE)); if (!dib) { LOG_warn << "Error loading freeimage from memory: " << imagePath; return false; } // Invert any rotation if necessary. if (rotation) { if (auto* temp = FreeImage_Rotate(dib, rotation)) { FreeImage_Unload(dib); dib = temp; } else { LOG_warn << "Couldn't remove rotation from image: " << imagePath; } } w = static_cast<int>(FreeImage_GetWidth(dib)); h = static_cast<int>(FreeImage_GetHeight(dib)); LOG_debug << "Video image ready"; return w > 0 && h > 0; } } actualNumFrames++; } } LOG_warn << "Error reading frame"; return false; } #endif #ifdef HAVE_PDFIUM const char* GfxProviderFreeImage::supportedformatsPDF() { return ".pdf."; } bool GfxProviderFreeImage::isPdfFile(const string &ext) { const char* ptr; ptr = strstr(supportedformatsPDF(), ext.c_str()); if (ptr && ptr[ext.size()] == '.') { return true; } return false; } bool GfxProviderFreeImage::readbitmapPdf(const LocalPath& imagePath, int /*size*/) { std::lock_guard<std::mutex> g(gfxMutex); if (!pdfiumInitialized) { pdfiumInitialized = true; PdfiumReader::init(); } int orientation; #ifdef _WIN32 wstring tmpPath; tmpPath.resize(MAX_PATH); LocalPath workingDir; if (!GetTempPathW(MAX_PATH, (LPWSTR)tmpPath.data())) // If the function fails, the return value is zero. { LOG_warn << "Error getting temporary path to process pdf."; workingDir.clear(); } else { workingDir = LocalPath::fromPlatformEncodedAbsolute(tmpPath.c_str()); } unique_ptr<char[]> data = PdfiumReader::readBitmapFromPdf(w, h, orientation, imagePath, workingDir); #else unique_ptr<char[]> data = PdfiumReader::readBitmapFromPdf(w, h, orientation, imagePath); #endif if (!data || !w || !h) { return false; } dib = FreeImage_ConvertFromRawBits(reinterpret_cast<BYTE*>(data.get()), w, h, w * 4, 32, 0xFF0000, 0x00FF00, 0x0000FF); if (!dib) { LOG_warn << "Error converting raw pdfium bitmap from memory: " << imagePath; return false; } FreeImage_FlipHorizontal(dib); return true; } #endif const char* GfxProviderFreeImage::supportedformats() { if (sformats.empty()) { //Disable thumbnail creation temporarily for .tiff.tif.exr.pict.pic.pct sformats+=".jpg.png.bmp.jpeg.cut.dds.g3.gif.hdr.ico.iff.ilbm" ".jbig.jng.jif.koala.pcd.mng.pcx.pbm.pgm.ppm.pfm.pds.raw.3fr.ari" ".arw.bay.crw.cr2.cap.dcs.dcr.dng.drf.eip.erf.fff.iiq.k25.kdc.mdc.mef.mos.mrw" ".nef.nrw.obm.orf.pef.ptx.pxn.r3d.raf.raw.rwl.rw2.rwz.sr2.srf.srw.x3f.ras.tga" ".xbm.xpm.jp2.j2k.jpf.jpx.webp."; #ifdef HAVE_FFMPEG sformats.append(supportedformatsFfmpeg()); #endif #ifdef HAVE_PDFIUM sformats.append(supportedformatsPDF()); #endif #ifdef USE_MEDIAINFO sformats.append(MediaProperties::supportedformatsMediaInfo()); #endif } return sformats.c_str(); } const char *GfxProviderFreeImage::supportedvideoformats() { return NULL; } bool GfxProviderFreeImage::readbitmap(const LocalPath& localname, int size) { bool bitmapLoaded = false; if (string extension = localname.extension(); !extension.empty()) { #ifdef USE_MEDIAINFO if (MediaProperties::isMediaFilenameExtAudio(extension)) { return readbitmapMediaInfo(localname); } #endif #ifdef HAVE_FFMPEG if (isFfmpegFile(extension)) { bitmapLoaded = true; if (!readbitmapFfmpeg(localname, size) ) { return false; } } #endif #ifdef HAVE_PDFIUM if (isPdfFile(extension)) { bitmapLoaded = true; if (!readbitmapPdf(localname, size) ) { return false; } } #endif } if (!bitmapLoaded) { if (!readbitmapFreeimage(localname, size)) { return false; } } return true; } bool GfxProviderFreeImage::resizebitmap(int rw, int rh, string* imageOut, Hint hint) { int px, py; if (!w || !h) return false; if (dib == NULL) return false; transform(w, h, rw, rh, px, py); if (!w || !h) return false; imageOut->clear(); // Rescale if (auto rescaled = rescale(dib, w, h); rescaled) { FreeImage_Unload(dib); dib = rescaled.release(); } else { return false; } // copy part auto tdib = FreeImage_Copy(dib, px, py, px + rw, py + rh); if (!tdib) { return false; } FreeImage_Unload(dib); dib = tdib; *imageOut = saveToJpegOrPng(dib, hint == Hint::FORMAT_PNG); return !imageOut->empty(); } void GfxProviderFreeImage::freebitmap() { if (dib != NULL) { FreeImage_Unload(dib); } } } // namespace #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/gfx_pdfium.cpp������������������������������������������������������������������0000664�0000000�0000000�00000015515�15162662266�0016665�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file gfx_pdfium.cpp * @brief class to get bitmaps from PDF files using pdfium * * (c) 2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/gfx/gfx_pdfium.h" #include "mega.h" #ifdef HAVE_PDFIUM #define MAX_PDF_MEM_SIZE 1024*1024*100 namespace mega { std::mutex PdfiumReader::pdfMutex; unsigned PdfiumReader::initialized = 0; void PdfiumReader::init() { std::lock_guard<std::mutex> g(pdfMutex); if (!initialized++) { FPDF_LIBRARY_CONFIG config; config.version = 2; config.m_pUserFontPaths = nullptr; config.m_pIsolate = nullptr; config.m_v8EmbedderSlot = 0; FPDF_InitLibraryWithConfig(&config); LOG_debug << "PDFium library initialized."; } } void PdfiumReader::destroy() { std::lock_guard<std::mutex> g(pdfMutex); if (!--initialized) { FPDF_DestroyLibrary(); LOG_debug << "PDFium library destroyed."; } } #ifdef _WIN32 std::unique_ptr<char[]> PdfiumReader::readBitmapFromPdf(int &w, int &h, int &orientation, const LocalPath &path, const LocalPath &workingDirFolder) #else std::unique_ptr<char[]> PdfiumReader::readBitmapFromPdf(int &w, int &h, int &orientation, const LocalPath &path) #endif { std::lock_guard<std::mutex> g(pdfMutex); assert (initialized); FPDF_DOCUMENT pdf_doc = FPDF_LoadDocument(path.toPath(false).c_str(), nullptr); #ifdef _WIN32 LocalPath tmpFilePath; bool removetemporaryfile = false; std::unique_ptr<byte[]> buffer; FSACCESS_CLASS fa; // In Windows it fails if the path has non-ascii chars if (pdf_doc == nullptr && FPDF_GetLastError() == FPDF_ERR_FILE) { std::unique_ptr<FileAccess> pdfFile = fa.newfileaccess(); if (pdfFile->fopen(path, FSLogging::logOnError)) { if (pdfFile->size > MAX_PDF_MEM_SIZE) { if (!workingDirFolder.empty()) { LocalPath originPath = path; tmpFilePath = workingDirFolder; tmpFilePath.appendWithSeparator(LocalPath::fromRelativePath(".megapdftmp"),false); if (fa.copylocal(originPath, tmpFilePath, pdfFile->mtime)) { pdf_doc = FPDF_LoadDocument(tmpFilePath.toPath(false).c_str(), nullptr); removetemporaryfile = true; } } } else if (pdfFile->openf(FSLogging::logOnError)) { buffer.reset(new byte[static_cast<size_t>(pdfFile->size)]); pdfFile->frawread(buffer.get(), static_cast<unsigned>(pdfFile->size), static_cast<m_off_t>(0), true, FSLogging::logOnError); pdfFile->closef(); pdf_doc = FPDF_LoadMemDocument(buffer.get(), static_cast<int>(pdfFile->size), nullptr); } } } #endif if (pdf_doc != nullptr) { int page_count = FPDF_GetPageCount(pdf_doc); if (page_count > 0) { FPDF_PAGE page = FPDF_LoadPage(pdf_doc, 0 /*pageIndex*/); if (page != nullptr) { w = static_cast<int>(FPDF_GetPageWidth(page)); h = static_cast<int>(FPDF_GetPageHeight(page)); // we should restrict the maximum size of PDF pages to process, otherwise // it may require too much memory (and CPU). // as a compromise, the A0 standarized size should be enough for most cases, // A0: 841 x 1188 mm -> 2384 x 3368 points (as returned by FPDF_GetPageX()) // to allow some margins, and rotated ones, avoid larger than 3500 points in // any dimension, which would require a buffer of maximum 3500x3500x4 = ~47MB if ((!w || !h) // error reading size || (w > 3500 || h > 3500)) // page too large { if (!w || !h) { LOG_err << "Error reading PDF page size for " << path; } else { LOG_err << "Page size too large. Skipping PDF preview for " << path; } FPDF_ClosePage(page); FPDF_CloseDocument(pdf_doc); #ifdef _WIN32 if (removetemporaryfile) { fa.unlinklocal(tmpFilePath); } #endif return nullptr; } // BGRA format, 4 bytes per pixel (32bits), byte order: blue, green, red, alpha. std::unique_ptr<char[]> bufferChar(new char[static_cast<size_t>(w * h * 4)]); FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(w, h, FPDFBitmap_BGRA, bufferChar.get(), w * 4); if (!bitmap) //out of memory { LOG_warn << "Error generating bitmap image (OOM)"; FPDF_ClosePage(page); FPDF_CloseDocument(pdf_doc); #ifdef _WIN32 if (removetemporaryfile) { fa.unlinklocal(tmpFilePath); } #endif return nullptr; } FPDFBitmap_FillRect(bitmap, 0, 0, w, h, 0xFFFFFFFF); FPDF_RenderPageBitmap(bitmap, page, 0, 0, w, h, 2, 0); FPDFBitmap_Destroy(bitmap); FPDF_ClosePage(page); FPDF_CloseDocument(pdf_doc); #ifdef _WIN32 if (removetemporaryfile) { fa.unlinklocal(tmpFilePath); } #endif // Needed by Qt: ROTATION_DOWN = 3 orientation = 3; return bufferChar; } else { FPDF_CloseDocument(pdf_doc); LOG_err << "Error loading PDF page to create thumb for " << path; } } else { FPDF_CloseDocument(pdf_doc); LOG_err << "Error getting number of pages for " << path; } } else { LOG_err << "Error loading PDF to create thumbnail for " << path << " " << FPDF_GetLastError(); } #ifdef _WIN32 if (removetemporaryfile) { fa.unlinklocal(tmpFilePath); } #endif return nullptr; } } // namespace mega #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/isolatedprocess.cpp�������������������������������������������������������������0000664�0000000�0000000�00000025542�15162662266�0017741�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/gfx/isolatedprocess.h" #include "mega/filesystem.h" #include "mega/gfx/worker/client.h" #include "mega/logging.h" #include "mega/process.h" #include <algorithm> #include <iterator> #include <mutex> #include <ratio> #include <string> #include <system_error> #include <thread> #include <vector> #include <chrono> using mega::gfx::GfxClient; using std::chrono::milliseconds; using std::chrono::seconds; using std::chrono::duration_cast; using std::chrono::time_point; using std::chrono::steady_clock; using namespace std::chrono_literals; namespace { // Send a shutdown command to the isolated process void shutdown(const std::string& endpointName) { GfxClient::create(endpointName).runShutDown(); } } namespace mega { const milliseconds AutoStartLauncher::MAX_BACKOFF(15 * 1000); const milliseconds AutoStartLauncher::START_BACKOFF(100); void HelloBeater::beat() { auto gfxclient = GfxClient::create(mEndpointName); auto intervalMs = duration_cast<milliseconds>(mPeriod); while(!mShuttingDown) { bool isCancelled = mSleeper.sleep(intervalMs); if (!isCancelled) { gfxclient.runHello("beat"); } } } void HelloBeater::shutdownOnce() { bool wasShuttingdown = mShuttingDown.exchange(true); if (wasShuttingdown) { return; } // cancel sleeper, thread in sleep is woken up if it is mSleeper.cancel(); if (mThread.joinable()) mThread.join(); } HelloBeater::~HelloBeater() { shutdownOnce(); } bool GfxProviderIsolatedProcess::Formats::isValid() const { return mIsValid; } const char* GfxProviderIsolatedProcess::Formats::formats() const { return (mIsValid && !mFormats.empty()) ? mFormats.c_str() : nullptr; } const char* GfxProviderIsolatedProcess::Formats::videoformats() const { return (mIsValid && !mVideoformats.empty()) ? mVideoformats.c_str() : nullptr; } std::unique_ptr<GfxProviderIsolatedProcess> GfxProviderIsolatedProcess::create(const GfxIsolatedProcess::Params& params) { if (!params.isValid()) return nullptr; auto process = std::make_unique<GfxIsolatedProcess>(params); return std::make_unique<GfxProviderIsolatedProcess>(std::move(process)); } void GfxProviderIsolatedProcess::Formats::setOnce(const std::string& formats, const std::string& videoformats) { const std::lock_guard<std::mutex> l(mMutex); // do not set again if it has been set if (mIsValid) return; mFormats = formats; mVideoformats = videoformats; mIsValid = true; } GfxProviderIsolatedProcess::GfxProviderIsolatedProcess(std::unique_ptr<GfxIsolatedProcess> process) : mProcess(std::move(process)) , mEndpointName(mProcess->endpointName()) { assert(mProcess); } std::vector<std::string> GfxProviderIsolatedProcess::generateImages( const LocalPath& localfilepath, const std::vector<GfxDimension>& dimensions) { // default return std::vector<std::string> images(dimensions.size()); auto gfxclient = GfxClient::create(mEndpointName); gfxclient.runGfxTask(localfilepath.toPath(false), dimensions, images); return images; } const char* GfxProviderIsolatedProcess::supportedformats() { return getformats(&Formats::formats); } const char* GfxProviderIsolatedProcess::supportedvideoformats() { return getformats(&Formats::videoformats); } const char* GfxProviderIsolatedProcess::getformats(const char* (Formats::*formatsFn)() const) { // already fetched from the server if (mFormats.isValid()) { return (mFormats.*formatsFn)(); } // do fetching std::string formats, videoformats; if (!GfxClient::create(mEndpointName).runSupportFormats(formats, videoformats)) { return nullptr; } else { mFormats.setOnce(formats, videoformats); return (mFormats.*formatsFn)(); } } AutoStartLauncher::AutoStartLauncher(const std::vector<std::string>& argv, std::function<void()> shutdowner) : mArgv(argv), mShutdowner(std::move(shutdowner)) { assert(!mArgv.empty()); } bool AutoStartLauncher::startUntilSuccess(Process& process) { milliseconds backOff = START_BACKOFF; while (!mShuttingDown) { // if previous gfxwoker is running // it could block this one // shutdown it blindly if (mShutdowner) mShutdowner(); // run if (process.run(mArgv)) { LOG_verbose << "process is started"; return true; } // backoff if fails LOG_err << "couldn't not start: " << mArgv[0]; mSleeper.sleep(backOff); backOff = std::min(backOff * 2, MAX_BACKOFF); // double it and MAX_BACKOFF at most } // ends due to shutdown return false; } bool AutoStartLauncher::start() { static const milliseconds maxBackoff(3000); static const milliseconds fastFailureThreshold(1000); if (mArgv.empty()) return false; // There are permanent startup failure such as missing DLL. This is not likey to happen // at customer's side as it will be installed properly. It is more likely during development // and testing phases. We want to implement some backOff to reduce CPU usage if it does happen // There are program crash due to gfx processing. We don't want to have backOff for this scenario // and here assume the continuously gfx processing crash is so few. // This is a naive way checking used seconds as the judgement auto backoffForFastFailure = [this](std::function<void()> f) { milliseconds backOff = START_BACKOFF; while(!mShuttingDown) { const time_point<steady_clock> start = steady_clock::now(); f(); const auto used = milliseconds((steady_clock::now() - start) / milliseconds(1)); // if less than threshhold, it fails right after startup. if ((used < fastFailureThreshold) && !mShuttingDown) { // LOG_verbose << "process existed too fast: " << used.count() << " backoff " // << backOff.count() << "ms"; mSleeper.sleep(backOff); backOff = std::min(backOff * 2, maxBackoff); // double it and maxBackoff at most } else { backOff = START_BACKOFF; } } }; auto launcher = [this, backoffForFastFailure]() { // Keep a copy, so the object is always live while the code is running auto keepRef = shared_from_this(); mThreadIsRunning = true; backoffForFastFailure([this](){ Process process; if (startUntilSuccess(process)) { bool ret = process.wait(); LOG_verbose << "wait: " << ret << " hasSignal: " << process.hasTerminateBySignal() << " " << (process.hasTerminateBySignal() ? std::to_string(process.getTerminatingSignal()) : "") << " hasExited: " << process.hasExited() << " " << (process.hasExited() ? std::to_string(process.getExitCode()) : ""); } }); mThreadIsRunning = false; }; mThread = std::thread(launcher); return true; } // // there is racing that mShutdowner() signal will be lost while the process // is just starting. so we'll retry in the loop, but there is no reason it // couldn't be shut down in 15 seconds // // @return true if thread exits, otherwise false bool AutoStartLauncher::exitLaunchLoopThread() { milliseconds interval{10}; milliseconds totalWaitTime{0}; while (mThreadIsRunning && totalWaitTime < 15s) { LOG_verbose << "interval " << interval.count() << " totalWaitTime " << totalWaitTime.count(); // shutdown the started process if (mShutdowner) mShutdowner(); // wait std::this_thread::sleep_for(interval); // Update total wait time totalWaitTime += interval; // backoff interval += 10ms; } return !mThreadIsRunning; } void AutoStartLauncher::stop() { bool wasShuttingdown = mShuttingDown.exchange(true); if (wasShuttingdown) { return; } LOG_info << "AutoStartLauncher is shutting down"; // cancel sleeper, thread in sleep is woken up if it is mSleeper.cancel(); if (exitLaunchLoopThread()) { if (mThread.joinable()) mThread.join(); } else { // Defensive: the thread doesn't exit, detach the thread // We had such bug and it is usually a bug assert(false && "AutoStartLauncher detaching loop thread"); LOG_warn << "AutoStartLauncher detaching loop thread"; mThread.detach(); } LOG_info << "AutoStartLauncher is down"; } bool CancellableSleeper::sleep(const milliseconds& period) { std::unique_lock<std::mutex> l(mMutex); if (mCancelled) return true; return mCv.wait_for(l, period, [this](){ return mCancelled;}); } void CancellableSleeper::cancel() { std::lock_guard<std::mutex> l(mMutex); mCancelled = true; mCv.notify_all(); } GfxIsolatedProcess::Params::Params(const std::string& endpointName, const std::string& executable, std::chrono::seconds keepAliveInSeconds, const std::vector<std::string>& rawArguments): endpointName(endpointName), executable(executable), keepAliveInSeconds(std::max(MIN_ALIVE_SECONDS, keepAliveInSeconds)), rawArguments(rawArguments) { } std::vector<std::string> GfxIsolatedProcess::Params::toArgs() const { LocalPath absolutePath = LocalPath::fromAbsolutePath(executable); std::vector<std::string> commandArgs = {absolutePath.toPath(false), "-n=" + endpointName, "-l=" + std::to_string(keepAliveInSeconds.count())}; // Append raw arguments // For simplicity, duplication isn't checked, leave to the executable to deal with std::copy(rawArguments.begin(), rawArguments.end(), std::back_inserter(commandArgs)); return commandArgs; } // We divide keepAliveInSeconds by three to set up mBeater so that it allows at least two // beats within the keep-alive period. GfxIsolatedProcess::GfxIsolatedProcess(const Params& params): mEndpointName{ params.endpointName }, mLauncher{new AutoStartLauncher{params.toArgs(), [endpointName = params.endpointName]() { shutdown(endpointName); }}}, mBeater{seconds(params.keepAliveInSeconds / 3), params.endpointName} { mLauncher->start(); } GfxIsolatedProcess::~GfxIsolatedProcess() { mLauncher->stop(); } } // Namespace ��������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/worker/�������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015333�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/worker/client.cpp���������������������������������������������������������������0000664�0000000�0000000�00000012774�15162662266�0017330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/gfx/worker/client.h" #include "mega/gfx/worker/command_serializer.h" #include "mega/gfx/worker/commands.h" #include "mega/gfx/worker/comms.h" #include "mega/logging.h" #include "mega/filesystem.h" #include "mega/types.h" #include <chrono> #include <memory> #include <thread> #include <tuple> #include <unordered_set> using std::chrono::milliseconds; namespace mega { namespace gfx { GfxClient::GfxClient(std::unique_ptr<IGfxCommunicationsClient> comms) : mComms{std::move(comms)} { assert(mComms); } bool GfxClient::runHello(const std::string& text) { auto endpoint = connect(); if (!endpoint) { LOG_err << "runHello Couldn't connect"; return false; } CommandHello command; command.Text = text; auto response = sendAndReceive<CommandHelloResponse>(endpoint.get(), command); if (response) { return true; } else { LOG_err << "GfxClient couldn't get hello response"; return false; } } bool GfxClient::runShutDown() { auto endpoint = connect(); if (!endpoint) { LOG_err << "runShutDown Couldn't connect"; return false; } CommandShutDown command; if (sendAndReceive<CommandShutDownResponse>(endpoint.get(), command)) { LOG_verbose << "GfxClient gets shutdown response"; return true; } else { LOG_err << "GfxClient couldn't get shutdown response"; return false; } } bool GfxClient::runGfxTask(const std::string& localpath, const std::vector<GfxDimension>& dimensions, std::vector<std::string>& images) { // 3 seconds at most auto endpoint = connectWithRetry(milliseconds(100), 30); if (!endpoint) { LOG_err << "runGfxTask Couldn't connect"; return false; } CommandNewGfx command; command.Task.Path = LocalPath::fromAbsolutePath(localpath).platformEncoded(); command.Task.Dimensions = dimensions; auto addReponse = sendAndReceive<CommandNewGfxResponse>(endpoint.get(), command); if (!addReponse) { LOG_err << "GfxClient couldn't get gfxTask response, " << localpath; return false; } else if (addReponse->ErrorCode == static_cast<uint32_t>(GfxTaskProcessStatus::ERR)) { LOG_info << "GfxClient gets gfxTask response with error: " << addReponse->ErrorText << ", " << localpath; return false; } else { LOG_verbose << "GfxClient gets gfxTask response successfully, " << localpath; images = std::move(addReponse->Images); return true; } } bool GfxClient::runSupportFormats(std::string& formats, std::string& videoformats) { auto endpoint = connectWithRetry(milliseconds(100), 30); // 3 seconds at most if (!endpoint) { LOG_err << "runSupportFormats Couldn't connect"; return false; } CommandSupportFormats command; auto reponse = sendAndReceive<CommandSupportFormatsResponse>(endpoint.get(), command); if (!reponse) { LOG_err << "GfxClient couldn't get supportformats response"; return false; } else { formats = std::move(reponse->formats); videoformats = std::move(reponse->videoformats); return true; } } GfxClient GfxClient::create(const std::string& endpointName) { return GfxClient(std::make_unique<GfxCommunicationsClient>(endpointName)); } // // CommError::NOT_EXIST is returned when server is not running, this could due to server is // restarted CommError::CLOSED is returned when server closed the handle such as it is crashed, only // on Windows // bool GfxClient::isConnectRetryError(CommError error) const { static const std::unordered_set<CommError> retryErrors = {CommError::NOT_EXIST, CommError::CLOSED}; return retryErrors.find(error) != retryErrors.end(); } std::unique_ptr<IEndpoint> GfxClient::connectWithRetry(milliseconds backoff, unsigned int maxRetries) { unsigned int loop = 0; do { auto [error, endpoint] = mComms->connect(); // connected if (endpoint) { return std::move(endpoint); // endpoint is reference } if (++loop > maxRetries) { return nullptr; } if (isConnectRetryError(error)) { std::this_thread::sleep_for(backoff); continue; } else { return nullptr; } } while (true); } std::unique_ptr<IEndpoint> GfxClient::connect() { return connectWithRetry(milliseconds(0), 0); } template<typename ResponseT, typename RequestT> std::unique_ptr<ResponseT> GfxClient::sendAndReceive(IEndpoint* endpoint, RequestT command, milliseconds sendTimeout, milliseconds receiveTimeout) { // send a request ProtocolWriter writer(endpoint); writer.writeCommand(&command, sendTimeout); // get the response ProtocolReader reader(endpoint); auto response = reader.readCommand(receiveTimeout); if (!dynamic_cast<ResponseT*>(response.get())) { LOG_err << "GfxClient couldn't get response"; return nullptr; } else { LOG_verbose << "GfxClient gets response"; return std::unique_ptr<ResponseT>(static_cast<ResponseT*>(response.release())); } } } } ����sdk-10.11.0/src/gfx/worker/command_serializer.cpp���������������������������������������������������0000664�0000000�0000000�00000006225�15162662266�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/gfx/worker/command_serializer.h" #include "mega/logging.h" #include "mega/utils.h" #include <chrono> using std::chrono::milliseconds; namespace { template<typename T> bool isInOpenEndRange(const T& value, const T& low, const T& high) { return value >= low && value < high; } } namespace mega { namespace gfx { enum class CommandProtocolVersion { V_1 = 1, UNSUPPORTED }; bool ProtocolWriter::writeCommand(ICommand* command, milliseconds timeout) const { auto dataToWrite = CommandSerializer::serialize(command); if (!dataToWrite) { return false; } if (!mWriter->write(dataToWrite->data(), dataToWrite->length(), timeout)) { return false; } return true; } std::unique_ptr<ICommand> ProtocolReader::readCommand(milliseconds timeout) const { return CommandSerializer::unserialize(*mReader, timeout); } std::unique_ptr<std::string> CommandSerializer::serialize(ICommand* command) { std::string dataToReturn; CacheableWriter writer(dataToReturn); // protocol version unit32_t writer.serializeu32(static_cast<uint32_t>(CommandProtocolVersion::V_1)); // command type writer.serializeu32(static_cast<uint32_t>(command->type())); // length and command std::string commandData = command->serialize(); writer.serializestring_u32(commandData); return std::make_unique<std::string>(std::move(dataToReturn)); } bool CommandSerializer::unserializeUInt32(IReader& reader, uint32_t& data, milliseconds timeout) { return reader.read(&data, sizeof(uint32_t), timeout); } bool CommandSerializer::unserializeString(IReader& reader, std::string& data, milliseconds timeout) { uint32_t len; if (!unserializeUInt32(reader, len, timeout)) { return false; } if (len == 0) // pipe couldn't read 0 byte, terminate early { return true; } data.resize(len); return reader.read(const_cast<char *>(data.data()), len, timeout); } std::unique_ptr<ICommand> CommandSerializer::unserialize(IReader& reader, milliseconds timeout) { // proto version unit32_t uint32_t protoVer; if (!reader.read(&protoVer, sizeof(protoVer), timeout)) { return nullptr; } if (protoVer != static_cast<uint32_t>(CommandProtocolVersion::V_1)) { return nullptr; } // command type uint32_t type; if (!reader.read(&type, sizeof(type), timeout)) { return nullptr; } if (!isInOpenEndRange(type, static_cast<uint32_t>(CommandType::BEGIN), static_cast<uint32_t>(CommandType::END))) { return nullptr; } // command data std::string data; if (!unserializeString(reader, data, timeout)) { return nullptr; } return unserializeCommand(static_cast<CommandType>(type), data); } std::unique_ptr<ICommand> CommandSerializer::unserializeCommand(CommandType type, const std::string& data) { auto command = ICommand::factory(type); if (!command) return nullptr; if (!command->unserialize(data)) { LOG_err << "CommandSerializer::unserializeCommand unable to unseriaize"; return nullptr; } return command; } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/gfx/worker/commands.cpp�������������������������������������������������������������0000664�0000000�0000000�00000014244�15162662266�0017645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/gfx/worker/commands.h" #include "mega/utils.h" #include <string> #include <cassert> namespace { using mega::CacheableWriter; using mega::CacheableReader; using mega::GfxDimension; class GfxSerializationHelper { static constexpr size_t MAX_VECT_SIZE = 100; public: static void serialize(CacheableWriter& writer, const GfxDimension& source) { writer.serializeu32(static_cast<uint32_t>(source.w())); writer.serializeu32(static_cast<uint32_t>(source.h())); } static void serialize(CacheableWriter& writer, const std::string& source) { writer.serializestring_u32(source); } template<typename T> static void serialize(CacheableWriter& writer, const std::vector<T>& target) { auto vecSize = target.size(); assert(vecSize < std::numeric_limits<uint32_t>::max()); writer.serializeu32(static_cast<uint32_t>(vecSize)); for (const auto& entry : target) { GfxSerializationHelper::serialize(writer, entry); } } static bool unserialize(CacheableReader& reader, GfxDimension& target) { uint32_t w = 0; if (!reader.unserializeu32(w)) { return false; } uint32_t h = 0; if (!reader.unserializeu32(h)) { return false; } target.setW(static_cast<int>(w)); target.setH(static_cast<int>(h)); return true; } static bool unserialize(CacheableReader& reader, std::string& target) { return reader.unserializestring_u32(target); } template<typename T> static bool unserialize(CacheableReader& reader, std::vector<T>& target, const size_t maxVectSize = MAX_VECT_SIZE) { uint32_t vecSize = 0; if (!reader.unserializeu32(vecSize)) { return false; } if (vecSize > maxVectSize) { return false; } target.resize(vecSize); for (auto& entry : target) { if (!GfxSerializationHelper::unserialize(reader, entry)) { return false; } } return true; } }; } namespace mega { namespace gfx { std::unique_ptr<ICommand> ICommand::factory(CommandType type) { switch (type) { case CommandType::NEW_GFX: return std::make_unique<CommandNewGfx>(); case CommandType::NEW_GFX_RESPONSE: return std::make_unique<CommandNewGfxResponse>(); case CommandType::SHUTDOWN: return std::make_unique<CommandShutDown>(); case CommandType::SHUTDOWN_RESPONSE: return std::make_unique<CommandShutDownResponse>(); case CommandType::HELLO: return std::make_unique<CommandHello>(); case CommandType::HELLO_RESPONSE: return std::make_unique<CommandHelloResponse>(); case CommandType::SUPPORT_FORMATS: return std::make_unique<CommandSupportFormats>(); case CommandType::SUPPORT_FORMATS_RESPONSE: return std::make_unique<CommandSupportFormatsResponse>(); default: assert(false); return nullptr; } } std::string CommandShutDown::serialize() const { return ""; } bool CommandShutDown::unserialize(const std::string& /*data*/) { return true; } std::string CommandShutDownResponse::serialize() const { return ""; } bool CommandShutDownResponse::unserialize(const std::string& /*data*/) { return true; } std::string CommandNewGfx::serialize() const { std::string toret; CacheableWriter writer(toret); writer.serializestring_u32(Task.Path); GfxSerializationHelper::serialize(writer, Task.Dimensions); return toret; } bool CommandNewGfx::unserialize(const std::string& data) { CacheableReader reader(data); // path if (!reader.unserializestring_u32(Task.Path)) { return false; } // dimensions if (!GfxSerializationHelper::unserialize(reader, Task.Dimensions)) { return false; } // empty dimensions considered an invalid task if (Task.Dimensions.size() == 0) { return false; } return true; } std::string CommandNewGfxResponse::serialize() const { std::string toret; CacheableWriter writer(toret); writer.serializeu32(ErrorCode); writer.serializestring_u32(ErrorText); GfxSerializationHelper::serialize(writer, Images); return toret; } bool CommandNewGfxResponse::unserialize(const std::string& data) { CacheableReader reader(data); // ErrorCode if (!reader.unserializeu32(ErrorCode)) { return false; } // ErrorText if (!reader.unserializestring_u32(ErrorText)) { return false; } // images if (!GfxSerializationHelper::unserialize(reader, Images)) { return false; } return true; } std::string CommandHello::serialize() const { std::string toret; CacheableWriter writer(toret); writer.serializestring_u32(Text); return toret; } bool CommandHello::unserialize(const std::string& data) { CacheableReader reader(data); // Text if (!reader.unserializestring_u32(Text)) { return false; } return true; } std::string CommandHelloResponse::serialize() const { std::string toret; CacheableWriter writer(toret); writer.serializestring_u32(Text); return toret; } bool CommandHelloResponse::unserialize(const std::string& data) { CacheableReader reader(data); // Text if (!reader.unserializestring_u32(Text)) { return false; } return true; } std::string CommandSupportFormats::serialize() const { return ""; } bool CommandSupportFormats::unserialize(const std::string& /*data*/) { return true; } std::string CommandSupportFormatsResponse::serialize() const { std::string toret; CacheableWriter writer(toret); writer.serializestring_u32(formats); writer.serializestring_u32(videoformats); return toret; } bool CommandSupportFormatsResponse::unserialize(const std::string& data) { CacheableReader reader(data); // formats if (!reader.unserializestring_u32(formats)) { return false; } // videoformats if (!reader.unserializestring_u32(videoformats)) { return false; } return true; } } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/hashcash.cpp������������������������������������������������������������������������0000664�0000000�0000000�00000035714�15162662266�0015536�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file hashcash.cpp * @brief Mega SDK PoW for login */ #include "mega/hashcash.h" #include "mega/base64.h" #include "mega/canceller.h" #include "mega/logging.h" #include "mega/testhooks.h" #include "mega/utils.h" #if defined(__APPLE__) #include <mach/host_info.h> #include <mach/mach.h> #elif !defined(_WIN32) #include <unistd.h> #endif namespace mega { namespace { using Clock = std::chrono::steady_clock; constexpr std::size_t kTokenBytes = 48; constexpr std::size_t kPrefixBytes = 4; constexpr std::size_t kRepeat = 262144; // 12MB / 48B constexpr std::size_t kBufSize = kPrefixBytes + kRepeat * kTokenBytes; constexpr std::size_t kSha256Block = CryptoPP::SHA256::BLOCKSIZE; #ifndef NDEBUG constexpr std::chrono::milliseconds kTtl{60000}; constexpr std::chrono::milliseconds kMinBudget{10000}; // constexpr unsigned kMaxRetries{5}; #else constexpr std::chrono::milliseconds kTtl{300000}; constexpr std::chrono::milliseconds kMinBudget{30000}; #endif constexpr unsigned kMaxRetries{2}; #if defined(__ANDROID__) || defined(USE_IOS) constexpr int kTtlDivisor{2}; #else constexpr int kTtlDivisor{3}; #endif RetryGencash retryGencash{}; uint32_t thresholdFromEasiness(const uint8_t e) { return static_cast<uint32_t>((((e & 63) << 1) + 1) << ((e >> 6) * 7 + 3)); } void initTokenArea(const std::string& tokenBin, std::vector<uint8_t>& buf) { assert(buf.size() == kBufSize); assert(tokenBin.size() == kTokenBytes); std::memcpy(buf.data() + kPrefixBytes, tokenBin.data(), kTokenBytes); std::size_t filled = kTokenBytes; while (filled < kRepeat * kTokenBytes) { const auto copy = std::min(filled, kRepeat * kTokenBytes - filled); std::memcpy(buf.data() + kPrefixBytes + filled, buf.data() + kPrefixBytes, copy); filled += copy; } } uint32_t sha256FirstWord(const uint8_t* data, const std::size_t len) { assert(data != nullptr); assert((len >= kSha256Block) && "SHA256 processes >= 1 block (64-byte)"); CryptoPP::SHA256 h; h.Update(data, static_cast<unsigned>(len)); uint32_t word{}; h.TruncatedFinal(reinterpret_cast<CryptoPP::byte*>(&word), 4); return word; } std::tuple<bool, std::string> getTokenBin(const std::string& token) { const auto tokenBin = ::mega::Base64::atob(token); if (tokenBin.size() != kTokenBytes) { LOG_err << "[getTokenBin] tokenBin.size (" << tokenBin.size() << ") != kTokenBytes (" << kTokenBytes << ") -> corrupted token from server? check with API"; return {false, {}}; } return {true, tokenBin}; } unsigned decideWorkers(const unsigned maxWorkers) { assert(maxWorkers > 0); const auto maxWorkersForDevice = std::max(std::thread::hardware_concurrency(), 1u); const auto workers = std::clamp(maxWorkers, 1u, maxWorkersForDevice); LOG_verbose << "[decideWorkers] workers = " << workers << " [maxWorkers = " << maxWorkers << ", maxWorkersForDevice = " << maxWorkersForDevice << "]"; return workers; } std::chrono::milliseconds computeBudget(const std::chrono::milliseconds ttl = kTtl) { if (ttl != kTtl) return ttl; const auto third = ttl / kTtlDivisor; const auto max90 = ttl - ttl / 10; return std::clamp(third, kMinBudget, max90); } std::chrono::milliseconds adjustBudget(const std::chrono::milliseconds budget) { if (retryGencash.mForceRetryCount >= RetryGencash::kMaxRetries) { LOG_verbose << "[computeBudget] forced retries (" << retryGencash.mForceRetryCount << " exceeded maxRetries = " << kMaxRetries << " <-- no more timeouts will be forced"; return std::chrono::hours(24 * 7); } return budget; } /** * @brief Search one stride of the HashCash nonce space using a shared precomputed message area. * * This worker enumerates 32 bit nonces in network (big endian) order: * * n = start, start + stride, start + 2*stride, ... * * For each candidate nonce it computes SHA-256 over the logical message * * [4-byte NONCE || (tokenArea bytes [4..63]) || (tokenArea bytes [64..tokenAreaSize-1])] * * Implementation notes: * - The first 64 bytes are split into a small hot block prepared on stack: * * block[0..3] is filled with NONCE in network order for each iteration. * * block[4..63] is copied once from tokenArea + 4 (60 bytes). * - The remaining bytes [tokenArea + 64 .. tokenArea + tokenAreaSize) are streamed as the * "cold" part, tokenArea is treated as read only and is shared across workers. * - hasher is thread_local, so there is no cross thread contention. * * A candidate is accepted when the top 32 bits of the SHA-256 digest (interpreted in big endian) * are <= thresholdNetOrder, which itself must be supplied in network (big endian) order. * * Cancellation / early exit: * - The loop exits as soon as either: * * stop becomes true (another worker won or the coordinator asked us to stop), or * * scopedCanceller.triggered() observes a global cancel (e.g., app requested logout). * - On success this worker does stop.store(true) to notify peers promptly. * * Threading & safety: * - tokenArea must outlive this call for the duration of the worker. * - tokenArea is never written to by this function (read only). * - stop is shared by all workers of the same gencash() attempt. * * @param tokenArea Pointer to the precomputed message area. Layout requirement: * bytes [0..3] correspond to the NONCE slot; bytes [4..] contain * token derived material prepared by the coordinator. * @param tokenAreaSize Size in bytes of @p tokenArea. Must be >= 64. * @param thresholdNetOrder Difficulty threshold in network (big endian) order. * @param start First nonce for this worker (0 <= start < stride). * @param stride Distance between successive nonces for this worker (> 0). * @param stop Shared flag; set to true when any worker finds a hit or the * coordinator decides to terminate early. * @param scopedCanceller Snapshot of a global cancel epoch. If it trips, the worker exits. * * @return Base64 encoded 4 byte NONCE (in network order) on success, or an empty string if * this worker did not win (either because another thread won or cancellation occurred). */ std::string gencashWorker(const uint8_t* tokenArea, const std::size_t tokenAreaSize, const uint32_t thresholdNetOrder, const uint32_t start, const uint32_t stride, std::atomic<bool>& stop, const ScopedCanceller& scopedCanceller) { assert(tokenArea); assert(tokenAreaSize >= 64); // we are going to read area+64 assert(stride > 0 && start < stride); // First 64 bytes of the logical message are: // [4B nonce][60B from area starting at offset 4] const auto* tokenStart = tokenArea + 4; uint8_t block[64]; std::memcpy(block + 4, tokenStart, 60); thread_local CryptoPP::SHA256 hasher; const auto stopCondition = [&stop, &scopedCanceller]() -> bool { return stop.load(std::memory_order_relaxed) || scopedCanceller.triggered(); }; for (uint32_t n = start; !stopCondition(); n += stride) { *reinterpret_cast<uint32_t*>(block) = htonl(n); hasher.Restart(); hasher.Update(block, 64); hasher.Update(tokenArea + 64, static_cast<unsigned>(tokenAreaSize - 64)); if (stopCondition()) { break; } uint32_t firstWord{}; hasher.TruncatedFinal(reinterpret_cast<CryptoPP::byte*>(&firstWord), sizeof(uint32_t)); if (htonl(firstWord) <= thresholdNetOrder) { stop.store(true, std::memory_order_relaxed); const uint32_t nonceNetOrder = htonl(n); return ::mega::Base64::btoa( std::string(reinterpret_cast<const char*>(&nonceNetOrder), sizeof(uint32_t))); } } return {}; } } // namespace std::string gencash(const std::string& token, const uint8_t easiness, const std::chrono::milliseconds ttl, const cancel_epoch_t reqSnapshot, const unsigned maxWorkers) { const ScopedCanceller scopedCanceller{reqSnapshot}; const auto& start = Clock::now(); const auto initialBudget = computeBudget(ttl); const auto budget = adjustBudget(initialBudget); const auto deadline = start + budget; const auto workers = decideWorkers(maxWorkers); DEBUG_TEST_HOOK_HASHCASH_CALCULATION_STARTED; const auto checkCancel = [&scopedCanceller, &easiness, &workers, &start](const bool cancelTriggered = false) -> bool { if (cancelTriggered || scopedCanceller.triggered()) { LOG_verbose << "[gencash] Calculating hashcash with easiness = " << +easiness << " has been CANCELLED by external request -> exit and reset num retries [workers " "= " << workers << "] [timelapsed = " << std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start) .count() << " ms] [numRetriesDueToTimeout = " << retryGencash.mForceRetryCount << "]"; retryGencash = {}; return true; } return false; }; if (checkCancel()) { return {}; } LOG_verbose << "[gencash] Calculating hashcash with easiness = " << static_cast<int>(easiness) << " and workers = " << workers << " [maxWorkers = " << maxWorkers << "] [timeLimit = " << budget.count() << " ms]"; // 1) Precompute everything once const auto [tokenBinResult, tokenBin] = getTokenBin(token); if (tokenBinResult == false) { retryGencash = {}; return {}; } auto tokenArea = std::make_shared<std::vector<uint8_t>>(kBufSize); initTokenArea(tokenBin, *tokenArea); const auto threshold = thresholdFromEasiness(easiness); if (checkCancel()) { return {}; } // 2) Spawn workers that all read from the same buffer std::atomic<bool> stop{false}; std::string winner; std::mutex winnerMx; std::condition_variable cv; std::vector<std::thread> pool; pool.reserve(workers); for (uint32_t w = 0; w < workers; ++w) { pool.emplace_back( [&tokenArea, &threshold, w, workers, &stop, &scopedCanceller, &winnerMx, &winner, &cv] { auto local = gencashWorker(tokenArea->data(), tokenArea->size(), threshold, w, workers, stop, scopedCanceller); bool shouldNotify{false}; if (!local.empty()) { std::lock_guard<std::mutex> lk(winnerMx); if (winner.empty()) { winner = std::move(local); shouldNotify = true; } } shouldNotify |= scopedCanceller.triggered(); if (shouldNotify) { stop.store(true, std::memory_order_relaxed); cv.notify_all(); } }); } bool cancelTriggered{false}; { std::unique_lock<std::mutex> lk(winnerMx); cv.wait_until(lk, deadline, [&] { return !winner.empty() || scopedCanceller.triggered(); }); cancelTriggered = scopedCanceller.triggered(); if (const auto earlyExit = winner.empty() || cancelTriggered; earlyExit) { stop.store(true, std::memory_order_relaxed); } } for (auto& t: pool) t.join(); const auto timeLapsed = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start); if (const auto forcedTimeout = winner.empty() && !cancelTriggered; forcedTimeout) { retryGencash = RetryGencash{retryGencash.mForceRetryCount + 1, easiness, budget, timeLapsed}; LOG_verbose << "[gencash] Calculating hashcash exceeded deadline -> EARLY EXIT & retry " "[timeLimit = " << budget.count() << " ms, timelapsed = " << timeLapsed.count() << " ms [workers = " << workers << "] [numRetries = " << retryGencash.mForceRetryCount << "]"; return winner; } if (checkCancel(cancelTriggered)) { return {}; } assert(!winner.empty()); LOG_verbose << "[gencash] Calculated hashcash with easiness = " << +easiness << " and workers = " << workers << " in " << timeLapsed.count() << " ms" << " [numRetries = " << retryGencash.mForceRetryCount << "]"; retryGencash = (retryGencash.mForceRetryCount >= RetryGencash::kMaxRetries && timeLapsed >= initialBudget) ? RetryGencash{0, easiness, initialBudget, timeLapsed} : RetryGencash{}; return winner; } std::string gencash(const std::string& token, const uint8_t easiness, const cancel_epoch_t reqSnapshot, const unsigned maxWorkers) { return gencash(token, easiness, kTtl, reqSnapshot, maxWorkers); } std::string gencash(const std::string& token, const uint8_t easiness, const cancel_epoch_t reqSnapshot) { return gencash(token, easiness, kTtl, reqSnapshot, MAX_WORKERS_FOR_GENCASH); } bool validateHashcash(const std::string& token, const uint8_t easiness, const std::string& prefixB64) { const auto prefix = Base64::atob(prefixB64); if (prefix.size() != kPrefixBytes) { LOG_debug << "[validateHashcash] prefix.size (" << prefix.size() << ") != valid prefix bytes (" << kPrefixBytes << ") -> return false"; return false; } const auto [tokenBinResult, tokenBin] = getTokenBin(token); if (tokenBinResult == false) { return false; } std::vector<uint8_t> buf(kBufSize); initTokenArea(tokenBin, buf); std::memcpy(buf.data(), prefix.data(), kPrefixBytes); const auto word = sha256FirstWord(buf.data(), buf.size()); return htonl(word) <= thresholdFromEasiness(easiness); } std::optional<RetryGencash> retryGencashData() { using namespace std::chrono_literals; assert(retryGencash.mEasiness == 0 || (retryGencash.mBudget > 0ms && (retryGencash.mGencashTime >= retryGencash.mBudget))); if (retryGencash.mEasiness == 0) return {}; return {retryGencash}; } } // namespace mega ����������������������������������������������������sdk-10.11.0/src/heartbeats.cpp����������������������������������������������������������������������0000664�0000000�0000000�00000030647�15162662266�0016076�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file heartbeats.cpp * @brief Classes for heartbeating Sync configuration and status * * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/heartbeats.h" #include "assert.h" #include "mega.h" #include "mega/command.h" #include "mega/testhooks.h" namespace mega { #ifdef ENABLE_SYNC static constexpr int FREQUENCY_HEARTBEAT_DS = 300; HeartBeatBackupInfo::HeartBeatBackupInfo() { } m_time_t HeartBeatBackupInfo::lastAction() const { return mLastAction; } handle HeartBeatBackupInfo::lastItemUpdated() const { return mLastItemUpdated; } void HeartBeatBackupInfo::setLastSyncedItem(const handle &lastSyncedItem) { if (mLastItemUpdated != lastSyncedItem) { mLastItemUpdated = lastSyncedItem; updateLastActionTime(); } } void HeartBeatBackupInfo::setLastAction(const m_time_t &lastAction) { mLastAction = lastAction; } void HeartBeatBackupInfo::updateLastActionTime() { setLastAction(m_time(nullptr)); mModified = true; } void HeartBeatBackupInfo::setLastBeat(const m_time_t &lastBeat) { mLastBeat = lastBeat; mModified = false; } m_time_t HeartBeatBackupInfo::lastBeat() const { return mLastBeat; } void HeartBeatSyncInfo::updateSPHBStatus(UnifiedSync& us) { assert(us.syncs.onSyncThread()); SPHBStatus status = CommandBackupPutHeartBeat::INACTIVE; if (us.mSync) { if (!us.mConfig.mError) { if (us.syncs.isSyncStalled(us.mConfig.mBackupId) || us.mSync->localroot->conflicts != TREE_RESOLVED) { status = CommandBackupPutHeartBeat::STALLED; } else if (!us.mConfig.mFinishedInitialScanning) { // only consider it "scanning" until it first completes scanning. Later scanning (even though we do it) interferes with the % display in Backup Centre status = CommandBackupPutHeartBeat::PENDING; // = scanning } else if (us.mSync->localroot->mightHaveMoves() || us.mSync->localroot->syncRequired()) { status = CommandBackupPutHeartBeat::SYNCING; } else { status = CommandBackupPutHeartBeat::UPTODATE; } } } if (mSPHBStatus != status) { mSPHBStatus = status; updateLastActionTime(); } } BackupInfoSync::BackupInfoSync(const SyncConfig& config, const string& device, handle drive, CommandBackupPut::SPState calculatedState) { backupId = config.mBackupId; type = getSyncType(config); backupName = config.mName, nodeHandle = config.mRemoteNode; localFolder = config.getLocalPath(); state = calculatedState; subState = config.mError; deviceId = device; driveId = drive; } BackupInfoSync::BackupInfoSync(const UnifiedSync &us, bool pauseDown, bool pauseUp) { backupId = us.mConfig.mBackupId; type = getSyncType(us.mConfig); backupName = us.mConfig.mName, nodeHandle = us.mConfig.mRemoteNode; localFolder = us.mConfig.getLocalPath(); state = BackupInfoSync::getSyncState(us, pauseDown, pauseUp); subState = us.mConfig.mError; deviceId = us.syncs.mClient.getDeviceidHash(); driveId = BackupInfoSync::getDriveId(us); assert(!(us.mConfig.isBackup() && us.mConfig.isExternal()) // not an external backup... || !ISUNDEF(driveId)); // ... or it must have a valid drive-id } CommandBackupPut::SPState BackupInfoSync::calculatePauseActiveState(bool pauseDown, bool pauseUp) { if (pauseDown && pauseUp) { return CommandBackupPut::PAUSE_FULL; } else if (pauseDown) { return CommandBackupPut::PAUSE_DOWN; } else if (pauseUp) { return CommandBackupPut::PAUSE_UP; } return CommandBackupPut::ACTIVE; } CommandBackupPut::SPState BackupInfoSync::getSyncState(const UnifiedSync& us, bool pauseDown, bool pauseUp) { return getSyncState(us.mConfig.mError, us.mConfig.mRunState, pauseDown, pauseUp); } CommandBackupPut::SPState BackupInfoSync::getSyncState(SyncError error, SyncRunState s, bool pauseDown, bool pauseUp) { switch (s) { case SyncRunState::Pending: case SyncRunState::Loading: case SyncRunState::Run: return calculatePauseActiveState(pauseDown, pauseUp); case SyncRunState::Pause: return CommandBackupPut::TEMPORARY_DISABLED; case SyncRunState::Suspend: return error > NO_SYNC_ERROR ? CommandBackupPut::FAILED : CommandBackupPut::TEMPORARY_DISABLED; case SyncRunState::Disable: return error > NO_SYNC_ERROR ? CommandBackupPut::FAILED : CommandBackupPut::DISABLED; } return CommandBackupPut::DISABLED; } CommandBackupPut::SPState BackupInfoSync::getSyncState(const SyncConfig& config, bool pauseDown, bool pauseUp) { auto error = config.mError; if (!error) { if (config.getEnabled()) { return calculatePauseActiveState(pauseDown, pauseUp); } else { return CommandBackupPut::DISABLED; } } else //error { if (config.getEnabled()) { return CommandBackupPut::TEMPORARY_DISABLED; } else { return CommandBackupPut::DISABLED; } } } handle BackupInfoSync::getDriveId(const UnifiedSync &us) { const auto& drivePath = us.mConfig.mExternalDrivePath; // Only external drives have drive IDs. if (drivePath.empty()) return UNDEF; // Get our hands on the config store. const auto store = us.syncs.syncConfigStore(); // It should always be available. assert(store); // Drive should be known. assert(store->driveKnown(drivePath)); // Ask the store for the drive's backup ID. auto id = store->driveID(drivePath); // It should never be undefined. assert(id != UNDEF); return id; } BackupType BackupInfoSync::getSyncType(const SyncConfig& config) { switch (config.getType()) { case SyncConfig::TYPE_UP: return BackupType::UP_SYNC; case SyncConfig::TYPE_DOWN: return BackupType::DOWN_SYNC; case SyncConfig::TYPE_TWOWAY: return BackupType::TWO_WAY; case SyncConfig::TYPE_BACKUP: return BackupType::BACKUP_UPLOAD; default: return BackupType::INVALID; } } BackupMonitor::BackupMonitor(Syncs& s) : syncs(s) { } void BackupMonitor::updateOrRegisterSync(UnifiedSync& us) { assert(syncs.onSyncThread()); if (us.mConfig.mSyncDeregisterSent) return; #ifndef NDEBUG handle backupId = us.mConfig.mBackupId; assert(!ISUNDEF(backupId)); // syncs are registered before adding them #endif auto currentInfo = BackupInfoSync(us, syncs.mDownloadsPaused, syncs.mUploadsPaused); if (!us.mBackupInfo || currentInfo != *us.mBackupInfo) { syncs.queueClient( [currentInfo](MegaClient& mc, DBTableTransactionCommitter&) { mc.queueCommand(new CommandBackupPut(&mc, currentInfo, nullptr)); }); } us.mBackupInfo = std::make_unique<BackupInfoSync>(currentInfo); } bool BackupInfoSync::operator==(const BackupInfoSync& o) const { return backupId == o.backupId && driveId == o.driveId && type == o.type && backupName == o.backupName && nodeHandle == o.nodeHandle && localFolder == o.localFolder && deviceId == o.deviceId && state == o.state && subState == o.subState; } bool BackupInfoSync::operator!=(const BackupInfoSync &o) const { return !(*this == o); } void BackupMonitor::beatBackupInfo(UnifiedSync& us) { assert(syncs.onSyncThread()); if (us.mConfig.mSyncDeregisterSent) return; // send registration or update in case we missed it updateOrRegisterSync(us); if (ISUNDEF(us.mConfig.mBackupId)) { LOG_warn << "Backup not registered yet. Skipping heartbeat..."; return; } std::shared_ptr<HeartBeatSyncInfo> hbs = us.mNextHeartbeat; if (us.mSync) { auto counts = us.mSync->threadSafeState->transferCounts(); if (hbs->mSnapshotTransferCounts != counts) { hbs->mSnapshotTransferCounts = counts; hbs->updateLastActionTime(); } } hbs->updateSPHBStatus(us); auto elapsedSec = m_time(nullptr) - hbs->lastBeat(); if ( !hbs->mSending && (elapsedSec >= MAX_HEARBEAT_SECS_DELAY || (elapsedSec*10 >= FREQUENCY_HEARTBEAT_DS && hbs->mModified))) { hbs->setLastBeat(m_time(nullptr)); m_off_t inflightProgress = 0; if (us.mSync) { // to be figured out for sync rework //inflightProgress = us.mSync->getInflightProgress(); } auto reportCounts = hbs->mSnapshotTransferCounts; reportCounts -= hbs->mResolvedTransferCounts; auto progress = reportCounts.progress(inflightProgress); DEBUG_TEST_HOOK_ON_TRANSFER_REPORT_PROGRESS(progress, inflightProgress, reportCounts.pendingTransferBytes()); if (progress > 1.0) { const std::string errMsg = "BackupMonitor::beatBackupInfo: Invalid reportCounts progress value"; LOG_err << errMsg; assert(false && errMsg.c_str()); progress = static_cast<uint8_t>(100.0 * progress); } hbs->mSending = true; auto backupId = us.mConfig.mBackupId; auto status = hbs->sphbStatus(); auto pendingUps = static_cast<uint32_t>(reportCounts.mUploads.mPending); auto pendingDowns = static_cast<uint32_t>(reportCounts.mUploads.mPending); auto lastAction = hbs->lastAction(); auto lastItemUpdated = hbs->lastItemUpdated(); syncs.queueClient( [=](MegaClient& mc, DBTableTransactionCommitter&) { mc.queueCommand(new CommandBackupPutHeartBeat(&mc, backupId, status, static_cast<int8_t>(progress), pendingUps, pendingDowns, lastAction, lastItemUpdated, [hbs](Error) { hbs->mSending = false; })); }); if (progress >= 100) { // once we reach 100%, start counting again from 0 for any later sync activity. hbs->mResolvedTransferCounts = hbs->mSnapshotTransferCounts; // Clean pending values from mResolvedTransferCounts, as values corresponding to pending // transfers (uploads and downloads) are constantly being updated, with larger or // smaller values depending on the current state, and new transfers being added and // other ones finishing, so subtracting any previously saved value is wrong and leading // to an overflow when the saved values are greater. hbs->mResolvedTransferCounts.clearPendingValues(); } } } void BackupMonitor::beat() { assert(syncs.onSyncThread()); lock_guard<std::recursive_mutex> guard(syncs.mSyncVecMutex); // Only send heartbeats for enabled active syncs. for (auto& us : syncs.mSyncVec) { if (us->mSync && us->mConfig.getEnabled()) { beatBackupInfo(*us); } }; } #endif } �����������������������������������������������������������������������������������������sdk-10.11.0/src/http.cpp����������������������������������������������������������������������������0000664�0000000�0000000�00000072134�15162662266�0014730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file http.cpp * @brief Generic host HTTP I/O interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/http.h" #include "mega/megaclient.h" #include "mega/logging.h" #include "mega/proxy.h" #include "mega/base64.h" #include "mega/testhooks.h" #if defined(WIN32) #include <winhttp.h> #endif #if defined(__APPLE__) && !(TARGET_OS_IPHONE) #include "mega/osx/osxutils.h" #endif namespace mega { // data receive timeout (ds) const int HttpIO::NETWORKTIMEOUT = 6000; // request timeout (ds) const int HttpIO::REQUESTTIMEOUT = 1200; // wait request timeout (ds) const int HttpIO::SCREQUESTTIMEOUT = 400; // connect timeout (ds) const int HttpIO::CONNECTTIMEOUT = 120; #ifdef _WIN32 const char* mega_inet_ntop(int af, const void* src, char* dst, int cnt) { wchar_t ip[INET6_ADDRSTRLEN]; int len = INET6_ADDRSTRLEN; int ret = 1; if (af == AF_INET) { struct sockaddr_in in = {}; in.sin_family = AF_INET; memcpy(&in.sin_addr, src, sizeof(struct in_addr)); ret = WSAAddressToString((struct sockaddr*) &in, sizeof(struct sockaddr_in), 0, ip, (LPDWORD)&len); } else if (af == AF_INET6) { struct sockaddr_in6 in = {}; in.sin6_family = AF_INET6; memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); ret = WSAAddressToString((struct sockaddr*) &in, sizeof(struct sockaddr_in6), 0, ip, (LPDWORD)&len); } if (ret != 0) { return NULL; } if (!WideCharToMultiByte(CP_UTF8, 0, ip, len, dst, cnt, NULL, NULL)) { return NULL; } return dst; } #endif HttpIO::HttpIO() { success = false; noinetds = 0; inetback = false; lastdata = NEVER; downloadSpeed = 0; uploadSpeed = 0; lock_guard<mutex> g(g_APIURL_default_mutex); APIURL = g_APIURL_default; disablepkp = g_disablepkp_default; } // signal Internet status - if the Internet was down for more than one minute, // set the inetback flag to trigger a reconnect void HttpIO::inetstatus(bool up) { if (up) { if (noinetds && Waiter::ds - noinetds > 600) { inetback = true; } noinetds = 0; } else if (!noinetds) { noinetds = Waiter::ds; } } // returns true once if an outage just ended bool HttpIO::inetisback() { if(inetback) { inetback = false; return true; } return false; } void HttpIO::updatedownloadspeed(m_off_t size) { downloadSpeed = downloadSpeedController.calculateSpeed(size); } void HttpIO::updateuploadspeed(m_off_t size) { uploadSpeed = uploadSpeedController.calculateSpeed(size); } Proxy *HttpIO::getautoproxy() { Proxy* proxy = new Proxy(); proxy->setProxyType(Proxy::NONE); #if defined(WIN32) WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig = { 0 }; if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig) == TRUE) { if (ieProxyConfig.lpszProxy) { string proxyURL; proxy->setProxyType(Proxy::CUSTOM); int len = static_cast<int>(wcslen(ieProxyConfig.lpszProxy)); proxyURL.assign((const char*)ieProxyConfig.lpszProxy, len * sizeof(wchar_t) + 1); // only save one proxy for (int i = 0; i < len; i++) { wchar_t* character = (wchar_t*)(proxyURL.data() + i * sizeof(wchar_t)); if (*character == ' ' || *character == ';') { proxyURL.resize(i*sizeof(wchar_t)); len = i; break; } } // remove protocol prefix, if any for (int i = len - 1; i >= 0; i--) { wchar_t* character = (wchar_t*)(proxyURL.data() + i * sizeof(wchar_t)); if (*character == '/' || *character == '=') { proxyURL = proxyURL.substr((i + 1) * sizeof(wchar_t)); break; } } proxy->setProxyURL(proxyURL); } else if (ieProxyConfig.lpszAutoConfigUrl || ieProxyConfig.fAutoDetect == TRUE) { WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions; if (ieProxyConfig.lpszAutoConfigUrl) { autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; autoProxyOptions.lpszAutoConfigUrl = ieProxyConfig.lpszAutoConfigUrl; autoProxyOptions.dwAutoDetectFlags = 0; } else { autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; autoProxyOptions.lpszAutoConfigUrl = NULL; autoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; } autoProxyOptions.fAutoLogonIfChallenged = TRUE; autoProxyOptions.lpvReserved = NULL; autoProxyOptions.dwReserved = 0; WINHTTP_PROXY_INFO proxyInfo; HINTERNET hSession = WinHttpOpen(L"MEGAsync proxy detection", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); if (WinHttpGetProxyForUrl(hSession, L"https://g.api.mega.co.nz/", &autoProxyOptions, &proxyInfo)) { if (proxyInfo.lpszProxy) { string proxyURL; proxy->setProxyType(Proxy::CUSTOM); proxyURL.assign((const char*)proxyInfo.lpszProxy, wcslen(proxyInfo.lpszProxy) * sizeof(wchar_t)); proxy->setProxyURL(proxyURL); } } WinHttpCloseHandle(hSession); } } if (ieProxyConfig.lpszProxy) { GlobalFree(ieProxyConfig.lpszProxy); } if (ieProxyConfig.lpszProxyBypass) { GlobalFree(ieProxyConfig.lpszProxyBypass); } if (ieProxyConfig.lpszAutoConfigUrl) { GlobalFree(ieProxyConfig.lpszAutoConfigUrl); } #elif defined(__APPLE__) && !(TARGET_OS_IPHONE) getOSXproxy(proxy); #elif !defined(__APPLE__) && !defined(__ANDROID__) getEnvProxy(proxy); #endif return proxy; } bool HttpIO::setmaxdownloadspeed(m_off_t) { return false; } bool HttpIO::setmaxuploadspeed(m_off_t) { return false; } m_off_t HttpIO::getmaxdownloadspeed() { return 0; } m_off_t HttpIO::getmaxuploadspeed() { return 0; } void HttpIO::setproxy(const Proxy&) {} std::optional<Proxy> HttpIO::getproxy() const { return std::nullopt; } void HttpReq::prepareMethod(HttpIO* clientHttpIo, const httpmethod_t reqMethod) { if (httpio) { LOG_warn << logname << "Ensuring that the request is finished before sending it again"; httpio->cancel(this); init(); } httpio = clientHttpIo; bufpos = 0; outpos = 0; notifiedbufpos = 0; inpurge = 0; method = reqMethod; contentlength = -1; lastdata = Waiter::ds; } void HttpReq::post(MegaClient* client, const char* data, unsigned len) { prepareMethod(client->httpio, METHOD_POST); DEBUG_TEST_HOOK_HTTPREQ_POST(this) httpio->post(this, data, len); } void HttpReq::get(MegaClient *client) { prepareMethod(client->httpio, METHOD_GET); httpio->post(this); } void HttpReq::dns(MegaClient *client) { prepareMethod(client->httpio, METHOD_NONE); httpio->post(this); } void HttpReq::disconnect() { if (httpio) { httpio->cancel(this); httpio = NULL; init(); } } std::atomic_uint32_t HttpReq::nextReqId{0u}; HttpReq::HttpReq(bool b): reqId{nextReqId++}, logname{"(Req#" + std::to_string(reqId) + ") "} { LOG_verbose << logname << "[HttpReq::HttpReq] CONSTRUCTOR CALL [this = " << this << "]"; binary = b; status = REQ_READY; buf = NULL; httpio = NULL; httpiohandle = NULL; out = &outbuf; method = METHOD_NONE; timeoutms = 0; type = REQ_JSON; buflen = 0; protect = false; minspeed = false; mChunked = false; init(); } HttpReq::~HttpReq() { LOG_verbose << logname << "[HttpReq::~HttpReq] DESTRUCTOR CALL [this = " << this << "]"; if (httpio) { httpio->cancel(this); } delete[] buf; } void HttpReq::init() { httpstatus = 0; mErrCode = 0; inpurge = 0; sslcheckfailed = false; bufpos = 0; notifiedbufpos = 0; contentlength = 0; timeleft = -1; lastdata = NEVER; outpos = 0; in.clear(); contenttype.clear(); mRedirectURL.clear(); } const char* HttpReq::getMethodString() { switch(method) { case METHOD_POST: return "POST"; case METHOD_GET: return "GET"; case METHOD_NONE: return "NONE"; default: return "UNKNOWN_METHOD"; } } void HttpReq::setreq(const char* u, contenttype_t t) { if (u) { posturl = u; } type = t; } // add data to fixed or variable buffer void HttpReq::put(void* data, unsigned len, bool purge) { if (buf) { if (bufpos + len > buflen) { len = static_cast<unsigned>(buflen - bufpos); } memcpy(buf + bufpos, data, len); } else { if (inpurge && purge) { in.erase(0, inpurge); inpurge = 0; } in.append((char*)data, len); } bufpos += len; } HttpReq::http_buf_t::http_buf_t(byte* b, size_t s, size_t e) : start(s), end(e), buf(b) { } HttpReq::http_buf_t::~http_buf_t() { delete[] buf; } void HttpReq::http_buf_t::swap(http_buf_t& other) { byte* tb = buf; buf = other.buf; other.buf = tb; size_t ts = start; start = other.start; other.start = ts; size_t te = end; end = other.end; other.end = te; } bool HttpReq::http_buf_t::isNull() const { return buf == NULL; } byte* HttpReq::http_buf_t::datastart() const { return buf + start; } size_t HttpReq::http_buf_t::datalen() const { return end - start; } // give up ownership of the buffer for client to use. struct HttpReq::http_buf_t* HttpReq::release_buf() { HttpReq::http_buf_t* result = new HttpReq::http_buf_t(buf, inpurge, (size_t)bufpos); buf = NULL; inpurge = 0; buflen = 0; bufpos = 0; outpos = 0; notifiedbufpos = 0; contentlength = -1; in.clear(); return result; } char* HttpReq::data() { return (char*)in.data() + inpurge; } size_t HttpReq::size() { return in.size() - inpurge; } // set amount of purgeable in data at 0 void HttpReq::purge(size_t numbytes) { inpurge += numbytes; if (mChunked) { // Immediate purge because there are several places // in the code directly accesing HttpReq::in instead // of HttpReq::data() and HttpReq::size() in.erase(0, inpurge); inpurge = 0; } } // set total response size void HttpReq::setcontentlength(m_off_t len) { if (!buf && type != REQ_BINARY && !mChunked) { in.reserve(static_cast<size_t>(len)); } contentlength = len; } // number of bytes transferred in this request m_off_t HttpReq::transferred(MegaClient*) { if (buf) { return bufpos; } else { return static_cast<m_off_t>(in.size()); } } HttpReqDL::HttpReqDL() : dlpos(0) , buffer_released(false) { } // prepare file chunk download void HttpReqDL::prepare(const char* tempurl, SymmCipher* /*key*/, uint64_t /*ctriv*/, m_off_t downloadPosition, m_off_t npos) { if (tempurl && *tempurl) { char urlbuf[512]; snprintf(urlbuf, sizeof urlbuf, "%s/%" PRIu64 "-%" PRIu64, tempurl, downloadPosition, npos ? npos - 1 : 0); setreq(urlbuf, REQ_BINARY); } else { setreq(nullptr, REQ_BINARY); } dlpos = downloadPosition; size = (unsigned)(npos - downloadPosition); buffer_released = false; if (!buf || buflen != size) { // (re)allocate buffer if (buf) { delete[] buf; buf = NULL; } if (size) { buf = new byte[(size + SymmCipher::BLOCKSIZE - 1) & ~(static_cast<size_t>(SymmCipher::BLOCKSIZE) - 1)]; } buflen = size; } } EncryptByChunks::EncryptByChunks(SymmCipher* k, chunkmac_map* m, uint64_t iv) : key(k), macs(m), ctriv(iv) { memset(crc, 0, CRCSIZE); } void EncryptByChunks::updateCRC(byte* data, unsigned size, unsigned offset) { uint32_t *intc = (uint32_t *)crc; unsigned ol = offset % CRCSIZE; if (ol) { unsigned ll = CRCSIZE - ol; if (ll > size) //last chunks could be smaller than CRCSIZE! { ll = size; } size -= ll; while (ll--) { crc[ol++] ^= *data; ++data; } } uint32_t *intdata = (uint32_t *)data; int ll = size % CRCSIZE; int l = size / CRCSIZE; if (l) { l *= 3; while (l) { l -= 3; intc[0] ^= intdata[l]; intc[1] ^= intdata[l + 1]; intc[2] ^= intdata[l + 2]; } } if (ll) { data += (size - static_cast<size_t>(ll)); while (ll--) { crc[ll] ^= data[ll]; } } } bool EncryptByChunks::encrypt(m_off_t pos, m_off_t npos, string& urlSuffix) { byte* buf; m_off_t startpos = pos; m_off_t finalpos = npos; m_off_t endpos = ChunkedHash::chunkceil(startpos, finalpos); m_off_t chunksize = endpos - startpos; while (chunksize) { buf = nextbuffer(unsigned(chunksize)); if (!buf) return false; // The chunk is fully encrypted but finished==false for now, // we only set finished after confirmation of the chunk uploading. macs->ctr_encrypt(startpos, key, buf, unsigned(chunksize), startpos, static_cast<int64_t>(ctriv), false); LOG_debug << "Encrypted chunk: " << startpos << " - " << endpos << " Size: " << chunksize; updateCRC(buf, unsigned(chunksize), unsigned(startpos - pos)); startpos = endpos; endpos = ChunkedHash::chunkceil(startpos, finalpos); chunksize = endpos - startpos; } assert(endpos == finalpos); buf = nextbuffer(0); // last call in case caller does buffer post-processing (such as write to file as we go) ostringstream s; s << "/" << pos << "?d=" << Base64Str<EncryptByChunks::CRCSIZE>(crc); urlSuffix = s.str(); return !!buf; } EncryptBufferByChunks::EncryptBufferByChunks(byte* b, SymmCipher* k, chunkmac_map* m, uint64_t iv) : EncryptByChunks(k, m, iv) , chunkstart(b) { } byte* EncryptBufferByChunks::nextbuffer(unsigned bufsize) { byte* pos = chunkstart; chunkstart += bufsize; return pos; } // prepare chunk for uploading: mac and encrypt void HttpReqUL::prepare(const char* tempurl, SymmCipher* key, uint64_t ctriv, m_off_t uploadPosition, m_off_t npos) { EncryptBufferByChunks eb((byte*)out->data(), key, &mChunkmacs, ctriv); string urlSuffix; eb.encrypt(uploadPosition, npos, urlSuffix); // unpad for POSTing size = (unsigned)(npos - uploadPosition); out->resize(size); setreq((tempurl + urlSuffix).c_str(), REQ_BINARY); } // number of bytes sent in this request m_off_t HttpReqUL::transferred(MegaClient* client) { if (httpiohandle) { return client->httpio->postpos(httpiohandle); } return 0; } GenericHttpReq::GenericHttpReq(PrnGen &rng, bool binary) : HttpReq(binary), bt(rng), maxbt(rng) { tag = 0; maxretries = 0; numretry = 0; isbtactive = false; } /********************\ * SpeedController * \********************/ SpeedController::SpeedController() { requestStarted(); } void SpeedController::requestStarted() { dstime currentTime = Waiter::ds; mRequestPos = 0; mRequestStart = mLastRequestUpdate = currentTime; // Increment the initial time by the time since the last circular update // (almost equivalent to mLastRequestUpdate). This ensures an accurate total // mean calculation, including previous data for the same connection. mInitialTime += mCircularCurrentTime ? (currentTime - mCircularCurrentTime) : currentTime; mCircularCurrentTime = currentTime; } m_off_t SpeedController::requestProgressed(m_off_t newPos) { if (newPos > mRequestPos) { m_off_t delta = newPos - mRequestPos; calculateSpeed(delta); mRequestPos = newPos; mLastRequestUpdate = Waiter::ds; return delta; } return 0; } m_off_t SpeedController::lastRequestMeanSpeed() const { // If deltaDs is 0 we consider it as 1, it won't be really accurate (the mean value will be lower than it should), but better than returning a 0 // For example, mRequestPos = 50 bytes; deltaDs = 0 [real value = 0.5]. 50 bytes per 0.5 ds = 100 bytes per decisecond = 1000 bytes per second // However, as we deltaDs is an integer value truncated to 0, we would have: 50 bytes * 10 decisecondsPerSecond / 1 = 500 bytes per second. // Lower than it should be, but better than just returning a 0. dstime deltaDs = std::max<dstime>(1, mLastRequestUpdate - mRequestStart); return aggregateProgressForTimePeriod(DS_PER_SECOND, deltaDs, mRequestPos); } dstime SpeedController::requestElapsedDs() const { return Waiter::ds - mRequestStart; } m_off_t SpeedController::getMeanSpeed() const { return mMeanSpeed; } // Get the current circular speed by aggregating progress (from deciseconds to seconds) over the circular time period (SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS). m_off_t SpeedController::getCircularMeanSpeed() const { assert(mCircularCurrentTime >= mInitialTime); if (mCircularCurrentSum == 0) { // Return zero speed if circular buffer is empty. return 0; } dstime deltaTimeFromBeginning = std::max<dstime>(1, mCircularCurrentTime - mInitialTime); // See comment in "calculateMeanSpeed()" to understand why we do this. dstime totalSumTime = deltaTimeFromBeginning >= ((SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS - 1) * DS_PER_SECOND) ? (((SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS - 1) * DS_PER_SECOND) + calculateCurrentSecondOffsetInDs()) : // We always have a "current/incomplete second" deltaTimeFromBeginning; assert(totalSumTime > 0); return aggregateProgressForTimePeriod(DS_PER_SECOND, totalSumTime, mCircularCurrentSum); } // Calculate the total mean speed by aggregating progress (from deciseconds to seconds) over the total time period. m_off_t SpeedController::calculateMeanSpeed() { // Same comment than in lastRequestMeanSpeed(). // If deltaDs is 0 we consider it as 1, it won't be really accurate (the mean value will be lower than it should), but better than returning a 0 // For example, mRequestPos = 50 bytes; deltaDs = 0 [real value = 0.5]. 50 bytes per 0.5 ds = 100 bytes per decisecond = 1000 bytes per second // However, as we deltaDs is an integer value truncated to 0, we would have: 50 bytes * 10 decisecondsPerSecond / 1 = 500 bytes per second. // Lower than it should be, but better than just returning a 0. assert(mInitialTime > 0); dstime deltaTimeFromBeginning = std::max<dstime>(1, Waiter::ds - mInitialTime); return aggregateProgressForTimePeriod(DS_PER_SECOND, deltaTimeFromBeginning, mTotalSumBytes); } // Calculate the circular mean speed by aggregating progress (from deciseconds to seconds) over the circular time period m_off_t SpeedController::calculateSpeed(m_off_t delta) { dstime currentTime = Waiter::ds; if (mInitialTime == 0 || mCircularCurrentTime == 0) { // Waiter::ds wasn't initialized when SpeedController was constructed. if (currentTime == 0) { LOG_err << "[SpeedController::calculateSpeed] Waiter::ds is not initialized yet!!!! We cannot calculate anything!!! And we will lose this delta!!!!"; assert(false && "Waiter::ds is not initialized yet, and it is needed for speed calculation"); return 0; } requestStarted(); } if (delta < 0) { // If delta is negative it is due to retries, failures or reconnections, so part of the requests had to start again from an earlier position // In this case, we can count this delta as a "zero", even if it will decrease the mean speed. We cannot really know which amount of the new progress value // (smaller than before, hence the negative delta) belongs to new transferred data, and how much was "lost" due to the retry. // So it's fine to assume it even if it will decrease the circular mean speed during some seconds. // The total mean speed (getMeanSpeed) will remain practically unaffected, as we will keep updating it with positive delta values right after the next call to calculateSpeed() LOG_warn << "[SpeedController::calculateSpeed] delta (" << delta << ") is smaller than 0 -> truncating it to 0"; delta = 0; } dstime deltaTimeFromPreviousCall = currentTime - mCircularCurrentTime; assert((currentTime == mCircularCurrentTime) || (deltaTimeFromPreviousCall > 0)); if (deltaTimeFromPreviousCall > 0) { // Check if the time difference from the previous call, converted to seconds, is within the allowed buffer size. if ((deltaTimeFromPreviousCall / DS_PER_SECOND) <= SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS) { updateCircularBufferWithinLimit(delta, deltaTimeFromPreviousCall); } else { updateCircularBufferWithWeightedAverageForDeltaExceedingLimit(delta, deltaTimeFromPreviousCall); } } else { // We are within the current decisecond, i.e, same decisecond than the one from last call. mCircularBuf[mCircularCurrentIndex] += delta; } // Update circular buffer and total sum used for the mean speed mCircularCurrentSum += delta; mTotalSumBytes += delta; mMeanSpeed = calculateMeanSpeed(); assert(mCircularCurrentSum >= 0); assert(mMeanSpeed >= 0); return getCircularMeanSpeed(); } // Calculate speed within circular buffer size limit void SpeedController::updateCircularBufferWithinLimit(m_off_t delta, dstime deltaTimeFromPreviousCall) { // Calculate the current second's offset in deciseconds dstime circularCurrentTimeOffset = calculateCurrentSecondOffsetInDs(); // Calculate remaining deciseconds to complete the current second dstime circularCurrentTimeRemainingOffsetToSecond = (DS_PER_SECOND - circularCurrentTimeOffset) % DS_PER_SECOND; // Calculate the current delta offset to update for the current incomplete second // If deltaTimeFromPreviousCall is greater than circularCurrentTimeRemainingOffsetToSecond, then we will truncate the offset to that limit dstime currentSecondDeltaOffset = std::min<dstime>(deltaTimeFromPreviousCall, circularCurrentTimeRemainingOffsetToSecond); // Update circular buffer for the current incomplete second with the calculated value above mCircularBuf[mCircularCurrentIndex] += aggregateProgressForTimePeriod(currentSecondDeltaOffset, deltaTimeFromPreviousCall, delta); // Now we can update the circular current time mCircularCurrentTime = Waiter::ds; if ((deltaTimeFromPreviousCall - currentSecondDeltaOffset) > 0) { // Update circular buffer for each full second in deltaTimeFromPreviousCall dstime numSeconds = (deltaTimeFromPreviousCall - circularCurrentTimeRemainingOffsetToSecond) / DS_PER_SECOND; for (dstime i = numSeconds; i--; ) { nextIndex(mCircularCurrentIndex); mCircularCurrentSum -= mCircularBuf[mCircularCurrentIndex]; mCircularBuf[mCircularCurrentIndex] = aggregateProgressForTimePeriod(DS_PER_SECOND, deltaTimeFromPreviousCall, delta); } // Update circular buffer for the new current incomplete second nextIndex(mCircularCurrentIndex); mCircularCurrentSum -= mCircularBuf[mCircularCurrentIndex]; mCircularBuf[mCircularCurrentIndex] = aggregateProgressForTimePeriod(calculateCurrentSecondOffsetInDs(), deltaTimeFromPreviousCall, delta); } } /* * Calculates the weighted average per second when delta exceeds circular buffer size: * If the time difference from the previous call exceeds the circular buffer size, * calculate a weighted average (per second) between the delta and deltaTimeFromPreviousCall, * and update each position of the circular buffer (each one corresponds to a second) with this value. * Note: The position for the current incomplete second should be filled weighted to the current offset in deciseconds. */ void SpeedController::updateCircularBufferWithWeightedAverageForDeltaExceedingLimit(m_off_t& delta, dstime deltaTimeFromPreviousCall) { // Calculate aggregated delta value per second m_off_t aggregatedDeltaValuePerSecond = aggregateProgressForTimePeriod(DS_PER_SECOND, deltaTimeFromPreviousCall, delta); // Fill circular buffer with aggregatedDeltaValuePerSecond std::fill(mCircularBuf.begin(), mCircularBuf.end(), aggregatedDeltaValuePerSecond); // Calculate the number of index positions to advance in the circular buffer auto deltaIndexPositions = deltaTimeFromPreviousCall / DS_PER_SECOND; nextIndex(mCircularCurrentIndex, static_cast<size_t>(deltaIndexPositions)); // Exclude the actual second from the delta calculation delta = (aggregatedDeltaValuePerSecond * (SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS - 1)); // Update circular buffer for the incomplete second if present mCircularCurrentTime = Waiter::ds; dstime circularCurrentTimeOffset = calculateCurrentSecondOffsetInDs(); if (circularCurrentTimeOffset) { // Calculate the ponderated average progress for the current second: // - Time to aggregate: circularCurrentTimeOffset (the decisecond offset for the current second) // - Total time: DS_PER_SECOND (10 deciseconds or 1 second, corresponding to aggregatedDeltaValuePerSecond) // - Bytes to aggregate: aggregatedDeltaValuePerSecond // - Result: the ponderated average progress for the decisecond time within the current second // Example: If aggregatedDeltaValuePerSecond = 100 KB and circularCurrentTimeOffset = 5 deciseconds, // then ponderatedProgressForCurrentSecond = (5 deciseconds * 100 KB / 10 deciseconds) = 50 KB auto ponderatedProgressForCurrentSecond = aggregateProgressForTimePeriod(circularCurrentTimeOffset, DS_PER_SECOND, aggregatedDeltaValuePerSecond); mCircularBuf[mCircularCurrentIndex] = ponderatedProgressForCurrentSecond; delta += ponderatedProgressForCurrentSecond; } else { mCircularBuf[mCircularCurrentIndex] = 0; // Current second (with no offset) starts from 0 } mCircularCurrentSum = 0; // Reset the current sum (it must be updated with the current delta value) after calling this method } // Calculate offset in deciseconds for the current second starting from the initial time dstime SpeedController::calculateCurrentSecondOffsetInDs() const { return (mCircularCurrentTime - mInitialTime) % DS_PER_SECOND; } void SpeedController::nextIndex(size_t ¤tCircularBufIndex, size_t positionsToAdvance) const { currentCircularBufIndex = (currentCircularBufIndex + positionsToAdvance) % SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS; } /* * Aggregate instantaneous delta values over a specified time subperiod to calculate a weighted average. * * Calculates the total progress over the given time period by aggregating * the provided delta values, considering the total time and bytes to aggregate. * * @param timePeriodToAggregate The duration of the time subperiod in deciseconds. * @param totalTime The total duration of the time period in deciseconds. * @param bytesToAggregate The delta value to aggregate over the time period. * @return The aggregated progress over the specified time period. * * Example: * If 200 bytes correspond to 20 deciseconds (2 seconds) and are aggregated * over a period of 10 deciseconds (1 second), the calculation would be: * (10 * 200) / 20 = 100 bytes per second. */ m_off_t SpeedController::aggregateProgressForTimePeriod(dstime timePeriodToAggregate, dstime totalTime, m_off_t bytesToAggregate) const { if (timePeriodToAggregate <= 0 || totalTime <= 0) { return 0; } return (timePeriodToAggregate * bytesToAggregate) / totalTime; } } // namespace ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/impl/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014177�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/impl/share.cpp����������������������������������������������������������������������0000664�0000000�0000000�00000010320�15162662266�0016001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "impl/share.h" namespace mega { namespace impl { ShareData::ShareData(MegaHandle nodeHandle, const Share* share, bool verified): mNodeHandle(nodeHandle), mShare(share), mVerified(verified) {} MegaHandle ShareData::getNodeHandle() const { return mNodeHandle; } const Share* ShareData::getShare() const { return mShare; } bool ShareData::isVerified() const { return mVerified; } m_time_t ShareData::creationTime() const { return mShare->ts; } vector<ShareData> ShareExtractor::extractOutShares(const Node* n, const KeyManager& keyManager, Filter filter) { vector<ShareData> shares; if (n->outshares) { for (const auto& outShare: *n->outshares) { const Share* share = outShare.second.get(); assert(!share->pcr); if (share->user) // public links have no user { const bool verified = !keyManager.isUnverifiedOutShare(n->nodehandle, toHandle(share->user->userhandle)); ShareData data{n->nodehandle, share, verified}; if (!filter || filter(data)) // no filter or filter() returns true { shares.push_back(std::move(data)); } } } } return shares; } vector<ShareData> ShareExtractor::extractPendingOutShares(const Node* n, const KeyManager& keyManager, Filter filter) { vector<ShareData> shares; if (n->pendingshares) { for (const auto& pendingShare: *n->pendingshares) { const Share* share = pendingShare.second.get(); if (share->pcr) { const bool verified = !keyManager.isUnverifiedOutShare(n->nodehandle, share->pcr->targetemail); ShareData data{n->nodehandle, share, verified}; if (!filter || filter(data)) // no filter or filter() returns true { shares.push_back(std::move(data)); } } } } return shares; } vector<ShareData> ShareExtractor::extractOutShares(const sharedNode_vector& sharedNodes, const KeyManager& keyManager, Filter filter) { vector<ShareData> shares; auto outputIt = std::back_inserter(shares); for (const auto& n: sharedNodes) { auto outShares = extractOutShares(n.get(), keyManager, filter); std::move(outShares.begin(), outShares.end(), outputIt); auto pendingShares = extractPendingOutShares(n.get(), keyManager, filter); std::move(pendingShares.begin(), pendingShares.end(), outputIt); } return shares; } vector<ShareData> ShareExtractor::extractPendingOutShares(const sharedNode_vector& sharedNodes, const KeyManager& keyManager) { vector<ShareData> shares; auto outputIt = std::back_inserter(shares); for (const auto& n: sharedNodes) { auto pendingShares = extractPendingOutShares(n.get(), keyManager, nullptr); std::move(pendingShares.begin(), pendingShares.end(), outputIt); } return shares; } void ShareSorter::sort(std::vector<ShareData>& shares, int order) { if (auto comp = getComparator(order); comp) { return std::sort(std::begin(shares), std::end(shares), comp); } } ShareSorter::CompFunc ShareSorter::getComparator(int order) { switch (order) { case MegaApi::ORDER_SHARE_CREATION_ASC: return [](const ShareData& a, const ShareData& b) { return a.creationTime() < b.creationTime(); }; case MegaApi::ORDER_SHARE_CREATION_DESC: return [](const ShareData& a, const ShareData& b) { return a.creationTime() > b.creationTime(); }; default: return {}; } } } // namespace impl } // namespace mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/json.cpp����������������������������������������������������������������������������0000664�0000000�0000000�00000106774�15162662266�0014732�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file json.cpp * @brief Linear non-strict JSON scanner * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/json.h" #include "mega/base64.h" #include "mega/logging.h" #include "mega/megaclient.h" #include <utf8proc/utf8proc.h> #include <cctype> #include <cstdint> #ifdef _WIN32 #include <locale.h> #elif defined(__APPLE__) #include <xlocale.h> #else #include <locale.h> #endif namespace mega { // store array or object in string s // reposition after object bool JSON::storeobject(string* s) { int openobject[2] = { 0 }; const char* ptr; bool escaped = false; while (*(const signed char*)pos > 0 && *pos <= ' ') { pos++; } if (*pos == ']' || *pos == '}') { return false; } if (*pos == ',') { pos++; } ptr = pos; for (;;) { if ((*ptr == '[') || (*ptr == '{')) { openobject[*ptr == '[']++; } else if ((*ptr == ']') || (*ptr == '}')) { openobject[*ptr == ']']--; if(openobject[*ptr == ']'] < 0) { LOG_err << "Parse error (])"; } } else if (*ptr == '"') { ptr++; while (*ptr && (escaped || *ptr != '"')) { escaped = *ptr == '\\' && !escaped; ptr++; } if (!*ptr) { LOG_err << "Parse error (\")"; return false; } } else if ((*ptr >= '0' && *ptr <= '9') || *ptr == '-' || *ptr == '.') { ptr++; while ((*ptr >= '0' && *ptr <= '9') || *ptr == '.' || *ptr == 'e' || *ptr == 'E') { ptr++; } ptr--; } else if (*ptr != ':' && *ptr != ',') { LOG_err << "Parse error (unexpected " << *ptr << ")"; return false; } ptr++; if (!openobject[0] && !openobject[1]) { if (s) { if (*pos == '"') { s->assign(pos + 1, static_cast<size_t>(ptr - pos - 2)); } else { s->assign(pos, static_cast<size_t>(ptr - pos)); } } pos = ptr; return true; } } } bool JSON::storeKeyValueFromObject(string& key, string& value) { // this one can be used when the key is not a nameid if (!storeobject(&key) || *pos != ':') { return false; } ++pos; return storeobject(&value); } bool JSON::skipnullvalue() { // this applies only to values, after ':' if (!pos) return false; switch (*pos) { case ',': // empty value, i.e. "foo":, ++pos; // fall through case ']': // empty value, i.e. "foo":] case'}': // empty value, i.e. "foo":} return true; default: // some other value, don't skip it return false; case 'n': if (strncmp(pos, "null", 4)) return false; // not enough information to skip it assert(false); // the MEGA servers should never send null. Investigation needed. // let's peak at what's after "null" switch (*(pos + 4)) { case ',': // null value, i.e. "foo":null, ++pos; // fall through case ']': // null value, i.e. "foo":null] case '}': // null value, i.e. "foo":null} pos += 4; return true; default: // some other value, don't skip it return false; } } } bool JSON::isnumeric() { if (*pos == ',') { pos++; } const char* ptr = pos; if (*ptr == '-') { ptr++; } return *ptr >= '0' && *ptr <= '9'; } nameid JSON::getnameid(const char* ptr) const { nameid id = EOO; while (*ptr && *ptr != '"') { id = (id << 8) + static_cast<nameid>(*ptr++); } return id; } nameid JSON::getnameid() { return getNameidSkipNull(true); } nameid JSON::getnameidvalue() { return getNameidSkipNull(false); } std::string JSON::getname() { const char* ptr = pos; string name; if (*ptr == ',' || *ptr == ':') { ptr++; } if (*ptr++ == '"') { while (*ptr && *ptr != '"') { name += *ptr; ptr++; } pos = ptr + 2; } return name; } std::string JSON::getnameWithoutAdvance() const { const char* ptr = pos; string name; if (*ptr == ',' || *ptr == ':') { ptr++; } if (*ptr++ == '"') { while (*ptr && *ptr != '"') { name += *ptr; ptr++; } } return name; } // pos points to [,]"name":... // returns nameid and repositons pos after : // no unescaping supported nameid JSON::getNameidSkipNull(bool skipnullvalues) { const char* ptr = pos; nameid id = 0; if (*ptr == ',' || *ptr == ':') { ptr++; } if (*ptr++ == '"') { while (*ptr && *ptr != '"') { id = (id << 8) + static_cast<nameid>(*ptr++); } assert(*ptr == '"'); // if either assert fails, check the json syntax, it might be something new/changed pos = ptr + 1; if (*pos == ':' || *pos == ',' ) { pos++; } else { // don't skip the char if we're at the end of a structure eg. actionpacket with only {"a":"xyz"} assert(*pos == '}' || *pos == ']'); } } bool skippedNull = id && skipnullvalues && skipnullvalue(); return skippedNull ? getnameid() : id; } // specific string comparison/skipping bool JSON::is(const char* value) { if (*pos == ',') { pos++; } if (*pos != '"') { return false; } size_t t = strlen(value); if (memcmp(pos + 1, value, t) || pos[t + 1] != '"') { return false; } pos += t + 2; return true; } // base64-decode binary value to designated fixed-length buffer int JSON::storebinary(byte* dst, int dstlen) { int l = 0; if (*pos == ',') { pos++; } if (*pos == '"') { l = Base64::atob(pos + 1, dst, dstlen); // skip string storeobject(); } return l; } // base64-decode binary value to designated string bool JSON::storebinary(string* dst) { if (*pos == ',') { pos++; } if (*pos == '"') { const char* ptr; ptr = strchr(pos + 1, '"'); if (!ptr) { LOG_err << "Parse error (storebinary)"; return false; } dst->resize(static_cast<size_t>((ptr - pos - 1) / 4 * 3 + 3)); dst->resize( static_cast<size_t>(Base64::atob(pos + 1, (byte*)dst->data(), int(dst->size())))); // skip string storeobject(); } return true; } // test for specific handle type bool JSON::ishandle(int size) { size = (size == 6) ? 8 : 11; if (*pos == ',') { pos++; } if (*pos == '"') { int i; // test for short string for (i = 0; i <= size; i++) { if (!pos[i]) { return false; } } return pos[i] == '"'; } return false; } // decode handle handle JSON::gethandle(int size) { byte buf[9] = { 0 }; // no arithmetic or semantic comparisons will be performed on handles, so // no endianness issues if (storebinary(buf, sizeof buf) == size) { return MemAccess::get<handle>((const char*)buf); } return UNDEF; } NodeHandle JSON::getNodeHandle() { return NodeHandle().set6byte(gethandle(6)); } // decode integer m_off_t JSON::getint() { const char* ptr; if (*pos == ':' || *pos == ',') { pos++; } ptr = pos; if (*ptr == '"') { ptr++; } if ((*ptr < '0' || *ptr > '9') && *ptr != '-') { LOG_err << "Parse error (getint)"; // An event about failing to parse the json cannot be sent because no MegaClient instance is // accessible from here. assert(false && "JSON::getint(): Unexpected value in JSON"); // It should probably return something less common in this case, like // std::numeric_limits<m_off_t>::min(). return -1; } handle r = static_cast<handle>(atoll(ptr)); storeobject(); return static_cast<m_off_t>(r); } // decode float // Ignore system locale to parse floats, otherwise // it may fail expecting "," as decimal separator instead of ".". // Note: std::from_chars is not supported yet for floats in all compilers. double JSON::getfloat() { if (*pos == ':' || *pos == ',') { pos++; } const char* ptr = pos; if (*ptr == '"') { ptr++; } if ((*ptr < '0' || *ptr > '9') && *ptr != '-' && *ptr != '.') { LOG_err << "Parse error (getfloat)"; assert(false && "JSON::getfloat(): No float value in JSON"); return -1; } double r{0.0}; char* endptr = nullptr; #ifdef _WIN32 static const _locale_t cLocale = _create_locale(LC_NUMERIC, "C"); r = cLocale ? _strtod_l(ptr, &endptr, cLocale) : strtod(ptr, &endptr); #else static const locale_t cLocale = newlocale(LC_NUMERIC_MASK, "C", nullptr); r = cLocale ? strtod_l(ptr, &endptr, cLocale) : strtod(ptr, &endptr); #endif if (endptr == ptr) { LOG_err << "Conversion error (getfloat)"; assert(false && "JSON::getfloat(): Unexpected value in JSON"); return r; } storeobject(); return r; } // return pointer to JSON payload data const char* JSON::getvalue() { const char* r; if (*pos == ':' || *pos == ',') { pos++; } if (*pos == '"') { r = pos + 1; } else { r = pos; } storeobject(); return r; } std::uint64_t JSON::getfsfp() { return gethandle(sizeof(std::uint64_t)); } uint64_t JSON::getuint64() { const char* ptr; if (*pos == ':' || *pos == ',') { pos++; } ptr = pos; if (*ptr == '"') { ptr++; } if (!is_digit(static_cast<unsigned>(*ptr))) { LOG_err << "Parse error (getuint64)"; return std::numeric_limits<uint64_t>::max(); } uint64_t r = strtoull(ptr, nullptr, 0); storeobject(); return r; } // try to to enter array bool JSON::enterarray() { if (*pos == ',' || *pos == ':') { pos++; } if (*pos == '[') { pos++; return true; } return false; } // leave array (must be at end of array) bool JSON::leavearray() { if (*pos == ']') { pos++; return true; } LOG_err << "Parse error (leavearray)"; return false; } // try to enter object bool JSON::enterobject() { if (*pos == '}') { pos++; } if (*pos == ',') { pos++; } if (*pos == '{') { pos++; return true; } return false; } // leave object (skip remainder) bool JSON::leaveobject() { for (; ;) { if (*pos == ':' || *pos == ',' || *pos == ' ') { pos++; } else if (*pos == '"' || (*pos >= '0' && *pos <= '9') || *pos == '-' || *pos == '[' || *pos == '{') { storeobject(); } else if(*pos == ']') { LOG_err << "Parse error (unexpected ']' character)"; pos++; } else { break; } } if (*pos == '}') { pos++; return true; } LOG_err << "Parse error (leaveobject)"; return false; } // unescape JSON string (non-strict) void JSON::unescape(string* s) { char c; int l; for (unsigned i = 0; i + 1 < s->size(); i++) { if ((*s)[i] == '\\') { switch ((*s)[i + 1]) { case 'n': c = '\n'; l = 2; break; case 'r': c = '\r'; l = 2; break; case 'b': c = '\b'; l = 2; break; case 'f': c = '\f'; l = 2; break; case 't': c = '\t'; l = 2; break; case '\\': c = '\\'; l = 2; break; case 'u': c = static_cast<char>((hexval((*s)[i + 4]) << 4) | hexval((*s)[i + 5])); l = 6; break; default: c = (*s)[i + 1]; l = 2; } s->replace(i, static_cast<size_t>(l), &c, 1); } } } bool JSON::extractstringvalue(const string &json, const string &name, string *value) { string pattern = name + "\":\""; size_t pos = json.find(pattern); if (pos == string::npos) { return false; } size_t end = json.find("\"", pos + pattern.length()); if (end == string::npos) { return false; } *value = json.substr(pos + pattern.size(), end - pos - pattern.size()); return true; } bool JSON::isNumericError(error &e) { const char* ptr = pos; if (*ptr == ',') { ptr++; } const char* auxPtr = ptr; if (*auxPtr != '-' && *auxPtr != '0') { e = API_OK; return false; } if (*auxPtr == '-') { auxPtr++; if (!(*auxPtr >= '1' && *auxPtr <= '9')) { e = API_OK; return false; } } e = static_cast<error>(atoll(ptr)); storeobject(); return true; } // position at start of object void JSON::begin(const char* json) { pos = json; } // copy remainder of quoted string (no unescaping, use for base64 data only) void JSON::copystring(string* s, const char* p) { if (p) { const char* pp; pp = strchr(p, '"'); if (pp) { s->assign(p, static_cast<size_t>(pp - p)); } else { *s = p; } } else { s->clear(); } } string JSON::stripWhitespace(const string& text) { return stripWhitespace(text.c_str()); } string JSON::stripWhitespace(const char* text) { JSON reader(text); string result; while (*reader.pos) { if (*reader.pos == '"') { string temp; result.push_back('"'); if (!reader.storeobject(&temp)) return result; result.append(temp); result.push_back('"'); } else if (is_space(static_cast<unsigned>(*reader.pos))) ++reader.pos; else result.push_back(*reader.pos++); } return result; } JSONWriter::JSONWriter() : mJson() , mLevels() , mLevel(-1) { } void JSONWriter::cmd(const char* cmd) { mJson.append("\"a\":\""); mJson.append(cmd); mJson.append("\""); } void JSONWriter::notself(MegaClient* client) { mJson.append(",\"i\":\""); mJson.append(client->sessionid, sizeof client->sessionid); mJson.append("\""); } void JSONWriter::arg(const char* name, const string& value, int quotes) { arg(name, value.c_str(), quotes); } void JSONWriter::arg(const char* name, const char* value, int quotes) { addcomma(); mJson.append("\""); mJson.append(name); mJson.append(quotes ? "\":\"" : "\":"); mJson.append(value); if (quotes) { mJson.append("\""); } } void JSONWriter::arg(const char* name, handle h, size_t len) { char buf[16]; Base64::btoa((const byte*)&h, len, buf); arg(name, buf); } void JSONWriter::arg(const char* name, NodeHandle h) { arg(name, h.as8byte(), 6); } void JSONWriter::arg(const char* name, const byte* value, size_t len) { char* buf = new char[len * 4 / 3 + 4]; Base64::btoa(value, len, buf); arg(name, buf); delete[] buf; } void JSONWriter::arg_B64(const char* n, const string& data) { arg(n, (const byte*)data.data(), data.size()); } void JSONWriter::arg_fsfp(const char* n, std::uint64_t fp) { arg(n, (const byte*)&fp, sizeof(fp)); } void JSONWriter::arg_stringWithEscapes(const char* name, const string& value, int quote) { arg(name, escape(value.c_str(), value.size()), quote); } void JSONWriter::arg_stringWithEscapes(const char* name, const char* value, int quote) { arg(name, escape(value, strlen(value)), quote); } void JSONWriter::arg(const char* name, m_off_t n) { char buf[32]; snprintf(buf, sizeof(buf), "%" PRId64, n); arg(name, buf, 0); } void JSONWriter::addcomma() { if (!mJson.empty() && !strchr("[{", mJson.back())) { mJson.append(","); } } void JSONWriter::appendraw(const char* s) { mJson.append(s); } void JSONWriter::appendraw(const char* s, int len) { mJson.append(s, static_cast<size_t>(len)); } void JSONWriter::beginarray() { addcomma(); mJson.append("["); openobject(); } void JSONWriter::beginarray(const char* name) { addcomma(); mJson.append("\""); mJson.append(name); mJson.append("\":["); openobject(); } void JSONWriter::endarray() { mJson.append("]"); closeobject(); } void JSONWriter::beginobject() { addcomma(); mJson.append("{"); } void JSONWriter::beginobject(const char* name) { addcomma(); mJson.append("\""); mJson.append(name); mJson.append("\":{"); } void JSONWriter::endobject() { mJson.append("}"); } void JSONWriter::element(int n) { if (elements()) { mJson.append(","); } mJson.append(std::to_string(n)); } void JSONWriter::element(handle h, size_t len) { char buf[16]; Base64::btoa((const byte*)&h, len, buf); mJson.append(elements() ? ",\"" : "\""); mJson.append(buf); mJson.append("\""); } void JSONWriter::element(const byte* data, size_t len) { char* buf = new char[len * 4 / 3 + 4]; len = Base64::btoa(data, len, buf); mJson.append(elements() ? ",\"" : "\""); mJson.append(buf, len); delete[] buf; mJson.append("\""); } void JSONWriter::element(const char* data) { mJson.append(elements() ? ",\"" : "\""); mJson.append(data); mJson.append("\""); } void JSONWriter::element(const string& data) { element(data.c_str()); } void JSONWriter::element_B64(const string& s) { element((const byte*)s.data(), s.size()); } void JSONWriter::openobject() { mLevels[static_cast<size_t>(++mLevel)] = 0; } void JSONWriter::closeobject() { --mLevel; } const byte* JSONWriter::getbytes() const { return reinterpret_cast<const byte*>(mJson.data()); } const string& JSONWriter::getstring() const { return mJson; } size_t JSONWriter::size() const { return mJson.size(); } int JSONWriter::elements() { assert(mLevel >= 0); if (!mLevels[static_cast<size_t>(mLevel)]) { mLevels[static_cast<size_t>(mLevel)] = 1; return 0; } return 1; } string JSONWriter::escape(const char* data, size_t length) const { const utf8proc_uint8_t* current = reinterpret_cast<const utf8proc_uint8_t *>(data); utf8proc_ssize_t remaining = static_cast<utf8proc_ssize_t>(length); utf8proc_int32_t codepoint = 0; string result; while (remaining > 0) { auto read = utf8proc_iterate(current, remaining, &codepoint); assert(codepoint >= 0); assert(read > 0); current += read; remaining -= read; if (read > 1) { result.append(current - read, current); continue; } switch (codepoint) { case '"': result.append("\\\""); break; case '\\': result.append("\\\\"); break; default: result.push_back(static_cast<char>(current[-1])); break; } } return result; } JSONSplitter::JSONSplitter() { clear(); } void JSONSplitter::clear() { mPos = nullptr; mLastPos = nullptr; mLastName.clear(); mStack.clear(); mCurrentPath.clear(); mProcessedBytes = 0; mExpectValue = 1; mStarting = true; mFinished = false; mFailed = false; mPaused = false; } m_off_t JSONSplitter::processChunk(const std::map<string, FilterCallback>* filters, const char* data) { FiltersChain chain; if (filters) { chain.emplace_back(filters); } return processChunk(chain, data); } m_off_t JSONSplitter::processChunk(const FiltersChain& filtersChain, const char* data) { if (hasFailed() || hasFinished()) { return 0; } if (!filtersChain.empty()) { auto callback = findCallback(filtersChain, "<"); if (callback) { JSON jsonData(""); if (CallbackResult::SUCCESS != (*callback)(&jsonData)) { LOG_err << "Error starting the processing of a chunk"; } } } mPaused = false; mPos = data; mLastPos = data; // Skip the data that was already processed during the previous call mPos += mProcessedBytes; mProcessedBytes = 0; if (mStarting) { if (!filtersChain.empty()) { auto callback = findCallback(filtersChain, ""); if (callback) { JSON jsonData(""); if (CallbackResult::SUCCESS != (*callback)(&jsonData)) { LOG_err << "Parsing error processing first streaming filter" << " Data: " << data; parseError(filtersChain); return 0; } } } mStarting = false; } JSON_CHUNK_PROCESSING << "JSON starting processChunk at path " << mCurrentPath << " ExpectValue: " << mExpectValue << " LastName: " << mLastName << " Data: " << std::string(data, strlen(data) < 32 ? strlen(data) : 32) << " Start: " << std::string(mPos, strlen(mPos) < 32 ? strlen(mPos) : 32); while (*mPos) { char c = *mPos; if (c == '[' || c == '{') { if (!mExpectValue) { LOG_err << "Malformed JSON - unexpected object or array"; parseError(filtersChain); return 0; } mStack.push_back(c + mLastName); mCurrentPath.append(mStack.back()); JSON_CHUNK_PROCESSING << "JSON starting path: " << mCurrentPath; if (!filtersChain.empty() && findCallback(filtersChain, mCurrentPath)) { // a filter is configured for this path - recurse mLastPos = mPos; } mPos++; mLastName.clear(); mExpectValue = c == '['; } else if (c == ']' || c == '}') { if (mExpectValue < 0) { LOG_err << "Malformed JSON - premature closure"; parseError(filtersChain); return 0; } if (mStack.empty()) { LOG_err << "Malformed JSON - unexpected closing bracket with empty stack"; parseError(filtersChain); return 0; } char open = mStack.back()[0]; if ((c == ']' && open != '[') || (c == '}' && open != '{')) { LOG_err << "Malformed JSON - mismatched close"; parseError(filtersChain); return 0; } mLastName.clear(); mPos++; // check if this concludes an exfiltrated object and return it if so if (!filtersChain.empty()) { std::string filter; if (mStack.size() == 1 && c == '}' && mLastPos == data && strncmp(data, "{\"err\":", 7) == 0) { // error response filter = "#"; } else { // regular closure filter = mCurrentPath; } auto callback = findCallback(filtersChain, filter); if (callback) { JSON_CHUNK_PROCESSING << "JSON object/array callback for path: " << filter << " Data: " << std::string(mLastPos, static_cast<size_t>(mPos - mLastPos)); JSON jsonData(mLastPos); if (CallbackResult::SUCCESS != (*callback)(&jsonData)) { LOG_err << "Parsing error processing streaming filter: " << filter << " Data: " << std::string(mLastPos, static_cast<size_t>(mPos - mLastPos)); parseError(filtersChain); return 0; } // Callbacks should consume the exact amount of JSON, except the last one if (mCurrentPath != "{" && jsonData.pos != mPos) { // I'm not aborting the parsing here because no errors were detected during // the processing so probably the callback just ignored some data that it // didn't need. Anyway it would be good to check this when it happens to fix // it. LOG_warn << (mPos - jsonData.pos) << " bytes were not processed by the following streaming filter: " << filter; assert(false); } mLastPos = mPos; } } JSON_CHUNK_PROCESSING << "JSON finishing path: " << mCurrentPath; mCurrentPath.resize(mCurrentPath.size() - mStack.back().size()); mStack.pop_back(); mExpectValue = 0; if (mStack.empty()) { assert(mCurrentPath.empty()); mLastPos = mPos; mFinished = true; break; } } else if (c == ',') { if (mExpectValue) { LOG_err << "Malformed JSON - stray comma"; parseError(filtersChain); return 0; } if (mLastPos == mPos) { mLastPos++; } mPos++; if (mStack.empty()) { LOG_err << "Malformed JSON - unexpected content with empty stack"; parseError(filtersChain); return 0; } mExpectValue = mStack.back()[0] == '['; } else if (c == '"') { int t = strEnd(); if (t < 0) { JSON_CHUNK_PROCESSING << "JSON chunk finished parsing a string." << " Data: " << mPos; break; } if (mExpectValue) { if (!filtersChain.empty()) { std::string filter = mCurrentPath + c + mLastName; auto callback = findCallback(filtersChain, filter); if (callback) { JSON_CHUNK_PROCESSING << "JSON string value callback for: " << filter << " Data: " << std::string(mPos, static_cast<size_t>(t)); JSON jsonData(mPos); auto result = (*callback)(&jsonData); if (result == CallbackResult::FAILED) { LOG_err << "Parsing error processing streaming filter: " << filter << " Data: " << std::string(mPos, static_cast<size_t>(t)); parseError(filtersChain); return 0; } else if (result == CallbackResult::PAUSED) { if (!chunkProcessingFinishedSuccessfully(filtersChain)) { LOG_err << "Error finishing the processing of a chunk after paused"; } auto consumedBytes = mLastPos - data; mProcessedBytes = mPos - mLastPos; mPaused = true; return consumedBytes; } mLastPos = mPos + t; } } JSON_CHUNK_PROCESSING << "JSON string value parsed at path " << mCurrentPath << " Data: " << mLastName << " = " << std::string(mPos + 1, static_cast<size_t>(t - 2)); mPos += t; mExpectValue = 0; mLastName.clear(); } else { // we need at least one char after end of property string if (!mPos[t]) { break; } if (mPos[t] != ':') { LOG_err << "Malformed JSON - no : found after property name"; parseError(filtersChain); return 0; } JSON_CHUNK_PROCESSING << "JSON property name parsed at path " << mCurrentPath << " Data: " << std::string(mPos + 1, static_cast<size_t>(t - 2)); mLastName = std::string(mPos + 1, static_cast<size_t>(t - 2)); mPos += t + 1; mExpectValue = -1; } } else if ((c >= '0' && c <= '9') || c == '.' || c == '-') { if (!mExpectValue) { LOG_err << "Malformed JSON - unexpected number"; parseError(filtersChain); return 0; } int j = numEnd(); if (j < 0 || !mPos[j]) { JSON_CHUNK_PROCESSING << "JSON chunk finished parsing a number." << " Data: " << mPos; break; } JSON_CHUNK_PROCESSING << "JSON number parsed at path " << mCurrentPath << " Data: " << mLastName << " = " << std::string(mPos, static_cast<size_t>(j)); mPos += j; mExpectValue = 0; if (mStack.empty()) { assert(mCurrentPath.empty()); if (!filtersChain.empty() && mLastPos == mPos - j) { assert(mLastPos == data); auto callback = findCallback(filtersChain, "#"); if (callback) { JSON jsonData(mLastPos); JSON_CHUNK_PROCESSING << "JSON error callback." << " Data: " << std::string(mLastPos, static_cast<size_t>(j)); auto result = (*callback)(&jsonData); if (result != CallbackResult::SUCCESS) { LOG_err << "Parsing error processing error streaming filter" << " Data: " << std::string(mLastPos, static_cast<size_t>(j)); parseError(filtersChain); return 0; } } } mLastPos = mPos; mFinished = true; break; } } else if (c == ' ') { // a concession to the API team's aesthetic sense mPos++; } else { LOG_err << "Malformed JSON - bogus char at position " << (mPos - data); parseError(filtersChain); return 0; } } if (!filtersChain.empty() && !chunkProcessingFinishedSuccessfully(filtersChain)) { LOG_err << "Error finishing the processing of a chunk"; } mProcessedBytes = mPos - mLastPos; m_off_t consumedBytes = mLastPos - data; JSON_CHUNK_PROCESSING << "JSON leaving processChunk at path " << mCurrentPath << "." << " Data: " << std::string(mPos, strlen(mPos) < 32 ? strlen(mPos) : 32) << " Processed: " << mProcessedBytes << " Consumed: " << consumedBytes << " Next start: " << std::string(mLastPos, strlen(mLastPos) < 32 ? strlen(mLastPos) : 32); return consumedBytes; } bool JSONSplitter::hasFinished() { return mFinished; } bool JSONSplitter::hasFailed() { return mFailed; } bool JSONSplitter::hasPaused() { return mPaused; } bool JSONSplitter::isStarting() { return mStarting; } int JSONSplitter::strEnd() { const char* ptr = mPos; while ((ptr = strchr(ptr + 1, '"')) != nullptr) { const char *e = ptr; while (*(--e) == '\\') { // noop } if ((ptr - e) & 1) { return int(ptr + 1 - mPos); } } return -1; } int JSONSplitter::numEnd() { const char* ptr = mPos; while (*ptr && strchr("0123456789-+eE.", *ptr)) { ptr++; } if (ptr > mPos) { return int(ptr - mPos); } return -1; } void JSONSplitter::parseError(const FiltersChain& filtersChain) { if (!filtersChain.empty()) { auto callback = findCallback(filtersChain, "E"); if (callback) { JSON jsonData(mPos); (*callback)(&jsonData); } if (!chunkProcessingFinishedSuccessfully(filtersChain)) { LOG_err << "Error finishing the processing of a chunk after error"; } } mFailed = true; assert(false); } bool JSONSplitter::chunkProcessingFinishedSuccessfully(const FiltersChain& filtersChain) { auto callback = findCallback(filtersChain, ">"); if (callback) { JSON jsonData(""); if (CallbackResult::SUCCESS != (*callback)(&jsonData)) { return false; } } return true; } const JSONSplitter::FilterCallback* JSONSplitter::findCallback(const FiltersChain& filtersChain, const std::string& path) { for (const auto& filters: filtersChain) { if (filters == nullptr) { continue; } auto filterit = filters->find(path); if (filterit != filters->end()) { return &filterit->second; } } return nullptr; } } // namespace ����sdk-10.11.0/src/localpath.cpp�����������������������������������������������������������������������0000664�0000000�0000000�00000146571�15162662266�0015727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ #include <mega/filesystem.h> #include <mega/localpath.h> #include <mega/logging.h> #include <mega/utils.h> #include <memory> #ifdef WIN32 #include <mega/win32/megafs.h> #include <cwctype> #elif TARGET_OS_MAC #include <mega/osx/megafs.h> #include <mega/osx/osxutils.h> #elif __ANDROID__ #include "mega/android/androidFileSystem.h" #else #include <mega/posix/megafs.h> #endif namespace mega { PlatformURIHelper* URIHandler::mPlatformHelper = nullptr; bool URIHandler::isURI(const string_type& uri) { if (mPlatformHelper) { return mPlatformHelper->isURI(uri); } return false; } std::optional<string_type> URIHandler::getName(const string_type& uri) { if (mPlatformHelper) { return mPlatformHelper->getName(uri); } return std::nullopt; } std::optional<string_type> URIHandler::getParentURI(const string_type& uri) { if (mPlatformHelper) { return mPlatformHelper->getParentURI(uri); } return std::nullopt; } std::optional<string_type> URIHandler::getPath(const string_type& uri) { if (mPlatformHelper) { return mPlatformHelper->getPath(uri); } return std::nullopt; } std::optional<string_type> URIHandler::getURI(const string_type& uri, const std::vector<string_type> leaves) { if (mPlatformHelper) { return mPlatformHelper->getURI(uri, leaves); } return std::nullopt; } void URIHandler::setPlatformHelper(PlatformURIHelper* platformHelper) { mPlatformHelper = platformHelper; } // anonymous namespace namespace { class Path: public mega::AbstractLocalPath { public: ~Path() override {} std::string platformEncoded() const override; auto asPlatformEncoded(const bool stripPrefix) const -> string_type override; bool empty() const override; void clear() override; LocalPath leafName() const override; std::string leafOrParentName() const override; void append(const LocalPath& additionalPath) override; void appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways) override; void prependWithSeparator(const LocalPath& additionalPath) override; LocalPath prependNewWithSeparator(const LocalPath& additionalPath) const override; void trimNonDriveTrailingSeparator() override; bool findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess& fsaccess) const override; bool beginsWithSeparator() const override; bool endsInSeparator() const override; size_t getLeafnameByteIndex() const override; LocalPath subpathFrom(const size_t bytePos) const override; void changeLeaf(const LocalPath& newLeaf) override; LocalPath parentPath() const override; LocalPath insertFilenameSuffix(const std::string& suffix) const override; std::string toPath(const bool normalize) const override; std::string toName(const FileSystemAccess& fsaccess) const override; bool isRootPath() const override; bool extension(std::string& extension) const override; std::string extension() const override; bool related(const LocalPath& other) const override; PathType getPathType() const override { return mPathType; } bool invariant() const override; // helper functions to ensure proper format especially on windows void normalizeAbsolute(); Path(const string_type& path): mLocalpath(path) {} Path() = default; std::unique_ptr<AbstractLocalPath> clone() const override { return std::make_unique<Path>(*this); } void setPathType(const PathType type) { mPathType = type; } std::string serialize() const override; bool unserialize(const std::string& data) override; string_type getRealPath() const override; private: string_type mLocalpath; // Track whether this LocalPath is from the root of a filesystem (ie, an absolute path) // It makes a big difference for windows, where we must prepend \\?\ prefix // to be able to access long paths, paths ending with space or `.`, etc PathType mPathType{PathType::RELATIVE_PATH}; void removeTrailingSeparators(); void truncate(size_t bytePos); LocalPath subpathTo(size_t bytePos) const; bool findNextSeparator(size_t& separatorBytePos) const override; }; class PathURI: public mega::AbstractLocalPath { public: ~PathURI() override {} auto asPlatformEncoded(const bool stripPrefix) const -> string_type override; std::string platformEncoded() const override; bool empty() const override; void clear() override; LocalPath leafName() const override; std::string leafOrParentName() const override; void append(const LocalPath& additionalPath) override; void appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways) override; void prependWithSeparator(const LocalPath& additionalPath) override; LocalPath prependNewWithSeparator(const LocalPath& additionalPath) const override; void trimNonDriveTrailingSeparator() override; bool findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess& fsaccess) const override; bool beginsWithSeparator() const override; bool endsInSeparator() const override; size_t getLeafnameByteIndex() const override; LocalPath subpathFrom(const size_t bytePos) const override; void changeLeaf(const LocalPath& newLeaf) override; LocalPath parentPath() const override; LocalPath insertFilenameSuffix(const std::string& suffix) const override; std::string toPath(const bool normalize) const override; std::string toName(const FileSystemAccess& fsaccess) const override; bool isRootPath() const override; PathType getPathType() const override { return PathType::URI_PATH; } bool extension(std::string& extension) const override; std::string extension() const override; bool related(const LocalPath& other) const override; bool invariant() const override; PathURI(const string_type& path): mUri(path) {} PathURI() = default; std::unique_ptr<AbstractLocalPath> clone() const override { return std::make_unique<PathURI>(*this); } std::string serialize() const override; bool unserialize(const std::string& data) override; string_type getRealPath() const override; private: // String allows to identify a file or folder string_type mUri; // Chain of elements that identify leaves from the tree // It isn't possible concat element as in a standard path // They are stored as elements in a vector std::vector<string_type> mAuxPath; void removeLastElement(); bool findNextSeparator(size_t& separatorBytePos) const override; }; } // end anonymous namespace class LocalPathImplementationHelper { public: static const PathURI* getPathURI(const LocalPath& p) { return dynamic_cast<const PathURI*>(p.getImpl()); } static PathURI* getPathURI(LocalPath& p) { return dynamic_cast<PathURI*>(p.getImpl()); } static const Path* getPathLocal(const LocalPath& p) { return dynamic_cast<const Path*>(p.getImpl()); } static Path* getPathLocal(LocalPath& p) { return dynamic_cast<Path*>(p.getImpl()); } template<typename T> static LocalPath buildLocalPath(const T& source) { static_assert(std::is_base_of<AbstractLocalPath, T>::value, "T must be derived from AbstractLocalPath"); LocalPath localPath; auto aux = std::make_unique<T>(source); localPath.setImpl(std::move(aux)); return localPath; } template<typename StringT> static LocalPath buildFromPlatformEncoded(StringT&& encodedPath, const PathType pathType, const bool normalize) { LocalPath p; auto aux = std::make_unique<Path>(std::forward<StringT>(encodedPath)); if (normalize) aux->normalizeAbsolute(); aux->setPathType(pathType); p.setImpl(std::move(aux)); return p; } static string_type convertPlatformEncoded(const std::string& path) { #ifdef _WIN32 assert((path.size() % sizeof(wchar_t) == 0) && "size is not a multiple of wchar_t !!!"); return std::wstring(reinterpret_cast<const wchar_t*>(path.data()), path.size() / sizeof(wchar_t)); #else return path; #endif } }; LocalPath::LocalPath(): mImplementation(std::make_unique<Path>()) { assert(invariant()); } LocalPath::LocalPath(LocalPath&& p) noexcept: mImplementation(std::move(p.mImplementation)) { assert(invariant()); } LocalPath& LocalPath::operator=(LocalPath&& p) noexcept { if (this != &p) { if (p.mImplementation) { mImplementation = std::move(p.mImplementation); } else { mImplementation.reset(new Path()); } } assert(invariant()); return *this; } LocalPath::LocalPath(const LocalPath& p) { if (p.mImplementation) { mImplementation = p.mImplementation->clone(); } else { mImplementation.reset(new Path()); } assert(invariant()); } LocalPath& LocalPath::operator=(const LocalPath& p) { if (this != &p) { if (p.mImplementation) { mImplementation = p.mImplementation->clone(); } else { mImplementation.reset(new Path()); } } assert(invariant()); return *this; } #if defined(_WIN32) // convert UTF-8 to Windows Unicode void LocalPath::path2local(const std::string* path, std::string* local) { // make space for the worst case local->resize((path->size() + 1) * sizeof(wchar_t)); const auto len = MultiByteToWideChar(CP_UTF8, 0, path->c_str(), -1, (wchar_t*)local->data(), int(local->size() / sizeof(wchar_t) + 1)); if (len) { // resize to actual result local->resize(sizeof(wchar_t) * (len - 1)); } else { local->clear(); } } // convert UTF-8 to Windows Unicode void LocalPath::path2local(const std::string* path, std::wstring* local) { // make space for the worst case local->resize(path->size() + 2); const auto len = MultiByteToWideChar(CP_UTF8, 0, path->c_str(), -1, const_cast<wchar_t*>(local->data()), int(local->size())); if (len) { // resize to actual result local->resize(len - 1); } else { local->clear(); } } // convert Windows Unicode to UTF-8 void LocalPath::local2path(const std::string* local, std::string* path, const bool normalize) { path->resize((local->size() + 1) * 4 / sizeof(wchar_t) + 1); path->resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)local->data(), int(local->size() / sizeof(wchar_t)), (char*)path->data(), int(path->size()), NULL, NULL)); if (normalize) utf8_normalize(path); } void LocalPath::local2path(const std::wstring* local, std::string* path, const bool normalize) { path->resize((local->size() * sizeof(wchar_t) + 1) * 4 / sizeof(wchar_t) + 1); path->resize(WideCharToMultiByte(CP_UTF8, 0, local->data(), int(local->size()), (char*)path->data(), int(path->size()), NULL, NULL)); if (normalize) utf8_normalize(path); } #else void LocalPath::path2local(const std::string* path, std::string* local) { #ifdef __MACH__ path2localMac(path, local); #else *local = *path; #endif } void LocalPath::local2path(const std::string* local, std::string* path, const bool normalize) { *path = *local; if (normalize) LocalPath::utf8_normalize(path); } #endif LocalPath LocalPath::fromAbsolutePath(const std::string& path) { LocalPath p; string_type newPath; path2local(&path, &newPath); if (LocalPath::isURIPath(path)) { p.mImplementation = std::make_unique<PathURI>(newPath); } else { auto aux = std::make_unique<Path>(newPath); aux->normalizeAbsolute(); aux->setPathType(PathType::ABSOLUTE_PATH); p.setImpl(std::move(aux)); } return p; } LocalPath LocalPath::fromRelativePath(const std::string& path) { LocalPath p; string_type newPath; path2local(&path, &newPath); auto aux = std::make_unique<Path>(newPath); aux->setPathType(PathType::RELATIVE_PATH); p.setImpl(std::move(aux)); assert(p.invariant()); return p; } LocalPath LocalPath::fromURIPath(const string_type& path) { LocalPath p; std::string auxStr; LocalPath::local2path(&path, &auxStr, false); std::unique_ptr<PathURI> aux; p.mImplementation = std::make_unique<PathURI>(path); return p; } #ifdef _WIN32 string_type LocalPath::toStringType(const std::wstring& path) { return path; } #endif string_type LocalPath::toStringType(const std::string& path) { string_type converted; LocalPath::path2local(&path, &converted); return converted; } bool LocalPath::isURIPath(const std::string& path) { return URIHandler::isURI(toStringType(path)); } LocalPath LocalPath::fromRelativeName(std::string path, const FileSystemAccess& fsaccess, const FileSystemType fsType) { fsaccess.escapefsincompatible(&path, fsType); return fromRelativePath(path); } LocalPath LocalPath::fromPlatformEncodedAbsolute(const std::string& path) { using helper = LocalPathImplementationHelper; if (const auto stringTypePath = toStringType(path); URIHandler::isURI(stringTypePath)) { return fromURIPath(stringTypePath); } return helper::buildFromPlatformEncoded(helper::convertPlatformEncoded(path), PathType::ABSOLUTE_PATH, true); } #if defined(_WIN32) LocalPath LocalPath::fromPlatformEncodedRelative(wstring&& wpath) { using helper = LocalPathImplementationHelper; return helper::buildFromPlatformEncoded(std::move(wpath), PathType::RELATIVE_PATH, false); } LocalPath LocalPath::fromPlatformEncodedAbsolute(wstring&& wpath) { using helper = LocalPathImplementationHelper; if (const auto stringTypePath = toStringType(wpath); URIHandler::isURI(stringTypePath)) { return fromURIPath(stringTypePath); } return helper::buildFromPlatformEncoded(std::move(wpath), PathType::ABSOLUTE_PATH, true); } #endif LocalPath LocalPath::fromPlatformEncodedRelative(const std::string& path) { using helper = LocalPathImplementationHelper; return helper::buildFromPlatformEncoded(helper::convertPlatformEncoded(path), PathType::RELATIVE_PATH, false); } void LocalPath::utf8_normalize(std::string* filename) { if (!filename) return; const char* cfilename = filename->c_str(); const auto fnsize = filename->size(); std::string result; for (size_t i = 0; i < fnsize;) { // allow NUL bytes between valid UTF-8 sequences if (!cfilename[i]) { result.append("", 1); i++; continue; } const char* substring = cfilename + i; char* normalized = (char*)utf8proc_NFC((uint8_t*)substring); if (!normalized) { filename->clear(); return; } result.append(normalized); free(normalized); i += strlen(substring); } *filename = std::move(result); } std::atomic<unsigned> LocalPath_tmpNameLocal_counter{}; LocalPath LocalPath::tmpNameLocal() { char buf[128]; snprintf(buf, sizeof(buf), ".getxfer.%lu.%u.mega", mega::getCurrentPid(), ++LocalPath_tmpNameLocal_counter); return LocalPath::fromRelativePath(buf); } std::string LocalPath::serialize() const { return mImplementation ? mImplementation->serialize() : std::string{}; } std::optional<LocalPath> LocalPath::unserialize(const std::string& d) { CacheableReader r(d); uint8_t type; r.unserializeu8(type); LocalPath p{}; if (static_cast<PathType>(type) == PathType::URI_PATH) { p.mImplementation = std::make_unique<PathURI>(); } else { p.mImplementation = std::make_unique<Path>(); } if (p.mImplementation->unserialize(d)) { return p; } return std::nullopt; } bool LocalPath::operator==(const LocalPath& p) const { return toPath(false) == p.toPath(false); } bool LocalPath::operator!=(const LocalPath& p) const { return toPath(false) != p.toPath(false); } bool LocalPath::operator<(const LocalPath& p) const { return toPath(false) < p.toPath(false); } auto LocalPath::asPlatformEncoded(const bool stripPrefix) const -> string_type { if (mImplementation) { return mImplementation->asPlatformEncoded(stripPrefix); } return string_type{}; } std::string LocalPath::platformEncoded() const { if (mImplementation) { return mImplementation->platformEncoded(); } return std::string{}; } bool LocalPath::empty() const { if (mImplementation) { return mImplementation->empty(); } return true; } void LocalPath::clear() { mImplementation.reset(new Path()); assert(invariant()); } LocalPath LocalPath::leafName() const { if (mImplementation) { return mImplementation->leafName(); } return LocalPath{}; } std::string LocalPath::leafOrParentName() const { if (mImplementation) { return mImplementation->leafOrParentName(); } return std::string{}; } void LocalPath::append(const LocalPath& additionalPath) { if (mImplementation) { mImplementation->append(additionalPath); } } void LocalPath::appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways) { if (mImplementation) { mImplementation->appendWithSeparator(additionalPath, separatorAlways); } } void LocalPath::prependWithSeparator(const LocalPath& additionalPath) { if (isAbsolute() || isURI()) { LOG_err << "Invalid parameter type (prependWithSeparator)"; assert(false); return; } if (additionalPath.isURI()) { const auto previousPath = this->toPath(false); mImplementation = std::make_unique<PathURI>(*LocalPathImplementationHelper::getPathURI(additionalPath)); auto leaves = splitString<std::vector<string>>(previousPath, localPathSeparator); for (auto& leaf: leaves) { mImplementation->appendWithSeparator(LocalPath::fromRelativePath(leaf), true); } return; } else if (!mImplementation) { mImplementation = std::make_unique<Path>(); } mImplementation->prependWithSeparator(additionalPath); } LocalPath LocalPath::prependNewWithSeparator(const LocalPath& additionalPath) const { if (additionalPath.isURI()) { LocalPath newPath = additionalPath; newPath.append(*this); return newPath; } if (mImplementation) { return mImplementation->prependNewWithSeparator(additionalPath); } return LocalPath{}; } void LocalPath::trimNonDriveTrailingSeparator() { if (mImplementation) { mImplementation->trimNonDriveTrailingSeparator(); } } bool LocalPath::findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess& fsaccess) const { if (mImplementation) { return mImplementation->findPrevSeparator(separatorBytePos, fsaccess); } return false; } bool LocalPath::beginsWithSeparator() const { if (mImplementation) { return mImplementation->beginsWithSeparator(); } return false; } bool LocalPath::endsInSeparator() const { if (mImplementation) { return mImplementation->endsInSeparator(); } return false; } size_t LocalPath::getLeafnameByteIndex() const { if (mImplementation) { return mImplementation->getLeafnameByteIndex(); } return 0u; } LocalPath LocalPath::subpathFrom(size_t bytePos) const { if (mImplementation) { return mImplementation->subpathFrom(bytePos); } return LocalPath{}; } void LocalPath::changeLeaf(const LocalPath& newLeaf) { if (mImplementation) { mImplementation->changeLeaf(newLeaf); } } LocalPath LocalPath::parentPath() const { if (mImplementation) { return mImplementation->parentPath(); } return LocalPath{}; } LocalPath LocalPath::insertFilenameSuffix(const std::string& suffix) const { if (mImplementation) { return mImplementation->insertFilenameSuffix(suffix); } return LocalPath{}; } bool LocalPath::isContainingPathOf(const LocalPath& path, size_t* subpathIndex) const { if (mImplementation) { return mImplementation->isContainingPathOf(path, subpathIndex); } return false; } bool LocalPath::nextPathComponent(size_t& subpathIndex, LocalPath& component) const { if (mImplementation) { return mImplementation->nextPathComponent(subpathIndex, component); } return false; } bool LocalPath::hasNextPathComponent(const size_t index) const { if (mImplementation) { return mImplementation->hasNextPathComponent(index); } return false; } std::string LocalPath::toPath(const bool normalize) const { if (mImplementation) { return mImplementation->toPath(normalize); } return {}; } std::string LocalPath::toName(const FileSystemAccess& fsaccess) const { if (mImplementation) { return mImplementation->toName(fsaccess); } return {}; } bool LocalPath::isRootPath() const { if (mImplementation) { return mImplementation->isRootPath(); } return false; } bool LocalPath::extension(std::string& extension) const { if (mImplementation) { return mImplementation->extension(extension); } return false; } std::string LocalPath::extension() const { if (mImplementation) { return mImplementation->extension(); } return {}; } bool LocalPath::related(const LocalPath& other) const { if (mImplementation) { return mImplementation->related(other); } return false; } bool LocalPath::invariant() const { if (mImplementation) { return mImplementation->invariant(); } return false; } string_type LocalPath::getRealPath() const { if (mImplementation) { return mImplementation->getRealPath(); } return {}; } auto Path::asPlatformEncoded([[maybe_unused]] const bool skipPrefix) const -> string_type { #ifdef WIN32 // Caller wants the prefix intact. if (!skipPrefix) return mLocalpath; // Path doesn't begin with the prefix. if (mLocalpath.size() < 4 || mLocalpath.compare(0, 4, L"\\\\?\\")) return mLocalpath; // Path doesn't begin wih the UNC prefix. if (mLocalpath.size() < 8 || mLocalpath.compare(4, 4, L"UNC\\")) return mLocalpath.substr(4); return mLocalpath.substr(8); #else return mLocalpath; #endif } std::string Path::platformEncoded() const { #ifdef WIN32 // this function is typically used where we need to pass a file path to the client app, which // expects utf16 in a std::string buffer some other backwards compatible cases need this format // also, eg. serialization std::string outstr; if (mLocalpath.size() >= 4 && 0 == mLocalpath.compare(0, 4, L"\\\\?\\", 4)) { if (0 == mLocalpath.compare(4, 4, L"UNC\\", 4)) { // when a path leaves LocalPath, we can remove prefix which is only needed internally outstr.resize((mLocalpath.size() - 6) * sizeof(wchar_t)); memcpy(const_cast<char*>(outstr.data()), mLocalpath.data() + 0, 2 * sizeof(wchar_t)); // "\\\\" memcpy(const_cast<char*>(outstr.data()) + 4, mLocalpath.data() + 8, (mLocalpath.size() - 8) * sizeof(wchar_t)); } else { // when a path leaves LocalPath, we can remove prefix which is only needed internally outstr.resize((mLocalpath.size() - 4) * sizeof(wchar_t)); memcpy(const_cast<char*>(outstr.data()), mLocalpath.data() + 4, (mLocalpath.size() - 4) * sizeof(wchar_t)); } } else { outstr.resize(mLocalpath.size() * sizeof(wchar_t)); memcpy(const_cast<char*>(outstr.data()), mLocalpath.data(), mLocalpath.size() * sizeof(wchar_t)); } return outstr; #else // for non-windows, it's just the same utf8 string we use anyway return mLocalpath; #endif } bool Path::empty() const { assert(invariant()); return mLocalpath.empty(); } void Path::clear() { assert(invariant()); mLocalpath.clear(); mPathType = PathType::RELATIVE_PATH; assert(invariant()); } LocalPath Path::leafName() const { Path result; assert(invariant()); auto p = mLocalpath.find_last_of(LocalPath::localPathSeparator); p = p == std::string::npos ? 0 : p + 1; result.mLocalpath = mLocalpath.substr(p, mLocalpath.size() - p); assert(result.invariant()); return LocalPathImplementationHelper::buildLocalPath(result); } std::string Path::leafOrParentName() const { assert(invariant()); LocalPath name; // win32: normalizeAbsolute() does not work with paths like "D:\\foo\\..\\bar.txt". TODO ? FSACCESS_CLASS().expanselocalpath(LocalPathImplementationHelper::buildLocalPath(*this), name); Path* auxPath = LocalPathImplementationHelper::getPathLocal(name); assert(auxPath); auxPath->removeTrailingSeparators(); if (name.empty()) { return std::string{}; } #ifdef WIN32 auto nameStr = name.asPlatformEncoded(false); if (nameStr.back() == L':') { // drop trailing ':' string n = name.toPath(true); n.pop_back(); return n; } #endif return name.leafName().toPath(true); } void Path::append(const LocalPath& additionalPath) { assert(!additionalPath.isAbsolute() && !additionalPath.isURI()); assert(invariant()); mLocalpath.append(additionalPath.asPlatformEncoded(false)); assert(invariant()); } void Path::appendWithSeparator(const LocalPath& additionalPath, const bool separatorAlways) { if (additionalPath.isAbsolute() || additionalPath.isURI()) { LOG_err << "Invalid parameter type (appendWithSeparator)"; assert(false); return; } #ifdef USE_IOS bool originallyUsesAppBasePath = getPathType() == PathType::ABSOLUTE_PATH && (empty() || !beginsWithSeparator()); #endif if (separatorAlways || mLocalpath.size()) { // still have to be careful about appending a \ to F:\ for example, on windows, which // produces an invalid path const Path* p = LocalPathImplementationHelper::getPathLocal(additionalPath); assert(p); if (!(endsInSeparator() || p->beginsWithSeparator())) { mLocalpath.append(1, LocalPath::localPathSeparator); } } mLocalpath.append(additionalPath.asPlatformEncoded(false)); #ifdef USE_IOS if (originallyUsesAppBasePath) { while (!empty() && beginsWithSeparator()) { mLocalpath.erase(0, 1); } } #endif assert(invariant()); } void Path::prependWithSeparator(const LocalPath& additionalPath) { if (mPathType == PathType::ABSOLUTE_PATH) { LOG_err << "Invalid parameter type (prependWithSeparator)"; return; } assert(invariant()); // no additional separator if there is already one after if (!mLocalpath.empty() && mLocalpath[0] != LocalPath::localPathSeparator) { // no additional separator if there is already one before if (!(beginsWithSeparator() || additionalPath.endsInSeparator())) { mLocalpath.insert(0, 1, LocalPath::localPathSeparator); } } mLocalpath.insert(0, additionalPath.asPlatformEncoded(false)); mPathType = additionalPath.isAbsolute() ? PathType::ABSOLUTE_PATH : PathType::RELATIVE_PATH; assert(invariant()); } LocalPath Path::prependNewWithSeparator(const LocalPath& additionalPath) const { Path p = *this; p.prependWithSeparator(additionalPath); return LocalPathImplementationHelper::buildLocalPath(p); } void Path::trimNonDriveTrailingSeparator() { assert(invariant()); if (endsInSeparator()) { // ok so the last character is a directory separator. But don't remove it for eg. F:\ on windows #ifdef WIN32 if (mLocalpath.size() > 1 && mLocalpath[mLocalpath.size() - 2] == L':') { return; } #endif mLocalpath.resize(mLocalpath.size() - 1); } assert(invariant()); } bool Path::findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess&) const { assert(invariant()); separatorBytePos = mLocalpath.rfind(LocalPath::localPathSeparator, separatorBytePos); return separatorBytePos != std::string::npos; } bool Path::endsInSeparator() const { assert(invariant()); return !mLocalpath.empty() && mLocalpath.back() == LocalPath::localPathSeparator; } size_t Path::getLeafnameByteIndex() const { assert(invariant()); size_t p = mLocalpath.size(); while (p && (p -= 1)) { if (mLocalpath[p] == LocalPath::localPathSeparator) { p += 1; break; } } return p; } LocalPath Path::subpathFrom(const size_t bytePos) const { assert(invariant()); Path result; result.mLocalpath = mLocalpath.substr(bytePos); assert(result.invariant()); return LocalPathImplementationHelper::buildLocalPath(result); } void Path::changeLeaf(const LocalPath& newLeaf) { const auto leafIndex = getLeafnameByteIndex(); truncate(leafIndex); appendWithSeparator(newLeaf, false); } LocalPath Path::parentPath() const { assert(invariant()); return subpathTo(getLeafnameByteIndex()); } LocalPath Path::insertFilenameSuffix(const std::string& suffix) const { assert(invariant()); const auto dotindex = mLocalpath.find_last_of('.'); const auto sepindex = mLocalpath.find_last_of(LocalPath::localPathSeparator); Path result, extension; if (dotindex == std::string::npos || (sepindex != std::string::npos && sepindex > dotindex)) { result.mLocalpath = mLocalpath; result.mPathType = mPathType; } else { result.mLocalpath = mLocalpath.substr(0, dotindex); result.mPathType = mPathType; extension.mLocalpath = mLocalpath.substr(dotindex); } result.mLocalpath += LocalPath::fromRelativePath(suffix).asPlatformEncoded(false) + extension.mLocalpath; assert(result.invariant()); return LocalPathImplementationHelper::buildLocalPath(result); } bool AbstractLocalPath::isContainingPathOf(const LocalPath& path, size_t* subpathIndex) const { string_type parameterLocalPath{path.getRealPath()}; string_type thisLocalPath{getRealPath()}; if (parameterLocalPath.size() >= thisLocalPath.size() && !Utils::pcasecmp(parameterLocalPath, thisLocalPath, thisLocalPath.size())) { if (parameterLocalPath.size() == thisLocalPath.size()) { if (subpathIndex) *subpathIndex = thisLocalPath.size(); return true; } else if (parameterLocalPath[thisLocalPath.size()] == LocalPath::localPathSeparator) { if (subpathIndex) *subpathIndex = thisLocalPath.size() + 1; return true; } else if (!thisLocalPath.empty() && parameterLocalPath[thisLocalPath.size() - 1] == LocalPath::localPathSeparator) { if (subpathIndex) *subpathIndex = thisLocalPath.size(); return true; } } return false; } bool AbstractLocalPath::nextPathComponent(size_t& subpathIndex, LocalPath& component) const { string_type parameterLocalPath{component.getRealPath()}; string_type thisLocalPath{getRealPath()}; while (subpathIndex < thisLocalPath.size() && thisLocalPath[subpathIndex] == LocalPath::localPathSeparator) { ++subpathIndex; } const auto start = subpathIndex; if (start >= thisLocalPath.size()) { return false; } else if (findNextSeparator(subpathIndex)) { parameterLocalPath = thisLocalPath.substr(start, subpathIndex - start); component = LocalPath::fromPlatformEncodedRelative(std::move(parameterLocalPath)); assert(component.invariant()); return true; } else { parameterLocalPath = thisLocalPath.substr(start, thisLocalPath.size() - start); component.clear(); component = LocalPath::fromPlatformEncodedRelative(std::move(parameterLocalPath)); subpathIndex = thisLocalPath.size(); assert(component.invariant()); return true; } } bool AbstractLocalPath::hasNextPathComponent(size_t index) const { assert(invariant()); string_type thisLocalPath{getRealPath()}; return index < thisLocalPath.size(); } std::string Path::toPath(const bool normalize) const { assert(invariant()); std::string path; LocalPath::local2path(&mLocalpath, &path, normalize); #ifdef WIN32 if (path.size() >= 4 && 0 == path.compare(0, 4, "\\\\?\\", 4)) { if (0 == mLocalpath.compare(4, 4, L"UNC\\", 4)) { // when a path leaves LocalPath, we can remove prefix which is only needed internally path.erase(2, 6); } else { // when a path leaves LocalPath, we can remove prefix which is only needed internally path.erase(0, 4); } } #endif return path; } std::string Path::toName(const FileSystemAccess& fsaccess) const { std::string name = toPath(true); fsaccess.unescapefsincompatible(&name); return name; } bool Path::isRootPath() const { #ifdef WIN32 if (mPathType != PathType::ABSOLUTE_PATH) return false; static const std::wstring prefix = L"\\\\?\\"; std::size_t length = mLocalpath.size(); std::size_t offset = 0; // Skip namespace prefix if present. if (mLocalpath.size() > prefix.size() && !mLocalpath.compare(0, prefix.size(), prefix)) offset = prefix.size(); // Path is too short to contain a drive letter. if (offset + 2 > mLocalpath.size()) return false; // Convenience. std::wint_t drive = mLocalpath[offset++]; // Drive letter's outside domain of wchar_t. if (drive < WCHAR_MIN || drive > WCHAR_MAX) return false; // Drive letter isn't actually a drive letter. if (!std::iswalpha(drive)) return false; // Path doesn't contain drive letter separator. if (mLocalpath[offset++] != L':') return false; // Path must end with a directory separator. if (length > offset) return mLocalpath[offset++] == L'\\' && length == offset; return true; #else if (mPathType == PathType::ABSOLUTE_PATH) return mLocalpath.size() == 1 && mLocalpath.back() == '/'; return false; #endif } bool Path::extension(std::string& extension) const { return extensionOf(leafName().toPath(false), extension); } std::string Path::extension() const { return extensionOf(leafName().toPath(false)); } bool Path::related(const LocalPath& other) const { assert(other.isAbsolute()); // This path is shorter: It may contain other. if (mLocalpath.size() <= other.toPath(true).size()) return isContainingPathOf(other); // Other is shorter: It may contain this path. return other.isContainingPathOf(LocalPathImplementationHelper::buildLocalPath(*this)); } void Path::removeTrailingSeparators() { assert(invariant()); // Remove trailing separator if present. while (mLocalpath.size() > 1 && mLocalpath.back() == LocalPath::localPathSeparator) { mLocalpath.pop_back(); } assert(invariant()); } bool Path::invariant() const { #ifdef USE_IOS // iOS is a tricky case. // We need to be able to use and persist absolute paths. however on iOS app restart, // our app base path may be different. So, we only record the path beyond that app // base path. That is what the app supplies for absolute paths, that's what is persisted. // Actual filesystem functions passed such an "absolute" path will prepend the app base path // unless it already started with / // and that's how it worked before we added the "absolute" feature to LocalPath. // As a result of that though, there's nothing to adjust or check here for iOS. #elif WIN32 if (mPathType == PathType::ABSOLUTE_PATH) { // if it starts with \\ then it's absolute, either by us or provided if (mLocalpath.size() >= 2 && mLocalpath[0] == '\\' && mLocalpath[1] == '\\') return true; // otherwise it must contain a drive letter if (mLocalpath.find(L":") == string_type::npos) return false; // ok so probably relative then, but double check: if (PathIsRelativeW(mLocalpath.c_str())) return false; } else { // must not contain a drive letter if (mLocalpath.find(L":") != string_type::npos) return false; // must not start "\\" if (mLocalpath.size() >= 2 && mLocalpath.substr(0, 2) == L"\\\\") return false; } #else if (mPathType == PathType::ABSOLUTE_PATH) { // must start / if (mLocalpath.size() < 1) return false; if (mLocalpath.front() != LocalPath::localPathSeparator) return false; } else { // this could contain /relative for appending etc. } #endif return true; } void Path::normalizeAbsolute() { assert(!mLocalpath.empty()); #ifdef USE_IOS // iOS is a tricky case. // We need to be able to use and persist absolute paths. however on iOS app restart, // our app base path may be different. So, we only record the path beyond that app // base path. That is what the app supplies for absolute paths, that's what is persisted. // Actual filesystem functions passed such an "absolute" path will prepend the app base path // unless it already started with / // and that's how it worked before we added the "absolute" feature to LocalPath. // As a result of that though, there's nothing to adjust or check here for iOS. mPathType = PathType::ABSOLUTE_PATH; // In addition, for iOS, should the app try to use ".", or "./", we interpret that to mean // that really it's relative to the app base path. So we convert: if (!mLocalpath.empty() && mLocalpath.front() == '.') { if (mLocalpath.size() == 1 || mLocalpath[1] == LocalPath::localPathSeparator) { mLocalpath.erase(0, 1); while (!mLocalpath.empty() && mLocalpath.front() == LocalPath::localPathSeparator) { mLocalpath.erase(0, 1); } } } #elif WIN32 // Add a drive separator if necessary. // append \ to bare Windows drive letter paths // GetFullPathNameW does all of this for windows. // The documentation says to prepend \\?\ to deal with long names, but it makes the function // fail it seems to work with long names anyway. // We also convert to absolute if it isn't already, which GetFullPathNameW does also. // So that when working with LocalPath, we always have the full path. // Historically, relative paths can come into the system, this will convert them. if (PathIsRelativeW(mLocalpath.c_str())) { // ms: In the ANSI version of this function, the name is limited to MAX_PATH characters. // ms: To extend this limit to 32,767 wide characters, call the Unicode version of the // function (GetFullPathNameW), and prepend "\\?\" to the path WCHAR buffer[32768]; DWORD stringLen = GetFullPathNameW(mLocalpath.c_str(), 32768, buffer, NULL); assert(stringLen < 32768); mLocalpath = wstring(buffer, stringLen); } mPathType = PathType::ABSOLUTE_PATH; // See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats // Also https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation // Basically, \\?\ is the magic prefix that means "don't mess with the path I gave you", // and lets us access otherwise inaccessible files (trailing ' ', '.', very long names, etc). // "Unless the path starts exactly with \\?\ (note the use of the canonical backslash), it is // normalized." // TODO: add long-path-aware manifest? (see 2nd link) if (mLocalpath.substr(0, 2) == L"\\\\") { // The caller already passed in a path that should be precise either with \\?\ or \\.\ or // \\<server> etc. Let's trust they know what they are doing and leave the path alone if (mLocalpath.substr(0, 4) != L"\\\\?\\" && mLocalpath.substr(0, 4) != L"\\\\.\\") { // However, it turns out, \\?\UNC\<server>\etc can allow us to operate on paths with // trailing spaces and other things Explorer doesn't like, which just \\server\ does not { mLocalpath.insert(2, L"?\\UNC\\"); } } } else { mLocalpath.insert(0, L"\\\\?\\"); } #else // convert to absolute if it isn't already if (!mLocalpath.empty() && mLocalpath[0] != LocalPath::localPathSeparator) { LocalPath lp; PosixFileSystemAccess::cwd_static(lp); lp.appendWithSeparator(LocalPathImplementationHelper::buildLocalPath(*this), false); mLocalpath = lp.toPath(false); } mPathType = PathType::ABSOLUTE_PATH; #endif assert(invariant()); } void Path::truncate(size_t bytePos) { assert(invariant()); mLocalpath.resize(bytePos); assert(invariant()); } bool Path::beginsWithSeparator() const { return !mLocalpath.empty() && mLocalpath.front() == LocalPath::localPathSeparator; } LocalPath Path::subpathTo(size_t bytePos) const { assert(invariant()); Path p; p.mLocalpath = mLocalpath.substr(0, bytePos); p.mPathType = mPathType; assert(p.invariant()); return LocalPathImplementationHelper::buildLocalPath(p); } bool Path::findNextSeparator(size_t& separatorBytePos) const { assert(invariant()); separatorBytePos = mLocalpath.find(LocalPath::localPathSeparator, separatorBytePos); return separatorBytePos != std::string::npos; } std::string Path::serialize() const { std::string d; CacheableWriter w(d); w.serializeu8(static_cast<uint8_t>(mPathType)); w.serializestring(mLocalpath); return d; } bool Path::unserialize(const std::string& data) { CacheableReader r(data); uint8_t type; r.unserializeu8(type); mPathType = static_cast<PathType>(type); return r.unserializestring(mLocalpath); } string_type Path::getRealPath() const { return asPlatformEncoded(false); } auto PathURI::asPlatformEncoded(const bool) const -> string_type { string_type path{mUri}; for (const auto& leaf: mAuxPath) { path.push_back(LocalPath::uriPathSeparator_utf8); path.append(leaf); } return path; } string PathURI::platformEncoded() const { auto src = std::invoke( [this]() -> std::optional<mega::string_type> { if (mAuxPath.empty()) return mUri; return URIHandler::getURI(mUri, mAuxPath); }); if (src) { std::string aux; LocalPath::local2path(&src.value(), &aux, false); return aux; } return toPath(false); } bool PathURI::empty() const { return mUri.empty(); } void PathURI::clear() { mUri.clear(); mAuxPath.clear(); } LocalPath PathURI::leafName() const { if (mAuxPath.size()) { std::string aux; LocalPath::local2path(&mAuxPath.back(), &aux, false); return LocalPath::fromRelativePath(aux); } else if (std::optional<string_type> optionalName = URIHandler::getName(mUri); optionalName.has_value()) { std::string aux; LocalPath::local2path(&optionalName.value(), &aux, false); return LocalPath::fromRelativePath(aux); } return {}; } std::string PathURI::leafOrParentName() const { if (mAuxPath.size()) { std::string aux; LocalPath::local2path(&mAuxPath.back(), &aux, false); return aux; } else { std::optional<string_type> optionalName = URIHandler::getName(mUri); if (optionalName.has_value()) { std::string aux; LocalPath::local2path(&optionalName.value(), &aux, false); return aux; } } return {}; } void PathURI::append(const LocalPath& additionalPath) { assert(!additionalPath.isAbsolute() && !additionalPath.isURI()); mAuxPath.back().append(additionalPath.asPlatformEncoded(false)); } void PathURI::appendWithSeparator(const LocalPath& additionalPath, const bool) { assert(!additionalPath.isAbsolute() && !additionalPath.isURI()); const auto auxPath = additionalPath.toPath(false); auto leaves = splitString<std::vector<std::string>>(auxPath, LocalPath::localPathSeparator_utf8); for (const auto& leaf: leaves) { string_type auxLeaf; LocalPath::path2local(&leaf, &auxLeaf); mAuxPath.emplace_back(auxLeaf); } } void PathURI::prependWithSeparator(const LocalPath&) { LOG_err << "Invalid operation for URI Path (prependWithSeparator)"; assert(false); } LocalPath PathURI::prependNewWithSeparator(const LocalPath&) const { LOG_err << "Invalid operation for URI Path (prependNewWithSeparator)"; assert(false); return LocalPathImplementationHelper::buildLocalPath(*this); } void PathURI::trimNonDriveTrailingSeparator() { LOG_err << "Invalid operation for URI Path (trimNonDriveTrailingSeparator)"; assert(false); return; } bool PathURI::findPrevSeparator(size_t& separatorBytePos, const FileSystemAccess&) const { LOG_err << "Invalid operation for URI Path (findPrevSeparator)"; separatorBytePos = std::string::npos; assert(false); return false; } bool PathURI::beginsWithSeparator() const { LOG_err << "Invalid operation for URI Path (beginsWithSeparator)"; assert(false); return false; } bool PathURI::endsInSeparator() const { return mAuxPath.empty(); } size_t PathURI::getLeafnameByteIndex() const { LOG_err << "Invalid operation for URI Path (getLeafnameByteIndex)"; assert(false); return 0; } LocalPath PathURI::subpathFrom(const size_t) const { LOG_err << "Invalid operation for URI Path (subpathFrom)"; assert(false); return LocalPath{}; } void PathURI::changeLeaf(const LocalPath& newLeaf) { if (newLeaf.isAbsolute() || newLeaf.isURI()) { LOG_err << "Invalid parameter type (appendWithSeparator)"; assert(false); return; } if (auto leaves = splitString<std::vector<std::string>>(newLeaf.toPath(false), LocalPath::localPathSeparator); leaves.size() > 1) { LOG_err << "Invalid newLeaf (contains multiple leaves)"; assert(false); return; } if (mAuxPath.size()) { mAuxPath.pop_back(); } else if (const auto uri = URIHandler::getParentURI(mUri); uri.has_value()) { mUri = uri.value(); } else { LOG_err << "Error change leaf with uri"; assert(false && "Error change leaf with uri"); } mAuxPath.emplace_back(newLeaf.asPlatformEncoded(false)); } LocalPath PathURI::parentPath() const { PathURI newPathUri{*this}; newPathUri.removeLastElement(); return LocalPathImplementationHelper::buildLocalPath(newPathUri); } LocalPath PathURI::insertFilenameSuffix(const std::string& suffix) const { PathURI newPathUri{*this}; auto auxPathName = newPathUri.leafName(); assert(!auxPathName.isRootPath() && !auxPathName.isURI()); newPathUri.changeLeaf(auxPathName.insertFilenameSuffix(suffix)); return LocalPathImplementationHelper::buildLocalPath(newPathUri); } std::string PathURI::toPath(const bool) const { std::string aux; string_type name = asPlatformEncoded(false); LocalPath::local2path(&name, &aux, false); return aux; } std::string PathURI::toName(const FileSystemAccess&) const { return toPath(false); } bool PathURI::isRootPath() const { return mAuxPath.empty(); } bool PathURI::extension(std::string& extension) const { return extensionOf(leafName().toPath(false), extension); } std::string PathURI::extension() const { return extensionOf(toPath(false)); } bool PathURI::related(const LocalPath&) const { LOG_err << "Invalid operation for URI Path (related)"; assert(false); return false; } bool PathURI::invariant() const { return true; } void PathURI::removeLastElement() { if (mAuxPath.size()) { mAuxPath.pop_back(); } else if (std::optional<string_type> parentPath = URIHandler::getParentURI(mUri); parentPath.has_value()) { mUri = parentPath.value(); } } std::string PathURI::serialize() const { std::string d; CacheableWriter w(d); uint8_t type = static_cast<uint8_t>(PathType::URI_PATH); w.serializeu8(type); std::string aux; LocalPath::local2path(&mUri, &aux, false); w.serializestring(aux); uint32_t numElements = static_cast<uint32_t>(mAuxPath.size()); // URI + leaves w.serializeu32(numElements); for (const auto& leaf: mAuxPath) { LocalPath::local2path(&leaf, &aux, false); w.serializestring(aux); } return d; } bool PathURI::unserialize(const std::string& data) { bool success = true; CacheableReader r(data); uint8_t type; r.unserializeu8(type); assert(type == static_cast<uint8_t>(PathType::URI_PATH)); std::string aux; success = r.unserializestring(aux); LocalPath::path2local(&aux, &mUri); uint32_t numElements; success = r.unserializeu32(numElements); for (uint32_t i = 0; i < numElements; ++i) { success = r.unserializestring(aux); string_type leaf; LocalPath::path2local(&aux, &leaf); mAuxPath.emplace_back(leaf); } return success; } bool PathURI::findNextSeparator(size_t& separatorBytePos) const { separatorBytePos = getRealPath().find(LocalPath::localPathSeparator, separatorBytePos); return separatorBytePos != std::string::npos; } string_type PathURI::getRealPath() const { string_type path{mUri}; auto pathOptional = URIHandler::getPath(mUri); if (pathOptional.has_value()) { path = pathOptional.value(); } for (const auto& leaf: mAuxPath) { path.push_back(LocalPath::localPathSeparator); path.append(leaf); } return path; } } ���������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/log_level.cpp�����������������������������������������������������������������������0000664�0000000�0000000�00000001643�15162662266�0015716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <map> #include <mega/log_level.h> #include <mega/utils.h> namespace mega { LogLevel toLogLevel(const std::string& level) { static const std::map<std::string, LogLevel> levels = { #define DEFINE_LOG_LEVEL_ENTRY(name) \ {Utils::toUpperUtf8(#name), log ## name}, DEFINE_LOG_LEVELS(DEFINE_LOG_LEVEL_ENTRY) #undef DEFINE_LOG_LEVEL_ENTRY }; // levels auto i = levels.find(level); if (i != levels.end()) return i->second; // Assume some sane default. return logInfo; } const char* toString(LogLevel level) { static const std::map<LogLevel, std::string> strings = { #define DEFINE_LOG_LEVEL_ENTRY(name) \ {log ## name, Utils::toUpperUtf8(#name)}, DEFINE_LOG_LEVELS(DEFINE_LOG_LEVEL_ENTRY) #undef DEFINE_LOG_LEVEL_ENTRY }; // strings if (auto i = strings.find(level); i != strings.end()) return i->second.c_str(); return "N/A"; } } // mega ���������������������������������������������������������������������������������������������sdk-10.11.0/src/logging.cpp�������������������������������������������������������������������������0000664�0000000�0000000�00000011412�15162662266�0015367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file logging.cpp * @brief Logging class * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ #include "mega/logging.h" #include <ctime> namespace mega { ExternalLogger& getExternalLogger() { // Leaky Singleton to expand the lifetime of the ExternalLogger object. // This will prevent the race condition during abnormal exit in multi-threaded environment // Note: If SDK was used as a dynamic library, this will cause memory leak, // However, this is a cheap price to pay for solving race conditions. // This approach is quite popular in several open-source projects, such as Glog, Chrome etc. static ExternalLogger* const instance = new ExternalLogger; return *instance; } ExclusiveLogger& getExclusiveLogger() { // Same Leaky Singleton static ExclusiveLogger* const instance = new ExclusiveLogger; return *instance; } std::atomic<Logger*> SimpleLogger::logger = &getExternalLogger(); // by the default, display logs with level equal or less than logInfo std::atomic<LogLevel> SimpleLogger::logCurrentLevel{logInfo}; #ifndef ENABLE_LOG_PERFORMANCE std::string SimpleLogger::getTime() { char ts[50]; time_t currentTime = std::time(NULL); std::tm tm{}; #ifdef WIN32 gmtime_s(&tm, ¤tTime); #else gmtime_r(¤tTime, &tm); #endif if (std::strftime(ts, sizeof(ts), "%H:%M:%S", &tm)) return ts; return {}; } #endif std::ostream& operator<< (std::ostream& ostr, const std::error_code &value) { return ostr << value.category().name() << ": " << value.message(); } std::ostream& operator<< (std::ostream& ostr, const std::system_error &se) { return ostr << se.code().category().name() << ": " << se.what(); } ExternalLogger::ExternalLogger() { logToConsole = false; } ExternalLogger::~ExternalLogger() {} void ExternalLogger::addMegaLogger(void* id, LogCallback lc) { std::lock_guard<std::recursive_mutex> g(mutex); megaLoggers[id] = lc; } void ExternalLogger::removeMegaLogger(void* id) { std::lock_guard<std::recursive_mutex> g(mutex); megaLoggers.erase(id); } void ExternalLogger::setLogToConsole(bool enable) { this->logToConsole = enable; } void ExternalLogger::log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0 #endif ) { if (!time) { time = ""; } if (!source) { source = ""; } if (!message) { message = ""; } lock_guard<std::recursive_mutex> g(mutex); // solve the mystery of why the mutex is recursive // if we hit the assert, it's due to logging from inside the processing of the logging? assert(!alreadyLogging); alreadyLogging = true; for (auto& logger : megaLoggers) { logger.second(time, loglevel, source, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, numberMessages #endif ); if (useOnlyFirstMegaLogger) { break; } } if (logToConsole) { std::cout << "[" << time << "][" << SimpleLogger::toStr((LogLevel)loglevel) << "] "; if (message) std::cout << message; #ifdef ENABLE_LOG_PERFORMANCE for (unsigned i = 0; i < numberMessages; ++i) { std::cout.write(directMessages[i], static_cast<std::streamsize>(directMessagesSizes[i])); } #endif std::cout << std::endl; } alreadyLogging = false; } void ExclusiveLogger::log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0 #endif ) { if (!time) { time = ""; } if (!source) { source = ""; } if (!message) { message = ""; } exclusiveCallback(time, loglevel, source, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, numberMessages #endif ); } } // namespace ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/mediafileattribute.cpp��������������������������������������������������������������0000664�0000000�0000000�00000103751�15162662266�0017614�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file mediafileattribute.cpp * @brief Classes for file attributes fetching * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/mediafileattribute.h" #include "mega/logging.h" #include "mega/base64.h" #include "mega/command.h" #include "mega/megaclient.h" #include "mega/megaapp.h" #ifdef USE_MEDIAINFO #include "MediaInfo/MediaInfo.h" #include "ZenLib/Ztring.h" #endif namespace mega { #define MEDIA_INFO_BUILD 1 // Increment this anytime we change the way we use mediainfo, eq query new or different fields etc. Needs to be coordinated with the way the webclient works also. #ifdef USE_MEDIAINFO uint32_t GetMediaInfoVersion() { static uint32_t version = 0; if (version == 0) { std::string s = ZenLib::Ztring(MediaInfoLib::MediaInfo::Option_Static(__T("Info_Version")).c_str()).To_Local(); // eg. __T("MediaInfoLib - v17.10") unsigned column = 1; for (size_t i = s.size(); i--; ) { if (is_digit(static_cast<unsigned>(s[i]))) { version += column * static_cast<uint32_t>(s[i] - '0'); column *= 10; } else if (s[i] == 'v') { break; } } assert(version != 0); } return version; } MediaFileInfo::MediaFileInfo() : mediaCodecsRequested(false) , mediaCodecsReceived(false) , mediaCodecsFailed(false) , downloadedCodecMapsVersion(0) { LOG_debug << "MediaInfo version: " << GetMediaInfoVersion(); } void MediaFileInfo::requestCodecMappingsOneTime(MegaClient* client, const LocalPath& ifSuitableFilename) { if (!mediaCodecsReceived && !mediaCodecsRequested) { if (!ifSuitableFilename.empty()) { string ext; if (!client->fsaccess->getextension(ifSuitableFilename, ext) || !MediaProperties::isMediaFilenameExt(ext)) { return; } } LOG_debug << "Requesting code mappings"; client->queueCommand( new CommandMediaCodecs(client, &MediaFileInfo::onCodecMappingsReceiptStatic)); mediaCodecsRequested = true; } } static void ReadShortFormats(std::vector<MediaFileInfo::MediaCodecs::shortformatrec>& vec, JSON& json); unsigned MediaFileInfo::Lookup(const std::string& name, std::map<std::string, unsigned>& data, unsigned notfoundvalue) { size_t seppos = name.find(" / "); if (seppos != std::string::npos) { // CodecId can contain a list in order of preference, separated by " / " size_t pos = 0; while (seppos != std::string::npos) { unsigned result = MediaFileInfo::Lookup(name.substr(pos, seppos), data, notfoundvalue); if (result != notfoundvalue) return result; pos = seppos + 3; seppos = name.find(" / ", pos); } return MediaFileInfo::Lookup(name.substr(pos), data, notfoundvalue); } std::map<std::string, unsigned>::iterator i = data.find(name); return i == data.end() ? notfoundvalue : i->second; } byte MediaFileInfo::LookupShortFormat(unsigned containerid, unsigned videocodecid, unsigned audiocodecid) { for (size_t i = mediaCodecs.shortformats.size(); i--; ) { // only 256 entries max, so iterating will be very quick MediaCodecs::shortformatrec& r = mediaCodecs.shortformats[i]; if (r.containerid == containerid && r.videocodecid == videocodecid && r.audiocodecid == audiocodecid) { return r.shortformatid; } } return 0; // 0 indicates an exotic combination, which requires attribute 9 } void MediaFileInfo::ReadIdRecords(std::map<std::string, unsigned>& data, JSON& json) { if (json.enterarray()) { while (json.enterarray()) { assert(json.isnumeric()); m_off_t id = json.getint(); std::string name; if (json.storeobject(&name) && id > 0) { data[name] = (unsigned) id; } json.leavearray(); } json.leavearray(); } } static void ReadShortFormats(std::vector<MediaFileInfo::MediaCodecs::shortformatrec>& vec, JSON& json) { bool working = json.enterarray(); if (working) { working = json.enterarray(); while (working) { MediaFileInfo::MediaCodecs::shortformatrec rec; unsigned id = static_cast<unsigned>(atoi(json.getvalue())); assert(id < 256); std::string a, b, c; working = json.storeobject(&a) && json.storeobject(&b) && json.storeobject(&c); if (working) { rec.shortformatid = byte(id); rec.containerid = static_cast<unsigned>(atoi(a.c_str())); rec.videocodecid = static_cast<unsigned>(atoi(b.c_str())); rec.audiocodecid = static_cast<unsigned>(atoi(c.c_str())); vec.push_back(rec); } json.leavearray(); working = json.enterarray(); } json.leavearray(); } } void MediaFileInfo::onCodecMappingsReceiptStatic(MegaClient* client, JSON& json, int codecListVersion) { client->mediaFileInfo.onCodecMappingsReceipt(client, json, codecListVersion); } void MediaFileInfo::onCodecMappingsReceipt(MegaClient* client, JSON& json, int codecListVersion) { if (codecListVersion < 0) { LOG_err << "Error getting media codec mappings"; mediaCodecsFailed = true; queuedForDownloadTranslation.clear(); } else { LOG_debug << "Media codec mappings correctly received"; downloadedCodecMapsVersion = static_cast<uint32_t>(codecListVersion); assert(downloadedCodecMapsVersion < 10000); json.enterarray(); ReadIdRecords(mediaCodecs.containers, json); ReadIdRecords(mediaCodecs.videocodecs, json); ReadIdRecords(mediaCodecs.audiocodecs, json); ReadShortFormats(mediaCodecs.shortformats, json); json.leavearray(); mediaCodecsReceived = true; // update any download transfers we already processed for (size_t i = queuedForDownloadTranslation.size(); i--; ) { queuedvp& q = queuedForDownloadTranslation[i]; sendOrQueueMediaPropertiesFileAttributesForExistingFile(q.vp, q.fakey, client, q.handle.nodeHandle()); } queuedForDownloadTranslation.clear(); } // resume any upload transfers that were waiting for this for (std::map<UploadHandle, queuedvp>::iterator i = uploadFileAttributes.begin(); i != uploadFileAttributes.end(); ) { UploadHandle th = i->second.handle.uploadHandle(); ++i; // the call below may remove this item from the map // indicate that file attribute 8 can be retrieved now, allowing the transfer to complete if (auto uploadFAPtr = client->fileAttributesUploading.lookupExisting(th)) { if (auto faPtr = uploadFAPtr->pendingfa.lookupExisting(fatype(fa_media))) { faPtr->valueIsSet = true; } } client->checkfacompletion(th); } client->app->mediadetection_ready(); } unsigned MediaFileInfo::queueMediaPropertiesFileAttributesForUpload(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, UploadHandle uploadHandle, Transfer* transfer) { if (mediaCodecsFailed) { return 0; // we can't do it - let the transfer complete anyway } MediaFileInfo::queuedvp q; q.handle = NodeOrUploadHandle(uploadHandle); q.vp = vp; memcpy(q.fakey, fakey, sizeof(q.fakey)); uploadFileAttributes[uploadHandle] = q; LOG_debug << "Media attribute enqueued for upload"; // indicate we have this attribute. If we have the codec mappings, it can be encoded. If not, hold the transfer until we do have it client->fileAttributesUploading.setFileAttributePending(uploadHandle, fatype(fa_media), transfer, mediaCodecsReceived); return 1; } void MediaFileInfo::sendOrQueueMediaPropertiesFileAttributesForExistingFile(MediaProperties& vp, uint32_t fakey[4], MegaClient* client, NodeHandle fileHandle) { if (mediaCodecsFailed) { return; // we can't do it } if (!mediaCodecsReceived) { MediaFileInfo::queuedvp q; q.handle = NodeOrUploadHandle(fileHandle); q.vp = vp; memcpy(q.fakey, fakey, sizeof(q.fakey)); queuedForDownloadTranslation.push_back(q); LOG_debug << "Media attribute enqueued for existing file"; } else { LOG_debug << "Sending media attributes"; std::string mediafileattributes = vp.convertMediaPropertyFileAttributes(fakey, client->mediaFileInfo); client->putFileAttributes(fileHandle.as8byte(), fa_media, mediafileattributes.c_str(), 0); } } void MediaFileInfo::addUploadMediaFileAttributes(UploadHandle uploadhandle, std::string* s) { std::map<UploadHandle, MediaFileInfo::queuedvp>::iterator i = uploadFileAttributes.find(uploadhandle); if (i != uploadFileAttributes.end()) { if (!mediaCodecsFailed) { if (!s->empty()) { *s += "/"; } *s += i->second.vp.convertMediaPropertyFileAttributes(i->second.fakey, *this); LOG_debug << "Media attributes added to putnodes"; } uploadFileAttributes.erase(i); } } #endif // USE_MEDIAINFO // ----------------------------------------- xxtea encryption / decryption -------------------------------------------------------- static uint32_t endianDetectionValue = 0x01020304; inline bool DetectBigEndian() { return 0x01 == *(byte*)&endianDetectionValue; } inline uint32_t EndianConversion32(uint32_t x) { return ((x & 0xff000000u) >> 24) | ((x & 0x00ff0000u) >> 8) | ((x & 0x0000ff00u) << 8) | ((x & 0x000000ffu) << 24); } inline void EndianConversion32(uint32_t* v, unsigned vlen) { for (; vlen--; ++v) *v = EndianConversion32(*v); } uint32_t DELTA = 0x9E3779B9; inline uint32_t mx(uint32_t sum, uint32_t y, uint32_t z, uint32_t p, uint32_t e, const uint32_t key[4]) { return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)); } void xxteaEncrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv) { if (endianConv) { if (DetectBigEndian()) { EndianConversion32(v, vlen); } else { // match webclient EndianConversion32(key, 4); } } uint32_t n = vlen - 1; uint32_t z = v[n]; uint32_t q = 6 + 52 / vlen; uint32_t sum = 0; for (; q > 0; --q) { sum += DELTA; uint32_t e = (sum >> 2) & 3; for (unsigned p = 0; p < n; ++p) { uint32_t y = v[p + 1]; z = v[p] = v[p] + mx(sum, y, z, p, e, key); } uint32_t y = v[0]; z = v[n] = v[n] + mx(sum, y, z, n, e, key); } if (endianConv) { if (DetectBigEndian()) { EndianConversion32(v, vlen); } else { EndianConversion32(key, 4); } } } void xxteaDecrypt(uint32_t* v, uint32_t vlen, uint32_t key[4], bool endianConv) { if (endianConv) { if (DetectBigEndian()) { EndianConversion32(v, vlen); } else { EndianConversion32(key, 4); } } uint32_t n = vlen - 1; uint32_t y = v[0]; uint32_t q = 6 + 52 / vlen; uint32_t sum = q * DELTA; for (; sum != 0; sum -= DELTA) { uint32_t e = (sum >> 2) & 3; for (unsigned p = n; p > 0; --p) { uint32_t z = v[p - 1]; y = v[p] = v[p] - mx(sum, y, z, p, e, key); } uint32_t z = v[n]; y = v[0] = v[0] - mx(sum, y, z, 0, e, key); } if (endianConv) { if (DetectBigEndian()) { EndianConversion32(v, vlen); } else { EndianConversion32(key, 4); } } } std::string formatfileattr(uint32_t id, byte* data, unsigned datalen, uint32_t fakey[4]) { assert(datalen % 4 == 0); xxteaEncrypt((uint32_t*)data, datalen/4, fakey); std::string encb64; Base64::btoa(std::string((char*)data, datalen), encb64); std::ostringstream result; result << id << "*" << encb64; return result.str(); } // ----------------------------------------- MediaProperties -------------------------------------------------------- MediaProperties::MediaProperties() : shortformat(UNKNOWN_FORMAT) , width(0) , height(0) , fps(0) , playtime(0) , containerid(0) , videocodecid(0) , audiocodecid(0) , is_VFR(false) , no_audio(false) { } MediaProperties::MediaProperties(const std::string& deserialize) { CacheableReader r(deserialize); r.unserializebyte(shortformat); r.unserializeu32(width); r.unserializeu32(height); r.unserializeu32(fps); r.unserializeu32(playtime); r.unserializeu32(containerid); r.unserializeu32(videocodecid); r.unserializeu32(audiocodecid); r.unserializebool(is_VFR); r.unserializebool(no_audio); } std::string MediaProperties::serialize() { std::string s; CacheableWriter r(s); r.serializebyte(shortformat); r.serializeu32(width); r.serializeu32(height); r.serializeu32(fps); r.serializeu32(playtime); r.serializeu32(containerid); r.serializeu32(videocodecid); r.serializeu32(audiocodecid); r.serializebool(is_VFR); r.serializebool(no_audio); r.serializeexpansionflags(); return s; } bool MediaProperties::isPopulated() { return shortformat != UNKNOWN_FORMAT; } bool MediaProperties::isIdentified() { return isPopulated() && shortformat != NOT_IDENTIFIED_FORMAT; } bool MediaProperties::operator==(const MediaProperties& o) const { return shortformat == o.shortformat && width == o.width && height == o.height && fps == o.fps && playtime == o.playtime && (shortformat || (containerid == o.containerid && videocodecid == o.videocodecid && audiocodecid == o.audiocodecid)); } // shortformat must be 0 if the format is exotic - in that case, container/videocodec/audiocodec must be valid // if shortformat is > 0, container/videocodec/audiocodec are ignored and no attribute 9 is returned. // fakey is an 4-uint32 Array with the file attribute key (the nonce from the file key) std::string MediaProperties::encodeMediaPropertiesAttributes(MediaProperties vp, uint32_t fakey[4]) { vp.width <<= 1; if (vp.width >= 32768) vp.width = ((vp.width - 32768) >> 3) | 1; if (vp.width >= 32768) vp.width = 32767; vp.height <<= 1; if (vp.height >= 32768) vp.height = ((vp.height - 32768) >> 3) | 1; if (vp.height >= 32768) vp.height = 32767; vp.playtime <<= 1; if (vp.playtime >= 262144) vp.playtime = ((vp.playtime - 262200) / 60) | 1; if (vp.playtime >= 262144) vp.playtime = 262143; vp.fps <<= 1; if (vp.fps >= 256) vp.fps = ((vp.fps - 256) >> 3) | 1; if (vp.fps >= 256) vp.fps = 255; // LE code below byte v[8]; v[7] = vp.shortformat; v[6] = byte(vp.playtime >> 10); v[5] = byte((vp.playtime >> 2) & 255); v[4] = byte(((vp.playtime & 3) << 6) + (vp.fps >> 2)); v[3] = byte(((vp.fps & 3) << 6) + ((vp.height >> 9) & 63)); v[2] = byte((vp.height >> 1) & 255); v[1] = byte(((vp.width >> 8) & 127) + ((vp.height & 1) << 7)); v[0] = byte(vp.width & 255); std::string result = formatfileattr(fa_media, v, sizeof v, fakey); if (!vp.shortformat) // exotic combination of container/codecids { LOG_debug << "The file requires extended media attributes"; memset(v, 0, sizeof v); v[3] = (vp.audiocodecid >> 4) & 255; v[2] = static_cast<byte>(((vp.videocodecid >> 8) & 15) + ((vp.audiocodecid & 15) << 4)); v[1] = vp.videocodecid & 255; v[0] = byte(vp.containerid); result.append("/"); result.append(formatfileattr(fa_mediaext, v, sizeof v, fakey)); } return result; } MediaProperties MediaProperties::decodeMediaPropertiesAttributes(const std::string& attrs, uint32_t fakey[4]) { MediaProperties r; int ppo = Node::hasfileattribute(&attrs, fa_media); int pos = ppo - 1; if (ppo && pos + 3 + 11 <= (int)attrs.size()) { std::string binary; Base64::atob(attrs.substr(static_cast<size_t>(pos + 3), 11), binary); assert(binary.size() == 8); byte v[8]; memcpy(v, binary.data(), std::min<size_t>(sizeof v, binary.size())); xxteaDecrypt((uint32_t*)v, sizeof(v)/4, fakey); r.width = static_cast<uint32_t>((v[0] >> 1) + ((v[1] & 127) << 7)); if (v[0] & 1) r.width = (r.width << 3) + 16384; r.height = static_cast<uint32_t>(v[2] + ((v[3] & 63) << 8)); if (v[1] & 128) r.height = (r.height << 3) + 16384; r.fps = static_cast<uint32_t>((v[3] >> 7) + ((v[4] & 63) << 1)); if (v[3] & 64) r.fps = (r.fps << 3) + 128; r.playtime = static_cast<uint32_t>((v[4] >> 7) + (v[5] << 1) + (v[6] << 9)); if (v[4] & 64) r.playtime = r.playtime * 60 + 131100; r.shortformat = v[7]; if (!r.shortformat) { ppo = Node::hasfileattribute(&attrs, fa_mediaext); pos = ppo - 1; if (ppo && pos + 3 + 11 <= (int)attrs.size()) { Base64::atob(attrs.substr(static_cast<size_t>(pos + 3), 11), binary); assert(binary.size() == 8); memcpy(v, binary.data(), std::min<size_t>(sizeof v, binary.size())); xxteaDecrypt((uint32_t*)v, sizeof(v) / 4, fakey); r.containerid = v[0]; r.videocodecid = static_cast<uint32_t>(v[1] + ((v[2] & 15) << 8)); r.audiocodecid = static_cast<uint32_t>((v[2] >> 4) + (v[3] << 4)); } } } return r; } #ifdef USE_MEDIAINFO const char* MediaProperties::supportedformatsMediaInfoAudio() { // list compiled from https://github.com/MediaArea/MediaInfoLib/blob/v19.09/Source/Resource/Text/DataBase/Format.csv static constexpr char audioformats[] = ".aa3.aac.aacp.aaf.ac3.act.adts.aes3.aif.aifc.aiff.als.amr.ape.at3.at9.atrac.atrac3.atrac9.au.caf" ".dd+.dts.dtshd.eac3.ec3.fla.flac.kar.la.m1a.m2a.mac.mid.midi.mlp.mp1.mp2.mp3.mpa.mpa1.mpa2.mtv" ".oga.ogg.oma.omg.opus.mpc.mp+.qcp.ra.rka.s3m.shn.spdif.spx.tak.thd.vqf.w64.wav.wma.wv.wvc.xm."; static_assert(sizeof(audioformats) > 1 && audioformats[0] == '.' && audioformats[sizeof(audioformats) - 2] == '.', "Supported audio formats need to start and end with '.'"); return audioformats; } bool MediaProperties::isMediaFilenameExtAudio(const std::string& ext) { for (const char* ptr = supportedformatsMediaInfoAudio(); (ptr = strstr(ptr, ext.c_str())) != nullptr; ptr += ext.size()) { if (ptr[ext.size()] == '.') { return true; } } return false; } const char* MediaProperties::supportedformatsMediaInfo() { static constexpr char nonaudioformats[] = ".264.265.3g2.3ga.3gp.3gpa.3gpp.3gpp2.apl.avc.avi.dde.divx.evo.f4a.f4b.f4v.gvi.h261.h263.h264.h265.hevc" ".isma.ismt.ismv.ivf.jpm.k3g.m1v.m2p.m2s.m2t.m2v.m4a.m4b.m4p.m4s.m4t.m4v.mk3d.mka.mks.mkv.mov.mp1v.mp2v" ".mp4.mp4v.mpeg.mpg.mpgv.mpv.mqv.ogm.ogv.qt.sls.tmf.trp.ts.ty.vc1.vob.vr.webm.wmv."; static_assert(sizeof(nonaudioformats) > 1 && nonaudioformats[0] == '.' && nonaudioformats[sizeof(nonaudioformats) - 2] == '.', "Supported formats need to start and end with '.'"); static const std::string allFormats = string(nonaudioformats) + supportedformatsMediaInfoAudio(); // this is thread safe in C++11 return allFormats.c_str(); } bool MediaProperties::isMediaFilenameExt(const std::string& ext) { for (const char* ptr = MediaProperties::supportedformatsMediaInfo(); NULL != (ptr = strstr(ptr, ext.c_str())); ptr += ext.size()) { if (ptr[ext.size()] == '.') { return true; } } return false; } template<class T> StringPair MediaProperties::getCoverFromId3v2(const T& file) { MediaInfoLib::MediaInfo mi; mi.Option(__T("Cover_Data"), __T("base64")); // set this _before_ opening the file ZenLib::Ztring zFile(file.c_str()); // make this work with both narrow and wide std strings if (!mi.Open(zFile)) { LOG_err << "MediaInfo: could not open local file to retrieve Cover: " << zFile.To_UTF8(); return std::make_pair(std::string(), std::string()); } // MIME (type/subtype) of the cover image. // According to id3v2 specs, it is "always an ISO-8859-1 text string". // Supported values are one of {"image/jpeg", "image/png"}. // However, allow "image/jpg" variant too because it occured for flac (MediaInfo bug?). const MediaInfoLib::String& coverMime = mi.Get(MediaInfoLib::Stream_General, 0, __T("Cover_Mime")); std::string syntheticExt; if (coverMime == __T("image/jpeg") || coverMime == __T("image/jpg")) { syntheticExt = "jpg"; } else if (coverMime == __T("image/png")) { syntheticExt = "png"; } else { if (!coverMime.empty()) { LOG_warn << "MediaInfo: Cover_Mime contained garbage, ignored Cover for file " << zFile.To_UTF8(); } return std::make_pair(std::string(), std::string()); } // Cover data: binary data, base64 encoded. ZenLib::Ztring coverData = mi.Get(MediaInfoLib::Stream_General, 0, __T("Cover_Data")); std::string data = coverData.To_UTF8(); if (data.empty()) { return std::make_pair(std::string(), std::string()); } data = mega::Base64::atob(data); return std::make_pair(data, syntheticExt); } // forward-declare this so the compiler will generate it (same conditional compilation as LocalPath::localpath) #if defined(_WIN32) template StringPair MediaProperties::getCoverFromId3v2(const std::wstring&); #else template StringPair MediaProperties::getCoverFromId3v2(const std::string&); #endif static inline uint32_t coalesce(uint32_t a, uint32_t b) { return a != 0 ? a : b; } bool MediaFileInfo::timeToRetryMediaPropertyExtraction(const std::string& fileattributes, uint32_t fakey[4]) { // Check if we should retry video property extraction, due to previous failure with older library MediaProperties vp = MediaProperties::decodeMediaPropertiesAttributes(fileattributes, fakey); if (vp.isIdentified()) { if (vp.fps < MEDIA_INFO_BUILD) { LOG_debug << "Media extraction retry needed with a newer build. Old: " << vp.fps << " New: " << MEDIA_INFO_BUILD; return true; } if (vp.width < GetMediaInfoVersion()) { LOG_debug << "Media extraction retry needed with a newer MediaInfo version. Old: " << vp.width << " New: " << GetMediaInfoVersion(); return true; } if (vp.playtime < downloadedCodecMapsVersion) { LOG_debug << "Media extraction retry needed with newer code mappings. Old: " << vp.playtime << " New: " << downloadedCodecMapsVersion; return true; } } return false; } bool mediaInfoOpenFileWithLimits(MediaInfoLib::MediaInfo& mi, LocalPath& filename, FileAccess* fa, unsigned maxBytesToRead, unsigned maxSeconds) { if (!fa->fopen(filename, OPEN_RDONLY, FSLogging::logOnError)) { LOG_err << "could not open local file for mediainfo"; return false; } m_off_t filesize = fa->size; size_t totalBytesRead = 0; mi.Open_Buffer_Init(static_cast<ZenLib::int64u>(filesize), 0); m_off_t readpos = 0; m_time_t startTime = 0; bool hasVideo = false; bool vidDuration = false; for (;;) { byte buf[30 * 1024]; unsigned n = unsigned(std::min<m_off_t>(filesize - readpos, sizeof(buf))); if (n == 0) { break; } if (totalBytesRead > maxBytesToRead || (startTime != 0 && ((m_time() - startTime) > maxSeconds))) { if (hasVideo && vidDuration) { break; } LOG_warn << "could not extract mediainfo data within reasonable limits"; mi.Open_Buffer_Finalize(); fa->closef(); return false; } if (!fa->frawread(buf, n, readpos, true, FSLogging::logOnError)) { LOG_err << "could not read local file"; mi.Open_Buffer_Finalize(); fa->closef(); return false; } readpos += n; if (startTime == 0) { startTime = m_time(); } totalBytesRead += n; size_t bitfield = mi.Open_Buffer_Continue((byte*)buf, n); // flag bitmask --> 1:accepted, 2:filled, 4:updated, 8:finalised bool accepted = bitfield & 1; bool filled = bitfield & 2; bool finalised = bitfield & 8; if (filled || finalised) { break; } if (accepted) { hasVideo = 0 < mi.Count_Get(MediaInfoLib::Stream_Video, 0); bool hasAudio = 0 < mi.Count_Get(MediaInfoLib::Stream_Audio, 0); vidDuration = !mi.Get(MediaInfoLib::Stream_Video, 0, __T("Duration"), MediaInfoLib::Info_Text).empty(); bool audDuration = !mi.Get(MediaInfoLib::Stream_Audio, 0, __T("Duration"), MediaInfoLib::Info_Text).empty(); if (hasVideo && hasAudio && vidDuration && audDuration) { break; } } m_off_t requestPos = static_cast<m_off_t>(mi.Open_Buffer_Continue_GoTo_Get()); if (requestPos != (m_off_t)-1) { readpos = requestPos; mi.Open_Buffer_Init(static_cast<ZenLib::int64u>(filesize), static_cast<ZenLib::int64u>(readpos)); } } mi.Open_Buffer_Finalize(); fa->closef(); return true; } void MediaProperties::extractMediaPropertyFileAttributes(LocalPath& localFilename, FileSystemAccess* fsa) { if (auto tmpfa = fsa->newfileaccess()) { try { MediaInfoLib::MediaInfo minfo; if (mediaInfoOpenFileWithLimits(minfo, localFilename, tmpfa.get(), 10485760, 3)) // we can read more off local disk { if (!minfo.Count_Get(MediaInfoLib::Stream_General, 0)) { LOG_warn << "mediainfo: no general information found in file"; } if (!minfo.Count_Get(MediaInfoLib::Stream_Video, 0)) { LOG_warn << "mediainfo: no video information found in file"; } if (!minfo.Count_Get(MediaInfoLib::Stream_Audio, 0)) { LOG_warn << "mediainfo: no audio information found in file"; no_audio = true; } ZenLib::Ztring gci = minfo.Get(MediaInfoLib::Stream_General, 0, __T("CodecID"), MediaInfoLib::Info_Text); ZenLib::Ztring gf = minfo.Get(MediaInfoLib::Stream_General, 0, __T("Format"), MediaInfoLib::Info_Text); ZenLib::Ztring gd = minfo.Get(MediaInfoLib::Stream_General, 0, __T("Duration"), MediaInfoLib::Info_Text); ZenLib::Ztring vw = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Width"), MediaInfoLib::Info_Text); ZenLib::Ztring vh = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Height"), MediaInfoLib::Info_Text); ZenLib::Ztring vd = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Duration"), MediaInfoLib::Info_Text); ZenLib::Ztring vfr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate"), MediaInfoLib::Info_Text); ZenLib::Ztring vrm = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Mode"), MediaInfoLib::Info_Text); ZenLib::Ztring vci = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("CodecID"), MediaInfoLib::Info_Text); ZenLib::Ztring vcf = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Format"), MediaInfoLib::Info_Text); ZenLib::Ztring vr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Rotation"), MediaInfoLib::Info_Text); ZenLib::Ztring aci = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("CodecID"), MediaInfoLib::Info_Text); ZenLib::Ztring acf = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Format"), MediaInfoLib::Info_Text); ZenLib::Ztring ad = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Duration"), MediaInfoLib::Info_Text); if (vr.To_int32u() == 90 || vr.To_int32u() == 270) { width = vh.To_int32u(); height = vw.To_int32u(); } else { width = vw.To_int32u(); height = vh.To_int32u(); } fps = vfr.To_int32u(); playtime = (coalesce(gd.To_int32u(), coalesce(vd.To_int32u(), ad.To_int32u()))) / 1000; videocodecNames = vci.To_Local(); videocodecFormat = vcf.To_Local(); audiocodecNames = aci.To_Local(); audiocodecFormat = acf.To_Local(); containerName = gci.To_Local(); containerFormat = gf.To_Local(); is_VFR = vrm.To_Local() == "VFR"; // variable frame rate - send through as 0 in fps field if (!fps) { ZenLib::Ztring vrn = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Num"), MediaInfoLib::Info_Text); ZenLib::Ztring vrd = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Den"), MediaInfoLib::Info_Text); uint32_t num = vrn.To_int32u(); uint32_t den = vrd.To_int32u(); if (num > 0 && den > 0) { fps = (num + den / 2) / den; } } if (!fps) { ZenLib::Ztring vro = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Original"), MediaInfoLib::Info_Text); fps = vro.To_int32u(); } if (SimpleLogger::getLogLevel() >= logDebug) { LOG_debug << "MediaInfo on " << localFilename << " | " << vw.To_Local() << " " << vh.To_Local() << " " << vd.To_Local() << " " << vr.To_Local() << " |\"" << gci.To_Local() << "\",\"" << gf.To_Local() << "\",\"" << vci.To_Local() << "\",\"" << vcf.To_Local() << "\",\"" << aci.To_Local() << "\",\"" << acf.To_Local() << "\""; } } } catch (std::exception& e) { LOG_err << "exception caught reading media file attributes: " << e.what(); } catch (...) { LOG_err << "unknown excption caught reading media file attributes"; } } } std::string MediaProperties::convertMediaPropertyFileAttributes(uint32_t fakey[4], MediaFileInfo& mediaInfo) { containerid = mediaInfo.Lookup(containerName, mediaInfo.mediaCodecs.containers, 0); if (!containerid) { containerid = mediaInfo.Lookup(containerFormat, mediaInfo.mediaCodecs.containers, 0); } videocodecid = mediaInfo.Lookup(videocodecNames, mediaInfo.mediaCodecs.videocodecs, 0); if (!videocodecid) { videocodecid = mediaInfo.Lookup(videocodecFormat, mediaInfo.mediaCodecs.videocodecs, 0); } audiocodecid = mediaInfo.Lookup(audiocodecNames, mediaInfo.mediaCodecs.audiocodecs, 0); if (!audiocodecid) { audiocodecid = mediaInfo.Lookup(audiocodecFormat, mediaInfo.mediaCodecs.audiocodecs, 0); } if (!(containerid && ( (videocodecid && width && height && /*(fps || is_VFR) &&*/ (audiocodecid || no_audio)) || (audiocodecid && !videocodecid)))) { LOG_warn << "mediainfo failed to extract media information for this file"; shortformat = NOT_IDENTIFIED_FORMAT; // mediaInfo could not fully identify this file. Maybe a later version can. fps = MEDIA_INFO_BUILD; // updated when we change relevant things in this executable width = GetMediaInfoVersion(); // mediaInfoLib version that couldn't do it. 1710 at time of writing (ie oct 2017 tag) height = 0; playtime = mediaInfo.downloadedCodecMapsVersion; // updated when we add more codec names etc } else { LOG_debug << "mediainfo processed the file correctly"; // attribute 8 valid, and either shortformat specifies a common combination of (containerid, videocodecid, audiocodecid), // or we make an attribute 9 with those values, and set shortformat=0. shortformat = mediaInfo.LookupShortFormat(containerid, videocodecid, audiocodecid); } LOG_debug << "MediaInfo converted: " << (int)shortformat << "," << width << "," << height << "," << fps << "," << playtime << "," << videocodecid << "," << audiocodecid << "," << containerid; std::string mediafileattributes = MediaProperties::encodeMediaPropertiesAttributes(*this, fakey); #ifdef _DEBUG // double check decode is the opposite of encode std::string simServerAttribs = ":" + mediafileattributes; size_t pos = simServerAttribs.find("/"); if (pos != std::string::npos) simServerAttribs.replace(pos, 1, ":"); MediaProperties decVp = MediaProperties::decodeMediaPropertiesAttributes(simServerAttribs, fakey); assert(*this == decVp); #endif return mediafileattributes; } #endif } // namespace �����������������������sdk-10.11.0/src/megaapi.cpp�������������������������������������������������������������������������0000664�0000000�0000000�00000612132�15162662266�0015352�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file megaapi.cpp * @brief Intermediate layer for the MEGA C++ SDK. * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "megaapi.h" #include "mega.h" #include "megaapi_impl.h" #include "megautils.h" #include <mega/fuse/common/service.h> #include <cstdint> namespace { inline const char* nullToEmpty(const char* param) { return param ? param : ""; } } namespace mega { MegaProxy::MegaProxy() { proxyType = PROXY_AUTO; username = NULL; password = NULL; proxyURL = NULL; } MegaProxy::~MegaProxy() { delete username; delete password; delete proxyURL; } void MegaProxy::setProxyType(int newProxyType) { proxyType = newProxyType; } void MegaProxy::setProxyURL(const char* newProxyURL) { if (proxyURL) delete proxyURL; proxyURL = MegaApi::strdup(newProxyURL); } void MegaProxy::setCredentials(const char* newUsername, const char* newPassword) { if (username) delete username; if (password) delete password; username = MegaApi::strdup(newUsername); password = MegaApi::strdup(newPassword); } int MegaProxy::getProxyType() { return proxyType; } const char * MegaProxy::getProxyURL() { return this->proxyURL; } bool MegaProxy::credentialsNeeded() { return (username != NULL); } const char *MegaProxy::getUsername() { return username; } const char *MegaProxy::getPassword() { return password; } MegaStringList *MegaStringList::createInstance() { return new MegaStringListPrivate(); } MegaStringList::~MegaStringList() { } MegaStringList *MegaStringList::copy() const { return NULL; } const char *MegaStringList::get(int) const { return NULL; } int MegaStringList::size() const { return 0; } void MegaStringList::add(const char *) { } MegaStringListMap::MegaStringListMap() { } MegaStringListMap::~MegaStringListMap() { } MegaStringListMap* MegaStringListMap::createInstance() { return new MegaStringListMapPrivate; } MegaStringListMap* MegaStringListMap::copy() const { return nullptr; } const MegaStringList* MegaStringListMap::get(const char*) const { return nullptr; } MegaStringList *MegaStringListMap::getKeys() const { return nullptr; } void MegaStringListMap::set(const char*, const MegaStringList*) { } int MegaStringListMap::size() const { return 0; } MegaStringTable::MegaStringTable() { } MegaStringTable::~MegaStringTable() { } MegaStringTable* MegaStringTable::createInstance() { return new MegaStringTablePrivate; } MegaStringTable* MegaStringTable::copy() const { return nullptr; } void MegaStringTable::append(const MegaStringList*) { } const MegaStringList* MegaStringTable::get(int) const { return nullptr; } int MegaStringTable::size() const { return 0; } MegaNodeList *MegaNodeList::createInstance() { return new MegaNodeListPrivate(); } MegaNodeList::MegaNodeList() { } MegaNodeList::~MegaNodeList() { } MegaNodeList *MegaNodeList::copy() const { return NULL; } MegaNode *MegaNodeList::get(int) const { return NULL; } int MegaNodeList::size() const { return 0; } void MegaNodeList::addNode(MegaNode*) {} MegaTransferList::~MegaTransferList() { } MegaTransfer *MegaTransferList::get(int) { return NULL; } int MegaTransferList::size() { return 0; } MegaContactRequestList::~MegaContactRequestList() { } MegaContactRequestList* MegaContactRequestList::copy() const { return NULL; } MegaContactRequest* MegaContactRequestList::get(int index) { auto self = static_cast<const MegaContactRequestList*>(this); return const_cast<MegaContactRequest*>(self->get(index)); } const MegaContactRequest* MegaContactRequestList::get(int) const { return NULL; } int MegaContactRequestList::size() const { return 0; } MegaUserList::~MegaUserList() { } MegaUserList *MegaUserList::copy() { return NULL; } MegaUser *MegaUserList::get(int) { return NULL; } int MegaUserList::size() { return 0; } MegaUserAlertList::~MegaUserAlertList() { } MegaUserAlertList *MegaUserAlertList::copy() const { return NULL; } MegaUserAlert *MegaUserAlertList::get(int) const { return NULL; } int MegaUserAlertList::size() const { return 0; } void MegaUserAlertList::clear() { } MegaRecentActionBucket::~MegaRecentActionBucket() { } MegaRecentActionBucket* MegaRecentActionBucket::copy() const { return NULL; } int64_t MegaRecentActionBucket::getTimestamp() const { return 0; } const char* MegaRecentActionBucket::getUserEmail() const { return NULL; } MegaHandle MegaRecentActionBucket::getParentHandle() const { return INVALID_HANDLE; } bool MegaRecentActionBucket::isUpdate() const { return false; } bool MegaRecentActionBucket::isMedia() const { return false; } const char* MegaRecentActionBucket::getId() const { return NULL; } const MegaNodeList* MegaRecentActionBucket::getNodes() const { return NULL; } MegaRecentActionBucketList::~MegaRecentActionBucketList() { } MegaRecentActionBucketList* MegaRecentActionBucketList::copy() const { return NULL; } MegaRecentActionBucket* MegaRecentActionBucketList::get(int /*i*/) const { return NULL; } int MegaRecentActionBucketList::size() const { return 0; } MegaShareList::~MegaShareList() { } MegaShare *MegaShareList::get(int) { return NULL; } int MegaShareList::size() { return 0; } const double MegaNode::INVALID_COORDINATE = -200; MegaNode::~MegaNode() { } MegaNode *MegaNode::copy() { return NULL; } int MegaNode::getType() const { return 0; } const char *MegaNode::getName() { return NULL; } const char *MegaNode::getFingerprint() { return NULL; } const char *MegaNode::getOriginalFingerprint() { return NULL; } bool MegaNode::hasCustomAttrs() { return false; } MegaStringList *MegaNode::getCustomAttrNames() { return NULL; } const char *MegaNode::getCustomAttr(const char* /*attrName*/) { return NULL; } int MegaNode::getDuration() { return -1; } bool MegaNode::isFavourite() { return false; } int MegaNode::getLabel() { return 0; } int MegaNode::getWidth() { return -1; } int MegaNode::getHeight() { return -1; } int MegaNode::getShortformat() { return -1; } int MegaNode::getVideocodecid() { return -1; } double MegaNode::getLatitude() { return INVALID_COORDINATE; } double MegaNode::getLongitude() { return INVALID_COORDINATE; } const char* MegaNode::getDescription() { return NULL; } MegaStringList* MegaNode::getTags() { return NULL; } char *MegaNode::getBase64Handle() { return NULL; } int64_t MegaNode::getSize() { return 0; } int64_t MegaNode::getCreationTime() { return 0; } int64_t MegaNode::getModificationTime() { return 0; } MegaHandle MegaNode::getHandle() const { return INVALID_HANDLE; } MegaHandle MegaNode::getRestoreHandle() { return INVALID_HANDLE; } MegaHandle MegaNode::getParentHandle() { return INVALID_HANDLE; } char *MegaNode::getBase64Key() { return NULL; } int64_t MegaNode::getExpirationTime() { return -1; } MegaHandle MegaNode::getPublicHandle() { return INVALID_HANDLE; } MegaNode* MegaNode::getPublicNode() { return NULL; } char* MegaNode::getPublicLink(bool /*includeKey*/) { return NULL; } int64_t MegaNode::getPublicLinkCreationTime() { return 0; } const char * MegaNode::getWritableLinkAuthKey() { return nullptr; } bool MegaNode::isFile() { return false; } bool MegaNode::isFolder() { return false; } bool MegaNode::isRemoved() { return false; } bool MegaNode::isMarkedSensitive() { return false; } bool MegaNode::hasChanged(uint64_t /*changeType*/) { return false; } uint64_t MegaNode::getChanges() { return 0; } bool MegaNode::hasThumbnail() { return false; } bool MegaNode::hasPreview() { return false; } bool MegaNode::isPublic() { return false; } bool MegaNode::isShared() { return false; } bool MegaNode::isOutShare() { return false; } bool MegaNode::isInShare() { return false; } bool MegaNode::isExported() { return false; } bool MegaNode::isExpired() { return false; } bool MegaNode::isTakenDown() { return false; } bool MegaNode::isForeign() { return false; } bool MegaNode::isCreditCardNode() const { return false; } bool MegaNode::isPasswordNode() const { return false; } bool MegaNode::isPasswordManagerNode() const { return false; } MegaNode::CreditCardNodeData* MegaNode::CreditCardNodeData::createInstance(const char* cardNumber, const char* notes, const char* cardHolderName, const char* cvv, const char* expirationDate) { return new MegaNodePrivate::CCNDataPrivate(cardNumber, notes, cardHolderName, cvv, expirationDate); } MegaNode::PasswordNodeData::TotpData* MegaNode::PasswordNodeData::TotpData::createRemovalInstance() { return MegaNodePrivate::PNDataPrivate::TotpDataPrivate::createRemovalInstance(); } MegaNode::PasswordNodeData::TotpData* MegaNode::PasswordNodeData::TotpData::createInstance(const char* sharedSecret, const int expirationTimeSecs, const int hashAlgorithm, const int ndigits) { return new MegaNodePrivate::PNDataPrivate::TotpDataPrivate(sharedSecret, expirationTimeSecs, hashAlgorithm, ndigits); } MegaNode::PasswordNodeData* MegaNode::PasswordNodeData::createInstance(const char* pwd, const char* notes, const char* url, const char* userName, const TotpData* totpData) { return new MegaNodePrivate::PNDataPrivate(pwd, notes, url, userName, totpData); } MegaNode::CreditCardNodeData* MegaNode::getCreditCardData() const { return NULL; } MegaNode::PasswordNodeData* MegaNode::getPasswordData() const { return NULL; } string *MegaNode::getNodeKey() { return NULL; } bool MegaNode::isNodeKeyDecrypted() { return false; } char *MegaNode::getFileAttrString() { return NULL; } void MegaNode::setPrivateAuth(const char *) { return; } MegaNodeList *MegaNode::getChildren() { return NULL; } MegaHandle MegaNode::getOwner() const { return INVALID_HANDLE; } const char* MegaNode::getDeviceId() const { return nullptr; } const char* MegaNode::getS4() const { return nullptr; } char *MegaNode::serialize() { return NULL; } MegaNode *MegaNode::unserialize(const char *d) { if (!d) { return NULL; } string data; data.resize(strlen(d) * 3 / 4 + 3); data.resize(static_cast<size_t>(Base64::atob(d, (byte*)data.data(), int(data.size())))); return MegaNodePrivate::unserialize(&data); } MegaUser::~MegaUser() { } MegaUser *MegaUser::copy() { return NULL; } const char *MegaUser::getEmail() { return NULL; } MegaHandle MegaUser::getHandle() { return INVALID_HANDLE; } int MegaUser::getVisibility() { return 0; } int64_t MegaUser::getTimestamp() { return 0; } bool MegaUser::hasChanged(uint64_t) { return false; } uint64_t MegaUser::getChanges() { return 0; } int MegaUser::isOwnChange() { return 0; } MegaUserAlert::~MegaUserAlert() { } MegaUserAlert *MegaUserAlert::copy() const { return NULL; } unsigned MegaUserAlert::getId() const { return (unsigned)-1; } bool MegaUserAlert::getSeen() const { return false; } bool MegaUserAlert::getRelevant() const { return false; } int MegaUserAlert::getType() const { return -1; } const char *MegaUserAlert::getTypeString() const { return NULL; } MegaHandle MegaUserAlert::getUserHandle() const { return INVALID_HANDLE; } MegaHandle MegaUserAlert::getNodeHandle() const { return INVALID_HANDLE; } MegaHandle MegaUserAlert::getPcrHandle() const { return INVALID_HANDLE; } const char* MegaUserAlert::getEmail() const { return NULL; } const char* MegaUserAlert::getPath() const { return NULL; } const char *MegaUserAlert::getName() const { return NULL; } const char *MegaUserAlert::getHeading() const { return NULL; } const char *MegaUserAlert::getTitle() const { return NULL; } int64_t MegaUserAlert::getNumber(unsigned) const { return -1; } int64_t MegaUserAlert::getTimestamp(unsigned) const { return -1; } const char* MegaUserAlert::getString(unsigned) const { return NULL; } #ifdef ENABLE_CHAT MegaHandle MegaUserAlert::getSchedId() const { return INVALID_HANDLE; } bool MegaUserAlert::hasSchedMeetingChanged(uint64_t) const { return false; } MegaStringList* MegaUserAlert::getUpdatedTitle() const { return NULL; } MegaStringList* MegaUserAlert::getUpdatedTimeZone() const { return NULL; } MegaIntegerList* MegaUserAlert::getUpdatedStartDate() const { return NULL; } MegaIntegerList* MegaUserAlert::getUpdatedEndDate() const { return NULL; } #endif MegaHandle MegaUserAlert::getHandle(unsigned) const { return INVALID_HANDLE; } bool MegaUserAlert::isOwnChange() const { return false; } bool MegaUserAlert::isRemoved() const { return false; } MegaShare::~MegaShare() { } MegaShare *MegaShare::copy() { return NULL; } const char *MegaShare::getUser() { return NULL; } MegaHandle MegaShare::getNodeHandle() { return INVALID_HANDLE; } int MegaShare::getAccess() { return 0; } int64_t MegaShare::getTimestamp() { return 0; } bool MegaShare::isPending() { return false; } bool MegaShare::isVerified() { return false; } MegaRequest::~MegaRequest() { } MegaRequest *MegaRequest::copy() { return NULL; } int MegaRequest::getType() const { return 0; } const char *MegaRequest::getRequestString() const { return NULL; } const char *MegaRequest::toString() const { return NULL; } MegaHandle MegaRequest::getNodeHandle() const { return INVALID_HANDLE; } const char *MegaRequest::getLink() const { return NULL; } MegaHandle MegaRequest::getParentHandle() const { return INVALID_HANDLE; } const char *MegaRequest::getSessionKey() const { return NULL; } const char *MegaRequest::getName() const { return NULL; } const char *MegaRequest::getEmail() const { return NULL; } const char *MegaRequest::getPassword() const { return NULL; } const char *MegaRequest::getNewPassword() const { return NULL; } const char *MegaRequest::getPrivateKey() const { return NULL; } int MegaRequest::getAccess() const { return 0; } const char *MegaRequest::getFile() const { return NULL; } int MegaRequest::getNumRetry() const { return 0; } MegaNode *MegaRequest::getPublicMegaNode() const { return NULL; } int MegaRequest::getParamType() const { return 0; } const char *MegaRequest::getText() const { return NULL; } long long MegaRequest::getNumber() const { return 0; } bool MegaRequest::getFlag() const { return false; } long long MegaRequest::getTransferredBytes() const { return 0; } long long MegaRequest::getTotalBytes() const { return 0; } MegaRequestListener *MegaRequest::getListener() const { return NULL; } MegaAccountDetails *MegaRequest::getMegaAccountDetails() const { return NULL; } MegaPricing *MegaRequest::getPricing() const { return NULL; } MegaCurrency *MegaRequest::getCurrency() const { return nullptr; } MegaAchievementsDetails *MegaRequest::getMegaAchievementsDetails() const { return NULL; } MegaTimeZoneDetails *MegaRequest::getMegaTimeZoneDetails() const { return NULL; } int MegaRequest::getTransferTag() const { return 0; } int MegaRequest::getNumDetails() const { return 0; } int MegaRequest::getTag() const { return 0; } #ifdef ENABLE_CHAT MegaTextChatPeerList *MegaRequest::getMegaTextChatPeerList() const { return NULL; } MegaTextChatList *MegaRequest::getMegaTextChatList() const { return NULL; } MegaScheduledMeetingList* MegaRequest::getMegaScheduledMeetingList() const { return nullptr; } #endif MegaHandleList* MegaRequest::getMegaHandleList() const { return nullptr; } MegaRecentActionBucketList *MegaRequest::getRecentActions() const { return nullptr; } MegaStringMap *MegaRequest::getMegaStringMap() const { return NULL; } MegaStringListMap* MegaRequest::getMegaStringListMap() const { return nullptr; } MegaStringTable* MegaRequest::getMegaStringTable() const { return nullptr; } MegaFolderInfo *MegaRequest::getMegaFolderInfo() const { return NULL; } const MegaPushNotificationSettings *MegaRequest::getMegaPushNotificationSettings() const { return NULL; } MegaBackgroundMediaUpload* MegaRequest::getMegaBackgroundMediaUploadPtr() const { return NULL; } MegaBannerList* MegaRequest::getMegaBannerList() const { return nullptr; } MegaStringList* MegaRequest::getMegaStringList() const { return nullptr; } MegaStringIntegerMap* MegaRequest::getMegaStringIntegerMap() const { return nullptr; } const MegaIntegerList* MegaRequest::getMegaIntegerList() const { return nullptr; } MegaSet* MegaRequest::getMegaSet() const { return nullptr; } MegaSetElementList* MegaRequest::getMegaSetElementList() const { return nullptr; } MegaBackupInfoList* MegaRequest::getMegaBackupInfoList() const { return nullptr; } #ifdef ENABLE_SYNC MegaSyncStallList* MegaRequest::getMegaSyncStallList() const { return nullptr; } MegaSyncStallMap* MegaRequest::getMegaSyncStallMap() const { return nullptr; } #endif // ENABLE_SYNC MegaVpnRegionList* MegaRequest::getMegaVpnRegionsDetailed() const { return nullptr; } MegaVpnCredentials* MegaRequest::getMegaVpnCredentials() const { return nullptr; } MegaNetworkConnectivityTestResults* MegaRequest::getMegaNetworkConnectivityTestResults() const { return nullptr; } const MegaNotificationList* MegaRequest::getMegaNotifications() const { return nullptr; } const MegaNodeTree* MegaRequest::getMegaNodeTree() const { return nullptr; } const MegaCancelSubscriptionReasonList* MegaRequest::getMegaCancelSubscriptionReasons() const { return nullptr; } MegaDiscountCodeList* MegaRequest::getMegaDiscountCodeList() const { return nullptr; } const MegaDiscountCodeInfo* MegaRequest::getMegaDiscountCodeInfo() const { return nullptr; } MegaTransfer::~MegaTransfer() { } MegaTransfer *MegaTransfer::copy() { return NULL; } int MegaTransfer::getType() const { return 0; } const char *MegaTransfer::getTransferString() const { return NULL; } const char *MegaTransfer::toString() const { return NULL; } int64_t MegaTransfer::getStartTime() const { return 0; } long long MegaTransfer::getTransferredBytes() const { return 0; } long long MegaTransfer::getTotalBytes() const { return 0; } const char *MegaTransfer::getPath() const { return NULL; } const char *MegaTransfer::getParentPath() const { return NULL; } MegaHandle MegaTransfer::getNodeHandle() const { return INVALID_HANDLE; } MegaHandle MegaTransfer::getParentHandle() const { return INVALID_HANDLE; } long long MegaTransfer::getStartPos() const { return 0; } long long MegaTransfer::getEndPos() const { return 0; } const char *MegaTransfer::getFileName() const { return NULL; } MegaTransferListener *MegaTransfer::getListener() const { return NULL; } int MegaTransfer::getNumRetry() const { return 0; } int MegaTransfer::getMaxRetries() const { return 0; } unsigned MegaTransfer::getStage() const { return 0; } uint32_t MegaTransfer::getUniqueId() const { return 0; } int MegaTransfer::getTag() const { return 0; } long long MegaTransfer::getSpeed() const { return 0; } long long MegaTransfer::getMeanSpeed() const { return 0; } long long MegaTransfer::getDeltaSize() const { return 0; } int64_t MegaTransfer::getUpdateTime() const { return 0; } MegaNode *MegaTransfer::getPublicMegaNode() const { return NULL; } bool MegaTransfer::isSyncTransfer() const { return false; } bool MegaTransfer::isStreamingTransfer() const { return false; } bool MegaTransfer::isFinished() const { return false; } bool MegaTransfer::isBackupTransfer() const { return false; } bool MegaTransfer::isForeignOverquota() const { return false; } bool MegaTransfer::isForceNewUpload() const { return false; } char *MegaTransfer::getLastBytes() const { return NULL; } const MegaError *MegaTransfer::getLastErrorExtended() const { return nullptr; } bool MegaTransfer::isFolderTransfer() const { return false; } int MegaTransfer::getFolderTransferTag() const { return 0; } const char *MegaTransfer::getAppData() const { return NULL; } int MegaTransfer::getState() const { return STATE_NONE; } unsigned long long MegaTransfer::getPriority() const { return 0; } long long MegaTransfer::getNotificationNumber() const { return 0; } bool MegaTransfer::getTargetOverride() const { return false; } MegaCancelToken* MegaTransfer::getCancelToken() { return NULL; } const char* MegaTransfer::stageToString(unsigned stage) { switch (stage) { case MegaTransfer::STAGE_NONE: return "Not initialized stage"; case MegaTransfer::STAGE_SCAN: return "Scan stage"; case MegaTransfer::STAGE_CREATE_TREE: return "Create tree stage"; case MegaTransfer::STAGE_TRANSFERRING_FILES: return "Transferring files stage"; default: return "Invalid stage"; } } MegaError::MegaError(int e) { errorCode = e; syncError = NO_SYNC_ERROR; } MegaError::MegaError(int e, int se) { errorCode = e; syncError = se; } MegaError::~MegaError() { } MegaError* MegaError::copy() const { return new MegaError(*this); } int MegaError::getErrorCode() const { return errorCode; } int MegaError::getMountResult() const { return MegaMount::SUCCESS; } int MegaError::getSyncError() const { return syncError; } long long MegaError::getValue() const { return 0; } bool MegaError::hasExtraInfo() const { return false; } long long MegaError::getUserStatus() const { return 0; } long long MegaError::getLinkStatus() const { return 0; } const char* MegaError::getErrorString() const { return MegaError::getErrorString(errorCode); } const char* MegaError::getErrorString(int errorCode) { return MegaError::getErrorString(errorCode, API_EC_DEFAULT); } const char* MegaError::getErrorString(int errorCode, ErrorContexts context) { if(errorCode <= 0) { switch(errorCode) { case API_OK: return "No error"; case API_EINTERNAL: return "Internal error"; case API_EARGS: return "Invalid argument"; case API_EAGAIN: return "Request failed, retrying"; case API_ERATELIMIT: return "Rate limit exceeded"; case API_EFAILED: return "Failed permanently"; case API_ETOOMANY: switch (context) { case API_EC_DOWNLOAD: return "Terms of Service breached"; default: return "Too many concurrent connections or transfers"; } case API_ERANGE: return "Out of range"; case API_EEXPIRED: return "Expired"; case API_ENOENT: return "Not found"; case API_ECIRCULAR: switch (context) { case API_EC_UPLOAD: return "Upload produces recursivity"; default: return "Circular linkage detected"; } case API_EACCESS: return "Access denied"; case API_EEXIST: return "Already exists"; case API_EINCOMPLETE: return "Incomplete"; case API_EKEY: return "Invalid key/Decryption error"; case API_ESID: return "Bad session ID"; case API_EBLOCKED: switch (context) { case API_EC_IMPORT: case API_EC_DOWNLOAD: return "File removed as it violated our Terms of Service"; default: return "Blocked"; } case API_EOVERQUOTA: return "Over quota"; case API_ETEMPUNAVAIL: return "Temporarily not available"; case API_ETOOMANYCONNECTIONS: return "Connection overflow"; case API_EWRITE: return "Write error"; case API_EREAD: return "Read error"; case API_EAPPKEY: return "Invalid application key"; case API_ESSL: return "SSL verification failed"; case API_EGOINGOVERQUOTA: return "Not enough quota"; case API_EROLLEDBACK: return "Strongly-grouped request rolled back"; case API_EMFAREQUIRED: return "Multi-factor authentication required"; case API_EMASTERONLY: return "Access denied for users"; case API_EBUSINESSPASTDUE: return "Business account has expired"; case API_EPAYWALL: return "Storage Quota Exceeded. Upgrade now"; case API_ESUBUSERKEYMISSING: return "A business error where a subuser has not yet encrypted their master key for " "the admin user and tries to perform a disallowed command (currently u and p)"; case LOCAL_ENOSPC: return "Insufficient disk space"; case LOCAL_ETIMEOUT: return "Local timeout error"; case LOCAL_ENETWORK: return "Local network error"; case PAYMENT_ECARD: return "Credit card rejected"; case PAYMENT_EBILLING: return "Billing failed"; case PAYMENT_EFRAUD: return "Rejected by fraud protection"; case PAYMENT_ETOOMANY: return "Too many requests"; case PAYMENT_EBALANCE: return "Balance error"; case PAYMENT_EGENERIC: default: return "Unknown error"; } } return "HTTP Error"; } const char* MegaError::toString() const { return getErrorString(); } MegaContactRequest::~MegaContactRequest() { } MegaContactRequest *MegaContactRequest::copy() const { return NULL; } MegaHandle MegaContactRequest::getHandle() const { return INVALID_HANDLE; } char *MegaContactRequest::getSourceEmail() const { return NULL; } char *MegaContactRequest::getSourceMessage() const { return NULL; } char *MegaContactRequest::getTargetEmail() const { return NULL; } int64_t MegaContactRequest::getCreationTime() const { return 0; } int64_t MegaContactRequest::getModificationTime() const { return 0; } int MegaContactRequest::getStatus() const { return 0; } bool MegaContactRequest::isOutgoing() const { return true; } bool MegaContactRequest::isAutoAccepted() const { return false; } //Request callbacks void MegaRequestListener::onRequestStart(MegaApi *, MegaRequest *) { } void MegaRequestListener::onRequestFinish(MegaApi *, MegaRequest *, MegaError *) { } void MegaRequestListener::onRequestUpdate(MegaApi *, MegaRequest *) { } void MegaRequestListener::onRequestTemporaryError(MegaApi *, MegaRequest *, MegaError *) { } MegaRequestListener::~MegaRequestListener() { } SynchronousRequestListener::SynchronousRequestListener() { listener = NULL; megaApi = NULL; megaRequest = NULL; megaError = NULL; semaphore = new MegaSemaphore(); } SynchronousRequestListener::~SynchronousRequestListener() { delete semaphore; if (megaRequest) { delete megaRequest; } if (megaError) { delete megaError; } } void SynchronousRequestListener::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *error) { this->megaApi = api; delete megaRequest; //in case of reused listener this->megaRequest = request ? request->copy() : nullptr; delete megaError; //in case of reused listener this->megaError = error->copy(); doOnRequestFinish(api, request, error); semaphore->release(); } void SynchronousRequestListener::doOnRequestFinish(MegaApi*, MegaRequest*, MegaError*) {} void SynchronousRequestListener::wait() { semaphore->wait(); } int SynchronousRequestListener::trywait(int milliseconds) { return semaphore->timedwait(milliseconds); } MegaRequest *SynchronousRequestListener::getRequest() const { return megaRequest; } MegaApi *SynchronousRequestListener::getApi() const { return megaApi; } MegaError *SynchronousRequestListener::getError() const { return megaError; } //Transfer callbacks void MegaTransferListener::onTransferStart(MegaApi *, MegaTransfer *) { } void MegaTransferListener::onTransferFinish(MegaApi*, MegaTransfer *, MegaError*) { } void MegaTransferListener::onTransferUpdate(MegaApi *, MegaTransfer *) { } void MegaTransferListener::onFolderTransferUpdate(MegaApi*, MegaTransfer*, int /*stage*/, uint32_t /*foldercount*/, uint32_t /*filecount*/, uint32_t /*createdfoldercount*/, const char* /*currentFolder*/, const char* /*currentFileLeafname*/) {} bool MegaTransferListener::onTransferData(MegaApi *, MegaTransfer *, char *, size_t) { return true; } void MegaTransferListener::onTransferTemporaryError(MegaApi *, MegaTransfer *, MegaError*) { } MegaTransferListener::~MegaTransferListener() { } SynchronousTransferListener::SynchronousTransferListener() { listener = NULL; megaApi = NULL; megaTransfer = NULL; megaError = NULL; semaphore = new MegaSemaphore(); } SynchronousTransferListener::~SynchronousTransferListener() { delete semaphore; delete megaTransfer; delete megaError; } void SynchronousTransferListener::onTransferFinish(MegaApi *api, MegaTransfer *transfer, MegaError *error) { this->megaApi = api; delete megaTransfer; //in case of reused listener this->megaTransfer = transfer ? transfer->copy() : nullptr; delete megaError; //in case of reused listener this->megaError = error ? error->copy() : nullptr; doOnTransferFinish(api, transfer, error); semaphore->release(); } void SynchronousTransferListener::doOnTransferFinish(MegaApi*, MegaTransfer*, MegaError*) {} void SynchronousTransferListener::wait() { semaphore->wait(); } int SynchronousTransferListener::trywait(int milliseconds) { return semaphore->timedwait(milliseconds); } MegaTransfer *SynchronousTransferListener::getTransfer() const { return megaTransfer; } MegaApi *SynchronousTransferListener::getApi() const { return megaApi; } MegaError *SynchronousTransferListener::getError() const { return megaError; } //Global callbacks void MegaGlobalListener::onUsersUpdate(MegaApi*, MegaUserList*) {} void MegaGlobalListener::onUserAlertsUpdate(MegaApi*, MegaUserAlertList*) {} void MegaGlobalListener::onNodesUpdate(MegaApi*, MegaNodeList*) {} void MegaGlobalListener::onAccountUpdate(MegaApi*) {} void MegaGlobalListener::onSetsUpdate(MegaApi*, MegaSetList*) {} void MegaGlobalListener::onSetElementsUpdate(MegaApi*, MegaSetElementList*) {} void MegaGlobalListener::onContactRequestsUpdate(MegaApi*, MegaContactRequestList*) {} void MegaGlobalListener::onEvent(MegaApi*, MegaEvent*) {} void MegaGlobalListener::onDrivePresenceChanged(MegaApi*, bool /*present*/, const char* /*rootPathInUtf8*/) {} void MegaGlobalListener::onSeqTagUpdate(MegaApi*, const std::string* /*seqTag*/) {} MegaGlobalListener::~MegaGlobalListener() { } //All callbacks void MegaListener::onRequestStart(MegaApi*, MegaRequest*) {} void MegaListener::onRequestFinish(MegaApi*, MegaRequest*, MegaError*) {} void MegaListener::onRequestUpdate(MegaApi*, MegaRequest*) {} void MegaListener::onRequestTemporaryError(MegaApi*, MegaRequest*, MegaError*) {} void MegaListener::onTransferStart(MegaApi*, MegaTransfer*) {} void MegaListener::onTransferFinish(MegaApi*, MegaTransfer*, MegaError*) {} void MegaListener::onTransferUpdate(MegaApi*, MegaTransfer*) {} void MegaListener::onTransferTemporaryError(MegaApi*, MegaTransfer*, MegaError*) {} void MegaListener::onUsersUpdate(MegaApi*, MegaUserList*) {} void MegaListener::onUserAlertsUpdate(MegaApi*, MegaUserAlertList*) {} void MegaListener::onNodesUpdate(MegaApi*, MegaNodeList*) {} void MegaListener::onAccountUpdate(MegaApi*) {} void MegaListener::onSetsUpdate(MegaApi*, MegaSetList*) {} void MegaListener::onSetElementsUpdate(MegaApi*, MegaSetElementList*) {} void MegaListener::onContactRequestsUpdate(MegaApi*, MegaContactRequestList*) {} void MegaListener::onEvent(MegaApi*, MegaEvent*) {} #ifdef ENABLE_SYNC void MegaGlobalListener::onGlobalSyncStateChanged(MegaApi*) {} void MegaListener::onSyncFileStateChanged(MegaApi*, MegaSync*, string*, int) {} void MegaListener::onSyncAdded(MegaApi*, MegaSync*) {} void MegaListener::onSyncDeleted(MegaApi*, MegaSync*) {} void MegaListener::onSyncStateChanged(MegaApi*, MegaSync*) {} void MegaListener::onSyncStatsUpdated(MegaApi*, MegaSyncStats*) {} void MegaListener::onGlobalSyncStateChanged(MegaApi*) {} void MegaListener::onSyncRemoteRootChanged(MegaApi*, MegaSync*) {} #endif void MegaListener::onBackupStateChanged(MegaApi *, MegaScheduledCopy *) { } void MegaListener::onBackupStart(MegaApi *, MegaScheduledCopy *) { } void MegaListener::onBackupFinish(MegaApi *, MegaScheduledCopy *, MegaError *) { } void MegaListener::onBackupUpdate(MegaApi *, MegaScheduledCopy *) { } void MegaListener::onBackupTemporaryError(MegaApi *, MegaScheduledCopy *, MegaError *) { } #ifdef ENABLE_CHAT void MegaGlobalListener::onChatsUpdate(MegaApi*, MegaTextChatList*) {} void MegaListener::onChatsUpdate(MegaApi*, MegaTextChatList*) {} #endif MegaListener::~MegaListener() {} void MegaListener::onMountAdded(MegaApi*, const char*, int) { } void MegaListener::onMountChanged(MegaApi*, const char*, int) { } void MegaListener::onMountDisabled(MegaApi*, const char*, int) { } void MegaListener::onMountEnabled(MegaApi*, const char*, int) { } void MegaListener::onMountRemoved(MegaApi*, const char*, int) { } bool MegaTreeProcessor::processMegaNode(MegaNode*) { return false; /* Stops the processing */ } MegaTreeProcessor::~MegaTreeProcessor() { } /* BEGIN MEGAAPI */ MegaApi::MegaApi(const char* /*appKey*/, MegaGfxProvider* provider, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType) { pImpl = new MegaApiImpl(this, provider, basePath, userAgent, workerThreadCount, clientType); } MegaApi::MegaApi(const char* /*appKey*/, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType) { pImpl = new MegaApiImpl(this, static_cast<MegaGfxProcessor*>(nullptr), basePath, userAgent, workerThreadCount, clientType); } #ifdef HAVE_MEGAAPI_RPC MegaApi::MegaApi() {} #endif MegaApi::~MegaApi() { delete pImpl; } int MegaApi::isLoggedIn() { return pImpl->isLoggedIn(); } bool MegaApi::isEphemeralPlusPlus() { return pImpl->isEphemeralPlusPlus(); } void MegaApi::whyAmIBlocked(MegaRequestListener *listener) { pImpl->whyAmIBlocked(false, listener); } void MegaApi::contactLinkCreate(bool renew, MegaRequestListener *listener) { pImpl->contactLinkCreate(renew, listener); } void MegaApi::contactLinkQuery(MegaHandle handle, MegaRequestListener *listener) { pImpl->contactLinkQuery(handle, listener); } void MegaApi::contactLinkDelete(MegaHandle handle, MegaRequestListener *listener) { pImpl->contactLinkDelete(handle, listener); } void MegaApi::keepMeAlive(int type, bool enable, MegaRequestListener *listener) { pImpl->keepMeAlive(type, enable, listener); } void MegaApi::setPSA(int id, MegaRequestListener *listener) { pImpl->setPSA(id, listener); } void MegaApi::getPSA(MegaRequestListener *listener) { pImpl->getPSA(false, listener); } void MegaApi::getPSAWithUrl(MegaRequestListener *listener) { pImpl->getPSA(true, listener); } void MegaApi::acknowledgeUserAlerts(MegaRequestListener *listener) { pImpl->acknowledgeUserAlerts(listener); } char *MegaApi::getMyEmail() { return pImpl->getMyEmail(); } void MegaApi::getRecommendedProLevel(MegaRequestListener* listener) { pImpl->getRecommendedProLevel(listener); } int64_t MegaApi::getAccountCreationTs() { return pImpl->getAccountCreationTs(); } char *MegaApi::getMyUserHandle() { return pImpl->getMyUserHandle(); } MegaHandle MegaApi::getMyUserHandleBinary() { return pImpl->getMyUserHandleBinary(); } MegaUser *MegaApi::getMyUser() { return pImpl->getMyUser(); } bool MegaApi::isAchievementsEnabled() { return pImpl->isAchievementsEnabled(); } bool MegaApi::isProFlexiAccount() { return pImpl->isProFlexiAccount(); } bool MegaApi::isBusinessAccount() { return pImpl->isBusinessAccount(); } bool MegaApi::isMasterBusinessAccount() { return pImpl->isMasterBusinessAccount(); } int MegaApi::getBusinessStatus() { return pImpl->getBusinessStatus(); } bool MegaApi::isBusinessAccountActive() { return pImpl->isBusinessAccountActive(); } bool MegaApi::checkPassword(const char *password) { return pImpl->checkPassword(password); } char *MegaApi::getMyCredentials() { return pImpl->getMyCredentials(); } void MegaApi::getUserCredentials(MegaUser *user, MegaRequestListener *listener) { pImpl->getUserCredentials(user, listener); } bool MegaApi::areCredentialsVerified(MegaUser *user) { return pImpl->areCredentialsVerified(user); } void MegaApi::verifyCredentials(MegaUser *user, MegaRequestListener *listener) { pImpl->verifyCredentials(user, listener); } void MegaApi::resetCredentials(MegaUser *user, MegaRequestListener *listener) { pImpl->resetCredentials(user, listener); } void MegaApi::setLogLevel(int logLevel) { MegaApiImpl::setLogLevel(logLevel); } void MegaApi::setLogExtraForModules(bool networking, bool syncs) { return pImpl->setLogExtraForModules(networking, syncs); } void MegaApi::setMaxPayloadLogSize(size_t maxSize) { MegaApiImpl::setMaxPayloadLogSize(maxSize); } void MegaApi::setLogToConsole(bool enable) { MegaApiImpl::setLogToConsole(enable); } void MegaApi::setLogJSONContent(bool enable) { MegaApiImpl::setLogJSONContent(enable); } void MegaApi::setLogJSON(uint32_t value) { MegaApiImpl::setLogJSON(value); } uint32_t MegaApi::getLogJSON() { return MegaApiImpl::getLogJSON(); } void MegaApi::addLoggerObject(MegaLogger *megaLogger, bool singleExclusiveLogger) { MegaApiImpl::addLoggerClass(megaLogger, singleExclusiveLogger); } void MegaApi::removeLoggerObject(MegaLogger *megaLogger, bool singleExclusiveLogger) { MegaApiImpl::removeLoggerClass(megaLogger, singleExclusiveLogger); } void MegaApi::log(int logLevel, const char *message, const char *filename, int line) { MegaApiImpl::log(logLevel, message, filename, line); } void MegaApi::setLoggingName(const char* loggingName) { pImpl->setLoggingName(loggingName); } long long MegaApi::getSDKtime() { return pImpl->getSDKtime(); } void MegaApi::getSessionTransferURL(const char *path, MegaRequestListener *listener) { pImpl->getSessionTransferURL(path, listener); } MegaHandle MegaApi::base32ToHandle(const char *base32Handle) { return MegaApiImpl::base32ToHandle(base32Handle); } uint64_t MegaApi::base64ToHandle(const char* base64Handle) { return MegaApiImpl::base64ToHandle(base64Handle); } uint64_t MegaApi::base64ToUserHandle(const char* base64Handle) { return MegaApiImpl::base64ToUserHandle(base64Handle); } MegaHandle MegaApi::base64ToBackupId(const char* backupId) { return MegaApiImpl::base64ToBackupId(backupId); } char *MegaApi::handleToBase64(MegaHandle handle) { return MegaApiImpl::handleToBase64(handle); } char *MegaApi::userHandleToBase64(MegaHandle handle) { return MegaApiImpl::userHandleToBase64(handle); } const char* MegaApi::backupIdToBase64(MegaHandle backupId) { return MegaApiImpl::backupIdToBase64(backupId); } void MegaApi::base64ToBinary(const char *base64string, unsigned char **binary, size_t* binarysize) { return MegaApiImpl::base64ToBinary(base64string, binary, binarysize); } char *MegaApi::binaryToBase64(const char* binaryData, size_t length) { return MegaApiImpl::binaryToBase64(binaryData, length); } void MegaApi::retryPendingConnections(bool disconnect, bool includexfers, MegaRequestListener* listener) { pImpl->retryPendingConnections(disconnect, includexfers, listener); } void MegaApi::setDnsServers(const char *dnsServers, MegaRequestListener *listener) { pImpl->setDnsServers(dnsServers, listener); } bool MegaApi::serverSideRubbishBinAutopurgeEnabled() { return pImpl->serverSideRubbishBinAutopurgeEnabled(); } bool MegaApi::appleVoipPushEnabled() { return pImpl->appleVoipPushEnabled(); } bool MegaApi::newLinkFormatEnabled() { return pImpl->newLinkFormatEnabled(); } bool MegaApi::accountIsNew() const { return pImpl->accountIsNew(); } unsigned int MegaApi::getABTestValue(const char* flag) { return pImpl->getABTestValue(flag); } int MegaApi::smsAllowedState() { return pImpl->smsAllowedState(); } char* MegaApi::smsVerifiedPhoneNumber() { return pImpl->smsVerifiedPhoneNumber(); } void MegaApi::resetSmsVerifiedPhoneNumber(MegaRequestListener *listener) { pImpl->resetSmsVerifiedPhoneNumber(listener); } bool MegaApi::multiFactorAuthAvailable() { return pImpl->multiFactorAuthAvailable(); } void MegaApi::multiFactorAuthCheck(const char *email, MegaRequestListener *listener) { pImpl->multiFactorAuthCheck(email, listener); } void MegaApi::multiFactorAuthGetCode(MegaRequestListener *listener) { pImpl->multiFactorAuthGetCode(listener); } void MegaApi::multiFactorAuthEnable(const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthEnable(pin, listener); } void MegaApi::multiFactorAuthDisable(const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthDisable(pin, listener); } void MegaApi::multiFactorAuthLogin(const char *email, const char *password, const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthLogin(email, password, pin, listener); } void MegaApi::multiFactorAuthChangePassword(const char *oldPassword, const char *newPassword, const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthChangePassword(oldPassword, newPassword, pin, listener); } void MegaApi::multiFactorAuthChangeEmail(const char *email, const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthChangeEmail(email, pin, listener); } void MegaApi::multiFactorAuthCancelAccount(const char *pin, MegaRequestListener *listener) { pImpl->multiFactorAuthCancelAccount(pin, listener); } void MegaApi::fetchTimeZone(MegaRequestListener *listener) { pImpl->fetchTimeZone(true /*forceApiFetch*/, listener); } void MegaApi::fetchTimeZoneFromLocal(MegaRequestListener* listener) { pImpl->fetchTimeZone(false /*forceApiFetch*/, listener); } void MegaApi::addEntropy(char *data, unsigned int size) { pImpl->addEntropy(data, size); } void MegaApi::fastLogin(const char *session, MegaRequestListener *listener) { pImpl->fastLogin(session, listener); } void MegaApi::killSession(MegaHandle sessionHandle, MegaRequestListener *listener) { pImpl->killSession(sessionHandle, listener); } void MegaApi::getUserData(MegaRequestListener *listener) { pImpl->getUserData(listener); } void MegaApi::getUserData(MegaUser *user, MegaRequestListener *listener) { pImpl->getUserData(user, listener); } void MegaApi::getUserData(const char *user, MegaRequestListener *listener) { pImpl->getUserData(user, listener); } void MegaApi::getMiscFlags(MegaRequestListener *listener) { pImpl->getMiscFlags(listener); } void MegaApi::sendDevCommand(const char *command, const char *email, MegaRequestListener *listener) { pImpl->sendDevCommand(command, email, 0, 0, 0, listener); } void MegaApi::sendOdqDevCommand(const char *email, MegaRequestListener *listener) { pImpl->sendDevCommand("aodq", email, 0, 0, 0, listener); } void MegaApi::sendUsedTransferQuotaDevCommand(long long quota, const char *email, MegaRequestListener *listener) { pImpl->sendDevCommand("tq", email, quota, 0, 0, listener); } void MegaApi::sendBusinessStatusDevCommand(int businessStatus, const char *email, MegaRequestListener *listener) { pImpl->sendDevCommand("bs", email, 0, businessStatus, 0, listener); } void MegaApi::sendSetAccountLevelDevCommand(int accountLevel, int quotaLengthInMonths, const char* email, MegaRequestListener* listener) { pImpl->sendDevCommand("sal", email, quotaLengthInMonths, 0, accountLevel, listener); } void MegaApi::sendUserStatusDevCommand(int userStatus, const char *email, MegaRequestListener *listener) { pImpl->sendDevCommand("us", email, 0, 0, userStatus, listener); } void MegaApi::login(const char *login, const char *password, MegaRequestListener *listener) { pImpl->login(login, password, listener); } char *MegaApi::dumpSession() { return pImpl->dumpSession(); } char *MegaApi::getSequenceNumber() { return pImpl->getSequenceNumber(); } char *MegaApi::getSequenceTag() { return pImpl->getSequenceTag(); } char *MegaApi::getAccountAuth() { return pImpl->getAccountAuth(); } void MegaApi::setAccountAuth(const char *auth) { pImpl->setAccountAuth(auth); } void MegaApi::createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaRequestListener *listener) { pImpl->createAccount(email, password, firstname, lastname, UNDEF, AFFILIATE_TYPE_INVALID, 0, listener); } void MegaApi::createEphemeralAccountPlusPlus(const char *firstname, const char *lastname, MegaRequestListener *listener) { pImpl->createEphemeralAccountPlusPlus(firstname, lastname, listener); } void MegaApi::resumeCreateAccount(const char* sid, MegaRequestListener *listener) { pImpl->resumeCreateAccount(sid, listener); } void MegaApi::resumeCreateAccountEphemeralPlusPlus(const char *sid, MegaRequestListener *listener) { pImpl->resumeCreateAccountEphemeralPlusPlus(sid, listener); } void MegaApi::cancelCreateAccount(MegaRequestListener *listener) { pImpl->cancelCreateAccount(listener); } void MegaApi::resendSignupLink(const char *email, const char *name, MegaRequestListener *listener) { pImpl->resendSignupLink(email, name, listener); } void MegaApi::querySignupLink(const char* link, MegaRequestListener *listener) { pImpl->querySignupLink(link, listener); } void MegaApi::confirmAccount(const char* link, MegaRequestListener* listener) { pImpl->confirmAccount(link, listener); } void MegaApi::confirmAccount(const char* link, const char* /*password*/, MegaRequestListener* listener) { pImpl->confirmAccount(link, listener); } void MegaApi::resetPassword(const char *email, bool hasMasterKey, MegaRequestListener *listener) { pImpl->resetPassword(email, hasMasterKey, listener); } void MegaApi::queryResetPasswordLink(const char *link, MegaRequestListener *listener) { pImpl->queryRecoveryLink(link, listener); } void MegaApi::confirmResetPassword(const char *link, const char *newPwd, const char *masterKey, MegaRequestListener *listener) { pImpl->confirmResetPasswordLink(link, newPwd, masterKey, listener); } void MegaApi::checkRecoveryKey(const char* link, const char* recoveryKey, MegaRequestListener* listener) { pImpl->checkRecoveryKey(link, recoveryKey, listener); } void MegaApi::cancelAccount(MegaRequestListener *listener) { pImpl->cancelAccount(listener); } void MegaApi::queryCancelLink(const char *link, MegaRequestListener *listener) { pImpl->queryRecoveryLink(link, listener); } void MegaApi::confirmCancelAccount(const char *link, const char *pwd, MegaRequestListener *listener) { pImpl->confirmCancelAccount(link, pwd, listener); } void MegaApi::resendVerificationEmail(MegaRequestListener *listener) { pImpl->resendVerificationEmail(listener); } void MegaApi::changeEmail(const char *email, MegaRequestListener *listener) { pImpl->changeEmail(email, listener); } void MegaApi::queryChangeEmailLink(const char *link, MegaRequestListener *listener) { pImpl->queryRecoveryLink(link, listener); } void MegaApi::confirmChangeEmail(const char *link, const char *pwd, MegaRequestListener *listener) { pImpl->confirmChangeEmail(link, pwd, listener); } void MegaApi::setProxySettings(MegaProxy *proxySettings, MegaRequestListener *listener) { pImpl->setProxySettings(proxySettings, listener); } MegaProxy *MegaApi::getAutoProxySettings() { return pImpl->getAutoProxySettings(); } void MegaApi::createFolder(const char *name, MegaNode *parent, MegaRequestListener *listener) { pImpl->createFolder(name, parent, listener); } void MegaApi::getPasswordManagerBase(MegaRequestListener *listener) { pImpl->getPasswordManagerBase(listener); } bool MegaApi::isPasswordNodeFolder(MegaHandle node) const { return pImpl->isPasswordManagerNodeFolder(node); } bool MegaApi::isPasswordManagerNodeFolder(MegaHandle node) const { return pImpl->isPasswordManagerNodeFolder(node); } void MegaApi::createCreditCardNode(const char* name, const MegaNode::CreditCardNodeData* data, MegaHandle parent, MegaRequestListener* listener) { pImpl->createCreditCardNode(name, data, parent, listener); } void MegaApi::createPasswordNode(const char* name, const MegaNode::PasswordNodeData* data, MegaHandle parent, MegaRequestListener* listener) { pImpl->createPasswordNode(name, data, parent, listener); } void MegaApi::updateCreditCardNode(MegaHandle node, const MegaNode::CreditCardNodeData* newData, MegaRequestListener* listener) { pImpl->updateCreditCardNode(node, newData, listener); } void MegaApi::updatePasswordNode(MegaHandle node, const MegaNode::PasswordNodeData* newData, MegaRequestListener* listener) { pImpl->updatePasswordNode(node, newData, listener); } void MegaApi::importPasswordsFromFile(const char* filePath, const int fileSource, MegaHandle parent, MegaRequestListener* listener) { pImpl->importPasswordsFromFile(filePath, fileSource, parent, listener); } bool MegaApi::createLocalFolder(const char *localPath) { return pImpl->createLocalFolder(localPath); } void MegaApi::moveNode(MegaNode *node, MegaNode *newParent, MegaRequestListener *listener) { pImpl->moveNode(node, newParent, listener); } void MegaApi::moveNode(MegaNode *node, MegaNode *newParent, const char *newName, MegaRequestListener *listener) { pImpl->moveNode(node, newParent, newName, listener); } void MegaApi::copyNode(MegaNode *node, MegaNode* target, MegaRequestListener *listener) { pImpl->copyNode(node, target, listener); } void MegaApi::copyNode(MegaNode *node, MegaNode *newParent, const char *newName, MegaRequestListener *listener) { pImpl->copyNode(node, newParent, newName, listener); } void MegaApi::renameNode(MegaNode *node, const char *newName, MegaRequestListener *listener) { pImpl->renameNode(node, newName, listener); } void MegaApi::remove(MegaNode *node, MegaRequestListener *listener) { pImpl->remove(node, false, listener); } void MegaApi::removeVersions(MegaRequestListener *listener) { pImpl->removeVersions(listener); } void MegaApi::removeVersion(MegaNode *node, MegaRequestListener *listener) { pImpl->remove(node, true, listener); } void MegaApi::restoreVersion(MegaNode *version, MegaRequestListener *listener) { pImpl->restoreVersion(version, listener); } void MegaApi::cleanRubbishBin(MegaRequestListener *listener) { pImpl->cleanRubbishBin(listener); } void MegaApi::sendFileToUser(MegaNode *node, MegaUser *user, MegaRequestListener *listener) { pImpl->sendFileToUser(node, user, listener); } void MegaApi::sendFileToUser(MegaNode *node, const char* email, MegaRequestListener *listener) { pImpl->sendFileToUser(node, email, listener); } void MegaApi::upgradeSecurity(MegaRequestListener* listener) { pImpl->upgradeSecurity(listener); } bool MegaApi::contactVerificationWarningEnabled() { return pImpl->contactVerificationWarningEnabled(); } void MegaApi::setManualVerificationFlag(bool enable) { pImpl->setManualVerificationFlag(enable); } void MegaApi::openShareDialog(MegaNode *node, MegaRequestListener *listener) { pImpl->openShareDialog(node, listener); } MegaShareList *MegaApi::getUnverifiedInShares(int order) { return pImpl->getUnverifiedInShares(order); } MegaShareList *MegaApi::getUnverifiedOutShares(int order) { return pImpl->getUnverifiedOutShares(order); } void MegaApi::share(MegaNode* node, MegaUser *user, int access, MegaRequestListener *listener) { pImpl->share(node, user, access, listener); } void MegaApi::share(MegaNode *node, const char* email, int access, MegaRequestListener *listener) { pImpl->share(node, email, access, listener); } void MegaApi::loginToFolder(const char* megaFolderLink, MegaRequestListener* listener) { pImpl->loginToFolder(megaFolderLink, nullptr, false, listener); } void MegaApi::loginToFolder(const char* megaFolderLink, const char* authKey, MegaRequestListener *listener) { pImpl->loginToFolder(megaFolderLink, authKey, false, listener); } void MegaApi::loginToFolder(const char* megaFolderLink, const char* authKey, const bool tryToResumeFolderLinkFromCache, MegaRequestListener* listener) { pImpl->loginToFolder(megaFolderLink, authKey, tryToResumeFolderLinkFromCache, listener); } void MegaApi::importFileLink(const char* megaFileLink, MegaNode *parent, MegaRequestListener *listener) { pImpl->importFileLink(megaFileLink, parent, listener); } void MegaApi::decryptPasswordProtectedLink(const char *link, const char *password, MegaRequestListener *listener) { pImpl->decryptPasswordProtectedLink(link, password, listener); } void MegaApi::encryptLinkWithPassword(const char *link, const char *password, MegaRequestListener *listener) { pImpl->encryptLinkWithPassword(link, password, listener); } void MegaApi::getPublicNode(const char* megaFileLink, MegaRequestListener *listener) { pImpl->getPublicNode(megaFileLink, listener); } void MegaApi::getDownloadUrl(MegaNode* node, bool singleUrl, MegaRequestListener *listener) { pImpl->getDownloadUrl(node, singleUrl, true, listener); } void MegaApi::getDownloadUrl(MegaNode* node, bool singleUrl, bool forceSSL, MegaRequestListener* listener) { pImpl->getDownloadUrl(node, singleUrl, forceSSL, listener); } const char *MegaApi::buildPublicLink(const char *publicHandle, const char *key, bool isFolder) { return pImpl->buildPublicLink(publicHandle, key, isFolder); } void MegaApi::getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { pImpl->getThumbnail(node, dstFilePath, listener); } void MegaApi::getThumbnail(MegaHandle handle, const char* dstFilePath, MegaRequestListener* listener) { pImpl->getThumbnail(handle, dstFilePath, listener); } void MegaApi::cancelGetThumbnail(MegaNode* node, MegaRequestListener *listener) { pImpl->cancelGetThumbnail(node, listener); } void MegaApi::setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener) { pImpl->setThumbnail(node, srcFilePath, listener); } void MegaApi::putThumbnail(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener) { pImpl->putThumbnail(bu, srcFilePath, listener); } void MegaApi::setThumbnailByHandle(MegaNode* node, MegaHandle fileattribute, MegaRequestListener *listener) { pImpl->setThumbnailByHandle(node, fileattribute, listener); } void MegaApi::getPreview(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { pImpl->getPreview(node, dstFilePath, listener); } void MegaApi::cancelGetPreview(MegaNode* node, MegaRequestListener *listener) { pImpl->cancelGetPreview(node, listener); } void MegaApi::setPreview(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener) { pImpl->setPreview(node, srcFilePath, listener); } void MegaApi::putPreview(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener) { pImpl->putPreview(bu, srcFilePath, listener); } void MegaApi::setPreviewByHandle(MegaNode* node, MegaHandle fileattribute, MegaRequestListener *listener) { pImpl->setPreviewByHandle(node, fileattribute, listener); } void MegaApi::getUserAvatar(MegaUser* user, const char *dstFilePath, MegaRequestListener *listener) { pImpl->getUserAvatar(user, dstFilePath, listener); } void MegaApi::getUserAvatar(const char* email_or_handle, const char *dstFilePath, MegaRequestListener *listener) { pImpl->getUserAvatar(email_or_handle, dstFilePath, listener); } void MegaApi::getUserAvatar(const char *dstFilePath, MegaRequestListener *listener) { pImpl->getUserAvatar((MegaUser*)NULL, dstFilePath, listener); } char *MegaApi::getUserAvatarColor(MegaUser *user) { return MegaApiImpl::getUserAvatarColor(user); } char *MegaApi::getUserAvatarColor(const char *userhandle) { return MegaApiImpl::getUserAvatarColor(userhandle); } char *MegaApi::getUserAvatarSecondaryColor(MegaUser *user) { return MegaApiImpl::getUserAvatarSecondaryColor(user); } char *MegaApi::getUserAvatarSecondaryColor(const char *userhandle) { return MegaApiImpl::getUserAvatarSecondaryColor(userhandle); } void MegaApi::setAvatar(const char *dstFilePath, MegaRequestListener *listener) { pImpl->setAvatar(dstFilePath, listener); } char* MegaApi::getPrivateKey(int type) { return pImpl->getPrivateKey(type); } bool MegaApi::testAllocation(unsigned allocCount, size_t allocSize) { return pImpl->testAllocation(allocCount, allocSize); } void MegaApi::getUserAttribute(MegaUser* user, int type, MegaRequestListener *listener) { pImpl->getUserAttribute(user, type, listener); } void MegaApi::getChatUserAttribute(const char *email_or_handle, int type, const char *ph, MegaRequestListener *listener) { pImpl->getChatUserAttribute(email_or_handle, type, ph, listener); } void MegaApi::getUserAttribute(int type, MegaRequestListener *listener) { pImpl->getUserAttribute((MegaUser*)NULL, type, listener); } const char *MegaApi::userAttributeToString(int attr) { return MegaApi::strdup(pImpl->userAttributeToString(attr).c_str()); } const char *MegaApi::userAttributeToLongName(int attr) { return MegaApi::strdup(pImpl->userAttributeToLongName(attr).c_str()); } int MegaApi::userAttributeFromString(const char *name) { return pImpl->userAttributeFromString(name); } void MegaApi::getUserEmail(MegaHandle handle, MegaRequestListener *listener) { pImpl->getUserEmail(handle, listener); } void MegaApi::getUserAttribute(const char *email_or_handle, int type, MegaRequestListener *listener) { pImpl->getUserAttribute(email_or_handle, type, listener); } void MegaApi::setUserAttribute(int type, const char *value, MegaRequestListener *listener) { pImpl->setUserAttribute(type, value, listener); } void MegaApi::setUserAttribute(int type, const MegaStringMap *value, MegaRequestListener *listener) { pImpl->setUserAttribute(type, value, listener); } void MegaApi::setCustomNodeAttribute(MegaNode *node, const char *attrName, const char *value, MegaRequestListener *listener) { pImpl->setCustomNodeAttribute(node, attrName, value, listener); } void MegaApi::setNodeS4(MegaNode *node, const char *value, MegaRequestListener *listener) { pImpl->setNodeS4(node, value, listener); } bool MegaApi::isS4Enabled() { return pImpl->isS4Enabled(); } MegaHandle MegaApi::getS4Container() { return pImpl->getS4Container(); } void MegaApi::setNodeLabel(MegaNode *node, int label, MegaRequestListener *listener) { pImpl->setNodeLabel(node, label, listener); } void MegaApi::resetNodeLabel(MegaNode *node, MegaRequestListener *listener) { pImpl->setNodeLabel(node, MegaNode::NODE_LBL_UNKNOWN, listener); } void MegaApi::setNodeFavourite(MegaNode *node, bool fav, MegaRequestListener *listener) { pImpl->setNodeFavourite(node, fav, listener); } void MegaApi::getFavourites(MegaNode* node, int count, MegaRequestListener* listener) { pImpl->getFavourites(node, count, listener); } void MegaApi::setNodeSensitive(MegaNode* node, bool sensitive, MegaRequestListener* listener) { pImpl->setNodeSensitive(node, sensitive, listener); } void MegaApi::setNodeCoordinates(MegaNode *node, double latitude, double longitude, MegaRequestListener *listener) { pImpl->setNodeCoordinates(node, false, latitude, longitude, listener); } void MegaApi::setUnshareableNodeCoordinates(MegaNode *node, double latitude, double longitude, MegaRequestListener *listener) { pImpl->setNodeCoordinates(node, true, latitude, longitude, listener); } void MegaApi::setUnshareableNodeCoordinates(MegaHandle nodeHandle, double latitude, double longitude, MegaRequestListener* listener) { pImpl->setNodeCoordinates(nodeHandle, true, latitude, longitude, listener); } void MegaApi::setNodeDescription(MegaNode* node, const char* description, MegaRequestListener* listener) { pImpl->setNodeDescription(node, description, listener); } void MegaApi::addNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener) { pImpl->addNodeTag(node, tag, listener); } void MegaApi::removeNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener) { pImpl->removeNodeTag(node, tag, listener); } void MegaApi::updateNodeTag(MegaNode* node, const char* newTag, const char* oldTag, MegaRequestListener* listener) { pImpl->updateNodeTag(node, newTag, oldTag, listener); } MegaStringList* MegaApi::getAllNodeTags(const char* pattern, MegaCancelToken* cancelToken) { return getAllNodeTagsBelow(UNDEF, pattern, cancelToken); } MegaStringList* MegaApi::getAllNodeTagsBelow(const MegaNode* node, const char* pattern, MegaCancelToken* cancelToken) { // Sanity. assert(node); // Caller's provided a sane node. if (node) return getAllNodeTagsBelow(node->getHandle(), pattern, cancelToken); // Caller didn't provide a sane node. return MegaStringList::createInstance(); } MegaStringList* MegaApi::getAllNodeTagsBelow(MegaHandle handle, const char* pattern, MegaCancelToken* cancelToken) { return pImpl->getAllNodeTagsBelow(handle, pattern ? pattern : "", convertToCancelToken(cancelToken)); } void MegaApi::exportNode(MegaNode *node, int64_t expireTime, bool writable, bool megaHosted, MegaRequestListener *listener) { pImpl->exportNode(node, expireTime, writable, megaHosted, listener); } void MegaApi::disableExport(MegaNode *node, MegaRequestListener *listener) { pImpl->disableExport(node, listener); } void MegaApi::fetchNodes(MegaRequestListener *listener) { pImpl->fetchNodes(listener); } void MegaApi::getCloudStorageUsed(MegaRequestListener *listener) { pImpl->getCloudStorageUsed(listener); } void MegaApi::getAccountDetails(MegaRequestListener *listener) { pImpl->getAccountDetails(true, true, true, false, false, false, -1, listener); } void MegaApi::getSpecificAccountDetails(bool storage, bool transfer, bool pro, int source, MegaRequestListener *listener) { pImpl->getAccountDetails(storage, transfer, pro, false, false, false, source, listener); } void MegaApi::getExtendedAccountDetails(bool sessions, bool purchases, bool transactions, MegaRequestListener *listener) { pImpl->getAccountDetails(false, false, false, sessions, purchases, transactions, -1, listener); } void MegaApi::queryTransferQuota(long long size, MegaRequestListener *listener) { pImpl->queryTransferQuota(size, listener); } int64_t MegaApi::getOverquotaDeadlineTs() { return pImpl->getOverquotaDeadlineTs(); } MegaIntegerList* MegaApi::getOverquotaWarningsTs() { return pImpl->getOverquotaWarningsTs(); } void MegaApi::getPricing(MegaRequestListener *listener) { pImpl->getPricing(std::nullopt, listener); } void MegaApi::getPricing(const char* countryCode, MegaRequestListener* listener) { pImpl->getPricing(countryCode, listener); } void MegaApi::getPaymentId(MegaHandle productHandle, MegaRequestListener *listener) { pImpl->getPaymentId(productHandle, UNDEF, AFFILIATE_TYPE_INVALID, 0, listener); } void MegaApi::upgradeAccount(MegaHandle productHandle, int paymentMethod, MegaRequestListener *listener) { pImpl->upgradeAccount(productHandle, paymentMethod, listener); } void MegaApi::submitPurchaseReceipt(int gateway, const char *receipt, MegaRequestListener *listener) { pImpl->submitPurchaseReceipt(gateway, receipt, UNDEF, AFFILIATE_TYPE_INVALID, 0, listener); } void MegaApi::submitPurchaseReceipt(int gateway, const char *receipt, MegaHandle lastPublicHandle, MegaRequestListener *listener) { pImpl->submitPurchaseReceipt(gateway, receipt, lastPublicHandle, AFFILIATE_TYPE_INVALID, 0, listener); } void MegaApi::submitPurchaseReceipt(int gateway, const char *receipt, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener) { pImpl->submitPurchaseReceipt(gateway, receipt, lastPublicHandle, lastPublicHandleType, lastAccessTimestamp, listener); } void MegaApi::creditCardStore(const char* address1, const char* address2, const char* city, const char* province, const char* country, const char *postalcode, const char* firstname, const char* lastname, const char* creditcard, const char* expire_month, const char* expire_year, const char* cv2, MegaRequestListener *listener) { pImpl->creditCardStore(address1, address2, city, province, country, postalcode, firstname, lastname, creditcard, expire_month, expire_year, cv2, listener); } void MegaApi::creditCardQuerySubscriptions(MegaRequestListener *listener) { pImpl->creditCardQuerySubscriptions(listener); } void MegaApi::creditCardCancelSubscriptions(const char* reason, const char* id, int canContact, MegaRequestListener* listener) { pImpl->creditCardCancelSubscriptions(reason, id, canContact, listener); } void MegaApi::creditCardCancelSubscriptions(const MegaCancelSubscriptionReasonList* reasonList, const char* id, int canContact, MegaRequestListener* listener) { pImpl->creditCardCancelSubscriptions(reasonList, id, canContact, listener); } void MegaApi::getPaymentMethods(MegaRequestListener* listener) { pImpl->getPaymentMethods(listener); } char *MegaApi::exportMasterKey() { return pImpl->exportMasterKey(); } void MegaApi::masterKeyExported(MegaRequestListener *listener) { pImpl->updatePwdReminderData(false, false, true, false, false, listener); } void MegaApi::passwordReminderDialogSucceeded(MegaRequestListener *listener) { pImpl->updatePwdReminderData(true, false, false, false, false, listener); } void MegaApi::passwordReminderDialogSkipped(MegaRequestListener *listener) { pImpl->updatePwdReminderData(false, true, false, false, false, listener); } void MegaApi::passwordReminderDialogBlocked(MegaRequestListener *listener) { pImpl->updatePwdReminderData(false, false, false, true, false, listener); } void MegaApi::shouldShowPasswordReminderDialog(bool atLogout, MegaRequestListener *listener) { pImpl->getUserAttr((const char*)NULL, MegaApi::USER_ATTR_PWD_REMINDER, NULL, atLogout, listener); } void MegaApi::isMasterKeyExported(MegaRequestListener *listener) { pImpl->getUserAttr((const char*)NULL, MegaApi::USER_ATTR_PWD_REMINDER, NULL, 0, listener); } void MegaApi::getPushNotificationSettings(MegaRequestListener *listener) { pImpl->getPushNotificationSettings(listener); } void MegaApi::setPushNotificationSettings(MegaPushNotificationSettings *settings, MegaRequestListener *listener) { pImpl->setPushNotificationSettings(settings, listener); } #ifdef ENABLE_CHAT void MegaApi::enableRichPreviews(bool enable, MegaRequestListener *listener) { pImpl->enableRichPreviews(enable, listener); } void MegaApi::isRichPreviewsEnabled(MegaRequestListener *listener) { pImpl->isRichPreviewsEnabled(listener); } void MegaApi::shouldShowRichLinkWarning(MegaRequestListener *listener) { pImpl->shouldShowRichLinkWarning(listener); } void MegaApi::setRichLinkWarningCounterValue(int value, MegaRequestListener *listener) { pImpl->setRichLinkWarningCounterValue(value, listener); } void MegaApi::enableGeolocation(MegaRequestListener *listener) { pImpl->enableGeolocation(listener); } void MegaApi::isGeolocationEnabled(MegaRequestListener *listener) { pImpl->isGeolocationEnabled(listener); } /* Class MegaScheduledMeeting */ MegaScheduledMeeting* MegaScheduledMeeting::createInstance(MegaHandle chatid, MegaHandle schedId, MegaHandle parentSchedId, MegaHandle organizerUserId, int cancelled, const char* timezone, MegaTimeStamp startDateTime, MegaTimeStamp endDateTime, const char* title, const char* description, const char* attributes, MegaTimeStamp overrides, MegaScheduledFlags* flags, MegaScheduledRules* rules) { return new MegaScheduledMeetingPrivate(chatid, timezone, startDateTime, endDateTime, title, description, schedId, parentSchedId, organizerUserId, cancelled, attributes, overrides, flags, rules); } MegaScheduledMeeting::~MegaScheduledMeeting() {} int MegaScheduledMeeting::cancelled() const { return 0; } MegaHandle MegaScheduledMeeting::chatid() const { return INVALID_HANDLE; } MegaHandle MegaScheduledMeeting::schedId() const { return INVALID_HANDLE; } MegaHandle MegaScheduledMeeting::organizerUserid() const { return INVALID_HANDLE; } MegaHandle MegaScheduledMeeting::parentSchedId() const { return INVALID_HANDLE; } MegaScheduledMeeting* MegaScheduledMeeting::copy() const { return NULL; } const char* MegaScheduledMeeting::timezone() const { return NULL; } MegaTimeStamp MegaScheduledMeeting::startDateTime() const { return MEGA_INVALID_TIMESTAMP; } MegaTimeStamp MegaScheduledMeeting::endDateTime() const { return MEGA_INVALID_TIMESTAMP; } const char* MegaScheduledMeeting::title() const { return NULL; } const char* MegaScheduledMeeting::description() const { return NULL; } const char* MegaScheduledMeeting::attributes() const { return NULL; } MegaTimeStamp MegaScheduledMeeting::overrides() const { return MEGA_INVALID_TIMESTAMP; } MegaScheduledRules* MegaScheduledMeeting::rules() const { return NULL; } MegaScheduledFlags* MegaScheduledMeeting::flags() const { return NULL; } /* class MegaScheduledFlags */ MegaScheduledFlags* MegaScheduledFlags::createInstance() { return new MegaScheduledFlagsPrivate(); } void MegaScheduledFlags::importFlagsValue(unsigned long /*val*/) { } MegaScheduledFlags* MegaScheduledFlags::copy() const { return NULL; } MegaScheduledFlags::~MegaScheduledFlags() { } void MegaScheduledFlags::reset() {} bool MegaScheduledFlags::isEmpty() const { return false; } unsigned long MegaScheduledFlags::getNumericValue() const { return ScheduledFlags::schedEmptyFlags; } /* Class MegaScheduledRules */ MegaScheduledRules* MegaScheduledRules::createInstance(int freq, int interval, MegaTimeStamp until, const ::mega::MegaIntegerList* byWeekDay, const ::mega::MegaIntegerList* byMonthDay, const ::mega::MegaIntegerMap* byMonthWeekDay) { return new MegaScheduledRulesPrivate(freq, interval, until, byWeekDay, byMonthDay, byMonthWeekDay); } MegaScheduledRules::~MegaScheduledRules() {} MegaScheduledRules* MegaScheduledRules::copy() const { return NULL; } int MegaScheduledRules::freq() const { return 0; } int MegaScheduledRules::interval() const { return 0; } MegaTimeStamp MegaScheduledRules::until() const { return MEGA_INVALID_TIMESTAMP; } const mega::MegaIntegerList* MegaScheduledRules::byWeekDay() const { return nullptr; } const mega::MegaIntegerList* MegaScheduledRules::byMonthDay() const { return nullptr; } const mega::MegaIntegerMap* MegaScheduledRules::byMonthWeekDay() const { return nullptr; } bool MegaScheduledRules::isValidFreq(int freq) { return MegaScheduledRulesPrivate::isValidFreq(freq);} bool MegaScheduledRules::isValidInterval(int interval) { return MegaScheduledRulesPrivate::isValidInterval(interval);} /* Class MegaScheduledMeetingList */ MegaScheduledMeetingList* MegaScheduledMeetingList::createInstance() { return new MegaScheduledMeetingListPrivate(); } MegaScheduledMeetingList::~MegaScheduledMeetingList() { } MegaScheduledMeetingList* MegaScheduledMeetingList::copy() const { return NULL; } unsigned long MegaScheduledMeetingList::size() const { return 0; } MegaScheduledMeeting* MegaScheduledMeetingList::at(unsigned long) const { return NULL; } MegaScheduledMeeting* MegaScheduledMeetingList::getBySchedId(MegaHandle) const { return NULL; } void MegaScheduledMeetingList::insert(MegaScheduledMeeting*) {} void MegaScheduledMeetingList::clear() {} #endif void MegaApi::setCameraUploadsFolder(MegaHandle nodehandle, MegaRequestListener *listener) { pImpl->setCameraUploadsFolder(nodehandle, false, listener); } void MegaApi::setCameraUploadsFolders(MegaHandle primaryFolder, MegaHandle secondaryFolder, MegaRequestListener *listener) { pImpl->setCameraUploadsFolders(primaryFolder, secondaryFolder, listener); } void MegaApi::getCameraUploadsFolder(MegaRequestListener *listener) { pImpl->getCameraUploadsFolder(false, listener); } void MegaApi::getCameraUploadsFolderSecondary(MegaRequestListener *listener) { pImpl->getCameraUploadsFolder(true, listener); } void MegaApi::setMyChatFilesFolder(MegaHandle nodehandle, MegaRequestListener *listener) { pImpl->setMyChatFilesFolder(nodehandle, listener); } void MegaApi::getMyChatFilesFolder(MegaRequestListener *listener) { pImpl->getMyChatFilesFolder(listener); } void MegaApi::setMyBackupsFolder(const char *localizedName, MegaRequestListener *listener) { pImpl->setMyBackupsFolder(localizedName, listener); } void MegaApi::getUserAlias(MegaHandle uh, MegaRequestListener *listener) { pImpl->getUserAlias(uh, listener); } void MegaApi::setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener) { pImpl->setUserAlias(uh, alias, listener); } void MegaApi::getRubbishBinAutopurgePeriod(MegaRequestListener *listener) { pImpl->getRubbishBinAutopurgePeriod(listener); } void MegaApi::setRubbishBinAutopurgePeriod(int days, MegaRequestListener *listener) { pImpl->setRubbishBinAutopurgePeriod(days, listener); } const char* MegaApi::getDeviceId() const { return pImpl->getDeviceId(); } void MegaApi::getDeviceName(const char *deviceId, MegaRequestListener *listener) { pImpl->getDeviceName(deviceId, listener); } void MegaApi::setDeviceName(const char *deviceId, const char *deviceName, MegaRequestListener *listener) { pImpl->setDeviceName(deviceId, deviceName, listener); } void MegaApi::getDriveName(const char *pathToDrive, MegaRequestListener *listener) { pImpl->getDriveName(pathToDrive, listener); } void MegaApi::setDriveName(const char *pathToDrive, const char *driveName, MegaRequestListener *listener) { pImpl->setDriveName(pathToDrive, driveName, listener); } void MegaApi::changePassword(const char *oldPassword, const char *newPassword, MegaRequestListener *listener) { pImpl->changePassword(oldPassword, newPassword, listener); } #ifdef ENABLE_SYNC void MegaApi::logout(bool keepSyncConfigsFile, MegaRequestListener *listener) { pImpl->logout(keepSyncConfigsFile, listener); } #else void MegaApi::logout(MegaRequestListener *listener) { pImpl->logout(false, listener); } #endif void MegaApi::localLogout(MegaRequestListener *listener) { pImpl->localLogout(listener); } void MegaApi::invalidateCache() { pImpl->invalidateCache(); } int MegaApi::getPasswordStrength(const char *password) { return pImpl->getPasswordStrength(password); } char* MegaApi::generateRandomCharsPassword(bool uU, bool uD, bool uS, unsigned int l) { return MegaApiImpl::generateRandomCharsPassword(uU, uD, uS, l); } void MegaApi::submitFeedback(int rating, const char* comment, MegaRequestListener* listener) { pImpl->submitFeedback(rating, comment, false /*transferFeedback*/, TRANSFER_STATS_BOTH, listener); } void MegaApi::submitFeedbackForTransfers(int rating, const char* comment, int transferType, MegaRequestListener* listener) { pImpl->submitFeedback(rating, comment, true /*transferFeedback*/, transferType, listener); } void MegaApi::sendEvent(int eventType, const char *message, bool addJourneyId, const char *viewId, MegaRequestListener *listener) { pImpl->sendEvent(eventType, message, addJourneyId, viewId, listener); } void MegaApi::createSupportTicket(const char *message, int type, MegaRequestListener *listener) { pImpl->createSupportTicket(message, type, listener); } void MegaApi::reportDebugEvent(const char *text, MegaRequestListener *listener) { pImpl->reportEvent(text, listener); } void MegaApi::useHttpsOnly(bool, MegaRequestListener*) {} bool MegaApi::usingHttpsOnly() { return true; } void MegaApi::inviteContact(const char *email, const char *message, int action, MegaRequestListener *listener) { pImpl->inviteContact(email, message, action, UNDEF, listener); } void MegaApi::inviteContact(const char *email, const char *message, int action, MegaHandle contactLink, MegaRequestListener *listener) { pImpl->inviteContact(email, message, action, contactLink, listener); } void MegaApi::replyContactRequest(const MegaContactRequest* r, int action, MegaRequestListener* listener) { pImpl->replyContactRequest(r, action, listener); } void MegaApi::removeContact(MegaUser *user, MegaRequestListener* listener) { pImpl->removeContact(user, listener); } void MegaApi::pauseTransfers(bool pause, MegaRequestListener* listener) { pImpl->pauseTransfers(pause, -1, listener); } void MegaApi::pauseTransfers(bool pause, int direction, MegaRequestListener *listener) { pImpl->pauseTransfers(pause, direction, listener); } void MegaApi::pauseTransfer(MegaTransfer *transfer, bool pause, MegaRequestListener *listener) { pImpl->pauseTransfer(transfer ? transfer->getTag() : 0, pause, listener); } void MegaApi::pauseTransferByTag(int transferTag, bool pause, MegaRequestListener *listener) { pImpl->pauseTransfer(transferTag, pause, listener); } void MegaApi::resumeTransfersForNotLoggedInInstance() { pImpl->resumeTransfersForNotLoggedInInstance(); } bool MegaApi::areTransfersPaused(int direction) { return pImpl->areTransfersPaused(direction); } //-1 -> AUTO, 0 -> NONE, >0 -> b/s void MegaApi::setUploadLimit(int /*bpslimit*/) {} void MegaApi::setMaxConnections(int direction, int connections, MegaRequestListener *listener) { pImpl->setMaxConnections(direction, connections, listener); } void MegaApi::setMaxConnections(int connections, MegaRequestListener *listener) { pImpl->setMaxConnections(-1, connections, listener); } void MegaApi::getMaxUploadConnections(MegaRequestListener* const listener) { pImpl->getMaxUploadConnections(listener); } void MegaApi::getMaxDownloadConnections(MegaRequestListener* const listener) { pImpl->getMaxDownloadConnections(listener); } void MegaApi::setDownloadMethod(int method) { pImpl->setDownloadMethod(method); } void MegaApi::setUploadMethod(int method) { pImpl->setUploadMethod(method); } int MegaApi::getMaxDownloadSpeed() { return pImpl->getMaxDownloadSpeed(); } int MegaApi::getMaxUploadSpeed() { return pImpl->getMaxUploadSpeed(); } bool MegaApi::setMaxDownloadSpeed(long long bpslimit) { return pImpl->setMaxDownloadSpeed(bpslimit); } bool MegaApi::setMaxUploadSpeed(long long bpslimit) { return pImpl->setMaxUploadSpeed(bpslimit); } int MegaApi::getCurrentDownloadSpeed() { return pImpl->getCurrentDownloadSpeed(); } int MegaApi::getCurrentUploadSpeed() { return pImpl->getCurrentUploadSpeed(); } int MegaApi::getCurrentSpeed(int type) { return pImpl->getCurrentSpeed(type); } int MegaApi::getDownloadMethod() { return pImpl->getDownloadMethod(); } int MegaApi::getUploadMethod() { return pImpl->getUploadMethod(); } MegaTransferData *MegaApi::getTransferData(MegaTransferListener *listener) { return pImpl->getTransferData(listener); } MegaTransfer *MegaApi::getFirstTransfer(int type) { return pImpl->getFirstTransfer(type); } void MegaApi::notifyTransfer(MegaTransfer *transfer, MegaTransferListener *listener) { pImpl->notifyTransfer(transfer ? transfer->getTag() : 0, listener); } void MegaApi::notifyTransferByTag(int transferTag, MegaTransferListener *listener) { pImpl->notifyTransfer(transferTag, listener); } MegaTransferList *MegaApi::getTransfers() { return pImpl->getTransfers(); } MegaTransferList *MegaApi::getStreamingTransfers() { return pImpl->getStreamingTransfers(); } MegaTransfer* MegaApi::getTransferByUniqueId(uint32_t transferUniqueId) const { return pImpl->getTransferByUniqueId(transferUniqueId); } MegaTransfer *MegaApi::getTransferByTag(int transferTag) { return pImpl->getTransferByTag(transferTag); } MegaTransferList *MegaApi::getTransfers(int type) { return pImpl->getTransfers(type); } MegaTransferList *MegaApi::getChildTransfers(int transferTag) { return pImpl->getChildTransfers(transferTag); } void MegaApi::startUploadForSupport(const char* localPath, bool isSourceTemporary, MegaTransferListener *listener) { pImpl->startUploadForSupport(localPath, isSourceTemporary, FS_UNKNOWN, listener); } MegaStringList *MegaApi::getBackupFolders(int backuptag) const { return pImpl->getBackupFolders(backuptag); } void MegaApi::setScheduledCopy(const char* localPath, MegaNode* parent, bool attendPastBackups, int64_t period, const char *periodstring, int numBackups, MegaRequestListener *listener) { pImpl->setScheduledCopy(localPath, parent, attendPastBackups, period, periodstring ? periodstring : "", numBackups, listener); } void MegaApi::removeScheduledCopy(int tag, MegaRequestListener *listener) { pImpl->removeScheduledCopy(tag, listener); } void MegaApi::abortCurrentScheduledCopy(int tag, MegaRequestListener *listener) { pImpl->abortCurrentScheduledCopy(tag, listener); } void MegaApi::startTimer( int64_t period, MegaRequestListener *listener) { pImpl->startTimer(period, listener); } void MegaApi::startUpload(const char *localPath, MegaNode *parent, const char *fileName, int64_t mtime, const char *appData, bool isSourceTemporary, bool startFirst, MegaCancelToken *cancelToken, MegaTransferListener *listener) { MegaApiImpl::MegaUploadOptionsPrivate options; if (fileName) { options.mPublicOptions.fileName = fileName; } options.mPublicOptions.mtime = mtime; options.mPublicOptions.appData = appData; options.mPublicOptions.isSourceTemporary = isSourceTemporary; options.mPublicOptions.startFirst = startFirst; const std::string normalizedLocalPath = localPath ? localPath : ""; pImpl->startUpload(normalizedLocalPath, parent, convertToCancelToken(cancelToken), options, listener); } void MegaApi::startUploadForChat(const char *localPath, MegaNode *parent, const char *appData, bool isSourceTemporary, const char* fileName, MegaTransferListener *listener) { MegaApiImpl::MegaUploadOptionsPrivate options; if (fileName) { options.mPublicOptions.fileName = fileName; } options.mPublicOptions.appData = appData; options.mPublicOptions.isSourceTemporary = isSourceTemporary; options.mPublicOptions.startFirst = true; options.mForceNewUpload = true; const std::string normalizedLocalPath = localPath ? localPath : ""; pImpl->startUpload(normalizedLocalPath, parent, CancelToken(), options, listener); } void MegaApi::startUpload(const std::string& localPath, MegaNode* parent, MegaCancelToken* cancelToken, const MegaUploadOptions* options, MegaTransferListener* listener) { MegaApiImpl::MegaUploadOptionsPrivate localOptionsPrivate; if (options) { localOptionsPrivate.mPublicOptions = *options; } if (localOptionsPrivate.mPublicOptions.isChatUpload) { localOptionsPrivate.mPublicOptions.startFirst = true; localOptionsPrivate.mForceNewUpload = true; } pImpl->startUpload(localPath, parent, convertToCancelToken(cancelToken), localOptionsPrivate, listener); } void MegaApi::startDownload(MegaNode* node, const char* localPath, const char *customName, const char *appData, bool startFirst, MegaCancelToken *cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener *listener) { pImpl->startDownload(startFirst, node, localPath, customName, 0 /*folderTransferTag*/, appData, convertToCancelToken(cancelToken), collisionCheck, collisionResolution, undelete, listener); } void MegaApi::cancelTransfer(MegaTransfer *t, MegaRequestListener *listener) { pImpl->cancelTransfer(t, listener); } void MegaApi::retryTransfer(MegaTransfer *transfer, MegaTransferListener *listener) { pImpl->retryTransfer(transfer, listener); } void MegaApi::moveTransferUp(MegaTransfer *transfer, MegaRequestListener *listener) { pImpl->moveTransferUp(transfer ? transfer->getTag() : 0, listener); } void MegaApi::moveTransferUpByTag(int transferTag, MegaRequestListener *listener) { pImpl->moveTransferUp(transferTag, listener); } void MegaApi::moveTransferDown(MegaTransfer *transfer, MegaRequestListener *listener) { pImpl->moveTransferDown(transfer ? transfer->getTag() : 0, listener); } void MegaApi::moveTransferDownByTag(int transferTag, MegaRequestListener *listener) { pImpl->moveTransferDown(transferTag, listener); } void MegaApi::moveTransferToFirst(MegaTransfer *transfer, MegaRequestListener *listener) { pImpl->moveTransferToFirst(transfer ? transfer->getTag() : 0, listener); } void MegaApi::moveTransferToFirstByTag(int transferTag, MegaRequestListener *listener) { pImpl->moveTransferToFirst(transferTag, listener); } void MegaApi::moveTransferToLast(MegaTransfer *transfer, MegaRequestListener *listener) { pImpl->moveTransferToLast(transfer ? transfer->getTag() : 0, listener); } void MegaApi::moveTransferToLastByTag(int transferTag, MegaRequestListener *listener) { pImpl->moveTransferToLast(transferTag, listener); } void MegaApi::moveTransferBefore(MegaTransfer *transfer, MegaTransfer *prevTransfer, MegaRequestListener *listener) { pImpl->moveTransferBefore(transfer ? transfer->getTag() : 0, prevTransfer ? prevTransfer->getTag() : 0, listener); } void MegaApi::moveTransferBeforeByTag(int transferTag, int prevTransferTag, MegaRequestListener *listener) { pImpl->moveTransferBefore(transferTag, prevTransferTag, listener); } void MegaApi::cancelTransferByTag(int transferTag, MegaRequestListener *listener) { pImpl->cancelTransferByTag(transferTag, listener); } void MegaApi::cancelTransfers(int direction, MegaRequestListener *listener) { pImpl->cancelTransfers(direction, listener); } void MegaApi::startStreaming(MegaNode* node, int64_t startPos, int64_t size, MegaTransferListener *listener) { pImpl->startStreaming(node, startPos, size, listener); } void MegaApi::setStreamingMinimumRate(int bytesPerSecond) { pImpl->setStreamingMinimumRate(bytesPerSecond); } #ifdef ENABLE_SYNC int MegaApi::syncPathState(string* path) { return pImpl->syncPathState(path); } MegaNode *MegaApi::getSyncedNode(string *path) { return pImpl->getSyncedNode(LocalPath::fromPlatformEncodedAbsolute(*path)); } void MegaApi::syncFolder(const MegaSync::SyncType syncType, const char* localSyncRootFolder, const char* name, const MegaHandle remoteSyncRootFolder, const char* driveRootIfExternal, MegaRequestListener* const listener, const char* /* excludePath */) { syncFolder(syncType, std::string(nullToEmpty(localSyncRootFolder)), std::string(nullToEmpty(name)), remoteSyncRootFolder, std::string(nullToEmpty(driveRootIfExternal)), listener); } void MegaApi::syncFolder(const MegaSync::SyncType syncType, const std::string& localSyncRootFolder, const std::string& name, const MegaHandle remoteSyncRootFolder, const std::string& driveRootIfExternal, MegaRequestListener* const listener) { MegaRequestSyncFolderParams params{localSyncRootFolder, name, remoteSyncRootFolder, static_cast<SyncConfig::Type>(syncType), driveRootIfExternal}; pImpl->syncFolder(std::move(params), listener); } void MegaApi::prevalidateSyncFolder(const MegaSync::SyncType syncType, const std::string& localSyncRootFolder, const std::string& name, const MegaHandle remoteSyncRootFolder, const std::string& driveRootIfExternal, MegaRequestListener* const listener) { MegaRequestSyncFolderParams params{localSyncRootFolder, name, remoteSyncRootFolder, static_cast<SyncConfig::Type>(syncType), driveRootIfExternal}; pImpl->prevalidateSyncFolder(std::move(params), listener); } void MegaApi::loadExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener) { pImpl->loadExternalBackupSyncsFromExternalDrive(externalDriveRoot, listener); } void MegaApi::closeExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener) { pImpl->closeExternalBackupSyncsFromExternalDrive(externalDriveRoot, listener); } void MegaApi::copySyncDataToCache(const char *localFolder, const char *name, MegaHandle megaHandle, const char *remotePath, long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener *listener) { pImpl->copySyncDataToCache(localFolder, name, megaHandle, remotePath, localfp, enabled, temporaryDisabled, listener); } void MegaApi::copySyncDataToCache(const char *localFolder, MegaHandle megaHandle, const char *remotePath, long long localfp, bool enabled, bool temporaryDisabled, MegaRequestListener *listener) { pImpl->copySyncDataToCache(localFolder, nullptr, megaHandle, remotePath, localfp, enabled, temporaryDisabled, listener); } void MegaApi::copyCachedStatus(int storageStatus, int blockStatus, int businessStatus, MegaRequestListener *listener) { pImpl->copyCachedStatus(storageStatus, blockStatus, businessStatus, listener); } void MegaApi::removeSync(MegaHandle backupId, MegaRequestListener *listener) { pImpl->removeSyncById(backupId, listener); } void MegaApi::setSyncRunState(MegaHandle backupId, MegaSync::SyncRunningState targetState, MegaRequestListener *listener) { pImpl->setSyncRunState(backupId, targetState, listener); } void MegaApi::rescanSync(MegaHandle backupId, bool reFingerprint) { pImpl->rescanSync(backupId, reFingerprint); } void MegaApi::importSyncConfigs(const char* configs, MegaRequestListener* listener) { pImpl->importSyncConfigs(configs, listener); } const char* MegaApi::exportSyncConfigs() { return pImpl->exportSyncConfigs(); } MegaSyncList* MegaApi::getSyncs() { return pImpl->getSyncs(); } long long MegaApi::getNumLocalNodes() { return pImpl->getNumLocalNodes(); } void MegaApi::getMegaSyncStallList(MegaRequestListener* listener) { pImpl->getMegaSyncStallList(listener); } void MegaApi::getMegaSyncStallMap(MegaRequestListener* listener) { pImpl->getMegaSyncStallMap(listener); } void MegaApi::clearStalledPath(MegaSyncStall* stall) { pImpl->clearStalledPath(stall); } void MegaApi::moveToDebris(const char* path, MegaHandle syncBackupId, MegaRequestListener* listener) { pImpl->moveToDebris(path, syncBackupId, listener); } void MegaApi::changeSyncRemoteRoot(const MegaHandle syncBackupId, const MegaHandle newRootNodeHandle, MegaRequestListener* listener) { pImpl->changeSyncRemoteRoot(syncBackupId, newRootNodeHandle, listener); } void MegaApi::changeSyncLocalRoot(const MegaHandle syncBackupId, const char* newLocalSyncRootPath, MegaRequestListener* listener) { pImpl->changeSyncLocalRoot(syncBackupId, newLocalSyncRootPath, listener); } void MegaApi::setSyncUploadThrottleUpdateRate(const unsigned updateRateInSeconds, MegaRequestListener* const listener) { pImpl->setSyncUploadThrottleUpdateRate(updateRateInSeconds, listener); } void MegaApi::setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, MegaRequestListener* const listener) { pImpl->setSyncMaxUploadsBeforeThrottle(maxUploadsBeforeThrottle, listener); } void MegaApi::getSyncUploadThrottleValues(MegaRequestListener* const listener) { pImpl->getSyncUploadThrottleValues(listener); } void MegaApi::getSyncUploadThrottleLowerLimits(MegaRequestListener* const listener) { const bool upperLimits = false; pImpl->getSyncUploadThrottleLimits(upperLimits, listener); } void MegaApi::getSyncUploadThrottleUpperLimits(MegaRequestListener* const listener) { const bool upperLimits = true; pImpl->getSyncUploadThrottleLimits(upperLimits, listener); } void MegaApi::checkSyncUploadsThrottled(MegaRequestListener* const listener) { pImpl->checkSyncUploadsThrottled(listener); } MegaSync *MegaApi::getSyncByBackupId(MegaHandle backupId) { return pImpl->getSyncByBackupId(backupId); } MegaSync *MegaApi::getSyncByNode(MegaNode *node) { return pImpl->getSyncByNode(node); } MegaSync *MegaApi::getSyncByPath(const char *localPath) { return pImpl->getSyncByPath(localPath); } bool MegaApi::isScanning() { return pImpl->isScanning(); } bool MegaApi::isSyncing() { return pImpl->isSyncing(); } int MegaApi::isNodeSyncable(MegaNode *node) { return pImpl->isNodeSyncable(node); } MegaError *MegaApi::isNodeSyncableWithError(MegaNode* node) { return pImpl->isNodeSyncableWithError(node); } void MegaApi::setLegacyExcludedNames(vector<string> *excludedNames) { pImpl->setLegacyExcludedNames(excludedNames); } void MegaApi::setLegacyExcludedPaths(vector<string> *excludedPaths) { pImpl->setLegacyExcludedPaths(excludedPaths); } void MegaApi::setLegacyExclusionLowerSizeLimit(unsigned long long limit) { pImpl->setLegacyExclusionLowerSizeLimit(limit); } void MegaApi::setLegacyExclusionUpperSizeLimit(unsigned long long limit) { pImpl->setLegacyExclusionUpperSizeLimit(limit); } MegaError* MegaApi::exportLegacyExclusionRules(const char* absolutePath) { return pImpl->exportLegacyExclusionRules(absolutePath); } #endif void MegaApi::moveOrRemoveDeconfiguredBackupNodes(MegaHandle deconfiguredBackupRoot, MegaHandle backupDestination, MegaRequestListener *listener) { pImpl->moveOrRemoveDeconfiguredBackupNodes(deconfiguredBackupRoot, backupDestination, listener); } MegaScheduledCopy *MegaApi::getScheduledCopyByTag(int tag) { return pImpl->getScheduledCopyByTag(tag); } MegaScheduledCopy *MegaApi::getScheduledCopyByNode(MegaNode *node) { return pImpl->getScheduledCopyByNode(node); } MegaScheduledCopy *MegaApi::getScheduledCopyByPath(const char *localPath) { return pImpl->getScheduledCopyByPath(localPath); } MegaNode *MegaApi::getRootNode() { return pImpl->getRootNode(); } MegaNode *MegaApi::getVaultNode() { return pImpl->getVaultNode(); } MegaNode* MegaApi::getRubbishNode() { return pImpl->getRubbishNode(); } MegaNode *MegaApi::getRootNode(MegaNode *node) { return pImpl->getRootNode(node); } bool MegaApi::isInCloud(MegaNode *node) { return pImpl->isInRootnode(node, 0); } bool MegaApi::isInRubbish(MegaNode *node) { return pImpl->isInRootnode(node, 2); } bool MegaApi::isSensitiveInherited(MegaNode* node) { return pImpl->isSensitiveInherited(node); } bool MegaApi::isInVault(MegaNode *node) { return pImpl->isInRootnode(node, 1); } void MegaApi::setDefaultFilePermissions(int permissions) { pImpl->setDefaultFilePermissions(permissions); } int MegaApi::getDefaultFilePermissions() { return pImpl->getDefaultFilePermissions(); } void MegaApi::setDefaultFolderPermissions(int permissions) { pImpl->setDefaultFolderPermissions(permissions); } int MegaApi::getDefaultFolderPermissions() { return pImpl->getDefaultFolderPermissions(); } long long MegaApi::getBandwidthOverquotaDelay() { return pImpl->getBandwidthOverquotaDelay(); } MegaUserList* MegaApi::getContacts() { return pImpl->getContacts(); } MegaUser* MegaApi::getContact(const char* user) { return pImpl->getContact(user); } MegaUserAlertList* MegaApi::getUserAlerts() { return pImpl->getUserAlerts(); } int MegaApi::getNumUnreadUserAlerts() { return pImpl->getNumUnreadUserAlerts(); } MegaNodeList* MegaApi::getInShares(MegaUser *megaUser, int order) { return pImpl->getInShares(megaUser, order); } MegaNodeList* MegaApi::getInShares(int order) { return pImpl->getInShares(order); } MegaShareList* MegaApi::getInSharesList(int order) { return pImpl->getInSharesList(order); } MegaUser *MegaApi::getUserFromInShare(MegaNode *node, bool recurse) { return pImpl->getUserFromInShare(node, recurse); } bool MegaApi::isPendingShare(MegaNode *node) { return pImpl->isPendingShare(node); } MegaShareList *MegaApi::getOutShares(int order) { return pImpl->getOutShares(order); } MegaShareList* MegaApi::getOutShares(MegaNode *megaNode) { return pImpl->getOutShares(megaNode); } MegaShareList *MegaApi::getPendingOutShares() { return pImpl->getPendingOutShares(); } MegaShareList *MegaApi::getPendingOutShares(MegaNode *node) { return pImpl->getPendingOutShares(node); } bool MegaApi::isPrivateNode(MegaHandle handle) { return pImpl->isPrivateNode(handle); } bool MegaApi::isForeignNode(MegaHandle handle) { return pImpl->isForeignNode(handle); } MegaNodeList *MegaApi::getPublicLinks(int order) { return pImpl->getPublicLinks(order); } MegaContactRequestList* MegaApi::getIncomingContactRequests() const { return pImpl->getIncomingContactRequests(); } MegaContactRequestList* MegaApi::getOutgoingContactRequests() const { return pImpl->getOutgoingContactRequests(); } int MegaApi::getAccess(MegaNode* megaNode) { return pImpl->getAccess(megaNode); } int MegaApi::getAccess(MegaHandle handle) { return pImpl->getAccess(handle); } void MegaApi::getRecentActionsAsync(unsigned days, unsigned maxnodes, MegaRequestListener *listener) { pImpl->getRecentActionsAsync(days, maxnodes, listener); } void MegaApi::getRecentActionsAsync(unsigned days, unsigned maxnodes, bool excludeSensitives, MegaRequestListener* listener) { pImpl->getRecentActionsAsync(days, maxnodes, excludeSensitives, listener); } void MegaApi::getRecentActionById(const char* id, MegaRequestListener* listener) { pImpl->getRecentActionById(id, listener); } void MegaApi::getRecentActionById(const char* id, bool excludeSensitives, MegaRequestListener* listener) { pImpl->getRecentActionById(id, excludeSensitives, listener); } void MegaApi::clearRecentActionHistory(MegaTimeStamp until, MegaRequestListener* listener) { pImpl->clearRecentActionHistory(until, listener); } bool MegaApi::processMegaTree(MegaNode* n, MegaTreeProcessor* processor, bool recursive) { return pImpl->processMegaTree(n, processor, recursive); } MegaNode *MegaApi::createForeignFileNode(MegaHandle handle, const char *key, const char *name, int64_t size, int64_t mtime, const char* fingerprintCrc, MegaHandle parentHandle, const char *privateAuth, const char *publicAuth, const char *chatAuth) { return pImpl->createForeignFileNode(handle, key, name, size, mtime, fingerprintCrc, parentHandle, privateAuth, publicAuth, chatAuth); } void MegaApi::getLastAvailableVersion(const char *appKey, MegaRequestListener *listener) { pImpl->getLastAvailableVersion(appKey, listener); } void MegaApi::getLocalSSLCertificate(MegaRequestListener *listener) { pImpl->getLocalSSLCertificate(listener); } void MegaApi::queryDNS(const char *hostname, MegaRequestListener *listener) { pImpl->queryDNS(hostname, listener); } void MegaApi::downloadFile(const char *url, const char *dstpath, MegaRequestListener *listener) { pImpl->downloadFile(url, dstpath, listener); } MegaNode *MegaApi::createForeignFolderNode(MegaHandle handle, const char *name, MegaHandle parentHandle, const char *privateAuth, const char *publicAuth) { return pImpl->createForeignFolderNode(handle, name, parentHandle, privateAuth, publicAuth); } MegaNode *MegaApi::authorizeNode(MegaNode *node) { return pImpl->authorizeNode(node); } #ifdef ENABLE_CHAT MegaNode *MegaApi::authorizeChatNode(MegaNode *node, const char *cauth) { return pImpl->authorizeChatNode(node, cauth); } #endif const char *MegaApi::getVersion() { return pImpl->getVersion(); } char *MegaApi::getOperatingSystemVersion() { return pImpl->getOperatingSystemVersion(); } const char *MegaApi::getUserAgent() { return pImpl->getUserAgent(); } const char *MegaApi::getBasePath() { return pImpl->getBasePath(); } void MegaApi::disableGfxFeatures(bool disable) { pImpl->disableGfxFeatures(disable); } bool MegaApi::areGfxFeaturesDisabled() { return pImpl->areGfxFeaturesDisabled(); } void MegaApi::changeApiUrl(const char *apiURL, bool disablepkp) { pImpl->changeApiUrl(apiURL, disablepkp); } bool MegaApi::setLanguage(const char *languageCode) { return pImpl->setLanguage(languageCode); } int MegaApi::enableSearchDBIndexes(bool enable) { return pImpl->enableSearchDBIndexes(enable); } int MegaApi::enableLexicographicDBIndexes(bool enable) { return pImpl->enableLexicographicDBIndexes(enable); } const char* MegaApi::generateViewId() { return strdup(pImpl->generateViewId().c_str()); } void MegaApi::setLanguagePreference(const char *languageCode, MegaRequestListener *listener) { pImpl->setLanguagePreference(languageCode, listener); } void MegaApi::getLanguagePreference(MegaRequestListener *listener) { pImpl->getLanguagePreference(listener); } void MegaApi::setFileVersionsOption(bool disable, MegaRequestListener *listener) { pImpl->setFileVersionsOption(disable, listener); } void MegaApi::setContactLinksOption(bool enable, MegaRequestListener* listener) { pImpl->setContactLinksOption(enable, listener); } void MegaApi::getFileVersionsOption(MegaRequestListener *listener) { pImpl->getFileVersionsOption(listener); } void MegaApi::getContactLinksOption(MegaRequestListener *listener) { pImpl->getContactLinksOption(listener); } void MegaApi::retrySSLerrors(bool enable) { pImpl->retrySSLerrors(enable); } void MegaApi::setPublicKeyPinning(bool enable) { pImpl->setPublicKeyPinning(enable); } void MegaApi::pauseActionPackets() { pImpl->pauseActionPackets(); } void MegaApi::resumeActionPackets() { pImpl->resumeActionPackets(); } char *MegaApi::base64ToBase32(const char *base64) { if(!base64) { return NULL; } unsigned binarylen = unsigned(strlen(base64) * 3/4 + 4); byte *binary = new byte[binarylen]; binarylen = static_cast<unsigned>(Base64::atob(base64, binary, static_cast<int>(binarylen))); char *result = new char[binarylen * 8/5 + 6]; Base32::btoa(binary, static_cast<int>(binarylen), result); delete [] binary; return result; } char *MegaApi::base32ToBase64(const char *base32) { if(!base32) { return NULL; } size_t binarylen = strlen(base32) * 5 / 8 + 8; byte *binary = new byte[binarylen]; int decodedLen = Base32::atob(base32, binary, static_cast<int>(binarylen)); if (decodedLen < 0) { return nullptr; } binarylen = static_cast<size_t>(decodedLen); char *result = new char[binarylen * 4/3 + 4]; Base64::btoa(binary, binarylen, result); delete [] binary; return result; } MegaNodeList* MegaApi::search(const MegaSearchFilter* filter, int order, MegaCancelToken* cancelToken, const MegaSearchPage* searchPage) { return pImpl->search(filter, order, convertToCancelToken(cancelToken), searchPage); } long long MegaApi::getSize(MegaNode *n) { return pImpl->getSize(n); } char *MegaApi::getFingerprint(const char *filePath) { return pImpl->getFingerprint(filePath); } char *MegaApi::getFingerprint(MegaInputStream *inputStream, int64_t mtime) { return pImpl->getFingerprint(inputStream, mtime); } MegaNode *MegaApi::getNodeByFingerprint(const char *fingerprint) { return pImpl->getNodeByFingerprint(fingerprint); } MegaNode *MegaApi::getNodeByFingerprint(const char *fingerprint, MegaNode *parent) { return pImpl->getNodeByFingerprint(fingerprint, parent); } MegaNodeList *MegaApi::getNodesByFingerprint(const char *fingerprint) { return pImpl->getNodesByFingerprint(fingerprint); } MegaNodeList* MegaApi::getNodesByFingerprintIgnoringMtime(const char* fingerprint) { return pImpl->getNodesByFingerprint(fingerprint, true); } MegaNodeList *MegaApi::getNodesByOriginalFingerprint(const char* originalFingerprint, MegaNode* parent) { return pImpl->getNodesByOriginalFingerprint(originalFingerprint, parent); } MegaNode *MegaApi::getExportableNodeByFingerprint(const char *fingerprint, const char *name) { return pImpl->getExportableNodeByFingerprint(fingerprint, name); } bool MegaApi::hasFingerprint(const char *fingerprint) { return pImpl->hasFingerprint(fingerprint); } char *MegaApi::getCRC(const char *filePath) { return pImpl->getCRC(filePath); } char *MegaApi::getCRCFromFingerprint(const char *fingerprint) { return pImpl->getCRCFromFingerprint(fingerprint); } char *MegaApi::getCRC(MegaNode *node) { return pImpl->getCRC(node); } MegaNode *MegaApi::getNodeByCRC(const char *crc, MegaNode *parent) { return pImpl->getNodeByCRC(crc, parent); } void MegaApi::addListener(MegaListener* listener) { pImpl->addListener(listener); } void MegaApi::addRequestListener(MegaRequestListener* listener) { pImpl->addRequestListener(listener); } void MegaApi::addTransferListener(MegaTransferListener* listener) { pImpl->addTransferListener(listener); } void MegaApi::addGlobalListener(MegaGlobalListener* listener) { pImpl->addGlobalListener(listener); } void MegaApi::addScheduledCopyListener(MegaScheduledCopyListener *listener) { pImpl->addScheduledCopyListener(listener); } bool MegaApi::removeScheduledCopyListener(MegaScheduledCopyListener *listener) { return pImpl->removeScheduledCopyListener(listener); } bool MegaApi::removeListener(MegaListener* listener) { return pImpl->removeListener(listener); } bool MegaApi::removeRequestListener(MegaRequestListener* listener) { return pImpl->removeRequestListener(listener); } bool MegaApi::removeTransferListener(MegaTransferListener* listener) { return pImpl->removeTransferListener(listener); } bool MegaApi::removeGlobalListener(MegaGlobalListener* listener) { return pImpl->removeGlobalListener(listener); } MegaError *MegaApi::checkAccessErrorExtended(MegaNode *node, int level) { return pImpl->checkAccessErrorExtended(node, level); } MegaError *MegaApi::checkMoveErrorExtended(MegaNode *node, MegaNode *target) { return pImpl->checkMoveErrorExtended(node, target); } bool MegaApi::isFilesystemAvailable() { return pImpl->isFilesystemAvailable(); } int MegaApi::getNumChildren(MegaNode* parent) { return pImpl->getNumChildren(parent); } int MegaApi::getNumChildFiles(MegaNode* parent) { return pImpl->getNumChildFiles(parent); } int MegaApi::getNumChildFolders(MegaNode* parent) { return pImpl->getNumChildFolders(parent); } MegaNodeList *MegaApi::getChildren(const MegaSearchFilter* filter, int order, MegaCancelToken* cancelToken, const MegaSearchPage* searchPage) { return pImpl->getChildren(filter, order, convertToCancelToken(cancelToken), searchPage); } MegaNodeList *MegaApi::getChildren(MegaNode* p, int order, MegaCancelToken* cancelToken) { return pImpl->getChildren(p, order, convertToCancelToken(cancelToken)); } MegaNodeList *MegaApi::getChildren(MegaNodeList *parentNodes, int order) { return pImpl->getChildren(parentNodes, order); } MegaNodeList* MegaApi::listChildNodesLexicographically( const MegaHandle parenthandle, MegaCancelToken* cancelToken, const size_t maxElements, const std::optional<MegaSearchLexicographicalOffset>& offset) { return pImpl->listChildNodesLexicographically(parenthandle, convertToCancelToken(cancelToken), maxElements, offset); } MegaNodeList *MegaApi::getVersions(MegaNode *node) { return pImpl->getVersions(node); } int MegaApi::getNumVersions(MegaNode *node) { return pImpl->getNumVersions(node); } bool MegaApi::hasVersions(MegaNode *node) { return pImpl->hasVersions(node); } void MegaApi::getFolderInfo(MegaNode *node, MegaRequestListener *listener) { pImpl->getFolderInfo(node, listener); } bool MegaApi::hasChildren(MegaNode *parent) { return pImpl->hasChildren(parent); } MegaNode *MegaApi::getChildNode(MegaNode *parent, const char* name) { return pImpl->getChildNode(parent, name); } MegaNode* MegaApi::getChildNodeOfType(MegaNode *parent, const char *name, int type) { return pImpl->getChildNodeOfType(parent, name, type); } MegaNode* MegaApi::getParentNode(MegaNode* n) { return pImpl->getParentNode(n); } char *MegaApi::getNodePath(MegaNode *node) { return pImpl->getNodePath(node); } char *MegaApi::getNodePathByNodeHandle(MegaHandle handle) { return pImpl->getNodePathByNodeHandle(handle); } MegaNode* MegaApi::getNodeByPath(const char *path, MegaNode* node) { return pImpl->getNodeByPath(path, node); } MegaNode* MegaApi::getNodeByPathOfType(const char* path, MegaNode* n, int type) { return pImpl->getNodeByPathOfType(path, n, type); } MegaNode* MegaApi::getNodeByHandle(uint64_t h) { return pImpl->getNodeByHandle(h); } MegaTotpTokenGenResult MegaApi::generateTotpTokenFromNode(MegaHandle handle) { return pImpl->generateTotpTokenFromNode(handle); } MegaContactRequest *MegaApi::getContactRequestByHandle(MegaHandle handle) { return pImpl->getContactRequestByHandle(handle); } unsigned long long MegaApi::getNumNodes() { return pImpl->getNumNodes(); } unsigned long long MegaApi::getAccurateNumNodes() { return pImpl->getAccurateNumNodes(); } void MegaApi::setLRUCacheSize(unsigned long long size) { pImpl->setLRUCacheSize(size); } unsigned long long MegaApi::getNumNodesAtCacheLRU() const { return pImpl->getNumNodesAtCacheLRU(); } int MegaApi::isWaiting() { return pImpl->isWaiting(); } bool MegaApi::isSyncStalled() { return pImpl->isSyncStalled(); } bool MegaApi::isSyncStalledChanged() { return pImpl->isSyncStalledChanged(); } void MegaApi::removeRecursively(const char *path) { MegaApiImpl::removeRecursively(path); } bool MegaApi::isOnline() { return pImpl->isOnline(); } void MegaApi::getAccountAchievements(MegaRequestListener *listener) { pImpl->getAccountAchievements(listener); } void MegaApi::getMegaAchievements(MegaRequestListener *listener) { pImpl->getMegaAchievements(listener); } void MegaApi::catchup(MegaRequestListener *listener) { pImpl->catchup(listener); } void MegaApi::getPublicLinkInformation(const char *megaFolderLink, MegaRequestListener *listener) { pImpl->getPublicLinkInformation(megaFolderLink, listener); } MegaApiLock* MegaApi::getMegaApiLock(bool lockNow) { return new MegaApiLock(pImpl, lockNow); } bool MegaApi::platformSetRLimitNumFile(int newNumFileLimit) const { return mega::platformSetRLimitNumFile(newNumFileLimit); } int MegaApi::platformGetRLimitNumFile() const { return mega::platformGetRLimitNumFile(); } void MegaApi::sendSMSVerificationCode(const char* phoneNumber, MegaRequestListener *listener, bool reverifying_whitelisted) { pImpl->sendSMSVerificationCode(phoneNumber, listener, reverifying_whitelisted); } void MegaApi::checkSMSVerificationCode(const char* verificationCode, MegaRequestListener *listener) { pImpl->checkSMSVerificationCode(verificationCode, listener); } void MegaApi::getCountryCallingCodes(MegaRequestListener *listener) { pImpl->getCountryCallingCodes(listener); } #ifdef HAVE_LIBUV bool MegaApi::httpServerStart(bool localOnly, int port, bool useTLS, const char * certificatepath, const char * keypath, bool useIPv6) { return pImpl->httpServerStart(localOnly, port, useTLS, certificatepath, keypath, useIPv6); } void MegaApi::httpServerStop() { pImpl->httpServerStop(); } int MegaApi::httpServerIsRunning() { return pImpl->httpServerIsRunning(); } bool MegaApi::httpServerIsLocalOnly() { return pImpl->httpServerIsLocalOnly(); } void MegaApi::httpServerEnableFileServer(bool enable) { pImpl->httpServerEnableFileServer(enable); } bool MegaApi::httpServerIsFileServerEnabled() { return pImpl->httpServerIsFileServerEnabled(); } void MegaApi::httpServerEnableFolderServer(bool enable) { pImpl->httpServerEnableFolderServer(enable); } void MegaApi::httpServerEnableOfflineAttribute(bool enable) { pImpl->httpServerEnableOfflineAttribute(enable); } bool MegaApi::httpServerIsOfflineAttributeEnabled() { return pImpl->httpServerIsOfflineAttributeEnabled(); } bool MegaApi::httpServerIsFolderServerEnabled() { return pImpl->httpServerIsFolderServerEnabled(); } void MegaApi::httpServerSetRestrictedMode(int mode) { pImpl->httpServerSetRestrictedMode(mode); } int MegaApi::httpServerGetRestrictedMode() { return pImpl->httpServerGetRestrictedMode(); } void MegaApi::httpServerEnableSubtitlesSupport(bool enable) { pImpl->httpServerEnableSubtitlesSupport(enable); } bool MegaApi::httpServerIsSubtitlesSupportEnabled() { return pImpl->httpServerIsSubtitlesSupportEnabled(); } void MegaApi::httpServerAddListener(MegaTransferListener *listener) { pImpl->httpServerAddListener(listener); } void MegaApi::httpServerRemoveListener(MegaTransferListener *listener) { pImpl->httpServerRemoveListener(listener); } char *MegaApi::httpServerGetLocalLink(MegaNode *node) { return pImpl->httpServerGetLocalLink(node); } char *MegaApi::httpServerGetLocalWebDavLink(MegaNode *node) { return pImpl->httpServerGetLocalWebDavLink(node); } MegaStringList *MegaApi::httpServerGetWebDavLinks() { return pImpl->httpServerGetWebDavLinks(); } MegaNodeList *MegaApi::httpServerGetWebDavAllowedNodes() { return pImpl->httpServerGetWebDavAllowedNodes(); } void MegaApi::httpServerRemoveWebDavAllowedNode(MegaHandle handle) { pImpl->httpServerRemoveWebDavAllowedNode(handle); } void MegaApi::httpServerRemoveWebDavAllowedNodes() { pImpl->httpServerRemoveWebDavAllowedNodes(); } void MegaApi::httpServerSetMaxBufferSize(int bufferSize) { pImpl->httpServerSetMaxBufferSize(bufferSize); } int MegaApi::httpServerGetMaxBufferSize() { return pImpl->httpServerGetMaxBufferSize(); } void MegaApi::httpServerSetMaxOutputSize(int outputSize) { pImpl->httpServerSetMaxOutputSize(outputSize); } int MegaApi::httpServerGetMaxOutputSize() { return pImpl->httpServerGetMaxOutputSize(); } //FTP Server: bool MegaApi::ftpServerStart(bool localOnly, int port, int dataportBegin, int dataPortEnd, bool useTLS, const char * certificatepath, const char * keypath) { return pImpl->ftpServerStart(localOnly, port, dataportBegin, dataPortEnd, useTLS, certificatepath, keypath); } void MegaApi::ftpServerStop() { pImpl->ftpServerStop(); } int MegaApi::ftpServerIsRunning() { return pImpl->ftpServerIsRunning(); } bool MegaApi::ftpServerIsLocalOnly() { return pImpl->ftpServerIsLocalOnly(); } void MegaApi::ftpServerSetRestrictedMode(int mode) { pImpl->ftpServerSetRestrictedMode(mode); } int MegaApi::ftpServerGetRestrictedMode() { return pImpl->ftpServerGetRestrictedMode(); } void MegaApi::ftpServerAddListener(MegaTransferListener *listener) { pImpl->ftpServerAddListener(listener); } void MegaApi::ftpServerRemoveListener(MegaTransferListener *listener) { pImpl->ftpServerRemoveListener(listener); } char *MegaApi::ftpServerGetLocalLink(MegaNode *node) { return pImpl->ftpServerGetLocalLink(node); } MegaStringList *MegaApi::ftpServerGetLinks() { return pImpl->ftpServerGetLinks(); } MegaNodeList *MegaApi::ftpServerGetAllowedNodes() { return pImpl->ftpServerGetAllowedNodes(); } void MegaApi::ftpServerRemoveAllowedNode(MegaHandle handle) { pImpl->ftpServerRemoveAllowedNode(handle); } void MegaApi::ftpServerRemoveAllowedNodes() { pImpl->ftpServerRemoveAllowedNodes(); } void MegaApi::ftpServerSetMaxBufferSize(int bufferSize) { pImpl->ftpServerSetMaxBufferSize(bufferSize); } int MegaApi::ftpServerGetMaxBufferSize() { return pImpl->ftpServerGetMaxBufferSize(); } void MegaApi::ftpServerSetMaxOutputSize(int outputSize) { pImpl->ftpServerSetMaxOutputSize(outputSize); } int MegaApi::ftpServerGetMaxOutputSize() { return pImpl->ftpServerGetMaxOutputSize(); } #endif char *MegaApi::getMimeType(const char *extension) { if (!extension) { return NULL; } if (*extension == '.') { extension++; } static const map<string, string> mimeMap{ // list copied from extmime in Webclient's filetypes.js // from c++11, this sort of static local initialization is one-time and thread safe {"3ds", "image/x-3ds"}, {"3g2", "video/3gpp2"}, {"3gp", "video/3gpp"}, {"7z", "application/x-7z-compressed"}, {"aac", "audio/x-aac"}, {"abw", "application/x-abiword"}, {"ace", "application/x-ace-compressed"}, {"adp", "audio/adpcm"}, {"aif", "audio/x-aiff"}, {"aifc", "audio/x-aiff"}, {"aiff", "audio/x-aiff"}, {"apk", "application/vnd.android.package-archive"}, {"asf", "video/x-ms-asf"}, {"asx", "video/x-ms-asf"}, {"atom", "application/atom+xml"}, {"au", "audio/basic"}, {"avi", "video/x-msvideo"}, {"avif", "image/avif"}, {"bat", "application/x-msdownload"}, {"bmp", "image/bmp"}, {"btif", "image/prs.btif"}, {"bz2", "application/x-bzip2"}, {"caf", "audio/x-caf"}, {"cgm", "image/cgm"}, {"cmx", "image/x-cmx"}, {"com", "application/x-msdownload"}, {"conf", "text/plain"}, {"css", "text/css"}, {"csv", "text/csv"}, {"dbk", "application/docbook+xml"}, {"deb", "application/x-debian-package"}, {"def", "text/plain"}, {"djv", "image/vnd.djvu"}, {"djvu", "image/vnd.djvu"}, {"dll", "application/x-msdownload"}, {"dmg", "application/x-apple-diskimage"}, {"doc", "application/msword"}, {"docm", "application/vnd.ms-word.document.macroenabled.12"}, {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, {"dot", "application/msword"}, {"dotm", "application/vnd.ms-word.template.macroenabled.12"}, {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, {"dra", "audio/vnd.dra"}, {"dtd", "application/xml-dtd"}, {"dts", "audio/vnd.dts"}, {"dtshd", "audio/vnd.dts.hd"}, {"dvb", "video/vnd.dvb.file"}, {"dwg", "image/vnd.dwg"}, {"dxf", "image/vnd.dxf"}, {"ecelp4800", "audio/vnd.nuera.ecelp4800"}, {"ecelp7470", "audio/vnd.nuera.ecelp7470"}, {"ecelp9600", "audio/vnd.nuera.ecelp9600"}, {"emf", "application/x-msmetafile"}, {"emz", "application/x-msmetafile"}, {"eol", "audio/vnd.digital-winds"}, {"epub", "application/epub+zip"}, {"exe", "application/x-msdownload"}, {"f4v", "video/x-f4v"}, {"fbs", "image/vnd.fastbidsheet"}, {"fh", "image/x-freehand"}, {"fh4", "image/x-freehand"}, {"fh5", "image/x-freehand"}, {"fh7", "image/x-freehand"}, {"fhc", "image/x-freehand"}, {"flac", "audio/x-flac"}, {"fli", "video/x-fli"}, {"flv", "video/x-flv"}, {"fpx", "image/vnd.fpx"}, {"fst", "image/vnd.fst"}, {"fvt", "video/vnd.fvt"}, {"g3", "image/g3fax"}, {"gif", "image/gif"}, {"gz", "application/x-gzip"}, {"h261", "video/h261"}, {"h263", "video/h263"}, {"h264", "video/h264"}, {"heic", "image/heic"}, {"heif", "image/heif"}, {"htm", "text/html"}, {"html", "text/html"}, {"ico", "image/x-icon"}, {"ief", "image/ief"}, {"iso", "application/x-iso9660-image"}, {"jpe", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"jpg", "image/jpeg"}, {"jpgm", "video/jpm"}, {"jpgv", "video/jpeg"}, {"jpm", "video/jpm"}, {"json", "application/json"}, {"jsonml", "application/jsonml+json"}, {"jxl", "image/jxl"}, {"kar", "audio/midi"}, {"ktx", "image/ktx"}, {"list", "text/plain"}, {"log", "text/plain"}, {"lvp", "audio/vnd.lucent.voice"}, {"m13", "application/x-msmediaview"}, {"m14", "application/x-msmediaview"}, {"m1v", "video/mpeg"}, {"m21", "application/mp21"}, {"m2a", "audio/mpeg"}, {"m2v", "video/mpeg"}, {"m3a", "audio/mpeg"}, {"m3u", "audio/x-mpegurl"}, {"m3u8", "application/vnd.apple.mpegurl"}, {"m4a", "audio/mp4"}, {"m4u", "video/vnd.mpegurl"}, {"m4v", "video/x-m4v"}, {"mdi", "image/vnd.ms-modi"}, {"mid", "audio/midi"}, {"midi", "audio/midi"}, {"mj2", "video/mj2"}, {"mjp2", "video/mj2"}, {"mk3d", "video/x-matroska"}, {"mka", "audio/x-matroska"}, {"mks", "video/x-matroska"}, {"mkv", "video/x-matroska"}, {"mmr", "image/vnd.fujixerox.edmics-mmr"}, {"mng", "video/x-mng"}, {"mov", "video/quicktime"}, {"movie", "video/x-sgi-movie"}, {"mp2", "audio/mpeg"}, {"mp21", "application/mp21"}, {"mp2a", "audio/mpeg"}, {"mp3", "audio/mpeg"}, {"mp4", "video/mp4"}, {"mp4a", "audio/mp4"}, {"mp4s", "application/mp4"}, {"mp4v", "video/mp4"}, {"mpe", "video/mpeg"}, {"mpeg", "video/mpeg"}, {"mpg", "video/mpeg"}, {"mpg4", "video/mp4"}, {"mpga", "audio/mpeg"}, {"mpkg", "application/vnd.apple.installer+xml"}, {"msi", "application/x-msdownload"}, {"mvb", "application/x-msmediaview"}, {"mxf", "application/mxf"}, {"mxml", "application/xv+xml"}, {"mxu", "video/vnd.mpegurl"}, {"npx", "image/vnd.net-fpx"}, {"odb", "application/vnd.oasis.opendocument.database"}, {"odc", "application/vnd.oasis.opendocument.chart"}, {"odf", "application/vnd.oasis.opendocument.formula"}, {"odft", "application/vnd.oasis.opendocument.formula-template"}, {"odg", "application/vnd.oasis.opendocument.graphics"}, {"odi", "application/vnd.oasis.opendocument.image"}, {"odm", "application/vnd.oasis.opendocument.text-master"}, {"odp", "application/vnd.oasis.opendocument.presentation"}, {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, {"odt", "application/vnd.oasis.opendocument.text"}, {"oga", "audio/ogg"}, {"ogg", "audio/ogg"}, {"ogv", "video/ogg"}, {"ogx", "application/ogg"}, {"otc", "application/vnd.oasis.opendocument.chart-template"}, {"otf", "application/octet-stream"}, {"otg", "application/vnd.oasis.opendocument.graphics-template"}, {"oth", "application/vnd.oasis.opendocument.text-web"}, {"oti", "application/vnd.oasis.opendocument.image-template"}, {"otp", "application/vnd.oasis.opendocument.presentation-template"}, {"ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, {"ott", "application/vnd.oasis.opendocument.text-template"}, {"oxt", "application/vnd.openofficeorg.extension"}, {"pbm", "image/x-portable-bitmap"}, {"pct", "image/x-pict"}, {"pcx", "image/x-pcx"}, {"pdf", "application/pdf"}, {"pgm", "image/x-portable-graymap"}, {"pic", "image/x-pict"}, {"plb", "application/vnd.3gpp.pic-bw-large"}, {"png", "image/png"}, {"pnm", "image/x-portable-anymap"}, {"pot", "application/vnd.ms-powerpoint"}, {"potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, {"ppm", "image/x-portable-pixmap"}, {"pps", "application/vnd.ms-powerpoint"}, {"ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, {"ppt", "application/vnd.ms-powerpoint"}, {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, {"psb", "application/vnd.3gpp.pic-bw-small"}, {"psd", "image/vnd.adobe.photoshop"}, {"pvb", "application/vnd.3gpp.pic-bw-var"}, {"pya", "audio/vnd.ms-playready.media.pya"}, {"pyv", "video/vnd.ms-playready.media.pyv"}, {"qt", "video/quicktime"}, {"ra", "audio/x-pn-realaudio"}, {"ram", "audio/x-pn-realaudio"}, {"rar", "application/octet-stream"}, {"ras", "image/x-cmu-raster"}, {"rgb", "image/x-rgb"}, {"rip", "audio/vnd.rip"}, {"rlc", "image/vnd.fujixerox.edmics-rlc"}, {"rmi", "audio/midi"}, {"rmp", "audio/x-pn-realaudio-plugin"}, {"s3m", "audio/s3m"}, {"sgi", "image/sgi"}, {"sgm", "text/sgml"}, {"sgml", "text/sgml"}, {"sh", "application/x-sh"}, {"sid", "image/x-mrsid-image"}, {"sil", "audio/silk"}, {"sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, {"smv", "video/x-smv"}, {"snd", "audio/basic"}, {"spx", "audio/ogg"}, {"srt", "application/x-subrip"}, {"sub", "text/vnd.dvb.subtitle"}, {"svg", "image/svg+xml"}, {"svgz", "image/svg+xml"}, {"swf", "application/x-shockwave-flash"}, {"tar", "application/x-tar"}, {"tcap", "application/vnd.3gpp2.tcap"}, {"text", "text/plain"}, {"tga", "image/x-tga"}, {"tif", "image/tiff"}, {"tiff", "image/tiff"}, {"torrent", "application/x-bittorrent"}, {"ts", "video/mp2t"}, {"tsv", "text/tab-separated-values"}, {"ttf", "application/octet-stream"}, {"ttl", "text/turtle"}, {"txt", "text/plain"}, {"udeb", "application/x-debian-package"}, {"uva", "audio/vnd.dece.audio"}, {"uvg", "image/vnd.dece.graphic"}, {"uvh", "video/vnd.dece.hd"}, {"uvi", "image/vnd.dece.graphic"}, {"uvm", "video/vnd.dece.mobile"}, {"uvp", "video/vnd.dece.pd"}, {"uvs", "video/vnd.dece.sd"}, {"uvu", "video/vnd.uvvu.mp4"}, {"uvv", "video/vnd.dece.video"}, {"uvva", "audio/vnd.dece.audio"}, {"uvvg", "image/vnd.dece.graphic"}, {"uvvh", "video/vnd.dece.hd"}, {"uvvi", "image/vnd.dece.graphic"}, {"uvvm", "video/vnd.dece.mobile"}, {"uvvp", "video/vnd.dece.pd"}, {"uvvs", "video/vnd.dece.sd"}, {"uvvu", "video/vnd.uvvu.mp4"}, {"uvvv", "video/vnd.dece.video"}, {"viv", "video/vnd.vivo"}, {"vob", "video/x-ms-vob"}, {"wav", "audio/x-wav"}, {"wax", "audio/x-ms-wax"}, {"wbmp", "image/vnd.wap.wbmp"}, {"wdp", "image/vnd.ms-photo"}, {"weba", "audio/webm"}, {"webm", "video/webm"}, {"webp", "image/webp"}, {"wm", "video/x-ms-wm"}, {"wma", "audio/x-ms-wma"}, {"wmf", "application/x-msmetafile"}, {"wmv", "video/x-ms-wmv"}, {"wmx", "video/x-ms-wmx"}, {"wvx", "video/x-ms-wvx"}, {"xap", "application/x-silverlight-app"}, {"xbm", "image/x-xbitmap"}, {"xht", "application/xhtml+xml"}, {"xhtml", "application/xhtml+xml"}, {"xhvml", "application/xv+xml"}, {"xif", "image/vnd.xiff"}, {"xls", "application/vnd.ms-excel"}, {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, {"xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, {"xm", "audio/xm"}, {"xml", "application/xml"}, {"xop", "application/xop+xml"}, {"xpl", "application/xproc+xml"}, {"xpm", "image/x-xpixmap"}, {"xsl", "application/xml"}, {"xslt", "application/xslt+xml"}, {"xspf", "application/xspf+xml"}, {"xvm", "application/xv+xml"}, {"xvml", "application/xv+xml"}, {"xwd", "image/x-xwindowdump"}, {"zip", "application/zip"}, // RAW Images {"3fr", "image/x-hasselblad-3fr"}, {"ari", "image/z-arrialexa-ari"}, {"arq", "image/x-sony-arq"}, {"arw", "image/x-sony-arw"}, {"bay", "image/x-casio-bay"}, {"bmq", "image/x-nucore-bmq"}, {"cap", "image/x-phaseone-cap"}, {"cr2", "image/x-canon-cr2"}, {"cr3", "image/x-canon-cr3"}, {"crw", "image/x-canon-crw"}, {"cs1", "image/x-sinar-cs1"}, {"dc2", "image/x-kodak-dc2"}, {"dcr", "image/x-kodak-dcr"}, {"dng", "image/x-dcraw"}, {"dsc", "image/x-kodak-dsc"}, {"drf", "image/x-kodak-drf"}, {"eip", "image/x-phaseone-eip"}, {"erf", "image/x-epson-erf"}, {"fff", "image/x-hasselblad-fff"}, {"iiq", "image/x-phaseone-iiq"}, {"k25", "image/x-kodak-k25"}, {"kc2", "image/x-kodak-kc2"}, {"kdc", "image/x-kodak-kdc"}, {"mdc", "image/x-monolta-mdc"}, {"mef", "image/x-mamiya-mef"}, {"mos", "image/x-leaf-mos"}, {"mrw", "image/x-minolta-mrw"}, {"nef", "image/x-nikon-nef"}, {"nrw", "image/x-nikon-nrw"}, {"obm", "image/x-olympus-obm"}, {"orf", "image/x-olympus-orf"}, {"ori", "image/x-olympus-ori"}, {"pef", "image/x-pentax-pef"}, {"ptx", "image/x-pentax-ptx"}, {"pxn", "image/x-logitech-pxn"}, {"qtk", "image/x-apple-qtx"}, {"raf", "image/x-fuji-raf"}, {"raw", "image/x-panasonic-raw"}, {"rdc", "image/x-difoma-rdc"}, {"rw2", "image/x-panasonic-rw2"}, {"rwl", "image/x-leica-rwl"}, {"rwz", "image/x-rawzor-rwz"}, {"sr2", "image/x-sony-sr2"}, {"srf", "image/x-sony-srf"}, {"srw", "image/x-samsung-srw"}, {"sti", "image/x-sinar-sti"}, {"x3f", "image/x-sigma-x3f"}, {"ciff", "image/x-canon-crw"}, {"cine", "image/x-phantom-cine"}, {"ia", "image/x-sinar-ia"}, // Uncommon Images {"aces", "image/aces"}, {"avci", "image/avci"}, {"avcs", "image/avcs"}, {"fits", "image/fits"}, {"g3fax", "image/g3fax"}, {"hej2k", "image/hej2k"}, {"hsj2", "image/hsj2"}, {"jls", "image/jls"}, {"jp2", "image/jp2"}, {"jph", "image/jph"}, {"jphc", "image/jphc"}, {"jpx", "image/jpx"}, {"jxr", "image/jxr"}, {"jxrA", "image/jxrA"}, {"jxrS", "image/jxrS"}, {"jxs", "image/jxs"}, {"jxsc", "image/jxsc"}, {"jxsi", "image/jxsi"}, {"jxss", "image/jxss"}, {"naplps", "image/naplps"}, {"pti", "image/prs.pti"}, {"t38", "image/t38"}}; string key = extension; tolower_string(key); map<string, string>::const_iterator it = mimeMap.find(key); return it == mimeMap.cend() ? nullptr : MegaApi::strdup(it->second.c_str()); } #ifdef ENABLE_CHAT void MegaApi::createChat(bool group, MegaTextChatPeerList* peers, const char* title, int chatOptions, const MegaScheduledMeeting* scheduledMeeting, MegaRequestListener* listener) { pImpl->createChat(group, false, peers, NULL, title, false, chatOptions, scheduledMeeting, listener); } void MegaApi::createPublicChat(MegaTextChatPeerList* peers, const MegaStringMap* userKeyMap, const char* title, bool meetingRoom, int chatOptions, const MegaScheduledMeeting* scheduledMeeting, MegaRequestListener* listener) { pImpl->createChat(true, true, peers, userKeyMap, title, meetingRoom, chatOptions, scheduledMeeting, listener); } void MegaApi::setChatOption(MegaHandle chatid, int option, bool enabled, MegaRequestListener* listener) { pImpl->setChatOption(chatid, option, enabled, listener); } void MegaApi::createOrUpdateScheduledMeeting(const MegaScheduledMeeting* scheduledMeeting, const char* chatTitle, MegaRequestListener* listener) { pImpl->createOrUpdateScheduledMeeting(scheduledMeeting, chatTitle, listener); } void MegaApi::removeScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener) { pImpl->removeScheduledMeeting(chatid, schedId, listener); } void MegaApi::fetchScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener) { pImpl->fetchScheduledMeeting(chatid, schedId, listener); } void MegaApi::fetchScheduledMeetingEvents(MegaHandle chatid, MegaTimeStamp since, MegaTimeStamp until, unsigned int count, MegaRequestListener* listener) { pImpl->fetchScheduledMeetingEvents(chatid, since, until, count, listener); } void MegaApi::inviteToChat(MegaHandle chatid, MegaHandle uh, int privilege, const char *title, MegaRequestListener *listener) { pImpl->inviteToChat(chatid, uh, privilege, false, NULL, title, listener); } void MegaApi::inviteToPublicChat(MegaHandle chatid, MegaHandle uh, int privilege, const char *unifiedKey, MegaRequestListener *listener) { pImpl->inviteToChat(chatid, uh, privilege, true, unifiedKey, NULL, listener); } void MegaApi::removeFromChat(MegaHandle chatid, MegaHandle uh, MegaRequestListener *listener) { pImpl->removeFromChat(chatid, uh, listener); } void MegaApi::getUrlChat(MegaHandle chatid, MegaRequestListener *listener) { pImpl->getUrlChat(chatid, listener); } void MegaApi::grantAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener) { pImpl->grantAccessInChat(chatid, n, uh, listener); } void MegaApi::removeAccessInChat(MegaHandle chatid, MegaNode *n, MegaHandle uh, MegaRequestListener *listener) { pImpl->removeAccessInChat(chatid, n, uh, listener); } void MegaApi::updateChatPermissions(MegaHandle chatid, MegaHandle uh, int privilege, MegaRequestListener *listener) { pImpl->updateChatPermissions(chatid, uh, privilege, listener); } void MegaApi::truncateChat(MegaHandle chatid, MegaHandle messageid, MegaRequestListener *listener) { pImpl->truncateChat(chatid, messageid, listener); } void MegaApi::setChatTitle(MegaHandle chatid, const char* title, MegaRequestListener *listener) { pImpl->setChatTitle(chatid, title, listener); } void MegaApi::getChatPresenceURL(MegaRequestListener *listener) { pImpl->getChatPresenceURL(listener); } void MegaApi::registerPushNotifications(int deviceType, const char *token, MegaRequestListener *listener) { pImpl->registerPushNotification(deviceType, token, listener); } void MegaApi::sendChatStats(const char *data, int port, MegaRequestListener *listener) { pImpl->sendChatStats(data, port, listener); } void MegaApi::sendChatLogs(const char *data, MegaHandle userid, MegaHandle callid, int port, MegaRequestListener *listener) { pImpl->sendChatLogs(data, userid, callid, port, listener); } MegaTextChatList* MegaApi::getChatList() { return pImpl->getChatList(); } MegaHandleList* MegaApi::getAttachmentAccess(MegaHandle chatid, MegaHandle h) { return pImpl->getAttachmentAccess(chatid, h); } bool MegaApi::hasAccessToAttachment(MegaHandle chatid, MegaHandle h, MegaHandle uh) { return pImpl->hasAccessToAttachment(chatid, h, uh); } const char* MegaApi::getFileAttribute(MegaHandle h) { return pImpl->getFileAttribute(h); } void MegaApi::archiveChat(MegaHandle chatid, int archive, MegaRequestListener *listener) { pImpl->archiveChat(chatid, archive, listener); } void MegaApi::setChatRetentionTime(MegaHandle chatid, unsigned period, MegaRequestListener *listener) { pImpl->setChatRetentionTime(chatid, period, listener); } void MegaApi::requestRichPreview(const char *url, MegaRequestListener *listener) { pImpl->requestRichPreview(url, listener); } void MegaApi::chatLinkQuery(MegaHandle chatid, MegaRequestListener *listener) { pImpl->chatLinkHandle(chatid, false, false, listener); } void MegaApi::chatLinkCreate(MegaHandle chatid, MegaRequestListener *listener) { pImpl->chatLinkHandle(chatid, false, true, listener); } void MegaApi::chatLinkDelete(MegaHandle chatid, MegaRequestListener *listener) { pImpl->chatLinkHandle(chatid, true, false, listener); } void MegaApi::getChatLinkURL(MegaHandle publichandle, MegaRequestListener *listener) { pImpl->getChatLinkURL(publichandle, listener); } void MegaApi::chatLinkClose(MegaHandle chatid, const char *title, MegaRequestListener *listener) { pImpl->chatLinkClose(chatid, title, listener); } void MegaApi::chatLinkJoin(MegaHandle publichandle, const char *unifiedKey, MegaRequestListener *listener) { pImpl->chatLinkJoin(publichandle, unifiedKey, listener); } bool MegaApi::isChatNotifiable(MegaHandle chatid) { return pImpl->isChatNotifiable(chatid); } void MegaApi::startChatCall(MegaHandle chatid, bool notRinging, MegaRequestListener* listener) { pImpl->startChatCall(chatid, notRinging, listener); } void MegaApi::joinChatCall(MegaHandle chatid, MegaHandle callid, MegaRequestListener *listener) { pImpl->joinChatCall(chatid, callid, listener); } void MegaApi::endChatCall(MegaHandle chatid, MegaHandle callid, int reason, MegaRequestListener *listener) { pImpl->endChatCall(chatid, callid, reason, listener); } void MegaApi::ringIndividualInACall(MegaHandle chatid, MegaHandle userid, MegaRequestListener* listener) { pImpl->ringIndividualInACall(chatid, userid, listener); } void MegaApi::setSFUid(int sfuid) { pImpl->setSFUid(sfuid); } #endif bool MegaApi::isSharesNotifiable() { return pImpl->isSharesNotifiable(); } bool MegaApi::isContactsNotifiable() { return pImpl->isContactsNotifiable(); } char* MegaApi::strdup(const char* buffer) { if (!buffer) return NULL; int tam = int(strlen(buffer) + 1); char* newbuffer = new char[static_cast<size_t>(tam)]; memcpy(newbuffer, buffer, static_cast<size_t>(tam)); return newbuffer; } #ifdef _WIN32 // convert Windows Unicode to UTF-8 void MegaApi::utf16ToUtf8(const wchar_t* utf16data, int utf16size, string* utf8string) { if(!utf16size) { utf8string->clear(); return; } utf8string->resize((utf16size + 1) * 4); utf8string->resize(WideCharToMultiByte(CP_UTF8, 0, utf16data, utf16size, (char*)utf8string->data(), int(utf8string->size() + 1), NULL, NULL)); } void MegaApi::utf8ToUtf16(const char* utf8data, string* utf16string) { if(!utf8data) { utf16string->clear(); utf16string->append("", 1); return; } int size = int(strlen(utf8data) + 1); // make space for the worst case utf16string->resize(size * sizeof(wchar_t)); // resize to actual result utf16string->resize(sizeof(wchar_t) * MultiByteToWideChar(CP_UTF8, 0, utf8data, size, (wchar_t*)utf16string->data(), int(utf16string->size() / sizeof(wchar_t) + 1))); if (utf16string->size()) { utf16string->resize(utf16string->size() - 1); } else { utf16string->append("", 1); } } #endif char *MegaApi::escapeFsIncompatible(const char *filename, const char *dstPath) { return pImpl->escapeFsIncompatible(filename, dstPath); } char *MegaApi::unescapeFsIncompatible(const char *name, const char *localPath) { return pImpl->unescapeFsIncompatible(name, localPath); } bool MegaApi::createThumbnail(const char *imagePath, const char *dstPath) { return pImpl->createThumbnail(imagePath, dstPath); } bool MegaApi::createPreview(const char *imagePath, const char *dstPath) { return pImpl->createPreview(imagePath, dstPath); } bool MegaApi::createAvatar(const char *imagePath, const char *dstPath) { return pImpl->createAvatar(imagePath, dstPath); } void MegaApi::backgroundMediaUploadRequestUploadURL(int64_t fullFileSize, MegaBackgroundMediaUpload* state, MegaRequestListener *listener) { return pImpl->backgroundMediaUploadRequestUploadURL(fullFileSize, state, listener); } void MegaApi::completeUpload(const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, const char *string64FileKey, MegaRequestListener *listener) { return pImpl->completeUpload(utf8Name, parent, fingerprint, fingerprintoriginal, string64UploadToken, string64FileKey, listener); } void MegaApi::getUploadURL(int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener) { return pImpl->getUploadURL(fullFileSize, forceSSL, listener); } void MegaApi::getThumbnailUploadURL(MegaHandle nodeHandle, int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener) { return pImpl->getFileAttributeUploadURL(nodeHandle, fullFileSize, GfxProc::THUMBNAIL, forceSSL, listener); } void MegaApi::getPreviewUploadURL(MegaHandle nodeHandle, int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener) { return pImpl->getFileAttributeUploadURL(nodeHandle, fullFileSize, GfxProc::PREVIEW, forceSSL, listener); } void MegaApi::backgroundMediaUploadComplete(MegaBackgroundMediaUpload* state, const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, MegaRequestListener *listener) { pImpl->backgroundMediaUploadComplete(state, utf8Name, parent, fingerprint, fingerprintoriginal, string64UploadToken, listener); } bool MegaApi::ensureMediaInfo() { return pImpl->ensureMediaInfo(); } void MegaApi::setOriginalFingerprint(MegaNode* node, const char* originalFingerprint, MegaRequestListener *listener) { return pImpl->setOriginalFingerprint(node, originalFingerprint, listener); } void MegaApi::getBanners(MegaRequestListener *listener) { pImpl->getBanners(listener); } void MegaApi::dismissBanner(int id, MegaRequestListener *listener) { pImpl->dismissBanner(id, listener); } void MegaApi::setBackup(int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener) { pImpl->setBackup(backupType, targetNode, localFolder, backupName, state, subState, listener); } void MegaApi::updateBackup(MegaHandle backupId, int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener) { pImpl->updateBackup(backupId, backupType, targetNode, localFolder, backupName, state, subState, listener); } void MegaApi::removeBackup(MegaHandle backupId, MegaRequestListener *listener) { pImpl->removeBackup(backupId, listener); } void MegaApi::removeFromBC(MegaHandle backupId, MegaHandle moveDestination, MegaRequestListener* listener) { pImpl->removeFromBC(backupId, moveDestination, listener); } void MegaApi::pauseFromBC(MegaHandle backupId, MegaRequestListener* listener) { pImpl->pauseFromBC(backupId, listener); } void MegaApi::resumeFromBC(MegaHandle backupId, MegaRequestListener* listener) { pImpl->resumeFromBC(backupId, listener); } void MegaApi::getBackupInfo(MegaRequestListener* listener) { pImpl->getBackupInfo(listener); } void MegaApi::sendBackupHeartbeat(MegaHandle backupId, int status, int progress, int ups, int downs, long long ts, MegaHandle lastNode, MegaRequestListener *listener) { pImpl->sendBackupHeartbeat(backupId, status, progress, ups, downs, ts, lastNode, listener); } void MegaApi::fetchAds(int adFlags, MegaStringList *adUnits, MegaHandle publicHandle, MegaRequestListener *listener) { pImpl->fetchAds(adFlags, adUnits, publicHandle, listener); } void MegaApi::queryAds(int adFlags, MegaHandle publicHandle, MegaRequestListener *listener) { pImpl->queryAds(adFlags, publicHandle, listener); } void MegaApi::setCookieSettings(int settings, MegaRequestListener *listener) { pImpl->setCookieSettings(settings, listener); } void MegaApi::getCookieSettings(MegaRequestListener *listener) { pImpl->getCookieSettings(listener); } bool MegaApi::cookieBannerEnabled() { return pImpl->cookieBannerEnabled(); } bool MegaApi::startDriveMonitor() { return pImpl->startDriveMonitor(); } void MegaApi::stopDriveMonitor() { pImpl->stopDriveMonitor(); } bool MegaApi::driveMonitorEnabled() { return pImpl->driveMonitorEnabled(); } void MegaApi::createSet(const char* name, int type, MegaRequestListener* listener) { int options = CREATE_SET | (name ? OPTION_SET_NAME : 0); pImpl->putSet(INVALID_HANDLE, options, name, INVALID_HANDLE, type, listener); } void MegaApi::updateSetName(MegaHandle sid, const char* name, MegaRequestListener* listener) { pImpl->putSet(sid, OPTION_SET_NAME, name, INVALID_HANDLE, MegaSet::SET_TYPE_IGNORE, listener); } void MegaApi::putSetCover(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener) { pImpl->putSet(sid, OPTION_SET_COVER, nullptr, eid, MegaSet::SET_TYPE_IGNORE, listener); } void MegaApi::removeSet(MegaHandle sid, MegaRequestListener* listener) { pImpl->removeSet(sid, listener); } void MegaApi::createSetElements(MegaHandle sid, const MegaHandleList* nodes, const MegaStringList* names, MegaRequestListener* listener) { pImpl->putSetElements(sid, nodes, names, listener); } void MegaApi::createSetElement(MegaHandle sid, MegaHandle node, const char* name, MegaRequestListener* listener) { int options = CREATE_ELEMENT | (name ? OPTION_ELEMENT_NAME : 0); pImpl->putSetElement(sid, INVALID_HANDLE, node, options, 0, name, listener); } void MegaApi::updateSetElementOrder(MegaHandle sid, MegaHandle eid, int64_t order, MegaRequestListener* listener) { pImpl->putSetElement(sid, eid, INVALID_HANDLE, OPTION_ELEMENT_ORDER, order, nullptr, listener); } void MegaApi::updateSetElementName(MegaHandle sid, MegaHandle eid, const char* name, MegaRequestListener* listener) { pImpl->putSetElement(sid, eid, INVALID_HANDLE, OPTION_ELEMENT_NAME, 0, name, listener); } void MegaApi::removeSetElements(MegaHandle sid, const MegaHandleList* eids, MegaRequestListener* listener) { pImpl->removeSetElements(sid, eids, listener); } void MegaApi::removeSetElement(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener) { pImpl->removeSetElement(sid, eid, listener); } MegaSetList* MegaApi::getSets() { return pImpl->getSets(); } MegaSet* MegaApi::getSet(MegaHandle sid) { return pImpl->getSet(sid); } MegaHandle MegaApi::getSetCover(MegaHandle sid) { return pImpl->getSetCover(sid); } unsigned MegaApi::getSetElementCount(MegaHandle sid, bool includeElementsInRubbishBin) { return pImpl->getSetElementCount(sid, includeElementsInRubbishBin); } MegaSetElementList* MegaApi::getSetElements(MegaHandle sid, bool includeElementsInRubbishBin) { return pImpl->getSetElements(sid, includeElementsInRubbishBin); } MegaSetElement* MegaApi::getSetElement(MegaHandle sid, MegaHandle eid) { return pImpl->getSetElement(sid, eid); } bool MegaApi::isExportedSet(MegaHandle sid) { return pImpl->isExportedSet(sid); } void MegaApi::exportSet(MegaHandle sid, MegaRequestListener *listener) { return pImpl->exportSet(sid, listener); } void MegaApi::disableExportSet(MegaHandle sid, MegaRequestListener *listener) { return pImpl->disableExportSet(sid, listener); } int MegaApi::getSetElementHandleSize() { return MegaApiImpl::getSetElementHandleSize(); } void MegaApi::fetchPublicSet(const char* publicSetLink, MegaRequestListener* listener) { pImpl->fetchPublicSet(publicSetLink, listener); } void MegaApi::stopPublicSetPreview() { return pImpl->stopPublicSetPreview(); } bool MegaApi::inPublicSetPreview() { return pImpl->inPublicSetPreview(); } MegaSet* MegaApi::getPublicSetInPreview() { return pImpl->getPublicSetInPreview(); } MegaSetElementList* MegaApi::getPublicSetElementsInPreview() { return pImpl->getPublicSetElementsInPreview(); } void MegaApi::getPreviewElementNode(MegaHandle eid, MegaRequestListener* listener) { return pImpl->getPreviewElementNode(eid, listener); } const char* MegaApi::getPublicLinkForExportedSet(MegaHandle sid) { return pImpl->getPublicLinkForExportedSet(sid); } void MegaApi::enableRequestStatusMonitor(bool enable) { return pImpl->enableRequestStatusMonitor(enable); } bool MegaApi::requestStatusMonitorEnabled() { return pImpl->requestStatusMonitorEnabled(); } void MegaApi::addMount(const MegaMount* mount, MegaRequestListener* listener) { assert(listener); assert(mount); pImpl->addMount(mount, listener); } void MegaApi::disableMount(const char* path, MegaRequestListener* listener, bool remember) { assert(listener); assert(path); pImpl->disableMount(path, listener, remember); } void MegaApi::enableMount(const char* path, MegaRequestListener* listener, bool remember) { assert(listener); assert(path); pImpl->enableMount(path, listener, remember); } MegaFuseFlags* MegaApi::getFUSEFlags() { return pImpl->getFUSEFlags(); } MegaMountFlags* MegaApi::getMountFlags(const char* path) { assert(path); return pImpl->getMountFlags(path); } MegaMount* MegaApi::getMountInfo(const char* path) { assert(path); return pImpl->getMountInfo(path); } char* MegaApi::getMountPath(const char* name) { assert(name); return pImpl->getMountPath(name); } MegaMountList* MegaApi::listMounts(bool enabled) { return pImpl->listMounts(enabled); } bool MegaApi::isCachedByPath(const char* path) { assert(path); return pImpl->isCached(path); } bool MegaApi::isFUSESupported() { return pImpl->isFUSESupported(); } bool MegaApi::isMountEnabled(const char* path) { assert(path); return pImpl->isMountEnabled(path); } void MegaApi::removeMount(const char* path, MegaRequestListener* listener) { assert(listener); assert(path); pImpl->removeMount(path, listener); } void MegaApi::setFUSEFlags(const MegaFuseFlags* flags) { assert(flags); pImpl->setFUSEFlags(*flags); } void MegaApi::setMountFlags(const MegaMountFlags* flags, const char* path, MegaRequestListener* listener) { assert(flags); assert(path); assert(listener); pImpl->setMountFlags(flags, path, listener); } void MegaApi::getVpnRegions(MegaRequestListener* listener) { pImpl->getVpnRegions(listener); } void MegaApi::getVpnCredentials(MegaRequestListener* listener) { pImpl->getVpnCredentials(listener); } void MegaApi::putVpnCredential(const char* region, MegaRequestListener* listener) { pImpl->putVpnCredential(region, listener); } void MegaApi::delVpnCredential(int slotID, MegaRequestListener* listener) { pImpl->delVpnCredential(slotID, listener); } void MegaApi::checkVpnCredential(const char* userPubKey, MegaRequestListener* listener) { pImpl->checkVpnCredential(userPubKey, listener); } void MegaApi::fetchCreditCardInfo(MegaRequestListener* listener) { pImpl->fetchCreditCardInfo(listener); } void MegaApi::getVisibleWelcomeDialog(MegaRequestListener* listener) { pImpl->getVisibleWelcomeDialog(listener); } void MegaApi::setVisibleWelcomeDialog(bool visible, MegaRequestListener* listener) { pImpl->setVisibleWelcomeDialog(visible, listener); } void MegaApi::getVisibleTermsOfService(MegaRequestListener* listener) { pImpl->getVisibleTermsOfService(listener); } void MegaApi::setVisibleTermsOfService(bool visible, MegaRequestListener* listener) { pImpl->setVisibleTermsOfService(visible, listener); } void MegaApi::createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, MegaRequestListener* listener) { pImpl->createNodeTree(parentNode, nodeTree, nullptr, listener); } void MegaApi::createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, const char* customerIpPort, MegaRequestListener* listener) { pImpl->createNodeTree(parentNode, nodeTree, customerIpPort, listener); } MegaIntegerList* MegaApi::getEnabledNotifications() { return pImpl->getEnabledNotifications(); } void MegaApi::enableTestNotifications(const MegaIntegerList* notificationIds, MegaRequestListener* listener) { pImpl->enableTestNotifications(notificationIds, listener); } void MegaApi::getNotifications(MegaRequestListener* listener) { pImpl->getNotifications(listener); } void MegaApi::setLastReadNotification(uint32_t notificationId, MegaRequestListener* listener) { pImpl->setLastReadNotification(notificationId, listener); } void MegaApi::getLastReadNotification(MegaRequestListener* listener) { pImpl->getLastReadNotification(listener); } void MegaApi::setLastActionedBanner(uint32_t notificationId, MegaRequestListener* listener) { pImpl->setLastActionedBanner(notificationId, listener); } void MegaApi::getLastActionedBanner(MegaRequestListener* listener) { pImpl->getLastActionedBanner(listener); } MegaFlag* MegaApi::getFlag(const char* flagName, bool commit, MegaRequestListener* listener) { return pImpl->getFlag(flagName, commit, listener); } MegaFlag* MegaApi::getFlag(const char* flagName, bool commit) { return pImpl->getFlag(flagName, commit); } void MegaApi::deleteUserAttribute(int type, MegaRequestListener* listener) { return pImpl->deleteUserAttribute(type, listener); } void MegaApi::getActiveSurveyTriggerActions(MegaRequestListener* listener) { return pImpl->getActiveSurveyTriggerActions(listener); } void MegaApi::getSurvey(unsigned int triggerActionId, MegaRequestListener* listener) { return pImpl->getSurvey(triggerActionId, listener); } void MegaApi::enableTestSurveys(const MegaHandleList* surveyHandles, MegaRequestListener* listener) { return pImpl->enableTestSurveys(surveyHandles, listener); } void MegaApi::answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const char* response, const char* comment, MegaRequestListener* listener) { return pImpl->answerSurvey(surveyHandle, triggerActionId, response, comment, listener); } void MegaApi::getMyIp(MegaRequestListener* listener) { pImpl->getMyIp(listener); } void MegaApi::runNetworkConnectivityTest(MegaRequestListener* listener) { pImpl->runNetworkConnectivityTest(listener); } void MegaApi::getSubscriptionCancellationDetails(unsigned int gatewayId, const char* originalTransactionId, MegaRequestListener* listener) { pImpl->getSubscriptionCancellationDetails(originalTransactionId, gatewayId, listener); } void MegaApi::getDiscountCodeInformation(const char* discountCode, MegaRequestListener* listener) { pImpl->getDiscountCodeInformation(discountCode, listener); } /* END MEGAAPI */ MegaHashSignature::MegaHashSignature(const char *base64Key) { pImpl = new MegaHashSignatureImpl(base64Key); } MegaHashSignature::~MegaHashSignature() { delete pImpl; } void MegaHashSignature::init() { pImpl->init(); } void MegaHashSignature::add(const char *data, unsigned size) { pImpl->add(data, size); } bool MegaHashSignature::checkSignature(const char *base64Signature) { return pImpl->checkSignature(base64Signature); } MegaAccountDetails::~MegaAccountDetails() { } int MegaAccountDetails::getProLevel() { return 0; } int64_t MegaAccountDetails::getProExpiration() { return 0; } int MegaAccountDetails::getSubscriptionStatus() { return 0; } int64_t MegaAccountDetails::getSubscriptionRenewTime() { return 0; } char *MegaAccountDetails::getSubscriptionMethod() { return NULL; } int MegaAccountDetails::getSubscriptionMethodId() { return 0; } char *MegaAccountDetails::getSubscriptionCycle() { return NULL; } long long MegaAccountDetails::getStorageMax() { return 0; } long long MegaAccountDetails::getStorageUsed() { return 0; } long long MegaAccountDetails::getVersionStorageUsed() { return 0; } long long MegaAccountDetails::getTransferMax() { return 0; } long long MegaAccountDetails::getTransferOwnUsed() { return 0; } long long MegaAccountDetails::getTransferSrvUsed() { return 0; } long long MegaAccountDetails::getTransferUsed() { return 0; } int MegaAccountDetails::getNumUsageItems() { return 0; } long long MegaAccountDetails::getStorageUsed(MegaHandle) { return 0; } long long MegaAccountDetails::getNumFiles(MegaHandle) { return 0; } long long MegaAccountDetails::getNumFolders(MegaHandle) { return 0; } long long MegaAccountDetails::getNumVersionFiles(MegaHandle) { return 0; } long long MegaAccountDetails::getVersionStorageUsed(MegaHandle) { return 0; } MegaAccountDetails *MegaAccountDetails::copy() { return NULL; } int MegaAccountDetails::getNumBalances() const { return 0; } MegaAccountBalance *MegaAccountDetails::getBalance(int) const { return NULL; } int MegaAccountDetails::getNumSessions() const { return 0; } MegaAccountSession *MegaAccountDetails::getSession(int) const { return NULL; } int MegaAccountDetails::getNumPurchases() const { return 0; } MegaAccountPurchase *MegaAccountDetails::getPurchase(int) const { return NULL; } int MegaAccountDetails::getNumTransactions() const { return 0; } MegaAccountTransaction *MegaAccountDetails::getTransaction(int) const { return NULL; } int MegaAccountDetails::getTemporalBandwidthInterval() { return 0; } long long MegaAccountDetails::getTemporalBandwidth() { return 0; } bool MegaAccountDetails::isTemporalBandwidthValid() { return false; } void MegaLogger::log(const char* /*time*/, int /*loglevel*/, const char* /*source*/, const char* /*message*/ #ifdef ENABLE_LOG_PERFORMANCE , const char ** /*directMessages*/, size_t * /*directMessagesSizes*/, int /*numberMessages*/ #endif ) { } bool MegaGfxProcessor::readBitmap(const char* /*path*/) { return false; } int MegaGfxProcessor::getWidth() { return 0; } int MegaGfxProcessor::getHeight() { return 0; } int MegaGfxProcessor::getBitmapDataSize(int /*width*/, int /*height*/, int /*px*/, int /*py*/, int /*rw*/, int /*rh*/, int /*hint*/) { return 0; } bool MegaGfxProcessor::getBitmapData(char* /*bitmapData*/, size_t /*size*/) { return 0; } void MegaGfxProcessor::freeBitmap() { } const char* MegaGfxProcessor::supportedImageFormats() { // This special string will cause all files to be attempted // (backwards compatibility for Android) return "all"; } const char* MegaGfxProcessor::supportedVideoFormats() { // This special string will cause all files to be attempted // (backwards compatibility for Android) return "all"; } MegaGfxProcessor::~MegaGfxProcessor() { } MegaPricing::~MegaPricing() { } int MegaPricing::getNumProducts() { return 0; } MegaHandle MegaPricing::getHandle(int) { return INVALID_HANDLE; } int MegaPricing::getProLevel(int) { return 0; } int MegaPricing::getGBStorage(int) { return 0; } int MegaPricing::getGBTransfer(int) { return 0; } int MegaPricing::getMonths(int) { return 0; } int MegaPricing::getAmount(int) { return 0; } int MegaPricing::getLocalPrice(int /*productIndex*/) { return 0; } double MegaPricing::getPriceNetWithDecimals(const int /*productIndex*/) const { return 0.0; } double MegaPricing::getLocalPriceNetWithDecimals(const int /*productIndex*/) const { return 0.0; } double MegaPricing::getAmountWithDecimals(const int /*productIndex*/) const { return 0.0; } double MegaPricing::getLocalPriceWithDecimals(const int /*productIndex*/) const { return 0.0; } double MegaPricing::getAmountMonthWithDecimals(const int /*productIndex*/) const { return 0.0; } double MegaPricing::getMonthlyBasePriceNetWithDecimals(const int /*productIndex*/) const { return 0.0; } const char *MegaPricing::getDescription(int) { return NULL; } const char *MegaPricing::getIosID(int) { return NULL; } const char *MegaPricing::getAndroidID(int) { return NULL; } bool MegaPricing::isBusinessType(int) { return false; } bool MegaPricing::isFeaturePlan(int) const { return false; } int MegaPricing::getAmountMonth(int) { return 0; } MegaPricing *MegaPricing::copy() { return NULL; } int MegaPricing::getGBStoragePerUser(int) { return 0; } int MegaPricing::getGBTransferPerUser(int) { return 0; } unsigned int MegaPricing::getMinUsers(int) { return 0; } unsigned int MegaPricing::getPricePerUser(int) { return 0; } unsigned int MegaPricing::getLocalPricePerUser(int) { return 0; } unsigned int MegaPricing::getPricePerStorage(int) { return 0; } unsigned int MegaPricing::getLocalPricePerStorage(int) { return 0; } int MegaPricing::getGBPerStorage(int) { return 0; } unsigned int MegaPricing::getPricePerTransfer(int) { return 0; } unsigned int MegaPricing::getLocalPricePerTransfer(int) { return 0; } int MegaPricing::getGBPerTransfer(int) { return 0; } MegaStringIntegerMap* MegaPricing::getFeatures(int) const { return nullptr; } const char *MegaCurrency::getCurrencySymbol() { return nullptr; } const char *MegaCurrency::getCurrencyName() { return nullptr; } const char *MegaCurrency::getLocalCurrencySymbol() { return nullptr; } const char *MegaCurrency::getLocalCurrencyName() { return nullptr; } unsigned int MegaPricing::getTestCategory(int) const { return 0; } bool MegaPricing::hasDiscount(int) const { return false; } const char* MegaPricing::getDiscountCode(int) const { return nullptr; } const char* MegaPricing::getDiscountName(int) const { return nullptr; } int MegaPricing::getDiscountGroup(int) const { return -1; } int MegaPricing::getDiscountMonths(int) const { return -1; } int MegaPricing::getDiscountPercentage(int) const { return -1; } #ifdef ENABLE_SYNC MegaSync::~MegaSync() { } MegaSync *MegaSync::copy() { return NULL; } MegaHandle MegaSync::getMegaHandle() const { return INVALID_HANDLE; } const char *MegaSync::getLocalFolder() const { return NULL; } const char *MegaSync::getName() const { return NULL; } const char *MegaSync::getLastKnownMegaFolder() const { return NULL; } MegaHandle MegaSync::getBackupId() const { return INVALID_HANDLE; } int MegaSync::getError() const { return MegaSync::Error::NO_SYNC_ERROR; } int MegaSync::getWarning() const { return MegaSync::Warning::NO_SYNC_WARNING; } int MegaSync::getType() const { return MegaSync::SyncType::TYPE_UNKNOWN; } int MegaSync::getRunState() const { return 0; } const char* MegaSync::getMegaSyncErrorCode() { return MegaSync::getMegaSyncErrorCode(getError()); } const char* MegaSync::getMegaSyncErrorCode(int errorCode) { return MegaApi::strdup(SyncConfig::syncErrorToStr(static_cast<SyncError>(errorCode)).c_str()); } const char* MegaSync::getMegaSyncWarningCode() { return MegaSync::getMegaSyncWarningCode(getWarning()); } const char* MegaSync::getMegaSyncWarningCode(int warningCode) { switch(warningCode) { case MegaSync::Warning::NO_SYNC_WARNING: return "No error"; case MegaSync::Warning::LOCAL_IS_FAT: return "Local filesystem is FAT"; case MegaSync::Warning::LOCAL_IS_HGFS: return "Local filesystem is HGFS"; default: return "Undefined warning"; } } MegaSyncList *MegaSyncList::createInstance() { return new MegaSyncListPrivate(); } MegaSyncList::MegaSyncList() { } MegaSyncList::~MegaSyncList() { } MegaSyncList *MegaSyncList::copy() const { return NULL; } MegaSync *MegaSyncList::get(int) const { return NULL; } int MegaSyncList::size() const { return 0; } void MegaSyncList::addSync(MegaSync*) {} MegaSyncStallList* MegaSyncStallList::copy() const { return nullptr; } size_t MegaSyncStallList::size() const { return 0; } size_t MegaSyncStallList::getHash() const { uint64_t hash = 0; for (size_t i = 0; i < size(); ++i) { hash = hashCombine(hash, get(i)->getHash()); } return static_cast<size_t>(hash); } const MegaSyncStall* MegaSyncStallList::get(size_t /*i*/) const { return nullptr; } MegaSyncStallMap* MegaSyncStallMap::copy() const { return nullptr; } size_t MegaSyncStallMap::size() const { return 0; } size_t MegaSyncStallMap::getHash() const { uint64_t hash = 0; const MegaHandleList* keys = getKeys(); for (unsigned int i = 0; i < keys->size(); ++i) { hash = hashCombine(hash, get(keys->get(i))->getHash()); } return static_cast<size_t>(hash); } const MegaSyncStallList* MegaSyncStallMap::get(const MegaHandle) const { return nullptr; } MegaHandleList* MegaSyncStallMap::getKeys() const { return nullptr; } #endif void MegaScheduledCopyListener::onBackupStateChanged(MegaApi *, MegaScheduledCopy *) { } void MegaScheduledCopyListener::onBackupStart(MegaApi *, MegaScheduledCopy *) { } void MegaScheduledCopyListener::onBackupFinish(MegaApi*, MegaScheduledCopy *, MegaError*) { } void MegaScheduledCopyListener::onBackupUpdate(MegaApi *, MegaScheduledCopy *) { } void MegaScheduledCopyListener::onBackupTemporaryError(MegaApi *, MegaScheduledCopy *, MegaError*) { } MegaScheduledCopyListener::~MegaScheduledCopyListener() { } MegaScheduledCopy::~MegaScheduledCopy() { } MegaScheduledCopy *MegaScheduledCopy::copy() { return NULL; } MegaHandle MegaScheduledCopy::getMegaHandle() const { return INVALID_HANDLE; } const char *MegaScheduledCopy::getLocalFolder() const { return NULL; } int MegaScheduledCopy::getTag() const { return 0; } bool MegaScheduledCopy::getAttendPastBackups() const { return false; } int64_t MegaScheduledCopy::getPeriod() const { return 0; } const char *MegaScheduledCopy::getPeriodString() const { return NULL; } long long MegaScheduledCopy::getNextStartTime(long long /*oldStartTimeAbsolute*/) const { return 0; } int MegaScheduledCopy::getMaxBackups() const { return 0; } int MegaScheduledCopy::getState() const { return MegaScheduledCopy::SCHEDULED_COPY_FAILED; } long long MegaScheduledCopy::getNumberFolders() const { return 0; } long long MegaScheduledCopy::getNumberFiles() const { return 0; } long long MegaScheduledCopy::getTotalFiles() const { return 0; } int64_t MegaScheduledCopy::getCurrentBKStartTime() const { return 0; } long long MegaScheduledCopy::getTransferredBytes() const { return 0; } long long MegaScheduledCopy::getTotalBytes() const { return 0; } long long MegaScheduledCopy::getSpeed() const { return 0; } long long MegaScheduledCopy::getMeanSpeed() const { return 0; } int64_t MegaScheduledCopy::getUpdateTime() const { return 0; } MegaTransferList *MegaScheduledCopy::getFailedTransfers() { return NULL; } MegaAccountBalance::~MegaAccountBalance() { } double MegaAccountBalance::getAmount() const { return 0; } char *MegaAccountBalance::getCurrency() const { return NULL; } MegaAccountSession::~MegaAccountSession() { } int64_t MegaAccountSession::getCreationTimestamp() const { return 0; } int64_t MegaAccountSession::getMostRecentUsage() const { return 0; } char *MegaAccountSession::getUserAgent() const { return NULL; } char *MegaAccountSession::getIP() const { return NULL; } char *MegaAccountSession::getCountry() const { return NULL; } bool MegaAccountSession::isCurrent() const { return false; } bool MegaAccountSession::isAlive() const { return false; } MegaHandle MegaAccountSession::getHandle() const { return INVALID_HANDLE; } char *MegaAccountSession::getDeviceId() const { return nullptr; } MegaAccountPurchase::~MegaAccountPurchase() { } int64_t MegaAccountPurchase::getTimestamp() const { return 0; } char *MegaAccountPurchase::getHandle() const { return NULL; } char *MegaAccountPurchase::getCurrency() const { return NULL; } double MegaAccountPurchase::getAmount() const { return 0; } int MegaAccountPurchase::getMethod() const { return 0; } MegaAccountTransaction::~MegaAccountTransaction() { } int64_t MegaAccountTransaction::getTimestamp() const { return 0; } char *MegaAccountTransaction::getHandle() const { return NULL; } char *MegaAccountTransaction::getCurrency() const { return NULL; } double MegaAccountTransaction::getAmount() const { return 0; } MegaBackgroundMediaUpload *MegaBackgroundMediaUpload::createInstance(MegaApi *api) { return new MegaBackgroundMediaUploadPrivate(api); } MegaBackgroundMediaUpload* MegaBackgroundMediaUpload::unserialize(const char* d, MegaApi* api) { unsigned char* binary; size_t binSize; MegaApi::base64ToBinary(d, &binary, &binSize); std::string binString((char*)binary, binSize); delete[] binary; return d ? new MegaBackgroundMediaUploadPrivate(binString, api) : NULL; } bool MegaBackgroundMediaUpload::analyseMediaInfo(const char* /*inputFilepath*/) { return false; } char* MegaBackgroundMediaUpload::encryptFile(const char* /*inputFilepath*/, int64_t /*startPos*/, int64_t* /*length*/, const char* /*outputFilepath*/, bool /*adjustsizeonly*/) { return NULL; } char *MegaBackgroundMediaUpload::getUploadURL() { return NULL; } char *MegaBackgroundMediaUpload::serialize() { return NULL; } void MegaBackgroundMediaUpload::setThumbnail(MegaHandle) {} void MegaBackgroundMediaUpload::setPreview(MegaHandle) {} void MegaBackgroundMediaUpload::setCoordinates(double /*lat*/, double /*lon*/, bool /*unshareable*/) {} MegaBackgroundMediaUpload::MegaBackgroundMediaUpload() { } MegaBackgroundMediaUpload::~MegaBackgroundMediaUpload() { } int64_t MegaInputStream::getSize() { return 0; } bool MegaInputStream::read(char* /*buffer*/, size_t /*size*/) { return false; } MegaInputStream::~MegaInputStream() { } MegaSearchFilter::MegaSearchFilter() { } MegaSearchFilter* MegaSearchFilter::createInstance() { return new MegaSearchFilterPrivate(); } MegaSearchFilter* MegaSearchFilter::copy() const { return nullptr; } MegaSearchFilter::~MegaSearchFilter() { } void MegaSearchFilter::byName(const char* /*searchString*/) { } void MegaSearchFilter::byNodeType(int /*nodeType*/) { } void MegaSearchFilter::byCategory(int /*mimeType*/) { } void MegaSearchFilter::byFavourite(int /*boolFilterOption*/) { } void MegaSearchFilter::bySensitivity(int /*boolFilterOption*/) { } void MegaSearchFilter::byLocationHandle(MegaHandle /*ancestorHandle*/) { } void MegaSearchFilter::byLocation(int /*locationType*/) { } void MegaSearchFilter::byCreationTime(int64_t /*lowerLimit*/, int64_t /*upperLimit*/) { } void MegaSearchFilter::byModificationTime(int64_t /*lowerLimit*/, int64_t /*upperLimit*/) { } void MegaSearchFilter::byDescription(const char* /*searchString*/) { } void MegaSearchFilter::byTag(const char* /*searchString*/) { } void MegaSearchFilter::useAndForTextQuery(bool /*useAnd*/) {} const char* MegaSearchFilter::byName() const { return nullptr; } int MegaSearchFilter::byNodeType() const { return MegaNode::TYPE_UNKNOWN; } int MegaSearchFilter::byCategory() const { return MegaApi::FILE_TYPE_DEFAULT; } int MegaSearchFilter::byFavourite() const { return MegaSearchFilter::BOOL_FILTER_DISABLED; } int MegaSearchFilter::bySensitivity() const { return BOOL_FILTER_DISABLED; } MegaHandle MegaSearchFilter::byLocationHandle() const { return INVALID_HANDLE; } int MegaSearchFilter::byLocation() const { return MegaApi::SEARCH_TARGET_ALL; } int64_t MegaSearchFilter::byCreationTimeLowerLimit() const { return 0; } int64_t MegaSearchFilter::byCreationTimeUpperLimit() const { return 0; } int64_t MegaSearchFilter::byModificationTimeLowerLimit() const { return 0; } int64_t MegaSearchFilter::byModificationTimeUpperLimit() const { return 0; } const char* MegaSearchFilter::byDescription() const { return nullptr; } const char* MegaSearchFilter::byTag() const { return nullptr; } bool MegaSearchFilter::useAndForTextQuery() const { return false; } MegaSearchPage::MegaSearchPage() { } MegaSearchPage* MegaSearchPage::createInstance(size_t startingOffset, size_t size) { return new MegaSearchPagePrivate(startingOffset, size); } MegaSearchPage* MegaSearchPage::copy() const { return nullptr; } MegaSearchPage::~MegaSearchPage() { } size_t MegaSearchPage::startingOffset() const { return 0u; } size_t MegaSearchPage::size() const { return 0u; } MegaApiLock::MegaApiLock(MegaApiImpl* ptr, bool lock) : api(ptr) { if (lock) { lockOnce(); } } MegaApiLock::~MegaApiLock() { unlockOnce(); } void MegaApiLock::lockOnce() { if (!locked) { api->lockMutex(); locked = true; } } bool MegaApiLock::tryLockFor(long long time) { if (!locked) { locked = api->tryLockMutexFor(time); } return locked; } void MegaApiLock::unlockOnce() { if (locked) { api->unlockMutex(); locked = false; } } #ifdef ENABLE_CHAT MegaTextChatPeerList * MegaTextChatPeerList::createInstance() { return new MegaTextChatPeerListPrivate(); } MegaTextChatPeerList::MegaTextChatPeerList() { } MegaTextChatPeerList::~MegaTextChatPeerList() { } MegaTextChatPeerList *MegaTextChatPeerList::copy() const { return NULL; } void MegaTextChatPeerList::addPeer(MegaHandle, int) { } MegaHandle MegaTextChatPeerList::getPeerHandle(int) const { return INVALID_HANDLE; } int MegaTextChatPeerList::getPeerPrivilege(int) const { return PRIV_UNKNOWN; } int MegaTextChatPeerList::size() const { return 0; } MegaTextChat::~MegaTextChat() { } MegaTextChat *MegaTextChat::copy() const { return NULL; } MegaHandle MegaTextChat::getHandle() const { return INVALID_HANDLE; } int MegaTextChat::getOwnPrivilege() const { return PRIV_UNKNOWN; } int MegaTextChat::getShard() const { return -1; } const MegaTextChatPeerList *MegaTextChat::getPeerList() const { return NULL; } void MegaTextChat::setPeerList(const MegaTextChatPeerList *) { } bool MegaTextChat::isGroup() const { return false; } MegaHandle MegaTextChat::getOriginatingUser() const { return INVALID_HANDLE; } const char * MegaTextChat::getTitle() const { return NULL; } const char * MegaTextChat::getUnifiedKey() const { return NULL; } unsigned char MegaTextChat::getChatOptions() const { return 0; } bool MegaTextChat::hasChanged(uint64_t) const { return false; } uint64_t MegaTextChat::getChanges() const { return 0; } int MegaTextChat::isOwnChange() const { return 0; } const MegaScheduledMeetingList* MegaTextChat::getScheduledMeetingList() const { return NULL; } const MegaScheduledMeetingList* MegaTextChat::getUpdatedOccurrencesList() const { return NULL; } const MegaHandleList* MegaTextChat::getSchedMeetingsChanged() const { return NULL; } int64_t MegaTextChat::getCreationTime() const { return 0; } bool MegaTextChat::isArchived() const { return false; } bool MegaTextChat::isPublicChat() const { return false; } bool MegaTextChat::isMeeting() const { return false; } MegaTextChatList::~MegaTextChatList() { } MegaTextChatList *MegaTextChatList::copy() const { return NULL; } const MegaTextChat *MegaTextChatList::get(unsigned int) const { return NULL; } int MegaTextChatList::size() const { return 0; } #endif // ENABLE_CHAT MegaStringMap *MegaStringMap::createInstance() { return new MegaStringMapPrivate(); } MegaStringMap::MegaStringMap() { } MegaStringMap::~MegaStringMap() { } MegaStringMap *MegaStringMap::copy() const { return NULL; } const char *MegaStringMap::get(const char*) const { return NULL; } MegaStringList *MegaStringMap::getKeys() const { return NULL; } void MegaStringMap::set(const char *, const char *) { } int MegaStringMap::size() const { return 0; } MegaIntegerMap* MegaIntegerMap::createInstance() { return new MegaIntegerMapPrivate(); } MegaIntegerMap::~MegaIntegerMap() { } MegaIntegerMap* MegaIntegerMap::copy() const { return NULL; } MegaIntegerList* MegaIntegerMap::getKeys() const { return NULL; } MegaIntegerList* MegaIntegerMap::get(int64_t /*key*/) const { return NULL; } void MegaIntegerMap::set(int64_t /*key*/, int64_t /*value*/) { } int64_t MegaIntegerMap::size() const { return 0; } MegaTransferData::~MegaTransferData() { } MegaTransferData *MegaTransferData::copy() const { return NULL; } int MegaTransferData::getNumDownloads() const { return 0; } int MegaTransferData::getNumUploads() const { return 0; } int MegaTransferData::getDownloadTag(int /*i*/) const { return 0; } int MegaTransferData::getUploadTag(int /*i*/) const { return 0; } unsigned long long MegaTransferData::getDownloadPriority(int /*i*/) const { return 0; } unsigned long long MegaTransferData::getUploadPriority(int /*i*/) const { return 0; } long long MegaTransferData::getNotificationNumber() const { return 0; } MegaEvent::~MegaEvent() { } MegaEvent *MegaEvent::copy() { return NULL; } int MegaEvent::getType() const { return 0; } const char *MegaEvent::getText() const { return NULL; } int64_t MegaEvent::getNumber() const { return 0; } MegaHandle MegaEvent::getHandle() const { return INVALID_HANDLE; } const char *MegaEvent::getEventString() const { return NULL; } std::optional<int64_t> MegaEvent::getNumber(const std::string& /* key */) const { return std::nullopt; } MegaHandleList *MegaHandleList::createInstance() { return new MegaHandleListPrivate(); } MegaHandleList::MegaHandleList() { } MegaHandleList::~MegaHandleList() { } MegaHandleList *MegaHandleList::copy() const { return NULL; } MegaHandle MegaHandleList::get(unsigned int /*i*/) const { return INVALID_HANDLE; } unsigned int MegaHandleList::size() const { return 0; } void MegaHandleList::addMegaHandle(MegaHandle) {} MegaChildrenLists::~MegaChildrenLists() { } MegaChildrenLists *MegaChildrenLists::copy() { return NULL; } MegaNodeList *MegaChildrenLists::getFileList() { return NULL; } MegaNodeList *MegaChildrenLists::getFolderList() { return NULL; } MegaAchievementsDetails::~MegaAchievementsDetails() { } long long MegaAchievementsDetails::getBaseStorage() { return 0; } bool MegaAchievementsDetails::isValidClass(int /*class_id*/) { return false; } long long MegaAchievementsDetails::getClassStorage(int /*class_id*/) { return 0; } long long MegaAchievementsDetails::getClassTransfer(int /*class_id*/) { return 0; } int MegaAchievementsDetails::getClassExpire(int /*class_id*/) { return 0; } unsigned int MegaAchievementsDetails::getAwardsCount() { return 0; } int MegaAchievementsDetails::getAwardClass(unsigned int /*index*/) { return 0; } int MegaAchievementsDetails::getAwardId(unsigned int /*index*/) { return 0; } int64_t MegaAchievementsDetails::getAwardTimestamp(unsigned int /*index*/) { return 0; } int64_t MegaAchievementsDetails::getAwardExpirationTs(unsigned int /*index*/) { return 0; } MegaStringList* MegaAchievementsDetails::getAwardEmails(unsigned int /*index*/) { return NULL; } int MegaAchievementsDetails::getRewardsCount() { return 0; } int MegaAchievementsDetails::getRewardAwardId(unsigned int /*index*/) { return 0; } long long MegaAchievementsDetails::getRewardStorage(unsigned int /*index*/) { return 0; } long long MegaAchievementsDetails::getRewardTransfer(unsigned int /*index*/) { return 0; } long long MegaAchievementsDetails::getRewardStorageByAwardId(int /*award_id*/) { return 0; } long long MegaAchievementsDetails::getRewardTransferByAwardId(int /*award_id*/) { return 0; } int MegaAchievementsDetails::getRewardExpire(unsigned int /*index*/) { return 0; } MegaAchievementsDetails *MegaAchievementsDetails::copy() { return NULL; } long long MegaAchievementsDetails::currentStorage() { return 0; } long long MegaAchievementsDetails::currentTransfer() { return 0; } long long MegaAchievementsDetails::currentStorageReferrals() { return 0; } long long MegaAchievementsDetails::currentTransferReferrals() { return 0; } MegaFolderInfo::~MegaFolderInfo() { } MegaFolderInfo *MegaFolderInfo::copy() const { return NULL; } int MegaFolderInfo::getNumVersions() const { return 0; } int MegaFolderInfo::getNumFiles() const { return 0; } int MegaFolderInfo::getNumFolders() const { return 0; } long long MegaFolderInfo::getCurrentSize() const { return 0; } long long MegaFolderInfo::getVersionsSize() const { return 0; } MegaTimeZoneDetails::~MegaTimeZoneDetails() { } MegaTimeZoneDetails *MegaTimeZoneDetails::copy() const { return NULL; } int MegaTimeZoneDetails::getNumTimeZones() const { return 0; } const char *MegaTimeZoneDetails::getTimeZone(int /*index*/) const { return NULL; } int MegaTimeZoneDetails::getTimeOffset(int /*index*/) const { return 0; } int MegaTimeZoneDetails::getDefault() const { return -1; } MegaPushNotificationSettings *MegaPushNotificationSettings::createInstance() { return new MegaPushNotificationSettingsPrivate(); } MegaPushNotificationSettings::~MegaPushNotificationSettings() { } MegaPushNotificationSettings *MegaPushNotificationSettings::copy() const { return NULL; } bool MegaPushNotificationSettings::isGlobalDndEnabled() const { return false; } bool MegaPushNotificationSettings::isGlobalChatsDndEnabled() const { return false; } int64_t MegaPushNotificationSettings::getGlobalDnd() const { return 0; } int64_t MegaPushNotificationSettings::getGlobalChatsDnd() const { return 0; } bool MegaPushNotificationSettings::isGlobalScheduleEnabled() const { return false; } int MegaPushNotificationSettings::getGlobalScheduleStart() const { return 0; } int MegaPushNotificationSettings::getGlobalScheduleEnd() const { return 0; } const char *MegaPushNotificationSettings::getGlobalScheduleTimezone() const { return NULL; } bool MegaPushNotificationSettings::isChatDndEnabled(MegaHandle /*chatid*/) const { return false; } int64_t MegaPushNotificationSettings::getChatDnd(MegaHandle /*chatid*/) const { return 0; } bool MegaPushNotificationSettings::isChatAlwaysNotifyEnabled(MegaHandle /*chatid*/) const { return false; } bool MegaPushNotificationSettings::isContactsEnabled() const { return false; } bool MegaPushNotificationSettings::isSharesEnabled() const { return false; } void MegaPushNotificationSettings::enableGlobal(bool /*enable*/) { } void MegaPushNotificationSettings::setGlobalDnd(int64_t /*timestamp*/) { } void MegaPushNotificationSettings::disableGlobalDnd() { } void MegaPushNotificationSettings::setGlobalSchedule(int /*start*/, int /*end*/, const char * /*timezone*/) { } void MegaPushNotificationSettings::disableGlobalSchedule() { } void MegaPushNotificationSettings::enableChat(MegaHandle /*chatid*/, bool /*enable*/) { } void MegaPushNotificationSettings::setChatDnd(MegaHandle /*chatid*/, int64_t /*timestamp*/) { } void MegaPushNotificationSettings::setGlobalChatsDnd(int64_t /*timestamp*/) { } void MegaPushNotificationSettings::enableChatAlwaysNotify(MegaHandle /*chatid*/, bool /*enable*/) { } void MegaPushNotificationSettings::enableContacts(bool /*enable*/) { } void MegaPushNotificationSettings::enableShares(bool /*enable*/) { } void MegaPushNotificationSettings::enableChats(bool /*enable*/) { } MegaPushNotificationSettings::MegaPushNotificationSettings() { } MegaCancelToken *MegaCancelToken::createInstance() { return new MegaCancelTokenPrivate(CancelToken(false)); } MegaCancelToken::MegaCancelToken() { } MegaCancelToken::~MegaCancelToken() { } MegaIntegerList::~MegaIntegerList() { } MegaIntegerList* MegaIntegerList::createInstance() { return new MegaIntegerListPrivate(); } MegaIntegerList *MegaIntegerList::copy() const { return nullptr; } int64_t MegaIntegerList::get(int /*i*/) const { return -1; } void MegaIntegerList::add(long long /*i*/) { } int MegaIntegerList::size() const { return 0; } MegaBanner::MegaBanner() {} MegaBanner::~MegaBanner() { } MegaBanner* MegaBanner::copy() const { return nullptr; } int MegaBanner::getId() const { return 0; } const char* MegaBanner::getTitle() const { return nullptr; } const char* MegaBanner::getDescription() const { return nullptr; } const char* MegaBanner::getImage() const { return nullptr; } const char* MegaBanner::getUrl() const { return nullptr; } const char* MegaBanner::getBackgroundImage() const { return nullptr; } const char* MegaBanner::getImageLocation() const { return nullptr; } int MegaBanner::getVariant() const { return -1; } const char* MegaBanner::getButton() const { return nullptr; } MegaBannerList::MegaBannerList() { } MegaBannerList::~MegaBannerList() { } MegaBannerList* MegaBannerList::copy() const { return nullptr; } const MegaBanner* MegaBannerList::get(int /*i*/) const { return nullptr; } int MegaBannerList::size() const { return 0; } MegaCurrency::~MegaCurrency() { } MegaCurrency *MegaCurrency::copy() { return nullptr; } /* MegaVpnCredentials BEGIN */ MegaVpnCredentials::MegaVpnCredentials() { } MegaVpnCredentials::~MegaVpnCredentials() { } MegaIntegerList* MegaVpnCredentials::getSlotIDs() const { return nullptr; } MegaStringList* MegaVpnCredentials::getVpnRegions() const { return nullptr; } MegaVpnRegionList* MegaVpnCredentials::getVpnRegionsDetailed() const { return nullptr; } const char* MegaVpnCredentials::getIPv4(int /*slotID*/) const { return nullptr; } const char* MegaVpnCredentials::getIPv6(int /*slotID*/) const { return nullptr; } const char* MegaVpnCredentials::getDeviceID(int /*slotID*/) const { return nullptr; } int MegaVpnCredentials::getClusterID(int /*slotID*/) const { return 0; } const char* MegaVpnCredentials::getClusterPublicKey(int /*clusterID*/) const { return nullptr; } MegaVpnCredentials* MegaVpnCredentials::copy() const { return nullptr; } /* MegaVpnCredentials END */ MegaNodeTree* MegaNodeTree::createInstance(const MegaNodeTree* nodeTreeChild, const char* name, const char* s4AttributeValue, const MegaCompleteUploadData* completeUploadData, MegaHandle sourceHandle) { return new MegaNodeTreePrivate(nodeTreeChild, name ? name : "", s4AttributeValue ? s4AttributeValue : "", completeUploadData, sourceHandle, INVALID_HANDLE); } MegaCompleteUploadData* MegaCompleteUploadData::createInstance(const char* fingerprint, const char* string64UploadToken, const char* string64FileKey) { return new MegaCompleteUploadDataPrivate(fingerprint ? fingerprint : "", string64UploadToken ? string64UploadToken : "", string64FileKey ? string64FileKey : ""); } MegaGfxProvider::~MegaGfxProvider() = default; MegaGfxProvider* MegaGfxProvider::createIsolatedInstance(const char* endpointName, const char* executable, unsigned int keepAliveInSeconds, const MegaStringList* extraArgs) { auto provider = MegaGfxProviderPrivate::createIsolatedInstance(endpointName, executable, keepAliveInSeconds, extraArgs); return provider.release(); } MegaGfxProvider* MegaGfxProvider::createExternalInstance(MegaGfxProcessor* processor) { return MegaGfxProviderPrivate::createExternalInstance(processor).release(); } MegaGfxProvider* MegaGfxProvider::createInternalInstance() { return MegaGfxProviderPrivate::createInternalInstance().release(); } MegaFuseExecutorFlags::MegaFuseExecutorFlags() = default; MegaFuseExecutorFlags::~MegaFuseExecutorFlags() = default; MegaFuseFlags::MegaFuseFlags() = default; MegaFuseFlags::~MegaFuseFlags() = default; MegaFuseFlags* MegaFuseFlags::create() { return new MegaFuseFlagsPrivate(fuse::ServiceFlags()); } MegaFuseInodeCacheFlags::MegaFuseInodeCacheFlags() = default; MegaFuseInodeCacheFlags::~MegaFuseInodeCacheFlags() = default; MegaMount::MegaMount() = default; MegaMount::~MegaMount() = default; MegaMount* MegaMount::create() { return new MegaMountPrivate(); } const char* MegaMount::getResultDescription(int result) { assert(result >= ABORTED && result <= UNSUPPORTED); return fuse::toDescription(static_cast<fuse::MountResult>(result)); } const char* MegaMount::getResultString(int result) { assert(result >= ABORTED && result <= UNSUPPORTED); return fuse::toString(static_cast<fuse::MountResult>(result)); } MegaMountFlags::MegaMountFlags() = default; MegaMountFlags::~MegaMountFlags() = default; MegaMountFlags* MegaMountFlags::create() { return new MegaMountFlagsPrivate(); } MegaMountList::MegaMountList() = default; MegaMountList::~MegaMountList() = default; MegaCancelSubscriptionReason* MegaCancelSubscriptionReason::create(const char* text, const char* position) { return new MegaCancelSubscriptionReasonPrivate(text, position); } MegaCancelSubscriptionReasonList* MegaCancelSubscriptionReasonList::create() { return new MegaCancelSubscriptionReasonListPrivate(); } MegaUploadOptions* MegaUploadOptions::createInstance() { return new MegaUploadOptions(); } MegaDiscountCode::MegaDiscountCode() {} MegaDiscountCode::~MegaDiscountCode() {} MegaDiscountCode* MegaDiscountCode::copy() const { return nullptr; } const char* MegaDiscountCode::getCode() const { return nullptr; } int MegaDiscountCode::getItem() const { return -1; } int MegaDiscountCode::getAccountLevel() const { return -1; } int MegaDiscountCode::getMonths() const { return -1; } int MegaDiscountCode::getPercentageDiscount() const { return -1; } int MegaDiscountCode::getBehaviorType() const { return -1; } MegaDiscountCodeList::MegaDiscountCodeList() {} MegaDiscountCodeList::~MegaDiscountCodeList() {} MegaDiscountCodeList* MegaDiscountCodeList::copy() const { return nullptr; } const MegaDiscountCode* MegaDiscountCodeList::get(int /*i*/) const { return nullptr; } int MegaDiscountCodeList::size() const { return 0; } MegaDiscountCodeInfo::MegaDiscountCodeInfo() {} MegaDiscountCodeInfo::~MegaDiscountCodeInfo() {} MegaDiscountCodeInfo* MegaDiscountCodeInfo::copy() const { return nullptr; } int MegaDiscountCodeInfo::getExpiry() const { return -1; } int MegaDiscountCodeInfo::getCompulsorySubscription() const { return -1; } int MegaDiscountCodeInfo::getMultiDiscount() const { return -1; } MegaStringIntegerMap* MegaDiscountCodeInfo::getFeatures() const { return nullptr; } int MegaDiscountCodeInfo::getTaxValue() const { return -1; } bool MegaDiscountCodeInfo::isTaxExempt() const { return false; } bool MegaDiscountCodeInfo::isTaxAppliedOnTop() const { return false; } int MegaDiscountCodeInfo::getTaxRate() const { return -1; } const char* MegaDiscountCodeInfo::getTaxName() const { return nullptr; } const char* MegaDiscountCodeInfo::getTaxCountry() const { return nullptr; } double MegaDiscountCodeInfo::getEuroTotalPrice() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountAmount() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountedTotalPrice() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountedMonthlyPrice() const { return 0; } double MegaDiscountCodeInfo::getEuroTotalPriceNet() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountAmountNet() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountedTotalPriceNet() const { return 0; } double MegaDiscountCodeInfo::getEuroDiscountedMonthlyPriceNet() const { return 0; } const char* MegaDiscountCodeInfo::getLocalCurrencyCode() const { return nullptr; } const char* MegaDiscountCodeInfo::getLocalCurrencySymbol() const { return nullptr; } double MegaDiscountCodeInfo::getLocalTotalPrice() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountAmount() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountedTotalPrice() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountedMonthlyPrice() const { return 0; } double MegaDiscountCodeInfo::getLocalTotalPriceNet() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountAmountNet() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountedTotalPriceNet() const { return 0; } double MegaDiscountCodeInfo::getLocalDiscountedMonthlyPriceNet() const { return 0; } } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/src/megaapi_impl.cpp��������������������������������������������������������������������0000664�0000000�0000000�00005027742�15162662266�0016407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file megaapi_impl.cpp * @brief Private implementation of the intermediate layer for the MEGA C++ SDK. * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #define _LARGE_FILES #define _GNU_SOURCE 1 #define _FILE_OFFSET_BITS 64 #define USE_VARARGS #define PREFER_STDARG #include "megaapi_impl.h" #include "mega/canceller.h" #include "mega/mediafileattribute.h" #include "mega/scoped_helpers.h" #include "mega/tlv.h" #include "mega/user_attribute.h" #include "mega/utils_optional.h" #include "megaapi.h" #ifdef ENABLE_ISOLATED_GFX #include "mega/gfx/isolatedprocess.h" #endif #include <algorithm> #include <cctype> #include <charconv> #include <cstdint> #include <functional> #include <iomanip> #include <locale> #include <numeric> #include <thread> #ifndef _WIN32 #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #include <signal.h> #endif #ifdef __APPLE__ #include <xlocale.h> #include <strings.h> #endif #ifdef _WIN32 #include <shlwapi.h> #endif #ifdef USE_OPENSSL #include <openssl/rand.h> #endif #include <zxcvbn-c/zxcvbn.h> // FUSE #include <mega/fuse/common/mount_event_type.h> #include <mega/fuse/common/mount_event.h> #include <mega/fuse/common/mount_info.h> namespace { using ::mega::IGfxProvider; using ::mega::MegaGfxProcessor; using ::mega::GfxProc; using ::mega::GfxProviderExternal; std::unique_ptr<GfxProc> createGfxProc(MegaGfxProcessor* processor) { // createInternalGfxProvider could return nullptr std::unique_ptr<IGfxProvider> provider = processor ? std::make_unique<GfxProviderExternal>(processor) : IGfxProvider::createInternalGfxProvider(); return provider ? std::make_unique<GfxProc>(std::move(provider)) : nullptr; } } namespace mega { MegaNodePrivate::MegaNodePrivate(const char *name, int type, int64_t size, int64_t ctime, int64_t mtime, uint64_t nodehandle, const string *nodekey, const string *fileattrstring, const char *fingerprint, const char *originalFingerprint, MegaHandle owner, MegaHandle parentHandle, const char *privateauth, const char *publicauth, bool ispublic, bool isForeign, const char *chatauth, bool isNodeKeyDecrypted) : MegaNode() { this->name = MegaApi::strdup(name); this->fingerprint = MegaApi::strdup(fingerprint); this->originalfingerprint = MegaApi::strdup(originalFingerprint); this->customAttrs = NULL; this->duration = -1; this->width = -1; this->height = -1; this->shortformat = -1; this->videocodecid = -1; this->latitude = INVALID_COORDINATE; this->longitude = INVALID_COORDINATE; this->type = type; this->size = size; this->ctime = ctime; this->mtime = mtime; this->nodehandle = nodehandle; this->parenthandle = parentHandle; mIsNodeKeyDecrypted = isNodeKeyDecrypted; this->fileattrstring.assign(fileattrstring->data(), fileattrstring->size()); this->nodekey.assign(nodekey->data(), nodekey->size()); this->changed = 0; this->thumbnailAvailable = (Node::hasfileattribute(fileattrstring, GfxProc::THUMBNAIL) != 0); this->previewAvailable = (Node::hasfileattribute(fileattrstring, GfxProc::PREVIEW) != 0); this->isPublicNode = ispublic; this->outShares = false; this->inShare = false; this->plink = NULL; this->mNewLinkFormat = false; this->sharekey = NULL; this->foreign = isForeign; this->children = NULL; this->owner = owner; this->mFavourite = false; this->mLabel = LBL_UNKNOWN; if (privateauth) { this->privateAuth = privateauth; } if (publicauth) { this->publicAuth = publicauth; } this->chatAuth = chatauth ? MegaApi::strdup(chatauth) : NULL; } MegaNodePrivate::MegaNodePrivate(MegaNode *node) : MegaNode() { this->name = MegaApi::strdup(node->getName()); this->fingerprint = MegaApi::strdup(node->getFingerprint()); this->originalfingerprint = MegaApi::strdup(node->getOriginalFingerprint()); this->customAttrs = NULL; MegaNodePrivate *np = dynamic_cast<MegaNodePrivate *>(node); if (!np) { LOG_err << "Critical error: Unexpected MegaNode extension received"; assert(false); return; } // Optimization to avoid decode media info when getter is called this->duration = np->duration; this->width = np->width; this->height = np->height; this->shortformat = np->shortformat; this->videocodecid = np->videocodecid; this->mFavourite = node->isFavourite(); this->mLabel = static_cast<nodelabel_t>(node->getLabel()); this->mDeviceId = node->getDeviceId(); this->mS4 = node->getS4(); this->mMarkedSensitive = node->isMarkedSensitive(); if (np->mOfficialAttrs) this->mOfficialAttrs = std::make_unique<attr_map>(*np->mOfficialAttrs); this->latitude = node->getLatitude(); this->longitude = node->getLongitude(); this->restorehandle = node->getRestoreHandle(); this->type = node->getType(); this->size = node->getSize(); this->ctime = node->getCreationTime(); this->mtime = node->getModificationTime(); this->nodehandle = node->getHandle(); this->parenthandle = node->getParentHandle(); mIsNodeKeyDecrypted = node->isNodeKeyDecrypted(); char* fileAttributeString = node->getFileAttrString(); if (fileAttributeString) { this->fileattrstring = std::string(fileAttributeString); delete [] fileAttributeString; } assert(node->getNodeKey() && "node key cannot be null"); this->nodekey = *node->getNodeKey(); this->changed = node->getChanges(); this->thumbnailAvailable = node->hasThumbnail(); this->previewAvailable = node->hasPreview(); this->isPublicNode = node->isPublic(); this->privateAuth = *np->getPrivateAuth(); this->publicAuth = *np->getPublicAuth(); this->chatAuth = np->getChatAuth() ? MegaApi::strdup(np->getChatAuth()) : NULL; this->outShares = node->isOutShare(); this->inShare = node->isInShare(); this->foreign = node->isForeign(); this->sharekey = NULL; this->children = NULL; this->owner = node->getOwner(); if (node->isExported()) { this->plink = new PublicLink(node->getPublicHandle(), node->getPublicLinkCreationTime(), node->getExpirationTime(), node->isTakenDown(), node->getWritableLinkAuthKey()); if (type == FOLDERNODE) { MegaNodePrivate *n = dynamic_cast<MegaNodePrivate *>(node); if (n) { string *sk = n->getSharekey(); if (sk) { this->sharekey = new string(*sk); } } } } else { this->plink = NULL; } this->mNewLinkFormat = np->isNewLinkFormat(); if (node->hasCustomAttrs()) { this->customAttrs = new attr_map(); MegaStringList *names = node->getCustomAttrNames(); for (int i = 0; i < names->size(); i++) { (*customAttrs)[AttrMap::string2nameid(names->get(i))] = node->getCustomAttr(names->get(i)); } delete names; } } MegaNodePrivate::MegaNodePrivate(Node *node) : MegaNode() { this->name = MegaApi::strdup(node->displayname()); this->fingerprint = NULL; this->originalfingerprint = NULL; this->children = NULL; if (node->isvalid) { string tempFingerprint; node->serializefingerprint(&tempFingerprint); string result = MegaNodePrivate::addAppPrefixToFingerprint(tempFingerprint, node->size); fingerprint = MegaApi::strdup(result.c_str()); } this->duration = -1; this->width = -1; this->height = -1; this->shortformat = -1; this->videocodecid = -1; this->latitude = INVALID_COORDINATE; this->longitude = INVALID_COORDINATE; this->customAttrs = NULL; this->restorehandle = UNDEF; this->mFavourite = false; this->mLabel = LBL_UNKNOWN; char buf[10]; for (attr_map::iterator it = node->attrs.map.begin(); it != node->attrs.map.end(); it++) { int attrlen = node->attrs.nameid2string(it->first, buf); buf[attrlen] = '\0'; if (buf[0] == '_') { if (!customAttrs) { customAttrs = new attr_map(); } nameid id = AttrMap::string2nameid(&buf[1]); (*customAttrs)[id] = it->second; } else { if (it->first == AttrMap::string2nameid("d")) { if (node->type == FILENODE) { duration = int(Base64::atoi(&it->second)); } } else if (it->first == AttrMap::string2nameid("l") || it->first == AttrMap::string2nameid("gp")) { if (node->type == FILENODE) { string coords = it->second; if ((it->first == AttrMap::string2nameid("l") && coords.size() != 8) || (it->first == AttrMap::string2nameid("gp") && coords.size() != Base64Str<16>::STRLEN)) { LOG_warn << "Malformed GPS coordinates attribute"; } else { bool ok = true; if (it->first == AttrMap::string2nameid("gp")) { if (node->client && node->client->unshareablekey.size() == Base64Str<SymmCipher::KEYLENGTH>::STRLEN && coords.size() == Base64Str<16>::STRLEN) { SymmCipher c; byte data[SymmCipher::BLOCKSIZE] = { 0 }; Base64::atob(coords.data(), data, Base64Str<SymmCipher::BLOCKSIZE>::STRLEN); node->client->setkey(&c, node->client->unshareablekey.data()); c.ctr_crypt(data, SymmCipher::BLOCKSIZE, 0, 0, NULL, false); ok = Utils::startswith(reinterpret_cast<const char*>(data), "unshare/"); if (ok) { coords = string((char*)data + 8, 8); } } else { ok = false; } } if (ok) { byte bufCoords[3]; int number = 0; if (Base64::atob((const char*)coords.substr(0, 4).data(), bufCoords, sizeof(bufCoords)) == sizeof(bufCoords)) { number = (bufCoords[2] << 16) | (bufCoords[1] << 8) | (bufCoords[0]); latitude = -90 + 180 * (double)number / 0xFFFFFF; } if (Base64::atob((const char*)coords.substr(4, 4).data(), bufCoords, sizeof(bufCoords)) == sizeof(bufCoords)) { number = (bufCoords[2] << 16) | (bufCoords[1] << 8) | (bufCoords[0]); longitude = -180 + 360 * (double)number / 0x01000000; } } } if (longitude < -180 || longitude > 180) { longitude = INVALID_COORDINATE; } if (latitude < -90 || latitude > 90) { latitude = INVALID_COORDINATE; } if (longitude == INVALID_COORDINATE || latitude == INVALID_COORDINATE) { longitude = INVALID_COORDINATE; latitude = INVALID_COORDINATE; } } } else if (it->first == AttrMap::string2nameid("rr")) { handle rr = 0; if (Base64::atob(it->second.c_str(), (byte *)&rr, sizeof(rr)) == MegaClient::NODEHANDLE) { restorehandle = rr; } } else if (it->first == AttrMap::string2nameid("c") && !fingerprint) { fingerprint = MegaApi::strdup(it->second.c_str()); } else if (it->first == AttrMap::string2nameid("c0")) { originalfingerprint = MegaApi::strdup(it->second.c_str()); } else if (it->first == AttrMap::string2nameid("fav")) { try { int fav = it->second.empty() ? 0 : std::stoi(it->second); if (fav != 1 && it->second != "0") { LOG_err << "Invalid value for node attr fav: " << fav; } else { mFavourite = fav != 0; } } catch (std::exception& ex) { LOG_err << "Conversion failure for node attr fav: " << ex.what(); } } else if (it->first == AttrMap::string2nameid("sen")) { try { int sen = it->second.empty() ? 0 : std::stoi(it->second); if (sen != 1 && it->second != "0") { LOG_err << "Invalid value for node attr sen: " << sen; } else { mMarkedSensitive = sen != 0; } } catch (std::exception& ex) { LOG_err << "Conversion failure for node attr sen: " << ex.what(); } } else if (it->first == AttrMap::string2nameid("lbl")) { try { int lbl = it->second.empty() ? LBL_UNKNOWN : std::stoi(it->second); if ((lbl < LBL_RED || lbl > LBL_GREY) && it->second != "0") { LOG_err << "Invalid value for node attr lbl: " << lbl; } else { mLabel = static_cast<nodelabel_t>(lbl); } } catch (std::exception& ex) { LOG_err << "Conversion failure for node attr lbl: " << ex.what(); } } else if (it->first == AttrMap::string2nameid("dev-id") || it->first == AttrMap::string2nameid("drv-id")) { mDeviceId = it->second; } else if (it->first == AttrMap::string2nameid("s4")) { mS4 = it->second; } else if (it->first == AttrMap::string2nameid(MegaClient::NODE_ATTR_PASSWORD_MANAGER) || it->first == AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_DESCRIPTION) || it->first == AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS)) { if (!mOfficialAttrs) mOfficialAttrs = std::make_unique<attr_map>(); (*mOfficialAttrs)[it->first] = it->second; } } } this->type = node->type; this->size = node->size; this->ctime = node->ctime; this->mtime = node->mtime; this->nodehandle = node->nodehandle; this->parenthandle = node->parent ? node->parent->nodehandle : INVALID_HANDLE; this->owner = node->owner; mIsNodeKeyDecrypted = node->attrstring == nullptr; // it's reset after node's key decryption successfull this->fileattrstring = node->fileattrstring; this->nodekey = node->nodekeyUnchecked(); this->changed = 0; if(node->changed.attrs) { this->changed |= MegaNode::CHANGE_TYPE_ATTRIBUTES; } if(node->changed.ctime) { this->changed |= MegaNode::CHANGE_TYPE_TIMESTAMP; } if(node->changed.fileattrstring) { this->changed |= MegaNode::CHANGE_TYPE_FILE_ATTRIBUTES; } if(node->changed.inshare) { this->changed |= MegaNode::CHANGE_TYPE_INSHARE; } if(node->changed.outshares) { this->changed |= MegaNode::CHANGE_TYPE_OUTSHARE; } if(node->changed.pendingshares) { this->changed |= MegaNode::CHANGE_TYPE_PENDINGSHARE; } if(node->changed.owner) { this->changed |= MegaNode::CHANGE_TYPE_OWNER; } if(node->changed.parent) { this->changed |= MegaNode::CHANGE_TYPE_PARENT; } if(node->changed.removed) { this->changed |= MegaNode::CHANGE_TYPE_REMOVED; } if(node->changed.publiclink) { this->changed |= MegaNode::CHANGE_TYPE_PUBLIC_LINK; } if(node->changed.newnode) { this->changed |= MegaNode::CHANGE_TYPE_NEW; } if (node->changed.name) { this->changed |= MegaNode::CHANGE_TYPE_NAME; } if (node->changed.favourite) { this->changed |= MegaNode::CHANGE_TYPE_FAVOURITE; } if (node->changed.counter) { this->changed |= MegaNode::CHANGE_TYPE_COUNTER; } if (node->changed.sensitive) { this->changed |= MegaNode::CHANGE_TYPE_SENSITIVE; } if (node->changed.pwd) { this->changed |= MegaNode::CHANGE_TYPE_PWD; } if (node->changed.description) { this->changed |= MegaNode::CHANGE_TYPE_DESCRIPTION; } if (node->changed.tags) { this->changed |= MegaNode::CHANGE_TYPE_TAGS; } this->thumbnailAvailable = (node->hasfileattribute(0) != 0); this->previewAvailable = (node->hasfileattribute(1) != 0); this->isPublicNode = false; this->foreign = false; // if there's only one share and it has no user --> public link this->outShares = (node->outshares) ? (node->outshares->size() > 1 || node->outshares->begin()->second->user) : false; this->inShare = node->inshare != nullptr; this->plink = node->plink ? new PublicLink(*node->plink) : NULL; this->mNewLinkFormat = node->client->mNewLinkFormat; if (plink && type == FOLDERNODE && node->sharekey) { char key[FOLDERNODEKEYLENGTH*4/3+3]; Base64::btoa(node->sharekey->key, FOLDERNODEKEYLENGTH, key); this->sharekey = new string(key); } else { this->sharekey = NULL; } } string* MegaNodePrivate::getSharekey() { return sharekey; } MegaNode *MegaNodePrivate::copy() { return new MegaNodePrivate(this); } char *MegaNodePrivate::serialize() { string d; if (!serialize(&d)) { return NULL; } char *ret = new char[d.size()*4/3+3]; Base64::btoa((byte*)d.data(), d.size(), ret); return ret; } bool MegaNodePrivate::serialize(string *d) const { CacheableWriter w(*d); w.serializecstr(name, true); w.serializecstr(fingerprint, true); w.serializei64(size); w.serializei64(ctime); w.serializei64(mtime); w.serializehandle(nodehandle); w.serializehandle(parenthandle); w.serializestring(""); // used to be attrstring w.serializestring(nodekey); w.serializestring(privateAuth); w.serializestring(publicAuth); w.serializebool(isPublicNode); w.serializebool(foreign); bool hasChatAuth = chatAuth && chatAuth[0]; bool hasOwner = true; bool hasOriginalFingerprint = originalfingerprint && originalfingerprint[0]; w.serializeexpansionflags(hasChatAuth, hasOwner, hasOriginalFingerprint, mIsNodeKeyDecrypted); if (hasChatAuth) { w.serializecstr(chatAuth, false); } if (hasOwner) { w.serializehandle(owner); } if (hasOriginalFingerprint) { w.serializecstr(originalfingerprint, false); } // 4th boolean in expansion flags will be set to the boolean value directly return true; } MegaNodePrivate *MegaNodePrivate::unserialize(string *d) { CacheableReader r(*d); string name, fingerprint, originalfingerprint, attrstring, nodekey, privauth, pubauth, chatauth; int64_t size, ctime, mtime; MegaHandle nodehandle, parenthandle, owner = INVALID_HANDLE; bool isPublic, isForeign, isNodeKeyDecrypted; unsigned char expansions[8]; string fileattrstring; // fileattrstring is not serialized if (!r.unserializecstr(name, true) || !r.unserializecstr(fingerprint, true) || !r.unserializei64(size) || !r.unserializei64(ctime) || !r.unserializei64(mtime) || !r.unserializehandle(nodehandle) || !r.unserializehandle(parenthandle) || !r.unserializestring(attrstring) || !r.unserializestring(nodekey) || !r.unserializestring(privauth) || !r.unserializestring(pubauth) || !r.unserializebool(isPublic) || !r.unserializebool(isForeign) || !r.unserializeexpansionflags(expansions, 4) || (expansions[0] && !r.unserializecstr(chatauth, false)) || (expansions[1] && !r.unserializehandle(owner)) || (expansions[2] && !r.unserializecstr(originalfingerprint, false))) { LOG_err << "MegaNode unserialization failed at field " << r.fieldnum; return NULL; } isNodeKeyDecrypted = expansions[3] > 0; // the expansion flag is used to represent its value r.eraseused(*d); return new MegaNodePrivate(name.c_str(), FILENODE, size, ctime, mtime, nodehandle, &nodekey, &fileattrstring, fingerprint.empty() ? NULL : fingerprint.c_str(), originalfingerprint.empty() ? NULL : originalfingerprint.c_str(), owner, parenthandle, privauth.c_str(), pubauth.c_str(), isPublic, isForeign, chatauth.empty() ? NULL : chatauth.c_str(), isNodeKeyDecrypted); } char *MegaNodePrivate::getBase64Handle() { char *base64Handle = new char[12]; Base64::btoa((byte*)&(nodehandle),MegaClient::NODEHANDLE,base64Handle); return base64Handle; } int MegaNodePrivate::getType() const { return type; } const char* MegaNodePrivate::getName() { if(type <= FOLDERNODE) { return name; } switch(type) { case ROOTNODE: return "Cloud Drive"; case VAULTNODE: return "Vault"; case RUBBISHNODE: return "Rubbish Bin"; default: return name; } } const char *MegaNodePrivate::getFingerprint() { return fingerprint; } const char *MegaNodePrivate::getOriginalFingerprint() { return originalfingerprint; } bool MegaNodePrivate::hasCustomAttrs() { return customAttrs != NULL; } MegaStringList *MegaNodePrivate::getCustomAttrNames() { if (!customAttrs) { return new MegaStringList(); } string_vector names; for (attr_map::iterator it = customAttrs->begin(); it != customAttrs->end(); it++) { names.push_back(AttrMap::nameid2string(it->first)); } return new MegaStringListPrivate(std::move(names)); } const char* MegaNodePrivate::getAttrFrom(const char *attrName, const attr_map* m) const { if (!m) { return NULL; } nameid n = AttrMap::string2nameid(attrName); if (!n) { return NULL; } auto it = m->find(n); if (it == m->end()) { return NULL; } return it->second.c_str(); } const char *MegaNodePrivate::getCustomAttr(const char *attrName) { return getAttrFrom(attrName, customAttrs); } const char *MegaNodePrivate::getOfficialAttr(const char *attrName) const { return getAttrFrom(attrName, mOfficialAttrs.get()); } int MegaNodePrivate::getDuration() { if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) { uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file && mediaProperties.shortformat != 254 // 254 = No information available && mediaProperties.playtime > 0) { return static_cast<int>(mediaProperties.playtime); } } return duration; } bool MegaNodePrivate::isFavourite() { return mFavourite; } bool MegaNodePrivate::isMarkedSensitive() { return mMarkedSensitive; } int MegaNodePrivate::getLabel() { return mLabel; } int MegaNodePrivate::getWidth() { if (width == -1) // not initialized yet, or not available { if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) { uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file && mediaProperties.shortformat != 254 // 254 = No information available && mediaProperties.width > 0) { width = static_cast<int>(mediaProperties.width); } } } return width; } int MegaNodePrivate::getHeight() { if (height == -1) // not initialized yet, or not available { if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) { uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file && mediaProperties.shortformat != 254 // 254 = No information available && mediaProperties.height > 0) { height = static_cast<int>(mediaProperties.height); } } } return height; } int MegaNodePrivate::getShortformat() { if (shortformat == -1) // not initialized yet, or not available { if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) { uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file && mediaProperties.shortformat != 254 // 254 = No information available && mediaProperties.shortformat > 0) { shortformat = mediaProperties.shortformat; } } } return shortformat; } int MegaNodePrivate::getVideocodecid() { if (videocodecid == -1) // not initialized yet, or not available { if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) { uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file && mediaProperties.shortformat != 254 // 254 = No information available && mediaProperties.videocodecid > 0) { videocodecid = static_cast<int>(mediaProperties.videocodecid); } } } return videocodecid; } double MegaNodePrivate::getLatitude() { return latitude; } double MegaNodePrivate::getLongitude() { return longitude; } const char* MegaNodePrivate::getDescription() { return getOfficialAttr(MegaClient::NODE_ATTRIBUTE_DESCRIPTION); } MegaStringList* MegaNodePrivate::getTags() { if (auto delimitedTags = getOfficialAttr(MegaClient::NODE_ATTRIBUTE_TAGS)) return new MegaStringListPrivate(MegaClient::getNodeTags(delimitedTags)); return new MegaStringListPrivate(); } int64_t MegaNodePrivate::getSize() { return size; } int64_t MegaNodePrivate::getCreationTime() { return ctime; } int64_t MegaNodePrivate::getModificationTime() { return mtime; } MegaHandle MegaNodePrivate::getRestoreHandle() { return restorehandle; } MegaHandle MegaNodePrivate::getParentHandle() { return parenthandle; } uint64_t MegaNodePrivate::getHandle() const { return nodehandle; } string *MegaNodePrivate::getNodeKey() { return &nodekey; } bool MegaNodePrivate::isNodeKeyDecrypted() { return mIsNodeKeyDecrypted; } char *MegaNodePrivate::getBase64Key() { char *key = NULL; // the key if (type == FILENODE && nodekey.size() >= FILENODEKEYLENGTH) { key = new char[FILENODEKEYLENGTH * 4 / 3 + 3]; Base64::btoa((const byte*)nodekey.data(), FILENODEKEYLENGTH, key); } else if (type == FOLDERNODE && sharekey) { key = MegaApi::strdup(sharekey->c_str()); } else { key = new char[1]; key[0] = 0; } return key; } char *MegaNodePrivate::getFileAttrString() { char* fileAttributes = NULL; if (fileattrstring.size() > 0) { fileAttributes = MegaApi::strdup(fileattrstring.c_str()); } return fileAttributes; } int64_t MegaNodePrivate::getExpirationTime() { return plink ? plink->ets : -1; } MegaHandle MegaNodePrivate::getPublicHandle() { return plink ? (MegaHandle) plink->ph : INVALID_HANDLE; } MegaNode* MegaNodePrivate::getPublicNode() { if (!plink || plink->isExpired()) { return NULL; } char *skey = getBase64Key(); string key(skey); MegaNode *node = new MegaNodePrivate( name, type, size, ctime, mtime, plink->ph, &key, &fileattrstring, fingerprint, originalfingerprint, INVALID_HANDLE); delete [] skey; return node; } char *MegaNodePrivate::getPublicLink(bool includeKey) { if (!plink) { return NULL; } char *base64k = getBase64Key(); TypeOfLink lType = MegaClient::validTypeForPublicURL(static_cast<nodetype_t>(type)); string strlink = MegaClient::publicLinkURL(mNewLinkFormat, lType, plink->ph, (includeKey ? base64k : nullptr)); delete [] base64k; return MegaApi::strdup(strlink.c_str()); } int64_t MegaNodePrivate::getPublicLinkCreationTime() { return plink ? plink->cts : -1; } const char *MegaNodePrivate::getWritableLinkAuthKey() { return (plink && !plink->mAuthKey.empty()) ? plink->mAuthKey.c_str() : nullptr; } bool MegaNodePrivate::isNewLinkFormat() { return mNewLinkFormat; } bool MegaNodePrivate::isFile() { return type == TYPE_FILE; } bool MegaNodePrivate::isFolder() { return (type != TYPE_FILE) && (type != TYPE_UNKNOWN); } bool MegaNodePrivate::isRemoved() { return hasChanged(MegaNode::CHANGE_TYPE_REMOVED); } bool MegaNodePrivate::hasChanged(uint64_t changeType) { return (changed & changeType); } uint64_t MegaNodePrivate::getChanges() { return changed; } MegaHandle MegaNodePrivate::getOwner() const { return owner; } const char* MegaNodePrivate::getDeviceId() const { return mDeviceId.c_str(); } const char* MegaNodePrivate::getS4() const { return mS4.c_str(); } string MegaNodePrivate::addAppPrefixToFingerprint(const string& fp, const m_off_t nodeSize) { if (fp.empty()) { LOG_warn << "Requesting app prefix addition to an empty fingerprint"; return string{}; } FileFingerprint ffp; if (!ffp.unserializefingerprint(&fp)) { LOG_err << "Internal error: fingerprint validation failed in app prefix addition. Unserialization check failed"; return string{}; } byte bsize[sizeof(nodeSize) + 1]; int l = Serialize64::serialize(bsize, static_cast<uint64_t>(nodeSize)); unique_ptr<char[]> buf(new char[static_cast<size_t>(l * 4 / 3 + 4)]); char ssize = static_cast<char>('A' + Base64::btoa(bsize, static_cast<size_t>(l), buf.get())); string result(1, ssize); result.append(buf.get()); result.append(fp); return result; } string MegaNodePrivate::removeAppPrefixFromFingerprint(const char* appFpParam, m_off_t* nodeSize) { const std::string appFp = appFpParam ? appFpParam : ""; if (appFp.empty()) { LOG_warn << "Requesting app prefix removal from an empty fingerprint"; return string{}; } const size_t sizelen = static_cast<size_t>(appFp[0] - 'A'); if (sizelen > (sizeof(m_off_t) * 4/3 + 4) || appFp.size() <= (sizelen + 1)) { LOG_err << "Internal error: fingerprint validation failed. Fingerprint with sizelen: " << sizelen << " and fplen: " << appFp.size(); return string{}; } if (nodeSize) { m_off_t nSize = 0; int len = sizeof(nSize); auto buf = std::make_unique<byte[]>(static_cast<size_t>(len)); Base64::atob(appFp.c_str() + 1, buf.get(), len); int l = Serialize64::unserialize(buf.get(), len, (uint64_t*)&nSize); if (l <= 0) { LOG_err << "Internal error: node size extraction from fingerprint failed"; return string{}; } *nodeSize = nSize; } FileFingerprint ffp; string result = appFp.substr(sizelen + 1); if (!ffp.unserializefingerprint(&result)) { LOG_err << "Internal error: fingerprint unserialization failed in app prefix removal"; return string{}; } return result; } MegaBackgroundMediaUploadPrivate::MegaBackgroundMediaUploadPrivate(MegaApi* capi) : api(MegaApiImpl::ImplOf(capi)) { // generate fresh random encryption key/CTR IV for this file api->client->rng.genblock(filekey, sizeof filekey); } MegaBackgroundMediaUploadPrivate::MegaBackgroundMediaUploadPrivate(const string& serialised, MegaApi* capi) : api(MegaApiImpl::ImplOf(capi)) { CacheableReader r(serialised); string mediapropertiesstr; unsigned char expansions[8]; string fileattrstring; // fileattrstring is not serialized if (!r.unserializebinary(filekey, sizeof(filekey)) || !r.unserializechunkmacs(chunkmacs) || !r.unserializestring(mediapropertiesstr) || !r.unserializestring(url) || !r.unserializedouble(latitude) || !r.unserializedouble(longitude) || !r.unserializebool(unshareableGPS) || !r.unserializehandle(thumbnailFA) || !r.unserializehandle(previewFA) || !r.unserializeexpansionflags(expansions, 0)) { LOG_err << "MegaBackgroundMediaUploadPrivate unserialization failed at field " << r.fieldnum; } else { mediaproperties = MediaProperties(mediapropertiesstr); } } bool MegaBackgroundMediaUploadPrivate::serialize(string* s) { CacheableWriter w(*s); w.serializebinary(filekey, sizeof(filekey)); w.serializechunkmacs(chunkmacs); w.serializestring(mediaproperties.serialize()); w.serializestring(url); w.serializedouble(latitude); w.serializedouble(longitude); w.serializebool(unshareableGPS); w.serializehandle(thumbnailFA); w.serializehandle(previewFA); w.serializeexpansionflags(); // if/when we add more in future, set the first one true to signal the new set are present. return s != nullptr; } char *MegaBackgroundMediaUploadPrivate::serialize() { string d; return serialize(&d) ? MegaApi::binaryToBase64(d.data(), d.size()) : NULL; } void MegaBackgroundMediaUploadPrivate::setThumbnail(MegaHandle h) { thumbnailFA = h; } void MegaBackgroundMediaUploadPrivate::setPreview(MegaHandle h) { previewFA = h; } void MegaBackgroundMediaUploadPrivate::setCoordinates(double lat, double lon, bool unsh) { latitude = lat; longitude = lon; unshareableGPS = unsh; } SymmCipher* MegaBackgroundMediaUploadPrivate::nodecipher(MegaClient* client) { return client->getRecycledTemporaryNodeCipher(filekey); } MegaBackgroundMediaUploadPrivate::~MegaBackgroundMediaUploadPrivate() { } bool MegaBackgroundMediaUploadPrivate::analyseMediaInfo([[maybe_unused]] const char* inputFilepath) { #ifdef USE_MEDIAINFO std::lock_guard<std::recursive_timed_mutex> g(api->sdkMutex); if (!api->client->mediaFileInfo.mediaCodecsReceived) { // the client app should already have requested these but just in case: api->client->mediaFileInfo.requestCodecMappingsOneTime(api->client, LocalPath()); return false; } auto localfilename = LocalPath::fromAbsolutePath(inputFilepath); string ext; if (api->fsAccess->getextension(localfilename, ext) && MediaProperties::isMediaFilenameExt(ext)) { mediaproperties.extractMediaPropertyFileAttributes(localfilename, api->fsAccess.get()); // cause the codec IDs to be looked up before serialization. Codec names are not serialized, just the codec IDs uint32_t dummykey[4]; mediaproperties.convertMediaPropertyFileAttributes(dummykey, api->client->mediaFileInfo); } #endif return true; } char *MegaBackgroundMediaUploadPrivate::encryptFile(const char* inputFilepath, int64_t startPos, int64_t* length, const char* outputFilepath, bool adjustsizeonly) { if (startPos != ChunkedHash::chunkfloor(startPos)) { LOG_err << "non-chunk start position supplied"; return nullptr; } std::unique_ptr<FileAccess> fain(api->fsAccess->newfileaccess()); auto localfilename = LocalPath::fromAbsolutePath(inputFilepath); if (fain->fopen(localfilename, OPEN_RDONLY, FSLogging::logOnError) || fain->type != FILENODE) { if (*length == -1) { *length = fain->size - startPos; } if (startPos < 0 || startPos > fain->size) { LOG_err << "invalid startPos supplied"; return nullptr; } else if (*length < 0 || startPos + *length > fain->size) { LOG_err << "invalid enryption length supplied"; return nullptr; } else { // make sure we load to a chunk boundary m_off_t endPos = ChunkedHash::chunkceil(startPos + *length, fain->size); *length = endPos - startPos; if (adjustsizeonly) { // return non-null to indicate success. As it's a string return in the standard case, caller must deallocate as usual. return MegaApi::strdup("1"); } else { auto localencryptedfilename = LocalPath::fromAbsolutePath(outputFilepath); std::unique_ptr<FileAccess> faout(api->fsAccess->newfileaccess()); if (faout->fopen(localencryptedfilename, OPEN_WRONLY, FSLogging::logOnError)) { SymmCipher cipher; cipher.setkey(filekey); uint64_t ctriv = MemAccess::get<uint64_t>((const char*)filekey + SymmCipher::KEYLENGTH); EncryptFilePieceByChunks ef(fain.get(), startPos, faout.get(), 0, &cipher, &chunkmacs, ctriv); string urlSuffix; if (ef.encrypt(startPos, endPos, urlSuffix)) { ((int64_t*)filekey)[3] = chunkmacs.macsmac(&cipher); return MegaApi::strdup(urlSuffix.c_str()); } } } } } return nullptr; } char *MegaBackgroundMediaUploadPrivate::getUploadURL() { return url.empty() ? nullptr : MegaApi::strdup(url.c_str()); } EncryptFilePieceByChunks::EncryptFilePieceByChunks(FileAccess *cFain, m_off_t cInPos, FileAccess *cFaout, m_off_t cOutPos, SymmCipher *cipher, chunkmac_map *chunkmacs, uint64_t ctriv) : EncryptByChunks(cipher, chunkmacs, ctriv) , fain(cFain), faout(cFaout) , inpos(cInPos), outpos(cOutPos) , lastsize(0) { } byte *EncryptFilePieceByChunks::nextbuffer(unsigned bufsize) { if (lastsize) { // write the last encrypted chunk if (!faout->fwrite((byte*)buffer.data(), lastsize, outpos)) { return NULL; } outpos += lastsize; } buffer.resize(bufsize + SymmCipher::BLOCKSIZE); memset((void*)(buffer.data() + bufsize), 0, SymmCipher::BLOCKSIZE); if (!fain->frawread((byte*)buffer.data(), bufsize, inpos, false, FSLogging::logOnError)) { return NULL; } lastsize = bufsize; inpos += bufsize; return (byte*)buffer.data(); } /* BEGIN MEGAAPIIMPL */ #ifdef ENABLE_SYNC int MegaApiImpl::isNodeSyncable(MegaNode *megaNode) { MegaError *merror = isNodeSyncableWithError(megaNode); int r = merror->getErrorCode(); delete merror; return r; } MegaError* MegaApiImpl::isNodeSyncableWithError(MegaNode* megaNode) { if (!megaNode) { return new MegaErrorPrivate(MegaError::API_EARGS); } SdkMutexGuard g(sdkMutex); shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if (!node) { return new MegaErrorPrivate(MegaError::API_ENOENT); } const auto [e, se] = client->isnodesyncable(node); return new MegaErrorPrivate(e, se); } bool MegaApiImpl::isScanning() { return receivedScanningStateFlag.load(); } bool MegaApiImpl::isSyncing() { return receivedSyncingStateFlag.load(); } MegaSync *MegaApiImpl::getSyncByBackupId(mega::MegaHandle backupId) { // syncs has its own thread safety SyncConfig config; if (client->syncs.syncConfigByBackupId(backupId, config)) { return new MegaSyncPrivate(config, client); } return nullptr; } MegaSync *MegaApiImpl::getSyncByNode(MegaNode *node) { if (!node) { return nullptr; } NodeHandle nodeHandle = NodeHandle().set6byte(node->getHandle()); // syncs has its own thread safety for (auto& config : client->syncs.getConfigs(false)) { if (config.mRemoteNode == nodeHandle) { return new MegaSyncPrivate(config, client); } } return nullptr; } MegaSync *MegaApiImpl::getSyncByPath(const char *localPath) { if (!localPath) { return nullptr; } // syncs has its own thread safety for (auto& config : client->syncs.getConfigs(false)) { if (config.getLocalPath().toPath(false) == localPath) { return new MegaSyncPrivate(config, client); } } return nullptr; } bool AddressedStallFilter::addressedNameConfict(const string& cloudPath, const LocalPath& localPath) { lock_guard<mutex> g(m); if (!cloudPath.empty()) { if (addressedNameConflictCloudStalls.find(cloudPath) != addressedNameConflictCloudStalls.end()) { return true; } } if (!localPath.empty()) { if (addressedNameConflictLocalStalls.find(localPath) != addressedNameConflictLocalStalls.end()) { return true; } } return false; } bool AddressedStallFilter::addressedCloudStall(const string& cloudPath) { assert (!cloudPath.empty()); lock_guard<mutex> g(m); return addressedSyncCloudStalls.find(cloudPath) != addressedSyncCloudStalls.end(); } bool AddressedStallFilter::addressedLocalStall(const LocalPath& localPath) { assert (!localPath.empty()); lock_guard<mutex> g(m); return addressedSyncLocalStalls.find(localPath) != addressedSyncLocalStalls.end(); } void AddressedStallFilter::filterStallCloud(const string& cloudPath, int completedPassCount) { lock_guard<mutex> g(m); addressedSyncCloudStalls[cloudPath] = completedPassCount; } void AddressedStallFilter::filterStallLocal(const LocalPath& localPath, int completedPassCount) { lock_guard<mutex> g(m); addressedSyncLocalStalls[localPath] = completedPassCount; } void AddressedStallFilter::filterNameConfict(const string& cloudPath, const LocalPath& localPath, int completedPassCount) { lock_guard<mutex> g(m); if (!cloudPath.empty()) { addressedNameConflictCloudStalls[cloudPath] = completedPassCount; } if (!localPath.empty()) { addressedNameConflictLocalStalls[localPath] = completedPassCount; } } void AddressedStallFilter::removeOldFilters(int completedPassCount) { lock_guard<mutex> g(m); // when a filter was added, the sync code could already have started a new pass, and passed this node. // So, only after we are on a number greater than n+1 of the added n can we remove a filter for (auto i = addressedNameConflictLocalStalls.begin(); i != addressedNameConflictLocalStalls.end(); ) { if (completedPassCount > i->second + 1) { i = addressedNameConflictLocalStalls.erase(i); } else ++i; } for (auto i = addressedNameConflictCloudStalls.begin(); i != addressedNameConflictCloudStalls.end(); ) { if (completedPassCount > i->second + 1) { i = addressedNameConflictCloudStalls.erase(i); } else ++i; } for (auto i = addressedSyncLocalStalls.begin(); i != addressedSyncLocalStalls.end(); ) { if (completedPassCount > i->second + 1) { i = addressedSyncLocalStalls.erase(i); } else ++i; } for (auto i = addressedSyncCloudStalls.begin(); i != addressedSyncCloudStalls.end(); ) { if (completedPassCount > i->second + 1) { i = addressedSyncCloudStalls.erase(i); } else ++i; } } void AddressedStallFilter::clear() { lock_guard<mutex> g(m); addressedSyncCloudStalls.clear(); addressedSyncLocalStalls.clear(); addressedNameConflictCloudStalls.clear(); addressedNameConflictLocalStalls.clear(); } void MegaApiImpl::getMegaSyncStallList(MegaRequestListener* listener) { auto request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SYNC_STALL_LIST, listener); request->performRequest = [this, request]() -> error { return performRequest_getSyncStalls(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMegaSyncStallMap(MegaRequestListener* listener) { auto request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SYNC_STALL_LIST, listener); request->setFlag(true); request->performRequest = [this, request]() -> error { return performRequest_getSyncStalls(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::clearStalledPath(MegaSyncStall* stall) { // do not report these ones anymore in calls to getMegaSyncStallList // until the sync core has made another full pass over the sync tree if (auto ptr = dynamic_cast<MegaSyncStallPrivate*>(stall)) { if (!ptr->info.cloudPath1.cloudPath.empty()) { mAddressedStallFilter.filterStallCloud(ptr->info.cloudPath1.cloudPath, client->syncs.completedPassCount.load()); } if (!ptr->info.localPath1.localPath.empty()) { mAddressedStallFilter.filterStallLocal(ptr->info.localPath1.localPath, client->syncs.completedPassCount.load()); } } else if (auto syncStall = dynamic_cast<MegaSyncNameConflictStallPrivate*>(stall)) { mAddressedStallFilter.filterNameConfict(syncStall->mConflict.cloudPath, syncStall->mConflict.localPath, client->syncs.completedPassCount.load()); } } void MegaApiImpl::moveToDebris(const char* path, MegaHandle syncBackupId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TO_DEBRIS, listener); request->setText(path); request->setNodeHandle(syncBackupId); request->performRequest = [this, request]() { const char* path = request->getText(); handle syncBackupId = request->getNodeHandle(); if (!path || syncBackupId == UNDEF) { return API_EARGS; } client->syncs.moveToSyncDebrisByBackupID(path, syncBackupId, nullptr, [this, request](error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::changeSyncRemoteRoot(const MegaHandle syncBackupId, const MegaHandle newRootNodeHandle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_SYNC_ROOT, listener); request->setNodeHandle(syncBackupId); request->setParentHandle(newRootNodeHandle); request->performRequest = [this, request]() { handle syncBackupId = request->getNodeHandle(); handle newRootNodeHandle = request->getParentHandle(); if (newRootNodeHandle == UNDEF || syncBackupId == UNDEF) { return API_EARGS; } client->changeSyncRoot(syncBackupId, newRootNodeHandle, nullptr, [this, request](error e, SyncError se) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e, se)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::changeSyncLocalRoot(const MegaHandle syncBackupId, const char* newLocalSyncRootPath, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_SYNC_ROOT, listener); request->setNodeHandle(syncBackupId); request->setFile(newLocalSyncRootPath); request->performRequest = [this, request]() { handle syncBackupId = request->getNodeHandle(); const char* newRootPath = request->getFile(); if (syncBackupId == UNDEF || !newRootPath || newRootPath[0] == '\0') { return API_EARGS; } client->changeSyncRoot(syncBackupId, UNDEF, newRootPath, [this, request](error e, SyncError se) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e, se)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setSyncUploadThrottleUpdateRate(const unsigned updateRateInSeconds, MegaRequestListener* const listener) { MegaRequestPrivate* const request = new MegaRequestPrivate(MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES, listener); request->setNumber(updateRateInSeconds); request->performRequest = [this, request]() { const auto updateRateInSeconds = static_cast<unsigned>(request->getNumber()); client->setSyncUploadThrottleUpdateRate( std::chrono::seconds(updateRateInSeconds), [this, request](const error errorSetUpdateRate) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(errorSetUpdateRate)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, MegaRequestListener* const listener) { MegaRequestPrivate* const request = new MegaRequestPrivate(MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES, listener); request->setTotalBytes(maxUploadsBeforeThrottle); request->performRequest = [this, request]() { const auto maxUploadsBeforeThrottle = static_cast<unsigned>(request->getTotalBytes()); client->setSyncMaxUploadsBeforeThrottle( maxUploadsBeforeThrottle, [this, request](const error errorSetMaxUploadsBeforeThrottle) { fireOnRequestFinish( request, std::make_unique<MegaErrorPrivate>(errorSetMaxUploadsBeforeThrottle)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getSyncUploadThrottleValues(MegaRequestListener* const listener) { MegaRequestPrivate* const request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES, listener); request->performRequest = [this, request]() { client->syncUploadThrottleValues( [this, request](const std::chrono::seconds updateRateInSeconds, const unsigned maxUploadsBeforeThrottle) { request->setNumber(updateRateInSeconds.count()); request->setTotalBytes(maxUploadsBeforeThrottle); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getSyncUploadThrottleLimits(const bool upperLimits, MegaRequestListener* const listener) { MegaRequestPrivate* const request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS, listener); request->setFlag(upperLimits); request->performRequest = [this, request]() { client->syncUploadThrottleValuesLimits( [this, request](ThrottleValueLimits&& throttleValueLimits) { const auto getUpperLimits = request->getFlag(); request->setNumber(getUpperLimits ? throttleValueLimits.throttleUpdateRateUpperLimit.count() : throttleValueLimits.throttleUpdateRateLowerLimit.count()); request->setTotalBytes(getUpperLimits ? throttleValueLimits.maxUploadsBeforeThrottleUpperLimit : throttleValueLimits.maxUploadsBeforeThrottleLowerLimit); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::checkSyncUploadsThrottled(MegaRequestListener* const listener) { MegaRequestPrivate* const request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_SYNC_UPLOAD_THROTTLED_ELEMENTS, listener); request->performRequest = [this, request]() { client->checkSyncUploadsThrottled( [this, request](const bool throttledElements) { request->setFlag(throttledElements); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } MegaSyncStallPrivate::MegaSyncStallPrivate(const SyncStallEntry& e) :info(e) {} MegaSyncStallPrivate* MegaSyncStallPrivate::copy() const { return new MegaSyncStallPrivate(*this); } size_t MegaSyncStallPrivate::getHash() const { if (!hashCache.first) hashCache = {true, std::hash<MegaSyncStallPrivate>{}(*this)}; return hashCache.second; } const char* MegaSyncStallPrivate::reasonDebugString(MegaSyncStall::SyncStallReason reason) { static_assert(static_cast<int>(SyncWaitReason::NoReason) == static_cast<int>(MegaSyncStall::SyncStallReason::NoReason), ""); static_assert(static_cast<int>(SyncWaitReason::FileIssue) == static_cast<int>(MegaSyncStall::SyncStallReason::FileIssue), ""); static_assert(static_cast<int>(SyncWaitReason::MoveOrRenameCannotOccur) == static_cast<int>(MegaSyncStall::SyncStallReason::MoveOrRenameCannotOccur), ""); static_assert( static_cast<int>(SyncWaitReason::DeleteOrMoveWaitingOnScanning) == static_cast<int>(MegaSyncStall::SyncStallReason::DeleteOrMoveWaitingOnScanning), ""); static_assert(static_cast<int>(SyncWaitReason::DeleteWaitingOnMoves) == static_cast<int>(MegaSyncStall::SyncStallReason::DeleteWaitingOnMoves), ""); static_assert(static_cast<int>(SyncWaitReason::UploadIssue) == static_cast<int>(MegaSyncStall::SyncStallReason::UploadIssue), ""); static_assert(static_cast<int>(SyncWaitReason::DownloadIssue) == static_cast<int>(MegaSyncStall::SyncStallReason::DownloadIssue), ""); static_assert(static_cast<int>(SyncWaitReason::CannotCreateFolder) == static_cast<int>(MegaSyncStall::SyncStallReason::CannotCreateFolder), ""); static_assert(static_cast<int>(SyncWaitReason::CannotPerformDeletion) == static_cast<int>(MegaSyncStall::SyncStallReason::CannotPerformDeletion), ""); static_assert( static_cast<int>(SyncWaitReason::SyncItemExceedsSupportedTreeDepth) == static_cast<int>(MegaSyncStall::SyncStallReason::SyncItemExceedsSupportedTreeDepth), ""); static_assert(static_cast<int>(SyncWaitReason::FolderMatchedAgainstFile) == static_cast<int>(MegaSyncStall::SyncStallReason::FolderMatchedAgainstFile), ""); static_assert( static_cast<int>( SyncWaitReason::LocalAndRemoteChangedSinceLastSyncedState_userMustChoose) == static_cast<int>(MegaSyncStall::SyncStallReason:: LocalAndRemoteChangedSinceLastSyncedState_userMustChoose), ""); static_assert( static_cast<int>(SyncWaitReason::LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose) == static_cast<int>(MegaSyncStall::SyncStallReason:: LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose), ""); static_assert(static_cast<int>(SyncWaitReason::NamesWouldClashWhenSynced) == static_cast<int>(MegaSyncStall::SyncStallReason::NamesWouldClashWhenSynced), ""); static_assert(static_cast<int>(SyncWaitReason::SyncWaitReason_LastPlusOne) == static_cast<int>(MegaSyncStall::SyncStallReason::SyncStallReason_LastPlusOne), ""); return syncWaitReasonDebugString(SyncWaitReason(reason)); } const char* MegaSyncStallPrivate::pathProblemDebugString(MegaSyncStall::SyncPathProblem reason) { static_assert(static_cast<int>(PathProblem::NoProblem) == static_cast<int>(MegaSyncStall::SyncPathProblem::NoProblem), ""); static_assert(static_cast<int>(PathProblem::FileChangingFrequently) == static_cast<int>(MegaSyncStall::SyncPathProblem::FileChangingFrequently), ""); static_assert(static_cast<int>(PathProblem::IgnoreRulesUnknown) == static_cast<int>(MegaSyncStall::SyncPathProblem::IgnoreRulesUnknown), ""); static_assert(static_cast<int>(PathProblem::DetectedHardLink) == static_cast<int>(MegaSyncStall::SyncPathProblem::DetectedHardLink), ""); static_assert(static_cast<int>(PathProblem::DetectedSymlink) == static_cast<int>(MegaSyncStall::SyncPathProblem::DetectedSymlink), ""); static_assert(static_cast<int>(PathProblem::DetectedSpecialFile) == static_cast<int>(MegaSyncStall::SyncPathProblem::DetectedSpecialFile), ""); static_assert( static_cast<int>(PathProblem::DifferentFileOrFolderIsAlreadyPresent) == static_cast<int>(MegaSyncStall::SyncPathProblem::DifferentFileOrFolderIsAlreadyPresent), ""); static_assert(static_cast<int>(PathProblem::ParentFolderDoesNotExist) == static_cast<int>(MegaSyncStall::SyncPathProblem::ParentFolderDoesNotExist), ""); static_assert( static_cast<int>(PathProblem::FilesystemErrorDuringOperation) == static_cast<int>(MegaSyncStall::SyncPathProblem::FilesystemErrorDuringOperation), ""); static_assert(static_cast<int>(PathProblem::NameTooLongForFilesystem) == static_cast<int>(MegaSyncStall::SyncPathProblem::NameTooLongForFilesystem), ""); static_assert(static_cast<int>(PathProblem::CannotFingerprintFile) == static_cast<int>(MegaSyncStall::SyncPathProblem::CannotFingerprintFile), ""); static_assert( static_cast<int>(PathProblem::DestinationPathInUnresolvedArea) == static_cast<int>(MegaSyncStall::SyncPathProblem::DestinationPathInUnresolvedArea), ""); static_assert(static_cast<int>(PathProblem::MACVerificationFailure) == static_cast<int>(MegaSyncStall::SyncPathProblem::MACVerificationFailure), ""); static_assert(static_cast<int>(PathProblem::UnknownDownloadIssue) == static_cast<int>(MegaSyncStall::SyncPathProblem::UnknownDownloadIssue), ""); static_assert(static_cast<int>(PathProblem::DeletedOrMovedByUser) == static_cast<int>(MegaSyncStall::SyncPathProblem::DeletedOrMovedByUser), ""); static_assert(static_cast<int>(PathProblem::FileFolderDeletedByUser) == static_cast<int>(MegaSyncStall::SyncPathProblem::FileFolderDeletedByUser), ""); static_assert(static_cast<int>(PathProblem::MoveToDebrisFolderFailed) == static_cast<int>(MegaSyncStall::SyncPathProblem::MoveToDebrisFolderFailed), ""); static_assert(static_cast<int>(PathProblem::IgnoreFileMalformed) == static_cast<int>(MegaSyncStall::SyncPathProblem::IgnoreFileMalformed), ""); static_assert( static_cast<int>(PathProblem::FilesystemErrorListingFolder) == static_cast<int>(MegaSyncStall::SyncPathProblem::FilesystemErrorListingFolder), ""); static_assert( static_cast<int>(PathProblem::WaitingForScanningToComplete) == static_cast<int>(MegaSyncStall::SyncPathProblem::WaitingForScanningToComplete), ""); static_assert( static_cast<int>(PathProblem::WaitingForAnotherMoveToComplete) == static_cast<int>(MegaSyncStall::SyncPathProblem::WaitingForAnotherMoveToComplete), ""); static_assert(static_cast<int>(PathProblem::SourceWasMovedElsewhere) == static_cast<int>(MegaSyncStall::SyncPathProblem::SourceWasMovedElsewhere), ""); static_assert( static_cast<int>(PathProblem::FilesystemCannotStoreThisName) == static_cast<int>(MegaSyncStall::SyncPathProblem::FilesystemCannotStoreThisName), ""); static_assert(static_cast<int>(PathProblem::CloudNodeInvalidFingerprint) == static_cast<int>(MegaSyncStall::SyncPathProblem::CloudNodeInvalidFingerprint), ""); static_assert(static_cast<int>(PathProblem::PutnodeDeferredByController) == static_cast<int>(MegaSyncStall::SyncPathProblem::PutnodeDeferredByController), ""); static_assert( static_cast<int>(PathProblem::PutnodeCompletionDeferredByController) == static_cast<int>(MegaSyncStall::SyncPathProblem::PutnodeCompletionDeferredByController), ""); static_assert(static_cast<int>(PathProblem::PutnodeCompletionPending) == static_cast<int>(MegaSyncStall::SyncPathProblem::PutnodeCompletionPending), ""); static_assert(static_cast<int>(PathProblem::UploadDeferredByController) == static_cast<int>(MegaSyncStall::SyncPathProblem::UploadDeferredByController), ""); static_assert(static_cast<int>(PathProblem::DetectedNestedMount) == static_cast<int>(MegaSyncStall::SyncPathProblem::DetectedNestedMount), ""); static_assert(static_cast<int>(PathProblem::CloudNodeIsBlocked) == static_cast<int>(MegaSyncStall::SyncPathProblem::CloudNodeIsBlocked), ""); static_assert(static_cast<int>(PathProblem::PathProblem_LastPlusOne) == static_cast<int>(MegaSyncStall::SyncPathProblem::SyncPathProblem_LastPlusOne), ""); return syncPathProblemDebugString(PathProblem(reason)); } size_t MegaSyncNameConflictStallPrivate::getHash() const { if (!hashCache.first) hashCache = {true, std::hash<MegaSyncNameConflictStallPrivate>{}(*this)}; return hashCache.second; } const char* MegaSyncNameConflictStallPrivate::reasonDebugString(MegaSyncStall::SyncStallReason reason) { return syncWaitReasonDebugString(SyncWaitReason(reason)); } const char* MegaSyncNameConflictStallPrivate::pathProblemDebugString(MegaSyncStall::SyncPathProblem reason) { return syncPathProblemDebugString(PathProblem(reason)); } MegaSyncStallListPrivate* MegaSyncStallListPrivate::copy() const { return new MegaSyncStallListPrivate(*this); } const MegaSyncStall* MegaSyncStallListPrivate::get(size_t i) const { if( i < mStalls.size()) { return mStalls[i].get(); } return nullptr; } MegaSyncStallListPrivate::MegaSyncStallListPrivate(SyncProblems&& sp, AddressedStallFilter& filter) { for (auto& itnc: sp.mConflictsMap) { for (auto& nc: itnc.second) { if (!filter.addressedNameConfict(nc.cloudPath, nc.localPath)) { mStalls.push_back(std::make_shared<MegaSyncNameConflictStallPrivate>(nc)); } } } for (auto& stalledSyncMapPair : sp.mStalls.syncStallInfoMaps) { auto& stalledSyncMap = stalledSyncMapPair.second; for(auto& stall : stalledSyncMap.cloud) { if (!filter.addressedCloudStall(stall.first)) { mStalls.push_back(std::make_shared<MegaSyncStallPrivate>(stall.second)); } } for(auto& stall : stalledSyncMap.local) { if (!filter.addressedLocalStall(stall.first)) { mStalls.push_back(std::make_shared<MegaSyncStallPrivate>(stall.second)); } } } } MegaSyncStallMapPrivate::MegaSyncStallMapPrivate(SyncProblems&& sp, AddressedStallFilter& filter) { for (const auto& [syncId, nameConflictList]: sp.mConflictsMap) { auto& stallList = mStallsMap[syncId]; for (const auto& nc: nameConflictList) { if (!filter.addressedNameConfict(nc.cloudPath, nc.localPath)) { stallList.addStall(std::make_shared<MegaSyncNameConflictStallPrivate>(nc)); } } } for (const auto& [syncId, stalledSyncMap]: sp.mStalls.syncStallInfoMaps) { auto addStalls = [&stallList = mStallsMap[syncId], &filter](const auto& stallMap, auto filterFunc) { for (const auto& stall: stallMap) { if (!std::invoke(filterFunc, filter, stall.first)) { stallList.addStall(std::make_shared<MegaSyncStallPrivate>(stall.second)); } } }; addStalls(stalledSyncMap.cloud, &AddressedStallFilter::addressedCloudStall); addStalls(stalledSyncMap.local, &AddressedStallFilter::addressedLocalStall); } } MegaHandleList* MegaSyncStallMapPrivate::getKeys() const { MegaHandleList* list = MegaHandleList::createInstance(); for (const auto& stall: mStallsMap) { list->addMegaHandle(stall.first); } return list; } const std::map<MegaHandle, MegaSyncStallListPrivate>& MegaSyncStallMapPrivate::getMap() const { return mStallsMap; } #endif // ENABLE_SYNC MegaScheduledCopy *MegaApiImpl::getScheduledCopyByTag(int tag) { SdkMutexGuard g(sdkMutex); if (backupsMap.find(tag) == backupsMap.end()) { return NULL; } return backupsMap.at(tag)->copy(); } MegaScheduledCopy *MegaApiImpl::getScheduledCopyByNode(MegaNode *node) { if (!node) { return NULL; } MegaScheduledCopy *result = NULL; MegaHandle nodeHandle = node->getHandle(); SdkMutexGuard g(sdkMutex); std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin(); while(it != backupsMap.end()) { MegaScheduledCopyController* backup = it->second; if (backup->getMegaHandle() == nodeHandle) { result = backup->copy(); break; } it++; } return result; } MegaScheduledCopy *MegaApiImpl::getScheduledCopyByPath(const char *localPath) { if (!localPath) { return NULL; } MegaScheduledCopy *result = NULL; SdkMutexGuard g(sdkMutex); std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin(); while(it != backupsMap.end()) { MegaScheduledCopyController* backup = it->second; if (!strcmp(localPath, backup->getLocalFolder())) { result = backup->copy(); break; } it++; } return result; } bool MegaNodePrivate::hasThumbnail() { return thumbnailAvailable; } bool MegaNodePrivate::hasPreview() { return previewAvailable; } bool MegaNodePrivate::isPublic() { return isPublicNode; } bool MegaNodePrivate::isShared() { return outShares || inShare; } bool MegaNodePrivate::isOutShare() { return outShares; } bool MegaNodePrivate::isInShare() { return inShare; } bool MegaNodePrivate::isExported() { return plink != nullptr; } bool MegaNodePrivate::isExpired() { return plink ? (plink->isExpired()) : false; } bool MegaNodePrivate::isTakenDown() { return plink ? plink->takendown : false; } bool MegaNodePrivate::isForeign() { return foreign; } bool MegaNodePrivate::isCreditCardNode() const { if (!isPasswordManagerNode()) return false; const auto passNodeAttr = getOfficialAttr(MegaClient::NODE_ATTR_PASSWORD_MANAGER); assert(passNodeAttr); AttrMap attrMap; attrMap.fromjson(passNodeAttr); return MegaClient::isPwmDataOfType(attrMap, MegaClient::PwmEntryType::CREDIT_CARD); } bool MegaNodePrivate::isPasswordNode() const { if (!isPasswordManagerNode()) return false; const auto passNodeAttr = getOfficialAttr(MegaClient::NODE_ATTR_PASSWORD_MANAGER); assert(passNodeAttr); AttrMap attrMap; attrMap.fromjson(passNodeAttr); return MegaClient::isPwmDataOfType(attrMap, MegaClient::PwmEntryType::PASSWORD); } bool MegaNodePrivate::isPasswordManagerNode() const { return (type == FOLDERNODE && getOfficialAttr(MegaClient::NODE_ATTR_PASSWORD_MANAGER) != nullptr); } MegaNode::CreditCardNodeData* MegaNodePrivate::getCreditCardData() const { if (!isCreditCardNode()) return nullptr; AttrMap aux; aux.fromjson(getOfficialAttr(MegaClient::NODE_ATTR_PASSWORD_MANAGER)); return new CCNDataPrivate{ getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_CREDIT_CARD_NUMBER)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_CREDIT_NOTES)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_CREDIT_CARD_HOLDER)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_CREDIT_CVV)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_CREDIT_EXP_DATE))}; } MegaNode::PasswordNodeData* MegaNodePrivate::getPasswordData() const { if (isPasswordNode()) { AttrMap aux; aux.fromjson(getOfficialAttr(MegaClient::NODE_ATTR_PASSWORD_MANAGER)); constexpr auto totpNameid = AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP); std::optional<PNDataPrivate::TotpDataPrivate> totp{}; if (aux.map.contains(totpNameid)) { AttrMap auxTotp; auxTotp.fromjsonObject( aux.map.at(AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP))); totp = PNDataPrivate::TotpDataPrivate::fromMap(auxTotp); } return new PNDataPrivate{ getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_PASSWORD_PWD)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_PASSWORD_NOTES)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_PASSWORD_URL)), getConstCharPtr(aux.getString(MegaClient::PWM_ATTR_PASSWORD_USERNAME)), getPtr(totp)}; } return nullptr; } string *MegaNodePrivate::getPrivateAuth() { return &privateAuth; } MegaNodeList *MegaNodePrivate::getChildren() { return children; } void MegaNodePrivate::setPrivateAuth(const char* newPrivateAuth) { if (!newPrivateAuth || !newPrivateAuth[0]) { privateAuth.clear(); } else { privateAuth = newPrivateAuth; } } void MegaNodePrivate::setPublicAuth(const char* newPublicAuth) { if (!newPublicAuth || !newPublicAuth[0]) { publicAuth.clear(); } else { publicAuth = newPublicAuth; } } void MegaNodePrivate::setChatAuth(const char* newChatAuth) { delete[] chatAuth; if (!newChatAuth || !newChatAuth[0]) { chatAuth = NULL; foreign = false; } else { chatAuth = MegaApi::strdup(newChatAuth); foreign = true; } } void MegaNodePrivate::setForeign(bool isForeign) { foreign = isForeign; } void MegaNodePrivate::setChildren(MegaNodeList* newChildren) { children = newChildren; } void MegaNodePrivate::setName(const char *newName) { if (name) delete [] name; name = MegaApi::strdup(newName); } string *MegaNodePrivate::getPublicAuth() { return &publicAuth; } const char *MegaNodePrivate::getChatAuth() { return chatAuth; } MegaNodePrivate::~MegaNodePrivate() { delete[] name; delete[] fingerprint; delete[] originalfingerprint; delete [] chatAuth; delete customAttrs; delete plink; delete sharekey; delete children; } MegaUserPrivate::MegaUserPrivate(User *user) : MegaUser() { email = MegaApi::strdup(user->email.c_str()); handle = user->userhandle; visibility = user->show; ctime = user->ctime; tag = user->getTag(); changed = 0; if (user->changed.authring) { changed |= MegaUser::CHANGE_TYPE_AUTHRING; } if(user->changed.avatar) { changed |= MegaUser::CHANGE_TYPE_AVATAR; } if(user->changed.lstint) { changed |= MegaUser::CHANGE_TYPE_LSTINT; } if(user->changed.firstname) { changed |= MegaUser::CHANGE_TYPE_FIRSTNAME; } if(user->changed.lastname) { changed |= MegaUser::CHANGE_TYPE_LASTNAME; } if(user->changed.email) { changed |= MegaUser::CHANGE_TYPE_EMAIL; } if(user->changed.keyring) { changed |= MegaUser::CHANGE_TYPE_KEYRING; } if(user->changed.country) { changed |= MegaUser::CHANGE_TYPE_COUNTRY; } if(user->changed.birthday) { changed |= MegaUser::CHANGE_TYPE_BIRTHDAY; } if(user->changed.puCu255) { changed |= MegaUser::CHANGE_TYPE_PUBKEY_CU255; } if(user->changed.puEd255) { changed |= MegaUser::CHANGE_TYPE_PUBKEY_ED255; } if(user->changed.sigPubk) { changed |= MegaUser::CHANGE_TYPE_SIG_PUBKEY_RSA; } if(user->changed.sigCu255) { changed |= MegaUser::CHANGE_TYPE_SIG_PUBKEY_CU255; } if(user->changed.language) { changed |= MegaUser::CHANGE_TYPE_LANGUAGE; } if(user->changed.pwdReminder) { changed |= MegaUser::CHANGE_TYPE_PWD_REMINDER; } if(user->changed.disableVersions) { changed |= MegaUser::CHANGE_TYPE_DISABLE_VERSIONS; } if(user->changed.noCallKit) { changed |= MegaUser::CHANGE_TYPE_NO_CALLKIT; } if(user->changed.contactLinkVerification) { changed |= MegaUser::CHANGE_TYPE_CONTACT_LINK_VERIFICATION; } if(user->changed.richPreviews) { changed |= MegaUser::CHANGE_TYPE_RICH_PREVIEWS; } if(user->changed.rubbishTime) { changed |= MegaUser::CHANGE_TYPE_RUBBISH_TIME; } if(user->changed.storageState) { changed |= MegaUser::CHANGE_TYPE_STORAGE_STATE; } if(user->changed.geolocation) { changed |= MegaUser::CHANGE_TYPE_GEOLOCATION; } if(user->changed.cameraUploadsFolder) { changed |= MegaUser::CHANGE_TYPE_CAMERA_UPLOADS_FOLDER; } if(user->changed.myChatFilesFolder) { changed |= MegaUser::CHANGE_TYPE_MY_CHAT_FILES_FOLDER; } if (user->changed.pushSettings) { changed |= MegaUser::CHANGE_TYPE_PUSH_SETTINGS; } if (user->changed.alias) { changed |= MegaUser::CHANGE_TYPE_ALIAS; } if (user->changed.unshareablekey) { changed |= MegaUser::CHANGE_TYPE_UNSHAREABLE_KEY; } if (user->changed.devicenames) { changed |= MegaUser::CHANGE_TYPE_DEVICE_NAMES; } if (user->changed.myBackupsFolder) { changed |= MegaUser::CHANGE_TYPE_MY_BACKUPS_FOLDER; } if (user->changed.cookieSettings) { changed |= MegaUser::CHANGE_TYPE_COOKIE_SETTINGS; } if (user->changed.aPrefs) { changed |= MegaUser::CHANGE_APPS_PREFS; } if (user->changed.ccPrefs) { changed |= MegaUser::CHANGE_CC_PREFS; } if (user->changed.recentClearTimestamp) { changed |= MegaUser::CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP; } // Don't need to notify about user->changed.enableTestSurveys } MegaUserPrivate::MegaUserPrivate(MegaUser *user) : MegaUser() { email = MegaApi::strdup(user->getEmail()); handle = user->getHandle(); visibility = user->getVisibility(); ctime = user->getTimestamp(); changed = user->getChanges(); tag = user->isOwnChange(); } MegaUser *MegaUserPrivate::fromUser(User *user) { if(!user) { return NULL; } return new MegaUserPrivate(user); } MegaUser *MegaUserPrivate::copy() { return new MegaUserPrivate(this); } MegaUserPrivate::~MegaUserPrivate() { delete[] email; } const char* MegaUserPrivate::getEmail() { return email; } MegaHandle MegaUserPrivate::getHandle() { return handle; } int MegaUserPrivate::getVisibility() { return visibility; } int64_t MegaUserPrivate::getTimestamp() { return ctime; } bool MegaUserPrivate::hasChanged(uint64_t changeType) { return (changed & changeType); } uint64_t MegaUserPrivate::getChanges() { return changed; } int MegaUserPrivate::isOwnChange() { return tag; } bool MegaSetPrivate::hasChanged(uint64_t changeType) const { return getChanges() & changeType; } bool MegaSetElementPrivate::hasChanged(uint64_t changeType) const { return getChanges() & changeType; } MegaUserAlertPrivate::MegaUserAlertPrivate(UserAlert::Base *b, MegaClient* mc) : id(b->id) , seen(b->seen()) , relevant(b->relevant()) , type(-1) , tag(b->tag) , userHandle(UNDEF) , nodeHandle(UNDEF) , removed(b->removed()) { b->text(heading, title, mc); timestamps.push_back(b->ts()); switch (b->type) { case name_id::ipc: { UserAlert::IncomingPendingContact* p = static_cast<UserAlert::IncomingPendingContact*>(b); if (p->requestWasDeleted) { type = TYPE_INCOMINGPENDINGCONTACT_CANCELLED; } else if (p->requestWasReminded) { type = TYPE_INCOMINGPENDINGCONTACT_REMINDER; } else { type = TYPE_INCOMINGPENDINGCONTACT_REQUEST; } userHandle = p->user(); mPcrHandle = p->mPcrHandle; email = p->email(); } break; case name_id::c: { UserAlert::ContactChange* p = static_cast<UserAlert::ContactChange*>(b); switch (p->action) { case 0: type = TYPE_CONTACTCHANGE_DELETEDYOU; break; case 1: type = TYPE_CONTACTCHANGE_CONTACTESTABLISHED; break; case 2: type = TYPE_CONTACTCHANGE_ACCOUNTDELETED; break; case 3: type = TYPE_CONTACTCHANGE_BLOCKEDYOU; break; } userHandle = p->user(); email = p->email(); } break; case name_id::upci: { UserAlert::UpdatedPendingContactIncoming* p = static_cast<UserAlert::UpdatedPendingContactIncoming*>(b); switch (p->action) { case 1: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED; break; case 2: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_ACCEPTED; break; case 3: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_DENIED; break; } userHandle = p->user(); email = p->email(); } break; case name_id::upco: { UserAlert::UpdatedPendingContactOutgoing* p = static_cast<UserAlert::UpdatedPendingContactOutgoing*>(b); switch (p->action) { case 1: type = TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED; break; case 2: type = TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED; break; case 3: type = TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED; break; } userHandle = p->user(); email = p->email(); } break; case name_id::share: { UserAlert::NewShare* p = static_cast<UserAlert::NewShare*>(b); type = TYPE_NEWSHARE; userHandle = p->user(); email = p->email(); nodeHandle = p->folderhandle; if (shared_ptr<Node> node = mc->nodebyhandle(p->folderhandle)) { nodePath = node->displaypath(); nodeName = node->displayname(); } } break; case name_id::dshare: { UserAlert::DeletedShare* p = static_cast<UserAlert::DeletedShare*>(b); type = TYPE_DELETEDSHARE; userHandle = p->user(); email = p->email(); nodePath = p->folderPath; nodeName = p->folderName; nodeHandle = p->folderHandle; bool accessRevoked = p->user() == p->ownerHandle; numbers.push_back(accessRevoked ? 1 : 0); } break; case name_id::put: { UserAlert::NewSharedNodes* p = static_cast<UserAlert::NewSharedNodes*>(b); type = TYPE_NEWSHAREDNODES; userHandle = p->user(); email = p->email(); nodeHandle = p->parentHandle; numbers.push_back(static_cast<int64_t>(p->folderNodeHandles.size())); numbers.push_back(static_cast<int64_t>(p->fileNodeHandles.size())); handles.assign(begin(p->folderNodeHandles), end(p->folderNodeHandles)); handles.insert(end(handles), begin(p->fileNodeHandles), end(p->fileNodeHandles)); } break; case name_id::d: { UserAlert::RemovedSharedNode* p = static_cast<UserAlert::RemovedSharedNode*>(b); type = TYPE_REMOVEDSHAREDNODES; userHandle = p->user(); email = p->email(); numbers.push_back(static_cast<int64_t>(p->nodeHandles.size())); } break; case name_id::u: { UserAlert::UpdatedSharedNode* p = static_cast<UserAlert::UpdatedSharedNode*>(b); type = TYPE_UPDATEDSHAREDNODES; userHandle = p->user(); email = p->email(); numbers.push_back(static_cast<int64_t>(p->nodeHandles.size())); } break; case name_id::psts: case name_id::psts_v2: { UserAlert::Payment* p = static_cast<UserAlert::Payment*>(b); type = p->success ? TYPE_PAYMENT_SUCCEEDED : TYPE_PAYMENT_FAILED; extraStrings.push_back(p->getProPlanName()); } break; case name_id::pses: { UserAlert::PaymentReminder* p = static_cast<UserAlert::PaymentReminder*>(b); type = TYPE_PAYMENTREMINDER; timestamps.push_back(p->expiryTime); } break; case name_id::ph: { UserAlert::Takedown* p = static_cast<UserAlert::Takedown*>(b); if (p->isTakedown) { type = TYPE_TAKEDOWN; } else if (p->isReinstate) { type = TYPE_TAKEDOWN_REINSTATED; } nodeHandle = p->nodeHandle; shared_ptr<Node> node = mc->nodebyhandle(nodeHandle); if (node) { nodePath = node->displaypath(); nodeName = node->displayname(); } } break; case name_id::ass: { UserAlert::SetTakedown* p = static_cast<UserAlert::SetTakedown*>(b); if (p->isTakedown) { type = TYPE_SET_TAKEDOWN; } else if (p->isReinstate) { type = TYPE_SET_TAKEDOWN_REINSTATED; } nodeHandle = p->setId; if (const Set* set = mc->getSet(nodeHandle); set) { nodeName = set->name(); } } break; #ifdef ENABLE_CHAT case name_id::mcsmp: { if (auto* p = dynamic_cast<UserAlert::NewScheduledMeeting*>(b)) { type = TYPE_SCHEDULEDMEETING_NEW; userHandle = p->user(); email = p->email(); nodeHandle = p->mChatid; schedMeetingId = p->mSchedMeetingHandle; mPcrHandle = p->mParentSchedId; numbers.push_back(p->mStartDateTime); } else { if (auto* updateAlert = dynamic_cast<UserAlert::UpdatedScheduledMeeting*>(b)) { type = TYPE_SCHEDULEDMEETING_UPDATED; userHandle = updateAlert->user(); email = updateAlert->email(); nodeHandle = updateAlert->mChatid; schedMeetingId = updateAlert->mSchedMeetingHandle; mPcrHandle = updateAlert->mParentSchedId; numbers.push_back(updateAlert->mStartDateTime); schedMeetingChangeset = updateAlert->mUpdatedChangeset; } else { assert(false); LOG_err << "Scheduled meeting user alert invalid sub-type (mangled): " << typeid(*b).name() << ", expected: NewSchedulingMeeting or UpdatedSchedulingMeeting"; } } } break; case name_id::mcsmr: { if (auto* p = dynamic_cast<UserAlert::DeletedScheduledMeeting*>(b)) { type = TYPE_SCHEDULEDMEETING_DELETED; userHandle = p->user(); email = p->email(); nodeHandle = p->mChatid; schedMeetingId = p->mSchedMeetingHandle; } else { LOG_err << "Scheduled meeting user alert invalid sub-type (mangled): " << typeid(*b).name() << ", expected: DeletedScheduledMeeting"; } } break; #endif } // end switch } MegaUserAlert *MegaUserAlertPrivate::copy() const { return new MegaUserAlertPrivate(*this); } unsigned MegaUserAlertPrivate::getId() const { return id; } bool MegaUserAlertPrivate::getSeen() const { return seen; } bool MegaUserAlertPrivate::getRelevant() const { return relevant; } int MegaUserAlertPrivate::getType() const { return type; } const char *MegaUserAlertPrivate::getTypeString() const { switch (type) { case TYPE_INCOMINGPENDINGCONTACT_REQUEST: return "NEW_CONTACT_REQUEST"; case TYPE_INCOMINGPENDINGCONTACT_CANCELLED: return "CONTACT_REQUEST_CANCELLED"; case TYPE_INCOMINGPENDINGCONTACT_REMINDER: return "CONTACT_REQUEST_REMINDED"; case TYPE_CONTACTCHANGE_DELETEDYOU: return "CONTACT_DISCONNECTED"; case TYPE_CONTACTCHANGE_CONTACTESTABLISHED: return "CONTACT_ESTABLISHED"; case TYPE_CONTACTCHANGE_ACCOUNTDELETED: return "CONTACT_ACCOUNTDELETED"; case TYPE_CONTACTCHANGE_BLOCKEDYOU: return "CONTACT_BLOCKED"; case TYPE_UPDATEDPENDINGCONTACTINCOMING_IGNORED: return "YOU_IGNORED_CONTACT"; case TYPE_UPDATEDPENDINGCONTACTINCOMING_ACCEPTED: return "YOU_ACCEPTED_CONTACT"; case TYPE_UPDATEDPENDINGCONTACTINCOMING_DENIED: return "YOU_DENIED_CONTACT"; case TYPE_UPDATEDPENDINGCONTACTOUTGOING_ACCEPTED: return "CONTACT_ACCEPTED_YOU"; case TYPE_UPDATEDPENDINGCONTACTOUTGOING_DENIED: return "CONTACT_DENIED_YOU"; case TYPE_NEWSHARE: return "NEW_SHARE"; case TYPE_DELETEDSHARE: return "SHARE_UNSHARED"; case TYPE_NEWSHAREDNODES: return "NEW_NODES_IN_SHARE"; case TYPE_REMOVEDSHAREDNODES: return "NODES_IN_SHARE_REMOVED"; case TYPE_UPDATEDSHAREDNODES: return "NODES_IN_SHARE_UPDATED"; case TYPE_PAYMENT_SUCCEEDED: return "PAYMENT_SUCCEEDED"; case TYPE_PAYMENT_FAILED: return "PAYMENT_FAILED"; case TYPE_PAYMENTREMINDER: return "PAYMENT_REMINDER"; case TYPE_TAKEDOWN: return "TAKEDOWN"; case TYPE_TAKEDOWN_REINSTATED: return "TAKEDOWN_REINSTATED"; case TYPE_SCHEDULEDMEETING_NEW: return "SCHEDULEDMEETING_NEW"; case TYPE_SCHEDULEDMEETING_UPDATED: return "SCHEDULEDMEETING_UPDATED"; case TYPE_SCHEDULEDMEETING_DELETED: return "SCHEDULEDMEETING_DELETED"; case TYPE_SET_TAKEDOWN: return "SET_TAKEDOWN"; case TYPE_SET_TAKEDOWN_REINSTATED: return "SET_TAKEDOWN_REINSTATED"; } return "<new type>"; } MegaHandle MegaUserAlertPrivate::getUserHandle() const { return userHandle; } MegaHandle MegaUserAlertPrivate::getNodeHandle() const { return nodeHandle; } MegaHandle mega::MegaUserAlertPrivate::getPcrHandle() const { return mPcrHandle; } const char* MegaUserAlertPrivate::getEmail() const { return email.empty() ? NULL : email.c_str(); } const char*MegaUserAlertPrivate::getPath() const { return nodePath.empty() ? NULL : nodePath.c_str(); } const char *MegaUserAlertPrivate::getName() const { return nodeName.empty() ? NULL : nodeName.c_str(); } const char *MegaUserAlertPrivate::getHeading() const { return heading.c_str(); } const char *MegaUserAlertPrivate::getTitle() const { return title.c_str(); } int64_t MegaUserAlertPrivate::getNumber(unsigned index) const { return index < numbers.size() ? numbers[index] : -1; } int64_t MegaUserAlertPrivate::getTimestamp(unsigned index) const { return index < timestamps.size() ? timestamps[index] : -1; } const char* MegaUserAlertPrivate::getString(unsigned index) const { return index < extraStrings.size() ? extraStrings[index].c_str() : NULL; } MegaHandle MegaUserAlertPrivate::getHandle(unsigned index) const { return index < handles.size() ? handles[index] : INVALID_HANDLE; } #ifdef ENABLE_CHAT MegaHandle MegaUserAlertPrivate::getSchedId() const { return schedMeetingId; } bool MegaUserAlertPrivate::hasSchedMeetingChanged(uint64_t changeType) const { return schedMeetingChangeset.hasChanged(changeType); } MegaStringList* MegaUserAlertPrivate::getUpdatedTitle() const { if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_TITLE) || !schedMeetingChangeset.getUpdatedTitle()) { return nullptr; } MegaStringList* updatedTitle = MegaStringList::createInstance(); updatedTitle->add(Base64::atob(schedMeetingChangeset.getUpdatedTitle()->oldValue).c_str()); updatedTitle->add(Base64::atob(schedMeetingChangeset.getUpdatedTitle()->newValue).c_str()); return updatedTitle; } MegaStringList* MegaUserAlertPrivate::getUpdatedTimeZone() const { if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_TIMEZONE) || !schedMeetingChangeset.getUpdatedTimeZone()) { return nullptr; } MegaStringList* updatedTimezone = MegaStringList::createInstance(); updatedTimezone->add(Base64::atob(schedMeetingChangeset.getUpdatedTimeZone()->oldValue).c_str()); updatedTimezone->add(Base64::atob(schedMeetingChangeset.getUpdatedTimeZone()->newValue).c_str()); return updatedTimezone; } MegaIntegerList* MegaUserAlertPrivate::getUpdatedStartDate() const { if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_STARTDATE) || !schedMeetingChangeset.getUpdatedStartDateTime()) { return nullptr; } MegaIntegerList* updatedStartDateTime = MegaIntegerList::createInstance(); updatedStartDateTime->add(schedMeetingChangeset.getUpdatedStartDateTime()->oldValue); updatedStartDateTime->add(schedMeetingChangeset.getUpdatedStartDateTime()->newValue); return updatedStartDateTime; } MegaIntegerList* MegaUserAlertPrivate::getUpdatedEndDate() const { if (!hasSchedMeetingChanged(SM_CHANGE_TYPE_ENDDATE) || !schedMeetingChangeset.getUpdatedEndDateTime()) { return nullptr; } MegaIntegerList* updatedEndDateTime = MegaIntegerList::createInstance(); updatedEndDateTime->add(schedMeetingChangeset.getUpdatedEndDateTime()->oldValue); updatedEndDateTime->add(schedMeetingChangeset.getUpdatedEndDateTime()->newValue); return updatedEndDateTime; } #endif bool MegaUserAlertPrivate::isOwnChange() const { return tag != 0; } bool mega::MegaUserAlertPrivate::isRemoved() const { return removed; } MegaNode *MegaNodePrivate::fromNode(Node *node) { if(!node) return NULL; return new MegaNodePrivate(node); } MegaSharePrivate::MegaSharePrivate(MegaShare *share) : MegaShare() { this->nodehandle = share->getNodeHandle(); this->user = MegaApi::strdup(share->getUser()); this->access = share->getAccess(); this->ts = share->getTimestamp(); this->pending = share->isPending(); this->mVerified = share->isVerified(); } MegaShare *MegaSharePrivate::copy() { return new MegaSharePrivate(this); } MegaSharePrivate::MegaSharePrivate(const impl::ShareData& data) { // Convenience const Share* share = data.getShare(); this->nodehandle = data.getNodeHandle(); this->user = share->user ? MegaApi::strdup(share->user->email.c_str()) : NULL; if ((!user || !*user) && share->pcr) { delete [] user; user = MegaApi::strdup(share->pcr->isoutgoing ? share->pcr->targetemail.c_str() : share->pcr->originatoremail.c_str()); } this->access = share->access; this->ts = share->ts; this->pending = share->pcr != nullptr; this->mVerified = data.isVerified(); } MegaShare* MegaSharePrivate::fromShare(const impl::ShareData& data) { return new MegaSharePrivate(data); } MegaSharePrivate::~MegaSharePrivate() { delete[] user; } const char *MegaSharePrivate::getUser() { return user; } uint64_t MegaSharePrivate::getNodeHandle() { return nodehandle; } int MegaSharePrivate::getAccess() { return access; } int64_t MegaSharePrivate::getTimestamp() { return ts; } bool MegaSharePrivate::isPending() { return pending; } bool MegaSharePrivate::isVerified() { return mVerified; } MegaTransferPrivate::MegaTransferPrivate(int type, MegaTransferListener *listener) : mCollisionCheck(CollisionChecker::Option::Fingerprint) , mCollisionResolution(CollisionResolution::RenameNewWithN) , mCollisionCheckResult(CollisionChecker::Result::NotYet) , mFsType(FileSystemType::FS_UNKNOWN) { this->type = type; this->tag = -1; this->path = NULL; this->nodeHandle = UNDEF; this->parentHandle = UNDEF; this->startPos = -1; this->endPos = -1; this->parentPath = NULL; this->listener = listener; this->retry = 0; this->maxRetries = 7; this->time = -1; this->startTime = 0; this->transferredBytes = 0; this->totalBytes = 0; this->fileName = NULL; this->transfer = NULL; this->speed = 0; this->deltaSize = 0; this->updateTime = 0; this->publicNode = NULL; this->lastBytes = NULL; this->syncTransfer = false; this->streamingTransfer = false; this->temporarySourceFile = false; this->startFirst = false; this->backupTransfer = false; this->foreignOverquota = false; this->folderTransferTag = 0; this->appData = NULL; this->state = STATE_NONE; this->priority = 0; this->meanSpeed = 0; this->notificationNumber = 0; this->mStage = MegaTransfer::STAGE_NONE; } MegaTransferPrivate::MegaTransferPrivate(const MegaTransferPrivate *transfer) { path = NULL; parentPath = NULL; fileName = NULL; publicNode = NULL; lastBytes = NULL; appData = NULL; this->listener = transfer->getListener(); this->transfer = transfer->getTransfer(); this->type = transfer->getType(); this->dbid = transfer->getUniqueId(); this->setState(transfer->getState()); this->setPriority(transfer->getPriority()); this->setTag(transfer->getTag()); this->updateLocalPathInternal(transfer->getLocalPath()); this->setNodeHandle(transfer->getNodeHandle()); this->setParentHandle(transfer->getParentHandle()); this->setStartPos(transfer->getStartPos()); this->setEndPos(transfer->getEndPos()); this->setParentPath(transfer->getParentPath()); this->setNumRetry(transfer->getNumRetry()); this->setMaxRetries(transfer->getMaxRetries()); this->setTime(transfer->getTime()); this->startTime = transfer->getStartTime(); this->setTransferredBytes(transfer->getTransferredBytes()); this->setTotalBytes(transfer->getTotalBytes()); this->setFileName(transfer->getFileName()); this->setSpeed(transfer->getSpeed()); this->setMeanSpeed(transfer->getMeanSpeed()); this->setDeltaSize(transfer->getDeltaSize()); this->setUpdateTime(transfer->getUpdateTime()); this->setPublicNode(transfer->getPublicNode()); this->setTransfer(transfer->getTransfer()); this->setSyncTransfer(transfer->isSyncTransfer()); this->setStreamingTransfer(transfer->isStreamingTransfer()); this->setSourceFileTemporary(transfer->isSourceFileTemporary()); this->setStartFirst(transfer->shouldStartFirst()); this->setBackupTransfer(transfer->isBackupTransfer()); this->setForeignOverquota(transfer->isForeignOverquota()); this->setForceNewUpload(transfer->isForceNewUpload()); this->setLastError(transfer->lastError.get()); this->setFolderTransferTag(transfer->getFolderTransferTag()); this->setAppData(transfer->getAppData()); this->setNotificationNumber(transfer->getNotificationNumber()); mCancelToken = transfer->mCancelToken; this->setStage(transfer->getStage()); this->setCollisionCheck(transfer->getCollisionCheck()); this->setCollisionResolution(transfer->getCollisionResolution()); this->setCollisionCheckResult(transfer->getCollisionCheckResult()); this->setFileSystemType(transfer->getFileSystemType()); } MegaTransfer* MegaTransferPrivate::copy() { return new MegaTransferPrivate(this); } void MegaTransferPrivate::setTransfer(Transfer* newTransfer) { transfer = newTransfer; } Transfer* MegaTransferPrivate::getTransfer() const { return transfer; } uint32_t MegaTransferPrivate::getUniqueId() const { return dbid; } int MegaTransferPrivate::getTag() const { return tag; } long long MegaTransferPrivate::getSpeed() const { return speed; } long long MegaTransferPrivate::getMeanSpeed() const { return meanSpeed; } long long MegaTransferPrivate::getDeltaSize() const { return deltaSize; } int64_t MegaTransferPrivate::getUpdateTime() const { return updateTime; } MegaNode *MegaTransferPrivate::getPublicNode() const { return publicNode; } MegaNode *MegaTransferPrivate::getPublicMegaNode() const { if(publicNode) { return publicNode->copy(); } return NULL; } bool MegaTransferPrivate::isSyncTransfer() const { return syncTransfer; } bool MegaTransferPrivate::isStreamingTransfer() const { return streamingTransfer; } bool MegaTransferPrivate::isFinished() const { return state == STATE_COMPLETED || state == STATE_CANCELLED || state == STATE_FAILED; } bool MegaTransferPrivate::isBackupTransfer() const { return backupTransfer; } bool MegaTransferPrivate::isForeignOverquota() const { return foreignOverquota; } bool MegaTransferPrivate::isForceNewUpload() const { return forceNewUpload; } bool MegaTransferPrivate::isSourceFileTemporary() const { return temporarySourceFile; } bool MegaTransferPrivate::shouldStartFirst() const { return startFirst; } int MegaTransferPrivate::getType() const { return type; } int64_t MegaTransferPrivate::getStartTime() const { return startTime; } long long MegaTransferPrivate::getTransferredBytes() const { return transferredBytes; } long long MegaTransferPrivate::getTotalBytes() const { return totalBytes; } const char* MegaTransferPrivate::getPath() const { return path; } const char* MegaTransferPrivate::getParentPath() const { return parentPath; } uint64_t MegaTransferPrivate::getNodeHandle() const { return nodeHandle; } uint64_t MegaTransferPrivate::getParentHandle() const { return parentHandle; } long long MegaTransferPrivate::getStartPos() const { return startPos; } long long MegaTransferPrivate::getEndPos() const { return endPos; } int MegaTransferPrivate::getNumRetry() const { return retry; } unsigned MegaTransferPrivate::getStage() const { return mStage; } int MegaTransferPrivate::getMaxRetries() const { return maxRetries; } int64_t MegaTransferPrivate::getTime() const { return time; } const char* MegaTransferPrivate::getFileName() const { return fileName; } char * MegaTransferPrivate::getLastBytes() const { return lastBytes; } const MegaError *MegaTransferPrivate::getLastErrorExtended() const { return lastError.get(); } bool MegaTransferPrivate::isFolderTransfer() const { return folderTransferTag < 0; } int MegaTransferPrivate::getFolderTransferTag() const { return this->folderTransferTag; } void MegaTransferPrivate::setAppData(const char *data) { if (this->appData) { delete [] this->appData; } this->appData = MegaApi::strdup(data); } const char *MegaTransferPrivate::getAppData() const { return this->appData; } void MegaTransferPrivate::setState(int newState) { state = newState; } int MegaTransferPrivate::getState() const { return state; } void MegaTransferPrivate::setPriority(unsigned long long p) { this->priority = p; } unsigned long long MegaTransferPrivate::getPriority() const { return priority; } long long MegaTransferPrivate::getNotificationNumber() const { return notificationNumber; } bool MegaTransferPrivate::getTargetOverride() const { return mTargetOverride; } CollisionChecker::Option MegaTransferPrivate::getCollisionCheck() const { return mCollisionCheck; } CollisionChecker::Result MegaTransferPrivate::getCollisionCheckResult() const { return mCollisionCheckResult; } CollisionResolution MegaTransferPrivate::getCollisionResolution() const { return mCollisionResolution; } MegaNode* MegaTransferPrivate::getNodeToUndelete() const { return nodeToUndelete.get(); } LocalPath MegaTransferPrivate::getLocalPath() const { return mLocalPath; } std::optional<string> MegaTransferPrivate::getInboxTarget() { constexpr size_t stringLength = 11; constexpr char charSeparator = '@'; bool uploadToInbox = ISUNDEF(getParentHandle()) && getParentPath() && (strchr(getParentPath(), charSeparator) || (strlen(getParentPath()) == stringLength)); auto inboxTarget = uploadToInbox ? std::make_optional<std::string>(getParentPath()) : std::nullopt; return inboxTarget; } Pitag MegaTransferPrivate::getPitag() const { return mPitag; } void MegaTransferPrivate::updateLocalPathInternal(const LocalPath& newPath) { if (path) delete[] path; mLocalPath = newPath; path = MegaApi::strdup(mLocalPath.toPath(false).c_str()); } bool MegaTransferPrivate::serialize(string *d) const { d->append((const char*)&type, sizeof(type)); d->append((const char*)&nodeHandle, sizeof(nodeHandle)); d->append((const char*)&parentHandle, sizeof(parentHandle)); unsigned short ll; ll = 0; d->append((char*)&ll, sizeof(ll)); ll = (unsigned short)(parentPath ? strlen(parentPath) + 1 : 0); d->append((char*)&ll, sizeof(ll)); d->append(parentPath, ll); ll = (unsigned short)(fileName ? strlen(fileName) + 1 : 0); d->append((char*)&ll, sizeof(ll)); d->append(fileName, ll); d->append((const char*)&folderTransferTag, sizeof(folderTransferTag)); bool hasLocalPath = true; d->append(reinterpret_cast<const char*>(&hasLocalPath), sizeof(bool)); d->append("\0\0\0\0\0", 6); ll = (unsigned short)(appData ? strlen(appData) + 1 : 0); if (ll) { char hasAppData = 1; d->append(&hasAppData, 1); d->append((char*)&ll, sizeof(ll)); d->append(appData, ll); } else { d->append("", 1); } auto localPathSerialized = mLocalPath.serialize(); ll = static_cast<unsigned short>(localPathSerialized.size()); d->append(reinterpret_cast<char*>(&ll), sizeof(ll)); d->append(localPathSerialized); MegaNodePrivate *node = dynamic_cast<MegaNodePrivate *>(publicNode); bool isPublic = (node != NULL); d->append((const char*)&isPublic, sizeof(bool)); if (isPublic) { node->serialize(d); } return true; } MegaTransferPrivate *MegaTransferPrivate::unserialize(string *d) { const char* ptr = d->data(); const char* end = ptr + d->size(); if (ptr + sizeof(int) + sizeof(MegaHandle) + sizeof(MegaHandle) + sizeof(unsigned short) > end) { LOG_err << "MegaTransfer unserialization failed - data too short"; return NULL; } int type = MemAccess::get<int>(ptr); ptr += sizeof(int); MegaTransferPrivate *transfer = new MegaTransferPrivate(type); transfer->nodeHandle = MemAccess::get<MegaHandle>(ptr); ptr += sizeof(MegaHandle); transfer->parentHandle = MemAccess::get<MegaHandle>(ptr); ptr += sizeof(MegaHandle); unsigned short pathlen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(unsigned short); if (ptr + pathlen + sizeof(unsigned short) > end) { LOG_err << "MegaTransfer unserialization failed - path too long"; delete transfer; return NULL; } std::string path; if (pathlen) { path.assign(ptr, pathlen - 1); } ptr += pathlen; unsigned short parentPathLen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(unsigned short); if (ptr + parentPathLen + sizeof(unsigned short) > end) { LOG_err << "MegaTransfer unserialization failed - parentpath too long"; delete transfer; return NULL; } std::string parentPath; if (parentPathLen) { parentPath.assign(ptr, parentPathLen - 1); } ptr += parentPathLen; unsigned short fileNameLen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(unsigned short); if (ptr + fileNameLen + sizeof(int) + 7 + sizeof(char) > end) { LOG_err << "MegaTransfer unserialization failed - filename too long"; delete transfer; return NULL; } std::string fileName; if (fileNameLen) { fileName.assign(ptr, fileNameLen - 1); } ptr += fileNameLen; transfer->folderTransferTag = MemAccess::get<int>(ptr); ptr += sizeof(int); bool hasLocalPath = MemAccess::get<bool>(ptr); ptr += sizeof(bool); if (memcmp(ptr, "\0\0\0\0\0", 6)) { LOG_err << "MegaTransfer unserialization failed - invalid version"; delete transfer; return NULL; } ptr += 6; char hasAppData = MemAccess::get<char>(ptr); ptr += sizeof(char); if (hasAppData > 1) { LOG_err << "MegaTransfer unserialization failed - invalid app data"; delete transfer; return NULL; } if (hasAppData) { if (ptr + sizeof(unsigned short) > end) { LOG_err << "MegaTransfer unserialization failed - no app data header"; delete transfer; return NULL; } unsigned short appDataLen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(unsigned short); if (!appDataLen || (ptr + appDataLen > end)) { LOG_err << "MegaTransfer unserialization failed - invalid appData"; delete transfer; return NULL; } string data; data.assign(ptr, appDataLen - 1); transfer->setAppData(data.c_str()); ptr += appDataLen; } std::optional<LocalPath> localPath; if (hasLocalPath) { if (ptr + sizeof(unsigned short) > end) { LOG_err << "MegaTransfer unserialization failed - LocaPath size"; delete transfer; return NULL; } unsigned short localPathLen = MemAccess::get<unsigned short>(ptr); ptr += sizeof(unsigned short); if (ptr + localPathLen > end) { LOG_err << "MegaTransfer unserialization failed - LocaPath"; delete transfer; return NULL; } std::string data; data.assign(ptr, localPathLen); localPath = LocalPath::unserialize(data); if (!localPath.has_value()) { LOG_err << "MegaTransfer unserialization failed - LocaPath::unserialize"; delete transfer; return NULL; } ptr += localPathLen; } if (ptr + sizeof(bool) > end) { LOG_err << "MegaTransfer unserialization failed - reading public node"; delete transfer; return NULL; } bool isPublic = MemAccess::get<bool>(ptr); ptr += sizeof(bool); d->erase(0, static_cast<size_t>(ptr - d->data())); if (isPublic) { MegaNodePrivate *publicNode = MegaNodePrivate::unserialize(d); if (!publicNode) { LOG_err << "MegaTransfer unserialization failed - unable to unserialize MegaNode"; delete transfer; return NULL; } transfer->setPublicNode(publicNode); delete publicNode; } if (localPath.has_value()) { transfer->setLocalPath(localPath.value()); } else if (path.length()) { transfer->setPath(path.c_str()); } if (parentPath.length()) { transfer->setParentPath(parentPath.c_str()); } if (fileName.length()) { transfer->setFileName(fileName.c_str()); } return transfer; } void MegaTransferPrivate::setTag(int newTag) { tag = newTag; } void MegaTransferPrivate::setSpeed(long long newSpeed) { speed = newSpeed; } void MegaTransferPrivate::setMeanSpeed(long long newMeanSpeed) { meanSpeed = newMeanSpeed; } void MegaTransferPrivate::setDeltaSize(long long newDeltaSize) { deltaSize = newDeltaSize; } void MegaTransferPrivate::setUpdateTime(int64_t newUpdateTime) { updateTime = newUpdateTime; } void MegaTransferPrivate::setPublicNode(MegaNode* newPublicNode, bool copyChildren) { if (publicNode) { delete publicNode; } if (!newPublicNode) { publicNode = nullptr; } else { MegaNodePrivate* nodePrivate = new MegaNodePrivate(newPublicNode); MegaNodeListPrivate* children = dynamic_cast<MegaNodeListPrivate*>(newPublicNode->getChildren()); if (children && copyChildren) { nodePrivate->setChildren(new MegaNodeListPrivate(children, true)); } publicNode = nodePrivate; } } void MegaTransferPrivate::setNodeToUndelete(MegaNode* toUndelete) { nodeToUndelete.reset(toUndelete ? toUndelete->copy() : nullptr); } void MegaTransferPrivate::setSyncTransfer(bool isSyncTransfer) { syncTransfer = isSyncTransfer; } void MegaTransferPrivate::setSourceFileTemporary(bool temporary) { this->temporarySourceFile = temporary; } void MegaTransferPrivate::setStartFirst(bool beFirst) { startFirst = beFirst; } void MegaTransferPrivate::setBackupTransfer(bool isBackupTransfer) { backupTransfer = isBackupTransfer; } void MegaTransferPrivate::setForeignOverquota(bool isForeignOverquota) { foreignOverquota = isForeignOverquota; } void MegaTransferPrivate::setForceNewUpload(bool isForceNewUpload) { forceNewUpload = isForceNewUpload; } void MegaTransferPrivate::setStreamingTransfer(bool isStreamingTransfer) { streamingTransfer = isStreamingTransfer; } void MegaTransferPrivate::setStartTime(int64_t newStartTime) { if (!startTime) { startTime = newStartTime; } } void MegaTransferPrivate::setTransferredBytes(long long newByteCount) { transferredBytes = newByteCount; } void MegaTransferPrivate::setTotalBytes(long long newByteCount) { totalBytes = newByteCount; } void MegaTransferPrivate::setLastBytes(char* newLastBytes) { lastBytes = newLastBytes; } void MegaTransferPrivate::setLastError(const MegaError *e) { lastError.reset(e ? e->copy() : nullptr); } void MegaTransferPrivate::setFolderTransferTag(int newFolderTag) { folderTransferTag = newFolderTag; } void MegaTransferPrivate::setNotificationNumber(long long number) { notificationNumber = number; } void MegaTransferPrivate::setListener(MegaTransferListener* newTransferListener) { listener = newTransferListener; } void MegaTransferPrivate::setTargetOverride(bool targetOverride) { mTargetOverride = targetOverride; } void MegaTransferPrivate::setCancelToken(CancelToken cancelToken) { mCancelToken = MegaCancelTokenPrivate(cancelToken); } void MegaTransferPrivate::setCollisionCheck(CollisionChecker::Option collisionCheck) { mCollisionCheck = collisionCheck; } void MegaTransferPrivate::setCollisionCheck(int collisionCheck) { if (collisionCheck >= static_cast<int>(CollisionChecker::Option::End) || collisionCheck < static_cast<int>(CollisionChecker::Option::Begin)) { mCollisionCheck = CollisionChecker::Option::Fingerprint; } else { mCollisionCheck = static_cast<CollisionChecker::Option>(collisionCheck); } } void MegaTransferPrivate::setCollisionCheckResult(CollisionChecker::Result result) { mCollisionCheckResult = result; } void MegaTransferPrivate::setCollisionResolution(CollisionResolution collisionResolution) { mCollisionResolution = collisionResolution; } void MegaTransferPrivate::setCollisionResolution(int collisionResolution) { if (collisionResolution >= static_cast<int>(CollisionResolution::End) || collisionResolution < static_cast<int>(CollisionResolution::Begin)) { mCollisionResolution = CollisionResolution::RenameNewWithN; } else { mCollisionResolution = static_cast<CollisionResolution>(collisionResolution); } } void MegaTransferPrivate::startRecursiveOperation(shared_ptr<MegaRecursiveOperation> op, MegaNode* node) { assert(op && !recursiveOperation); recursiveOperation = std::move(op); // for folder transfers, we must have a CancelToken even if the user did not supply one // so that we can cancel the remainder of the batch if the user cancels the folder transfer if (!mCancelToken.cancelFlag.exists()) { mCancelToken.cancelFlag = CancelToken(false); } recursiveOperation->start(node); } void MegaTransferPrivate::stopRecursiveOperationThread() { if (recursiveOperation) recursiveOperation->ensureThreadStopped(); } long long MegaTransferPrivate::getPlaceInQueue() const { return placeInQueue; } void MegaTransferPrivate::setPlaceInQueue(long long value) { placeInQueue = value; } MegaCancelToken* MegaTransferPrivate::getCancelToken() { return mCancelToken.existencePtr(); } size_t MegaTransferPrivate::getTotalRecursiveOperation() const { return recursiveOperation ? recursiveOperation->getTransfersTotalCount() : 0; } void MegaTransferPrivate::setPath(const char* newPath) { if (path) delete[] path; mLocalPath = LocalPath::fromRelativePath(newPath); path = MegaApi::strdup(newPath); for (int i = int(strlen(newPath) - 1); i >= 0; i--) { if (newPath[i] == LocalPath::localPathSeparator_utf8) { setFileName(&(newPath[i + 1])); char* parentFolderPath = MegaApi::strdup(newPath); parentFolderPath[i + 1] = '\0'; setParentPath(parentFolderPath); delete[] parentFolderPath; return; } } setFileName(newPath); } void MegaTransferPrivate::setLocalPath(const LocalPath& newPath) { updateLocalPathInternal(newPath); LocalPath name = mLocalPath.leafName(); setFileName(name.toPath(false).c_str()); LocalPath parent = mLocalPath.parentPath(); if (!parent.empty()) { setParentPath(parent.toPath(false).c_str()); } } void MegaTransferPrivate::setParentPath(const char* newParentPath) { if (parentPath) delete[] parentPath; parentPath = MegaApi::strdup(newParentPath); } void MegaTransferPrivate::setFileName(const char* newFileName) { if (fileName) delete[] fileName; fileName = MegaApi::strdup(newFileName); } void MegaTransferPrivate::setNodeHandle(MegaHandle newNodeHandle) { nodeHandle = newNodeHandle; } void MegaTransferPrivate::setParentHandle(MegaHandle newParentHandle) { parentHandle = newParentHandle; } void MegaTransferPrivate::setStartPos(long long newStartPos) { startPos = newStartPos; } void MegaTransferPrivate::setEndPos(long long newEndPos) { endPos = newEndPos; } void MegaTransferPrivate::setNumRetry(int newRetryCount) { retry = newRetryCount; } void MegaTransferPrivate::setStage(unsigned stage) { this->mStage = static_cast<uint8_t>(stage); } void MegaTransferPrivate::setMaxRetries(int newMaxRetryCount) { maxRetries = newMaxRetryCount; } void MegaTransferPrivate::setTime(int64_t newTime) { time = newTime; } const char * MegaTransferPrivate::getTransferString() const { switch(type) { case TYPE_UPLOAD: return "UPLOAD"; case TYPE_DOWNLOAD: return "DOWNLOAD"; case TYPE_LOCAL_TCP_DOWNLOAD: return "LOCAL_HTTP_DOWNLOAD"; } return "UNKNOWN"; } MegaTransferListener* MegaTransferPrivate::getListener() const { return listener; } MegaTransferPrivate::~MegaTransferPrivate() { if (recursiveOperation && !recursiveOperation->allSubtransfersResolved()) { // Folder transfers can only be resolved after all their sub-transfers // are resolved. Eg, cancellation is via setting the cancelToken for all subTransfers // Therefore, logically we should never be deleting this object- assert to catch any errors assert(false); // from review: also log just in case LOG_warn << "~MegaTransferPrivate called before all sub-transfers were resolved"; } delete[] path; delete[] parentPath; delete [] fileName; delete [] appData; delete publicNode; } const char * MegaTransferPrivate::toString() const { return getTransferString(); } MegaContactRequestPrivate::MegaContactRequestPrivate(PendingContactRequest *request) { handle = request->id; sourceEmail = request->originatoremail.size() ? MegaApi::strdup(request->originatoremail.c_str()) : NULL; sourceMessage = request->msg.size() ? MegaApi::strdup(request->msg.c_str()) : NULL; targetEmail = request->targetemail.size() ? MegaApi::strdup(request->targetemail.c_str()) : NULL; creationTime = request->ts; modificationTime = request->uts; autoaccepted = request->autoaccepted; if(request->changed.accepted) { status = MegaContactRequest::STATUS_ACCEPTED; } else if(request->changed.deleted) { status = MegaContactRequest::STATUS_DELETED; } else if(request->changed.denied) { status = MegaContactRequest::STATUS_DENIED; } else if(request->changed.ignored) { status = MegaContactRequest::STATUS_IGNORED; } else if(request->changed.reminded) { status = MegaContactRequest::STATUS_REMINDED; } else { status = MegaContactRequest::STATUS_UNRESOLVED; } outgoing = request->isoutgoing; } MegaContactRequestPrivate::MegaContactRequestPrivate(const MegaContactRequest *request) { handle = request->getHandle(); sourceEmail = MegaApi::strdup(request->getSourceEmail()); sourceMessage = MegaApi::strdup(request->getSourceMessage()); targetEmail = MegaApi::strdup(request->getTargetEmail()); creationTime = request->getCreationTime(); modificationTime = request->getModificationTime(); status = request->getStatus(); outgoing = request->isOutgoing(); autoaccepted = request->isAutoAccepted(); } MegaContactRequestPrivate::~MegaContactRequestPrivate() { delete [] sourceEmail; delete [] sourceMessage; delete [] targetEmail; } MegaContactRequest *MegaContactRequestPrivate::fromContactRequest(PendingContactRequest *request) { return new MegaContactRequestPrivate(request); } MegaContactRequest *MegaContactRequestPrivate::copy() const { return new MegaContactRequestPrivate(this); } MegaHandle MegaContactRequestPrivate::getHandle() const { return handle; } char *MegaContactRequestPrivate::getSourceEmail() const { return sourceEmail; } char *MegaContactRequestPrivate::getSourceMessage() const { return sourceMessage; } char *MegaContactRequestPrivate::getTargetEmail() const { return targetEmail; } int64_t MegaContactRequestPrivate::getCreationTime() const { return creationTime; } int64_t MegaContactRequestPrivate::getModificationTime() const { return modificationTime; } int MegaContactRequestPrivate::getStatus() const { return status; } bool MegaContactRequestPrivate::isOutgoing() const { return outgoing; } bool MegaContactRequestPrivate::isAutoAccepted() const { return autoaccepted; } MegaAccountDetails *MegaAccountDetailsPrivate::fromAccountDetails(AccountDetails *details) { return new MegaAccountDetailsPrivate(details); } MegaAccountDetailsPrivate::MegaAccountDetailsPrivate(AccountDetails *details) { this->details = (*details); } MegaAccountDetailsPrivate::~MegaAccountDetailsPrivate() { } MegaRequest *MegaRequestPrivate::copy() { return new MegaRequestPrivate(this); } MegaRequestPrivate::MegaRequestPrivate(int type, MegaRequestListener *listener) { this->type = type; this->tag = 0; this->transfer = 0; this->listener = listener; this->backupListener = NULL; this->nodeHandle = UNDEF; this->link = NULL; this->parentHandle = UNDEF; this->sessionKey = NULL; this->name = NULL; this->email = NULL; this->text = NULL; this->password = NULL; this->newPassword = NULL; this->privateKey = NULL; this->access = MegaShare::ACCESS_UNKNOWN; this->numRetry = 0; this->publicNode = NULL; this->numDetails = 0; this->file = NULL; this->attrType = 0; this->flag = false; this->totalBytes = -1; this->transferredBytes = 0; this->number = 0; this->timeZoneDetails = NULL; if (type == MegaRequest::TYPE_ACCOUNT_DETAILS || type == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN) { this->accountDetails = std::make_shared<AccountDetails>(); } else { this->accountDetails.reset(); } if (type == MegaRequest::TYPE_GET_ACHIEVEMENTS) { this->achievementsDetails = new AchievementsDetails(); } else { this->achievementsDetails = NULL; } if ((type == MegaRequest::TYPE_GET_PRICING) || (type == MegaRequest::TYPE_GET_PAYMENT_ID) || type == MegaRequest::TYPE_UPGRADE_ACCOUNT || type == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN) { megaPricing = new MegaPricingPrivate(); megaCurrency = new MegaCurrencyPrivate(); } else { megaPricing = NULL; megaCurrency = nullptr; } #ifdef ENABLE_CHAT if (type == MegaRequest::TYPE_CHAT_CREATE) { this->chatPeerList = new MegaTextChatPeerListPrivate(); } else { this->chatPeerList = NULL; } if (type == MegaRequest::TYPE_CHAT_FETCH) { this->chatList = new MegaTextChatListPrivate(); } else { this->chatList = NULL; } #endif stringMap = NULL; mStringListMap = NULL; mStringTable = NULL; folderInfo = NULL; settings = NULL; backgroundMediaUpload = NULL; } MegaRequestPrivate::MegaRequestPrivate(MegaRequestPrivate *request) { this->link = NULL; this->sessionKey = NULL; this->name = NULL; this->email = NULL; this->text = NULL; this->password = NULL; this->newPassword = NULL; this->privateKey = NULL; this->access = MegaShare::ACCESS_UNKNOWN; this->file = NULL; this->publicNode = NULL; this->type = request->getType(); this->setTag(request->getTag()); this->setNodeHandle(request->getNodeHandle()); this->setLink(request->getLink()); this->setParentHandle(request->getParentHandle()); this->setSessionKey(request->getSessionKey()); this->setName(request->getName()); this->setEmail(request->getEmail()); this->setPassword(request->getPassword()); this->setNewPassword(request->getNewPassword()); this->setPrivateKey(request->getPrivateKey()); this->setAccess(request->getAccess()); this->setNumRetry(request->getNumRetry()); this->setNumDetails(request->getNumDetails()); this->setFile(request->getFile()); this->setParamType(request->getParamType()); this->setText(request->getText()); this->setNumber(request->getNumber()); this->setPublicNode(request->getPublicNode()); this->setFlag(request->getFlag()); this->setTransferTag(request->getTransferTag()); this->setTotalBytes(request->getTotalBytes()); this->setTransferredBytes(request->getTransferredBytes()); this->listener = request->getListener(); this->backupListener = request->getBackupListener(); this->megaPricing = (MegaPricingPrivate *)request->getPricing(); this->megaCurrency = (MegaCurrencyPrivate *)request->getCurrency(); this->accountDetails.reset(); if(request->getAccountDetails()) { this->accountDetails = std::make_shared<AccountDetails>(); *(this->accountDetails) = *(request->getAccountDetails()); } this->achievementsDetails = NULL; if(request->getAchievementsDetails()) { this->achievementsDetails = new AchievementsDetails(); *(this->achievementsDetails) = *(request->getAchievementsDetails()); } this->timeZoneDetails = request->getMegaTimeZoneDetails() ? request->timeZoneDetails->copy() : NULL; #ifdef ENABLE_CHAT this->chatPeerList = request->getMegaTextChatPeerList() ? request->chatPeerList->copy() : NULL; this->chatList = request->getMegaTextChatList() ? request->chatList->copy() : NULL; this->mScheduledMeetingList.reset(request->mScheduledMeetingList ? request->mScheduledMeetingList->copy() : nullptr); #endif this->stringMap = request->getMegaStringMap() ? request->stringMap->copy() : NULL; this->mStringListMap = request->getMegaStringListMap() ? request->mStringListMap->copy() : NULL; this->mStringTable = request->getMegaStringTable() ? request->mStringTable->copy() : NULL; this->folderInfo = request->getMegaFolderInfo() ? request->folderInfo->copy() : NULL; this->settings = request->getMegaPushNotificationSettings() ? request->settings->copy() : NULL; this->backgroundMediaUpload = NULL; this->mBannerList.reset(request->mBannerList ? request->mBannerList->copy() : nullptr); this->mHandleList.reset(request->mHandleList ? request->mHandleList->copy() : nullptr); this->mRecentActions.reset(request->mRecentActions ? request->mRecentActions->copy() : nullptr); this->mMegaBackupInfoList.reset(request->mMegaBackupInfoList ? request->mMegaBackupInfoList->copy() : nullptr); this->mMegaSet.reset(request->mMegaSet ? request->mMegaSet->copy() : nullptr); this->mMegaSetElementList.reset(request->mMegaSetElementList ? request->mMegaSetElementList->copy() : nullptr); this->mMegaIntegerList.reset(request->mMegaIntegerList ? request->mMegaIntegerList->copy() : nullptr); #ifdef ENABLE_SYNC if (request->mSyncStallList) mSyncStallList.reset(request->mSyncStallList->copy()); if (request->mSyncStallMap) { mSyncStallMap.reset(request->mSyncStallMap->copy()); } #endif // ENABLE_SYNC this->mStringList.reset(request->mStringList ? request->mStringList->copy() : nullptr); this->mMegaVpnRegions.reset(request->mMegaVpnRegions ? request->mMegaVpnRegions->copy() : nullptr); this->mMegaVpnCredentials.reset(request->mMegaVpnCredentials ? request->mMegaVpnCredentials->copy() : nullptr); mNetworkConnectivityTestResults.reset(request->mNetworkConnectivityTestResults ? request->mNetworkConnectivityTestResults->copy() : nullptr); this->mMegaNotifications.reset(request->mMegaNotifications ? request->mMegaNotifications->copy() : nullptr); this->mMegaNodeTree.reset(request->mMegaNodeTree ? request->mMegaNodeTree->copy() : nullptr); this->mStringIntegerMap.reset(request->mStringIntegerMap ? request->mStringIntegerMap->copy() : nullptr); this->mMegaDiscountCodeList.reset( request->mMegaDiscountCodeList ? request->mMegaDiscountCodeList->copy() : nullptr); this->mMegaDiscountCodeInfo.reset( request->mMegaDiscountCodeInfo ? request->mMegaDiscountCodeInfo->copy() : nullptr); } std::shared_ptr<AccountDetails> MegaRequestPrivate::getAccountDetails() const { return accountDetails; } MegaAchievementsDetails *MegaRequestPrivate::getMegaAchievementsDetails() const { if (achievementsDetails) { return MegaAchievementsDetailsPrivate::fromAchievementsDetails(achievementsDetails); } return NULL; } AchievementsDetails *MegaRequestPrivate::getAchievementsDetails() const { return achievementsDetails; } MegaTimeZoneDetails *MegaRequestPrivate::getMegaTimeZoneDetails() const { return timeZoneDetails; } MegaStringList *MegaRequestPrivate::getMegaStringList() const { return mStringList.get(); } MegaStringIntegerMap* MegaRequestPrivate::getMegaStringIntegerMap() const { return mStringIntegerMap.get(); } MegaHandleList* MegaRequestPrivate::getMegaHandleList() const { return mHandleList.get(); } #ifdef ENABLE_SYNC MegaSyncStallList* MegaRequestPrivate::getMegaSyncStallList() const { return mSyncStallList.get(); } MegaSyncStallMap* MegaRequestPrivate::getMegaSyncStallMap() const { return mSyncStallMap.get(); } void MegaRequestPrivate::setMegaSyncStallList(unique_ptr<MegaSyncStallList>&& sl) { mSyncStallList = std::move(sl); } void MegaRequestPrivate::setMegaSyncStallMap(std::unique_ptr<MegaSyncStallMap>&& sm) { mSyncStallMap = std::move(sm); } #endif // ENABLE_SYNC #ifdef ENABLE_CHAT MegaTextChatPeerList *MegaRequestPrivate::getMegaTextChatPeerList() const { return chatPeerList; } void MegaRequestPrivate::setMegaTextChatPeerList(MegaTextChatPeerList *chatPeers) { if (this->chatPeerList) { delete this->chatPeerList; } this->chatPeerList = chatPeers ? chatPeers->copy() : nullptr; } MegaTextChatList *MegaRequestPrivate::getMegaTextChatList() const { return chatList; } void MegaRequestPrivate::setMegaTextChatList(MegaTextChatList* newChatList) { if (chatList) delete chatList; chatList = newChatList->copy(); } MegaScheduledMeetingList* MegaRequestPrivate::getMegaScheduledMeetingList() const { return mScheduledMeetingList.get(); } void MegaRequestPrivate::setMegaScheduledMeetingList(const MegaScheduledMeetingList* schedMeetingList) { mScheduledMeetingList.reset(); if (schedMeetingList) { mScheduledMeetingList = unique_ptr<MegaScheduledMeetingList>(schedMeetingList->copy()); } } #endif MegaStringMap *MegaRequestPrivate::getMegaStringMap() const { return stringMap; } void MegaRequestPrivate::setMegaStringMap(const MegaStringMap* newStringMap) { if (stringMap) { delete stringMap; } stringMap = newStringMap ? newStringMap->copy() : nullptr; } void MegaRequestPrivate::setMegaStringMap(const map<string, string>& newValues) { delete stringMap; stringMap = new MegaStringMapPrivate(&newValues); } MegaStringListMap *MegaRequestPrivate::getMegaStringListMap() const { return mStringListMap; } void MegaRequestPrivate::setMegaStringListMap(const MegaStringListMap* stringListMap) { if (mStringListMap) { delete mStringListMap; } mStringListMap = stringListMap ? stringListMap->copy() : nullptr; } MegaStringTable *MegaRequestPrivate::getMegaStringTable() const { return mStringTable; } void MegaRequestPrivate::setMegaStringTable(const MegaStringTable* stringTable) { if (mStringTable) { delete mStringTable; } mStringTable = stringTable ? stringTable->copy() : nullptr; } MegaFolderInfo *MegaRequestPrivate::getMegaFolderInfo() const { return folderInfo; } void MegaRequestPrivate::setMegaFolderInfo(const MegaFolderInfo* newFolderInfo) { if (folderInfo) { delete folderInfo; } folderInfo = newFolderInfo ? newFolderInfo->copy() : nullptr; } const MegaPushNotificationSettings *MegaRequestPrivate::getMegaPushNotificationSettings() const { return settings; } void MegaRequestPrivate::setMegaPushNotificationSettings( const MegaPushNotificationSettings* newSettings) { if (settings) { delete settings; } settings = newSettings ? newSettings->copy() : nullptr; } MegaBackgroundMediaUpload *MegaRequestPrivate::getMegaBackgroundMediaUploadPtr() const { // non-owned pointer return backgroundMediaUpload; } void MegaRequestPrivate::setMegaBackgroundMediaUploadPtr(MegaBackgroundMediaUpload *p) { // non-owned pointer backgroundMediaUpload = p; } void MegaRequestPrivate::setMegaStringList(const MegaStringList* stringList) { mStringList.reset(); if (stringList) { mStringList = unique_ptr<MegaStringList>(stringList->copy()); } } void MegaRequestPrivate::setMegaStringIntegerMap(const MegaStringIntegerMap* stringIntegerMap) { mStringIntegerMap.reset(); if (stringIntegerMap) { mStringIntegerMap = std::unique_ptr<MegaStringIntegerMap>(stringIntegerMap->copy()); } } void MegaRequestPrivate::setMegaHandleList(const vector<handle> &handles) { mHandleList.reset(new MegaHandleListPrivate(handles)); } void MegaRequestPrivate::setMegaHandleList(const MegaHandleList* handles) { mHandleList.reset(handles ? handles->copy() : nullptr); } MegaScheduledCopyListener *MegaRequestPrivate::getBackupListener() const { return backupListener; } void MegaRequestPrivate::setBackupListener(MegaScheduledCopyListener *value) { backupListener = value; } MegaAccountDetails *MegaRequestPrivate::getMegaAccountDetails() const { if(accountDetails) { return MegaAccountDetailsPrivate::fromAccountDetails(accountDetails.get()); } return NULL; } MegaRequestPrivate::~MegaRequestPrivate() { delete [] link; delete [] name; delete [] email; delete [] password; delete [] newPassword; delete [] privateKey; delete [] sessionKey; delete publicNode; delete [] file; delete megaPricing; delete megaCurrency; delete achievementsDetails; delete [] text; delete stringMap; delete mStringListMap; delete mStringTable; delete folderInfo; delete timeZoneDetails; delete settings; #ifdef ENABLE_CHAT delete chatPeerList; delete chatList; #endif } int MegaRequestPrivate::getType() const { return type; } uint64_t MegaRequestPrivate::getNodeHandle() const { return nodeHandle; } const char* MegaRequestPrivate::getLink() const { return link; } uint64_t MegaRequestPrivate::getParentHandle() const { return parentHandle; } const char* MegaRequestPrivate::getSessionKey() const { return sessionKey; } const char* MegaRequestPrivate::getName() const { return name; } const char* MegaRequestPrivate::getEmail() const { return email; } const char* MegaRequestPrivate::getPassword() const { return password; } const char* MegaRequestPrivate::getNewPassword() const { return newPassword; } const char* MegaRequestPrivate::getPrivateKey() const { return privateKey; } int MegaRequestPrivate::getAccess() const { return access; } const char* MegaRequestPrivate::getFile() const { return file; } int MegaRequestPrivate::getParamType() const { return attrType; } const char *MegaRequestPrivate::getText() const { return text; } long long MegaRequestPrivate::getNumber() const { return number; } bool MegaRequestPrivate::getFlag() const { return flag; } long long MegaRequestPrivate::getTransferredBytes() const { return transferredBytes; } long long MegaRequestPrivate::getTotalBytes() const { return totalBytes; } int MegaRequestPrivate::getNumRetry() const { return numRetry; } int MegaRequestPrivate::getNumDetails() const { return numDetails; } int MegaRequestPrivate::getTag() const { return tag; } MegaPricing *MegaRequestPrivate::getPricing() const { return megaPricing ? megaPricing->copy() : NULL; } MegaCurrency *MegaRequestPrivate::getCurrency() const { return megaCurrency ? megaCurrency->copy() : nullptr; } void MegaRequestPrivate::setNumDetails(int count) { numDetails = count; } MegaNode *MegaRequestPrivate::getPublicNode() const { return publicNode; } MegaNode *MegaRequestPrivate::getPublicMegaNode() const { if(publicNode) { return publicNode->copy(); } return NULL; } void MegaRequestPrivate::setNodeHandle(MegaHandle newNodeHandle) { nodeHandle = newNodeHandle; } void MegaRequestPrivate::setParentHandle(MegaHandle newParentHandle) { parentHandle = newParentHandle; } void MegaRequestPrivate::setSessionKey(const char* newSessionKey) { if (sessionKey) delete[] sessionKey; sessionKey = MegaApi::strdup(newSessionKey); } void MegaRequestPrivate::setNumRetry(int count) { numRetry = count; } void MegaRequestPrivate::setLink(const char* newLink) { if (link) delete[] link; link = MegaApi::strdup(newLink); } void MegaRequestPrivate::setName(const char* newName) { if (name) delete[] name; name = MegaApi::strdup(newName); } void MegaRequestPrivate::setEmail(const char* newEmail) { if (email) delete[] email; email = MegaApi::strdup(newEmail); } void MegaRequestPrivate::setPassword(const char* pass) { if (password) delete[] password; password = MegaApi::strdup(pass); } void MegaRequestPrivate::setNewPassword(const char* pass) { if (newPassword) delete[] newPassword; newPassword = MegaApi::strdup(pass); } void MegaRequestPrivate::setPrivateKey(const char* newPrivateKey) { if (privateKey) delete[] privateKey; privateKey = MegaApi::strdup(newPrivateKey); } void MegaRequestPrivate::setAccess(int newAccess) { access = newAccess; } void MegaRequestPrivate::setFile(const char* newFile) { if (file) delete[] file; file = MegaApi::strdup(newFile); } void MegaRequestPrivate::setParamType(int newType) { attrType = newType; } void MegaRequestPrivate::setText(const char* newText) { if (text) delete[] text; text = MegaApi::strdup(newText); } void MegaRequestPrivate::setNumber(long long newNumber) { number = newNumber; } void MegaRequestPrivate::setFlag(bool newFlag) { flag = newFlag; } void MegaRequestPrivate::setTransferTag(int newTag) { transfer = newTag; } void MegaRequestPrivate::setListener(MegaRequestListener* newListener) { listener = newListener; } void MegaRequestPrivate::setTotalBytes(long long byteCount) { totalBytes = byteCount; } void MegaRequestPrivate::setTransferredBytes(long long byteCount) { transferredBytes = byteCount; } void MegaRequestPrivate::setTag(int newTag) { tag = newTag; } void MegaRequestPrivate::addProduct(const Product& product) { if (megaPricing) { megaPricing->addProduct(product); } } void MegaRequestPrivate::setCurrency(std::unique_ptr<CurrencyData> currencyData) { if (megaCurrency) { megaCurrency->setCurrency(std::move(currencyData)); } } void MegaRequestPrivate::setProxy(Proxy* newProxy) { proxy = newProxy; } Proxy *MegaRequestPrivate::getProxy() { return proxy; } void MegaRequestPrivate::setTimeZoneDetails(MegaTimeZoneDetails* newDetails) { if (timeZoneDetails) { delete timeZoneDetails; } timeZoneDetails = newDetails ? newDetails->copy() : nullptr; } void MegaRequestPrivate::setPublicNode(MegaNode* newPublicNode, bool copyChildren) { if (publicNode) { delete publicNode; } if (!newPublicNode) { publicNode = nullptr; } else { MegaNodePrivate* nodePrivate = new MegaNodePrivate(newPublicNode); MegaNodeListPrivate* children = dynamic_cast<MegaNodeListPrivate*>(newPublicNode->getChildren()); if (children && copyChildren) { nodePrivate->setChildren(new MegaNodeListPrivate(children, true)); } publicNode = nodePrivate; } } MegaBannerList* MegaRequestPrivate::getMegaBannerList() const { return mBannerList.get(); } void MegaRequestPrivate::setBanners(vector<BannerDetails>&& banners) { mBannerList = std::make_unique<MegaBannerListPrivate>(); for (auto&& b : banners) { mBannerList->add(MegaBannerPrivate(std::move(b))); } } MegaRecentActionBucketList* MegaRequestPrivate::getRecentActions() const { return mRecentActions.get(); } void MegaRequestPrivate::setRecentActions(std::unique_ptr<MegaRecentActionBucketList> recentActionBucketList) { mRecentActions.reset(recentActionBucketList.release()); } MegaSet* MegaRequestPrivate::getMegaSet() const { return mMegaSet.get(); } void MegaRequestPrivate::setMegaSet(std::unique_ptr<MegaSet> s) { mMegaSet.swap(s); } MegaSetElementList* MegaRequestPrivate::getMegaSetElementList() const { return mMegaSetElementList.get(); } void MegaRequestPrivate::setMegaSetElementList(std::unique_ptr<MegaSetElementList> els) { mMegaSetElementList.swap(els); } const MegaIntegerList* MegaRequestPrivate::getMegaIntegerList() const { return mMegaIntegerList.get(); } void MegaRequestPrivate::setMegaIntegerList(std::unique_ptr<MegaIntegerList> ints) { mMegaIntegerList.swap(ints); } MegaBackupInfoList* MegaRequestPrivate::getMegaBackupInfoList() const { return mMegaBackupInfoList.get(); } void MegaRequestPrivate::setMegaBackupInfoList(std::unique_ptr<MegaBackupInfoList> bkps) { mMegaBackupInfoList.swap(bkps); } MegaVpnRegionList* MegaRequestPrivate::getMegaVpnRegionsDetailed() const { return mMegaVpnRegions.get(); } void MegaRequestPrivate::setMegaVpnRegionsDetailed(MegaVpnRegionList* vpnRegions) { mMegaVpnRegions.reset(vpnRegions); } MegaVpnCredentials* MegaRequestPrivate::getMegaVpnCredentials() const { return mMegaVpnCredentials.get(); } void MegaRequestPrivate::setMegaVpnCredentials(MegaVpnCredentials* megaVpnCredentials) { mMegaVpnCredentials.reset(megaVpnCredentials); } MegaNetworkConnectivityTestResults* MegaRequestPrivate::getMegaNetworkConnectivityTestResults() const { return mNetworkConnectivityTestResults.get(); } void MegaRequestPrivate::setMegaNetworkConnectivityTestResults( MegaNetworkConnectivityTestResults* networkConnectivityTestResults) { mNetworkConnectivityTestResults.reset(networkConnectivityTestResults); } const char *MegaRequestPrivate::getRequestString() const { switch(type) { case TYPE_LOGIN: return "LOGIN"; case TYPE_CREATE_FOLDER: return "CREATE_FOLDER"; case TYPE_MOVE: return "MOVE"; case TYPE_COPY: return "COPY"; case TYPE_RENAME: return "RENAME"; case TYPE_REMOVE: return "REMOVE"; case TYPE_SHARE: return "SHARE"; case TYPE_IMPORT_LINK: return "IMPORT_LINK"; case TYPE_EXPORT: return "EXPORT"; case TYPE_FETCH_NODES: return "FETCH_NODES"; case TYPE_ACCOUNT_DETAILS: return "ACCOUNT_DETAILS"; case TYPE_CHANGE_PW: return "CHANGE_PW"; case TYPE_UPLOAD: return "UPLOAD"; case TYPE_LOGOUT: return "LOGOUT"; case TYPE_GET_PUBLIC_NODE: return "GET_PUBLIC_NODE"; case TYPE_GET_ATTR_FILE: return "GET_ATTR_FILE"; case TYPE_SET_ATTR_FILE: return "SET_ATTR_FILE"; case TYPE_GET_ATTR_USER: return "GET_ATTR_USER"; case TYPE_SET_ATTR_USER: return "SET_ATTR_USER"; case TYPE_RETRY_PENDING_CONNECTIONS: return "RETRY_PENDING_CONNECTIONS"; case TYPE_REMOVE_CONTACT: return "REMOVE_CONTACT"; case TYPE_CREATE_ACCOUNT: return "CREATE_ACCOUNT"; case TYPE_CONFIRM_ACCOUNT: return "CONFIRM_ACCOUNT"; case TYPE_QUERY_SIGNUP_LINK: return "QUERY_SIGNUP_LINK"; case TYPE_ADD_SYNC: return "ADD_SYNC"; //case TYPE_ENABLE_SYNC: return "ENABLE_SYNC"; //case TYPE_DISABLE_SYNC: return "DISABLE_SYNC"; case TYPE_COPY_SYNC_CONFIG: return "TYPE_COPY_SYNC_CONFIG"; case TYPE_COPY_CACHED_STATUS: return "TYPE_COPY_CACHED_STATUS"; case TYPE_IMPORT_SYNC_CONFIGS: return "TYPE_IMPORT_SYNC_CONFIGS"; case TYPE_REMOVE_SYNC: return "REMOVE_SYNC"; case TYPE_REMOVE_SYNCS: return "REMOVE_SYNCS"; case TYPE_PAUSE_TRANSFERS: return "PAUSE_TRANSFERS"; case TYPE_CANCEL_TRANSFER: return "CANCEL_TRANSFER"; case TYPE_CANCEL_TRANSFERS: return "CANCEL_TRANSFERS"; case TYPE_DELETE: return "DELETE"; case TYPE_REPORT_EVENT: return "REPORT_EVENT"; case TYPE_CANCEL_ATTR_FILE: return "CANCEL_ATTR_FILE"; case TYPE_GET_PRICING: return "GET_PRICING"; case TYPE_GET_PAYMENT_ID: return "GET_PAYMENT_ID"; case TYPE_UPGRADE_ACCOUNT: return "UPGRADE_ACCOUNT"; case TYPE_GET_USER_DATA: return "GET_USER_DATA"; case TYPE_LOAD_BALANCING: return "LOAD_BALANCING"; case TYPE_KILL_SESSION: return "KILL_SESSION"; case TYPE_SUBMIT_PURCHASE_RECEIPT: return "SUBMIT_PURCHASE_RECEIPT"; case TYPE_CREDIT_CARD_STORE: return "CREDIT_CARD_STORE"; case TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS: return "CREDIT_CARD_QUERY_SUBSCRIPTIONS"; case TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS: return "CREDIT_CARD_CANCEL_SUBSCRIPTIONS"; case TYPE_GET_SESSION_TRANSFER_URL: return "GET_SESSION_TRANSFER_URL"; case TYPE_GET_PAYMENT_METHODS: return "GET_PAYMENT_METHODS"; case TYPE_INVITE_CONTACT: return "INVITE_CONTACT"; case TYPE_REPLY_CONTACT_REQUEST: return "REPLY_CONTACT_REQUEST"; case TYPE_SUBMIT_FEEDBACK: return "SUBMIT_FEEDBACK"; case TYPE_SEND_EVENT: return "SEND_EVENT"; case TYPE_CLEAN_RUBBISH_BIN: return "CLEAN_RUBBISH_BIN"; case TYPE_SET_ATTR_NODE: return "SET_ATTR_NODE"; case TYPE_CHAT_CREATE: return "CHAT_CREATE"; case TYPE_CHAT_FETCH: return "CHAT_FETCH"; case TYPE_CHAT_INVITE: return "CHAT_INVITE"; case TYPE_CHAT_REMOVE: return "CHAT_REMOVE"; case TYPE_CHAT_URL: return "CHAT_URL"; case TYPE_CHAT_GRANT_ACCESS: return "CHAT_GRANT_ACCESS"; case TYPE_CHAT_REMOVE_ACCESS: return "CHAT_REMOVE_ACCESS"; case TYPE_SET_PROXY: return "SET_PROXY"; case TYPE_GET_RECOVERY_LINK: return "GET_RECOVERY_LINK"; case TYPE_QUERY_RECOVERY_LINK: return "QUERY_RECOVERY_LINK"; case TYPE_CONFIRM_RECOVERY_LINK: return "CONFIRM_RECOVERY_LINK"; case TYPE_GET_CANCEL_LINK: return "GET_CANCEL_LINK"; case TYPE_CONFIRM_CANCEL_LINK: return "CONFIRM_CANCEL_LINK"; case TYPE_GET_CHANGE_EMAIL_LINK: return "GET_CHANGE_EMAIL_LINK"; case TYPE_CONFIRM_CHANGE_EMAIL_LINK: return "CONFIRM_CHANGE_EMAIL_LINK"; case TYPE_PAUSE_TRANSFER: return "PAUSE_TRANSFER"; case TYPE_MOVE_TRANSFER: return "MOVE_TRANSFER"; case TYPE_CHAT_SET_TITLE: return "CHAT_SET_TITLE"; case TYPE_CHAT_UPDATE_PERMISSIONS: return "CHAT_UPDATE_PERMISSIONS"; case TYPE_CHAT_TRUNCATE: return "CHAT_TRUNCATE"; case TYPE_SET_MAX_CONNECTIONS: return "SET_MAX_CONNECTIONS"; case TYPE_CHAT_PRESENCE_URL: return "CHAT_PRESENCE_URL"; case TYPE_REGISTER_PUSH_NOTIFICATION: return "REGISTER_PUSH_NOTIFICATION"; case TYPE_GET_USER_EMAIL: return "GET_USER_EMAIL"; case TYPE_APP_VERSION: return "APP_VERSION"; case TYPE_GET_LOCAL_SSL_CERT: return "GET_LOCAL_SSL_CERT"; case TYPE_SEND_SIGNUP_LINK: return "SEND_SIGNUP_LINK"; case TYPE_QUERY_DNS: return "QUERY_DNS"; case TYPE_CHAT_STATS: return "CHAT_STATS"; case TYPE_DOWNLOAD_FILE: return "DOWNLOAD_FILE"; case TYPE_QUERY_TRANSFER_QUOTA: return "QUERY_TRANSFER_QUOTA"; case TYPE_PASSWORD_LINK: return "PASSWORD_LINK"; case TYPE_RESTORE: return "RESTORE"; case TYPE_GET_ACHIEVEMENTS: return "GET_ACHIEVEMENTS"; case TYPE_REMOVE_VERSIONS: return "REMOVE_VERSIONS"; case TYPE_CHAT_ARCHIVE: return "CHAT_ARCHIVE"; case TYPE_WHY_AM_I_BLOCKED: return "WHY_AM_I_BLOCKED"; case TYPE_CONTACT_LINK_CREATE: return "CONTACT_LINK_CREATE"; case TYPE_CONTACT_LINK_QUERY: return "CONTACT_LINK_QUERY"; case TYPE_CONTACT_LINK_DELETE: return "CONTACT_LINK_DELETE"; case TYPE_FOLDER_INFO: return "FOLDER_INFO"; case TYPE_RICH_LINK: return "RICH_LINK"; case TYPE_CHAT_LINK_HANDLE: return "CHAT_LINK_HANDLE"; case TYPE_CHAT_LINK_URL: return "CHAT_LINK_URL"; case TYPE_SET_PRIVATE_MODE: return "SET_PRIVATE_MODE"; case TYPE_AUTOJOIN_PUBLIC_CHAT: return "AUTOJOIN_PUBLIC_CHAT"; case TYPE_KEEP_ME_ALIVE: return "KEEP_ME_ALIVE"; case TYPE_MULTI_FACTOR_AUTH_CHECK: return "MULTI_FACTOR_AUTH_CHECK"; case TYPE_MULTI_FACTOR_AUTH_GET: return "MULTI_FACTOR_AUTH_GET"; case TYPE_MULTI_FACTOR_AUTH_SET: return "MULTI_FACTOR_AUTH_SET"; case TYPE_ADD_SCHEDULED_COPY: return "ADD_BACKUP"; // TODO: consider renaming this in the future. case TYPE_REMOVE_SCHEDULED_COPY: return "REMOVE_BACKUP"; // TODO: consider renaming this in the future.. case TYPE_ABORT_CURRENT_SCHEDULED_COPY: return "ABORT_BACKUP"; // TODO: consider renaming this in the future. case TYPE_TIMER: return "SET_TIMER"; case TYPE_GET_PSA: return "GET_PSA"; case TYPE_FETCH_TIMEZONE: return "FETCH_TIMEZONE"; case TYPE_USERALERT_ACKNOWLEDGE: return "USERALERT_ACKNOWLEDGE"; case TYPE_CATCHUP: return "CATCHUP"; case TYPE_PUBLIC_LINK_INFORMATION: return "PUBLIC_LINK_INFORMATION"; case TYPE_GET_BACKGROUND_UPLOAD_URL: return "GET_BACKGROUND_UPLOAD_URL"; case TYPE_COMPLETE_BACKGROUND_UPLOAD: return "COMPLETE_BACKGROUND_UPLOAD"; case TYPE_GET_CLOUD_STORAGE_USED: return "GET_CLOUD_STORAGE_USED"; case TYPE_SEND_SMS_VERIFICATIONCODE: return "SEND_SMS_VERIFICATIONCODE"; case TYPE_CHECK_SMS_VERIFICATIONCODE: return "CHECK_SMS_VERIFICATIONCODE"; case TYPE_GET_COUNTRY_CALLING_CODES: return "GET_COUNTRY_CALLING_CODES"; case TYPE_VERIFY_CREDENTIALS: return "VERIFY_CREDENTIALS"; case TYPE_GET_MISC_FLAGS: return "GET_MISC_FLAGS"; case TYPE_RESEND_VERIFICATION_EMAIL: return "RESEND_VERIFICATION_EMAIL"; case TYPE_SUPPORT_TICKET: return "SUPPORT_TICKET"; case TYPE_SET_RETENTION_TIME: return "SET_RETENTION_TIME"; case TYPE_RESET_SMS_VERIFIED_NUMBER: return "RESET_SMS_VERIFIED_NUMBER"; case TYPE_SEND_DEV_COMMAND: return "SEND_DEV_COMMAND"; case TYPE_GET_BANNERS: return "GET_BANNERS"; case TYPE_DISMISS_BANNER: return "DISMISS_BANNER"; case TYPE_BACKUP_PUT: return "BACKUP_PUT"; case TYPE_BACKUP_REMOVE: return "BACKUP_REMOVE"; case TYPE_BACKUP_PUT_HEART_BEAT: return "BACKUP_PUT_HEART_BEAT"; case TYPE_FETCH_ADS: return "FETCH_ADS"; case TYPE_QUERY_ADS: return "QUERY_ADS"; case TYPE_GET_ATTR_NODE: return "GET_ATTR_NODE"; case TYPE_START_CHAT_CALL: return "START_CHAT_CALL"; case TYPE_JOIN_CHAT_CALL: return "JOIN_CHAT_CALL"; case TYPE_END_CHAT_CALL: return "END_CHAT_CALL"; case TYPE_LOAD_EXTERNAL_DRIVE_BACKUPS: return "LOAD_EXTERNAL_DRIVE_BACKUPS"; case TYPE_CLOSE_EXTERNAL_DRIVE_BACKUPS: return "CLOSE_EXTERNAL_DRIVE_BACKUPS"; case TYPE_GET_DOWNLOAD_URLS: return "GET_DOWNLOAD_URLS"; case TYPE_GET_FA_UPLOAD_URL: return "GET_FA_UPLOAD_URL"; case TYPE_EXECUTE_ON_THREAD: return "EXECUTE_ON_THREAD"; case TYPE_SET_CHAT_OPTIONS: return "SET_CHAT_OPTIONS"; case TYPE_GET_RECENT_ACTIONS: return "GET_RECENT_ACTIONS"; case TYPE_GET_RECENT_ACTION_BY_ID: return "GET_RECENT_ACTION_BY_ID"; case TYPE_CHECK_RECOVERY_KEY: return "CHECK_RECOVERY_KEY"; case TYPE_SET_MY_BACKUPS: return "SET_MY_BACKUPS"; case TYPE_EXPORT_SET: return "EXPORT_SET"; case TYPE_PUT_SET: return "PUT_SET"; case TYPE_REMOVE_SET: return "REMOVE_SET"; case TYPE_FETCH_SET: return "FETCH_SET"; case TYPE_PUT_SET_ELEMENTS: return "PUT_SET_ELEMENTS"; case TYPE_PUT_SET_ELEMENT: return "PUT_SET_ELEMENT"; case TYPE_REMOVE_SET_ELEMENT: return "REMOVE_SET_ELEMENT"; case TYPE_REMOVE_SET_ELEMENTS: return "REMOVE_SET_ELEMENTS"; case TYPE_REMOVE_OLD_BACKUP_NODES: return "REMOVE_OLD_BACKUP_NODES"; case TYPE_SET_SYNC_RUNSTATE: return "SET_SYNC_RUNSTATE"; case TYPE_ADD_UPDATE_SCHEDULED_MEETING: return "ADD_SCHEDULED_MEETING"; case TYPE_DEL_SCHEDULED_MEETING: return "DEL_SCHEDULED_MEETING"; case TYPE_FETCH_SCHEDULED_MEETING: return "FETCH_SCHEDULED_MEETING"; case TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES: return "FETCH_SCHEDULED_MEETING_EVENTS"; case TYPE_GET_EXPORTED_SET_ELEMENT: return "GET_EXPORTED_SET_ELEMENT"; case TYPE_OPEN_SHARE_DIALOG: return "OPEN_SHARE_DIALOG"; case TYPE_UPGRADE_SECURITY: return "UPGRADE_SECURITY"; case TYPE_GET_RECOMMENDED_PRO_PLAN: return "GET_RECOMMENDED_PRO_PLAN"; case TYPE_BACKUP_INFO: return "BACKUP_INFO"; case TYPE_BACKUP_REMOVE_MD: return "BACKUP_REMOVE_MD"; case TYPE_AB_TEST_ACTIVE: return "AB_TEST_ACTIVE"; case TYPE_GET_VPN_REGIONS: return "GET_VPN_REGIONS"; case TYPE_GET_VPN_CREDENTIALS: return "GET_VPN_CREDENTIALS"; case TYPE_PUT_VPN_CREDENTIAL: return "PUT_VPN_CREDENTIAL"; case TYPE_DEL_VPN_CREDENTIAL: return "DEL_VPN_CREDENTIAL"; case TYPE_CHECK_VPN_CREDENTIAL: return "CHECK_VPN_CREDENTIAL"; case TYPE_GET_SYNC_STALL_LIST: return "GET_SYNC_STALL_LIST"; case TYPE_FETCH_CREDIT_CARD_INFO: return "FETCH_CREDIT_CARD_INFO"; case TYPE_MOVE_TO_DEBRIS: return "MOVE_TO_DEBRIS"; case TYPE_RING_INDIVIDUAL_IN_CALL: return "RING_INDIVIDUAL_IN_CALL"; case TYPE_CREATE_NODE_TREE: return "CREATE_NODE_TREE"; case TYPE_CREATE_PASSWORD_MANAGER_BASE: return "CREATE_PASSWORD_MANAGER_BASE"; case TYPE_CREATE_PASSWORD_NODE: return "CREATE_PASSWORD_NODE"; case TYPE_UPDATE_PASSWORD_NODE: return "UPDATE_PASSWORD_NODE"; case TYPE_GET_NOTIFICATIONS: return "GET_NOTIFICATIONS"; case TYPE_DEL_ATTR_USER: return "DEL_ATTR_USER"; case TYPE_BACKUP_PAUSE_MD: return "BACKUP_PAUSE_MD"; case TYPE_BACKUP_RESUME_MD: return "BACKUP_RESUME_MD"; case TYPE_IMPORT_PASSWORDS_FROM_FILE: return "IMPORT_PASSWORDS_FROM_FILE"; case TYPE_GET_SUBSCRIPTION_CANCELLATION_DETAILS: return "TYPE_GET_SUBSCRIPTION_CANCELLATION_DETAILS"; case TYPE_GET_DISCOUNT_CODE_INFORMATION: return "TYPE_GET_DISCOUNT_CODE_INFORMATION"; // FUSE requests. case TYPE_ADD_MOUNT: return "TYPE_ADD_MOUNT"; case TYPE_DISABLE_MOUNT: return "TYPE_DISABLE_MOUNT"; case TYPE_ENABLE_MOUNT: return "TYPE_ENABLE_MOUNT"; case TYPE_REMOVE_MOUNT: return "TYPE_REMOVE_MOUNT"; case TYPE_SET_MOUNT_FLAGS: return "TYPE_SET_MOUNT_FLAGS"; case TYPE_GET_ACTIVE_SURVEY_TRIGGER_ACTIONS: return "TYPE_GET_ACTIVE_SURVEY_TRIGGER_ACTIONS"; case TYPE_GET_SURVEY: return "TYPE_GET_SURVEY"; case TYPE_ANSWER_SURVEY: return "TYPE_ANSWER_SURVEY"; case TYPE_CHANGE_SYNC_ROOT: return "TYPE_CHANGE_SYNC_ROOT"; case TYPE_GET_MY_IP: return "TYPE_GET_MY_IP"; case TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES: return "TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES"; case TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES: return "TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES"; case TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS: return "TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS"; case TYPE_RUN_NETWORK_CONNECTIVITY_TEST: return "TYPE_RUN_NETWORK_CONNECTIVITY_TEST"; case TYPE_ADD_SYNC_PREVALIDATION: return "TYPE_ADD_SYNC_PREVALIDATION"; case TYPE_GET_MAX_CONNECTIONS: return "GET_MAX_CONNECTIONS"; } return "UNKNOWN"; } MegaRequestListener *MegaRequestPrivate::getListener() const { return listener; } int MegaRequestPrivate::getTransferTag() const { return transfer; } const char *MegaRequestPrivate::toString() const { return getRequestString(); } const MegaNotificationList* MegaRequestPrivate::getMegaNotifications() const { return mMegaNotifications.get(); } void MegaRequestPrivate::setMegaNotifications(MegaNotificationList* megaNotifications) { mMegaNotifications.reset(megaNotifications); } const MegaNodeTree* MegaRequestPrivate::getMegaNodeTree() const { return mMegaNodeTree.get(); } void MegaRequestPrivate::setMegaNodeTree(MegaNodeTree* megaNodeTree) { mMegaNodeTree.reset(megaNodeTree); } const MegaCancelSubscriptionReasonList* MegaRequestPrivate::getMegaCancelSubscriptionReasons() const { return mMegaCancelSubscriptionReasons.get(); } void MegaRequestPrivate::setMegaCancelSubscriptionReasons( MegaCancelSubscriptionReasonList* cancelReasons) { mMegaCancelSubscriptionReasons.reset(cancelReasons); } MegaDiscountCodeList* MegaRequestPrivate::getMegaDiscountCodeList() const { return mMegaDiscountCodeList.get(); } void MegaRequestPrivate::setMegaDiscountCodes(std::vector<DiscountCode>&& discountCodes) { unique_ptr<MegaDiscountCodeListPrivate> tempList = std::make_unique<MegaDiscountCodeListPrivate>(); for (auto& dc: discountCodes) { tempList->add(MegaDiscountCodePrivate(std::move(dc))); } mMegaDiscountCodeList = std::move(tempList); } const MegaDiscountCodeInfo* MegaRequestPrivate::getMegaDiscountCodeInfo() const { return mMegaDiscountCodeInfo.get(); } void MegaRequestPrivate::setMegaDiscountCodeInfo( std::unique_ptr<MegaDiscountCodeInfo> discountCodeInfo) { mMegaDiscountCodeInfo = std::move(discountCodeInfo); } bool MegaRequestPrivate::causesLocklessRequest(const int type) { // List of request which create lockless commands. switch (type) { case TYPE_GET_DOWNLOAD_URLS: case TYPE_GET_PUBLIC_NODE: case TYPE_IMPORT_LINK: return true; default: return false; } } MegaBannerPrivate::MegaBannerPrivate(BannerDetails&& details): mDetails(std::move(details)) { } MegaBanner* MegaBannerPrivate::copy() const { return new MegaBannerPrivate(*this); } int MegaBannerPrivate::getId() const { return mDetails.id; } const char* MegaBannerPrivate::getTitle() const { return mDetails.title.c_str(); } const char* MegaBannerPrivate::getDescription() const { return mDetails.description.c_str(); } const char* MegaBannerPrivate::getImage() const { return mDetails.image.c_str(); } const char* MegaBannerPrivate::getUrl() const { return mDetails.url.c_str(); } const char* MegaBannerPrivate::getBackgroundImage() const { return mDetails.backgroundImage.c_str(); } const char* MegaBannerPrivate::getImageLocation() const { return mDetails.imageLocation.c_str(); } int MegaBannerPrivate::getVariant() const { return mDetails.variant; } const char* MegaBannerPrivate::getButton() const { return mDetails.button.c_str(); } MegaBannerListPrivate* MegaBannerListPrivate::copy() const { return new MegaBannerListPrivate(*this); } const MegaBanner* MegaBannerListPrivate::get(int i) const { return (i >= 0 && static_cast<size_t>(i) < mVector.size()) ? &(mVector[static_cast<size_t>(i)]) : nullptr; } int MegaBannerListPrivate::size() const { return int(mVector.size()); } void MegaBannerListPrivate::add(MegaBannerPrivate&& banner) { mVector.emplace_back(std::move(banner)); } MegaStringMapPrivate::MegaStringMapPrivate() { } MegaStringMapPrivate::MegaStringMapPrivate(const string_map *map, bool toBase64) { strMap.insert(map->begin(),map->end()); if (toBase64) { char* buf; string_map::iterator it; for (it = strMap.begin(); it != strMap.end(); it++) { buf = new char[it->second.length() * 4 / 3 + 4]; Base64::btoa((const byte*)it->second.data(), it->second.length(), buf); it->second.assign(buf); delete [] buf; } } } MegaStringMapPrivate::~MegaStringMapPrivate() { } MegaStringMap *MegaStringMapPrivate::copy() const { return new MegaStringMapPrivate(this); } const char *MegaStringMapPrivate::get(const char *key) const { string_map::const_iterator it = strMap.find(key); if (it == strMap.end()) { return NULL; } return it->second.data(); } MegaStringList *MegaStringMapPrivate::getKeys() const { string_vector keys; for (auto& it : strMap) { keys.push_back(it.first); } return new MegaStringListPrivate(std::move(keys)); } void MegaStringMapPrivate::set(const char *key, const char *value) { strMap[key] = value; } int MegaStringMapPrivate::size() const { return int(strMap.size()); } const string_map *MegaStringMapPrivate::getMap() const { return &strMap; } MegaStringMapPrivate::MegaStringMapPrivate(const MegaStringMapPrivate *megaStringMap) { MegaStringList *keys = megaStringMap->getKeys(); const char *key = NULL; const char *value = NULL; for (int i=0; i < keys->size(); i++) { key = keys->get(i); value = megaStringMap->get(key); strMap[key] = value; } delete keys; } MegaIntegerMapPrivate::MegaIntegerMapPrivate() { } MegaIntegerMapPrivate::MegaIntegerMapPrivate(const MegaIntegerMapPrivate& megaIntegerMap) :mIntegerMap(megaIntegerMap.getMap() ? *megaIntegerMap.getMap() : integer_map()) { } MegaIntegerMapPrivate::MegaIntegerMapPrivate(const std::multimap<int8_t, int8_t>& bytesMap) { for (const auto& element: bytesMap) { mIntegerMap.emplace(static_cast<int64_t>(element.first), static_cast<int64_t>(element.second)); } } MegaIntegerMapPrivate::MegaIntegerMapPrivate(const std::multimap<int64_t, int64_t>& integerMap) :mIntegerMap(integerMap) { } MegaIntegerMapPrivate::~MegaIntegerMapPrivate() { } MegaSmallIntMap* MegaIntegerMapPrivate::toByteMap() const { MegaSmallIntMap* byteMap = new MegaSmallIntMap(); for (const auto& pair: mIntegerMap) { byteMap->emplace(static_cast<int8_t>(pair.first), static_cast<int8_t>(pair.second)); } return byteMap; } MegaIntegerMap* MegaIntegerMapPrivate::copy() const { return new MegaIntegerMapPrivate(*this); } MegaIntegerList* MegaIntegerMapPrivate::getKeys() const { vector<int64_t> keys; for (auto& it : mIntegerMap) { keys.push_back(it.first); } return new MegaIntegerListPrivate(keys); } int64_t MegaIntegerMapPrivate::size() const { return static_cast<int64_t>(mIntegerMap.size()); } MegaIntegerList* MegaIntegerMapPrivate::get(int64_t key) const { vector<int64_t> values; auto range = mIntegerMap.equal_range(key); for (auto i = range.first; i != range.second; ++i) { values.emplace_back(i->second); } return new MegaIntegerListPrivate(values); } void MegaIntegerMapPrivate::set(int64_t key, int64_t value) { mIntegerMap.emplace(key, value); } const integer_map* MegaIntegerMapPrivate::getMap() const { return &mIntegerMap; } MegaStringListPrivate* MegaStringIntegerMapPrivate::getKeys() const { MegaStringListPrivate* keys = new MegaStringListPrivate(); for (const auto& p : mStorage) { keys->add(p.first.c_str()); } return keys; } MegaIntegerListPrivate* MegaStringIntegerMapPrivate::get(const char* key) const { if (!key) { return nullptr; } auto it = mStorage.find(key); if (it == mStorage.end()) { return nullptr; } MegaIntegerListPrivate* intList = new MegaIntegerListPrivate(); intList->add(it->second); return intList; } void MegaStringIntegerMapPrivate::set(const char* key, int64_t value) { assert(key); if (key) { mStorage[key] = value; } } void MegaStringIntegerMapPrivate::set(const string& key, int64_t value) { mStorage[key] = value; } MegaStringListPrivate::MegaStringListPrivate(string_vector&& v) : mList(std::move(v)) { } MegaStringListPrivate::MegaStringListPrivate(const string_vector& v): mList(v) {} MegaStringList *MegaStringListPrivate::copy() const { return new MegaStringListPrivate(*this); } const char *MegaStringListPrivate::get(int i) const { if((i < 0) || (static_cast<size_t>(i) >= mList.size())) return nullptr; return mList[static_cast<size_t>(i)].c_str(); } int MegaStringListPrivate::size() const { return int(mList.size()); } void MegaStringListPrivate::add(const char *value) { if (value) { mList.push_back(value); } } const string_vector& MegaStringListPrivate::getVector() const { return mList; } bool operator==(const MegaStringList& lhs, const MegaStringList& rhs) { if (lhs.size() != rhs.size()) { return false; } for (int i = 0; i < lhs.size(); ++i) { if (strcmp(lhs.get(i), rhs.get(i)) != 0) { return false; } } return true; } MegaStringListMap* MegaStringListMapPrivate::copy() const { auto map = new MegaStringListMapPrivate; for (const auto& pair : mMap) { map->set(pair.first.get(), pair.second->copy()); } return map; } const MegaStringList* MegaStringListMapPrivate::get(const char* key) const { auto key_ptr = std::unique_ptr<const char[]>{key}; auto iter = mMap.find(key_ptr); key_ptr.release(); if (iter != mMap.end()) { return iter->second.get(); } return nullptr; } MegaStringList *MegaStringListMapPrivate::getKeys() const { string_vector list; for (const auto& pair : mMap) { list.push_back(pair.first.get()); } return new MegaStringListPrivate(std::move(list)); } void MegaStringListMapPrivate::set(const char* key, const MegaStringList* value) { std::unique_ptr<const char[]> key_ptr{MegaApi::strdup(key)}; mMap[std::move(key_ptr)] = std::unique_ptr<const MegaStringList>{value}; } int MegaStringListMapPrivate::size() const { return static_cast<int>(mMap.size()); } bool MegaStringListMapPrivate::Compare::operator()(const std::unique_ptr<const char[]>& rhs, const std::unique_ptr<const char[]>& lhs) const { return strcmp(rhs.get(), lhs.get()) < 0; } MegaStringTable* MegaStringTablePrivate::copy() const { auto table = new MegaStringTablePrivate; for (const auto& value : mTable) { table->append(value->copy()); } return table; } void MegaStringTablePrivate::append(const MegaStringList* value) { mTable.emplace_back(value); } const MegaStringList* MegaStringTablePrivate::get(int i) const { if (i >= 0 && i < size()) { return mTable[static_cast<size_t>(i)].get(); } return nullptr; } int MegaStringTablePrivate::size() const { return static_cast<int>(mTable.size()); } MegaNodeListPrivate::MegaNodeListPrivate() { list = NULL; s = 0; } MegaNodeListPrivate::MegaNodeListPrivate(Node** newlist, int size) { list = NULL; s = size; if(!size) return; list = new MegaNode*[static_cast<size_t>(size)]; for(int i=0; i<size; i++) list[i] = MegaNodePrivate::fromNode(newlist[i]); } MegaNodeListPrivate::MegaNodeListPrivate(const MegaNodeListPrivate *nodeList, bool copyChildren) { s = nodeList->size(); if (!s) { list = NULL; return; } list = new MegaNode*[static_cast<size_t>(s)]; for (int i = 0; i<s; i++) { MegaNode *node = nodeList->get(i); MegaNodePrivate *nodePrivate = new MegaNodePrivate(node); MegaNodeListPrivate *children = dynamic_cast<MegaNodeListPrivate *>(node->getChildren()); if (children && copyChildren) { nodePrivate->setChildren(new MegaNodeListPrivate(children, true)); } list[i] = nodePrivate; } } MegaNodeListPrivate::MegaNodeListPrivate(sharedNode_vector& v) { list = NULL; s = static_cast<int>(v.size()); if (!s) return; list = new MegaNode*[static_cast<size_t>(s)]; for (int i = 0; i < s; i++) list[i] = MegaNodePrivate::fromNode(v[static_cast<size_t>(i)].get()); } MegaNodeListPrivate::MegaNodeListPrivate(sharedNode_list& l) { list = NULL; s = static_cast<int>(l.size()); if (!s) return; list = new MegaNode*[static_cast<size_t>(s)]; int i = 0; for (auto& node : l) { list[i] = MegaNodePrivate::fromNode(node.get()); i++; } } MegaNodeListPrivate::~MegaNodeListPrivate() { if(!list) return; for(int i=0; i<s; i++) delete list[i]; delete [] list; } MegaNodeList *MegaNodeListPrivate::copy() const { return new MegaNodeListPrivate(this); } MegaNode *MegaNodeListPrivate::get(int i) const { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaNodeListPrivate::size() const { return s; } void MegaNodeListPrivate::addNode(std::unique_ptr<MegaNode> node) { MegaNode** copyList = list; s = s + 1; list = new MegaNode*[static_cast<size_t>(s)]; for (int i = 0; i < s - 1; ++i) { list[i] = copyList[i]; } list[s - 1] = node.release(); if (copyList != NULL) { delete [] copyList; } } void MegaNodeListPrivate::addNode(MegaNode *node) { MegaNode** copyList = list; s = s + 1; list = new MegaNode*[static_cast<size_t>(s)]; for (int i = 0; i < s - 1; ++i) { list[i] = copyList[i]; } list[s - 1] = node->copy(); if (copyList != NULL) { delete [] copyList; } } MegaUserListPrivate::MegaUserListPrivate() { list = NULL; s = 0; } MegaUserListPrivate::MegaUserListPrivate(User** newlist, int size) { list = NULL; s = size; if(!size) return; list = new MegaUser*[static_cast<size_t>(size)]; for(int i=0; i<size; i++) list[i] = MegaUserPrivate::fromUser(newlist[i]); } MegaUserListPrivate::MegaUserListPrivate(MegaUserListPrivate *userList) { s = userList->size(); if (!s) { list = NULL; return; } list = new MegaUser*[static_cast<size_t>(s)]; for (int i = 0; i<s; i++) list[i] = new MegaUserPrivate(userList->get(i)); } MegaUserListPrivate::~MegaUserListPrivate() { if(!list) return; for(int i=0; i<s; i++) delete list[i]; delete [] list; } MegaUserList *MegaUserListPrivate::copy() { return new MegaUserListPrivate(this); } MegaUser *MegaUserListPrivate::get(int i) { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaUserListPrivate::size() { return s; } MegaUserAlertListPrivate::MegaUserAlertListPrivate() { list = NULL; s = 0; } MegaUserAlertListPrivate::MegaUserAlertListPrivate(UserAlert::Base** newlist, int size, MegaClient* mc) { list = NULL; s = size; if (!size) return; list = new MegaUserAlert*[static_cast<size_t>(size)]; for (int i = 0; i < size; i++) { list[i] = new MegaUserAlertPrivate(newlist[i], mc); } } MegaUserAlertListPrivate::MegaUserAlertListPrivate(const MegaUserAlertListPrivate &userList) { s = userList.size(); list = s ? new MegaUserAlert*[static_cast<size_t>(s)] : NULL; for (int i = 0; i < s; ++i) { list[i] = userList.get(i)->copy(); } } MegaUserAlertListPrivate::~MegaUserAlertListPrivate() { for (int i = 0; i < s; i++) { delete list[i]; } delete[] list; } MegaUserAlertList *MegaUserAlertListPrivate::copy() const { return new MegaUserAlertListPrivate(*this); } MegaUserAlert *MegaUserAlertListPrivate::get(int i) const { if (!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaUserAlertListPrivate::size() const { return s; } void MegaUserAlertListPrivate::clear() { delete[] list; s = 0; list = nullptr; } MegaRecentActionBucketPrivate::MegaRecentActionBucketPrivate(recentaction&& ra) { mData.timestamp = ra.time; mData.meta = std::move(ra.meta); mData.id = std::move(ra.id); mData.nodes = new MegaNodeListPrivate(ra.nodes); } MegaRecentActionBucketPrivate::MegaRecentActionBucketPrivate(const BucketData& data) { mData.timestamp = data.timestamp; mData.meta = data.meta; mData.id = data.id; mData.nodes = data.nodes ? data.nodes->copy() : nullptr; } MegaRecentActionBucketPrivate::~MegaRecentActionBucketPrivate() { delete mData.nodes; } MegaRecentActionBucket *MegaRecentActionBucketPrivate::copy() const { return new MegaRecentActionBucketPrivate(mData); } int64_t MegaRecentActionBucketPrivate::getTimestamp() const { return mData.timestamp; } const char* MegaRecentActionBucketPrivate::getUserEmail() const { return mData.meta.userEmail.c_str(); } MegaHandle MegaRecentActionBucketPrivate::getParentHandle() const { return mData.meta.parent; } bool MegaRecentActionBucketPrivate::isUpdate() const { return mData.meta.updated; } bool MegaRecentActionBucketPrivate::isMedia() const { return mData.meta.media; } const char* MegaRecentActionBucketPrivate::getId() const { return mData.id.c_str(); } const MegaNodeList* MegaRecentActionBucketPrivate::getNodes() const { return mData.nodes; } MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate() { list = NULL; s = 0; } MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate(recentactions_vector& v) { list = NULL; s = static_cast<int>(v.size()); if (!s) return; list = new MegaRecentActionBucketPrivate*[static_cast<size_t>(s)]; for (int i = 0; i < s; i++) { list[i] = new MegaRecentActionBucketPrivate(std::move(v[static_cast<size_t>(i)])); } } MegaRecentActionBucketListPrivate::MegaRecentActionBucketListPrivate(const MegaRecentActionBucketListPrivate &o) { s = o.size(); list = s ? new MegaRecentActionBucketPrivate*[static_cast<size_t>(s)] : NULL; for (int i = 0; i < s; ++i) { list[i] = (MegaRecentActionBucketPrivate*)o.get(i)->copy(); } } MegaRecentActionBucketListPrivate::~MegaRecentActionBucketListPrivate() { for (int i = 0; i < s; i++) { delete list[i]; } delete[] list; } MegaRecentActionBucketList *MegaRecentActionBucketListPrivate::copy() const { return new MegaRecentActionBucketListPrivate(*this); } MegaRecentActionBucket *MegaRecentActionBucketListPrivate::get(int i) const { if (!list || (i < 0) || (i >= s)) { return NULL; } return list[i]; } int MegaRecentActionBucketListPrivate::size() const { return s; } MegaShareListPrivate::MegaShareListPrivate() { list = NULL; s = 0; } MegaShareListPrivate::MegaShareListPrivate(const std::vector<impl::ShareData>& shares) { // Convinence const auto size = shares.size(); // Default list = nullptr; s = static_cast<int>(size); // Empty if (!size) { return; } // Construct list if it is not empty list = new MegaShare*[size]; for (size_t i = 0; i < size; i++) { list[i] = MegaSharePrivate::fromShare(shares[i]); } } MegaShareListPrivate::~MegaShareListPrivate() { if(!list) return; for(int i=0; i<s; i++) delete list[i]; delete [] list; } MegaShare *MegaShareListPrivate::get(int i) { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaShareListPrivate::size() { return s; } MegaTransferListPrivate::MegaTransferListPrivate() { list = NULL; s = 0; } MegaTransferListPrivate::MegaTransferListPrivate(MegaTransfer** newlist, int size) { list = NULL; s = size; if(!size) return; list = new MegaTransfer*[static_cast<size_t>(size)]; for(int i=0; i<size; i++) list[i] = newlist[i]->copy(); } MegaTransferListPrivate::~MegaTransferListPrivate() { if(!list) return; for(int i=0; i < s; i++) delete list[i]; delete [] list; } MegaTransfer *MegaTransferListPrivate::get(int i) { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaTransferListPrivate::size() { return s; } MegaContactRequestListPrivate::MegaContactRequestListPrivate() { list = NULL; s = 0; } MegaContactRequestListPrivate::MegaContactRequestListPrivate(PendingContactRequest **newlist, int size) { list = NULL; s = size; if(!size) return; list = new MegaContactRequest*[static_cast<size_t>(size)]; for(int i=0; i<size; i++) list[i] = new MegaContactRequestPrivate(newlist[i]); } MegaContactRequestListPrivate::~MegaContactRequestListPrivate() { if(!list) return; for(int i=0; i < s; i++) delete list[i]; delete [] list; } MegaContactRequestList* MegaContactRequestListPrivate::copy() const { return new MegaContactRequestListPrivate(this); } const MegaContactRequest* MegaContactRequestListPrivate::get(int i) const { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaContactRequestListPrivate::size() const { return s; } MegaContactRequestListPrivate::MegaContactRequestListPrivate( const MegaContactRequestListPrivate* requestList) { s = requestList->size(); if (!s) { list = NULL; return; } list = new MegaContactRequest*[static_cast<size_t>(s)]; for (int i = 0; i < s; i++) list[i] = new MegaContactRequestPrivate(requestList->get(i)); } MegaFile::MegaFile() : File() { megaTransfer = NULL; } void MegaFile::setTransfer(MegaTransferPrivate* newTransfer) { megaTransfer = newTransfer; } MegaTransferPrivate *MegaFile::getTransfer() { return megaTransfer; } bool MegaFile::serialize(string *d) const { if (!megaTransfer) { return false; } if (!File::serialize(d)) { return false; } megaTransfer->dbid = dbid; if (!megaTransfer->serialize(d)) { return false; } d->append("\0\0\0\0\0\0\0", 8); return true; } MegaFile *MegaFile::unserialize(string *d) { File *file = File::unserialize(d); if (!file) { LOG_err << "Error unserializing MegaFile: Unable to unserialize File"; return NULL; } MegaFile *megaFile = new MegaFile(); *(File *)megaFile = *(File *)file; file->chatauth = NULL; delete file; MegaTransferPrivate *transfer = MegaTransferPrivate::unserialize(d); if (!transfer) { delete megaFile; return NULL; } else { transfer->dbid = megaFile->dbid; } const char* ptr = d->data(); const char* end = ptr + d->size(); if (ptr + 8 > end) { LOG_err << "MegaFile unserialization failed - data too short"; delete megaFile; delete transfer; return NULL; } if (memcmp(ptr, "\0\0\0\0\0\0\0", 8)) { LOG_err << "MegaFile unserialization failed - invalid version"; delete megaFile; delete transfer; return NULL; } ptr += 8; d->erase(0, static_cast<size_t>(ptr - d->data())); transfer->setSourceFileTemporary(megaFile->temporaryfile); megaFile->setTransfer(transfer); return megaFile; } MegaFileGet::MegaFileGet(MegaClient *client, Node *n, const LocalPath& dstPath, FileSystemType fsType, CollisionResolution collisionResolution) : MegaFile() { setCollisionResolution(collisionResolution); h = n->nodeHandle(); *(FileFingerprint*)this = *n; name = n->displayname(); auto lpName = LocalPath::fromRelativeName(name, *client->fsaccess, fsType); LocalPath finalPath; if(!dstPath.empty()) { if (dstPath.endsInSeparator()) { finalPath = dstPath; finalPath.appendWithSeparator(lpName, true); } else finalPath = dstPath; } else finalPath = lpName; size = n->size; mtime = n->mtime; if(n->nodekey().size()>=sizeof(filekey)) memcpy(filekey,n->nodekey().data(),sizeof filekey); setLocalname(finalPath); hprivate = true; hforeign = false; } MegaFileGet::MegaFileGet(MegaClient *client, MegaNode *n, const LocalPath& dstPath, CollisionResolution collisionResolution) : MegaFile() { setCollisionResolution(collisionResolution); h.set6byte(n->getHandle()); FileSystemType fsType = client->fsaccess->getlocalfstype(dstPath); name = n->getName(); auto lpName = LocalPath::fromRelativeName(name, *client->fsaccess, fsType); LocalPath finalPath; if(!dstPath.empty()) { if (dstPath.endsInSeparator()) { finalPath = dstPath; finalPath.appendWithSeparator(lpName, true); } else finalPath = dstPath; } else finalPath = lpName; const char *fingerprint = n->getFingerprint(); if (fingerprint) { unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (fp) { *(FileFingerprint *)this = *(FileFingerprint *)fp.get(); } } size = n->getSize(); mtime = n->getModificationTime(); if(n->getNodeKey()->size()>=sizeof(filekey)) memcpy(filekey,n->getNodeKey()->data(),sizeof filekey); setLocalname(finalPath); hprivate = !n->isPublic(); hforeign = n->isForeign(); MegaNodePrivate* np = dynamic_cast<MegaNodePrivate*>(n); assert(np); if (np->getPrivateAuth()->size()) { privauth = *np->getPrivateAuth(); } if (np->getPublicAuth()->size()) { pubauth = *np->getPublicAuth(); } chatauth = np->getChatAuth() ? MegaApi::strdup(np->getChatAuth()) : NULL; } bool MegaFileGet::serialize(string *d) const { if (!MegaFile::serialize(d)) { return false; } CacheableWriter cw(*d); cw.serializeexpansionflags(mUndelete); return true; } MegaFileGet *MegaFileGet::unserialize(string *d) { MegaFile *file = MegaFile::unserialize(d); if (!file) { LOG_err << "Error unserializing MegaFileGet: Unable to unserialize MegaFile"; return NULL; } const char* ptr = d->data(); const char* end = ptr + d->size(); if (ptr + 8 > end) { LOG_err << "MegaFileGet unserialization failed - data too short"; delete file; return NULL; } byte expansions[8]; CacheableReader cr(*d); if (!cr.unserializeexpansionflags(expansions, 1)) { LOG_err << "MegaFileGet unserialization failed - invalid version"; delete file; return NULL; } MegaFileGet *megaFile = new MegaFileGet(); *(MegaFile *)megaFile = *(MegaFile *)file; megaFile->setUndelete(expansions[0] > 0); file->chatauth = NULL; delete file; return megaFile; } void MegaFileGet::prepare(FileSystemAccess&) { if (transfer->localfilename.empty()) { transfer->localfilename = getLocalname(); assert(transfer->localfilename.isAbsolute() || transfer->localfilename.isURI()); transfer->localfilename.changeLeaf(LocalPath::tmpNameLocal()); } } void MegaFileGet::updatelocalname() { #ifdef _WIN32 RemoveHiddenFileAttribute(transfer->localfilename); #endif } void MegaFileGet::progress() { #ifdef _WIN32 if(transfer->slot && !transfer->slot->progressreported) { AddHiddenFileAttribute(transfer->localfilename); } #endif } void MegaFileGet::completed(Transfer*, putsource_t /*source*/) { delete this; } void MegaFileGet::terminated(error) { delete this; } MegaFilePut::MegaFilePut(MegaClient *, LocalPath clocalname, string *filename, NodeHandle ch, const char* ctargetuser, int64_t mtime, bool isSourceTemporary, std::shared_ptr<Node> pvNode) : MegaFile() { // full local path setLocalname(clocalname); // target parent node h = ch; // target user targetuser = ctargetuser; // new node name name = *filename; // If the file's time is to be used, this is MegaApi::INVALID_CUSTOM_MOD_TIME customMtime = mtime; temporaryfile = isSourceTemporary; previousNode = pvNode; } bool MegaFilePut::serialize(string *d) const { if (!MegaFile::serialize(d)) { return false; } d->append((char*)&customMtime, sizeof(customMtime)); d->append("\0\0\0\0\0\0\0", 8); return true; } MegaFilePut *MegaFilePut::unserialize(string *d) { MegaFile *file = MegaFile::unserialize(d); if (!file) { LOG_err << "Error unserializing MegaFilePut: Unable to unserialize MegaFile"; return NULL; } const char* ptr = d->data(); const char* end = ptr + d->size(); if (ptr + sizeof(int64_t) + 8 > end) { LOG_err << "MegaFilePut unserialization failed - data too short"; delete file; return NULL; } int64_t customMtime = MemAccess::get<int64_t>(ptr); ptr += sizeof(customMtime); if (memcmp(ptr, "\0\0\0\0\0\0\0", 8)) { LOG_err << "MegaFilePut unserialization failed - invalid version"; delete file; return NULL; } ptr += 8; if (ptr != end) { LOG_err << "MegaFilePut unserialization failed - wrong size"; delete file; return NULL; } MegaFilePut *megaFile = new MegaFilePut(); *(MegaFile *)megaFile = *(MegaFile *)file; file->chatauth = NULL; delete file; megaFile->customMtime = customMtime; return megaFile; } void MegaFilePut::completed(Transfer* t, putsource_t source) { assert(!transfer || t == transfer); assert(source == PUTNODES_APP); assert(t->type == PUT); // allow for putnodes with a different mtime to the actual file sendPutnodesOfUpload(t->client, t->uploadhandle, "", *t->ultoken, t->filekey, source, NodeHandle(), nullptr, customMtime == MegaApi::INVALID_CUSTOM_MOD_TIME ? nullptr : &customMtime, false, getPitag()); delete this; } void MegaFilePut::terminated(error) { delete this; } void MegaSearchFilterPrivate::byName(const char* searchString) { mNameFilter = searchString ? searchString : string(); } void MegaSearchFilterPrivate::byNodeType(int nodeType) { assert(MegaNode::TYPE_UNKNOWN <= nodeType && nodeType <= MegaNode::TYPE_FOLDER); if (nodeType < MegaNode::TYPE_UNKNOWN || MegaNode::TYPE_FOLDER < nodeType) { LOG_warn << "Invalid nodeType for SearchFilter: " << nodeType << ". Ignored."; return; } mNodeType = nodeType; } void MegaSearchFilterPrivate::byCategory(int mimeType) { assert(MegaApi::FILE_TYPE_DEFAULT <= mimeType && mimeType <= MegaApi::FILE_TYPE_LAST); if (mimeType < MegaApi::FILE_TYPE_DEFAULT || MegaApi::FILE_TYPE_LAST < mimeType) { LOG_warn << "Invalid mimeType for SearchFilter: " << mimeType << ". Ignored."; return; } mMimeCategory = mimeType; } void MegaSearchFilterPrivate::byFavourite(int boolFilterOption) { mFavouriteFilterOption = validateBoolFilterOption(boolFilterOption); } void MegaSearchFilterPrivate::bySensitivity(int boolFilterOption) { mExcludeSensitive = validateBoolFilterOption(boolFilterOption); } void MegaSearchFilterPrivate::byLocationHandle(MegaHandle ancestorHandle) { mLocationHandle = ancestorHandle; mLocationType = MegaApi::SEARCH_TARGET_ALL; } void MegaSearchFilterPrivate::byLocation(int locationType) { assert(MegaApi::SEARCH_TARGET_INSHARE <= locationType && locationType <= MegaApi::SEARCH_TARGET_ALL); if (locationType < MegaApi::SEARCH_TARGET_INSHARE || MegaApi::SEARCH_TARGET_ALL < locationType) { LOG_warn << "Invalid locationType for SearchFilter: " << locationType << ". Ignored."; return; } mLocationType = locationType; mLocationHandle = INVALID_HANDLE; } void MegaSearchFilterPrivate::byCreationTime(int64_t lowerLimit, int64_t upperLimit) { mCreationLowerLimit = lowerLimit; mCreationUpperLimit = upperLimit; } void MegaSearchFilterPrivate::byModificationTime(int64_t lowerLimit, int64_t upperLimit) { mModificationLowerLimit = lowerLimit; mModificationUpperLimit = upperLimit; } void MegaSearchFilterPrivate::byDescription(const char* searchString) { mDescriptionFilter = searchString ? searchString : string(); } void MegaSearchFilterPrivate::byTag(const char* searchString) { mTag = searchString ? searchString : string(); } void MegaSearchFilterPrivate::useAndForTextQuery(bool useAnd) { mUseAndForTextQuery = useAnd; } MegaSearchFilterPrivate* MegaSearchFilterPrivate::copy() const { return new MegaSearchFilterPrivate(*this); } int MegaSearchFilterPrivate::validateBoolFilterOption(const int value) { switch (value) { case MegaSearchFilter::BOOL_FILTER_DISABLED: case MegaSearchFilter::BOOL_FILTER_ONLY_TRUE: case MegaSearchFilter::BOOL_FILTER_ONLY_FALSE: return value; default: LOG_warn << "Invalid value for a boolean filtering option: " << value; return MegaSearchFilter::BOOL_FILTER_DISABLED; } } std::unique_ptr<MegaGfxProviderPrivate> MegaGfxProviderPrivate::createIsolatedInstance([[maybe_unused]] const char* endpointName, [[maybe_unused]] const char* executable, [[maybe_unused]] unsigned int keepAliveInSeconds, [[maybe_unused]] const MegaStringList* extraArgs) { #ifdef ENABLE_ISOLATED_GFX if (!endpointName || !executable) return nullptr; auto args = dynamic_cast<const MegaStringListPrivate*>(extraArgs); GfxIsolatedProcess::Params params{std::string{endpointName}, std::string{executable}, std::chrono::seconds{keepAliveInSeconds}, args ? args->getVector() : string_vector{}}; auto provider = GfxProviderIsolatedProcess::create(params); return std::make_unique<MegaGfxProviderPrivate>(std::move(provider)); #else return nullptr; #endif } std::unique_ptr<MegaGfxProviderPrivate> MegaGfxProviderPrivate::createExternalInstance(MegaGfxProcessor* processor) { return std::make_unique<MegaGfxProviderPrivate>(std::make_unique<GfxProviderExternal>(processor)); } std::unique_ptr<MegaGfxProviderPrivate> MegaGfxProviderPrivate::createInternalInstance() { return std::make_unique<MegaGfxProviderPrivate>(IGfxProvider::createInternalGfxProvider()); } //Entry point for the blocking thread void *MegaApiImpl::threadEntryPoint(void *param) { #ifndef _WIN32 struct sigaction noaction; memset(&noaction, 0, sizeof(noaction)); noaction.sa_handler = SIG_IGN; ::sigaction(SIGPIPE, &noaction, 0); #endif MegaApiImpl *megaApiImpl = (MegaApiImpl *)param; megaApiImpl->loop(); return 0; } MegaTransferPrivate *MegaApiImpl::getMegaTransferPrivate(int tag) { map<int, MegaTransferPrivate *>::iterator it = transferMap.find(tag); if (it == transferMap.end()) { return NULL; } return it->second; } MegaApiImpl::MegaApiImpl(MegaApi* api, MegaGfxProcessor* processor, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType) { init(api, createGfxProc(processor), basePath, userAgent, workerThreadCount, clientType); } MegaApiImpl::MegaApiImpl(MegaApi* api, MegaGfxProvider* provider, const char* basePath, const char* userAgent, unsigned workerThreadCount, int clientType) { auto p = dynamic_cast<MegaGfxProviderPrivate*>(provider); auto iProvider = p ? p->releaseProvider() : nullptr; auto gfxproc = iProvider ? std::make_unique<GfxProc>(std::move(iProvider)) : nullptr; init(api, std::move(gfxproc), basePath, userAgent, workerThreadCount, clientType); } void MegaApiImpl::init(MegaApi* publicApi, std::unique_ptr<GfxProc> gfxproc, const char* newBasePath, const char* userAgent, unsigned clientWorkerThreadCount, int clientType) { api = publicApi; maxRetries = 7; currentTransfer = NULL; client = NULL; waitingRequest = RETRY_NONE; notificationNumber = 0; #ifdef HAVE_LIBUV httpServer = NULL; httpServerMaxBufferSize = 0; httpServerMaxOutputSize = 0; httpServerEnableFiles = true; httpServerEnableFolders = false; httpServerOfflineAttributeEnabled = false; httpServerRestrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS; httpServerSubtitlesSupportEnabled = false; ftpServer = NULL; ftpServerMaxBufferSize = 0; ftpServerMaxOutputSize = 0; ftpServerRestrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS; const char *uvversion = uv_version_string(); if (uvversion) { LOG_debug << "libuv version: " <<uvversion; } #endif mTimezones = NULL; httpio = new MegaHttpIO(); waiter.reset(new MegaWaiter()); fsAccess = mega::createFSA(); fingerprintingFsAccess = mega::createFSA(); if (newBasePath) { basePath = newBasePath; } else { basePath = std::filesystem::current_path().string(); } dbAccess = new MegaDbAccess(LocalPath::fromAbsolutePath(basePath)); gfxAccess = gfxproc.release(); if (gfxAccess) { gfxAccess->startProcessingThread(); } if(!userAgent) { userAgent = ""; } nocache = false; client = new MegaClient(this, waiter, httpio, dbAccess, gfxAccess, userAgent, clientWorkerThreadCount, MegaClient::ClientType(clientType)); #if defined(_WIN32) httpio->unlock(); #endif //Start blocking thread threadExit = 0; thread = std::thread([this](){ threadEntryPoint(this); } ); threadId = thread.get_id(); } MegaApiImpl::~MegaApiImpl() { // the fireOnFinish won't be called for this one, so delete it ourselves auto shutdownRequest = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_DELETE); requestQueue.push(shutdownRequest.get()); waiter->notify(); thread.join(); assert(client == nullptr); delete mTimezones; assert(requestMap.empty()); assert(backupsMap.empty()); assert(transferMap.empty()); delete gfxAccess; #ifndef DONT_RELEASE_HTTPIO delete httpio; #endif } MegaApiImpl* MegaApiImpl::ImplOf(MegaApi* api) { // Sometimes we need to be able to reference the MegaApiImpl from objects other than MegaApi (without giving clients access to the pImpl pointer) return api->pImpl; } void MegaApiImpl::loggedInStateChanged(sessiontype_t s, handle me, const string& email) { std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex); mLastReceivedLoggedInState = s; mLastReceivedLoggedInMeHandle = me; mLastReceivedLoggedInMyEmail = email; } int MegaApiImpl::isLoggedIn() { std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex); return mLastReceivedLoggedInState; } bool MegaApiImpl::isEphemeralPlusPlus() { return isLoggedIn() == EPHEMERALACCOUNTPLUSPLUS; } char* MegaApiImpl::getMyEmail() { std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex); if (mLastReceivedLoggedInState == NOTLOGGEDIN || mLastReceivedLoggedInMyEmail.empty()) { return nullptr; } return MegaApi::strdup(mLastReceivedLoggedInMyEmail.c_str()); } int64_t MegaApiImpl::getAccountCreationTs() { return client->accountsince; } char *MegaApiImpl::getMyUserHandle() { std::lock_guard<std::mutex> g(mLastRecievedLoggedMeMutex); if (mLastReceivedLoggedInState == NOTLOGGEDIN || ISUNDEF(mLastReceivedLoggedInMeHandle)) { return NULL; } char buf[12]; Base64::btoa((const byte*)&mLastReceivedLoggedInMeHandle, MegaClient::USERHANDLE, buf); char *result = MegaApi::strdup(buf); return result; } MegaHandle MegaApiImpl::getMyUserHandleBinary() { SdkMutexGuard g(sdkMutex); return client->me; } MegaUser *MegaApiImpl::getMyUser() { SdkMutexGuard g(sdkMutex); return MegaUserPrivate::fromUser(client->finduser(client->me)); } bool MegaApiImpl::isAchievementsEnabled() { assert(!isBusinessAccount() || !client->achievements_enabled); return client->achievements_enabled; } bool MegaApiImpl::isProFlexiAccount() { return client->isProFlexi(); } bool MegaApiImpl::isBusinessAccount() { return client->mBizStatus != BIZ_STATUS_INACTIVE && client->mBizStatus != BIZ_STATUS_UNKNOWN; } bool MegaApiImpl::isMasterBusinessAccount() { return client->mBizMode == BIZ_MODE_MASTER; } bool MegaApiImpl::isBusinessAccountActive() { return getBusinessStatus() >= BIZ_STATUS_ACTIVE; } int MegaApiImpl::getBusinessStatus() { // Prevent return apps unknown status return (client->mBizStatus == BIZ_STATUS_UNKNOWN) ? BIZ_STATUS_INACTIVE : client->mBizStatus; } int64_t MegaApiImpl::getOverquotaDeadlineTs() { return client->mOverquotaDeadlineTs; } MegaIntegerList *MegaApiImpl::getOverquotaWarningsTs() { return new MegaIntegerListPrivate(client->mOverquotaWarningTs); } bool MegaApiImpl::checkPassword(const char *password) { SdkMutexGuard g(sdkMutex); return client->validatepwdlocally(password); } char *MegaApiImpl::getMyCredentials() { SdkMutexGuard g(sdkMutex); if (ISUNDEF(client->me)) { return NULL; } string result; if (client->mEd255Key) { result = AuthRing::fingerprint( string((const char*)client->mEd255Key->pubKey, EdDSA::PUBLIC_KEY_LENGTH), true); } return result.size() ? MegaApi::strdup(result.c_str()) : nullptr; } void MegaApiImpl::getUserCredentials(MegaUser *user, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(ATTR_ED25519_PUBK); request->setFlag(true); if(user) { request->setEmail(user->getEmail()); } request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::areCredentialsVerified(MegaUser *user) { SdkMutexGuard g(sdkMutex); return user ? client->areCredentialsVerified(user->getHandle()) : false; } void MegaApiImpl::verifyCredentials(MegaUser *user, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_VERIFY_CREDENTIALS, listener); if(user) { request->setNodeHandle(user->getHandle()); } request->performRequest = [this, request]() { return performRequest_verifyCredentials(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resetCredentials(MegaUser *user, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_VERIFY_CREDENTIALS, listener); if(user) { request->setNodeHandle(user->getHandle()); } request->setFlag(true); request->performRequest = [this, request]() { return performRequest_verifyCredentials(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setLogExtraForModules(bool networking, [[maybe_unused]] bool syncs) { g_netLoggingOn = networking; #ifdef ENABLE_SYNC client->syncs.mDetailedSyncLogging = syncs; #endif } void MegaApiImpl::setLogLevel(int logLevel) { SimpleLogger::setLogLevel(LogLevel(logLevel)); } void MegaApiImpl::setMaxPayloadLogSize(size_t maxSize) { SimpleLogger::setMaxPayloadLogSize(maxSize); } void MegaApiImpl::addLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger) { if (singleExclusiveLogger) { assert(!getExclusiveLogger().exclusiveCallback); getExclusiveLogger().exclusiveCallback = [megaLogger](const char* time, int loglevel, const char* source, const char* message #ifdef ENABLE_LOG_PERFORMANCE , const char** directMessages, size_t* directMessagesSizes, unsigned numberMessages #endif ) { megaLogger->log(time, loglevel, source, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, static_cast<int>(numberMessages) #endif ); }; SimpleLogger::setOutputClass(&getExclusiveLogger()); } else { getExternalLogger().addMegaLogger(megaLogger, [megaLogger](const char* time, int loglevel, const char* source, const char* message #ifdef ENABLE_LOG_PERFORMANCE , const char** directMessages, size_t* directMessagesSizes, unsigned numberMessages #endif ) { megaLogger->log(time, loglevel, source, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, static_cast<int>(numberMessages) #endif ); }); } } void MegaApiImpl::removeLoggerClass(MegaLogger *megaLogger, bool singleExclusiveLogger) { if (singleExclusiveLogger) { SimpleLogger::setOutputClass(&getExternalLogger()); getExclusiveLogger().exclusiveCallback = nullptr; } else { getExternalLogger().removeMegaLogger(megaLogger); } } void MegaApiImpl::setLogToConsole(bool enable) { // only supported for external (not exclusive) loggers getExternalLogger().setLogToConsole(enable); } void MegaApiImpl::setLogJSONContent(bool enable) { JSONLog::set(JSONLog::CHUNK_CONSUMED | JSONLog::SENDING | JSONLog::NONCHUNK_RECEIVED); if (enable) SimpleLogger::setMaxPayloadLogSize(0); // Max size else SimpleLogger::setMaxPayloadLogSize(); // Default } void MegaApiImpl::setLogJSON(uint32_t value) { JSONLog::set(value); } uint32_t MegaApiImpl::getLogJSON() { return JSONLog::get(); } void MegaApiImpl::log(int logLevel, const char *message, const char *filename, int line) { SimpleLogger::postLog(LogLevel(logLevel), message, filename, line); } void MegaApiImpl::setLoggingName(const char* loggingName) { SdkMutexGuard g(sdkMutex); if (loggingName) { client->clientname = string(loggingName) + " "; } else { client->clientname.clear(); } } long long MegaApiImpl::getSDKtime() { return Waiter::ds; } MegaHandle MegaApiImpl::base32ToHandle(const char *base32Handle) { if(!base32Handle) return INVALID_HANDLE; handle h = 0; Base32::atob(base32Handle,(byte*)&h, MegaClient::USERHANDLE); return h; } const char* MegaApiImpl::ebcEncryptKey(const char* encryptionKey, const char* plainKey) { if(!encryptionKey || !plainKey) return NULL; char pwkey[SymmCipher::KEYLENGTH]; Base64::atob(encryptionKey, (byte *)pwkey, sizeof pwkey); SymmCipher key; key.setkey((byte*)pwkey); char plkey[SymmCipher::KEYLENGTH]; Base64::atob(plainKey, (byte*)plkey, sizeof plkey); key.ecb_encrypt((byte*)plkey); char* buf = new char[SymmCipher::KEYLENGTH*4/3+4]; Base64::btoa((byte*)plkey, SymmCipher::KEYLENGTH, buf); return buf; } handle MegaApiImpl::base64ToHandle(const char* base64Handle) { if(!base64Handle) return UNDEF; handle h = 0; Base64::atob(base64Handle,(byte*)&h,MegaClient::NODEHANDLE); return h; } handle MegaApiImpl::base64ToUserHandle(const char *base64Handle) { if(!base64Handle) return UNDEF; handle h = 0; Base64::atob(base64Handle,(byte*)&h,MegaClient::USERHANDLE); return h; } handle MegaApiImpl::base64ToBackupId(const char* backupId) { if (!backupId || !*backupId) return UNDEF; handle result = 0x0; Base64::atob(backupId, reinterpret_cast<byte*>(&result), MegaClient::BACKUPHANDLE); return result; } char *MegaApiImpl::handleToBase64(MegaHandle handle) { char *base64Handle = new char[12]; Base64::btoa((byte*)&(handle),MegaClient::NODEHANDLE,base64Handle); return base64Handle; } char *MegaApiImpl::userHandleToBase64(MegaHandle handle) { char *base64Handle = new char[14]; Base64::btoa((byte*)&(handle),MegaClient::USERHANDLE,base64Handle); return base64Handle; } const char* MegaApiImpl::backupIdToBase64(MegaHandle backupId) { unique_ptr<char[]> result(new char[14]); Base64::btoa(reinterpret_cast<byte*>(&backupId), MegaClient::BACKUPHANDLE, result.get()); return result.release(); } char *MegaApiImpl::binaryToBase64(const char *binaryData, size_t length) { char *ret = new char[length * 4 / 3 + 3]; Base64::btoa((byte*)binaryData, length, ret); return ret; } void MegaApiImpl::base64ToBinary(const char *base64string, unsigned char **binary, size_t* binarysize) { string data; data.resize(strlen(base64string) * 3 / 4 + 3); data.resize( static_cast<size_t>(Base64::atob(base64string, (byte*)data.data(), int(data.size())))); *binarysize = data.size(); *binary = new unsigned char[*binarysize]; memcpy(*binary, data.data(), *binarysize); } void MegaApiImpl::retryPendingConnections(bool disconnect, bool includexfers, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS, listener); request->setFlag(disconnect); request->setNumber(includexfers); request->performRequest = [this, request]() { return performRequest_retryPendingConnections(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setDnsServers(const char *dnsServers, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_RETRY_PENDING_CONNECTIONS, listener); request->setFlag(true); request->setNumber(true); request->setText(dnsServers); request->performRequest = [this, request]() { return performRequest_retryPendingConnections(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::addEntropy(char *data, unsigned int size) { if(client && client->rng.CanIncorporateEntropy()) { client->rng.IncorporateEntropy((const byte*)data, size); } #ifdef USE_OPENSSL RAND_seed(data, static_cast<int>(size)); #endif } string MegaApiImpl::userAttributeToString(int type) { return User::attr2string((::mega::attr_t) type); } string MegaApiImpl::userAttributeToLongName(int type) { return User::attr2longname((::mega::attr_t) type); } int MegaApiImpl::userAttributeFromString(const char *name) { if (!name) { return MegaApi::USER_ATTR_UNKNOWN; } return User::string2attr(name); } char MegaApiImpl::userAttributeToScope(int type) { char scope = User::scope(static_cast<attr_t>(type)); if (scope == ATTR_SCOPE_UNKNOWN) { LOG_err << "Invalid scope for user attribute of type " << type; } return scope; } bool MegaApiImpl::serverSideRubbishBinAutopurgeEnabled() { return client->ssrs_enabled; } bool MegaApiImpl::appleVoipPushEnabled() { return client->aplvp_enabled; } bool MegaApiImpl::newLinkFormatEnabled() { return client->mNewLinkFormat; } bool MegaApiImpl::accountIsNew() const { return client->accountIsNew; } unsigned int MegaApiImpl::getABTestValue(const char* flag) { if (!flag) return 0u; unique_ptr<uint32_t> v = client->mABTestFlags.get(flag); if (v) { sendABTestActive(flag, nullptr); } return v ? *v : 0u; } void MegaApiImpl::sendABTestActive(const char* flag, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_AB_TEST_ACTIVE, listener); request->setText(flag); request->performRequest = [this, request]() { return client->sendABTestActive(request->getText(), [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); }; requestQueue.push(request); waiter->notify(); } int MegaApiImpl::smsAllowedState() { return (client->mSmsVerificationState != SMS_STATE_UNKNOWN) ? client->mSmsVerificationState : 0; } char* MegaApiImpl::smsVerifiedPhoneNumber() { SdkMutexGuard g(sdkMutex); return client->mSmsVerifiedPhone.empty() ? NULL : MegaApi::strdup(client->mSmsVerifiedPhone.c_str()); } bool MegaApiImpl::multiFactorAuthAvailable() { return client->gmfa_enabled; } void MegaApiImpl::multiFactorAuthEnable(const char *pin, MegaRequestListener *listener) { return multiFactorAuthEnableOrDisable(pin, true, listener); } void MegaApiImpl::multiFactorAuthDisable(const char *pin, MegaRequestListener *listener) { return multiFactorAuthEnableOrDisable(pin, false, listener); } void MegaApiImpl::multiFactorAuthLogin(const char *email, const char *password, const char *pin, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener); request->setTransferredBytes(static_cast<long long>(cancel_epoch_snapshot())); request->setEmail(email); request->setPassword(password); request->setText(pin); request->performRequest = [this, request]() { return performRequest_login(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthChangePassword(const char *oldPassword, const char *newPassword, const char *pin, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_PW, listener); request->setPassword(oldPassword); request->setNewPassword(newPassword); request->setText(pin); request->performRequest = [this, request]() { return performRequest_changePw(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthChangeEmail(const char *email, const char *pin, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK, listener); request->setEmail(email); request->setText(pin); request->performRequest = [this, request]() { return performRequest_getChangeEmailLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthCancelAccount(const char *pin, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CANCEL_LINK, listener); request->setText(pin); request->performRequest = [this, request]() { return performRequest_getCancelLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fastLogin(const char *session, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener); request->setTransferredBytes(static_cast<long long>(cancel_epoch_snapshot())); request->setSessionKey(session); request->performRequest = [this, request]() { return performRequest_login(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserData(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener); request->performRequest = [this, request]() { return performRequest_getUserData(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserData(MegaUser *user, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener); request->setFlag(true); if(user) { request->setEmail(user->getEmail()); } request->performRequest = [this, request]() { return performRequest_getUserData(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserData(const char *user, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_DATA, listener); request->setFlag(true); request->setEmail(user); request->performRequest = [this, request]() { return performRequest_getUserData(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::login(const char *login, const char *password, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener); request->setTransferredBytes(static_cast<long long>(cancel_epoch_snapshot())); request->setEmail(login); request->setPassword(password); request->performRequest = [this, request]() { return performRequest_login(request); }; requestQueue.push(request); waiter->notify(); } char *MegaApiImpl::dumpSession() { SdkMutexGuard g(sdkMutex); string session; if (client->dumpsession(session)) { return MegaApi::strdup(Base64::btoa(session).c_str()); } return nullptr; } char *MegaApiImpl::getSequenceNumber() { SdkMutexGuard g(sdkMutex); return MegaApi::strdup(client->scsn.text()); } char *MegaApiImpl::getSequenceTag() { SdkMutexGuard g(sdkMutex); //Note: we rely on mScDbStateRecord.seqTag, since mLastReceivedScSeqTag is cleared after notified return MegaApi::strdup(client->mScDbStateRecord.seqTag.c_str()); } char *MegaApiImpl::getAccountAuth() { SdkMutexGuard g(sdkMutex); if (client->loggedin()) { return MegaApi::strdup(Base64::btoa(client->sid).c_str()); } return nullptr; } void MegaApiImpl::setAccountAuth(const char *auth) { SdkMutexGuard g(sdkMutex); client->setFolderLinkAccountAuth(auth); } void MegaApiImpl::createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); request->setEmail(email); request->setPassword(password); request->setName(firstname); request->setText(lastname); request->setNodeHandle(lastPublicHandle); request->setAccess(lastPublicHandleType); request->setTransferredBytes(lastAccessTimestamp); request->setParamType(MegaApi::CREATE_ACCOUNT); request->performRequest = [this, request]() { return performRequest_createAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::createEphemeralAccountPlusPlus(const char *firstname, const char *lastname, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); request->setName(firstname); request->setText(lastname); request->setParamType(MegaApi::CREATE_EPLUSPLUS_ACCOUNT); request->performRequest = [this, request]() { return performRequest_createAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resumeCreateAccount(const char *sid, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); request->setSessionKey(sid); request->setParamType(MegaApi::RESUME_ACCOUNT); request->performRequest = [this, request]() { return performRequest_createAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resumeCreateAccountEphemeralPlusPlus(const char *sid, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); request->setSessionKey(sid); request->setParamType(MegaApi::RESUME_EPLUSPLUS_ACCOUNT); request->performRequest = [this, request]() { return performRequest_createAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::cancelCreateAccount(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); request->setParamType(MegaApi::CANCEL_ACCOUNT); request->performRequest = [this, request]() { return performRequest_createAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resendSignupLink(const char *email, const char *name, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SIGNUP_LINK, listener); request->setEmail(email); request->setName(name); request->performRequest = [this, request]() { return performRequest_sendSignupLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::confirmAccount(const char* link, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_ACCOUNT, listener); request->setLink(link); request->performRequest = [this, request]() { return performRequest_confirmAccount(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::cancelAccount(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CANCEL_LINK, listener); request->performRequest = [this, request]() { return performRequest_getCancelLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::changeEmail(const char *email, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK, listener); request->setEmail(email); request->performRequest = [this, request]() { return performRequest_getChangeEmailLink(request); }; requestQueue.push(request); waiter->notify(); } MegaProxy *MegaApiImpl::getAutoProxySettings() { MegaProxy *proxySettings = new MegaProxy; unique_ptr<Proxy> localProxySettings; { SdkMutexGuard g(sdkMutex); localProxySettings.reset(httpio->getautoproxy()); } proxySettings->setProxyType(localProxySettings->getProxyType()); if(localProxySettings->getProxyType() == Proxy::CUSTOM) { string localProxyURL = localProxySettings->getProxyURL(); string proxyURL; LocalPath::local2path(&localProxyURL, &proxyURL, true); LOG_debug << "Autodetected proxy: " << proxyURL; proxySettings->setProxyURL(proxyURL.c_str()); } return proxySettings; } void MegaApiImpl::loop() { #ifdef _WIN32 httpio->lock(); #endif while(true) { int r; { SdkMutexGuard g(sdkMutex); r = client->preparewait(); } if (!r) { r = client->dowait(); { SdkMutexGuard g(sdkMutex); r |= client->checkevents(); } } if (r & Waiter::NEEDEXEC) { WAIT_CLASS::bumpds(); updateBackups(); if (sendPendingTransfers(nullptr)) { yield(); } sendPendingRequests(); sendPendingScRequest(); if (threadExit) { break; } { SdkMutexGuard g(sdkMutex); client->exec(); } } } SdkMutexGuard g(sdkMutex); delete client; client = nullptr; } bool MegaApiImpl::createLocalFolder(const char *path) { if (!path) { return false; } string sPath(path); auto localpath = LocalPath::fromAbsolutePath(sPath); SdkMutexGuard g(sdkMutex); return client->fsaccess->mkdirlocal(localpath, false, true); } Error MegaApiImpl::createLocalFolder_unlocked(LocalPath& localPath, FileSystemAccess& fsaccess, const CollisionResolution& collisionResolution) { auto da = fsaccess.newfileaccess(); LocalPath currentLeafName; // Try to open the target path in case-sensitive mode // (unless collision resolution is Overwrite, in which case open insensitive case (merge // folders)) bool opened = da->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError, nullptr, false, (collisionResolution == CollisionResolution::Overwrite) ? true : false, ¤tLeafName); if (!opened) { // Target folder doesn't exist, try to create it bool mkdirSucceeded = fsaccess.mkdirlocal(localPath, false, false); if (!mkdirSucceeded) { // If the folder still doesn't exist, report an error if (!fsaccess.target_exists) { LOG_err << "Unable to create folder: " << localPath; return API_EWRITE; } // Target already exists — apply collision resolution strategy switch (collisionResolution) { case CollisionResolution::RenameExistingToOldN: { auto newPath = FileNameGenerator::suffixWithOldN(da.get(), localPath); fsaccess.renamelocal(localPath, newPath, true); break; } case CollisionResolution::RenameNewWithN: localPath = FileNameGenerator::suffixWithN(da.get(), localPath); break; case CollisionResolution::Overwrite: LOG_err << "Unexpected error for Overwrite CollisionResolution " << localPath; return API_EINTERNAL; break; default: LOG_err << "Invalid folder resolution strategy " << localPath; return API_EARGS; break; } // Retry folder creation with the new (renamed) path if (!fsaccess.mkdirlocal(localPath, false, false)) { LOG_err << "Unable to create folder: " << localPath; return API_EWRITE; } } } else if (da->type == FILENODE) { LOG_err << "Local file detected where there should be a folder: " << localPath; return API_EARGS; } else { LOG_debug << "Already existing folder detected: " << localPath; return API_EEXIST; } return API_OK; } void MegaApiImpl::moveNode(MegaNode *node, MegaNode *newParent, MegaRequestListener *listener) { moveNode(node, newParent, nullptr, listener); } void MegaApiImpl::copyNode(MegaNode *node, MegaNode* target, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener); if (node) { request->setPublicNode(node, true); request->setNodeHandle(node->getHandle()); } if(target) request->setParentHandle(target->getHandle()); request->performRequest = [this, request]() { return performRequest_copy(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::copyNode(MegaNode *node, MegaNode *target, const char *newName, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener); if (node) { request->setPublicNode(node, true); request->setNodeHandle(node->getHandle()); } if(target) request->setParentHandle(target->getHandle()); request->setName(newName); request->performRequest = [this, request]() { return performRequest_copy(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::sendFileToUser(MegaNode *node, MegaUser *user, MegaRequestListener *listener) { return sendFileToUser(node, user ? user->getEmail() : NULL, listener); } void MegaApiImpl::sendFileToUser(MegaNode *node, const char* email, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_COPY, listener); if (node) { request->setPublicNode(node, true); request->setNodeHandle(node->getHandle()); } request->setEmail(email); request->performRequest = [this, request]() { return performRequest_copy(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::upgradeSecurity(MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_UPGRADE_SECURITY, listener); request->performRequest = [this, request]() { client->upgradeSecurity([this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::contactVerificationWarningEnabled() { SdkMutexGuard m(sdkMutex); return client->mKeyManager.getContactVerificationWarning(); } void MegaApiImpl::setManualVerificationFlag(bool enable) { SdkMutexGuard m(sdkMutex); client->mKeyManager.setManualVerificationFlag(enable); } void MegaApiImpl::openShareDialog(MegaNode* node, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_OPEN_SHARE_DIALOG, listener); if (node) { request->setNodeHandle(node->getHandle()); } request->performRequest = [this, request]() { shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); client->openShareDialog(node.get(), [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } MegaShareList *MegaApiImpl::getUnverifiedInShares(int order) { SdkMutexGuard lock(sdkMutex); sharedNode_vector nodes = client->getUnverifiedInShares(); sortByComparatorFunction(nodes, order, *client); vector<impl::ShareData> shares; for (const auto& node: nodes) { shares.emplace_back(node->nodehandle, node->inshare.get(), false); } return new MegaShareListPrivate(shares); } MegaShareList *MegaApiImpl::getUnverifiedOutShares(int order) { SdkMutexGuard guard(sdkMutex); // Get shared nodes sharedNode_vector sharedNodes = getSharedNodes(); // Sort in place MegaApiImpl::sortByComparatorFunction(sharedNodes, order, *client); // Predicate if a share is unverified or not auto isUnverified = [](const impl::ShareData& data) { return !data.isVerified(); }; // Extract unverified shares auto unverifiedShares = impl::ShareExtractor::extractOutShares(sharedNodes, client->mKeyManager, isUnverified); // Sort shares in place impl::ShareSorter::sort(unverifiedShares, order); return new MegaShareListPrivate(unverifiedShares); } void MegaApiImpl::share(MegaNode* node, MegaUser *user, int access, MegaRequestListener *listener) { return share(node, user ? user->getEmail() : NULL, access, listener); } void MegaApiImpl::loginToFolder(const char* megaFolderLink, const char* authKey, bool tryToResumeFolderLinkFromCache, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener); request->setTransferredBytes(static_cast<long long>(cancel_epoch_snapshot())); request->setLink(megaFolderLink); request->setPassword(authKey); request->setEmail("FOLDER"); request->setFlag(tryToResumeFolderLinkFromCache); request->performRequest = [this, request]() { return performRequest_login(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::importFileLink(const char* megaFileLink, MegaNode *parent, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_IMPORT_LINK, listener); if(parent) request->setParentHandle(parent->getHandle()); request->setLink(megaFileLink); request->performRequest = [this, request]() { return performRequest_importLink_getPublicNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::decryptPasswordProtectedLink(const char *link, const char *password, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_PASSWORD_LINK, listener); request->setLink(link); request->setPassword(password); request->performRequest = [this, request]() { return performRequest_passwordLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::encryptLinkWithPassword(const char *link, const char *password, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_PASSWORD_LINK, listener); request->setLink(link); request->setPassword(password); request->setFlag(true); // encrypt request->performRequest = [this, request]() { return performRequest_passwordLink(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPublicNode(const char* megaFileLink, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PUBLIC_NODE, listener); request->setLink(megaFileLink); request->performRequest = [this, request]() { return performRequest_importLink_getPublicNode(request); }; requestQueue.push(request); waiter->notify(); } const char *MegaApiImpl::buildPublicLink(const char *publicHandle, const char *key, bool isFolder) { handle ph = MegaApi::base64ToHandle(publicHandle); string link = client->publicLinkURL(client->mNewLinkFormat, isFolder ? TypeOfLink::FOLDER : TypeOfLink::FILE, ph, key); return MegaApi::strdup(link.c_str()); } void MegaApiImpl::getThumbnail(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { getNodeAttribute(node, GfxProc::THUMBNAIL, dstFilePath, listener); } void MegaApiImpl::getThumbnail(MegaHandle handle, const char* dstFilePath, MegaRequestListener* listener) { getNodeAttribute(handle, GfxProc::THUMBNAIL, dstFilePath, listener); } void MegaApiImpl::cancelGetThumbnail(MegaNode* node, MegaRequestListener *listener) { cancelGetNodeAttribute(node, GfxProc::THUMBNAIL, listener); } void MegaApiImpl::setThumbnail(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener) { setNodeAttribute(node, GfxProc::THUMBNAIL, srcFilePath, INVALID_HANDLE, listener); } void MegaApiImpl::putThumbnail(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener) { putNodeAttribute(bu, GfxProc::THUMBNAIL, srcFilePath, listener); } void MegaApiImpl::setThumbnailByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener) { setNodeAttribute(node, GfxProc::THUMBNAIL, nullptr, attributehandle, listener); } void MegaApiImpl::getPreview(MegaNode* node, const char *dstFilePath, MegaRequestListener *listener) { getNodeAttribute(node, GfxProc::PREVIEW, dstFilePath, listener); } void MegaApiImpl::cancelGetPreview(MegaNode* node, MegaRequestListener *listener) { cancelGetNodeAttribute(node, GfxProc::PREVIEW, listener); } void MegaApiImpl::setPreview(MegaNode* node, const char *srcFilePath, MegaRequestListener *listener) { setNodeAttribute(node, GfxProc::PREVIEW, srcFilePath, INVALID_HANDLE, listener); } void MegaApiImpl::putPreview(MegaBackgroundMediaUpload* bu, const char *srcFilePath, MegaRequestListener *listener) { putNodeAttribute(bu, GfxProc::PREVIEW, srcFilePath, listener); } void MegaApiImpl::setPreviewByHandle(MegaNode* node, MegaHandle attributehandle, MegaRequestListener *listener) { setNodeAttribute(node, GfxProc::PREVIEW, nullptr, attributehandle, listener); } void MegaApiImpl::getUserAvatar(MegaUser* user, const char *dstFilePath, MegaRequestListener *listener) { const char *email = NULL; if (user) { email = user->getEmail(); } getUserAttr(email, MegaApi::USER_ATTR_AVATAR, dstFilePath, 0, listener); } void MegaApiImpl::getUserAvatar(const char* email_or_handle, const char *dstFilePath, MegaRequestListener *listener) { getUserAttr(email_or_handle, MegaApi::USER_ATTR_AVATAR, dstFilePath, 0, listener); } char *MegaApiImpl::getUserAvatarColor(MegaUser *user) { return user ? MegaApiImpl::getAvatarColor((handle) user->getHandle()) : NULL; } char *MegaApiImpl::getUserAvatarColor(const char *userhandle) { return userhandle ? MegaApiImpl::getAvatarColor(MegaApiImpl::base64ToUserHandle(userhandle)) : NULL; } char *MegaApiImpl::getUserAvatarSecondaryColor(MegaUser *user) { return user ? MegaApiImpl::getAvatarSecondaryColor((handle) user->getHandle()) : NULL; } char *MegaApiImpl::getUserAvatarSecondaryColor(const char *userhandle) { return userhandle ? MegaApiImpl::getAvatarSecondaryColor(MegaApiImpl::base64ToUserHandle(userhandle)) : NULL; } void MegaApiImpl::setAvatar(const char *dstFilePath, MegaRequestListener *listener) { setUserAttr(MegaApi::USER_ATTR_AVATAR, dstFilePath, listener); } char* MegaApiImpl::getPrivateKey(int type) { SdkMutexGuard g(sdkMutex); if (type < MegaApi::PRIVATE_KEY_ED25519 || type > MegaApi::PRIVATE_KEY_CU25519) { return nullptr; } User *u = client->ownuser(); if (!u) { LOG_warn << "User is not defined yet"; assert(false); return nullptr; } string privateKey; if (client->mKeyManager.generation()) // account has ^!keys already available { switch (type) { case MegaApi::PRIVATE_KEY_ED25519: privateKey = client->mKeyManager.privEd25519(); break; case MegaApi::PRIVATE_KEY_CU25519: privateKey = client->mKeyManager.privCu25519(); break; default: assert(false); return nullptr; } } else { const UserAttribute* attribute = u->getAttribute(ATTR_KEYRING); if (attribute && attribute->isValid()) { unique_ptr<string_map> records{ tlv::containerToRecords(attribute->value(), client->key)}; if (records && (type == MegaApi::PRIVATE_KEY_ED25519 || type == MegaApi::PRIVATE_KEY_CU25519)) { privateKey = type == MegaApi::PRIVATE_KEY_ED25519 ? std::move((*records)[EdDSA::TLV_KEY]) : std::move((*records)[ECDH::TLV_KEY]); } else { LOG_warn << "Failed to decrypt keyring while initialization or invalid key type"; return nullptr; } } else { return nullptr; } } std::string privateKeyBase64 = Base64::btoa(privateKey); return MegaApi::strdup(privateKeyBase64.c_str()); } void MegaApiImpl::getUserAttribute(MegaUser* user, int type, MegaRequestListener *listener) { const char* email = NULL; if (user) { email = user->getEmail(); } getUserAttribute(email, type, listener); } bool MegaApiImpl::testAllocation(unsigned allocCount, size_t allocSize) { bool success = true; std::vector<char*> v; try { for (unsigned i = allocCount; i--; ) { v.push_back(new char[allocSize]); } } catch (std::bad_alloc&) { LOG_warn << "MegaApi::testAllocation detected low memory: " << allocCount << " " << allocSize; success = false; } for (auto it : v) { delete[] it; } return success; } void MegaApiImpl::getUserAttribute(const char* email_or_handle, int type, MegaRequestListener *listener) { // allow only types documented to be valid switch (type) { case ATTR_FIRSTNAME: case ATTR_LASTNAME: case ATTR_AUTHRING: case ATTR_LAST_INT: case ATTR_ED25519_PUBK: case ATTR_CU25519_PUBK: case ATTR_KEYRING: case ATTR_SIG_RSA_PUBK: case ATTR_SIG_CU255_PUBK: case ATTR_LANGUAGE: case ATTR_PWD_REMINDER: case ATTR_DISABLE_VERSIONS: case ATTR_CONTACT_LINK_VERIFICATION: case ATTR_RICH_PREVIEWS: case ATTR_RUBBISH_TIME: case ATTR_LAST_PSA: case ATTR_STORAGE_STATE: case ATTR_GEOLOCATION: case ATTR_CAMERA_UPLOADS_FOLDER: case ATTR_MY_CHAT_FILES_FOLDER: case ATTR_PUSH_SETTINGS: case ATTR_ALIAS: case ATTR_DEVICE_NAMES: case ATTR_MY_BACKUPS_FOLDER: case ATTR_COOKIE_SETTINGS: case ATTR_JSON_SYNC_CONFIG_DATA: case ATTR_NO_CALLKIT: case ATTR_APPS_PREFS: case ATTR_CC_PREFS: case ATTR_VISIBLE_WELCOME_DIALOG: case ATTR_VISIBLE_TERMS_OF_SERVICE: case ATTR_PWM_BASE: case ATTR_LAST_READ_NOTIFICATION: case ATTR_LAST_ACTIONED_BANNER: case ATTR_RECENT_CLEAR_TIMESTAMP: // undocumented types, allowed only for testing: case ATTR_KEYS: case ATTR_DEV_OPT: getUserAttr(email_or_handle, type, nullptr, 0, listener); break; default: getUserAttr(email_or_handle, ATTR_UNKNOWN, nullptr, 0, listener); } } void MegaApiImpl::getChatUserAttribute(const char *email_or_handle, int type, const char *ph, MegaRequestListener *listener) { getChatUserAttr(email_or_handle, type ? type : -1, NULL, ph, 0, listener); } void MegaApiImpl::setUserAttribute(int type, const char *value, MegaRequestListener *listener) { // allow only types documented to be valid switch (type) { case ATTR_FIRSTNAME: case ATTR_LASTNAME: case ATTR_LANGUAGE: case ATTR_DISABLE_VERSIONS: case ATTR_CONTACT_LINK_VERIFICATION: case ATTR_RUBBISH_TIME: case ATTR_LAST_PSA: case ATTR_PUSH_SETTINGS: case ATTR_NO_CALLKIT: case ATTR_VISIBLE_WELCOME_DIALOG: case ATTR_VISIBLE_TERMS_OF_SERVICE: case ATTR_LAST_READ_NOTIFICATION: case ATTR_LAST_ACTIONED_BANNER: // undocumented types, allowed only for testing: case ATTR_ENABLE_TEST_NOTIFICATIONS: case ATTR_ENABLE_TEST_SURVEYS: // undocumented types, allowed only to notify with a specific error: case ATTR_KEYRING: case ATTR_KEYS: case ATTR_AUTHRING: case ATTR_AUTHCU255: case ATTR_CU25519_PUBK: case ATTR_ED25519_PUBK: case ATTR_SIG_CU255_PUBK: case ATTR_SIG_RSA_PUBK: case ATTR_PWD_REMINDER: case ATTR_MY_BACKUPS_FOLDER: case ATTR_DEV_OPT: setUserAttr(type, value, listener); break; default: setUserAttr(ATTR_UNKNOWN, value, listener); } } void MegaApiImpl::setUserAttribute(int type, const MegaStringMap* value, MegaRequestListener* listener) { // allow only types documented to be valid switch (type) { case ATTR_AUTHRING: case ATTR_LAST_INT: case ATTR_KEYRING: case ATTR_RICH_PREVIEWS: case ATTR_GEOLOCATION: case ATTR_ALIAS: case ATTR_DEVICE_NAMES: case ATTR_APPS_PREFS: case ATTR_CC_PREFS: setUserAttr(type, value, listener); break; default: setUserAttr(ATTR_UNKNOWN, value, listener); } } void MegaApiImpl::setUserAttr(int type, const MegaStringMap* value, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setMegaStringMap(value); request->setParamType(type); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getRubbishBinAutopurgePeriod(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_RUBBISH_TIME); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setRubbishBinAutopurgePeriod(int days, MegaRequestListener *listener) { ostringstream oss; oss << days; string value = oss.str(); MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setText(value.data()); request->setParamType(MegaApi::USER_ATTR_RUBBISH_TIME); request->setNumber(days); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } const char* MegaApiImpl::getDeviceId() const { return MegaApi::strdup(client->getDeviceidHash().c_str()); } void MegaApiImpl::getDeviceName(const char* deviceId, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES); string id = deviceId ? deviceId : client->getDeviceidHash(); request->setText(id.c_str()); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setDeviceName(const char *deviceId, const char *deviceName, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); MegaStringMapPrivate stringMap; string id = deviceId ? deviceId : client->getDeviceidHash(); string buf = deviceName ? deviceName : ""; stringMap.set(id.c_str(), Base64::btoa(buf).c_str()); request->setMegaStringMap(&stringMap); request->setText(id.c_str()); request->setName(deviceName); request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getDriveName(const char *pathToDrive, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES); request->setFlag(true); // external drive request->setFile(pathToDrive); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setDriveName(const char *pathToDrive, const char *driveName, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setFile(pathToDrive); request->setName(driveName); request->setParamType(MegaApi::USER_ATTR_DEVICE_NAMES); request->setFlag(true); // external drive request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setCustomNodeAttribute(MegaNode *node, const char *attrName, const char *value, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if(node) request->setNodeHandle(node->getHandle()); request->setName(attrName); request->setText(value); request->setFlag(false); // is official attribute or not request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setNodeS4(MegaNode *node, const char *value, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if(node) request->setNodeHandle(node->getHandle()); request->setParamType(MegaApi::NODE_ATTR_S4); request->setText(value); request->setFlag(true); // is official attribute or not request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::isS4Enabled() { return client->mIsS4Enabled.load(); } MegaHandle MegaApiImpl::getS4Container() { return client->mS4Container.load().as8byte(); } void MegaApiImpl::setNodeLabel(MegaNode *node, int label, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if(node) request->setNodeHandle(node->getHandle()); request->setParamType(MegaApi::NODE_ATTR_LABEL); request->setNumDetails(label); request->setFlag(true); // is official attribute or not request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setNodeFavourite(MegaNode *node, bool fav, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if(node) request->setNodeHandle(node->getHandle()); request->setParamType(MegaApi::NODE_ATTR_FAV); request->setNumDetails(fav); request->setFlag(true); // is official attribute or not request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setNodeSensitive(MegaNode* node, bool sensitive, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if (node) request->setNodeHandle(node->getHandle()); request->setParamType(MegaApi::NODE_ATTR_SEN); request->setNumDetails(sensitive); request->setFlag(true); // is official attribute or not request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::isSensitiveInherited(MegaNode* mnode) { // there is no MegaNode::getParentNode() so the traversal must be here in MegaApi SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodeByHandle(NodeHandle().set6byte(mnode->getHandle())); if (node == nullptr) return false; return node->isSensitiveInherited(); } static void encodeCoordinates(double latitude, double longitude, int& lat, int& lon) { lat = int(latitude); if (latitude != MegaNode::INVALID_COORDINATE) { lat = int(((latitude + 90) / 180) * 0xFFFFFF); } lon = int(longitude); if (longitude != MegaNode::INVALID_COORDINATE) { lon = int((longitude == 180) ? 0 : ((longitude + 180) / 360) * 0x01000000); } } void MegaApiImpl::setNodeCoordinates(std::variant<MegaNode*, MegaHandle> nodeOrNodeHandle, bool unshareable, double latitude, double longitude, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); MegaHandle nodeHandle{INVALID_HANDLE}; if (std::holds_alternative<MegaNode*>(nodeOrNodeHandle)) { if (auto node{std::get<MegaNode*>(nodeOrNodeHandle)}) { nodeHandle = node->getHandle(); } } else if (std::holds_alternative<MegaHandle>(nodeOrNodeHandle)) { nodeHandle = std::get<MegaHandle>(nodeOrNodeHandle); } request->setNodeHandle(nodeHandle); int lat, lon; encodeCoordinates(latitude, longitude, lat, lon); request->setParamType(MegaApi::NODE_ATTR_COORDINATES); request->setTransferTag(lat); request->setNumDetails(lon); request->setAccess(unshareable); request->setFlag(true); // official attribute (otherwise it would go in the custom section) request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setNodeDescription(MegaNode* node, const char* description, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); if (node) { request->setNodeHandle(node->getHandle()); } request->setParamType(MegaApi::NODE_ATTR_DESCRIPTION); request->setText(description); request->setFlag(true); // official attribute (otherwise it would go in the custom section) request->performRequest = [this, request]() { return performRequest_setAttrNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::addNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener) { CRUDNodeTagOperation(node, MegaApi::TAG_NODE_SET, tag, nullptr, listener); } void MegaApiImpl::removeNodeTag(MegaNode* node, const char* tag, MegaRequestListener* listener) { CRUDNodeTagOperation(node, MegaApi::TAG_NODE_REMOVE, tag, nullptr, listener); } void MegaApiImpl::updateNodeTag(MegaNode* node, const char* newTag, const char* oldTag, MegaRequestListener* listener) { CRUDNodeTagOperation(node, MegaApi::TAG_NODE_UPDATE, newTag, oldTag, listener); } MegaStringList* MegaApiImpl::getAllNodeTagsBelow(MegaHandle handle, const std::string& pattern, CancelToken cancelToken) { // Try and retrieve all tags below this account's root nodes. auto tags = ([&]() { // Make sure we have exclusive access to the client. SdkMutexGuard guard(sdkMutex); // Ask the client for the list of tags. return client->getNodeTagsBelow(std::move(cancelToken), NodeHandle().set6byte(handle), pattern); })(); // Couldn't get a list of tags. if (!tags) return new MegaStringListPrivate(); // Make sure the tags are in sorted order. std::vector<std::string> sorted(tags->begin(), tags->end()); std::sort(sorted.begin(), sorted.end(), NaturalSortingComparator()); // Return sorted list of tags to the caller. return new MegaStringListPrivate(std::move(sorted)); } void MegaApiImpl::exportNode(MegaNode *node, int64_t expireTime, bool writable, bool megaHosted, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT, listener); if(node) request->setNodeHandle(node->getHandle()); request->setNumber(expireTime); request->setAccess(1); request->setTransferTag(megaHosted ? 1 : 0); request->setFlag(writable); request->performRequest = [this, request]() { return performRequest_export(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::disableExport(MegaNode *node, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT, listener); if(node) request->setNodeHandle(node->getHandle()); request->setAccess(0); request->performRequest = [this, request]() { return performRequest_export(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPricing(const std::optional<std::string>& countryCode, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PRICING, listener); if (countryCode && countryCode->size()) { request->setText(countryCode->c_str()); } request->performRequest = [this, request]() { return performRequest_enumeratequotaitems(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getRecommendedProLevel(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN, listener); request->performRequest = [this, request]() { if (client->loggedin() != FULLACCOUNT) { return API_EACCESS; } client->getaccountdetails(request->getAccountDetails(), true /*storage*/, false, false, false, false, false, -1); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPaymentId(handle productHandle, handle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PAYMENT_ID, listener); request->setNodeHandle(productHandle); request->setParentHandle(lastPublicHandle); request->setParamType(lastPublicHandleType); request->setTransferredBytes(lastAccessTimestamp); request->performRequest = [this, request]() { return performRequest_enumeratequotaitems(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::upgradeAccount(MegaHandle productHandle, int paymentMethod, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_UPGRADE_ACCOUNT, listener); request->setNodeHandle(productHandle); request->setNumber(paymentMethod); request->performRequest = [this, request]() { return performRequest_enumeratequotaitems(request); }; requestQueue.push(request); waiter->notify(); } char *MegaApiImpl::exportMasterKey() { SdkMutexGuard g(sdkMutex); char* buf = NULL; if(client->loggedin()) { buf = new char[SymmCipher::KEYLENGTH * 4 / 3 + 4]; Base64::btoa(client->key.key, SymmCipher::KEYLENGTH, buf); } return buf; } void MegaApiImpl::updatePwdReminderData(bool lastSuccess, bool lastSkipped, bool mkExported, bool dontShowAgain, bool lastLogin, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_PWD_REMINDER); int numDetails = 0; if (lastSuccess) numDetails |= 0x01; if (lastSkipped) numDetails |= 0x02; if (mkExported) numDetails |= 0x04; if (dontShowAgain) numDetails |= 0x08; if (lastLogin) numDetails |= 0x10; request->setNumDetails(numDetails); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::changePassword(const char *oldPassword, const char *newPassword, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHANGE_PW, listener); request->setPassword(oldPassword); request->setNewPassword(newPassword); request->performRequest = [this, request]() { return performRequest_changePw(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::logout(bool keepSyncConfigsFile, MegaRequestListener *listener) { cancel_epoch_bump(); MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT, listener); request->setFlag(true); request->setTransferTag(keepSyncConfigsFile); request->performRequest = [this, request]() { return performRequest_logout(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::localLogout(MegaRequestListener *listener) { cancel_epoch_bump(); MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT, listener); request->setFlag(false); request->performRequest = [this, request]() { return performRequest_logout(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::invalidateCache() { SdkMutexGuard g(sdkMutex); nocache = true; } int MegaApiImpl::getPasswordStrength(const char *password) { if (!password || strlen(password) < 8) { return MegaApi::PASSWORD_STRENGTH_VERYWEAK; } double entrophy = ZxcvbnMatch(password, NULL, NULL); if (entrophy > 75) { return MegaApi::PASSWORD_STRENGTH_STRONG; } if (entrophy > 50) { return MegaApi::PASSWORD_STRENGTH_GOOD; } if (entrophy > 40) { return MegaApi::PASSWORD_STRENGTH_MEDIUM; } if (entrophy > 15) { return MegaApi::PASSWORD_STRENGTH_WEAK; } return MegaApi::PASSWORD_STRENGTH_VERYWEAK; } char* MegaApiImpl::generateRandomCharsPassword(bool uU, bool uD, bool uS, unsigned int len) { const std::string pwd = MegaClient::generatePasswordChars(uU, uD, uS, len); return pwd.empty() ? nullptr : MegaApi::strdup(pwd.c_str()); } void MegaApiImpl::setNodeAttribute(MegaNode *node, int type, const char *srcFilePath, MegaHandle attributehandle, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_FILE, listener); if (srcFilePath) request->setFile(srcFilePath); request->setNumber(static_cast<long long>(srcFilePath ? INVALID_HANDLE : attributehandle)); request->setParamType(type); request->setNodeHandle(node ? node->getHandle() : INVALID_HANDLE); request->setMegaBackgroundMediaUploadPtr(nullptr); request->performRequest = [this, request]() { return performRequest_setAttrFile(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::putNodeAttribute(MegaBackgroundMediaUpload* bu, int type, const char *srcFilePath, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_FILE, listener); request->setFile(srcFilePath); request->setParamType(type); request->setMegaBackgroundMediaUploadPtr(bu); request->setNumber(static_cast<long long>(INVALID_HANDLE)); request->setParentHandle(INVALID_HANDLE); request->performRequest = [this, request]() { return performRequest_setAttrFile(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserAttr(const char *email_or_handle, int type, const char *dstFilePath, int number, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); if (type == MegaApi::USER_ATTR_AVATAR && dstFilePath && *dstFilePath) { string path(dstFilePath); int c = path[path.size()-1]; if((c=='/') || (c == '\\')) { path.append(email_or_handle); path.push_back(static_cast<char>('0' + type)); path.append(".jpg"); } request->setFile(path.c_str()); } request->setParamType(type); request->setNumber(number); if(email_or_handle) { request->setEmail(email_or_handle); } request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getChatUserAttr(const char *email_or_handle, int type, const char *dstFilePath, const char *ph, int number, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); if (type == MegaApi::USER_ATTR_AVATAR && dstFilePath) { string path(dstFilePath); int c = path[path.size()-1]; if((c=='/') || (c == '\\')) { path.append(email_or_handle); path.push_back(static_cast<char>('0' + type)); path.append(".jpg"); } request->setFile(path.c_str()); } request->setSessionKey(ph); request->setParamType(type); request->setNumber(number); if(email_or_handle) { request->setEmail(email_or_handle); } request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setUserAttr(int type, const char *value, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); if(type == MegaApi::USER_ATTR_AVATAR) { request->setFile(value); } else { request->setText(value); } request->setParamType(type); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserAttr(User* user, attr_t type, MegaRequestPrivate* request) { assert(request); client->getua( user, type, -1, [this, request](error e) { getua_completion(e, request); }, [this, request](byte* data, unsigned len, attr_t type) { getua_completion(data, len, type, request); }, [this, request](unique_ptr<string_map> records, attr_t type) { getua_completion(std::move(records), type, request); }); } void MegaApiImpl::getUserAttr(const string& email, attr_t type, const char* ph, MegaRequestPrivate* request) { assert(request); client->getua( email.c_str(), type, ph, -1, [this, request](error e) { getua_completion(e, request); }, [this, request](byte* data, unsigned len, attr_t type) { getua_completion(data, len, type, request); }, [this, request](unique_ptr<string_map> records, attr_t type) { getua_completion(std::move(records), type, request); }); } char *MegaApiImpl::getAvatarColor(handle userhandle) { string colors[] = { "#55D2F0", // Blue "#BC2086", // Eggplant "#FFD200", // Gold "#5FDB00", // Green "#00BDB2", // Jade "#FFA700", // Orange "#E4269B", // Purple "#FF626C", // Red "#FF8989", // Salmon "#9AEAFF", // Sky "#00D5E2", // Teal "#FFEB00" // Yellow }; auto index = userhandle % (handle)(sizeof(colors)/sizeof(colors[0])); return MegaApi::strdup(colors[index].c_str()); } char *MegaApiImpl::getAvatarSecondaryColor(handle userhandle) { string colors[] = { "#2BA6DE", // Blue "#880E4F", // Eggplant "#FFA500", // Gold "#31B500", // Green "#00897B", // Jade "#FF6F00", // Orange "#C51162", // Purple "#FF333A", // Red "#FF5252", // Salmon "#61D2FF", // Sky "#00ACC1", // Teal "#FFD300" // Yellow }; auto index = userhandle % (handle)(sizeof(colors)/sizeof(colors[0])); return MegaApi::strdup(colors[index].c_str()); } bool MegaApiImpl::isGlobalNotifiable(MegaPushNotificationSettingsPrivate* pushSettings) { return !pushSettings || (!pushSettings->isGlobalDndEnabled() && isScheduleNotifiable(pushSettings)); } bool MegaApiImpl::isScheduleNotifiable(MegaPushNotificationSettingsPrivate* pushSettings) { if (!mTimezones) { LOG_warn << "Timezones are not available yet"; return true; } if (!pushSettings || !pushSettings->isGlobalScheduleEnabled()) { return true; } // find the configured timezone for notification's schedule and get the corresponding offset based on UTC int offsetTz = 0; bool tzFound = false; for (int i = 0; i < mTimezones->getNumTimeZones(); i++) { if (strcmp(pushSettings->getGlobalScheduleTimezone(), mTimezones->getTimeZone(i)) == 0) { offsetTz = mTimezones->getTimeOffset(i); tzFound = true; break; } } if (!tzFound) { LOG_err << "Timezone not found: " << pushSettings->getGlobalScheduleTimezone(); assert(false); return true; // better to generate the notification, in this case } // calculate the timestamp for time 00:00:00 of the current day in the configured timezone m_time_t now = m_time(NULL) + offsetTz; struct tm tmp; m_gmtime(now, &tmp); tmp.tm_hour = tmp.tm_min = tmp.tm_sec = 0; // set the time to 00:00:00 m_time_t dayStart = m_mktime_UTC(&tmp); // calculate the timestamps for the scheduled period int offsetStart = pushSettings->getGlobalScheduleStart() * 60; // convert minutes into seconds int offsetEnd = pushSettings->getGlobalScheduleEnd() * 60; m_time_t scheduleStart = dayStart + offsetStart; m_time_t scheduleEnd = dayStart + offsetEnd; if (offsetStart <= offsetEnd) { return now >= scheduleStart && now <= scheduleEnd; } else // the scheduled period covers 2 days { assert(now >= dayStart && now <= dayStart + 24 * 60 * 60); return now <= scheduleEnd || now >= scheduleStart; } } // clears backups/requests/transfers notifying failure with EACCESS (and resets total up/down bytes) void MegaApiImpl::abortPendingActions(error preverror) { if (!preverror) { preverror = API_EACCESS; } // -- Backups -- for (auto it : backupsMap) { delete it.second; } backupsMap.clear(); // -- CS Requests in progress -- deque<MegaRequestPrivate*> requests; for (auto requestPair : requestMap) { if (requestPair.second) { requests.push_back(requestPair.second); } } for (auto request : requests) { if (request->getType() == MegaRequest::TYPE_DELETE) { continue; // this request is deleted in MegaApiImpl dtor, its finish is the Impl destructor exiting. } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(preverror)); } requestMap.clear(); // -- Transfers in progress -- { // -- Transfers in the queue -- // clear queued transfers, not yet started (and not added to cache) while (MegaTransferPrivate *transfer = transferQueue.pop()) { fireOnTransferStart(transfer); transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(preverror)); } // clear existing transfers while (!transferMap.empty()) { MegaTransferPrivate* transfer = transferMap.begin()->second; if (transfer->isRecursive() && transfer->getTotalRecursiveOperation()) // None subtransfer has been created yet (scan period) { // just remove it from the map. When its last dependent transfer is deleted // then it will have its fireOnTransferFinish called also. transferMap.erase(transferMap.begin()); } else { if (transfer->isRecursive()) { transfer->stopRecursiveOperationThread(); } transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(preverror)); } } assert(transferMap.empty()); transferMap.clear(); } } bool MegaApiImpl::hasToForceUpload(const Node& node, const MegaTransferPrivate& transfer) const { const string name = node.displayname(); const auto lp = LocalPath::fromRelativePath(name); const auto hasPreview = (Node::hasfileattribute(&node.fileattrstring, GfxProc::PREVIEW) != 0); const auto hasThumbnail = (Node::hasfileattribute(&node.fileattrstring, GfxProc::THUMBNAIL) != 0); const auto isMedia = gfxAccess && (gfxAccess->isgfx(lp) || gfxAccess->isvideo(lp)); const auto canForceUpload = transfer.isForceNewUpload(); const auto isPdf = name.find(".pdf") != string::npos; if (canForceUpload && (isMedia || isPdf) && !(hasPreview && hasThumbnail)) { LOG_debug << "[MegaApiImpl::hasToForceUpload] Force to upload a local file (Media type or " "Pdf) if previous node in cloud doesn't have but thumbnail or preview..." << " [handle = " << node.nodeHandle() << "]"; return true; } if (node.hasZeroKey()) { // If the node has a zerokey, we need to discard it, regardless other conditions. LOG_warn << "[MegaApiImpl::hasToForceUpload] Node has a zerokey, forcing a new upload..." << " [handle = " << node.nodeHandle() << "]"; client->sendevent(99486, "Node has a zerokey"); return true; } return false; } void MegaApiImpl::moveTransferUp(int transferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(true); request->setNumber(MegaTransfer::MOVE_TYPE_UP); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_moveTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::moveTransferDown(int transferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(true); request->setNumber(MegaTransfer::MOVE_TYPE_DOWN); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_moveTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::moveTransferToFirst(int transferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(true); request->setNumber(MegaTransfer::MOVE_TYPE_TOP); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_moveTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::moveTransferToLast(int transferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(true); request->setNumber(MegaTransfer::MOVE_TYPE_BOTTOM); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_moveTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::moveTransferBefore(int transferTag, int prevTransferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(false); request->setNumber(prevTransferTag); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_moveTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::areTransfersPaused(int direction) { if(direction != MegaTransfer::TYPE_DOWNLOAD && direction != MegaTransfer::TYPE_UPLOAD) { return false; } bool result; SdkMutexGuard g(sdkMutex); if(direction == MegaTransfer::TYPE_DOWNLOAD) { result = client->xferpaused[GET]; } else { result = client->xferpaused[PUT]; } return result; } void MegaApiImpl::setDownloadMethod(int method) { switch(method) { case MegaApi::TRANSFER_METHOD_NORMAL: client->usealtdownport = false; client->autodownport = false; break; case MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT: client->usealtdownport = true; client->autodownport = false; break; case MegaApi::TRANSFER_METHOD_AUTO: client->autodownport = true; break; case MegaApi::TRANSFER_METHOD_AUTO_NORMAL: client->usealtdownport = false; client->autodownport = true; break; case MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE: client->usealtdownport = true; client->autodownport = true; break; default: break; } } void MegaApiImpl::setUploadMethod(int method) { switch(method) { case MegaApi::TRANSFER_METHOD_NORMAL: client->usealtupport = false; client->autoupport = false; break; case MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT: client->usealtupport = true; client->autoupport = false; break; case MegaApi::TRANSFER_METHOD_AUTO: client->autoupport = true; break; case MegaApi::TRANSFER_METHOD_AUTO_NORMAL: client->usealtupport = false; client->autoupport = true; break; case MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE: client->usealtupport = true; client->autoupport = true; break; default: break; } } bool MegaApiImpl::setMaxDownloadSpeed(m_off_t bpslimit) { SdkMutexGuard g(sdkMutex); return client->setmaxdownloadspeed(bpslimit); } bool MegaApiImpl::setMaxUploadSpeed(m_off_t bpslimit) { SdkMutexGuard g(sdkMutex); return client->setmaxuploadspeed(bpslimit); } int MegaApiImpl::getMaxDownloadSpeed() { return int(client->getmaxdownloadspeed()); } int MegaApiImpl::getMaxUploadSpeed() { return int(client->getmaxuploadspeed()); } int MegaApiImpl::getCurrentDownloadSpeed() { return int(httpio->downloadSpeed); } int MegaApiImpl::getCurrentUploadSpeed() { return int(httpio->uploadSpeed); } int MegaApiImpl::getCurrentSpeed(int type) { switch (type) { case MegaTransfer::TYPE_DOWNLOAD: return int(httpio->downloadSpeed); case MegaTransfer::TYPE_UPLOAD: return int(httpio->uploadSpeed); default: return 0; } } int MegaApiImpl::getDownloadMethod() { if (client->autodownport) { if(client->usealtdownport) { return MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE; } else { return MegaApi::TRANSFER_METHOD_AUTO_NORMAL; } } if (client->usealtdownport) { return MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT; } return MegaApi::TRANSFER_METHOD_NORMAL; } int MegaApiImpl::getUploadMethod() { if (client->autoupport) { if(client->usealtupport) { return MegaApi::TRANSFER_METHOD_AUTO_ALTERNATIVE; } else { return MegaApi::TRANSFER_METHOD_AUTO_NORMAL; } } if (client->usealtupport) { return MegaApi::TRANSFER_METHOD_ALTERNATIVE_PORT; } return MegaApi::TRANSFER_METHOD_NORMAL; } MegaTransferData *MegaApiImpl::getTransferData(MegaTransferListener *listener) { SdkMutexGuard g(sdkMutex); MegaTransferData *data = new MegaTransferDataPrivate(&client->transferlist, notificationNumber); if (listener) { transferListeners.insert(listener); } return data; } MegaTransfer *MegaApiImpl::getFirstTransfer(int type) { if (type != MegaTransfer::TYPE_DOWNLOAD && type != MegaTransfer::TYPE_UPLOAD) { return NULL; } MegaTransfer* transfer = NULL; SdkMutexGuard g(sdkMutex); auto it = client->transferlist.begin((direction_t)type); if (it != client->transferlist.end((direction_t)type)) { Transfer *t = (*it); if (t->files.size()) { MegaTransferPrivate *megaTransfer = getMegaTransferPrivate(t->files.front()->tag); if (megaTransfer) { transfer = megaTransfer->copy(); } } } return transfer; } void MegaApiImpl::notifyTransfer(int transferTag, MegaTransferListener *listener) { SdkMutexGuard g(sdkMutex); MegaTransferPrivate *t = getMegaTransferPrivate(transferTag); if (!t) { return; } fireOnTransferUpdate(t); if (listener) { listener->onTransferUpdate(api, t); } } MegaTransferList *MegaApiImpl::getTransfers() { SdkMutexGuard g(sdkMutex); vector<MegaTransfer *> transfers; for (int d = GET; d == GET || d == PUT; d += PUT - GET) { auto end = client->transferlist.end((direction_t)d); for (auto it = client->transferlist.begin((direction_t)d); it != end; it++) { Transfer *t = (*it); for (file_list::iterator it2 = t->files.begin(); it2 != t->files.end(); it2++) { MegaTransferPrivate* transfer = getMegaTransferPrivate((*it2)->tag); if (transfer) { transfers.push_back(transfer); } } } } return new MegaTransferListPrivate(transfers.data(), int(transfers.size())); } MegaTransferList *MegaApiImpl::getStreamingTransfers() { SdkMutexGuard g(sdkMutex); vector<MegaTransfer *> transfers; for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++) { MegaTransferPrivate *transfer = it->second; if (transfer->isStreamingTransfer()) { transfers.push_back(transfer); } } return new MegaTransferListPrivate(transfers.data(), int(transfers.size())); } MegaTransfer* MegaApiImpl::getTransferByUniqueId(uint32_t transferUniqueId) const { SdkMutexGuard g(sdkMutex); const auto it = std::find_if(begin(transferMap), end(transferMap), [&id = transferUniqueId](const auto& p) { return p.second->getUniqueId() == id; }); return it != end(transferMap) ? it->second->copy() : nullptr; } MegaTransfer *MegaApiImpl::getTransferByTag(int transferTag) { SdkMutexGuard g(sdkMutex); MegaTransfer* value = getMegaTransferPrivate(transferTag); if (value) { value = value->copy(); } return value; } MegaTransferList *MegaApiImpl::getTransfers(int type) { if(type != MegaTransfer::TYPE_DOWNLOAD && type != MegaTransfer::TYPE_UPLOAD) { return new MegaTransferListPrivate(); } SdkMutexGuard g(sdkMutex); vector<MegaTransfer *> transfers; auto end = client->transferlist.end((direction_t)type); for (auto it = client->transferlist.begin((direction_t)type); it != end; it++) { Transfer *t = (*it); for (file_list::iterator it2 = t->files.begin(); it2 != t->files.end(); it2++) { MegaTransferPrivate* transfer = getMegaTransferPrivate((*it2)->tag); if (transfer) { transfers.push_back(transfer); } } } return new MegaTransferListPrivate(transfers.data(), int(transfers.size())); } MegaTransferList *MegaApiImpl::getChildTransfers(int transferTag) { SdkMutexGuard g(sdkMutex); MegaTransfer *transfer = getMegaTransferPrivate(transferTag); if (!transfer) { return new MegaTransferListPrivate(); } if (!transfer->isFolderTransfer()) { return new MegaTransferListPrivate(); } vector<MegaTransfer *> transfers; for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++) { MegaTransferPrivate *t = it->second; if (t->getFolderTransferTag() == transferTag) { transfers.push_back(transfer); } } return new MegaTransferListPrivate(transfers.data(), int(transfers.size())); } MegaTransferList *MegaApiImpl::getTansfersByFolderTag(int folderTransferTag) { SdkMutexGuard g(sdkMutex); vector<MegaTransfer *> transfers; for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++) { MegaTransferPrivate *t = it->second; if (t->getFolderTransferTag() == folderTransferTag) { transfers.push_back(t); } } return new MegaTransferListPrivate(transfers.data(), int(transfers.size())); } MegaStringList *MegaApiImpl::getBackupFolders(int backuptag) { map<int64_t, string> backupTimesPaths; { SdkMutexGuard g(sdkMutex); map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(backuptag) ; if (itr == backupsMap.end()) { LOG_err << "Failed to find backup with tag " << backuptag; return NULL; } MegaScheduledCopyController *mbc = itr->second; MegaNode * parentNode = getNodeByHandle(mbc->getMegaHandle()); if (parentNode) { MegaNodeList* children = getChildren(parentNode, MegaApi::ORDER_NONE); if (children) { for (int i = 0; i < children->size(); i++) { MegaNode *childNode = children->get(i); string childname = childNode->getName(); if (mbc->isBackup(childname, mbc->getBackupName()) ) { int64_t timeofbackup = mbc->getTimeOfBackup(childname); if (timeofbackup) { backupTimesPaths[timeofbackup]=getNodePath(childNode); } else { LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded."; } } } delete children; } delete parentNode; } } string_vector listofpaths; for(map<int64_t, string>::iterator itr = backupTimesPaths.begin(); itr != backupTimesPaths.end(); itr++) { listofpaths.push_back(itr->second); } return new MegaStringListPrivate(std::move(listofpaths)); } void MegaApiImpl::abortCurrentScheduledCopy(int tag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_ABORT_CURRENT_SCHEDULED_COPY, listener); request->setNumber(tag); request->performRequest = [this, request]() { return processAbortBackupRequest(request); }; requestQueue.push(request); waiter->notify(); } MegaTransferPrivate* MegaApiImpl::createUploadTransfer(const LocalPath& localPath, MegaNode* parent, const MegaUploadOptionsPrivate& options, CancelToken cancelToken, MegaTransferListener* listener, const FileFingerprint* preFingerprintedFile) { FileSystemType fsType = options.mFsType; if (fsType == FS_UNKNOWN) { fsType = fsAccess->getlocalfstype(localPath); } const char* fileName = options.mPublicOptions.fileName.empty() ? nullptr : options.mPublicOptions.fileName.c_str(); const char* targetUser = options.mTargetUser.empty() ? nullptr : options.mTargetUser.c_str(); const int64_t mtime = options.mPublicOptions.mtime; const int folderTransferTag = options.mFolderTransferTag; MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_UPLOAD, listener); if (!localPath.empty()) { transfer->setLocalPath(localPath); } if (parent) { transfer->setParentHandle(parent->getHandle()); } if (targetUser) { transfer->setParentPath(targetUser); } transfer->setMaxRetries(maxRetries); transfer->setAppData(options.mPublicOptions.appData); transfer->setSourceFileTemporary(options.mPublicOptions.isSourceTemporary); transfer->setStartFirst(options.mPublicOptions.startFirst); transfer->setCancelToken(cancelToken); transfer->setBackupTransfer(options.mIsBackup); if (fileName || transfer->getFileName()) { string auxName = fileName ? fileName : transfer->getFileName(); client->fsaccess->unescapefsincompatible(&auxName); transfer->setFileName(auxName.c_str()); } transfer->setTime(mtime); if (preFingerprintedFile) { transfer->fingerprint_error = preFingerprintedFile->isvalid ? API_OK : API_EREAD; transfer->fingerprint_filetype = FILENODE; transfer->fingerprint_onDisk = *preFingerprintedFile; } else // if no fingerprint provided, calculate it, to avoid extra workload (and reduce mutex locking time) to SDK thread at sendPendingTransfers { lock_guard<mutex> g(fingerprintingFsAccessMutex); assert(fingerprintingFsAccess); // somehow ::init() wasn't evaluated auto fa = fingerprintingFsAccess->newfileaccess(); if (localPath.empty() || !fa->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError)) { transfer->fingerprint_error = API_EREAD; } else { transfer->fingerprint_filetype = fa->type; // => [FILENODE | FOLDERNODE] auto uploadToInbox = transfer->getInboxTarget(); if (fa->type == FOLDERNODE && uploadToInbox.has_value()) { //Folder upload is not possible when sending to Inbox: //API won't return handle for folder creation, and even if that was the case //doing a put nodes with t = userhandle & the corresponding handle as parent p, API returns EACCESS transfer->fingerprint_error = API_EREAD; } else { transfer->fingerprint_error = API_OK; } if (fa->type == FILENODE) // just file nodes have a valid fingerprint { transfer->fingerprint_onDisk.genfingerprint(fa.get()); } } } if(folderTransferTag) { transfer->setFolderTransferTag(folderTransferTag); } transfer->setForceNewUpload(options.mForceNewUpload); return transfer; } void MegaApiImpl::startUpload(const std::string localPath, MegaNode* parent, CancelToken cancelToken, const MegaUploadOptionsPrivate& options, MegaTransferListener* listener) { LocalPath path; if (!localPath.empty()) { path = LocalPath::fromAbsolutePath(localPath.c_str()); } const PitagTrigger pitagTrigger = pitagTriggerFromChar(options.mPublicOptions.pitagTrigger); const PitagTarget pitagTarget = pitagTargetFromChar(options.mPublicOptions.pitagTarget); MegaTransferPrivate* transfer = createUploadTransfer(path, parent, options, cancelToken, listener); const auto nodeType = (transfer->fingerprint_filetype == FILENODE) ? PitagNodeType::File : PitagNodeType::Folder; const Pitag pitag{PitagPurpose::Upload, pitagTrigger, nodeType, pitagTarget, PitagImportSource::NotApplicable}; transfer->setPitag(pitag); transferQueue.push(transfer); waiter->notify(); } void MegaApiImpl::startUploadForSupport(const char* localPath, bool isSourceFileTemporary, FileSystemType fsType, MegaTransferListener* listener) { LocalPath path; if (localPath) { path = LocalPath::fromAbsolutePath(localPath); } MegaUploadOptionsPrivate options; options.mPublicOptions.startFirst = true; options.mPublicOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; options.mPublicOptions.isSourceTemporary = isSourceFileTemporary; options.mFsType = fsType; options.mTargetUser = MegaClient::SUPPORT_USER_HANDLE; MegaTransferPrivate* transfer = createUploadTransfer(path, nullptr, options, CancelToken(), listener); transferQueue.push(transfer); waiter->notify(); } void MegaApiImpl::startDownload (bool startFirst, MegaNode *node, const char* localPath, const char *customName, int folderTransferTag, const char *appData, CancelToken cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener *listener) { LocalPath path; if (localPath) { path = LocalPath::fromAbsolutePath(localPath); } FileSystemType fsType = fsAccess->getlocalfstype(path); MegaTransferPrivate* transfer = createDownloadTransfer(startFirst, node, path, customName, folderTransferTag, appData, cancelToken, collisionCheck, collisionResolution, undelete, listener, fsType); transferQueue.push(transfer); waiter->notify(); } MegaTransferPrivate* MegaApiImpl::createDownloadTransfer(bool startFirst, MegaNode* node, const LocalPath& localPath, const char* customName, int folderTransferTag, const char* appData, CancelToken cancelToken, int collisionCheck, int collisionResolution, bool undelete, MegaTransferListener* listener, FileSystemType fsType) { assert(!undelete || node); MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_DOWNLOAD, listener); if (!localPath.empty()) { if (localPath.endsInSeparator()) { transfer->setParentPath(localPath.toPath(false).c_str()); } else { transfer->setLocalPath(localPath); } } if (node) { transfer->setNodeHandle(node->getHandle()); if (undelete) { transfer->setNodeToUndelete(node); } else if (node->isPublic() || node->isForeign()) { transfer->setPublicNode(node, true); } } transfer->setMaxRetries(maxRetries); transfer->setAppData(appData); transfer->setStartFirst(startFirst); transfer->setCancelToken(cancelToken); transfer->setCollisionCheck(collisionCheck); transfer->setCollisionResolution(collisionResolution); // cache fsType to transfer as get fsType on a network driver could be expensive transfer->setFileSystemType(fsType); if (customName) { // set custom file/folder name if exists and escape incompatible characters for destination FS std::string auxName = customName; client->fsaccess->escapefsincompatible(&auxName, fsType); transfer->setFileName(auxName.c_str()); } if (folderTransferTag) { transfer->setFolderTransferTag(folderTransferTag); } return transfer; } void MegaApiImpl::cancelTransfer(MegaTransfer *t, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFER, listener); if(t) { request->setTransferTag(t->getTag()); } request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_cancelTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::cancelTransferByTag(int transferTag, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFER, listener); request->setTransferTag(transferTag); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { return performTransferRequest_cancelTransfer(request, committer); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::startStreaming(MegaNode* node, m_off_t startPos, m_off_t size, MegaTransferListener *listener) { MegaTransferPrivate* transfer = new MegaTransferPrivate(MegaTransfer::TYPE_DOWNLOAD, listener); if (node) { transfer->setNodeHandle(node->getHandle()); if (node->isPublic() || node->isForeign()) { transfer->setPublicNode(node, true); } } transfer->setStreamingTransfer(true); transfer->setStartPos(startPos); transfer->setEndPos(startPos + size - 1); transfer->setMaxRetries(maxRetries); transferQueue.push(transfer); waiter->notify(); } void MegaApiImpl::setStreamingMinimumRate(int bytesPerSecond) { SdkMutexGuard g(sdkMutex); LOG_debug << "Setting minimum acceptable speed for streaming: " << bytesPerSecond << "B/s"; client->minstreamingrate = bytesPerSecond; } void MegaApiImpl::retryTransfer(MegaTransfer *transfer, MegaTransferListener *listener) { MegaTransferPrivate *t = dynamic_cast<MegaTransferPrivate*>(transfer); if (!t || (t->getType() != MegaTransfer::TYPE_DOWNLOAD && t->getType() != MegaTransfer::TYPE_UPLOAD)) { return; } int type = t->getType(); if (type == MegaTransfer::TYPE_DOWNLOAD) { MegaNode *node = t->getNodeToUndelete() ? t->getNodeToUndelete() : t->getPublicMegaNode(); if (!node) { node = getNodeByHandle(t->getNodeHandle()); } this->startDownload(true, node, t->getPath(), NULL, 0, t->getAppData(), CancelToken(), static_cast<int>(t->getCollisionCheck()), static_cast<int>(t->getCollisionResolution()), t->getNodeToUndelete() != nullptr, listener); delete node; } else { MegaNode *parent = getNodeByHandle(t->getParentHandle()); Pitag pitag{t->getPitag()}; MegaUploadOptionsPrivate options; if (const char* fileName = t->getFileName()) { options.mPublicOptions.fileName = fileName; } options.mPublicOptions.mtime = t->getTime(); options.mPublicOptions.appData = t->getAppData(); options.mPublicOptions.isSourceTemporary = t->isSourceFileTemporary(); options.mPublicOptions.startFirst = true; options.mPublicOptions.pitagTrigger = static_cast<char>(pitag.trigger); options.mIsBackup = t->isBackupTransfer(); options.mForceNewUpload = t->isForceNewUpload(); options.mFsType = client->fsaccess->getlocalfstype(t->getLocalPath()); options.mPitagTarget = pitag.target; const char* transferPath = t->getPath(); const std::string normalizedPath = transferPath ? transferPath : ""; this->startUpload(normalizedPath, parent, t->accessCancelToken(), options, listener); delete parent; } } #ifdef ENABLE_SYNC int MegaApiImpl::syncPathState(string* platformEncoded) { LocalPath localpath = LocalPath::fromPlatformEncodedAbsolute(*platformEncoded); int cached_ts; if (mRecentlyRequestedOverlayIconPaths.lookup(localpath, cached_ts) || mRecentlyNotifiedOverlayIconPaths.lookup(localpath, cached_ts)) { return cached_ts; } handle containingSyncId = client->syncs.getSyncIdContainingActivePath(localpath); if (containingSyncId == UNDEF) return MegaApi::STATE_IGNORED; // Avoid blocking on the mutex for a long time, as we may be blocking windows explorer (or another platform's equivalent) from opening or displaying a window, unrelated to sync folders // We try to lock the SDK mutex. If we can't get it in 10ms then we return a simple default, and subsequent requests try to lock the mutex but don't wait. std::unique_lock<std::timed_mutex> g(client->syncs.mLocalNodeChangeMutex, std::defer_lock); if ((!syncPathStateLockTimeout && !g.try_lock_for(std::chrono::milliseconds(10))) || (syncPathStateLockTimeout && !g.try_lock())) { // mLocalNodeChangeMutex is not locked if (!syncPathStateLockTimeout) { LOG_verbose << "Cannot get lock to report treestate for path " << localpath; } syncPathStateLockTimeout = true; // store up to 1000 paths for lookup later when we can lock mLocalNodeChangeMutex lock_guard<mutex> syncPathStateLock(syncPathStateDeferredSetMutex); if (syncPathStateDeferredSet.size() < 1024) { syncPathStateDeferredSet.insert(localpath); } return MegaApi::STATE_IGNORED; } // mLocalNodeChangeMutex is locked (and will be unlocked by unique_lock destructor) treestate_t ts = client->syncs.getSyncStateForLocalPath(containingSyncId, localpath); if (syncPathStateLockTimeout) { LOG_verbose << "Resuming reporting treestate. " << ts << " for path " << localpath; // once we do manage to lock, return to normal operation. syncPathStateLockTimeout = false; // issue notifications for the OS to ask about the paths we skipped while we couldn't lock the mutex auto tmp = std::make_shared<set<LocalPath>>(); { lock_guard<mutex> syncPathStateLock(syncPathStateDeferredSetMutex); tmp->swap(syncPathStateDeferredSet); } LOG_verbose << "Issuing updates for OS path icon ovelays for . " << tmp->size() << " paths"; auto mc = client; client->syncs.queueSync([tmp, mc](){ LOG_verbose << "Processing updates for OS path icon ovelays for . " << tmp->size() << " paths"; for (auto& p : *tmp) { treestate_t ts; nodetype_t nt; SyncConfig sc; if (mc->syncs.getSyncStateForLocalPath(p, ts, nt, sc)) { mc->app->syncupdate_treestate(sc, p, ts, nt); } } LOG_verbose << "Completed updates for OS path icon ovelays for . " << tmp->size() << " paths"; }, "syncPathState catchup after glitches"); } mRecentlyRequestedOverlayIconPaths.addOrUpdate(localpath, ts); return ts; } MegaNode *MegaApiImpl::getSyncedNode(const LocalPath& path) { // syncs has its own thread safety NodeHandle h = client->syncs.getSyncedNodeForLocalPath(path); SdkMutexGuard g(sdkMutex); return MegaNodePrivate::fromNode(client->nodeByHandle(h).get()); } const char* MegaApiImpl::exportSyncConfigs() { string configs; { SdkMutexGuard guard(sdkMutex); configs = client->syncs.exportSyncConfigs(); } return MegaApi::strdup(configs.c_str()); } void MegaApiImpl::setSyncRunState(MegaHandle backupId, MegaSync::SyncRunningState targetState, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_SYNC_RUNSTATE, listener); request->setParentHandle(backupId); request->performRequest = [this, request, targetState](){ auto backupId = request->getParentHandle(); switch (targetState) { case MegaSync::RUNSTATE_RUNNING: { client->syncs.enableSyncByBackupId(backupId, true, [this, request](error err, SyncError serr, handle) { request->setNumDetails(serr); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(err, serr), true); }, false, ""); return API_OK; } case MegaSync::RUNSTATE_SUSPENDED: case MegaSync::RUNSTATE_DISABLED: { if (targetState == MegaSync::SyncRunningState(SyncRunState::Pause)) { LOG_warn << "[MegaApiImpl::setSyncRunState] Target state: SyncRunState::Pause. Sync will be suspended"; } bool keepSyncDb = targetState == MegaSync::SyncRunningState(SyncRunState::Pause) || targetState == MegaSync::SyncRunningState(SyncRunState::Suspend); client->syncs.disableSyncByBackupId( backupId, NO_SYNC_ERROR, false, keepSyncDb, [this, request](){ fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK), true); }); return API_OK; } case MegaSync::RUNSTATE_PENDING: case MegaSync::RUNSTATE_LOADING: default: assert(false); return API_EARGS; } }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::rescanSync(MegaHandle backupId, bool reFingerprint) { // no need to go via the Request queue // (since syncs are threaded independently, no need to lock sdkMutex) client->syncs.setSyncsNeedFullSync(true, reFingerprint, backupId); } MegaSyncList *MegaApiImpl::getSyncs() { vector<MegaSyncPrivate*> vMegaSyncs; // syncs has its own thread safety for (auto& config : client->syncs.getConfigs(false)) { vMegaSyncs.push_back(new MegaSyncPrivate(config, client)); } MegaSyncList *syncList = new MegaSyncListPrivate(vMegaSyncs.data(), int(vMegaSyncs.size())); for (auto p : vMegaSyncs) delete p; return syncList; } void MegaApiImpl::setLegacyExcludedNames(vector<string>*) { return; } void MegaApiImpl::setLegacyExcludedPaths(vector<string>*) { return; } void MegaApiImpl::setLegacyExclusionLowerSizeLimit(unsigned long long) { return; } void MegaApiImpl::setLegacyExclusionUpperSizeLimit(unsigned long long) { return; } MegaError* MegaApiImpl::exportLegacyExclusionRules(const char*) { return new MegaErrorPrivate(API_EINTERNAL); } long long MegaApiImpl::getNumLocalNodes() { return client->syncs.totalLocalNodes; } #endif void MegaApiImpl::moveOrRemoveDeconfiguredBackupNodes(MegaHandle deconfiguredBackupRoot, MegaHandle backupDestination, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_OLD_BACKUP_NODES, listener); request->setNodeHandle(backupDestination); request->performRequest = [deconfiguredBackupRoot, backupDestination, this, request]() { std::shared_ptr<Node> deconfiguredBackupRootNode = client->nodebyhandle(deconfiguredBackupRoot); std::shared_ptr<Node> backupDestinationNode = client->nodebyhandle(backupDestination); if (!deconfiguredBackupRootNode) { LOG_debug << "Backup root node not found"; return API_ENOENT; } LOG_debug << "About to move/remove backup nodes from " << deconfiguredBackupRootNode->displaypath(); if (!deconfiguredBackupRootNode->parent || // device !deconfiguredBackupRootNode->parent->parent || // my backups node !deconfiguredBackupRootNode->parent->parent->parent || // Vault root deconfiguredBackupRootNode->parent->parent->parent->nodehandle != client->mNodeManager.getRootNodeVault().as8byte()) { LOG_debug << "Node not in the right place to be a backup root"; return API_EARGS; } if (backupDestinationNode && backupDestinationNode->firstancestor()->nodeHandle() != client->mNodeManager.getRootNodeFiles().as8byte() && backupDestinationNode->firstancestor()->nodeHandle() != client->mNodeManager.getRootNodeRubbish().as8byte()) { LOG_debug << "Destination node not in the main files root, or in rubbish: " << backupDestinationNode->displaypath(); return API_EARGS; } NodeHandle root = NodeHandle().set6byte(deconfiguredBackupRoot); NodeHandle destination = NodeHandle().set6byte(backupDestination); if (backupDestinationNode && backupDestinationNode->hasChildWithName(deconfiguredBackupRootNode->displayname())) { LOG_err << "A node with the same name already exists in the destination. Can't move " "the backup node " << toNodeHandle(root) << " into " << toNodeHandle(destination); return API_EEXIST; } client->unlinkOrMoveBackupNodes(root, destination, [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } MegaNode *MegaApiImpl::getRootNode() { // return without locking the main mutex if possible. // (always lock for folder links, since node attributes can change) // Only compare fixed-location 8-byte values std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex); if (client->mNodeManager.getRootNodeFiles().isUndef()) return nullptr; if (!mLastKnownRootNode || client->loggedIntoFolder() || mLastKnownRootNode->getHandle() != client->mNodeManager.getRootNodeFiles().as8byte()) { // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownRootNode with the mutexes locking in the other order g.unlock(); MegaNode* newPtr = nullptr; { SdkMutexGuard lock(sdkMutex); newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeFiles()).get()); } g.lock(); mLastKnownRootNode.reset(newPtr); } return mLastKnownRootNode ? mLastKnownRootNode->copy() : nullptr; } MegaNode* MegaApiImpl::getVaultNode() { // return without locking the main mutex if possible. // Only compare fixed-location 8-byte values std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex); if (client->mNodeManager.getRootNodeVault().isUndef()) return nullptr; if (!mLastKnownVaultNode || mLastKnownVaultNode->getHandle() != client->mNodeManager.getRootNodeVault().as8byte()) { // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownVaultNode with the mutexes locking in the other order g.unlock(); MegaNode* newPtr = nullptr; { SdkMutexGuard lock(sdkMutex); newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeVault()).get()); } g.lock(); mLastKnownVaultNode.reset(newPtr); } return mLastKnownVaultNode ? mLastKnownVaultNode->copy() : nullptr; } MegaNode* MegaApiImpl::getRubbishNode() { // return without locking the main mutex if possible. // Only compare fixed-location 8-byte values std::unique_lock<mutex> g(mLastRecievedLoggedMeMutex); if (client->mNodeManager.getRootNodeRubbish().isUndef()) return nullptr; if (!mLastKnownRubbishNode || mLastKnownRubbishNode->getHandle() != client->mNodeManager.getRootNodeRubbish().as8byte()) { // ok now lock main mutex, but not while mLastRecievedLoggedMeMutex is locked or we might get deadlocks as another thread updates mLastKnownRubbishNode with the mutexes locking in the other order g.unlock(); MegaNode* newPtr = nullptr; { SdkMutexGuard lock(sdkMutex); newPtr = MegaNodePrivate::fromNode(client->nodeByHandle(client->mNodeManager.getRootNodeRubbish()).get()); } g.lock(); mLastKnownRubbishNode.reset(newPtr); } return mLastKnownRubbishNode ? mLastKnownRubbishNode->copy() : nullptr; } MegaNode *MegaApiImpl::getRootNode(MegaNode *node) { MegaNode *rootnode = NULL; SdkMutexGuard lock(sdkMutex); std::shared_ptr<Node> n; if (node && (n = client->nodebyhandle(node->getHandle()))) { while (n->parent) { n = n->parent; } rootnode = MegaNodePrivate::fromNode(n.get()); } return rootnode; } bool MegaApiImpl::isInRootnode(MegaNode *node, int index) { bool ret = false; SdkMutexGuard lock(sdkMutex); if (MegaNode *rootnode = getRootNode(node)) { ret = (index == 0 && rootnode->getHandle() == client->mNodeManager.getRootNodeFiles().as8byte()) || (index == 1 && rootnode->getHandle() == client->mNodeManager.getRootNodeVault().as8byte()) || (index == 2 && rootnode->getHandle() == client->mNodeManager.getRootNodeRubbish().as8byte()); delete rootnode; } return ret; } void MegaApiImpl::setDefaultFilePermissions(int permissions) { SdkMutexGuard lock(sdkMutex); fsAccess->setdefaultfilepermissions(permissions); client->fsaccess->setdefaultfilepermissions(permissions); #ifdef ENABLE_SYNC client->syncs.setdefaultfilepermissions(permissions); #endif } int MegaApiImpl::getDefaultFilePermissions() { return fsAccess->getdefaultfilepermissions(); } void MegaApiImpl::setDefaultFolderPermissions(int permissions) { SdkMutexGuard lock(sdkMutex); fsAccess->setdefaultfolderpermissions(permissions); client->fsaccess->setdefaultfolderpermissions(permissions); #ifdef ENABLE_SYNC client->syncs.setdefaultfolderpermissions(permissions); #endif } int MegaApiImpl::getDefaultFolderPermissions() { return fsAccess->getdefaultfolderpermissions(); } long long MegaApiImpl::getBandwidthOverquotaDelay() { long long result = client->overquotauntil; return result > Waiter::ds ? (result - Waiter::ds) / 10 : 0; } bool MegaApiImpl::userComparatorDefaultASC (User *i, User *j) { if(strcasecmp(i->email.c_str(), j->email.c_str())<=0) return 1; return 0; } m_off_t MegaApiImpl::sizeDifference(Node* i, Node* j) { assert(i->type == FOLDERNODE || i->size == i->getCounter().storage); assert(j->type == FOLDERNODE || j->size == j->getCounter().storage); return i->getCounter().storage - j->getCounter().storage; } char *MegaApiImpl::escapeFsIncompatible(const char *filename, const char *dstPath) { if(!filename) { return NULL; } string name = filename; client->fsaccess->escapefsincompatible(&name, dstPath ? client->fsaccess->getlocalfstype(LocalPath::fromAbsolutePath(dstPath)) : FS_UNKNOWN); return MegaApi::strdup(name.c_str()); } char* MegaApiImpl::unescapeFsIncompatible(const char* name, const char* /*path*/) { if (!name) { return NULL; } string filename = name; client->fsaccess->unescapefsincompatible(&filename); return MegaApi::strdup(filename.c_str()); } bool MegaApiImpl::createThumbnail(const char *imagePath, const char *dstPath) { if (!gfxAccess || !imagePath || !dstPath) { return false; } LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath); LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath); SdkMutexGuard g(sdkMutex); return gfxAccess->savefa(localImagePath, GfxProc::DIMENSIONS[GfxProc::THUMBNAIL], localDstPath); } bool MegaApiImpl::createPreview(const char *imagePath, const char *dstPath) { if (!gfxAccess || !imagePath || !dstPath) { return false; } LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath); LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath); SdkMutexGuard g(sdkMutex); return gfxAccess->savefa(localImagePath, GfxProc::DIMENSIONS[GfxProc::PREVIEW], localDstPath); } bool MegaApiImpl::createAvatar(const char *imagePath, const char *dstPath) { if (!gfxAccess || !imagePath || !dstPath) { return false; } LocalPath localImagePath = LocalPath::fromAbsolutePath(imagePath); LocalPath localDstPath = LocalPath::fromAbsolutePath(dstPath); SdkMutexGuard g(sdkMutex); return gfxAccess->savefa(localImagePath, GfxProc::DIMENSIONS_AVATAR[GfxProc::AVATAR250X250], localDstPath); } void MegaApiImpl::getUploadURL(int64_t fullFileSize, bool forceSSL, MegaRequestListener* listener) { MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL, listener); req->setNumber(fullFileSize); req->setFlag(forceSSL); req->performRequest = [this, req]() { return performRequest_getBackgroundUploadURL(req); }; requestQueue.push(req); waiter->notify(); } void MegaApiImpl::completeUpload(const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, const char *string64FileKey, MegaRequestListener *listener) { MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD, listener); req->setPassword(fingerprintoriginal); req->setNewPassword(fingerprint); req->setName(utf8Name); req->setPrivateKey(string64FileKey); if (parent) { req->setParentHandle(parent->getHandle()); } if (string64UploadToken) { req->setSessionKey(string64UploadToken); } req->performRequest = [this, req]() { return performRequest_completeBackgroundUpload(req); }; requestQueue.push(req); waiter->notify(); } void MegaApiImpl::backgroundMediaUploadRequestUploadURL(int64_t fullFileSize, MegaBackgroundMediaUpload* state, MegaRequestListener *listener) { MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL, listener); req->setNumber(fullFileSize); req->setFlag(true); // always SSL for background uploads req->setMegaBackgroundMediaUploadPtr(state); req->performRequest = [this, req]() { return performRequest_getBackgroundUploadURL(req); }; requestQueue.push(req); waiter->notify(); } void MegaApiImpl::backgroundMediaUploadComplete(MegaBackgroundMediaUpload* state, const char* utf8Name, MegaNode *parent, const char* fingerprint, const char* fingerprintoriginal, const char *string64UploadToken, MegaRequestListener *listener) { MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD, listener); req->setMegaBackgroundMediaUploadPtr(static_cast<MegaBackgroundMediaUploadPrivate*>(state)); req->setPassword(fingerprintoriginal); req->setNewPassword(fingerprint); req->setName(utf8Name); if (parent) { req->setParentHandle(parent->getHandle()); } if (string64UploadToken) { req->setSessionKey(string64UploadToken); } req->performRequest = [this, req]() { return performRequest_completeBackgroundUpload(req); }; requestQueue.push(req); waiter->notify(); } bool MegaApiImpl::ensureMediaInfo() { #ifdef USE_MEDIAINFO if (client->mediaFileInfo.mediaCodecsReceived) { return true; } else { SdkMutexGuard g(sdkMutex); client->mediaFileInfo.requestCodecMappingsOneTime(client, LocalPath()); return false; } #else return false; #endif } void MegaApiImpl::setOriginalFingerprint(MegaNode* node, const char* originalFingerprint, MegaRequestListener *listener) { MegaRequestPrivate* req = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_NODE, listener); req->setParamType(MegaApi::NODE_ATTR_ORIGINALFINGERPRINT); req->setText(originalFingerprint); req->setNodeHandle(node->getHandle()); req->setFlag(true); req->performRequest = [this, req]() { return performRequest_setAttrNode(req); }; requestQueue.push(req); waiter->notify(); } bool MegaApiImpl::isOnline() { return !client->httpio->noinetds; } #ifdef HAVE_LIBUV bool MegaApiImpl::httpServerStart(bool localOnly, int port, bool useTLS, const char *certificatepath, const char *keypath, bool useIPv6) { #ifndef ENABLE_EVT_TLS if (useTLS) { LOG_err << "Could not start HTTP server: TLS is not supported in current compilation"; return false; } #endif if (useTLS && (!certificatepath || !keypath || !strlen(certificatepath) || !strlen(keypath))) { LOG_err << "Could not start HTTP server: No certificate/key provided"; return false; } SdkMutexGuard g(sdkMutex); if (httpServer && httpServer->getPort() == port && httpServer->isLocalOnly() == localOnly) { httpServer->clearAllowedHandles(); return true; } httpServerStop(); httpServer = new MegaHTTPServer(this, basePath, useTLS, certificatepath ? certificatepath : string(), keypath ? keypath : string(), useIPv6); httpServer->setMaxBufferSize(httpServerMaxBufferSize); httpServer->setMaxOutputSize(httpServerMaxOutputSize); httpServer->enableFileServer(httpServerEnableFiles); httpServer->enableOfflineAttribute(httpServerOfflineAttributeEnabled); httpServer->enableFolderServer(httpServerEnableFolders); httpServer->setRestrictedMode(httpServerRestrictedMode); httpServer->enableSubtitlesSupport(httpServerRestrictedMode != 0); bool result = httpServer->start(port, localOnly); if (!result) { MegaHTTPServer *server = httpServer; httpServer = NULL; g.unlock(); delete server; } return result; } void MegaApiImpl::httpServerStop() { SdkMutexGuard g(sdkMutex); if (httpServer) { MegaHTTPServer *server = httpServer; httpServer = NULL; g.unlock(); server->stop(); delete server; } } int MegaApiImpl::httpServerIsRunning() { SdkMutexGuard g(sdkMutex); if (httpServer) { return httpServer->getPort(); } return 0; } char *MegaApiImpl::httpServerGetLocalLink(MegaNode *node) { if (!node) { return NULL; } SdkMutexGuard g(sdkMutex); if (!httpServer) { return NULL; } return httpServer->getLink(node, "http"); } char *MegaApiImpl::httpServerGetLocalWebDavLink(MegaNode *node) { if (!node) { return NULL; } SdkMutexGuard g(sdkMutex); if (!httpServer) { return NULL; } return httpServer->getWebDavLink(node); } MegaStringList *MegaApiImpl::httpServerGetWebDavLinks() { SdkMutexGuard g(sdkMutex); if (!httpServer) { return NULL; } set<handle> handles = httpServer->getAllowedWebDavHandles(); string_vector listoflinks; for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it) { handle h = *it; MegaNode *n = getNodeByHandle(h); if (n) { unique_ptr<char[]> link(httpServer->getWebDavLink(n)); listoflinks.push_back(link.get()); } } return new MegaStringListPrivate(std::move(listoflinks)); } MegaNodeList *MegaApiImpl::httpServerGetWebDavAllowedNodes() { SdkMutexGuard g(sdkMutex); if (!httpServer) { return NULL; } set<handle> handles = httpServer->getAllowedWebDavHandles(); vector<std::shared_ptr<Node>> listofnodes; for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it) { handle h = *it; std::shared_ptr<Node>n = client->nodebyhandle(h); if (n) { listofnodes.push_back(n); } } return new MegaNodeListPrivate(listofnodes); } void MegaApiImpl::httpServerRemoveWebDavAllowedNode(MegaHandle handle) { SdkMutexGuard g(sdkMutex); if (httpServer) { httpServer->removeAllowedWebDavHandle(handle); } } void MegaApiImpl::httpServerRemoveWebDavAllowedNodes() { SdkMutexGuard g(sdkMutex); if (httpServer) { httpServer->clearAllowedHandles(); } } void MegaApiImpl::httpServerSetMaxBufferSize(int bufferSize) { SdkMutexGuard g(sdkMutex); httpServerMaxBufferSize = bufferSize <= 0 ? 0 : bufferSize; httpServerMaxOutputSize = httpServerMaxBufferSize / 10; if (httpServer) { httpServer->setMaxBufferSize(httpServerMaxBufferSize); httpServer->setMaxOutputSize(httpServerMaxOutputSize); } } int MegaApiImpl::httpServerGetMaxBufferSize() { SdkMutexGuard g(sdkMutex); if (httpServerMaxBufferSize) { return httpServerMaxBufferSize; } else { return StreamingBuffer::MAX_BUFFER_SIZE; } } void MegaApiImpl::httpServerSetMaxOutputSize(int outputSize) { SdkMutexGuard g(sdkMutex); httpServerMaxOutputSize = outputSize <= 0 ? 0 : outputSize; if (httpServer) { httpServer->setMaxOutputSize(httpServerMaxOutputSize); } } int MegaApiImpl::httpServerGetMaxOutputSize() { SdkMutexGuard g(sdkMutex); if (httpServerMaxOutputSize) { return httpServerMaxOutputSize; } else { return StreamingBuffer::MAX_OUTPUT_SIZE; } } void MegaApiImpl::httpServerEnableFileServer(bool enable) { SdkMutexGuard g(sdkMutex); this->httpServerEnableFiles = enable; if (httpServer) { httpServer->enableFileServer(enable); } } bool MegaApiImpl::httpServerIsFileServerEnabled() { return httpServerEnableFiles; } void MegaApiImpl::httpServerEnableFolderServer(bool enable) { SdkMutexGuard g(sdkMutex); this->httpServerEnableFolders = enable; if (httpServer) { httpServer->enableFolderServer(enable); } } void MegaApiImpl::httpServerEnableOfflineAttribute(bool enable) { SdkMutexGuard g(sdkMutex); this->httpServerOfflineAttributeEnabled = enable; if (httpServer) { httpServer->enableOfflineAttribute(enable); } } bool MegaApiImpl::httpServerIsFolderServerEnabled() { return httpServerEnableFolders; } bool MegaApiImpl::httpServerIsOfflineAttributeEnabled() { return httpServerOfflineAttributeEnabled; } void MegaApiImpl::httpServerSetRestrictedMode(int mode) { if (mode != MegaApi::TCP_SERVER_DENY_ALL && mode != MegaApi::TCP_SERVER_ALLOW_ALL && mode != MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS && mode != MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK) { return; } SdkMutexGuard g(sdkMutex); httpServerRestrictedMode = mode; if (httpServer) { httpServer->setRestrictedMode(httpServerRestrictedMode); } } int MegaApiImpl::httpServerGetRestrictedMode() { return httpServerRestrictedMode; } void MegaApiImpl::httpServerEnableSubtitlesSupport(bool enable) { SdkMutexGuard g(sdkMutex); httpServerSubtitlesSupportEnabled = enable; if (httpServer) { httpServer->enableSubtitlesSupport(httpServerSubtitlesSupportEnabled); } } bool MegaApiImpl::httpServerIsSubtitlesSupportEnabled() { return httpServerSubtitlesSupportEnabled; } bool MegaApiImpl::httpServerIsLocalOnly() { bool localOnly = true; SdkMutexGuard g(sdkMutex); if (httpServer) { localOnly = httpServer->isLocalOnly(); } return localOnly; } void MegaApiImpl::httpServerAddListener(MegaTransferListener *listener) { if (!listener) { return; } SdkMutexGuard g(sdkMutex); httpServerListeners.insert(listener); } void MegaApiImpl::httpServerRemoveListener(MegaTransferListener *listener) { if (!listener) { return; } SdkMutexGuard g(sdkMutex); httpServerListeners.erase(listener); } void MegaApiImpl::fireOnStreamingStart(MegaTransferPrivate *transfer) { for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++) (*it)->onTransferStart(api, transfer); } void MegaApiImpl::fireOnStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++) (*it)->onTransferTemporaryError(api, transfer, e.get()); } void MegaApiImpl::fireOnStreamingFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { if(e->getErrorCode()) { LOG_warn << "Streaming request finished with error: " << e->getErrorString(); } else { LOG_info << "Streaming request finished"; } for(set<MegaTransferListener *>::iterator it = httpServerListeners.begin(); it != httpServerListeners.end() ; it++) (*it)->onTransferFinish(api, transfer, e.get()); delete transfer; } bool MegaApiImpl::ftpServerStart(bool localOnly, int port, int dataportBegin, int dataPortEnd, bool useTLS, const char *certificatepath, const char *keypath) { #ifndef ENABLE_EVT_TLS if (useTLS) { LOG_err << "Could not start FTP server: TLS is not supported in current compilation"; return false; } #endif SdkMutexGuard g(sdkMutex); if (ftpServer && ftpServer->getPort() == port && ftpServer->isLocalOnly() == localOnly) { ftpServer->clearAllowedHandles(); return true; } ftpServerStop(); ftpServer = new MegaFTPServer(this, basePath, dataportBegin, dataPortEnd, useTLS, certificatepath ? certificatepath : string(), keypath ? keypath : string()); ftpServer->setRestrictedMode(MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS); ftpServer->setRestrictedMode(ftpServerRestrictedMode); ftpServer->setMaxBufferSize(ftpServerMaxBufferSize); ftpServer->setMaxOutputSize(ftpServerMaxOutputSize); bool result = ftpServer->start(port, localOnly); if (!result) { MegaFTPServer *server = ftpServer; ftpServer = NULL; g.unlock(); delete server; } return result; } void MegaApiImpl::ftpServerStop() { SdkMutexGuard g(sdkMutex); if (ftpServer) { MegaFTPServer *server = ftpServer; ftpServer = NULL; g.unlock(); server->stop(); delete server; } } int MegaApiImpl::ftpServerIsRunning() { SdkMutexGuard g(sdkMutex); if (ftpServer) { return ftpServer->getPort(); } return 0; } char *MegaApiImpl::ftpServerGetLocalLink(MegaNode *node) { if (!node) { return NULL; } SdkMutexGuard g(sdkMutex); if (!ftpServer) { return NULL; } return ftpServer->getLink(node, "ftp"); } MegaStringList *MegaApiImpl::ftpServerGetLinks() { SdkMutexGuard g(sdkMutex); if (!ftpServer) { return NULL; } set<handle> handles = ftpServer->getAllowedHandles(); string_vector listoflinks; for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it) { handle h = *it; MegaNode *n = getNodeByHandle(h); if (n) { unique_ptr<char[]> link(ftpServer->getLink(n)); listoflinks.push_back(link.get()); } } return new MegaStringListPrivate(std::move(listoflinks)); } MegaNodeList *MegaApiImpl::ftpServerGetAllowedNodes() { SdkMutexGuard g(sdkMutex); if (!ftpServer) { return NULL; } set<handle> handles = ftpServer->getAllowedHandles(); vector<std::shared_ptr<Node>> listofnodes; for (std::set<handle>::iterator it = handles.begin(); it != handles.end(); ++it) { handle h = *it; std::shared_ptr<Node>n = client->nodebyhandle(h); if (n) { listofnodes.push_back(n); } } return new MegaNodeListPrivate(listofnodes); } void MegaApiImpl::ftpServerRemoveAllowedNode(MegaHandle handle) { SdkMutexGuard g(sdkMutex); if (ftpServer) { ftpServer->removeAllowedHandle(handle); } } void MegaApiImpl::ftpServerRemoveAllowedNodes() { SdkMutexGuard g(sdkMutex); if (ftpServer) { ftpServer->clearAllowedHandles(); } } void MegaApiImpl::ftpServerSetMaxBufferSize(int bufferSize) { SdkMutexGuard g(sdkMutex); ftpServerMaxBufferSize = bufferSize <= 0 ? 0 : bufferSize; if (ftpServer) { ftpServer->setMaxBufferSize(ftpServerMaxBufferSize); } } int MegaApiImpl::ftpServerGetMaxBufferSize() { SdkMutexGuard g(sdkMutex); if (ftpServerMaxBufferSize) { return ftpServerMaxBufferSize; } else { return StreamingBuffer::MAX_BUFFER_SIZE; } } void MegaApiImpl::ftpServerSetMaxOutputSize(int outputSize) { SdkMutexGuard g(sdkMutex); ftpServerMaxOutputSize = outputSize <= 0 ? 0 : outputSize; if (ftpServer) { ftpServer->setMaxOutputSize(ftpServerMaxOutputSize); } } int MegaApiImpl::ftpServerGetMaxOutputSize() { SdkMutexGuard g(sdkMutex); if (ftpServerMaxOutputSize) { return ftpServerMaxOutputSize; } else { return StreamingBuffer::MAX_OUTPUT_SIZE; } } void MegaApiImpl::ftpServerSetRestrictedMode(int mode) { if (mode != MegaApi::TCP_SERVER_DENY_ALL && mode != MegaApi::TCP_SERVER_ALLOW_ALL && mode != MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS && mode != MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK) { return; } SdkMutexGuard g(sdkMutex); ftpServerRestrictedMode = mode; if (ftpServer) { ftpServer->setRestrictedMode(ftpServerRestrictedMode); } } int MegaApiImpl::ftpServerGetRestrictedMode() { return ftpServerRestrictedMode; } bool MegaApiImpl::ftpServerIsLocalOnly() { bool localOnly = true; SdkMutexGuard g(sdkMutex); if (ftpServer) { localOnly = ftpServer->isLocalOnly(); } return localOnly; } void MegaApiImpl::ftpServerAddListener(MegaTransferListener *listener) { if (!listener) { return; } SdkMutexGuard g(sdkMutex); ftpServerListeners.insert(listener); } void MegaApiImpl::ftpServerRemoveListener(MegaTransferListener *listener) { if (!listener) { return; } SdkMutexGuard g(sdkMutex); ftpServerListeners.erase(listener); } void MegaApiImpl::fireOnFtpStreamingStart(MegaTransferPrivate *transfer) { assert(threadId == std::this_thread::get_id()); for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++) (*it)->onTransferStart(api, transfer); } void MegaApiImpl::fireOnFtpStreamingTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { assert(threadId == std::this_thread::get_id()); for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++) (*it)->onTransferTemporaryError(api, transfer, e.get()); } void MegaApiImpl::fireOnFtpStreamingFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { assert(threadId == std::this_thread::get_id()); if(e->getErrorCode()) { LOG_warn << "Streaming request finished with error: " << e->getErrorString(); } else { LOG_info << "Streaming request finished"; } for(set<MegaTransferListener *>::iterator it = ftpServerListeners.begin(); it != ftpServerListeners.end() ; it++) (*it)->onTransferFinish(api, transfer, e.get()); delete transfer; } #endif #ifdef ENABLE_CHAT void MegaApiImpl::sendChatStats(const char *data, int port, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_STATS, listener); request->setName(data); request->setNumber(port); request->setParamType(1); request->performRequest = [this, request]() { return performRequest_chatStats(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::sendChatLogs(const char *data, MegaHandle userid, MegaHandle callid, int port, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_STATS, listener); request->setName(data); request->setNodeHandle(userid); request->setParentHandle(callid); request->setParamType(2); request->setNumber(port); request->performRequest = [this, request]() { return performRequest_chatStats(request); }; requestQueue.push(request); waiter->notify(); } MegaTextChatList *MegaApiImpl::getChatList() { SdkMutexGuard g(sdkMutex); return new MegaTextChatListPrivate(&client->chats); } MegaHandleList *MegaApiImpl::getAttachmentAccess(MegaHandle chatid, MegaHandle h) { MegaHandleList *uhList = new MegaHandleListPrivate(); if (chatid == INVALID_HANDLE || h == INVALID_HANDLE) { return uhList; } SdkMutexGuard g(sdkMutex); textchat_map::iterator itc = client->chats.find(chatid); if (itc != client->chats.end()) { set<handle> userList = itc->second->getUsersOfAttachment(h); std::for_each(userList.begin(), userList.end(), [uhList](const handle& u) { uhList->addMegaHandle(u); }); } return uhList; } bool MegaApiImpl::hasAccessToAttachment(MegaHandle chatid, MegaHandle h, MegaHandle uh) { bool ret = false; if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || uh == INVALID_HANDLE) { return ret; } SdkMutexGuard g(sdkMutex); textchat_map::iterator itc = client->chats.find(chatid); if (itc != client->chats.end()) { ret = itc->second->isUserOfAttachment(h, uh); } return ret; } const char* MegaApiImpl::getFileAttribute(MegaHandle h) { char* fileAttributes = NULL; SdkMutexGuard g(sdkMutex); if (std::shared_ptr<Node> node = client->nodebyhandle(h)) { fileAttributes = MegaApi::strdup(node->fileattrstring.c_str()); } return fileAttributes; } void MegaApiImpl::enableRichPreviews(bool enable, MegaRequestListener *listener) { MegaStringMap *stringMap = new MegaStringMapPrivate(); string rawvalue = enable ? "1" : "0"; string base64value; Base64::btoa(rawvalue, base64value); stringMap->set("num", base64value.c_str()); setUserAttr(MegaApi::USER_ATTR_RICH_PREVIEWS, stringMap, listener); delete stringMap; } void MegaApiImpl::isRichPreviewsEnabled(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_RICH_PREVIEWS); request->setNumDetails(0); // 0 --> flag should indicate whether rich-links are enabled or not request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::shouldShowRichLinkWarning(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_RICH_PREVIEWS); request->setNumDetails(1); // 1 --> flag should indicate whether to show the warning or not request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setRichLinkWarningCounterValue(int value, MegaRequestListener *listener) { MegaStringMap *stringMap = new MegaStringMapPrivate(); std::ostringstream oss; oss << value; string base64value; Base64::btoa(oss.str(), base64value); stringMap->set("c", base64value.c_str()); setUserAttr(MegaApi::USER_ATTR_RICH_PREVIEWS, stringMap, listener); delete stringMap; } void MegaApiImpl::enableGeolocation(MegaRequestListener *listener) { MegaStringMap *stringMap = new MegaStringMapPrivate(); string base64value; Base64::btoa("1", base64value); stringMap->set("v", base64value.c_str()); setUserAttr(MegaApi::USER_ATTR_GEOLOCATION, stringMap, listener); delete stringMap; } void MegaApiImpl::isGeolocationEnabled(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_GEOLOCATION); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::isChatNotifiable(MegaHandle chatid) { std::unique_ptr<MegaPushNotificationSettingsPrivate> pushSettings = getMegaPushNotificationSetting(); if (pushSettings) { if (pushSettings->isChatAlwaysNotifyEnabled(chatid)) { return true; } return (!pushSettings->isChatDndEnabled(chatid) && isGlobalNotifiable(pushSettings.get()) && !pushSettings->isGlobalChatsDndEnabled()); } return true; } void MegaApiImpl::setSFUid(int sfuid) { SdkMutexGuard g(sdkMutex); client->mSfuid = sfuid; } #endif void MegaApiImpl::getCameraUploadsFolder(bool secondary, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER); request->setFlag(secondary); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setCameraUploadsFolder(MegaHandle nodehandle, bool secondary, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); MegaStringMapPrivate stringMap; const char *key = secondary ? "sh" : "h"; stringMap.set(key, Base64Str<MegaClient::NODEHANDLE>(nodehandle)); request->setMegaStringMap(&stringMap); request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER); request->setFlag(secondary); request->setNodeHandle(nodehandle); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setCameraUploadsFolders(MegaHandle primaryFolder, MegaHandle secondaryFolder, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); MegaStringMapPrivate stringMap; if (!ISUNDEF(primaryFolder)) { stringMap.set("h", Base64Str<MegaClient::NODEHANDLE>(primaryFolder)); } if (!ISUNDEF(secondaryFolder)) { stringMap.set("sh", Base64Str<MegaClient::NODEHANDLE>(secondaryFolder)); } request->setMegaStringMap(&stringMap); request->setParamType(MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER); request->setNodeHandle(primaryFolder); request->setParentHandle(secondaryFolder); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMyChatFilesFolder(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setMyChatFilesFolder(MegaHandle nodehandle, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); MegaStringMapPrivate stringMap; stringMap.set("h", Base64Str<MegaClient::NODEHANDLE>(nodehandle)); request->setMegaStringMap(&stringMap); request->setParamType(MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER); request->setNodeHandle(nodehandle); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUserAlias(MegaHandle uh, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_ALIAS); request->setNodeHandle(uh); request->setText(Base64Str<MegaClient::USERHANDLE>(uh)); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setUserAlias(MegaHandle uh, const char *alias, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); MegaStringMapPrivate stringMap; string buf = alias ? alias : ""; // alias is null to remove it stringMap.set(Base64Str<MegaClient::USERHANDLE>(uh), Base64::btoa(buf).c_str()); request->setMegaStringMap(&stringMap); request->setParamType(MegaApi::USER_ATTR_ALIAS); request->setNodeHandle(uh); request->setText(alias); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPushNotificationSettings(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_PUSH_SETTINGS); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setPushNotificationSettings(MegaPushNotificationSettings *settings, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_PUSH_SETTINGS); request->setMegaPushNotificationSettings(settings); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::isSharesNotifiable() { std::unique_ptr<MegaPushNotificationSettingsPrivate> pushSettings = getMegaPushNotificationSetting(); return !pushSettings || (pushSettings->isSharesEnabled() && isScheduleNotifiable(pushSettings.get())); } bool MegaApiImpl::isContactsNotifiable() { std::unique_ptr<MegaPushNotificationSettingsPrivate> pushSettings = getMegaPushNotificationSetting(); return !pushSettings || (pushSettings->isContactsEnabled() && isScheduleNotifiable(pushSettings.get())); } void MegaApiImpl::getAccountAchievements(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ACHIEVEMENTS, listener); request->performRequest = [this, request]() { return performRequest_getAchievements(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMegaAchievements(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ACHIEVEMENTS, listener); request->setFlag(true); request->performRequest = [this, request]() { return performRequest_getAchievements(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::catchup(MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CATCHUP, listener); scRequestQueue.push(request); waiter->notify(); } MegaUserList* MegaApiImpl::getContacts() { SdkMutexGuard g(sdkMutex); vector<User*> vUsers; for (user_map::iterator it = client->users.begin() ; it != client->users.end() ; it++ ) { User *u = &(it->second); if (u->userhandle == client->me) { continue; } vector<User *>::iterator i = std::lower_bound(vUsers.begin(), vUsers.end(), u, MegaApiImpl::userComparatorDefaultASC); vUsers.insert(i, u); } return new MegaUserListPrivate(vUsers.data(), int(vUsers.size())); } MegaUser* MegaApiImpl::getContact(const char *uid) { SdkMutexGuard g(sdkMutex); MegaUser *user = MegaUserPrivate::fromUser(client->finduser(uid, 0)); if (user && user->getHandle() == client->me) { delete user; user = NULL; // it's not a contact } return user; } MegaUserAlertList* MegaApiImpl::getUserAlerts() { SdkMutexGuard g(sdkMutex); vector<UserAlert::Base*> v; v.reserve(client->useralerts.alerts.size()); for (UserAlerts::Alerts::iterator it = client->useralerts.alerts.begin(); it != client->useralerts.alerts.end(); ++it) { if (!(*it)->removed()) { v.push_back(*it); } } return new MegaUserAlertListPrivate(v.data(), int(v.size()), client); } int MegaApiImpl::getNumUnreadUserAlerts() { int result = 0; SdkMutexGuard g(sdkMutex); for (UserAlerts::Alerts::iterator it = client->useralerts.alerts.begin(); it != client->useralerts.alerts.end(); ++it) { if (!(*it)->removed() && !(*it)->seen()) { result++; } } return result; } MegaNodeList* MegaApiImpl::getInShares(MegaUser *megaUser, int order) { if (!megaUser) { return new MegaNodeListPrivate(); } SdkMutexGuard g(sdkMutex); sharedNode_vector vNodes; User *user = client->finduser(megaUser->getEmail(), 0); if (!user) { return new MegaNodeListPrivate(); } for (handle_set::iterator sit = user->sharing.begin(); sit != user->sharing.end(); sit++) { std::shared_ptr<Node> n; if ((n = client->nodebyhandle(*sit)) && !n->parent) { vNodes.push_back(n); } } MegaNodeList *nodeList; if (vNodes.size()) { sortByComparatorFunction(vNodes, order, *client); nodeList = new MegaNodeListPrivate(vNodes); } else { nodeList = new MegaNodeListPrivate(); } return nodeList; } MegaNodeList* MegaApiImpl::getInShares(int order) { SdkMutexGuard lock(sdkMutex); sharedNode_vector sharedNodes = client->getInShares(); sortByComparatorFunction(sharedNodes, order, *client); return new MegaNodeListPrivate(sharedNodes); } MegaShareList* MegaApiImpl::getInSharesList(int order) { SdkMutexGuard lock(sdkMutex); sharedNode_vector nodes = client->getVerifiedInShares(); sortByComparatorFunction(nodes, order, *client); vector<impl::ShareData> shares; for (const auto& node: nodes) { shares.emplace_back(node->nodehandle, node->inshare.get(), true); } return new MegaShareListPrivate(shares); } MegaUser *MegaApiImpl::getUserFromInShare(MegaNode *megaNode, bool recurse) { if (!megaNode) { return NULL; } MegaUser *user = NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if (recurse && node) { node = client->getrootnode(node); } if (node && node->inshare && node->inshare->user) { user = MegaUserPrivate::fromUser(node->inshare->user); } return user; } bool MegaApiImpl::isPendingShare(MegaNode *megaNode) { if(!megaNode) return false; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if(!node) { return false; } return node->pendingshares != NULL; } // // Retrieve nodes that have any outgoing shares (including pending ones). Each node appears // only once in the list. // sharedNode_vector MegaApiImpl::getSharedNodes() const { sharedNode_vector outshares = client->mNodeManager.getNodesWithOutShares(); // Avoid duplicate nodes present in both outshares and pending shares sharedNode_vector pendingShares = client->mNodeManager.getNodesWithPendingOutShares(); for (auto& pendingShare : pendingShares) { bool found = false; for (auto& node : outshares) { if (node->nodeHandle() == pendingShare->nodeHandle()) { found = true; break; } } if (!found) { outshares.push_back(pendingShare); } } return outshares; } MegaShareList* MegaApiImpl::getOutShares(int order) { SdkMutexGuard guard(sdkMutex); // Get nodes having any outgoing shares sharedNode_vector sharedNodes = getSharedNodes(); // Sort nodes in place MegaApiImpl::sortByComparatorFunction(sharedNodes, order, *client); // Extract shares from nodes auto shares = impl::ShareExtractor::extractOutShares(sharedNodes, client->mKeyManager); // Sort shares in place impl::ShareSorter::sort(shares, order); return new MegaShareListPrivate(shares); } MegaShareList* MegaApiImpl::getOutShares(MegaNode *megaNode) { if(!megaNode) return new MegaShareListPrivate(); SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if(!node) { return new MegaShareListPrivate(); } return new MegaShareListPrivate( impl::ShareExtractor::extractOutShares({node}, client->mKeyManager)); } MegaShareList *MegaApiImpl::getPendingOutShares() { SdkMutexGuard guard(sdkMutex); sharedNode_vector nodes = client->mNodeManager.getNodesWithPendingOutShares(); return new MegaShareListPrivate( impl::ShareExtractor::extractPendingOutShares(nodes, client->mKeyManager)); } MegaShareList *MegaApiImpl::getPendingOutShares(MegaNode *megaNode) { if(!megaNode) { return new MegaShareListPrivate(); } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if (!node) { return new MegaShareListPrivate(); } return new MegaShareListPrivate( impl::ShareExtractor::extractPendingOutShares({node}, client->mKeyManager)); } bool MegaApiImpl::isPrivateNode(MegaHandle h) { SdkMutexGuard lock(sdkMutex); return client->isPrivateNode(NodeHandle().set6byte(h)); } bool MegaApiImpl::isForeignNode(MegaHandle h) { SdkMutexGuard lock(sdkMutex); return client->isForeignNode(NodeHandle().set6byte(h)); } MegaNodeList *MegaApiImpl::getPublicLinks(int order) { SdkMutexGuard g(sdkMutex); sharedNode_vector vNodes = client->mNodeManager.getNodesWithLinks(); // avoid to return the link associated to the S4 container, in line with Webclient if (client->mIsS4Enabled) { for (auto it = vNodes.begin(); it != vNodes.end(); ++it) { if (*it && (*it)->nodeHandle().eq(client->mS4Container)) { vNodes.erase(it); break; } } } sortByComparatorFunction(vNodes, order, *client); return new MegaNodeListPrivate(vNodes); } MegaContactRequestList* MegaApiImpl::getIncomingContactRequests() const { SdkMutexGuard g(sdkMutex); vector<PendingContactRequest*> vContactRequests; for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++) { if(!it->second->isoutgoing && !it->second->removed()) { vContactRequests.push_back(it->second.get()); } } return new MegaContactRequestListPrivate(vContactRequests.data(), int(vContactRequests.size())); } MegaContactRequestList* MegaApiImpl::getOutgoingContactRequests() const { SdkMutexGuard g(sdkMutex); vector<PendingContactRequest*> vContactRequests; for (handlepcr_map::iterator it = client->pcrindex.begin(); it != client->pcrindex.end(); it++) { if(it->second->isoutgoing && !it->second->removed()) { vContactRequests.push_back(it->second.get()); } } return new MegaContactRequestListPrivate(vContactRequests.data(), int(vContactRequests.size())); } int MegaApiImpl::getAccess(const std::variant<MegaNode*, MegaHandle>& nodeOrNodeHandle) { MegaHandle nodeHandle{INVALID_HANDLE}; if (auto node = std::get_if<MegaNode*>(&nodeOrNodeHandle)) { if (*node) { nodeHandle = (*node)->getHandle(); } } else if (auto handle = std::get_if<MegaHandle>(&nodeOrNodeHandle)) { nodeHandle = *handle; } if (nodeHandle == INVALID_HANDLE) { return MegaShare::ACCESS_UNKNOWN; } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(nodeHandle); if(!node) { return MegaShare::ACCESS_UNKNOWN; } if (!client->loggedin()) { return MegaShare::ACCESS_READ; } if(node->type > FOLDERNODE) { return MegaShare::ACCESS_OWNER; } Node *n = node.get(); accesslevel_t a = OWNER; while (n) { if (n->inshare) { a = n->inshare->access; break; } n = n->parent.get(); } switch(a) { case RDONLY: return MegaShare::ACCESS_READ; case RDWR: return MegaShare::ACCESS_READWRITE; case FULL: return MegaShare::ACCESS_FULL; default: return MegaShare::ACCESS_OWNER; } } bool MegaApiImpl::processMegaTree(MegaNode* n, MegaTreeProcessor* processor, bool recursive) { if (!n) { return true; } if (!processor) { return false; } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = NULL; if (!n->isForeign() && !n->isPublic()) { node = client->nodebyhandle(n->getHandle()); } if (!node) { if (n->getType() != FILENODE) { MegaNodeList *nList = n->getChildren(); if (nList) { for (int i = 0; i < nList->size(); i++) { MegaNode *child = nList->get(i); if (recursive) { if (!processMegaTree(child, processor)) { return 0; } } else { if (!processor->processMegaNode(child)) { return 0; } } } } } return processor->processMegaNode(n); } if (node->type != FILENODE) { sharedNode_list nodeList = client->getChildren(node.get()); for (sharedNode_list::iterator it = nodeList.begin(); it != nodeList.end(); ) { Node* sharedNode = it->get(); it++; unique_ptr<MegaNode> megaNode(MegaNodePrivate::fromNode(sharedNode)); if (recursive) { if (!processMegaTree(megaNode.get(), processor)) { return 0; } } else { if (!processor->processMegaNode(megaNode.get())) { return 0; } } } } return processor->processMegaNode(n); } MegaNode *MegaApiImpl::createForeignFileNode(MegaHandle handle, const char *key, const char *name, m_off_t size, m_off_t mtime, const char* fingerprintCrc, MegaHandle parentHandle, const char* privateauth, const char *publicauth, const char *chatauth) { string nodekey; string fileattrsting; nodekey.resize(strlen(key) * 3 / 4 + 3); nodekey.resize( static_cast<size_t>(Base64::atob(key, (byte*)nodekey.data(), int(nodekey.size())))); string fingerprintStr; string sdkFingerprintStr; if (fingerprintCrc) { FileFingerprint ff; ff.size = size; ff.mtime = mtime; int bytesWritten = Base64::atob(fingerprintCrc, (byte*)&ff.crc, sizeof(ff.crc)); assert(bytesWritten == sizeof(ff.crc)); if (bytesWritten == sizeof(ff.crc)) { // only contains crc, mtime. because Node has size serialized separately ff.serializefingerprint(&fingerprintStr); // prepend size on the front sdkFingerprintStr = MegaNodePrivate::addAppPrefixToFingerprint(fingerprintStr, size); } } return new MegaNodePrivate(name, FILENODE, size, mtime, mtime, handle, &nodekey, &fileattrsting, sdkFingerprintStr.empty() ? nullptr : sdkFingerprintStr.c_str(), NULL, INVALID_HANDLE, parentHandle, privateauth, publicauth, false, true, chatauth, true); } MegaNode *MegaApiImpl::createForeignFolderNode(MegaHandle handle, const char *name, MegaHandle parentHandle, const char *privateauth, const char *publicauth) { string nodekey; string fileattrsting; return new MegaNodePrivate(name, FOLDERNODE, 0, 0, 0, handle, &nodekey, &fileattrsting, NULL, NULL, INVALID_HANDLE, parentHandle, privateauth, publicauth, false, true); } MegaNode *MegaApiImpl::authorizeNode(MegaNode *node) { if (!node) { return NULL; } if (node->isPublic() || node->isForeign()) { return node->copy(); } MegaNodePrivate *result = NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> n = client->nodebyhandle(node->getHandle()); if (n) { result = new MegaNodePrivate(node); authorizeMegaNodePrivate(result); } return result; } void MegaApiImpl::authorizeMegaNodePrivate(MegaNodePrivate *node) { // Versions are not added to authorized MegaNodes. // If it's decided to do so, it's needed to modify the processing // of MegaNode copies accordingly node->setForeign(true); if (node->getType() == MegaNode::TYPE_FILE) { char *h = NULL; if (client->sid.size()) { h = getAccountAuth(); node->setPrivateAuth(h); } else { h = MegaApiImpl::handleToBase64(client->mNodeManager.getRootNodeFiles().as8byte()); node->setPublicAuth(h); } delete [] h; } else { MegaNodeList *children = getChildren(node, MegaApi::ORDER_NONE); node->setChildren(children); for (int i = 0; i < children->size(); i++) { MegaNodePrivate *privNode = (MegaNodePrivate *)children->get(i); authorizeMegaNodePrivate(privNode); } } } MegaNode *MegaApiImpl::authorizeChatNode(MegaNode *node, const char *cauth) { if (!node) { return NULL; } MegaNodePrivate *result = new MegaNodePrivate(node); result->setChatAuth(cauth); return result; } const char *MegaApiImpl::getVersion() { return client->version(); } char *MegaApiImpl::getOperatingSystemVersion() { string version; fsAccess->osversion(&version, false); return MegaApi::strdup(version.c_str()); } void MegaApiImpl::setPSA(int id, MegaRequestListener *listener) { std::ostringstream oss; oss << id; string value = oss.str(); setUserAttr(MegaApi::USER_ATTR_LAST_PSA, value.c_str(), listener); } void MegaApiImpl::disableGfxFeatures(bool disable) { client->gfxdisabled = disable; } bool MegaApiImpl::areGfxFeaturesDisabled() { return !client->gfx || client->gfxdisabled; } const char *MegaApiImpl::getUserAgent() { return client->useragent.c_str(); } const char *MegaApiImpl::getBasePath() { return basePath.c_str(); } void MegaApiImpl::changeApiUrl(const char *apiURL, bool disablepkp) { { // change defaults for future MegaApi construction lock_guard<mutex> g(g_APIURL_default_mutex); g_APIURL_default = apiURL; g_disablepkp_default = disablepkp; } // change this MegaApi too SdkMutexGuard g(sdkMutex); client->httpio->APIURL = apiURL; client->httpio->disablepkp = disablepkp; client->abortbackoff(); client->disconnect(); } bool MegaApiImpl::setLanguage(const char *languageCode) { string code; if (!getLanguageCode(languageCode, &code)) { return false; } SdkMutexGuard g(sdkMutex); return client->setlang(&code); } int MegaApiImpl::enableSearchDBIndexes(bool enable) { if (client->loggedin() != sessiontype_t::NOTLOGGEDIN) { LOG_warn << "This method should be called before login"; return API_EACCESS; } client->enableSearchDBIndexes(enable); return API_OK; } int MegaApiImpl::enableLexicographicDBIndexes(bool enable) { if (client->loggedin() != sessiontype_t::NOTLOGGEDIN) { LOG_warn << "This method should be called before login"; return API_EACCESS; } client->enableLexicographicDBIndexes(enable); return API_OK; } string MegaApiImpl::generateViewId() { return MegaClient::generateViewId(client->rng); } void MegaApiImpl::setLanguagePreference(const char *languageCode, MegaRequestListener *listener) { setUserAttr(MegaApi::USER_ATTR_LANGUAGE, languageCode, listener); } void MegaApiImpl::getLanguagePreference(MegaRequestListener *listener) { getUserAttr(NULL, MegaApi::USER_ATTR_LANGUAGE, NULL, 0, listener); } bool MegaApiImpl::getLanguageCode(const char *languageCode, string *code) { if (!languageCode || !code) { return false; } size_t len = strlen(languageCode); if (len < 2 || len > 7) { return false; } code->clear(); string s = languageCode; tolower_string(s); while (s.size() >= 2) { JSON json; nameid id = json.getnameid(s.c_str()); switch (id) { // Regular language codes case makeNameid("ar"): case makeNameid("bg"): case makeNameid("de"): case makeNameid("en"): case makeNameid("es"): case makeNameid("fa"): case makeNameid("fi"): case makeNameid("fr"): case makeNameid("he"): case makeNameid("hu"): case makeNameid("id"): case makeNameid("it"): case makeNameid("nl"): case makeNameid("pl"): case makeNameid("ro"): case makeNameid("ru"): case makeNameid("sk"): case makeNameid("sl"): case makeNameid("sr"): case makeNameid("th"): case makeNameid("tl"): case makeNameid("tr"): case makeNameid("uk"): case makeNameid("vi"): // Not used on apps case makeNameid("cz"): case makeNameid("jp"): case makeNameid("kr"): case makeNameid("br"): case makeNameid("se"): case makeNameid("cn"): case makeNameid("ct"): *code = s; break; // Conversions case makeNameid("cs"): *code = "cz"; break; case makeNameid("ja"): *code = "jp"; break; case makeNameid("ko"): *code = "kr"; break; case makeNameid("pt"): case makeNameid("pt_br"): case makeNameid("pt-br"): case makeNameid("pt_pt"): case makeNameid("pt-pt"): *code = "br"; break; case makeNameid("sv"): *code = "se"; break; case makeNameid("zh"): case makeNameid("zh_cn"): case makeNameid("zh-cn"): case makeNameid("zh_hans"): case makeNameid("zh-hans"): *code = "cn"; break; case makeNameid("zh_tw"): case makeNameid("zh-tw"): case makeNameid("zh_hant"): case makeNameid("zh-hant"): *code = "ct"; break; case makeNameid("in"): *code = "id"; break; case makeNameid("iw"): *code = "he"; break; // Not supported in the web case makeNameid("ee"): case makeNameid("hr"): case makeNameid("ka"): break; default: LOG_debug << "Unknown language code: " << s.c_str(); break; } if (code->size()) { return true; } s.resize(s.size() - 1); } LOG_debug << "Unsupported language code: " << languageCode; return false; } void MegaApiImpl::setFileVersionsOption(bool disable, MegaRequestListener *listener) { string av = disable ? "1" : "0"; setUserAttr(MegaApi::USER_ATTR_DISABLE_VERSIONS, av.data(), listener); } void MegaApiImpl::getFileVersionsOption(MegaRequestListener *listener) { getUserAttr(NULL, MegaApi::USER_ATTR_DISABLE_VERSIONS, NULL, 0, listener); } void MegaApiImpl::setContactLinksOption(bool enable, MegaRequestListener* listener) { string av = enable ? "1" : "0"; setUserAttr(MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION, av.data(), listener); } void MegaApiImpl::getContactLinksOption(MegaRequestListener *listener) { getUserAttr(NULL, MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION, NULL, 0, listener); } void MegaApiImpl::retrySSLerrors(bool enable) { SdkMutexGuard g(sdkMutex); client->retryessl = enable; } void MegaApiImpl::setPublicKeyPinning(bool enable) { SdkMutexGuard g(sdkMutex); client->httpio->disablepkp = !enable; } void MegaApiImpl::pauseActionPackets() { SdkMutexGuard g(sdkMutex); LOG_debug << "Pausing action packets"; client->scpaused = true; } void MegaApiImpl::resumeActionPackets() { SdkMutexGuard g(sdkMutex); LOG_debug << "Resuming action packets"; client->scpaused = false; } bool MegaApiImpl::isValidTypeNode(const Node *node, int type) const { assert(node); if (!client) { return true; } switch (type) { case MegaApi::FILE_TYPE_PHOTO: return client->nodeIsPhoto(node, false); case MegaApi::FILE_TYPE_AUDIO: return client->nodeIsAudio(node); case MegaApi::FILE_TYPE_VIDEO: return client->nodeIsVideo(node); case MegaApi::FILE_TYPE_DOCUMENT: return client->nodeIsDocument(node); case MegaApi::FILE_TYPE_PDF: return client->nodeIsPdf(node); case MegaApi::FILE_TYPE_PRESENTATION: return client->nodeIsPresentation(node); case MegaApi::FILE_TYPE_ARCHIVE: return client->nodeIsArchive(node); case MegaApi::FILE_TYPE_PROGRAM: return client->nodeIsProgram(node); case MegaApi::FILE_TYPE_MISC: return client->nodeIsMiscellaneous(node); case MegaApi::FILE_TYPE_SPREADSHEET: return client->nodeIsSpreadsheet(node); case MegaApi::FILE_TYPE_ALL_DOCS: return client->nodeIsDocument(node) || client->nodeIsPdf(node) || client->nodeIsPresentation(node) || client->nodeIsSpreadsheet(node); case MegaApi::FILE_TYPE_ALL_VISUAL_MEDIA: return client->nodeIsPhoto(node, false) || client->nodeIsVideo(node); case MegaApi::FILE_TYPE_OTHERS: return client->nodeIsOtherType(node); case MegaApi::FILE_TYPE_DEFAULT: default: return true; } } // map to an ACCOUNT_* value to an int that can be compared: // free -> starter -> basic -> essential -> lite -> proi -> proii -> proiii // the ACCOUNT_* values are out of order // int proLevel is a MegaAccountDetails::ACCOUNT_* static inline int orderProLevel(int proLevel) { switch (proLevel) { case MegaAccountDetails::ACCOUNT_TYPE_STARTER: return 1; case MegaAccountDetails::ACCOUNT_TYPE_BASIC: return 2; case MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL: return 3; case MegaAccountDetails::ACCOUNT_TYPE_LITE: return 4; case MegaAccountDetails::ACCOUNT_TYPE_PROI: return 5; case MegaAccountDetails::ACCOUNT_TYPE_PROII: return 6; case MegaAccountDetails::ACCOUNT_TYPE_PROIII: return 7; default: return 0; } } int MegaApiImpl::calcRecommendedProLevel(MegaPricing& pricing, MegaAccountDetails& details) { // if this algorithm changes also have the webclient implementation updated int currProLevel = details.getProLevel(); if (currProLevel == MegaAccountDetails::ACCOUNT_TYPE_BUSINESS || currProLevel == MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI) return currProLevel; // business can not upgrade, flexi can only change to free so we do not recommend that int orderedCurrProLevel = orderProLevel(currProLevel); uint64_t usedStorageBytes = static_cast<uint64_t>(details.getStorageUsed()); int bestProLevel = -1; uint64_t bestStorageBytes = UINT64_MAX; for (int i = 0; i <= pricing.getNumProducts(); ++i) { // only upgrade to starter, basic, essential, lite, pro1, pro2 and pro3 int planProLevel = pricing.getProLevel(i); if (planProLevel < MegaAccountDetails::ACCOUNT_TYPE_PROI || planProLevel > MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL) continue; // only monthly plans int planMonths = pricing.getMonths(i); if (planMonths != 1) continue; // must have enough space for user's data int planStorageGb = pricing.getGBStorage(i); if (planStorageGb < 0) { assert(false && "business plan, should never happen"); continue; } uint64_t planStorageBytes = (uint64_t)planStorageGb * (uint64_t)(1024 * 1024 * 1024); if (usedStorageBytes > planStorageBytes) continue; // must be an upgrade free->starter->basic->essential->lite->proi->proii->proiii int orderedPlanProLevel = orderProLevel(planProLevel); if (orderedCurrProLevel >= orderedPlanProLevel) continue; // get smallest storage if (planStorageBytes >= bestStorageBytes) continue; bestProLevel = planProLevel; bestStorageBytes = planStorageBytes; } if (bestStorageBytes != UINT64_MAX) { assert(bestProLevel != -1); return bestProLevel; } // too much storage required return MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI; } MegaNodeList* MegaApiImpl::search(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage) { // guard against unsupported or removed order criteria assert((MegaApi::ORDER_NONE <= order && order <= MegaApi::ORDER_MODIFICATION_DESC) || (MegaApi::ORDER_LABEL_ASC <= order && order <= MegaApi::ORDER_FAV_DESC)); if (!filter || (filter->byNodeType() == MegaNode::TYPE_FOLDER && filter->byCategory() != MegaApi::FILE_TYPE_DEFAULT)) { return new MegaNodeListPrivate(); } sharedNode_vector searchResults; // search { SdkMutexGuard g(sdkMutex); switch (filter->byLocation()) { case MegaApi::SEARCH_TARGET_ALL: case MegaApi::SEARCH_TARGET_ROOTNODE: // Search on Cloud root and Vault, excluding Rubbish case MegaApi::SEARCH_TARGET_INSHARE: case MegaApi::SEARCH_TARGET_OUTSHARE: case MegaApi::SEARCH_TARGET_PUBLICLINK: searchResults = searchInNodeManager(filter, order, cancelToken, searchPage); break; default: LOG_err << "Search not implemented for Location " << filter->byLocation(); } } // end scope for mutex guard MegaNodeListPrivate* nodeList = new MegaNodeListPrivate(searchResults); return nodeList; } namespace { /** * @brief A helper function to convert a MegaSearchFilter (external layer) into a NodeSearchFilter * (internal). * * @param filter The filter to convert * @param includedShares (Optional) What kind of share nodes to check in the search * @return A NodeSearchFilter object to be used in search methods from the NodeManager */ NodeSearchFilter searchToNodeFilter(const MegaSearchFilter& filter, const ShareType_t includedShares = NO_SHARES) { NodeSearchFilter nf; nf.byName(filter.byName()); nf.byNodeType(static_cast<nodetype_t>(filter.byNodeType())); nf.byCategory(static_cast<MimeType_t>(filter.byCategory())); nf.bySensitivity(static_cast<NodeSearchFilter::BoolFilter>(filter.bySensitivity())); nf.byFavourite(static_cast<NodeSearchFilter::BoolFilter>(filter.byFavourite())); nf.byLocationHandle(filter.byLocationHandle()); nf.setIncludedShares(includedShares); nf.byCreationTimeLowerLimitInSecs(filter.byCreationTimeLowerLimit()); nf.byCreationTimeUpperLimitInSecs(filter.byCreationTimeUpperLimit()); nf.byModificationTimeLowerLimitInSecs(filter.byModificationTimeLowerLimit()); nf.byModificationTimeUpperLimitInSecs(filter.byModificationTimeUpperLimit()); nf.byDescription(filter.byDescription()); nf.byTag(filter.byTag()); nf.useAndForTextQuery(filter.useAndForTextQuery()); return nf; } } sharedNode_vector MegaApiImpl::searchInNodeManager(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage) { int shareType = filter->byLocation() == MegaApi::SEARCH_TARGET_INSHARE ? IN_SHARES : (filter->byLocation() == MegaApi::SEARCH_TARGET_OUTSHARE ? static_cast<ShareType_t>(OUT_SHARES | PENDING_OUTSHARES) : (filter->byLocation() == MegaApi::SEARCH_TARGET_PUBLICLINK ? LINK : NO_SHARES)); NodeSearchFilter nf = searchToNodeFilter(*filter, static_cast<ShareType_t>(shareType)); if (filter->byLocation() == MegaApi::SEARCH_TARGET_ROOTNODE) { // search under Cloud root and Vault nf.byAncestors({ client->mNodeManager.getRootNodeFiles().as8byte(), client->mNodeManager.getRootNodeVault().as8byte(), UNDEF }); } else if (filter->byLocation() == MegaApi::SEARCH_TARGET_ALL && filter->byLocationHandle() == INVALID_HANDLE) { // search under Cloud root, Vault, Rubbish and among in-shares nf.byAncestors({ client->mNodeManager.getRootNodeFiles().as8byte(), client->mNodeManager.getRootNodeVault().as8byte(), client->mNodeManager.getRootNodeRubbish().as8byte() }); nf.setIncludedShares(IN_SHARES); } const NodeSearchPage& np = searchPage ? NodeSearchPage(searchPage->startingOffset(), searchPage->size()) : NodeSearchPage(0, 0); sharedNode_vector results = client->mNodeManager.searchNodes(nf, order, cancelToken, np); return results; } long long MegaApiImpl::getSize(MegaNode *n) { if(!n) return 0; if (n->getType() == MegaNode::TYPE_FILE) { return n->getSize(); } if (n->isForeign()) { MegaSizeProcessor megaSizeProcessor; processMegaTree(n, &megaSizeProcessor); return megaSizeProcessor.getTotalBytes(); } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(n->getHandle()); if(!node) { return 0; } NodeCounter nodeCounter = node->getCounter(); return nodeCounter.storage; } char *MegaApiImpl::getFingerprint(const char *filePath) { if(!filePath) return NULL; auto localpath = LocalPath::fromAbsolutePath(filePath); auto fa = fsAccess->newfileaccess(); if (!fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)) return NULL; FileFingerprint fp; fp.genfingerprint(fa.get()); m_off_t size = fa->size; if(fp.size < 0) return NULL; string fingerprint; fp.serializefingerprint(&fingerprint); string result = MegaNodePrivate::addAppPrefixToFingerprint(fingerprint, size); return MegaApi::strdup(result.c_str()); } void MegaApiImpl::transfer_failed(Transfer* t, const Error& e, dstime timeleft) { for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++) { MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag); if (!transfer) { continue; } processTransferFailed(t, transfer, e, timeleft); } } char *MegaApiImpl::getFingerprint(MegaInputStream *inputStream, int64_t mtime) { if(!inputStream) return NULL; ExternalInputStream is(inputStream); m_off_t size = is.size(); if(size < 0) return NULL; FileFingerprint fp; fp.genfingerprint(&is, mtime); if(fp.size < 0) return NULL; string fingerprint; fp.serializefingerprint(&fingerprint); string result = MegaNodePrivate::addAppPrefixToFingerprint(fingerprint, size); return MegaApi::strdup(result.c_str()); } MegaNode *MegaApiImpl::getNodeByFingerprint(const char *fingerprint) { if(!fingerprint) return NULL; SdkMutexGuard g(sdkMutex); return MegaNodePrivate::fromNode(getNodeByFingerprintInternal(fingerprint).get()); } MegaNodeList* MegaApiImpl::getNodesByFingerprint(const char* fingerprint, const bool excludeMtime) { unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (!fp) { return new MegaNodeListPrivate(); } SdkMutexGuard g(sdkMutex); sharedNode_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp, excludeMtime); return new MegaNodeListPrivate(nodes); } MegaNodeList *MegaApiImpl::getNodesByOriginalFingerprint(const char *originalfingerprint, MegaNode* megaparent) { SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> parent = megaparent ? client->nodebyhandle(megaparent->getHandle()) : NULL; if (!originalfingerprint || (megaparent && (!parent || parent->type == FILENODE))) { return new MegaNodeListPrivate(); } sharedNode_vector nodes = client->mNodeManager.getNodesByOrigFingerprint(originalfingerprint, parent.get()); MegaNodeList *result = new MegaNodeListPrivate(nodes); return result; } MegaNode *MegaApiImpl::getExportableNodeByFingerprint(const char *fingerprint, const char *name) { MegaNode *result = NULL; unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (!fp) { return NULL; } SdkMutexGuard g(sdkMutex); sharedNode_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp); for (auto &node : nodes) { if ((!name || !strcmp(name, node->displayname())) && client->checkaccess(node.get(), OWNER)) { Node *n = node.get(); while (n) { if (n->type == RUBBISHNODE) { node = NULL; break; } n = n->parent.get(); } if (!node) { continue; } result = MegaNodePrivate::fromNode(node.get()); break; } } return result; } MegaNode *MegaApiImpl::getNodeByFingerprint(const char *fingerprint, MegaNode* parent) { if(!fingerprint) return NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> p; if(parent) { p = client->nodebyhandle(parent->getHandle()); } return MegaNodePrivate::fromNode(getNodeByFingerprintInternal(fingerprint, p.get()).get()); } bool MegaApiImpl::hasFingerprint(const char *fingerprint) { return (getNodeByFingerprintInternal(fingerprint) != NULL); } char *MegaApiImpl::getCRC(const char *filePath) { if(!filePath) return NULL; auto localpath = LocalPath::fromAbsolutePath(filePath); auto fa = fsAccess->newfileaccess(); if (!fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)) return NULL; FileFingerprint fp; fp.genfingerprint(fa.get()); if(fp.size < 0) return NULL; string result; result.resize((sizeof fp.crc) * 4 / 3 + 4); result.resize(Base64::btoa((const byte*)fp.crc.data(), sizeof fp.crc, (char*)result.c_str())); return MegaApi::strdup(result.c_str()); } char *MegaApiImpl::getCRCFromFingerprint(const char *fingerprint) { unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (!fp) { return NULL; } string result; result.resize((sizeof fp->crc) * 4 / 3 + 4); result.resize(Base64::btoa((const byte*)fp->crc.data(), sizeof fp->crc, (char*)result.c_str())); return MegaApi::strdup(result.c_str()); } char *MegaApiImpl::getCRC(MegaNode *n) { if(!n) return NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(n->getHandle()); if(!node || node->type != FILENODE || node->size < 0 || !node->isvalid) { return NULL; } string result; result.resize((sizeof node->crc) * 4 / 3 + 4); result.resize(Base64::btoa((const byte*)node->crc.data(), sizeof node->crc.data(), (char*)result.c_str())); return MegaApi::strdup(result.c_str()); } MegaNode *MegaApiImpl::getNodeByCRC(const char *crc, MegaNode *parent) { if(!parent) return NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(parent->getHandle()); if(!node || node->type == FILENODE) { return NULL; } byte binarycrc[sizeof(node->crc)]; Base64::atob(crc, binarycrc, sizeof(binarycrc)); sharedNode_list nodeList = client->getChildren(node.get()); for (sharedNode_list::iterator it = nodeList.begin(); it != nodeList.end(); it++) { Node *child = it->get(); if(!memcmp(child->crc.data(), binarycrc, sizeof(node->crc))) { MegaNode *result = MegaNodePrivate::fromNode(child); return result; } } return NULL; } void MegaApiImpl::file_added(File *f) { Transfer *t = f->transfer; MegaTransferPrivate *transfer = currentTransfer; if (!transfer) { transfer = new MegaTransferPrivate(t->type); transfer->setSyncTransfer(f->syncxfer); if (t->type == GET) { transfer->setNodeHandle(f->h.as8byte()); } else { transfer->setParentHandle(f->h.as8byte()); } // Extract the transfer's logical path. // Set the transfer's raw path. transfer->setLocalPath(f->logicalPath()); } currentTransfer = NULL; transfer->setTransfer(t); transfer->setState(t->state); transfer->setPriority(t->priority); transfer->setTotalBytes(t->size); transfer->setTransferredBytes(t->progresscompleted); transfer->setTag(f->tag); transferMap[f->tag] = transfer; fireOnTransferStart(transfer); } void MegaApiImpl::file_removed(File *f, const Error &e) { MegaTransferPrivate* transfer = getMegaTransferPrivate(f->tag); if (transfer) { processTransferRemoved(f->transfer, transfer, e); } } void MegaApiImpl::file_complete(File *f) { auto* transfer = getMegaTransferPrivate(f->tag); if (!transfer) return; if (!f->isFuseTransfer()) { if (f->transfer->type == GET) { transfer->setLocalPath(f->getLocalname()); } else if (f->transfer->type == PUT && (f->getLocalname() != transfer->getLocalPath())) { LOG_debug << "[MegaApiImpl::file_complete] Changing transfer path from '" << transfer->getLocalPath().toPath(false) << "' to '" << f->getLocalname().toPath(false) << "'"; transfer->setLocalPath(f->getLocalname()); } } processTransferComplete(f->transfer, transfer); } void MegaApiImpl::transfer_complete(Transfer *t) { MegaTransferPrivate* transfer = getMegaTransferPrivate(t->tag); if (transfer) { transfer->setTransfer(nullptr); } } void MegaApiImpl::transfer_removed(Transfer *t) { MegaTransferPrivate* transfer = getMegaTransferPrivate(t->tag); if (transfer) { transfer->setTransfer(nullptr); } } void MegaApiImpl::transfer_prepare(Transfer *t) { for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++) { MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag); if (!transfer) { continue; } processTransferPrepare(t, transfer); } } void MegaApiImpl::transfer_update(Transfer *t) { for (file_list::iterator it = t->files.begin(); it != t->files.end(); it++) { MegaTransferPrivate* transfer = getMegaTransferPrivate((*it)->tag); if (!transfer) { continue; } if ((*it)->getLocalname() != transfer->getLocalPath()) { LOG_debug << "[MegaApiImpl::transfer_update] Changing transfer path from '" << transfer->getLocalPath().toPath(false) << "' to '" << (*it)->getLocalname().toPath(false) << "'"; transfer->setLocalPath((*it)->getLocalname()); } if (it == t->files.begin() && transfer->getUpdateTime() == Waiter::ds && transfer->getState() == t->state && transfer->getPriority() == t->priority && (!t->slot || (t->slot->progressreported && t->slot->progressreported != t->size))) { // don't send more than one callback per decisecond // if the state doesn't change, the priority doesn't change // and there isn't anything new or it's not the first // nor the last callback return; } processTransferUpdate(t, transfer); } } void MegaApiImpl::file_resume(string* d, direction_t* type, uint32_t dbid, FileResumeData& data) { assert(type); assert(d); if (d->size() < sizeof(char)) { LOG_err << "MegaApiImpl::file_resume - Invalid data size"; return; } *type = (direction_t)MemAccess::get<char>(d->data()); switch (*type) { case GET: { data.file = MegaFileGet::unserialize(d); break; } case PUT: { MegaFile* file = NULL; file = MegaFilePut::unserialize(d); if (!file) { break; } data.file = file; MegaTransferPrivate* transfer = file->getTransfer(); data.inboxTarget = transfer->getInboxTarget(); std::shared_ptr<Node> parent = client->nodebyhandle(transfer->getParentHandle()); sharedNode_vector nodes = client->mNodeManager.getNodesByFingerprint(*file); const char *name = transfer->getFileName(); auto removeFile = [this, &data, &transfer]() { TransferDbCommitter committer(client->tctable); delete data.file; delete transfer; // committer needed here // Stored to clean the Transfer data.sameNodeHandle.setUndef(); data.file = NULL; data.parentHandle.setUndef(); }; if (parent && nodes.size() && name) { // Get previous node if any file->previousNode = client->childnodebyname(parent.get(), name, true); for (auto &node : nodes) { if (node->parent == parent && !strcmp(node->displayname(), name)) { // don't resume the upload if the node already exist in the target folder removeFile(); break; } // Update data to copy remote, any element from nodes is valid else { data.sameNodeHandle = node->nodeHandle(); data.remoteName = name; data.parentHandle = parent->nodeHandle(); } } } else if (!parent) { // don't resume the upload if parent node doesn't exist removeFile(); } break; } default: break; } if (data.file) { data.file->dbid = dbid; currentTransfer = static_cast<MegaFile*>(data.file)->getTransfer(); currentTransfer->dbid = data.file->dbid; waiter->notify(); } } dstime MegaApiImpl::pread_failure(const Error &e, int retry, void* param, dstime timeLeft) { MegaTransferPrivate *transfer = (MegaTransferPrivate *)param; if (!transfer) { LOG_warn << "pread_failure: transfer is invalid"; return NEVER; } transfer->setUpdateTime(Waiter::ds); transfer->setDeltaSize(0); transfer->setSpeed(0); transfer->setMeanSpeed(0); transfer->setLastBytes(NULL); if (retry <= transfer->getMaxRetries() && e != API_EINCOMPLETE && !(e == API_ETOOMANY && e.hasExtraInfo())) { auto megaError = std::make_unique<MegaErrorPrivate>(e, timeLeft / 10); transfer->setLastError(megaError.get()); transfer->setState(MegaTransfer::STATE_RETRYING); fireOnTransferTemporaryError(transfer, std::move(megaError)); LOG_debug << "Streaming temporarily failed " << retry; if (retry <= 1) { return 0; } return static_cast<dstime>(1) << (retry - 1); } else { if (e && (e != API_EINCOMPLETE || (e == API_ETOOMANY && e.hasExtraInfo()))) { transfer->setState(MegaTransfer::STATE_FAILED); } else { transfer->setState(MegaTransfer::STATE_COMPLETED); } fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); return NEVER; } } bool MegaApiImpl::pread_data(byte *buffer, m_off_t len, m_off_t, m_off_t speed, m_off_t meanSpeed, void* param) { MegaTransferPrivate *transfer = (MegaTransferPrivate *)param; LOG_verbose << "Read new data received from transfer: len = " << len << ", speed = " << (speed/1024) << " KB/s, meanSpeed = " << (meanSpeed/1024) << " KB/s, total transferred bytes = " << transfer->getTransferredBytes() << ""; dstime currentTime = Waiter::ds; transfer->setStartTime(currentTime); transfer->setState(MegaTransfer::STATE_ACTIVE); transfer->setUpdateTime(currentTime); transfer->setDeltaSize(len); transfer->setLastBytes((char *)buffer); transfer->setTransferredBytes(transfer->getTransferredBytes() + len); transfer->setSpeed(speed); transfer->setMeanSpeed(meanSpeed); bool end = (transfer->getTransferredBytes() == transfer->getTotalBytes()); fireOnTransferUpdate(transfer); if (!fireOnTransferData(transfer) || end) { LOG_debug << "[MegaApiImpl::pread_data] Finish. Transfer: " << param << ", end = " << end << " [this = " << this << "]"; transfer->setState(end ? MegaTransfer::STATE_COMPLETED : MegaTransfer::STATE_CANCELLED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(end ? API_OK : API_EINCOMPLETE)); return false; } return true; } void MegaApiImpl::reportevent_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_REPORT_EVENT)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::sessions_killed(handle, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_KILL_SESSION)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::cleanrubbishbin_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CLEAN_RUBBISH_BIN)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getrecoverylink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_GET_RECOVERY_LINK) && (request->getType() != MegaRequest::TYPE_GET_CANCEL_LINK))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::queryrecoverylink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_QUERY_RECOVERY_LINK) && (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) && (request->getType() != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::queryrecoverylink_result(int type, const char *email, const char *ip, time_t, handle uh, const vector<string> *) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); int reqType = request->getType(); if(!request || ((reqType != MegaRequest::TYPE_QUERY_RECOVERY_LINK) && (reqType != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) && (reqType != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK))) return; request->setEmail(email); request->setFlag(type == RECOVER_WITH_MASTERKEY); request->setNumber(type); // not specified in MegaApi documentation request->setText(ip); // not specified in MegaApi documentation request->setNodeHandle(uh); // not specified in MegaApi documentation if (reqType == MegaRequest::TYPE_QUERY_RECOVERY_LINK) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>()); return; } else if (reqType == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) { int creqtag = client->reqtag; client->reqtag = client->restag; client->prelogin(email); client->reqtag = creqtag; return; } else if (reqType == MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK) { if (type != CHANGE_EMAIL) { LOG_debug << "Unknown type of change email link"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } const char* code; code = strstr(request->getLink(), MegaClient::verifyLinkPrefix()); if (code) { code += strlen(MegaClient::verifyLinkPrefix()); if (!checkPassword(request->getPassword())) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); return; } int creqtag = client->reqtag; client->reqtag = client->restag; if (client->accountversion == 1) { byte pwkey[SymmCipher::KEYLENGTH]; client->pw_key(request->getPassword(), pwkey); client->confirmemaillink(code, request->getEmail(), pwkey); } else if (client->accountversion == 2) { client->confirmemaillink(code, request->getEmail(), NULL); } else { LOG_warn << "Version of account not supported"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EINTERNAL)); } client->reqtag = creqtag; } else { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); } } } void MegaApiImpl::getprivatekey_result(error e, const byte *privk, const size_t len_privk) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK && request->getType() != MegaRequest::TYPE_CHECK_RECOVERY_KEY)) return; if (e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } const char *link = request->getLink(); const char* code; code = strstr(link, MegaClient::recoverLinkPrefix()); if (code) { code += strlen(MegaClient::recoverLinkPrefix()); } else { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } byte mk[SymmCipher::KEYLENGTH]; Base64::atob(request->getPrivateKey(), mk, sizeof mk); // check the private RSA is valid after decryption with master key SymmCipher key; key.setkey(mk); byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; memcpy(privkbuf, privk, len_privk); key.ecb_decrypt(privkbuf, len_privk); AsymmCipher uk; if (!uk.setkey(AsymmCipher::PRIVKEY, privkbuf, int(len_privk))) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EKEY)); return; } if (request->getType() == MegaRequest::TYPE_CHECK_RECOVERY_KEY) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return; } int creqtag = client->reqtag; client->reqtag = client->restag; client->confirmrecoverylink(code, request->getEmail(), request->getPassword(), mk, request->getParamType()); client->reqtag = creqtag; } void MegaApiImpl::confirmrecoverylink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::confirmcancellink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_CANCEL_LINK)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getemaillink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::resendverificationemail_result(error e) { auto it = requestMap.find(client->restag); if (it == requestMap.end()) return; MegaRequestPrivate *request = it->second; if (!request || ((request->getType() != MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::resetSmsVerifiedPhoneNumber_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate *request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::confirmemaillink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getversion_result(int versionCode, const char *versionString, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_APP_VERSION)) return; if (!e) { request->setNumber(versionCode); request->setName(versionString); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getlocalsslcertificate_result(m_time_t ts, string *certdata, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_LOCAL_SSL_CERT)) return; if (!e) { string result; const char *data = certdata->data(); const char *enddata = certdata->data() + certdata->size(); MegaStringMapPrivate *datamap = new MegaStringMapPrivate(); for (int i = 0; data < enddata; i++) { result = i ? "-----BEGIN CERTIFICATE-----\n" : "-----BEGIN RSA PRIVATE KEY-----\n"; const char *end = strstr(data, ";"); if (!end) { if (!i) { delete datamap; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EINTERNAL)); return; } end = enddata; } while (data < end) { int remaining = int(end - data); int dataSize = (remaining > 64) ? 64 : remaining; result.append(data, static_cast<size_t>(dataSize)); result.append("\n"); data += dataSize; } switch (i) { case 0: { result.append("-----END RSA PRIVATE KEY-----\n"); datamap->set("key", result.c_str()); break; } case 1: { result.append("-----END CERTIFICATE-----\n"); datamap->set("cert", result.c_str()); break; } default: { result.append("-----END CERTIFICATE-----\n"); std::ostringstream oss; oss << "intermediate_" << (i - 1); datamap->set(oss.str().c_str(), result.c_str()); break; } } data++; } request->setNumber(ts); request->setMegaStringMap(datamap); delete datamap; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getmegaachievements_result(AchievementsDetails *, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_ACHIEVEMENTS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::mediadetection_ready() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MEDIA_INFO_READY); fireOnEvent(event); } void MegaApiImpl::storagesum_changed(int64_t newsum) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_STORAGE_SUM_CHANGED); event->setNumber(newsum); fireOnEvent(event); } void MegaApiImpl::getmiscflags_result(error e) { if (e == API_OK) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MISC_FLAGS_READY); fireOnEvent(event); } if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_GET_MISC_FLAGS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } #ifdef ENABLE_CHAT void MegaApiImpl::chatcreate_result(TextChat *chat, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_CREATE)) return; if (!e) { // encapsulate the chat in a list for the request textchat_map chatList; chatList[chat->getChatId()] = chat; auto megaChatList = std::make_unique<MegaTextChatListPrivate>(&chatList); request->setMegaTextChatList(megaChatList.get()); if (request->getMegaScheduledMeetingList() && request->getMegaScheduledMeetingList()->size()) { const map<handle/*schedId*/, std::unique_ptr<ScheduledMeeting>>& schedmap = chat->getSchedMeetings(); if (!schedmap.empty()) { if (schedmap.size() > 1) { LOG_warn << "Scheduled meeting list contains more than 1 element for a new chatroom"; } std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance()); for (auto const& sm: schedmap) { l->insert(new MegaScheduledMeetingPrivate(sm.second.get())); } request->setMegaScheduledMeetingList(l.get()); } } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatinvite_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_INVITE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatremove_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_REMOVE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chaturl_result(string *url, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_URL)) return; if (!e) { request->setLink(url->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatgrantaccess_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_GRANT_ACCESS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatremoveaccess_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_REMOVE_ACCESS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatupdatepermissions_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chattruncate_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_TRUNCATE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatsettitle_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_SET_TITLE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatpresenceurl_result(string *url, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_PRESENCE_URL)) return; if (!e) { request->setLink(url->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::registerpushnotification_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::archivechat_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_ARCHIVE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::setchatretentiontime_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate *request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_SET_RETENTION_TIME)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chats_updated(textchat_map* chats, int /*count*/) { if (chats) { MegaTextChatList *chatList = new MegaTextChatListPrivate(chats); fireOnChatsUpdate(chatList); delete chatList; } else { fireOnChatsUpdate(NULL); } } void MegaApiImpl::richlinkrequest_result(string *richLink, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_RICH_LINK)) return; if (!e) { request->setText(richLink->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatlink_result(handle h, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_LINK_HANDLE)) return; if (!e && !request->getFlag()) { request->setParentHandle(h); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatlinkurl_result(handle chatid, int shard, string *link, string *ct, int numPeers, m_time_t ts, bool meetingRoom, int chatOptions, const std::vector<std::unique_ptr<ScheduledMeeting>>* smList, handle callid, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CHAT_LINK_URL)) return; if (!e) { request->setLink(link->c_str()); request->setAccess(shard); request->setParentHandle(chatid); request->setText(ct->c_str()); request->setNumDetails(numPeers); request->setNumber(ts); request->setParamType(chatOptions); request->setFlag(meetingRoom); if (smList && !smList->empty()) { std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance()); for (auto const& sm: *smList) { l->insert(new MegaScheduledMeetingPrivate(sm.get())); } request->setMegaScheduledMeetingList(l.get()); } if (callid != INVALID_HANDLE) { std::vector<MegaHandle> handleList; handleList.push_back(callid); request->setMegaHandleList(handleList); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatlinkclose_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SET_PRIVATE_MODE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::chatlinkjoin_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_AUTOJOIN_PUBLIC_CHAT)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } #endif void MegaApiImpl::folderlinkinfo_result(error e, handle owner, handle /*ph*/, string *attr, string* k, m_off_t currentSize, uint32_t numFiles, uint32_t numFolders, m_off_t versionsSize, uint32_t numVersions) { MegaRequestPrivate* request = NULL; auto it = requestMap.find(client->restag); if (it == requestMap.end()) return; request = it->second; if (!request || request->getType() != MegaRequest::TYPE_PUBLIC_LINK_INFORMATION) return; if (e == API_OK) { // Decrypt nodekey with the key of the folder link SymmCipher cipher; byte folderkey[SymmCipher::KEYLENGTH]; Base64::atob(request->getPrivateKey(), folderkey, sizeof(folderkey)); cipher.setkey(folderkey); const char *nodekeystr = k->data() + 9; // skip the userhandle(8) and the `:` byte nodekey[FOLDERNODEKEYLENGTH]; if (client->decryptkey(nodekeystr, nodekey, sizeof(nodekey), &cipher, 0, UNDEF)) { // Decrypt node attributes with the nodekey cipher.setkey(nodekey); byte* buf = Node::decryptattr(&cipher, attr->c_str(), attr->size()); if (buf) { AttrMap attrs; string fileName; string fingerprint; FileFingerprint ffp; m_time_t mtime = 0; Node::parseattr(buf, attrs, currentSize, mtime, fileName, fingerprint, ffp); fingerprint = MegaNodePrivate::addAppPrefixToFingerprint(fingerprint, ffp.size); // Normalize node name to UTF-8 string attr_map::iterator itAttribute = attrs.map.find('n'); if (itAttribute != attrs.map.end() && !itAttribute->second.empty()) { LocalPath::utf8_normalize(&(itAttribute->second)); fileName = itAttribute->second.c_str(); } MegaFolderInfoPrivate* folderInfo = new MegaFolderInfoPrivate(static_cast<int>(numFiles), static_cast<int>(numFolders) - 1, static_cast<int>(numVersions), currentSize, versionsSize); request->setMegaFolderInfo(folderInfo); request->setParentHandle(owner); request->setText(fileName.c_str()); delete folderInfo; delete [] buf; } else { LOG_err << "Error decrypting node attributes with decrypted nodekey"; e = API_EKEY; } } else { LOG_err << "Error decrypting nodekey with folder link key"; e = API_EKEY; } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } std::unique_ptr<MegaPushNotificationSettingsPrivate> MegaApiImpl::getMegaPushNotificationSetting() { User* ownUser = client->ownuser(); if (!ownUser) return nullptr; const UserAttribute* settingsJson = ownUser->getAttribute(ATTR_PUSH_SETTINGS); if (!settingsJson || settingsJson->isNotExisting()) { return nullptr; } std::unique_ptr<MegaPushNotificationSettingsPrivate> pushSettings = std::make_unique<MegaPushNotificationSettingsPrivate>(settingsJson->value()); if (pushSettings->isValid()) { return pushSettings; } else { LOG_err << "Invalid JSON for received notification settings"; } return nullptr; } #ifdef ENABLE_SYNC MegaSyncPrivate* MegaApiImpl::cachedMegaSyncPrivateByBackupId(const SyncConfig& config) { if (mCachedMegaSyncPrivate && config.mBackupId == mCachedMegaSyncPrivate->getBackupId()) { return mCachedMegaSyncPrivate.get(); } mCachedMegaSyncPrivate.reset(new MegaSyncPrivate(config, client)); return mCachedMegaSyncPrivate.get(); } void MegaApiImpl::syncupdate_stateconfig(const SyncConfig& config) { mCachedMegaSyncPrivate.reset(); mRecentlyNotifiedOverlayIconPaths.clear(); mRecentlyRequestedOverlayIconPaths.clear(); if (auto megaSync = cachedMegaSyncPrivateByBackupId(config)) { fireOnSyncStateChanged(megaSync); } } void MegaApiImpl::syncupdate_stats(handle backupId, const PerSyncStats& stats) { MegaSyncStatsPrivate msp(backupId, stats); fireOnSyncStatsUpdated(&msp); } void MegaApiImpl::syncupdate_scanning(bool scanning) { receivedScanningStateFlag.store(scanning); fireOnGlobalSyncStateChanged(); } void MegaApiImpl::syncupdate_stalled(bool stalled) { receivedStallFlag.store(stalled); fireOnGlobalSyncStateChanged(); } void MegaApiImpl::syncupdate_conflicts(bool conflicts) { receivedNameConflictsFlag.store(conflicts); fireOnGlobalSyncStateChanged(); } void MegaApiImpl::syncupdate_totalstalls(bool totalstalls) { receivedTotalStallsFlag.store(totalstalls); if (totalstalls) { fireOnGlobalSyncStateChanged(); } } void MegaApiImpl::syncupdate_totalconflicts(bool totalconflicts) { receivedTotalNameConflictsFlag.store(totalconflicts); if (totalconflicts) { fireOnGlobalSyncStateChanged(); } } void MegaApiImpl::syncupdate_syncing(bool syncing) { receivedSyncingStateFlag.store(syncing); fireOnGlobalSyncStateChanged(); } void MegaApiImpl::syncupdate_treestate(const SyncConfig &config, const LocalPath& lp, treestate_t ts, nodetype_t) { mRecentlyNotifiedOverlayIconPaths.addOrUpdate(lp, ts); mRecentlyRequestedOverlayIconPaths.overwriteExisting(lp, ts); if (auto megaSync = cachedMegaSyncPrivateByBackupId(config)) { string s = lp.toPath(false); // MegaSync was changed to expect utf8 for all platforms fireOnFileSyncStateChanged(megaSync, &s, (int)ts); } } void MegaApiImpl::sync_removed(const SyncConfig& config) { mRecentlyNotifiedOverlayIconPaths.clear(); mRecentlyRequestedOverlayIconPaths.clear(); auto msp_ptr = std::make_unique<MegaSyncPrivate>(config, client); fireOnSyncDeleted(msp_ptr.get()); } void MegaApiImpl::sync_added(const SyncConfig& config) { mCachedMegaSyncPrivate.reset(); auto megaSync = cachedMegaSyncPrivateByBackupId(config); fireOnSyncAdded(megaSync); } void MegaApiImpl::syncupdate_remote_root_changed(const SyncConfig &config) { mCachedMegaSyncPrivate.reset(); if (auto megaSync = cachedMegaSyncPrivateByBackupId(config)) { fireOnSyncRemoteRootChanged(megaSync); } } void MegaApiImpl::syncs_restored(SyncError syncError) { mCachedMegaSyncPrivate.reset(); MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_SYNCS_RESTORED); event->setNumber(syncError); fireOnEvent(event); } void MegaApiImpl::syncs_disabled(SyncError syncError) { mCachedMegaSyncPrivate.reset(); MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_SYNCS_DISABLED); event->setNumber(syncError); fireOnEvent(event); } #endif void MegaApiImpl::backupput_result(const Error& e, handle backupId) { if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_BACKUP_PUT)) return; request->setParentHandle(backupId); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } // user addition/update (users never get deleted) void MegaApiImpl::users_updated(User** u, int count) { if(!count) { return; } MegaUserList *userList = NULL; if(u != NULL) { userList = new MegaUserListPrivate(u, count); fireOnUsersUpdate(userList); } else { fireOnUsersUpdate(NULL); } delete userList; } void MegaApiImpl::useralerts_updated(UserAlert::Base** b, int count) { if (count) { MegaUserAlertList *userAlertList = b ? new MegaUserAlertListPrivate(b, count, client) : NULL; fireOnUserAlertsUpdate(userAlertList); delete userAlertList; } } void MegaApiImpl::account_updated() { fireOnAccountUpdate(); } void MegaApiImpl::sets_updated(Set** sets, int count) { LOG_debug << "Sets updated: " << count; if (!count) { return; } if (sets) { unique_ptr<MegaSetListPrivate> sList(new MegaSetListPrivate(sets, count)); fireOnSetsUpdate(sList.get()); } else { fireOnSetsUpdate(nullptr); } } void MegaApiImpl::setelements_updated(SetElement** elements, int count) { LOG_debug << "Elements updated: " << count; if (!count) { return; } if (elements) { unique_ptr<MegaSetElementListPrivate> eList(new MegaSetElementListPrivate(elements, count)); fireOnSetElementsUpdate(eList.get()); } else { fireOnSetElementsUpdate(nullptr); } } void MegaApiImpl::pcrs_updated(PendingContactRequest **r, int count) { if(!count) { return; } MegaContactRequestList *requestList = NULL; if(r != NULL) { requestList = new MegaContactRequestListPrivate(r, count); fireOnContactRequestsUpdate(requestList); } else { fireOnContactRequestsUpdate(NULL); } delete requestList; } void MegaApiImpl::sequencetag_update(const string& seqTag) { assert(threadId == std::this_thread::get_id()); // no need for a separate MegaApiImpl::fireOnSeqTagUpdate (but mentioning it here for search purposes) for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onSeqTagUpdate(api, &seqTag); } } void MegaApiImpl::unlink_result(handle h, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_REMOVE) && (request->getType() != MegaRequest::TYPE_MOVE))) { return; } if (request->getType() != MegaRequest::TYPE_MOVE) { request->setNodeHandle(h); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::unlinkversions_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || request->getType() != MegaRequest::TYPE_REMOVE_VERSIONS) { return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::fetchnodes_result(const Error &e) { MegaRequestPrivate* request = NULL; if (!client->restag) { for (map<int, MegaRequestPrivate *>::iterator it = requestMap.begin(); it != requestMap.end(); it++) { if (it->second->getType() == MegaRequest::TYPE_FETCH_NODES) { request = it->second; break; } } if (!request) { request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_NODES); //request->performRequest not required as not put to requestQueue } if (e == API_OK) { assert(!client->mNodeManager.getRootNodeFiles().isUndef()); // is folder link fetched properly? request->setNodeHandle(client->getFolderLinkPublicHandle()); if (!client->isValidFolderLink()) // is the key for the folder link invalid? { request->setFlag(true); } } if (!e && client->loggedin() == FULLACCOUNT && client->isNewSession) { updatePwdReminderData(false, false, false, false, true); client->isNewSession = false; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if (requestMap.find(client->restag) == requestMap.end()) { return; } request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_FETCH_NODES) && (request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) { return; } if (request->getType() == MegaRequest::TYPE_FETCH_NODES) { if (e == API_OK) { // Sanity check for required root nodes switch (client->getClientType()) { case MegaClient::ClientType::DEFAULT: { break; } case MegaClient::ClientType::PASSWORD_MANAGER: assert(!client->mNodeManager.getRootNodeVault().isUndef()); break; case MegaClient::ClientType::VPN: // Allow Fetch nodes for VPN to start receiving Action Packets break; default: { LOG_err << "Fetch nodes requested for unexpected MegaApi type " << static_cast<int>(client->getClientType()); assert(false); break; } } request->setNodeHandle(client->getFolderLinkPublicHandle()); if (!client->isValidFolderLink()) // is the key for the folder link invalid? { request->setFlag(true); } } if (!e && client->loggedin() == FULLACCOUNT && client->isNewSession) { updatePwdReminderData(false, false, false, false, true); client->isNewSession = false; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } else // TYPE_CREATE_ACCOUNT { if (e != API_OK || request->getParamType() == MegaApi::RESUME_ACCOUNT) // resuming ephemeral session { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } else // new account has been created { // set names silently... int creqtag = client->reqtag; client->reqtag = 0; string firstname = request->getName() ? request->getName() : ""; if (!firstname.empty()) { client->putua(ATTR_FIRSTNAME, (const byte*)request->getName(), static_cast<unsigned int>(strlen(request->getName())), -1, request->getNodeHandle(), request->getAccess(), request->getTransferredBytes(), [](Error) {}); } string lastname = request->getText() ? request->getText() : ""; if (!lastname.empty()) { client->putua(ATTR_LASTNAME, (const byte*)request->getText(), static_cast<unsigned int>(strlen(request->getText())), -1, UNDEF, 0, 0, [](Error) {}); } client->reqtag = creqtag; // restore current reqtag, for future requests // Ephemeral++ don't have an email when account is created, so cannot send a signup link -> account is ready if (request->getParamType() == MegaApi::CREATE_EPLUSPLUS_ACCOUNT) // creation of account E++ { // The session id cannot follow the same pattern, since no password is provided (yet) // In consequence, the session resumption requires a regular session id (instead of the // usual one with the user's handle and the pwd cipher) string sid; client->dumpsession(sid); request->setPrivateKey(sid.c_str()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } // Ephemeral accounts have an email -> send signup link if (!request->getPrivateKey()) // ...and finally send confirmation link { string fullname = firstname + lastname; string derivedKey = client->sendsignuplink2(request->getEmail(), request->getPassword(), fullname.c_str(), client->restag); // Use the tag where the request belongs string b64derivedKey; Base64::btoa(derivedKey, b64derivedKey); request->setPrivateKey(b64derivedKey.c_str()); char buf[SymmCipher::KEYLENGTH * 4 / 3 + 3]; Base64::btoa((byte*) &client->me, sizeof client->me, buf); string sid; sid.append(buf); sid.append("#"); Base64::btoa((byte *)derivedKey.data(), SymmCipher::KEYLENGTH, buf); sid.append(buf); request->setSessionKey(sid.c_str()); return; } } } } void MegaApiImpl::putnodes_result(const Error& inputErr, targettype_t t, vector<NewNode>& nn, bool targetOverride, int tag, const map<string, string>& fileHandles) { handle h = UNDEF; std::shared_ptr<Node> n = NULL; Error e = inputErr; if (!e && t != USER_HANDLE) { assert(!nn.empty() && nn.front().added && nn.front().mAddedHandle != UNDEF); n = client->nodebyhandle(nn.front().mAddedHandle); if(n) { n->applykey(); n->setattr(); h = n->nodehandle; } } MegaTransferPrivate* transfer = getMegaTransferPrivate(tag); if (transfer) { if (transfer->getType() == MegaTransfer::TYPE_DOWNLOAD) { return; } //scale to get the handle of the new node Node *ntmp; if (n) { handle ph = transfer->getParentHandle(); for (ntmp = n.get(); ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != ph) ); ntmp = ntmp->parent.get()); if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == ph)) { h = ntmp->nodehandle; } } transfer->setNodeHandle(h); transfer->setTargetOverride(targetOverride); LOG_verbose << "Set transferred bytes to transfer total bytes: " << transfer->getTotalBytes() << " (prev transferredBytes = " << transfer->getTransferredBytes() << ")"; transfer->setTransferredBytes(transfer->getTotalBytes()); if (!e) { transfer->setState(MegaTransfer::STATE_COMPLETED); } else { transfer->setState(MegaTransfer::STATE_FAILED); transfer->setForeignOverquota(e == API_EOVERQUOTA && client->isForeignNode(NodeHandle().set6byte(transfer->getParentHandle()))); } fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); return; } auto reqIt = requestMap.find(tag); MegaRequestPrivate* request = reqIt == requestMap.end() ? nullptr : reqIt->second; if (!request || ((request->getType() != MegaRequest::TYPE_IMPORT_LINK) && (request->getType() != MegaRequest::TYPE_CREATE_FOLDER) && (request->getType() != MegaRequest::TYPE_CREATE_PASSWORD_NODE) && (request->getType() != MegaRequest::TYPE_COPY) && (request->getType() != MegaRequest::TYPE_MOVE) && (request->getType() != MegaRequest::TYPE_RESTORE) && (request->getType() != MegaRequest::TYPE_ADD_SYNC) && (request->getType() != MegaRequest::TYPE_ADD_SYNC_PREVALIDATION) && (request->getType() != MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD) && (request->getType() != MegaRequest::TYPE_IMPORT_PASSWORDS_FROM_FILE))) return; if (request->getType() == MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD) { request->setNodeHandle(h); request->setFlag(targetOverride); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if (request->getType() == MegaRequest::TYPE_IMPORT_PASSWORDS_FROM_FILE) { if (e == API_OK) { std::vector<handle> nodeHandles; std::transform(nn.begin(), nn.end(), std::back_inserter(nodeHandles), [](const NewNode& newNode) { assert(newNode.mAddedHandle != UNDEF); return newNode.mAddedHandle; }); request->setMegaHandleList(nodeHandles); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if (request->getType() == MegaRequest::TYPE_MOVE || request->getType() == MegaRequest::TYPE_COPY) { //scale to get the handle of the moved/copied node Node *ntmp; if (n) { for (ntmp = n.get(); ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != request->getParentHandle()) ); ntmp = ntmp->parent.get()); if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == request->getParentHandle())) { h = ntmp->nodehandle; } } } else if (request->getType() == MegaRequest::TYPE_CREATE_NODE_TREE) { request->setMegaStringMap(fileHandles); } if (request->getType() != MegaRequest::TYPE_MOVE) { request->setNodeHandle(h); request->setFlag(targetOverride); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } else { if (!e) { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); if (!node) { e = API_ENOENT; } else { request->setNodeHandle(h); request->setFlag(targetOverride); e = client->unlink(node.get(), false, request->getTag(), false); } } if (e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } } void MegaApiImpl::setpcr_result(handle h, error e, opcactions_t action) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || request->getType() != MegaRequest::TYPE_INVITE_CONTACT) return; if (e) { LOG_debug << "Outgoing pending contact request failed (" << MegaError::getErrorString(e) << ")"; } else { switch (action) { case OPCA_DELETE: LOG_debug << "Outgoing pending contact request deleted successfully"; break; case OPCA_REMIND: LOG_debug << "Outgoing pending contact request reminded successfully"; break; case OPCA_ADD: char buffer[12]; Base64::btoa((byte*)&h, MegaClient::PCRHANDLE, buffer); LOG_debug << "Outgoing pending contact request succeeded, id: " << buffer; break; } } request->setNodeHandle(h); request->setNumber(action); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::updatepcr_result(error e, ipcactions_t action) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || request->getType() != MegaRequest::TYPE_REPLY_CONTACT_REQUEST) return; if (e) { LOG_debug << "Incoming pending contact request update failed (" << MegaError::getErrorString(e) << ")"; } else { string labels[3] = {"accepted", "denied", "ignored"}; LOG_debug << "Incoming pending contact request successfully " << labels[(int)action]; } request->setNumber(action); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::fa_complete(handle, fatype, const char* data, uint32_t len) { int tag = client->restag; while(tag) { if(requestMap.find(tag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(tag); if(!request || (request->getType() != MegaRequest::TYPE_GET_ATTR_FILE)) return; tag = int(request->getNumber()); auto f = client->fsaccess->newfileaccess(); string filePath(request->getFile()); auto localPath = LocalPath::fromAbsolutePath(filePath); fsAccess->unlinklocal(localPath); bool success = f->fopen(localPath, OPEN_WRONLY, FSLogging::logOnError) && f->fwrite((const byte*)data, len, 0); f.reset(); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(success ? API_OK : API_EWRITE)); } } int MegaApiImpl::fa_failed(handle, fatype, int retries, error e) { int tag = client->restag; while(tag) { if(requestMap.find(tag) == requestMap.end()) return 1; MegaRequestPrivate* request = requestMap.at(tag); if(!request || (request->getType() != MegaRequest::TYPE_GET_ATTR_FILE)) return 1; tag = int(request->getNumber()); if(retries >= 2) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } else { fireOnRequestTemporaryError(request, std::make_unique<MegaErrorPrivate>(e)); } } return (retries >= 2); } void MegaApiImpl::putfa_result(handle h, fatype, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || request->getType() != MegaRequest::TYPE_SET_ATTR_FILE) return; if (e == API_OK && request->getMegaBackgroundMediaUploadPtr()) { request->setNodeHandle(h); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::enumeratequotaitems_result(const Product& product) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) && (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) && (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) && (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN))) { return; } request->addProduct(product); } void MegaApiImpl::enumeratequotaitems_result(unique_ptr<CurrencyData> currencyData) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) && (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) && (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) && (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN))) { return; } request->setCurrency(std::move(currencyData)); } void MegaApiImpl::enumeratequotaitems_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_GET_PRICING) && (request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) && (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT) && (request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN))) { return; } if (request->getType() == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN) { if (e != API_OK) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } unique_ptr<MegaAccountDetails> details(request->getMegaAccountDetails()); unique_ptr<MegaPricing> pricing(request->getPricing()); int recommended = calcRecommendedProLevel(*pricing.get(), *details.get()); request->setNumber(recommended); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if(request->getType() == MegaRequest::TYPE_GET_PRICING) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } else { MegaPricing *pricing = request->getPricing(); MegaCurrency *currency = request->getCurrency(); int i; for(i = 0; i < pricing->getNumProducts(); i++) { if (pricing->getHandle(i) == request->getNodeHandle()) { int phtype = request->getParamType(); int64_t ts = request->getTransferredBytes(); requestMap.erase(request->getTag()); int nextTag = client->nextreqtag(); request->setTag(nextTag); requestMap[nextTag]=request; client->purchase_additem(0, request->getNodeHandle(), static_cast<unsigned int>(pricing->getAmount(i)), currency->getCurrencySymbol(), 0, NULL, request->getParentHandle(), phtype, ts); break; } } if (i == pricing->getNumProducts()) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); } delete pricing; delete currency; } } void MegaApiImpl::additem_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_GET_PAYMENT_ID) && (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT))) return; if(e != API_OK) { client->purchase_begin(); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if(request->getType() == MegaRequest::TYPE_GET_PAYMENT_ID) { char saleid[16]; Base64::btoa((byte *)&client->purchase_basket.back(), 8, saleid); request->setLink(saleid); client->purchase_begin(); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return; } //MegaRequest::TYPE_UPGRADE_ACCOUNT int method = int(request->getNumber()); int creqtag = client->reqtag; client->reqtag = client->restag; client->purchase_checkout(method); client->reqtag = creqtag; } void MegaApiImpl::checkout_result(const char *errortype, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_UPGRADE_ACCOUNT)) return; if(!errortype) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if(!strcmp(errortype, "FP")) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e - 100)); return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::PAYMENT_EGENERIC)); return; } void MegaApiImpl::submitpurchasereceipt_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::creditcardquerysubscriptions_result(int number, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS)) return; request->setNumber(number); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::creditcardcancelsubscriptions_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getpaymentmethods_result(int methods, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_PAYMENT_METHODS)) return; request->setNumber(methods); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::userfeedbackstore_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SUBMIT_FEEDBACK)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::sendevent_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SEND_EVENT)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::supportticket_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SUPPORT_TICKET)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::creditcardstore_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_CREDIT_CARD_STORE)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::copysession_result(string *session, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_SESSION_TRANSFER_URL)) return; if(e == API_OK) { const char *path = request->getText(); string data = client->sessiontransferdata(path, session); data.insert(0, MegaClient::getMegaURL() + "/#sitetransfer!"); request->setLink(data.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::clearing() { #ifdef ENABLE_SYNC mCachedMegaSyncPrivate.reset(); #endif } void MegaApiImpl::notify_retry(dstime dsdelta, retryreason_t reason) { if(!dsdelta) waitingRequest = RETRY_NONE; else if(dsdelta > 40) waitingRequest = reason; if (dsdelta && requestMap.size() == 1) { MegaRequestPrivate *request = requestMap.begin()->second; fireOnRequestTemporaryError(request, std::make_unique<MegaErrorPrivate>(API_EAGAIN, reason)); } } void MegaApiImpl::notify_dbcommit() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_COMMIT_DB); event->setText(client->scsn.text()); fireOnEvent(event); } void MegaApiImpl::notify_storage(int storageEvent) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_STORAGE); event->setNumber(storageEvent); fireOnEvent(event); } void MegaApiImpl::notify_confirmation(const char *email) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_ACCOUNT_CONFIRMATION); event->setText(email); fireOnEvent(event); } void MegaApiImpl::notify_confirm_user_email(handle user, const char* email) { MegaEventPrivate* event = new MegaEventPrivate(MegaEvent::EVENT_CONFIRM_USER_EMAIL); event->setHandle(user); event->setText(email); fireOnEvent(event); } void MegaApiImpl::notify_disconnect() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_DISCONNECT); fireOnEvent(event); } void MegaApiImpl::notify_business_status(BizStatus status) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_BUSINESS_STATUS); event->setNumber(status); fireOnEvent(event); } void MegaApiImpl::http_result(error e, int httpCode, byte* data, m_off_t size) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_QUERY_DNS && request->getType() != MegaRequest::TYPE_CHAT_STATS && request->getType() != MegaRequest::TYPE_DOWNLOAD_FILE)) { return; } request->setNumber(httpCode); request->setTotalBytes(size); if (request->getType() == MegaRequest::TYPE_CHAT_STATS || request->getType() == MegaRequest::TYPE_QUERY_DNS) { string result; result.assign((const char*)data, static_cast<size_t>(size)); request->setText(result.c_str()); } else if (request->getType() == MegaRequest::TYPE_DOWNLOAD_FILE) { const char *file = request->getFile(); if (file && e == API_OK) { auto f = client->fsaccess->newfileaccess(); string filePath(file); auto localPath = LocalPath::fromAbsolutePath(filePath); fsAccess->unlinklocal(localPath); if (!f->fopen(localPath, OPEN_WRONLY, FSLogging::logOnError)) { e = API_EWRITE; } else if (size) { if (!f->fwrite((const byte*)data, static_cast<unsigned int>(size), 0)) { e = API_EWRITE; } } } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::timer_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_TIMER)) { return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::notify_creditCardExpiry() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_CREDIT_CARD_EXPIRY); fireOnEvent(event); } // callback for non-EAGAIN request-level errors // retrying is futile // this can occur e.g. with syntactically malformed requests (due to a bug) or due to an invalid application key void MegaApiImpl::request_error(error e) { // todo: shouldn't this sort of logic be part of SDK Core, rather than intermediate layer, if it is even needed? if (e == API_EBLOCKED && client->sid.size()) { whyAmIBlocked(true); return; } if (e == API_ESID && client->loggingout) { // no need to panic; we caused this ourselves deliberately return; } MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT); bool keepSyncConfigsFile = true; request->setFlag(false); request->setTransferTag(keepSyncConfigsFile); request->setParamType(e); if (e == API_ESSL && client->sslfakeissuer.size()) { request->setText(client->sslfakeissuer.c_str()); } if (e == API_ESID) { client->locallogout(true, keepSyncConfigsFile); } request->performRequest = [this, request]() { return performRequest_logout(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::request_response_progress(m_off_t currentProgress, m_off_t totalProgress) { LOG_verbose << "Request response progress: current progress = " << currentProgress << ", total progress = " << totalProgress; if (!client->isFetchingNodesPendingCS()) { return; } for (std::map<int,MegaRequestPrivate*>::iterator it = requestMap.begin(); it != requestMap.end(); it++) { MegaRequestPrivate *request = it->second; if (request && request->getType() == MegaRequest::TYPE_FETCH_NODES) { request->setTransferredBytes(currentProgress); if (totalProgress != -1) { request->setTotalBytes(totalProgress); } fireOnRequestUpdate(request); } } } void MegaApiImpl::prelogin_result(int version, string* email, string *salt, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_LOGIN) && (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK))) { return; } if (e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if (request->getType() == MegaRequest::TYPE_LOGIN) { const char* pin = request->getText(); if (version == 1) { const char *password = request->getPassword(); { error err; byte pwkey[SymmCipher::KEYLENGTH]; err = client->pw_key(password, pwkey); if (err) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(err)); return; } int creqtag = client->reqtag; client->reqtag = client->restag; client->saveV1Pwd(password); // for automatic upgrade to V2 // cannot be null by now client->login(email->c_str(), pwkey, pin); client->reqtag = creqtag; } } else if (version == 2 && salt) { const char *password = request->getPassword(); { int creqtag = client->reqtag; client->reqtag = client->restag; client->login2(email->c_str(), password, salt, pin); client->reqtag = creqtag; } } else { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EINTERNAL)); } } else if (request->getType() == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) { request->setParamType(version); const char *link = request->getLink(); const char* code; const char *mk64; code = strstr(link, MegaClient::recoverLinkPrefix()); if (code) { code += strlen(MegaClient::recoverLinkPrefix()); } else { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } long long type = request->getNumber(); switch (type) { case RECOVER_WITH_MASTERKEY: { mk64 = request->getPrivateKey(); if (!mk64) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } int creqtag = client->reqtag; client->reqtag = client->restag; client->getprivatekey(code); client->reqtag = creqtag; break; } case RECOVER_WITHOUT_MASTERKEY: { int creqtag = client->reqtag; client->reqtag = client->restag; client->confirmrecoverylink(code, email->c_str(), request->getPassword(), NULL, version); client->reqtag = creqtag; break; } default: LOG_debug << "Unknown type of recovery link"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } } } // login result void MegaApiImpl::login_result(error result) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_LOGIN && request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT)) return; // if login with user+pwd succeed, update lastLogin timestamp if (result == API_OK && request->getEmail() && request->getPassword()) { client->isNewSession = true; client->tsLogin = m_time(); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(result)); } void MegaApiImpl::logout_result(error e, MegaRequestPrivate* request) { if(!e || e == API_ESID) { requestMap.erase(request->getTag()); error preverror = (error)request->getParamType(); abortPendingActions(preverror); waitingRequest = RETRY_NONE; delete mTimezones; mTimezones = NULL; #ifdef ENABLE_SYNC mCachedMegaSyncPrivate.reset(); receivedStallFlag.store(false); receivedNameConflictsFlag.store(false); receivedTotalStallsFlag.store(false); receivedTotalNameConflictsFlag.store(false); receivedScanningStateFlag.store(false); receivedSyncingStateFlag.store(false); mAddressedStallFilter.clear(); #endif mLastReceivedLoggedInState = NOTLOGGEDIN; mLastReceivedLoggedInMeHandle = UNDEF; mLastReceivedLoggedInMyEmail.clear(); mLastKnownRootNode.reset(); mLastKnownVaultNode.reset(); mLastKnownRubbishNode.reset(); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::userdata_result(string* name, string* pubk, string* privk, std::vector<DiscountCode>&& discountCodes, Error result) { // notify apps about the availability/update of user-flags, such as `aplvp` // (note that usually the API command is triggered internally, so no request is associated) if (result == API_OK) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_MISC_FLAGS_READY); fireOnEvent(event); } if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_USER_DATA)) return; if(result == API_OK) { request->setPassword(pubk->c_str()); request->setPrivateKey(privk->c_str()); request->setName(name->c_str()); request->setMegaDiscountCodes(std::move(discountCodes)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(result)); } void MegaApiImpl::pubkey_result(User *u) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_GET_USER_DATA)) return; if(!u) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); return; } if(!u->pubk.isvalid()) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EACCESS)); return; } string key; u->pubk.serializekey(&key, AsymmCipher::PUBKEY); char pubkbuf[AsymmCipher::MAXKEYLENGTH * 4 / 3 + 4]; Base64::btoa((byte*)key.data(), key.size(), pubkbuf); request->setPassword(pubkbuf); char jid[16]; Base32::btoa((byte *)&u->userhandle, MegaClient::USERHANDLE, jid); request->setText(jid); if(u->email.size()) { request->setEmail(u->email.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } // password change result void MegaApiImpl::changepw_result(error result) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || request->getType() != MegaRequest::TYPE_CHANGE_PW) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(result)); } // the requested link could not be opened void MegaApiImpl::openfilelink_result(const Error& result) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_IMPORT_LINK) && (request->getType() != MegaRequest::TYPE_GET_PUBLIC_NODE))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(result)); } // the requested link was opened successfully // (it is the application's responsibility to delete n!) void MegaApiImpl::openfilelink_result(handle ph, const byte* key, m_off_t size, string* a, string* fa, int) { MegaRequestPrivate* request = NULL; auto it = requestMap.find(client->restag); if (it == requestMap.end()) return; request = it->second; if (!request || (request->getType() != MegaRequest::TYPE_IMPORT_LINK && request->getType() != MegaRequest::TYPE_GET_PUBLIC_NODE)) return; if (!client->loggedin() && (request->getType() == MegaRequest::TYPE_IMPORT_LINK)) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_EACCESS)); return; } // no key provided --> check only that the nodehandle is valid if (!key && (request->getType() == MegaRequest::TYPE_GET_PUBLIC_NODE)) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_EINCOMPLETE)); return; } AttrMap attrs; string fileName; string validName; string fingerprint; string originalfingerprint; FileFingerprint ffp; m_time_t mtime = 0; string attrstring; attrstring.resize(a->length()*4/3+4); attrstring.resize(Base64::btoa((const byte*)a->data(), a->length(), (char*)attrstring.data())); bool isNodeKeyDecrypted; string keystring; SymmCipher nodeKey; keystring.assign((char*)key, FILENODEKEYLENGTH); nodeKey.setkey(key, FILENODE); byte *buf = Node::decryptattr(&nodeKey, attrstring.c_str(), attrstring.size()); if (buf) { Node::parseattr(buf, attrs, size, mtime, fileName, fingerprint, ffp); fingerprint = MegaNodePrivate::addAppPrefixToFingerprint(fingerprint, ffp.size); // Normalize node name to UTF-8 string attr_map::iterator itAttribute = attrs.map.find('n'); if (itAttribute != attrs.map.end() && !itAttribute->second.empty()) { LocalPath::utf8_normalize(&(itAttribute->second)); fileName = itAttribute->second.c_str(); validName = fileName; } delete [] buf; isNodeKeyDecrypted = true; } else { fileName = Node::CRYPTO_ERROR; request->setFlag(true); isNodeKeyDecrypted = false; } if (request->getType() == MegaRequest::TYPE_IMPORT_LINK) { auto parenthandle = NodeHandle().set6byte(request->getParentHandle()); shared_ptr<Node> target = client->nodeByHandle(parenthandle); if (!target) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_EARGS)); return; } NodeHandle ovhandle; std::shared_ptr<Node> ovn = client->childnodebyname(target.get(), validName.c_str(), true); if (ovn) { if (ffp.isvalid && ovn->isvalid && ffp == *(FileFingerprint*)ovn.get()) { request->setNodeHandle(ovn->nodehandle); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return; } ovhandle = ovn->nodeHandle(); } vector<NewNode> newnodes(1); auto newnode = &newnodes[0]; // set up new node as folder node newnode->source = NEW_PUBLIC; newnode->type = FILENODE; newnode->nodehandle = ph; newnode->parenthandle = UNDEF; newnode->nodekey.assign((char*)key,FILENODEKEYLENGTH); newnode->attrstring.reset(new string(*a)); newnode->ovhandle = ovhandle; // add node requestMap.erase(request->getTag()); int nextTag = client->nextreqtag(); request->setTag(nextTag); requestMap[nextTag]=request; Pitag pitag{PitagPurpose::Import, PitagTrigger::NotApplicable, PitagNodeType::File, PitagTarget::CloudDrive, PitagImportSource::FileLink}; if (target->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; })) { pitag.target = PitagTarget::IncomingShare; } client->putnodes(parenthandle, UseLocalVersioningFlag, std::move(newnodes), nullptr, nextTag, false, {}, // customerIpPort nullptr, pitag); } else { MegaNodePrivate *megaNodePrivate = new MegaNodePrivate(fileName.c_str(), FILENODE, size, 0, mtime, ph, &keystring, fa, fingerprint.size() ? fingerprint.c_str() : NULL, originalfingerprint.size() ? originalfingerprint.c_str() : NULL, INVALID_HANDLE, INVALID_HANDLE, nullptr, nullptr, true, false, nullptr, isNodeKeyDecrypted); request->setPublicNode(megaNodePrivate); delete megaNodePrivate; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_OK)); } } // it may need a full reload, depending on the reason of the error void MegaApiImpl::notifyError(const char* reason, ErrorReason errorReason) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_FATAL_ERROR); event->setText(reason); event->setNumber(static_cast<int64_t>(errorReason)); fireOnEvent(event); } void MegaApiImpl::reloading() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_RELOADING); fireOnEvent(event); } // nodes have been modified // (nodes with their removed flag set will be deleted immediately after returning from this call, // at which point their pointers will become invalid at that point.) void MegaApiImpl::nodes_updated(sharedNode_vector* nodes, int count) { LOG_debug << "Nodes updated: " << count; if (!count) { return; } MegaNodeList *nodeList = NULL; if (nodes != NULL) { nodeList = new MegaNodeListPrivate(*nodes); fireOnNodesUpdate(nodeList); } else { fireOnNodesUpdate(NULL); } delete nodeList; } void MegaApiImpl::account_details(AccountDetails*, bool, bool, bool, bool, bool, bool) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_ACCOUNT_DETAILS && request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)) return; if (request->getType() == MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN) { // only ever one message client->purchase_enumeratequotaitems(); return; } long long numPending = request->getNumber(); numPending--; request->setNumber(numPending); if(!numPending) { bool storage_requested = request->getNumDetails() & 0x01; if (storage_requested && !request->getAccountDetails()->storage_max) fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_EACCESS)); else fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(MegaError::API_OK)); } } void MegaApiImpl::account_details(AccountDetails*, error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_ACCOUNT_DETAILS && request->getType() != MegaRequest::TYPE_GET_RECOMMENDED_PRO_PLAN)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::querytransferquota_result(int code) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_QUERY_TRANSFER_QUOTA)) return; // pre-warn about a possible overquota for codes 2 and 3, like in the webclient request->setFlag((code == 2 || code == 3) ? true : false); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } void MegaApiImpl::removecontact_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_REMOVE_CONTACT)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getua_completion(error e, MegaRequestPrivate* request) { // if attempted to get ^!prd attribute but not exists yet... if (e == API_ENOENT) { if (request->getParamType() == MegaApi::USER_ATTR_PWD_REMINDER) { if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER) { string newValue; User::mergePwdReminderData(request->getNumDetails(), NULL, 0, &newValue); request->setText(newValue.c_str()); // set the attribute using same request tag client->putua(ATTR_PWD_REMINDER, (byte*) newValue.data(), unsigned(newValue.size()), client->restag, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return; } else if (request->getType() == MegaRequest::TYPE_GET_ATTR_USER) { m_time_t currenttime = m_time(); if ((currenttime - client->accountsince) > User::PWD_SHOW_AFTER_ACCOUNT_AGE && (currenttime - client->tsLogin) > User::PWD_SHOW_AFTER_LASTLOGIN) { request->setFlag(true); // the password reminder dialog should be shown } } } else if (request->getParamType() == MegaApi::USER_ATTR_RICH_PREVIEWS && request->getType() == MegaRequest::TYPE_GET_ATTR_USER) { if (request->getNumDetails() == 0) // used to check if rich-links are enabled { request->setFlag(false); } else if (request->getNumDetails() == 1) // used to check if should show warning { request->setFlag(true); } } else if ((request->getParamType() == MegaApi::USER_ATTR_ALIAS || request->getParamType() == MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER || request->getParamType() == MegaApi::USER_ATTR_DEVICE_NAMES || request->getParamType() == MegaApi::USER_ATTR_CC_PREFS || request->getParamType() == MegaApi::USER_ATTR_APPS_PREFS) && request->getType() == MegaRequest::TYPE_SET_ATTR_USER) { // The attribute doesn't exists so we have to create it string_map records; MegaStringMap *stringMap = request->getMegaStringMap(); std::unique_ptr<MegaStringList> keys(stringMap->getKeys()); attr_t type = static_cast<attr_t>(request->getParamType()); bool keyPrefixModifier = request->getParamType() == MegaApi::USER_ATTR_DEVICE_NAMES && request->getFlag(); string keyPrefix = User::attributePrefixInTLV(type, keyPrefixModifier); for (int i = 0; i < keys->size(); i++) { const char *key = keys->get(i); records.emplace(keyPrefix + key, Base64::atob(stringMap->get(key))); } client->putua(type, std::move(records), client->restag, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return; } else if ((request->getType() == MegaRequest::TYPE_GET_ATTR_USER) && (request->getParamType() == MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG || request->getParamType() == MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE)) { request->setFlag(true); } else if ((request->getParamType() == MegaApi::USER_ATTR_LAST_READ_NOTIFICATION || request->getParamType() == MegaApi::USER_ATTR_LAST_ACTIONED_BANNER) && request->getType() == MegaRequest::TYPE_GET_ATTR_USER) { request->setNumber(0); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getua_completion(byte* data, unsigned len, attr_t type, MegaRequestPrivate* request) { error e = API_OK; assert(type == request->getParamType()); if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER) { static_assert(int(ATTR_PWD_REMINDER) == int(MegaApi::USER_ATTR_PWD_REMINDER), "User Attribute Enum Mismatch"); if (int(type) == MegaApi::USER_ATTR_PWD_REMINDER) { // merge received value with updated items string newValue; bool changed = User::mergePwdReminderData(request->getNumDetails(), (const char*) data, len, &newValue); request->setText(newValue.data()); if (changed) { // set the attribute using same request tag client->putua(ATTR_PWD_REMINDER, (byte*) newValue.data(), unsigned(newValue.size()), client->restag, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } else { LOG_debug << "Password-reminder data not changed, already up to date"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } } return; } // only for TYPE_GET_ATTR_USER switch (type) { case MegaApi::USER_ATTR_AVATAR: if (len) { auto f = client->fsaccess->newfileaccess(); string filePath(request->getFile()); auto localPath = LocalPath::fromAbsolutePath(filePath); fsAccess->unlinklocal(localPath); bool success = f->fopen(localPath, OPEN_WRONLY, FSLogging::logOnError) && f->fwrite((const byte*)data, len, 0); f.reset(); if (!success) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EWRITE)); return; } } else // no data for the avatar { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); return; } break; // null-terminated char arrays case MegaApi::USER_ATTR_FIRSTNAME: case MegaApi::USER_ATTR_LASTNAME: case MegaApi::USER_ATTR_LANGUAGE: // it's a c-string in binary format, want the plain data case MegaApi::USER_ATTR_PWD_REMINDER: case MegaApi::USER_ATTR_DISABLE_VERSIONS: case MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION: case MegaApi::USER_ATTR_NO_CALLKIT: case MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG: case MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE: { string str((const char*)data,len); request->setText(str.c_str()); static_assert(int(MegaApi::USER_ATTR_DISABLE_VERSIONS) == ATTR_DISABLE_VERSIONS, "User Attribute Enum Mismatch"); static_assert(int(MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION) == ATTR_CONTACT_LINK_VERIFICATION, "User Attribute Enum Mismatch"); static_assert(int(MegaApi::USER_ATTR_PWD_REMINDER) == ATTR_PWD_REMINDER, "User Attribute Enum Mismatch"); static_assert(int(MegaApi::USER_ATTR_NO_CALLKIT) == ATTR_NO_CALLKIT, "User Attribute Enum Mismatch"); static_assert(int(MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG) == ATTR_VISIBLE_WELCOME_DIALOG, "User Attribute Enum Mismatch"); static_assert(int(MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE) == ATTR_VISIBLE_TERMS_OF_SERVICE, "User Attribute Enum Mismatch"); if (int(type) == MegaApi::USER_ATTR_DISABLE_VERSIONS || int(type) == MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION || int(type) == MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG || int(type) == MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE) { request->setFlag(str == "1"); } else if (int(type) == MegaApi::USER_ATTR_PWD_REMINDER) { m_time_t currenttime = m_time(); bool isMasterKeyExported = User::getPwdReminderData(User::PWD_MK_EXPORTED, (const char*)data, len) != 0; bool isLogout = request->getNumber() != 0; bool pwdDontShow = User::getPwdReminderData(User::PWD_DONT_SHOW, (const char*)data, len) != 0; if ((!isMasterKeyExported && !pwdDontShow && (currenttime - client->accountsince) > User::PWD_SHOW_AFTER_ACCOUNT_AGE && (currenttime - User::getPwdReminderData(User::PWD_LAST_SUCCESS, (const char*)data, len)) > User::PWD_SHOW_AFTER_LASTSUCCESS && (currenttime - User::getPwdReminderData(User::PWD_LAST_LOGIN, (const char*)data, len)) > User::PWD_SHOW_AFTER_LASTLOGIN && (currenttime - User::getPwdReminderData(User::PWD_LAST_SKIPPED, (const char*)data, len)) > (request->getNumber() ? User::PWD_SHOW_AFTER_LASTSKIP_LOGOUT : User::PWD_SHOW_AFTER_LASTSKIP) && (currenttime - client->tsLogin) > User::PWD_SHOW_AFTER_LASTLOGIN) || (isLogout && !pwdDontShow)) { request->setFlag(true); // the password reminder dialog should be shown } request->setAccess(isMasterKeyExported ? 1 : 0); } } break; // numbers case MegaApi::USER_ATTR_RUBBISH_TIME: case MegaApi::USER_ATTR_STORAGE_STATE: { char *endptr; string str((const char*)data, len); m_off_t value = strtoll(str.c_str(), &endptr, 10); if (endptr == str.c_str() || *endptr != '\0' || value == LLONG_MAX || value == LLONG_MIN) { value = -1; } request->setNumber(value); static_assert(int(MegaApi::USER_ATTR_STORAGE_STATE) == ATTR_STORAGE_STATE, "User Attribute Enum Mismatch"); if (int(type) == MegaApi::USER_ATTR_STORAGE_STATE && (value < MegaApi::STORAGE_STATE_GREEN || value > MegaApi::STORAGE_STATE_RED)) { e = API_EINTERNAL; } } break; case MegaApi::USER_ATTR_COOKIE_SETTINGS: { e = getCookieSettings_getua_result(data, len, request); } break; case MegaApi::USER_ATTR_PUSH_SETTINGS: { request->setMegaPushNotificationSettings(getMegaPushNotificationSetting().get()); } break; case MegaApi::USER_ATTR_MY_BACKUPS_FOLDER: { handle h = 0; if (len != MegaClient::NODEHANDLE) { LOG_err << "Wrong received data size for 'My Backups' node handle: " << len << "; expected " << MegaClient::NODEHANDLE; assert(false); } memcpy(&h, data, MegaClient::NODEHANDLE); if (!client->nodebyhandle(h)) { LOG_warn << "'My Backups' node was missing, or invalid handle in USER_ATTR_MY_BACKUPS_FOLDER"; e = API_ENOENT; } else { request->setNodeHandle(h); } } break; case MegaApi::USER_ATTR_PWM_BASE: { request->setNodeHandle(client->getPasswordManagerBase().as8byte()); } break; case MegaApi::USER_ATTR_LAST_READ_NOTIFICATION: { e = getLastReadNotification_getua_result(data, len, request); } break; case MegaApi::USER_ATTR_LAST_ACTIONED_BANNER: { e = getLastActionedBanner_getua_result(data, len, request); } break; // byte arrays with possible nulls in the middle --> to Base64 case MegaApi::USER_ATTR_ED25519_PUBLIC_KEY: // fall-through { if (request->getFlag()) // asking for the fingerprint { string fingerprint = AuthRing::fingerprint(string((const char*)data, len), true); request->setPassword(fingerprint.c_str()); break; } } // fall through case MegaApi::USER_ATTR_CU25519_PUBLIC_KEY: case MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY: case MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY: default: { string str; str.resize(len * 4 / 3 + 4); str.resize(Base64::btoa(data, len, (char*)str.data())); request->setText(str.c_str()); } break; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getua_completion(unique_ptr<string_map> uaRecords, attr_t type, MegaRequestPrivate* request) { error e = API_OK; assert(type == static_cast<attr_t>(request->getParamType())); if (uaRecords) { string_map& records = *uaRecords; if (request->getType() == MegaRequest::TYPE_SET_ATTR_USER) { const string_map *newValuesMap = static_cast<MegaStringMapPrivate*>(request->getMegaStringMap())->getMap(); unique_ptr<string_map> prefixedValueMap; if (type == ATTR_DEVICE_NAMES) { // allow only unique names for Devices and Drives if (haveDuplicatedValues(records, *newValuesMap)) // ignores keys { e = API_EEXIST; LOG_err << "Attribute " << User::attr2string(type) << " attempted to add duplicated value (2): " << Base64::atob(newValuesMap->begin()->second); // will only have a single value fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } if (request->getFlag()) // external drive { string prefix = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true); prefixedValueMap = std::make_unique<string_map>(); for_each(newValuesMap->begin(), newValuesMap->end(), [&prefixedValueMap, &prefix](const string_map::value_type& a) { prefixedValueMap->emplace(prefix + a.first, a.second); }); newValuesMap = prefixedValueMap.get(); } } if (User::mergeUserAttribute(type, *newValuesMap, records)) { client->putua(type, string_map{records}, client->restag, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } else { LOG_debug << "Attribute " << User::attr2string(type) << " not changed, already up to date"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } return; } // end of get+set if (request->getParamType() == MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP) { MegaTimeStamp time = formatRecentClearTimestamp(&records); if (MegaClient::isValidMegaTimeStamp(time)) { std::unique_ptr<MegaStringMap> stringMap(new MegaStringMapPrivate(&records, false)); request->setMegaStringMap(stringMap.get()); request->setNumber(time); } else { e = API_EINTERNAL; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } // TLV data usually includes byte arrays with zeros in the middle, so values // must be converted into Base64 strings to avoid problems std::unique_ptr<MegaStringMap> stringMap(new MegaStringMapPrivate(&records, true)); request->setMegaStringMap(stringMap.get()); switch (request->getParamType()) { // prepare request params to know if a warning should show or not case MegaApi::USER_ATTR_RICH_PREVIEWS: { const char *num = stringMap->get("num"); if (request->getNumDetails() == 0) // used to check if rich-links are enabled { if (num) { string sValue = num; string bValue; Base64::atob(sValue, bValue); request->setFlag(bValue == "1"); } else { request->setFlag(false); } } else if (request->getNumDetails() == 1) // used to check if should show warning { request->setFlag(!num); // it doesn't matter the value, just if it exists const char *value = stringMap->get("c"); if (value) { string sValue = value; string bValue; Base64::atob(sValue, bValue); request->setNumber(atoi(bValue.c_str())); } } break; } case MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER: case MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER: { // If attr is CAMERA_UPLOADS_FOLDER determine if we want to retrieve primary or secondary folder // If attr is MY_CHAT_FILES_FOLDER, there's no secondary folder const char *key = request->getParamType() == MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER && request->getFlag() ? "sh" : "h"; const char *value = stringMap->get(key); if (!value) { e = API_ENOENT; break; } else { handle nodehandle = 0; // make sure top two bytes are 0 Base64::atob(value, (byte*) &nodehandle, MegaClient::NODEHANDLE); request->setNodeHandle(nodehandle); } break; } case MegaApi::USER_ATTR_ALIAS: { // If a handle was set in the request, we have to find it in the corresponding map and return it if (const char* h = request->getText()) { if (auto it = records.find(h); it != records.end()) { request->setName(it->second.c_str()); } else { e = API_ENOENT; } } break; } case MegaApi::USER_ATTR_DEVICE_NAMES: { if (!request->getFlag() && !request->getText()) // all devices and drives { // the list of device names is passed in the MegaStringMap of the MegaRequest break; } const char* buf = nullptr; if (request->getFlag()) // external drive { handle driveId = request->getNodeHandle(); string key = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true) + string(Base64Str<MegaClient::DRIVEHANDLE>(driveId)); buf = stringMap->get(key.c_str()); } else if (request->getText()) // device { buf = stringMap->get(request->getText()); } if (buf) { request->setName(Base64::atob(buf).c_str()); } else { e = API_ENOENT; } break; } default: break; } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } #ifdef DEBUG void MegaApiImpl::delua_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_DEL_ATTR_USER)) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } #endif void MegaApiImpl::senddevcommand_result(int value) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || (request->getType() != MegaRequest::TYPE_SEND_DEV_COMMAND)) return; error e = static_cast<error>(value); std::string command = request->getName() ? request->getName() :""; if (!command.compare("aodq") && value > 0) { e = API_OK; request->setNumber(value); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getuseremail_result(string *email, error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || (request->getType() != MegaRequest::TYPE_GET_USER_EMAIL)) { return; } if (e == API_OK && email) { request->setEmail(email->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return; } // user attribute update notification void MegaApiImpl::userattr_update(User*, int, const char*) { } void MegaApiImpl::nodes_current() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_NODES_CURRENT); fireOnEvent(event); } void MegaApiImpl::catchup_result() { // sc requests are sent sequentially, it must be the one at front and already started (tag == 1) MegaRequestPrivate *request = scRequestQueue.front(); if (!request || (request->getType() != MegaRequest::TYPE_CATCHUP) || !request->getTag()) return; request = scRequestQueue.pop(); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); // if there are more sc requests in the queue, send the next one if (scRequestQueue.front()) { waiter->notify(); } } void MegaApiImpl::key_modified(handle userhandle, attr_t attribute) { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_KEY_MODIFIED); switch (attribute) { case ATTR_CU25519_PUBK: event->setNumber(0); break; case ATTR_ED25519_PUBK: event->setNumber(1); break; case ATTR_UNKNOWN: // used internally for RSA event->setNumber(2); break; case ATTR_SIG_CU255_PUBK: event->setNumber(3); break; case ATTR_SIG_RSA_PUBK: event->setNumber(4); break; default: event->setNumber(-1); break; } event->setHandle(userhandle); fireOnEvent(event); } void MegaApiImpl::upgrading_security() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_UPGRADE_SECURITY); fireOnEvent(event); } void MegaApiImpl::downgrade_attack() { MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_DOWNGRADE_ATTACK); fireOnEvent(event); } void MegaApiImpl::ephemeral_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::ephemeral_result(handle uh, const byte* pw) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return; // save uh and pwcipher for session resumption of ephemeral accounts string sid; if (client->loggedin() == EPHEMERALACCOUNT) { char buf[SymmCipher::KEYLENGTH * 4 / 3 + 3]; Base64::btoa((byte*) &uh, sizeof uh, buf); sid.append(buf); sid.append("#"); Base64::btoa(pw, SymmCipher::KEYLENGTH, buf); sid.append(buf); } else // ephemeral++ { string session; client->dumpsession(session); sid = Base64::btoa(session); } request->setSessionKey(sid.c_str()); // chain a fetchnodes to get waitlink for ephemeral account int creqtag = client->reqtag; client->reqtag = client->restag; client->fetchnodes(false, false, false); client->reqtag = creqtag; } void MegaApiImpl::cancelsignup_result(error e) { auto it = requestMap.find(client->restag); if (it == requestMap.end()) return; MegaRequestPrivate *request = it->second; if (!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::whyamiblocked_result(int code) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_WHY_AM_I_BLOCKED))) { return; } if (code <= 0) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(code)); } else // code > 0 { string reason = "Your account was terminated due to a breach of Mega's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse."; if (code == MegaApi::ACCOUNT_BLOCKED_TOS_COPYRIGHT) { reason = "Your account has been suspended due to copyright violations. Please check your email inbox."; } else if (code == MegaApi::ACCOUNT_BLOCKED_TOS_NON_COPYRIGHT) { reason = "Your account was terminated due to a breach of MEGA's Terms of Service, such as abuse of rights of others; sharing and/or importing illegal data; or system abuse."; } else if (code == MegaApi::ACCOUNT_BLOCKED_SUBUSER_DISABLED) { reason = "Your account has been disabled by your administrator. You may contact your business account administrator for further details."; } else if (code == MegaApi::ACCOUNT_BLOCKED_SUBUSER_REMOVED) { reason = "Your account has been removed by your administrator. You may contact your business account administrator for further details."; } else if (code == MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS) { reason = "Your account has been blocked pending verification via SMS."; } else if (code == MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL) { reason = "Your account has been temporarily suspended for your safety. Please verify your email and follow its steps to unlock your account."; } //else if (code == ACCOUNT_BLOCKED_DEFAULT) --> default reason bool logoutAllowed = request->getFlag(); request->setNumber(code); request->setText(reason.c_str()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); MegaEventPrivate *event = new MegaEventPrivate(MegaEvent::EVENT_ACCOUNT_BLOCKED); event->setNumber(code); event->setText(reason.c_str()); fireOnEvent(event); // (don't log out if we can be unblocked by email or sms) if (logoutAllowed && code != MegaApi::ACCOUNT_BLOCKED_VERIFICATION_SMS && code != MegaApi::ACCOUNT_BLOCKED_VERIFICATION_EMAIL) { bool keepSyncConfigsFile = true; client->locallogout(true, keepSyncConfigsFile); MegaRequestPrivate *logoutRequest = new MegaRequestPrivate(MegaRequest::TYPE_LOGOUT); logoutRequest->setFlag(false); logoutRequest->setTransferTag(keepSyncConfigsFile); logoutRequest->setParamType(API_EBLOCKED); logoutRequest->performRequest = [this, logoutRequest]() { return performRequest_logout(logoutRequest); }; requestQueue.push(logoutRequest); waiter->notify(); } } } void MegaApiImpl::contactlinkcreate_result(error e, handle h) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_CREATE))) { return; } if (!e) { request->setNodeHandle(h); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::contactlinkquery_result(error e, handle h, string *email, string *firstname, string *lastname, string *avatar) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_QUERY))) { return; } if (!e) { request->setParentHandle(h); request->setEmail(email->c_str()); request->setName(Base64::atob(*firstname).c_str()); request->setText(Base64::atob(*lastname).c_str()); request->setFile(avatar->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::contactlinkdelete_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_CONTACT_LINK_DELETE))) { return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::keepmealive_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_KEEP_ME_ALIVE))) { return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::getpsa_result(error e, int id, string *title, string *text, string *image, string *buttontext, string *buttonlink, std::string *url) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_GET_PSA))) { return; } if (!e) { request->setNumber(id); if (request->getFlag()) // supports URL retrieval { request->setEmail(url->c_str()); } //else -> when `w` is provided, it's still possible to receive the old format request->setName(title->c_str()); request->setText(text->c_str()); request->setFile(image->c_str()); request->setPassword(buttontext->c_str()); request->setLink(buttonlink->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::multifactorauthsetup_result(string *code, error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET) && (request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET))) { return; } if (request->getType() == MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET && !e) { if (!code) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EINTERNAL)); return; } request->setText(code->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::multifactorauthcheck_result(int enabled) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK))) { return; } if (enabled < 0) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(enabled)); return; } request->setFlag(enabled != 0); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } void MegaApiImpl::multifactorauthdisable_result(error e) { if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET))) { return; } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::fetchtimezone_result(error e, vector<std::string> *timezones, vector<int> *timezoneoffsets, int defaulttz) { unique_ptr<MegaTimeZoneDetails> tzDetails; if (!e) { tzDetails.reset(new MegaTimeZoneDetailsPrivate(timezones, timezoneoffsets, defaulttz)); // update the cached timezones for notifications filtering delete mTimezones; mTimezones = tzDetails->copy(); } if (requestMap.find(client->restag) == requestMap.end()) { return; } MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_FETCH_TIMEZONE))) { return; } request->setTimeZoneDetails(tzDetails.get()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::acknowledgeuseralerts_result(error e) { map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag); if (it != requestMap.end()) { MegaRequestPrivate* request = it->second; if (request && ((request->getType() == MegaRequest::TYPE_USERALERT_ACKNOWLEDGE))) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } } void MegaApiImpl::smsverificationsend_result(error e) { map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag); if (it != requestMap.end()) { MegaRequestPrivate* request = it->second; if (request && ((request->getType() == MegaRequest::TYPE_SEND_SMS_VERIFICATIONCODE))) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } } void MegaApiImpl::smsverificationcheck_result(error e, string* phoneNumber) { map<int, MegaRequestPrivate *>::iterator it = requestMap.find(client->restag); if (it != requestMap.end()) { MegaRequestPrivate* request = it->second; if (request && ((request->getType() == MegaRequest::TYPE_CHECK_SMS_VERIFICATIONCODE))) { if (e == API_OK && phoneNumber) { request->setName(phoneNumber->c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } } void MegaApiImpl::getcountrycallingcodes_result(error e, map<string, vector<string>>* data) { auto it = requestMap.find(client->restag); if (it != requestMap.end()) { MegaRequestPrivate* request = it->second; if (request && ((request->getType() == MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES))) { if (data) { auto stringListMap = std::unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()}; for (const auto& pair : *data) { string_vector list; for (const auto& value : pair.second) { list.emplace_back(value); } auto stringList = new MegaStringListPrivate(std::move(list)); stringListMap->set(pair.first.c_str(), stringList); } request->setMegaStringListMap(stringListMap.get()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } } void MegaApiImpl::sendsignuplink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if(!request || ((request->getType() != MegaRequest::TYPE_CREATE_ACCOUNT) && (request->getType() != MegaRequest::TYPE_SEND_SIGNUP_LINK))) return; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::confirmsignuplink2_result(handle, const char *name, const char *email, error e) { if (requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); if (!request || ((request->getType() != MegaRequest::TYPE_CONFIRM_ACCOUNT) && (request->getType() != MegaRequest::TYPE_QUERY_SIGNUP_LINK))) return; if (!e) { assert(strcmp(email, request->getEmail()) == 0); assert(strcmp(name, request->getName()) == 0); request->setName(name); request->setEmail(email); request->setFlag(true); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } void MegaApiImpl::setkeypair_result(error) { } void MegaApiImpl::getbanners_result(error e) { auto it = requestMap.find(client->restag); if (it != requestMap.end() && it->second && (it->second->getType() == MegaRequest::TYPE_GET_BANNERS)) { fireOnRequestFinish(it->second, std::make_unique<MegaErrorPrivate>(e)); } } void MegaApiImpl::getbanners_result(vector<BannerDetails>&& banners) { auto it = requestMap.find(client->restag); if (it == requestMap.end()) return; MegaRequestPrivate* request = it->second; if (!request || (request->getType() != MegaRequest::TYPE_GET_BANNERS)) return; request->setBanners(std::move(banners)); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } void MegaApiImpl::dismissbanner_result(error e) { auto itReq = requestMap.find(client->restag); if (itReq != requestMap.end() && itReq->second && (itReq->second->getType() == MegaRequest::TYPE_DISMISS_BANNER)) { fireOnRequestFinish(itReq->second, std::make_unique<MegaErrorPrivate>(e)); } } void MegaApiImpl::reqstat_progress(int permilprogress) { MegaEventPrivate* event = new MegaEventPrivate(MegaEvent::EVENT_REQSTAT_PROGRESS); event->setNumber(permilprogress); fireOnEvent(event); } void MegaApiImpl::addListener(MegaListener* listener) { if(!listener) return; SdkMutexGuard g(sdkMutex); listeners.insert(listener); } void MegaApiImpl::addRequestListener(MegaRequestListener* listener) { if(!listener) return; SdkMutexGuard g(sdkMutex); requestListeners.insert(listener); } void MegaApiImpl::addTransferListener(MegaTransferListener* listener) { if(!listener) return; SdkMutexGuard g(sdkMutex); transferListeners.insert(listener); } void MegaApiImpl::addScheduledCopyListener(MegaScheduledCopyListener* listener) { if(!listener) return; SdkMutexGuard g(sdkMutex); backupListeners.insert(listener); } void MegaApiImpl::addGlobalListener(MegaGlobalListener* listener) { if(!listener) return; SdkMutexGuard g(sdkMutex); globalListeners.insert(listener); } bool MegaApiImpl::removeListener(MegaListener* listener) { if(!listener) return false; SdkMutexGuard g(sdkMutex); return listeners.erase(listener) > 0; } bool MegaApiImpl::removeRequestListener(MegaRequestListener* listener) { if(!listener) return false; SdkMutexGuard g(sdkMutex); auto removed = requestListeners.erase(listener) > 0; std::map<int, MegaRequestPrivate*>::iterator it = requestMap.begin(); while(it != requestMap.end()) { MegaRequestPrivate* request = it->second; if(request->getListener() == listener) request->setListener(NULL); it++; } requestQueue.removeListener(listener); return removed; } bool MegaApiImpl::removeTransferListener(MegaTransferListener* listener) { if(!listener) return false; SdkMutexGuard g(sdkMutex); auto removed = transferListeners.erase(listener) > 0; std::map<int, MegaTransferPrivate*>::iterator it = transferMap.begin(); while(it != transferMap.end()) { MegaTransferPrivate* transfer = it->second; if(transfer->getListener() == listener) transfer->setListener(NULL); it++; } transferQueue.removeListener(listener); return removed; } bool MegaApiImpl::removeScheduledCopyListener(MegaScheduledCopyListener* listener) { if(!listener) return false; SdkMutexGuard g(sdkMutex); auto removed = backupListeners.erase(listener); std::map<int, MegaScheduledCopyController*>::iterator it = backupsMap.begin(); while(it != backupsMap.end()) { MegaScheduledCopyController* backup = it->second; if(backup->getBackupListener() == listener) backup->setBackupListener(NULL); it++; } requestQueue.removeListener(listener); return removed > 0; } bool MegaApiImpl::removeGlobalListener(MegaGlobalListener* listener) { if(!listener) return false; SdkMutexGuard g(sdkMutex); return globalListeners.erase(listener) > 0; } void MegaApiImpl::fireOnRequestStart(MegaRequestPrivate *request) { assert(threadId == std::this_thread::get_id()); LOG_info << client->clientname << "Request (" << request->getRequestString() << ") starting"; for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;) { (*it++)->onRequestStart(api, request); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onRequestStart(api, request); } MegaRequestListener* listener = request->getListener(); if(listener) { listener->onRequestStart(api, request); } } void MegaApiImpl::fireOnRequestFinish(MegaRequestPrivate* request, unique_ptr<MegaErrorPrivate> e, [[maybe_unused]] bool callbackIsFromSyncThread) { assert(callbackIsFromSyncThread || threadId == std::this_thread::get_id()); #ifdef ENABLE_SYNC assert(!callbackIsFromSyncThread || client->syncs.onSyncThread()); #endif // call from other threads like sync thread. Push to requestQueue with performFireOnRequestFinish assigned, // all fireOneRequestFinish is therefore handled in sendPendingRequests processed by a single thread. if (threadId != std::this_thread::get_id()) { auto ePtr = e.release(); request->performFireOnRequestFinish = [this, request, ePtr]() { fireOnRequestFinish(request, std::unique_ptr<MegaErrorPrivate>(ePtr), false); }; requestQueue.push(request); waiter->notify(); return; } if(e->getErrorCode()) { LOG_warn << (client ? client->clientname : "") << "Request (" << request->getRequestString() << ") finished with error: " << e->getErrorString(); } else { LOG_info << (client ? client->clientname : "") << "Request (" << request->getRequestString() << ") finished"; } for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;) { (*it++)->onRequestFinish(api, request, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onRequestFinish(api, request, e.get()); } MegaRequestListener* listener = request->getListener(); if(listener) { listener->onRequestFinish(api, request, e.get()); } requestMap.erase(request->getTag()); delete request; } void MegaApiImpl::fireOnRequestUpdate(MegaRequestPrivate *request) { assert(threadId == std::this_thread::get_id()); for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;) { (*it++)->onRequestUpdate(api, request); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onRequestUpdate(api, request); } MegaRequestListener* listener = request->getListener(); if(listener) { listener->onRequestUpdate(api, request); } } void MegaApiImpl::fireOnRequestTemporaryError(MegaRequestPrivate *request, unique_ptr<MegaErrorPrivate> e) { assert(threadId == std::this_thread::get_id()); request->setNumRetry(request->getNumRetry() + 1); for(set<MegaRequestListener *>::iterator it = requestListeners.begin(); it != requestListeners.end() ;) { (*it++)->onRequestTemporaryError(api, request, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onRequestTemporaryError(api, request, e.get()); } MegaRequestListener* listener = request->getListener(); if(listener) { listener->onRequestTemporaryError(api, request, e.get()); } } void MegaApiImpl::fireOnTransferStart(MegaTransferPrivate *transfer) { assert(threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;) { (*it++)->onTransferStart(api, transfer); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onTransferStart(api, transfer); } MegaTransferListener* listener = transfer->getListener(); if(listener) { listener->onTransferStart(api, transfer); } } void MegaApiImpl::fireOnTransferFinish(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { assert(threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); transfer->setLastError(e.get()); if(e->getErrorCode()) { if (!(transfer->getState() == MegaTransfer::STATE_CANCELLED && e->getErrorCode() == API_EINCOMPLETE && transfer->getFolderTransferTag() > 0)) { // avoid logging large numbers of lines for cancelled folder transfers with huge numbers of sub-transfers LOG_warn << "Transfer (" << transfer->getTransferString() << ") finished with error: " << e->getErrorString() << " File: " << transfer->getFileName(); if (e->hasExtraInfo() && e->getErrorCode() == API_ETOOMANY) { LOG_warn << "ETD affected: user status: " << e->getUserStatus() << " link status: " << e->getLinkStatus(); } } } else { LOG_info << "Transfer (" << transfer->getTransferString() << ") finished. File: " << transfer->getFileName(); } if (transfer->getType() == MegaTransfer::TYPE_UPLOAD && transfer->isSourceFileTemporary() && transfer->fingerprint_filetype == FILENODE && transfer->getPath() && (transfer->getState() == MegaTransfer::STATE_COMPLETED || transfer->getState() == MegaTransfer::STATE_CANCELLED || transfer->getState() == MegaTransfer::STATE_FAILED)) { const auto wLocalPath = transfer->getLocalPath(); bool fileRemoved = !client->fsaccess->fileExistsAt(wLocalPath); if (!fileRemoved) { // This prevents that file is tried to be removed more than once. // Anyway in case that happens, FileSystemAccess::transient_error wouldn't be set true fileRemoved = client->fsaccess->unlinklocal(wLocalPath); if (!fileRemoved) { LOG_err << "fireOnTransferFinish (TYPE_UPLOAD): cannot remove temporary local file " << wLocalPath; } } transfer->setStage(fileRemoved); } for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;) { (*it++)->onTransferFinish(api, transfer, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onTransferFinish(api, transfer, e.get()); } MegaTransferListener* listener = transfer->getListener(); if (listener) { listener->onTransferFinish(api, transfer, e.get()); } transferMap.erase(transfer->getTag()); if (transfer->isStreamingTransfer()) { client->removeAppData(transfer); } delete transfer; } void MegaApiImpl::fireOnTransferTemporaryError(MegaTransferPrivate *transfer, unique_ptr<MegaErrorPrivate> e) { assert(threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); transfer->setNumRetry(transfer->getNumRetry() + 1); for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;) { (*it++)->onTransferTemporaryError(api, transfer, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onTransferTemporaryError(api, transfer, e.get()); } MegaTransferListener* listener = transfer->getListener(); if(listener) { listener->onTransferTemporaryError(api, transfer, e.get()); } } MegaClient *MegaApiImpl::getMegaClient() { return client; } void MegaApiImpl::fireOnTransferUpdate(MegaTransferPrivate *transfer) { assert(threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); for(set<MegaTransferListener *>::iterator it = transferListeners.begin(); it != transferListeners.end() ;) { (*it++)->onTransferUpdate(api, transfer); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onTransferUpdate(api, transfer); } MegaTransferListener* listener = transfer->getListener(); if(listener) { listener->onTransferUpdate(api, transfer); } } void MegaApiImpl::fireOnFolderTransferUpdate(MegaTransferPrivate *transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const LocalPath* currentFolder, const LocalPath* currentFileLeafname) { // this occurs on worker thread for scanning stage (for uploads) and create tree (for downloads), and on SDK thread for the rest of calls assert((threadId != std::this_thread::get_id() && ((stage == MegaTransfer::STAGE_SCAN && transfer->getType() == MegaTransfer::TYPE_UPLOAD) || (stage == MegaTransfer::STAGE_CREATE_TREE && transfer->getType() == MegaTransfer::TYPE_DOWNLOAD))) || threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); // This one is defined to only be called back on the listener for the transfer // not any of the global or megaapi listeners if (MegaTransferListener* listener = transfer->getListener()) { listener->onFolderTransferUpdate(api, transfer, stage, foldercount, createdfoldercount, filecount, currentFolder ? currentFolder->toPath(false).c_str() : nullptr, currentFileLeafname ? currentFileLeafname->toPath(false).c_str() : nullptr); } } bool MegaApiImpl::fireOnTransferData(MegaTransferPrivate *transfer) { assert(threadId == std::this_thread::get_id()); notificationNumber++; transfer->setNotificationNumber(notificationNumber); bool result = false; MegaTransferListener* listener = transfer->getListener(); if(listener) { result = listener->onTransferData(api, transfer, transfer->getLastBytes(), size_t(transfer->getDeltaSize())); } return result; } void MegaApiImpl::fireOnUsersUpdate(MegaUserList *users) { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onUsersUpdate(api, users); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onUsersUpdate(api, users); } } void MegaApiImpl::fireOnUserAlertsUpdate(MegaUserAlertList *userAlerts) { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onUserAlertsUpdate(api, userAlerts); } for (set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end();) { (*it++)->onUserAlertsUpdate(api, userAlerts); } } void MegaApiImpl::fireOnContactRequestsUpdate(MegaContactRequestList *requests) { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onContactRequestsUpdate(api, requests); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onContactRequestsUpdate(api, requests); } } void MegaApiImpl::fireOnNodesUpdate(MegaNodeList *nodes) { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onNodesUpdate(api, nodes); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onNodesUpdate(api, nodes); } } void MegaApiImpl::fireOnAccountUpdate() { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onAccountUpdate(api); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onAccountUpdate(api); } } void MegaApiImpl::fireOnSetsUpdate(MegaSetList* sets) { assert(threadId == std::this_thread::get_id()); for (set<MegaGlobalListener*>::iterator it = globalListeners.begin(); it != globalListeners.end();) { (*it++)->onSetsUpdate(api, sets); } for (set<MegaListener*>::iterator it = listeners.begin(); it != listeners.end();) { (*it++)->onSetsUpdate(api, sets); } } void MegaApiImpl::fireOnSetElementsUpdate(MegaSetElementList* elements) { assert(threadId == std::this_thread::get_id()); for (set<MegaGlobalListener*>::iterator it = globalListeners.begin(); it != globalListeners.end();) { (*it++)->onSetElementsUpdate(api, elements); } for (set<MegaListener*>::iterator it = listeners.begin(); it != listeners.end();) { (*it++)->onSetElementsUpdate(api, elements); } } void MegaApiImpl::fireOnEvent(MegaEventPrivate *event) { LOG_debug << "Sending " << event->getEventString() << " to app." << event->getValidDataToString(); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onEvent(api, event); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onEvent(api, event); } delete event; } #ifdef ENABLE_SYNC void MegaApiImpl::fireOnSyncStateChanged(MegaSyncPrivate *sync) { assert(sync->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onSyncStateChanged(api, sync); } } void MegaApiImpl::fireOnSyncStatsUpdated(MegaSyncStatsPrivate *stats) { assert(stats->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onSyncStatsUpdated(api, stats); } } void MegaApiImpl::fireOnSyncAdded(MegaSyncPrivate *sync) { assert(sync->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onSyncAdded(api, sync); } } void MegaApiImpl::fireOnSyncRemoteRootChanged(MegaSyncPrivate* sync) { assert(sync->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for (set<MegaListener*>::iterator it = listeners.begin(); it != listeners.end();) { (*it++)->onSyncRemoteRootChanged(api, sync); } } void MegaApiImpl::fireOnSyncDeleted(MegaSyncPrivate *sync) { assert(sync->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onSyncDeleted(api, sync); } } void MegaApiImpl::fireOnGlobalSyncStateChanged() { assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onGlobalSyncStateChanged(api); } for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onGlobalSyncStateChanged(api); } } void MegaApiImpl::fireOnFileSyncStateChanged(MegaSyncPrivate *sync, string *localPath, int newState) { assert(sync->getBackupId() != INVALID_HANDLE); assert(client->syncs.onSyncThread()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onSyncFileStateChanged(api, sync, localPath, newState); } } #endif void MegaApiImpl::fireOnBackupStateChanged(MegaScheduledCopyController *backup) { assert(threadId == std::this_thread::get_id()); for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onBackupStateChanged(api, backup); } for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;) { (*it++)->onBackupStateChanged(api, backup); } MegaScheduledCopyListener* listener = backup->getBackupListener(); if(listener) { listener->onBackupStateChanged(api, backup); } } void MegaApiImpl::fireOnBackupStart(MegaScheduledCopyController *backup) { for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;) { (*it++)->onBackupStart(api, backup); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onBackupStart(api, backup); } MegaScheduledCopyListener* listener = backup->getBackupListener(); if(listener) { listener->onBackupStart(api, backup); } } void MegaApiImpl::fireOnBackupFinish(MegaScheduledCopyController *backup, unique_ptr<MegaErrorPrivate> e) { for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;) { (*it++)->onBackupFinish(api, backup, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onBackupFinish(api, backup, e.get()); } MegaScheduledCopyListener* listener = backup->getBackupListener(); if(listener) { listener->onBackupFinish(api, backup, e.get()); } } void MegaApiImpl::fireOnBackupTemporaryError(MegaScheduledCopyController *backup, unique_ptr<MegaErrorPrivate> e) { for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;) { (*it++)->onBackupTemporaryError(api, backup, e.get()); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onBackupTemporaryError(api, backup, e.get()); } MegaScheduledCopyListener* listener = backup->getBackupListener(); if(listener) { listener->onBackupTemporaryError(api, backup, e.get()); } } void MegaApiImpl::fireOnBackupUpdate(MegaScheduledCopyController *backup) { assert(threadId == std::this_thread::get_id()); // notificationNumber++; //TODO: should we use notificationNumber for backups?? for(set<MegaScheduledCopyListener *>::iterator it = backupListeners.begin(); it != backupListeners.end() ;) { (*it++)->onBackupUpdate(api, backup); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onBackupUpdate(api, backup); } MegaScheduledCopyListener* listener = backup->getBackupListener(); if(listener) { listener->onBackupUpdate(api, backup); } } #ifdef ENABLE_CHAT void MegaApiImpl::fireOnChatsUpdate(MegaTextChatList *chats) { assert(threadId == std::this_thread::get_id()); for(set<MegaGlobalListener *>::iterator it = globalListeners.begin(); it != globalListeners.end() ;) { (*it++)->onChatsUpdate(api, chats); } for(set<MegaListener *>::iterator it = listeners.begin(); it != listeners.end() ;) { (*it++)->onChatsUpdate(api, chats); } } #endif void MegaApiImpl::processTransferPrepare(Transfer *t, MegaTransferPrivate *transfer) { transfer->setTotalBytes(t->size); transfer->setState(t->state); transfer->setPriority(t->priority); LOG_info << "Transfer (" << transfer->getTransferString() << ") starting. File: " << transfer->getFileName(); } void MegaApiImpl::processTransferUpdate(Transfer *tr, MegaTransferPrivate *transfer) { dstime currentTime = Waiter::ds; if (tr->slot) { m_off_t prevTransferredBytes = transfer->getTransferredBytes(); m_off_t deltaSize = tr->slot->progressreported - prevTransferredBytes; LOG_verbose << "Transfer update: progress to update = " << deltaSize << ", transfer size = " << tr->size << ", transferred bytes = " << transfer->getTransferredBytes() << ", progress reported = " << tr->slot->progressreported << ", progress completed = " << tr->progresscompleted << " [speed: " << (tr->slot->speed / 1024) << " KB/s] [mean speed: " << (tr->slot->meanSpeed / 1024) << " KB/s] [transfer->name = " << tr->localfilename << "]"; transfer->setStartTime(currentTime); transfer->setTransferredBytes(tr->slot->progressreported); transfer->setDeltaSize(deltaSize); transfer->setSpeed(tr->slot->speed); transfer->setMeanSpeed(tr->slot->meanSpeed); } else { LOG_verbose << "No TransferSlot. Reset last progress, speed and mean speed."; transfer->setDeltaSize(0); transfer->setSpeed(0); transfer->setMeanSpeed(0); } transfer->setState(tr->state); transfer->setPriority(tr->priority); transfer->setUpdateTime(currentTime); fireOnTransferUpdate(transfer); } void MegaApiImpl::processTransferComplete(Transfer *tr, MegaTransferPrivate *transfer) { dstime currentTime = Waiter::ds; m_off_t deltaSize = tr->size - transfer->getTransferredBytes(); LOG_verbose << "Transfer complete: final progress to update = " << deltaSize << ", transfer size = " << tr->size << ", transferred bytes = " << transfer->getTransferredBytes(); transfer->setStartTime(currentTime); transfer->setUpdateTime(currentTime); transfer->setTransferredBytes(tr->size); transfer->setPriority(tr->priority); transfer->setDeltaSize(deltaSize); if (tr->slot) { transfer->setSpeed(tr->slot->speed); transfer->setMeanSpeed(tr->slot->meanSpeed); } if (tr->type == GET) { transfer->setState(MegaTransfer::STATE_COMPLETED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_OK)); } else { transfer->setState(MegaTransfer::STATE_COMPLETING); transfer->setTransfer(NULL); fireOnTransferUpdate(transfer); } } void MegaApiImpl::processTransferFailed(Transfer *tr, MegaTransferPrivate *transfer, const Error& e, dstime timeleft) { auto megaError = std::make_unique<MegaErrorPrivate>(e, timeleft / 10); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setDeltaSize(0); transfer->setSpeed(0); transfer->setMeanSpeed(0); transfer->setLastError(megaError.get()); transfer->setPriority(tr->priority); if (e == API_ETOOMANY && e.hasExtraInfo()) { transfer->setState(MegaTransfer::STATE_FAILED); transfer->setForeignOverquota(false); fireOnTransferFinish(transfer, std::move(megaError)); } else { transfer->setState(MegaTransfer::STATE_RETRYING); LOG_verbose << "processTransferFailed checking handle " << transfer->getParentHandle(); transfer->setForeignOverquota(e == API_EOVERQUOTA && client->isForeignNode(NodeHandle().set6byte(transfer->getParentHandle()))); fireOnTransferTemporaryError(transfer, std::move(megaError)); } } void MegaApiImpl::processTransferRemoved(Transfer *tr, MegaTransferPrivate *transfer, const Error& e) { if (tr) { transfer->setPriority(tr->priority); } transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(e == API_EINCOMPLETE ? MegaTransfer::STATE_CANCELLED : MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); } MegaError *MegaApiImpl::checkAccessErrorExtended(MegaNode *megaNode, int level) { if(!megaNode || level < MegaShare::ACCESS_UNKNOWN || level > MegaShare::ACCESS_OWNER) { return new MegaErrorPrivate(API_EARGS); } SdkMutexGuard g(sdkMutex); shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); if(!node) { return new MegaErrorPrivate(API_ENOENT); } accesslevel_t a = OWNER; switch(level) { case MegaShare::ACCESS_UNKNOWN: case MegaShare::ACCESS_READ: a = RDONLY; break; case MegaShare::ACCESS_READWRITE: a = RDWR; break; case MegaShare::ACCESS_FULL: a = FULL; break; case MegaShare::ACCESS_OWNER: a = OWNER; break; } return client->checkaccess(node.get(), a) ? new MegaErrorPrivate(API_OK) : new MegaErrorPrivate(API_EACCESS); } MegaError *MegaApiImpl::checkMoveErrorExtended(MegaNode *megaNode, MegaNode *targetNode) { if(!megaNode || !targetNode) return new MegaErrorPrivate(API_EARGS); SdkMutexGuard g(sdkMutex); shared_ptr<Node> node = client->nodebyhandle(megaNode->getHandle()); shared_ptr<Node> target = client->nodebyhandle(targetNode->getHandle()); if(!node || !target) { return new MegaErrorPrivate(API_ENOENT); } return new MegaErrorPrivate(client->checkmove(node.get(), target.get())); } bool MegaApiImpl::isFilesystemAvailable() { SdkMutexGuard g(sdkMutex); return client->nodeByHandle(client->mNodeManager.getRootNodeFiles()) != NULL; } std::function<bool(Node*, Node*)> MegaApiImpl::getComparatorFunction(int order, MegaClient&) { switch (order) { case MegaApi::ORDER_NONE: return nullptr; case MegaApi::ORDER_DEFAULT_ASC: return MegaApiImpl::nodeComparatorDefaultASC; case MegaApi::ORDER_DEFAULT_DESC: return MegaApiImpl::nodeComparatorDefaultDESC; case MegaApi::ORDER_SIZE_ASC: return MegaApiImpl::nodeComparatorSizeASC; case MegaApi::ORDER_SIZE_DESC: return MegaApiImpl::nodeComparatorSizeDESC; case MegaApi::ORDER_CREATION_ASC: return MegaApiImpl::nodeComparatorCreationASC; case MegaApi::ORDER_CREATION_DESC: return MegaApiImpl::nodeComparatorCreationDESC; case MegaApi::ORDER_MODIFICATION_ASC: return MegaApiImpl::nodeComparatorModificationASC; case MegaApi::ORDER_MODIFICATION_DESC: return MegaApiImpl::nodeComparatorModificationDESC; case MegaApi::ORDER_LINK_CREATION_ASC: return MegaApiImpl::nodeComparatorPublicLinkCreationASC; case MegaApi::ORDER_LINK_CREATION_DESC: return MegaApiImpl::nodeComparatorPublicLinkCreationDESC; case MegaApi::ORDER_LABEL_ASC: return MegaApiImpl::nodeComparatorLabelASC; case MegaApi::ORDER_LABEL_DESC: return MegaApiImpl::nodeComparatorLabelDESC; case MegaApi::ORDER_FAV_ASC: return MegaApiImpl::nodeComparatorFavASC; case MegaApi::ORDER_FAV_DESC: return MegaApiImpl::nodeComparatorFavDESC; case MegaApi::ORDER_SHARE_CREATION_ASC: case MegaApi::ORDER_SHARE_CREATION_DESC: return nullptr; } assert(false); return nullptr; } void MegaApiImpl::sortByComparatorFunction(sharedNode_vector&v, int order, MegaClient& mc) { if (auto f = getComparatorFunction(order, mc)) { std::sort(v.begin(), v.end(), [f](std::shared_ptr<Node> i, std::shared_ptr<Node> j) -> bool { return f(i.get(), j.get()); }); } } bool MegaApiImpl::nodeNaturalComparatorASC(Node *i, Node *j) { int r = naturalsorting_compare(i->displayname(), j->displayname()); if (r < 0) { return 1; } return 0; } bool MegaApiImpl::nodeNaturalComparatorDESC(Node *i, Node *j) { int r = naturalsorting_compare(i->displayname(), j->displayname()); if (r <= 0) { return 0; } return 1; } bool MegaApiImpl::nodeComparatorDefaultASC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorDefaultDESC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorDESC(i, j); } bool MegaApiImpl::nodeComparatorSizeASC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } m_off_t r = sizeDifference(i, j); if (r < 0) { return 1; } if (r > 0) { return 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorSizeDESC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } m_off_t r = sizeDifference(i, j); if (r < 0) { return 0; } if (r > 0) { return 1; } return nodeNaturalComparatorDESC(i, j); } bool MegaApiImpl::nodeComparatorCreationASC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (i->ctime < j->ctime) { return 1; } if (i->ctime > j->ctime) { return 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorCreationDESC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (i->ctime < j->ctime) { return 0; } if (i->ctime > j->ctime) { return 1; } return nodeNaturalComparatorDESC(i, j); } bool MegaApiImpl::nodeComparatorModificationASC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (i->type != FILENODE) // Only file nodes have last modified date { // If node doesn't have mtime, order alphabetically ascending return nodeNaturalComparatorASC(i, j); } m_time_t r = i->mtime - j->mtime; if (r < 0) { return 1; } if (r > 0) { return 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorModificationDESC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (i->type != FILENODE) { // If node doesn't have mtime, order alphabetically descending return nodeNaturalComparatorDESC(i, j); } m_time_t r = i->mtime - j->mtime; if (r < 0) { return 0; } if (r > 0) { return 1; } return nodeNaturalComparatorDESC(i, j); } bool MegaApiImpl::nodeComparatorPublicLinkCreationASC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (!i->plink || !j->plink) { return nodeNaturalComparatorASC(i, j); } if (i->plink->cts < j->plink->cts) { return 1; } if (i->plink->cts > j->plink->cts) { return 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorPublicLinkCreationDESC(Node *i, Node *j) { int t = typeComparator(i, j); if (t >= 0) { return t != 0; } if (!i->plink || !j->plink) { return nodeNaturalComparatorDESC(i, j); } if (i->plink->cts < j->plink->cts) { return 0; } if (i->plink->cts > j->plink->cts) { return 1; } return nodeNaturalComparatorDESC(i, j); } bool MegaApiImpl::nodeComparatorLabelASC(Node *i, Node *j) { nameid labelId = AttrMap::string2nameid("lbl"); int iLabel = MegaNode::NODE_LBL_UNKNOWN; auto iAttrIt = i->attrs.map.find(labelId); if (iAttrIt != i->attrs.map.end()) { iLabel = std::atoi(iAttrIt->second.c_str()); } int jLabel = MegaNode::NODE_LBL_UNKNOWN; auto jAttrIt = j->attrs.map.find(labelId); if (jAttrIt != j->attrs.map.end()) { jLabel = std::atoi(jAttrIt->second.c_str()); } if (iLabel == MegaNode::NODE_LBL_UNKNOWN && jLabel == MegaNode::NODE_LBL_UNKNOWN) { return nodeComparatorDefaultASC(i, j); } if (iLabel == MegaNode::NODE_LBL_UNKNOWN) { return 0; } if (jLabel == MegaNode::NODE_LBL_UNKNOWN) { return 1; } if (iLabel < jLabel) { return 1; } if (iLabel > jLabel) { return 0; } int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorLabelDESC(Node* i, Node* j) { nameid labelId = AttrMap::string2nameid("lbl"); int iLabel = MegaNode::NODE_LBL_UNKNOWN; auto iAttrIt = i->attrs.map.find(labelId); if (iAttrIt != i->attrs.map.end()) { iLabel = std::atoi(iAttrIt->second.c_str()); } int jLabel = MegaNode::NODE_LBL_UNKNOWN; auto jAttrIt = j->attrs.map.find(labelId); if (jAttrIt != j->attrs.map.end()) { jLabel = std::atoi(jAttrIt->second.c_str()); } if (iLabel == MegaNode::NODE_LBL_UNKNOWN && jLabel == MegaNode::NODE_LBL_UNKNOWN) { return nodeComparatorDefaultASC(i, j); } if (iLabel == MegaNode::NODE_LBL_UNKNOWN) { return 0; } if (jLabel == MegaNode::NODE_LBL_UNKNOWN) { return 1; } if (iLabel < jLabel) { return 0; } if (iLabel > jLabel) { return 1; } int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorASC(i, j); } bool MegaApiImpl::nodeComparatorFavASC(Node *i, Node *j) { nameid favId = AttrMap::string2nameid("fav"); bool iFav = (i->attrs.map.find(favId) != i->attrs.map.end()); bool jFav = (j->attrs.map.find(favId) != j->attrs.map.end()); if (!(iFav ^ jFav)) { // if both or none of them, have the same attribute value, order type and natural comparator // ASC int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorASC(i, j); } else if (iFav) { return 1; } else { return 0; } } bool MegaApiImpl::nodeComparatorFavDESC(Node *i, Node *j) { nameid favId = AttrMap::string2nameid("fav"); bool iFav = (i->attrs.map.find(favId) != i->attrs.map.end()); bool jFav = (j->attrs.map.find(favId) != j->attrs.map.end()); if (!(iFav ^ jFav)) { // if both or none of them, have the same attribute value, order type and natural comparator // ASC int t = typeComparator(i, j); if (t >= 0) { return t != 0; } return nodeNaturalComparatorASC(i, j); } else if (iFav) { return 0; } else { return 1; } } // Compare node types. Returns -1 if i==j, 0 if i goes first, +1 if j goes first. int MegaApiImpl::typeComparator(Node *i, Node *j) { if (i->type < j->type) { return 0; } if (i->type > j->type) { return 1; } return -1; } int MegaApiImpl::getNumChildren(MegaNode* p) { if (!p || p->getType() == MegaNode::TYPE_FILE) { return 0; } SdkMutexGuard lock(sdkMutex); return static_cast<int>(client->getNumberOfChildren(NodeHandle().set6byte(p->getHandle()))); } int MegaApiImpl::getNumChildFiles(MegaNode* p) { if (!p || p->getType() == MegaNode::TYPE_FILE) { return 0; } SdkMutexGuard lock(sdkMutex); std::shared_ptr<Node> parent = client->nodebyhandle(p->getHandle()); if (!parent || parent->type == FILENODE) { return 0; } return static_cast<int>(client->mNodeManager.getNumberOfChildrenByType(parent->nodeHandle(), FILENODE)); } int MegaApiImpl::getNumChildFolders(MegaNode* p) { if (!p || p->getType() == MegaNode::TYPE_FILE) { return 0; } SdkMutexGuard lock(sdkMutex); std::shared_ptr<Node> parent = client->nodebyhandle(p->getHandle()); if (!parent || parent->type == FILENODE) { return 0; } return static_cast<int>(client->mNodeManager.getNumberOfChildrenByType(parent->nodeHandle(), FOLDERNODE)); } MegaNodeList *MegaApiImpl::getChildren(const MegaSearchFilter* filter, int order, CancelToken cancelToken, const MegaSearchPage* searchPage) { // guard against unsupported or removed order criteria assert((MegaApi::ORDER_NONE <= order && order <= MegaApi::ORDER_MODIFICATION_DESC) || (MegaApi::ORDER_LABEL_ASC <= order && order <= MegaApi::ORDER_FAV_DESC)); // validations if (!filter || filter->byLocationHandle() == INVALID_HANDLE || (filter->byNodeType() == MegaNode::TYPE_FOLDER && filter->byCategory() != MegaApi::FILE_TYPE_DEFAULT)) { assert(filter && filter->byLocationHandle() != INVALID_HANDLE); return new MegaNodeListPrivate(); } SdkMutexGuard guard(sdkMutex); NodeSearchFilter nf = searchToNodeFilter(*filter); const NodeSearchPage& np = searchPage ? NodeSearchPage(searchPage->startingOffset(), searchPage->size()) : NodeSearchPage(0u, 0u); sharedNode_vector results = client->mNodeManager.getChildren(nf, order, cancelToken, np); return new MegaNodeListPrivate(results); } MegaNodeList* MegaApiImpl::listChildNodesLexicographically( const handle parenthandle, CancelToken cancelFlag, const size_t maxElements, const std::optional<MegaSearchLexicographicalOffset>& offset) { const auto megaToNodeOffset = [](const MegaSearchLexicographicalOffset& off) -> NodeSearchLexicographicalOffset { return {off.mLastName, off.mLastType, off.mLastHandle}; }; SdkMutexGuard guard(sdkMutex); sharedNode_vector results = client->mNodeManager.listChildNodesLexicographically(parenthandle, cancelFlag, maxElements, offset | transform(megaToNodeOffset)); return new MegaNodeListPrivate(results); } MegaNodeList *MegaApiImpl::getChildren(const MegaNode* p, int order, CancelToken cancelToken) { if (!p || p->getType() == MegaNode::TYPE_FILE) { return new MegaNodeListPrivate(); } sharedNode_vector childrenNodes; SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> parent = client->nodebyhandle(p->getHandle()); if (parent && parent->type != FILENODE) { sharedNode_list nodeList = client->getChildren(parent.get(), cancelToken); childrenNodes.reserve(nodeList.size()); for (sharedNode_list::iterator it = nodeList.begin(); it != nodeList.end(); ) { childrenNodes.push_back(*it++); } sortByComparatorFunction(childrenNodes, order, *client); } return new MegaNodeListPrivate(childrenNodes); } MegaNodeList *MegaApiImpl::getChildren(MegaNodeList *parentNodes, int order) { SdkMutexGuard guard(sdkMutex); // prepare a vector with children of every parent node all together sharedNode_vector childrenNodes; for (int i = 0; i < parentNodes->size(); i++) { MegaNode *p = parentNodes->get(i); if (!p || p->getType() == MegaNode::TYPE_FILE) { continue; } std::shared_ptr<Node> parent = client->nodebyhandle(p->getHandle()); if (parent && parent->type != FILENODE) { sharedNode_list nodeChildrenList = client->getChildren(parent.get()); childrenNodes.reserve(childrenNodes.size() + nodeChildrenList.size()); for (auto& node : nodeChildrenList) { childrenNodes.push_back(node); } } } sortByComparatorFunction(childrenNodes, order, *client); return new MegaNodeListPrivate(childrenNodes); } MegaNodeList *MegaApiImpl::getVersions(MegaNode *node) { if (!node || node->getType() != MegaNode::TYPE_FILE) { return new MegaNodeListPrivate(); } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> current = client->nodebyhandle(node->getHandle()); if (!current || current->type != FILENODE) { return new MegaNodeListPrivate(); } vector<std::shared_ptr<Node> > versions; versions.push_back(current); bool lookingFor = true; while (lookingFor) { sharedNode_list nodeList = client->getChildren(current.get(), mega::CancelToken(), true); if (nodeList.empty()) { lookingFor = false; } else { assert(nodeList.back()->parent == current); current = nodeList.back(); assert(current->type == FILENODE); versions.push_back(current); } } return new MegaNodeListPrivate(versions); } int MegaApiImpl::getNumVersions(MegaNode *node) { if (!node || node->getType() != MegaNode::TYPE_FILE) { return 0; } SdkMutexGuard guard(sdkMutex); return client->mNodeManager.getNumVersions(NodeHandle().set6byte(node->getHandle())); } bool MegaApiImpl::hasVersions(MegaNode *node) { return getNumVersions(node) > 1; } bool MegaApiImpl::hasChildren(MegaNode *parent) { if (!parent || parent->getType() == MegaNode::TYPE_FILE) { return false; } SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> p = client->nodebyhandle(parent->getHandle()); if (!p || p->type == FILENODE) { return false; } return !!client->getNumberOfChildren(p->nodeHandle()); } MegaNode *MegaApiImpl::getChildNode(MegaNode *parent, const char* name) { if (!parent || !name || parent->getType() == MegaNode::TYPE_FILE) { return NULL; } SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> parentNode = client->nodebyhandle(parent->getHandle()); if (!parentNode || parentNode->type == FILENODE) { return NULL; } return MegaNodePrivate::fromNode(client->childnodebyname(parentNode.get(), name).get()); } MegaNode* MegaApiImpl::getChildNodeOfType(MegaNode *parent, const char *name, int type) { if (!name || !parent || (type != MegaNode::TYPE_FILE && type != MegaNode::TYPE_FOLDER)) { return nullptr; } SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> parentNode = client->nodebyhandle(parent->getHandle()); if (!parentNode || parentNode->type == FILENODE) { return nullptr; } return MegaNodePrivate::fromNode(client->childnodebynametype(parentNode.get(), name, static_cast<nodetype_t>(type)).get()); } std::shared_ptr<Node> MegaApiImpl::getNodeByFingerprintInternal(const char *fingerprint) { unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (!fp) { return NULL; } SdkMutexGuard g(sdkMutex); return client->mNodeManager.getNodeByFingerprint(*fp); } std::shared_ptr<Node> MegaApiImpl::getNodeByFingerprintInternal(const char *fingerprint, Node *parent) { unique_ptr<FileFingerprint> fp(MegaApiImpl::getFileFingerprintInternal(fingerprint)); if (!fp) { return NULL; } std::shared_ptr<Node> n = NULL; SdkMutexGuard g(sdkMutex); sharedNode_vector nodes = client->mNodeManager.getNodesByFingerprint(*fp); if (nodes.size()) { n = nodes.at(0); } if (n && parent && n->parent.get() != parent) { for (unsigned int i = 1; i < nodes.size(); i++) { std::shared_ptr<Node> node = nodes.at(i); if (node->parent.get() == parent) { n = node; break; } } } return n; } FileFingerprint *MegaApiImpl::getFileFingerprintInternal(const char *fingerprint) { m_off_t size = 0; string sfingerprint = MegaNodePrivate::removeAppPrefixFromFingerprint(fingerprint, &size); if (sfingerprint.empty()) return nullptr; FileFingerprint *fp = new FileFingerprint; if(!fp->unserializefingerprint(&sfingerprint)) { delete fp; return NULL; } fp->size = size; return fp; } MegaNode* MegaApiImpl::getParentNode(MegaNode* n) { if(!n) return NULL; SdkMutexGuard g(sdkMutex); std::shared_ptr<Node> node = client->nodebyhandle(n->getHandle()); if(!node) { return NULL; } return MegaNodePrivate::fromNode(node->parent.get()); } char* MegaApiImpl::getNodePath(MegaNode *node) { if(!node) return nullptr; SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> n = client->nodebyhandle(node->getHandle()); if(!n) { return nullptr; } return MegaApi::strdup(n->displaypath().c_str()); } char* MegaApiImpl::getNodePathByNodeHandle(MegaHandle handle) { SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> n = client->nodebyhandle(handle); if(!n) { return nullptr; } return MegaApi::strdup(n->displaypath().c_str()); } MegaNode* MegaApiImpl::getNodeByPath(const char *path, MegaNode* node) { SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> root = nullptr; if (node) { root = client->nodebyhandle(node->getHandle()); if (!root) { return nullptr; } } std::shared_ptr<Node> result = client->nodeByPath(path, root); return MegaNodePrivate::fromNode(result.get()); } MegaNode* MegaApiImpl::getNodeByPathOfType(const char* path, MegaNode* node, int type) { SdkMutexGuard guard(sdkMutex); std::shared_ptr<Node> root = nullptr; if (node) { root = client->nodebyhandle(node->getHandle()); if (!root) { return nullptr; } } nodetype_t t = (type == MegaNode::TYPE_FILE ? FILENODE : (type == MegaNode::TYPE_FOLDER ? FOLDERNODE : TYPE_UNKNOWN)); std::shared_ptr<Node> result = client->nodeByPath(path, root, t); return MegaNodePrivate::fromNode(result.get()); } MegaNode* MegaApiImpl::getNodeByHandle(handle handle) { if(handle == UNDEF) return NULL; SdkMutexGuard g(sdkMutex); return MegaNodePrivate::fromNode(client->nodebyhandle(handle).get()); } MegaTotpTokenGenResult MegaApiImpl::generateTotpTokenFromNode(const MegaHandle handle) { SdkMutexGuard g(sdkMutex); const auto result = client->generateTotpTokenFromNode(handle); return {result.first, {result.second.first, result.second.second}}; } MegaContactRequest *MegaApiImpl::getContactRequestByHandle(MegaHandle handle) { SdkMutexGuard guard(sdkMutex); auto it = client->pcrindex.find(handle); return it == client->pcrindex.end() ? nullptr : MegaContactRequestPrivate::fromContactRequest(it->second.get()); } void MegaApiImpl::updateBackups() { for (std::map<int, MegaScheduledCopyController *>::iterator it = backupsMap.begin(); it != backupsMap.end(); ++it) { MegaScheduledCopyController *backupController=it->second; backupController->update(); } } bool MegaApiImpl::updateNodeMtime(std::shared_ptr<Node> node, MegaTransferPrivate* transfer, const m_time_t newMtime, const int nextTag) { if (!transfer) { LOG_err << "updateNodeMtime: invalid transfer"; assert(false); return false; } if (!node) { LOG_err << "updateNodeMtime: invalid node"; assert(false); return false; } LOG_debug << "Updating mtime to node(" << toNodeHandle(node->nodeHandle()) << ")"; const auto immediateErrCode = client->updateNodeMtime( node, newMtime, [this, nextTag](NodeHandle h, Error e) { MegaTransferPrivate* transfer = getMegaTransferPrivate(nextTag); if (!transfer) { LOG_debug << "updateNodeMtime for node(" << toNodeHandle(h) << ") has finished with errorCode(" << e << "), but transfer with tag(" << nextTag << ") does not exists anymore"; return; } if (e) { LOG_debug << "updateNodeMtime could not update mtime for node(" << toNodeHandle(h) << "), errorCode(" << e << ")"; transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_EWRITE)); } else { LOG_debug << "updateNodeMtime has finished successfully for node(" << toNodeHandle(h) << ")"; transfer->setState(MegaTransfer::STATE_COMPLETED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); } }); if (immediateErrCode != API_OK) { LOG_debug << "updateNodeMtime immediate error for node(" << toNodeHandle(node->nodehandle) << ")"; return false; } transfer->setState(MegaTransfer::STATE_QUEUED); transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setTotalBytes(transfer->fingerprint_onDisk.size); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); fireOnTransferStart(transfer); transfer->setDeltaSize(transfer->fingerprint_onDisk.size); transfer->setSpeed(0); transfer->setMeanSpeed(0); transfer->setState(MegaTransfer::STATE_COMPLETING); fireOnTransferUpdate(transfer); return true; } MegaFilePut* MegaApiImpl::createMegaFileForRemoteCopyTransfer(MegaTransferPrivate& megaTransfer, std::shared_ptr<Node> prevNodeSameName, TransferDbCommitter& committer) { auto inboxTarget = megaTransfer.getInboxTarget(); string sname = megaTransfer.getFileName(); bool isSourceTemporary = megaTransfer.isSourceFileTemporary(); MegaFilePut* f = new MegaFilePut(client, megaTransfer.getLocalPath(), &sname, NodeHandle().set6byte(megaTransfer.getParentHandle()), inboxTarget.has_value() ? inboxTarget->c_str() : "", megaTransfer.getTime(), isSourceTemporary, prevNodeSameName); *static_cast<FileFingerprint*>(f) = megaTransfer.fingerprint_onDisk; // deliberate slicing - startxfer would re-fingerprint if // we don't supply this info f->setPitag(megaTransfer.getPitag()); f->setTransfer( &megaTransfer); // sets internal `megaTransfer`, different from internal `transfer`! f->cancelToken = megaTransfer.accessCancelToken(); Transfer* t = new Transfer(client, direction_t::PUT); t->skipserialization = false; *(FileFingerprint*)t = *(FileFingerprint*)f; t->tag = megaTransfer.getTag(); f->tag = megaTransfer.getTag(); t->size = megaTransfer.getTotalBytes(); t->progresscompleted = megaTransfer.getTotalBytes(); f->file_it = t->files.insert(t->files.end(), f); f->transfer = t; if (!f->dbid) { client->filecacheadd(f, committer); } f->prepare(*client->fsaccess); client->transferlist.addtransfer(t, committer, false); client->app->transfer_added(t); client->app->file_added(f); return f; } void MegaApiImpl::executeOnThread(shared_ptr<ExecuteOnce> f) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_EXECUTE_ON_THREAD, nullptr); request->functionToExecute = std::move(f); requestQueue.push_front(request); // these operations are part of requests that already queued, and should occur before other requests; queue at front waiter->notify(); } bool CollisionChecker::CompareLocalFileMetaMac(FileAccess* fa, MegaNode* fileNode) { if (fileNode->getNodeKey() == nullptr) { return false; } auto name = fileNode->getName() ? fileNode->getName() : std::optional<std::string>(std::nullopt); return CompareLocalFileMetaMacWithNodeKey(fa, *fileNode->getNodeKey(), fileNode->getType(), name) .areEqualMacs; } bool CollisionChecker::fingerprintEqualRelaxed(const FileFingerprint& lhs, const FileFingerprint& rhs) { #ifdef __ANDROID__ return lhs.equalExceptMtime(rhs); #else return lhs == rhs; #endif } CollisionChecker::Result CollisionChecker::check(std::function<bool()> fingerprintEqualF, std::function<bool()> metamacEqualF, Option option) { auto decision = CollisionChecker::Result::Download; switch (option) { case Option::AssumeSame: { decision = Result::Skip; break; } case Option::AlwaysError: { decision = Result::ReportError; break; } case Option::Fingerprint: { if (fingerprintEqualF()) { decision = Result::Skip; } break; } case Option::Metamac: { if (metamacEqualF()) { decision = Result::Skip; } break; } case Option::AssumeDifferent: { decision = Result::Download; break; } default: break; } return decision; } CollisionChecker::Result CollisionChecker::check(std::function<FileAccess*()> faGetter, MegaNode* fileNode, Option option) { if (!fileNode) { return CollisionChecker::Result::NotYet; } auto fingerprintEqualF = [fileNode, faGetter]() { auto fa = faGetter(); if (!fa) { return false; } unique_ptr<FileFingerprint> ff(MegaApiImpl::getFileFingerprintInternal(fileNode->getFingerprint())); if (!ff) { return false; } FileFingerprint fp; auto resGenFp = fp.genfingerprint(fa); return ff->isvalid && resGenFp && fp.isvalid && fingerprintEqualRelaxed(*ff, fp); }; auto metaMacFunc = [fileNode, faGetter]() { auto fa = faGetter(); if (!fa) { return false; } return CompareLocalFileMetaMac(fa, fileNode); }; return check( fingerprintEqualF, metaMacFunc, option); } CollisionChecker::Result CollisionChecker::check(FileSystemAccess* fsaccess, const LocalPath& fileLocalPath, MegaNode* fileNode, Option option) { auto fa = fsaccess->newfileaccess(); auto fap = fa.get(); auto faGetter = [fap, &fileLocalPath]() { return fap->fopen(fileLocalPath, OPEN_RDONLY, FSLogging::logExceptFileNotFound) && fap->type == FILENODE ? fap : nullptr; }; return CollisionChecker::check(std::move(faGetter), fileNode, option); } CollisionChecker::Result CollisionChecker::check(std::function<FileAccess* ()> faGetter, Node* node, Option option) { if (!node) { return CollisionChecker::Result::NotYet; } auto fingerprintEqualF = [node, faGetter]() { auto fa = faGetter(); if (!fa) { return false; } FileFingerprint nodeFp = *node; FileFingerprint fp; return nodeFp.isvalid && fp.genfingerprint(fa) && fp.isvalid && fingerprintEqualRelaxed(fp, nodeFp); }; auto metaMacFunc = [node, faGetter]() { auto fa = faGetter(); if (!fa) { return false; } return CompareLocalFileMetaMacWithNode(fa, node); }; return check( fingerprintEqualF, metaMacFunc, option); } unsigned MegaApiImpl::sendPendingTransfers(TransferQueue *queue, MegaRecursiveOperation* recursiveTransfer, m_off_t availableDiskSpace) { CodeCounter::ScopeTimer ccst(client->performanceStats.megaapiSendPendingTransfers); auto t0 = std::chrono::steady_clock::now(); unsigned count = 0; SdkMutexGuard guard(sdkMutex); TransferDbCommitter committer(client->tctable); TransferQueue &auxQueue = queue ? *queue // custom transferQueue used for folder uploads/downloads : transferQueue; // transfer queue of class MegaApiImpl // if we are processing a folder transfer custom queue, we need to process in one shot // if the folder transfer cancel token is activated, each of the remaining // transfers will be straight away called back for start/finish rather than // passed to the SDK. bool canSplit = !queue; while (MegaTransferPrivate *transfer = auxQueue.pop()) { error e = API_OK; int nextTag = client->nextreqtag(); transfer->setState(MegaTransfer::STATE_QUEUED); if (transfer->accessCancelToken().isCancelled()) { if (queue && recursiveTransfer && recursiveTransfer->isCancelledByFolderTransferToken()) { // shortcut in case we have a huge queue // millions of listener callbacks, logging etc takes a while LOG_debug << "Folder transfer is cancelled, skipping remaining subtransfers: " << auxQueue.size(); recursiveTransfer->setTransfersTotalCount( recursiveTransfer->getTransfersTotalCount() - auxQueue.size()); // Remove remaining transfers in recursiveTransfer's custom queue auxQueue.clear(); // let the pop'd transfer notify the parent, we may be completely finished // otherwise the folder completes when transfers already established in the SDK core finish } transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setState(MegaTransfer::STATE_QUEUED); fireOnTransferStart(transfer); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_CANCELLED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_EINCOMPLETE)); continue; } switch(transfer->getType()) { case MegaTransfer::TYPE_UPLOAD: { LocalPath wLocalPath = transfer->getLocalPath(); const char* fileName = transfer->getFileName(); int64_t mtime = transfer->getTime(); bool isSourceTemporary = transfer->isSourceFileTemporary(); std::shared_ptr<Node> parent = client->nodebyhandle(transfer->getParentHandle()); bool startFirst = transfer->shouldStartFirst(); // This bool below is a bit tricky: for example, this would be true for // uploadForSupport: the MegaUploadOptionsPrivate::mTargetUser is populated with // MegaClient::SUPPORT_USER_HANDLE (length = 11), and that value is used to populate // transfer->parentPath (i.e.: it's not really a path, but a handle). At the same // time, parentHandle is undef. So "uploadToInbox" would be true here. Later, when // creating the MegaFilePut object, the cusertarget constructor param will have the // value of inboxTarget (see below), so MegaFilePut::targetuser will have the value // of MegaClient::SUPPORT_USER_HANDLE. This comparison (File::targetuser != // MegaClient::SUPPORT_USER_HANDLE) can be used later to check if a transfer is for // support. auto inboxTarget = transfer->getInboxTarget(); if (wLocalPath.empty() || !fileName || !(*fileName) || (!inboxTarget.has_value() && (!parent || parent->type == FILENODE))) { e = API_EARGS; break; } if (parent && parent->inshare && !client->checkaccess(parent.get(), RDWR)) { e = API_EACCESS; break; } if (transfer->fingerprint_error != API_OK) { LOG_debug << "Upload had already failed before queueing"; e = transfer->fingerprint_error; break; } if (transfer->fingerprint_filetype == FILENODE && !transfer->fingerprint_onDisk.isvalid) { LOG_debug << "Upload had already failed to be fingerprinted before queueing"; e = API_EREAD; break; } assert(transfer->fingerprint_filetype == FILENODE || transfer->fingerprint_filetype == FOLDERNODE); if (transfer->fingerprint_filetype == FILENODE) { FileFingerprint fp_forCloud = transfer->fingerprint_onDisk; // don't clone an existing node unless it also already has the overridden mtime // don't think that an existing file at this path is the right file unless it also has the overridden mtime if (mtime != MegaApi::INVALID_CUSTOM_MOD_TIME) { fp_forCloud.mtime = mtime; } auto immediateFinishSameNodeNameFoundInTarget = [nextTag, this](MegaTransferPrivate* transfer, const handle h) { if (!transfer) { LOG_err << "finishTransferSameNodeNameFoundInTarget: invalid transfer"; assert(false); return; } LOG_debug << "Another node(" << toNodeHandle(h) << ") with same name, FP and MAC exists in target, and the " << "upload is not forced."; transfer->setState(MegaTransfer::STATE_QUEUED); transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setTotalBytes(transfer->fingerprint_onDisk.size); transfer->setTransferredBytes(0); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); fireOnTransferStart(transfer); transfer->setNodeHandle(h); transfer->setDeltaSize(transfer->fingerprint_onDisk.size); transfer->setSpeed(0); transfer->setMeanSpeed(0); transfer->setState(MegaTransfer::STATE_COMPLETED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_OK)); }; auto forceToUpload{false}; const auto skipSearchBySameName = !parent || client->getNumberOfChildren(parent->nodeHandle()) > MAX_CHILDREN_FOR_SAME_NAME_SEARCH; const auto prevNodeSameName = !skipSearchBySameName ? client->childnodebyname(parent.get(), fileName, false) : nullptr; if (prevNodeSameName) { if (prevNodeSameName->type == FOLDERNODE) { if (!recursiveTransfer) { e = API_EARGS; break; } /* In case we found a folder (in cloud drive) with a duplicate name for * any subfile of the folder we are trying to upload SDK core will * resolve the name conflict * - If versioning is enabled, it creates a new version. * - If versioning is disabled, it overwrites the file (old one is * deleted permanently). */ } else if (forceToUpload = hasToForceUpload(*prevNodeSameName.get(), *transfer); !forceToUpload) { auto [compRes, _] = CompareLocalFileWithNodeMacAndFpExludingMtime( *client, wLocalPath, fp_forCloud, prevNodeSameName.get()); if (compRes == NODE_COMP_DIFFERS_MTIME) { if (auto succeeded = updateNodeMtime(prevNodeSameName, transfer, fp_forCloud.mtime, nextTag); succeeded) { e = API_OK; // In case of mtime update, Transfer // (Start/Update/Finish) is delegated to // updateNodeMtime so ensure `e` is API OK to avoid // duplicated actions break; } // Fallback setmtime failed with immediate error, so let's try with // clone node/regular upload } else if (compRes == NODE_COMP_EQUAL) { immediateFinishSameNodeNameFoundInTarget( transfer, prevNodeSameName->nodehandle); break; } } // Do not make transfer fail if CompareLocalFileWithNodeFpAndMac result // is not NODE_COMP_EQUAL, just continue with transfer } // If has been found by name and it's necessary force upload, it isn't necessary // look for it again if (!forceToUpload) { std::shared_ptr<Node> sameNodeFpFound; sharedNode_vector nodes = client->mNodeManager.getNodesByFingerprint(fp_forCloud, true /*excludeMtime*/); const auto alreadyCheckedSameNodeNameInTarget = !skipSearchBySameName; bool sameNodeSameNameInTarget{false}; // MAC computation requires reading the local file (expensive I/O). When // many cloud nodes share the same fingerprint, verifying all of them is // wasteful. The two-phase search below minimizes MAC computations by // prioritizing high-value candidates first and deferring the remote-copy // fallback search. const auto findNodeWithMacMatch = [&](auto shouldCheckMac) -> bool { for (auto& n: nodes) { if (!hasToForceUpload(*n.get(), *transfer)) { const bool nameMatch = (n->parent && parent) && (n->parent->nodeHandle() == parent->nodeHandle()) && (fileName == n->displayname()); if (shouldCheckMac(nameMatch)) { auto [compRes, _] = CompareLocalFileWithNodeMacAndFpExludingMtime( *client, wLocalPath, fp_forCloud, n.get()); if (compRes == NODE_COMP_EQUAL || compRes == NODE_COMP_DIFFERS_MTIME) { // There only can be one node with same name in target // node sameNodeFpFound = n; sameNodeSameNameInTarget = nameMatch; return true; } } } } return false; }; // Phase 1: check MAC only for nodes that are worth verifying immediately. // - Small folder (alreadyCheckedSameNodeNameInTarget == true): the name // lookup was already done, so any fingerprint-matching node is a valid // candidate; check all of them and break on the first MAC match. // - Large folder (alreadyCheckedSameNodeNameInTarget == false): the name // lookup was skipped; only check nodes whose name and parent match the // upload target, since those are the only ones that can skip the upload // entirely (same content, same location). findNodeWithMacMatch( [&](bool nameMatch) { return alreadyCheckedSameNodeNameInTarget || nameMatch; }); // Phase 2: remote-copy fallback, only for large folders when Phase 1 found // nothing. A cloud node at a different location but with the same content // (MAC-verified) can be used as the source of a server-side copy, avoiding // a full re-upload. nameMatch nodes are skipped here because they were // already checked in Phase 1. Breaks at the first MAC-verified match, so // typically only one extra MAC computation is needed. if (!sameNodeFpFound && !alreadyCheckedSameNodeNameInTarget) { findNodeWithMacMatch( [](bool nameMatch) { return !nameMatch; }); } if (sameNodeFpFound) { const auto differentMtime{fp_forCloud.mtime != sameNodeFpFound->mtime}; if (sameNodeSameNameInTarget) { if (differentMtime) { if (auto succeeded = updateNodeMtime(sameNodeFpFound, transfer, fp_forCloud.mtime, nextTag); succeeded) { e = API_OK; // In case of mtime update, Transfer // (Start/Update/Finish) is delegated to // updateNodeMtime so ensure `e` is API OK to // avoid duplicated actions break; } // Fallback setmtime failed with immediate error, so let's try // with clone node } else { immediateFinishSameNodeNameFoundInTarget( transfer, sameNodeFpFound->nodehandle); break; } } LOG_debug << "Another node (" << toNodeHandle(sameNodeFpFound->nodehandle) << ") with same FP and MAC exists in Cloud. Perform remote " "copy"; Pitag pitag = transfer->getPitag(); pitag.purpose = PitagPurpose::CopyInternal; pitag.nodeType = PitagNodeType::File; if (pitag.target == PitagTarget::NotApplicable) { const bool inIncomingShare = parent && parent->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); pitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } transfer->setPitag(pitag); currentTransfer = transfer; transfer->setState(MegaTransfer::STATE_QUEUED); transfer->setTag(nextTag); transfer->setTotalBytes(transfer->fingerprint_onDisk.size); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); auto f = createMegaFileForRemoteCopyTransfer(*transfer, sameNodeFpFound, committer); e = client->transferRemoteCopy( f, sameNodeFpFound, transfer->getFileName(), parent, nextTag, differentMtime ? fp_forCloud : std::optional<FileFingerprint>{std::nullopt}, inboxTarget); if (e == API_OK) { transfer->setDeltaSize(transfer->fingerprint_onDisk.size); transfer->setSpeed(0); transfer->setMeanSpeed(0); } break; } } currentTransfer = transfer; string wFileName = fileName; MegaFilePut* f = new MegaFilePut(client, std::move(wLocalPath), &wFileName, NodeHandle().set6byte(transfer->getParentHandle()), inboxTarget.has_value() ? inboxTarget->c_str() : "", mtime, isSourceTemporary, prevNodeSameName); *static_cast<FileFingerprint*>(f) = transfer->fingerprint_onDisk; // deliberate slicing - startxfer would re-fingerprint if we don't supply this info f->setTransfer(transfer); // sets internal `megaTransfer`, different from internal `transfer`! f->cancelToken = transfer->accessCancelToken(); f->setPitag(transfer->getPitag()); error result = API_OK; bool started = client->startxfer(PUT, f, committer, true, startFirst, transfer->isBackupTransfer(), UseLocalVersioningFlag, &result, nextTag); if (!started) { transfer->setState(MegaTransfer::STATE_QUEUED); if (!f->isvalid) { //Unable to read the file transferMap[nextTag] = transfer; transfer->setTag(nextTag); fireOnTransferStart(transfer); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_EREAD)); } else { MegaTransferPrivate* prevTransfer = NULL; auto range = client->multi_transfers[PUT].equal_range(f); for (auto it = range.first; it != range.second; ++it) { Transfer *t = it->second; for (file_list::iterator fi = t->files.begin(); fi != t->files.end(); fi++) { if (f->h != UNDEF && f->h == (*fi)->h && !f->targetuser.size() && !(*fi)->targetuser.size() && f->name == (*fi)->name) { prevTransfer = getMegaTransferPrivate((*fi)->tag); break; } } } if (prevTransfer && transfer->getAppData()) { string appData = prevTransfer->getAppData() ? string(prevTransfer->getAppData()) + "!" : string(); appData.append(transfer->getAppData()); prevTransfer->setAppData(appData.c_str()); } //Already existing transfer transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setTotalBytes(f->size); fireOnTransferStart(transfer); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_CANCELLED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(result)); } delete f; // `started` was false, `f` wasn't stored at Transfer::files } currentTransfer = NULL; } else { transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->startRecursiveOperation(std::make_shared<MegaFolderUploadController>(this, transfer), nullptr); } break; } case MegaTransfer::TYPE_DOWNLOAD: { std::shared_ptr<Node> node; MegaNode *publicNode = transfer->getPublicNode(); MegaNode *nodeToUndelete = transfer->getNodeToUndelete(); const char *parentPath = transfer->getParentPath(); const char *fileName = transfer->getFileName(); LocalPath path = transfer->getLocalPath(); bool startFirst = transfer->shouldStartFirst(); if (!publicNode) { handle nodehandle = transfer->getNodeHandle(); node = nodeToUndelete ? nullptr : client->nodebyhandle(nodehandle); } if (!node && !nodeToUndelete && !publicNode) { e = API_ENOENT; break; } if (!transfer->isStreamingTransfer() && !parentPath && !fileName) { e = API_EARGS; break; } if (!transfer->isStreamingTransfer() && ((node && node->type != FILENODE) || (publicNode && publicNode->getType() != FILENODE)) ) { // Folder download transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->startRecursiveOperation(std::make_shared<MegaFolderDownloadController>(this, transfer), publicNode); break; } // File download if (!transfer->isStreamingTransfer()) { LocalPath name; LocalPath wLocalPath; if (path.isURI()) { wLocalPath = path.parentPath(); } else if (parentPath) { wLocalPath = LocalPath::fromAbsolutePath(parentPath); } else { // Using the Absolute form, and passing "." means an absolute path of the current folder // (ideally client apps would only pass absolute paths) wLocalPath = LocalPath::fromAbsolutePath("."); wLocalPath.appendWithSeparator(LocalPath::fromRelativePath(""), true); } FileSystemType fsType = transfer->getFileSystemType() != FileSystemType::FS_UNKNOWN ? transfer->getFileSystemType() : fsAccess->getlocalfstype(wLocalPath); if (node) { if (!fileName) { attr_map::iterator ait = node->attrs.map.find('n'); if (ait == node->attrs.map.end()) { name = LocalPath::fromRelativePath(Node::CRYPTO_ERROR); } else if(!ait->second.size()) { name = LocalPath::fromRelativePath(Node::BLANK); } else { name = LocalPath::fromRelativeName(ait->second, *fsAccess, fsType); } } else { name = LocalPath::fromRelativeName(fileName, *fsAccess, fsType); } } else { if (!transfer->getFileName()) { name = LocalPath::fromRelativeName(publicNode->getName(), *fsAccess, fsType); } else { name = LocalPath::fromRelativeName(transfer->getFileName(), *fsAccess, fsType); } } wLocalPath.appendWithSeparator(name, true); transfer->setLocalPath(wLocalPath); // retry requires path set // collision check if hasn't been checked yet auto fa = fsAccess->newfileaccess(); if (fa && (transfer->getCollisionCheckResult() == CollisionChecker::Result::NotYet) && fa->fopen(wLocalPath, OPEN_RDONLY, FSLogging::logExceptFileNotFound) && fa->type == FILENODE) { auto fap = fa.get(); if (node) { transfer->setCollisionCheckResult( CollisionChecker::check( [fap]() { return fap; } , node.get() , transfer->getCollisionCheck())); } else { transfer->setCollisionCheckResult( CollisionChecker::check( [fap]() { return fap; } , publicNode , transfer->getCollisionCheck())); } } else if (transfer->getCollisionCheckResult() == CollisionChecker::Result::NotYet) // no collision { transfer->setCollisionCheckResult(CollisionChecker::Result::Download); } // decision check for early returns { auto decision = transfer->getCollisionCheckResult(); if (decision == CollisionChecker::Result::ReportError) { e = API_EEXIST; break; } else if (decision == CollisionChecker::Result::Skip) // complete with OK { CompleteFileDownloadBySkip(transfer, node ? node->size : publicNode->getSize(), node ? node->nodehandle : publicNode->getHandle(), nextTag, wLocalPath); break; } } currentTransfer = transfer; unique_ptr<MegaFileGet> f; if (node) { f.reset(new MegaFileGet(client, node.get(), wLocalPath, fsType, transfer->getCollisionResolution())); } else if (nodeToUndelete) { f.reset(new MegaFileGet(client, nodeToUndelete, wLocalPath, transfer->getCollisionResolution())); f->setUndelete(); } else { f.reset(new MegaFileGet(client, publicNode, wLocalPath, transfer->getCollisionResolution())); } f->setTransfer(transfer); f->cancelToken = transfer->accessCancelToken(); bool skipDuplicates = transfer->getFolderTransferTag() <= 0; //Let folder subtransfer have duplicates, so that repeated downloads can co-exist and progress accordingly mega::error cause; bool ok = client->startxfer(GET, f.release(), committer, skipDuplicates, startFirst, false, UseLocalVersioningFlag, &cause, nextTag, availableDiskSpace); if (!ok) { //Already existing transfer transfer->setState(MegaTransfer::STATE_QUEUED); transferMap[nextTag]=transfer; transfer->setTag(nextTag); fireOnTransferStart(transfer); long long overquotaDelay = getBandwidthOverquotaDelay(); if (overquotaDelay) { fireOnTransferTemporaryError(transfer, std::make_unique<MegaErrorPrivate>(API_EOVERQUOTA, overquotaDelay)); } transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(cause)); } } else { currentTransfer = transfer; m_off_t startPos = transfer->getStartPos(); m_off_t endPos = transfer->getEndPos(); if (startPos < 0 || endPos < 0 || startPos > endPos) { e = API_EARGS; break; } if (node) { transfer->setFileName(node->displayname()); if (startPos >= node->size || endPos >= node->size) { e = API_EARGS; break; } m_off_t totalBytes = endPos - startPos + 1; transferMap[nextTag]=transfer; transfer->setTotalBytes(totalBytes); transfer->setTag(nextTag); transfer->setState(MegaTransfer::STATE_QUEUED); fireOnTransferStart(transfer); client->pread(node.get(), startPos, totalBytes, transfer); waiter->notify(); } else { MegaNode* notOwnedNode = nodeToUndelete ? nodeToUndelete : publicNode; transfer->setFileName(notOwnedNode->getName()); if (startPos >= notOwnedNode->getSize() || endPos >= notOwnedNode->getSize()) { e = API_EARGS; break; } m_off_t totalBytes = endPos - startPos + 1; transferMap[nextTag]=transfer; transfer->setTotalBytes(totalBytes); transfer->setTag(nextTag); transfer->setState(MegaTransfer::STATE_QUEUED); fireOnTransferStart(transfer); SymmCipher cipher; cipher.setkey(notOwnedNode->getNodeKey()); MegaNodePrivate* privateNode = dynamic_cast<MegaNodePrivate*>(notOwnedNode); assert(privateNode); client->pread(notOwnedNode->getHandle(), &cipher, MemAccess::get<int64_t>( (const char*)notOwnedNode->getNodeKey()->data() + SymmCipher::KEYLENGTH), startPos, totalBytes, transfer, !notOwnedNode->isForeign(), privateNode->getPrivateAuth()->c_str(), privateNode->getPublicAuth()->c_str(), privateNode->getChatAuth()); waiter->notify(); } } currentTransfer = NULL; break; } } if (e) { transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setState(MegaTransfer::STATE_QUEUED); fireOnTransferStart(transfer); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_FAILED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); } if (canSplit && (++count > 100 || std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0).count() > 100)) { break; } } return count; } void MegaApiImpl::CompleteFileDownloadBySkip(MegaTransferPrivate* transfer, m_off_t size, uint64_t nodehandle, int nextTag, const LocalPath& localPath) { transfer->setState(MegaTransfer::STATE_QUEUED); transferMap[nextTag] = transfer; transfer->setTag(nextTag); transfer->setTotalBytes(size); transfer->setTransferredBytes(0); transfer->setLocalPath(localPath); transfer->setStartTime(Waiter::ds); transfer->setUpdateTime(Waiter::ds); fireOnTransferStart(transfer); transfer->setNodeHandle(nodehandle); transfer->setDeltaSize(size); transfer->setSpeed(0); transfer->setMeanSpeed(0); transfer->setState(MegaTransfer::STATE_COMPLETED); fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_OK)); } void MegaApiImpl::removeRecursively(const char *path) { #ifndef _WIN32 auto localpath = LocalPath::fromPlatformEncodedAbsolute(path); #ifndef __ANDROID__ FSACCESS_CLASS::emptydirlocal(localpath); #else if (auto fsa = createFSA(); dynamic_cast<AndroidFileSystemAccess*>(fsa.get())) AndroidFileSystemAccess::emptydirlocal(localpath); else LinuxFileSystemAccess::emptydirlocal(localpath); #endif #else auto localpath = LocalPath::fromAbsolutePath(path); WinFileSystemAccess::emptydirlocal(localpath); #endif } error MegaApiImpl::processAbortBackupRequest(MegaRequestPrivate *request) { int tag = int(request->getNumber()); map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(tag) ; if (itr != backupsMap.end()) { MegaScheduledCopyController *backup = itr->second; bool flag = request->getFlag(); if (!flag) { if (backup->getState() == MegaScheduledCopy::SCHEDULED_COPY_ONGOING) { for (std::map<int, MegaTransferPrivate *>::iterator it = transferMap.begin(); it != transferMap.end(); it++) { MegaTransferPrivate *t = it->second; if (t->getFolderTransferTag() == backup->getFolderTransferTag()) { api->cancelTransferByTag(t->getTag()); //what if any of these fail? (Although I don't think that's possible) } } request->setFlag(true); requestQueue.push(request); } else { LOG_debug << "Abort failed: no ongoing backup"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); } } else { backup->abortCurrent(); //TODO: THIS MAY CAUSE NEW REQUESTS, should we consider them before fireOnRequestFinish?!!! fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return API_OK; } else { return API_ENOENT; } } void MegaApiImpl::yield() { #if __cplusplus >= 201100L std::this_thread::yield(); #elif !defined(_WIN32) sched_yield(); #endif } static void appendFileAttribute(string& s, int n, MegaHandle h) { if (h != INVALID_HANDLE) { if (!s.empty()) { s += "/"; } char buf[64]; snprintf(buf, sizeof(buf), "%u*", n); Base64::btoa((byte*)&h, sizeof(h), strchr(buf + 2, 0)); s += buf; } } static error updateAttributesMapWithCoordinates(attr_map& attrUpdates, int latitude, int longitude, bool unshareable, MegaClient* client) { static const nameid coordsNameShareable = AttrMap::string2nameid("l"); static const nameid coordsNameUnshareable = AttrMap::string2nameid("gp"); if (longitude == MegaNode::INVALID_COORDINATE && latitude == MegaNode::INVALID_COORDINATE) { attrUpdates[coordsNameShareable] = ""; attrUpdates[coordsNameUnshareable] = ""; } else { if (longitude < 0 || longitude >= 0x01000000 || latitude < 0 || latitude >= 0x01000000) { return API_EARGS; } Base64Str<3> latEncoded((const byte*)&latitude); Base64Str<3> lonEncoded((const byte*)&longitude); string coordsValue = string(latEncoded) + lonEncoded.chars; if (coordsValue.size() != 8) { return API_EARGS; } if (unshareable) { if (client->unshareablekey.size() != Base64Str<SymmCipher::KEYLENGTH>::STRLEN) { return API_EKEY; } SymmCipher c; byte data[SymmCipher::BLOCKSIZE] = { 0 }; memcpy(data, "unshare/", 8); memcpy(data + 8, (void*)coordsValue.data(), coordsValue.size()); client->setkey(&c, client->unshareablekey.data()); c.ctr_crypt(data, unsigned(8 + coordsValue.size()), 0, 0, NULL, true); attrUpdates[coordsNameUnshareable] = Base64Str<SymmCipher::BLOCKSIZE>(data); attrUpdates[coordsNameShareable] = ""; } else { attrUpdates[coordsNameShareable] = coordsValue; attrUpdates[coordsNameUnshareable] = ""; } } return API_OK; } void MegaApiImpl::sendPendingScRequest() { MegaRequestPrivate *request = scRequestQueue.front(); if (!request || request->getTag()) { return; } assert(request->getType() == MegaRequest::TYPE_CATCHUP); SdkMutexGuard g(sdkMutex);; request->setTag(1); fireOnRequestStart(request); client->catchup(); } void MegaApiImpl::sendPendingRequests() { SdkMutexGuard g(sdkMutex); // For multiple consecutive requests of the same type (eg. remove transfer) this committer will put all the database activity into a single commit TransferDbCommitter committer(client->tctable); int lastRequestType = -1; int lastRequestConsecutive = 0; error e = API_OK; MegaRequestPrivate *request = nullptr; bool abortBackoffTimer{true}; constexpr dstime abortBackoffCoolDown{50}; // in deciseconds, 5 seconds while(1) { // Report the error from the last loop iteration (if any). (success must be reported directly by each case) // Having this code here is good for in-place conversions of case statements // to the `performRequest` system and reduce the switch size // And also shares this block of code between the switch and performRequest cases. if (e && request) { LOG_err << "Error starting request: " << e; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } e = API_OK; request = requestQueue.pop(); if (!request) { break; } if (request->performFireOnRequestFinish) { request->performFireOnRequestFinish(); request = nullptr; continue; } // also we avoid yielding for consecutive transaction cancel operations (we used to yeild every time, but we need to keep the sdkMutex lock while the database transaction is ongoing) if ((lastRequestType == -1 || lastRequestType == request->getType()) && lastRequestConsecutive < 1024) { ++lastRequestConsecutive; } else { committer.commitNow(); // Scoped retaining the request back into the requestQueue, allowing its listener // to be removed if removeListener is called in the scope by any chance const auto scopedRetaining = requestQueue.scopedRetainingRequest(request); g.unlock(); yield(); g.lock(); lastRequestConsecutive = 0; } lastRequestType = request->getType(); // Abort backoff timers if there are requests in the queue, but only once. // Do not abort backoff timers for logout or MegaRequest which will end in a lockless // request. if (abortBackoffTimer && request->getType() != MegaRequest::TYPE_LOGOUT && !MegaRequestPrivate::causesLocklessRequest(request->getType())) { // Calculate the cool down before triggering a new abortbackoff if (Waiter::ds - latestAbortBackoffs > abortBackoffCoolDown) { client->abortbackoff(false); latestAbortBackoffs = Waiter::ds; } abortBackoffTimer = false; } if (request->getType() != MegaRequest::TYPE_EXECUTE_ON_THREAD) { if (!request->getTag()) { int nextTag = client->nextreqtag(); request->setTag(nextTag); requestMap[nextTag] = request; fireOnRequestStart(request); } else { // this case happens when we queue requests already started } } if (request->performRequest) { // the action should result in request destruction via fireOnRequestFinish // or a requeue of another step, etc. e = request->performRequest(); continue; } if (request->performTransferRequest) { // the action has same requirement as the above performRequest e = request->performTransferRequest(committer); continue; } switch (request->getType()) { default: { // Default case if not performRequest and not implemented below. // Keeping this at the top means we can convert the last case // to a performRequest while keeping the code in-place for diffs e = API_EINTERNAL; break; } #ifdef ENABLE_SYNC case MegaRequest::TYPE_REMOVE_SYNCS: { assert(false); // this function deprecated, it wasn't used (and, questions about which error to report if multiple occur) e = API_EARGS; break; } #endif case MegaRequest::TYPE_DELETE: { #ifdef HAVE_LIBUV g.unlock(); httpServerStop(); ftpServerStop(); g.lock(); #endif abortPendingActions(); threadExit = 1; break; } case MegaRequest::TYPE_EXECUTE_ON_THREAD: { request->functionToExecute->exec(); //requestMap.erase(request->getTag()); // per the test for TYPE_EXECUTE_ON_THREAD above, we didn't add it to the map or assign it a tag delete request; request = nullptr; break; } } } } void MegaApiImpl::putSet(MegaHandle sid, int optionFlags, const char* name, MegaHandle cover, int type, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET, listener); request->setParentHandle(sid); request->setParamType(optionFlags); request->setText(name); request->setNodeHandle(cover); request->setAccess(type); request->performRequest = [this, request]() { Set s; s.setId(request->getParentHandle()); if (request->getParamType() & MegaApi::OPTION_SET_NAME) { s.setName(request->getText() ? request->getText() : string()); } if (request->getParamType() & MegaApi::OPTION_SET_COVER) { s.setCover(request->getNodeHandle()); } if (request->getParamType() & MegaApi::CREATE_SET) { const int t = request->getAccess(); const int max = static_cast<int>(std::numeric_limits<uint8_t>::max()); const int min = static_cast<int>(std::numeric_limits<uint8_t>::min()); if (t > max || t < min) { LOG_err << "Sets: type requested " << t << " is out of valid range [" << min << "," << max << "]"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return API_OK; } s.setType(static_cast<Set::SetType>(t)); } client->putSet(std::move(s), [this, request](Error e, const Set* s) { if (request->getParentHandle() == UNDEF && s) { request->setMegaSet(std::make_unique<MegaSetPrivate>(*s)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeSet(MegaHandle sid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET, listener); request->setParentHandle(sid); request->performRequest = [this, request]() { client->removeSet(request->getParentHandle(), [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::putSetElement(MegaHandle sid, MegaHandle eid, MegaHandle node, int optionFlags, int64_t order, const char* name, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET_ELEMENT, listener); request->setTotalBytes(static_cast<long long>(sid)); request->setParentHandle(eid); request->setNodeHandle(node); request->setParamType(optionFlags); request->setNumber(order); request->setText(name); request->performRequest = [this, request]() { SetElement el; el.setSet(static_cast<handle>(request->getTotalBytes())); el.setId(request->getParentHandle()); el.setNode(request->getNodeHandle()); if (request->getParamType() & MegaApi::OPTION_ELEMENT_ORDER) { el.setOrder(request->getNumber()); } if (request->getParamType() & MegaApi::OPTION_ELEMENT_NAME) { el.setName(request->getText() ? request->getText() : string()); } client->putSetElement(std::move(el), [this, request](Error e, const SetElement* el) { if (e == API_OK) // only return SetElement upon create, not update { bool isNew = request->getParentHandle() == UNDEF; assert(!isNew || el); if (isNew && el) // return the Element only when is created, not when updated { request->setMegaSetElementList(std::make_unique<MegaSetElementListPrivate>(&el, 1)); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeSetElement(MegaHandle sid, MegaHandle eid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET_ELEMENT, listener); request->setTotalBytes(static_cast<long long>(sid)); request->setParentHandle(eid); request->performRequest = [this, request]() { client->removeSetElement(static_cast<handle>(request->getTotalBytes()), request->getParentHandle(), [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_login(MegaRequestPrivate* request) { const char *login = request->getEmail(); const char *password = request->getPassword(); const char* megaFolderLink = request->getLink(); const char* sessionKey = request->getSessionKey(); const bool tryToResumeFolderLinkFromCache{request->getFlag()}; const auto cancelSnapshot{request->getTransferredBytes()}; if (!megaFolderLink && (!(login && password)) && !sessionKey) { return API_EARGS; } string slogin; if (login) { slogin = login; slogin.erase(slogin.begin(), std::find_if(slogin.begin(), slogin.end(), char_is_not_space)); slogin.erase(std::find_if(slogin.rbegin(), slogin.rend(), char_is_not_space).base(), slogin.end()); } requestMap.erase(request->getTag()); abortPendingActions(); requestMap[request->getTag()]=request; error e = API_OK; client->locallogout(false, true); client->mLoginCancelSnapshot = static_cast<cancel_epoch_t>(cancelSnapshot); if (ScopedCanceller(client->mLoginCancelSnapshot).triggered()) { // Should we abort at this point? LOG_warn << "[performRequest_login] A MegaApi locallogout triggered the cancel " "epoch, which will affect this request"; } if (sessionKey) { client->login(Base64::atob(string(sessionKey))); } else if (login && password && !megaFolderLink) { client->prelogin(slogin.c_str()); } else { e = client->folderaccess(megaFolderLink, password, tryToResumeFolderLinkFromCache); if(e == API_OK) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } } return e; } error MegaApiImpl::performRequest_tagNode(MegaRequestPrivate* request) { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); int operation = request->getParamType(); if (!node) { return API_EARGS; } if (!request->getText()) { return API_EARGS; } std::string tag = request->getText(); if (tag.find(MegaClient::TAG_DELIMITER) != std::string::npos) { return API_EARGS; } switch (operation) { case MegaApi::TAG_NODE_SET: { return client->addTagToNode(node, tag, [this, request](NodeHandle, Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } case MegaApi::TAG_NODE_REMOVE: { return client->removeTagFromNode( node, tag, [this, request](NodeHandle, Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } case MegaApi::TAG_NODE_UPDATE: { if (!request->getName()) { return API_EARGS; } std::string oldTag = request->getName(); return client->updateTagNode( node, tag, oldTag, [this, request](NodeHandle, Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } } return API_EARGS; } void MegaApiImpl::CRUDNodeTagOperation(MegaNode* node, int operationType, const char* tag, const char* oldTag, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_TAG_NODE, listener); if (node) { request->setNodeHandle(node->getHandle()); } request->setParamType(operationType); request->setText(tag); request->setName(oldTag); request->performRequest = [this, request]() { return performRequest_tagNode(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthCheck(const char* email, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_CHECK, listener); request->setEmail(email); request->performRequest = [this, request]() { const char *email = request->getEmail(); if (!email) { return API_EARGS; } client->multifactorauthcheck(email); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthGetCode(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_GET, listener); request->performRequest = [this]() { client->multifactorauthsetup(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::multiFactorAuthEnableOrDisable(const char* pin, bool enable, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MULTI_FACTOR_AUTH_SET, listener); request->setFlag(enable); request->setPassword(pin); request->performRequest = [this, request]() { bool flag = request->getFlag(); const char *pin = request->getPassword(); if (!pin) { return API_EARGS; } if (flag) { client->multifactorauthsetup(pin); } else { client->multifactorauthdisable(pin); } return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fetchTimeZone(bool forceApiFetch, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_TIMEZONE, listener); request->setFlag(forceApiFetch); request->performRequest = [this, request]() { if (!mTimezones || request->getFlag() /*forceApiFetch*/) { client->fetchtimezone(); } else { request->setTimeZoneDetails(mTimezones); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::checkCreateFolderPrecons(const char* name, std::shared_ptr<Node> parent, MegaRequestPrivate* request) { if (!name || !(*name) || !parent) return API_EARGS; // prevent to create a duplicate folder with same name in same path std::shared_ptr<Node> folder = client->childnodebyname(parent.get(), name, false); if (folder && folder->type == FOLDERNODE) { request->setNodeHandle(folder->nodehandle); return API_EEXIST; } return API_OK; } void MegaApiImpl::sendUserfeedback(const int rating, const char* comment, const bool transferFeedback, const int transferType) { auto sendFeedback = [this](const int rating, const std::string& base64comment, const bool transferFeedback, const direction_t transferType) { std::ostringstream feedback; if (transferFeedback) { std::string ts = client->mTransferStatsManager.metricsToJsonForTransferType(transferType); feedback << R"({\"r\":\")" << rating << R"(\",\"m\":\")" << base64comment << R"(\",\"u\":\")" << toHandle(client->me) << R"(\",)" << ts << "}"; } else { feedback << R"({\"r\":\")" << rating << R"(\",\"m\":\")" << base64comment << R"(\",\"u\":\")" << toHandle(client->me) << R"(\"})"; } client->userfeedbackstore(feedback.str().c_str()); }; std::string base64comment{}; if (comment) { base64comment = Base64::btoa(comment); } if (transferFeedback) { if (transferType == MegaApi::TRANSFER_STATS_DOWNLOAD || transferType == MegaApi::TRANSFER_STATS_BOTH) { sendFeedback(rating, base64comment, transferFeedback, GET); } if (transferType == MegaApi::TRANSFER_STATS_UPLOAD || transferType == MegaApi::TRANSFER_STATS_BOTH) { sendFeedback(rating, base64comment, transferFeedback, PUT); } } else { sendFeedback(rating, base64comment, transferFeedback, NONE); } } void MegaApiImpl::createFolder(const char* name, MegaNode* parent, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_FOLDER, listener); if (parent) request->setParentHandle(parent->getHandle()); request->setName(name); request->performRequest = [this, request]() { std::shared_ptr<Node> parent = client->nodebyhandle(request->getParentHandle()); auto name = request->getName(); if (const auto error = checkCreateFolderPrecons(name, parent, request); error != API_OK) return error; return client->createFolder(parent, name, request->getTag()); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::moveNode(MegaNode* node, MegaNode* newParent, const char* newName, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_MOVE, listener); if (node) request->setNodeHandle(node->getHandle()); if (newParent) request->setParentHandle(newParent->getHandle()); request->setName(newName); request->performRequest = [this, request]() { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); std::shared_ptr<Node> newParent = client->nodebyhandle(request->getParentHandle()); const char *name = request->getName(); if (!node || !newParent) { return API_EARGS; } if (node->parent == newParent) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } if (node->type == ROOTNODE || node->type == VAULTNODE || node->type == RUBBISHNODE || !node->parent) // rootnodes cannot be moved { return API_EACCESS; } // old versions cannot be moved if (node->parent->type == FILENODE) { return API_EACCESS; } // target must be a folder with enough permissions if (newParent->type == FILENODE || !client->checkaccess(newParent.get(), RDWR)) { return API_EACCESS; } error e = API_OK; e = client->checkmove(node.get(), newParent.get()); if (e) { // If it's not possible to move the node, try copy-delete, // but only when it's not possible due to access rights // the node and the target are from different node trees, // it's possible to put nodes in the target folder // and also to remove the source node if (e != API_EACCESS) { return e; } Node *nodeRoot = node.get(); while (nodeRoot->parent) { nodeRoot = nodeRoot->parent.get(); } Node *parentRoot = newParent.get(); while (parentRoot->parent) { parentRoot = parentRoot->parent.get(); } if ((nodeRoot == parentRoot) || !client->checkaccess(node.get(), FULL) || !client->checkaccess(newParent.get(), RDWR)) { return e; } unsigned nc; TreeProcCopy tc; const bool fullInternalOperation = node && newParent && node->owner == client->me && client->me == newParent->owner; tc.resetSensitive = !fullInternalOperation; NodeHandle ovhandle; if (node->type == FILENODE) { string newName; if (name) { newName.assign(name); LocalPath::utf8_normalize(&newName); } else { attr_map::iterator it = node->attrs.map.find('n'); if (it != node->attrs.map.end()) { newName = it->second; } } if (!newName.empty()) { shared_ptr<Node> ovn = client->childnodebyname(newParent.get(), newName.c_str(), true); if (ovn) { if (node->isvalid && ovn->isvalid && *(FileFingerprint*)node.get() == *(FileFingerprint*)ovn.get()) { request->setNodeHandle(UNDEF); e = client->unlink(node.get(), false, request->getTag(), false); return e; // request finishes now if error, otherwise on unlink_result } ovhandle = ovn->nodeHandle(); } } } // determine number of nodes to be copied client->proctree(node, &tc, !ovhandle.isUndef()); tc.allocnodes(); nc = tc.nc; // build new nodes array client->proctree(node, &tc, !ovhandle.isUndef()); if (!nc) { e = API_EARGS; return e; } tc.nn[0].parenthandle = UNDEF; tc.nn[0].ovhandle = ovhandle; if (name) // move and rename { string newName(name); LocalPath::utf8_normalize(&newName); AttrMap attrs = node->attrs; attrs.map['n'] = newName; // We need to ensure we are not undoing the sensitive reset if (tc.resetSensitive && attrs.map.erase(AttrMap::string2nameid("sen"))) { LOG_debug << "Removing sen attribute"; } string attrstring; attrs.getjson(&attrstring); SymmCipher key; key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type); client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); } // Mark node to be restored if moving to Rubbish Bin if (client->getrootnode(newParent)->type == RUBBISHNODE && client->getrootnode(node)->type != RUBBISHNODE) { // "rr" attribute name and value nameid rrname = AttrMap::string2nameid("rr"); Base64Str<MegaClient::NODEHANDLE> rrvalue(node->parent->nodehandle); // Add attribute to a copy of old attributes AttrMap attrs = node->attrs; attrs.map[rrname] = rrvalue; // Again, need to ensure we are not undoing the sensitive reset if (tc.resetSensitive && attrs.map.erase(AttrMap::string2nameid("sen"))) { LOG_debug << "Removing sen attribute when moving node to trash"; } // Magic incantations for setting attributes string attrstring; attrs.getjson(&attrstring); SymmCipher key; key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type); client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); } client->putnodes(newParent->nodeHandle(), UseLocalVersioningFlag, std::move(tc.nn), nullptr, request->getTag(), false); e = API_OK; return e; } e = client->rename(node, newParent, SYNCDEL_NONE, NodeHandle(), name, false, [request, this](NodeHandle h, Error e) { request->setNodeHandle(h.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return e; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_copy(MegaRequestPrivate* request) { std::shared_ptr<Node> node; std::shared_ptr<Node> target = client->nodebyhandle(request->getParentHandle()); const char* email = request->getEmail(); MegaNode *megaNode = request->getPublicNode(); const char *newName = request->getName(); NodeHandle ovhandle; if (!megaNode || (!target && !email) || (newName && !(*newName)) || (target && target->type == FILENODE)) { return API_EARGS; } if (!megaNode->isForeign() && !megaNode->isPublic()) { node = client->nodebyhandle(request->getNodeHandle()); if (!node) { return API_ENOENT; } } const bool targetIsIncomingShare = target && target->matchesOrHasAncestorMatching( [](const Node& n) { return n.inshare != nullptr; }); Pitag pitag; pitag.target = targetIsIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; if (!node) { if (!megaNode->getNodeKey()->size()) { return API_EKEY; } MegaNodePrivate* privateNode = dynamic_cast<MegaNodePrivate*>(megaNode); assert(privateNode); string sname = megaNode->getName(); if (newName) { if (privateNode) { sname = newName; LocalPath::utf8_normalize(&sname); privateNode->setName(sname.c_str()); } else { LOG_err << "Unknown node type"; } } if (target && megaNode->getType() == MegaNode::TYPE_FILE) { std::shared_ptr<Node> ovn = client->childnodebyname(target.get(), sname.c_str(), true); if (ovn) { FileFingerprint *fp = getFileFingerprintInternal(megaNode->getFingerprint()); if (fp) { if (fp->isvalid && ovn->isvalid && *fp == *(FileFingerprint*)ovn.get()) { request->setNodeHandle(ovn->nodehandle); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); delete fp; return API_OK; } delete fp; } ovhandle = ovn->nodeHandle(); } } MegaTreeProcCopy tc(client); processMegaTree(megaNode, &tc); tc.allocnodes(); // build new nodes array processMegaTree(megaNode, &tc); tc.nn[0].parenthandle = UNDEF; tc.nn[0].ovhandle = ovhandle; pitag.purpose = PitagPurpose::Import; pitag.nodeType = megaNode->getType() == MegaNode::TYPE_FILE ? PitagNodeType::File : PitagNodeType::Folder; pitag.importSource = megaNode->getType() == MegaNode::TYPE_FILE ? PitagImportSource::FileLink : PitagImportSource::FolderLink; if (target) { client->putnodes(target->nodeHandle(), UseLocalVersioningFlag, std::move(tc.nn), privateNode ? privateNode->getChatAuth() : nullptr, request->getTag(), false, {}, nullptr, pitag); } else { client->putnodes(email, std::move(tc.nn), request->getTag()); } } else { pitag.purpose = PitagPurpose::Copy; pitag.nodeType = node->type == FOLDERNODE ? PitagNodeType::Folder : PitagNodeType::File; pitag.importSource = node->inshare ? PitagImportSource::IncomingShare : PitagImportSource::CloudDrive; vector<NewNode> nn; error err = copyTreeFromOwnedNode(node, newName, target, nn); if (err != API_OK) { if (err == API_EEXIST) // dedicated error code when that exact same file already existed { assert(!nn.empty()); // never empty because other error should have been reported in that case request->setNodeHandle(nn[0].ovhandle.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } return err; } assert(!nn.empty()); // never empty because an error should have been reported in that case nn[0].parenthandle = UNDEF; if (target) { client->putnodes(target->nodeHandle(), UseLocalVersioningFlag, std::move(nn), nullptr, request->getTag(), false, {}, nullptr, pitag); } else { client->putnodes(email, std::move(nn), request->getTag()); } } return API_OK; } error MegaApiImpl::copyTreeFromOwnedNode(shared_ptr<Node> node, const char* newName, shared_ptr<Node> target, vector<NewNode>& treeCopy, const std::optional<std::string>& s4AttributeValue) { assert(node); assert(!newName || *newName); if (!node->nodekey().size()) { LOG_err << "Failed to copy owned node: Node had no key"; return API_EKEY; } if (node->attrstring) { node->applykey(); node->setattr(); if (node->attrstring) { LOG_err << "Failed to copy owned node: Node had bad key"; return API_EKEY; } } // process new name string sname; if (newName) { sname = newName; LocalPath::utf8_normalize(&sname); } else { attr_map::iterator it = node->attrs.map.find('n'); if (it != node->attrs.map.end()) { sname = it->second; } } // determine handling of older versions NodeHandle ovhandle; bool fileAlreadyExisted = false; if (std::shared_ptr<Node> ovn = (node->type == FILENODE) ? client->getovnode(target.get(), &sname) : nullptr) { ovhandle = ovn->nodeHandle(); fileAlreadyExisted = node->isvalid && ovn->isvalid && node->EqualExceptValidFlag(*ovn); } // determine number of nodes to be copied TreeProcCopy tc; const bool fullInternalOperation = node && target && node->owner == client->me && client->me == target->owner; tc.resetSensitive = !fullInternalOperation; client->proctree(node, &tc, false, !ovhandle.isUndef()); tc.allocnodes(); // If the sensitivity was reset, the file didn't exist fileAlreadyExisted = fileAlreadyExisted && !(tc.resetSensitive && node->isMarkedSensitive()); // build new nodes array client->proctree(node, &tc, false, !ovhandle.isUndef()); if (tc.nn.empty()) { LOG_err << "Failed to copy owned node: Failed to find nodes"; return API_EARGS; } tc.nn[0].parenthandle = UNDEF; tc.nn[0].ovhandle = ovhandle; // Update name or S4 attr if (newName || s4AttributeValue) { SymmCipher key; AttrMap attrs; string attrstring; key.setkey((const byte*)tc.nn[0].nodekey.data(), node->type); attrs = node->attrs; if (newName) { attrs.map['n'] = sname; } if (s4AttributeValue) { const auto s4AttributeKey{AttrMap::string2nameid("s4")}; if (const auto it{node->attrs.map.find(s4AttributeKey)}; (it == node->attrs.map.end() && !s4AttributeValue->empty()) || (it != node->attrs.map.end() && it->second != s4AttributeValue.value())) { if (fileAlreadyExisted) { LOG_debug << "The s4 attribute has changed, so the file is no longer the same"; fileAlreadyExisted = false; } attrs.map[s4AttributeKey] = s4AttributeValue.value().c_str(); } } // We need to ensure we are not undoing the sensitive reset if (tc.resetSensitive && attrs.map.erase(AttrMap::string2nameid("sen"))) { LOG_debug << "Removing sen attribute"; } attrs.getjson(&attrstring); client->makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); } treeCopy = std::move(tc.nn); // If the exact same file was already there do not send the request. // Let the caller know by returning a dedicated error code, so that they // can continue as if API_OK was received from the cloud API. return fileAlreadyExisted ? API_EEXIST : API_OK; } void MegaApiImpl::restoreVersion(MegaNode* version, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESTORE, listener); if (version) { request->setNodeHandle(version->getHandle()); } request->performRequest = [this, request]() { std::shared_ptr<Node> version = client->nodebyhandle(request->getNodeHandle()); if (!version) { return API_ENOENT; } if (version->type != FILENODE || !version->parent || version->parent->type != FILENODE) { return API_EARGS; } Node *current = version.get(); while (current->parent && current->parent->type == FILENODE) { current = current->parent.get(); } if (!current->parent) { return API_EINTERNAL; } vector<NewNode> newnodes(1); NewNode* newnode = &newnodes[0]; string attrstring; SymmCipher key; newnode->source = NEW_NODE; newnode->type = FILENODE; newnode->nodehandle = version->nodehandle; newnode->parenthandle = UNDEF; newnode->ovhandle = current->nodeHandle(); newnode->nodekey = version->nodekey(); newnode->attrstring.reset(new string); if (newnode->nodekey.size()) { key.setkey((const byte*)version->nodekey().data(), version->type); static constexpr std::array attributesToKeep{MegaClient::NODE_ATTRIBUTE_DESCRIPTION, MegaClient::NODE_ATTRIBUTE_TAGS}; std::for_each(std::begin(attributesToKeep), std::end(attributesToKeep), [&versionMap = version->attrs.map, ¤tMap = current->attrs.map](auto&& attribute) { const auto attrId = AttrMap::string2nameid(attribute); versionMap[attrId] = currentMap[attrId]; }); version->attrs.getjson(&attrstring); client->makeattr(&key, newnode->attrstring, attrstring.c_str()); } client->putnodes(current->parent->nodeHandle(), ClaimOldVersion, std::move(newnodes), nullptr, request->getTag(), false); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::renameNode(MegaNode* node, const char* newName, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RENAME, listener); if (node) request->setNodeHandle(node->getHandle()); request->setName(newName); request->performRequest = [this, request]() -> error { const auto nh = NodeHandle{}.set6byte(request->getNodeHandle()); CommandSetAttr::Completion cb = [request, this](NodeHandle h, Error e) { assert(request->getNodeHandle() == h.as8byte()); request->setNodeHandle(h.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; return client->renameNode(nh, request->getName(), std::move(cb)); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::remove(MegaNode* node, bool keepversions, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE, listener); if (node) request->setNodeHandle(node->getHandle()); request->setFlag(keepversions); request->performRequest = [this, request]() -> error { const auto nh = NodeHandle{}.set6byte(request->getNodeHandle()); return client->removeNode(nh, request->getFlag(), request->getTag()); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeVersions(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_VERSIONS, listener); request->performRequest = [this]() { client->unlinkversions(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::share(MegaNode* node, const char* email, int access, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SHARE, listener); if (node) request->setNodeHandle(node->getHandle()); request->setEmail(email); request->setAccess(access); request->performRequest = [this, request]() { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); const char* email = request->getEmail(); int access = request->getAccess(); if(!node || !email || !strchr(email, '@')) { return API_EARGS; } error e = API_OK; accesslevel_t a = ACCESS_UNKNOWN; switch(access) { case MegaShare::ACCESS_UNKNOWN: a = ACCESS_UNKNOWN; break; case MegaShare::ACCESS_READ: a = RDONLY; break; case MegaShare::ACCESS_READWRITE: a = RDWR; break; case MegaShare::ACCESS_FULL: a = FULL; break; case MegaShare::ACCESS_OWNER: a = OWNER; break; default: e = API_EARGS; } if (e == API_OK) { client->setshare(node, email, a, false, nullptr, request->getTag(), [this, request](Error e, bool){ fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } return e; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_importLink_getPublicNode(MegaRequestPrivate* request) { std::shared_ptr<Node> node = client->nodebyhandle(request->getParentHandle()); const char* megaFileLink = request->getLink(); if (!megaFileLink) { return API_EARGS; } if ((request->getType() == MegaRequest::TYPE_IMPORT_LINK) && !node) { return API_EARGS; } handle ph = UNDEF; byte key[FILENODEKEYLENGTH]; error e = client->parsepubliclink(megaFileLink, ph, key, TypeOfLink::FILE); if (e == API_OK) { client->openfilelink(ph, key); } else if (e == API_EINCOMPLETE) // no key provided, check only the existence of the node { client->openfilelink(ph, nullptr); e = API_OK; } return e; } void MegaApiImpl::getDownloadUrl(MegaNode* node, bool singleUrl, bool forceSSL, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_DOWNLOAD_URLS, listener); request->setFlag(singleUrl); request->setAccess(forceSSL ? 1 : 0); if (node) request->setNodeHandle(node->getHandle()); request->performRequest = [this, request]() { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); if(!node) // works only for existing nodes, not the ones that need to be undeleted (see "gd" command) { return API_EARGS; } client->queueCommand(new CommandGetFile( client, (const byte*)node->nodekey().data(), node->nodekey().size(), false /*undelete*/, node->nodehandle, true, nullptr, nullptr, nullptr, request->getFlag() /*singleUrl*/, static_cast<bool>(request->getAccess()) /*forceSSL*/, [this, request, h = node->nodehandle](const Error& e, m_off_t /*size*/, dstime /*timeleft*/, std::string* /*filename*/, std::string* /*fingerprint*/, std::string* /*fileattrstring*/, const std::vector<std::string>& urls, const std::vector<std::string>& ips, const std::string& fileHandle) { if (e == API_OK && urls.size() && ips.size()) { string surls, sipsv4, sipsv6; auto delimFields=";"; for (auto &u : urls) { if (!surls.empty()) surls.append(delimFields); surls.append(u); } bool ipv4 = true; for (auto &ip : ips) { auto &w = ipv4 ? sipsv4 : sipsv6; if (!w.empty()) w.append(delimFields); w.append(ip); ipv4 = !ipv4; } request->setName(surls.c_str()); request->setLink(sipsv4.c_str()); request->setText(sipsv6.c_str()); request->setMegaStringMap({ {Base64Str<MegaClient::NODEHANDLE>(h).chars, fileHandle} }); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return true; })); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_passwordLink(MegaRequestPrivate* request) { const char *link = request->getLink(); const char *pwd = request->getPassword(); bool encryptLink = request->getFlag(); error e = API_OK; string result; if (encryptLink) { e = client->encryptlink(link, pwd, &result); } else { e = client->decryptlink(link, pwd, &result); } if (!e) { request->setText(result.c_str()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } return e; } error MegaApiImpl::performRequest_export(MegaRequestPrivate* request) { std::shared_ptr<Node> node = client->nodebyhandle(request->getNodeHandle()); if (!node) { return API_EARGS; } bool writable = request->getFlag(); bool megaHosted = request->getTransferTag() != 0; // exportnode() will take care of creating a share first, should it be a folder return client->exportnode( node, !request->getAccess(), request->getNumber(), writable, megaHosted, request->getTag(), [this, request, megaHosted](Error e, handle h, handle ph, string&& encryptionKey) { if (e || !request->getAccess()) // disable export doesn't return h and ph { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } else { // This block of code used to be inside exportnode_result if (std::shared_ptr<Node> n = client->nodebyhandle(h)) { char key[FILENODEKEYLENGTH * 4 / 3 + 3]; // the key if (n->type == FILENODE) { if (n->nodekey().size() >= FILENODEKEYLENGTH) { Base64::btoa((const byte*)n->nodekey().data(), FILENODEKEYLENGTH, key); } else { key[0] = 0; } } else if (n->sharekey) { Base64::btoa(n->sharekey->key, FOLDERNODEKEYLENGTH, key); } else { fireOnRequestFinish( request, std::make_unique<MegaErrorPrivate>(MegaError::API_EKEY)); return; } TypeOfLink lType = client->validTypeForPublicURL(n->type); string link = client->publicLinkURL(client->mNewLinkFormat, lType, ph, key); request->setLink(link.c_str()); if (n->plink && n->plink->mAuthKey.size()) { request->setPrivateKey(n->plink->mAuthKey.c_str()); if (megaHosted) { request->setPassword(encryptionKey.c_str()); } } fireOnRequestFinish( request, std::make_unique<MegaErrorPrivate>(MegaError::API_OK)); } else { request->setNodeHandle(UNDEF); fireOnRequestFinish( request, std::make_unique<MegaErrorPrivate>(MegaError::API_ENOENT)); } } }); } void MegaApiImpl::fetchNodes(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_NODES, listener); request->performRequest = [this]() { bool forceLoadFromServers = nocache; nocache = false; client->fetchnodes(client->fetchnodesAlreadyCompletedThisSession, !client->syncsAlreadyLoadedOnStatecurrent, forceLoadFromServers); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getCloudStorageUsed(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_CLOUD_STORAGE_USED, listener); request->performRequest = [this, request]() { if (client->loggedin() != FULLACCOUNT && !client->loggedIntoFolder()) { return API_EACCESS; } NodeCounter nc = client->mNodeManager.getCounterOfRootNodes(); request->setNumber(nc.storage + nc.versionStorage); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getAccountDetails(bool storage, bool transfer, bool pro, bool sessions, bool purchases, bool transactions, int source, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ACCOUNT_DETAILS, listener); int numDetails = 0; if (storage) numDetails |= 0x01; if (transfer) numDetails |= 0x02; if (pro) numDetails |= 0x04; if (transactions) numDetails |= 0x08; if (purchases) numDetails |= 0x10; if (sessions) numDetails |= 0x20; request->setNumDetails(numDetails); request->setAccess(source); request->performRequest = [this, request]() { if (client->loggedin() == NOTLOGGEDIN) { return API_EACCESS; } int numDetails = request->getNumDetails(); bool storage = (numDetails & 0x01) != 0; bool transfer = (numDetails & 0x02) != 0; bool pro = (numDetails & 0x04) != 0; bool transactions = (numDetails & 0x08) != 0; bool purchases = (numDetails & 0x10) != 0; bool sessions = (numDetails & 0x20) != 0; int numReqs = int(storage || transfer || pro) + int(transactions) + int(purchases) + int(sessions); if (numReqs == 0) { return API_EARGS; } request->setNumber(numReqs); client->getaccountdetails(request->getAccountDetails(), storage, transfer, pro, transactions, purchases, sessions, request->getAccess()); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::queryTransferQuota(long long size, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_TRANSFER_QUOTA, listener); request->setNumber(size); request->performRequest = [this, request]() { m_off_t size = request->getNumber(); client->querytransferquota(size); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_changePw(MegaRequestPrivate* request) { const char* oldPassword = request->getPassword(); const char* newPassword = request->getNewPassword(); const char* pin = request->getText(); if (!newPassword) { return API_EARGS; } if (oldPassword && !checkPassword(oldPassword)) { return API_EARGS; } return client->changepw(newPassword, pin); } error MegaApiImpl::performRequest_logout(MegaRequestPrivate* request) { if (request->getParamType() == API_ESSL && client->retryessl) { return API_EINCOMPLETE; } if (request->getFlag()) { bool keepSyncConfigsFile = request->getTransferTag() != 0; client->logout(keepSyncConfigsFile, [this, request](error result) { LOG_debug << "executing logout completion, error: " << result; // track possible lack of logout callbacks logout_result(result, request); }); } else { client->locallogout(false, true); client->restag = request->getTag(); logout_result(API_OK, request); } return API_OK; } void MegaApiImpl::getNodeAttribute(std::variant<MegaNode*, MegaHandle> nodeOrHandle, int type, const char* dstFilePath, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_FILE, listener); if (dstFilePath) { string path(dstFilePath); int c = path[path.size() - 1]; if ((c == '/') || (c == '\\')) { const char* base64Handle = std::visit( [](auto&& arg) -> const char* { using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, MegaNode*>) { return arg ? arg->getBase64Handle() : nullptr; } else { return handleToBase64(arg); } }, nodeOrHandle); if (base64Handle) { path.append(base64Handle); path.push_back(static_cast<char>('0' + type)); path.append(".jpg"); delete[] base64Handle; } } request->setFile(path.c_str()); } request->setParamType(type); if (std::holds_alternative<MegaNode*>(nodeOrHandle)) { MegaNode* node = std::get<MegaNode*>(nodeOrHandle); if (node) { request->setNodeHandle(node->getHandle()); const char* fileAttributes = node->getFileAttrString(); if (fileAttributes) { request->setText(fileAttributes); const char* nodekey = node->getBase64Key(); request->setPrivateKey(nodekey); delete[] nodekey; delete[] fileAttributes; } } } else { MegaHandle handle = std::get<MegaHandle>(nodeOrHandle); request->setNodeHandle(handle); } request->performRequest = [this, request]() { const char* dstFilePath = request->getFile(); int type = request->getParamType(); handle h = request->getNodeHandle(); const char* fa = request->getText(); const char* base64key = request->getPrivateKey(); std::shared_ptr<Node> node = client->nodebyhandle(h); if (!dstFilePath || (!fa && !node) || (fa && (!base64key || ISUNDEF(h)))) { return API_EARGS; } string fileattrstring; string key; if (!fa) { fileattrstring = node->fileattrstring; key = node->nodekey(); } else { fileattrstring = fa; byte nodekey[FILENODEKEYLENGTH]; if (Base64::atob(base64key, nodekey, sizeof nodekey) != sizeof nodekey) { return API_EKEY; } key.assign((const char*)nodekey, sizeof nodekey); } error e = client->getfa(h, &fileattrstring, key, (fatype)type); if (e == API_EEXIST) { e = API_OK; int prevtag = client->restag; MegaRequestPrivate* req = NULL; while (prevtag) { if (requestMap.find(prevtag) == requestMap.end()) { LOG_err << "Invalid duplicate getattr request"; req = NULL; e = API_EINTERNAL; break; } req = requestMap.at(prevtag); if (!req || (req->getType() != MegaRequest::TYPE_GET_ATTR_FILE)) { LOG_err << "Invalid duplicate getattr type"; req = NULL; e = API_EINTERNAL; break; } prevtag = int(req->getNumber()); } if (req) { LOG_debug << "Duplicate getattr detected"; req->setNumber(request->getTag()); } } return e; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getAttrUser(MegaRequestPrivate* request) { attr_t type = static_cast<attr_t>(request->getParamType()); if (type == ATTR_UNKNOWN) { return API_EARGS; } const char* value = request->getFile(); const char *email = request->getEmail(); const char *ph = request->getSessionKey(); string attrname = MegaApiImpl::userAttributeToString(type); char scope = MegaApiImpl::userAttributeToScope(type); if ((!client->loggedin() && ph == NULL) || (ph && !ph[0])) { return API_EARGS; } User *user = email ? client->finduser(email, 0) : client->finduser(client->me, 0); if (!user) // email/handle not found among (ex)contacts { if (scope != ATTR_SCOPE_PUBLIC_UNENCRYPTED && scope != ATTR_SCOPE_PROTECTED_UNENCRYPTED) { LOG_warn << "Cannot retrieve private/protected attributes from users other than yourself."; return API_EACCESS; } getUserAttr(email, type, ph, request); return API_OK; } bool externalDriveRequest = type == ATTR_DEVICE_NAMES && request->getFlag(); if (attrname.empty() // unknown attribute type || (type == ATTR_AVATAR && !value) // no destination file for avatar || (externalDriveRequest && !value)) // no drive path to look for drive-id { return API_EARGS; } // if attribute is private and user is not logged in user... if (scope == ATTR_SCOPE_PRIVATE_ENCRYPTED && user->userhandle != client->me) { return API_EACCESS; } if (externalDriveRequest) { // check if drive-id already exists handle driveId; error e = readDriveId(*client->fsaccess, value, driveId); if (e != API_OK) { return e; } request->setNodeHandle(driveId); } getUserAttr(user, type, request); return API_OK; } error MegaApiImpl::performRequest_setAttrUser(MegaRequestPrivate* request) { attr_t type = static_cast<attr_t>(request->getParamType()); if (type == ATTR_UNKNOWN) { return API_EARGS; } const char* file = request->getFile(); const char* value = request->getText(); char scope = MegaApiImpl::userAttributeToScope(type); string attrname = MegaApiImpl::userAttributeToString(type); if (attrname.empty()) // unknown attribute type { return API_EARGS; } User *ownUser = client->finduser(client->me); if (!ownUser) { assert(false && "Setting attribute without having logged in"); return API_EACCESS; } std::function<void(Error)> putuaCompletion = [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; string attrvalue; if (type == ATTR_KEYRING || type == ATTR_KEYS || User::isAuthring(type) || type == ATTR_CU25519_PUBK || type == ATTR_ED25519_PUBK || type == ATTR_SIG_CU255_PUBK || type == ATTR_SIG_RSA_PUBK) { return API_EACCESS; } else if (type == ATTR_MY_BACKUPS_FOLDER) { LOG_err << "Cannot set 'My backups' folder attribute directly. Please, use MegaApi::setMyBackupsFolder"; return API_EACCESS; } else if (type == ATTR_AVATAR) { // read the attribute value from file if (file) { auto localpath = LocalPath::fromAbsolutePath(file); auto f = fsAccess->newfileaccess(); if (!f->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)) { return API_EREAD; } if (!f->fread(&attrvalue, unsigned(f->size), 0, 0, FSLogging::logOnError)) { return API_EREAD; } f.reset(); client->putua(type, (byte *)attrvalue.data(), unsigned(attrvalue.size()), -1, UNDEF, 0, 0, std::move(putuaCompletion)); return API_OK; } else // removing current attribute's value { client->putua(type, nullptr, 0, -1, UNDEF, 0, 0, std::move(putuaCompletion)); return API_OK; } } else if (scope == ATTR_SCOPE_PRIVATE_ENCRYPTED) { if (type == ATTR_DEVICE_NAMES && request->getFlag()) // external drive { const char *pathToDrive = request->getFile(); if (!pathToDrive) { return API_EARGS; } // check if the drive id is already created // read <pathToDrive>/.megabackup/drive-id handle driveId; error e = readDriveId(*client->fsaccess, pathToDrive, driveId); if (e == API_ENOENT) { // generate new id driveId = generateDriveId(client->rng); // write <pathToDrive>/.megabackup/drive-id e = writeDriveId(*client->fsaccess, pathToDrive, driveId); } if (e != API_OK) return e; MegaStringMapPrivate stringMap; const char *driveName = request->getName(); string buf = driveName ? driveName : ""; stringMap.set(Base64Str<MegaClient::DRIVEHANDLE>(driveId), Base64::btoa(buf).c_str()); request->setMegaStringMap(&stringMap); // makes a copy } MegaStringMap *stringMap = request->getMegaStringMap(); if (!stringMap) { return API_EARGS; } string_map destination; if (type == ATTR_ALIAS || type == ATTR_CAMERA_UPLOADS_FOLDER || type == ATTR_DEVICE_NAMES || type == ATTR_CC_PREFS || type == ATTR_APPS_PREFS) { const UserAttribute* attribute = ownUser->getAttribute(type); if (!attribute || !attribute->isValid()) // not fetched yet or outdated { // always get updated value before update it getUserAttr(ownUser, type, request); return API_OK; } else if (auto old = tlv::containerToRecords(attribute->value(), client->key)) { destination.swap(*old); } } const string_map *newValuesMap = static_cast<MegaStringMapPrivate*>(stringMap)->getMap(); unique_ptr<string_map> prefixedValueMap; if (type == ATTR_DEVICE_NAMES) { // allow only unique names for Devices and Drives if (haveDuplicatedValues(destination, *newValuesMap)) { LOG_err << "Attribute " << User::attr2string(type) << " attempted to add duplicated value (1): " << Base64::atob(newValuesMap->begin()->second); // will only have a single value return API_EEXIST; } if (request->getFlag()) // external drive { string prefix = User::attributePrefixInTLV(ATTR_DEVICE_NAMES, true); prefixedValueMap = std::make_unique<string_map>(); for_each(newValuesMap->begin(), newValuesMap->end(), [&prefixedValueMap, &prefix](const string_map::value_type& a) {prefixedValueMap->emplace(prefix + a.first, a.second); }); newValuesMap = prefixedValueMap.get(); } } if (User::mergeUserAttribute(type, *newValuesMap, destination)) { client->putua(type, std::move(destination), -1, UNDEF, 0, 0, std::move(putuaCompletion)); } else { // no changes, current value equal to new value LOG_debug << "Attribute " << User::attr2string(type) << " not changed, already up to date"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return API_OK; } else if (scope == ATTR_SCOPE_PRIVATE_UNENCRYPTED) { if (type == ATTR_LANGUAGE) { if (!value) { return API_EARGS; } string code; if (!getLanguageCode(value, &code)) { return API_ENOENT; } client->putua(type, (byte *)code.data(), unsigned(code.length()), -1, UNDEF, 0, 0, [putuaCompletion, request, this](Error e) { // if user just set the preferred language... change the GET param to the new language if (e == API_OK) { setLanguage(request->getText()); } putuaCompletion(e); }); return API_OK; } else if (type == ATTR_PWD_REMINDER) { if (request->getNumDetails() == 0) // nothing to be changed { return API_EARGS; } // always get updated value before update it getUserAttr(ownUser, type, request); return API_OK; } else if ((type == ATTR_DISABLE_VERSIONS) || (type == ATTR_NO_CALLKIT) || (type == ATTR_CONTACT_LINK_VERIFICATION) || (type == ATTR_VISIBLE_WELCOME_DIALOG) || (type == ATTR_VISIBLE_TERMS_OF_SERVICE)) { if (!value || strlen(value) != 1 || (value[0] != '0' && value[0] != '1')) { return API_EARGS; } client->putua(type, (byte*)value, 1, -1, UNDEF, 0, 0, std::move(putuaCompletion)); } else if (type == ATTR_RUBBISH_TIME || type == ATTR_LAST_PSA) { if (!value || !value[0]) { return API_EARGS; } char *endptr; m_off_t number = strtoll(value, &endptr, 10); if (endptr == value || *endptr != '\0' || number == LLONG_MAX || number == LLONG_MIN || number < 0) { return API_EARGS; } string tmp(value); client->putua(type, (byte *)tmp.data(), unsigned(tmp.size()), -1, UNDEF, 0, 0, std::move(putuaCompletion)); } else if (type == ATTR_COOKIE_SETTINGS) { setCookieSettings_sendPendingRequests(request); } else if (type == ATTR_PUSH_SETTINGS) { const MegaPushNotificationSettingsPrivate *settings = (MegaPushNotificationSettingsPrivate*)(request->getMegaPushNotificationSettings()); if (!settings) { return API_EARGS; } string settingsJson = settings->generateJson(); if (settingsJson.empty()) { return API_EARGS; } client->putua(type, (byte *)settingsJson.data(), unsigned(settingsJson.size()), -1, UNDEF, 0, 0, std::move(putuaCompletion)); } else if (type == ATTR_ENABLE_TEST_NOTIFICATIONS) { performRequest_enableTestNotifications(request); } else if (type == ATTR_LAST_READ_NOTIFICATION) { performRequest_setLastReadNotification(request); } else if (type == ATTR_LAST_ACTIONED_BANNER) { performRequest_setLastActionedBanner(request); } else if (type == ATTR_ENABLE_TEST_SURVEYS) { performRequest_enableTestSurveys(request); } else if (type == ATTR_DEV_OPT) { if (!value) { return API_EARGS; } client->putua(type, reinterpret_cast<const byte*>(value), static_cast<unsigned>(strlen(value)), -1, UNDEF, 0, 0, std::move(putuaCompletion)); } else { return API_EARGS; } } else // any other type of attribute { if (!value) { return API_EARGS; } client->putua(type, (byte *)value, unsigned(strlen(value)), -1, UNDEF, 0, 0, std::move(putuaCompletion)); return API_OK; } return API_OK; } void MegaApiImpl::getUserEmail(MegaHandle h, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_USER_EMAIL, listener); request->setNodeHandle(h); request->performRequest = [this, request]() { handle uh = request->getNodeHandle(); if (uh == INVALID_HANDLE) { return API_EARGS; } char uid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid); uid[11] = 0; client->getUserEmail(uid); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_setAttrFile(MegaRequestPrivate* request) { const char* srcFilePath = request->getFile(); int type = request->getParamType(); std::shared_ptr<Node> node = request->getNodeHandle() == INVALID_HANDLE ? nullptr : client->nodebyhandle(request->getNodeHandle()); MegaHandle fileattrhandle = (uint64_t)request->getNumber(); auto bu = static_cast<MegaBackgroundMediaUploadPrivate*>(request->getMegaBackgroundMediaUploadPtr()); if (!srcFilePath) { if (!node || fileattrhandle == INVALID_HANDLE) { return API_EARGS; } string fileattr; appendFileAttribute(fileattr, type, fileattrhandle); client->putFileAttributes(node->nodehandle, fatype(type), fileattr, request->getTag()); return API_OK; } if (!node == !bu) { return API_EARGS; } auto localpath = LocalPath::fromAbsolutePath(srcFilePath); std::unique_ptr<string> attributedata(new string); std::unique_ptr<FileAccess> f(fsAccess->newfileaccess()); if (!f->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)) { return API_EREAD; } // make the string a little bit larger initially with SymmCipher::BLOCKSIZE to avoid heap // activity growing it for the encryption attributedata->reserve(size_t(f->size + SymmCipher::BLOCKSIZE)); if (!f->fread(attributedata.get(), unsigned(f->size), 0, 0, FSLogging::logOnError)) { return API_EREAD; } return client->putfa(NodeOrUploadHandle(node ? node->nodeHandle() : NodeHandle()), static_cast<fatype>(type), bu ? bu->nodecipher(client) : node->nodecipher(), request->getTag(), std::move(attributedata)); } error MegaApiImpl::performRequest_setAttrNode(MegaRequestPrivate* request) { constexpr char logPre[] = "performRequest_setAttrNode. "; const handle h = request->getNodeHandle(); if (h == INVALID_HANDLE) { LOG_err << logPre << "invalid handle"; return API_EARGS; } error e = API_OK; std::shared_ptr<Node> node = client->nodebyhandle(h); if (!node) { LOG_err << logPre << "node not found with handle" << Base64Str<MegaClient::NODEHANDLE>(h); return API_EARGS; } if (!client->checkaccess(node.get(), FULL)) { LOG_err << logPre << "unexpected access level for node: " << Base64Str<MegaClient::NODEHANDLE>(h); return API_EACCESS; } attr_map attrUpdates; if (const auto isOfficial = request->getFlag(); isOfficial) { if (int type = request->getParamType(); type == MegaApi::NODE_ATTR_DURATION) { int secs = int(request->getNumber()); if (node->type != FILENODE || secs < MegaNode::INVALID_DURATION) { LOG_err << logPre << "invalid duration: " << secs; return API_EARGS; } if (secs == MegaNode::INVALID_DURATION) { attrUpdates['d'] = ""; } else { string attrVal; Base64::itoa(secs, &attrVal); if (attrVal.size()) { attrUpdates['d'] = attrVal; } } } else if (type == MegaApi::NODE_ATTR_COORDINATES) { if (node->type != FILENODE) { LOG_err << logPre << "invalid nodetype: " << node->type; return API_EARGS; } int longitude = request->getNumDetails(); int latitude = request->getTransferTag(); int unshareable = request->getAccess(); e = updateAttributesMapWithCoordinates(attrUpdates, latitude, longitude, !!unshareable, client); if (e != API_OK) { return e; } } else if (type == MegaApi::NODE_ATTR_ORIGINALFINGERPRINT) { nameid nid = AttrMap::string2nameid("c0"); if (!request->getText()) { attrUpdates[nid] = ""; } else { attrUpdates[nid] = request->getText(); } } else if (type == MegaApi::NODE_ATTR_LABEL || type == MegaApi::NODE_ATTR_FAV || type == MegaApi::NODE_ATTR_SEN) { std::shared_ptr<Node> current = node; bool remove = false; nameid nid = 0; int value = 0; if (type == MegaApi::NODE_ATTR_LABEL) { value = request->getNumDetails(); if (value < LBL_UNKNOWN || value > LBL_GREY) { LOG_err << logPre << "invalid label: " << value; return API_EARGS; } nid = AttrMap::string2nameid("lbl"); remove = (value == LBL_UNKNOWN); } else if (type == MegaApi::NODE_ATTR_SEN) { nid = AttrMap::string2nameid("sen"); remove = !request->getNumDetails(); value = 1; } else { nid = AttrMap::string2nameid("fav"); remove = !request->getNumDetails(); value = 1; } if (remove) { attrUpdates[nid] = ""; } else { attrUpdates[nid] = std::to_string(value); } // update file versions if any if (current->type == FILENODE) { sharedNode_list childrens = client->getChildren(current.get()); while (childrens.size()) { assert(childrens.size() == 1); // versions are 1-child chains std::shared_ptr<Node> n = *childrens.begin(); client->setattr(n, attr_map(attrUpdates), nullptr, false); // no callback for these childrens = client->getChildren(n.get()); } } return client->setattr( current, std::move(attrUpdates), [request, this](NodeHandle h, Error e) { request->setNodeHandle(h.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }, false); } else if (bool isTypeS4 = (type == MegaApi::NODE_ATTR_S4); isTypeS4 || type == MegaApi::NODE_ATTR_DESCRIPTION) { const char* attrValue = request->getText(); if (type == MegaApi::NODE_ATTR_DESCRIPTION && attrValue && strlen(attrValue) > MegaApi::MAX_NODE_DESCRIPTION_SIZE) { LOG_err << logPre << "invalid attrValue len"; return API_EARGS; } const char* attributeName = isTypeS4 ? "s4" : MegaClient::NODE_ATTRIBUTE_DESCRIPTION; attrUpdates[AttrMap::string2nameid(attributeName)] = attrValue ? attrValue : ""; } else { LOG_err << logPre << "invalid attrType: " << type; return API_EARGS; } } else // custom attribute, not official { const char* attrName = request->getName(); const char* attrValue = request->getText(); if (!attrName || !attrName[0] || strlen(attrName) > 7) { LOG_err << logPre << "invalid attrName: " << (attrName && attrName[0] ? attrName : ""); return API_EARGS; } string sname = attrName; LocalPath::utf8_normalize(&sname); sname.insert(0, "_"); nameid attr = AttrMap::string2nameid(sname.c_str()); if (attrValue) { string svalue = attrValue; LocalPath::utf8_normalize(&svalue); attrUpdates[attr] = svalue; } else { attrUpdates[attr] = ""; } } if (!e && !attrUpdates.empty()) { e = client->setattr( node, std::move(attrUpdates), [request, this](NodeHandle h, Error e) { request->setNodeHandle(h.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }, false); } return e; } void MegaApiImpl::getFavourites(MegaNode* node, int count, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_NODE, listener); if (node) { request->setNodeHandle(node->getHandle()); } else { request->setNodeHandle(UNDEF); } request->setParamType(MegaApi::NODE_ATTR_FAV); request->setNumDetails(count); request->performRequest = [this, request]() { int type = request->getParamType(); if (type == MegaApi::NODE_ATTR_FAV) { int count = request->getNumDetails(); MegaHandle folderHandle = request->getNodeHandle(); std::shared_ptr<Node> node; if (!ISUNDEF(folderHandle)) { node = client->nodebyhandle(folderHandle); if (!node) { return API_ENOENT; } if (node->type != FOLDERNODE) { return API_EARGS; } } else { node = client->nodeByHandle(client->mNodeManager.getRootNodeFiles()); if (!node) { LOG_debug << "Lookup of files root node failed"; return API_ENOENT; } } // Check if 'node' is favourite, DB query starts at 'node' children std::vector<NodeHandle> favouriteNodes; nameid nid = AttrMap::string2nameid("fav"); auto attrMapIterator = node->attrs.map.find(nid); if (attrMapIterator != node->attrs.map.end() && attrMapIterator->second == "1") { favouriteNodes.push_back(node->nodeHandle()); } if (count != 1 || favouriteNodes.empty()) { std::vector<NodeHandle> favs = client->mNodeManager.getFavouritesNodeHandles(node->nodeHandle(), static_cast<uint32_t>(count)); favouriteNodes.insert(favouriteNodes.end(), favs.begin(), favs.end()); } std::vector<handle> handles; for (const NodeHandle& nodeHandle : favouriteNodes) { handles.push_back(nodeHandle.as8byte()); } request->setMegaHandleList(handles); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::cancelGetNodeAttribute(MegaNode* node, int type, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_ATTR_FILE, listener); request->setParamType(type); if (node) { request->setNodeHandle(node->getHandle()); const char* fileAttributes = node->getFileAttrString(); if (fileAttributes) { request->setText(fileAttributes); delete[] fileAttributes; } } request->performRequest = [this, request]() { int type = request->getParamType(); handle h = request->getNodeHandle(); const char *fa = request->getText(); std::shared_ptr<Node> node = client->nodebyhandle(h); if((!fa && !node) || (fa && ISUNDEF(h))) { return API_EARGS; } string fileattrstring = fa ? string(fa) : node->fileattrstring; error e = client->getfa(h, &fileattrstring, string(), (fatype)type, 1); if (!e) { std::map<int, MegaRequestPrivate*>::iterator it = requestMap.begin(); while(it != requestMap.end()) { MegaRequestPrivate *r = it->second; it++; if (r->getType() == MegaRequest::TYPE_GET_ATTR_FILE && r->getParamType() == request->getParamType() && r->getNodeHandle() == request->getNodeHandle()) { fireOnRequestFinish(r, std::make_unique<MegaErrorPrivate>(API_EINCOMPLETE)); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); } return e; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_retryPendingConnections(MegaRequestPrivate* request) { bool disconnect = request->getFlag(); bool includexfers = request->getNumber() != 0; const char *dnsservers = request->getText(); error result{API_OK}; client->abortbackoff(includexfers); if (disconnect) { client->disconnect(); if (dnsservers) { if (!httpio->setdnsservers(dnsservers)) { LOG_warn << "libcurl does not have support for a DNS resolver backend. " "Build libcurl with c-ares support."; result = API_ENOENT; } } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(result)); return result; } void MegaApiImpl::inviteContact(const char* email, const char* message, int action, MegaHandle contactLink, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_INVITE_CONTACT, listener); request->setNumber(action); request->setEmail(email); request->setText(message); request->setNodeHandle(contactLink); request->performRequest = [this, request]() { const char *email = request->getEmail(); const char *message = request->getText(); int action = int(request->getNumber()); MegaHandle contactLink = request->getNodeHandle(); if(client->loggedin() != FULLACCOUNT) { return API_EACCESS; } if(!email || !client->finduser(client->me)->email.compare(email)) { return API_EARGS; } if (action != OPCA_ADD && action != OPCA_REMIND && action != OPCA_DELETE) { return API_EARGS; } if (action == OPCA_ADD && client->findpcr(string{email})) { return API_EEXIST; } client->setpcr(email, (opcactions_t)action, message, NULL, contactLink); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::replyContactRequest(const MegaContactRequest* r, int action, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REPLY_CONTACT_REQUEST, listener); if (r) { request->setNodeHandle(r->getHandle()); } request->setNumber(action); request->performRequest = [this, request]() { handle h = request->getNodeHandle(); int action = int(request->getNumber()); if(h == INVALID_HANDLE || action < 0 || action > MegaContactRequest::REPLY_ACTION_IGNORE) { return API_EARGS; } client->updatepcr(h, (ipcactions_t)action); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeContact(MegaUser* user, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_CONTACT, listener); if (user) { request->setEmail(user->getEmail()); } request->performRequest = [this, request]() { const char *email = request->getEmail(); User *u = client->finduser(email); if(!u || u->show == HIDDEN || u->userhandle == client->me) { return API_EARGS; } if (client->mBizMode == BIZ_MODE_SUBUSER && u->mBizMode != BIZ_MODE_UNKNOWN) { return API_EMASTERONLY; } return client->removecontact(email, HIDDEN); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_createAccount(MegaRequestPrivate* request) { const char *email = request->getEmail(); const char *password = request->getPassword(); const char *name = request->getName(); const char *lastname = request->getText(); const char *sid = request->getSessionKey(); bool resumeProcess = (request->getParamType() == MegaApi::RESUME_ACCOUNT); // resume existing ephemeral account bool cancelProcess = (request->getParamType() == MegaApi::CANCEL_ACCOUNT); bool createEphemeralPlusPlus = (request->getParamType() == MegaApi::CREATE_EPLUSPLUS_ACCOUNT); // create ephemeral++ account bool resumeEphemeralPlusPlus = (request->getParamType() == MegaApi::RESUME_EPLUSPLUS_ACCOUNT); // resume existing ephemeral++ account handle lastPublicHandle = request->getNodeHandle(); int lastPublicHandleType = request->getAccess(); int64_t lastAccessTimestamp =request->getTransferredBytes(); if (!ISUNDEF(lastPublicHandle) && ((lastPublicHandleType <= mega::MegaApi::AFFILIATE_TYPE_INVALID || lastPublicHandleType > mega::MegaApi::AFFILIATE_TYPE_CONTACT) || !lastAccessTimestamp)) { return API_EARGS; } if ((!resumeProcess && !cancelProcess && !resumeEphemeralPlusPlus && !createEphemeralPlusPlus && (!email || !name || !password)) || ((resumeProcess || resumeEphemeralPlusPlus) && !sid) || (createEphemeralPlusPlus && !(name && lastname))) { return API_EARGS; } byte pwbuf[SymmCipher::KEYLENGTH]; handle uh = UNDEF; if (resumeProcess) { size_t pwkeyLen = strlen(sid); size_t pwkeyLenExpected = SymmCipher::KEYLENGTH * 4 / 3 + 3 + 10; if (pwkeyLen != pwkeyLenExpected || Base64::atob(sid, (byte*) &uh, sizeof uh) != sizeof uh || uh == UNDEF || sid[11] != '#' || Base64::atob(sid + 12, pwbuf, sizeof pwbuf) != sizeof pwbuf) { return API_EARGS; } } if (cancelProcess) { client->cancelsignup(); } else { int reqtag = request->getTag(); requestMap.erase(reqtag); abortPendingActions(); requestMap[reqtag] = request; client->locallogout(false, true); if (resumeProcess) { client->resumeephemeral(uh, pwbuf); } else if (resumeEphemeralPlusPlus) { client->resumeephemeralPlusPlus(Base64::atob(string(sid))); } else if (createEphemeralPlusPlus) { client->createephemeralPlusPlus(); } else { assert(request->getParamType() == MegaApi::CREATE_ACCOUNT); client->createephemeral(); } } return API_OK; } error MegaApiImpl::performRequest_sendSignupLink(MegaRequestPrivate* request) { const char *email = request->getEmail(); const char *name = request->getName(); if (!email || !name) { return API_EARGS; } if (client->loggedin() != EPHEMERALACCOUNT && client->loggedin() != EPHEMERALACCOUNTPLUSPLUS) { return API_EACCESS; } if (client->accountversion != 2) { return API_EINTERNAL; } client->resendsignuplink2(email, name); return API_OK; } void MegaApiImpl::querySignupLink(const char* link, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_SIGNUP_LINK, listener); request->setLink(link); request->performRequest = [this, request]() { const char *link = request->getLink(); if(!link) { return API_EARGS; } const char* ptr = link; const char* tptr; // is it a link to confirm the account? ie. https://mega.app/#confirm<code_in_B64> tptr = strstr(ptr, MegaClient::confirmLinkPrefix()); if (tptr) { ptr = tptr + strlen(MegaClient::confirmLinkPrefix()); string code = Base64::atob(string(ptr)); if (!code.empty()) { if (code.find("ConfirmCodeV2") != string::npos) { // “ConfirmCodeV2” (13B) || Email Confirmation Token (15B) || Email (>=5B) || \t || Fullname || Hash (8B) size_t posEmail = 13 + 15; size_t endEmail = code.find("\t", posEmail); if (endEmail != string::npos) { string email = code.substr(posEmail, endEmail - posEmail); request->setEmail(email.c_str()); request->setName(code.substr(endEmail + 1, code.size() - endEmail - 9).c_str()); sessiontype_t session = client->loggedin(); if (session == FULLACCOUNT) { return (client->ownuser()->email == email) ? API_EEXPIRED : API_EACCESS; } else // not-logged-in / ephemeral account / partially confirmed { client->confirmsignuplink2((const byte*)code.data(), unsigned(code.size())); return API_OK; } } } } } // is it a new singup link? ie. https://mega.app/#newsignup<code_in_B64> else if ((tptr = strstr(ptr, MegaClient::newsignupLinkPrefix())) != nullptr) { ptr = tptr + strlen(MegaClient::newsignupLinkPrefix()); unsigned len = static_cast<unsigned>( (strlen(link) - static_cast<size_t>(ptr - link)) * 3 / 4 + 4); byte *c = new byte[len]; len = static_cast<unsigned>(Base64::atob(ptr, c, static_cast<int>(len))); if (len > 8) { // extract email and email_hash from link byte *email = c; byte *sha512bytes = c+len-8; // last 11 chars // get the hash for the received email Hash sha512; sha512.add(email, len-8); string sha512str; sha512.get(&sha512str); // and finally check it if (memcmp(sha512bytes, sha512str.data(), 8) == 0) { email[len-8] = '\0'; request->setEmail((const char *)email); delete[] c; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } } delete[] c; } return API_EARGS; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_confirmAccount(MegaRequestPrivate* request) { const char* link = request->getLink(); if (!link) { return API_EARGS; } const char* ptr = link; const char* tptr; tptr = strstr(ptr, MegaClient::confirmLinkPrefix()); if (tptr) ptr = tptr + strlen(MegaClient::confirmLinkPrefix()); error e = API_OK; string code = Base64::atob(string(ptr)); if (code.find("ConfirmCodeV2") != string::npos) { // “ConfirmCodeV2” (13B) || Email Confirmation Token (15B) || Email (>=5B) || \t || Fullname || Hash (8B) size_t posEmail = 13 + 15; size_t endEmail = code.find("\t", posEmail); if (endEmail != string::npos) { string email = code.substr(posEmail, endEmail - posEmail); request->setEmail(email.c_str()); request->setName(code.substr(endEmail + 1, code.size() - endEmail - 9).c_str()); sessiontype_t session = client->loggedin(); if (session == FULLACCOUNT) { e = (client->ownuser()->email == email) ? API_EEXPIRED : API_EACCESS; } else // not-logged-in / ephemeral account / partially confirmed { client->confirmsignuplink2((const byte*)code.data(), unsigned(code.size())); } } else { e = API_EARGS; } } else { e = API_EARGS; } return e; } void MegaApiImpl::resetPassword(const char* email, bool hasMasterKey, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECOVERY_LINK, listener); request->setEmail(email); request->setFlag(hasMasterKey); request->performRequest = [this, request]() { const char *email = request->getEmail(); bool hasMasterKey = request->getFlag(); if (!email || !email[0]) { return API_EARGS; } client->getrecoverylink(email, hasMasterKey); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::queryRecoveryLink(const char* link, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_RECOVERY_LINK, listener); request->setLink(link); request->performRequest = [this, request]() { const char *link = request->getLink(); const char* code; if (link && (code = strstr(link, MegaClient::recoverLinkPrefix())) != nullptr) { code += strlen(MegaClient::recoverLinkPrefix()); } else if (link && (code = strstr(link, MegaClient::verifyLinkPrefix())) != nullptr) { code += strlen(MegaClient::verifyLinkPrefix()); } else if (link && (code = strstr(link, MegaClient::cancelLinkPrefix())) != nullptr) { code += strlen(MegaClient::cancelLinkPrefix()); } else { return API_EARGS; } client->queryrecoverylink(code); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::confirmResetPasswordLink(const char* link, const char* newPwd, const char* masterKey, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_RECOVERY_LINK, listener); request->setLink(link); request->setPassword(newPwd); request->setPrivateKey(masterKey); request->performRequest = [this, request]() { const char *link = request->getLink(); const char *newPwd = request->getPassword(); if (!newPwd || !link) { return API_EARGS; } const char* code; code = strstr(link, MegaClient::recoverLinkPrefix()); if (!code) { return API_EARGS; } code += strlen(MegaClient::recoverLinkPrefix()); // concatenate query + confirm requests client->queryrecoverylink(code); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::checkRecoveryKey(const char* link, const char* recoveryKey, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_RECOVERY_KEY, listener); request->setLink(link); request->setPrivateKey(recoveryKey); request->performRequest = [this, request]() { const char* link = request->getLink(); if (!link) { return API_EARGS; } const char* code; code = strstr(link, MegaClient::recoverLinkPrefix()); if (!code) { return API_EARGS; } code += strlen(MegaClient::recoverLinkPrefix()); // concatenate query + confirm requests client->getprivatekey(code); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getCancelLink(MegaRequestPrivate* request) { if (client->loggedin() != FULLACCOUNT) { return API_EACCESS; } User *u = client->finduser(client->me); if (!u) { return API_ENOENT; } const char *pin = request->getText(); client->getcancellink(u->email.c_str(), pin); return API_OK; } void MegaApiImpl::confirmCancelAccount(const char* link, const char* pwd, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_CANCEL_LINK, listener); request->setLink(link); request->setPassword(pwd); request->performRequest = [this, request]() { const char *link = request->getLink(); const char *pwd = request->getPassword(); if (client->loggedin() != FULLACCOUNT) { return API_EACCESS; } if (!pwd || !link) { return API_EARGS; } const char* code; code = strstr(link, MegaClient::cancelLinkPrefix()); if (!code) { return API_EARGS; } if (!checkPassword(pwd)) { return API_ENOENT; } code += strlen(MegaClient::cancelLinkPrefix()); client->confirmcancellink(code); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getChangeEmailLink(MegaRequestPrivate* request) { if (client->loggedin() != FULLACCOUNT) { return API_EACCESS; } const char *email = request->getEmail(); const char *pin = request->getText(); if (!email) { return API_EARGS; } client->getemaillink(email, pin); return API_OK; } void MegaApiImpl::confirmChangeEmail(const char* link, const char* pwd, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK, listener); request->setLink(link); request->setPassword(pwd); request->performRequest = [this, request]() { const char *link = request->getLink(); const char *pwd = request->getPassword(); if (client->loggedin() != FULLACCOUNT) { return API_EACCESS; } if (!pwd || !link) { return API_EARGS; } const char* code; code = strstr(link, MegaClient::verifyLinkPrefix()); if (!code) { return API_EARGS; } code += strlen(MegaClient::verifyLinkPrefix()); // concatenates query + validate pwd + confirm client->queryrecoverylink(code); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::pauseTransfers(bool pause, int direction, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PAUSE_TRANSFERS, listener); request->setFlag(pause); request->setNumber(direction); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { bool pause = request->getFlag(); int direction = int(request->getNumber()); if(direction != -1 && direction != MegaTransfer::TYPE_DOWNLOAD && direction != MegaTransfer::TYPE_UPLOAD) { return API_EARGS; } if(direction == -1) { client->pausexfers(PUT, pause, false, committer); client->pausexfers(GET, pause, false, committer); } else if(direction == MegaTransfer::TYPE_DOWNLOAD) { client->pausexfers(GET, pause, false, committer); } else { client->pausexfers(PUT, pause, false, committer); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::pauseTransfer(int transferTag, bool pause, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PAUSE_TRANSFER, listener); request->setTransferTag(transferTag); request->setFlag(pause); request->performTransferRequest = [this, request](TransferDbCommitter& committer) { bool pause = request->getFlag(); int transferTag = request->getTransferTag(); MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag); if (!megaTransfer) { return API_ENOENT; } error e = client->transferlist.pause(megaTransfer->getTransfer(), pause, committer); if (!e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resumeTransfersForNotLoggedInInstance() { SdkMutexGuard g(sdkMutex); client->resumeTransfersForNotLoggedInInstance(); } error MegaApiImpl::performTransferRequest_moveTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer) { bool automove = request->getFlag(); int transferTag = request->getTransferTag(); int number = int(request->getNumber()); if (!transferTag || !number) { return API_EARGS; } MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag); if (!megaTransfer) { return API_ENOENT; } Transfer *transfer = megaTransfer->getTransfer(); if (!transfer) { return API_ENOENT; } if (automove) { switch (number) { case MegaTransfer::MOVE_TYPE_UP: client->transferlist.moveup(transfer, committer); break; case MegaTransfer::MOVE_TYPE_DOWN: client->transferlist.movedown(transfer, committer); break; case MegaTransfer::MOVE_TYPE_TOP: client->transferlist.movetofirst(transfer, committer); break; case MegaTransfer::MOVE_TYPE_BOTTOM: client->transferlist.movetolast(transfer, committer); break; default: return API_EARGS; } } else { MegaTransferPrivate* prevMegaTransfer = getMegaTransferPrivate(number); if (!prevMegaTransfer) { return API_ENOENT; } Transfer *prevTransfer = prevMegaTransfer->getTransfer(); if (!prevTransfer) { client->transferlist.movetransfer(transfer, client->transferlist.transfers[transfer->type].begin(), committer); } else { if (transfer->type != prevTransfer->type) { return API_EARGS; } else { client->transferlist.movetransfer(transfer, prevTransfer, committer); } } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } void MegaApiImpl::setMaxConnections(int direction, int connections, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_MAX_CONNECTIONS, listener); request->setParamType(direction); request->setNumber(connections); request->performRequest = [this, request]() { int direction = request->getParamType(); int connections = int(request->getNumber()); if (connections <= 0 || (direction != -1 && direction != MegaTransfer::TYPE_DOWNLOAD && direction != MegaTransfer::TYPE_UPLOAD)) { return API_EARGS; } if ((unsigned int) connections > MegaClient::MAX_NUM_CONNECTIONS) { return API_ETOOMANY; } if (direction == -1) { client->setmaxconnections(GET, connections); client->setmaxconnections(PUT, connections); } else if (direction == MegaTransfer::TYPE_DOWNLOAD) { client->setmaxconnections(GET, connections); } else { client->setmaxconnections(PUT, connections); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMaxTransferConnections(const direction_t direction, MegaRequestListener* const listener) { auto* const request = new MegaRequestPrivate(MegaRequest::TYPE_GET_MAX_CONNECTIONS, listener); assert(direction == GET || direction == PUT); request->setParamType(direction); request->performRequest = [this, request]() { const auto direction = request->getParamType(); if (direction != GET && direction != PUT) return API_EINTERNAL; request->setNumber(client->connections[direction]); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMaxUploadConnections(MegaRequestListener* const listener) { getMaxTransferConnections(PUT, listener); } void MegaApiImpl::getMaxDownloadConnections(MegaRequestListener* const listener) { getMaxTransferConnections(GET, listener); } error MegaApiImpl::performTransferRequest_cancelTransfer(MegaRequestPrivate* request, TransferDbCommitter& committer) { int transferTag = request->getTransferTag(); MegaTransferPrivate* megaTransfer = getMegaTransferPrivate(transferTag); if (!megaTransfer) { return API_ENOENT; } if (megaTransfer->getType() == MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD) { return API_EACCESS; } if (megaTransfer->isFolderTransfer()) { // folder upload or folder download. // we can do this by cancel token // all sub-transfers must have the same cancel token if (!megaTransfer->getCancelToken()) { LOG_warn << "Cancel requested for folder transfer, but it has lost its cancel token"; return API_EARGS; } else { // there is a race to this point, in theory the operation could have just completed successfully on the thread megaTransfer->stopRecursiveOperationThread(); // mark this transfer and any/all sub-transfers as cancelled megaTransfer->getCancelToken()->cancel(); // report cancellation signalled fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } } if (!megaTransfer->isStreamingTransfer()) { Transfer *transfer = megaTransfer->getTransfer(); if (!transfer) { return API_ENOENT; } #ifdef _WIN32 if (transfer->type == GET) { RemoveHiddenFileAttribute(transfer->localfilename); } #endif MegaErrorPrivate megaError(API_EINCOMPLETE); megaTransfer->setLastError(&megaError); bool found = false; file_list files = transfer->files; file_list::iterator iterator = files.begin(); while (iterator != files.end()) { File *file = *iterator; iterator++; if (file->tag == transferTag) { found = true; if (!file->syncxfer) { client->stopxfer(file, &committer); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } else { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EACCESS)); } break; } } if (!found) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_ENOENT)); } } else { m_off_t startPos = megaTransfer->getStartPos(); m_off_t endPos = megaTransfer->getEndPos(); m_off_t totalBytes = endPos - startPos + 1; MegaNode *publicNode = megaTransfer->getPublicNode(); if (publicNode) { client->preadabort(publicNode->getHandle(), startPos, totalBytes); } else { std::shared_ptr<Node> node = client->nodebyhandle(megaTransfer->getNodeHandle()); if (node) { client->preadabort(node.get(), startPos, totalBytes); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return API_OK; } void MegaApiImpl::cancelTransfers(int direction, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CANCEL_TRANSFERS, listener); request->setParamType(direction); request->performRequest = [this, request]() { int direction = request->getParamType(); if ((direction != MegaTransfer::TYPE_DOWNLOAD) && (direction != MegaTransfer::TYPE_UPLOAD)) { return API_EARGS; } CancelToken cancelled(true); // 1. Set all intermediate layer MegaTransfer object cancel tokens transferQueue.setAllCancelled(cancelled, direction); for (auto& t : transferMap) { if (t.second->getType() == direction && !t.second->isSyncTransfer() && !t.second->isStreamingTransfer()) { t.second->setCancelToken(cancelled); } } // 2. cancel transfers that were already startxfer'd for (auto& it : client->multi_transfers[direction]) { for (auto& it2 : it.second->files) { if (!it2->syncxfer) { it2->cancelToken = cancelled; } } } LOG_verbose << "Marked all non-sync non-streaming transfers as cancelled. direction: " << direction; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setScheduledCopy(const char* localFolder, MegaNode* parent, bool attendPastBackups, int64_t period, string periodstring, int numBackups, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_SCHEDULED_COPY, listener); if (parent) request->setNodeHandle(parent->getHandle()); if (localFolder) { request->setFile(localFolder); } request->setNumRetry(numBackups); request->setNumber(period); request->setText(periodstring.c_str()); request->setFlag(attendPastBackups); request->performRequest = [this, request]() { std::shared_ptr<Node> parent = client->nodebyhandle(request->getNodeHandle()); const char *localPath = request->getFile(); if(!parent || (parent->type==FILENODE) || !localPath) { return API_EARGS; } string utf8name(localPath); MegaScheduledCopyController *mbc = NULL; int tagexisting = 0; bool existing = false; for (std::map<int, MegaScheduledCopyController *>::iterator it = backupsMap.begin(); it != backupsMap.end(); ++it) { if (!strcmp(it->second->getLocalFolder(), utf8name.c_str()) && it->second->getMegaHandle() == request->getNodeHandle()) { existing = true; mbc = it->second; tagexisting = it->first; } } if (existing){ LOG_debug << "Updating existing backup parameters: " << utf8name.c_str() << " to " << request->getNodeHandle(); mbc->setPeriod(request->getNumber()); mbc->setPeriodstring(request->getText()); mbc->setMaxBackups(request->getNumRetry()); mbc->setAttendPastBackups(request->getFlag()); request->setTransferTag(tagexisting); if (!mbc->isValid()) { LOG_err << "Failed to update backup parameters: Invalid parameters"; return API_EARGS; } } else { int tag = request->getTag(); int tagForFolderTansferTag = client->nextreqtag(); string speriod = request->getText(); bool attendPastBackups= request->getFlag(); //TODO: add existence of local folder check (optional??) MegaScheduledCopyController* newScheduledCopyController = new MegaScheduledCopyController(this, tag, tagForFolderTansferTag, request->getNodeHandle(), utf8name.c_str(), attendPastBackups, speriod.c_str(), request->getNumber(), request->getNumRetry()); // TODO: should we add this in setScheduledCopy? newScheduledCopyController->setBackupListener(request->getBackupListener()); if (newScheduledCopyController->isValid()) { backupsMap[tag] = newScheduledCopyController; request->setTransferTag(tag); } else { delete newScheduledCopyController; return API_EARGS; } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeScheduledCopy(int tag, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SCHEDULED_COPY, listener); request->setNumber(tag); request->performRequest = [this, request]() { int backuptag = int(request->getNumber()); bool found = false; bool flag = request->getFlag(); error e = API_OK; map<int, MegaScheduledCopyController *>::iterator itr = backupsMap.find(backuptag) ; if (itr != backupsMap.end()) { found = true; } if (found) { if (!flag) { MegaRequestPrivate *requestabort = new MegaRequestPrivate(MegaRequest::TYPE_ABORT_CURRENT_SCHEDULED_COPY); requestabort->setNumber(backuptag); requestabort->performRequest = [this, requestabort]() { return processAbortBackupRequest(requestabort); }; int nextTag = client->nextreqtag(); requestabort->setTag(nextTag); requestMap[nextTag]=requestabort; fireOnRequestStart(requestabort); e = processAbortBackupRequest(requestabort); if (e) { LOG_err << "Failed to abort backup upon remove request"; fireOnRequestFinish(requestabort, std::make_unique<MegaErrorPrivate>(API_OK)); } else { request->setFlag(true); requestQueue.push(request); } } else { MegaScheduledCopyController * todelete = itr->second; backupsMap.erase(backuptag); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); delete todelete; } } else { e = API_ENOENT; } return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::startTimer(int64_t period, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_TIMER, listener); request->setNumber(period); request->performRequest = [this, request]() { int delta = int(request->getNumber()); TimerWithBackoff *twb = new TimerWithBackoff(client->rng, request->getTag()); twb->backoff(delta); return client->addtimer(twb); }; requestQueue.push(request); waiter->notify(); } #ifdef ENABLE_SYNC void MegaApiImpl::loadExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_LOAD_EXTERNAL_DRIVE_BACKUPS, listener); request->setFile(externalDriveRoot); request->setListener(listener); request->performRequest = [this, request]() { const char* externalDrive = request->getFile(); if (!externalDrive) { return API_EARGS; } else { client->syncs.backupOpenDrive(LocalPath::fromAbsolutePath(externalDrive), [this, request](Error e){ fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e), true); }); return API_OK; } }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::closeExternalBackupSyncsFromExternalDrive(const char* externalDriveRoot, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CLOSE_EXTERNAL_DRIVE_BACKUPS, listener); request->setFile(externalDriveRoot); request->setListener(listener); request->performRequest = [this, request]() { const char* externalDrive = request->getFile(); if (!externalDrive) { return API_EARGS; } else { client->syncs.backupCloseDrive(LocalPath::fromAbsolutePath(externalDrive), [this, request](Error e){ fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e), true); }); return API_OK; } }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::importSyncConfigs(const char* configs, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_IMPORT_SYNC_CONFIGS, listener); request->setText(configs); request->performRequest = [this, request]() { if (const auto* configs = request->getText()) { auto completion = [request, this](error result) { auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(request, std::move(error)); }; client->importSyncConfigs(configs, std::move(completion)); return API_OK; } return API_EARGS; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::copySyncDataToCache(const char*, const char*, MegaHandle, const char*, long long, bool, bool, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_COPY_SYNC_CONFIG, listener); request->performRequest = []() { return API_EINTERNAL; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::copyCachedStatus(int, int, int, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_COPY_CACHED_STATUS, listener); request->performRequest = []() { return API_EINTERNAL; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeSyncById(handle backupId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SYNC, listener); request->setParentHandle(backupId); request->setFlag(true); request->performRequest = [this, request]() { auto backupId = request->getParentHandle(); if (backupId == INVALID_HANDLE) { return API_EARGS; } SyncConfig c; if (!client->syncs.syncConfigByBackupId(backupId, c)) { LOG_err << "Backup id not found: " << Base64Str<MegaClient::BACKUPHANDLE>(backupId); return API_ENOENT; } request->setFile(c.mLocalPath.toPath(false).c_str()); std::function<void(Error)> completion = [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(error(e))); }; client->syncs.deregisterThenRemoveSyncById(backupId, std::move(completion)); return API_OK; }; requestQueue.push(request); waiter->notify(); } #endif // ENABLE_SYNC void MegaApiImpl::reportEvent(const char* details, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REPORT_EVENT, listener); request->setText(details); request->performRequest = [this, request]() { const char *details = request->getText(); if(!details) { return API_EARGS; } string event = "A"; //Application event size_t size = strlen(details); char* base64details = new char[size * 4 / 3 + 4]; Base64::btoa((byte *)details, size, base64details); client->reportevent(event.c_str(), base64details); delete [] base64details; return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_enumeratequotaitems(MegaRequestPrivate* request) { if ((request->getType() == MegaRequest::TYPE_GET_PAYMENT_ID) && (request->getParamType() < mega::MegaApi::AFFILIATE_TYPE_INVALID || request->getParamType() > mega::MegaApi::AFFILIATE_TYPE_CONTACT)) { return API_EARGS; } int method = int(request->getNumber()); if(method != MegaApi::PAYMENT_METHOD_BALANCE && method != MegaApi::PAYMENT_METHOD_CREDIT_CARD) { return API_EARGS; } std::optional<std::string> countryCode; if (request->getText()) { countryCode = request->getText(); } client->purchase_enumeratequotaitems(countryCode); return API_OK; } void MegaApiImpl::submitPurchaseReceipt(int gateway, const char* receipt, MegaHandle lastPublicHandle, int lastPublicHandleType, int64_t lastAccessTimestamp, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUBMIT_PURCHASE_RECEIPT, listener); request->setNumber(gateway); request->setText(receipt); request->setNodeHandle(lastPublicHandle); request->setParamType(lastPublicHandleType); request->setTransferredBytes(lastAccessTimestamp); request->performRequest = [this, request]() { const char* receipt = request->getText(); int type = int(request->getNumber()); handle lph = request->getNodeHandle(); int phtype = request->getParamType(); int64_t ts = request->getTransferredBytes(); if (request->getParamType() < mega::MegaApi::AFFILIATE_TYPE_INVALID || request->getParamType() > mega::MegaApi::AFFILIATE_TYPE_CONTACT) { return API_EARGS; } if(!receipt || (type != MegaApi::PAYMENT_METHOD_GOOGLE_WALLET && type != MegaApi::PAYMENT_METHOD_ITUNES && type != MegaApi::PAYMENT_METHOD_WINDOWS_STORE && type != MegaApi::PAYMENT_METHOD_HUAWEI_WALLET)) { return API_EARGS; } if(type == MegaApi::PAYMENT_METHOD_ITUNES && client->loggedin() != FULLACCOUNT) { return API_EACCESS; } string base64receipt; if (type == MegaApi::PAYMENT_METHOD_GOOGLE_WALLET || type == MegaApi::PAYMENT_METHOD_WINDOWS_STORE || type == MegaApi::PAYMENT_METHOD_HUAWEI_WALLET) { size_t len = strlen(receipt); base64receipt.resize(len * 4 / 3 + 4); base64receipt.resize( Base64::btoa((byte*)receipt, len, (char*)base64receipt.data())); } else // MegaApi::PAYMENT_METHOD_ITUNES { base64receipt = receipt; } client->submitpurchasereceipt(type, base64receipt.c_str(), lph, phtype, ts); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::creditCardStore(const char* address1, const char* address2, const char* city, const char* province, const char* country, const char* postalcode, const char* firstname, const char* lastname, const char* creditcard, const char* expire_month, const char* expire_year, const char* cv2, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_STORE, listener); string email; { SdkMutexGuard g(sdkMutex); User* u = client->finduser(client->me); if (u) { email = u->email; } } if (email.size()) { string saddress1, saddress2, scity, sprovince, scountry, spostalcode; string sfirstname, slastname, screditcard, sexpire_month, sexpire_year, scv2; if (address1) { saddress1 = address1; } if (address2) { saddress2 = address2; } if (city) { scity = city; } if (province) { sprovince = province; } if (country) { scountry = country; } if (postalcode) { spostalcode = postalcode; } if (firstname) { sfirstname = firstname; } if (lastname) { slastname = lastname; } if (creditcard) { screditcard = creditcard; screditcard.erase(std::remove_if(screditcard.begin(), screditcard.end(), char_is_not_digit), screditcard.end()); } if (expire_month) { sexpire_month = expire_month; } if (expire_year) { sexpire_year = expire_year; } if (cv2) { scv2 = cv2; } size_t tam = 256 + sfirstname.size() + slastname.size() + screditcard.size() + sexpire_month.size() + sexpire_year.size() + scv2.size() + saddress1.size() + saddress2.size() + scity.size() + sprovince.size() + spostalcode.size() + scountry.size() + email.size(); char* ccplain = new char[tam]; snprintf(ccplain, tam, "{\"first_name\":\"%s\",\"last_name\":\"%s\"," "\"card_number\":\"%s\"," "\"expiry_date_month\":\"%s\",\"expiry_date_year\":\"%s\"," "\"cv2\":\"%s\",\"address1\":\"%s\"," "\"address2\":\"%s\",\"city\":\"%s\"," "\"province\":\"%s\",\"postal_code\":\"%s\"," "\"country_code\":\"%s\",\"email_address\":\"%s\"}", sfirstname.c_str(), slastname.c_str(), screditcard.c_str(), sexpire_month.c_str(), sexpire_year.c_str(), scv2.c_str(), saddress1.c_str(), saddress2.c_str(), scity.c_str(), sprovince.c_str(), spostalcode.c_str(), scountry.c_str(), email.c_str()); request->setText((const char*)ccplain); delete[] ccplain; } request->performRequest = [this, request]() { const char* ccplain = request->getText(); return client->creditcardstore(ccplain); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::creditCardQuerySubscriptions(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_QUERY_SUBSCRIPTIONS, listener); request->performRequest = [this]() { client->creditcardquerysubscriptions(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::creditCardCancelSubscriptions(const char* reason, const char* id, int canContact, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS, listener); request->setText(reason); request->setName(id); request->setNumDetails(canContact); request->performRequest = [this, request]() { CommandCreditCardCancelSubscriptions::CancelSubscription cancelSubscription{ request->getText(), // reason request->getName(), // id request->getNumDetails(), // canContact }; client->creditcardcancelsubscriptions(cancelSubscription); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::creditCardCancelSubscriptions(const MegaCancelSubscriptionReasonList* reasons, const char* id, int canContact, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREDIT_CARD_CANCEL_SUBSCRIPTIONS, listener); request->setMegaCancelSubscriptionReasons(reasons ? reasons->copy() : nullptr); request->setName(id); request->setNumDetails(canContact); request->performRequest = [this, request]() { vector<pair<string, string>> reasons; if (const MegaCancelSubscriptionReasonList* megaReasons = request->getMegaCancelSubscriptionReasons()) { for (size_t i = 0; i < megaReasons->size(); ++i) { const MegaCancelSubscriptionReason* r = megaReasons->get(i); assert(r && r->text() && r->position()); string text{(r && r->text()) ? r->text() : ""}; string position{(r && r->position()) ? r->position() : ""}; reasons.emplace_back(std::move(text), std::move(position)); } } CommandCreditCardCancelSubscriptions::CancelSubscription cancelSubscription{ std::move(reasons), request->getName(), // id request->getNumDetails(), // canContact }; client->creditcardcancelsubscriptions(cancelSubscription); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPaymentMethods(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PAYMENT_METHODS, listener); request->performRequest = [this]() { client->getpaymentmethods(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::submitFeedback(int rating, const char* comment, bool transferFeedback, int transferType, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUBMIT_FEEDBACK, listener); request->setText(comment); request->setNumber(rating); request->setFlag(transferFeedback); request->setParamType(transferType); request->performRequest = [this, request]() { const int rating{static_cast<int>(request->getNumber())}; if (rating < 1 || rating > 5) { LOG_err << "Request (TYPE_SUBMIT_FEEDBACK). Invalid rating: " << rating; return API_EARGS; } const int transferType{request->getParamType()}; if (transferType < MegaApi::TRANSFER_STATS_DOWNLOAD || transferType > MegaApi::TRANSFER_STATS_MAX) { LOG_err << "Request (TYPE_SUBMIT_FEEDBACK). Invalid transferType: " << transferType; return API_EARGS; } const bool transferFeedback{request->getFlag()}; const char* comment{request->getText()}; sendUserfeedback(rating, comment, transferFeedback, transferType); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::sendEvent(int eventType, const char* message, bool addJourneyId, const char* viewId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_EVENT, listener); request->setNumber(eventType); request->setText(message); request->setFlag(addJourneyId); request->setSessionKey(viewId); request->performRequest = [this, request]() { // See https://confluence.developers.mega.co.nz/display/STS/Event+Ranges static const std::vector<std::pair<int, int>> excluded = { { 0, 98000}, // Below MEGAproxy. { 99600, 100000}, // WebClient range. {100000, 300000}, // API extended range. {500000, 600000}, // WebClient extended range. {750000, 800000}, // API extended range. {900000, INT_MAX} // Unassigned. }; // excluded const char *text = request->getText(); // No message. if (!text) return API_EARGS; int number = int(request->getNumber()); // Is the event in an acceptable range? for (auto& e : excluded) { // Events in a disallowed range. if (number >= e.first && number < e.second) return API_EARGS; } const char* viewId = request->getSessionKey(); bool addJourneyId = request->getFlag(); client->sendevent(number, text, viewId, addJourneyId); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::createSupportTicket(const char* message, int type, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SUPPORT_TICKET, listener); request->setParamType(type); request->setText(message); request->performRequest = [this, request]() { int type = request->getParamType(); const char *message = request->getText(); if ((type < 0 || type > 10) || !message) { return API_EARGS; } client->supportticket(message, type); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getUserData(MegaRequestPrivate* request) { const char* email = request->getEmail(); if (request->getFlag() && !email) { return API_EARGS; } if(!request->getFlag()) { client->getuserdata(client->reqtag); } else { client->getpubkey(email); } return API_OK; } void MegaApiImpl::sendDevCommand(const char* command, const char* email, long long quota, int businessStatus, int userStatus, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_DEV_COMMAND, listener); request->setName(command); request->setEmail(email); request->setTotalBytes(quota); request->setAccess(businessStatus); request->setNumDetails(userStatus); request->performRequest = [this, request]() { const char *command = request->getName(); if (!command) { return API_EARGS; } const char* email = request->getEmail(); long long q = request->getTotalBytes(); int bs = request->getAccess(); int us = request->getNumDetails(); bool isSalSubcmd = !strcmp(command, "sal"); bool isOdqSubcmd = !strcmp(command, "aodq"); bool isTqSubcmd = !strcmp(command, "tq"); bool isBsSubcmd = !strcmp(command, "bs"); bool isUsSubcmd = !strcmp(command, "us"); bool isFrSubcmd = !strcmp(command, "fr"); // force reload via -6 on sc channel if (!isOdqSubcmd && !isTqSubcmd && !isBsSubcmd && !isUsSubcmd && !isFrSubcmd && !isSalSubcmd) { return API_EARGS; } if (isTqSubcmd) { if (q < 0) { return API_EARGS; } } else if (isBsSubcmd) { if (bs < -1 || bs > 2) { return API_EARGS; } } else if (isUsSubcmd) { if (us == 1 || us < 0 || us > 9) { return API_EARGS; } } client->senddevcommand(command, email, q, bs, us); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::killSession(MegaHandle sessionHandle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_KILL_SESSION, listener); request->setNodeHandle(sessionHandle); request->performRequest = [this, request]() { MegaHandle handle = request->getNodeHandle(); if (handle == INVALID_HANDLE) { client->killallsessions(); } else { client->killsession(handle); } return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getSessionTransferURL(const char* path, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SESSION_TRANSFER_URL, listener); request->setText(path); request->performRequest = [this, request]() { error e = client->copysession(); if (e == API_ENOENT) // no session to copy because not logged in { string url = MegaClient::getMegaURL() + "/#"; auto path = request->getText(); if (path) url.append(path); request->setLink(url.c_str()); e = API_OK; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); return e; } return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resendVerificationEmail(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESEND_VERIFICATION_EMAIL, listener); request->performRequest = [this]() { client->resendverificationemail(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resetSmsVerifiedPhoneNumber(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RESET_SMS_VERIFIED_NUMBER, listener); request->performRequest = [this]() { client->resetSmsVerifiedPhoneNumber(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::cleanRubbishBin(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CLEAN_RUBBISH_BIN, listener); request->performRequest = [this]() { client->cleanrubbishbin(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setProxySettings(MegaProxy* proxySettings, MegaRequestListener* listener) { Proxy* localProxySettings = new Proxy(); localProxySettings->setProxyType(proxySettings->getProxyType()); string url; if (proxySettings->getProxyURL()) url = proxySettings->getProxyURL(); string localurl; #if defined(_WIN32) localurl = url; #else LocalPath::path2local(&url, &localurl); #endif localProxySettings->setProxyURL(localurl); if (proxySettings->credentialsNeeded()) { string username; if (proxySettings->getUsername()) username = proxySettings->getUsername(); string localusername; #if defined(_WIN32) localusername = username; #else LocalPath::path2local(&username, &localusername); #endif string password; if (proxySettings->getPassword()) password = proxySettings->getPassword(); string localpassword; #if defined(_WIN32) localpassword = password; #else LocalPath::path2local(&password, &localpassword); #endif localProxySettings->setCredentials(localusername, localpassword); } MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PROXY, listener); request->setProxy(localProxySettings); request->performRequest = [this, request]() { auto proxy = makeUniqueFrom(request->getProxy()); httpio->setproxy(*proxy); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getLastAvailableVersion(const char* /*anyAppKey*/, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_APP_VERSION, listener); request->performRequest = []() { return API_EACCESS; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getLocalSSLCertificate(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_LOCAL_SSL_CERT, listener); request->performRequest = [this]() { client->getlocalsslcertificate(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::queryDNS(const char* hostname, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_DNS, listener); request->setName(hostname); request->performRequest = [this, request]() { const char *hostname = request->getName(); if (!hostname) { return API_EARGS; } client->dnsrequest(hostname); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::downloadFile(const char* url, const char* dstpath, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DOWNLOAD_FILE, listener); request->setLink(url); request->setFile(dstpath); request->performRequest = [this, request]() { const char *url = request->getLink(); const char *file = request->getFile(); if (!url || !file) { return API_EARGS; } client->httprequest(url, METHOD_GET, true); return API_OK; }; requestQueue.push(request); waiter->notify(); } #ifdef ENABLE_CHAT void MegaApiImpl::createChat(bool group, bool publicchat, MegaTextChatPeerList* peers, const MegaStringMap* userKeyMap, const char* title, bool meetingRoom, int chatOptions, const MegaScheduledMeeting* scheduledMeeting, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_CREATE, listener); request->setFlag(group); request->setAccess(publicchat ? 1 : 0); request->setMegaTextChatPeerList(peers); request->setText(title); request->setMegaStringMap(userKeyMap); request->setNumber(meetingRoom); request->setParamType(chatOptions); if (scheduledMeeting) { std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance()); l->insert(scheduledMeeting->copy()); request->setMegaScheduledMeetingList(l.get()); } request->performRequest = [this, request]() { MegaTextChatPeerList *chatPeers = request->getMegaTextChatPeerList(); bool group = request->getFlag(); const char *title = request->getText(); bool publicchat = (request->getAccess() == 1); MegaStringMap* userKeyMap = request->getMegaStringMap(); int numPeers = chatPeers ? chatPeers->size() : 0; const string_map *uhkeymap = NULL; if(publicchat) { if (!group || !userKeyMap || (userKeyMap->size() != numPeers + 1)) // includes our own key { return API_EARGS; } uhkeymap = ((MegaStringMapPrivate*)userKeyMap)->getMap(); } else { if (!group && numPeers > 1) { return API_EACCESS; } } bool meetingRoom = static_cast<bool>(request->getNumber()); if (!group && request->getMegaStringList()) { return API_EARGS; } MegaScheduledMeetingPrivate* schedMeeting = nullptr; if (request->getMegaScheduledMeetingList() && request->getMegaScheduledMeetingList()->size() == 1) { schedMeeting = static_cast<MegaScheduledMeetingPrivate*>(request->getMegaScheduledMeetingList()->at(0)); } const userpriv_vector* userpriv; if (numPeers) { userpriv = ((MegaTextChatPeerListPrivate*)chatPeers)->getList(); // if 1:1 chat, peer is enforced to be moderator too if (!group && numPeers && userpriv->at(0).second != PRIV_MODERATOR) { ((MegaTextChatPeerListPrivate*)chatPeers) ->setPeerPrivilege(userpriv->at(0).first, PRIV_MODERATOR); } } else { userpriv = nullptr; } client->createChat(group, publicchat, userpriv, uhkeymap, title, meetingRoom, request->getParamType() /*chat options value*/, schedMeeting ? schedMeeting->scheduledMeeting() : nullptr); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setChatOption(MegaHandle chatid, int option, bool enabled, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_CHAT_OPTIONS, listener); request->setNodeHandle(chatid); request->setAccess(option); request->setFlag(enabled); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); if (chatid == INVALID_HANDLE) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat* chat = it->second; if (!chat->getGroup()) { return API_EARGS; } client->queueCommand(new CommandSetChatOptions( client, chatid, request->getAccess() /*option*/, request->getFlag() /*enabled*/, [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::inviteToChat(MegaHandle chatid, MegaHandle uh, int privilege, bool openMode, const char* unifiedKey, const char* title, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_INVITE, listener); request->setNodeHandle(chatid); request->setParentHandle(uh); request->setAccess(privilege); request->setText(title); request->setFlag(openMode); request->setSessionKey(unifiedKey); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle uh = request->getParentHandle(); int access = request->getAccess(); const char *title = request->getText(); bool publicMode = request->getFlag(); const char *unifiedKey = request->getSessionKey(); if (publicMode && !unifiedKey) { LOG_err << "Request (TYPE_CHAT_INVITE). Invalid unified key for Public chat: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_EINCOMPLETE; } if (chatid == INVALID_HANDLE || uh == INVALID_HANDLE) { LOG_err << "Request (TYPE_CHAT_INVITE). Invalid user handle: " << Base64Str<MegaClient::USERHANDLE>(uh) << ", or chatid: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_ENOENT; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { LOG_err << "Request (TYPE_CHAT_INVITE). chatroom not found: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_ENOENT; } TextChat *chat = it->second; if (chat->publicChat() != publicMode) { LOG_err << "Request (TYPE_CHAT_INVITE). " << (chat->publicChat() ? "Public chat mode" : "Private chat mode") << " ,unexpected for chat: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_EACCESS; } // new participants of private chats require the title to be encrypted to them if (!chat->publicChat() && (!chat->getTitle().empty() && (!title || title[0] == '\0'))) { LOG_err << "Request (TYPE_CHAT_INVITE). Invalid title for chat: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_EINCOMPLETE; } if (!chat->getGroup()) { LOG_err << "Request (TYPE_CHAT_INVITE). Invalid chat (1on1): " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_EACCESS; } if (chat->getOwnPrivileges() < PRIV_MODERATOR) { ChatOptions chatOptions(static_cast<ChatOptions_t>(chat->getChatOptions())); if (chat->getOwnPrivileges() < PRIV_STANDARD || !chatOptions.openInvite()) { // only allowed moderators or participants with standard permissions just if openInvite is enabled LOG_err << "Request (TYPE_CHAT_INVITE). Insufficient permissions to perform this action, for chat: " << Base64Str<MegaClient::USERHANDLE>(chatid); return API_EACCESS; } } client->inviteToChat(chatid, uh, access, unifiedKey, title); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeFromChat(MegaHandle chatid, MegaHandle uh, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_REMOVE, listener); request->setNodeHandle(chatid); if (uh != INVALID_HANDLE) // if not provided, it removes oneself from the chat { request->setParentHandle(uh); } request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle uh = request->getParentHandle(); if (chatid == INVALID_HANDLE) { return API_ENOENT; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; // user is optional. If not provided, command apply to own user if (uh != INVALID_HANDLE) { if (!chat->getGroup() || (uh != client->me && chat->getOwnPrivileges() != PRIV_MODERATOR)) { return API_EACCESS; } client->removeFromChat(chatid, uh); } else { request->setParentHandle(client->me); client->removeFromChat(chatid, client->me); } return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getUrlChat(MegaHandle chatid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_URL, listener); request->setNodeHandle(chatid); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); if (chatid == INVALID_HANDLE) { return API_EARGS; } client->getUrlChat(chatid); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::grantAccessInChat(MegaHandle chatid, MegaNode* n, MegaHandle uh, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_GRANT_ACCESS, listener); request->setParentHandle(chatid); request->setNodeHandle(n->getHandle()); char uid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid); uid[11] = 0; request->setEmail(uid); request->performRequest = [this, request]() { handle chatid = request->getParentHandle(); handle h = request->getNodeHandle(); const char *uid = request->getEmail(); if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || !uid) { return API_ENOENT; } client->grantAccessInChat(chatid, h, uid); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeAccessInChat(MegaHandle chatid, MegaNode* n, MegaHandle uh, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_REMOVE_ACCESS, listener); request->setParentHandle(chatid); request->setNodeHandle(n->getHandle()); char uid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, uid); uid[11] = 0; request->setEmail(uid); request->performRequest = [this, request]() { handle chatid = request->getParentHandle(); handle h = request->getNodeHandle(); const char *uid = request->getEmail(); if (chatid == INVALID_HANDLE || h == INVALID_HANDLE || !uid) { return API_ENOENT; } client->removeAccessInChat(chatid, h, uid); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::updateChatPermissions(MegaHandle chatid, MegaHandle uh, int privilege, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS, listener); request->setNodeHandle(chatid); request->setParentHandle(uh); request->setAccess(privilege); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle uh = request->getParentHandle(); int access = request->getAccess(); if (chatid == INVALID_HANDLE || uh == INVALID_HANDLE) { return API_ENOENT; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (!chat->getGroup() || chat->getOwnPrivileges() != PRIV_MODERATOR) { return API_EACCESS; } client->updateChatPermissions(chatid, uh, access); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::truncateChat(MegaHandle chatid, MegaHandle messageid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_TRUNCATE, listener); request->setNodeHandle(chatid); request->setParentHandle(messageid); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); handle messageid = request->getParentHandle(); if (chatid == INVALID_HANDLE || messageid == INVALID_HANDLE) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (chat->getOwnPrivileges() != PRIV_MODERATOR) { return API_EACCESS; } client->truncateChat(chatid, messageid); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setChatTitle(MegaHandle chatid, const char* title, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_SET_TITLE, listener); request->setNodeHandle(chatid); request->setText(title); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); const char *title = request->getText(); if (chatid == INVALID_HANDLE || title == NULL) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (!chat->getGroup() || chat->getOwnPrivileges() != PRIV_MODERATOR) { return API_EACCESS; } client->setChatTitle(chatid, title); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getChatPresenceURL(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_PRESENCE_URL, listener); request->performRequest = [this]() { client->getChatPresenceUrl(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::registerPushNotification(int deviceType, const char* token, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REGISTER_PUSH_NOTIFICATION, listener); request->setNumber(deviceType); request->setText(token); request->performRequest = [this, request]() { int deviceType = int(request->getNumber()); const char* token = request->getText(); if ((deviceType != MegaApi::PUSH_NOTIFICATION_ANDROID && deviceType != MegaApi::PUSH_NOTIFICATION_IOS_VOIP && deviceType != MegaApi::PUSH_NOTIFICATION_IOS_STD && deviceType != MegaApi::PUSH_NOTIFICATION_ANDROID_HUAWEI) || token == NULL) { return API_EARGS; } client->registerPushNotification(deviceType, token); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::archiveChat(MegaHandle chatid, int archive, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_ARCHIVE, listener); request->setNodeHandle(chatid); request->setFlag(archive != 0); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); bool archive = request->getFlag(); if (chatid == INVALID_HANDLE) { return API_ENOENT; } client->archiveChat(chatid, archive); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setChatRetentionTime(MegaHandle chatid, unsigned period, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_RETENTION_TIME, listener); request->setNodeHandle(chatid); request->setTotalBytes(period); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); unsigned period = static_cast <unsigned>(request->getTotalBytes()); if (chatid == INVALID_HANDLE) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (chat->getOwnPrivileges() != PRIV_MODERATOR) { return API_EACCESS; } client->setchatretentiontime(chatid, period); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_chatStats(MegaRequestPrivate* request) { const char* json = request->getName(); if (!json) { return API_EARGS; } int port = int(request->getNumber()); if (port < 0 || port > 65535) { return API_EARGS; } int type = request->getParamType(); if (type == 1) { client->sendchatstats(json, port); } else if (type == 2) { handle userid = request->getNodeHandle(); if (userid == UNDEF) { return API_EARGS; } handle callid = request->getParentHandle(); client->sendchatlogs(json, userid, callid, port); } else { return API_EARGS; } return API_OK; } void MegaApiImpl::requestRichPreview(const char* url, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RICH_LINK, listener); request->setLink(url); request->performRequest = [this, request]() { const char *url = request->getLink(); if (!url) { return API_EARGS; } client->richlinkrequest(url); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::chatLinkHandle(MegaHandle chatid, bool del, bool createifmissing, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_LINK_HANDLE, listener); request->setNodeHandle(chatid); request->setFlag(del); request->setAccess(createifmissing ? 1 : 0); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); bool del = request->getFlag(); bool createifmissing = request->getAccess() != 0; if (del && createifmissing) { return API_EARGS; } if (chatid == INVALID_HANDLE) { return API_ENOENT; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (!chat->getGroup() || !chat->publicChat() || chat->getOwnPrivileges() == PRIV_RM || ((del || createifmissing) && chat->getOwnPrivileges() != PRIV_MODERATOR)) { return API_EACCESS; } client->chatlink(chatid, del, createifmissing); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getChatLinkURL(MegaHandle publichandle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_LINK_URL, listener); request->setNodeHandle(publichandle); request->performRequest = [this, request]() { MegaHandle publichandle = request->getNodeHandle(); if (publichandle == INVALID_HANDLE) { return API_ENOENT; } client->chatlinkurl(publichandle); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::chatLinkClose(MegaHandle chatid, const char* title, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_PRIVATE_MODE, listener); request->setNodeHandle(chatid); request->setText(title); request->performRequest = [this, request]() { MegaHandle chatid = request->getNodeHandle(); const char *title = request->getText(); if (chatid == INVALID_HANDLE) { return API_ENOENT; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat *chat = it->second; if (!chat->publicChat()) { return API_EEXIST; } if (!chat->getGroup() || chat->getOwnPrivileges() != PRIV_MODERATOR) { return API_EACCESS; } if (!chat->getTitle().empty() && (!title || title[0] == '\0')) { return API_EARGS; } client->chatlinkclose(chatid, title); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::chatLinkJoin(MegaHandle publichandle, const char* unifiedkey, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_AUTOJOIN_PUBLIC_CHAT, listener); request->setNodeHandle(publichandle); request->setSessionKey(unifiedkey); request->performRequest = [this, request]() { MegaHandle publichandle = request->getNodeHandle(); const char *unifiedkey = request->getSessionKey(); if (publichandle == INVALID_HANDLE) { return API_ENOENT; } if (unifiedkey == NULL) { return API_EINCOMPLETE; } client->chatlinkjoin(publichandle, unifiedkey); return API_OK; }; requestQueue.push(request); waiter->notify(); } #endif void MegaApiImpl::whyAmIBlocked(bool logout, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_WHY_AM_I_BLOCKED, listener); request->setFlag(logout); request->performRequest = [this]() { client->whyamiblocked(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::contactLinkCreate(bool renew, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_CREATE, listener); request->setFlag(renew); request->performRequest = [this, request]() { client->contactlinkcreate(request->getFlag()); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::contactLinkQuery(MegaHandle h, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_QUERY, listener); request->setNodeHandle(h); request->performRequest = [this, request]() { handle h = request->getNodeHandle(); if (ISUNDEF(h)) { return API_EARGS; } client->contactlinkquery(h); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::contactLinkDelete(MegaHandle h, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CONTACT_LINK_DELETE, listener); request->setNodeHandle(h); request->performRequest = [this, request]() { handle h = request->getNodeHandle(); client->contactlinkdelete(h); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::keepMeAlive(int type, bool enable, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_KEEP_ME_ALIVE, listener); request->setParamType(type); request->setFlag(enable); request->performRequest = [this, request]() { int type = request->getParamType(); bool enable = request->getFlag(); if (type != MegaApi::KEEP_ALIVE_CAMERA_UPLOADS) { return API_EARGS; } client->keepmealive(type, enable); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPSA(bool urlSupported, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_PSA, listener); request->setFlag(urlSupported); request->performRequest = [this, request]() { client->getpsa(request->getFlag()); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getFolderInfo(MegaNode* node, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FOLDER_INFO, listener); if (node) { request->setNodeHandle(node->getHandle()); } request->performRequest = [this, request]() { MegaHandle h = request->getNodeHandle(); if (ISUNDEF(h)) { return API_EARGS; } std::shared_ptr<Node> node = client->nodebyhandle(h); if (!node) { return API_ENOENT; } if (node->type == FILENODE) { return API_EARGS; } NodeCounter nc = node->getCounter(); std::unique_ptr<MegaFolderInfo> folderInfo = std::make_unique<MegaFolderInfoPrivate>( (int)nc.files, node->type == FOLDERNODE ? ((int)nc.folders - 1) : (int)nc.folders, (int)nc.versions, nc.storage, nc.versionStorage); request->setMegaFolderInfo(folderInfo.get()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getAchievements(MegaRequestPrivate* request) { if (request->getFlag()) { client->getmegaachievements(request->getAchievementsDetails()); } else { client->getaccountachievements(request->getAchievementsDetails()); } return API_OK; } void MegaApiImpl::acknowledgeUserAlerts(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_USERALERT_ACKNOWLEDGE, listener); request->performRequest = [this]() { client->acknowledgeuseralerts(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getPublicLinkInformation(const char* megaFolderLink, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUBLIC_LINK_INFORMATION, listener); request->setLink(megaFolderLink); request->performRequest = [this, request]() { const char *link = request->getLink(); if (!link) { return API_EARGS; } handle h = UNDEF; byte folderkey[FOLDERNODEKEYLENGTH]; error e = client->parsepubliclink(link, h, folderkey, TypeOfLink::FOLDER); if (e == API_OK) { request->setNodeHandle(h); Base64Str<FOLDERNODEKEYLENGTH> folderkeyB64(folderkey); request->setPrivateKey(folderkeyB64.chars); client->getpubliclinkinfo(h); } return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getFileAttributeUploadURL(MegaHandle nodehandle, int64_t fullFileSize, int faType, bool forceSSL, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_FA_UPLOAD_URL, listener); request->setNodeHandle(nodehandle); request->setNumber(fullFileSize); request->setFlag(forceSSL); request->setParamType(faType); request->performRequest = [this, request]() { bool getIp = true; uint64_t nodeHandle = request->getNodeHandle(); int intFaType = request->getParamType(); assert(intFaType >= 0 && (intFaType < (1 << (sizeof(fatype)*8)))); // Value of intFaType <= (2^(fatype_numbits) - 1) fatype faType = static_cast<fatype>(intFaType); // if the assert above is true, int should fit fine // into a fatype (uint16_t) bool forceSSL = request->getFlag(); size_t fullSize = static_cast<size_t>(request->getNumber()); NodeOrUploadHandle nuh(NodeHandle().set6byte(nodeHandle)); client->queueCommand(new CommandPutFA( std::move(nuh), faType, forceSSL, -1, fullSize, getIp, [this, request](Error e, const std::string& url, const std::vector<std::string>& ips) { assert(e != API_OK || !url.empty()); if (e == API_OK && !url.empty()) { request->setName(url.c_str()); if (!ips.empty()) { request->setLink(ips.at(0).c_str()); } if (ips.size() > 1) { request->setText(ips.at(1).c_str()); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getBackgroundUploadURL(MegaRequestPrivate* request) { //if not using MegaBackgroundMediaUpload, ask for IPs bool getIp = !request->getMegaBackgroundMediaUploadPtr(); client->queueCommand(new CommandGetPutUrl( request->getNumber(), request->getFlag(), getIp, [this, request](Error e, const std::string& url, const std::vector<std::string>& ips) { assert(e != API_OK || !url.empty()); if (e == API_OK && !url.empty()) { if (request->getMegaBackgroundMediaUploadPtr()) { MegaBackgroundMediaUploadPrivate* mu = static_cast<MegaBackgroundMediaUploadPrivate*>( request->getMegaBackgroundMediaUploadPtr()); mu->url = url; } else { request->setName(url.c_str()); if (!ips.empty()) { request->setLink(ips.at(0).c_str()); } if (ips.size() > 1) { request->setText(ips.at(1).c_str()); } } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; } error MegaApiImpl::performRequest_completeBackgroundUpload(MegaRequestPrivate* request) { MegaBackgroundMediaUploadPrivate* bgMediaUpload = static_cast<MegaBackgroundMediaUploadPrivate*>(request->getMegaBackgroundMediaUploadPtr()); const char * base64fileKey = request->getPrivateKey(); if (!base64fileKey && !bgMediaUpload) { return API_EINCOMPLETE; } const char* utf8Name = request->getName(); auto parentHandle = NodeHandle().set6byte(request->getParentHandle()); const char *uploadToken = request->getSessionKey(); const char* fingerprintOriginal = request->getPassword(); const char* fingerprint = request->getNewPassword(); if (!fingerprint || !utf8Name || !uploadToken || parentHandle.isUndef()) { return API_EINCOMPLETE; } UploadToken ulToken; auto binTokSize = Base64::atob(uploadToken, &ulToken[0], sizeof(ulToken)); if (binTokSize != 36 || binTokSize != sizeof(ulToken)) { LOG_err << "Invalid upload token: " << uploadToken; return API_EARGS; } byte *theFileKey; std::unique_ptr<byte[]> filekey; if (bgMediaUpload) //using MegaBackgroundMediaUploadPrivate key { theFileKey = bgMediaUpload->filekey; } else //app driven upload (key provided as parameter) { theFileKey = new byte[FILENODEKEYLENGTH]; auto binFileKeySize = Base64::atob(base64fileKey, theFileKey, FILENODEKEYLENGTH); filekey.reset(theFileKey); if (binFileKeySize != FILENODEKEYLENGTH) { LOG_err << "Invalid file key"; return API_EARGS; } } std::shared_ptr<Node> parentNode = client->nodeByHandle(parentHandle); if (!parentNode) { LOG_err << "Parent node doesn't exist anymore"; return API_ENOENT; } const string megafingerprint = MegaNodePrivate::removeAppPrefixFromFingerprint(fingerprint); if (megafingerprint.empty()) { LOG_err << "Bad fingerprint"; return API_EARGS; } std::function<error(string&)> addFileAttrsFunc; std::function<error(AttrMap& attrs)> addNodeAttrsFunc; if (bgMediaUpload) { addFileAttrsFunc = [bgMediaUpload](string& fileattributes) { appendFileAttribute(fileattributes, GfxProc::THUMBNAIL, bgMediaUpload->thumbnailFA); appendFileAttribute(fileattributes, GfxProc::PREVIEW, bgMediaUpload->previewFA); #ifdef USE_MEDIAINFO if (bgMediaUpload->mediaproperties.isPopulated()) { if (!fileattributes.empty()) { fileattributes.append("/"); } fileattributes.append(MediaProperties::encodeMediaPropertiesAttributes( bgMediaUpload->mediaproperties, (uint32_t*)(bgMediaUpload->filekey + FILENODEKEYLENGTH / 2))); } #endif return API_OK; }; addNodeAttrsFunc = [this, bgMediaUpload](AttrMap& attrs) { error e = API_OK; int lat, lon; encodeCoordinates(bgMediaUpload->latitude, bgMediaUpload->longitude, lat, lon); attr_map updates; if (API_OK != (e = updateAttributesMapWithCoordinates(updates, lat, lon, bgMediaUpload->unshareableGPS, client))) { return e; } attrs.applyUpdates(updates); return e; }; } vector<NewNode> newnodes(1); NewNode* newnode = &newnodes[0]; error e = client->putnodes_prepareOneFile(newnode, parentNode.get(), utf8Name, ulToken, theFileKey, megafingerprint.c_str(), fingerprintOriginal, std::move(addNodeAttrsFunc), std::move(addFileAttrsFunc)); if (e != API_OK) { return e; } Pitag pitag{PitagPurpose::Upload, PitagTrigger::Camera, PitagNodeType::File, PitagTarget::CloudDrive, PitagImportSource::NotApplicable}; const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching( [](const Node& n) { return n.inshare != nullptr; }); pitag.target = inIncomingShare ? PitagTarget::IncomingShare : pitag.target; client->queueCommand(new CommandPutNodes(client, parentHandle, NULL, UseLocalVersioningFlag, std::move(newnodes), request->getTag(), PUTNODES_APP, nullptr, nullptr, false, {}, // customerIpPort pitag)); return e; } error MegaApiImpl::performRequest_verifyCredentials(MegaRequestPrivate* request) { handle uh = request->getNodeHandle(); bool isReset = request->getFlag(); std::function<void(Error)> completion = [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; if (isReset) { return client->resetCredentials(uh, std::move(completion)); } else { return client->verifyCredentials(uh, std::move(completion)); } } void MegaApiImpl::sendSMSVerificationCode(const char* phoneNumber, MegaRequestListener* listener, bool reverifying_whitelisted) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SEND_SMS_VERIFICATIONCODE, listener); request->setText(phoneNumber); request->setFlag(reverifying_whitelisted); request->performRequest = [this, request]() { string phoneNumber = request->getText(); bool reverifying_whitelisted = request->getFlag(); return client->smsverificationsend(phoneNumber, reverifying_whitelisted); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::checkSMSVerificationCode(const char* verificationCode, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_SMS_VERIFICATIONCODE, listener); request->setText(verificationCode); request->performRequest = [this, request]() { string code = request->getText(); error e = client->smsverificationcheck(code); // FIXME: if the API returned the new state and the verified phone number in // the response to the code's verification, the following block can be deleted if (e == API_OK) { client->queueCommand(new CommandGetUserData(client, client->reqtag, nullptr)); } return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getCountryCallingCodes(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES, listener); request->performRequest = [this]() { client->queueCommand(new CommandGetCountryCallingCodes{client}); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMiscFlags(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_MISC_FLAGS, listener); request->performRequest = [this]() { if (client->loggedin()) { // it only returns not-user-related flags (ie. server-sider-rubbish scheduler is missing) return API_EACCESS; } client->getmiscflags(); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getBanners(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_BANNERS, listener); request->performRequest = [this]() { client->queueCommand(new CommandGetBanners(client)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::dismissBanner(int id, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DISMISS_BANNER, listener); request->setParamType(id); // banner id request->setNumber(m_time(nullptr)); // timestamp request->performRequest = [this, request]() { client->queueCommand( new CommandDismissBanner(client, request->getParamType(), request->getNumber())); return API_OK; }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_backupPut(MegaRequestPrivate* request) { if (!client->loggedin()) return API_EACCESS; // it requires master key to encrypt data, which is received in response to login NodeHandle remoteNode = NodeHandle().set6byte(request->getNodeHandle()); const char* backupName = request->getName(); const char* localFolder = request->getFile(); int backupType = static_cast<int>(request->getTotalBytes()); BackupType bType = static_cast<BackupType>(backupType); handle backupId = request->getParentHandle(); CommandBackupPut::BackupInfo info; info.backupId = backupId; info.type = bType; info.backupName = backupName ? backupName : ""; info.nodeHandle = remoteNode; info.localFolder = localFolder ? LocalPath::fromAbsolutePath(localFolder) : LocalPath(); info.deviceId = client->getDeviceidHash(); if (info.deviceId.empty()) { LOG_err << "Failed to get Device ID while handling backup " << info.backupName; return API_EARGS; } info.state = CommandBackupPut::SPState(request->getAccess()); info.subState = request->getNumDetails(); bool isNew = request->getFlag(); if (isNew) // Register a new sync/backup { if ((backupType != MegaApi::BACKUP_TYPE_CAMERA_UPLOADS && backupType != MegaApi::BACKUP_TYPE_MEDIA_UPLOADS) || !localFolder || !backupName || remoteNode.isUndef() || !ISUNDEF(backupId)) // new backups don't have a backup id yet { return API_EARGS; } client->queueCommand(new CommandBackupPut(client, info, nullptr)); } else // update an existing sync/backup { if ((backupType != MegaApi::BACKUP_TYPE_CAMERA_UPLOADS && backupType != MegaApi::BACKUP_TYPE_MEDIA_UPLOADS && backupType != MegaApi::BACKUP_TYPE_INVALID) || ISUNDEF(backupId)) // existing backups must have a backup id { return API_EARGS; } client->queueCommand(new CommandBackupPut(client, info, nullptr)); } return API_OK; } void MegaApiImpl::removeBackup(MegaHandle backupId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_REMOVE, listener); request->setParentHandle(backupId); request->performRequest = [this, request]() { client->queueCommand(new CommandBackupRemove( client, request->getParentHandle(), [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeFromBC(MegaHandle backupId, MegaHandle moveDestination, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_REMOVE_MD, listener); request->setParentHandle(backupId); request->setNodeHandle(moveDestination); request->performRequest = [this, request]() { auto finalCompletion = [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; client->removeFromBC(request->getParentHandle(), request->getNodeHandle(), finalCompletion); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::pauseFromBC(MegaHandle backupId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PAUSE_MD, listener); request->setParentHandle(backupId); request->performRequest = [this, request]() { auto finalCompletion = [this, request](const Error& e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; client->updateStateInBC(request->getParentHandle(), CommandBackupPut::TEMPORARY_DISABLED, finalCompletion); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::resumeFromBC(MegaHandle backupId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_RESUME_MD, listener); request->setParentHandle(backupId); request->performRequest = [this, request]() { auto finalCompletion = [this, request](const Error& e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; client->updateStateInBC(request->getParentHandle(), CommandBackupPut::ACTIVE, finalCompletion); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getBackupInfo(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_INFO, listener); request->performRequest = [this, request]() { client->getBackupInfo([this, request](const Error& e, const vector<CommandBackupSyncFetch::Data>& d) { if (e == API_OK) { request->setMegaBackupInfoList(std::make_unique<MegaBackupInfoListPrivate>(d)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::sendBackupHeartbeat(MegaHandle backupId, int status, int progress, int ups, int downs, long long ts, MegaHandle lastNode, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT_HEART_BEAT, listener); request->setParentHandle(backupId); request->setAccess(status); request->setNumDetails(progress); request->setParamType(ups); request->setTransferTag(downs); request->setNumber(ts); request->setNodeHandle(lastNode); request->performRequest = [this, request]() { client->queueCommand(new CommandBackupPutHeartBeat( client, (MegaHandle)request->getParentHandle(), CommandBackupPutHeartBeat::SPHBStatus(request->getAccess()), static_cast<int8_t>(request->getNumDetails()), (uint32_t)request->getParamType(), (uint32_t)request->getTransferTag(), request->getNumber(), request->getNodeHandle(), [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } #ifdef ENABLE_CHAT void MegaApiImpl::startChatCall(MegaHandle chatid, bool notRinging, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_START_CHAT_CALL, listener); request->setNodeHandle(chatid); request->setFlag(notRinging); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); if (chatid == INVALID_HANDLE) { return API_EARGS; } const bool notRinging = request->getFlag(); client->queueCommand(new CommandMeetingStart( client, chatid, notRinging, [request, this](Error e, std::string sfuUrl, handle callid) { if (e == API_OK) { request->setParentHandle(callid); request->setText(sfuUrl.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::joinChatCall(MegaHandle chatid, MegaHandle callid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_JOIN_CHAT_CALL, listener); request->setNodeHandle(chatid); request->setParentHandle(callid); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle callid = request->getParentHandle(); if (chatid == INVALID_HANDLE || callid == INVALID_HANDLE) { return API_EARGS; } client->queueCommand(new CommandMeetingJoin( client, chatid, callid, [request, this](Error e, std::string sfuUrl) { if (e == API_OK) { request->setText(sfuUrl.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::endChatCall(MegaHandle chatid, MegaHandle callid, int reason, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_END_CHAT_CALL, listener); request->setNodeHandle(chatid); request->setParentHandle(callid); request->setAccess(reason); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle callid = request->getParentHandle(); int reason = request->getAccess(); if (chatid == INVALID_HANDLE || callid == INVALID_HANDLE || !client->isValidEndCallReason(reason)) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } TextChat* chat = it->second; if (reason == END_CALL_REASON_BY_MODERATOR && !chat->getGroup()) { return API_EACCESS; } client->queueCommand(new CommandMeetingEnd( client, chatid, callid, reason, [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::ringIndividualInACall(MegaHandle chatid, MegaHandle userid, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_RING_INDIVIDUAL_IN_CALL, listener); request->setNodeHandle(chatid); request->setParentHandle(userid); request->performRequest = [this, request]() { const handle chatid = request->getNodeHandle(); const handle userid = request->getParentHandle(); if (chatid == INVALID_HANDLE || userid == INVALID_HANDLE) { return API_EARGS; } textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } client->queueCommand(new CommandRingUser( client, chatid, userid, [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } #endif void MegaApiImpl::setMyBackupsFolder(const char* localizedName, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_MY_BACKUPS, listener); request->setText(localizedName); request->performRequest = [this, request]() { auto addua_completion = [request, this](Error e) { if (e == API_OK) { const UserAttribute* attribute = client->ownuser()->getAttribute(ATTR_MY_BACKUPS_FOLDER); if (attribute && !attribute->isNotExisting()) { assert(attribute->value().size() == MegaClient::NODEHANDLE); handle h = 0; memcpy(&h, attribute->value().data(), MegaClient::NODEHANDLE); request->setNodeHandle(h); } else { LOG_err << "Invalid value for ATTR_MY_BACKUPS_FOLDER"; e = API_EINTERNAL; } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; return client->setbackupfolder(request->getText(), request->getTag(), addua_completion); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getRecentActionsAsync(unsigned days, unsigned maxnodes, MegaRequestListener* listener) { getRecentActionsAsyncInternal(days, maxnodes, nullptr, listener); } void MegaApiImpl::getRecentActionsAsync(unsigned days, unsigned maxnodes, bool excludeSensitives, MegaRequestListener* listener) { getRecentActionsAsyncInternal(days, maxnodes, &excludeSensitives, listener); } void MegaApiImpl::getRecentActionByIdInternal(const char* id, std::optional<bool> excludeSensitives, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECENT_ACTION_BY_ID, listener); if (id) { request->setText(id); } if (excludeSensitives.has_value()) { request->setFlag(*excludeSensitives); } request->performRequest = [this, request, hasExcludeSensitives = excludeSensitives.has_value()]() { const char* id = request->getText(); recentaction ra; error e; if (hasExcludeSensitives) { const bool excl = request->getFlag(); e = client->getRecentActionById(id, excl, ra); } else { e = client->getRecentActionById(id, ra); } if (e != API_OK) { return e; } recentactions_vector rav; rav.emplace_back(std::move(ra)); std::unique_ptr<MegaRecentActionBucketList> recentActions( new MegaRecentActionBucketListPrivate(rav)); request->setRecentActions(std::move(recentActions)); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getRecentActionById(const char* id, MegaRequestListener* listener) { getRecentActionByIdInternal(id, std::nullopt, listener); } void MegaApiImpl::getRecentActionById(const char* id, bool excludeSensitives, MegaRequestListener* listener) { getRecentActionByIdInternal(id, std::make_optional(excludeSensitives), listener); } void MegaApiImpl::getRecentActionsAsyncInternal(unsigned days, unsigned maxnodes, bool* optExcludeSensitives, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_RECENT_ACTIONS, listener); request->setNumber(days); request->setParamType(static_cast<int>(maxnodes)); if (optExcludeSensitives) request->setFlag(*optExcludeSensitives); request->performRequest = [this, request, withExcludeSensitives = (optExcludeSensitives != nullptr)]() { unsigned maxnodes = static_cast<unsigned>(request->getParamType()); if (maxnodes == 0) { return API_EARGS; } int days = static_cast<int>(request->getNumber()); if (days <= 0) { return API_EARGS; } m_time_t since = m_time() - days * 86400; MegaTimeStamp sinceClearHistory = getRecentClearTimestamp(); since = std::max(since, sinceClearHistory); recentactions_vector v; if (withExcludeSensitives) { bool excludeSensitives = request->getFlag(); v = client->getRecentActions(maxnodes, since, excludeSensitives); } else { v = client->getRecentActions(maxnodes, since); } std::unique_ptr<MegaRecentActionBucketList> recentActions( new MegaRecentActionBucketListPrivate(v)); request->setRecentActions(std::move(recentActions)); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::clearRecentActionHistory(MegaTimeStamp until, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP); request->setNumber(until); request->performRequest = [this, request]() { MegaTimeStamp time = request->getNumber(); if (!MegaClient::isValidMegaTimeStamp(time)) { return API_EARGS; } MegaStringMapPrivate stringMap; string key = "t"; auto b64 = Base64Str<sizeof(time)>(reinterpret_cast<void*>(&time)); stringMap.set(key.c_str(), (Base64::btoa(b64.chars)).c_str()); request->setMegaStringMap(&stringMap); return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } MegaTimeStamp MegaApiImpl::getRecentClearTimestamp() { mega::User* user = client->finduser(client->me); if (user == nullptr) { return MEGA_INVALID_TIMESTAMP; } const UserAttribute* attr = user->getAttribute(ATTR_RECENT_CLEAR_TIMESTAMP); if (attr == nullptr || attr->value().empty()) { return MEGA_INVALID_TIMESTAMP; } std::unique_ptr<string_map> records{tlv::containerToRecords(attr->value(), client->key)}; if (!records || records->empty()) { return MEGA_INVALID_TIMESTAMP; } return formatRecentClearTimestamp(records.get()); } MegaTimeStamp MegaApiImpl::formatRecentClearTimestamp(string_map* records) { MegaTimeStamp recentClearTimestamp = MEGA_INVALID_TIMESTAMP; auto it = records->find("t"); if (it == records->end()) { return recentClearTimestamp; } string value = Base64::atob(it->second); if (value.size() != sizeof(recentClearTimestamp)) { return recentClearTimestamp; } memcpy(&recentClearTimestamp, value.data(), sizeof(recentClearTimestamp)); return recentClearTimestamp; } #ifdef ENABLE_CHAT void MegaApiImpl::createOrUpdateScheduledMeeting(const MegaScheduledMeeting* scheduledMeeting, const char* chatTitle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING, listener); assert(scheduledMeeting); if (scheduledMeeting) { std::unique_ptr<MegaScheduledMeetingList> l(MegaScheduledMeetingList::createInstance()); l->insert(scheduledMeeting->copy()); request->setMegaScheduledMeetingList(l.get()); } request->setText(chatTitle); request->performRequest = [this, request]() { if (!request->getMegaScheduledMeetingList() || request->getMegaScheduledMeetingList()->size() != 1) { return API_EARGS; } MegaScheduledMeetingPrivate* schedMeeting = static_cast<MegaScheduledMeetingPrivate*>(request->getMegaScheduledMeetingList()->at(0)); handle chatid = schedMeeting->chatid(); textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } const char* chatTitle = request->getText(); if (chatTitle && !strlen(chatTitle)) { return API_EARGS; } client->queueCommand(new CommandScheduledMeetingAddOrUpdate( client, schedMeeting->scheduledMeeting(), chatTitle, [request, this](Error e, const ScheduledMeeting* sm) { if (sm) { std::unique_ptr<MegaScheduledMeetingList> l( MegaScheduledMeetingList::createInstance()); l->insert(new MegaScheduledMeetingPrivate(sm)); request->setMegaScheduledMeetingList(l.get()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DEL_SCHEDULED_MEETING, listener); request->setNodeHandle(chatid); request->setParentHandle(schedId); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle schedId = request->getParentHandle(); textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } client->queueCommand(new CommandScheduledMeetingRemove( client, chatid, schedId, [request, this](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fetchScheduledMeeting(MegaHandle chatid, MegaHandle schedId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SCHEDULED_MEETING, listener); request->setNodeHandle(chatid); request->setParentHandle(schedId); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); handle schedId = request->getParentHandle(); textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } client->queueCommand(new CommandScheduledMeetingFetch( client, chatid, schedId, [request, this](Error e, const std::vector<std::unique_ptr<ScheduledMeeting>>* result) { if (result && !result->empty()) { std::unique_ptr<MegaScheduledMeetingList> l( MegaScheduledMeetingList::createInstance()); for (auto const& sm: *result) { l->insert(new MegaScheduledMeetingPrivate(sm.get())); } request->setMegaScheduledMeetingList(l.get()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fetchScheduledMeetingEvents(MegaHandle chatid, MegaTimeStamp since, MegaTimeStamp until, unsigned int count, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SCHEDULED_MEETING_OCCURRENCES, listener); request->setNodeHandle(chatid); request->setNumber(since); request->setTotalBytes(until); request->setTransferredBytes(count); request->performRequest = [this, request]() { handle chatid = request->getNodeHandle(); m_time_t since = request->getNumber(); m_time_t until = request->getTotalBytes(); unsigned int count = static_cast<unsigned int>(request->getTransferredBytes()); textchat_map::iterator it = client->chats.find(chatid); if (it == client->chats.end()) { return API_ENOENT; } client->queueCommand(new CommandScheduledMeetingFetchEvents( client, chatid, since, until, count, true /*byDemand*/, [request, this](Error e, const std::vector<std::unique_ptr<ScheduledMeeting>>* result) { if (result && !result->empty()) { std::unique_ptr<MegaScheduledMeetingList> l( MegaScheduledMeetingList::createInstance()); for (auto const& sm: *result) { l->insert(new MegaScheduledMeetingPrivate(sm.get())); } request->setMegaScheduledMeetingList(l.get()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } #endif unsigned long long MegaApiImpl::getNumNodes() { return client->totalNodes.load(); } unsigned long long MegaApiImpl::getAccurateNumNodes() { SdkMutexGuard g(sdkMutex); return client->totalNodes.load(); } void MegaApiImpl::setLRUCacheSize(unsigned long long size) { client->mNodeManager.setCacheLRUMaxSize(size); } unsigned long long MegaApiImpl::getNumNodesAtCacheLRU() const { return client->mNodeManager.getNumNodesAtCacheLRU(); } bool MegaApiImpl::isSyncStalled() { // no need to lock sdkMutex for these simple flags #ifdef ENABLE_SYNC if (receivedStallFlag.load()) return true; if (receivedNameConflictsFlag.load()) return true; #endif return false; } bool MegaApiImpl::isSyncStalledChanged() { // no need to lock sdkMutex for these simple flags #ifdef ENABLE_SYNC if (receivedTotalStallsFlag.load()) return true; if (receivedTotalNameConflictsFlag.load()) return true; #endif return false; } int MegaApiImpl::isWaiting() { // no need to lock sdkMutex for these simple flags if (waitingRequest) { LOG_debug << "SDK waiting for a request. Reason: " << waitingRequest; } return waitingRequest; } void MegaApiImpl::lockMutex() { sdkMutex.lock(); } void MegaApiImpl::unlockMutex() { sdkMutex.unlock(); } bool MegaApiImpl::tryLockMutexFor(long long time) { if (time <= 0) { return sdkMutex.try_lock(); } else { return sdkMutex.try_lock_for(std::chrono::milliseconds(time)); } } void MegaApiImpl::setBackup(int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT, listener); request->setTotalBytes(backupType); request->setNodeHandle(targetNode); request->setFile(localFolder); request->setName(backupName); request->setAccess(state); request->setNumDetails(subState); request->setFlag(true); // indicates it's a new backup request->performRequest = [this, request]() { return performRequest_backupPut(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::updateBackup(MegaHandle backupId, int backupType, MegaHandle targetNode, const char* localFolder, const char* backupName, int state, int subState, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_BACKUP_PUT, listener); request->setParentHandle(backupId); if (backupType != BackupType::INVALID) { request->setTotalBytes(backupType); } if (targetNode != UNDEF) { request->setNodeHandle(targetNode); } if (localFolder) { request->setFile(localFolder); } if (backupName) { request->setName(backupName); } if (state >= 0) { request->setAccess(state); } if (subState >= 0) { request->setNumDetails(subState); } request->performRequest = [this, request]() { return performRequest_backupPut(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fetchAds(int adFlags, MegaStringList *adUnits, MegaHandle publicHandle, MegaRequestListener *listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_ADS, listener); request->setNumber(adFlags); request->setMegaStringList(adUnits); request->setNodeHandle(publicHandle); request->performRequest = [this, request]() { int flags = int(request->getNumber()); MegaStringListPrivate* ads = static_cast<MegaStringListPrivate*>(request->getMegaStringList()); if (flags < MegaApi::ADS_DEFAULT || flags > MegaApi::ADS_FLAG_IGNORE_ROLLOUT || !ads || !ads->size()) { return API_EARGS; } client->queueCommand(new CommandFetchAds( client, flags, ads->getVector(), request->getNodeHandle(), [request, this](Error e, string_map value) { if (e == API_OK) { std::unique_ptr<MegaStringMap> stringMap = std::unique_ptr<MegaStringMap>(MegaStringMap::createInstance()); for (const auto& itMap: value) { stringMap->set(itMap.first.c_str(), itMap.second.c_str()); } request->setMegaStringMap(stringMap.get()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::queryAds(int adFlags, MegaHandle publicHandle, MegaRequestListener *listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_QUERY_ADS, listener); request->setNumber(adFlags); request->setNodeHandle(publicHandle); request->performRequest = [this, request]() { int flags = int(request->getNumber()); if (flags < MegaApi::ADS_DEFAULT || flags > MegaApi::ADS_FLAG_IGNORE_ROLLOUT) { return API_EARGS; } client->queueCommand(new CommandQueryAds( client, flags, request->getNodeHandle(), [request, this](Error e, int value) { if (e == API_OK) request->setNumDetails(value); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); })); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setCookieSettings(int settings, MegaRequestListener *listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_COOKIE_SETTINGS); request->setNumDetails(settings); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::setCookieSettings_sendPendingRequests(MegaRequestPrivate* request) { auto tmp = std::to_string(request->getNumDetails()); client->putua(ATTR_COOKIE_SETTINGS, (byte *)tmp.data(), unsigned(tmp.size()), -1, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } void MegaApiImpl::getCookieSettings(MegaRequestListener *listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_COOKIE_SETTINGS); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::getCookieSettings_getua_result(byte* data, unsigned len, MegaRequestPrivate* request) { // make a copy to make sure we don't read too much from a non-null terminated string unique_ptr<char[]> buff(new char[len + 1]); buff[len] = 0; char* startptr = buff.get(); strncpy(startptr, (const char*)data, len); // get the value char* endptr; long value = strtol(startptr, &endptr, 10); // validate the value error e = API_OK; if (endptr == startptr || *endptr != '\0' || value == LONG_MAX || value == LONG_MIN) { value = -1; LOG_err << "Invalid value for Cookie Settings bitmap"; e = API_EINTERNAL; } request->setNumDetails(int(value)); return e; } bool MegaApiImpl::cookieBannerEnabled() { return client->mCookieBannerEnabled; } bool MegaApiImpl::startDriveMonitor() { SdkMutexGuard g(sdkMutex); return client->startDriveMonitor(); } void MegaApiImpl::stopDriveMonitor() { SdkMutexGuard g(sdkMutex); client->stopDriveMonitor(); } bool MegaApiImpl::driveMonitorEnabled() { SdkMutexGuard g(sdkMutex); return client->driveMonitorEnabled(); } void MegaApiImpl::enableRequestStatusMonitor(bool enable) { SdkMutexGuard g(sdkMutex); enable ? client->startRequestStatusMonitor() : client->stopRequestStatusMonitor(); } bool MegaApiImpl::requestStatusMonitorEnabled() { SdkMutexGuard g(sdkMutex); return client->requestStatusMonitorEnabled(); } #ifdef USE_DRIVE_NOTIFICATIONS void MegaApiImpl::drive_presence_changed(bool appeared, const LocalPath& driveRoot) { for (auto it = globalListeners.begin(); it != globalListeners.end(); ++it) { (*it)->onDrivePresenceChanged(api, appeared, driveRoot.platformEncoded().c_str()); } } #endif void MegaApiImpl::addMount(const MegaMount* mount, MegaRequestListener* listener) { assert(listener); assert(mount); auto request = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_ADD_MOUNT); request->setFile(mount->getPath()); request->setListener(listener); auto execute = [this](const fuse::MountInfo& info, MegaRequestPrivate& request) { auto result = client->mFuseService.add(info); auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(&request, std::move(error)); return API_OK; }; request->performRequest = std::bind(std::move(execute), static_cast<const MegaMountPrivate*>(mount)->asInfo(), std::ref(*request)); requestQueue.push(std::move(request)); waiter->notify(); } void MegaApiImpl::disableMount(const char* name, MegaRequestListener* listener, bool remember) { assert(listener); assert(name); auto request = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_DISABLE_MOUNT); request->setFile(name); request->setListener(listener); auto execute = [remember, this](MegaRequestPrivate& request) { using namespace fuse; auto callback = [this](MegaRequestPrivate& request, MountResult result) { auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(&request, std::move(error)); }; std::string name = request.getFile(); client->mFuseService.disable(std::bind(std::move(callback), std::ref(request), std::placeholders::_1), name, remember); return API_OK; }; request->performRequest = std::bind(std::move(execute), std::ref(*request)); requestQueue.push(std::move(request)); waiter->notify(); } void MegaApiImpl::enableMount(const char* name, MegaRequestListener* listener, bool remember) { assert(listener); assert(name); auto request = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_ENABLE_MOUNT); request->setFile(name); request->setListener(listener); auto execute = [remember, this](MegaRequestPrivate& request) { auto name = std::string(request.getFile()); auto result = client->mFuseService.enable(name, remember); auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(&request, std::move(error)); return API_OK; }; request->performRequest = std::bind(std::move(execute), std::ref(*request)); requestQueue.push(std::move(request)); waiter->notify(); } void MegaApiImpl::fireOnFuseEvent(FuseEventHandler handler, const fuse::MountEvent& event) { // No listeners? No need to translate the path. if (listeners.empty()) return; // Latch path and result. auto result = static_cast<int>(event.mResult); // Signal listeners. for (auto* listener : listeners) (listener->*handler)(api, event.mName.c_str(), result); } MegaMountFlags* MegaApiImpl::getMountFlags(const char* name) { SdkMutexGuard guard(sdkMutex); assert(name); auto flags = client->mFuseService.flags(name); if (flags) return new MegaMountFlagsPrivate(*flags); return nullptr; } MegaMount* MegaApiImpl::getMountInfo(const char* name) { SdkMutexGuard guard(sdkMutex); assert(name); auto info = client->mFuseService.get(std::string(name)); if (info) return new MegaMountPrivate(*info); return nullptr; } char* MegaApiImpl::getMountPath(const char* name) { SdkMutexGuard guard(sdkMutex); if (auto path = client->mFuseService.path(name); !path.empty()) return MegaApi::strdup(path.toPath(false).c_str()); return nullptr; } MegaMountList* MegaApiImpl::listMounts(bool enabled) { SdkMutexGuard guard(sdkMutex); auto mounts = client->mFuseService.get(enabled); return new MegaMountListPrivate(std::move(mounts)); } void MegaApiImpl::onFuseEvent(const fuse::MountEvent& event) { static const FuseEventHandler handlers[] = { &MegaListener::onMountAdded, &MegaListener::onMountChanged, &MegaListener::onMountDisabled, &MegaListener::onMountEnabled, &MegaListener::onMountRemoved }; // handlers // Sanity. assert(&handlers[event.mType] < std::end(handlers)); // Broadcast the event. fireOnFuseEvent(handlers[event.mType], event); } bool MegaApiImpl::isCached(const char* path) { assert(path); SdkMutexGuard guard(sdkMutex); return client->mFuseService.cached(LocalPath::fromPlatformEncodedAbsolute(path)); } bool MegaApiImpl::isFUSESupported() { SdkMutexGuard guard(sdkMutex); return client->mFuseService.supported(); } bool MegaApiImpl::isMountEnabled(const char* name) { assert(name); SdkMutexGuard gaurd(sdkMutex); return client->mFuseService.enabled(name); } void MegaApiImpl::removeMount(const char* name, MegaRequestListener* listener) { assert(listener); assert(name); auto request = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_REMOVE_MOUNT); request->setFile(name); request->setListener(listener); auto execute = [this](MegaRequestPrivate& request) { auto name = std::string(request.getFile()); auto result = client->mFuseService.remove(name); auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(&request, std::move(error)); return API_OK; }; request->performRequest = std::bind(std::move(execute), std::ref(*request)); requestQueue.push(std::move(request)); waiter->notify(); } void MegaApiImpl::setFUSEFlags(const MegaFuseFlags& flags) { SdkMutexGuard guard(sdkMutex); auto& flags_ = reinterpret_cast<const MegaFuseFlagsPrivate&>(flags); client->mFuseService.serviceFlags(flags_.getFlags()); } MegaFuseFlags* MegaApiImpl::getFUSEFlags() { SdkMutexGuard guard(sdkMutex); return new MegaFuseFlagsPrivate(client->mFuseService.serviceFlags()); } void MegaApiImpl::setMountFlags(const MegaMountFlags* flags, const char* name, MegaRequestListener* listener) { assert(flags); assert(listener); assert(name); auto request = std::make_unique<MegaRequestPrivate>(MegaRequest::TYPE_SET_MOUNT_FLAGS); request->setFile(name); request->setListener(listener); auto execute = [this](const fuse::MountFlags& flags, MegaRequestPrivate& request) { auto name = std::string(request.getFile()); auto result = client->mFuseService.flags(name, flags); auto error = std::make_unique<MegaErrorPrivate>(result); fireOnRequestFinish(&request, std::move(error)); return API_OK; }; request->performRequest = std::bind(std::move(execute), static_cast<const MegaMountFlagsPrivate*>(flags)->getFlags(), std::ref(*request)); requestQueue.push(std::move(request)); waiter->notify(); } // // Sets and Elements // void MegaApiImpl::putSetElements(MegaHandle sid, const MegaHandleList* nodes, const MegaStringList* names, MegaRequestListener* listener) { assert(nodes && nodes->size() && (!names || names->size() == static_cast<int>(nodes->size()))); MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_SET_ELEMENTS, listener); request->setTotalBytes(static_cast<long long>(sid)); request->setMegaHandleList(nodes); request->setMegaStringList(names); request->performRequest = [this, request]() { MegaHandleList* nodes = request->getMegaHandleList(); MegaStringList* names = request->getMegaStringList(); vector<SetElement> els(nodes->size()); for (size_t i = 0u; i < els.size(); ++i) { SetElement& el = els[i]; el.setSet(static_cast<handle>(request->getTotalBytes())); el.setNode(nodes->get(static_cast<unsigned int>(i))); if (names) { el.setName(names->get(static_cast<int>(i))); } } client->putSetElements(std::move(els), [this, request](Error e, const vector<const SetElement*>* retEls, const vector<int64_t>* elErrs) { if (e == API_OK) { if (retEls) { request->setMegaSetElementList(std::make_unique<MegaSetElementListPrivate>(retEls->data(), static_cast<int>(retEls->size()))); } if (elErrs) { request->setMegaIntegerList(std::make_unique<MegaIntegerListPrivate>(*elErrs)); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::removeSetElements(MegaHandle sid, const MegaHandleList* eids, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_REMOVE_SET_ELEMENTS, listener); request->setTotalBytes(static_cast<long long>(sid)); request->setMegaHandleList(eids); request->performRequest = [this, request]() { const MegaHandleList* eidsList = request->getMegaHandleList(); if (!eidsList) { return API_ENOENT; } std::vector<handle> eids(eidsList->size()); for (size_t i = 0u; i < eids.size(); ++i) { eids[i] = eidsList->get(static_cast<unsigned int>(i)); } client->removeSetElements( static_cast<handle>(request->getTotalBytes()), std::move(eids), [this, request](Error e, const vector<int64_t>* elErrs) { if (e == API_OK && elErrs) { request->setMegaIntegerList(std::make_unique<MegaIntegerListPrivate>(*elErrs)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } MegaSetList* MegaApiImpl::getSets() { SdkMutexGuard g(sdkMutex); MegaSetListPrivate* sList = new MegaSetListPrivate(client->getSets()); return sList; } MegaSet* MegaApiImpl::getSet(MegaHandle sid) { SdkMutexGuard g(sdkMutex); const Set* s = client->getSet(sid); return s ? (new MegaSetPrivate(*s)) : nullptr; } MegaHandle MegaApiImpl::getSetCover(MegaHandle sid) { SdkMutexGuard g(sdkMutex); const Set* s = client->getSet(sid); return s ? s->cover() : INVALID_HANDLE; } bool MegaApiImpl::nodeInRubbishCheck(handle h) const { std::shared_ptr<Node> n = client->nodebyhandle(h); bool inRubbish = n && n->firstancestor()->type == RUBBISHNODE; return inRubbish; } unsigned MegaApiImpl::getSetElementCount(MegaHandle sid, bool includeElementsInRubbishBin) { SdkMutexGuard g(sdkMutex); if (includeElementsInRubbishBin) { return client->getSetElementCount(sid); } else { auto els = client->getSetElements(sid); auto ret = count_if(begin(*els), end(*els), [this](const pair<const handle, SetElement>& e) { return !nodeInRubbishCheck(e.second.node()); }); return static_cast<unsigned>(ret); } } MegaSetElementList* MegaApiImpl::getSetElements(MegaHandle sid, bool includeElementsInRubbishBin) { SdkMutexGuard g(sdkMutex); auto* elements = client->getSetElements(sid); std::function<bool(handle)> filterOut; if (!includeElementsInRubbishBin) { filterOut = std::bind(&MegaApiImpl::nodeInRubbishCheck, this, std::placeholders::_1); } MegaSetElementListPrivate* eList = new MegaSetElementListPrivate(elements, filterOut); return eList; } MegaSetElement* MegaApiImpl::getSetElement(MegaHandle sid, MegaHandle eid) { SdkMutexGuard g(sdkMutex); const SetElement* el = client->getSetElement(sid, eid); return el ? (new MegaSetElementPrivate(*el)) : nullptr; } MegaSetListPrivate::MegaSetListPrivate(const Set *const* sets, int count) { if (sets && count) { mSets.reserve(static_cast<size_t>(count)); for (int i = 0; i < count; ++i) { const Set& s = *sets[i]; add(MegaSetPrivate(s)); } } } MegaSetListPrivate::MegaSetListPrivate(const map<handle, Set>& sets) { mSets.reserve(sets.size()); for (const auto& sp : sets) { const Set& s = sp.second; add(MegaSetPrivate(s)); } } void MegaSetListPrivate::add(MegaSetPrivate&& s) { mSets.emplace_back(std::move(s)); } MegaSetElementListPrivate::MegaSetElementListPrivate(const SetElement* const* elements, int count) { if (elements && count) { mElements.reserve(static_cast<size_t>(count)); for (int i = 0; i < count; ++i) { const SetElement& el = *elements[i]; add(MegaSetElementPrivate(el)); } } } MegaSetElementListPrivate::MegaSetElementListPrivate(const elementsmap_t* elements, const std::function<bool(handle)>& filterOut) { if (elements) { mElements.reserve(elements->size()); for (const auto& e : *elements) { if (!filterOut || !filterOut(e.second.node())) { const SetElement& el = e.second; add(MegaSetElementPrivate(el)); } } mElements.shrink_to_fit(); } } void MegaSetElementListPrivate::add(MegaSetElementPrivate&& el) { mElements.emplace_back(std::move(el)); } bool MegaApiImpl::isExportedSet(MegaHandle sid) { SdkMutexGuard g(sdkMutex); return client->isExportedSet(sid); } void MegaApiImpl::exportSet(MegaHandle sid, bool create, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_EXPORT_SET, listener); request->setNodeHandle(sid); request->setFlag(create); request->performRequest = [this, request]() { client->exportSet(request->getNodeHandle(), request->getFlag(), [this, request](Error e) { if (e == API_OK) { const bool isExport = request->getFlag(); const auto sid = request->getNodeHandle(); const Set* updatedSet = client->getSet(sid); if (!updatedSet) { LOG_err << "Sets: Set to be updated not found for " << (isExport ? "en" : "dis") << "able export operation. Set id " << toHandle(sid); assert(false); } if((isExport && !updatedSet->isExported()) || (!isExport && updatedSet->isExported())) { LOG_err << "Sets: Set " << (isExport ? "en" : "dis") << "able operation with" << " incoherent result state isExported()==" << updatedSet->isExported() << ". Set id " << toHandle(sid); assert(false); } string url; if (isExport) { std::tie(e, url) = client->getPublicSetLink(updatedSet->id()); } if (e == API_OK) { request->setLink(url.c_str()); request->setMegaSet(unique_ptr<MegaSet>(new MegaSetPrivate(*updatedSet))); unique_ptr<MegaSetListPrivate> updatedSetList(new MegaSetListPrivate(&updatedSet, 1)); fireOnSetsUpdate(updatedSetList.get()); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::exportSet(MegaHandle sid, MegaRequestListener* listener) { exportSet(sid, true, listener); } void MegaApiImpl::disableExportSet(MegaHandle sid, MegaRequestListener* listener) { exportSet(sid, false, listener); } void MegaApiImpl::fetchPublicSet(const char* publicSetLink, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_SET, listener); request->setLink(publicSetLink); request->performRequest = [this, request]() -> ErrorCodes { const auto e = client->fetchPublicSet( request->getLink(), [this, request](Error e, Set* s, elementsmap_t* els) { unique_ptr<Set> sp(s); unique_ptr<elementsmap_t> elsp(els); if (e == API_OK) { assert(sp && elsp); if (sp && elsp) { request->setMegaSet(std::make_unique<MegaSetPrivate>(*sp)); request->setMegaSetElementList(std::make_unique<MegaSetElementListPrivate>(elsp.get())); } } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return e; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::stopPublicSetPreview() { SdkMutexGuard g(sdkMutex); client->stopSetPreview(); } bool MegaApiImpl::inPublicSetPreview() { SdkMutexGuard g(sdkMutex); return client->inPublicSetPreview(); } MegaSet* MegaApiImpl::getPublicSetInPreview() { SdkMutexGuard g(sdkMutex); const auto s = client->getPreviewSet(); return s ? new MegaSetPrivate(*s) : nullptr; } MegaSetElementList* MegaApiImpl::getPublicSetElementsInPreview() { SdkMutexGuard g(sdkMutex); const auto els = client->getPreviewSetElements(); return els ? new MegaSetElementListPrivate(els) : nullptr; } void MegaApiImpl::getPreviewElementNode(MegaHandle eid, MegaRequestListener* listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_EXPORTED_SET_ELEMENT, listener); request->performRequest = [eid, this, request]() { const string paramErr = "Error failed to get MegaNode for Set Element " + toHandle(eid) + ". "; if (!client->inPublicSetPreview()) { LOG_err << paramErr << "Public Set preview mode disable"; return API_EACCESS; } auto element = client->getPreviewSetElement(eid); if (!element) { LOG_err << paramErr << "Element not found in preview mode Set " << toHandle(client->getPreviewSet()->id()); return API_EARGS; } auto nm = element->nodeMetadata(); if (!nm) { LOG_err << paramErr << "Element node not found for preview"; return API_ENOENT; } FileFingerprint ffp; m_time_t tm = ffp.unserializefingerprint(&nm->fingerprint) ? ffp.mtime : 0; const string megaApiImplFingerprint = MegaNodePrivate::addAppPrefixToFingerprint(nm->fingerprint, nm->s); MegaNodePrivate ret(nm->filename.c_str(), FILENODE, nm->s, nm->ts, tm, nm->h, &element->key(), &nm->fa, megaApiImplFingerprint.empty() ? nullptr : megaApiImplFingerprint.c_str(), nullptr, nm->u, INVALID_HANDLE, nullptr, nullptr, false /*isPublic*/, true /*isForeign*/); request->setPublicNode(&ret); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; }; requestQueue.push(request); waiter->notify(); } const char* MegaApiImpl::getPublicLinkForExportedSet(MegaHandle sid) { string retStr; error e; { SdkMutexGuard g(sdkMutex); std::tie(e, retStr) = client->getPublicSetLink(sid); } char* link = nullptr; if (e == API_OK) { auto sz = retStr.size() + 1; link = new char[sz]; std::strncpy(link, retStr.c_str(), sz); LOG_verbose << "Successfully created public link " << retStr << "for Set " << toHandle(sid); } else { LOG_err << "Failing to create a public link for Set " << toHandle(sid) << " with error code " << e << "(" << MegaError::getErrorString(e) << ")"; } return link; } /* MegaApiImpl VPN commands BEGIN */ void MegaApiImpl::getVpnRegions(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_VPN_REGIONS, listener); request->performRequest = [this, request]() { client->getVpnRegions( [this, request](const Error& e, std::vector<VpnRegion>&& vpnRegions) { if (e == API_OK) { auto vpnRegionsMegaStringList = std::make_unique<MegaStringListPrivate>(); for (const auto& r: vpnRegions) { vpnRegionsMegaStringList->add(r.getName().c_str()); } request->setMegaStringList(vpnRegionsMegaStringList.get()); MegaVpnRegionListPrivate* regionsWithDetails = new MegaVpnRegionListPrivate(vpnRegions); request->setMegaVpnRegionsDetailed(regionsWithDetails); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getVpnCredentials(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_VPN_CREDENTIALS, listener); request->performRequest = [this, request]() { client->getVpnCredentials([this, request] (const Error& e, CommandGetVpnCredentials::MapSlotIDToCredentialInfo&& mapSlotIDToCredentialInfo, /* Map of SlotID: { ClusterID, IPv4, IPv6, DeviceID } */ CommandGetVpnCredentials::MapClusterPublicKeys&& mapClusterPubKeys, /* Map of ClusterID: Cluster Public Key */ std::vector<VpnRegion>&& vpnRegions /* VPN Regions */) { if (e == API_OK && !mapSlotIDToCredentialInfo.empty() && !mapClusterPubKeys.empty() && !vpnRegions.empty()) { request->setMegaVpnCredentials( new MegaVpnCredentialsPrivate(std::move(mapSlotIDToCredentialInfo), std::move(mapClusterPubKeys), std::move(vpnRegions))); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::putVpnCredential(const char* region, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_PUT_VPN_CREDENTIAL, listener); request->setText(region); request->performRequest = [this, request]() { const char* vpnRegion = request->getText(); if (!vpnRegion || !*vpnRegion) { LOG_err << "[MegaApiImpl::putVpnCredential] VPN region is EMPTY!"; return API_EARGS; } client->putVpnCredential(vpnRegion, [this, request] (const Error& e, int slotID, std::string&& userPubKey, std::string&& newCredential) { // SlotIDs are considered valid when greater than 0 if (e == API_OK && (slotID > 0) && !userPubKey.empty() && !newCredential.empty()) { request->setNumber(slotID); request->setPassword(userPubKey.c_str()); request->setSessionKey(newCredential.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } /** * @brief Helper function specifically introduced to guarantee equivalence between internal and * public enums. * A new entry added to NetworkConnectivityTestMessageStatus will trigger a compilation warning. */ static constexpr int toPublicNetworkConnectivityTestStatus(const NetworkConnectivityTestMessageStatus s) { switch (s) { case NetworkConnectivityTestMessageStatus::PASS: return MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_PASS; case NetworkConnectivityTestMessageStatus::FAIL: case NetworkConnectivityTestMessageStatus::NOT_RUN: return MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_FAIL; case NetworkConnectivityTestMessageStatus::NET_UNREACHABLE: return MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE; } assert(s == NetworkConnectivityTestMessageStatus::FAIL); // should never get here return MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_FAIL; } void MegaApiImpl::delVpnCredential(int slotID, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DEL_VPN_CREDENTIAL, listener); request->setNumber(slotID); request->performRequest = [this, request]() { int slotID = static_cast<int>(request->getNumber()); client->delVpnCredential(slotID, [this, request] (const Error& e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::checkVpnCredential(const char* userPubKey, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CHECK_VPN_CREDENTIAL, listener); request->setText(userPubKey); request->performRequest = [this, request]() { const char* userPK = request->getText(); if (!userPK || !*userPK) { LOG_err << "[MegaApiImpl::checkVpnCredential] User Public Key is EMPTY!"; return API_EARGS; } client->checkVpnCredential(userPK, [this, request] (const Error& e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::runNetworkConnectivityTest(MegaRequestListener* listener) { auto* request = new MegaRequestPrivate(MegaRequest::TYPE_RUN_NETWORK_CONNECTIVITY_TEST, listener); request->performRequest = [this, request]() { client->runNetworkConnectivityTest( [this, request](const Error& e, NetworkConnectivityTestResults&& results) { if (e == API_OK) { client->sendNetworkConnectivityTestEvent(results); request->setMegaNetworkConnectivityTestResults( new MegaNetworkConnectivityTestResultsPrivate( toPublicNetworkConnectivityTestStatus(results.ipv4.messages), toPublicNetworkConnectivityTestStatus(results.ipv4.dns), toPublicNetworkConnectivityTestStatus(results.ipv6.messages), toPublicNetworkConnectivityTestStatus(results.ipv6.dns))); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } /* MegaApiImpl VPN commands END */ void MegaApiImpl::getPasswordManagerBase(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_PASSWORD_MANAGER_BASE, listener); request->performRequest = [this, request]() { // 1. Shortcut: if already present, nothing to be done if (!client->getPasswordManagerBase().isUndef()) { request->setNodeHandle(client->getPasswordManagerBase().as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } // 2. Check node existance (pwmh user attribute) CommandGetUA::CompletionErr ce = [this, request](error e) -> void { LOG_err << "Password Manager: pwmh user attribute request failed unexpectedly with " << "error " << e << ". Finishing request"; fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; CommandGetUA::CompletionBytes cb = [this, request](byte* /*data*/, unsigned /*len*/, attr_t /*type*/) -> void { request->setNodeHandle(client->getPasswordManagerBase().as8byte()); assert(!ISUNDEF(request->getNodeHandle())); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); }; CommandGetUA::CompletionTLV ctlv = [this, request](unique_ptr<string_map>, attr_t) -> void { LOG_err << "Password Manager: ERROR CompletionTLV callback evaluated from CommandGetUA"; assert(false); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EINTERNAL)); }; client->getua(client->finduser(client->me), ATTR_PWM_BASE, request->getTag(), std::move(ce), std::move(cb), std::move(ctlv)); return API_OK; }; requestQueue.push(request); waiter->notify(); } bool MegaApiImpl::isPasswordManagerNodeFolder(MegaHandle h) const { if (h == UNDEF) return false; SdkMutexGuard g{sdkMutex}; const auto n = client->nodebyhandle(h); assert(n && "Node not found by handle"); return n && n->isPasswordManagerNodeFolder(); } static std::string totpToJson(const MegaNode::PasswordNodeData::TotpData& totp) { if (totp.markedToRemove()) { return ""; } AttrMap attrMap{}; if (const auto shse = totp.sharedSecret(); shse) attrMap.map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_SHSE)] = shse; if (const auto expirationTime = totp.expirationTime(); expirationTime >= 0) attrMap.map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_EXPT)] = std::to_string(expirationTime); if (const auto hashAlgorithm = totp.hashAlgorithm(); hashAlgorithm >= 0) attrMap.map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_HASH_ALG)] = totp::hashAlgorithmPubToStrView(hashAlgorithm); if (const auto nDigits = totp.nDigits(); nDigits >= 0) attrMap.map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_TOTP_NDIGITS)] = std::to_string(nDigits); auto result = attrMap.getJsonObject(); return result; } void MegaApiImpl::createPasswordManagerBase(MegaRequestPrivate* request) { CommandCreatePasswordManagerBase::Completion cb = [this, request](Error e, std::unique_ptr<NewNode> nn) -> void { if (e == API_OK) { request->setNodeHandle(nn->nodeHandle().as8byte()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; client->createPasswordManagerBase(request->getTag(), std::move(cb)); } static std::function<void(const std::string_view, const char*)> getAttrSetter(AttrMap& attrMap) { return [&attrMap](const std::string_view key, const char* value) { if (value) attrMap.map[AttrMap::string2nameid(key)] = value; }; } std::unique_ptr<AttrMap> MegaApiImpl::toAttrMapCreditCard(const MegaNode::CreditCardNodeData* data) const { if (!data) return nullptr; auto attrMap = std::make_unique<AttrMap>(); const auto addAttr = getAttrSetter(*attrMap); addAttr(MegaClient::PWM_ATTR_NODE_TYPE, std::string{MegaClient::PWM_ATTR_NODE_TYPE_CREDIT_CARD}.c_str()); addAttr(MegaClient::PWM_ATTR_CREDIT_CARD_NUMBER, data->cardNumber()); addAttr(MegaClient::PWM_ATTR_CREDIT_NOTES, data->notes()); addAttr(MegaClient::PWM_ATTR_CREDIT_CARD_HOLDER, data->cardHolderName()); addAttr(MegaClient::PWM_ATTR_CREDIT_CVV, data->cvv()); addAttr(MegaClient::PWM_ATTR_CREDIT_EXP_DATE, data->expirationDate()); return attrMap; } std::unique_ptr<AttrMap> MegaApiImpl::toAttrMapPassword(const MegaNode::PasswordNodeData* data) const { if (!data) return nullptr; auto attrMap = std::make_unique<AttrMap>(); const auto addAttr = getAttrSetter(*attrMap); addAttr(MegaClient::PWM_ATTR_PASSWORD_NOTES, data->notes()); addAttr(MegaClient::PWM_ATTR_PASSWORD_PWD, data->password()); addAttr(MegaClient::PWM_ATTR_PASSWORD_URL, data->url()); addAttr(MegaClient::PWM_ATTR_PASSWORD_USERNAME, data->userName()); if (const auto totp = data->totpData(); totp) addAttr(MegaClient::PWM_ATTR_PASSWORD_TOTP, totpToJson(*totp).c_str()); return attrMap; } void MegaApiImpl::createCreditCardNode(const char* name, const MegaNode::CreditCardNodeData* ccData, const MegaHandle parentHandle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_PASSWORD_NODE, listener); request->setName(name); request->setParentHandle(parentHandle); request->setParamType(MegaApi::PWM_NODE_TYPE_CREDIT_CARD); request->performRequest = [this, request, ccData]() -> error { auto name = request->getName(); auto parentHandle = client->nodebyhandle(request->getParentHandle()); auto data = toAttrMapCreditCard(ccData); if (const auto error = checkCreateFolderPrecons(name, parentHandle, request); error != API_OK) return error; // using default this->putnodes_result as callback return client->createPasswordEntry(name, std::move(data), MegaClient::validateNewCreditCardNodeData, parentHandle, request->getTag()); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::createPasswordNode(const char* name, const MegaNode::PasswordNodeData* pwData, MegaHandle parentHandle, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_PASSWORD_NODE, listener); request->setParentHandle(parentHandle); request->setName(name); request->setParamType(MegaApi::PWM_NODE_TYPE_PASSWORD); request->performRequest = [this, request, pwData]() -> error { if (pwData && pwData->totpData() && pwData->totpData()->markedToRemove()) { LOG_err << "createPasswordNode: Invalid TotpData with remove arg set to `true`"; return API_EARGS; } auto name = request->getName(); auto parent = client->nodebyhandle(request->getParentHandle()); auto data = toAttrMapPassword(pwData); if (const auto error = checkCreateFolderPrecons(name, parent, request); error != API_OK) return error; // using default this->putnodes_result as callback return client->createPasswordEntry(name, std::move(data), MegaClient::validateNewPasswordNodeData, parent, request->getTag()); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::updateCreditCardNode(MegaHandle node, const MegaNode::CreditCardNodeData* ccData, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_UPDATE_PASSWORD_NODE, listener); request->setNodeHandle(node); request->setParamType(MegaApi::PWM_NODE_TYPE_CREDIT_CARD); request->performRequest = [this, request, ccData]() -> error { auto nhPwdNode = NodeHandle{}.set6byte(request->getNodeHandle()); auto data = toAttrMapCreditCard(ccData); CommandSetAttr::Completion cbRequest = [this, request](NodeHandle nh, Error e) { assert(request->getNodeHandle() == nh.as8byte()); request->setNodeHandle(nh.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; return client->updateCreditCardNode(nhPwdNode, std::move(data), std::move(cbRequest)); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::updatePasswordNode(MegaHandle h, const MegaNode::PasswordNodeData* pwData, MegaRequestListener *listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_UPDATE_PASSWORD_NODE, listener); request->setNodeHandle(h); request->setParamType(MegaApi::PWM_NODE_TYPE_PASSWORD); request->performRequest = [this, request, pwData]() -> error { auto nhPwdNode = NodeHandle{}.set6byte(request->getNodeHandle()); auto data = toAttrMapPassword(pwData); CommandSetAttr::Completion cbRequest = [this, request](NodeHandle nh, Error e) { assert(request->getNodeHandle() == nh.as8byte()); request->setNodeHandle(nh.as8byte()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; return client->updatePasswordNode(nhPwdNode, std::move(data), std::move(cbRequest)); }; requestQueue.push(request); waiter->notify(); } /** * @brief Helper function specifically introduced to guarantee equivalence between internal and * public enums. If a new entry is added on PasswordEntryError this will trigger a compilation * warning. */ static constexpr int toPublicErrorCode(const PasswordEntryError e) { switch (e) { case PasswordEntryError::OK: return 0; case PasswordEntryError::PARSE_ERROR: return MegaApi::IMPORTED_PASSWORD_ERROR_PARSER; case PasswordEntryError::MISSING_PASSWORD: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD; case PasswordEntryError::MISSING_NAME: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGNAME; case PasswordEntryError::MISSING_TOTP_SHARED_SECRET: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSING_TOTP_SHSE; case PasswordEntryError::INVALID_TOTP_SHARED_SECRET: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_TOTP_SHSE; case PasswordEntryError::MISSING_TOTP_NDIGITS: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSING_TOTP_NDIGITS; case PasswordEntryError::INVALID_TOTP_NDIGITS: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_TOTP_NDIGITS; case PasswordEntryError::MISSING_TOTP_EXPT: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSING_TOTP_EXPTIME; case PasswordEntryError::INVALID_TOTP_EXPT: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_TOTP_EXPTIME; case PasswordEntryError::MISSING_TOTP_HASH_ALG: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSING_TOTP_HASH_ALG; case PasswordEntryError::INVALID_TOTP_HASH_ALG: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_TOTP_HASH_ALG; case PasswordEntryError::MISSING_CREDIT_CARD_NUMBER: return MegaApi::IMPORTED_PASSWORD_ERROR_MISSING_CREDIT_CARD_NUMBER; case PasswordEntryError::INVALID_CREDIT_CARD_NUMBER: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_NUMBER; case PasswordEntryError::INVALID_CREDIT_CARD_CVV: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_CVV; case PasswordEntryError::INVALID_CREDIT_CARD_EXPIRATION_DATE: return MegaApi::IMPORTED_PASSWORD_ERROR_INVALID_CREDIT_CARD_EXPIRATION_DATE; } assert(false); return -1; // We should never get here } void MegaApiImpl::importPasswordsFromFile(const char* filePath, const int fileSource, MegaHandle parent, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_IMPORT_PASSWORDS_FROM_FILE, listener); request->setFile(filePath); request->setParamType(fileSource); request->setParentHandle(parent); request->performRequest = [this, request]() -> error { using namespace pwm::import; const NodeHandle parentHandle = NodeHandle{}.set6byte(request->getParentHandle()); if (parentHandle.isUndef() || !request->getFile() || request->getParamType() != MegaApi::IMPORT_PASSWORD_SOURCE_GOOGLE) { LOG_err << "Import password: invalid parameters"; return API_EARGS; } const std::string filePath{request->getFile()}; const auto source = static_cast<FileSource>(request->getParamType()); const auto [error, badEntries, nGoodEntries] = client->importPasswordsFromFile(filePath, source, parentHandle, request->getTag()); // Populate request with bad entries MegaStringIntegerMapPrivate stringIntegerMap; std::for_each(badEntries.begin(), badEntries.end(), [&stringIntegerMap](const std::pair<std::string, PasswordEntryError>& arg) { stringIntegerMap.set(arg.first, toPublicErrorCode(arg.second)); }); request->setMegaStringIntegerMap(&stringIntegerMap); if (error == API_OK && nGoodEntries == 0) { // Special case: All entries are wrong, so there was no call to putnodes request->setMegaHandleList(std::vector<handle>{}); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); } return error; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::fetchCreditCardInfo(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_FETCH_CREDIT_CARD_INFO, listener); request->performRequest = [this, request]() { client->fetchCreditCardInfo( [this, request](Error e, const std::map<std::string, std::string>& creditCardInfo) { std::unique_ptr<MegaStringMapPrivate> stringMap = std::make_unique<MegaStringMapPrivate>(&creditCardInfo); request->setMegaStringMap(stringMap.get()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getVisibleWelcomeDialog(MegaRequestListener* listener) { getUserAttr(nullptr, MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG, nullptr, 0, listener); } void MegaApiImpl::setVisibleWelcomeDialog(bool visible, MegaRequestListener* listener) { const auto attributeValue{std::to_string(visible)}; setUserAttr(MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG, attributeValue.c_str(), listener); } void MegaApiImpl::getVisibleTermsOfService(MegaRequestListener* listener) { getUserAttr(nullptr, MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE, nullptr, 0, listener); } void MegaApiImpl::setVisibleTermsOfService(bool visible, MegaRequestListener* listener) { const string attributeValue{std::to_string(visible)}; setUserAttr(MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE, attributeValue.c_str(), listener); } void MegaApiImpl::createNodeTree(const MegaNode* parentNode, MegaNodeTree* nodeTree, const char* customerIpPort, MegaRequestListener* listener) { auto request{new MegaRequestPrivate(MegaRequest::TYPE_CREATE_NODE_TREE, listener)}; request->setParentHandle(parentNode ? parentNode->getHandle() : INVALID_HANDLE); request->setMegaNodeTree(nodeTree ? nodeTree->copy() : nullptr); request->setText(customerIpPort); request->performRequest = [this, request]() { if (request->getParentHandle() == INVALID_HANDLE || !request->getMegaNodeTree()) { LOG_err << "Failed to create node tree: Missing arguments"; return API_EARGS; } // Check for old versions when copying directly to parentNode // (not to a newly created intermediary folder). shared_ptr<Node> ovLocation{ request->getMegaNodeTree()->getNodeTreeChild() ? nullptr : client->nodebyhandle(request->getParentHandle()) }; std::vector<NewNode> newNodes; handle tmpNodeHandle{1}; handle tmpParentNodeHandle{UNDEF}; std::unique_ptr<MegaNodeTree> nodeTree(request->getMegaNodeTree()->copy()); for (auto tmpNodeTree{dynamic_cast<MegaNodeTreePrivate*>(nodeTree.get())}; tmpNodeTree; tmpNodeTree = dynamic_cast<MegaNodeTreePrivate*>(tmpNodeTree->getNodeTreeChild())) { if ((tmpNodeTree->getNodeTreeChild() && tmpNodeTree->getCompleteUploadData()) || (tmpNodeTree->getNodeTreeChild() && tmpNodeTree->getSourceHandle() != INVALID_HANDLE) || (tmpNodeTree->getCompleteUploadData() && tmpNodeTree->getSourceHandle() != INVALID_HANDLE) || tmpNodeTree->getName().empty() ) { LOG_err << "Failed to create node tree: Invalid arguments"; return API_EARGS; } if (tmpNodeTree->getSourceHandle() != INVALID_HANDLE) // copy existing node (source) { shared_ptr<Node> source{ client->nodebyhandle(tmpNodeTree->getSourceHandle()) }; if (!source || source->type != FILENODE) { LOG_err << "Failed to create node tree: Source was not a file"; return API_EARGS; } vector<NewNode> treeToCopy; error err = copyTreeFromOwnedNode(source, tmpNodeTree->getName().c_str(), ovLocation, treeToCopy, tmpNodeTree->getS4AttributeValue()); if (err != API_OK) { if (err == API_EEXIST) // dedicated error code when that exact same file already existed { assert(!treeToCopy.empty()); // never empty because other error should have been reported in that case tmpNodeTree->setNodeHandle(treeToCopy[0].ovhandle.as8byte()); request->setNodeHandle(tmpNodeTree->getNodeHandle()); request->setMegaNodeTree(nodeTree.release()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } return err; } assert(!treeToCopy.empty()); // never empty because an error should have been reported in that case treeToCopy[0].parenthandle = tmpParentNodeHandle; newNodes.emplace_back(std::move(treeToCopy[0])); } else // complete upload { const auto completeUploadData{ dynamic_cast<const MegaCompleteUploadDataPrivate*>( tmpNodeTree->getCompleteUploadData()) }; NewNode newNode{}; newNode.source = completeUploadData ? NEW_UPLOAD : NEW_NODE; newNode.type = completeUploadData ? FILENODE : FOLDERNODE; newNode.nodehandle = tmpNodeHandle++; newNode.parenthandle = tmpParentNodeHandle; newNode.fileattributes.clear(); tmpParentNodeHandle = newNode.nodehandle; // Normalize name std::string name{ tmpNodeTree->getName() }; LocalPath::utf8_normalize(&name); // Set node key if (completeUploadData) { // determine handling of older versions if (std::shared_ptr<Node> ovn = ovLocation ? client->getovnode(ovLocation.get(), &name) : nullptr) { auto ovfp = ovn->attrs.map.find('c'); if (ovfp != ovn->attrs.map.end() && ovfp->second == MegaNodePrivate::removeAppPrefixFromFingerprint(completeUploadData->getFingerprint().c_str())) { tmpNodeTree->setNodeHandle(ovn->nodeHandle().as8byte()); request->setNodeHandle(tmpNodeTree->getNodeHandle()); request->setMegaNodeTree(nodeTree.release()); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); return API_OK; } newNode.ovhandle = ovn->nodeHandle(); } byte* nodeKey; size_t nodeKeyLength{ FILENODEKEYLENGTH }; base64ToBinary(completeUploadData->getString64FileKey().c_str(), &nodeKey, &nodeKeyLength); newNode.nodekey.assign(reinterpret_cast<char*>(nodeKey), nodeKeyLength); delete[] nodeKey; SymmCipher::xorblock( reinterpret_cast<const byte*>(newNode.nodekey.data()) + SymmCipher::KEYLENGTH, const_cast<byte*>(reinterpret_cast<const byte*>(newNode.nodekey.data()))); } else { constexpr size_t nodeKeyLength{ FOLDERNODEKEYLENGTH }; byte nodeKey[nodeKeyLength]; client->rng.genblock(nodeKey, nodeKeyLength); newNode.nodekey.assign(reinterpret_cast<char*>(nodeKey), nodeKeyLength); } // Set name AttrMap attributes; attributes.map['n'] = name; // Set S4 attribute attributes.map[AttrMap::string2nameid("s4")] = tmpNodeTree->getS4AttributeValue(); // Set fingerprint if (completeUploadData) { attributes.map['c'] = MegaNodePrivate::removeAppPrefixFromFingerprint(completeUploadData->getFingerprint().c_str()); } // Set attributes std::string attrstring; attributes.getjson(&attrstring); newNode.attrstring.reset(new std::string); SymmCipher cipher; cipher.setkey(&newNode.nodekey); client->makeattr(&cipher, newNode.attrstring, attrstring.c_str()); // Set upload if (completeUploadData) { UploadToken uploadToken{}; const size_t uploadTokenSize{ static_cast<size_t>( Base64::atob(completeUploadData->getString64UploadToken().c_str(), &uploadToken[0], sizeof(uploadToken))) }; if ((uploadTokenSize != UPLOADTOKENLEN) || (uploadTokenSize != sizeof(uploadToken))) { return API_EARGS; } newNode.uploadtoken = uploadToken; newNode.uploadhandle = client->mUploadHandle.next(); } newNodes.push_back(std::move(newNode)); } } auto result{[this, request, nodeTree = std::shared_ptr<MegaNodeTree>(nodeTree.release())]( const Error& error, targettype_t, vector<NewNode>& newNodes, bool, int, const map<string, string>& fileHandles) { size_t i{}; auto tmpNodeTree{dynamic_cast<MegaNodeTreePrivate*>(nodeTree.get())}; while (i < newNodes.size() && tmpNodeTree) { if (auto node{client->nodebyhandle(newNodes[i].mAddedHandle)}) { tmpNodeTree->setNodeHandle(node->nodehandle); } i++; tmpNodeTree = dynamic_cast<MegaNodeTreePrivate*>(tmpNodeTree->getNodeTreeChild()); } request->setMegaNodeTree(nodeTree->copy()); request->setMegaStringMap(fileHandles); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(error)); }}; client->putnodes(NodeHandle().set6byte(request->getParentHandle()), UseLocalVersioningFlag, std::move(newNodes), nullptr, request->getTag(), false, request->getText() ? request->getText() : string{}, result); return API_OK; }; requestQueue.push(request); waiter->notify(); } MegaIntegerList* MegaApiImpl::getEnabledNotifications() const { SdkMutexGuard g(sdkMutex); return new MegaIntegerListPrivate(client->getEnabledNotifications()); } void MegaApiImpl::enableTestNotifications(const MegaIntegerList * notificationIds, MegaRequestListener * listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_ENABLE_TEST_NOTIFICATIONS); MegaIntegerList* ids = notificationIds ? notificationIds->copy() : nullptr; request->setMegaIntegerList(std::unique_ptr<MegaIntegerList>{ ids }); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::performRequest_enableTestNotifications(MegaRequestPrivate* request) { const auto* ids = request->getMegaIntegerList(); if (!ids) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } // Convert MegaIntegerList to CSV format string tmp; for (int i = 0; i < ids->size(); ++i) { tmp += std::to_string(ids->get(i)) + ','; } if (!tmp.empty()) { tmp.pop_back(); // drop trailing ',' } client->putua(ATTR_ENABLE_TEST_NOTIFICATIONS, reinterpret_cast<const byte*>(tmp.c_str()), unsigned(tmp.size()), -1, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } void MegaApiImpl::getNotifications(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_NOTIFICATIONS, listener); request->performRequest = [this, request]() { return performRequest_getNotifications(request); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_getNotifications(MegaRequestPrivate* request) { auto onResult = [this, request](const Error& error, vector<DynamicMessageNotification>&& notifications) { MegaNotificationList* megaNotifications = new MegaNotificationListPrivate(std::move(notifications)); request->setMegaNotifications(megaNotifications); fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(error)); }; client->getNotifications(onResult); return API_OK; } void MegaApiImpl::setLastReadNotification(uint32_t notificationId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_LAST_READ_NOTIFICATION); request->setNumber(notificationId); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::performRequest_setLastReadNotification(MegaRequestPrivate* request) { const string& tmp = request->getNumber() ? std::to_string(static_cast<uint32_t>(request->getNumber())) : string{}; client->putua(ATTR_LAST_READ_NOTIFICATION, reinterpret_cast<const byte*>(tmp.c_str()), static_cast<unsigned>(tmp.size()), -1, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } void MegaApiImpl::getLastReadNotification(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_LAST_READ_NOTIFICATION); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::getLastReadNotification_getua_result(byte* data, unsigned len, MegaRequestPrivate* request) { uint32_t value = 0u; error e = API_OK; if (len) { // make a copy to make sure we don't read too much from a non-null terminated stream string buff{ reinterpret_cast<char*>(data), len }; size_t processed = 0; value = static_cast<uint32_t>(stoul(buff, &processed)); // validate the value if (processed < buff.size()) { value = 0; LOG_err << "Invalid value for Last Read Notification"; e = API_EINTERNAL; } } request->setNumber(static_cast<long long>(value)); return e; } void MegaApiImpl::setLastActionedBanner(uint32_t notificationId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_LAST_ACTIONED_BANNER); request->setNumber(notificationId); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::performRequest_setLastActionedBanner(MegaRequestPrivate* request) { const string& tmp = request->getNumber() ? std::to_string(static_cast<uint32_t>(request->getNumber())) : string{}; client->putua(ATTR_LAST_ACTIONED_BANNER, reinterpret_cast<const byte*>(tmp.c_str()), static_cast<unsigned>(tmp.size()), -1, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } void MegaApiImpl::getLastActionedBanner(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_LAST_ACTIONED_BANNER); request->performRequest = [this, request]() { return performRequest_getAttrUser(request); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::getLastActionedBanner_getua_result(byte* data, unsigned len, MegaRequestPrivate* request) { uint32_t value = 0u; error e = API_OK; if (len) { // make a copy to make sure we don't read too much from a non-null terminated stream string buff{ reinterpret_cast<char*>(data), len }; size_t processed = 0; value = static_cast<uint32_t>(stoul(buff, &processed)); // validate the value if (processed < buff.size()) { value = 0; LOG_err << "Invalid value for Last Read Notification"; e = API_EINTERNAL; } } request->setNumber(static_cast<long long>(value)); return e; } MegaFlagPrivate* MegaApiImpl::getFlag(const char* flagName, bool commit, MegaRequestListener* listener) { std::pair<uint32_t, uint32_t> flag = client->getFlag(flagName); if (flag.first == static_cast<decltype(flag.first)>(MegaFlag::FLAG_TYPE_AB_TEST) && commit) { sendABTestActive(flagName, listener); } return new MegaFlagPrivate(flag.first, flag.second); } void MegaApiImpl::deleteUserAttribute(int type, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_DEL_ATTR_USER, listener); request->setParamType(type); request->performRequest = #ifdef DEBUG [this, request]() { attr_t type = static_cast<attr_t>(request->getParamType()); string attributeName = MegaApiImpl::userAttributeToString(type); if (attributeName.empty()) { return API_EARGS; } client->delua(attributeName.c_str()); return API_OK; }; #else []() { return API_EACCESS; }; #endif requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getActiveSurveyTriggerActions(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_ACTIVE_SURVEY_TRIGGER_ACTIONS, listener); request->performRequest = [this, request]() { auto completion = [this, request](const Error& e, const vector<uint32_t>& ids) { if (e == API_OK) { request->setMegaIntegerList(std::make_unique<MegaIntegerListPrivate>(ids)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; client->getActiveSurveyTriggerActions(std::move(completion)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const char* response, const char* comment, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_ANSWER_SURVEY, listener); request->setNodeHandle(surveyHandle); request->setParamType(static_cast<int>(triggerActionId)); request->setText(response); request->setFile(comment); request->performRequest = [this, request]() { auto completion = [this, request](const Error& e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; auto parseRating = [](const char* response) -> std::pair<ErrorCodes, int> { assert(response); int rating{}; if (auto [ptr, ec] = std::from_chars(response, response + std::strlen(response), rating); ec != std::errc{}) { LOG_err << "Request (TYPE_ANSWER_SURVEY). Cannot convert rating into a numeric value: " << rating; return {API_EARGS, rating}; } if (rating < 1 || rating > 5) { LOG_err << "Request (TYPE_ANSWER_SURVEY). Rating value is out of valid range: " << rating; return {API_EARGS, rating}; } return {API_OK, rating}; }; const unsigned int triggerActionId{static_cast<unsigned int>(request->getParamType())}; if (triggerActionId < MegaApi::ACT_END_UPLOAD || triggerActionId > MegaApi::ACT_SHARE_FOLDER_FILE) { LOG_err << "Request (TYPE_ANSWER_SURVEY). triggerActionId value is out of valid range: " << triggerActionId; return API_EARGS; } const char* response{request->getText()}; const char* comment{request->getFile()}; if (const auto sendTransferFeedback = triggerActionId == MegaApi::ACT_END_UPLOAD; sendTransferFeedback) { if (!response) { LOG_err << "Request (TYPE_ANSWER_SURVEY). Invalid response param"; return API_EARGS; } // if no comment is provided send empty string comment. const std::string c{comment ? comment : ""}; auto [err, rating] = parseRating(response); if (err != API_OK) { return err; } sendUserfeedback(rating, c.c_str(), true /*transferFeedback*/, MegaApi::TRANSFER_STATS_UPLOAD); } CommandAnswerSurvey::Answer answer{request->getNodeHandle(), // survey handle triggerActionId, response, comment}; client->answerSurvey(answer, std::move(completion)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getSurvey(unsigned int triggerActionId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SURVEY, listener); request->setParamType(static_cast<int>(triggerActionId)); request->performRequest = [this, request]() { auto completion = [this, request](const Error& e, const CommandGetSurvey::Survey& survey) { if (e == API_OK) { request->setNodeHandle(survey.h); request->setNumDetails(static_cast<int>(survey.maxResponse)); request->setFile(survey.image.c_str()); request->setText(survey.content.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }; const auto triggerActionId = static_cast<unsigned int>(request->getParamType()); client->getSurvey(triggerActionId, std::move(completion)); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::performRequest_enableTestSurveys(MegaRequestPrivate* request) { const MegaHandleList* ids = request->getMegaHandleList(); if (!ids) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } // Example: Join the elements to form "A,B,C" const auto joinWithCommaInB64 = [](const MegaHandleList* ids) -> string { string result; for (unsigned i = 0; i < ids->size(); ++i) { result += string{Base64Str<MegaClient::SURVEYHANDLE>(ids->get(i))} + ','; } // Remove the ending "," if (!result.empty()) result.pop_back(); return result; }; const string attributeValue{joinWithCommaInB64(ids)}; client->putua(ATTR_ENABLE_TEST_SURVEYS, reinterpret_cast<const byte*>(attributeValue.c_str()), unsigned(attributeValue.size()), -1, UNDEF, 0, 0, [this, request](Error e) { fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); } #ifdef ENABLE_SYNC error MegaApiImpl::performRequest_getSyncStalls(MegaRequestPrivate* request) { const auto completion = [this, request](std::unique_ptr<SyncProblems> problems) { mAddressedStallFilter.removeOldFilters(client->syncs.completedPassCount.load()); // If the user already addressed some sync issues but the sync hasn't made another pass // to generate a new stall list, then the filter will hide those for now if (const auto getMap = request->getFlag(); getMap) { auto stallsMap = std::make_unique<MegaSyncStallMapPrivate>(std::move(*problems), mAddressedStallFilter); request->setMegaSyncStallMap(std::move(stallsMap)); } else { auto stallsList = std::make_unique<MegaSyncStallListPrivate>(std::move(*problems), mAddressedStallFilter); request->setMegaSyncStallList(std::move(stallsList)); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(API_OK)); }; client->syncs.getSyncProblems(std::move(completion), true); return API_OK; } #endif void MegaApiImpl::enableTestSurveys(const MegaHandleList* surveyHandles, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_SET_ATTR_USER, listener); request->setParamType(MegaApi::USER_ATTR_ENABLE_TEST_SURVEYS); request->setMegaHandleList(surveyHandles); request->performRequest = [this, request]() { return performRequest_setAttrUser(request); }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getMyIp(MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_MY_IP, listener); request->performRequest = [this, request]() { client->getMyIp( [this, request](const Error& e, string&& countryCode, string&& ipAddress) { if (!e) { request->setName(countryCode.c_str()); request->setText(ipAddress.c_str()); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getSubscriptionCancellationDetails(const char* originalTransactionId, unsigned int gatewayId, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SUBSCRIPTION_CANCELLATION_DETAILS, listener); request->setText(originalTransactionId); request->setNumber(gatewayId); request->performRequest = [this, request]() { const char* transactionId = request->getText(); unsigned int gateway = static_cast<unsigned int>(request->getNumber()); client->getSubscriptionCancellationDetails( transactionId, gateway, [this, request](const Error& e, std::string&& originalTransactionId, int expiresDate, int cancelledDate) { if (!e) { request->setText(originalTransactionId.c_str()); request->setNumber(static_cast<long long>(expiresDate)); request->setNumDetails(cancelledDate); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } void MegaApiImpl::getDiscountCodeInformation(const char* discountCode, MegaRequestListener* listener) { MegaRequestPrivate* request = new MegaRequestPrivate(MegaRequest::TYPE_GET_DISCOUNT_CODE_INFORMATION, listener); request->setText(discountCode); request->performRequest = [this, request]() { const char* code = request->getText(); if (!code) { LOG_err << "getDiscountCodeInformation: Invalid discount code provided"; return API_EARGS; } client->getDiscountCodeInformation( code, [this, code, request](const Error& e, DiscountCodeInfoExtended&& info) { if (!e) { assert(info.alfanumDiscountCode == code); request->setMegaDiscountCodeInfo( std::make_unique<MegaDiscountCodeInfoPrivate>(std::move(info))); } fireOnRequestFinish(request, std::make_unique<MegaErrorPrivate>(e)); }); return API_OK; }; requestQueue.push(request); waiter->notify(); } /* END MEGAAPIIMPL */ int TransferQueue::getLastPushedTag() const { return lastPushedTransferTag; } TransferQueue::TransferQueue() { } void TransferQueue::push(MegaTransferPrivate *transfer) { std::lock_guard<std::mutex> g(mutex); transfers.push_back(transfer); transfer->setPlaceInQueue(++lastPushedTransferTag); } void TransferQueue::push_front(MegaTransferPrivate *transfer) { std::lock_guard<std::mutex> g(mutex); transfers.push_front(transfer); } bool TransferQueue::empty() { std::lock_guard<std::mutex> g(mutex); return transfers.empty(); } size_t TransferQueue::size() { std::lock_guard<std::mutex> g(mutex); return transfers.size(); } void TransferQueue::clear() { std::lock_guard<std::mutex> g(mutex); for (auto& t: transfers) { delete t; t = nullptr; } transfers.clear(); } MegaTransferPrivate *TransferQueue::pop() { std::lock_guard<std::mutex> g(mutex); if(transfers.empty()) { return NULL; } MegaTransferPrivate *transfer = transfers.front(); transfers.pop_front(); return transfer; } std::vector<MegaTransferPrivate *> TransferQueue::popUpTo(int lastQueuedTransfer, int direction) { std::lock_guard<std::mutex> g(mutex); std::vector<MegaTransferPrivate*> toret; for (auto it = transfers.begin(); it != transfers.end();) { MegaTransferPrivate *transfer = *it; if (transfer->getPlaceInQueue() > lastQueuedTransfer) { break; } if (!transfer->isSyncTransfer() && transfer->getType() == direction) { toret.push_back(transfer); it = transfers.erase(it); } else { it++; } } return toret; } void TransferQueue::removeWithFolderTag(int folderTag, std::function<void(MegaTransferPrivate *)> callback) { // We need to lock the TransferQueue's mutex or it's not safe to iterate transfers. // However this is risky because we are making callbacks with it locked // We shouldn't cause a deadlock wih the impl mutex because that one is always locked before calling here. // However the callback (including its calls to fireOnXYZ() ) must be careful not to lock any mutex which // may have been locked during other MegaApi function calls. std::lock_guard<std::mutex> g(mutex); for (auto it = transfers.begin(); it != transfers.end();) { if ((*it)->getFolderTransferTag() == folderTag) { if (callback) { callback(*it); } it = transfers.erase(it); } else { it++; } } } void TransferQueue::removeListener(MegaTransferListener *listener) { std::lock_guard<std::mutex> g(mutex); std::deque<MegaTransferPrivate *>::iterator it = transfers.begin(); while(it != transfers.end()) { MegaTransferPrivate *transfer = (*it); if(transfer->getListener() == listener) transfer->setListener(NULL); it++; } } void TransferQueue::setAllCancelled(CancelToken cancelled, int direction) { std::lock_guard<std::mutex> g(mutex); for (auto& t : transfers) { if (t->getType() == direction && !t->isSyncTransfer() && !t->isStreamingTransfer()) { t->setCancelToken(cancelled); } } } void RequestQueue::RetainedRequest::removeListener(MegaRequestListener* listener) { if (mRequest && mRequest->getListener() == listener) mRequest->setListener(NULL); } void RequestQueue::RetainedRequest::removeListener(MegaScheduledCopyListener* listener) { if (mRequest && mRequest->getBackupListener() == listener) mRequest->setBackupListener(NULL); } RequestQueue::ScopedRetainingRequest::ScopedRetainingRequest( RequestQueue::RetainedRequest& retainedRequest, MegaRequestPrivate* request): mRetainedRequest{retainedRequest} { mRetainedRequest.set(request); } RequestQueue::ScopedRetainingRequest::~ScopedRetainingRequest() { mRetainedRequest.clear(); } RequestQueue::RequestQueue() { } std::unique_ptr<RequestQueue::ScopedRetainingRequest> RequestQueue::scopedRetainingRequest(MegaRequestPrivate* request) { std::lock_guard<std::mutex> guard(mutex); return std::make_unique<RequestQueue::ScopedRetainingRequest>(this->retainedRequest, request); } void RequestQueue::push(MegaRequestPrivate *request) { std::lock_guard<std::mutex> g(mutex); requests.push_back(request); } void RequestQueue::push(std::unique_ptr<MegaRequestPrivate> request) { std::lock_guard<std::mutex> guard(mutex); requests.push_back(request.get()); request.release(); } void RequestQueue::push_front(MegaRequestPrivate *request) { std::lock_guard<std::mutex> g(mutex); requests.push_front(request); } MegaRequestPrivate *RequestQueue::pop() { std::lock_guard<std::mutex> g(mutex); if(requests.empty()) { return NULL; } MegaRequestPrivate *request = requests.front(); requests.pop_front(); return request; } MegaRequestPrivate *RequestQueue::front() { std::lock_guard<std::mutex> g(mutex); if(requests.empty()) { return NULL; } MegaRequestPrivate *request = requests.front(); return request; } void RequestQueue::removeListener(MegaRequestListener *listener) { std::lock_guard<std::mutex> g(mutex); std::deque<MegaRequestPrivate *>::iterator it = requests.begin(); while(it != requests.end()) { MegaRequestPrivate *request = (*it); if(request->getListener()==listener) request->setListener(NULL); it++; } retainedRequest.removeListener(listener); } void RequestQueue::removeListener(MegaScheduledCopyListener *listener) { std::lock_guard<std::mutex> g(mutex); std::deque<MegaRequestPrivate *>::iterator it = requests.begin(); while(it != requests.end()) { MegaRequestPrivate *request = (*it); if(request->getBackupListener()==listener) request->setBackupListener(NULL); it++; } retainedRequest.removeListener(listener); } MegaHashSignatureImpl::MegaHashSignatureImpl(const char *base64Key) { hashSignature = new HashSignature(new Hash()); asymmCypher = new AsymmCipher(); string pubks; int len = int(strlen(base64Key)/4*3+3); pubks.resize(static_cast<size_t>(len)); pubks.resize(static_cast<size_t>(Base64::atob(base64Key, (byte*)pubks.data(), len))); asymmCypher->setkey(AsymmCipher::PUBKEY,(byte*)pubks.data(), int(pubks.size())); } MegaHashSignatureImpl::~MegaHashSignatureImpl() { delete hashSignature; delete asymmCypher; } void MegaHashSignatureImpl::init() { hashSignature->get(asymmCypher, NULL, 0); } void MegaHashSignatureImpl::add(const char *data, unsigned size) { hashSignature->add((const byte *)data, size); } bool MegaHashSignatureImpl::checkSignature(const char *base64Signature) { char signature[512]; int l = Base64::atob(base64Signature, (byte *)signature, sizeof(signature)); if(l != sizeof(signature)) return false; return hashSignature->checksignature(asymmCypher, (const byte *)signature, sizeof(signature)); } int MegaAccountDetailsPrivate::getProLevel() { for (const auto& plan: details.plans) { if (plan.isProPlan()) { return plan.level; } } return MegaAccountDetails::ACCOUNT_TYPE_FREE; } int64_t MegaAccountDetailsPrivate::getProExpiration() { return details.pro_until; } int MegaAccountDetailsPrivate::getSubscriptionStatus() { // Consider only the first subscription if (!details.subscriptions.empty()) { char subType = details.subscriptions.front().type; if (subType == 'S') { return MegaAccountSubscription::SUBSCRIPTION_STATUS_VALID; } if (subType == 'R') { return MegaAccountSubscription::SUBSCRIPTION_STATUS_INVALID; } } return MegaAccountSubscription::SUBSCRIPTION_STATUS_NONE; } int64_t MegaAccountDetailsPrivate::getSubscriptionRenewTime() { // Consider only the first subscription if (!details.subscriptions.empty()) { return details.subscriptions.front().renew; } return MEGA_INVALID_TIMESTAMP; } char *MegaAccountDetailsPrivate::getSubscriptionMethod() { // Consider only the first subscription if (!details.subscriptions.empty()) { return MegaApi::strdup(details.subscriptions.front().paymentMethod.c_str()); } return new char[1]{'\0'}; } int MegaAccountDetailsPrivate::getSubscriptionMethodId() { // Consider only the first subscription if (!details.subscriptions.empty()) { return details.subscriptions.front().paymentMethodId; } return MegaAccountDetails::ACCOUNT_TYPE_FREE; } char *MegaAccountDetailsPrivate::getSubscriptionCycle() { // Consider only the first subscription if (!details.subscriptions.empty()) { return MegaApi::strdup(details.subscriptions.front().cycle.c_str()); } return new char[1]{'\0'}; } long long MegaAccountDetailsPrivate::getStorageMax() { return details.storage_max; } long long MegaAccountDetailsPrivate::getStorageUsed() { return details.storage_used; } long long MegaAccountDetailsPrivate::getVersionStorageUsed() { long long total = 0; handlestorage_map::iterator it; for (it = details.storage.begin(); it != details.storage.end(); it++) { total += it->second.version_bytes; } return total; } long long MegaAccountDetailsPrivate::getTransferMax() { return details.transfer_max; } long long MegaAccountDetailsPrivate::getTransferOwnUsed() { return details.transfer_own_used; } long long MegaAccountDetailsPrivate::getTransferSrvUsed() { return details.transfer_srv_used; } long long MegaAccountDetailsPrivate::getTransferUsed() { long long total = details.transfer_srv_used + details.transfer_own_used + getTemporalBandwidth(); // in case the total exceed the maximum allowance (due to the free IP-based quota)... if (details.transfer_max && total > details.transfer_max) //do not limit for free user (no max allowance configured) { total = details.transfer_max; } return total; } int MegaAccountDetailsPrivate::getNumUsageItems() { return int(details.storage.size()); } long long MegaAccountDetailsPrivate::getStorageUsed(MegaHandle handle) { handlestorage_map::iterator it = details.storage.find(handle); if (it != details.storage.end()) { return it->second.bytes; } else { return 0; } } long long MegaAccountDetailsPrivate::getNumFiles(MegaHandle handle) { handlestorage_map::iterator it = details.storage.find(handle); if (it != details.storage.end()) { return it->second.files; } else { return 0; } } long long MegaAccountDetailsPrivate::getNumFolders(MegaHandle handle) { handlestorage_map::iterator it = details.storage.find(handle); if (it != details.storage.end()) { return it->second.folders; } else { return 0; } } long long MegaAccountDetailsPrivate::getVersionStorageUsed(MegaHandle handle) { handlestorage_map::iterator it = details.storage.find(handle); if (it != details.storage.end()) { return it->second.version_bytes; } else { return 0; } } long long MegaAccountDetailsPrivate::getNumVersionFiles(MegaHandle handle) { handlestorage_map::iterator it = details.storage.find(handle); if (it != details.storage.end()) { return it->second.version_files; } else { return 0; } } MegaAccountDetails* MegaAccountDetailsPrivate::copy() { return new MegaAccountDetailsPrivate(&details); } int MegaAccountDetailsPrivate::getNumBalances() const { return int(details.balances.size()); } MegaAccountBalance *MegaAccountDetailsPrivate::getBalance(int i) const { if ((unsigned int)i < details.balances.size()) { return MegaAccountBalancePrivate::fromAccountBalance(&(details.balances[(unsigned int)i])); } return NULL; } int MegaAccountDetailsPrivate::getNumSessions() const { return int(details.sessions.size()); } MegaAccountSession *MegaAccountDetailsPrivate::getSession(int i) const { if ((unsigned int)i < details.sessions.size()) { return MegaAccountSessionPrivate::fromAccountSession(&(details.sessions[(unsigned int)i])); } return NULL; } int MegaAccountDetailsPrivate::getNumPurchases() const { return int(details.purchases.size()); } MegaAccountPurchase *MegaAccountDetailsPrivate::getPurchase(int i) const { if ((unsigned int)i < details.purchases.size()) { return MegaAccountPurchasePrivate::fromAccountPurchase(&(details.purchases[(unsigned int)i])); } return NULL; } int MegaAccountDetailsPrivate::getNumTransactions() const { return int(details.transactions.size()); } MegaAccountTransaction *MegaAccountDetailsPrivate::getTransaction(int i) const { if ((unsigned int)i < details.transactions.size()) { return MegaAccountTransactionPrivate::fromAccountTransaction(&(details.transactions[(unsigned int)i])); } return NULL; } int MegaAccountDetailsPrivate::getTemporalBandwidthInterval() { return int(details.transfer_hist.size()); } long long MegaAccountDetailsPrivate::getTemporalBandwidth() { long long result = 0; for (unsigned int i = 0; i < details.transfer_hist.size(); i++) { result += details.transfer_hist[i]; } return result; } bool MegaAccountDetailsPrivate::isTemporalBandwidthValid() { return details.transfer_hist_valid; } int MegaAccountDetailsPrivate::getNumActiveFeatures() const { return static_cast<int>(details.activeFeatures.size()); } MegaAccountFeature* MegaAccountDetailsPrivate::getActiveFeature(int featureIndex) const { if (static_cast<size_t>(featureIndex) < details.activeFeatures.size()) { return MegaAccountFeaturePrivate::fromAccountFeature( &(details.activeFeatures[static_cast<size_t>(featureIndex)])); } return nullptr; } int64_t MegaAccountDetailsPrivate::getSubscriptionLevel() const { // Consider only the first subscription if (!details.subscriptions.empty()) { return details.subscriptions.front().level; } return MegaAccountDetails::ACCOUNT_TYPE_FREE; } MegaStringIntegerMap* MegaAccountDetailsPrivate::getSubscriptionFeatures() const { MegaStringIntegerMapPrivate* subscriptionFeatures = new MegaStringIntegerMapPrivate(); // Consider only the first subscription if (!details.subscriptions.empty()) { for (const auto& f: details.subscriptions.front().features) { // Since all the received features are active, all have a 1. // Hardcoded to keep the interface. subscriptionFeatures->set(f, 1); } } return subscriptionFeatures; } int MegaAccountDetailsPrivate::getNumSubscriptions() const { return static_cast<int>(details.subscriptions.size()); } MegaAccountSubscription* MegaAccountDetailsPrivate::getSubscription(int featureIndex) const { size_t index = static_cast<size_t>(featureIndex); if (index < details.subscriptions.size()) { return MegaAccountSubscriptionPrivate::fromAccountSubscription( details.subscriptions[index]); } return nullptr; } int MegaAccountDetailsPrivate::getNumPlans() const { return static_cast<int>(details.plans.size()); } MegaAccountPlan* MegaAccountDetailsPrivate::getPlan(int featureIndex) const { size_t index = static_cast<size_t>(featureIndex); if (index < details.plans.size()) { return MegaAccountPlanPrivate::fromAccountPlan(details.plans[index]); } return nullptr; } MegaErrorPrivate::MegaErrorPrivate(int errorCode) : MegaError(errorCode) { } MegaErrorPrivate::MegaErrorPrivate(int errorCode, SyncError syncError) : MegaError(errorCode, syncError) { } #ifdef ENABLE_SYNC MegaErrorPrivate::MegaErrorPrivate(int errorCode, MegaSync::Error syncError) : MegaError(errorCode, syncError) { } #endif MegaErrorPrivate::MegaErrorPrivate(int errorCode, long long value) : MegaError(errorCode) , mValue(value) { } MegaErrorPrivate::MegaErrorPrivate(const Error& err) : MegaError(err) , mValue(0) , mUserStatus(err.getUserStatus()) , mLinkStatus(err.getLinkStatus()) { } MegaErrorPrivate::MegaErrorPrivate(fuse::MountResult result) : MegaError(result == fuse::MOUNT_SUCCESS ? API_OK : API_EFAILED) , mMountResult(result) { } MegaErrorPrivate::MegaErrorPrivate(const MegaError &megaError) : MegaError(megaError) , mValue(megaError.getValue()) , mUserStatus(megaError.getUserStatus()) , mLinkStatus(megaError.getLinkStatus()) { } MegaErrorPrivate::~MegaErrorPrivate() { } MegaError* MegaErrorPrivate::copy() const { return new MegaErrorPrivate(*this); } int MegaErrorPrivate::getErrorCode() const { return errorCode; } int MegaErrorPrivate::getMountResult() const { return static_cast<int>(mMountResult); } long long MegaErrorPrivate::getValue() const { return mValue; } bool MegaErrorPrivate::hasExtraInfo() const { return mUserStatus != MegaError::UserErrorCode::USER_ETD_UNKNOWN || mLinkStatus != MegaError::LinkErrorCode::LINK_UNKNOWN; } long long MegaErrorPrivate::getUserStatus() const { return mUserStatus; } long long MegaErrorPrivate::getLinkStatus() const { return mLinkStatus; } const char* MegaErrorPrivate::getErrorString() const { return MegaError::getErrorString(errorCode); } const char* MegaErrorPrivate::toString() const { return getErrorString(); } MegaPricingPrivate::~MegaPricingPrivate() {} int MegaPricingPrivate::getNumProducts() { return int(products.size()); } handle MegaPricingPrivate::getHandle(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].productHandle; } return UNDEF; } int MegaPricingPrivate::getProLevel(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return static_cast<int>(products[static_cast<size_t>(productIndex)].proLevel); } return 0; } int MegaPricingPrivate::getGBStorage(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].gbStorage; } return 0; } int MegaPricingPrivate::getGBTransfer(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].gbTransfer; } return 0; } int MegaPricingPrivate::getMonths(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return static_cast<int>(products[static_cast<size_t>(productIndex)].months); } return 0; } int MegaPricingPrivate::getAmount(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { const auto v = products[static_cast<size_t>(productIndex)].amount; return static_cast<int>(std::round(v)); } return 0; } int mega::MegaPricingPrivate::getLocalPrice(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { const auto v = products[static_cast<size_t>(productIndex)].localPrice; return static_cast<int>(std::round(v)); } return 0; } double mega::MegaPricingPrivate::getAmountWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].amount; } return 0.0; } double mega::MegaPricingPrivate::getLocalPriceWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].localPrice; } return 0.0; } double mega::MegaPricingPrivate::getAmountMonthWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].amountMonth; } return 0.0; } double MegaPricingPrivate::getPriceNetWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].priceNet; } return 0.0; } double MegaPricingPrivate::getLocalPriceNetWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].localPriceNet; } return 0.0; } double MegaPricingPrivate::getMonthlyBasePriceNetWithDecimals(const int productIndex) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].monthlyBasePriceNet; } return 0.0; } bool MegaPricingPrivate::hasMobileOffers(int productIndex) const { if (auto index = static_cast<size_t>(productIndex); index < products.size()) { return products[index].mobileOffer.has_value(); } return false; } std::string mega::MegaPricingPrivate::getMobileOfferId(int productIndex) const { if (auto index = static_cast<size_t>(productIndex); index < products.size() && products[index].mobileOffer.has_value()) { return products[index].mobileOffer->id; } return {}; } bool mega::MegaPricingPrivate::hasMobileOfferUat(int productIndex) const { if (auto index = static_cast<size_t>(productIndex); index < products.size() && products[index].mobileOffer.has_value()) { return products[index].mobileOffer->uat; } return {}; } const char *MegaPricingPrivate::getDescription(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].description.c_str(); } return nullptr; } const char *MegaPricingPrivate::getIosID(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].iosid.c_str(); } return nullptr; } const char *MegaPricingPrivate::getAndroidID(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].androidid.c_str(); } return nullptr; } bool MegaPricingPrivate::isBusinessType(int productIndex) { return isType(productIndex, PlanType::BUSINESS); } bool MegaPricingPrivate::isFeaturePlan(int productIndex) const { return isType(productIndex, PlanType::FEATURE); } bool MegaPricingPrivate::isType(int productIndex, unsigned t) const { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { return products[static_cast<size_t>(productIndex)].planType == t; } return false; } int MegaPricingPrivate::getAmountMonth(int productIndex) { if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size()) { const auto v = products[static_cast<size_t>(productIndex)].amountMonth; return static_cast<int>(std::round(v)); } return 0; } MegaPricing *MegaPricingPrivate::copy() { MegaPricingPrivate *megaPricing = new MegaPricingPrivate(); megaPricing->products = this->products; return megaPricing; } void MegaPricingPrivate::addProduct(const Product& product) { this->products.push_back(product); } int MegaPricingPrivate::getGBStoragePerUser(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->gbStoragePerUser; } return 0; } int MegaPricingPrivate::getGBTransferPerUser(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->gbTransferPerUser; } return 0; } unsigned int MegaPricingPrivate::getMinUsers(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->minUsers; } return 0; } unsigned int MegaPricingPrivate::getPricePerUser(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->pricePerUser; } return 0; } unsigned int MegaPricingPrivate::getLocalPricePerUser(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->localPricePerUser; } return 0; } unsigned int MegaPricingPrivate::getPricePerStorage(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->pricePerStorage; } return 0; } unsigned int MegaPricingPrivate::getLocalPricePerStorage(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->localPricePerStorage; } return 0; } int MegaPricingPrivate::getGBPerStorage(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->gbPerStorage; } return 0; } unsigned int MegaPricingPrivate::getPricePerTransfer(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->pricePerTransfer; } return 0; } unsigned int MegaPricingPrivate::getLocalPricePerTransfer(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->localPricePerTransfer; } return 0; } int MegaPricingPrivate::getGBPerTransfer(int productIndex) { // some Pro plans don't have a valid pointer, only business plans if (productIndex >= 0 && static_cast<unsigned int>(productIndex) < products.size() && products[static_cast<size_t>(productIndex)].businessPlan) { return products[static_cast<size_t>(productIndex)].businessPlan->gbPerTransfer; } return 0; } MegaStringIntegerMap* MegaPricingPrivate::getFeatures(int productIndex) const { if ((unsigned)productIndex > products.size()) { return nullptr; } MegaStringIntegerMapPrivate* returnFeatures = new MegaStringIntegerMapPrivate(); for (const auto& feature: products[static_cast<size_t>(productIndex)].features) { returnFeatures->set(feature.first, feature.second); } return returnFeatures; } unsigned int MegaPricingPrivate::getTestCategory(int productIndex) const { if ((unsigned)productIndex < products.size()) { return products[static_cast<size_t>(productIndex)].testCategory; } return 0; } unsigned int MegaPricingPrivate::getTrialDurationInDays(int productIndex) const { if ((unsigned)productIndex < products.size()) { return products[static_cast<size_t>(productIndex)].trialDays; } return 0; } const InstantDiscounts* MegaPricingPrivate::getInstantDiscounts(const int productIndex) const { if (productIndex < 0 || static_cast<size_t>(productIndex) >= products.size()) { return nullptr; } if (const auto& opt = products[static_cast<size_t>(productIndex)].instantDiscounts; opt.has_value()) { return &opt.value(); } return nullptr; } bool MegaPricingPrivate::hasDiscount(int productIndex) const { return getInstantDiscounts(productIndex); } const char* MegaPricingPrivate::getDiscountCode(int productIndex) const { if (const auto* discounts = getInstantDiscounts(productIndex)) { return discounts->discountCode.c_str(); } return nullptr; } const char* MegaPricingPrivate::getDiscountName(int productIndex) const { if (const auto* discounts = getInstantDiscounts(productIndex)) { return discounts->discountName.c_str(); } return nullptr; } int MegaPricingPrivate::getDiscountGroup(int productIndex) const { if (const auto* discounts = getInstantDiscounts(productIndex)) { return discounts->discountGroup; } return -1; } int MegaPricingPrivate::getDiscountMonths(int productIndex) const { if (const auto* discounts = getInstantDiscounts(productIndex)) { return discounts->discountMonths; } return -1; } int MegaPricingPrivate::getDiscountPercentage(int productIndex) const { if (const auto* discounts = getInstantDiscounts(productIndex)) { return discounts->discountPercentage; } return -1; } MegaCurrencyPrivate::~MegaCurrencyPrivate() { } MegaCurrency *MegaCurrencyPrivate::copy() { return new MegaCurrencyPrivate(*this); } const char *MegaCurrencyPrivate::getCurrencySymbol() { return mCurrencyData.currencySymbol.c_str(); } const char *MegaCurrencyPrivate::getCurrencyName() { return mCurrencyData.currencyName.c_str(); } const char *MegaCurrencyPrivate::getLocalCurrencySymbol() { return mCurrencyData.localCurrencySymbol.c_str(); } const char *MegaCurrencyPrivate::getLocalCurrencyName() { return mCurrencyData.localCurrencyName.c_str(); } void MegaCurrencyPrivate::setCurrency(std::unique_ptr<CurrencyData> data) { assert(data); mCurrencyData = *data; } #ifdef ENABLE_SYNC MegaSyncPrivate::MegaSyncPrivate(const SyncConfig& config, MegaClient* client /* never null */) : mRunState(MegaSync::SyncRunningState(config.mRunState)) , mType(static_cast<SyncType>(config.getType())) { this->megaHandle = config.mRemoteNode.as8byte(); this->localFolder = NULL; setLocalFolder(config.getLocalPath().toPath(false).c_str()); this->mName= NULL; if (!config.mName.empty()) { setName(config.mName.c_str()); } else { //using leaf name of localpath as name: setName(config.getLocalPath().leafName().toName(*client->fsaccess).c_str()); } this->lastKnownMegaFolder = NULL; setLastKnownMegaFolder(config.mOriginalPathOfRemoteRootNode.c_str()); setError(config.mError < 0 ? 0 : config.mError); setWarning(config.mWarning); setBackupId(config.mBackupId); } MegaSyncPrivate::MegaSyncPrivate(MegaSyncPrivate *sync) : mRunState(sync->mRunState) , mType(sync->mType) { this->setBackupId(sync->mBackupId); this->localFolder = NULL; this->mName = NULL; this->setLocalFolder(sync->getLocalFolder()); this->setName(sync->getName()); this->lastKnownMegaFolder = NULL; this->setLastKnownMegaFolder(sync->getLastKnownMegaFolder()); this->setMegaHandle(sync->getMegaHandle()); this->setError(sync->getError()); this->setWarning(sync->getWarning()); } MegaSyncPrivate::~MegaSyncPrivate() { delete [] localFolder; delete [] mName; delete [] lastKnownMegaFolder; } MegaSync *MegaSyncPrivate::copy() { return new MegaSyncPrivate(this); } MegaHandle MegaSyncPrivate::getMegaHandle() const { return megaHandle; } void MegaSyncPrivate::setMegaHandle(MegaHandle handle) { this->megaHandle = handle; } const char *MegaSyncPrivate::getLocalFolder() const { return localFolder; } void MegaSyncPrivate::setLocalFolder(const char *path) { if (localFolder) { delete [] localFolder; } localFolder = MegaApi::strdup(path); } const char *MegaSyncPrivate::getName() const { return mName; } void MegaSyncPrivate::setName(const char *name) { if (mName) { delete [] mName; } mName = MegaApi::strdup(name); } const char *MegaSyncPrivate::getLastKnownMegaFolder() const { return lastKnownMegaFolder; } void MegaSyncPrivate::setLastKnownMegaFolder(const char *path) { if (lastKnownMegaFolder) { delete [] lastKnownMegaFolder; } lastKnownMegaFolder = MegaApi::strdup(path); } MegaHandle MegaSyncPrivate::getBackupId() const { return mBackupId; } void MegaSyncPrivate::setBackupId(MegaHandle backupId) { this->mBackupId = backupId; } int MegaSyncPrivate::getError() const { return mError; } void MegaSyncPrivate::setError(int error) { mError = error; } int MegaSyncPrivate::getWarning() const { return mWarning; } void MegaSyncPrivate::setWarning(int warning) { mWarning = warning; } int MegaSyncPrivate::getType() const { return mType; } void MegaSyncPrivate::setType(MegaSync::SyncType type) { mType = type; } int MegaSyncPrivate::getRunState() const { return mRunState; } MegaSyncListPrivate::MegaSyncListPrivate() { list = NULL; s = 0; } MegaSyncListPrivate::MegaSyncListPrivate(MegaSyncPrivate** newlist, int size) { list = NULL; s = size; if(!size) return; list = new MegaSync*[static_cast<size_t>(size)]; for(int i=0; i<size; i++) list[i] = newlist[i]->copy(); } MegaSyncListPrivate::MegaSyncListPrivate(const MegaSyncListPrivate *syncList) { s = syncList->size(); if (!s) { list = NULL; return; } list = new MegaSync*[static_cast<size_t>(s)]; for (int i = 0; i<s; i++) { list[i] = new MegaSyncPrivate(static_cast<MegaSyncPrivate *>(syncList->get(i))); } } MegaSyncListPrivate::~MegaSyncListPrivate() { if(!list) return; for(int i=0; i<s; i++) delete list[i]; delete [] list; } MegaSyncList *MegaSyncListPrivate::copy() const { return new MegaSyncListPrivate(this); } MegaSync *MegaSyncListPrivate::get(int i) const { if(!list || (i < 0) || (i >= s)) return NULL; return list[i]; } int MegaSyncListPrivate::size() const { return s; } void MegaSyncListPrivate::addSync(MegaSync *sync) { MegaSync** copyList = list; s = s + 1; list = new MegaSync*[static_cast<size_t>(s)]; for (int i = 0; i < s - 1; ++i) { list[i] = copyList[i]; } list[s - 1] = sync->copy(); if (copyList != NULL) { delete [] copyList; } } #endif MegaAccountBalance *MegaAccountBalancePrivate::fromAccountBalance(const AccountBalance *balance) { return new MegaAccountBalancePrivate(balance); } MegaAccountBalancePrivate::~MegaAccountBalancePrivate() { } MegaAccountBalance *MegaAccountBalancePrivate::copy() { return new MegaAccountBalancePrivate(&balance); } double MegaAccountBalancePrivate::getAmount() const { return balance.amount; } char *MegaAccountBalancePrivate::getCurrency() const { return MegaApi::strdup(balance.currency); } MegaAccountBalancePrivate::MegaAccountBalancePrivate(const AccountBalance *balance) { this->balance = *balance; } MegaAccountSession *MegaAccountSessionPrivate::fromAccountSession(const AccountSession *session) { return new MegaAccountSessionPrivate(session); } MegaAccountSessionPrivate::~MegaAccountSessionPrivate() { } MegaAccountSession *MegaAccountSessionPrivate::copy() { return new MegaAccountSessionPrivate(&session); } int64_t MegaAccountSessionPrivate::getCreationTimestamp() const { return session.timestamp; } int64_t MegaAccountSessionPrivate::getMostRecentUsage() const { return session.mru; } char *MegaAccountSessionPrivate::getUserAgent() const { return MegaApi::strdup(session.useragent.c_str()); } char *MegaAccountSessionPrivate::getIP() const { return MegaApi::strdup(session.ip.c_str()); } char *MegaAccountSessionPrivate::getCountry() const { return MegaApi::strdup(session.country); } bool MegaAccountSessionPrivate::isCurrent() const { return session.current != 0; } bool MegaAccountSessionPrivate::isAlive() const { return session.alive != 0; } MegaHandle MegaAccountSessionPrivate::getHandle() const { return session.id; } char *MegaAccountSessionPrivate::getDeviceId() const { return MegaApi::strdup(session.deviceid.c_str()); } MegaAccountSessionPrivate::MegaAccountSessionPrivate(const AccountSession *session) { this->session = *session; } MegaAccountPurchase *MegaAccountPurchasePrivate::fromAccountPurchase(const AccountPurchase *purchase) { return new MegaAccountPurchasePrivate(purchase); } MegaAccountPurchasePrivate::~MegaAccountPurchasePrivate() { } MegaAccountPurchase *MegaAccountPurchasePrivate::copy() { return new MegaAccountPurchasePrivate(&purchase); } int64_t MegaAccountPurchasePrivate::getTimestamp() const { return purchase.timestamp; } char *MegaAccountPurchasePrivate::getHandle() const { return MegaApi::strdup(purchase.handle); } char *MegaAccountPurchasePrivate::getCurrency() const { return MegaApi::strdup(purchase.currency); } double MegaAccountPurchasePrivate::getAmount() const { return purchase.amount; } int MegaAccountPurchasePrivate::getMethod() const { return purchase.method; } MegaAccountPurchasePrivate::MegaAccountPurchasePrivate(const AccountPurchase *purchase) { this->purchase = *purchase; } MegaAccountTransaction *MegaAccountTransactionPrivate::fromAccountTransaction(const AccountTransaction *transaction) { return new MegaAccountTransactionPrivate(transaction); } MegaAccountTransactionPrivate::~MegaAccountTransactionPrivate() { } MegaAccountTransaction *MegaAccountTransactionPrivate::copy() { return new MegaAccountTransactionPrivate(&transaction); } int64_t MegaAccountTransactionPrivate::getTimestamp() const { return transaction.timestamp; } char *MegaAccountTransactionPrivate::getHandle() const { return MegaApi::strdup(transaction.handle); } char *MegaAccountTransactionPrivate::getCurrency() const { return MegaApi::strdup(transaction.currency); } double MegaAccountTransactionPrivate::getAmount() const { return transaction.delta; } MegaAccountTransactionPrivate::MegaAccountTransactionPrivate(const AccountTransaction *transaction) { this->transaction = *transaction; } MegaAccountFeaturePrivate* MegaAccountFeaturePrivate::fromAccountFeature(const AccountFeature* feature) { return new MegaAccountFeaturePrivate(feature); } MegaAccountFeaturePrivate::MegaAccountFeaturePrivate(const AccountFeature* feature) { assert(feature); if (feature) { mFeature = *feature; } } int64_t MegaAccountFeaturePrivate::getExpiry() const { return mFeature.expiryTimestamp; } char* MegaAccountFeaturePrivate::getId() const { return MegaApi::strdup(mFeature.featureId.c_str()); } MegaAccountSubscriptionPrivate* MegaAccountSubscriptionPrivate::fromAccountSubscription(const AccountSubscription& subscription) { return new MegaAccountSubscriptionPrivate(subscription); } MegaAccountSubscriptionPrivate::MegaAccountSubscriptionPrivate( const AccountSubscription& subscription) { mSubscription = subscription; } char* MegaAccountSubscriptionPrivate::getId() const { return MegaApi::strdup(mSubscription.id.c_str()); } int MegaAccountSubscriptionPrivate::getStatus() const { if (mSubscription.type == 'S') { return MegaAccountSubscription::SUBSCRIPTION_STATUS_VALID; } if (mSubscription.type == 'R') { return MegaAccountSubscription::SUBSCRIPTION_STATUS_INVALID; } return MegaAccountSubscription::SUBSCRIPTION_STATUS_NONE; } char* MegaAccountSubscriptionPrivate::getCycle() const { return MegaApi::strdup(mSubscription.cycle.c_str()); } char* MegaAccountSubscriptionPrivate::getPaymentMethod() const { return MegaApi::strdup(mSubscription.paymentMethod.c_str()); } int32_t MegaAccountSubscriptionPrivate::getPaymentMethodId() const { return mSubscription.paymentMethodId; } int64_t MegaAccountSubscriptionPrivate::getRenewTime() const { return mSubscription.renew; } int32_t MegaAccountSubscriptionPrivate::getAccountLevel() const { return mSubscription.level; } MegaStringList* MegaAccountSubscriptionPrivate::getFeatures() const { MegaStringListPrivate* subscriptionFeatures = new MegaStringListPrivate(); for (const auto& f: mSubscription.features) { subscriptionFeatures->add(f.c_str()); } return subscriptionFeatures; } bool MegaAccountSubscriptionPrivate::isTrial() const { return mSubscription.isTrial; } MegaAccountPlanPrivate* MegaAccountPlanPrivate::fromAccountPlan(const AccountPlan& plan) { return new MegaAccountPlanPrivate(plan); } MegaAccountPlanPrivate::MegaAccountPlanPrivate(const AccountPlan& plan) { mPlan = plan; } bool MegaAccountPlanPrivate::isProPlan() const { return (mPlan.level > MegaAccountDetails::ACCOUNT_TYPE_FREE && mPlan.level != MegaAccountDetails::ACCOUNT_TYPE_FEATURE); } int32_t MegaAccountPlanPrivate::getAccountLevel() const { return mPlan.level; } MegaStringList* MegaAccountPlanPrivate::getFeatures() const { MegaStringListPrivate* planFeatures = new MegaStringListPrivate(); for (const auto& f: mPlan.features) { planFeatures->add(f.c_str()); } return planFeatures; } int64_t MegaAccountPlanPrivate::getExpirationTime() const { return mPlan.expiration; } int32_t MegaAccountPlanPrivate::getType() const { return mPlan.type; } char* MegaAccountPlanPrivate::getId() const { return MegaApi::strdup(mPlan.subscriptionId.c_str()); } bool MegaAccountPlanPrivate::isTrial() const { return mPlan.isTrial; } ExternalInputStream::ExternalInputStream(MegaInputStream *inputStream) { this->inputStream = inputStream; } m_off_t ExternalInputStream::size() { return inputStream->getSize(); } bool ExternalInputStream::read(byte *buffer, unsigned size) { return inputStream->read((char *)buffer, size); } MegaTreeProcCopy::MegaTreeProcCopy(MegaClient *client) { this->client = client; } void MegaTreeProcCopy::allocnodes() { nn.resize(nc); allocated = true; } bool MegaTreeProcCopy::processMegaNode(MegaNode *n) { if (allocated) { // prepare map of attributes AttrMap attrs; { string sname = n->getName(); LocalPath::utf8_normalize(&sname); attrs.map['n'] = sname; } { string sfp = MegaNodePrivate::removeAppPrefixFromFingerprint(n->getFingerprint()); if (!sfp.empty()) { attrs.map['c'] = std::move(sfp); } } client->putnodes_prepareCopy(nn, nc, static_cast<nodetype_t>(n->getType()), n->getHandle(), n->getParentHandle() ? n->getParentHandle() : UNDEF, *n->getNodeKey(), attrs, false, n->isPublic()); } else { nc++; } return true; } MegaFolderUploadController::MegaFolderUploadController(MegaApiImpl* api, MegaTransferPrivate* t): MegaRecursiveOperation(api->getMegaClient()) { fsaccess = mega::createFSA(); megaApi = api; transfer = t; listener = t->getListener(); recursive = 0; tag = transfer->getTag(); mMainThreadId = std::this_thread::get_id(); assert(mMainThreadId == megaApi->threadId); } void MegaFolderUploadController::start(MegaNode*) { assert(mMainThreadId == std::this_thread::get_id()); transfer->setFolderTransferTag(-1); transfer->setStartTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_QUEUED); megaApi->fireOnTransferStart(transfer); unique_ptr<MegaNode> parent(megaApi->getNodeByHandle(transfer->getParentHandle())); if (!parent) { transfer->setState(MegaTransfer::STATE_FAILED); megaApi->fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_EARGS)); return; } // set transfer target node as megaNode of root tree (mUploadTree) mUploadTree.megaNode.reset(parent.release()); // create a subtree for the folder that we want to upload unique_ptr<Tree> newTreeNode(new Tree); LocalPath path = LocalPath::fromAbsolutePath(transfer->getPath()); auto leaf = transfer->getFileName() ? transfer->getFileName() : path.leafName().toPath(true); // if folder node already exists in remote, set it as new subtree's megaNode, otherwise call putnodes_prepareOneFolder newTreeNode->megaNode.reset(megaApi->getChildNode(mUploadTree.megaNode.get(), leaf.c_str())); if (newTreeNode->megaNode && newTreeNode->megaNode->getType() == MegaNode::TYPE_FILE) { // there's a node (TYPE_FILE) with the same name in the destination path, we will make fail transfer transfer->setState(MegaTransfer::STATE_FAILED); megaApi->fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(API_EEXIST)); return; } else if (!newTreeNode->megaNode) // there's no other node with the same name in the destination path { newTreeNode->folderName = leaf; newTreeNode->fsType = fsaccess->getlocalfstype(path); megaapiThreadClient()->putnodes_prepareOneFolder(&newTreeNode->newnode, leaf, false); newTreeNode->newnode.nodehandle = nextUploadId(); newTreeNode->newnode.parenthandle = UNDEF; } // else => if there's another node (TYPE_FOLDER) with the same name, in the destination path, the content of both folders will be merged // add the tree above, to subtrees vector for root tree mUploadTree.subtrees.push_back(std::move(newTreeNode)); // it's mandatory to notify stage change from MegaApiImpl's thread to avoid deadlocks and other issues notifyStage(MegaTransfer::STAGE_SCAN); mWorkerThread = std::thread ([this, path]() { // recurse all subfolders on disk, building up tree structure to match // not yet existing folders get a temporary upload id instead of a handle uint32_t foldercount = 0; uint32_t filecount = 0; LocalPath lp = path; scanFolder_result scanResult = scanFolder(*mUploadTree.subtrees.front(), lp, foldercount, filecount); // mCompletionForMegaApiThread lambda will be executed on the MegaApiImpl's thread // use a weak_ptr in case this 'this' object doesn't exist anymore when lambda starts executing weak_ptr<MegaFolderUploadController> weak_this = shared_from_this(); // if the thread runs, we always queue a function to execute on MegaApi thread for onFinish() // we keep a pointer to it in case we need to execute it early and directly on cancel() mCompletionForMegaApiThread.reset(new ExecuteOnce([this, scanResult, weak_this, filecount]() { // double check our object still exists when completion function starts executing if (!weak_this.lock()) return; assert(weak_this.lock().get() == this); // these next parts must run on MegaApiImpl's thread again, as // genUploadTransfersForFiles or checkCompletion may call the fireOnXYZ() functions assert(mMainThreadId == std::this_thread::get_id()); // make sure the thread is joined. This lets us add error-catching asserts elsewhere. if (mWorkerThread.joinable()) { mWorkerThread.join(); } if (scanResult == scanFolder_failed) { // scan stage could not finish properly, because some dir could not be accessed complete(API_EACCESS); return; } else if (scanResult == scanFolder_cancelled) { complete(API_EINCOMPLETE, true); return; } // create folders in batches, not too many at once // createNextFolderBatch is responsible for starting the transfers once all needed folders (if any) are created. notifyStage(MegaTransfer::STAGE_CREATE_TREE); vector<NewNode> newnodes; #ifndef NDEBUG batchResult r = #endif createNextFolderBatch(mUploadTree, newnodes, filecount, true); assert(r == batchResult_cancelled || r == batchResult_requestSent || r == batchResult_batchesComplete); })); // Queue that function. megaApi->executeOnThread(mCompletionForMegaApiThread); }); } // this method provides a temporal handle useful to indicate putnodes()-local parent linkage handle MegaFolderUploadController::nextUploadId() { return mCurrUploadId = (mCurrUploadId + 1 <= 0xFFFFFFFFFFFF) ? mCurrUploadId + 1 : 0; } void MegaRecursiveOperation::onTransferStart(MegaApi *, MegaTransfer *t) { assert(mMainThreadId == std::this_thread::get_id()); assert(transfer); ++transfersStartedCount; if (transfersStartedCount == transfersTotalCount && !transfer->accessCancelToken().isCancelled() && !startedTransferring) { // Apps expect this one called when all sub-transfers have // been queued in SDK core already notifyStage(MegaTransfer::STAGE_TRANSFERRING_FILES); megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_TRANSFERRING_FILES, 0, 0, unsigned(transfersTotalCount), nullptr, nullptr); startedTransferring = true; } if (transfer) { transfer->setState(t->getState()); transfer->setPriority(t->getPriority()); transfer->setTotalBytes(transfer->getTotalBytes() + t->getTotalBytes()); transfer->setUpdateTime(Waiter::ds); megaApi->fireOnTransferUpdate(transfer); } } void MegaRecursiveOperation::onTransferUpdate(MegaApi *, MegaTransfer *t) { assert(mMainThreadId == std::this_thread::get_id()); assert(transfer); if (transfer) { LOG_verbose << "MegaRecursiveOperation: on transfer update -> adding new progress " << t->getDeltaSize() << " to previous transferred bytes " << transfer->getTransferredBytes() << " -> updated transferred bytes = " << (transfer->getTransferredBytes() + t->getDeltaSize()); transfer->setState(t->getState()); transfer->setPriority(t->getPriority()); transfer->setTransferredBytes(transfer->getTransferredBytes() + t->getDeltaSize()); transfer->setUpdateTime(Waiter::ds); transfer->setSpeed(t->getSpeed()); transfer->setMeanSpeed(t->getMeanSpeed()); megaApi->fireOnTransferUpdate(transfer); } } void MegaRecursiveOperation::onTransferFinish(MegaApi *, MegaTransfer *t, MegaError *e) { assert(mMainThreadId == std::this_thread::get_id()); ++transfersFinishedCount; assert(transfer); if (transfer) { LOG_verbose << "MegaRecursiveOperation: on transfer finish -> adding new progress " << t->getDeltaSize() << " to previous transferred bytes " << transfer->getTransferredBytes() << " -> updated transferred bytes = " << (transfer->getTransferredBytes() + t->getDeltaSize()); transfer->setState(MegaTransfer::STATE_ACTIVE); transfer->setPriority(t->getPriority()); transfer->setTransferredBytes(transfer->getTransferredBytes() + t->getDeltaSize()); transfer->setUpdateTime(Waiter::ds); transfer->setSpeed(t->getSpeed()); transfer->setMeanSpeed(t->getMeanSpeed()); megaApi->fireOnTransferUpdate(transfer); } if (e->getErrorCode() != API_OK) { mIncompleteTransfers++; } LOG_debug << "MegaRecursiveOperation finished subtransfers: " << transfersFinishedCount << " of " << transfersTotalCount; if (allSubtransfersResolved()) { setRootNodeHandleInTransfer(); // Cancelled or not, there is always an onTransferFinish callback for the folder transfer. // If subtransfers were started, completion is always by the last subtransfer completing complete(mIncompleteTransfers ? API_EINCOMPLETE : API_OK); } } MegaFolderUploadController::~MegaFolderUploadController() { assert(mMainThreadId == std::this_thread::get_id()); LOG_debug << "MegaFolderUploadController dtor is being called from main thread"; ensureThreadStopped(); //we shouldn't need to detach as transfer listener: all listened transfer should have been cancelled/completed } MegaFolderUploadController::scanFolder_result MegaFolderUploadController::scanFolder(Tree& tree, LocalPath& localPath, uint32_t& foldercount, uint32_t& filecount) { recursive++; unique_ptr<DirAccess> da(fsaccess->newdiraccess()); if (!da->dopen(&localPath, nullptr, false)) { LOG_err << "Can't open local directory" << localPath; recursive--; return scanFolder_failed; } megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, foldercount, 0, filecount, &localPath, nullptr); LocalPath localname; nodetype_t dirEntryType; LocalPath childPath = localPath; while (da->dnext(childPath, localname, false, &dirEntryType)) { if (isStoppedOrCancelled("MegaFolderUploadController::scanFolder")) { return scanFolder_cancelled; } megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, foldercount, 0, filecount, &localPath, &localname); if (!childPath.isURI()) { childPath.appendWithSeparator(localname, false); } if (dirEntryType == FILENODE) { // Do the fingerprinting for uploads on the scan thread, so we don't lock the main mutex for so long FileFingerprint fp; auto fa = fsaccess->newfileaccess(); if (fa->fopen(childPath, OPEN_RDONLY, FSLogging::logOnError)) { fp.genfingerprint(fa.get()); } // if we couldn't get the fingerprint, !isvalid and we'll fail the transfer tree.files.emplace_back(childPath, fp); filecount += 1; } else if (dirEntryType == FOLDERNODE) { // generate new subtree unique_ptr<Tree> newTreeNode(new Tree); newTreeNode->folderName = localname.toName(*fsaccess); newTreeNode->fsType = fsaccess->getlocalfstype(childPath); // generate fresh random key and node attributes MegaClient::putnodes_prepareOneFolder(&newTreeNode->newnode, newTreeNode->folderName, rng, tmpnodecipher, false); // set nodeHandle newTreeNode->newnode.nodehandle = nextUploadId(); newTreeNode->newnode.parenthandle = tree.newnode.nodehandle; scanFolder_result sr = scanFolder(*newTreeNode, childPath, foldercount, filecount); if (sr != scanFolder_succeeded) { recursive--; return sr; } tree.subtrees.push_back(std::move(newTreeNode)); foldercount += 1; } childPath = localPath; } recursive--; return scanFolder_succeeded; } MegaFolderUploadController::batchResult MegaFolderUploadController::createNextFolderBatch(Tree& tree, vector<NewNode>& newnodes, uint32_t filecount, bool isBatchRootLevel) { assert(mMainThreadId == std::this_thread::get_id()); // preload children for this level (optimization to speed up searches by name/type) // (done here instead of at scanFolder() to avoid locking the mutex from the worker) if (!tree.childrenLoaded && tree.megaNode) { std::shared_ptr<Node> parent = megaApi->client->nodebyhandle(tree.megaNode->getHandle()); assert(parent); megaApi->client->getChildren(parent.get()); tree.childrenLoaded = true; } // recurse until we find nodes not yet created for (auto& t : tree.subtrees) { assert(newnodes.size() <= MAXNODESUPLOAD); if (newnodes.size() >= MAXNODESUPLOAD) { // avoid iterating through tree structure when a batch has reached the limit of nodes break; } if (!t->megaNode && tree.megaNode) // check if our last call created it (or it always existed) { t->megaNode.reset(megaApi->getChildNodeOfType(tree.megaNode.get(), t->folderName.c_str(), MegaNode::TYPE_FOLDER)); } // if node doesn't exist yet and we haven't exceeded the limit per batch if (!t->megaNode && newnodes.size() < MAXNODESUPLOAD) { if (isBatchRootLevel) { /* the parent of the root newNode (for current batch) must already exist in remote, * so parent handle for root newNode must be UNDEF */ assert(tree.megaNode); t->newnode.parenthandle = UNDEF; } newnodes.push_back(std::move(t->newnode)); } // if newnodes contains at least one newNode, isBatchRootLevel will be false batchResult br = createNextFolderBatch(*t, newnodes, filecount, newnodes.empty()); if (br != batchResult_stillRecursing) { return br; } } if (isCancelledByFolderTransferToken()) { complete(API_EINCOMPLETE, true); return batchResult_cancelled; } if (isBatchRootLevel && !newnodes.empty()) { // the lambda will be exeuted on the MegaApiImpl's thread // use a weak_ptr in case this operation was cancelled, and 'this' object doesn't exist // anymore when the request completes weak_ptr<MegaFolderUploadController> weak_this = shared_from_this(); auto parent = megaApi->client->mNodeManager.getNodeByHandle( NodeHandle().set6byte(tree.megaNode->getHandle())); const bool inIncomingShare = parent && parent->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); Pitag localPitag = transfer->getPitag(); localPitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; megaapiThreadClient()->putnodes( NodeHandle().set6byte(tree.megaNode->getHandle()), UseLocalVersioningFlag, std::move(newnodes), nullptr, megaapiThreadClient()->nextreqtag(), false, {}, // customerIpPort [this, weak_this, filecount](const Error& e, targettype_t, vector<NewNode>&, bool, int /*tag*/, const map<string, string>& /*fileHandles*/) { // double check our object still exists on request completion if (!weak_this.lock()) return; assert(weak_this.lock().get() == this); assert(mMainThreadId == std::this_thread::get_id()); // lambda function that will be executed as completion function in putnodes procresult if (e) { complete(e); } else { // start the next batch, if there are any left (or start transfers, if we are ready) vector<NewNode> newnodes; #ifndef NDEBUG batchResult r = #endif createNextFolderBatch(mUploadTree, newnodes, filecount, true); assert(r == batchResult_cancelled || r == batchResult_requestSent || r == batchResult_batchesComplete); } }, localPitag); unsigned existing = 0, total = 0; mUploadTree.recursiveCountFolders(existing, total); megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_CREATE_TREE, total, existing, filecount, nullptr, nullptr); return batchResult_requestSent; } if (&tree == &mUploadTree) { // we recursed the entire tree without finding any more folder nodes to create. // time to set the file uploads in motion. TransferQueue transferQueue; if (!genUploadTransfersForFiles(mUploadTree, transferQueue)) { transferQueue.clear(); complete(API_EINCOMPLETE, true); } else if (transferQueue.empty()) { complete(API_OK); } else { // once we call sendPendingTransfers, we are guaranteed start/finish callbacks for each file transfer // the last callback of onFinish for one of these will also complete and destroy this MegaFolderUploadController transfersTotalCount = transferQueue.size(); megaApi->sendPendingTransfers(&transferQueue, this); // no further code can be added here, this object may now be deleted (eg, due to cancel token activation) } return batchResult_batchesComplete; } return batchResult_stillRecursing; } bool MegaFolderUploadController::genUploadTransfersForFiles(Tree& tree, TransferQueue& transferQueue) { for (const auto& localpath : tree.files) { MegaApiImpl::MegaUploadOptionsPrivate subOptions; subOptions.mPublicOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; subOptions.mFolderTransferTag = tag; subOptions.mPublicOptions.appData = transfer->getAppData(); subOptions.mFsType = tree.fsType; MegaTransferPrivate* subTransfer = megaApi->createUploadTransfer(localpath.lp, tree.megaNode.get(), subOptions, transfer->accessCancelToken(), this, &localpath.fp); Pitag pitag{PitagPurpose::Upload, transfer->getPitag().trigger, PitagNodeType::Folder, transfer->getPitag().target, PitagImportSource::NotApplicable}; subTransfer->setPitag(pitag); transferQueue.push(subTransfer); if (isCancelledByFolderTransferToken()) return false; } for (auto& t : tree.subtrees) { genUploadTransfersForFiles(*t, transferQueue); if (isCancelledByFolderTransferToken()) return false; } return true; } void MegaRecursiveOperation::setRootNodeHandleInTransfer() { if (transfer && transfer->getType() == MegaTransfer::TYPE_UPLOAD) { // set root folder node handle in MegaTransfer LocalPath path = LocalPath::fromAbsolutePath(transfer->getPath()); auto rootFolderName = transfer->getFileName() ? transfer->getFileName() : path.leafName().toPath(true); unique_ptr<MegaNode> parentRootNode(megaApi->getNodeByHandle(transfer->getParentHandle())); std::unique_ptr<MegaNode>root(megaApi->getChildNode(parentRootNode.get(), rootFolderName.c_str())); if (root) { if (transfer->getNodeHandle() != INVALID_HANDLE && transfer->getNodeHandle() != root->getHandle()) { LOG_debug << "setRootNodeHandleInTransfer root nodehandle: " << Base64Str<MegaClient::NODEHANDLE>(root->getHandle()) << ": doesn't match with current one: " << Base64Str<MegaClient::NODEHANDLE>(transfer->getNodeHandle()) ; } transfer->setNodeHandle(root->getHandle()); } } } void MegaRecursiveOperation::complete(Error e, bool cancelledByUser) { assert(mMainThreadId == std::this_thread::get_id()); // Completion only happens on this same thread // and only by two possible paths: // 1. the very last file sub-transfer has completed // 2. there were no files (either by cancel/fail or only folders were needed) // for both those cases, the thread must have completed already. assert(!mWorkerThread.joinable()); std::string logMsg = "MegaRecursiveOperation"; if (cancelledByUser) { logMsg.append(" (has been cancelled by user)"); } e ? logMsg.append(" finished with error [").append(std::to_string(e).c_str()).append("]") : logMsg.append(" finished successfully"); LOG_debug << logMsg << " - bytes: " << transfer->getTransferredBytes() << " of " << transfer->getTotalBytes(); if (allSubtransfersResolved()) { setRootNodeHandleInTransfer(); } transfer->setState(cancelledByUser ? MegaTransfer::STATE_CANCELLED : MegaTransfer::STATE_COMPLETED); megaApi->fireOnTransferFinish(transfer, std::make_unique<MegaErrorPrivate>(e)); } MegaScheduledCopyController::MegaScheduledCopyController(MegaApiImpl *megaApi, int tag, int folderTransferTag, handle parenthandle, const char* filename, bool attendPastBackups, const char *speriod, int64_t period, int maxBackups) { LOG_info << "Registering backup for folder " << filename << " period=" << period << " speriod=" << speriod << " Number-of-Backups=" << maxBackups; this->basepath = filename; size_t found = basepath.find_last_of("/\\"); string aux = basepath; while (aux.size() && (found == (aux.size()-1))) { aux = aux.substr(0, found - 1); found = aux.find_last_of("/\\"); } this->backupName = aux.substr((found == string::npos)?0:found+1); this->parenthandle = parenthandle; this->megaApi = megaApi; this->client = megaApi->getMegaClient(); this->attendPastBackups = attendPastBackups; this->pendingTags = 0; clearCurrentBackupData(); lastbackuptime = getLastBackupTime(); this->backupListener = NULL; this->maxBackups = maxBackups; this->pendingremovals = 0; this->lastwakeuptime = 0; this->tag = tag; this->folderTransferTag = folderTransferTag; valid = true; this->setPeriod(period); this->setPeriodstring(speriod); if (!backupName.size()) { valid = false; } if (valid) { megaApi->startTimer(this->startTime - Waiter::ds + 1); //wake the sdk when required this->state = MegaScheduledCopy::SCHEDULED_COPY_ACTIVE; megaApi->fireOnBackupStateChanged(this); removeexceeding(false); } else { this->state = MegaScheduledCopy::SCHEDULED_COPY_FAILED; } } MegaScheduledCopyController::MegaScheduledCopyController(MegaScheduledCopyController *backup) { this->pendingremovals = backup->pendingremovals; this->setTag(backup->getTag()); this->setLocalFolder(backup->getLocalFolder()); this->setMegaHandle(backup->getMegaHandle()); this->setFolderTransferTag(backup->getFolderTransferTag()); this->megaApi = backup->megaApi; this->client = backup->client; this->setBackupListener(backup->getBackupListener()); //copy currentBackup data this->recursive = backup->recursive; this->pendingTransfers = backup->pendingTransfers; this->pendingTags = backup->pendingTags; for (auto it = backup->pendingFolders.begin(); it != backup->pendingFolders.end(); it++) { this->pendingFolders.push_back(*it); } for (std::vector<MegaTransfer *>::iterator it = backup->failedTransfers.begin(); it != backup->failedTransfers.end(); it++) { this->failedTransfers.push_back(((MegaTransfer *)*it)->copy()); } this->currentHandle = backup->currentHandle; this->currentBKStartTime = backup->currentBKStartTime; this->updateTime = backup->updateTime; this->transferredBytes = backup->transferredBytes; this->totalBytes = backup->totalBytes; this->speed = backup->speed; this->meanSpeed = backup->meanSpeed; this->numberFiles = backup->numberFiles; this->totalFiles = backup->totalFiles; this->numberFolders = backup->numberFolders; this->attendPastBackups = backup->attendPastBackups; this->offsetds=backup->getOffsetds(); this->lastbackuptime=backup->getLastBackupTime(); this->state=backup->getState(); this->startTime=backup->getStartTime(); this->period=backup->getPeriod(); this->ccronexpr=backup->getCcronexpr(); this->periodstring=backup->getPeriodString(); this->valid=backup->isValid(); this->setLocalFolder(backup->getLocalFolder()); this->setBackupName(backup->getBackupName()); this->setMegaHandle(backup->getMegaHandle()); this->setMaxBackups(backup->getMaxBackups()); } long long MegaScheduledCopyController::getNextStartTime(long long oldStartTimeAbsolute) const { if (oldStartTimeAbsolute == -1) { return (getNextStartTimeDs() + this->offsetds)/10; } else { return (getNextStartTimeDs(oldStartTimeAbsolute*10 - this->offsetds) + this->offsetds)/10; } } long long MegaScheduledCopyController::getNextStartTimeDs(long long oldStartTimeds) const { if (oldStartTimeds == -1) { return startTime; } if (period != -1) { return oldStartTimeds + period; } else { if (!valid) { return oldStartTimeds; } long long current_ds = oldStartTimeds + this->offsetds; // 64 bit long long newt = cron_next(&ccronexpr, time_t(current_ds/10)); // time_t is 32 bit still on many systems long long newStarTimeds = newt*10-offsetds; // 64 bit again return newStarTimeds; } } void MegaScheduledCopyController::update() { if (!valid) { if (!isBusy()) { state = SCHEDULED_COPY_FAILED; } return; } if (Waiter::ds > startTime) { if (!isBusy()) { long long nextStartTime = getNextStartTimeDs(startTime); if (nextStartTime <= startTime) { LOG_err << "Invalid calculated NextStartTime" ; valid = false; state = SCHEDULED_COPY_FAILED; return; } if (nextStartTime > Waiter::ds) { start(); } else { LOG_warn << " BACKUP discarded (too soon, time for the next): " << basepath; start(true); megaApi->startTimer(1); //wake sdk } startTime = nextStartTime; } else { LOG_verbose << "Backup busy: " << basepath << ". State=" << ((state==MegaScheduledCopy::SCHEDULED_COPY_ONGOING)?"On Going":"Removing exeeding") << ". Postponing ..."; if ((lastwakeuptime+10) < Waiter::ds ) { megaApi->startTimer(10); //give it a while lastwakeuptime = Waiter::ds+10; } } } else { if (lastwakeuptime < Waiter::ds || ((this->startTime + 1) < lastwakeuptime)) { LOG_debug << " Waking in " << (this->startTime - Waiter::ds + 1) << " deciseconds to do backup"; megaApi->startTimer(this->startTime - Waiter::ds + 1); //wake the sdk when required lastwakeuptime = this->startTime + 1; } } } void MegaScheduledCopyController::removeexceeding(bool currentoneOK) { map<int64_t, MegaNode *> backupTimesNodes; int ncompleted=0; MegaNode * parentNode = megaApi->getNodeByHandle(parenthandle); if (parentNode) { MegaNodeList* children = megaApi->getChildren(parentNode, MegaApi::ORDER_NONE); if (children) { for (int i = 0; i < children->size(); i++) { MegaNode *childNode = children->get(i); string childname = childNode->getName(); if (isBackup(childname, backupName) ) { const char *backstvalue = childNode->getCustomAttr("BACKST"); if ( ( !backstvalue || !strcmp(backstvalue,"ONGOING") ) && ( childNode->getHandle() != currentHandle ) ) { LOG_err << "Found unexpected ONGOING backup (probably from previous executions). Changing status to MISCARRIED"; this->pendingTags++; megaApi->setCustomNodeAttribute(childNode, "BACKST", "MISCARRIED", this); } if ( (backstvalue && !strcmp(backstvalue,"COMPLETE")) || ( childNode->getHandle() == currentHandle && currentoneOK ) //either its completed or is the current one and it went ok (it might not have backstvalue yet set ) { ncompleted++; } int64_t timeofbackup = getTimeOfBackup(childname); if (timeofbackup) { backupTimesNodes[timeofbackup]=childNode; } else { LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded."; } } } } while (backupTimesNodes.size() > (unsigned int)maxBackups) { map<int64_t, MegaNode *>::iterator itr = backupTimesNodes.begin(); const char *backstvalue = itr->second->getCustomAttr("BACKST"); if ( (ncompleted == 1) && backstvalue && (!strcmp(backstvalue,"COMPLETE")) && backupTimesNodes.size() > 1) { itr++; } MegaNode * nodeToDelete = itr->second; int64_t timetodelete = itr->first; backstvalue = nodeToDelete->getCustomAttr("BACKST"); if (backstvalue && !strcmp(backstvalue,"COMPLETE")) { ncompleted--; } char * nodepath = megaApi->getNodePath(nodeToDelete); LOG_info << " Removing exceeding backup " << nodepath; delete []nodepath; state = SCHEDULED_COPY_REMOVING_EXCEEDING; megaApi->fireOnBackupStateChanged(this); pendingremovals++; megaApi->remove(nodeToDelete, false, this); backupTimesNodes.erase(timetodelete); } delete children; } delete parentNode; } int64_t MegaScheduledCopyController::getLastBackupTime() { map<int64_t, MegaNode *> backupTimesPaths; int64_t latesttime=0; MegaNode * parentNode = megaApi->getNodeByHandle(parenthandle); if (parentNode) { MegaNodeList* children = megaApi->getChildren(parentNode, MegaApi::ORDER_NONE); if (children) { for (int i = 0; i < children->size(); i++) { MegaNode *childNode = children->get(i); string childname = childNode->getName(); if (isBackup(childname, backupName) ) { int64_t timeofbackup = getTimeOfBackup(childname); if (timeofbackup) { backupTimesPaths[timeofbackup]=childNode; latesttime = (std::max)(latesttime, timeofbackup); } else { LOG_err << "Failed to get backup time for folder: " << childname << ". Discarded."; } } } delete children; } delete parentNode; } return latesttime; } bool MegaScheduledCopyController::isBackup(string localname, string backupname) const { return ( localname.compare(0, backupname.length(), backupname) == 0) && (localname.find("_bk_") != string::npos); } int64_t MegaScheduledCopyController::getTimeOfBackup(string localname) const { size_t pos = localname.find("_bk_"); if (pos == string::npos || ( (pos+4) >= (localname.size()-1) ) ) { return 0; } string rest = localname.substr(pos + 4).c_str(); // int64_t toret = atol(rest.c_str()); int64_t toret = stringToTimestamp(rest, FORMAT_SCHEDULED_COPY); return toret; } bool MegaScheduledCopyController::getAttendPastBackups() const { return attendPastBackups; } MegaTransferList *MegaScheduledCopyController::getFailedTransfers() { MegaTransferList *result = new MegaTransferListPrivate(failedTransfers.data(), int(failedTransfers.size())); return result; } void MegaScheduledCopyController::setAttendPastBackups(bool value) { attendPastBackups = value; } bool MegaScheduledCopyController::isValid() const { return valid; } void MegaScheduledCopyController::setValid(bool value) { valid = value; } cron_expr MegaScheduledCopyController::getCcronexpr() const { return ccronexpr; } void MegaScheduledCopyController::setCcronexpr(const cron_expr &value) { ccronexpr = value; } MegaScheduledCopyListener *MegaScheduledCopyController::getBackupListener() const { return backupListener; } void MegaScheduledCopyController::setBackupListener(MegaScheduledCopyListener *value) { backupListener = value; } long long MegaScheduledCopyController::getTotalFiles() const { return totalFiles; } void MegaScheduledCopyController::setTotalFiles(long long value) { totalFiles = value; } int64_t MegaScheduledCopyController::getCurrentBKStartTime() const { return currentBKStartTime; } void MegaScheduledCopyController::setCurrentBKStartTime(const int64_t &value) { currentBKStartTime = value; } int64_t MegaScheduledCopyController::getUpdateTime() const { return updateTime; } void MegaScheduledCopyController::setUpdateTime(const int64_t &value) { updateTime = value; } long long MegaScheduledCopyController::getTransferredBytes() const { return transferredBytes; } void MegaScheduledCopyController::setTransferredBytes(long long value) { transferredBytes = value; } long long MegaScheduledCopyController::getTotalBytes() const { return totalBytes; } void MegaScheduledCopyController::setTotalBytes(long long value) { totalBytes = value; } long long MegaScheduledCopyController::getSpeed() const { return speed; } void MegaScheduledCopyController::setSpeed(long long value) { speed = value; } long long MegaScheduledCopyController::getMeanSpeed() const { return meanSpeed; } void MegaScheduledCopyController::setMeanSpeed(long long value) { meanSpeed = value; } long long MegaScheduledCopyController::getNumberFiles() const { return numberFiles; } void MegaScheduledCopyController::setNumberFiles(long long value) { numberFiles = value; } long long MegaScheduledCopyController::getNumberFolders() const { return numberFolders; } void MegaScheduledCopyController::setNumberFolders(long long value) { numberFolders = value; } int64_t MegaScheduledCopyController::getLastbackuptime() const { return lastbackuptime; } void MegaScheduledCopyController::setLastbackuptime(const int64_t &value) { lastbackuptime = value; } void MegaScheduledCopyController::setState(int value) { state = value; } bool MegaScheduledCopyController::isBusy() const { return (state == SCHEDULED_COPY_ONGOING) || (state == SCHEDULED_COPY_REMOVING_EXCEEDING || (state == SCHEDULED_COPY_SKIPPING)); } std::string MegaScheduledCopyController::epochdsToString(const int64_t rawtimeds) const { struct tm dt; char buffer [40]; time_t rawtime = rawtimeds/10; m_localtime(rawtime, &dt); strftime(buffer, sizeof( buffer ), "%Y%m%d%H%M%S", &dt); return std::string(buffer); } void MegaScheduledCopyController::clearCurrentBackupData() { this->recursive = 0; this->pendingTransfers = 0; this->pendingFolders.clear(); for (std::vector<MegaTransfer *>::iterator it = failedTransfers.begin(); it != failedTransfers.end(); it++) { delete *it; } this->failedTransfers.clear(); this->currentHandle = UNDEF; this->currentBKStartTime = 0; this->updateTime = 0; this->transferredBytes = 0; this->totalBytes = 0; this->speed = 0; this->meanSpeed = 0; this->numberFiles = 0; this->totalFiles = 0; this->numberFolders = 0; } void MegaScheduledCopyController::start(bool skip) { LOG_info << "starting backup of " << basepath << ". Next one will be in " << getNextStartTimeDs(startTime)-offsetds << " ds" ; clearCurrentBackupData(); this->setCurrentBKStartTime(Waiter::ds); //notice: this is != StarTime size_t plastsep = basepath.find_last_of("\\/"); if(plastsep == string::npos) plastsep = size_t(-1); string name = basepath.substr(plastsep+1); std::ostringstream ossremotename; ossremotename << name; ossremotename << "_bk_"; ossremotename << epochdsToString(offsetds+startTime); string backupname = ossremotename.str(); currentName = backupname; lastbackuptime = (std::max)(lastbackuptime,offsetds+startTime); megaApi->fireOnBackupStart(this); MegaNode *parent = megaApi->getNodeByHandle(parenthandle); if(!parent) { LOG_err << "Could not start backup: "<< name << ". Parent node not found"; megaApi->fireOnBackupFinish(this, std::make_unique<MegaErrorPrivate>(API_ENOENT)); } else { if (skip) { state = SCHEDULED_COPY_SKIPPING; } else { state = SCHEDULED_COPY_ONGOING; } megaApi->fireOnBackupStateChanged(this); auto localpath = LocalPath::fromAbsolutePath(basepath); MegaNode *child = megaApi->getChildNode(parent, backupname.c_str()); if(!child || !child->isFolder()) { pendingFolders.push_back(localpath); megaApi->createFolder(backupname.c_str(), parent, this); } else { LOG_err << "Could not start backup: "<< backupname << ". Backup already exists"; megaApi->fireOnBackupFinish(this, std::make_unique<MegaErrorPrivate>(API_EEXIST)); state = SCHEDULED_COPY_ACTIVE; } delete child; delete parent; } } void MegaScheduledCopyController::onFolderAvailable(MegaHandle handle) { MegaNode *parent = megaApi->getNodeByHandle(handle); if(currentHandle == UNDEF)//main folder of the backup instance { currentHandle = handle; if (state == SCHEDULED_COPY_ONGOING) { this->pendingTags++; megaApi->setCustomNodeAttribute(parent, "BACKST", "ONGOING", this); } else { this->pendingTags++; megaApi->setCustomNodeAttribute(parent, "BACKST", "SKIPPED", this); } } else { numberFolders++; } recursive++; LocalPath localPath = pendingFolders.front(); pendingFolders.pop_front(); if (state == SCHEDULED_COPY_ONGOING) { LocalPath localname; auto da = client->fsaccess->newdiraccess(); if (da->dopen(&localPath, NULL, false)) { FileSystemType fsType = client->fsaccess->getlocalfstype(localPath); LocalPath childPath = localPath; while (da->dnext(childPath, localname, false)) { childPath.appendWithSeparator(localname, false); //TODO: add exclude filters here auto fa = client->fsaccess->newfileaccess(); if (fa->fopen(childPath, OPEN_RDONLY, FSLogging::logOnError)) { string name = localname.toName(*client->fsaccess); if(fa->type == FILENODE) { pendingTransfers++; totalFiles++; const auto parentNode = client->mNodeManager.getNodeByHandle(NodeHandle().set6byte(handle)); const bool inIncomingShare = parentNode && parentNode->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); const PitagTarget pitagTarget = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; MegaApiImpl::MegaUploadOptionsPrivate uploadOptions; uploadOptions.mPublicOptions.mtime = -1; uploadOptions.mFolderTransferTag = folderTransferTag; uploadOptions.mIsBackup = true; uploadOptions.mFsType = fsType; uploadOptions.mPitagTarget = pitagTarget; const std::string childPathStr = childPath.toPath(false); megaApi->startUpload(childPathStr, parent, CancelToken(), uploadOptions, this); } else { MegaNode *child = megaApi->getChildNode(parent, name.c_str()); if(!child || !child->isFolder()) { pendingFolders.push_back(childPath); megaApi->createFolder(name.c_str(), parent, this); } else { pendingFolders.push_front(childPath); onFolderAvailable(child->getHandle()); } delete child; } } childPath = localPath; } } } else if (state == SCHEDULED_COPY_SKIPPING) { //do nth } else { LOG_warn << " Backup folder created while not ONGOING: " << localPath; } delete parent; recursive--; checkCompletion(); } bool MegaScheduledCopyController::checkCompletion() { if(!recursive && !pendingFolders.size() && !pendingTransfers && !pendingTags) { error e = API_OK; LOG_debug << "Folder transfer finished - " << this->getTransferredBytes() << " of " << this->getTotalBytes(); MegaNode *node = megaApi->getNodeByHandle(currentHandle); if (node) { if (failedTransfers.size()) { this->pendingTags++; megaApi->setCustomNodeAttribute(node, "BACKST", "INCOMPLETE", this); e = API_EINCOMPLETE; } else if (state != SCHEDULED_COPY_SKIPPING) { this->pendingTags++; megaApi->setCustomNodeAttribute(node, "BACKST", "COMPLETE", this); } else { e = API_EEXPIRED; } delete node; } else { LOG_err << "Could not set backup attribute, node not found for: " << currentName; e = API_ENOENT; } state = SCHEDULED_COPY_ACTIVE; megaApi->fireOnBackupFinish(this, std::make_unique<MegaErrorPrivate>(e)); megaApi->fireOnBackupStateChanged(this); removeexceeding(e == API_OK); return true; } return false; } int MegaScheduledCopyController::getFolderTransferTag() const { return folderTransferTag; } void MegaScheduledCopyController::setFolderTransferTag(int value) { folderTransferTag = value; } int64_t MegaScheduledCopyController::getOffsetds() const { return offsetds; } void MegaScheduledCopyController::setOffsetds(const int64_t &value) { offsetds = value; } void MegaScheduledCopyController::abortCurrent() { LOG_debug << "Setting backup as aborted: " << currentName; if (state == SCHEDULED_COPY_ONGOING || state == SCHEDULED_COPY_SKIPPING) { megaApi->fireOnBackupFinish(this, std::make_unique<MegaErrorPrivate>(API_EINCOMPLETE)); } state = SCHEDULED_COPY_ACTIVE; megaApi->fireOnBackupStateChanged(this); MegaNode *node = megaApi->getNodeByHandle(currentHandle); if (node) { this->pendingTags++; megaApi->setCustomNodeAttribute(node, "BACKST", "ABORTED", this); delete node; } else { LOG_err << "Could not set backup attribute, node not found for: " << currentName; } clearCurrentBackupData(); } void MegaScheduledCopyController::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e) { int type = request->getType(); int errorCode = e->getErrorCode(); if(type == MegaRequest::TYPE_CREATE_FOLDER) { if(!errorCode) { onFolderAvailable(request->getNodeHandle()); megaApi->fireOnBackupUpdate(this); } else { pendingFolders.pop_front(); megaApi->fireOnBackupUpdate(this); checkCompletion(); } } else if(type == MegaRequest::TYPE_REMOVE) { pendingremovals--; if (!pendingremovals) { assert(pendingTags>=0); if (pendingTags <= 0) { state = SCHEDULED_COPY_ACTIVE; } megaApi->fireOnBackupStateChanged(this); } } else if(type == MegaRequest::TYPE_SET_ATTR_NODE) { pendingTags--; assert(pendingTags>=0); if (!pendingTags) { if (state == SCHEDULED_COPY_ONGOING || state == SCHEDULED_COPY_SKIPPING) { checkCompletion(); } else // from REMOVING OR after abort { if (state != SCHEDULED_COPY_ACTIVE) { state = SCHEDULED_COPY_ACTIVE; megaApi->fireOnBackupStateChanged(this); } } } } } void MegaScheduledCopyController::onTransferStart(MegaApi *, MegaTransfer *t) { LOG_verbose << " at MegaScheduledCopyController::onTransferStart: "+ string(t->getFileName()); this->setTotalBytes(this->getTotalBytes() + t->getTotalBytes()); this->setUpdateTime(Waiter::ds); megaApi->fireOnBackupUpdate(this); } void MegaScheduledCopyController::onTransferUpdate(MegaApi *, MegaTransfer *t) { LOG_verbose << " at MegaScheduledCopyController::onTransferUpdate"; this->setTransferredBytes(this->getTransferredBytes() + t->getDeltaSize()); this->setUpdateTime(Waiter::ds); this->setSpeed(t->getSpeed()); this->setMeanSpeed(t->getMeanSpeed()); megaApi->fireOnBackupUpdate(this); } void MegaScheduledCopyController::onTransferTemporaryError(MegaApi*, MegaTransfer*, MegaError* e) { LOG_verbose << " at MegaScheduledCopyController::onTransferTemporaryError"; unique_ptr<MegaErrorPrivate> errorPrivate; if (dynamic_cast<MegaErrorPrivate *>(e)) { errorPrivate = unique_ptr<MegaErrorPrivate>(dynamic_cast<MegaErrorPrivate *>(e->copy())); } else { errorPrivate = std::make_unique<MegaErrorPrivate>(e->getErrorCode()); } megaApi->fireOnBackupTemporaryError(this, std::move(errorPrivate)); // we received a non-owning pointer but we need to pass ownership to fireOnBackupTemporaryError } void MegaScheduledCopyController::onTransferFinish(MegaApi *, MegaTransfer *t, MegaError *e) { LOG_verbose << " at MegaackupController::onTransferFinish"; pendingTransfers--; // this->setTransferredBytes(this->getTransferredBytes() + t->getDeltaSize()); //TODO: THIS was in MegaUploaderController (which seems wrong) this->setUpdateTime(Waiter::ds); this->setSpeed(t->getSpeed()); this->setMeanSpeed(t->getMeanSpeed()); if (e->getErrorCode() != MegaError::API_OK) { failedTransfers.push_back(t->copy()); } else { numberFiles++; } megaApi->fireOnBackupUpdate(this); checkCompletion(); } MegaScheduledCopy *MegaScheduledCopyController::copy() { return new MegaScheduledCopyController(this); } int MegaScheduledCopyController::getMaxBackups() const { return maxBackups; } void MegaScheduledCopyController::setMaxBackups(int value) { maxBackups = value; } string MegaScheduledCopyController::getBackupName() const { return backupName; } void MegaScheduledCopyController::setBackupName(const string &value) { backupName = value; } int64_t MegaScheduledCopyController::getPeriod() const { return period; } const char *MegaScheduledCopyController::getPeriodString() const { return periodstring.c_str(); } void MegaScheduledCopyController::setPeriod(const int64_t &value) { period = value; if (value != -1) { int64_t ds = Waiter::ds.load(); offsetds = m_time(NULL) * 10 - ds; startTime = lastbackuptime ? (lastbackuptime + period - offsetds) : ds; startTime = std::max(startTime, ds); } } void MegaScheduledCopyController::setPeriodstring(const string &value) { periodstring = value; valid = true; if (value.size()) { const char* err = NULL; memset((cron_expr *)&ccronexpr, 0, sizeof(ccronexpr)); cron_parse_expr(periodstring.c_str(), &ccronexpr, &err); if (err != NULL) { valid = false; return; } this->offsetds=m_time(NULL)*10 - Waiter::ds; if (!lastbackuptime) { this->startTime = Waiter::ds; } else { this->startTime = this->getNextStartTimeDs(lastbackuptime-offsetds); } if (this->startTime < Waiter::ds) { //to avoid skipping (do empty backups with SKIPPED attr) for a long while (e.g: period too short or downtime too long) // we determine a max number of executions to skip. int maxBackupToSkip = maxBackups + 10; int64_t* starttimes = new int64_t[static_cast<size_t>(maxBackupToSkip)]; int64_t next = lastbackuptime-offsetds; int64_t previousnext = next; for (int i = 0; i < maxBackupToSkip; i++) { starttimes[i] = startTime; } int j = 0; do { previousnext = next; next = this->getNextStartTimeDs(next); starttimes[j] = next; j = (j==(maxBackupToSkip-1))?0:j+1; } while (next > previousnext && next < Waiter::ds); if (!attendPastBackups) { this->startTime = next; } else { this->startTime = starttimes[j]; //starttimes[j] should have the oldest time } delete [] starttimes; } LOG_debug << " Next Backup set in " << startTime - Waiter::ds << " deciseconds. At: " << epochdsToString((this->startTime+this->offsetds)); } } int64_t MegaScheduledCopyController::getStartTime() const { return startTime; } void MegaScheduledCopyController::setStartTime(const int64_t &value) { startTime = value; } int MegaScheduledCopyController::getTag() const { return this->tag; } void MegaScheduledCopyController::setTag(int value) { tag = value; } MegaHandle MegaScheduledCopyController::getMegaHandle() const { return this->parenthandle; } void MegaScheduledCopyController::setMegaHandle(const MegaHandle &value) { parenthandle = value; } const char *MegaScheduledCopyController::getLocalFolder() const { return this->basepath.c_str(); } void MegaScheduledCopyController::setLocalFolder(const string &value) { basepath = value; } int MegaScheduledCopyController::getState() const { return state; } MegaScheduledCopyController::~MegaScheduledCopyController() { megaApi->removeRequestListener(this); megaApi->removeTransferListener(this); for (std::vector<MegaTransfer *>::iterator it = failedTransfers.begin(); it != failedTransfers.end(); it++) { delete *it; } } // Note: it's mandatory to notify stage change from MegaApiImpl's thread void MegaRecursiveOperation::notifyStage(uint8_t stage) { // Make a copy of transfer and set stage in the temp transfer. // If we set stage in the original transfer, we may incur in a wrong order of notification. assert(mMainThreadId == std::this_thread::get_id()); assert (stage > MegaTransfer::STAGE_NONE && stage <= MegaTransfer::STAGE_MAX); LOG_debug << "MegaRecursiveOperation: starting " << MegaTransfer::stageToString(stage); unique_ptr<MegaTransfer> tempTransfer(transfer->copy()); MegaTransferPrivate *transferPtr = static_cast<MegaTransferPrivate*>(tempTransfer.get()); transferPtr->setStage(stage); megaApi->fireOnTransferUpdate(transferPtr); } void MegaRecursiveOperation::ensureThreadStopped() { mWorkerThreadStopFlag = true; if (mWorkerThread.joinable()) { mWorkerThread.join(); } } bool MegaRecursiveOperation::isCancelledByFolderTransferToken() const { return transfer->accessCancelToken().isCancelled(); } MegaClient* MegaRecursiveOperation::megaapiThreadClient() { assert(mMainThreadId == std::this_thread::get_id()); return mMegaapiThreadClient; } MegaFolderDownloadController::MegaFolderDownloadController(MegaApiImpl* api, MegaTransferPrivate* t): MegaRecursiveOperation(api->client) { fsaccess = mega::createFSA(); megaApi = api; fsaccess->setdefaultfilepermissions(megaApi->getDefaultFilePermissions()); // Grant default file permissions fsaccess->setdefaultfolderpermissions(megaApi->getDefaultFolderPermissions()); // Grant default folder permissions transfer = t; listener = t->getListener(); recursive = 0; tag = t->getTag(); mMainThreadId = std::this_thread::get_id(); } MegaFolderDownloadController::~MegaFolderDownloadController() { assert(mMainThreadId == std::this_thread::get_id()); LOG_debug << "MegaFolderDownloadController dtor is being called from main thread"; ensureThreadStopped(); } void MegaFolderDownloadController::start(MegaNode *node) { assert(mMainThreadId == std::this_thread::get_id()); transfer->setFolderTransferTag(-1); transfer->setStartTime(Waiter::ds); transfer->setState(MegaTransfer::STATE_QUEUED); megaApi->fireOnTransferStart(transfer); unique_ptr<MegaNode> autoDelNode; if (!node) { node = megaApi->getNodeByHandle(transfer->getNodeHandle()); autoDelNode.reset(node); if (!node) { LOG_debug << "Folder download failed. Node not found"; complete(API_ENOENT); return; } } LocalPath path; if (transfer->getParentPath()) { path = LocalPath::fromAbsolutePath(transfer->getParentPath()); } else { path = LocalPath::fromAbsolutePath("."); path.appendWithSeparator(LocalPath::fromRelativePath(""), true); } FileSystemType fsType = megaapiThreadClient()->fsaccess->getlocalfstype(path); const LocalPath &name = (!transfer->getFileName() || !strlen(transfer->getFileName())) ? LocalPath::fromRelativeName(node->getName(), *megaapiThreadClient()->fsaccess, fsType) : LocalPath::fromRelativeName(transfer->getFileName(), *megaapiThreadClient()->fsaccess, fsType); path.appendWithSeparator(name, true); transfer->setLocalPath(path); // check we are not overwriting file with folder: auto tmpfileaccess = fsaccess->newfileaccess(); if (tmpfileaccess->isfile(path)) { // destination path could not be open or there's a node (TYPE_FILE) with the same name in the destination path complete(API_EEXIST); return; } notifyStage(MegaTransfer::STAGE_SCAN); // for download scan is just checking nodes, we can do this all in one quick pass unsigned fileAddedCount = 0; scanFolder_result sr = scanFolder(node, path, fsType, fileAddedCount); if (sr != scanFolder_succeeded) { if (sr == scanFolder_cancelled) { complete(API_EINCOMPLETE, true); } else // inconsistent node state { complete(API_EINTERNAL); } } else { // it's mandatory to notify stage change from MegaApiImpl's thread to avoid deadlocks and other issues notifyStage(MegaTransfer::STAGE_CREATE_TREE); // start worker thread to create local folder tree mWorkerThread = std::thread([this, fsType, path, fileAddedCount](){ // local folder creation runs on the download worker thread (and checks the cancelled flag) Error e; std::shared_ptr<TransferQueue> transferQueue = createFolderGenDownloadTransfersForFiles(fsType, fileAddedCount, e); // mCompletionForMegaApiThread lambda will be executed on the MegaApiImpl's thread // use a weak_ptr in case this 'this' object doesn't exist anymore when lambda starts executing weak_ptr<MegaFolderDownloadController> weak_this = shared_from_this(); // the thread always queues a function to execute on MegaApi thread for onFinish() // we keep a pointer to it in case we need to cancel() mCompletionForMegaApiThread.reset(new ExecuteOnce([this, e, transferQueue, path, weak_this]() { // double check our object still exists when completion function starts executing if (!weak_this.lock()) return; assert(weak_this.lock().get() == this); // these next parts must run on MegaApiImpl's thread again, as // genUploadTransfersForFiles or checkCompletion may call the fireOnXYZ() functions assert(mMainThreadId == std::this_thread::get_id()); // make sure the thread is joined. This lets us add error-catching asserts elsewhere. if (mWorkerThread.joinable()) { mWorkerThread.join(); } if (e) { if (transferQueue) { transferQueue->clear(); } complete(e); } else { // downloadFiles must run on the megaApi thread, as it may call fireOnTransferXYZ() if (!transferQueue) { complete(API_EINCOMPLETE, true); } else if (transferQueue->empty()) { complete(API_OK); } else { // once we call sendPendingTransfers, we are guaranteed start/finish callbacks for each file transfer // the last callback of onFinish for one of these will also complete and destroy this MegaFolderUploadController transfersTotalCount = transferQueue->size(); megaApi->sendPendingTransfers(transferQueue.get(), this, megaapiThreadClient()->fsaccess->availableDiskSpace(path)); // no further code can be added here, this object may now be deleted (eg, due to cancel token activation) // complete() will finally be called when the last sub-transfer finishes } } })); // Queue that function. megaApi->executeOnThread(mCompletionForMegaApiThread); }); } } MegaFolderDownloadController::scanFolder_result MegaFolderDownloadController::scanFolder(MegaNode *node, LocalPath& localpath, FileSystemType fsType, unsigned& fileAddedCount) { assert(mMainThreadId == std::this_thread::get_id()); if (isCancelledByFolderTransferToken()) { return scanFolder_cancelled; } recursive++; size_t index = 0; if (node->getType() == FOLDERNODE || node->getType() == ROOTNODE) { // If node is a folder or root node, store it's localPath, along with a vector with it's children file nodes mLocalTree.emplace_back(LocalTree(localpath)); index = mLocalTree.size() - 1; } megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_SCAN, unsigned(mLocalTree.size()), 0, fileAddedCount, &localpath, nullptr); MegaNodeList *children = nullptr; unique_ptr<MegaNodeList> autoDelChildren; if (node->isForeign()) { children = node->getChildren(); } else { children = megaApi->getChildren(node, MegaApi::ORDER_NONE); // no order is much faster for a very large folder (or nested folders with large subfolders) autoDelChildren.reset(children); } if (!children) { LOG_err << "Child nodes not found: " << localpath; recursive--; return scanFolder_failed; } for (int i = 0; i < children->size(); i++) { if (isCancelledByFolderTransferToken()) { return scanFolder_cancelled; } MegaNode *child = children->get(i); if (child->getType() == MegaNode::TYPE_FILE) { // Add child node to vector in mLocalTree at index we have stored it's localPath ::mega::unique_ptr<MegaNode> childNode (child->copy()); mLocalTree.at(index).childrenNodes.push_back(std::move(childNode)); fileAddedCount += 1; } else { LocalPath newpath{localpath}; newpath.appendWithSeparator( LocalPath::fromRelativeName(child->getName(), *fsaccess, fsType), true); scanFolder_result result = scanFolder(child, newpath, fsType, fileAddedCount); if (result != scanFolder_succeeded) { recursive--; return result; } } } recursive--; return scanFolder_succeeded; } bool MegaRecursiveOperation::isStoppedOrCancelled(const std::string& name) const { if (mWorkerThreadStopFlag) { LOG_debug << name << " thread stopped by flag"; return true; } if (isCancelledByFolderTransferToken()) { LOG_debug << name << " thread stopped by cancel token"; return true; } return false; } // Create all local directories in one shot (on the download worker thread) // for performance and reducing UI waiting time, we combine createFolder and transferQueue generating in one loop std::unique_ptr<TransferQueue> MegaFolderDownloadController::createFolderGenDownloadTransfersForFiles(FileSystemType fsType, uint32_t fileCount, Error &e) { unsigned created = 0; assert(mMainThreadId != std::this_thread::get_id()); auto transferQueue = std::make_unique<TransferQueue>(); // update stage to begin if (!mLocalTree.empty()) { megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_CREATE_TREE, unsigned(mLocalTree.size()), created, fileCount, nullptr, nullptr); } // creating folders and generate transfers for files auto it = mLocalTree.begin(); while (it != mLocalTree.end()) { if (isStoppedOrCancelled("MegaFolderDownloadController::createFolderGenDownloadTransfersForFiles")) { e = API_EINCOMPLETE; return transferQueue; } LocalPath &localpath = it->localPath; auto collistionResolution = transfer->getCollisionResolution(); // try to create the folder e = MegaApiImpl::createLocalFolder_unlocked(localpath, *fsaccess, collistionResolution); // errors besides the folder already exists is an error if (e && e != API_EEXIST) { mLocalTree.clear(); return transferQueue; } auto folderAlreadyExist = (e && e == API_EEXIST); if (!genDownloadTransfersForFiles(transferQueue.get(), *it, fsType, folderAlreadyExist)) { e = API_EINCOMPLETE; return transferQueue; } ++it; ++created; megaApi->fireOnFolderTransferUpdate(transfer, MegaTransfer::STAGE_CREATE_TREE, unsigned(mLocalTree.size()), created, fileCount, nullptr, nullptr); } e = API_OK; return transferQueue; } bool MegaFolderDownloadController::genDownloadTransfersForFiles( TransferQueue* transferQueue, LocalTree& folder, FileSystemType fsType, bool folderExists) { assert(transferQueue); for (auto& fileNode : folder.childrenNodes) { if (isStoppedOrCancelled("MegaFolderDownloadController::genDownloadTransfersForFiles")) { return false; } // get file local path auto fileLocalPath = folder.localPath; fileLocalPath.appendWithSeparator(LocalPath::fromRelativeName(fileNode->getName(), *fsaccess, fsType), true); auto decision = CollisionChecker::Result::Download; // collision might exist only if the folder already exists if (folderExists) { auto fa = fsaccess->newfileaccess(); if (fa && fa->fopen(fileLocalPath, OPEN_RDONLY, FSLogging::logExceptFileNotFound) && fa->type == FILENODE) { decision = CollisionChecker::check(fsaccess.get(), fileLocalPath, fileNode.get(), transfer->getCollisionCheck()); } } MegaTransferPrivate* transferDownload = megaApi->createDownloadTransfer(false, fileNode.get(), fileLocalPath, nullptr, tag, transfer->getAppData(), transfer->accessCancelToken(), static_cast<int>(transfer->getCollisionCheck()), static_cast<int>(transfer->getCollisionResolution()), transfer->getNodeToUndelete() != nullptr, this, fsType); transferDownload->setCollisionCheckResult(decision); transferQueue->push(transferDownload); } return true; } #ifdef HAVE_LIBUV StreamingBuffer::StreamingBuffer(const std::string& logName): logname{logName} { this->capacity = 0; this->buffer = NULL; this->inpos = 0; this->outpos = 0; this->size = 0; this->free = 0; this->maxBufferSize = MAX_BUFFER_SIZE; this->maxOutputSize = MAX_OUTPUT_SIZE; this->fileSize = 0; this->duration = 0; } StreamingBuffer::~StreamingBuffer() { delete [] buffer; } void StreamingBuffer::init(size_t newCapacity) { assert(newCapacity > 0); // Recalculate maxBufferSize and maxOutputSize. calcMaxBufferAndMaxOutputSize(); // Truncate new capacity if needed if (newCapacity > maxBufferSize) { const auto bytesPerSecond = static_cast<size_t>(getBytesPerSecond()); LOG_warn << getLogName() << "[Streaming] Truncating requested capacity due to being greater than maxBufferSize." << " Capacity requested = " << newCapacity << " bytes" << ", truncated to = " << maxBufferSize << " bytes" << " [file size = " << fileSize << " bytes" << ", total duration = " << (duration ? (std::to_string(duration).append(" secs")) : "not a media file") << (duration ? std::string(", estimated duration in truncated buffer: ") .append(std::to_string(maxBufferSize / bytesPerSecond)) .append(" secs") .append(", max length to be served: ") .append(std::to_string(maxOutputSize / bytesPerSecond)) .append(" secs") .append(", bytes per second: ") .append(std::to_string(bytesPerSecond / 1024)) .append(" KB/s") : "") << "]"; newCapacity = maxBufferSize; } else { LOG_debug << getLogName() << "[Streaming] Init StreamingBuffer." << " Capacity requested = " << newCapacity << " bytes" << " [file size = " << fileSize << " bytes" << ", total duration = " << (duration ? (std::to_string(duration).append(" secs")) : "not a media file") << (duration ? std::string(", estimated duration in buffer: ") .append(std::to_string( partialDuration(static_cast<m_off_t>(newCapacity))) .append(" secs")) : "") << "]"; } capacity = static_cast<unsigned>(newCapacity); this->buffer = new char[capacity]; this->inpos = 0; this->outpos = 0; this->size = 0; this->free = capacity; } void StreamingBuffer::calcMaxBufferAndMaxOutputSize() { size_t maxReadChunkSize = static_cast<size_t>(DirectReadSlot::MAX_DELIVERY_CHUNK); #if defined(__ANDROID__) || defined(USE_IOS) size_t minNeededBufferSize = (maxReadChunkSize * 5) / 2; // Equivalent to x2.5 [x * (2 + 1/2) = x * 5/2] #else constexpr size_t BUFFER_PAUSE_RESUME_FACTOR = 3; // Take into account pausing/resuming actions: we need that margin. size_t minNeededBufferSize = maxReadChunkSize * BUFFER_PAUSE_RESUME_FACTOR; #endif size_t maxDeliveryChunksPerByteRate; if (duration) { const auto bytesPerSecond = static_cast<size_t>(getBytesPerSecond()); // Desirable min buffer capacity: 10 seconds at least. Limit: 2x max buffer size requested by the client. size_t minDesirableBufferSize = std::min(10 * bytesPerSecond, maxBufferSize * 2); // Min buffer capacity needed to prevent data from shrinking: minNeededBufferSize at least. minNeededBufferSize = std::max(minNeededBufferSize, minDesirableBufferSize); // Calc multiplication factor for maxOutputSize depending on the byteRate and the maxReadChunkSize from DirectReadSlot. maxDeliveryChunksPerByteRate = std::max<size_t>((bytesPerSecond / maxReadChunkSize) + ((bytesPerSecond % maxReadChunkSize != 0) ? 1 : 0), 1ul); } else { // Default multiplication factor for maxOutputSize (there is no byteRate to adapt the value). maxDeliveryChunksPerByteRate = 1; } // Set maxBufferSize taking into account minNeededBufferSize, truncating the value to be divisible by maxReadChunkSize. maxBufferSize = (std::max(maxBufferSize, minNeededBufferSize) / maxReadChunkSize) * maxReadChunkSize; // Set max outputSize depending on maxDeliveryChunksPerByteRate. Limit is maxBufferSize. maxOutputSize = std::min(maxDeliveryChunksPerByteRate * maxReadChunkSize, maxBufferSize); } void StreamingBuffer::reset(bool freeData, size_t sizeToReset) { if (!sizeToReset || sizeToReset > size) { sizeToReset = size; } LOG_warn << getLogName() << "[Streaming] Reset streaming buffer. Actual size: " << size << ", free: " << free << ", capacity = " << capacity << ", size to reset: " << sizeToReset << "] [inpos = " << inpos << ", outpos = " << outpos << "]"; this->inpos = this->inpos >= sizeToReset ? this->inpos - sizeToReset : this->capacity - (sizeToReset - this->inpos); this->outpos = this->outpos >= sizeToReset ? this->outpos - sizeToReset : this->capacity - (sizeToReset - this->outpos); this->size -= sizeToReset; if (freeData) { this->free += sizeToReset; } } size_t StreamingBuffer::append(const char *buf, size_t len) { if (!buffer) { // initialize the buffer if it's not initialized yet init(len); } if (free < len) { LOG_debug << getLogName() << "[Streaming] Not enough available space, len will be truncated. " << " [requested = " << len << ", buffered = " << free << ", discarded = " << (len - free) << "]"; len = free; } // update the internal state size_t currentIndex = inpos; inpos += len; int remaining = static_cast<int>(inpos - capacity); inpos %= capacity; size += len; free -= len; // append the new data if (remaining <= 0) { memcpy(buffer + currentIndex, buf, len); } else { size_t num = static_cast<size_t>(static_cast<int>(len) - remaining); LOG_debug << getLogName() << "[Streaming] Length exceeds limits of circular buffer. Writing a piece of " << num << " bytes to the end and the others " << remaining << " bytes from the beginning" << " [current index = " << currentIndex << ", len = " << len << ", capacity = " << capacity << "]"; memcpy(buffer + currentIndex, buf, num); memcpy(buffer, buf + num, static_cast<size_t>(remaining)); } return len; } size_t StreamingBuffer::availableData() const { return size; } size_t StreamingBuffer::availableSpace() const { return free; } size_t StreamingBuffer::availableCapacity() const { return capacity; } uv_buf_t StreamingBuffer::nextBuffer() { if (!size) { // no data available return uv_buf_init(NULL, 0); } // prepare output buffer char *outbuf = buffer + outpos; size_t len = size < maxOutputSize ? size : maxOutputSize; if (outpos + len > capacity) { LOG_debug << getLogName() << "[Streaming] Available length exceeds limits of circular buffer: " << "Truncating output buffer length to " << (capacity - outpos) << " bytes" << " [outpos = " << outpos << ", len = " << len << ", capacity = " << capacity << "]"; len = capacity - outpos; } // update the internal state size -= len; outpos += len; outpos %= capacity; // return the buffer return uv_buf_init(outbuf, (unsigned int)(len)); } void StreamingBuffer::freeData(size_t len) { LOG_verbose << getLogName() << "[Streaming] Streaming buffer free data: len = " << len << ", actual free = " << free << ", new free = " << (free + len) << ", size = " << size << " [capacity = " << capacity << "]"; // update the internal state free += len; } void StreamingBuffer::setMaxBufferSize(unsigned int bufferSize) { LOG_debug << getLogName() << "[Streaming] Set new max buffer size for StreamingBuffer: " << bufferSize; if (bufferSize) { this->maxBufferSize = bufferSize; } else { this->maxBufferSize = MAX_BUFFER_SIZE; } } void StreamingBuffer::setMaxOutputSize(unsigned int outputSize) { LOG_debug << getLogName() << "[Streaming] Set new max output size for StreamingBuffer: " << outputSize; if (outputSize) { this->maxOutputSize = outputSize; } else { this->maxOutputSize = MAX_OUTPUT_SIZE; } } void StreamingBuffer::setFileSize(m_off_t newFileSize) { fileSize = newFileSize; LOG_debug << getLogName() << "[Streaming] File size set to " << fileSize << " bytes"; } void StreamingBuffer::setDuration(int newDuration) { if (!newDuration) { // Param 'newDuration' is documented in MegaNode::getDuration() to be -1 if it's not // defined. Hence, if it's 0, the file should be a media file... which has a duration of 0 // seconds. LOG_warn << getLogName() << "[Streaming] Duration value is 0 seconds for this media file!"; } duration = newDuration > 0 ? newDuration : 0; LOG_debug << getLogName() << "[Streaming] File duration set to " << duration << " secs"; } m_off_t StreamingBuffer::getBytesPerSecond() const { if (fileSize < duration) { LOG_err << getLogName() << "[Streaming] File size is smaller than its duration in seconds!" << " [file size = " << fileSize << " bytes" << " , duration = " << duration << " secs]"; } return duration ? (fileSize / duration) : 0; } m_off_t StreamingBuffer::partialDuration(m_off_t partialSize) const { partialSize = std::min(partialSize, fileSize); m_off_t bytesPerSecond = getBytesPerSecond(); return bytesPerSecond ? (partialSize / bytesPerSecond) : 0; } unsigned StreamingBuffer::getMaxBufferSize() { return this->maxBufferSize ? static_cast<unsigned>(this->maxBufferSize) : StreamingBuffer::MAX_BUFFER_SIZE; } unsigned StreamingBuffer::getMaxOutputSize() { return this->maxOutputSize ? static_cast<unsigned>(this->maxOutputSize) : StreamingBuffer::MAX_OUTPUT_SIZE; } std::string StreamingBuffer::bufferStatus() const { std::string bufferState; bufferState.reserve(256); bufferState.append("[|Buffer status| buffered = ") .append(std::to_string(size)); if (duration) bufferState.append(" (").append( std::to_string(partialDuration(static_cast<m_off_t>(size))).append(" secs)")); bufferState.append(", free = ").append(std::to_string(free)); if (duration) bufferState.append(" (").append( std::to_string(partialDuration(static_cast<m_off_t>(free))).append(" secs)")); bufferState.append(", capacity = ").append(std::to_string(capacity)); if (duration) bufferState.append(" (").append( std::to_string(partialDuration(static_cast<m_off_t>(capacity))).append(" secs)")); bufferState.append("]"); return bufferState; } // http_parser settings http_parser_settings MegaTCPServer::parsercfg; MegaTCPServer::MegaTCPServer(MegaApiImpl* megaApi, string basePath, [[maybe_unused]] bool tls, [[maybe_unused]] string certificatepath, [[maybe_unused]] string keypath, bool ipv6): useIPv6(ipv6), #ifdef ENABLE_EVT_TLS useTLS(tls) #else useTLS(false) #endif { this->megaApi = megaApi; this->localOnly = true; this->started = false; this->port = 0; this->maxBufferSize = 0; this->maxOutputSize = 0; this->restrictedMode = MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS; this->lastHandle = INVALID_HANDLE; this->remainingcloseevents = 0; this->closing = false; this->thread = new MegaThread(); #ifdef ENABLE_EVT_TLS this->certificatepath = certificatepath; this->keypath = keypath; this->closing = false; this->remainingcloseevents = 0; this->evtrequirescleaning = false; #endif fsAccess = mega::createFSA(); if (basePath.size()) { LocalPath lp = LocalPath::fromAbsolutePath(basePath); if (!lp.endsInSeparator()) { lp.appendWithSeparator(LocalPath(), true); } string sBasePath = lp.toPath(false); this->basePath = sBasePath; } semaphoresdestroyed = false; uv_sem_init(&semaphoreEnd, 0); uv_sem_init(&semaphoreStartup, 0); } MegaTCPServer::~MegaTCPServer() { LOG_verbose << "MegaTCPServer::~MegaTCPServer BEGIN"; stop(); thread->join(); delete thread; semaphoresdestroyed = true; uv_sem_destroy(&semaphoreStartup); uv_sem_destroy(&semaphoreEnd); fsAccess.reset(); LOG_verbose << "MegaTCPServer::~MegaTCPServer END"; } bool MegaTCPServer::start(int newPort, bool newLocalOnly) { if (started && port == newPort && localOnly == newLocalOnly) { LOG_verbose << "MegaTCPServer::start Alread started at that port, returning " << started; return true; } if (started) { stop(); } port = newPort; localOnly = newLocalOnly; thread->start(threadEntryPoint, this); uv_sem_wait(&semaphoreStartup); if (!started) port = 0; LOG_verbose << "MegaTCPServer::start. port = " << port << ", returning " << started; return started; } #ifdef ENABLE_EVT_TLS int MegaTCPServer::uv_tls_writer(evt_tls_t *evt_tls, void *bfr, int sz) { int rv = 0; uv_buf_t b; b.base = (char*)bfr; b.len = sz; MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data; assert(tcpctx != NULL); if (uv_is_writable((uv_stream_t*)(&tcpctx->tcphandle))) { uv_write_t *req = new uv_write_t(); tcpctx->writePointers.push_back((char*)bfr); req->data = tcpctx; LOG_verbose << "Sending " << sz << " bytes of TLS data on port = " << tcpctx->server->port; if (int err = uv_write(req, (uv_stream_t*)&tcpctx->tcphandle, &b, 1, onWriteFinished_tls_async)) { LOG_warn << "At uv_tls_writer: Finishing due to an error sending the response: " << err; tcpctx->writePointers.pop_back(); delete [] (char*)bfr; delete req; closeTCPConnection(tcpctx); } rv = sz; //writer should return the written size } else { delete [] (char*)bfr; LOG_debug << " uv_is_writable returned false"; } return rv; } #endif // return 0 on error, otherwise a port number (positive) static int getBoundPort(uv_tcp_t* server) { assert(server); struct sockaddr_storage bound_addr; int addr_len = sizeof(bound_addr); if (uv_tcp_getsockname(server, reinterpret_cast<struct sockaddr*>(&bound_addr), &addr_len)) { // error return 0; } int port = 0; if (bound_addr.ss_family == AF_INET) { const auto addrin = reinterpret_cast<struct sockaddr_in*>(&bound_addr); port = ntohs(addrin->sin_port); } else if (bound_addr.ss_family == AF_INET6) { const auto addrin6 = reinterpret_cast<struct sockaddr_in6*>(&bound_addr); port = ntohs(addrin6->sin6_port); } else { // Doesn't support others assert(false); } return port; } void MegaTCPServer::run() { LOG_debug << " Running tcp server: " << port << " TLS=" << useTLS; #ifdef ENABLE_EVT_TLS if (useTLS) { if (evt_ctx_init_ex(&evtctx, certificatepath.c_str(), keypath.c_str()) != 1 ) { LOG_err << "Unable to init evt ctx"; uv_sem_post(&semaphoreStartup); uv_sem_post(&semaphoreEnd); return; } evt_ctx_set_nio(&evtctx, NULL, uv_tls_writer); } #endif uv_loop_init(&uv_loop); uv_async_init(&uv_loop, &exit_handle, onCloseRequested); exit_handle.data = this; uv_tcp_init(&uv_loop, &server); server.data = this; uv_tcp_keepalive(&server, 0, 0); union { struct sockaddr_in6 ipv6; struct sockaddr_in ipv4; } address; if (useIPv6) { if (localOnly) { uv_ip6_addr("::1", port, &address.ipv6); } else { uv_ip6_addr("::", port, &address.ipv6); } } else { if (localOnly) { uv_ip4_addr("127.0.0.1", port, &address.ipv4); } else { uv_ip4_addr("0.0.0.0", port, &address.ipv4); } } uv_connection_cb onNewClientCB; #ifdef ENABLE_EVT_TLS if (useTLS) { onNewClientCB = onNewClient_tls; } else { #endif onNewClientCB = onNewClient; #ifdef ENABLE_EVT_TLS } #endif auto cleanOnError = [this](const char* action) { LOG_err << "TCP failed to " << action << " = " << port; uv_close((uv_handle_t *)&exit_handle,NULL); uv_close((uv_handle_t *)&server,NULL); uv_sem_post(&semaphoreStartup); uv_sem_post(&semaphoreEnd); uv_run(&uv_loop, UV_RUN_ONCE); // so that resources are cleaned peacefully int closeVal = uv_loop_close(&uv_loop); // Clean up loop resources if (closeVal) { LOG_err << "[MegaTCPServer::run] Error closing uv_loop: " << uv_strerror(closeVal); } }; if (uv_tcp_bind(&server, (const struct sockaddr*)&address, 0)) { cleanOnError("bind port"); return; } // Update port to bound port if request port is 0 if (port == 0) { port = getBoundPort(&server); if (port == 0) // still zero? { cleanOnError("get bound port"); return; } } if (uv_listen((uv_stream_t*)&server, 32, onNewClientCB)) { cleanOnError("listen port"); return; } LOG_info << "TCP" << (useTLS ? "(tls)" : "") << " server started on port " << port; started = true; uv_sem_post(&semaphoreStartup); LOG_info << "Starting uv loop ..."; uv_run(&uv_loop, UV_RUN_DEFAULT); // Can get here only after stop() has been called LOG_info << "UV loop ended"; #ifdef ENABLE_EVT_TLS if (useTLS) { //evt_ctx_free(&evtctx); //This causes invalid free when called second time!! collides with memory allocated elsewhere (e.g: via curl_global_init!) SSL_CTX_free(evtctx.ctx); } #endif int closeVal = uv_loop_close(&uv_loop); // Clean up loop resources if (closeVal) { LOG_err << "[MegaTCPServer::run] Error closing uv_loop: " << uv_strerror(closeVal); } LOG_debug << "UV loop thread exit"; } void MegaTCPServer::stop(bool doNotWait) { if (!started) { LOG_verbose << "Stopping non started MegaTCPServer port=" << port; return; } LOG_debug << "Stopping MegaTCPServer port = " << port; uv_async_send(&exit_handle); if (!doNotWait) { LOG_verbose << "Waiting for sempahoreEnd to conclude server stop port = " << port; uv_sem_wait(&semaphoreEnd); //this is signaled when closed my last connection } LOG_debug << "Stopped MegaTCPServer port = " << port; started = false; port = 0; } int MegaTCPServer::getPort() { return port; } bool MegaTCPServer::isLocalOnly() { return localOnly; } void MegaTCPServer::setMaxBufferSize(int bufferSize) { this->maxBufferSize = bufferSize <= 0 ? 0 : bufferSize; } void MegaTCPServer::setMaxOutputSize(int outputSize) { this->maxOutputSize = outputSize <= 0 ? 0 : outputSize; } int MegaTCPServer::getMaxBufferSize() { if (maxBufferSize) { return maxBufferSize; } return StreamingBuffer::MAX_BUFFER_SIZE; } int MegaTCPServer::getMaxOutputSize() { if (maxOutputSize) { return maxOutputSize; } return StreamingBuffer::MAX_OUTPUT_SIZE; } void MegaTCPServer::setRestrictedMode(int mode) { this->restrictedMode = mode; } int MegaTCPServer::getRestrictedMode() { return restrictedMode; } bool MegaTCPServer::isHandleAllowed(handle h) { return restrictedMode == MegaApi::TCP_SERVER_ALLOW_ALL || (restrictedMode == MegaApi::TCP_SERVER_ALLOW_CREATED_LOCAL_LINKS && allowedHandles.count(h)) || (restrictedMode == MegaApi::TCP_SERVER_ALLOW_LAST_LOCAL_LINK && h == lastHandle); } void MegaTCPServer::clearAllowedHandles() { allowedHandles.clear(); lastHandle = INVALID_HANDLE; } char *MegaTCPServer::getLink(MegaNode *node, string protocol) { if (!node) { return NULL; } lastHandle = node->getHandle(); allowedHandles.insert(lastHandle); string localhostIP = useIPv6 ? "[::1]" : "127.0.0.1"; ostringstream oss; oss << protocol << (useTLS ? "s" : "") << "://" << localhostIP << ":" << port << "/"; char *base64handle = node->getBase64Handle(); oss << base64handle; delete [] base64handle; if (node->isPublic() || node->isForeign()) { char *base64key = node->getBase64Key(); oss << "!" << base64key; delete [] base64key; if (node->isForeign()) { oss << "!" << node->getSize(); auto nodePrivate{dynamic_cast<MegaNodePrivate*>(node)}; if (!nodePrivate) { LOG_err << "Critical error: Unexpected MegaNode extension received"; assert(false); return nullptr; } string* publicAuth = nodePrivate->getPublicAuth(); string* privAuth = nodePrivate->getPrivateAuth(); const char* chatAuth = nodePrivate->getChatAuth(); if (privAuth->size()) { oss << "!f" << *privAuth; } else if (publicAuth->size()) { oss << "!p" << *publicAuth; } else if (chatAuth && chatAuth[0]) { oss << "!c" << chatAuth; } } } oss << "/"; string name = node->getName(); string escapedName; URLCodec::escape(&name, &escapedName); oss << escapedName; string link = oss.str(); return MegaApi::strdup(link.c_str()); } set<handle> MegaTCPServer::getAllowedHandles() { return allowedHandles; } void MegaTCPServer::removeAllowedHandle(MegaHandle handle) { allowedHandles.erase(handle); } void *MegaTCPServer::threadEntryPoint(void *param) { #ifndef _WIN32 struct sigaction noaction; memset(&noaction, 0, sizeof(noaction)); noaction.sa_handler = SIG_IGN; ::sigaction(SIGPIPE, &noaction, 0); #endif MegaTCPServer *tcpServer = (MegaTCPServer *)param; tcpServer->run(); return NULL; } #ifdef ENABLE_EVT_TLS void MegaTCPServer::evt_on_rd(evt_tls_t *evt_tls, char *bfr, int sz) { MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data; assert(tcpctx != NULL); uv_buf_t data; data.base = bfr; data.len = sz; if (!tcpctx->invalid) { tcpctx->server->processReceivedData(tcpctx, sz, &data); } else { LOG_debug << " Not procesing invalid data after failed evt_close"; } } void MegaTCPServer::on_evt_tls_close(evt_tls_t *evt_tls, int status) { MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data; assert(tcpctx != NULL); LOG_debug << "TLS connection closed. status = " << status; if (status == 1) { closeTCPConnection(tcpctx); } else { LOG_debug << "TLS connection closed failed!!! status = " << status; tcpctx->invalid = true; } } void MegaTCPServer::on_hd_complete( evt_tls_t *evt_tls, int status) { MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data; LOG_debug << "TLS handshake finished in port = " << tcpctx->server->port << ". Status: " << status; if (status) { evt_tls_read(evt_tls, evt_on_rd); //this only establish callback if ( tcpctx->server->respondNewConnection(tcpctx) ) { // we dont need to explicitally start reading. on_tcp_read will be called } } else { evt_tls_close(evt_tls, on_evt_tls_close); } } void MegaTCPServer::onNewClient_tls(uv_stream_t *server_handle, int status) { if (status < 0) { LOG_warn << " onNewClient_tls unexpected status: " << status; return; } // Create an object to save context information MegaTCPContext* tcpctx = ((MegaTCPServer *)server_handle->data)->initializeContext(server_handle); LOG_debug << "Connection received at port " << tcpctx->server->port << " ! " << tcpctx->server->connections.size(); // Mutex to protect the data buffer uv_mutex_init(&tcpctx->mutex); // Async handle to perform writes uv_async_init(&tcpctx->server->uv_loop, &tcpctx->asynchandle, onAsyncEvent); // Accept the connection uv_tcp_init(&tcpctx->server->uv_loop, &tcpctx->tcphandle); if (uv_accept(server_handle, (uv_stream_t*)&tcpctx->tcphandle)) { LOG_err << "uv_accept failed"; onClose((uv_handle_t*)&tcpctx->tcphandle); return; } tcpctx->evt_tls = evt_ctx_get_tls(&tcpctx->server->evtctx); assert(tcpctx->evt_tls != NULL); tcpctx->evt_tls->data = tcpctx; if (evt_tls_accept(tcpctx->evt_tls, on_hd_complete)) { LOG_err << "evt_tls_accept failed"; evt_tls_close(tcpctx->evt_tls, on_evt_tls_close); return; } tcpctx->server->connections.push_back(tcpctx); tcpctx->server->readData(tcpctx); } #endif void MegaTCPServer::readData(MegaTCPContext* tcpctx) { #ifdef ENABLE_EVT_TLS if (useTLS) { uv_read_start((uv_stream_t*)(&tcpctx->tcphandle), allocBuffer, on_tcp_read); } else { #endif uv_read_start((uv_stream_t*)&tcpctx->tcphandle, allocBuffer, onDataReceived); #ifdef ENABLE_EVT_TLS } #endif } void MegaTCPServer::onNewClient(uv_stream_t* server_handle, int status) { if (status < 0) { return; } // Create an object to save context information MegaTCPContext* tcpctx = ((MegaTCPServer *)server_handle->data)->initializeContext(server_handle); LOG_debug << "Connection received at port " << tcpctx->server->port << "! " << tcpctx->server->connections.size() << " tcpctx = " << tcpctx; // Mutex to protect the data buffer uv_mutex_init(&tcpctx->mutex); // Async handle to perform writes uv_async_init(&tcpctx->server->uv_loop, &tcpctx->asynchandle, onAsyncEvent); // Accept the connection uv_tcp_init(&tcpctx->server->uv_loop, &tcpctx->tcphandle); if (uv_accept(server_handle, (uv_stream_t*)&tcpctx->tcphandle)) { LOG_err << "uv_accept failed"; onClose((uv_handle_t*)&tcpctx->tcphandle); return; } tcpctx->server->connections.push_back(tcpctx); if (tcpctx->server->respondNewConnection(tcpctx)) { // Start reading tcpctx->server->readData(tcpctx); } } void MegaTCPServer::allocBuffer(uv_handle_t *, size_t suggested_size, uv_buf_t* buf) { // Reserve a buffer with the suggested size *buf = uv_buf_init(new char[suggested_size], static_cast<unsigned>(suggested_size)); } void MegaTCPServer::onDataReceived(uv_stream_t* tcp, ssize_t nread, const uv_buf_t * buf) { MegaTCPContext *tcpctx = (MegaTCPContext*) tcp->data; tcpctx->server->processReceivedData(tcpctx, nread, buf); delete [] buf->base; } #ifdef ENABLE_EVT_TLS void MegaTCPServer::on_tcp_read(uv_stream_t *tcp, ssize_t nrd, const uv_buf_t *data) { MegaTCPContext *tcpctx = (MegaTCPContext*) tcp->data; assert( tcpctx != NULL); LOG_debug << "Received " << nrd << " bytes at port " << tcpctx->server->port; if (!nrd) { return; } if (nrd < 0) { if (evt_tls_is_handshake_over(tcpctx->evt_tls)) { LOG_verbose << "MegaTCPServer::on_tcp_read calling processReceivedData"; tcpctx->server->processReceivedData(tcpctx, nrd, data); evt_tls_close(tcpctx->evt_tls, on_evt_tls_close); } else { //if handshake is not over, simply tear down without close_notify closeTCPConnection(tcpctx); } delete[] data->base; return; } evt_tls_feed_data(tcpctx->evt_tls, data->base, static_cast<int>(nrd)); delete[] data->base; } #endif void MegaTCPServer::onClose(uv_handle_t* handle) { MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data; // streaming transfers are automatically stopped when their listener is removed tcpctx->megaApi->removeTransferListener(tcpctx); tcpctx->megaApi->removeRequestListener(tcpctx); tcpctx->server->connections.remove(tcpctx); LOG_debug << "Connection closed: " << tcpctx->server->connections.size() << " port = " << tcpctx->server->port << " closing async handle"; uv_close((uv_handle_t *)&tcpctx->asynchandle, onAsyncEventClose); } void MegaTCPServer::onAsyncEventClose(uv_handle_t *handle) { MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data; assert(!tcpctx->writePointers.size()); int port = tcpctx->server->port; tcpctx->server->remainingcloseevents--; tcpctx->server->processOnAsyncEventClose(tcpctx); LOG_verbose << "At onAsyncEventClose port = " << tcpctx->server->port << " remaining=" << tcpctx->server->remainingcloseevents; if (!tcpctx->server->remainingcloseevents && tcpctx->server->closing && !tcpctx->server->semaphoresdestroyed) { uv_sem_post(&tcpctx->server->semaphoreStartup); uv_sem_post(&tcpctx->server->semaphoreEnd); } uv_mutex_destroy(&tcpctx->mutex); delete tcpctx; LOG_debug << "Connection deleted, port = " << port; } #ifdef ENABLE_EVT_TLS void MegaTCPServer::onWriteFinished_tls(evt_tls_t *evt_tls, int status) { MegaTCPContext *tcpctx = (MegaTCPContext*)evt_tls->data; assert(tcpctx != NULL); if (status < 0) { LOG_warn << " error received at onWriteFinished_tls: " << status; } if (tcpctx->finished) { LOG_debug << "At onWriteFinished_tls; TCP link closed, ignoring the result of the write"; return; } tcpctx->server->processWriteFinished(tcpctx, status); } void MegaTCPServer::onWriteFinished_tls_async(uv_write_t* req, int status) { MegaTCPContext *tcpctx = (MegaTCPContext*)req->data; assert(tcpctx->writePointers.size()); delete [] tcpctx->writePointers.front(); tcpctx->writePointers.pop_front(); delete req; if (tcpctx->finished) { if (tcpctx->size == tcpctx->bytesWritten && !tcpctx->writePointers.size()) { LOG_debug << "TCP link closed, shutdown result: " << status << " port = " << tcpctx->server->port; } else { LOG_debug << "TCP link closed, ignoring the result of the async TLS write: " << status << " port = " << tcpctx->server->port; } return; } if (status < 0) { LOG_warn << "Finishing request. Async TLS write failed: " << status; evt_tls_close(tcpctx->evt_tls, on_evt_tls_close); return; } if (tcpctx->size == tcpctx->bytesWritten && !tcpctx->writePointers.size()) { LOG_debug << "Finishing request. All data delivered"; evt_tls_close(tcpctx->evt_tls, on_evt_tls_close); return; } LOG_verbose << "Async TLS write finished"; uv_async_send(&tcpctx->asynchandle); } #endif void MegaTCPServer::onWriteFinished(uv_write_t* req, int status) { MegaTCPContext* tcpctx = (MegaTCPContext*) req->data; assert(tcpctx != NULL); if (tcpctx->finished) { LOG_debug << "At onWriteFinished; TCP link closed, ignoring the result of the write"; delete req; return; } tcpctx->server->processWriteFinished(tcpctx, status); delete req; } MegaTCPContext::MegaTCPContext() { size = -1; finished = false; bytesWritten = 0; #ifdef ENABLE_EVT_TLS evt_tls = NULL; invalid = false; #endif server = NULL; megaApi = NULL; } MegaTCPContext::~MegaTCPContext() { #ifdef ENABLE_EVT_TLS if (evt_tls) { evt_tls_free(evt_tls); } #endif if(!finished) // Set listener pointers to NULL upon premature destruction { megaApi->removeTransferListener(this); megaApi->removeRequestListener(this); } } void MegaTCPServer::onAsyncEvent(uv_async_t* handle) { MegaTCPContext* tcpctx = (MegaTCPContext*) handle->data; assert(tcpctx->server != NULL); #ifdef ENABLE_EVT_TLS if (tcpctx->server->useTLS && !evt_tls_is_handshake_over(tcpctx->evt_tls)) { LOG_debug << " skipping processAsyncEvent due to handshake not over on port = " << tcpctx->server->port; return; } #endif tcpctx->server->processAsyncEvent(tcpctx); } void MegaTCPServer::onExitHandleClose(uv_handle_t *handle) { MegaTCPServer *tcpServer = (MegaTCPServer*) handle->data; assert(tcpServer != NULL); tcpServer->remainingcloseevents--; LOG_verbose << "At onExitHandleClose port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents; tcpServer->processOnExitHandleClose(tcpServer); if (!tcpServer->remainingcloseevents && !tcpServer->semaphoresdestroyed) { uv_sem_post(&tcpServer->semaphoreStartup); uv_sem_post(&tcpServer->semaphoreEnd); } } void MegaTCPServer::onCloseRequested(uv_async_t *handle) { MegaTCPServer *tcpServer = (MegaTCPServer*) handle->data; LOG_debug << "TCP server stopping port=" << tcpServer->port; tcpServer->closing = true; for (list<MegaTCPContext*>::iterator it = tcpServer->connections.begin(); it != tcpServer->connections.end(); it++) { MegaTCPContext *tcpctx = (*it); closeTCPConnection(tcpctx); } tcpServer->remainingcloseevents++; LOG_verbose << "At onCloseRequested: closing server port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents; uv_close((uv_handle_t *)&tcpServer->server, onExitHandleClose); tcpServer->remainingcloseevents++; LOG_verbose << "At onCloseRequested: closing exit_handle port = " << tcpServer->port << " remainingcloseevent = " << tcpServer->remainingcloseevents; uv_close((uv_handle_t *)&tcpServer->exit_handle, onExitHandleClose); } void MegaTCPServer::closeConnection(MegaTCPContext *tcpctx) { LOG_verbose << "At closeConnection port = " << tcpctx->server->port; #ifdef ENABLE_EVT_TLS if (tcpctx->server->useTLS) { evt_tls_close(tcpctx->evt_tls, on_evt_tls_close); } else { #endif closeTCPConnection(tcpctx); return; #ifdef ENABLE_EVT_TLS } #endif } void MegaTCPServer::closeTCPConnection(MegaTCPContext *tcpctx) { tcpctx->finished = true; if (!uv_is_closing((uv_handle_t*)&tcpctx->tcphandle)) { tcpctx->server->remainingcloseevents++; LOG_verbose << "At closeTCPConnection port = " << tcpctx->server->port << " remainingcloseevent = " << tcpctx->server->remainingcloseevents; uv_close((uv_handle_t*)&tcpctx->tcphandle, onClose); } } void MegaTCPServer::processOnAsyncEventClose(MegaTCPContext*) // without this closing breaks! { LOG_debug << "At supposed to be virtual processOnAsyncEventClose"; } void MegaTCPServer::processOnExitHandleClose(MegaTCPServer*) // without this closing breaks! { LOG_debug << "At supposed to be virtual processOnExitHandleClose"; } void MegaTCPServer::processReceivedData(MegaTCPContext*, ssize_t /*nread*/, const uv_buf_t*) { LOG_debug << "At supposed to be virtual processReceivedData"; } void MegaTCPServer::processAsyncEvent(MegaTCPContext*) { LOG_debug << "At supposed to be virtual processAsyncEvent"; } /////////////////////////////// // MegaHTTPServer specifics // /////////////////////////////// MegaHTTPServer::MegaHTTPServer(MegaApiImpl *megaApi, string basePath, bool useTLS, string certificatepath, string keypath, bool useIPv6) : MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath, useIPv6) { // parser callbacks parsercfg.on_url = onUrlReceived; parsercfg.on_message_begin = onMessageBegin; parsercfg.on_headers_complete = onHeadersComplete; parsercfg.on_message_complete = onMessageComplete; parsercfg.on_header_field = onHeaderField; parsercfg.on_header_value = onHeaderValue; parsercfg.on_body = onBody; this->fileServerEnabled = true; this->folderServerEnabled = true; this->offlineAttribute = false; this->subtitlesSupportEnabled = false; } MegaTCPContext * MegaHTTPServer::initializeContext(uv_stream_t *server_handle) { MegaHTTPContext* httpctx = new MegaHTTPContext(); // Initialize the parser http_parser_init(&httpctx->parser, HTTP_REQUEST); // Set connection data MegaHTTPServer* httpServer = (MegaHTTPServer*)(server_handle->data); httpctx->server = httpServer; httpctx->megaApi = httpServer->megaApi; httpctx->parser.data = httpctx; httpctx->tcphandle.data = httpctx; httpctx->asynchandle.data = httpctx; return httpctx; } void MegaHTTPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf) { MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx); LOG_debug << httpctx->getLogName() << "Received " << nread << " bytes"; ssize_t parsed = -1; if (nread >= 0) { if (nread == 0 && httpctx->parser.method == HTTP_PUT) //otherwise it will fail for files >65k in GVFS-DAV { LOG_debug << httpctx->getLogName() << " Skipping parsing 0 length data for HTTP_PUT"; parsed = 0; } else { parsed = static_cast<ssize_t>(http_parser_execute(&httpctx->parser, &parsercfg, buf->base, static_cast<size_t>(nread))); } } LOG_verbose << httpctx->getLogName() << " at onDataReceived, received " << nread << " parsed = " << parsed; if (parsed < 0 || nread < 0 || parsed < nread || httpctx->parser.upgrade) { LOG_debug << httpctx->getLogName() << "Finishing request. Connection reset by peer or unsupported data"; closeConnection(httpctx); } } void MegaHTTPServer::processWriteFinished(MegaTCPContext* tcpctx, int status) { MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx); if (httpctx->finished) { LOG_debug << httpctx->getLogName() << "HTTP link closed, ignoring the result of the write"; return; } httpctx->bytesWritten += httpctx->lastBufferLen; LOG_verbose << httpctx->getLogName() << "Bytes written: " << httpctx->lastBufferLen << " Remaining: " << (httpctx->size - httpctx->bytesWritten); httpctx->lastBuffer = NULL; if (status < 0 || httpctx->size == httpctx->bytesWritten) { if (status < 0) { LOG_warn << httpctx->getLogName() << "Finishing request. Write failed: " << status; } else { LOG_debug << httpctx->getLogName() << "Finishing request. All data sent"; if (httpctx->resultCode == API_EINTERNAL) { httpctx->resultCode = API_OK; } } closeConnection(httpctx); return; } uv_mutex_lock(&httpctx->mutex); if (httpctx->lastBufferLen) { httpctx->streamingBuffer.freeData(httpctx->lastBufferLen); } if (httpctx->pause) { if (httpctx->streamingBuffer.availableSpace() >= DirectReadSlot::MAX_DELIVERY_CHUNK) { httpctx->pause = false; m_off_t start = httpctx->rangeStart + httpctx->rangeWritten + static_cast<m_off_t>(httpctx->streamingBuffer.availableData()); m_off_t len = httpctx->rangeEnd - httpctx->rangeStart - httpctx->rangeWritten - static_cast<m_off_t>(httpctx->streamingBuffer.availableData()); LOG_debug << httpctx->getLogName() << "[Streaming] Resuming streaming from " << start << " len: " << len << " " << httpctx->streamingBuffer.bufferStatus(); httpctx->megaApi->startStreaming(httpctx->node, start, len, httpctx); } } httpctx->lastBufferLen = 0; uv_mutex_unlock(&httpctx->mutex); uv_async_send(&httpctx->asynchandle); } void MegaHTTPServer::processOnAsyncEventClose(MegaTCPContext* tcpctx) { MegaHTTPContext* httpctx = dynamic_cast<MegaHTTPContext *>(tcpctx); if (httpctx->resultCode == API_EINTERNAL) { httpctx->resultCode = API_EINCOMPLETE; } if (httpctx->transfer) { httpctx->megaApi->cancelTransfer(httpctx->transfer.get()); httpctx->megaApi->fireOnStreamingFinish(httpctx->transfer.release(), std::make_unique<MegaErrorPrivate>(httpctx->resultCode)); // transfer will be deleted in fireOnStreamingFinish } delete httpctx->node; httpctx->node = NULL; } bool MegaHTTPServer::respondNewConnection(MegaTCPContext*) { return true; } void MegaHTTPServer::processOnExitHandleClose(MegaTCPServer*) {} MegaHTTPServer::~MegaHTTPServer() { // if not stopped, the uv thread might want to access a pointer to this. // though this is done in the parent destructor, it could try to access it after vtable has been erased stop(); } bool MegaHTTPServer::isHandleWebDavAllowed(handle h) { return allowedWebDavHandles.count(h) > 0; } void MegaHTTPServer::clearAllowedHandles() { allowedWebDavHandles.clear(); MegaTCPServer::clearAllowedHandles(); } set<handle> MegaHTTPServer::getAllowedWebDavHandles() { return allowedWebDavHandles; } void MegaHTTPServer::removeAllowedWebDavHandle(MegaHandle handle) { allowedWebDavHandles.erase(handle); } void MegaHTTPServer::enableFileServer(bool enable) { this->fileServerEnabled = enable; } void MegaHTTPServer::enableFolderServer(bool enable) { this->folderServerEnabled = enable; } void MegaHTTPServer::enableOfflineAttribute(bool enable) { this->offlineAttribute = enable; } bool MegaHTTPServer::isFileServerEnabled() { return fileServerEnabled; } bool MegaHTTPServer::isFolderServerEnabled() { return folderServerEnabled; } bool MegaHTTPServer::isOfflineAttributeEnabled() { return offlineAttribute; } bool MegaHTTPServer::isSubtitlesSupportEnabled() { return subtitlesSupportEnabled; } void MegaHTTPServer::enableSubtitlesSupport(bool enable) { this->subtitlesSupportEnabled = enable; } char *MegaHTTPServer::getWebDavLink(MegaNode *node) { allowedWebDavHandles.insert(node->getHandle()); return getLink(node); } int MegaHTTPServer::onMessageBegin(http_parser *) { return 0; } int MegaHTTPServer::onHeadersComplete(http_parser *) { return 0; } int MegaHTTPServer::onUrlReceived(http_parser *parser, const char *url, size_t length) { MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data; httpctx->path.assign(url, length); LOG_debug << httpctx->getLogName() << "URL received: " << httpctx->path; if (length < 9 || url[0] != '/' || (length >= 10 && url[9] != '/' && url[9] != '!')) { LOG_debug << httpctx->getLogName() << "URL without node handle"; return 0; } size_t index = 9; httpctx->nodehandle.assign(url + 1, 8); LOG_debug << httpctx->getLogName() << "Node handle: " << httpctx->nodehandle; if (length > 53 && url[index] == '!') { httpctx->nodekey.assign(url + 10, 43); LOG_debug << httpctx->getLogName() << "Link key: " << httpctx->nodekey; index = 53; if (length > 54 && url[index] == '!') { const char* startsize = url + index + 1; const char* endsize = strstr(startsize, "/"); const char* endparam = strstr(startsize, "!"); if (endsize && *startsize >= '0' && *startsize <= '9') { char* endptr; m_off_t size = strtoll(startsize, &endptr, 10); if ((endptr == endsize || endptr == endparam) && errno != ERANGE) { httpctx->nodesize = size; LOG_debug << httpctx->getLogName() << "Link size: " << size; index += static_cast<size_t>(endptr - startsize) + 1u; if (url[index] == '!') { const char *typeauth = url + index + 1; index++; const char *ptr = url + index + 1; string auth; auth.assign(ptr, static_cast<size_t>(endsize - ptr)); if (*typeauth == 'p') { assert(auth.size() == 8); httpctx->nodepubauth = auth; LOG_debug << httpctx->getLogName() << "Link public auth: " << auth; } else if (*typeauth == 'c') { assert(auth.size() == 8); httpctx->nodechatauth = auth; LOG_debug << httpctx->getLogName() << "Chat link auth: " << auth; } else if (*typeauth == 'f') { httpctx->nodeprivauth = auth; LOG_debug << httpctx->getLogName() << "Link private auth: " << auth; } else { LOG_err << httpctx->getLogName() << "Unknown type of auth token: " << *typeauth; } index += auth.size() + 1; } } } } } if (length > index && url[index] != '/') { LOG_warn << httpctx->getLogName() << "Invalid URL"; return 0; } index++; if (length > index) { string nodename(url + index, length - index); //get subpath (used in webdav) size_t psep = nodename.find("/"); if (psep != string::npos) { string subpathrelative = nodename.substr(psep + 1); nodename = nodename.substr(0, psep); URLCodec::unescape(&subpathrelative, &httpctx->subpathrelative); LOG_debug << httpctx->getLogName() << "subpathrelative: " << httpctx->subpathrelative; } URLCodec::unescape(&nodename, &httpctx->nodename); LocalPath::utf8_normalize(&httpctx->nodename); LOG_debug << httpctx->getLogName() << "Node name: " << httpctx->nodename; } return 0; } int MegaHTTPServer::onHeaderField(http_parser *parser, const char *at, size_t length) { MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data; httpctx->lastheader = string(at, length); tolower_string(httpctx->lastheader); if (Utils::startswith(at, "Range")) { httpctx->range = true; LOG_debug << httpctx->getLogName() << "Range header detected"; } return 0; } int MegaHTTPServer::onHeaderValue(http_parser *parser, const char *at, size_t length) { MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data; string value(at, length); size_t index; char *endptr; LOG_verbose << httpctx->getLogName() << " onHeaderValue: " << httpctx->lastheader << " = " << value; if (httpctx->lastheader == "depth") { httpctx->depth = atoi(value.c_str()); } else if (httpctx->lastheader == "host") { httpctx->host = value; } else if (httpctx->lastheader == "destination") { httpctx->destination = value; } else if (httpctx->lastheader == "overwrite") { httpctx->overwrite = (value == "T"); } else if (httpctx->range) { LOG_debug << httpctx->getLogName() << "Range header value: " << value; httpctx->range = false; if (Utils::startswith(at, "bytes=") && ((index = value.find_first_of('-')) != string::npos)) { endptr = (char *)value.c_str(); unsigned long long number = strtoull(value.c_str() + 6, &endptr, 10); if (endptr == value.c_str() || *endptr != '-' || number == ULLONG_MAX) { return 0; } httpctx->rangeStart = static_cast<m_off_t>(number); if (length > (index + 1)) { number = strtoull(value.c_str() + index + 1, &endptr, 10); if (endptr == value.c_str() || *endptr != '\0' || number == ULLONG_MAX) { return 0; } httpctx->rangeEnd = static_cast<m_off_t>(number); } LOG_debug << httpctx->getLogName() << "Range value parsed: " << httpctx->rangeStart << " - " << httpctx->rangeEnd; } } return 0; } int MegaHTTPServer::onBody(http_parser *parser, const char *b, size_t n) { MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data; if (parser->method == HTTP_PUT) { //create tmp file with contents in messageBody if (!httpctx->tmpFileAccess) { httpctx->tmpFileName=httpctx->server->basePath; httpctx->tmpFileName.append("httputfile"); httpctx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false)); string ext; LocalPath localpath = LocalPath::fromAbsolutePath(httpctx->path); if (httpctx->server->fsAccess->getextension(localpath, ext)) { httpctx->tmpFileName.append(ext); } httpctx->tmpFileAccess = httpctx->server->fsAccess->newfileaccess(); LocalPath localPath = LocalPath::fromAbsolutePath(httpctx->tmpFileName); httpctx->server->fsAccess->unlinklocal(localPath); if (!httpctx->tmpFileAccess->fopen(localPath, OPEN_WRONLY, FSLogging::logOnError)) { returnHttpCode(httpctx, 500); //is it ok to have a return here (not int onMessageComplete)? return 0; } } if (!httpctx->tmpFileAccess->fwrite(reinterpret_cast<const byte*>(b), static_cast<unsigned>(n), static_cast<m_off_t>(httpctx->messageBodySize))) { returnHttpCode(httpctx, 500); return 0; } httpctx->messageBodySize += n; } else { char *newbody = new char[n + httpctx->messageBodySize]; memcpy(newbody, httpctx->messageBody, httpctx->messageBodySize); memcpy(newbody + httpctx->messageBodySize, b, n); httpctx->messageBodySize += n; delete [] httpctx->messageBody; httpctx->messageBody = newbody; } return 0; } string MegaHTTPServer::getWebDavProfFindNodeContents(MegaNode *node, string baseURL, bool offlineAttribute) { std::ostringstream web; web << "<d:response>\r\n" "<d:href>" << webdavurlescape(baseURL) << "</d:href>\r\n" "<d:propstat>\r\n" "<d:status>HTTP/1.1 200 OK</d:status>\r\n" "<d:prop>\r\n" "<d:displayname>"<< webdavnameescape(node->getName()) << "</d:displayname>\r\n" "<d:creationdate>" << rfc1123_datetime(node->getCreationTime()) << "</d:creationdate>" "<d:getlastmodified>" << rfc1123_datetime(node->getModificationTime()) << "</d:getlastmodified>" ; if (offlineAttribute) { //(perhaps this could be based on number of files / or even better: size) web << "<Z:Win32FileAttributes>00001000</Z:Win32FileAttributes> \r\n"; //FILE_ATTRIBUTE_OFFLINE // web << "<Z:Win32FileAttributes>00040000</Z:Win32FileAttributes> \r\n"; // FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS (no actual difference) } if (node->isFolder()) { web << "<d:resourcetype>\r\n" "<d:collection />\r\n" "</d:resourcetype>\r\n"; } else { web << "<d:resourcetype />\r\n"; web << "<d:getcontentlength>" << node->getSize() << "</d:getcontentlength>\r\n"; } web << "</d:prop>\r\n" "</d:propstat>\r\n"; web << "</d:response>\r\n"; return web.str(); } string MegaHTTPServer::getWebDavPropFindResponseForNode(string baseURL, string subnodepath, MegaNode *node, MegaHTTPContext* httpctx) { std::ostringstream response; std::ostringstream web; web << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" "<d:multistatus xmlns:d=\"DAV:\" xmlns:Z=\"urn:schemas-microsoft-com::\">\r\n"; string subbaseURL = baseURL + subnodepath; if (node->isFolder() && subbaseURL.size() && subbaseURL.at(subbaseURL.size() - 1) != '/') { subbaseURL.append("/"); } MegaHTTPServer* httpserver = dynamic_cast<MegaHTTPServer *>(httpctx->server); web << getWebDavProfFindNodeContents(node, subbaseURL, httpserver->isOfflineAttributeEnabled()); if (node->isFolder() && (httpctx->depth != 0)) { MegaNodeList *children = httpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE); for (int i = 0; i < children->size(); i++) { MegaNode *child = children->get(i); string childURL = subbaseURL + child->getName(); web << getWebDavProfFindNodeContents(child, childURL, httpserver->isOfflineAttributeEnabled()); } delete children; } web << "</d:multistatus>" "\r\n"; string sweb = web.str(); response << "HTTP/1.1 207 Multi-Status\r\n" "content-length: " << sweb.size() << "\r\n" "content-type: application/xml; charset=utf-8\r\n" "server: MEGAsdk\r\n" "\r\n"; response << sweb; httpctx->resultCode = API_OK; return response.str(); } string MegaHTTPServer::getResponseForNode(MegaNode *node, MegaHTTPContext* httpctx) { MegaNode *parent = httpctx->megaApi->getParentNode(node); MegaNodeList *children = httpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE); std::ostringstream response; std::ostringstream web; // Title web << "<title>MEGA"; // Styles web << ""; // Folder path web << ""; char *path = httpctx->megaApi->getNodePath(node); if (path) { web << path; delete [] path; } else { web << node->getName(); } web << "

    "; // Child nodes web << ""; if (parent) { web << ""; } for (int i = 0; i < children->size(); i++) { web << ""; } web << "
    "; char *base64Handle = parent->getBase64Handle(); if (httpctx->megaApi->httpServerGetRestrictedMode() == MegaApi::TCP_SERVER_ALLOW_ALL) { web << "getName(); } else { web << "getName(); } web << "\">.."; delete [] base64Handle; delete parent; web << "
    "; MegaNode *child = children->get(i); char *base64Handle = child->getBase64Handle(); if (httpctx->megaApi->httpServerGetRestrictedMode() == MegaApi::TCP_SERVER_ALLOW_ALL) { web << "getName(); } else { web << "getName() << "/" << child->getName(); } web << "\">isFile() ? "file" : "folder") << "\">" << child->getName() << ""; delete [] base64Handle; if (!child->isFile()) { web << ""; } else { unsigned const long long KB = 1024; unsigned const long long MB = 1024 * KB; unsigned const long long GB = 1024 * MB; unsigned const long long TB = 1024 * GB; web << ""; unsigned long long bytes = static_cast(child->getSize()); if (bytes > TB) web << double(((unsigned long long)((100 * bytes) / TB)))/100.0 << " TB"; else if (bytes > GB) web << double(((unsigned long long)((100 * bytes) / GB)))/100.0 << " GB"; else if (bytes > MB) web << double(((unsigned long long)((100 * bytes) / MB)))/100.0 << " MB"; else if (bytes > KB) web << double(((unsigned long long)((100 * bytes) / KB)))/100.0 << " KB"; web << ""; } web << "
    "; delete children; string sweb = web.str(); response << "HTTP/1.1 200 OK\r\n" << "Content-Type: text/html; charset=utf-8\r\n" << "Connection: close\r\n" << "Content-Length: " << sweb.size() << "\r\n" << "Access-Control-Allow-Origin: *\r\n" << "\r\n"; if (httpctx->parser.method != HTTP_HEAD) { response << sweb; } httpctx->resultCode = API_OK; return response.str(); } string MegaHTTPServer::getHTTPMethodName(int httpmethod) { switch (httpmethod) { case HTTP_DELETE: return "HTTP_DELETE"; case HTTP_GET: return "HTTP_GET"; case HTTP_HEAD: return "HTTP_HEAD"; case HTTP_POST: return "HTTP_POST"; case HTTP_PUT: return "HTTP_PUT"; case HTTP_CONNECT: return "HTTP_CONNECT"; case HTTP_OPTIONS: return "HTTP_OPTIONS"; case HTTP_TRACE: return "HTTP_TRACE"; case HTTP_COPY: return "HTTP_COPY"; case HTTP_LOCK: return "HTTP_LOCK"; case HTTP_MKCOL: return "HTTP_MKCOL"; case HTTP_MOVE: return "HTTP_MOVE"; case HTTP_PROPFIND: return "HTTP_PROPFIND"; case HTTP_PROPPATCH: return "HTTP_PROPPATCH"; case HTTP_SEARCH: return "HTTP_SEARCH"; case HTTP_UNLOCK: return "HTTP_UNLOCK"; case HTTP_BIND: return "HTTP_BIND"; case HTTP_REBIND: return "HTTP_REBIND"; case HTTP_UNBIND: return "HTTP_UNBIND"; case HTTP_ACL: return "HTTP_ACL"; case HTTP_REPORT: return "HTTP_REPORT"; case HTTP_MKACTIVITY: return "HTTP_MKACTIVITY"; case HTTP_CHECKOUT: return "HTTP_CHECKOUT"; case HTTP_MERGE: return "HTTP_MERGE"; case HTTP_MSEARCH: return "HTTP_MSEARCH"; case HTTP_NOTIFY: return "HTTP_NOTIFY"; case HTTP_SUBSCRIBE: return "HTTP_SUBSCRIBE"; case HTTP_UNSUBSCRIBE: return "HTTP_UNSUBSCRIBE"; case HTTP_PATCH: return "HTTP_PATCH"; case HTTP_PURGE: return "HTTP_PURGE"; case HTTP_MKCALENDAR: return "HTTP_MKCALENDAR"; case HTTP_LINK: return "HTTP_LINK"; case HTTP_UNLINK: return "HTTP_UNLINK"; default: return "HTTP_UNKOWN"; } } string MegaHTTPServer::getHTTPErrorString(int errorcode) { switch (errorcode) { case 200: return "OK"; case 201: return "Created"; case 204: return "No Content"; case 403: return "Forbidden"; case 404: return "Not Found"; case 409: return "Conflict"; case 412: return "Precondition Failed"; case 423: return "Locked"; case 500: return "Internal Server Error"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 507: return "Insufficient Storage"; case 508: return "Loop Detected"; default: return "Unknown Error"; } } void MegaHTTPServer::returnHttpCodeBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e, bool synchronous) { int reqError = e->getErrorCode(); int httpreturncode = 500; switch(reqError) { case API_EACCESS: httpreturncode = 403; break; case API_EOVERQUOTA: case API_EGOINGOVERQUOTA: httpreturncode = 507; break; case API_EAGAIN: case API_ERATELIMIT: case API_ETEMPUNAVAIL: httpreturncode = 503; break; case API_ECIRCULAR: httpreturncode = 508; break; default: httpreturncode = 500; break; } LOG_debug << httpctx->getLogName() << "HTTP petition failed. request error = " << reqError << " HTTP status to return = " << httpreturncode; string errorMessage = e->getErrorString(reqError); return returnHttpCode(httpctx, httpreturncode, errorMessage, synchronous); } void MegaHTTPServer::returnHttpCode(MegaHTTPContext* httpctx, int errorCode, string errorMessage, bool synchronous) { std::ostringstream response; response << "HTTP/1.1 " << errorCode << " " << (errorMessage.size() ? errorMessage : getHTTPErrorString(errorCode)) << "\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = errorCode; string resstr = response.str(); if (synchronous) { sendHeaders(httpctx, &resstr); } else { uv_mutex_lock(&httpctx->mutex_responses); httpctx->responses.push_back(resstr); uv_mutex_unlock(&httpctx->mutex_responses); uv_async_send(&httpctx->asynchandle); } } void MegaHTTPServer::returnHttpCodeAsyncBasedOnRequestError(MegaHTTPContext* httpctx, MegaError *e) { return returnHttpCodeBasedOnRequestError(httpctx, e, false); } void MegaHTTPServer::returnHttpCodeAsync(MegaHTTPContext* httpctx, int errorCode, string errorMessage) { return returnHttpCode(httpctx, errorCode, errorMessage, false); } int MegaHTTPServer::onMessageComplete(http_parser *parser) { MegaNode *node = NULL; std::ostringstream response; MegaHTTPContext* httpctx = (MegaHTTPContext*) parser->data; LOG_debug << httpctx->getLogName() << "Message complete"; httpctx->bytesWritten = 0; httpctx->size = 0; httpctx->streamingBuffer.setMaxBufferSize( static_cast(httpctx->server->getMaxBufferSize())); httpctx->streamingBuffer.setMaxOutputSize( static_cast(httpctx->server->getMaxOutputSize())); MegaHTTPServer* httpserver = dynamic_cast(httpctx->server); switch (parser->method) { case HTTP_GET: case HTTP_POST: case HTTP_HEAD: case HTTP_OPTIONS: case HTTP_MOVE: case HTTP_PUT: case HTTP_DELETE: case HTTP_MKCOL: case HTTP_COPY: case HTTP_LOCK: case HTTP_UNLOCK: case HTTP_PROPPATCH: case HTTP_PROPFIND: LOG_debug << httpctx->getLogName() << "Request method: " << getHTTPMethodName(parser->method); break; default: LOG_debug << httpctx->getLogName() << "Method not allowed: " << getHTTPMethodName(parser->method); response << "HTTP/1.1 405 Method not allowed\r\n" "Connection: close\r\n" "\r\n"; httpctx->resultCode = 405; string resstr = response.str(); sendHeaders(httpctx, &resstr); return 0; } if (httpctx->path == "/favicon.ico") { LOG_debug << httpctx->getLogName() << "Favicon requested"; response << "HTTP/1.1 301 Moved Permanently\r\n" "Location: "; response << MegaClient::getMegaURL(); response << "/favicon.ico\r\n" "Connection: close\r\n" "\r\n"; httpctx->resultCode = API_OK; string resstr = response.str(); sendHeaders(httpctx, &resstr); return 0; } if (httpctx->path == "/") { node = httpctx->megaApi->getRootNode(); char *base64Handle = node->getBase64Handle(); httpctx->nodehandle = base64Handle; delete [] base64Handle; httpctx->nodename = node->getName(); } else if (httpctx->nodehandle.size()) { node = httpctx->megaApi->getNodeByHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str())); } if (!httpctx->nodehandle.size()) { response << "HTTP/1.1 404 Not Found\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 404; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; return 0; } handle h = MegaApi::base64ToHandle(httpctx->nodehandle.c_str()); if (!httpctx->server->isHandleAllowed(h)) { LOG_debug << httpctx->getLogName() << "Forbidden due to the restricted mode"; response << "HTTP/1.1 403 Forbidden\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 403; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; return 0; } if (parser->method == HTTP_OPTIONS) { LOG_debug << httpctx->getLogName() << "Returning HTTP_OPTIONS for a " << (httpserver->isHandleWebDavAllowed(h) ? "" : "non ") << "WEBDAV URI"; response << "HTTP/1.1 200 OK\r\n"; if (httpserver->isHandleWebDavAllowed(h)) { response << "Allow: GET, POST, HEAD, OPTIONS, PROPFIND, MOVE, PUT, DELETE, MKCOL, COPY, LOCK, UNLOCK, PROPPATCH\r\n" "dav: 1, 2 \r\n"; // 2 requires LOCK to be fully functional } else { response << "Allow: GET,POST,HEAD,OPTIONS\r\n"; } response << "content-length: 0\r\n" "Connection: close\r\n" "\r\n"; httpctx->resultCode = API_OK; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; return 0; } //if webdav method, check is handle is a valid webdav if ((parser->method != HTTP_GET) && (parser->method != HTTP_POST) && (parser->method != HTTP_PUT) && (parser->method != HTTP_HEAD) && !httpserver->isHandleWebDavAllowed(h)) { LOG_debug << httpctx->getLogName() << "Forbidden due to not webdav allowed"; returnHttpCode(httpctx, 405); delete node; return 0; } if (!node) { if (!httpctx->nodehandle.size() || !httpctx->nodekey.size()) { LOG_warn << httpctx->getLogName() << "URL not found: " << httpctx->path; response << "HTTP/1.1 404 Not Found\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 404; string resstr = response.str(); sendHeaders(httpctx, &resstr); return 0; } else if (httpctx->nodesize >= 0) { LOG_debug << httpctx->getLogName() << "Getting foreign node"; node = httpctx->megaApi->createForeignFileNode( h, httpctx->nodekey.c_str(), httpctx->nodename.c_str(), httpctx->nodesize, -1, nullptr, UNDEF, httpctx->nodeprivauth.c_str(), httpctx->nodepubauth.c_str(), httpctx->nodechatauth.c_str()); } else { handle httpNodeHandle = MegaApi::base64ToHandle(httpctx->nodehandle.c_str()); string link = MegaClient::publicLinkURL(httpctx->megaApi->getMegaClient()->mNewLinkFormat, TypeOfLink::FILE, httpNodeHandle, httpctx->nodekey.c_str()); LOG_debug << httpctx->getLogName() << "Getting public link: " << link; httpctx->megaApi->getPublicNode(link.c_str(), httpctx); httpctx->transfer.reset(new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD)); httpctx->transfer->setPath(httpctx->path.c_str()); httpctx->transfer->setFileName(httpctx->nodename.c_str()); httpctx->transfer->setNodeHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str())); httpctx->transfer->setStartTime(Waiter::ds); return 0; } } if (node && httpctx->nodename != node->getName()) { if (parser->method == HTTP_PROPFIND) { response << "HTTP/1.1 404 Not Found\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 404; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; return 0; } else { //Subtitles support bool subtitles = false; if (httpserver->isSubtitlesSupportEnabled()) { string originalname = node->getName(); string::size_type dotpos = originalname.find_last_of('.'); if (dotpos != string::npos) { originalname.resize(dotpos); } if (dotpos == httpctx->nodename.find_last_of('.') && !memcmp(originalname.data(), httpctx->nodename.data(), originalname.size())) { LOG_debug << httpctx->getLogName() << "Possible subtitles file"; MegaNode *parent = httpctx->megaApi->getParentNode(node); if (parent) { MegaNode *child = httpctx->megaApi->getChildNode(parent, httpctx->nodename.c_str()); if (child) { LOG_debug << httpctx->getLogName() << "Matching file found: " << httpctx->nodename << " - " << node->getName(); subtitles = true; delete node; node = child; } delete parent; } } } if (!subtitles) { LOG_warn << httpctx->getLogName() << "Invalid name: " << httpctx->nodename << " - " << node->getName(); response << "HTTP/1.1 404 Not Found\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 404; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; return 0; } } } MegaNode *baseNode = NULL; if (httpctx->subpathrelative.size()) { string subnodepath = httpctx->subpathrelative; //remove trailing "/" size_t seppos = subnodepath.find_last_of("/"); while ( (seppos != string::npos) && ((seppos + 1) == subnodepath.size()) ) { subnodepath = subnodepath.substr(0,seppos); seppos = subnodepath.find_last_of("/"); } MegaNode *subnode = httpctx->megaApi->getNodeByPath(subnodepath.c_str(), node); if (parser->method != HTTP_PUT && parser->method != HTTP_MKCOL && !subnode) { returnHttpCode(httpctx, 404); delete node; return 0; } else { baseNode = node; node = subnode; } } if (parser->method == HTTP_PROPFIND) { string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://" + httpctx->host + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/"; string resstr = getWebDavPropFindResponseForNode(baseURL, httpctx->subpathrelative, node, httpctx); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } else if (parser->method == HTTP_UNLOCK) { // let's create a minimum unlock compliant response returnHttpCode(httpctx, 204); delete node; delete baseNode; return 0; } else if (parser->method == HTTP_PROPPATCH) { std::ostringstream web; // Typicall Body received: //TODO: actualy update creation and mod times? does not seem to be required (it seems PUT updates them ... with what time: file or PUT time?) // // // // // Tue, 20 Feb 2018 18:00:20 GMT // Tue, 20 Feb 2018 18:00:21 GMT // Tue, 20 Feb 2018 18:00:21 GMT // 00000020 // // // web << "\r\n" "\r\n" "\r\n" //"" << urlelement << "\r\n" //this should come from the input but seems to be not required! "\r\n" "HTTP/1.1 200 OK\r\n" "\r\n" // Here we might want to include the updated properties. //we might want to to do so with original namespace?(not sure if that makes sense). e.g: // "\r\n" "\r\n" "\r\n" "\r\n" "\r\n\r\n"; string sweb = web.str(); response << "HTTP/1.1 207 Multi-Status\r\n" "Content-Type: application/xml; charset=\"utf-8\" \r\n" "Content-Length: " << sweb.size() << "\r\n\r\n"; response << sweb; httpctx->resultCode = 207; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } else if (parser->method == HTTP_LOCK) { std::ostringstream web; // let's create a minimum lock compliant response. we should actually provide locking functionality based on URL web << "\r\n" "\r\n" "\r\n" "\r\n" "\r\n" "\r\n" // "infinity\r\n" // read from req? "\r\n" // "" << owner << "\r\n" // should be read from req body "\r\n" // "Second-604800\r\n" "\r\n" //"urn:uuid:e71d4fae-5dec-22d6-fea5-00a0c91e6be4\r\n" //An unique identifier is required "urn:uuid:this-is-a-fake-lock\r\n" //An unique identifier is required "\r\n" "\r\n" // "" << urlelement << "\r\n" // should be read from req body "\r\n" "\r\n" "\r\n" "\r\n\r\n"; string sweb = web.str(); response << "HTTP/1.1 200 OK\r\n" "Lock-Token: \r\n" "Content-Type: application/xml; charset=\"utf-8\" \r\n" "Content-Length: " << sweb.size() << "\r\n\r\n"; response << sweb; httpctx->resultCode = 200; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } else if (parser->method == HTTP_DELETE) { if (!node) { returnHttpCode(httpctx, 404); delete node; delete baseNode; return 0; } httpctx->megaApi->remove(node, false, httpctx); delete node; delete baseNode; return 0; } else if (parser->method == HTTP_MKCOL) { // 201 (Created) The collection was created. // 401 (Access Denied) Resource requires authorization or authorization was denied. // 403 (Forbidden) The server does not allow collections to be created at the specified location, or the parent collection of the specified request URI exists but cannot accept members. // 405 (Method Not Allowed) The MKCOL method can only be performed on a deleted or non-existent resource. // 409 (Conflict) A resource cannot be created at the destination URI until one or more intermediate collections are created. // 415 (Unsupported Media Type) The request type of the body is not supported by the server. // 507 (Insufficient Storage) The destination resource does not have sufficient storage space. if (node) { returnHttpCode(httpctx, 405); delete node; delete baseNode; return 0; } MegaNode *newParentNode = NULL; string newname; string dest = httpctx->subpathrelative; size_t seppos = dest.find_last_of("/"); while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) ) { dest = dest.substr(0,seppos); seppos = dest.find_last_of("/"); } if (seppos == string::npos) { if (baseNode) newParentNode = baseNode->copy(); else if (node) newParentNode = node->copy(); else newParentNode = nullptr; newname = dest; } else { if ((seppos + 1) < dest.size()) { newname = dest.substr(seppos + 1); } string newparentpath = dest.substr(0, seppos); newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(),baseNode?baseNode:node); } if (!newParentNode) { returnHttpCode(httpctx, 409); delete node; delete baseNode; return 0; } httpctx->megaApi->createFolder(newname.c_str(), newParentNode, httpctx); delete newParentNode; delete node; delete baseNode; return 0; } else if (parser->method == HTTP_COPY) { // 201 (Created) The resource was successfully copied. // 204 (No Content) The source resource was successfully copied to a pre-existing destination resource. // 403 (Forbidden) The source URI and the destination URI are the same. // 409 (Conflict) A resource cannot be created at the destination URI until one or more intermediate collections are created. // 412 (Precondition Failed) Either the Overwrite header is "F" and the state of the destination resource is not null, or the method was used in a Depth: 0 transaction. // 423 (Locked) The destination resource is locked. // 502 (Bad Gateway) The COPY destination is located on a different server, which refuses to accept the resource. // 507 (Insufficient Storage) The destination resource does not have sufficient storage space. if (!node) { returnHttpCode(httpctx, 404); delete node; delete baseNode; return 0; } MegaNode *newParentNode = NULL; string newname; string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://" + httpctx->host + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/"; string dest; URLCodec::unescape(&httpctx->destination, &dest); size_t posBase = dest.find(baseURL); if (posBase != 0) // Notice that if 2 WEBDAV locations are enabled we won't be able to copy between the 2 { returnHttpCode(httpctx, 502); // The destination URI is located elsewhere delete node; delete baseNode; return 0; } dest = dest.substr(baseURL.size()); MegaNode *destNode = httpctx->megaApi->getNodeByPath(dest.c_str(), baseNode ? baseNode : node); if (destNode) { if (node->getHandle() == destNode->getHandle()) { returnHttpCode(httpctx, 403); delete node; delete baseNode; delete destNode; return 0; } else { //overwrite? if (httpctx->overwrite) { newParentNode = httpctx->megaApi->getNodeByHandle(destNode->getParentHandle()); } else { returnHttpCode(httpctx, 412); delete node; delete baseNode; delete destNode; return 0; } } } if (!newParentNode) { size_t seppos = dest.find_last_of("/"); while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) ) { dest = dest.substr(0,seppos); seppos = dest.find_last_of("/"); } if (seppos == string::npos) { newParentNode = baseNode?baseNode->copy():node->copy(); newname = dest; } else { if ((seppos + 1) < dest.size()) { newname = dest.substr(seppos + 1); } string newparentpath = dest.substr(0, seppos); newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(),baseNode?baseNode:node); } } if (!newParentNode) { returnHttpCode(httpctx, 409); delete node; delete baseNode; return 0; } if (newname.size()) { httpctx->megaApi->copyNode(node, newParentNode, newname.c_str(), httpctx); } else { httpctx->megaApi->copyNode(node, newParentNode, httpctx); } delete node; delete baseNode; delete newParentNode; return 0; } else if (parser->method == HTTP_PUT) { if (node && !httpctx->overwrite) { returnHttpCode(httpctx, 412); delete node; delete baseNode; return 0; } else { MegaNode *newParentNode = NULL; string newname; string dest = httpctx->subpathrelative; size_t seppos = dest.find_last_of("/"); while ( (seppos != string::npos) && ((seppos + 1) == dest.size()) ) { dest = dest.substr(0,seppos); seppos = dest.find_last_of("/"); } if (seppos == string::npos) { newParentNode = baseNode ? baseNode->copy() : node->copy(); newname = dest; } else { if ((seppos + 1) < dest.size()) { newname = dest.substr(seppos + 1); } string newparentpath = dest.substr(0, seppos); newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(), baseNode ? baseNode : node); } if (!newParentNode) { returnHttpCode(httpctx, 409); delete node; delete baseNode; return 0; } if (!httpctx->tmpFileAccess) //put with no body contents { httpctx->tmpFileName=httpctx->server->basePath; httpctx->tmpFileName.append("httputfile"); httpctx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false)); string ext; if (httpctx->server->fsAccess->getextension(LocalPath::fromAbsolutePath(httpctx->path), ext)) { httpctx->tmpFileName.append(ext); } httpctx->tmpFileAccess = httpctx->server->fsAccess->newfileaccess(); auto tmpFileNamePath = LocalPath::fromAbsolutePath(httpctx->tmpFileName); httpctx->server->fsAccess->unlinklocal(tmpFileNamePath); if (!httpctx->tmpFileAccess->fopen(tmpFileNamePath, OPEN_WRONLY, FSLogging::logOnError)) { returnHttpCode(httpctx, 500); delete node; delete baseNode; delete newParentNode; return 0; } } FileSystemType fsType = httpctx->server->fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(httpctx->tmpFileName)); MegaApiImpl::MegaUploadOptionsPrivate uploadOptions; uploadOptions.mPublicOptions.fileName = newname; uploadOptions.mPublicOptions.mtime = -1; uploadOptions.mIsBackup = true; uploadOptions.mFsType = fsType; httpctx->megaApi->startUpload(httpctx->tmpFileName, newParentNode, CancelToken(), uploadOptions, httpctx); delete node; delete baseNode; delete newParentNode; return 0; } } else if (parser->method == HTTP_MOVE) { // 201 (Created) The resource was moved successfully and a new resource was created at the specified destination URI. // 204 (No Content) The resource was moved successfully to a pre-existing destination URI. // 403 (Forbidden) The source URI and the destination URI are the same. // 409 (Conflict) A resource cannot be created at the destination URI until one or more intermediate collections are created. // 412 (Precondition Failed) Either the Overwrite header is "F" and the state of the destination resource is not null, or the method was used in a Depth: 0 transaction. // 423 (Locked) The destination resource is locked. // 502 (Bad Gateway) The destination URI is located on a different server, which refuses to accept the resource. if (!node) { returnHttpCode(httpctx, 404); delete node; delete baseNode; return 0; } string baseURL = string("http") + (httpctx->server->useTLS ? "s" : "") + "://" + httpctx->host + "/" + httpctx->nodehandle + "/" + httpctx->nodename + "/"; string dest; URLCodec::unescape(&httpctx->destination, &dest); size_t posBase = dest.find(baseURL); if (posBase != 0) // Notice that if 2 WEBDAV locations are enabled we won't be able to copy between the 2 { returnHttpCode(httpctx, 502); // The destination URI is located elsewhere delete node; delete baseNode; return 0; } dest = dest.substr(baseURL.size()); MegaNode *destNode = httpctx->megaApi->getNodeByPath(dest.c_str(), baseNode ? baseNode : node); if (destNode) { if (node->getHandle() == destNode->getHandle()) { returnHttpCode(httpctx, 403); delete node; delete baseNode; delete destNode; return 0; } else { //overwrite? if (httpctx->overwrite) { httpctx->newParentNode = destNode->getParentHandle(); httpctx->newname = destNode->getName(); httpctx->nodeToMove = node->getHandle(); httpctx->megaApi->remove(destNode, false, httpctx); delete node; delete baseNode; delete destNode; return 0; } else { returnHttpCode(httpctx, 412); delete node; delete baseNode; delete destNode; return 0; } } } else { MegaNode *newParentNode = NULL; size_t seppos = dest.find_last_of("/"); httpctx->newname = dest; if (seppos == string::npos) { newParentNode = baseNode ? baseNode->copy() : node->copy(); } else { if ((seppos + 1) < httpctx->newname.size()) { httpctx->newname = httpctx->newname.substr(seppos + 1); } string newparentpath = dest.substr(0, seppos); newParentNode = httpctx->megaApi->getNodeByPath(newparentpath.c_str(), baseNode ? baseNode : node); } if (!newParentNode) { returnHttpCode(httpctx, 409); delete node; delete baseNode; return 0; } if (newParentNode->getHandle() == node->getHandle()) { LOG_warn << httpctx->getLogName() << "HTTP_MOVE trying to mov a node into itself"; returnHttpCode(httpctx, 500); delete node; delete baseNode; delete newParentNode; return 0; } if (newParentNode->getHandle() != node->getParentHandle()) { httpctx->megaApi->moveNode(node, newParentNode, httpctx); } else { httpctx->megaApi->renameNode(node, httpctx->newname.c_str(), httpctx); } delete newParentNode; } delete node; delete baseNode; return 0; } else //GET/POST/HEAD { if (node->isFolder()) { if (!httpserver->isFolderServerEnabled()) { response << "HTTP/1.1 403 Forbidden\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 403; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } string resstr = getResponseForNode(node, httpctx); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } //File node if (!httpserver->isFileServerEnabled()) { response << "HTTP/1.1 403 Forbidden\r\n" "Connection: close\r\n" << "\r\n"; httpctx->resultCode = 403; string resstr = response.str(); sendHeaders(httpctx, &resstr); delete node; delete baseNode; return 0; } httpctx->transfer.reset(new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD)); httpctx->transfer->setPath(httpctx->path.c_str()); if (httpctx->nodename.size()) { httpctx->transfer->setFileName(httpctx->nodename.c_str()); } if (httpctx->nodehandle.size()) { httpctx->transfer->setNodeHandle(MegaApi::base64ToHandle(httpctx->nodehandle.c_str())); } httpctx->transfer->setStartTime(Waiter::ds); delete httpctx->node; httpctx->node = node; streamNode(httpctx); } delete baseNode; return 0; } int MegaHTTPServer::streamNode(MegaHTTPContext *httpctx) { std::ostringstream response; MegaNode *node = httpctx->node; string name; const char *extension = NULL; const char *nodeName = httpctx->node->getName(); if (nodeName) { name = nodeName; } string::size_type dotindex = name.find_last_of('.'); if (dotindex != string::npos) { extension = name.c_str() + dotindex; } char *mimeType = MegaApi::getMimeType(extension); if (!mimeType) { mimeType = MegaApi::strdup("application/octet-stream"); } m_off_t totalSize = node->getSize(); m_off_t start = 0; m_off_t end = totalSize - 1; if (httpctx->rangeStart >= 0) { start = httpctx->rangeStart; } httpctx->rangeStart = start; if (httpctx->rangeEnd >= 0) { end = std::min(totalSize - 1, httpctx->rangeEnd); } httpctx->rangeEnd = end + 1; bool rangeRequested = (httpctx->rangeEnd - httpctx->rangeStart) != totalSize; m_off_t len = end - start + 1; if (totalSize && (start < 0 || start >= totalSize || end < 0 || end >= totalSize || len <= 0 || len > totalSize) ) { response << "HTTP/1.1 416 Requested Range Not Satisfiable\r\n" << "Content-Type: " << mimeType << "\r\n" << "Connection: close\r\n" << "Access-Control-Allow-Origin: *\r\n" << "Accept-Ranges: bytes\r\n" << "Content-Range: bytes 0-0/" << totalSize << "\r\n" << "\r\n"; delete [] mimeType; httpctx->resultCode = 416; string resstr = response.str(); sendHeaders(httpctx, &resstr); return 0; } if (rangeRequested) { response << "HTTP/1.1 206 Partial Content\r\n"; response << "Content-Range: bytes " << start << "-" << end << "/" << totalSize << "\r\n"; } else { response << "HTTP/1.1 200 OK\r\n"; } response << "Content-Type: " << mimeType << "\r\n" << "Connection: close\r\n" << "Content-Length: " << len << "\r\n" << "Access-Control-Allow-Origin: *\r\n" << "Accept-Ranges: bytes\r\n" << "\r\n"; delete [] mimeType; httpctx->pause = false; httpctx->lastBuffer = NULL; httpctx->lastBufferLen = 0; if (httpctx->transfer) { httpctx->transfer->setStartPos(start); httpctx->transfer->setEndPos(end); } httpctx->streamingBuffer.setFileSize(totalSize); httpctx->streamingBuffer.setDuration(httpctx->node->getDuration()); string resstr = response.str(); if (httpctx->parser.method != HTTP_HEAD) { // Body data can be written to streamingBuffer before the header is sent httpctx->streamingBuffer.init(static_cast(len) + resstr.size()); httpctx->server->setMaxBufferSize( static_cast(httpctx->streamingBuffer.getMaxBufferSize())); httpctx->server->setMaxOutputSize( static_cast(httpctx->streamingBuffer.getMaxOutputSize())); httpctx->size = len; } sendHeaders(httpctx, &resstr); if (httpctx->parser.method == HTTP_HEAD) { return 0; } LOG_debug << httpctx->getLogName() << "Requesting range. From " << start << " size " << len; httpctx->rangeWritten = 0; if (start || len) { httpctx->streamingBuffer.reset(!httpctx->lastBufferLen, resstr.size()); httpctx->megaApi->startStreaming(node, start, len, httpctx); } else { MegaHTTPServer *httpserver = ((MegaHTTPServer *)httpctx->server); LOG_debug << httpctx->getLogName() << "Skipping startStreaming call since empty file"; httpserver->processWriteFinished(httpctx, 0); } return 0; } void MegaHTTPServer::sendHeaders(MegaHTTPContext *httpctx, string *headers) { LOG_debug << httpctx->getLogName() << "Response headers: " << *headers; httpctx->streamingBuffer.append(headers->data(), headers->size()); uv_buf_t resbuf = httpctx->streamingBuffer.nextBuffer(); httpctx->size += headers->size(); httpctx->lastBuffer = resbuf.base; httpctx->lastBufferLen = resbuf.len; if (httpctx->transfer) { httpctx->transfer->setTotalBytes(httpctx->size); httpctx->megaApi->fireOnStreamingStart(httpctx->transfer.get()); } #ifdef ENABLE_EVT_TLS if (httpctx->server->useTLS) { assert (resbuf.len); int err = evt_tls_write(httpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls); if (err <= 0) { LOG_warn << httpctx->getLogName() << "Finishing due to an error sending the response: " << err; closeConnection(httpctx); } } else { #endif uv_write_t *req = new uv_write_t(); req->data = httpctx; if (int err = uv_write(req, (uv_stream_t*)&httpctx->tcphandle, &resbuf, 1, onWriteFinished)) { delete req; LOG_warn << httpctx->getLogName() << "Finishing due to an error sending the response: " << err; closeTCPConnection(httpctx); } #ifdef ENABLE_EVT_TLS } #endif } void MegaHTTPServer::processAsyncEvent(MegaTCPContext* tcpctx) { MegaHTTPContext* httpctx = dynamic_cast(tcpctx); if (httpctx->finished) { LOG_debug << httpctx->getLogName() << "HTTP link closed, ignoring async event"; return; } if (httpctx->failed) { LOG_warn << httpctx->getLogName() << "Streaming transfer failed. Closing connection."; closeConnection(httpctx); return; } uv_mutex_lock(&httpctx->mutex_responses); while (httpctx->responses.size()) { sendHeaders(httpctx,&httpctx->responses.front()); httpctx->responses.pop_front(); } uv_mutex_unlock(&httpctx->mutex_responses); if (httpctx->nodereceived) { httpctx->nodereceived = false; if (!httpctx->node || httpctx->nodename != httpctx->node->getName()) { if (!httpctx->node) { LOG_warn << httpctx->getLogName() << "Public link not found"; } else { LOG_warn << httpctx->getLogName() << "Invalid name for public link"; } httpctx->resultCode = 404; string resstr = "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n"; sendHeaders(httpctx, &resstr); return; } streamNode(httpctx); return; } sendNextBytes(httpctx); } void MegaHTTPServer::sendNextBytes(MegaHTTPContext *httpctx) { if (httpctx->finished) { LOG_debug << httpctx->getLogName() << "HTTP link closed, aborting write"; return; } if (httpctx->lastBuffer) { LOG_verbose << httpctx->getLogName() << "[Streaming] Skipping write due to another ongoing write"; return; } uv_mutex_lock(&httpctx->mutex); if (httpctx->lastBufferLen) { httpctx->streamingBuffer.freeData(httpctx->lastBufferLen); httpctx->lastBufferLen = 0; } if (httpctx->tcphandle.write_queue_size > httpctx->streamingBuffer.availableCapacity() / 8) { LOG_warn << httpctx->getLogName() << "[Streaming] Skipping write. Too much queued data. " << httpctx->streamingBuffer.bufferStatus(); uv_mutex_unlock(&httpctx->mutex); return; } uv_buf_t resbuf = httpctx->streamingBuffer.nextBuffer(); uv_mutex_unlock(&httpctx->mutex); if (!resbuf.len) { LOG_debug << httpctx->getLogName() << "[Streaming] Skipping write. No data available. " << httpctx->streamingBuffer.bufferStatus(); return; } LOG_verbose << httpctx->getLogName() << "Writing " << resbuf.len << " bytes"; httpctx->rangeWritten += resbuf.len; httpctx->lastBuffer = resbuf.base; httpctx->lastBufferLen = resbuf.len; #ifdef ENABLE_EVT_TLS if (httpctx->server->useTLS) { //notice this, contrary to !useTLS is synchronous int err = evt_tls_write(httpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls); if (err <= 0) { LOG_warn << httpctx->getLogName() << "[Streaming] Finishing due to an error sending the response: " << err; evt_tls_close(httpctx->evt_tls, on_evt_tls_close); } } else { #endif uv_write_t *req = new uv_write_t(); req->data = httpctx; if (uv_write(req, (uv_stream_t*)&httpctx->tcphandle, &resbuf, 1, onWriteFinished)) { delete req; httpctx->finished = true; if (!uv_is_closing((uv_handle_t*)&httpctx->tcphandle)) { uv_close((uv_handle_t*)&httpctx->tcphandle, onClose); } } #ifdef ENABLE_EVT_TLS } #endif } std::atomic_uint32_t MegaHTTPContext::nextId{0u}; MegaHTTPContext::MegaHTTPContext(): contextId{nextId++}, logname{"(HttpCtx#" + std::to_string(contextId) + ") "}, streamingBuffer(logname) { rangeStart = -1; rangeEnd = -1; rangeWritten = -1; range = false; failed = false; pause = false; nodereceived = false; resultCode = API_EINTERNAL; node = NULL; nodesize = -1; messageBody = NULL; messageBodySize = 0; tmpFileAccess = NULL; newParentNode = UNDEF; nodeToMove = UNDEF; depth = -1; overwrite = true; //GVFS-DAV via command line does not include this header (assumed true) lastBuffer = NULL; lastBufferLen = 0; // Mutex to protect the data buffer uv_mutex_init(&mutex_responses); } MegaHTTPContext::~MegaHTTPContext() { delete node; if (tmpFileName.size()) { LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName); server->fsAccess->unlinklocal(localPath); } delete [] messageBody; uv_mutex_destroy(&mutex_responses); } void MegaHTTPContext::onTransferStart(MegaApi*, MegaTransfer* httpTransfer) { if (transfer) { transfer->setTag(httpTransfer->getTag()); } } bool MegaHTTPContext::onTransferData(MegaApi*, MegaTransfer* httpTransfer, char* buffer, size_t dataSize) { LOG_verbose << logname << "Streaming data received: " << httpTransfer->getTransferredBytes() << " Size: " << dataSize << " Queued: " << this->tcphandle.write_queue_size << " " << streamingBuffer.bufferStatus(); if (finished) { LOG_info << logname << "Removing streaming transfer after " << httpTransfer->getTransferredBytes() << " bytes"; return false; } // append the data to the buffer uv_mutex_lock(&mutex); long long remaining = static_cast(dataSize) + (httpTransfer->getTotalBytes() - httpTransfer->getTransferredBytes()); long long availableSpace = static_cast(streamingBuffer.availableSpace()); if ((remaining > availableSpace) && ((availableSpace - static_cast(dataSize)) < static_cast(DirectReadSlot::MAX_DELIVERY_CHUNK))) { LOG_debug << logname << "[Streaming] Buffer full: Pausing streaming. " << streamingBuffer.bufferStatus(); pause = true; } streamingBuffer.append(buffer, dataSize); uv_mutex_unlock(&mutex); // notify the HTTP server uv_async_send(&asynchandle); return !pause; } void MegaHTTPContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e) { if (finished) { LOG_debug << logname << "HTTP link closed, ignoring the result of the transfer"; return; } MegaHTTPServer* httpserver = dynamic_cast(server); int ecode = e->getErrorCode(); if (parser.method == HTTP_PUT) { if (ecode == API_OK) { httpserver->returnHttpCodeAsync(this, 201); //TODO actually if resource already existed this should be 200 } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } if (ecode != API_OK && ecode != API_EINCOMPLETE) { LOG_warn << logname << "Transfer failed with error code: " << ecode; failed = true; } uv_async_send(&asynchandle); } void MegaHTTPContext::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e) { if (finished) { LOG_debug << logname << "HTTP link closed, ignoring the result of the request"; return; } MegaHTTPServer* httpserver = dynamic_cast(server); if (request->getType() == MegaRequest::TYPE_MOVE) { if (e->getErrorCode() == MegaError::API_OK) { if (this->newname.size()) { MegaNode *nodetoRename = this->megaApi->getNodeByHandle(request->getNodeHandle()); if (!nodetoRename || !strcmp(nodetoRename->getName(), newname.c_str())) { httpserver->returnHttpCodeAsync(this, 204); } else { this->megaApi->renameNode(nodetoRename, newname.c_str(), this); } delete nodetoRename; } else { httpserver->returnHttpCodeAsync(this, 204); } } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_RENAME) { if (e->getErrorCode() == MegaError::API_OK ) { httpserver->returnHttpCodeAsync(this, 204); } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_REMOVE) { if (e->getErrorCode() == MegaError::API_OK) { MegaNode *n = this->megaApi->getNodeByHandle(nodeToMove); MegaNode *p = this->megaApi->getNodeByHandle(newParentNode); if (n && p) //delete + move { this->megaApi->moveNode(n, p, this); } else { httpserver->returnHttpCodeAsync(this, 204); // Standard success response } nodeToMove = UNDEF; newParentNode = UNDEF; delete n; delete p; } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_CREATE_FOLDER) { if (e->getErrorCode() == MegaError::API_OK) { httpserver->returnHttpCodeAsync(this, 201); } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_COPY) { if (e->getErrorCode() == MegaError::API_OK) { httpserver->returnHttpCodeAsync(this, 201); } else { httpserver->returnHttpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_GET_PUBLIC_NODE) { node = request->getPublicMegaNode(); nodereceived = true; } uv_async_send(&asynchandle); } ////////////////////////////// // MegaFTPServer specifics // ////////////////////////////// /** * Gets permissions string: e.g: 777 -> rwxrwxrwx * @param permissions permissions * @param permsString permission string buffer */ void MegaFTPServer::getPermissionsString(int permissions, char *permsString) { string ps = ""; for(int i = 0; i<3; i++) // user, group, others { int curperm = permissions%10; permissions = permissions / 10; bool read = (curperm >> 2) & 0x1; bool write = (curperm >> 1) & 0x1; bool exec = (curperm >> 0) & 0x1; char rwx[4]; snprintf(rwx, sizeof(rwx), "%c%c%c",read?'r':'-' ,write?'w':'-', exec?'x':'-'); rwx[3]='\0'; ps = rwx + ps; } strncat(permsString, ps.c_str(), ps.size() + 1); } //ftp_parser_settings MegaTCPServer::parsercfg; MegaFTPServer::MegaFTPServer(MegaApiImpl *megaApi, string basePath, int dataportBegin, int dataPortEnd, bool useTLS, string certificatepath, string keypath) : MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath) { nodeHandleToRename = UNDEF; this->pport = dataportBegin; this->dataportBegin = dataportBegin; this->dataPortEnd = dataPortEnd; crlfout = "\r\n"; } MegaFTPServer::~MegaFTPServer() { // if not stopped, the uv thread might want to access a pointer to this. // though this is done in the parent destructor, it could try to access it after vtable has been erased stop(); } MegaTCPContext* MegaFTPServer::initializeContext(uv_stream_t *server_handle) { MegaFTPContext* ftpctx = new MegaFTPContext(); // Set connection data MegaFTPServer* ftpServer = (MegaFTPServer*)(server_handle->data); ftpctx->server = ftpServer; ftpctx->megaApi = ftpServer->megaApi; ftpctx->tcphandle.data = ftpctx; ftpctx->asynchandle.data = ftpctx; return ftpctx; } void MegaFTPServer::processWriteFinished(MegaTCPContext*, int status) { LOG_verbose << "MegaFTPServer::processWriteFinished. status=" << status; } string MegaFTPServer::getListingLineFromNode(MegaNode *child, string nameToShow) { char perms[10]; memset(perms,0,10); //str_perm((statbuf.st_mode & ALLPERMS), perms); getPermissionsString(child->isFolder() ? 777 : 664, perms); char timebuff[80]; time_t rawtime = (child->isFolder()?child->getCreationTime():child->getModificationTime()); struct tm time; m_localtime(rawtime, &time); strftime(timebuff,80,"%b %d %H:%M",&time); char toprint[3000]; snprintf(toprint, sizeof(toprint), "%c%s %5d %4d %4d %8" PRId64 " %s %s", (child->isFolder())?'d':'-', perms, 1,//number of contents for folders 1000, //uid 1000, //gid (child->isFolder())?4:child->getSize(), timebuff, nameToShow.size()?nameToShow.c_str():child->getName()); return toprint; } string MegaFTPServer::getFTPErrorString(int errorcode, string argument) { switch (errorcode) { case 110: return "Restart marker reply."; case 120: return "Service ready in " + argument + " minutes."; case 125: return "Data connection already open; transfer starting."; case 150: return "File status okay; about to open data connection."; case 200: return "Command okay."; case 202: return "Command not implemented, superfluous at this site."; case 211: return "System status, or system help reply."; case 212: return "Directory status."; case 213: return "File status."; case 214: return "Help message."; case 215: return "NAME system type."; case 220: return "Service ready for new user."; case 221: return "Service closing control connection."; case 225: return "Data connection open; no transfer in progress."; case 226: return "Closing data connection. Requested file action successful."; case 227: return "Entering Passive Mode (h1,h2,h3,h4,p1,p2)."; case 230: return "User logged in, proceed."; case 250: return "Requested file action okay, completed."; case 257: return argument + " created."; case 331: return "User name okay, need password."; case 332: return "Need account for login."; case 350: return "Requested file action pending further information."; case 421: return "Service not available, closing control connection."; case 425: return "Can't open data connection."; case 426: return "Connection closed; transfer aborted."; case 450: return "Requested file action not taken. File unavailable (e.g., file busy)."; case 451: return "Requested action aborted: local error in processing."; case 452: return "Requested action not taken. Insufficient storage space in system."; case 500: return "Syntax error, command unrecognized."; case 501: return "Syntax error in parameters or arguments."; case 502: return "Command not implemented."; case 503: return "Bad sequence of commands."; case 504: return "Command not implemented for that parameter."; case 530: return "Not logged in."; case 532: return "Need account for storing files."; case 550: return "Requested action not taken. File unavailable (e.g., file not found, no access)."; case 551: return "Requested action aborted: page type unknown."; case 552: return "Requested file action aborted. Exceeded storage allocation."; // (for current directory or dataset). case 553: return "Requested action not taken. File name not allowed."; default: return "Unknown Error"; } } void MegaFTPServer::returnFtpCodeBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e) { int reqError = e->getErrorCode(); int ftpreturncode = 500; switch(reqError) { case API_OK: ftpreturncode = 300; break; case API_EACCESS: ftpreturncode = 550; //this might not be accurate break; case API_EOVERQUOTA: case API_EGOINGOVERQUOTA: ftpreturncode = 452; // 552? break; case API_EAGAIN: case API_ERATELIMIT: case API_ETEMPUNAVAIL: ftpreturncode = 120; break; case API_EREAD: ftpreturncode = 450; break; case API_ECIRCULAR: ftpreturncode = 508; break; default: ftpreturncode = 503; break; } LOG_debug << "FTP petition failed. request error = " << reqError << " FTP status to return = " << ftpreturncode; string errorMessage = e->getErrorString(reqError); return returnFtpCode(ftpctx, ftpreturncode, errorMessage); } void MegaFTPServer::returnFtpCode(MegaFTPContext* ftpctx, int errorCode, string errorMessage) { MegaFTPServer* ftpserver = dynamic_cast(ftpctx->server); std::ostringstream response; response << errorCode << " " << (errorMessage.size() ? errorMessage : getFTPErrorString(errorCode)) << ftpserver->crlfout; string resstr = response.str(); uv_mutex_lock(&ftpctx->mutex_responses); ftpctx->responses.push_back(resstr); uv_mutex_unlock(&ftpctx->mutex_responses); uv_async_send(&ftpctx->asynchandle); } void MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(MegaFTPContext* ftpctx, MegaError *e) { return returnFtpCodeBasedOnRequestError(ftpctx, e); } void MegaFTPServer::returnFtpCodeAsync(MegaFTPContext* ftpctx, int errorCode, string errorMessage) { return returnFtpCode(ftpctx, errorCode, errorMessage); } // you get the ownership MegaNode *MegaFTPServer::getBaseFolderNode(string path) { if (path.size() && path.at(0) == '/') { string rest = path.substr(1); size_t possep = rest.find('/'); handle h = megaApi->base64ToHandle(rest.substr(0,possep).c_str()); MegaNode *n = megaApi->getNodeByHandle(h); if (possep != string::npos && possep != (rest.size() - 1) ) { if (n) { if (rest.size() > (possep + 1)) { rest = rest.substr(possep + 1); if (rest == n->getName()) { return n; } if (rest.size() > strlen(n->getName()) && (rest.at(strlen(n->getName())) == '/' ) && (rest.find(n->getName()) == 0) ) { return n; } } delete n; } } else { return n; } } return NULL; } // you get the ownership MegaNode *MegaFTPServer::getNodeByFullFtpPath(string path) { if (path.size() && path.at(0) == '/') { string rest = path.substr(1); size_t possep = rest.find('/'); handle h = megaApi->base64ToHandle(rest.substr(0,possep).c_str()); MegaNode *n = megaApi->getNodeByHandle(h); if (possep != string::npos && possep != (rest.size() - 1) ) { if (n) { if (rest.size() > possep) { rest = rest.substr(possep + 1); if (rest == n->getName()) { return n; } if (rest.size() > strlen(n->getName()) && (rest.at(strlen(n->getName())) == '/' ) && (rest.find(n->getName()) == 0) ) { string relpath = rest.substr(strlen(n->getName())+1); MegaNode *toret = megaApi->getNodeByPath(relpath.c_str(), n); delete n; return toret; } } delete n; } } else { return n; } } return NULL; } MegaNode * MegaFTPServer::getNodeByFtpPath(MegaFTPContext* ftpctx, string path) { if (ftpctx->atroot && path.size() && path.at(0) != '/') { path= "/" + path; } else if (ftpctx->athandle && path.size() && path.at(0) != '/') { char *cshandle = ftpctx->megaApi->handleToBase64(ftpctx->cwd); string handle(cshandle); delete []cshandle; path= "/" + handle + "/" + path; } else if (path.size() && path.at(0) != '/') { path = ftpctx->cwdpath + "/" + path; path = shortenpath(path); } if (path.find("..") == 0) { string fullpath = ftpctx->cwdpath + "/" + path; size_t seppos = fullpath.find("/"); int count = 0; while (seppos != string::npos && fullpath.size() > (seppos +1) ) { string part = fullpath.substr(0,seppos); if (part.size() && part != "..") { count++; } if (part == "..") { count--; if (count < 2) { return NULL; // do not allow to escalate in the path } } fullpath = fullpath.substr(seppos+1); if (fullpath == ".." && count == 2) { return NULL; // do not allow to escalate in the path } seppos = fullpath.find("/"); } } if (path.size() && path.at(0) == '/') { MegaNode *baseFolderNode = getBaseFolderNode(path); if (!baseFolderNode) { return NULL; } if (!isHandleAllowed(baseFolderNode->getHandle()) ) { delete baseFolderNode; return NULL; } delete baseFolderNode; return getNodeByFullFtpPath(path); } else //it should only enter here if path == "" { MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd); if (!n) { return NULL; } MegaNode *toret = ftpctx->megaApi->getNodeByPath(path.c_str(), n); delete n; return toret; } } std::string MegaFTPServer::shortenpath(std::string path) { string orig = path; while ((path.size() > 1) && path.at(path.size() - 1) == '/') // remove trailing / { path = path.substr(0,path.size()-1); } list parts; size_t seppos = path.find("/"); while (seppos != string::npos && path.size() > (seppos +1) ) { string part = path.substr(0,seppos); if (part.size() && part != "..") { parts.push_back(part); } if (part == "..") { if (!parts.size()) { return "INVALIDPATH"; // FAILURE! } parts.pop_back(); } path = path.substr(seppos+1); if (path == "..") { if (!parts.size()) { return "INVALIDPATH"; // FAILURE! } parts.pop_back(); path = ""; } seppos = path.find("/"); } if (path.size() && path != "..") { parts.push_back(path); } string toret; if (!parts.size() && orig.size() && orig.at(0) == '/') { toret = "/"; } else { while (parts.size()) { toret.append("/"); toret.append(parts.front()); parts.pop_front(); } } return toret; } std::string MegaFTPServer::cdup(handle parentHandle, MegaFTPContext* ftpctx) { string response; MegaNode *newcwd = ftpctx->megaApi->getNodeByHandle(parentHandle); if (newcwd) { bool allowed = isHandleAllowed(newcwd->getHandle()) || isHandleAllowed(newcwd->getParentHandle()); MegaNode *pn = ftpctx->megaApi->getNodeByHandle(newcwd->getHandle()); while (!allowed && pn) { MegaNode *aux = pn; pn = ftpctx->megaApi->getNodeByHandle(pn->getParentHandle()); delete aux; if (pn) { allowed = isHandleAllowed(pn->getParentHandle()); } } delete pn; if (!allowed) { LOG_warn << "Ftp client trying to access not allowed path"; response = "550 Path not allowed"; } else if (newcwd->isFolder() && newcwd->getHandle() != UNDEF) { ftpctx->cwd = newcwd->getHandle(); ftpctx->cwdpath = ftpctx->cwdpath + "/.."; ftpctx->cwdpath = shortenpath(ftpctx->cwdpath); ftpctx->athandle = false; ftpctx->atroot = false; ptrdiff_t seps = std::count(ftpctx->cwdpath.begin(), ftpctx->cwdpath.end(), '/'); if (seps < 2) { ftpctx->cwdpath = string("/") + megaApi->handleToBase64(newcwd->getHandle()) + "/" + newcwd->getName(); } ftpctx->parentcwd = newcwd->getParentHandle(); response = "250 Directory successfully changed"; } else { response = "550 CDUP failed."; } delete newcwd; } else { response = "550 Not Found"; } return response; } std::string MegaFTPServer::cd(string newpath, MegaFTPContext* ftpctx) { string response; if (newpath == "/") { MegaNode *rootNode = megaApi->getRootNode(); if (rootNode) { ftpctx->cwd = rootNode->getHandle(); ftpctx->cwdpath = "/"; ftpctx->atroot = true; ftpctx->athandle = false; response = "250 Directory successfully changed"; delete rootNode; return response; } response = "550 CWD not Found."; return response; } MegaNode *newcwd = getNodeByFtpPath(ftpctx, newpath); if (!newcwd) { response = "550 CWD not Found."; return response; } ftpctx->cwd = newcwd->getHandle(); if (newpath.size() && newpath.at(0) == '/') { ftpctx->cwdpath = newpath; } else // relative paths! { ftpctx->cwdpath = (ftpctx->cwdpath == "/"?"":ftpctx->cwdpath) + "/" + newpath; } ftpctx->cwdpath = shortenpath(ftpctx->cwdpath); ftpctx->athandle = false; string handlepath = "/"; char *cshandle = megaApi->handleToBase64(newcwd->getHandle()); string shandle(cshandle); delete []cshandle; handlepath.append(shandle); if (ftpctx->cwdpath == handlepath || ftpctx->cwdpath == shandle || ftpctx->cwdpath == (handlepath +"/") ) { ftpctx->cwdpath = handlepath; ftpctx->athandle = true; } ftpctx->atroot = false; ftpctx->parentcwd = newcwd->getParentHandle(); if (ftpctx->athandle || newcwd->isFolder()) { response = "250 Directory successfully changed"; } else { response = "550 CWD failed."; //chrome requires this } delete newcwd; return response; } void MegaFTPServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf) { MegaFTPContext* ftpctx = dynamic_cast(tcpctx); ssize_t parsed = -1; ((void)(parsed)); string petition; std::string command; string response; bool delayresponse = false; if (!nread) { LOG_debug << " Discarding processReceivedData read = " << nread; return; } uv_mutex_lock(&tcpctx->mutex); if (nread >= 0) { bool failed = false; const char *separators = " "; const char *crlf = "\r\n"; petition = string(buf->base, static_cast(nread)); LOG_verbose << "FTP Server received: " << petition << " at port = " << port; size_t psep = petition.find_first_of(separators); size_t psepend = petition.find(crlf); if (psepend != petition.size()-strlen(crlf)) { parsed = -1; failed = true; LOG_warn << " Failed to parse petition:<" << petition << ">" << " psep=" << psep << " psepend=" << psepend << " petition.size=" << petition.size() << " tcpctx=" << tcpctx; } else { parsed = static_cast(petition.size()); petition = petition.substr(0,psepend); command = petition.substr(0,psep); for (char& c : command) { c = static_cast(toupper(c)); }; } if (failed) { ftpctx->command = FTP_CMD_INVALID; } else if(command == "USER") { ftpctx->command = FTP_CMD_USER; } else if(command == "PASS") { ftpctx->command = FTP_CMD_PASS; } else if(command == "ACCT") { ftpctx->command = FTP_CMD_ACCT; } else if(command == "CWD") { ftpctx->command = FTP_CMD_CWD; } else if(command == "CDUP") { ftpctx->command = FTP_CMD_CDUP; } else if(command == "SMNT") { ftpctx->command = FTP_CMD_SMNT; } else if(command == "QUIT") { ftpctx->command = FTP_CMD_QUIT; } else if(command == "REIN") { ftpctx->command = FTP_CMD_REIN; } else if(command == "PORT") { ftpctx->command = FTP_CMD_PORT; } else if(command == "PASV") { ftpctx->command = FTP_CMD_PASV; } else if(command == "TYPE") { ftpctx->command = FTP_CMD_TYPE; } else if(command == "STRU") { ftpctx->command = FTP_CMD_STRU; } else if(command == "MODE") { ftpctx->command = FTP_CMD_MODE; } else if(command == "RETR") { ftpctx->command = FTP_CMD_RETR; } else if(command == "STOR") { ftpctx->command = FTP_CMD_STOR; } else if(command == "STOU") { ftpctx->command = FTP_CMD_STOU; } else if(command == "APPE") { ftpctx->command = FTP_CMD_APPE; } else if(command == "ALLO") { ftpctx->command = FTP_CMD_ALLO; } else if(command == "REST") { ftpctx->command = FTP_CMD_REST; } else if(command == "RNFR") { ftpctx->command = FTP_CMD_RNFR; } else if(command == "RNTO") { ftpctx->command = FTP_CMD_RNTO; } else if(command == "ABOR") { ftpctx->command = FTP_CMD_ABOR; } else if(command == "DELE") { ftpctx->command = FTP_CMD_DELE; } else if(command == "RMD") { ftpctx->command = FTP_CMD_RMD; } else if(command == "MKD") { ftpctx->command = FTP_CMD_MKD; } else if(command == "PWD") { ftpctx->command = FTP_CMD_PWD; } else if(command == "LIST") { ftpctx->command = FTP_CMD_LIST; } else if(command == "NLST") { ftpctx->command = FTP_CMD_NLST; } else if(command == "SITE") { ftpctx->command = FTP_CMD_SITE; } else if(command == "SYST") { ftpctx->command = FTP_CMD_SYST; } else if(command == "STAT") { ftpctx->command = FTP_CMD_STAT; } else if(command == "HELP") { ftpctx->command = FTP_CMD_HELP; } else if(command == "FEAT") { ftpctx->command = FTP_CMD_FEAT; } else if(command == "SIZE") { ftpctx->command = FTP_CMD_SIZE; } else if(command == "PROT") { ftpctx->command = FTP_CMD_PROT; } else if(command == "NOOP") { ftpctx->command = FTP_CMD_NOOP; } else if(command == "EPSV") { ftpctx->command = FTP_CMD_EPSV; } else if(command == "PBSZ") { ftpctx->command = FTP_CMD_PBSZ; } else if(command == "OPTS") { ftpctx->command = FTP_CMD_OPTS; } else { LOG_warn << " Could not match command: " << command; ftpctx->command = FTP_CMD_INVALID; } LOG_debug << " parsed command = " << ftpctx->command << " tcpctx=" << tcpctx; ftpctx->arg1 = ""; ftpctx->arg2 = ""; switch (ftpctx->command) { // no args case FTP_CMD_ABOR: case FTP_CMD_STOU: case FTP_CMD_QUIT: case FTP_CMD_REIN: case FTP_CMD_CDUP: case FTP_CMD_PASV: case FTP_CMD_EPSV: case FTP_CMD_PWD: case FTP_CMD_SYST: case FTP_CMD_FEAT: case FTP_CMD_NOOP: if (psep != string::npos) { parsed = -1; } break; // single arg case FTP_CMD_USER: case FTP_CMD_PASS: case FTP_CMD_ACCT: case FTP_CMD_CWD: case FTP_CMD_SMNT: case FTP_CMD_PORT: case FTP_CMD_TYPE: case FTP_CMD_STRU: case FTP_CMD_MODE: case FTP_CMD_RETR: case FTP_CMD_STOR: case FTP_CMD_APPE: case FTP_CMD_DELE: case FTP_CMD_RMD: case FTP_CMD_MKD: case FTP_CMD_REST: case FTP_CMD_RNFR: case FTP_CMD_RNTO: case FTP_CMD_SITE: case FTP_CMD_SIZE: case FTP_CMD_PBSZ: case FTP_CMD_PROT: if (psep != string::npos && ( (psep + 1)< petition.size()) ) { string rest = petition.substr(psep+1); ftpctx->arg1 = rest; } else { parsed = -1; } break; //optional arg case FTP_CMD_LIST: case FTP_CMD_NLST: case FTP_CMD_STAT: case FTP_CMD_HELP: if (psep != string::npos) { string rest = petition.substr(psep+1); ftpctx->arg1 = rest; } break; case FTP_CMD_ALLO: //ALLO [ R ] if (psep != string::npos && ((psep + 1) < petition.size())) { string rest = petition.substr(psep+1); psep = rest.find_first_of(separators); ftpctx->arg1 = rest.substr(0,psep); if (psep != string::npos && ((psep + 1) < petition.size())) { //optional R rest = petition.substr(psep+1); psep = rest.find_first_of(separators); if (psep != 1 && (rest.at(0) != 'R' || rest.at(0) != 'r')) { parsed = -1; } else if ((psep + 1) < petition.size()) { rest = petition.substr(psep+1); ftpctx->arg2 = rest; } else { parsed = -1; } } } else { parsed = -1; } break; case FTP_CMD_OPTS: if (psep != string::npos) { string rest = petition.substr(psep+1); psep = rest.find_first_of(separators); ftpctx->arg1 = rest.substr(0,psep); if (psep != string::npos && ((psep + 1) < rest.size())) { ftpctx->arg2 = rest.substr(psep+1); } else { parsed = -1; } } break; default: parsed = -1; break; }; switch (ftpctx->command) { case FTP_CMD_USER: { response = "331 User name okay, need password"; break; } case FTP_CMD_PASS: { response = "230 User logged in, proceed"; MegaNode *n = ftpctx->megaApi->getRootNode(); if (n) { ftpctx->cwd = n->getHandle(); ftpctx->cwdpath = "/"; ftpctx->atroot = true; ftpctx->athandle = false; delete n; } break; } case FTP_CMD_NOOP: { response = "200 NOOP from MEGA!"; break; } case FTP_CMD_TYPE: case FTP_CMD_PROT: // we might want to require that arg1 = "P". or disable useTLS in data channel otherwise { response = "200 OK"; break; } case FTP_CMD_PBSZ: //we don't use received buffer size (some client might fail) { response = "200 PBSZ=0"; break; } case FTP_CMD_PASV: case FTP_CMD_EPSV: { if (!ftpctx->ftpDataServer) { if (pport > (dataPortEnd)) { pport = dataportBegin; } ftpctx->pasiveport = pport++; LOG_debug << "Creating new MegaFTPDataServer on port " << ftpctx->pasiveport; #ifdef ENABLE_EVT_TLS MegaFTPDataServer *fds = new MegaFTPDataServer(megaApi, basePath, ftpctx, useTLS, certificatepath, keypath); #else MegaFTPDataServer *fds = new MegaFTPDataServer(megaApi, basePath, ftpctx, useTLS, string(), string()); #endif bool result = fds->start(ftpctx->pasiveport, localOnly); if (result) { ftpctx->ftpDataServer = fds; } else { response = "421 Failed to initialize data channel"; break; } } else { LOG_debug << "Reusing FTP Data connection with port: " << ftpctx->pasiveport; } // gathering IP connected to struct sockaddr_in addr; int sadrlen = sizeof (struct sockaddr_in); uv_tcp_getsockname(&ftpctx->tcphandle,(struct sockaddr*)&addr, &sadrlen); #ifdef WIN32 string sIPtoPASV = inet_ntoa(addr.sin_addr); #else char strIP[INET_ADDRSTRLEN]; inet_ntop( AF_INET, &addr.sin_addr.s_addr, strIP, INET_ADDRSTRLEN); string sIPtoPASV = strIP; #endif replace( sIPtoPASV.begin(), sIPtoPASV.end(), '.', ','); if (ftpctx->command == FTP_CMD_PASV) { char url[30]; snprintf(url, sizeof(url), "%s,%d,%d", sIPtoPASV.c_str(), ftpctx->pasiveport/256, ftpctx->pasiveport%256); response = "227 Entering Passive Mode ("; response.append(url); response.append(")"); } else // FTP_CMD_EPSV { char url[30]; snprintf(url, sizeof(url), "%d", ftpctx->pasiveport); response = "229 Entering Extended Passive Mode (|||"; response.append(url); response.append("|)"); } break; } case FTP_CMD_OPTS: { for (char& c : ftpctx->arg1) { c = static_cast(toupper(c)); }; for (char& c : ftpctx->arg2) { c = static_cast(toupper(c)); }; if (ftpctx->arg1 == "UTF8" && ftpctx->arg2 == "ON") { response = "200 All good"; } else { response = "501 Unrecognized OPTS " + ftpctx->arg1 + " " + ftpctx->arg2; } break; } case FTP_CMD_PWD: { MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd); if (n) { response = "257 "; response.append("\""); response.append(ftpctx->cwdpath); response.append("\""); delete n; } else { response = "550 Not Found"; } break; } case FTP_CMD_CWD: { response = cd(ftpctx->arg1, ftpctx); break; } case FTP_CMD_CDUP: { MegaNode *n = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd); if (n) { handle parentHandle = n->getParentHandle(); response = cdup(parentHandle, ftpctx); delete n; } else { response = "550 CWD not Found."; } break; } case FTP_CMD_LIST: case FTP_CMD_NLST: { if (!ftpctx->ftpDataServer) { response = "425 No Data Connection available"; break; } MegaNode *node = NULL; if (ftpctx->arg1.size() && ftpctx->arg1 != "-l" && ftpctx->arg1 != "-a") { node = getNodeByFtpPath(ftpctx, ftpctx->arg1); } else { if (ftpctx->atroot) { assert(!ftpctx->ftpDataServer->resultmsj.size()); set handles = getAllowedHandles(); for (std::set::iterator it = handles.begin(); it != handles.end(); ++it) { string name = megaApi->handleToBase64(*it); MegaNode *n = megaApi->getNodeByHandle(*it); if (n) { string toret = getListingLineFromNode(n, name); toret.append(crlfout); ftpctx->ftpDataServer->resultmsj.append(toret); delete n; } } ftpctx->ftpDataServer->resultmsj.append(crlfout); response = "150 Here comes the directory listing"; break; } else if (ftpctx->athandle) { MegaNode *n = megaApi->getNodeByHandle(ftpctx->cwd); if (n) { string toret = getListingLineFromNode(n); toret.append(crlfout); assert(!ftpctx->ftpDataServer->resultmsj.size()); ftpctx->ftpDataServer->resultmsj.append(toret); delete n; } ftpctx->ftpDataServer->resultmsj.append(crlfout); response = "150 Here comes the directory listing"; break; } node = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd); } if (node) { if (node->isFolder()) { MegaNodeList *children = ftpctx->megaApi->getChildren(node, MegaApi::ORDER_NONE); assert(!ftpctx->ftpDataServer->resultmsj.size()); if (ftpctx->command == FTP_CMD_LIST) { ftpctx->ftpDataServer->resultmsj.append(getListingLineFromNode(node,".")); ftpctx->ftpDataServer->resultmsj.append(crlfout); } for (int i = 0; i < children->size(); i++) { MegaNode *child = children->get(i); //string childURL = subbaseURL + child->getName(); string toret; if (ftpctx->command == FTP_CMD_LIST) { toret = getListingLineFromNode(child); } else //NLST { toret = child->getName(); } toret.append(crlfout); ftpctx->ftpDataServer->resultmsj.append(toret); } delete children; ftpctx->ftpDataServer->resultmsj.append(crlfout); response = "150 Here comes the directory listing"; } else { string toret; if (ftpctx->command == FTP_CMD_LIST) { toret = getListingLineFromNode(node); } else //NLST { toret = node->getName(); } toret.append(crlfout); assert(!ftpctx->ftpDataServer->resultmsj.size()); ftpctx->ftpDataServer->resultmsj.append(toret); response = "150 Here comes the file listing"; } delete node; } else { response = "550 Not Found"; } break; } case FTP_CMD_RETR: { if (!ftpctx->ftpDataServer) { response = "425 No Data Connection available"; break; } MegaNode *node = getNodeByFtpPath(ftpctx, ftpctx->arg1); if (node) { if (node->isFile()) { uv_mutex_lock(&ftpctx->mutex_nodeToDownload); MegaNode *oldNodeToDownload = ftpctx->ftpDataServer->nodeToDownload; ftpctx->ftpDataServer->nodeToDownload = node; delete oldNodeToDownload; uv_mutex_unlock(&ftpctx->mutex_nodeToDownload); response = "150 Here comes the file: "; response.append(node->getName()); } else { response = "501 Not a file"; delete node; } } else { response = "550 Not Found"; } break; } // case FTP_CMD_STOU: //do create random unique name case FTP_CMD_STOR: { //We might want to deal with foreign node / public link and so on ? if (!ftpctx->ftpDataServer) { response = "425 No Data Connection available"; break; } MegaNode *newParentNode = NULL; size_t seppos = ftpctx->arg1.find_last_of("/"); ftpctx->ftpDataServer->newNameToUpload = ftpctx->arg1; if (seppos == string::npos) { ftpctx->ftpDataServer->newParentNodeHandle = ftpctx->cwd; } else { if (!seppos) //new folder structure does not allow this { response = "550 Not Found"; break; } if ((seppos + 1) < ftpctx->ftpDataServer->newNameToUpload.size()) { ftpctx->ftpDataServer->newNameToUpload = ftpctx->ftpDataServer->newNameToUpload.substr(seppos + 1); } string newparentpath = ftpctx->arg1.substr(0, seppos); newParentNode = getNodeByFtpPath(ftpctx, newparentpath); if (newParentNode) { ftpctx->ftpDataServer->newParentNodeHandle = newParentNode->getHandle(); delete newParentNode; } else { ftpctx->ftpDataServer->newNameToUpload = ""; //empty } } if (ftpctx->ftpDataServer->newNameToUpload.size()) { ftpctx->ftpDataServer->remotePathToUpload = ftpctx->arg1; response = "150 Opening data connection for storing "; response.append(ftpctx->arg1); } else { response = "550 Destiny Not Found"; } break; } case FTP_CMD_RNFR: { MegaNode *nodetoRename = getNodeByFtpPath(ftpctx, ftpctx->arg1); if (nodetoRename) { this->nodeHandleToRename = nodetoRename->getHandle(); response = "350 Pending RNTO"; delete nodetoRename; } else { response = "550 Not Found"; } break; } case FTP_CMD_RMD: case FTP_CMD_DELE: { MegaNode *nodeToDelete = getNodeByFtpPath(ftpctx, ftpctx->arg1); if (nodeToDelete) { if ((ftpctx->command == FTP_CMD_DELE) ? nodeToDelete->isFile() : nodeToDelete->isFolder()) { ftpctx->megaApi->remove(nodeToDelete, false, ftpctx); delayresponse = true; } else { response = "501 Wrong type"; } delete nodeToDelete; } else { response = "550 Not Found"; } break; } case FTP_CMD_RNTO: { if (this->nodeHandleToRename == UNDEF) { response = "503 Bad sequence of commands, required RNFR first"; } else { MegaNode *nodeToRename = ftpctx->megaApi->getNodeByHandle(this->nodeHandleToRename); if (nodeToRename) { MegaNode *n = getNodeByFtpPath(ftpctx, ftpctx->arg1); if (n) { ftpctx->nodeToDeleteAfterMove = n; } MegaNode *newParentNode = NULL; size_t seppos = ftpctx->arg1.find_last_of("/"); string newName = ftpctx->arg1; if (seppos != string::npos) { if (!seppos) //new folder structure does not allow this { response = "553 Requested action not taken: Invalid destiny"; delete n; ftpctx->nodeToDeleteAfterMove = NULL; delete nodeToRename; break; } if ((seppos + 1) < newName.size()) { newName = newName.substr(seppos + 1); } string newparentpath = ftpctx->arg1.substr(0, seppos); newParentNode = getNodeByFtpPath(ftpctx, newparentpath); if (!newParentNode) { newName = ""; //empty } } if (newName.size()) { if (newParentNode && newParentNode->getHandle() != nodeToRename->getParentHandle()) { newNameAfterMove = newName; ftpctx->megaApi->moveNode(nodeToRename, newParentNode, ftpctx); delayresponse = true; } else { ftpctx->megaApi->renameNode(nodeToRename, newName.c_str(), ftpctx); delayresponse = true; } } else { response = "553 Requested action not taken. Invalid destiny"; delete n; ftpctx->nodeToDeleteAfterMove = NULL; } delete newParentNode; delete nodeToRename; } else { response = "553 Requested action not taken. Origin not found: no longer available"; } nodeHandleToRename = UNDEF; } break; } case FTP_CMD_MKD: { MegaNode *newParentNode = NULL; size_t seppos = ftpctx->arg1.find_last_of("/"); string newNameFolder = ftpctx->arg1; MegaNode *n = getNodeByFtpPath(ftpctx, newNameFolder); if (n) { response = "550 already existing!"; delete n; } else { if (!seppos) //new folder structure does not allow this { response = "550 Not Found"; break; } if (seppos != string::npos) { if ((seppos + 1) < newNameFolder.size()) { newNameFolder = newNameFolder.substr(seppos + 1); } string newparentpath = ftpctx->arg1.substr(0, seppos); newParentNode = getNodeByFtpPath(ftpctx, newparentpath); if (!newParentNode) { newNameFolder = ""; //empty } } if (newNameFolder.size()) { MegaNode *nodecwd = ftpctx->megaApi->getNodeByHandle(ftpctx->cwd); ftpctx->megaApi->createFolder(newNameFolder.c_str(),newParentNode ? newParentNode : nodecwd, ftpctx); delete nodecwd; delayresponse = true; } else { response = "550 Not Found"; } delete newParentNode; } break; } case FTP_CMD_REST: { if (ftpctx->ftpDataServer && ftpctx->ftpDataServer->nodeToDownload) { unsigned long long number = strtoull(ftpctx->arg1.c_str(), NULL, 10); if (number != ULLONG_MAX) { ftpctx->ftpDataServer->rangeStartREST = static_cast(number); response = "350 Restarting at: "; response.append(ftpctx->arg1); } else { response = "500 Syntax error, invalid start point"; } } else { response = "350 Requested file action pending further information: Download file not available"; } break; } case FTP_CMD_FEAT: { response = "211-Features:"; response.append(crlfout); response.append(" SIZE"); response.append(crlfout); response.append(" PROT"); response.append(crlfout); response.append(" EPSV"); response.append(crlfout); response.append(" PBSZ"); response.append(crlfout); // response.append(" OPTS"); // This is actually compulsory when FEAT exists (no need to return it) // response.append(crlfout); response.append(" UTF8 ON"); response.append(crlfout); response.append("211 End"); break; } case FTP_CMD_SIZE: //This has to be exact, and depends ond STRU, MODE & TYPE!! (since assumed TYPE I, it's ok) { MegaNode *nodeToGetSize = getNodeByFtpPath(ftpctx, ftpctx->arg1); if (nodeToGetSize) { if (nodeToGetSize->isFile()) { response = "213 "; ostringstream sizenumber; sizenumber << nodeToGetSize->getSize(); response.append(sizenumber.str()); } else { response = "213 "; response.append("0"); } delete nodeToGetSize; } else { response = "550 Not Found"; } break; } case FTP_CMD_INVALID: { response = "500 Syntax error, command unrecognized"; break; } default: response = "502 Command not implemented"; break; } response += crlfout; } if (nread < 0) { LOG_debug << "FTP Control Server received invalid read size. Closing connection"; closeConnection(ftpctx); } else { if (!delayresponse) { LOG_verbose << " Processed: " << petition << ". command="<< ftpctx->command << ".\n Responding: " << response << " tpctx=" << ftpctx; answer(ftpctx,response.c_str(),response.size()); } else { LOG_verbose << " Processed: " << petition << ". command="<< ftpctx->command << ".\n Delaying response. tpctx=" << ftpctx; } // Initiate data transfer for required commands if (ftpctx->ftpDataServer && ( ftpctx->command == FTP_CMD_LIST || ftpctx->command == FTP_CMD_NLST || ftpctx->command == FTP_CMD_REST || ftpctx->command == FTP_CMD_STOR || ftpctx->command == FTP_CMD_RETR ) ) { LOG_debug << " calling sending data... "; ftpctx->ftpDataServer->sendData(); //rename to sth more sensefull: like wake data channel } } uv_mutex_unlock(&tcpctx->mutex); } void MegaFTPServer::processAsyncEvent(MegaTCPContext *tcpctx) { LOG_verbose << "Processing FTP Server async event"; if (tcpctx->finished) { LOG_debug << "FTP link closed, ignoring async event"; return; } MegaFTPContext* ftpctx = dynamic_cast(tcpctx); uv_mutex_lock(&ftpctx->mutex_responses); while (ftpctx->responses.size()) { answer(tcpctx,ftpctx->responses.front().c_str(),ftpctx->responses.front().size()); ftpctx->responses.pop_front(); } uv_mutex_unlock(&ftpctx->mutex_responses); } void MegaFTPServer::processOnAsyncEventClose(MegaTCPContext*) { LOG_verbose << "At MegaFTPServer::processOnAsyncEventClose"; } void MegaTCPServer::answer(MegaTCPContext* tcpctx, const char *rsp, size_t rlen) { LOG_verbose << " answering in port " << tcpctx->server->port << " : " << string(rsp,rlen); uv_buf_t resbuf = uv_buf_init((char *)rsp, static_cast(rlen)); #ifdef ENABLE_EVT_TLS if (tcpctx->server->useTLS) { // we are sending the response as a whole int err = evt_tls_write(tcpctx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls); if (err <= 0) { LOG_warn << "Finishing due to an error sending the response: " << err; closeConnection(tcpctx); } } else { #endif uv_write_t *req = new uv_write_t(); req->data = tcpctx; if (int err = uv_write(req, (uv_stream_t*)&tcpctx->tcphandle, &resbuf, 1, onWriteFinished)) { delete req; LOG_warn << "Finishing due to an error sending the response: " << err; closeTCPConnection(tcpctx); } #ifdef ENABLE_EVT_TLS } #endif } bool MegaFTPServer::respondNewConnection(MegaTCPContext* tcpctx) { MegaFTPContext* ftpctx = dynamic_cast(tcpctx); string response = "220 Wellcome to FTP MEGA Server"; response.append(crlfout); answer(ftpctx, response.c_str(), response.size()); return true; } void MegaFTPServer::processOnExitHandleClose(MegaTCPServer*) {} MegaFTPContext::MegaFTPContext() { command = 0; resultcode = 0; cwd = UNDEF; atroot = false; athandle = false; parentcwd = UNDEF; pasiveport = -1; ftpDataServer = NULL; nodeToDeleteAfterMove = NULL; uv_mutex_init(&mutex_responses); uv_mutex_init(&mutex_nodeToDownload); } MegaFTPContext::~MegaFTPContext() { if (ftpDataServer) { LOG_verbose << "Deleting ftpDataServer associated with ftp context"; delete ftpDataServer; } if (tmpFileName.size()) { LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName); server->fsAccess->unlinklocal(localPath); tmpFileName = ""; } uv_mutex_destroy(&mutex_responses); uv_mutex_destroy(&mutex_nodeToDownload); } void MegaFTPContext::onTransferStart(MegaApi*, MegaTransfer*) {} bool MegaFTPContext::onTransferData(MegaApi*, MegaTransfer*, char* /*buffer*/, size_t /*size*/) { LOG_verbose << "MegaFTPContext::onTransferData"; return true; } void MegaFTPContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e) { if (finished) { LOG_debug << "FTP link closed, ignoring the result of the transfer"; return; } if (e->getErrorCode() == MegaError::API_OK) { MegaFTPServer::returnFtpCodeAsync(this, 250); } else { MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e); } if (tmpFileName.size()) { LocalPath localPath = LocalPath::fromAbsolutePath(tmpFileName); server->fsAccess->unlinklocal(localPath); tmpFileName = ""; } } void MegaFTPContext::onRequestFinish(MegaApi *, MegaRequest *request, MegaError *e) { if (finished) { LOG_debug << "HTTP link closed, ignoring the result of the request"; return; } MegaFTPServer* ftpserver = dynamic_cast(this->server); if (request->getType() == MegaRequest::TYPE_REMOVE) { if (e->getErrorCode() == MegaError::API_OK) { if (cwd == request->getNodeHandle()) { LOG_verbose << " Removing cwd node, going back to parent"; ftpserver->cdup(parentcwd, this); } else // This could be unexpected if CWD is removed elsewhere by the time this RequestFinish completes //TODO: decide upon revision: is this OK? { MegaNode *n = megaApi->getNodeByHandle(cwd); size_t seps = static_cast(std::count(cwdpath.begin(), cwdpath.end(), '/')); unsigned int isep = 0; string sup = cwdpath; while (!n && (isep++ < seps)) { sup.append("/.."); string nsup = ftpserver->shortenpath(sup); ftpserver->cd(nsup, this); n = megaApi->getNodeByHandle(cwd); } delete n; } MegaFTPServer::returnFtpCodeAsync(this, 250); } else { MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_CREATE_FOLDER) { if (e->getErrorCode() == MegaError::API_OK) { MegaFTPServer::returnFtpCodeAsync(this, 257, request->getName()); } else { MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_RENAME) { if (e->getErrorCode() == MegaError::API_OK ) { if (nodeToDeleteAfterMove) { this->megaApi->remove(nodeToDeleteAfterMove, false, this); nodeToDeleteAfterMove = NULL; delete nodeToDeleteAfterMove; } else { MegaFTPServer::returnFtpCodeAsync(this, 250); } } else { MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e); } } else if (request->getType() == MegaRequest::TYPE_MOVE) { if (e->getErrorCode() == MegaError::API_OK) { if (ftpserver->newNameAfterMove.size()) { MegaNode *nodetoRename = this->megaApi->getNodeByHandle(request->getNodeHandle()); if (!nodetoRename) { MegaFTPServer::returnFtpCodeAsync(this, 550, "Moved node not found"); } else if (!strcmp(nodetoRename->getName(), ftpserver->newNameAfterMove.c_str())) { if (nodeToDeleteAfterMove) { this->megaApi->remove(nodeToDeleteAfterMove, false, this); nodeToDeleteAfterMove = NULL; delete nodeToDeleteAfterMove; } else { MegaFTPServer::returnFtpCodeAsync(this, 250); } } else { this->megaApi->renameNode(nodetoRename, ftpserver->newNameAfterMove.c_str(), this); } delete nodetoRename; } else { if (nodeToDeleteAfterMove) { this->megaApi->remove(nodeToDeleteAfterMove, false, this); nodeToDeleteAfterMove = NULL; delete nodeToDeleteAfterMove; } else { MegaFTPServer::returnFtpCodeAsync(this, 250); } } } else { MegaFTPServer::returnFtpCodeAsyncBasedOnRequestError(this, e); } } uv_async_send(&asynchandle); } // FTP DATA Server MegaFTPDataServer::MegaFTPDataServer(MegaApiImpl *megaApi, string basePath, MegaFTPContext * controlftpctx, bool useTLS, string certificatepath, string keypath) : MegaTCPServer(megaApi, basePath, useTLS, certificatepath, keypath) { this->controlftpctx = controlftpctx; this->nodeToDownload = NULL; this->rangeStartREST = 0; this->notifyNewConnectionRequired = false; this->newParentNodeHandle = UNDEF; } MegaFTPDataServer::~MegaFTPDataServer() { LOG_verbose << "MegaFTPDataServer::~MegaFTPDataServer"; delete nodeToDownload; // if not stopped, the uv thread might want to access a pointer to this. // though this is done in the parent destructor, it could try to access it after vtable has been erased stop(); LOG_verbose << "MegaFTPDataServer::~MegaFTPDataServer. end"; } MegaTCPContext* MegaFTPDataServer::initializeContext(uv_stream_t *server_handle) { MegaFTPDataContext* ftpctx = new MegaFTPDataContext(); // Set connection data MegaFTPDataServer* ftpDataServer = (MegaFTPDataServer*)(server_handle->data); ftpctx->server = ftpDataServer; ftpctx->megaApi = ftpDataServer->megaApi; ftpctx->tcphandle.data = ftpctx; ftpctx->asynchandle.data = ftpctx; return ftpctx; } void MegaFTPDataServer::processWriteFinished(MegaTCPContext *tcpctx, int status) { if (status < 0) { LOG_warn << " error received at processWriteFinished: " << status << ": " << uv_err_name(status); } MegaFTPDataContext* ftpdatactx = dynamic_cast(tcpctx); LOG_debug << " processWriteFinished on MegaFTPDataServer. status = " << status; if (resultmsj.size()) { resultmsj = ""; // empty result msj // this would be incorrect if we used partial writes (does not seem to be required) if (this->controlftpctx) { ftpdatactx->setControlCodeUponDataClose(226); } else { LOG_verbose << "Avoiding waking controlftp aync handle, ftpctx already closed"; } closeConnection(tcpctx); } else // transfering node (download) { ftpdatactx->bytesWritten += ftpdatactx->lastBufferLen; LOG_verbose << "Bytes written: " << ftpdatactx->lastBufferLen << " Remaining: " << (ftpdatactx->size - ftpdatactx->bytesWritten); ftpdatactx->lastBuffer = NULL; if (status < 0 || ftpdatactx->size == ftpdatactx->bytesWritten) { if (status < 0) { LOG_warn << "Finishing request. Write failed: " << status << ": " << uv_err_name(status); } else { LOG_debug << "Finishing request. All data sent"; } if (this->controlftpctx) { ftpdatactx->setControlCodeUponDataClose(226); } else { LOG_verbose << "Avoiding waking controlftp aync handle, ftpctx already closed"; } closeConnection(ftpdatactx); return; } uv_mutex_lock(&ftpdatactx->mutex); if (ftpdatactx->lastBufferLen) { ftpdatactx->streamingBuffer.freeData(ftpdatactx->lastBufferLen); ftpdatactx->lastBufferLen = 0; } if (ftpdatactx->pause) { if (ftpdatactx->streamingBuffer.availableSpace() > ftpdatactx->streamingBuffer.availableCapacity() / 2) { ftpdatactx->pause = false; m_off_t start = ftpdatactx->rangeStart + ftpdatactx->rangeWritten + static_cast(ftpdatactx->streamingBuffer.availableData()); m_off_t len = ftpdatactx->rangeEnd - ftpdatactx->rangeStart - ftpdatactx->rangeWritten - static_cast(ftpdatactx->streamingBuffer.availableData()); LOG_debug << "[Streaming] Resuming streaming from " << start << " len: " << len << " " << ftpdatactx->streamingBuffer.bufferStatus(); ftpdatactx->megaApi->startStreaming(ftpdatactx->node, start, len, ftpdatactx); } } uv_mutex_unlock(&ftpdatactx->mutex); uv_async_send(&ftpdatactx->asynchandle); } } void MegaFTPDataServer::sendData() { MegaTCPContext * tcpctx = NULL; this->notifyNewConnectionRequired = true; if (connections.size()) { tcpctx = connections.back(); //only interested in the last connection received (the one that needs response) } //Some client might create connections before receiving a 150 in the control channel (e.g: ftp linux command) // This could cause never answered / never closed connections. //assert(connections.size() <= 1); //This might not be true due to that if (tcpctx) { LOG_verbose << "MegaFTPDataServer::sendData. triggering asyncsend for tcpctx=" << tcpctx; #ifdef ENABLE_EVT_TLS if (tcpctx->evt_tls == NULL) { LOG_warn << "MegaFTPDataServer::sendData, evt_tls is NULL"; } if (useTLS && (!tcpctx->evt_tls || tcpctx->finished || !evt_tls_is_handshake_over(tcpctx->evt_tls))) { if (!tcpctx->evt_tls) { LOG_verbose << "MegaFTPDataServer::sendData. no evt_tls"; } else if (tcpctx->finished) { LOG_verbose << "MegaFTPDataServer::sendData. tcpctx->finished"; this->notifyNewConnectionRequired = false; } else { LOG_verbose << "MegaFTPDataServer::sendData. handshake not over"; } } else { #endif LOG_verbose << "MegaFTPDataServer::sendData. do triggering asyncsend 03"; this->notifyNewConnectionRequired = false; uv_async_send(&tcpctx->asynchandle); #ifdef ENABLE_EVT_TLS } #endif } else { LOG_verbose << "MegaFTPDataServer::sendData. no tcpctx. notifyNewConnectionRequired"; this->notifyNewConnectionRequired = true; } } void MegaFTPDataServer::processReceivedData(MegaTCPContext *tcpctx, ssize_t nread, const uv_buf_t * buf) { MegaFTPDataContext* ftpdatactx = dynamic_cast(tcpctx); MegaFTPDataServer* fds = dynamic_cast(ftpdatactx->server); if (fds->newNameToUpload.size()) { //create tmp file with contents in messageBody if (!ftpdatactx->tmpFileAccess) { ftpdatactx->tmpFileName = fds->basePath; ftpdatactx->tmpFileName.append("ftpstorfile"); ftpdatactx->tmpFileName.append(LocalPath::tmpNameLocal().toPath(false)); string ext; if (ftpdatactx->server->fsAccess->getextension(LocalPath::fromAbsolutePath(fds->controlftpctx->arg1), ext)) { ftpdatactx->tmpFileName.append(ext); } ftpdatactx->tmpFileAccess = fds->fsAccess->newfileaccess(); LocalPath localPath = LocalPath::fromAbsolutePath(ftpdatactx->tmpFileName); fds->fsAccess->unlinklocal(localPath); if (!ftpdatactx->tmpFileAccess->fopen(localPath, OPEN_WRONLY, FSLogging::logOnError)) { ftpdatactx->setControlCodeUponDataClose(450); remotePathToUpload = ""; //empty, so that we don't read in the next connections closeConnection(tcpctx); return; } } if (nread > 0) { LOG_verbose << " Writing " << nread << " bytes " << " to temporal file: " << ftpdatactx->tmpFileName; if (!ftpdatactx->tmpFileAccess->fwrite(reinterpret_cast(buf->base), static_cast(nread), static_cast(ftpdatactx->tmpFileSize))) { ftpdatactx->setControlCodeUponDataClose(450); remotePathToUpload = ""; //empty, so that we don't read in the next connections closeConnection(tcpctx); } ftpdatactx->tmpFileSize += static_cast(nread); } } else { LOG_err << "FTPData server receiving unexpected data: " << nread << " bytes"; } if (nread < 0) //transfer finish { LOG_verbose << "FTP Data Channel received invalid read size: " << nread << ". Closing connection"; if (ftpdatactx->tmpFileName.size()) { std::unique_ptr newParentNode {ftpdatactx->megaApi->getNodeByHandle(fds->newParentNodeHandle)}; if (newParentNode) { LOG_debug << "Starting upload of file " << fds->newNameToUpload; fds->controlftpctx->tmpFileName = ftpdatactx->tmpFileName; FileSystemType fsType = fds->fsAccess->getlocalfstype(LocalPath::fromAbsolutePath(ftpdatactx->tmpFileName)); MegaApiImpl::MegaUploadOptionsPrivate uploadOptions; uploadOptions.mPublicOptions.fileName = fds->newNameToUpload; uploadOptions.mPublicOptions.mtime = -1; uploadOptions.mIsBackup = true; uploadOptions.mFsType = fsType; ftpdatactx->megaApi->startUpload(ftpdatactx->tmpFileName, newParentNode.get(), CancelToken(), uploadOptions, fds->controlftpctx); ftpdatactx->controlRespondedElsewhere = true; } else { LOG_err << "Unable to start upload: " << fds->newNameToUpload; ftpdatactx->setControlCodeUponDataClose(550, "Destination folder not available"); } ftpdatactx->tmpFileName=""; } else { LOG_err << "Data channel received close without tmp file created!"; ftpdatactx->setControlCodeUponDataClose(426); } ftpdatactx->tmpFileName = ""; // empty so that we don't try to upload it remotePathToUpload = ""; //empty, so that we don't read in the next connections closeConnection(tcpctx); return; } } void MegaFTPDataServer::processAsyncEvent(MegaTCPContext *tcpctx) { LOG_verbose << "MegaFTPDataServer::processAsyncEvent. tcptcx= " << tcpctx; MegaFTPDataContext* ftpdatactx = dynamic_cast(tcpctx); MegaFTPDataServer* fds = dynamic_cast(tcpctx->server); if (ftpdatactx->finished) { LOG_debug << "FTP DATA link closed, ignoring async event"; return; } if (ftpdatactx->failed) { LOG_warn << "Streaming transfer failed. Closing connection."; closeConnection(ftpdatactx); return; } uv_mutex_lock(&fds->controlftpctx->mutex_nodeToDownload); if (resultmsj.size()) { LOG_debug << " responding DATA: " << resultmsj; answer(ftpdatactx, resultmsj.c_str(), resultmsj.size()); } else if (remotePathToUpload.size()) { LOG_debug << " receive data to store in tmp file:"; readData(ftpdatactx); } else if (nodeToDownload) { if (!ftpdatactx->node || rangeStartREST) //alterantive to || rangeStart, define aborted? { if (!rangeStartREST) { LOG_debug << "Initiating node download via port " << fds->port; } else { LOG_debug << "Initiating node download from: " << rangeStartREST << " via port " << fds->port; } ftpdatactx->rangeStart = rangeStartREST; rangeStartREST = 0;// so as not to start again ftpdatactx->bytesWritten = 0; ftpdatactx->size = 0; ftpdatactx->streamingBuffer.setMaxBufferSize( static_cast(ftpdatactx->server->getMaxBufferSize())); ftpdatactx->streamingBuffer.setMaxOutputSize( static_cast(ftpdatactx->server->getMaxOutputSize())); ftpdatactx->transfer = new MegaTransferPrivate(MegaTransfer::TYPE_LOCAL_TCP_DOWNLOAD); ftpdatactx->transfer->setPath(fds->controlftpctx->arg1.c_str()); if (ftpdatactx->nodename.size()) { ftpdatactx->transfer->setFileName(ftpdatactx->nodename.c_str()); } if (ftpdatactx->nodehandle.size()) { ftpdatactx->transfer->setNodeHandle(MegaApi::base64ToHandle(ftpdatactx->nodehandle.c_str())); } ftpdatactx->transfer->setStartTime(Waiter::ds); m_off_t totalSize = nodeToDownload->getSize(); m_off_t start = 0; m_off_t end = totalSize - 1; if (ftpdatactx->rangeStart > 0) { start = ftpdatactx->rangeStart; } ftpdatactx->rangeEnd = end + 1; ftpdatactx->rangeStart = start; //bool rangeRequested = (/*ftpdatactx->rangeEnd*/ - ftpdatactx->rangeStart) != totalSize; m_off_t len = end - start + 1; ftpdatactx->pause = false; ftpdatactx->lastBuffer = NULL; ftpdatactx->lastBufferLen = 0; ftpdatactx->transfer->setStartPos(0); ftpdatactx->transfer->setEndPos(end); //This will actually be override later ftpdatactx->node = nodeToDownload->copy(); ftpdatactx->streamingBuffer.setFileSize(nodeToDownload->getSize()); ftpdatactx->streamingBuffer.setDuration(nodeToDownload->getDuration()); ftpdatactx->streamingBuffer.init(static_cast(len)); ftpdatactx->size = len; ftpdatactx->megaApi->fireOnFtpStreamingStart(ftpdatactx->transfer); LOG_debug << "Requesting range. From " << start << " size " << len; ftpdatactx->rangeWritten = 0; if (start || len) { ftpdatactx->megaApi->startStreaming(nodeToDownload, start, len, ftpdatactx); } else { LOG_debug << "Skipping startStreaming call since empty file"; ftpdatactx->megaApi->fireOnFtpStreamingFinish(ftpdatactx->transfer, std::make_unique(API_OK)); ftpdatactx->transfer = NULL; // this has been deleted in fireOnStreamingFinish fds->processWriteFinished(ftpdatactx, 0); } } else { LOG_debug << "Calling sendNextBytes port = " << fds->port; sendNextBytes(ftpdatactx); } } else { LOG_err << " Async event with no result mesj!!!"; } uv_mutex_unlock(&fds->controlftpctx->mutex_nodeToDownload); } void MegaFTPDataServer::processOnAsyncEventClose(MegaTCPContext* tcpctx) { MegaFTPDataContext* ftpdatactx = dynamic_cast(tcpctx); MegaFTPDataServer *fds = ((MegaFTPDataServer *)ftpdatactx->server); LOG_verbose << "MegaFTPDataServer::processOnAsyncEventClose. tcpctx=" << tcpctx << " port = " << fds->port << " remaining = " << fds->remainingcloseevents; fds->remotePathToUpload = ""; if (ftpdatactx->transfer) { ftpdatactx->megaApi->cancelTransfer(ftpdatactx->transfer); ftpdatactx->megaApi->fireOnFtpStreamingFinish(ftpdatactx->transfer, std::make_unique(ftpdatactx->ecode)); ftpdatactx->transfer = NULL; // this has been deleted in fireOnStreamingFinish } if (!fds->remainingcloseevents && fds->closing) { LOG_verbose << "MegaFTPDataServer::processOnAsyncEventClose stopping without waiting. port = " << fds->port; fds->stop(true); } if (!ftpdatactx->controlRespondedElsewhere && fds->started && !this->controlftpctx->finished) { LOG_debug << "MegaFTPDataServer::processOnAsyncEventClose port = " << fds->port << ". Responding " << ftpdatactx->controlResponseCode << ". " << ftpdatactx->controlResponseMessage; MegaFTPServer* ftpControlServer = dynamic_cast(fds->controlftpctx->server); ftpControlServer->returnFtpCode(this->controlftpctx, ftpdatactx->controlResponseCode, ftpdatactx->controlResponseMessage); } } bool MegaFTPDataServer::respondNewConnection(MegaTCPContext* tcpctx) { MegaFTPDataContext* ftpdatactx = dynamic_cast(tcpctx); if (notifyNewConnectionRequired) //in some cases, control connection tried this before the ftpdatactx was ready. this fixes that { LOG_verbose << "MegaFTPDataServer::respondNewConnection async sending to notify new connection"; uv_async_send(&ftpdatactx->asynchandle); notifyNewConnectionRequired = false; } return false; } void MegaFTPDataServer::processOnExitHandleClose(MegaTCPServer*) {} void MegaFTPDataServer::sendNextBytes(MegaFTPDataContext *ftpdatactx) { if (ftpdatactx->finished) { LOG_debug << "FTP link closed, aborting write"; return; } if (ftpdatactx->lastBuffer) { LOG_verbose << "[Streaming] Skipping write due to another ongoing write"; return; } uv_mutex_lock(&ftpdatactx->mutex); if (ftpdatactx->lastBufferLen) { ftpdatactx->streamingBuffer.freeData(ftpdatactx->lastBufferLen); ftpdatactx->lastBufferLen = 0; } if (ftpdatactx->tcphandle.write_queue_size > ftpdatactx->streamingBuffer.availableCapacity() / 8) { LOG_warn << "[Streaming] Skipping write. Too much queued data. " << ftpdatactx->streamingBuffer.bufferStatus(); uv_mutex_unlock(&ftpdatactx->mutex); return; } uv_buf_t resbuf = ftpdatactx->streamingBuffer.nextBuffer(); uv_mutex_unlock(&ftpdatactx->mutex); if (!resbuf.len) { LOG_verbose << "[Streaming] Skipping write. No data available. " << ftpdatactx->streamingBuffer.bufferStatus(); return; } LOG_verbose << "Writing " << resbuf.len << " bytes" << " buffered = " << ftpdatactx->streamingBuffer.availableData(); ftpdatactx->rangeWritten += resbuf.len; ftpdatactx->lastBuffer = resbuf.base; ftpdatactx->lastBufferLen = resbuf.len; #ifdef ENABLE_EVT_TLS if (ftpdatactx->server->useTLS) { //notice this, contrary to !useTLS is synchronous int err = evt_tls_write(ftpdatactx->evt_tls, resbuf.base, resbuf.len, onWriteFinished_tls); if (err <= 0) { LOG_warn << "[Streaming] Finishing due to an error sending the response: " << err; closeConnection(ftpdatactx); } } else { #endif uv_write_t *req = new uv_write_t(); req->data = ftpdatactx; if (int err = uv_write(req, (uv_stream_t*)&ftpdatactx->tcphandle, &resbuf, 1, onWriteFinished)) { delete req; LOG_warn << "[Streaming] Finishing due to an error in uv_write: " << err; closeTCPConnection(ftpdatactx); } #ifdef ENABLE_EVT_TLS } #endif } MegaFTPDataContext::MegaFTPDataContext() { transfer = NULL; lastBuffer = NULL; lastBufferLen = 0; failed = false; ecode = API_OK; pause = false; node = NULL; rangeWritten = 0; rangeStart = 0; rangeEnd = 0; tmpFileAccess = NULL; tmpFileSize = 0; controlRespondedElsewhere = false; controlResponseCode = 426; } MegaFTPDataContext::~MegaFTPDataContext() { delete transfer; delete node; } void MegaFTPDataContext::setControlCodeUponDataClose(int code, string msg) { controlResponseCode = code; controlResponseMessage = msg; } void MegaFTPDataContext::onTransferStart(MegaApi*, MegaTransfer* ftpDataTransfer) { transfer->setTag(ftpDataTransfer->getTag()); } bool MegaFTPDataContext::onTransferData(MegaApi*, MegaTransfer* ftpDataTransfer, char* buffer, size_t dataSize) { LOG_verbose << "Streaming data received: " << ftpDataTransfer->getTransferredBytes() << " Size: " << dataSize << " Remaining from transfer: " << (dataSize + static_cast(ftpDataTransfer->getTotalBytes() - ftpDataTransfer->getTransferredBytes())) << " Remaining to write TCP: " << (size - bytesWritten) << " Queued: " << this->tcphandle.write_queue_size << " Buffered: " << streamingBuffer.availableData() << " Free: " << streamingBuffer.availableSpace(); if (finished) { LOG_info << "Removing streaming transfer after " << ftpDataTransfer->getTransferredBytes() << " bytes"; return false; } // append the data to the buffer uv_mutex_lock(&mutex); long long remaining = static_cast(dataSize) + (ftpDataTransfer->getTotalBytes() - ftpDataTransfer->getTransferredBytes()); long long availableSpace = static_cast(streamingBuffer.availableSpace()); if (remaining > availableSpace && availableSpace < (2 * m_off_t(dataSize))) { LOG_debug << "[Streaming] Buffer full: Pausing streaming. " << streamingBuffer.bufferStatus(); pause = true; } streamingBuffer.append(buffer, dataSize); uv_mutex_unlock(&mutex); // notify the HTTP server uv_async_send(&asynchandle); return !pause; } void MegaFTPDataContext::onTransferFinish(MegaApi *, MegaTransfer *, MegaError *e) { LOG_verbose << "MegaFTPDataContext::onTransferFinish"; if (finished) { LOG_debug << "FTP Data link closed"; return; } ecode = e->getErrorCode(); if (ecode != API_OK && ecode != API_EINCOMPLETE) { LOG_warn << "Transfer failed with error code: " << ecode; failed = true; } uv_async_send(&asynchandle); } void MegaFTPDataContext::onRequestFinish(MegaApi*, MegaRequest*, MegaError*) { if (finished) { LOG_debug << "FTP data link closed, ignoring the result of the request"; return; } uv_async_send(&asynchandle); } #endif #ifdef ENABLE_CHAT MegaTextChatPeerListPrivate::MegaTextChatPeerListPrivate() { } MegaTextChatPeerListPrivate::~MegaTextChatPeerListPrivate() { } MegaTextChatPeerList *MegaTextChatPeerListPrivate::copy() const { MegaTextChatPeerListPrivate *ret = new MegaTextChatPeerListPrivate; for (size_t i = 0; i < static_cast(size()); i++) { ret->addPeer(list.at(i).first, list.at(i).second); } return ret; } void MegaTextChatPeerListPrivate::addPeer(MegaHandle h, int priv) { list.push_back(userpriv_pair(h, (privilege_t) priv)); } MegaHandle MegaTextChatPeerListPrivate::getPeerHandle(int i) const { if (i > size()) { return INVALID_HANDLE; } else { return list.at(static_cast(i)).first; } } int MegaTextChatPeerListPrivate::getPeerPrivilege(int i) const { if (i > size()) { return PRIV_UNKNOWN; } else { return list.at(static_cast(i)).second; } } int MegaTextChatPeerListPrivate::size() const { return int(list.size()); } const userpriv_vector *MegaTextChatPeerListPrivate::getList() const { return &list; } void MegaTextChatPeerListPrivate::setPeerPrivilege(handle uh, privilege_t priv) { for (unsigned int i = 0; i < list.size(); i++) { if (list.at(i).first == uh) { list.at(i).second = priv; return; } } // handle not found. Create new item addPeer(uh, priv); } MegaTextChatPeerListPrivate::MegaTextChatPeerListPrivate(const userpriv_vector *userpriv) { handle uh; privilege_t priv; for (unsigned i = 0; i < userpriv->size(); i++) { uh = userpriv->at(i).first; priv = userpriv->at(i).second; this->addPeer(uh, priv); } } MegaTextChatPrivate::MegaTextChatPrivate(const MegaTextChat *chat) { this->id = chat->getHandle(); this->priv = chat->getOwnPrivilege(); this->shard = chat->getShard(); this->peers = chat->getPeerList() ? chat->getPeerList()->copy() : NULL; this->group = chat->isGroup(); this->ou = chat->getOriginatingUser(); this->title = chat->getTitle() ? chat->getTitle() : ""; this->ts = chat->getCreationTime(); this->archived = chat->isArchived(); this->publicchat = chat->isPublicChat(); this->tag = chat->isOwnChange(); this->changed = chat->getChanges(); this->unifiedKey = chat->getUnifiedKey() ? chat->getUnifiedKey() : ""; this->meeting = chat->isMeeting(); this->chatOptions = chat->getChatOptions(); if (chat->getScheduledMeetingList() && chat->getScheduledMeetingList()->size()) { mScheduledMeetings.reset(chat->getScheduledMeetingList()->copy()); } if (chat->getSchedMeetingsChanged() && chat->getSchedMeetingsChanged()->size()) { mSchedMeetingsChanged.reset(chat->getSchedMeetingsChanged()->copy()); } if (chat->getUpdatedOccurrencesList() && chat->getUpdatedOccurrencesList()->size()) { mUpdatedOcurrences.reset(chat->getUpdatedOccurrencesList()->copy()); } } MegaTextChatPrivate::MegaTextChatPrivate(const TextChat *chat) { this->id = chat->getChatId(); this->priv = chat->getOwnPrivileges(); this->shard = chat->getShard(); this->peers = chat->getUserPrivileges() ? new MegaTextChatPeerListPrivate(chat->getUserPrivileges()) : nullptr; this->group = chat->getGroup(); this->ou = chat->getOwnUser(); this->title = chat->getTitle(); this->tag = chat->getTag(); this->ts = chat->getTs(); this->archived = chat->isFlagSet(TextChat::FLAG_OFFSET_ARCHIVE); this->publicchat = chat->publicChat(); this->unifiedKey = chat->getUnifiedKey(); this->meeting = chat->getMeeting(); this->chatOptions = chat->getChatOptions(); this->changed = 0; if (!chat->getSchedMeetings().empty()) { mScheduledMeetings.reset(MegaScheduledMeetingList::createInstance()); for (auto it = chat->getSchedMeetings().begin(); it != chat->getSchedMeetings().end(); it++) { mScheduledMeetings->insert(new MegaScheduledMeetingPrivate(it->second.get())); } } if (!chat->getSchedMeetingsChanged().empty()) { mSchedMeetingsChanged.reset(MegaHandleList::createInstance()); for (auto it = chat->getSchedMeetingsChanged().begin(); it != chat->getSchedMeetingsChanged().end(); it++) { mSchedMeetingsChanged->addMegaHandle(*it); } changed |= MegaTextChat::CHANGE_TYPE_SCHED_MEETING; } if (chat->changed.schedOcurrReplace || chat->changed.schedOcurrAppend) { changed |= chat->changed.schedOcurrReplace ? MegaTextChat::CHANGE_TYPE_SCHED_REPLACE_OCURR : MegaTextChat::CHANGE_TYPE_SCHED_APPEND_OCURR; if (!chat->getUpdatedOcurrences().empty()) { mUpdatedOcurrences.reset(MegaScheduledMeetingList::createInstance()); for (auto it = chat->getUpdatedOcurrences().begin(); it != chat->getUpdatedOcurrences().end(); it++) { mUpdatedOcurrences->insert(new MegaScheduledMeetingPrivate((*it).get())); } } } if (chat->changed.attachments) { changed |= MegaTextChat::CHANGE_TYPE_ATTACHMENT; } if (chat->changed.flags) { changed |= MegaTextChat::CHANGE_TYPE_FLAGS; } if (chat->changed.mode) { changed |= MegaTextChat::CHANGE_TYPE_MODE; } if (chat->changed.options) { changed |= MegaTextChat::CHANGE_TYPE_CHAT_OPTIONS; } } MegaTextChat *MegaTextChatPrivate::copy() const { return new MegaTextChatPrivate(this); } MegaTextChatPrivate::~MegaTextChatPrivate() { delete peers; } MegaHandle MegaTextChatPrivate::getHandle() const { return id; } int MegaTextChatPrivate::getOwnPrivilege() const { return priv; } int MegaTextChatPrivate::getShard() const { return shard; } const MegaTextChatPeerList *MegaTextChatPrivate::getPeerList() const { return peers; } void MegaTextChatPrivate::setPeerList(const MegaTextChatPeerList* newPeers) { if (peers) { delete peers; } peers = newPeers ? newPeers->copy() : nullptr; } bool MegaTextChatPrivate::isGroup() const { return group; } MegaHandle MegaTextChatPrivate::getOriginatingUser() const { return ou; } const char *MegaTextChatPrivate::getTitle() const { return !title.empty() ? title.c_str() : NULL; } const char *MegaTextChatPrivate::getUnifiedKey() const { return !unifiedKey.empty() ? unifiedKey.c_str() : NULL; } unsigned char MegaTextChatPrivate::getChatOptions() const { return chatOptions; } int MegaTextChatPrivate::isOwnChange() const { return tag; } int64_t MegaTextChatPrivate::getCreationTime() const { return ts; } bool MegaTextChatPrivate::isArchived() const { return archived; } bool MegaTextChatPrivate::isPublicChat() const { return publicchat; } bool MegaTextChatPrivate::isMeeting() const { return meeting; } bool MegaTextChatPrivate::hasChanged(uint64_t changeType) const { return (changed & changeType); } uint64_t MegaTextChatPrivate::getChanges() const { return changed; } const MegaScheduledMeetingList* MegaTextChatPrivate::getScheduledMeetingList() const { return mScheduledMeetings.get(); } const MegaScheduledMeetingList* MegaTextChatPrivate::getUpdatedOccurrencesList() const { return mUpdatedOcurrences.get(); } const MegaHandleList* MegaTextChatPrivate::getSchedMeetingsChanged() const { return mSchedMeetingsChanged.get(); } MegaTextChatListPrivate::~MegaTextChatListPrivate() { for (unsigned int i = 0; i < (unsigned int) size(); i++) { delete list.at(i); } } MegaTextChatList *MegaTextChatListPrivate::copy() const { return new MegaTextChatListPrivate(this); } const MegaTextChat *MegaTextChatListPrivate::get(unsigned int i) const { if (i >= (unsigned int) size()) { return NULL; } else { return list.at(i); } } int MegaTextChatListPrivate::size() const { return int(list.size()); } void MegaTextChatListPrivate::addChat(MegaTextChatPrivate *chat) { list.push_back(chat); } MegaTextChatListPrivate::MegaTextChatListPrivate(const MegaTextChatListPrivate *list) { MegaTextChatPrivate *chat; for (unsigned int i = 0; i < (unsigned int) list->size(); i++) { chat = new MegaTextChatPrivate(list->get(i)); this->list.push_back(chat); } } MegaTextChatListPrivate::MegaTextChatListPrivate() { } MegaTextChatListPrivate::MegaTextChatListPrivate(textchat_map *list) { MegaTextChatPrivate *megaChat; textchat_map::iterator it; for (it = list->begin(); it != list->end(); it++) { megaChat = new MegaTextChatPrivate(it->second); this->list.push_back(megaChat); } } MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate() : mScheduledFlags(std::make_unique()) {} MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const unsigned long numericValue) : mScheduledFlags(std::make_unique(numericValue)) {} MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const MegaScheduledFlagsPrivate *flags) : mScheduledFlags(std::make_unique(flags ? flags->getNumericValue() : ScheduledFlags::schedEmptyFlags)) {} MegaScheduledFlagsPrivate::MegaScheduledFlagsPrivate(const ScheduledFlags* flags) : mScheduledFlags(std::make_unique(flags ? flags->getNumericValue() : ScheduledFlags::schedEmptyFlags)) {} void MegaScheduledFlagsPrivate::reset() { mScheduledFlags->reset(); } void MegaScheduledFlagsPrivate::setSendEmails(bool enabled) { mScheduledFlags->setSendEmails(enabled); } void MegaScheduledFlagsPrivate::importFlagsValue(unsigned long val) { mScheduledFlags->importFlagsValue(val); } bool MegaScheduledFlagsPrivate::sendEmails() const { return mScheduledFlags->sendEmails(); } unsigned long MegaScheduledFlagsPrivate::getNumericValue() const { return mScheduledFlags->getNumericValue();} bool MegaScheduledFlagsPrivate::isEmpty() const { return mScheduledFlags->isEmpty(); } unique_ptr MegaScheduledFlagsPrivate::getSdkScheduledFlags() const { return std::make_unique(getNumericValue()); } MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const int freq, const int interval, const MegaTimeStamp until, const MegaIntegerList* byWeekDay, const MegaIntegerList* byMonthDay, const MegaIntegerMap* byMonthWeekDay) : mScheduledRules(std::make_unique( isValidFreq(freq) ? freq : FREQ_INVALID, isValidInterval(interval) ? interval : int{INTERVAL_INVALID}, // ^^^^^^^^^^^^^^^^^^^^^ // int workaroud due to ODR-use requirement in C++11 (dropped from C++17) isValidUntil(until) ? until : MEGA_INVALID_TIMESTAMP, byWeekDay ? unique_ptr( dynamic_cast(byWeekDay)->toByteList() ).get() : nullptr, byMonthDay ? unique_ptr( dynamic_cast(byMonthDay)->toByteList() ).get() :nullptr, byMonthWeekDay ? unique_ptr( dynamic_cast(byMonthWeekDay)->toByteMap() ).get() : nullptr)) {} MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const MegaScheduledRulesPrivate *rules) : mScheduledRules(rules->getSdkScheduledRules()) {} MegaScheduledRulesPrivate::MegaScheduledRulesPrivate(const ScheduledRules *rules) : mScheduledRules(rules->copy()) {} int MegaScheduledRulesPrivate::freq() const { return mScheduledRules->freq(); } int MegaScheduledRulesPrivate::interval() const { return mScheduledRules->interval(); } MegaTimeStamp MegaScheduledRulesPrivate::until() const { return mScheduledRules->until(); } /* * Data cannot be changed through the public interface and is only set via ctor. We abuse the * choke point of the following getters to set it instead of the various ctors. * * Probable redesign of MegaApi will end up with returning ownership of these pointers * and the temporal variables won't be needed */ const MegaIntegerList* MegaScheduledRulesPrivate::byWeekDay() const { const auto p = mScheduledRules->byWeekDay(); if (!mTransformedByWeekDay && p) { mTransformedByWeekDay = std::make_unique(*p); } return mTransformedByWeekDay.get(); } const MegaIntegerList* MegaScheduledRulesPrivate::byMonthDay() const { const auto p = mScheduledRules->byMonthDay(); if (!mTransformedByMonthDay && p) { mTransformedByMonthDay.reset(new MegaIntegerListPrivate(*p)); } return mTransformedByMonthDay.get(); } const MegaIntegerMap* MegaScheduledRulesPrivate::byMonthWeekDay() const { const auto p = mScheduledRules->byMonthWeekDay(); if (!mTransformedByMonthWeekDay && p) { mTransformedByMonthWeekDay.reset(new MegaIntegerMapPrivate(*p)); } return mTransformedByMonthWeekDay.get(); } unique_ptr MegaScheduledRulesPrivate::getSdkScheduledRules() const { return unique_ptr(mScheduledRules->copy()); } bool MegaScheduledRulesPrivate::isValidFreq(const int freq) { return ScheduledRules::isValidFreq(freq); } bool MegaScheduledRulesPrivate::isValidInterval(const int interval) { return ScheduledRules::isValidInterval(interval); } bool MegaScheduledRulesPrivate::isValidUntil(const m_time_t until) { return ScheduledRules::isValidUntil(until); } MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const MegaHandle chatid, const char* timezone, const MegaTimeStamp startDateTime, const MegaTimeStamp endDateTime, const char* title, const char* description, MegaHandle schedId, MegaHandle parentSchedId, MegaHandle organizerUserId, int cancelled, const char* attributes, MegaTimeStamp overrides, const MegaScheduledFlags* flags, const MegaScheduledRules* rules) : mScheduledMeeting(std::make_unique( chatid, timezone ? timezone : std::string(), startDateTime, endDateTime, title ? title : std::string(), description ? description : std::string(), organizerUserId, schedId, parentSchedId, cancelled, attributes ? attributes : std::string(), overrides, flags ? dynamic_cast(flags)->getSdkScheduledFlags().get() : nullptr, rules ? dynamic_cast(rules)->getSdkScheduledRules().get() : nullptr)) {} MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const MegaScheduledMeetingPrivate* sm) : mScheduledMeeting(sm->scheduledMeeting()->copy()) {} MegaScheduledMeetingPrivate::MegaScheduledMeetingPrivate(const ScheduledMeeting *scheduledMeeting) : mScheduledMeeting(scheduledMeeting->copy()) {} MegaHandle MegaScheduledMeetingPrivate::chatid() const { return mScheduledMeeting->chatid(); } MegaHandle MegaScheduledMeetingPrivate::schedId() const { return mScheduledMeeting->schedId(); } MegaHandle MegaScheduledMeetingPrivate::parentSchedId() const { return mScheduledMeeting->parentSchedId(); } MegaHandle MegaScheduledMeetingPrivate::organizerUserid() const { return mScheduledMeeting->organizerUserid(); } const char* MegaScheduledMeetingPrivate::timezone() const { return mScheduledMeeting->timezone().size() ? mScheduledMeeting->timezone().c_str() : nullptr; } MegaTimeStamp MegaScheduledMeetingPrivate::startDateTime() const { return mScheduledMeeting->startDateTime(); } MegaTimeStamp MegaScheduledMeetingPrivate::endDateTime() const { return mScheduledMeeting->endDateTime(); } const char* MegaScheduledMeetingPrivate::title() const { return mScheduledMeeting->title().size() ? mScheduledMeeting->title().c_str() : nullptr; } const char* MegaScheduledMeetingPrivate::description() const { return mScheduledMeeting->description().size() ? mScheduledMeeting->description().c_str() : nullptr; } const char* MegaScheduledMeetingPrivate::attributes() const { return mScheduledMeeting->attributes().size() ? mScheduledMeeting->attributes().c_str() : nullptr; } MegaTimeStamp MegaScheduledMeetingPrivate::overrides() const { return mScheduledMeeting->overrides(); } int MegaScheduledMeetingPrivate::cancelled() const { return mScheduledMeeting->cancelled(); } MegaScheduledFlags* MegaScheduledMeetingPrivate::flags() const { return mScheduledMeeting->flags() ? new MegaScheduledFlagsPrivate(mScheduledMeeting->flags()) : nullptr;} MegaScheduledRules* MegaScheduledMeetingPrivate::rules() const { return mScheduledMeeting->rules() ? new MegaScheduledRulesPrivate(mScheduledMeeting->rules()) : nullptr;} MegaScheduledMeetingListPrivate::MegaScheduledMeetingListPrivate() { } MegaScheduledMeetingListPrivate::MegaScheduledMeetingListPrivate(const MegaScheduledMeetingListPrivate& l) { mList.reserve(l.size()); for (unsigned long i = 0; i < l.size(); i++) { mList.emplace_back(l.at(i)->copy()); } } MegaScheduledMeetingListPrivate::~MegaScheduledMeetingListPrivate() { // all objects managed by unique_ptr's containted in mList will be deallocated when mList is destroyed } unsigned long MegaScheduledMeetingListPrivate::size() const { return static_cast(mList.size()); } MegaScheduledMeetingListPrivate* MegaScheduledMeetingListPrivate::copy() const { return new MegaScheduledMeetingListPrivate(*this); } MegaScheduledMeeting* MegaScheduledMeetingListPrivate::at(unsigned long i) const { return mList.at(i).get(); } MegaScheduledMeeting* MegaScheduledMeetingListPrivate::getBySchedId(handle h) const { auto it = std::find_if(mList.begin(), mList.end(), [h](const std::unique_ptr& sm) -> bool { return h == sm ->schedId(); }); return (it != mList.end()) ? it->get() : nullptr; } void MegaScheduledMeetingListPrivate::insert(MegaScheduledMeeting* sm) { mList.emplace_back(sm); } void MegaScheduledMeetingListPrivate::clear() { mList.clear(); } #endif MegaTransferDataPrivate::MegaTransferDataPrivate(TransferList *transferList, long long notificationNumber) { numDownloads = int(transferList->transfers[GET].size()); downloadTags.reserve(static_cast(numDownloads)); downloadPriorities.reserve(static_cast(numDownloads)); for (auto it = transferList->begin(GET); it != transferList->end(GET); it++) { Transfer *transfer = (*it); for (file_list::iterator fit = transfer->files.begin(); fit != transfer->files.end(); fit++) { File *file = (*fit); downloadTags.push_back(file->tag); downloadPriorities.push_back(transfer->priority); } } numDownloads = int(downloadTags.size()); numUploads = int(transferList->transfers[PUT].size()); uploadTags.reserve(static_cast(numUploads)); uploadPriorities.reserve(static_cast(numUploads)); for (auto it = transferList->begin(PUT); it != transferList->end(PUT); it++) { Transfer *transfer = (*it); for (file_list::iterator fit = transfer->files.begin(); fit != transfer->files.end(); fit++) { File *file = (*fit); uploadTags.push_back(file->tag); uploadPriorities.push_back(transfer->priority); } } numUploads = int(uploadTags.size()); this->notificationNumber = notificationNumber; } MegaTransferDataPrivate::MegaTransferDataPrivate(const MegaTransferDataPrivate *transferData) { this->numDownloads = transferData->numDownloads; this->numUploads = transferData->numUploads; this->downloadTags = transferData->downloadTags; this->uploadTags = transferData->uploadTags; this->downloadPriorities = transferData->downloadPriorities; this->uploadPriorities = transferData->uploadPriorities; this->notificationNumber = transferData->notificationNumber; } MegaTransferDataPrivate::~MegaTransferDataPrivate() { } MegaTransferData *MegaTransferDataPrivate::copy() const { return new MegaTransferDataPrivate(this); } int MegaTransferDataPrivate::getNumDownloads() const { return numDownloads; } int MegaTransferDataPrivate::getNumUploads() const { return numUploads; } int MegaTransferDataPrivate::getDownloadTag(int i) const { return downloadTags[static_cast(i)]; } int MegaTransferDataPrivate::getUploadTag(int i) const { return uploadTags[static_cast(i)]; } unsigned long long MegaTransferDataPrivate::getDownloadPriority(int i) const { return downloadPriorities[static_cast(i)]; } unsigned long long MegaTransferDataPrivate::getUploadPriority(int i) const { return uploadPriorities[static_cast(i)]; } long long MegaTransferDataPrivate::getNotificationNumber() const { return notificationNumber; } MegaSizeProcessor::MegaSizeProcessor() { totalBytes = 0; } bool MegaSizeProcessor::processMegaNode(MegaNode *node) { if (node->getType() == MegaNode::TYPE_FILE) { totalBytes += node->getSize(); } return true; } long long MegaSizeProcessor::getTotalBytes() { return totalBytes; } MegaEventPrivate::MegaEventPrivate(int atype) :type(atype) { } MegaEventPrivate::MegaEventPrivate(MegaEventPrivate *event) { this->type = event->getType(); this->setText(event->getText()); this->setNumber(event->getNumber()); this->setHandle(event->getHandle()); } MegaEventPrivate::~MegaEventPrivate() { delete [] text; } MegaEvent *MegaEventPrivate::copy() { return new MegaEventPrivate(this); } int MegaEventPrivate::getType() const { return type; } const char *MegaEventPrivate::getText() const { return text; } int64_t MegaEventPrivate::getNumber() const { return number; } void MegaEventPrivate::setText(const char* newText) { if (text) { delete[] text; } text = MegaApi::strdup(newText); } void MegaEventPrivate::setNumber(int64_t newNumber) { number = newNumber; } std::optional MegaEventPrivate::getNumber(const std::string& key) const { if (auto it = numberMap.find(key); it != numberMap.end()) { return it->second; } return std::nullopt; } void MegaEventPrivate::setNumber(const std::string& key, int64_t value) { numberMap[key] = value; } MegaHandle MegaEventPrivate::getHandle() const { return mHandle; } const char *MegaEventPrivate::getEventString() const { return MegaEventPrivate::getEventString(type); } const char *MegaEventPrivate::getEventString(int type) { switch (type) { case MegaEvent::EVENT_COMMIT_DB: return "EVENT_COMMIT_DB"; case MegaEvent::EVENT_ACCOUNT_CONFIRMATION: return "EVENT_ACCOUNT_CONFIRMATION"; case MegaEvent::EVENT_CONFIRM_USER_EMAIL: return "EVENT_CONFIRM_USER_EMAIL"; case MegaEvent::EVENT_DISCONNECT: return "EVENT_DISCONNECT"; case MegaEvent::EVENT_ACCOUNT_BLOCKED: return "EVENT_ACCOUNT_BLOCKED"; case MegaEvent::EVENT_STORAGE: return "EVENT_STORAGE"; case MegaEvent::EVENT_NODES_CURRENT: return "EVENT_NODES_CURRENT"; case MegaEvent::EVENT_MEDIA_INFO_READY: return "EVENT_MEDIA_INFO_READY"; case MegaEvent::EVENT_STORAGE_SUM_CHANGED: return "EVENT_STORAGE_SUM_CHANGED"; case MegaEvent::EVENT_BUSINESS_STATUS: return "BUSINESS_STATUS"; case MegaEvent::EVENT_KEY_MODIFIED: return "KEY_MODIFIED"; case MegaEvent::EVENT_MISC_FLAGS_READY: return "MISC_FLAGS_READY"; #ifdef ENABLE_SYNC case MegaEvent::EVENT_SYNCS_DISABLED: return "SYNCS_DISABLED"; case MegaEvent::EVENT_SYNCS_RESTORED: return "SYNCS_RESTORED"; #endif case MegaEvent::EVENT_REQSTAT_PROGRESS: return "REQSTAT_PROGRESS"; case MegaEvent::EVENT_RELOADING: return "RELOADING"; case MegaEvent::EVENT_FATAL_ERROR: return "FATAL_ERROR"; case MegaEvent::EVENT_UPGRADE_SECURITY: return "UPGRADE_SECURITY"; case MegaEvent::EVENT_DOWNGRADE_ATTACK: return "DOWNGRADE_ATTACK"; case MegaEvent::EVENT_CREDIT_CARD_EXPIRY: return "CREDIT_CARD_EXPIRY"; case MegaEvent::EVENT_NETWORK_ACTIVITY: return "NETWORK_ACTIVITY"; } return "UNKNOWN"; } std::string MegaEventPrivate::getValidDataToString() const { std::string msg; if (getText()) { msg.append(" text: ").append(getText()); } if (getNumber() >= 0) { msg.append(" number: ").append(std::to_string(getNumber())); } if (getHandle() != INVALID_HANDLE) { msg.append(" handle: ").append(Base64Str(getHandle())); } return msg; } void MegaEventPrivate::setHandle(const MegaHandle &handle) { mHandle = handle; } MegaHandleListPrivate::MegaHandleListPrivate() { } MegaHandleListPrivate::MegaHandleListPrivate(const MegaHandleListPrivate *hList) { mList = hList->mList; } MegaHandleListPrivate::MegaHandleListPrivate(const vector &handles) { mList = handles; } MegaHandleListPrivate::~MegaHandleListPrivate() { } MegaHandleList *MegaHandleListPrivate::copy() const { return new MegaHandleListPrivate(this); } MegaHandle MegaHandleListPrivate::get(unsigned int i) const { MegaHandle h = INVALID_HANDLE; if (i < mList.size()) { h = mList.at(i); } return h; } unsigned int MegaHandleListPrivate::size() const { return unsigned(mList.size()); } void MegaHandleListPrivate::addMegaHandle(MegaHandle h) { mList.push_back(h); } MegaIntegerListPrivate::MegaIntegerListPrivate(const vector& bytesList) { mIntegers.reserve(bytesList.size()); std::transform(bytesList.begin(), bytesList.end(), std::back_inserter(mIntegers), [](int8_t x) { return static_cast(x);}); } MegaIntegerListPrivate::MegaIntegerListPrivate(const vector& integerList) : mIntegers(integerList) { } MegaIntegerListPrivate::MegaIntegerListPrivate(const vector& bytesList) { mIntegers.reserve(bytesList.size()); std::transform(bytesList.begin(), bytesList.end(), std::back_inserter(mIntegers), [](uint32_t x) { return static_cast(x);}); } MegaIntegerListPrivate::MegaIntegerListPrivate() { } MegaIntegerListPrivate::~MegaIntegerListPrivate() { } MegaSmallIntVector* MegaIntegerListPrivate::toByteList() const { MegaSmallIntVector* bytesList= new MegaSmallIntVector(); bytesList->reserve(mIntegers.size()); std::transform(mIntegers.begin(), mIntegers.end(), std::back_inserter(*bytesList), [](int64_t x) { return static_cast(x);}); return bytesList; } MegaIntegerList* MegaIntegerListPrivate::copy() const { return new MegaIntegerListPrivate(mIntegers); } int64_t MegaIntegerListPrivate::get(int i) const { if (i >= static_cast(mIntegers.size())) { return -1; } return mIntegers.at(static_cast(i)); } void MegaIntegerListPrivate::add(long long i) { mIntegers.emplace_back(i); } int MegaIntegerListPrivate::size() const { return static_cast(mIntegers.size()); } const vector* MegaIntegerListPrivate::getList() const { return &mIntegers; } MegaChildrenListsPrivate::MegaChildrenListsPrivate(MegaChildrenLists *list) : folders(list->getFolderList()->copy()) , files(list->getFileList()->copy()) { } MegaChildrenListsPrivate::MegaChildrenListsPrivate(unique_ptr folderList, unique_ptr fileList) : folders(std::move(folderList)) , files(std::move(fileList)) { } MegaChildrenLists *MegaChildrenListsPrivate::copy() { return new MegaChildrenListsPrivate(this); } MegaNodeList *MegaChildrenListsPrivate::getFileList() { return files.get(); } MegaNodeList *MegaChildrenListsPrivate::getFolderList() { return folders.get(); } MegaChildrenListsPrivate::MegaChildrenListsPrivate() : folders(new MegaNodeListPrivate()) , files(new MegaNodeListPrivate()) { } MegaAchievementsDetails *MegaAchievementsDetailsPrivate::fromAchievementsDetails(AchievementsDetails *details) { return new MegaAchievementsDetailsPrivate(details); } MegaAchievementsDetailsPrivate::~MegaAchievementsDetailsPrivate() { } MegaAchievementsDetails *MegaAchievementsDetailsPrivate::copy() { return new MegaAchievementsDetailsPrivate(&details); } long long MegaAchievementsDetailsPrivate::getBaseStorage() { return details.permanent_size; } bool MegaAchievementsDetailsPrivate::isValidClass(int class_id) { return details.achievements.find(static_cast(class_id)) != details.achievements.end(); } long long MegaAchievementsDetailsPrivate::getClassStorage(int class_id) { achievements_map::iterator it = details.achievements.find(static_cast(class_id)); if (it != details.achievements.end()) { return it->second.storage; } return 0; } long long MegaAchievementsDetailsPrivate::getClassTransfer(int class_id) { achievements_map::iterator it = details.achievements.find(static_cast(class_id)); if (it != details.achievements.end()) { return it->second.transfer; } return 0; } int MegaAchievementsDetailsPrivate::getClassExpire(int class_id) { achievements_map::iterator it = details.achievements.find(static_cast(class_id)); if (it != details.achievements.end()) { return it->second.expire; } return 0; } unsigned int MegaAchievementsDetailsPrivate::getAwardsCount() { return unsigned(details.awards.size()); } int MegaAchievementsDetailsPrivate::getAwardClass(unsigned int index) { if (index < details.awards.size()) { return static_cast(details.awards.at(index).achievement_class); } return 0; } int MegaAchievementsDetailsPrivate::getAwardId(unsigned int index) { if (index < details.awards.size()) { return details.awards.at(index).award_id; } return 0; } int64_t MegaAchievementsDetailsPrivate::getAwardTimestamp(unsigned int index) { if (index < details.awards.size()) { return details.awards.at(index).ts; } return 0; } int64_t MegaAchievementsDetailsPrivate::getAwardExpirationTs(unsigned int index) { if (index < details.awards.size()) { return details.awards.at(index).expire; } return 0; } MegaStringList *MegaAchievementsDetailsPrivate::getAwardEmails(unsigned int index) { if (index < details.awards.size()) { if (details.awards.at(index).achievement_class == MEGA_ACHIEVEMENT_INVITE) { string_vector data; vector::iterator it = details.awards.at(index).emails_invited.begin(); while (it != details.awards.at(index).emails_invited.end()) { data.push_back(*it); it++; } return new MegaStringListPrivate(std::move(data)); } } return new MegaStringListPrivate(); } int MegaAchievementsDetailsPrivate::getRewardsCount() { return int(details.rewards.size()); } int MegaAchievementsDetailsPrivate::getRewardAwardId(unsigned int index) { if (index < details.rewards.size()) { return details.rewards.at(index).award_id; } return -1; } long long MegaAchievementsDetailsPrivate::getRewardStorage(unsigned int index) { if (index < details.rewards.size()) { return details.rewards.at(index).storage; } return 0; } long long MegaAchievementsDetailsPrivate::getRewardTransfer(unsigned int index) { if (index < details.rewards.size()) { return details.rewards.at(index).transfer; } return 0; } long long MegaAchievementsDetailsPrivate::getRewardStorageByAwardId(int award_id) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == award_id) { return itr->storage; } } return 0; } long long MegaAchievementsDetailsPrivate::getRewardTransferByAwardId(int award_id) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == award_id) { return itr->transfer; } } return 0; } int MegaAchievementsDetailsPrivate::getRewardExpire(unsigned int index) { if (index < details.rewards.size()) { return details.rewards.at(index).expire; } return 0; } long long MegaAchievementsDetailsPrivate::currentStorage() { long long total = 0; m_time_t ts = m_time(); for (vector::iterator it = details.awards.begin(); it != details.awards.end(); it++) { if (it->expire > ts || it->expire == 0) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == it->award_id) { total += itr->storage; } } } } return total; } long long MegaAchievementsDetailsPrivate::currentTransfer() { long long total = 0; m_time_t ts = m_time(); for (vector::iterator it = details.awards.begin(); it != details.awards.end(); it++) { if (it->expire > ts || it->expire == 0) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == it->award_id) { total += itr->transfer; } } } } return total; } long long MegaAchievementsDetailsPrivate::currentStorageReferrals() { long long total = 0; m_time_t ts = m_time(); for (vector::iterator it = details.awards.begin(); it != details.awards.end(); it++) { if ( (it->expire > ts) && (it->achievement_class == MEGA_ACHIEVEMENT_INVITE) ) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == it->award_id) { total += itr->storage; } } } } return total; } long long MegaAchievementsDetailsPrivate::currentTransferReferrals() { long long total = 0; m_time_t ts = m_time(); for (vector::iterator it = details.awards.begin(); it != details.awards.end(); it++) { if ( (it->expire > ts) && (it->achievement_class == MEGA_ACHIEVEMENT_INVITE) ) { for (vector::iterator itr = details.rewards.begin(); itr != details.rewards.end(); itr++) { if (itr->award_id == it->award_id) { total += itr->transfer; } } } } return total; } MegaAchievementsDetailsPrivate::MegaAchievementsDetailsPrivate(AchievementsDetails *details) { this->details = (*details); } MegaFolderInfoPrivate::MegaFolderInfoPrivate(int numFiles, int numFolders, int numVersions, long long currentSize, long long versionsSize) { this->numFiles = numFiles; this->numFolders = numFolders; this->numVersions = numVersions; this->currentSize = currentSize; this->versionsSize = versionsSize; } MegaFolderInfoPrivate::MegaFolderInfoPrivate(const MegaFolderInfoPrivate *folderData) { this->numFiles = folderData->getNumFiles(); this->numFolders = folderData->getNumFolders(); this->numVersions = folderData->getNumVersions(); this->currentSize = folderData->getCurrentSize(); this->versionsSize = folderData->getVersionsSize(); } MegaFolderInfoPrivate::~MegaFolderInfoPrivate() { } MegaFolderInfo *MegaFolderInfoPrivate::copy() const { return new MegaFolderInfoPrivate(this); } int MegaFolderInfoPrivate::getNumVersions() const { return numVersions; } int MegaFolderInfoPrivate::getNumFiles() const { return numFiles; } int MegaFolderInfoPrivate::getNumFolders() const { return numFolders; } long long MegaFolderInfoPrivate::getCurrentSize() const { return currentSize; } long long MegaFolderInfoPrivate::getVersionsSize() const { return versionsSize; } MegaTimeZoneDetailsPrivate::MegaTimeZoneDetailsPrivate(vector *timeZones, vector *timeZoneOffsets, int defaultTimeZone) { this->timeZones = *timeZones; this->timeZoneOffsets = *timeZoneOffsets; this->defaultTimeZone = defaultTimeZone; } MegaTimeZoneDetailsPrivate::MegaTimeZoneDetailsPrivate(const MegaTimeZoneDetailsPrivate *timeZoneDetails) { this->timeZones = timeZoneDetails->timeZones; this->timeZoneOffsets = timeZoneDetails->timeZoneOffsets; this->defaultTimeZone = timeZoneDetails->defaultTimeZone; } MegaTimeZoneDetailsPrivate::~MegaTimeZoneDetailsPrivate() { } MegaTimeZoneDetails *MegaTimeZoneDetailsPrivate::copy() const { return new MegaTimeZoneDetailsPrivate(this); } int MegaTimeZoneDetailsPrivate::getNumTimeZones() const { return int(timeZones.size()); } const char *MegaTimeZoneDetailsPrivate::getTimeZone(int index) const { if (index >= 0 && index < int(timeZones.size())) { return timeZones[static_cast(index)].c_str(); } return ""; } int MegaTimeZoneDetailsPrivate::getTimeOffset(int index) const { if (index >= 0 && index < int(timeZoneOffsets.size())) { return timeZoneOffsets[static_cast(index)]; } return 0; } int MegaTimeZoneDetailsPrivate::getDefault() const { return defaultTimeZone; } MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate(const string &settingsJSON) { JSON json; json.begin(settingsJSON.c_str() + 1); std::string name = json.getname(); while (!name.empty()) { std::string globalObject; json.storeobject(&globalObject); if (name == "GLOBAL") { JSON jsonGlobal; jsonGlobal.begin(globalObject.c_str() + 1); std::string globalSubsetting = jsonGlobal.getname(); while (!globalSubsetting.empty()) { if (globalSubsetting == "dnd") { if (jsonGlobal.isnumeric()) { mGlobalDND = jsonGlobal.getint(); } if (mGlobalDND < 0) { LOG_err << "Invalid format in GLOBAL.dnd notification settings"; mJsonInvalid = true; } } else if (globalSubsetting == "nsch") { std::string schedule; jsonGlobal.storeobject(&schedule); JSON scheduleJson; scheduleJson.begin(schedule.c_str() + 1); std::string scheduleSubTitle = scheduleJson.getname(); while (!scheduleSubTitle.empty()) { if (scheduleSubTitle == "tz") { scheduleJson.storeobject(&mGlobalScheduleTimezone); } else if (scheduleSubTitle == "start" && scheduleJson.isnumeric()) { mGlobalScheduleStart = scheduleJson.getint32(); } else if (scheduleSubTitle == "end" && scheduleJson.isnumeric()) { mGlobalScheduleEnd = scheduleJson.getint32(); } else { LOG_warn << "Unknown option in GLOBAL.nsch notification settings"; } scheduleSubTitle = scheduleJson.getname(); } if (mGlobalScheduleTimezone.empty() || mGlobalScheduleStart < 0 || mGlobalScheduleEnd < 0) { LOG_err << "Invalid format in GLOBAL.nsch notification settings"; mJsonInvalid = true; } } else { LOG_warn << "Unknown option in Global notification settings"; mJsonInvalid = true; } globalSubsetting = jsonGlobal.getname(); } } else if (name == "PCR") { JSON jsonContact; jsonContact.begin(globalObject.c_str() + 1); std::string subname = jsonContact.getname(); if (subname != "dnd") { LOG_warn << "Unknown option in PCR notification settings"; mJsonInvalid = true; } else if (jsonContact.isnumeric()) { mContactsDND = jsonContact.getint(); } else { LOG_err << "Invalid format in PCR.dnd notification settings"; mJsonInvalid = true; } } else if (name == "INSHARE") { JSON jsonShares; jsonShares.begin(globalObject.c_str() + 1); std::string subname = jsonShares.getname(); if (subname != "dnd") { LOG_warn << "Unknown option in SHARES notification settings"; mJsonInvalid = true; } else if (jsonShares.isnumeric()) { mSharesDND = jsonShares.getint(); } else { LOG_err << "Invalid format in SHARES.dnd notification settings"; mJsonInvalid = true; } } else if (name == "CHAT") { JSON jsonChats; jsonChats.begin(globalObject.c_str() + 1); std::string subname = jsonChats.getname(); if (subname != "dnd") { LOG_warn << "Unknown option in CHAT notification settings"; mJsonInvalid = true; } else if (jsonChats.isnumeric()) { mGlobalChatsDND = jsonChats.getint(); } else { LOG_err << "Invalid format in CHAT.dnd notification settings"; mJsonInvalid = true; } } else if (name.length() == 11) // settings for specific 'chatid' { MegaHandle chatid; if (Base64::atob(name.c_str(), (byte*)&chatid, MegaClient::CHATHANDLE) == MegaClient::CHATHANDLE) { JSON jsonChat; jsonChat.begin(globalObject.c_str() + 1); std::string subname = jsonChat.getname(); if (subname == "dnd" && jsonChat.isnumeric()) { mChatDND[chatid] = jsonChat.getint(); } else if (subname == "an" && jsonChat.isnumeric()) { bool alwaysNotify = jsonChat.getbool(); if (alwaysNotify) { mChatAlwaysNotify[chatid] = true; } } else { LOG_err << "Unknown option for chatid '" << name << "' in notification settings"; mJsonInvalid = true; } } else { LOG_warn << "Invalid format for chatid '" << name << "' in notification settings"; mJsonInvalid = true; } } else { LOG_err << "Unknown option in notification settings"; mJsonInvalid = true; } if (mJsonInvalid) { return; } name = json.getname(); } } MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate() { } MegaPushNotificationSettingsPrivate::MegaPushNotificationSettingsPrivate(const MegaPushNotificationSettingsPrivate *settings) { mGlobalDND = settings->mGlobalDND; mGlobalScheduleStart = settings->mGlobalScheduleStart; mGlobalScheduleEnd = settings->mGlobalScheduleEnd; mGlobalScheduleTimezone = settings->mGlobalScheduleTimezone; mChatDND = settings->mChatDND; mChatAlwaysNotify = settings->mChatAlwaysNotify; mContactsDND = settings->mContactsDND; mSharesDND = settings->mSharesDND; mGlobalChatsDND = settings->mGlobalChatsDND; } bool MegaPushNotificationSettingsPrivate::operator==( const MegaPushNotificationSettingsPrivate& other) const { return mGlobalDND == other.mGlobalDND && mGlobalScheduleStart == other.mGlobalScheduleStart && mGlobalScheduleEnd == other.mGlobalScheduleEnd && mGlobalScheduleTimezone == other.mGlobalScheduleTimezone && mChatDND == other.mChatDND && mChatAlwaysNotify == other.mChatAlwaysNotify && mContactsDND == other.mContactsDND && mSharesDND == other.mSharesDND && mGlobalChatsDND == other.mGlobalChatsDND && mJsonInvalid == other.mJsonInvalid; } string MegaPushNotificationSettingsPrivate::generateJson() const { std::string json; if ((mGlobalScheduleStart > -1 && mGlobalScheduleEnd == -1) || (mGlobalScheduleStart == -1 && mGlobalScheduleEnd > -1)) { LOG_warn << "Invalid notification settings for GLOBAL.nsch"; return json; } json = "{"; if (mGlobalDND > -1 || isGlobalScheduleEnabled()) { json.append("\"GLOBAL\":{"); if (isGlobalDndEnabled()) { json.append("\"dnd\":").append(std::to_string(mGlobalDND)); json.append(","); } if (isGlobalScheduleEnabled()) { json.append("\"nsch\":{\"start\":").append(std::to_string(mGlobalScheduleStart)); json.append(",\"end\":").append(std::to_string(mGlobalScheduleEnd)); json.append(",\"tz\":\"").append(mGlobalScheduleTimezone).append("\"}"); } if (json.at(json.length() - 1) == ',') // clear a tailing comma (not needed) { json.pop_back(); } json.append("},"); } if (mContactsDND > -1) { json.append("\"PCR\":{\"dnd\":").append(std::to_string(mContactsDND)).append("}"); json.append(","); } if (mSharesDND > -1) { json.append("\"INSHARE\":{\"dnd\":").append(std::to_string(mSharesDND)).append("}"); json.append(","); } if (isGlobalChatsDndEnabled()) { json.append("\"CHAT\":{\"dnd\":").append(std::to_string(mGlobalChatsDND)).append("}"); json.append(","); } char chatid[MegaClient::CHATHANDLE * 4 / 3 + 4]; std::map::const_iterator itDND; for (itDND = mChatDND.begin(); itDND != mChatDND.end(); itDND++) { if (!isChatAlwaysNotifyEnabled(itDND->first) && isChatDndEnabled(itDND->first)) { Base64::btoa((byte*)&(itDND->first), MegaClient::CHATHANDLE, chatid); json.append("\"").append(chatid).append("\":{"); json.append("\"dnd\":").append(std::to_string(itDND->second)).append("}"); json.append(","); } } std::map::const_iterator itAn; for (itAn = mChatAlwaysNotify.begin(); itAn != mChatAlwaysNotify.end(); itAn++) { assert(isChatAlwaysNotifyEnabled(itAn->first)); assert(!isChatDndEnabled(itAn->first)); if (isChatAlwaysNotifyEnabled(itAn->first)) { Base64::btoa((byte*)&(itAn->first), MegaClient::CHATHANDLE, chatid); json.append("\"").append(chatid).append("\":{"); json.append("\"an\":").append("1").append("}"); json.append(","); } } if (json.at(json.length() - 1) == ',') // clear a tailing comma (not needed) { json.pop_back(); } json.append("}"); return json; } bool MegaPushNotificationSettingsPrivate::isValid() const { return !mJsonInvalid; } MegaPushNotificationSettingsPrivate::~MegaPushNotificationSettingsPrivate() { } bool MegaPushNotificationSettingsPrivate::isGlobalDndEnabled() const { return (mGlobalDND == 0 || mGlobalDND > m_time(NULL)); } bool MegaPushNotificationSettingsPrivate::isGlobalChatsDndEnabled() const { return (mGlobalChatsDND == 0 || mGlobalChatsDND > m_time(NULL)); } int64_t MegaPushNotificationSettingsPrivate::getGlobalDnd() const { return mGlobalDND; } int64_t MegaPushNotificationSettingsPrivate::getGlobalChatsDnd() const { return mGlobalChatsDND; } bool MegaPushNotificationSettingsPrivate::isGlobalScheduleEnabled() const { return (mGlobalScheduleStart != -1 && mGlobalScheduleEnd != -1 && !mGlobalScheduleTimezone.empty()); } int MegaPushNotificationSettingsPrivate::getGlobalScheduleStart() const { return mGlobalScheduleStart; } int MegaPushNotificationSettingsPrivate::getGlobalScheduleEnd() const { return mGlobalScheduleEnd; } const char *mega::MegaPushNotificationSettingsPrivate::getGlobalScheduleTimezone() const { return MegaApi::strdup(mGlobalScheduleTimezone.c_str()); } bool MegaPushNotificationSettingsPrivate::isChatDndEnabled(MegaHandle chatid) const { std::map::const_iterator it = mChatDND.find(chatid); return (it != mChatDND.end() && (it->second == 0 || it->second > m_time(NULL))); } int64_t MegaPushNotificationSettingsPrivate::getChatDnd(MegaHandle chatid) const { std::map::const_iterator it = mChatDND.find(chatid); if (it != mChatDND.end()) { assert(it->second != -1); return it->second; } return -1; } bool MegaPushNotificationSettingsPrivate::isChatAlwaysNotifyEnabled(MegaHandle chatid) const { std::map::const_iterator it = mChatAlwaysNotify.find(chatid); return (it != mChatAlwaysNotify.end() && it->second); } bool MegaPushNotificationSettingsPrivate::isContactsEnabled() const { return (mContactsDND == -1 || (mContactsDND > 0 && mContactsDND < m_time(NULL))); } bool MegaPushNotificationSettingsPrivate::isSharesEnabled() const { return (mSharesDND == -1 || (mSharesDND > 0 && mSharesDND < m_time(NULL))); } MegaPushNotificationSettings *MegaPushNotificationSettingsPrivate::copy() const { return new MegaPushNotificationSettingsPrivate(this); } void MegaPushNotificationSettingsPrivate::enableGlobal(bool enable) { if (!isGlobalDndEnabled() == enable) { return; } mGlobalDND = enable ? -1 : 0; } void MegaPushNotificationSettingsPrivate::setGlobalDnd(int64_t timestamp) { assert(timestamp > 0); if (isGlobalDndEnabled()) { LOG_warn << "setGlobalDnd(): global notifications are currently disabled." " Setting a new time period for DND mode"; } mGlobalDND = timestamp; } void MegaPushNotificationSettingsPrivate::disableGlobalDnd() { if (isGlobalDndEnabled()) { LOG_warn << "disableGlobalDnd(): global notifications were disabled. Now are enabled"; } mGlobalDND = -1; } void MegaPushNotificationSettingsPrivate::setGlobalSchedule(int start, int end, const char *timezone) { if (start <= -1 || end <= -1 || !timezone || !timezone[0] || start == end) { LOG_warn << "setGlobalSchedule(): wrong arguments"; assert(false); return; } mGlobalScheduleStart = start; mGlobalScheduleEnd = end; mGlobalScheduleTimezone.assign(timezone); } void MegaPushNotificationSettingsPrivate::disableGlobalSchedule() { mGlobalScheduleStart = -1; mGlobalScheduleEnd = -1; mGlobalScheduleTimezone.clear(); } void MegaPushNotificationSettingsPrivate::enableChat(MegaHandle chatid, bool enable) { assert(!ISUNDEF(chatid)); if (!isChatDndEnabled(chatid) == enable) { return; } if (enable) { mChatDND.erase(chatid); } else // disable { mChatDND[chatid] = 0; if (isChatAlwaysNotifyEnabled(chatid)) { LOG_warn << "enableChat(): always notify was enabled. Now is disabled"; enableChatAlwaysNotify(chatid, false); } } } void MegaPushNotificationSettingsPrivate::setChatDnd(MegaHandle chatid, int64_t timestamp) { assert(timestamp > 0); assert(!ISUNDEF(chatid)); if (isChatAlwaysNotifyEnabled(chatid)) { LOG_warn << "setChatDnd(): always notify was enabled. Now is disabled"; enableChatAlwaysNotify(chatid, false); } mChatDND[chatid] = timestamp; } void MegaPushNotificationSettingsPrivate::setGlobalChatsDnd(int64_t timestamp) { assert(timestamp > 0); if (isGlobalChatsDndEnabled()) { LOG_warn << "setChatsDnd(): global chats notifications are currently disabled." " Setting a new time period for chats DND mode"; } mGlobalChatsDND = timestamp; } void MegaPushNotificationSettingsPrivate::enableChatAlwaysNotify(MegaHandle chatid, bool enable) { assert(!ISUNDEF(chatid)); if (enable) { if (isChatDndEnabled(chatid)) { LOG_warn << "enableChatAlwaysNotify(): notifications are now disabled, DND mode is enabled"; enableChat(chatid, true); } mChatAlwaysNotify[chatid] = 1; } else { mChatAlwaysNotify.erase(chatid); } } void MegaPushNotificationSettingsPrivate::enableContacts(bool enable) { mContactsDND = enable ? -1 : 0; } void MegaPushNotificationSettingsPrivate::enableShares(bool enable) { mSharesDND = enable ? -1 : 0; } void MegaPushNotificationSettingsPrivate::enableChats(bool enable) { mGlobalChatsDND = enable ? -1 : 0; } MegaCancelTokenPrivate::MegaCancelTokenPrivate() { } MegaCancelTokenPrivate::MegaCancelTokenPrivate(CancelToken t) : cancelFlag(t) { } void MegaCancelTokenPrivate::cancel() { cancelFlag.cancel(); } bool MegaCancelTokenPrivate::isCancelled() const { return cancelFlag.isCancelled(); } MegaStringList* MegaVpnClusterPrivate::getDns() const { MegaStringListPrivate* dns = new MegaStringListPrivate(mCluster.getDns()); return dns; } MegaStringList* MegaVpnClusterPrivate::getAdBlockingDns() const { auto* adBlockingDns{new MegaStringListPrivate(mCluster.getAdBlockingDns())}; return adBlockingDns; } MegaIntegerListPrivate* MegaVpnClusterMapPrivate::getKeys() const { MegaIntegerListPrivate* keys = new MegaIntegerListPrivate(); for (const auto& entry: mClusters) { keys->add(entry.first); } return keys; } MegaVpnClusterPrivate* MegaVpnClusterMapPrivate::get(int64_t key) const { const auto it = mClusters.find(static_cast(key)); return it == mClusters.end() ? nullptr : new MegaVpnClusterPrivate(it->second); } MegaVpnClusterMapPrivate* MegaVpnRegionPrivate::getClusters() const { return new MegaVpnClusterMapPrivate(mRegion.getClusters()); } MegaVpnRegionListPrivate::MegaVpnRegionListPrivate(const vector& regions) { for (const auto& r: regions) { mRegions.push_back(r); } } /* MegaVpnCredentialsPrivate BEGIN */ MegaVpnCredentialsPrivate::MegaVpnCredentialsPrivate( MapSlotIDToCredentialInfo&& mapSlotIDToCredentialInfo, MapClusterPublicKeys&& mapClusterPubKeys, std::vector&& vpnRegions): mMapSlotIDToCredentialInfo(std::move(mapSlotIDToCredentialInfo)), mMapClusterPubKeys(std::move(mapClusterPubKeys)), mVpnRegions(std::move(vpnRegions)) {} MegaVpnCredentialsPrivate::MegaVpnCredentialsPrivate(const MegaVpnCredentialsPrivate& other): mMapSlotIDToCredentialInfo{other.mMapSlotIDToCredentialInfo}, mMapClusterPubKeys{other.mMapClusterPubKeys}, mVpnRegions{other.mVpnRegions} {} MegaIntegerList* MegaVpnCredentialsPrivate::getSlotIDs() const { vector slotIDs; for (auto& it : mMapSlotIDToCredentialInfo) { slotIDs.emplace_back(it.first); } return new MegaIntegerListPrivate(std::move(slotIDs)); } MegaStringList* MegaVpnCredentialsPrivate::getVpnRegions() const { MegaStringList* regions = MegaStringList::createInstance(); for (const auto& r : mVpnRegions) { regions->add(r.getName().c_str()); } return regions; } MegaVpnRegionListPrivate* MegaVpnCredentialsPrivate::getVpnRegionsDetailed() const { return new MegaVpnRegionListPrivate(mVpnRegions); } const char* MegaVpnCredentialsPrivate::getIPv4(int slotID) const { auto slotInfo = mMapSlotIDToCredentialInfo.find(slotID); if (slotInfo != mMapSlotIDToCredentialInfo.end()) { auto& credentialInfo = slotInfo->second; return credentialInfo.ipv4.c_str(); } return nullptr; } const char* MegaVpnCredentialsPrivate::getIPv6(int slotID) const { auto slotInfo = mMapSlotIDToCredentialInfo.find(slotID); if (slotInfo != mMapSlotIDToCredentialInfo.end()) { auto& credentialInfo = slotInfo->second; return credentialInfo.ipv6.c_str(); } return nullptr; } const char* MegaVpnCredentialsPrivate::getDeviceID(int slotID) const { auto slotInfo = mMapSlotIDToCredentialInfo.find(slotID); if (slotInfo != mMapSlotIDToCredentialInfo.end()) { auto& credentialInfo = slotInfo->second; return credentialInfo.deviceID.c_str(); } return nullptr; } int MegaVpnCredentialsPrivate::getClusterID(int slotID) const { auto slotInfo = mMapSlotIDToCredentialInfo.find(slotID); if (slotInfo != mMapSlotIDToCredentialInfo.end()) { auto& credentialInfo = slotInfo->second; return credentialInfo.clusterID; } return -1; } const char* MegaVpnCredentialsPrivate::getClusterPublicKey(int clusterID) const { auto pubKeyForClusterID = mMapClusterPubKeys.find(clusterID); if (pubKeyForClusterID != mMapClusterPubKeys.end()) { return pubKeyForClusterID->second.c_str(); } return nullptr; } MegaVpnCredentials* MegaVpnCredentialsPrivate::copy() const { return new MegaVpnCredentialsPrivate(*this); } /* MegaVpnCredentials END */ MegaNodeTreePrivate::MegaNodeTreePrivate(const MegaNodeTree* nodeTreeChild, const std::string& name, const std::string& s4AttributeValue, const MegaCompleteUploadData* completeUploadData, MegaHandle sourceHandle, MegaHandle nodeHandle): mName{name}, mS4AttributeValue{s4AttributeValue}, mSourceHandle{sourceHandle}, mNodeHandle{nodeHandle} { if (nodeTreeChild) { mNodeTreeChild.reset(nodeTreeChild->copy()); } if (completeUploadData) { mCompleteUploadData.reset(completeUploadData->copy()); } } MegaNetworkConnectivityTestResultsPrivate* MegaNetworkConnectivityTestResultsPrivate::copy() const { return new MegaNetworkConnectivityTestResultsPrivate(mIPv4, mIPv4DNS, mIPv6, mIPv6DNS); } MegaNodeTree* MegaNodeTreePrivate::getNodeTreeChild() const { return mNodeTreeChild.get(); } const std::string& MegaNodeTreePrivate::getName() const { return mName; } const std::string& MegaNodeTreePrivate::getS4AttributeValue() const { return mS4AttributeValue; } const MegaCompleteUploadData* MegaNodeTreePrivate::getCompleteUploadData() const { return mCompleteUploadData.get(); } MegaHandle MegaNodeTreePrivate::getNodeHandle() const { return mNodeHandle; } void MegaNodeTreePrivate::setNodeHandle(const MegaHandle& nodeHandle) { mNodeHandle = nodeHandle; } MegaNodeTree* MegaNodeTreePrivate::copy() const { MegaNodeTreePrivate* megaNodeTreeCopy = new MegaNodeTreePrivate( mNodeTreeChild.get(), mName, mS4AttributeValue, mCompleteUploadData.get(), mSourceHandle, mNodeHandle ); return megaNodeTreeCopy; } MegaCompleteUploadDataPrivate::MegaCompleteUploadDataPrivate(const std::string& fingerprint, const std::string& string64UploadToken, const std::string& string64FileKey): mFingerprint{fingerprint}, mString64UploadToken{string64UploadToken}, mString64FileKey{string64FileKey} {} const std::string& MegaCompleteUploadDataPrivate::getFingerprint() const { return mFingerprint; } const std::string& MegaCompleteUploadDataPrivate::getString64UploadToken() const { return mString64UploadToken; } const std::string& MegaCompleteUploadDataPrivate::getString64FileKey() const { return mString64FileKey; } MegaCompleteUploadData* MegaCompleteUploadDataPrivate::copy() const { return new MegaCompleteUploadDataPrivate(*this); } MegaFuseExecutorFlagsPrivate::MegaFuseExecutorFlagsPrivate(common::TaskExecutorFlags& flags) : MegaFuseExecutorFlags() , mFlags(flags) { } MegaStringList* MegaNotificationPrivate::getRenderModes() const { if (mNotification.renderModes.empty()) { return nullptr; } MegaStringList* modes = MegaStringList::createInstance(); for (const auto& m: mNotification.renderModes) { modes->add(m.first.c_str()); } return modes; } MegaStringMap* MegaNotificationPrivate::getRenderModeFields(const char* mode) const { if (!mode || mNotification.renderModes.empty()) { return nullptr; } for (const auto& m: mNotification.renderModes) { if (m.first == mode) { return new MegaStringMapPrivate(&m.second); } } return nullptr; } size_t MegaFuseExecutorFlagsPrivate::getMinThreadCount() const { return mFlags.mMinWorkers; } size_t MegaFuseExecutorFlagsPrivate::getMaxThreadCount() const { return mFlags.mMaxWorkers; } size_t MegaFuseExecutorFlagsPrivate::getMaxThreadIdleTime() const { return static_cast(mFlags.mIdleTime.count()); } bool MegaFuseExecutorFlagsPrivate::setMaxThreadCount(size_t max) { if (!max) return false; mFlags.mMaxWorkers = max; return true; } void MegaFuseExecutorFlagsPrivate::setMinThreadCount(size_t min) { mFlags.mMinWorkers = min; } void MegaFuseExecutorFlagsPrivate::setMaxThreadIdleTime(size_t max) { mFlags.mIdleTime = std::chrono::seconds(max); } MegaFuseInodeCacheFlagsPrivate::MegaFuseInodeCacheFlagsPrivate(fuse::InodeCacheFlags& flags) : MegaFuseInodeCacheFlags() , mFlags(flags) { } size_t MegaFuseInodeCacheFlagsPrivate::getCleanAgeThreshold() const { return static_cast(mFlags.mCleanAgeThreshold.count()); } size_t MegaFuseInodeCacheFlagsPrivate::getCleanInterval() const { return static_cast(mFlags.mCleanInterval.count()); } size_t MegaFuseInodeCacheFlagsPrivate::getCleanSizeThreshold() const { return mFlags.mCleanSizeThreshold; } size_t MegaFuseInodeCacheFlagsPrivate::getMaxSize() const { return mFlags.mMaxSize; } void MegaFuseInodeCacheFlagsPrivate::setCleanAgeThreshold(std::size_t seconds) { mFlags.mCleanAgeThreshold = std::chrono::seconds(seconds); } void MegaFuseInodeCacheFlagsPrivate::setCleanInterval(std::size_t seconds) { mFlags.mCleanInterval = std::chrono::seconds(seconds); } void MegaFuseInodeCacheFlagsPrivate::setCleanSizeThreshold(std::size_t size) { mFlags.mCleanSizeThreshold = size; } void MegaFuseInodeCacheFlagsPrivate::setMaxSize(std::size_t size) { mFlags.mMaxSize = size; } MegaFuseFlagsPrivate::MegaFuseFlagsPrivate(const fuse::ServiceFlags& flags) : MegaFuseFlags() , mFlags(flags) , mInodeCacheFlags(mFlags.mInodeCacheFlags) , mMountExecutorFlags(mFlags.mMountExecutorFlags) , mSubsystemExecutorFlags(mFlags.mServiceExecutorFlags) { } MegaFuseFlags* MegaFuseFlagsPrivate::copy() const { return new MegaFuseFlagsPrivate(mFlags); } const fuse::ServiceFlags& MegaFuseFlagsPrivate::getFlags() const { return mFlags; } size_t MegaFuseFlagsPrivate::getFlushDelay() const { return static_cast(mFlags.mFlushDelay.count()); } int MegaFuseFlagsPrivate::getLogLevel() const { return static_cast(mFlags.mLogLevel); } int MegaFuseFlagsPrivate::getFileExplorerView() const { return static_cast(mFlags.mFileExplorerView); } MegaFuseInodeCacheFlags* MegaFuseFlagsPrivate::getInodeCacheFlags() { return &mInodeCacheFlags; } MegaFuseExecutorFlags* MegaFuseFlagsPrivate::getMountExecutorFlags() { return &mMountExecutorFlags; } MegaFuseExecutorFlags* MegaFuseFlagsPrivate::getSubsystemExecutorFlags() { return &mSubsystemExecutorFlags; } void MegaFuseFlagsPrivate::setFlushDelay(size_t seconds) { mFlags.mFlushDelay = std::chrono::seconds(seconds); } void MegaFuseFlagsPrivate::setLogLevel(int level) { mFlags.mLogLevel = static_cast(level); } void MegaFuseFlagsPrivate::setFileExplorerView(int view) { mFlags.mFileExplorerView = static_cast(view); } MegaMountPrivate::MegaMountPrivate() : MegaMount() , mFlags(std::make_unique()) , mHandle(UNDEF) , mPath() { } MegaMountPrivate::MegaMountPrivate(const fuse::MountInfo& info) : MegaMount() , mFlags(std::make_unique(info.mFlags)) , mHandle(info.mHandle.as8byte()) , mPath(info.mPath.toPath(false)) { } MegaMountPrivate::MegaMountPrivate(const MegaMountPrivate& other) : MegaMount(other) , mFlags(other.mFlags->copy()) , mHandle(other.mHandle) , mPath(other.mPath) { } fuse::MountInfo MegaMountPrivate::asInfo() const { fuse::MountInfo info; info.mFlags = static_cast(*mFlags).getFlags(); info.mHandle.set6byte(mHandle); if (!mPath.empty()) { info.mPath = LocalPath::fromAbsolutePath(mPath); } return info; } MegaMount* MegaMountPrivate::copy() const { return new MegaMountPrivate(*this); } MegaMountFlags* MegaMountPrivate::getFlags() const { return mFlags.get(); } MegaHandle MegaMountPrivate::getHandle() const { return mHandle; } const char* MegaMountPrivate::getPath() const { return mPath.c_str(); } void MegaMountPrivate::setFlags(const MegaMountFlags* flags) { assert(flags); mFlags.reset(flags->copy()); } void MegaMountPrivate::setHandle(MegaHandle handle) { mHandle = handle; } void MegaMountPrivate::setPath(const char* path) { assert(path); mPath = path; } MegaMountFlagsPrivate::MegaMountFlagsPrivate(const fuse::MountFlags& flags) : MegaMountFlags() , mFlags(flags) { } MegaMountFlags* MegaMountFlagsPrivate::copy() const { return new MegaMountFlagsPrivate(*this); } bool MegaMountFlagsPrivate::getEnableAtStartup() const { return mFlags.mEnableAtStartup; } const fuse::MountFlags& MegaMountFlagsPrivate::getFlags() const { return mFlags; } const char* MegaMountFlagsPrivate::getName() const { return mFlags.mName.c_str(); } bool MegaMountFlagsPrivate::getPersistent() const { return mFlags.mPersistent; } bool MegaMountFlagsPrivate::getReadOnly() const { return mFlags.mReadOnly; } void MegaMountFlagsPrivate::setEnableAtStartup(bool enable) { mFlags.mEnableAtStartup = enable; mFlags.mPersistent |= true; } void MegaMountFlagsPrivate::setName(const char* name) { assert(name); mFlags.mName = name; } void MegaMountFlagsPrivate::setPersistent(bool persistent) { mFlags.mEnableAtStartup &= persistent; mFlags.mPersistent = persistent; } void MegaMountFlagsPrivate::setReadOnly(bool readOnly) { mFlags.mReadOnly = readOnly; } MegaMountListPrivate::MegaMountListPrivate(fuse::MountInfoVector&& mounts) : MegaMountList() , mMounts() { for (auto& m : mounts) { auto mount = std::make_unique(std::move(m)); mMounts.emplace_back(std::move(mount)); } } MegaMountListPrivate::MegaMountListPrivate(const MegaMountListPrivate& other) : MegaMountList() , mMounts() { mMounts.reserve(other.mMounts.size()); for (const auto& m : other.mMounts) mMounts.emplace_back(m->copy()); } MegaMountList* MegaMountListPrivate::copy() const { return new MegaMountListPrivate(*this); } const MegaMount* MegaMountListPrivate::get(size_t index) const { if (index < mMounts.size()) return mMounts[index].get(); return nullptr; } size_t MegaMountListPrivate::size() const { return mMounts.size(); } MegaCancelSubscriptionReasonPrivate::MegaCancelSubscriptionReasonPrivate(const char* text, const char* position): mText{text ? text : ""}, mPosition{position ? position : ""} {} const char* MegaCancelSubscriptionReasonPrivate::text() const { return mText.c_str(); } const char* MegaCancelSubscriptionReasonPrivate::position() const { return mPosition.c_str(); } MegaCancelSubscriptionReasonPrivate* MegaCancelSubscriptionReasonPrivate::copy() const { return new MegaCancelSubscriptionReasonPrivate(*this); } void MegaCancelSubscriptionReasonListPrivate::add(const MegaCancelSubscriptionReason* reason) { mReasons.emplace_back(reason->copy()); } const MegaCancelSubscriptionReason* MegaCancelSubscriptionReasonListPrivate::get(size_t index) const { return index >= mReasons.size() ? nullptr : mReasons[index].get(); } size_t MegaCancelSubscriptionReasonListPrivate::size() const { return mReasons.size(); } MegaCancelSubscriptionReasonListPrivate* MegaCancelSubscriptionReasonListPrivate::copy() const { return new MegaCancelSubscriptionReasonListPrivate(*this); } MegaDiscountCodePrivate::MegaDiscountCodePrivate(DiscountCode&& discountCode): mDiscountCode{std::move(discountCode)} {} MegaDiscountCode* MegaDiscountCodePrivate::copy() const { return new MegaDiscountCodePrivate(*this); } const char* MegaDiscountCodePrivate::getCode() const { return mDiscountCode.alfanumDiscountCode.c_str(); } int MegaDiscountCodePrivate::getItem() const { return mDiscountCode.item; } int MegaDiscountCodePrivate::getAccountLevel() const { return mDiscountCode.accountLevel; } int MegaDiscountCodePrivate::getMonths() const { return mDiscountCode.numMonths; } int MegaDiscountCodePrivate::getPercentageDiscount() const { return mDiscountCode.percentageDiscount; } int MegaDiscountCodePrivate::getBehaviorType() const { return mDiscountCode.behaviourType; } MegaDiscountCodeListPrivate::MegaDiscountCodeListPrivate(): mDiscountCodes{} {} MegaDiscountCodeList* MegaDiscountCodeListPrivate::copy() const { return new MegaDiscountCodeListPrivate(*this); } int MegaDiscountCodeListPrivate::size() const { return static_cast(mDiscountCodes.size()); } const MegaDiscountCode* MegaDiscountCodeListPrivate::get(int i) const { if (i < 0 || i >= static_cast(mDiscountCodes.size())) { return nullptr; } return &mDiscountCodes[static_cast(i)]; } void MegaDiscountCodeListPrivate::add(MegaDiscountCodePrivate&& discountCode) { mDiscountCodes.emplace_back(std::move(discountCode)); } MegaDiscountCodeInfoPrivate::MegaDiscountCodeInfoPrivate( DiscountCodeInfoExtended&& discountCodeInfo): mDiscountCodeInfo{std::move(discountCodeInfo)} {} MegaDiscountCodeInfo* MegaDiscountCodeInfoPrivate::copy() const { return new MegaDiscountCodeInfoPrivate(*this); } const char* MegaDiscountCodeInfoPrivate::getCode() const { return mDiscountCodeInfo.alfanumDiscountCode.c_str(); } int MegaDiscountCodeInfoPrivate::getItem() const { return mDiscountCodeInfo.item; } int MegaDiscountCodeInfoPrivate::getAccountLevel() const { return mDiscountCodeInfo.accountLevel; } int MegaDiscountCodeInfoPrivate::getMonths() const { return mDiscountCodeInfo.numMonths; } int MegaDiscountCodeInfoPrivate::getPercentageDiscount() const { return mDiscountCodeInfo.percentageDiscount; } int MegaDiscountCodeInfoPrivate::getBehaviorType() const { return mDiscountCodeInfo.behaviourType; } int MegaDiscountCodeInfoPrivate::getExpiry() const { return mDiscountCodeInfo.expiry; } int MegaDiscountCodeInfoPrivate::getCompulsorySubscription() const { return mDiscountCodeInfo.compulsorySubscription; } int MegaDiscountCodeInfoPrivate::getMultiDiscount() const { return mDiscountCodeInfo.multiDiscount; } int MegaDiscountCodeInfoPrivate::getTaxValue() const { return mDiscountCodeInfo.txva; } MegaStringIntegerMap* MegaDiscountCodeInfoPrivate::getFeatures() const { MegaStringIntegerMapPrivate* featuresMap = new MegaStringIntegerMapPrivate(); std::for_each(mDiscountCodeInfo.features.begin(), mDiscountCodeInfo.features.end(), [featuresMap](const std::pair& f) { featuresMap->set(f.first.c_str(), f.second); }); return featuresMap; } bool MegaDiscountCodeInfoPrivate::isTaxExempt() const { return (mDiscountCodeInfo.taxExempt & DiscountCodeInfoExtended::TaxFlags::TAX_EXEMPT) != 0; } bool MegaDiscountCodeInfoPrivate::isTaxAppliedOnTop() const { return (mDiscountCodeInfo.taxExempt & DiscountCodeInfoExtended::TaxFlags::TAX_ON_TOP) != 0; } int MegaDiscountCodeInfoPrivate::getTaxRate() const { return mDiscountCodeInfo.taxRate; } const char* MegaDiscountCodeInfoPrivate::getTaxName() const { return mDiscountCodeInfo.taxName.c_str(); } const char* MegaDiscountCodeInfoPrivate::getTaxCountry() const { return mDiscountCodeInfo.taxCountry.c_str(); } double MegaDiscountCodeInfoPrivate::getEuroTotalPrice() const { return mDiscountCodeInfo.euroTotalPrice; } double MegaDiscountCodeInfoPrivate::getEuroDiscountAmount() const { return mDiscountCodeInfo.euroDiscountAmount; } double MegaDiscountCodeInfoPrivate::getEuroDiscountedTotalPrice() const { return mDiscountCodeInfo.euroDiscountedTotalPrice; } double MegaDiscountCodeInfoPrivate::getEuroDiscountedMonthlyPrice() const { return mDiscountCodeInfo.euroDiscountedMonthlyPrice; } double MegaDiscountCodeInfoPrivate::getEuroTotalPriceNet() const { return mDiscountCodeInfo.euroTotalPriceNet; } double MegaDiscountCodeInfoPrivate::getEuroDiscountAmountNet() const { return mDiscountCodeInfo.euroDiscountAmountNet; } double MegaDiscountCodeInfoPrivate::getEuroDiscountedTotalPriceNet() const { return mDiscountCodeInfo.euroDiscountedTotalPriceNet; } double MegaDiscountCodeInfoPrivate::getEuroDiscountedMonthlyPriceNet() const { return mDiscountCodeInfo.euroDiscountedMonthlyPriceNet; } const char* MegaDiscountCodeInfoPrivate::getLocalCurrencyCode() const { return mDiscountCodeInfo.localCurrencyCode.c_str(); } const char* MegaDiscountCodeInfoPrivate::getLocalCurrencySymbol() const { return mDiscountCodeInfo.localCurrencySymbol.c_str(); } double MegaDiscountCodeInfoPrivate::getLocalTotalPrice() const { return mDiscountCodeInfo.localTotalPrice; } double MegaDiscountCodeInfoPrivate::getLocalDiscountAmount() const { return mDiscountCodeInfo.localDiscountAmount; } double MegaDiscountCodeInfoPrivate::getLocalDiscountedTotalPrice() const { return mDiscountCodeInfo.localDiscountedTotalPrice; } double MegaDiscountCodeInfoPrivate::getLocalDiscountedMonthlyPrice() const { return mDiscountCodeInfo.localDiscountedMonthlyPrice; } double MegaDiscountCodeInfoPrivate::getLocalTotalPriceNet() const { return mDiscountCodeInfo.localTotalPriceNet; } double MegaDiscountCodeInfoPrivate::getLocalDiscountAmountNet() const { return mDiscountCodeInfo.localDiscountAmountNet; } double MegaDiscountCodeInfoPrivate::getLocalDiscountedTotalPriceNet() const { return mDiscountCodeInfo.localDiscountedTotalPriceNet; } double MegaDiscountCodeInfoPrivate::getLocalDiscountedMonthlyPriceNet() const { return mDiscountCodeInfo.localDiscountedMonthlyPriceNet; } std::unique_ptr createFSA() { auto fsaccess = std::unique_ptr{new FSACCESS_CLASS}; #ifdef __ANDROID__ if (!AndroidFileSystemAccess::isFileWrapperActive(fsaccess.get())) { LOG_verbose << "[mega::createFSA] JNI FileWrapper not present. Creating " "LinuxFileSystemAccess instead of AndroidFileSystemAccess"; fsaccess.reset(new LinuxFileSystemAccess); } #endif return fsaccess; } void MegaApiImpl::notify_network_activity(int networkActivityChannel, int networkActivityType, int code) { MegaEventPrivate* event = new MegaEventPrivate(MegaEvent::EVENT_NETWORK_ACTIVITY); event->setNumber("channel", networkActivityChannel); event->setNumber("activity_type", networkActivityType); event->setNumber("error_code", code); fireOnEvent(event); } } // namespace mega sdk-10.11.0/src/megaapi_impl_sync.cpp000066400000000000000000000201321516266226600174200ustar00rootroot00000000000000/** * @file megaapi_impl_sync.cpp * @brief Private implementation of the intermediate layer for sync-related functionality. */ #ifdef ENABLE_SYNC #include "megaapi_impl.h" namespace { using namespace mega; /** * @brief Helper to populate the related request fields corresponding to the * MegaRequestSyncFolderParams. * * @see MegaApiImpl::syncFolder() * @see MegaApiImpl::prevalidateSyncFolder() */ void populateRequest_syncFolder(MegaRequestPrivate& request, MegaRequestSyncFolderParams&& params) { request.setNodeHandle(params.megaHandle); if (!params.localFolder.empty()) request.setFile(params.localFolder.c_str()); // Use provided name if available, or if it's a backup, even an empty name if (!params.name.empty() || params.type == SyncConfig::TYPE_BACKUP) { request.setName(params.name.c_str()); } else if (!params.localFolder.empty()) { request.setName(params.localFolder.c_str()); // fallback to localFolder } request.setParamType(params.type); if (!params.driveRootIfExternal.empty()) request.setLink(params.driveRootIfExternal.c_str()); } /** * @brief Helper to build an initial sync configuration with the request fields populated after * populateRequest_syncFolder(). * * @param client A reference to the MegaClient used for remote node checking purposes. * @return A pair with an error (API_OK if success) and the new SyncConfig (empty object if there is * an error). * * @see populateRequest_syncFolder() * @see MegaApiImpl::syncFolder() * @see MegaApiImpl::prevalidateSyncFolder() */ std::pair prepareSyncConfig(const MegaRequestPrivate& request, MegaClient& client) { const std::string localPath{request.getFile() ? request.getFile() : ""}; const std::string name{request.getName() ? request.getName() : ""}; const std::string drivePath{request.getLink() ? request.getLink() : ""}; return buildSyncConfig(static_cast(request.getParamType()), localPath, name, drivePath, request.getNodeHandle(), client); } } // namespace namespace mega { // Public methods void MegaApiImpl::syncFolder(MegaRequestSyncFolderParams&& params, MegaRequestListener* const listener) { auto completion = [this](const auto request, auto&& config, auto&& revertOnError) { completeRequest_syncFolder_AddSync(request, std::forward(config), std::forward(revertOnError)); }; addRequest_syncFolder(MegaRequest::TYPE_ADD_SYNC, std::move(params), listener, std::move(completion)); } void MegaApiImpl::prevalidateSyncFolder(MegaRequestSyncFolderParams&& params, MegaRequestListener* const listener) { auto completion = [this](auto&&... params) { completeRequest_syncFolder_PrevalidateAddSync(std::forward(params)...); }; addRequest_syncFolder(MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, std::move(params), listener, std::move(completion)); } // Private/internal methods void MegaApiImpl::addRequest_syncFolder(const int megaRequestType, MegaRequestSyncFolderParams&& params, MegaRequestListener* const listener, SyncFolderRequestCompletion&& completion) { auto* const request = new MegaRequestPrivate(megaRequestType, listener); populateRequest_syncFolder(*request, std::move(params)); request->performRequest = [this, request, completion = std::move(completion)]() mutable -> error { return performRequest_syncFolder(request, std::move(completion)); }; requestQueue.push(request); waiter->notify(); } error MegaApiImpl::performRequest_syncFolder(MegaRequestPrivate* const request, SyncFolderRequestCompletion&& completion) { auto [err, syncConfig] = prepareSyncConfig(*request, *client); if (err != API_OK) { return err; } if (syncConfig.getType() != SyncConfig::TYPE_BACKUP) { completion(request, std::move(syncConfig), nullptr); return API_OK; } auto preparebackupCompletion = [this, request, completion = std::move(completion)](const auto err, auto&& backupConfig, auto&& revertOnError) { if (err) { fireOnRequestFinish(request, std::make_unique(err)); return; } assert(revertOnError && "No revertOnError action if there is a failure during backup set up!"); request->setNodeHandle(backupConfig.mRemoteNode.as8byte()); completion(request, std::forward(backupConfig), std::forward(revertOnError)); }; client->preparebackup(std::move(syncConfig), std::move(preparebackupCompletion)); return API_OK; } void MegaApiImpl::completeRequest_syncFolder_AddSync(MegaRequestPrivate* const request, SyncConfig&& syncConfig, MegaClient::UndoFunction&& revertOnError) { auto completion = [this, request, revertOnError = std::move(revertOnError)](auto err, const auto syncError, const auto backupId) { request->setNumDetails(syncError); if (client->syncs.hasSyncConfigByBackupId(backupId)) { request->setParentHandle(backupId); fireOnRequestFinish(request, std::make_unique(err, syncError)); return; } if (!err) { LOG_debug << "[MegaApiImpl::completeRequest_syncFolder_AddSync] Correcting error " "to API_ENOENT for sync add"; err = API_ENOENT; } if (!revertOnError) { fireOnRequestFinish(request, std::make_unique(err, syncError)); return; } revertOnError( [this, request, err, syncError]() { fireOnRequestFinish(request, std::make_unique(err, syncError)); }); }; client->addsync(std::move(syncConfig), std::move(completion), "", basePath); } void MegaApiImpl::completeRequest_syncFolder_PrevalidateAddSync( MegaRequestPrivate* const request, SyncConfig&& syncConfig, MegaClient::UndoFunction&& revertForBackup) { const auto syncErrorInfo = client->checkSyncConfig(syncConfig); const auto err = std::get<0>(syncErrorInfo); const auto syncError = std::get<1>(syncErrorInfo); request->setNumDetails(syncError); if (syncConfig.getType() != SyncConfig::TYPE_BACKUP) { fireOnRequestFinish(request, std::make_unique(err, syncError)); return; } if (!revertForBackup) { LOG_err << "[MegaApiImpl::prevalidateAddSyncByRequest] expected a handler to revert the " "backup node and it is null"; assert(false && "expected a handler to revert the backup node and it is null!"); fireOnRequestFinish(request, std::make_unique(err, syncError)); return; } revertForBackup( [this, request, err, syncError]() { request->setNodeHandle(MegaHandle()); fireOnRequestFinish(request, std::make_unique(err, syncError)); }); } } // namespace mega #endif // ENABLE_SYNC sdk-10.11.0/src/megaclient.cpp000066400000000000000000031526271516266226600160720ustar00rootroot00000000000000/** * @file megaclient.cpp * @brief Client access engine core logic * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/common/badge.h" #include "mega/hashcash.h" #include "mega/heartbeats.h" #include "mega/logging.h" #include "mega/mediafileattribute.h" #include "mega/network_connectivity_test.h" #include "mega/recent_actions.h" #include "mega/scoped_helpers.h" #include "mega/testhooks.h" #include "mega/tlv.h" #include "mega/totp.h" #include "mega/transfer.h" #include "mega/user_attribute.h" #include "mega/utils.h" #include // required for derive key of master key #include #include #include #include #include #include #include #include #include #include #undef min // avoid issues with std::min and std::max #undef max #ifdef __ANDROID__ #include "mega/android/androidFileSystem.h" #endif namespace mega { // FIXME: generate cr element for file imports // FIXME: support invite links (including responding to sharekey requests) // FIXME: instead of copying nodes, move if the source is in the rubbish to reduce node creation load on the servers // FIXME: prevent synced folder from being moved into another synced folder // default for disable public key pinning (for testing purposes) (determines if we check the public key from APIURL) bool g_disablepkp_default = false; // root URL for API access // MegaClient statics must be const or we get threading problems. And this one is edited so it can't be const. // Instead, we require a mutex to be locked before editing/reading it. MegaClient's HttpIO takes a copy on construction std::mutex g_APIURL_default_mutex; string g_APIURL_default = "https://g.api.mega.co.nz/"; // user handle for customer support user const string MegaClient::SUPPORT_USER_HANDLE = "pGTOqu7_Fek"; // root URL for chat stats // MegaClient statics must be const or we get threading problems const string MegaClient::SFUSTATSURL = "https://stats.sfu.mega.co.nz"; // root URLs for request status monitoring // MegaClient statics must be const or we get threading problems const string MegaClient::REQSTATURL = "https://reqstat.api.mega.co.nz"; // root URLs for Website const string MegaClient::MEGAURL_NZ = "https://mega.nz"; const string MegaClient::MEGAURL_APP = "https://mega.app"; // Non-const MegaClient static protected with a shared_mutex string MegaClient::MEGAURL = MegaClient::MEGAURL_NZ; std::shared_mutex MegaClient::megaUrlMutex; // maximum number of concurrent transfers (uploads + downloads) const unsigned MegaClient::MAXTOTALTRANSFERS = 48; // maximum number of concurrent transfers (uploads or downloads) const unsigned MegaClient::MAXTRANSFERS = 32; // minimum maximum number of concurrent transfers for dynamic calculation const unsigned MegaClient::MIN_MAXTRANSFERS = 12; // maximum number of concurrent raided transfers for mobile const unsigned MegaClient::MAX_RAIDTRANSFERS_FOR_MOBILE = std::max(MAXTRANSFERS - 10, MIN_MAXTRANSFERS); // meaningful portion of the maximum transfer queue size to consider raid representation // i.e., there must be at least this number of raid transfers to let us predict whether the next download transfer will be raided or non-raided const unsigned MegaClient::MEANINGFUL_PORTION_OF_MAXTRANSFERS_QUEUE_FOR_RAID_PREDICTIVE_SYSTEM = std::max(MAXTRANSFERS / 6, 1); // maximum number of queued putfa before halting the upload queue const int MegaClient::MAXQUEUEDFA = 30; // maximum number of concurrent putfa const int MegaClient::MAXPUTFA = 10; #ifdef ENABLE_SYNC // //bin/SyncDebris/yyyy-mm-dd base folder name const char* const MegaClient::SYNCDEBRISFOLDERNAME = "SyncDebris"; #endif // exported link marker const char* const MegaClient::EXPORTEDLINK = "EXP"; // public key to send payment details const char MegaClient::PAYMENT_PUBKEY[] = "CADB-9t4WSMCs6we8CNcAmq97_bP-eXa9pn7SwGPxXpTuScijDrLf_ooneCQnnRBDvE" "MNqTK3ULj1Q3bt757SQKDZ0snjbwlU2_D-rkBBbjWCs-S61R0Vlg8AI5q6oizH0pjpD" "eOhpsv2DUlvCa4Hjgy_bRpX8v9fJvbKI2bT3GXJWE7tu8nlKHgz8Q7NE3Ycj5XuUfCW" "GgOvPGBC-8qPOyg98Vloy53vja2mBjw4ycodx-ZFCt8i8b9Z8KongRMROmvoB4jY8ge" "ym1mA5iSSsMroGLypv9PueOTfZlG3UTpD83v6F3w8uGHY9phFZ-k2JbCd_-s-7gyfBE" "TpPvuz-oZABEBAAE"; // default number of seconds to wait after a bandwidth overquota dstime MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS = 3600; // default number of seconds to wait after a bandwidth overquota dstime MegaClient::USER_DATA_EXPIRATION_BACKOFF_SECS = 86400; // 1 day // How many bytes to logging at most static constexpr size_t CONSUMED_CHUNK_MAX_LOGGING = 20; // -- JourneyID constructor and methods -- MegaClient::JourneyID::JourneyID(unique_ptr& clientFsaccess, const LocalPath& rootPath) : mTrackValue(false), mClientFsaccess(clientFsaccess) { if (!rootPath.empty()) { LocalPath newCacheFilePath = rootPath; mCacheFilePath = newCacheFilePath; mCacheFilePath.appendWithSeparator(LocalPath::fromRelativePath("jid"), true); auto fileAccess = mClientFsaccess->newfileaccess(false); LOG_verbose << "[MegaClient::JourneyID] Cache file path set [mCacheFilePath = '" << mCacheFilePath.toPath(false) << "']"; // Try to open the file if (fileAccess->fopen(mCacheFilePath, FSLogging::logOnError)) { // The file already exists - load values from cache loadValuesFromCache(); } } else { LOG_debug << "[MegaClient::JourneyID] No file path for cache. No cache will be used"; } }; // Declaration of constexpr constexpr size_t MegaClient::JourneyID::HEX_STRING_SIZE; // Set the JourneyID value or update tracking flag bool MegaClient::JourneyID::setValue(const string& jidValue) { bool updateJourneyID = false; bool updateTrackingFlag = false; if (!jidValue.empty()) { if (jidValue.size() != HEX_STRING_SIZE) { LOG_err << "[MegaClient::JourneyID::setValue] Param jidValue has an invalid size (" << jidValue.size() << "), expected size: " << HEX_STRING_SIZE; assert(false && "Invalid size for new jidValue"); return false; } if (mJidValue.empty()) { assert(!mTrackValue && "There is no JourneyID value, but tracking flag is set!!!"); LOG_debug << "[MegaClient::JourneyID::setValue] Set new JourneyID: '" << jidValue << "'"; mJidValue = jidValue; updateJourneyID = true; } else if (mTrackValue) { LOG_verbose << "[MegaClient::JourneyID::setValue] Tracking flag is already set [mJidValue: " << mJidValue << ", mTrackValue = " << mTrackValue << "]"; return false; } LOG_debug << "[MegaClient::JourneyID::setValue] Set tracking flag [mJidValue: " << mJidValue << "]"; mTrackValue = true; updateTrackingFlag = true; } else { if (!mTrackValue) { LOG_verbose << "[MegaClient::JourneyID::setValue] Tracking flag is already false [mJidValue: " << mJidValue << ", mTrackValue = " << mTrackValue << "]"; return false; } LOG_debug << "[MegaClient::JourneyID::setValue] Unset tracking flag"; mTrackValue = false; updateTrackingFlag = true; } LOG_debug << "[MegaClient::JourneyID::setValue] Store updated values in cache file"; storeValuesToCache(updateJourneyID, updateTrackingFlag); return true; } // Check if the journeyID must be tracked (used on API reqs) bool MegaClient::JourneyID::isTrackingOn() const { if (mTrackValue && mJidValue.empty()) { LOG_err << "[MegaClient::JourneyID::isTrackingOn] TrackValue is ON without a valid jidValue (0)"; assert(false && "TrackValue is ON without a valid jidValue"); } return mTrackValue; } // Get the 16-char hex string value string MegaClient::JourneyID::getValue() const { return mJidValue; } bool MegaClient::JourneyID::loadValuesFromCache() { if (mCacheFilePath.empty()) { LOG_debug << "[MegaClient::JourneyID::loadValuesFromCache] Cache file path is empty. Cannot load values from the local cache"; return false; } auto fileAccess = mClientFsaccess->newfileaccess(false); bool success = fileAccess->fopen(mCacheFilePath, OPEN_RDONLY, FSLogging::logOnError); if (success) { string cachedJidValue, cachedTrackValue; success &= fileAccess->fread(&cachedJidValue, HEX_STRING_SIZE, 0, 0, FSLogging::logOnError); success &= fileAccess->fread(&cachedTrackValue, 1, 0, HEX_STRING_SIZE, FSLogging::logOnError); if (success) { if (cachedJidValue.size() != HEX_STRING_SIZE) { resetCacheAndValues(); LOG_err << "[MegaClient::JourneyID::loadValuesFromCache] CachedJidValue size is not HEX_STRING_SIZE!!!! -> reset cache"; assert(false && "CachedJidValue size is not HEX_STRING_SIZE!!!!"); return false; } if (cachedTrackValue.size() != 1) { resetCacheAndValues(); LOG_err << "[MegaClient::JourneyID::loadValuesFromCache] CachedTrackValue size is not 1!!!! -> reset cache"; assert(false && "CachedJidValue size is not 1!!!!"); return false; } if (cachedTrackValue != "1" && cachedTrackValue != "0") { resetCacheAndValues(); LOG_err << "[MegaClient::JourneyID::loadValuesFromCache] CachedTrackValue is not 1 or 0!!!! -> reset cache"; assert(false && "CachedTrackValue size is not 1 or 0!!!!"); return false; } mJidValue = cachedJidValue; mTrackValue = (cachedTrackValue == "1") ? true : false; } } if (!success) { resetCacheAndValues(); LOG_err << "[MegaClient::JourneyID::loadValuesFromCache] Unable to load values from the local cache"; return false; } LOG_debug << "[MegaClient::JourneyID::loadValuesFromCache] Values loaded from the local cache"; return true; } bool MegaClient::JourneyID::storeValuesToCache(bool storeJidValue, bool storeTrackValue) const { if (mCacheFilePath.empty()) { LOG_debug << "[MegaClient::JourneyID::storeValuesToCache] Cache file path is empty. Cannot store values to the local cache"; return false; } if (mJidValue.empty()) { LOG_warn << "[MegaClient::JourneyID::storeValuesToCache] Jid value is empty. It cannot be stored to the cache"; assert(!storeTrackValue && "storeTrackValue is true with an empty mJidValue!!!"); return false; } auto fileAccess = mClientFsaccess->newfileaccess(false); bool success = fileAccess->fopen(mCacheFilePath, OPEN_WRONLY, FSLogging::logOnError); if (success) { if (storeJidValue) { success &= fileAccess->fwrite((const byte*)(getValue().c_str()), HEX_STRING_SIZE, 0); } if (storeTrackValue) { success &= fileAccess->fwrite((const byte*)(mTrackValue ? "1" : "0"), 1, HEX_STRING_SIZE); } } if (!success) { LOG_err << "[MegaClient::JourneyID::storeValuesToCache] Unable to store values in the local cache"; return false; } LOG_err << "[MegaClient::JourneyID::storeValuesToCache] Values stored in the local cache"; return true; } bool MegaClient::JourneyID::resetCacheAndValues() { // Reset local values mJidValue = ""; mTrackValue = false; // Remove local cache file if (mCacheFilePath.empty()) { LOG_debug << "[MegaClient::JourneyID::resetCacheAndValues] Cache file path is empty. Cannot remove local cache file"; return false; } if (!mClientFsaccess->unlinklocal(mCacheFilePath)) { LOG_err << "[MegaClient::JourneyID::resetCacheAndValues] Unable to remove local cache file"; return false; } return true; } // -- JourneyID methods end -- // Generate ViewID string MegaClient::generateViewId(PrnGen& rng) { uint64_t viewId; rng.genblock((byte*)&viewId, sizeof(viewId)); // Incorporate current timestamp in ms into the generated value for uniqueness long long tsInMs = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); viewId ^= static_cast(tsInMs); return Utils::uint64ToHexString(viewId); } // decrypt key (symmetric or asymmetric) bool MegaClient::decryptkey(const char* sk, byte* tk, int tl, SymmCipher* sc, int /*type*/, handle /*node*/) { int sl; const char* ptr = sk; // measure key length while (*ptr && *ptr != '"' && *ptr != '/') { ptr++; } sl = int(ptr - sk); if (sl > 4 * FILENODEKEYLENGTH / 3 + 1) { // RSA-encrypted key - decrypt sl = sl / 4 * 3 + 3; if (sl > 4096) { return false; } byte* buf = new byte[static_cast(sl)]; sl = Base64::atob(sk, buf, sl); // decrypt and set session ID for subsequent API communication if (!mPrivateRsaKey.decrypt(buf, static_cast(sl), tk, static_cast(tl))) { delete[] buf; LOG_warn << "Corrupt or invalid RSA node key"; return false; } delete[] buf; } else { if (Base64::atob(sk, tk, tl) != tl) { LOG_warn << "Corrupt or invalid symmetric node key"; return false; } sc->ecb_decrypt(tk, static_cast(tl)); } return true; } // apply queued new shares void MegaClient::mergenewshares(bool notify, bool skipWriteInDb) { newshare_list::iterator it; for (it = newshares.begin(); it != newshares.end(); ) { NewShare* s = *it; mergenewshare(s, notify, skipWriteInDb); delete s; newshares.erase(it++); } } void MegaClient::mergenewshare(NewShare *s, bool notify, bool skipWriteInDb) { bool skreceived = false; std::shared_ptr n = nodebyhandle(s->h); if (!n) { return; } if (s->access != ACCESS_UNKNOWN && !s->have_key) { // Check if the key is already in the key manager std::string shareKey = mKeyManager.getShareKey(s->h); if (shareKey.size() == sizeof(s->key)) { memcpy(s->key, shareKey.data(), sizeof(s->key)); s->have_key = 1; s->outgoing = s->outgoing > 0 ? -1 : s->outgoing; // always authenticated when loaded from KeyManager } } // n was no shared or it was shared but sharekey has changed if (s->have_key && (!n->sharekey || memcmp(s->key, n->sharekey->key, SymmCipher::KEYLENGTH))) { // setting an outbound sharekey requires node authentication // unless coming from a trusted source (the local cache) bool auth = true; if (s->outgoing > 0) { // Once secure=true, the "ha" for shares are ignored or set to 0, so // comparing against "ha" values is not needed. if (!checkaccess(n.get(), OWNERPRELOGIN)) { LOG_warn << "Attempt to create dislocated outbound share foiled: " << toNodeHandle(s->h); auth = false; } else if (!mKeyManager.generation()) { byte buf[SymmCipher::KEYLENGTH]; handleauth(s->h, buf); if (memcmp(buf, s->auth, sizeof buf)) { LOG_warn << "Attempt to create forged outbound share foiled: " << toNodeHandle(s->h); auth = false; } } } // if authentication token is received... // and the sharekey can decrypt encrypted node's attributes (if still encrypted)... if (auth && n->testShareKey(s->key)) { std::string newShareKey((const char *)s->key, SymmCipher::KEYLENGTH); std::string secureShareKey = mKeyManager.getShareKey(n->nodehandle); // newShareKey can arrive here from ^!keys from KeyManager::loadShareKeys // or loaded from mKeyManager at the beginning of this function // If newShareKey != secureShareKey the key should be legacy bool legacyKey = newShareKey != secureShareKey; // we don't allow legacy keys to replace trusted keys if (legacyKey && mKeyManager.isShareKeyTrusted(n->nodehandle)) { LOG_warn << "A legacy key for " << toNodeHandle(n->nodehandle) << " has not been" << " allowed to replace a trusted share key"; } else { // in all other cases, we can apply the key if (n->sharekey) { if (!fetchingnodes) { sendevent(99428,"Replacing share key", 0); } } n->sharekey.reset(new SymmCipher(s->key)); skreceived = true; } } } if (s->access == ACCESS_UNKNOWN && !s->have_key) { // share was deleted if (s->outgoing) { bool found = false; if (n->outshares) { // outgoing share to user u deleted share_map::iterator shareit = n->outshares->find(s->peer); if (shareit != n->outshares->end()) { n->outshares->erase(shareit); found = true; if (notify) { n->changed.outshares = true; mNodeManager.notifyNode(n); } } if (n->outshares->empty()) { n->outshares.reset(); } } if (n->pendingshares && !found && s->pending) { // delete the pending share share_map::iterator shareit = n->pendingshares->find(s->pending); if (shareit != n->pendingshares->end()) { n->pendingshares->erase(shareit); found = true; if (notify) { n->changed.pendingshares = true; mNodeManager.notifyNode(n); } } if (n->pendingshares->empty()) { n->pendingshares.reset(); } } // Erase sharekey if no outgoing shares (incl pending) exist // Sharekey is kept in KeyManager if (s->remove_key && !n->outshares && !n->pendingshares) { rewriteforeignkeys(n); n->sharekey.reset(); } } else { if (n->inshare) { n->inshare->user->sharing.erase(n->nodehandle); notifyuser(n->inshare->user); n->inshare.reset(); } // incoming share deleted - remove tree if (!n->parent || n->parent->changed.removed) { TreeProcDel td; proctree(n, &td, true); } else if (notify) { n->changed.inshare = true; mNodeManager.notifyNode(n); } } } else { if (s->outgoing) { if ((!s->upgrade_pending_to_full && (!ISUNDEF(s->peer) || !ISUNDEF(s->pending))) || (s->upgrade_pending_to_full && !ISUNDEF(s->peer) && !ISUNDEF(s->pending))) { // perform mandatory verification of outgoing shares: // only on own nodes and signed unless read from cache if (checkaccess(n.get(), OWNERPRELOGIN)) { unique_ptr* sharep; if (!ISUNDEF(s->pending)) { // Pending share if (!n->pendingshares) { n->pendingshares.reset(new share_map()); } if (s->upgrade_pending_to_full) { share_map::iterator shareit = n->pendingshares->find(s->pending); if (shareit != n->pendingshares->end()) { // This is currently a pending share that needs to be upgraded to a full share // erase from pending shares & delete the pending share list if needed n->pendingshares->erase(shareit); if (notify) { n->changed.pendingshares = true; mNodeManager.notifyNode(n); } } if (n->pendingshares->empty()) { n->pendingshares.reset(); } // clear this so we can fall through to below and have it re-create the share in // the outshares list s->pending = UNDEF; // create the outshares list if needed if (!n->outshares) { n->outshares.reset(new share_map()); } sharep = &((*n->outshares)[s->peer]); } else { sharep = &((*n->pendingshares)[s->pending]); } } else { // Normal outshare if (!n->outshares) { n->outshares.reset(new share_map()); } sharep = &((*n->outshares)[s->peer]); } // modification of existing share or new share if (*sharep) { (*sharep)->update(s->access, s->ts, findpcr(s->pending)); } else { sharep->reset(new Share(ISUNDEF(s->peer) ? NULL : finduser(s->peer, 1), s->access, s->ts, findpcr(s->pending))); } if (notify) { if (!ISUNDEF(s->pending)) { n->changed.pendingshares = true; } else { n->changed.outshares = true; } mNodeManager.notifyNode(n); } } } else { LOG_debug << "Merging share without peer information."; // Outgoing shares received during fetchnodes are merged in two steps: // 1. From readok(), a NewShare is created with the 'sharekey' // 2. From readoutshares(), a NewShare is created with the 'peer' information } } else { if (!ISUNDEF(s->peer)) { if (s->peer) { if (!checkaccess(n.get(), OWNERPRELOGIN)) { // modification of existing share or new share if (n->inshare) { n->inshare->update(s->access, s->ts); } else { n->inshare.reset(new Share(finduser(s->peer, 1), s->access, s->ts, NULL)); n->inshare->user->sharing.insert(n->nodehandle); } if (notify) { n->changed.inshare = true; mNodeManager.notifyNode(n); } } else { LOG_warn << "Invalid inbound share location"; } } else { LOG_warn << "Invalid null peer on inbound share"; } } else { if (skreceived && notify) { TreeProcApplyKey td; proctree(n, &td); } } } #ifdef ENABLE_SYNC // "access" is default (ACCESS_UNKNOWN) for pending keys, so let's ignore it. if (n->inshare && s->access != FULL && s->access != ACCESS_UNKNOWN) { // check if the low(ered) access level is affecting any syncs // a) have we just cut off full access to a subtree of a sync? auto activeConfigs = syncs.getConfigs(true); for (auto& sc : activeConfigs) { if (n->isbelow(sc.mRemoteNode)) { LOG_warn << "Existing inbound share sync or part thereof lost full access"; syncs.disableSyncByBackupId(sc.mBackupId, SHARE_NON_FULL_ACCESS, false, true, nullptr); // passing true for SYNC_FAILED } } // b) have we just lost full access to the subtree a sync is in? std::shared_ptr root; for (auto& sc : activeConfigs) { if (n->isbelow(sc.mRemoteNode) && (nullptr != (root = nodeByHandle(sc.mRemoteNode))) && !checkaccess(root.get(), FULL)) { LOG_warn << "Existing inbound share sync lost full access"; syncs.disableSyncByBackupId(sc.mBackupId, SHARE_NON_FULL_ACCESS, false, true, nullptr); // passing true for SYNC_FAILED } }; } #endif } if (!notify && !skipWriteInDb) { mNodeManager.updateNode(n.get()); } //else -> It will be updated at notifypurge } sharedNode_vector MegaClient::getInShares() { sharedNode_vector nodes; forEachIncomingShare( [&nodes](std::shared_ptr node) { nodes.emplace_back(std::move(node)); }); return nodes; } sharedNode_vector MegaClient::getVerifiedInShares() { sharedNode_vector nodes; for (auto &it : users) { for (auto &share : it.second.sharing) { std::shared_ptr n = nodebyhandle(share); if (n && !n->parent && !mKeyManager.isUnverifiedInShare(n->nodehandle, it.second.userhandle)) // top-level inshare have parent==nullptr { nodes.push_back(n); } } } return nodes; } sharedNode_vector MegaClient::getUnverifiedInShares() { sharedNode_vector nodes; for (auto &it : users) { for (auto &share : it.second.sharing) { std::shared_ptr n = nodebyhandle(share); if (n && !n->parent && mKeyManager.isUnverifiedInShare(n->nodehandle, it.second.userhandle)) // top-level inshare have parent==nullptr { nodes.push_back(n); } } } return nodes; } bool MegaClient::setlang(string *code) { if (code && code->size() == 2) { lang = "&lang="; lang.append(*code); return true; } lang.clear(); LOG_err << "Invalid language code: " << (code ? *code : "(null)"); return false; } void MegaClient::enableSearchDBIndexes(bool enable) { mEnableSearchDBIndexes = enable; } void MegaClient::enableLexicographicDBIndexes(bool enable) { mEnableLexicographicDBIndexes = enable; } void MegaClient::dropSearchDBIndexes() { mNodeManager.dropSearchDBIndexes(); } // -- MegaClient JourneyID methods -- string MegaClient::getJourneyId() const { return mJourneyId->getValue(); } bool MegaClient::trackJourneyId() const { return !getJourneyId().empty() && mJourneyId->isTrackingOn(); } // Load the JourneyID values from the local cache. bool MegaClient::loadJourneyIdCacheValues() { return mJourneyId->loadValuesFromCache(); } bool MegaClient::setJourneyId(const string& jid) { if (mJourneyId->setValue(jid)) { LOG_debug << "[MegaClient::setJourneyID] Set journeyID from string = '" << jid << "') [tracking: " << mJourneyId->isTrackingOn() << "]"; return true; } return false; } // -- MegaClient JourneyID methods end -- error MegaClient::setbackupfolder(const char* foldername, int tag, std::function addua_completion) { if (!foldername) { return API_EARGS; } User* u = ownuser(); if (!u) { return API_EACCESS; // not logged in? } const UserAttribute* attribute = u->getAttribute(ATTR_MY_BACKUPS_FOLDER); if (attribute && attribute->isValid()) { // if the attribute was previously set, only allow setting it again if the folder node was // missing (should never happen, but it already did) handle h = 0; memcpy(&h, attribute->value().data(), NODEHANDLE); if (nodebyhandle(h)) { // cannot set a new folder if it already exists return API_EEXIST; } } // 1. prepare the NewNode and create it via putnodes(), with flag `vw:1` vector newnodes(1); NewNode& newNode = newnodes.back(); putnodes_prepareOneFolder(&newNode, foldername, true); // 2. upon completion of putnodes(), set the user's attribute `^!bak` auto addua = [addua_completion, this](const Error& e, [[maybe_unused]] targettype_t handletype, vector& nodes, bool /*targetOverride*/, int /*tag*/, const map& /*fileHandles*/) { if (e != API_OK) { addua_completion(e); return; } assert(handletype == NODE_HANDLE && nodes.size() == 1 && nodes.back().mAddedHandle != UNDEF); putua(ATTR_MY_BACKUPS_FOLDER, (const byte*)&nodes.back().mAddedHandle, NODEHANDLE, -1, UNDEF, 0, 0, addua_completion); }; putnodes(mNodeManager.getRootNodeVault(), NoVersioning, std::move(newnodes), nullptr, tag, true, {}, // customerIpPort addua); // Note: this request should not finish until the user's attribute is set successfully return API_OK; } void MegaClient::updateStateInBC(handle bkpId, CommandBackupPut::SPState newState, std::function finalCompletion) { shared_ptr bkpRoot = std::make_shared(0); // step 4: handle result of setattr()/putua() auto setAttrCompletion = [bkpId, finalCompletion](NodeHandle, Error setAttrErr) { if (setAttrErr != API_OK) { LOG_err << "Update backup/sync: failed to set 'sds' for " << toHandle(bkpId) << ": " << setAttrErr; } finalCompletion(setAttrErr); }; // step 3: set sds attribute auto updateSds = [this, bkpId, newState, bkpRoot, setAttrCompletion, finalCompletion]( const Error& updateStateErr, [[maybe_unused]] handle backupId) mutable { assert(backupId == UNDEF || bkpId == backupId); if (updateStateErr != API_OK) { LOG_err << "Update backup/sync: failed to update " << toHandle(bkpId) << ", error: " << error(updateStateErr); // Don't break execution here in case of error. Still try to set 'sds' node attribute. } std::shared_ptr bkpRootNode = nodebyhandle(*bkpRoot); if (!bkpRootNode) { LOG_err << "Update backup/sync: root node not found to set SDS attribute for when updating " << toHandle(bkpId); finalCompletion(API_ENOENT); return; } // SDS values for full-syncs (account root node) are set in the *!sds attribute // instead of as an attribute of the backup/sync root node. if (mNodeManager.getRootNodeFiles().eq(*bkpRoot)) { auto updateSDSUserAttr = [this, bkpId, newState, setAttrCompletion](unique_ptr currentSds, attr_t) { if (!currentSds) { // Create an empty map when the attribute doesn't exist. // Could be empty value in the getua or a non existing attribute. currentSds = std::make_unique(); } const string b64BkupId = toHandle(bkpId); const string state = std::to_string(newState); (*currentSds)[b64BkupId] = state; putua(ATTR_SYNC_DESIRED_STATE, std::move(*currentSds), 0, UNDEF, 0, 0, [setAttrCompletion](Error e) { setAttrCompletion({}, e); }); }; getua( ownuser(), ATTR_SYNC_DESIRED_STATE, 0, [setAttrCompletion, updateSDSUserAttr](Error e) { if (e == API_ENOENT) { // updateSDSUserAttr will manage null pointers updateSDSUserAttr(std::unique_ptr(), ATTR_SYNC_DESIRED_STATE); } else { setAttrCompletion({}, e); } }, nullptr, updateSDSUserAttr); } else { vector> sdsBkps = bkpRootNode->getSdsBackups(); sdsBkps.emplace_back(bkpId, newState); const string sdsValue = Node::toSdsString(sdsBkps); attr_map sdsAttrMap(Node::sdsId(), sdsValue); auto e = setattr(bkpRootNode, std::move(sdsAttrMap), setAttrCompletion, true); if (e != API_OK) { LOG_err << "Update backup/sync: failed to set the 'sds' node attribute in client"; finalCompletion(e); } } }; // step 1: fetch Backup/sync data from Backup Centre getBackupInfo([this, bkpId, newState, bkpRoot, updateSds, finalCompletion](const Error& e, const vector& data) { if (e != API_OK) { LOG_err << "Update backup/sync: getBackupInfo failed with " << e; finalCompletion(e); return; } auto it = std::find_if(data.begin(), data.end(), [bkpId](const auto& d) { return d.backupId == bkpId; }); if (it == data.end()) { LOG_err << "Backup/sync to update: " << toHandle(bkpId) << " not returned by 'sr' command"; finalCompletion(API_ENOENT); return; } // allow only Active -> Paused and Paused -> Active const auto& d = *it; if ((d.syncState == CommandBackupPut::ACTIVE && newState == CommandBackupPut::TEMPORARY_DISABLED) || (d.syncState == CommandBackupPut::TEMPORARY_DISABLED && newState == CommandBackupPut::ACTIVE)) { if (!nodebyhandle(d.rootNode)) { LOG_err << "Update backup/sync: root node not found to set SDS attribute for, " "after fetching " << toHandle(bkpId); finalCompletion(API_ENOENT); return; } *bkpRoot = d.rootNode; // step 2: update backup/sync CommandBackupPut::BackupInfo info; info.backupId = d.backupId; info.type = d.backupType; info.backupName = d.backupName; info.nodeHandle.set6byte(d.rootNode); info.localFolder.fromAbsolutePath(d.localFolder); info.deviceId = d.deviceId; info.state = newState; info.subState = d.syncSubstate; queueCommand(new CommandBackupPut(this, info, updateSds)); } else { LOG_err << "Update backup/sync: state change not allowed: " << d.syncState << " -> " << newState; finalCompletion(API_EARGS); } }); } void MegaClient::removeFromBC(handle bkpId, handle targetDest, std::function finalCompletion) { shared_ptr bkpRoot = std::make_shared(0); shared_ptr isBackup = std::make_shared(false); // step 4: move or delete backup contents auto moveOrDeleteBackup = [this, bkpId, bkpRoot, targetDest, isBackup, finalCompletion](NodeHandle, Error setAttrErr) { if (!*isBackup || setAttrErr != API_OK) { if (setAttrErr != API_OK) { LOG_err << "Remove backup/sync: failed to set 'sds' for " << toHandle(bkpId) << ": " << setAttrErr; } finalCompletion(setAttrErr); return; } NodeHandle bkpRootNH; bkpRootNH.set6byte(*bkpRoot); NodeHandle targetDestNH; targetDestNH.set6byte(targetDest ? targetDest : UNDEF); // allow 0 as well unlinkOrMoveBackupNodes(bkpRootNH, targetDestNH, finalCompletion); }; // step 3: set sds attribute auto updateSds = [this, bkpId, bkpRoot, moveOrDeleteBackup, finalCompletion](const Error& bkpRmvErr) mutable { if (bkpRmvErr != API_OK) { LOG_err << "Remove backup/sync: failed to remove " << toHandle(bkpId); // Don't break execution here in case of error. Still try to set 'sds' node attribute. } std::shared_ptr bkpRootNode = nodebyhandle(*bkpRoot); if (!bkpRootNode) { LOG_warn << "Remove backup/sync: root folder not found, thus SDS cannot be set; considering it completely removed"; finalCompletion(API_OK); return; } // SDS values for full-syncs (account root node) are set in the *!sds attribute // instead of as an attribute of the backup/sync root node. if (mNodeManager.getRootNodeFiles().eq(*bkpRoot)) { auto updateSDSUserAttr = [this, bkpId, moveOrDeleteBackup](unique_ptr currentSds, attr_t) { if (!currentSds) { // Create an empty map when the attribute doesn't exist. // Could be empty value in the getua or a non existing attribute. currentSds = std::make_unique(); } const string b64BkupId = toHandle(bkpId); const string state = std::to_string(CommandBackupPut::DELETED); (*currentSds)[b64BkupId] = state; putua(ATTR_SYNC_DESIRED_STATE, std::move(*currentSds), 0, UNDEF, 0, 0, [moveOrDeleteBackup](Error e) { moveOrDeleteBackup({}, e); }); }; getua( ownuser(), ATTR_SYNC_DESIRED_STATE, 0, [moveOrDeleteBackup, updateSDSUserAttr](Error e) { if (e == API_ENOENT) { // updateSDSUserAttr will manage null pointers updateSDSUserAttr(std::unique_ptr(), ATTR_SYNC_DESIRED_STATE); } else { moveOrDeleteBackup({}, e); } }, nullptr, updateSDSUserAttr); } else { vector> sdsBkps = bkpRootNode->getSdsBackups(); sdsBkps.emplace_back(std::make_pair(bkpId, CommandBackupPut::DELETED)); const string& sdsValue = Node::toSdsString(sdsBkps); attr_map sdsAttrMap(Node::sdsId(), sdsValue); auto e = setattr(bkpRootNode, std::move(sdsAttrMap), moveOrDeleteBackup, true); if (e != API_OK) { LOG_err << "Remove backup/sync: failed to set the 'sds' node attribute"; finalCompletion(e); } } }; // step 1: fetch Backup/sync data from Backup Centre getBackupInfo( [this, bkpId, bkpRoot, updateSds, isBackup, targetDest, finalCompletion]( const Error& e, const vector& data) { if (e != API_OK) { LOG_err << "Remove backup/sync: getBackupInfo failed with " << e; finalCompletion(e); return; } for (auto& d: data) { if (d.backupId != bkpId) { continue; } *bkpRoot = d.rootNode; *isBackup = d.backupType == BackupType::BACKUP_UPLOAD; // Check for destination clash before proceeding. if (*isBackup && targetDest != UNDEF) { const std::shared_ptr backupRootNode{nodebyhandle(*bkpRoot)}; const std::shared_ptr backupDestinationNode{nodebyhandle(targetDest)}; if (backupRootNode && backupDestinationNode) { if (backupDestinationNode->hasChildWithName(backupRootNode->displayname())) { LOG_err << "A node with the same name already exists in the " "destination. Failed to remove backup " << toHandle(bkpId); finalCompletion(API_EEXIST); return; } } } // step 2: remove backup/sync queueCommand(new CommandBackupRemove(this, bkpId, updateSds)); return; } LOG_err << "Remove backup/sync: " << toHandle(bkpId) << " not returned by 'sr' command"; finalCompletion(API_ENOENT); }); } void MegaClient::getBackupInfo(std::function&)> f) { queueCommand(new CommandBackupSyncFetch(f)); } void MegaClient::setFolderLinkAccountAuth(const char *auth) { if (auth) { mFolderLink.mAccountAuth = auth; } else { mFolderLink.mAccountAuth.clear(); } } handle MegaClient::getFolderLinkPublicHandle() { return mFolderLink.mPublicHandle; } bool MegaClient::isValidEndCallReason(int reason) { return reason == END_CALL_REASON_REJECTED || reason == END_CALL_REASON_BY_MODERATOR; } bool MegaClient::isValidFolderLink() { if (!ISUNDEF(mFolderLink.mPublicHandle)) { NodeHandle h = mNodeManager.getRootNodeFiles(); // is the actual rootnode handle received? if (!h.isUndef()) { std::shared_ptr n = nodeByHandle(h); if (n && (n->attrs.map.find('n') != n->attrs.map.end())) // is it decrypted? (valid key) { return true; } } } return false; } shared_ptr MegaClient::getrootnode(shared_ptr node) { if (!node) { return NULL; } std::shared_ptr n = node; while (n->parent) { n = n->parent; } return n; } bool MegaClient::isPrivateNode(NodeHandle h) { std::shared_ptr node = nodeByHandle(h); if (!node) { return false; } NodeHandle rootnode = getrootnode(node)->nodeHandle(); return mNodeManager.isRootNode(rootnode); } bool MegaClient::isForeignNode(NodeHandle h) { std::shared_ptr node = nodeByHandle(h); if (!node) { return false; } NodeHandle rootnode = getrootnode(node)->nodeHandle(); return !mNodeManager.isRootNode(rootnode); } SCSN::SCSN() { clear(); } void SCSN::clear() { memset(scsn, 0, sizeof(scsn)); stopsc = false; LOG_debug << "scsn cleared"; } // set server-client sequence number bool SCSN::setScsn(JSON* j) { handle t; if (j->storebinary((byte*)&t, sizeof t) != sizeof t) { return false; } setScsn(t); return true; } void SCSN::setScsn(handle h) { bool wasReady = ready(); Base64::btoa((byte*)&h, sizeof h, scsn); if (ready() != wasReady) { LOG_debug << "scsn now ready: " << ready(); } } void SCSN::stopScsn() { memset(scsn, 0, sizeof(scsn)); stopsc = true; LOG_debug << "scsn stopped"; } bool SCSN::ready() const { return !stopsc && *scsn; } bool SCSN::stopped() const { return stopsc; } const char* SCSN::text() const { assert(ready()); return scsn; } handle SCSN::getHandle() const { assert(ready()); handle t; Base64::atob(scsn, (byte*)&t, sizeof t); return t; } std::ostream& operator<<(std::ostream &os, const SCSN &scsn) { os << scsn.text(); return os; } SimpleLogger& operator<<(SimpleLogger &os, const SCSN &scsn) { os << scsn.text(); return os; } int MegaClient::nextreqtag() { return ++reqtag; } void MegaClient::getrecoverylink(const char *email, bool hasMasterkey) { queueCommand(new CommandGetRecoveryLink(this, email, hasMasterkey ? RECOVER_WITH_MASTERKEY : RECOVER_WITHOUT_MASTERKEY)); } void MegaClient::queryrecoverylink(const char *code) { queueCommand(new CommandQueryRecoveryLink(this, code)); } void MegaClient::getprivatekey(const char *code) { queueCommand(new CommandGetPrivateKey(this, code)); } void MegaClient::confirmrecoverylink(const char* code, const char* email, const char* password, const byte* masterkeyptr, int ownAccountVersion) { if (ownAccountVersion == 1) { byte pwkey[SymmCipher::KEYLENGTH]; pw_key(password, pwkey); SymmCipher pwcipher(pwkey); string emailstr = email; uint64_t loginHash = stringhash64(&emailstr, &pwcipher); if (masterkeyptr) { // encrypt provided masterkey using the new password byte encryptedMasterKey[SymmCipher::KEYLENGTH]; memcpy(encryptedMasterKey, masterkeyptr, sizeof encryptedMasterKey); pwcipher.ecb_encrypt(encryptedMasterKey); queueCommand(new CommandConfirmRecoveryLink(this, code, (byte*)&loginHash, sizeof(loginHash), NULL, encryptedMasterKey, NULL)); } else { // create a new masterkey byte newmasterkey[SymmCipher::KEYLENGTH]; rng.genblock(newmasterkey, sizeof newmasterkey); // generate a new session byte initialSession[2 * SymmCipher::KEYLENGTH]; rng.genblock(initialSession, sizeof initialSession); key.setkey(newmasterkey); key.ecb_encrypt(initialSession, initialSession + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); // and encrypt the master key to the new password pwcipher.ecb_encrypt(newmasterkey); queueCommand(new CommandConfirmRecoveryLink(this, code, (byte*)&loginHash, sizeof(loginHash), NULL, newmasterkey, initialSession)); } } else { byte clientkey[SymmCipher::KEYLENGTH]; rng.genblock(clientkey, sizeof(clientkey)); string salt; HashSHA256 hasher; string buffer = "mega.nz"; buffer.resize(200, 'P'); buffer.append((char *)clientkey, sizeof(clientkey)); hasher.add((const byte*)buffer.data(), unsigned(buffer.size())); hasher.get(&salt); vector derivedKey = deriveKey(password, salt, 2 * SymmCipher::KEYLENGTH); string hashedauthkey; const byte *authkey = derivedKey.data() + SymmCipher::KEYLENGTH; hasher.add(authkey, SymmCipher::KEYLENGTH); hasher.get(&hashedauthkey); hashedauthkey.resize(SymmCipher::KEYLENGTH); SymmCipher cipher; cipher.setkey(derivedKey.data()); if (masterkeyptr) { // encrypt provided masterkey using the new password byte encryptedMasterKey[SymmCipher::KEYLENGTH]; memcpy(encryptedMasterKey, masterkeyptr, sizeof encryptedMasterKey); cipher.ecb_encrypt(encryptedMasterKey); queueCommand(new CommandConfirmRecoveryLink(this, code, (byte*)hashedauthkey.data(), SymmCipher::KEYLENGTH, clientkey, encryptedMasterKey, NULL)); } else { // create a new masterkey byte newmasterkey[SymmCipher::KEYLENGTH]; rng.genblock(newmasterkey, sizeof newmasterkey); // generate a new session byte initialSession[2 * SymmCipher::KEYLENGTH]; rng.genblock(initialSession, sizeof initialSession); key.setkey(newmasterkey); key.ecb_encrypt(initialSession, initialSession + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); // and encrypt the master key to the new password cipher.ecb_encrypt(newmasterkey); queueCommand(new CommandConfirmRecoveryLink(this, code, (byte*)hashedauthkey.data(), SymmCipher::KEYLENGTH, clientkey, newmasterkey, initialSession)); } } } void MegaClient::getcancellink(const char *email, const char *pin) { queueCommand(new CommandGetRecoveryLink(this, email, CANCEL_ACCOUNT, pin)); } void MegaClient::confirmcancellink(const char *code) { queueCommand(new CommandConfirmCancelLink(this, code)); } void MegaClient::getemaillink(const char *email, const char *pin) { queueCommand(new CommandGetEmailLink(this, email, 1, pin)); } void MegaClient::confirmemaillink(const char *code, const char *email, const byte *pwkey) { if (pwkey) { SymmCipher pwcipher(pwkey); string emailstr = email; uint64_t loginHash = stringhash64(&emailstr, &pwcipher); queueCommand(new CommandConfirmEmailLink(this, code, email, (const byte*)&loginHash, true)); } else { queueCommand(new CommandConfirmEmailLink(this, code, email, NULL, true)); } } void MegaClient::contactlinkcreate(bool renew) { queueCommand(new CommandContactLinkCreate(this, renew)); } void MegaClient::contactlinkquery(handle h) { queueCommand(new CommandContactLinkQuery(this, h)); } void MegaClient::contactlinkdelete(handle h) { queueCommand(new CommandContactLinkDelete(this, h)); } void MegaClient::multifactorauthsetup(const char *pin) { queueCommand(new CommandMultiFactorAuthSetup(this, pin)); } void MegaClient::multifactorauthcheck(const char *email) { queueCommand(new CommandMultiFactorAuthCheck(this, email)); } void MegaClient::multifactorauthdisable(const char *pin) { queueCommand(new CommandMultiFactorAuthDisable(this, pin)); } void MegaClient::fetchtimezone() { string timeoffset; m_time_t rawtime = m_time(NULL); if (rawtime != -1) { struct tm lt, ut, it; memset(<, 0, sizeof(struct tm)); memset(&ut, 0, sizeof(struct tm)); memset(&it, 0, sizeof(struct tm)); m_localtime(rawtime, <); m_gmtime(rawtime, &ut); if (memcmp(&ut, &it, sizeof(struct tm)) && memcmp(<, &it, sizeof(struct tm))) { m_time_t local_time = m_mktime(<); m_time_t utc_time = m_mktime(&ut); if (local_time != -1 && utc_time != -1) { double foffset = difftime(local_time, utc_time); int offset = int(fabs(foffset)); if (offset <= 43200) { ostringstream oss; oss << ((foffset >= 0) ? "+" : "-"); oss << (offset / 3600) << ":"; int minutes = ((offset % 3600) / 60); if (minutes < 10) { oss << "0"; } oss << minutes; timeoffset = oss.str(); } } } } queueCommand(new CommandFetchTimeZone(this, "", timeoffset.c_str())); } void MegaClient::keepmealive(int type, bool enable) { queueCommand(new CommandKeepMeAlive(this, type, enable)); } void MegaClient::getpsa(bool urlSupport) { queueCommand(new CommandGetPSA(urlSupport, this)); } void MegaClient::acknowledgeuseralerts() { useralerts.acknowledgeAll(); } void MegaClient::activateoverquota(dstime timeleft, bool isPaywall) { if (timeleft) { assert(!isPaywall); LOG_warn << "Bandwidth overquota for " << timeleft << " seconds"; overquotauntil = Waiter::ds + timeleft; for (auto& it : multi_transfers[GET]) { Transfer *t = it.second; t->bt.backoff(timeleft); if (t->slot && (t->state != TRANSFERSTATE_RETRYING || !t->slot->retrying || t->slot->retrybt.nextset() != overquotauntil)) { t->state = TRANSFERSTATE_RETRYING; t->slot->retrybt.backoff(timeleft); t->slot->retrying = true; app->transfer_failed(t, API_EOVERQUOTA, timeleft); ++performanceStats.transferTempErrors; } } } else if (setstoragestatus(isPaywall ? STORAGE_PAYWALL : STORAGE_RED)) { LOG_warn << "Storage overquota"; int start = (isPaywall) ? GET : PUT; // in Paywall state, none DLs/UPs can progress for (int d = start; d <= PUT; d += PUT - GET) { for (auto& it : multi_transfers[d]) { Transfer *t = it.second; t->bt.backoff(NEVER); if (t->slot) { t->state = TRANSFERSTATE_RETRYING; t->slot->retrybt.backoff(NEVER); t->slot->retrying = true; app->transfer_failed(t, isPaywall ? API_EPAYWALL : API_EOVERQUOTA, 0); ++performanceStats.transferTempErrors; } } } } } std::string MegaClient::getDeviceidHash() { string deviceIdHash; if (MegaClient::statsid.empty()) { fsaccess->statsid(&statsid); } string id = MegaClient::statsid; DEBUG_TEST_HOOK_DEVICE_ID(id); if (id.size()) { string hash; HashSHA256 hasher; hasher.add((const byte*)id.data(), unsigned(id.size())); hasher.get(&hash); Base64::btoa(hash, deviceIdHash); } return deviceIdHash; } // set warn level void MegaClient::warn(const char* msg) { LOG_warn << msg; warned = true; } // reset and return warnlevel bool MegaClient::warnlevel() { return warned ? (warned = false) | true : false; } // Preserve previous version attrs that should be kept void MegaClient::honorPreviousVersionAttrs(Node *previousNode, AttrMap &attrs) { if (previousNode) { for (const string& attr : Node::attributesToCopyIntoPreviousVersions) { nameid id = AttrMap::string2nameid(attr.c_str()); auto it = previousNode->attrs.map.find(id); if (it != previousNode->attrs.map.end()) { attrs.map[id] = it->second; } } } } // returns a matching child node by UTF-8 name (does not resolve name clashes) // folder nodes take precedence over file nodes // To improve performance, if this method is called several times over same folder // getChildren should be call before the first call to this method, // watch NodeManager::childNodeByNameType std::shared_ptr MegaClient::childnodebyname(const Node* p, const char* name, bool skipfolders) { string nname = name; if (!p || p->type == FILENODE) { return nullptr; } LocalPath::utf8_normalize(&nname); std::shared_ptr node; if (!skipfolders) { node = mNodeManager.childNodeByNameType(p, nname, FOLDERNODE); } if (!node) { node = mNodeManager.childNodeByNameType(p, nname, FILENODE); } return node; } // returns a matching child node by UTF-8 name (does not resolve name clashes) // folder nodes take precedence over file nodes // To improve performance, if this method is called several times over same folder // getChildren should be call before the first call to this method, // watch NodeManager::childNodeByNameType std::shared_ptr MegaClient::childnodebynametype(Node* p, const char* name, nodetype_t mustBeType) { string nname = name; if (!p || p->type == FILENODE) { return nullptr; } LocalPath::utf8_normalize(&nname); return mNodeManager.childNodeByNameType(p, nname, mustBeType); } // returns a matching child node that has the given attribute with the given value std::shared_ptr MegaClient::childnodebyattribute(Node* p, nameid attrId, const char* attrValue) { if (!p || p->type == FILENODE) { return nullptr; } // Using a DB query to avoid loading all children of 'p' (instead of only the matching // child nodes) will require to have dedicated columns for each attribute ID. // On top of that, this method is used exclusively upon creation of a new backup, // which implies ENABLE_SYNC. // (syncing always have all sync tree nodes in memory, so the DB query won't be faster) sharedNode_list childrenNodeList = getChildren(p); for (auto& child : childrenNodeList) { // find the attribute const auto& attrMap = child->attrs.map; auto found = attrMap.find(attrId); if (found != attrMap.end() && found->second == attrValue) { return child; } } return nullptr; } // returns all the matching child nodes by UTF-8 name sharedNode_vector MegaClient::childnodesbyname(Node* p, const char* name, bool skipfolders) { string nname = name; sharedNode_vector found; if (!p || p->type == FILENODE) { return found; } LocalPath::utf8_normalize(&nname); // TODO: a DB query could return the matching child nodes directly, avoiding to load all // children. However, currently this method is used only for internal sync tests. // (syncing always have all nodes in memory, so the DB query won't be faster) sharedNode_list nodeList = getChildren(p); for (sharedNode_list::iterator it = nodeList.begin(); it != nodeList.end(); it++) { if (nname == (*it)->displayname()) { if ((*it)->type == FILENODE || !skipfolders) { found.push_back(*it); } } } return found; } void MegaClient::init() { warned = false; csretrying = false; chunkfailed = false; statecurrent = false; actionpacketsCurrent = false; totalNodes.store(0); faretrying = false; #ifdef ENABLE_SYNC syncdebrisadding = false; syncdebrisminute = 0; #endif mNodeManager.setRootNodeFiles(NodeHandle()); mNodeManager.setRootNodeVault(NodeHandle()); mNodeManager.setRootNodeRubbish(NodeHandle()); pendingsc.reset(); pendingscUserAlerts.reset(); mBlocked = false; mBlockedSet = false; pendingcs_serverBusySent = false; btcs.reset(); mBackoffTimerLocklessCS.reset(); btsc.reset(); btpfa.reset(); btbadhost.reset(); btreqstat.reset(); abortlockrequest(); transferHttpCounter = 0; nextDispatchTransfersDs = 0; jsonsc.pos = NULL; insca = false; insca_notlast = false; scnotifyurl.clear(); mPendingCatchUps = 0; mReceivingCatchUp = false; scsn.clear(); // initialize random client application instance ID (for detecting own // actions in server-client stream) resetId(sessionid, sizeof sessionid, rng); mNotifiedSumSize = 0; mCurrentSeqtag.clear(); mPriorSeqTag.clear(); mCurrentSeqtagSeen = false; mCurrentSeqtagCmdtag = 0; mScDbStateRecord = ScDbStateRecord(); mLastReceivedScSeqTag.clear(); // Reset last known capacity. mLastKnownCapacity = -1; } MegaClient::MegaClient(MegaApp* a, shared_ptr w, HttpIO* h, DbAccess* d, GfxProc* g, const char* u, unsigned workerThreadCount, ClientType clientType): mAsyncQueue(*w, workerThreadCount), mCachedStatus(this), useralerts(*this), btugexpiration(rng), btcs(rng), mBackoffTimerLocklessCS(rng), btbadhost(rng), btworkinglock(rng), btreqstat(rng), btsc(rng), btpfa(rng), fsaccess(new FSACCESS_CLASS()), dbaccess(d), mNodeManager(*this), mIsS4Enabled{false}, mS4Container{NodeHandle()}, #ifdef ENABLE_SYNC syncs(*this), #endif reqs(rng), mReqsLockless(rng), mKeyManager(*this), mClientType(clientType), mJourneyId(), mClientAdapter(*this), mFileService(), mFuseService(mClientAdapter) { #ifdef __ANDROID__ if (!AndroidFileSystemAccess::isFileWrapperActive(fsaccess.get())) { LOG_verbose << "[MegaClient::MegaClient] replacing AndroidFileSystemAccess by " "LinuxFileSystemAccess due to missing FileWrapper in JNI"; fsaccess = std::make_unique(); } #endif mJourneyId = std::make_unique(fsaccess, dbaccess ? dbaccess->rootPath() : LocalPath()); mNodeManager.reset(); sctable.reset(); tctable = NULL; statusTable = nullptr; me = UNDEF; usealtdownport = false; usealtupport = false; retryessl = false; scpaused = false; asyncfopens = 0; achievements_enabled = false; isNewSession = false; tsLogin = 0; versions_disabled = false; accountsince = 0; accountversion = 0; gmfa_enabled = false; gfxdisabled = false; ssrs_enabled = false; aplvp_enabled = false; mSmsVerificationState = SMS_STATE_UNKNOWN; loggingout = 0; cachedug = false; minstreamingrate = -1; ephemeralSession = false; ephemeralSessionPlusPlus = false; #ifndef EMSCRIPTEN autodownport = true; autoupport = true; orderdownloadedchunks = false; #else autodownport = false; autoupport = false; orderdownloadedchunks = true; #endif fetchingnodes = false; fetchnodestag = 0; pendingcs = NULL; xferpaused[PUT] = false; xferpaused[GET] = false; mBizGracePeriodTs = 0; mBizExpirationTs = 0; mBizMode = BIZ_MODE_UNKNOWN; mBizStatus = BIZ_STATUS_UNKNOWN; overquotauntil = 0; ststatus = STORAGE_UNKNOWN; mOverquotaDeadlineTs = 0; mEd255Key = NULL; mX255Key = NULL; init(); fsaccess->waiter = w.get(); transferlist.client = this; app = a; if (app) { a->client = this; } waiter = w; httpio = h; gfx = g; if (gfx) { g->client = this; } slotit = tslots.end(); userid = 0; #if defined(__ANDROID__) || defined(USE_IOS) connections[PUT] = 3; connections[GET] = 4; #else connections[PUT] = 8; connections[GET] = 8; #endif LOG_debug << clientname << "MegaClient initial max connections: uploads = " << +connections[PUT] << ", downloads = " << +connections[GET]; reqtag = 0; badhostcs = NULL; scsn.clear(); cachedscsn = UNDEF; // initialize useragent useragent = u; useragent.append(" ("); fsaccess->osversion(&useragent, true); useragent.append(") MegaClient/" TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING(MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION)); useragent += sizeof(char*) == 8 ? "/64" : (sizeof(char*) == 4 ? "/32" : ""); LOG_debug << "User-Agent: " << useragent; LOG_debug << "Cryptopp version: " << CRYPTOPP_VERSION; LOG_debug << "icu version: " << Utils::getIcuVersion(); h->setuseragent(&useragent); h->setmaxdownloadspeed(0); h->setmaxuploadspeed(0); } MegaClient::~MegaClient() { LOG_debug << clientname << "~MegaClient running"; destructorRunning = true; locallogout(false, true); delete pendingcs; delete badhostcs; delete dbaccess; LOG_debug << clientname << "~MegaClient completing"; } void resetId(char *id, size_t length, PrnGen& rng) { for (size_t i = length; i--; ) { id[i] = static_cast('a' + rng.genuint32(26)); } } TypeOfLink MegaClient::validTypeForPublicURL(nodetype_t type) { bool error; TypeOfLink lType; std::tie(error, lType) = toTypeOfLink(type); if (error) { assert(false); LOG_err << "Attempting to get a public link for node type " << type << ". Only valid node types are folders (" << FOLDERNODE << ") and files (" << FILENODE << ")"; } return lType; } std::string MegaClient::publicLinkURL(bool newLinkFormat, TypeOfLink type, handle ph, const char *key) { string strlink = MegaClient::getMegaURL() + "/"; string nodeType; if (newLinkFormat) { static const map typeSchema = {{TypeOfLink::FOLDER, "folder/"} ,{TypeOfLink::FILE, "file/"} ,{TypeOfLink::SET, "collection/"} }; nodeType = typeSchema.at(type); } else if (type == TypeOfLink::SET) { LOG_err << "Requesting old link format URL for Set type"; return string(); } else { nodeType = (type == TypeOfLink::FOLDER ? "#F!" : "#!"); } strlink += nodeType; Base64Str base64ph(ph); strlink += base64ph; strlink += (newLinkFormat ? "#" : ""); if (key) { strlink += (newLinkFormat ? "" : "!"); strlink += key; } return strlink; } std::string MegaClient::getWritableLinkAuthKey(handle nodeHandle) { auto node = nodebyhandle(nodeHandle); if (node->plink) { return node->plink->mAuthKey; } return {}; } // nonblocking state machine executing all operations currently in progress void MegaClient::exec() { CodeCounter::ScopeTimer ccst(performanceStats.execFunction); WAIT_CLASS::bumpds(); if (overquotauntil && overquotauntil < Waiter::ds) { overquotauntil = 0; } if (httpio->inetisback()) { LOG_info << "Internet connectivity returned - resetting all backoff timers"; abortbackoff(overquotauntil <= Waiter::ds); } if (EVER(httpio->lastdata) && Waiter::ds >= httpio->lastdata + HttpIO::NETWORKTIMEOUT && !pendingcs) { LOG_debug << "Network timeout. Reconnecting"; disconnect(); } else if (EVER(disconnecttimestamp)) { if (disconnecttimestamp <= Waiter::ds) { LOG_debug << "Timeout (server idle)"; sendevent(99427, "Timeout (server idle)", 0); disconnect(); } } else if (pendingcs && EVER(pendingcs->lastdata) && !requestLock && !fetchingnodes && Waiter::ds >= pendingcs->lastdata + HttpIO::REQUESTTIMEOUT) { LOG_debug << clientname << "Request timeout. Triggering a lock request"; app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, LOCAL_ETIMEOUT); requestLock = true; } // successful network operation with a failed transfer chunk: increment error count // and continue transfers if (httpio->success && chunkfailed) { chunkfailed = false; for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); it++) { if ((*it)->failure) { (*it)->lasterror = API_EFAILED; (*it)->errorcount++; (*it)->failure = false; (*it)->lastdata = Waiter::ds; LOG_warn << "Transfer error count raised: " << (*it)->errorcount; app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_ERROR, API_EFAILED); } } } bool first = true; do { if (!first) { WAIT_CLASS::bumpds(); } first = false; if (cachedug && btugexpiration.armed()) { LOG_debug << "Cached user data expired"; getuserdata(reqtag); fetchtimezone(); } if (pendinghttp.size()) { pendinghttp_map::iterator it = pendinghttp.begin(); while (it != pendinghttp.end()) { GenericHttpReq *req = it->second; switch (static_cast(req->status)) { case REQ_FAILURE: if (!req->httpstatus && (!req->maxretries || (req->numretry + 1) < req->maxretries)) { if (req->mDnsFailure) { app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType ::REQUEST_SENT, LOCAL_ENETWORK); } req->numretry++; req->status = REQ_PREPARED; req->bt.backoff(); req->isbtactive = true; LOG_warn << "Request failed (" << req->posturl << ") retrying (" << (req->numretry + 1) << " of " << req->maxretries << ")"; it++; break; } app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, req->httpstatus == 0 ? API_EFAILED : req->httpstatus); // no retry -> fall through // fall through case REQ_SUCCESS: { restag = it->first; m_off_t actualLength = req->buf != nullptr || req->mChunked ? req->bufpos : static_cast(req->in.size()); app->http_result(req->httpstatus ? API_OK : API_EFAILED, req->httpstatus, req->buf ? (byte*)req->buf : (byte*)req->in.data(), actualLength); delete req; pendinghttp.erase(it++); break; } case REQ_PREPARED: if (req->bt.armed()) { req->isbtactive = false; LOG_debug << "Sending retry for " << req->posturl; switch (req->method) { case METHOD_GET: req->get(this); break; case METHOD_POST: req->post(this); break; case METHOD_NONE: req->dns(this); break; } it++; break; } // no retry -> fall through // fall through case REQ_INFLIGHT: if (req->maxbt.nextset() && req->maxbt.armed()) { LOG_debug << "Max total time exceeded for request: " << req->posturl; app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, API_EFAILED); restag = it->first; app->http_result(API_EFAILED, 0, NULL, 0); delete req; pendinghttp.erase(it++); break; } // fall through default: it++; } } } // file attribute puts (handled sequentially as a FIFO) if (activefa.size()) { TransferDbCommitter committer(tctable); auto curfa = activefa.begin(); while (curfa != activefa.end()) { shared_ptr fa = *curfa; auto erasePos = curfa; ++curfa; m_off_t p = fa->transferred(this); if (fa->progressreported < p) { httpio->updateuploadspeed(p - fa->progressreported); fa->progressreported = p; } switch (static_cast(fa->status)) { case REQ_SUCCESS: if (fa->in.size() == sizeof(handle)) { LOG_debug << "File attribute uploaded OK - " << fa->th; // successfully wrote file attribute - store handle & // remove from list handle fah = MemAccess::get(fa->in.data()); if (fa->th.isUndef()) { // client app requested the upload without a node yet, and it will use the fa handle restag = fa->tag; app->putfa_result(fah, fa->type, API_OK); } else { // do we have a valid upload handle? if (fa->th.isNodeHandle()) { if (std::shared_ptr n = nodeByHandle(fa->th.nodeHandle())) { LOG_debug << "Attaching file attribute to Node"; queueCommand(new CommandAttachFA(this, n->nodehandle, fa->type, fah, fa->tag)); } else { LOG_debug << "Node to attach file attribute to no longer exists"; restag = fa->tag; app->putfa_result(fa->th.nodeHandle().as8byte(), fa->type, API_ENOENT); } } else { if (auto uploadFAPtr = fileAttributesUploading.lookupExisting(fa->th.uploadHandle())) { if (auto waitingFAPtr = uploadFAPtr->pendingfa.lookupExisting(fa->type)) { waitingFAPtr->fileAttributeHandle = fah; waitingFAPtr->valueIsSet = true; LOG_debug << "File attribute, type " << fa->type << " for upload handle " << fa->th << " received: " << toHandle(fah); checkfacompletion(fa->th.uploadHandle()); } else { LOG_debug << "File attribute, type " << fa->type << " for upload handle " << fa->th << " received, but that type was no longer needed"; } } else { LOG_debug << "File attribute, type " << fa->type << " for upload handle " << fa->th << " received, but the upload was previously resolved"; } } } } else { if (fa->th.isNodeHandle()) { // TODO: possibly another gap here where we were generating for an existing Node, code only dealt with the upload-in-progress case LOG_warn << "Error returned from file attribute servers for existing Node case: " << fa->in; } else { if (auto uploadFAPtr = fileAttributesUploading.lookupExisting(fa->th.uploadHandle())) { LOG_debug << "File attribute, type " << fa->type << " for upload handle " << fa->th << " failed. Discarding the need for it"; uploadFAPtr->pendingfa.erase(fa->type); } else { LOG_debug << "File attribute, type " << fa->type << " for upload handle " << fa->th << " failed, but the upload was previously resolved"; } checkfacompletion(fa->th.uploadHandle()); sendevent(99407,"Attribute attach failed during active upload", 0); } } activefa.erase(erasePos); LOG_debug << "Remaining file attributes: " << activefa.size() << " active, " << queuedfa.size() << " queued"; btpfa.reset(); faretrying = false; break; case REQ_FAILURE: // repeat request with exponential backoff LOG_warn << "Error setting file attribute. Will retry after backoff"; app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, API_EFAILED); activefa.erase(erasePos); fa->status = REQ_READY; queuedfa.push_back(fa); btpfa.backoff(); faretrying = true; break; case REQ_INFLIGHT: // check if the transfer/file was cancelled while we were waiting for fa response (only for file uploads, not Node updates or app-requested fa put) if (!fa->th.isNodeHandle()) { if (auto uploadFAPtr = fileAttributesUploading.lookupExisting(fa->th.uploadHandle())) { uploadFAPtr->transfer->removeCancelledTransferFiles(&committer); if (uploadFAPtr->transfer->files.empty()) { // this also removes it from slots and fileAttributesUploading activefa.erase(erasePos); uploadFAPtr->transfer->removeAndDeleteSelf(TRANSFERSTATE_CANCELLED); } } else { LOG_debug << "activefa for " << fa->th.uploadHandle() << " has been orphaned, discarding"; activefa.erase(erasePos); } } break; default: // other cases are not relevant for this one break; } } } if (btpfa.armed()) { faretrying = false; activatefa(); } if (fafcs.size()) { // file attribute fetching (handled in parallel on a per-cluster basis) // cluster channels are never purged fafc_map::iterator cit; FileAttributeFetchChannel* fc; for (cit = fafcs.begin(); cit != fafcs.end(); cit++) { fc = cit->second; // is this request currently in flight? switch (static_cast(fc->req.status)) { case REQ_SUCCESS: fc->parse(cit->first, true); // notify app in case some attributes were not returned, then redispatch fc->failed(); fc->req.disconnect(); fc->req.status = REQ_PREPARED; fc->timeout.reset(); fc->bt.reset(); break; case REQ_INFLIGHT: if (!fc->req.httpio) { break; } if (fc->inbytes != fc->req.in.size()) { httpio->lock(); fc->parse(cit->first, false); httpio->unlock(); fc->timeout.backoff(100); fc->inbytes = fc->req.in.size(); } if (!fc->timeout.armed()) break; LOG_warn << "Timeout getting file attr"; // timeout! // fall through case REQ_FAILURE: LOG_warn << "Error getting file attr"; app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, API_EFAILED); fc->failed(); fc->timeout.reset(); fc->bt.backoff(); fc->urltime = 0; fc->req.disconnect(); fc->req.status = REQ_PREPARED; default: ; } if (fc->req.status != REQ_INFLIGHT && fc->bt.armed() && (fc->fafs[1].size() || fc->fafs[0].size())) { fc->req.in.clear(); if (!fc->urltime || (Waiter::ds - fc->urltime) > 600) { // fetches pending for this unconnected channel - dispatch fresh connection LOG_debug << "Getting fresh download URL"; fc->timeout.reset(); queueCommand(new CommandGetFA(this, cit->first, fc->fahref)); fc->req.status = REQ_INFLIGHT; } else { // redispatch cached URL if not older than one minute LOG_debug << "Using cached download URL"; fc->dispatch(); } } } } // handle API client-server requests for (;;) { // do we have an API request outstanding? if (pendingcs) { // handle retry reason for requests retryreason_t reason = RETRY_NONE; if (pendingcs->status == REQ_SUCCESS || pendingcs->status == REQ_FAILURE) { performanceStats.csRequestWaitTime.stop(); } switch (static_cast(pendingcs->status)) { case REQ_READY: break; case REQ_INFLIGHT: if (pendingcs->contentlength > 0) { if (fetchingnodes && fnstats.timeToFirstByte == NEVER && pendingcs->bufpos > 10) { WAIT_CLASS::bumpds(); fnstats.timeToFirstByte = WAIT_CLASS::ds - fnstats.startTime; } if (pendingcs->bufpos > pendingcs->notifiedbufpos) { if (pendingcs->mChunked) { size_t consumedBytes = reqs.serverChunk(pendingcs->data(), this); JSON_CHUNK_CONSUMED << "Consumed a chunk of " << consumedBytes << " bytes. " << "Total: " << reqs.chunkedProgress() << " of " << pendingcs->contentlength << ". " << MaxDirectMessage(pendingcs->data(), consumedBytes, CONSUMED_CHUNK_MAX_LOGGING); pendingcs->purge(consumedBytes); } abortlockrequest(); app->request_response_progress(pendingcs->bufpos, pendingcs->contentlength); pendingcs->notifiedbufpos = pendingcs->bufpos; } } break; case REQ_SUCCESS: abortlockrequest(); app->request_response_progress(pendingcs->bufpos, pendingcs->contentlength); if ((!pendingcs->mChunked && pendingcs->in != "-3" && pendingcs->in != "-4") || (pendingcs->mChunked && (reqs.chunkedProgress() || (pendingcs->in != "-3" && pendingcs->in != "-4")))) { // Have we been telling the application about request progress? if (std::exchange(mRequestProgressNotified, false)) { // Let the application know that the request has completed. app->reqstat_progress(-1); } if ((!pendingcs->mChunked && *pendingcs->in.c_str() == '[') || (pendingcs->mChunked && (reqs.chunkedProgress() || *pendingcs->in.c_str() == '[' || pendingcs->in.empty()))) { CodeCounter::ScopeTimer successProcessingTime( performanceStats.csSuccessProcessingTime); if (fetchingnodes && fnstats.timeToFirstByte == NEVER) { WAIT_CLASS::bumpds(); fnstats.timeToFirstByte = WAIT_CLASS::ds - fnstats.startTime; } if (csretrying) { app->notify_retry(0, RETRY_NONE); csretrying = false; } if (!pendingcs->mChunked) { // request succeeded, process result array reqs.serverresponse(std::move(pendingcs->in), this); } else { size_t consumedBytes = reqs.serverChunk(pendingcs->data(), this); if (consumedBytes) { JSON_CHUNK_CONSUMED << "Consumed the last chunk of " << consumedBytes << " bytes. " << MaxDirectMessage(pendingcs->data(), consumedBytes, CONSUMED_CHUNK_MAX_LOGGING); } // The requests should be already terminated assert(!reqs.chunkedProgress()); } WAIT_CLASS::bumpds(); delete pendingcs; pendingcs = NULL; notifypurge(); if (auto completion = std::move(mOnCSCompletion)) { LOG_debug << "calling mOnCSCompletion after request reply processing"; // track possible lack of logout callbacks assert(mOnCSCompletion == nullptr); completion(this); } } else { // request failed JSON json; json.pos = pendingcs->in.c_str(); std::string requestError; error e; bool valid = json.storeobject(&requestError); if (valid) { if (strncmp(requestError.c_str(), "{\"err\":", 7) == 0) { e = (error)atoi(requestError.c_str() + 7); } else { e = (error)atoi(requestError.c_str()); } } else { e = API_EINTERNAL; requestError = std::to_string(e); } if (!e) { e = API_EINTERNAL; requestError = std::to_string(e); } if (e == API_EBLOCKED && sid.size()) { block(); } // A failed request implies any retry in progress has ended. if (csretrying) app->notify_retry(0, RETRY_NONE); app->request_error(e); delete pendingcs; pendingcs = NULL; csretrying = false; reqs.servererror(requestError, this); break; } btcs.reset(); break; } else { if (pendingcs->in == "-3") { reason = RETRY_API_LOCK; } else { reason = RETRY_RATE_LIMIT; } if (fetchingnodes) { fnstats.eAgainCount++; } } // fall through case REQ_FAILURE: if (pendingcs->httpstatus == 402) { // get X-Hashcash header if (pendingcs->mHashcashToken.empty()) { LOG_err << "X-Hashcash header missing for HTTP status " << pendingcs->httpstatus; } else if (pendingcs->contentlength > 0) { LOG_err << "Content-Length not 0, as it should be when X-Hashcash " "header was received"; } else { mReqHashcashToken = std::move(pendingcs->mHashcashToken); pendingcs->mHashcashToken.clear(); // just to be sure mReqHashcashEasiness = pendingcs->mHashcashEasiness; } processHashcashSendevent(); } if (!reason && pendingcs->httpstatus != 200) { if (pendingcs->httpstatus == 500) { reason = RETRY_SERVERS_BUSY; } else if (pendingcs->httpstatus == 0) { if (pendingcs->mDnsFailure) { app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType ::REQUEST_SENT, LOCAL_ENETWORK); } reason = RETRY_CONNECTIVITY; } } if (fetchingnodes && pendingcs->httpstatus != 200) { if (pendingcs->httpstatus == 500) { fnstats.e500Count++; } else { fnstats.eOthersCount++; } } abortlockrequest(); if (pendingcs->sslcheckfailed) { sendevent(99453, "Invalid public key"); sslfakeissuer = pendingcs->sslfakeissuer; app->request_error(API_ESSL); sslfakeissuer.clear(); if (!retryessl) { delete pendingcs; pendingcs = NULL; csretrying = false; reqs.servererror(std::to_string(API_ESSL), this); if (std::exchange(mRequestProgressNotified, false)) { app->reqstat_progress(-1); } app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, API_ESSL); break; } } // failure, repeat with capped exponential backoff app->request_response_progress(pendingcs->bufpos, -1); delete pendingcs; pendingcs = NULL; if (!reason) reason = RETRY_UNKNOWN; btcs.backoff(); app->notify_retry(btcs.retryin(), reason); csretrying = true; LOG_warn << "Retrying cs request in " << btcs.retryin() << " ds"; app->notify_network_activity(NetworkActivityChannel::CS, NetworkActivityType::REQUEST_ERROR, API_EAGAIN); // the in-progress request will be resent, unchanged (for idempotence), when we are ready again. reqs.inflightFailure(reason); default: ; } if (pendingcs) { break; } } if (btcs.armed()) { if (reqs.readyToSend()) { abortlockrequest(); pendingcs = new HttpReq(); pendingcs->protect = true; pendingcs->setLogName(clientname + "cs "); pendingcs_serverBusySent = false; pendingcs->mCancelSnapshot = mLoginCancelSnapshot; string idempotenceId; *pendingcs->out = reqs.serverrequest(pendingcs->includesFetchingNodes, this, idempotenceId); pendingcs->posturl = httpio->APIURL; pendingcs->posturl.append("cs?id="); pendingcs->posturl.append(idempotenceId); pendingcs->posturl.append(getAuthURI()); pendingcs->posturl.append("&v=3"); if (lang.size()) { pendingcs->posturl.append("&"); pendingcs->posturl.append(lang); } if (trackJourneyId()) { pendingcs->posturl.append("&j="); pendingcs->posturl.append(mJourneyId->getValue()); } pendingcs->type = REQ_JSON; if (pendingcs->includesFetchingNodes && !mNodeManager.hasCacheLoaded()) { // Currently only fetchnodes requests can take advantage of chunked processing // However VPN client shouldn't need it, because it'll receive a minimal response pendingcs->mChunked = !isClientType(ClientType::VPN); } // Remember whether we've told the app about request progress. mRequestProgressNotified = mReqStatEnabled && mRequestProgress; // Immediately notify application of request progress. if (mRequestProgressNotified) { app->reqstat_progress(*mRequestProgress); } pendingcs->mHashcashToken = std::move(mReqHashcashToken); mReqHashcashToken.clear(); pendingcs->mHashcashEasiness = mReqHashcashEasiness; performanceStats.csRequestWaitTime.start(); pendingcs->post(this); continue; } else { btcs.reset(); } } break; } // handle API lockless client-server requests for (;;) { // do we have an API lockless request outstanding? if (mPendingLocklessCS) { // handle retry reason for requests retryreason_t reason = RETRY_NONE; DEBUG_TEST_HOOK_INTERCEPT_LOCKLESS_CS_REQUEST(mPendingLocklessCS); switch (mPendingLocklessCS->status) { case REQ_READY: break; case REQ_INFLIGHT: if (Waiter::ds >= mPendingLocklessCS->lastdata + HttpIO::REQUESTTIMEOUT) { LOG_warn << clientname << "Lockless CS request timeout. Retry."; mPendingLocklessCS.reset(); mBackoffTimerLocklessCS.reset(); // Already waited for REQUESTTIMEOUT mReqsLockless.inflightFailure(RETRY_CONNECTIVITY); } break; case REQ_SUCCESS: if (mPendingLocklessCS->in != "-3" && mPendingLocklessCS->in != "-4") { if (*mPendingLocklessCS->in.c_str() == '[') { // request succeeded, process result array mReqsLockless.serverresponse(std::move(mPendingLocklessCS->in), this); mPendingLocklessCS.reset(); } else { // request failed JSON json; json.pos = mPendingLocklessCS->in.c_str(); error e; std::string requestError; bool valid = json.storeobject(&requestError); if (valid) { if (strncmp(requestError.c_str(), "{\"err\":", 7) == 0) { e = (error)atoi(requestError.c_str() + 7); } else { e = (error)atoi(requestError.c_str()); } } else { e = API_EINTERNAL; requestError = std::to_string(e); } if (!e) { e = API_EINTERNAL; requestError = std::to_string(e); } mPendingLocklessCS.reset(); mReqsLockless.servererror(requestError, this); break; } mBackoffTimerLocklessCS.reset(); break; } else { if (mPendingLocklessCS->in == "-3") { reason = RETRY_API_LOCK; } else { reason = RETRY_RATE_LIMIT; } } [[fallthrough]]; case REQ_FAILURE: if (!reason && mPendingLocklessCS->httpstatus != 200) { if (mPendingLocklessCS->httpstatus == 500) { reason = RETRY_SERVERS_BUSY; } else if (mPendingLocklessCS->httpstatus == 0) { reason = RETRY_CONNECTIVITY; } } if (mPendingLocklessCS->sslcheckfailed) { if (!retryessl) { mPendingLocklessCS.reset(); mReqsLockless.servererror(std::to_string(API_ESSL), this); break; } } // failure, repeat with capped exponential backoff mPendingLocklessCS.reset(); if (!reason) reason = RETRY_UNKNOWN; mBackoffTimerLocklessCS.backoff(); LOG_warn << clientname << "Retrying lockless cs request in " << mBackoffTimerLocklessCS.retryin() << " ds"; // the in-progress request will be resent, unchanged (for idempotence), when // we are ready again. mReqsLockless.inflightFailure(reason); break; default: break; } if (mPendingLocklessCS) { break; } } if (mBackoffTimerLocklessCS.armed()) { if (mReqsLockless.readyToSend()) { mPendingLocklessCS = std::make_unique(); mPendingLocklessCS->protect = true; mPendingLocklessCS->setLogName(clientname + "llcs "); string idempotenceId; *mPendingLocklessCS->out = mReqsLockless.serverrequest(mPendingLocklessCS->includesFetchingNodes, this, idempotenceId); mPendingLocklessCS->posturl = httpio->APIURL; mPendingLocklessCS->posturl.append("cs?id="); mPendingLocklessCS->posturl.append(idempotenceId); mPendingLocklessCS->posturl.append(getAuthURI()); mPendingLocklessCS->posturl.append("&v=3"); if (lang.size()) { mPendingLocklessCS->posturl.append("&"); mPendingLocklessCS->posturl.append(lang); } if (trackJourneyId()) { mPendingLocklessCS->posturl.append("&j="); mPendingLocklessCS->posturl.append(mJourneyId->getValue()); } mPendingLocklessCS->type = REQ_JSON; mPendingLocklessCS->post(this); continue; } else { mBackoffTimerLocklessCS.reset(); } } break; } // handle the request for the last 50 UserAlerts if (pendingscUserAlerts) { switch (static_cast(pendingscUserAlerts->status)) { case REQ_SUCCESS: if (*pendingscUserAlerts->in.c_str() == '{') { // there should be no User Alerts when logged into folder assert(!loggedIntoFolder()); JSON json; json.begin(pendingscUserAlerts->in.c_str()); json.enterobject(); if (useralerts.procsc_useralert(json)) { // NULL vector: "notify all elements" app->useralerts_updated(NULL, int(useralerts.alerts.size())); // there are no 'removed' alerts at this point } pendingscUserAlerts.reset(); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, API_OK); break; } // fall through case REQ_FAILURE: if (pendingscUserAlerts->httpstatus == 200) { error e = (error)atoi(pendingscUserAlerts->in.c_str()); if (e == API_EAGAIN || e == API_ERATELIMIT) { btsc.backoff(); pendingscUserAlerts.reset(); LOG_warn << "Backing off before retrying useralerts request: " << btsc.retryin(); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, e); break; } LOG_err << "Unexpected sc response: " << pendingscUserAlerts->in; } LOG_warn << "Useralerts request failed, continuing without them"; if (pendingscUserAlerts->mDnsFailure) { app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType ::REQUEST_SENT, LOCAL_ENETWORK); } if (useralerts.begincatchup) { useralerts.begincatchup = false; useralerts.catchupdone = true; } pendingscUserAlerts.reset(); break; default: break; } } // handle API server-client requests handleScChannel(); if (!pendingsc && !pendingscUserAlerts && scsn.ready() && btsc.armed() && !mBlocked) { if (useralerts.begincatchup) { pendingscUserAlerts.reset(new HttpReq()); pendingscUserAlerts->setLogName(clientname + "sc50 "); pendingscUserAlerts->protect = true; pendingscUserAlerts->posturl = httpio->APIURL; pendingscUserAlerts->posturl.append("sc"); // notifications/useralerts on sc rather than wsc, no timeout pendingscUserAlerts->posturl.append("?c=50"); pendingscUserAlerts->posturl.append(getAuthURI()); pendingscUserAlerts->type = REQ_JSON; pendingscUserAlerts->post(this); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_SENT, API_OK); } else { pendingsc.reset(new HttpReq()); pendingsc->setLogName(clientname + "sc "); if (mPendingCatchUps && !mReceivingCatchUp) { scnotifyurl.clear(); pendingsc->posturl = httpio->APIURL; pendingsc->posturl.append("sc/wsc"); mReceivingCatchUp = true; } else { if (scnotifyurl.size()) { pendingsc->posturl = scnotifyurl; } else { pendingsc->posturl = httpio->APIURL; pendingsc->posturl.append("wsc"); } } pendingsc->protect = true; pendingsc->posturl.append("?sn="); pendingsc->posturl.append(scsn.text()); // folder links should not send "sid" to avoid receiving packets unrelated to the folder link bool suppressSID = loggedIntoFolder() ? true : false; pendingsc->posturl.append(getAuthURI(suppressSID, true)); if (isClientType(ClientType::PASSWORD_MANAGER) || isClientType(ClientType::VPN)) { pendingsc->posturl.append(getPartialAPs()); } pendingsc->type = REQ_JSON; pendingsc->post(this); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_SENT, API_OK); } jsonsc.pos = NULL; } if (badhostcs) { if (badhostcs->status == REQ_SUCCESS) { LOG_debug << "Successful badhost report"; btbadhost.reset(); delete badhostcs; badhostcs = NULL; } else if(badhostcs->status == REQ_FAILURE || (badhostcs->status == REQ_INFLIGHT && Waiter::ds >= (badhostcs->lastdata + HttpIO::REQUESTTIMEOUT))) { LOG_debug << "Failed badhost report. Retrying..."; btbadhost.backoff(); badhosts = badhostcs->outbuf; delete badhostcs; badhostcs = NULL; } } if (workinglockcs) { if (workinglockcs->status == REQ_SUCCESS) { LOG_debug << "Successful lock request"; btworkinglock.reset(); if (workinglockcs->in == "1") { LOG_warn << "Timeout (server idle)"; disconnecttimestamp = Waiter::ds + HttpIO::CONNECTTIMEOUT; } else if (workinglockcs->in == "0") { if (!pendingcs_serverBusySent) { sendevent(99425, "Timeout (server busy)", 0); pendingcs_serverBusySent = true; } pendingcs->lastdata = Waiter::ds; } else { LOG_err << "Error in lock request: " << workinglockcs->in; disconnecttimestamp = Waiter::ds + HttpIO::CONNECTTIMEOUT; } workinglockcs.reset(); requestLock = false; } else if (workinglockcs->status == REQ_FAILURE || (workinglockcs->status == REQ_INFLIGHT && Waiter::ds >= (workinglockcs->lastdata + HttpIO::REQUESTTIMEOUT))) { LOG_warn << "Failed lock request. Retrying..."; btworkinglock.backoff(); workinglockcs.reset(); } } if (mReqStatCS) { if (mReqStatCS->status == REQ_SUCCESS) { if (mReqStatCS->isRedirection() && mReqStatCS->mRedirectURL.size()) { std::string reqstaturl = mReqStatCS->mRedirectURL; LOG_debug << "Accessing reqstat URL: " << reqstaturl; mReqStatCS.reset(new HttpReq()); mReqStatCS->setLogName(clientname + "reqstat "); mReqStatCS->posturl = reqstaturl; mReqStatCS->type = REQ_BINARY; mReqStatCS->binary = true; mReqStatCS->protect = true; mReqStatCS->get(this); } else { LOG_debug << "Successful reqstat request"; btreqstat.reset(); mReqStatCS.reset(); } } else if (mReqStatCS->status == REQ_FAILURE) { LOG_err << "Failed reqstat request. Retrying"; btreqstat.backoff(); mReqStatCS.reset(); } else if (mReqStatCS->status == REQ_INFLIGHT) { if (mReqStatCS->in.size()) { size_t bytesConsumed = procreqstat(); if (bytesConsumed) { btreqstat.reset(); mReqStatCS->in.erase(0, bytesConsumed); } } } } // fill transfer slots from the queue if (nextDispatchTransfersDs <= Waiter::ds) { size_t lastCount = 0; size_t transferCount = multi_transfers[GET].size() + multi_transfers[PUT].size(); do { lastCount = transferCount; // Check the list of transfers and start a few big files, and many small, up to configured limits. dispatchTransfers(); // if we are cancelling a lot of transfers (eg. nodes to download were deleted), keep going. Avoid stalling when no transfers are active and all queued fail transferCount = multi_transfers[GET].size() + multi_transfers[PUT].size(); } while (transferCount < lastCount); // don't run this too often or it may use a lot of cpu without starting new transfers, if the list is long nextDispatchTransfersDs = transferCount ? Waiter::ds + 1 : 0; } #ifndef EMSCRIPTEN assert(!asyncfopens); #endif slotit = tslots.begin(); if (!mBlocked) // handle active unpaused transfers { TransferDbCommitter committer(tctable); while (slotit != tslots.end()) { transferslot_list::iterator it = slotit; slotit++; // remove transfer files whose MegaTransfer associated has been cancelled (via cancel token) (*it)->transfer->removeCancelledTransferFiles(&committer); if ((*it)->transfer->files.empty()) { // this also removes it from slots (*it)->transfer->removeAndDeleteSelf(TRANSFERSTATE_CANCELLED); } else if ((!xferpaused[(*it)->transfer->type] || (*it)->transfer->isForSupport()) && (!(*it)->retrying || (*it)->retrybt.armed())) { (*it)->doio(this, committer); } } } else { LOG_debug << "skipping slots doio while blocked"; } #ifdef ENABLE_SYNC if (!pendingDebris.empty()) { // in case we need to retry after a prior failure getOrCreateSyncdebrisFolder(); } setSyncUploadThrottleParamsFromAPIAfterTimeout(); if (!syncs.clientThreadActions.empty()) { CodeCounter::ScopeTimer clientActionTime(performanceStats.clientThreadActions); dstime ctr_start = waiter->ds; size_t ctr_N = 0; TransferDbCommitter committer(tctable); std::function f; waiter->bumpds(); while (ctr_start + 5 >= waiter->ds && syncs.clientThreadActions.popFront(f)) { f(*this, committer); ++ctr_N; waiter->bumpds(); } if (auto n = syncs.clientThreadActions.size()) { LOG_debug << "Processed " << ctr_N << " sync requests in " << (waiter->ds - ctr_start) << "ms, " << n << " requests outstanding"; } } auto noPutnodes = transferBackstop.getAbandoned(); for (auto& np : noPutnodes) { restag = np->tag; app->file_removed(np.get(), API_EINCOMPLETE); } noPutnodes.clear(); #endif // Dispatch client-side requests. mClientAdapter.dispatch(); notifypurge(); if (!badhostcs && badhosts.size() && btbadhost.armed()) { // report hosts affected by failed requests LOG_debug << "Sending badhost report: " << badhosts; badhostcs = new HttpReq(); badhostcs->posturl = httpio->APIURL; badhostcs->posturl.append("pf?h"); badhostcs->outbuf = badhosts; badhostcs->type = REQ_JSON; badhostcs->post(this); badhosts.clear(); } if (!workinglockcs && requestLock && btworkinglock.armed()) { string auth = getAuthURI(); if (auth.size()) { LOG_debug << clientname << "Sending lock request"; workinglockcs.reset(new HttpReq()); workinglockcs->setLogName(clientname + "accountBusyCheck "); workinglockcs->posturl = httpio->APIURL; workinglockcs->posturl.append("cs?"); workinglockcs->posturl.append(getAuthURI()); workinglockcs->posturl.append("&wlt=1"); workinglockcs->type = REQ_JSON; workinglockcs->post(this); } else if (!EVER(disconnecttimestamp)) { LOG_warn << "Possible server timeout, but we don't have auth yet, disconnect and retry"; disconnecttimestamp = Waiter::ds + HttpIO::CONNECTTIMEOUT; } } if (!mReqStatCS && sid.size() && btreqstat.armed()) { LOG_debug << clientname << "Sending reqstat request"; mReqStatCS.reset(new HttpReq()); mReqStatCS->setLogName(clientname + "reqstat "); mReqStatCS->mExpectRedirect = true; mReqStatCS->posturl = httpio->APIURL; mReqStatCS->posturl.append("cs/rs?sid="); mReqStatCS->posturl.append(Base64::btoa(sid)); mReqStatCS->type = REQ_BINARY; mReqStatCS->protect = true; mReqStatCS->binary = true; mReqStatCS->post(this); } for (vector::iterator it = bttimers.begin(); it != bttimers.end(); ) { TimerWithBackoff *bttimer = *it; if (bttimer->armed()) { restag = bttimer->tag; app->timer_result(API_OK); delete bttimer; it = bttimers.erase(it); } else { ++it; } } httpio->updatedownloadspeed(); httpio->updateuploadspeed(); } while (httpio->doio() || execdirectreads() || (!pendingcs && reqs.readyToSend() && btcs.armed())); if (!fetchingnodes) { NodeCounter nc = mNodeManager.getCounterOfRootNodes(); m_off_t sum = nc.storage + nc.versionStorage; if (mNotifiedSumSize != sum) { mNotifiedSumSize = sum; app->storagesum_changed(mNotifiedSumSize); } } #ifdef MEGA_MEASURE_CODE ccst.complete(); performanceStats.transfersActiveTime.start(!tslots.empty() && !performanceStats.transfersActiveTime.inprogress()); performanceStats.transfersActiveTime.stop(tslots.empty() && performanceStats.transfersActiveTime.inprogress()); static auto lasttime = Waiter::ds; static unsigned reportFreqDs = 6000; if (Waiter::ds > lasttime + reportFreqDs) { lasttime = Waiter::ds; LOG_info << performanceStats.report(false, httpio, waiter.get(), reqs); debugLogHeapUsage(); } #endif #ifdef USE_DRIVE_NOTIFICATIONS // check for Drive [dis]connects for (auto di = mDriveInfoCollector.get(); !di.first.empty(); di = mDriveInfoCollector.get()) { app->drive_presence_changed(di.second, LocalPath::fromPlatformEncodedAbsolute(std::move(di.first))); } #endif reportLoggedInChanges(); } // get next event time from all subsystems, then invoke the waiter if needed // returns true if an engine-relevant event has occurred, false otherwise int MegaClient::wait() { int r = preparewait(); if (r) { return r; } r |= dowait(); r |= checkevents(); return r; } int MegaClient::preparewait() { CodeCounter::ScopeTimer ccst(performanceStats.prepareWait); dstime nds; // get current dstime and clear wait events WAIT_CLASS::bumpds(); #ifdef ENABLE_SYNC if (!syncs.clientThreadActions.empty()) { nds = Waiter::ds; return Waiter::NEEDEXEC; } else #endif { // next retry of a failed transfer nds = NEVER; if (httpio->success && chunkfailed) { // there is a pending transfer retry, don't wait nds = Waiter::ds; } nexttransferretry(PUT, &nds); nexttransferretry(GET, &nds); // retry transferslots transferSlotsBackoff.update(&nds, false); // newly queued transfers if (nextDispatchTransfersDs) nds = std::max(nextDispatchTransfersDs, Waiter::ds.load()); for (pendinghttp_map::iterator it = pendinghttp.begin(); it != pendinghttp.end(); it++) { if (it->second->isbtactive) { it->second->bt.update(&nds); } if (it->second->maxbt.nextset()) { it->second->maxbt.update(&nds); } } // retry failed client-server requests if (!pendingcs) { btcs.update(&nds); } if (!mPendingLocklessCS) { mBackoffTimerLocklessCS.update(&nds); } // retry failed server-client requests if (!pendingsc && !pendingscUserAlerts && scsn.ready() && !mBlocked) { btsc.update(&nds); } // retry failed badhost requests if (!badhostcs && badhosts.size()) { btbadhost.update(&nds); } if (!workinglockcs && requestLock) { btworkinglock.update(&nds); } if (!mReqStatCS && sid.size()) { btreqstat.update(&nds); } for (vector::iterator cit = bttimers.begin(); cit != bttimers.end(); cit++) { (*cit)->update(&nds); } // retry failed file attribute puts if (faretrying) { btpfa.update(&nds); } // retry failed file attribute gets for (fafc_map::iterator cit = fafcs.begin(); cit != fafcs.end(); cit++) { if (cit->second->req.status == REQ_INFLIGHT) { cit->second->timeout.update(&nds); } else if (cit->second->fafs[1].size() || cit->second->fafs[0].size()) { cit->second->bt.update(&nds); } } // next pending pread event if (!dsdrns.empty()) { if (dsdrns.begin()->first < nds) { if (dsdrns.begin()->first <= Waiter::ds) { nds = Waiter::ds; } else { nds = dsdrns.begin()->first; } } } if (cachedug) { btugexpiration.update(&nds); } // detect stuck network if (EVER(httpio->lastdata) && !pendingcs) { dstime timeout = httpio->lastdata + HttpIO::NETWORKTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } if (pendingcs && EVER(pendingcs->lastdata)) { if (EVER(disconnecttimestamp)) { if (disconnecttimestamp > Waiter::ds && disconnecttimestamp < nds) { nds = disconnecttimestamp; } else if (disconnecttimestamp <= Waiter::ds) { nds = 0; } } else if (!requestLock && !fetchingnodes) { dstime timeout = pendingcs->lastdata + HttpIO::REQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } else if (workinglockcs && EVER(workinglockcs->lastdata) && workinglockcs->status == REQ_INFLIGHT) { dstime timeout = workinglockcs->lastdata + HttpIO::REQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } } if (mPendingLocklessCS && EVER(mPendingLocklessCS->lastdata) && mPendingLocklessCS->status == REQ_INFLIGHT) { dstime timeout = mPendingLocklessCS->lastdata + HttpIO::REQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } if (badhostcs && EVER(badhostcs->lastdata) && badhostcs->status == REQ_INFLIGHT) { dstime timeout = badhostcs->lastdata + HttpIO::REQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } if (!pendingscTimedOut && !jsonsc.pos && pendingsc && pendingsc->status == REQ_INFLIGHT) { dstime timeout = pendingsc->lastdata + HttpIO::SCREQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; } else if (timeout <= Waiter::ds) { nds = 0; } } } // immediate action required? if (!nds) { ++performanceStats.prepwaitImmediate; return Waiter::NEEDEXEC; } // nds is either MAX_INT (== no pending events) or > Waiter::ds if (EVER(nds)) { nds -= Waiter::ds; } #ifdef MEGA_MEASURE_CODE bool reasonGiven = false; if (nds == 0) { ++performanceStats.prepwaitZero; reasonGiven = true; } #endif waiter->init(nds); // set subsystem wakeup criteria (WinWaiter assumes httpio to be set first!) waiter->wakeupby(httpio, Waiter::NEEDEXEC); #ifdef MEGA_MEASURE_CODE if (waiter->maxds == 0 && !reasonGiven) { ++performanceStats.prepwaitHttpio; reasonGiven = true; } #endif waiter->wakeupby(fsaccess.get(), Waiter::NEEDEXEC); #ifdef MEGA_MEASURE_CODE if (waiter->maxds == 0 && !reasonGiven) { ++performanceStats.prepwaitFsaccess; reasonGiven = true; } if (!reasonGiven) { ++performanceStats.nonzeroWait; } #endif return 0; } int MegaClient::dowait() { CodeCounter::ScopeTimer ccst(performanceStats.doWait); return waiter->wait(); } int MegaClient::checkevents() { CodeCounter::ScopeTimer ccst(performanceStats.checkEvents); int r = httpio->checkevents(waiter.get()); r |= fsaccess->checkevents(waiter.get()); if (gfx) { r |= gfx->checkevents(waiter.get()); } return r; } // reset all backoff timers and transfer retry counters bool MegaClient::abortbackoff(bool includexfers) { bool r = false; WAIT_CLASS::bumpds(); if (includexfers) { overquotauntil = 0; if (ststatus != STORAGE_PAYWALL) // in ODQ Paywall, ULs/DLs are not allowed { // in ODQ Red, only ULs are disallowed int end = (ststatus != STORAGE_RED) ? PUT : GET; for (int d = GET; d <= end; d += PUT - GET) { for (auto& it : multi_transfers[d]) { if (it.second->bt.arm()) { r = true; } if (it.second->slot && it.second->slot->retrying) { if (it.second->slot->retrybt.arm()) { r = true; } } } } for (handledrn_map::iterator it = hdrns.begin(); it != hdrns.end();) { (it++)->second->retry(API_OK); } } } for (pendinghttp_map::iterator it = pendinghttp.begin(); it != pendinghttp.end(); it++) { if (it->second->bt.arm()) { r = true; } } if (btcs.arm()) { r = true; } if (mBackoffTimerLocklessCS.arm()) { r = true; } if (btbadhost.arm()) { r = true; } if (btworkinglock.arm()) { r = true; } if (!pendingsc && !pendingscUserAlerts && btsc.arm()) { r = true; } if (activefa.size() < MAXPUTFA && btpfa.arm()) { r = true; } for (fafc_map::iterator it = fafcs.begin(); it != fafcs.end(); it++) { if (it->second->req.status != REQ_INFLIGHT && it->second->bt.arm()) { r = true; } } return r; } // activate enough queued transfers as necessary to keep the system busy - but not too busy void MegaClient::dispatchTransfers() { if (CancelToken::haveAnyCancelsOccurredSince(lastKnownCancelCount)) { // first deal with the possibility of cancelled transfers // only do this if any cancel tokens were activated // as walking the whole list on every exec() would be too expensive for large sets of transfers static direction_t putget[] = { PUT, GET }; for (direction_t direction : putget) { TransferDbCommitter committer(tctable); auto& directionList = multi_transfers[direction]; for (auto i = directionList.begin(); i != directionList.end(); ) { auto it = i++; // in case this entry is removed Transfer* transfer = it->second; transfer->removeCancelledTransferFiles(&committer); if (transfer->files.empty()) { // this also removes it from slots transfer->removeAndDeleteSelf(TRANSFERSTATE_CANCELLED); } } } } // do we have any transfer slots available? if (!slotavail()) { LOG_verbose << "No slots available"; return; } CodeCounter::ScopeTimer ccst(performanceStats.dispatchTransfers); struct counter { m_off_t remainingsum = 0; double total = 0; double added = 0; bool hasVeryBig = false; void addexisting(m_off_t size, m_off_t progressed, double transferWeight) { remainingsum += size - progressed; total += transferWeight; if (size > 100 * 1024 * 1024 && (size - progressed) > 5 * 1024 * 1024) { hasVeryBig = true; } } void addnew(m_off_t size, double transferWeight) { addexisting(size, 0, transferWeight); added += transferWeight; } }; std::array counters; // Exponential function to calculate the maximum transfer queue size // This function uses a threshold (in KB/s) so the function has two different behaviors: // 1. Before the threshold, the function grows slowly from the minSize to the maxSize // 2. After the threshold, the function grows very quickly to the maxSize // This allows us to optimize the queue limit based on throughput. auto calcDynamicQueueLimit = [this]() -> unsigned { // Define the minimum and maximum scaling factors const int minScalingFactor = 2000; // KB/s const int maxScalingFactor = 20000; // KB/s // Define the minimum and maximum size limits const int minSize = MIN_MAXTRANSFERS; // Adjusted minimum size const int maxSize = MAXTRANSFERS; const int threshold = 16500; // Threshold (KB/S) to activate the additional term -> before this threshold, dynamic size grows slowly from minSize to maxSize. After the threshold, it will quickly grow to maxSize. if (threshold <= minScalingFactor) { LOG_err << "[calcDynamicQueueLimit] threshold (" << threshold << ") IS SMALLER OR EQUAL minScalingFactor(" << minScalingFactor << ") !!!!!!!!!! This must be fixed!!!!"; assert(false && "[calcDynamicQueueLimit] thresold <= minScalingFactor!"); return maxSize; } // Use an expontential function to obtain a very low growth rate before the threshold m_off_t throughputInKBPerSec = std::min(httpio->downloadSpeed / 1024, minScalingFactor); // KB/s const double reductiveGrowthMultiplier = 0.12; // This allows us to keep the scaling factor within a very low growth rate double scaleFactor = (static_cast(throughputInKBPerSec - minScalingFactor) / (threshold - minScalingFactor)) * reductiveGrowthMultiplier; double size = minSize + (maxSize - minSize) * (1 - exp(-scaleFactor)); size = std::min(size, static_cast(maxSize)); if (throughputInKBPerSec >= threshold) { double minSizeAfterThreshold = size; scaleFactor = static_cast(throughputInKBPerSec - threshold) / (maxScalingFactor - threshold); // Calculate size using exponential function with an additional term for a high growth rate after the threshold double additionalTerm = 20 * log(1 + static_cast(throughputInKBPerSec - threshold) / threshold); double sizeAfterThreshold = minSizeAfterThreshold + (maxSize - minSizeAfterThreshold) * (1 - exp(-scaleFactor)) + additionalTerm; size = std::min(sizeAfterThreshold, static_cast(maxSize)); } //LOG_verbose << "[calcDynamicQueueSize] customLimit = " << size << " [throughput = " << (throughputInKBPerSec) << " KB/s]"; #if defined(__ANDROID__) || defined(USE_IOS) return std::min(static_cast(size), MAX_RAIDTRANSFERS_FOR_MOBILE); #else return static_cast(size); #endif }; auto calcTransferWeight = [this, &calcDynamicQueueLimit](mega::direction_t transferDirection, bool forceDynamicLimit = false) -> double { if (transferDirection == GET && ((raidTransfersCounter >= MEANINGFUL_PORTION_OF_MAXTRANSFERS_QUEUE_FOR_RAID_PREDICTIVE_SYSTEM) // 1/6 of the hard limit || forceDynamicLimit)) { double averageFileSize = 0; for (TransferSlot* ts : tslots) { averageFileSize += (static_cast(ts->transfer->size) / static_cast(tslots.size())); // Take into account VERY LARGE FILE SIZES, that's why I divide for each iteration and not at the end } unsigned dynamicQueueLimit; const unsigned maxAverageFilesizeForFixedLimit = 2 * 1024 * 1024; // 2MB, it's better to used a fixed limit (and a larger queue max size) for transfer queues whose average filesize is smaller than this value if (static_cast(averageFileSize) <= maxAverageFilesizeForFixedLimit) // Truncate double averageFileSize (2,x MB ≡ 2MB) { #if defined(__ANDROID__) || defined(USE_IOS) dynamicQueueLimit = MAX_RAIDTRANSFERS_FOR_MOBILE; #else dynamicQueueLimit = MAXTRANSFERS + 10; #endif } else { dynamicQueueLimit = calcDynamicQueueLimit(); } double transferWeight = static_cast(MAXTRANSFERS) / static_cast(dynamicQueueLimit); //LOG_verbose << "[calcTransferWeight] raidTransfersCounter = " << raidTransfersCounter << ", >= " << MEANINGFUL_PORTION_OF_MAXTRANSFERS_QUEUE_FOR_RAID_PREDICTIVE_SYSTEM << " -> transferWeight = " << transferWeight << " [tslots = " << tslots.size() << "] [dynamicQueueLimit = " << dynamicQueueLimit << "] [averageFileSize = " << (averageFileSize / 1024) << " KBs]"; return transferWeight; } return 1; }; // Determine average speed and total amount of data remaining for the given direction/size-category // We prepare data for put/get in index 0..1, and the put/get/big/small combinations in index 2..5 for (TransferSlot* ts : tslots) { assert(ts->transfer->type == PUT || ts->transfer->type == GET); double transferWeightKnown = 0.0; TransferCategory tc(ts->transfer); if (ts->transfer->type == GET) { if (!ts->transfer->tempurls.empty()) { if (ts->transferbuf.isNewRaid()) // Raid { // Keep the counter within the limit to avoid overrepresentation: 1/6 of max transfers // i.e., if the counter has already reached that value, we don't continue increasing it if (raidTransfersCounter < MEANINGFUL_PORTION_OF_MAXTRANSFERS_QUEUE_FOR_RAID_PREDICTIVE_SYSTEM) raidTransfersCounter += 1; transferWeightKnown = calcTransferWeight(tc.direction, true); // We already know this transfer is raided, force the dynamic calculation } else // Old raid or non-raid { // As we kept the raid counter within a max limit (1/6), we obtain an accurate representation by decreasing the counter every time we find a non-raid transfer. if (raidTransfersCounter > 1) raidTransfersCounter -= 1; transferWeightKnown = 1.0; // We alredy know this transfer is non-raided, the weight should be 1. } } } auto transferWeight = transferWeightKnown != 0.0 ? transferWeightKnown : calcTransferWeight(tc.direction); counters[tc.index()].addexisting(ts->transfer->size, ts->progressreported, transferWeight); counters[tc.directionIndex()].addexisting(ts->transfer->size, ts->progressreported, transferWeight); } if (tslots.empty()) { if (raidTransfersCounter != 0) { LOG_verbose << "[MegaClient::dispatchTransfers] reset raidTransfersCounter to 0!!! [raidTransfersCounter = " << raidTransfersCounter << "]"; } raidTransfersCounter = 0; } std::function continueDirection = [this, &counters](direction_t putget) { if (Waiter::ds % 50 == 0) // Avoid to log too frequently, do it every 5 secs { LOG_verbose << "[continueDirection] counters[putget].total = " << std::round(counters[putget].total) << ", counters[putget].added = " << std::round(counters[putget].added) << ", MAXTRANSFERS = " << MAXTRANSFERS << " [tslots = " << tslots.size() << "]";; } // hard limit on puts/gets if (static_cast(std::round(counters[putget].total)) >= MAXTRANSFERS) { return false; } // only request half the max at most, to get a quicker response from the API and get overlap with transfers going if (static_cast(std::round(counters[putget].added)) >= MAXTRANSFERS/2) { return false; } return true; }; std::function testAddTransferFunction = [&counters, this, &calcTransferWeight](Transfer* t) { TransferCategory tc(t); // If we have one very big file, that is enough to max out the bandwidth by itself; get that one done quickly (without preventing more small files). if (counters[tc.index()].hasVeryBig) { return false; } // queue up enough transfers that we can expect to keep busy for at least the next 30 seconds in this category m_off_t speed = (tc.direction == GET) ? httpio->downloadSpeed : httpio->uploadSpeed; m_off_t targetOutstanding = 30 * speed; targetOutstanding = std::max(targetOutstanding, 2 * 1024 * 1024); targetOutstanding = std::min(targetOutstanding, 100 * 1024 * 1024); if (counters[tc.index()].remainingsum >= targetOutstanding) { return false; } auto transferWeight = calcTransferWeight(tc.direction); counters[tc.index()].addnew(t->size, transferWeight); counters[tc.directionIndex()].addnew(t->size, transferWeight); return true; }; TransferDbCommitter committer(tctable); std::array, 6> nextInCategory = transferlist.nexttransfers(testAddTransferFunction, continueDirection, committer); // Iterate the 4 combinations in this order: static const TransferCategory categoryOrder[] = { TransferCategory(PUT, LARGEFILE), TransferCategory(GET, LARGEFILE), TransferCategory(PUT, SMALLFILE), TransferCategory(GET, SMALLFILE), }; for (auto category : categoryOrder) { for (Transfer *nexttransfer : nextInCategory[category.index()]) { if (!slotavail()) { return; } if (category.direction == PUT && queuedfa.size() > MAXQUEUEDFA) { // file attribute jam? halt uploads. LOG_warn << "Attribute queue full: " << queuedfa.size(); break; } if (nexttransfer->localfilename.empty()) { // this is a fresh transfer rather than the resumption of a partly // completed and deferred one if (nexttransfer->type == PUT) { // generate fresh random encryption key/CTR IV for this file byte keyctriv[SymmCipher::KEYLENGTH + sizeof(int64_t)]; rng.genblock(keyctriv, sizeof keyctriv); memcpy(nexttransfer->transferkey.data(), keyctriv, SymmCipher::KEYLENGTH); nexttransfer->ctriv = static_cast( MemAccess::get((const char*)keyctriv + SymmCipher::KEYLENGTH)); } else { // set up keys for the decryption of this file (k == NULL => private node) const byte* keyData = nullptr; bool missingPrivateNode = false; // locate suitable template file for (file_list::iterator it = nexttransfer->files.begin(); it != nexttransfer->files.end(); it++) { if ((*it)->hprivate && !(*it)->hforeign && !(*it)->undelete()) { // Make sure we have the size field std::shared_ptr n = nodeByHandle((*it)->h); if (!n) { missingPrivateNode = true; } else if (n->type == FILENODE) { keyData = (const byte*)n->nodekey().data(); nexttransfer->size = n->size; } } else { keyData = (*it)->filekey; nexttransfer->size = (*it)->size; } if (keyData) { memcpy(nexttransfer->transferkey.data(), keyData, SymmCipher::KEYLENGTH); SymmCipher::xorblock(keyData + SymmCipher::KEYLENGTH, nexttransfer->transferkey.data()); nexttransfer->ctriv = MemAccess::get((const char*)keyData + SymmCipher::KEYLENGTH); nexttransfer->metamac = MemAccess::get( (const char*)keyData + SymmCipher::KEYLENGTH + sizeof(int64_t)); break; } } if (!keyData) { // there are no keys to decrypt this download - if it's because the node to download doesn't exist anymore, fail the transfer (otherwise wait for keys to become available) if (missingPrivateNode) { nexttransfer->failed(API_EARGS, committer); } continue; } } nexttransfer->localfilename.clear(); // set file localnames (ultimate target) and one transfer-wide temp // localname for (file_list::iterator it = nexttransfer->files.begin(); nexttransfer->localfilename.empty() && it != nexttransfer->files.end(); it++) { (*it)->prepare(*fsaccess); } assert(nexttransfer->localfilename.isAbsolute()); // app-side transfer preparations (populate localname, create thumbnail...) app->transfer_prepare(nexttransfer); } bool openok = false; bool openfinished = false; // verify that a local path was given and start/resume transfer if (!nexttransfer->localfilename.empty()) { TransferSlot *ts = nullptr; if (!nexttransfer->slot) { // allocate transfer slot ts = new TransferSlot(nexttransfer); } else { ts = nexttransfer->slot; } if (ts->fa->asyncavailable()) { if (!nexttransfer->asyncopencontext) { LOG_debug << "Starting async open: " << nexttransfer->localfilename; // try to open file (PUT transfers: open in nonblocking mode) nexttransfer->asyncopencontext.reset( (nexttransfer->type == PUT) ? ts->fa->asyncfopen(nexttransfer->localfilename, FSLogging::logOnError) : ts->fa->asyncfopen(nexttransfer->localfilename, false, true, nexttransfer->size)); asyncfopens++; } if (nexttransfer->asyncopencontext->finished) { LOG_debug << "Async open finished: " << nexttransfer->localfilename; openok = !nexttransfer->asyncopencontext->failed; openfinished = true; nexttransfer->asyncopencontext.reset(); asyncfopens--; ts->fa->fopenSucceeded = openok; } assert(!asyncfopens); //FIXME: Improve the management of asynchronous fopen when they can //be really asynchronous. All transfers could open its file in this //stage (not good) and, if we limit it, the transfer queue could hang because //it's full of transfers in that state. Transfer moves also complicates //the management because transfers that haven't been opened could be //placed over transfers that are already being opened. //Probably, the best approach is to add the slot of these transfers to //the queue and ensure that all operations (transfer moves, pauses) //are correctly cancelled when needed } else { // try to open file (PUT transfers: open in nonblocking mode) LOG_debug << "Sync open: " << nexttransfer->localfilename; openok = (nexttransfer->type == PUT) ? ts->fa->fopen(nexttransfer->localfilename, FSLogging::logOnError) : ts->fa->fopen(nexttransfer->localfilename, OPEN_WRONLY, FSLogging::logOnError); openfinished = true; } if (openfinished && openok) { NodeHandle h; bool hprivate = true; const char *privauth = NULL; const char *pubauth = NULL; const char *chatauth = NULL; nexttransfer->pos = 0; nexttransfer->setProgresscompleted(0); if (nexttransfer->type == GET || nexttransfer->tempurls.size()) { m_off_t p = 0; // resume at the end of the last contiguous completed block m_off_t pCompleted{0}; nexttransfer->chunkmacs.calcprogress(nexttransfer->size, nexttransfer->pos, pCompleted, &p); nexttransfer->setProgresscompleted(pCompleted); if (nexttransfer->progresscompleted > nexttransfer->size) { LOG_err << "Invalid transfer progress!"; nexttransfer->pos = nexttransfer->size; nexttransfer->setProgresscompleted(nexttransfer->size); } m_off_t progresscontiguous = ts->updatecontiguousprogress(); LOG_debug << "Resuming transfer at " << nexttransfer->pos << " Completed: " << nexttransfer->progresscompleted << " Contiguous: " << progresscontiguous << " Partial: " << p << " Size: " << nexttransfer->size << " ultoken: " << (nexttransfer->ultoken != NULL); } else { nexttransfer->chunkmacs.clear(); } ts->progressreported = nexttransfer->progresscompleted; if (nexttransfer->type == PUT) { if (ts->fa->mtime != nexttransfer->mtime || ts->fa->size != nexttransfer->size) { LOG_warn << "Modification detected starting upload." << " Path: " << nexttransfer->localfilename << " Size: " << nexttransfer->size << " Mtime: " << nexttransfer->mtime << " FaSize: " << ts->fa->size << " FaMtime: " << ts->fa->mtime; nexttransfer->failed(API_EREAD, committer); continue; } // create thumbnail/preview imagery, if applicable (FIXME: do not re-create upon restart) if (!nexttransfer->localfilename.empty() && nexttransfer->uploadhandle.isUndef()) { nexttransfer->uploadhandle = mUploadHandle.next(); if (!gfxdisabled && gfx && gfx->isgfx(nexttransfer->localfilename)) { // we want all imagery to be safely tucked away before completing the upload, so we bump minfa int bitmask = gfx->gendimensionsputfa(nexttransfer->localfilename, NodeOrUploadHandle(nexttransfer->uploadhandle), nexttransfer->transfercipher(), -1); if (bitmask & (1 << GfxProc::THUMBNAIL)) { fileAttributesUploading.setFileAttributePending(nexttransfer->uploadhandle, GfxProc::THUMBNAIL, nexttransfer); } if (bitmask & (1 << GfxProc::PREVIEW)) { fileAttributesUploading.setFileAttributePending(nexttransfer->uploadhandle, GfxProc::PREVIEW, nexttransfer); } } } } else { for (file_list::iterator it = nexttransfer->files.begin(); it != nexttransfer->files.end(); it++) { if ((*it)->undelete() || !(*it)->hprivate || (*it)->hforeign || nodeByHandle((*it)->h)) { h = (*it)->h; hprivate = (*it)->hprivate; privauth = (*it)->privauth.size() ? (*it)->privauth.c_str() : NULL; pubauth = (*it)->pubauth.size() ? (*it)->pubauth.c_str() : NULL; chatauth = (*it)->chatauth; break; } else { LOG_err << "Unexpected node ownership"; } } } // dispatch request for temporary source/target URL if (nexttransfer->tempurls.size()) { ts->transferbuf.setIsRaid(nexttransfer, nexttransfer->tempurls, nexttransfer->pos, ts->maxRequestSize); app->transfer_prepare(nexttransfer); } else { ts->pendingcmd = (nexttransfer->type == PUT) ? (Command*)new CommandPutFile(this, ts) : new CommandGetFile( this, ts->transfer->transferkey.data(), SymmCipher::KEYLENGTH, (!ts->transfer->files.empty() && ts->transfer->files.front()->undelete()), h.as8byte(), hprivate, privauth, pubauth, chatauth, false /*singleURL*/, true /*forceSSL*/, [this, ts, hprivate, h]( const Error& e, m_off_t s, dstime tl /*timeleft*/, std::string* filename, std::string* /*fingerprint*/, std::string* /*fileattrstring*/, const std::vector& tempurls, const std::vector& /*ips*/, const std::string& /*fileHandle*/) { auto tslot = ts; auto priv = hprivate; tslot->pendingcmd = nullptr; if (!filename) // failed! (Notice: calls not coming from // !callFailedCompletion) will allways // have that != nullptr { assert(s == -1 && "failing a transfer too soon: coming from a " "successful mCompletion call"); tslot->transfer->failed(e, *mTctableRequestCommitter); return true; } if (s >= 0 && s != tslot->transfer->size) { tslot->transfer->size = s; for (file_list::iterator it = tslot->transfer->files.begin(); it != tslot->transfer->files.end(); it++) { (*it)->size = s; } if (priv) { std::shared_ptr n = mNodeManager.getNodeByHandle(h); if (n) { n->size = s; mNodeManager.notifyNode(n); } } sendevent(99411, "Node size mismatch", 0); } tslot->starttime = tslot->lastdata = waiter->ds; if ((tempurls.size() == 1 || tempurls.size() == RAIDPARTS) && s >= 0) { tslot->transfer->tempurls = tempurls; tslot->transfer->adjustNonRaidedProgressIfNowIsRaided(); tslot->transfer->downloadFileHandle = h; tslot->transferbuf.setIsRaid(tslot->transfer, tempurls, tslot->transfer->pos, tslot->maxRequestSize); tslot->progress(); return true; } if (e == API_EOVERQUOTA && tl <= 0) { // default retry interval tl = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS; } tslot->transfer->failed(e, *mTctableRequestCommitter, e == API_EOVERQUOTA ? tl * 10 : 0); return true; }); queueCommand(ts->pendingcmd); } LOG_debug << "Activating transfer"; ts->slots_it = tslots.insert(tslots.begin(), ts); // notify the app about the starting transfer for (file_list::iterator it = nexttransfer->files.begin(); it != nexttransfer->files.end(); it++) { (*it)->start(); } app->transfer_update(nexttransfer); performanceStats.transferStarts += 1; } else if (openfinished) { if (nexttransfer->type == GET) { LOG_err << "Error dispatching transfer. Temporary file not writable: " << nexttransfer->localfilename; nexttransfer->failed(API_EWRITE, committer); } else if (!ts->fa->retry) { LOG_err << "Error dispatching transfer. Local file permanently unavailable: " << nexttransfer->localfilename; nexttransfer->failed(API_EREAD, committer); } else { LOG_warn << "Error dispatching transfer. Local file temporarily unavailable: " << nexttransfer->localfilename; nexttransfer->failed(API_EREAD, committer); } } } else { LOG_err << "Error preparing transfer. No localfilename"; nexttransfer->failed(API_EREAD, committer); } } } } // do we have an upload that is still waiting for file attributes before being completed? void MegaClient::checkfacompletion(UploadHandle th, Transfer* t, bool uploadCompleted) { // For any particular transfer, this is called first with t supplied, when the file's data upload completes. // Calls after that are with !t, to check if we have all the file attributes yet, so we can make the putnodes call assert(!th.isUndef()); if (auto uploadFAPtr = fileAttributesUploading.lookupExisting(th)) { assert(!t || uploadFAPtr->transfer == t); t = uploadFAPtr->transfer; if (uploadCompleted) { uploadFAPtr->uploadCompleted = true; multi_transfers[t->type].erase(t->transfers_it); t->transfers_it = multi_transfers[t->type].end(); delete t->slot; t->slot = NULL; } // abort if upload still running if (!uploadFAPtr->uploadCompleted) { LOG_debug << "Upload still running checking a file attribute - " << th; return; } assert(uploadFAPtr->transfer && uploadFAPtr->transfer->type == PUT); // do we have all the required the file attributes available? complete upload. int numUnresolvedFA = 0; for (auto& i : uploadFAPtr->pendingfa) { if (!i.second.valueIsSet) ++numUnresolvedFA; // todo: fa_media } if (numUnresolvedFA) { LOG_debug << "Pending file attributes for upload - " << th << " : " << numUnresolvedFA; return; } } if (!t) return; LOG_debug << "Transfer finished, sending callbacks - " << th; t->state = TRANSFERSTATE_COMPLETED; t->completefiles(); app->transfer_complete(t); delete t; } // clear transfer queue void MegaClient::freeq(direction_t d) { TransferDbCommitter committer(tctable); for (auto transferPtr : multi_transfers[d]) { transferPtr.second->mOptimizedDelete = true; // so it doesn't remove itself from this list while deleting app->transfer_removed(transferPtr.second); delete transferPtr.second; } multi_transfers[d].clear(); transferlist.transfers[GET].clear(); transferlist.transfers[PUT].clear(); } bool MegaClient::isFetchingNodesPendingCS() { return pendingcs && pendingcs->includesFetchingNodes; } // determine next scheduled transfer retry void MegaClient::nexttransferretry(direction_t d, dstime* dsmin) { if (!xferpaused[d]) // avoid setting the timer's next=1 if it won't be processed { transferRetryBackoffs[d].update(dsmin, true); } } // disconnect all HTTP connections (slows down operations, but is semantically neutral) void MegaClient::disconnect() { if (pendingcs) { app->request_response_progress(-1, -1); pendingcs->disconnect(); } if (mPendingLocklessCS) { mPendingLocklessCS->disconnect(); } if (pendingsc) { pendingsc->disconnect(); } if (pendingscUserAlerts) { pendingscUserAlerts->disconnect(); } abortlockrequest(); for (pendinghttp_map::iterator it = pendinghttp.begin(); it != pendinghttp.end(); it++) { it->second->disconnect(); } for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); it++) { (*it)->disconnect(); } for (handledrn_map::iterator it = hdrns.begin(); it != hdrns.end();) { (it++)->second->retry(API_OK); } for (auto it = activefa.begin(); it != activefa.end(); it++) { (*it)->disconnect(); } for (fafc_map::iterator it = fafcs.begin(); it != fafcs.end(); it++) { it->second->req.disconnect(); } for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); it++) { (*it)->errorcount = 0; } if (badhostcs) { badhostcs->disconnect(); } if (mReqStatCS) { mReqStatCS->disconnect(); } httpio->lastdata = NEVER; httpio->disconnect(); app->notify_disconnect(); } // force retrieval of pending actionpackets immediately // by closing pending sc, reset backoff and clear waitd URL void MegaClient::catchup() { mPendingCatchUps++; if (pendingsc && !jsonsc.pos) { LOG_debug << "Terminating pendingsc connection for catchup. Pending: " << mPendingCatchUps; pendingsc->disconnect(); pendingsc.reset(); } btsc.reset(); } void MegaClient::abortlockrequest() { workinglockcs.reset(); btworkinglock.reset(); requestLock = false; disconnecttimestamp = NEVER; } void MegaClient::logout(bool keepSyncConfigsFile, CommandLogout::Completion completion) { // Avoids us having to check validity later. if (!completion) { completion = [](error) { }; } if (loggedin() != FULLACCOUNT) { locallogout(true, keepSyncConfigsFile); restag = reqtag; completion(API_OK); reportLoggedInChanges(); return; } loggingout++; auto sendFinalLogout = [keepSyncConfigsFile, completion, this]() { queueCommand(new CommandLogout(this, std::move(completion), keepSyncConfigsFile)); }; #ifdef ENABLE_SYNC syncs.prepareForLogout(keepSyncConfigsFile, [this, keepSyncConfigsFile, sendFinalLogout](){ syncs.locallogout(true, keepSyncConfigsFile, false); sendFinalLogout(); }); #else sendFinalLogout(); #endif } void MegaClient::locallogout(bool removecaches, [[maybe_unused]] bool keepSyncsConfigFile) { LOG_debug << clientname << "executing locallogout processing"; // track possible lack of logout callbacks executingLocalLogout = true; mAsyncQueue.clearDiscardable(); mV1PswdVault.reset(); #ifdef ENABLE_SYNC syncs.locallogout(removecaches, keepSyncsConfigFile, false); // Process any lingering client actions. if (!syncs.clientThreadActions.empty()) { TransferDbCommitter committer(tctable); Syncs::QueuedClientFunc func; while (syncs.clientThreadActions.popFront(func)) { func(*this, committer); } } #endif if (removecaches) { removeCaches(); } // Abort any active direct reads. abortreads(); // Deinitialize the FUSE Client Adapter. // // Keep in mind that at this point, FUSE mounts may be active and one or // more threads may be executing in the client when this call is made. // // That is, some threads may be in the process of asking the client for // information about some node, trying to start an upload or even be // waiting on the completion of a download. // // What this call does, is execute any tasks queued for execution and // sets a marker so that in the future if a thread queues a task for // execution, that task completes immediately as if it were cancelled. // // The marker also ensures that threads no longer come into the client // proper when trying to learn something about a node. Instead, all // requests for information for some node fail as it the node didn't // exist. // // While many threads may be in the client when call begins, all such // threads will have escaped by the time the call completes. // // Note, however, that when this completes, some threads may still be // waiting for the completion of some task such as a transfer or a // client-server request such as a putnodes. // // Threads blocked on some transfer will be awoken later when the // client tears down the transfer queues. // // Threads blocked on some request like putnodes will be awoken below // when all pending requests are "abandoned." mClientAdapter.deinitialize(); // Make sure the application hides any progress bars thay may have been visible. if (std::exchange(mRequestProgressNotified, false)) { app->reqstat_progress(-1); } // Clear cached request progress. mRequestProgress.reset(); sctable.reset(); mNodeManager.setTable(nullptr); statusTable.reset(); me = UNDEF; uid.clear(); unshareablekey.clear(); mFolderLink.mPublicHandle = UNDEF; mFolderLink.mWriteAuth.clear(); cachedscsn = UNDEF; achievements_enabled = false; isNewSession = false; tsLogin = 0; versions_disabled = false; accountsince = 0; gmfa_enabled = false; ssrs_enabled = false; aplvp_enabled = false; mNewLinkFormat = false; mCookieBannerEnabled = false; mABTestFlags.clear(); mFeatureFlags.clear(); mProFlexi = false; mSmsVerificationState = SMS_STATE_UNKNOWN; mSmsVerifiedPhone.clear(); loggingout = 0; mOnCSCompletion = nullptr; cachedug = false; minstreamingrate = -1; ephemeralSession = false; ephemeralSessionPlusPlus = false; #ifdef USE_MEDIAINFO mediaFileInfo = MediaFileInfo(); #endif mSets.clear(); mSetElements.clear(); stopSetPreview(); mIsS4Enabled.store(false); mS4Container.store(NodeHandle()); #ifdef ENABLE_CHAT mSfuid = sfu_invalid_id; #endif // remove any cached transfers older than two days that have not been resumed (updates transfer list) purgeOrphanTransfers(); // delete all remaining transfers (optimized not to remove from transfer list one by one) // transfer destructors update the transfer in the cache database freeq(GET); freeq(PUT); disconnect(); // commit and close the transfer cache database. if (tctable && tctable->getTransactionCommitter()) { auto committer = dynamic_cast(tctable->getTransactionCommitter()); if (committer) { // If we don't commit the last changes to the transfer database here, they would be reverted in closetc() // freeq() has its own committer, but it doesn't do anything because it's usually nested by the one // in the intermediate layer (MegaApiImpl::sendPendingTransfers). committer->commitNow(); } } closetc(); freeq(GET); // freeq after closetc due to optimizations freeq(PUT); // Deinitialize the File Service. mFileService.deinitialize(); // Deinitialize the FUSE Service. mFuseService.deinitialize(); purgenodesusersabortsc(false); userid = 0; mNodeManager.reset(); reqs.clear(); mReqsLockless.clear(); delete pendingcs; pendingcs = NULL; mPendingLocklessCS.reset(); scsn.clear(); mBlocked = false; mBlockedSet = false; for (pendinghttp_map::iterator it = pendinghttp.begin(); it != pendinghttp.end(); it++) { delete it->second; } for (vector::iterator it = bttimers.begin(); it != bttimers.end(); it++) { delete *it; } queuedfa.clear(); activefa.clear(); pendinghttp.clear(); bttimers.clear(); xferpaused[PUT] = false; xferpaused[GET] = false; fetchingnodes = false; fetchnodestag = 0; ststatus = STORAGE_UNKNOWN; overquotauntil = 0; mOverquotaDeadlineTs = 0; mOverquotaWarningTs.clear(); mBizGracePeriodTs = 0; mBizExpirationTs = 0; mBizMode = BIZ_MODE_UNKNOWN; mBizStatus = BIZ_STATUS_UNKNOWN; mBizMasters.clear(); mCachedStatus.clear(); scpaused = false; mLargestEverSeenScSeqTag.clear(); for (fafc_map::iterator cit = fafcs.begin(); cit != fafcs.end(); cit++) { for (int i = 2; i--; ) { for (faf_map::iterator it = cit->second->fafs[i].begin(); it != cit->second->fafs[i].end(); it++) { delete it->second; } } delete cit->second; } fafcs.clear(); fileAttributesUploading.clear(); // erase keys & session ID resetKeyring(); key.setkey(SymmCipher::zeroiv); tckey.setkey(SymmCipher::zeroiv); mPrivateRsaKey.resetkey(); mSerializedPrivateRsaKey.clear(); mPublicRsaKey.resetkey(); sessionkey.clear(); accountversion = 0; accountsalt.clear(); sid.clear(); k.clear(); mAuthRings.clear(); mAuthRingsTemp.clear(); mPendingContactKeys.clear(); reportLoggedInChanges(); mLastLoggedInReportedState = NOTLOGGEDIN; syncsAlreadyLoadedOnStatecurrent = false; fetchnodesAlreadyCompletedThisSession = false; init(); if (dbaccess) { dbaccess->currentDbVersion = DbAccess::LEGACY_DB_VERSION; } executingLocalLogout = false; mMyAccount = MyAccountData{}; mKeyManager.reset(); mLastFatalErrorDetected = ErrorReason::REASON_ERROR_NO_ERROR; mLastDBErrorDetected = DBError::DB_ERROR_UNKNOWN; mReqHashcashEasiness = 0; mReqHashcashToken.clear(); } void MegaClient::removeCaches() { mJourneyId->resetCacheAndValues(); if (sctable) { mNodeManager.setTable(nullptr); sctable->remove(); sctable.reset(); } if (statusTable) { statusTable->remove(); statusTable.reset(); } disabletransferresumption(); } const char *MegaClient::version() { return TOSTRING(MEGA_MAJOR_VERSION) "." TOSTRING(MEGA_MINOR_VERSION) "." TOSTRING(MEGA_MICRO_VERSION); } void MegaClient::getlocalsslcertificate() { queueCommand(new CommandGetLocalSSLCertificate(this)); } void MegaClient::dnsrequest(const char *hostname) { GenericHttpReq *req = new GenericHttpReq(rng); req->tag = reqtag; req->maxretries = 0; pendinghttp[reqtag] = req; req->posturl = string("https://") + hostname; req->dns(this); } void MegaClient::sendchatstats(const char *json, int port) { GenericHttpReq *req = new GenericHttpReq(rng); req->tag = reqtag; req->maxretries = 0; pendinghttp[reqtag] = req; req->posturl = SFUSTATSURL; if (port > 0) { req->posturl.append(":"); char stringPort[6]; snprintf(stringPort, sizeof(stringPort), "%d", static_cast(port)); req->posturl.append(stringPort); } req->posturl.append("/stats"); req->protect = true; req->out->assign(json); req->post(this); } void MegaClient::sendchatlogs(const char* json, handle forUserID, handle callid, int port) { GenericHttpReq *req = new GenericHttpReq(rng); req->tag = reqtag; req->maxretries = 0; pendinghttp[reqtag] = req; req->posturl = SFUSTATSURL; if (port > 0) { req->posturl.append(":"); char stringPort[6]; snprintf(stringPort, sizeof(stringPort), "%d", static_cast(port)); req->posturl.append(stringPort); } Base64Str uidB64(forUserID); req->posturl.append("/msglog?userid="); req->posturl.append(uidB64); req->posturl.append("&t=e"); if (callid != UNDEF) { Base64Str cid(callid); req->posturl.append("&callid="); req->posturl.append(cid); } req->protect = true; req->out->assign(json); req->post(this); } void MegaClient::httprequest(const char *url, int method, bool binary, const char *json, int retries) { GenericHttpReq *req = new GenericHttpReq(rng, binary); req->tag = reqtag; req->maxretries = retries; pendinghttp[reqtag] = req; if (method == METHOD_GET) { req->posturl = url; req->get(this); } else { req->posturl = url; if (json) { req->out->assign(json); } req->post(this); } } // process server-client request bool MegaClient::procsc(JSON& json) { // prevent the sync thread from looking things up while we change the tree std::unique_lock nodeTreeIsChanging(nodeTreeMutex); bool originalAC = actionpacketsCurrent; actionpacketsCurrent = false; CodeCounter::ScopeTimer ccst(performanceStats.scProcessingTime); std::shared_ptr lastAPDeletedNode; for (;;) { if (!insca) { switch (json.getnameid()) { case makeNameid("w"): json.storeobject(&scnotifyurl); break; case makeNameid("ir"): // when spoonfeeding is in action, there may still be more actionpackets to be delivered. insca_notlast = json.getint() == 1; break; case makeNameid("sn"): // the sn element is guaranteed to be the last in sequence (except for notification requests (c=50)) sc_storeSn(json); break; case EOO: sc_procEoo(nodeTreeIsChanging, originalAC); return true; case makeNameid("a"): if (json.enterarray()) { LOG_debug << "Processing action packets for " << string(sessionid, sizeof(sessionid)); insca = true; break; } // fall through default: if (!json.storeobject()) { LOG_err << "Error parsing sc request"; return true; } } } if (insca) { if (!sc_checkActionPacketPreservePos(json, lastAPDeletedNode.get())) { return false; } if (!sc_procActionPacket(json, lastAPDeletedNode)) { // No more Actions Packets. Force it to advance and process all the remaining // command responses until a new "st" is found, if any. // It will also process the latest command response associated (by the Sequence Tag) // with the latest AP processed here. sc_checkSequenceTag(string()); json.leavearray(); insca = false; } } } } void MegaClient::sc_storeSn(JSON& json) { scsn.setScsn(&json); // At this point no CurrentSeqtag should be seen. mCurrentSeqtagSeen is set true // when action package is processed and the seq tag matches with mCurrentSeqtag assert(!mCurrentSeqtagSeen); notifypurge(); if (sctable) { LOG_debug << "DB transaction COMMIT (sessionid: " << string(sessionid, sizeof(sessionid)) << ")"; sctable->commit(); sctable->begin(); app->notify_dbcommit(); } } void MegaClient::sc_procEoo(std::unique_lock& nodeTreeIsChanging, bool originalAC) { if (!useralerts.isDeletedSharedNodesStashEmpty()) { useralerts.purgeNodeVersionsFromStash(); useralerts.convertStashedDeletedSharedNodes(); } LOG_debug << "Processing of action packets for " << string(sessionid, sizeof(sessionid)) << " finished. More to follow: " << insca_notlast; mergenewshares(1); applykeys(); mNewKeyRepository.clear(); // with actionpacket spoonfeeding, just finishing a batch does not mean we // are up to date yet - keep going while "ir":1 if (!statecurrent && !insca_notlast) { if (fetchingnodes) { notifypurge(); if (sctable) { LOG_debug << "DB transaction COMMIT (sessionid: " << string(sessionid, sizeof(sessionid)) << ")"; sctable->commit(); sctable->begin(); } WAIT_CLASS::bumpds(); fnstats.timeToResult = Waiter::ds - fnstats.startTime; fnstats.timeToCurrent = fnstats.timeToResult; fetchingnodes = false; restag = fetchnodestag; fetchnodestag = 0; // block state not received in this execution, and cached // says we were blocked last time if (!mBlockedSet && mCachedStatus.lookup(CacheableStatus::STATUS_BLOCKED, 0)) { LOG_debug << "cached blocked states reports blocked, and no block state has been " "received before, issuing whyamiblocked"; whyamiblocked(); // lets query again, to trigger transition and restoreSyncs } enabletransferresumption(); app->fetchnodes_result(API_OK); app->notify_dbcommit(); fetchnodesAlreadyCompletedThisSession = true; WAIT_CLASS::bumpds(); fnstats.timeToSyncsResumed = Waiter::ds - fnstats.startTime; if (!loggedIntoFolder()) { // historic user alerts are not supported for public folders // now that we have fetched everything and caught up actionpackets since that state, // our next sc request can be for useralerts useralerts.begincatchup = true; } } else { WAIT_CLASS::bumpds(); fnstats.timeToCurrent = Waiter::ds - fnstats.startTime; } uint64_t numNodes = mNodeManager.getNodeCount(); fnstats.nodesCurrent = static_cast(numNodes); if (mKeyManager.generation()) { // Clear in-use bit if needed for the shared nodes in ^!keys. mKeyManager.syncSharekeyInUseBit(); } statecurrent = true; app->nodes_current(); mFuseService.current(); LOG_debug << "Cloud node tree up to date"; #ifdef ENABLE_SYNC // Don't start sync activity until `statecurrent` as it could take actions based on old // state The reworked sync code can figure out what to do once fully up to date. nodeTreeIsChanging.unlock(); if (!syncsAlreadyLoadedOnStatecurrent) { syncs.resumeSyncsOnStateCurrent(); syncsAlreadyLoadedOnStatecurrent = true; } #endif if (tctable && cachedfiles.size()) { resumeTransferFromDB(); } WAIT_CLASS::bumpds(); fnstats.timeToTransfersResumed = Waiter::ds - fnstats.startTime; string report; fnstats.toJsonArray(&report); sendevent(99426, report.c_str(), 0); // Treeproc performance log // NULL vector: "notify all elements" app->nodes_updated(NULL, int(numNodes)); app->users_updated(NULL, int(users.size())); app->pcrs_updated(NULL, int(pcrindex.size())); app->sets_updated(nullptr, int(mSets.size())); app->setelements_updated(nullptr, int(mSetElements.size())); #ifdef ENABLE_CHAT app->chats_updated(NULL, int(chats.size())); #endif app->useralerts_updated(nullptr, int(useralerts.alerts.size())); mNodeManager.removeChanges(); // if ^!keys doesn't exist yet -> migrate the private keys from legacy attrs to ^!keys if (loggedin() == FULLACCOUNT) { if (!mKeyManager.generation()) { assert(!mKeyManager.getPostRegistration()); app->upgrading_security(); } else { fetchContactsKeys(); sc_pk(); } } } { // In case a fetchnodes() occurs mid-session. We should not allow // the syncs to see the new tree unless we've caught up to at least // the same scsn/seqTag as we were at before. ir:1 is not always reliable bool scTagNotCaughtUp = !mScDbStateRecord.seqTag.empty() && !mLargestEverSeenScSeqTag.empty() && (mScDbStateRecord.seqTag.size() < mLargestEverSeenScSeqTag.size() || (mScDbStateRecord.seqTag.size() == mLargestEverSeenScSeqTag.size() && mScDbStateRecord.seqTag < mLargestEverSeenScSeqTag)); bool ac = statecurrent && !insca_notlast && !scTagNotCaughtUp; if (!originalAC && ac) { LOG_debug << clientname << "actionpacketsCurrent is true again"; } actionpacketsCurrent = ac; } if (!insca_notlast) { if (mReceivingCatchUp) { mReceivingCatchUp = false; mPendingCatchUps--; LOG_debug << "catchup complete. Still pending: " << mPendingCatchUps; app->catchup_result(); } } #ifdef ENABLE_SYNC syncs.waiter->notify(); #endif } bool MegaClient::sc_checkActionPacketPreservePos(JSON& json, const Node* lastAPDeletedNode) { auto actionpacketStart = json.pos; if (json.enterobject()) { // Check if it is ok to process the current action packet. if (!sc_checkActionPacket(json, lastAPDeletedNode)) { // We can't continue actionpackets until we know the next mCurrentSeqtag to match // against, wait for the CS request to deliver it. assert(reqs.cmdsInflight()); json.pos = actionpacketStart; return false; } } json.pos = actionpacketStart; return true; } bool MegaClient::sc_procActionPacket(JSON& json, std::shared_ptr& lastAPDeletedNode) { if (json.enterobject()) { // the "a" attribute is guaranteed to be the first in the object if (json.getnameid() == makeNameid("a")) { nameid name = json.getnameidvalue(); bool isSelfOriginating = Utils::startswith(json.pos, "\"i\":\"") && !memcmp(json.pos + 5, sessionid, sizeof sessionid) && json.pos[5 + sizeof sessionid] == '"'; sc_procActionPacketWithoutCommonTags(json, name, isSelfOriginating, lastAPDeletedNode); } else { lastAPDeletedNode.reset(); } json.leaveobject(); return true; } return false; } void MegaClient::sc_procActionPacketWithoutCommonTags(JSON& json, nameid name, bool isSelfOriginating, std::shared_ptr& lastAPDeletedNode) { bool moveOperation = false; // true if "d" packet has "m":1 if (!statecurrent) { fnstats.actionPackets++; } // only process server-client request if not marked as // self-originating ("i" marker element guaranteed to be following // "a" element if present) if (fetchingnodes || !isSelfOriginating || name == name_id::d || name == 't') // we still set 'i' on move commands to produce backward // compatible actionpackets, so don't skip those here { #ifdef ENABLE_CHAT bool readingPublicChat = false; #endif switch (name) { case name_id::u: // node update sc_updatenode(json); break; case makeNameid("t"): { // node addition { if (!loggedIntoFolder()) useralerts.beginNotingSharedNodes(); handle originatingUser = sc_newnodes(json); mergenewshares(1); if (!loggedIntoFolder()) useralerts.convertNotedSharedNodes(true, originatingUser); } } break; case name_id::d: // node deletion lastAPDeletedNode = sc_deltree(json, moveOperation); break; case makeNameid("s"): case makeNameid("s2"): // share addition/update/revocation if (sc_shares(json)) { int creqtag = reqtag; reqtag = 0; mergenewshares(1); reqtag = creqtag; } break; case name_id::c: // contact addition/update sc_contacts(json); break; case makeNameid("fa"): // file attribute update sc_fileattr(json); break; case makeNameid("ua"): // user attribute update sc_userattr(json); break; case name_id::psts: case name_id::psts_v2: case makeNameid("ftr"): if (sc_upgrade(json, name)) { app->account_updated(); abortbackoff(true); } break; case name_id::pses: sc_paymentreminder(json); break; case name_id::ipc: // incoming pending contact request (to us) sc_ipc(json); break; case makeNameid("opc"): // outgoing pending contact request (from us) sc_opc(json); break; case name_id::upci: // incoming pending contact request update (accept/deny/ignore) sc_upc(json, true); break; case name_id::upco: // outgoing pending contact request update (from them, accept/deny/ignore) sc_upc(json, false); break; case makeNameid("ph"): // public links handles sc_ph(json); break; case makeNameid("se"): // set email sc_se(json); break; #ifdef ENABLE_CHAT case makeNameid("mcpc"): { readingPublicChat = true; } // fall-through case makeNameid("mcc"): // chat creation / peer's invitation / peer's removal sc_chatupdate(json, readingPublicChat); break; case makeNameid("mcfpc"): // fall-through case makeNameid("mcfc"): // chat flags update sc_chatflags(json); break; case makeNameid("mcpna"): // fall-through case makeNameid("mcna"): // granted / revoked access to a node sc_chatnode(json); break; case name_id::mcsmp: // scheduled meetings updates sc_scheduledmeetings(json); break; case name_id::mcsmr: // scheduled meetings removal sc_delscheduledmeeting(json); break; #endif case makeNameid("uac"): sc_uac(json); break; case makeNameid("la"): // last acknowledged sc_la(json); break; case makeNameid("ub"): // business account update sc_ub(json); break; case makeNameid("sqac"): // storage quota allowance changed sc_sqac(json); break; case makeNameid("asp"): // new/update of a Set sc_asp(json); break; case makeNameid("ass"): sc_ass(json); break; case makeNameid("asr"): // removal of a Set sc_asr(json); break; case makeNameid("aep"): // new/update of a Set Element sc_aep(json); break; case makeNameid("aer"): // removal of a Set Element sc_aer(json); break; case makeNameid("pk"): // pending keys sc_pk(); break; case makeNameid("uec"): // User Email Confirm (uec) sc_uec(json); break; case makeNameid("cce"): // credit card for this user is potentially expiring soon or new card is // registered sc_cce(); break; } } if (!moveOperation) { lastAPDeletedNode.reset(); } } size_t MegaClient::procreqstat() { // reqstat packet format: // [][] // data input enough for reading 2 bytes (number of users)? if (!mReqStatCS || mReqStatCS->in.size() < sizeof(uint16_t)) { return 0; } uint16_t numUsers = MemAccess::get(mReqStatCS->in.data()); if (!numUsers) { LOG_debug << "reqstat: No operation in progress"; // Clear cached request progress. mRequestProgress.reset(); // resetting cs backoff here, because the account should be unlocked // and there should be connectivity with MEGA servers btcs.arm(); return 2; } size_t startPosUsers = sizeof(uint16_t); size_t pos = startPosUsers + USERHANDLE * numUsers; // will read them later // data input enough for reading users + 2 bytes (number of operations)? if (mReqStatCS->in.size() < pos + sizeof(uint16_t)) // is there data for users + numOps? { return 0; } uint16_t numOps = MemAccess::get(mReqStatCS->in.data() + pos); pos += sizeof(uint16_t); // will read them later // data input enough for reading number of operations + 12 bytes (start + current + end)? if (mReqStatCS->in.size() < pos + numOps + 3 * sizeof(uint32_t)) { return 0; } // read users std::ostringstream oss; oss << "reqstat: User " << Base64::btoa(mReqStatCS->in.substr(startPosUsers, USERHANDLE)); if (numUsers > 1) { oss << ", affecting "; for (unsigned i = 1; i < numUsers; ++i) { if (i > 1) { oss << ","; } oss << Base64::btoa(mReqStatCS->in.substr(startPosUsers + USERHANDLE * i, USERHANDLE)); } oss << ","; } // read operations if (numOps > 0) { oss << " is executing a "; for (unsigned i = 0; i < numOps; ++i) { if (i) { oss << "/"; } if (mReqStatCS->in[pos + i] == 'p') { oss << "file or folder creation"; } else { oss << "UNKNOWN operation"; } } } pos += numOps; uint32_t start = MemAccess::get(mReqStatCS->in.data() + pos); pos += sizeof(uint32_t); uint32_t curr = MemAccess::get(mReqStatCS->in.data() + pos); pos += sizeof(uint32_t); uint32_t end = MemAccess::get(mReqStatCS->in.data() + pos); pos += sizeof(uint32_t); float progress = 100.0f * static_cast(curr) / static_cast(end); oss << " since " << start << ", " << progress << "%"; oss << " [" << curr << "/" << end << "]"; LOG_debug << oss.str(); // Update cached request progress. mRequestProgress = static_cast(1000u * curr / end); // Remember whether we've told the app about request progress. mRequestProgressNotified = mReqStatEnabled && (pendingcs || csretrying); // Notify application if necessary. if (mRequestProgressNotified) { app->reqstat_progress(*mRequestProgress); } return pos; } // update the user's local state cache, on completion of the fetchnodes command // (note that if immediate-completion commands have been issued in the // meantime, the state of the affected nodes // may be ahead of the recorded scsn - their consistency will be checked by // subsequent server-client commands.) // initsc() is called after all initial decryption has been performed, so we // are tolerant towards incomplete/faulty nodes. void MegaClient::initsc() { if (sctable) { bool complete; sctable->truncate(); // 1. write current scsn handle tscsn = scsn.getHandle(); complete = sctable->put(CACHEDSCSN, (char*)&tscsn, sizeof tscsn); if (complete && !mScDbStateRecord.seqTag.empty()) { complete = sctable->put(CACHEDDBSTATE, &mScDbStateRecord, &key); LOG_debug << "saving seqtag in db: " << mScDbStateRecord.seqTag; } if (complete) { // 2. write all users for (user_map::iterator it = users.begin(); it != users.end(); it++) { complete = sctable->put(CACHEDUSER, &it->second, &key); if (!complete) { break; } } } if (complete) { // 4. write new or modified pcrs, purge deleted pcrs for (handlepcr_map::iterator it = pcrindex.begin(); it != pcrindex.end(); it++) { complete = sctable->put(CACHEDPCR, it->second.get(), &key); if (!complete) { break; } } } if (complete) { // 5. write Sets complete = initscsets(); } if (complete) { // 6. write SetElements complete = initscsetelements(); } // Nothing to do for persisting Alerts. cmd("f") will not provide any data about Alerts #ifdef ENABLE_CHAT if (complete) { // 7. write new or modified chats for (textchat_map::iterator it = chats.begin(); it != chats.end(); it++) { complete = sctable->put(CACHEDCHAT, it->second, &key); if (!complete) { break; } } } LOG_debug << "Saving SCSN " << scsn.text() << " (sessionid: " << string(sessionid, sizeof(sessionid)) << ") with " << mNodeManager.getNodeCount() << " nodes, " << users.size() << " users, " << pcrindex.size() << " pcrs, " << mSets.size() << " sets and " << mSetElements.size() << " elements and " << chats.size() << " chats to local cache (" << complete << ")"; #else LOG_debug << "Saving SCSN " << scsn.text() << " (sessionid: " << string(sessionid, sizeof(sessionid)) << ") with " << mNodeManager.getNodeCount() << " nodes, " << users.size() << " users, " << pcrindex.size() << " pcrs, " << mSets.size() << " sets and " << mSetElements.size() << " elements to local cache (" << complete << ")"; #endif finalizesc(complete); if (complete) { // We have the data, and we have the corresponding scsn, all from fetchnodes finishing just now. // Commit now, otherwise we'll have to do fetchnodes again (on restart) if no actionpackets arrive. LOG_debug << "DB transaction COMMIT (sessionid: " << string(sessionid, sizeof(sessionid)) << ")"; sctable->commit(); sctable->begin(); } } } void MegaClient::initStatusTable() { if (statusTable) { // statusTable is different from sctable in that we begin/commit with each change DBTableTransactionCommitter committer(statusTable); statusTable->truncate(); } } // erase and and fill user's local state cache void MegaClient::updatesc() { if (sctable) { string t; sctable->get(CACHEDSCSN, &t); if (t.size() != sizeof cachedscsn) { if (t.size()) { LOG_err << "Invalid scsn size"; } return; } if (!scsn.ready()) { LOG_err << "scsn not known, not updating database"; return; } bool complete; // 1. update associated scsn handle tscsn = scsn.getHandle(); complete = sctable->put(CACHEDSCSN, (char*)&tscsn, sizeof tscsn); LOG_debug << "SCSN write at DB " << tscsn << " - " << scsn.text(); if (complete && !mScDbStateRecord.seqTag.empty()) { complete = sctable->put(CACHEDDBSTATE, &mScDbStateRecord, &key); LOG_debug << "saving seqtag in db: " << mScDbStateRecord.seqTag; } if (complete) { // 2. write new or update modified users for (user_vector::iterator it = usernotify.begin(); it != usernotify.end(); it++) { char base64[12]; if ((*it)->show == INACTIVE && (*it)->userhandle != me) { if ((*it)->dbid) { LOG_verbose << clientname << "Removing inactive user from database: " << (Base64::btoa((byte*)&((*it)->userhandle),MegaClient::USERHANDLE,base64) ? base64 : ""); complete = sctable->del((*it)->dbid); if (!complete) { break; } } } else { LOG_verbose << clientname << "Adding/updating user to database: " << (Base64::btoa((byte*)&((*it)->userhandle),MegaClient::USERHANDLE,base64) ? base64 : ""); complete = sctable->put(CACHEDUSER, *it, &key); if (!complete) { break; } } } } // 3. write new or modified nodes, purge deleted pcrs // NoD -> nodes are written to DB immediately if (complete) { // 4. write new or modified pcrs, purge deleted pcrs for (pcr_vector::iterator it = pcrnotify.begin(); it != pcrnotify.end(); it++) { char base64[12]; if ((*it)->removed()) { if ((*it)->dbid) { LOG_verbose << "Removing pcr from database: " << (Base64::btoa((byte*)&((*it)->id),MegaClient::PCRHANDLE,base64) ? base64 : ""); complete = sctable->del((*it)->dbid); if (!complete) { break; } } } else if (!(*it)->removed()) { LOG_verbose << "Adding pcr to database: " << (Base64::btoa((byte*)&((*it)->id),MegaClient::PCRHANDLE,base64) ? base64 : ""); complete = sctable->put(CACHEDPCR, *it, &key); if (!complete) { break; } } } } if (complete) { // 5. write new or modified Sets, purge deleted ones complete = updatescsets(); } if (complete) { // 6. write new or modified SetElements, purge deleted ones complete = updatescsetelements(); } #ifdef ENABLE_CHAT if (complete) { // 6. write new or modified chats for (textchat_map::iterator it = chatnotify.begin(); it != chatnotify.end(); it++) { LOG_verbose << "Adding chat to database: " << Base64Str(it->second->getChatId()); complete = sctable->put(CACHEDCHAT, it->second, &key); if (!complete) { break; } } } LOG_debug << "Saving SCSN " << scsn.text() << " (sessionid: " << string(sessionid, sizeof(sessionid)) << ") with " << mNodeManager.nodeNotifySize() << " modified nodes, " << usernotify.size() << " users, " << pcrnotify.size() << " pcrs, " << setnotify.size() << " sets, " << setelementnotify.size() << " elements and " << chatnotify.size() << " chats to local cache (" << complete << ")"; #else LOG_debug << "Saving SCSN " << scsn.text() << " (sessionid: " << string(sessionid, sizeof(sessionid)) << ") with " << mNodeManager.nodeNotifySize() << " modified nodes, " << usernotify.size() << " users, " << pcrnotify.size() << " pcrs, " << setnotify.size() << " sets, " << setelementnotify.size() << " elements to local cache (" << complete << ")"; #endif finalizesc(complete); } } // commit or purge local state cache void MegaClient::finalizesc(bool complete) { if (complete) { cachedscsn = scsn.getHandle(); } else { LOG_err << "Cache update DB write error"; assert(false); } } // queue node file attribute for retrieval or cancel retrieval error MegaClient::getfa(handle h, string *fileattrstring, const string &nodekey, fatype t, int cancel) { assert((cancel && nodekey.empty()) || (!cancel && !nodekey.empty())); // locate this file attribute type in the nodes's attribute string handle fah; int p, pp; // find position of file attribute or 0 if not present p = Node::hasfileattribute(fileattrstring, t); if (!p) { return API_ENOENT; } pp = p - 1; while (pp && fileattrstring->at(static_cast(pp - 1)) >= '0' && fileattrstring->at(static_cast(pp - 1)) <= '9') { pp--; } if (p == pp) { return API_ENOENT; } if (Base64::atob(strchr(fileattrstring->c_str() + p, '*') + 1, (byte*)&fah, sizeof(fah)) != sizeof(fah)) { return API_ENOENT; } int c = atoi(fileattrstring->c_str() + pp); if (cancel) { // cancel pending request fafc_map::iterator cit; if ((cit = fafcs.find(c)) != fafcs.end()) { faf_map::iterator it; for (int i = 2; i--; ) { if ((it = cit->second->fafs[i].find(fah)) != cit->second->fafs[i].end()) { delete it->second; cit->second->fafs[i].erase(it); // none left: tear down connection if (!cit->second->fafs[1].size() && cit->second->req.status == REQ_INFLIGHT) { cit->second->req.disconnect(); } return API_OK; } } } return API_ENOENT; } else { // add file attribute cluster channel and set cluster reference node handle FileAttributeFetchChannel** fafcp = &fafcs[c]; if (!*fafcp) { *fafcp = new FileAttributeFetchChannel(this); } if (!(*fafcp)->fafs[1].count(fah)) { (*fafcp)->fahref = fah; // map returned handle to type/node upon retrieval response FileAttributeFetch** fafp = &(*fafcp)->fafs[0][fah]; if (!*fafp) { *fafp = new FileAttributeFetch(h, nodekey, t, reqtag); } else { restag = (*fafp)->tag; return API_EEXIST; } } else { FileAttributeFetch** fafp = &(*fafcp)->fafs[1][fah]; restag = (*fafp)->tag; return API_EEXIST; } return API_OK; } } // build pending attribute string for this handle and remove void MegaClient::pendingattrstring(UploadHandle h, string* fa) { char buf[128]; if (auto uploadFAPtr = fileAttributesUploading.lookupExisting(h)) { for (auto& it : uploadFAPtr->pendingfa) { if (it.first != fa_media) { snprintf(buf, sizeof(buf), "/%u*", (unsigned)it.first); Base64::btoa((byte*)&it.second.fileAttributeHandle, sizeof(it.second.fileAttributeHandle), strchr(buf + 3, 0)); fa->append(buf + !fa->size()); LOG_debug << "Added file attribute " << it.first << " to putnodes"; } } } #ifdef USE_MEDIAINFO // Append MediaInfo attributes, if any mediaFileInfo.addUploadMediaFileAttributes(h, fa); #endif } // Upload file attribute data to fa servers. node handle can be UNDEF if we are giving fa handle back to the app // Used for attaching file attribute to a Node, or prepping for Node creation after upload, or getting fa handle for app. // FIXME: to avoid unnecessary roundtrips to the attribute servers, also cache locally error MegaClient::putfa(NodeOrUploadHandle th, fatype t, SymmCipher* cypher, int tag, std::unique_ptr data) { // CBC-encrypt attribute data (padded to next multiple of BLOCKSIZE) data->resize((data->size() + SymmCipher::BLOCKSIZE - 1) & ~(static_cast(SymmCipher::BLOCKSIZE) - 1)); // Make sure the attribute isn't too large. if (data->size() >= MAX_FILE_ATTRIBUTE_SIZE) { // Monitoring. sendevent(99485, "Exceeded size for file attribute"); // Clarity. LOG_err << "Exceeded size for file attribute: " << tag; // Let the caller know we couldn't upload this attribute. return API_EARGS; } if (!cypher->cbc_encrypt((byte*)data->data(), data->size())) { LOG_err << "Failed to CBC encrypt Node attribute data."; return API_EKEY; } queuedfa.emplace_back(new HttpReqFA(th, t, tag, std::move(data), true, this)); LOG_debug << "File attribute added to queue - " << th << " : " << queuedfa.size() << " queued, " << activefa.size() << " active"; // no other file attribute storage request currently in progress? POST this one. activatefa(); return API_OK; } void MegaClient::activatefa() { while (activefa.size() < MAXPUTFA && queuedfa.size()) { auto curfa = queuedfa.begin(); shared_ptr fa = *curfa; queuedfa.erase(curfa); activefa.push_back(fa); LOG_debug << "Adding file attribute to the active queue"; fa->status = REQ_GET_URL; // will become REQ_INFLIGHT after we get the URL and start data upload. Don't delete while the reqs subsystem would end up with a dangling pointer queueCommand(fa->getURLForFACmd()); } } // has the limit of concurrent transfer tslots been reached? bool MegaClient::slotavail() const { return !mBlocked && tslots.size() < MAXTOTALTRANSFERS; } bool MegaClient::processStorageStatusFromCmd(const storagestatus_t status) { switch (status) { case STORAGE_RED: { const auto isPaywall = (ststatus == STORAGE_PAYWALL); LOG_verbose << "[processStorageStatusFromCmd] Storage status is RED [isPaywall = " << isPaywall << "]"; activateoverquota(0, isPaywall); return true; } case STORAGE_ORANGE: { LOG_verbose << "[processStorageStatusFromCmd] Storage status is ORANGE (close to the " "storage capacity)"; [[fallthrough]]; } case STORAGE_GREEN: { setstoragestatus(status); return true; } case STORAGE_UNKNOWN: { LOG_warn << "[processStorageStatusFromCmd] Storage status is unknown"; return false; } default: { LOG_err << "[processStorageStatusFromCmd] Invalid storage status: " << status; return false; } } } bool MegaClient::setstoragestatus(storagestatus_t status) { // transition from paywall to red should not happen assert(status != STORAGE_RED || ststatus != STORAGE_PAYWALL); if (ststatus != status && (status != STORAGE_RED || ststatus != STORAGE_PAYWALL)) { storagestatus_t previousStatus = ststatus; ststatus = status; mCachedStatus.addOrUpdate(CacheableStatus::STATUS_STORAGE, status); app->notify_storage(ststatus); #ifdef ENABLE_SYNC if (status == STORAGE_RED || status == STORAGE_PAYWALL) // transitioning to OQ { syncs.disableSyncs(STORAGE_OVERQUOTA, false, true); } #endif switch (previousStatus) { case STORAGE_UNKNOWN: if (!(status == STORAGE_GREEN || status == STORAGE_ORANGE)) { break; } [[fallthrough]]; case STORAGE_PAYWALL: mOverquotaDeadlineTs = 0; mOverquotaWarningTs.clear(); [[fallthrough]]; case STORAGE_RED: // Transition from OQ. abortbackoff(true); break; default: break; } return true; } return false; } void MegaClient::getpubliclinkinfo(handle h) { queueCommand(new CommandFolderLinkInfo(this, h)); } error MegaClient::smsverificationsend(const string& phoneNumber, bool reVerifyingWhitelisted) { if (!CommandSMSVerificationSend::isPhoneNumber(phoneNumber)) { return API_EARGS; } queueCommand(new CommandSMSVerificationSend(this, phoneNumber, reVerifyingWhitelisted)); if (reVerifyingWhitelisted) { queueCommand(new CommandGetUserData(this, reqtag, nullptr)); } return API_OK; } error MegaClient::smsverificationcheck(const std::string &verificationCode) { if (!CommandSMSVerificationCheck::isVerificationCode(verificationCode)) { return API_EARGS; } queueCommand(new CommandSMSVerificationCheck(this, verificationCode)); return API_OK; } // It evaluates if the sequence tag matches, if it is ahead of the currently // expected one for the request or if the expected tag has not arrived yet. // // When the tag parameter is not empty: // It returns true when the tag matches the expected in the request or if the "st" in the AP is // below the expected one for the next command response so it is safe to continue. // If the tag in the AP is ahead of the expected in the command and it has been seen it calls // reqs.continueProcessing(this) to continue processing the commands until the next "st" is found // in a command. // It returns false when we don't know the next tag yet (no response for request has been received) // // When the tag parameter is empty: // It always returns true. // When the expected tag has been already seen, it calls reqs.continueProcessing(this) // to continue processing the commands responses. It may find another command with an "st". // This may happen while processing an Action Packet with no "st" or when there are no more // Action Packets to be processed, so we let the pending command responses to be processed. bool MegaClient::sc_checkSequenceTag(const string& tag) { if (tag.empty()) { if (!mCurrentSeqtag.empty() && mCurrentSeqtagSeen) { // Special case to process commands when the current AP has no tag (or there are no // more APs). Needed to process the command associated with the previous Action Packet // or other commands which don't have Sequence Tag. LOG_verbose << clientname << "st tag exhausted for " << mCurrentSeqtag; reqs.continueProcessing(this); } if (!mLastReceivedScSeqTag.empty()) { // We've reached the end of a sequence of actionpackets with "st" // Report the latest "st". app->sequencetag_update(mLastReceivedScSeqTag); LOG_debug << "updated seqtag: " << mLastReceivedScSeqTag; mLastReceivedScSeqTag.clear(); } return true; } else { mLastReceivedScSeqTag = tag; if (mLastReceivedScSeqTag.size() > mLargestEverSeenScSeqTag.size() || (mLastReceivedScSeqTag.size() == mLargestEverSeenScSeqTag.size() && mLastReceivedScSeqTag > mLargestEverSeenScSeqTag)) { mLargestEverSeenScSeqTag = mLastReceivedScSeqTag; } // Also prep this one to be committed to db (if we delay until time to notify the app, we're too late for the commit) mScDbStateRecord.seqTag = mLastReceivedScSeqTag; for (;;) { if (mCurrentSeqtag.empty()) // No commands are expecting any Sequence Tag { if (reqs.cmdsInflight()) { LOG_verbose << clientname << "st tag " << tag << ". Wait for cs response to arrive"; break; // we can't tell yet if a command will give us a tag to wait for } else { if (statecurrent) { // too much logging during catch-up otherwise LOG_verbose << clientname << "st tag " << tag << " no tag pending"; } return true; // nothing pending, process everything } } else // We have received the cs response and we know the current (expected) tag { if (tag == mCurrentSeqtag) { // Tag in the Action Packet is the one we are expecting for the command // response. LOG_verbose << clientname << "st tag " << tag << " matched with the next command response."; // there may be more than one, process until a different one arrives mCurrentSeqtagSeen = true; return true; } else if (mCurrentSeqtagSeen) { // Action Packet tag is ahead of our current expected one, which we have already seen. // Continue processing command responses until we found a new "st". LOG_verbose << clientname << "st tag " << tag << ". Processing commands starting at " << mCurrentSeqtag; reqs.continueProcessing(this); continue; // we may have a new mCurrentSeqtag now } else { // We know there is a mCurrentSeqtag that we must receive, but we have not encountered it yet. Continue with actionpackets LOG_verbose << clientname << "current st tag " << mCurrentSeqtag; LOG_verbose << clientname << "st tag " << tag << " catching up"; assert(tag.size() < mCurrentSeqtag.size() || (tag.size() == mCurrentSeqtag.size() && tag < mCurrentSeqtag)); return true; } } } } return false; } // Looks for the "st" in the action packet, if any. // It calls sc_checkSequenceTag to know if we can continue processing // this action packet or if we have to wait. // True: Action Packet can be processed. // False: Stop processing Action Packets, wait for cs response. bool MegaClient::sc_checkActionPacket(JSON& json, const Node* lastAPDeletedNode) { nameid cmd = 0; for (;;) { switch (json.getnameid()) { case makeNameid("a"): // action referred by the packet cmd = json.getnameid(); break; case makeNameid("i"): // id of the client who made the action triggering this packet json.storeobject(); break; case makeNameid("st"): // sequence tag { string tag; json.storeobject(&tag); return sc_checkSequenceTag(tag); } default: // if we reach any other tag, then 'st' is not present. return sc_checkActionPacketWithoutSt(cmd, lastAPDeletedNode); } } } bool MegaClient::sc_checkActionPacketWithoutSt(nameid cmd, const Node* lastAPDeletedNode) { if (cmd == makeNameid("t") && lastAPDeletedNode && dynamic_cast(reqs.getCurrentCommand(mCurrentSeqtagSeen))) { // special case for actionpackets from the move command - the 'd'+'t' sequence has the tag // on 'd' but not 't'. However we must process the 't' as part of the move, and only call // the command completion after. LOG_verbose << clientname << "st tag implicitly not changing for moves"; return true; } else { // Action Packet with no Sequence Tag. return sc_checkSequenceTag(string()); } } // server-client node update processing void MegaClient::sc_updatenode(JSON& json) { handle h = UNDEF; handle u = 0; const char* a = NULL; m_time_t ts = -1; for (;;) { switch (json.getnameid()) { case makeNameid("n"): h = json.gethandle(); break; case name_id::u: u = json.gethandle(USERHANDLE); break; case makeNameid("at"): a = json.getvalue(); break; case makeNameid("ts"): ts = json.getint(); break; case EOO: if (!ISUNDEF(h)) { std::shared_ptr n; bool notify = false; if ((n = mNodeManager.getNodeByHandle(NodeHandle().set6byte(h)))) { if (u && n->owner != u) { n->owner = u; n->changed.owner = true; notify = true; } if (a && ((n->attrstring && strcmp(n->attrstring->c_str(), a)) || !n->attrstring)) { if (!n->attrstring) { n->attrstring.reset(new string); } JSON::copystring(n->attrstring.get(), a); n->changed.attrs = true; notify = true; #ifdef ENABLE_SYNC if (n->parent) { // possible node name change; sync parent syncs.triggerSync(n->parent->nodeHandle()); } #endif } if (ts != -1 && n->ctime != ts) { n->ctime = ts; n->changed.ctime = true; notify = true; } n->applykey(); n->setattr(); if (notify) { if (!mCurrentSeqtag.empty() && mCurrentSeqtagSeen) { // if this change by this client resulted in NO_KEY, report it n->changed.modifiedByThisClient = true; } mNodeManager.notifyNode(n); } } } return; default: if (!json.storeobject()) { return; } } } } void MegaClient::CacheableStatusMap::loadCachedStatus(CacheableStatus::Type type, int64_t value) { #ifndef NDEBUG auto it = #endif insert(pair(type, CacheableStatus(type, value))); assert(it.second); LOG_verbose << "Loaded status from cache: " << CacheableStatus::typeToStr(type) << " = " << value; switch(type) { case CacheableStatus::Type::STATUS_STORAGE: { mClient->ststatus = static_cast(value); // Notify the storage status if over quota if ((STORAGE_RED == mClient->ststatus) || (STORAGE_PAYWALL == mClient->ststatus)) { mClient->app->notify_storage(mClient->ststatus); } break; } case CacheableStatus::Type::STATUS_BUSINESS: { mClient->mBizStatus = static_cast(value); break; } default: break; } } bool MegaClient::CacheableStatusMap::addOrUpdate(CacheableStatus::Type type, int64_t value) { bool changed = false; CacheableStatus status(type, value); auto it_bool = emplace(type, status); if (!it_bool.second) // already exists { if (it_bool.first->second.value() != value) { it_bool.first->second.setValue(value); // don't replace it, or we lose the dbid changed = true; } } else // added { changed = true; } assert(mClient->statusTable && "Updating status without status table"); if (changed && mClient->statusTable) { DBTableTransactionCommitter committer(mClient->statusTable); LOG_verbose << "Adding/updating status to database: " << status.typeToStr() << " = " << value; if (!mClient->statusTable->put(MegaClient::CACHEDSTATUS, &it_bool.first->second, &mClient->key)) { LOG_err << "Failed to add/update status to db: " << status.typeToStr() << " = " << value; } } return changed; } int64_t MegaClient::CacheableStatusMap::lookup(CacheableStatus::Type type, int64_t defaultValue) { auto it = find(type); return it == end() ? defaultValue : it->second.value(); } CacheableStatus *MegaClient::CacheableStatusMap::getPtr(CacheableStatus::Type type) { auto it = find(type); return it == end() ? nullptr : &it->second; } // read tree object (nodes and users) void MegaClient::readtree(JSON* j) { if (j->enterobject()) { for (;;) { switch (j->getnameid()) { case makeNameid("f"): if (auto putnodesCmd = dynamic_cast(reqs.getCurrentCommand(mCurrentSeqtagSeen))) { putnodesCmd->emptyResponse = Utils::startswith(j->pos, "[]"); readnodes(j, 1, putnodesCmd->source, &putnodesCmd->nn, putnodesCmd->tag != 0, true); } else { readnodes(j, 1, PUTNODES_APP, nullptr, false, false); } break; case makeNameid("f2"): if (auto putnodesCmd = dynamic_cast(reqs.getCurrentCommand(mCurrentSeqtagSeen))) { // new nodes can appear in copied version lists too readnodes(j, 1, putnodesCmd->source, &putnodesCmd->nn, putnodesCmd->tag != 0, true); } else { readnodes(j, 1, PUTNODES_APP, nullptr, false, false); } break; case name_id::u: readusers(j, true); break; case EOO: j->leaveobject(); return; default: if (!j->storeobject()) { return; } } } } } // server-client newnodes processing handle MegaClient::sc_newnodes(JSON& json) { handle originatingUser = UNDEF; for (;;) { switch (json.getnameid()) { case makeNameid("t"): readtree(&json); break; case name_id::u: readusers(&json, true); break; case makeNameid("ou"): originatingUser = json.gethandle(USERHANDLE); break; case EOO: return originatingUser; default: if (!json.storeobject()) { return originatingUser; } } } } // share requests come in the following flavours: // - n/k (set share key) (always symmetric) // - n/o/u[/okd] (share deletion) // - n/o/u/k/r/ts[/ok][/ha] (share addition) (k can be asymmetric) // returns 0 in case of a share addition or error, 1 otherwise bool MegaClient::sc_shares(JSON& json) { handle h = UNDEF; handle oh = UNDEF; handle uh = UNDEF; handle p = UNDEF; handle ou = UNDEF; bool upgrade_pending_to_full = false; const char* sk = NULL; const char* ok = NULL; bool okremoved = false; byte ha[SymmCipher::BLOCKSIZE]; byte sharekey[SymmCipher::BLOCKSIZE]; int have_ha = 0; accesslevel_t r = ACCESS_UNKNOWN; m_time_t ts = 0; int outbound; for (;;) { switch (json.getnameid()) { case makeNameid("p"): // Pending contact request handle for an s2 packet p = json.gethandle(PCRHANDLE); break; case makeNameid("op"): upgrade_pending_to_full = true; break; case makeNameid("n"): // share node h = json.gethandle(); break; case makeNameid("o"): // owner user oh = json.gethandle(USERHANDLE); break; case name_id::u: // target user uh = json.is(EXPORTEDLINK) ? 0 : json.gethandle(USERHANDLE); break; case makeNameid("ou"): ou = json.gethandle(USERHANDLE); break; case makeNameid("ok"): // owner key ok = json.getvalue(); break; case makeNameid("okd"): okremoved = (json.getint() == 1); // owner key removed break; case makeNameid("ha"): // outgoing share signature have_ha = Base64::atob(json.getvalue(), ha, sizeof ha) == sizeof ha; break; case makeNameid("r"): // share access level r = (accesslevel_t)json.getint(); break; case makeNameid("ts"): // share timestamp ts = json.getint(); break; case makeNameid("k"): // share key sk = json.getvalue(); break; case EOO: // we do not process share commands unless logged into a full // account if (loggedin() != FULLACCOUNT) { return false; } // need a share node if (ISUNDEF(h)) { return false; } // ignore unrelated share packets (should never be triggered) outbound = (oh == me); if (!ISUNDEF(oh) && !outbound && (uh != me)) { return false; } // am I the owner of the share? use ok, otherwise k. if (ok && oh == me) { sk = ok; } if (!sk || mKeyManager.generation()) // Same logic as below but without using the key { if (!(!ISUNDEF(oh) && (!ISUNDEF(uh) || !ISUNDEF(p)))) { return false; } if(outbound && ou != me && r == ACCESS_UNKNOWN // Sharee abandoned the share. && mKeyManager.generation() // ^!keys in use && statecurrent) { // Clear the in-use bit for the share key in ^!keys if it was the last sharee if (mKeyManager.isShareKeyInUse(h)) { std::shared_ptr n = nodebyhandle(h); assert(n); // Share removals are received before node deletion. if (n) { size_t total = n->outshares ? n->outshares->size() : 0; total += n->pendingshares ? n->pendingshares->size() : 0; if (total == 1) { // Commit to clear the bit. LOG_debug << "Last sharee has left the share. uh: " << toHandle(uh) << ". Disabling in-use flag for the sharekey in KeyManager. nh: " << toNodeHandle(h); mKeyManager.commit( [this, h]() { mKeyManager.setSharekeyInUse(h, false); }); } } } } if (!loggedIntoFolder()) // ignore User Alerts when logged into folder { if (r == ACCESS_UNKNOWN) { handle peer = outbound ? uh : oh; if (peer != me && peer && !ISUNDEF(peer) && statecurrent && ou != me) { User* u = finduser(peer); useralerts.add(new UserAlert::DeletedShare(peer, u ? u->email : "", oh, h, ts == 0 ? m_time() : ts, useralerts.nextId())); } } else { if (!outbound && statecurrent) { User* u = finduser(oh); // only new shares should be notified (skip permissions changes) bool newShare = u && u->sharing.find(h) == u->sharing.end(); if (newShare) { useralerts.add(new UserAlert::NewShare(h, oh, u->email, ts, useralerts.nextId())); // no need to alert on nodes already in the new share, which are // delivered next useralerts.ignoreNextSharedNodesUnder(h); } } } } newshares.push_back(new NewShare(h, outbound, outbound ? uh : oh, r, ts, NULL, NULL, p, upgrade_pending_to_full, okremoved)); return r == ACCESS_UNKNOWN; } if (sk) { if (!decryptkey(sk, sharekey, sizeof sharekey, &key, 1, h)) { return false; } if (ISUNDEF(oh) && ISUNDEF(uh)) { // share key update on inbound share newshares.push_back(new NewShare(h, 0, UNDEF, ACCESS_UNKNOWN, 0, sharekey)); return true; } if (!ISUNDEF(oh) && (!ISUNDEF(uh) || !ISUNDEF(p))) { if (!outbound && statecurrent && !loggedIntoFolder()) { User* u = finduser(oh); // only new shares should be notified (skip permissions changes) bool newShare = u && u->sharing.find(h) == u->sharing.end(); if (newShare) { useralerts.add(new UserAlert::NewShare(h, oh, u->email, ts, useralerts.nextId())); useralerts.ignoreNextSharedNodesUnder(h); // no need to alert on nodes already in the new share, which are delivered next } } // new share - can be inbound or outbound newshares.push_back(new NewShare(h, outbound, outbound ? uh : oh, r, ts, sharekey, have_ha ? ha : NULL, p, upgrade_pending_to_full)); //Returns false because as this is a new share, the node //could not have been received yet return false; } } return false; default: if (!json.storeobject()) { return false; } } } } bool MegaClient::sc_upgrade(JSON& json, nameid paymentType) { string result; bool success = false; int proNumber = 0; int itemclass = 0; for (;;) { switch (json.getnameid()) { case makeNameid("it"): itemclass = int(json.getint()); // itemclass, 0=Pro, 1=Business, 2=Feature break; case makeNameid("p"): proNumber = int(json.getint()); // Account level break; case makeNameid("r"): json.storeobject(&result); if (result == "s") { success = true; } break; case EOO: // No User Alert for 'ftr' and features if (paymentType != makeNameid("ftr") && (itemclass == 0 || itemclass == 1) && statecurrent && !loggedIntoFolder()) { useralerts.add(new UserAlert::Payment(success, proNumber, m_time(), useralerts.nextId(), paymentType)); } return success; default: if (!json.storeobject()) { return false; } } } } void MegaClient::sc_paymentreminder(JSON& json) { m_time_t expiryts = 0; for (;;) { switch (json.getnameid()) { case makeNameid("ts"): expiryts = int(json.getint()); // timestamp break; case EOO: if (statecurrent && !loggedIntoFolder()) { useralerts.add(new UserAlert::PaymentReminder(expiryts ? expiryts : m_time(), useralerts.nextId())); } return; default: if (!json.storeobject()) { return; } } } } // user/contact updates come in the following format: // u:[{c/m/ts}*] - Add/modify user/contact void MegaClient::sc_contacts(JSON& json) { handle ou = UNDEF; for (;;) { switch (json.getnameid()) { case name_id::u: useralerts.startprovisional(); readusers(&json, true); break; case makeNameid("ou"): ou = json.gethandle(MegaClient::USERHANDLE); break; case EOO: useralerts.evalprovisional(ou); return; default: if (!json.storeobject()) { return; } } } } // server-client file attribute update void MegaClient::sc_fileattr(JSON& json) { std::shared_ptr n = NULL; const char* fa = NULL; for (;;) { switch (json.getnameid()) { case makeNameid("fa"): fa = json.getvalue(); break; case makeNameid("n"): handle h; if (!ISUNDEF(h = json.gethandle())) { n = nodebyhandle(h); } break; case EOO: if (fa && n) { JSON::copystring(&n->fileattrstring, fa); n->changed.fileattrstring = true; mNodeManager.notifyNode(n); } return; default: if (!json.storeobject()) { return; } } } } // server-client user attribute update notification void MegaClient::sc_userattr(JSON& json) { handle uh = UNDEF; User *u = NULL; string ua, uav; string_vector ualist; // stores attribute names string_vector uavlist; // stores attribute versions string_vector::const_iterator itua, ituav; for (;;) { switch (json.getnameid()) { case name_id::u: uh = json.gethandle(USERHANDLE); break; case makeNameid("ua"): if (json.enterarray()) { while (json.storeobject(&ua)) { ualist.push_back(ua); } json.leavearray(); } break; case makeNameid("v"): if (json.enterarray()) { while (json.storeobject(&uav)) { uavlist.push_back(uav); } json.leavearray(); } break; case EOO: if (ISUNDEF(uh)) { LOG_err << "Failed to parse the user :" << uh; } else { u = finduser(uh); } if (!u) { LOG_debug << "User attributes update for non-existing user"; } else if (ualist.size() == uavlist.size()) { assert(ualist.size() && uavlist.size()); // invalidate only out-of-date attributes for (itua = ualist.begin(), ituav = uavlist.begin(); itua != ualist.end(); itua++, ituav++) { attr_t type = User::string2attr(itua->c_str()); if (type == ATTR_UNKNOWN) // several user attributes are ignored by SDK continue; const UserAttribute* attribute = u->getAttribute(type); if (attribute) { if (attribute->version() != *ituav) { bool alreadyExisting = !attribute->isNotExisting(); u->setAttributeExpired(type); switch(type) { case ATTR_KEYRING: if (alreadyExisting) { assert(false); resetKeyring(); } break; case ATTR_MY_BACKUPS_FOLDER: // There should be no actionpackets for this attribute. It // is created and never updated afterwards. if (alreadyExisting) { LOG_err << "The node handle for My backups folder has " "changed"; } [[fallthrough]]; // some attributes should be fetched upon invalidation case ATTR_S4: case ATTR_S4_CONTAINER: case ATTR_KEYS: case ATTR_AUTHRING: case ATTR_AUTHCU255: case ATTR_DEVICE_NAMES: case ATTR_JSON_SYNC_CONFIG_DATA: case ATTR_SYNC_DESIRED_STATE: case ATTR_PUSH_SETTINGS: case ATTR_DISABLE_VERSIONS: case ATTR_STORAGE_STATE: case ATTR_RECENT_CLEAR_TIMESTAMP: { if ((type == ATTR_AUTHRING || type == ATTR_AUTHCU255) && mKeyManager.generation()) { // legacy authrings not useful anymore LOG_warn << "Ignoring update of : " << User::attr2string(type); break; } if (type == ATTR_JSON_SYNC_CONFIG_DATA && alreadyExisting) { // this user's attribute should be set only once and // never change afterwards. If it has changed, it // may indicate a race condition setting the // attribute from another client at the same time LOG_warn << "Sync config data has changed, when it " "should not"; assert(false); } // mCurrentSeqtagSeen is true if the next command response // st matches with the current AP st. So, it is true if we // are the account and session setting the attribute, using // a V3 request, false in any other case. if (mCurrentSeqtagSeen) { LOG_debug << "Skipping " << User::attr2string(type) << " self action packet. Will be managed by " "the command response."; } else { LOG_debug << User::attr2string(type) << " has changed externally. Fetching..."; getua(u, type, 0); } break; } default: LOG_debug << User::attr2string(type) << " has changed" << (!mCurrentSeqtagSeen ? " externally. " : ". ") << "(skip fetching)"; break; } } else { LOG_info << "User attribute already up to date: " << User::attr2string(type); continue; } } else { u->setChanged(type); // if this attr was just created, add it to cache with empty value and set it as invalid // (it will allow to detect if the attr exists upon resumption from cache, in case the value wasn't received yet) if (type == ATTR_DISABLE_VERSIONS && !u->getAttribute(type)) { string emptyStr; u->setAttribute(type, emptyStr, emptyStr); u->setAttributeExpired(type); } } } u->setTag(0); notifyuser(u); } else // different number of attributes than versions --> error { LOG_err << "Unpaired user attributes and versions"; } return; default: if (!json.storeobject()) { return; } } } } // Incoming pending contact additions or updates, always triggered by the creator (reminders, deletes, etc) void MegaClient::sc_ipc(JSON& json) { // fields: m, ts, uts, rts, dts, msg, p, ps m_time_t ts = 0; m_time_t uts = 0; m_time_t rts = 0; m_time_t dts = 0; m_off_t clv = 0; const char *m = NULL; const char *msg = NULL; handle p = UNDEF; PendingContactRequest *pcr; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("m"): m = json.getvalue(); break; case makeNameid("ts"): ts = json.getint(); break; case makeNameid("uts"): uts = json.getint(); break; case makeNameid("rts"): rts = json.getint(); break; case makeNameid("dts"): dts = json.getint(); break; case makeNameid("msg"): msg = json.getvalue(); break; case makeNameid("clv"): clv = json.getint(); break; case makeNameid("p"): p = json.gethandle(MegaClient::PCRHANDLE); break; case EOO: done = true; if (ISUNDEF(p)) { LOG_err << "p element not provided"; break; } if (m && statecurrent && !loggedIntoFolder()) { string email; JSON::copystring(&email, m); useralerts.add(new UserAlert::IncomingPendingContact(dts, rts, p, email, ts, useralerts.nextId())); } pcr = pcrindex.count(p) ? pcrindex[p].get() : (PendingContactRequest *) NULL; if (dts != 0) { //Trying to remove an ignored request if (pcr) { // this is a delete, find the existing object in state pcr->uts = dts; pcr->changed.deleted = true; } } else if (pcr && rts != 0) { // reminder if (uts == 0) { LOG_err << "uts element not provided"; break; } pcr->uts = uts; pcr->changed.reminded = true; } else { // new if (!m) { LOG_err << "m element not provided"; break; } if (ts == 0) { LOG_err << "ts element not provided"; break; } if (uts == 0) { LOG_err << "uts element not provided"; break; } pcr = new PendingContactRequest(p, m, NULL, ts, uts, msg, false); mappcr(p, unique_ptr(pcr)); pcr->autoaccepted = clv != 0; } notifypcr(pcr); break; default: if (!json.storeobject()) { return; } } } } // Outgoing pending contact additions or updates, always triggered by the creator (reminders, deletes, etc) void MegaClient::sc_opc(JSON& json) { // fields: e, m, ts, uts, rts, dts, msg, p m_time_t ts = 0; m_time_t uts = 0; m_time_t rts = 0; m_time_t dts = 0; const char *e = NULL; const char *m = NULL; const char *msg = NULL; handle p = UNDEF; PendingContactRequest *pcr; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("e"): e = json.getvalue(); break; case makeNameid("m"): m = json.getvalue(); break; case makeNameid("ts"): ts = json.getint(); break; case makeNameid("uts"): uts = json.getint(); break; case makeNameid("rts"): rts = json.getint(); break; case makeNameid("dts"): dts = json.getint(); break; case makeNameid("msg"): msg = json.getvalue(); break; case makeNameid("p"): p = json.gethandle(MegaClient::PCRHANDLE); break; case EOO: done = true; if (ISUNDEF(p)) { LOG_err << "p element not provided"; break; } pcr = pcrindex.count(p) ? pcrindex[p].get() : (PendingContactRequest *) NULL; if (dts != 0) // delete PCR { // this is a delete, find the existing object in state if (pcr) { pcr->uts = dts; pcr->changed.deleted = true; } } else if (!e || !m || ts == 0 || uts == 0) { LOG_err << "Pending Contact Request is incomplete."; break; } else if (ts == uts) // add PCR { pcr = new PendingContactRequest(p, e, m, ts, uts, msg, true); mappcr(p, unique_ptr(pcr)); } else // remind PCR { if (rts == 0) { LOG_err << "Pending Contact Request is incomplete (rts element)."; break; } if (pcr) { pcr->uts = rts; pcr->changed.reminded = true; } } notifypcr(pcr); break; default: if (!json.storeobject()) { return; } } } } // Incoming pending contact request updates, always triggered by the receiver of the request (accepts, denies, etc) void MegaClient::sc_upc(JSON& json, bool incoming) { // fields: p, uts, s, m m_time_t uts = 0; int s = 0; const char *m = NULL; handle p = UNDEF, ou = UNDEF; PendingContactRequest *pcr; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("m"): m = json.getvalue(); break; case makeNameid("uts"): uts = json.getint(); break; case makeNameid("s"): s = int(json.getint()); break; case makeNameid("p"): p = json.gethandle(MegaClient::PCRHANDLE); break; case makeNameid("ou"): ou = json.gethandle(MegaClient::PCRHANDLE); break; case EOO: done = true; if (ISUNDEF(p)) { LOG_err << "p element not provided"; break; } pcr = pcrindex.count(p) ? pcrindex[p].get() : (PendingContactRequest *) NULL; if (!pcr) { // As this was an update triggered by us, on an object we must know about, this is kinda a problem. LOG_err << "upci PCR not found, huge massive problem"; break; } else { if (!m) { LOG_err << "m element not provided"; break; } if (s == 0) { LOG_err << "s element not provided"; break; } if (uts == 0) { LOG_err << "uts element not provided"; break; } switch (s) { case 1: // ignored pcr->changed.ignored = true; break; case 2: // accepted pcr->changed.accepted = true; break; case 3: // denied pcr->changed.denied = true; break; } pcr->uts = uts; } if (statecurrent && ou != me && (incoming || s != 2) && !loggedIntoFolder()) { string email; JSON::copystring(&email, m); using namespace UserAlert; useralerts.add(incoming ? (UserAlert::Base*) new UpdatedPendingContactIncoming(s, p, email, uts, useralerts.nextId()) : (UserAlert::Base*) new UpdatedPendingContactOutgoing(s, p, email, uts, useralerts.nextId())); } notifypcr(pcr); break; default: if (!json.storeobject()) { return; } } } } // Public links updates void MegaClient::sc_ph(JSON& json) { // fields: h, ph, d, n, ets handle h = UNDEF; handle ph = UNDEF; bool deleted = false; bool created = false; bool updated = false; bool takendown = false; bool reinstated = false; m_time_t ets = 0; m_time_t cts = 0; std::shared_ptr n; std::string authKey; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("h"): h = json.gethandle(MegaClient::NODEHANDLE); break; case makeNameid("ph"): ph = json.gethandle(MegaClient::NODEHANDLE); break; case makeNameid("w"): static_cast(json.storeobject(&authKey)); break; case name_id::d: deleted = (json.getint() == 1); break; case makeNameid("n"): created = (json.getint() == 1); break; case name_id::u: updated = (json.getint() == 1); break; case makeNameid("down"): { int down = int(json.getint()); takendown = (down == 1); reinstated = (down == 0); } break; case makeNameid("ets"): ets = json.getint(); break; case makeNameid("ts"): cts = json.getint(); break; case EOO: done = true; if (ISUNDEF(h)) { LOG_err << "h element not provided"; break; } if (ISUNDEF(ph)) { LOG_err << "ph element not provided"; break; } if (!deleted && !created && !updated && !takendown) { LOG_err << "d/n/u/down element not provided"; break; } if (!deleted && !cts) { LOG_err << "creation timestamp element not provided"; break; } n = nodebyhandle(h); if (n) { if ((takendown || reinstated) && !ISUNDEF(h) && statecurrent && !loggedIntoFolder()) { useralerts.add(new UserAlert::Takedown(takendown, reinstated, n->type, h, m_time(), useralerts.nextId())); } if (deleted) // deletion { n->plink.reset(); } else { n->setpubliclink(ph, cts, ets, takendown, authKey); } n->changed.publiclink = true; mNodeManager.notifyNode(n); } else { LOG_warn << "node for public link not found"; } break; default: if (!json.storeobject()) { return; } } } } void MegaClient::sc_se(JSON& json) { // fields: e, s string email; int status = -1; handle uh = UNDEF; User *u; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("e"): json.storeobject(&email); break; case name_id::u: uh = json.gethandle(USERHANDLE); break; case makeNameid("s"): status = int(json.getint()); break; case EOO: done = true; if (email.empty()) { LOG_err << "e element not provided"; break; } if (uh == UNDEF) { LOG_err << "u element not provided"; break; } if (status == -1) { LOG_err << "s element not provided"; break; } if (status != EMAIL_REMOVED && status != EMAIL_PENDING_REMOVED && status != EMAIL_PENDING_ADDED && status != EMAIL_FULLY_ACCEPTED) { LOG_err << "unknown value for s element: " << status; break; } u = finduser(uh); if (!u) { LOG_warn << "user for email change not found. Not a contact?"; } else if (status == EMAIL_FULLY_ACCEPTED) { LOG_debug << "Email changed from `" << u->email << "` to `" << email << "`"; setEmail(u, email); } // TODO: manage different status once multiple-emails is supported break; default: if (!json.storeobject()) { return; } } } } #ifdef ENABLE_CHAT void MegaClient::sc_chatupdate(JSON& json, bool readingPublicChat) { // fields: id, u, cs, n, g, ou, ct, ts, m, ck handle chatid = UNDEF; userpriv_vector *userpriv = NULL; int shard = -1; userpriv_vector *upnotif = NULL; bool group = false; handle ou = UNDEF; string title; m_time_t ts = -1; bool publicchat = false; string unifiedkey; bool meeting = false; // chat options: [0 (remove) | 1 (add)], if chat option is not included on action packet, that option is disabled int waitingRoom = 0; int openInvite = 0; int speakRequest = 0; bool done = false; while (!done) { switch (json.getnameid()) { case makeNameid("id"): chatid = json.gethandle(MegaClient::CHATHANDLE); break; case name_id::u: // list of users participating in the chat (+privileges) userpriv = readuserpriv(&json); break; case makeNameid("cs"): shard = int(json.getint()); break; case makeNameid("n"): // the new user, for notification purposes (not used) upnotif = readuserpriv(&json); break; case makeNameid("g"): group = json.getbool(); break; case makeNameid("ou"): ou = json.gethandle(MegaClient::USERHANDLE); break; case makeNameid("ct"): json.storeobject(&title); break; case makeNameid("ts"): // actual creation timestamp ts = json.getint(); break; case makeNameid("m"): assert(readingPublicChat); publicchat = json.getbool(); break; case makeNameid("ck"): assert(readingPublicChat); json.storeobject(&unifiedkey); break; case makeNameid("mr"): assert(readingPublicChat); meeting = json.getbool(); break; case makeNameid("w"): // waiting room waitingRoom = json.getbool(); break; case makeNameid("sr"): // speak request speakRequest = json.getbool(); break; case makeNameid("oi"): // open invite openInvite = json.getbool(); break; case EOO: done = true; if (ISUNDEF(chatid)) { LOG_err << "Cannot read handle of the chat"; } else if (ISUNDEF(ou)) { LOG_err << "Cannot read originating user of action packet"; } else if (shard == -1) { LOG_err << "Cannot read chat shard"; } else { TextChat* chat = nullptr; bool mustHaveUK = false; privilege_t oldPriv = PRIV_UNKNOWN; if (chats.find(chatid) == chats.end()) { chat = new TextChat(readingPublicChat ? publicchat : false); chats[chatid] = chat; mustHaveUK = true; } else { chat = chats[chatid]; oldPriv = chat->getOwnPrivileges(); if (readingPublicChat) { setChatMode(chat, publicchat); } } chat->setChatId(chatid); chat->setShard(shard); chat->setGroup(group); chat->setOwnPrivileges(PRIV_UNKNOWN); chat->setOwnUser(ou); chat->setTitle(title); // chat->flags = ?; --> flags are received in other AP: mcfc if (ts != -1) { chat->setTs(ts); // only in APs related to chat creation or when you're added to } chat->setMeeting(meeting); if (group) { chat->addOrUpdateChatOptions(speakRequest, waitingRoom, openInvite); } bool found = false; userpriv_vector::iterator upvit; if (userpriv) { // find 'me' in the list of participants, get my privilege and remove from peer's list for (upvit = userpriv->begin(); upvit != userpriv->end(); upvit++) { if (upvit->first == me) { found = true; mustHaveUK = (oldPriv <= PRIV_RM && upvit->second > PRIV_RM); chat->setOwnPrivileges(upvit->second); userpriv->erase(upvit); if (userpriv->empty()) { delete userpriv; userpriv = NULL; } break; } } } // if `me` is not found among participants list and there's a notification list... if (!found && upnotif) { // ...then `me` may have been removed from the chat: get the privilege level=PRIV_RM for (upvit = upnotif->begin(); upvit != upnotif->end(); upvit++) { if (upvit->first == me) { mustHaveUK = (oldPriv <= PRIV_RM && upvit->second > PRIV_RM); chat->setOwnPrivileges(upvit->second); break; } } } if (chat->getOwnPrivileges() == PRIV_RM) { // clear the list of peers because API still includes peers in the // actionpacket, but not in a fresh fetchnodes delete userpriv; userpriv = NULL; } chat->setUserPrivileges(userpriv); if (readingPublicChat) { if (!unifiedkey.empty()) // not all actionpackets include it { chat->setUnifiedKey(unifiedkey); } else if (mustHaveUK) { LOG_err << "Public chat without unified key detected"; } } auto cmd = reqs.getCurrentCommand(mCurrentSeqtagSeen); if (cmd) { chat->setTag(cmd->tag ? cmd->tag : -1); } else { chat->setTag(0); // 0 for external change } notifychat(chat); } delete upnotif; break; default: if (!json.storeobject()) { delete upnotif; return; } } } } void MegaClient::sc_chatnode(JSON& json) { handle chatid = UNDEF; handle h = UNDEF; handle uh = UNDEF; bool r = false; bool g = false; for (;;) { switch (json.getnameid()) { case makeNameid("g") : // access granted g = json.getbool(); break; case makeNameid("r"): // access revoked r = json.getbool(); break; case makeNameid("id"): chatid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("n"): h = json.gethandle(MegaClient::NODEHANDLE); break; case name_id::u: uh = json.gethandle(MegaClient::USERHANDLE); break; case EOO: if (chatid != UNDEF && h != UNDEF && uh != UNDEF && (r || g)) { textchat_map::iterator it = chats.find(chatid); if (it == chats.end()) { LOG_err << "Unknown chat for user/node access to attachment"; return; } TextChat *chat = it->second; if (r) // access revoked { if(!chat->setNodeUserAccess(h, uh, true)) { LOG_err << "Unknown user/node at revoke access to attachment"; } } else // access granted { chat->setNodeUserAccess(h, uh); } auto cmd = reqs.getCurrentCommand(mCurrentSeqtagSeen); if (cmd) { chat->setTag(cmd->tag ? cmd->tag : -1); } else { chat->setTag(0); // 0 for external change } notifychat(chat); } else { LOG_err << "Failed to parse attached node information"; } return; default: if (!json.storeobject()) { return; } } } } void MegaClient::sc_chatflags(JSON& json) { bool done = false; handle chatid = UNDEF; byte flags = 0; while(!done) { switch (json.getnameid()) { case makeNameid("id"): chatid = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("f"): flags = byte(json.getint()); break; case EOO: { done = true; textchat_map::iterator it = chats.find(chatid); if (it == chats.end()) { string chatidB64; string tmp((const char*)&chatid, sizeof(chatid)); Base64::btoa(tmp, chatidB64); LOG_err << "Received flags for unknown chatid: " << chatidB64.c_str(); break; } TextChat *chat = chats[chatid]; chat->setFlags(flags); chat->setTag(0); // external change notifychat(chat); break; } default: if (!json.storeobject()) { return; } break; } } } // process mcsmr action packet void MegaClient::sc_delscheduledmeeting(JSON& json) { bool done = false; handle schedId = UNDEF; handle ou = UNDEF; while(!done) { switch (json.getnameid()) { case makeNameid("id"): schedId = json.gethandle(MegaClient::CHATHANDLE); break; case makeNameid("ou"): ou = json.gethandle(MegaClient::USERHANDLE); break; case EOO: { done = true; for (auto auxit = chats.begin(); auxit != chats.end(); auxit++) { TextChat* chat = auxit->second; if (chat->removeSchedMeeting(schedId)) { // remove children scheduled meetings (API requirement) handle_set deletedChildren = chat->removeChildSchedMeetings(schedId); handle chatid = chat->getChatId(); if (statecurrent) { for_each(begin(deletedChildren), end(deletedChildren), [this, ou, chatid](handle sm) { createDeletedSMAlert(ou, chatid, sm); }); createDeletedSMAlert(ou, chatid, schedId); } clearSchedOccurrences(*chat); chat->setTag(0); // external change notifychat(chat); break; } } break; } default: if (!json.storeobject()) { return; } break; } } } // process mcsmp action packet (parse just 1 scheduled meeting per AP) void MegaClient::sc_scheduledmeetings(JSON& json) { handle ou = UNDEF; std::vector> schedMeetings; UserAlert::UpdatedScheduledMeeting::Changeset cs; handle_set childMeetingsDeleted; error e = parseScheduledMeetings(schedMeetings, false, &json, true, &ou, &cs, &childMeetingsDeleted); if (e != API_OK) { LOG_err << "Failed to parse 'mcsmp' action packet. Error: " << e; return; } assert(schedMeetings.size() == 1); for (auto &sm: schedMeetings) { textchat_map::iterator it = chats.find(sm->chatid()); if (it == chats.end()) { LOG_err << "Unknown chatid [" << Base64Str(sm->chatid()) << "] received on mcsmp"; continue; } TextChat* chat = it->second; handle schedId = sm->schedId(); handle parentSchedId = sm->parentSchedId(); m_time_t overrides = sm->overrides(); bool isNewSchedMeeting = !chat->hasScheduledMeeting(schedId); // remove child scheduled meetings in cmd (child meetings deleted) array chat->removeSchedMeetingsList(childMeetingsDeleted); // update scheduled meeting with updated record received at mcsmp AP bool res = chat->addOrUpdateSchedMeeting(std::move(sm)); if (res || !childMeetingsDeleted.empty()) { if (!res) { LOG_debug << "Error adding or updating a scheduled meeting schedId [" << Base64Str(schedId) << "]"; } // if we couldn't update scheduled meeting, but we have deleted it's children, we also need to notify apps handle chatid = chat->getChatId(); if (statecurrent) { // generate deleted scheduled meetings user alerts for each member in cmd (child meetings deleted) array for_each(begin(childMeetingsDeleted), end(childMeetingsDeleted), [this, ou, chatid](const handle& sm) { createDeletedSMAlert(ou, chatid, sm); }); if (res) { if (isNewSchedMeeting) createNewSMAlert(ou, chat->getChatId(), schedId, parentSchedId, overrides); else createUpdatedSMAlert(ou, chat->getChatId(), schedId, parentSchedId, overrides, decltype(cs){cs}); } } } clearSchedOccurrences(*chat); chat->setTag(0); // external change notifychat(chat); } } void MegaClient::createNewSMAlert(const handle& ou, handle chatid, handle sm, handle parentSchedId, m_time_t startDateTime) { if (ou == me) { LOG_verbose << "ScheduledMeetings: Avoiding New SM alert generated by myself" << " in a different session"; return; } useralerts.add(new UserAlert::NewScheduledMeeting(ou, m_time(), useralerts.nextId(), chatid, sm, parentSchedId, startDateTime)); } void MegaClient::createDeletedSMAlert(const handle& ou, handle chatid, handle sm) { if (ou == me) { LOG_verbose << "ScheduledMeetings: Avoiding Deleted SM alert generated by myself" << " in a different session"; return; } useralerts.add(new UserAlert::DeletedScheduledMeeting(ou, m_time(), useralerts.nextId(), chatid, sm)); } void MegaClient::createUpdatedSMAlert(const handle& ou, handle chatid, handle sm, handle parentSchedId, m_time_t startDateTime, UserAlert::UpdatedScheduledMeeting::Changeset&& cs) { if (ou == me) { LOG_verbose << "ScheduledMeetings: Avoiding Updated SM alert generated by myself" << " in a differet session"; return; } useralerts.add(new UserAlert::UpdatedScheduledMeeting(ou, m_time(), useralerts.nextId(), chatid, sm, parentSchedId, startDateTime, std::move(cs))); } #endif void MegaClient::sc_uac(JSON& json) { string email; for (;;) { switch (json.getnameid()) { case makeNameid("m"): json.storeobject(&email); break; case EOO: if (email.empty()) { LOG_warn << "Missing email address in `uac` action packet"; } app->account_updated(); app->notify_confirmation(email.c_str()); ephemeralSession = false; ephemeralSessionPlusPlus = false; return; default: if (!json.storeobject()) { LOG_warn << "Failed to parse `uac` action packet"; return; } } } } void MegaClient::sc_uec(JSON& json) { handle u = UNDEF; string email; for (;;) { switch (json.getnameid()) { case makeNameid("m"): json.storeobject(&email); break; case name_id::u: u = json.gethandle(USERHANDLE); break; case EOO: if (email.empty()) { LOG_warn << "Missing email address in `uec` action packet"; } if (u == UNDEF) { LOG_warn << "Missing user handle in `uec` action packet"; } if (u == me && email.size()) setEmail(ownuser(), email); app->account_updated(); app->notify_confirm_user_email(u, email.c_str()); ephemeralSession = false; ephemeralSessionPlusPlus = false; return; default: if (!json.storeobject()) { LOG_warn << "Failed to parse `uec` action packet"; return; } } } } void MegaClient::sc_sqac(JSON& json) { m_off_t gb = -1; for (;;) { switch (json.getnameid()) { case makeNameid("gb"): gb = json.getint(); // should there be a notification about this? break; case EOO: if (gb == -1) { LOG_warn << "Missing GB allowance in `sqac` action packet"; } // Invalidate cached storage info. mLastKnownCapacity = -1; getuserdata(0); return; default: if (!json.storeobject()) { LOG_warn << "Failed to parse `sqac` action packet"; return; } } } } void MegaClient::sc_pk() { if (!mKeyManager.generation()) { LOG_debug << "Account not upgraded yet"; return; } if (!statecurrent) { LOG_debug << "Skip fetching pending keys triggered by action packet during new session"; return; } queueCommand(new CommandPendingKeys( this, [this](Error e, std::string lastcompleted, std::shared_ptr>> keys) { if (e) { LOG_debug << "No share keys: " << e; if (mKeyManager.promotePendingShares()) { LOG_warn << "Promoting pending shares without new keys (received before " "contact keys?)"; mKeyManager.commit( [this]() { // Changes to apply in the commit mKeyManager.promotePendingShares(); }); // No completion callback in this case } return; } mKeyManager.commit( [this, keys]() { // Changes to apply in the commit LOG_debug << "Processing pending keys"; for (const auto& kv: *keys.get()) { for (const auto& kv2: kv.second) { handle userHandle = kv.first; handle shareHandle = kv2.first; std::string key = kv2.second; mKeyManager.addPendingInShare(toNodeHandle(shareHandle), userHandle, key); } } mKeyManager.promotePendingShares(); }, [this, lastcompleted](error e) { if (e == API_OK) { LOG_debug << "All pending keys were processed"; queueCommand( new CommandPendingKeys(this, lastcompleted, [](Error e) { if (e) { LOG_err << "Error deleting pending keys"; return; } LOG_debug << "Pending keys deleted"; })); } }); })); } void MegaClient::sc_cce() { LOG_debug << "Processing Credit Card Expiry"; app->notify_creditCardExpiry(); } void MegaClient::sc_la(JSON& json) { for (;;) { switch (json.getnameid()) { case EOO: useralerts.onAcknowledgeReceived(); return; default: if (!json.storeobject()) { LOG_warn << "Failed to parse `la` action packet"; return; } } } } void MegaClient::setBusinessStatus(BizStatus newBizStatus) { BizStatus prevBizStatus = mBizStatus; if (newBizStatus != mBizStatus) //has changed { mBizStatus = newBizStatus; mCachedStatus.addOrUpdate(CacheableStatus::STATUS_BUSINESS, newBizStatus); #ifdef ENABLE_SYNC if (mBizStatus == BIZ_STATUS_EXPIRED) //transitioning to expired { syncs.disableSyncs(ACCOUNT_EXPIRED, false, true); } #endif } if (prevBizStatus != BIZ_STATUS_UNKNOWN && prevBizStatus != mBizStatus) //has changed { app->notify_business_status(mBizStatus); } } void MegaClient::sc_ub(JSON& json) { BizStatus status = BIZ_STATUS_UNKNOWN; BizMode mode = BIZ_MODE_UNKNOWN; BizStatus prevBizStatus = mBizStatus; for (;;) { switch (json.getnameid()) { case makeNameid("s"): status = BizStatus(json.getint()); break; case makeNameid("m"): mode = BizMode(json.getint()); break; case EOO: if ((status < BIZ_STATUS_EXPIRED || status > BIZ_STATUS_GRACE_PERIOD)) { std::string err = "Missing or invalid status in `ub` action packet"; LOG_err << err; sendevent(99449, err.c_str(), 0); return; } if ( (mode != BIZ_MODE_MASTER && mode != BIZ_MODE_SUBUSER) && (status != BIZ_STATUS_INACTIVE) ) // when inactive, `m` might be missing (unknown/undefined) { LOG_err << "Unexpected mode for business account at `ub`. Mode: " << mode; return; } mBizMode = mode; setBusinessStatus(status); if (mBizMode != BIZ_MODE_UNKNOWN) { LOG_info << "Disable achievements for business account type"; achievements_enabled = false; } // FIXME: if API decides to include the expiration ts, remove the block below if (mBizStatus == BIZ_STATUS_ACTIVE) { // If new status is active, reset timestamps of transitions mBizGracePeriodTs = 0; mBizExpirationTs = 0; } if (prevBizStatus == BIZ_STATUS_INACTIVE) { app->account_updated(); getuserdata(reqtag); // update account flags } return; default: if (!json.storeobject()) { LOG_warn << "Failed to parse `ub` action packet"; return; } } } } // scan notified nodes for // - name differences with an existing LocalNode // - appearance of new folders // - (re)appearance of files // - deletions // - set export enable/disable // purge removed nodes after notification void MegaClient::notifypurge(void) { if (!mNodeManager.ready()) { // the filesystem is being initialized return; } int i, t; handle tscsn = cachedscsn; if (scsn.ready()) tscsn = scsn.getHandle(); if (mNodeManager.nodeNotifySize() || usernotify.size() || pcrnotify.size() || setnotify.size() || setelementnotify.size() || !useralerts.useralertnotify.empty() #ifdef ENABLE_CHAT || chatnotify.size() #endif || cachedscsn != tscsn) { if (scsn.ready()) { // in case of CS operations inbetween login and fetchnodes, don't // write these to the database yet, as we don't have the scsn updatesc(); } } // purge of notifications related to nodes have been moved to NodeManager since NodesOnDemand mNodeManager.notifyPurge(); t = int(pcrnotify.size()); if (t) { if (!fetchingnodes) { app->pcrs_updated(&pcrnotify[0], t); } // check all notified nodes for removed status and purge for (i = 0; i < t; i++) { PendingContactRequest* pcr = pcrnotify[static_cast(i)]; if (pcr->removed()) { pcrindex.erase(pcr->id); } else { pcr->notified = false; memset(&(pcr->changed), 0, sizeof(pcr->changed)); } } pcrnotify.clear(); } // users are never deleted (except at account cancellation) t = int(usernotify.size()); if (t) { if (!fetchingnodes) { app->users_updated(&usernotify[0], t); } for (i = 0; i < t; i++) { User* u = usernotify[static_cast(i)]; u->notified = false; u->resetTag(); memset(&(u->changed), 0, sizeof(u->changed)); if (u->show == INACTIVE && u->userhandle != me) { // delete any remaining shares with this user for (handle_set::iterator it = u->sharing.begin(); it != u->sharing.end(); it++) { std::shared_ptr n = nodebyhandle(*it); if (n && !n->changed.removed) { sendevent(99435, "Orphan incoming share", 0); } } u->sharing.clear(); discarduser(u->userhandle, false); } } usernotify.clear(); } useralerts.purgescalerts(); if (!setelementnotify.empty()) { notifypurgesetelements(); } if (!setnotify.empty()) { notifypurgesets(); } #ifdef ENABLE_CHAT t = int(chatnotify.size()); if (t) { if (!fetchingnodes) { app->chats_updated(&chatnotify, t); } for (textchat_map::iterator it = chatnotify.begin(); it != chatnotify.end(); it++) { TextChat *chat = it->second; chat->notified = false; chat->resetTag(); chat->changed = {}; chat->clearSchedMeetingsChanged(); chat->clearUpdatedSchedMeetingOccurrences(); } chatnotify.clear(); } #endif totalNodes.store(mNodeManager.getNodeCount()); } void MegaClient::persistAlert(UserAlert::Base* a) { if (!sctable) return; // Alerts are not critical. There is no need to break execution if db ops failed for some (rare) reason if (a->removed()) { if (a->dbid) { if (sctable->del(a->dbid)) { LOG_verbose << "UserAlert of type " << a->type << " removed from db."; } else { LOG_err << "Failed to remove UserAlert of type " << a->type << " from db."; } } } else // insert or replace { if (sctable->put(CACHEDALERT, a, &key)) { LOG_verbose << "UserAlert of type " << a->type << " inserted or replaced in db."; } else { LOG_err << "Failed to insert or update UserAlert of type " << a->type << " in db."; } } } // return node pointer derived from node handle shared_ptr MegaClient::nodebyhandle(handle h) { return nodeByHandle(NodeHandle().set6byte(h)); } shared_ptr MegaClient::nodeByHandle(NodeHandle h) { if (h.isUndef()) return nullptr; return mNodeManager.getNodeByHandle(h); } /** * @brief Helper function to convert AttrMap containing totp data. If any of the fields is missing, * nullopt will be returned. */ static std::optional toTotpParameters(const AttrMap& totpAttrs) { const auto shse = totpAttrs.getString(MegaClient::PWM_ATTR_PASSWORD_TOTP_SHSE); if (!shse) return {}; const auto exptStr = totpAttrs.getStringView(MegaClient::PWM_ATTR_PASSWORD_TOTP_EXPT); const auto exptOpt = convertIfPositive( stringToNumber(exptStr.value_or("")).value_or(-1)); if (!exptOpt) return {}; const auto ndigitsStr = totpAttrs.getStringView(MegaClient::PWM_ATTR_PASSWORD_TOTP_NDIGITS); const auto ndigitsOpt = convertIfPositive(stringToNumber(ndigitsStr.value_or("")).value_or(-1)); if (!ndigitsOpt) return {}; const auto hashAlgoStr = totpAttrs.getStringView(MegaClient::PWM_ATTR_PASSWORD_TOTP_HASH_ALG); const auto hashAlgoOpt = totp::charTohashAlgorithm(hashAlgoStr.value_or("")); if (!hashAlgoOpt) return {}; return totp::TotpParameters{*shse, *ndigitsOpt, *exptOpt, *hashAlgoOpt}; } MegaClient::TotpTokenResult MegaClient::generateTotpTokenFromNode(const handle h) { static const auto logAndError = generateLogAndReturnError( [](const error e) -> TotpTokenResult { return {e, {"", 0}}; }); if (h == UNDEF) return logAndError(API_EARGS, "Invalid handle (UNDEF) to generate TotpToken"); const auto node = nodebyhandle(h); if (!node) return logAndError(API_ENOENT, "No node found with the given handle"); if (!node->isPasswordNode()) return logAndError(API_ENOENT, "No password node found with the given handle"); const auto totpData = node->attrs.getComplexNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER, MegaClient::PWM_ATTR_PASSWORD_TOTP); if (!totpData) return logAndError(API_EKEY, "Trying to generate totp token for a node with no totp data"); AttrMap totpMap; totpMap.fromjsonObject( totpData->map.at(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP)).c_str()); const auto totpParams = toTotpParameters(totpMap); if (!totpParams) { assert(false && "If present in a password node, totp data must be well formatted"); return logAndError(API_EINTERNAL, "Trying to generate totp token for a node with incomplete totp data"); } const auto [token, lifetime] = totp::generateTOTP(*totpParams); if (token.empty()) { assert(false && "If present in a password node, totp data must be well formatted"); return logAndError(API_EINTERNAL, "Trying to generate totp token for a node with ill-formed totp data"); } return {API_OK, {token, static_cast(lifetime.count())}}; } shared_ptr MegaClient::nodeByPath(const char* path, std::shared_ptr node, nodetype_t type) { if (!path) return NULL; std::shared_ptr cwd = node; vector c; string s; int l = 0; const char* bptr = path; int remote = 0; std::shared_ptr n; // split path by / or : do { if (!l) { if (*(const signed char*)path >= 0) { if (*path == '\\') { if (path > bptr) { s.append(bptr, static_cast(path - bptr)); } bptr = ++path; if (*bptr == 0) { c.push_back(s); break; } continue; } if (*path == '/' || *path == ':' || !*path) { if (*path == ':') { if (c.size()) { return NULL; } remote = 1; } if (path > bptr) { s.append(bptr, static_cast(path - bptr)); } bptr = path + 1; c.push_back(s); s.erase(); } } else if ((*path & 0xf0) == 0xe0) { l = 1; } else if ((*path & 0xf8) == 0xf0) { l = 2; } else if ((*path & 0xfc) == 0xf8) { l = 3; } else if ((*path & 0xfe) == 0xfc) { l = 4; } } else { l--; } } while (*path++); if (l) { return NULL; } if (remote) { // target: user inbox - it's not a node - return NULL if (c.size() == 2 && !c[1].size()) { return NULL; } User* u; u = finduser(c[0].c_str()); if (u) { // locate matching share from this user handle_set::iterator sit; string name; for (sit = u->sharing.begin(); sit != u->sharing.end(); sit++) { if ((n = nodebyhandle(*sit))) { if(!name.size()) { name = c[1]; LocalPath::utf8_normalize(&name); } if (!strcmp(name.c_str(), n->displayname())) { l = 2; break; } } } } if (!l) { return NULL; } } else { // path starting with / if (c.size() > 1 && !c[0].size()) { // path starting with // if (c.size() > 2 && !c[1].size()) { if (c[2] == "in") { n = nodeByHandle(mNodeManager.getRootNodeVault()); assert(!n || n->type == VAULTNODE); } else if (c[2] == "bin") { n = nodeByHandle(mNodeManager.getRootNodeRubbish()); assert(!n || n->type == RUBBISHNODE); } else { return NULL; } l = 3; } else { n = nodeByHandle(mNodeManager.getRootNodeFiles()); assert(!n || loggedIntoFolder() || n->type == ROOTNODE); //folder links root node type is not ROOTNODE l = 1; } } else { n = cwd; } } // parse relative path while (n && l < (int)c.size()) { if (c[static_cast(l)] != ".") { if (c[static_cast(l)] == "..") { if (n->parent) { n = n->parent; } } else { // locate child node (explicit ambiguity resolution: not implemented) if (c[static_cast(l)].size()) { std::shared_ptr nn; switch (type) { case FILENODE: case FOLDERNODE: nn = childnodebynametype(n.get(), c[static_cast(l)].c_str(), l + 1 < int(c.size()) ? FOLDERNODE : type); // only the last leaf could be a file break; case TYPE_UNKNOWN: default: nn = childnodebyname(n.get(), c[static_cast(l)].c_str()); break; } if (!nn) { return NULL; } n = nn; } } } l++; } return (type == TYPE_UNKNOWN || (n && type == n->type)) ? n : nullptr; } // server-client deletion std::shared_ptr MegaClient::sc_deltree(JSON& json, bool& moveOperation) { std::shared_ptr n; handle originatingUser = UNDEF; for (;;) { switch (json.getnameid()) { case makeNameid("n"): handle h; if (!ISUNDEF((h = json.gethandle()))) { n = nodebyhandle(h); } break; case makeNameid("m"): moveOperation = json.getbool(); break; case makeNameid("ou"): originatingUser = json.gethandle(USERHANDLE); break; case EOO: if (n) { TreeProcDel td; if (!loggedIntoFolder()) { useralerts.beginNotingSharedNodes(); } int creqtag = reqtag; reqtag = 0; td.setOriginatingUser(originatingUser); proctree(n, &td); reqtag = creqtag; if (!loggedIntoFolder()) { useralerts.stashDeletedNotedSharedNodes(originatingUser); } #ifdef ENABLE_SYNC // None sync operation is required if version is removed if (n->parent && n->parent->type != FILENODE) { syncs.triggerSync(n->parent->nodeHandle()); } #endif if (!loggedIntoFolder()) { useralerts.convertNotedSharedNodes(false, originatingUser); } } return moveOperation ? n : nullptr; default: if (!json.storeobject()) { return NULL; } } } } // generate handle authentication token void MegaClient::handleauth(handle h, byte* auth) { Base64::btoa((byte*)&h, NODEHANDLE, (char*)auth); memcpy(auth + sizeof h, auth, sizeof h); key.ecb_encrypt(auth); } // make attribute string; add magic number prefix void MegaClient::makeattr(SymmCipher* key, string* attrstring, const char* json, int l) { if (l < 0) { l = int(strlen(json)); } int ll = (l + 6 + SymmCipher::KEYLENGTH - 1) & - SymmCipher::KEYLENGTH; byte* buf = new byte[static_cast(ll)]; memcpy(buf, "MEGA{", 5); // check for the presence of the magic number "MEGA" memcpy(buf + 5, json, static_cast(l)); buf[l + 5] = '}'; memset(buf + 6 + l, 0, static_cast(ll - l - 6)); if (!key->cbc_encrypt(buf, static_cast(ll))) { LOG_err << "Failed to CBC encrypt attribute"; // Is refactoring needed to add return value for current function? assert(false); } attrstring->assign((char*)buf, static_cast(ll)); delete[] buf; } void MegaClient::makeattr(SymmCipher* key, const std::unique_ptr& attrstring, const char* json, int l) { makeattr(key, attrstring.get(), json, l); } error MegaClient::addTagToNode(std::shared_ptr node, const string& tag, CommandSetAttr::Completion&& c) { nameid tagNameid = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); std::string tags = node->attrs.map[tagNameid]; std::set tokens = splitString(tags, TAG_DELIMITER); if (tokens.size() == MAX_NUMBER_TAGS) { return API_ETOOMANY; } if (getTagPosition(tokens, escapeWildCards(tag), false) != tokens.end()) { return API_EEXIST; } if (tags.size()) // first tag, delimeter isnt' required { tags.push_back(TAG_DELIMITER); } tags.append(tag); if (tags.size() > MAX_TAGS_SIZE) { return API_EARGS; } AttrMap map; map.map[tagNameid] = std::move(tags); return setattr(node, std::move(map.map), std::move(c), false); } std::vector MegaClient::getNodeTags(const std::string& delimitedTags) { // Translate list of delimited tags into a vector. auto tags = splitString>(delimitedTags, TAG_DELIMITER); // Make sure tags are in ascending natural order. std::sort(tags.begin(), tags.end(), NaturalSortingComparator()); // Return tags to caller. return tags; } std::vector MegaClient::getNodeTags(std::shared_ptr node) { // No node? No tags. if (!node) return {}; // Get our hands on the node's delimited list of tags. auto delimitedTags = node->attrs.getString(NODE_ATTRIBUTE_TAGS); // Node's list of delimited tags is empty or missing. if (!delimitedTags || delimitedTags->empty()) return {}; // Translate delimited list of tags into a vector. return getNodeTags(*delimitedTags); } auto MegaClient::getNodeTagsBelow(CancelToken cancelToken, NodeHandle handle, const std::string& pattern) -> std::optional> { std::set handles; if (handle.isUndef()) { // Callers interested in all tags visible under the usual root nodes. for (auto node: mNodeManager.getRootNodes()) handles.emplace(node->nodeHandle()); // As well as those visible below full-access incoming shares. forEachIncomingShare( [&handles](std::shared_ptr node) { if (node->inshare->access == FULL) handles.emplace(node->nodeHandle()); }); } else { // Callers interested in tags visible under a particular node. handles.emplace(handle); } // Get all tags visible at or below the specified node handles. return mNodeManager.getNodeTagsBelow(std::move(cancelToken), handles, pattern); } auto MegaClient::getNodeTagsBelow(NodeHandle handle, const std::string& pattern) -> std::optional> { return getNodeTagsBelow(CancelToken(), handle, pattern); } error MegaClient::removeTagFromNode(std::shared_ptr node, const string& tag, CommandSetAttr::Completion&& c) { nameid tagNameid = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); std::string tags = node->attrs.map[tagNameid]; std::set tokens = splitString(tags, TAG_DELIMITER); auto tagPosition = getTagPosition(tokens, escapeWildCards(tag), false); if (tagPosition == tokens.end()) { return API_ENOENT; } tokens.erase(tagPosition); std::string str = joinStrings(tokens.begin(), tokens.end(), std::string{TAG_DELIMITER}); AttrMap map; map.map[tagNameid] = std::move(str); return setattr(node, std::move(map.map), std::move(c), false); } error MegaClient::updateTagNode(std::shared_ptr node, const string& newTag, const string& oldTag, CommandSetAttr::Completion&& c) { nameid tagNameid = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); std::string tags = node->attrs.map[tagNameid]; std::set tokens = splitString(tags, TAG_DELIMITER); auto tagPosition = getTagPosition(tokens, escapeWildCards(oldTag), false); if (tagPosition == tokens.end()) { return API_ENOENT; } tokens.erase(tagPosition); tagPosition = getTagPosition(tokens, escapeWildCards(newTag), false); if (tagPosition != tokens.end()) { return API_EEXIST; } tokens.insert(newTag); std::string str = joinStrings(tokens.begin(), tokens.end(), std::string{TAG_DELIMITER}); if (str.size() > MAX_TAGS_SIZE) { return API_EARGS; } AttrMap map; map.map[tagNameid] = std::move(str); return setattr(node, std::move(map.map), std::move(c), false); } // update node attributes error MegaClient::setattr(std::shared_ptr n, attr_map&& updates, CommandSetAttr::Completion&& c, bool canChangeVault) { if (ststatus == STORAGE_PAYWALL) { return API_EPAYWALL; } if (!checkaccess(n.get(), FULL)) { return API_EACCESS; } if (SymmCipher* cipher = n->nodecipher()) { std::string at; AttrMap attributeMap; attributeMap.map = updates; attributeMap.getjson(&at); makeattr(cipher, &at, at.c_str(), int(at.size())); if (at.size() > MAX_NODE_ATTRIBUTE_SIZE) { sendevent(99484, "Node attribute exceed maximun size"); LOG_err << "Node attribute exceed maximun size"; return API_EARGS; } } // Check and delete invalid fav attributes if (n->firstancestor()->getShareType() == ShareType_t::IN_SHARES) // Avoid an inshare to be tagged as favourite or sensitive by the // sharee { std::vector nameIds = {AttrMap::string2nameid("fav"), AttrMap::string2nameid("sen")}; for (nameid& nameId: nameIds) { updates.erase(nameId); } if (updates.empty()) { return API_EACCESS; } } // we only update the values stored in the node once the command completes successfully queueCommand(new CommandSetAttr(this, n, std::move(updates), std::move(c), canChangeVault)); return API_OK; } error MegaClient::putnodes_prepareOneFile(NewNode* newnode, Node* parentNode, const char* utf8Name, const UploadToken& binaryUploadToken, const byte* theFileKey, const char* megafingerprint, const char* fingerprintOriginal, std::function addNodeAttrsFunc, std::function addFileAttrsFunc) { error e = API_OK; // set up new node as file node newnode->source = NEW_UPLOAD; newnode->type = FILENODE; newnode->uploadtoken = binaryUploadToken; newnode->parenthandle = UNDEF; newnode->uploadhandle = mUploadHandle.next(); newnode->attrstring.reset(new string); newnode->fileattributes.clear(); // add custom file attributes if (addFileAttrsFunc) { e = addFileAttrsFunc(newnode->fileattributes); if (e != API_OK) { return e; } } // fill node attributes (honoring those in previous version) AttrMap attrs; shared_ptr previousNode = childnodebyname(parentNode, utf8Name, true); honorPreviousVersionAttrs(previousNode.get(), attrs); attrs.map[makeNameid("n")] = utf8Name; attrs.map[name_id::c] = megafingerprint; if (fingerprintOriginal) { attrs.map[makeNameid("c0")] = fingerprintOriginal; } // add custom node attributes if (addNodeAttrsFunc) { e = addNodeAttrsFunc(attrs); if (e != API_OK) { return e; } } // JSON-encode object and encrypt attribute string and node key itself too string tattrstring; attrs.getjson(&tattrstring); SymmCipher cipher; cipher.setkey(theFileKey); makeattr(&cipher, newnode->attrstring, tattrstring.c_str()); newnode->nodekey.assign((char*)theFileKey, FILENODEKEYLENGTH); SymmCipher::xorblock((const byte*)newnode->nodekey.data() + SymmCipher::KEYLENGTH, (byte*)newnode->nodekey.data()); // adjust previous version node string name(utf8Name); if (std::shared_ptr ovn = getovnode(parentNode, &name)) { newnode->ovhandle = ovn->nodeHandle(); } return e; } void MegaClient::putnodes_prepareOneFolder(NewNode* newnode, std::string foldername, bool canChangeVault, std::function addAttrs) { MegaClient::putnodes_prepareOneFolder(newnode, foldername, rng, tmpnodecipher, canChangeVault, addAttrs); } void MegaClient::putnodes_prepareOneFolder(NewNode* newnode, std::string foldername, PrnGen& rng, SymmCipher& tmpnodecipher, bool canChangeVault, std::function addAttrs) { string attrstring; byte buf[FOLDERNODEKEYLENGTH]; // set up new node as folder node newnode->source = NEW_NODE; newnode->type = FOLDERNODE; newnode->nodehandle = 0; newnode->parenthandle = UNDEF; newnode->canChangeVault = canChangeVault; // generate fresh random key for this folder node rng.genblock(buf, FOLDERNODEKEYLENGTH); newnode->nodekey.assign((char*)buf, FOLDERNODEKEYLENGTH); tmpnodecipher.setkey(buf); // generate fresh attribute object with the folder name AttrMap attrs; LocalPath::utf8_normalize(&foldername); attrs.map['n'] = foldername; // add custom attributes if (addAttrs) addAttrs(attrs); // JSON-encode object and encrypt attribute string attrs.getjson(&attrstring); newnode->attrstring.reset(new string); makeattr(&tmpnodecipher, newnode->attrstring, attrstring.c_str()); } void MegaClient::putnodes_prepareCopy(std::vector& nn, unsigned& nc, const nodetype_t type, const handle nodehandle, const handle parenthandle, const string& nodekey, const AttrMap& attrs, const bool resetSensitive, const bool isPublic) { assert(nc > 0); NewNode* t = &nn[--nc]; // copy node t->source = isPublic ? NEW_PUBLIC : NEW_NODE; t->type = type; t->nodehandle = nodehandle; t->parenthandle = parenthandle; // copy key (if file) or generate new key (if folder) if (type == FILENODE) { t->nodekey = nodekey; } else { byte buf[FOLDERNODEKEYLENGTH]; rng.genblock(buf, sizeof buf); t->nodekey.assign((char*)buf, FOLDERNODEKEYLENGTH); } AttrMap tattrs; tattrs.map = attrs.map; nameid rrname = AttrMap::string2nameid("rr"); attr_map::iterator it = tattrs.map.find(rrname); if (it != tattrs.map.end()) { LOG_debug << "Removing rr attribute"; tattrs.map.erase(it); } if (resetSensitive && tattrs.map.erase(AttrMap::string2nameid("sen"))) { LOG_debug << "Removing sen attribute"; } string attrstring; t->attrstring.reset(new string); tattrs.getjson(&attrstring); SymmCipher cipher; cipher.setkey((const byte*)t->nodekey.data(), type); makeattr(&cipher, t->attrstring, attrstring.c_str()); } error MegaClient::updateNodeMtime(std::shared_ptr node, const m_time_t newMtime, std::function&& completion) { if (!node || node->mtime == newMtime || !completion) { LOG_err << "updateNodeMtime immediate error (EARGS)"; return API_EARGS; } // Compute the node's new fingerprint attribute. auto attribute = ([&]() { // Grab the node's current fingerprint. auto fingerprint = node->fingerprint(); // Update the modification time. fingerprint.mtime = newMtime; std::string attribute; // Serialize the fingerprint into an attribute. fingerprint.serializefingerprint(&attribute); // Return attribute to caller. return attribute; })(); return setattr(node, attr_map('c', std::move(attribute)), std::move(completion), true); } error MegaClient::updateNodeFingerprint(std::shared_ptr node, const FileFingerprint& newFingerprint, std::function&& completion) { if (!node || node->type != FILENODE || !completion) { LOG_err << "updateNodeFingerprint immediate error (EARGS)"; return API_EARGS; } if (!newFingerprint.isvalid || newFingerprint.size < 0) { LOG_err << "updateNodeFingerprint immediate error (EARGS): invalid fingerprint"; return API_EARGS; } if (node->size != newFingerprint.size) { LOG_err << "updateNodeFingerprint immediate error (EARGS): size mismatch (node=" << node->size << ", fp=" << newFingerprint.size << ")"; return API_EARGS; } std::string attribute; newFingerprint.serializefingerprint(&attribute); return setattr(node, attr_map('c', std::move(attribute)), std::move(completion), true); } // send new nodes to API for processing void MegaClient::putnodes(NodeHandle h, VersioningOption vo, vector&& newnodes, const char* cauth, int tag, bool canChangeVault, string customerIpPort, CommandPutNodes::Completion&& resultFunction, std::optional pitag) { queueCommand(new CommandPutNodes(this, h, NULL, vo, std::move(newnodes), tag, PUTNODES_APP, cauth, std::move(resultFunction), canChangeVault, customerIpPort, pitag)); } // drop nodes into a user's inbox (must have RSA keypair) - obsolete feature, kept for sending logs // to helpdesk void MegaClient::putnodes(const char* user, vector&& newnodes, int tag, CommandPutNodes::Completion&& completion) { if (!finduser(user, 0) && !user) { if (completion) completion(API_EARGS, USER_HANDLE, newnodes, false, tag, {}); else app->putnodes_result(API_EARGS, USER_HANDLE, newnodes, false, tag, {}); return; } queuepubkeyreq(user, std::make_unique(std::move(newnodes), tag, std::move(completion))); } void MegaClient::putFileAttributes(handle h, fatype t, const string& encryptedAttributes, int tag) { std::shared_ptr node = mNodeManager.getNodeByHandle(NodeHandle().set6byte(h)); // Make sure the node's file attribute string is within acceptable limits. if (node && node->fileattrstring.size() + encryptedAttributes.size() >= MAX_NODE_ATTRIBUTE_SIZE) { // Monitoring. sendevent(99485, "Exceeded size for node attribute"); // Clarity. LOG_err << "Exceeded size for node attribute: " << t; restag = tag; // Let app know the request failed. app->putfa_result(h, t, API_EARGS); return; } queueCommand(new CommandAttachFA(this, h, t, encryptedAttributes, tag)); } // returns 1 if node has accesslevel a or better, 0 otherwise int MegaClient::checkaccess(Node* n, accesslevel_t a) { // writable folder link access is supposed to be full if (loggedIntoWritableFolder()) { return a <= FULL; } // folder link access is always read-only - ignore login status during // initial tree fetch if (a < OWNERPRELOGIN && !loggedin()) { return a == RDONLY; } // trace back to root node (always full access) or share node while (n) { if (n->inshare) { return n->inshare->access >= a; } if (!n->parent) { return n->type > FOLDERNODE; } n = n->parent.get(); } return 0; } // returns API_OK if a move operation is permitted, API_EACCESS or // API_ECIRCULAR otherwise. Also returns API_EPAYWALL if in PAYWALL. error MegaClient::checkmove(Node* fn, Node* tn) { // precondition #0: not in paywall if (ststatus == STORAGE_PAYWALL) { return API_EPAYWALL; } // condition #1: cannot move top-level node, must have full access to fn's // parent if (!fn->parent.get() || !checkaccess(fn->parent.get(), FULL)) { return API_EACCESS; } // condition #2: target must be folder if (tn->type == FILENODE) { return API_EACCESS; } // condition #3: must have write access to target if (!checkaccess(tn, RDWR)) { return API_EACCESS; } // condition #4: source can't be a version if (fn->parent->type == FILENODE) { return API_EACCESS; } // condition #5: tn must not be below fn (would create circular linkage) for (;;) { if (tn == fn) { return API_ECIRCULAR; } if (tn->inshare || !tn->parent) { break; } tn = tn->parent.get(); } // condition #6: fn cannot be the S4 container if S4 is enabled if (mIsS4Enabled && fn->nodeHandle().eq(mS4Container)) { return API_EACCESS; } // condition #7: fn and tn must be in the same tree (same ultimate parent // node or shared by the same user) for (;;) { if (fn->inshare || !fn->parent) { break; } fn = fn->parent.get(); } // moves within the same tree or between the user's own trees are permitted if (fn == tn || (!fn->inshare && !tn->inshare)) { return API_OK; } // moves between inbound shares from the same user are permitted if (fn->inshare && tn->inshare && fn->inshare->user == tn->inshare->user) { return API_OK; } return API_EACCESS; } // move node to new parent node (for changing the filename, use setattr and // modify the 'n' attribute) error MegaClient::rename(std::shared_ptr n, std::shared_ptr p, syncdel_t syncdel, NodeHandle prevparenthandle, const char *newName, bool canChangeVault, CommandMoveNode::Completion&& c) { if (mBizStatus == BIZ_STATUS_EXPIRED) { return API_EBUSINESSPASTDUE; } error e; e = checkmove(n.get(), p.get()); if (e) { return e; } if (p->firstancestor()->type == RUBBISHNODE) { // similar to the webclient, send `s2` along with `m` if the node is moving to the rubbish removeOutSharesFromSubtree(n, 0); } std::shared_ptr prevParent = NULL; if (!prevparenthandle.isUndef()) { prevParent = nodeByHandle(prevparenthandle); } else { prevParent = n->parent; } attr_map attrUpdates; if (prevParent) { std::shared_ptr prevRoot = getrootnode(prevParent); std::shared_ptr newRoot = getrootnode(p); handle rubbishHandle = mNodeManager.getRootNodeRubbish().as8byte(); nameid rrname = AttrMap::string2nameid("rr"); if (prevRoot->nodehandle != rubbishHandle && newRoot->nodehandle == rubbishHandle) { // deleted node char base64Handle[12]; Base64::btoa((byte*)&prevParent->nodehandle, MegaClient::NODEHANDLE, base64Handle); if (strcmp(base64Handle, n->attrs.map[rrname].c_str())) { LOG_debug << "Adding rr attribute"; attrUpdates[rrname] = base64Handle; } } else if (prevRoot->nodehandle == rubbishHandle && newRoot->nodehandle != rubbishHandle) { // undeleted node attr_map::iterator it = n->attrs.map.find(rrname); if (it != n->attrs.map.end()) { LOG_debug << "Removing rr attribute"; attrUpdates[rrname] = ""; } } } if (n && n->owner != me && n->isMarkedSensitive() && attrUpdates.erase(AttrMap::string2nameid("sen"))) { LOG_debug << "Removing sen attribute"; } if (newName) { attrUpdates['n'] = newName; } if (!attrUpdates.empty()) { // send attribute changes first so that any rename is already applied when the move node completes setattr(n, std::move(attrUpdates), nullptr, canChangeVault); // no completion function, result is after the move completes } // rewrite keys of foreign nodes that are moved out of an outbound share rewriteforeignkeys(n); queueCommand( new CommandMoveNode(this, n, p, syncdel, prevparenthandle, std::move(c), canChangeVault)); return API_OK; } error MegaClient::createFolder(std::shared_ptr parent, const char* name, int rTag) { assert(parent); assert(name && *name); std::vector nn(1); bool canChangeVault = parent->isPasswordManagerNodeFolder(); NewNode& newPasswordNode = nn.front(); putnodes_prepareOneFolder(&newPasswordNode, name, canChangeVault); const char* cauth = nullptr; const bool inIncomingShare = parent && parent->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); Pitag pitag; if (canChangeVault) { pitag.purpose = PitagPurpose::Password; pitag.nodeType = PitagNodeType::Folder; pitag.target = PitagTarget::CloudDrive; } else { pitag.purpose = PitagPurpose::CreateFolder; pitag.nodeType = PitagNodeType::Folder; pitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } // newNode.nodekey will be encrypted with user's MK in Command construction // using existing logic with default client->app->putnodes_result as callback for completion putnodes(parent->nodeHandle(), VersioningOption::NoVersioning, std::move(nn), cauth, rTag, canChangeVault, {}, nullptr, pitag); return API_OK; } error MegaClient::checkRenameNodePrecons(std::shared_ptr n) { if (ststatus == STORAGE_PAYWALL) return API_EPAYWALL; if (!n) return API_EARGS; if (!checkaccess(n.get(), FULL)) return API_EACCESS; return API_OK; } error MegaClient::renameNode(NodeHandle nh, const char* newName, CommandSetAttr::Completion&& cbRequest) { auto node = nodeByHandle(nh); if (const auto err = checkRenameNodePrecons(node); err != API_OK) return err; if (!(newName && *newName)) return API_EARGS; const bool canChangeVault = node->isPasswordManagerNodeFolder() || node->isPasswordManagerNode(); string sname = newName; LocalPath::utf8_normalize(&sname); return setattr(node, attr_map('n', sname), std::move(cbRequest), canChangeVault); } error MegaClient::removeNode(NodeHandle nh, bool keepVersions, int rTag) { std::shared_ptr node = nodeByHandle(nh); if (!node) return API_ENOENT; if (keepVersions && node->type != FILENODE) return API_EARGS; bool canChangeVault = false; const bool isPNFolder = node->isPasswordManagerNodeFolder(); if (isPNFolder || node->isPasswordManagerNode()) { if (isPNFolder && node->nodeHandle() == getPasswordManagerBase()) { LOG_err << "Password Manager: Password Manager Base cannot be deleted"; return API_EARGS; } keepVersions = false; canChangeVault = true; } else if (node->type == ROOTNODE || node->type == VAULTNODE || node->type == RUBBISHNODE) // rootnodes cannot be deleted { return API_EACCESS; } // use default callback function app->unlink_result return unlink(node.get(), keepVersions, rTag, canChangeVault); } void MegaClient::removeOutSharesFromSubtree(std::shared_ptr n, int tag) { if (n->pendingshares) { for (auto& it : *n->pendingshares) { if (it.second->pcr) { setshare(n, it.second->pcr->targetemail.c_str(), ACCESS_UNKNOWN, false, nullptr, tag, [](Error, bool){}); } } } if (n->outshares) { for (auto& it : *n->outshares) { if (it.second->user) { setshare(n, it.second->user->email.c_str(), ACCESS_UNKNOWN, false, nullptr, tag, [](Error, bool){}); } else // folder links are a shared folder without user { setshare(n, nullptr, ACCESS_UNKNOWN, false, nullptr, tag, [](Error, bool) {}); } } } // Remove public link for FILENODE as well if (n->type == FILENODE && n->plink) { exportnode(n, 1, 0, false, false, tag, [](Error e, handle, handle, string&&) { if (e != API_OK) { LOG_err << "Failed to remove public link upon moving file to Rubbish bin"; } }); } for (auto& c : getChildren(n.get())) { removeOutSharesFromSubtree(c, tag); } } void MegaClient::unlinkOrMoveBackupNodes(NodeHandle backupRootNode, NodeHandle destination, std::function completion) { std::shared_ptr n = nodeByHandle(backupRootNode); if (!n) { if (destination.isUndef()) { // we were going to delete these anyway, so no problem completion(API_OK); } else { // we can't move them if they don't exist completion(API_EARGS); } return; } if (n->firstancestor()->nodeHandle() != mNodeManager.getRootNodeVault()) { // backup nodes are supposed to be in the vault - if not, something is wrong completion(API_EARGS); return; } if (destination.isUndef()) { error e = unlink(n.get(), false, 0, true, [completion](NodeHandle, Error e) { completion(e); }); if (e) { // error before we sent a request so call completion directly completion(e); } } else { // moving to target node std::shared_ptr p = nodeByHandle(destination); if (!p || p->firstancestor()->nodeHandle() != mNodeManager.getRootNodeFiles()) { completion(API_EARGS); return; } if (p->hasChildWithName(n->displayname())) { LOG_err << "Name clash. A node with the same name already exists in the destination"; completion(API_EEXIST); return; } error e = rename(n, p, SYNCDEL_NONE, NodeHandle(), nullptr, true, [completion](NodeHandle, Error e){ completion(e); }); if (e) { // error before we sent a request so call completion directly completion(e); } } } // delete node tree error MegaClient::unlink(Node* n, bool keepversions, int tag, bool canChangeVault, std::function&& resultFunction) { if (mBizStatus == BIZ_STATUS_EXPIRED) { return API_EBUSINESSPASTDUE; } if (!n->inshare && !checkaccess(n, FULL)) { return API_EACCESS; } if (mBizStatus > BIZ_STATUS_INACTIVE && mBizMode == BIZ_MODE_SUBUSER && n->inshare && mBizMasters.find(n->inshare->user->userhandle) != mBizMasters.end()) { // business subusers cannot leave inshares from master biz users return API_EMASTERONLY; } if (ststatus == STORAGE_PAYWALL) { return API_EPAYWALL; } // S4 container cannot be deleted if S4 is enabled if (mIsS4Enabled && n->nodeHandle().eq(mS4Container)) { return API_EACCESS; } bool kv = (keepversions && n->type == FILENODE); queueCommand(new CommandDelNode(this, n->nodeHandle(), kv, tag, std::move(resultFunction), canChangeVault)); return API_OK; } void MegaClient::unlinkversions() { queueCommand(new CommandDelVersions(this)); } // Converts a string in UTF8 to array of int32 in the same way than Webclient converts a string in UTF16 to array of 32-bit elements // (returns NULL if the input is invalid UTF-8) // unfortunately, discards bits 8-31 of multibyte characters for backwards compatibility char* MegaClient::utf8_to_a32forjs(const char* str, int* len) { if (!str) { return NULL; } int t = int(strlen(str)); int t2 = 4 * ((t + 3) >> 2); char* result = new char[static_cast(t2)](); uint32_t* a32 = (uint32_t*)result; uint32_t unicode; int i = 0; int j = 0; while (i < t) { char c = static_cast(str[i++] & 0xff); if (!(c & 0x80)) { unicode = c & 0xff; } else if ((c & 0xe0) == 0xc0) { if (i >= t || (str[i] & 0xc0) != 0x80) { delete[] result; return NULL; } unicode = static_cast((c & 0x1f) << 6); unicode |= str[i++] & 0x3f; } else if ((c & 0xf0) == 0xe0) { if (i + 2 > t || (str[i] & 0xc0) != 0x80 || (str[i + 1] & 0xc0) != 0x80) { delete[] result; return NULL; } unicode = static_cast((c & 0x0f) << 12); unicode |= static_cast((str[i++] & 0x3f) << 6); unicode |= str[i++] & 0x3f; } else if ((c & 0xf8) == 0xf0) { if (i + 3 > t || (str[i] & 0xc0) != 0x80 || (str[i + 1] & 0xc0) != 0x80 || (str[i + 2] & 0xc0) != 0x80) { delete[] result; return NULL; } unicode = static_cast((c & 0x07) << 18); unicode |= static_cast((str[i++] & 0x3f) << 12); unicode |= static_cast((str[i++] & 0x3f) << 6); unicode |= str[i++] & 0x3f; // management of surrogate pairs like the JavaScript code uint32_t hi = 0xd800 | ((unicode >> 10) & 0x3F) | (((unicode >> 16) - 1) << 6); uint32_t low = 0xdc00 | (unicode & 0x3ff); a32[j >> 2] |= htonl(hi << (24 - (j & 3) * 8)); j++; unicode = low; } else { delete[] result; return NULL; } a32[j >> 2] |= htonl(unicode << (24 - (j & 3) * 8)); j++; } *len = j; return result; } // compute UTF-8 password hash error MegaClient::pw_key(const char* utf8pw, byte* keyBuffer) const { int t; char* pw; pw = utf8_to_a32forjs(utf8pw, &t); if (!pw) { return API_EARGS; } int n = (t + 15) / 16; SymmCipher* keys = new SymmCipher[static_cast(n)]; for (int i = 0; i < n; i++) { int valid = (i != (n - 1)) ? SymmCipher::BLOCKSIZE : (t - SymmCipher::BLOCKSIZE * i); memcpy(keyBuffer, pw + i * SymmCipher::BLOCKSIZE, static_cast(valid)); memset(keyBuffer + valid, 0, static_cast(SymmCipher::BLOCKSIZE - valid)); keys[i].setkey(keyBuffer); } memcpy(keyBuffer, "\x93\xC4\x67\xE3\x7D\xB0\xC7\xA4\xD1\xBE\x3F\x81\x01\x52\xCB\x56", SymmCipher::BLOCKSIZE); for (int r = 65536; r--; ) { for (int i = 0; i < n; i++) { keys[i].ecb_encrypt(keyBuffer); } } delete[] keys; delete[] pw; return API_OK; } SymmCipher* MegaClient::getRecycledTemporaryTransferCipher(const byte* newKey, int type) { tmptransfercipher.setkey(newKey, type); return &tmptransfercipher; } SymmCipher* MegaClient::getRecycledTemporaryNodeCipher(const string* newKey) { return tmpnodecipher.setkey(newKey) ? &tmpnodecipher : nullptr; } SymmCipher* MegaClient::getRecycledTemporaryNodeCipher(const byte* newKey) { tmpnodecipher.setkey(newKey); return &tmpnodecipher; } // compute generic string hash void MegaClient::stringhash(const char* s, byte* hash, SymmCipher* cipher) { int t = static_cast(strlen(s) & ~(static_cast(SymmCipher::BLOCKSIZE) - 1)); strncpy((char*)hash, s + t, SymmCipher::BLOCKSIZE); while (t) { t -= SymmCipher::BLOCKSIZE; SymmCipher::xorblock((byte*)s + t, hash); } for (t = 16384; t--; ) { cipher->ecb_encrypt(hash); } memcpy(hash + 4, hash + 8, 4); } // (transforms s to lowercase) uint64_t MegaClient::stringhash64(string* s, SymmCipher* c) { byte hash[SymmCipher::KEYLENGTH+1]; tolower_string(*s); stringhash(s->c_str(), hash, c); return MemAccess::get((const char*)hash); } // read and add/verify node array g int MegaClient::readnodes(JSON* j, int notify, putsource_t source, vector* nn, bool modifiedByThisClient, bool applykeys) { if (!j->enterarray()) { return 0; } handle previousHandleForAlert = UNDEF; #ifdef ENABLE_SYNC set allParents; #endif NodeManager::MissingParentNodes missingParentNodes; while (int e = readnode(j, notify, source, nn, modifiedByThisClient, applykeys, missingParentNodes, previousHandleForAlert, #ifdef ENABLE_SYNC &allParents)) #else nullptr)) #endif { if (e != 1) { LOG_err << "Parsing error in readnodes: " << e; return 0; } } mergenewshares(notify != 0); mNodeManager.checkOrphanNodes(missingParentNodes); #ifdef ENABLE_SYNC for (NodeHandle p : allParents) { syncs.triggerSync(p); } #endif return j->leavearray(); } int MegaClient::readnode(JSON* j, int notify, putsource_t /*source*/, vector* nn, bool modifiedByThisClient, bool applykeys, mega::NodeManager::MissingParentNodes& missingParentNodes, handle& previousHandleForAlert, set* allParents) { std::shared_ptr n; if (j->enterobject()) { handle h = UNDEF, ph = UNDEF; handle u = 0, su = UNDEF; nodetype_t t = TYPE_UNKNOWN; const char* a = NULL; const char* nodeKey = nullptr; const char* fa = NULL; const char *sk = NULL; accesslevel_t rl = ACCESS_UNKNOWN; m_off_t s = NEVER; m_time_t ts = -1, sts = -1; nameid name; int nni = -1; while ((name = j->getnameid()) != EOO) { switch (name) { case makeNameid("h"): // new node: handle h = j->gethandle(); break; case makeNameid("p"): // parent node ph = j->gethandle(); break; case name_id::u: // owner user u = j->gethandle(USERHANDLE); break; case makeNameid("t"): // type t = (nodetype_t)j->getint(); break; case makeNameid("a"): // attributes a = j->getvalue(); break; case makeNameid("k"): // key(s) nodeKey = j->getvalue(); break; case makeNameid("s"): // file size s = j->getint(); break; case makeNameid("i"): // related source NewNode index nni = int(j->getint()); break; case makeNameid("ts"): // actual creation timestamp ts = j->getint(); break; case makeNameid("fa"): // file attributes fa = j->getvalue(); break; // inbound share attributes case makeNameid("r"): // share access level rl = (accesslevel_t)j->getint(); break; case makeNameid("sk"): // share key sk = j->getvalue(); break; case makeNameid("su"): // sharing user su = j->gethandle(USERHANDLE); break; case makeNameid("sts"): // share timestamp sts = j->getint(); break; default: if (!j->storeobject()) { return 2; } } } if (ISUNDEF(h)) { warn("Missing node handle"); } else { if (t == TYPE_UNKNOWN) { warn("Unknown node type"); } else if (t == FILENODE || t == FOLDERNODE) { if (ISUNDEF(ph)) { warn("Missing parent"); } else if (!a) { warn("Missing node attributes"); } else if (!nodeKey) { warn("Missing node key"); } if (t == FILENODE && ISUNDEF(s)) { warn("File node without file size"); } } } if (fa && t != FILENODE) { warn("Spurious file attributes"); } if (ph != UNDEF && allParents) { allParents->insert(NodeHandle().set6byte(ph)); } if (!warnlevel()) { // 'notify' is false only while processing fetchnodes command // In that case, we can skip the lookup, since nodes are all new ones, // (they will not be found in DB) if (notify && (n = nodebyhandle(h))) { std::shared_ptr p; if (!ISUNDEF(ph)) { p = nodebyhandle(ph); } if (n->changed.removed) { // node marked for deletion is being resurrected, possibly // with a new parent (server-client move operation) n->changed.removed = false; } if (!ISUNDEF(ph)) { if (p) { if (n->setparent(p)) { n->changed.parent = true; } } else { n->setparent(NULL); n->parenthandle = ph; missingParentNodes[n->parentHandle()].insert(n); } } if (a && nodeKey && n->attrstring) { LOG_warn << "Updating the key of a NO_KEY node"; JSON::copystring(n->attrstring.get(), a); n->setkeyfromjson(nodeKey); } } else { vector buf(SymmCipher::KEYLENGTH); if (!ISUNDEF(su)) { if (t != FOLDERNODE) { warn("Invalid share node type"); } if (rl == ACCESS_UNKNOWN) { warn("Missing access level"); } if (warnlevel()) { su = UNDEF; } else { if (!mKeyManager.generation()) { if (sk) { decryptkey(sk, buf.data(), static_cast(buf.size()), &key, 1, h); } } else { sk = nullptr; } } } string fas; JSON::copystring(&fas, fa); // fallback timestamps if (!(ts + 1)) { ts = m_time(); } if (!(sts + 1)) { sts = ts; } n = std::make_shared(*this, NodeHandle().set6byte(h), NodeHandle().set6byte(ph), t, s, u, fas.c_str(), ts); n->changed.newnode = true; n->changed.modifiedByThisClient = modifiedByThisClient; n->attrstring.reset(new string); JSON::copystring(n->attrstring.get(), a); n->setkeyfromjson(nodeKey); if (loggedIntoFolder()) { // folder link access: first returned record defines root node and identity // (this code used to be in Node::Node but is not suitable for session resume) if (mNodeManager.getRootNodeFiles().isUndef()) { mNodeManager.setRootNodeFiles(NodeHandle().set6byte(h)); mCachedStatus.addOrUpdate( CacheableStatus::STATUS_ROOT_FOLDER_LINK_HANDLE, static_cast(mNodeManager.getRootNodeFiles().as8byte())); if (loggedIntoWritableFolder()) { // If logged into writable folder, we need the sharekey set in the root node // so as to include it in subsequent put nodes n->sharekey.reset(new SymmCipher(key)); //we use the "master key", in this case the secret share key } } } // NodeManager takes n ownership mNodeManager.addNode(n, notify != 0, fetchingnodes, missingParentNodes); if (!ISUNDEF(su)) // node represents an incoming share { newshares.push_back(new NewShare(h, 0, su, rl, sts, sk ? buf.data() : NULL)); if (sk) // only if the key is valid, add it to the repository { mNewKeyRepository[NodeHandle().set6byte(h)] = std::move(buf); } } if (u != me && !ISUNDEF(u) && !fetchingnodes && !loggedIntoFolder()) { useralerts.noteSharedNode(u, t, ts, n.get(), name_id::put); } if (nn && nni >= 0 && nni < int(nn->size())) { auto& nn_nni = (*nn)[static_cast(nni)]; nn_nni.added = true; nn_nni.mAddedHandle = h; } } if (applykeys) { n->applykey(); } if (!n->keyApplied()) { mNodeManager.addNodePendingApplykey(n); } if (notify) { // node is save in DB at notifypurge mNodeManager.notifyNode(n); } else // Only need to save in DB if node is not notified { mNodeManager.saveNodeInDb(n.get()); } n = nullptr; // ownership is taken by NodeManager upon addNode() // update-alerts for shared-nodes management if (!ISUNDEF(ph) && !loggedIntoFolder()) { if (useralerts.isHandleInAlertsAsRemoved(h) && ISUNDEF(previousHandleForAlert)) { useralerts.setNewNodeAlertToUpdateNodeAlert(nodebyhandle(ph).get()); useralerts.removeNodeAlerts(nodebyhandle(h).get()); previousHandleForAlert = h; } else if ((t == FILENODE) || (t == FOLDERNODE)) { if (previousHandleForAlert == ph) { useralerts.removeNodeAlerts(nodebyhandle(h).get()); previousHandleForAlert = h; } // otherwise, the added TYPE_NEWSHAREDNODE is kept } } } return 1; } return 0; } // decrypt and set encrypted sharekey void MegaClient::setkey(SymmCipher* c, const char* newKeyB64) { byte newkey[SymmCipher::KEYLENGTH]; if (Base64::atob(newKeyB64, newkey, sizeof newkey) == sizeof newkey) { key.ecb_decrypt(newkey); c->setkey(newkey); } } // read outbound share keys void MegaClient::readok(JSON* j) { if (j->enterarray()) { while (j->enterobject()) { readokelement(j); } j->leavearray(); } } // - h/ha/k (outbound sharekeys, always symmetric) void MegaClient::readokelement(JSON* j) { handle h = UNDEF; byte ha[SymmCipher::BLOCKSIZE]; byte auth[SymmCipher::BLOCKSIZE]; int have_ha = 0; const char* shareKey = nullptr; for (;;) { switch (j->getnameid()) { case makeNameid("h"): h = j->gethandle(); break; case makeNameid("ha"): // share authentication tag have_ha = Base64::atob(j->getvalue(), ha, sizeof ha) == sizeof ha; break; case makeNameid("k"): // share key(s) shareKey = j->getvalue(); break; case EOO: if (ISUNDEF(h)) { LOG_warn << "Missing outgoing share handle in ok element"; return; } if (!mKeyManager.generation()) // client not migrated yet { if (!shareKey) { LOG_warn << "Missing outgoing share key in ok element"; return; } if (!have_ha) { LOG_warn << "Missing outbound share signature"; return; } vector buf(SymmCipher::BLOCKSIZE); if (decryptkey(shareKey, buf.data(), static_cast(buf.size()), &key, 1, h)) { newshares.push_back(new NewShare(h, 1, UNDEF, ACCESS_UNKNOWN, 0, buf.data(), ha)); if (mNewKeyRepository.find(NodeHandle().set6byte(h)) == mNewKeyRepository.end()) { handleauth(h, auth); if (!memcmp(auth, ha, buf.size())) { mNewKeyRepository[NodeHandle().set6byte(h)] = std::move(buf); } } } } else { LOG_debug << "Ignoring outgoing share keys from `ok0` (secured client with ^!keys already)"; } return; default: if (!j->storeobject()) { return; } } } } // read outbound shares and pending shares void MegaClient::readoutshares(JSON* j) { if (j->enterarray()) { while (j->enterobject()) { readoutshareelement(j); } j->leavearray(); mergenewshares(0); } } // - h/u/r/ts/p (outbound share or pending share) void MegaClient::readoutshareelement(JSON* j) { handle h = UNDEF; handle uh = UNDEF; handle p = UNDEF; accesslevel_t r = ACCESS_UNKNOWN; m_time_t ts = 0; for (;;) { switch (j->getnameid()) { case makeNameid("h"): h = j->gethandle(); break; case makeNameid("p"): p = j->gethandle(PCRHANDLE); break; case name_id::u: // share target user uh = j->is(EXPORTEDLINK) ? 0 : j->gethandle(USERHANDLE); break; case makeNameid("r"): // access r = (accesslevel_t)j->getint(); break; case makeNameid("ts"): // timestamp ts = j->getint(); break; case EOO: if (ISUNDEF(h)) { LOG_warn << "Missing outgoing share node"; return; } if (ISUNDEF(uh) && ISUNDEF(p)) { LOG_warn << "Missing outgoing share user"; return; } if (r == ACCESS_UNKNOWN) { LOG_warn << "Missing outgoing share access"; return; } newshares.push_back(new NewShare(h, 1, uh, r, ts, NULL, NULL, p)); return; default: if (!j->storeobject()) { return; } } } } void MegaClient::readipc(JSON *j) { // fields: ps, m, ts, uts, msg, p if (j->enterarray()) { while (j->enterobject()) { m_time_t ts = 0; m_time_t uts = 0; const char *m = NULL; const char *msg = NULL; handle p = UNDEF; bool done = false; while (!done) { switch (j->getnameid()) { case makeNameid("m"): m = j->getvalue(); break; case makeNameid("ts"): ts = j->getint(); break; case makeNameid("uts"): uts = j->getint(); break; case makeNameid("msg"): msg = j->getvalue(); break; case makeNameid("p"): p = j->gethandle(MegaClient::PCRHANDLE); break; case EOO: done = true; if (ISUNDEF(p)) { LOG_err << "p element not provided"; break; } if (!m) { LOG_err << "m element not provided"; break; } if (ts == 0) { LOG_err << "ts element not provided"; break; } if (uts == 0) { LOG_err << "uts element not provided"; break; } if (pcrindex[p] != NULL) { pcrindex[p]->update(m, NULL, ts, uts, msg, false); } else { pcrindex[p].reset(new PendingContactRequest(p, m, NULL, ts, uts, msg, false)); } break; default: if (!j->storeobject()) { j->leavearray(); return; } } } } j->leavearray(); } } void MegaClient::readopc(JSON *j) { // fields: e, m, ts, uts, rts, msg, p if (j->enterarray()) { while (j->enterobject()) { m_time_t ts = 0; m_time_t uts = 0; const char *e = NULL; const char *m = NULL; const char *msg = NULL; handle p = UNDEF; bool done = false; while (!done) { switch (j->getnameid()) { case makeNameid("e"): e = j->getvalue(); break; case makeNameid("m"): m = j->getvalue(); break; case makeNameid("ts"): ts = j->getint(); break; case makeNameid("uts"): uts = j->getint(); break; case makeNameid("msg"): msg = j->getvalue(); break; case makeNameid("p"): p = j->gethandle(MegaClient::PCRHANDLE); break; case EOO: done = true; if (!e) { LOG_err << "e element not provided"; break; } if (!m) { LOG_err << "m element not provided"; break; } if (ts == 0) { LOG_err << "ts element not provided"; break; } if (uts == 0) { LOG_err << "uts element not provided"; break; } if (pcrindex[p] != NULL) { pcrindex[p]->update(e, m, ts, uts, msg, true); } else { pcrindex[p].reset(new PendingContactRequest(p, e, m, ts, uts, msg, true)); } break; default: if (!j->storeobject()) { j->leavearray(); return; } } } } j->leavearray(); } } error MegaClient::readmiscflags(JSON *json) { // Clear flags to ensure they are regenerated when changing account types. mABTestFlags.clear(); mFeatureFlags.clear(); for (bool journeyIdFound = false;;) { string fieldName = json->getnameWithoutAdvance(); switch (json->getnameid()) { // mcs:1 --> MegaChat enabled case makeNameid("ach"): achievements_enabled = bool(json->getint()); // Mega Achievements enabled break; case makeNameid("mfae"): // multi-factor authentication enabled gmfa_enabled = bool(json->getint()); break; case makeNameid("ssrs"): // server-side rubish-bin scheduler (only available when logged in) ssrs_enabled = bool(json->getint()); break; case makeNameid("aplvp"): // apple VOIP push enabled (only available when logged in) aplvp_enabled = bool(json->getint()); break; case makeNameid("smsve"): // 2 = Opt-in and unblock SMS allowed 1 = Only unblock SMS allowed 0 = No SMS allowed mSmsVerificationState = static_cast(json->getint()); break; case makeNameid("nlfe"): // new link format enabled mNewLinkFormat = static_cast(json->getint()); break; case makeNameid("cspe"): // cookie banner enabled mCookieBannerEnabled = bool(json->getint()); break; // case makeNameid("pf"): // is this account able to subscribe a pro flexi plan? // json->getint(); // break; case makeNameid("jid"): // JourneyID value (16-char hex value) { string jid; if (!json->storeobject(&jid)) { LOG_err << "Invalid JourneyID (jid)"; assert(false); } if (jid.size() != JourneyID::HEX_STRING_SIZE) { if (!jid.empty()) // If empty, it will be equivalent to no journeyId found (journeyIdFound = false) { LOG_err << "Invalid JourneyID size (" << jid.size() << ") expected: " << JourneyID::HEX_STRING_SIZE; jid.clear(); assert(false); } } else { journeyIdFound = true; if (!trackJourneyId()) // If there is already a value and tracking flag is true, do nothing { LOG_verbose << "[MegaClient::readmiscflags] set jid: '" << jid << "'"; mJourneyId->setValue(jid); } } } break; case EOO: { if (!journeyIdFound && trackJourneyId()) // If there is no value or tracking flag is false, do nothing { LOG_verbose << "[MegaClient::readmiscflags] No JourneyId found -> set tracking " "to false"; mJourneyId->setValue(""); } auto flagValue = mFeatureFlags.get("site"); const auto& targetURL = (flagValue && *flagValue == 1) ? MegaClient::MEGAURL_APP : MegaClient::MEGAURL_NZ; if (getMegaURL() != targetURL) { setMegaURL(targetURL); } return API_OK; } default: if (fieldName.rfind("ab_", 0) == 0) // Starting with "ab_" { string tag = fieldName.substr(3); // The string after "ab_" prefix int64_t value = json->getint(); if (value >= 0) { mABTestFlags.set(tag, static_cast(value)); } else { LOG_err << "[MegaClient::readmiscflags] Invalid value for A/B Test flag"; assert(value >= 0 && "A/B test value must be greater or equal to 0"); } } else if (fieldName.rfind("ff_", 0) == 0) // Starting with "ff_" { string tag = fieldName.substr(3); // The string after "ff_" prefix int64_t value = json->getint(); if (value >= 0) { mFeatureFlags.set(tag, static_cast(value)); } else { LOG_err << "[MegaClient::readmiscflags] Invalid value for Feature flag"; assert(value >= 0 && "Feature flag value must be greater or equal to 0"); } } else if (!json->storeobject()) { return API_EINTERNAL; } } } } bool MegaClient::procph(JSON *j) { if (!j->enterarray()) { return false; } while (int e = procphelement(j)) { if (e != 1) { LOG_err << "Parsing error in procph: " << e; j->leavearray(); return false; } } return j->leavearray(); } int MegaClient::procphelement(JSON *j) { // fields: h, ph, ets if (j->enterobject()) { handle h = UNDEF; handle ph = UNDEF; m_time_t ets = 0; m_time_t cts = 0; std::shared_ptr n; bool takendown = false; std::string authKey; bool done = false; while (!done) { switch (j->getnameid()) { case makeNameid("h"): h = j->gethandle(MegaClient::NODEHANDLE); break; case makeNameid("ph"): ph = j->gethandle(MegaClient::NODEHANDLE); break; case makeNameid("w"): j->storeobject(&authKey); break; case makeNameid("ets"): ets = j->getint(); break; case makeNameid("ts"): cts = j->getint(); break; case makeNameid("down"): takendown = (j->getint() == 1); break; case EOO: done = true; if (ISUNDEF(h)) { LOG_err << "h element not provided"; break; } if (ISUNDEF(ph)) { LOG_err << "ph element not provided"; break; } if (!cts) { LOG_err << "creation timestamp element not provided"; break; } n = nodebyhandle(h); if (n) { n->setpubliclink(ph, cts, ets, takendown, authKey); mNodeManager.updateNode(n.get()); } else { LOG_warn << "node for public link not found"; } break; default: if (!j->storeobject()) { return 2; } } } return 1; } return 0; } void MegaClient::applykeys() { CodeCounter::ScopeTimer ccst(performanceStats.applyKeys); mNodeManager.applyKeys(); if (!nodekeyrewrite.empty()) { LOG_err << "Skipped to send key rewrites (secured client)"; assert(nodekeyrewrite.empty()); nodekeyrewrite.clear(); } } // user/contact list bool MegaClient::readusers(JSON* j, bool actionpackets) { if (!j->enterarray()) { return false; } while (int e = readuser(j, actionpackets)) { if (e != 1) { LOG_err << "Parsing error in readusers: " << e; j->leavearray(); return false; } } return j->leavearray(); } int MegaClient::readuser(JSON* j, bool actionpackets) { if (j->enterobject()) { handle uh = 0; visibility_t v = VISIBILITY_UNKNOWN; // new share objects do not override existing visibility m_time_t ts = 0; const char* m = NULL; nameid name; BizMode bizMode = BIZ_MODE_UNKNOWN; string publicKey, puEd255, puCu255, sigPubk, sigCu255; bool exit = false; while (!exit) { string fieldName = j->getnameWithoutAdvance(); name = j->getnameid(); switch (name) { case name_id::u: // new user: handle uh = j->gethandle(USERHANDLE); break; case name_id::c: // visibility v = (visibility_t)j->getint(); break; case makeNameid("m"): // email m = j->getvalue(); break; case makeNameid("ts"): ts = j->getint(); break; case makeNameid("b"): { if (j->enterobject()) { nameid businessName; while ((businessName = j->getnameid()) != EOO) { switch (businessName) { case makeNameid("m"): bizMode = static_cast(j->getint()); break; default: if (!j->storeobject()) return 2; break; } } j->leaveobject(); } break; } case makeNameid("pubk"): j->storebinary(&publicKey); break; case makeNameid("+puEd255"): j->storebinary(&puEd255); break; case makeNameid("+puCu255"): j->storebinary(&puCu255); break; case makeNameid("+sigPubk"): j->storebinary(&sigPubk); break; case EOO: exit = true; break; default: switch (User::string2attr(fieldName.c_str())) { case ATTR_SIG_CU255_PUBK: j->storebinary(&sigCu255); break; default: if (!j->storeobject()) { return 3; } break; } break; } } if (ISUNDEF(uh)) { warn("Missing contact user handle"); } if (!m) { warn("Unknown contact user e-mail address"); } if (!warnlevel()) { if (actionpackets && v >= 0 && v <= 3 && statecurrent && !loggedIntoFolder()) { string email; JSON::copystring(&email, m); useralerts.add(new UserAlert::ContactChange(v, uh, email, ts, useralerts.nextId())); } User* u = finduser(uh, 0); bool notify = !u; if (u || (u = finduser(uh, 1)) != nullptr) { const string oldEmail = u->email; mapuser(uh, m); u->mBizMode = bizMode; // The attributes received during the "ug" also include the version. // Keep them instead of the ones with no version from the fetch nodes. if (!(uh == me && fetchingnodes)) { if (!publicKey.empty()) { u->pubk.setkey(AsymmCipher::PUBKEY, (const byte*)publicKey.data(), (int)publicKey.size()); } if (puEd255.size()) { u->setAttribute(ATTR_ED25519_PUBK, puEd255, {}); } if (puCu255.size()) { u->setAttribute(ATTR_CU25519_PUBK, puCu255, {}); } if (sigPubk.size()) { u->setAttribute(ATTR_SIG_RSA_PUBK, sigPubk, {}); } if (sigCu255.size()) { u->setAttribute(ATTR_SIG_CU255_PUBK, sigCu255, {}); } } if (v != VISIBILITY_UNKNOWN) { if (u->show != v || u->ctime != ts) { if (u->show == HIDDEN && v == VISIBLE) { u->setAttributeExpired(ATTR_FIRSTNAME); u->setAttributeExpired(ATTR_LASTNAME); if (oldEmail != u->email) { u->changed.email = true; } } else if (u->show == VISIBILITY_UNKNOWN && v == VISIBLE && uh != me && statecurrent) // otherwise, fetched when statecurrent is set { // new user --> fetch contact keys if they are not yet available. // If keys are available for the user, fetchContactKeys will call trackKey directly. fetchContactKeys(u); } u->set(v, ts); notify = true; } } if (notify) { notifyuser(u); } } } return 1; } return 0; } // Supported formats: // - file links: #![!] // [!] // /file/[][#] // // - folder links: #F![!] // /folder/[][#] // - set links: /collection/[][#] error MegaClient::parsepubliclink(const char* link, handle& ph, byte* extractedKey, TypeOfLink type) { bool isFolder; const char* ptr = nullptr; if ((ptr = strstr(link, "#F!")) != nullptr) { ptr += 3; isFolder = true; } else if ((ptr = strstr(link, "folder/")) != nullptr) { ptr += 7; isFolder = true; } else if ((ptr = strstr(link, "#!")) != nullptr) { ptr += 2; isFolder = false; } else if ((ptr = strstr(link, "file/")) != nullptr) { ptr += 5; isFolder = false; } else if ((ptr = strstr(link, "collection/")) != nullptr) { ptr += 11; // std::strlen("collection/"); isFolder = false; } else // legacy file link format without '#' { ptr = link; isFolder = false; } if (isFolder != (type == TypeOfLink::FOLDER)) { return API_EARGS; // type of link mismatch } if (strlen(ptr) < 8) // no public handle in the link { return API_EARGS; } ph = 0; //otherwise atob will give an unexpected result if (Base64::atob(ptr, (byte*)&ph, NODEHANDLE) == NODEHANDLE) { ptr += 8; // skip any tracking parameter introduced by third-party websites while(*ptr && *ptr != '!' && *ptr != '#') { ptr++; } if (!*ptr || ((*ptr == '#' || *ptr == '!') && *(ptr + 1) == '\0')) // no key provided { return API_EINCOMPLETE; } if (*ptr == '!' || *ptr == '#') { const char* keyBuffer = ptr + 1; // skip '!' or '#' separator static const map nodetypeKeylength = {{TypeOfLink::FOLDER, FOLDERNODEKEYLENGTH} ,{TypeOfLink::FILE, FILENODEKEYLENGTH} ,{TypeOfLink::SET, SETNODEKEYLENGTH} }; int keylen = nodetypeKeylength.at(type); if (Base64::atob(keyBuffer, extractedKey, keylen) == keylen) { return API_OK; } } } return API_EARGS; } void MegaClient::openStatusTable(bool loadFromCache) { if (statusTable) { statusTable.reset(); mCachedStatus.clear(); } doOpenStatusTable(); if (loadFromCache && statusTable) { fetchStatusTable(statusTable.get()); } } void MegaClient::checkForResumeableSCDatabase() { // see if we can resume from an already cached set of nodes for this folder opensctable(); string t; if (sctable && sctable->get(CACHEDSCSN, &t) && t.size() == sizeof cachedscsn) { cachedscsn = MemAccess::get(t.data()); } } error MegaClient::folderaccess(const char* folderlink, const char* authKey, bool tryToResumeFolderLinkFromCache) { handle h = UNDEF; byte folderkey[FOLDERNODEKEYLENGTH]; error e = parsepubliclink(folderlink, h, folderkey, TypeOfLink::FOLDER); if (e != API_OK) { return e; } if (authKey) { auto ptr = authKey; while (*ptr) { if (!URLCodec::issafe(*ptr)) { LOG_warn << "Authkey is not valid"; return API_EACCESS; } ptr++; } mFolderLink.mWriteAuth = authKey; } mFolderLink.mPublicHandle = h; // mFolderLink.mAccountAuth remain unchanged, since it can be reused for multiple links key.setkey(folderkey); if (tryToResumeFolderLinkFromCache) { openStatusTable(true); int64_t cachedRootNode = mCachedStatus.lookup(CacheableStatus::STATUS_ROOT_FOLDER_LINK_HANDLE, static_cast(999)); if (cachedRootNode != 999) { mNodeManager.setRootNodeFiles( NodeHandle().set6byte(static_cast(cachedRootNode))); if (!mNodeManager.getRootNodeFiles().isUndef()) { checkForResumeableSCDatabase(); reportLoggedInChanges(); return API_OK; } } } openStatusTable(false); return API_OK; } void MegaClient::prelogin(const char *email, CommandPrelogin::Completion completion) { // Convenience. using std::bind; using namespace std::placeholders; // Invoke app callback if no completion has been specified. if (!completion) completion = bind(&MegaApp::prelogin_result, app, _1, _2, _3, _4); queueCommand(new CommandPrelogin(this, std::move(completion), email)); } // create new session void MegaClient::login(const char* email, const byte* pwkey, const char* pin, CommandLogin::Completion completion) { if (!completion) completion = std::bind(&MegaApp::login_result, app, std::placeholders::_1); string lcemail(email); key.setkey((byte*)pwkey); uint64_t emailhash = stringhash64(&lcemail, &key); byte sek[SymmCipher::KEYLENGTH]; rng.genblock(sek, sizeof sek); queueCommand(new CommandLogin(this, std::move(completion), email, (byte*)&emailhash, sizeof(emailhash), sek, 0, pin)); } // create new session (v2) void MegaClient::login2(const char *email, const char *password, const string *salt, const char *pin, CommandLogin::Completion completion) { if (!completion) completion = std::bind(&MegaApp::login_result, app, std::placeholders::_1); string bsalt; Base64::atob(*salt, bsalt); vector derivedKey = deriveKey(password, bsalt, 2 * SymmCipher::KEYLENGTH); login2(email, derivedKey.data(), pin, std::move(completion)); } void MegaClient::login2(const char *email, const byte *derivedKey, const char* pin, CommandLogin::Completion completion) { if (!completion) completion = std::bind(&MegaApp::login_result, app, std::placeholders::_1); key.setkey((byte*)derivedKey); const byte *authKey = derivedKey + SymmCipher::KEYLENGTH; byte sek[SymmCipher::KEYLENGTH]; rng.genblock(sek, sizeof sek); queueCommand(new CommandLogin(this, std::move(completion), email, authKey, SymmCipher::KEYLENGTH, sek, 0, pin)); } void MegaClient::fastlogin(const char* email, const byte* pwkey, uint64_t emailhash, CommandLogin::Completion completion) { if (!completion) completion = std::bind(&MegaApp::login_result, app, std::placeholders::_1); key.setkey((byte*)pwkey); byte sek[SymmCipher::KEYLENGTH]; rng.genblock(sek, sizeof sek); queueCommand(new CommandLogin(this, std::move(completion), email, (byte*)&emailhash, sizeof(emailhash), sek)); } void MegaClient::getuserdata( int tag, std::function&&, error)> completion) { cachedug = false; queueCommand(new CommandGetUserData(this, tag, std::move(completion))); } void MegaClient::getmiscflags() { queueCommand(new CommandGetMiscFlags(this)); } void MegaClient::getpubkey(const char *user) { queuepubkeyreq(user, std::make_unique(reqtag)); } // resume session - load state from local cache, if available void MegaClient::login(string session, CommandLogin::Completion completion) { if (!completion) completion = std::bind(&MegaApp::login_result, app, std::placeholders::_1); int sessionversion = 0; if (session.size() == sizeof key.key + SIDLEN + 1) { sessionversion = session[0]; if (sessionversion != 1) { restag = reqtag; completion(API_EARGS); return; } session.erase(0, 1); } if (session.size() == sizeof key.key + SIDLEN) { key.setkey((const byte*)session.data()); sid.assign((const char*)session.data() + sizeof key.key, SIDLEN); checkForResumeableSCDatabase(); byte sek[SymmCipher::KEYLENGTH]; rng.genblock(sek, sizeof sek); queueCommand( new CommandLogin(this, std::move(completion), NULL, NULL, 0, sek, sessionversion)); fetchtimezone(); } else if (!session.empty() && session[0] == 2) { // folder link - read only or writable CacheableReader cr(session); byte sessionVersion; handle publicHandle, rootnode; byte accountKey[FOLDERNODEKEYLENGTH]; string writeAuth, accountAuth, padding; byte expansions[8]; if (!cr.unserializebyte(sessionVersion) || !cr.unserializenodehandle(publicHandle) || !cr.unserializenodehandle(rootnode) || !cr.unserializebinary(accountKey, sizeof(accountKey)) || !cr.unserializeexpansionflags(expansions, 3) || (expansions[0] && !cr.unserializestring(writeAuth)) || (expansions[1] && !cr.unserializestring(accountAuth)) || (expansions[2] && !cr.unserializestring(padding)) || cr.hasdataleft()) { restag = reqtag; completion(API_EARGS); } else { mNodeManager.setRootNodeFiles(NodeHandle().set6byte(rootnode)); restag = reqtag; if (mNodeManager.getRootNodeFiles().isUndef()) { completion(API_EARGS); } else { mFolderLink.mPublicHandle = publicHandle; mFolderLink.mWriteAuth = writeAuth; mFolderLink.mAccountAuth = accountAuth; key.setkey(accountKey, FOLDERNODE); checkForResumeableSCDatabase(); openStatusTable(true); completion(API_OK); reportLoggedInChanges(); } } } else { restag = reqtag; completion(API_EARGS); } } // check password's integrity error MegaClient::validatepwd(const char* pswd) { User *u = finduser(me); if (!u) { return API_EACCESS; } if (accountversion == 1) { byte pwkey[SymmCipher::KEYLENGTH]; pw_key(pswd, pwkey); SymmCipher pwcipher(pwkey); pwcipher.setkey((byte*)pwkey); string lcemail(u->email); uint64_t emailhash = stringhash64(&lcemail, &pwcipher); vector eh((byte*)&emailhash, (byte*)&emailhash + sizeof(emailhash) / sizeof(byte)); queueCommand(new CommandValidatePassword(this, lcemail.c_str(), eh)); return API_OK; } else if (accountversion == 2) { vector dk = deriveKey(pswd, accountsalt, 2 * SymmCipher::KEYLENGTH); dk = vector(dk.data() + SymmCipher::KEYLENGTH, dk.data() + 2 * SymmCipher::KEYLENGTH); queueCommand(new CommandValidatePassword(this, u->email.c_str(), dk)); return API_OK; } else { return API_ENOENT; } } bool MegaClient::validatepwdlocally(const char* pswd) { if (!pswd || !pswd[0] || k.size() != SymmCipher::KEYLENGTH) { return false; } string tmpk = k; if (accountversion == 1) { byte pwkey[SymmCipher::KEYLENGTH]; if (pw_key(pswd, pwkey)) { return false; } SymmCipher cipher(pwkey); cipher.ecb_decrypt((byte*)tmpk.data()); } else if (accountversion == 2) { if (accountsalt.size() != 32) // SHA256 { return false; } byte derivedKey[2 * SymmCipher::KEYLENGTH]; CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte*)pswd, strlen(pswd), (const byte*)accountsalt.data(), accountsalt.size(), 100000); SymmCipher cipher(derivedKey); cipher.ecb_decrypt((byte*)tmpk.data()); } else { LOG_warn << "Version of account not supported"; return false; } return !memcmp(tmpk.data(), key.key, SymmCipher::KEYLENGTH); } int MegaClient::dumpsession(string& session) { session.clear(); if (!loggedIntoFolder()) { if (loggedin() == NOTLOGGEDIN) { return 0; } if (sessionkey.size()) { session.resize(sizeof key.key + 1); session[0] = 1; byte sessionData[SymmCipher::KEYLENGTH]; SymmCipher cipher; cipher.setkey((const byte *)sessionkey.data(), int(sessionkey.size())); cipher.ecb_encrypt(key.key, sessionData); memcpy(const_cast(session.data()) + 1, sessionData, sizeof(sessionData)); } else { session.resize(sizeof key.key); memcpy(const_cast(session.data()), key.key, sizeof key.key); } session.append(sid.data(), sid.size()); } else { // Folder link sessions are identifed by type 2. // Read-only and writeable links are supported // As is the accountAuth if used CacheableWriter cw(session); cw.serializebyte(2); cw.serializenodehandle(mFolderLink.mPublicHandle); cw.serializenodehandle(mNodeManager.getRootNodeFiles().as8byte()); cw.serializebinary(key.key, sizeof(key.key)); cw.serializeexpansionflags(!mFolderLink.mWriteAuth.empty(), !mFolderLink.mAccountAuth.empty(), true); if (!mFolderLink.mWriteAuth.empty()) { cw.serializestring(mFolderLink.mWriteAuth); } if (!mFolderLink.mAccountAuth.empty()) { cw.serializestring(mFolderLink.mAccountAuth); } // make sure the final length is not equal to the old pre-versioned session length string padding(session.size() <= sizeof key.key + SIDLEN ? sizeof key.key + SIDLEN - session.size() + 3 : 1, 'P'); cw.serializestring(padding); } return int(session.size()); } void MegaClient::resendverificationemail() { queueCommand(new CommandResendVerificationEmail(this)); } void MegaClient::resetSmsVerifiedPhoneNumber() { queueCommand(new CommandResetSmsVerifiedPhoneNumber(this)); } error MegaClient::copysession() { // only accounts fully confirmed are allowed to transfer a session, // since the transfer requires the RSA keypair to be available if (loggedin() != FULLACCOUNT) { return (loggedin() == NOTLOGGEDIN) ? API_ENOENT : API_EACCESS; } queueCommand(new CommandCopySession(this)); return API_OK; } string MegaClient::sessiontransferdata(const char *url, string *session) { std::stringstream ss; // open array ss << "["; // add AES key string aeskey; key.serializekeyforjs(&aeskey); ss << aeskey << ",\""; // add session ID ss << *session << "\",\""; // add URL if (url) { ss << url; } ss << "\",false]"; // standard Base64 encoding string json = ss.str(); string base64; base64.resize(json.size() * 4 / 3 + 4); base64.resize(Base64::btoa((byte*)json.data(), json.size(), (char*)base64.data())); std::replace(base64.begin(), base64.end(), '-', '+'); std::replace(base64.begin(), base64.end(), '_', '/'); return base64; } void MegaClient::killsession(handle session) { queueCommand(new CommandKillSessions(this, session)); } // Kill all sessions (except current) void MegaClient::killallsessions() { queueCommand(new CommandKillSessions(this)); } void MegaClient::opensctable() { // called from both login() and fetchnodes() if (dbaccess && !sctable) { string dbname; if (sid.size() >= SIDLEN) { dbname.resize((SIDLEN - sizeof key.key) * 4 / 3 + 3); dbname.resize(Base64::btoa((const byte*)sid.data() + sizeof key.key, SIDLEN - sizeof key.key, (char*)dbname.c_str())); } else if (loggedIntoFolder()) { dbname.resize(static_cast(NODEHANDLE * 4 / 3 + 3)); dbname.resize(Base64::btoa((const byte*)&mFolderLink.mPublicHandle, NODEHANDLE, (char*)dbname.c_str())); } if (dbname.size()) { // Historically, DB upgrades by asking the API for permission at login // If permission is granted, then the existing DB is discarding and a new // is created from the response of fetchnodes (from server) // NOD is a special case where existing DB can be upgraded by renaming the existing // file and migrating data to the new DB scheme. In consequence, we just want to // recycle it (hence the flag DB_OPEN_FLAG_RECYCLE) // Similarly, for VFINGERPRINT (Nodes table with fingerprint virtual column) where // existing DB can be upgraded by renaming the existing file and migrating data to the // new DB scheme. Similarly, for SRW, we just need to rename the existing legacy DB, and // only delete the DB if there is a downgrade (SRW to NO SRW), hence why we need to // increase the DB version, but without affecting the upgrade from NO SRW to SRW. int recycleDBVersion = (DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_NOD || DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_SRW || DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_VFINGERPRINT) ? DB_OPEN_FLAG_RECYCLE : 0; sctable.reset(dbaccess->openTableWithNodes(rng, *fsaccess, dbname, recycleDBVersion, [this](DBError error) { handleDbError(error); })); if (sctable) { DBTableNodes *nodeTable = dynamic_cast(sctable.get()); assert(nodeTable); mNodeManager.setTable(nodeTable); if (!mEnableSearchDBIndexes) { mNodeManager.dropSearchDBIndexes(); } if (!mEnableLexicographicDBIndexes) { mNodeManager.dropLexicographicDBIndexes(); } // DB connection always has a transaction started (applies to both tables, statecache and nodes) // We only commit once we have an up to date SCSN and the table state matches it. sctable->begin(); } else { LOG_err << "Failed to open DB"; MegaClient::fatalError(REASON_ERROR_DB_IO); } } } } void MegaClient::doOpenStatusTable() { if (dbaccess && !statusTable) { string dbname; if (sid.size() >= SIDLEN) { dbname.resize((SIDLEN - sizeof key.key) * 4 / 3 + 3); dbname.resize(Base64::btoa((const byte*)sid.data() + sizeof key.key, SIDLEN - sizeof key.key, (char*)dbname.c_str())); } else if (loggedIntoFolder()) { dbname.resize(static_cast(NODEHANDLE * 4 / 3 + 3)); dbname.resize(Base64::btoa((const byte*)&mFolderLink.mPublicHandle, NODEHANDLE, (char*)dbname.c_str())); } else { assert(false && "attempted to open status table without sid nor folderlink"); } if (dbname.size()) { dbname.insert(0, "status_"); statusTable.reset(dbaccess->open(rng, *fsaccess, dbname, DB_OPEN_FLAG_RECYCLE, [this](DBError error) { handleDbError(error); })); } } } // verify a static symmetric password challenge int MegaClient::checktsid(byte* sidbuf, unsigned len) { if (len != SIDLEN) { return 0; } key.ecb_encrypt(sidbuf); return !memcmp(sidbuf, sidbuf + SIDLEN - SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); } // locate user by e-mail address or ASCII handle User* MegaClient::finduser(const char* userID, int add) { // null user for folder links? if (!userID || !*userID) { return NULL; } if (!strchr(userID, '@')) { // not an e-mail address: must be ASCII handle handle uh; if (Base64::atob(userID, (byte*)&uh, sizeof uh) == sizeof uh) { return finduser(uh, add); } return NULL; } string nuid; User* u; // convert e-mail address to lowercase (ASCII only) JSON::copystring(&nuid, userID); tolower_string(nuid); um_map::iterator it = umindex.find(nuid); if (it == umindex.end()) { if (!add) { return NULL; } // add user by lowercase e-mail address u = &users[++userid]; u->uid = nuid; JSON::copystring(&u->email, nuid.c_str()); umindex[nuid] = userid; return u; } else { return &users[it->second]; } } // locate user by binary handle User* MegaClient::finduser(handle uh, int add) { if (!uh) { return NULL; } User* u; uh_map::iterator it = uhindex.find(uh); if (it == uhindex.end()) { if (!add) { return NULL; } // add user by binary handle u = &users[++userid]; char tempUid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, tempUid); u->uid.assign(tempUid, 11); uhindex[uh] = userid; u->userhandle = uh; if (uh == me) { // we've just created the instance for logged-in user u->cacheNonExistingAttributes(); } return u; } else { return &users[it->second]; } } User *MegaClient::ownuser() { return finduser(me); } // add missing mapping (handle or email) // reduce uid to ASCII uh if only known by email void MegaClient::mapuser(handle uh, const char* email) { if (!email || !*email) { return; } User* u; string nuid; JSON::copystring(&nuid, email); tolower_string(nuid); // does user uh exist? uh_map::iterator hit = uhindex.find(uh); if (hit != uhindex.end()) { // yes: add email reference u = &users[hit->second]; um_map::iterator mit = umindex.find(nuid); if (mit != umindex.end() && mit->second != hit->second && (users[mit->second].show != INACTIVE || users[mit->second].userhandle == me)) { // duplicated user: one by email, one by handle discardnotifieduser(&users[mit->second]); assert(!users[mit->second].sharing.size()); users.erase(mit->second); } // if mapping a different email, remove old index if (strcmp(u->email.c_str(), nuid.c_str())) { umindex.erase(u->email); JSON::copystring(&u->email, nuid.c_str()); } umindex[nuid] = hit->second; return; } // does user email exist? um_map::iterator mit = umindex.find(nuid); if (mit != umindex.end()) { // yes: add uh reference u = &users[mit->second]; uhindex[uh] = mit->second; u->userhandle = uh; char tempUid[12]; Base64::btoa((byte*)&uh, MegaClient::USERHANDLE, tempUid); u->uid.assign(tempUid, 11); } } void MegaClient::dodiscarduser(User* u, bool discardnotified) { if (!u) { return; } u->removepkrs(this); if (discardnotified) { discardnotifieduser(u); } int uidx = -1; if (!u->email.empty()) { auto it = umindex.find(u->email); if (it != umindex.end()) { uidx = it->second; umindex.erase(it); } } if (u->userhandle != UNDEF) { auto it = uhindex.find(u->userhandle); if (it != uhindex.end()) { assert(uidx == -1 || uidx == it->second); uidx = it->second; uhindex.erase(it); } } assert(uidx != -1); users.erase(uidx); } void MegaClient::discarduser(handle uh, bool discardnotified) { User *u = finduser(uh); dodiscarduser(u, discardnotified); } PendingContactRequest* MegaClient::findpcr(handle p) { if (ISUNDEF(p)) { return NULL; } auto& pcr = pcrindex[p]; if (!pcr) { pcr.reset(new PendingContactRequest(p)); assert(fetchingnodes); // while fetchingnodes, outgoing shares reference an "empty" PCR that is completed when `opc` is parsed } return pcr.get(); } // only for incoming PCRs std::optional MegaClient::findpcr(const string& email) { for (auto& pcr: pcrindex) { if (pcr.second->isoutgoing) continue; if (pcr.second->originatoremail == email) return pcr.second.get(); } return std::nullopt; } void MegaClient::mappcr(handle id, unique_ptr&& pcr) { pcrindex[id] = std::move(pcr); } bool MegaClient::discardnotifieduser(User *u) { for (user_vector::iterator it = usernotify.begin(); it != usernotify.end(); it++) { if (*it == u) { usernotify.erase(it); return true; // no duplicated users in the notify vector } } return false; } void MegaClient::clearKeys() { User *u = finduser(me); u->setAttributeExpired(ATTR_KEYRING); u->setAttributeExpired(ATTR_ED25519_PUBK); u->setAttributeExpired(ATTR_CU25519_PUBK); u->setAttributeExpired(ATTR_SIG_RSA_PUBK); u->setAttributeExpired(ATTR_SIG_CU255_PUBK); } void MegaClient::resetKeyring() { delete mEd255Key; mEd255Key = NULL; delete mX255Key; mX255Key = NULL; } // process node tree (bottom up) void MegaClient::proctree(std::shared_ptr n, TreeProc* tp, bool skipinshares, bool skipversions) { if (!n) return; if (!skipversions || n->type != FILENODE) { sharedNode_list children = getChildren(n.get()); for (sharedNode_list::iterator it = children.begin(); it != children.end(); ) { auto& node = *it; it++; if (!(skipinshares && node->inshare)) { proctree(node, tp, skipinshares); } } } tp->proc(this, n); } // queue PubKeyAction request to be triggered upon availability of the user's // public key void MegaClient::queuepubkeyreq(User* u, std::unique_ptr pka) { if (!u || u->pubk.isvalid()) { restag = pka->tag; pka->proc(this, u); unique_ptr cleanup(u && u->isTemporary ? u : nullptr); } else { u->pkrs.push_back(std::move(pka)); if (!u->pubkrequested) { u->pkrs.back()->cmd = new CommandPubKeyRequest(this, u); queueCommand(u->pkrs.back()->cmd); u->pubkrequested = true; } } } User* MegaClient::getUserForSharing(const char* userID) { User* u = finduser(userID, 0); if (!u && userID) { if (strchr(userID, '@')) // uid is an e-mail address { string nuid; JSON::copystring(&nuid, userID); tolower_string(nuid); u = new User(nuid.c_str()); u->uid = nuid; u->isTemporary = true; } else // not an e-mail address: must be ASCII handle { handle uh; if (Base64::atob(userID, (byte*)&uh, sizeof uh) == sizeof uh) { u = new User(NULL); u->userhandle = uh; u->uid = userID; u->isTemporary = true; } } } return u; } void MegaClient::queuepubkeyreq(const char* userID, std::unique_ptr pka) { User* u = getUserForSharing(userID); queuepubkeyreq(u, std::move(pka)); } // rewrite keys of foreign nodes due to loss of underlying shareufskey void MegaClient::rewriteforeignkeys(std::shared_ptr n) { TreeProcForeignKeys rewrite; proctree(n, &rewrite); if (nodekeyrewrite.size()) { queueCommand(new CommandNodeKeyUpdate(this, &nodekeyrewrite)); nodekeyrewrite.clear(); } } // Migrate the account to start using the new ^!keys attr. void MegaClient::upgradeSecurity(std::function completion) { // Upgrade only fully logged in accounts. // All keys must be available before proceeding. if (loggedin() != FULLACCOUNT) { LOG_warn << "Not fully logged into an account to be upgraded."; completion(API_EARGS); return; } if (mKeyManager.generation()) { LOG_warn << "Already upgraded"; completion(API_OK); return; } LOG_debug << "Upgrading cryptographic subsystem."; string prEd255; string prCu255; User *u = finduser(me); assert(u); const UserAttribute* attribute = u->getAttribute(ATTR_KEYRING); if (attribute && attribute->isValid()) { unique_ptr records{tlv::containerToRecords(attribute->value(), key)}; if (records) { prEd255.swap((*records)[EdDSA::TLV_KEY]); prCu255.swap((*records)[ECDH::TLV_KEY]); } else { LOG_warn << "Failed to decrypt keyring while initialization"; completion(API_EKEY); return; } } else { LOG_warn << "Keys not available"; completion(API_ETEMPUNAVAIL); return; } assert(prEd255.size() == EdDSA::SEED_KEY_LENGTH); assert(prCu255.size() == ECDH::PRIVATE_KEY_LENGTH); if ((prEd255.size() != EdDSA::SEED_KEY_LENGTH) || (prCu255.size() != ECDH::PRIVATE_KEY_LENGTH)) { LOG_warn << "Invalid keys"; completion(API_EKEY); return; } mKeyManager.setKey(key); mKeyManager.init(prEd255, prCu255, mSerializedPrivateRsaKey); int migratedInShares = 0; int totalInShares = 0; int migratedOutShares = 0; int totalOutShares = 0; sharedNode_vector shares = getInShares(); for (auto &n : shares) { ++totalInShares; if (n->sharekey) { ++migratedInShares; mKeyManager.addShareKey(n->nodehandle, std::string((const char *)n->sharekey->key, SymmCipher::KEYLENGTH)); } } shares = mNodeManager.getNodesWithOutShares(); for (auto &n : shares) { ++totalOutShares; if (n->sharekey) { ++migratedOutShares; mKeyManager.addShareKey(n->nodehandle, std::string((const char *)n->sharekey->key, SymmCipher::KEYLENGTH)); } } shares = mNodeManager.getNodesWithPendingOutShares(); for (auto &n : shares) { ++totalOutShares; if (n->sharekey) { ++migratedOutShares; mKeyManager.addShareKey(n->nodehandle, std::string((const char *)n->sharekey->key, SymmCipher::KEYLENGTH)); } } shares = mNodeManager.getNodesWithLinks(); for (auto &n : shares) { ++totalOutShares; if (n->sharekey) { ++migratedOutShares; mKeyManager.addShareKey(n->nodehandle, std::string((const char *)n->sharekey->key, SymmCipher::KEYLENGTH)); } } LOG_debug << "Migrated inshares: " << migratedInShares << " of " << totalInShares; LOG_debug << "Migrated outshares: " << migratedOutShares << " of " << totalOutShares; auto it = mAuthRings.find(ATTR_AUTHRING); assert(it != mAuthRings.end()); if (it != mAuthRings.end()) { mKeyManager.setAuthRing(it->second.serializeForJS()); } it = mAuthRings.find(ATTR_AUTHCU255); assert(it != mAuthRings.end()); if (it != mAuthRings.end()) { mKeyManager.setAuthCU255(it->second.serializeForJS()); } mKeyManager.commit( []() { // Nothing to include in this commit (apart from the changes already applied to mKeyManager). // putua shouldn't fail in this case. Otherwise, another client would have upgraded the account // at the same time and therefore we wouldn't have to apply the changes again. }, [this, completion](error e) { completion(e); if (e) return; // Get pending keys for inshares fetchContactsKeys(); sc_pk(); }); } void MegaClient::setContactVerificationWarning(bool enabled, std::function completion) { if (mKeyManager.getContactVerificationWarning() == enabled) { if (completion) completion(API_OK); return; } mKeyManager.commit( [this, enabled]() { mKeyManager.setContactVerificationWarning(enabled); }, [completion](error e) { if (completion) completion(e); }); } // Creates a new share key for the node if there is no share key already created. void MegaClient::openShareDialog(Node* n, std::function completion) { if (!n) { completion(API_EARGS); return; } if (!mKeyManager.generation()) { LOG_err << "Account not upgraded yet"; completion(API_EINCOMPLETE); return; } bool updateKeys = false; if (!n->sharekey) { string previousKey = mKeyManager.getShareKey(n->nodehandle); if (!previousKey.size()) { LOG_debug << "Creating new share key for " << toHandle(n->nodehandle); byte newKey[SymmCipher::KEYLENGTH]; rng.genblock(newKey, sizeof(newKey)); n->sharekey.reset(new SymmCipher(newKey)); updateKeys = true; } else { LOG_debug << "Setting node's sharekey from KeyManager (openShareDialog)"; n->sharekey.reset(new SymmCipher((const byte*)previousKey.data())); } } else assert(mKeyManager.getShareKey(n->nodehandle).size()); if (updateKeys) // new share: add key to ^!keys { handle nodehandle = n->nodehandle; std::string shareKey((const char *)n->sharekey->key, SymmCipher::KEYLENGTH); LOG_debug << "Adding new share key to ^!keys for outshare " << toNodeHandle(nodehandle); mKeyManager.commit( [this, nodehandle, shareKey]() { // Changes to apply in the commit mKeyManager.addShareKey(nodehandle, shareKey, true); }, [completion](error e) { completion(e); }); } else { completion(API_OK); } } // if user has a known public key, complete instantly // otherwise, queue and request public key if not already pending // `user` is null for creating folder links void MegaClient::setshare(std::shared_ptr n, const char* user, accesslevel_t a, bool writable, const char* personal_representation, int tag, std::function completion) { assert(completion); if (!mKeyManager.generation()) { LOG_err << "Account not upgraded yet"; completion(API_EINCOMPLETE, writable); return; } size_t total = n->outshares ? n->outshares->size() : 0; total += n->pendingshares ? n->pendingshares->size() : 0; if (a == ACCESS_UNKNOWN && total == 1) { // rewrite keys of foreign nodes located in the outbound share that is getting canceled // FIXME: verify that it is really getting canceled to prevent benign premature rewrite rewriteforeignkeys(n); } if (a == ACCESS_UNKNOWN) { User *u = getUserForSharing(user); handle nodehandle = n->nodehandle; queueCommand(new CommandSetShare( this, n, u, a, 0, NULL, writable, personal_representation, tag, [this, u, total, nodehandle, completion](Error e, bool writable) { if (!e && total == 1) { if (mKeyManager.isShareKeyInUse(nodehandle)) { LOG_debug << "Last share: disabling in-use flag for the sharekey in " "KeyManager. nh: " << toNodeHandle(nodehandle); mKeyManager.commit( [this, nodehandle]() { mKeyManager.setSharekeyInUse(nodehandle, false); }, [completion, writable](error commitError) { completion(commitError, writable); }); } else { if (mKeyManager.isShareKeyTrusted(nodehandle)) { LOG_warn << "in-use flag was already disabled for the sharekey in " "KeyManager when removing the last share. nh: " << toNodeHandle(nodehandle); } completion(e, writable); } } else { completion(e, writable); } if (u && u->isTemporary) { delete u; } })); return; } User *u = getUserForSharing(user); setShareCompletion(n.get(), u, a, writable, personal_representation, tag, std::move(completion)); // will release u, if temporary } void MegaClient::setShareCompletion(Node *n, User *user, accesslevel_t a, bool writable, const char* personal_representation, int tag, std::function completion) { std::string msg; if (personal_representation) { msg = personal_representation; } std::string userID; if (user) { userID = (user->show == VISIBLE) ? user->uid : user->email; } bool newshare = !n->isShared(); // if creating a folder link and there's no sharekey already bool newShareKey = false; if (!n->sharekey && userID.empty()) { assert(newshare); string previousKey = mKeyManager.getShareKey(n->nodehandle); if (!previousKey.size()) { LOG_debug << "Creating new share key for folder link on " << toHandle(n->nodehandle); byte newKey[SymmCipher::KEYLENGTH]; rng.genblock(newKey, sizeof(newKey)); n->sharekey.reset(new SymmCipher(newKey)); newShareKey = true; } else { LOG_debug << "Reusing node's sharekey from KeyManager for folder link on " << toHandle(n->nodehandle); n->sharekey.reset(new SymmCipher((const byte*)previousKey.data())); } } if (!n->sharekey) { LOG_err << "You should first create the key using MegaClient::openShareDialog (setshare)"; completion(API_EKEY, writable); if (user && user->isTemporary) delete user; return; } handle nodehandle = n->nodehandle; std::string shareKey((const char *)n->sharekey->key, SymmCipher::KEYLENGTH); std::function completeShare = [this, user, nodehandle, a, newshare, msg, tag, writable, completion]() { std::shared_ptr n; // node vanished: bail if (!(n = nodebyhandle(nodehandle))) { completion(API_ENOENT, writable); if (user && user->isTemporary) delete user; return; } queueCommand(new CommandSetShare( this, n, user, a, newshare, NULL, writable, msg.c_str(), tag, [this, user, newshare, nodehandle, completion](Error e, bool writable) { if (!e) { if (mKeyManager.isShareKeyTrusted(nodehandle) && !mKeyManager.isShareKeyInUse(nodehandle)) { if (!newshare) { LOG_warn << "in-use flag for the sharekey in KeyManager is not set but " "the node was already shared. nh: " << toNodeHandle(nodehandle); } LOG_debug << "Enabling in-use flag for the sharekey in KeyManager. nh: " << toNodeHandle(nodehandle); mKeyManager.commit( [this, nodehandle]() { mKeyManager.setSharekeyInUse(nodehandle, true); }, [completion, writable](error commitError) { completion(commitError, writable); }); } else { if (!mKeyManager.isShareKeyTrusted(nodehandle)) // Legacy share { LOG_debug << "in-use flag for the sharekey in KeyManager not set. " "Share Key is not trusted. nh: " << toNodeHandle(nodehandle); } else if (newshare) // trusted, bit set but was not shared. { LOG_err << "in-use flag for the sharekey in KeyManager is already set " "but the node was not being shared before. nh: " << toNodeHandle(nodehandle); string msg = "in-use flag already set for a node with no previous active share"; sendevent(99479, msg.c_str()); assert(!newshare && msg.c_str()); } completion(e, writable); } } else { completion(e, writable); } if (user && user->isTemporary) delete user; })); }; if (userID.size() || newShareKey) // share with a user or folder-link requiring new sharekey { LOG_debug << "Updating ^!keys before sharing " << toNodeHandle(nodehandle); mKeyManager.commit( [this, newShareKey, nodehandle, shareKey, userID]() { // Changes to apply in the commit if (newShareKey) { // Add outshare key into ^!keys mKeyManager.addShareKey(nodehandle, shareKey, true); } if (userID.size()) // not a folder link, but a share with a user { // Add pending outshare; mKeyManager.addPendingOutShare(nodehandle, userID); } }, [completeShare, completion, user, writable](error e) { if (e == API_OK) completeShare(); else { completion(e, writable); if (user && user->isTemporary) delete user; } }); return; } else // folder link on an already shared folder or reusing existing sharekey -> no need to update ^!keys { completeShare(); } } // Add/delete/remind outgoing pending contact request void MegaClient::setpcr(const char* temail, opcactions_t action, const char* msg, const char* oemail, handle contactLink, CommandSetPendingContact::Completion completion) { queueCommand(new CommandSetPendingContact(this, temail, action, msg, oemail, contactLink, std::move(completion))); } void MegaClient::updatepcr(handle p, ipcactions_t action, CommandUpdatePendingContact::Completion completion) { queueCommand(new CommandUpdatePendingContact(this, p, action, std::move(completion))); } void MegaClient::getDiscountCodeInformation( const std::string& code, CommandDiscountCodeGetInfo::CompletionCallback completion) { queueCommand(new CommandDiscountCodeGetInfo(this, code, std::move(completion))); } // enumerate Pro account purchase options (not fully implemented) void MegaClient::purchase_enumeratequotaitems(const std::optional& countryCode) { queueCommand(new CommandEnumerateQuotaItems(countryCode, this)); } // begin a new purchase (FIXME: not fully implemented) void MegaClient::purchase_begin() { purchase_basket.clear(); } // submit purchased product for payment void MegaClient::purchase_additem(int itemclass, handle item, unsigned price, const char* currency, unsigned tax, const char* country, handle lastPublicHandle, int phtype, int64_t ts) { queueCommand(new CommandPurchaseAddItem(this, itemclass, item, price, currency, tax, country, lastPublicHandle, phtype, ts)); } // obtain payment URL for given provider void MegaClient::purchase_checkout(int gateway) { queueCommand(new CommandPurchaseCheckout(this, gateway)); } void MegaClient::submitpurchasereceipt(int type, const char *receipt, handle lph, int phtype, int64_t ts) { queueCommand(new CommandSubmitPurchaseReceipt(this, type, receipt, lph, phtype, ts)); } error MegaClient::creditcardstore(const char *ccplain) { if (!ccplain) { return API_EARGS; } string ccnumber, expm, expy, cv2, ccode; if (!JSON::extractstringvalue(ccplain, "card_number", &ccnumber) || (ccnumber.size() < 10) || !JSON::extractstringvalue(ccplain, "expiry_date_month", &expm) || (expm.size() != 2) || !JSON::extractstringvalue(ccplain, "expiry_date_year", &expy) || (expy.size() != 4) || !JSON::extractstringvalue(ccplain, "cv2", &cv2) || (cv2.size() != 3) || !JSON::extractstringvalue(ccplain, "country_code", &ccode) || (ccode.size() != 2)) { return API_EARGS; } string::iterator it = find_if(ccnumber.begin(), ccnumber.end(), char_is_not_digit); if (it != ccnumber.end()) { return API_EARGS; } it = find_if(expm.begin(), expm.end(), char_is_not_digit); if (it != expm.end() || atol(expm.c_str()) > 12) { return API_EARGS; } it = find_if(expy.begin(), expy.end(), char_is_not_digit); if (it != expy.end() || atol(expy.c_str()) < 2015) { return API_EARGS; } it = find_if(cv2.begin(), cv2.end(), char_is_not_digit); if (it != cv2.end()) { return API_EARGS; } //Luhn algorithm int odd = 1, sum = 0; for (size_t i = ccnumber.size(); i--; odd = !odd) { int digit = ccnumber[i] - '0'; sum += odd ? digit : ((digit < 5) ? 2 * digit : 2 * (digit - 5) + 1); } if (sum % 10) { return API_EARGS; } byte pubkdata[sizeof(PAYMENT_PUBKEY) * 3 / 4 + 3]; int pubkdatalen = Base64::atob(PAYMENT_PUBKEY, (byte *)pubkdata, sizeof(pubkdata)); string ccenc; string ccplain1 = ccplain; PayCrypter payCrypter(rng); if (!payCrypter.hybridEncrypt(&ccplain1, pubkdata, pubkdatalen, &ccenc)) { return API_EARGS; } string last4 = ccnumber.substr(ccnumber.size() - 4); char hashstring[256]; int ret = snprintf(hashstring, sizeof(hashstring), "{\"card_number\":\"%s\"," "\"expiry_date_month\":\"%s\"," "\"expiry_date_year\":\"%s\"," "\"cv2\":\"%s\"}", ccnumber.c_str(), expm.c_str(), expy.c_str(), cv2.c_str()); if (ret < 0 || ret >= (int)sizeof(hashstring)) { return API_EARGS; } HashSHA256 hash; string binaryhash; hash.add((byte*)hashstring, static_cast(strlen(hashstring))); hash.get(&binaryhash); static const char hexchars[] = "0123456789abcdef"; ostringstream oss; string hexHash; for (size_t i=0;i> 4) & 0x0F]); oss.put(hexchars[binaryhash[i] & 0x0F]); } hexHash = oss.str(); string base64cc; base64cc.resize(ccenc.size()*4/3+4); base64cc.resize(Base64::btoa((byte*)ccenc.data(), ccenc.size(), (char*)base64cc.data())); std::replace( base64cc.begin(), base64cc.end(), '-', '+'); std::replace( base64cc.begin(), base64cc.end(), '_', '/'); queueCommand(new CommandCreditCardStore(this, base64cc.data(), last4.c_str(), expm.c_str(), expy.c_str(), hexHash.data())); return API_OK; } void MegaClient::creditcardquerysubscriptions() { queueCommand(new CommandCreditCardQuerySubscriptions(this)); } void MegaClient::creditcardcancelsubscriptions( const CommandCreditCardCancelSubscriptions::CancelSubscription& cancelSubscription) { queueCommand(new CommandCreditCardCancelSubscriptions(this, cancelSubscription)); } void MegaClient::getpaymentmethods() { queueCommand(new CommandGetPaymentMethods(this)); } // delete or block an existing contact error MegaClient::removecontact(const char* email, visibility_t show, CommandRemoveContact::Completion completion) { if (!strchr(email, '@') || (show != HIDDEN && show != BLOCKED)) { return API_EARGS; } queueCommand(new CommandRemoveContact(this, email, show, std::move(completion))); return API_OK; } void MegaClient::putua(attr_t at, string_map&& records, int ctag, handle lastPublicHandle, int phtype, int64_t ts, std::function completion) { // serialize and encrypt the TLV container unique_ptr container(tlv::recordsToContainer(std::move(records), rng, key)); if (!container) { completion ? completion(API_EINTERNAL) : app->putua_result(API_EINTERNAL); return; } putua(at, reinterpret_cast(container->data()), static_cast(container->size()), ctag, lastPublicHandle, phtype, ts, std::move(completion)); } /** * @brief Attach/update/delete a user attribute. * * Attributes are stored as base64-encoded binary blobs. They use internal * attribute name prefixes: * * "*" - Private and encrypted. Use a TLV container (key-value) * "#" - Protected and plain text, accessible only by contacts. * "+" - Public and plain text, accessible by anyone knowing userhandle * "^" - Private and non-encrypted. * * @param at Attribute type. * @param av Attribute value. * @param avl Attribute value length. * @param ctag Tag to identify the request at intermediate layer */ void MegaClient::putua(attr_t at, const byte* av, unsigned avl, int ctag, handle lastPublicHandle, int phtype, int64_t ts, std::function completion) { assert(at != ATTR_STORAGE_STATE); // putua is forbidden for this attribute string data; if (!completion) { completion = [this](Error e){ app->putua_result(e); }; } if (!av) { if (at == ATTR_AVATAR) // remove avatar { data = "none"; } av = (const byte*) data.data(); avl = unsigned(data.size()); } int tag = (ctag != -1) ? ctag : reqtag; User *u = ownuser(); assert(u); if (!u) { LOG_err << "Own user not found when attempting to set user attributes"; restag = tag; completion(API_EACCESS); return; } int needversion = u->needversioning(at); if (needversion == -1) { restag = tag; completion(API_EARGS); // attribute not recognized return; } if (avl >= User::getMaxAttributeSize(at)) { sendevent(99483, "User attribute exceeds maximum size"); LOG_err << "User attribute exceeds maximum size: " << User::attr2string(at); restag = tag; return completion(API_EARGS); } if (!needversion) { queueCommand(new CommandPutUA(this, at, av, avl, tag, lastPublicHandle, phtype, ts, std::move(completion))); } else { // if the cached value is outdated, first need to fetch the latest version const UserAttribute* attribute = u->getAttribute(at); if (attribute && attribute->isExpired()) { restag = tag; completion(API_EEXPIRED); return; } queueCommand(new CommandPutUAVer(this, at, av, avl, tag, std::move(completion))); } } void MegaClient::putua(userattr_map *attrs, int ctag, std::function completion) { int tag = (ctag != -1) ? ctag : reqtag; User *u = ownuser(); if (!completion) { completion = [this](Error e){ app->putua_result(e); }; } if (!u || !attrs || !attrs->size()) { restag = tag; return completion(API_EARGS); } for (userattr_map::iterator it = attrs->begin(); it != attrs->end(); it++) { attr_t type = it->first; assert(type != ATTR_STORAGE_STATE); // putua is forbidden for this attribute if (User::needversioning(type) != 1) { restag = tag; return completion(API_EARGS); } // if the cached value is outdated, first need to fetch the latest version const UserAttribute* attribute = u->getAttribute(type); if (attribute && attribute->isExpired()) { restag = tag; return completion(API_EEXPIRED); } if (it->second.size() >= User::getMaxAttributeSize(type)) { sendevent(99483, "User attribute exceeds maximum size"); LOG_err << "User attribute exceeds maximum size: " << User::attr2string(it->first); restag = tag; completion(API_EARGS); return; } } queueCommand(new CommandPutMultipleUAVer(this, attrs, tag, std::move(completion))); } /** * @brief Queue a user attribute retrieval. * * @param u User. * @param at Attribute type. * @param ctag Tag to identify the request at intermediate layer * * @return False when attribute requires a request to server. True otherwise (if cached, or unknown) */ bool MegaClient::getua(User* u, const attr_t at, int ctag, mega::CommandGetUA::CompletionErr completionErr, mega::CommandGetUA::CompletionBytes completionBytes, mega::CommandGetUA::CompletionTLV completionTLV) { if (!u || at == ATTR_UNKNOWN) { completionErr ? completionErr(API_ENOENT) : app->getua_result(API_ENOENT); } else { // if we can solve those requests locally (cached values)... const UserAttribute* attribute = u->getAttribute(at); int tag = (ctag != -1) ? ctag : reqtag; if (attribute && attribute->isValid() && at != ATTR_AVATAR) // value of Avatar is always empty { const string& cachedav = attribute->value(); if (User::scope(at) == ATTR_SCOPE_PRIVATE_ENCRYPTED) // TLV encoding { unique_ptr records{tlv::containerToRecords(cachedav, key)}; restag = tag; completionTLV ? completionTLV(std::move(records), at) : app->getua_result(std::move(records), at); return true; } else { restag = tag; completionBytes ? completionBytes((byte*)cachedav.data(), unsigned(cachedav.size()), at) : app->getua_result((byte*)cachedav.data(), unsigned(cachedav.size()), at); return true; } } else if (attribute && attribute->isNotExisting()) // own user attrs marked as "no exits" { assert(u->userhandle == me); restag = tag; completionErr ? completionErr(API_ENOENT) : app->getua_result(API_ENOENT); } else // never created or expired { queueCommand(new CommandGetUA(this, u->uid.c_str(), at, NULL, tag, completionErr, completionBytes, completionTLV)); return false; } } return true; } void MegaClient::getua(const char *email_handle, const attr_t at, const char *ph, int ctag, mega::CommandGetUA::CompletionErr ce, mega::CommandGetUA::CompletionBytes cb, mega::CommandGetUA::CompletionTLV ctlv) { if (email_handle && at != ATTR_UNKNOWN) { queueCommand(new CommandGetUA(this, email_handle, at, ph, (ctag != -1) ? ctag : reqtag, ce, cb, ctlv)); } } void MegaClient::getUserEmail(const char* userID) { queueCommand(new CommandGetUserEmail(this, userID)); } void MegaClient::loginResult(CommandLogin::Completion completion, error e, std::function onLoginOk) { if (e != API_OK) { mV1PswdVault.reset(); // clear this before the app knows that login is done, might improve security completion(e); return; } // Is the user fully logged on? auto isFullyLoggedIn = loggedin() == FULLACCOUNT; assert(!mV1PswdVault || accountversion == 1); if (accountversion == 1 && mV1PswdVault) { auto v1PswdVault(std::move(mV1PswdVault)); if (isFullyLoggedIn) { // initiate automatic upgrade to V2 string pwd; bool upgrade = false; unique_ptr records{ tlv::containerToRecords(v1PswdVault->first, v1PswdVault->second)}; if (records) { if (auto it = records->find("p"); it != records->end()) { pwd.swap(it->second); upgrade = true; } } if (upgrade) { if (pwd.empty()) { char msg[] = "Account upgrade to v2 has failed (invalid content in vault)"; LOG_err << msg; sendevent(99475, msg); // report successful login, even if upgrade failed; user data was not affected, so apps can continue running completion(API_OK); if (onLoginOk) { onLoginOk(); } return; } upgradeAccountToV2(pwd, restag, [this, completion, onLoginOk](error e) { // handle upgrade result if (e == API_EEXIST) { LOG_debug << "Account upgrade to V2 failed with EEXIST. It must have been upgraded in the meantime. Fetching user data again."; // upgrade done in the meantime by different client; get account details again getuserdata( restag, [completion, onLoginOk](string*, string*, string*, std::vector&&, error e) { error loginErr = e == API_OK ? API_OK : API_EINTERNAL; completion(loginErr); // if error, report for login too because user data is inconsistent now if (e != API_OK) { LOG_err << "Failed to get user data after acccount upgrade to V2 ended with EEXIST, error: " << e; } else if (onLoginOk) { onLoginOk(); } }); } else { if (e == API_OK) { LOG_info << "Account successfully upgraded to V2."; } else { LOG_warn << "Failed to upgrade account to V2, error: " << e; } // report successful login, even if upgrade failed; user data was not affected, so apps can continue running completion(API_OK); if (onLoginOk) { onLoginOk(); } } } ); return; // stop here when account upgrade was initiated } } } // V2, or V1 without mandatory requirements for upgrade completion(API_OK); if (onLoginOk) { onLoginOk(); } } // // Account upgrade to V2 // void MegaClient::saveV1Pwd(const char* pwd) { assert(pwd); if (pwd && accountversion == 1) { vector pwkey(SymmCipher::KEYLENGTH); rng.genblock(pwkey.data(), pwkey.size()); SymmCipher pwcipher(pwkey.data()); unique_ptr encrypted{ tlv::recordsToContainer({{"p", pwd}}, rng, pwcipher) }; if (encrypted) { mV1PswdVault.reset( new pair(std::move(*encrypted), std::move(pwcipher))); } } } void MegaClient::upgradeAccountToV2(const string& pwd, int ctag, std::function completion) { assert(loggedin() == FULLACCOUNT); assert(accountversion == 1); assert(!pwd.empty()); vector clientRandomValue; vector encmasterkey; string hashedauthkey; string salt; fillCypheredAccountDataV2(pwd.c_str(), clientRandomValue, encmasterkey, hashedauthkey, salt); queueCommand(new CommandAccountVersionUpgrade(std::move(clientRandomValue), std::move(encmasterkey), std::move(hashedauthkey), std::move(salt), ctag, completion)); } // -------- end of Account upgrade to V2 #ifdef DEBUG void MegaClient::delua(const char *an) { if (an) { queueCommand(new CommandDelUA(this, an)); } } #endif void MegaClient::senddevcommand(const char* command, const char* email, long long q, int bs, int us, const char* abs_c) { queueCommand(new CommandSendDevCommand(this, command, email, q, bs, us, abs_c)); } void MegaClient::transfercacheadd(Transfer *transfer, TransferDbCommitter* committer) { if (tctable && !transfer->skipserialization) { if (committer) committer->addTransferCount += 1; tctable->checkCommitter(committer); tctable->put(MegaClient::CACHEDTRANSFER, transfer, &tckey); } } void MegaClient::transfercachedel(Transfer *transfer, TransferDbCommitter* committer) { if (tctable && transfer->dbid) { if (committer) committer->removeTransferCount += 1; tctable->checkCommitter(committer); tctable->del(transfer->dbid); } } void MegaClient::filecacheadd(File *file, TransferDbCommitter& committer) { if (tctable && !file->syncxfer) { committer.addFileCount += 1; tctable->checkCommitter(&committer); tctable->put(MegaClient::CACHEDFILE, file, &tckey); } } void MegaClient::filecachedel(File *file, TransferDbCommitter* committer) { if (tctable && !file->syncxfer) { if (committer) committer->removeFileCount += 1; tctable->checkCommitter(committer); tctable->del(file->dbid); } if (file->temporaryfile) { LOG_debug << "Removing temporary file"; fsaccess->unlinklocal(file->getLocalname()); } } // queue user for notification void MegaClient::notifyuser(User* u) { if (!u->notified) { u->notified = true; usernotify.push_back(u); } } // queue pcr for notification void MegaClient::notifypcr(PendingContactRequest* pcr) { if (pcr && !pcr->notified) { pcr->notified = true; pcrnotify.push_back(pcr); } } #ifdef ENABLE_CHAT void MegaClient::notifychat(TextChat *chat) { if (!chat->notified) { chat->notified = true; chatnotify[chat->getChatId()] = chat; } } #endif #ifdef ENABLE_CHAT void MegaClient::procmcf(JSON *j) { if (j->enterobject()) { bool done = false; while (!done) { bool readingPublicChats = false; switch(j->getnameid()) { case makeNameid("pc"): // list of public and/or formerly public chatrooms { readingPublicChats = true; } // fall-through case name_id::c: // list of chatrooms { j->enterarray(); while(j->enterobject()) // while there are more chats to read... { handle chatid = UNDEF; privilege_t priv = PRIV_UNKNOWN; int shard = -1; userpriv_vector *userpriv = NULL; bool group = false; string title; string unifiedKey; m_time_t ts = -1; bool publicchat = false; bool meeting = false; // chat options: [0 (remove) | 1 (add)], if chat option is not included, that option is disabled int waitingRoom = 0; int openInvite = 0; int speakRequest = 0; bool readingChat = true; while(readingChat) // read the chat information { switch (j->getnameid()) { case makeNameid("id"): chatid = j->gethandle(MegaClient::CHATHANDLE); break; case makeNameid("p"): priv = (privilege_t) j->getint(); break; case makeNameid("cs"): shard = int(j->getint()); break; case name_id::u: // list of users participating in the chat // (+privileges) userpriv = readuserpriv(j); break; case makeNameid("g"): group = j->getbool(); break; case makeNameid("ct"): j->storeobject(&title); break; case makeNameid("ck"): // store unified key for public chats assert(readingPublicChats); j->storeobject(&unifiedKey); break; case makeNameid("ts"): // actual creation timestamp ts = j->getint(); break; case makeNameid("m"): // operation mode: 1 -> public chat; 0 -> private chat assert(readingPublicChats); publicchat = j->getbool(); break; case makeNameid("mr"): // meeting room: 1; no meeting room: 0 meeting = j->getbool(); assert(readingPublicChats || !meeting); // public chats can be meetings or not. Private chats cannot be meetings break; case makeNameid("w"): // waiting room waitingRoom = static_cast(j->getint()); break; case makeNameid("sr"): // speak request speakRequest = static_cast(j->getint()); break; case makeNameid("oi"): // open invite openInvite = static_cast(j->getint()); break; case EOO: if (chatid != UNDEF && priv != PRIV_UNKNOWN && shard != -1) { TextChat* chat = nullptr; if (chats.find(chatid) == chats.end()) { chat = new TextChat(readingPublicChats && publicchat); chats[chatid] = chat; } else { chat = chats[chatid]; if (readingPublicChats) { setChatMode(chat, publicchat); } } chat->setChatId(chatid); chat->setOwnPrivileges(priv); chat->setShard(shard); chat->setGroup(group); chat->setTitle(title); chat->setTs(ts != -1 ? ts : 0); chat->setMeeting(meeting); if (group) { chat->addOrUpdateChatOptions(speakRequest, waitingRoom, openInvite); } if (readingPublicChats) { chat->setUnifiedKey(unifiedKey); if (unifiedKey.empty()) { LOG_err << "Received public (or formerly public) chat without unified key"; } } // remove yourself from the list of users (only peers matter) if (userpriv) { if (chat->getOwnPrivileges() == PRIV_RM) { // clear the list of peers because API still includes peers in the // actionpacket, but not in a fresh fetchnodes delete userpriv; userpriv = NULL; } else { userpriv_vector::iterator upvit; for (upvit = userpriv->begin(); upvit != userpriv->end(); upvit++) { if (upvit->first == me) { userpriv->erase(upvit); if (userpriv->empty()) { delete userpriv; userpriv = NULL; } break; } } } } chat->setUserPrivileges(userpriv); } else { LOG_err << "Failed to parse chat information"; } readingChat = false; break; default: if (!j->storeobject()) { LOG_err << "Failed to parse chat information"; readingChat = false; delete userpriv; userpriv = NULL; } break; } } j->leaveobject(); } j->leavearray(); break; } case makeNameid("pcf"): // list of flags for public and/or formerly public chatrooms { readingPublicChats = true; } // fall-through case makeNameid("cf"): { j->enterarray(); while(j->enterobject()) // while there are more chatid/flag tuples to read... { handle chatid = UNDEF; byte flags = 0xFF; bool readingFlags = true; while (readingFlags) { switch (j->getnameid()) { case makeNameid("id"): chatid = j->gethandle(MegaClient::CHATHANDLE); break; case makeNameid("f"): flags = byte(j->getint()); break; case EOO: if (chatid != UNDEF && flags != 0xFF) { textchat_map::iterator it = chats.find(chatid); if (it == chats.end()) { string chatidB64; string tmp((const char*)&chatid, sizeof(chatid)); Base64::btoa(tmp, chatidB64); LOG_err << "Received flags for unknown chatid: " << chatidB64.c_str(); } else { it->second->setFlags(flags); assert(!readingPublicChats || !it->second->getUnifiedKey().empty()); } } else { LOG_err << "Failed to parse chat flags"; } readingFlags = false; break; default: if (!j->storeobject()) { LOG_err << "Failed to parse chat flags"; readingFlags = false; } break; } } j->leaveobject(); } j->leavearray(); break; } case EOO: done = true; j->leaveobject(); break; default: if (!j->storeobject()) { return; } } } } } void MegaClient::procmcna(JSON *j) { if (j->enterarray()) { while(j->enterobject()) // while there are more nodes to read... { handle chatid = UNDEF; handle h = UNDEF; handle uh = UNDEF; bool readingNode = true; while(readingNode) // read the attached node information { switch (j->getnameid()) { case makeNameid("id"): chatid = j->gethandle(MegaClient::CHATHANDLE); break; case makeNameid("n"): h = j->gethandle(MegaClient::NODEHANDLE); break; case name_id::u: uh = j->gethandle(MegaClient::USERHANDLE); break; case EOO: if (chatid != UNDEF && h != UNDEF && uh != UNDEF) { textchat_map::iterator it = chats.find(chatid); if (it == chats.end()) { LOG_err << "Unknown chat for user/node access to attachment"; } else { it->second->setNodeUserAccess(h, uh); } } else { LOG_err << "Failed to parse attached node information"; } readingNode = false; break; default: if (!j->storeobject()) { LOG_err << "Failed to parse attached node information"; readingNode = false; } break; } } j->leaveobject(); } j->leavearray(); } } // process mcsm array at fetchnodes void MegaClient::procmcsm(JSON *j) { std::vector> schedMeetings; if (j && j->enterarray()) { error e = parseScheduledMeetings(schedMeetings, false, j); if (e != API_OK) { LOG_err << "Failed to parse 'mcsm' array at fetchnodes. Error: " << e; } if (!j->leavearray()) { LOG_err << "Failed to leave array at procmcsm. Error: " << API_EINTERNAL; } if (e != API_OK) { return; } } for (auto &sm: schedMeetings) { textchat_map::iterator it = chats.find(sm->chatid()); if (it == chats.end()) { assert(false); LOG_err << "Unknown chatid [" << Base64Str(sm->chatid()) << "] received on mcsm"; continue; } // add scheduled meeting TextChat* chat = it->second; chat->addOrUpdateSchedMeeting(std::move(sm), false); // don't need to notify, as chats are also provided to karere } } #endif // add node to vector, return position, deduplicate unsigned MegaClient::addnode(sharedNode_vector* v, std::shared_ptr n) const { // linear search not particularly scalable, but fine for the relatively // small real-world requests for (unsigned i = unsigned(v->size()); i--; ) { if ((*v)[i] == n) { return i; } } v->push_back(n); return unsigned(v->size() - 1); } void MegaClient::getaccountdetails(std::shared_ptr ad, bool storage, bool transfer, bool pro, bool transactions, bool purchases, bool sessions, int source) { if (storage || transfer || pro) { queueCommand(new CommandGetUserQuota(this, ad, storage, transfer, pro, source)); } if (transactions) { queueCommand(new CommandGetUserTransactions(this, ad)); } if (purchases) { queueCommand(new CommandGetUserPurchases(this, ad)); } if (sessions) { queueCommand(new CommandGetUserSessions(this, ad)); } } void MegaClient::getstorageinfo(std::function completion) { assert(completion); // Haven't cached any info from the cloud. if (mLastKnownCapacity < 0) { auto wrapper = [this](std::function& completion, const std::shared_ptr details, Error result) { StorageInfo info; // Latch the account's capacity. if (result == API_OK) { assert(details); info.mCapacity = details->storage_max; info.mUsed = details->storage_used; if (info.mCapacity >= info.mUsed) { info.mAvailable = info.mCapacity - info.mUsed; } mLastKnownCapacity = info.mCapacity; } // Forward result to user callback. completion(info, result); }; // wrapper std::function, Error)> callback; callback = std::bind(std::move(wrapper), std::move(completion), std::placeholders::_1, std::placeholders::_2); return queueCommand(new CommandGetUserQuota(this, std::make_shared(), true /* storage */, false /* transfer */, false /* pro */, -1 /* source */, std::move(callback))); } // Ask the NM for our root node's storage info. auto counter = mNodeManager.getCounterOfRootNodes(); StorageInfo info; // Populate info based on counter and cached state. info.mUsed = counter.storage + counter.versionStorage; info.mCapacity = mLastKnownCapacity; if (info.mCapacity >= info.mUsed) info.mAvailable = info.mCapacity - info.mUsed; // Forward info to caller. completion(info, API_OK); } void MegaClient::querytransferquota(m_off_t size) { queueCommand(new CommandQueryTransferQuota(this, size)); } // export node link error MegaClient::exportnode( std::shared_ptr n, int del, m_time_t ets, bool writable, bool megaHosted, int tag, std::function completion) { if (n->plink && !del && !n->plink->takendown && (ets == n->plink->ets) && !n->plink->isExpired() && ( (writable && n->plink->mAuthKey.size()) || (!writable && !n->plink->mAuthKey.size()) ) ) { if (ststatus == STORAGE_PAYWALL) { LOG_warn << "Rejecting public link request when ODQ paywall"; return API_EPAYWALL; } restag = tag; completion(API_OK, n->nodehandle, n->plink->ph, {}); return API_OK; } if (!checkaccess(n.get(), OWNER)) { return API_EACCESS; } // the link associated to the S4 container cannot be deleted if S4 is enabled if (mIsS4Enabled && n->nodeHandle().eq(mS4Container)) { return API_EACCESS; } // export node switch (n->type) { case FILENODE: requestPublicLink(n.get(), del, ets, writable, false, tag, std::move(completion)); break; case FOLDERNODE: if (del) { // deletion of outgoing share also deletes the link automatically // need to first remove the link and then the share NodeHandle h = n->nodeHandle(); requestPublicLink( n.get(), del, ets, writable, false, tag, [this, completion, writable, tag, h](Error e, handle, handle, string&&) { std::shared_ptr n = nodeByHandle(h); if (e || !n) { completion(e, UNDEF, UNDEF, {}); } else { setshare(n, NULL, ACCESS_UNKNOWN, writable, nullptr, tag, [completion](Error e, bool) { completion(e, UNDEF, UNDEF, {}); }); } }); } else { // Exporting folder - need to create share first // If share creation is successful, the share completion function calls requestPublicLink handle h = n->nodehandle; setshare(n, NULL, writable ? FULL : RDONLY, writable, nullptr, tag, [this, megaHosted, h, ets, tag, writable, completion](Error e, bool) { if (e) { completion(e, UNDEF, UNDEF, {}); } else if (std::shared_ptr node = nodebyhandle(h)) { requestPublicLink(node.get(), false, ets, writable, megaHosted, tag, completion); } else { completion(API_ENOENT, UNDEF, UNDEF, {}); } }); } break; default: return API_EACCESS; } return API_OK; } void MegaClient::requestPublicLink(Node* n, int del, m_time_t ets, bool writable, bool megaHosted, int tag, CommandSetPH::CompletionType f) { queueCommand(new CommandSetPH(this, n, del, ets, writable, megaHosted, tag, std::move(f))); } // open exported file link // formats supported: ...#!publichandle!key, publichandle!key or file/[][#] void MegaClient::openfilelink(handle ph, const byte* fileKey) { queueCommand(new CommandGetPH(this, ph, fileKey, 1)); // check link } /* Format of password-protected links * * algorithm = 1 byte - A byte to identify which algorithm was used (for future upgradability), initially is set to 0 * file/folder = 1 byte - A byte to identify if the link is a file or folder link (0 = folder, 1 = file) * public handle = 6 bytes - The public folder/file handle * salt = 32 bytes - A 256 bit randomly generated salt * encrypted key = 16 or 32 bytes - The encrypted actual folder or file key * MAC tag = 32 bytes - The MAC of all the previous data to ensure integrity of the link i.e. calculated as: * HMAC-SHA256(MAC key, (algorithm || file/folder || public handle || salt || encrypted key)) */ error MegaClient::decryptlink(const char *link, const char *pwd, string* decryptedLink) { if (!pwd || !link) { LOG_err << "Empty link or empty password to decrypt link"; return API_EARGS; } const char* ptr = NULL; const char* end = NULL; ptr = strstr(link, "#P!"); if (!ptr) { LOG_err << "This link is not password protected"; return API_EARGS; } ptr += 3; // Decode the link int linkLen = 1 + 1 + 6 + 32 + 32 + 32; // maximum size in binary, for file links string linkBin; linkBin.resize(static_cast(linkLen)); linkLen = Base64::atob(ptr, (byte*)linkBin.data(), linkLen); ptr = (char *)linkBin.data(); end = ptr + linkLen; if ((ptr + 2) >= end) { LOG_err << "This link is too short"; return API_EINCOMPLETE; } int algorithm = *ptr++; if (algorithm != 1 && algorithm != 2) { LOG_err << "The algorithm used to encrypt this link is not supported"; return API_EINTERNAL; } int isFolder = !(*ptr++); if (isFolder > 1) { LOG_err << "This link doesn't reference any folder or file"; return API_EARGS; } size_t encKeyLen = isFolder ? FOLDERNODEKEYLENGTH : FILENODEKEYLENGTH; if ((ptr + 38 + encKeyLen + 32) > end) { LOG_err << "This link is too short"; return API_EINCOMPLETE; } handle ph = MemAccess::get(ptr); ptr += 6; string salt(ptr, 32); ptr += salt.size(); string encKey; encKey.resize(encKeyLen); memcpy((byte *)encKey.data(), ptr, encKeyLen); ptr += encKeyLen; byte hmac[32]; memcpy((char*)&hmac, ptr, 32); ptr += 32; // Derive MAC key with salt+pwd vector derivedKey = deriveKey(pwd, salt, 64); byte hmacComputed[32]; if (algorithm == 1) { // verify HMAC with macKey(alg, f/F, ph, salt, encKey) HMACSHA256 hmacsha256((byte *)linkBin.data(), 40 + encKeyLen); hmacsha256.add(derivedKey.data() + 32, 32); hmacsha256.get(hmacComputed); } else // algorithm == 2 (fix legacy Webclient bug: swap data and key) { // verify HMAC with macKey(alg, f/F, ph, salt, encKey) HMACSHA256 hmacsha256(derivedKey.data() + 32, 32); hmacsha256.add((byte *)linkBin.data(), unsigned(40 + encKeyLen)); hmacsha256.get(hmacComputed); } if (memcmp(hmac, hmacComputed, 32)) { LOG_err << "HMAC verification failed. Possible tampered or corrupted link"; return API_EKEY; } if (decryptedLink) { // Decrypt encKey using X-OR with first 16/32 bytes of derivedKey byte keyBuffer[FILENODEKEYLENGTH]; for (unsigned int i = 0; i < encKeyLen; i++) { keyBuffer[i] = static_cast(encKey[i] ^ derivedKey[i]); } Base64Str keyStr(keyBuffer); decryptedLink->assign(publicLinkURL(mNewLinkFormat, isFolder ? TypeOfLink::FOLDER : TypeOfLink::FILE, ph, keyStr)); } return API_OK; } error MegaClient::encryptlink(const char *link, const char *pwd, string *encryptedLink) { if (!pwd || !link || !encryptedLink) { LOG_err << "Empty link or empty password to encrypt link"; return API_EARGS; } if(strstr(link, "collection/")) { LOG_err << "Attempting to encrypt a non-folder, non-file link"; assert(false); return API_EARGS; } bool isFolder = (strstr(link, "#F!") || strstr(link, "folder/")); handle ph; size_t linkKeySize = isFolder ? FOLDERNODEKEYLENGTH : FILENODEKEYLENGTH; std::unique_ptr linkKey(new byte[linkKeySize]); error e = parsepubliclink(link, ph, linkKey.get(), (isFolder ? TypeOfLink::FOLDER : TypeOfLink::FILE)); if (e == API_OK) { // Derive MAC key with salt+pwd string salt(32u, '\0'); rng.genblock((byte*)salt.data(), salt.size()); vector derivedKey = deriveKey(pwd, salt, 64); // Prepare encryption key string encKey; encKey.resize(linkKeySize); for (unsigned int i = 0; i < linkKeySize; i++) { encKey[i] = static_cast(derivedKey[i] ^ linkKey[i]); } // Preapare payload to derive encryption key byte algorithm = 2; byte type = isFolder ? 0 : 1; string payload; payload.append((char*) &algorithm, sizeof algorithm); payload.append((char*) &type, sizeof type); payload.append((char*) &ph, NODEHANDLE); payload.append(salt); payload.append(encKey); // Prepare HMAC byte hmac[32]; if (algorithm == 1) { HMACSHA256 hmacsha256((byte *)payload.data(), payload.size()); hmacsha256.add(derivedKey.data() + 32, 32); hmacsha256.get(hmac); } else if (algorithm == 2) // fix legacy Webclient bug: swap data and key { HMACSHA256 hmacsha256(derivedKey.data() + 32, 32); hmacsha256.add((byte *)payload.data(), unsigned(payload.size())); hmacsha256.get(hmac); } else { LOG_err << "Invalid algorithm to encrypt link"; return API_EINTERNAL; } // Prepare encrypted link string encLinkBytes; encLinkBytes.append((char*) &algorithm, sizeof algorithm); encLinkBytes.append((char*) &type, sizeof type); encLinkBytes.append((char*) &ph, NODEHANDLE); encLinkBytes.append(salt); encLinkBytes.append(encKey); encLinkBytes.append((char*) hmac, sizeof hmac); string encLink; Base64::btoa(encLinkBytes, encLink); encryptedLink->clear(); encryptedLink->append(MegaClient::getMegaURL()); encryptedLink->append("/#P!"); encryptedLink->append(encLink); if (isFolder) { sendevent(99459, "Public folder link encrypted to a password"); } else { sendevent(99460, "Public file link encrypted to a password"); } } return e; } sessiontype_t MegaClient::loggedin() { if (ISUNDEF(me)) { return NOTLOGGEDIN; } if (ephemeralSessionPlusPlus) { return EPHEMERALACCOUNTPLUSPLUS; } if (ephemeralSession) { return EPHEMERALACCOUNT; } if (!mPrivateRsaKey.isvalid(AsymmCipher::PRIVKEY)) { return CONFIRMEDACCOUNT; } return FULLACCOUNT; } void MegaClient::reportLoggedInChanges() { auto currState = loggedin(); string currentEmail = ownuser() ? ownuser()->email : ""; if (mLastLoggedInReportedState != currState || mLastLoggedInMeHandle != me || (mLastLoggedInMyEmail != currentEmail)) { mLastLoggedInReportedState = currState; mLastLoggedInMeHandle = me; mLastLoggedInMyEmail = currentEmail; app->loggedInStateChanged(currState, me, currentEmail); } } void MegaClient::whyamiblocked() { // make sure the smsve flag is up to date when we get the response getmiscflags(); // queue the actual request queueCommand(new CommandWhyAmIblocked(this)); } void MegaClient::setBlocked(bool value) { mBlocked = value; mBlockedSet = true; mCachedStatus.addOrUpdate(CacheableStatus::STATUS_BLOCKED, mBlocked); } void MegaClient::block(bool fromServerClientResponse) { LOG_verbose << "Blocking MegaClient, fromServerClientResponse: " << fromServerClientResponse; setBlocked(true); #ifdef ENABLE_SYNC syncs.disableSyncs(ACCOUNT_BLOCKED, false, true); #endif } void MegaClient::unblock() { LOG_verbose << "Unblocking MegaClient"; setBlocked(false); } error MegaClient::changepw(const char* password, const char *pin) { User* u; if (!loggedin()) { return API_EACCESS; } u = finduser(me); if (!u) { return API_EACCESS; } // Confirm account version, not rely on cached values string spwd = password ? password : string(); std::optional spin; if (pin) { spin.emplace(pin); } getuserdata(reqtag, [this, u, spwd, spin](string* /*name*/, string* /*pubk*/, string* /*privk*/, std::vector&&, error e) { if (e != API_OK) { app->changepw_result(e); return; } switch (accountversion) { case 1: e = changePasswordV1(u, spwd.c_str(), spin ? spin->c_str() : nullptr); break; default: LOG_warn << "Unexpected account version v" << accountversion << " processed as v2"; [[fallthrough]]; case 2: e = changePasswordV2(spwd.c_str(), spin ? spin->c_str() : nullptr); break; } if (e != API_OK) { app->changepw_result(e); } }); return API_OK; } error MegaClient::changePasswordV1(User* u, const char* password, const char* pin) { error e; byte newpwkey[SymmCipher::KEYLENGTH]; e = pw_key(password, newpwkey); if (e) { return e; } byte newkey[SymmCipher::KEYLENGTH]; SymmCipher pwcipher; memcpy(newkey, key.key, sizeof newkey); pwcipher.setkey(newpwkey); pwcipher.ecb_encrypt(newkey); string email = u->email; uint64_t stringhash = stringhash64(&email, &pwcipher); queueCommand(new CommandSetMasterKey(this, newkey, (const byte*)&stringhash, sizeof(stringhash), NULL, pin)); return API_OK; } error MegaClient::changePasswordV2(const char* password, const char* pin) { vector clientRandomValue; vector encmasterkey; string hashedauthkey; string salt; fillCypheredAccountDataV2(password, clientRandomValue, encmasterkey, hashedauthkey, salt); // Pass the salt and apply to this->accountsalt if the command succeed to allow posterior checks of the password without getting it from the server queueCommand(new CommandSetMasterKey(this, encmasterkey.data(), reinterpret_cast(hashedauthkey.data()), SymmCipher::KEYLENGTH, clientRandomValue.data(), pin, &salt)); return API_OK; } void MegaClient::fillCypheredAccountDataV2(const char* password, vector& clientRandomValue, vector& encmasterkey, string& hashedauthkey, string& salt) { clientRandomValue.resize(SymmCipher::KEYLENGTH, 0); rng.genblock(clientRandomValue.data(), clientRandomValue.size()); string buffer = "mega.nz"; buffer.resize(200, 'P'); buffer.append(reinterpret_cast(clientRandomValue.data()), clientRandomValue.size()); HashSHA256 hasher; hasher.add(reinterpret_cast(buffer.data()), unsigned(buffer.size())); hasher.get(&salt); vector derivedKey = deriveKey(password, salt, 2 * SymmCipher::KEYLENGTH); SymmCipher cipher; cipher.setkey(derivedKey.data()); encmasterkey.resize(SymmCipher::KEYLENGTH, 0); cipher.ecb_encrypt(key.key, encmasterkey.data()); const byte *authkey = derivedKey.data() + SymmCipher::KEYLENGTH; hasher.add(authkey, SymmCipher::KEYLENGTH); hasher.get(&hashedauthkey); hashedauthkey.resize(SymmCipher::KEYLENGTH); } vector MegaClient::deriveKey(const char* password, const string& salt, size_t derivedKeySize) { vector derivedKey(derivedKeySize); CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; pbkdf2.DeriveKey(derivedKey.data(), derivedKey.size(), 0, (const byte*)password, strlen(password), (const byte*)salt.data(), salt.size(), 100000); return derivedKey; } // create ephemeral session void MegaClient::createephemeral() { ephemeralSession = true; byte keybuf[SymmCipher::KEYLENGTH]; byte pwbuf[SymmCipher::KEYLENGTH]; byte sscbuf[2 * SymmCipher::KEYLENGTH]; rng.genblock(keybuf, sizeof keybuf); rng.genblock(pwbuf, sizeof pwbuf); rng.genblock(sscbuf, sizeof sscbuf); key.setkey(keybuf); key.ecb_encrypt(sscbuf, sscbuf + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); key.setkey(pwbuf); key.ecb_encrypt(keybuf); queueCommand(new CommandCreateEphemeralSession(this, keybuf, pwbuf, sscbuf)); } void MegaClient::resumeephemeral(handle uh, const byte* pw, int ctag) { ephemeralSession = true; queueCommand(new CommandResumeEphemeralSession(this, uh, pw, ctag ? ctag : reqtag)); } void MegaClient::resumeephemeralPlusPlus(const std::string& session) { ephemeralSessionPlusPlus = true; // E++ cannot resume sessions as regular ephemeral accounts. The acccount's creation // does not require a password, so the session token would be undecryptable. That's // the reason to use a regular session ID and perform a regular login, instead of // calling here resumeephemeral() directly. login(session); } void MegaClient::cancelsignup() { queueCommand(new CommandCancelSignup(this)); } void MegaClient::createephemeralPlusPlus() { ephemeralSessionPlusPlus = true; createephemeral(); } string MegaClient::sendsignuplink2(const char *email, const char *password, const char* name, int ctag) { byte clientrandomvalue[SymmCipher::KEYLENGTH]; rng.genblock(clientrandomvalue, sizeof(clientrandomvalue)); string salt; HashSHA256 hasher; string buffer = "mega.nz"; buffer.resize(200, 'P'); buffer.append((char *)clientrandomvalue, sizeof(clientrandomvalue)); hasher.add((const byte*)buffer.data(), unsigned(buffer.size())); hasher.get(&salt); vector derivedKey = deriveKey(password, salt, 2 * SymmCipher::KEYLENGTH); byte encmasterkey[SymmCipher::KEYLENGTH]; SymmCipher cipher; cipher.setkey(derivedKey.data()); cipher.ecb_encrypt(key.key, encmasterkey); string hashedauthkey; const byte *authkey = derivedKey.data() + SymmCipher::KEYLENGTH; hasher.add(authkey, SymmCipher::KEYLENGTH); hasher.get(&hashedauthkey); hashedauthkey.resize(SymmCipher::KEYLENGTH); accountversion = 2; accountsalt = salt; queueCommand(new CommandSendSignupLink2(this, email, name, clientrandomvalue, encmasterkey, (byte*)hashedauthkey.data(), ctag ? ctag : reqtag)); return string((const char*)derivedKey.data(), derivedKey.size()); } void MegaClient::resendsignuplink2(const char *email, const char *name) { queueCommand(new CommandSendSignupLink2(this, email, name)); } void MegaClient::confirmsignuplink2(const byte *code, unsigned len) { queueCommand(new CommandConfirmSignupLink2(this, code, len)); } // generate and configure encrypted private key, plaintext public key void MegaClient::setkeypair() { CryptoPP::Integer newPubKey[AsymmCipher::PUBKEY]; string privks, pubks; mPrivateRsaKey.genkeypair(rng, newPubKey, 2048); AsymmCipher::serializeintarray(newPubKey, AsymmCipher::PUBKEY, &pubks); AsymmCipher::serializeintarray(mPrivateRsaKey.getKey(), AsymmCipher::PRIVKEY, &privks); // Initialize pubkey AsymmCipher. mPublicRsaKey.setkey(AsymmCipher::PUBKEY, reinterpret_cast(pubks.data()), static_cast(pubks.size())); // add random padding and ECB-encrypt with master key unsigned t = unsigned(privks.size()); privks.resize((t + SymmCipher::BLOCKSIZE - 1) & ~(static_cast(SymmCipher::BLOCKSIZE) - 1)); rng.genblock((byte*)(privks.data() + t), privks.size() - t); // Initialize serialized private RSA key variable. // After adding the random padding, to match how it is initialized later from the API response. mSerializedPrivateRsaKey = Base64::btoa(privks); key.ecb_encrypt((byte*)privks.data(), (byte*)privks.data(), privks.size()); queueCommand(new CommandSetKeyPair(this, (const byte*)privks.data(), unsigned(privks.size()), (const byte*)pubks.data(), unsigned(pubks.size()))); mKeyManager.setPostRegistration(true); } bool MegaClient::fetchsc(DbTable* stateCacheTable) { uint32_t id; string data; std::shared_ptr n; User* u; PendingContactRequest* pcr; LOG_info << "Loading session from local cache"; stateCacheTable->rewind(); bool hasNext = stateCacheTable->next(&id, &data, &key); WAIT_CLASS::bumpds(); fnstats.timeToFirstByte = Waiter::ds - fnstats.startTime; bool isDbUpgraded = false; // true when legacy DB is migrated to NOD's DB schema std::map >> delayedParents; while (hasNext) { switch (id & (DbTable::IDSPACING - 1)) { case CACHEDSCSN: if (data.size() != sizeof cachedscsn) { return false; } break; case CACHEDNODE: if ((n = mNodeManager.getNodeFromBlob(&data))) { // When all nodes are loaded we force a commit isDbUpgraded = true; bool rootNode = n->type == ROOTNODE || n->type == RUBBISHNODE || n->type == VAULTNODE; if (rootNode) { mNodeManager.setrootnode(n); } else if (n->parent == nullptr) { // nodes in 'statecache' are not ordered by parent-child // -> we might load nodes whose parents are not loaded yet delayedParents[n->parentHandle()].push_back(n); } stateCacheTable->del(id); // delete record from old DB table 'statecache' } else { LOG_err << "Failed - node record read error"; return false; } break; case CACHEDPCR: pcr = PendingContactRequest::unserialize(&data); if (pcr) { mappcr(pcr->id, unique_ptr(pcr)); pcr->dbid = id; } else { LOG_err << "Failed - pcr record read error"; return false; } break; case CACHEDUSER: u = User::unserialize(this, &data); if (u) { u->dbid = id; } else { LOG_err << "Failed - user record read error"; return false; } break; case CACHEDALERT: { if (loggedIntoFolder()) { stateCacheTable->del(id); // delete record from old DB table 'statecache' } else if (!useralerts.unserializeAlert(&data, id)) { LOG_err << "Failed - user notification read error"; // don't break execution, just ignore it } break; } case CACHEDCHAT: #ifdef ENABLE_CHAT { TextChat *chat; chat = TextChat::unserialize(this, &data); if (chat) { chat->dbid = id; } else { LOG_err << "Failed - chat record read error"; return false; } } #endif break; case CACHEDDBSTATE: { mScDbStateRecord = ScDbStateRecord::unserialize(data); mScDbStateRecord.dbid = id; LOG_debug << "reloaded seqtag from db: " << mScDbStateRecord.seqTag; } break; case CACHEDSET: { if (!fetchscset(&data, id)) { return false; } break; } case CACHEDSETELEMENT: { if (!fetchscsetelement(&data, id)) { return false; } break; } } hasNext = stateCacheTable->next(&id, &data, &key); } LOG_debug << "Max dbId after resume session: " << id; if (isDbUpgraded) // nodes loaded during migration from `statecache` to `nodes` table and kept in RAM { LOG_info << "Upgrading cache to NOD"; // call setparent() for the nodes whose parent was not available upon unserialization for (auto& it : delayedParents) { std::shared_ptr parent = mNodeManager.getNodeByHandle(it.first); for (auto& child : it.second) { // In DB migration we have to calculate counters as they aren't calculated previously child->setparent(parent, true); } } // now that Users and PCRs are loaded, need to mergenewshare() mergenewshares(0, true); // finally write nodes in DB mNodeManager.dumpNodes(); // and force commit, since old DB has been upgraded to new schema for NOD LOG_debug << "DB transaction COMMIT (sessionid: " << string(sessionid, sizeof(sessionid)) << ")"; stateCacheTable->commit(); stateCacheTable->begin(); } else { // nodes are not loaded, proceed to load them only after Users and PCRs are loaded, // since Node::unserialize() will call mergenewshare(), and the latter requires // Users and PCRs to be available if (!mNodeManager.loadNodes()) { return false; } } WAIT_CLASS::bumpds(); fnstats.timeToLastByte = Waiter::ds - fnstats.startTime; // user alerts are restored from DB upon session resumption. No need to send the sc50 to catchup, it will // generate new alerts from action packets as usual, once the session is up and running useralerts.catchupdone = true; if (!mScDbStateRecord.seqTag.empty()) { app->sequencetag_update(mScDbStateRecord.seqTag); } return true; } bool MegaClient::fetchStatusTable(DbTable* table) { uint32_t id; string data; LOG_info << "Loading session state from local cache"; table->rewind(); bool hasNext = table->next(&id, &data, &key); while (hasNext) { switch (id & (15)) { case CACHEDSTATUS: { auto status = CacheableStatus::unserialize(this, data); if (status) { status->dbid = id; } else { LOG_err << "Failed - status record read error"; return false; } break; } } hasNext = table->next(&id, &data, &key); } return true; } void MegaClient::purgeOrphanTransfers(bool remove) { bool purgeOrphanTransfers = statecurrent; unsigned purgeCount = 0; unsigned notPurged = 0; for (int d = GET; d == GET || d == PUT; d += PUT - GET) { TransferDbCommitter committer(tctable); while (multi_cachedtransfers[d].size()) { transfer_multimap::iterator it = multi_cachedtransfers[d].begin(); Transfer *transfer = it->second; if (remove || (purgeOrphanTransfers && (m_time() - transfer->lastaccesstime) >= Transfer::TEMPURL_TIMEOUT_TS)) { if (purgeCount == 0) { LOG_warn << "Purging orphan transfers"; } purgeCount ++; transfer->finished = true; } else { notPurged ++; } // if the transfer is still in cachedtransfers, then the app never knew about it // during this running instance, so no need to call the app back here. //app->transfer_removed(transfer); delete transfer; multi_cachedtransfers[d].erase(it); } } if (purgeCount > 0 || notPurged > 0) { LOG_warn << "Purged " << purgeCount << " orphan transfers, " << notPurged << " non-referenced cached transfers remain"; } } void MegaClient::closetc(bool remove) { pendingtcids.clear(); cachedfiles.clear(); cachedfilesdbids.clear(); if (remove && tctable) { tctable->remove(); } tctable.reset(); } void MegaClient::enabletransferresumption() { if (!dbaccess || tctable) { return; } string dbname = getTransferDBName(); if (sid.size() >= SIDLEN || loggedIntoFolder()) { tckey = key; } else { string lok; Hash hash; hash.add((const byte *)dbname.c_str(), unsigned(dbname.size() + 1)); hash.get(&lok); tckey.setkey((const byte*)lok.data()); } dbname.insert(0, "transfers_"); tctable.reset(dbaccess->open(rng, *fsaccess, dbname, DB_OPEN_FLAG_RECYCLE | DB_OPEN_FLAG_TRANSACTED, [this](DBError error) { handleDbError(error); })); if (!tctable) { return; } // TODO: After SDK-5196, not logged-in clients will persist transfers for authorized // nodes (from folder links, but using the main MegaApi instance) in a default DB cache. // However, upon login into an account, previously cached transfers are discarded. // If we want to resume those transfers after logging in on the main instance, // we should read them from the default cache and resume them. uint32_t id; string data; Transfer* t; size_t cachedTransfersLoaded = 0; size_t cachedFilesLoaded = 0; LOG_info << "Loading transfers from local cache"; tctable->rewind(); { TransferDbCommitter committer(tctable); // needed in case of tctable->del() while (tctable->next(&id, &data, &tckey)) { switch (id & 15) { case CACHEDTRANSFER: t = Transfer::unserialize(this, &data, multi_cachedtransfers); if (t) { t->dbid = id; if (t->priority > transferlist.currentpriority) { transferlist.currentpriority = t->priority; } cachedTransfersLoaded += 1; } else { tctable->del(id); LOG_err << "Failed - transfer record read error, or duplicate"; } break; case CACHEDFILE: cachedfiles.push_back(data); cachedfilesdbids.push_back(id); cachedFilesLoaded += 1; break; } } } LOG_debug << "Cached transfers loaded: " << cachedTransfersLoaded; LOG_debug << "Cached files loaded: " << cachedFilesLoaded; LOG_debug << "Cached transfer PUT count: " << multi_cachedtransfers[PUT].size(); LOG_debug << "Cached transfer GET count: " << multi_cachedtransfers[GET].size(); // if we are logged in but the filesystem is not current yet // postpone the resumption until the filesystem is updated if ((!sid.size() && !loggedIntoFolder()) || statecurrent) { resumeTransferFromDB(); } } void MegaClient::disabletransferresumption() { if (!dbaccess) { return; } purgeOrphanTransfers(true); closetc(true); std::string dbname = getTransferDBName(); dbname.insert(0, "transfers_"); tctable.reset(dbaccess->open(rng, *fsaccess, dbname, DB_OPEN_FLAG_RECYCLE | DB_OPEN_FLAG_TRANSACTED, [this](DBError error) { handleDbError(error); })); if (!tctable) { return; } purgeOrphanTransfers(true); closetc(true); } void MegaClient::resumeTransfersForNotLoggedInInstance() { if (loggedin() != NOTLOGGEDIN) { return; } string dbname = getTransferDBName(); dbname.insert(0, "transfers_"); // Only call enableTransferresumption if db default exist // Avoid create unnecesary files if (LocalPath path = dbaccess->databasePath(*fsaccess.get(), dbname, DbAccess::DB_VERSION); fsaccess->newfileaccess()->fopen(path, FSLogging::noLogging)) { enabletransferresumption(); } } string MegaClient::getTransferDBName() { string dbname; if (sid.size() >= SIDLEN) { dbname.resize((SIDLEN - sizeof key.key) * 4 / 3 + 3); dbname.resize(Base64::btoa((const byte*)sid.data() + sizeof key.key, SIDLEN - sizeof key.key, (char*)dbname.c_str())); } else if (loggedIntoFolder()) { dbname.resize(static_cast(NODEHANDLE * 4 / 3 + 3)); dbname.resize(Base64::btoa((const byte*)&mFolderLink.mPublicHandle, NODEHANDLE, (char*)dbname.c_str())); } else { dbname = "default"; } return dbname; } void MegaClient::resumeTransferFromDB() { TransferDbCommitter committer(tctable); for (unsigned int i = 0; i < cachedfiles.size(); i++) { direction_t type = NONE; MegaApp::FileResumeData data; app->file_resume(&cachedfiles.at(i), &type, cachedfilesdbids.at(i), data); File* file = data.file; if (!file || (type != GET && type != PUT)) { tctable->del(cachedfilesdbids.at(i)); continue; } bool requiredStatxfer{true}; const int tag = nextreqtag(); if (!data.sameNodeHandle.isUndef()) { assert(type == PUT); auto [start, end] = multi_cachedtransfers[type].equal_range(file); Transfer* t{nullptr}; for (auto& it = start; it != end;) { requiredStatxfer = false; t = it->second; assert(t->localfilename == file->getLocalname()); it = multi_cachedtransfers[type].erase(it); t->tag = tag; file->tag = tag; file->file_it = t->files.insert(t->files.end(), file); file->transfer = t; auto parent = mNodeManager.getNodeByHandle(data.parentHandle); if (!parent) { LOG_err << "Error resuming transfer via copy remote - No parent"; // file is auto removed t->removeTransferFile(API_ENOENT, file, &committer); t->removeAndDeleteSelf(transferstate_t::TRANSFERSTATE_FAILED); continue; } auto remoteCopyNode = mNodeManager.getNodeByHandle(data.sameNodeHandle); // It should be valid, obtained in file_resume assert(remoteCopyNode); transferRemoteCopy(file, remoteCopyNode, data.remoteName, parent, tag, std::nullopt, data.inboxTarget); break; } } // Transfer download GET // Transfer upload PUT // - Upload file // - The node with fingerprint match was been removed, initiating a new transfer if (requiredStatxfer) { error e; // TODO: should we have serialized these flags and restored them? if (!startxfer(type, file, committer, false, false, false, UseLocalVersioningFlag, &e, tag)) { LOG_err << "Error resuming transfer - launching new transfer"; tctable->del(cachedfilesdbids.at(i)); auto t = file->transfer; // file is auto removed if (t) { t->removeTransferFile(e, file, &committer); t->removeAndDeleteSelf(transferstate_t::TRANSFERSTATE_FAILED); } else { filecachedel(file, &committer); app->file_removed(file, e); file->terminated(e); } continue; } } } purgeOrphanTransfers(true); cachedfiles.clear(); cachedfilesdbids.clear(); } void MegaClient::handleDbError(DBError error) { // Return early for repeated errors if (mLastDBErrorDetected == error) { return; } // Remember the new error mLastDBErrorDetected = error; std::string reason; switch (error) { case DBError::DB_ERROR_UNKNOWN: reason = "DB error. unknown error"; sendevent(800000, reason.c_str(), 0); break; case DBError::DB_ERROR: reason = "DB error. Generic error"; sendevent(800001, reason.c_str(), 0); break; case DBError::DB_ERROR_INTERNAL: reason = "DB error. Internal logic error"; sendevent(800002, reason.c_str(), 0); break; case DBError::DB_ERROR_PERM: reason = "DB error. Access permission denied"; sendevent(800003, reason.c_str(), 0); break; case DBError::DB_ERROR_ABORT: reason = "DB error. Callback routine requested an abort"; sendevent(800004, reason.c_str(), 0); break; case DBError::DB_ERROR_BUSY: reason = "DB error. File is locked"; sendevent(800005, reason.c_str(), 0); break; case DBError::DB_ERROR_LOCKED: reason = "DB error. Table is locked"; sendevent(800006, reason.c_str(), 0); break; case DBError::DB_ERROR_NOMEM: reason = "DB error. Memory error"; sendevent(800007, reason.c_str(), 0); break; case DBError::DB_ERROR_READONLY: reason = "DB error. Error attempting to write a readonly database"; sendevent(800008, reason.c_str(), 0); break; case DBError::DB_ERROR_INTERRUPT: reason = "DB error. Operation interrupted"; sendevent(800009, reason.c_str(), 0); break; case DBError::DB_ERROR_IO: reason = "Writing in DB error"; sendevent(99467, reason.c_str(), 0); fatalError(ErrorReason::REASON_ERROR_DB_IO); return; case DBError::DB_ERROR_CORRUPT: reason = "DB file is corrupt"; sendevent(99497, reason.c_str(), 0); fatalError(ErrorReason::REASON_ERROR_DB_CORRUPT); return; case DBError::DB_ERROR_NOTFOUND: reason = "DB error. Unknown opcode"; sendevent(800010, reason.c_str(), 0); break; case DBError::DB_ERROR_FULL: reason = "DB error. Database is full"; sendevent(800011, reason.c_str(), 0); fatalError(ErrorReason::REASON_ERROR_DB_FULL); return; case DBError::DB_ERROR_CANTOPEN: reason = "DB error. Unable to open the database file"; sendevent(800012, reason.c_str(), 0); break; case DBError::DB_ERROR_PROTOCOL: reason = "DB error. Database lock protocol error"; sendevent(800013, reason.c_str(), 0); break; case DBError::DB_ERROR_EMPTY: reason = "DB error. Empty database"; sendevent(800014, reason.c_str(), 0); break; case DBError::DB_ERROR_SCHEMA: reason = "DB error. Database schema changed"; sendevent(800015, reason.c_str(), 0); break; case DBError::DB_ERROR_TOOBIG: reason = "DB error. Field too big"; sendevent(800016, reason.c_str(), 0); break; case DBError::DB_ERROR_CONSTRAINT: reason = "DB error. Abort due to constraint violation"; sendevent(800017, reason.c_str(), 0); break; case DBError::DB_ERROR_MISMATCH: reason = "DB error. Data type mismatch"; sendevent(800018, reason.c_str(), 0); break; case DBError::DB_ERROR_MISUSE: reason = "DB error. Library used incorrectly"; sendevent(800019, reason.c_str(), 0); break; case DBError::DB_ERROR_NOLFS: reason = "DB error. Unsupported features"; sendevent(800020, reason.c_str(), 0); break; case DBError::DB_ERROR_AUTH: reason = "DB error. Authorization denied"; sendevent(800021, reason.c_str(), 0); break; case DBError::DB_ERROR_FORMAT: reason = "DB error.  Not used"; sendevent(800022, reason.c_str(), 0); break; case DBError::DB_ERROR_RANGE: reason = "DB error. Out of range"; sendevent(800023, reason.c_str(), 0); break; case DBError::DB_ERROR_NOTADB: reason = "DB error. Database file wrong type"; sendevent(800024, reason.c_str(), 0); break; case DBError::DB_ERROR_INDEX_OVERFLOW: reason = "DB index overflow"; sendevent(99471, reason.c_str(), 0); fatalError(ErrorReason::REASON_ERROR_DB_INDEX_OVERFLOW); return; default: reason = "DB error: Unknown"; sendevent(800025, reason.c_str(), 0); break; } fatalError(ErrorReason::REASON_ERROR_DB_UNKNOWN); } void MegaClient::fatalError(ErrorReason errorReason) { // Return early for repeated errorReason if (mLastFatalErrorDetected == errorReason) { return; } // Remember the new errorReason mLastFatalErrorDetected = errorReason; // Disable sync. // Sync is not enabled in case of ErrorReason::REASON_ERROR_NO_JSCD or // ErrorReason::REASON_ERROR_REGENERATE_JSCD therefore no need to disable it. #ifdef ENABLE_SYNC if (errorReason != ErrorReason::REASON_ERROR_NO_JSCD && errorReason != ErrorReason::REASON_ERROR_REGENERATE_JSCD) { syncs.disableSyncs(FAILURE_ACCESSING_PERSISTENT_STORAGE, false, true); } #endif // Get Reason and send events std::string reason; switch (errorReason) { case ErrorReason::REASON_ERROR_DB_IO: reason = "Writing in DB error"; break; case ErrorReason::REASON_ERROR_UNSERIALIZE_NODE: reason = "Failed to unserialize node"; sendevent(99468, reason.c_str(), 0); break; case ErrorReason::REASON_ERROR_DB_FULL: reason = "Database is full"; break; case ErrorReason::REASON_ERROR_DB_INDEX_OVERFLOW: reason = "DB index overflow"; break; case mega::ErrorReason::REASON_ERROR_DB_CORRUPT: reason = "DB file is corrupt"; break; case ErrorReason::REASON_ERROR_DB_UNKNOWN: reason = "DB error: Unknown"; // map to generic unknown error, since apps don't need // specific handling of the DB unknown errors errorReason = REASON_ERROR_UNKNOWN; break; case ErrorReason::REASON_ERROR_NO_JSCD: reason = "Failed to get JSON SYNC configuration data"; sendevent(99488, reason.c_str(), 0); break; case ErrorReason::REASON_ERROR_REGENERATE_JSCD: reason = "Fix JSON SYNC configuration data"; sendevent(99489, reason.c_str(), 0); break; case ErrorReason::REASON_ERROR_UNKNOWN: reason = "Unknown fatal error"; sendevent(99496, reason.c_str(), 0); break; default: reason = "Unknown reason"; break; } // Notify app->notifyError(reason.c_str(), errorReason); // TODO: maybe it's worth a locallogout } bool MegaClient::accountShouldBeReloadedOrRestarted() const { return mLastFatalErrorDetected != REASON_ERROR_NO_ERROR; } void MegaClient::fetchnodes(bool nocache, bool loadSyncs, bool forceLoadFromServers) { if (fetchingnodes) { return; } WAIT_CLASS::bumpds(); fnstats.init(); if (sid.size() >= SIDLEN) { fnstats.type = FetchNodesStats::TYPE_ACCOUNT; } else if (loggedIntoFolder()) { fnstats.type = FetchNodesStats::TYPE_FOLDER; } opensctable(); if (sctable && cachedscsn == UNDEF) { LOG_debug << "Cachedscsn is UNDEF so we will not load the account database (and we are truncating it, for clean operation)"; sctable->truncate(); } std::unique_lock nodeTreeIsChanging(nodeTreeMutex); // only initial load from local cache if (!forceLoadFromServers && (loggedin() == FULLACCOUNT || loggedIntoFolder() || loggedin() == EPHEMERALACCOUNTPLUSPLUS) && !mNodeManager.ready() && !ISUNDEF(cachedscsn) && sctable && fetchsc(sctable.get())) { nodeTreeIsChanging.unlock(); // nodes loaded from db debugLogHeapUsage(); // Copy the current tag (the one from fetch nodes) so we can capture it in the lambda below. // ensuring no new request happens in between auto fetchnodesTag = reqtag; auto startCachedFetchNodes = [this, fetchnodesTag #ifdef ENABLE_SYNC // maybe_unused not allowed for lambda captures , loadSyncs #endif ](Error e) { restag = fetchnodesTag; if (e != API_OK) { // Notify and continue with or without JSCD attribute. LOG_warn << "Unable to retrieve JSCD attribute. Continue loading from cache."; } WAIT_CLASS::bumpds(); fnstats.mode = FetchNodesStats::MODE_DB; fnstats.cache = FetchNodesStats::API_NO_CACHE; fnstats.nodesCached = static_cast(mNodeManager.getNodeCount()); fnstats.timeToCached = Waiter::ds - fnstats.startTime; fnstats.timeToResult = fnstats.timeToCached; statecurrent = false; // allow sc requests to start scsn.setScsn(cachedscsn); LOG_info << "Session loaded from local cache. SCSN: " << scsn.text(); assert( isClientType(ClientType::VPN) || // minimal fetch for VPN does not receive nodes mNodeManager.getNodeCount() > 0); // otherwise if you see this please investigate why (before we alter the db) assert( isClientType(ClientType::VPN) || // minimal fetch for VPN does not receive nodes !mNodeManager.getRootNodeFiles().isUndef() || // we should know this by now - if (isClientType(ClientType::PASSWORD_MANAGER) && // not, why not, please investigate !mNodeManager.getRootNodeVault().isUndef())); // (before we alter the db) if (loggedIntoWritableFolder()) { // If logged into writable folder, we need the sharekey set in the root node // so as to include it in subsequent put nodes if (std::shared_ptr n = nodeByHandle(mNodeManager.getRootNodeFiles())) { n->sharekey.reset(new SymmCipher( key)); // we use the "master key", in this case the secret share key } } enabletransferresumption(); #ifdef ENABLE_SYNC if (loadSyncs) syncs.loadSyncConfigsOnFetchnodesComplete(true); #endif app->fetchnodes_result(API_OK); fetchnodesAlreadyCompletedThisSession = true; loadAuthrings(); WAIT_CLASS::bumpds(); fnstats.timeToSyncsResumed = Waiter::ds - fnstats.startTime; }; auto onuserdataCompletion = [this, fetchnodesTag, startCachedFetchNodes = std::move(startCachedFetchNodes) #ifdef ENABLE_SYNC // maybe_unused not allowed for lambda captures , loadSyncs #endif ](string*, string*, string*, std::vector&&, error e) { // upon ug completion if (e != API_OK) { LOG_err << "Session load failed: unable to get user data"; restag = fetchnodesTag; app->fetchnodes_result(API_EINTERNAL); return; // from completion function } #ifdef ENABLE_SYNC if (loggedin() == FULLACCOUNT && loadSyncs) { injectSyncSensitiveData(std::move(startCachedFetchNodes)); return; } #endif startCachedFetchNodes(API_OK); }; if (!loggedIntoFolder()) { getuserdata(0, onuserdataCompletion); } else { onuserdataCompletion(nullptr, nullptr, nullptr, {}, API_OK); } } else if (!fetchingnodes) { fnstats.mode = FetchNodesStats::MODE_API; fnstats.cache = nocache ? FetchNodesStats::API_NO_CACHE : FetchNodesStats::API_CACHE; fetchingnodes = true; // delay resetting the sc channel state. // we wait until `f` is sent, because when `f` is queued, there may be // other commands queued ahead of it, and those may need sc responses in order // to fully complete, and so we can't reset those members at this time. if (!loggedIntoFolder()) { // Copy the current tag so we can capture it in the lambda below. const auto fetchtag = reqtag; // Sanity clean before getting the User Data resetKeyring(); // Make sure that our own user is defined finduser(me, 1); auto startFetchNodes = [this, fetchtag, loadSyncs, nocache](Error e) { if (e != API_OK) { // Notify and continue with or without JSCD attribute. LOG_warn << "Unable to retrieve JSCD attribute. Continue fetchnodes."; } // FetchNodes procresult() needs some data from `ug` (or it may try to make new Sync // User Attributes for example) So only submit the request after `ug` completes, // otherwise everything is interleaved const auto partialFetchRoot = isClientType(ClientType::PASSWORD_MANAGER) ? getPasswordManagerBase() : NodeHandle{}; if (!isClientType(ClientType::PASSWORD_MANAGER) || (isClientType(ClientType::PASSWORD_MANAGER) && !partialFetchRoot.isUndef())) { queueCommand(new CommandFetchNodes(this, fetchtag, nocache, loadSyncs, partialFetchRoot)); } else { createPasswordManagerBase( fetchtag, [this, fetchtag, nocache, loadSyncs](Error e, std::unique_ptr newNode) { if (e != API_OK) app->fetchnodes_result(e); else if (newNode) { // Force request attribute, attribute change isn't received, // it's generated before fetch nodes queueCommand(new CommandGetUA(this, ownuser()->uid.c_str(), ATTR_PWM_BASE, nullptr, -1, nullptr, nullptr, nullptr)); queueCommand(new CommandFetchNodes(this, fetchtag, nocache, loadSyncs, newNode->nodeHandle())); } else { assert(false); } }); } }; auto getUserDataCompletion = [this, fetchtag, startFetchNodes = std::move(startFetchNodes) #ifdef ENABLE_SYNC // maybe_unused not allowed for lambda captures , loadSyncs #endif ](string*, string*, string*, std::vector&&, error e) { if (e != API_OK) { LOG_err << "Pre-failing fetching nodes: unable to get user data"; restag = fetchtag; fetchingnodes = false; app->fetchnodes_result(e); return; } if (loggedin() == FULLACCOUNT || loggedin() == EPHEMERALACCOUNTPLUSPLUS || loggedin() == CONFIRMEDACCOUNT) { initializekeys(); loadAuthrings(); } #ifdef ENABLE_SYNC if (loggedin() == FULLACCOUNT && loadSyncs) { injectSyncSensitiveData(std::move(startFetchNodes)); return; } #endif startFetchNodes(API_OK); }; getuserdata(0, std::move(getUserDataCompletion)); fetchtimezone(); } else { actionpacketsCurrent = false; queueCommand(new CommandFetchNodes(this, reqtag, nocache, loadSyncs)); } } } void MegaClient::resetScForFetchnodes() { // reset all the sc channel state, prevent sending sc requests while fetchnodes is sent // we wait until this moment, because when `f` is queued, there may be // other commands queued ahead of it, and those may need sc responses in order // to fully complete, and so we can't reset these members at that time. // prevent the processing of previous sc requests pendingsc.reset(); if (pendingscUserAlerts) { /** * sc50 request is sent after fetchnodes has finished ("f" command + processing of action * packets). If we perform multiple fetchnodes in a short period of time, we may send * another sc50 request before receiving response for the previous one. * In that case we first need to discard the sc50 request currently in-flight (if any), * which will result on ignoring the API response, and then prepare a new one. */ LOG_warn << "resetScForFetchnodes: Another sc50 Request is inflight, we'll discard it as " "it's obsolete"; sendevent(99487, "Another sc50 Request is inflight"); if (!useralerts.begincatchup) { LOG_warn << "resetScForFetchnodes: pendingscUserAlerts is not null (sc50 req in " "flight) but begincatchup is false"; } } /** * begincatchup is just set true when fetchnodes has finished, before sending sc50 request * if we are going to start a new fetchnodes and we want to discard inflight sc50 request, * we also should reset begincatchup flag. When this fetchnodes finishes begincatchup will * be set true again * * this action will prevent that assert(!fetchingnodes) at Megaclient::exec() fails, as we * have reset begincatchup. * * Be aware of API management of multiple sc50 inflight requests, it may incur into API lock * **/ useralerts.begincatchup = false; useralerts.catchupdone = false; pendingscUserAlerts.reset(); jsonsc.pos = NULL; scnotifyurl.clear(); mPendingCatchUps = 0; mReceivingCatchUp = false; insca = false; insca_notlast = false; btsc.reset(); // don't allow to start new sc requests yet scsn.clear(); } void MegaClient::initializekeys() { if (!mPublicRsaKey.isvalid() && !mPrivateRsaKey.isvalid(AsymmCipher::PRIVKEY)) { if (loggedin() != EPHEMERALACCOUNTPLUSPLUS) { LOG_info << "Generating and adding missing RSA keypair"; setkeypair(); } else { LOG_info << "Skip creation of RSA keypair for E++ account."; } } else if (mPublicRsaKey.isvalid() != mPrivateRsaKey.isvalid(AsymmCipher::PRIVKEY)) { LOG_err << "One of the RSA keys is missing."; sendevent(99498, "Incomplete RSA keypair"); mPrivateRsaKey.resetkey(); mPublicRsaKey.resetkey(); mSerializedPrivateRsaKey.clear(); return; } string prEd255, puEd255; // keypair for Ed25519 --> MegaClient::signkey string prCu255, puCu255; // keypair for Cu25519 --> MegaClient::chatkey string sigCu255, sigPubk; // signatures for Cu25519 and RSA User *u = finduser(me); assert(u && "Own user not available while initializing keys"); if (mKeyManager.generation()) // account has ^!keys already available { prEd255 = mKeyManager.privEd25519(); prCu255 = mKeyManager.privCu25519(); } else { const UserAttribute* attribute = u->getAttribute(ATTR_KEYRING); if (attribute && attribute->isValid()) { unique_ptr records{tlv::containerToRecords(attribute->value(), key)}; if (records) { prEd255.swap((*records)[EdDSA::TLV_KEY]); prCu255.swap((*records)[ECDH::TLV_KEY]); } else { LOG_warn << "Failed to decrypt keyring while initialization"; } } } // get public keys and signatures const UserAttribute* attribute = u->getAttribute(ATTR_ED25519_PUBK); if (attribute && attribute->isValid()) puEd255 = attribute->value(); attribute = u->getAttribute(ATTR_CU25519_PUBK); if (attribute && attribute->isValid()) puCu255 = attribute->value(); attribute = u->getAttribute(ATTR_SIG_CU255_PUBK); if (attribute && attribute->isValid()) sigCu255 = attribute->value(); attribute = u->getAttribute(ATTR_SIG_RSA_PUBK); if (attribute && attribute->isValid()) sigPubk = attribute->value(); // Initialize private keys if (prEd255.size() == EdDSA::SEED_KEY_LENGTH) { mEd255Key = new EdDSA(rng, (unsigned char*)prEd255.data()); if (!mEd255Key->initializationOK) { delete mEd255Key; mEd255Key = NULL; clearKeys(); return; } } if (prCu255.size() == ECDH::PRIVATE_KEY_LENGTH) { mX255Key = new ECDH(prCu255); if (!mX255Key->initializationOK) { delete mX255Key; mX255Key = NULL; clearKeys(); return; } } if (mX255Key && mEd255Key) // THERE ARE KEYS { // Check Ed25519 public key against derived version if ((puEd255.size() != EdDSA::PUBLIC_KEY_LENGTH) || memcmp(puEd255.data(), mEd255Key->pubKey, EdDSA::PUBLIC_KEY_LENGTH)) { LOG_warn << "Public key for Ed25519 mismatch."; sendevent(99417, "Ed25519 public key mismatch", 0); clearKeys(); resetKeyring(); return; } // Check Cu25519 public key against derive version if ((puCu255.size() != ECDH::PUBLIC_KEY_LENGTH) || memcmp(puCu255.data(), mX255Key->getPubKey(), ECDH::PUBLIC_KEY_LENGTH)) { LOG_warn << "Public key for Cu25519 mismatch."; sendevent(99412, "Cu25519 public key mismatch", 0); clearKeys(); resetKeyring(); return; } // Verify signatures for Cu25519 if (!sigCu255.size() || !EdDSA::verifyKey((unsigned char*) puCu255.data(), puCu255.size(), &sigCu255, (unsigned char*) puEd255.data())) { LOG_warn << "Signature of public key for Cu25519 not found or mismatch"; sendevent(99413, "Signature of Cu25519 public key mismatch", 0); clearKeys(); resetKeyring(); return; } if (loggedin() != EPHEMERALACCOUNTPLUSPLUS) // E++ accounts don't have RSA keys { // Verify signature for RSA public key if (mPublicRsaKey.isvalid() && sigPubk.empty()) { string pubkStr; std::string buf; userattr_map attrs; mPublicRsaKey.serializekeyforjs(pubkStr); mEd255Key->signKey((unsigned char*)pubkStr.data(), pubkStr.size(), &sigPubk); buf.assign(sigPubk.data(), sigPubk.size()); attrs[ATTR_SIG_RSA_PUBK] = buf; putua(&attrs, 0); } string pubkstr; if (mPublicRsaKey.isvalid()) { mPublicRsaKey.serializekeyforjs(pubkstr); } if (!pubkstr.size() || !sigPubk.size()) { if (!pubkstr.size()) { LOG_warn << "Error serializing RSA public key"; sendevent(99421, "Error serializing RSA public key", 0); } if (!sigPubk.size()) { LOG_warn << "Signature of public key for RSA not found"; sendevent(99422, "Signature of public key for RSA not found", 0); } clearKeys(); resetKeyring(); return; } if (!EdDSA::verifyKey((unsigned char*) pubkstr.data(), pubkstr.size(), &sigPubk, (unsigned char*) puEd255.data())) { LOG_warn << "Verification of signature of public key for RSA failed"; sendevent(99414, "Verification of signature of public key for RSA failed", 0); clearKeys(); resetKeyring(); return; } } if (mKeyManager.generation() && mPrivateRsaKey.isvalid(AsymmCipher::PRIVKEY) && !mKeyManager.getPrivRSA().size()) { // Ephemeral++ accounts create ^!keys before having RSA keys LOG_debug << "Attaching private RSA key into ^!keys"; std::string privRSA; mPrivateRsaKey.serializekey(&privRSA, AsymmCipher::PRIVKEY_SHORT); mKeyManager.commit( [this, privRSA]() { mKeyManager.setPrivRSA(privRSA); }); } // if we reached this point, everything is OK LOG_info << "Keypairs and signatures loaded successfully"; return; } else if (!mEd255Key && !mX255Key) // THERE ARE NO KEYS { // Check completeness of keypairs if (puEd255.size() || puCu255.size() || sigCu255.size() || sigPubk.size() || (!mPublicRsaKey.isvalid() && loggedin() != EPHEMERALACCOUNTPLUSPLUS)) // E++ accounts don't have RSA keys { LOG_warn << "Public keys and/or signatures found without their respective private key."; sendevent(99415, "Incomplete keypair detected", 0); clearKeys(); return; } else // No keys were set --> generate keypairs and related attributes { // generate keypairs EdDSA* newSignKey = new EdDSA(rng); ECDH* newChatKey = new ECDH(); prEd255 = string((char*)newSignKey->keySeed, EdDSA::SEED_KEY_LENGTH); prCu255 = string((char*)newChatKey->getPrivKey(), ECDH::PRIVATE_KEY_LENGTH); if (!newChatKey->initializationOK || !newSignKey->initializationOK) { LOG_err << "Initialization of keys Cu25519 and/or Ed25519 failed"; clearKeys(); delete newSignKey; delete newChatKey; return; } // store keys into user attributes (skipping the procresult() <-- reqtag=0) // prepare map of attributes to set: ^!keys|*!keyring + puEd255 + puCu255 + sigPubk + sigCu255 userattr_map attrs; // private keys string buf; // save private keys into the ^!keys attribute assert(mKeyManager.generation() == 0); // creating them, no init() done yet mKeyManager.setKey(key); mKeyManager.init(prEd255, prCu255, mSerializedPrivateRsaKey); // We are initializing the keys, so it's safe to assume that authrings are empty mAuthRings.emplace(ATTR_AUTHRING, AuthRing(ATTR_AUTHRING)); mAuthRings.emplace(ATTR_AUTHCU255, AuthRing(ATTR_AUTHCU255)); // Not using mKeyManager::commit() here to set ^!keys along with the other attributes. // Since the account is being initialized, putua should not fail. buf = mKeyManager.toKeysContainer(); attrs[ATTR_KEYS] = buf; // save private keys into the *!keyring attribute (for backwards compatibility, so legacy // clients can retrieve chat and signing key for accounts created with ^!keys support) string_map records{ {EdDSA::TLV_KEY, string{reinterpret_cast(newSignKey->keySeed), EdDSA::SEED_KEY_LENGTH} }, {ECDH::TLV_KEY, string{reinterpret_cast(newChatKey->getPrivKey()), ECDH::PRIVATE_KEY_LENGTH}}, }; unique_ptr container{tlv::recordsToContainer(std::move(records), rng, key)}; attrs[ATTR_KEYRING] = container ? std::move(*container) : string{}; // create signatures of public RSA and Cu25519 keys if (loggedin() != EPHEMERALACCOUNTPLUSPLUS) // Ephemeral++ don't have RSA keys until confirmation, but need chat and signing key { // prepare signatures string pubkStr; mPublicRsaKey.serializekeyforjs(pubkStr); newSignKey->signKey((unsigned char*)pubkStr.data(), pubkStr.size(), &sigPubk); } newSignKey->signKey(newChatKey->getPubKey(), ECDH::PUBLIC_KEY_LENGTH, &sigCu255); buf.assign((const char*)newSignKey->pubKey, EdDSA::PUBLIC_KEY_LENGTH); attrs[ATTR_ED25519_PUBK] = buf; buf.assign((const char*)newChatKey->getPubKey(), ECDH::PUBLIC_KEY_LENGTH); attrs[ATTR_CU25519_PUBK] = buf; if (loggedin() != EPHEMERALACCOUNTPLUSPLUS) // Ephemeral++ don't have RSA keys until confirmation, but need chat and signing key { buf.assign(sigPubk.data(), sigPubk.size()); attrs[ATTR_SIG_RSA_PUBK] = buf; } buf.assign(sigCu255.data(), sigCu255.size()); attrs[ATTR_SIG_CU255_PUBK] = buf; putua(&attrs, 0, [this](Error e) { if (e != API_OK) { LOG_err << "Error attaching keys: " << e; sendevent(99419, "Error Attaching keys", 0); clearKeys(); resetKeyring(); } }); delete newChatKey; delete newSignKey; // MegaClient::signkey & chatkey are created on putua::procresult() LOG_info << "Creating new keypairs and signatures"; return; } } else // there is chatkey but no signing key, or viceversa { LOG_warn << "Keyring exists, but it's incomplete."; if (!mX255Key) { sendevent(99416, "Incomplete keyring detected: private key for Cu25519 not found.", 0); } else // !signkey { sendevent(99423, "Incomplete keyring detected: private key for Ed25519 not found.", 0); } resetKeyring(); clearKeys(); return; } } void MegaClient::loadAuthrings() { if (User* ownUser = finduser(me)) { // if KeyManager is ready, authrings are already retrieved by getuserdata (from ^!keys attribute) if (!mKeyManager.generation()) { std::set attrs { ATTR_AUTHRING, ATTR_AUTHCU255 }; for (auto at : attrs) { const UserAttribute* attribute = ownUser->getAttribute(at); if (attribute && !attribute->isNotExisting()) { if (attribute->isValid()) { unique_ptr records{ tlv::containerToRecords(attribute->value(), key)}; if (records) { mAuthRings.emplace(at, AuthRing(at, *records)); LOG_info << "Authring successfully loaded from cache: " << User::attr2string(at); } else { LOG_err << "Failed to decrypt " << User::attr2string(at) << " from cached attribute"; } continue; } else // expired { LOG_err << User::attr2string(at) << " not available: found in cache, but out of date."; } } else { // It does not exist yet in the account. Will be created upon retrieval of contact keys. LOG_warn << User::attr2string(at) << " not found in cache. Setting an empty one."; mAuthRings.emplace(at, AuthRing(at)); } } fetchContactsKeys(); } // --> end if(!mKeyManager.generation()) } } void MegaClient::fetchContactsKeys() { assert(mAuthRings.size() == 2); // Populate mPendingContactKeys first, because if it's done just before fetchContactKeys, // it could finish synchronously and deactivate mAuthRingsTemp too early mPendingContactKeys.clear(); auto &pendingEdKeys = mPendingContactKeys[ATTR_AUTHRING]; auto &pendingCuKeys = mPendingContactKeys[ATTR_AUTHCU255]; for (auto &it : users) { User *user = &it.second; if (user->userhandle != me) { pendingEdKeys.insert(user->userhandle); pendingCuKeys.insert(user->userhandle); } } if (pendingEdKeys.empty()) { LOG_debug << "No need to fetch contact keys (no contacts)"; mPendingContactKeys.clear(); return; } mAuthRingsTemp = mAuthRings; for (auto &it : users) { User *user = &it.second; if (user->userhandle != me) { fetchContactKeys(user); } } } void MegaClient::fetchContactKeys(User *user) { // call trackKey() in case the key is in cache // otherwise, send getua() to server, CommandGetUA::procresult() will call trackKey() attr_t attrType = ATTR_ED25519_PUBK; const UserAttribute* attribute = user->getAttribute(attrType); if (!attribute || !attribute->isValid()) { getua(user, attrType, 0); // if Ed25519 is not in cache, better to ensure that Ed25519 is tracked before Cu25519 user->setAttributeExpired(ATTR_CU25519_PUBK); } else { trackKey(attrType, user->userhandle, attribute->value()); } attrType = ATTR_CU25519_PUBK; attribute = user->getAttribute(attrType); if (!attribute || !attribute->isValid()) getua(user, attrType, 0); else trackKey(attrType, user->userhandle, attribute->value()); // TODO: remove obsolete retrieval of public RSA keys and its signatures // (authrings for RSA are deprecated) if (!user->pubk.isvalid()) { int creqtag = reqtag; reqtag = 0; getpubkey(user->uid.c_str()); reqtag = creqtag; } } error MegaClient::trackKey(attr_t keyType, handle uh, const std::string &pubKey) { User *user = finduser(uh); if (!user) { LOG_err << "Attempt to track a key for an unknown user " << Base64Str(uh) << ": " << User::attr2string(keyType); assert(false); return API_EARGS; } const char* userID = user->uid.c_str(); attr_t authringType = AuthRing::keyTypeToAuthringType(keyType); if (authringType == ATTR_UNKNOWN) { LOG_err << "Attempt to track an unknown type of key for user " << userID << ": " << User::attr2string(keyType); assert(false); return API_EARGS; } // If checking authrings for all contacts (new session), accumulate updates for all contacts first // in temporal authrings to put them all at once. Otherwise, update authring immediately AuthRing *authring = nullptr; unique_ptr aux; auto it = mAuthRingsTemp.find(authringType); bool temporalAuthring = it != mAuthRingsTemp.end(); if (temporalAuthring) { authring = &it->second; // modify the temporal authring directly } else { it = mAuthRings.find(authringType); if (it == mAuthRings.end()) { LOG_warn << "Failed to track public key in " << User::attr2string(authringType) << " for user " << userID << ": authring not available"; assert(false); return API_ETEMPUNAVAIL; } aux = std::make_unique(it->second); // make a copy, once saved in API, it is updated authring = aux.get(); } // compute key's fingerprint string keyFingerprint = AuthRing::fingerprint(pubKey); bool fingerprintMatch = false; // check if user's key is already being tracked in the authring bool keyTracked = authring->isTracked(uh); if (keyTracked) { fingerprintMatch = (keyFingerprint == authring->getFingerprint(uh)); if (!fingerprintMatch) { if (!authring->isSignedKey()) { LOG_err << "Failed to track public key in " << User::attr2string(authringType) << " for user " << userID << ": fingerprint mismatch"; app->key_modified(uh, keyType); sendevent(99451, "Key modification detected"); // flush the temporal authring if needed if (temporalAuthring) { updateAuthring(authring, authringType, temporalAuthring, uh); } return API_EKEY; } //else --> verify signature, despite fingerprint does not match (it will be checked again later) } else { LOG_debug << "Authentication of public key in " << User::attr2string(authringType) << " for user " << userID << " was successful. Auth method: " << AuthRing::authMethodToStr(authring->getAuthMethod(uh)); } } if (authring->isSignedKey()) { // User's public Ed25519 should be in cache already assert(user->getAttribute(ATTR_ED25519_PUBK) != nullptr); attr_t attrType = AuthRing::authringTypeToSignatureType(authringType); const UserAttribute* attribute = user->getAttribute(attrType); if (attribute && attribute->isValid()) { trackSignature(attrType, uh, attribute->value()); } else { getua(user, attrType, 0); // in CommandGetUA::procresult(), we check signature actually matches } } else // then it's the authring for public Ed25519 key { if (!keyTracked) { LOG_debug << "Adding public key to " << User::attr2string(authringType) << " as seen for user " << userID; // tracking has changed --> persist authring authring->add(uh, keyFingerprint, AUTH_METHOD_SEEN); } error e = updateAuthring(authring, authringType, temporalAuthring, uh); if (e) { return e; } } return API_OK; } error MegaClient::trackSignature(attr_t signatureType, handle uh, const std::string &signature) { const User* user = finduser(uh); if (!user) { LOG_err << "Attempt to track a key for an unknown user " << Base64Str(uh) << ": " << User::attr2string(signatureType); assert(false); return API_EARGS; } const char* userID = user->uid.c_str(); attr_t authringType = AuthRing::signatureTypeToAuthringType(signatureType); if (authringType == ATTR_UNKNOWN) { LOG_err << "Attempt to track an unknown type of signature for user " << userID << ": " << User::attr2string(signatureType); assert(false); return API_EARGS; } // If checking authrings for all contacts (new session), accumulate updates for all contacts first // in temporal authrings to put them all at once. Otherwise, send the update immediately AuthRing *authring = nullptr; unique_ptr aux; auto it = mAuthRingsTemp.find(authringType); bool temporalAuthring = it != mAuthRingsTemp.end(); if (temporalAuthring) { authring = &it->second; // modify the temporal authring directly } else { it = mAuthRings.find(authringType); if (it == mAuthRings.end()) { LOG_warn << "Failed to track signature of public key in " << User::attr2string(authringType) << " for user " << uid << ": authring not available"; assert(false); return API_ETEMPUNAVAIL; } aux = std::make_unique(it->second); // make a copy, once saved in API, it is updated authring = aux.get(); } const string *pubKey; if (signatureType == ATTR_SIG_CU255_PUBK) { // retrieve public key whose signature wants to be verified, from cache const UserAttribute* attribute = user->getAttribute(ATTR_CU25519_PUBK); if (!attribute || !attribute->isValid()) { LOG_warn << "Failed to verify signature " << User::attr2string(signatureType) << " for user " << userID << ": CU25519 public key is not available"; assert(false); return API_EINTERNAL; } pubKey = &attribute->value(); } else { LOG_err << "Attempt to track an unknown type of signature: " << User::attr2string(signatureType); assert(false); return API_EINTERNAL; } // retrieve signing key from cache const UserAttribute* attribute = user->getAttribute(ATTR_ED25519_PUBK); if (!attribute || !attribute->isValid()) { LOG_warn << "Failed to retrieve signing key " << User::attr2string(ATTR_ED25519_PUBK) << " for user " << userID << ": signing public key is not available"; assert(attribute && attribute->isValid()); return API_ETEMPUNAVAIL; } const string* signingPubKey = &attribute->value(); // compute key's fingerprint string keyFingerprint = AuthRing::fingerprint(*pubKey); bool fingerprintMatch = false; bool keyTracked = authring->isTracked(uh); // check signature for the public key bool signatureVerified = EdDSA::verifyKey((unsigned char*) pubKey->data(), pubKey->size(), (string*)&signature, (unsigned char*) signingPubKey->data()); if (signatureVerified) { LOG_debug << "Signature " << User::attr2string(signatureType) << " successfully verified for user " << user->uid; // check if user's key is already being tracked in the authring if (keyTracked) { fingerprintMatch = (keyFingerprint == authring->getFingerprint(uh)); if (!fingerprintMatch) { LOG_err << "Failed to track signature of public key in " << User::attr2string(authringType) << " for user " << userID << ": fingerprint mismatch"; app->key_modified(uh, signatureType == ATTR_SIG_CU255_PUBK ? ATTR_CU25519_PUBK : ATTR_UNKNOWN); sendevent(99451, "Key modification detected"); return API_EKEY; } else if (authring->getAuthMethod(uh) != AUTH_METHOD_SIGNATURE) { LOG_debug << "Updating authentication method for user " << userID << " to signature verified"; authring->update(uh, AUTH_METHOD_SIGNATURE); } } else { LOG_debug << "Adding public key to " << User::attr2string(authringType) << " as signature verified for user " << userID; authring->add(uh, keyFingerprint, AUTH_METHOD_SIGNATURE); } error e = updateAuthring(authring, authringType, temporalAuthring, uh); if (e) { return e; } } else { LOG_err << "Failed to verify signature of public key in " << User::attr2string(authringType) << " for user " << userID << ": signature mismatch"; app->key_modified(uh, signatureType); sendevent(99452, "Signature mismatch for public key"); // flush the temporal authring if needed if (temporalAuthring) { updateAuthring(authring, authringType, temporalAuthring, uh); } return API_EKEY; } return API_OK; } error MegaClient::updateAuthring(AuthRing *authring, attr_t authringType, bool temporalAuthring, handle updateduh) { // if checking authrings for all contacts, accumulate updates for all contacts first bool finished = true; if (temporalAuthring) { auto it = mPendingContactKeys.find(authringType); assert(it != mPendingContactKeys.end()); if (it != mPendingContactKeys.end()) { it->second.erase(updateduh); if (it->second.size()) { // initialization not finished yet finished = false; } else { mPendingContactKeys.erase(it); LOG_debug << "Authring " << User::attr2string(authringType) << " initialization finished"; } } } if (finished) { if (authring->needsUpdate()) { std::string serializedAuthring = authring->serializeForJS(); if (mKeyManager.generation()) { LOG_debug << "Updating " << User::attr2string(authringType) << " in ^!keys"; mKeyManager.commit( [this, authringType, serializedAuthring]() { // Changes to apply in the commit if (authringType == ATTR_AUTHRING) { mKeyManager.setAuthRing(serializedAuthring); } else if (authringType == ATTR_AUTHCU255) { mKeyManager.setAuthCU255(serializedAuthring); } }); } else { // Account not migrated yet. Apply changes synchronously in the local authring to have it ready for the migration auto it = mAuthRings.find(authringType); if (it == mAuthRings.end()) { LOG_warn << "Failed to track signature of public key in " << User::attr2string(authringType) << " for user " << uid << ": account not migrated and authring not available"; assert(false); return API_ETEMPUNAVAIL; } it->second = *authring; } } mAuthRingsTemp.erase(authringType); } return API_OK; } error MegaClient::verifyCredentials(handle uh, std::function completion) { if (!mKeyManager.generation()) { LOG_err << "Account not upgraded yet"; return API_EINCOMPLETE; } Base64Str uidB64(uh); auto itEd = mAuthRings.find(ATTR_AUTHRING); bool hasEdAuthring = itEd != mAuthRings.end(); auto itCu = mAuthRings.find(ATTR_AUTHCU255); bool hasCuAuthring = itCu != mAuthRings.end(); if (!hasEdAuthring || !hasCuAuthring) { LOG_warn << "Failed to verify public Ed25519 key for user " << uidB64 << ": authring(s) not available"; return API_ETEMPUNAVAIL; } if (itCu->second.getAuthMethod(uh) != AUTH_METHOD_SIGNATURE) { LOG_err << "Failed to verify credentials for user " << uidB64 << ": signature of Cu25519 public key is not verified"; assert(false); // Let's try to authenticate Cu25519 for this user // because its verification is required to promote pending sharesUser *user = finduser(uh); User *user = finduser(uh); if (user) { attr_t attrType = ATTR_CU25519_PUBK; const UserAttribute* attribute = user->getAttribute(attrType); if (!attribute || !attribute->isValid()) getua(user, attrType, 0); else trackKey(attrType, user->userhandle, attribute->value()); } return API_EINTERNAL; } AuthMethod authMethod = itEd->second.getAuthMethod(uh); switch (authMethod) { case AUTH_METHOD_SEEN: LOG_debug << "Updating authentication method of Ed25519 public key for user " << uidB64 << " from seen to signature verified"; break; case AUTH_METHOD_FINGERPRINT: LOG_err << "Failed to verify credentials for user " << uidB64 << ": already verified"; return API_EEXIST; case AUTH_METHOD_SIGNATURE: LOG_err << "Failed to verify credentials for user " << uidB64 << ": invalid authentication method"; return API_EINTERNAL; case AUTH_METHOD_UNKNOWN: { User *user = finduser(uh); const UserAttribute* pubKeyAttribute = user ? user->getAttribute(ATTR_ED25519_PUBK) : nullptr; if (pubKeyAttribute && pubKeyAttribute->isValid()) { LOG_warn << "Adding authentication method of Ed25519 public key for user " << uidB64 << ": key is not tracked yet"; } else { LOG_err << "Failed to verify credentials for user " << uidB64 << ": key not tracked and not available"; return API_ETEMPUNAVAIL; } break; } } mKeyManager.commit( [this, uh, uidB64]() { // Changes to apply in the commit auto itEd = mAuthRings.find(ATTR_AUTHRING); auto itCu = mAuthRings.find(ATTR_AUTHCU255); if (itEd == mAuthRings.end() || itCu == mAuthRings.end()) { LOG_warn << "Failed to verify public Ed25519 key for user " << uidB64 << ": authring(s) not available during commit"; return; } if (itCu->second.getAuthMethod(uh) != AUTH_METHOD_SIGNATURE) { LOG_err << "Failed to verify credentials for user " << uidB64 << ": signature of Cu25519 public key is not verified during commit"; return; } AuthRing authring = itEd->second; // copy, do not modify yet the cached authring AuthMethod authMethod = authring.getAuthMethod(uh); switch (authMethod) { case AUTH_METHOD_SEEN: authring.update(uh, AUTH_METHOD_FINGERPRINT); break; case AUTH_METHOD_UNKNOWN: { User* user = finduser(uh); const UserAttribute* pubKeyAttribute = user ? user->getAttribute(ATTR_ED25519_PUBK) : nullptr; if (pubKeyAttribute && pubKeyAttribute->isValid()) { string keyFingerprint = AuthRing::fingerprint(pubKeyAttribute->value()); LOG_warn << "Adding authentication method of Ed25519 public key for user " << uidB64 << ": key is not tracked yet during commit"; authring.add(uh, keyFingerprint, AUTH_METHOD_FINGERPRINT); break; } else { LOG_err << "Failed to verify credentials for user " << uidB64 << ": key not tracked and not available during commit"; return; } break; } default: LOG_err << "Failed to verify credentials for user " << uidB64 << " unexpected authMethod (" << authMethod << ") during commit"; return; } std::string serializedAuthring = authring.serializeForJS(); mKeyManager.setAuthRing(serializedAuthring); }, [completion](error e) { completion(e); }); return API_OK; } error MegaClient::resetCredentials(handle uh, std::function completion) { if (!mKeyManager.generation()) { LOG_err << "Account not upgraded yet"; return API_EINCOMPLETE; } Base64Str uidB64(uh); auto it = mAuthRings.find(ATTR_AUTHRING); if (it == mAuthRings.end()) { LOG_warn << "Failed to reset credentials for user " << uidB64 << ": authring not available"; return API_ETEMPUNAVAIL; } AuthMethod authMethod = it->second.getAuthMethod(uh); if (authMethod == AUTH_METHOD_SEEN) { LOG_warn << "Failed to reset credentials for user " << uidB64 << ": Ed25519 key is not verified by fingerprint"; return API_EARGS; } else if (authMethod == AUTH_METHOD_UNKNOWN) { LOG_warn << "Failed to reset credentials for user " << uidB64 << ": Ed25519 key is not tracked yet"; return API_ENOENT; } assert(authMethod == AUTH_METHOD_FINGERPRINT); // Ed25519 authring cannot be at AUTH_METHOD_SIGNATURE LOG_debug << "Resetting credentials for user " << uidB64 << "..."; mKeyManager.commit( [this, uh, uidB64]() { auto it = mAuthRings.find(ATTR_AUTHRING); if (it == mAuthRings.end()) { LOG_warn << "Failed to reset credentials for user " << uidB64 << ": authring not available during commit"; return; } AuthRing authring = it->second; // copy, do not update cached authring yet AuthMethod authMethod = authring.getAuthMethod(uh); if (authMethod != AUTH_METHOD_FINGERPRINT) { LOG_warn << "Failed to reset credentials for user " << uidB64 << " unexpected authMethod (" << authMethod << ") during commit"; return; } authring.update(uh, AUTH_METHOD_SEEN); string serializedAuthring = authring.serializeForJS(); // Changes to apply in the commit mKeyManager.setAuthRing(serializedAuthring); }, [completion](error e) { if (completion) { completion(e); } return; }); return API_OK; } bool MegaClient::areCredentialsVerified(handle uh) { if (uh == me) { return false; } AuthRingsMap::const_iterator itCu = mAuthRings.find(ATTR_AUTHCU255); bool cuAuthringFound = itCu != mAuthRings.end(); if (!cuAuthringFound || !itCu->second.areCredentialsVerified(uh)) { LOG_err << "Cu25519 for " << toHandle(uh) << ": " << (!cuAuthringFound ? "authring missing" : "signature not verified"); return false; } AuthRingsMap::const_iterator it = mAuthRings.find(ATTR_AUTHRING); bool edAuthringFound = it != mAuthRings.end(); if (!edAuthringFound || !it->second.areCredentialsVerified(uh)) { if (!edAuthringFound) LOG_err << "Ed25519 for " << toHandle(uh) << ": " << "authring missing"; return false; } return true; } void MegaClient::purgenodesusersabortsc(bool keepOwnUser) { // this function's purpose is to remove from RAM everything that we would populate in FetchNodes. app->clearing(); // Abort any active direct reads. abortreads(); mNodeManager.cleanNodes(); #ifdef ENABLE_SYNC pendingDebris.clear(); #endif for (fafc_map::iterator cit = fafcs.begin(); cit != fafcs.end(); cit++) { for (int i = 2; i--; ) { for (faf_map::iterator it = cit->second->fafs[i].begin(); it != cit->second->fafs[i].end(); it++) { delete it->second; } cit->second->fafs[i].clear(); } } for (newshare_list::iterator it = newshares.begin(); it != newshares.end(); it++) { delete *it; } newshares.clear(); mNewKeyRepository.clear(); usernotify.clear(); pcrnotify.clear(); useralerts.clear(); #ifdef ENABLE_CHAT for (textchat_map::iterator it = chats.begin(); it != chats.end();) { delete it->second; chats.erase(it++); } chatnotify.clear(); #endif for (user_map::iterator it = users.begin(); it != users.end(); ) { User *u = &(it->second); if ((!keepOwnUser || u->userhandle != me) || u->userhandle == UNDEF) { ++it; dodiscarduser(u, true); } else { // if there are changes to notify, restore the notification in the queue if (u->notified) { usernotify.push_back(u); } u->dbid = 0; it++; } } assert(users.size() <= 1 && uhindex.size() <= 1 && umindex.size() <= 1); if (!keepOwnUser) // Force to remove all elements from user maps { users.clear(); uhindex.clear(); umindex.clear(); } pcrindex.clear(); scsn.clear(); if (pendingsc) { app->request_response_progress(-1, -1); pendingsc->disconnect(); } if (pendingscUserAlerts) { pendingscUserAlerts->disconnect(); } init(); } // request direct read by node pointer void MegaClient::pread(Node* node, m_off_t offset, m_off_t count, DirectRead::Callback&& callback) { queueread(node->nodehandle, false, node->nodecipher(), MemAccess::get((const char*)node->nodekey().data() + SymmCipher::KEYLENGTH), offset, count, std::move(callback)); } void MegaClient::pread(handle handle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, DirectRead::Callback&& callback, bool isPublicHandle, const char* privateAuth, const char* publicAuth, const char* chatAuth) { queueread(handle, isPublicHandle, cipher, iv, offset, count, std::move(callback), privateAuth, publicAuth, chatAuth); } void MegaClient::pread(Node* node, m_off_t offset, m_off_t count, void* appData) { queueread(node->nodehandle, false, node->nodecipher(), MemAccess::get((const char*)node->nodekey().data() + SymmCipher::KEYLENGTH), offset, count, appData); } // request direct read by exported handle / key void MegaClient::pread(handle handle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, void* appData, bool isPublicHandle, const char* privateAuth, const char* publicAuth, const char* chatAuth) { queueread(handle, isPublicHandle, cipher, iv, offset, count, appData, privateAuth, publicAuth, chatAuth); } // since only the first six bytes of a handle are in use, we use the seventh to encode its type void MegaClient::encodeHandleType(handle* h, bool isPublicHandle) { if (!isPublicHandle) // Not a public handle { ((char*)h)[NODEHANDLE] = 1; } } void MegaClient::queueread(handle handle, bool isPublicHandle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, void* appData, const char* privateAuth, const char* publicAuth, const char* chatAuth) { assert(appData); auto callback = [this, appData](DirectRead::CallbackParam& param) mutable { std::visit(overloaded{[&](DirectRead::Data& data) { data.ret = app->pread_data(data.buffer, data.len, data.offset, data.speed, data.meanSpeed, appData); }, [&](DirectRead::Failure& failure) { failure.ret = app->pread_failure(failure.e, failure.retry, appData, failure.timeLeft); }, [&](DirectRead::Revoke& revoke) { if (appData == revoke.appdata) { appData = nullptr; revoke.ret = true; } }, [&](DirectRead::IsValid& isValid) { isValid.ret = appData != nullptr; }}, param); }; queueread(handle, isPublicHandle, cipher, iv, offset, count, std::move(callback), privateAuth, publicAuth, chatAuth); } void MegaClient::queueread(handle handle, bool isPublicHandle, SymmCipher* cipher, int64_t iv, m_off_t offset, m_off_t count, DirectRead::Callback&& callback, const char* privateAuth, const char* publicAuth, const char* chatAuth) { assert(callback); handledrn_map::iterator it; encodeHandleType(&handle, isPublicHandle); it = hdrns.find(handle); if (it == hdrns.end()) { // this handle is not being accessed yet: insert it = hdrns.insert(hdrns.end(), std::make_pair(handle, new DirectReadNode(this, handle, isPublicHandle, cipher, iv, privateAuth, publicAuth, chatAuth))); it->second->hdrn_it = it; auto directRead = it->second->enqueue(offset, count, reqtag, std::move(callback)); if (overquotauntil && overquotauntil > Waiter::ds) { dstime timeleft = dstime(overquotauntil - Waiter::ds); directRead->onFailure(API_EOVERQUOTA, 0, timeleft); it->second->schedule(timeleft); } else { it->second->dispatch(); } } else { auto directRead = it->second->enqueue(offset, count, reqtag, std::move(callback)); if (overquotauntil && overquotauntil > Waiter::ds) { dstime timeleft = dstime(overquotauntil - Waiter::ds); directRead->onFailure(API_EOVERQUOTA, 0, timeleft); it->second->schedule(timeleft); } } } void MegaClient::removeAppData(void* t) { for (auto it = hdrns.begin(); it != hdrns.end(); ++it) { dr_list& dreads = it->second->reads; for(auto it2 = dreads.begin(); it2 != dreads.end(); ++it2) { DirectRead* dr = *it2; if (dr) dr->revokeCallback(t); } } } // cancel direct read by node pointer / count / count void MegaClient::preadabort(Node* node, m_off_t offset, m_off_t count) { abortreads(node->nodehandle, false, offset, count); } // cancel direct read by exported handle / offset / count void MegaClient::preadabort(handle handle, m_off_t offset, m_off_t count) { abortreads(handle, true, offset, count); } void MegaClient::abortreads(handle handle, bool isPublicHandle, m_off_t offset, m_off_t count) { encodeHandleType(&handle, isPublicHandle); // Try and find the direct read node associated with our handle. auto i = hdrns.find(handle); // No node assocated with this handle. if (i == hdrns.end()) return; auto predicate = [=](const DirectRead& read) { return (count < 0 || count == read.count) && (offset < 0 || offset == read.offset); }; // predicate // Abort all reads matching our predicate. i->second->abort(std::move(predicate)); } void MegaClient::abortreads() { auto always = [](const DirectRead&) { return true; }; // Iterate over any active direct read nodes. for (auto i = hdrns.begin(); i != hdrns.end();) { // Abort pending reads queued on that node. i->second->abort(always); // Destroy the node. delete i++->second; } } // execute pending directreads bool MegaClient::execdirectreads() { CodeCounter::ScopeTimer ccst(performanceStats.execdirectreads); bool r = false; if (drss.size() < MAXDRSLOTS) { // fill slots for (dr_list::iterator it = drq.begin(); it != drq.end(); it++) { if (!(*it)->drs) { (*it)->drs = new DirectReadSlot(*it); r = true; if (drss.size() >= MAXDRSLOTS) break; } } } // perform slot I/O for (drs_list::iterator it = drss.begin(); it != drss.end(); ) { if ((*(it++))->doio()) { r = true; break; } } while (!dsdrns.empty() && dsdrns.begin()->first <= Waiter::ds) { if (dsdrns.begin()->second->reads.size() && (dsdrns.begin()->second->tempurls.size() || dsdrns.begin()->second->pendingcmd)) { LOG_warn << "DirectRead scheduled retry"; dsdrns.begin()->second->retry(API_EAGAIN); } else { LOG_debug << "Dispatching scheduled streaming"; dsdrns.begin()->second->dispatch(); } } return r; } error MegaClient::addtimer(TimerWithBackoff *twb) { bttimers.push_back(twb); return API_OK; } #ifdef ENABLE_SYNC std::pair MegaClient::isnodesyncable(std::shared_ptr remotenode, const bool excludeSelf) { if (!remotenode) { LOG_err << "Calling isnodesyncable with a null node. This should not happen"; assert(false && "Calling isnodesyncable with a null node. This should not happen"); return {API_EARGS, REMOTE_NODE_NOT_FOUND}; } if (remotenode->type != FOLDERNODE && remotenode->type != ROOTNODE) return {API_EACCESS, INVALID_REMOTE_TYPE}; if (remotenode->firstancestor()->type == RUBBISHNODE) return {API_EACCESS, REMOTE_NODE_INSIDE_RUBBISH}; if (auto syncError = anyActiveSyncAboveOrBelow(*remotenode, excludeSelf); syncError != NO_SYNC_ERROR) return {API_EEXIST, syncError}; if (userHasRestrictedAccessToNode(*remotenode)) return {API_EACCESS, SHARE_NON_FULL_ACCESS}; return {API_OK, NO_SYNC_ERROR}; } SyncError MegaClient::anyActiveSyncAboveOrBelow(const Node& remotenode, const bool excludeSelf) { const auto activeConfigs = syncs.getConfigs(true); for (const auto& syncConfig: activeConfigs) { if (excludeSelf && syncConfig.mRemoteNode == remotenode.nodeHandle()) continue; std::shared_ptr syncRoot = nodeByHandle(syncConfig.mRemoteNode); if (!syncRoot) continue; bool thereIsSyncRootAbove = remotenode.isbelow(syncRoot.get()); bool thereIsSyncRootBelow = syncRoot->isbelow(&remotenode); if (thereIsSyncRootAbove && thereIsSyncRootBelow) return ACTIVE_SYNC_SAME_PATH; if (thereIsSyncRootBelow) return ACTIVE_SYNC_BELOW_PATH; if (thereIsSyncRootAbove) return ACTIVE_SYNC_ABOVE_PATH; } return NO_SYNC_ERROR; } bool MegaClient::userHasRestrictedAccessToNode(const Node& targetNode) { bool firstInShareAncestorHasRestrictedAccess = true; if (const bool anyAncestorInShare = targetNode.matchesOrHasAncestorMatching( [&firstInShareAncestorHasRestrictedAccess](const Node& node) -> bool { if (node.inshare) { firstInShareAncestorHasRestrictedAccess = node.inshare->access != FULL; return true; } return false; }); !anyAncestorInShare) return false; // No restrictions if not inside an inshare if (firstInShareAncestorHasRestrictedAccess) return true; // if target node is inside an inshare with full perms, we need to ensure we have full access to // all the descendants. For that we evaluate all the nodes being shared that are inside target const auto isSuccessorOfTargetNodeAndHasNotFullAccess = [remotenodeHandle = targetNode.nodeHandle(), this](const handle nh) -> bool { const auto testNode = nodebyhandle(nh); return testNode && testNode->inshare && testNode->inshare->access != FULL && (testNode->hasNHOrHasAncestorWithNH(remotenodeHandle)); }; const auto isUserSharingNotFullAccessNodeUnderTarget = [&isSuccessorOfTargetNodeAndHasNotFullAccess](const auto& idUserPair) -> bool { const auto& userSharings = idUserPair.second.sharing; return std::any_of(std::begin(userSharings), std::end(userSharings), isSuccessorOfTargetNodeAndHasNotFullAccess); }; return std::any_of(std::begin(users), std::end(users), isUserSharingNotFullAccessNodeUnderTarget); } std::pair MegaClient::isLocalPathSyncable(const LocalPath& newPath, const handle excludeBackupId) const { if (newPath.empty()) return {API_EARGS, LOCAL_PATH_UNAVAILABLE}; LocalPath newLocallyEncodedAbsolutePath; fsaccess->expanselocalpath(newPath, newLocallyEncodedAbsolutePath); for (const auto& config: syncs.getConfigs(false)) { if (config.mBackupId == excludeBackupId || !config.getEnabled() || config.mError) continue; LocalPath otherLocallyEncodedAbsolutePath; fsaccess->expanselocalpath(config.getLocalPath(), otherLocallyEncodedAbsolutePath); if (newLocallyEncodedAbsolutePath.isContainingPathOf(otherLocallyEncodedAbsolutePath) || otherLocallyEncodedAbsolutePath.isContainingPathOf(newLocallyEncodedAbsolutePath)) { LOG_warn << "Path already associated with a sync: " << newLocallyEncodedAbsolutePath << " " << toHandle(config.mBackupId) << " " << otherLocallyEncodedAbsolutePath; return {API_EARGS, LOCAL_PATH_SYNC_COLLISION}; } } return {API_OK, NO_SYNC_ERROR}; } SyncErrorInfo MegaClient::isValidLocalSyncRoot(const LocalPath& localPath, const handle backupIdToExclude) { if (!localPath.isAbsolute() && !localPath.isURI()) return {API_EARGS, NO_SYNC_ERROR, NO_SYNC_WARNING}; #ifdef __ANDROID__ if (!localPath.isURI()) { LOG_warn << "Sync path is not from URIPath type: " << localPath; return {API_EFAILED, UNABLE_TO_ADD_WATCH, NO_SYNC_WARNING}; } #endif const auto rootPathWithoutEndingSeparator = std::invoke( [&localPath]() -> LocalPath { auto rootPath = localPath; rootPath.trimNonDriveTrailingSeparator(); return rootPath; }); // Make sure the user isn't trying to sync a FUSE mount. if (!mFuseService.syncable(rootPathWithoutEndingSeparator)) { LOG_warn << "Trying to sync in a FUSE mount: " << rootPathWithoutEndingSeparator; return {API_EFAILED, LOCAL_PATH_MOUNTED, NO_SYNC_WARNING}; } bool isnetwork = false; SyncError auxSErr; SyncWarning syncWarning = NO_SYNC_WARNING; if (!fsaccess->issyncsupported(rootPathWithoutEndingSeparator, isnetwork, auxSErr, syncWarning)) { LOG_warn << "Unsupported filesystem"; return {API_EFAILED, UNSUPPORTED_FILE_SYSTEM, syncWarning}; } if (isnetwork) { sendevent(800035, "Detected an attempt to setup a sync involving a network drive"); } std::unique_ptr openedLocalFolder = fsaccess->newfileaccess(); // we do allow, eg. mounting an exFAT drive over an NTFS folder, and making a sync at that path bool reparsePointOkAtRoot = true; if (!openedLocalFolder->fopen(rootPathWithoutEndingSeparator, OPEN_RDONLY, FSLogging::logOnError, nullptr, reparsePointOkAtRoot, true, nullptr)) { LOG_warn << "Cannot open rootpath for sync: " << rootPathWithoutEndingSeparator; if (openedLocalFolder->retry) return {API_ETEMPUNAVAIL, LOCAL_PATH_TEMPORARY_UNAVAILABLE, syncWarning}; return {API_ENOENT, LOCAL_PATH_UNAVAILABLE, syncWarning}; } if (openedLocalFolder->type != FOLDERNODE) { LOG_warn << "Cannot sync non-folder: " << rootPathWithoutEndingSeparator; return {API_EACCESS, INVALID_LOCAL_TYPE, syncWarning}; } if (openedLocalFolder->fsid == UNDEF) { LOG_warn << "Cannot get its fsid: " << rootPathWithoutEndingSeparator; return {API_EFAILED, UNABLE_TO_RETRIEVE_ROOT_FSID, syncWarning}; } auto fsfp = fsaccess->fsFingerprint(rootPathWithoutEndingSeparator); if (!fsfp) { LOG_warn << "Cannot retrieve filesystem fingerprint: " << rootPathWithoutEndingSeparator; return {API_EFAILED, FILESYSTEM_ID_UNAVAILABLE, syncWarning}; } #ifdef __APPLE__ auto fsstableids = fsaccess->fsStableIDs(rootPathWithoutEndingSeparator); if (!fsstableids) { // On Mac, stat() can and sometimes does return different IDs between calls // for the same path, for exFAT, and probably other FAT variants too. LOG_warn << "Filesystem IDs are not stable: " << rootPathWithoutEndingSeparator; return {API_EFAILED, FILESYSTEM_FILE_IDS_ARE_UNSTABLE, syncWarning}; } #endif if (const auto [e, syncError] = isLocalPathSyncable(localPath, backupIdToExclude); e != API_OK) { LOG_warn << "Local path not syncable: " << localPath; return {e, syncError, syncWarning}; } return {API_OK, NO_SYNC_ERROR, syncWarning}; } SyncErrorInfo MegaClient::checkSyncConfig(const SyncConfig& syncConfig) { assert(syncConfig.mExternalDrivePath.empty() || syncConfig.mExternalDrivePath.isAbsolute()); assert(syncConfig.mLocalPath.isAbsolute()); // If failed to unserialize nodes from DB, syncs get disabled -> prevent re-enable them // until the account is reloaded (or the app restarts) if (accountShouldBeReloadedOrRestarted()) { LOG_warn << "Cannot re-enable sync until account's reload (unserialize errors)"; return {API_EINTERNAL, FAILURE_ACCESSING_PERSISTENT_STORAGE, NO_SYNC_WARNING}; } std::shared_ptr remotenode = nodeByHandle(syncConfig.mRemoteNode); if (!remotenode) { LOG_warn << "Sync root does not exist in the cloud: " << syncConfig.getLocalPath() << ": " << LOG_NODEHANDLE(syncConfig.mRemoteNode); return {API_ENOENT, REMOTE_NODE_NOT_FOUND, NO_SYNC_WARNING}; } if (const auto [e, syncError] = isnodesyncable(remotenode); e) { LOG_debug << "Node is not syncable for sync add"; return {e, syncError, NO_SYNC_WARNING}; } // Has the user requested the sync operate in periodic scanning mode with an invalid interval? if (syncConfig.mChangeDetectionMethod == CDM_PERIODIC_SCANNING && !syncConfig.mScanIntervalSec) { LOG_debug << "No scan interval for periodic sync add"; return {API_EARGS, INVALID_SCAN_INTERVAL, NO_SYNC_WARNING}; } if (syncs.mBackupRestrictionsEnabled && syncConfig.isBackup() && remotenode->firstancestor()->nodeHandle() != mNodeManager.getRootNodeVault()) { return {API_EARGS, INVALID_REMOTE_TYPE, NO_SYNC_WARNING}; } if (syncConfig.isExternal()) { if (!syncConfig.isBackup()) { LOG_warn << "Only Backups can be external"; return {API_EARGS, NO_SYNC_ERROR, NO_SYNC_WARNING}; } if (!syncConfig.isGoodPathForExternalBackup(syncConfig.mLocalPath)) { LOG_warn << "Drive path inconsistent for sync local path"; return {API_EARGS, BACKUP_SOURCE_NOT_BELOW_DRIVE, NO_SYNC_WARNING}; } } const auto [err, sErr, sWarn] = isValidLocalSyncRoot(syncConfig.getLocalPath(), syncConfig.mBackupId); if (err != API_OK) return {err, sErr, sWarn}; // check we are not in any blocking situation using CType = CacheableStatus::Type; // the order is important here: a user needs to resolve blocked in order to resolve storage if (const bool overStorage = mCachedStatus.lookup(CType::STATUS_STORAGE, STORAGE_UNKNOWN) >= STORAGE_RED; overStorage) { LOG_debug << "Overstorage for sync add"; return {API_EFAILED, STORAGE_OVERQUOTA, sWarn}; } else if (const bool businessExpired = mCachedStatus.lookup(CType::STATUS_BUSINESS, BIZ_STATUS_UNKNOWN) == BIZ_STATUS_EXPIRED; businessExpired) { LOG_debug << "Account expired for sync add"; return {API_EFAILED, ACCOUNT_EXPIRED, sWarn}; } else if (const bool blocked = mCachedStatus.lookup(CType::STATUS_BLOCKED, 0) == 1; blocked) { LOG_debug << "Account blocked for sync add"; return {API_EFAILED, ACCOUNT_BLOCKED, sWarn}; } // This only matters if there were no errors return {API_OK, NO_SYNC_ERROR, NO_SYNC_WARNING}; } void MegaClient::importSyncConfigs(const char* configs, std::function completion) { // Kick off the import. syncs.importSyncConfigs(configs, std::move(completion)); } void MegaClient::changeSyncRoot(const handle backupId, const handle newRemoteRootNodeHandle, const char* const newLocalRootPath, std::function&& completion) { const bool noNode = newRemoteRootNodeHandle == UNDEF; const bool noPath = newLocalRootPath == nullptr; if ((noNode && noPath) || (!noNode && !noPath)) { if (noNode) LOG_err << "changeSyncRoot invoked with null local and remote new roots"; else LOG_err << "changeSyncRoot invoked with both new local and remote node. Only accepts " "one at a time"; return completion(API_EARGS, NO_SYNC_ERROR); } if (noNode) { auto newRootPath = LocalPath::fromAbsolutePath(newLocalRootPath); if (const auto [err, syncErr, syncWarn] = isValidLocalSyncRoot(newRootPath, UNDEF); err != API_OK) { LOG_err << "changeSyncRoot: Invalid new local root. Error: " << SyncConfig::syncErrorToStr(syncErr); return completion(err, syncErr); } return syncs.changeSyncLocalRoot(backupId, std::move(newRootPath), std::move(completion)); } // When changing remote root, validate the new target const auto newRootNode = nodebyhandle(newRemoteRootNodeHandle); if (!newRootNode) { LOG_err << "changeSyncRoot: Invalid new root node handle"; return completion(API_EARGS, REMOTE_NODE_NOT_FOUND); } if (const auto [err, syncErr] = isnodesyncable(newRootNode); err != API_OK) { LOG_err << "changeSyncRoot: Given new root node is not syncable. Error: " << SyncConfig::syncErrorToStr(syncErr); return completion(err, syncErr); } syncs.changeSyncRemoteRoot(backupId, std::move(newRootNode), std::move(completion)); } void MegaClient::setSyncUploadThrottleParamsFromAPIAfterTimeout() { if (mSetSyncUploadThrottleParamsFromAPILastTime == std::chrono::steady_clock::time_point{}) return; if (const auto timeSinceLastSetSyncUploadThrottleParamsFromAPIInSeconds = timeSinceLastSetSyncUploadThrottleParamsFromAPI(); timeSinceLastSetSyncUploadThrottleParamsFromAPIInSeconds >= TIMEOUT_TO_SET_SYNC_UPLOAD_THROTTLE_PARAMS_FROM_API) { LOG_debug << "[MegaClient::setSyncUploadThrottleParamsFromAPIAfterTimeout] call " "setSyncUploadThrottleParamsFromAPI " "after " << timeSinceLastSetSyncUploadThrottleParamsFromAPIInSeconds.count() << " secs [timeout: " << TIMEOUT_TO_SET_SYNC_UPLOAD_THROTTLE_PARAMS_FROM_API.count() << " secs]"; setSyncUploadThrottleParamsFromAPI(); } } void MegaClient::handleSetThrottleResult(const CommandSetThrottlingParams::ResultVariant& result) { const auto handleError = [](const Error& err) { LOG_debug << "[MegaClient::handleSetThrottleResult] Command failed with error: " << err; }; const auto handleParams = [this](const CommandSetThrottlingParams::ThrottlingParamsFromAPI& params) { setSyncUploadThrottleUpdateRate(std::chrono::seconds(params.updateRateInSeconds), [](const error) {}); setSyncMaxUploadsBeforeThrottle(static_cast(params.maxUploadsBeforeThrottle), [](const error) {}); LOG_warn << "[MegaClient::handleSetThrottleResult] Ignoring " "uploadCounterInactivityTime: " << params.uploadCounterInactivityTime; }; std::visit(overloaded{handleError, handleParams}, result); } void MegaClient::setSyncUploadThrottleParamsFromAPI() { LOG_verbose << "[MegaClient::setSyncUploadThrottleParamsFromAPI] call [last time since last call: " << [this] { if ((mSetSyncUploadThrottleParamsFromAPILastTime == std::chrono::steady_clock::time_point{})) { return std::string("first call"); } return std::to_string(timeSinceLastSetSyncUploadThrottleParamsFromAPI().count()) + " secs"; }() << "]"; auto commandSetThrottlingParamsCompletion = [this](const CommandSetThrottlingParams::ResultVariant& result) { handleSetThrottleResult(result); }; queueCommand( new CommandSetThrottlingParams(*this, std::move(commandSetThrottlingParamsCompletion))); mSetSyncUploadThrottleParamsFromAPILastTime = std::chrono::steady_clock::now(); } void MegaClient::setSyncUploadThrottleUpdateRate(const std::chrono::seconds updateRateInSeconds, std::function&& completion) { syncs.setThrottleUpdateRate(updateRateInSeconds, std::move(completion)); } void MegaClient::setSyncMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, std::function&& completion) { syncs.setMaxUploadsBeforeThrottle(maxUploadsBeforeThrottle, std::move(completion)); } void MegaClient::syncUploadThrottleValues( std::function&& completion) { syncs.uploadThrottleValues(std::move(completion)); } void MegaClient::syncUploadThrottleValuesLimits( std::function&& completion) { syncs.uploadThrottleValuesLimits(std::move(completion)); } void MegaClient::checkSyncUploadsThrottled(std::function&& completion) { syncs.checkSyncUploadsThrottled(std::move(completion)); } void MegaClient::setSyncUploadThrottlingManager( std::shared_ptr uploadThrottlingManager, std::function&& completion) { syncs.setThrottlingManager(std::move(uploadThrottlingManager), std::move(completion)); } void MegaClient::addsync(SyncConfig&& config, std::function completion, const string& logname, const string& excludedPath) { assert(completion); error e; std::tie(e, config.mError, config.mWarning) = checkSyncConfig(config); if (e) { // the cause is already logged in checkSyncConfig completion(e, config.mError, UNDEF); return; } string deviceIdHash = getDeviceidHash(); if (deviceIdHash.empty()) { completion(API_EARGS, UNABLE_TO_RETRIEVE_DEVICE_ID, UNDEF); return; } // Are we adding an external backup? handle driveId = UNDEF; if (config.isExternal()) { const string& p = config.mExternalDrivePath.toPath(false); e = readDriveId(*fsaccess, p.c_str(), driveId); if (e != API_OK) { LOG_debug << "readDriveId failed for sync add"; completion(e, config.mError, UNDEF); return; } } // Add the sync. BackupInfoSync info(config, deviceIdHash, driveId, BackupInfoSync::getSyncState(config, xferpaused[GET], xferpaused[PUT])); queueCommand(new CommandBackupPut( this, info, [this, config, completion, logname, excludedPath](Error e, handle backupId) mutable { if (ISUNDEF(backupId) && !e) { LOG_debug << "Request for backupId failed for sync add"; e = API_EFAILED; } if (e) { LOG_warn << "Failed to register heartbeat record for new sync. Error: " << int(e); completion(e, config.mError, backupId); } else { // if we got this far, the syncConfig is kept (in db and in memory) config.mBackupId = backupId; syncs.appendNewSync(config, true, completion, true, logname, excludedPath); } })); } void MegaClient::preparebackup(SyncConfig sc, std::function completion) { // get current user User* u = ownuser(); // get handle of remote "My Backups" folder, from user attributes const UserAttribute* attribute = u ? u->getAttribute(ATTR_MY_BACKUPS_FOLDER) : nullptr; if (!attribute || !attribute->isValid()) { LOG_err << "Add backup: \"My Backups\" folder was not set"; return completion(API_EACCESS, sc, nullptr); } if (attribute->value().size() != MegaClient::NODEHANDLE) { LOG_err << "Add backup: ATTR_MY_BACKUPS_FOLDER attribute had invalid value"; return completion(API_EACCESS, sc, nullptr); } handle h = 0; memcpy(&h, attribute->value().data(), MegaClient::NODEHANDLE); if (!h || h == UNDEF) { LOG_err << "Add backup: ATTR_MY_BACKUPS_FOLDER attribute contained invalid handler value"; return completion(API_ENOENT, sc, nullptr); } // get Node of remote "My Backups" folder std::shared_ptr myBackupsNode = nodebyhandle(h); if (!myBackupsNode) { LOG_err << "Add backup: \"My Backups\" folder could not be found using the stored handle"; return completion(API_ENOENT, sc, nullptr); } // get 'device-id' string deviceId; bool isInternalDrive = sc.mExternalDrivePath.empty(); if (isInternalDrive) { deviceId = getDeviceidHash(); } else // external drive { // drive-id must have been already written to external drive, since a name was given to it handle driveId; error e = readDriveId(*fsaccess, sc.mExternalDrivePath.toPath(false).c_str(), driveId); if (e != API_OK) { LOG_err << "Add backup (external): failed to read drive id"; return completion(e, sc, nullptr); } // create the device id from the drive id deviceId = Base64Str(driveId); } if (deviceId.empty()) { LOG_err << "Add backup: invalid device id"; return completion(API_EINCOMPLETE, sc, nullptr); } // prepare for new nodes vector newnodes; nameid attrId = isInternalDrive ? AttrMap::string2nameid("dev-id") : // "device-id" would be too long AttrMap::string2nameid("drv-id"); std::function addAttrsFunc = [=](AttrMap& attrs) { attrs.map[attrId] = deviceId; }; // search for remote folder "My Backups"/`DEVICE_ID`/ std::shared_ptr deviceBackupNode = childnodebyattribute(myBackupsNode.get(), attrId, deviceId.c_str()); if (deviceBackupNode) // validate this node { if (deviceBackupNode->type != FOLDERNODE) { LOG_err << "Add backup: device-id node did not have FOLDERNODE type"; return completion(API_EACCESS, sc, nullptr); } // make sure there is no folder with the same name as the backup std::shared_ptr backupNameNode = childnodebyname(deviceBackupNode.get(), sc.mName.c_str()); if (backupNameNode) { LOG_err << "Add backup: a backup with the same name (" << sc.mName << ") already existed"; return completion(API_EACCESS, sc, nullptr); } } else // create `DEVICE_ID` remote dir { // is there a folder with the same device-id already? deviceBackupNode = childnodebyname(myBackupsNode.get(), deviceId.c_str()); if (deviceBackupNode) { LOG_err << "Add backup: new device, but a folder with the same device-id (" << deviceId << ") already existed"; return completion(API_EEXIST, sc, nullptr); } // add a new node for it newnodes.emplace_back(); NewNode& newNode = newnodes.back(); putnodes_prepareOneFolder(&newNode, deviceId, true, addAttrsFunc); newNode.nodehandle = AttrMap::string2nameid("dummy"); // any value should do, let's make it somewhat "readable" } // create backupName remote dir newnodes.emplace_back(); NewNode& backupNameNode = newnodes.back(); putnodes_prepareOneFolder(&backupNameNode, sc.mName, true); // backup node should not include dev-id/drv-id if (!deviceBackupNode) { // Set parent handle if part of the new nodes array (it cannot be from an existing node) backupNameNode.parenthandle = newnodes[0].nodehandle; } // create the new node(s) putnodes(deviceBackupNode ? deviceBackupNode->nodeHandle() : myBackupsNode->nodeHandle(), NoVersioning, std::move(newnodes), nullptr, reqtag, true, {}, // customerIpPort [completion, sc, this](const Error& e, targettype_t, vector& nn, bool /*targetOverride*/, int /*tag*/, const map& /*fileHandles*/) { if (e) { completion(e, sc, nullptr); } else { handle newBackupNodeHandle = nn.back().mAddedHandle; SyncConfig updatedConfig = sc; updatedConfig.mRemoteNode.set6byte(newBackupNodeHandle); if (std::shared_ptr backupRoot = nodeByHandle(updatedConfig.mRemoteNode)) { updatedConfig.mOriginalPathOfRemoteRootNode = backupRoot->displaypath(); } else { LOG_err << "Node created for backup is missing already"; completion(API_EEXIST, updatedConfig, nullptr); } // Offer the option to the caller, to remove the new Backup node if a later // step fails UndoFunction undoOnFail = [newBackupNodeHandle, this](std::function continuation) { if (std::shared_ptr n = nodebyhandle(newBackupNodeHandle)) { unlink(n.get(), false, 0, true, [continuation](NodeHandle, Error) { if (continuation) continuation(); }); } else { if (continuation) continuation(); } }; completion(API_OK, updatedConfig, undoOnFail); } }); } // move node to //bin, then on to the SyncDebris folder of the day (to prevent // dupes) void MegaClient::movetosyncdebris(Node* dn, bool inshare, std::function&& completion, bool canChangeVault) { execmovetosyncdebris(dn, std::move(completion), canChangeVault, inshare); } void MegaClient::execmovetosyncdebris(Node* requestedNode, std::function&& completion, bool canChangeVault, bool isInshare) { if (requestedNode) { pendingDebris.emplace_back(requestedNode->nodeHandle(), std::move(completion), isInshare, canChangeVault); } if (std::shared_ptr debrisTarget = getOrCreateSyncdebrisFolder()) { for (auto& rec : pendingDebris) { if (std::shared_ptr n = nodeByHandle(rec.nodeHandle)) { if (!rec.mIsInshare) { LOG_debug << "Moving to cloud Syncdebris: " << n->displaypath() << " in " << debrisTarget->displaypath() << " Nhandle: " << LOG_NODEHANDLE(n->nodehandle); rename(n, debrisTarget, SYNCDEL_DEBRISDAY, n->parent ? n->parent->nodeHandle() : NodeHandle(), nullptr, rec.mCanChangeVault, std::move(rec.completion)); } else { LOG_debug << "Copy and delete to cloud Syncdebris: " << n->displaypath() << " in " << debrisTarget->displaypath() << " Nhandle: " << LOG_NODEHANDLE(n->nodehandle); TreeProcCopy tc; proctree(n, &tc, false, false); tc.allocnodes(); proctree(n, &tc, false, false); tc.nn[0].parenthandle = UNDEF; putnodes(debrisTarget->nodeHandle(), NoVersioning, std::move(tc.nn), nullptr, reqtag, rec.mCanChangeVault, {}, // customerIpPort [this, rec](const Error& e, targettype_t, vector&, bool, int, const map& /*fileHandles*/) { if (e) { LOG_warn << "Error copying files at SyncDebris folder, " "continue process (remove them)"; } if (std::shared_ptr n = nodeByHandle(rec.nodeHandle)) { unlink(n.get(), false, 0, false, [rec](NodeHandle, Error e) { if (rec.completion) rec.completion(rec.nodeHandle, e); }); } else { if (rec.completion) rec.completion(rec.nodeHandle, API_EEXIST); } }); } } else { if (rec.completion) rec.completion(rec.nodeHandle, API_EEXIST); } } pendingDebris.clear(); } } std::string MegaClient::getDailyDebrisName(const m_time_t& ts) const { struct tm tms; char dailyDebrisNameBuf[32]; struct tm* ptm = m_localtime(ts, &tms); snprintf(dailyDebrisNameBuf, sizeof(dailyDebrisNameBuf), "%04d-%02d-%02d", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday); return dailyDebrisNameBuf; } std::shared_ptr MegaClient::getSyncdebrisDaily(std::shared_ptr binSyncDebrisNode, const std::string& dailyDebrisName) { if (!binSyncDebrisNode) { assert(false && "getSyncdebrisDaily: //bin/SyncDebris/ provided node is null"); return nullptr; } // locate //bin/SyncDebris/yyyy-mm-dd auto nodes = childnodesbyname(binSyncDebrisNode.get(), dailyDebrisName.c_str()); if (nodes.empty()) { return nullptr; } unsigned filesFoundWithSameName{0}; std::shared_ptr dailyDebrisNode; std::for_each(std::begin(nodes), std::end(nodes), [&filesFoundWithSameName, &dailyDebrisNode](const auto& n) { if (n->type != FOLDERNODE) { ++filesFoundWithSameName; return; } if (!dailyDebrisNode || n->ctime < dailyDebrisNode->ctime) { dailyDebrisNode = n; } }); const std::string prefix{"getSyncdebrisDaily"}; if (nodes.size() > 1) { LOG_warn << prefix << "There is " << nodes.size() << " node/s with the same name than searched SyncDebris daily " "folder: " << dailyDebrisName; } if (filesFoundWithSameName) { LOG_warn << prefix << "There is " << filesFoundWithSameName << " File node/s with the same name than searched SyncDebris daily folder: " << dailyDebrisName; } return dailyDebrisNode; } std::tuple, std::shared_ptr, std::shared_ptr, std::string> MegaClient::getSyncdebrisDailyAndAncestors(const m_time_t& ts) { const auto dailyDebrisName = getDailyDebrisName(ts); std::shared_ptr binNode = nodeByHandle(mNodeManager.getRootNodeRubbish()); if (!binNode) { return {nullptr, nullptr, nullptr, dailyDebrisName}; } assert(binNode->type == RUBBISHNODE); std::shared_ptr binSyncDebrisNode = childnodebynametype(binNode.get(), SYNCDEBRISFOLDERNAME, FOLDERNODE); if (!binSyncDebrisNode) { return {binNode, nullptr, nullptr, dailyDebrisName}; } auto dailysyncDebrisNode = getSyncdebrisDaily(binSyncDebrisNode, dailyDebrisName); return {binNode, binSyncDebrisNode, dailysyncDebrisNode, dailyDebrisName}; } std::shared_ptr MegaClient::getOrCreateSyncdebrisFolder() { bool foundDebrisNode = false; const m_time_t now = m_time(); const m_time_t currentminute = now / 60; const std::string prefix{clientname + ". getOrCreateSyncdebrisFolder: "}; auto [binNode, syncDebrisNode, dailySyncDebrisNode, dailyDebrisName] = getSyncdebrisDailyAndAncestors(now); if (!binNode) { return nullptr; } foundDebrisNode = syncDebrisNode != nullptr; if (syncDebrisNode && dailySyncDebrisNode) { return dailySyncDebrisNode; } if (syncdebrisadding || syncdebrisminute == currentminute) { // avoid excesive attempts to create daily debris return nullptr; } LOG_debug << prefix << "Creating cloud daily SyncDebris and daily folder: " << dailyDebrisName; syncdebrisadding = true; syncdebrisminute = currentminute; // create missing component(s) of the sync debris folder of the day vector nnVec; SymmCipher tkey; string tattrstring; AttrMap tattrs; nnVec.resize((foundDebrisNode) ? 1 : 2); for (size_t i = nnVec.size(); i--;) { auto nn = &nnVec[i]; nn->source = NEW_NODE; nn->type = FOLDERNODE; nn->nodehandle = i; nn->parenthandle = i ? 0 : UNDEF; nn->nodekey.resize(FOLDERNODEKEYLENGTH); rng.genblock((byte*)nn->nodekey.data(), FOLDERNODEKEYLENGTH); // set new name, encrypt and attach attributes tattrs.map['n'] = (i || foundDebrisNode) ? dailyDebrisName : SYNCDEBRISFOLDERNAME; tattrs.getjson(&tattrstring); tkey.setkey((const byte*)nn->nodekey.data(), FOLDERNODE); nn->attrstring.reset(new string); makeattr(&tkey, nn->attrstring, tattrstring.c_str()); } Pitag pitag; pitag.purpose = PitagPurpose::Sync; pitag.trigger = PitagTrigger::SyncAlgorithm; pitag.nodeType = PitagNodeType::Folder; const auto parentNodeHandle = foundDebrisNode ? syncDebrisNode->nodeHandle() : binNode->nodeHandle(); queueCommand(new CommandPutNodes( this, parentNodeHandle, NULL, NoVersioning, std::move(nnVec), 0, PUTNODES_SYNCDEBRIS, nullptr, [this, prefix, now, auxDebrisName = dailyDebrisName]( const Error& e, targettype_t, vector&, bool /*targetOverride*/, int /*tag*/, const map& /*fileHandles*/) { syncdebrisadding = false; if (e != API_OK) { LOG_err << prefix << "Putnodes failed for creating (" << auxDebrisName << "). errorCode(" << e << "). Calling execmovetosyncdebris anyway"; execmovetosyncdebris(nullptr, nullptr, false, false); return; } auto [binNode, binSyncDebrisNode, dailySyncDebrisNode, dailyDebrisName] = getSyncdebrisDailyAndAncestors(now); assert(binNode); if (!binNode || !binSyncDebrisNode || !dailySyncDebrisNode) { LOG_err << prefix << "PutNodes for dailySyncDebrisNode finished with API_OK, but node could " "not be retrieved from NodeManager." << " Bin: " << (binNode ? toHandle(binNode->nodehandle) : "UNDEF") << ", SyncDebris: " << (binSyncDebrisNode ? toHandle(binSyncDebrisNode->nodehandle) : "UNDEF") << ", SyncDebris Daily: " << (dailySyncDebrisNode ? toHandle(dailySyncDebrisNode->nodehandle) : "UNDEF"); } else { LOG_debug << prefix << "daily cloud SyncDebris folder (" << toHandle(dailySyncDebrisNode->nodehandle) << ") created successfully: " << dailyDebrisName << ". Trigger remaining debris moves: " << pendingDebris.size(); } // on completion, send the queued nodes execmovetosyncdebris(nullptr, nullptr, false, false); }, false, {}, // customerIpPort pitag)); return nullptr; } #endif string MegaClient::cypherTLVTextWithMasterKey(const char* name, const string& text) { string_map records{ {name, text} }; unique_ptr container(tlv::recordsToContainer(std::move(records), rng, key)); return Base64::btoa(*container); } string MegaClient::decypherTLVTextWithMasterKey(const char* name, const string& encoded) { string unencoded = Base64::atob(encoded); unique_ptr records{tlv::containerToRecords(unencoded, key)}; return records ? (*records)[name] : string{}; } // inject file into transfer subsystem // if file's fingerprint is not valid, it will be obtained from the local file // (PUT) or the file's key (GET) bool MegaClient::startxfer(direction_t d, File* f, TransferDbCommitter& committer, bool skipdupes, bool startfirst, bool donotpersist, VersioningOption vo, error* cause, int tag, m_off_t availableDiskSpace) { assert(f->getLocalname().isAbsolute()); f->mVersioningOption = vo; // Dummy to avoid checking later. if (!cause) { // Initializer provided to silence warnings. static error dummy = API_OK; cause = &dummy; } // Is caller trying to start a download? if (d == GET) { // Force to enable transfer resumption when app is not logged-in and attempts // to download an authorized node, retrieved by another instance of SDK logged-in // into a folder link. if (loggedin() == NOTLOGGEDIN) { enabletransferresumption(); } auto targetPath = f->getLocalname().parentPath(); assert(f->size >= 0); // Do we have enough space for the download? // fsaccess->availableDiskSpace is expensive over network driver. // Pass in a positive value, check will use this value. A use case is downloading a folder auto available = availableDiskSpace > 0 ? availableDiskSpace : fsaccess->availableDiskSpace(targetPath); if (available <= f->size) { LOG_warn << "Insufficient space available for download: " << f->getLocalname() << ": available: " << available << ", required: " << f->size; *cause = LOCAL_ENOSPC; return false; } } if (!f->transfer) { if (d == PUT) { if (!nodeByHandle(f->h) && (f->targetuser != SUPPORT_USER_HANDLE)) { // the folder to upload is unknown - perhaps this is a resumed transfer // and the folder was deleted in the meantime *cause = API_EARGS; return false; } if (!f->isvalid) // (sync LocalNodes always have this set) { // missing FileFingerprint for local file - generate auto fa = fsaccess->newfileaccess(); if (fa->fopen(f->getLocalname(), OPEN_RDONLY, FSLogging::logOnError)) { f->genfingerprint(fa.get()); } } // if we are unable to obtain a valid file FileFingerprint, don't proceed if (!f->isvalid) { LOG_err << "Unable to get a fingerprint " << f->name; *cause = API_EREAD; return false; } #ifdef USE_MEDIAINFO mediaFileInfo.requestCodecMappingsOneTime(this, f->getLocalname()); #endif } else { if (!f->isvalid) { LOG_warn << "Downloading a file with invalid fingerprint, was: " << f->fingerprintDebugString() << " name: " << f->getLocalname(); // no valid fingerprint: use filekey as its replacement memcpy(f->crc.data(), f->filekey, sizeof f->crc); LOG_warn << "Downloading a file with invalid fingerprint, adjusted to: " << f->fingerprintDebugString() << " name: " << f->getLocalname(); } } Transfer* t = NULL; auto range = multi_transfers[d].equal_range(f); auto nodexfer = nodeByHandle(f->h); for (auto it = range.first; it != range.second; ++it) { if (it->second->files.empty()) continue; if (d == GET) { File* f2 = it->second->files.front(); auto auxnode = nodeByHandle(f2->h); if (nodexfer && auxnode) { if (areEqualNodesByMetaMac(nodexfer->nodekey(), auxnode->nodekey())) { t = it->second; break; } continue; } // Fallback to original mechanism } File* f2 = it->second->files.front(); string ext1, ext2; if (fsaccess->getextension(f->getLocalname(), ext1) && fsaccess->getextension(f2->getLocalname(), ext2)) { if (!ext1.empty() && ext1[0] == '.') ext1.erase(0, 1); if (!ext2.empty() && ext2[0] == '.') ext2.erase(0, 1); if (treatAsIfFileDataEqual(*f, ext1, *f2, ext2)) { // Upload data for both these Files just once overall - two Files in one Transfer t = it->second; break; } } } if (t) { if (skipdupes) { for (file_list::iterator fi = t->files.begin(); fi != t->files.end(); fi++) { if ((d == GET && f->getLocalname() == (*fi)->getLocalname()) || (d == PUT && f->h != UNDEF && f->h == (*fi)->h && !f->targetuser.size() && !(*fi)->targetuser.size() && f->name == (*fi)->name)) { LOG_warn << "Skipping duplicated transfer"; *cause = API_EEXIST; return false; } } } f->file_it = t->files.insert(t->files.end(), f); f->transfer = t; f->tag = tag; if (!f->dbid && !donotpersist) { filecacheadd(f, committer); } app->file_added(f); if (startfirst) { transferlist.movetofirst(t, committer); } if (overquotauntil && overquotauntil > Waiter::ds && d != PUT) { dstime timeleft = dstime(overquotauntil - Waiter::ds); t->failed(API_EOVERQUOTA, committer, timeleft); } else if (d == PUT && ststatus == STORAGE_RED) { t->failed(API_EOVERQUOTA, committer); } else if (ststatus == STORAGE_PAYWALL) { t->failed(API_EPAYWALL, committer); } } else { // there is no existing transfer uploading this file (or any duplicate of it) // check if there used to be, and can we resume one. // Note that these multi_cachedtransfers always have an empty Files list, those are not attached when loading from db // Only the Transfer's own localpath field tells us the path it was uploading auto rangeCached = multi_cachedtransfers[d].equal_range(f); for (auto& it = rangeCached.first; it != rangeCached.second; ++it) { assert(it->second->files.empty()); if (it->second->localfilename.empty()) continue; if (d == PUT) { // for uploads, check for the same source file if (it->second->localfilename == f->getLocalname()) { // the exact same file, so use this one (fingerprint is double checked below) t = it->second; multi_cachedtransfers[d].erase(it); break; } } else { // for downloads, check for the same source node if (it->second->downloadFileHandle == f->h && !it->second->downloadFileHandle.isUndef()) { // the exact same cloud file, so use this one t = it->second; multi_cachedtransfers[d].erase(it); break; } } } if (!t && d == PUT) { // look to see if there a cached transfer that is similar enough // this case could occur if there were multiple Files before the transfer // got suspended (eg by app exit), and we are considering the File of one of the others // than the actual file path that was being uploaded. for (auto& it = rangeCached.first; it != rangeCached.second; ++it) { assert(it->second->files.empty()); if (it->second->localfilename.empty()) continue; string ext1, ext2; if (fsaccess->getextension(f->getLocalname(), ext1) && fsaccess->getextension(it->second->localfilename, ext2)) { if (!ext1.empty() && ext1[0] == '.') ext1.erase(0, 1); if (!ext2.empty() && ext2[0] == '.') ext2.erase(0, 1); if (treatAsIfFileDataEqual(*f, ext1, *it->first, ext2)) { t = it->second; multi_cachedtransfers[d].erase(it); break; } } } } const auto currentTime = m_time(); if (t) { t->discardTempUrlsIfNoDataDownloadedOrTimeoutReached(d, currentTime); auto fa = fsaccess->newfileaccess(); if (t->localfilename.empty() || !fa->fopen(t->localfilename, FSLogging::logExceptFileNotFound)) { if (d == PUT) { if (!t->localfilename.empty()) { // if empty, we had not started the upload. The real path is in the first File LOG_warn << "Local file not found: " << t->localfilename; } // the transfer will be retried to ensure that the file // is not just just temporarily blocked } else { if (const auto hadAnyData = (t->pos != 0); hadAnyData) { LOG_warn << "Temporary file not found:" << t->localfilename; } t->localfilename.clear(); t->chunkmacs.clear(); t->setProgresscompleted(0); t->pos = 0; } } else { if (d == PUT) { if (f->genfingerprint(fa.get())) { LOG_warn << "The local file has been modified: " << t->localfilename; t->tempurls.clear(); t->chunkmacs.clear(); t->setProgresscompleted(0); t->ultoken.reset(); t->pos = 0; } } else { if (t->progresscompleted > fa->size) { LOG_warn << "Truncated temporary file: " << t->localfilename; t->chunkmacs.clear(); t->setProgresscompleted(0); t->pos = 0; } } } LOG_debug << "Transfer resumed"; } if (!t) { t = new Transfer(this, d); *(FileFingerprint*)t = *(FileFingerprint*)f; } t->skipserialization = donotpersist; t->lastaccesstime = currentTime; t->tag = tag; f->tag = tag; t->transfers_it = multi_transfers[d].insert(pair((FileFingerprint*)t, t)); f->file_it = t->files.insert(t->files.end(), f); f->transfer = t; if (!f->dbid && !donotpersist) { filecacheadd(f, committer); } transferlist.addtransfer(t, committer, startfirst); app->transfer_added(t); app->file_added(f); if (overquotauntil && overquotauntil > Waiter::ds && d != PUT) { dstime timeleft = dstime(overquotauntil - Waiter::ds); t->failed(API_EOVERQUOTA, committer, timeleft); } else if (d == PUT && ststatus == STORAGE_RED) { t->failed(API_EOVERQUOTA, committer); } else if (ststatus == STORAGE_PAYWALL) { t->failed(API_EPAYWALL, committer); } } assert( (f->h.isUndef() && f->targetuser.size() && (f->targetuser.size() == 11 || f->targetuser.find("@")!=string::npos) ) // <- uploading to inbox || (!f->h.isUndef() && (nodeByHandle(f->h) || d == GET) )); // target handle for the upload should be known at this time (except for inbox uploads) } *cause = API_OK; return true; } // remove file from transfer subsystem void MegaClient::stopxfer(File* f, TransferDbCommitter* committer) { if (f->transfer) { LOG_debug << "Stopping transfer: " << f->name; Transfer *transfer = f->transfer; transfer->removeTransferFile(API_EINCOMPLETE, f, committer); // last file for this transfer removed? shut down transfer. if (!transfer->files.size()) { transfer->removeAndDeleteSelf(TRANSFERSTATE_CANCELLED); } else { if (transfer->type == PUT && !transfer->localfilename.empty()) { LOG_debug << "Updating transfer path"; transfer->files.front()->prepare(*fsaccess); } } } } // pause/unpause transfers void MegaClient::pausexfers(direction_t d, bool pause, bool hard, TransferDbCommitter& committer) { xferpaused[d] = pause; if (!pause || hard) { WAIT_CLASS::bumpds(); for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); ) { if ((*it)->transfer->type == d) { if (pause) { if (hard) { (*it++)->disconnect(); } } else { (*it)->lastdata = Waiter::ds; (*it++)->doio(this, committer); } } else { it++; } } } #ifdef ENABLE_SYNC syncs.transferPauseFlagsUpdated(xferpaused[GET], xferpaused[PUT]); #endif } error MegaClient::transferRemoteCopy(File* file, std::shared_ptr sameNode, const string& name, std::shared_ptr parent, int tag, std::optional overridenFp, std::optional inboxTarget) { assert(file); TreeProcCopy tc; proctree(sameNode, &tc, false, true); tc.allocnodes(); proctree(sameNode, &tc, false, true); tc.nn[0].parenthandle = UNDEF; SymmCipher nodeKey; AttrMap attrs; string attrstring; nodeKey.setkey((const byte*)tc.nn[0].nodekey.data(), sameNode->type); string sname = name; LocalPath::utf8_normalize(&sname); attrs.map['n'] = sname; if (overridenFp.has_value()) { overridenFp->serializefingerprint(&attrs.map['c']); } else { // avoid serializing FP again attrs.map['c'] = sameNode->attrs.map['c']; } attrs.getjson(&attrstring); makeattr(&nodeKey, tc.nn[0].attrstring, attrstring.c_str()); auto pitag = file->getPitag(); if (pitag && pitag->target == PitagTarget::NotApplicable) { const bool inIncomingShare = parent && parent->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); pitag->target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } if (tc.nn[0].type == FILENODE) { if (std::shared_ptr ovn = getovnode(parent.get(), &sname)) { tc.nn[0].ovhandle = ovn->nodeHandle(); } } if (inboxTarget.has_value()) { putnodes(inboxTarget.value().c_str(), std::move(tc.nn), tag); } else { if (!parent) { LOG_err << "SendPendingTransfers(upload): invalid parent for " << sname; assert(false && "SendPendingTransfers(upload): invalid parent"); return API_EARGS; } putnodes(parent->nodeHandle(), UseLocalVersioningFlag, std::move(tc.nn), nullptr, tag, false, {}, // customerIpPort nullptr, pitag); } // Delete Transfer and File // Notify data transfer complete, pending putnodes Transfer* t = file->transfer; vector& ids = pendingtcids[tag]; assert(t->files.size() == 1); ids.push_back(file->dbid); ids.push_back(t->dbid); app->file_complete(file); file->transfer = NULL; delete file; t->files.clear(); app->transfer_complete(t); delete t; return API_OK; } void MegaClient::setmaxconnections(direction_t d, int num) { if (num > 0) { if ((unsigned int) num > MegaClient::MAX_NUM_CONNECTIONS) { num = MegaClient::MAX_NUM_CONNECTIONS; } if (connections[d] != num) { LOG_debug << "[MegaClient::setmaxconnections] Set max parallel " << connDirectionToStr(d) << " connections per transfer to " << num << " [prev: " << connections[d] << "]"; connections[d] = (unsigned char)num; for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); ) { TransferSlot *slot = *it++; if (slot->transfer->type == d) { slot->transfer->state = TRANSFERSTATE_QUEUED; if (slot->transfer->client->ststatus != STORAGE_RED || slot->transfer->type == GET) { slot->transfer->bt.arm(); } delete slot; } } } } } #if 0 std::shared_ptr MegaClient::nodebyfingerprint(LocalNode* localNode) { sharedNode_vector remoteNodes = mNodeManager.getNodesByFingerprint(*localNode); if (remoteNodes.empty()) return nullptr; // Only compare metamac if the node doesn't already exist. sharedNode_vector::const_iterator remoteNode = std::find_if(remoteNodes.begin(), remoteNodes.end(), [&](std::shared_ptr remoteNode) -> bool { return localNode->toName_of_localname == remoteNode->displayname(); }); if (remoteNode != remoteNodes.end()) return *remoteNode; remoteNode = remoteNodes.begin(); // Compare the local file's metamac against a random candidate. // // If we're unable to generate the metamac, fail in such a way that // guarantees safe behavior. // // That is, treat both nodes as distinct until we're absolutely certain // they are identical. auto ifAccess = fsaccess->newfileaccess(); auto localPath = localNode->getLocalPath(); if (!ifAccess->fopen(localPath, true, false, FSLogging::logOnError)) return nullptr; std::string remoteKey = (*remoteNode)->nodekey(); const char *iva = &remoteKey[SymmCipher::KEYLENGTH]; SymmCipher cipher; cipher.setkey((byte*)&remoteKey[0], (*remoteNode)->type); int64_t remoteIv = MemAccess::get(iva); int64_t remoteMac = MemAccess::get(iva + sizeof(int64_t)); auto result = generateMetaMac(cipher, *ifAccess, remoteIv); if (!result.first || result.second != remoteMac) return nullptr; return *remoteNode; } #endif /* ENABLE_SYNC */ bool MegaClient::nodeIsMedia(const Node *n, bool *isphoto, bool *isvideo) const { if (n->type != FILENODE) { return false; } bool a = n->isIncludedForMimetype(MimeType_t::MIME_TYPE_PHOTO); if (isphoto) { *isphoto = a; } if (a && !isvideo) { return true; } bool b = n->isIncludedForMimetype(MimeType_t::MIME_TYPE_VIDEO); if (isvideo) { *isvideo = b; } return a || b; } bool MegaClient::nodeIsVideo(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_VIDEO); } bool MegaClient::nodeIsPhoto(const Node *n, bool checkPreview) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_PHOTO, checkPreview); } bool MegaClient::nodeIsAudio(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_AUDIO); } bool MegaClient::nodeIsDocument(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_DOCUMENT); } bool MegaClient::nodeIsPdf(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_PDF); } bool MegaClient::nodeIsPresentation(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_PRESENTATION); } bool MegaClient::nodeIsArchive(const Node* n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_ARCHIVE); } bool MegaClient::nodeIsProgram(const Node* n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_PROGRAM); } bool MegaClient::nodeIsMiscellaneous(const Node* n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_MISC); } bool MegaClient::nodeIsSpreadsheet(const Node *n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_SPREADSHEET); } bool MegaClient::nodeIsOtherType(const Node* n) const { return n->isIncludedForMimetype(MimeType_t::MIME_TYPE_OTHERS); } bool MegaClient::treatAsIfFileDataEqual(const FileFingerprint& node1, const LocalPath& file2, const std::string& filenameExtensionLowercaseNoDot) const { // if equal, upload or download could be skipped if (filenameExtensionLowercaseNoDot.empty()) return false; assert(filenameExtensionLowercaseNoDot[0] != '.'); FileFingerprint fp; auto fa = fsaccess->newfileaccess(); if (fa->fopen(file2, OPEN_RDONLY, FSLogging::logOnError)) { if (!fp.genfingerprint(fa.get())) return false; if (fp != node1) return false; if (!fp.isvalid || !node1.isvalid) return false; // In future (for non-media files) we might recalculate the MAC // of the on-disk file to see if it matches the Node's MAC. // That would be much, much more accurate return fp.size > 128 * 1024 && isPhotoVideoAudioByName(filenameExtensionLowercaseNoDot); } return false; } bool MegaClient::treatAsIfFileDataEqual(const FileFingerprint& fp1, const std::string& filenameExtensionLowercaseNoDot1, const FileFingerprint& fp2, const std::string& filenameExtensionLowercaseNoDot2) const { // if equal, upload or download could be skipped or combined assert(filenameExtensionLowercaseNoDot1.empty() || filenameExtensionLowercaseNoDot1[0] != '.'); assert(filenameExtensionLowercaseNoDot2.empty() || filenameExtensionLowercaseNoDot2[0] != '.'); if (filenameExtensionLowercaseNoDot1.empty() || filenameExtensionLowercaseNoDot2.empty()) return false; if (filenameExtensionLowercaseNoDot1 != filenameExtensionLowercaseNoDot2) return false; if (!fp1.isvalid || !fp2.isvalid) return false; if (fp1 != fp2) return false; // In future (for non-media files) we might recalculate the MAC // of the on-disk file to see if it matches the Node's MAC. // That would be much, much more accurate. (more parameters will be needed) return fp1.size > 128 * 1024 && isPhotoVideoAudioByName(filenameExtensionLowercaseNoDot1); } recentactions_vector MegaClient::getRecentActions(unsigned maxcount, m_time_t since, bool excludeSensitives) { return RecentActions(this).getRecentActions(maxcount, since, excludeSensitives); } error MegaClient::getRecentActionById(const char* id, recentaction& output) { return RecentActions(this).getById(id, output); } error MegaClient::getRecentActionById(const char* id, bool excludeSensitives, recentaction& output) { return RecentActions(this).getById(id, excludeSensitives, output); } // a chunk transfer request failed: record failed protocol & host void MegaClient::setchunkfailed(string* url) { if (!chunkfailed && url->size() > 19) { LOG_debug << "Adding badhost report for URL " << *url; chunkfailed = true; httpio->success = false; // record protocol and hostname if (badhosts.size()) { badhosts.append(","); } const char* ptr = url->c_str()+4; if (*ptr == 's') { badhosts.append("S"); ptr++; } badhosts.append(ptr+6,7); btbadhost.reset(); } } void MegaClient::reportevent(const char* event, const char* details) { LOG_err << "SERVER REPORT: " << event << " DETAILS: " << details; queueCommand( new CommandSendReport(this, event, details, Base64Str(me))); } void MegaClient::reportevent(const char* event, const char* details, int tag) { int creqtag = reqtag; reqtag = tag; reportevent(event, details); reqtag = creqtag; } bool MegaClient::setmaxdownloadspeed(m_off_t bpslimit) { return httpio->setmaxdownloadspeed(bpslimit >= 0 ? bpslimit : 0); } bool MegaClient::setmaxuploadspeed(m_off_t bpslimit) { return httpio->setmaxuploadspeed(bpslimit >= 0 ? bpslimit : 0); } m_off_t MegaClient::getmaxdownloadspeed() { return httpio->getmaxdownloadspeed(); } m_off_t MegaClient::getmaxuploadspeed() { return httpio->getmaxuploadspeed(); } std::shared_ptr MegaClient::getovnode(Node *parent, string *name) { if (parent && name) { return childnodebyname(parent, name->c_str(), true); } return nullptr; } sharedNode_list MegaClient::getChildren(const Node* parent, CancelToken cancelToken, bool includeVersions) { return mNodeManager.getChildren(parent, cancelToken, includeVersions); } size_t MegaClient::getNumberOfChildren(NodeHandle parentHandle) { return mNodeManager.getNumberOfChildrenFromNode(parentHandle); } bool MegaClient::loggedIntoFolder() const { return !ISUNDEF(mFolderLink.mPublicHandle); } bool MegaClient::loggedIntoWritableFolder() const { return loggedIntoFolder() && !mFolderLink.mWriteAuth.empty(); } std::string MegaClient::getAuthURI(bool supressSID, bool supressAuthKey) { string auth; if (loggedIntoFolder()) { auth.append("&n="); auth.append(Base64Str(mFolderLink.mPublicHandle)); if (!supressAuthKey) { auth.append(mFolderLink.mWriteAuth); } if (!supressSID && !mFolderLink.mAccountAuth.empty()) { auth.append("&sid="); auth.append(mFolderLink.mAccountAuth); } } else { if (!supressSID && !sid.empty()) { auth.append("&sid="); auth.append(Base64::btoa(sid)); } if (inPublicSetPreview()) { auth.append("&s="); auth.append(Base64Str(mPreviewSet->mPublicId)); } } return auth; } void MegaClient::userfeedbackstore(const char *message) { string type = "feedback."; string base64userAgent; base64userAgent.resize(useragent.size() * 4 / 3 + 4); Base64::btoa((byte*)useragent.data(), useragent.size(), (char*)base64userAgent.data()); type.append(base64userAgent); queueCommand(new CommandSendReport(this, type.c_str(), message, NULL)); } void MegaClient::sendevent(int event, const char *desc, const char* viewId, bool addJourneyId) { LOG_warn << clientname << "Event " << event << ": " << desc; queueCommand(new CommandSendEvent(this, event, desc, addJourneyId, viewId)); } void MegaClient::sendevent(int event, const char *message, int tag, const char *viewId, bool addJourneyId) { int creqtag = reqtag; reqtag = tag; sendevent(event, message, viewId, addJourneyId); reqtag = creqtag; } void MegaClient::supportticket(const char *message, int type) { queueCommand(new CommandSupportTicket(this, message, type)); } void MegaClient::cleanrubbishbin() { queueCommand(new CommandCleanRubbishBin(this)); } #ifdef ENABLE_CHAT void MegaClient::createChat(bool group, bool publicchat, const userpriv_vector* userpriv, const string_map* userkeymap, const char* title, bool meetingRoom, int chatOptions, const ScheduledMeeting* schedMeeting) { queueCommand(new CommandChatCreate(this, group, publicchat, userpriv, userkeymap, title, meetingRoom, chatOptions, schedMeeting)); } void MegaClient::inviteToChat(handle chatid, handle uh, int priv, const char *unifiedkey, const char *title) { queueCommand(new CommandChatInvite(this, chatid, uh, (privilege_t)priv, unifiedkey, title)); } void MegaClient::removeFromChat(handle chatid, handle uh) { queueCommand(new CommandChatRemove(this, chatid, uh)); } void MegaClient::getUrlChat(handle chatid) { queueCommand(new CommandChatURL(this, chatid)); } void MegaClient::setChatMode(TextChat* chat, bool pubChat) { if (!chat) { LOG_warn << "setChatMode: Invalid chat provided"; return; } if (chat->setMode(pubChat) == API_EACCESS) { std::string msg = "setChatMode: trying to convert a chat from private into public. chatid: " + std::string(Base64Str(chat->getChatId())); sendevent(99476, msg.c_str(), 0); LOG_warn << msg; } } userpriv_vector *MegaClient::readuserpriv(JSON *j) { userpriv_vector *userpriv = NULL; if (j->enterarray()) { while(j->enterobject()) { handle uh = UNDEF; privilege_t priv = PRIV_UNKNOWN; bool readingUsers = true; while(readingUsers) { switch (j->getnameid()) { case name_id::u: uh = j->gethandle(MegaClient::USERHANDLE); break; case makeNameid("p"): priv = (privilege_t) j->getint(); break; case EOO: if(uh == UNDEF || priv == PRIV_UNKNOWN) { delete userpriv; j->leavearray(); return NULL; } if (!userpriv) { userpriv = new userpriv_vector; } userpriv->push_back(userpriv_pair(uh, priv)); readingUsers = false; break; default: if (!j->storeobject()) { delete userpriv; j->leavearray(); return NULL; } break; } } j->leaveobject(); } j->leavearray(); } return userpriv; } void MegaClient::grantAccessInChat(handle chatid, handle h, const char* peer) { queueCommand(new CommandChatGrantAccess(this, chatid, h, peer)); } void MegaClient::removeAccessInChat(handle chatid, handle h, const char* peer) { queueCommand(new CommandChatRemoveAccess(this, chatid, h, peer)); } void MegaClient::updateChatPermissions(handle chatid, handle uh, int priv) { queueCommand(new CommandChatUpdatePermissions(this, chatid, uh, (privilege_t)priv)); } void MegaClient::truncateChat(handle chatid, handle messageid) { queueCommand(new CommandChatTruncate(this, chatid, messageid)); } void MegaClient::setChatTitle(handle chatid, const char *title) { queueCommand(new CommandChatSetTitle(this, chatid, title)); } void MegaClient::getChatPresenceUrl() { queueCommand(new CommandChatPresenceURL(this)); } void MegaClient::registerPushNotification(int deviceType, const char *token) { queueCommand(new CommandRegisterPushNotification(this, deviceType, token)); } void MegaClient::archiveChat(handle chatid, bool archived) { queueCommand(new CommandArchiveChat(this, chatid, archived)); } void MegaClient::richlinkrequest(const char *url) { queueCommand(new CommandRichLink(this, url)); } void MegaClient::chatlink(handle chatid, bool del, bool createifmissing) { queueCommand(new CommandChatLink(this, chatid, del, createifmissing)); } void MegaClient::chatlinkurl(handle publichandle) { queueCommand(new CommandChatLinkURL(this, publichandle)); } void MegaClient::chatlinkclose(handle chatid, const char *title) { queueCommand(new CommandChatLinkClose(this, chatid, title)); } void MegaClient::chatlinkjoin(handle publichandle, const char *unifiedkey) { queueCommand(new CommandChatLinkJoin(this, publichandle, unifiedkey)); } void MegaClient::setchatretentiontime(handle chatid, unsigned period) { queueCommand(new CommandSetChatRetentionTime(this, chatid, period)); } error MegaClient::parseScheduledMeetings(std::vector>& schedMeetings, bool parsingOccurrences, JSON *j, bool parseOnce, handle* ou, UserAlert::UpdatedScheduledMeeting::Changeset* cs, handle_set* childMeetingsDeleted) { /* - if any parsing error occurs: this method returns API_EINTERNAL, and schedMeetings vector will * contain those valid scheduled meetings already parsed * - if no parsing error but any sched meeting is considered ill-formed by ScheduledMeeting::isValid(): * that sched meeting won't be added added to schedMeetings vector, and we'll continue processing JSON */ JSON* auxJson = j; bool illFormedElems = false; bool parse = parseOnce ? true // parse a single object : auxJson->enterobject(); // parse an array of objects while (parse) { bool exit = false; handle chatid = UNDEF; handle organizerUserId = UNDEF; handle schedId = UNDEF; handle parentSchedId = UNDEF; handle originatingUser = UNDEF; std::string timezone; m_time_t startDateTime = mega_invalid_timestamp; m_time_t endDateTime = mega_invalid_timestamp; std::string title; std::string description; std::string attributes; m_time_t overrides = mega_invalid_timestamp; int cancelled = 0; std::unique_ptr flags; std::unique_ptr rules; while (!exit) { switch (auxJson->getnameid()) { case makeNameid("cid"): // chatid { chatid = auxJson->gethandle(MegaClient::CHATHANDLE); break; } case makeNameid("id"): // scheduled meeting id { schedId = auxJson->gethandle(MegaClient::CHATHANDLE); break; } case makeNameid("p"): // parent callid { parentSchedId = auxJson->gethandle(MegaClient::CHATHANDLE); break; } case name_id::u: // organizer user Handle { organizerUserId = auxJson->gethandle(MegaClient::CHATHANDLE); break; } case makeNameid("ou"): // originating user { assert(ou); originatingUser = auxJson->gethandle(MegaClient::USERHANDLE); break; } case makeNameid("tz"): // timezone { auxJson->storeobject(&timezone); break; } case makeNameid("s"): // start date time { startDateTime = auxJson->getint(); break; } case makeNameid("e"): // end date time { endDateTime = auxJson->getint(); break; } case makeNameid("t"): // title { auxJson->storeobject(&title); break; } case name_id::d: // description { auxJson->storeobject(&description); break; } case makeNameid("at"): // attributes { auxJson->storeobject(&attributes); break; } case makeNameid("o"): // override { overrides = auxJson->getint(); break; } case name_id::c: // cancelled { cancelled = static_cast(auxJson->getint()); break; } case makeNameid("f"): // flags { flags.reset(new ScheduledFlags(static_cast(auxJson->getint()))); break; } case makeNameid("r"): // scheduled meeting rules { if (auxJson->enterobject()) { string freq; m_time_t until = mega_invalid_timestamp; int interval = ScheduledRules::INTERVAL_INVALID; ScheduledRules::rules_vector vWeek; ScheduledRules::rules_vector vMonth; ScheduledRules::rules_map mMonth; bool exitRules = false; while (!exitRules) { switch (auxJson->getnameid()) { case makeNameid("f"): { auxJson->storeobject(&freq); break; } case makeNameid("i"): { interval = static_cast(auxJson->getint()); break; } case name_id::u: { until = auxJson->getint(); break; } case makeNameid("wd"): { if (auxJson->enterarray()) { while(auxJson->isnumeric()) { vWeek.emplace_back(static_cast(auxJson->getint())); } auxJson->leavearray(); } break; } case makeNameid("md"): { if (auxJson->enterarray()) { while(auxJson->isnumeric()) { vMonth.emplace_back(static_cast(auxJson->getint())); } auxJson->leavearray(); } break; } case makeNameid("mwd"): { if (auxJson->enterarray()) { while (auxJson->enterarray()) { int8_t tempKey = -1; int8_t value = -1; int i = 0; while (auxJson->isnumeric()) { int8_t val = static_cast(auxJson->getint()); if (i == 0) { tempKey = val; } if (i == 1) { value = val; } i++; } if (i > 2 || tempKey == -1 || value == -1) // ensure that each array just contains // a pair of elemements { LOG_err << "scheduled meetings rules component mwd, is malformed"; } else { mMonth.emplace(tempKey, value); } auxJson->leavearray(); } auxJson->leavearray(); } break; } case EOO: { exitRules = true; break; } default: { if (!auxJson->storeobject()) { return API_EINTERNAL; } } } } auxJson->leaveobject(); rules.reset(new ScheduledRules(ScheduledRules::stringToFreq(freq.c_str()), interval, until, &vWeek, &vMonth, &mMonth)); } break; } case makeNameid("cs"): { assert(cs); if (auxJson->enterobject()) { if (parseScheduledMeetingChangeset(auxJson, cs) != API_OK) { LOG_err << "UpdatedScheduledMeeting user alert ctor: error parsing cs array"; assert(false); return API_EINTERNAL; } auxJson->leaveobject(); } break; } case makeNameid("cmd"): { assert(childMeetingsDeleted); if (auxJson->enterarray() && childMeetingsDeleted) { while(auxJson->ishandle(MegaClient::CHATHANDLE)) { childMeetingsDeleted->insert( auxJson->gethandle(MegaClient::CHATHANDLE)); } auxJson->leavearray(); } break; } case EOO: { exit = true; if (!parseOnce) { auxJson->leaveobject(); } // note: we need to B64 decode the following params: timezone, title, description, attributes std::unique_ptr auxMeet(new ScheduledMeeting(chatid, Base64::atob(timezone), startDateTime, endDateTime, Base64::atob(title), Base64::atob(description), organizerUserId, schedId, parentSchedId, cancelled, Base64::atob(attributes), overrides, flags.get(), rules.get())); if ((parsingOccurrences && rules) || !auxMeet->isValid()) { illFormedElems = true; } else { schedMeetings.emplace_back(std::move(auxMeet)); if (ou) *ou = originatingUser; } break; } default: { if (!auxJson->storeobject()) { return API_EINTERNAL; } } } } parse = parseOnce ? false : auxJson->enterobject(); } if (illFormedElems) { reportInvalidSchedMeeting(); } return API_OK; } error MegaClient::parseScheduledMeetingChangeset(JSON* j, UserAlert::UpdatedScheduledMeeting::Changeset* cs) { error e = API_OK; bool keepParsing = true; auto wasFieldUpdated = [&j]() { bool updated = false; if (j->enterarray()) { j->storeobject(); updated = j->storeobject(); // if it is an array, it didn't change unless there are 2 values j->leavearray(); } else { int v = static_cast(j->getint()); if (v == 1) { //field has changed but don't receive old|new values due to size reasons updated = true; } else { LOG_err << "ScheduledMeetings: Expected a different flag to indicate updated " << "field. Expected 1 received " << v; assert(false); } } return updated; }; auto getOldNewStrValues = [&j](UserAlert::UpdatedScheduledMeeting::Changeset::StrChangeset& cs, const char *fieldMsg) { if (!j->enterarray()) { LOG_err << "ScheduledMeetings: Received updated SM with updated " << fieldMsg << ". Array could not be accessed, ill-formed Json"; assert(false); return API_EINTERNAL; } if (!j->storeobject(&cs.oldValue)) { cs.oldValue.clear(); } if (!j->storeobject(&cs.newValue)) { cs.newValue.clear(); } j->leavearray(); return API_OK; }; auto getOldNewTsValues = [&j](UserAlert::UpdatedScheduledMeeting::Changeset::TsChangeset& cs, const char *fieldMsg) { if (!j->enterarray()) { LOG_err << "ScheduledMeetings: Received updated SM with updated " << fieldMsg << ". Array could not be accessed, ill-formed Json"; assert(false); return API_EINTERNAL; } auto getTsVal = [&j](m_time_t& out) { out = mega_invalid_timestamp; if (j->isnumeric()) { auto val = j->getint(); if (val > -1) { out = val; } } }; getTsVal(cs.oldValue); getTsVal(cs.newValue); j->leavearray(); return API_OK; }; UserAlert::UpdatedScheduledMeeting::Changeset auxCS; using Changeset = UserAlert::UpdatedScheduledMeeting::Changeset; do { switch(j->getnameid()) { case makeNameid("t"): { Changeset::StrChangeset tCs; auto err = getOldNewStrValues(tCs, "Title"); if (err == API_OK) { if (!tCs.oldValue.empty() && !tCs.newValue.empty()) { auxCS.addChange(Changeset::CHANGE_TYPE_TITLE, &tCs); } // else => item unchanged, but old value provided for rendering purposes } else if (err == API_EINTERNAL && !j->storeobject()) { return API_EINTERNAL; } } break; case name_id::d: if (wasFieldUpdated()) { auxCS.addChange(Changeset::CHANGE_TYPE_DESCRIPTION); } break; case name_id::c: if (wasFieldUpdated()) { auxCS.addChange(Changeset::CHANGE_TYPE_CANCELLED); } break; case makeNameid("tz"): { Changeset::StrChangeset tzCs; auto err = getOldNewStrValues(tzCs, "TimeZone"); if (err == API_OK) { if (!tzCs.oldValue.empty() && !tzCs.newValue.empty()) { auxCS.addChange(Changeset::CHANGE_TYPE_TIMEZONE, &tzCs); } // else => item unchanged, but old value provided for rendering purposes } else if (err == API_EINTERNAL && !j->storeobject()) { return API_EINTERNAL; } } break; case makeNameid("s"): { Changeset::TsChangeset sdCs; auto err = getOldNewTsValues(sdCs, "StartDateTime"); if (err == API_OK) { if (sdCs.oldValue != mega_invalid_timestamp && sdCs.newValue != mega_invalid_timestamp) { auxCS.addChange(Changeset::CHANGE_TYPE_STARTDATE, nullptr, &sdCs); } // else => item unchanged, but old value provided for rendering purposes } else if (err == API_EINTERNAL && !j->storeobject()) { return API_EINTERNAL; } } break; case makeNameid("e"): { Changeset::TsChangeset edCs; auto err = getOldNewTsValues(edCs, "EndDateTime"); if (err == API_OK) { if (edCs.oldValue != mega_invalid_timestamp && edCs.newValue != mega_invalid_timestamp) { auxCS.addChange(Changeset::CHANGE_TYPE_ENDDATE, nullptr, &edCs); } // else => item unchanged, but old value provided for rendering purposes } else if (err == API_EINTERNAL && !j->storeobject()) { return API_EINTERNAL; } } break; case makeNameid("r"): /* - empty rules field => scheduled meeting doesn't have rules * - rules array with 1 element => rules not modified * - rules array with 2 elements => rules modified [old value, new value] * + note: if 2º value in array is empty, rules have been removed */ if (wasFieldUpdated()) { auxCS.addChange(Changeset::CHANGE_TYPE_RULES); } break; case EOO: keepParsing = false; *cs = std::move(auxCS); break; default: if (!j->storeobject()) { return API_EINTERNAL; } break; } } while (keepParsing); return e; } void MegaClient::clearSchedOccurrences(TextChat& chat) { chat.clearUpdatedSchedMeetingOccurrences(); chat.changed.schedOcurrReplace = true; } void MegaClient::reportInvalidSchedMeeting(const ScheduledMeeting* sched) { std::string errMsg = "Ill-formed sched meeting(s)"; sendevent(99481, errMsg.c_str()); if (sched) { errMsg.append(" chatid: ").append(toHandle(sched->chatid())) .append(" schedid: ").append(toHandle(sched->schedId())); } LOG_err << errMsg; assert(false); } #endif void MegaClient::getaccountachievements(AchievementsDetails *details) { queueCommand(new CommandGetMegaAchievements(this, details)); } void MegaClient::getmegaachievements(AchievementsDetails *details) { queueCommand(new CommandGetMegaAchievements(this, details, false)); } bool MegaClient::startDriveMonitor() { #ifdef USE_DRIVE_NOTIFICATIONS auto notify = std::bind(&Waiter::notify, waiter); return mDriveInfoCollector.start(notify); #else return false; #endif } void MegaClient::stopDriveMonitor() { #ifdef USE_DRIVE_NOTIFICATIONS mDriveInfoCollector.stop(); #endif } bool MegaClient::driveMonitorEnabled() { #ifdef USE_DRIVE_NOTIFICATIONS return mDriveInfoCollector.enabled(); #else return false; #endif } const char* MegaClient::newsignupLinkPrefix() { static constexpr const char* prefix = "newsignup"; return prefix; } const char* MegaClient::confirmLinkPrefix() { static constexpr const char* prefix = "confirm"; return prefix; } const char* MegaClient::verifyLinkPrefix() { static constexpr const char* prefix = "verify"; return prefix; } const char* MegaClient::recoverLinkPrefix() { static constexpr const char* prefix = "recover"; return prefix; } const char* MegaClient::cancelLinkPrefix() { static constexpr const char* prefix = "cancel"; return prefix; } #ifdef MEGA_MEASURE_CODE extern CodeCounter::ScopeStats computeSyncSequencesStats; std::string MegaClient::PerformanceStats::report(bool reset, HttpIO* httpio, Waiter* waiter, const RequestDispatcher& reqs) { std::ostringstream s; s << prepareWait.report(reset) << "\n" << doWait.report(reset) << "\n" << checkEvents.report(reset) << "\n" << execFunction.report(reset) << "\n" << megaapiSendPendingTransfers.report(reset) << "\n" << transferslotDoio.report(reset) << "\n" << execdirectreads.report(reset) << "\n" << transferComplete.report(reset) << "\n" << dispatchTransfers.report(reset) << "\n" << applyKeys.report(reset) << "\n" << scProcessingTime.report(reset) << "\n" << csResponseProcessingTime.report(reset) << "\n" << csSuccessProcessingTime.report(reset) << "\n" #ifdef ENABLE_SYNC << recursiveSyncTime.report(reset) << "\n" << computeSyncTripletsTime.report(reset) << "\n" << computeSyncSequencesStats.report(reset) << "\n" << ScanService::syncScanTime.report(reset) << "\n" << inferSyncTripletsTime.report(reset) << "\n" << g_compareUtfTimings.report(reset) << "\n" << syncItem.report(reset) << "\n" << syncItemCheckMove.report(reset) << "\n" << syncItemXXX.report(reset) << "\n" << syncItemXXF.report(reset) << "\n" << syncItemXSX.report(reset) << "\n" << syncItemXSF.report(reset) << "\n" << syncItemCXX.report(reset) << "\n" << syncItemCXF.report(reset) << "\n" << syncItemCSX.report(reset) << "\n" << syncItemCSF.report(reset) << "\n" << clientThreadActions.report(reset) << "\n" #endif << " cs Request waiting time: " << csRequestWaitTime.report(reset) << "\n" << " cs requests sent/received: " << reqs.csRequestsSent << "/" << reqs.csRequestsCompleted << " batches: " << reqs.csBatchesSent << "/" << reqs.csBatchesReceived << "\n" << " transfers active time: " << transfersActiveTime.report(reset) << "\n" << " transfer starts/finishes: " << transferStarts << " " << transferFinishes << "\n" << " transfer temperror/fails: " << transferTempErrors << " " << transferFails << "\n" << " nowait reason: immedate: " << prepwaitImmediate << " zero: " << prepwaitZero << " httpio: " << prepwaitHttpio << " fsaccess: " << prepwaitFsaccess << " nonzero waits: " << nonzeroWait << "\n"; if (auto curlhttpio = dynamic_cast(httpio)) { s << curlhttpio->countCurlHttpIOAddevents.report(reset) << "\n" << curlhttpio->countAddCurlEventsCode.report(reset) << "\n" << curlhttpio->countProcessCurlEventsCode.report(reset) << "\n"; } #ifdef WIN32 s << " waiter nonzero timeout: " << static_cast(waiter)->performanceStats.waitTimedoutNonzero << " zero timeout: " << static_cast(waiter)->performanceStats.waitTimedoutZero << " io trigger: " << static_cast(waiter)->performanceStats.waitIOCompleted << " event trigger: " << static_cast(waiter)->performanceStats.waitSignalled << "\n"; #endif if (reset) { transferStarts = transferFinishes = transferTempErrors = transferFails = 0; prepwaitImmediate = prepwaitZero = prepwaitHttpio = prepwaitFsaccess = nonzeroWait = 0; } return s.str(); } #endif m_time_t MegaClient::MyAccountData::getTimeLeft() { auto timeleft = mProUntil - static_cast(std::time(nullptr)); auto isuserpro = mProLevel > AccountType::ACCOUNT_TYPE_FREE; return ( isuserpro ? timeleft : -1); }; dstime MegaClient::overTransferQuotaBackoff(HttpReq* req) { bool isuserpro = this->mMyAccount.getProLevel() > AccountType::ACCOUNT_TYPE_FREE; // if user is pro, subscription's remaining time is used // otherwise, use limit per IP coming from the header X-MEGA-Time-Left response header m_time_t timeleft = (isuserpro) ? this->mMyAccount.getTimeLeft() : req->timeleft; // send event only for negative timelefts received in the request header if (!isuserpro && (timeleft < 0)) { sendevent(99408, "Overquota without timeleft", 0); } dstime backoff; if (timeleft > 0) { backoff = dstime(timeleft * 10); } else { // default retry interval backoff = MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS * 10; } return backoff; } // // Sets and Elements // void MegaClient::putSet(Set&& s, std::function completion) { string encrSetKey; std::unique_ptr encrAttrs; // create Set if (s.id() == UNDEF) { if (s.type() >= Set::TYPE_SIZE) { LOG_err << "Sets: Invalid Set type " << static_cast(s.type()) << ". Maximum valid type is " << static_cast(Set::TYPE_SIZE) - 1; if (completion) completion(API_EARGS, nullptr); return; } // generate AES-128 Set key encrSetKey = rng.genstring(SymmCipher::KEYLENGTH); s.setKey(encrSetKey); // encrypt Set key with master key if (!key.cbc_encrypt((byte*)&encrSetKey[0], encrSetKey.size())) // in c++17 and beyond it should use encrSetKey.data() { LOG_err << "Sets: Failed to encrypt Set key with master key."; if (completion) { completion(API_EKEY, nullptr); } return; } if (s.hasAttrs()) { if (s.cover() != UNDEF) { LOG_err << "Sets: Cover cannot be set for a newly created Set."; if (completion) completion(API_EARGS, nullptr); return; } string enc = s.encryptAttributes([this](const string_map& a, const string& k) { return encryptAttrs(a, k); }); encrAttrs.reset(new string(std::move(enc))); } } // update Set else { if (!s.hasAttrs()) // should either remove all attrs or update [some of] them { LOG_err << "Sets: Nothing to update."; if (completion) completion(API_EARGS, nullptr); return; } auto it = mSets.find(s.id()); if (it == mSets.end()) { LOG_err << "Sets: Failed to update Set (not found)."; if (completion) completion(API_ENOENT, nullptr); return; } if (s.cover() != UNDEF && !getSetElement(s.id(), s.cover())) { LOG_err << "Sets: Requested cover was not an Element of Set " << toHandle(s.id()); if (completion) completion(API_EARGS, nullptr); return; } // copy the details that won't change const Set& setToBeUpdated = it->second; s.setKey(setToBeUpdated.key()); s.setUser(setToBeUpdated.user()); s.rebaseAttrsOn(setToBeUpdated); s.setPublicLink(setToBeUpdated.getPublicLink()); s.setType(setToBeUpdated.type()); string enc = s.encryptAttributes([this](const string_map& a, const string& k) { return encryptAttrs(a, k); }); encrAttrs.reset(new string(std::move(enc))); } queueCommand(new CommandPutSet(this, std::move(s), std::move(encrAttrs), std::move(encrSetKey), completion)); } void MegaClient::removeSet(handle setID, std::function completion) { if (getSet(setID)) { queueCommand(new CommandRemoveSet(this, setID, completion)); } else if (completion) { completion(API_ENOENT); } } void MegaClient::putSetElements(vector&& els, std::function*, const vector*)> completion) { // set-id is required assert(!els.empty() && els.front().set() != UNDEF); // make sure Set id is valid const Set* existingSet = (els.empty() || els.front().set() == UNDEF) ? nullptr : getSet(els.front().set()); if (!existingSet) { LOG_err << "Sets: Set not found when adding bulk Elements"; if (completion) { completion(API_ENOENT, nullptr, nullptr); } return; } // build encrypted details vector encrDetails(els.size()); // vector < {encrypted attrs, encrypted key} > for (size_t i = 0u; i < els.size(); ++i) { SetElement& el = els[i]; std::shared_ptr n = nodebyhandle(el.node()); if (!n || !n->keyApplied() || !n->nodecipher() || n->attrstring || n->type != FILENODE) { // if file node was invalid, reset it and let the API return error for it, to allow the other Elements to be created el.setNode(UNDEF); } else { el.setKey(n->nodekey()); assert(el.key().size() == FILENODEKEYLENGTH); // encrypt element.key with set.key byte encryptBuffer[FILENODEKEYLENGTH]; std::copy_n(el.key().begin(), sizeof(encryptBuffer), encryptBuffer); tmpnodecipher.setkey(&existingSet->key()); if (!tmpnodecipher.cbc_encrypt(encryptBuffer, sizeof(encryptBuffer))) { LOG_err << "Sets: Failed to CBC encrypt an Element key with Set key"; if (completion) { completion(API_EKEY, nullptr, nullptr); } return; } auto& ed = encrDetails[i]; ed.second.assign(reinterpret_cast(encryptBuffer), sizeof(encryptBuffer)); if (el.hasAttrs()) { ed.first = el.encryptAttributes([this](const string_map& a, const string& k) { return encryptAttrs(a, k); }); } } } queueCommand( new CommandPutSetElements(this, std::move(els), std::move(encrDetails), completion)); } void MegaClient::putSetElement(SetElement&& el, std::function completion) { // setId is required assert(el.set() != UNDEF); // make sure Set id is valid const Set* existingSet = el.set() == UNDEF ? nullptr : getSet(el.set()); if (!existingSet) { LOG_err << "Sets: Set not found when adding or updating Element"; if (completion) completion(API_ENOENT, nullptr); return; } const SetElement* existingElement = nullptr; // copy element.key from nodekey (only for new Element) string encrKey; if (el.id() == UNDEF) { std::shared_ptr n = nodebyhandle(el.node()); error e = !n ? API_ENOENT : (!n->keyApplied() || !n->nodecipher() || n->attrstring ? API_EKEY : (n->type != FILENODE ? API_EARGS : API_OK)); if (e != API_OK) { LOG_err << "Sets: Invalid node for Element"; if (completion) completion(e, nullptr); return; } el.setKey(n->nodekey()); assert(el.key().size() == FILENODEKEYLENGTH); // encrypt element.key with set.key byte encryptBuffer[FILENODEKEYLENGTH]; std::copy_n(el.key().begin(), sizeof(encryptBuffer), encryptBuffer); tmpnodecipher.setkey(&existingSet->key()); if (!tmpnodecipher.cbc_encrypt(encryptBuffer, sizeof(encryptBuffer))) { LOG_err << "Sets: Failed to CBC encrypt Element key with Set key"; if (completion) { completion(API_EKEY, nullptr); } return; } encrKey.assign((char*)encryptBuffer, sizeof(encryptBuffer)); } // get element.key from existing element (only when updating attributes) else if (el.hasAttrs()) { existingElement = getSetElement(el.set(), el.id()); if (!existingElement) { LOG_err << "Sets: Element not found when updating Element: " << toHandle(el.id()); if (completion) completion(API_ENOENT, nullptr); return; } el.setKey(existingElement->key()); } // store element.attrs to TLV, and encrypt with element.key (copied from nodekey) std::unique_ptr encrAttrs; if (el.hasAttrs()) { if (existingElement && existingElement->hasAttrs()) { el.rebaseAttrsOn(*existingElement); // the request to clear the last attribute of an existing Element must be remembered, so that // after a successful update, attrs of the existing Element will be cleared el.setAttrsClearedByLastUpdate(!el.hasAttrs()); } string enc = el.encryptAttributes([this](const string_map& a, const string& k) { return encryptAttrs(a, k); }); encrAttrs.reset(new string(std::move(enc))); } queueCommand(new CommandPutSetElement(this, std::move(el), std::move(encrAttrs), std::move(encrKey), completion)); } void MegaClient::removeSetElements(handle setID, vector&& eids, std::function*)> completion) { // set-id is required assert(setID != UNDEF && !eids.empty()); // make sure Set id is valid const Set* existingSet = (eids.empty() || setID == UNDEF) ? nullptr : getSet(setID); if (!existingSet) { LOG_err << "Sets: Invalid request data when removing bulk Elements"; if (completion) { completion(API_ENOENT, nullptr); } return; } // Do not validate Element ids here. Let the API return error for invalid ones, // to allow valid ones to be removed. queueCommand(new CommandRemoveSetElements(this, setID, std::move(eids), completion)); } void MegaClient::removeSetElement(handle setID, handle eid, std::function completion) { if (!getSetElement(setID, eid)) { if (completion) { completion(API_ENOENT); } return; } queueCommand(new CommandRemoveSetElement(this, setID, eid, completion)); } bool MegaClient::procaesp(JSON& j) { bool ok = j.enterobject(); if (ok) { map newSets; map newElements; ok &= (readSetsAndElements(j, newSets, newElements) == API_OK); if (ok) { // save new data mSets.swap(newSets); mSetElements.swap(newElements); } ok &= j.leaveobject(); } return ok; } error MegaClient::readSetsAndElements(JSON& j, map& newSets, map& newElements) { std::unique_ptr> nodeData; for (bool loopAgain = true; loopAgain;) { switch (j.getnameid()) { case makeNameid("s"): { // reuse this in "aft" (fetch-Set command) and "aesp" (in gettree/fetchnodes/"f" command): // "aft" will return a single Set for "s", while "aesp" will return an array of Sets bool enteredSetArray = j.enterarray(); error e = readSets(j, newSets); if (e != API_OK) return e; if (enteredSetArray) { j.leavearray(); } break; } case makeNameid("e"): { error e = readElements(j, newElements); if (e != API_OK) return e; break; } case makeNameid("n"): { nodeData.reset(new std::map()); error e = readAllNodeMetadata(j, *nodeData); if (e != API_OK) return e; break; } case makeNameid("p"): { // precondition: sets which ph is coming are already read and in memory if (!newSets.empty()) { error e = readSetsPublicHandles(j, newSets); if (e != API_OK) return e; } break; } default: // skip unknown member if (!j.storeobject()) { return API_EINTERNAL; } break; case EOO: loopAgain = false; break; } } // decrypt data, and confirm that all elements are valid #ifndef NDEBUG size_t elCount = #endif decryptAllSets(newSets, newElements, nodeData.get()); // check for orphan Elements, it should not happen assert(elCount == [&newElements]() { size_t c = 0; for (const auto& els : newElements) c += els.second.size(); return c; } ()); return API_OK; } size_t MegaClient::decryptAllSets(map& newSets, map& newElements, map* nodeData) { size_t elCount = 0; for (auto itS = newSets.begin(); itS != newSets.end();) { error e = decryptSetData(itS->second); if (e != API_OK) { // skip this Set and its Elements // allow execution to continue, including the test for this scenario newElements.erase(itS->first); itS = newSets.erase(itS); continue; } auto itEls = newElements.find(itS->first); if (itEls != newElements.end()) { for (auto itE = itEls->second.begin(); itE != itEls->second.end();) { // decrypt element key and attrs e = decryptElementData(itE->second, itS->second.key()); if (e != API_OK) { LOG_err << "Failed to decrypt element attributes. " << "Element id = " << toHandle(itE->first) << ", Element key << " << Base64::btoa(itE->second.key()) << ", Set id = " << toHandle(itS->first) << ", Set key = " << Base64::btoa(itS->second.key()) << ", e = " << e; assert(false && "failed to decrypt Element attributes"); // failed to decrypt Element attributes itE = itEls->second.erase(itE); continue; } // fill in node attributes in case of having foreign node if (nodeData) { auto itNode = nodeData->find(itE->second.node()); if (itNode != nodeData->end()) { SetElement::NodeMetadata& nodeMeta = itNode->second; if (!nodeMeta.at.empty() && decryptNodeMetadata(nodeMeta, itE->second.key())) { itE->second.setNodeMetadata(std::move(nodeMeta)); } nodeData->erase(itNode); } if (!itE->second.nodeMetadata()) { LOG_err << "Invalid node for element. " << "Element id = " << toHandle(itE->first) << ", Element key << " << Base64::btoa(itE->second.key()) << ", Set id = " << toHandle(itS->first) << ", Set key = " << Base64::btoa(itS->second.key()); itE = itEls->second.erase(itE); continue; } } ++elCount; ++itE; } } ++itS; } return elCount; } error MegaClient::decryptSetData(Set& s) { if (!s.id() || s.id() == UNDEF) { LOG_err << "Sets: Missing mandatory Set data"; return API_EINTERNAL; } if (inPublicSetPreview()) { if ((mPreviewSet->mSet.id() == UNDEF) // first time receiving Set data for preview Set || mPreviewSet->mSet.id() == s.id()) // followup receiving Set data for preview Set { s.setKey(mPreviewSet->mPublicKey); // already decrypted s.setPublicLink(std::make_unique(mPreviewSet->mPublicId)); } else { LOG_err << "Sets: Data for Set |" << toHandle(s.id()) << "| fetched while public Set preview mode active for Set |" << toHandle(mPreviewSet->mSet.id()) << "|\n"; return API_EARGS; } } else { if (s.key().empty()) { LOG_err << "Sets: Missing mandatory Set key"; return API_EINTERNAL; } // decrypt Set key using the master key s.setKey(decryptKey(s.key(), key)); } // decrypt attrs if (s.hasEncrAttrs()) { auto decryptFunc = [this](const string& in, const string& k, string_map& out) { return decryptAttrs(in, k, out); }; if (!s.decryptAttributes(decryptFunc)) { LOG_err << "Sets: Unable to decrypt Set attrs " << toHandle(s.id()); return API_EINTERNAL; } } return API_OK; } error MegaClient::decryptElementData(SetElement& el, const string& setKey) { if (!el.id() || el.id() == UNDEF || !el.node() || el.node() == UNDEF || el.key().empty()) { LOG_err << "Sets: Missing mandatory Element data [el.id = " << el.id() << ", el.node = " << el.node() << ", el.key = " << el.key() << "]"; return API_EINTERNAL; } tmpnodecipher.setkey(&setKey); el.setKey(decryptKey(el.key(), tmpnodecipher)); // decrypt attrs if (el.hasEncrAttrs()) { auto decryptFunc = [this](const string& in, const string& k, string_map& out) { return decryptAttrs(in, k, out); }; if (!el.decryptAttributes(decryptFunc)) { LOG_err << "Sets: Unable to decrypt Element attrs " << toHandle(el.id()); return API_EINTERNAL; } } return API_OK; } string MegaClient::decryptKey(const string& encryptedKey, SymmCipher& cipher) const { unique_ptr decrKey(new byte[encryptedKey.size()]{0}); std::copy_n(encryptedKey.begin(), encryptedKey.size(), decrKey.get()); if (!cipher.cbc_decrypt(decrKey.get(), encryptedKey.size())) { LOG_err << "Failed to CBC decrypt key"; return string(); } return string((char*)decrKey.get(), encryptedKey.size()); } string MegaClient::encryptAttrs(const string_map& attrs, const string& encryptionKey) { if (attrs.empty()) { return string(); } if (!tmpnodecipher.setkey(&encryptionKey)) { LOG_err << "Sets: Failed to use cipher key when encrypting attrs"; return string(); } unique_ptr encrAttrs(tlv::recordsToContainer(string_map{attrs}, rng, tmpnodecipher)); if (!encrAttrs || encrAttrs->empty()) { LOG_err << "Sets: Failed to write name to TLV container"; return string(); } return *encrAttrs; } bool MegaClient::decryptAttrs(const string& attrs, const string& decrKey, string_map& output) { if (attrs.empty()) { output.clear(); return true; } assert(decrKey.size() == SymmCipher::KEYLENGTH || decrKey.size() == FILENODEKEYLENGTH); if (!tmpnodecipher.setkey(&decrKey)) { LOG_err << "Sets: Failed to assign key to cipher when decrypting attrs"; return false; } unique_ptr records{tlv::containerToRecords(attrs, tmpnodecipher)}; if (records) { output.swap(*records); } else { LOG_err << "Sets: Failed to build TLV container of attrs"; return false; } return true; } error MegaClient::readSets(JSON& j, map& sets) { while (j.enterobject()) { Set s; error e = readSet(j, s); if (e) { return e; } sets[s.id()] = std::move(s); j.leaveobject(); } return API_OK; } error MegaClient::readSet(JSON& j, Set& s) { for (;;) { switch (j.getnameid()) { case makeNameid("id"): s.setId(j.gethandle(MegaClient::SETHANDLE)); break; case name_id::ph: { handle publicLink = j.gethandle(MegaClient::PUBLICSETHANDLE); // overwrite if existed if (publicLink != UNDEF) { s.setPublicLink(std::make_unique(publicLink)); } break; } case makeNameid("at"): { string attrs; j.copystring(&attrs, j.getvalue()); // B64 encoded if (!attrs.empty()) { attrs = Base64::atob(attrs); } s.setEncryptedAttrs(std::move(attrs)); // decrypt them after reading everything break; } case name_id::u: s.setUser(j.gethandle(MegaClient::USERHANDLE)); break; case makeNameid("k"): // used to encrypt attrs; encrypted itself with owner's key { string setKey; j.copystring(&setKey, j.getvalue()); // B64 encoded s.setKey(Base64::atob(setKey)); break; } case makeNameid("ts"): s.setTs(j.getint()); break; case makeNameid("cts"): s.setCTs(j.getint()); break; case makeNameid("t"): s.setType(static_cast(j.getint())); break; default: // skip unknown member if (!j.storeobject()) { LOG_err << "Sets: Failed to parse Set"; return API_EINTERNAL; } break; case EOO: return API_OK; } } } error MegaClient::readElements(JSON& j, map& elements) { if (!j.enterarray()) { return API_EINTERNAL; } while (j.enterobject()) { SetElement el; error e = readElement(j, el); if (e) { j.leavearray(); return e; } handle setID = el.set(); handle eid = el.id(); elements[setID].emplace(eid, std::move(el)); j.leaveobject(); } j.leavearray(); return API_OK; } error MegaClient::readElement(JSON& j, SetElement& el) { for (;;) { switch (j.getnameid()) { case makeNameid("id"): el.setId(j.gethandle(MegaClient::SETELEMENTHANDLE)); break; case makeNameid("s"): el.setSet(j.gethandle(MegaClient::SETHANDLE)); break; case makeNameid("h"): el.setNode(j.gethandle(MegaClient::NODEHANDLE)); break; case makeNameid("at"): { string elementAttrs; j.copystring(&elementAttrs, j.getvalue()); if (!elementAttrs.empty()) { elementAttrs = Base64::atob(elementAttrs); } el.setEncryptedAttrs(std::move(elementAttrs)); // decrypt them after reading everything break; } case makeNameid("o"): el.setOrder(j.getint()); break; case makeNameid("ts"): el.setTs(j.getint()); break; case makeNameid("k"): { string elementKey; j.copystring(&elementKey, j.getvalue()); if (!elementKey.empty()) { elementKey = Base64::atob(elementKey); } el.setKey(std::move(elementKey)); break; } default: // skip unknown member if (!j.storeobject()) { LOG_err << "Sets: Failed to parse Element"; return API_EINTERNAL; } break; case EOO: return API_OK; } } } error MegaClient::readAllNodeMetadata(JSON& j, map& nodes) { if (!j.enterarray()) { return API_EINTERNAL; } while (j.enterobject()) { SetElement::NodeMetadata eln; error e = readSingleNodeMetadata(j, eln); if (e) { j.leavearray(); return e; } nodes.emplace(eln.h, std::move(eln)); j.leaveobject(); } j.leavearray(); return API_OK; } error MegaClient::readSingleNodeMetadata(JSON& j, SetElement::NodeMetadata& eln) { for (;;) { switch (j.getnameid()) { case makeNameid("h"): eln.h = j.gethandle(MegaClient::NODEHANDLE); break; case name_id::u: eln.u = j.gethandle(MegaClient::USERHANDLE); break; case makeNameid("s"): eln.s = j.getint(); break; case makeNameid("at"): if (!j.storeobject(&eln.at)) { LOG_err << "Sets: Failed to read node attributes"; assert(false); return API_EINTERNAL; } break; case makeNameid("fa"): if (!j.storeobject(&eln.fa)) { LOG_err << "Sets: Failed to read file attributes"; assert(false); return API_EINTERNAL; } break; case makeNameid("ts"): eln.ts = j.getint(); break; default: // skip unknown member if (!j.storeobject()) { LOG_err << "Sets: Failed to parse node metadata"; return API_EINTERNAL; } break; case EOO: return API_OK; } } } bool MegaClient::decryptNodeMetadata(SetElement::NodeMetadata& nodeMeta, const string& encryptionKey) { SymmCipher* cipher = getRecycledTemporaryNodeCipher(&encryptionKey); std::unique_ptr buf; buf.reset(Node::decryptattr(cipher, nodeMeta.at.c_str(), nodeMeta.at.size())); if (!buf) { LOG_err << "Decrypting node attributes failed. Node Handle = " << toNodeHandle(nodeMeta.h); return false; } // all good, let's parse the attribute string JSON attrJson; attrJson.begin(reinterpret_cast(buf.get()) + 5); // skip "MEGA{" prefix for (bool jsonHasData = true; jsonHasData;) { switch (attrJson.getnameid()) { case name_id::c: if (!attrJson.storeobject(&nodeMeta.fingerprint)) { LOG_err << "Reading node fingerprint failed. Node Handle = " << toNodeHandle(nodeMeta.h); } break; case makeNameid("n"): if (!attrJson.storeobject(&nodeMeta.filename)) { LOG_err << "Reading node filename failed. Node Handle = " << toNodeHandle(nodeMeta.h); } break; case EOO: jsonHasData = false; break; default: if (!attrJson.storeobject()) { LOG_err << "Skipping unexpected node attribute failed. Node Handle = " << toNodeHandle(nodeMeta.h); } } } nodeMeta.at.clear(); return true; } const Set* MegaClient::getSet(handle setID) const { auto it = mSets.find(setID); return it == mSets.end() ? nullptr : &it->second; } const Set* MegaClient::addSet(Set&& a) { handle setID = a.id(); auto add = mSets.emplace(setID, std::move(a)); assert(add.second); if (add.second) // newly inserted { Set& added = add.first->second; added.setChanged(Set::CH_NEW); notifyset(&added); } return &add.first->second; } void MegaClient::fixSetElementWithWrongKey(const Set& s) { const auto els = getSetElements(s.id()); if (!els) return; vector newEls; vector taintedEls; const auto hasWrongKey = [](const SetElement& el) { // Elements created by Webclient prior to a certain ts had invalid keys. // Some had keys of wrong size, others had keys of correct size but still invalid. // A criteria deemed good enough to spot the latter was to check for ts prior to a known value // and having no name - Webclient would create Elements without 'name' attribute being set. return el.key().size() != static_cast(FILENODEKEYLENGTH) || (el.ts() <= 1695340800 && el.name().empty()); }; for (auto& p : *els) // candidate to paral in >C++17 via algorithms { const SetElement& e = p.second; if (hasWrongKey(e)) { LOG_warn << "Sets: SetElement " << toHandle(e.id()) << " from Set " << toHandle(s.id()) << " has invalid key"; taintedEls.push_back(e.id()); newEls.emplace_back(e); } } if (taintedEls.empty()) return; const auto logResult = [this](Error e, const vector* results, const std::string& msg) { if (e == API_OK && (!results || std::all_of(begin(*results), end(*results), [](int64_t r) { return r == API_OK; }))) { const std::string m = "Sets: SetElements with wrong key " + msg + " successfully"; LOG_debug << m; sendevent(99477, m.c_str()); } else { const std::string m = "Sets: Error: SetElements with wrong key failed to be " + msg; LOG_warn << m; sendevent(99478, m.c_str()); } }; // removal must take place before because there can't be 2 SetElements with the same node removeSetElements(s.id(), std::move(taintedEls), [logResult](Error e, const vector* results) { logResult(e, results, "removed"); }); putSetElements(std::move(newEls), [logResult](Error e, const vector*, const vector* results) { logResult(e, results, "created"); }); } bool MegaClient::updateSet(Set&& s) { auto it = mSets.find(s.id()); if (it != mSets.end()) { if (it->second.updateWith(std::move(s))) { notifyset(&it->second); } return true; // return true if found, even if nothing was updated } return false; } bool MegaClient::deleteSet(handle setID) { auto it = mSets.find(setID); if (it != mSets.end()) { it->second.setChanged(Set::CH_REMOVED); notifyset(&it->second); return true; } return false; } unsigned MegaClient::getSetElementCount(handle setID) const { auto* elements = getSetElements(setID); return elements ? static_cast(elements->size()) : 0u; } const SetElement* MegaClient::getSetElement(handle setID, handle eid) const { auto* elements = getSetElements(setID); if (elements) { auto ite = elements->find(eid); if (ite != elements->end()) { return &(ite->second); } } return nullptr; } const elementsmap_t* MegaClient::getSetElements(handle setID) const { auto itS = mSetElements.find(setID); return itS == mSetElements.end() ? nullptr : &itS->second; } bool MegaClient::deleteSetElement(handle setID, handle eid) { auto its = mSetElements.find(setID); if (its != mSetElements.end()) { auto ite = its->second.find(eid); if (ite != its->second.end()) { ite->second.setChanged(SetElement::CH_EL_REMOVED); notifysetelement(&ite->second); return true; } } return false; } const SetElement* MegaClient::addOrUpdateSetElement(SetElement&& el) { handle setID = el.set(); assert(setID != UNDEF); handle eid = el.id(); auto itS = mSetElements.find(setID); if (itS != mSetElements.end()) { auto& elements = itS->second; auto ite = elements.find(eid); if (ite != elements.end()) { if (ite->second.updateWith(std::move(el))) { notifysetelement(&ite->second); } return &ite->second; } } // not found, add it auto add = mSetElements[setID].emplace(eid, std::move(el)); assert(add.second); SetElement& added = add.first->second; added.setChanged(SetElement::CH_EL_NEW); notifysetelement(&added); return &added; } void MegaClient::sc_asp(JSON& json) { Set s; error e = readSet(json, s); if (e != API_OK) { LOG_err << "Sets: Failed to parse `asp` action packet"; return; } // Set key is always received, let's use that if (decryptSetData(s) != API_OK) { LOG_err << "Sets: failed to decrypt attributes from `asp`. Skipping Set: " << toHandle(s.id()); return; } auto it = mSets.find(s.id()); if (it == mSets.end()) // add new { addSet(std::move(s)); } else // update existing Set { Set& existing = it->second; if (s.key() != existing.key()) { LOG_err << "Sets: key differed from existing one. Skipping Set:" << toHandle(s.id()); sendevent(99458, "Set key has changed"); assert(false); return; } // copy any existing data not received via AP s.setPublicLink(existing.getPublicLink()); if (existing.updateWith(std::move(s))) { notifyset(&existing); } } } void MegaClient::sc_asr(JSON& json) { handle setId = UNDEF; for (;;) { switch (json.getnameid()) { case makeNameid("id"): { setId = json.gethandle(MegaClient::SETHANDLE); break; } case EOO: if (ISUNDEF(setId) || !deleteSet(setId)) { LOG_err << "Sets: Failed to remove Set in `asr` action packet for Set " << toHandle(setId); } return; default: if (!json.storeobject()) { LOG_warn << "Sets: Failed to parse `asr` action packet"; return; } } } } void MegaClient::sc_aep(JSON& json) { SetElement el; if (readElement(json, el) != API_OK) { LOG_err << "Sets: `aep` action packet: failed to parse data"; return; } // find the Set for this Element Set* s = nullptr; auto it = mSets.find(el.set()); if (it != mSets.end()) { s = &it->second; } else { LOG_err << "Sets: `aep` action packet: failed to find Set for Element"; return; } if (decryptElementData(el, s->key()) != API_OK) { LOG_err << "Sets: `aep` action packet: failed to decrypt Element data"; return; } addOrUpdateSetElement(std::move(el)); } void MegaClient::sc_aer(JSON& json) { handle elemId = UNDEF; handle setId = UNDEF; for (;;) { switch (json.getnameid()) { case makeNameid("id"): elemId = json.gethandle(MegaClient::SETELEMENTHANDLE); break; case makeNameid("s"): setId = json.gethandle(MegaClient::SETHANDLE); break; case EOO: if (ISUNDEF(setId) || ISUNDEF(elemId) || !deleteSetElement(setId, elemId)) { LOG_err << "Sets: Failed to remove Element in `aer` action packet for Set " << toHandle(setId) << " and Element " << toHandle(elemId); } return; default: if (!json.storeobject()) { LOG_warn << "Sets: Failed to parse `aer` action packet"; return; } } } } error MegaClient::readExportedSet(JSON& j, Set& s) { handle publicHandle{UNDEF}; PublicLinkSet::LinkDeletionReason reason{PublicLinkSet::LinkDeletionReason::NO_REMOVED}; bool removed{false}; bool containsTakenDownInfo = false; bool isDown = false; for (;;) { switch (j.getnameid()) { case makeNameid("s"): s.setId(j.gethandle(MegaClient::SETHANDLE)); break; case name_id::ph: publicHandle = j.gethandle(MegaClient::PUBLICSETHANDLE); // overwrite if existed break; case makeNameid("ts"): s.setTs(j.getint()); break; // The presence of this parameter indicates a takeup/takedown. 0 means takeup and 1 means // takedown case MAKENAMEID2('t', 'd'): containsTakenDownInfo = true; isDown = j.getint() == 1; break; // The presence of this parameter indicates a set public handle removal case MAKENAMEID1('r'): removed = true; j.getint(); break; case name_id::c: reason = PublicLinkSet::apiCodeToDeletionReason(j.getint()); break; default: // skip 'i' and any unknown/unexpected member { if (!j.storeobject()) { LOG_err << "Sets: Failed to parse Set"; return API_EINTERNAL; } LOG_debug << "Sets: Unknown member received in 'ass' action packet"; break; } case EOO: // Create an user alert if needed if (containsTakenDownInfo && !ISUNDEF(s.id()) && statecurrent) { useralerts.add(new UserAlert::SetTakedown(isDown, !isDown, reason, s.id(), m_time(), useralerts.nextId())); } if (removed) { s.setPublicLink(nullptr); } else { std::unique_ptr publicLinkSet; publicLinkSet.reset(new PublicLinkSet(publicHandle)); publicLinkSet->setTakeDown(isDown); publicLinkSet->setLinkDeletionReason(reason); s.setPublicLink(std::move(publicLinkSet)); } return API_OK; } } } error MegaClient::readSetPublicHandle(JSON& j, map& sets) { handle item = UNDEF, itemPH = UNDEF; m_off_t ts = 0; m_off_t dts = 0; bool takeDown = 0; for (;;) { switch (j.getnameid()) { case makeNameid("s"): item = j.gethandle(MegaClient::SETHANDLE); break; case name_id::ph: itemPH = j.gethandle(MegaClient::PUBLICSETHANDLE); break; case makeNameid("ts"): ts = j.getint(); break; case MAKENAMEID1('c'): takeDown = j.getint() == 1; break; case MAKENAMEID3('d', 't', 's'): dts = j.getint(); break; default: // skip any unknown/unexpected member { if (!j.storeobject()) { LOG_err << "Sets: Failed to parse public handles for Sets"; return API_EINTERNAL; } LOG_debug << "Sets: Unknown member received in 'aesp' for an 'f' command"; break; } case EOO: assert(item != UNDEF && itemPH != UNDEF); if (sets.find(item) != end(sets)) { std::unique_ptr publicLinkSet = std::make_unique(itemPH); if (takeDown) { publicLinkSet->setTakeDown(takeDown); // Set link with ETS and ATS aren't returned, only valid reason is // Dispute(copyright) publicLinkSet->setLinkDeletionReason( PublicLinkSet::LinkDeletionReason::DISPUTE); } sets[item].setPublicLink(std::move(publicLinkSet)); // setTs store last modification ts sets[item].setTs(dts > ts ? dts : ts); } else LOG_warn << "Sets: Set handle " << toHandle(item) << " not found in user's Sets"; return API_OK; } } } error MegaClient::readSetsPublicHandles(JSON& j, map& sets) { if (!j.enterarray()) return API_EINTERNAL; error e = API_OK; while (j.enterobject()) { e = readSetPublicHandle(j, sets); j.leaveobject(); if (e != API_OK) break; } j.leavearray(); return e; } void MegaClient::sc_ass(JSON& json) { Set s; const error e = readExportedSet(json, s); if (e != API_OK) { LOG_err << "Sets: Failed to parse `ass` action packet"; return; } const auto existingSet = mSets.find(s.id()); if (existingSet == mSets.end()) { LOG_debug << "Sets: Received action packet for Set " << toHandle(s.id()) << " which is unrelated to current user"; } else { Set updatedSet(existingSet->second); updatedSet.setTs(s.ts()); updatedSet.setChanged(Set::CH_EXPORTED); updatedSet.setPublicLink(s.getPublicLink()); updateSet(std::move(updatedSet)); } } bool MegaClient::isExportedSet(handle setID) const { auto s = getSet(setID); return s && s->isExported(); } void MegaClient::exportSet(handle setID, bool makePublic, std::function completion) { const auto setToBeUpdated = getSet(setID); if (setToBeUpdated) { if (makePublic) // legacy bug: some Element's key were set incorrectly -> repair { fixSetElementWithWrongKey(*setToBeUpdated); } if (setToBeUpdated->isExported() == makePublic) completion(API_OK); else { Set s(*setToBeUpdated); queueCommand(new CommandExportSet(this, std::move(s), makePublic, completion)); } } else { LOG_warn << "Sets: export requested for unknown Set " << toHandle(setID); if (completion) completion(API_ENOENT); } } pair MegaClient::getPublicSetLink(handle setID) const { const string paramErrMsg = "Sets: Incorrect parameters to create a public link for Set " + toHandle(setID); const auto& setIt = mSets.find(setID); if (setIt == end(mSets)) { LOG_err << paramErrMsg << ". Provided Set id doesn't match any owned Set"; return make_pair(API_ENOENT, string()); } const Set& s = setIt->second; if (!s.isExported()) { LOG_err << paramErrMsg << ". Provided Set is not exported"; return make_pair(API_ENOENT, string()); } error e = API_OK; string url = publicLinkURL(true /*newLinkFormat*/, TypeOfLink::SET, s.publicId(), Base64::btoa(s.key()).c_str()); if (url.empty()) e = API_EARGS; return make_pair(e, url); } void MegaClient::fetchSetInPreviewMode(std::function completion) { if (!inPublicSetPreview()) { LOG_err << "Sets: Fetch set request with public Set preview mode disabled"; completion(API_EACCESS, nullptr, nullptr); return; } auto clientUpdateOnCompletion = [completion, this](Error e, Set* s, elementsmap_t* els) { if ((e == API_OK) && s && els && inPublicSetPreview()) { auto& previewSet = mPreviewSet->mSet; previewSet = *s; auto& previewMapElements = mPreviewSet->mElements; previewMapElements = *els; } else if (e != API_OK && inPublicSetPreview()) { stopSetPreview(); } completion(e, s, els); }; queueCommand(new CommandFetchSet(this, clientUpdateOnCompletion)); } error MegaClient::fetchPublicSet(const char* publicSetLink, std::function completion) { handle publicSetId = UNDEF; std::array publicSetKey; error e = parsepubliclink(publicSetLink, publicSetId, publicSetKey.data(), TypeOfLink::SET); if (e == API_OK) { assert(publicSetId != UNDEF); if (inPublicSetPreview()) { if (mPreviewSet->mPublicId == publicSetId) { completion(API_OK, new Set(mPreviewSet->mSet), new elementsmap_t(mPreviewSet->mElements)); return e; } else stopSetPreview(); } // 1. setup member mPreviewSet: publicId, key, publicSetLink mPreviewSet = std::make_unique(); mPreviewSet->mPublicId = publicSetId; mPreviewSet->mPublicKey.assign(reinterpret_cast(publicSetKey.data()), publicSetKey.size()); mPreviewSet->mPublicLink.assign(publicSetLink); // 2. send `aft` command and intercept to save at mPreviewSet: Set, SetElements map fetchSetInPreviewMode(completion); } return e; } bool MegaClient::initscsets() { for (auto& i : mSets) { if (!sctable->put(CACHEDSET, &i.second, &key)) { return false; } } return true; } bool MegaClient::initscsetelements() { for (auto& s : mSetElements) { assert(mSets.find(s.first) != mSets.end()); if (mSets.find(s.first) == mSets.end()) { LOG_err << "Sets: elements for unknown set: " << toHandle(s.first); continue; } for (auto& e : s.second) { if (!sctable->put(CACHEDSETELEMENT, &e.second, &key)) { return false; } } } return true; } bool MegaClient::fetchscset(string* data, uint32_t id) { auto s = Set::unserialize(data); if (!s) { LOG_err << "Failed - Set record read error"; return false; } handle setID = s->id(); auto its = mSets.emplace(setID, std::move(*s)); assert(its.second); // insertion must have occurred Set& addedSet = its.first->second; addedSet.resetChanges(); addedSet.dbid = id; return true; } bool MegaClient::fetchscsetelement(string* data, uint32_t id) { auto el = SetElement::unserialize(data); if (!el) { LOG_err << "Failed - SetElement record read error"; return false; } handle setID = el->set(); handle eid = el->id(); auto ite = mSetElements[setID].emplace(eid, std::move(*el)); assert(ite.second); // insertion must have occurred SetElement& addedEl = ite.first->second; addedEl.resetChanges(); addedEl.dbid = id; return true; } bool MegaClient::updatescsets() { for (Set* s : setnotify) { assert(s->changes()); if (!s->changes()) { LOG_err << "Sets: Notifying about unchanged Set: " << toHandle(s->id()); continue; } char base64[12]; if (!s->hasChanged(Set::CH_REMOVED)) // add / replace / exported / exported disabled { LOG_verbose << "Adding Set to database: " << (Base64::btoa((byte*)&(s->id()), MegaClient::SETHANDLE, base64) ? base64 : ""); if (!sctable->put(CACHEDSET, s, &key)) { return false; } } else if (s->dbid) // remove { LOG_verbose << "Removing Set from database: " << (Base64::btoa((byte*)&(s->id()), MegaClient::SETHANDLE, base64) ? base64 : ""); // remove all elements of this set auto* elements = getSetElements(s->id()); if (elements) { for (auto& e : *elements) { if (!sctable->del(e.second.dbid)) { return false; } } clearsetelementnotify(s->id()); mSetElements.erase(s->id()); } if (!sctable->del(s->dbid)) { return false; } } } return true; } bool MegaClient::updatescsetelements() { for (SetElement* e : setelementnotify) { assert(e->changes()); if (!e->changes()) { LOG_err << "Sets: Notifying about unchanged SetElement: " << toHandle(e->id()); continue; } char base64[12]; if (!e->hasChanged(SetElement::CH_EL_REMOVED)) // add / replace { if (mSets.find(e->set()) == mSets.end()) { continue; } LOG_verbose << (e->hasChanged(SetElement::CH_EL_NEW) ? "Adding" : "Updating") << " SetElement to database: " << (Base64::btoa((byte*)&(e->id()), MegaClient::SETELEMENTHANDLE, base64) ? base64 : ""); if (!sctable->put(CACHEDSETELEMENT, e, &key)) { return false; } } else if (e->dbid) // remove { LOG_verbose << "Removing SetElement from database: " << (Base64::btoa((byte*)&(e->id()), MegaClient::SETELEMENTHANDLE, base64) ? base64 : ""); if (!sctable->del(e->dbid)) { return false; } } } return true; } void MegaClient::notifyset(Set* s) { if (!s->notified) { s->notified = true; setnotify.push_back(s); } } void MegaClient::notifysetelement(SetElement* e) { if (!e->notified) { e->notified = true; setelementnotify.push_back(e); } } void MegaClient::notifypurgesets() { if (!fetchingnodes) { app->sets_updated(setnotify.data(), (int)setnotify.size()); } for (auto& s : setnotify) { if (s->hasChanged(Set::CH_REMOVED)) { clearsetelementnotify(s->id()); mSetElements.erase(s->id()); mSets.erase(s->id()); } else { s->notified = false; s->resetChanges(); } } setnotify.clear(); } void MegaClient::notifypurgesetelements() { if (!fetchingnodes) { app->setelements_updated(setelementnotify.data(), (int)setelementnotify.size()); } for (auto& e : setelementnotify) { if (e->hasChanged(SetElement::CH_EL_REMOVED)) { mSetElements[e->set()].erase(e->id()); } else { e->notified = false; e->resetChanges(); } } setelementnotify.clear(); } void MegaClient::clearsetelementnotify(handle setID) { for (size_t i = setelementnotify.size(); i; --i) { if (setelementnotify[i - 1]->set() == setID) { setelementnotify.erase(setelementnotify.begin() + static_cast(i) - 1); } } } void MegaClient::setProFlexi(bool newProFlexi) { mProFlexi = newProFlexi; } void MegaClient::setEmail(User* u, const string& email) { assert(u); if (email == u->email) { return; } mapuser(u->userhandle, email.c_str()); // update email used as index for user's map u->changed.email = true; notifyuser(u); if (u->userhandle == me) { reportLoggedInChanges(); // produce a callback to update cached email in MegaApp } } Error MegaClient::sendABTestActive(const char* flag, CommandABTestActive::Completion completion) { queueCommand(new CommandABTestActive(this, flag, std::move(completion))); return API_OK; } /* Mega VPN methods BEGIN */ StringKeyPair MegaClient::generateVpnKeyPair() { auto vpnKey = std::make_unique(); if (!vpnKey->initializationOK) { LOG_err << "Initialization of keys Cu25519 and/or Ed25519 failed"; return StringKeyPair(std::string(), std::string()); } string privateKey = std::string((const char *)vpnKey->getPrivKey(), ECDH::PRIVATE_KEY_LENGTH); string publicKey = std::string((const char *)vpnKey->getPubKey(), ECDH::PUBLIC_KEY_LENGTH); return StringKeyPair(std::move(privateKey), std::move(publicKey)); } // Call "vpnr" command. void MegaClient::getVpnRegions(CommandGetVpnRegions::Cb&& completion) { queueCommand(new CommandGetVpnRegions(this, std::move(completion))); } // Call "vpng" command. void MegaClient::getVpnCredentials(CommandGetVpnCredentials::Cb&& completion) { queueCommand(new CommandGetVpnCredentials(this, std::move(completion))); } // Call "vpnp" command. void MegaClient::putVpnCredential(std::string&& vpnRegion, CommandPutVpnCredential::Cb&& completion) { auto vpnKeyPair = generateVpnKeyPair(); queueCommand(new CommandPutVpnCredential(this, std::move(vpnRegion), std::move(vpnKeyPair), std::move(completion))); } // Call "vpnd" command. void MegaClient::delVpnCredential(int slotID, CommandDelVpnCredential::Cb&& completion) { queueCommand(new CommandDelVpnCredential(this, slotID, std::move(completion))); } // Call "vpnc" command. void MegaClient::checkVpnCredential(std::string&& userPubKey, CommandDelVpnCredential::Cb&& completion) { queueCommand(new CommandCheckVpnCredential(this, std::move(userPubKey), std::move(completion))); } // Generate the credential string. string MegaClient::generateVpnCredentialString(const std::string& host, const std::vector& dns, std::string&& ipv4, std::string&& ipv6, StringKeyPair&& peerKeyPair) { string peerPrivateKey = Base64::btoa(peerKeyPair.privKey); string peerPublicKey = std::move(peerKeyPair.pubKey); // Base64 standard format for Peer Key Pair Base64::toStandard(peerPrivateKey); Base64::toStandard(peerPublicKey); assert(peerPrivateKey.size() == 4 * ((ECDH::PUBLIC_KEY_LENGTH + 2) / 3)); // Check lengths as we have keys from different sources assert(peerPrivateKey.size() == peerPublicKey.size()); // Now they peer keys are valid for WireGuard and can be added to the credentials. string credential; credential.reserve(300); credential.append("[Interface]\n") .append("PrivateKey = ") .append(peerPrivateKey) .append("\n") .append("Address = ") .append(ipv4) .append("/32") .append(", ") .append(ipv6) .append("/128\n") .append("DNS = ") .append(std::accumulate(dns.begin(), dns.end(), std::string{}, [](std::string& a, const std::string& b) { return a.empty() ? b : (std::move(a) + ',' + b); })) .append("\n\n") .append("[Peer]\n") .append("PublicKey = ") .append(peerPublicKey) .append("\n") .append("AllowedIPs = 0.0.0.0/0, ::/0\n") .append("Endpoint = ") .append(host) .append(":51820"); return credential; } void MegaClient::getNetworkConnectivityTestServerInfo( CommandGetNetworkConnectivityTestServerInfo::Completion&& completion) { queueCommand(new CommandGetNetworkConnectivityTestServerInfo(this, std::move(completion))); } void MegaClient::runNetworkConnectivityTest( std::function&& testCompletion) { getNetworkConnectivityTestServerInfo( [this, testCompletion = std::move(testCompletion)](const Error& e, NetworkConnectivityTestServerInfo&& serverInfo) { if (e) { LOG_err << "NetworkConnectivityTest: failed to retrieve server info."; testCompletion(e, {}); return; } UdpSocketTester::TestSuite testSuite{10, // loop count 3, // short message count 3, // long message count 3, // DNS message count me}; // user id LOG_debug << "NetworkConnectivityTest: messages to be sent (hex):"; LOG_debug << "NetworkConnectivityTest: S: " << Utils::stringToHex(testSuite.getShortMessage(), true); LOG_debug << "NetworkConnectivityTest: L: " << Utils::stringToHex(testSuite.getLongMessage(), true); LOG_debug << "NetworkConnectivityTest: D (IPV4): " << Utils::stringToHex(testSuite.getDnsIPv4Message(), true); LOG_debug << "NetworkConnectivityTest: D (IPV6): " << Utils::stringToHex(testSuite.getDnsIPv6Message(), true); using namespace std::chrono; auto netTestStart = high_resolution_clock::now(); NetworkConnectivityTest test; if (!test.start(std::move(testSuite), std::move(serverInfo))) { LOG_err << "NetworkConnectivityTest: failed to start test (already running?)."; testCompletion(API_EINTERNAL, {}); return; } auto testResults{test.getResults()}; // Log errors from socket communication for (const auto& singleError: testResults.ipv4.socketErrors) LOG_err << "NetworkConnectivityTest: " << singleError; for (const auto& singleError: testResults.ipv6.socketErrors) LOG_err << "NetworkConnectivityTest: " << singleError; LOG_info << "NetworkConnectivityTest: test ran for " << duration_cast(high_resolution_clock::now() - netTestStart).count() << " ms."; testCompletion(API_OK, std::move(testResults)); }); } void MegaClient::sendNetworkConnectivityTestEvent(const NetworkConnectivityTestResults& results) { string resultString; if (results.ipv4.messages != NetworkConnectivityTestMessageStatus::NET_UNREACHABLE && results.ipv4.dns != NetworkConnectivityTestMessageStatus::NET_UNREACHABLE) { resultString = results.ipv4.summary; } if (results.ipv6.messages != NetworkConnectivityTestMessageStatus::NET_UNREACHABLE && results.ipv6.dns != NetworkConnectivityTestMessageStatus::NET_UNREACHABLE) { if (!resultString.empty()) resultString += ' '; resultString += results.ipv6.summary; } if (!resultString.empty()) { static constexpr int VPN_NETWORK_CONNECTIVITY_TEST_EVENT = 99495; sendevent(VPN_NETWORK_CONNECTIVITY_TEST_EVENT, resultString.c_str()); } } /* Mega VPN methods END */ void MegaClient::fetchCreditCardInfo(CommandFetchCreditCardCompletion completion) { queueCommand(new CommandFetchCreditCard(this, std::move(completion))); } const char* const MegaClient::NODE_ATTR_PASSWORD_MANAGER = "pwm"; NodeHandle MegaClient::getPasswordManagerBase() { auto u = ownuser(); const UserAttribute* attribute = u ? u->getAttribute(ATTR_PWM_BASE) : nullptr; return (attribute && attribute->isValid() && attribute->value().size() == MegaClient::NODEHANDLE) ? toNodeHandle(&attribute->value()) : NodeHandle{}; } void MegaClient::preparePasswordManagerNodeData(attr_map& attrs, const AttrMap& data) const { assert(!data.map.empty()); auto auxstr = data.getjson(); auto auxDataAttrMap = data; if (auxDataAttrMap.map.contains(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP))) { const auto totp = auxDataAttrMap.map.extract(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP)); auxstr = auxDataAttrMap.getjson(); if (!auxDataAttrMap.map.empty()) { auxstr += ",\"" + AttrMap::nameid2string(totp.key()) + "\":" + totp.mapped(); } } attrs[AttrMap::string2nameid(NODE_ATTR_PASSWORD_MANAGER)] = auxstr; } std::string MegaClient::getPartialAPs() { std::string ret; assert(isClientType(ClientType::PASSWORD_MANAGER) || isClientType(ClientType::VPN)); if (isClientType(ClientType::PASSWORD_MANAGER) && !getPasswordManagerBase().isUndef()) { // List of handles to recieve updates from subtree ret = "&e=" + toNodeHandle(getPasswordManagerBase()) + "&ir=1"; } // ir=1 -> Ignoring roots // ap=1 -> account packets ret += "&ap=1"; return ret; } void MegaClient::createPasswordManagerBase(int rTag, CommandCreatePasswordManagerBase::Completion cbRequest) { LOG_info << "Password Manager: Requesting pwmh creation to server"; auto newNode = std::make_unique(); const bool canChangeVault = true; const std::string defaultBaseFolderName = "My Passwords"; // arbitrary default name, eventually updatable by client apps putnodes_prepareOneFolder(newNode.get(), defaultBaseFolderName, canChangeVault); // encrypt node password with user's master key to be sent to backend/API for storage std::array encryptedKey; this->key.ecb_encrypt(const_cast(reinterpret_cast(newNode->nodekey.data())), encryptedKey.data(), newNode->nodekey.size()); newNode->nodekey.assign(reinterpret_cast(encryptedKey.data()), encryptedKey.size()); queueCommand( new CommandCreatePasswordManagerBase(this, std::move(newNode), rTag, std::move(cbRequest))); } error MegaClient::createPasswordEntry(const char* name, std::unique_ptr data, PasswordDataValidator dataValidator, std::shared_ptr nParent, int rTag) { if (!name || !data) return API_EARGS; std::map> aux; aux[name] = std::move(data); return createPasswordEntries(std::move(aux), dataValidator, nParent, rTag); } error MegaClient::createPasswordEntries( ValidPasswordData&& data, std::function dataValidator, std::shared_ptr nParent, int rTag) { assert(nParent); if (!nParent->isPasswordManagerNodeFolder()) { LOG_err << "Password Manager: failed Password entry creation wrong parameters: Password " "Node Folder parent handle"; return API_EARGS; } std::vector nn(data.size()); size_t nodeToFillIndex = 0; const bool canChangeVault = true; Pitag pitag{PitagPurpose::Password, PitagTrigger::NotApplicable, PitagNodeType::NotApplicable, PitagTarget::CloudDrive, PitagImportSource::NotApplicable}; for (const auto& [name, dataAttrMap]: data) { if (name.empty() || !dataAttrMap) { assert(false); continue; } // Prevent storing password entry fields with empty strings dataAttrMap->removeEmptyValues(); if (const auto validCode = dataValidator(*dataAttrMap); validCode != PasswordEntryError::OK) { std::string errMsg = "Password Manager: Wrong parameters. Failed Password Entry " "creation for entry with name \"" + name + "\": "; LOG_err << errMsg << toString(validCode); return API_EAPPKEY; } const auto addAttrs = [this, d = dataAttrMap.get()](AttrMap& attrs) { preparePasswordManagerNodeData(attrs.map, *d); }; NewNode& newPasswordNode = nn[nodeToFillIndex++]; putnodes_prepareOneFolder(&newPasswordNode, name, canChangeVault, addAttrs); } const char* cauth = nullptr; putnodes(nParent->nodeHandle(), VersioningOption::NoVersioning, std::move(nn), cauth, rTag, canChangeVault, {}, // customerIpPort nullptr, pitag); return API_OK; } static std::vector getNodesNames(const sharedNode_list& nodes) { std::vector nodesNames; std::for_each(nodes.begin(), nodes.end(), [&nodesNames](const auto& node) { if (!node) { assert(false && "null nodes are not expected here"); return; } nodesNames.emplace_back(node->displayname()); }); return nodesNames; } MegaClient::ImportPaswordResult MegaClient::importPasswordsFromFile(const std::string& filePath, const pwm::import::FileSource source, const NodeHandle parentHandle, const int rTag) { using namespace pwm::import; const auto logReturnErr = [](const error err, const char* errMsg) -> ImportPaswordResult { LOG_err << "importPasswordsFromFile: " << errMsg; return {err, {}, 0}; }; std::shared_ptr parent = nodeByHandle(parentHandle); if (!parent || !parent->isPasswordManagerNodeFolder()) return logReturnErr(API_EARGS, "parent node doesn't exist"); PassFileParseResult parserResult = readPasswordImportFile(filePath, source); switch (parserResult.mErrCode) { case PassFileParseResult::ErrCode::OK: break; case PassFileParseResult::ErrCode::MISSING_COLUMN: case PassFileParseResult::ErrCode::NO_VALID_ENTRIES: return logReturnErr(API_EARGS, "invalid file format"); case PassFileParseResult::ErrCode::FILE_NOT_FOUND: case PassFileParseResult::ErrCode::CANT_OPEN_FILE: return logReturnErr(API_EREAD, "file can't be opened or doesn't exist"); case PassFileParseResult::ErrCode::INVALID_HEADER: return logReturnErr(API_EACCESS, "invalid header"); } ncoll::NameCollisionSolver collisionSolver{getNodesNames(getChildren(parent.get()))}; auto [badEntries, goodEntries] = MegaClient::validatePasswordEntries(std::move(parserResult.mResults), collisionSolver); const auto nGoodEntries = goodEntries.size(); if (nGoodEntries == 0) return {API_OK, badEntries, nGoodEntries}; return {createPasswordEntries(std::move(goodEntries), MegaClient::validateNewPasswordNodeData, parent, rTag), badEntries, nGoodEntries}; } PasswordEntryError MegaClient::validateTotpDataFormat(const AttrMap& data) { const auto shse = data.getStringView(PWM_ATTR_PASSWORD_TOTP_SHSE); std::optional nDigitsNum; if (const auto nDigits = data.getStringView(PWM_ATTR_PASSWORD_TOTP_NDIGITS); nDigits) nDigitsNum = static_cast(stringToNumber(*nDigits).value_or(0)); std::optional expTime; if (const auto expirationTime = data.getStringView(PWM_ATTR_PASSWORD_TOTP_EXPT); expirationTime) expTime = std::chrono::seconds(stringToNumber(*expirationTime).value_or(0)); const auto alg = data.getStringView(PWM_ATTR_PASSWORD_TOTP_HASH_ALG); const auto errors = totp::validateFields(shse, nDigitsNum, expTime, alg); if (errors.none()) return PasswordEntryError::OK; if (errors[totp::INVALID_TOTP_SHARED_SECRET]) return PasswordEntryError::INVALID_TOTP_SHARED_SECRET; if (errors[totp::INVALID_TOTP_NDIGITS]) return PasswordEntryError::INVALID_TOTP_NDIGITS; if (errors[totp::INVALID_TOTP_EXPT]) return PasswordEntryError::INVALID_TOTP_EXPT; if (errors[totp::INVALID_TOTP_ALG]) return PasswordEntryError::INVALID_TOTP_HASH_ALG; static_assert(errors.size() == totp::INVALID_TOTP_ALG + 1, "Missing validation error "); return PasswordEntryError::INVALID_TOTP_HASH_ALG; } PasswordEntryError MegaClient::validateNewNodeTotpData(const AttrMap& data) { if (!data.getStringView(PWM_ATTR_PASSWORD_TOTP_SHSE)) return PasswordEntryError::MISSING_TOTP_SHARED_SECRET; if (!data.getStringView(PWM_ATTR_PASSWORD_TOTP_NDIGITS)) return PasswordEntryError::MISSING_TOTP_NDIGITS; if (!data.getStringView(PWM_ATTR_PASSWORD_TOTP_EXPT)) return PasswordEntryError::MISSING_TOTP_EXPT; if (!data.getStringView(PWM_ATTR_PASSWORD_TOTP_HASH_ALG)) return PasswordEntryError::MISSING_TOTP_HASH_ALG; return validateTotpDataFormat(data); } PasswordEntryError MegaClient::validateNewPasswordNodeData(const AttrMap& data) { if (!isPwmDataOfType(data, PwmEntryType::PASSWORD)) return PasswordEntryError::PARSE_ERROR; if (const bool pwdPresent = (data.map.contains(AttrMap::string2nameid(PWM_ATTR_PASSWORD_PWD))); !pwdPresent) { return PasswordEntryError::MISSING_PASSWORD; } if (const bool totpPresent = (data.map.contains(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP))); totpPresent) { AttrMap totpMap; totpMap.fromjsonObject(data.map.at(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP)).c_str()); if (const auto res = validateNewNodeTotpData(totpMap); res != PasswordEntryError::OK) { return res; } } return PasswordEntryError::OK; } PasswordEntryError MegaClient::validateNewCreditCardNodeData(const AttrMap& data) { auto isValidExpDateFormat = [](const std::string_view s) { if ((s.size() != 5) || (s.at(2) != '/')) return false; if (const auto month = stringToNumber(s.substr(0, 2)); !month || *month > 12) return false; if (!isAllDigits(s.substr(3))) return false; return true; }; if (!isPwmDataOfType(data, PwmEntryType::CREDIT_CARD)) return PasswordEntryError::PARSE_ERROR; const auto creditCardNumber = data.getStringView(PWM_ATTR_CREDIT_CARD_NUMBER); if (!creditCardNumber || creditCardNumber->empty()) return PasswordEntryError::MISSING_CREDIT_CARD_NUMBER; if (!isAllDigits(*creditCardNumber)) return PasswordEntryError::INVALID_CREDIT_CARD_NUMBER; if (const auto cvv = data.getStringView(PWM_ATTR_CREDIT_CVV); cvv && !isAllDigits(*cvv)) return PasswordEntryError::INVALID_CREDIT_CARD_CVV; if (const auto exp = data.getStringView(PWM_ATTR_CREDIT_EXP_DATE); exp && !isValidExpDateFormat(*exp)) return PasswordEntryError::INVALID_CREDIT_CARD_EXPIRATION_DATE; return PasswordEntryError::OK; } std::pair MegaClient::validatePasswordEntries(std::vector&& entries, ncoll::NameCollisionSolver& nameValidator) { std::pair result; auto& bad = result.first; auto& good = result.second; for (auto& entry: entries) { if (entry.mErrCode != pwm::import::PassEntryParseResult::ErrCode::OK) { bad[std::move(entry.mOriginalContent)] = PasswordEntryError::PARSE_ERROR; continue; } if (entry.mName.empty()) { bad[std::move(entry.mOriginalContent)] = PasswordEntryError::MISSING_NAME; continue; } auto attrMap = std::make_unique(); auto addField = [&attrMap](std::string&& field, const std::string_view fieldKey) { if (!field.empty()) attrMap->map[AttrMap::string2nameid(fieldKey)] = std::move(field); }; addField(std::move(entry.mUrl), PWM_ATTR_PASSWORD_URL); addField(std::move(entry.mUserName), PWM_ATTR_PASSWORD_USERNAME); addField(std::move(entry.mPassword), PWM_ATTR_PASSWORD_PWD); addField(std::move(entry.mNote), PWM_ATTR_PASSWORD_NOTES); if (auto validationError = validateNewPasswordNodeData(*attrMap); validationError != PasswordEntryError::OK) { bad[entry.mOriginalContent] = validationError; continue; } good[nameValidator(entry.mName)] = std::move(attrMap); } return result; } error MegaClient::updateCreditCardNode(const NodeHandle nh, std::unique_ptr newData, CommandSetAttr::Completion&& cb) { auto pwdNode = nodeByHandle(nh); if (const auto err = checkRenameNodePrecons(pwdNode); err != API_OK) return err; if (!newData || newData->map.empty() || (newData->map.size() == 1 && newData->getStringView(PWM_ATTR_NODE_TYPE))) return logAndReturnError( API_EARGS, "Password Manager: calling updateCreditCardNode with nothing to update"); if (!pwdNode->isCreditCardNode()) return logAndReturnError( API_EARGS, "Password Manager: calling updateCreditCardNode with not a Credit Card Node handle"); AttrMap mergedData = pwdNode->attrs.getNestedJsonObject(NODE_ATTR_PASSWORD_MANAGER).value_or(AttrMap{}); mergedData.applyUpdates(newData->map); if (const auto validCode = validateNewCreditCardNodeData(mergedData); validCode != PasswordEntryError::OK) return logAndReturnError(API_EAPPKEY, "Password Manager: calling updateCreditCardNode with data that " "leaves the node in an invalid state. Validation error: " + std::string{toString(validCode)}); attr_map updates; preparePasswordManagerNodeData(updates, mergedData); static constexpr bool CAN_CHANGE_VAULT{true}; return setattr(std::move(pwdNode), std::move(updates), std::move(cb), CAN_CHANGE_VAULT); } error MegaClient::updatePasswordNode(const NodeHandle nh, std::unique_ptr newData, CommandSetAttr::Completion&& cb) { auto pwdNode = nodeByHandle(nh); if (const auto err = checkRenameNodePrecons(pwdNode); err != API_OK) return err; if (!newData || newData->map.empty()) return logAndReturnError( API_EARGS, "Password Manager: calling updatePasswordNode with nothing to update"); if (!pwdNode->isPasswordNode()) return logAndReturnError( API_EARGS, "Password Manager: calling updatePasswordNode with not a Password node handle"); AttrMap mergedData = pwdNode->attrs.getNestedJsonObject(NODE_ATTR_PASSWORD_MANAGER).value_or(AttrMap{}); mergedData.removeEmptyValues(); // complex Json Objects inside another AttrMap value (like totp) must be extracted using // getComplexNestedJsonObject auto totpData = pwdNode->attrs.getComplexNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER, MegaClient::PWM_ATTR_PASSWORD_TOTP); if (totpData && totpData->map.contains(AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP))) { mergedData.map[AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP)] = totpData->map[AttrMap::string2nameid(PWM_ATTR_PASSWORD_TOTP)]; } mergedData.applyUpdatesWithNestedFields(*newData, std::array{PWM_ATTR_PASSWORD_TOTP}); if (const auto validCode = validateNewPasswordNodeData(mergedData); validCode != PasswordEntryError::OK) return logAndReturnError(API_EAPPKEY, "Password Manager: calling updatePasswordNode with data that " "leaves the node in an invalid state. Validation error: " + std::string{toString(validCode)}); attr_map updates; preparePasswordManagerNodeData(updates, mergedData); static constexpr bool CAN_CHANGE_VAULT{true}; return setattr(std::move(pwdNode), std::move(updates), std::move(cb), CAN_CHANGE_VAULT); } std::string MegaClient::generatePasswordChars(const bool useUpper, const bool useDigits, const bool useSymbols, const unsigned int length) { if (length < 8 || length > 64) { LOG_err << "Characters-based password generation requested for invalid length. " << "Valid length range is [8, 64]"; return std::string{}; } static const std::string lowerCase = "abcdefghijklmnopqrstuvwxyz"; static const std::string upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const std::string digits = "0123456789"; static const std::string symbols = "!@#$%^&*()"; std::string pwd; pwd.reserve(length); std::random_device rd; std::mt19937 gen(rd()); // Mersenne Twister generator const auto appendOneRandom = [&pwd, &gen](const std::string& src) { std::uniform_int_distribution<> dis(0, static_cast(src.size() - 1)); pwd += src[static_cast(dis(gen))]; }; std::string pool = lowerCase; appendOneRandom(lowerCase); if (useUpper) { appendOneRandom(upperCase); // Make sure there is at least 1 pool += upperCase; } if (useDigits) { appendOneRandom(digits); pool += digits; } if (useSymbols) { appendOneRandom(symbols); pool += symbols; } std::uniform_int_distribution<> dis(0, static_cast(pool.size() - 1)); for (auto i = pwd.size(); i < length; ++i) pwd += pool[static_cast(dis(gen))]; // We shuffle to avoid the first mandatory types std::shuffle(std::begin(pwd), std::end(pwd), gen); return pwd; } void MegaClient::getNotifications(CommandGetNotifications::ResultFunc onResult) { queueCommand(new CommandGetNotifications(this, onResult)); } void MegaClient::getActiveSurveyTriggerActions( CommandGetActiveSurveyTriggerActions::Completion&& completion) { queueCommand(new CommandGetActiveSurveyTriggerActions(this, std::move(completion))); } void MegaClient::getSurvey(unsigned int triggerActionId, CommandGetSurvey::Completion&& completion) { queueCommand(new CommandGetSurvey(this, triggerActionId, std::move(completion))); } void MegaClient::answerSurvey(const CommandAnswerSurvey::Answer& answer, CommandAnswerSurvey::Completion&& completion) { queueCommand(new CommandAnswerSurvey(this, answer, std::move(completion))); } std::pair MegaClient::getFlag(const char* flagName) { enum : uint32_t // 1:1 with enum values from public interface { FLAG_TYPE_INVALID = 0, FLAG_TYPE_AB_TEST = 1, FLAG_TYPE_FEATURE = 2, }; if (!flagName) { return {FLAG_TYPE_INVALID, 0}; } unique_ptr flagValue = mABTestFlags.get(flagName); if (flagValue) { return {FLAG_TYPE_AB_TEST, *flagValue}; } flagValue = mFeatureFlags.get(flagName); if (flagValue) { return {FLAG_TYPE_FEATURE, *flagValue}; } return {FLAG_TYPE_INVALID, 0}; } FetchNodesStats::FetchNodesStats() { init(); } void FetchNodesStats::init() { mode = MODE_NONE; type = TYPE_NONE; cache = API_NONE; nodesCached = 0; nodesCurrent = 0; actionPackets = 0; eAgainCount = 0; e500Count = 0; eOthersCount = 0; startTime = Waiter::ds; timeToFirstByte = NEVER; timeToLastByte = NEVER; timeToCached = NEVER; timeToResult = NEVER; timeToSyncsResumed = NEVER; timeToCurrent = NEVER; timeToTransfersResumed = NEVER; } void FetchNodesStats::toJsonArray(string *json) { if (!json) { return; } ostringstream oss; oss << "[" << mode << "," << type << "," << nodesCached << "," << nodesCurrent << "," << actionPackets << "," << eAgainCount << "," << e500Count << "," << eOthersCount << "," << timeToFirstByte << "," << timeToLastByte << "," << timeToCached << "," << timeToResult << "," << timeToSyncsResumed << "," << timeToCurrent << "," << timeToTransfersResumed << "," << cache << "]"; json->append(oss.str()); } const std::string KeyManager::SVCRYPTO_PAIRWISE_KEY = "strongvelope pairwise key\x01"; void KeyManager::init(const string& prEd25519, const string& prCu25519, const string& prRSA) { if (mVersion != 0 || mGeneration != 0) { LOG_err << "Init invoked incorrectly"; assert(false); return; } mVersion = 1; mCreationTime = static_cast(time(nullptr)); mIdentity = mClient.me; mGeneration = 1; mPrivEd25519 = prEd25519; mPrivCu25519 = prCu25519; mPrivRSA.clear(); if (prRSA.size()) { string prRSABin = Base64::atob(prRSA); AsymmCipher ac; if (!ac.setkey(AsymmCipher::PRIVKEY, (const unsigned char*)prRSABin.data(), (int)prRSABin.size())) { LOG_err << "Priv RSA key problem during KeyManager initialization."; assert(false); } else { // Store it in the short format (3 Ints): pqd. ac.serializekey(&mPrivRSA, AsymmCipher::PRIVKEY_SHORT); } } else { assert(mClient.loggedin() == EPHEMERALACCOUNTPLUSPLUS); } if (!mPostRegistration) { // We request the upgrade after nodes_current to be able to migrate shares // mClient.app->upgrading_security(); } else { mPostRegistration = false; } } void KeyManager::setKey(const mega::SymmCipher &masterKey) { // Derive key from MK CryptoPP::HKDF hkdf; byte derivedKey[SymmCipher::KEYLENGTH]; byte info[1]; info[0] = 1; hkdf.DeriveKey(derivedKey, sizeof(derivedKey), masterKey.key, SymmCipher::KEYLENGTH, nullptr, 0, info, sizeof(info)); mKey.setkey(derivedKey); if (mDebugContents) { LOG_verbose << "Derived key (B64): " << Base64::btoa(string((const char*)derivedKey, SymmCipher::KEYLENGTH)); } } bool KeyManager::fromKeysContainer(const string &data) { bool success = false; KeyManager km(mClient); // keymanager to store values temporary if (data.size() > 2 && data[0] == 20) { // data[1] is reserved, always 0 if (data.size() > 2 + IV_LEN) { const string keysCiphered((const char*)(data.data() + 2 + IV_LEN), (size_t)(data.size() - 2 - IV_LEN)); const string iv((const char*)data.data() + 2, IV_LEN); // Decrypt ^!keys attribute string keysPlain; if (!mKey.gcm_decrypt(&keysCiphered, (byte*)data.data() + 2, IV_LEN, 16, &keysPlain)) { LOG_err << "Failed to GCM decrypt ^!keys."; return false; } success = unserialize(km, keysPlain); if (!success) { LOG_err << "Failed to unserialize ^!keys. Ignoring received value"; mClient.sendevent(99463, "KeyMgr / Failed to unserialize ^!keys"); } } else LOG_err << "Failed to decode ^!keys. Unexpected size"; } // validate received data and update local values if (success) success = isValidKeysContainer(km); if (success) { updateValues(km); } assert(success); return success; } bool KeyManager::isValidKeysContainer(const KeyManager& km) { // downgrade attack detection if (km.mGeneration < mGeneration) { ostringstream msg; msg << "KeyMgr / Downgrade attack for ^!keys: " << km.mGeneration << " < " << mGeneration; LOG_err << msg.str(); mClient.sendevent(99461, msg.str().c_str()); // block updates of ^!keys attribute and notify the app, so it can // warn about the potential attack and block user's interface mDowngradeAttack = true; mClient.app->downgrade_attack(); return false; } // validate private Ed25519 key if (mPrivEd25519.empty()) { mPrivEd25519 = km.mPrivEd25519; } assert(mPrivEd25519 == km.mPrivEd25519); // validate private Cu25519 key if (mPrivCu25519.empty()) { mPrivCu25519 = km.mPrivCu25519; } assert(mPrivCu25519 == km.mPrivCu25519); // validate private RSA key if (mPrivRSA.empty()) { assert(km.mPrivRSA.empty() || km.mPrivRSA.size() >= 512); if (km.mPrivRSA.empty()) { LOG_warn << "Empty RSA key"; } else if (km.mPrivRSA.size() < 512) { LOG_err << "Invalid RSA key"; } else { mPrivRSA = km.mPrivRSA; if (!decodeRSAKey()) { LOG_warn << "Private key malformed while unserializing ^!keys."; } // Note: the copy of privRSA from ^!keys will be used exclusively for legacy RSA functionality (MEGAdrop, not supported by SDK) } } assert(mPrivRSA == km.mPrivRSA); return true; } void KeyManager::updateValues(KeyManager &km) { mVersion = km.mVersion; mCreationTime = km.mCreationTime; mIdentity = km.mIdentity; mGeneration = km.mGeneration; mAttr = std::move(km.mAttr); // private keys do not change -> no need to update // mPrivEd25519 = km.mPrivEd25519; // mPrivCu25519 = km.mPrivCu25519; // mPrivRSA = km.mPrivRSA; updateAuthring(ATTR_AUTHRING, km.mAuthEd25519); updateAuthring(ATTR_AUTHCU255, km.mAuthCu25519); updateShareKeys(km.mShareKeys); mPendingOutShares = std::move(km.mPendingOutShares); mPendingInShares = std::move(km.mPendingInShares); mBackups = std::move(km.mBackups); mWarnings = std::move(km.mWarnings); mOther = std::move(km.mOther); if (promotePendingShares()) { LOG_debug << "Promoting pending shares after an update of ^!keys"; commit([this]() { // Changes to apply in the commit promotePendingShares(); }); // No completion callback in this case } } string KeyManager::toKeysContainer() { if (mVersion == 0) { LOG_err << "Failed to prepare container from keys. Not initialized yet"; assert(false); return string(); } // Do not update mGeneration here, since it may lead to fake // detection of downgrade-attacks. Instead, use mGeneration+1 // at the serialize(). The mGeneration will be updated later, // when the putua() from updateAttribute() success. //++mGeneration; const string iv = mClient.rng.genstring(IV_LEN); const string keysPlain = serialize(); string keysCiphered; if (!mKey.gcm_encrypt(&keysPlain, (byte*)iv.data(), IV_LEN, 16, &keysCiphered)) { LOG_err << "Failed to encrypt keys attribute."; assert(false); return string(); } #ifndef NDEBUG byte header[2] = {20, 0}; assert(string({20, 0}) == string((const char*)header, sizeof(header))); #endif return string({20, 0}) + iv + keysCiphered; } string KeyManager::tagHeader(const byte tag, size_t len) const { vector res; res.push_back(tag); res.push_back(static_cast((len & 0xFF0000) >> 16)); res.push_back(static_cast((len & 0xFF00) >> 8)); res.push_back(static_cast(len & 0xFF)); return string((const char*)res.data(), res.size()); } string KeyManager::serialize() const { string result; result.append(tagHeader(TAG_VERSION, sizeof(mVersion))); result.append((const char*)&mVersion, sizeof(mVersion)); result.append(tagHeader(TAG_CREATION_TIME, sizeof(mCreationTime))); uint32_t creationTimeBE = htonl(mCreationTime); // Webclient sets this value as BigEndian result.append((const char*)&creationTimeBE, sizeof(creationTimeBE)); result.append(tagHeader(TAG_IDENTITY, sizeof(mIdentity))); result.append((const char*)&mIdentity, sizeof(mIdentity)); result.append(tagHeader(TAG_GENERATION, sizeof(mGeneration))); uint32_t generationBE = htonl(mGeneration+1); // Webclient sets this value as BigEndian result.append((const char*)&generationBE, sizeof(generationBE)); result.append(tagHeader(TAG_ATTR, mAttr.size())); result.append(mAttr); assert(mPrivEd25519.size() == EdDSA::SEED_KEY_LENGTH); result.append(tagHeader(TAG_PRIV_ED25519, EdDSA::SEED_KEY_LENGTH)); result.append(mPrivEd25519); assert(mPrivCu25519.size() == ECDH::PRIVATE_KEY_LENGTH); result.append(tagHeader(TAG_PRIV_CU25519, ECDH::PRIVATE_KEY_LENGTH)); result.append(mPrivCu25519); assert(!mPrivRSA.size() || mPrivRSA.size() > 512); result.append(tagHeader(TAG_PRIV_RSA, mPrivRSA.size())); result.append(mPrivRSA); result.append(tagHeader(TAG_AUTHRING_ED25519, mAuthEd25519.size())); result.append(mAuthEd25519); result.append(tagHeader(TAG_AUTHRING_CU25519, mAuthCu25519.size())); result.append(mAuthCu25519); string shareKeys = serializeShareKeys(); result.append(tagHeader(TAG_SHAREKEYS, shareKeys.size())); result.append(shareKeys); string pendingOutshares = serializePendingOutshares(); result.append(tagHeader(TAG_PENDING_OUTSHARES, pendingOutshares.size())); result.append(pendingOutshares); string pendingInshares = serializePendingInshares(); result.append(tagHeader(TAG_PENDING_INSHARES, pendingInshares.size())); result.append(pendingInshares); string backups = serializeBackups(); result.append(tagHeader(TAG_BACKUPS, backups.size())); result.append(backups); string warnings = serializeWarnings(); result.append(tagHeader(TAG_WARNINGS, warnings.size())); result.append(warnings); result.append(mOther); return result; } uint32_t KeyManager::generation() const { return mGeneration; } string KeyManager::privEd25519() const { return mPrivEd25519; } string KeyManager::privCu25519() const { return mPrivCu25519; } void KeyManager::setPostRegistration(bool postRegistration) { mPostRegistration = postRegistration; } bool KeyManager::addPendingOutShare(handle sharehandle, std::string uid) { mPendingOutShares[sharehandle].insert(uid); return true; } bool KeyManager::addPendingInShare(std::string sharehandle, handle userHandle, std::string encrytedKey) { mPendingInShares[sharehandle] = pair(userHandle, encrytedKey); return true; } bool KeyManager::removePendingOutShare(handle sharehandle, std::string uid) { bool removed = false; User *user = mClient.finduser(uid.c_str(), 0); if (user) { removed = mPendingOutShares[sharehandle].erase(user->email) > 0; removed |= mPendingOutShares[sharehandle].erase(user->uid) > 0; } else { removed = mPendingOutShares[sharehandle].erase(uid) > 0; } return removed; } bool KeyManager::removePendingInShare(std::string shareHandle) { return mPendingInShares.erase(shareHandle) > 0; } bool KeyManager::addShareKey(handle sharehandle, std::string shareKey, bool sharedSecurely) { auto it = mShareKeys.find(sharehandle); if (it != mShareKeys.end() && it->second.second[ShareKeyFlagsId::TRUSTED] && it->second.first != shareKey) { LOG_warn << "Replacement of trusted sharekey for " << toNodeHandle(sharehandle); mClient.sendevent(99470, "KeyMgr / Replacing trusted sharekey"); assert(false); } ShareKeyFlags flags; flags[ShareKeyFlagsId::TRUSTED] = sharedSecurely; mShareKeys[sharehandle] = pair(shareKey, flags); return true; } string KeyManager::getShareKey(handle sharehandle) const { auto it = mShareKeys.find(sharehandle); if (it != mShareKeys.end()) { return it->second.first; } return std::string(); } bool KeyManager::isShareKeyTrusted(handle sharehandle) const { auto it = mShareKeys.find(sharehandle); return it != mShareKeys.end() && it->second.second[ShareKeyFlagsId::TRUSTED]; } bool KeyManager::isShareKeyInUse(handle sharehandle) const { auto it = mShareKeys.find(sharehandle); return it != mShareKeys.end() && it->second.second[ShareKeyFlagsId::INUSE]; } void KeyManager::setSharekeyInUse(handle sharehandle, bool sent) { auto it = mShareKeys.find(sharehandle); if (it != mShareKeys.end()) { it->second.second[ShareKeyFlagsId::INUSE] = sent; } else { string msg = "Trying to set share key as in-use for non-existing share key"; LOG_err << msg; assert(it != mShareKeys.end() && msg.c_str()); } } void KeyManager::syncSharekeyInUseBit() { vector sharesToClear; for(const auto &it : mShareKeys) { // Store the handle of the existing nodes which are no longer shared and with the in-use bit still set. if(it.second.second[ShareKeyFlagsId::INUSE]) { NodeHandle nh = NodeHandle().set6byte(it.first); std::shared_ptr n = mClient.nodeByHandle(nh); if (n && !n->isShared()) { sharesToClear.emplace_back(nh); } } } if(sharesToClear.size()) { commit( [this, sharesToClear]() { string handles; for(const auto &nh : sharesToClear) { setSharekeyInUse(nh.as8byte(), false); handles += " " + toNodeHandle(nh); } LOG_debug << "Clearing in-use bit of the share-keys no longer in use. Applied to:" << handles; }); } } string KeyManager::encryptShareKeyTo(handle userhandle, std::string shareKey) { if (verificationRequired(userhandle)) { return std::string(); } std::string sharedKey = computeSymmetricKey(userhandle); if (!sharedKey.size()) { return std::string(); } std::string encryptedKey; encryptedKey.resize(CryptoPP::AES::BLOCKSIZE); CryptoPP::ECB_Mode::Encryption aesencryption((byte *)sharedKey.data(), sharedKey.size()); aesencryption.ProcessData((byte *)encryptedKey.data(), (byte *)shareKey.data(), shareKey.size()); return encryptedKey; } string KeyManager::decryptShareKeyFrom(handle userhandle, std::string key) { if (verificationRequired(userhandle)) { return std::string(); } std::string sharedKey = computeSymmetricKey(userhandle); if (!sharedKey.size()) { return std::string(); } std::string shareKey; shareKey.resize(CryptoPP::AES::BLOCKSIZE); CryptoPP::ECB_Mode::Decryption aesencryption((byte *)sharedKey.data(), sharedKey.size()); aesencryption.ProcessData((byte *)shareKey.data(), (byte *)key.data(), key.size()); return shareKey; } void KeyManager::setAuthRing(std::string authring) { mAuthEd25519 = authring; } void KeyManager::setAuthCU255(std::string authring) { mAuthCu25519 = authring; } void KeyManager::setPrivRSA(std::string privRSA) { mPrivRSA = privRSA; } string KeyManager::getPrivRSA() { return mPrivRSA; } bool KeyManager::promotePendingShares() { bool attributeUpdated = false; bool newshares = false; std::vector keysToDelete; for (const auto& it : mPendingOutShares) { handle nodehandle = it.first; for (const auto& uid : it.second) { User *u = mClient.finduser(uid.c_str(), 0); if (u && !verificationRequired(u->userhandle)) { LOG_debug << "Promoting pending outshare of node " << toNodeHandle(nodehandle) << " for " << uid; auto shareit = mShareKeys.find(nodehandle); if (shareit != mShareKeys.end()) { std::string encryptedKey = encryptShareKeyTo(u->userhandle, shareit->second.first); if (encryptedKey.size()) { mClient.queueCommand(new CommandPendingKeys( &mClient, u->userhandle, nodehandle, (byte*)encryptedKey.data(), [uid](Error err) { if (err) { LOG_err << "Error sending share key: " << err; } else { LOG_debug << "Share key correctly sent"; } })); keysToDelete.push_back(uid); attributeUpdated = true; } else { LOG_warn << "Unable to encrypt share key to promote pending outshare " << toNodeHandle(nodehandle) << " uh: " << toHandle(u->userhandle); } } } } for (const auto& uid : keysToDelete) { removePendingOutShare(nodehandle, uid); } keysToDelete.clear(); } for (const auto& it : mPendingInShares) { handle nodeHandle = 0; Base64::atob(it.first.c_str(), (byte*)&nodeHandle, MegaClient::NODEHANDLE); handle userHandle = it.second.first; std::string encryptedShareKey = it.second.second; auto skit = mShareKeys.find(nodeHandle); if (!verificationRequired(userHandle)) { if (encryptedShareKey.size() > 16) { // Legacy bug: SDK used to store share keys in B64. If size > 16 // the share key should be converted first into binary data. string msg = "KeyMgr / Pending inshare key from string to binary"; mClient.sendevent(99480, msg.c_str()); encryptedShareKey = Base64::atob(it.second.second); } LOG_debug << "Promoting pending inshare of node " << toNodeHandle(nodeHandle) << " for " << toHandle(userHandle); std::string shareKey = decryptShareKeyFrom(userHandle, encryptedShareKey); if (shareKey.size()) { if (skit != mShareKeys.end() && skit->second.first != shareKey) { LOG_warn << "Updating share key for inshare " << toNodeHandle(nodeHandle) << " uh: " << toHandle(userHandle); } addShareKey(nodeHandle, shareKey, true); mClient.newshares.push_back(new NewShare(nodeHandle, 0, UNDEF, ACCESS_UNKNOWN, 0, (byte *)shareKey.data())); keysToDelete.push_back(it.first); attributeUpdated = true; newshares = true; } else { LOG_warn << "Unable to decrypt share key to promote pending inshare " << toNodeHandle(nodeHandle) << " uh: " << toHandle(userHandle); } } else { // If the inshare root is already present and decrypted, // do not keep it in pending-inshares // (avoid downgrading established shares) auto node = mClient.mNodeManager.getNodeByHandle(NodeHandle().set6byte(nodeHandle)); const bool establishedUsableInshare = node && node->keyApplied() && node->inshare && node->inshare->user && node->inshare->user->userhandle == userHandle && (skit != mShareKeys.end()); if (establishedUsableInshare) { keysToDelete.push_back(it.first); attributeUpdated = true; continue; } } } for (const auto& shareHandle : keysToDelete) { removePendingInShare(shareHandle); } keysToDelete.clear(); if (newshares) { mClient.mergenewshares(true); } return attributeUpdated; } bool KeyManager::isUnverifiedOutShare(handle nodeHandle, const string& uid) const { auto it = mPendingOutShares.find(nodeHandle); if (it == mPendingOutShares.end()) { return false; } for (const auto& uidIt : it->second) { if (uidIt == uid) { return true; } // if 'uid' is a userhandle, try to match by email // (in case of pending outshare that later upgrades to outshare by // sharee accepting the PCR, the 'uid' in 'keys.pendingoutshares' will // keep the email, but we already know the userHandle) if (uid.find("@") == uid.npos) { User* u = mClient.finduser(uid.c_str(), 0); if (u && uidIt == u->email) { return true; } } } return false; } bool KeyManager::isUnverifiedInShare(handle nodeHandle, handle userHandle) { auto it = mPendingInShares.find(toNodeHandle(nodeHandle)); if (it == mPendingInShares.end()) { return false; } if (it->second.first == userHandle) { return true; } return false; } void KeyManager::loadShareKeys() { for (const auto& it : mShareKeys) { handle sharehandle = it.first; std::string shareKey = it.second.first; std::shared_ptr n = mClient.nodebyhandle(sharehandle); if (n && !n->sharekey) { std::unique_ptr newShare(new NewShare(sharehandle, n->inshare ? 0 : -1, UNDEF, ACCESS_UNKNOWN, 0, (byte *)shareKey.data())); mClient.mergenewshare(newShare.get(), true, false); } } } void KeyManager::commit(std::function applyChanges, std::function completion) { LOG_debug << "[keymgr] New update requested"; if (mVersion == 0) { LOG_err << "Not initialized yet. Cancelling the update."; assert(false); if (completion) { completion(API_EINTERNAL); } return; } nextQueue.push_back(std::pair, std::function>(std::move(applyChanges), std::move(completion))); if (activeQueue.size()) { LOG_debug << "[keymgr] Another commit is in progress. Queued updates: " << nextQueue.size(); return; } nextCommit(); } void KeyManager::reset() { mVersion = 0; mCreationTime = 0; mIdentity = UNDEF; mGeneration = 0; mAttr.clear(); mPrivEd25519.clear(); mPrivCu25519.clear(); mPrivRSA.clear(); mAuthEd25519.clear(); mAuthCu25519.clear(); mBackups.clear(); mWarnings.clear(); mOther.clear(); mPendingInShares.clear(); mPendingOutShares.clear(); mShareKeys.clear(); mDowngradeAttack = false; mManualVerification = false; mPostRegistration = false; nextQueue.clear(); activeQueue.clear(); } string KeyManager::toString() const { ostringstream buf; buf << "Version: " << (int)mVersion << "\n"; buf << "Creation time: " << mCreationTime<< "\n"; buf << "Identity: " << toHandle(mIdentity)<< "\n"; buf << "Generation: " << mGeneration<< "\n"; buf << "Attr: " << Base64::btoa(mAttr)<< "\n"; buf << "PrivEd25519: " << Base64::btoa(mPrivEd25519)<< "\n"; buf << "PrivCu25519: " << Base64::btoa(mPrivCu25519)<< "\n"; buf << "PrivRSA: " << Base64::btoa(mPrivRSA)<< "\n"; buf << "Authring Ed25519:\n" << AuthRing::toString(mClient.mAuthRings.at(ATTR_AUTHRING))<< "\n"; buf << "Authring Cu25519:\n" << AuthRing::toString(mClient.mAuthRings.at(ATTR_AUTHCU255))<< "\n"; buf << shareKeysToString(*this); buf << pendingOutsharesToString(*this); buf << pendingInsharesToString(*this); buf << "Backups: " << Base64::btoa(mBackups) << "\n"; buf << warningsToString(*this); return buf.str(); } bool KeyManager::getContactVerificationWarning() { auto it = mWarnings.find("cv"); if (it != mWarnings.end() && mWarnings["cv"].size()) { char* endp; long int res; errno = 0; res = strtol(mWarnings["cv"].c_str(), &endp, 10); if (*endp != '\0' || endp == mWarnings["cv"].c_str() || errno == ERANGE) { LOG_err << "cv field in warnings is malformed"; return false; } return res != 0; } return false; } void KeyManager::setContactVerificationWarning(bool enabled) { mWarnings["cv"] = std::to_string(enabled); } string KeyManager::shareKeysToString(const KeyManager& km) { ostringstream buf; buf << "Share Keys:\n"; unsigned count = 0; for (const auto &it : km.mShareKeys) { ++count; handle h = it.first; const string& shareKeyStr = it.second.first; bool trust = it.second.second[ShareKeyFlagsId::TRUSTED]; bool inUse = it.second.second[ShareKeyFlagsId::INUSE]; buf << "\t#" << count << "\t h: " << toNodeHandle(h) << " sk: " << Base64::btoa(shareKeyStr) << " t: " << trust << " used: " << inUse << "\n"; } return buf.str(); } string KeyManager::pendingOutsharesToString(const KeyManager& km) { ostringstream buf; buf << "Pending Outshares:\n"; unsigned count = 0; for (const auto &it : km.mPendingOutShares) { ++count; handle h = it.first; for (const auto& uid : it.second) { buf << "\t#" << count << "\th: " << toNodeHandle(h) << " user: " << uid << "\n"; } } return buf.str(); } string KeyManager::pendingInsharesToString(const KeyManager& km) { ostringstream buf; buf << "Pending Inshares:\n"; unsigned count = 0; for (const auto &it : km.mPendingInShares) { ++count; const string& nh = it.first; const handle& uh = it.second.first; const string& shareKey = it.second.second; buf << "\t#" << count << "\tn: " << nh << " uh: " << toHandle(uh) << " sk: " << Base64::btoa(shareKey) << "\n"; } return buf.str(); } string KeyManager::warningsToString(const KeyManager& km) { ostringstream buf; buf << "Warnings:\n"; for (const auto &it : km.mWarnings) { buf << "\ttag: \"" << it.first << "\" \tval: \"" << it.second << "\"\n"; } return buf.str(); } void KeyManager::nextCommit() { assert(activeQueue.empty()); if (nextQueue.size()) { LOG_debug << "[keymgr] Initializing a new commit" << " with " << nextQueue.size() << " updates"; activeQueue = std::move(nextQueue); nextQueue = {}; tryCommit(API_EINCOMPLETE, [this]() { nextCommit(); }); } else { LOG_debug << "[keymgr] No more updates in the queue."; } } void KeyManager::tryCommit(Error e, std::function completion) { if (!e || mDowngradeAttack || e == API_EARGS) { const std::string tmp(" with " + std::to_string(activeQueue.size()) + " updates"); if (e == API_OK) LOG_debug << "[keymgr] Commit completed" << tmp; else if (e == API_EARGS) LOG_debug << "[keymgr] Commit aborted, keys too large?" << tmp; else LOG_debug << "[keymgr] Commit aborted (downgrade attack)" << tmp; for (auto &activeCommit : activeQueue) { if (activeCommit.second) { activeCommit.second(e); // Run update completion callback } } activeQueue = {}; completion(); // -> nextCommit() or log "No more updates in the queue" return; } LOG_debug << "[keymgr] " << (e == API_EINCOMPLETE ? "Starting" : "Retrying") << " commit with " << activeQueue.size() << " updates"; for (auto &activeCommit : activeQueue) { if (activeCommit.first) { activeCommit.first(); // Apply commit changes } } updateAttribute([this, completion](Error e) { tryCommit(e, completion); }); } void KeyManager::updateAttribute(std::function completion) { string buf = toKeysContainer(); mClient.putua(ATTR_KEYS, (byte*)buf.data(), static_cast(buf.size()), 0, UNDEF, 0, 0, [this, completion](Error e) { if (!e) { completion(API_OK); return; } User* ownUser = mClient.finduser(mClient.me); if (!ownUser) { LOG_err << "[keymgr] Not logged in during commit"; completion(API_OK); // Returning API_OK to stop the loop return; } LOG_warn << "[keymgr] Error setting the value of ^!keys: (" << e << ")"; if (e != API_EEXPIRED) { completion(e); return; } mClient.sendevent(99462, "KeyMgr / Versioning clash for ^!keys"); mClient.queueCommand(new CommandGetUA( &mClient, ownUser->uid.c_str(), ATTR_KEYS, nullptr, 0, [completion](error err) { LOG_err << "[keymgr] Error getting the value of ^!keys (" << err << ")"; completion(err); }, [completion](byte*, unsigned, attr_t) { LOG_debug << "[keymgr] Success getting the value of ^!keys"; completion(API_EEXPIRED); }, nullptr)); }); } bool KeyManager::getPostRegistration() const { return mPostRegistration; } bool KeyManager::unserialize(KeyManager& km, const string &keysContainer) { // Decode blob const char* blob = keysContainer.data(); size_t blobLength = keysContainer.length(); static const uint8_t headerSize = 4; // 1 byte for Tag, 3 bytes for Length size_t offset = headerSize; while (offset <= blobLength) { byte tag = static_cast(blob[offset - headerSize]); size_t len = static_cast((static_cast(blob[offset - 3]) << 16) + (static_cast(blob[offset - 2]) << 8) + static_cast(blob[offset - 1])); if (offset + len > blobLength) { LOG_err << "Invalid record in ^!keys attributes: offset: " << offset << ", len: " << len << ", size: " << blobLength; return false; } if (mDebugContents) { LOG_verbose << "Tag: " << (int)tag << " Len: " << len; } switch (tag) { case TAG_VERSION: if (len != sizeof(km.mVersion)) return false; km.mVersion = MemAccess::get(blob + offset); if (mDebugContents) { LOG_verbose << "Version: " << (int)km.mVersion; } break; case TAG_CREATION_TIME: if (len != sizeof(km.mCreationTime)) return false; km.mCreationTime = MemAccess::get(blob + offset); km.mCreationTime = ntohl(km.mCreationTime); // Webclient sets this value as BigEndian if (mDebugContents) { LOG_verbose << "Creation time: " << km.mCreationTime; } break; case TAG_IDENTITY: if (len != sizeof(mIdentity)) return false; km.mIdentity = MemAccess::get(blob + offset); if (mDebugContents) { LOG_verbose << "Identity: " << toHandle(km.mIdentity); } break; case TAG_GENERATION: { if (len != sizeof(km.mGeneration)) return false; km.mGeneration = MemAccess::get(blob + offset); km.mGeneration = ntohl(km.mGeneration); // Webclient sets this value as BigEndian LOG_verbose << "KeyManager generation: " << km.mGeneration; break; } case TAG_ATTR: km.mAttr.assign(blob + offset, len); if (mDebugContents) { LOG_verbose << "Attr: " << Base64::btoa(km.mAttr); } break; case TAG_PRIV_ED25519: if (len != EdDSA::SEED_KEY_LENGTH) return false; km.mPrivEd25519.assign(blob + offset, len); if (mDebugContents) { LOG_verbose << "PrivEd25519: " << Base64::btoa(km.mPrivEd25519); } break; case TAG_PRIV_CU25519: if (len != ECDH::PRIVATE_KEY_LENGTH) return false; km.mPrivCu25519.assign(blob + offset, len); if (mDebugContents) { LOG_verbose << "PrivCu25519: " << Base64::btoa(km.mPrivCu25519); } break; case TAG_PRIV_RSA: { km.mPrivRSA.assign(blob + offset, len); if (mDebugContents) { LOG_verbose << "PrivRSA: " << Base64::btoa(km.mPrivRSA); } break; } case TAG_AUTHRING_ED25519: { attr_t at = ATTR_AUTHRING; km.mAuthEd25519.assign(blob + offset, len); AuthRing tmp(at, km.mAuthEd25519); if (mDebugContents) { LOG_verbose << "Authring Ed25519:\n" << AuthRing::toString(tmp); } break; } case TAG_AUTHRING_CU25519: { attr_t at = ATTR_AUTHCU255; km.mAuthCu25519.assign(blob + offset, len); AuthRing tmp(at, km.mAuthCu25519); if (mDebugContents) { LOG_verbose << "Authring Cu25519:\n" << AuthRing::toString(tmp); } break; } case TAG_SHAREKEYS: { string buf(blob + offset, len); if (!deserializeShareKeys(km, buf)) return false; if (mDebugContents) { LOG_verbose << shareKeysToString(km); } break; } case TAG_PENDING_OUTSHARES: { string buf(blob + offset, len); if (!deserializePendingOutshares(km, buf)) return false; if (mDebugContents) { LOG_verbose << pendingOutsharesToString(km); } break; } case TAG_PENDING_INSHARES: { string buf(blob + offset, len); if (!deserializePendingInshares(km, buf)) return false; if (mDebugContents) { LOG_verbose << pendingInsharesToString(km); } break; } case TAG_BACKUPS: { string buf(blob + offset, len); if (!deserializeBackups(km, buf)) return false; if (mDebugContents) { LOG_verbose << "Backups: " << Base64::btoa(km.mBackups); } break; } case TAG_WARNINGS: { string buf(blob + offset, len); if (!deserializeWarnings(km, buf)) return false; if (mDebugContents) { LOG_verbose << warningsToString(km); } break; } default: // any other tag needs to be stored as well, and included in newer versions km.mOther.append(blob + offset - headerSize, headerSize + len); break; } offset += headerSize + len; } return true; } bool KeyManager::deserializeShareKeys(KeyManager& km, const string &blob) { // clean old data, so we don't left outdated sharekeys in place km.mShareKeys.clear(); // [nodeHandle.6 shareKey.16 flags.1]* CacheableReader r(blob); while(r.hasdataleft()) { handle h = UNDEF; byte shareKey[SymmCipher::KEYLENGTH]; byte flagsBuf = 0; if (!r.unserializenodehandle(h) || !r.unserializebinary(shareKey, sizeof(shareKey)) || !r.unserializebyte(flagsBuf)) { LOG_err << "Share keys is corrupt"; return false; } string shareKeyStr((const char*)shareKey, sizeof(shareKey)); ShareKeyFlags flags(flagsBuf); km.mShareKeys[h] = pair(shareKeyStr, flags); } return true; } string KeyManager::serializeShareKeys() const { string result; CacheableWriter w(result); for (const auto& it : mShareKeys) { handle h = it.first; w.serializenodehandle(h); size_t shareKeyLen = it.second.first.size(); byte *shareKey = (byte*)it.second.first.data(); w.serializebinary(shareKey, shareKeyLen); byte flagsBuf = static_cast(it.second.second.to_ulong()); w.serializebyte(flagsBuf); } return result; } bool KeyManager::deserializePendingOutshares(KeyManager& km, const string &blob) { // clean old data, so we don't left outdated pending outshares in place km.mPendingOutShares.clear(); // [len.1 nodeHandle.6 uid]* // if len=0 -> uid is a user handle // if len!=0 -> uid is an email address CacheableReader r(blob); while(r.hasdataleft()) { byte len = 0; handle h = UNDEF; string uid; if (!r.unserializebyte(len) || !r.unserializenodehandle(h)) { LOG_err << "Pending outshare is corrupt: len or nodehandle"; return false; } bool success; if (len == 0) // user handle { handle uh = UNDEF; success = r.unserializehandle(uh); uid = toHandle(uh); } else { byte buf[256]; success = r.unserializebinary(buf, len); uid.append((const char*)buf, len); } if (!success) { LOG_err << "Pending outshare is corrupt: uid"; return false; } km.mPendingOutShares[h].emplace(uid); } return true; } string KeyManager::serializePendingOutshares() const { // [len.1 nodeHandle.6 uid] // if uid is a user handle --> len = 0 // if uid is an email address --> len = emailAddress.size() string result; CacheableWriter w(result); for (const auto& itNodes : mPendingOutShares) { handle h = itNodes.first; // handle of shared folder for (const std::string& uid : itNodes.second) { byte len = 0; if (uid.find('@') != string::npos) { if (uid.size() >= 256) { LOG_err << "Incorrect email size in pending outshare: " << uid; assert(false && "Incorrect email size in pending outshare"); continue; } len = static_cast(uid.size()); } else { if (uid.size() != 11) { LOG_err << "Incorrect user handle in pending outshare: " << uid; assert(false && "Incorrect user handle in pending outshare"); continue; } } w.serializebyte(len); w.serializenodehandle(h); bool isEmail = len > 0; if (isEmail) // uid is an email { w.serializebinary((byte*)uid.data(), uid.size()); } else // user's handle in binary format, 8 bytes { handle uh; #ifndef NDEBUG int uhsize = #endif Base64::atob(uid.c_str(), (byte*)&uh, sizeof uh); assert(uhsize == MegaClient::USERHANDLE); w.serializehandle(uh); } } } return result; } bool KeyManager::deserializeFromLTLV(const string& blob, map& data) { // blob format as follows: // [len.1 tag.len lenValue.2|6 value.lenValue]* // if lenValue == 0xFFFF -> length is indicated by next 4 extra bytes // if lenValue < 0xFFFF -> actual length (no extra bytes present) CacheableReader r(blob); while(r.hasdataleft()) { // length of the tag byte len = 0; if (!r.unserializebyte(len)) { LOG_err << "Corrupt LTLV: len of tag"; return false; } // read the tag string tag; tag.resize(len); if (!r.unserializebinary((byte*)tag.data(), tag.size())) { LOG_err << "Corrupt LTLV: tag"; return false; } // len of the value uint32_t lenValue = 0; uint16_t lenValue16 = 0; bool success = r.unserializeu16(lenValue16); lenValue16 = ntohs(lenValue16); // Webclient sets length as BigEndian if (lenValue16 == 0xFFFF) { success = r.unserializeu32(lenValue); lenValue = ntohl(lenValue); } else { lenValue = lenValue16; } if (!success) { LOG_err << "Corrupt LTLV: value len"; return false; } // read the value string value; value.resize(lenValue); if (!r.unserializebinary((byte*)value.data(), value.size())) { LOG_err << "Corrupt LTLV: value"; return false; } data[tag] = value; } return true; } string KeyManager::serializeToLTLV(const map& values) { // Encoded format as follows: // [len.1 tag.len lenValue.2|6 value.lenValue]* // if length of value < 0xFFFF -> 2 bytes for lenValue // else -> 2 bytes set as 0xFFFF and 4 extra bytes for actual length string result; CacheableWriter w(result); for (const auto& it : values) { // write tag length w.serializebyte(static_cast(it.first.size())); // write tag w.serializebinary((byte*)it.first.data(), it.first.size()); // write value length if (it.second.size() < 0xFFFF) { uint16_t lenValue16 = static_cast(it.second.size()); uint16_t lenValue16BE = htons(lenValue16); // Webclient sets length as BigEndian w.serializeu16(lenValue16BE); } else // excess, 4 extra bytes { w.serializeu16(0xFFFF); uint32_t lenValue32 = static_cast(it.second.size()); uint32_t lenValue32BE = htonl(lenValue32); w.serializeu32(lenValue32BE); } // write value w.serializebinary((byte*)it.second.data(), it.second.size()); } return result; } bool KeyManager::deserializePendingInshares(KeyManager& km, const string &blob) { // clean old data, so we don't left outdated pending inshares in place km.mPendingInShares.clear(); // key is the node handle, value includes the user's handle (8 bytes) and the encrypted share key map decodedBlob; if (!deserializeFromLTLV(blob, decodedBlob)) { LOG_err << "Pending inshare is corrupt"; return false; } for ( const auto& it : decodedBlob ) { if (it.second.size() < sizeof(handle)) // it may have only the user handle (no share key yet) { LOG_err << "Pending inshare is corrupt: incorrect value size"; return false; } // user handle (sharer) and share key CacheableReader r(it.second); handle uh = UNDEF; string shareKey; shareKey.resize(it.second.size() - sizeof(uh)); if (!r.unserializehandle(uh) || !r.unserializebinary((byte*)shareKey.data(), shareKey.size())) { LOG_err << "Pending inshare is corrupt: incorrect sharer handle or sharekey"; return false; } km.mPendingInShares[it.first] = pair(uh, shareKey); } return true; } string KeyManager::serializePendingInshares() const { map pendingInsharesToEncode; for (const auto& it : mPendingInShares) { string value; CacheableWriter w(value); w.serializehandle(it.second.first); // share's owner user handle w.serializebinary((byte*)it.second.second.data(), it.second.second.size()); pendingInsharesToEncode[it.first] = value; } return serializeToLTLV(pendingInsharesToEncode); } bool KeyManager::deserializeBackups(KeyManager& km, const string &blob) { // FIXME: add support to deserialize backups km.mBackups = blob; return true; } string KeyManager::serializeWarnings() const { return serializeToLTLV(mWarnings); } bool KeyManager::deserializeWarnings(KeyManager& km, const string &blob) { // clean old data, so we don't left outdated warnings km.mWarnings.clear(); return deserializeFromLTLV(blob, km.mWarnings); } string KeyManager::computeSymmetricKey(handle user) { User *u = mClient.finduser(user, 0); if (!u) { return std::string(); } const UserAttribute* attribute = u->getAttribute(ATTR_CU25519_PUBK); if (!attribute || !attribute->isValid()) { LOG_warn << "Unable to generate symmetric key. Public key not cached."; if (mClient.statecurrent && mClient.mAuthRingsTemp.find(ATTR_CU25519_PUBK) == mClient.mAuthRingsTemp.end()) { // if statecurrent=true -> contact keys should have been fetched // if no temporal authring for Cu25519 -> contact keys should be in cache LOG_warn << "Public key not cached with the authring already updated."; assert(false); mClient.sendevent(99464, "KeyMgr / Ed/Cu retrieval failed"); } return std::string(); } std::string sharedSecret; ECDH ecdh(mClient.mX255Key->getPrivKey(), attribute->value()); if (!ecdh.computeSymmetricKey(sharedSecret)) { return std::string(); } std::string step1; step1.resize(32); CryptoPP::HMAC hmac1(nullptr, 0); hmac1.CalculateDigest((byte *)step1.data(), (byte *)sharedSecret.data(), sharedSecret.size()); std::string sharedKey; sharedKey.resize(32); CryptoPP::HMAC hmac2((byte *)step1.data(), step1.size()); hmac2.CalculateDigest((byte *)sharedKey.data(), (byte *)SVCRYPTO_PAIRWISE_KEY.data(), SVCRYPTO_PAIRWISE_KEY.size()); sharedKey.resize(CryptoPP::AES::BLOCKSIZE); return sharedKey; } bool KeyManager::decodeRSAKey() { // LOG_verbose << Base64::btoa(mPrivRSA) << "\n\n" << Utils::stringToHex(mPrivRSA); string currentPK; mClient.mPrivateRsaKey.serializekey(¤tPK, AsymmCipher::PRIVKEY_SHORT); // Compare serialized keys using find just in case pqdKey has extra bytes. It should be found at pos = 0. size_t pos = mPrivRSA.find(currentPK); bool keyOk = (pos == 0); // Keep client RSA key serialized as received by login/"ug" // It is used by intermediate layer and login/ug are 4 Ints long, not 3, which could cause problems when comparing them. // mClient.mPrivKey = Base64::btoa(mPrivRSA); // update asymcipher to use RSA from ^!keys if (keyOk && !mClient.mPrivateRsaKey.setkey(AsymmCipher::PRIVKEY_SHORT, (const unsigned char*)mPrivRSA.data(), (int)mPrivRSA.size())) { keyOk = false; } assert(keyOk); return keyOk; } void KeyManager::updateAuthring(attr_t at, string& value) { string& authring = (at == ATTR_AUTHRING) ? mAuthEd25519 : mAuthCu25519; authring = std::move(value); mClient.mAuthRings.erase(at); if (authring.empty()) { mClient.mAuthRings.emplace(at, AuthRing(at)); } else { mClient.mAuthRings.emplace(at, AuthRing(at, authring)); } } void KeyManager::updateShareKeys(map>& shareKeys) { for (const auto& itNew : shareKeys) { handle h = itNew.first; const auto& itOld = mShareKeys.find(h); if (itOld != mShareKeys.end() && itNew.second != itOld->second) { if (itNew.second.first != itOld->second.first) { LOG_warn << "[keymgr] Sharekey for " << toNodeHandle(h) << " has changed. Updating..."; assert(!itOld->second.second[ShareKeyFlagsId::TRUSTED]); mClient.sendevent(99469, "KeyMgr / Replacing sharekey"); } else { if (itNew.second.second[ShareKeyFlagsId::TRUSTED] != itOld->second.second[ShareKeyFlagsId::TRUSTED]) { LOG_warn << "[keymgr] Trust for " << toNodeHandle(h) << " share key has changed (" << static_cast(itOld->second.second[ShareKeyFlagsId::TRUSTED]) << " -> " << static_cast(itNew.second.second[ShareKeyFlagsId::TRUSTED]) << "). Updating..."; } if (itNew.second.second[ShareKeyFlagsId::INUSE] != itOld->second.second[ShareKeyFlagsId::INUSE]) { LOG_debug << "[keymgr] In-use flag for " << toNodeHandle(h) << " share key has changed (" << static_cast(itOld->second.second[ShareKeyFlagsId::INUSE]) << " -> " << static_cast(itNew.second.second[ShareKeyFlagsId::INUSE]) << "). Updating..."; } // Compare the remaining flags ShareKeyFlags mask(0x03); // ShareKeyFlagsId::TRUSTED and ShareKeyFlagsId::INUSE mask.flip(); if ((itNew.second.second & mask) != (itOld->second.second & mask)) { LOG_warn << "[keymgr] Flags for " << toNodeHandle(h) << " share key has changed (" << itOld->second.second.to_ulong() << " -> " << itNew.second.second.to_ulong() << "). Updating..."; } } } } mShareKeys = std::move(shareKeys); // Set the sharekey to the node, if missing (since it might not have been received along with // the share itself (ok / k is discontinued since ^!keys) loadShareKeys(); } bool KeyManager::verificationRequired(handle userHandle) { if (mManualVerification) { return !mClient.areCredentialsVerified(userHandle); } // if no manual verification required, still check Ed25519 public key is SEEN AuthRingsMap::const_iterator it = mClient.mAuthRings.find(ATTR_AUTHRING); bool edAuthringFound = it != mClient.mAuthRings.end(); return !edAuthringFound || (it->second.getAuthMethod(userHandle) < AUTH_METHOD_SEEN); } string KeyManager::serializeBackups() const { // FIXME: once we add support to deserialize, adjust it here too return mBackups; } bool ScDbStateRecord::serialize(string* s) const { CacheableWriter w(*s); w.serializestring(seqTag); w.serializeexpansionflags(); return s != nullptr; } ScDbStateRecord ScDbStateRecord::unserialize(const std::string& data) { unsigned char ef[8] = {0, }; ScDbStateRecord result; CacheableReader reader(data); if (reader.unserializestring(result.seqTag) && reader.unserializeexpansionflags(ef, 0)) { // future fields processed here } return result; } #ifdef ENABLE_SYNC void MegaClient::getJSCData(GetJSCDataCallback callback) { // Sanity. assert(callback); // Try and get our hands on the currently logged in user. auto* user = ownuser(); // No user? No attributes. if (!user) { return callback({}, API_EFAILED); } // Convenience. using std::bind; using std::placeholders::_1; auto failed = bind(&MegaClient::JSCDataRetrieved, this, callback, _1, nullptr); // Try and retrieve the user's JSCD attribute. getua(user, ATTR_JSON_SYNC_CONFIG_DATA, 0, std::move(failed), nullptr, bind(&MegaClient::JSCDataRetrieved, this, std::move(callback), API_OK, _1)); } void MegaClient::createJSCData(GetJSCDataCallback callback) { // Sanity. assert(callback); // Convenience. constexpr auto Length = SymmCipher::KEYLENGTH; // Instantiate store to contain the JSC data. string_map store{ {"ak", rng.genstring(Length)}, // Key used to authenticate the sync configuration database. {"ck", rng.genstring(Length)}, // Key used to encrypt the sync configuration database. {"fn", rng.genstring(Length)}, // The name of the sync configuration database. }; // Convenience. using std::bind; using std::placeholders::_1; // Try and create the JSCD attribute. putua(ATTR_JSON_SYNC_CONFIG_DATA, std::move(store), 0, UNDEF, 0, 0, bind(&MegaClient::JSCDataCreated, this, std::move(callback), _1)); } void MegaClient::injectSyncSensitiveData(std::function callback) { // Sanity. assert(callback); // Called when the JSCD user attributes have been retrieved. auto retrieved = [callback = std::move(callback), this] (JSCData data, Error result) { // Couldn't retrieve JSC data. if (result != API_OK) { LOG_err << "Couldn't retrieve JSC data: " << result; if (result != API_EBLOCKED) { fatalError(ErrorReason::REASON_ERROR_NO_JSCD); } // This shouldn't prevent the user from continue using the account. return callback(API_OK); } SyncSensitiveData sensitiveData; // Prepare sensitive data for injection. sensitiveData.jscData = std::move(data); sensitiveData.stateCacheKey.assign(reinterpret_cast(key.key), sizeof(key.key)); // Inject sensitive data into the sync engine. LOG_debug << "JSCD retrieved. Injecting data to the sync system."; syncs.injectSyncSensitiveData(std::move(sensitiveData)); // Let the user know that JSCD has been successfully retrieved. callback(API_OK); }; // retrieved // Try and retrieve the JSC data. getJSCData(std::move(retrieved)); } void MegaClient::JSCDataCreated(GetJSCDataCallback& callback, Error result) { // Sanity. assert(callback); // Couldn't create JSCD and it wasn't created by another client. if (result != API_OK && result != API_EEXPIRED) { LOG_warn << "Couldn't create JSC data: " << result; return callback({}, result); } // JSCD was created by us or by another client so retrieve it. getJSCData(std::move(callback)); } void MegaClient::JSCDataRetrieved(GetJSCDataCallback& callback, Error result, unique_ptr store) { // Sanity. assert(callback); // The user doesn't have any JSC data or the attribute can't be decrypted or the attribute // exists but is fully empty. In any case, create a new one. if (result == API_ENOENT || result == API_EKEY || (result == API_OK && !store)) { if (result != API_ENOENT) { LOG_err << "Couldn't extract JSON SYNC configuration data. Automatically regenerating " "the attribute"; // Send an event and notify the app. Existing syncs (if any) have been removed. fatalError(ErrorReason::REASON_ERROR_REGENERATE_JSCD); } return createJSCData(std::move(callback)); } // We weren't able to retrieve the user's JSC data. if (result != API_OK) { return callback({}, result); } else if (mLastFatalErrorDetected == ErrorReason::REASON_ERROR_REGENERATE_JSCD) { LOG_debug << "JSON SYNC configuration has been correctly regenerated."; // Cleanup error to allow syncs to be configured mLastFatalErrorDetected = ErrorReason::REASON_ERROR_NO_ERROR; } JSCData data; // Extract JSC data. if (store) { data.authenticationKey.swap((*store)["ak"]); data.cipherKey.swap((*store)["ck"]); data.fileName.swap((*store)["fn"]); } // Saves a little typing. auto valid = [](const std::string& datum) { return datum.size() == SymmCipher::KEYLENGTH; }; // valid // Check validity of JSC data. if (!valid(data.authenticationKey)) { return callback({}, API_EINTERNAL); } if (!valid(data.cipherKey)) { return callback({}, API_EINTERNAL); } if (!valid(data.fileName)) { return callback({}, API_EINTERNAL); } // Translate filename to base64, as expected. data.fileName = Base64::btoa(data.fileName); // Pass attributes to callback. callback(std::move(data), API_OK); } #endif // ENABLE_SYNC // Call "wmip" command. void MegaClient::getMyIp(CommandGetMyIP::Cb&& completion) { queueCommand(new CommandGetMyIP(this, std::move(completion))); } // Call "gsc" command. void MegaClient::getSubscriptionCancellationDetails( const char* originalTransactionId, unsigned int gatewayId, CommandGetSubscriptionCancellationDetails::CompletionCallback&& completion) { queueCommand(new CommandGetSubscriptionCancellationDetails(this, originalTransactionId, gatewayId, std::move(completion))); } void MegaClient::queueCommand(Command* command) { // Convenience. using common::Badge; // Sanity. assert(command); // Transmit lockless commands on the lockless CS channel. if (command->isLockless()) return mReqsLockless.add({}, command); // Transmit lockfull commands on the standard CS channel. reqs.add({}, command); } void MegaClient::processHashcashSendevent() { const auto retryGencash = retryGencashData(); if (!retryGencash.has_value() || retryGencash->mEasiness == 0) return; int eventId{-1}; std::string eventMsg; switch (retryGencash->mForceRetryCount) { case 0u: { eventId = 800028; eventMsg = "Hashcash last retry timed out"; break; } case 1u: { eventId = 800026; eventMsg = "Hashcash preemptively forces retry n1"; break; } case RetryGencash::kMaxRetries: { eventId = 800027; eventMsg = "Hashcash preemptively forces retry n" + std::to_string(RetryGencash::kMaxRetries); break; } default: { LOG_err << "[MegaClient::processHashcashSendeven] unexpected RetryGencash counter: " << retryGencash->mForceRetryCount; return; } } eventMsg.append(". E: ") .append(std::to_string(retryGencash->mEasiness)) .append(". B: ") .append(std::to_string(retryGencash->mBudget.count())) .append(". T: ") .append(std::to_string(retryGencash->mGencashTime.count())); sendevent(eventId, eventMsg.c_str()); } std::string MegaClient::getMegaURL() { std::shared_lock lock(megaUrlMutex); return MEGAURL; } void MegaClient::setMegaURL(const std::string& url) { std::unique_lock lock(megaUrlMutex); MEGAURL = url; } void MegaClient::handleScChannel() { return handleScNonStreaming(); } void MegaClient::handleScNonStreaming() { if (!jsonsc.pos && !pendingscUserAlerts && pendingsc) { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED if (globalMegaTestHooks.interceptSCRequest) { globalMegaTestHooks.interceptSCRequest(pendingsc); } #endif switch (static_cast(pendingsc->status)) { case REQ_SUCCESS: pendingscTimedOut = false; if (pendingsc->contentlength == 1 && pendingsc->in.size() && pendingsc->in[0] == '0') { LOG_debug << "SC keep-alive received"; pendingsc.reset(); btsc.reset(); break; } if (*pendingsc->in.c_str() == '{') { insca = false; insca_notlast = false; jsonsc.begin(pendingsc->in.c_str()); jsonsc.enterobject(); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, API_OK); break; } else { handleScErrorInSuccessState(); } [[fallthrough]]; case REQ_FAILURE: handleScInFailureState(); break; case REQ_INFLIGHT: if (!pendingscTimedOut && Waiter::ds >= (pendingsc->lastdata + HttpIO::SCREQUESTTIMEOUT)) { LOG_debug << clientname << "sc timeout expired at ds: " << Waiter::ds << " and lastdata ds: " << pendingsc->lastdata; // In almost all cases the server won't take more than SCREQUESTTIMEOUT seconds. // But if it does, break the cycle of endless requests for the same thing pendingscTimedOut = true; pendingsc.reset(); btsc.reset(); } break; default: break; } } processScMessageNonStreaming(); return; } void MegaClient::processScMessageNonStreaming() { if (!scpaused && jsonsc.pos) { // FIXME: reload in case of bad JSON if (procsc(jsonsc)) { // completed - initiate next SC request jsonsc.pos = nullptr; pendingsc.reset(); btsc.reset(); // upon reception of action packets, if the cs request is waiting for a retry // and it failed due to -3 or -4 error from API, we can abort the backoff if (reqs.retryReasonIsApi()) { btcs.reset(); } } } return; } void MegaClient::handleScErrorInSuccessState() { error e = (error)atoi(pendingsc->in.c_str()); if ((e == API_ESID) || (e == API_ENOENT && loggedIntoFolder())) { app->request_error(e); scsn.stopScsn(); app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, e); } else if (e == API_ETOOMANY) { LOG_warn << "Too many pending updates - reloading local state"; app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, e); // Stop the sc channel to prevent the reception of multiple // API_ETOOMANY errors causing multiple consecutive reloads scsn.stopScsn(); app->reloading(); int creqtag = reqtag; reqtag = fetchnodestag; // associate with ongoing request, if any fetchingnodes = false; fetchnodestag = 0; // reloading mid-session so we definitely go to the servers // the node tree will be replaced when the reply arrives // actionpacketsCurrent will be reset at that time // nocache = true so that we get to an equal or later SCSN // right away. The ir:1 mechanism is not reliable for this fetchnodes(true, false, true); reqtag = creqtag; } else if (e == API_EAGAIN || e == API_ERATELIMIT) { if (!statecurrent) { fnstats.eAgainCount++; } app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_RECEIVED, e); } else if (e == API_EBLOCKED) { app->request_error(API_EBLOCKED); block(true); } else { LOG_err << "Unexpected sc response: " << pendingsc->in; app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_ERROR, e); scsn.stopScsn(); } return; } void MegaClient::handleScInFailureState() { pendingscTimedOut = false; if (pendingsc) { if (!statecurrent && pendingsc->httpstatus != 200) { if (pendingsc->httpstatus == 500) { fnstats.e500Count++; } else { fnstats.eOthersCount++; } } if (pendingsc->httpstatus == 500 && !scnotifyurl.empty()) { sendevent(99482, "500 received on wsc url"); LOG_err << "500 error on wsc URL"; } if (pendingsc->sslcheckfailed) { sendevent(99453, "Invalid public key"); sslfakeissuer = pendingsc->sslfakeissuer; app->request_error(API_ESSL); sslfakeissuer.clear(); if (!retryessl) { scsn.stopScsn(); } } if (!scsn.stopped()) { if (pendingsc->mDnsFailure) { app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType ::REQUEST_SENT, LOCAL_ENETWORK); } else { app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType ::REQUEST_RECEIVED, pendingsc->httpstatus); } } pendingsc.reset(); } if (scsn.stopped()) { app->notify_network_activity(NetworkActivityChannel::SC, NetworkActivityType::REQUEST_ERROR, API_ESSL); btsc.backoff(NEVER); } else { // failure, repeat with capped exponential backoff btsc.backoff(); LOG_debug << clientname << "sc backing off with delay ds: " << btsc.retryin(); } return; } } // namespace sdk-10.11.0/src/megautils.cpp000066400000000000000000000060501516266226600157350ustar00rootroot00000000000000/** * @file megautils.cpp * * (c) 2024 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include namespace mega { std::vector toNamesVector(const MegaNodeList& nodes) { std::vector result; result.reserve(static_cast(nodes.size())); for (int i = 0; i < nodes.size(); ++i) { result.emplace_back(nodes.get(i)->getName()); } return result; } std::vector toNamesAndFingerprintVector(const MegaNodeList& nodes) { std::vector result; result.reserve(static_cast(nodes.size())); for (int i = 0; i < nodes.size(); ++i) { std::optional fp; if (nodes.get(i)->getType() == MegaNode::TYPE_FILE) { fp = nodes.get(i)->getFingerprint(); } ChildNameAndFingerprint res{nodes.get(i)->getName(), std::move(fp)}; result.emplace_back(std::move(res)); } return result; } std::vector stringListToVector(const MegaStringList& l) { std::vector result; result.reserve(static_cast(l.size())); for (int i = 0; i < l.size(); ++i) result.emplace_back(l.get(i)); return result; } std::map stringIntegerMapToMap(const MegaStringIntegerMap& m) { std::map result; std::unique_ptr keys{m.getKeys()}; if (!keys) return {}; for (int i = 0; i < keys->size(); ++i) { const auto& key = keys->get(i); result[key] = m.get(key)->get(0); } return result; } std::vector> bucketsToVector(const MegaRecentActionBucketList& buckets) { std::vector> result; for (int i = 0; i < buckets.size(); ++i) { auto bucketNodes = buckets.get(i)->getNodes(); if (!bucketNodes) continue; std::vector bucketInfo; for (int j = 0; j < bucketNodes->size(); ++j) { bucketInfo.emplace_back(bucketNodes->get(j)->getName()); } result.emplace_back(std::move(bucketInfo)); } return result; } #ifdef ENABLE_SYNC std::vector> toSyncStallVector(const MegaSyncStallList& stallList) { std::vector> result; result.reserve(stallList.size()); for (size_t i = 0; i < stallList.size(); ++i) { result.emplace_back(std::unique_ptr(stallList.get(i)->copy())); } return result; } #endif } sdk-10.11.0/src/name_collision.cpp000066400000000000000000000120511516266226600167340ustar00rootroot00000000000000#include "mega/name_collision.h" #include "mega/utils.h" #include #include namespace mega::ncoll { std::tuple getBaseNameKindId(const std::string& input) { static const std::regex pattern(R"(^(.*?)(\ ?)\((\d+)\)$)"); if (std::smatch matches; std::regex_match(input, matches, pattern) && matches[1].matched && matches[2].matched && matches[3].matched) { const nameId_t identifier = static_cast(std::stoul(matches[3].str())); const ENameType t = matches[2].str().empty() ? ENameType::withIdNoSpace : ENameType::withIdSpace; return {matches[1].str(), t, identifier}; } return {input, ENameType::baseNameOnly, 0}; } void NewFreeIndexProvider::addOccupiedIndex(const ENameType kind, const nameId_t index) { static const auto addIndex = [](const nameId_t index, std::vector& containter) { auto it = std::lower_bound(std::cbegin(containter), std::cend(containter), index); containter.insert(it, index); }; switch (kind) { case ENameType::baseNameOnly: baseNameOccupied = true; return; case ENameType::withIdSpace: addIndex(index, occupiedIndicesSpace); return; case ENameType::withIdNoSpace: addIndex(index, occupiedIndicesNoSpace); return; } } nameId_t NewFreeIndexProvider::getNextFreeIndex(ENameType kind, nameId_t minimumAllowed) { if (kind == ENameType::baseNameOnly) { if (!baseNameOccupied) { baseNameOccupied = true; return 0; } kind = ENameType::withIdSpace; minimumAllowed = 1; } auto& container = kind == ENameType::withIdSpace ? occupiedIndicesSpace : occupiedIndicesNoSpace; auto startLookIt = std::lower_bound(std::cbegin(container), std::cend(container), minimumAllowed); if (startLookIt == std::end(container) || *startLookIt != minimumAllowed) return *container.insert(startLookIt, minimumAllowed); // Increase minimumAllowed until a free index is found. while (startLookIt != std::end(container)) { if (*startLookIt != minimumAllowed) break; ++minimumAllowed; ++startLookIt; } return *container.insert(startLookIt, minimumAllowed); } bool NewFreeIndexProvider::isFree(const ENameType kind, const nameId_t index) const { if (kind == ENameType::baseNameOnly) return !baseNameOccupied; const auto& container = kind == ENameType::withIdSpace ? occupiedIndicesSpace : occupiedIndicesNoSpace; const auto it = std::lower_bound(std::cbegin(container), std::cend(container), index); return it == std::cend(container) || *it != index; } NameCollisionSolver::NameCollisionSolver(const std::vector& existingNames) { std::for_each(std::begin(existingNames), std::end(existingNames), [this](const std::string& name) { const auto [basename, kind, id] = getBaseNameKindId(name); indexProviders[basename].addOccupiedIndex(kind, id); }); } std::string NameCollisionSolver::operator()(const std::string& nameToValidate) { const auto [basename, kind, id] = getBaseNameKindId(nameToValidate); auto& provider = indexProviders[basename]; if (provider.isFree(kind, id)) { provider.addOccupiedIndex(kind, id); return nameToValidate; } nameId_t validId = provider.getNextFreeIndex(kind, id); std::string result = basename; if (kind == ENameType::withIdSpace || kind == ENameType::baseNameOnly) result += ' '; return result + "(" + std::to_string(validId) + ")"; } FileNameCollisionSolver::FileNameCollisionSolver(const std::vector& existingNames) { std::for_each(std::begin(existingNames), std::end(existingNames), [this](const std::string& name) { const size_t dotPos = fileExtensionDotPosition(name); const auto [basename, kind, id] = getBaseNameKindId(name.substr(0, dotPos)); indexProviders[basename + name.substr(dotPos)].addOccupiedIndex(kind, id); }); } std::string FileNameCollisionSolver::operator()(const std::string& nameToValidate) { const size_t dotPos = fileExtensionDotPosition(nameToValidate); const auto [basename, kind, id] = getBaseNameKindId(nameToValidate.substr(0, dotPos)); const std::string extension = nameToValidate.substr(dotPos); auto& provider = indexProviders[basename + extension]; if (provider.isFree(kind, id)) { provider.addOccupiedIndex(kind, id); return nameToValidate; } nameId_t validId = provider.getNextFreeIndex(kind, id); std::string result = basename; if (kind == ENameType::withIdSpace || kind == ENameType::baseNameOnly) result += ' '; return result + "(" + std::to_string(validId) + ")" + extension; } } sdk-10.11.0/src/network_connectivity_test.cpp000066400000000000000000000151131516266226600212710ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/network_connectivity_test.h" #include "mega/udp_socket_tester.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #else #include #endif #include #include using namespace std; using namespace std::chrono; namespace mega { bool NetworkConnectivityTest::start(UdpSocketTester::TestSuite&& testSuite, const NetworkConnectivityTestServerInfo& serverInfo) { if (!mSocketTestersIPv4.empty() || !mSocketTestersIPv6.empty()) { return false; } mTestsPerSocket = testSuite.totalMessageCount(); mResults = {}; // Build a list of unique ports and keep DNS port separate unordered_set ports(serverInfo.ports.begin(), serverInfo.ports.end()); // Initiate test suite on all sockets static constexpr int DNS_PORT = 53; mSocketTesterIPv4Dns = std::make_shared(serverInfo.ipv4, DNS_PORT); mSocketTesterIPv4Dns->startSuite(testSuite); mSocketTesterIPv6Dns = std::make_shared(serverInfo.ipv6, DNS_PORT); mSocketTesterIPv6Dns->startSuite(testSuite); mSocketTestersIPv4.reserve(ports.size()); mSocketTestersIPv6.reserve(ports.size()); for (int p: ports) { mSocketTestersIPv4.emplace_back(std::make_shared(serverInfo.ipv4, p)) ->startSuite(testSuite); mSocketTestersIPv6.emplace_back(std::make_shared(serverInfo.ipv6, p)) ->startSuite(testSuite); } return true; } const NetworkConnectivityTestResults& NetworkConnectivityTest::getResults() { mTimeoutOfReceive = high_resolution_clock::now() + 1s; // IPv4 results mResults.ipv4 = getTestResults(mSocketTesterIPv4Dns, mSocketTestersIPv4, '4'); mSocketTestersIPv4.clear(); // IPv6 results mResults.ipv6 = getTestResults(mSocketTesterIPv6Dns, mSocketTestersIPv6, '6'); mSocketTestersIPv6.clear(); return mResults; } NetworkConnectivityTestIpResults NetworkConnectivityTest::getTestResults(shared_ptr dnsTester, vector>& testers, char summaryPrefix) { NetworkConnectivityTestIpResults testResults; vector allSocketResults; allSocketResults.reserve(testers.size() * mTestsPerSocket); map uniqueLogEntries; // DNS test if (dnsTester) { const auto& dnsResults = dnsTester->getSocketResults(mTimeoutOfReceive); // extract status for each message type for (const auto& m: dnsResults.messageResults) { updateStatus(m.errorCode, testResults.dns); } // keep unique log entries (and occurrence count) for (const auto& l: dnsResults.log) { uniqueLogEntries[l.first] += l.second; } allSocketResults.push_back(dnsResults); } for (auto& t: testers) { const auto& socketResults = t->getSocketResults(mTimeoutOfReceive); // extract status for each message type for (const auto& m: socketResults.messageResults) { updateStatus(m.errorCode, testResults.messages); } // keep unique log entries (and occurrence count) for (const auto& l: socketResults.log) { uniqueLogEntries[l.first] += l.second; } allSocketResults.push_back(socketResults); } // convert log entry map to simple messages testResults.socketErrors.reserve(uniqueLogEntries.size()); for (const auto& l: uniqueLogEntries) { string& entry = testResults.socketErrors.emplace_back(l.first); if (l.second > 1) entry += " (repeated " + std::to_string(l.second) + " times)"; } testResults.summary = getSummary(summaryPrefix, allSocketResults); return testResults; } void NetworkConnectivityTest::updateStatus(int error, NetworkConnectivityTestMessageStatus& status) { if (!error) { if (status == NetworkConnectivityTestMessageStatus::NOT_RUN) status = NetworkConnectivityTestMessageStatus::PASS; else if (status != NetworkConnectivityTestMessageStatus::PASS) status = NetworkConnectivityTestMessageStatus::FAIL; } else if (isNetworkUnreachable(error)) { if (status == NetworkConnectivityTestMessageStatus::NOT_RUN) status = NetworkConnectivityTestMessageStatus::NET_UNREACHABLE; else if (status != NetworkConnectivityTestMessageStatus::NET_UNREACHABLE) status = NetworkConnectivityTestMessageStatus::FAIL; } else { status = NetworkConnectivityTestMessageStatus::FAIL; } } bool NetworkConnectivityTest::isNetworkUnreachable(int errorCode) { #if defined(_WIN32) return errorCode == WSAENETUNREACH; #elif defined(__linux__) return errorCode == ENETUNREACH; #elif defined(__APPLE__) return errorCode == EHOSTUNREACH; #else return false; #endif } string NetworkConnectivityTest::getSummary(char ipPrefix, const vector& socketResults) { // Build a string from all results, with format // "ip_ver:port:message_type:attempts:successful [...]", ex: // "4:1234:S:30:29 [...]" map> results; for (const auto& r: socketResults) { string prefix = string{ipPrefix, ':'} + std::to_string(r.port) + ':'; for (const auto& m: r.messageResults) { auto& counters = results[prefix + static_cast(m.messageType)]; ++counters.first; // attempts if (!m.errorCode) ++counters.second; // successful } } string summary; for (const auto& r: results) { if (!summary.empty()) summary += ' '; summary += r.first + ':' + std::to_string(r.second.first) + ':' + std::to_string(r.second.second); } return summary; } } // namespace mega sdk-10.11.0/src/node.cpp000066400000000000000000003714541516266226600147050ustar00rootroot00000000000000/** * @file node.cpp * @brief Classes for accessing local and remote nodes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/node.h" #include "mega.h" #include "mega/base64.h" #include "mega/heartbeats.h" #include "mega/logging.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/scoped_helpers.h" #include "mega/serialize64.h" #include "mega/share.h" #include "mega/sync.h" #include "mega/transfer.h" #include "mega/transferslot.h" #include "megafs.h" #ifdef __ANDROID__ #include "mega/android/androidFileSystem.h" #endif namespace mega { const vector Node::attributesToCopyIntoPreviousVersions{ "fav", "lbl", "sen", MegaClient::NODE_ATTRIBUTE_DESCRIPTION, MegaClient::NODE_ATTRIBUTE_TAGS}; const std::string Node::BLANK("BLANK"); const std::string Node::CRYPTO_ERROR("CRYPTO_ERROR"); const std::string Node::NO_KEY("NO_KEY"); Node::Node(MegaClient& cclient, NodeHandle h, NodeHandle ph, nodetype_t t, m_off_t s, handle u, const char* fa, m_time_t ts) : client(&cclient) { appdata = NULL; nodehandle = h.as8byte(); parenthandle = ph.as8byte(); parent = NULL; type = t; size = s; owner = u; JSON::copystring(&fileattrstring, fa); ctime = ts; foreignkey = false; memset(&changed, 0, sizeof changed); mFingerPrintPosition = client->mNodeManager.invalidFingerprintPos(); if (type == FILENODE) { mCounter.files = 1; mCounter.storage = size; } else if (type == FOLDERNODE) { mCounter.folders = 1; } client->mNodeManager.increaseNumNodesInRam(); } Node::~Node() { if (keyApplied()) { client->mNodeManager.decreaseNumNodesAppliedKey(); assert(client->mNodeManager.getNumNodesKeyApplied() >= 0); // if the assert above fails, the reason could be that the client is // already logged-out, but the Node survived because the app kept a // shared pointer, so the object is destroyed after the logout and // the MegaClient::NodeManager::mAppliedKeyNodeCount is already reset to 0, which // results on a negative (-1) count. // If that is the case, the app should reset the shared pointer before // the logout. } else { client->mNodeManager.removeNodePendingApplyKeys(this); } // abort pending direct reads client->preadabort(this); client->mNodeManager.decreaseNumNodesInRam(); } int Node::getShareType() const { int shareType = ShareType_t::NO_SHARES; if (inshare) { shareType |= ShareType_t::IN_SHARES; } else { if (outshares) { for (share_map::iterator it = outshares->begin(); it != outshares->end(); it++) { Share *share = it->second.get(); if (share->user) // folder links are shares without user { shareType |= ShareType_t::OUT_SHARES; break; } } } if (pendingshares && pendingshares->size()) { shareType |= ShareType_t::PENDING_OUTSHARES; } if (plink) { shareType |= ShareType_t::LINK; } } return shareType; } bool Node::hasAncestorMatching(const nodeCondition_t& condition) const { const Node* ancestor = parent.get(); while (ancestor) { if (condition(*ancestor)) return true; ancestor = ancestor->parent.get(); } return false; } bool Node::hasChildWithName(const string& name) const { return client->childnodebyname(this, name.c_str()) ? true : false; } Node::Flags Node::getDBFlagsBitset() const { Flags flags; flags.set(FLAGS_IS_VERSION, parent && parent->type == FILENODE); flags.set(FLAGS_IS_IN_RUBBISH, isAncestor(client->mNodeManager.getRootNodeRubbish())); flags.set(FLAGS_IS_MARKED_SENSTIVE, isMarkedSensitive()); return flags; } uint64_t Node::getDBFlags() const { return getDBFlagsBitset().to_ulong(); } //static uint64_t Node::getDBFlags(uint64_t oldFlags, bool isInRubbish, bool isVersion, bool isSensitive) { Flags flags = oldFlags; flags.set(FLAGS_IS_VERSION, isVersion); flags.set(FLAGS_IS_IN_RUBBISH, isInRubbish); flags.set(FLAGS_IS_MARKED_SENSTIVE, isSensitive); return flags.to_ulong(); } bool Node::getExtension(std::string& ext, const string& nodeName) { size_t dotPos = nodeName.rfind('.'); if (dotPos == string::npos) { ext.clear(); return false; } ext = nodeName.substr(dotPos + 1); for (auto& c : ext) { c = static_cast(tolower(c)); } return true; } // these lists of file extensions (and the logic to use them) all come from the webclient - if updating here, please make sure the webclient is updated too, preferably webclient first. const std::set& documentExtensions() { static const std::set docs {makeNameid("abw"), makeNameid("doc"), makeNameid("docm"), makeNameid("docx"), makeNameid("dot"), makeNameid("dotm"), makeNameid("dotx"), makeNameid("odt"), makeNameid("sxc"), makeNameid("sxd"), makeNameid("sxi"), makeNameid("text"), makeNameid("tsv"), makeNameid("ttl"), makeNameid("txt"), makeNameid("org")}; return docs; } const std::set& spreadsheetExtensions() { static const std::set spds {makeNameid("csv"), makeNameid("ods"), makeNameid("xls"), makeNameid("xlsm"), makeNameid("xlsx")}; return spds; } const std::set& pdfExtensions() { static const std::set pdfs{makeNameid("pdf")}; return pdfs; } const std::set& presentationExtensions() { static const std::set pres {makeNameid("odc"), makeNameid("odp"), makeNameid("otc"), makeNameid("otp"), makeNameid("pot"), makeNameid("potx"), makeNameid("pps"), makeNameid("ppsx"), makeNameid("ppt"), makeNameid("pptx"), makeNameid("sldx")}; return pres; } const std::set& archiveExtensions() { static const std::set acvs {makeNameid("7z"), makeNameid("ace"), makeNameid("bz2"), makeNameid("gz"), makeNameid("rar"), makeNameid("tar"), makeNameid("zip")}; return acvs; } const std::set& programExtensions() { static const std::set pgms {makeNameid("apk"), makeNameid("bat"), makeNameid("com"), makeNameid("deb"), makeNameid("exe"), makeNameid("msi"), makeNameid("sh")}; return pgms; } const std::set& miscExtensions() { static const std::set misc {makeNameid("csv"), makeNameid("json"), makeNameid("log"), makeNameid("otf"), makeNameid("ttf")}; return misc; } const std::set& audioExtensions() { static const std::set auds {makeNameid("aac"), makeNameid("adp"), makeNameid("aif"), makeNameid("aifc"), makeNameid("aiff"), makeNameid("au"), makeNameid("caf"), makeNameid("dra"), makeNameid("dts"), makeNameid("dtshd"), makeNameid("eol"), makeNameid("flac"), makeNameid("kar"), makeNameid("lvp"), makeNameid("m2a"), makeNameid("m3a"), makeNameid("m3u"), makeNameid("m4a"), makeNameid("mid"), makeNameid("midi"), makeNameid("mka"), makeNameid("mp2"), makeNameid("mp2a"), makeNameid("mp3"), makeNameid("mp4a"), makeNameid("mpga"), makeNameid("oga"), makeNameid("ogg"), makeNameid("pya"), makeNameid("ra"), makeNameid("ram"), makeNameid("rip"), makeNameid("rmi"), makeNameid("rmp"), makeNameid("s3m"), makeNameid("sil"), makeNameid("snd"), makeNameid("spx"), makeNameid("uva"), makeNameid("uvva"), makeNameid("wav"), makeNameid("wax"), makeNameid("weba"), makeNameid("wma"), makeNameid("xm")}; return auds; } // Store extension than can't be stored in nameid due they have more than 8 characters const std::set& longAudioExtension() { static const std::set laud {"ecelp4800", "ecelp7470", "ecelp9600"}; return laud; } const std::set& videoExtensions() { static const std::set vids{ makeNameid("3g2"), makeNameid("3gp"), makeNameid("asf"), makeNameid("asx"), makeNameid("avi"), makeNameid("dvb"), makeNameid("f4v"), makeNameid("fli"), makeNameid("flv"), makeNameid("fvt"), makeNameid("h261"), makeNameid("h263"), makeNameid("h264"), makeNameid("jpgm"), makeNameid("jpgv"), makeNameid("jpm"), makeNameid("m1v"), makeNameid("m2v"), makeNameid("m4u"), makeNameid("m4v"), makeNameid("mj2"), makeNameid("mjp2"), makeNameid("mk3d"), makeNameid("mks"), makeNameid("mkv"), makeNameid("mng"), makeNameid("mov"), makeNameid("movie"), makeNameid("mp4"), makeNameid("mp4v"), makeNameid("mpe"), makeNameid("mpeg"), makeNameid("mpg"), makeNameid("mpg4"), makeNameid("mxu"), makeNameid("ogv"), makeNameid("pyv"), makeNameid("qt"), makeNameid("smv"), makeNameid("ts"), makeNameid("uvh"), makeNameid("uvm"), makeNameid("uvp"), makeNameid("uvs"), makeNameid("uvu"), makeNameid("uvv"), makeNameid("uvvh"), makeNameid("uvvm"), makeNameid("uvvp"), makeNameid("uvvs"), makeNameid("uvvu"), makeNameid("uvvv"), makeNameid("viv"), makeNameid("vob"), makeNameid("webm"), makeNameid("wm"), makeNameid("wmv"), makeNameid("wmx"), makeNameid("wvx")}; return vids; } const std::set& photoExtensions() { static const std::set phts {makeNameid("3ds"), makeNameid("avif"), makeNameid("bmp"), makeNameid("btif"), makeNameid("cgm"), makeNameid("cmx"), makeNameid("djv"), makeNameid("djvu"), makeNameid("dwg"), makeNameid("dxf"), makeNameid("fbs"), makeNameid("fh"), makeNameid("fh4"), makeNameid("fh5"), makeNameid("fh7"), makeNameid("fhc"), makeNameid("fpx"), makeNameid("fst"), makeNameid("g3"), makeNameid("gif"), makeNameid("heic"), makeNameid("heif"), makeNameid("ico"), makeNameid("ief"), makeNameid("jpe"), makeNameid("jpeg"), makeNameid("jpg"), makeNameid("jxl"), makeNameid("ktx"), makeNameid("mdi"), makeNameid("mmr"), makeNameid("npx"), makeNameid("pbm"), makeNameid("pct"), makeNameid("pcx"), makeNameid("pgm"), makeNameid("pic"), makeNameid("png"), makeNameid("pnm"), makeNameid("ppm"), makeNameid("psd"), makeNameid("ras"), makeNameid("rgb"), makeNameid("rlc"), makeNameid("sgi"), makeNameid("sid"), makeNameid("svg"), makeNameid("svgz"), makeNameid("tga"), makeNameid("tif"), makeNameid("tiff"), makeNameid("uvg"), makeNameid("uvi"), makeNameid("uvvg"), makeNameid("uvvi"), makeNameid("wbmp"), makeNameid("wdp"), makeNameid("webp"), makeNameid("xbm"), makeNameid("xif"), makeNameid("xpm"), makeNameid("xwd")}; return phts; } const std::set& photoRawExtensions() { static const std::set phrs {makeNameid("3fr"), makeNameid("ari"), makeNameid("arq"), makeNameid("arw"), makeNameid("bay"), makeNameid("bmq"), makeNameid("cap"), makeNameid("ciff"), makeNameid("cine"), makeNameid("cr2"), makeNameid("cr3"), makeNameid("crw"), makeNameid("cs1"), makeNameid("dc2"), makeNameid("dcr"), makeNameid("dng"), makeNameid("drf"), makeNameid("dsc"), makeNameid("eip"), makeNameid("erf"), makeNameid("fff"), makeNameid("ia"), makeNameid("iiq"), makeNameid("k25"), makeNameid("kc2"), makeNameid("kdc"), makeNameid("mdc"), makeNameid("mef"), makeNameid("mos"), makeNameid("mrw"), makeNameid("nef"), makeNameid("nrw"), makeNameid("obm"), makeNameid("orf"), makeNameid("ori"), makeNameid("pef"), makeNameid("ptx"), makeNameid("pxn"), makeNameid("qtk"), makeNameid("raf"), makeNameid("raw"), makeNameid("rdc"), makeNameid("rw2"), makeNameid("rwl"), makeNameid("rwz"), makeNameid("sr2"), makeNameid("srf"), makeNameid("srw"), makeNameid("sti"), makeNameid("x3f")}; return phrs; } const std::set& photoImageDefExtension() { static const std::set phis {makeNameid("bmp"), makeNameid("gif"), makeNameid("jpg"), makeNameid("jpeg"), makeNameid("png")}; return phis; } bool Node::isPhoto(const std::string& ext) { nameid extNameid = getExtensionNameId(ext); return photoImageDefExtension().find(extNameid) != photoImageDefExtension().end() || photoRawExtensions().find(extNameid) != photoRawExtensions().end() || photoExtensions().find(extNameid) != photoExtensions().end(); } bool Node::isPhotoWithFileAttributes(bool checkPreview) const { std::string ext; if (!Node::getExtension(ext, displayname())) { return false; } // evaluate according to the webclient rules, so that we get exactly the same bucketing. return (isPhoto(ext) && (!checkPreview || Node::hasfileattribute(&fileattrstring, GfxProc::PREVIEW))); } bool Node::isVideo(const std::string& ext) { return videoExtensions().find(getExtensionNameId(ext)) != videoExtensions().end(); } bool Node::isAudio(const std::string& ext) { nameid extNameid = getExtensionNameId(ext); if (extNameid != 0) { return audioExtensions().find(extNameid) != audioExtensions().end(); } // Check longer extension return longAudioExtension().find(ext) != longAudioExtension().end(); } bool Node::isDocument(const std::string& ext) { return documentExtensions().find(getExtensionNameId(ext)) != documentExtensions().end(); } bool Node::isSpreadsheet(const std::string& ext) { return spreadsheetExtensions().find(getExtensionNameId(ext)) != spreadsheetExtensions().end(); } bool Node::isPdf(const std::string& ext) { return pdfExtensions().find(getExtensionNameId(ext)) != pdfExtensions().end(); } bool Node::isPresentation(const std::string& ext) { return presentationExtensions().find(getExtensionNameId(ext)) != presentationExtensions().end(); } bool Node::isArchive(const std::string& ext) { return archiveExtensions().find(getExtensionNameId(ext)) != archiveExtensions().end(); } bool Node::isProgram(const std::string& ext) { return programExtensions().find(getExtensionNameId(ext)) != programExtensions().end(); } bool Node::isMiscellaneous(const std::string& ext) { return miscExtensions().find(getExtensionNameId(ext)) != miscExtensions().end(); } bool Node::isOfMimetype(MimeType_t mimetype, const string& ext) { switch (mimetype) { case MimeType_t::MIME_TYPE_PHOTO: return Node::isPhoto(ext); case MimeType_t::MIME_TYPE_AUDIO: return Node::isAudio(ext); case MimeType_t::MIME_TYPE_VIDEO: return Node::isVideo(ext); case MimeType_t::MIME_TYPE_DOCUMENT: return Node::isDocument(ext); case MimeType_t::MIME_TYPE_PDF: return Node::isPdf(ext); case MimeType_t::MIME_TYPE_PRESENTATION: return Node::isPresentation(ext); case MimeType_t::MIME_TYPE_ARCHIVE: return Node::isArchive(ext); case MimeType_t::MIME_TYPE_PROGRAM: return Node::isProgram(ext); case MimeType_t::MIME_TYPE_MISC: return Node::isMiscellaneous(ext); case MimeType_t::MIME_TYPE_SPREADSHEET: return Node::isSpreadsheet(ext); case MimeType_t::MIME_TYPE_ALL_DOCS: return Node::isDocument(ext) || Node::isPdf(ext) || Node::isPresentation(ext) || Node::isSpreadsheet(ext); case MimeType_t::MIME_TYPE_ALL_VISUAL_MEDIA: return Node::isPhoto(ext) || Node::isVideo(ext); case MimeType_t::MIME_TYPE_OTHERS: return ext.empty() || !(Node::isPhoto(ext) || Node::isAudio(ext) || Node::isVideo(ext) || Node::isDocument(ext) || Node::isPdf(ext) || Node::isPresentation(ext) || Node::isArchive(ext) || Node::isProgram(ext) || Node::isMiscellaneous(ext) || Node::isSpreadsheet(ext)); default: return false; } } MimeType_t Node::getMimetype(const std::string& ext) { if (isPhoto(ext)) return MimeType_t::MIME_TYPE_PHOTO; if (isAudio(ext)) return MimeType_t::MIME_TYPE_AUDIO; if (isVideo(ext)) return MimeType_t::MIME_TYPE_VIDEO; if (isPdf(ext)) return MimeType_t::MIME_TYPE_PDF; if (isPresentation(ext)) return MimeType_t::MIME_TYPE_PRESENTATION; if (isSpreadsheet(ext)) return MimeType_t::MIME_TYPE_SPREADSHEET; if (isDocument(ext)) return MimeType_t::MIME_TYPE_DOCUMENT; if (isArchive(ext)) return MimeType_t::MIME_TYPE_ARCHIVE; if (isProgram(ext)) return MimeType_t::MIME_TYPE_PROGRAM; if (isMiscellaneous(ext)) return MimeType_t::MIME_TYPE_MISC; return MimeType_t::MIME_TYPE_OTHERS; } nameid Node::getExtensionNameId(const std::string& ext) { if (ext.length() > 8) { return 0; } JSON json; return json.getnameid(ext.c_str()); } // update node key data from JSON void Node::setkeyfromjson(const char* k) { string tmp; JSON::copystring(&tmp, k); setKey(tmp); } // update node key (already decrypted) and attempt to decrypt attributes void Node::setkey(const byte* newkey) { if (newkey) { string tmp(reinterpret_cast(newkey), (type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH); setKey(tmp); } setattr(); } // set the node key (encrypted or decrypted) void Node::setKey(const string& key) { if (keyApplied()) client->mNodeManager.decreaseNumNodesAppliedKey(); nodekeydata = key; if (keyApplied()) client->mNodeManager.increaseNumNodesAppliedKey(); assert(client->mNodeManager.getNumNodesKeyApplied() >= 0); } std::shared_ptr Node::unserialize(MegaClient& client, const std::string* d, bool fromOldCache, std::list>& ownNewshares) { NodeData nd(d->data(), d->size(), NodeData::COMPONENT_ALL); return nd.createNode(client, fromOldCache, ownNewshares); } // serialize node - nodes with pending or RSA keys are unsupported bool Node::serialize(string* d) const { switch (type) { case FILENODE: if (!attrstring && (int)nodekeydata.size() != FILENODEKEYLENGTH) { return false; } break; case FOLDERNODE: if (!attrstring && (int)nodekeydata.size() != FOLDERNODEKEYLENGTH) { return false; } break; default: if (nodekeydata.size()) { return false; } } unsigned short ll; short numshares; m_off_t s; s = type ? -type : size; d->append((char*)&s, sizeof s); d->append((char*)&nodehandle, MegaClient::NODEHANDLE); if (parenthandle != UNDEF) { d->append((char*)&parenthandle, MegaClient::NODEHANDLE); } else { d->append("\0\0\0\0\0", MegaClient::NODEHANDLE); } d->append((char*)&owner, MegaClient::USERHANDLE); // FIXME: use Serialize64 time_t ts = 0; // we don't want to break backward compatibility by changing the size (where m_time_t differs) d->append((char*)&ts, sizeof(ts)); ts = (time_t)ctime; d->append((char*)&ts, sizeof(ts)); if (attrstring) { auto length = 0u; if (type == FOLDERNODE) { length = FOLDERNODEKEYLENGTH; } else if (type == FILENODE) { length = FILENODEKEYLENGTH; } d->append(length, '\0'); } else { d->append(nodekeydata); } if (type == FILENODE) { ll = static_cast(fileattrstring.size() + 1); d->append((char*)&ll, sizeof ll); d->append(fileattrstring.c_str(), ll); } char isExported = plink ? 1 : 0; d->append((char*)&isExported, 1); char hasLinkCreationTs = plink ? 1 : 0; d->append((char*)&hasLinkCreationTs, 1); if (isExported && plink && plink->mAuthKey.size()) { auto authKeySize = (char)plink->mAuthKey.size(); d->append((char*)&authKeySize, sizeof(authKeySize)); d->append(plink->mAuthKey.data(), static_cast(authKeySize)); } else { d->append("", 1); } d->append(1, static_cast(!!attrstring)); if (attrstring) { d->append(1, '\1'); } // Use these bytes for extensions. d->append(4, '\0'); if (inshare) { numshares = -1; } else { numshares = 0; if (outshares) { numshares += static_cast(outshares->size()); } if (pendingshares) { numshares += static_cast(pendingshares->size()); } } d->append((char*)&numshares, sizeof numshares); if (numshares) { if (sharekey) { d->append((char*)sharekey->key, SymmCipher::KEYLENGTH); } else // with ^!keys, shares may not receive the sharekey along with the share's data { d->append(SymmCipher::KEYLENGTH, '\0'); } if (inshare) { inshare->serialize(d); } else { if (outshares) { for (share_map::iterator it = outshares->begin(); it != outshares->end(); it++) { it->second->serialize(d); } } if (pendingshares) { for (share_map::iterator it = pendingshares->begin(); it != pendingshares->end(); it++) { it->second->serialize(d); } } } } attrs.serialize(d); if (isExported) { d->append((char*) &plink->ph, MegaClient::NODEHANDLE); d->append((char*) &plink->ets, sizeof(plink->ets)); d->append((char*) &plink->takendown, sizeof(plink->takendown)); if (hasLinkCreationTs) { d->append((char*) &plink->cts, sizeof(plink->cts)); } } // Write data necessary to thaw encrypted nodes. if (attrstring) { // Write node key data. uint32_t length = static_cast(nodekeydata.size()); d->append((char*)&length, sizeof(length)); d->append(nodekeydata, 0, length); // Write attribute string data. length = static_cast(attrstring->size()); d->append((char*)&length, sizeof(length)); d->append(*attrstring, 0, length); } return true; } // decrypt attrstring and check magic number prefix byte* Node::decryptattr(SymmCipher* key, const char* attrstring, size_t attrstrlen) { if (attrstrlen) { int l = int(attrstrlen * 3 / 4 + 3); auto buf = std::make_unique(static_cast(l)); l = Base64::atob(attrstring, buf.get(), l); if (!(l & (SymmCipher::BLOCKSIZE - 1))) { if (!key->cbc_decrypt(buf.get(), static_cast(l))) { return nullptr; } if (Utils::startswith(reinterpret_cast(buf.get()), "MEGA{\"")) { return buf.release(); } } } return NULL; } void Node::parseattr(byte *bufattr, AttrMap &attrs, m_off_t size, m_time_t &mtime , string &fileName, string &fingerprint, FileFingerprint &ffp) { attrs.fromjson(reinterpret_cast(bufattr) + 5); attr_map::iterator it = attrs.map.find('n'); // filename if (it == attrs.map.end()) { fileName = CRYPTO_ERROR; } else if (it->second.empty()) { fileName = BLANK; } it = attrs.map.find('c'); // checksum if (it != attrs.map.end()) { if (ffp.unserializefingerprint(&it->second)) { ffp.size = size; mtime = ffp.mtime; fingerprint = it->second; } } } // return temporary SymmCipher for this nodekey SymmCipher* Node::nodecipher() { return client->getRecycledTemporaryNodeCipher(&nodekeydata); } // decrypt attributes and build attribute hash void Node::setattr() { byte* buf; SymmCipher* cipher; if (!attrstring) { return; } cipher = nodecipher(); if (!cipher) { return; } buf = decryptattr(cipher, attrstring->c_str(), attrstring->size()); if (!buf) { return; } AttrMap oldAttrs(attrs); attrs.map.clear(); attrs.fromjson(reinterpret_cast(buf) + 5); auto it = attrs.map.find('n'); if (it != std::end(attrs.map)) LocalPath::utf8_normalize(&it->second); changed.name = attrs.hasDifferentValue('n', oldAttrs.map); changed.favourite = attrs.hasDifferentValue(AttrMap::string2nameid("fav"), oldAttrs.map); changed.sensitive = attrs.hasDifferentValue(AttrMap::string2nameid("sen"), oldAttrs.map); const auto pwdNameid = AttrMap::string2nameid(MegaClient::NODE_ATTR_PASSWORD_MANAGER); changed.pwd = attrs.hasDifferentValue(pwdNameid, oldAttrs.map); const auto descriptionNameid = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_DESCRIPTION); changed.description = attrs.hasDifferentValue(descriptionNameid, oldAttrs.map); const auto tagsNameid = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); changed.tags = attrs.hasDifferentValue(tagsNameid, oldAttrs.map); setfingerprint(); delete[] buf; attrstring.reset(); } nameid Node::sdsId() { constexpr nameid nid = makeNameid("sds"); return nid; } bool Node::isMarkedSensitive() const { return attrs.getBool("sen"); } bool Node::isSensitiveInherited() const { if (isMarkedSensitive()) return true; Node* p = parent.get(); while (p) { if (p->isMarkedSensitive()) { return true; } p = p->parent.get(); } return false; } bool Node::areFlagsValid(Node::Flags requiredFlags, Node::Flags excludeFlags, Node::Flags excludeRecursiveFlags) const { if (excludeRecursiveFlags.any() && anyExcludeRecursiveFlag(excludeRecursiveFlags)) return false; if (requiredFlags.any() || excludeFlags.any()) { Node::Flags flags = getDBFlagsBitset(); if ((flags & excludeFlags).any()) return false; if ((flags & requiredFlags) != requiredFlags) return false; } return true; } bool Node::anyExcludeRecursiveFlag(Node::Flags excludeRecursiveFlags) const { if ((getDBFlagsBitset() & excludeRecursiveFlags).any()) return true; const Node* p = parent.get(); while (p) { Node::Flags flags = p->getDBFlagsBitset(); if ((excludeRecursiveFlags & flags).any()) { return true; } p = p->parent.get(); } return false; } vector> Node::getSdsBackups() const { vector> bkps; auto it = attrs.map.find(sdsId()); if (it != attrs.map.end()) { std::istringstream is(it->second); // "b64aa:8,b64bb:8" while (!is.eof()) { string b64BkpIdStr; std::getline(is, b64BkpIdStr, ':'); if (!is.good()) { LOG_err << "Invalid format in 'sds' attr value for backup id"; break; } handle bkpId = UNDEF; Base64::atob(b64BkpIdStr.c_str(), (byte*)&bkpId, MegaClient::BACKUPHANDLE); assert(bkpId != UNDEF); string stateStr; std::getline(is, stateStr, ','); try { int state = std::stoi(stateStr); bkps.push_back(std::make_pair(bkpId, state)); } catch (...) { LOG_err << "Invalid backup state in 'sds' attr value"; break; } } } return bkps; } string Node::toSdsString(const vector>& ids) { string value; for (const auto& i : ids) { std::string idStr(Base64Str(i.first)); value += idStr + ':' + std::to_string(i.second) + ','; // `b64aa:8,b64bb:8,` } if (!value.empty()) { value.pop_back(); // remove trailing ',' } return value; } // if present, configure FileFingerprint from attributes // otherwise, the file's fingerprint is derived from the file's mtime/size/key void Node::setfingerprint() { if (type == FILENODE && nodekeydata.size() >= sizeof crc) { client->mNodeManager.removeFingerprint(this); attr_map::iterator it = attrs.map.find('c'); if (it != attrs.map.end()) { if (!unserializefingerprint(&it->second)) { LOG_warn << "Invalid fingerprint"; } } // if we lack a valid FileFingerprint for this file, use file's key, // size and client timestamp instead if (!isvalid) { memcpy(crc.data(), nodekeydata.data(), sizeof crc); mtime = ctime; } mFingerPrintPosition = client->mNodeManager.insertFingerprint(this); } } bool Node::hasName(const string& name) const { auto it = attrs.map.find('n'); return it != attrs.map.end() && it->second == name; } bool Node::hasName() const { auto i = attrs.map.find('n'); return i != attrs.map.end() && !i->second.empty(); } // return file/folder name or special status strings const char* Node::displayname(LogCondition log) const { // not yet decrypted if (attrstring && !(log & LOG_CONDITION_DISABLE_NO_KEY)) { LOG_debug << NO_KEY << " " << type << " " << size << " " << Base64Str(nodehandle); return NO_KEY.c_str(); } attr_map::const_iterator it; it = attrs.map.find('n'); if (it == attrs.map.end()) { if (type < ROOTNODE || type > RUBBISHNODE) { LOG_debug << CRYPTO_ERROR << " " << type << " " << size << " " << nodehandle; } return CRYPTO_ERROR.c_str(); } if (!it->second.size()) { LOG_debug << BLANK << " " << type << " " << size << " " << nodehandle; return BLANK.c_str(); } return it->second.c_str(); } string Node::displaypath() const { // factored from nearly identical functions in megapi_impl and megacli string path; const Node* n = this; for (; n; n = n->parent.get()) { switch (n->type) { case FOLDERNODE: path.insert(0, n->displayname()); if (n->inshare) { path.insert(0, ":"); if (n->inshare->user) { path.insert(0, n->inshare->user->email); } else { path.insert(0, "UNKNOWN"); } return path; } break; case VAULTNODE: path.insert(0, "//in"); return path; case ROOTNODE: return path.empty() ? "/" : path; case RUBBISHNODE: path.insert(0, "//bin"); return path; case TYPE_DONOTSYNC: case TYPE_NESTED_MOUNT: case TYPE_SPECIAL: case TYPE_SYMLINK: case TYPE_UNKNOWN: case FILENODE: path.insert(0, n->displayname()); } path.insert(0, "/"); } return path; } bool Node::isIncludedForMimetype(MimeType_t mimetype, bool checkPreview) const { if (type != FILENODE) { return false; } if (mimetype == MimeType_t::MIME_TYPE_PHOTO) { return isPhotoWithFileAttributes(checkPreview); } std::string extension; if (!getExtension(extension, displayname())) { return MimeType_t::MIME_TYPE_OTHERS == mimetype; } return Node::isOfMimetype(mimetype, extension); } bool isPhotoVideoAudioByName(const string& filenameExtensionLowercaseNoDot) { nameid id = filenameExtensionLowercaseNoDot.length() > 8 ? 0 : AttrMap::string2nameid(filenameExtensionLowercaseNoDot.c_str()); if (id) { if (photoImageDefExtension().find(id) != photoImageDefExtension().end()) return true; if (photoRawExtensions().find(id) != photoRawExtensions().end()) return true; if (photoExtensions().find(id) != photoExtensions().end()) return true; if (videoExtensions().find(id) != videoExtensions().end()) return true; if (audioExtensions().find(id) != audioExtensions().end()) return true; } if (longAudioExtension().find(filenameExtensionLowercaseNoDot) != longAudioExtension().end()) return true; return false; } // returns position of file attribute or 0 if not present int Node::hasfileattribute(fatype t) const { return Node::hasfileattribute(&fileattrstring, t); } int Node::hasfileattribute(const string *fileattrstring, fatype t) { char buf[24]; snprintf(buf, sizeof(buf), ":%u*", t); return static_cast(fileattrstring->find(buf) + 1); } // attempt to apply node key - sets nodekey to a raw key if successful bool Node::applykey() { if (type > FOLDERNODE) { //Root nodes contain an empty attrstring attrstring.reset(); } if (keyApplied() || !nodekeydata.size()) { return false; } int l = -1; size_t t = 0; handle h; const char* k = NULL; SymmCipher* sc = &client->key; handle me = client->loggedIntoFolder() ? client->mNodeManager.getRootNodeFiles().as8byte() : client->me; while ((t = nodekeydata.find_first_of(':', t)) != string::npos) { // compound key: locate suitable subkey (always symmetric) h = 0; l = Base64::atob(nodekeydata.c_str() + (nodekeydata.find_last_of('/', t) + 1), (byte*)&h, sizeof h); t++; if (l == MegaClient::USERHANDLE) { // this is a user handle - reject if it's not me if (h != me) { continue; } } else { // look for share key if not folder link access with folder master key if (h != me) { // this is a share node handle - check if share key is available if (client->mKeyManager.generation()) { std::string key = client->mKeyManager.getShareKey(h); if (key.size()) { sc = client->getRecycledTemporaryNodeCipher(&key); } else { continue; } } else // check at new keys repository and, if not found, at the root node of share { auto it = client->mNewKeyRepository.find(NodeHandle().set6byte(h)); if (it == client->mNewKeyRepository.end()) { shared_ptr n; if (!(n = client->nodebyhandle(h)) || !n->sharekey) { continue; } sc = n->sharekey.get(); } else { sc = client->getRecycledTemporaryNodeCipher(it->second.data()); } } // this key will be rewritten when the node leaves the outbound share foreignkey = true; } } k = nodekeydata.c_str() + t; break; } // no: found => personal key, use directly // otherwise, no suitable key available yet - bail (it might arrive soon) if (!k) { if (l < 0) { k = nodekeydata.c_str(); } else { return false; } } byte key[FILENODEKEYLENGTH]; unsigned keylength = (type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH; if (client->decryptkey(k, key, static_cast(keylength), sc, 0, nodehandle)) { std::string undecryptedKey = nodekeydata; client->mNodeManager.increaseNumNodesAppliedKey(); nodekeydata.assign((const char*)key, keylength); bool keyApplied{true}; setattr(); if (attrstring) { if (foreignkey) { // Decryption with a foreign share key failed. // Restoring the undecrypted node key because an updated // share key can be received later. client->mNodeManager.decreaseNumNodesAppliedKey(); nodekeydata = undecryptedKey; keyApplied = false; } LOG_warn << "Failed to decrypt attributes for node: " << toNodeHandle(nodehandle); } if (keyApplied) { client->mNodeManager.removeNodePendingApplyKeys(this); } } bool applied = keyApplied(); if (!applied) { LOG_warn << "Failed to apply key for node: " << Base64Str(nodehandle); // keys could be missing due to nested inshares with multiple users: user A shares a folder 1 // with user B and folder 1 has a subfolder folder 1_1. User A shares folder 1_1 with user C // and user C adds some files, which will be undecryptable for user B. // The ticket SDK-1959 aims to mitigate the problem. Uncomment next line when done: // assert(applied); // This can also happen due to a race condition between the creation / destruction of shares // the retrieval keys using "pk" and the update of the "^!keys" attribute. // If a folder is shared / unshared / shared, it's possible to reach this code with the old share // key, that could be in "mNewKeyRepository" (not in n->sharekey because Node::testShareKey is // used to prevent it). } return applied; } bool Node::testShareKey(const byte *shareKey) { if (keyApplied() || !attrstring) { return true; } std::string mark = toNodeHandle(nodehandle) + ":"; size_t p = nodekeydata.find(mark); if (p == string::npos) { return true; } byte key[FILENODEKEYLENGTH]; unsigned keylength = (type == FILENODE) ? FILENODEKEYLENGTH : FOLDERNODEKEYLENGTH; const char* k = nodekeydata.c_str() + p + mark.size(); SymmCipher *sc = client->getRecycledTemporaryNodeCipher(shareKey); if (!client->decryptkey(k, key, static_cast(keylength), sc, 0, UNDEF)) { // This should never happen (malformed key) LOG_err << "Malformed node key detected"; assert(false); return true; // The share key could be OK } sc = client->getRecycledTemporaryNodeCipher(key); byte* buf = Node::decryptattr(sc, attrstring->c_str(), attrstring->size()); if (!buf) { LOG_warn << "Outdated / incorrect share key detected for " << toNodeHandle(nodehandle); return false; } delete [] buf; return true; } NodeCounter Node::getCounter() const { return mCounter; } void Node::setCounter(const NodeCounter &counter) { mCounter = counter; } // returns whether node was moved bool Node::setparent(std::shared_ptr p, bool updateNodeCounters) { if (p == parent) { return false; } std::shared_ptr oldparent = parent; if (oldparent) { client->mNodeManager.removeChild(oldparent.get(), nodeHandle()); } parenthandle = p ? p->nodehandle : UNDEF; parent = p; if (parent) { client->mNodeManager.addChild(parent->nodeHandle(), nodeHandle(), this); } if (updateNodeCounters) { std::shared_ptr node = this->mNodePosition->second.getNodeInRam(); assert(node); client->mNodeManager.updateCounter(node, oldparent); } return true; } const Node* Node::firstancestor() const { const Node* n = this; while (n->parent != NULL) { n = n->parent.get(); } return n; } std::shared_ptr Node::latestFileVersion() const { std::shared_ptr n = this->mNodePosition->second.getNodeInRam(); if (type == FILENODE) { while (n->parent && n->parent->type == FILENODE) { n = n->parent; } } return n; } unsigned Node::depth() const { auto node = latestFileVersion(); unsigned depth = 0u; for ( ; node->parent.get(); node = node->parent) ++depth; return depth; } // returns 1 if n is under p, 0 otherwise bool Node::isbelow(const Node* p) const { const Node* n = this; for (;;) { if (!n) { return false; } if (n == p) { return true; } n = n->parent.get(); } } bool Node::isbelow(NodeHandle p) const { const Node* n = this; for (;;) { if (!n) { return false; } if (n->nodeHandle() == p) { return true; } n = n->parent.get(); } } void Node::setpubliclink(handle ph, m_time_t cts, m_time_t ets, bool takendown, const string &authKey) { if (!plink) // creation { plink.reset(new PublicLink(ph, cts, ets, takendown, authKey.empty() ? nullptr : authKey.c_str())); } else // update { plink->ph = ph; plink->cts = cts; plink->ets = ets; plink->takendown = takendown; plink->mAuthKey = authKey; } } bool Node::isCreditCardNode() const { if (!isPasswordManagerNode()) return false; const auto pwmData = attrs.getNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER); return pwmData && MegaClient::isPwmDataOfType(*pwmData, MegaClient::PwmEntryType::CREDIT_CARD); } bool Node::isPasswordNode() const { if (!isPasswordManagerNode()) return false; const auto pwmData = attrs.getNestedJsonObject(MegaClient::NODE_ATTR_PASSWORD_MANAGER); return pwmData && MegaClient::isPwmDataOfType(*pwmData, MegaClient::PwmEntryType::PASSWORD); } bool Node::isPasswordManagerNode() const { return ((type == FOLDERNODE) && (attrs.map.contains(AttrMap::string2nameid(MegaClient::NODE_ATTR_PASSWORD_MANAGER)))); } bool Node::isPasswordManagerNodeFolder() const { assert(client); const auto nhBase = client->getPasswordManagerBase(); return ((type == FOLDERNODE) && (nodeHandle() == nhBase || isAncestor(nhBase))) && !isPasswordManagerNode(); } bool NodeData::readComponents() { mReadAttempted = true; const char* ptr = mStart; if (!ptr || ptr + sizeof(m_off_t) + MegaClient::NODEHANDLE > mEnd) { return false; } /// node type mSize = MemAccess::get(ptr); ptr += sizeof(m_off_t); mType = (mSize < 0 && mSize >= -RUBBISHNODE) ? (nodetype_t)-mSize : FILENODE; int nodeKeyLen = (mType == FILENODE) ? FILENODEKEYLENGTH : ((mType == FOLDERNODE) ? FOLDERNODEKEYLENGTH : 0); /// node handle memcpy((char*)&mHandle, ptr, MegaClient::NODEHANDLE); ptr += MegaClient::NODEHANDLE; // size of next data chunk constexpr int lenBeforeNodeKey = MegaClient::NODEHANDLE + MegaClient::USERHANDLE + 2 * sizeof(time_t); if (mComp == COMPONENT_ALL) { if (ptr + lenBeforeNodeKey + nodeKeyLen > mEnd) { return false; } /// parent handle memcpy((char*)&mParentHandle, ptr, MegaClient::NODEHANDLE); if (!mParentHandle) { mParentHandle = UNDEF; } ptr += MegaClient::NODEHANDLE; /// user handle memcpy((char*)&mUserHandle, ptr, MegaClient::USERHANDLE); ptr += MegaClient::USERHANDLE; /// ctime ptr += sizeof(time_t); // FIME: use m_time_t / Serialize64 instead mCtime = (uint32_t)MemAccess::get(ptr); ptr += sizeof(time_t); /// node key if ((mType == FILENODE) || (mType == FOLDERNODE)) { if (ptr + nodeKeyLen > mEnd) { return false; } mNodeKey.assign(ptr, static_cast(nodeKeyLen)); ptr += nodeKeyLen; } } else { ptr += lenBeforeNodeKey + nodeKeyLen; } /// file attributes if (mType == FILENODE) { if (ptr + sizeof(unsigned short) > mEnd) { return false; } unsigned short faLen = MemAccess::get(ptr); ptr += sizeof(unsigned short); if (mComp == COMPONENT_ALL) { if (ptr + faLen > mEnd) { return false; } mFileAttributes.assign(ptr, faLen); } ptr += faLen; } if (ptr + 3 > mEnd) { return false; } /// is exported mIsExported = ptr[0]; /// has link creation Ts char hasLinkCreationTs = ptr[1]; if (mComp == COMPONENT_ALL) { /// auth key size char authKeySize = ptr[2]; ptr += 3; if (authKeySize) { /// auth key if (ptr + authKeySize > mEnd) { return false; } mAuthKey.assign(ptr, static_cast(authKeySize)); ptr += authKeySize; } } else { ptr += 3 + ptr[2]; } /// is encrypted if (ptr + 2 > mEnd) { return false; } mIsEncrypted = ptr[0] && ptr[1]; ptr += (unsigned)*ptr + 1; /// skip some bytes for (int i = 4; i--;) { if (ptr + 1 > mEnd) { return false; } ptr += (unsigned char)*ptr + 1; } /// share count if (ptr + sizeof(short) > mEnd) { return false; } short shareCount = MemAccess::get(ptr); ptr += sizeof(short); if (shareCount) { if (mComp == COMPONENT_ALL) { /// share key if (ptr + SymmCipher::KEYLENGTH > mEnd) { return false; } mShareKey.reset(new byte[SymmCipher::KEYLENGTH]); std::copy(ptr, ptr + SymmCipher::KEYLENGTH, mShareKey.get()); } ptr += SymmCipher::KEYLENGTH; mShareDirection = (shareCount > 0) ? -1 : 0; // inshare, outshares, or pending shares for (; shareCount; --shareCount) // inshares: -1, outshare/s: positive value { // share size, see Share::unserialize() size_t shareSize = sizeof(handle) + sizeof(m_time_t) + 1; if (ptr + shareSize + 1 > mEnd) { return false; } char version_flag = ptr[shareSize]; shareSize += 1 + (version_flag >= 1 ? sizeof(handle) : 0); if (mComp == COMPONENT_ALL) { if (ptr + shareSize > mEnd) { return false; } mShares.emplace_back(shareSize); std::copy(ptr, ptr + shareSize, mShares.back().data()); } ptr += shareSize; if (!mShareDirection) // inshare { break; } } } /// node attributes ptr = mAttrs.unserialize(ptr, mEnd); if (!ptr) { LOG_err << "Failed to unserialize attrs"; assert(ptr); return false; } /// public link if (mIsExported) { if (mComp == COMPONENT_ALL) { if (ptr + MegaClient::NODEHANDLE + sizeof(m_time_t) + sizeof(bool) > mEnd) { return false; } memcpy((char*)&mPubLinkHandle, ptr, MegaClient::NODEHANDLE); ptr += MegaClient::NODEHANDLE; mPubLinkEts = MemAccess::get(ptr); ptr += sizeof(mPubLinkEts); mPubLinkTakenDown = MemAccess::get(ptr); ptr += sizeof(mPubLinkTakenDown); if (hasLinkCreationTs) { mPubLinkCts = MemAccess::get(ptr); ptr += sizeof(m_time_t); } } else { ptr += MegaClient::NODEHANDLE + sizeof(m_time_t) + sizeof(bool) + (hasLinkCreationTs ? sizeof(m_time_t) : 0); } } if (mIsEncrypted) { // Have we encoded the node key data's length? if (ptr + sizeof(uint32_t) > mEnd) { return false; } uint32_t length = MemAccess::get(ptr); ptr += sizeof(uint32_t); if (mComp == COMPONENT_ALL) { // Have we encoded the node key data? if (ptr + length > mEnd) { return false; } mNodeKey.assign(ptr, length); } ptr += length; // Have we encoded the length of the attribute string? if (ptr + sizeof(uint32_t) > mEnd) { return false; } length = MemAccess::get(ptr); ptr += sizeof(uint32_t); if (mComp == COMPONENT_ALL) { // Have we encoded the attribute string? if (ptr + length > mEnd) { return false; } mAttrString.assign(ptr, length); } ptr += length; } mReadSucceeded = ptr == mEnd; return mReadSucceeded; } m_time_t NodeData::getMtime() { if (readFailed() || mType != FILENODE) { return 0; } auto attrIt = mAttrs.map.find('c'); if (attrIt != mAttrs.map.end()) { FileFingerprint fp; if (fp.unserializefingerprint(&attrIt->second) && fp.isvalid) { return fp.mtime; } } return 0; } int NodeData::getLabel() { if (readFailed()) { return LBL_UNKNOWN; } static const nameid labelId = AttrMap::string2nameid("lbl"); auto attrIt = mAttrs.map.find(labelId); return attrIt == mAttrs.map.end() ? LBL_UNKNOWN : std::atoi(attrIt->second.c_str()); } std::string NodeData::getDescription() { if (readFailed()) { return std::string(); } static const nameid descriptionId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_DESCRIPTION); auto attrIt = mAttrs.map.find(descriptionId); return attrIt == mAttrs.map.end() ? std::string() : attrIt->second.c_str(); } std::string NodeData::getTags() { if (readFailed()) { return std::string(); } static const nameid tagId = AttrMap::string2nameid(MegaClient::NODE_ATTRIBUTE_TAGS); auto attrIt = mAttrs.map.find(tagId); return attrIt == mAttrs.map.end() ? std::string() : attrIt->second.c_str(); } handle NodeData::getHandle() { if (readFailed()) { return UNDEF; } return mHandle; } std::shared_ptr NodeData::createNode(MegaClient& client, bool fromOldCache, std::list>& ownNewshares) { assert(mComp == COMPONENT_ALL); if (readFailed()) { return nullptr; } std::shared_ptr n = std::make_shared(client, NodeHandle().set6byte(mHandle), NodeHandle().set6byte(mParentHandle), mType, mSize, mUserHandle, mFileAttributes.c_str(), mCtime); // read inshare, outshares, or pending shares for (const auto& s : mShares) { const char* ptr = s.data(); NewShare* newShare = Share::unserialize(mShareDirection, mHandle, mShareKey.get(), &ptr, ptr + s.size()); if (!newShare) { LOG_err << "Failed to unserialize Share"; break; } if (fromOldCache) { // mergenewshare should be called when users and pcr are loaded // It's used only when we are migrating the cache // mergenewshares is called when all nodes, user and pcr are loaded (fetchsc) client.newshares.push_back(newShare); } else { ownNewshares.emplace_back(newShare); } } n->attrs = mAttrs; if (fromOldCache) { // It's needed to re-normalize node names because // the updated version of utf8proc doesn't provide // exactly the same output as the previous one that // we were using attr_map::iterator it = n->attrs.map.find('n'); if (it != n->attrs.map.end()) { LocalPath::utf8_normalize(&(it->second)); } } // else from new cache, names has been normalized before to store in DB if (mIsExported) { n->plink.reset(new PublicLink(mPubLinkHandle, mPubLinkCts, mPubLinkEts, mPubLinkTakenDown, mAuthKey.c_str())); } if (mIsEncrypted) { n->attrstring.reset(new string(mAttrString)); } n->setKey(mNodeKey); // it can be decrypted or encrypted if (!n->keyApplied()) { client.mNodeManager.addNodePendingApplykey(n); } if (!mIsEncrypted) { // only if the node is not encrypted, we can generate a valid // fingerprint, based on the node's attribute 'c' n->setfingerprint(); } return n; } bool NewNode::hasZeroKey() const { return Node::hasZeroKey(nodekey); } PublicLink::PublicLink(handle ph, m_time_t cts, m_time_t ets, bool takendown, const char *authKey) { this->ph = ph; this->cts = cts; this->ets = ets; this->takendown = takendown; if (authKey) { this->mAuthKey = authKey; } } bool PublicLink::isExpired() { if (!ets) // permanent link: ets=0 return false; m_time_t t = m_time(); return ets < t; } #ifdef ENABLE_SYNC // set, change or remove LocalNode's parent and localname/slocalname. // newlocalpath must be a leaf name and must not point to an empty string (unless newparent == NULL). // no shortname allowed as the last path component. void LocalNode::setnameparent(LocalNode* newparent, const LocalPath& newlocalpath, std::unique_ptr newshortname) { Sync* oldsync = NULL; if (newshortname && *newshortname == newlocalpath) { // if the short name is the same, don't bother storing it. newshortname.reset(); } bool parentChange = newparent != parent; bool localnameChange = newlocalpath != localname; bool shortnameChange = (newshortname && !slocalname) || (slocalname && !newshortname) || (newshortname && slocalname && *newshortname != *slocalname); if (parent) { if (parentChange || localnameChange) { // remove existing child linkage for localname auto it = parent->children.find(localname); if (it != parent->children.end() && it->second == this) { parent->children.erase(it); } } if (slocalname && ( parentChange || shortnameChange)) { // remove existing child linkage for slocalname auto it = parent->schildren.find(*slocalname); if (it != parent->schildren.end() && it->second == this) { parent->schildren.erase(it); } } } // reset treestate for old subtree (before we update the names for this node, in case we generate paths while recursing) // in case of just not syncing that subtree anymore - updates icon overlays if (parent && !newparent && !sync->mDestructorRunning) { // since we can't do it after the parent is updated // send out notifications with the current (soon to be old) paths, saying these are not consdiered by the sync anymore recursiveSetAndReportTreestate(TREESTATE_NONE, true, true); } if (localnameChange) { // set new name localname = newlocalpath; toName_of_localname = localname.toName(*sync->syncs.fsaccess); } if (shortnameChange) { // set new shortname slocalname = std::move(newshortname); } if (parentChange) { parent = newparent; if (parent && sync != parent->sync) { oldsync = sync; LOG_debug << "Moving files between different syncs"; } } // add to parent map by localname if (parent && (parentChange || localnameChange)) { #ifndef NDEBUG auto it = parent->children.find(localname); assert(it == parent->children.end()); // check we are not about to orphan the old one at this location... if we do then how did we get a clash in the first place? #endif parent->children[localname] = this; } // add to parent map by shortname if (parent && slocalname && (parentChange || shortnameChange)) { // it's quite possible that the new folder still has an older LocalNode with clashing shortname, that represents a file/folder since moved, but which we don't know about yet. // just assign the new one, we forget the old reference. The other LocalNode will not remove this one since the LocalNode* will not match. parent->schildren[*slocalname] = this; } // reset treestate if (parent && parentChange && !sync->mDestructorRunning) { // As we recurse through the update tree, we will see // that it's different from this, and send out the true state recursiveSetAndReportTreestate(TREESTATE_NONE, true, false); } if (oldsync) { DBTableTransactionCommitter committer(oldsync->statecachetable); // prepare localnodes for a sync change or/and a copy operation LocalTreeProcMove tp(parent->sync); sync->syncs.proclocaltree(this, &tp); // add to new parent map by localname// update local cache if there is a sync change oldsync->cachenodes(); sync->cachenodes(); } if (parent && parentChange) { LocalTreeProcUpdateTransfers tput; sync->syncs.proclocaltree(this, &tput); } } void LocalNode::moveContentTo(LocalNode* ln, LocalPath& fullPath, bool setScanAgain) { moveContentTo(ln, fullPath, setScanAgain, true); } void LocalNode::moveContentTo(LocalNode* ln, LocalPath& fullPath, bool setScanAgain, bool moveTransfer) { vector workingList; workingList.reserve(children.size()); for (auto& c : children) workingList.push_back(c.second); for (auto& c : workingList) { LocalPath newpath{fullPath}; newpath.appendWithSeparator(c->localname, true); c->setnameparent(ln, newpath.leafName(), sync->syncs.fsaccess->fsShortname(newpath)); // if moving between syncs, removal from old sync db is already done ln->sync->statecacheadd(c); if (setScanAgain) { c->setScanAgain(false, true, true, 0); } } if (moveTransfer) { if (transferSP) { if (const auto isUpload = dynamic_cast(transferSP.get()) != nullptr; isUpload) { LOG_debug << "Moving upload (" << transferSP->getLocalname() << ") source from: '" << getLocalPath().toPath(false) << "' to '" << ln->getLocalPath().toPath(false) << "'"; } else { LOG_debug << "Moving download (" << transferSP->getLocalname() << ") source from: '" << getCloudPath(true) << "' to '" << ln->getCloudPath(true) << "'"; } } ln->resetTransfer(std::move(transferSP)); LocalTreeProcUpdateTransfers tput; tput.proc(*sync->syncs.fsaccess, ln); } ln->mWaitingForIgnoreFileLoad = mWaitingForIgnoreFileLoad; // Make sure our exclusion state is recomputed. ln->setRecomputeExclusionState(true, false); } // delay uploads by 1.1 s to prevent server flooding while a file is still being written void LocalNode::bumpnagleds() { nagleds = Waiter::ds + 11; } LocalNode::LocalNode(Sync* csync) : sync(csync) , scanAgain(TREE_RESOLVED) , checkMovesAgain(TREE_RESOLVED) , syncAgain(TREE_RESOLVED) , conflicts(TREE_RESOLVED) , unstableFsidAssigned(false) , deletedFS(false) , moveApplyingToLocal(false) , moveAppliedToLocal(false) , scanInProgress(false) , scanObsolete(false) , parentSetScanAgain(false) , parentSetCheckMovesAgain(false) , parentSetSyncAgain(false) , parentSetContainsConflicts(false) , fsidSyncedReused(false) , fsidScannedReused(false) , confirmDeleteCount(0) , certainlyOrphaned(0) , neverScanned(0) , localFSCannotStoreThisName(0) , mIsIgnoreFile(false) { fsid_lastSynced_it = sync->syncs.localnodeBySyncedFsid.end(); fsid_asScanned_it = sync->syncs.localnodeByScannedFsid.end(); syncedCloudNodeHandle_it = sync->syncs.localnodeByNodeHandle.end(); sync->syncs.totalLocalNodes++; } // initialize fresh LocalNode object - must be called exactly once void LocalNode::init(nodetype_t ctype, LocalNode* cparent, const LocalPath& cfullpath, std::unique_ptr shortname) { parent = NULL; unstableFsidAssigned = false; deletedFS = false; moveAppliedToLocal = false; moveApplyingToLocal = false; oneTimeUseSyncedFingerprintInScan = false; recomputeFingerprint = false; scanAgain = TREE_RESOLVED; checkMovesAgain = TREE_RESOLVED; syncAgain = TREE_RESOLVED; conflicts = TREE_RESOLVED; parentSetCheckMovesAgain = false; parentSetSyncAgain = false; parentSetScanAgain = false; parentSetContainsConflicts = false; fsidSyncedReused = false; fsidScannedReused = false; confirmDeleteCount = 0; certainlyOrphaned = 0; neverScanned = 0; scanInProgress = false; scanObsolete = false; slocalname = NULL; type = ctype; if (type != FILENODE) { neverScanned = 1; ++sync->threadSafeState->neverScannedFolderCount; } mReportedSyncState = TREESTATE_NONE; bumpnagleds(); mWaitingForIgnoreFileLoad = false; if (cparent) { setnameparent(cparent, cfullpath.leafName(), std::move(shortname)); mIsIgnoreFile = type == FILENODE && localname == IGNORE_FILE_NAME; mExclusionState = parent->exclusionState(localname, type, -1); } else { localname = cfullpath; toName_of_localname = localname.toName(*sync->syncs.fsaccess); slocalname.reset(shortname && *shortname != localname ? shortname.release() : nullptr); mExclusionState = ES_INCLUDED; } sync->threadSafeState->incrementSyncNodeCount(type, 1); } LocalNode::RareFields::ScanBlocked::ScanBlocked(PrnGen &rng, const LocalPath& lp, LocalNode* ln, Sync* s) : scanBlockedTimer(rng) , scanBlockedLocalPath(lp) , localNode(ln) , sync(s) { scanBlockedTimer.backoff(Sync::SCANNING_DELAY_DS); } auto LocalNode::rare() -> RareFields& { // Rare fields are those that are hardly ever used, and we don't want every LocalNode using more RAM for them all the time. // Those rare fields are put in this RareFields struct instead, and LocalNode holds an optional unique_ptr to them // Only a tiny subset of the LocalNodes should have populated RareFields at any one time. // If any of the rare fields are in use, the struct is present. trimRareFields() removes the struct when none are in use. // This function should be used when one of those field is needed, as it creates the struct if it doesn't exist yet // and then returns it. if (!rareFields) { rareFields.reset(new RareFields); } return *rareFields; } auto LocalNode::rareRO() const -> const RareFields& { // RO = read only // Use this function when you're not sure if rare fields have been populated, but need to check if (!rareFields) { static RareFields blankFields; return blankFields; } return *rareFields; } void LocalNode::trimRareFields() { if (rareFields) { if (!scanInProgress) rareFields->scanRequest.reset(); if (!rareFields->scanBlocked && !rareFields->scanRequest && rareFields->movePendingFrom.expired() && !rareFields->movePendingTo && !rareFields->moveFromHere && !rareFields->moveToHere && !rareFields->filterChain && !rareFields->badlyFormedIgnoreFilePath && !rareFields->createFolderHere && !rareFields->removeNodeHere && rareFields->unlinkHere.expired() && rareFields->localFSRenamedToThisName.empty() && !rareFields->macComputation) { rareFields.reset(); } } } void LocalNode::resetMacComputationIfAny() { if (rareFields && rareFields->macComputation) { rareFields->macComputation.reset(); trimRareFields(); } } unique_ptr LocalNode::cloneShortname() const { return unique_ptr( slocalname ? new LocalPath(*slocalname) : nullptr); } void LocalNode::setScanAgain(bool doParent, bool doHere, bool doBelow, dstime delayds) { if (doHere && scanInProgress) { scanObsolete = true; } auto state = TreeState((doHere?1u:0u) << 1 | (doBelow?1u:0u)); if (state >= TREE_ACTION_HERE && delayds > 0) scanDelayUntil = std::max(scanDelayUntil, Waiter::ds + delayds); scanAgain = std::max(scanAgain, state); for (auto p = parent; p != NULL; p = p->parent) { p->scanAgain = std::max(p->scanAgain, TREE_DESCENDANT_FLAGGED); } // for scanning, we only need to set the parent once if (parent && doParent) { parent->scanAgain = std::max(parent->scanAgain, TREE_ACTION_HERE); doParent = false; parentSetScanAgain = false; } parentSetScanAgain = parentSetScanAgain || doParent; } void LocalNode::setCheckMovesAgain(bool doParent, bool doHere, bool doBelow) { auto state = TreeState((doHere?1u:0u) << 1 | (doBelow?1u:0u)); checkMovesAgain = std::max(checkMovesAgain, state); for (auto p = parent; p != NULL; p = p->parent) { p->checkMovesAgain = std::max(p->checkMovesAgain, TREE_DESCENDANT_FLAGGED); } parentSetCheckMovesAgain = parentSetCheckMovesAgain || doParent; } void LocalNode::setSyncAgain(bool doParent, bool doHere, bool doBelow) { auto state = TreeState((doHere?1u:0u) << 1 | (doBelow?1u:0u)); parentSetSyncAgain = parentSetSyncAgain || doParent; syncAgain = std::max(syncAgain, state); for (auto p = parent; p != NULL; p = p->parent) { if (doParent) { p->syncAgain = std::max(p->syncAgain, TREE_ACTION_HERE); doParent = false; } p->syncAgain = std::max(p->syncAgain, TREE_DESCENDANT_FLAGGED); } } void LocalNode::setContainsConflicts(bool doParent, bool doHere, bool doBelow) { // using the 3 flags for consistency & understandabilty but doBelow is not relevant assert(!doBelow); auto state = TreeState((doHere?1u:0u) << 1 | (doBelow?1u:0u)); conflicts = std::max(conflicts, state); for (auto p = parent; p != NULL; p = p->parent) { p->conflicts = std::max(p->conflicts, TREE_DESCENDANT_FLAGGED); } parentSetContainsConflicts = parentSetContainsConflicts || doParent; } void LocalNode::initiateScanBlocked(bool folderBlocked, bool containsFingerprintBlocked) { // Setting node as scan-blocked. The main loop will check it regularly by weak_ptr if (!rare().scanBlocked) { rare().scanBlocked.reset(new RareFields::ScanBlocked(sync->syncs.rng, getLocalPath(), this, sync)); sync->syncs.scanBlockedPaths.push_back(rare().scanBlocked); } if (folderBlocked && !rare().scanBlocked->folderUnreadable) { rare().scanBlocked->folderUnreadable = true; LOG_verbose << sync->syncname << "Directory scan has become inaccessible for path: " << getLocalPath(); // Mark all immediate children as requiring refingerprinting. for (auto& childIt : children) { if (childIt.second->type == FILENODE) childIt.second->recomputeFingerprint = true; } } if (containsFingerprintBlocked && !rare().scanBlocked->filesUnreadable) { LOG_verbose << sync->syncname << "Directory scan contains fingerprint-blocked files: " << getLocalPath(); rare().scanBlocked->filesUnreadable = true; } } bool LocalNode::checkForScanBlocked(FSNode* fsNode) { if (rareRO().scanBlocked && rare().scanBlocked->folderUnreadable) { const auto cleanRareFieldsIfFilesUnreachable = [this]() -> bool { if (rare().scanBlocked->filesUnreadable) return false; rare().scanBlocked.reset(); trimRareFields(); return true; }; // The blocked path does not exist anymore? if (!fsNode) { LOG_verbose << sync->syncname << "Recovered from being scan blocked after deleting fsNode"; if (cleanRareFieldsIfFilesUnreachable()) return false; } // Have we recovered? if (fsNode && fsNode->type != TYPE_UNKNOWN && !fsNode->isBlocked) { LOG_verbose << sync->syncname << "Recovered from being scan blocked: " << getLocalPath(); type = fsNode->type; // original scan may not have been able to discern type, fix it now realScannedFingerprint = FileFingerprint(); setScannedFsid(UNDEF, sync->syncs.localnodeByScannedFsid, fsNode->localname, FileFingerprint()); sync->statecacheadd(this); if (cleanRareFieldsIfFilesUnreachable()) return false; } LOG_verbose << sync->syncname << "Waiting on scan blocked timer, retry in ds: " << rare().scanBlocked->scanBlockedTimer.retryin() << " for " << getLocalPath(); // make sure path stays accurate in case this node moves rare().scanBlocked->scanBlockedLocalPath = getLocalPath(); return true; } if (fsNode && (fsNode->type == TYPE_UNKNOWN || fsNode->isBlocked)) { // We were not able to get details of the filesystem item when scanning the directory. // Consider it a blocked file, and we'll rescan the folder from time to time. LOG_verbose << sync->syncname << "File/folder was blocked when reading directory, retry later: " << getLocalPath() << " [fsNode->type = " << fsNode->type << ", fsNode->isBlocked = " << fsNode->isBlocked << "]"; // Setting node as scan-blocked. The main loop will check it regularly by weak_ptr initiateScanBlocked(true, false); return true; } return false; } bool LocalNode::scanRequired() const { return scanAgain != TREE_RESOLVED; } void LocalNode::clearRegeneratableFolderScan(SyncPath& fullPath, vector& childRows) { if (lastFolderScan && lastFolderScan->size() == children.size()) { // check for scan-blocked entries, those are not regeneratable for (auto& c : *lastFolderScan) { if (c.type == TYPE_UNKNOWN) return; if (c.isBlocked) return; } // check that generating the fsNodes results in the same set unsigned nChecked = 0; for (auto& row : childRows) { if (!!row.syncNode != !!row.fsNode) return; if (row.syncNode && row.fsNode) { if (row.syncNode->type == FILENODE && !scannedFingerprint.isvalid) { return; } ++nChecked; auto generated = row.syncNode->getScannedFSDetails(); //[SDK-5551_TODO] -> Do we need to add metamac comparison at equivalent to? if (!generated.equivalentTo(*row.fsNode)) return; } } if (nChecked == children.size()) { // LocalNodes are now consistent with the last scan. LOG_debug << sync->syncname << "Clearing regeneratable folder scan records (" << lastFolderScan->size() << ") at " << fullPath.localPath; lastFolderScan.reset(); } } } bool LocalNode::mightHaveMoves() const { return checkMovesAgain != TREE_RESOLVED; } bool LocalNode::syncRequired() const { return syncAgain != TREE_RESOLVED; } void LocalNode::propagateAnySubtreeFlags() { for (auto& child : children) { if (child.second->type != FILENODE) { if (scanAgain == TREE_ACTION_SUBTREE) { child.second->scanDelayUntil = std::max(child.second->scanDelayUntil, scanDelayUntil); } child.second->scanAgain = propagateSubtreeFlag(scanAgain, child.second->scanAgain); child.second->checkMovesAgain = propagateSubtreeFlag(checkMovesAgain, child.second->checkMovesAgain); child.second->syncAgain = propagateSubtreeFlag(syncAgain, child.second->syncAgain); } } if (scanAgain == TREE_ACTION_SUBTREE) scanAgain = TREE_ACTION_HERE; if (checkMovesAgain == TREE_ACTION_SUBTREE) checkMovesAgain = TREE_ACTION_HERE; if (syncAgain == TREE_ACTION_SUBTREE) syncAgain = TREE_ACTION_HERE; } bool isDoNotSyncFileName(const string& name) { return name == "desktop.ini" // on windows, automatically updated by Explorer based on folder content || name == ".DS_Store" // on mac, contains some info about contents of that folder || name == ".Spotlight-V100" // created on external USB on mac - can't be listed, and is based on disk contents- we also don't want to be continuously uploading things from it on every other change || name == "Icon\x0d"; // on mac, icon for the folder (maybe automatic or if the user chooses an icon?). Awkward for the control character. Possibly only produced by older OS? } bool LocalNode::processBackgroundFolderScan(SyncRow& row, SyncPath& fullPath) { bool syncHere = false; assert(row.syncNode == this); assert(row.fsNode); assert(!sync->localdebris.isContainingPathOf(fullPath.localPath)); std::shared_ptr ourScanRequest = scanInProgress ? rare().scanRequest : nullptr; std::shared_ptr* availableScanSlot = nullptr; if (!sync->mActiveScanRequestGeneral || sync->mActiveScanRequestGeneral->completed()) { availableScanSlot = &sync->mActiveScanRequestGeneral; } else if (neverScanned && (!sync->mActiveScanRequestUnscanned || sync->mActiveScanRequestUnscanned->completed())) { availableScanSlot = &sync->mActiveScanRequestUnscanned; } if (!ourScanRequest && availableScanSlot) { // we can start a single new request if we are still recursing and the last request from this sync completed already if (scanDelayUntil != 0 && Waiter::ds < scanDelayUntil) { LOG_verbose << sync->syncname << "Too soon to scan this folder, needs more ds: " << scanDelayUntil - Waiter::ds; } else { // queueScan() already logs: LOG_verbose << "Requesting scan for: " << fullPath.toPath(*client->fsaccess); scanObsolete = false; scanInProgress = true; // If enough details of the scan are the same, we can reuse fingerprints instead of recalculating map priorScanChildren; if (lastFolderScan) { // use the same fingerprint shortcut data as the last time we scanned, // if we still have it (including fingerprint isvalid flag) for (auto& f : *lastFolderScan) { if (f.type == FILENODE && f.fingerprint.isvalid) { priorScanChildren.emplace(f.localname, f.clone()); } } } for (auto& childIt : children) { auto& child = *childIt.second; bool useSyncedFP = child.oneTimeUseSyncedFingerprintInScan; child.oneTimeUseSyncedFingerprintInScan = false; bool forceRecompute = child.recomputeFingerprint; child.recomputeFingerprint = false; // Can't fingerprint directories. if (child.type != FILENODE || forceRecompute) { priorScanChildren.erase(child.localname); continue; } if (priorScanChildren.find(child.localname) != priorScanChildren.end()) { // already using not yet discarded last-scan data continue; } if (child.scannedFingerprint.isvalid) { // as-scanned by this instance is more accurate if available priorScanChildren.emplace(childIt.first, child.getScannedFSDetails()); } else if (useSyncedFP && child.fsid_lastSynced != UNDEF && child.syncedFingerprint.isvalid) { // But otherwise, already-synced syncs on startup should not re-fingerprint // files that match the synced fingerprint by fsid/size/mtime (for quick startup) priorScanChildren.emplace(childIt.first, child.getLastSyncedFSDetails()); } } ourScanRequest = sync->syncs.mScanService->queueScan(fullPath.localPath, row.fsNode->fsid, false, std::move(priorScanChildren), sync->syncs.waiter); rare().scanRequest = ourScanRequest; *availableScanSlot = ourScanRequest; LOG_verbose << sync->syncname << "Issuing Directory scan request for : " << fullPath.localPath << (availableScanSlot == &sync->mActiveScanRequestUnscanned ? " (in unscanned slot)" : ""); } } else if (ourScanRequest && ourScanRequest->completed()) { if (ourScanRequest == sync->mActiveScanRequestGeneral) sync->mActiveScanRequestGeneral.reset(); if (ourScanRequest == sync->mActiveScanRequestUnscanned) sync->mActiveScanRequestUnscanned.reset(); scanInProgress = false; if (SCAN_FSID_MISMATCH == ourScanRequest->completionResult()) { LOG_verbose << sync->syncname << "Directory scan detected outdated fsid : " << fullPath.localPath; scanObsolete = true; setScanAgain(true, false, false, 0); } if (SCAN_SUCCESS == ourScanRequest->completionResult() && ourScanRequest->fsidScanned() != row.fsNode->fsid) { LOG_verbose << sync->syncname << "Directory scan returned was for now outdated fsid : " << fullPath.localPath; scanObsolete = true; setScanAgain(true, false, false, 0); } if (scanObsolete) { LOG_verbose << sync->syncname << "Directory scan outdated for : " << fullPath.localPath; scanObsolete = false; // Scan results are out of date but may still be useful. lastFolderScan.reset(new vector(ourScanRequest->resultNodes())); // Mark this directory as requiring another scan. setScanAgain(false, true, false, 10); } else if (SCAN_SUCCESS == ourScanRequest->completionResult()) { lastFolderScan.reset(new vector(ourScanRequest->resultNodes())); for (auto& i : *lastFolderScan) { if (isDoNotSyncFileName(i.localname.toPath(true))) { // These are special shell-generated files for win & mac, only relevant in the filesystem they were created i.type = TYPE_DONOTSYNC; LOG_debug << "do-not-sync path identified by name: " << i.localname; } } LOG_verbose << sync->syncname << "Received " << lastFolderScan->size() << " directory scan results for: " << fullPath.localPath; if (neverScanned) { neverScanned = 0; --sync->threadSafeState->neverScannedFolderCount; LOG_debug << "neverScannedFolderCount decremented: " << getLocalPath() << " count: " << sync->threadSafeState->neverScannedFolderCount.load(); LOG_verbose << sync->syncname << "Remaining known unscanned folders: " << sync->threadSafeState->neverScannedFolderCount.load(); } scanDelayUntil = Waiter::ds + 20; // don't scan too frequently scanAgain = TREE_RESOLVED; setSyncAgain(false, true, false); syncHere = true; size_t numFingerprintBlocked = 0; for (auto& n : *lastFolderScan) { if (n.type == FILENODE && !n.fingerprint.isvalid) { if (ES_EXCLUDED == exclusionState(n.localname, FILENODE, n.fingerprint.size)) { // we need to keep the type in case of the .megaignore rules being changed // n.type = TYPE_DONOTSYNC; LOG_verbose << "Non-fingerprintable file is excluded: " << n.localname; } else { LOG_debug << "Directory scan contains a file that could not be fingerprinted: " << n.localname; ++numFingerprintBlocked; } } else if (n.type == TYPE_SPECIAL || n.type == TYPE_SYMLINK) { // we need to keep the type in case of the .megaignore rules being changed // if (ES_EXCLUDED == exclusionState(n.localname, n.type, n.fingerprint.size)) // { // // no need to complain about this one anymore, the user excluded it // n.type = TYPE_DONOTSYNC; // } // but let's at least compute the exclusion state and log it for these rare cases LOG_verbose << "Exclusion state for special/symlink " << n.localname << " is " << exclusionState(n.localname, n.type, n.fingerprint.size); } } if (numFingerprintBlocked) { initiateScanBlocked(false, true); } else if (rareRO().scanBlocked && rareRO().scanBlocked->filesUnreadable) { LOG_verbose << sync->syncname << "Directory scan fingerprint-blocked files all resolved at: " << getLocalPath(); rare().scanBlocked.reset(); trimRareFields(); } } else // SCAN_INACCESSIBLE { // we were previously able to scan this node, but now we can't. row.fsNode->isBlocked = true; if (!checkForScanBlocked(row.fsNode)) { initiateScanBlocked(true, false); } } } trimRareFields(); return syncHere; } void LocalNode::reassignUnstableFsidsOnceOnly(const FSNode* fsnode) { if (!sync->fsstableids && !unstableFsidAssigned) { // for FAT and other filesystems where we can't rely on fsid // being the same after remount, so update our previously synced nodes // with the actual fsids now attached to them (usually generated by FUSE driver) if (fsid_lastSynced != UNDEF) { auto fsid = UNDEF - 1; auto sname = unique_ptr(); if (fsnode) { if (sync->syncEqual(*fsnode, *this)) fsid = fsnode->fsid; sname = fsnode->cloneShortname(); } setSyncedFsid(fsid, sync->syncs.localnodeBySyncedFsid, localname, std::move(sname)); sync->statecacheadd(this); } unstableFsidAssigned = true; } } void LocalNode::recursiveSetAndReportTreestate(treestate_t ts, bool recurse, bool reportToApp) { if (reportToApp && ts != mReportedSyncState) { assert(sync->syncs.onSyncThread()); sync->syncs.mClient.app->syncupdate_treestate(sync->getConfig(), getLocalPath(), ts, type); } mReportedSyncState = ts; if (recurse) { for (auto& i : children) { i.second->recursiveSetAndReportTreestate(ts, recurse, reportToApp); } } } treestate_t LocalNode::checkTreestate(bool notifyChangeToApp) { // notify file explorer if the sync state overlay icon should change treestate_t ts = TREESTATE_NONE; if (ES_INCLUDED != exclusionState()) { ts = TREESTATE_NONE; } else if (scanAgain == TREE_RESOLVED && checkMovesAgain == TREE_RESOLVED && syncAgain == TREE_RESOLVED) { ts = TREESTATE_SYNCED; } else if (type == FILENODE) { ts = TREESTATE_PENDING; } else if (scanAgain <= TREE_DESCENDANT_FLAGGED && checkMovesAgain <= TREE_DESCENDANT_FLAGGED && syncAgain <= TREE_DESCENDANT_FLAGGED) { ts = TREESTATE_SYNCING; } else { ts = TREESTATE_PENDING; } recursiveSetAndReportTreestate(ts, false, notifyChangeToApp); return ts; } // set fsid - assume that an existing assignment of the same fsid is no longer current and revoke void LocalNode::setSyncedFsid(handle newfsid, fsid_localnode_map& fsidnodes, const LocalPath& fsName, std::unique_ptr newshortname) { if (fsid_lastSynced_it != fsidnodes.end()) { if (newfsid == fsid_lastSynced && localname == fsName) { return; } fsidnodes.erase(fsid_lastSynced_it); } fsid_lastSynced = newfsid; fsidSyncedReused = false; // if synced to fs, localname should match exactly (no differences in case/escaping etc) if (localname != fsName || !!newshortname != !!slocalname || (newshortname && slocalname && *newshortname != *slocalname)) { // localname must always be set by this function, to maintain parent's child maps setnameparent(parent, fsName, std::move(newshortname)); } // LOG_verbose << "localnode " << this << " fsid " << toHandle(fsid_lastSynced) << " localname " << fsName.toPath() << " parent " << parent; if (fsid_lastSynced == UNDEF) { fsid_lastSynced_it = fsidnodes.end(); } else { fsid_lastSynced_it = fsidnodes.insert(std::make_pair(fsid_lastSynced, this)); } // assert(localname.empty() || name.empty() || (!parent && parent_dbid == UNDEF) || parent_dbid == 0 || // 0 == compareUtf(localname, true, name, false, true)); } void LocalNode::setScannedFsid(handle newfsid, fsid_localnode_map& fsidnodes, [[maybe_unused]] const LocalPath& fsName, const FileFingerprint& scanfp) { if (fsid_asScanned_it != fsidnodes.end()) { fsidnodes.erase(fsid_asScanned_it); } fsid_asScanned = newfsid; fsidScannedReused = false; scannedFingerprint = scanfp; if (fsid_asScanned == UNDEF) { fsid_asScanned_it = fsidnodes.end(); } else { fsid_asScanned_it = fsidnodes.insert(std::make_pair(fsid_asScanned, this)); } assert(fsid_asScanned == UNDEF || 0 == compareUtf(localname, true, fsName, true, true)); } void LocalNode::setSyncedNodeHandle(NodeHandle h) { if (syncedCloudNodeHandle_it != sync->syncs.localnodeByNodeHandle.end()) { if (h == syncedCloudNodeHandle) { return; } assert(syncedCloudNodeHandle_it->first == syncedCloudNodeHandle); // too verbose for million-node syncs //LOG_verbose << sync->syncname << "removing synced handle " << syncedCloudNodeHandle << " for " << localnodedisplaypath(*sync->syncs.fsaccess); sync->syncs.localnodeByNodeHandle.erase(syncedCloudNodeHandle_it); } syncedCloudNodeHandle = h; if (syncedCloudNodeHandle == UNDEF) { syncedCloudNodeHandle_it = sync->syncs.localnodeByNodeHandle.end(); } else { // too verbose for million-node syncs //LOG_verbose << sync->syncname << "adding synced handle " << syncedCloudNodeHandle << " for " << localnodedisplaypath(*sync->syncs.fsaccess); syncedCloudNodeHandle_it = sync->syncs.localnodeByNodeHandle.insert(std::make_pair(syncedCloudNodeHandle, this)); } // assert(localname.empty() || name.empty() || (!parent && parent_dbid == UNDEF) || parent_dbid == 0 || // 0 == compareUtf(localname, true, name, false, true)); } LocalNode::~LocalNode() { if (!sync->mDestructorRunning && dbid) { sync->statecachedel(this); } if (neverScanned) { neverScanned = 0; --sync->threadSafeState->neverScannedFolderCount; } if (sync->dirnotify && !sync->mDestructorRunning) { // deactivate corresponding notifyq records sync->dirnotify->fsEventq.replaceLocalNodePointers(this, (LocalNode*)~0); } if (!sync->syncs.mExecutingLocallogout) { // for Locallogout, we will resume syncs and their transfers on re-login. // for other cases - single sync cancel, disable etc - transfers are cancelled. resetTransfer(nullptr); sync->syncs.mMoveInvolvedLocalNodes.erase(this); // remove from fsidnode map, if present if (fsid_lastSynced_it != sync->syncs.localnodeBySyncedFsid.end()) { sync->syncs.localnodeBySyncedFsid.erase(fsid_lastSynced_it); } if (fsid_asScanned_it != sync->syncs.localnodeByScannedFsid.end()) { sync->syncs.localnodeByScannedFsid.erase(fsid_asScanned_it); } if (syncedCloudNodeHandle_it != sync->syncs.localnodeByNodeHandle.end()) { sync->syncs.localnodeByNodeHandle.erase(syncedCloudNodeHandle_it); } } sync->syncs.totalLocalNodes--; sync->threadSafeState->incrementSyncNodeCount(type, -1); // remove parent association if (parent) { setnameparent(nullptr, LocalPath(), nullptr); } deleteChildren(); } void LocalNode::deleteChildren() { for (localnode_map::iterator it = children.begin(); it != children.end(); ) { // the destructor removes the child from our `children` map delete it++->second; } assert(children.empty()); } bool LocalNode::conflictsDetected() const { return conflicts != TREE_RESOLVED; } bool LocalNode::isAbove(const LocalNode& other) const { return other.isBelow(*this); } bool LocalNode::isBelow(const LocalNode& other) const { for (auto* node = parent; node; node = node->parent) { if (node == &other) { return true; } } return false; } void LocalNode::setSubtreeNeedsRefingerprint() { // Re-calculate fingerprints on disk // setScanAgain should be called separately recomputeFingerprint = true; oneTimeUseSyncedFingerprintInScan = false; for (auto& child : children) { if (type != FILENODE) // no need to set it for file versions { child.second->setSubtreeNeedsRefingerprint(); } } } LocalPath LocalNode::getLocalPath() const { LocalPath lp; getlocalpath(lp); return lp; } void LocalNode::getlocalpath(LocalPath& path) const { path.clear(); for (const LocalNode* l = this; l != nullptr; l = l->parent) { assert(!l->parent || l->parent->sync == sync); // sync root has absolute path, the rest are just their leafname path.prependWithSeparator(l->localname); } } string LocalNode::getCloudPath(bool guessLeafName) const { // We may need to guess the leaf name if we suspect // or know that the corresponding cloud node has been moved/renamed // and we need its old name string path; const LocalNode* l = this; if (guessLeafName) { path = l->localname.toName(*sync->syncs.fsaccess); l = l->parent; } for (; l != nullptr; l = l->parent) { string name; CloudNode cn; string fullpath; if (sync->syncs.lookupCloudNode(l->syncedCloudNodeHandle, cn, l->parent ? nullptr : &fullpath, nullptr, nullptr, nullptr, nullptr, Syncs::LATEST_VERSION)) { name = cn.name; } else { name = l->localname.toName(*sync->syncs.fsaccess); } assert(!l->parent || l->parent->sync == sync); if (!path.empty()) path.insert(0, 1, '/'); path.insert(0, l->parent ? name : fullpath); } return path; } string LocalNode::debugGetParentList() { string s; for (const LocalNode* l = this; l != nullptr; l = l->parent) { s += l->localname.toPath(false) + "(" + std::to_string(reinterpret_cast(l)) + ") "; } return s; } // locate child by localname or slocalname LocalNode* LocalNode::childbyname(LocalPath* localChildName) { localnode_map::iterator it; if (!localChildName || ((it = children.find(*localChildName)) == children.end() && (it = schildren.find(*localChildName)) == schildren.end())) { return nullptr; } return it->second; } LocalNode* LocalNode::findChildWithSyncedNodeHandle(NodeHandle h) { for (auto& c : children) { if (c.second->syncedCloudNodeHandle == h) { return c.second; } } return nullptr; } FSNode LocalNode::getLastSyncedFSDetails() const { assert(fsid_lastSynced != UNDEF); FSNode n; n.localname = localname; n.shortname = slocalname ? std::make_unique(*slocalname): nullptr; n.type = type; n.fsid = fsid_lastSynced; n.isSymlink = false; // todo: store localndoes for symlinks but don't use them? n.fingerprint = syncedFingerprint; assert(syncedFingerprint.isvalid || type != FILENODE); return n; } FSNode LocalNode::getScannedFSDetails() const { FSNode n; n.localname = localname; n.shortname = slocalname ? std::make_unique(*slocalname): nullptr; n.type = type; n.fsid = fsid_asScanned; n.isSymlink = false; // todo: store localndoes for symlinks but don't use them? n.fingerprint = scannedFingerprint; assert(scannedFingerprint.isvalid || type != FILENODE); return n; } bool LocalNode::hasPendingTransfers() const { return transferSP != nullptr || std::any_of(std::begin(children), std::end(children), [](const auto& p) { return p.second && p.second->hasPendingTransfers(); }); } void LocalNode::updateMoveInvolvement() { bool moveInvolved = hasRare() && (rare().moveToHere || rare().moveFromHere); if (moveInvolved) { sync->syncs.mMoveInvolvedLocalNodes.insert(this); } else { sync->syncs.mMoveInvolvedLocalNodes.erase(this); } } bool LocalNode::queueClientUpload(shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut) { resetTransfer(upload); if (mUploadThrottling.checkUploadThrottling( sync->syncs.maxUploadsBeforeThrottle(), sync->syncs.uploadCounterInactivityExpirationTime())) { sync->syncs.addToDelayedUploads( DelayedSyncUpload(std::move(upload), vo, queueFirst, ovHandleIfShortcut)); return false; } sync->syncs.queueClient( [syncUpload = std::move(upload), vo, queueFirst, ovHandleIfShortcut]( MegaClient& mc, TransferDbCommitter& committer) { clientUpload(mc, committer, std::move(syncUpload), vo, queueFirst, ovHandleIfShortcut); }); return true; } void LocalNode::queueClientDownload(shared_ptr download, bool queueFirst) { resetTransfer(download); sync->syncs.queueClient( [syncDownload = std::move(download), queueFirst](MegaClient& mc, TransferDbCommitter& committer) { syncDownload->selfKeepAlive = syncDownload; clientDownload(mc, committer, std::move(syncDownload), queueFirst); }); } void LocalNode::resetTransfer(shared_ptr p) { if (transferSP) { if (!transferSP->wasTerminated && !transferSP->wasFileTransferCompleted) { LOG_debug << "Abandoning old transfer, and queueing its cancel on client thread"; // this flag allows in-progress transfers to self-cancel transferSP->wasRequesterAbandoned = true; transferSP->attributeOnlyUpdate = SyncTransfer_inClient::AttributeOnlyUpdate::None; // also queue an operation on the client thread to cancel it if it's queued auto tsp = transferSP; sync->syncs.queueClient([tsp](MegaClient& mc, TransferDbCommitter& committer) { mc.nextreqtag(); mc.stopxfer(tsp.get(), &committer); }); } } transferSP = std::move(p); } void LocalNode::updateTransferLocalname() { if (transferSP) { transferSP->setLocalname(getLocalPath()); } } bool LocalNode::transferResetUnlessMatched(const direction_t dir, const FileFingerprint& fingerprint, const int64_t metamac) { if (!transferSP) return true; const auto uploadPtr = dynamic_cast(transferSP.get()); const bool transferDirectionNeedsToChange = dir != (uploadPtr ? PUT : GET); auto different = false; auto compRes = NODE_COMP_EQUAL; auto transferMetamac = transferSP->mMetaMac.has_value() ? transferSP->mMetaMac.value() : INVALID_META_MAC; if (transferMetamac != INVALID_META_MAC && metamac != INVALID_META_MAC) { compRes = CompareMacAndFpExcludingMtime(transferSP->fingerprint(), fingerprint, transferSP->mMetaMac.value(), metamac); different = transferDirectionNeedsToChange || compRes != NODE_COMP_EQUAL; } else { different = transferDirectionNeedsToChange || transferSP->fingerprint() != fingerprint; } const auto transferTerminatedAndIsRetryable = transferSP->wasTerminated && transferSP->mError != API_EKEY && transferSP->mError != API_EBLOCKED; if (!different && !transferTerminatedAndIsRetryable) { return true; } if (uploadPtr && !mUploadThrottling.handleAbortUpload(*uploadPtr, transferDirectionNeedsToChange, fingerprint, sync->syncs.maxUploadsBeforeThrottle(), transferSP->getLocalname())) { return !uploadPtr->upsyncStarted; } if (compRes == NODE_COMP_DIFFERS_MTIME) { LOG_debug << sync->syncname << "Updating fingerprint mtime of " << transferSP->getLocalname() << " to " << fingerprint.mtime; uploadPtr->updateFingerprintMtime(fingerprint.mtime); return true; } LOG_debug << sync->syncname << "Cancelling superseded transfer of " << transferSP->getLocalname() << ". Reason: " << (transferDirectionNeedsToChange ? "Transfer direction needs to change." : (transferSP->wasTerminated ? "Terminated after failing." : "Fingerprint change. Was: " + transferSP->fingerprintDebugString() + ", now: " + fingerprint.fingerprintDebugString())); resetTransfer(nullptr); return true; } // serialize/unserialize the following LocalNode properties: // - type/size // - fsid // - parent LocalNode's dbid // - corresponding Node handle // - local name // - fingerprint crc/mtime (filenodes only) bool LocalNodeCore::write(string& destination, uint32_t parentID) const { // We need size even if we're not synced. auto size = syncedFingerprint.isvalid ? syncedFingerprint.size : 0; CacheableWriter w(destination); w.serializei64(type ? -type : size); w.serializehandle(fsid_lastSynced); w.serializeu32(parentID); w.serializenodehandle(syncedCloudNodeHandle.as8byte()); w.serializestring(localname.platformEncoded()); if (type == FILENODE) { if (syncedFingerprint.isvalid) { w.serializebinary((byte*)syncedFingerprint.crc.data(), sizeof(syncedFingerprint.crc)); w.serializecompressedi64(syncedFingerprint.mtime); } else { static FileFingerprint zeroFingerprint; w.serializebinary((byte*)zeroFingerprint.crc.data(), sizeof(zeroFingerprint.crc)); w.serializecompressedi64(zeroFingerprint.mtime); } } // Formerly mSyncable. // // No longer meaningful but serialized to maintain compatibility. w.serializebyte(1u); // first flag indicates we are storing slocalname. // Storing it is much, much faster than looking it up on startup. w.serializeexpansionflags(1, 1, 1); auto tmpstr = slocalname ? slocalname->platformEncoded() : string(); w.serializepstr(slocalname ? &tmpstr : nullptr); w.serializebool(namesSynchronized); if (type == FILENODE) { // Difference between realScannedFingerprint and scannedFingerprint is only mtime w.serializecompressedi64(realScannedFingerprint.mtime); } return true; } bool LocalNode::serialize(string* d) const { assert(type != TYPE_UNKNOWN); // In fact this can occur, eg we invalidated scannedFingerprint when it was below a removed node, when an ancestor folder moved // Or (probably) from a node created from the cloud only //assert(type != FILENODE || syncedFingerprint.isvalid || scannedFingerprint.isvalid); // Every node we serialize should have a parent. assert(parent); // The only node with a zero DBID should be the root. assert(parent->dbid || !parent->parent); #ifdef DEBUG if (fsid_lastSynced != UNDEF) { LocalPath localpath = getLocalPath(); auto fa = sync->syncs.fsaccess->newfileaccess(false); if (fa->fopen(localpath, FSLogging::logExceptFileNotFound)) // exists, is file { auto sn = sync->syncs.fsaccess->fsShortname(localpath); if (!(!localname.empty() && ((!slocalname && (!sn || localname == *sn)) || (slocalname && sn && !slocalname->empty() && *slocalname != localname && *slocalname == *sn)))) { // we can't assert here or it can cause test failures, when the LocalNode just hasn't been updated from the disk state yet. // but we can log ERR to try to detect any issues during development. Occasionally there will be false positives, // but also please do investigate when it's not a test that got shut down while busy. LOG_err << "Shortname mismatch on LocalNode serialize! " << "localname: " << localname << " slocalname " << (slocalname?*slocalname:LocalPath()) << (slocalname?"":"") << " actual shorname " << (sn?*sn:LocalPath()) << (sn?"":"") << " for path " << localpath; } } } #endif auto parentID = parent ? parent->dbid : 0; auto result = LocalNodeCore::write(*d, parentID); #ifdef DEBUG // Quick (de)serizliation check. { string source = *d; uint32_t id = 0u; auto node = unserialize(*sync, source, id); assert(node); assert(node->localname == localname); assert(!node->slocalname == !slocalname); assert(!node->slocalname || *node->slocalname == *slocalname); node->type = TYPE_UNKNOWN; // prevent mis-counting in destructor } #endif return result; } bool LocalNodeCore::read(const string& source, uint32_t& parentID) { if (source.size() < sizeof(m_off_t) // type/size combo + sizeof(handle) // fsid + sizeof(uint32_t) // parent dbid + MegaClient::NODEHANDLE // handle + sizeof(short)) // localname length { LOG_err << "LocalNode unserialization failed - short data"; return false; } CacheableReader r(source); nodetype_t nodeType; m_off_t size; if (!r.unserializei64(size)) return false; if (size < 0 && size >= -FOLDERNODE) { // will any compiler optimize this to a const assignment? nodeType = (nodetype_t)-size; size = 0; } else { nodeType = FILENODE; } handle fsid; handle h = 0; string name, shortname; m_time_t mtime = 0; int32_t crc[4]; memset(crc, 0, sizeof crc); byte syncable = 1; unsigned char expansionflags[8] = { 0 }; bool ns = false; m_time_t extraMtime = 0; if (!r.unserializehandle(fsid) || !r.unserializeu32(parentID) || !r.unserializenodehandle(h) || !r.unserializestring(name) || (nodeType == FILENODE && !r.unserializebinary((byte*)crc, sizeof(crc))) || (nodeType == FILENODE && !r.unserializecompressedi64(mtime)) || (r.hasdataleft() && !r.unserializebyte(syncable)) || (r.hasdataleft() && !r.unserializeexpansionflags(expansionflags, 3)) || (expansionflags[0] && !r.unserializecstr(shortname, false)) || (expansionflags[1] && !r.unserializebool(ns)) || (expansionflags[2] && nodeType == FILENODE && !r.unserializecompressedi64(extraMtime))) { LOG_err << "LocalNode unserialization failed at field " << r.fieldnum; assert(false); return false; } assert(!r.hasdataleft()); type = nodeType; this->syncedFingerprint.size = size; this->fsid_lastSynced = fsid; localname = LocalPath::fromPlatformEncodedRelative(name); this->slocalname.reset(shortname.empty() ? nullptr : new LocalPath(LocalPath::fromPlatformEncodedRelative(shortname))); this->slocalname_in_db = 0 != expansionflags[0]; this->namesSynchronized = ns; memcpy(this->syncedFingerprint.crc.data(), crc, sizeof crc); this->syncedFingerprint.mtime = mtime; this->syncedFingerprint.isvalid = mtime != 0; // previously we scanned and created the LocalNode, but we had not set syncedFingerprint this->syncedCloudNodeHandle.set6byte(h); bool hasRealScannedFingerprint = expansionflags[2]; if (hasRealScannedFingerprint) { memcpy(this->realScannedFingerprint.crc.data(), crc, sizeof crc); this->realScannedFingerprint.mtime = extraMtime; this->realScannedFingerprint.isvalid = extraMtime != 0; this->realScannedFingerprint.size = size; } return true; } unique_ptr LocalNode::unserialize(Sync& sync, const string& source, uint32_t& parentID) { auto node = std::make_unique(&sync); if (!node->read(source, parentID)) return nullptr; return node; } #ifdef USE_INOTIFY LocalNode::WatchHandle::WatchHandle() : mEntry(mSentinel.end()) { } LocalNode::WatchHandle::~WatchHandle() { operator=(nullptr); } auto LocalNode::WatchHandle::operator=(WatchMapIterator entry) -> WatchHandle& { if (mEntry == entry) return *this; operator=(nullptr); mEntry = entry; return *this; } auto LocalNode::WatchHandle::operator=(std::nullptr_t) -> WatchHandle& { if (mEntry == mSentinel.end()) return *this; auto& node = *mEntry->second.first; auto& sync = *node.sync; auto& notifier = #ifndef __ANDROID__ static_cast(*sync.dirnotify); #else static_cast(*sync.dirnotify); #endif notifier.removeWatch(mEntry); invalidate(); return *this; } void LocalNode::WatchHandle::invalidate() { mEntry = mSentinel.end(); } bool LocalNode::WatchHandle::operator==(handle fsid) const { if (mEntry == mSentinel.end()) return false; return fsid == mEntry->second.second; } WatchResult LocalNode::watch(const LocalPath& path, handle fsid) { // Can't add a watch if we don't have a notifier. if (!sync->dirnotify) return WR_SUCCESS; // Do we need to (re)create a watch? if (mWatchHandle == fsid) { LOG_verbose << "Watch for path: " << path << " with mWatchHandle == fsid == " << fsid << " Already in place"; return WR_SUCCESS; } // Get our hands on the notifier. auto& notifier = #ifndef __ANDROID__ static_cast(*sync->dirnotify); #else static_cast(*sync->dirnotify); #endif // Add the watch. auto result = notifier.addWatch(*this, path, fsid); // Were we able to add the watch? if (result.second == WatchResult::WR_SUCCESS) { // Yup so assign the handle. mWatchHandle = result.first; } else { // Make sure any existing watch is invalidated. mWatchHandle = nullptr; } return result.second; } WatchMap LocalNode::WatchHandle::mSentinel; #else // USE_INOTIFY WatchResult LocalNode::watch(const LocalPath&, handle) { // Only inotify requires us to create watches for each node. return WR_SUCCESS; } #endif // ! USE_INOTIFY void LocalNode::clearFilters() { // Only for directories. assert(type == FOLDERNODE); // Clear filter state. if (rareRO().filterChain) { rare().filterChain.reset(); rare().badlyFormedIgnoreFilePath.reset(); trimRareFields(); } // Reset ignore file state. setRecomputeExclusionState(false, false); // Re-examine this subtree. setScanAgain(false, true, true, 0); setSyncAgain(false, true, true); } const FilterChain& LocalNode::filterChainRO() const { static const FilterChain dummy; auto& filterChainPtr = rareRO().filterChain; if (filterChainPtr) return *filterChainPtr; return dummy; } bool LocalNode::loadFilters(const LocalPath& path) { assert(type == FOLDERNODE); auto& fc = rare().filterChain; fc.reset(new FilterChain); fc->mLoadSucceeded = FLR_SUCCESS == fc->load(*sync->syncs.fsaccess, path); return fc->mLoadSucceeded; } ExclusionState LocalNode::calcExcluded(RemotePathPair namePath, nodetype_t applicableType, bool inherited) const { // This specialization only makes sense for directories. assert(this->type == FOLDERNODE); // Check whether the file is excluded by any filters. for (auto* node = this; node; node = node->parent) { assert(node->mExclusionState == ES_INCLUDED); if (node->rareRO().filterChain) { // Should we only consider inheritable filter rules? inherited = inherited || node != this; // Check for a filter match. auto result = node->filterChainRO().match(namePath, applicableType, inherited); // Was the file matched by any filters? if (result != ES_UNMATCHED) return result; } // Update path so that it's applicable to the next node's path filters. namePath.second.prependWithSeparator(node->toName_of_localname); } // If no rule matches, file's included. return ES_INCLUDED; } ExclusionState LocalNode::calcExcluded(const RemotePathPair&, m_off_t size) const { // Specialization only meaningful for directories. assert(type == FOLDERNODE); // Consider files of unknown size included. if (size < 0) return ES_INCLUDED; // Check whether this file is excluded by any size filters. for (auto* node = this; node; node = node->parent) { // Sanity: We should never be called if either of these is true. assert(node->mExclusionState == ES_INCLUDED); if (node->rareRO().filterChain) { // Check for a filter match. auto result = node->filterChainRO().match(size); // Was the file matched by any filters? if (result != ES_UNMATCHED) return result; } } // File's included. return ES_INCLUDED; } void LocalNode::setRecomputeExclusionState(bool includingThisOne, bool scan) { LOG_debug << "Clearing all LocalNode exclusion state from " << getLocalPath() << (includingThisOne ? " inclusive" : ""); if (includingThisOne) { mExclusionState = ES_UNKNOWN; } if (scan) { // test this one by using "Ignore" button to exclude special symlink "My Pictures" // and then manually remove the exclusion and see if the stall returns // (same folder as .megaignore, and separately a subfolder) setScanAgain(false, true, true, 0); } else { setSyncAgain(false, true, true); } if (type == FILENODE) return; list pending(1, this); while (!pending.empty()) { auto& node = *pending.front(); for (auto& childIt : node.children) { auto& child = *childIt.second; if (child.mExclusionState == ES_UNKNOWN) continue; child.mExclusionState = ES_UNKNOWN; if (child.type == FOLDERNODE) { pending.emplace_back(&child); // make sure we visit this node later and recalculate exclusions child.setSyncAgain(false, true, true); } } pending.pop_front(); } } bool LocalNode::waitingForIgnoreFileLoad() const { for (auto* node = this; node; node = node->parent) { if (node->mWaitingForIgnoreFileLoad) return true; } return false; } // Query whether a file is excluded by this node or one of its parents. template typename std::enable_if::value, ExclusionState>::type LocalNode::exclusionState(const PathType& path, nodetype_t type, m_off_t size) const { // This specialization is only meaningful for directories. assert(this->type == FOLDERNODE); // We can't determine our child's exclusion state if we don't know our own. // Our children are excluded if we are. if (mExclusionState != ES_INCLUDED) return mExclusionState; // Children of unknown type still have to be handled. // Scan-blocked appear as TYPE_UNKNOWN and the user must be // able to exclude them when they are notified of them if (rareRO().filterChain) { auto& fc = rareRO().filterChain; if (type == FILENODE && path == IGNORE_FILE_NAME) { // if there's no local file, allow download if (!fc->mFingerprint.isvalid) return ES_INCLUDED; // Make sure we evaluate the local content first to see if it has the sync flag // if we can't load it yet, the user must adjust it before we can know if (!fc->mLoadSucceeded) return ES_EXCLUDED; // Ignore files are synced or not depending on flags in the file text return fc->mSyncThisMegaignore ? ES_INCLUDED : ES_EXCLUDED; } if (!rareRO().filterChain->mLoadSucceeded) { return ES_UNKNOWN; } } // We can't know the child's state unless our filters are current. if (mWaitingForIgnoreFileLoad) return ES_UNKNOWN; // Computed cloud name and relative cloud path. RemotePathPair namePath; // Current path component. PathType component; // Check if any intermediary path components are excluded. for (size_t index = 0; path.nextPathComponent(index, component); ) { // Compute cloud name. namePath.first = component.toName(*sync->syncs.fsaccess); // Compute relative cloud path. namePath.second.appendWithSeparator(namePath.first, false); // Have we hit the final path component? if (!path.hasNextPathComponent(index)) break; // Is this path component excluded? // A component could only be FOLDERNODE as we don't recusively go into others such as symlink if (ES_EXCLUDED == calcExcluded(namePath, FOLDERNODE, false)) return ES_EXCLUDED; } // Does the final path component represent a file? if (type == FILENODE) { // Ignore files are only excluded if one of their parents is. if (namePath.first == IGNORE_FILE_NAME) return ES_INCLUDED; // Is the file excluded by any size filters? if (ES_EXCLUDED == calcExcluded(namePath, size)) return ES_EXCLUDED; } // Is the file excluded by any name filters? return calcExcluded(namePath, type, false); } // Make sure we instantiate the two types. Jenkins gcc can't handle this in the header. template ExclusionState LocalNode::exclusionState(const LocalPath& path, nodetype_t type, m_off_t size) const; template ExclusionState LocalNode::exclusionState(const RemotePath& path, nodetype_t type, m_off_t size) const; ExclusionState LocalNode::exclusionState(const string& name, nodetype_t applicableType, m_off_t size) const { assert(this->type == FOLDERNODE); // Consider providing a specialized implementation to avoid conversion. auto fsAccess = sync->syncs.fsaccess.get(); auto fsType = sync->mFilesystemType; LocalPath absoluteName = LocalPath::fromRelativeName(name, *fsAccess, fsType); return exclusionState(absoluteName, applicableType, size); } ExclusionState LocalNode::exclusionState() const { if (isDoNotSyncFileName(toName_of_localname)) return ES_EXCLUDED; return mExclusionState; } bool LocalNode::isIgnoreFile() const { return mIsIgnoreFile; } bool LocalNode::recomputeExclusionState() { // We should never be asked to recompute the root's exclusion state. assert(parent); // Only recompute the state if it's necessary. if (mExclusionState != ES_UNKNOWN) return false; mExclusionState = parent->exclusionState(localname, type, -1); if (mExclusionState == ES_EXCLUDED) { // excluded nodes are as if they didn't exist. So remove from db // also don't remember anything that might result in a move/delete etc if they get un-ignored setSyncedFsid(UNDEF, sync->syncs.localnodeBySyncedFsid, localname, cloneShortname()); void setSyncedNodeHandle(NodeHandle()); sync->statecachedel(this); } else if (mExclusionState == ES_INCLUDED) { if (!dbid) { // This node should be part of the database again sync->statecacheadd(this); } } return mExclusionState != ES_UNKNOWN; } #endif // ENABLE_SYNC void NodeCounter::operator += (const NodeCounter& o) { storage += o.storage; files += o.files; folders += o.folders; versions += o.versions; versionStorage += o.versionStorage; } void NodeCounter::operator -= (const NodeCounter& o) { storage -= o.storage; files -= o.files; folders -= o.folders; versions -= o.versions; versionStorage -= o.versionStorage; } std::string NodeCounter::serialize() const { std::string nodeCountersBlob; CacheableWriter w(nodeCountersBlob); w.serializeu32(static_cast(files)); w.serializeu32(static_cast(folders)); w.serializei64(storage); w.serializeu32(static_cast(versions)); w.serializei64(versionStorage); return nodeCountersBlob; } NodeCounter::NodeCounter(const std::string &blob) { CacheableReader r(blob); if (blob.size() == 28) // 4 + 4 + 8 + 4 + 8 { uint32_t auxFiles; uint32_t auxFolders; uint32_t auxVersions; if (!r.unserializeu32(auxFiles) || !r.unserializeu32(auxFolders) || !r.unserializei64(storage) || !r.unserializeu32(auxVersions) || !r.unserializei64(versionStorage)) { LOG_err << "Failure to unserialize node counter"; assert(false); return; } files = auxFiles; folders = auxFolders; versions = auxVersions; } // During internal testing, 'files', 'folders' and 'versions' were stored as 'size_t', whose size is platform-dependent // -> in some machines it is 8 bytes, in others is 4 bytes. With the only goal of providing backwards compatibility for // internal testers, if the blob doesn't have expected size (using 4 bytes), check if size matches the expected using 8 bytes else if (blob.size() == 40) // 8 + 8 + 8 + 8 + 8 { uint64_t auxFiles; uint64_t auxFolders; uint64_t auxVersions; if (!r.unserializeu64(auxFiles) || !r.unserializeu64(auxFolders) || !r.unserializei64(storage) || !r.unserializeu64(auxVersions) || !r.unserializei64(versionStorage)) { LOG_err << "Failure to unserialize node counter (files, folders and versions uint64_t)"; assert(false); return; } files = static_cast(auxFiles); folders = static_cast(auxFolders); versions = static_cast(auxVersions); } else { LOG_err << "Invalid size at node counter unserialization"; assert(false); } } CloudNode::CloudNode(const Node& n) : name(n.hasName() ? n.displayname() : "") , type(n.type) , handle(n.nodeHandle()) , parentHandle(n.parent ? n.parent->nodeHandle() : NodeHandle()) , parentType(n.parent ? n.parent->type : TYPE_UNKNOWN) , fingerprint(n.fingerprint()) { assert(fingerprint.isvalid || type != FILENODE || name.empty()); // Accept NO_NAME nodes to be excluded later } bool CloudNode::isIgnoreFile() const { return type == FILENODE && name == IGNORE_FILE_NAME; } NodeManagerNode::NodeManagerNode(NodeManager& nodeManager, NodeHandle nodeHandle) : mLRUPosition(nodeManager.invalidCacheLRUPos()) , mNodeHandle(nodeHandle) , mNodeManager(nodeManager) { } void NodeManagerNode::setNode(shared_ptr node) { assert(mNode.expired() && "There is a valid node assigned"); mNode = node; } shared_ptr NodeManagerNode::getNodeInRam(bool updatePositionAtLRU) { shared_ptr node = mNode.lock(); if (node && updatePositionAtLRU) { mNodeManager.insertNodeCacheLRU(node); } return node; } NodeHandle NodeManagerNode::getNodeHandle() const { return mNodeHandle; } } // namespace sdk-10.11.0/src/nodemanager.cpp000066400000000000000000002174631516266226600162370ustar00rootroot00000000000000/** * @file nodemanager.cpp * @brief Client access engine core logic * * (c) 2013-2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/nodemanager.h" #include "mega/base64.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/share.h" namespace mega { NodeManager::NoKeyLogger NodeManager::mNoKeyLogger{}; bool NodeSearchFilter::isValidNodeType(const nodetype_t nodeType) const { return mNodeType == nodeType; } bool NodeSearchFilter::isValidCreationTime(const int64_t time) const { if (mCreationLowerLimit && time <= mCreationLowerLimit) return false; if (mCreationUpperLimit && time >= mCreationUpperLimit) return false; return true; } bool NodeSearchFilter::isValidModificationTime(const int64_t time) const { if (mModificationLowerLimit && time <= mModificationLowerLimit) return false; if (mModificationUpperLimit && (time <= 0 || time >= mModificationUpperLimit)) return false; return true; } bool NodeSearchFilter::isValidCategory(const MimeType_t category, const nodetype_t nodeType) const { if (nodeType != FILENODE) return false; if (mMimeCategory == MIME_TYPE_ALL_DOCS && isDocType(category)) return true; if (mMimeCategory == MIME_TYPE_ALL_VISUAL_MEDIA && isVisualMediaType(category)) { return true; } return category == mMimeCategory; } bool NodeSearchFilter::isValidName(const uint8_t* testName) const { const auto& pattern = mNameFilter.getPattern(); if (pattern.empty() || !testName) return true; return likeCompare(pattern.c_str(), reinterpret_cast(testName)); } bool NodeSearchFilter::isValidDescription(const uint8_t* testDescription) const { const auto& pattern = mDescriptionFilter.getPattern(); if (pattern.empty()) return true; return testDescription && likeCompare(pattern.c_str(), reinterpret_cast(testDescription)); } bool NodeSearchFilter::isValidTagSequence(const uint8_t* tagSequence) const { if (!hasTag()) return true; // we know tags can not contain delimiter if (!tagSequence || mTagFilterContainsSeparator) return false; auto tokens = splitString(reinterpret_cast(tagSequence), TAG_DELIMITER); return getTagPosition(tokens, mTagFilter.getPattern()) != tokens.end(); } bool NodeSearchFilter::isValidFav(const bool isNodeFav) const { return !hasFav() || (mFavouriteFilterOption == BoolFilter::onlyTrue) == isNodeFav; } bool NodeSearchFilter::isValidSensitivity(const bool isNodeSensitive) const { if (!hasSensitive()) return true; if (isNodeSensitive && mExcludeSensitive == BoolFilter::onlyFalse) return true; if (!isNodeSensitive && mExcludeSensitive == BoolFilter::onlyTrue) return true; return false; } bool NodeSearchFilter::isDocType(const MimeType_t t) { switch (t) { case MIME_TYPE_DOCUMENT: case MIME_TYPE_PDF: case MIME_TYPE_PRESENTATION: case MIME_TYPE_SPREADSHEET: return true; default: return false; } } bool NodeSearchFilter::isVisualMediaType(const MimeType_t t) { switch (t) { case MIME_TYPE_PHOTO: case MIME_TYPE_VIDEO: return true; default: return false; } } // Log the first 256 entries and then every 256 entries void NodeManager::NoKeyLogger::log(const Node& n) const { auto current = mCount.fetch_add(1); if (current <= 256 || (current % 256 == 0)) { LOG_debug << "Storing an encrypted node[" << current << "]: " << n.type << " " << n.size << " " << Base64Str(n.nodehandle); } } NodeManager::NodeManager(MegaClient& client) : mClient(client) , mNodesInRam{0} { } void NodeManager::setTable(DBTableNodes *table) { LockGuard g(mMutex); setTable_internal(table); } void NodeManager::setTable_internal(DBTableNodes *table) { assert(mMutex.owns_lock()); mTable = table; } void NodeManager::reset() { LockGuard g(mMutex); reset_internal(); } void NodeManager::reset_internal() { assert(mMutex.owns_lock()); setTable_internal(nullptr); cleanNodes_internal(); mNullRootNodesReported = false; } bool NodeManager::setrootnode(std::shared_ptr node) { LockGuard g(mMutex); return setrootnode_internal(node); } bool NodeManager::setrootnode_internal(std::shared_ptr node) { assert(mMutex.owns_lock()); switch (node->type) { case ROOTNODE: rootnodes.files = node->nodeHandle(); rootnodes.mRootNodes[ROOTNODE] = node; return true; case VAULTNODE: rootnodes.vault = node->nodeHandle(); rootnodes.mRootNodes[VAULTNODE] = node; return true; case RUBBISHNODE: rootnodes.rubbish = node->nodeHandle(); rootnodes.mRootNodes[RUBBISHNODE] = node; return true; default: if (rootnodes.files == node->nodeHandle()) // Folder link { rootnodes.mRootNodes[ROOTNODE] = node; return true; } else { assert(false); } return false; } } void NodeManager::notifyNode(std::shared_ptr n, sharedNode_vector* nodesToReport) { LockGuard g(mMutex); notifyNode_internal(n, nodesToReport); } void NodeManager::notifyNode_internal(std::shared_ptr n, sharedNode_vector* nodesToReport) { assert(mMutex.owns_lock()); n->applykey(); if (!mClient.fetchingnodes) { if (n->changed.modifiedByThisClient && !n->changed.removed && n->attrstring) { // report a "NO_KEY" event char* buf = new char[n->nodekey().size() * 4 / 3 + 4]; Base64::btoa((byte*)n->nodekey().data(), n->nodekey().size(), buf); int changed = 0; changed |= (int)n->changed.removed; changed |= n->changed.attrs << 1; changed |= n->changed.owner << 2; changed |= n->changed.ctime << 3; changed |= n->changed.fileattrstring << 4; changed |= n->changed.inshare << 5; changed |= n->changed.outshares << 6; changed |= n->changed.pendingshares << 7; changed |= n->changed.parent << 8; changed |= n->changed.publiclink << 9; changed |= n->changed.newnode << 10; changed |= n->changed.name << 11; changed |= n->changed.favourite << 12; changed |= n->changed.sensitive << 13; changed |= n->changed.pwd << 14; auto attrlen = n->attrstring->size(); string base64attrstring; base64attrstring.resize(static_cast(attrlen * 4 / 3 + 4)); base64attrstring.resize(Base64::btoa((byte*)n->attrstring->data(), n->attrstring->size(), (char*)base64attrstring.data())); char report[512]; Base64::btoa((const byte *)&n->nodehandle, MegaClient::NODEHANDLE, report); snprintf(report + 8, sizeof(report) - 8, " %d %" PRIu64 " %zu %X %.200s %.200s", n->type, n->size, attrlen, changed, buf, base64attrstring.c_str()); mClient.reportevent("NK", report, 0); mClient.sendevent(99400, report, 0); delete [] buf; } } if (!n->notified) { n->notified = true; if (nodesToReport) { nodesToReport->push_back(n); } else { mNodeNotify.push_back(n); } } } bool NodeManager::addNode(std::shared_ptr node, bool notify, bool isFetching, MissingParentNodes& missingParentNodes) { LockGuard g(mMutex); return addNode_internal(node, notify, isFetching, missingParentNodes); } bool NodeManager::addNode_internal(std::shared_ptr node, bool notify, bool isFetching, MissingParentNodes& missingParentNodes) { assert(mMutex.owns_lock()); // ownership of 'node' is taken by NodeManager::mNodes if node is kept in memory, // and by NodeManager::mNodeToWriteInDB if node is only written to DB. In the latter, // the 'node' is deleted upon saveNodeInDb() // 'isFetching' is true only when CommandFetchNodes is in flight and/or it has been received, // but it's been complemented with actionpackets. It's false when loaded from DB. // 'notify' is false when loading nodes from API or DB. True when node is received from // actionpackets and/or from response of CommandPutnodes bool rootNode = isFromRootNodeType(*node.get()); // getRootNodeFiles() is always set for folder links before adding any node (upon login) bool isFolderLink = rootnodes.files == node->nodeHandle(); bool keepNodeInMemory = rootNode || isFolderLink || !isFetching || notify || node->parentHandle() == rootnodes.files; // first level of children for CloudDrive // Note: incoming shares are not kept in ram during fetchnodes from API. Instead, they are loaded // upon mergenewshares(), when fetchnodes is completed if (keepNodeInMemory) { saveNodeInRAM(node, rootNode || isFolderLink, missingParentNodes); // takes ownership } else { // still keep it in memory temporary, until saveNodeInDb() assert(!mNodeToWriteInDb); mNodeToWriteInDb = node; // when keepNodeInMemory is true, NodeManager::addChild is called by Node::setParent (from NodeManager::saveNodeInRAM) auto pair = mNodes.emplace(node->nodeHandle(), NodeManagerNode(*this, node->nodeHandle())); // The NodeManagerNode could have been added by NodeManager::addChild() but, in that case, mNode would be invalid auto& nodePosition = pair.first; nodePosition->second.mAllChildrenHandleLoaded = true; // Receive a new node, children aren't received yet or they are stored in nodesWithMissingParents addChild_internal(node->parentHandle(), node->nodeHandle(), nullptr); } return true; } bool NodeManager::updateNode(Node *node) { LockGuard g(mMutex); return updateNode_internal(node); } bool NodeManager::updateNode_internal(Node *node) { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return false; } putNodeInDb(node); return true; } void NodeManager::reportNullRootNodes(const size_t rootNodesSize) { if (mNullRootNodesReported) { return; } rootNodesSize >= rootnodes.MIN_NUM_ROOT_NODES ? mClient.sendevent(99490, "Null rootnode/s detected", 0) : mClient.sendevent(99491, "Null rootnode/s detected and wrong number of root nodes retrieved", 0); LOG_err << "getNodeCount_internal: Null rootnode/s detected. Number of root nodes: " << rootNodesSize; mNullRootNodesReported = true; assert(false); } std::shared_ptr NodeManager::getNodeByHandle(NodeHandle handle) { LockGuard g(mMutex); return getNodeByHandle_internal(handle); } std::shared_ptr NodeManager::getNodeByHandle_internal(NodeHandle handle) { assert(mMutex.owns_lock()); if (handle.isUndef()) return nullptr; if (mNodes.empty()) { return nullptr; } std::shared_ptr node = getNodeInRAM(handle); if (!node) { node = getNodeFromDataBase(handle); } return node; } sharedNode_list NodeManager::getChildren(const Node* parent, CancelToken cancelToken, bool includeVersions) { LockGuard g(mMutex); return getChildren_internal(parent, cancelToken, includeVersions); } sharedNode_list NodeManager::getChildren_internal(const Node* parent, CancelToken cancelToken, bool includeVersions) { assert(mMutex.owns_lock()); sharedNode_list childrenList; if (!parent || !mTable || mNodes.empty()) { return childrenList; } // if handles of all children are known, load missing child nodes one by one if (parent->mNodePosition->second.mAllChildrenHandleLoaded) { if (!parent->mNodePosition->second.mChildren) { return childrenList; } for (const auto &child : *parent->mNodePosition->second.mChildren) { if (cancelToken.isCancelled()) { childrenList.clear(); return childrenList; } if (child.second) { childrenList.push_back(getNodeFromNodeManagerNode(*child.second)); } else { shared_ptr n = getNodeFromDataBase(child.first); assert(n && "Node should be present at DB"); if (n) { childrenList.push_back(std::move(n)); } } } } else // get all children from DB directly and load missing ones { if (parent->mNodePosition->second.mChildren) { for (const auto& child : *parent->mNodePosition->second.mChildren) { if (child.second) { if (shared_ptr node = child.second->getNodeInRam()) { childrenList.push_back(std::move(node)); } } } } std::vector> nodesFromTable; NodeSearchFilter nf; nf.includeVersions(includeVersions); nf.byAncestors({parent->nodehandle, UNDEF, UNDEF}); mTable->getChildren(nf, 0 /*Order none*/, nodesFromTable, cancelToken, NodeSearchPage{0, 0}); if (cancelToken.isCancelled()) { childrenList.clear(); return childrenList; } if (!nodesFromTable.empty() && !parent->mNodePosition->second.mChildren) { parent->mNodePosition->second.mChildren = std::make_unique>(); } for (const auto& nodeSerializedIt : nodesFromTable) { if (cancelToken.isCancelled()) { childrenList.clear(); return childrenList; } auto childIt = parent->mNodePosition->second.mChildren->find(nodeSerializedIt.first); if (childIt == parent->mNodePosition->second.mChildren->end() || !childIt->second) // handle or node not loaded { auto itNode = mNodes.find(nodeSerializedIt.first); if ( itNode == mNodes.end() || !itNode->second.getNodeInRam()) // not loaded { shared_ptr n(getNodeFromNodeSerialized(nodeSerializedIt.second)); if (!n) { childrenList.clear(); return childrenList; } childrenList.push_back(std::move(n)); } else // -> node loaded, but it isn't associated to the parent -> the node has been moved but DB isn't already updated { assert(getNodeFromNodeManagerNode(itNode->second)->parentHandle() != parent->nodeHandle()); } } } parent->mNodePosition->second.mAllChildrenHandleLoaded = true; } return childrenList; } sharedNode_vector NodeManager::listChildNodesLexicographically( const handle parenthandle, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset) { LockGuard g(mMutex); return listChildNodesLexicographically_internal(parenthandle, cancelFlag, maxElements, offset); } sharedNode_vector NodeManager::listChildNodesLexicographically_internal( const handle parentHandle, CancelToken cancelFlag, const size_t maxElements, const std::optional& offset) { assert(mMutex.owns_lock()); // validation if (parentHandle == UNDEF || !mTable || mNodes.empty()) { assert(parentHandle != UNDEF && mTable && !mNodes.empty()); return sharedNode_vector(); } // db look-up std::vector> nodesFromTable; if (!mTable->listChildNodesLexicographically(parentHandle, nodesFromTable, cancelFlag, maxElements, offset)) { return sharedNode_vector(); } sharedNode_vector nodes = processUnserializedNodes(nodesFromTable, cancelFlag); return nodes; } sharedNode_vector NodeManager::getChildren(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page) { LockGuard g(mMutex); return getChildren_internal(filter, order, cancelFlag, page); } sharedNode_vector NodeManager::getChildren_internal(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page) { assert(mMutex.owns_lock()); // validation if (filter.byParentHandle() == UNDEF || !mTable || mNodes.empty()) { assert(filter.byParentHandle() != UNDEF && mTable && !mNodes.empty()); return sharedNode_vector(); } // small optimization to possibly skip the db look-up if (filter.bySensitivity() == NodeSearchFilter::BoolFilter::onlyTrue) { shared_ptr node = getNodeByHandle_internal(NodeHandle().set6byte(filter.byParentHandle())); if (!node || node->isSensitiveInherited()) { return sharedNode_vector(); } } // db look-up vector> nodesFromTable; if (!mTable->getChildren(filter, order, nodesFromTable, cancelFlag, page)) { return sharedNode_vector(); } sharedNode_vector nodes = processUnserializedNodes(nodesFromTable, cancelFlag); return nodes; } sharedNode_vector NodeManager::getRecentNodes(unsigned maxcount, m_time_t since, bool excludeSensitives) { LockGuard g(mMutex); sharedNode_vector result = getRecentNodes_internal(NodeSearchPage{0, maxcount}, since); if (!excludeSensitives) return result; const auto isSensitive = [](const std::shared_ptr& node) -> bool { return node && node->isSensitiveInherited(); }; const auto filterSensitives = [&isSensitive](sharedNode_vector& v) -> void { auto it = std::remove_if(std::begin(v), std::end(v), isSensitive); v.erase(it, std::end(v)); }; filterSensitives(result); if (result.size() == maxcount) return result; // Keep asking for more no sensitive nodes to the db unsigned start = maxcount; unsigned querySize = maxcount; while (true) { auto moreResults = getRecentNodes_internal(NodeSearchPage{start, querySize}, since); if (moreResults.empty()) // No more potential results return result; filterSensitives(moreResults); if (const auto remaining = maxcount - result.size(); moreResults.size() > remaining) { result.insert(std::end(result), std::begin(moreResults), std::begin(moreResults) + static_cast(remaining)); return result; } result.insert(std::end(result), std::begin(moreResults), std::end(moreResults)); start += querySize; constexpr unsigned MAX_QUERY_SIZE = 100000U; if (querySize < MAX_QUERY_SIZE) querySize *= 2; } } sharedNode_vector NodeManager::getRecentNodes_internal(const NodeSearchPage& page, m_time_t since) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { return sharedNode_vector(); } std::vector> nodesFromTable; mTable->getRecentNodes(page, since, nodesFromTable); return processUnserializedNodes(nodesFromTable); } uint64_t NodeManager::getNodeCount() { LockGuard g(mMutex); return getNodeCount_internal(); } uint64_t NodeManager::getNodeCount_internal() { assert(mMutex.owns_lock()); if (mNodes.empty()) { return 0; } uint64_t count = 0; sharedNode_vector roots = getRootNodesAndInshares(); for (auto& node: roots) { if (node) { NodeCounter nc = node->getCounter(); count += nc.files + nc.folders + nc.versions; } else { reportNullRootNodes(roots.size()); } } // add roots to the count if logged into account (and fetchnodes is done <- roots are ready) if (!mClient.loggedIntoFolder() && roots.size()) { // Root nodes aren't taken into consideration as part of node counters if (mClient.isClientType(MegaClient::ClientType::DEFAULT)) { count += 3; assert(!rootnodes.files.isUndef() && !rootnodes.vault.isUndef() && !rootnodes.rubbish.isUndef()); } else if (mClient.isClientType(MegaClient::ClientType::PASSWORD_MANAGER)) { count += 1; assert(!rootnodes.vault.isUndef()); } else { LOG_err << "Unexpected MegaClient type (" << static_cast(mClient.getClientType()) << ") requested nodes count"; assert(false); } } #ifndef NDEBUG if (mNodes.size()) { uint64_t countDb = mTable ? mTable->getNumberOfNodes() : 0; if (!(mTable || count == countDb)) { assert(!mTable || count == countDb); } } #endif return count; } auto NodeManager::getNodeTagsBelow(CancelToken cancelToken, const std::set& handles, const std::string& pattern) -> std::optional> { // Make sure we have exclusive access to the database. LockGuard guard(mMutex); // Convenience. static const auto warning = [](const char* message) { LOG_warn << "getNodeTagsBelow: " << message; return std::nullopt; }; // warning // Make sure the database is sane and that some nodes exist. if (!mTable || mNodes.empty()) return warning("The database hasn't been opened or there are no nodes present"); // Make sure the caller isn't trying to filter by multiple tags simultaneously. if (pattern.find(MegaClient::TAG_DELIMITER) != std::string::npos) return warning("You can't filter by multiple tags at the same time"); std::optional> accumulatedTags; // Try and retrieve the tags below the specified nodes. for (const auto& handle: handles) { // Try and retrieve tags below this node. auto tags = mTable->getNodeTagsBelow(cancelToken, handle, pattern); // Couldn't get tags. if (!tags) continue; // Merge tags into accumulated result if possible. if (accumulatedTags) accumulatedTags->merge(*tags); else accumulatedTags = std::move(tags); } // Return accumulated tags to caller. return accumulatedTags; } sharedNode_vector NodeManager::searchNodes(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page) { LockGuard g(mMutex); return searchNodes_internal(filter, order, cancelFlag, page); } sharedNode_vector NodeManager::searchNodes_internal(const NodeSearchFilter& filter, int order, CancelToken cancelFlag, const NodeSearchPage& page) { assert(mMutex.owns_lock()); // validation if (!mTable || mNodes.empty()) { assert(mTable && !mNodes.empty()); return sharedNode_vector(); } // small optimization to possibly skip the db look-up const vector& ancestors = filter.byAncestorHandles(); if (filter.bySensitivity() == NodeSearchFilter::BoolFilter::onlyTrue && filter.includedShares() == NO_SHARES && std::all_of(ancestors.begin(), ancestors.end(), [this](handle a) { shared_ptr node = getNodeByHandle_internal(NodeHandle().set6byte(a)); return node && node->isSensitiveInherited(); })) { return sharedNode_vector(); } // db look-up vector> nodesFromTable; if (!mTable->searchNodes(filter, order, nodesFromTable, cancelFlag, page)) { return sharedNode_vector(); } sharedNode_vector nodes = processUnserializedNodes(nodesFromTable, cancelFlag); return nodes; } sharedNode_vector NodeManager::getNodesWithInShares() { LockGuard g(mMutex); return getNodesWithSharesOrLink_internal(ShareType_t::IN_SHARES); } sharedNode_vector NodeManager::getNodesWithOutShares() { LockGuard g(mMutex); return getNodesWithSharesOrLink_internal(ShareType_t::OUT_SHARES); } sharedNode_vector NodeManager::getNodesWithPendingOutShares() { LockGuard g(mMutex); return getNodesWithSharesOrLink_internal(ShareType_t::PENDING_OUTSHARES); } sharedNode_vector NodeManager::getNodesWithLinks() { LockGuard g(mMutex); return getNodesWithSharesOrLink_internal(ShareType_t::LINK); } sharedNode_vector NodeManager::getNodesByFingerprint(const FileFingerprint& fingerprint, const bool excludeMtime) { LockGuard g(mMutex); return getNodesByFingerprint_internal(fingerprint, excludeMtime); } sharedNode_vector NodeManager::getNodesByFingerprint_internal(const FileFingerprint& fingerprint, const bool excludeMtime) { assert(mMutex.owns_lock()); sharedNode_vector nodes; if (!mTable || mNodes.empty()) { assert(false); return nodes; } std::set fpLoaded; std::vector> nodesFromTable; // Take first nodes in RAM auto p = mFingerPrintsNoMtime.equal_range(&fingerprint); for (auto it = p.first; it != p.second; ++it) { const auto node = static_cast(*it); if (excludeMtime || (node->mtime == fingerprint.mtime)) { fpLoaded.emplace(node->nodeHandle()); std::shared_ptr sharedNode = node->mNodePosition->second.getNodeInRam(); assert(sharedNode && "Node loaded at fingerprint map should have a node in RAM "); nodes.push_back(std::move(sharedNode)); } } // If all fingerprints are loaded at DB, it isn't necessary search in DB if (mFingerPrintsNoMtime.allFingerprintsAreLoaded(&fingerprint)) { return nodes; } // Look for nodes at DB std::string fingerprintNoMtimeStr; fingerprint.FileFingerprint::serializeExcludingMtime(&fingerprintNoMtimeStr); mTable->getNodesByFingerprintNoMtime(fingerprintNoMtimeStr, nodesFromTable); /* * It’s important to add the `fingerprint` received as param to `mAllFingerprintsLoaded` before * processing the retrieved nodes from the DB, as upon node unserialization, the LRU cache will * properly manage them by inserting into `mFingerPrintsNoMtime` and removing them from both * `mFingerPrintsNoMtime` and `mAllFingerprintsLoaded`. */ mFingerPrintsNoMtime.setAllFingerprintLoaded(&fingerprint); if (nodesFromTable.size()) { for (const auto& nodeIt: nodesFromTable) { // avoid to load already loaded nodes (found at mFingerPrints) if (fpLoaded.find(nodeIt.first) == fpLoaded.end()) { std::shared_ptr node; auto it = mNodes.find(nodeIt.first); if (it != mNodes.end()) { node = it->second.getNodeInRam(); } if (!node) { node = getNodeFromNodeSerialized(nodeIt.second); if (!node) { nodes.clear(); return nodes; } } if (excludeMtime || node->mtime == fingerprint.mtime) { nodes.push_back(std::move(node)); } } } } return nodes; } sharedNode_vector NodeManager::getNodesByOrigFingerprint(const std::string &fingerprint, Node *parent) { LockGuard g(mMutex); return getNodesByOrigFingerprint_internal(fingerprint, parent); } sharedNode_vector NodeManager::getNodesByOrigFingerprint_internal(const std::string &fingerprint, Node *parent) { assert(mMutex.owns_lock()); sharedNode_vector nodes; if (!mTable || mNodes.empty()) { assert(false); return nodes; } std::vector> nodesFromTable; mTable->getNodesByOrigFingerprint(fingerprint, nodesFromTable); nodes = processUnserializedNodes(nodesFromTable, parent ? parent->nodeHandle() : NodeHandle(), CancelToken()); return nodes; } std::shared_ptr NodeManager::getNodeByFingerprint(FileFingerprint &fingerprint) { LockGuard g(mMutex); return getNodeByFingerprint_internal(fingerprint); } std::shared_ptr NodeManager::getNodeByFingerprint_internal(FileFingerprint &fingerprint) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { assert(false); return nullptr; } auto it = mFingerPrintsNoMtime.find(&fingerprint); if (it != mFingerPrintsNoMtime.end()) { const auto n = static_cast(*it); assert(n); return n->mNodePosition->second.getNodeInRam(); } NodeSerialized nodeSerialized; std::string fingerprintString; fingerprint.FileFingerprint::serialize(&fingerprintString); NodeHandle handle; mTable->getNodeByFingerprint(fingerprintString, nodeSerialized, handle); auto itNode = mNodes.find(handle); std::shared_ptr node = itNode != mNodes.end() ? itNode->second.getNodeInRam() : nullptr; if (!node && nodeSerialized.mNode.size()) // nodes with that fingerprint found in DB { node = getNodeFromNodeSerialized(nodeSerialized); } return node; } std::shared_ptr NodeManager::childNodeByNameType(const Node* parent, const std::string &name, nodetype_t nodeType) { LockGuard g(mMutex); return childNodeByNameType_internal(parent, name, nodeType); } std::shared_ptr NodeManager::childNodeByNameType_internal(const Node* parent, const std::string &name, nodetype_t nodeType) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { assert(false); return nullptr; } // mAllChildrenHandleLoaded = false -> if not found, need check DB // mAllChildrenHandleLoaded = true -> if all children have a pointer, no need to check DB bool allChildrenLoaded = parent->mNodePosition->second.mAllChildrenHandleLoaded; if (allChildrenLoaded && !parent->mNodePosition->second.mChildren) { return nullptr; // valid case } if (parent->mNodePosition->second.mChildren) { for (const auto& itNode : *parent->mNodePosition->second.mChildren) { if (itNode.second) { shared_ptr node = itNode.second->getNodeInRam(); if (node && node->type == nodeType && name == node->displayname()) { return node; } else if (!node) { // If not all child nodes have been loaded, check the DB allChildrenLoaded = false; } } else { allChildrenLoaded = false; } } } if (allChildrenLoaded) { return nullptr; // There is no match } std::pair nodeSerialized; if (!mTable->childNodeByNameType(parent->nodeHandle(), name, nodeType, nodeSerialized)) { return nullptr; // Not found at DB either } assert(!getNodeInRAM(nodeSerialized.first)); // not loaded yet return getNodeFromNodeSerialized(nodeSerialized.second); } sharedNode_vector NodeManager::getRootNodes() { LockGuard g(mMutex); return getRootNodes_internal(); } sharedNode_vector NodeManager::getRootNodes_internal() { assert(mMutex.owns_lock()); sharedNode_vector nodes; if (!mTable) { assert(false); return nodes; } if (mNodes.size()) // nodes already loaded from DB { const auto loadVault = [this, &nodes]() -> void { auto vaultIt = rootnodes.mRootNodes.find(VAULTNODE); if (vaultIt != rootnodes.mRootNodes.end() && vaultIt->second) { nodes.push_back(vaultIt->second); } else { LOG_err << "Vault node should be defined (except logged into folder link)"; mClient.sendevent(800032, "Vault node is not present", 0); assert(vaultIt != rootnodes.mRootNodes.end() && "Vault node is not defined"); assert(vaultIt->second && "Vault node is defined but it is null"); } }; if (mClient.isClientType(MegaClient::ClientType::DEFAULT)) { auto rootIt = rootnodes.mRootNodes.find(ROOTNODE); if (rootIt != rootnodes.mRootNodes.end() && rootIt->second) { nodes.push_back(rootIt->second); } else { LOG_err << "Root node should be defined"; mClient.sendevent(800031, "Root node is not present", 0); assert(rootIt != rootnodes.mRootNodes.end() && "Root node is not defined"); assert(rootIt->second && "Root node is defined but it is null"); } if (!mClient.loggedIntoFolder()) { loadVault(); auto rubbishIt = rootnodes.mRootNodes.find(RUBBISHNODE); if (rubbishIt != rootnodes.mRootNodes.end() && rubbishIt->second) { nodes.push_back(rubbishIt->second); } else { LOG_err << "Rubbishbin node should be defined (except logged into folder link)"; mClient.sendevent(800033, "Rubbishbin node is not present", 0); assert(rubbishIt != rootnodes.mRootNodes.end() && "Rubbishbin node is not defined"); assert(rubbishIt->second && "Rubbishbin node is defined but it is null"); } } } else if (mClient.isClientType(MegaClient::ClientType::PASSWORD_MANAGER)) { loadVault(); } else { LOG_warn << "Unexpected MegaClient type " << static_cast(mClient.getClientType()); assert(false); } } else // nodes not loaded yet { if (mClient.loggedIntoFolder()) { NodeSerialized nodeSerialized; mTable->getNode(rootnodes.files, nodeSerialized); std::shared_ptr n = getNodeFromNodeSerialized(nodeSerialized); if (!n) { return nodes; } setrootnode_internal(n); nodes.push_back(std::move(n)); } else { std::vector> nodesFromTable; mTable->getRootNodes(nodesFromTable); for (const auto& nHandleSerialized : nodesFromTable) { assert(!getNodeInRAM(nHandleSerialized.first)); std::shared_ptr n = getNodeFromNodeSerialized(nHandleSerialized.second); if (!n) { nodes.clear(); return nodes; } setrootnode_internal(n); nodes.push_back(std::move(n)); } } } return nodes; } sharedNode_vector NodeManager::getNodesWithSharesOrLink_internal(ShareType_t shareType) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { //assert(false); return sharedNode_vector(); } std::vector> nodesFromTable; mTable->getNodesWithSharesOrLink(nodesFromTable, shareType); return processUnserializedNodes(nodesFromTable); } shared_ptr NodeManager::getNodeFromNodeSerialized(const NodeSerialized &nodeSerialized) { assert(mMutex.owns_lock()); shared_ptr node = unserializeNode(&nodeSerialized.mNode, false); if (!node) { assert(false); LOG_err << "Failed to unserialize node. Notifying the error to user"; mClient.fatalError(ErrorReason::REASON_ERROR_UNSERIALIZE_NODE); return nullptr; } setNodeCounter(node, NodeCounter(nodeSerialized.mNodeCounter), false, nullptr); // do not automatically try to reload the account if we can't unserialize. // (1) we might go around in circles downloading the account over and over, DDOSing MEGA, because we get the same data back each time // (2) this function has no idea what is going on in the rest of the program. // Reloading Nodes may be a terrible idea depending on what operations are in progress and calling this function. // (3) Reloading nodes will take a long time, and in the meantime we will be operating without this node anyway. So, the damage is already done (eg, with syncs) and reloading is adding extra complications to diagnosis // (4) There should be an event issued here, so we can gather statistics on whether this happens or not, or how often // (5) Likely, reloading from here is completely untested. return node; } void NodeManager::setNodeCounter(std::shared_ptr n, const NodeCounter &counter, bool notify, sharedNode_vector* nodesToReport) { assert(mMutex.owns_lock()); n->setCounter(counter); if (notify) { n->changed.counter = true; notifyNode_internal(n, nodesToReport); } } void NodeManager::updateTreeCounter(std::shared_ptr origin, NodeCounter nc, OperationType operation, sharedNode_vector* nodesToReport) { assert(mMutex.owns_lock()); while (origin) { NodeCounter ancestorCounter = origin->getCounter(); switch (operation) { case INCREASE: ancestorCounter += nc; break; case DECREASE: ancestorCounter -= nc; break; } setNodeCounter(origin, ancestorCounter, true, nodesToReport); origin = origin->parent; } } NodeCounter NodeManager::calculateNodeCounter(const NodeHandle& nodehandle, nodetype_t parentType, std::shared_ptr node, bool isInRubbish) { assert(mMutex.owns_lock()); NodeCounter nc; if (!mTable) { assert(false); return nc; } m_off_t nodeSize = 0u; uint64_t flags = 0; nodetype_t nodeType = TYPE_UNKNOWN; if (node) { nodeType = node->type; nodeSize = node->size; flags = node->getDBFlags(); } else { if (!mTable->getNodeSizeTypeAndFlags(nodehandle, nodeSize, nodeType, flags)) { assert(false); return nc; } std::bitset bitset(flags); flags = Node::getDBFlags(flags, isInRubbish, parentType == FILENODE, bitset.test(Node::FLAGS_IS_MARKED_SENSTIVE)); } std::map* children = nullptr; auto it = mNodes.find(nodehandle); if (it != mNodes.end()) { children = it->second.mChildren.get(); } if (children) { for (auto& itNode : *children) { shared_ptr child = itNode.second ? itNode.second->getNodeInRam() : nullptr; nc += calculateNodeCounter(itNode.first, nodeType, child, isInRubbish); } } if (nodeType == FILENODE) { bool isVersion = parentType == FILENODE; if (isVersion) { nc.versions++; nc.versionStorage += nodeSize; } else { nc.files++; nc.storage += nodeSize; } } else if (nodeType == FOLDERNODE) { nc.folders++; } if (node) { setNodeCounter(node, nc, false, nullptr); } mTable->updateCounterAndFlags(nodehandle, flags, nc.serialize()); return nc; } std::vector NodeManager::getFavouritesNodeHandles(NodeHandle node, uint32_t count) { LockGuard g(mMutex); return getFavouritesNodeHandles_internal(node, count); } std::vector NodeManager::getFavouritesNodeHandles_internal(NodeHandle node, uint32_t count) { assert(mMutex.owns_lock()); std::vector nodeHandles; if (!mTable || mNodes.empty()) { assert(false); return nodeHandles; } mTable->getFavouritesHandles(node, count, nodeHandles); return nodeHandles; } size_t NodeManager::getNumberOfChildrenFromNode(NodeHandle parentHandle) { LockGuard g(mMutex); return getNumberOfChildrenFromNode_internal(parentHandle); } size_t NodeManager::getNumberOfChildrenFromNode_internal(NodeHandle parentHandle) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { assert(false); return 0; } auto parentIt = mNodes.find(parentHandle); if (parentIt != mNodes.end() && parentIt->second.mAllChildrenHandleLoaded) { return parentIt->second.mChildren ? parentIt->second.mChildren->size() : 0; } return static_cast(mTable->getNumberOfChildren(parentHandle)); } size_t NodeManager::getNumberOfChildrenByType(NodeHandle parentHandle, nodetype_t nodeType) { LockGuard g(mMutex); return getNumberOfChildrenByType_internal(parentHandle, nodeType); } size_t NodeManager::getNumberOfChildrenByType_internal(NodeHandle parentHandle, nodetype_t nodeType) { assert(mMutex.owns_lock()); if (!mTable || mNodes.empty()) { assert(false); return 0; } assert(nodeType == FILENODE || nodeType == FOLDERNODE); return static_cast(mTable->getNumberOfChildrenByType(parentHandle, nodeType)); } bool NodeManager::isAncestor(NodeHandle nodehandle, NodeHandle ancestor, CancelToken cancelFlag) { LockGuard g(mMutex); return isAncestor_internal(nodehandle, ancestor, cancelFlag); } bool NodeManager::isAncestor_internal(NodeHandle nodehandle, NodeHandle ancestor, CancelToken cancelFlag) { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return false; } return mTable->isAncestor(nodehandle, ancestor, cancelFlag); } void NodeManager::removeChanges() { LockGuard g(mMutex); removeChanges_internal(); } void NodeManager::removeChanges_internal() { assert(mMutex.owns_lock()); if (mNodesInRam <= mCacheLRU.size() + rootnodes.mRootNodes.size()) { for (auto& node: mCacheLRU) { memset(&(node->changed), 0, sizeof node->changed); } for (auto& [_, node]: rootnodes.mRootNodes) { memset(&(node->changed), 0, sizeof node->changed); } } else { for (auto& [_, nodeManagerNode]: mNodes) { std::shared_ptr node = nodeManagerNode.getNodeInRam(false); if (node) { memset(&(node->changed), 0, sizeof node->changed); } } } } void NodeManager::cleanNodes() { LockGuard g(mMutex); cleanNodes_internal(); } void NodeManager::cleanNodes_internal() { assert(mMutex.owns_lock()); mFingerPrintsNoMtime.clear(); mNodes.clear(); mCacheLRU.clear(); mNodeToWriteInDb.reset(); mNodeNotify.clear(); mNodePendingApplyKeys.clear(); rootnodes.clear(); if (mTable) mTable->removeNodes(); mInitialized = false; mAppliedKeyNodeCount = 0; mNodesInRam = 0; } std::shared_ptr NodeManager::getNodeFromBlob(const std::string* nodeSerialized) { LockGuard g(mMutex); return getNodeFromBlob_internal(nodeSerialized); } std::shared_ptr NodeManager::getNodeFromBlob_internal(const std::string* nodeSerialized) { assert(mMutex.owns_lock()); return unserializeNode(nodeSerialized, true); } // parse serialized node and return Node object - updates nodes hash and parent // mismatch vector shared_ptr NodeManager::unserializeNode(const std::string *d, bool fromOldCache) { assert(mMutex.owns_lock()); std::list> ownNewshares; if (shared_ptr n = Node::unserialize(mClient, d, fromOldCache, ownNewshares)) { auto pair = mNodes.emplace(n->nodeHandle(), NodeManagerNode(*this, n->nodeHandle())); // The NodeManagerNode could have been added in the initial fetch nodes (without session) // Now, the node is loaded from DB, NodeManagerNode is updated with correct values auto& nodePosition = pair.first; nodePosition->second.setNode(n); n->mNodePosition = nodePosition; insertNodeCacheLRU_internal(n); // setparent() skiping update of node counters, since they are already calculated in DB // In DB migration we have to calculate them as they aren't calculated previously n->setparent(getNodeByHandle_internal(n->parentHandle()), fromOldCache); // recreate node members related to shares (no need to write to DB, // since we just loaded the node from DB and has no changes) for (auto& share : ownNewshares) { mClient.mergenewshare(share.get(), false, true); } return n; } return nullptr; } void NodeManager::applyKeys() { LockGuard g(mMutex); applyKeys_internal(); } void NodeManager::addNodePendingApplykey(std::shared_ptr node) { LockGuard g(mMutex); if (isFromRootNodeType(*node)) { return; } auto [it, inserted] = mNodePendingApplyKeys.emplace(node->nodehandle, node); if (!inserted) { if (auto existing = it->second.lock()) { // Same handle already tracked; ensure it's the same instance. assert(existing.get() == node.get() && "Same handle with different Node instance in mNodePendingApplyKeys"); } else { // Expired entry: replace with current node. assert(false && "Element removed at ~Node, no empty element should be present"); it->second = node; } } } void NodeManager::applyKeys_internal() { assert(mMutex.owns_lock()); for (auto it = mNodePendingApplyKeys.begin(); it != mNodePendingApplyKeys.end();) { auto& [_, pendingNode] = *it; auto currentIt = it; ++it; if (auto sharedNode = pendingNode.lock()) { if (sharedNode->keyApplied()) { mNodePendingApplyKeys.erase(currentIt); continue; } // Node::applykey already remove element from mNodePendingApplyKeys if key has been // applied sharedNode->applykey(); } else { mNodePendingApplyKeys.erase(currentIt); } } #ifdef DEBUG // In case of folder links, root node is not from type rootnode and it is decryptable unsigned rootNodeUndecrypted = (!getRootNodeFiles().isUndef() && rootnodes.mRootNodes[ROOTNODE]->type == ROOTNODE) ? 1 : 0; unsigned noKeyExpected = rootNodeUndecrypted + (getRootNodeVault().isUndef() ? 0 : 1) + (getRootNodeRubbish().isUndef() ? 0 : 1); if (const auto unexpectedCount = mNodesInRam - (static_cast(mAppliedKeyNodeCount) + noKeyExpected) != mNodePendingApplyKeys.size(); unexpectedCount) { LOG_warn << "applyKeys_internal: " << " Nodes in Ram: " << mNodesInRam << ", Applied Keys: " << mAppliedKeyNodeCount << ", NoKeys: " << noKeyExpected << ", mNodePendingApplyKeys: " << mNodePendingApplyKeys.size(); assert(mNodesInRam - (static_cast(mAppliedKeyNodeCount) + noKeyExpected) == mNodePendingApplyKeys.size() && "Unexpected num nodes with key pending to be applied in RAM"); } #endif } void NodeManager::notifyPurge() { mClient.applykeys(); // only lock to get the nodes to report sharedNode_vector nodesToReport; { LockGuard g(mMutex); nodesToReport.swap(mNodeNotify); } // we do our reporting outside the lock, as it involves callbacks to the client if (!nodesToReport.empty()) { if (!mClient.fetchingnodes) { assert(!mMutex.owns_lock()); mClient.app->nodes_updated(&nodesToReport, static_cast(nodesToReport.size())); } LockGuard g(mMutex); // Let the client adapter know that nodes have been updated. mClient.mClientAdapter.updated(nodesToReport); TransferDbCommitter committer(mClient.tctable); unsigned removed = 0; unsigned added = 0; // check all notified nodes for removed status and purge for (size_t i = 0; i < nodesToReport.size(); i++) { std::shared_ptr n = nodesToReport[i]; if (n->changed.removed) { // remove inbound share if (n->inshare) { n->inshare->user->sharing.erase(n->nodehandle); mClient.notifyuser(n->inshare->user); } } else { n->notified = false; memset(&(n->changed), 0, sizeof(n->changed)); n->changed.modifiedByThisClient = false; } if (!mTable) { assert(false); return; } if (n->changed.removed) { NodeHandle h = n->nodeHandle(); // This will also require notifying/updating parents back to the root. Report and // update them in this same operation, to ensure consistency in case of commit updateTreeCounter(n->parent, n->getCounter(), DECREASE, &nodesToReport); if (n->parent) { // optimization: if the parent has already been deleted, the relationship // of children with their parent has been removed by the parent already // so we can avoid lookups for non existing parent handle. removeChild(n->parent.get(), h); } sharedNode_list children = getChildren(n.get()); for (auto& child : children) { child->parent = nullptr; } removeFingerprint(n.get()); removeNodeCacheLRU_internal(n.get()); mNodes.erase(n->mNodePosition); n->mNodePosition = mNodes.end(); mTable->remove(h); removed += 1; } else { putNodeInDb(n.get()); added += 1; } } if (removed) { LOG_verbose << mClient.clientname << "Removed " << removed << " nodes from database"; } if (added) { LOG_verbose << mClient.clientname << "Added " << added << " nodes to database"; } } } bool NodeManager::hasCacheLoaded() { LockGuard g(mMutex); return mNodes.size() > 0; } bool NodeManager::loadNodes() { LockGuard g(mMutex); return loadNodes_internal(); } bool NodeManager::loadNodes_internal() { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return false; } sharedNode_vector allRootNodes = getRootNodes_internal(); for (const auto& node: allRootNodes) { getChildren_internal(node.get()); } mInitialized = true; return true; } shared_ptr NodeManager::getNodeInRAM(NodeHandle handle) { assert(mMutex.owns_lock()); auto itNode = mNodes.find(handle); if (itNode != mNodes.end()) { std::shared_ptr node = itNode->second.getNodeInRam(); return node; } return nullptr; } void NodeManager::saveNodeInRAM(std::shared_ptr node, bool isRootnode, MissingParentNodes& missingParentNodes) { assert(mMutex.owns_lock()); auto pair = mNodes.emplace(node->nodeHandle(), NodeManagerNode(*this, node->nodeHandle())); // The NodeManagerNode could have been added by NodeManager::addChild() but, in that case, mNode would be invalid auto& nodePosition = pair.first; nodePosition->second.setNode(node); nodePosition->second.mAllChildrenHandleLoaded = true; // Receive a new node, children aren't received yet or they are stored a mNodesWithMissingParents node->mNodePosition = nodePosition; insertNodeCacheLRU_internal(node); // In case of rootnode, no need to add to missingParentNodes if (!isRootnode) { std::shared_ptr parent; if ((parent = getNodeByHandle_internal(node->parentHandle()))) { node->setparent(parent); } else { missingParentNodes[node->parentHandle()].insert(node); } } else { setrootnode_internal(node); } auto it = missingParentNodes.find(node->nodeHandle()); if (it != missingParentNodes.end()) { for (auto& n : it->second) { n->setparent(node); } missingParentNodes.erase(it); } } bool NodeManager::isRootNode(NodeHandle h) const { LockGuard g(mMutex); return rootnodes.isRootNode(h); } int NodeManager::getNumVersions(NodeHandle nodeHandle) { LockGuard g(mMutex); Node *node = getNodeByHandle_internal(nodeHandle).get(); if (!node || node->type != FILENODE) { return 0; } return static_cast(node->getCounter().versions) + 1; } NodeHandle NodeManager::getRootNodeFiles() const { LockGuard g(mMutex); return rootnodes.files; } NodeHandle NodeManager::getRootNodeVault() const { LockGuard g(mMutex); return rootnodes.vault; } NodeHandle NodeManager::getRootNodeRubbish() const { LockGuard g(mMutex); return rootnodes.rubbish; } void NodeManager::setRootNodeFiles(NodeHandle h) { LockGuard g(mMutex); rootnodes.files = h; } void NodeManager::setRootNodeVault(NodeHandle h) { LockGuard g(mMutex); rootnodes.vault = h; } void NodeManager::setRootNodeRubbish(NodeHandle h) { LockGuard g(mMutex); rootnodes.rubbish = h; } void NodeManager::checkOrphanNodes(MissingParentNodes& nodesWithMissingParent) { // we don't actually use any members here, so no need to lock. (well, just mClient, not part of our data structure) assert(!mMutex.owns_lock()); // detect if there's any orphan node and report to API for (const auto& it : nodesWithMissingParent) { for (const auto& orphan : it.second) { // For inshares, we get sent the inshare node including its parent handle // even though we will never actually get that parent node (unless the share is nested) // So, don't complain about those ones. Just about really un-attached subtrees. if (!orphan->inshare) { // At this point, all nodes have been already parsed, so the parent should never arrive. // The orphan node won't be reachable anymore, and could have a whole tree inside. // This can happen if the local instance of the SDK deletes a folder, receives the response // from the server via the cs channel, and after that it receives action packets related to // things that happened inside the deleted folder. // This race condition should disappear when the local cache is exclusively driven via // action packets and Speculative Instant Completion (SIC) is gone. TreeProcDel td; mClient.proctree(orphan, &td); // TODO: Change this warning to an error when Speculative Instant Completion (SIC) is gone LOG_warn << mClient.clientname << "Detected orphan node: " << toNodeHandle(orphan->nodehandle) << " Parent: " << toNodeHandle(orphan->parentHandle()); mClient.sendevent(99455, "Orphan node(s) detected"); // If we didn't get all the parents of all the (not inshare) nodes, // then the API is sending us inconsistent data, // or we have a bug processing it. Please investigate assert(false); } } } } void NodeManager::initCompleted() { LockGuard g(mMutex); initCompleted_internal(); } void NodeManager::dropSearchDBIndexes() { assert(mNodeNotify.empty()); if (!mTable || mNodesInRam > 0) { LOG_err << "DB isn't opened yet or nodes has been already loaded"; return; } mTable->dropSearchDBIndexes(); } void NodeManager::dropLexicographicDBIndexes() { assert(mNodeNotify.empty()); if (!mTable || mNodesInRam > 0) { LOG_err << "DB isn't opened yet or nodes has been already loaded"; return; } mTable->dropLexicographicDBIndexes(); } std::shared_ptr NodeManager::getNodeFromNodeManagerNode(NodeManagerNode& nodeManagerNode) { LockGuard g(mMutex); shared_ptr node = nodeManagerNode.getNodeInRam(); if (!node) { node = getNodeFromDataBase(nodeManagerNode.getNodeHandle()); } return node; } void NodeManager::insertNodeCacheLRU(std::shared_ptr node) { LockGuard g(mMutex); insertNodeCacheLRU_internal(node); } void NodeManager::increaseNumNodesInRam() { ++mNodesInRam; } void NodeManager::decreaseNumNodesInRam() { --mNodesInRam; } void NodeManager::increaseNumNodesAppliedKey() { ++mAppliedKeyNodeCount; } void NodeManager::decreaseNumNodesAppliedKey() { --mAppliedKeyNodeCount; } uint64_t NodeManager::getCacheLRUMaxSize() const { return mCacheLRUMaxSize; } void NodeManager::setCacheLRUMaxSize(uint64_t cacheLRUMaxSize) { LockGuard g(mMutex); mCacheLRUMaxSize = cacheLRUMaxSize; unLoadNodeFromCacheLRU(); // check if it's necessary unload nodes } uint64_t NodeManager::getNumNodesAtCacheLRU() const { LockGuard g(mMutex); return mCacheLRU.size(); } void NodeManager::initCompleted_internal() { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return; } sharedNode_vector rootNodes = getRootNodesAndInshares(); for (auto& node: rootNodes) { if (node) { calculateNodeCounter(node->nodeHandle(), TYPE_UNKNOWN, node, node->type == RUBBISHNODE); } else { reportNullRootNodes(rootNodes.size()); } } mTable->createIndexes(mClient.mEnableSearchDBIndexes, mClient.mEnableLexicographicDBIndexes); mInitialized = true; } bool NodeManager::ready() { return mInitialized; } bool NodeManager::isFromRootNodeType(const Node& node) const { return node.type == ROOTNODE || node.type == RUBBISHNODE || node.type == VAULTNODE; } void NodeManager::removeNodePendingApplyKeys(const Node* node) { LockGuard g(mMutex); removeNodePendingApplyKeys_internal(node); } void NodeManager::insertNodeCacheLRU_internal(std::shared_ptr node) { assert(mMutex.owns_lock() && "Mutex should be locked by this thread"); removeNodeCacheLRU_internal(node.get()); node->mNodePosition->second.mLRUPosition = mCacheLRU.insert(mCacheLRU.begin(), node); unLoadNodeFromCacheLRU(); // check if it's necessary unload nodes // setfingerprint again to force to insert into NodeManager::mFingerPrints // only nodes in LRU are at NodeManager::mFingerPrints if (node->mFingerPrintPosition == invalidFingerprintPos()) { node->setfingerprint(); } } void NodeManager::unLoadNodeFromCacheLRU() { assert(mMutex.owns_lock() && "Mutex should be locked by this thread"); while (mCacheLRU.size() > mCacheLRUMaxSize) { std::shared_ptr node = mCacheLRU.back(); removeFingerprint(node.get(), true); node->mNodePosition->second.mLRUPosition = invalidCacheLRUPos(); mCacheLRU.pop_back(); } } void NodeManager::removeNodeCacheLRU_internal(Node* node) { assert(mMutex.owns_lock() && "Mutex should be locked by this thread"); if (!node || node->mNodePosition == mNodes.end() || node->mNodePosition->second.mLRUPosition == mCacheLRU.end()) { return; } mCacheLRU.erase(node->mNodePosition->second.mLRUPosition); node->mNodePosition->second.mLRUPosition = invalidCacheLRUPos(); } void NodeManager::removeNodePendingApplyKeys_internal(const Node* node) { assert(mMutex.owns_lock() && "Mutex should be locked by this thread"); mNodePendingApplyKeys.erase(node->nodehandle); } NodeCounter NodeManager::getCounterOfRootNodes() { LockGuard g(mMutex); return getCounterOfRootNodes_internal(); } NodeCounter NodeManager::getCounterOfRootNodes_internal() { assert(mMutex.owns_lock()); NodeCounter c; // if not logged in yet, node counters are not available if (mNodes.empty()) { assert((rootnodes.files.isUndef() && rootnodes.vault.isUndef() && rootnodes.rubbish.isUndef()) || (mClient.loggedIntoFolder())); return c; } sharedNode_vector rootNodes = getRootNodes_internal(); for (auto& node: rootNodes) { if (node) { c += node->getCounter(); } else { reportNullRootNodes(rootNodes.size()); } } return c; } void NodeManager::updateCounter(std::shared_ptr n, std::shared_ptr oldParent) { LockGuard g(mMutex); updateCounter_internal(n, oldParent); } void NodeManager::updateCounter_internal(std::shared_ptr n, std::shared_ptr oldParent) { assert(mMutex.owns_lock()); NodeCounter nc = n->getCounter(); updateTreeCounter(oldParent, nc, DECREASE, nullptr); // if node is a new version if (n->parent && n->parent->type == FILENODE) { if (nc.files > 0) { assert(nc.files == 1); // discount the old version, previously counted as file nc.files--; nc.storage -= n->size; nc.versions++; nc.versionStorage += n->size; setNodeCounter(n, nc, true, nullptr); } } // newest element at chain versions has been removed, the second one element is the newest now. Update node counter properly else if (oldParent && oldParent->type == FILENODE && n->parent && n->parent->type != FILENODE) { nc.files++; nc.storage += n->size; nc.versions--; nc.versionStorage -= n->size; setNodeCounter(n, nc, true, nullptr); } updateTreeCounter(n->parent, nc, INCREASE, nullptr); } FingerprintPosition NodeManager::insertFingerprint(Node *node) { LockGuard g(mMutex); return insertFingerprint_internal(node); } FingerprintPosition NodeManager::insertFingerprint_internal(Node* node) { assert(mMutex.owns_lock()); assert(node->mFingerPrintPosition == invalidFingerprintPos()); if (node->type != FILENODE) { return mFingerPrintsNoMtime.end(); } // Only insert fingerprint for nodes currently cached in LRU, otherwise nodes could be destroyed // leaving dangling pointers in `mFingerPrintsNoMtime` if (mNodeToWriteInDb.get() == node) { return mFingerPrintsNoMtime.end(); } if (auto it = mNodes.find(node->nodeHandle()); it == mNodes.end() || it->second.mLRUPosition == invalidCacheLRUPos()) { return mFingerPrintsNoMtime.end(); } return mFingerPrintsNoMtime.insert(node); } void NodeManager::removeFingerprint(Node *node, bool unloadNode) { LockGuard g(mMutex); removeFingerprint_internal(node, unloadNode); } void NodeManager::removeFingerprint_internal(Node *node, bool unloadNode) { assert(mMutex.owns_lock()); if (node->type == FILENODE && node->mFingerPrintPosition != mFingerPrintsNoMtime.end()) // remove from mFingerPrints { mFingerPrintsNoMtime.erase(node->mFingerPrintPosition); node->mFingerPrintPosition = mFingerPrintsNoMtime.end(); if (unloadNode) { FileFingerprint fingerPrint = *node; mFingerPrintsNoMtime.removeAllFingerprintLoaded(&fingerPrint); } } } FingerprintPosition NodeManager::invalidFingerprintPos() { // no locking for this one, it returns a constant return mFingerPrintsNoMtime.end(); } std::list >::const_iterator NodeManager::invalidCacheLRUPos() const { // no locking for this one, it returns a constant return mCacheLRU.end(); } void NodeManager::dumpNodes() { LockGuard g(mMutex); dumpNodes_internal(); } void NodeManager::dumpNodes_internal() { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return; } for (auto &it : mNodes) { shared_ptr node = getNodeFromNodeManagerNode(it.second); if (node) { putNodeInDb(node.get()); } } mTable->createIndexes(mClient.mEnableSearchDBIndexes, mClient.mEnableLexicographicDBIndexes); mInitialized = true; } void NodeManager::saveNodeInDb(Node *node) { LockGuard g(mMutex); saveNodeInDb_internal(node); } void NodeManager::saveNodeInDb_internal(Node *node) { assert(mMutex.owns_lock()); if (!mTable) { assert(false); return; } putNodeInDb(node); if (mNodeToWriteInDb) // not to be kept in memory { assert(mNodeToWriteInDb.get() == node); mNodeToWriteInDb.reset(); } } uint64_t NodeManager::getNumberNodesInRam() const { LockGuard g(mMutex); return mNodesInRam; } long long NodeManager::getNumNodesKeyApplied() const { LockGuard g(mMutex); return mAppliedKeyNodeCount; } void NodeManager::addChild(NodeHandle parent, NodeHandle child, Node* node) { LockGuard g(mMutex); addChild_internal(parent, child, node); } void NodeManager::addChild_internal(NodeHandle parent, NodeHandle child, Node* node) { assert(mMutex.owns_lock()); auto pair = mNodes.emplace(parent, NodeManagerNode(*this, parent)); // The NodeManagerNode could have been added in add node, only update the child if (!pair.first->second.mChildren) { pair.first->second.mChildren = std::make_unique>(); } NodeManagerNode *nodeManagerNode = nullptr; if (node) { assert(node->mNodePosition != mNodes.end()); nodeManagerNode = &node->mNodePosition->second; } (*pair.first->second.mChildren)[child] = nodeManagerNode; } void NodeManager::removeChild(Node* parent, NodeHandle child) { LockGuard g(mMutex); removeChild_internal(parent, child); } void NodeManager::removeChild_internal(Node* parent, NodeHandle child) { assert(mMutex.owns_lock()); assert(parent->mNodePosition->second.mChildren); if (parent->mNodePosition->second.mChildren) { parent->mNodePosition->second.mChildren->erase(child); } } shared_ptr NodeManager::getNodeFromDataBase(NodeHandle handle) { assert(mMutex.owns_lock()); if (!mTable) { assert(!mClient.loggedin()); return nullptr; } shared_ptr node = nullptr; NodeSerialized nodeSerialized; if (mTable->getNode(handle, nodeSerialized)) { node = getNodeFromNodeSerialized(nodeSerialized); } return node; } sharedNode_vector NodeManager::getRootNodesAndInshares() { assert(mMutex.owns_lock()); sharedNode_vector allRootNodes; allRootNodes = getRootNodes_internal(); if (!mClient.loggedIntoFolder()) // logged into user's account: incoming shared folders { sharedNode_vector inshares = mClient.getInShares(); allRootNodes.insert(allRootNodes.end(), inshares.begin(), inshares.end()); } return allRootNodes; } sharedNode_vector NodeManager::processUnserializedNodes(const vector>& nodesFromTable, CancelToken cancelFlag) { assert(mMutex.owns_lock()); sharedNode_vector nodes; for (const auto& nodeIt : nodesFromTable) { // Check pointer and value if (cancelFlag.isCancelled()) break; shared_ptr n = getNodeInRAM(nodeIt.first); if (!n) { n = getNodeFromNodeSerialized(nodeIt.second); if (!n) { nodes.clear(); return nodes; } } nodes.push_back(n); } return nodes; } sharedNode_vector NodeManager::processUnserializedNodes(const std::vector >& nodesFromTable, NodeHandle ancestorHandle, CancelToken cancelFlag) { assert(mMutex.owns_lock()); sharedNode_vector nodes; for (const auto& nodeIt : nodesFromTable) { // Check pointer and value if (cancelFlag.isCancelled()) break; std::shared_ptr n = getNodeInRAM(nodeIt.first); if (!ancestorHandle.isUndef()) // filter results by subtree (nodeHandle) { bool skip = n ? !n->isAncestor(ancestorHandle) : !mTable->isAncestor(nodeIt.first, ancestorHandle, cancelFlag); if (skip) continue; } if (!n) { n = std::shared_ptr (getNodeFromNodeSerialized(nodeIt.second)); if (!n) { nodes.clear(); return nodes; } } nodes.push_back(std::move(n)); } return nodes; } void NodeManager::putNodeInDb(Node* node) const { if (!node) { return; } if (node->attrstring) { // Last attempt to decrypt the node before storing it. node->applykey(); node->setattr(); if (node->attrstring) { mNoKeyLogger.log(*node); } } mTable->put(node); } size_t NodeManager::nodeNotifySize() const { LockGuard g(mMutex); return mNodeNotify.size(); } bool NodeManager::FingerprintContainer::allFingerprintsAreLoaded(const FileFingerprint *fingerprint) const { return mAllFingerprintsLoaded.find(*fingerprint) != mAllFingerprintsLoaded.end(); } void NodeManager::FingerprintContainer::setAllFingerprintLoaded(const mega::FileFingerprint *fingerprint) { mAllFingerprintsLoaded.insert(*fingerprint); } void NodeManager::FingerprintContainer::removeAllFingerprintLoaded( const FileFingerprint* fingerprint) { mAllFingerprintsLoaded.erase(*fingerprint); } void NodeManager::FingerprintContainer::clear() { fingerprintNoMtime_set::clear(); mAllFingerprintsLoaded.clear(); } void NodeManager::Rootnodes::clear() { mRootNodes.clear(); files.setUndef(); rubbish.setUndef(); vault.setUndef(); } } // namespace sdk-10.11.0/src/osx/000077500000000000000000000000001516266226600140475ustar00rootroot00000000000000sdk-10.11.0/src/osx/drivenotifyosx.cpp000066400000000000000000000212161516266226600176510ustar00rootroot00000000000000/** * @file osx/drivenotifyosx.cpp * @brief Mega SDK various utilities and helper classes * * (c) 2013-2021 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_DRIVE_NOTIFICATIONS #include "mega/drivenotify.h" #include namespace mega { // CFArrayRef objects need to be constructed from const C-style array. // We watch for changes in the Volume Path Key. static const CFStringRef watchArray[1] = {kDADiskDescriptionVolumePathKey}; // See note here regarding callback registration/unregistration: // https://developer.apple.com/library/archive/documentation/DriversKernelHardware/Conceptual/DiskArbitrationProgGuide/ArbitrationBasics/ArbitrationBasics.html#//apple_ref/doc/uid/TP40009310-CH2-SW9 // MediaTypeCallbacks is an abstract base class which uses the Non-Virtual Interface Pattern // to implement callbacks, which are static member functions, where the `void* context` is a pointer // to a concrete derived class. Per the note above, we are registering the same callback (i.e., pointer to static method) // with a different context (i.e., pointer to derived class cast to void*) void MediaTypeCallbacks::registerCallbacks(DASessionRef session) { DARegisterDiskAppearedCallback( session, matchingDict(), &MediaTypeCallbacks::onDiskAppeared, this); DARegisterDiskDisappearedCallback( session, matchingDict(), &MediaTypeCallbacks::onDiskDisappeared, this); registerAdditionalCallbacks(session); } void MediaTypeCallbacks::unregisterCallbacks(DASessionRef session) { DAUnregisterCallback(session, reinterpret_cast(&MediaTypeCallbacks::onDiskAppeared), this); DAUnregisterCallback(session, reinterpret_cast(&MediaTypeCallbacks::onDiskDisappeared), this); DAUnregisterCallback(session, reinterpret_cast(&MediaTypeCallbacks::onDiskDescriptionChanged), this); DAUnregisterApprovalCallback(session, reinterpret_cast(&MediaTypeCallbacks::onUnmountApproval), this); } void MediaTypeCallbacks::addDrive(CFURLRef path, bool connected) { char buf[MAXPATHLEN]; if (CFURLGetFileSystemRepresentation(path, false, reinterpret_cast(buf), sizeof(buf))) { DriveInfo di; di.mountPoint = buf; di.connected = connected; mParent.add(std::move(di)); } } void MediaTypeCallbacks::onDiskAppeared(DADiskRef disk, void* context) { MediaTypeCallbacks& self = castSelf(context); UniqueCFRef diskDescription = description(disk); if (!self.shouldNotify(diskDescription)) return; CFURLRef path = volumePath(diskDescription); if (path) { self.addDrive(path, true); } else { self.handleNoPathAppeared(diskDescription); } } void MediaTypeCallbacks::onDiskDisappeared(DADiskRef disk, void* context) { MediaTypeCallbacks& self = castSelf(context); UniqueCFRef diskDescription = description(disk); if (!self.shouldNotify(diskDescription)) return; CFURLRef path = volumePath(diskDescription); if (path) self.addDrive(path, false); self.processDisappeared(diskDescription); } DADissenterRef MediaTypeCallbacks::onUnmountApproval(DADiskRef disk, void* context) { MediaTypeCallbacks::onDiskDisappeared(disk, context); return nullptr; } PhysicalMediaCallbacks::PhysicalMediaCallbacks(DriveNotifyOsx& parent) : MediaTypeCallbacks(parent), mMatchingDict([]{ // Constructing with immediately invoked lambda expression to allow storage of immutable ref CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Do not match Whole Media, else we get multiple notifications for the same drive CFDictionaryAddValue(matchingDict, kDADiskDescriptionMediaWholeKey, kCFBooleanFalse); // Match removable/ejectable media CFDictionaryAddValue(matchingDict, kDADiskDescriptionMediaRemovableKey, kCFBooleanTrue); CFDictionaryAddValue(matchingDict, kDADiskDescriptionMediaEjectableKey, kCFBooleanTrue); return matchingDict; }()), mKeysToMonitor(CFArrayCreate( kCFAllocatorDefault, reinterpret_cast(&watchArray), 1, &kCFTypeArrayCallBacks)), mDisksPendingPath([](const CFUUIDBytes& u1, const CFUUIDBytes& u2) noexcept { return std::memcmp(&u1, &u2, sizeof(CFUUIDBytes)) < 0; }) {} bool PhysicalMediaCallbacks::shouldNotify(CFDictionaryRef diskDescription) const noexcept { auto deviceProtocol = reinterpret_cast( CFDictionaryGetValue(diskDescription, kDADiskDescriptionDeviceProtocolKey)); return CFStringCompare(deviceProtocol, CFSTR(kIOPropertyPhysicalInterconnectTypeVirtual), 0) != kCFCompareEqualTo; } void PhysicalMediaCallbacks::registerAdditionalCallbacks(DASessionRef session) { DARegisterDiskDescriptionChangedCallback( session, matchingDict(), mKeysToMonitor, &MediaTypeCallbacks::onDiskDescriptionChanged, this); DARegisterDiskUnmountApprovalCallback( session, matchingDict(), &MediaTypeCallbacks::onUnmountApproval, this); } void PhysicalMediaCallbacks::handleNoPathAppeared(CFDictionaryRef diskDescription) { CFUUIDRef uuid = volumeUUID(diskDescription); if (uuid) mDisksPendingPath.insert(CFUUIDGetUUIDBytes(uuid)); } void PhysicalMediaCallbacks::processDisappeared(CFDictionaryRef diskDescription) { CFUUIDRef uuid = volumeUUID(diskDescription); if (uuid) mDisksPendingPath.erase(CFUUIDGetUUIDBytes(uuid)); } // The actual onDiskDescriptionChanged callback allows for significantly greater generality than our use, which is // basically just to try again performing the onDiskAppeared logic with slight modification void PhysicalMediaCallbacks::onDiskDescriptionChangedImpl(DADiskRef disk, CFArrayRef matchingKeys, void* context) { auto diskDescription = UniqueCFRef(MediaTypeCallbacks::description(disk)); CFURLRef path = volumePath(diskDescription); if (!path) return; CFUUIDRef uuid = volumeUUID(diskDescription); if (!uuid) return; auto findItr = mDisksPendingPath.find(CFUUIDGetUUIDBytes(uuid)); if (findItr == mDisksPendingPath.end()) return; addDrive(path, true); mDisksPendingPath.erase(findItr); } NetworkDriveCallbacks::NetworkDriveCallbacks(DriveNotifyOsx& parent) : MediaTypeCallbacks(parent), mMatchingDict([]{ // Constructing with immediately invoked lambda expression to allow storage of immutable ref CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionaryAddValue(matchingDict, kDADiskDescriptionVolumeNetworkKey, kCFBooleanTrue); return matchingDict; }()) {} bool NetworkDriveCallbacks::shouldNotify(CFDictionaryRef diskDescription) const noexcept { auto volumeKind = reinterpret_cast( CFDictionaryGetValue(diskDescription, kDADiskDescriptionVolumeKindKey)); return CFStringCompare(volumeKind, CFSTR("autofs"), 0) != kCFCompareEqualTo; } DriveNotifyOsx::DriveNotifyOsx() : mSession(DASessionCreate(kCFAllocatorDefault)), mPhysicalCbs(*this), mNetworkCbs(*this) {} bool DriveNotifyOsx::notifierSetup() { mPhysicalCbs.registerCallbacks(mSession); mNetworkCbs.registerCallbacks(mSession); return true; } void DriveNotifyOsx::notifierTeardown() { mPhysicalCbs.unregisterCallbacks(mSession); mNetworkCbs.unregisterCallbacks(mSession); } void DriveNotifyOsx::doInThread() { CFRunLoopRef currentThread = CFRunLoopGetCurrent(); DASessionScheduleWithRunLoop(mSession, currentThread, kCFRunLoopDefaultMode); // CFRunLoopRunInMode will run for the special setting of 0 seconds, which means process // precisely one event before returning. while (!shouldStop() && CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)) ; CFRunLoopStop(currentThread); } } // namespace mega #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/src/osx/fs.cpp000066400000000000000000000377741516266226600152050ustar00rootroot00000000000000#include #include #include #include #include "mega.h" // Disk Arbitration is not available on iOS. #ifndef USE_IOS # include #endif // ! USE_IOS namespace mega { // Convenience. template class CFPtr { T mRef; public: CFPtr() : mRef(nullptr) { } explicit CFPtr(T ref) : mRef(ref) { } CFPtr(const CFPtr& other) : mRef(other.mRef) { if (mRef) CFRetain(mRef); } CFPtr(CFPtr&& other) : mRef(other.mRef) { other.mRef = nullptr; } ~CFPtr() { if (mRef) CFRelease(mRef); } operator bool() const { return mRef; } CFPtr& operator=(const CFPtr& rhs) { CFPtr temp(rhs); std::swap(mRef, temp.mRef); return *this; } CFPtr& operator=(CFPtr&& rhs) { CFPtr temp(std::move(rhs)); std::swap(mRef, temp.mRef); return *this; } bool operator!() const { return mRef == nullptr; } T get() const { return mRef; } }; // CFPtr // Convenience. using DeviceOfResult = std::pair; static DeviceOfResult deviceOf(const std::string& path) { struct statfs buffer; DeviceOfResult result; // Couldn't determine which device contains path. if (statfs(path.c_str(), &buffer)) { // Latch error. auto error = errno; LOG_err << "Couldn't determine which device contains " << path << ": " << strerror(error); return result; } // Compute legacy fingerprint. std::memcpy(&result.second, &buffer.f_fsid, sizeof(result.second)); ++result.second; // Latch device mount point. result.first = buffer.f_mntfromname; // Return result to caller. return result; } #ifndef USE_IOS static std::string uuidOf(const std::string& device) { // Convenience. using DictionaryPtr = CFPtr; using DiskPtr = CFPtr; using SessionPtr = CFPtr; using StringPtr = CFPtr; auto allocator = kCFAllocatorDefault; // Try and establish a DA session. SessionPtr session(DASessionCreate(allocator)); // Couldn't establish DA session. if (!session) return {}; // Try and get a reference to the specified device. DiskPtr disk(DADiskCreateFromBSDName(allocator, session.get(), device.c_str())); // Couldn't get a reference to the device. if (!disk) return {}; // Try and get the device's description. DictionaryPtr info(DADiskCopyDescription(disk.get())); // Couldn't get device's description. if (!info) return {}; // What UUIDs do we want to retrieve? static const std::vector names = { // UUID of a particular filesystem. "DAVolumeUUID", // UUID of a particular device. "DAMediaUUID" }; // names // Convenience. auto encoding = kCFStringEncodingASCII; // Try and extract one of the UUIDs named above. for (auto* name : names) { // Translate name into a usable key. StringPtr key(CFStringCreateWithCStringNoCopy(allocator, name, encoding, kCFAllocatorNull)); // Couldn't create key. if (!key) return {}; // Try and retrieve UUID associated with key. auto uuid = ([&]() { // Try and retrieve value. auto value = CFDictionaryGetValue(info.get(), key.get()); // Return value casted to appropriate type. return static_cast(value); })(); // Key had no UUID. if (!uuid) continue; // Try and translate UUID to a string. StringPtr string(CFUUIDCreateString(allocator, uuid)); // Couldn't translate UUID to a string. if (!string) break; // Try and retrieve the string's raw value. if (auto* value = CFStringGetCStringPtr(string.get(), encoding)) return value; // Compute necessary buffer length. auto required = CFStringGetMaximumSizeForEncoding(CFStringGetLength(string.get()), encoding); // Sanity. assert(required > 0); // Create suitable sized buffer. std::string buffer(static_cast(required + 1), 'X'); // Try and transcode the UUID's string value. auto result = CFStringGetCString(string.get(), &buffer[0], required + 1, encoding); // Couldn't transcode the UUID's string value. if (!result) break; // Shrink buffer to fit. buffer.resize(static_cast(required)); // Return UUID to caller. return buffer; } // Couldn't retrieve UUID. return {}; } #else // ! USE_IOS static std::string uuidOf(const std::string&) { return {}; } #endif // USE_IOS fsfp_t FileSystemAccess::fsFingerprint(const LocalPath& path) const { // Convenience. using detail::adjustBasePath; // What device contains path? auto device = deviceOf(adjustBasePath(path)); // Couldn't determine which device contains path. if (device.first.empty()) return fsfp_t(); // Try and determine the device's UUID. auto uuid = uuidOf(device.first); // Return fingerprint to caller. return fsfp_t(device.second, std::move(uuid)); } MacFileSystemAccess::MacFileSystemAccess() : PosixFileSystemAccess() , mDispatchQueue(nullptr) , mNumNotifiers(0u) { } MacFileSystemAccess::~MacFileSystemAccess() { // Make sure there are no notifiers active. assert(mNumNotifiers == 0); // Bail if we don't have a dispatch queue. if (!mDispatchQueue) return; // Release the dispatch queue. dispatch_release(mDispatchQueue); } void MacFileSystemAccess::addevents(Waiter*, int) { // Here until we factor Linux stuff out of PFSA. } int MacFileSystemAccess::checkevents(Waiter*) { // Here until we factor Linux stuff out of PFSA. return 0; } void MacFileSystemAccess::flushDispatchQueue() { dispatch_barrier_sync_f(mDispatchQueue, nullptr, [](void*) {}); } #ifdef ENABLE_SYNC bool MacFileSystemAccess::initFilesystemNotificationSystem() { static const char* name = "mega.FilesystemMonitor"; // Try and create a dispatch queue. mDispatchQueue = dispatch_queue_create(name, DISPATCH_QUEUE_SERIAL); // We're successful if the queue was created. return mDispatchQueue; } DirNotify* MacFileSystemAccess::newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* waiter) { // Make sure we've been passed a sane waiter. assert(waiter); return new MacDirNotify(*this, root, rootPath, *waiter); } MacDirNotify::MacDirNotify(MacFileSystemAccess& owner, LocalNode& root, const LocalPath& rootPath, Waiter& waiter) : DirNotify(rootPath) , mEventStream(nullptr) , mOwner(owner) , mRoot(root) , mRootPathLength() , mWaiter(waiter) { // Assume we'll be unable to create the event stream. setFailed(1, "Unable to create filesystem event stream."); // Create the event stream. mEventStream = ([&rootPath, this]{ // What path are we monitoring? auto path = CFStringCreateWithCString(nullptr, rootPath.toPath(false).c_str(), kCFStringEncodingUTF8); // What paths are we monitoring? auto paths = CFArrayCreate( nullptr, reinterpret_cast(&path), 1, &kCFTypeArrayCallBacks); // Now owned by the array. CFRelease(path); // Context describing the stream we're about to create. FSEventStreamContext context{}; // Passed to the trampoline when it is invoked. context.info = this; // Flags customizing the stream's behavior. constexpr auto flags = kFSEventStreamCreateFlagFileEvents; // How long the stream should wait before sending events. constexpr auto latency = 0.1; // Try and create the stream. auto stream = FSEventStreamCreate(nullptr, &MacDirNotify::trampoline, &context, paths, kFSEventStreamEventIdSinceNow, latency, flags); // No longer necessary. CFRelease(paths); // Return the stream to the caller. return stream; })(); // Bail if we couldn't create the stream. if (!mEventStream) return; // How long is the normalized root path((like std::canonical and Exclude any trailing separator)? LocalPath expanseRootPath; if (!FSACCESS_CLASS().expanselocalpath(rootPath, expanseRootPath)) { LOG_err << "Fail to expanseRootPath:" << rootPath.toPath(false).c_str(); return; } mRootPathLength = expanseRootPath.endsInSeparator() ? expanseRootPath.toPath(false).size() - 1 : expanseRootPath.toPath(false).size(); // Specify who should process our filesystem events. FSEventStreamSetDispatchQueue(mEventStream, mOwner.mDispatchQueue); // Start monitoring filesystem events. FSEventStreamStart(mEventStream); // Let the owner know we're active. ++mOwner.mNumNotifiers; // Let the engine know everything's ok. setFailed(0, ""); } MacDirNotify::~MacDirNotify() { // No stream? Nothing to clean up. if (!mEventStream) return; // Stop monitoring the filesystem for events. FSEventStreamStop(mEventStream); // Remove the stream from the owner's run loop. FSEventStreamInvalidate(mEventStream); // Destroy the event stream. FSEventStreamRelease(mEventStream); // Works on the queue might target for this MacDirNotify object. // Or the callback is running might is owned by this MacDirNotify object. // This call ensures that all tasks on the dispatch queue have been completed. mOwner.flushDispatchQueue(); // Let our owner know we are no longer active. --mOwner.mNumNotifiers; } void MacDirNotify::callback(const FSEventStreamEventFlags* flags, std::size_t numEvents, const char** paths) { while (numEvents--) { auto flag = *flags++; auto path = *paths++; LOG_debug << "FSNotification: " << flag << " " << path; path += mRootPathLength; // Skip leading seperator. if (*path == '/') ++path; // Has the root path been invalidated? if ((flag & kFSEventStreamEventFlagRootChanged)) setFailed(EINVAL, "The root path has been invalidated."); // Has a device been unmounted below the root? if ((flag & kFSEventStreamEventFlagUnmount)) setFailed(EINVAL, "A device has been unmounted below the root path."); // even a folder renamed, comes in as that folder's path and we need to rescan the parent to see the changed name auto scanFlags = Notification::NEEDS_PARENT_SCAN; // Have some events been coalesced? if ((flag & kFSEventStreamEventFlagMustScanSubDirs)) scanFlags = Notification::FOLDER_NEEDS_SCAN_RECURSIVE; // log the unusual possiblities if (flag == kFSEventStreamEventFlagNone) LOG_debug << "FSEv flag none"; if (flag & kFSEventStreamEventFlagMustScanSubDirs) LOG_debug << "FSEv scan subdirs"; if (flag & kFSEventStreamEventFlagUserDropped) LOG_debug << "FSEv user dropped"; if (flag & kFSEventStreamEventFlagKernelDropped) LOG_debug << "FSEv kernel dropped"; if (flag & kFSEventStreamEventFlagEventIdsWrapped) LOG_debug << "FSEv ids wrapped"; if (flag & kFSEventStreamEventFlagHistoryDone) LOG_debug << "FSEv history done"; if (flag & kFSEventStreamEventFlagRootChanged) LOG_debug << "FSEv root changed"; if (flag & kFSEventStreamEventFlagMount) LOG_debug << "FSEv mount"; if (flag & kFSEventStreamEventFlagUnmount) LOG_debug << "FSEv unmount"; if (flag & kFSEventStreamEventFlagItemCreated) LOG_debug << "FSEv item created"; if (flag & kFSEventStreamEventFlagItemRemoved) LOG_debug << "FSEv item removed"; //if (flag & kFSEventStreamEventFlagItemInodeMetaMod) LOG_debug << "FSEv inode meta mod"; //if (flag & kFSEventStreamEventFlagItemRenamed) LOG_debug << "FSEv item renamed"; //if (flag & kFSEventStreamEventFlagItemModified) LOG_debug << "FSEv item modified"; if (flag & kFSEventStreamEventFlagItemFinderInfoMod) LOG_debug << "FSEv finder info mod"; if (flag & kFSEventStreamEventFlagItemChangeOwner) LOG_debug << "FSEv change owner"; if (flag & kFSEventStreamEventFlagItemXattrMod) LOG_debug << "FSEv xattr mod"; //if (flag & kFSEventStreamEventFlagItemIsFile) LOG_debug << "FSEv is file"; //if (flag & kFSEventStreamEventFlagItemIsDir) LOG_debug << "FSEv is dir"; if (flag & kFSEventStreamEventFlagItemIsSymlink) LOG_debug << "FSEv is symlink"; if (flag & kFSEventStreamEventFlagOwnEvent) LOG_debug << "FSEv own event"; if (flag & kFSEventStreamEventFlagItemIsHardlink) LOG_debug << "FSEv is hard link"; if (flag & kFSEventStreamEventFlagItemIsLastHardlink) LOG_debug << "FSEv is last hard link"; //if (flag & kFSEventStreamEventFlagItemCloned) LOG_debug << "FSEv item cloned"; // Pass the notification to the engine. notify(fsEventq, &mRoot, scanFlags, LocalPath::fromPlatformEncodedRelative(path)); // No need for the below if we're performing a recursive scan. if (scanFlags == Notification::FOLDER_NEEDS_SCAN_RECURSIVE) continue; // Are we dealing with a directory? if (!(flag & kFSEventStreamEventFlagItemIsDir)) continue; // Has its permissions changed? if (!(flag & kFSEventStreamEventFlagItemChangeOwner)) continue; LOG_debug << "FSNotification folder self-rescan: " << path; // If so, rescan the directory's contents. // // The reason for this is that we may not have been able to list // the directory's contents before. If we didn't rescan, we // wouldn't notice these files until some other event is // triggered in or below this directory. notify(fsEventq, &mRoot, Notification::FOLDER_NEEDS_SELF_SCAN, LocalPath::fromPlatformEncodedRelative(path)); } // Let the engine know it has events to process. mWaiter.notify(); } void MacDirNotify::trampoline(ConstFSEventStreamRef, void *context, std::size_t numPaths, void* paths, const FSEventStreamEventFlags* flags, const FSEventStreamEventId*) { // What instance is associated with these events? auto instance = reinterpret_cast(context); // Let the instance process the events. instance->callback(flags, numPaths, static_cast(paths)); } #endif // ENABLE_SYNC } // mega sdk-10.11.0/src/osx/osxutils.mm000066400000000000000000000112371516266226600163000ustar00rootroot00000000000000#include "mega/osx/osxutils.h" #if defined(__APPLE__) && !(TARGET_OS_IPHONE) #include #include #elif TARGET_OS_IOS #include #endif #include "mega.h" using namespace mega; using namespace std; enum { HTTP_PROXY = 0, HTTPS_PROXY }; void path2localMac(const string* path, string* local) { if (!path->size()) { *local = ""; return; } // Multiple calls to path2localMac cause a high memory usage on macOS. To avoid it, use autorelease pool to release any temp object at the end of the pool. // At the end of the block, the temporary objects are released, which typically results in their deallocation thereby reducing the program’s memory footprint. @autoreleasepool { // Compatibility with new APFS filesystem // Use the fileSystemRepresentation property of NSString objects when creating and opening // files with lower-level filesystem APIs such as POSIX open(2), or when storing filenames externally from the filesystem` NSString *tempPath = [[NSString alloc] initWithUTF8String:path->c_str()]; const char *pathRepresentation = NULL; @try { pathRepresentation = [tempPath fileSystemRepresentation]; } @catch (NSException *e) { LOG_err << "Failed getting file system representation (APFS filesystem)"; local->clear(); #if !__has_feature(objc_arc) [tempPath release]; #endif return; } if (pathRepresentation) { *local = pathRepresentation; } else { local->clear(); } #if !__has_feature(objc_arc) [tempPath release]; #endif } } #if defined(__APPLE__) && !(TARGET_OS_IPHONE) CFTypeRef getValueFromKey(CFDictionaryRef dict, const void *key, CFTypeID type) { CFTypeRef value = CFDictionaryGetValue(dict, key); if (!value) { return NULL; } if ((CFGetTypeID(value) == type)) { return value; } return NULL; } bool getProxyConfiguration(CFDictionaryRef dict, int proxyType, Proxy* proxy) { int isEnabled = 0; int port; if (proxyType != HTTPS_PROXY && proxyType != HTTP_PROXY) { return false; } CFStringRef proxyEnableKey = proxyType == HTTP_PROXY ? kSCPropNetProxiesHTTPEnable : kSCPropNetProxiesHTTPSEnable; CFStringRef proxyHostKey = proxyType == HTTP_PROXY ? kSCPropNetProxiesHTTPProxy : kSCPropNetProxiesHTTPSProxy; CFStringRef portKey = proxyType == HTTP_PROXY ? kSCPropNetProxiesHTTPPort : kSCPropNetProxiesHTTPSPort; CFNumberRef proxyEnabledRef = (CFNumberRef)getValueFromKey(dict, proxyEnableKey, CFNumberGetTypeID()); if (proxyEnabledRef && CFNumberGetValue(proxyEnabledRef, kCFNumberIntType, &isEnabled) && (isEnabled != 0)) { if ([(__bridge NSDictionary*)dict valueForKey: proxyType == HTTP_PROXY ? @"HTTPUser" : @"HTTPSUser"] != nil) { // Username set, skip proxy configuration. We only allow proxies withouth user/pw credentials // to not have to request the password to the user to read the keychain return false; } CFStringRef hostRef = (CFStringRef)getValueFromKey(dict, proxyHostKey, CFStringGetTypeID()); if (hostRef) { CFIndex length = CFStringGetLength(hostRef); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; char *buffer = new char[static_cast(maxSize)]; if (CFStringGetCString(hostRef, buffer, maxSize, kCFStringEncodingUTF8)) { CFNumberRef portRef = (CFNumberRef)getValueFromKey(dict, portKey, CFNumberGetTypeID()); if (portRef && CFNumberGetValue(portRef, kCFNumberIntType, &port)) { ostringstream oss; oss << (proxyType == HTTP_PROXY ? "http://" : "https://") << buffer << ":" << port; string link = oss.str(); proxy->setProxyType(Proxy::CUSTOM); proxy->setProxyURL(link); delete [] buffer; return true; } } delete [] buffer; } } return false; } void getOSXproxy(Proxy* proxy) { CFDictionaryRef proxySettings = NULL; proxySettings = SCDynamicStoreCopyProxies(NULL); if (!proxySettings) { return; } if (!getProxyConfiguration(proxySettings, HTTPS_PROXY, proxy)) { getProxyConfiguration(proxySettings, HTTP_PROXY, proxy); } CFRelease(proxySettings); } #endif sdk-10.11.0/src/pendingcontactrequest.cpp000066400000000000000000000103431516266226600203540ustar00rootroot00000000000000/** * @file pendingcontactrequest.cpp * @brief Class for manipulating pending contact requests * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/pendingcontactrequest.h" #include "mega/megaclient.h" namespace mega { PendingContactRequest::PendingContactRequest(const handle id) { this->id = id; this->ts = 0; this->uts = 0; this->isoutgoing = true; this->autoaccepted = false; memset(&changed, 0, sizeof changed); } PendingContactRequest::PendingContactRequest(const handle id,const char *oemail, const char *temail, const m_time_t ts, const m_time_t uts, const char *msg, bool outgoing) { this->id = id; this->targetemail = ""; this->autoaccepted = false; this->update(oemail, temail, ts, uts, msg, outgoing); memset(&changed, 0, sizeof changed); } void PendingContactRequest::update(const char* oemail, const char* temail, const m_time_t newTs, const m_time_t newUts, const char* newMessage, bool outgoing) { if (oemail) { JSON::copystring(&(this->originatoremail), oemail); } if (temail) { JSON::copystring(&(this->targetemail), temail); } ts = newTs; uts = newUts; if (newMessage) { JSON::copystring(&(msg), newMessage); } this->isoutgoing = outgoing; } bool PendingContactRequest::removed() { return changed.accepted || changed.denied || changed.ignored || changed.deleted; } bool PendingContactRequest::serialize(string *d) const { unsigned char l; d->append((char*)&id, sizeof id); l = (unsigned char)originatoremail.size(); d->append((char*)&l, sizeof l); d->append(originatoremail.c_str(), l); l = (unsigned char)targetemail.size(); d->append((char*)&l, sizeof l); d->append(targetemail.c_str(), l); d->append((char*)&ts, sizeof ts); d->append((char*)&uts, sizeof uts); l = (unsigned char)msg.size(); d->append((char*)&l, sizeof l); d->append(msg.c_str(), l); d->append((char*)&isoutgoing, sizeof isoutgoing); return true; } PendingContactRequest* PendingContactRequest::unserialize(string *d) { handle id; string oemail; string temail; m_time_t ts; m_time_t uts; string msg; bool isoutgoing = false; const char* ptr = d->data(); const char* end = ptr + d->size(); unsigned char l; if (ptr + sizeof id + sizeof l > end) { return NULL; } id = MemAccess::get(ptr); ptr += sizeof id; l = static_cast(*ptr++); if (ptr + l + sizeof l > end) { return NULL; } oemail.assign(ptr, l); ptr += l; l = static_cast(*ptr++); if (ptr + l + sizeof ts + sizeof uts + sizeof l > end) { return NULL; } temail.assign(ptr, l); ptr += l; ts = MemAccess::get(ptr); ptr += sizeof ts; uts = MemAccess::get(ptr); ptr += sizeof uts; l = static_cast(*ptr++); if (ptr + l > end) // should be ptr+l+sizeof(isoutgoing), but legacy code writes 0 bytes when false { return NULL; } msg.assign(ptr, l); ptr += l; if (ptr == end) // legacy bug writes 0 bytes for incoming PCRs { isoutgoing = false; } else if (ptr + sizeof isoutgoing == end) { isoutgoing = MemAccess::get(ptr); ptr += sizeof isoutgoing; } if (ptr == end) { return new PendingContactRequest(id, oemail.c_str(), temail.c_str(), ts, uts, msg.c_str(), isoutgoing); } else { return NULL; } } } //namespace sdk-10.11.0/src/posix/000077500000000000000000000000001516266226600144005ustar00rootroot00000000000000sdk-10.11.0/src/posix/console.cpp000066400000000000000000000040131516266226600165440ustar00rootroot00000000000000/** * @file posix/console.cpp * @brief POSIX console/terminal control * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" namespace mega { using namespace std; PosixConsole::PosixConsole() { // set up the console if (tcgetattr(STDIN_FILENO, &term) < 0) { perror("tcgetattr"); throw runtime_error("tcgetattr"); } oldlflag = term.c_lflag; oldvtime = term.c_cc[VTIME]; term.c_lflag &= static_cast(~ICANON); term.c_cc[VTIME] = 1; if (tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0) { perror("tcsetattr"); throw runtime_error("tcsetattr at ctor"); } } PosixConsole::~PosixConsole() { term.c_lflag = oldlflag; term.c_cc[VTIME] = oldvtime; if (tcsetattr(STDIN_FILENO, TCSANOW, &term) < 0) { perror("tcsetattr"); } } // FIXME: UTF-8 compatibility void PosixConsole::readpwchar(char* pw_buf, int pw_buf_size, int* pw_buf_pos, char** line) { char c; if (read(STDIN_FILENO, &c, 1) == 1) { if (c == 8 && *pw_buf_pos) { (*pw_buf_pos)--; } else if (c == 13) { *line = (char*)malloc(static_cast(*pw_buf_pos + 1)); memcpy(*line, pw_buf, static_cast(*pw_buf_pos)); (*line)[*pw_buf_pos] = 0; } else if (*pw_buf_pos < pw_buf_size) { pw_buf[(*pw_buf_pos)++] = c; } } } void PosixConsole::setecho(bool /*echo*/) { } } // namespace sdk-10.11.0/src/posix/consolewaiter.cpp000066400000000000000000000022361516266226600177650ustar00rootroot00000000000000/** * @file posix/consolewaiter.cpp * @brief POSIX event/timeout handling, listens for stdin * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "megaconsolewaiter.h" namespace mega { int PosixConsoleWaiter::wait() { int r; // application's own wakeup criteria: wake up upon user input MEGA_FD_SET(STDIN_FILENO, &rfds); MEGA_FD_SET(STDIN_FILENO, &ignorefds); bumpmaxfd(STDIN_FILENO); r = PosixWaiter::wait(); // application's own event processing: user interaction from stdin? if (MEGA_FD_ISSET(STDIN_FILENO, &rfds)) { r |= HAVESTDIN; } return r; } } // namespace sdk-10.11.0/src/posix/drivenotifyposix.cpp000066400000000000000000000155051516266226600205370ustar00rootroot00000000000000/** * @file mega/posix/drivenotifyposix.cpp * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_DRIVE_NOTIFICATIONS #include "mega/drivenotify.h" #include // Ubuntu: sudo apt-get install libudev-dev #include #include #include namespace mega { // // DriveNotifyPosix ///////////////////////////////////////////// bool DriveNotifyPosix::notifierSetup() { // init udev resource mUdev = udev_new(); if (!mUdev) return false; // is udevd daemon running? // init udev monitor mUdevMon = udev_monitor_new_from_netlink(mUdev, "udev"); if (!mUdevMon) { udev_unref(mUdev); mUdev = nullptr; return false; } cacheMountedPartitions(); // On unix systems you need to define your udev rules to allow notifications for // your device. // // i.e. on Ubuntu create file "100-megasync-udev.rules" either in // /etc/udev/rules.d/ OR // /usr/lib/udev/rules.d/ // and add line: // SUBSYSTEM=="block", ATTRS{idDevtype}=="partition" udev_monitor_filter_add_match_subsystem_devtype(mUdevMon, "block", "partition"); udev_monitor_enable_receiving(mUdevMon); return true; } void DriveNotifyPosix::notifierTeardown() { // release udev monitor if (mUdevMon) { udev_monitor_filter_remove(mUdevMon); udev_monitor_unref(mUdevMon); mUdevMon = nullptr; } // release udev resource if (mUdev) { udev_unref(mUdev); mUdev = nullptr; } mMounted.clear(); } void DriveNotifyPosix::doInThread() { int fd = udev_monitor_get_fd(mUdevMon); while (!shouldStop()) { // use a blocking call, with a timeout, to check for device events fd_set fds; FD_ZERO(&fds); FD_SET(fd, &fds); timeval tv; tv.tv_sec = 0; tv.tv_usec = 250 * 1000; // 250ms int ret = select(fd+1, &fds, nullptr, nullptr, &tv); if (ret <= 0 || !FD_ISSET(fd, &fds)) continue; // get any [dis]connected device udev_device* dev = udev_monitor_receive_device(mUdevMon); if (dev) { evaluateDevice(dev); udev_device_unref(dev); } } // do some cleanup notifierTeardown(); } void DriveNotifyPosix::evaluateDevice(udev_device* dev) // dev must Not be null { // filter "partition" events const char* devtype = udev_device_get_devtype(dev); // "partition" if (!devtype || strcmp(devtype, "partition")) return; // filter "add"/"remove" actions const char* action = udev_device_get_action(dev); // "add" / "remove" bool added = action && !strcmp(action, "add"); bool removed = action && !added && !strcmp(action, "remove"); if (!(added || removed)) return; // ignore other possible actions // get device location const char* devNode = udev_device_get_devnode(dev); // "/dev/sda1" if (!devNode) return; // did something go wrong? const std::string& devNodeStr(devNode); // gather drive info DriveInfo drvInfo; drvInfo.connected = added; // get the mount point if (removed) { // get it from the cache, because it will have already been removed from // the relevant locations by the time getMountPoint() will attempt to read it drvInfo.mountPoint = mMounted[devNodeStr]; mMounted.erase(devNodeStr); // remove from cache } else // added { // reading it might happen before the relevant locations have been updated, // so allow retrying a few times, say at 100ms intervals for (int i = 0; i < 5; ++i) { drvInfo.mountPoint = getMountPoint(devNodeStr); if (!drvInfo.mountPoint.empty()) { mMounted[devNodeStr] = drvInfo.mountPoint; // cache it break; } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } // send notification if (!drvInfo.mountPoint.empty()) { add(std::move(drvInfo)); } } std::string DriveNotifyPosix::getMountPoint(const std::string& device) { std::string mountPoint; // go to all mounts FILE* fsMounts = setmntent("/proc/mounts", "r"); if (!fsMounts) // this should never happen { // make another attempt fsMounts = setmntent("/etc/mtab", "r"); if (!fsMounts) return mountPoint; } // search mount point for the current device for (mntent* mnt = getmntent(fsMounts); mnt; mnt = getmntent(fsMounts)) { if (device == mnt->mnt_fsname) { mountPoint = mnt->mnt_dir; break; } } endmntent(fsMounts); // closes the file descriptor return mountPoint; } void DriveNotifyPosix::cacheMountedPartitions() { // enumerate all mounted partitions udev_enumerate* enumerate = udev_enumerate_new(mUdev); udev_enumerate_add_match_subsystem(enumerate, "block"); udev_enumerate_add_match_property(enumerate, "DEVTYPE", "partition"); udev_enumerate_scan_devices(enumerate); udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate); udev_list_entry *entry; udev_list_entry_foreach(entry, devices) // for loop { // get a partition const char* entryName = udev_list_entry_get_name(entry); udev_device* blockDev = udev_device_new_from_syspath(mUdev, entryName); if (!blockDev) continue; // filter only removable ones if (isRemovable(blockDev)) { // get partition node const char* devNode = udev_device_get_devnode(blockDev); // "/dev/sda1" // cache mount point std::string mountPoint = getMountPoint(devNode); if (!mountPoint.empty()) { mMounted[devNode] = mountPoint; } } udev_device_unref(blockDev); } udev_enumerate_unref(enumerate); } bool DriveNotifyPosix::isRemovable(udev_device* part) { udev_device* parent = udev_device_get_parent(part); // do not unref this one! if (!parent) return false; const char* removable = udev_device_get_sysattr_value(parent, "removable"); return removable && !strcmp(removable, "1"); } } // namespace #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/src/posix/fs.cpp000066400000000000000000002224011516266226600155150ustar00rootroot00000000000000/** * @file posix/fs.cpp * @brief POSIX filesystem/directory access/notification * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * MacOS X fsevents code based on osxbook.com/software/fslogger * (requires euid == root or passing an existing /dev/fsevents fd) * (c) Amit Singh * * You should have received a copy of the license along with this * program. */ #ifndef __APPLE__ #include #endif // ! __APPLE__ #include "mega.h" #include "mega/scoped_helpers.h" #include #include #include #include #include #ifdef TARGET_OS_MAC #include "mega/osx/osxutils.h" #endif #ifdef __ANDROID__ #include #include extern JavaVM* MEGAjvm; extern jclass fileWrapper; #endif #if defined(__MACH__) && !(TARGET_OS_IPHONE) #include #endif #ifdef __linux__ #ifndef __ANDROID__ #include #endif /* ! __ANDROID__ */ #include #include #ifndef FUSEBLK_SUPER_MAGIC #define FUSEBLK_SUPER_MAGIC 0x65735546ul #endif /* ! FUSEBLK_SUPER_MAGIC */ #ifndef FUSECTL_SUPER_MAGIC #define FUSECTL_SUPER_MAGIC 0x65735543ul #endif /* ! FUSECTL_SUPER_MAGIC */ #ifndef HFS_SUPER_MAGIC #define HFS_SUPER_MAGIC 0x4244ul #endif /* ! HFS_SUPER_MAGIC */ #ifndef HFSPLUS_SUPER_MAGIC #define HFSPLUS_SUPER_MAGIC 0x482Bul #endif /* ! HFSPLUS_SUPER_MAGIC */ #ifndef NTFS_SB_MAGIC #define NTFS_SB_MAGIC 0x5346544Eul #endif /* ! NTFS_SB_MAGIC */ #ifndef SDCARDFS_SUPER_MAGIC #define SDCARDFS_SUPER_MAGIC 0x5DCA2DF5ul #endif /* ! SDCARDFS_SUPER_MAGIC */ #ifndef F2FS_SUPER_MAGIC #define F2FS_SUPER_MAGIC 0xF2F52010ul #endif /* ! F2FS_SUPER_MAGIC */ #ifndef XFS_SUPER_MAGIC #define XFS_SUPER_MAGIC 0x58465342ul #endif /* ! XFS_SUPER_MAGIC */ #ifndef CIFS_MAGIC_NUMBER #define CIFS_MAGIC_NUMBER 0xFF534D42ul #endif // ! CIFS_MAGIC_NUMBER #ifndef NFS_SUPER_MAGIC #define NFS_SUPER_MAGIC 0x6969ul #endif // ! NFS_SUPER_MAGIC #ifndef SMB_SUPER_MAGIC #define SMB_SUPER_MAGIC 0x517Bul #endif // ! SMB_SUPER_MAGIC #ifndef SMB2_MAGIC_NUMBER #define SMB2_MAGIC_NUMBER 0xfe534d42ul #endif // ! SMB2_MAGIC_NUMBER #endif /* __linux__ */ #if defined(__APPLE__) || defined(USE_IOS) #include #include #endif /* __APPLE__ || USE_IOS */ namespace mega { namespace detail { #ifdef USE_IOS static std::string GetBasePath() { static std::string basePath; static std::once_flag onceOnly; // Compute base path as necessary. std::call_once(onceOnly, []() { ios_appbasepath(&basePath); basePath.append("/"); }); // Return base path to caller. return basePath; } AdjustBasePathResult adjustBasePath(const LocalPath& path) { // Get our hands on the app's base path. auto basePath = GetBasePath(); // No base path. if (basePath.empty()) return path.asPlatformEncoded(false); // Path is absolute. if (path.beginsWithSeparator()) return path.asPlatformEncoded(false); // Compute absolute path. basePath.append(path.asPlatformEncoded(false)); // Return absolute path to caller. return basePath; } #else // USE_IOS AdjustBasePathResult adjustBasePath(const LocalPath& path) { return path.asPlatformEncoded(false); } #endif // ! USE_IOS } // detail using namespace std; // Make AdjustBasePath visible in current scope. using detail::adjustBasePath; using detail::AdjustBasePathResult; bool PosixFileAccess::mFoundASymlink = false; void FileSystemAccess::setMinimumDirectoryPermissions(int permissions) { mMinimumDirectoryPermissions = permissions & 07777; } void FileSystemAccess::setMinimumFilePermissions(int permissions) { mMinimumFilePermissions = permissions & 07777; } int platformCompareUtf(const string& p1, bool unescape1, const string& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, false); } int platformCompareUtf(const string& p1, bool unescape1, const LocalPath& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, false); } int platformCompareUtf(const LocalPath& p1, bool unescape1, const string& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, false); } int platformCompareUtf(const LocalPath& p1, bool unescape1, const LocalPath& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, false); } #ifdef HAVE_AIO_RT PosixAsyncIOContext::PosixAsyncIOContext() : AsyncIOContext() { aiocb = NULL; } PosixAsyncIOContext::~PosixAsyncIOContext() { LOG_verbose << "Deleting PosixAsyncIOContext"; finish(); } void PosixAsyncIOContext::finish() { if (aiocb) { if (!finished) { LOG_debug << "Synchronously waiting for async operation"; AsyncIOContext::finish(); } delete aiocb; aiocb = NULL; } assert(finished); } #endif PosixFileAccess::PosixFileAccess(Waiter *w, int defaultfilepermissions, bool followSymLinks) : FileAccess(w) { fd = -1; this->defaultfilepermissions = defaultfilepermissions; #ifndef HAVE_FDOPENDIR dp = NULL; #endif mFollowSymLinks = followSymLinks; fsidvalid = false; } PosixFileAccess::~PosixFileAccess() { fclose(); } bool PosixFileAccess::setSparse() { // Whether or not a file is sparse is up to the filesystem. return true; } auto PosixFileAccess::getFileSize() const -> std::optional> { // File isn't open. if (fd < 0) return std::nullopt; struct stat attributes; // Couldn't retrieve the file's attributes. if (::fstat(fd, &attributes) < 0) { // Latch error code. auto error = errno; // Let debuggers know why we couldn't get the file's sizes. LOG_err << "Unable to stat descriptor: " << fd << ". Error was: " << error; return std::nullopt; } // Note that st_blocks is reported in units of 512B sectors. auto allocatedSize = static_cast(attributes.st_blocks) * 512ul; // st_size is reported in units of bytes. auto reportedSize = static_cast(attributes.st_size); return std::make_pair(allocatedSize, reportedSize); } bool PosixFileAccess::sysstat(m_time_t* mtime, m_off_t* size, FSLogging) { AdjustBasePathResult nameStr = adjustBasePath(nonblocking_localname); struct stat statbuf; retry = false; type = TYPE_UNKNOWN; mIsSymLink = lstat(nameStr.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode); if (mIsSymLink && !PosixFileAccess::mFoundASymlink) { LOG_warn << "Enabling symlink check for syncup"; PosixFileAccess::mFoundASymlink = true; } if (!(mFollowSymLinks ? stat(nameStr.c_str(), &statbuf) : lstat(nameStr.c_str(), &statbuf))) { errorcode = 0; if (S_ISDIR(statbuf.st_mode)) { type = FOLDERNODE; return false; } type = FILENODE; *size = statbuf.st_size; *mtime = statbuf.st_mtime; FileSystemAccess::captimestamp(mtime); return true; } errorcode = errno; return false; } bool PosixFileAccess::sysopen(bool, FSLogging fsl) { assert(fd < 0 && "There should be no opened file descriptor at this point"); errorcode = 0; if (fd >= 0) { sysclose(); } assert(mFollowSymLinks); //Notice: symlinks are not considered here for the moment, // this is ok: this is not called with mFollowSymLinks = false, but from transfers doio. // When fully supporting symlinks, this might need to be reassessed fd = open(adjustBasePath(nonblocking_localname).c_str(), O_RDONLY); if (fd < 0) { errorcode = errno; if (fsl.doLog(errorcode)) { LOG_err << "Failed to open('" << adjustBasePath(nonblocking_localname) << "'): error " << errorcode << ": " << PosixFileSystemAccess::getErrorMessage(errorcode); } } return fd >= 0; } void PosixFileAccess::sysclose() { assert(nonblocking_localname.empty() || fd >= 0); if (fd >= 0) { close(fd); fd = -1; } } bool PosixFileAccess::asyncavailable() { #ifdef HAVE_AIO_RT #ifdef __APPLE__ return false; #endif return true; #else return false; #endif } #ifdef HAVE_AIO_RT AsyncIOContext *PosixFileAccess::newasynccontext() { return new PosixAsyncIOContext(); } void PosixFileAccess::asyncopfinished(sigval sigev_value) { PosixAsyncIOContext *context = (PosixAsyncIOContext *)(sigev_value.sival_ptr); struct aiocb *aiocbp = context->aiocb; int e = aio_error(aiocbp); assert (e != EINPROGRESS); context->retry = (e == EAGAIN); context->failed = (aio_return(aiocbp) < 0); if (!context->failed) { if (context->op == AsyncIOContext::READ && context->pad) { memset((void *)(((char *)(aiocbp->aio_buf)) + aiocbp->aio_nbytes), 0, context->pad); LOG_verbose << "Async read finished OK"; } else { LOG_verbose << "Async write finished OK"; } } else { LOG_warn << "Async operation finished with error: " << e; } asyncfscallback userCallback = context->userCallback; void *userData = context->userData; context->finished = true; if (userCallback) { userCallback(userData); } } #endif void PosixFileAccess::asyncsysopen([[maybe_unused]] AsyncIOContext *context) { #ifdef HAVE_AIO_RT const auto flag = AsyncIOContext::toOpenFlag(context->access); context->failed = !fopen(context->openPath, flag, FSLogging::logOnError); if (context->failed) { LOG_err << "Failed to fopen('" << context->openPath << "'): error " << errorcode << ": " << PosixFileSystemAccess::getErrorMessage(errorcode); } context->retry = retry; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } #endif } void PosixFileAccess::asyncsysread([[maybe_unused]] AsyncIOContext *context) { #ifdef HAVE_AIO_RT if (!context) { return; } PosixAsyncIOContext *posixContext = dynamic_cast(context); if (!posixContext) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } return; } struct aiocb *aiocbp = new struct aiocb; memset(aiocbp, 0, sizeof (struct aiocb)); aiocbp->aio_fildes = fd; aiocbp->aio_buf = (void *)posixContext->dataBuffer; aiocbp->aio_nbytes = posixContext->dataBufferLen; aiocbp->aio_offset = posixContext->posOfBuffer; aiocbp->aio_sigevent.sigev_notify = SIGEV_THREAD; aiocbp->aio_sigevent.sigev_notify_function = asyncopfinished; aiocbp->aio_sigevent.sigev_value.sival_ptr = (void *)posixContext; posixContext->aiocb = aiocbp; if (aio_read(aiocbp)) { posixContext->retry = (errno == EAGAIN); posixContext->failed = true; posixContext->finished = true; posixContext->aiocb = NULL; delete aiocbp; LOG_warn << "Async read failed at startup:" << errno; if (posixContext->userCallback) { posixContext->userCallback(posixContext->userData); } } #endif } void PosixFileAccess::asyncsyswrite([[maybe_unused]] AsyncIOContext *context) { #ifdef HAVE_AIO_RT if (!context) { return; } PosixAsyncIOContext *posixContext = dynamic_cast(context); if (!posixContext) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } return; } struct aiocb *aiocbp = new struct aiocb; memset(aiocbp, 0, sizeof (struct aiocb)); aiocbp->aio_fildes = fd; aiocbp->aio_buf = (void *)posixContext->dataBuffer; aiocbp->aio_nbytes = posixContext->dataBufferLen; aiocbp->aio_offset = posixContext->posOfBuffer; aiocbp->aio_sigevent.sigev_notify = SIGEV_THREAD; aiocbp->aio_sigevent.sigev_notify_function = asyncopfinished; aiocbp->aio_sigevent.sigev_value.sival_ptr = (void *)posixContext; posixContext->aiocb = aiocbp; if (aio_write(aiocbp)) { posixContext->retry = (errno == EAGAIN); posixContext->failed = true; posixContext->finished = true; posixContext->aiocb = NULL; delete aiocbp; LOG_warn << "Async write failed at startup: " << errno; if (posixContext->userCallback) { posixContext->userCallback(posixContext->userData); } } #endif } // update local name void PosixFileAccess::updatelocalname(const LocalPath& name, bool force) { if (force || !nonblocking_localname.empty()) { nonblocking_localname = name; } } bool PosixFileAccess::sysread(void* buffer, unsigned long length, m_off_t offset, bool* cretry) { // Sanity. assert(buffer || !length); assert(offset >= 0); // Keeps logic simple. if (!cretry) cretry = &retry; // Reads are never retriable on POSIX systems. *cretry = false; // Perform the read. auto result = pread(fd, buffer, length, offset); // Read failed. if (result < 0) { errorcode = errno; return false; } // Short read - file was likely truncated if (static_cast(result) != length) { errorcode = EIO; return false; } // Read was successful. return true; } void PosixFileAccess::fclose() { #ifndef HAVE_FDOPENDIR if (dp) closedir(dp); dp = nullptr; #endif // HAVE_FDOPENDIR if (fd >= 0) close(fd); fd = -1; } bool PosixFileAccess::fwrite(const void* buffer, unsigned long length, m_off_t offset, unsigned long* numWritten, bool* cretry) { // Sanity. assert(buffer || !length); assert(offset >= 0); // Keeps logic simple. if (!cretry) cretry = &retry; auto numWritten_ = 0ul; if (!numWritten) numWritten = &numWritten_; // Assume we can't write any data to file. *numWritten = 0; // Write failures are not retriable on POSIX systems. *cretry = false; // Try and perform the write. auto result = pwrite(fd, buffer, length, offset); // Couldn't perform the write. if (result < 0) return false; // Let the user know how many bytes were written. *numWritten = static_cast(result); // Write is successful if all bytes were be written. return length == *numWritten; } bool PosixFileAccess::fstat(m_time_t& modified, m_off_t& size) { struct stat attributes; retry = false; if (::fstat(fd, &attributes)) { errorcode = errno; LOG_err << "Unable to stat descriptor: " << fd << ". Error was: " << errorcode; return false; } modified = attributes.st_mtime; size = static_cast(attributes.st_size); return true; } bool PosixFileAccess::ftruncate(m_off_t size) { retry = false; // Truncate the file. if (::ftruncate(fd, size) == 0) { // Set the file pointer to the end. return lseek(fd, size, SEEK_SET) == size; } // Couldn't truncate the file. return false; } int PosixFileAccess::stealFileDescriptor() { int toret = fd; fd = -1; return toret; } bool PosixFileAccess::fopen(const LocalPath& f, OpenFlag flag, FSLogging fsl, DirAccess* iteratingDir, bool, [[maybe_unused]] bool skipcasecheck, [[maybe_unused]] LocalPath* actualLeafNameIfDifferent) { struct stat statbuf; fopenSucceeded = false; retry = false; bool statok = false; if (iteratingDir) //reuse statbuf from iterator { statbuf = static_cast(iteratingDir)->currentItemStat; mIsSymLink = S_ISLNK(statbuf.st_mode) || static_cast(iteratingDir)->currentItemFollowedSymlink; statok = true; } AdjustBasePathResult fstr = adjustBasePath(f); const bool read = openRead(flag); const bool write = openWrite(flag); #ifdef __MACH__ if (!write) { char resolved_path[PATH_MAX]; if (fstr != "." && fstr != ".." && (statok || !lstat(fstr.c_str(), &statbuf)) && !S_ISLNK(statbuf.st_mode) && realpath(fstr.c_str(), resolved_path) == resolved_path) { const char *fname; size_t fnamesize; if ((fname = strrchr(fstr.c_str(), '/'))) { fname++; fnamesize = fstr.size() - (static_cast(fname - fstr.c_str())); } else { fname = fstr.c_str(); fnamesize = fstr.size(); } fnamesize++; const char *rname; size_t rnamesize; if ((rname = strrchr(resolved_path, '/'))) { rname++; } else { rname = resolved_path; } rnamesize = strlen(rname) + 1; bool equalNameAndCapitalization = (rnamesize == fnamesize && !memcmp(fname, rname, fnamesize)); if (actualLeafNameIfDifferent && !equalNameAndCapitalization) { *actualLeafNameIfDifferent = LocalPath::fromRelativePath(std::string(rname)); } if (!skipcasecheck) { if (rnamesize == fnamesize && memcmp(fname, rname, fnamesize)) { LOG_warn << "fopen failed due to invalid case: " << fstr << " File system real name: " << rname; return false; } } } } #endif #ifndef HAVE_FDOPENDIR if (!write) { // workaround for the very unfortunate platforms that do not implement fdopendir() (MacOS...) // (FIXME: can this be done without intruducing a race condition?) if ((dp = opendir(fstr.c_str()))) { // stat & check if the directory is still a directory... if (stat(fstr.c_str(), &statbuf) || !S_ISDIR(statbuf.st_mode)) { return false; } size = 0; mtime = statbuf.st_mtime; type = FOLDERNODE; fsid = (handle)statbuf.st_ino; fsidvalid = true; FileSystemAccess::captimestamp(&mtime); fopenSucceeded = true; return true; } if (errno != ENOTDIR) return false; } #endif if (!statok) { mIsSymLink = lstat(fstr.c_str(), &statbuf) == 0 && S_ISLNK(statbuf.st_mode); if (mIsSymLink && !PosixFileAccess::mFoundASymlink) { LOG_warn << "Enabling symlink check for syncup."; PosixFileAccess::mFoundASymlink = true; } if (mIsSymLink && !mFollowSymLinks) { statok = true; //we will use statbuf filled by lstat instead of fstat } } mode_t mode = 0; if (write) { mode = umask(0); } #ifndef O_PATH #define O_PATH 0 // Notice in systems were O_PATH is not available, open will fail for links with O_NOFOLLOW #endif assert(fd < 0 && "There should be no opened file descriptor at this point"); sysclose(); // Compute open flags. auto openFlags = [&]() { // We're dealing with a symlink that isn't being followed. if (!mFollowSymLinks && mIsSymLink) return O_NOFOLLOW | O_PATH; // Sanity. assert(read || write); // Caller only wants to read the file. if (!write) return O_RDONLY; // Assume caller wants to read and write the file. auto flags = O_CREAT | O_RDWR; // Caller only wants to write the file. if (!read) flags = (flags & ~O_RDWR) | O_WRONLY; // Return flags to caller. return flags; }(); errorcode = 0; fd = open(fstr.c_str(), openFlags, defaultfilepermissions); if (fd < 0) { errorcode = errno; // streaming may set errno if (fsl.doLog(errorcode)) { LOG_err << "Failed to open('" << fstr << "'): error " << errorcode << ": " << PosixFileSystemAccess::getErrorMessage(errorcode) << (statok ? " (statok so may still open ok)" : ""); } } if (fd >= 0 || statok) { if (write) { umask(mode); } if (!statok) { statok = !::fstat(fd, &statbuf); } if (statok) { #ifdef __MACH__ //If creation time equal to kMagicBusyCreationDate if(statbuf.st_birthtimespec.tv_sec == -2082844800) { LOG_debug << "File is busy: " << fstr; retry = true; return false; } #endif type = S_ISDIR(statbuf.st_mode) ? FOLDERNODE : FILENODE; size = (type == FILENODE || mIsSymLink) ? statbuf.st_size : 0; mtime = statbuf.st_mtime; // in the future we might want to add LINKNODE to type and set it here using S_ISLNK fsid = (handle)statbuf.st_ino; fsidvalid = true; FileSystemAccess::captimestamp(&mtime); fopenSucceeded = true; return true; } close(fd); } else if (write) { umask(mode); } return false; } std::string FileSystemAccess::getErrorMessage(int error) { return strerror(error); } int FileSystemAccess::isFileHidden(const LocalPath& path, FSLogging) { // What file are we actually referencing? auto name = path.leafName().toPath(false); // Only consider dotfiles hidden. return name.size() > 1 && name.front() == '.'; } bool FileSystemAccess::setFileHidden(const LocalPath& path, FSLogging logWhen) { return isFileHidden(path, logWhen); } bool FSLogging::isFileNotFound(int error) { return error == ENOENT; } PosixFileSystemAccess::PosixFileSystemAccess() { assert(sizeof(off_t) == 8); defaultfilepermissions = 0600; defaultfolderpermissions = 0700; } #ifdef __linux__ #ifdef ENABLE_SYNC bool LinuxFileSystemAccess::initFilesystemNotificationSystem() { mNotifyFd = inotify_init1(IN_NONBLOCK); if (mNotifyFd < 0) return mNotifyFd = -errno, false; return true; } #endif // ENABLE_SYNC LinuxFileSystemAccess::~LinuxFileSystemAccess() { #ifdef ENABLE_SYNC // Make sure there are no active notifiers. assert(mNotifiers.empty()); // Release inotify descriptor, if any. if (mNotifyFd >= 0) close(mNotifyFd); #endif // ENABLE_SYNC } #endif // __linux__ bool PosixFileSystemAccess::cwd(LocalPath& path) const { return cwd_static(path); } bool PosixFileSystemAccess::cwd_static(LocalPath& path) { string buf(128, '\0'); while (!getcwd(&buf[0], buf.size())) { if (errno != ERANGE) { return false; } buf.resize(buf.size() << 1); } buf.resize(strlen(buf.c_str())); path = LocalPath::fromPlatformEncodedAbsolute(std::move(buf)); return true; } // wake up from filesystem updates #ifdef __linux__ void LinuxFileSystemAccess::addevents([[maybe_unused]] Waiter* waiter, int /*flags*/) { #ifdef ENABLE_SYNC if (mNotifyFd < 0) return; auto w = static_cast(waiter); MEGA_FD_SET(mNotifyFd, &w->rfds); MEGA_FD_SET(mNotifyFd, &w->ignorefds); w->bumpmaxfd(mNotifyFd); #endif // ENABLE_SYNC } // read all pending inotify events and queue them for processing int LinuxFileSystemAccess::checkevents([[maybe_unused]] Waiter* waiter) { int result = 0; #ifdef ENABLE_SYNC if (mNotifyFd < 0) return result; // Called so that related syncs perform a rescan. auto notifyTransientFailure = [&]() { for (auto* notifier : mNotifiers) ++notifier->mErrorCount; }; auto* w = static_cast(waiter); if (!MEGA_FD_ISSET(mNotifyFd, &w->rfds)) return result; char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; ssize_t p, l; inotify_event* in; WatchMapIterator it; string localpath; auto notifyAll = [&](int handle, const string& name) { // Loop over and notify all associated nodes. auto associated = mWatches.equal_range(handle); for (auto i = associated.first; i != associated.second;) { // Convenience. using std::move; auto& node = *i->second.first; auto& sync = *node.sync; auto& notifier = *sync.dirnotify; LOG_debug << "Filesystem notification:" << " Root: " << node.localname << " Path: " << name; if ((in->mask & IN_DELETE_SELF)) { // The FS directory watched is gone node.mWatchHandle.invalidate(); // Remove it from the container (C++11 and up) i = mWatches.erase(i); } else { ++i; } auto localName = LocalPath::fromPlatformEncodedRelative(name); notifier.notify(notifier.fsEventq, &node, Notification::NEEDS_PARENT_SCAN, std::move(localName)); // We need to rescan the directory if it's changed permissions. // // The reason for this is that we may not have been able to list // the directory's contents before. If we didn't rescan, we // wouldn't notice these files until some other event is // triggered in or below this directory. if (in->mask == (IN_ATTRIB | IN_ISDIR)) notifier.notify(notifier.fsEventq, &node, Notification::FOLDER_NEEDS_SELF_SCAN, LocalPath::fromPlatformEncodedRelative(name)); result |= Waiter::NEEDEXEC; } }; while ((l = read(mNotifyFd, buf, sizeof buf)) > 0) { for (p = 0; p < l; p += offsetof(inotify_event, name) + in->len) { in = (inotify_event*)(buf + p); if ((in->mask & (IN_Q_OVERFLOW | IN_UNMOUNT))) { LOG_err << "inotify " << (in->mask & IN_Q_OVERFLOW ? "IN_Q_OVERFLOW" : "IN_UNMOUNT"); notifyTransientFailure(); } // this flag was introduced in glibc 2.13 and Linux 2.6.36 (released October 20, 2010) #ifndef IN_EXCL_UNLINK #define IN_EXCL_UNLINK 0x04000000 #endif if ((in->mask & (IN_ATTRIB | IN_CREATE | IN_DELETE_SELF | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_CLOSE_WRITE | IN_EXCL_UNLINK))) { LOG_verbose << "Filesystem notification:" << " event " << in->name << ": " << std::hex << in->mask; it = mWatches.find(in->wd); if (it != mWatches.end()) { // What nodes are associated with this handle? notifyAll(it->first, in->len? in->name : ""); } } } } #endif // ENABLE_SYNC return result; } #endif // __linux__ // no legacy DOS garbage here... bool PosixFileSystemAccess::getsname(const LocalPath&, LocalPath&) const { return false; } bool PosixFileSystemAccess::renamelocal(const LocalPath& oldname, const LocalPath& newname, bool override) { AdjustBasePathResult oldnamestr = adjustBasePath(oldname); AdjustBasePathResult newnamestr = adjustBasePath(newname); #ifdef __MACH__ // On case-insensitive file systems, renaming a file to a name that differs // only by case (e.g., "B" → "A" when "a" already exists) does not actually change the case of // the target filename. Instead, the existing "a" is overwritten in content but its original // casing is preserved. // // To ensure that the renamed file takes the exact desired casing ("A" in this case), we // explicitly remove the existing target using unlink() before calling rename(). This guarantees // that the resulting file name matches the requested casing exactly, regardless of file system // case sensitivity. if (override) { unlinklocal(newname); } #endif bool existingandcare = !override && (0 == access(newnamestr.c_str(), F_OK)); if (!existingandcare && !rename(oldnamestr.c_str(), newnamestr.c_str())) { LOG_verbose << "Successfully moved file: " << oldnamestr << " to " << newnamestr; return true; } target_exists = existingandcare || errno == EEXIST || errno == EISDIR || errno == ENOTEMPTY || errno == ENOTDIR; target_name_too_long = errno == ENAMETOOLONG; transient_error = !existingandcare && isTransient(errno); int e = errno; if (e != EEXIST || !skip_targetexists_errorreport) { LOG_warn << "Unable to move file: " << oldnamestr << " to " << newnamestr << ". Error code: " << e; } return false; } bool PosixFileSystemAccess::copylocal(const LocalPath& oldname, const LocalPath& newname, m_time_t mtime) { AdjustBasePathResult oldnamestr = adjustBasePath(oldname); AdjustBasePathResult newnamestr = adjustBasePath(newname); int sfd, tfd; ssize_t t = -1; #ifdef HAVE_SENDFILE // Linux-specific - kernel 2.6.33+ required if ((sfd = open(oldnamestr.c_str(), O_RDONLY | O_DIRECT)) >= 0) { LOG_verbose << "Copying via sendfile"; mode_t mode = umask(0); if ((tfd = open(newnamestr.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_DIRECT, defaultfilepermissions)) >= 0) { umask(mode); while ((t = sendfile(tfd, sfd, NULL, 1024 * 1024 * 1024)) > 0); #else char buf[16384]; if ((sfd = open(oldnamestr.c_str(), O_RDONLY)) >= 0) { LOG_verbose << "Copying via read/write"; mode_t mode = umask(0); if ((tfd = open(newnamestr.c_str(), O_WRONLY | O_CREAT | O_TRUNC, defaultfilepermissions)) >= 0) { umask(mode); while (((t = read(sfd, buf, sizeof buf)) > 0) && write(tfd, buf, static_cast(t)) == t) ; #endif close(tfd); } else { umask(mode); target_exists = errno == EEXIST; target_name_too_long = errno == ENAMETOOLONG; transient_error = isTransient(errno); int e = errno; LOG_warn << "Unable to copy file. Error code: " << e; } close(sfd); } if (!t) { #ifdef ENABLE_SYNC t = !setmtimelocal(newname, mtime); #else // fails in setmtimelocal are allowed in non sync clients. setmtimelocal(newname, mtime); #endif } else { int e = errno; LOG_debug << "Unable to copy file: " << oldnamestr << " to " << newnamestr << ". Error code: " << e; } return !t; } bool PosixFileSystemAccess::unlinklocal(const LocalPath& name) { if (!unlink(adjustBasePath(name).c_str())) { return true; } auto error = errno; transient_error = isTransient(error); LOG_debug << "Couldn't unlink file: " << name.toPath(false) << ": " << strerror(error); return false; } bool PosixFileSystemAccess::isTransient(const int e) { return e == ETXTBSY || e == EBUSY; } // delete all files, folders and symlinks contained in the specified folder // (does not recurse into mounted devices) void PosixFileSystemAccess::emptydirlocal(const LocalPath& nameParam, dev_t basedev) { LocalPath name = nameParam; DIR* dp; dirent* d; int removed; struct stat statbuf; AdjustBasePathResult namestr = adjustBasePath(name); if (!basedev) { if (lstat(namestr.c_str(), &statbuf) || !S_ISDIR(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) { return; } basedev = statbuf.st_dev; } if ((dp = opendir(namestr.c_str()))) { for (;;) { removed = 0; while ((d = readdir(dp))) { if (d->d_type != DT_DIR || *d->d_name != '.' || (d->d_name[1] && (d->d_name[1] != '.' || d->d_name[2]))) { LocalPath newpath{name}; newpath.appendWithSeparator(LocalPath::fromPlatformEncodedRelative(d->d_name), true); AdjustBasePathResult nameStr = adjustBasePath(newpath); if (!lstat(nameStr.c_str(), &statbuf)) { if (!S_ISLNK(statbuf.st_mode) && S_ISDIR(statbuf.st_mode) && statbuf.st_dev == basedev) { emptydirlocal(newpath, basedev); removed |= !rmdir(nameStr.c_str()); } else { removed |= !unlink(nameStr.c_str()); } } } } if (!removed) { break; } rewinddir(dp); } closedir(dp); } } int PosixFileSystemAccess::getdefaultfilepermissions() { return defaultfilepermissions; } void PosixFileSystemAccess::setdefaultfilepermissions(int permissions) { // Sanitize permissions. permissions &= 07777; defaultfilepermissions = permissions | mMinimumFilePermissions; } int PosixFileSystemAccess::getdefaultfolderpermissions() { return defaultfolderpermissions; } void PosixFileSystemAccess::setdefaultfolderpermissions(int permissions) { // Sanitize permissions. permissions &= 07777; defaultfolderpermissions = permissions | mMinimumDirectoryPermissions; } bool PosixFileSystemAccess::rmdirlocal(const LocalPath& name) { emptydirlocal(name); if (!rmdir(adjustBasePath(name).c_str())) { return true; } transient_error = isTransient(errno); return false; } bool PosixFileSystemAccess::mkdirlocal(const LocalPath& name, bool, bool logAlreadyExistsError) { AdjustBasePathResult nameStr = adjustBasePath(name); mode_t mode = umask(0); bool r = !mkdir(nameStr.c_str(), static_cast(defaultfolderpermissions)); umask(mode); if (!r) { target_exists = errno == EEXIST; target_name_too_long = errno == ENAMETOOLONG; if (target_exists) { if (logAlreadyExistsError) { LOG_debug << "Failed to create local directory: " << nameStr << " (already exists)"; } } else { LOG_err << "Error creating local directory: " << nameStr << " errno: " << errno; } transient_error = isTransient(errno); } return r; } std::pair PosixFileSystemAccess::getmtimelocal(const LocalPath& name) { AdjustBasePathResult nameStr = adjustBasePath(name); struct stat statbuf; if (stat(nameStr.c_str(), &statbuf) != 0) { transient_error = isTransient(errno); LOG_err << "Error getting file stats for mtime: " << nameStr << " errno: " << errno << " [isTransient: " << transient_error << "]"; return {false, 0}; } return {true, static_cast(statbuf.st_mtime)}; } bool PosixFileSystemAccess::setmtimelocal(const LocalPath& name, m_time_t mtime) { AdjustBasePathResult nameStr = adjustBasePath(name); struct utimbuf times = { (time_t)mtime, (time_t)mtime }; bool success = !utime(nameStr.c_str(), ×); if (!success) { transient_error = isTransient(errno); LOG_err << "Error setting mtime: " << nameStr << " mtime: " << mtime << " errno: " << errno << " [isTransient: " << transient_error << "]"; } return success; } bool PosixFileSystemAccess::chdirlocal(LocalPath& name) const { return !chdir(adjustBasePath(name).c_str()); } bool PosixFileSystemAccess::expanselocalpath(const LocalPath& source, LocalPath& destination) { return expandLocalPathFileSystem(source, destination); } #ifdef __linux__ string <rimEtcProperty(string &s, const char &c) { size_t pos = s.find_first_not_of(c); s = s.substr(pos == string::npos ? s.length() : pos, s.length()); return s; } string &rtrimEtcProperty(string &s, const char &c) { size_t pos = s.find_last_not_of(c); if (pos != string::npos) { pos++; } s = s.substr(0, pos); return s; } string &trimEtcproperty(string &what) { rtrimEtcProperty(what,' '); ltrimEtcProperty(what,' '); if (what.size() > 1) { if (what[0] == '\'' || what[0] == '"') { rtrimEtcProperty(what, what[0]); ltrimEtcProperty(what, what[0]); } } return what; } string getPropertyFromEtcFile(const char *configFile, const char *propertyName) { ifstream infile(configFile); string line; while (getline(infile, line)) { if (line.length() > 0 && line[0] != '#') { if (!strlen(propertyName)) //if empty return first line { return trimEtcproperty(line); } string key, value; size_t pos = line.find("="); if (pos != string::npos && ((pos + 1) < line.size())) { key = line.substr(0, pos); rtrimEtcProperty(key, ' '); if (!strcmp(key.c_str(), propertyName)) { value = line.substr(pos + 1); return trimEtcproperty(value); } } } } return string(); } string getDistro() { string distro; distro = getPropertyFromEtcFile("/etc/lsb-release", "DISTRIB_ID"); if (!distro.size()) { distro = getPropertyFromEtcFile("/etc/os-release", "ID"); } if (!distro.size()) { distro = getPropertyFromEtcFile("/etc/redhat-release", ""); } if (!distro.size()) { distro = getPropertyFromEtcFile("/etc/debian-release", ""); } if (distro.size() > 20) { distro = distro.substr(0, 20); } transform(distro.begin(), distro.end(), distro.begin(), ::tolower); return distro; } string getDistroVersion() { string version; version = getPropertyFromEtcFile("/etc/lsb-release", "DISTRIB_RELEASE"); if (!version.size()) { version = getPropertyFromEtcFile("/etc/os-release", "VERSION_ID"); } if (version.size() > 10) { version = version.substr(0, 10); } transform(version.begin(), version.end(), version.begin(), ::tolower); return version; } #endif void PosixFileSystemAccess::osversion(string* u, bool /*includeArchExtraInfo*/) const { #ifdef __linux__ string distro = getDistro(); if (distro.size()) { u->append(distro); string distroversion = getDistroVersion(); if (distroversion.size()) { u->append(" "); u->append(distroversion); u->append("/"); } else { u->append("/"); } } #endif utsname uts; if (!uname(&uts)) { u->append(uts.sysname); u->append(" "); u->append(uts.release); u->append(" "); u->append(uts.machine); } } void PosixFileSystemAccess::statsid(string *id) const { #ifdef __ANDROID__ if (!MEGAjvm) { LOG_err << "No JVM found"; return; } try { JNIEnv *env; MEGAjvm->AttachCurrentThread(&env, NULL); jclass appGlobalsClass = env->FindClass("android/app/AppGlobals"); if (!appGlobalsClass) { env->ExceptionClear(); LOG_err << "Failed to get android/app/AppGlobals"; MEGAjvm->DetachCurrentThread(); return; } jmethodID getInitialApplicationMID = env->GetStaticMethodID(appGlobalsClass,"getInitialApplication","()Landroid/app/Application;"); if (!getInitialApplicationMID) { env->ExceptionClear(); LOG_err << "Failed to get getInitialApplication()"; MEGAjvm->DetachCurrentThread(); return; } jobject context = env->CallStaticObjectMethod(appGlobalsClass, getInitialApplicationMID); if (!context) { LOG_err << "Failed to get context"; MEGAjvm->DetachCurrentThread(); return; } jclass contextClass = env->GetObjectClass(context); if (!contextClass) { LOG_err << "Failed to get context class"; MEGAjvm->DetachCurrentThread(); return; } jmethodID getContentResolverMID = env->GetMethodID(contextClass, "getContentResolver", "()Landroid/content/ContentResolver;"); if (!getContentResolverMID) { env->ExceptionClear(); LOG_err << "Failed to get getContentResolver()"; MEGAjvm->DetachCurrentThread(); return; } jobject contentResolver = env->CallObjectMethod(context, getContentResolverMID); if (!contentResolver) { LOG_err << "Failed to get ContentResolver"; MEGAjvm->DetachCurrentThread(); return; } jclass settingsSecureClass = env->FindClass("android/provider/Settings$Secure"); if (!settingsSecureClass) { env->ExceptionClear(); LOG_err << "Failed to get Settings.Secure class"; MEGAjvm->DetachCurrentThread(); return; } jmethodID getStringMID = env->GetStaticMethodID(settingsSecureClass, "getString", "(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;"); if (!getStringMID) { env->ExceptionClear(); LOG_err << "Failed to get getString()"; MEGAjvm->DetachCurrentThread(); return; } jstring idStr = (jstring) env->NewStringUTF("android_id"); if (!idStr) { LOG_err << "Failed to get idStr"; MEGAjvm->DetachCurrentThread(); return; } jstring androidId = (jstring) env->CallStaticObjectMethod(settingsSecureClass, getStringMID, contentResolver, idStr); if (!androidId) { LOG_err << "Failed to get android_id"; env->DeleteLocalRef(idStr); MEGAjvm->DetachCurrentThread(); return; } const char *androidIdString = env->GetStringUTFChars(androidId, NULL); if (!androidIdString) { LOG_err << "Failed to get android_id bytes"; env->DeleteLocalRef(idStr); MEGAjvm->DetachCurrentThread(); return; } id->append(androidIdString); env->DeleteLocalRef(idStr); env->ReleaseStringUTFChars(androidId, androidIdString); MEGAjvm->DetachCurrentThread(); } catch (...) { try { MEGAjvm->DetachCurrentThread(); } catch (...) { } } #elif TARGET_OS_IPHONE #ifdef USE_IOS ios_statsid(id); #endif #elif defined(__MACH__) uuid_t uuid; struct timespec wait = {1, 0}; if (gethostuuid(uuid, &wait)) { return; } char uuid_str[37]; uuid_unparse(uuid, uuid_str); id->append(uuid_str); #else int fd = open("/etc/machine-id", O_RDONLY); if (fd < 0) { fd = open("/var/lib/dbus/machine-id", O_RDONLY); if (fd < 0) { return; } } char buff[512]; ssize_t len = read(fd, buff, 512); close(fd); if (len <= 0) { return; } if (buff[len - 1] == '\n') { len--; } if (len > 0) { id->append(buff, static_cast(len)); } #endif } #if defined(ENABLE_SYNC) #if defined(__linux__) LinuxDirNotify::LinuxDirNotify(LinuxFileSystemAccess& owner, LocalNode& /*root*/, const LocalPath& rootPath): DirNotify(rootPath), mOwner(owner), mNotifiersIt(owner.mNotifiers.insert(owner.mNotifiers.end(), this)) { // Assume our owner couldn't initialize. setFailed(-owner.mNotifyFd, "Unable to create filesystem monitor."); // Did our owner initialize correctly? if (owner.mNotifyFd >= 0) setFailed(0, ""); } LinuxDirNotify::~LinuxDirNotify() { // Remove ourselves from our owner's list of notiifers. mOwner.mNotifiers.erase(mNotifiersIt); } #if defined(USE_INOTIFY) AddWatchResult LinuxDirNotify::addWatch(LocalNode& node, const LocalPath& path, handle fsid) { using std::forward_as_tuple; using std::piecewise_construct; assert(node.type == FOLDERNODE); // Convenience. auto& watches = mOwner.mWatches; auto handle = inotify_add_watch(mOwner.mNotifyFd, path.toPath(false).c_str(), IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_EXCL_UNLINK | IN_MOVED_FROM // event->cookie set as IN_MOVED_TO | IN_MOVED_TO | IN_ONLYDIR); if (handle >= 0) { auto entry = watches.emplace(piecewise_construct, forward_as_tuple(handle), forward_as_tuple(&node, fsid)); return make_pair(entry, WR_SUCCESS); } LOG_warn << "Unable to monitor path for filesystem notifications: " << path.toPath(false).c_str() << ": Descriptor: " << mOwner.mNotifyFd << ": Error: " << errno; if (errno == ENOMEM || errno == ENOSPC) return make_pair(watches.end(), WR_FATAL); return make_pair(watches.end(), WR_FAILURE); } void LinuxDirNotify::removeWatch(WatchMapIterator entry) { LOG_verbose << "removeWatch for handle: " << entry->first; auto& watches = mOwner.mWatches; auto handle = entry->first; assert(handle >= 0); watches.erase(entry); // Removes first instance if (watches.find(handle) != watches.end()) { LOG_warn << " There are more watches under handle: " << handle; auto it = watches.find(handle); while (it!=watches.end() && it->first == handle) { LOG_warn << "Handle: " << handle << " fsid:" << it->second.second; ++it; } return; } auto const removedResult = inotify_rm_watch(mOwner.mNotifyFd, handle); if (removedResult) { LOG_verbose << "inotify_rm_watch for handle: " << handle << " error no: " << errno; } } #endif // USE_INOTIFY #endif // __linux__ #endif //ENABLE_SYNC // Used by directoryScan(...) below to avoid extra stat(...) calls. class UnixStreamAccess : public InputStreamAccess { public: UnixStreamAccess(const char* path, m_off_t size) : mDescriptor(open(path)) , mOffset(0) , mSize(size) { } MEGA_DISABLE_COPY_MOVE(UnixStreamAccess); ~UnixStreamAccess() { if (mDescriptor >= 0) close(mDescriptor); } operator bool() const { return mDescriptor >= 0; } bool read(byte* buffer, unsigned size) override { if (mDescriptor < 0) return false; if (!buffer) return mOffset += (m_off_t)size, true; auto result = pread(mDescriptor, (void*)buffer, size, mOffset); if (result < 0 || (unsigned)result < size) return false; mOffset += result; return true; } m_off_t size() override { return mDescriptor >= 0 ? mSize : -1; } private: // open with O_NOATIME if possible int open(const char *path) { #ifdef TARGET_OS_IPHONE // building for iOS, there is no O_NOATIME flag int fd = ::open(path, O_RDONLY) ; #else // for sync in particular, try to open without setting access-time // we don't want to update that every time we get a fingerprint to see if it's changed // and we don't want to be processing the filesystem notifications that would cause either int fd = ::open(path, O_NOATIME | O_RDONLY); if (fd < 0 && errno == EPERM) { // But then, on some systems (Android) sometimes (for external storage, but not for internal), the call fails if we try to set O_NOATIME fd = ::open(path, O_RDONLY); } #endif return fd; } int mDescriptor; m_off_t mOffset; m_off_t mSize; }; // UnixStreamAccess ScanResult PosixFileSystemAccess::directoryScan(const LocalPath& targetPath, handle expectedFsid, map& known, std::vector& results, bool followSymLinks, unsigned& nFingerprinted) { // Scan path should always be absolute. assert(targetPath.isAbsolute()); // Whether we can reuse an existing fingerprint. // I.e. Can we avoid computing the CRC? auto reuse = [](const FSNode& lhs, const FSNode& rhs) { return lhs.type == rhs.type && lhs.fsid == rhs.fsid && lhs.fingerprint.mtime == rhs.fingerprint.mtime && lhs.fingerprint.size == rhs.fingerprint.size; }; // So we don't duplicate link chasing logic. auto stat = [&](const char* path, struct stat& metadata, bool* followSymLinkHere = nullptr) { auto result = !lstat(path, &metadata); if (!result) return false; bool followSymLink = followSymLinkHere ? *followSymLinkHere : followSymLinks; if (!followSymLink || !S_ISLNK(metadata.st_mode)) return result; return !::stat(path, &metadata); }; // Where we store file information. struct stat metadata; // Try and get information about the scan target. bool scanTarget_followSymLink = true; // Follow symlink for the parent directory, so we retrieve the stats of the path that the symlinks points to if (!stat(targetPath.toPath(false).c_str(), metadata, &scanTarget_followSymLink)) { LOG_warn << "Failed to directoryScan: " << "Unable to stat(...) scan target: " << targetPath << ". Error code was: " << errno; return SCAN_INACCESSIBLE; } // Is the scan target a directory? if (!S_ISDIR(metadata.st_mode)) { LOG_warn << "Failed to directoryScan: " << "Scan target is not a directory: " << targetPath; return SCAN_INACCESSIBLE; } // Are we scanning the directory we think we are? if (expectedFsid != (handle)metadata.st_ino) { LOG_warn << "Failed to directoryScan: " << "Scan target mismatch on expected FSID: " << targetPath << " was " << expectedFsid << " now " << (handle)metadata.st_ino; return SCAN_FSID_MISMATCH; } // Try and open the directory for iteration. auto directory = opendir(targetPath.toPath(false).c_str()); if (!directory) { LOG_warn << "Failed to directoryScan: " << "Unable to open scan target for iteration: " << targetPath << ". Error code was: " << errno; return SCAN_INACCESSIBLE; } // What device is this directory on? auto device = metadata.st_dev; // Iterate over the directory's children. auto entry = readdir(directory); auto path = targetPath; for ( ; entry; entry = readdir(directory)) { // Skip special hardlinks. if (!strcmp(entry->d_name, ".")) continue; if (!strcmp(entry->d_name, "..")) continue; // Push a new scan record. auto& result = (results.emplace_back(), results.back()); result.fsid = (handle)entry->d_ino; result.localname = LocalPath::fromPlatformEncodedRelative(entry->d_name); // Compute this entry's absolute name. LocalPath newpath{path}; newpath.appendWithSeparator(result.localname, false); // Try and get information about this entry. if (!stat(newpath.toPath(false).c_str(), metadata)) { LOG_warn << "directoryScan: " << "Unable to stat(...) file: " << newpath << ". Error code was: " << errno; // Entry's unknown if we can't determine otherwise. result.type = TYPE_UNKNOWN; continue; } result.fingerprint.mtime = metadata.st_mtime; captimestamp(&result.fingerprint.mtime); // Are we dealing with a directory? if (S_ISDIR(metadata.st_mode)) { // Then no fingerprint is necessary. result.fingerprint.size = 0; // Assume this directory isn't a mount point. result.type = FOLDERNODE; // Directory's a mount point. if (device != metadata.st_dev) { // Mark directory as a mount so we can emit a stall. result.type = TYPE_NESTED_MOUNT; // Leave a trail for debuggers. LOG_warn << "directoryScan: " << "Encountered a nested mount: " << newpath << ". Expected device " << major(static_cast(device)) << ":" << minor(static_cast(device)) << ", got device " << major(static_cast(metadata.st_dev)) << ":" << minor(static_cast(metadata.st_dev)); } continue; } result.fingerprint.size = metadata.st_size; // Are we dealing with a special file? if (!S_ISREG(metadata.st_mode)) { LOG_warn << "directoryScan: " << "Encountered a special file: " << newpath << ". Mode flags were: " << (metadata.st_mode & S_IFMT); result.isSymlink = S_ISLNK(metadata.st_mode); result.type = result.isSymlink ? TYPE_SYMLINK: TYPE_SPECIAL; continue; } // We're dealing with a regular file. result.type = FILENODE; #ifdef __MACH__ // 1904/01/01 00:00:00 +0000 GMT. // // Special marker set by Finder when it begins a long-lasting // operation such as copying a file from/to USB storage. // // In some cases, attributes such as mtime or size can be unstable // and effectively meaningless. constexpr auto busyDate = -2082844800; // The file's temporarily unaccessible while it's busy. // // Attributes such as size are pretty much meaningless. result.isBlocked = metadata.st_birthtimespec.tv_sec == busyDate; if (result.isBlocked) { LOG_warn << "directoryScan: " << "Finder has marked this file as busy: " << newpath; continue; } #endif // __MACH__ // Have we processed this file before? auto it = known.find(result.localname); // Can we avoid recomputing this file's fingerprint? if (it != known.end() && reuse(result, it->second)) { result.fingerprint = std::move(it->second.fingerprint); continue; } // Try and open the file for reading. UnixStreamAccess isAccess(newpath.toPath(false).c_str(), result.fingerprint.size); // Only fingerprint the file if we could actually open it. if (!isAccess) { LOG_warn << "directoryScan: " << "Unable to open file for fingerprinting: " << newpath << ". Error was: " << errno; continue; } // Fingerprint the file. result.fingerprint.genfingerprint( &isAccess, result.fingerprint.mtime); ++nFingerprinted; } // We're done iterating the directory. closedir(directory); return SCAN_SUCCESS; } #ifndef __APPLE__ // Determine which device contains the specified path. static std::string deviceOf(const std::string& database, const std::string& path) { // Convenience. using FileDeleter = std::function; using FilePtr = std::unique_ptr; LOG_verbose << "Opening mount database: " << database; // Try and open mount database. FilePtr mounts(setmntent(database.c_str(), "r"), endmntent); // Couldn't open mount database. if (!mounts) { // Latch error. auto error = errno; LOG_warn << "Couldn't open mount database: " << database << ". Error was: " << strerror(error); return std::string(); } // What device contains path? std::string device; // Determines which device is the strongest match. // // As an example consider: // /dev/sda1 -> /mnt/usb // /dev/sda2 -> /mnt/usb/a/b/c // // /dev/sda2 is a better match for /mnt/usb/a/b/c/d. std::size_t score = 0; // Temporary storage space for mount entries. std::string storage(3 * PATH_MAX, '\0'); // Try and determine which device contains path. for (errno = 0; ; ) { struct mntent entry; // Couldn't retrieve mount entry. if (!getmntent_r(mounts.get(), &entry, storage.data(), static_cast(storage.size()))) break; // Where is this device mounted? std::string target = entry.mnt_dir; // Path's too short to be contained by target. if (path.size() < target.size()) continue; // Target doesn't contain path. if (path.compare(0, target.size(), target)) continue; // Existing device is a better match. if (score >= target.size()) continue; // This device is a better match. device = entry.mnt_fsname; score = target.size(); } // Couldn't retrieve mount entry. if (errno) { // Latch error. auto error = errno; LOG_warn << "Couldn't enumerate mount database: " << database << ". Error was: " << std::strerror(error); return std::string(); } // No device seems to contain path. if (device.empty()) { LOG_warn << "No device seems to contain path: " << path; return std::string(); } // Device isn't actually a device. if (device.front() != '/') { LOG_warn << "A virtual device " << device << " seems to contain path: " << path; return std::string(); } // Couldn't resolve symlinks in device. // // This is necessary to correctly handle nodes managed by device-mapper. // Say, the user is using LUKS or LVM. if (!realpath(device.c_str(), storage.data())) { // Latch error. auto error = errno; LOG_warn << "Couldn't resolve device symlink: " << device << ". Error was: " << std::strerror(error); return std::string(); } // Truncate storage down to size. storage.erase(storage.find('\0')); // Sanity. assert(!storage.empty()); // For debugging purposes. LOG_verbose << "Path " << path << " is on device " << storage; // Return device to caller. return storage; } static std::string deviceOf(const std::string& path) { // Which mount databases should we search? static const std::vector databases = { "/proc/mounts", "/etc/mtab" }; // databases // Try and determine which device contains path. for (const auto& database : databases) { // Ask database which devices contains path. auto device = deviceOf(database, path); // Database has a mapping for path. if (!device.empty()) return device; } LOG_warn << "Couldn't determine which device contains path: " << path; // No database has a mapping for this path. return std::string(); } // Compute legacy filesystem fingerprint. static std::uint64_t fingerprintOf(const std::string& path) { struct statfs buffer; // What filesystem contains our path? if (statfs(path.c_str(), &buffer)) { // Latch error. auto error = errno; LOG_warn << "Couldn't retrieve filesystem ID: " << path << ". Error was: " << std::strerror(error); return 0; } std::uint64_t value; // Alias-friendly conversion to uint64_t. std::memcpy(&value, &buffer.f_fsid, sizeof(value)); return ++value; } // Determine the UUID of the specified device. static std::string uuidOf(const std::string& device) { // Convenience. using IteratorDeleter = std::function; using IteratorPtr = std::unique_ptr; std::string path = "/dev/disk/by-uuid"; // Try and open /dev/disk/by-uuid. IteratorPtr iterator(opendir(path.c_str()), closedir); // Couldn't open /dev/disk/by-uuid. if (!iterator) { // Latch error. auto error = errno; LOG_warn << "Couldn't determine device UUID: " << strerror(error); return std::string(); } // Convenience. auto size = path.size(); // Try and determine which entry references device. for (errno = 0; ; ) { // Try and retrieve next directory entry. const auto* entry = readdir(iterator.get()); // Couldn't retrieve directory entry. if (!entry) break; // Restore path's size. path.resize(size); // Extract entry's name. auto name = std::string(entry->d_name); // Compute path of directory entry. path.append(1, '/'); path.append(name); // Temporary storage. std::string storage(PATH_MAX, '\0'); // Couldn't resolve link. if (!realpath(path.c_str(), storage.data())) { // Latch error. auto error = errno; LOG_warn << "[uuidOf] Couldn't resolve path link: '" << storage << "'. Error was: " << std::strerror(error); continue; } // Truncate storage down to size. storage.erase(storage.find('\0')); // Sanity. assert(!storage.empty()); // Resolved path matches our device. if (device == storage) return name; } // Couldn't determine device's UUID. return std::string(); } fsfp_t FileSystemAccess::fsFingerprint(const LocalPath& path) const { // Try and compute legacy filesystem fingerprint. auto fingerprint = fingerprintOf(path.toPath(false)); // Couldn't compute legacy fingerprint. if (!fingerprint) return fsfp_t(); // What device contains the specified path? auto device = deviceOf(path.toPath(false)); // We know what device contains path. if (!device.empty()) { // Try and determine the device's UUID. auto uuid = uuidOf(device); // We retrieved the device's UUID. if (!uuid.empty()) return fsfp_t(fingerprint, std::move(uuid)); } // Couldn't determine filesystem UUID. return fsfp_t(fingerprint, std::string()); } #endif // ! __APPLE__ #ifdef ENABLE_SYNC bool PosixFileSystemAccess::fsStableIDs(const LocalPath& path) const { FileSystemType type; if (!getlocalfstype(path, type)) { LOG_err << "Failed to get filesystem type. Error code:" << errno; return true; } return type != FS_EXFAT && type != FS_FAT32 && type != FS_FUSE && type != FS_LIFS; } #endif // ENABLE_SYNC bool PosixFileSystemAccess::hardLink(const LocalPath& source, const LocalPath& target) { AdjustBasePathResult sourcePath = adjustBasePath(source); AdjustBasePathResult targetPath = adjustBasePath(target); if (link(sourcePath.c_str(), targetPath.c_str())) { LOG_warn << "Unable to create hard link from " << sourcePath << " to " << targetPath << ". Error code was: " << errno; return false; } return true; } std::unique_ptr PosixFileSystemAccess::newfileaccess(bool followSymLinks) { #ifndef __ANDROID__ return std::unique_ptr{new PosixFileAccess{waiter, defaultfilepermissions, followSymLinks}}; #else if (fileWrapper != nullptr) { return std::unique_ptr{ new AndroidFileAccess{waiter, defaultfilepermissions, followSymLinks}}; } else { return std::unique_ptr{ new PosixFileAccess{waiter, defaultfilepermissions, followSymLinks}}; } #endif } unique_ptr PosixFileSystemAccess::newdiraccess() { #ifndef __ANDROID__ return unique_ptr(new PosixDirAccess()); #else if (fileWrapper != nullptr) { return unique_ptr(new AndroidDirAccess()); } else { return unique_ptr(new PosixDirAccess()); } #endif } #ifdef __linux__ #ifdef ENABLE_SYNC DirNotify* LinuxFileSystemAccess::newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter*) { return new LinuxDirNotify(*this, root, rootPath); } #endif #endif bool PosixFileSystemAccess::issyncsupported(const LocalPath& localpathArg, bool& isnetwork, SyncError& syncError, SyncWarning& syncWarning) { // What filesystem is hosting our sync? auto type = getlocalfstype(localpathArg); // Is it a known network filesystem? isnetwork = isNetworkFilesystem(type); if (isnetwork) { LOG_debug << "Network folder detected"; } syncError = NO_SYNC_ERROR; syncWarning = NO_SYNC_WARNING; return true; } bool PosixFileSystemAccess::getlocalfstype(const LocalPath& path, FileSystemType& type) const { #if defined(__linux__) || defined(__ANDROID__) struct statfs statbuf; if (!statfs(path.toPath(false).c_str(), &statbuf)) { switch (static_cast(statbuf.f_type)) { case EXT2_SUPER_MAGIC: type = FS_EXT; break; case MSDOS_SUPER_MAGIC: type = FS_FAT32; break; case HFS_SUPER_MAGIC: case HFSPLUS_SUPER_MAGIC: type = FS_HFS; break; case NTFS_SB_MAGIC: type = FS_NTFS; break; #if defined(__ANDROID__) case F2FS_SUPER_MAGIC: type = FS_F2FS; break; case FUSEBLK_SUPER_MAGIC: case FUSECTL_SUPER_MAGIC: type = FS_FUSE; break; case SDCARDFS_SUPER_MAGIC: type = FS_SDCARDFS; break; #endif /* __ANDROID__ */ case XFS_SUPER_MAGIC: type = FS_XFS; break; case CIFS_MAGIC_NUMBER: type = FS_CIFS; break; case NFS_SUPER_MAGIC: type = FS_NFS; break; case SMB_SUPER_MAGIC: type = FS_SMB; break; case SMB2_MAGIC_NUMBER: type = FS_SMB2; break; default: type = FS_UNKNOWN; break; } return true; } #endif /* __linux__ || __ANDROID__ */ #if defined(__APPLE__) || defined(USE_IOS) static const map filesystemTypes = { {"apfs", FS_APFS}, {"exfat", FS_EXFAT}, {"hfs", FS_HFS}, {"msdos", FS_FAT32}, {"nfs", FS_NFS}, {"ntfs", FS_NTFS}, // Apple NTFS {"smbfs", FS_SMB}, {"tuxera_ntfs", FS_NTFS}, // Tuxera NTFS for Mac {"ufsd_NTFS", FS_NTFS}, // Paragon NTFS for Mac {"lifs", FS_LIFS}, // on macos (in Ventura at least), external USB with exFAT are reported as "lifs" }; /* filesystemTypes */ struct statfs statbuf; if (!statfs(path.toPath(false).c_str(), &statbuf)) { auto it = filesystemTypes.find(statbuf.f_fstypename); if (it != filesystemTypes.end()) { type = it->second; return true; } type = FS_UNKNOWN; return true; } #endif /* __APPLE__ || USE_IOS */ type = FS_UNKNOWN; return false; } bool PosixDirAccess::dopen(LocalPath* path, FileAccess* f, bool doglob) { if (doglob) { if (glob(adjustBasePath(*path).c_str(), GLOB_NOSORT, NULL, &globbuf)) { return false; } globbing = true; globindex = 0; return true; } if (f) { #ifdef HAVE_FDOPENDIR dp = fdopendir(((PosixFileAccess*)f)->stealFileDescriptor()); #else dp = ((PosixFileAccess*)f)->dp; ((PosixFileAccess*)f)->dp = NULL; #endif } else { dp = opendir(adjustBasePath(*path).c_str()); } return dp != NULL; } bool PosixDirAccess::dnext(LocalPath& path, LocalPath& name, bool followsymlinks, nodetype_t* type) { if (globbing) { struct stat &statbuf = currentItemStat; while (globindex < globbuf.gl_pathc) { if (followsymlinks ? !stat(globbuf.gl_pathv[globindex], &statbuf) : !lstat(globbuf.gl_pathv[globindex], ¤tItemStat)) { if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) // this evaluates false for symlinks //if (statbuf.st_mode & (S_IFREG | S_IFDIR)) //TODO: use this when symlinks are supported { name = LocalPath::fromPlatformEncodedAbsolute(globbuf.gl_pathv[globindex]); *type = (statbuf.st_mode & S_IFREG) ? FILENODE : FOLDERNODE; globindex++; return true; } } globindex++; } return false; } dirent* d; struct stat &statbuf = currentItemStat; while ((d = readdir(dp))) { LocalPath newpath{path}; if (*d->d_name != '.' || (d->d_name[1] && (d->d_name[1] != '.' || d->d_name[2]))) { newpath.appendWithSeparator(LocalPath::fromPlatformEncodedRelative(d->d_name), true); AdjustBasePathResult pathStr = adjustBasePath(newpath); bool statOk = !lstat(pathStr.c_str(), &statbuf); if (followsymlinks && statOk && S_ISLNK(statbuf.st_mode)) { currentItemFollowedSymlink = true; statOk = !stat(pathStr.c_str(), &statbuf); } else { currentItemFollowedSymlink = false; } if (statOk) { if (S_ISREG(statbuf.st_mode) || S_ISDIR(statbuf.st_mode)) // this evalves false for symlinks //if (statbuf.st_mode & (S_IFREG | S_IFDIR)) //TODO: use this when symlinks are supported { name = LocalPath::fromPlatformEncodedRelative(d->d_name); if (type) { *type = S_ISREG(statbuf.st_mode) ? FILENODE : FOLDERNODE; } return true; } } } } return false; } PosixDirAccess::PosixDirAccess() { dp = NULL; globbing = false; memset(&globbuf, 0, sizeof(glob_t)); globindex = 0; } PosixDirAccess::~PosixDirAccess() { if (dp) { closedir(dp); } if (globbing) { globfree(&globbuf); } } // A more robust implementation would check whether the device has storage // quotas enabled and if so, return the amount of space available before // saturating that quota. m_off_t PosixFileSystemAccess::availableDiskSpace(const LocalPath& drivePath) { struct statfs buffer; m_off_t constexpr maximumBytes = std::numeric_limits::max(); if (statfs(adjustBasePath(drivePath).c_str(), &buffer) < 0) { auto result = errno; LOG_warn << "Unable to determine available disk space on volume: " << drivePath << ". Error code was: " << result; return maximumBytes; } uint64_t availableBytes = buffer.f_bavail * (uint64_t)buffer.f_bsize; if (availableBytes >= (uint64_t)maximumBytes) return maximumBytes; return (m_off_t)availableBytes; } } // namespace sdk-10.11.0/src/posix/gfx/000077500000000000000000000000001516266226600151645ustar00rootroot00000000000000sdk-10.11.0/src/posix/gfx/worker/000077500000000000000000000000001516266226600164755ustar00rootroot00000000000000sdk-10.11.0/src/posix/gfx/worker/comms.cpp000066400000000000000000000021351516266226600203200ustar00rootroot00000000000000#include "mega/posix/gfx/worker/comms.h" #include "mega/posix/gfx/worker/socket_utils.h" #include "mega/logging.h" #include using std::chrono::milliseconds; namespace mega { namespace gfx { Socket::Socket(Socket&& other) { this->mSocket = other.mSocket; this->mName = std::move(other.mName); other.mSocket = -1; } Socket::~Socket() { if (isValid()) { ::close(mSocket); LOG_verbose << "Socket " << mName << "_" << mSocket << " closed"; } } bool Socket::doWrite(const void* data, size_t n, milliseconds timeout) { const auto errorCode = SocketUtils::write(mSocket, data, n, timeout); if (errorCode) { LOG_err << "Write to socket " << mName << "_" << mSocket << " error: " << errorCode.message(); } return !errorCode; } bool Socket::doRead(void* out, size_t n, milliseconds timeout) { const auto errorCode = SocketUtils::read(mSocket, out, n, timeout); if (errorCode) { LOG_err << "Read from socket " << mName << "_" << mSocket << " error: " << errorCode.message(); } return !errorCode; } } // namespace } sdk-10.11.0/src/posix/gfx/worker/comms_client.cpp000066400000000000000000000020551516266226600216570ustar00rootroot00000000000000#include "mega/posix/gfx/worker/comms_client.h" #include "mega/posix/gfx/worker/socket_utils.h" #include "mega/posix/gfx/worker/comms.h" namespace mega { namespace gfx { GfxCommunicationsClient::GfxCommunicationsClient(const std::string& socketName) : mSocketName(socketName) { } std::pair> GfxCommunicationsClient::connect() { auto [errorCode, fd] = SocketUtils::connect(SocketUtils::toSocketPath(mSocketName)); if (errorCode) { return {toCommError(errorCode.value()), nullptr}; } std::unique_ptr endpoint = std::make_unique(fd, "client"); return {CommError::OK, std::move(endpoint)}; } CommError GfxCommunicationsClient::toCommError(int error) const { switch (error) { case ENOENT: // case socket hasn't been created yet case ECONNREFUSED: // case socket is created, but server is not listenning { return CommError::NOT_EXIST; } default: { return CommError::ERR; } } } } }sdk-10.11.0/src/posix/gfx/worker/socket_utils.cpp000066400000000000000000000220271516266226600217140ustar00rootroot00000000000000#include "mega/posix/gfx/worker/socket_utils.h" #include "mega/logging.h" #include "mega/scoped_timer.h" #include #include #include #include #include #include #include using std::chrono::milliseconds; using std::error_code; using std::chrono::duration_cast; using std::system_category; namespace fs = std::filesystem; namespace { // Refer man pages for read, write, accept and etc.. For these errors, we can/shall retry. bool isRetryErrorNo(int errorNo) { return errorNo == EAGAIN || errorNo == EWOULDBLOCK || errorNo == EINTR; } // Refer man page for poll bool isPollError(int event) { return event & (POLLERR | POLLHUP | POLLNVAL); } // Poll a group of file descriptors. It deals with EINTR. error_code poll(std::vector fds, milliseconds timeout) { const mega::ScopedSteadyTimer timer; milliseconds remaining{timeout}; //Remaining timeout in case of EINTR int ret = 0; do { ret = ::poll(fds.data(), static_cast(fds.size()), static_cast(timeout.count())); remaining -= duration_cast(timer.passedTime()); } while (ret < 0 && errno == EINTR && remaining > milliseconds{0}); if (ret < 0) { LOG_err << "Failed to poll: " << errno; return error_code{errno, system_category()}; } if (ret == 0) { return error_code{ETIMEDOUT, system_category()}; } return error_code{}; } // Poll a single file descriptor error_code pollFd(int fd, short events, milliseconds timeout) { // Poll std::vector fds{ {.fd = fd, .events = events, .revents = 0} }; if (auto errorCode = poll(fds, timeout)) { return errorCode; } // check if the poll returns an error event const auto& polledFd = fds.front(); if (isPollError(polledFd.revents)) { return error_code{ECONNABORTED, system_category()}; } return error_code{}; } error_code pollForRead(int fd, milliseconds timeout) { return pollFd(fd, POLLIN, timeout); } error_code pollForWrite(int fd, milliseconds timeout) { return pollFd(fd, POLLOUT, timeout); } error_code pollForAccept(int fd, milliseconds timeout) { return pollFd(fd, POLLIN, timeout); } constexpr size_t maxSocketPathLength() { return sizeof(sockaddr_un::sun_path); } // The caller should check that the socketPath does not exceed the capability of sun_path // to avoid truncation sockaddr_un initSocketAddr(const std::string& socketPath) { // Zero initialization with the address family value AF_UNIX sockaddr_un addr{}; addr.sun_family = AF_UNIX; // Calculate the count for copying and leave one for null size_t count = std::min(socketPath.size(), maxSocketPathLength() - 1); std::copy_n(socketPath.begin(), count, addr.sun_path); return addr; } // Bind the fd on the socketPath and listen on it error_code doBindAndListen(int fd, const std::string& socketPath) { constexpr int QUEUE_LEN = 10; // Extra 1 for null terminated if (socketPath.size() + 1 > maxSocketPathLength()) { LOG_err << "Unix domain socket name is too long, " << socketPath; return error_code{ENAMETOOLONG, system_category()}; } auto addr = initSocketAddr(socketPath); // Bind name if (::bind(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { LOG_err << "Failed to bind UNIX domain socket name: " << socketPath << " errno: " << errno; return error_code{errno, system_category()}; } // Listen if (::listen(fd, QUEUE_LEN) < 0) { LOG_err << "Failed to listen UNIX domain socket name: " << socketPath << " errno: " << errno; return error_code{errno, system_category()}; } // Success return error_code{}; } std::error_code createDirectories(const fs::path& directory) { std::error_code errorCode; fs::create_directories(directory, errorCode); return errorCode; } std::error_code removePath(const fs::path& path) { std::error_code errorCode; fs::remove(path, errorCode); return errorCode; } } namespace mega { namespace gfx { fs::path SocketUtils::toSocketPath(const std::string& name) { return fs::path{"/tmp"} / ("MegaLimited" + std::to_string(getuid())) / name; } std::error_code SocketUtils::removeSocketFile(const std::string& name) { return removePath(toSocketPath(name)); } std::pair SocketUtils::accept(int listeningFd, milliseconds timeout) { do { const auto errorCode = pollForAccept(listeningFd, timeout); if (errorCode) { return {errorCode, -1}; } const auto dataSocket = ::accept(listeningFd, nullptr, nullptr); // Success if (dataSocket >= 0) { return {error_code{}, dataSocket}; } assert(dataSocket < 0); // None retry errors if (!isRetryErrorNo(errno)) { return {error_code{errno, system_category()}, -1}; } // Retry errors assert(isRetryErrorNo(errno)); } while (true); } error_code SocketUtils::write(int fd, const void* data, size_t n, milliseconds timeout) { size_t offset = 0; while (offset < n) { // Poll if (const auto errorCode = pollForWrite(fd, timeout)) { LOG_err << "Failed to pollForWrite, " << errorCode.message(); return errorCode; } // Write const size_t remaining = n - offset; const ssize_t written = ::write(fd, static_cast(data) + offset, remaining); // Non retry errors if (written < 0 && !isRetryErrorNo(errno)) { LOG_err << "Failed to write, errno: " << errno; return error_code{errno, system_category()}; } // Retry errors if (written < 0 && isRetryErrorNo(errno)) { continue; } // Success assert(written >= 0); offset += static_cast(written); } return error_code{}; } error_code SocketUtils::read(int fd, void* buf, size_t n, milliseconds timeout) { size_t offset = 0; while (offset < n) { // Poll if (const auto errorCode = pollForRead(fd, timeout)) { LOG_err << "Failed to pollForRead, " << errorCode.message(); return errorCode; } // Read const size_t remaining = n - offset; const ssize_t bytesRead = ::read(fd, static_cast(buf) + offset, remaining); // None retry errors if (bytesRead < 0 && !isRetryErrorNo(errno)) { LOG_err << "Failed to read, errno: " << errno; return error_code{errno, system_category()}; } // Retry errors if (bytesRead < 0 && isRetryErrorNo(errno)) { continue; } // End of file and not read all needed if (bytesRead == 0 && offset < n) { LOG_err << "Failed to read, aborted"; return error_code{ECONNABORTED, system_category()}; } // Success assert(bytesRead >= 0); offset += static_cast(bytesRead); } // Success return error_code{}; } std::pair SocketUtils::connect(const fs::path& socketPath) { // Extra 1 for null terminated if (socketPath.native().size() + 1 > maxSocketPathLength()) { LOG_err << "Unix domain socket name is too long, " << socketPath.string(); return {error_code{ENAMETOOLONG, system_category()}, -1}; } auto fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { LOG_err << "Failed to create a UNIX domain socket: " << socketPath.string() << " errno: " << errno; return {error_code{errno, system_category()}, -1}; } auto addr = initSocketAddr(socketPath.native()); if (::connect(fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { LOG_err << "Failed to connect " << socketPath.string() << " errno: " << errno; ::close(fd); return {error_code{errno, system_category()}, -1}; } return {error_code{}, fd}; } std::pair SocketUtils::listen(const fs::path& socketPath) { // Try to remove, it may not exist removePath(socketPath); // Try to create path, it may already exist createDirectories(socketPath.parent_path()); const auto socketPathStr = socketPath.string(); // Create a UNIX domain socket const auto fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { LOG_err << "Failed to create a UNIX domain socket: " << socketPathStr << " errno: " << errno; return {error_code{errno, system_category()}, -1}; } // Bind and Listen on if (const auto error_code = doBindAndListen(fd, socketPathStr)) { ::close(fd); return { error_code, -1}; } // Success LOG_verbose << "Listening on UNIX domain socket name: " << socketPathStr; return {error_code{}, fd}; } } // namespace } sdk-10.11.0/src/posix/net.cpp000066400000000000000000002204131516266226600156740ustar00rootroot00000000000000/** * @file posix/net.cpp * @brief POSIX network access layer (using cURL) * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/hashcash.h" #include "mega/logging.h" #include "mega/posix/meganet.h" #include "mega/testhooks.h" #include "mega/utils.h" #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN // Reduce content pulled in by Windows.h -> speed up compile time #include #include #include #else // _WIN32 #include #include #endif // ! _WIN32 #if defined(USE_OPENSSL) #include #endif #include #define MAX_SPEED_CONTROL_TIMEOUT_MS 500 namespace mega { std::atomic g_netLoggingOn{false}; #define NET_verbose if (g_netLoggingOn) LOG_verbose #define NET_debug if (g_netLoggingOn) LOG_debug #if defined(_WIN32) HANDLE SockInfo::sharedEventHandle() { return mSharedEvent; } bool SockInfo::createAssociateEvent() { int events = (mode & SockInfo::READ ? FD_READ : 0) | (mode & SockInfo::WRITE ? FD_WRITE : 0); if (associatedHandleEvents != events) { if (WSAEventSelect(fd, mSharedEvent, events)) { auto err = WSAGetLastError(); LOG_err << "WSAEventSelect failed " << fd << " " << mSharedEvent << " " << events << " " << err; closeEvent(); return false; } associatedHandleEvents = events; } return true; } bool SockInfo::checkEvent(bool& read, bool& write, bool logErr) { WSANETWORKEVENTS wne; memset(&wne, 0, sizeof(wne)); auto err = WSAEnumNetworkEvents(fd, NULL, &wne); if (err) { if (logErr) { auto e = WSAGetLastError(); LOG_err << "WSAEnumNetworkEvents error " << e; } return false; } read = 0 != (FD_READ & wne.lNetworkEvents); write = 0 != (FD_WRITE & wne.lNetworkEvents); // Even though the writeable network event occurred, double check there is no space available in the write buffer // Otherwise curl can report a spurious timeout error if (FD_WRITE & associatedHandleEvents) { // per https://curl.haxx.se/mail/lib-2009-10/0313.html check if the socket has any buffer space // The trick is that we want to wait on the event handle to know when we can read and write // that works fine for read, however for write the event is not signalled in the normal case // where curl wrote to the socket, but not enough to cause it to become unwriteable for now. // So, we need to signal curl to write again if it has more data to write, if the socket can take // more data. This trick with WSASend for 0 bytes enables that - if it fails with would-block // then we can stop asking curl to write to the socket, and start waiting on the handle to // know when to try again. // If curl has finished writing to the socket, it will call us back to change the mode to read only. WSABUF buf{ 0, (CHAR*)&buf }; DWORD bSent = 0; auto writeResult = WSASend(fd, &buf, 1, &bSent, 0, NULL, NULL); auto writeError = WSAGetLastError(); write = writeResult == 0 || (writeError != WSAEWOULDBLOCK && writeError != WSAENOTCONN); if (writeResult != 0 && writeError != WSAEWOULDBLOCK && writeError != WSAENOTCONN) { LOG_err << "Unexpected WSASend check error: " << writeError; } } if (read || write) { signalledWrite = signalledWrite || write; return true; // if we return true, both read and write must have been set. } return false; } void SockInfo::closeEvent(bool adjustSocket) { if (adjustSocket) { int result = WSAEventSelect(fd, NULL, 0); // cancel association by specifying lNetworkEvents = 0 if (result) { auto err = WSAGetLastError(); LOG_err << "WSAEventSelect error: " << err; } } associatedHandleEvents = 0; signalledWrite = false; } SockInfo::SockInfo(SockInfo&& o) : fd(o.fd) , mode(o.mode) , signalledWrite(o.signalledWrite) , mSharedEvent(o.mSharedEvent) , associatedHandleEvents(o.associatedHandleEvents) { } SockInfo::~SockInfo() { } #endif std::mutex CurlHttpIO::curlMutex; #if defined(USE_OPENSSL) && !defined(OPENSSL_IS_BORINGSSL) std::recursive_mutex **CurlHttpIO::sslMutexes = NULL; static std::mutex lock_init_mutex; void CurlHttpIO::locking_function(int mode, int lockNumber, const char *, int) { std::recursive_mutex *mutex = sslMutexes[lockNumber]; if (mutex == NULL) { // we still have to be careful about multiple threads getting to this point simultaneously lock_init_mutex.lock(); mutex = sslMutexes[lockNumber]; if (!mutex) { mutex = sslMutexes[lockNumber] = new std::recursive_mutex; } lock_init_mutex.unlock(); } if (mode & CRYPTO_LOCK) { mutex->lock(); } else { mutex->unlock(); } } #if OPENSSL_VERSION_NUMBER >= 0x10000000 || defined (LIBRESSL_VERSION_NUMBER) void CurlHttpIO::id_function([[maybe_unused]] CRYPTO_THREADID* id) { CRYPTO_THREADID_set_pointer(id, (void *)THREAD_CLASS::currentThreadId()); } #else unsigned long CurlHttpIO::id_function() { return THREAD_CLASS::currentThreadId(); } #endif #endif CurlHttpIO::CurlHttpIO() { #ifdef WIN32 mSocketsWaitEvent = WSACreateEvent(); if (mSocketsWaitEvent == WSA_INVALID_EVENT) { LOG_err << "Failed to create WSA event for cURL"; } #endif curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); if (data->version) { LOG_debug << "curl version: " << data->version; } if (data->ssl_version) { LOG_debug << "SSL version: " << data->ssl_version; string curlssl = data->ssl_version; tolower_string(curlssl); if (strstr(curlssl.c_str(), "gskit")) { LOG_fatal << "Unsupported SSL backend (GSKit). Aborting."; throw std::runtime_error("Unsupported SSL backend (GSKit). Aborting."); } if (data->version_num < 0x072c00 // At least curl 7.44.0 #ifdef USE_OPENSSL && !(strstr(curlssl.c_str(), "openssl") && data->version_num > 0x070b00) // or curl 7.11.0 with OpenSSL #endif ) { LOG_fatal << "curl built without public key pinning support. Aborting."; throw std::runtime_error("curl built without public key pinning support. Aborting."); } } if (data->libz_version) { LOG_debug << "libz version: " << data->libz_version; } if (data->zstd_version) { LOG_debug << "zstd version: " << data->zstd_version; } int i; for (i = 0; data->protocols[i]; i++) { if (strstr(data->protocols[i], "http")) { break; } } if (!data->protocols[i] || !(data->features & CURL_VERSION_SSL)) { LOG_fatal << "curl built without HTTP/HTTPS support. Aborting."; throw std::runtime_error("curl built without HTTP/HTTPS support. Aborting."); } if (data->ares) { int version{data->ares_num}; int major{(version >> 16) & 0xFF}; int minor{(version >> 8) & 0xFF}; int patch{version & 0xFF}; LOG_debug << "curl built with c-ares backend as DNS resolver."; LOG_debug << "c-ares version: " << major << "." << minor << "." << patch; } dnsok = false; reset = false; statechange = false; disconnecting = false; maxspeed[GET] = 0; maxspeed[PUT] = 0; pkpErrors = 0; WAIT_CLASS::bumpds(); curlMutex.lock(); #if defined(USE_OPENSSL) && !defined(OPENSSL_IS_BORINGSSL) // It's needed to check if sslMutexes have been already initialized because // in OpenSSL versions >= 1.1.0 these mutexes are not needed anymore and // CRYPTO_get_locking_callback() always returns NULL. // OPENSSL_VERSION_NUMBER could be used to skip this initialization, but // since there are so many implementations of OpenSSL, I think that it's // safer to provide the mutexes even if they are not really needed. if (!CRYPTO_get_locking_callback() && !sslMutexes #if OPENSSL_VERSION_NUMBER >= 0x10000000 || defined (LIBRESSL_VERSION_NUMBER) && !CRYPTO_THREADID_get_callback()) #else && !CRYPTO_get_id_callback()) #endif { LOG_debug << "Initializing OpenSSL locking callbacks"; size_t numLocks = CRYPTO_num_locks(); sslMutexes = new std::recursive_mutex*[numLocks]; memset(sslMutexes, 0, numLocks * sizeof(std::recursive_mutex*)); #if OPENSSL_VERSION_NUMBER >= 0x10000000 || defined (LIBRESSL_VERSION_NUMBER) ((void)(CRYPTO_THREADID_set_callback(CurlHttpIO::id_function))); #else CRYPTO_set_id_callback(CurlHttpIO::id_function); #endif CRYPTO_set_locking_callback(CurlHttpIO::locking_function); } #endif if (++instanceCount == 1) { curl_global_init(CURL_GLOBAL_DEFAULT); }; curlMutex.unlock(); curlm[API] = curl_multi_init(); curlm[GET] = curl_multi_init(); curlm[PUT] = curl_multi_init(); numconnections[API] = 0; numconnections[GET] = 0; numconnections[PUT] = 0; curl_multi_setopt(curlm[API], CURLMOPT_SOCKETFUNCTION, api_socket_callback); curl_multi_setopt(curlm[API], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[API], CURLMOPT_TIMERFUNCTION, api_timer_callback); curl_multi_setopt(curlm[API], CURLMOPT_TIMERDATA, this); curltimeoutreset[API] = -1; arerequestspaused[API] = false; curl_multi_setopt(curlm[GET], CURLMOPT_SOCKETFUNCTION, download_socket_callback); curl_multi_setopt(curlm[GET], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[GET], CURLMOPT_TIMERFUNCTION, download_timer_callback); curl_multi_setopt(curlm[GET], CURLMOPT_TIMERDATA, this); #ifdef _WIN32 curl_multi_setopt(curlm[GET], CURLMOPT_MAXCONNECTS, 200); #endif curltimeoutreset[GET] = -1; arerequestspaused[GET] = false; curl_multi_setopt(curlm[PUT], CURLMOPT_SOCKETFUNCTION, upload_socket_callback); curl_multi_setopt(curlm[PUT], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[PUT], CURLMOPT_TIMERFUNCTION, upload_timer_callback); curl_multi_setopt(curlm[PUT], CURLMOPT_TIMERDATA, this); #ifdef _WIN32 curl_multi_setopt(curlm[PUT], CURLMOPT_MAXCONNECTS, 200); #endif curltimeoutreset[PUT] = -1; arerequestspaused[PUT] = false; curlsh = curl_share_init(); curl_share_setopt(curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); curl_share_setopt(curlsh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); contenttypejson = curl_slist_append(NULL, "Content-Type: application/json"); contenttypejson = curl_slist_append(contenttypejson, "Expect:"); contenttypebinary = curl_slist_append(NULL, "Content-Type: application/octet-stream"); contenttypebinary = curl_slist_append(contenttypebinary, "Expect:"); proxyinflight = 0; waiter = NULL; proxyport = 0; proxytype = Proxy::AUTO; } void CurlHttpIO::addcurlevents(Waiter* eventWaiter, direction_t d) { #ifdef MEGA_MEASURE_CODE CodeCounter::ScopeTimer ccst(countAddCurlEventsCode); #endif #if defined(_WIN32) bool anyWriters = false; #endif SockInfoMap &socketmap = curlsockets[d]; for (SockInfoMap::iterator it = socketmap.begin(); it != socketmap.end(); it++) { SockInfo &info = it->second; if (!info.mode) { continue; } #if defined(_WIN32) anyWriters = anyWriters || info.signalledWrite; info.signalledWrite = false; info.createAssociateEvent(); #else if (info.mode & SockInfo::READ) { MEGA_FD_SET(info.fd, &((PosixWaiter*)eventWaiter)->rfds); ((PosixWaiter*)eventWaiter)->bumpmaxfd(info.fd); } if (info.mode & SockInfo::WRITE) { MEGA_FD_SET(info.fd, &((PosixWaiter*)eventWaiter)->wfds); ((PosixWaiter*)eventWaiter)->bumpmaxfd(info.fd); } #endif } #if defined(_WIN32) if (anyWriters) { // so long as we are writing at least one socket, keep looping until the socket is full, then start waiting on its associated event static_cast(eventWaiter)->maxds = 0; } #endif } int CurlHttpIO::checkevents(Waiter*) { #ifdef WIN32 // if this assert triggers, it means that we detected that cURL needs to be called, // and it was not called. Since we reset the event, we don't get another chance. assert(!mSocketsWaitEvent_curl_call_needed); bool wasSet = WAIT_OBJECT_0 == WaitForSingleObject(mSocketsWaitEvent, 0); mSocketsWaitEvent_curl_call_needed = wasSet; ResetEvent(mSocketsWaitEvent); return wasSet ? Waiter::NEEDEXEC : 0; #else return 0; #endif } void CurlHttpIO::closecurlevents(direction_t d) { SockInfoMap &socketmap = curlsockets[d]; #if defined(_WIN32) for (SockInfoMap::iterator it = socketmap.begin(); it != socketmap.end(); it++) { it->second.closeEvent(false); } #endif socketmap.clear(); } void CurlHttpIO::processcurlevents(direction_t d) { #ifdef MEGA_MEASURE_CODE CodeCounter::ScopeTimer ccst(countProcessCurlEventsCode); #endif #ifdef WIN32 mSocketsWaitEvent_curl_call_needed = false; #else auto *rfds = &((PosixWaiter *)waiter)->rfds; auto *wfds = &((PosixWaiter *)waiter)->wfds; #endif int dummy = 0; SockInfoMap *socketmap = &curlsockets[d]; bool *paused = &arerequestspaused[d]; for (SockInfoMap::iterator it = socketmap->begin(); !(*paused) && it != socketmap->end();) { SockInfo &info = (it++)->second; if (!info.mode) { continue; } #if defined(_WIN32) bool read, write; if (info.checkEvent(read, write)) // if checkEvent returns true, both `read` and `write` have been set. { //LOG_verbose << "Calling curl for socket " << info.fd << (read && write ? " both" : (read ? " read" : " write")); curl_multi_socket_action(curlm[d], info.fd, (read ? CURL_CSELECT_IN : 0) | (write ? CURL_CSELECT_OUT : 0), &dummy); } #else if (((info.mode & SockInfo::READ) && MEGA_FD_ISSET(info.fd, rfds)) || ((info.mode & SockInfo::WRITE) && MEGA_FD_ISSET(info.fd, wfds))) { curl_multi_socket_action(curlm[d], info.fd, (((info.mode & SockInfo::READ) && MEGA_FD_ISSET(info.fd, rfds)) ? CURL_CSELECT_IN : 0) | (((info.mode & SockInfo::WRITE) && MEGA_FD_ISSET(info.fd, wfds)) ? CURL_CSELECT_OUT : 0), &dummy); } #endif } if (curltimeoutreset[d] >= 0 && curltimeoutreset[d] <= Waiter::ds) { curltimeoutreset[d] = -1; NET_debug << "Informing cURL of timeout reached for " << d << " at " << Waiter::ds; curl_multi_socket_action(curlm[d], CURL_SOCKET_TIMEOUT, 0, &dummy); } for (SockInfoMap::iterator it = socketmap->begin(); it != socketmap->end();) { SockInfo &info = it->second; if (!info.mode) { socketmap->erase(it++); } else { it++; } } } CurlHttpIO::~CurlHttpIO() { disconnecting = true; curl_multi_cleanup(curlm[API]); curl_multi_cleanup(curlm[GET]); curl_multi_cleanup(curlm[PUT]); curl_share_cleanup(curlsh); closecurlevents(API); closecurlevents(GET); closecurlevents(PUT); #ifdef WIN32 WSACloseEvent(mSocketsWaitEvent); #endif curlMutex.lock(); if (--instanceCount == 0) { curl_global_cleanup(); } curlMutex.unlock(); curl_slist_free_all(contenttypejson); curl_slist_free_all(contenttypebinary); } int CurlHttpIO::instanceCount = 0; void CurlHttpIO::setuseragent(string* u) { useragent = *u; } bool CurlHttpIO::setdnsservers(const char* servers) { const curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); if (!data->ares) { return false; } if (servers) { dnsservers = servers; LOG_debug << "Setting custom DNS servers: " << dnsservers; } return true; } void CurlHttpIO::disconnect() { LOG_debug << "Reinitializing the network layer"; disconnecting = true; assert(!numconnections[API] && !numconnections[GET] && !numconnections[PUT]); curl_multi_cleanup(curlm[API]); curl_multi_cleanup(curlm[GET]); curl_multi_cleanup(curlm[PUT]); if (numconnections[API] || numconnections[GET] || numconnections[PUT]) { LOG_err << "Disconnecting without cancelling all requests first"; numconnections[API] = 0; numconnections[GET] = 0; numconnections[PUT] = 0; } closecurlevents(API); closecurlevents(GET); closecurlevents(PUT); curlm[API] = curl_multi_init(); curlm[GET] = curl_multi_init(); curlm[PUT] = curl_multi_init(); curl_multi_setopt(curlm[API], CURLMOPT_SOCKETFUNCTION, api_socket_callback); curl_multi_setopt(curlm[API], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[API], CURLMOPT_TIMERFUNCTION, api_timer_callback); curl_multi_setopt(curlm[API], CURLMOPT_TIMERDATA, this); curltimeoutreset[API] = -1; arerequestspaused[API] = false; curl_multi_setopt(curlm[GET], CURLMOPT_SOCKETFUNCTION, download_socket_callback); curl_multi_setopt(curlm[GET], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[GET], CURLMOPT_TIMERFUNCTION, download_timer_callback); curl_multi_setopt(curlm[GET], CURLMOPT_TIMERDATA, this); #ifdef _WIN32 curl_multi_setopt(curlm[GET], CURLMOPT_MAXCONNECTS, 200); #endif curltimeoutreset[GET] = -1; arerequestspaused[GET] = false; curl_multi_setopt(curlm[PUT], CURLMOPT_SOCKETFUNCTION, upload_socket_callback); curl_multi_setopt(curlm[PUT], CURLMOPT_SOCKETDATA, this); curl_multi_setopt(curlm[PUT], CURLMOPT_TIMERFUNCTION, upload_timer_callback); curl_multi_setopt(curlm[PUT], CURLMOPT_TIMERDATA, this); #ifdef _WIN32 curl_multi_setopt(curlm[PUT], CURLMOPT_MAXCONNECTS, 200); #endif curltimeoutreset[PUT] = -1; arerequestspaused[PUT] = false; disconnecting = false; if (proxyurl.size() && !proxyip.size()) { LOG_debug << "Unresolved proxy name. Resolving..."; request_proxy_ip(); } } bool CurlHttpIO::setmaxdownloadspeed(m_off_t bpslimit) { LOG_debug << "[CurlHttpIO::setmaxdownloadspeed] Set max download speed to " << bpslimit << " B/s"; maxspeed[GET] = bpslimit; return true; } bool CurlHttpIO::setmaxuploadspeed(m_off_t bpslimit) { LOG_debug << "[CurlHttpIO::setmaxuploadspeed] Set max upload speed to " << bpslimit << " B/s"; maxspeed[PUT] = bpslimit; return true; } m_off_t CurlHttpIO::getmaxdownloadspeed() { return maxspeed[GET]; } m_off_t CurlHttpIO::getmaxuploadspeed() { return maxspeed[PUT]; } int CurlHttpIO::cacheresolvedurls(const std::vector& urls, const std::vector& ips) { // Each URI should be associated with an IPv4 and an IPv6 address. if (ips.size() != urls.size() * 2) return -1; // Assume all IPs are valid. auto result = 0; // Add URLs with a valid IPv4 address to the cache. for (auto i = 0u; i < urls.size(); ++i) { // Get a copy of this URI's IPv4 and IPv6 addresses. auto ipv4 = ips[i * 2]; auto ipv6 = ips[i * 2 + 1]; // URI doesn't have a valid IPv4 address. if (!isValidIPv4Address(ipv4)) { ipv4.clear(); ++result; } // URI doesn't have a valid IPv6 address. if (!isValidIPv6Address(ipv6)) { ipv6.clear(); ++result; } std::string host; std::string scheme; int port; // Couldn't extract the URI's host name. if (!crackURI(urls[i], scheme, host, port) || host.empty()) continue; // URI isn't in the cache and has no valid IP addresses. if (ipv4.empty() && ipv6.empty() && !dnscache.count(host)) continue; // Add a DNS cache entry for this host. auto& entry = dnscache[host]; // Update the host's IP addresses. entry.ipv4 = std::move(ipv4); entry.ipv6 = std::move(ipv6); } // Let our caller know the cache was updated. return result; } // wake up from cURL I/O void CurlHttpIO::addevents(Waiter* w, int) { #ifdef MEGA_MEASURE_CODE CodeCounter::ScopeTimer ccst(countCurlHttpIOAddevents); #endif waiter = (WAIT_CLASS*)w; long curltimeoutms = -1; addcurlevents(waiter, API); #ifdef WIN32 ((WinWaiter*)waiter)->addhandle(mSocketsWaitEvent, Waiter::NEEDEXEC); #endif if (curltimeoutreset[API] >= 0) { m_time_t ds = curltimeoutreset[API] - Waiter::ds; if (ds <= 0) { curltimeoutms = 0; } else { if (curltimeoutms < 0 || curltimeoutms > ds * 100) { curltimeoutms = long(ds * 100); } } } for (int d = GET; d == GET || d == PUT; d += PUT - GET) { if (arerequestspaused[d]) { if (curltimeoutms < 0 || curltimeoutms > 100) { curltimeoutms = 100; } } else { addcurlevents(waiter, (direction_t)d); if (curltimeoutreset[d] >= 0) { m_time_t ds = curltimeoutreset[d] - Waiter::ds; if (ds <= 0) { curltimeoutms = 0; } else { if (curltimeoutms < 0 || curltimeoutms > ds * 100) { curltimeoutms = long(ds * 100); } } } } } if ((curltimeoutms < 0 || curltimeoutms > MAX_SPEED_CONTROL_TIMEOUT_MS) && (downloadSpeed || uploadSpeed)) { curltimeoutms = MAX_SPEED_CONTROL_TIMEOUT_MS; } if (curltimeoutms >= 0) { m_time_t timeoutds = curltimeoutms / 100; if (curltimeoutms % 100) { timeoutds++; } if (timeoutds < waiter->maxds) { waiter->maxds = dstime(timeoutds); } } } struct curl_slist* CurlHttpIO::clone_curl_slist(struct curl_slist* inlist) { struct curl_slist* outlist = NULL; struct curl_slist* tmp; while (inlist) { tmp = curl_slist_append(outlist, inlist->data); if (!tmp) { curl_slist_free_all(outlist); return NULL; } outlist = tmp; inlist = inlist->next; } return outlist; } const char* CurlHttpIO::pubkeyForUrl(const char* url) const { if (Utils::startswith(url, APIURL.c_str()) || Utils::startswith(url, MegaClient::REQSTATURL.c_str())) { return "sha256//0W38e765pAfPqS3DqSVOrPsC4MEOvRBaXQ7nY1AJ47E=;" // API 1 "sha256//gSRHRu1asldal0HP95oXM/5RzBfP1OIrPjYsta8og80="; // API 2 } else if (Utils::startswith(url, MegaClient::SFUSTATSURL.c_str())) { return "sha256//2ZAltznnzY3Iee3NIZPOgqIQVNXVjvDEjWTmAreYVFU=;" // STATSSFU 1 "sha256//7jLrvaEtfqTCHew0iibvEm2k61iatru+rwhFD7g3nxA="; // STATSSFU 2 } return nullptr; } void CurlHttpIO::send_request(CurlHttpContext* httpctx) { CurlHttpIO* httpio = httpctx->httpio; HttpReq* req = httpctx->req; auto len = httpctx->len; const char* data = httpctx->data; LOG_debug << httpctx->req->getLogName() << req->getMethodString() << " target URL: " << getSafeUrl(req->posturl); if (req->binary) { LOG_debug << httpctx->req->getLogName() << "[sending " << (data ? len : req->out->size()) << " bytes of raw data]"; } else { JSON_SENDING << httpctx->req->getLogName() << "Sending " << req->out->size() << ": " << MaxDirectMessage(req->out->c_str(), req->out->size(), SimpleLogger::getMaxPayloadLogSize()) << " (at ds: " << Waiter::ds << ")"; } req->outpos = 0; httpctx->headers = clone_curl_slist(req->type == REQ_JSON ? httpio->contenttypejson : httpio->contenttypebinary); httpctx->posturl = req->posturl; if (!req->mHashcashToken.empty()) { const auto nextValue = gencash(req->mHashcashToken, req->mHashcashEasiness, req->mCancelSnapshot); string xHashcashHeader{"X-Hashcash: 1:" + req->mHashcashToken + ":" + std::move(nextValue)}; httpctx->headers = curl_slist_append(httpctx->headers, xHashcashHeader.c_str()); LOG_warn << httpctx->req->getLogName() << "X-Hashcash computed: " << xHashcashHeader; req->mHashcashToken.clear(); } CURL* curl; curl = curl_easy_init(); if (curl) { switch (req->method) { case METHOD_POST: curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data ? len : req->out->size()); break; case METHOD_GET: curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break; case METHOD_NONE: curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); break; } if (req->timeoutms) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, req->timeoutms); } curl_easy_setopt(curl, CURLOPT_URL, httpctx->posturl.c_str()); curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_data); curl_easy_setopt(curl, CURLOPT_READDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, seek_data); curl_easy_setopt(curl, CURLOPT_SEEKDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_USERAGENT, httpio->useragent.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, httpctx->headers); curl_easy_setopt(curl, CURLOPT_ENCODING, ""); curl_easy_setopt(curl, CURLOPT_SHARE, httpio->curlsh); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, check_header); curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_PRIVATE, (void*)req); curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, HttpIO::CONNECTTIMEOUT / 10); curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 90L); curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 60L); curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback); curl_easy_setopt(curl, CURLOPT_SOCKOPTDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_QUICK_EXIT, 1L); // Some networks (eg vodafone UK) seem to block TLS 1.3 ClientHello. 1.2 is secure, and works: curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2); if (httpio->maxspeed[GET] && httpio->maxspeed[GET] <= 102400) { LOG_debug << "Low maxspeed, set curl buffer size to 4 KB"; curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 4096L); } if (req->minspeed) { LOG_debug << "Setting low speed limit (<30 Bytes/s) and how much time the speed is allowed to be lower than the limit before aborting (30 secs)"; curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L); curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L); } if (!httpio->disablepkp && req->protect) { #if LIBCURL_VERSION_NUM >= 0x072c00 // At least cURL 7.44.0 if (curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, httpio->pubkeyForUrl(req->posturl)) == CURLE_OK) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); if (httpio->pkpErrors) { curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); } } else #endif { #ifdef USE_OPENSSL // options only available for OpenSSL if (curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, ssl_ctx_function) != CURLE_OK) { LOG_err << "Could not set curl option CURLOPT_SSL_CTX_FUNCTION"; } if (curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, (void*)req) != CURLE_OK) { LOG_err << "Could not set curl option CURLOPT_SSL_CTX_DATA"; } #else LOG_fatal << "cURL built without support for public key pinning. Aborting."; throw std::runtime_error("ccURL built without support for public key pinning. Aborting."); #endif if (curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1) != CURLE_OK) { LOG_err << "Could not set curl option CURLOPT_SSL_VERIFYPEER"; } } } else { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); if (httpio->disablepkp) { LOG_warn << "Public key pinning disabled."; } } curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_CAINFO, NULL); curl_easy_setopt(curl, CURLOPT_CAPATH, NULL); curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, debug_callback); curl_easy_setopt(curl, CURLOPT_DEBUGDATA, (void*)req); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); if (httpio->proxyip.size()) { if (!httpio->proxyschema.size() || !httpio->proxyschema.compare(0, 4, "http")) { LOG_debug << "Using HTTP proxy"; curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); } else if (!httpio->proxyschema.compare(0, 5, "socks")) { LOG_debug << "Using SOCKS proxy"; curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); } else { LOG_warn << "Unknown proxy type"; } curl_easy_setopt(curl, CURLOPT_PROXY, httpio->proxyip.c_str()); curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); if (httpio->proxyusername.size()) { LOG_debug << "Using proxy authentication " << httpio->proxyusername.size() << " " << httpio->proxypassword.size(); curl_easy_setopt(curl, CURLOPT_PROXYUSERNAME, httpio->proxyusername.c_str()); curl_easy_setopt(curl, CURLOPT_PROXYPASSWORD, httpio->proxypassword.c_str()); } else { LOG_debug << "NOT using proxy authentication"; } if(httpctx->port == 443) { curl_easy_setopt(curl, CURLOPT_HTTPPROXYTUNNEL, 1L); } } else if (httpio->proxytype == Proxy::NONE) { curl_easy_setopt(curl, CURLOPT_PROXY, ""); } else if (httpio->proxytype == Proxy::AUTO) { curl_easy_setopt(curl, CURLOPT_PROXY, nullptr); } if (!httpio->dnsservers.empty()) { curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, httpio->dnsservers.c_str()); } if (!httpio->proxyip.size()) { auto it = httpio->dnscache.find(httpctx->hostname); if (it != httpio->dnscache.end()) { std::ostringstream ostream; if (!it->second.ipv4.empty()) ostream << it->second.ipv4; if (!it->second.ipv6.empty()) { if (ostream.tellp() > 0) ostream << ","; ostream << "[" << it->second.ipv6 << "]"; } httpio->addDnsResolution(curl, httpctx->mCurlDnsList, httpctx->hostname, ostream.str(), httpctx->port); } } httpio->numconnections[httpctx->d]++; curl_multi_add_handle(httpio->curlm[httpctx->d], curl); httpctx->curl = curl; } else { req->status = REQ_FAILURE; req->httpiohandle = NULL; curl_slist_free_all(httpctx->headers); httpctx->req = NULL; delete httpctx; } httpio->statechange = true; } void CurlHttpIO::request_proxy_ip() { if (!proxyhost.empty() && proxyport) { // No need to resolve the proxy's IP: cURL will resolve it for us. std::ostringstream ostream; ostream << proxyhost << ":" << proxyport; proxyip = ostream.str(); } } int CurlHttpIO::debug_callback(CURL*, curl_infotype type, char* data, size_t size, void* debugdata) { std::string_view dataView(data, size); while (!dataView.empty() && (dataView.back() == '\r' || dataView.back() == '\n')) // Trim { dataView.remove_suffix(1); } if (dataView.empty()) { return 0; } if (type == CURLINFO_TEXT) { std::string errnoInfo; if (dataView.find("SSL_ERROR_SYSCALL") != std::string_view::npos) { // This function is called quite early by curl code, and hopefully no other call would have // modified errno in the meantime. errnoInfo = " (System errno: " + std::to_string(errno) + #if defined(USE_OPENSSL) "; OpenSSL last err: " + std::to_string(ERR_peek_last_error()) + #endif ")"; } NET_verbose << (debugdata ? static_cast(debugdata)->getLogName() : string()) << "cURL: " << dataView << errnoInfo; } else if (type == CURLINFO_HEADER_IN) { NET_verbose << "CURL incoming header: " << dataView; } else if (type == CURLINFO_HEADER_OUT) { NET_verbose << "CURL outgoing header: " << dataView; } return 0; } // POST request to URL void CurlHttpIO::post(HttpReq* req, const char* data, unsigned len) { CurlHttpContext* httpctx = new CurlHttpContext; httpctx->curl = NULL; httpctx->httpio = this; httpctx->req = req; httpctx->len = len; httpctx->data = data; httpctx->headers = NULL; httpctx->d = (req->type == REQ_JSON || req->method == METHOD_NONE) ? API : ((data ? len : req->out->size()) ? PUT : GET); req->httpiohandle = (void*)httpctx; bool validrequest = true; if ((proxyurl.size() && !proxyhost.size()) // malformed proxy string || (validrequest = crackURI(req->posturl, httpctx->schema, httpctx->hostname, httpctx->port)) != true) // invalid request { if (validrequest) { LOG_err << "Malformed proxy string: " << proxyurl; } else { LOG_err << "Invalid request: " << req->posturl; } delete httpctx; req->httpiohandle = NULL; req->status = REQ_FAILURE; statechange = true; return; } req->in.clear(); req->status = REQ_INFLIGHT; req->postStartTime = std::chrono::steady_clock::now(); if (proxyip.size() && req->method != METHOD_NONE) { // we are using a proxy, don't resolve the IP LOG_debug << "Sending the request through the proxy"; send_request(httpctx); return; } if (proxyurl.size() && proxyinflight) { // we are waiting for a proxy, queue the request pendingrequests.push(httpctx); LOG_debug << "Queueing request for the proxy"; return; } httpctx->hostheader = "Host: "; httpctx->hostheader.append(httpctx->hostname); send_request(httpctx); } std::optional CurlHttpIO::getproxy() const { // No prior proxy configuration. if (proxytype == Proxy::NONE) return std::nullopt; Proxy proxy; // Copy proxy configuration. proxy.setCredentials(proxyusername, proxypassword); proxy.setProxyURL(proxyurl); proxy.setProxyType(proxytype); // Return (possibly invalid) proxy configuration. return proxy; } void CurlHttpIO::setproxy(const Proxy& proxy) { // clear the previous proxy IP proxyip.clear(); if (proxy.getProxyType() != Proxy::CUSTOM || !proxy.getProxyURL().size()) { LOG_debug << "CurlHttpIO::setproxy:" << (proxy.getProxyType() == Proxy::CUSTOM ? " Invalid arguments." : "") << " type: " << proxy.getProxyType() << " url: " << proxy.getProxyURL() << " Invalidating inflight proxy changes"; proxyschema.clear(); proxyhost.clear(); proxyport = 0; proxyusername.clear(); proxypassword.clear(); proxytype = proxy.getProxyType() == Proxy::AUTO ? Proxy::AUTO : Proxy::NONE; proxyurl.clear(); // send pending requests without a proxy send_pending_requests(); return; } proxyurl = proxy.getProxyURL(); proxyusername = proxy.getUsername(); proxypassword = proxy.getPassword(); proxytype = proxy.getProxyType(); LOG_debug << "Setting proxy: " << proxyurl; if (!crackURI(proxyurl, proxyschema, proxyhost, proxyport)) { LOG_err << "Malformed proxy string: " << proxyurl; // invalidate inflight proxy changes // mark the proxy as invalid (proxyurl set but proxyhost not set) proxyhost.clear(); proxyschema.clear(); // drop all pending requests drop_pending_requests(); return; } request_proxy_ip(); } // cancel pending HTTP request void CurlHttpIO::cancel(HttpReq* req) { if (req->httpiohandle) { CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; if (httpctx->curl) { numconnections[httpctx->d]--; pausedrequests[httpctx->d].erase(httpctx->curl); curl_multi_remove_handle(curlm[httpctx->d], httpctx->curl); curl_easy_cleanup(httpctx->curl); curl_slist_free_all(httpctx->headers); } httpctx->req = NULL; if (req->status == REQ_FAILURE || httpctx->curl) { delete httpctx; } req->httpstatus = 0; req->mErrCode = 0; if (req->status != REQ_FAILURE) { req->status = REQ_FAILURE; statechange = true; } req->httpiohandle = NULL; } } // real-time progress information on POST data m_off_t CurlHttpIO::postpos(void* handle) { assert(handle); const CurlHttpContext* httpctx = static_cast(handle); if (!httpctx || !httpctx->curl) return 0; curl_off_t bytes; if (const CURLcode errorCode = curl_easy_getinfo(httpctx->curl, CURLINFO_SIZE_UPLOAD_T, &bytes); errorCode) { LOG_err << "Unable to get CURLINFO_SIZE_UPLOAD_T. Error code: " << errorCode; return 0; } return bytes; } // process events bool CurlHttpIO::doio() { bool result; statechange = false; result = statechange; statechange = false; processcurlevents(API); result |= multidoio(curlm[API]); for (int d = GET; d == GET || d == PUT; d += PUT - GET) { partialdata[d] = 0; if (arerequestspaused[d]) { arerequestspaused[d] = false; set::iterator it = pausedrequests[d].begin(); while (!arerequestspaused[d] && it != pausedrequests[d].end()) { CURL *easy_handle = *it; pausedrequests[d].erase(it++); curl_easy_pause(easy_handle, CURLPAUSE_CONT); } if (!arerequestspaused[d]) { int dummy; curl_multi_socket_action(curlm[d], CURL_SOCKET_TIMEOUT, 0, &dummy); } } if (!arerequestspaused[d]) { processcurlevents((direction_t)d); result |= multidoio(curlm[d]); } } return result; } bool CurlHttpIO::multidoio(CURLM *curlmhandle) { int dummy = 0; CURLMsg* msg; bool result; while ((msg = curl_multi_info_read(curlmhandle, &dummy)) != nullptr) { HttpReq* req = NULL; if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char**)&req) == CURLE_OK && req) { req->httpio = NULL; if (msg->msg == CURLMSG_DONE) { measureLatency(msg->easy_handle, req); CURLcode errorCode = msg->data.result; req->mErrCode = errorCode; if (errorCode != CURLE_OK && errorCode != CURLE_HTTP_RETURNED_ERROR && errorCode != CURLE_WRITE_ERROR) { LOG_debug << req->getLogName() << "CURLMSG_DONE with error " << errorCode << ": " << curl_easy_strerror(errorCode); #if LIBCURL_VERSION_NUM >= 0x072c00 // At least cURL 7.44.0 if (errorCode == CURLE_SSL_PINNEDPUBKEYNOTMATCH) { pkpErrors++; LOG_warn << req->getLogName() << "Invalid public key?"; if (pkpErrors == 3) { pkpErrors = 0; LOG_err << req->getLogName() << "Invalid public key. Possible MITM attack!!"; req->sslcheckfailed = true; struct curl_certinfo *ci; if (curl_easy_getinfo(msg->easy_handle, CURLINFO_CERTINFO, &ci) == CURLE_OK) { LOG_warn << req->getLogName() << "Fake SSL certificate data:"; for (int i = 0; i < ci->num_of_certs; i++) { struct curl_slist *slist = ci->certinfo[i]; while (slist) { LOG_warn << req->getLogName() << i << ": " << slist->data; if (i == 0 && Utils::startswith(slist->data, "Issuer:")) { const char* issuer = strstr(slist->data, "CN = "); if (issuer) { issuer += 5; } else { issuer = strstr(slist->data, "CN="); if (issuer) { issuer += 3; } } if (issuer) { req->sslfakeissuer = issuer; } } slist = slist->next; } } if (req->sslfakeissuer.size()) { LOG_debug << req->getLogName() << "Fake certificate issuer: " << req->sslfakeissuer; } } } } #endif } else if (req->protect) { pkpErrors = 0; } long httpstatus; curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &httpstatus); req->httpstatus = int(httpstatus); // Get the used ip address, if any. char* resolvedIpAddress = nullptr; curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIMARY_IP, &resolvedIpAddress); LOG_debug << req->getLogName() << "CURLMSG_DONE with HTTP status: " << req->httpstatus << " from " << (req->httpiohandle ? (((CurlHttpContext*)req->httpiohandle)->hostname + " - " + (resolvedIpAddress ? resolvedIpAddress : "")) : "(unknown) "); if (req->httpstatus) { if (req->mExpectRedirect && req->isRedirection()) // HTTP 3xx response { char *url = NULL; curl_easy_getinfo(msg->easy_handle, CURLINFO_REDIRECT_URL, &url); if (url) { req->mRedirectURL = url; LOG_debug << req->getLogName() << "Redirected to " << req->mRedirectURL; } } if (req->method == METHOD_NONE && req->httpiohandle) { char *ip = NULL; CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIMARY_IP, &ip) == CURLE_OK && ip && !strstr(httpctx->hostip.c_str(), ip)) { LOG_err << req->getLogName() << "cURL has changed the original IP! " << httpctx->hostip << " -> " << ip; req->in = strstr(ip, ":") ? (string("[") + ip + "]") : string(ip); } else { req->in = httpctx->hostip; } req->httpstatus = 200; } if (req->binary) { LOG_debug << req->getLogName() << "[received " << (req->buf ? req->bufpos : (int)req->in.size()) << " bytes of raw data]"; } else if (req->mChunked) { // Chunked data logging is handled in write_data callback to avoid // duplicate logging. The 'in' field may contain previously received // data that would be logged multiple times if printed here. } else { JSON_NONCHUNK_RECEIVED << req->getLogName() << "Received " << req->in.size() << ": " << MaxDirectMessage(req->in.c_str(), req->in.size(), SimpleLogger::getMaxPayloadLogSize()) << " (at ds: " << Waiter::ds << ")"; } } // check httpstatus, redirecturl and response length m_off_t actualLength = req->buf != nullptr || req->mChunked ? req->bufpos : static_cast(req->in.size()); req->status = ((req->httpstatus == 200 || (req->mExpectRedirect && req->isRedirection() && req->mRedirectURL.size())) && errorCode != CURLE_PARTIAL_FILE && (req->contentlength < 0 || req->contentlength == actualLength)) ? REQ_SUCCESS : REQ_FAILURE; if (req->status == REQ_SUCCESS) { dnsok = true; lastdata = Waiter::ds; req->lastdata = Waiter::ds; } else { LOG_warn << req->getLogName() << "REQ_FAILURE." << " Status: " << req->httpstatus << " CURLcode: " << errorCode << " Content-Length: " << req->contentlength << " buffer? " << (req->buf != NULL) << " bufferSize: " << actualLength; } if (req->httpstatus) { success = true; } } else { req->status = REQ_FAILURE; } statechange = true; // signal if the request has failed due to a DNS error (httpstatus = 0) req->mDnsFailure = (req->status == REQ_FAILURE && !req->httpstatus); DEBUG_TEST_HOOK_HTTPREQ_FINISH(req->httpstatus, req->mErrCode, req->status != REQ_SUCCESS); } else { req = NULL; } curl_multi_remove_handle(curlmhandle, msg->easy_handle); curl_easy_cleanup(msg->easy_handle); if (req) { inetstatus(req->httpstatus != 0); CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; if (httpctx) { numconnections[httpctx->d]--; pausedrequests[httpctx->d].erase(httpctx->curl); curl_slist_free_all(httpctx->headers); req->httpiohandle = NULL; httpctx->req = NULL; delete httpctx; } } } result = statechange; statechange = false; return result; } // Measure latency and connect time void CurlHttpIO::measureLatency(CURL* easy_handle, HttpReq* req) { if (auto httpReqXfer = dynamic_cast(req)) { double start_transfer_time = -1; double connect_time = -1; CURLcode start_transfer_time_res = curl_easy_getinfo(easy_handle, CURLINFO_STARTTRANSFER_TIME, &start_transfer_time); CURLcode connect_time_res = curl_easy_getinfo(easy_handle, CURLINFO_CONNECT_TIME, &connect_time); if (start_transfer_time_res == CURLE_OK) { start_transfer_time *= 1000; // Convert to milliseconds httpReqXfer->mStartTransferTime = start_transfer_time; } else { LOG_warn << "Failed to get start transfer time info: " << curl_easy_strerror(start_transfer_time_res); } if (connect_time_res == CURLE_OK) { connect_time *= 1000; // Convert to milliseconds httpReqXfer->mConnectTime = connect_time; } else { LOG_warn << "Failed to get connect time info: " << curl_easy_strerror(connect_time_res); } LOG_verbose << "Connect time and start transfer latency for request " << req->getLogName() << ": " << connect_time << " ms - " << start_transfer_time << " ms"; } } // callback for incoming HTTP payload void CurlHttpIO::send_pending_requests() { while (pendingrequests.size()) { CurlHttpContext* httpctx = pendingrequests.front(); if (httpctx->req) { send_request(httpctx); } else { delete httpctx; } pendingrequests.pop(); } } void CurlHttpIO::drop_pending_requests() { while (pendingrequests.size()) { CurlHttpContext* httpctx = pendingrequests.front(); if (httpctx->req) { httpctx->req->status = REQ_FAILURE; httpctx->req->httpiohandle = NULL; statechange = true; } httpctx->req = NULL; delete httpctx; pendingrequests.pop(); } } size_t CurlHttpIO::read_data(void* ptr, size_t size, size_t nmemb, void* source) { const char *buf; size_t totalsize; HttpReq *req = (HttpReq*)source; CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; size_t len = size * nmemb; CurlHttpIO* httpio = (CurlHttpIO*)req->httpio; if (httpctx->data) { buf = httpctx->data; totalsize = httpctx->len; } else { buf = req->out->data(); totalsize = req->out->size(); } buf += req->outpos; size_t nread = totalsize - req->outpos; if (nread > len) { nread = len; } if (!nread) { return 0; } req->lastdata = Waiter::ds; if (httpio->maxspeed[PUT]) { bool isApi = (req->type == REQ_JSON); if (!isApi) { long maxbytes = long( ((httpio->maxspeed[PUT] - httpio->uploadSpeed) * SpeedController::SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS) - httpio->partialdata[PUT] ); if (maxbytes <= 0) { httpio->pausedrequests[PUT].insert(httpctx->curl); httpio->arerequestspaused[PUT] = true; return CURL_READFUNC_PAUSE; } if (nread > (size_t)maxbytes) { nread = static_cast(maxbytes); } httpio->partialdata[PUT] += nread; } } memcpy(ptr, buf, nread); req->outpos += nread; // LOG_debug << req->getLogName() << "Supplying " << nread << " bytes to cURL to send"; return nread; } size_t CurlHttpIO::write_data(void* ptr, size_t size, size_t nmemb, void* target) { int len = int(size * nmemb); HttpReq *req = (HttpReq*)target; CurlHttpIO* httpio = (CurlHttpIO*)req->httpio; if (httpio) { if (httpio->maxspeed[GET]) { CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; bool isUpload = (httpctx->data ? httpctx->len : req->out->size()) > 0; bool isApi = (req->type == REQ_JSON); if (!isApi && !isUpload) { if ((httpio->downloadSpeed + ((httpio->partialdata[GET] + len) / static_cast(SpeedController::SPEED_MEAN_CIRCULAR_BUFFER_SIZE_SECONDS))) > httpio->maxspeed[GET]) { httpio->pausedrequests[GET].insert(httpctx->curl); httpio->arerequestspaused[GET] = true; return CURL_WRITEFUNC_PAUSE; } httpio->partialdata[GET] += len; } } if (len) { req->put(ptr, static_cast(len), true); // Chunked data is logged here when written since chunks are not // consumed immediately upon receipt, avoiding duplicate logging. if (req->mChunked) { JSON_CHUNK_RECEIVED << req->getLogName() << "Received chunk " << len << ": " << MaxDirectMessage(static_cast(ptr), static_cast(len), SimpleLogger::getMaxPayloadLogSize()) << " (at ds: " << Waiter::ds << ")"; } } httpio->lastdata = Waiter::ds; req->lastdata = Waiter::ds; } return static_cast(len); } // set contentlength according to Original-Content-Length header size_t CurlHttpIO::check_header(const char* ptr, size_t size, size_t nmemb, void* target) { HttpReq *req = (HttpReq*)target; size_t len = size * nmemb; // HEADER can end in \r\n (protocol specs) or \n (also accepted) unsigned endChars = std::invoke( [&ptr, &len]() -> unsigned { if (Utils::endswith(ptr, len, "\r\n", 2)) return 2; if (Utils::endswith(ptr, len, "\n", 1)) return 1; return 0; }); if (len > endChars) { NET_verbose << req->getLogName() << "Header: " << std::string(ptr, len - endChars); } // 2 -> "\r\n" 1 -> "\n" assert(endChars == 2u || endChars == 1u); const char* val = nullptr; if (Utils::startswith(ptr, "HTTP/")) { if (req->contentlength >= 0) { // For authentication with some proxies, cURL sends two requests in the context of a single one // Content-Length is reset here to not take into account the header from the first response LOG_warn << req->getLogName() << "Receiving a second response. Resetting Content-Length"; req->contentlength = -1; } return size * nmemb; } else if ((val = Utils::startswith(ptr, "Content-Length:")) != nullptr) { if (req->contentlength < 0) { req->setcontentlength(atoll(val)); } } else if ((val = Utils::startswith(ptr, "Original-Content-Length:")) != nullptr) { req->setcontentlength(atoll(val)); } else if ((val = Utils::startswith(ptr, "X-MEGA-Time-Left:")) != nullptr) { req->timeleft = atol(val); } else if ((val = Utils::startswith(ptr, "Content-Type:")) != nullptr) { req->contenttype.assign(val, len - 15); // length of "Content-Type:" + 2 } else if ((val = Utils::startswith(ptr, "X-Hashcash:")) != nullptr) { const char* end = ptr + len - 3; // point to the char before CRLF terminator if (end - val < 4) // minimum hashcash len is 5 { LOG_warn << req->getLogName() << "Ignoring too short X-Hashcash header"; return len; } // trim trailing CRLF, from right to left, up to end of "X-Hashcash:" while (end > val && *end < ' ') end--; assert(end - val >= 0); string buffer{val, static_cast((end - val) + 1)}; LOG_warn << req->getLogName() << "X-Hashcash received:" << buffer; // Example of hashcash header // 1:100:1731410499:RUvIePV2PNO8ofg8xp1aT5ugBcKSEzwKoLBw9o4E6F_fmn44eC3oMpv388UtFl2K // ::: std::stringstream ss(buffer); vector hc; for (size_t i = 0; i < 4; i++) { string buf; if (!getline(ss, buf, ':')) break; hc.push_back(std::move(buf)); } if (hc.size() != 4 // incomplete data || stoi(hc[0]) != 1 // header version || stoi(hc[1]) < 0 || stoi(hc[1]) > 255 // invalid easiness [0, 255] || hc[3].size() != 64) // token is 64 chars in B64 { req->mHashcashToken.clear(); } else { req->mHashcashToken = hc[3].substr(0, 64); req->mHashcashEasiness = static_cast(stoi(hc[1])); } } else { return len; } if (req->httpio) { req->httpio->lastdata = Waiter::ds; req->lastdata = Waiter::ds; } return len; } int CurlHttpIO::seek_data(void *userp, curl_off_t offset, int origin) { HttpReq *req = (HttpReq*)userp; CurlHttpContext* httpctx = (CurlHttpContext*)req->httpiohandle; curl_off_t newoffset; size_t totalsize; if (httpctx->data) { totalsize = httpctx->len; } else { totalsize = req->out->size(); } switch (origin) { case SEEK_SET: newoffset = offset; break; case SEEK_CUR: newoffset = static_cast(req->outpos) + offset; break; case SEEK_END: newoffset = static_cast(totalsize) + offset; break; default: LOG_err << "Invalid origin in seek function: " << origin; return CURL_SEEKFUNC_FAIL; } if (newoffset > (int) totalsize || newoffset < 0) { LOG_err << "Invalid offset " << origin << " " << offset << " " << totalsize << " " << req->outbuf << " " << newoffset; return CURL_SEEKFUNC_FAIL; } req->outpos = size_t(newoffset); LOG_debug << "Successful seek to position " << newoffset << " of " << totalsize; return CURL_SEEKFUNC_OK; } int CurlHttpIO::socket_callback(CURL *, curl_socket_t s, int what, void *userp, void *, direction_t d) { CurlHttpIO *httpio = (CurlHttpIO *)userp; SockInfoMap &socketmap = httpio->curlsockets[d]; if (what == CURL_POLL_REMOVE) { auto it = socketmap.find(s); if (it != socketmap.end()) { LOG_debug << "Removing socket " << s; #if defined(_WIN32) it->second.closeEvent(); #endif it->second.mode = 0; } } else { auto it = socketmap.find(s); if (it == socketmap.end()) { LOG_debug << "Adding curl socket " << s << " to " << what; #ifdef WIN32 auto pair = socketmap.emplace(s, SockInfo(httpio->mSocketsWaitEvent)); #else auto pair = socketmap.emplace(s, SockInfo()); #endif it = pair.first; } else { // Networking seems to be fine after performance improvments, no need for this logging anymore - but keep it in comments for a while to inform people debugging older logs //LOG_debug << "Setting curl socket " << s << " to " << what; } auto& info = it->second; info.fd = s; info.mode = what; #if defined(_WIN32) info.createAssociateEvent(); if (what & CURL_POLL_OUT) { info.signalledWrite = true; } #endif } return 0; } // CURL doco: When set, this callback function gets called by libcurl when the socket has been // created, but before the connect call to allow applications to change specific socket options.The // callback's purpose argument identifies the exact purpose for this particular socket: int CurlHttpIO::sockopt_callback([[maybe_unused]] void* clientp, curl_socket_t, curlsocktype) { return CURL_SOCKOPT_OK; } int CurlHttpIO::api_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp) { return socket_callback(e, s, what, userp, socketp, API); } int CurlHttpIO::download_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp) { return socket_callback(e, s, what, userp, socketp, GET); } int CurlHttpIO::upload_socket_callback(CURL *e, curl_socket_t s, int what, void *userp, void *socketp) { return socket_callback(e, s, what, userp, socketp, PUT); } int CurlHttpIO::timer_callback(CURLM *, long timeout_ms, void *userp, direction_t d) { CurlHttpIO *httpio = (CurlHttpIO *)userp; //auto oldValue = httpio->curltimeoutreset[d]; if (timeout_ms < 0) { httpio->curltimeoutreset[d] = -1; } else { m_time_t timeoutds = timeout_ms / 100; if (timeout_ms % 100) { timeoutds++; } httpio->curltimeoutreset[d] = Waiter::ds + timeoutds; } // Networking seems to be fine after performance improvments, no need for this logging anymore - but keep it in comments for a while to inform people debugging older logs //if (oldValue != httpio->curltimeoutreset[d]) //{ // LOG_debug << "Set cURL timeout[" << d << "] to " << httpio->curltimeoutreset[d] << " from " << timeout_ms << "(ms) at ds: " << Waiter::ds; //} return 0; } int CurlHttpIO::api_timer_callback(CURLM *multi, long timeout_ms, void *userp) { return timer_callback(multi, timeout_ms, userp, API); } int CurlHttpIO::download_timer_callback(CURLM *multi, long timeout_ms, void *userp) { return timer_callback(multi, timeout_ms, userp, GET); } int CurlHttpIO::upload_timer_callback(CURLM *multi, long timeout_ms, void *userp) { return timer_callback(multi, timeout_ms, userp, PUT); } #ifdef USE_OPENSSL CURLcode CurlHttpIO::ssl_ctx_function(CURL*, void* sslctx, void*req) { SSL_CTX_set_cert_verify_callback((SSL_CTX*)sslctx, cert_verify_callback, req); return CURLE_OK; } #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) #define X509_STORE_CTX_get0_cert(ctx) (ctx->cert) #define X509_STORE_CTX_get0_untrusted(ctx) (ctx->untrusted) #define EVP_PKEY_get0_DSA(_pkey_) ((_pkey_)->pkey.dsa) #define EVP_PKEY_get0_RSA(_pkey_) ((_pkey_)->pkey.rsa) #endif #if (OPENSSL_VERSION_NUMBER < 0x1010100fL) || defined (LIBRESSL_VERSION_NUMBER) const BIGNUM *RSA_get0_n(const RSA *rsa) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) return rsa->n; #else const BIGNUM *result; RSA_get0_key(rsa, &result, NULL, NULL); return result; #endif } const BIGNUM *RSA_get0_e(const RSA *rsa) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) return rsa->e; #else const BIGNUM *result; RSA_get0_key(rsa, NULL, &result, NULL); return result; #endif } const BIGNUM *RSA_get0_d(const RSA *rsa) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) return rsa->d; #else const BIGNUM *result; RSA_get0_key(rsa, NULL, NULL, &result); return result; #endif } #endif // SSL public key pinning int CurlHttpIO::cert_verify_callback(X509_STORE_CTX* ctx, void* req) { HttpReq *request = (HttpReq *)req; CurlHttpIO *httpio = (CurlHttpIO *)request->httpio; unsigned char buf[sizeof(APISSLMODULUS1) - 1]; int ok = 0; if (httpio->disablepkp) { LOG_warn << "Public key pinning disabled."; return 1; } EVP_PKEY* evp = X509_PUBKEY_get(X509_get_X509_PUBKEY(X509_STORE_CTX_get0_cert(ctx))); if (evp && EVP_PKEY_id(evp) == EVP_PKEY_RSA) { // get needed components of RSA key: // n: modulus common to both public and private key; // e: public exponent. #if OPENSSL_VERSION_NUMBER < 0x30000000L const rsa_st* rsaKey = EVP_PKEY_get0_RSA(evp); const BIGNUM* rsaN = RSA_get0_n(rsaKey); const BIGNUM* rsaE = RSA_get0_e(rsaKey); bool rsaOk = true; #else BIGNUM* rsaN = nullptr; BIGNUM* rsaE = nullptr; bool rsaOk = EVP_PKEY_get_bn_param(evp, "n", &rsaN) && EVP_PKEY_get_bn_param(evp, "e", &rsaE); // ensure cleanup std::unique_ptr nCleanup(rsaN, &BN_free); std::unique_ptr eCleanup(rsaE, &BN_free); #endif if (rsaOk && BN_num_bytes(rsaN) == sizeof APISSLMODULUS1 - 1 && BN_num_bytes(rsaE) == sizeof APISSLEXPONENT - 1) { BN_bn2bin(rsaN, buf); // check the public key matches for the URL of the connection (API or SFU-stats) if ((Utils::startswith(request->posturl, httpio->APIURL) && (!memcmp(buf, APISSLMODULUS1, sizeof APISSLMODULUS1 - 1) || !memcmp(buf, APISSLMODULUS2, sizeof APISSLMODULUS2 - 1))) || (Utils::startswith(request->posturl, MegaClient::SFUSTATSURL) && (!memcmp(buf, SFUSTATSSSLMODULUS, sizeof SFUSTATSSSLMODULUS - 1) || !memcmp(buf, SFUSTATSSSLMODULUS2, sizeof SFUSTATSSSLMODULUS2 - 1)))) { BN_bn2bin(rsaE, buf); if (!memcmp(buf, APISSLEXPONENT, sizeof APISSLEXPONENT - 1)) { LOG_debug << "SSL public key OK"; ok = 1; } } else { LOG_warn << "Public key mismatch for " << request->posturl; } } else { LOG_warn << "Public key size mismatch " << BN_num_bytes(rsaN) << " " << BN_num_bytes(rsaE); } } else { LOG_warn << "Public key not found"; } EVP_PKEY_free(evp); if (!ok) { httpio->pkpErrors++; LOG_warn << "Invalid public key?"; if (httpio->pkpErrors == 3) { httpio->pkpErrors = 0; LOG_err << "Invalid public key. Possible MITM attack!!"; request->sslcheckfailed = true; request->sslfakeissuer.resize(256); int len = X509_NAME_get_text_by_NID (X509_get_issuer_name (X509_STORE_CTX_get0_cert(ctx)), NID_commonName, (char *)request->sslfakeissuer.data(), int(request->sslfakeissuer.size())); request->sslfakeissuer.resize(len > 0 ? static_cast(len) : 0); LOG_debug << "Fake certificate issuer: " << request->sslfakeissuer; } } return ok; } #endif void CurlHttpIO::addDnsResolution( CURL* curl, std::unique_ptr& dnsList, const string& host, const string& ips, const int port) { string curlListEntry = host + ":" + std::to_string(port) + ":" + ips; dnsList.reset(curl_slist_append(dnsList.release(), curlListEntry.c_str())); curl_easy_setopt(curl, CURLOPT_RESOLVE, dnsList.get()); } bool crackURI(const string& uri, string& scheme, string& host, int& port) { if (uri.empty()) return false; port = 0; scheme.clear(); host.clear(); size_t starthost, endhost = 0, startport, endport; starthost = uri.find("://"); if (starthost != string::npos) { scheme = uri.substr(0, starthost); starthost += 3; } else { starthost = 0; } if (uri[starthost] == '[' && uri.size() > 0) { starthost++; } startport = uri.find("]:", starthost); if (startport == string::npos) { startport = uri.find(":", starthost); if (startport != string::npos) { endhost = startport; } } else { endhost = startport; startport++; } if (startport != string::npos) { startport++; endport = uri.find("/", startport); if (endport == string::npos) { endport = uri.size(); } if (endport <= startport || endport - startport > 5) { port = -1; } else { for (size_t i = startport; i < endport; i++) { int c = uri.data()[i]; if (c < '0' || c > '9') { port = -1; break; } } } if (!port) { port = atoi(uri.data() + startport); if (port > 65535) { port = -1; } } } else { endhost = uri.find("]/", starthost); if (endhost == string::npos) { endhost = uri.find("/", starthost); if (endhost == string::npos) { endhost = uri.size(); } } } if (!port) { if (!scheme.compare("https")) { port = 443; } else if (!scheme.compare("http")) { port = 80; } else if (!scheme.compare(0, 5, "socks")) { port = 1080; } else { port = -1; } } host = uri.substr(starthost, endhost - starthost); if (port <= 0 || starthost == string::npos || starthost >= endhost) { port = 0; scheme.clear(); host.clear(); return false; } return true; } static bool isValidIPAddress(const std::string& string, int type) { // Sanity. assert(type == AF_INET || type == AF_INET6); // Throwaway buffer: Necessary for parsing. union { struct in_addr inaddr; struct in6_addr in6addr; } buffer; // Try and parse the provided address string. return inet_pton(type, string.data(), &buffer) > 0; } bool isValidIPv4Address(const std::string& string) { return isValidIPAddress(string, AF_INET); } bool isValidIPv6Address(const std::string& string) { return isValidIPAddress(string, AF_INET6); } } // namespace sdk-10.11.0/src/posix/waiter.cpp000066400000000000000000000106311516266226600164000ustar00rootroot00000000000000/** * @file posix/wait.cpp * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #ifdef USE_POLL #include //poll #endif namespace mega { PosixWaiter::PosixWaiter() { // pipe to be able to leave the select() call if (pipe(m_pipe) < 0) { LOG_fatal << "Error creating pipe"; throw std::runtime_error("Error creating pipe"); } if (fcntl(m_pipe[0], F_SETFL, O_NONBLOCK) < 0) { LOG_err << "fcntl error"; } maxfd = -1; } PosixWaiter::~PosixWaiter() { close(m_pipe[0]); close(m_pipe[1]); } void PosixWaiter::init(dstime ds) { Waiter::init(ds); maxfd = -1; MEGA_FD_ZERO(&rfds); MEGA_FD_ZERO(&wfds); MEGA_FD_ZERO(&efds); MEGA_FD_ZERO(&ignorefds); } // update maxfd for select() void PosixWaiter::bumpmaxfd(int fd) { if (fd > maxfd) { maxfd = fd; } } // checks if an unfiltered fd is set // FIXME: use bitwise & instead of scanning bool PosixWaiter::fd_filter(int nfds, mega_fd_set_t* fds, mega_fd_set_t* ignorefds) const { while (nfds--) { if (MEGA_FD_ISSET(nfds, fds) && !MEGA_FD_ISSET(nfds, ignorefds)) return true; } return false; } // wait for supplied events (sockets, filesystem changes), plus timeout + application events // maxds specifies the maximum amount of time to wait in deciseconds (or // NEVER if no timeout scheduled) returns application-specific bitmask. // bit 0 set indicates that exec() needs to be called. int PosixWaiter::wait() { int numfd = 0; timeval tv; //Pipe added to rfds to be able to leave select() when needed MEGA_FD_SET(m_pipe[0], &rfds); bumpmaxfd(m_pipe[0]); if (EVER(maxds)) { dstime us = 1000000 / 10 * maxds; tv.tv_sec = us / 1000000; tv.tv_usec = (suseconds_t)(us - tv.tv_sec * 1000000); } #ifdef USE_POLL // wait infinite (-1) if maxds is max dstime OR it would overflow platform's int int timeoutInMs = -1; if (EVER(maxds) && maxds <= std::numeric_limits::max() / 100) { timeoutInMs = static_cast(maxds) * 100; } auto total = rfds.size() + wfds.size() + efds.size(); struct pollfd fds[total]; int polli = 0; for (auto & fd : rfds) { fds[polli].fd = fd; fds[polli].events = POLLIN_SET; polli++; } for (auto & fd : wfds) { fds[polli].fd = fd; fds[polli].events = POLLOUT_SET; polli++; } for (auto & fd : efds) { fds[polli].fd = fd; fds[polli].events = POLLEX_SET; polli++; } numfd = poll(fds, total, timeoutInMs); #else numfd = select(maxfd + 1, &rfds, &wfds, &efds, EVER(maxds) ? &tv : NULL); #endif // empty pipe uint8_t buf; bool external = false; { std::lock_guard g(mMutex); while (read(m_pipe[0], &buf, sizeof buf) > 0) { external = true; } alreadyNotified = false; } // timeout or error if (external || numfd <= 0) { return NEEDEXEC; } // request exec() to be run only if a non-ignored fd was triggered #ifdef USE_POLL for (unsigned int i = 0 ; i < total ; i++) { if ((fds[i].revents & (POLLIN_SET | POLLOUT_SET | POLLEX_SET) ) && !MEGA_FD_ISSET(fds[i].fd, &ignorefds) ) { return NEEDEXEC; } } return 0; #else return (fd_filter(maxfd + 1, &rfds, &ignorefds) || fd_filter(maxfd + 1, &wfds, &ignorefds) || fd_filter(maxfd + 1, &efds, &ignorefds)) ? NEEDEXEC : 0; #endif } void PosixWaiter::notify() { std::lock_guard g(mMutex); if (!alreadyNotified) { auto w = write(m_pipe[1], "0", 1); if (w <= 0) { LOG_warn << "PosixWaiter::notify(), write returned " << w; } alreadyNotified = true; } } } // namespace sdk-10.11.0/src/process.cpp000066400000000000000000000504471516266226600154320ustar00rootroot00000000000000/** * @file process.cpp * @brief Mega SDK run subprocess * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/process.h" #include "mega.h" #ifdef WIN32 #else #include #include #endif #include using namespace mega; using namespace std; Process::EnvironmentChanger::EnvironmentChanger(const unordered_map& env) { for (auto& i : env) { if (const auto [val, hasValue] = Utils::getenv(i.first); hasValue) { saved[i.first] = val; } else { unset.insert(i.first); } Utils::setenv(i.first, i.second); } } Process::EnvironmentChanger::~EnvironmentChanger() { for (auto& i : saved) { Utils::setenv(i.first, i.second); } for (auto& i : unset) { Utils::unsetenv(i); } } bool Process::run(const vector& args, const unordered_map& env, DataReaderFunc istdoutReader, DataReaderFunc istderrReader) { LOG_debug << "Process::Process(\"" << formCommandLine(args) << "...)"; stdoutReader = istdoutReader; stderrReader = istderrReader; #ifdef WIN32 string cmdLine = formCommandLine(args); LOG_debug << "cmdLine = '" << cmdLine << "'"; EnvironmentChanger envChanger(env); AutoFileHandle stdoutHandleChild; AutoFileHandle stderrHandleChild; SECURITY_ATTRIBUTES saAttr; // Set the bInheritHandle flag so pipe handles are inherited. saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = nullptr; // Connect streams from child process to streams declared in parent process // stdout if (!CreatePipe(readFd.ptr(), stdoutHandleChild.ptr(), &saAttr, 0)) { reportWindowsError("Could not create pipe for child stdout"); return false; } DWORD pipeMode = PIPE_NOWAIT | PIPE_READMODE_BYTE; if (!SetNamedPipeHandleState(readFd, &pipeMode, nullptr, nullptr)) { reportWindowsError("Could not make readFd non blocking"); return false; } // stderr if (!CreatePipe(readErrorFd.ptr(), stderrHandleChild.ptr(), &saAttr, 0)) { reportWindowsError("Could not create pipe for child stderror"); return false; } pipeMode = PIPE_NOWAIT | PIPE_READMODE_BYTE; if (!SetNamedPipeHandleState(readErrorFd, &pipeMode, nullptr, nullptr)) { reportWindowsError("Could not make readErrorFd non blocking"); return false; } PROCESS_INFORMATION piProcInfo; // Set up members of the PROCESS_INFORMATION structure. ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); // Set up members of the STARTUPINFO structure. STARTUPINFO siStartInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = stderrHandleChild; siStartInfo.hStdOutput = stdoutHandleChild; siStartInfo.hStdInput = INVALID_HANDLE_VALUE; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; wstring cmdLineW; LocalPath::path2local(&cmdLine, &cmdLineW); BOOL r = CreateProcess( nullptr, // appName - use 1st param from commandLine (LPWSTR)cmdLineW.c_str(), // command line nullptr, // process security attributes nullptr, // primary thread security attributes TRUE, // handles are inherited DETACHED_PROCESS, // creation flags nullptr, // use parent's environment nullptr, // use parent's current directory (nullptr) or named dir &siStartInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION if (!r) { reportWindowsError("Could not spawn child process '" + cmdLine + "'"); return false; } launched = true; ::CloseHandle(piProcInfo.hThread); ::CloseHandle(piProcInfo.hProcess); childPid = piProcInfo.dwProcessId; return true; #else // WIN32 // Connect streams from child process to streams declared in parent process // Note: read from [0], write to [1] // stdout int parentReadFd[2] = { -1, -1 }; if (pipe(parentReadFd) != 0) { reportError("Could not open pipe()"); return false; } readFd = parentReadFd[0]; int childReadFd = parentReadFd[1]; // stderr int parentReadErrorFd[2] = { -1, -1 }; if (pipe(parentReadErrorFd) != 0) { reportError("Could not open second pipe()"); return false; } readErrorFd = parentReadErrorFd[0]; int childReadErrorFd = parentReadErrorFd[1]; EnvironmentChanger envChanger(env); // Create child process via fork() LOG_debug << "fork()"; childPid = ::fork(); if (childPid == -1) { ::close(childReadFd); ::close(childReadErrorFd); reportError("Could not fork()"); return false; } launched = true; // Child process - specific code if (childPid == 0) { // These belong to the parent now. ::close(readFd); ::close(readErrorFd); // Redirect streams to the parent process // stdout ::close(STDOUT_FILENO); if (dup2(childReadFd, STDOUT_FILENO) == -1) { reportError("Could not redirect stdout"); _exit(EXIT_FAILURE); } // stderr ::close(STDERR_FILENO); if (dup2(childReadErrorFd, STDERR_FILENO) == -1) { reportError("Could not redirect stderr"); _exit(EXIT_FAILURE); } // Prepare command-line arguments for child process if (args.empty()) { cerr << "Process: Can not execute, no arguments given" << endl; _exit(EXIT_FAILURE); } vector argv; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) { argv.push_back(strdup(i->c_str())); } argv.push_back(nullptr); // Execute 1st param (our executable) execvp(argv[0], &argv.front()); // --- Only if execvp() fails, the code below this point is executed --- // (log error and exit) int savedErrno = errno; // cerr so parent process sees this cerr << "Could not execute '" + string(argv[0]) + "'" << ": " << savedErrno << ": " << strerror(savedErrno) << endl; reportError("Could not execute '" + string(argv[0]) + "'", savedErrno); _exit(EXIT_FAILURE); } // else --> parent process // These belong to the child now. ::close(childReadFd); ::close(childReadErrorFd); // Set the read/write from/to child process streams as non-blocking // stdout of child process if (fcntl(readFd, F_SETFL, fcntl(readFd, F_GETFL) | O_NONBLOCK) == -1) { reportError("Could not make readFd non blocking"); return false; } // stderr of child process if (fcntl(readErrorFd, F_SETFL, fcntl(readErrorFd, F_GETFL) | O_NONBLOCK) == -1) { reportError("Could not make readErrorFd non blocking"); return false; } return true; #endif // else WIN32 } bool Process::terminate() { // return false if already terminated LOG_debug << "Process::terminate()"; if (hasStatus()) { LOG_debug << "already exited"; return false; } #ifdef WIN32 AutoFileHandle childHandle = ::OpenProcess(PROCESS_TERMINATE, false, childPid); if (childHandle == nullptr) { DWORD error = ::GetLastError(); if (error == ERROR_INVALID_PARAMETER) { // no such PID return false; } reportWindowsError("Failed to OpenProcess() for spawned process PID " + to_string(childPid)); return false; } LOG_debug << "calling TerminateProcess()"; if (::TerminateProcess(childHandle, 0)) { // 0 = exit code // returns immediately LOG_debug << "Termination started"; } else { // TerminateProcess() failed reportWindowsError("Failed to TerminateProcess() for spawned process PID " + to_string(childPid)); return false; } return true; #else // !WIN32 if (!isAlive()) { LOG_debug << "not alive"; return false; } LOG_debug << "kill(" << childPid << ", SIGTERM)"; if (kill(childPid, SIGTERM) != 0) { reportError("Error terminating child process " + to_string(childPid)); return false; } return true; #endif } Process::~Process() { LOG_debug << "Process::~Process()"; close(); terminate(); } void Process::close() { LOG_debug << "Process::close()"; readFd.close(); readErrorFd.close(); } bool Process::poll() { bool ro = readStdout(); //LOG_debug << "readStdout() -> " << ro; if (!isOpen()) { LOG_debug << " Process::poll(): closed after read stdout"; return ro; } bool re = readStderr(); //LOG_debug << "readStderr() -> " << re; if (!isOpen()) { LOG_debug << " Process::poll(): closed after read stderr"; } return ro || re; } bool Process::readStdout() { //LOG_debug << "readStdout()"; if (!isStdOutOpen()) { //LOG_debug << "isStdOutOpen() false"; return false; } unsigned char buf[32 * 1024]; #ifdef WIN32 DWORD len; if (!ReadFile(readFd, buf, sizeof buf, &len, nullptr)) { int savedError = GetLastError(); if (savedError == ERROR_NO_DATA) return false; reportWindowsError("Process::readStdout() error", savedError); close(); return false; } else if (len == 0) { return false; } else { //LOG_debug << "Process::readStdout() read " << len; if (stdoutReader != nullptr) { stdoutReader(buf, (size_t)len); } else { fwrite(buf, 1, len, stdout); } return true; } #else ssize_t len = read(readFd, buf, sizeof buf); if (len == 0) { // when child exits get continuious reads of 0 bytes //LOG_debug << "read() " << len; return false; } else if (len == -1) { if (errno == EAGAIN) { // no data available // when child alive but not writing get alternating EAGAIN and reads of 0 bytes //LOG_debug << "EAGAIN"; return false; } //LOG_debug << "read() " << len; reportError("Process::readStdout() error"); close(); return false; } else { //LOG_debug << "Process::readStdout() read " << len; if (stdoutReader != nullptr) { stdoutReader(buf, (size_t)len); } else if (::write(STDOUT_FILENO, buf, (size_t)len) < 0) { reportError("Process::readStdout() -> ::write() error: " + std::to_string(errno)); } return true; } #endif } bool Process::readStderr() { if (!isStdErrOpen()) return false; unsigned char buf[32 * 1024]; #ifdef WIN32 DWORD len; if (!ReadFile(readErrorFd, buf, sizeof buf, &len, nullptr)) { int savedError = GetLastError(); if (savedError == ERROR_NO_DATA) return false; reportWindowsError("Process::readStderror() error", savedError); close(); return false; } else if (len == 0) { return false; } else { //LOG_debug << "Process::readStderr() read " << len; if (stderrReader != nullptr) { stderrReader(buf, (size_t)len); } else { fwrite(buf, 1, len, stderr); } return true; } #else ssize_t len = read(readErrorFd, buf, sizeof(buf)); if (len == 0) { // when child exits get continuious reads of 0 bytes return false; } else if (len == -1) { if (errno == EAGAIN) { // no data available return false; } reportError("Process::readStderr() error"); close(); return false; } else { //LOG_debug << "Process::readStderr() read " << len; if (stderrReader != nullptr) { stderrReader(buf, (size_t)len); } else if (::write(STDOUT_FILENO, buf, (size_t)len) < 0) { reportError("Process::readStderr() -> ::write() error: " + std::to_string(errno)); } return true; } #endif } std::string Process::getExitSignalDescription() const { // Unix only // returns SIG* description return describeSignal(getTerminatingSignal()); } //static string Process::describeSignal(int sig) { #ifdef WIN32 assert(!"Process::DescribeSignal not implemented on Windows"); return "[Process::DescribeSignal(" + to_string(sig) + ") called on Windows]"; #else const char* str = strsignal(sig); return str ? string(str) : "[Unknown signal #" + to_string(sig) + "]"; #endif } string Process::getExitMessage() const { // return description of exit // "Exited ok" // "Exited with status 3" // "Terminated with signal: SIGTERM - Termination Signal" if (hasExited()) { int rc = getExitCode(); if (rc == 0) return "Exited ok"; else return "Exited with status " + to_string(rc); } else if (hasTerminateBySignal()) { return "Terminated with signal: " + describeSignal(getTerminatingSignal()); } else { return "Unknown process status " + to_string(status); } } bool Process::checkStatus() { // return true if child has reported status (exited) if (hasStatus()) { //LOG_debug << "already hasStatus " << status; return true; } if (!launched) { //LOG_debug << "not lanuched"; setLaunchFailureStatus(); // otherwise may spin forever return true; } #ifdef WIN32 // If an app exits with code 259 will look like they are still running // so open the process using the PID every time // 259 may be a reference to the face that on Unix if a process exits with 256 // the wait()er [parent] sees 0! Try "exit 256" is in sh script, $? is 0. AutoFileHandle tmpChildHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, childPid); if (tmpChildHandle == nullptr) { DWORD error = ::GetLastError(); if (error != ERROR_INVALID_PARAMETER) { // no such PID reportWindowsError("Could not OpenProcess() for spwaned process PID " + to_string(childPid)); } setWaitFailureStatus(); // otherwise may spin forever return true; } DWORD exitCode = 0; if (!::GetExitCodeProcess(tmpChildHandle, &exitCode)) { reportWindowsError("Could not GetExitCodeProcess() for spwaned process"); setWaitFailureStatus(); // otherwise may spin forever return true; } if (exitCode != STILL_ACTIVE) { setExitStatus(exitCode); } return hasStatus(); #else // !WIN32 int tmp = 0; int rc = waitpid(childPid, &tmp, WNOHANG); if (rc < 0) { reportError("Can not wait on child PID " + to_string(childPid)); setWaitFailureStatus(); // otherwise may spin forever return true; } if (rc == 0) { //LOG_debug << "still running"; return false; } if (rc == childPid) { //LOG_debug << "childPid" << childPid; if (WIFEXITED(tmp)) { setExitStatus(WEXITSTATUS(tmp)); } else if (WIFSIGNALED(tmp)) { setSignalledStatus(WTERMSIG(tmp)); } else { assert(false && "waitpid() but not exited not signalled"); setWaitFailureStatus(); // otherwise may spin forever } return true; } reportError("Error waiting on child PID " + to_string(childPid) + " returned status for PID " + to_string(rc)); setWaitFailureStatus(); // otherwise may spin forever return true; #endif } bool Process::wait() { // can not wait() forever on Posix as need to read stdout and stderr if (hasStatus()) return hasExitedOk(); while (!checkStatus()) { if (!poll()) usleep(100000); // 0.1 sec } // can have buffered output after exited flush(); return hasExitedOk(); } bool Process::flush() { bool action = false; while (poll()) { action = true; } return action; } #ifdef WIN32 //static string Process::windowsQuoteArg(const string& str) { if (str.empty() || str.find_first_of(" \t\\\"") != string::npos) { // "quote spaces" // must be compatible with Win32 CommandLineToArgvW() // https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx // 1. 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark. // 2. (2n) +1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. // 3. n backslashes not followed by a quotation mark simply produce n backslashes. string buffer = "\""; int numBackslashes = 0; for(char chr: str) { if (chr == '\"') { // (2n) +1 backslashes followed by a quotation mark again produce n backslashes followed by a quotation mark. for (int i = 0; i < numBackslashes; ++i) buffer += '\\'; buffer += '\\'; numBackslashes = 0; } else if (chr == '\\') { ++numBackslashes; } else { numBackslashes = 0; } buffer += chr; } if (numBackslashes > 0) { // 2n backslashes followed by a quotation mark produce n backslashes followed by a quotation mark. for (int i = 0; i < numBackslashes; ++i) buffer += '\\'; } buffer += '"'; return buffer; } return str; } //static string Process::formCommandLine(const std::vector& args) { string cmdLine; bool first = true; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) { if (!first) cmdLine += " "; cmdLine += windowsQuoteArg(*i); first = false; } return cmdLine; } #else //static // for display only string Process::formCommandLine(const std::vector& args) { return Utils::join(args, " "); } #endif void ConsoleProgressBar::show() const { std::cout << '\r'; put(std::cout); if (mWriteNewLine) std::cout << std::endl; else std::cout << '\r'; } void ConsoleProgressBar::setPrefix(const std::string& newPrefix) { prefix = newPrefix; } ConsoleProgressBar::ConsoleProgressBar(size_t imax, bool writeNewLine) : max(imax), start(::mega::m_time()), mWriteNewLine(writeNewLine) { } void ConsoleProgressBar::add(size_t n) { value += n; if (autoOutput) show(); } void ConsoleProgressBar::inc() { add(1); } std::ostream& ConsoleProgressBar::put(std::ostream& os) const { size_t currentBarWidth = 0; if (max != 0) currentBarWidth = value * barWidth / max; if (currentBarWidth > barWidth) currentBarWidth = barWidth; m_time_t elapsed = m_time() - start; size_t totalSec = size_t((double)elapsed / ((double)value / (double)max)); m_time_t etta = static_cast(totalSec) - elapsed; char ettaStr[1024]; tm tm; m_gmtime(etta, &tm); strftime(ettaStr, sizeof ettaStr, "%H:%M:%S", &tm); os << prefix << value << '/' << max << " ETTA " << ettaStr << " [" << string(currentBarWidth, '>') << string(barWidth - currentBarWidth, ' ') << ']'; return os; } sdk-10.11.0/src/proxy.cpp000066400000000000000000000115641516266226600151320ustar00rootroot00000000000000/** * @file proxy.cpp * @brief Class for manipulating proxy data * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/proxy.h" #include "mega/logging.h" #include "mega/scoped_helpers.h" #include "mega/utils.h" #include using namespace std; namespace mega { // A help function to get part from CURLU static auto getCurlUrlPart(CURLU* handle, CURLUPart what, unsigned int flags) { assert(handle); char* value = nullptr; curl_url_get(handle, what, &value, flags); return makeUniqueFrom(value, [](void* p) { curl_free(p); }); }; Proxy::Proxy() { proxyType = AUTO; } void Proxy::setProxyType(int newType) { proxyType = newType; } void Proxy::setProxyURL(const string& newURL) { proxyURL = newURL; } void Proxy::setCredentials(const string& newUsername, const string& newPassword) { username = newUsername; password = newPassword; } int Proxy::getProxyType() const { return proxyType; } string Proxy::getProxyURL() const { return this->proxyURL; } bool Proxy::credentialsNeeded() const { return (username.size() != 0); } string Proxy::getUsername() const { return username; } string Proxy::getPassword() const { return password; } Proxy Proxy::parseFromURL(const std::string& url) { if (url.empty()) return {}; // Allocate a CURLU auto curlUrl = makeUniqueFrom(curl_url(), [](CURLU* handle) { curl_url_cleanup(handle); }); if (!curlUrl) { LOG_err << "curl_url failed"; return {}; } // Set full url, allow non-supported as proxy URL has makeup, and guess if (auto rc = curl_url_set(curlUrl.get(), CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME | CURLU_GUESS_SCHEME); rc != CURLUE_OK) { LOG_err << "curl_url_set failed: " << rc << " url: " << url; return {}; } // Get parts auto scheme = getCurlUrlPart(curlUrl.get(), CURLUPART_SCHEME, 0); auto username = getCurlUrlPart(curlUrl.get(), CURLUPART_USER, CURLU_URLDECODE); auto password = getCurlUrlPart(curlUrl.get(), CURLUPART_PASSWORD, CURLU_URLDECODE); auto host = getCurlUrlPart(curlUrl.get(), CURLUPART_HOST, CURLU_URLDECODE); auto port = getCurlUrlPart(curlUrl.get(), CURLUPART_PORT, 0); if (!host) { LOG_err << "curl_url_get host failed: " << url; return {}; } Proxy proxy{}; // Username and password, optional if (username && password) { proxy.setCredentials(username.get(), password.get()); } // Format URL without user and password std::ostringstream oss; if (scheme) oss << scheme.get() << "://"; oss << host.get(); if (port) oss << ":" << port.get(); // Set URL proxy.setProxyURL(oss.str()); proxy.setProxyType(CUSTOM); return proxy; } bool Proxy::operator==(const Proxy& other) const { return other.proxyType == proxyType && other.username == username && other.password == password && other.proxyURL == proxyURL; } int proxyTypeFromString(const std::string& type) { #define ENTRY(name) {#name, Proxy::name}, static const std::map types = {DEFINE_PROXY_TYPES(ENTRY)}; // types #undef ENTRY if (auto i = types.find(type); i != types.end()) return i->second; return NONE; } const std::string* proxyTypeToString(int type) { #define ENTRY(name) {Proxy::name, #name}, static const std::map strings = {DEFINE_PROXY_TYPES(ENTRY)}; // strings #undef ENTRY if (auto i = strings.find(type); i != strings.end()) return &i->second; return nullptr; } std::string detectProxyFromEnv() { const std::array envs{ "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", }; for (const auto& env: envs) { if (const auto& [value, hasValue] = Utils::getenv(env); hasValue && !value.empty()) return value; } return ""; } void getEnvProxy(Proxy* proxy) { assert(proxy); *proxy = Proxy::parseFromURL(detectProxyFromEnv()); } } sdk-10.11.0/src/pubkeyaction.cpp000066400000000000000000000061661516266226600164500ustar00rootroot00000000000000/** * @file pubkeyaction.cpp * @brief Classes for managing public keys * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/pubkeyaction.h" #include "mega/megaapp.h" #include "mega/megaclient.h" namespace mega { PubKeyAction::PubKeyAction() { cmd = NULL; } PubKeyActionPutNodes::PubKeyActionPutNodes(vector&& newnodes, int ctag, CommandPutNodes::Completion&& c) : nn(std::move(newnodes)) , completion(std::move(c)) { tag = ctag; } void PubKeyActionPutNodes::proc(MegaClient* client, User* u) { if (u && u->pubk.isvalid()) { byte buf[AsymmCipher::MAXKEYLENGTH]; int t = 0; // re-encrypt all node keys to the user's public key for (size_t i = nn.size(); i--;) { t = u->pubk.encrypt(client->rng, (const byte*)nn[i].nodekey.data(), nn[i].nodekey.size(), buf, sizeof buf); if (!t) { if (completion) completion(API_EINTERNAL, USER_HANDLE, nn, false, tag, {}); else client->app->putnodes_result(API_EINTERNAL, USER_HANDLE, nn, false, tag, {}); return; } nn[i].nodekey.assign((char*)buf, static_cast(t)); } client->queueCommand(new CommandPutNodes(client, NodeHandle(), u->uid.c_str(), NoVersioning, std::move(nn), tag, PUTNODES_APP, nullptr, std::move(completion), false, // canChangeVault {})); // customerIpPort // 'canChangeVault' is false here because this code path is to write to user's Inbox, which // should not require "vw:1" } else { if (completion) completion(API_ENOENT, USER_HANDLE, nn, false, tag, {}); else client->app->putnodes_result(API_ENOENT, USER_HANDLE, nn, false, tag, {}); } } void PubKeyActionNotifyApp::proc(MegaClient *client, User *u) { client->app->pubkey_result(u); } PubKeyActionNotifyApp::PubKeyActionNotifyApp(int ctag) { tag = ctag; } } // namespace sdk-10.11.0/src/pwm_file_parser.cpp000066400000000000000000000116651516266226600171310ustar00rootroot00000000000000#include "mega/pwm_file_parser.h" #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wtype-limits" #endif #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable: 4244) // conversion from '__int64' to 'int' #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __GNUC__ #pragma GCC diagnostic pop #endif namespace mega::pwm::import { namespace { /** * @brief Checks that all the names in expected are contained in existing. Each time one of the * expected is not found in existing, an error messages is appended to the outErrMsg output * parameter. * * @return The number of missing entries. */ unsigned int missingNames(const std::vector& existing, const std::vector& expected, std::string& outErrMsg) { unsigned int missing = 0; for (const auto& e: expected) { if (std::find(std::begin(existing), std::end(existing), e) == std::end(existing)) { outErrMsg += "Missing mandatory column with name: " + e + "\n"; ++missing; } } return missing; } inline bool fileIsAccessible(const std::string& fname) { std::ifstream f(fname.c_str()); return f.good(); } } PassFileParseResult parseGooglePasswordCSVFile(const std::string& filePath) { csv::CSVFormat format; format.delimiter(',').header_row(0).variable_columns(true); // We will go through the file in parallel to get original content for error reporting PassFileParseResult result; std::ifstream openFile(filePath.c_str()); if (!openFile.good()) { result.mErrCode = PassFileParseResult::ErrCode::CANT_OPEN_FILE; result.mErrMsg = "File can not be opened"; return result; } if (std::string headerLine; !std::getline(openFile, headerLine)) { // File is empty result.mErrCode = PassFileParseResult::ErrCode::INVALID_HEADER; result.mErrMsg = "File should have at least a header row"; return result; } csv::CSVReader reader{filePath, format}; const auto colNames = reader.get_col_names(); static const std::vector expectedColumnNames{"name", "url", "username", "password", "note"}; if (unsigned int nMissing = missingNames(colNames, expectedColumnNames, result.mErrMsg); nMissing != 0) { if (nMissing == expectedColumnNames.size()) { result.mErrCode = PassFileParseResult::ErrCode::INVALID_HEADER; result.mErrMsg += "The first line of the .csv file is expected to be a header with the " "column names separated by commas."; } else { result.mErrCode = PassFileParseResult::ErrCode::MISSING_COLUMN; } return result; } size_t expectedNumCols = colNames.size(); bool thereIsAValidEntry = false; for (auto& row: reader) { PassEntryParseResult entryResult; // Save the original line std::getline(openFile, entryResult.mOriginalContent); if (row.size() != expectedNumCols) { entryResult.mErrCode = PassEntryParseResult::ErrCode::INVALID_NUM_OF_COLUMN; result.mResults.emplace_back(std::move(entryResult)); continue; } entryResult.mName = row["name"].get(); entryResult.mUrl = row["url"].get(); entryResult.mUserName = row["username"].get(); entryResult.mPassword = row["password"].get(); entryResult.mNote = row["note"].get(); result.mResults.emplace_back(std::move(entryResult)); thereIsAValidEntry = true; } if (!thereIsAValidEntry) { result.mErrCode = PassFileParseResult::ErrCode::NO_VALID_ENTRIES; result.mErrMsg = result.mResults.empty() ? "The input file has no entries to read" : "All the entries in the file were wrongly formatted"; } return result; } PassFileParseResult readPasswordImportFile(const std::string& filePath, const FileSource source) { // Common validation // TODO: Once C++17 filesystem is allowed, check for existence for a more detailed error report if (!fileIsAccessible(filePath)) { PassFileParseResult result; result.mErrCode = PassFileParseResult::ErrCode::CANT_OPEN_FILE; result.mErrMsg = "File (" + filePath + ") could not be opened."; return result; } switch (source) { case FileSource::GOOGLE_PASSWORD: return parseGooglePasswordCSVFile(filePath); } assert(false); // All cases should be covered by the switch statement return {}; } } sdk-10.11.0/src/raid.cpp000066400000000000000000001502751516266226600146730ustar00rootroot00000000000000/** * @file mega/raid.cpp * @brief helper classes for managing cloudraid downloads * * (c) 2013-2019 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/raid.h" #include "mega/transfer.h" #include "mega/testhooks.h" #include "mega.h" // for thread definitions #include "mega/raidproxy.h" #undef min //avoids issues with std::min namespace mega { const unsigned RAID_ACTIVE_CHANNEL_FAIL_THRESHOLD = 5; struct FaultyServers { // Records URLs that had recent problems, so we can start the next raid download with URLs that can work first try. // In particular this is useful when one server in a raid set is unavailable for an extended period // This class may be shared amongst many megaclients, so thread safety is needed typedef map Map; Map recentFails; std::mutex m_mutex; string server(const string& url) { size_t n = url.find("://"); if (n != string::npos) { n += 3; size_t m = url.find("/", n); if (m != string::npos) { return url.substr(n, m - n); } } return ""; } void add(const string& url) { std::lock_guard g(m_mutex); recentFails[server(url)] = m_time(); } /** * @brief Select the worst server based on records of recent failures * @param urls The set of URLs to check against previosly failing servers * @return The index from 0 to 5, or 6 (RAIDPARTS) if none of the URLs have failed recently. */ unsigned selectWorstServer(vector urls) { // start with 6 connections and drop the slowest to respond, build the file from the other 5. // (unless we recently had problems with the server of one of the 6 URLs, in which case start with the other 5 right away) unsigned worstindex = RAIDPARTS; std::lock_guard g(m_mutex); if (!recentFails.empty()) { m_time_t now = m_time(); m_time_t worsttime = now - 10 * 3600; // 10 hours for (auto i = unsigned(urls.size()); i--; ) { Map::iterator j = recentFails.find(server(urls[i])); if (j != recentFails.end() && j->second > worsttime) { // select URL that failed less than 10 hours ago worstindex = i; worsttime = j->second; } } // cleanup recentFails from URLs older than 10 hours bool cleanup = false; Map::iterator jj; for (Map::iterator j = recentFails.begin(); j != recentFails.end(); cleanup ? (jj = j, ++j, (void)recentFails.erase(jj)) : (void)++j) { cleanup = j->second < (now - 10 * 3600); } } return worstindex; } }; FaultyServers g_faultyServers; RaidBufferManager::FilePiece::FilePiece() : pos(0) , buf(NULL, 0, 0) { } RaidBufferManager::FilePiece::FilePiece(m_off_t p, size_t len) : pos(p) , buf(new byte[len + std::min(SymmCipher::BLOCKSIZE, RAIDSECTOR)], 0, len) // SymmCipher::ctr_crypt requirement: decryption: data must be padded to BLOCKSIZE. Also make sure we can xor up to RAIDSECTOR more for convenience { } RaidBufferManager::FilePiece::FilePiece(m_off_t p, HttpReq::http_buf_t* b) // taking ownership : pos(p) , buf(NULL, 0, 0) { buf.swap(*b); // take its buffer and copy other members delete b; // client no longer owns it so we must delete. Similar to move semantics where we would just assign } void RaidBufferManager::FilePiece::swap(FilePiece& other) { m_off_t tp = pos; pos = other.pos; other.pos = tp; chunkmacs.swap(other.chunkmacs); buf.swap(other.buf); } RaidBufferManager::RaidBufferManager() : is_raid(false) , is_newRaid(false) , raidKnown(false) , raidLinesPerChunk(16 * 1024) , unusedRaidConnection(0) , raidpartspos(0) , outputfilepos(0) , startfilepos(0) , resumewastedbytes(0) , mDisableAvoidSmallLastRequest(false) { for (int i = RAIDPARTS; i--; ) { raidrequestpartpos[i] = 0; connectionPaused[i] = false; raidHttpGetErrorCount[i] = 0; connectionStarted[i] = false; } } static void clearOwningFilePieces(std::deque& q) { for (std::deque::iterator i = q.begin(); i != q.end(); ++i) { delete *i; } q.clear(); } RaidBufferManager::~RaidBufferManager() { for (int i = RAIDPARTS; i--; ) { clearOwningFilePieces(raidinputparts[i]); } } void RaidBufferManager::setIsRaid(const std::vector& tempUrls, m_off_t resumepos, m_off_t readtopos, m_off_t filesize, m_off_t maxRequestSize, bool isNewRaid) { assert(tempUrls.size() == RAIDPARTS || tempUrls.size() == 1); assert(0 <= resumepos && resumepos <= readtopos && readtopos <= filesize); assert(!raidKnown); tempurls = tempUrls; if (tempurls.size() == RAIDPARTS) { if (isNewRaid) { is_newRaid = true; } else { is_raid = true; } } raidKnown = true; fullfilesize = filesize; deliverlimitpos = readtopos; acquirelimitpos = deliverlimitpos + RAIDLINE - 1; acquirelimitpos -= acquirelimitpos % RAIDLINE; acquirelimitpos = std::min(acquirelimitpos, fullfilesize); outputfilepos = resumepos; startfilepos = resumepos; if (is_raid) { raidpartspos = resumepos / EFFECTIVE_RAIDPARTS; raidpartspos -= raidpartspos % RAIDSECTOR; resumewastedbytes = size_t(outputfilepos - raidpartspos * EFFECTIVE_RAIDPARTS); outputfilepos -= resumewastedbytes; // we'll skip over these bytes on the first output for (int i = RAIDPARTS; i--;) { raidrequestpartpos[i] = raidpartspos; } // How much buffer space can we use. Assuming two chunk sets incoming, one outgoing raidLinesPerChunk = static_cast(maxRequestSize / (RAIDPARTS * 3 * RAIDSECTOR)); raidLinesPerChunk -= raidLinesPerChunk % 1024; raidLinesPerChunk = std::min(raidLinesPerChunk, 256 * 1024); // max 256K * RAIDSECTOR * 5 raidLinesPerChunk = std::max(raidLinesPerChunk, 64 * 1024); // min 64K * RAIDSECTOR * 5 unusedRaidConnection = g_faultyServers.selectWorstServer(tempurls); LOG_debug << "[RaidBufferManager::setIsRaid] unusedRaidConnection = " << unusedRaidConnection; } DEBUG_TEST_HOOK_RAIDBUFFERMANAGER_SETISRAID(this) } void RaidBufferManager::updateUrlsAndResetPos(const std::vector& tempUrls) { // A request to restart from whereever we got to, with new URLs. // the old requested-to pos is not valid anymore, as one or more Http requests failed or were abandoned assert(tempurls.size() == tempUrls.size()); if (tempurls.size() == tempUrls.size()) { tempurls = tempUrls; if (isRaid()) { for (unsigned i = RAIDPARTS; i--; ) { std::deque& connectionpieces = raidinputparts[i]; transferPos(i) = connectionpieces.empty() ? raidpartspos : connectionpieces.back()->pos + static_cast(connectionpieces.back()->buf.datalen()); } } else { transferPos(0) = outputfilepos; // if there is any data waiting in asyncoutputbuffers this value is alreday ahead of it } } } void RaidBufferManager::disableAvoidSmallLastRequest() { mDisableAvoidSmallLastRequest = true; } bool RaidBufferManager::isRaid() const { assert(raidKnown); return is_raid; } bool RaidBufferManager::isNewRaid() const { assert(raidKnown); return is_newRaid; } bool RaidBufferManager::isUnusedRaidConection(unsigned connectionNum) const { return connectionNum == unusedRaidConnection; } bool RaidBufferManager::isRaidConnectionProgressBlocked(unsigned connectionNum) const { return connectionPaused[connectionNum]; } const std::string& RaidBufferManager::tempURL(unsigned connectionNum) { if (isRaid()) { assert(connectionNum < tempurls.size()); return tempurls[connectionNum]; } else if (!tempurls.empty()) { return tempurls[0]; } else { assert(false); // this class shouldn't be used until we have the URLs, but don't crash return emptyReturnString; } } const std::vector& RaidBufferManager::tempUrlVector() const { return tempurls; } // takes ownership of the buffer void RaidBufferManager::submitBuffer(unsigned connectionNum, FilePiece* piece) { if (isRaid()) { assert(connectionNum < RAIDPARTS); assert(piece->buf.datalen() % RAIDSECTOR == 0 || piece->pos + m_off_t(piece->buf.datalen()) == raidPartSize(connectionNum, acquirelimitpos)); if (!piece->buf.isNull()) { raidHttpGetErrorCount[connectionNum] = 0; } std::deque& connectionpieces = raidinputparts[connectionNum]; m_off_t contiguouspos = connectionpieces.empty() ? raidpartspos : connectionpieces.back()->pos + static_cast(connectionpieces.back()->buf.datalen()); assert(piece->pos == contiguouspos); if (piece->pos == contiguouspos) { // in case of download piece arriving after connection failure recovery transferPos(connectionNum) = piece->pos + static_cast(piece->buf.datalen()); raidinputparts[connectionNum].push_back(piece); } } else { finalize(*piece); assert(asyncoutputbuffers.find(connectionNum) == asyncoutputbuffers.end() || !asyncoutputbuffers[connectionNum]); asyncoutputbuffers[connectionNum].reset(piece); } } std::shared_ptr RaidBufferManager::getAsyncOutputBufferPointer(unsigned connectionNum) { auto i = asyncoutputbuffers.find(connectionNum); if (isRaid() && (i == asyncoutputbuffers.end() || !i->second)) { combineRaidParts(connectionNum); i = asyncoutputbuffers.find(connectionNum); } return (i == asyncoutputbuffers.end()) ? NULL : i->second; } void RaidBufferManager::bufferWriteCompleted(unsigned connectionNum, bool success) { auto aob = asyncoutputbuffers.find(connectionNum); if (aob != asyncoutputbuffers.end()) { assert(aob->second); if (aob->second) { if (success) { bufferWriteCompletedAction(*aob->second); } aob->second.reset(); } } } void RaidBufferManager::bufferWriteCompletedAction(FilePiece&) { // overridden for Transfers } m_off_t& RaidBufferManager::transferPos(unsigned connectionNum) { assert(isRaid()); return raidrequestpartpos[connectionNum]; } std::pair RaidBufferManager::nextNPosForConnection(unsigned connectionNum, bool& newInputBufferSupplied, bool& pauseConnectionForRaid) { // returning a pair for clarity - specifying the beginning and end position of the next data block, as the 'current pos' may be updated during this function newInputBufferSupplied = false; pauseConnectionForRaid = false; if (!isRaid()) { return std::make_pair(transferPos(connectionNum), deliverlimitpos); // simple case for non-raid direct streaming, get the entire portion of the file requested in one http get } else // raid { m_off_t curpos = transferPos(connectionNum); // if we use submitBuffer, transferPos() may be updated to protect against single connection failure recovery m_off_t maxpos = transferSize(connectionNum); // if this connection gets too far ahead of the others, pause it until the others catch up a bit if ((curpos >= raidpartspos + RaidReadAheadChunksPausePoint * raidLinesPerChunk * RAIDSECTOR) || (curpos > raidpartspos + RaidReadAheadChunksUnpausePoint * raidLinesPerChunk * RAIDSECTOR && connectionPaused[connectionNum])) { connectionPaused[connectionNum] = true; pauseConnectionForRaid = true; return std::make_pair(curpos, curpos); } else { connectionPaused[connectionNum] = false; } m_off_t npos = std::min(curpos + raidLinesPerChunk * RAIDSECTOR * RaidMaxChunksPerRead, maxpos); size_t nextChunkSize = (curpos < npos) ? static_cast(npos - curpos) : 0; LOG_debug << "Raid lines per chunk = " << raidLinesPerChunk << ", curpos = " << curpos << ", npos = " << npos << ", maxpos = " << maxpos << ", acquirelimitpos = " << acquirelimitpos << ", nextChunkSize = " << nextChunkSize; if (!mDisableAvoidSmallLastRequest) { size_t lastChunkSize = (npos < maxpos) ? // Last chunk left apart from the current chunk static_cast(maxpos - npos) : 0; if (lastChunkSize && (lastChunkSize < MIN_LAST_CHUNK)) // Dont leave a last chunk smaller than MIN_LAST_CHUNK for the last request { // If this chunk and the last one are greater or equal than +16 MB (or the corresponding value for MAX_CHUNK_SIZE), we'll ask for two chunks of +8 MB. // Otherwise, we'll request the remaining: -15 MB size_t remainingSize = static_cast( maxpos - curpos); // Remaining size (current chunk + last chunk) npos = (remainingSize >= MAX_LAST_CHUNK) ? // If the remaining size (current chunk + last chunk) is // greater than MAX_LAST_CHUNK (curpos + static_cast((remainingSize / 2) & ~(static_cast(RAIDSECTOR) - 1))) : // Npos moved to half of the remaining // size (truncated to RAIDSECTOR) maxpos; // Npos moved to the end assert(npos <= maxpos); LOG_debug << "Avoiding small last request (" << lastChunkSize << "), change npos to " << npos << ", new nextChunkSize = " << (npos - curpos); } } if (unusedRaidConnection == connectionNum && npos > curpos) { submitBuffer(connectionNum, new RaidBufferManager::FilePiece(curpos, new HttpReq::http_buf_t(NULL, 0, size_t(npos - curpos)))); transferPos(connectionNum) = npos; newInputBufferSupplied = true; } return std::make_pair(curpos, npos); } } void RaidBufferManager::resetPart(unsigned connectionNum) { assert(isRaid()); transferPos(connectionNum) = raidpartspos; // if we are downloading many files at once, eg. initial sync, or large manual folder, it's better to just use 5 connections immediately after the first g_faultyServers.add(tempurls[connectionNum]); } m_off_t RaidBufferManager::transferSize(unsigned connectionNum) { if (isRaid()) { return raidPartSize(connectionNum, acquirelimitpos); } else { return fullfilesize; } } m_off_t RaidBufferManager::raidPartSize(unsigned part, m_off_t filesize) { // compute the size of this raid part based on the original file size len m_off_t r = filesize % RAIDLINE; // residual part m_off_t t = r - (part - !!part)*RAIDSECTOR; // parts 0 (parity) & 1 (largest data) are the same size // (excess length will be found in the following sectors, // negative length in the preceding sectors) if (t < 0) { t = 0; } else if (t > RAIDSECTOR) { t = RAIDSECTOR; } return (filesize - r) / EFFECTIVE_RAIDPARTS + t; } m_off_t RaidBufferManager::offsetToRaidLine(const m_off_t offset) { return offset % RAIDLINE; } void RaidBufferManager::combineRaidParts(unsigned connectionNum) { assert(asyncoutputbuffers.find(connectionNum) == asyncoutputbuffers.end() || !asyncoutputbuffers[connectionNum]); assert(raidpartspos * EFFECTIVE_RAIDPARTS == outputfilepos + m_off_t(leftoverchunk.buf.datalen())); size_t partslen = 0x10000000, sumdatalen = 0, xorlen = 0; for (unsigned i = RAIDPARTS; i--; ) { if (raidinputparts[i].empty()) { partslen = 0; } else { FilePiece& r = *raidinputparts[i].front(); assert(r.pos == raidpartspos); // check all are in sync at the front partslen = std::min(partslen, r.buf.datalen()); (i > 0 ? sumdatalen : xorlen) += r.buf.datalen(); } } partslen -= partslen % RAIDSECTOR; // restrict to raidline boundary // for correct mac processing, we need to process the output file in pieces delimited by the chunkfloor / chunkceil algorithm m_off_t newdatafilepos = outputfilepos + static_cast(leftoverchunk.buf.datalen()); assert(newdatafilepos + m_off_t(sumdatalen) <= acquirelimitpos); bool processToEnd = (newdatafilepos + m_off_t(sumdatalen) == acquirelimitpos) // data to the end && (newdatafilepos / EFFECTIVE_RAIDPARTS + m_off_t(xorlen) == raidPartSize(0, acquirelimitpos)); // parity to the end assert(!partslen || !processToEnd || sumdatalen - partslen * EFFECTIVE_RAIDPARTS <= RAIDLINE); if (partslen > 0 || processToEnd) { m_off_t macchunkpos = calcOutputChunkPos(newdatafilepos + static_cast(partslen) * EFFECTIVE_RAIDPARTS); size_t buflen = static_cast(processToEnd ? sumdatalen : partslen * EFFECTIVE_RAIDPARTS); LOG_debug << "Combining raid parts -> partslen = " << partslen << ", buflen = " << buflen << ", outputfilepos = " << outputfilepos << ", leftoverchunk = " << leftoverchunk.buf.datalen(); FilePiece* outputrec = combineRaidParts(partslen, buflen, outputfilepos, leftoverchunk); // includes a bit of extra space for non-full sectors if we are at the end of the file rollInputBuffers(partslen); raidpartspos += partslen; sumdatalen -= partslen * EFFECTIVE_RAIDPARTS; outputfilepos += partslen * EFFECTIVE_RAIDPARTS + leftoverchunk.buf.datalen(); byte* dest = outputrec->buf.datastart() + partslen * EFFECTIVE_RAIDPARTS + leftoverchunk.buf.datalen(); FilePiece emptyFilePiece; leftoverchunk.swap(emptyFilePiece); // this data is entirely included in the outputrec now, so discard and reset if (processToEnd && sumdatalen > 0) { // fill in the last of the buffer with non-full sectors from the end of the file assert(outputfilepos + m_off_t(sumdatalen) == acquirelimitpos); combineLastRaidLine(dest, sumdatalen); rollInputBuffers(RAIDSECTOR); } else if (!processToEnd && outputfilepos > macchunkpos) { // for transfers we do mac processing which must be done in chunks, delimited by chunkfloor and chunkceil. If we don't have the right amount then hold the remainder over for next time. size_t excessdata = static_cast(outputfilepos - macchunkpos); FilePiece newleftover(outputfilepos - static_cast(excessdata), excessdata); leftoverchunk.swap(newleftover); memcpy(leftoverchunk.buf.datastart(), outputrec->buf.datastart() + outputrec->buf.datalen() - excessdata, excessdata); outputrec->buf.end -= excessdata; outputfilepos -= excessdata; assert(raidpartspos * EFFECTIVE_RAIDPARTS == outputfilepos + m_off_t(leftoverchunk.buf.datalen())); } // discard any excess data that we had to fetch when resuming a file (to align the parts appropriately) size_t n = std::min(outputrec->buf.datalen(), resumewastedbytes); if (n > 0) { outputrec->pos += n; outputrec->buf.start += n; resumewastedbytes -= n; } // don't deliver any excess data that we needed for parity calculations in the last raid line if (outputrec->pos + m_off_t(outputrec->buf.datalen()) > deliverlimitpos) { size_t excess = static_cast(outputrec->pos) + outputrec->buf.datalen() - static_cast(deliverlimitpos); excess = std::min(excess, outputrec->buf.datalen()); outputrec->buf.end -= excess; } // store the result in a place that can be read out async if (outputrec->buf.datalen() > 0) { finalize(*outputrec); asyncoutputbuffers[connectionNum].reset(outputrec); } else { delete outputrec; // this would happen if we got some data to process on all connections, but not enough to reach the next chunk boundary yet (and combined data is in leftoverchunk) } } } RaidBufferManager::FilePiece* RaidBufferManager::combineRaidParts(size_t partslen, size_t bufflen, m_off_t filepos, FilePiece& prevleftoverchunk) { assert(prevleftoverchunk.buf.datalen() == 0 || prevleftoverchunk.pos == filepos); // add a bit of extra space and copy prev chunk to the front FilePiece* result = new FilePiece(filepos, bufflen + prevleftoverchunk.buf.datalen()); if (prevleftoverchunk.buf.datalen() > 0) { memcpy(result->buf.datastart(), prevleftoverchunk.buf.datastart(), prevleftoverchunk.buf.datalen()); } // usual case, for simple and fast processing: all input buffers are the same size, and aligned, and a multiple of raidsector if (partslen > 0) { byte* inputbufs[RAIDPARTS]; for (unsigned i = RAIDPARTS; i--; ) { FilePiece* inputPiece = raidinputparts[i].front(); inputbufs[i] = inputPiece->buf.isNull() ? NULL : inputPiece->buf.datastart(); } byte* b = result->buf.datastart() + prevleftoverchunk.buf.datalen(); byte* endpos = b + partslen * EFFECTIVE_RAIDPARTS; for (unsigned i = 0; b < endpos; i += RAIDSECTOR) { for (unsigned j = 1; j < RAIDPARTS; ++j) { assert(b + RAIDSECTOR <= result->buf.datastart() + result->buf.datalen()); if (inputbufs[j]) { memcpy(b, inputbufs[j] + i, RAIDSECTOR); } else { recoverSectorFromParity(b, inputbufs, i); } b += RAIDSECTOR; } } assert(b == endpos); } return result; } void RaidBufferManager::recoverSectorFromParity(byte* dest, byte* inputbufs[], unsigned offset) { assert(sizeof(m_off_t)*2 == RAIDSECTOR); bool set = false; for (unsigned i = RAIDPARTS; i--; ) { if (inputbufs[i]) { if (!set) { memcpy(dest, inputbufs[i] + offset, RAIDSECTOR); set = true; } else { *(m_off_t*)dest ^= *(m_off_t*)(inputbufs[i] + offset); *(m_off_t*)(dest + sizeof(m_off_t)) ^= *(m_off_t*)(inputbufs[i] + offset + sizeof(m_off_t)); } } } } void RaidBufferManager::combineLastRaidLine(byte* dest, size_t remainingbytes) { // we have to be careful to use the right number of bytes from each sector for (unsigned i = 1; i < RAIDPARTS && remainingbytes > 0; ++i) { if (!raidinputparts[i].empty()) { FilePiece* sector = raidinputparts[i].front(); size_t n = std::min(remainingbytes, sector->buf.datalen()); if (!sector->buf.isNull()) { memcpy(dest, sector->buf.datastart(), n); } else { memset(dest, 0, n); for (unsigned j = RAIDPARTS; j--; ) { if (!raidinputparts[j].empty() && !raidinputparts[j].front()->buf.isNull()) { FilePiece* xs = raidinputparts[j].front(); for (size_t x = std::min(n, xs->buf.datalen()); x--; ) { // Integer promotion with bitwise operators dest[x] = static_cast(dest[x] ^ xs->buf.datastart()[x]); } } } } dest += n; remainingbytes -= n; } } } void RaidBufferManager::rollInputBuffers(size_t dataToDiscard) { // remove finished input buffers for (unsigned i = RAIDPARTS; i--; ) { if (!raidinputparts[i].empty()) { FilePiece& ip = *raidinputparts[i].front(); ip.buf.start += dataToDiscard; ip.pos += dataToDiscard; if (ip.buf.start >= ip.buf.end) { delete raidinputparts[i].front(); raidinputparts[i].pop_front(); } } } } m_off_t TransferBufferManager::calcOutputChunkPos(m_off_t acquiredpos) { return ChunkedHash::chunkfloor(acquiredpos); // we can only mac to the chunk boundary, hold the rest over } // decrypt, mac downloaded chunk bool RaidBufferManager::FilePiece::finalize(bool parallel, m_off_t filesize, int64_t ctriv, SymmCipher *cipher, chunkmac_map* source_chunkmacs) { assert(!finalized); bool queueParallel = false; byte *chunkstart = buf.datastart(); m_off_t startpos = pos; m_off_t finalpos = startpos + static_cast(buf.datalen()); assert(finalpos <= filesize); if (finalpos != filesize) { finalpos &= -SymmCipher::BLOCKSIZE; } m_off_t endpos = ChunkedHash::chunkceil(startpos, finalpos); unsigned chunksize = static_cast(endpos - startpos); while (chunksize) { m_off_t chunkid = ChunkedHash::chunkfloor(startpos); if (!chunkmacs.finishedAt(chunkid)) { if (source_chunkmacs) { source_chunkmacs->copyEntryTo(chunkid, chunkmacs); } if (endpos == ChunkedHash::chunkceil(chunkid, filesize)) { if (parallel) { // executing on a worker thread (or synchronously on transferslot destruction) // these are independent chunks, or the earlier part of the chunk is already done. chunkmacs.ctr_decrypt(chunkid, cipher, chunkstart, chunksize, startpos, ctriv, true); LOG_debug << "Finished chunk: " << startpos << " - " << endpos << " Size: " << chunksize; } else { queueParallel = true; } } else if (!parallel) { // these part chunks must be done serially (and first), since later parts of a chunk need the mac of earlier parts as input. chunkmacs.ctr_decrypt(chunkid, cipher, chunkstart, chunksize, startpos, ctriv, false); LOG_debug << "Decrypted partial chunk: " << startpos << " - " << endpos << " Size: " << chunksize; } } chunkstart += chunksize; startpos = endpos; endpos = ChunkedHash::chunkceil(startpos, finalpos); chunksize = static_cast(endpos - startpos); } finalized = !queueParallel; if (finalized) finalizedCV.notify_one(); return queueParallel; } void TransferBufferManager::finalize(FilePiece&) { // for transfers (as opposed to DirectRead), decrypt/mac is now done on threads } bool RaidBufferManager::tryRaidHttpGetErrorRecovery(unsigned errorConnectionNum, bool incrementErrors) { assert(isRaid()); if (incrementErrors) { raidHttpGetErrorCount[errorConnectionNum] += 1; } g_faultyServers.add(tempurls[errorConnectionNum]); unsigned errorSum = 0; unsigned highestErrors = 0; for (unsigned i = RAIDPARTS; i--; ) { errorSum += raidHttpGetErrorCount[i]; highestErrors = std::max(highestErrors, raidHttpGetErrorCount[i]); } // Allow for one nonfunctional channel and one glitchy channel. We can still make progress swapping back and forth if ((errorSum - highestErrors) < RAID_ACTIVE_CHANNEL_FAIL_THRESHOLD) { if (unusedRaidConnection < RAIDPARTS) { LOG_warn << "5 connection cloudraid shutting down connection " << errorConnectionNum << " due to error, and starting " << unusedRaidConnection << " instead"; // start up the old unused connection, and cancel this one. Other connections all have real data since we were already in 5 connection mode clearOwningFilePieces(raidinputparts[unusedRaidConnection]); clearOwningFilePieces(raidinputparts[errorConnectionNum]); raidrequestpartpos[unusedRaidConnection] = raidpartspos; raidrequestpartpos[errorConnectionNum] = raidpartspos; } else { LOG_warn << "6 connection cloudraid shutting down connection " << errorConnectionNum << " due to error"; clearOwningFilePieces(raidinputparts[errorConnectionNum]); raidrequestpartpos[errorConnectionNum] = raidpartspos; } unusedRaidConnection = errorConnectionNum; return true; } else { return false; } } bool RaidBufferManager::detectSlowestRaidConnection(unsigned thisConnection, unsigned& slowestConnection) { if (isRaid() && unusedRaidConnection == RAIDPARTS) { connectionStarted[thisConnection] = true; int count = 0; for (unsigned j = RAIDPARTS; j--; ) { if (!connectionStarted[j]) { slowestConnection = j; ++count; } } if (count == 1) { unusedRaidConnection = slowestConnection; raidrequestpartpos[unusedRaidConnection] = raidpartspos; return true; } } return false; } bool RaidBufferManager::setUnusedRaidConnection(unsigned newUnusedRaidConnection) { if (isRaid() && newUnusedRaidConnection < RAIDPARTS) { LOG_debug << "Set unused raid connection to " << newUnusedRaidConnection << " (clear previous unused connection: " << unusedRaidConnection << ")"; if (unusedRaidConnection < RAIDPARTS) clearOwningFilePieces(raidinputparts[unusedRaidConnection]); clearOwningFilePieces(raidinputparts[newUnusedRaidConnection]); if (unusedRaidConnection < RAIDPARTS) raidrequestpartpos[unusedRaidConnection] = raidpartspos; raidrequestpartpos[newUnusedRaidConnection] = raidpartspos; unusedRaidConnection = newUnusedRaidConnection; return true; } return false; } unsigned RaidBufferManager::getUnusedRaidConnection() const { return unusedRaidConnection; } m_off_t RaidBufferManager::progress() const { assert(isRaid()); m_off_t reportPos = 0; for (unsigned j = RAIDPARTS; j--; ) { for (FilePiece* p : raidinputparts[j]) { if (!p->buf.isNull()) { reportPos += p->buf.datalen(); } } } if (!leftoverchunk.buf.isNull()) { // Need to count the valid data on the leftover chunk, if any. reportPos += leftoverchunk.buf.datalen(); } return reportPos; } TransferBufferManager::TransferBufferManager() : transfer(NULL) { } void TransferBufferManager::setIsRaid(Transfer* t, const std::vector& tempUrls, m_off_t resumepos, m_off_t maxRequestSize, bool isNewRaid) { transfer = t; RaidBufferManager::setIsRaid(tempUrls, resumepos, t->size, t->size, maxRequestSize, isNewRaid && t->type == GET); } m_off_t& TransferBufferManager::transferPos(unsigned connectionNum) { return isRaid() ? RaidBufferManager::transferPos(connectionNum) : transfer->pos; } std::pair TransferBufferManager::nextNPosForConnection(unsigned connectionNum, m_off_t maxRequestSize, unsigned connectionCount, bool& newInputBufferSupplied, bool& pauseConnectionForRaid, m_off_t uploadSpeed) { // returning a pair for clarity - specifying the beginning and end position of the next data block, as the 'current pos' may be updated during this function newInputBufferSupplied = false; pauseConnectionForRaid = false; if (isRaid()) { return RaidBufferManager::nextNPosForConnection(connectionNum, newInputBufferSupplied, pauseConnectionForRaid); } transfer->pos = transfer->size ? transfer->chunkmacs.nextUnprocessedPosFrom(transfer->pos) : 0; m_off_t npos = transfer->size ? ChunkedHash::chunkceil(transfer->pos, transfer->size) : 0; if(npos > transfer->pos) { // Calc limit for request size value depending on connection/transfer/progress heuristics. m_off_t maxReqSize = 0; if (transfer->type == PUT) { // choose upload chunks that are big enough to saturate the connection, so we don't start HTTP PUT request too frequently // make them smaller at the end of the file so we still have the last parts delivered in parallel m_off_t largeSize = 32 * 1024 * 1024; m_off_t maxsize = largeSize; if (npos + 2 * maxsize > transfer->size) maxsize /= 2; if (npos + maxsize > transfer->size) maxsize /= 2; if (npos + maxsize > transfer->size) maxsize /= 2; m_off_t speedsize = std::min(maxsize, uploadSpeed * 2 / 3); // two seconds of data over 3 connections m_off_t sizesize = transfer->size > largeSize ? 8 * 1024 * 1024 : 0; // start with large-ish portions for large files. m_off_t targetsize = std::max(sizesize, speedsize); maxReqSize = targetsize; } else if (transfer->type == GET) { if (isNewRaid()) { if (const auto posOffsetToRaidLine = offsetToRaidLine(transfer->pos); posOffsetToRaidLine) { LOG_err << "Wrong transfer->pos for new raid, not padded to RAIDLINE: " "transfer->pos = " << transfer->pos << ", RAIDLINE = " << RAIDLINE << ", mod = " << posOffsetToRaidLine; return std::make_pair(-1, -1); } // We need to adjust the size taking into account that our RaidReqs will be split into 5 requests (one for each part) // Besides, we need that the RaidReqs (except for the last one) are padded to a RAIDLINE m_off_t defaultMaxReqSize = static_cast((TransferSlot::MAX_REQ_SIZE_NEW_RAID) * EFFECTIVE_RAIDPARTS); const m_off_t AVERAGE_NUMBER_OF_TRANSFERSLOT_CONNECTIONS = 4; // based on the average number of connections per download (and default value in MEGASync) m_off_t maxReqsSize = defaultMaxReqSize * AVERAGE_NUMBER_OF_TRANSFERSLOT_CONNECTIONS; maxReqSize = static_cast(maxReqsSize / transfer->slot->connections); // divided by the real number of connections maxReqSize = std::max(maxReqSize, (1 * 1024 * 1024) * EFFECTIVE_RAIDPARTS); // min 1MB for each raidpart DEBUG_TEST_HOOK_LIMIT_MAX_REQ_SIZE(maxReqSize) // Limit max request size if needed maxReqSize = std::min(maxReqSize, transfer->size); // Not greater than the transfer itself if (transfer->size <= TransferSlot::UPPER_FILESIZE_LIMIT_FOR_SMALLER_CHUNKS) { maxReqSize = transfer->size / AVERAGE_NUMBER_OF_TRANSFERSLOT_CONNECTIONS; } m_off_t nextChunk = ChunkedHash::chunkceil(transfer->pos + maxReqSize, transfer->size); while ((nextChunk < transfer->size) && (((nextChunk - transfer->pos) % RAIDLINE) != 0)) { // Needed for expandUnprocessedPiece to return a chunk padded to raid-line (for chunks which are not the last one) maxReqSize = ChunkedHash::chunkceil(maxReqSize, transfer->size); nextChunk = ChunkedHash::chunkceil(transfer->pos + maxReqSize, transfer->size); } maxReqSize += 1; // Same as above, needed for expandUnProcessedPiece to return a chunk padded to raid-line } else { // Non raid maxReqSize = (transfer->size - transfer->progresscompleted) / connectionCount / 2; if (maxReqSize > maxRequestSize) { maxReqSize = maxRequestSize; } if (maxReqSize > 0x100000) { m_off_t val = 0x100000; while (val <= maxReqSize) { val <<= 1; } maxReqSize = val >> 1; maxReqSize -= 0x100000; } else { maxReqSize = 0; } } } // Calc npos limit depending on the maxReqSize, the next processed piece and the transfer size. npos = transfer->chunkmacs.expandUnprocessedPiece(transfer->pos, npos, transfer->size, maxReqSize); if (isNewRaid() && (npos < transfer->size)) { if (const auto chunkSize = (npos - transfer->pos), chunkOffsetToRaidLine = offsetToRaidLine(chunkSize); chunkOffsetToRaidLine) { LOG_err << "Wrong chunk size for new raid, not padded to RAIDLINE: pos = " << transfer->pos << ", npos = " << npos << ", chunk size = " << chunkSize << ", RAIDLINE = " << RAIDLINE << ", mod = " << chunkOffsetToRaidLine; return std::make_pair(-1, -1); } } LOG_debug << std::string(transfer->type == PUT ? "Uploading" : transfer->type == GET ? "Downloading" : "?") << " chunk of size " << npos - transfer->pos; assert(npos > transfer->pos); } return std::make_pair(transfer->pos, npos); } void TransferBufferManager::bufferWriteCompletedAction(FilePiece& r) { r.chunkmacs.copyEntriesTo(transfer->chunkmacs); r.chunkmacs.clear(); transfer->setProgresscompleted(static_cast(r.buf.datalen()), true /*append*/); LOG_debug << "On write completed -> Cached data at: " << r.pos << " Size: " << r.buf.datalen() << " Progress completed: " << transfer->progresscompleted << "/" << transfer->size; } DirectReadBufferManager::DirectReadBufferManager(DirectRead* dr) { directRead = dr; } m_off_t& DirectReadBufferManager::transferPos(unsigned connectionNum) { return isRaid() ? RaidBufferManager::transferPos(connectionNum) : directRead->nextrequestpos; } m_off_t DirectReadBufferManager::calcOutputChunkPos(m_off_t acquiredpos) { return acquiredpos; // give all the data straight away for streaming, no need to hold any over for mac boundaries } void DirectReadBufferManager::finalize(FilePiece& fp) { int r, l, t; // decrypt, pass to app and erase r = fp.pos & (SymmCipher::BLOCKSIZE - 1); t = int(fp.buf.datalen()); if (r) { byte buf[SymmCipher::BLOCKSIZE]; l = static_cast(sizeof(buf)) - r; if (l > t) { l = t; } memcpy(buf + r, fp.buf.datastart(), static_cast(l)); directRead->drn->symmcipher.ctr_crypt(buf, sizeof buf, fp.pos - r, static_cast(directRead->drn->ctriv), NULL, false); memcpy(fp.buf.datastart(), buf + r, static_cast(l)); } else { l = 0; } if (t > l) { // the buffer has some extra at the end to allow full blocksize decrypt at the end directRead->drn->symmcipher.ctr_crypt(fp.buf.datastart() + l, static_cast(t - l), fp.pos + l, static_cast(directRead->drn->ctriv), NULL, false); } } class CloudRaid::CloudRaidImpl { private: std::vector> mRaidReqPoolArray; int mConnections; TransferSlot* mTSlot; MegaClient* mClient; bool mStarted; uint8_t mUnusedRaidConnection; std::pair<::mega::error, dstime> mTransferFailed; // Error and backoff to call transfer->failed() public: CloudRaidImpl(TransferSlot* tslot, MegaClient* client, int connections) : mConnections(connections) , mTSlot(tslot) , mClient(client) , mStarted(false) , mUnusedRaidConnection(RAIDPARTS) { LOG_verbose << "[CloudRaidImpl::CloudRaidImpl] CONSTRUCTOR CALL [this = " << this << "]"; assert(mTSlot != nullptr); assert(mClient != nullptr); mTransferFailed = std::make_pair(API_OK, 0); start(); } ~CloudRaidImpl() { LOG_verbose << "[CloudRaidImpl::~CloudRaidImpl] DESTRUCTOR CALL [this = " << this << "]"; stop(); } /* TransferSlot functionality */ bool disconnect(const std::shared_ptr& req) { if (!mStarted) return false; mTSlot->disconnect(req); return true; } bool prepareRequest(const std::shared_ptr& req, const string& tempURL, m_off_t pos, m_off_t npos) { if (!mStarted) return false; mTSlot->prepareRequest(req, tempURL, pos, npos); return req->status == REQ_PREPARED; } bool post(const std::shared_ptr& req) { if (!mStarted) return false; mTSlot->processRequestPost(mClient, req); return req->status == REQ_INFLIGHT; } bool onRequestFailure(const std::shared_ptr& req, uint8_t part, dstime& backoff) { if (!mStarted) return false; dstime tslot_backoff = 0; auto failValues = mTSlot->processRequestFailure(mClient, req, tslot_backoff, static_cast(part)); backoff = tslot_backoff; if (failValues.first != API_OK) { setTransferFailure(failValues.first, failValues.second); } return true; } bool setTransferFailure(::mega::error e, dstime backoff) { if (!mStarted) return false; if (mTransferFailed.first) { LOG_warn << "[CloudRaid::setTransferFailure] Transfer failed values are already set. Previous values: error = " << e << ", backoff = " << backoff; } LOG_debug << "[CloudRaid::setTransferFailure] Transfer failed values set to: error = " << e << ", backoff = " << backoff; mTransferFailed.first = e; mTransferFailed.second = backoff; return true; } std::pair<::mega::error, dstime> checkTransferFailure() { if (!mStarted) return std::make_pair(API_OK, 0); return std::make_pair(mTransferFailed.first, mTransferFailed.second); } bool setUnusedRaidConnection(uint8_t part, bool addToFaultyServers) { if (!mStarted) return false; if (part >= RAIDPARTS) { LOG_warn << "[CloudRaid::setUnusedRaidConnection] Invalid connection index, setting it to 0"; assert(false && "Unused raid must be within RAIDPARTS"); mUnusedRaidConnection = 0; return false; } LOG_debug << "[CloudRaid::setUnusedRaidConnection] Set unused raid connection to " << (int)part << " (clear previous unused connection: " << (int)mUnusedRaidConnection << ") [addToFaultyServers = " << addToFaultyServers << "]"; mUnusedRaidConnection = part; if (addToFaultyServers) { g_faultyServers.add(mTSlot->transferbuf.tempUrlVector()[part]); } return true; } uint8_t getUnusedRaidConnection() const { return mUnusedRaidConnection; // No need to check if mStarted, there is always a default value, and if we stop it with a previous set value it is ok to retrieve it } m_off_t transferred(const std::shared_ptr& req) const { if (!mStarted) return 0; return req->transferred(mClient); } bool processRequestLatency(const std::shared_ptr& req) { if (!mStarted) return false; mTSlot->processRequestLatency(req); return true; } /* CloudRaid functionality */ bool balancedRequest(int connection, const std::vector &tempUrls, size_t cfilesize, m_off_t cstart, size_t creqlen) { if (!mStarted) { start(); } RaidProxy::RaidReq::Params raidReqParams(tempUrls, cfilesize, cstart, creqlen); mRaidReqPoolArray[static_cast(connection)].reset(new RaidProxy::RaidReqPool()); mRaidReqPoolArray[static_cast(connection)]->request(raidReqParams, mTSlot->getcloudRaidPtr()); return mRaidReqPoolArray[static_cast(connection)]->rr() != nullptr; } bool start() { if (mStarted) { return false; } mRaidReqPoolArray.resize(static_cast(mConnections)); mStarted = true; if (mUnusedRaidConnection == RAIDPARTS) { mUnusedRaidConnection = static_cast(g_faultyServers.selectWorstServer(mTSlot->transferbuf.tempUrlVector())); } LOG_debug << "[CloudRaid::start] CloudRAID started. Initial unused raid connection: " << (int)mUnusedRaidConnection; return true; } bool stop() { LOG_verbose << "[CloudRaid::stop] stop CALL [started = " << mStarted << "] [this = " << this << "]"; if (!mStarted) { return false; } mRaidReqPoolArray.clear(); mStarted = false; return true; } bool removeRaidReq(int connection) { LOG_verbose << "[CloudRaid::removeRaidReq] connection = " << connection << " [started = " << mStarted << "] [this = " << this << "]"; if (mStarted && mRaidReqPoolArray[static_cast(connection)]) { mRaidReqPoolArray[static_cast(connection)].reset(); return true; } return false; } bool resumeAllConnections() { if (mStarted) { int i = mConnections; while (i-- > 0) { if (mRaidReqPoolArray[static_cast(i)]) { mRaidReqPoolArray[static_cast(i)]->rr()->resumeall(); } } return true; } return false; } m_off_t readData(int connection, byte* buf, m_off_t len) { m_off_t readData = -1; if (mStarted && mRaidReqPoolArray[static_cast(connection)]) { readData = static_cast( mRaidReqPoolArray[static_cast(connection)]->rr()->readdata(buf, len)); } return readData; } bool raidReqDoio(int connection) { if (mStarted && mRaidReqPoolArray[static_cast(connection)]) { mRaidReqPoolArray[static_cast(connection)]->raidproxyio(); return true; } return false; } m_off_t progress() const { m_off_t progressCount = 0; if (mStarted) { int i = mConnections; while (i-- > 0) { if (mRaidReqPoolArray[static_cast(i)]) { progressCount += mRaidReqPoolArray[static_cast(i)]->rr()->progress(); } } } return progressCount; } }; CloudRaid::CloudRaid() { } CloudRaid::CloudRaid(TransferSlot* tslot, MegaClient* client, int connections) { LOG_verbose << "[CloudRaid::CloudRaid] CONSTRUCTOR CALL [this = " << this << "]"; init(tslot, client, connections); } CloudRaid::~CloudRaid() { LOG_verbose << "[CloudRaid::~CloudRaid] DESTRUCTOR CALL [this = " << this << "]"; } bool CloudRaid::isShown() const { return mShown; } /* TransferSlot functionality */ bool CloudRaid::disconnect(const std::shared_ptr& req) { if (!mShown) return false; return mPimpl()->disconnect(req); } bool CloudRaid::prepareRequest(const std::shared_ptr& req, const string& tempURL, m_off_t pos, m_off_t npos) { if (!mShown) return false; return mPimpl()->prepareRequest(req, tempURL, pos, npos); } bool CloudRaid::post(const std::shared_ptr& req) { if (!mShown) return false; return mPimpl()->post(req); } bool CloudRaid::onRequestFailure(const std::shared_ptr& req, uint8_t part, dstime& backoff) { if (!mShown) return false; return mPimpl()->onRequestFailure(req, part, backoff); } bool CloudRaid::setTransferFailure(::mega::error e, dstime backoff) { if (!mShown) return false; return mPimpl()->setTransferFailure(e, backoff); } std::pair<::mega::error, dstime> CloudRaid::checkTransferFailure() { if (!mShown) return std::make_pair(API_OK, 0); return mPimpl()->checkTransferFailure(); } bool CloudRaid::setUnusedRaidConnection(uint8_t part, bool addToFaultyServers) { if (!mShown) return false; return mPimpl()->setUnusedRaidConnection(part, addToFaultyServers); } uint8_t CloudRaid::getUnusedRaidConnection() const { return mPimpl()->getUnusedRaidConnection(); // No need to check if mShown, there is always a default value, and if we stop it with a previous set value it is ok to retrieve it } m_off_t CloudRaid::transferred(const std::shared_ptr& req) const { if (!mShown) return 0; return mPimpl()->transferred(req); } bool CloudRaid::processRequestLatency(const std::shared_ptr& req) { if (!mShown) return false; return mPimpl()->processRequestLatency(req); } bool CloudRaid::init(TransferSlot* tslot, MegaClient* client, int connections) { m_pImpl = std::make_unique(tslot, client, static_cast(connections)); mShown = m_pImpl != nullptr; return mShown; } bool CloudRaid::balancedRequest(int connection, const std::vector& tempUrls, size_t cfilesize, m_off_t cstart, size_t creqlen) { if (!mShown) return false; return mPimpl()->balancedRequest(connection, tempUrls, cfilesize, cstart, creqlen); } bool CloudRaid::removeRaidReq(int connection) { if (!mShown) return false; return mPimpl()->removeRaidReq(connection); } m_off_t CloudRaid::readData(int connection, byte* buf, m_off_t len) { if (!mShown) return -1; return mPimpl()->readData(connection, buf, len); } bool CloudRaid::resumeAllConnections() { if (!mShown) return false; return mPimpl()->resumeAllConnections(); } bool CloudRaid::raidReqDoio(int connection) { if (!mShown) return false; return mPimpl()->raidReqDoio(connection); } bool CloudRaid::stop() { if (!mShown) return false; return mPimpl()->stop(); } m_off_t CloudRaid::progress() const { if (!mShown) return 0; return mPimpl()->progress(); } }; // namespace sdk-10.11.0/src/raidproxy.cpp000066400000000000000000001404021516266226600157640ustar00rootroot00000000000000#include #include #include #include #include #include "mega/raidproxy.h" #include "mega.h" using namespace ::mega::RaidProxy; #define MAX_DELAY_IN_SECONDS 30 #define OWNREADAHEAD 64 #define ISOWNREADAHEAD(X) ((X) & OWNREADAHEAD) #define VALIDPARTS(X) ((X) & (OWNREADAHEAD-1)) #define SECTORFLOOR(X) ((X) & -RAIDSECTOR) // Macro to calculate the number of sectors per part based on NUMLINES (NL) #define SECTORSPERPART(NL) (NL * RAIDSECTOR) /* -------------- PartFetcher --------------*/ PartFetcher::PartFetcher() : lastdata(Waiter::ds) { } PartFetcher::~PartFetcher() { if (rr) { closesocket(); while (!mReadahead.empty()) { free(mReadahead.begin()->second.first); mReadahead.erase(mReadahead.begin()); } } } // sets the next read position (pos) and the remaining read length (rem/remfeed), // taking into account all readahead data and ongoing reads on other connected fetchers. void PartFetcher::setposrem() { // we want to continue reading at the 2nd lowest position: // take the two lowest positions and use the higher one. static thread_local map chunks; m_off_t basepos = rr->mDataline * RAIDSECTOR; m_off_t curpos = basepos + rr->mPartpos[part]; // determine the next suitable read range to ensure the availability // of EFFECTIVE_RAIDPARTS sources based on ongoing reads and stored readahead data. for (m_off_t index = RAIDPARTS; index--;) { size_t i = static_cast(index); // compile boundaries of data chunks that have been or are being fetched // (we do not record the beginning, as position 0 is implicitly valid) // a) already read data in the PartReq buffer chunks[SECTORFLOOR(basepos + rr->mPartpos[i])]--; // b) ongoing fetches on *other* channels if (i != part) { if (rr->mFetcher[i].mRem) { chunks[SECTORFLOOR(rr->mFetcher[i].mPos)]++; chunks[SECTORFLOOR(rr->mFetcher[i].mPos + rr->mFetcher[i].mRem)]--; } } // c) existing readahead data auto it = rr->mFetcher[i].mReadahead.begin(); auto end = rr->mFetcher[i].mReadahead.end(); while (it != end) { m_off_t t = it->first; // we mark our own readahead as always valid to prevent double reads char delta = 1; if (i == part) delta += OWNREADAHEAD; chunks[SECTORFLOOR(t)] += delta; // (concatenate contiguous readahead chunks to reduce chunks inserts) do { t += it->second.second; } while (++it != end && t == it->first); chunks[SECTORFLOOR(t)] -= delta; } } // find range from the first position after the current read position // with less than EFFECTIVE_RAIDPARTS valid sources // (where we need to start fetching) to the first position thereafter with // EFFECTIVE_RAIDPARTS valid sources, if any (where we would need to stop fetching) char valid = RAIDPARTS; m_off_t startpos = -1, endpos = -1; for (auto it = chunks.begin(); it != chunks.end(); ) { auto next_it = it; next_it++; valid += it->second; assert(valid >= 0 && VALIDPARTS(valid) < RAIDPARTS); if (startpos == -1) { // no startpos yet+ (our own readahead is excluded by valid being bumped by OWNREADAHEAD) if (valid < EFFECTIVE_RAIDPARTS) { if (curpos < it->first) { startpos = it->first; } else if (next_it == chunks.end() || curpos < next_it->first) { startpos = curpos; } } } else { // startpos valid, look for suitable endpos // (must not cross own readahead data or any already sufficient raidparts) if (valid >= EFFECTIVE_RAIDPARTS) { endpos = it->first; break; } } it = next_it; } // clear early, hoping that much of chunks is still in the L1/L2 cache chunks.clear(); // there is always a startpos, even though it may be at sourcesize assert(startpos != -1); // we always resume fetching at a raidsector boundary. // (this may result in the unnecessary retransmission of up to 15 bytes // in case we resume on the same part.) startpos &= -RAIDSECTOR; if (endpos == -1) { // no sufficient number of sources past startpos, we read to the end mRem = rr->mPaddedpartsize - startpos; } else { assert(endpos >= startpos); mRem = endpos - startpos; } mPos = startpos; setremfeed(mRem); } bool PartFetcher::setremfeed(m_off_t numBytes) { // request 1 less RAIDSECTOR as we will add up to 1 sector of 0 bytes at the end of the file - this leaves enough buffer space in the buffer pased to procdata for us to write past the reported length mRemfeed = numBytes ? std::min(mRem, numBytes) : mRem; if (mSourcesize - mPos < mRemfeed) { if (mSourcesize - mPos >= 0) { mRemfeed = mSourcesize - mPos; // we only read to the physical end of the part } else { mRem = 0; mRemfeed = 0; } } return mRemfeed != 0; } int64_t PartFetcher::onFailure() { auto& httpReq = rr->mHttpReqs[part]; assert(httpReq != nullptr); if (httpReq->status == REQ_FAILURE) { raidTime backoff = 0; { if (rr->mCloudRaid->onRequestFailure(httpReq, part, backoff)) { if (httpReq->status == REQ_FAILURE) { auto failValues = rr->mCloudRaid->checkTransferFailure(); if (!failValues.first) { LOG_warn << "[PartFetcher::onFailure] Request failure on part " << (int)part << " with no transfer fail values" << " [this = " << this << "]"; assert(false); } rr->setNewUnusedRaidConnection(part); closesocket(); return -1; } if (httpReq->status == REQ_PREPARED) { return trigger(backoff); } } } LOG_warn << "CloudRAID connection to part " << (int)part << " failed. Http status: " << httpReq->httpstatus << " [this = " << this << "]"; mErrors++; if (mConsecutiveErrors > MAXRETRIES || httpReq->status == REQ_READY) { rr->setNewUnusedRaidConnection(part); closesocket(); for (uint8_t i = RAIDPARTS; i--;) { if (i != part && !rr->mFetcher[i].mConnected) { rr->mFetcher[i].trigger(); } } return -1; } else { mConsecutiveErrors++; for (uint8_t i = RAIDPARTS; i--;) { if (i != part && !rr->mFetcher[i].mConnected) { rr->mFetcher[i].trigger(); } } if (httpReq->status == REQ_FAILURE) // shouldn't be { httpReq->status = REQ_READY; } return trigger(backoff); } } else { httpReq->status = REQ_READY; resume(); } return -1; } m_off_t PartFetcher::getSocketSpeed() const { if (!mTimeInflight) { return 0; } // In Bytes per millisec return mReqBytesReceived / mTimeInflight; } bool PartFetcher::setsource(const std::string& partUrl, RaidReq* crr, uint8_t cpart) { mUrl = partUrl; part = cpart; rr = crr; mPartStartPos = rr->mReqStartPos / EFFECTIVE_RAIDPARTS; assert(mPartStartPos % RAIDSECTOR == 0); mSourcesize = static_cast(RaidReq::raidPartSize(part, rr->mFilesize)); LOG_debug << "[PartFetcher::setsource] part = " << (int)cpart << ", partStartPos = " << mPartStartPos << ", sourcesize = " << mSourcesize << " [this = " << this << "]"; return true; } // (re)create, set up socket and start (optionally delayed) io on it int64_t PartFetcher::trigger(raidTime delay, bool disconnect) { if (delay == MAX_DELAY_IN_SECONDS) { rr->mCloudRaid->setTransferFailure(); return -1; } assert(!mUrl.empty()); if (mFinished) { closesocket(); return -1; } if (disconnect) { if (rr->mHttpReqs[part]->status == REQ_SUCCESS) { mRem = 0; mRemfeed = 0; } else closesocket(true); } if (!mRem) { assert(mPos <= rr->mPaddedpartsize); if (mPos == rr->mPaddedpartsize) { closesocket(); return -1; } } directTrigger(!delay); if (delay) mDelayuntil = Waiter::ds + delay; return delay; } bool PartFetcher::directTrigger(bool addDirectio) { auto httpReq = rr->mHttpReqs[part]; assert(httpReq != nullptr); if (addDirectio && rr->mPool.addDirectio(httpReq)) { return true; } return !addDirectio; } // close socket void PartFetcher::closesocket(bool reuseSocket) { mRem = 0; mRemfeed = 0; // need to clear remfeed so that the disconnected channel does not corrupt feedlag mPostCompleted = false; if (mInbuf) mInbuf.reset(nullptr); auto& httpReq = rr->mHttpReqs[part]; if (httpReq) { if (mConnected) { if (!reuseSocket || httpReq->status == REQ_INFLIGHT) { rr->mCloudRaid->disconnect(httpReq); } httpReq->status = REQ_READY; } rr->mPool.removeio(httpReq); } mConnected = false; } // perform I/O on socket (which is assumed to exist) int64_t PartFetcher::io() { // prevent spurious epoll events from triggering a delayed reconnect early if (mFinished && rr->mCompleted < rr->mNumLines && (rr->mRem > ((rr->mCompleted * RAIDLINE) - rr->mSkip))) { rr->procreadahead(); } if ((Waiter::ds < mDelayuntil) || mFinished) return -1; auto httpReq = rr->mHttpReqs[part]; assert(httpReq != nullptr); if (httpReq->status == REQ_FAILURE) { return onFailure(); } else if (rr->allconnected(part)) { LOG_warn << "There are 6 connected channels, and there shouldn't be" << " [this = " << this << "]"; assert(false && "There shouldn't be 6 connected channels"); // we only need EFFECTIVE_RAIDPARTS connections, so shut down the slowest one closesocket(true); return -1; } else if (httpReq->status == REQ_INFLIGHT) { directTrigger(); return -1; } // unless the fetch position/length for the connection has been computed // before, we do so *after* the connection so that the order in which // the connections are established are the first criterion for slow source heuristics else if (!mRem && httpReq->status != REQ_SUCCESS) { setposrem(); if (httpReq->status == REQ_PREPARED) { httpReq->status = REQ_READY; } } if (httpReq->status == REQ_READY) { if (mRem <= 0) { closesocket(); rr->resumeall(part); return -1; } if (mPostCompleted && (rr->processFeedLag() == part)) { return -1; } if (mInbuf) { mInbuf.reset(nullptr); } m_off_t npos = mPos + mRem; assert(npos <= rr->mPaddedpartsize); LOG_verbose << "[PartFetcher::io] [part " << (int)part << "] prepareRequest -> pos = " << (mPos + mPartStartPos) << ", npos = " << (npos + mPartStartPos) << " [partStartPos = " << mPartStartPos << ", mRem = " << mRem << "]" << " [this = " << this << "]"; rr->mCloudRaid->prepareRequest(httpReq, mUrl, mPos + mPartStartPos, npos + mPartStartPos); assert(httpReq->status == REQ_PREPARED); mConnected = true; } if (mConnected) { if (httpReq->status == REQ_PREPARED) { bool postDone = rr->mCloudRaid->post(httpReq); if (postDone) { lastdata = Waiter::ds; mPostStartTime = std::chrono::system_clock::now(); } else { return onFailure(); } } if (httpReq->status == REQ_SUCCESS) { rr->mCloudRaid->processRequestLatency(httpReq); LOG_verbose << "[PartFetcher::io] [part " << (int)part << "] REQ_SUCCESS [pos = " << mPos << ", partStartPos = " << mPartStartPos << "] [httpReq->dlpos = " << httpReq->dlpos << ", httpReq->size = " << httpReq->size << ", inbuf = " << (mInbuf ? static_cast(mInbuf->datalen()) : -1) << "]" << " [this = " << this << "]"; assert(!mInbuf || httpReq->buffer_released); if (!mInbuf || !httpReq->buffer_released) { assert((mPos + mPartStartPos) == httpReq->dlpos); assert(mPostStartTime.time_since_epoch() != std::chrono::milliseconds(0)); const auto& postEndTime = std::chrono::system_clock::now(); auto reqTime = std::chrono::duration_cast(postEndTime - mPostStartTime).count(); mTimeInflight += reqTime; mInbuf.reset(httpReq->release_buf()); httpReq->buffer_released = true; mReqBytesReceived += mInbuf->datalen(); mPostCompleted = true; rr->mFeedlag[part] += static_cast(mInbuf->datalen() ? (((mInbuf->datalen() / static_cast(reqTime)) * 1000) / 1024) : 0); // KB/s rr->resumeall(part); } while (mRemfeed && mInbuf->datalen()) { size_t bufSize = mInbuf->datalen(); if (mRemfeed < static_cast(bufSize)) bufSize = static_cast(mRemfeed); mRemfeed -= bufSize; mRem -= bufSize; // completed a read: reset consecutive_errors if (!mRem && mConsecutiveErrors) mConsecutiveErrors = 0; rr->procdata(part, mInbuf->datastart(), mPos, static_cast(bufSize)); if (!mConnected) { break; } mPos += bufSize; mInbuf->start += bufSize; } lastdata = Waiter::ds; if (!mRemfeed && mPos == mSourcesize && mSourcesize < rr->mPaddedpartsize) { // we have reached the end of a part requires padding static byte nulpad[RAIDSECTOR]; rr->procdata(part, nulpad, mPos, rr->mPaddedpartsize - mSourcesize); mRem = 0; mPos = rr->mPaddedpartsize; } rr->procreadahead(); if (!mRem) { if (mPos == rr->mPaddedpartsize) { closesocket(); mFinished = true; return -1; } } else if (mInbuf->datalen() == 0) { mInbuf.reset(nullptr); httpReq->status = REQ_READY; } else { setremfeed(static_cast(mInbuf->datalen())); } } assert(httpReq->status == REQ_READY || httpReq->status == REQ_INFLIGHT || httpReq->status == REQ_SUCCESS || httpReq->status == REQ_FAILURE || httpReq->status == REQ_PREPARED); if (httpReq->status == REQ_FAILURE) { return onFailure(); } if (httpReq->status == REQ_INFLIGHT) { directTrigger(); } else { trigger(); } } return -1; } // request a further chunk of data from the open connection void PartFetcher::cont(m_off_t numbytes) { if (mConnected && mPos < rr->mPaddedpartsize) { assert(!mFinished); setremfeed(numbytes); trigger(); } } // feed suitable readahead data bool PartFetcher::feedreadahead() { if (mReadahead.empty()) return false; auto total = mReadahead.size(); auto remaining = total; while (remaining) { auto it = mReadahead.begin(); // make sure that we feed gaplessly if ((it->first < ((rr->mDataline + rr->mCompleted) * RAIDSECTOR))) { LOG_warn << "Gaps found on read ahead feed" << " [this = " << this << "]"; } assert(it->first >= ((rr->mDataline + rr->mCompleted) * RAIDSECTOR)); // we only take over from any source if we match the completed boundary precisely if (it->first == ((rr->mDataline + rr->mCompleted) * RAIDSECTOR)) rr->mPartpos[part] = it->first - (rr->mDataline * RAIDSECTOR); // always continue at any position on our own source if (it->first != ((rr->mDataline * RAIDSECTOR) + rr->mPartpos[part])) break; // we do not feed chunks that cannot even be processed in part (i.e. that start at or past the end of the buffer) if ((it->first - (rr->mDataline * RAIDSECTOR)) >= SECTORSPERPART(rr->mNumLines)) break; m_off_t p = it->first; byte* d = it->second.first; unsigned l = it->second.second; mReadahead.erase(it); rr->procdata(part, d, p, l); free(d); remaining--; } return total && total != remaining; } // resume fetching on a parked source that has become eligible again void PartFetcher::resume(bool forceSetPosRem) { bool resumeCondition = mFinished ? false : true; if (resumeCondition) { if (forceSetPosRem || ((!mConnected || !mRem) && (mPos < rr->mPaddedpartsize))) { setposrem(); } if (mRem || (mPos < rr->mPaddedpartsize) || rr->mHttpReqs[part]->status == REQ_SUCCESS) { trigger(); } } } m_off_t PartFetcher::progress() const { m_off_t progressCount = 0; m_off_t totalReadAhead = 0; for (auto& it : mReadahead) { totalReadAhead += static_cast(it.second.second); } auto& httpReq = rr->mHttpReqs[part]; assert(httpReq != nullptr); m_off_t reqsProgress; switch (httpReq->status.load()) { case REQ_INFLIGHT: { reqsProgress = rr->mCloudRaid->transferred(httpReq); break; } case REQ_SUCCESS: { if (mInbuf) { assert(httpReq->buffer_released); reqsProgress = static_cast(mInbuf->datalen()); } else { reqsProgress = httpReq->size; } break; } default: { reqsProgress = 0; break; } } progressCount = totalReadAhead + reqsProgress + rr->mPartpos[part]; // Consecutive part data for not completed raidlines (i.e., other parts still not finished) if (progressCount) { // Parts are padded to paddedpartsize, so we can only count as much as sourcesize (which is the real data size for that part). assert(part != rr->unusedPart()); // If there is progress, this part shouldn't be the unused one. auto partIndex = part == 0 ? rr->unusedPart() : part; // Parity must get the sourcesize of the unused part (for example, if part 5 is the unused one, it can be smaller than part 0, so we must adjust the progress) m_off_t paddedOffsetToSubtract = (mPos + reqsProgress) - rr->mFetcher[partIndex].mSourcesize; if (paddedOffsetToSubtract > 0) // The current position is above the source size, subtract it. { assert((mPos + reqsProgress) <= rr->mPaddedpartsize); progressCount -= paddedOffsetToSubtract; assert ((progressCount + (rr->mDataline * RAIDSECTOR)) == rr->mFetcher[partIndex].mSourcesize); } } assert(progressCount >= 0); return progressCount; } /* -------------- PartFetcher END --------------*/ /* -------------- RaidReq --------------*/ RaidReq::RaidReq(const Params& p, RaidReqPool& rrp, const std::shared_ptr& cloudRaid): mPool(rrp), mCloudRaid(cloudRaid), mRem(static_cast(p.reqlen)), mFilesize(p.filesize), mReqStartPos(p.reqStartPos), mPaddedpartsize((static_cast(raidPartSize(0, mFilesize)) + RAIDSECTOR - 1) & -RAIDSECTOR), lastdata(Waiter::ds) { assert(p.tempUrls.size() > 0); assert((mReqStartPos >= 0) && (mRem <= static_cast(mFilesize))); assert(mReqStartPos % RAIDSECTOR == 0); assert(mReqStartPos % RAIDLINE == 0); mHttpReqs.resize(p.tempUrls.size()); for(auto& httpReq : mHttpReqs) { httpReq = std::make_shared(); } calculateNumLinesAndBufferSizes(); mData.reset(new byte[mDataSize]); mParity.reset(new byte[mParitySize]); mInvalid.reset(new char[static_cast(mNumLines)]); std::fill(mData.get(), mData.get() + mDataSize, 0); std::fill(mParity.get(), mParity.get() + mParitySize, 0); std::fill(mInvalid.get(), mInvalid.get() + mNumLines, (1 << RAIDPARTS) - 1); mUnusedRaidConnection = mCloudRaid->getUnusedRaidConnection(); if (mUnusedRaidConnection == RAIDPARTS) { LOG_verbose << "[RaidReq::RaidReq] No previous unused raid connection: set initial unused raid connection to 0" << " [this = " << this << "]"; setNewUnusedRaidConnection(0, false); } LOG_verbose << "[RaidReq::RaidReq] filesize = " << mFilesize << ", paddedpartsize = " << mPaddedpartsize << ", unusedRaidConnection = " << (int)mUnusedRaidConnection << " [mNumLines = " << mNumLines << ", mDataSize = " << mDataSize << ", mParitySize = " << mParitySize << ", MAX_NUMLINES = " << MAX_NUMLINES << "] [this = " << this << "]"; for (uint8_t i = RAIDPARTS; i--; ) { if (!p.tempUrls[i].empty()) { // we don't trigger I/O on unknown source servers (which shouldn't exist in normal ops anyway) if (mFetcher[i].setsource(p.tempUrls[i], this, i)) { // this kicks off I/O on that source if (i != mUnusedRaidConnection) mFetcher[i].trigger(); } } else { mMissingSource = true; } } } RaidReq::~RaidReq() { LOG_verbose << "[RaidReq::~RaidReq] DESTRUCTOR [this = " << this << "]"; // Use feedlag to set next unused source uint8_t slowest, fastest; if (!mFaultysourceadded && getSlowestAndFastestParts(slowest, fastest, false /* no need for the parts to be connected, just have feedlag */) && differenceBetweenPartsSpeedIsSignificant(fastest, slowest)) { LOG_verbose << "[RaidReq::~RaidReq] Detected slowest part for this RaidReq: " << (int)slowest << ". There's no sources with errors reported, so we will use this one as the unused connection for next RaidReq" << " [this = " << this << "]"; mCloudRaid->setUnusedRaidConnection(slowest, true); } } void RaidReq::calculateNumLinesAndBufferSizes() { // Calculate the number of lines based on the file size and RAIDLINE mNumLines = std::min((mFilesize + RAIDLINE - 1) / RAIDLINE, MAX_NUMLINES); // Calculate the required size for data buffer (padded to RAIDLINE) mDataSize = static_cast(mNumLines * RAIDLINE); // Calculate the required size for parity buffer (padded to RAIDSECTOR) mParitySize = static_cast(SECTORSPERPART(mNumLines)); assert(mNumLines >= 1); assert(mDataSize >= RAIDLINE); assert(mParitySize >= RAIDSECTOR); } void RaidReq::shiftdata(m_off_t len) { mSkip += len; mRem -= len; if (mRem) { auto shiftby = mSkip / RAIDLINE; mCompleted -= shiftby; mSkip %= RAIDLINE; // we remove completed sectors/lines from the beginning of all state buffers m_off_t eobData = 0, eobAll = 0; for (uint8_t i = RAIDPARTS; i--; ) { if (i > 0 && mPartpos[i] > eobData) eobData = mPartpos[i]; if (mPartpos[i] > eobAll) eobAll = mPartpos[i]; } eobData = (eobData + RAIDSECTOR - 1) / RAIDSECTOR; eobAll = (eobAll + RAIDSECTOR - 1) / RAIDSECTOR; if (eobData > shiftby) { memmove(mData.get(), mData.get() + (shiftby * RAIDLINE), static_cast((eobData - shiftby) * RAIDLINE)); } if (eobAll > shiftby) { memmove(mInvalid.get(), mInvalid.get() + shiftby, static_cast(eobAll - shiftby)); std::fill(mInvalid.get() + eobAll - shiftby, mInvalid.get() + eobAll, (1 << RAIDPARTS) - 1); } else { std::fill(mInvalid.get(), mInvalid.get() + shiftby, (1 << RAIDPARTS) - 1); } mDataline += shiftby; shiftby *= RAIDSECTOR; if (mPartpos[0] > shiftby) { memmove(mParity.get(), mParity.get() + shiftby, static_cast(mPartpos[0] - shiftby)); } // shift mPartpos[] by the dataline increment and retrigger data flow for (uint8_t i = RAIDPARTS; i--; ) { mPartpos[i] -= shiftby; if (mPartpos[i] < 0) mPartpos[i] = 0; else { if (!mFetcher[i].mFinished) { // request 1 less RAIDSECTOR as we will add up to 1 sector of 0 bytes at the end of the file - this leaves enough buffer space in the buffer passed to procdata for us to write past the reported length mFetcher[i].cont(SECTORSPERPART(mNumLines) - mPartpos[i]); } } } mHaddata = true; lastdata = Waiter::ds; } } bool RaidReq::allconnected(uint8_t excludedPart) const { for (uint8_t i = RAIDPARTS; i--; ) { if (i != excludedPart && !mFetcher[i].mConnected) { return false; } } return true; } uint8_t RaidReq::numPartsUnfinished() const { uint8_t count = 0; for (uint8_t i = RAIDPARTS; i--;) { if (!mFetcher[i].mFinished) { count++; } } return count; } uint8_t RaidReq::hangingSources(uint8_t* hangingSource = nullptr, uint8_t* idleGoodSource = nullptr) { uint8_t numHangingSources = 0; if (hangingSource) *hangingSource = RAIDPARTS; if (idleGoodSource) *idleGoodSource = RAIDPARTS; for (uint8_t i = RAIDPARTS; i--; ) { if (mFetcher[i].mConnected) { if (mHttpReqs[i]->status == REQ_INFLIGHT) { mFetcher[i].lastdata = mHttpReqs[i]->lastdata; if (mFetcher[i].mRemfeed && ((Waiter::ds - mFetcher[i].lastdata) > PartFetcher::LASTDATA_DSTIME_FOR_HANGING_SOURCE)) { LOG_verbose << "[RaidReq::hangingSources] Part " << (int)i << " last data time reached time boundary: considering it as a hanging source [lastDataTime = " << ((Waiter::ds - mFetcher[i].lastdata)/10) << " secs, limit = " << (PartFetcher::LASTDATA_DSTIME_FOR_HANGING_SOURCE/10) << " secs] [this = " << this << "]"; numHangingSources++; if (hangingSource) *hangingSource = i; } } } else if (idleGoodSource && !mFetcher[i].mFinished && !mFetcher[i].mErrors) *idleGoodSource = i; } return numHangingSources; } // watchdog: resolve stuck connections void RaidReq::watchdog() { if (mMissingSource) return; // check for a single fast source hanging uint8_t hangingsource; uint8_t idlegoodsource; uint8_t hanging = hangingSources(&hangingsource, &idlegoodsource); if (hanging) { assert(hangingsource != RAIDPARTS); if (idlegoodsource == RAIDPARTS) { // Try a source with less errors: for (uint8_t i = RAIDPARTS; i--; ) { if (!mFetcher[i].mConnected && !mFetcher[i].mFinished && !mPool.lookupHttpReq(mHttpReqs[i]) && (mFetcher[i].mErrors <= MAX_ERRORS_FOR_IDLE_GOOD_SOURCE || (idlegoodsource != RAIDPARTS && mFetcher[i].mErrors < mFetcher[idlegoodsource].mErrors))) idlegoodsource = i; } if (mUnusedRaidConnection != idlegoodsource) { LOG_warn << "[RaidReq::watchdog] idlegoodsource (" << idlegoodsource << ") doesn't match with unused part detected (" << mUnusedRaidConnection << ")"; } } if (idlegoodsource != RAIDPARTS) { LOG_verbose << "Hanging source!! hangingsource = " << (int)hangingsource << " (HttpReq: " << (void*)mHttpReqs[hangingsource].get() << "), idlegoodsource = " << (int)idlegoodsource << " (HttpReq: " << (void*)mHttpReqs[idlegoodsource].get() << ") [mFetcher[hangingsource].lastdata = " << mFetcher[hangingsource].lastdata << ", Waiter::ds = " << Waiter::ds << "] [this = " << this << "]"; mFetcher[hangingsource].mErrors++; setNewUnusedRaidConnection(hangingsource); mFetcher[hangingsource].closesocket(); if (mFetcher[idlegoodsource].trigger() == -1) { mFetcher[hangingsource].trigger(); } return; } else { LOG_verbose << "Hanging source and no idle good source to switch!! hangingsource = " << (int)hangingsource << " (HttpReq: " << (void*)mHttpReqs[hangingsource].get() << ") [mFetcher[hangingsource].lastdata = " << mFetcher[hangingsource].lastdata << ", Waiter::ds = " << Waiter::ds << "] [this = " << this << "]"; } } } bool RaidReq::differenceBetweenPartsSpeedIsSignificant(uint8_t part1, uint8_t part2) const { return mFeedlag[part1] * 4 > mFeedlag[part2] * 5; } bool RaidReq::getSlowestAndFastestParts(uint8_t& slowest, uint8_t& fastest, bool mustBeConnected) const { slowest = 0; fastest = 0; uint8_t i = RAIDPARTS; while (i-- > 0 && ((mustBeConnected && !mFetcher[slowest].mConnected) || !mFeedlag[slowest])) { slowest++; fastest++; } if (slowest == RAIDPARTS) // Cannot compare yet { return false; } for (i = RAIDPARTS; --i; ) { if ((mFetcher[i].mConnected || !mustBeConnected) && mFeedlag[i]) { if (mFeedlag[i] < mFeedlag[slowest]) slowest = i; else if (mFeedlag[i] > mFeedlag[fastest]) fastest = i; } } return true; } // procdata() handles input in any order/size and will push excess data to readahead // data is assumed to be 0-padded to paddedpartsize at EOF void RaidReq::procdata(uint8_t part, byte* ptr, m_off_t pos, m_off_t len) { m_off_t basepos = mDataline * RAIDSECTOR; // we never read backwards assert((pos & -RAIDSECTOR) >= (basepos + (mPartpos[part] & -RAIDSECTOR))); bool consecutive = pos == basepos + mPartpos[part]; // is the data non-consecutive (i.e. a readahead), OR is it extending past the end of our buffer? if (!consecutive || ((pos + len) > (basepos + SECTORSPERPART(mNumLines)))) { auto ahead_ptr = ptr; auto ahead_pos = pos; auto ahead_len = len; // if this is a consecutive feed, we store the overflowing part as readahead data // and process the non-overflowing part normally if (consecutive) { ahead_pos = basepos + SECTORSPERPART(mNumLines); ahead_ptr = ptr + (ahead_pos - pos); ahead_len = len - (ahead_pos - pos); } // enqueue for future processing auto itReadAhead = mFetcher[part].mReadahead.find(ahead_pos); if (itReadAhead == mFetcher[part].mReadahead.end() || itReadAhead->second.second < ahead_len) { byte* p = itReadAhead != mFetcher[part].mReadahead.end() ? static_cast(std::realloc(itReadAhead->second.first, static_cast(ahead_len))) : static_cast(malloc(static_cast(ahead_len))); std::copy(ahead_ptr, ahead_ptr + ahead_len, p); mFetcher[part].mReadahead[ahead_pos] = pair(p, static_cast(ahead_len)); } // if this is a pure readahead, we're done if (!consecutive) return; len = ahead_pos - pos; } // non-readahead data must flow contiguously assert(pos == mPartpos[part] + (mDataline * RAIDSECTOR)); mPartpos[part] += len; auto t = pos - (mDataline * RAIDSECTOR); // ascertain absence of overflow (also covers parity part) assert((t + len) <= static_cast(mDataSize / EFFECTIVE_RAIDPARTS)); // set valid bit for every block that's been received in full char partmask = static_cast(1 << part); m_off_t until = (t + len) / RAIDSECTOR; // Number of sectors - corresponding to the number of lines for this part (for each part, N_RAIDSECTORS == N_RAIDLINES) for (m_off_t i = t / RAIDSECTOR; i < until; i++) { assert(mInvalid[static_cast(i)] & partmask); mInvalid[static_cast(i)] -= partmask; } // copy (partial) blocks to data or parity buf if (part) { part--; byte* ptr2 = ptr; m_off_t len2 = len; byte* target = mData.get() + part * RAIDSECTOR + (t / RAIDSECTOR) * RAIDLINE; auto partialSector = t % RAIDSECTOR; if (partialSector != 0) { target += partialSector; auto sectorBytes = std::min(len2, RAIDSECTOR - partialSector); std::copy(ptr2, ptr2 + sectorBytes, target); target += sectorBytes + RAIDSECTOR * (RAIDPARTS - 2); len2 -= sectorBytes; ptr2 += sectorBytes; } while (len2 >= RAIDSECTOR) { *reinterpret_cast(target) = *reinterpret_cast(ptr2); target += RAIDSECTOR * EFFECTIVE_RAIDPARTS; ptr2 += RAIDSECTOR; len2 -= RAIDSECTOR; } partialSector = len2; if (partialSector != 0) { std::copy(ptr2, ptr2 + partialSector, target); } } else { // store parity data for subsequent merging std::copy(ptr, ptr + len, mParity.get() + t); } // merge new consecutive completed RAID lines so they are ready to be sent, direct from the data[] array auto old_completed = mCompleted; for (; mCompleted < until; mCompleted++) { unsigned char mask = static_cast(mInvalid[static_cast(mCompleted)]); std::bitset bits(mask); auto bitsSet = bits.count(); assert(bitsSet); if (bitsSet > 1) { break; } else { if (!(mask & 1)) { // parity involved in this line int index = -1; #ifdef _MSC_VER unsigned long bitIndex; if (_BitScanForward(&bitIndex, mask)) { index = static_cast(bitIndex); } #else // __GNUC__ is defined for both GCC and Clang #if defined(__GNUC__) index = __builtin_ctz(mask); // counts least significant consecutive 0 bits (ie 0-based index of least significant 1 bit). Windows equivalent is _bitScanForward #else // Fallback to a loop for other compilers for (uint8_t i = 0; i < RAIDLINE; ++i) { if (mask & (1 << i)) { index = i; break; } } #endif #endif if (index != -1) // index > 0 && index < RAIDLINE { auto sectors = reinterpret_cast(mData.get() + (RAIDLINE * mCompleted)); raidsector_t& target = sectors[index - 1]; target = reinterpret_cast(mParity.get())[mCompleted]; if (!(mask & (1 << 1))) target ^= sectors[0]; // this method requires source and target are both aligned to their size if (!(mask & (1 << 2))) target ^= sectors[1]; if (!(mask & (1 << 3))) target ^= sectors[2]; if (!(mask & (1 << 4))) target ^= sectors[3]; if (!(mask & (1 << 5))) target ^= sectors[4]; } } } } if (mCompleted > old_completed) { lastdata = Waiter::ds; } } m_off_t RaidReq::readdata(byte* buf, m_off_t len) { watchdog(); m_off_t lenCompleted = 0; m_off_t old_completed, new_completed; m_off_t t; do { if (mCompleted < mNumLines) { old_completed = mCompleted; procreadahead(); } else { old_completed = 0; } new_completed = mCompleted; t = (mCompleted * RAIDLINE) - mSkip; if (t > 0) { if ((t + lenCompleted) > len) t = len - lenCompleted; memmove(buf + lenCompleted, mData.get() + mSkip, static_cast(t)); lenCompleted += t; shiftdata(t); } else { auto lastDataOffset = Waiter::ds - lastdata; if (lastDataOffset > LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK) { bool feedStuck = true; uint8_t hanging = hangingSources(); feedStuck = hanging ? true : (lastDataOffset > LASTDATA_DSTIME_FOR_REPORTING_FEED_STUCK_WITH_NO_HANGING_SOURCES); if (feedStuck) { const auto lastDataDsTimeForTimeout = hanging ? LASTDATA_DSTIME_FOR_TIMEOUT : LASTDATA_DSTIME_FOR_TIMEOUT_WITH_NO_HANGING_SOURCES; if (lastDataOffset > lastDataDsTimeForTimeout) { LOG_warn << "CloudRAID feed timed out [lastDataTime = " << (lastDataOffset/10) << " secs, haddata = " << mHaddata << "] [hangingSources = " << (int)hanging << "] [this = " << this << "]"; return -1; } if (!mReported) { mReported = true; LOG_warn << "CloudRAID feed stuck [lastDataTime = " << (lastDataOffset/10) << " secs, haddata = " << mHaddata << "] [hangingSources = " << (int)hanging << "] [this = " << this << "]"; } } } } } while (new_completed > old_completed && t > 0 && lenCompleted < len); if (lenCompleted) { resumeall(); } return lenCompleted; } // try to resume fetching on all sources void RaidReq::resumeall(uint8_t excludedPart) { if (mRem) { for (uint8_t i = RAIDPARTS; i--; ) { if (i != excludedPart) { if (mFetcher[i].mFinished) { mFetcher[i].directTrigger(); } else if (mFetcher[i].mConnected) { mFetcher[i].resume(); } } } } } void RaidReq::dispatchio(const HttpReqPtr& httpReq) { // fast lookup of which PartFetcher to call from a single cache line // we don't check for httpReq not being found since we know sometimes it won't be when we closed a socket to a slower/nonresponding server for (uint8_t i = RAIDPARTS; i--; ) { if (mHttpReqs[i] == httpReq) { int64_t t = mFetcher[i].io(); if (t > 0) { // this is a relatively infrequent ocurrence, so we tolerate the overhead of a std::set insertion/erasure mPool.addScheduledio(Waiter::ds + t, mHttpReqs[i]); } break; } } } // feed relevant read-ahead data to procdata void RaidReq::procreadahead() { bool fed; do { fed = false; for (uint8_t i = RAIDPARTS; i--; ) { if (mFetcher[i].feedreadahead()) fed = true; } } while (fed); } void RaidReq::disconnect() { for (auto& httpReq : mHttpReqs) { httpReq->disconnect(); } } uint8_t RaidReq::processFeedLag() { uint8_t laggedPart = RAIDPARTS; if (++mLagrounds >= numPartsUnfinished()) { // dominance is defined as the ratio between fastest and slowest uint8_t slowest, fastest; if (!getSlowestAndFastestParts(slowest, fastest)) // Cannot compare yet { return RAIDPARTS; } if (!mMissingSource && mFetcher[slowest].mConnected && !mFetcher[slowest].mFinished && mHttpReqs[slowest]->status != REQ_SUCCESS && ((mFetcher[slowest].mRem - mFetcher[fastest].mRem) > ((SECTORSPERPART(mNumLines) * LAGINTERVAL * 3) / 4) || differenceBetweenPartsSpeedIsSignificant(fastest, slowest))) { // slow channel detected { LOG_debug << "Slow channel " << (int)slowest << " (" << (void*)mHttpReqs[slowest].get() << ")" << " detected!" << " [this = " << this << "]"; mFetcher[slowest].mErrors++; // check if we have a fresh and idle channel left to try uint8_t fresh = 0; while (fresh <= EFFECTIVE_RAIDPARTS && (mFetcher[fresh].mConnected || mFetcher[fresh].mErrors || mFetcher[fresh].mFinished)) { fresh++; } if (fresh <= EFFECTIVE_RAIDPARTS) { LOG_verbose << "New fresh channel: " << (int)fresh << " (" << (void*)mHttpReqs[fresh].get() << ")" << " [this = " << this << "]"; setNewUnusedRaidConnection(slowest); mFetcher[slowest].closesocket(); mFetcher[fresh].resume(true); laggedPart = slowest; } else { LOG_verbose << "No fresh channel to switch with the slow channel" << " [this = " << this << "]"; } } } if (laggedPart != RAIDPARTS) { std::fill(mFeedlag.begin(), mFeedlag.end(), 0); } mLagrounds = 0; } return laggedPart; } m_off_t RaidReq::progress() const { m_off_t progressCount = 0; for (uint8_t i = RAIDPARTS; i--; ) { m_off_t partProgress = mFetcher[i].progress(); progressCount += partProgress; } assert((mCompleted * RAIDLINE) - mSkip >= 0); progressCount += ((mCompleted * RAIDLINE) - mSkip); assert(progressCount >= 0); assert(progressCount <= static_cast(mFilesize)); return progressCount; } uint8_t RaidReq::unusedPart() const { uint8_t partIndex = RAIDPARTS; for (uint8_t i = RAIDPARTS; i--;) { if (!mFetcher[i].mConnected && !mFetcher[i].mFinished && !mPool.lookupHttpReq(mHttpReqs[i])) { if (partIndex != RAIDPARTS) { LOG_warn << "[RaidReq::unusedPart] More than one unused part detected!!! [unusedPart = " << (int)partIndex << ", otherUnusedPart = " << (int)i << "]" << " [this = " << this << "]"; assert(false && "More than one unused part detected!!!!!"); } partIndex = i; } } if (partIndex != mUnusedRaidConnection) { LOG_warn << "[RaidReq::unusedPart] Unused part (" << (int)partIndex << ") does not match with unused raid connection (" << (int)mUnusedRaidConnection << ") !!!!" << " [this = " << this << "]"; assert(false && "Unused part does not match with unused raid connection"); } assert(partIndex != RAIDPARTS); return partIndex; } std::pair<::mega::error, raidTime> RaidReq::checkTransferFailure() { return mCloudRaid->checkTransferFailure(); } bool RaidReq::setNewUnusedRaidConnection(uint8_t part, bool addToFaultyServers) { if (!mCloudRaid->setUnusedRaidConnection(part, addToFaultyServers)) { LOG_warn << "[RaidReq::setNewUnusedRaidConnection] Could not set unused raid connection, setting it to 0" << " [this = " << this << "]"; assert(false && "Unused raid connection couldn't be set"); mUnusedRaidConnection = 0; return false; } LOG_verbose << "[RaidReq::setNewUnusedRaidConnection] Set unused raid connection to " << (int)part << " (clear previous unused connection: " << (int)mUnusedRaidConnection << ") [addToFaultyServers = " << addToFaultyServers << "]" << " [this = " << this << "]"; mUnusedRaidConnection = part; if (addToFaultyServers) mFaultysourceadded = true; return true; } void RaidReq::processRequestLatency(const HttpReqPtr& httpReq) { if (httpReq) { mCloudRaid->processRequestLatency(httpReq); } } size_t RaidReq::raidPartSize(uint8_t part, size_t fullfilesize) { // compute the size of this raid part based on the original file size len m_off_t r = static_cast(fullfilesize) % RAIDLINE; // residual part // parts 0 (parity) & 1 (largest data) are the same size m_off_t t = r - ((part - !!part) * RAIDSECTOR); // (excess length will be found in the following sectors, // negative length in the preceding sectors) if (t < 0) t = 0; else if (t > RAIDSECTOR) t = RAIDSECTOR; LOG_debug << "[RaidReq::raidPartSize] return (fullfilesize - r) / EFFECTIVE_RAIDPARTS + t = (" << fullfilesize << " - " << r << ") / (" << EFFECTIVE_RAIDPARTS << ") + " << t << " = " << ((static_cast(fullfilesize) - r) / EFFECTIVE_RAIDPARTS) << " + " << t << " = " << ((static_cast(fullfilesize) - r) / EFFECTIVE_RAIDPARTS + t); return static_cast((static_cast(fullfilesize) - r) / EFFECTIVE_RAIDPARTS + t); } /* -------------- RaidReq END --------------*/ /* -------------- RaidReqPool --------------*/ bool RaidReqPool::addScheduledio(raidTime scheduledFor, const HttpReqPtr& req) { auto it = mSetHttpReqs.insert(req); if (it.second) { auto itScheduled = mScheduledio.insert(std::make_pair(scheduledFor, req)); return itScheduled.second; } return false; } bool RaidReqPool::addDirectio(const HttpReqPtr& req) { return addScheduledio(0, req); } bool RaidReqPool::removeio(const HttpReqPtr& req) { return mSetHttpReqs.erase(req) > 0; } void RaidReqPool::raidproxyio() { if (mIsRunning) { if (!mScheduledio.empty()) { auto itScheduled = mScheduledio.begin(); bool transferFailed = false; while (mIsRunning && !transferFailed && (itScheduled != mScheduledio.end() && (itScheduled->first <= Waiter::ds))) { const HttpReqPtr& httpReq = itScheduled->second; if (httpReq->status != REQ_INFLIGHT) { if ((mSetHttpReqs.find(httpReq) != mSetHttpReqs.end())) { mRaidReq->dispatchio(httpReq); itScheduled++; } else { itScheduled = mScheduledio.erase(itScheduled); } } else { mRaidReq->processRequestLatency(httpReq); itScheduled++; } if (mRaidReq->checkTransferFailure().first) { LOG_debug << "[RaidReqPool::raidproxyio] Found transfer failed flag. Stop" << " [this = " << this << "]"; transferFailed = true; } } } } } RaidReqPool::RaidReqPool() { LOG_verbose << "[RaidReqPool::RaidReqPool] CONSTRUCTOR CALL [this = " << this << "]"; } RaidReqPool::~RaidReqPool() { LOG_verbose << "[RaidReqPool::~RaidReqPool] DESTRUCTOR CALL [this = " << this << "]"; mIsRunning = false; mRaidReq.reset(); } void RaidReqPool::request(const RaidReq::Params& p, const std::shared_ptr& cloudRaid) { RaidReq* rr = new RaidReq(p, *this, cloudRaid); mRaidReq.reset(rr); } /* -------------- RaidReqPool END --------------*/ sdk-10.11.0/src/recent_actions.cpp000066400000000000000000000346471516266226600167600ustar00rootroot00000000000000/** * @file recent_actions.cpp * @brief Business logic for getRecentActions and getRecentActionById * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/recent_actions.h" #include "mega.h" #include "mega/megaclient.h" #include #include namespace mega { namespace { constexpr m_time_t kSecondsPerHour = 3600; constexpr m_time_t kSecondsPerDay = 24 * kSecondsPerHour; constexpr int kWindowSizeHours = 6; enum class BucketIdIndex { DayStart = 0, WindowStart = 1, WindowEnd = 2, User = 3, Parent = 4, Media = 5, Updated = 6, ExcludeSensitives = 7 }; // Matches MegaApi::ORDER_CREATION_DESC. Defined locally to avoid depending on megaapi.h. constexpr int kOrderCreationDesc = 6; static bool nodes_ctime_greater(const Node* a, const Node* b) { return a->ctime > b->ctime; } static bool nodes_ctime_greater_shared_ptr(const std::shared_ptr& a, const std::shared_ptr& b) { return nodes_ctime_greater(a.get(), b.get()); } namespace action_bucket_compare { static bool comparetime(const recentaction& a, const recentaction& b) { return a.time > b.time; } } // end namespace action_bucket_compare static m_time_t recentActionDayStartUtc(m_time_t ts) { return (ts / kSecondsPerDay) * kSecondsPerDay; } static int recentActionWindowStartHourUtc(m_time_t ts, int windowSizeHours = kWindowSizeHours) { if (windowSizeHours <= 0) { return 0; } const m_time_t secondsSinceDayStart = ts - recentActionDayStartUtc(ts); const int hourOfDay = static_cast(secondsSinceDayStart / kSecondsPerHour); return (hourOfDay / windowSizeHours) * windowSizeHours; } static std::string buildRecentActionId(m_time_t ctime, handle userHandle, handle parent, bool media, bool update, bool excludeSensitives) { const m_time_t dayStart = recentActionDayStartUtc(ctime); const int startWindow = recentActionWindowStartHourUtc(ctime); const int endWindow = startWindow + kWindowSizeHours; std::string parentStr = "UNDEF"; if (!ISUNDEF(parent)) { parentStr = Base64Str(parent); } std::string userHandleStr = "UNDEF"; if (!ISUNDEF(userHandle)) { userHandleStr = Base64Str(userHandle); } std::string id; id.reserve(64); id.append(std::to_string(dayStart)); id.append("|"); id.append(std::to_string(startWindow)); id.append("|"); id.append(std::to_string(endWindow)); id.append("|"); id.append(userHandleStr); id.append("|"); id.append(parentStr); id.append("|"); id.append(media ? "1" : "0"); id.append("|"); id.append(update ? "1" : "0"); id.append("|"); id.append(excludeSensitives ? "1" : "0"); return id; } static bool parseRecentActionBool(const std::string& token, bool& value) { if (token == "1") { value = true; return true; } if (token == "0") { value = false; return true; } return false; } static bool parseRecentActionWindow(const std::string& token, int& window, int windowSizeHours = kWindowSizeHours) { if (token.empty() || token.size() > 2) { return false; } if (token[0] == '-') { return false; } int temp = 0; try { temp = std::stoi(token); } catch (...) { return false; } if (temp < 0 || temp > 24 || (windowSizeHours > 0 && temp % windowSizeHours != 0)) { return false; } window = temp; return true; } static bool parseRecentActionDate(const std::string& token, m_time_t& dayStart) { if (token.empty() || token.size() > 10) { return false; } for (const char c: token) { if (c < '0' || c > '9') { return false; } } try { dayStart = static_cast(std::stoll(token)); } catch (...) { return false; } return dayStart > 0; } static bool parseRecentActionParentHandle(const std::string& token, handle& parent) { if (token == "UNDEF") { return false; } handle parsedParent = 0; const int decoded = Base64::atob(token.c_str(), reinterpret_cast(&parsedParent), MegaClient::NODEHANDLE); if (decoded != MegaClient::NODEHANDLE || ISUNDEF(parsedParent)) { return false; } parent = parsedParent; return true; } static bool parseRecentActionUserHandle(const std::string& token, handle& userHandle) { if (token == "UNDEF") { return false; } handle parsedUserHandle = 0; const int decoded = Base64::atob(token.c_str(), reinterpret_cast(&parsedUserHandle), MegaClient::USERHANDLE); if (decoded != MegaClient::USERHANDLE || ISUNDEF(parsedUserHandle)) { return false; } userHandle = parsedUserHandle; return true; } static bool parseRecentActionId(const std::string& id, m_time_t& dayStart, int& windowStart, int& windowEnd, RecentActionBucketMeta& meta, bool& excludeSensitives) { dayStart = 0; meta = RecentActionBucketMeta{}; std::vector tokens = splitString>(id, '|'); if (tokens.size() != 8) { return false; } if (!parseRecentActionDate(tokens[static_cast(BucketIdIndex::DayStart)], dayStart)) { return false; } if (!parseRecentActionWindow(tokens[static_cast(BucketIdIndex::WindowStart)], windowStart)) { return false; } if (!parseRecentActionWindow(tokens[static_cast(BucketIdIndex::WindowEnd)], windowEnd)) { return false; } if (windowEnd <= windowStart || windowEnd > windowStart + kWindowSizeHours) { return false; } if (!parseRecentActionUserHandle(tokens[static_cast(BucketIdIndex::User)], meta.user)) { return false; } if (!parseRecentActionParentHandle(tokens[static_cast(BucketIdIndex::Parent)], meta.parent)) { return false; } if (!parseRecentActionBool(tokens[static_cast(BucketIdIndex::Media)], meta.media)) { return false; } if (!parseRecentActionBool(tokens[static_cast(BucketIdIndex::Updated)], meta.updated)) { return false; } if (!parseRecentActionBool(tokens[static_cast(BucketIdIndex::ExcludeSensitives)], excludeSensitives)) { return false; } return true; } } // anonymous namespace RecentActions::RecentActions(MegaClient* client): mClient(client) {} recentactions_vector RecentActions::getRecentActions(unsigned maxcount, m_time_t since, bool excludeSensitives) const { sharedNode_vector v = mClient->mNodeManager.getRecentNodes(maxcount, since, excludeSensitives); return buildFromNodes(std::move(v), excludeSensitives); } recentactions_vector RecentActions::buildFromNodes(sharedNode_vector&& v, bool excludeSensitives) const { recentactions_vector rav; std::unordered_map bucketIndex; for (const auto& node: v) { if (!node) { continue; } const handle parentHandle = node->parent ? node->parent->nodehandle : UNDEF; const bool updated = mClient->getNumberOfChildren(node->nodeHandle()) > 0; const bool media = mClient->nodeIsMedia(node.get(), nullptr, nullptr); const std::string key = buildRecentActionId(node->ctime, node->owner, parentHandle, media, updated, excludeSensitives); auto it = bucketIndex.find(key); if (it == bucketIndex.end()) { const User* user = mClient->finduser(node->owner, 0); const std::string email = user ? user->email : std::string(); recentaction ra; ra.time = node->ctime; ra.meta.user = node->owner; ra.meta.userEmail = email; ra.meta.parent = parentHandle; ra.meta.updated = updated; ra.meta.media = media; ra.id = key; rav.push_back(std::move(ra)); it = bucketIndex.emplace(std::move(key), rav.size() - 1).first; } rav[it->second].nodes.push_back(node); } // sort nodes inside each bucket for (recentactions_vector::iterator i = rav.begin(); i != rav.end(); ++i) { // for the bucket vector, most recent (larger ctime) first sortBucketNodes(i->nodes); i->time = i->nodes.front()->ctime; } // sort buckets in the vector std::sort(rav.begin(), rav.end(), action_bucket_compare::comparetime); return rav; } sharedNode_vector RecentActions::getBucketCandidates(m_time_t startTime, m_time_t endTime, handle parent, bool isMedia, bool excludeSensitives) const { NodeSearchFilter filter; filter.byNodeType(FILENODE); filter.byCreationTimeLowerLimitInSecs(startTime - 1); filter.byCreationTimeUpperLimitInSecs(endTime); if (isMedia) { filter.byCategory(MIME_TYPE_ALL_VISUAL_MEDIA); } filter.byLocationHandle(parent); filter.bySensitivity(excludeSensitives ? NodeSearchFilter::BoolFilter::onlyTrue : NodeSearchFilter::BoolFilter::disabled); return mClient->mNodeManager.getChildren(filter, kOrderCreationDesc, CancelToken(), NodeSearchPage{0, 0}); } void RecentActions::sortBucketNodes(sharedNode_vector& nodes) const { std::sort(nodes.begin(), nodes.end(), nodes_ctime_greater_shared_ptr); } sharedNode_vector RecentActions::filterCandidatesByMeta(const sharedNode_vector& candidates, const RecentActionBucketMeta& meta, m_time_t startTime, m_time_t endTime) const { sharedNode_vector bucketNodes; for (const auto& node: candidates) { if (!node) { continue; } if (node->owner != meta.user) { continue; } // byLocationHandle already restricts the DB query to meta.parent; this // guards against any in-memory inconsistency. assert(!node->parent || node->parent->nodehandle == meta.parent); if (node->ctime < startTime || node->ctime >= endTime) { continue; } const bool updated = mClient->getNumberOfChildren(node->nodeHandle()) > 0; if (updated != meta.updated) { continue; } const bool media = mClient->nodeIsMedia(node.get(), nullptr, nullptr); if (media != meta.media) { continue; } bucketNodes.push_back(node); } return bucketNodes; } error RecentActions::getById(const char* id, recentaction& ra) const { return getById(id, std::nullopt, ra); } error RecentActions::getById(const char* id, bool excludeSensitives, recentaction& ra) const { return getById(id, std::make_optional(excludeSensitives), ra); } error RecentActions::getById(const char* id, std::optional excludeSensitives, recentaction& ra) const { if (!id || !*id) { return API_EARGS; } m_time_t dayStart = 0; int windowStart = 0; int windowEnd = 0; bool parsedExcludeSensitives = false; RecentActionBucketMeta meta; if (!parseRecentActionId(id, dayStart, windowStart, windowEnd, meta, parsedExcludeSensitives)) { return API_EARGS; } const bool effectiveExcludeSensitives = excludeSensitives.value_or(parsedExcludeSensitives); const m_time_t startTime = dayStart + windowStart * kSecondsPerHour; const m_time_t endTime = dayStart + windowEnd * kSecondsPerHour; sharedNode_vector candidates = getBucketCandidates(startTime, endTime, meta.parent, meta.media, effectiveExcludeSensitives); sharedNode_vector bucketNodes = filterCandidatesByMeta(candidates, meta, startTime, endTime); if (bucketNodes.empty()) { return API_ENOENT; } sortBucketNodes(bucketNodes); User* user = mClient->finduser(meta.user, 0); // to ensure that user email is available in cache for the result meta.userEmail = user ? user->email : std::string(); ra.time = bucketNodes.front()->ctime; ra.meta = std::move(meta); ra.id = id; ra.nodes = std::move(bucketNodes); return API_OK; } } // namespace mega sdk-10.11.0/src/request.cpp000066400000000000000000000423031516266226600154340ustar00rootroot00000000000000/** * @file request.cpp * @brief Generic request interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/request.h" #include "mega/command.h" #include "mega/common/badge.h" #include "mega/logging.h" #include "mega/megaclient.h" namespace mega { // Convenience. using common::Badge; bool Request::isFetchNodes() const { return cmds.size() == 1 && dynamic_cast(cmds.back().get()); } void Request::add(Command* c) { // Once this becomes the in-progress request, it must not have anything added assert(cachedJSON.empty()); cmds.push_back(unique_ptr(c)); } size_t Request::size() const { return cmds.size(); } string Request::get(MegaClient* client, char reqidCounter[10], string& idempotenceId) const { if (cachedJSON.empty()) { // concatenate all command objects, resulting in an API request string& req = cachedJSON; req = "["; map counts; for (int i = 0; i < (int)cmds.size(); i++) { req.append(i ? ",{" : "{"); req.append(cmds[static_cast(i)]->getJSON(client)); req.append("}"); ++counts[cmds[static_cast(i)]->commandStr]; } req.append("]"); for (auto& e : counts) { if (!cachedCounts.empty()) cachedCounts += " "; cachedCounts += e.first + ":" + std::to_string(e.second); } // increment unique request ID for (int i = 10; i--; ) { if (reqidCounter[i]++ < 'z') { break; } else { reqidCounter[i] = 'a'; } } cachedIdempotenceId = string(reqidCounter, 10); } // once we send the commands, any retry must be for exactly // the same JSON, or idempotence will not work properly LOG_debug << "Req command counts: " << cachedCounts; idempotenceId = cachedIdempotenceId; return cachedJSON; } bool Request::processCmdJSON(Command* cmd, bool couldBeError, JSON& jsonResponse) { Error e; if (couldBeError && cmd->checkError(e, jsonResponse)) { return cmd->procresult(Command::Result(Command::CmdError, e), jsonResponse); } else if (jsonResponse.enterobject()) { return cmd->procresult(Command::CmdObject, jsonResponse) && jsonResponse.leaveobject(); } else if (jsonResponse.enterarray()) { return cmd->procresult(Command::CmdArray, jsonResponse) && jsonResponse.leavearray(); } else { return cmd->procresult(Command::CmdItem, jsonResponse); } } bool Request::processSeqTag(Command* cmd, bool withJSON, bool& parsedOk, bool inSeqTagArray, JSON& processingJson) { string st; processingJson.storeobject(&st); if (inSeqTagArray) { if (*processingJson.pos == ',') ++processingJson.pos; } // In normal operation, we get called once to figure out the `st` to watch out for in sc json (else case below) // then the sc processing will process actionpackets up to and including the one(s) with that `st` // and only after that, call us a second time (with mCurrentSeqtagSeen true), and we will execute the if block. // And one additional case to consider, before we start the sc channel, it's ok to send non-actionpacket (ie, non-st) requests (hence scsn check) if (cmd->client->mCurrentSeqtag == st || !cmd->client->scsn.ready()) // if we have not started the sc channel, we won't receive the matching st, so ignore st until we do { if (!cmd->client->scsn.ready()) { LOG_verbose << "Processing command result regardless of st as we don't have scsn yet. " << st << cmd->client->mCurrentSeqtag; } assert(cmd->client->mCurrentSeqtagSeen || !cmd->client->scsn.ready()); cmd->client->mCurrentSeqtag.clear(); cmd->client->mCurrentSeqtagSeen = false; parsedOk = withJSON ? processCmdJSON(cmd, false, processingJson) : cmd->procresult(Command::Result(Command::CmdError, API_OK), processingJson); // just an `st` returned is implicitly successful return true; } else { // result processing paused until we encounter and process actionpackets matching client->mCurrentSeqtag cmd->client->mPriorSeqTag = cmd->client->mCurrentSeqtag; cmd->client->mCurrentSeqtag = st; cmd->client->mCurrentSeqtagSeen = false; cmd->client->mCurrentSeqtagCmdtag = cmd->tag; assert(cmd->client->mPriorSeqTag.size() < cmd->client->mCurrentSeqtag.size() || (cmd->client->mPriorSeqTag.size() == cmd->client->mCurrentSeqtag.size() && cmd->client->mPriorSeqTag < cmd->client->mCurrentSeqtag)); return false; } } m_off_t Request::processChunk(const char* chunk, MegaClient *client) { if (stopProcessing || cmds.size() != 1) { clear(); return 0; } // Only fetchnodes command is currently supported assert(isFetchNodes()); m_off_t consumed = 0; Command& cmd = *cmds[0]; client->restag = cmd.tag; cmd.client = client; bool start = !json.pos; json.begin(chunk); if (start) { if (!json.enterarray()) { // Request-level error clear(); return 0; } consumed++; assert(mJsonSplitter.isStarting()); } consumed += mJsonSplitter.processChunk(&cmd.mFilters, json.pos); if (mJsonSplitter.hasFailed()) { // stop the processing cmds[0].reset(); clear(); return 0; } mChunkedProgress += static_cast(consumed); json.begin(chunk + consumed); if (mJsonSplitter.hasFinished()) { if (!json.leavearray()) { LOG_err << "Unexpected end of JSON stream: " << json.pos; assert(false); } else { consumed++; } assert(!chunk[consumed]); cmds[0].reset(); clear(); } return consumed; } m_off_t Request::totalChunkedProgress() { return static_cast(mChunkedProgress); } void Request::process(MegaClient* client) { TransferDbCommitter committer(client->tctable); client->mTctableRequestCommitter = &committer; JSON processingJson = json; for (; processindex < cmds.size() && !stopProcessing; processindex++) { Command* cmd = cmds[processindex].get(); client->restag = cmd->tag; cmd->client = client; auto cmdJSON = processingJson; bool parsedOk = true; if (*processingJson.pos == ',') ++processingJson.pos; if (cmd->mSeqtagArray && processingJson.enterarray()) { // Responses parsed here: // -> ["", ] : Sequence Tag + a json // -> [0, "string"] : API_OK + a no-json-string // Commands should be marked as mSeqtagArray=true to be differentiated from a json array // like ["ES","FR","IE","GB"] If a command returning a string fails, there is no array, // just the error code // If the command failed, there is no array, just the error code assert(*processingJson.pos == '0' || *processingJson.pos == '\"'); if (*processingJson.pos == '0' && *(processingJson.pos+1) == ',') { processingJson.pos += 2; parsedOk = processCmdJSON(cmd, false, processingJson); } else if (!processSeqTag(cmd, true, parsedOk, true, processingJson)) // executes the command's procresult if we match the seqtag { // we need to wait for sc processing to catch up with the seqtag we just read json = cmdJSON; return; } if (parsedOk && !processingJson.leavearray()) { LOG_err << "Invalid seqtag array"; parsedOk = false; } } else if (*processingJson.pos == '"') { // Responses parsed here: // -> "" : Only a Sequence Tag. if (!processSeqTag(cmd, false, parsedOk, false, processingJson)) { // we need to wait for sc processing to catch up with the seqtag we just read json = cmdJSON; return; } } else { // Responses parsed here: // -> 0 or -N : Error codes, plain numeric values. // -> {..} or [..,..,] : Json response, could be an object or an array. parsedOk = processCmdJSON(cmd, true, processingJson); } if (!parsedOk) { LOG_err << "JSON for that command was not recognised/consumed properly, adjusting"; processingJson = cmdJSON; processingJson.storeobject(); // alert devs to the JSON problem (bad JSON from server, or bad parsing of it) immediately assert(false); } else { #ifdef DEBUG // double check the command consumed the right amount of JSON cmdJSON.storeobject(); if (processingJson.pos != cmdJSON.pos) { assert(processingJson.pos == cmdJSON.pos); } #endif } // delete the command as soon as it's not needed anymore cmds[processindex].reset(); } json = processingJson; if (processindex == cmds.size() || stopProcessing) { clear(); } client->mTctableRequestCommitter = nullptr; } Command* Request::getCurrentCommand() { assert(processindex < cmds.size()); return cmds[processindex].get(); } void Request::serverresponse(std::string&& movestring, MegaClient*) { assert(processindex == 0); jsonresponse = std::move(movestring); json.begin(jsonresponse.c_str()); if (!json.enterarray()) { LOG_err << "Invalid response from server"; } } void Request::servererror(const std::string& e, MegaClient* client) { ostringstream s; s << "["; for (size_t i = cmds.size(); i--; ) { s << e << (i ? "," : ""); } s << "]"; serverresponse(s.str(), client); } void Request::clear() { cmds.clear(); jsonresponse.clear(); json.pos = NULL; processindex = 0; mJsonSplitter.clear(); mChunkedProgress = 0; stopProcessing = false; } bool Request::empty() const { return cmds.empty(); } void Request::swap(Request& r) { // we use swap to move between queues, but process only after it gets into the completedreqs cmds.swap(r.cmds); std::swap(cachedJSON, r.cachedJSON); std::swap(cachedIdempotenceId, r.cachedIdempotenceId); std::swap(cachedCounts, r.cachedCounts); // Although swap would usually swap all fields, these must be empty anyway // If swap was used when these were active, we would be moving needed info out of the request-in-progress assert(jsonresponse.empty() && r.jsonresponse.empty()); assert(json.pos == NULL && r.json.pos == NULL); assert(processindex == 0 && r.processindex == 0); } RequestDispatcher::RequestDispatcher(PrnGen& rng) { // initialize random API request sequence ID (server API is idempotent) resetId(reqid, sizeof reqid, rng); nextreqs.push_back(Request()); } #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) void RequestDispatcher::sendDeferred() { if (!nextreqs.back().empty()) { LOG_debug << "sending deferred requests"; nextreqs.push_back(Request()); } nextreqs.back().swap(deferredRequests); } #endif void RequestDispatcher::add(Badge, Command* c) { #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) if (deferRequests && deferRequests(c)) { LOG_debug << "deferring request"; deferredRequests.add(c); return; } #endif if (nextreqs.back().size() >= MAX_COMMANDS) { LOG_debug << "Starting an additional Request due to MAX_COMMANDS"; nextreqs.push_back(Request()); } if (c->batchSeparately && !nextreqs.back().empty()) { LOG_debug << "Starting an additional Request for a batch-separately command"; nextreqs.push_back(Request()); } nextreqs.back().add(c); if (c->batchSeparately) { nextreqs.push_back(Request()); } } bool RequestDispatcher::readyToSend() const { if (!inflightreq.empty()) { // retry of prior attempt. Otherwise, we are waiting response, so not ready return inflightFailReason != RETRY_NONE; } else { return nextreqs.empty() ? false : !nextreqs.front().empty(); } } bool RequestDispatcher::cmdsInflight() const { // stays true even through network errors, retries, etc until we get that response // but not when the API has notified that commands were not applied return !inflightreq.empty() && inflightFailReason != RETRY_API_LOCK && inflightFailReason != RETRY_RATE_LIMIT; } bool RequestDispatcher::retryReasonIsApi() const { return !inflightreq.empty() && (inflightFailReason == RETRY_API_LOCK || inflightFailReason == RETRY_RATE_LIMIT); } Command* RequestDispatcher::getCurrentCommand(bool currSeqtagSeen) { return currSeqtagSeen ? inflightreq.getCurrentCommand() : nullptr; } string RequestDispatcher::serverrequest(bool& includesFetchingNodes, MegaClient* client, string& idempotenceId) { if (!inflightreq.empty() && inflightFailReason != RETRY_NONE) { // this is a retry after connection failure // everything is already set up, JSON is cached etc. LOG_debug << "cs Retrying the last request after code: " << inflightFailReason; } else { assert(inflightreq.empty()); inflightreq.swap(nextreqs.front()); nextreqs.pop_front(); if (nextreqs.empty()) { nextreqs.push_back(Request()); } } string requestJSON = inflightreq.get(client, reqid, idempotenceId); includesFetchingNodes = inflightreq.isFetchNodes(); #ifdef MEGA_MEASURE_CODE csRequestsSent += inflightreq.size(); csBatchesSent += 1; #endif inflightFailReason = RETRY_NONE; return requestJSON; } void RequestDispatcher::inflightFailure(retryreason_t reason) { #ifdef MEGA_MEASURE_CODE csBatchesReceived += 1; #endif assert(!inflightreq.empty()); assert(!nextreqs.empty()); // we keep inflightreq as it needs to be resent exactly as it was, for idempotence // just track whether we do need to resend, for cmdsInflight() signal assert(reason != RETRY_NONE); inflightFailReason = reason; } void RequestDispatcher::serverresponse(std::string&& movestring, MegaClient *client) { CodeCounter::ScopeTimer ccst(client->performanceStats.csResponseProcessingTime); #ifdef MEGA_MEASURE_CODE csBatchesReceived += 1; csRequestsCompleted += inflightreq.size(); #endif processing = true; inflightreq.serverresponse(std::move(movestring), client); inflightreq.process(client); processing = false; if (clearWhenSafe) { clear(); } } size_t RequestDispatcher::serverChunk(const char *chunk, MegaClient *client) { processing = true; size_t consumed = static_cast(inflightreq.processChunk(chunk, client)); processing = false; if (clearWhenSafe) { clear(); } return consumed; } size_t RequestDispatcher::chunkedProgress() { return static_cast(inflightreq.totalChunkedProgress()); } void RequestDispatcher::servererror(const std::string& e, MegaClient *client) { // notify all the commands in the batch of the failure // so that they can deallocate memory, take corrective action etc. processing = true; inflightreq.servererror(e, client); inflightreq.process(client); assert(inflightreq.empty()); inflightFailReason = RETRY_NONE; processing = false; if (clearWhenSafe) { clear(); } } void RequestDispatcher::continueProcessing(MegaClient* client) { assert(!inflightreq.empty()); processing = true; inflightreq.process(client); processing = false; if (clearWhenSafe) { clear(); } } void RequestDispatcher::clear() { if (processing) { // we are being called from a command that is in progress (eg. logout) - delay wiping the data structure until that call ends. clearWhenSafe = true; inflightreq.stopProcessing = true; } else { inflightreq.clear(); inflightFailReason = RETRY_NONE; for (auto& r : nextreqs) { r.clear(); } nextreqs.clear(); nextreqs.push_back(Request()); processing = false; clearWhenSafe = false; } } } // namespace sdk-10.11.0/src/serialize64.cpp000066400000000000000000000024301516266226600161020ustar00rootroot00000000000000/** * @file serialize64.cpp * @brief 64-bit int serialization/unserialization * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/serialize64.h" namespace mega { int Serialize64::serialize(byte* bytes, uint64_t value) { byte byteCount = 0; while (value) { bytes[++byteCount] = (byte)value; value >>= 8; } bytes[0] = byteCount; return byteCount + 1; } int Serialize64::unserialize(byte* bytes, int blen, uint64_t* value) { byte byteCount = bytes[0]; if ((byteCount > sizeof(*value)) || (byteCount >= blen)) { return -1; } *value = 0; while (byteCount) { *value = (*value << 8) + bytes[(int)byteCount--]; } return bytes[0] + 1; } } // namespace sdk-10.11.0/src/setandelement.cpp000066400000000000000000000253711516266226600166020ustar00rootroot00000000000000/** * @file setandelement.cpp * @brief Class for manipulating Sets and their Elements * * (c) 2013-2022 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/base64.h" #include "mega/setandelement.h" #include "mega/utils.h" using namespace ::std; namespace mega { const string CommonSE::nameTag = "n"; const string Set::coverTag = "c"; void CommonSE::setName(string&& name) { setAttr(nameTag, std::move(name)); } void CommonSE::setAttr(const string& tag, string&& value) { if (!mAttrs) { mAttrs.reset(new string_map()); } (*mAttrs)[tag] = std::move(value); } void CommonSE::rebaseCommonAttrsOn(const string_map* baseAttrs) { if (!baseAttrs) { return; // nothing to do } if (!mAttrs) { mAttrs.reset(new string_map()); } // copy missing attributes if (mAttrs->empty()) // small optimizations { *mAttrs = *baseAttrs; } else { string_map rebased = *baseAttrs; for (auto& a : *mAttrs) { if (a.second.empty()) { rebased.erase(a.first); } else { rebased[a.first].swap(a.second); } } mAttrs->swap(rebased); } if (mAttrs->empty()) { mAttrs.reset(); } } bool CommonSE::hasAttrChanged(const string& tag, const unique_ptr& otherAttrs) const { string otherValue; if (otherAttrs) { auto it = otherAttrs->find(tag); otherValue = it != otherAttrs->end() ? it->second : ""; } const string& value = getAttr(tag); return value != otherValue; } const string& CommonSE::getAttr(const string& tag) const { static const string value; if (!mAttrs) { return value; } auto it = mAttrs->find(tag); return it != mAttrs->end() ? it->second : value; } bool CommonSE::decryptAttributes(std::function f) { if (!mEncryptedAttrs) // 'at' was not received { return true; } if (mEncryptedAttrs->empty()) // 'at' was received empty { mAttrs.reset(new string_map()); mEncryptedAttrs.reset(); return true; } unique_ptr newAttrs(new string_map()); if (f(*mEncryptedAttrs, mKey, *newAttrs)) { mAttrs.swap(newAttrs); mEncryptedAttrs.reset(); return true; } return false; } string CommonSE::encryptAttributes(std::function f) const { if (!mAttrs || mAttrs->empty()) { return string(); } return f(*mAttrs, mKey); } handle Set::cover() const { string hs = getAttr(coverTag); if (!hs.empty()) { handle h = 0; Base64::atob(hs.c_str(), (byte*)&h, SetElement::HANDLESIZE); return h; } return UNDEF; } void Set::setCover(handle h) { if (h == UNDEF) { setAttr(coverTag, string()); } else { Base64Str b64s(h); setAttr(coverTag, string(b64s.chars)); } } bool Set::serialize(string* d) const { CacheableWriter r(*d); r.serializehandle(mId); handle publicId = mPublicLink ? mPublicLink->getPublicHandle() : UNDEF; r.serializehandle(publicId); r.serializehandle(mUser); r.serializecompressedi64(mTs); r.serializestring(mKey); size_t asize = mAttrs ? mAttrs->size() : 0; r.serializeu32((uint32_t)asize); if (asize) { for (auto& aa : *mAttrs) { r.serializestring(aa.first); r.serializestring(aa.second); } } r.serializeexpansionflags(true, true, !!mPublicLink); r.serializecompressedi64(mCTs); r.serializeu8(mType); if (mPublicLink) { r.serializeu8(static_cast(mPublicLink->getLinkDeletionReason())); r.serializebool(mPublicLink->isTakenDown()); } return true; } unique_ptr Set::unserialize(string* d) { handle id = 0, publicId = 0, u = 0; m_time_t ts = 0; string k; uint32_t attrCount = 0; CacheableReader r(*d); if (!r.unserializehandle(id) || !r.unserializehandle(publicId) || !r.unserializehandle(u) || !r.unserializecompressedi64(ts) || !r.unserializestring(k) || !r.unserializeu32(attrCount)) { return nullptr; } // get all attrs string_map attrs; for (uint32_t i = 0; i < attrCount; ++i) { string ak, av; if (!r.unserializestring(ak) || !r.unserializestring(av)) { return nullptr; } attrs[std::move(ak)] = std::move(av); } unsigned char expansionsS[8]; m_time_t cts = 0; SetType t = TYPE_ALBUM; // by default, for migration of existing Sets if (!r.unserializeexpansionflags(expansionsS, 3) || (expansionsS[0] && !r.unserializecompressedi64(cts)) || // creation timestamp (expansionsS[1] && !r.unserializeu8(t))) // type { return nullptr; } bool publicLink = static_cast(expansionsS[2]); std::unique_ptr publicLinkSet; if (publicLink) { uint8_t reason; bool takeDown; if (!r.unserializeu8(reason) || !r.unserializebool(takeDown)) { return nullptr; } publicLinkSet = std::make_unique(publicId); publicLinkSet->setLinkDeletionReason( static_cast(reason)); publicLinkSet->setTakeDown(takeDown); } auto s = std::make_unique(id, std::move(k), u, std::move(attrs), t); s->setTs(ts); s->setCTs(cts); s->setPublicLink(std::move(publicLinkSet)); return s; } bool Set::updateWith(Set&& s) { setTs(s.ts()); if (s.publicId() != publicId() || (getPublicLink() && s.getPublicLink()->isTakenDown() != getPublicLink()->isTakenDown())) { setChanged(CH_EXPORTED); setPublicLink(s.getPublicLink()); } else { if (hasAttrChanged(nameTag, s.mAttrs)) setChanged(CH_NAME); if (hasAttrChanged(coverTag, s.mAttrs)) setChanged(CH_COVER); mAttrs.swap(s.mAttrs); } return changes() > 0; } void Set::setChanged(int changeType) { auto ct = static_cast(changeType); if (validChangeType(ct, CH_SIZE)) { mChanges[static_cast(ct)] = 1; } } bool SetElement::updateWith(SetElement&& el) { if (el.hasOrder()) { setOrder(el.order()); } setTs(el.ts()); // attrs of existing Element should be replaced if any of them has been updated, or // if they have been completely cleared (by the last 'aep' command) if (el.hasAttrs() || el.hasAttrsClearedByLastUpdate()) { if (hasAttrChanged(nameTag, el.mAttrs)) setChanged(CH_EL_NAME); mAttrs.swap(el.mAttrs); } return changes() > 0; } void SetElement::setChanged(int changeType) { auto ct = static_cast(changeType); if (validChangeType(ct, CH_EL_SIZE)) { mChanges[static_cast(ct)] = 1; } } void SetElement::setOrder(int64_t order) { if (!mOrder) { mOrder.reset(new int64_t(order)); setChanged(CH_EL_ORDER); } else if (*mOrder != order) { *mOrder = order; setChanged(CH_EL_ORDER); } } bool SetElement::serialize(string* d) const { CacheableWriter r(*d); r.serializehandle(mSetId); r.serializehandle(mId); r.serializenodehandle(mNodeHandle); r.serializei64(mOrder ? *mOrder : 0); // it will always have Order r.serializecompressedi64(mTs); r.serializestring(mKey); size_t asize = mAttrs ? mAttrs->size() : 0; r.serializeu32((uint32_t)asize); if (asize) { for (auto& aa : *mAttrs) { r.serializestring(aa.first); r.serializestring(aa.second); } } r.serializeexpansionflags(); return true; } unique_ptr SetElement::unserialize(string* d) { handle sid = 0, eid = 0; handle h = 0; int64_t o = 0; m_time_t ts = 0; string k; uint32_t attrCount = 0; unsigned char expansionsE[8]; CacheableReader r(*d); if (!r.unserializehandle(sid) || !r.unserializehandle(eid) || !r.unserializenodehandle(h) || !r.unserializei64(o) || !r.unserializecompressedi64(ts) || !r.unserializestring(k) || !r.unserializeu32(attrCount)) { return nullptr; } // get all attrs string_map attrs; for (size_t i = 0; i < attrCount; ++i) { string ak, av; if (!r.unserializestring(ak) || !r.unserializestring(av)) { return nullptr; } attrs[std::move(ak)] = std::move(av); } if (!r.unserializeexpansionflags(expansionsE, 0)) { return nullptr; } auto el = std::make_unique(sid, h, eid, std::move(k), std::move(attrs)); el->setOrder(o); el->setTs(ts); return el; } } //namespace sdk-10.11.0/src/share.cpp000066400000000000000000000061461516266226600150530ustar00rootroot00000000000000/** * @file share.cpp * @brief Classes for manipulating share credentials * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/share.h" namespace mega { Share::Share(User* u, accesslevel_t a, m_time_t t, PendingContactRequest* pending) { user = u; access = a; ts = t; this->pcr = pending; } void Share::serialize(string* d) { handle uh = user ? user->userhandle : 0; char a = (char)access; char version = 1; handle ph = pcr!=NULL ? pcr->id : UNDEF; d->append((char*)&uh, sizeof uh); d->append((char*)&ts, sizeof ts); d->append((char*)&a, 1); d->append((char*)&version, 1); d->append((char*)&ph, sizeof ph); } NewShare* Share::unserialize(int direction, handle h, const byte* key, const char** ptr, const char* end) { if (*ptr + sizeof(handle) + sizeof(m_time_t) + 2 > end) { return nullptr; } char version_flag = (*ptr)[sizeof(handle) + sizeof(m_time_t) + 1]; handle ph = UNDEF; if (version_flag >= 1) { // Pending flag exists ph = MemAccess::get(*ptr + sizeof(handle) + sizeof(m_time_t) + 2); } auto newShare = new NewShare(h, direction, MemAccess::get(*ptr), (accesslevel_t)(*ptr)[sizeof(handle) + sizeof(m_time_t)], MemAccess::get(*ptr + sizeof(handle)), key, NULL, ph); *ptr += sizeof(handle) + sizeof(m_time_t) + 2; if (version_flag >= 1) { *ptr += sizeof(handle); } return newShare; } void Share::update(accesslevel_t a, m_time_t t, PendingContactRequest* pending) { access = a; ts = t; pcr = pending; } // coutgoing: < 0 - don't authenticate, > 0 - authenticate using handle auth NewShare::NewShare(handle ch, int coutgoing, handle cpeer, accesslevel_t caccess, m_time_t cts, const byte* ckey, const byte* cauth, handle cpending,bool cupgrade_pending_to_full, bool okremoved) { h = ch; outgoing = coutgoing; peer = cpeer; access = caccess; ts = cts; pending = cpending; upgrade_pending_to_full = cupgrade_pending_to_full; remove_key = okremoved; if (ckey && !SymmCipher::isZeroKey(ckey, SymmCipher::BLOCKSIZE)) { memcpy(key, ckey, sizeof key); have_key = 1; } else { memset(key, 0, sizeof key); have_key = 0; } if ((outgoing > 0) && cauth) { memcpy(auth, cauth, sizeof auth); have_auth = 1; } else { memset(auth, 0, sizeof auth); have_auth = 0; } } } // namespace sdk-10.11.0/src/sharenodekeys.cpp000066400000000000000000000071271516266226600166150ustar00rootroot00000000000000/** * @file sharenodekeys.cpp * @brief cr element share/node map key generator * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/sharenodekeys.h" #include "mega/node.h" #include "mega/base64.h" #include "mega/megaclient.h" #include "mega/command.h" #include "mega/logging.h" namespace mega { // add share node and return its index int ShareNodeKeys::addshare(std::shared_ptr sn) { for (int i = static_cast(shares.size()); i--;) { if (shares[static_cast(i)] == sn) { return i; } } shares.push_back(sn); return static_cast(shares.size() - 1); } void ShareNodeKeys::add(std::shared_ptr n, std::shared_ptr sn, bool includeParentChain) { if (!sn) { sn = n; } if (n->attrstring) // invalid nodekey or undecryptable attributes { LOG_err << "Skip CR request for node: " << toNodeHandle(n->nodehandle) << " (invalid node key)"; return; } add(n->nodekey(), n->nodehandle, sn, includeParentChain); } // add a nodecore (!sn: all relevant shares, otherwise starting from sn, fixed: only sn) void ShareNodeKeys::add(const string& nodekey, handle nodehandle, std::shared_ptr sn, bool includeParentChain, const byte* item, int itemlen) { char buf[96]; char* ptr; byte key[FILENODEKEYLENGTH]; int addnode = 0; // emit all share nodekeys for known shares do { if (sn->sharekey) { snprintf(buf, sizeof(buf), ",%d,%d,\"", addshare(sn), (int)items.size()); sn->sharekey->ecb_encrypt((byte*)nodekey.data(), key, nodekey.size()); ptr = strchr(buf + 5, 0); ptr += Base64::btoa(key, nodekey.size(), ptr); *ptr++ = '"'; keys.append(buf, static_cast(ptr - buf)); addnode = 1; } } while (includeParentChain && (sn = sn->parent)); if (addnode) { items.resize(items.size() + 1); if (item) { items[items.size() - 1].assign((const char*)item, static_cast(itemlen)); } else { items[items.size() - 1].assign((const char*)&nodehandle, MegaClient::NODEHANDLE); } } } void ShareNodeKeys::get(Command* c, bool skiphandles) { if (keys.size()) { c->beginarray("cr"); // emit share node handles c->beginarray(); for (unsigned i = 0; i < shares.size(); i++) { c->element((const byte*)&shares[i]->nodehandle, MegaClient::NODEHANDLE); } c->endarray(); // emit item handles (can be node handles or upload tokens) c->beginarray(); if (!skiphandles) { for (unsigned i = 0; i < items.size(); i++) { c->element((const byte*)items[i].c_str(), items[i].size()); } } c->endarray(); // emit linkage/keys c->beginarray(); c->appendraw(keys.c_str() + 1, int(keys.size() - 1)); c->endarray(); c->endarray(); } } } // namespace sdk-10.11.0/src/sync.cpp000066400000000000000000021352401516266226600147250ustar00rootroot00000000000000/** * @file sync.cpp * @brief Class for synchronizing local and remote trees * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include #include #include #include #include #ifdef ENABLE_SYNC #include "mega/base64.h" #include "mega/heartbeats.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/scoped_helpers.h" #include "mega/sync.h" #include "mega/syncinternals/syncinternals.h" #include "mega/syncinternals/syncinternals_logging.h" #include "mega/syncinternals/syncuploadthrottlingmanager.h" #include "mega/tlv.h" #include "mega/transfer.h" #include "mega/user_attribute.h" #ifdef __ANDROID__ #include "mega/android/androidFileSystem.h" #endif namespace mega { const int Sync::SCANNING_DELAY_DS = 5; const int Sync::EXTRA_SCANNING_DELAY_DS = 150; const int Sync::FILE_UPDATE_DELAY_DS = 30; const int Sync::FILE_UPDATE_MAX_DELAY_SECS = 60; const dstime Sync::RECENT_VERSION_INTERVAL_SECS = 10800; const unsigned Sync::MAX_CLOUD_DEPTH = 64; using namespace std::chrono_literals; const std::chrono::milliseconds Syncs::MIN_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT{100ms}; const std::chrono::milliseconds Syncs::MAX_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT{10s}; static bool handleCloneMacStatusFromSyncThread(Syncs& syncs, std::shared_ptr upload, const CloneMacStatus macStatus, const bool queueFirst, const NodeHandle& ovHandleIfShortcut) { if (!upload) { return false; } if (macStatus == CloneMacStatus::Ready || macStatus == CloneMacStatus::Failed) { syncs.queueClient( [upload, queueFirst, ovHandleIfShortcut, macStatus](MegaClient& mc, TransferDbCommitter& committer) { processCloneMacResult(mc, committer, upload, UseLocalVersioningFlag, queueFirst, ovHandleIfShortcut, macStatus); }); return true; } return false; } bool PerSyncStats::operator==(const PerSyncStats& other) { return scanning == other.scanning && syncing == other.syncing && numFiles == other.numFiles && numFolders == other.numFolders && numUploads == other.numUploads && numDownloads == other.numDownloads; } bool PerSyncStats::operator!=(const PerSyncStats& other) { return !(*this == other); } ChangeDetectionMethod changeDetectionMethodFromString(const string& method) { static const auto notifications = changeDetectionMethodToString(CDM_NOTIFICATIONS); static const auto scanning = changeDetectionMethodToString(CDM_PERIODIC_SCANNING); if (method == notifications) return CDM_NOTIFICATIONS; if (method == scanning) return CDM_PERIODIC_SCANNING; return CDM_UNKNOWN; } string changeDetectionMethodToString(const ChangeDetectionMethod method) { switch (method) { case CDM_NOTIFICATIONS: return "notifications"; case CDM_PERIODIC_SCANNING: return "scanning"; default: return "unknown"; } } bool SyncPath::appendRowNames(const SyncRow& row, FileSystemType filesystemType) { if (row.isNoName()) { auto noName = string("NO_NAME"); // Multiple no name triplets? if (row.hasClashes()) noName += "S"; cloudPath += "/" + noName; localPath.appendWithSeparator(LocalPath::fromRelativePath(noName), true); syncPath += "/" + noName; return true; } // add to localPath if (row.fsNode) { localPath.appendWithSeparator(row.fsNode->localname, true); } else if (row.syncNode) { localPath.appendWithSeparator(row.syncNode->localname, true); } else if (row.cloudNode) { // this is the local name used when downsyncing a cloud name, if previously unmatched localPath.appendWithSeparator(LocalPath::fromRelativeName(row.cloudNode->name, *syncs.fsaccess, filesystemType), true); } else if (!row.cloudClashingNames.empty() || !row.fsClashingNames.empty()) { // so as not to mislead in logs etc localPath.appendWithSeparator(LocalPath::fromRelativeName("<<>>", *syncs.fsaccess, filesystemType), true); } else { // this is a legitimate case; eg. we only had a syncNode and it is removed in resolve_delSyncNode return false; } // add to cloudPath if (cloudPath.empty() || cloudPath.back() != '/') cloudPath += "/"; CloudNode cn; if (row.cloudNode) { cloudPath += row.cloudNode->name; } else if (row.syncNode && syncs.lookupCloudNode(row.syncNode->syncedCloudNodeHandle, cn, nullptr, nullptr, nullptr, nullptr, nullptr, Syncs::LATEST_VERSION)) { cloudPath += cn.name; } else if (row.syncNode) { cloudPath += row.syncNode->toName_of_localname; } else if (row.fsNode) { cloudPath += row.fsNode->localname.toName(*syncs.fsaccess); } else if (!row.cloudClashingNames.empty() || !row.fsClashingNames.empty()) { // so as not to mislead in logs etc cloudPath += "<<>>"; } else { // this is a legitimate case; eg. we only had a syncNode and it is removed in resolve_delSyncNode return false; } // add to syncPath if (syncPath.empty() || syncPath.back() != '/') syncPath += "/"; if (row.cloudNode) { syncPath += row.cloudNode->name; } else if (row.syncNode) { syncPath += row.syncNode->toName_of_localname; } else if (row.fsNode) { syncPath += row.fsNode->localname.toName(*syncs.fsaccess); } else if (!row.cloudClashingNames.empty() || !row.fsClashingNames.empty()) { // so as not to mislead in logs etc syncPath += "<<>>"; } else { // this is a legitimate case; eg. we only had a syncNode and it is removed in resolve_delSyncNode return false; } return true; } LocalPath SyncThreadsafeState::syncTmpFolder() const { lock_guard g(mMutex); return mSyncTmpFolder; } void SyncThreadsafeState::setSyncTmpFolder(const LocalPath& tmpFolder) { lock_guard g(mMutex); mSyncTmpFolder = tmpFolder; } void SyncThreadsafeState::addExpectedUpload(NodeHandle parentHandle, const string& name, weak_ptr wp) { lock_guard g(mMutex); mExpectedUploads[toNodeHandle(parentHandle) + ":" + name] = wp; LOG_verbose << "Expecting upload-putnode " << (toNodeHandle(parentHandle) + ":" + name); } void SyncThreadsafeState::removeExpectedUpload(NodeHandle parentHandle, const string& name) { lock_guard g(mMutex); mExpectedUploads.erase(toNodeHandle(parentHandle) + ":" + name); LOG_verbose << "Unexpecting upload-putnode " << (toNodeHandle(parentHandle) + ":" + name); } shared_ptr SyncThreadsafeState::isNodeAnExpectedUpload(NodeHandle parentHandle, const string& name) { lock_guard g(mMutex); auto it = mExpectedUploads.find(toNodeHandle(parentHandle) + ":" + name); return it == mExpectedUploads.end() ? nullptr : it->second.lock(); } void SyncThreadsafeState::adjustTransferCounts(bool upload, int32_t adjustQueued, int32_t adjustCompleted, m_off_t adjustQueuedBytes, m_off_t adjustCompletedBytes) { lock_guard g(mMutex); auto& tc = upload ? mTransferCounts.mUploads : mTransferCounts.mDownloads; assert(adjustQueued >= 0 || tc.mPending); assert(adjustCompleted >= 0 || tc.mCompleted); assert(adjustQueuedBytes >= 0 || tc.mPendingBytes); assert(adjustCompletedBytes >= 0 || tc.mCompletedBytes); tc.mPending += static_cast(adjustQueued); tc.mCompleted += static_cast(adjustCompleted); tc.mPendingBytes += static_cast(adjustQueuedBytes); tc.mCompletedBytes += static_cast(adjustCompletedBytes); if (!tc.mPending && tc.mCompletedBytes == tc.mPendingBytes) { tc.mCompletedBytes = 0; tc.mPendingBytes = 0; } } void SyncThreadsafeState::transferBegin(direction_t direction, m_off_t numBytes) { adjustTransferCounts(direction == PUT, 1, 0, numBytes, 0); } void SyncThreadsafeState::transferComplete(direction_t direction, m_off_t numBytes) { adjustTransferCounts(direction == PUT, -1, 1, -numBytes, numBytes); } void SyncThreadsafeState::transferFailed(direction_t direction, m_off_t numBytes) { adjustTransferCounts(direction == PUT, -1, 0, -numBytes, 0); } SyncTransferCounts SyncThreadsafeState::transferCounts() const { lock_guard guard(mMutex); return mTransferCounts; } void SyncThreadsafeState::incrementSyncNodeCount(nodetype_t type, int32_t count) { lock_guard guard(mMutex); if (type == FILENODE) mFileCount += count; if (type == FOLDERNODE) mFolderCount += count; } void SyncThreadsafeState::getSyncNodeCounts(int32_t& files, int32_t& folders) { lock_guard guard(mMutex); files = mFileCount; folders = mFolderCount; } SyncConfig::SyncConfig(LocalPath localPath, std::string name, NodeHandle remoteNode, const std::string& remotePath, const fsfp_t localFingerprint, const LocalPath& externalDrivePath, const bool enabled, const SyncConfig::Type syncType, const SyncError error, const SyncWarning warning, mega::handle hearBeatID): mEnabled(enabled), mLocalPath(std::move(localPath)), mName(std::move(name)), mRemoteNode(remoteNode), mOriginalPathOfRemoteRootNode(remotePath), mFilesystemFingerprint(localFingerprint), mLocalPathFsid(UNDEF), mSyncType(syncType), mError(error), mWarning(warning), mBackupId(hearBeatID), mExternalDrivePath(externalDrivePath) {} bool SyncConfig::getEnabled() const { return mEnabled; } void SyncConfig::setEnabled(bool enabled) { mEnabled = enabled; } const LocalPath& SyncConfig::getLocalPath() const { return mLocalPath; } SyncConfig::Type SyncConfig::getType() const { return mSyncType; } bool SyncConfig::isBackup() const { return mSyncType == TYPE_BACKUP; } bool SyncConfig::isExternal() const { return !mExternalDrivePath.empty(); } bool SyncConfig::isInternal() const { return mExternalDrivePath.empty(); } bool SyncConfig::stateFieldsChanged() { bool changed = mError != mKnownError || mEnabled != mKnownEnabled || mKnownRunState != mRunState; if (changed) { mKnownError = mError; mKnownEnabled = mEnabled; mKnownRunState = mRunState; } return changed; } std::string SyncConfig::syncErrorToStr() { return syncErrorToStr(mError); } std::string SyncConfig::syncErrorToStr(SyncError errorCode) { switch(errorCode) { case NO_SYNC_ERROR: case UNLOADING_SYNC: return "No error"; case UNKNOWN_ERROR: return "Unknown error"; case UNSUPPORTED_FILE_SYSTEM: return "File system not supported"; case INVALID_REMOTE_TYPE: return "Remote node is not valid"; case INVALID_LOCAL_TYPE: return "Local path is not valid"; case INITIAL_SCAN_FAILED: return "Initial scan failed"; case LOCAL_PATH_TEMPORARY_UNAVAILABLE: return "Local path temporarily unavailable"; case LOCAL_PATH_UNAVAILABLE: return "Local path not available"; case REMOTE_NODE_NOT_FOUND: return "Remote node not found"; case STORAGE_OVERQUOTA: return "Reached storage quota limit"; case ACCOUNT_EXPIRED: return "Your plan has expired"; case FOREIGN_TARGET_OVERSTORAGE: return "Foreign target storage quota reached"; case REMOTE_PATH_HAS_CHANGED: return "Remote path has changed"; case REMOTE_NODE_MOVED_TO_RUBBISH: return "Remote node moved to Rubbish Bin"; case SHARE_NON_FULL_ACCESS: return "Share without full access"; case LOCAL_FILESYSTEM_MISMATCH: return "Local filesystem mismatch"; case PUT_NODES_ERROR: return "Put nodes error"; case ACTIVE_SYNC_BELOW_PATH: return "Active sync below path"; case ACTIVE_SYNC_ABOVE_PATH: return "Active sync above path"; case REMOTE_NODE_INSIDE_RUBBISH: return "Remote node is inside Rubbish Bin"; case VBOXSHAREDFOLDER_UNSUPPORTED: return "Unsupported VBoxSharedFolderFS filesystem"; case LOCAL_PATH_SYNC_COLLISION: return "Local path collides with an existing sync"; case ACCOUNT_BLOCKED: return "Your account is blocked"; case UNKNOWN_TEMPORARY_ERROR: return "Unknown temporary error"; case TOO_MANY_ACTION_PACKETS: return "Too many changes in account, local state invalid"; case LOGGED_OUT: return "Session closed"; case BACKUP_MODIFIED: return "Backup externally modified"; case BACKUP_SOURCE_NOT_BELOW_DRIVE: return "Backup source path not below drive path."; case SYNC_CONFIG_WRITE_FAILURE: return "Unable to write sync config to disk."; case ACTIVE_SYNC_SAME_PATH: return "Active sync same path"; case COULD_NOT_MOVE_CLOUD_NODES: return "Unable to move cloud nodes."; case COULD_NOT_CREATE_IGNORE_FILE: return "Unable to create initial ignore file."; case SYNC_CONFIG_READ_FAILURE: return "Unable to read sync configs from disk."; case UNKNOWN_DRIVE_PATH: return "Unknown drive path."; case INVALID_SCAN_INTERVAL: return "Invalid scan interval specified."; case NOTIFICATION_SYSTEM_UNAVAILABLE: return "Filesystem notification subsystem unavailable."; case UNABLE_TO_ADD_WATCH: return "Unable to add filesystem watch."; case UNABLE_TO_RETRIEVE_ROOT_FSID: return "Unable to retrieve sync root FSID."; case UNABLE_TO_OPEN_DATABASE: return "Unable to open state cache database."; case INSUFFICIENT_DISK_SPACE: return "Insufficient disk space."; case FAILURE_ACCESSING_PERSISTENT_STORAGE: return "Failure accessing to persistent storage"; case UNABLE_TO_RETRIEVE_DEVICE_ID: return "Unable to retrieve the ID of current device"; case MISMATCH_OF_ROOT_FSID: return "Mismatch on sync root FSID."; case FILESYSTEM_FILE_IDS_ARE_UNSTABLE: return "Syncing of exFAT, FAT32, FUSE and LIFS file systems is not supported by MEGA on macOS."; case FILESYSTEM_ID_UNAVAILABLE: return "Could not get the filesystem's ID."; case LOCAL_PATH_MOUNTED: return "Local path is a FUSE mount."; default: return "Undefined error"; } } const char* SyncConfig::synctypename(const SyncConfig::Type type) { switch (type) { case SyncConfig::TYPE_BACKUP: return "BACKUP"; case SyncConfig::TYPE_DOWN: return "DOWN"; case SyncConfig::TYPE_UP: return "UP"; case SyncConfig::TYPE_TWOWAY: return "TWOWAY"; default: return "UNKNOWN"; } } bool SyncConfig::synctypefromname(const string& name, Type& type) { if (name == "BACKUP") { return type = TYPE_BACKUP, true; } if (name == "DOWN") { return type = TYPE_DOWN, true; } else if (name == "UP") { return type = TYPE_UP, true; } else if (name == "TWOWAY") { return type = TYPE_TWOWAY, true; } assert(false && "Unknown sync type name."); return false; } SyncError SyncConfig::knownError() const { return mKnownError; } bool SyncConfig::isScanOnly() const { return mChangeDetectionMethod == CDM_PERIODIC_SCANNING; } string SyncConfig::getSyncDbStateCacheName(handle fsid, NodeHandle nh, handle userId) const { handle tableid[3]; tableid[0] = fsid; tableid[1] = nh.as8byte(); tableid[2] = userId; string dbname; dbname.resize(sizeof tableid * 4 / 3 + 3); dbname.resize(Base64::btoa((byte*)tableid, sizeof tableid, (char*)dbname.c_str())); return dbname; } std::optional SyncConfig::getSyncDbPath(const FileSystemAccess& fsAccess, const MegaClient& client) const { const auto fname = getSyncDbStateCacheName(mLocalPathFsid, mRemoteNode, client.me); return client.dbaccess->getExistingDbPath(fsAccess, fname); } void SyncConfig::renameDBToMatchTarget(const SyncConfig& targetConfig, FileSystemAccess& fsAccess, const MegaClient& client) const { const auto newDbFileName = targetConfig.getSyncDbStateCacheName(targetConfig.mLocalPathFsid, targetConfig.mRemoteNode, client.me); const auto currentDbFileName = targetConfig.getSyncDbStateCacheName(mLocalPathFsid, mRemoteNode, client.me); client.dbaccess->renameDBFiles( fsAccess, client.dbaccess->databasePath(fsAccess, currentDbFileName, DbAccess::DB_VERSION), client.dbaccess->databasePath(fsAccess, newDbFileName, DbAccess::DB_VERSION)); } std::pair buildSyncConfig(const SyncConfig::Type syncType, const std::string& localPath, const std::string& name, const std::string& drivePath, const handle nodeHandle, MegaClient& client) { if (localPath.empty()) { LOG_debug << "[buildSyncConfig] Error: empty local path"; return {API_EARGS, {}}; } const auto syncLocalPath = LocalPath::fromAbsolutePath(localPath); const auto [errNode, remoteNodeHandle, originalPathOfRemoteNode] = std::invoke( [&syncType, &client, &nodeHandle]() -> std::tuple { if (syncType == SyncConfig::TYPE_BACKUP) return {API_OK, {}, {}}; if (const auto node = client.nodebyhandle(nodeHandle); node && node->type != FILENODE) return {API_OK, NodeHandle().set6byte(nodeHandle), node->displaypath()}; LOG_debug << "[buildSyncConfig] Node not found for sync add"; return {API_EARGS, {}, {}}; }); if (errNode != API_OK) return {errNode, {}}; const auto syncName = name.empty() ? syncLocalPath.leafOrParentName() : name; const auto syncDrivePath = drivePath.empty() ? LocalPath() : LocalPath::fromAbsolutePath(drivePath); const auto enabled = true; const SyncConfig syncConfig{syncLocalPath, syncName, remoteNodeHandle, originalPathOfRemoteNode, fsfp_t(), syncDrivePath, enabled, syncType}; return {API_OK, syncConfig}; } // new Syncs are automatically inserted into the session's syncs list // and a full read of the subtree is initiated Sync::Sync(UnifiedSync& us, const std::string& logname, SyncError& e): syncs(us.syncs), localroot(nullptr), mUnifiedSync(us), syncscanbt(us.syncs.rng), threadSafeState( new SyncThreadsafeState(us.mConfig.mBackupId, &syncs.mClient, us.mConfig.isBackup())), // assuming backups are only in Vault mLocalPath(us.mConfig.getLocalPath()) { e = NO_SYNC_ERROR; assert(syncs.onSyncThread()); localroot.reset(new LocalNode(this)); std::unique_lock syncVecMutexLock(syncs.mSyncVecMutex); const SyncConfig& config = us.mConfig; const auto remoteNodeHandle = config.mRemoteNode; syncVecMutexLock.unlock(); syncs.lookupCloudNode(remoteNodeHandle, cloudRoot, &cloudRootPath, nullptr, nullptr, nullptr, nullptr, Syncs::FOLDER_ONLY, &cloudRootOwningUser); inshare = syncs.isCloudNodeInShare(cloudRoot); syncVecMutexLock.lock(); tmpfa = NULL; syncname = logname; // can be updated to be more specific in logs assert(mUnifiedSync.mConfig.mRunState == SyncRunState::Loading); mFilesystemType = syncs.fsaccess->getlocalfstype(mLocalPath); LOG_debug << "Sync being created on filesystem type " << mFilesystemType << ": " << FileSystemAccess::fstypetostring(mFilesystemType); isnetwork = isNetworkFilesystem(mFilesystemType); localroot->init(FOLDERNODE, NULL, mLocalPath, nullptr); // the root node must have the absolute path. We don't store shortname, to avoid accidentally using relative paths. localroot->setSyncedNodeHandle(config.mRemoteNode); localroot->setScanAgain(false, true, true, 0); localroot->setCheckMovesAgain(false, true, true); localroot->setSyncAgain(false, true, true); debris = DEBRISFOLDER; localdebrisname = LocalPath::fromRelativePath(debris); localdebris = localdebrisname; localdebris.prependWithSeparator(mLocalPath); // Should this sync make use of filesystem notifications? if (us.mConfig.mChangeDetectionMethod == CDM_NOTIFICATIONS) { // Notifications may be queueing from this moment dirnotify.reset(syncs.fsaccess->newdirnotify(*localroot, mLocalPath, syncs.waiter.get())); } // set specified fsfp or get from fs if none auto fsfp = syncs.fsaccess->fsFingerprint(mLocalPath); if (!fsfp) { e = FILESYSTEM_ID_UNAVAILABLE; return; } auto& original_fsfp = mUnifiedSync.mConfig.mFilesystemFingerprint; if (original_fsfp) { if (!original_fsfp.equivalent(fsfp)) { // leave this function before we create a Rubbish/.debris folder where we maybe shouldn't LOG_err << "Sync root path is a different Filesystem than when the sync was created. Original filesystem id: " << original_fsfp.toString() << " Current: " << fsfp.toString(); e = LOCAL_FILESYSTEM_MISMATCH; return; } } fsstableids = syncs.fsaccess->fsStableIDs(mLocalPath); LOG_info << "Filesystem IDs are stable: " << fsstableids; if (!fsstableids) { #ifdef __APPLE__ // On Mac, stat() can and sometimes does return different IDs between calls // for the same path, for exFAT, and probably other FAT variants too. e = FILESYSTEM_FILE_IDS_ARE_UNSTABLE; return; #endif } auto fas = syncs.fsaccess->newfileaccess(); // we do allow, eg. mounting an exFAT drive over an NTFS folder, and making a sync at that path bool reparsePointOkAtRoot = true; if (!fas->fopen(mLocalPath, OPEN_RDONLY, FSLogging::logOnError, nullptr, reparsePointOkAtRoot, true, nullptr) || fas->fsid == UNDEF) { LOG_err << "Could not open sync root folder, could not get its fsid: " << mLocalPath; e = UNABLE_TO_RETRIEVE_ROOT_FSID; return; } else if (us.mConfig.mLocalPathFsid != UNDEF && us.mConfig.mLocalPathFsid != fas->fsid) { // We can't start a sync with the wrong root folder fsid because that is part of // the name of the sync database. So we can't retrieve the sync state LOG_err << "Sync root folder does not have the same fsid as before: " << mLocalPath << " was " << toHandle(us.mConfig.mLocalPathFsid) << " now " << toHandle(fas->fsid); e = MISMATCH_OF_ROOT_FSID; return; } // record the fsid of the synced folder localroot->fsid_lastSynced = fas->fsid; us.mConfig.mLocalPathFsid = fas->fsid; us.mConfig.mFilesystemFingerprint = fsfp; // Make sure the engine knows about this fingerprint. syncs.mFingerprintTracker.add(fsfp); LOG_debug << "Constructed Sync has filesystemId: " << us.mConfig.mFilesystemFingerprint.toString() << " and root folder id: " << us.mConfig.mLocalPathFsid; // load LocalNodes from cache (only for internal syncs) us.mConfig.mDatabaseExists = shouldHaveDatabase() && openOrCreateDb( [this](DBError error) { syncs.mClient.handleDbError(error); }); if (us.mConfig.mDatabaseExists) readstatecache(); us.mConfig.mRunState = SyncRunState::Run; mCaseInsensitive = determineCaseInsenstivity(false); LOG_debug << "Sync case insensitivity for " << mLocalPath << " is " << mCaseInsensitive; // Increment counter of active syncs. ++syncs.mNumSyncsActive; } Sync::~Sync() { assert(syncs.onSyncThread()); // Remove our reference to this fingerprint. syncs.mFingerprintTracker.remove(fsfp()); // must be set to prevent remote mass deletion while rootlocal destructor runs mDestructorRunning = true; mUnifiedSync.mConfig.mRunState = mUnifiedSync.mConfig.mDatabaseExists ? SyncRunState::Suspend : SyncRunState::Disable; // unlock tmp lock tmpfa.reset(); // Deleting localnodes after this will not remove them from the db. statecachetable.reset(); // This will recursively delete all LocalNodes in the sync. // If they have transfers associated, the SyncUpload_inClient and SyncDownload_inClient will have their wasRequesterAbandoned flag set true localroot.reset(); // Decrement counter of active syncs. --syncs.mNumSyncsActive; } bool Sync::openOrCreateDb(DBErrorCallback&& errorHandler) { // We are using SQLite in the no-mutex mode, so only access a database from a single thread. assert(syncs.onSyncThread()); auto& config = mUnifiedSync.mConfig; const std::string dbname = config.getSyncDbStateCacheName(config.mLocalPathFsid, config.mRemoteNode, syncs.mClient.me); // Check if the database exists on disk. const bool dbExistsOnDisk = syncs.mClient.dbaccess->probe(*syncs.fsaccess, dbname); // Note, we opened dbaccess in thread-safe mode statecachetable.reset( syncs.mClient.dbaccess->open(syncs.rng, *syncs.fsaccess, dbname, DB_OPEN_FLAG_RECYCLE | DB_OPEN_FLAG_TRANSACTED, std::move(errorHandler))); return dbExistsOnDisk || statecachetable != nullptr; }; bool Sync::isBackup() const { assert(syncs.onSyncThread()); return getConfig().isBackup(); } bool Sync::shouldHaveDatabase() const { return mUnifiedSync.shouldHaveDatabase(); } bool Sync::hasPendingTransfersThreadSafeState() const { if (!threadSafeState) return false; const auto counts = threadSafeState->transferCounts(); return counts.mUploads.mPending > 0 || counts.mDownloads.mPending > 0; } bool Syncs::anySyncHasPendingTransfersThreadSafeState() const { assert(onSyncThread() || !onSyncThread()); lock_guard guard(mSyncVecMutex); for (const auto& us: mSyncVec) { if (us && us->mSync && us->mSync->hasPendingTransfersThreadSafeState()) { return true; } } return false; } const fsfp_t& Sync::fsfp() const { return getConfig().mFilesystemFingerprint; } void Sync::addstatecachechildren(uint32_t parent_dbid, idlocalnode_map* tmap, LocalPath& localpath, LocalNode *p, int maxdepth) { assert(syncs.onSyncThread()); auto range = tmap->equal_range(parent_dbid); // remove processed elements as we go, so we can then clean the database at the end. for (auto it = range.first; it != tmap->end() && it->first == parent_dbid; it = tmap->erase(it)) { LocalNode* const l = it->second; auto preExisting = p->children.find(l->localname); if (preExisting != p->children.end()) { // tidying up from prior versions of the SDK which might have duplicate LocalNodes LOG_debug << "Removing duplicate LocalNode: " << preExisting->second->debugGetParentList(); delete preExisting->second; // also detaches and preps removal from db assert(p->children.find(l->localname) == p->children.end()); // l will be added in its place. Later entries were the ones used by the old algorithm } LocalPath newpath{localpath}; newpath.appendWithSeparator(l->localname, true); handle fsid = l->fsid_lastSynced; m_off_t size = l->syncedFingerprint.size; // clear localname to force newnode = true in setnameparent l->localname.clear(); // if we already have the shortname from database, use that, otherwise (db is from old code) look it up std::unique_ptr shortname; if (l->slocalname_in_db) { // null if there is no shortname, or the shortname matches the localname. shortname.reset(l->slocalname.release()); } else { shortname = syncs.fsaccess->fsShortname(newpath); } l->init(l->type, p, newpath, nullptr); l->syncedFingerprint.size = size; l->setSyncedFsid(fsid, syncs.localnodeBySyncedFsid, l->localname, std::move(shortname)); l->setSyncedNodeHandle(l->syncedCloudNodeHandle); l->oneTimeUseSyncedFingerprintInScan = true; if (!l->slocalname_in_db) { statecacheadd(l); if (insertq.size() > 50000) { DBTableTransactionCommitter committer(statecachetable); cachenodes(); // periodically output updated nodes with shortname updates, so people who restart megasync still make progress towards a fast startup } } if (maxdepth) { addstatecachechildren(l->dbid, tmap, newpath, l, maxdepth - 1); } } } void Sync::readstatecache() { assert(syncs.onSyncThread()); string cachedata; idlocalnode_map tmap; uint32_t cid; LOG_debug << syncname << "Sync " << toHandle(getConfig().mBackupId) << " about to load from db"; statecachetable->rewind(); unsigned numLocalNodes = 0; // bulk-load cached nodes into tmap assert(!SymmCipher::isZeroKey(syncs.syncKey.key, sizeof(syncs.syncKey.key))); while (statecachetable->next(&cid, &cachedata, &syncs.syncKey)) { uint32_t parentID = 0; if (auto l = LocalNode::unserialize(*this, cachedata, parentID)) { l->dbid = cid; tmap.emplace(parentID, l.release()); numLocalNodes += 1; } } // recursively build LocalNode tree { DBTableTransactionCommitter committer(statecachetable); LocalPath pathBuffer = localroot->localname; // don't let localname be appended during recurse addstatecachechildren(0, &tmap, pathBuffer, localroot.get(), 100); if (!tmap.empty()) { // if there is anything left in tmap, those are orphan nodes - tidy up the db LOG_debug << "Removing " << tmap.size() << " LocalNode orphans from db"; for (auto& ln : tmap) { statecachedel(ln.second); } } } cachenodes(); LOG_debug << syncname << "Sync " << toHandle(getConfig().mBackupId) << " loaded from db with " << numLocalNodes << " sync nodes"; localroot->setScanAgain(false, true, true, 0); } SyncConfig& Sync::getConfig() { return mUnifiedSync.mConfig; } const SyncConfig& Sync::getConfig() const { return mUnifiedSync.mConfig; } // remove LocalNode from DB cache void Sync::statecachedel(LocalNode* l) { assert(syncs.onSyncThread()); assert(l->sync == this); if (!statecachetable) { return; } if (l->dbid && statecachetable) { statecachetable->del(l->dbid); } l->dbid = 0; insertq.erase(l); } // insert LocalNode into DB cache void Sync::statecacheadd(LocalNode* l) { assert(syncs.onSyncThread()); assert(l->sync == this); if (!statecachetable) { return; } if (l->type < 0) { LOG_verbose << syncname << "Leaving type " << l->type << " out of DB, (scan blocked/symlink/reparsepoint/systemhidden etc): " << l->getLocalPath(); return; } insertq.insert(l); assert(l != localroot.get()); assert(l->parent); } void Sync::cachenodes() { assert(syncs.onSyncThread()); // Purge the queues if we have no state cache. if (!statecachetable) { insertq.clear(); return; } if (insertq.size()) { LOG_debug << syncname << "Saving LocalNode database with " << insertq.size() << " additions"; DBTableTransactionCommitter committer(statecachetable); // additions - we iterate until completion or until we get stuck bool added; do { added = false; for (set::iterator it = insertq.begin(); it != insertq.end(); ) { assert((*it)->type >= 0); assert((*it)->sync == this); assert((*it)->parent->parent || (*it)->parent == localroot.get()); if ((*it)->parent->dbid || (*it)->parent == localroot.get()) { // add once we know the parent dbid so that the parent/child structure is correct in db assert(!SymmCipher::isZeroKey(syncs.syncKey.key, sizeof(syncs.syncKey.key))); statecachetable->put(MegaClient::CACHEDLOCALNODE, *it, &syncs.syncKey); insertq.erase(it++); added = true; } else it++; } } while (added); if (insertq.size()) { LOG_err << "LocalNode caching did not complete"; assert(false); } } } void Sync::changestate(SyncError newSyncError, bool newEnableFlag, bool notifyApp, bool keepSyncDb) { mUnifiedSync.changeState(newSyncError, newEnableFlag, notifyApp, keepSyncDb); } void UnifiedSync::changeState(SyncError newSyncError, bool newEnableFlag, bool notifyApp, bool keepSyncDb) { assert(syncs.onSyncThread()); // External backups should not auto-start newEnableFlag &= mConfig.isInternal(); assert(!(newSyncError == DECONFIGURING_SYNC && keepSyncDb)); assert(!(newEnableFlag && !keepSyncDb)); if (!keepSyncDb) { if (mSync && mSync->statecachetable) { // flush our data structures before we close it. mSync->cachenodes(); // remove the LocalNode database files on sync disablement (historic behaviour; sync re-enable with LocalNode state from non-matching SCSN is not supported (yet)) mSync->statecachetable->remove(); mSync->statecachetable.reset(); } else { // delete the database file directly since we don't have an object for it auto fas = syncs.fsaccess->newfileaccess(false); if (fas->fopen(mConfig.mLocalPath, OPEN_RDONLY, FSLogging::logOnError)) { string dbname = mConfig.getSyncDbStateCacheName(fas->fsid, mConfig.mRemoteNode, syncs.mClient.me); // If the user is upgrading from NO SRW to SRW, we rename the DB files to the new // SRW version. However, if there are db files from a previous SRW version (i.e., // the user downgraded from SRW to NO SRW and then upgraded again to SRW) we need // to remove the SRW db files. The flag DB_OPEN_FLAG_RECYCLE is used for this // purpose. A similar case happens if user is upgrading from No Fingerprint virtual // column in Nodes table to a newer version with this column. int dbFlags = DB_OPEN_FLAG_TRANSACTED; // Unused if (DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_SRW || DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_VFINGERPRINT) { dbFlags |= DB_OPEN_FLAG_RECYCLE; } LocalPath dbPath; syncs.mClient.dbaccess->checkDbFileAndAdjustLegacy(*syncs.fsaccess, dbname, dbFlags, dbPath); LOG_debug << "Deleting sync database at: " << dbPath; syncs.fsaccess->unlinklocal(dbPath); } } mConfig.mDatabaseExists = false; } if (newSyncError != NO_SYNC_ERROR && mSync && mSync->statecachetable) { // if we are keeping the db and unloading the sync, // prevent any more changes to it from this point mSync->cachenodes(); mSync->statecachetable.reset(); } mConfig.mError = newSyncError; mConfig.setEnabled(newEnableFlag); if (newSyncError) { mConfig.mRunState = mConfig.mDatabaseExists ? SyncRunState::Suspend : SyncRunState::Disable; } changedConfigState(!!syncs.mSyncConfigStore, notifyApp); mNextHeartbeat->updateSPHBStatus(*this); } void UnifiedSync::suspendSync() { assert(syncs.onSyncThread()); if (!mSync) return; changeState(UNLOADING_SYNC, false, false, true); mSync.reset(); } void UnifiedSync::resumeSync(std::function&& completion) { assert(syncs.onSyncThread()); syncs.enableSyncByBackupId_inThread(mConfig.mBackupId, true, std::move(completion), ""); } bool UnifiedSync::shouldHaveDatabase() const { return syncs.mClient.dbaccess && !mConfig.isExternal(); } SyncError UnifiedSync::changeConfigLocalRoot(const LocalPath& newPath) { if (mSync != nullptr) { LOG_err << "Calling changeConfigLocalRoot on a enabled sync. Disable it first"; return UNKNOWN_ERROR; } if (!mConfig.isGoodPathForExternalBackup(newPath)) return BACKUP_SOURCE_NOT_BELOW_DRIVE; const auto fsfp = syncs.fsaccess->fsFingerprint(newPath); if (!fsfp) { LOG_err << "Unable to get the file system fingerprint for path " << newPath; return FILESYSTEM_ID_UNAVAILABLE; } if (const auto& original_fsfp = mConfig.mFilesystemFingerprint; original_fsfp && !original_fsfp.equivalent(fsfp)) { LOG_err << "Sync root path is a different Filesystem than when the sync was created. " "Original filesystem id: " << original_fsfp.toString() << " Current: " << fsfp.toString(); return LOCAL_FILESYSTEM_MISMATCH; } auto fas = syncs.fsaccess->newfileaccess(); // we do allow, eg. mounting an exFAT drive over an NTFS folder and making a sync at that path const bool reparsePointOkAtRoot = true; if (!fas->fopen(newPath, OPEN_RDONLY, FSLogging::logOnError, nullptr, reparsePointOkAtRoot, true, nullptr) || fas->fsid == UNDEF) { LOG_err << "Could not open sync root folder, could not get its fsid: " << newPath; return UNABLE_TO_RETRIEVE_ROOT_FSID; } // Shortcut, no need to rename the database file if (mConfig.mLocalPath == newPath && mConfig.mLocalPathFsid == fas->fsid) return NO_SYNC_ERROR; const auto oldConfig = mConfig; mConfig.mLocalPath = newPath; mConfig.mLocalPathFsid = fas->fsid; mConfig.mFilesystemFingerprint = fsfp; if (!syncs.commitConfigToDb(mConfig)) { mConfig = oldConfig; LOG_err << "Couldn't commit the configuration into the database, cancelling remote root change"; return SYNC_CONFIG_WRITE_FAILURE; } oldConfig.renameDBToMatchTarget(mConfig, *syncs.fsaccess, syncs.mClient); return NO_SYNC_ERROR; } // walk localpath and return corresponding LocalNode and its parent // localpath must be relative to l or start with the root prefix if l == NULL // localpath must be a full sync path, i.e. start with localroot->localname // NULL: no match, optionally returns residual path LocalNode* Sync::localnodebypath(LocalNode* l, const LocalPath& localpath, LocalNode** parent, LocalPath* outpath, bool #ifndef NDEBUG fromOutsideThreadAlreadyLocked #endif ) { assert(syncs.onSyncThread() || fromOutsideThreadAlreadyLocked); assert(!outpath || outpath->empty()); size_t subpathIndex = 0; if (!l) { // verify matching localroot prefix - this should always succeed for // internal use if (!localroot->localname.isContainingPathOf(localpath, &subpathIndex)) { if (parent) { *parent = NULL; } return NULL; } l = localroot.get(); } if (localpath.empty()) { if (outpath) outpath->clear(); if (parent) *parent = l->parent; return l; } LocalPath component; while (localpath.nextPathComponent(subpathIndex, component)) { if (parent) { *parent = l; } localnode_map::iterator it; if ((it = l->children.find(component)) == l->children.end() && (it = l->schildren.find(component)) == l->schildren.end()) { // no full match: store residual path, return NULL with the // matching component LocalNode in parent if (outpath) { *outpath = std::move(component); auto remainder = localpath.subpathFrom(subpathIndex); if (!remainder.empty()) { outpath->appendWithSeparator(remainder, false); } } return NULL; } l = it->second; } // full match: no residual path, return corresponding LocalNode if (outpath) { outpath->clear(); } return l; } bool Sync::determineCaseInsenstivity(bool secondTry) { assert(mLocalPath == getConfig().mLocalPath); static constexpr auto logPre = "[Sync::determineCaseInsenstivity] "; if (auto caseInsensitive = isCaseInsensitive(mLocalPath, syncs.fsaccess.get()); caseInsensitive != std::nullopt) { return caseInsensitive.value(); } if (secondTry) { // If we didn't figure it out, it may be a read-only empty folder, in which case it's irrelevant whether the fs is case insensitive LOG_debug << logPre << " [secondTry] We could not determine case sensitivity even after attempting " "to create a " "local sync .debris folder. Using platform default"; #if defined(WIN32) || defined(__APPLE__) return true; #else return false; #endif } // we didn't find any files/folders that could be tested for case sensitivity. // so create the debris folder (if we can) and retry LOG_verbose << logPre << "We didn't find files that could be used to test case insensitivity. Create " "debris folder and retry"; createDebrisTmpLockOnce(); LOG_verbose << logPre << "Debris folder created, run second try for determineCaseInsensitivity"; return determineCaseInsenstivity(true); } void Sync::createDebrisTmpLockOnce() { assert(syncs.onSyncThread()); if (!tmpfa) { tmpfa = syncs.fsaccess->newfileaccess(); int i = 3; while (i--) { LocalPath localfilename = localdebris; if (syncs.fsaccess->mkdirlocal(localfilename, true, false)) { LOG_verbose << syncname << "Created local sync debris folder"; } LocalPath tmpname = LocalPath::fromRelativePath("tmp"); localfilename.appendWithSeparator(tmpname, true); if (syncs.fsaccess->mkdirlocal(localfilename, false, false)) { LOG_verbose << syncname << "Created local sync debris tmp folder"; } tmpfaPath = localfilename; // lock it LocalPath lockname = LocalPath::fromRelativePath("lock"); localfilename.appendWithSeparator(lockname, true); if (tmpfa->fopen(localfilename, OPEN_WRONLY, FSLogging::logOnError)) { LOG_verbose << syncname << "Locked local sync debris tmp lock file"; break; } } // if we failed to create the tmp dir three times in a row, fall // back to the sync's root if (i < 0) { tmpfa.reset(); tmpfaPath = getConfig().mLocalPath; } threadSafeState->setSyncTmpFolder(tmpfaPath); } } /* StallInfoMaps BEGIN */ void SyncStallInfo::StallInfoMaps::moveFromKeepingProgress(SyncStallInfo::StallInfoMaps& source) { cloud = std::move(source.cloud); local = std::move(source.local); noProgress = source.noProgress; noProgressCount = source.noProgressCount; } SyncStallInfo::StallInfoMaps& SyncStallInfo::StallInfoMaps::operator=(SyncStallInfo::StallInfoMaps&& other) noexcept { if (this != &other) { moveFromKeepingProgress(other); } return *this; } bool SyncStallInfo::StallInfoMaps::hasProgressLack() const { return noProgressCount > MIN_NOPROGRESS_COUNT_FOR_LACK_OF_PROGRESS; } bool SyncStallInfo::StallInfoMaps::empty() const { return cloud.empty() && local.empty(); } size_t SyncStallInfo::StallInfoMaps::size() const { return cloud.size() + local.size(); } size_t SyncStallInfo::StallInfoMaps::reportableSize() const { if (hasProgressLack()) { return size(); } size_t totalReportableSize = 0; for (auto& cloudStallEntry: cloud) { if (cloudStallEntry.second.alertUserImmediately) { ++totalReportableSize; } } for (auto& localStallEntry: local) { if (localStallEntry.second.alertUserImmediately) { ++totalReportableSize; } } return totalReportableSize; } void SyncStallInfo::StallInfoMaps::updateNoProgress() { if (noProgress && noProgressCount < MAX_NOPROGRESS_COUNT) { ++noProgressCount; } } void SyncStallInfo::StallInfoMaps::setNoProgress() { assert((noProgress || noProgressCount == 0) && "noProgressCount is not zero when setting progress"); noProgress = true; } void SyncStallInfo::StallInfoMaps::resetNoProgress() { noProgress = false; noProgressCount = 0; } void SyncStallInfo::StallInfoMaps::clearStalls() { cloud.clear(); local.clear(); } /* StallInfoMaps END */ /* SyncStallInfo BEGIN */ bool SyncStallInfo::empty() const { for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; if (!syncStallInfoMap.empty()) { return false; } } return true; } bool SyncStallInfo::waitingCloud(handle backupId, const string& mapKeyPath, SyncStallEntry&& e) { auto& syncStallInfoMap = syncStallInfoMaps[backupId]; for (auto i = syncStallInfoMap.cloud.begin(); i != syncStallInfoMap.cloud.end(); ) { // No need to add a new entry as we've already reported some parent. if (IsContainingCloudPathOf(i->first, mapKeyPath) && e.reason == i->second.reason) return false; // Remove entries that are below cloudPath1. if (IsContainingCloudPathOf(mapKeyPath, i->first) && e.reason == i->second.reason) { i = syncStallInfoMap.cloud.erase(i); continue; } // Check the next entry. ++i; } // Add a new entry. syncStallInfoMap.cloud.emplace(mapKeyPath, std::move(e)); return true; } bool SyncStallInfo::waitingLocal(handle backupId, const LocalPath& mapKeyPath, SyncStallEntry&& e) { auto& syncStallInfoMap = syncStallInfoMaps[backupId]; for (auto i = syncStallInfoMap.local.begin(); i != syncStallInfoMap.local.end(); ) { if (i->first.isContainingPathOf(mapKeyPath) && e.reason == i->second.reason) return false; if (mapKeyPath.isContainingPathOf(i->first) && e.reason == i->second.reason) { i = syncStallInfoMap.local.erase(i); continue; } ++i; } syncStallInfoMap.local.emplace(mapKeyPath, std::move(e)); return true; } bool SyncStallInfo::isSyncStalled(handle backupId) const { return syncStallInfoMaps.find(backupId) != syncStallInfoMaps.end(); } bool SyncStallInfo::hasImmediateStallReason() const { for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; for (auto& i : syncStallInfoMap.cloud) { if (i.second.alertUserImmediately) { return true; } } for (auto& i : syncStallInfoMap.local) { if (i.second.alertUserImmediately) { return true; } } } return false; } bool SyncStallInfo::hasProgressLackStall() const { for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; if (syncStallInfoMap.hasProgressLack()) { return true; } } return false; } size_t SyncStallInfo::size() const { size_t stallInfoSize = 0; for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; stallInfoSize += syncStallInfoMap.size(); } return stallInfoSize; } size_t SyncStallInfo::reportableSize() const { size_t stallInfoReportableSize = 0; for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; stallInfoReportableSize += syncStallInfoMap.reportableSize(); } return stallInfoReportableSize; } void SyncStallInfo::updateNoProgress() { for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; syncStallInfoMap.updateNoProgress(); } } void SyncStallInfo::setNoProgress() { for (auto& syncStallInfoMapPair : syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; syncStallInfoMap.setNoProgress(); } } void SyncStallInfo::moveFromButKeepCounters(SyncStallInfo& source) { for (auto sourceSyncStallInfoMapIt = source.syncStallInfoMaps.begin(); sourceSyncStallInfoMapIt != source.syncStallInfoMaps.end(); ) { auto&& [id, sourceSyncStallInfoMap] = *sourceSyncStallInfoMapIt; if (sourceSyncStallInfoMap.empty()) { // if there are no stalls, this key is obsolete: remove entry in the source map and update iterator sourceSyncStallInfoMapIt = source.syncStallInfoMaps.erase(sourceSyncStallInfoMapIt); } else { // Add or update the key by moving the source maps (counters will be kept in the source maps) syncStallInfoMaps[id] = std::move(sourceSyncStallInfoMap); ++sourceSyncStallInfoMapIt; } } } void SyncStallInfo::clearObsoleteKeys(SyncStallInfo& source) { // Clear obsolete keys for (auto syncStallInfoMapIt = syncStallInfoMaps.begin(); syncStallInfoMapIt != syncStallInfoMaps.end(); ) { if (source.syncStallInfoMaps.find(syncStallInfoMapIt->first) == source.syncStallInfoMaps.end()) { syncStallInfoMapIt = syncStallInfoMaps.erase(syncStallInfoMapIt); } else { ++syncStallInfoMapIt; } } } void SyncStallInfo::moveFromButKeepCountersAndClearObsoleteKeys(SyncStallInfo& source) { moveFromButKeepCounters(source); clearObsoleteKeys(source); } #ifndef NDEBUG void SyncStallInfo::debug() const { LOG_debug << "[SyncStallInfo] Num SyncIDs = " << syncStallInfoMaps.size() << ""; for (const auto& syncStallInfoMapPair : syncStallInfoMaps) { const auto& syncStallInfoMap = syncStallInfoMapPair.second; LOG_debug << "[SyncID: " << syncStallInfoMapPair.first << "]"; LOG_debug << "noProgress: " << syncStallInfoMap.noProgress << ", noProgressCount: " << syncStallInfoMap.noProgressCount << " [HasProgressLack: " << std::string(syncStallInfoMap.hasProgressLack() ? "true" : "false") << "]"; LOG_debug << "Num cloud stalls: " << syncStallInfoMap.cloud.size(); for (const auto& [stallPath, stallEntry] : syncStallInfoMap.cloud) { LOG_debug << " Cloud stall reason: " << syncWaitReasonDebugString(stallEntry.reason) << " [path = '" << stallPath << "'] [immediate = " << stallEntry.alertUserImmediately << "]"; } LOG_debug << "Num local stalls: " << syncStallInfoMap.local.size(); for (const auto& [stallPath, stallEntry] : syncStallInfoMap.local) { LOG_debug << " Local stall reason: " << syncWaitReasonDebugString(stallEntry.reason) << " [path = '" << stallPath << "'] [immediate = " << stallEntry.alertUserImmediately << "]"; } } } #endif /* SyncStallInfo END */ struct Sync::ProgressingMonitor { bool resolved = false; Sync& sync; SyncFlags& sf; SyncRow& sr; SyncPath& sp; ProgressingMonitor(Sync& s, SyncRow& row, SyncPath& fullPath) : sync(s), sf(*s.syncs.mSyncFlags), sr(row), sp(fullPath) {} void waitingCloud(const string& mapKeyPath, SyncStallEntry&& e) { assert(sync.syncs.onSyncThread()); // the caller has a path in the cloud that an operation is in progress for, or can't be dealt with yet. // update our list of subtree roots containing such paths resolved = true; if (sf.stall.empty()) { SYNCS_verbose_timed << sync.syncname << "First sync node cloud-waiting: " << int(e.reason) << " " << sync.logTriplet(sr, sp); } sf.stall.waitingCloud(sync.getConfig().mBackupId, mapKeyPath, std::move(e)); } void waitingLocal(const LocalPath& mapKeyPath, SyncStallEntry&& e) { assert(sync.syncs.onSyncThread()); // the caller has a local path that an operation is in progress for, or can't be dealt with yet. // update our list of subtree roots containing such paths resolved = true; if (sf.stall.empty()) { SYNCS_verbose_timed << sync.syncname << "First sync node local-waiting: " << int(e.reason) << " " << sync.logTriplet(sr, sp); } sf.stall.waitingLocal(sync.getConfig().mBackupId, mapKeyPath, std::move(e)); } void noResult() { // call this if we are still waiting but for something certain to complete // and for which we don't need to report a path resolved = true; } // For brevity in programming, if none of the above occurred, // the destructor records that we are progressing (ie, not stalled). ~ProgressingMonitor() { assert(sync.syncs.onSyncThread()); if (!resolved) { auto syncStallInfoMapPair = sf.stall.syncStallInfoMaps.find(sync.getConfig().mBackupId); if (syncStallInfoMapPair != sf.stall.syncStallInfoMaps.end()) { auto& syncStallInfoMap = syncStallInfoMapPair->second; syncStallInfoMap.resetNoProgress(); } } } }; bool Sync::checkSpecialFile(SyncRow& child, SyncRow& parent, SyncPath& path) { // Convenience. auto* f = child.fsNode; // Child doesn't have a local presence. if (!f) return false; // Assume child isn't a special file. auto message = ""; auto problem = PathProblem::NoProblem; // Is this child a special file? switch (f->type) { case TYPE_NESTED_MOUNT: message = "nested mount"; problem = PathProblem::DetectedNestedMount; break; case TYPE_SPECIAL: message = "special file"; problem = PathProblem::DetectedSpecialFile; break; case TYPE_SYMLINK: message = "symbolic link"; problem = PathProblem::DetectedSymlink; break; default: break; } // Child isn't a special file. if (problem == PathProblem::NoProblem) return false; // Check if this child's excluded. { // Convenience. auto& p = parent; auto* s = child.syncNode; if (s && s->exclusionState() == ES_EXCLUDED) return false; if (p.syncNode && p.exclusionState(*f) == ES_EXCLUDED) return false; } // Child's not excluded so emit a stall. ProgressingMonitor monitor(*this, child, path); monitor.waitingLocal(path.localPath, SyncStallEntry(SyncWaitReason::FileIssue, true, false, {}, {}, {path.localPath, problem}, {})); // Leave a trail for debuggers. LOG_warn << syncname << "Checked path is a " << message << ", blocked: " << path.localPath; // Let caller know we've encountered a special file. return true; } bool Sync::checkLocalPathForMovesRenames(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult, bool belowRemovedCloudNode) { // We have detected that this LocalNode might be a move/rename target (the moved-to location). // Ie, there is a new/different FSItem in this row. // This function detects whether this is in fact a move or not, and takes care of performing the corresponding cloud move // If we do determine it's a move, then we perform the corresponding move in the cloud. // Of course that's not instant, so we need to wait until the move completes (or fails) // In order to keep track of that, we put a shared_ptr to the move-tracking object into this LocalNode's rare fields. // The client thread actually performing the move will update flags in that object, or it might release its shared_ptr to the move object. // In the meantime this thread can continue recursing and iterate over the tree multiple times until the move is resolved. // We don't recurse below the moved-from or moved-to node in the meantime though as that would cause incorrect decisions to be made. // (well we do but in a very limited capacity, only checking if the cloud side has new nodes to detect crossed-over moves) // // If the move/rename is successful, then we will set the syncedNodeHandle and syncedFsid for this syncNode. // That captures that the row has been synced, and if the Node and FSNode are still in place, that completes the operation // (well, the old syncNode representing the source location will need to be removed, that won't have any fields preventing that anymore) // However if other changes occurred in the meantime, such as the local item being renamed or moved again, // while this operation was in progress, then the actions taken here will set it up to be in a situation // equal to how it would have been if we completed this operation first, so that the fields are set up for // the new change to be detected and acted on in turn. // // In the original conception of the algoritm, we would have waited for resolve_rowMatched to detect a fully synced row, // however for the case described above, it was insufficient because with another move/rename starting, the // FSNode or cloudNode would not be present at the end of our initial operation, and so the syncedFsid and // syncedNodeHandle would not be set, and so we could not get into a synced state or indeed detect the subsequent // move/rename properly. However we still keep resolve_rowMatched as a backstop to deal with // cases where rows become synced independently, such as via the actions of the user themselves, // perhaps while a stall is going on. // // We do briefly have two LocalNodes for a the single moved file/folder while the move goes on. // That's convenient algorithmicaly for tracking the move, and also it's not safe to delete the old node early // as it might have come from a parent folder, which in data structures in the recursion stack are referring to. // If the move/rename fails, it's likely because the move is no longer appropriate, eg new parent folder // is missing. In that case, we clean up the data structure and let the algorithm make a new choice. assert(syncs.onSyncThread()); // New or different FSNode at this row. Check if this node is where a filesystem item moved to. if (row.fsNode->type == TYPE_DONOTSYNC) { return false; } if (row.syncNode) { if (row.syncNode->exclusionState() == ES_EXCLUDED) { return rowResult = false, false; } } else if (parentRow.syncNode && parentRow.exclusionState(*row.fsNode) == ES_EXCLUDED) { return rowResult = false, false; } // Convenience. using MovePending = LocalNode::RareFields::MovePending; // Valid only if: // - We were part of a move that was pending due to unstable files. // - Those files have now become stable. // // If this pointer is valid, it means that the source has stabilized // and that we don't need to check it again. shared_ptr pendingTo; if (auto* s = row.syncNode) { // Do w have any rare fields? if (s->hasRare()) { // Move is pending? if (auto& movePendingTo = s->rare().movePendingTo) { // If checkIfFileIsChanging returns nullopt it means that mFileChangingCheckState is // not initialized, so it's stable and we can procceed with sync auto waitforupdateOpt = checkIfFileIsChanging(*row.fsNode, movePendingTo->sourcePath); // Check if the source/target has stabilized. if (waitforupdateOpt && *waitforupdateOpt) { ProgressingMonitor monitor(*this, row, fullPath); // Let the engine know why we're not making any progress. monitor.waitingLocal(movePendingTo->sourcePath, SyncStallEntry( SyncWaitReason::FileIssue, false, false, {}, {}, {movePendingTo->sourcePath, PathProblem::FileChangingFrequently}, {})); // Source and/or target is still unstable. return rowResult = false, true; } // Source and target have become stable. pendingTo = std::move(movePendingTo); } // Move is (was) in progress? if (auto& moveToHere = s->rare().moveToHere) { if (moveToHere->failed) { // Move's failed. Try again. moveToHere->syncCodeProcessedResult = true; moveToHere.reset(); s->updateMoveInvolvement(); } else { if (moveToHere->succeeded && s->rare().moveFromHere.get() == moveToHere.get()) { // case insensitive rename is complete s->rare().moveFromHere.reset(); s->rare().moveToHere.reset(); s->updateMoveInvolvement(); row.suppressRecursion = true; rowResult = false; // pass through to resolve_rowMatched on this pass, if appropriate row.syncNode->setSyncAgain(false, true, false); return false; } // Move's in progress. // // Revisit when the move is complete. // In the mean time, don't recurse below this node. row.suppressRecursion = true; rowResult = false; // When false, we can visit resolve_rowMatched(...). return !moveToHere->succeeded; } } // Unlink in progress? if (!s->rare().unlinkHere.expired()) { // Don't recurse into our children. row.suppressRecursion = true; // Row isn't synced. rowResult = false; // Move isn't complete. return true; } } } // we already checked fsid differs before calling // This line can be useful to uncomment for debugging, but it does add a lot to the log. //SYNC_verbose << "Is this a local move destination, by fsid " << toHandle(row.fsNode->fsid) << " at " << logTriplet(row, fullPath); // find where this fsid used to be, and the corresponding cloud Node is the one to move (provided it hasn't also moved) // Note that it's possible that there are multiple LocalNodes with this synced fsid, due to chained moves for example. // We want the one that does have the corresponding synced Node still present on the cloud side. // also possible: was the file overwritten by moving an existing file over it? LocalNode* sourceSyncNodeExcludedByFingerprintDuringPutnodes{nullptr}; const NodeMatchByFSIDAttributes fsNodeAttributes{row.fsNode->type, fsfp(), cloudRootOwningUser, row.fsNode->fingerprint, FileFingerprint{}}; const auto [foundExclusionUnknown, sourceSyncNode] = syncs.findLocalNodeBySyncedFsid( row.fsNode->fsid, fsNodeAttributes, fullPath.localPath, nullptr, [&sourceSyncNodeExcludedByFingerprintDuringPutnodes](LocalNode* matchedNodeByFsid) { sourceSyncNodeExcludedByFingerprintDuringPutnodes = matchedNodeByFsid; }); if (sourceSyncNodeExcludedByFingerprintDuringPutnodes) { ProgressingMonitor monitor(*this, row, fullPath); if (auto upload = std::dynamic_pointer_cast( sourceSyncNodeExcludedByFingerprintDuringPutnodes->transferSP); upload && upload->upsyncStarted) { // If the putnodes request has started, we need to wait. LOG_debug << "Potential move-source has outstanding putnodes: " << logTriplet(row, fullPath); // Only emit a stall for this case if: // - A sync controller has been injected into the engine. // - The reference to that controller is still "live." if (syncs.hasSyncController()) { // Signal a stall that observers can easily detect. monitor.waitingLocal( fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {}, {}, {sourceSyncNodeExcludedByFingerprintDuringPutnodes->getLocalPath(), PathProblem::PutnodeCompletionPending}, {fullPath.localPath, PathProblem::NoProblem})); } } else { LOG_warn << "Source sync node matched by FSID has been detected as excluded by " "fingerprint while having a putnodes operation ongoing, but the upload is " "not found or putnodes has not started"; assert(false && "Source sync node matched by FSID excluded by fingerprint during " "putnodes operation ongoing is not meeting the requirements"); } // Make sure we visit the source again. sourceSyncNodeExcludedByFingerprintDuringPutnodes->setSyncAgain(false, true, false); // We can't move just yet. rowResult = false; return true; } if (foundExclusionUnknown) { // this may occur during eg. the first pass of the tree after loading from Suspended state // and the corresponding node is later in the tree // on the next pass we should have resolved all the exclusion states, so delay this decision until then LOG_debug << syncname << "Move detected by fsid but the fsid's exclusion state is not determined yet. Destination here at " << logTriplet(row, fullPath); // Attempt the move later once exclusions have propagatged through the tree. parentRow.syncNode->setCheckMovesAgain(false, true, false); return rowResult = false, true; } if (sourceSyncNode) { // We've found a node associated with the local file's FSID. assert(parentRow.syncNode); ProgressingMonitor monitor(*this, row, fullPath); // Are we moving an ignore file? if (row.isIgnoreFile() || sourceSyncNode->isIgnoreFile()) { // Then it's not subject to move processing. return false; } // Is the move target excluded? if (parentRow.exclusionState(*row.fsNode) != ES_INCLUDED) { // Then don't perform the move. return false; } auto markSiblingSourceRow = [&sourceSyncNode = std::as_const(*sourceSyncNode), &syncNode = std::as_const(*parentRow.syncNode), &rowSiblings = row.rowSiblings]() { if (!rowSiblings) return false; if (&sourceSyncNode != &syncNode) return false; for (auto& sibling: *rowSiblings) { if (sibling.syncNode == &sourceSyncNode) { sibling.itemProcessed = true; sibling.syncNode->setSyncAgain(true, false, false); return true; } } return false; }; if (!row.syncNode) { if (!makeSyncNode_fromFS(row, parentRow, fullPath, false)) { // if it failed, it already set up a waitingLocal with PathProblem::CannotFingerprintFile row.suppressRecursion = true; return rowResult = false, true; } assert(row.syncNode); } row.syncNode->namesSynchronized = sourceSyncNode->namesSynchronized; row.syncNode->setCheckMovesAgain(true, false, false); // Is the source's exclusion state well-defined? if (sourceSyncNode->exclusionState() == ES_UNKNOWN) { // Let the engine know why we can't perform the move. monitor.waitingLocal(sourceSyncNode->getLocalPath(), SyncStallEntry( SyncWaitReason::FileIssue, false, false, {}, {}, {sourceSyncNode->getLocalPath(), PathProblem::IgnoreRulesUnknown}, {})); // In some cases the move source may be below the target. // // This flag is necessary so that we continue to descend down // from the move target to the source such that we recompute // the source's exclusion state. // // TODO: Should we only set this flag if the source is below // the target? row.recurseBelowRemovedFsNode = true; row.suppressRecursion = true; // Attempt the move later. return rowResult = false, true; } // Sanity. assert(sourceSyncNode->exclusionState() == ES_INCLUDED); // Check if the move source is still present and has the same // FSID as the target. If it does, we've encountered a hard link // and need to stall. // // If we don't stall, we'll trigger an infinite rename loop. { auto sourcePath = sourceSyncNode->getLocalPath(); auto targetPath = fullPath.localPath; auto sourceFSID = syncs.fsaccess->fsidOf(sourcePath, false, false, FSLogging::logExceptFileNotFound); // skipcasecheck == false here so we only get the fsid for an exact name match auto targetFSID = syncs.fsaccess->fsidOf(targetPath, false, false, FSLogging::logExceptFileNotFound); // recheck this node again to be 100% confident there are two FSNodes with the same fsid if (sourcePath == targetPath) { // if we run the pre-rework sync code against the same database, // sometimes we end up with duplicate LocalNodes that then make it seem // that we have hard links. LOG_warn << "Possible duplicate LocalNode at " << sourceSyncNode->debugGetParentList() << " vs " << row.syncNode->debugGetParentList(); return rowResult = false, true; } else if (sourceFSID != UNDEF && targetFSID != UNDEF && sourceFSID == targetFSID) { assert(targetFSID == row.fsNode->fsid); // Let the user know why we can't perform the move. // Actually we shouldn't even think this is a move since // it's just due to duplicate fsids. Just report these // two files as hard-links of the same file monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FileIssue, true, false, {}, {}, {sourceSyncNode->getLocalPath(), PathProblem::DetectedHardLink}, {fullPath.localPath, PathProblem::DetectedHardLink})); // Cancel any pending upload work (including MAC computation) // associated with this LocalNode so we don't leave transfers // in-flight while the hard-link stall is active. if (row.syncNode) { if (row.syncNode->transferSP) { row.syncNode->resetTransfer(nullptr); } row.syncNode->resetMacComputationIfAny(); } // Don't try and synchronize our associate. markSiblingSourceRow(); // Don't descend below this node. row.suppressRecursion = true; // Attempt the move later. return rowResult = false, true; } } // Is there something in the way at the move destination? string nameOverwritten; if (row.cloudNode && !row.hasCaseInsensitiveLocalNameChange()) { if (row.cloudNode->handle == sourceSyncNode->syncedCloudNodeHandle) { // The user or someone/something else already performed the corresponding move // just let the syncItem() notice the local and cloud match now SYNC_verbose << syncname << "Detected local move that is already performed remotely, at " << logTriplet(row, fullPath); // and also let the source LocalNode be deleted now sourceSyncNode->setSyncedNodeHandle(NodeHandle()); sourceSyncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNode->localname, nullptr); sourceSyncNode->sync->statecacheadd(sourceSyncNode); // not synced and no move needed return rowResult = false, false; } SYNC_verbose << syncname << "Move detected by fsid " << toHandle(row.fsNode->fsid) << " but something else with that name (" << row.cloudNode->name << ") is already here in the cloud. Type: " << row.cloudNode->type << " new path: " << fullPath.localPath << " old localnode: " << sourceSyncNode->getLocalPath() << logTriplet(row, fullPath); // but, is it ok to overwrite that thing? If that's what // happened locally to a synced file, and the cloud item was // also synced and is still there, then it's legit // overwriting a folder like that is not possible so far as // I know Note that the original algorithm would overwrite a // file or folder, moving the old one to cloud debris // Assume the overwrite is legitimate. PathProblem problem = PathProblem::NoProblem; // Does the overwrite appear legitimate? if ((row.syncNode->syncedCloudNodeHandle != row.cloudNode->handle) && !isBackup()) problem = PathProblem::DifferentFileOrFolderIsAlreadyPresent; // Has the engine completed all pending scans? if (problem == PathProblem::NoProblem && !mScanningWasComplete) problem = PathProblem::WaitingForScanningToComplete; // Is the file on disk visible elsewhere? const auto [foundOtherButExclusionUnknown, other] = std::invoke( [&syncs = std::as_const(syncs), &cloudRootOwningUser = std::as_const(cloudRootOwningUser), &fsfp = std::as_const(fsfp()), &syncNode = std::as_const(*row.syncNode), &fullPath = std::as_const(fullPath), problem]() -> std::pair { if (problem == PathProblem::NoProblem && !syncNode.fsidSyncedReused) { const NodeMatchByFSIDAttributes syncNodeAttributes{ syncNode.type, fsfp, cloudRootOwningUser, syncNode.syncedFingerprint, FileFingerprint{}}; return syncs.findLocalNodeByScannedFsid(syncNode.fsid_lastSynced, syncNodeAttributes, fullPath.localPath); } return {false, nullptr}; }); // Then it's probably part of another move. if ((other && other != row.syncNode && other != sourceSyncNode) || foundOtherButExclusionUnknown) { problem = PathProblem::WaitingForAnotherMoveToComplete; } // Is the overwrite legitimate? if (problem != PathProblem::NoProblem) { // Make sure we revisit the source, too. sourceSyncNode->setSyncAgain(false, true, false); // The move source might be below the target. row.recurseBelowRemovedFsNode = true; // If there is a different file or folder already present in the cloud side, let the user decide if (problem == PathProblem::DifferentFileOrFolderIsAlreadyPresent) return resolve_userIntervention(row, fullPath); // Otherwise, let the engine know why we can't proceed. monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, problem})); // Move isn't complete and this row isn't synced. return rowResult = false, true; } // Overwrite is legitimate. SYNC_verbose << syncname << "Move is a legit overwrite of a synced file/folder, so we overwrite that in the cloud also." << logTriplet(row, fullPath); // Capture the cloud node's name for anomaly detection. nameOverwritten = row.cloudNode->name; } // logic to detect files being updated in the local computer moving the original file // to another location as a temporary backup if (!pendingTo && sourceSyncNode->type == FILENODE) { // If checkIfFileIsChanging returns nullopt it means that mFileChangingCheckState is // not initialized, so it's stable and we can procceed with sync auto waitforupdateOpt = checkIfFileIsChanging(*row.fsNode, sourceSyncNode->getLocalPath()); if (waitforupdateOpt && *waitforupdateOpt) { // Make sure we don't process the source until the move is completed. if (!markSiblingSourceRow()) { // Source isn't a sibling so we need to add a marker. pendingTo = std::make_shared(sourceSyncNode->getLocalPath()); row.syncNode->rare().movePendingTo = pendingTo; sourceSyncNode->rare().movePendingFrom = pendingTo; // Make sure we revisit the source. sourceSyncNode->setSyncAgain(true, false, false); } // if we revist here and the file is still the same after enough time, we'll move it monitor.waitingLocal( sourceSyncNode->getLocalPath(), SyncStallEntry( SyncWaitReason::FileIssue, false, false, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath(), PathProblem::FileChangingFrequently}, {fullPath.localPath})); return rowResult = false, true; } } row.suppressRecursion = true; // wait until we have moved the other LocalNodes below this one // we don't want the source LocalNode to be visited until the move completes // because it might see a new file with the same name, and start an // upload attached to that LocalNode (which would create a wrong version chain in the account) // TODO: consider alternative of preventing version on upload completion - probably resulting in much more complicated name matching though markSiblingSourceRow(); // Check if the move source is part of an ongoing download. auto sourceRequirement = Syncs::EXACT_VERSION; if (std::dynamic_pointer_cast(sourceSyncNode->transferSP)) { // Since we were part of an ongoing download, we can infer // that the local move-source must have been considered // synced. If it wasn't, we wouldn't have detected this move // or a stall would've been generated during CSF processing. // // So, it should be safe for us to move the latest version // of the node in the cloud. // // Ideally, we'll be able to continue download processing as // soon as the move has been confirmed during CSF // processing, provided the local move-target matches the // previously synced local move-source. LOG_debug << "Move-source is part of an ongoing download: " << logTriplet(row, fullPath); sourceRequirement = Syncs::LATEST_VERSION; } // Although we have detected a move locally, there's no guarantee the cloud side // is complete, the corresponding parent folders in the cloud may not match yet. // ie, either of sourceCloudNode or targetCloudNode could be null here. // So, we have a heirarchy of statuses: // If any scan flags anywhere are set then we can't be sure a missing fs node isn't a move // After all scanning is done, we need one clean tree traversal with no moves detected // before we can be sure we can remove nodes or upload/download. CloudNode sourceCloudNode, targetCloudNode; string sourceCloudNodePath, targetCloudNodePath; handle sourceNodeUser = UNDEF, targetNodeUser = UNDEF; // Note that we get the EXACT_VERSION, not the latest version of that file. A new file may have been added locally at that location // in the meantime, causing a version chain for that node. But, we need the exact node (and especially so the Filefingerprint matches once the row lines up) bool foundSourceCloudNode = syncs.lookupCloudNode(sourceSyncNode->syncedCloudNodeHandle, sourceCloudNode, &sourceCloudNodePath, nullptr, nullptr, nullptr, nullptr, sourceRequirement, &sourceNodeUser); bool foundTargetCloudNode = syncs.lookupCloudNode(parentRow.syncNode->syncedCloudNodeHandle, targetCloudNode, &targetCloudNodePath, nullptr, nullptr, nullptr, nullptr, Syncs::FOLDER_ONLY, &targetNodeUser); if (foundSourceCloudNode && foundTargetCloudNode) { SYNC_verbose << syncname << "Move detected by fsid " << toHandle(row.fsNode->fsid) << ". Type: " << sourceSyncNode->type << " new path: " << fullPath.localPath << " old localnode: " << sourceSyncNode->getLocalPath() << logTriplet(row, fullPath); if (sourceNodeUser != targetNodeUser) { LOG_debug << syncname << "Move cannot be performed in the cloud, node would be moved to a different user"; // act like we did not find a move. Sync will be by upload new, trash old return rowResult = false, false; } else if (belowRemovedCloudNode) { SYNC_verbose_timed << syncname << "Move destination detected for fsid " << toHandle(row.fsNode->fsid) << " but we are belowRemovedCloudNode, must wait for resolution at: " << fullPath.cloudPath << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath, PathProblem::ParentFolderDoesNotExist}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath})); row.syncNode->setSyncAgain(true, false, false); } else if (sourceSyncNode->parent && !sourceSyncNode->parent->syncedCloudNodeHandle.isUndef() && sourceSyncNode->parent->syncedCloudNodeHandle != sourceCloudNode.parentHandle) { monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, PathProblem::SourceWasMovedElsewhere})); // Don't descend below this node. row.suppressRecursion = true; // Attempt the move later. return rowResult = false, true; } else { // movePtr stays alive until the move completes // if it's all successful, we will detect the completed move in resolve_rowMatches // and the details from this shared_ptr will help move sub-LocalNodes. // In the meantime, the shared_ptr reminds us not to start another move auto movePtr = std::make_shared(); Syncs::QueuedClientFunc simultaneousMoveReplacedNodeToDebris = nullptr; if (row.cloudNode && row.cloudNode->handle != sourceCloudNode.handle) { LOG_debug << syncname << "Moving node to debris for replacement: " << fullPath.cloudPath << logTriplet(row, fullPath); auto deletePtr = std::make_shared(); // do not remember this one, as this is not the main operation this time // we should not adjust syncedFsid on completion of this aspect //sourceSyncNode->rare().removeNodeHere = deletePtr; bool inshareFlag = inshare; auto deleteHandle = row.cloudNode->handle; bool canChangeVault = threadSafeState->mCanChangeVault; simultaneousMoveReplacedNodeToDebris = [deleteHandle, inshareFlag, deletePtr, canChangeVault](MegaClient& mc, TransferDbCommitter&) { if (auto n = mc.nodeByHandle(deleteHandle)) { mc.movetosyncdebris( n.get(), inshareFlag, [deletePtr](NodeHandle, Error e) { // deletePtr must live until the operation is fully complete, // and we get the actionpacket back indicating Nodes are // adjusted already. otherwise, we may see the node still // present, no pending actions, and downsync it if (e) deletePtr->failed = true; else deletePtr->succeeded = true; }, canChangeVault); } }; syncs.queueClient(std::move(simultaneousMoveReplacedNodeToDebris)); // For the normal move case, we would have made this (empty) row.syncNode specifically for the move // But for this case we are reusing this existing LocalNode and it may be a folder with children // Those children should be removed, should this whole operation succeed. Make a list // and remove them if the cloud actions succeed. for (auto& c : row.syncNode->children) { movePtr->priorChildrenToRemove[c.second->localname] = c.second; } } // record details so we can look up the source LocalNode again after the move completes: movePtr->sourceFsfp = syncs.mFingerprintTracker.get(fsfp()); movePtr->sourceFsid = row.fsNode->fsid; movePtr->sourceType = row.fsNode->type; movePtr->sourceFingerprint = row.fsNode->fingerprint; movePtr->sourcePtr = sourceSyncNode; movePtr->movedHandle = sourceCloudNode.handle; string newName = row.fsNode->localname.toName(*syncs.fsaccess); if (newName == sourceCloudNode.name || sourceSyncNode->localname == row.fsNode->localname) { // if it wasn't renamed locally, or matches the target anyway // then don't change the name newName.clear(); } std::function signalMoveBegin; #ifndef NDEBUG { // For purposes of capture. auto sourcePath = sourceSyncNode->getLocalPath(); auto targetPath = row.syncNode->getLocalPath(); signalMoveBegin = [sourcePath, targetPath](MegaClient& client) { client.app->move_begin(sourcePath, targetPath); }; } #endif // ! NDEBUG LOG_debug << syncname << "Sync - detected local rename/move " << sourceSyncNode->getLocalPath() << " -> " << fullPath.localPath; if (sourceCloudNode.parentHandle == targetCloudNode.handle && !newName.empty()) { // send the command to change the node name LOG_debug << syncname << "Renaming node: " << sourceCloudNodePath << " to " << newName << logTriplet(row, fullPath); auto renameHandle = sourceCloudNode.handle; bool canChangeVault = threadSafeState->mCanChangeVault; syncs.queueClient([renameHandle, newName, movePtr, simultaneousMoveReplacedNodeToDebris, signalMoveBegin, canChangeVault](MegaClient& mc, TransferDbCommitter& committer) { if (auto n = mc.nodeByHandle(renameHandle)) { // first move the old thing at the target path to debris. // this should occur in the same batch so it looks simultaneous if (simultaneousMoveReplacedNodeToDebris) { simultaneousMoveReplacedNodeToDebris(mc, committer); } if (signalMoveBegin) signalMoveBegin(mc); if (newName == ".gitignore") { mc.sendevent(99493, "New .gitignore file synced up"); } mc.setattr(n, attr_map('n', newName), [&mc, movePtr, newName](NodeHandle, Error err){ LOG_debug << mc.clientname << "SYNC Rename completed: " << newName << " err:" << err; movePtr->succeeded = !error(err); movePtr->failed = !!error(err); }, canChangeVault); } }); row.syncNode->rare().moveToHere = movePtr; row.syncNode->updateMoveInvolvement(); sourceSyncNode->rare().moveFromHere = movePtr; sourceSyncNode->updateMoveInvolvement(); rowResult = false; return true; } else { // send the command to move the node LOG_debug << syncname << "Moving node: " << sourceCloudNodePath << " into " << targetCloudNodePath << (newName.empty() ? "" : (" as " + newName).c_str()) << logTriplet(row, fullPath); bool canChangeVault = threadSafeState->mCanChangeVault; if (!canChangeVault && sourceSyncNode->sync != this) { // possibly we need to move the source out of a backup in Vault, into a non-Vault sync canChangeVault = sourceSyncNode->sync->threadSafeState->mCanChangeVault; } syncs.queueClient([sourceCloudNode, targetCloudNode, newName, movePtr, simultaneousMoveReplacedNodeToDebris, signalMoveBegin, canChangeVault](MegaClient& mc, TransferDbCommitter& committer) { if (signalMoveBegin) signalMoveBegin(mc); auto fromNode = mc.nodeByHandle(sourceCloudNode.handle); // yes, it must be the exact version (should there be a version chain) auto toNode = mc.nodeByHandle(targetCloudNode.handle); // folders don't have version chains if (fromNode && toNode) { // first move the old thing at the target path to debris. // this should occur in the same batch so it looks simultaneous if (simultaneousMoveReplacedNodeToDebris) { simultaneousMoveReplacedNodeToDebris(mc, committer); } auto err = mc.rename(fromNode, toNode, SYNCDEL_NONE, sourceCloudNode.parentHandle, newName.empty() ? nullptr : newName.c_str(), canChangeVault, [&mc, movePtr](NodeHandle, Error err){ LOG_debug << mc.clientname << "SYNC Move completed. err:" << err; movePtr->succeeded = !error(err); movePtr->failed = !!error(err); }); if (err) { // todo: or should we mark this one as blocked and otherwise continue. const auto fromDisplayPath = fromNode->displaypath(); const auto toDisplayPath = toNode->displaypath(); // err could be EACCESS or ECIRCULAR for example LOG_warn << mc.clientname << "SYNC Rename not permitted due to err " << err << ": " << fromDisplayPath << " to " << toDisplayPath << (newName.empty() ? "" : (" as " + newName).c_str()); movePtr->failed = true; // todo: figure out if the problem could be overcome by copying and later deleting the source // but for now, mark the sync as disabled // todo: work out the right sync error code // todo: find another place to detect this condition? Or, is this something that might happen anyway due to async changes and race conditions, we should be able to reevaluate. //changestate(COULD_NOT_MOVE_CLOUD_NODES, false, true); } } // movePtr.reset(); // kept alive until completion - then the sync code knows it's finished }); // command sent, now we wait for the actinpacket updates, later we will recognise // the row as synced from fsNode, cloudNode and update the syncNode from those row.syncNode->rare().moveToHere = movePtr; row.syncNode->updateMoveInvolvement(); sourceSyncNode->rare().moveFromHere = movePtr; sourceSyncNode->updateMoveInvolvement(); row.suppressRecursion = true; row.syncNode->setSyncAgain(true, true, false); // keep visiting this node rowResult = false; return true; } } } else { if (!foundSourceCloudNode) { // eg. upload in progress for this node, and locally renamed in the meantime. // we can still update the LocalNode, and the uploaded node will be renamed later. if (!foundSourceCloudNode) SYNC_verbose << syncname << "Adjusting LN for local move/rename before cloud node exists." << logTriplet(row, fullPath); if (sourceSyncNode->fsid_lastSynced != row.fsNode->fsid) { LOG_warn << "Moving content from source sync node to target sync node, but " "sourceSyncNode fsid_lastSynced (" << sourceSyncNode->fsid_lastSynced << ") is different from fsNode fsid (" << row.fsNode->fsid << ")"; assert(false && "Moving content from a source sync node whose fsid_lastSynced " "is different from the fsNode fsid"); } // remove fsid (and handle) from source node, so we don't detect // that as a move source anymore sourceSyncNode->moveContentTo(row.syncNode, fullPath.localPath, true); sourceSyncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNode->localname, nullptr); // no longer associted with an fs item sourceSyncNode->sync->statecacheadd(sourceSyncNode); // do not consider this one synced though, or we won't recognize it as a move target when the uploaded node appears //row.syncNode->setSyncedFsid(row.fsNode->fsid, syncs.localnodeBySyncedFsid, row.syncNode->localname, nullptr); // in case of further local moves before upload completes //statecacheadd(row.syncNode); // we know we have orphaned the sourceSyncNode so it can be removed at the first opportunity, no need to wait sourceSyncNode->confirmDeleteCount = 2; sourceSyncNode->certainlyOrphaned = 1; } else { // eg. cloud parent folder not synced yet (maybe Localnode is created, but not handle matched yet) if (!foundTargetCloudNode) SYNC_verbose_timed << syncname << "Target parent cloud node doesn't exist yet" << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath, PathProblem::ParentFolderDoesNotExist}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath})); row.suppressRecursion = true; rowResult = false; return true; } } } return false; } #ifndef NDEBUG bool debug_confirm_getfsid(const LocalPath& p, FileSystemAccess& fsa, handle expectedFsid) { auto fa = fsa.newfileaccess(); LocalPath lp = p; if (fa->fopen(lp, OPEN_RDONLY, FSLogging::logOnError, nullptr, false)) { return fa->fsid == expectedFsid; } else { LOG_warn << "could not get fsid to confirm"; return true; } } #endif bool Sync::checkForCompletedFolderCreateHere(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath, bool& rowResult) { // if this cloud move was a sync decision, don't look to make it locally too if (row.syncNode && row.syncNode->hasRare() && row.syncNode->rare().createFolderHere) { auto& folderCreate = row.syncNode->rare().createFolderHere; if (folderCreate->failed) { SYNC_verbose << syncname << "Cloud folder create here failed, reset for reevaluation" << logTriplet(row, fullPath); folderCreate.reset(); row.syncNode->updateMoveInvolvement(); } else if (folderCreate->succeededHandle.isUndef()) { SYNC_verbose << syncname << "Cloud folder create already issued for this node, waiting for it to complete. " << logTriplet(row, fullPath); rowResult = false; return true; // row processed (no further action) but not synced } else if (row.cloudNode && row.cloudNode->handle == folderCreate->succeededHandle) { SYNC_verbose << syncname << "Cloud folder create completed in expected location, setting synced handle/fsid" << logTriplet(row, fullPath); // we consider this row synced now, as it was intended as a full row // the local node may have moved though, and there may even be a new and different item with this name in this row // but, setting up the row as if it had been synced means we can then calculate the next // action to continue syncing - such as moving this new node to the location/name of the moved/renamed FSNode row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); // we could set row.cloudNode->handle, but then we would not download after move if the file was both moved and updated; row.syncNode->setSyncedFsid(folderCreate->originalFsid, syncs.localnodeBySyncedFsid, row.syncNode->localname, nullptr); // setting the synced fsid enables chained moves row.syncNode->syncedFingerprint = row.cloudNode->fingerprint; folderCreate.reset(); row.syncNode->trimRareFields(); statecacheadd(row.syncNode); rowResult = false; return true; } else if (syncs.triggerHandlesPending()) { SYNC_verbose << syncname << "Cloud folder create completed, cloud node does not match, but there " "are pending trigger handles to process. Skip until next loop." << logTriplet(row, fullPath); rowResult = false; return true; // row processed (no further action) but not synced } else { SYNC_verbose << syncname << "Cloud folder create completed, but cloud node does not match now. " "Reset to reevaluate." << logTriplet(row, fullPath); folderCreate.reset(); row.syncNode->updateMoveInvolvement(); } } rowResult = false; return false; } bool Sync::checkForCompletedCloudMovedToDebris(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath, bool& rowResult) { // if this cloud move was a sync decision, don't look to make it locally too if (row.syncNode && row.syncNode->hasRare() && row.syncNode->rare().removeNodeHere) { auto& ptr = row.syncNode->rare().removeNodeHere; if (ptr->failed) { SYNC_verbose << syncname << "Cloud move to debris here failed, reset for reevaluation" << logTriplet(row, fullPath); ptr.reset(); } else if (!ptr->succeeded) { SYNC_verbose << syncname << "Cloud move to debris already issued for this node, waiting for it to complete. " << logTriplet(row, fullPath); rowResult = false; return true; // row processed (no further action) but not synced } else { SYNC_verbose << syncname << "Cloud move to debris completed in expected location, setting synced handle/fsid" << logTriplet(row, fullPath); // Now that the operation completed, it's appropriate to set synced-ids so we can apply more logic to the updated state row.syncNode->setSyncedNodeHandle(NodeHandle()); // we could set row.cloudNode->handle, but then we would not download after move if the file was both moved and updated; row.syncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, row.syncNode->localname, nullptr); ptr.reset(); row.syncNode->trimRareFields(); statecacheadd(row.syncNode); } } rowResult = false; return false; } bool Sync::isSyncScanning() const { if (!mUnifiedSync.mConfig.mError && localroot->scanRequired()) { SYNC_verbose_timed << syncname << " scan still required for this sync"; return true; } return false; } bool Sync::checkScanningWasComplete() { mScanningWasCompletePreviously = mScanningWasComplete && !syncs.mSyncFlags->isInitialPass.load(); mScanningWasComplete = !isSyncScanning(); return mScanningWasComplete; } void Sync::unsetScanningWasComplete() { mScanningWasComplete = false; } bool Sync::scanningWasComplete() const { return mScanningWasComplete; } bool Sync::checkMovesWereComplete() { mMovesWereComplete = true; if (!mUnifiedSync.mConfig.mError) { if (!mScanningWasCompletePreviously) { SYNC_verbose_timed << syncname << " scan was not complete previously for this sync -> consider might have moves as true"; mMovesWereComplete = false; } else if (localroot->scanRequired()) { SYNC_verbose_timed << syncname << " scan still required for this sync -> consider might have moves as true"; mMovesWereComplete = false; } else if (localroot->mightHaveMoves()) { SYNC_verbose_timed << syncname << " might have pending moves"; mMovesWereComplete = false; } } return mMovesWereComplete; } bool Sync::movesWereComplete() const { return mMovesWereComplete; } bool Sync::processCompletedUploadFromHere(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath, bool& rowResult, shared_ptr upload) { // we already checked that the upload including putnodes completed before calling here. assert(row.syncNode && upload && upload->wasUpsyncCompleted); if (upload->upsyncResultHandle.isUndef()) { assert(upload->upsyncFailed); SYNC_verbose << syncname << "Upload from here failed, reset for reevaluation" << logTriplet(row, fullPath); row.syncNode->bypassThrottlingNextTime(syncs.maxUploadsBeforeThrottle()); } else { assert(!upload->upsyncFailed); // Should we complete the putnodes later? if (syncs.deferPutnodeCompletion(fullPath.localPath)) { // Let debuggers know why we haven't completed the putnodes request. LOG_debug << syncname << "Putnode completion deferred by controller " << fullPath.localPath << logTriplet(row, fullPath); // Don't process this row any further. row.itemProcessed = true; // File isn't synchronized. rowResult = false; // Emit a special stall for observers to detect. ProgressingMonitor monitor(*this, row, fullPath); // Convenience. auto problem = PathProblem::PutnodeCompletionDeferredByController; monitor.waitingLocal(fullPath.localPath, SyncStallEntry(SyncWaitReason::UploadIssue, false, false, {NodeHandle(), fullPath.cloudPath, problem}, {}, {fullPath.localPath, problem}, {})); // The upload is still in progress. return true; } // connect up the original cloud-sync-fs triplet, so that we can detect any // further moves that happened in the meantime. row.syncNode->increaseUploadCounter(); SYNC_verbose << syncname << "Upload from here completed, considering this file synced to original: " << toHandle(upload->sourceFsid) << " [Num uploads: " << row.syncNode->uploadCounter() << "]" << logTriplet(row, fullPath); row.syncNode->setSyncedFsid(upload->sourceFsid, syncs.localnodeBySyncedFsid, row.syncNode->localname, row.syncNode->cloneShortname()); row.syncNode->syncedFingerprint = *upload; row.syncNode->setSyncedNodeHandle(upload->upsyncResultHandle); statecacheadd(row.syncNode); // Record mtime-only operation to throttle future MAC computations if (upload->attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly) { row.syncNode->recordMtimeOnlyOperation(); } // void going into syncItem() in case we only just got the cloud Node // and we are iterating that very directory already, in which case we won't have // the cloud side node, and we would create an extra upload row.itemProcessed = true; } // either way, we reset and revisit. Following the signature pattern for similar functions row.syncNode->transferSP.reset(); rowResult = false; return true; } bool Sync::checkForCompletedCloudMoveToHere(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath, bool& rowResult) { // if this cloud move was a sync decision, don't look to make it locally too if (row.syncNode && row.syncNode->hasRare() && row.syncNode->rare().moveToHere && !(mCaseInsensitive && row.hasCaseInsensitiveCloudNameChange())) { auto& moveHerePtr = row.syncNode->rare().moveToHere; if (moveHerePtr->failed) { SYNC_verbose << syncname << "Cloud move to here failed, reset for reevaluation" << logTriplet(row, fullPath); moveHerePtr.reset(); row.syncNode->updateMoveInvolvement(); } else if (!moveHerePtr->succeeded) { SYNC_verbose << syncname << "Cloud move already issued for this node, waiting for it to complete. " << logTriplet(row, fullPath); rowResult = false; return true; // row processed (no further action) but not synced } else if (row.cloudNode && row.cloudNode->handle == moveHerePtr->movedHandle) { SYNC_verbose << syncname << "Cloud move completed, setting synced handle/fsid" << logTriplet(row, fullPath); syncs.setSyncedFsidReused(*moveHerePtr->sourceFsfp, moveHerePtr->sourceFsid); // prevent reusing that one as move source for chained move cases LOG_debug << syncname << "Looking up move source by fsid " << toHandle(moveHerePtr->sourceFsid); LocalNode* sourceSyncNode = syncs.findMoveFromLocalNode(moveHerePtr); if (sourceSyncNode == row.syncNode) { LOG_debug << syncname << "Resolving sync cloud case-only rename from : " << sourceSyncNode->getCloudPath(true) << ", here! " << logTriplet(row, fullPath); sourceSyncNode->rare().moveFromHere.reset(); } else if (sourceSyncNode && sourceSyncNode->rareRO().moveFromHere == moveHerePtr) { LOG_debug << syncname << "Resolving sync cloud move/rename from : " << sourceSyncNode->getCloudPath(true) << ", here! " << logTriplet(row, fullPath); assert(sourceSyncNode == moveHerePtr->sourcePtr); row.syncNode->setSyncedNodeHandle(sourceSyncNode->syncedCloudNodeHandle); // we could set row.cloudNode->handle, but then we would not download after move if the file was both moved and updated; row.syncNode->setSyncedFsid(moveHerePtr->sourceFsid, syncs.localnodeBySyncedFsid, row.syncNode->localname, nullptr); // setting the synced fsid enables chained moves // Assign the same syncedFingerprint as the move-from node // That way, if that row had some other sync aspect needed // (such as upload from an edit-then-move case, and the sync performs the move first) // then we will detect that same operation at this new row. row.syncNode->syncedFingerprint = sourceSyncNode->syncedFingerprint; // remove fsid (and handle) from source node, so we don't detect // that as a move source anymore const auto sourceSyncNodeTransferIsUpload = sourceSyncNode->transferSP && (dynamic_cast(sourceSyncNode->transferSP.get()) != nullptr); // Detect the rename+create-new race: // - moveHerePtr->sourceFsid is the fsid of the file that was renamed // - sourceSyncNode->fsid_asScanned is the fsid currently present at the *old* // location If they differ, the source LocalNode now represents a different file // (likely the newly created one), so we must NOT migrate/rename the in-flight // upload transfer. const auto sourceSyncNodeTransferBelongsToDifferentFsid = sourceSyncNodeTransferIsUpload && moveHerePtr->sourceFsid != UNDEF && sourceSyncNode->fsid_asScanned != UNDEF && moveHerePtr->sourceFsid != sourceSyncNode->fsid_asScanned; // If a file was renamed and a new file with the old name was created immediately // after, the transfer in-flight can belong to the newly created file. // In that case, migrating the transfer to the target node (and updating its path) // would transiently rename it, confusing clients/listeners and potentially causing // duplicate uploads. In this scenario, keep the transfer on the source node. sourceSyncNode->syncedFingerprint = FileFingerprint(); sourceSyncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNode->localname, sourceSyncNode->cloneShortname()); sourceSyncNode->setSyncedNodeHandle(NodeHandle()); sourceSyncNode->sync->statecacheadd(sourceSyncNode); // Move all the LocalNodes under the source node to the new location // We can't move the source node itself as the recursive callers may be using it sourceSyncNode->moveContentTo(row.syncNode, fullPath.localPath, true, !sourceSyncNodeTransferBelongsToDifferentFsid); if (sourceSyncNodeTransferBelongsToDifferentFsid) { LOG_debug << "[Sync::checkForCompletedCloudMoveToHere] SourceSyncNode had a " "transfer inflight and the scanned fsid and synced fsid were " "different: keeping the transfer on the sourceSyncNode " "(it could belong to a new file with same name)"; // The transfer should not have been migrated nor renamed. assert(sourceSyncNode->transferSP); assert(sourceSyncNode->transferSP->getLocalname() == sourceSyncNode->getLocalPath()); if (row.syncNode->transferSP) { LOG_debug << "[Sync::checkForCompletedCloudMoveToHere] Unexpected transferSP " "on destination node in this scenario. Triplet after move: " << logTriplet(row, fullPath); } } else if (row.syncNode->transferSP) { LOG_debug << "[Sync::checkForCompletedCloudMoveToHere] row.syncNode has now a " "transferSP! The sourceSyncNode had that transferSP but the " "sourceSyncNode->fsid_lastSynced was not different from " "sourceSyncNode->fsid_asScanned! Check this scenario! Triplet " "after move: " << logTriplet(row, fullPath); } row.syncNode->setScanAgain(false, true, true, 0); sourceSyncNode->setScanAgain(true, false, false, 0); sourceSyncNode->rare().moveFromHere.reset(); sourceSyncNode->trimRareFields(); sourceSyncNode->updateMoveInvolvement(); // If this node was repurposed for the move, rather than the normal case of creating a fresh one, we remove the old content if it was a folder // We have to do this after all processing of sourceSyncNode, in case the source was (through multiple operations) one of the subnodes about to be removed. // TODO: however, there is a risk of name collisions - probably we should use a multimap for LocalNode::children. for (auto& oldc : moveHerePtr->priorChildrenToRemove) { for (auto& c : row.syncNode->children) { if (c.first == oldc.first && c.second == oldc.second) { delete c.second; // removes itself from the parent map break; } } } } else if (sourceSyncNode) { // just alert us to this an double check the case in the debugger // resetting the movePtrs should cause re-evaluation LOG_debug << syncname << "We found the source move node, but the source movePtr is no longer there." << sourceSyncNode->getCloudPath(true) << logTriplet(row, fullPath); assert(false); } else { // just alert us to this an double check the case in the debugger // resetting the movePtrs should cause re-evaluation LOG_debug << syncname << "Could not find move source node." << logTriplet(row, fullPath); assert(false); } // regardless, make sure we don't get stuck moveHerePtr->syncCodeProcessedResult = true; moveHerePtr.reset(); row.syncNode->trimRareFields(); row.syncNode->updateMoveInvolvement(); statecacheadd(row.syncNode); rowResult = false; return true; } else if (syncs.triggerHandlesPending()) { SYNC_verbose << syncname << "Cloud move completed, cloud node does not match, but there " "are pending trigger handles to process. Skip until next loop." << logTriplet(row, fullPath); rowResult = false; return true; // row processed (no further action) but not synced } else { SYNC_verbose << syncname << "Cloud move completed, but cloud node does not match now. Reset to reevaluate." << logTriplet(row, fullPath); moveHerePtr->syncCodeProcessedResult = true; moveHerePtr.reset(); row.syncNode->updateMoveInvolvement(); } } rowResult = false; return false; } bool Sync::checkCloudPathForMovesRenames(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool& rowResult, bool belowRemovedFsNode) { // We have detected that this LocalNode might be a move/rename target (the moved-to location). // Ie, there is a new/different CloudNode in this row. // This function detects whether this is in fact a move or not, and takes care of performing the corresponding local move/rename // If we do determine it's a move/rename, then we perform the corresponding action in the local FS. // We perform the local action synchronously so we don't need to track it with shared_ptrs etc. // Should it fail for some reason though, we report that in the stall tracking system and continue. // In the meantime this thread can continue recursing and iterate over the tree multiple times until the move is resolved. // We don't recurse below the moved-from or moved-to node in the meantime though as that would cause incorrect decisions to be made. // (well we do but in a very limited capacity, only checking if the local side has new nodes to detect crossed-over moves) // // If the move/rename is successful, we set the syncedNodeHandle and syncedFsid for this node appropriately, // so that even in the presence of other actions happening in this sync row, such as another move/rename occurring // that we see on the next pass over the tree, we can still know that this row was synced, and we have // sufficient state recorded in order to be able to detect that subsequent move/rename, and take further actions // to propagate that to the other side. // // We also have the backstop of resolve_rowMatched which will recognize rows that have gotten into a synced state // perhaps via the actions of the user themselves, rather than the efforts of the sync (eg, when resolving stall cases) // That function will update our data structures, moving the sub-LocalNodes from the // moved-from LocalNode to the moved-to LocalNode. Later the moved-from LocalNode will be removed as it has no FSNode or CloudNode. // // We do briefly have two LocalNodes for a the single moved file/folder while the move goes on. // That's convenient algorithmicaly for tracking the move, and also it's not safe to delete the old node early // as it might have come from a parent folder, which in data structures in the recursion stack are referring to. // If the move/rename fails, it's likely because the move is no longer appropriate, eg new parent folder // is missing. In that case, we clean up the data structure and let the algorithm make a new choice. assert(syncs.onSyncThread()); // this one is a bit too verbose for large down-syncs //SYNC_verbose << syncname << "checking localnodes for synced cloud handle " << row.cloudNode->handle; if (!row.cloudNode) // row.cloudNode is expected to exist { LOG_err << "[Sync::checkCloudPathForMovesRenames] row.CloudNode is nullptr and it shouldn't be!!!"; assert (false); return false; } // Are we moving an ignore file? if (row.isIgnoreFile()) { // Then it's not subject to the usual move procesing. return false; } ProgressingMonitor monitor(*this, row, fullPath); unique_ptr childrenToDeleteOnFunctionExit; // find out where the node was when synced, and is now. // If they are the same, both pointer are set to that one. LocalNode* sourceSyncNodeOriginal = nullptr; LocalNode* sourceSyncNode = nullptr; bool unsureDueToIncompleteScanning = false; bool unsureDueToUnknownExclusionMoveSource = false; if (syncs.findLocalNodeByNodeHandle(row.cloudNode->handle, sourceSyncNodeOriginal, sourceSyncNode, unsureDueToIncompleteScanning, unsureDueToUnknownExclusionMoveSource)) { // If we reach this point, sourceSyncNodeOriginal and sourceSyncNode should be valid pointers, as their validity is checked by findLocalNodeByHandle before returning true assert(sourceSyncNode && sourceSyncNodeOriginal); // Check if the source file/folder is still present if (sourceSyncNodeOriginal != sourceSyncNode) { if (row.syncNode && sourceSyncNode->getLocalPath() == row.syncNode->getLocalPath()) { SYNC_verbose << "Detected cloud move that is already performed remotely, at " << logTriplet(row, fullPath); // let the normal syncItem() matching resolve these completed moves to the same location auto oldFsid = sourceSyncNodeOriginal->fsid_lastSynced; // and also let the source LocalNode be deleted now (note it could have been in a different sync) sourceSyncNodeOriginal->setSyncedNodeHandle(NodeHandle()); sourceSyncNodeOriginal->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNodeOriginal->localname, nullptr); sourceSyncNodeOriginal->sync->statecacheadd(sourceSyncNodeOriginal); // since we caused this move, set the synced handle and fsid. // this will allow us to detect chained moves row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->setSyncedFsid(oldFsid, syncs.localnodeBySyncedFsid, row.syncNode->localname, nullptr); statecacheadd(row.syncNode); rowResult = false; return true; } else { monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNodeOriginal->syncedCloudNodeHandle, sourceSyncNodeOriginal->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNodeOriginal->getLocalPath(), PathProblem::SourceWasMovedElsewhere}, {fullPath.localPath})); if (parentRow.syncNode) parentRow.syncNode->setSyncAgain(false, true, false); rowResult = false; return true; } } LocalPath sourcePath = sourceSyncNode->getLocalPath(); if (sourceSyncNode == row.syncNode) { if (mCaseInsensitive && row.hasCaseInsensitiveCloudNameChange()) { LOG_debug << "Move is the same node but is also a case insensitive name change: " << sourcePath; } else { return false; } } else if (sourcePath == fullPath.localPath) { // This case was seen in a log, possibly due to duplicate LocalNodes. // We don't want to move the target out of the way to the .debris, then find it's not present for move/rename LOG_debug << "Move would be to self: " << sourcePath; return false; } // Are we moving an ignore file? if (sourceSyncNode->isIgnoreFile()) { // Then it's not subject to the usual move procesing. return false; } // Is the move target excluded? if (parentRow.exclusionState(*row.cloudNode) != ES_INCLUDED) { // Then don't perform the move. return false; } // It's a move or rename if (isBackup()) { LOG_warn << "Cloud node move detected and this is a BACKUP! Triplet: " << logTriplet(row, fullPath); assert(false && "Cloud node modifications should not happen for a backup!"); rowResult = false; // Let's solve the issue in the syncItem step return true; } assert(parentRow.syncNode); if (parentRow.syncNode) parentRow.syncNode->setCheckMovesAgain(false, true, false); if (row.syncNode) row.syncNode->setCheckMovesAgain(true, false, false); // Is the source's exclusion state well defined? if (sourceSyncNode->exclusionState() == ES_UNKNOWN) { // Let the engine know why we couldn't process this move. monitor.waitingLocal(sourceSyncNode->getLocalPath(), SyncStallEntry( SyncWaitReason::FileIssue, false, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(false), PathProblem::IgnoreRulesUnknown}, {}, {sourceSyncNode->getLocalPath(), PathProblem::IgnoreRulesUnknown}, {})); row.recurseBelowRemovedCloudNode = true; row.suppressRecursion = true; // Complete the move later. return rowResult = false, true; } // Convenience. auto markSiblingSourceRow = [&]() { if (!row.rowSiblings) return; if (sourceSyncNode->parent != parentRow.syncNode) return; for (auto& sibling : *row.rowSiblings) { if (sibling.syncNode == sourceSyncNode) { sibling.itemProcessed = true; return; } } }; // True if the move-target exists and we're free to "overwrite" it. auto overwrite = false; bool caseInsensitiveRename = mCaseInsensitive && row.syncNode && row.syncNode->syncedCloudNodeHandle == row.cloudNode->handle && row.hasCaseInsensitiveCloudNameChange(); // is there already something else at the target location though? // and skipping the case of a case insensitive rename if (row.fsNode && !caseInsensitiveRename) { // todo: should we check if the node that is already here is in fact a match? in which case we should allow progressing to resolve_rowMatched SYNC_verbose << syncname << "Move detected by nodehandle, but something else with that name is already here locally. Type: " << row.fsNode->type << " moved node: " << fullPath.cloudPath << " old parent correspondence: " << (sourceSyncNode->parent ? sourceSyncNode->parent->getLocalPath().toPath(false) : "") << logTriplet(row, fullPath); // Assume we've encountered an illegitimate overwrite. PathProblem problem = PathProblem::DifferentFileOrFolderIsAlreadyPresent; // Does the file on disk match what this node was previously synced against? if (row.syncNode && (row.syncNode->type == row.fsNode->type && row.syncNode->fsid_lastSynced == row.fsNode->fsid)) problem = PathProblem::NoProblem; // Does the node exist elsewhere in the tree? while (problem == PathProblem::NoProblem) { CloudNode node; auto active = false; auto excluded = false; auto trash = false; auto found = syncs.lookupCloudNode(row.syncNode->syncedCloudNodeHandle, node, nullptr, &trash, &active, &excluded, nullptr, Syncs::EXACT_VERSION); if (!found || !active || excluded || trash) break; if (node.parentHandle != row.cloudNode->parentHandle || node.name != row.cloudNode->name) problem = PathProblem::WaitingForAnotherMoveToComplete; break; } if (problem != PathProblem::NoProblem) { parentRow.syncNode->setCheckMovesAgain(false, true, false); // If there is a different file or folder already present in the local side, let the user decide if (problem == PathProblem::DifferentFileOrFolderIsAlreadyPresent && !isBackup()) return resolve_userIntervention(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath, problem}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath})); // Move isn't complete and this row isn't synced. return rowResult = false, true; } SYNC_verbose << syncname << "Move is a legit overwrite of a synced file, so we overwrite that locally too." << logTriplet(row, fullPath); overwrite = true; } if (!sourceSyncNode->moveApplyingToLocal && !belowRemovedFsNode && parentRow.cloudNode) { LOG_debug << syncname << "Move detected by nodehandle. Type: " << sourceSyncNode->type << " moved node: " << fullPath.cloudPath << " old parent correspondence: " << (sourceSyncNode->parent ? sourceSyncNode->parent->getLocalPath().toPath(false) : "") << logTriplet(row, fullPath); LOG_debug << "Sync - detected remote move " << fullPath.cloudPath << " from corresponding " << (sourceSyncNode->parent ? sourceSyncNode->parent->getLocalPath().toPath(false) : "") << " to " << parentRow.cloudNode->name; sourceSyncNode->moveApplyingToLocal = true; } assert(!isBackup()); // we don't want the source LocalNode to be visited until after the move completes, and we revisit with rescanned folder data // because it might see a new file with the same name, and start a download, keeping the row instead of removing it markSiblingSourceRow(); if (belowRemovedFsNode) { SYNC_verbose_timed << syncname << "Move destination detected for node " << row.cloudNode->handle << " but we are belowRemovedFsNode, must wait for resolution at: " << logTriplet(row, fullPath);; monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, PathProblem::ParentFolderDoesNotExist})); if (parentRow.syncNode) parentRow.syncNode->setSyncAgain(false, true, false); rowResult = false; return true; } // check filesystem is not changing fsids as a result of rename // // Only meaningful on filesystems with stable FSIDs. // // We've observed strange behavior when running on FAT filesystems under Windows. // There, moving a directory (or file) to another parent will cause that directory // (or file) to gain a new FSID. assert(!fsstableids || debug_confirm_getfsid(sourcePath, *syncs.fsaccess, sourceSyncNode->fsid_lastSynced)); if (overwrite) { // If overwrite is true, row.syncNode must exist at this point assert(row.syncNode); SYNC_verbose << "Move-target exists and must be moved to local debris: " << fullPath.localPath; if (!movetolocaldebris(fullPath.localPath)) { // Couldn't move the target to local debris. LOG_err << "Couldn't move move-target to local debris: " << fullPath.localPath; monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::CannotPerformDeletion, false, true, {}, {}, {fullPath.localPath, PathProblem::MoveToDebrisFolderFailed}, {})); // Don't recurse as the subtree's fubar. row.suppressRecursion = true; // Move hasn't completed. sourceSyncNode->moveAppliedToLocal = false; // Row hasn't been synced. rowResult = false; return true; } LOG_debug << syncname << "Move-target moved to local debris: " << fullPath.localPath; // Explicitly rescan the parent. // // This is necessary even when we're not operating in periodic // scan mode as we can't rely on the system delivering // filesystem events to us in a timely manner. parentRow.syncNode->setScanAgain(false, true, false, 0); // Therefore there is nothing in the local subfolder anymore // And we should delete the localnodes corresponding to the items we moved to debris. // BUT what if the move is coming from inside that folder ?!! // Therefore move them to an unattached locallnode which will delete them on function exit //row.syncNode->deleteChildren(); childrenToDeleteOnFunctionExit.reset(new LocalNode(this)); while (!row.syncNode->children.empty()) { auto* child = row.syncNode->children.begin()->second; child->setnameparent(childrenToDeleteOnFunctionExit.get(), child->localname, child->cloneShortname()); } } if (caseInsensitiveRename) { auto oldPath = fullPath.localPath; fullPath.localPath = parentRow.syncNode->getLocalPath(); fullPath.localPath.appendWithSeparator(LocalPath::fromRelativeName(row.cloudNode->name, *syncs.fsaccess, mFilesystemType), true); LOG_debug << "Executing case-only local rename: " << oldPath << " to " << fullPath.localPath << " (also this path should match: " << sourcePath << ")"; } if (syncs.fsaccess->renamelocal(sourcePath, fullPath.localPath)) { // todo: move anything at this path to sync debris first? Old algo didn't though // todo: additional consideration: what if there is something here, and it should be moved/renamed to elsewhere in the sync (not the debris) first? // todo: additional consideration: what if things to be renamed/moved form a cycle? // check filesystem is not changing fsids as a result of rename // // Only meaningful on filesystems with stable FSIDs. // // We've observed strange behavior when running on FAT filesystems under Windows. // There, moving a directory (or file) to another parent will cause that directory // (or file) to gain a new FSID. assert(overwrite || !fsstableids || debug_confirm_getfsid(fullPath.localPath, *syncs.fsaccess, sourceSyncNode->fsid_lastSynced)); LOG_debug << syncname << "Sync - executed local rename/move " << sourceSyncNode->getLocalPath() << " -> " << fullPath.localPath; if (caseInsensitiveRename) { row.syncNode->setScanAgain(false, true, false, 0); // if caseInsensitiveRename, row.syncNode is a valid pointer } else { if (!row.syncNode) { resolve_makeSyncNode_fromCloud(row, parentRow, fullPath, false); assert(row.syncNode); } row.syncNode->namesSynchronized = sourceSyncNode->namesSynchronized; // remove fsid (and handle) from source node, so we don't detect // that as a move source anymore sourceSyncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNode->localname, nullptr); // shortname will be updated when rescan sourceSyncNode->setSyncedNodeHandle(NodeHandle()); sourceSyncNode->sync->statecacheadd(sourceSyncNode); sourceSyncNode->moveContentTo(row.syncNode, fullPath.localPath, true); sourceSyncNode->moveAppliedToLocal = true; sourceSyncNode->setScanAgain(true, false, false, 0); row.syncNode->setScanAgain(true, true, true, 0); // scan parent to see this moved fs item, also scan subtree to see if anything new is in there to overcome race conditions with fs notifications from the prior fs subtree paths // Mark this row as synced immediately, to cover the case where the user // moves the item back immediately, perhaps they made a mistake, // and we don't get a chance to recognise the row as synced in a future pass // Becuase in that case, we would end up with a download instead of a chained move if (auto fsNode = FSNode::fromPath(*syncs.fsaccess, fullPath.localPath, false, FSLogging::logOnError)) { // Make this new fsNode part of our sync data structure parentRow.fsAddedSiblings.emplace_back(std::move(*fsNode)); row.fsNode = &parentRow.fsAddedSiblings.back(); row.syncNode->slocalname = row.fsNode->cloneShortname(); row.syncNode->setSyncedFsid(row.fsNode->fsid, syncs.localnodeBySyncedFsid, row.fsNode->localname, row.fsNode->cloneShortname()); row.syncNode->syncedFingerprint = row.fsNode->fingerprint; row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); statecacheadd(row.syncNode); } } rowResult = false; return true; } else if (syncs.fsaccess->transient_error) { LOG_warn << "transient error moving folder: " << sourcePath << logTriplet(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, PathProblem::FilesystemErrorDuringOperation})); row.suppressRecursion = true; sourceSyncNode->moveApplyingToLocal = false; rowResult = false; return true; } else if (syncs.fsaccess->target_name_too_long) { LOG_warn << "Unable to move folder as the move target's name is too long: " << sourcePath << logTriplet(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, true, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, PathProblem::NameTooLongForFilesystem})); row.suppressRecursion = true; sourceSyncNode->moveApplyingToLocal = false; rowResult = false; return true; } else { SYNC_verbose << "Move to here delayed since local parent doesn't exist yet: " << sourcePath << logTriplet(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNode->syncedCloudNodeHandle, sourceSyncNode->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNode->getLocalPath()}, {fullPath.localPath, PathProblem::ParentFolderDoesNotExist})); rowResult = false; return true; } } else if (unsureDueToIncompleteScanning) { monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {sourceSyncNodeOriginal->syncedCloudNodeHandle, sourceSyncNodeOriginal->getCloudPath(true)}, {NodeHandle(), fullPath.cloudPath}, {sourceSyncNodeOriginal->getLocalPath()}, {fullPath.localPath, PathProblem::WaitingForScanningToComplete})); rowResult = false; return true; } else if (unsureDueToUnknownExclusionMoveSource) { SYNC_verbose << "Move to here delayed since unsureDueToUnknownExclusionMoveSource at: " << logTriplet(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {NodeHandle(), string(), PathProblem::IgnoreRulesUnknown}, {row.cloudNode->handle, fullPath.cloudPath}, {LocalPath(), PathProblem::IgnoreRulesUnknown}, {fullPath.localPath})); rowResult = false; return true; } else { monitor.noResult(); } return false; } // Just mark the relative LocalNodes as needing to be rescanned. dstime Sync::procscanq() { assert(syncs.onSyncThread()); assert(dirnotify.get()); NotificationDeque& queue = dirnotify->fsEventq; if (queue.empty()) { return NEVER; } LOG_verbose << syncname << "Marking sync tree with filesystem notifications: " << queue.size(); Notification notification; dstime delay = NEVER; while (queue.popFront(notification)) { lastFSNotificationTime = syncs.waiter->ds; // Skip invalidated notifications. if (notification.invalidated()) { LOG_debug << syncname << "Notification skipped: " << notification.path; continue; } // Skip notifications from this sync's debris folder. if (notification.fromDebris(*this)) { LOG_debug << syncname << "Debris notification skipped: " << notification.path; continue; } LocalPath remainder; LocalNode* nearest = nullptr; LocalNode* node = notification.localnode; // Notify the node or its parent LocalNode* match = localnodebypath(node, notification.path, &nearest, &remainder, false); // Check it's not below an excluded path if (nearest && !remainder.empty()) { if (nearest->type == TYPE_DONOTSYNC) { SYNC_verbose << "Ignoring notification under do-not-sync node: " << node->getLocalPath() << string(LocalPath::localPathSeparator_utf8, 1) << notification.path; continue; } LocalPath firstComponent; size_t index = 0; if (remainder.nextPathComponent(index, firstComponent)) { // firstComponent is a folder if has next path component, otherwise it is unknown auto type = remainder.hasNextPathComponent(index) ? FOLDERNODE : TYPE_UNKNOWN; if (!(firstComponent == IGNORE_FILE_NAME) && (isDoNotSyncFileName(firstComponent.toPath(false)) || ES_EXCLUDED == nearest->exclusionState(firstComponent, type, 0))) { // no need to rescan anything when the change was in an excluded folder SYNC_verbose << "Ignoring notification under excluded/do-not-sync node:" << node->getLocalPath() << string(1, LocalPath::localPathSeparator_utf8) << notification.path;; continue; } } } bool scanDescendants = false; // figure out which node we are going to scan. 'nearest' will be assigned the one (or it is already) if (match) { if (match->type == FILENODE) { // the node was a file, so it's always the parent that needs scanning so we see the // updated file metadata in the directory entry. Additionally, re-fingerprint to detect data change match->recomputeFingerprint = true; nearest = match->parent; if (!nearest) continue; } else { // we found the exact node specified, recursion on request makes sense scanDescendants = notification.scanRequirement == Notification::FOLDER_NEEDS_SCAN_RECURSIVE; // for a folder path, we support either path specified (entries added/removed) // or the parent (eg access changed), depending on flags passed from platform layer nearest = match->parent && notification.scanRequirement == Notification::NEEDS_PARENT_SCAN ? match->parent : match; } } else { // we didn't find the exact path specified. But, if we are only one layer up // and we would have scanned parent anyway (ie, file or NEEDS_PARENT_SCAN), then it's equivalent // if we are higher up the tree than that, it's again the same // basically, scan the folder we can determine this notification is below: nearest. if (nearest && nearest->type == FILENODE) { nearest->recomputeFingerprint = true; nearest = nearest->parent; assert(nearest && nearest->type != FILENODE); } } if (!nearest) { // we didn't find any suitable ancestor within the sync LOG_debug << "Notification had no scannable result:" << node->getLocalPath() << " " << notification.path;; continue; } if (nearest->expectedSelfNotificationCount > 0) { if (nearest->scanDelayUntil >= syncs.waiter->ds) { // self-caused notifications shouldn't cause extra waiting --nearest->expectedSelfNotificationCount; SYNC_verbose << "Skipping self-notification (remaining: " << nearest->expectedSelfNotificationCount << ") at: " << nearest->getLocalPath(); continue; } else { SYNC_verbose << "Expected more self-notifications (" << nearest->expectedSelfNotificationCount << ") but they were late, at: " << nearest->getLocalPath(); nearest->expectedSelfNotificationCount = 0; } } // Let the parent know it needs to perform a scan. //if (nearest->scanAgain < TREE_ACTION_HERE) { SYNC_verbose << "Trigger scan flag by fs notification on " << nearest->getLocalPath() << (scanDescendants ? " (recursive)" : ""); } nearest->setScanAgain(false, true, scanDescendants, SCANNING_DELAY_DS); if (nearest->rareRO().scanBlocked) { // in case permissions changed on a scan-blocked folder // retry straight away, but don't reset the backoff delay nearest->rare().scanBlocked->scanBlockedTimer.set(syncs.waiter->ds); } // How long the caller should wait before syncing. delay = SCANNING_DELAY_DS; } return delay; } bool Sync::movetolocaldebris(const LocalPath& localpath) { assert(syncs.onSyncThread()); assert(!isBackup()); // first make sure the debris folder exists createDebrisTmpLockOnce(); char buf[42]; struct tm tms; string day, localday; struct tm* ptm = m_localtime(m_time(), &tms); // first try a subfolder with only the date (we expect that we may have target filename clashes here) snprintf(buf, sizeof(buf), "%04d-%02d-%02d", ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday); LocalPath targetFolder = localdebris; targetFolder.appendWithSeparator(LocalPath::fromRelativePath(buf), true); bool failedDueToTargetExists = false; if (movetolocaldebrisSubfolder(localpath, targetFolder, false, failedDueToTargetExists)) { return true; } if (!failedDueToTargetExists) return false; // next try a subfolder with additional time and sequence - target filename clashes here should not occur snprintf(strchr(buf, 0), sizeof(buf) - strlen(buf), " %02d.%02d.%02d.", ptm->tm_hour, ptm->tm_min, ptm->tm_sec); string datetime = buf; bool counterReset = false; if (datetime != mLastDailyDateTimeDebrisName) { mLastDailyDateTimeDebrisName = datetime; mLastDailyDateTimeDebrisCounter = 0; counterReset = true; } // initially try wih the same sequence number as last time, to avoid making large numbers of these when possible LocalPath targetFolderWithDate = targetFolder; targetFolder.appendWithSeparator(LocalPath::fromRelativePath( datetime + std::to_string(mLastDailyDateTimeDebrisCounter)), false); if (movetolocaldebrisSubfolder(localpath, targetFolder, counterReset, failedDueToTargetExists)) { return true; } if (!failedDueToTargetExists) return false; if (counterReset) { // no need to try an incremented number if it was a new folder anyway return false; } // if that fails, try with the sequence incremented, that should be a new, empty folder with no filename clash possible ++mLastDailyDateTimeDebrisCounter; targetFolder = targetFolderWithDate; targetFolder.appendWithSeparator(LocalPath::fromRelativePath( datetime + std::to_string(mLastDailyDateTimeDebrisCounter)), true); if (movetolocaldebrisSubfolder(localpath, targetFolder, true, failedDueToTargetExists)) { return true; } return false; } bool Sync::movetolocaldebrisSubfolder(const LocalPath& localpath, const LocalPath& targetFolder, bool logFailReason, bool& failedDueToTargetExists) { failedDueToTargetExists = false; bool createdFolder = false; if (syncs.fsaccess->mkdirlocal(targetFolder, false, false)) { createdFolder = true; } else { if (!syncs.fsaccess->target_exists) { return false; } } LocalPath moveTarget = targetFolder; moveTarget.appendWithSeparator(localpath.leafName(), true); syncs.fsaccess->skip_targetexists_errorreport = !logFailReason; bool success = syncs.fsaccess->renamelocal(localpath, moveTarget, false); syncs.fsaccess->skip_targetexists_errorreport = false; failedDueToTargetExists = !success && syncs.fsaccess->target_exists; if (createdFolder) { if (success) { LOG_verbose << syncname << "Created daily local debris folder: " << targetFolder; } else { // we didn't use the folder anyway, remove to avoid making huge numbers of them syncs.fsaccess->rmdirlocal(targetFolder); } } return success; } UnifiedSync::UnifiedSync(Syncs& s, const SyncConfig& c) : syncs(s), mConfig(c) { mNextHeartbeat.reset(new HeartBeatSyncInfo()); } void Syncs::confirmOrCreateDefaultMegaignore(unique_ptr& resultIfDfc, unique_ptr& resultIfMegaignoreDefault) { resultIfDfc.reset(new DefaultFilterChain(mNewSyncFilterChain)); // However, if .megaignore.default already exists, then use that one of course. // If it doesn't exist yet, write it. auto defaultpath = mClient.dbaccess->rootPath(); defaultpath.appendWithSeparator(LocalPath::fromRelativePath(".megaignore.default"), false); if (!fsaccess->fileExistsAt(defaultpath)) { LOG_info << "Writing .megaignore.default at " << defaultpath; if (!resultIfDfc->create(defaultpath, false, *fsaccess, false)) { LOG_err << "Failed to write .megaignore.default"; } } else { // If we are in transitionToMegaignore, don't load from the default as it will lose // the absolute paths which might be relevant for a particular sync resultIfMegaignoreDefault.reset(new string_vector); auto fa = fsaccess->newfileaccess(false); if (fa->fopen(defaultpath, OPEN_RDONLY, FSLogging::logOnError)) { if (readLines(*fa, *resultIfMegaignoreDefault)) { // Return the text of the file. // FilterChain and FilterChainDefault are very different and conversion between them is not really practical resultIfDfc.reset(); return; } } LOG_err << "Failed to load .megaignore.default, going with default defaults instead"; resultIfMegaignoreDefault.reset(); } } void Syncs::enableSyncByBackupId(handle backupId, bool setOriginalPath, std::function completion, bool completionInClient, const string& logname) { assert(!onSyncThread()); auto clientCompletion = [=](error e, SyncError se, handle) { queueClient([completion, e, se, backupId](MegaClient&, TransferDbCommitter&) { if (completion) completion(e, se, backupId); }); }; queueSync([=]() { enableSyncByBackupId_inThread(backupId, setOriginalPath, completionInClient ? clientCompletion : completion, logname); }, "enableSyncByBackupId"); } void Syncs::enableSyncByBackupId_inThread(handle backupId, bool setOriginalPath, std::function completion, const string& logname, const string& excludedPath) { assert(onSyncThread()); UnifiedSync* usPtr = nullptr; // Make a copy of `mSyncVec` to protect against changes to its structure after unlocking // syncVecMutexLock auto auxSyncVec = getSyncVecCopy(); std::unique_lock syncVecMutexLock(mSyncVecMutex); for (auto& s: auxSyncVec) { if (s->mConfig.mBackupId == backupId) { usPtr = s.get(); } } if (!usPtr) { LOG_debug << "Enablesync could not find sync"; if (completion) completion(API_ENOENT, UNKNOWN_ERROR, backupId); return; } UnifiedSync& us = *usPtr; if (us.mSync) { // already exists if (us.mConfig.mError == NO_SYNC_ERROR) { if (us.mConfig.mRunState == SyncRunState::Run) { // it's already running LOG_debug << "Sync with id " << backupId << " is already running"; } else if (us.mConfig.mRunState == SyncRunState::Suspend) // this actually represents "paused" syncs { LOG_debug << "Sync with id " << backupId << " switched to running"; us.mConfig.mRunState = SyncRunState::Run; mClient.app->syncupdate_stateconfig(us.mConfig); } else { LOG_err << "Sync with id " << backupId << " already exists and should be in SyncRunState::Run(" << static_cast(SyncRunState::Run) << "), however, the actual state is " << static_cast(us.mConfig.mRunState) << " (state will now be set to SyncRunState::Run)"; assert(false && "us.mConfig.mRunState should be SyncRunState::Run but it is not"); us.mConfig.mRunState = SyncRunState::Run; mClient.app->syncupdate_stateconfig(us.mConfig); } if (completion) completion(API_OK, NO_SYNC_ERROR, backupId); return; } // there is a sync error, it should've been reset in Syncs::stopSyncsInErrorState, but next loop hasn't be executed yet (so let's do it now) LOG_warn << "Sync with id " << backupId << " has a sync error (" << us.mConfig.mError << "). It will be reset now"; us.mSync.reset(); } us.mConfig.mError = NO_SYNC_ERROR; us.mConfig.mRunState = SyncRunState::Loading; // Regenerate LN cache if no fingerprint's been assigned. bool resetFingerprint = !us.mConfig.mFilesystemFingerprint; #ifdef __APPLE__ if (!resetFingerprint) { LOG_debug << "turning on reset of filesystem fingerprint on Mac, as they are not consistent there"; // eg. from networked filesystem, qnap shared drive resetFingerprint = true; } #endif if (!resetFingerprint && !us.mConfig.mDatabaseExists) { // It's ok to sync to a new folder (new fs even) at the same path, if we are truly going from scratch // Users may, eg. put a sync to disabled, move the local folder elsewhere, make an empty folder with that same name, restart the sync LOG_debug << "turning on reset of filesystem fingerprint for previously disabled sync (ie, had no database)"; resetFingerprint = true; } if (resetFingerprint) { us.mConfig.mFilesystemFingerprint.reset(); //This will cause the local filesystem fingerprint to be recalculated us.mConfig.mLocalPathFsid = UNDEF; } const auto remoteNodeHandle = us.mConfig.mRemoteNode; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; in this case at`lookupCloudNode` and explicit lock of `nodeTreeMutex` some lines // below, so we need to release mutex before. syncVecMutexLock.unlock(); if (setOriginalPath) { CloudNode cloudNode; string cloudNodePath; const auto lookupCloudNodeSuccess = lookupCloudNode(remoteNodeHandle, cloudNode, &cloudNodePath, nullptr, nullptr, nullptr, nullptr, Syncs::FOLDER_ONLY); syncVecMutexLock.lock(); if (lookupCloudNodeSuccess && us.mConfig.mOriginalPathOfRemoteRootNode != cloudNodePath) { us.mConfig.mOriginalPathOfRemoteRootNode = cloudNodePath; ensureDriveOpenedAndMarkDirty(us.mConfig.mExternalDrivePath); } syncVecMutexLock.unlock(); } error e; { // todo: even better thead safety lock_guard g(mClient.nodeTreeMutex); syncVecMutexLock.lock(); std::tie(e, us.mConfig.mError, us.mConfig.mWarning) = mClient.checkSyncConfig(us.mConfig); us.mConfig.mEnabled = e == API_OK && us.mConfig.mError == NO_SYNC_ERROR; syncVecMutexLock.unlock(); } // Lock `syncVecMutex` again syncVecMutexLock.lock(); if (e) { // error and enable flag were already changed LOG_debug << "Enablesync checks resulted in error: " << e; us.mConfig.mRunState = us.mConfig.mDatabaseExists ? SyncRunState::Suspend : SyncRunState::Disable; us.changedConfigState(true, true); if (completion) completion(e, us.mConfig.mError, backupId); return; } auto auxConfig = us.mConfig; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; in this case at`hasIgnoreFile`, so we need to release mutex before. syncVecMutexLock.unlock(); // Does this sync already contain an ignore file? const auto auxHasIgnoreFileAux = hasIgnoreFile(auxConfig); // Lock `syncVecMutex` again syncVecMutexLock.lock(); if (!auxHasIgnoreFileAux) { // Create a new chain so that we can add custom rules if necessary. unique_ptr resultIfDfc; unique_ptr resultIfMegaignoreDefault; confirmOrCreateDefaultMegaignore(resultIfDfc, resultIfMegaignoreDefault); assert(resultIfDfc || resultIfMegaignoreDefault); bool writeMegaignoreFailed = false; if (resultIfDfc) { // We are using default rules, or legacy rules for this sync // Do we have a custom rule to apply? if (!excludedPath.empty()) { resultIfDfc->excludePath(excludedPath); } // Try and create the missing ignore file. Not synced by default if (!resultIfDfc->create(us.mConfig.mLocalPath, true, *fsaccess, false)) { LOG_debug << "Failed to create ignore file for sync without one at: " << us.mConfig.mLocalPath; writeMegaignoreFailed = true; } } else { // We are copying .megaignore.default but adding one more excluded path into it // Do we have a custom rule to apply? if (!excludedPath.empty()) { string temp = excludedPath; LocalPath::utf8_normalize(&temp); auto targetPath = LocalPath::fromAbsolutePath(std::move(temp)); size_t index; if (us.mConfig.mLocalPath.isContainingPathOf(targetPath, &index)) { // Path exclusions should be relative to the .megaignore file resultIfMegaignoreDefault->push_back("-p:" + targetPath.subpathFrom(index).toPath(false)); } } string wholefile = string("\xEF\xBB\xBF", 3); // utf8-BOM for (auto& line : *resultIfMegaignoreDefault) { #ifdef WIN32 wholefile += line + "\r\n"; #else wholefile += line + "\n"; #endif } auto filePath = us.mConfig.mLocalPath; filePath.appendWithSeparator(IGNORE_FILE_NAME, false); auto fa = fsaccess->newfileaccess(false); writeMegaignoreFailed = true; if (fa->fopen(filePath, OPEN_WRONLY, FSLogging::logOnError)) { if (fa->fwrite((const byte*)wholefile.data(), (unsigned)wholefile.size(), 0)) { writeMegaignoreFailed = false; LOG_debug << "Applied .megaignore from default: " << wholefile; } } } if (writeMegaignoreFailed) { // for backups, it's ok to be backup up read-only folders. // for syncs, we can't sync if we can't bring changes back if (us.mConfig.isBackup()) { LOG_debug << "As it's a Backup, continuing without .megaigore for: " << us.mConfig.mLocalPath; } else { us.mConfig.mError = COULD_NOT_CREATE_IGNORE_FILE; us.mConfig.mEnabled = false; us.mConfig.mRunState = us.mConfig.mDatabaseExists ? SyncRunState::Suspend : SyncRunState::Disable; us.changedConfigState(true, true); if (completion) completion(API_EWRITE, us.mConfig.mError, backupId); return; } } // Engine-generated ignore files should be invisible. if (!writeMegaignoreFailed) { // Generate path to ignore file. auto path = us.mConfig.mLocalPath; path.appendWithSeparator(IGNORE_FILE_NAME, false); // Try and make the ignore file invisible. fsaccess->setFileHidden(path); } } // If we're a backup sync... if (us.mConfig.isBackup()) { LOG_verbose << "Starting Backup Sync " << toHandle(us.mConfig.mBackupId) << "[isExternal = " << us.mConfig.isExternal() << ", RunState = " << (int)us.mConfig.mRunState << "]"; } us.mConfig.mError = NO_SYNC_ERROR; us.mConfig.mEnabled = true; us.mConfig.mRunState = SyncRunState::Loading; us.changedConfigState(true, true); mHeartBeatMonitor->updateOrRegisterSync(us); // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; in this case at`startSync_inThread->Sync::Sync->lookupCloudNode`, so we need to // release mutex before. syncVecMutexLock.unlock(); startSync_inThread(us, completion, logname); // Lock `syncVecMutexLock` again syncVecMutexLock.lock(); us.mNextHeartbeat->updateSPHBStatus(us); } bool Syncs::checkSyncRemoteLocationChange(SyncConfig& config, const bool exists, const std::string& cloudPath) { assert(onSyncThread()); if (!exists) { if (!config.mRemoteNode.isUndef()) { config.mRemoteNode = NodeHandle(); } return false; } if (cloudPath == config.mOriginalPathOfRemoteRootNode) { return false; } LOG_debug << "Sync root path changed! Was: " << config.mOriginalPathOfRemoteRootNode << " now: " << cloudPath; config.mOriginalPathOfRemoteRootNode = cloudPath; return true; } void Syncs::changeSyncRemoteRoot(const handle backupId, std::shared_ptr&& newRootNode, std::function&& completionForClient) { assert(!onSyncThread()); // We need to change to the syncs thread to run the missing validations and commit the change queueSync( [this, backupId, newRootNode = std::move(newRootNode), completionForClientWrapped = wrapToRunInClientThread(std::move(completionForClient), FromAnyThread::yes)]() mutable { changeSyncRemoteRootInThread(backupId, std::move(newRootNode), std::move(completionForClientWrapped)); }, "changeSyncRemoteRoot"); } void Syncs::changeSyncRemoteRootInThread(const handle backupId, std::shared_ptr&& newRootNode, std::function&& completion) { assert(onSyncThread()); // Make a copy of `mSyncVec` to protect against changes to its structure after unlocking // syncVecMutexLock auto auxSyncVec = getSyncVecCopy(); std::unique_lock syncVecMutexLock(mSyncVecMutex); const auto it = std::find_if(std::begin(auxSyncVec), std::end(auxSyncVec), [backupId](const auto& unifSync) { return unifSync && unifSync->mConfig.mBackupId == backupId; }); if (it == std::end(auxSyncVec)) { LOG_err << "There are no syncs with the given backupId"; return completion(API_EARGS, UNKNOWN_ERROR); } const auto& unifSync = (*it); auto& config = unifSync->mConfig; if (config.isBackup()) { LOG_err << "Trying to change remote root of a backup sync. Operation not supported yet"; return completion(API_EARGS, UNKNOWN_ERROR); } const auto newRootNH = newRootNode->nodeHandle(); if (config.mRemoteNode == newRootNH) { LOG_err << "The given node to set as new root is already the root of the sync"; return completion(API_EEXIST, UNKNOWN_ERROR); } const auto& syncs = unifSync->syncs; const auto currentDbPath = config.getSyncDbPath(*syncs.fsaccess, syncs.mClient); const auto currentRootNH = std::exchange(config.mRemoteNode, newRootNH); if (!commitConfigToDb(config)) { config.mRemoteNode = currentRootNH; LOG_err << "Couldn't commit the configuration into the database, cancelling remote root change"; return completion(API_EWRITE, SYNC_CONFIG_WRITE_FAILURE); } const auto renameDbAndNotifyServer = [&config, &unifSync, &syncs, ¤tDbPath, newRootNH, this]() { if (currentDbPath) { // Move db to the new expected location const auto newDbFileName = config.getSyncDbStateCacheName(config.mLocalPathFsid, newRootNH, syncs.mClient.me); std::filesystem::path newDbPath{ syncs.mClient.dbaccess ->databasePath(*syncs.fsaccess, newDbFileName, DbAccess::DB_VERSION) .asPlatformEncoded(false)}; std::filesystem::rename(*currentDbPath, newDbPath); } mHeartBeatMonitor->updateOrRegisterSync(*unifSync); }; if (const bool syncRunning = (unifSync->mSync != nullptr); syncRunning) { unifSync->suspendSync(); renameDbAndNotifyServer(); // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks; in this case at `resumeSync->enableSyncByBackupId_inThread`, so we // need to release mutex before. syncVecMutexLock.unlock(); unifSync->resumeSync( [completion = std::move(completion)](error err, SyncError serr, handle) { if (err) LOG_err << "Error resuming sync after remote root change: " << err << ", Sync err: " << serr; completion(API_OK, NO_SYNC_ERROR); }); } else { renameDbAndNotifyServer(); completion(API_OK, NO_SYNC_ERROR); } } void Syncs::changeSyncLocalRoot(const handle backupId, LocalPath&& newValidLocalRootPath, std::function&& completionForClient) { assert(!onSyncThread()); // We need to change to the syncs thread to run the missing validations and commit the change queueSync( [this, backupId, newPath = std::move(newValidLocalRootPath), completionForClientWrapped = wrapToRunInClientThread(std::move(completionForClient), FromAnyThread::yes)]() mutable { changeSyncLocalRootInThread(backupId, std::move(newPath), std::move(completionForClientWrapped)); }, "changeSyncLocalRoot"); } void Syncs::changeSyncLocalRootInThread(const handle backupId, LocalPath&& newValidLocalRootPath, std::function&& completion) { assert(onSyncThread()); // Make a copy of `mSyncVec` to protect against changes to its structure after unlocking // syncVecMutexLock auto auxSyncVec = getSyncVecCopy(); std::unique_lock syncVecMutexLock(mSyncVecMutex); const auto it = std::find_if(std::begin(auxSyncVec), std::end(auxSyncVec), [backupId](const auto& unifSync) { return unifSync && unifSync->mConfig.mBackupId == backupId; }); if (it == std::end(auxSyncVec)) { LOG_err << "There are no syncs with the given backupId"; return completion(API_EARGS, UNKNOWN_ERROR); } const auto& unifSync = *it; const bool syncWasRunning = unifSync->mSync != nullptr; if (syncWasRunning) unifSync->suspendSync(); const auto exitResumingIfNeeded = [syncWasRunning, completion = std::move(completion), &unifSync](error e, SyncError se) { // `mSyncVecMutex` is unlocked in all places before calling `exitResumingIfNeeded` if (!syncWasRunning) return completion(e, se); unifSync->resumeSync( [completion = std::move(completion), e, se](error err, SyncError serr, handle) { if (err) LOG_err << "Error resuming sync after local root change: " << err << ", Sync err: " << serr; completion(e, se); }); }; if (const auto syncErr = unifSync->changeConfigLocalRoot(newValidLocalRootPath); syncErr != NO_SYNC_ERROR) { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks; in this case at `resumeSync`, so we need to release mutex before. syncVecMutexLock.unlock(); const error apiErr = syncErr == SYNC_CONFIG_WRITE_FAILURE ? API_EWRITE : API_EARGS; return exitResumingIfNeeded(apiErr, syncErr); } mHeartBeatMonitor->updateOrRegisterSync(*unifSync); // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks; in this case at `resumeSync`, so we need to release mutex before. syncVecMutexLock.unlock(); exitResumingIfNeeded(API_OK, NO_SYNC_ERROR); } void Syncs::manageRemoteRootLocationChange(Sync& sync) const { std::unique_lock syncVecMutexLock(mSyncVecMutex); // Currently, we don't support movements for backup roots if (sync.isBackup()) { LOG_err << "Remote root node move/rename is not expected to take place for backup syncs"; assert(false); sync.changestate(SyncError::BACKUP_MODIFIED, false, true, false); return; } // we need to check if the node in its new location is syncable const auto& config = sync.getConfig(); // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; so we need to release mutex before. syncVecMutexLock.unlock(); error e; SyncError syncError; { std::lock_guard g(mClient.nodeTreeMutex); std::tie(e, syncError) = mClient.isnodesyncable(mClient.mNodeManager.getNodeByHandle(config.mRemoteNode), true); } // Lock `syncVecMutexLock` again syncVecMutexLock.lock(); if (e) { LOG_debug << "Node is not syncable after moving to a new location: " << syncError; sync.changestate(syncError, false, true, true); } else { // Notify the change in the root path mClient.app->syncupdate_remote_root_changed(config); } } void Syncs::startSync_inThread(UnifiedSync& us, std::function completion, const string& logname) { assert(onSyncThread()); std::unique_lock syncVecMutexLock(mSyncVecMutex); assert(!us.mSync); auto fail = [&us, &completion](Error e, SyncError se) -> void { us.changeState(se, false, true, true); us.mSync.reset(); LOG_debug << "Final error for sync start: " << e; if (completion) completion(e, us.mConfig.mError, us.mConfig.mBackupId); }; #ifdef __ANDROID__ if (!us.mConfig.mLocalPath.isURI()) { LOG_warn << "startSync_inThread: sync path is not from URIPath type"; return fail(API_EFAILED, UNABLE_TO_ADD_WATCH); } #endif us.mConfig.mRunState = SyncRunState::Loading; us.changedConfigState(false, true); SyncError constructResult = NO_SYNC_ERROR; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; in this case at `Sync::Sync->lookupCloudNode`, so we need to // release mutex before. syncVecMutexLock.unlock(); auto auxSync = std::make_unique(us, logname, constructResult); // Lock `syncVecMutexLock` again syncVecMutexLock.lock(); us.mSync = std::move(auxSync); if (constructResult != NO_SYNC_ERROR) { LOG_err << "Sync creation failed, syncerr: " << constructResult; return fail(API_EFAILED, constructResult); } debugLogHeapUsage(); us.mSync->purgeStaleDownloads(); // this was already set in the Sync constructor assert(us.mConfig.mRunState == SyncRunState::Run); us.changedConfigState(false, true); // Make sure we could open the state cache database. if (us.mSync->shouldHaveDatabase() && !us.mSync->statecachetable) { LOG_err << "Unable to open state cache database."; return fail(API_EFAILED, UNABLE_TO_OPEN_DATABASE); } else if (us.mSync->localroot->watch(us.mConfig.getLocalPath(), UNDEF) != WR_SUCCESS) { LOG_err << "Unable to add a watch for the sync root: " << us.mConfig.getLocalPath(); return fail(API_EFAILED, UNABLE_TO_ADD_WATCH); } ensureDriveOpenedAndMarkDirty(us.mConfig.mExternalDrivePath); mSyncFlags->isInitialPass.store(true); if (completion) completion(API_OK, us.mConfig.mError, us.mConfig.mBackupId); } void UnifiedSync::changedConfigState(bool save, bool notifyApp) { assert(syncs.onSyncThread()); if (mConfig.stateFieldsChanged()) { LOG_debug << "Sync " << toHandle(mConfig.mBackupId) << " now in runState: " << int(mConfig.mRunState) << " enabled: " << mConfig.mEnabled << " error: " << mConfig.mError << " (isBackup: " << mConfig.isBackup() << ")"; if (save) { syncs.ensureDriveOpenedAndMarkDirty(mConfig.mExternalDrivePath); } if (notifyApp && !mConfig.mRemovingSyncBySds) { assert(syncs.onSyncThread()); syncs.mClient.app->syncupdate_stateconfig(mConfig); } } } Syncs::Syncs(MegaClient& mc): waiter(new WAIT_CLASS), mClient(mc), fsaccess(std::make_unique()), mSyncFlags(new SyncFlags), mScanService(new ScanService()), mThrottlingManager(std::make_shared()) { assert(mThrottlingManager && "Throttling manager should be valid in the Syncs constructor!"); fsaccess->initFilesystemNotificationSystem(); mHeartBeatMonitor.reset(new BackupMonitor(*this)); syncThread = std::thread( [this]() { syncLoop(); }); } Syncs::~Syncs() { assert(!onSyncThread()); // null function is the signal to end the thread syncThreadActions.pushBack(QueuedSyncFunc(nullptr, "")); waiter->notify(); if (syncThread.joinable()) syncThread.join(); } void Syncs::syncRun(std::function f, const string& actionName) { assert(!onSyncThread()); std::promise synchronous; syncThreadActions.pushBack(QueuedSyncFunc([&]() { f(); synchronous.set_value(true); }, actionName)); mSyncFlags->earlyRecurseExitRequested = true; waiter->notify(); synchronous.get_future().get(); } void Syncs::queueSync(std::function&& f, const string& actionName) { assert(!onSyncThread()); syncThreadActions.pushBack(QueuedSyncFunc(std::move(f), actionName)); mSyncFlags->earlyRecurseExitRequested = true; waiter->notify(); } void Syncs::queueClient(std::function&& f, [[maybe_unused]] bool fromAnyThread) { assert(onSyncThread() || fromAnyThread); clientThreadActions.pushBack(std::move(f)); mClient.waiter->notify(); } void Syncs::getSyncProblems(std::function)> completion, bool completionInClient) { using MC = MegaClient; using DBTC = TransferDbCommitter; if (completionInClient) { completion = [this, completion](unique_ptr problems) { SyncProblems* rawPtr = problems.release(); queueClient([completion, rawPtr](MC&, DBTC&) mutable { completion(unique_ptr(rawPtr)); }); }; } queueSync( [this, completion]() mutable { unique_ptr problems(new SyncProblems); getSyncProblems_inThread(*problems); completion(std::move(problems)); }, "getSyncProblems"); } void Syncs::getSyncProblems_inThread(SyncProblems& problems) { assert(onSyncThread()); problems.mStallsDetected = stallsDetected(problems.mStalls); problems.mConflictsDetected = conflictsDetected(problems.mConflictsMap); // Try to present just one item for a move/rename, instead of two. // We may have generated two items, one for the source node // and one for the target node. Most paths will match between the two. // If for some reason we only know one side of the move/rename, we will keep that item. auto combinePathProblems = [](const SyncStallEntry& soEntry, SyncStallEntry& siEntry) { // We always keep the PathProblem present on any of the two stalls // If we have two PathProblems, one of them should be caused by an unresolved area (waiting for moves, etc), so we keep the more meaningful one (which could be solvable) if (soEntry.localPath1.problem != PathProblem::NoProblem && (siEntry.localPath1.problem == PathProblem::NoProblem || siEntry.localPath1.problem == PathProblem::DestinationPathInUnresolvedArea)) { siEntry.localPath1.problem = soEntry.localPath1.problem; } if (soEntry.localPath2.problem != PathProblem::NoProblem && (siEntry.localPath2.problem == PathProblem::NoProblem || siEntry.localPath2.problem == PathProblem::DestinationPathInUnresolvedArea)) { siEntry.localPath2.problem = soEntry.localPath2.problem; } if (soEntry.cloudPath1.problem != PathProblem::NoProblem && (siEntry.cloudPath1.problem == PathProblem::NoProblem || siEntry.cloudPath1.problem == PathProblem::DestinationPathInUnresolvedArea)) { siEntry.cloudPath1.problem = soEntry.cloudPath1.problem; } if (soEntry.cloudPath2.problem != PathProblem::NoProblem && (siEntry.cloudPath2.problem == PathProblem::NoProblem || siEntry.cloudPath2.problem == PathProblem::DestinationPathInUnresolvedArea)) { siEntry.cloudPath2.problem = soEntry.cloudPath2.problem; } }; for (auto& syncStallInfoMapsPair : problems.mStalls.syncStallInfoMaps) { auto& syncStallInfoMaps = syncStallInfoMapsPair.second; for (auto si = syncStallInfoMaps.local.begin(); si != syncStallInfoMaps.local.end(); ++si) { if (si->second.reason == SyncWaitReason::MoveOrRenameCannotOccur) { auto so = syncStallInfoMaps.local.find(si->second.localPath2.localPath); if (so != syncStallInfoMaps.local.end()) { if (so != si && so->second.reason == SyncWaitReason::MoveOrRenameCannotOccur && so->second.localPath1.localPath == si->second.localPath1.localPath && so->second.localPath2.localPath == si->second.localPath2.localPath && so->second.cloudPath1.cloudPath == si->second.cloudPath1.cloudPath) { combinePathProblems(so->second, si->second); if (si->second.cloudPath2.cloudPath.empty()) { // if we know the destination in one, make sure we keep it si->second.cloudPath2.cloudPath = so->second.cloudPath2.cloudPath; } // other iterators are not invalidated in std::map syncStallInfoMaps.local.erase(so); } } } } for (auto si = syncStallInfoMaps.cloud.begin(); si != syncStallInfoMaps.cloud.end(); ++si) { if (si->second.reason == SyncWaitReason::MoveOrRenameCannotOccur) { auto so = syncStallInfoMaps.cloud.find(si->second.cloudPath2.cloudPath); if (so != syncStallInfoMaps.cloud.end()) { if (so != si && so->second.reason == SyncWaitReason::MoveOrRenameCannotOccur && so->second.cloudPath1.cloudPath == si->second.cloudPath1.cloudPath && so->second.cloudPath2.cloudPath == si->second.cloudPath2.cloudPath && so->second.localPath1.localPath == si->second.localPath1.localPath) { combinePathProblems(so->second, si->second); if (si->second.localPath2.localPath.empty()) { // if we know the destination in one, make sure we keep it si->second.localPath2.localPath = so->second.localPath2.localPath; } // other iterators are not invalidated in std::map syncStallInfoMaps.cloud.erase(so); } } } } } } void Syncs::getSyncStatusInfo(handle backupID, SyncStatusInfoCompletion completion, bool completionInClient) { // No completion? No work to be done! if (!completion) return; // Convenience. using DBTC = TransferDbCommitter; using MC = MegaClient; using SV = vector; // Is it up to the client to call the completion function? if (completionInClient) completion = [completion, this](SV info) { // Delegate to the user's completion function. queueClient([completion, info = std::move(info)](MC&, DBTC&) mutable { completion(std::move(info)); }); }; // Queue the request on the sync thread. queueSync([backupID, completion, this]() { getSyncStatusInfoInThread(backupID, std::move(completion)); }, "getSyncStatusInfo"); } void Syncs::getSyncStatusInfoInThread(handle backupID, SyncStatusInfoCompletion completion) { // Make sure we're running on the right thread. assert(onSyncThread()); // Make sure no one's changing the syncs beneath our feet. lock_guard guard(mSyncVecMutex); // Gathers information about a specific sync. struct gather { gather(const Sync& sync) : mSync(sync) { } operator SyncStatusInfo() const { SyncStatusInfo info; auto& config = mSync.getConfig(); info.mBackupID = config.mBackupId; info.mName = config.mName; info.mTransferCounts = mSync.threadSafeState->transferCounts(); tally(info, *mSync.localroot); return info; } void tally(SyncStatusInfo& info, const LocalNode& node) const { // Not synced? Not interested. if (node.parent && node.syncedCloudNodeHandle.isUndef()) return; ++info.mTotalSyncedNodes; // Directories don't have a size. if (node.type == FILENODE) info.mTotalSyncedBytes += static_cast(node.syncedFingerprint.size); // Process children, if any. for (auto& childIt : node.children) tally(info, *childIt.second); } const Sync& mSync; }; // gather // Status info collected from syncs. vector info; // Gather status information from active syncs. for (auto& us : mSyncVec) { // Not active? Not interested. if (!us->mSync) continue; // Convenience. auto& config = us->mConfig; // Is this sync something we're interested in? if (backupID != UNDEF && backupID != config.mBackupId) continue; // Gather status information about this sync. info.emplace_back(gather(*us->mSync)); } // Pass the information to the caller. completion(std::move(info)); } SyncConfigVector Syncs::configsForDrive(const LocalPath& drive) const { assert(onSyncThread() || !onSyncThread()); lock_guard guard(mSyncVecMutex); SyncConfigVector v; for (auto& s : mSyncVec) { if (s->mConfig.mExternalDrivePath == drive) { v.push_back(s->mConfig); } } return v; } SyncController::SyncController() = default; SyncController::~SyncController() = default; bool SyncController::deferPutnode(const LocalPath&) const { return true; } bool SyncController::deferPutnodeCompletion(const LocalPath&) const { return true; } bool SyncController::deferUpload(const LocalPath&) const { return true; } void Syncs::injectSyncSensitiveData(SyncSensitiveData data) { syncRun([data = std::move(data), this]() { // Nothing should've been injected yet. assert(SymmCipher::isZeroKey(syncKey.key, sizeof(syncKey.key))); assert(!mSyncConfigIOContext); assert(!mSyncConfigStore); // Inject state cache key (sync key.) syncKey.setkey(reinterpret_cast(data.stateCacheKey.data())); // Construct IO context. mSyncConfigIOContext.reset( new SyncConfigIOContext(*fsaccess, data.jscData.authenticationKey, data.jscData.cipherKey, data.jscData.fileName, rng)); }, __func__); } SyncConfigVector Syncs::getConfigs(bool onlyActive) const { assert(onSyncThread() || !onSyncThread()); lock_guard guard(mSyncVecMutex); SyncConfigVector v; for (auto& s : mSyncVec) { if (s->mSync || !onlyActive) { v.push_back(s->mConfig); } } return v; } handle Syncs::getSyncIdContainingActivePath(const LocalPath& lp) const { assert(onSyncThread() || !onSyncThread()); lock_guard guard(mSyncVecMutex); SyncConfigVector v; for (auto& s : mSyncVec) { if (s->mSync) { if (s->mConfig.mLocalPath.isContainingPathOf(lp)) { auto debrisPath = s->mConfig.mLocalPath; debrisPath.appendWithSeparator(LocalPath::fromRelativePath(DEBRISFOLDER), false); if (debrisPath.isContainingPathOf(lp)) { return UNDEF; } else { return s->mConfig.mBackupId; } } } } return UNDEF; } bool Syncs::configById(handle backupId, SyncConfig& configResult) const { assert(!onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& s : mSyncVec) { if (s->mConfig.mBackupId == backupId) { configResult = s->mConfig; return true; } } return false; } void Syncs::backupCloseDrive(const LocalPath& drivePath, std::function clientCallback) { assert(!onSyncThread()); assert(clientCallback); queueSync( [this, drivePath, clientCallback]() { Error e = backupCloseDrive_inThread(drivePath); queueClient( [clientCallback, e](MegaClient&, TransferDbCommitter&) { clientCallback(e); }); }, "backupCloseDrive"); } error Syncs::backupCloseDrive_inThread(LocalPath drivePath) { assert(onSyncThread()); assert(drivePath.isAbsolute() || drivePath.empty()); // Is the path valid? if (drivePath.empty()) { return API_EARGS; } auto* store = syncConfigStore(); // Does the store exist? if (!store) { // Nope and we need it. return API_EINTERNAL; } // Is this drive actually loaded? if (!store->driveKnown(drivePath)) { return API_ENOENT; } auto result = store->write(drivePath, configsForDrive(drivePath)); store->removeDrive(drivePath); auto syncsOnDrive = selectedSyncConfigs( [&](SyncConfig& config, Sync*) { return config.mExternalDrivePath == drivePath; }); for (auto& sc : syncsOnDrive) { SyncConfig removed; unloadSyncByBackupID(sc.mBackupId, sc.mEnabled, removed); } return result; } void Syncs::backupOpenDrive(const LocalPath& drivePath, std::function clientCallback) { assert(!onSyncThread()); assert(clientCallback); queueSync( [this, drivePath, clientCallback]() { Error e = backupOpenDrive_inThread(drivePath); queueClient( [clientCallback, e](MegaClient&, TransferDbCommitter&) { clientCallback(e); }); }, "backupOpenDrive"); } error Syncs::backupOpenDrive_inThread(const LocalPath& drivePath) { assert(onSyncThread()); assert(drivePath.isAbsolute()); // Is the drive path valid? if (drivePath.empty()) { return API_EARGS; } // Can we get our hands on the config store? auto* store = syncConfigStore(); if (!store) { LOG_err << "Couldn't restore " << drivePath << " as there is no config store."; // Nope and we can't do anything without it. return API_EINTERNAL; } // Has this drive already been opened? if (store->driveKnown(drivePath)) { LOG_debug << "Skipped restore of " << drivePath << " as it has already been opened."; // Then we don't have to do anything. return API_EEXIST; } SyncConfigVector configs; // Try and open the database on the drive. auto result = store->read(drivePath, configs, true); // Try and restore the backups in the database. if (result == API_OK) { LOG_debug << "Attempting to restore backup syncs from " << drivePath; size_t numRestored = 0; // Create a unified sync for each backup config. for (const auto& config : configs) { lock_guard guard(mSyncVecMutex); bool skip = false; for (auto& us : mSyncVec) { // Make sure there aren't any syncs with this backup id. if (config.mBackupId == us->mConfig.mBackupId) { skip = true; LOG_err << "Skipping restore of backup " << config.mLocalPath << " on " << drivePath << " as a sync already exists with the backup id " << toHandle(config.mBackupId); } } if (!skip) { // Create the unified sync. mSyncVec.emplace_back(std::make_shared(*this, config)); // Track how many configs we've restored. ++numRestored; } } // Log how many backups we could restore. LOG_debug << "Restored " << numRestored << " out of " << configs.size() << " backup(s) from " << drivePath; return API_OK; } // Couldn't open the database. LOG_warn << "Failed to restore " << drivePath << " as we couldn't open its config database: " << drivePath; return result; } SyncConfigStore* Syncs::syncConfigStore() { assert(onSyncThread()); // Have we already created the database? if (mSyncConfigStore) { // Yep, return a reference to the caller. return mSyncConfigStore.get(); } // Is the client using a database? if (!mClient.dbaccess) { // Nope and we need it for the configuration path. return nullptr; } // Can we get our hands on an IO context? if (!syncConfigIOContext()) { // We need it if we want to write the DB to disk. return nullptr; } // Where the database will be stored. auto dbPath = mClient.dbaccess->rootPath(); // Create the database. mSyncConfigStore.reset( new SyncConfigStore(dbPath, *mSyncConfigIOContext)); return mSyncConfigStore.get(); } NodeHandle Syncs::getSyncedNodeForLocalPath(const LocalPath& lp) { assert(!onSyncThread()); // synchronous for now but we could make async one day (intermediate layer would need its function made async first) NodeHandle result; syncRun([&](){ lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (us->mSync) { LocalNode* match = us->mSync->localnodebypath(NULL, lp, nullptr, nullptr, false); if (match) { result = match->syncedCloudNodeHandle; break; } } } }, "getSyncedNodeForLocalPath"); return result; } treestate_t Syncs::getSyncStateForLocalPath(handle backupId, const LocalPath& lp) { assert(!onSyncThread()); // mLocalNodeChangeMutex must already be locked!! // we never have mSyncVecMutex and then lock mLocalNodeChangeMutex lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (us->mConfig.mBackupId == backupId && us->mSync) { if (LocalNode* match = us->mSync->localnodebypath(nullptr, lp, nullptr, nullptr, true)) { return match->checkTreestate(false); } return TREESTATE_NONE; } } return TREESTATE_NONE; } bool Syncs::getSyncStateForLocalPath(const LocalPath& lp, treestate_t& ts, nodetype_t& nt, SyncConfig& sc) { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (us->mSync && us->mConfig.mLocalPath.isContainingPathOf(lp)) { if (LocalNode* match = us->mSync->localnodebypath(nullptr, lp, nullptr, nullptr, true)) { ts = match->checkTreestate(false); nt = match->type; sc = us->mConfig; return true; } return false; } } return false; } error Syncs::syncConfigStoreAdd(const SyncConfig& config) { assert(!onSyncThread()); error result = API_OK; syncRun([&](){ syncConfigStoreAdd_inThread(config, [&](error e){ result = e; }); }, "syncConfigStoreAdd"); return result; } void Syncs::moveToSyncDebrisByBackupID(const string& path, handle backupId, std::function completion, std::function completionInClient) { auto moveToDebris = [this, path, backupId, completion, completionInClient]() { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); Sync* sync = nullptr; error e = API_ENOENT; for (auto& s : mSyncVec) { if (s->mSync && s->mConfig.mBackupId == backupId) { sync = s->mSync.get(); } } if (sync) { e = sync->movetolocaldebris(LocalPath::fromAbsolutePath(path)) ? API_OK : API_EINTERNAL; } if (completion) { completion(e); } if (completionInClient) { queueClient([completionInClient, e](MegaClient& , TransferDbCommitter&) { completionInClient(e); }); } }; if (onSyncThread()) { moveToDebris(); } else { queueSync([moveToDebris]() { moveToDebris(); }, "Move to node to derbis"); } } void Syncs::syncConfigStoreAdd_inThread(const SyncConfig& config, std::function completion) { assert(onSyncThread()); // Convenience. static auto equal = [](const LocalPath& lhs, const LocalPath& rhs) { return !platformCompareUtf(lhs, false, rhs, false); }; auto* store = syncConfigStore(); // Could we get our hands on the store? if (!store) { // Nope and we can't proceed without it. completion(API_EINTERNAL); return; } SyncConfigVector configs; bool known = store->driveKnown(LocalPath()); // Load current configs from disk. auto result = store->read(LocalPath(), configs, false); if (result == API_ENOENT || result == API_OK) { SyncConfigVector::iterator i = configs.begin(); // Are there any syncs already present for this root? for ( ; i != configs.end(); ++i) { if (equal(i->mLocalPath, config.mLocalPath)) { break; } } // Did we find any existing config? if (i != configs.end()) { // Yep, replace it. LOG_debug << "Replacing existing sync config for: " << i->mLocalPath; *i = config; } else { // Nope, add it. configs.emplace_back(config); } // Write the configs to disk. result = store->write(LocalPath(), configs); } // Remove the drive if it wasn't already known. if (!known) { store->removeDrive(LocalPath()); } completion(result); return; } bool Syncs::syncConfigStoreDirty() { assert(onSyncThread()); return mSyncConfigStore && mSyncConfigStore->dirty(); } bool Syncs::syncConfigStoreFlush() { assert(onSyncThread()); // No need to flush if the store's not dirty. if (!syncConfigStoreDirty()) return true; // Try and flush changes to disk. LOG_debug << "Attempting to flush config store changes."; auto failed = mSyncConfigStore->writeDirtyDrives(getConfigs(false)); if (failed.empty()) return true; LOG_err << "Failed to flush " << failed.size() << " drive(s)."; // Disable syncs present on drives that we couldn't write. auto nDisabled = 0u; for (auto& drivePath : failed) { // Determine which syncs are present on this drive. auto configs = configsForDrive(drivePath); // Disable those that aren't already disabled. for (auto& config : configs) { // Already disabled? Nothing to do. // // dgw: This is what prevents an infinite flush cycle. if (!config.mEnabled) continue; // Disable the sync. disableSyncByBackupId(config.mBackupId, SYNC_CONFIG_WRITE_FAILURE, false, true, nullptr); ++nDisabled; } } LOG_warn << "Disabled" << nDisabled << " sync(s) on " << failed.size() << " drive(s)."; return false; } error Syncs::syncConfigStoreLoad(SyncConfigVector& configs) { assert(onSyncThread()); LOG_debug << "Attempting to load internal sync configs from disk."; auto result = API_EAGAIN; // Can we get our hands on the internal sync config database? if (auto* store = syncConfigStore()) { // Try and read the internal database from disk. result = store->read(LocalPath(), configs, false); if (result == API_ENOENT || result == API_OK) { LOG_debug << "Loaded " << configs.size() << " internal sync config(s) from disk."; // check if sync databases exist, so we know if a sync is disabled or merely suspended // note that the sync root path might not be available now, and we might start the // sync later if the drive appears (and its suspension reason was that path disappearing) for (auto& c: configs) { handle root_fsid = c.mLocalPathFsid; if (root_fsid == UNDEF) { // backward compatibilty for when we didn't store the fsid in serialized config auto fas = fsaccess->newfileaccess(false); if (fas->fopen(c.mLocalPath, OPEN_RDONLY, FSLogging::logOnError)) { root_fsid = fas->fsid; } } if (root_fsid != UNDEF) { string dbname = c.getSyncDbStateCacheName(root_fsid, c.mRemoteNode, mClient.me); // Note, we opened dbaccess in thread-safe mode // If the user is upgrading from NO SRW to SRW, we rename the DB files to the // new SRW version. However, if there are db files from a previous SRW version // (i.e., the user downgraded from SRW to NO SRW and then upgraded again to // SRW) we need to remove the SRW db files. The flag DB_OPEN_FLAG_RECYCLE is // used for this purpose. A similar case happens if user is upgrading from No // Fingerprint virtual column in Nodes table to a newer version with this // column. int dbFlags = DB_OPEN_FLAG_TRANSACTED; // Unused if (DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_SRW || DbAccess::LEGACY_DB_VERSION == DbAccess::LAST_DB_VERSION_WITHOUT_VFINGERPRINT) { dbFlags |= DB_OPEN_FLAG_RECYCLE; } LocalPath dbPath; c.mDatabaseExists = mClient.dbaccess->checkDbFileAndAdjustLegacy(*fsaccess, dbname, dbFlags, dbPath); } if (c.mEnabled) { c.mRunState = SyncRunState::Pending; } else { c.mRunState = c.mDatabaseExists ? SyncRunState::Suspend : SyncRunState::Disable; } } return API_OK; } } LOG_err << "Couldn't load internal sync configs from disk: " << result; return result; } string Syncs::exportSyncConfigs(const SyncConfigVector configs) const { assert(!onSyncThread()); JSONWriter writer; writer.beginobject(); writer.beginarray("configs"); for (const auto& config : configs) { exportSyncConfig(writer, config); } writer.endarray(); writer.endobject(); return writer.getstring(); } string Syncs::exportSyncConfigs() const { assert(!onSyncThread()); return exportSyncConfigs(configsForDrive(LocalPath())); } void Syncs::importSyncConfigs(const char* data, std::function completion) { assert(!onSyncThread()); // Convenience. struct Context; using CompletionFunction = std::function; using ContextPtr = std::shared_ptr; // Bundles state we need to create backup IDs. struct Context { static void put(ContextPtr context) { using std::bind; using std::move; using std::placeholders::_1; using std::placeholders::_2; // Convenience. auto& client = *context->mClient; auto& config = *context->mConfig; auto& deviceHash = context->mDeviceHash; // Backup Info. auto state = BackupInfoSync::getSyncState(config, context->mSyncs->mDownloadsPaused, context->mSyncs->mUploadsPaused); auto info = BackupInfoSync(config, deviceHash, UNDEF, state); LOG_debug << "Generating backup ID for config " << context->signature() << "..."; // Completion chain. auto completion = bind(&putComplete, std::move(context), _1, _2); // Create and initiate request. auto* request = new CommandBackupPut(&client, info, std::move(completion)); client.queueCommand(request); } static void putComplete(ContextPtr context, Error result, handle backupID) { // No backup ID even though the request succeeded? if (!result && ISUNDEF(result)) { // Then we've encountered an internal error. result = API_EINTERNAL; } // Convenience; auto& client = *context->mClient; // Were we able to create a backup ID? if (result) { LOG_err << "Unable to generate backup ID for config " << context->signature(); auto i = context->mConfigs.begin(); auto j = context->mConfig; // Remove the IDs we've created so far. LOG_debug << "Releasing backup IDs generated so far..."; for ( ; i != j; ++i) { auto* request = new CommandBackupRemove(&client, i->mBackupId, nullptr); client.queueCommand(request); // don't wait for the cleanup to notify the client about failure of import // (error/success of cleanup is irrelevant for the app) } // Let the client know the import has failed. context->mCompletion(result); return; } // Assign the newly generated backup ID. context->mConfig->mBackupId = backupID; // Have we assigned IDs for all the syncs? if (++context->mConfig == context->mConfigs.end()) { auto& syncs = *context->mSyncs; LOG_debug << context->mConfigs.size() << " backup ID(s) have been generated."; LOG_debug << "Importing " << context->mConfigs.size() << " configs(s)..."; // Yep, add them to the sync. for (const auto& config : context->mConfigs) { // So we can wait for the sync to be added. std::promise waiter; // Called when the engine has added the sync. auto completion = [&waiter](error, SyncError, handle) { waiter.set_value(); }; // Add the new sync, optionally enabling it. syncs.appendNewSync(config, false, std::move(completion), false, config.mName); // Wait for this sync to be added. waiter.get_future().get(); } LOG_debug << context->mConfigs.size() << " sync(s) imported successfully."; // Let the client know the import has completed. context->mCompletion(API_OK); return; } // Generate an ID for the next config. put(std::move(context)); } string signature() const { ostringstream ostream; ostream << mConfig - mConfigs.begin() + 1 << "/" << mConfigs.size(); return ostream.str(); } // Client. MegaClient* mClient; // Who to call back when we're done. CompletionFunction mCompletion; // Next config requiring a backup ID. SyncConfigVector::iterator mConfig; // Configs requiring a backup ID. SyncConfigVector mConfigs; // Identifies the device we're adding configs to. string mDeviceHash; // Who we're adding the configs to. Syncs* mSyncs; }; // Context // Sanity. if (!data || !*data) { completion(API_EARGS); return; } // Preprocess input so to remove all extraneous whitespace. auto strippedData = JSON::stripWhitespace(data); // Try and translate JSON back into sync configs. SyncConfigVector configs; if (!importSyncConfigs(strippedData, configs)) { // No love. Inform the client. completion(API_EREAD); return; } // Don't import configs that already appear to be present. { lock_guard guard(mSyncVecMutex); // Checks if two configs have an equivalent mapping. auto equivalent = [](const SyncConfig& lhs, const SyncConfig& rhs) { auto& lrp = lhs.mOriginalPathOfRemoteRootNode; auto& rrp = rhs.mOriginalPathOfRemoteRootNode; return lhs.mLocalPath == rhs.mLocalPath && lrp == rrp; }; // Checks if an equivalent config has already been loaded. auto present = [&](const SyncConfig& config) { for (auto& us : mSyncVec) { if (equivalent(us->mConfig, config)) return true; } return false; }; // Strip configs that already appear to be present. auto j = std::remove_if(configs.begin(), configs.end(), present); configs.erase(j, configs.end()); } // No configs? Nothing to import! if (configs.empty()) { completion(API_OK); return; } // Create and initialize context. ContextPtr context = std::make_unique(); context->mClient = &mClient; context->mCompletion = std::move(completion); context->mConfigs = std::move(configs); context->mConfig = context->mConfigs.begin(); context->mDeviceHash = mClient.getDeviceidHash(); context->mSyncs = this; if (context->mDeviceHash.empty()) { LOG_err << "Failed to get Device ID while importing sync configs"; completion(API_EARGS); return; } LOG_debug << "Attempting to generate backup IDs for " << context->mConfigs.size() << " imported config(s)..."; // Generate backup IDs. Context::put(std::move(context)); } void Syncs::exportSyncConfig(JSONWriter& writer, const SyncConfig& config) const { assert(!onSyncThread()); // Internal configs only for the time being. if (!config.mExternalDrivePath.empty()) { LOG_warn << "Skipping export of external backup: " << config.mLocalPath; return; } string localPath = config.mLocalPath.toPath(false); string remotePath; const string& name = config.mName; const char* type = SyncConfig::synctypename(config.mSyncType); if (const auto node = mClient.nodeByHandle(config.mRemoteNode)) { // Get an accurate remote path, if possible. remotePath = node->displaypath(); } else { // Otherwise settle for what we had stored. remotePath = config.mOriginalPathOfRemoteRootNode; } writer.beginobject(); writer.arg_stringWithEscapes("localPath", localPath); writer.arg_stringWithEscapes("name", name); writer.arg_stringWithEscapes("remotePath", remotePath); writer.arg_stringWithEscapes("type", type); const auto changeMethod = changeDetectionMethodToString(config.mChangeDetectionMethod); writer.arg_stringWithEscapes("changeMethod", changeMethod); writer.arg("scanInterval", config.mScanIntervalSec); writer.endobject(); } bool Syncs::importSyncConfig(JSON& reader, SyncConfig& config) { assert(!onSyncThread()); static const string TYPE_CHANGE_METHOD = "changeMethod"; static const string TYPE_LOCAL_PATH = "localPath"; static const string TYPE_NAME = "name"; static const string TYPE_REMOTE_PATH = "remotePath"; static const string TYPE_SCAN_INTERVAL = "scanInterval"; static const string TYPE_TYPE = "type"; LOG_debug << "Attempting to parse config object: " << reader.pos; // Default to notification change detection method. string changeMethod = changeDetectionMethodToString(CDM_NOTIFICATIONS); string localPath; string name; string remotePath; string scanInterval; string type; // Parse config properties. for (string key; ; ) { // What property are we parsing? key = reader.getname(); // Have we processed all the properties? if (key.empty()) break; string value; // Extract property value if we can. if (!reader.storeobject(&value)) { LOG_err << "Parse error extracting property: " << key << ": " << reader.pos; return false; } if (key == TYPE_CHANGE_METHOD) { changeMethod = std::move(value); } if (key == TYPE_LOCAL_PATH) { localPath = std::move(value); } else if (key == TYPE_NAME) { name = std::move(value); } else if (key == TYPE_REMOTE_PATH) { remotePath = std::move(value); } else if (key == TYPE_SCAN_INTERVAL) { scanInterval = std::move(value); } else if (key == TYPE_TYPE) { type = std::move(value); } else { LOG_debug << "Skipping unknown property: " << key << ": " << value; } } // Basic validation on properties. if (localPath.empty()) { LOG_err << "Invalid config: no local path defined."; return false; } if (name.empty()) { LOG_err << "Invalid config: no name defined."; return false; } if (remotePath.empty()) { LOG_err << "Invalid config: no remote path defined."; return false; } reader.unescape(&localPath); reader.unescape(&name); reader.unescape(&remotePath); reader.unescape(&type); // Populate config object. config.mBackupId = UNDEF; config.mEnabled = false; config.mError = NO_SYNC_ERROR; config.mFilesystemFingerprint.reset(); config.mLocalPath = LocalPath::fromAbsolutePath(localPath); config.mName = std::move(name); config.mOriginalPathOfRemoteRootNode = remotePath; config.mWarning = NO_SYNC_WARNING; // Set node handle if possible. if (const auto root = mClient.nodeByPath(remotePath.c_str())) { config.mRemoteNode = root->nodeHandle(); } else { LOG_err << "Invalid config: " << "unable to find node for remote path: " << remotePath; return false; } // Set change detection method. config.mChangeDetectionMethod = changeDetectionMethodFromString(changeMethod); if (config.mChangeDetectionMethod == CDM_UNKNOWN) { LOG_err << "Invalid config: " << "unknown change detection method: " << changeMethod; return false; } // Set scan interval. if (config.mChangeDetectionMethod == CDM_PERIODIC_SCANNING) { std::istringstream istream(scanInterval); istream >> config.mScanIntervalSec; auto failed = istream.fail() || !istream.eof(); if (failed || !config.mScanIntervalSec) { LOG_err << "Invalid config: " << "malformed scan interval: " << scanInterval; return false; } } // Set type. if (!config.synctypefromname(type, config.mSyncType)) { LOG_err << "Invalid config: " << "unknown sync type name: " << type; return false; } // Config's been parsed. LOG_debug << "Config successfully parsed."; return true; } bool Syncs::importSyncConfigs(const string& data, SyncConfigVector& configs) { assert(!onSyncThread()); static const string TYPE_CONFIGS = "configs"; JSON reader(data); LOG_debug << "Attempting to import configs from: " << data; // Enter configs object. if (!reader.enterobject()) { LOG_err << "Parse error entering root object: " << reader.pos; return false; } // Parse sync configs. for (string key; ; ) { // What property are we parsing? key = reader.getname(); // Is it a property we know about? if (key != TYPE_CONFIGS) { // Have we hit the end of the configs object? if (key.empty()) break; // Skip unknown properties. string object; if (!reader.storeobject(&object)) { LOG_err << "Parse error skipping unknown property: " << key << ": " << reader.pos; return false; } LOG_debug << "Skipping unknown property: " << key << ": " << object; // Parse the next property. continue; } LOG_debug << "Found configs property: " << reader.pos; // Enter array of sync configs. if (!reader.enterarray()) { LOG_err << "Parse error entering configs array: " << reader.pos; return false; } // Parse each sync config object. while (reader.enterobject()) { SyncConfig config; // Try and parse this sync config object. if (!importSyncConfig(reader, config)) return false; if (!reader.leaveobject()) { LOG_err << "Parse error leaving config object: " << reader.pos; return false; } configs.emplace_back(std::move(config)); } if (!reader.leavearray()) { LOG_err << "Parse error leaving configs array: " << reader.pos; return false; } LOG_debug << configs.size() << " config(s) successfully parsed."; } // Leave configs object. if (!reader.leaveobject()) { LOG_err << "Parse error leaving root object: " << reader.pos; return false; } return true; } SyncConfigIOContext* Syncs::syncConfigIOContext() { assert(onSyncThread()); return mSyncConfigIOContext.get(); } template bool Syncs::defer(bool (SyncController::*predicate)(Parameters...) const, Arguments&&... arguments) const { // Consult controller if available. if (auto controller = syncController()) return (*controller.*predicate)(std::forward(arguments)...); // Otherwise assume we shouldn't defer any activity. return false; } bool Syncs::hasImmediateStall(const SyncStallInfo& stalls) const { std::lock_guard guard(mImmediateStallLock); if (mHasImmediateStall) return mHasImmediateStall(stalls); return stalls.hasImmediateStallReason(); } bool Syncs::isImmediateStall(const SyncStallEntry& entry) const { std::lock_guard guard(mImmediateStallLock); if (mIsImmediateStall) return mIsImmediateStall(entry); return entry.alertUserImmediately; } bool Syncs::deferPutnode(const LocalPath& path) const { return defer(&SyncController::deferPutnode, path); } bool Syncs::deferPutnodeCompletion(const LocalPath& path) const { return defer(&SyncController::deferPutnodeCompletion, path); } bool Syncs::hasSyncController() const { std::lock_guard guard(mSyncControllerLock); // Reference to controller has grown stale. if (mSyncController.expired()) return mSyncController.reset(), false; // Reference to controller is still live. return true; } bool Syncs::deferUpload(const LocalPath& path) const { return defer(&SyncController::deferUpload, path); } void Syncs::setHasImmediateStall(HasImmediateStallPredicate predicate) { std::lock_guard guard(mImmediateStallLock); mHasImmediateStall = std::move(predicate); } void Syncs::setIsImmediateStall(IsImmediateStallPredicate predicate) { std::lock_guard guard(mImmediateStallLock); mIsImmediateStall = std::move(predicate); } void Syncs::setSyncController(SyncControllerPtr controller) { std::lock_guard guard(mSyncControllerLock); mSyncController = controller; } SyncControllerPtr Syncs::syncController() const { std::lock_guard guard(mSyncControllerLock); // Return controller if it's still alive. if (auto controller = mSyncController.lock()) return controller; // Clear controller if it's stale. mSyncController.reset(); return nullptr; } bool Syncs::isSyncStalled(handle backupId) const { assert(onSyncThread()); lock_guard guard(stallReportMutex); return syncStallState && stallReport.isSyncStalled(backupId); } LocalNode* Syncs::findMoveFromLocalNode(const shared_ptr& moveTo) { assert(onSyncThread()); // There should never be many moves in progress so we can iterate the entire thing // Pointers are safe to access because destruction of these LocalNodes removes them from the set for (auto ln : mMoveInvolvedLocalNodes) { assert(ln->rareRO().moveFromHere || ln->rareRO().moveToHere); if (auto& moveFrom = ln->rareRO().moveFromHere) { if (moveFrom == moveTo) { return ln; } } } return nullptr; } void Syncs::clear_inThread(bool reopenStoreAfter) { assert(onSyncThread()); assert(!mSyncConfigStore); mSyncConfigStore.reset(); { lock_guard guard(mSyncVecMutex); mSyncVec.clear(); } mNumSyncsActive = 0; if (!reopenStoreAfter) { mSyncConfigIOContext.reset(); syncKey.setkey(SymmCipher::zeroiv); } { lock_guard guard(stallReportMutex); stallReport = SyncStallInfo(); } { lock_guard g(triggerMutex); triggerHandles.clear(); triggerLocalpaths.clear(); } localnodeByScannedFsid.clear(); localnodeBySyncedFsid.clear(); localnodeByNodeHandle.clear(); mSyncFlags.reset(new SyncFlags); mHeartBeatMonitor.reset(new BackupMonitor(*this)); mFileChangingCheckState.clear(); mMoveInvolvedLocalNodes.clear(); if (syncscanstate) { assert(onSyncThread()); syncscanstate = false; mClient.app->syncupdate_scanning(false); } if (syncBusyState) { assert(onSyncThread()); syncBusyState = false; mClient.app->syncupdate_syncing(false); } syncStallState = false; syncConflictState = false; totalSyncStalls.store(0); totalSyncConflicts.store(0); totalLocalNodes = 0; mSyncsLoaded.store(false, std::memory_order_relaxed); mSyncsResumed.store(false, std::memory_order_relaxed); } void Syncs::appendNewSync(const SyncConfig& c, bool startSync, std::function completion, bool completionInClient, const string& logname, const string& excludedPath) { assert(!onSyncThread()); assert(c.mBackupId != UNDEF); auto clientCompletion = [this, completion](error e, SyncError se, handle backupId) { queueClient( [e, se, backupId, completion](MegaClient&, TransferDbCommitter&) { if (completion) completion(e, se, backupId); }); }; queueSync([=]() { appendNewSync_inThread(c, startSync, completionInClient ? clientCompletion : completion, logname, excludedPath); }, "appendNewSync"); } void Syncs::appendNewSync_inThread(const SyncConfig& c, bool startSync, std::function completion, const string& logname, const string& excludedPath) { assert(onSyncThread()); // Get our hands on the sync config store. auto* store = syncConfigStore(); // Can we get our hands on the config store? if (!store) { LOG_err << "Unable to add backup " << c.mLocalPath << " on " << c.mExternalDrivePath << " as there is no config store."; if (completion) completion(API_EINTERNAL, c.mError, c.mBackupId); return; } // Do we already know about this drive? if (!store->driveKnown(c.mExternalDrivePath)) { // Are we adding an internal sync? if (c.isInternal()) { LOG_debug << "Drive for internal syncs not known: " << c.mExternalDrivePath; // Then signal failure as the internal drive isn't available. if (completion) completion(API_EFAILED, UNKNOWN_DRIVE_PATH, c.mBackupId); return; } // Restore the drive's backups, if any. auto result = backupOpenDrive_inThread(c.mExternalDrivePath); if (result != API_OK && result != API_ENOENT) { // Couldn't read an existing database. LOG_err << "Unable to add backup " << c.mLocalPath << " on " << c.mExternalDrivePath << " as we could not read its config database."; if (completion) completion(API_EFAILED, c.mError, c.mBackupId); return; } } { lock_guard guard(mSyncVecMutex); mSyncVec.push_back(std::make_shared(*this, c)); } ensureDriveOpenedAndMarkDirty(c.mExternalDrivePath); mClient.app->sync_added(c); if (!startSync) { if (completion) completion(API_OK, c.mError, c.mBackupId); return; } enableSyncByBackupId_inThread(c.mBackupId, true, completion, logname, excludedPath); } Sync* Syncs::runningSyncByBackupIdForTests(handle backupId) const { assert(!onSyncThread()); // returning a Sync* is not really thread safe but the tests are using these directly currently. So long as they only browse the Sync while nothing changes, it should be ok lock_guard guard(mSyncVecMutex); for (auto& s : mSyncVec) { if (s->mSync && s->mConfig.mBackupId == backupId) { return s->mSync.get(); } } return nullptr; } template bool Syncs::ifFoundSyncConfigByBackupId(const handle backupId, SyncConfigCallable&& action) const { assert(!onSyncThread()); std::lock_guard guard(mSyncVecMutex); auto findCondition = [&backupId](const auto& unifiedSync) { return unifiedSync && unifiedSync->mConfig.mBackupId == backupId; }; if (const auto it = std::find_if(mSyncVec.begin(), mSyncVec.end(), std::move(findCondition)); it != mSyncVec.end()) { std::forward(action)((*it)->mConfig); return true; } return false; } bool Syncs::hasSyncConfigByBackupId(const handle backupId) const { return ifFoundSyncConfigByBackupId(backupId, [](const auto&) {}); } bool Syncs::syncConfigByBackupId(const handle backupId, SyncConfig& syncConfig) const { auto withMatchedSyncConfigAction = [&syncConfig](const auto& matchedConfig) { syncConfig = matchedConfig; }; return ifFoundSyncConfigByBackupId(backupId, std::move(withMatchedSyncConfigAction)); } void Syncs::transferPauseFlagsUpdated(bool downloadsPaused, bool uploadsPaused) { assert(!onSyncThread()); bool unchanged = mDownloadsPaused == downloadsPaused && mUploadsPaused == uploadsPaused; mDownloadsPaused = downloadsPaused; mUploadsPaused = uploadsPaused; if (!unchanged) { mTransferPauseFlagsChanged.store(true, std::memory_order_relaxed); } } void Syncs::stopSyncsInErrorState() { assert(onSyncThread()); // An error has occurred, and it's time to destroy the in-RAM structures // If the sync db should be kept, then we already null'd the sync->syncstatecache lock_guard guard(mSyncVecMutex); for (auto& unifiedSync : mSyncVec) { if (unifiedSync->mSync && unifiedSync->mConfig.mError != NO_SYNC_ERROR) { unifiedSync->mSync.reset(); } } } void Syncs::purgeRunningSyncs() { assert(!onSyncThread()); syncRun([&](){ purgeRunningSyncs_inThread(); }, "purgeRunningSyncs"); } void Syncs::purgeRunningSyncs_inThread() { assert(onSyncThread()); // Called from locallogout (which always happens on ~MegaClient as well as on request) // Any syncs that are running should be resumed on next start. // We stop the syncs here, but don't call the client to say they are stopped. // And localnode databases are preserved. lock_guard guard(mSyncVecMutex); for (auto& s : mSyncVec) { if (s->mSync) { // Deleting the sync will close/save the sync's localnode database file in its current state. // And then delete objects in RAM. s->mSync.reset(); } } } void Syncs::renameSync(handle backupId, const string& newname, std::function completion) { assert(!onSyncThread()); assert(completion); auto clientCompletion = [this, completion](Error e) { queueClient( [completion, e](MegaClient&, TransferDbCommitter&) { completion(e); }); }; queueSync([this, backupId, newname, clientCompletion]() { renameSync_inThread(backupId, newname, clientCompletion); }, "renameSync"); } void Syncs::renameSync_inThread(handle backupId, const string& newname, std::function completion) { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto &i : mSyncVec) { if (i->mConfig.mBackupId == backupId) { i->mConfig.mName = newname; // cause an immediate `sp` command to update the backup/sync heartbeat master record mHeartBeatMonitor->updateOrRegisterSync(*i); // queue saving the change locally if (mSyncConfigStore) mSyncConfigStore->markDriveDirty(i->mConfig.mExternalDrivePath); completion(API_OK); return; } } completion(API_EEXIST); } void Syncs::disableSyncs(SyncError syncError, bool newEnabledFlag, bool keepSyncDb) { assert(!onSyncThread()); queueSync([this, syncError, newEnabledFlag, keepSyncDb]() { assert(onSyncThread()); SyncConfigVector v = getConfigs(false); int nEnabled = 0; for (auto& c : v) { if (c.getEnabled()) ++nEnabled; } for (auto& c : v) { if (c.getEnabled()) { std::function completion = nullptr; if (!--nEnabled) { completion = [=](){ LOG_info << "Disabled syncs. error = " << syncError; mClient.app->syncs_disabled(syncError); }; } disableSyncByBackupId_inThread(c.mBackupId, syncError, newEnabledFlag, keepSyncDb, completion); } } }, "disableSyncs"); } void Syncs::disableSyncByBackupId(handle backupId, SyncError syncError, bool newEnabledFlag, bool keepSyncDb, std::function completion) { assert(!onSyncThread()); queueSync([this, backupId, syncError, newEnabledFlag, keepSyncDb, completion]() { disableSyncByBackupId_inThread(backupId, syncError, newEnabledFlag, keepSyncDb, completion); }, "disableSyncByBackupId"); } void Syncs::disableSyncByBackupId_inThread(handle backupId, SyncError syncError, bool newEnabledFlag, bool keepSyncDb, std::function completion) { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto i = mSyncVec.size(); i--; ) { auto& us = *mSyncVec[i]; auto& config = us.mConfig; if (config.mBackupId == backupId) { if (syncError == NO_SYNC_ERROR) { syncError = UNLOADING_SYNC; } // if we are logging out, we don't need to bother the user about // syncs stopping, the user expects everything to stop bool notifyApp = !mClient.loggingout; us.changeState(syncError, newEnabledFlag, notifyApp, keepSyncDb); //This will cause the later deletion of Sync (not MegaSyncPrivate) object mHeartBeatMonitor->updateOrRegisterSync(us); } } if (completion) completion(); } SyncConfigVector Syncs::selectedSyncConfigs(std::function selector) const { SyncConfigVector selected; lock_guard guard(mSyncVecMutex); for (size_t i = 0; i < mSyncVec.size(); ++i) { if (selector(mSyncVec[i]->mConfig, mSyncVec[i]->mSync.get())) { selected.emplace_back(mSyncVec[i]->mConfig); } } return selected; } std::function Syncs::prepareSdsCleanupForBackup(UnifiedSync& us, const vector>& sds) { us.sdsUpdateInProgress.reset(new bool(true)); LOG_debug << "SDS: preparing sds command attribute for sync/backup " << toHandle(us.mConfig.mBackupId) << " on node " << us.mConfig.mRemoteNode << " for execution after update requested by sds"; return [remoteNode = us.mConfig.mRemoteNode, backupId = us.mConfig.mBackupId, remainingSds = sds, boolsptr = us.sdsUpdateInProgress](MegaClient& mc, TransferDbCommitter&) mutable { // Lambda to be executed in the client thread if (std::shared_ptr node = mc.nodeByHandle(remoteNode)) { remainingSds.erase(std::remove_if(remainingSds.begin(), remainingSds.end(), [&backupId](const pair& sdsRequest) { return sdsRequest.first == backupId; })); // SDS values for full-syncs (account root node) are set in the *!sds attribute // instead of as an attribute of the backup/sync root node. if (mc.mNodeManager.getRootNodeFiles().eq(remoteNode)) { string_map records; // Map current sds to a string_map compatible with putua. for (const auto& [bkpId, state]: remainingSds) { records[toHandle(bkpId)] = std::to_string(state); } mc.putua(ATTR_SYNC_DESIRED_STATE, std::move(records), 0, UNDEF, 0, 0, [boolsptr](Error e) { if (e != API_OK) { LOG_warn << "Failed to update " << User::attr2string(ATTR_SYNC_DESIRED_STATE) << " user attribute: " << e; } *boolsptr = false; }); } else { mc.setattr( node, attr_map(Node::sdsId(), Node::toSdsString(remainingSds)), [boolsptr](NodeHandle handle, Error result) { LOG_debug << "SDS: Attribute updated on " << handle << " result: " << result; *boolsptr = false; }, true); } } }; } bool Syncs::processPauseResumeSyncBySds(UnifiedSync& us, vector>& sdsBackups) { assert(onSyncThread()); if (us.sdsUpdateInProgress && *us.sdsUpdateInProgress) { return false; } // find the last SDS request to Pause or Resume current sync auto sdsPauseResumeRequest = std::find_if( sdsBackups.rbegin(), sdsBackups.rend(), [id = us.mConfig.mBackupId](const pair& v) { return v.first == id && (v.second == CommandBackupPut::ACTIVE || v.second == CommandBackupPut::TEMPORARY_DISABLED); }); if (sdsPauseResumeRequest == sdsBackups.rend()) { return false; } auto clientRemoveSdsEntryFunction = prepareSdsCleanupForBackup(us, sdsBackups); if ((sdsPauseResumeRequest->second == CommandBackupPut::ACTIVE && us.mConfig.mRunState == SyncRunState::Run) || (sdsPauseResumeRequest->second == CommandBackupPut::TEMPORARY_DISABLED && us.mConfig.mRunState > SyncRunState::Run)) { // no need to change state, just do the clean-up queueClient(std::move(clientRemoveSdsEntryFunction)); } else if (sdsPauseResumeRequest->second == CommandBackupPut::ACTIVE) { // switch to Active enableSyncByBackupId_inThread( us.mConfig.mBackupId, true, [clientRemoveSdsEntryFunction, this](error err, SyncError serr, handle h) mutable { if (err) { LOG_err << "Failed to set sync/backup " << h << " as Active. Error " << err << ". SyncError: " << serr; } else { LOG_info << "Set sync/backup " << h << " as Active."; } queueClient(std::move(clientRemoveSdsEntryFunction)); // do the cleanup }, ""); } else if (sdsPauseResumeRequest->second == CommandBackupPut::TEMPORARY_DISABLED) { // switch to Pause disableSyncByBackupId_inThread( us.mConfig.mBackupId, NO_SYNC_ERROR, false, true, [h = us.mConfig.mBackupId, clientRemoveSdsEntryFunction, this]() mutable { LOG_info << "Set sync/backup " << h << " as Paused."; queueClient(std::move(clientRemoveSdsEntryFunction)); // do the cleanup }); } return true; // confirm the state update request for the given sync } // process backup is removed by SDS if there is any // // case 1: Backup is moved from backup center to the cloud node // we'll receive sds delete and the backup root node is moved in action packets // case 2: Backup is deleted from backup center // we'll receive sds delete and the backup root node is deleted in action packets // The node deleteion could appear first or after sds delete due to async // @return true if removing sync by sds bool Syncs::processRemovingSyncBySds(UnifiedSync& us, bool foundRootNode, vector>& sdsBackups) { assert(onSyncThread()); // prevent the reentry due to aync nature if (us.mConfig.mRemovingSyncBySds) { return true; } if (!foundRootNode && us.mConfig.isBackup()) { LOG_debug << "Backup root node no longer exists " << toHandle(us.mConfig.mBackupId); deregisterThenRemoveSyncBySds(us, nullptr); return true; } // find any SDS request to Delete current sync if ((!us.sdsUpdateInProgress || !*us.sdsUpdateInProgress) && std::find(sdsBackups.begin(), sdsBackups.end(), pair{us.mConfig.mBackupId, static_cast(CommandBackupPut::DELETED)}) != sdsBackups.end()) { LOG_debug << "SDS: command received to stop sync " << toHandle(us.mConfig.mBackupId); auto clientRemoveSdsEntryFunction = prepareSdsCleanupForBackup(us, sdsBackups); deregisterThenRemoveSyncBySds(us, clientRemoveSdsEntryFunction); return true; } return false; } void Syncs::deregisterThenRemoveSyncBySds(UnifiedSync& us, std::function clientRemoveSdsEntryFunction) { assert(onSyncThread()); us.mConfig.mRemovingSyncBySds = true; if (Sync* sync = us.mSync.get()) { // prevent the sync doing anything more before we delete it sync->changestate(NO_SYNC_ERROR, false, false, false); } auto backupId = us.mConfig.mBackupId; queueClient( [backupId, clientRemoveSdsEntryFunction](MegaClient& mc, TransferDbCommitter&) { mc.syncs.deregisterThenRemoveSync(backupId, nullptr, clientRemoveSdsEntryFunction); }); } void Syncs::deregisterThenRemoveSyncById(handle backupId, std::function&& completion) { assert(!onSyncThread()); queueSync( [this, backupId]() { lock_guard guard(mSyncVecMutex); if (const auto it = std::find_if(std::begin(mSyncVec), std::end(mSyncVec), [backupId](const auto& us) { return us && us->mConfig.mBackupId == backupId; }); it != std::end(mSyncVec)) (*it)->changeState(NO_SYNC_ERROR, false, false, false); }, "removeSyncFromDb"); deregisterThenRemoveSync(backupId, std::move(completion), {}); } void Syncs::deregisterThenRemoveSync(handle backupId, std::function completion, std::function clientRemoveSdsEntryFunction) { assert(!onSyncThread()); // Try and deregister this sync's backup ID first. // If later removal operations fail, the heartbeat record will be resurrected LOG_debug << "Deregistering backup ID: " << toHandle(backupId); { // since we are only setting flags, we can actually do this off-thread // (but using mSyncVecMutex and hidden inside Syncs class) lock_guard guard(mSyncVecMutex); for (size_t i = 0; i < mSyncVec.size(); ++i) { auto& config = mSyncVec[i]->mConfig; if (config.mBackupId == backupId) { // prevent any sp or sphb messages being queued after config.mSyncDeregisterSent = true; } } } // use queueClient since we are not certain to be locked on client thread queueClient( [backupId, completion, this, clientRemoveSdsEntryFunction](MegaClient& mc, TransferDbCommitter&) { mc.queueCommand(new CommandBackupRemove( &mc, backupId, [backupId, completion, this, clientRemoveSdsEntryFunction](Error e) { if (e) { // de-registering is not critical - we continue anyway LOG_warn << "API error deregisterig sync " << toHandle(backupId) << ":" << e; } queueSync( [=]() { removeSyncAfterDeregistration_inThread(backupId, std::move(completion), clientRemoveSdsEntryFunction); }, "deregisterThenRemoveSync"); })); }, true); } void Syncs::removeSyncAfterDeregistration_inThread(handle backupId, std::function clientCompletion, std::function clientRemoveSdsEntryFunction) { assert(onSyncThread()); Error e = API_OK; SyncConfig configCopy; if (unloadSyncByBackupID(backupId, false, configCopy)) { mClient.app->sync_removed(configCopy); mSyncConfigStore->markDriveDirty(configCopy.mExternalDrivePath); // lastly, send the command to remove the sds entry from the (former) sync root Node's attributes if (clientRemoveSdsEntryFunction) { queueClient(std::move(clientRemoveSdsEntryFunction)); } } else { e = API_EEXIST; } if (clientCompletion) { // this case for if we didn't need to deregister anything queueClient([clientCompletion, e](MegaClient&, TransferDbCommitter&){ clientCompletion(e); }); } } bool Syncs::unloadSyncByBackupID(handle id, bool newEnabledFlag, SyncConfig& configCopy) { assert(onSyncThread()); LOG_debug << "Unloading sync: " << toHandle(id); lock_guard guard(mSyncVecMutex); for (auto i = mSyncVec.size(); i--; ) { if (mSyncVec[i]->mConfig.mBackupId == id) { if (auto& syncPtr = mSyncVec[i]->mSync) { // if it was running, the app gets a callback saying it's no longer active // SYNC_CANCELED is a special value that means we are shutting it down without changing config mSyncVec[i]->changeState(UNLOADING_SYNC, newEnabledFlag, false, true); assert(!syncPtr->statecachetable); syncPtr.reset(); // deletes sync } configCopy = mSyncVec[i]->mConfig; // the sync config is not affected by this operation; it should already be up to date on disk (or be pending) // we don't call sync_removed back since the sync is not deleted // we don't unregister from the backup/sync heartbeats as the sync can be resumed later mSyncVec.erase(mSyncVec.begin() + static_cast(i)); return true; } } return false; } void Syncs::prepareForLogout(bool keepSyncsConfigFile, std::function clientCompletion) { queueSync([=](){ prepareForLogout_inThread(keepSyncsConfigFile, clientCompletion); }, "prepareForLogout"); } void Syncs::prepareForLogout_inThread(bool keepSyncsConfigFile, std::function clientCompletion) { assert(onSyncThread()); if (keepSyncsConfigFile) { // Special case backward compatibility for MEGAsync // The syncs will be disabled, if the user logs back in they can then manually re-enable. lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (us->mConfig.getEnabled()) { disableSyncByBackupId_inThread(us->mConfig.mBackupId, LOGGED_OUT, false, false, nullptr); } } } else // if logging out and syncs won't be kept... { // regardless of that, we de-register all syncs/backups in Backup Centre lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { std::function onFinalDeregister = nullptr; if (us.get() == mSyncVec.back().get()) { // this is the last one, so we'll arrange clientCompletion // to run after it completes. Earlier de-registers must finish first onFinalDeregister = std::move(clientCompletion); clientCompletion = nullptr; } us->mConfig.mSyncDeregisterSent = true; auto backupId = us->mConfig.mBackupId; queueClient( [backupId, onFinalDeregister](MegaClient& mc, TransferDbCommitter&) { mc.queueCommand(new CommandBackupRemove(&mc, backupId, [onFinalDeregister](Error) { if (onFinalDeregister) onFinalDeregister(); })); }); } } if (clientCompletion) { // this case for if we didn't need to deregister anything queueClient([clientCompletion](MegaClient&, TransferDbCommitter&){ clientCompletion(); }); } } void Syncs::locallogout(bool removecaches, bool keepSyncsConfigFile, bool reopenStoreAfter) { assert(!onSyncThread()); syncRun([=](){ locallogout_inThread(removecaches, keepSyncsConfigFile, reopenStoreAfter); }, "locallogout"); } void Syncs::locallogout_inThread(bool removecaches, bool keepSyncsConfigFile, bool reopenStoreAfter) { assert(onSyncThread()); mExecutingLocallogout = true; // NULL the statecachetable databases for Syncs first, then Sync destruction won't remove LocalNodes from them // If we are deleting syncs then just remove() the database direct std::unique_lock syncVecMutexLock(mSyncVecMutex); for (auto i = mSyncVec.size(); i--;) { if (Sync* sync = mSyncVec[i]->mSync.get()) { if (sync->statecachetable) { if (removecaches) sync->statecachetable->remove(); sync->statecachetable.reset(); } } } syncVecMutexLock.unlock(); if (mSyncConfigStore) { if (!keepSyncsConfigFile) { mSyncConfigStore->write(LocalPath(), SyncConfigVector()); } else { syncConfigStoreFlush(); } } mSyncConfigStore.reset(); // Remove all syncs from RAM. for (auto& sc : getConfigs(false)) { SyncConfig removed; unloadSyncByBackupID(sc.mBackupId, false, removed); } assert(mSyncVec.empty()); // make sure we didn't resurrect the store, singleton style assert(!mSyncConfigStore); clear_inThread(reopenStoreAfter); mExecutingLocallogout = false; if (reopenStoreAfter) { SyncConfigVector configs; syncConfigStoreLoad(configs); } } bool Syncs::commitConfigToDb(const SyncConfig& config) { assert(onSyncThread()); ensureDriveOpenedAndMarkDirty(config.mExternalDrivePath); return syncConfigStoreFlush(); } void Syncs::ensureDriveOpenedAndMarkDirty(const LocalPath& externalDrivePath) { assert(onSyncThread()); if (auto* store = syncConfigStore()) { // If the app hasn't opened this drive itself, then we open it now (loads any syncs that // already exist there) if (!externalDrivePath.empty() && !store->driveKnown(externalDrivePath)) { backupOpenDrive_inThread(externalDrivePath); } store->markDriveDirty(externalDrivePath); } } void Syncs::loadSyncConfigsOnFetchnodesComplete(bool resetSyncConfigStore) { assert(!onSyncThread()); if (mSyncsLoaded.load()) { return; } mSyncsLoaded.store(true, std::memory_order_relaxed); queueSync([this, resetSyncConfigStore]() { loadSyncConfigsOnFetchnodesComplete_inThread(resetSyncConfigStore); }, "loadSyncConfigsOnFetchnodesComplete"); } void Syncs::resumeSyncsOnStateCurrent() { assert(!onSyncThread()); // Double check the client only calls us once (per session) for this if (mSyncsResumed.load()) { assert(false && "resumeSyncsOnStateCurrent: unexpected mSyncsResumed value `true`"); return; } mSyncsResumed.store(true, std::memory_order_relaxed); queueSync([this]() { resumeSyncsOnStateCurrent_inThread(); }, "resumeSyncsOnStateCurrent"); } void Syncs::loadSyncConfigsOnFetchnodesComplete_inThread(bool resetSyncConfigStore) { assert(onSyncThread()); if (resetSyncConfigStore) { mSyncConfigStore.reset(); static_cast(syncConfigStore()); } SyncConfigVector configs; if (error e = syncConfigStoreLoad(configs)) { LOG_warn << "syncConfigStoreLoad failed: " << e; mClient.app->syncs_restored(SYNC_CONFIG_READ_FAILURE); return; } lock_guard guard(mSyncVecMutex); // There should be no syncs yet. assert(mSyncVec.empty()); for (auto& config: configs) { mSyncVec.push_back(std::make_shared(*this, config)); } for (auto& us : mSyncVec) { mClient.app->sync_added(us->mConfig); } } void Syncs::resumeSyncsOnStateCurrent_inThread() { assert(onSyncThread()); // Make a copy of mSyncVec to avoid locking mutex so many time auto auxSyncVec = getSyncVecCopy(); for (auto& unifiedSync: auxSyncVec) { std::unique_lock syncVecMutexLock(mSyncVecMutex); if (!unifiedSync->mSync) { if (unifiedSync->mConfig.mOriginalPathOfRemoteRootNode.empty()) { // this should only happen on initial migraion from from old caches CloudNode cloudNode; string cloudNodePath; const auto remoteNodeHandle = unifiedSync->mConfig.mRemoteNode; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks, in this case by calling `lookupCloudNode` this could happen // if we do not release `mSyncVecMutex` before syncVecMutexLock.unlock(); const auto lookupCloudNodeSuccess = lookupCloudNode(remoteNodeHandle, cloudNode, &cloudNodePath, nullptr, nullptr, nullptr, nullptr, Syncs::FOLDER_ONLY); syncVecMutexLock.lock(); if (lookupCloudNodeSuccess) { unifiedSync->mConfig.mOriginalPathOfRemoteRootNode = cloudNodePath; ensureDriveOpenedAndMarkDirty(unifiedSync->mConfig.mExternalDrivePath); } } if (unifiedSync->mConfig.getEnabled()) { #ifdef __APPLE__ unifiedSync->mConfig.mFilesystemFingerprint.reset(); //for certain MacOS, fsfp seems to vary when restarting. we set it to 0, so that it gets recalculated #endif LOG_debug << "Resuming cached sync: " << toHandle(unifiedSync->mConfig.mBackupId) << " " << unifiedSync->mConfig.getLocalPath() << " fsfp= " << unifiedSync->mConfig.mFilesystemFingerprint.toString() << " error = " << unifiedSync->mConfig.mError; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks; in this case at `enableSyncByBackupId_inThread`, so we need // to release mutex before. syncVecMutexLock.unlock(); enableSyncByBackupId_inThread( unifiedSync->mConfig.mBackupId, false, [&unifiedSync](error, SyncError se, handle backupId) { LOG_debug << "Sync autoresumed: " << toHandle(backupId) << " " << unifiedSync->mConfig.getLocalPath() << " fsfp= " << unifiedSync->mConfig.mFilesystemFingerprint.toString() << " error = " << se; }, ""); // Lock `syncVecMutexLock` again syncVecMutexLock.lock(); } else { LOG_debug << "Sync loaded (but not resumed): " << toHandle(unifiedSync->mConfig.mBackupId) << " " << unifiedSync->mConfig.getLocalPath() << " fsfp= " << unifiedSync->mConfig.mFilesystemFingerprint.toString() << " error = " << unifiedSync->mConfig.mError; } } } mClient.app->syncs_restored(NO_SYNC_ERROR); } void Sync::recursiveCollectNameConflicts(list* conflicts, size_t* count, size_t* limit) { assert(syncs.onSyncThread()); assert(conflicts || count); FSNode rootFsNode(localroot->getLastSyncedFSDetails()); SyncRow row{ &cloudRoot, localroot.get(), &rootFsNode }; SyncPath pathBuffer(syncs, localroot->localname, cloudRootPath); size_t dummyCount = 0; size_t dummyMax = std::numeric_limits::max(); recursiveCollectNameConflicts(row, pathBuffer, conflicts, count ? *count : dummyCount, limit ? *limit : dummyMax); } void Sync::purgeStaleDownloads() { // Convenience. using MC = MegaClient; using DBTC = TransferDbCommitter; // Make sure we're running on the right thread. assert(syncs.onSyncThread()); auto globPath = localdebris; // Get our hands on this sync's temporary directory. globPath.appendWithSeparator(LocalPath::fromRelativePath("tmp"), true); // Generate glob pattern to match all temporary downloads. globPath.appendWithSeparator(LocalPath::fromRelativePath(".*.mega"), true); // Remainder of work takes place on the client thread. // // The idea here is to prevent the client from starting any new // transfers while we're busy purging temporary files. syncs.queueClient([globPath](MC& client, DBTC&) mutable { // Figure out which temporaries are currently present. auto dirAccess = client.fsaccess->newdiraccess(); auto paths = set(); if (!dirAccess->dopen(&globPath, nullptr, true)) return; LocalPath path; LocalPath name; nodetype_t type; while (dirAccess->dnext(path, name, false, &type)) { if (type == FILENODE) paths.emplace(name); } // Filter out paths that are still "alive." for (auto& i : client.multi_cachedtransfers[GET]) { if (!i.second->localfilename.empty()) paths.erase(i.second->localfilename); } // Remove "dead" temporaries. for (const auto& tempPath: paths) client.fsaccess->unlinklocal(tempPath); }); } void SyncRow::inferOrCalculateChildSyncRows(bool wasSynced, vector& childRows, vector& fsInferredChildren, vector& fsChildren, vector& cloudChildren, bool belowRemovedFsNode, fsid_localnode_map& localnodeByScannedFsid) { // This is the function that determines the list of syncRows that recursiveSync() will operate on for this folder. // Each SyncRow a (CloudNodes, SyncNodes, FSNodes) tuple that all match up by name (taking escapes/case into account) // For the case of a folder that contains already-synced content, and in which nothing changed, we can "infer" the set // much more efficiently, by generating it from SyncNodes alone. // Otherwise we need to calculate it, which involves sorting the three sets and matching them up. // An additional complication is that we try to save RAM by not storing the scanned FSNodes for this folder // If we can regenerate the list of FSNodes from the LocalNodes, then we would have done that, and so we // need to recreate that list. So our extra RAM usage is just for the folders on the stack, and not the entire tree. // (Plus for those SyncNode folders where regeneration wouldn't match the FSNodes (yet)) // (SyncNode = LocalNode, we'll rename LocalNode eventually) if (wasSynced && !belowRemovedFsNode && !syncNode->lastFolderScan && syncNode->syncAgain < TREE_ACTION_HERE && // if fully matching, we would have removed the fsNode vector to save space syncNode->sync->inferRegeneratableTriplets(cloudChildren, *syncNode, fsInferredChildren, childRows)) { // success, the already sorted and aligned triplets were inferred // and the results were filled in: childRows, cloudChildren, fsInferredChildren } else { // Effective children are from the last scan, if present. vector* effectiveFsChildren = belowRemovedFsNode ? nullptr : syncNode->lastFolderScan.get(); if (!effectiveFsChildren) { // Otherwise, we can reconstruct the filesystem entries from the LocalNodes fsChildren.reserve(syncNode->children.size() + 50); // leave some room for others to be added in syncItem() for (auto &childIt : syncNode->children) { assert(childIt.first == childIt.second->localname); if (belowRemovedFsNode) { if (childIt.second->fsid_asScanned != UNDEF) { childIt.second->setScannedFsid(UNDEF, localnodeByScannedFsid, LocalPath(), FileFingerprint()); childIt.second->scannedFingerprint = FileFingerprint(); childIt.second->realScannedFingerprint = FileFingerprint(); } } else if (childIt.second->fsid_asScanned != UNDEF) { fsChildren.emplace_back(childIt.second->getScannedFSDetails()); } } effectiveFsChildren = &fsChildren; } childRows = syncNode->sync->computeSyncTriplets(cloudChildren, *syncNode, *effectiveFsChildren); } } void Sync::recursiveCollectNameConflicts(SyncRow& row, SyncPath& fullPath, list* ncs, size_t& count, size_t& limit) { assert(syncs.onSyncThread()); assert(row.syncNode); if (!row.syncNode->conflictsDetected()) { return; } if (count >= limit) { return; } // Get sync triplets. vector childRows; vector fsInferredChildren; vector fsChildren; vector cloudChildren; if (row.cloudNode) { syncs.lookupCloudChildren(row.cloudNode->handle, cloudChildren); } bool wasSynced = false; // todo row.inferOrCalculateChildSyncRows(wasSynced, childRows, fsInferredChildren, fsChildren, cloudChildren, false, syncs.localnodeByScannedFsid); for (auto& childRow : childRows) { if (childRow.hasClashes()) { if (ncs) { NameConflict nc; if (childRow.hasCloudPresence()) nc.cloudPath = fullPath.cloudPath; if (childRow.hasLocalPresence()) nc.localPath = fullPath.localPath; // Only meaningful if there are no cloud clashes. if (auto* c = childRow.cloudNode) nc.clashingCloud.emplace_back(c->name, c->handle); // Only meaningful if there are no local clashes. if (auto* f = childRow.fsNode) nc.clashingLocalNames.emplace_back(f->localname); for (auto* c : childRow.cloudClashingNames) nc.clashingCloud.emplace_back(c->name, c->handle); for (auto* f : childRow.fsClashingNames) nc.clashingLocalNames.emplace_back(f->localname); ncs->emplace_back(std::move(nc)); } if (++count >= limit) { break; } } // recurse after dealing with all items, so any renames within the folder have been completed if (childRow.syncNode && childRow.syncNode->type == FOLDERNODE) { SyncPath newPath{fullPath}; if (!newPath.appendRowNames(childRow, mFilesystemType) || localdebris.isContainingPathOf(newPath.localPath)) { // This is a legitimate case; eg. we only had a syncNode and it is removed in resolve_delSyncNode // Or if this is the debris folder, ignore it continue; } recursiveCollectNameConflicts(childRow, newPath, ncs, count, limit); } } } bool SyncRow::hasClashes() const { return !cloudClashingNames.empty() || !fsClashingNames.empty(); } bool SyncRow::hasCloudPresence() const { return cloudNode || !cloudClashingNames.empty(); } bool SyncRow::hasLocalPresence() const { return fsNode || !fsClashingNames.empty(); } const LocalPath& SyncRow::comparisonLocalname() const { if (syncNode) { return syncNode->localname; } else if (fsNode) { return fsNode->localname; } else if (!fsClashingNames.empty()) { return fsClashingNames[0]->localname; } else { assert(false); static LocalPath nullResult; return nullResult; } } SyncRowType SyncRow::type() const { auto c = static_cast(cloudNode != nullptr); auto s = static_cast(syncNode != nullptr); auto f = static_cast(fsNode != nullptr); return static_cast(c * 4 + s * 2 + f); } ExclusionState SyncRow::exclusionState(const CloudNode& node) const { assert(syncNode); assert(syncNode->type > FILENODE); return syncNode->exclusionState(node.name, node.type, node.fingerprint.size); } ExclusionState SyncRow::exclusionState(const FSNode& node) const { assert(syncNode); assert(syncNode->type > FILENODE); return syncNode->exclusionState(node.localname, node.type, node.fingerprint.size); } ExclusionState SyncRow::exclusionState(const LocalPath& name, nodetype_t type, m_off_t size) const { assert(syncNode); assert(syncNode->type != FILENODE); return syncNode->exclusionState(name, type, size); } bool SyncRow::hasCaseInsensitiveCloudNameChange() const { // only call this if the sync is mCaseInsensitive return fsNode && syncNode && cloudNode && syncNode->namesSynchronized && 0 != compareUtf(syncNode->localname, true, cloudNode->name, true, false) && 0 == compareUtf(syncNode->localname, true, cloudNode->name, true, true) && 0 != compareUtf(fsNode->localname, true, cloudNode->name, true, false); } bool SyncRow::hasCaseInsensitiveLocalNameChange() const { // only call this if the sync is mCaseInsensitive return fsNode && syncNode && cloudNode && syncNode->namesSynchronized && 0 != compareUtf(syncNode->localname, true, fsNode->localname, true, false) && 0 == compareUtf(syncNode->localname, true, fsNode->localname, true, true) && 0 != compareUtf(cloudNode->name, true, fsNode->localname, true, false); } bool SyncRow::isLocalOnlyIgnoreFile() const { return isIgnoreFile() && syncNode && syncNode->parent && syncNode->parent->rareRO().filterChain && !syncNode->parent->rareRO().filterChain->mSyncThisMegaignore; } bool SyncRow::isIgnoreFile() const { struct Predicate { bool operator()(const CloudNode& node) const { // So to avoid ambiguity. const string& name = IGNORE_FILE_NAME; return node.type == FILENODE && !platformCompareUtf(node.name, true, name, false); } bool operator()(const FSNode& node) const { const LocalPath& name = IGNORE_FILE_NAME; return node.type == FILENODE && !platformCompareUtf(node.localname, true, name, false); } } predicate; if (auto* s = syncNode) return s->isIgnoreFile(); if (auto* f = fsNode) return predicate(*f); if (!fsClashingNames.empty()) return predicate(*fsClashingNames.front()); if (auto* c = cloudNode) return predicate(*c); if (!cloudClashingNames.empty()) return predicate(*cloudClashingNames.front()); return false; } bool SyncRow::isNoName() const { // TODO: Notify the app about the presence os NoName nodes (the stall issue has been removed after SDK-3859) // Can't be a no-name triplet if we have clashing filesystem names. if (!fsClashingNames.empty()) return false; // Could be a no-name triplet if we have clashing cloud names. if (!cloudClashingNames.empty()) return cloudClashingNames.front()->name.empty(); // Could be a no-name triplet if we have a cloud node. if (cloudNode && cloudNode->name.empty()) { if (fsNode || syncNode) { LOG_debug << "Considering a cloudNode to be NO_NAME with either a fsNode or syncNode: " << "fsNode: " << string(fsNode ? "true" : "false") << "fsNode: " << string(syncNode ? "true" : "false"); } return true; } return false; } void SyncRow::reassignFingerprints() { if (!fsNode || fsNode->type != FILENODE || !syncNode) { return; } if (syncNode->realScannedFingerprint != fsNode->fingerprint) { syncNode->realScannedFingerprint = fsNode->fingerprint; } #ifdef __ANDROID__ // In Android is not possible set mtime when file is download // Update fsNode->fingerprint with syncNode->syncedFingerprint in case they only have mtime // different This means it has scanned but it is already synced Real value that it is obtained // from file system is stored at syncNode->realScannedFingerprint if (syncNode->syncedFingerprint.isvalid && syncNode->syncedFingerprint.equalExceptMtimeAndIsValid(fsNode->fingerprint)) { fsNode->fingerprint.mtime = syncNode->syncedFingerprint.mtime; } #endif if (syncNode->scannedFingerprint != fsNode->fingerprint) { syncNode->scannedFingerprint = fsNode->fingerprint; } } void Sync::combineTripletSet(vector::iterator a, vector::iterator b) const { assert(syncs.onSyncThread()); //#ifdef DEBUG // // log before case // LOG_debug << " combineTripletSet BEFORE " << std::distance(a, b); // for (auto i = a; i != b; ++i) // { // LOG_debug // << (i->cloudNode ? i->cloudNode->name : "") << " " // << (i->syncNode ? i->syncNode->localname.toPath() : "") << " " // << (i->fsNode ? i->fsNode->localname.toPath() : "") << " "; // } //#endif // match up elements that are still present and were already synced vector::iterator lastFullySynced = b; vector::iterator lastNotFullySynced = b; unsigned syncNode_nfs_count = 0; for (auto i = a; i != b; ++i) { if (auto sn = i->syncNode) { if (!sn->syncedCloudNodeHandle.isUndef()) { for (auto j = a; j != b; ++j) { if (j->cloudNode && j->cloudNode->handle == sn->syncedCloudNodeHandle) { std::swap(j->cloudNode, i->cloudNode); break; } } } if (sn->fsid_lastSynced != UNDEF) { for (auto j = a; j != b; ++j) { if (j->fsNode && j->fsNode->fsid == sn->fsid_lastSynced) { std::swap(j->fsNode, i->fsNode); break; } } } // is this row fully synced already? if so, put it aside in case there are more syncNodes if (i->cloudNode && i->fsNode) { std::swap(*a, *i); lastFullySynced = a; ++a; } else { lastNotFullySynced = i; ++syncNode_nfs_count; } } } // if this fails, please figure out how we got into that state if (syncNode_nfs_count >= 2) { LOG_err << "syncNode_nfs_count(" << syncNode_nfs_count << ") >= 2! This should not happen!"; assert(false && "syncNode_nfs_count >= 2, please figure out how we got into that state"); } // gather up the remaining into a single row, there may be clashes. auto targetrow = lastNotFullySynced != b ? lastNotFullySynced : (lastFullySynced != b ? lastFullySynced : a); // check for duplicate names as we go. All but one row will be left as all nullptr for (auto i = a; i != b; ++i) if (i != targetrow) { if (i->fsNode) { if (targetrow->fsNode && !(targetrow->syncNode && targetrow->syncNode->fsid_lastSynced == targetrow->fsNode->fsid)) { SYNC_verbose_timed << syncname << "Conflicting filesystem name: " << targetrow->fsNode->localname; targetrow->fsClashingNames.push_back(targetrow->fsNode); targetrow->fsNode = nullptr; } if (targetrow->fsNode || !targetrow->fsClashingNames.empty()) { SYNC_verbose_timed << syncname << "Conflicting filesystem name: " << i->fsNode->localname; targetrow->fsClashingNames.push_back(i->fsNode); i->fsNode = nullptr; } if (!targetrow->fsNode && targetrow->fsClashingNames.empty()) { std::swap(targetrow->fsNode, i->fsNode); } } if (i->cloudNode) { if (!targetrow->cloudNode || !targetrow->cloudNode->name.empty()) // Avoid NO_NAME nodes to be considered { if (targetrow->cloudNode && !(targetrow->syncNode && targetrow->syncNode->syncedCloudNodeHandle == targetrow->cloudNode->handle)) { SYNC_verbose_timed << syncname << "Conflicting cloud name: " << targetrow->cloudNode->name; targetrow->cloudClashingNames.push_back(targetrow->cloudNode); targetrow->cloudNode = nullptr; } if (targetrow->cloudNode || !targetrow->cloudClashingNames.empty()) { SYNC_verbose_timed << syncname << "Conflicting cloud name: " << i->cloudNode->name; targetrow->cloudClashingNames.push_back(i->cloudNode); i->cloudNode = nullptr; } if (!targetrow->cloudNode && targetrow->cloudClashingNames.empty()) { std::swap(targetrow->cloudNode, i->cloudNode); } } } } //#ifdef DEBUG // // log after case // LOG_debug << " combineTripletSet AFTER " << std::distance(a, b); // for (auto i = a; i != b; ++i) // { // LOG_debug // << (i->cloudNode ? i->cloudNode->name : "") << " " // << (i->syncNode ? i->syncNode->localname.toPath() : "") << " " // << (i->fsNode ? i->fsNode->localname.toPath() : "") << " "; // for (auto j : i->cloudClashingNames) // { // LOG_debug << "with clashing cloud name: " << j->name; // } // for (auto j : i->fsClashingNames) // { // LOG_debug << "with clashing fs name: " << j->localname.toPath(); // } // } //#endif #ifdef DEBUG // confirm all are empty except target for (auto i = a; i != b; ++i) { assert(i == targetrow || i->empty() || (i->cloudNode && (i->cloudNode->name.empty()))); } #endif } auto Sync::computeSyncTriplets(vector& cloudNodes, const LocalNode& syncParent, vector& fsNodes) const -> vector { assert(syncs.onSyncThread()); CodeCounter::ScopeTimer rst(syncs.mClient.performanceStats.computeSyncTripletsTime); vector triplets; triplets.reserve(cloudNodes.size() + syncParent.children.size() + fsNodes.size()); for (auto& cn : cloudNodes) triplets.emplace_back(&cn, nullptr, nullptr); for (auto& sn : syncParent.children) triplets.emplace_back(nullptr, sn.second, nullptr); for (auto& fsn : fsNodes) triplets.emplace_back(nullptr, nullptr, &fsn); auto tripletCompare = [this](const SyncRow& lhs, const SyncRow& rhs) -> int { // Sanity. assert(!lhs.fsNode || !lhs.fsNode->localname.empty()); assert(!rhs.fsNode || !rhs.fsNode->localname.empty()); assert(!lhs.syncNode || !lhs.syncNode->localname.empty()); assert(!rhs.syncNode || !rhs.syncNode->localname.empty()); // Although it would be great to efficiently compare cloud names in utf8 directly against filesystem names // in utf16, without any conversions or copied and manipulated strings, unfortunately we have // a few obstacles to that. Mainly, that the utf8 encoding can differ - especially on Mac // where they normalize the names that go to the filesystem, but with a different normalization // than we chose for the Node names. In order to compare these effectively and efficiently // we pretty much have to first duplicate and convert both strings to a single utf8 normalization first. if (lhs.cloudNode) { if (rhs.cloudNode) { return compareUtf(lhs.cloudNode->name, true, rhs.cloudNode->name, true, mCaseInsensitive); } else if (rhs.syncNode) { return compareUtf(lhs.cloudNode->name, true, rhs.syncNode->toName_of_localname, false, mCaseInsensitive); } else // rhs.fsNode { return compareUtf(lhs.cloudNode->name, true, rhs.fsNode->toName_of_localname(*syncs.fsaccess), false, mCaseInsensitive); } } else if (lhs.syncNode) { if (rhs.cloudNode) { return compareUtf(lhs.syncNode->toName_of_localname, false, rhs.cloudNode->name, true, mCaseInsensitive); } else if (rhs.syncNode) { return compareUtf(lhs.syncNode->toName_of_localname, false, rhs.syncNode->toName_of_localname, false, mCaseInsensitive); } else // rhs.fsNode { return compareUtf(lhs.syncNode->toName_of_localname, false, rhs.fsNode->toName_of_localname(*syncs.fsaccess), false, mCaseInsensitive); } } else // lhs.fsNode { if (rhs.cloudNode) { return compareUtf(lhs.fsNode->toName_of_localname(*syncs.fsaccess), false, rhs.cloudNode->name, true, mCaseInsensitive); } else if (rhs.syncNode) { return compareUtf(lhs.fsNode->toName_of_localname(*syncs.fsaccess), false, rhs.syncNode->toName_of_localname, false, mCaseInsensitive); } else // rhs.fsNode { return compareUtf(lhs.fsNode->toName_of_localname(*syncs.fsaccess), false, rhs.fsNode->toName_of_localname(*syncs.fsaccess), false, mCaseInsensitive); } } }; std::sort(triplets.begin(), triplets.end(), [=](const SyncRow& lhs, const SyncRow& rhs) { return tripletCompare(lhs, rhs) < 0; }); auto currSet = triplets.begin(); auto end = triplets.end(); while (currSet != end) { // Determine the next set that are all comparator-equal auto nextSet = currSet; ++nextSet; while (nextSet != end && 0 == tripletCompare(*currSet, *nextSet)) { ++nextSet; } combineTripletSet(currSet, nextSet); currSet = nextSet; } auto newEnd = std::remove_if(triplets.begin(), triplets.end(), [](SyncRow& row){ return row.empty(); }); triplets.erase(newEnd, triplets.end()); return triplets; } bool Sync::inferRegeneratableTriplets(vector& cloudChildren, const LocalNode& syncParent, vector& inferredFsNodes, vector& inferredRows) const { assert(syncs.onSyncThread()); CodeCounter::ScopeTimer rst(syncs.mClient.performanceStats.inferSyncTripletsTime); if (cloudChildren.size() != syncParent.children.size()) return false; inferredFsNodes.reserve(syncParent.children.size()); auto cloudHandleLess = [](const CloudNode& a, const CloudNode& b){ return a.handle < b.handle; }; std::sort(cloudChildren.begin(), cloudChildren.end(), cloudHandleLess); for (auto& child : syncParent.children) { CloudNode compareTo; compareTo.handle = child.second->syncedCloudNodeHandle; auto iters = std::equal_range(cloudChildren.begin(), cloudChildren.end(), compareTo, cloudHandleLess); if (std::distance(iters.first, iters.second) != 1) { // the node tree has actually changed so we need to run the full algorithm return false; } CloudNode* node = &*iters.first; if (node->parentType == FILENODE) { LOG_err << "Looked up a file version node during infer: " << node->name; assert(false); return false; } if (child.second->fsid_asScanned == UNDEF || (!child.second->scannedFingerprint.isvalid && child.second->type == FILENODE)) { // we haven't scanned yet, or the scans don't match up with LocalNodes yet return false; } inferredFsNodes.push_back(child.second->getScannedFSDetails()); inferredRows.emplace_back(node, child.second, &inferredFsNodes.back()); } return true; } using IndexPair = pair; using IndexPairVector = vector; CodeCounter::ScopeStats computeSyncSequencesStats = { "computeSyncSequences" }; static IndexPairVector computeSyncSequences(vector& children) { // No children, no work to be done. if (children.empty()) return IndexPairVector(); CodeCounter::ScopeTimer rst(computeSyncSequencesStats); // Separate our children into those that are ignore files and those that are not. auto i = std::partition(children.begin(), children.end(), [](const SyncRow& child) { return child.isIgnoreFile(); }); // No prioritization necessary if there's only a single class of child. if (i == children.begin() || i == children.end()) { // Is it a run of regular files? if (!children.front().isIgnoreFile()) return IndexPairVector(1, IndexPair(0, children.size())); IndexPairVector sequences; sequences.emplace_back(0, children.size()); sequences.emplace_back(children.size(), children.size()); return sequences; } IndexPairVector sequences; // Convenience. auto j = i - children.begin(); // Ignore files should be completely processed first. sequences.emplace_back(0, j); sequences.emplace_back(j, children.size()); return sequences; } bool Sync::recursiveSync(SyncRow& row, SyncPath& fullPath, bool belowRemovedCloudNode, bool belowRemovedFsNode, unsigned depth) { assert(syncs.onSyncThread()); { // in case of sync failing while we recurse lock_guard guard(syncs.mSyncVecMutex); if (getConfig().mError) return false; } assert(row.syncNode); assert(row.syncNode->type > FILENODE); assert(row.syncNode->getLocalPath() == fullPath.localPath); if (depth + mCurrentRootDepth == MAX_CLOUD_DEPTH - 1) { ProgressingMonitor monitor(*this, row, fullPath); LOG_debug << "Attempting to synchronize overly deep directory: " << logTriplet(row, fullPath) << ": Effective depth is " << depth + mCurrentRootDepth; monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::SyncItemExceedsSupportedTreeDepth, true, true, {row.cloudHandleOpt(), fullPath.cloudPath}, {}, {fullPath.localPath}, {})); return true; } // nothing to do for this subtree? Skip traversal if (!(row.syncNode->scanRequired() || row.syncNode->mightHaveMoves() || row.syncNode->syncRequired())) { //SYNC_verbose << syncname << "No scanning/moving/syncing needed at " << logTriplet(row, fullPath); return true; } SYNC_verbose_timed << syncname << (belowRemovedCloudNode ? "belowRemovedCloudNode " : "") << (belowRemovedFsNode ? "belowRemovedFsNode " : "") << "Entering folder with " << row.syncNode->scanAgain << "-" << row.syncNode->checkMovesAgain << "-" << row.syncNode->syncAgain << " (" << row.syncNode->conflicts << ") at " << fullPath.syncPath; row.syncNode->propagateAnySubtreeFlags(); // Whether we should perform sync actions at this level. bool wasSynced = row.syncNode->scanAgain < TREE_ACTION_HERE && row.syncNode->syncAgain < TREE_ACTION_HERE && row.syncNode->checkMovesAgain < TREE_ACTION_HERE; bool syncHere = !wasSynced; bool recurseHere = true; //SYNC_verbose << syncname << "sync/recurse here: " << syncHere << recurseHere; // reset this node's sync flag. It will be set again below or while recursing if anything remains to be done. auto originalScanAgain = row.syncNode->scanAgain; auto originalSyncAgain = row.syncNode->syncAgain; row.syncNode->syncAgain = TREE_RESOLVED; if (!row.fsNode || belowRemovedFsNode) { row.syncNode->scanAgain = TREE_RESOLVED; row.syncNode->realScannedFingerprint = FileFingerprint(); row.syncNode->setScannedFsid(UNDEF, syncs.localnodeByScannedFsid, LocalPath(), FileFingerprint()); syncHere = row.syncNode->parent ? row.syncNode->parent->scanAgain < TREE_ACTION_HERE : true; recurseHere = false; // If we need to scan, we need the folder to exist first - revisit later row.syncNode->lastFolderScan.reset(); belowRemovedFsNode = true; // this flag will prevent us reconstructing from scannedFingerprint etc } else { row.reassignFingerprints(); if (row.syncNode->fsid_asScanned == UNDEF || row.syncNode->fsid_asScanned != row.fsNode->fsid) { row.syncNode->scanAgain = TREE_ACTION_HERE; row.syncNode->setScannedFsid(row.fsNode->fsid, syncs.localnodeByScannedFsid, row.fsNode->localname, row.fsNode->fingerprint); } } // Do we need to scan this node? if (row.syncNode->scanAgain >= TREE_ACTION_HERE) { if (!row.syncNode->rareRO().scanBlocked) { // not stalling, so long as we are still scanning. Destructor resets stall state ProgressingMonitor monitor(*this, row, fullPath); } syncs.mSyncFlags->reachableNodesAllScannedThisPass.store(false); syncHere = row.syncNode->processBackgroundFolderScan(row, fullPath); } else { // this will be restored at the end of the function if any nodes below in the tree need it row.syncNode->scanAgain = TREE_RESOLVED; } if (row.syncNode->scanAgain >= TREE_ACTION_HERE) { // we must return later when we do have the scan data. // restore sync flag row.syncNode->setSyncAgain(false, true, false); SYNC_verbose << syncname << "Early exit from recursiveSync due to no scan data yet. " << logTriplet(row, fullPath); syncHere = false; recurseHere = false; } auto originalCheckMovesAgain = row.syncNode->checkMovesAgain; auto originalConflicsFlag = row.syncNode->conflicts; bool earlyExit = false; if (syncHere || recurseHere) { // Reset these flags before we evaluate each subnode. // They could be set again during that processing, // And additionally we double check with each child node after, in case we had reason to skip it. if (!belowRemovedCloudNode && !belowRemovedFsNode) { // only expect to resolve these in normal case row.syncNode->checkMovesAgain = TREE_RESOLVED; } row.syncNode->conflicts = TREE_RESOLVED; // Get sync triplets. vector childRows; vector fsInferredChildren; vector fsChildren; vector cloudChildren; if (row.cloudNode) { syncs.lookupCloudChildren(row.cloudNode->handle, cloudChildren); } row.inferOrCalculateChildSyncRows(wasSynced, childRows, fsInferredChildren, fsChildren, cloudChildren, belowRemovedFsNode, syncs.localnodeByScannedFsid); bool anyNameConflicts = false; // Ignore files must be fully processed before any other child. auto sequences = computeSyncSequences(childRows); SyncRow* ignoreRow = !childRows.empty() && childRows.front().isIgnoreFile() ? &childRows.front() : nullptr; FSNode* ignoreFile = ignoreRow ? ignoreRow->fsNode : nullptr; bool invalidateExclusions = false; if (ignoreRow && !row.syncNode->rareRO().filterChain) { // instantiate filterChain as soon as we have a row, even if we don't have a // local file yet, in order to prevent any sync steps for other things until // we get the local file by download, or stall if there are cloud duplicates etc. LOG_debug << syncname << ".megaignore row detected inside " << logTriplet(row, fullPath); row.syncNode->rare().filterChain.reset(new FilterChain); invalidateExclusions = true; } if (!ignoreRow && row.syncNode->rareRO().filterChain) { LOG_debug << syncname << ".megaignore row disappeared inside " << logTriplet(row, fullPath); row.syncNode->rare().filterChain.reset(); invalidateExclusions = true; } if (ignoreRow && !ignoreFile && row.syncNode->rareRO().filterChain && row.syncNode->rare().filterChain->mLoadSucceeded) { LOG_debug << syncname << ".megaignore file disappeared inside " << logTriplet(row, fullPath); row.syncNode->rare().filterChain->mFingerprint = FileFingerprint(); row.syncNode->rare().filterChain->mLoadSucceeded = false; invalidateExclusions = true; } if (ignoreFile && row.syncNode->rareRO().filterChain) { if (!row.syncNode->rareRO().filterChain->mFingerprint.equalExceptMtime( ignoreFile->fingerprint)) { LOG_debug << syncname << "loading .megaignore file inside " << logTriplet(row, fullPath); auto ignorepath = fullPath.localPath; ignorepath.appendWithSeparator(IGNORE_FILE_NAME, true); bool ok = row.syncNode->loadFilters(ignorepath); if (ok != !row.syncNode->rareRO().badlyFormedIgnoreFilePath) { if (ok) { row.syncNode->rare().badlyFormedIgnoreFilePath.reset(); } else { row.syncNode->rare().badlyFormedIgnoreFilePath.reset(new LocalNode::RareFields::BadlyFormedIgnore(ignorepath, this)); syncs.badlyFormedIgnoreFilePaths.push_back(row.syncNode->rare().badlyFormedIgnoreFilePath); } } invalidateExclusions = true; } } if (invalidateExclusions) { row.syncNode->setRecomputeExclusionState(false, false); } // Here is where we loop over the syncRows for this folder. // We must process things in a particular order // - first, check all rows for involvement in moves (case 0) // such a row is excluded from further checks // - second, check all remaining rows for sync actions on that row (case 1) // - third, recurse into each folder (case 2) // there are various reasons we may skip that, based on flags set by earlier steps // - zeroth, we perform first->third for any .megaignore file before any others // as any change involving .megaignore may affect anything else we do PerFolderLogSummaryCounts pflsc; for (auto& sequence : sequences) { // The main three steps: moves, node itself, recurse for (unsigned step = 0; step < 3; ++step) { if (step == 2 && sequence.second == sequences.back().second && !belowRemovedCloudNode && !belowRemovedFsNode) { string message; if (pflsc.report(message)) { SYNC_verbose_timed << syncname << message << " out of folder items:" << childRows.size(); } } for (auto i = sequence.first; i != sequence.second; ++i) { // Convenience. auto& childRow = childRows[i]; { // in case of sync failing while we recurse lock_guard guard(syncs.mSyncVecMutex); if (getConfig().mError) return false; } if (syncs.mSyncFlags->earlyRecurseExitRequested) { // restore flags to at least what they were, for when we revisit on next full recurse row.syncNode->scanAgain = std::max(row.syncNode->scanAgain, originalScanAgain); row.syncNode->syncAgain = std::max(row.syncNode->syncAgain, originalSyncAgain); row.syncNode->checkMovesAgain = std::max(row.syncNode->checkMovesAgain, originalCheckMovesAgain); row.syncNode->conflicts = std::max(row.syncNode->conflicts, originalConflicsFlag); LOG_debug << syncname << "recursiveSync early exit due to pending outside request with " << row.syncNode->scanAgain << "-" << row.syncNode->checkMovesAgain << "-" << row.syncNode->syncAgain << " (" << row.syncNode->conflicts << ") at " << fullPath.syncPath; return false; } if (!childRow.cloudClashingNames.empty() || !childRow.fsClashingNames.empty()) { anyNameConflicts = true; row.syncNode->setContainsConflicts(false, true, false); // in case we don't call setItem due to !syncHere } childRow.rowSiblings = &childRows; if (auto* s = childRow.syncNode) { if (auto* f = childRow.fsNode) { // Maintain scanned FSID. childRow.reassignFingerprints(); if (s->fsid_asScanned != f->fsid) { syncs.setScannedFsidReused(fsfp(), f->fsid); s->setScannedFsid(f->fsid, syncs.localnodeByScannedFsid, f->localname, f->fingerprint); } } // Recompute this row's exclusion state. if (s->recomputeExclusionState()) { // Make sure we visit this node's children if its filter state // has changed and we're currently recursing below a removed // node. childRow.recurseBelowRemovedCloudNode |= belowRemovedCloudNode; childRow.recurseBelowRemovedFsNode |= belowRemovedFsNode; } if (s->exclusionState() == ES_EXCLUDED) { if (!s->children.empty()) { // We keep the immediately excluded node (parent folder is not excluded), but remove anything below it LOG_debug << syncname << "Removing " << s->children.size() << " child LocalNodes from excluded " << s->getLocalPath(); vector cs; cs.reserve(s->children.size()); for (auto& child: s->children) { cs.push_back(child.second); } // this technique might seem a bit roundabout, but deletion will cause these to // remove themselves from s->children. // we can't have that happening while we iterate that map. for (auto p : cs) { // deletion this way includes statecachedel delete p; } } if (s->transferSP) { s->resetTransfer(nullptr); } s->checkTreestate(true); continue; } } SyncPath newPath{fullPath}; if (!newPath.appendRowNames(childRow, mFilesystemType) || localdebris.isContainingPathOf(newPath.localPath)) { // This is a legitimate case; eg. we only had a syncNode and it is removed in resolve_delSyncNode // Or if this is the debris folder, ignore it continue; } if (childRow.syncNode) { #ifdef DEBUG auto p = childRow.syncNode->getLocalPath(); assert(0 == compareUtf(p, true, newPath.localPath, true, mCaseInsensitive)); #endif childRow.syncNode->reassignUnstableFsidsOnceOnly(childRow.fsNode); } switch (step) { case 0: // first pass: check for any renames within the folder (or other move activity) // these must be processed first, otherwise if another file // was added with a now-renamed name, an upload would be // attached to the wrong node, resulting in node versions if (syncHere || belowRemovedCloudNode || belowRemovedFsNode) { if (!syncItem_checkMoves(childRow, row, newPath, belowRemovedCloudNode, belowRemovedFsNode)) { if (childRow.itemProcessed) { row.syncNode->setSyncAgain(false, true, false); } } } break; case 1: // second pass: full syncItem processing for each node that wasn't part of a move // moved from the end of syncItem_checkMoves. So we can check ignore files also, as those skip move processing if ((syncHere || belowRemovedCloudNode || belowRemovedFsNode) && syncItem_checkFilenameClashes(childRow, row, newPath)) { row.syncNode->setSyncAgain(false, true, false); break; } if (belowRemovedCloudNode) { // when syncing/scanning below a removed cloud node, we just want to collect up scan fsids // and make syncNodes to visit, so we can be sure of detecting all the moves, // in particular contradictory moves. if (childRow.type() == SRT_XXF && row.exclusionState(*childRow.fsNode) == ES_INCLUDED) { makeSyncNode_fromFS(childRow, row, newPath, false); } } else if (belowRemovedFsNode) { // when syncing/scanning below a removed local node, we just want to // and make syncNodes to visit, so we can be sure of detecting all the moves, // in particular contradictroy moves. if (childRow.type() == SRT_CXX && row.exclusionState(*childRow.cloudNode) == ES_INCLUDED) { resolve_makeSyncNode_fromCloud(childRow, row, newPath, false); } } else if (syncHere && !childRow.itemProcessed) { // normal case: consider all the combinations if (!syncItem(childRow, row, newPath, pflsc)) { if (childRow.syncNode && childRow.syncNode->type != FOLDERNODE) { childRow.syncNode->setSyncAgain(true, true, false); } else { row.syncNode->setSyncAgain(false, true, false); } } } if (childRow.syncNode && childRow.syncNode->type == FILENODE) { childRow.syncNode->checkTreestate(true); } break; case 2: // third and final pass: recurse into the folders if (childRow.syncNode && childRow.syncNode->type > FILENODE && ( (childRow.recurseBelowRemovedCloudNode && (childRow.syncNode->scanRequired() || childRow.syncNode->syncRequired())) || (childRow.recurseBelowRemovedFsNode && childRow.syncNode->syncRequired()) || (recurseHere && !childRow.suppressRecursion && //!childRow.syncNode->deletedFS && we should not check this one, or we won't remove the LocalNode //childRow.syncNode->rareRO().removeNodeHere.expired() && // this is shared_ptr now, not weak_ptr. And checked early in checkForCompletedCloudMovedToDebris childRow.syncNode->rareRO().unlinkHere.expired() && !childRow.syncNode->rareRO().moveToHere))) // don't create new LocalNodes under a moving-to folder, we'll move the already existing LocalNodes when the move completes { // Add watches as necessary. if (childRow.fsNode) { auto result = childRow.syncNode->watch(newPath.localPath, childRow.fsNode->fsid); { // lock `mSyncVecMutex` to protect config change but release as // soon as we have done lock_guard guard(syncs.mSyncVecMutex); // Any fatal errors while adding the watch? if (result == WR_FATAL) { changestate(UNABLE_TO_ADD_WATCH, false, true, true); } } } if (!recursiveSync( childRow, newPath, belowRemovedCloudNode || childRow.recurseBelowRemovedCloudNode, belowRemovedFsNode || childRow.recurseBelowRemovedFsNode, depth + 1)) { earlyExit = true; } } break; } if (!childRow.fsNode && childRow.syncNode) { // if there's no local file/folder, we can't scan // avoid the case of large folder upload, deleted while uploading // that then persists in thinking subtree needs scanning childRow.syncNode->scanAgain = TREE_RESOLVED; childRow.syncNode->parentSetScanAgain = false; } } } //if (ignoreRow && ignoreRow->syncNode /*&& ignoreRow->syncNode->transferSP*/) //{ // if (!row.syncNode->rareRO().filterChain || // !row.syncNode->rareRO().filterChain->mLoadSucceeded) // { // // we can't calculate what's included yet. Let the download complete // // and come back when the .megaignore is present and well-formed // break; // } //} } if (!anyNameConflicts) { // here childRows still contains pointers into lastFolderScan, fsAddedSiblings etc row.syncNode->clearRegeneratableFolderScan(fullPath, childRows); } // If we still don't match the known fs state, and if we added any FSNodes that // aren't part of our scan data (and we think we don't need another scan), // add them to the scan data to avoid re-fingerprinting no the next folder scan if (!row.fsAddedSiblings.empty()) { auto& scan = row.syncNode->lastFolderScan; if (scan && row.syncNode->scanAgain < TREE_ACTION_HERE) { scan->reserve(scan->size() + row.fsAddedSiblings.size()); for (auto& ptr: row.fsAddedSiblings) { scan->push_back(std::move(ptr)); } row.fsAddedSiblings.clear(); } } } // Recompute our LocalNode flags from children // Flags for this row could have been set during calls to the node // If we skipped a child node this time (or if not), the set-parent // flags let us know if future actions are needed at this level for (auto& child : row.syncNode->children) { assert(child.first == child.second->localname); if (child.second->exclusionState() == ES_EXCLUDED) { continue; } if (child.second->type > FILENODE) { row.syncNode->scanAgain = updateTreestateFromChild(row.syncNode->scanAgain, child.second->scanAgain); row.syncNode->syncAgain = updateTreestateFromChild(row.syncNode->syncAgain, child.second->syncAgain); } row.syncNode->checkMovesAgain = updateTreestateFromChild(row.syncNode->checkMovesAgain, child.second->checkMovesAgain); row.syncNode->conflicts = updateTreestateFromChild(row.syncNode->conflicts, child.second->conflicts); if (child.second->parentSetScanAgain) row.syncNode->setScanAgain(false, true, false, 0); if (child.second->parentSetCheckMovesAgain) row.syncNode->setCheckMovesAgain(false, true, false); if (child.second->parentSetSyncAgain) row.syncNode->setSyncAgain(false, true, false); if (child.second->parentSetContainsConflicts) row.syncNode->setContainsConflicts(false, true, false); child.second->parentSetScanAgain = false; // we should only use this one once } // keep sync overlay icons up to date as we recurse (including the sync root node) row.syncNode->checkTreestate(true); SYNC_verbose_timed << syncname << (belowRemovedCloudNode ? "belowRemovedCloudNode " : "") << "Exiting folder with " << row.syncNode->scanAgain << "-" << row.syncNode->checkMovesAgain << "-" << row.syncNode->syncAgain << " (" << row.syncNode->conflicts << ") at " << fullPath.syncPath; return !earlyExit; } std::string Sync::logTriplet(const SyncRow& row, const SyncPath& fullPath) { static constexpr std::string_view PREFIX{" triplet: "}; static constexpr std::string_view NULLPATH{"(null)"}; static constexpr std::string_view NOHANDLE{"(no handle)"}; const auto cloudHandleStr = row.cloudNode ? toNodeHandle(row.cloudNode->handle) : ""; const auto fsPathStr = row.fsNode ? fullPath.localPath.toPath(false) : ""; const auto cloudHandle = row.cloudNode ? std::string_view{cloudHandleStr} : NOHANDLE; const auto cloudPath = row.cloudNode ? std::string_view{fullPath.cloudPath} : NULLPATH; const auto syncPath = row.syncNode ? std::string_view{fullPath.syncPath} : NULLPATH; const auto fsPath = row.fsNode ? std::string_view{fsPathStr} : NULLPATH; std::string s; s.reserve(PREFIX.size() + 3 /* spaces */ + cloudHandle.size() + cloudPath.size() + syncPath.size() + fsPath.size()); s += PREFIX; s += cloudHandle; s += ' '; s += cloudPath; s += ' '; s += syncPath; s += ' '; s += fsPath; return s; } bool Sync::syncItem_checkMoves(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool belowRemovedCloudNode, bool belowRemovedFsNode) { assert(syncs.onSyncThread()); CodeCounter::ScopeTimer rst(syncs.mClient.performanceStats.syncItemCheckMove); // Since we are visiting this node this time, reset its flags-for-parent // They should only stay set when the conditions require it if (row.syncNode) { row.syncNode->parentSetScanAgain = false; row.syncNode->parentSetCheckMovesAgain = false; row.syncNode->parentSetSyncAgain = false; row.syncNode->parentSetContainsConflicts = false; } // Does this row have a sync node? if (auto* s = row.syncNode) { // Is this row part of an ongoing upload? if (auto u = std::dynamic_pointer_cast(s->transferSP)) { // Is it waiting for a putnodes request to complete? if (u->upsyncStarted) { if (!u->wasUpsyncCompleted) { LOG_debug << "Waiting for putnodes to complete, defer move checking: " << logTriplet(row, fullPath); // Then come back later. return false; } else { bool rowResult = false; if (processCompletedUploadFromHere(row, parentRow, fullPath, rowResult, u)) { return rowResult; } } } } } // Under some circumstances on sync startup, our shortname records can be out of date. // If so, we adjust for that here, as the directories are scanned if (row.syncNode && row.fsNode && row.fsNode->shortname) { if ((!row.syncNode->slocalname || *row.syncNode->slocalname != *row.fsNode->shortname) && row.syncNode->localname != *row.fsNode->shortname) { LOG_warn << syncname << "Updating slocalname: " << *row.fsNode->shortname << " at " << fullPath.localPath << " was " << (row.syncNode->slocalname ? row.syncNode->slocalname->toPath(false) : "(null)") << logTriplet(row, fullPath); row.syncNode->setnameparent(row.syncNode->parent, row.syncNode->localname, row.fsNode->cloneShortname()); statecacheadd(row.syncNode); } } if (row.fsNode && (row.fsNode->type == TYPE_UNKNOWN || row.fsNode->fsid == UNDEF)) { // We can't consider moves if we don't even know file/folder or fsid. // Skip ahead to plain item comparison where we wil consider exclusion filters. return false; } // Are we dealing with a no-name triplet? if (row.isNoName()) { LOG_debug << syncname << "No name triplets here. " << "Excluding this triplet from sync for now. " << logTriplet(row, fullPath); return true; } if (row.fsNode && isDoNotSyncFileName(row.fsNode->toName_of_localname(*syncs.fsaccess))) { // don't consider these for moves return true; } // Don't perform any moves until we know the row's exclusion state. if ((row.cloudNode && parentRow.exclusionState(*row.cloudNode) == ES_UNKNOWN) || (row.fsNode && parentRow.exclusionState(*row.fsNode) == ES_UNKNOWN)) { row.itemProcessed = true; row.suppressRecursion = true; return true; } bool rowResult; if (checkForCompletedFolderCreateHere(row, parentRow, fullPath, rowResult)) { row.itemProcessed = true; return rowResult; } if (checkForCompletedCloudMoveToHere(row, parentRow, fullPath, rowResult)) { row.itemProcessed = true; return rowResult; } if (checkForCompletedCloudMovedToDebris(row, parentRow, fullPath, rowResult)) { row.itemProcessed = true; return rowResult; } // Don't try and synchronize special files. if (checkSpecialFile(row, parentRow, fullPath)) { row.itemProcessed = true; return false; } // First deal with detecting local moves/renames and propagating correspondingly // Independent of the syncItem() combos below so we don't have duplicate checks in those. // Be careful of unscannable folder entries which may have no detected type or fsid. // todo: We also have to consider the possibility this item was moved locally and remotely, // and possibly from different locations, or even possibly from the same location. if (row.fsNode && (!row.syncNode || row.syncNode->fsid_lastSynced == UNDEF || row.syncNode->fsid_lastSynced != row.fsNode->fsid || (mCaseInsensitive && row.hasCaseInsensitiveLocalNameChange()))) { if (checkLocalPathForMovesRenames(row, parentRow, fullPath, rowResult, belowRemovedCloudNode)) { row.itemProcessed = true; return rowResult; } } if (row.cloudNode && (!row.syncNode || row.syncNode->syncedCloudNodeHandle.isUndef() || row.syncNode->syncedCloudNodeHandle != row.cloudNode->handle || (mCaseInsensitive && row.hasCaseInsensitiveCloudNameChange()))) { if (checkCloudPathForMovesRenames(row, parentRow, fullPath, rowResult, belowRemovedFsNode)) { row.itemProcessed = true; return rowResult; } } return false; } bool Sync::syncItem_checkBackupCloudNameClash(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath) { assert(isBackup() == threadSafeState->mCanChangeVault); if (!isBackup()) return false; if (!row.cloudClashingNames.empty()) { // Duplicates like this should not occur for backups. However, before SRW, it could occur due to bugs, races etc. // Since stall resolution at the app level won't work, as it can't alter the Vault, we have to address it here // Choose one to delete (to debris) - avoid the one that is marked as synced already, if there is one if (row.syncNode) { if (auto& rn = row.syncNode->rare().removeNodeHere) { if (!rn->failed && !rn->succeeded) { return true; // concentrate on the delete until that is done } LOG_debug << syncname << "[Sync::syncItem_checkBackupCloudNameClash] Completed duplicate backup cloud item to cloud sync debris but some dupes remain (" << rn->succeeded << "): " << fullPath.cloudPath << logTriplet(row, fullPath); rn.reset(); } } // choose which one to remove NodeHandle avoidHandle = row.syncNode ? row.syncNode->syncedCloudNodeHandle : NodeHandle(); CloudNode* cn = nullptr; auto consider = [&](CloudNode* c){ if (c && c->handle != avoidHandle) { if (!cn) cn = c; } }; for (auto& i : row.cloudClashingNames) { consider(i); } consider(row.cloudNode); // set up the operation and pass it to the client thread, track the operation by removeNodeHere if (cn) { LOG_debug << syncname << "[Sync::syncItem_checkBackupCloudNameClash] Moving duplicate backup cloud item to cloud sync debris: " << cn->handle << " " << cn->name << " " << fullPath.cloudPath << logTriplet(row, fullPath); bool fromInshare = inshare; auto debrisNodeHandle = cn->handle; auto deletePtr = std::make_shared(); deletePtr->pathDeleting = fullPath.cloudPath; bool canChangeVault = threadSafeState->mCanChangeVault; syncs.queueClient( [debrisNodeHandle, fromInshare, deletePtr, canChangeVault](MegaClient& mc, TransferDbCommitter&) { if (auto n = mc.nodeByHandle(debrisNodeHandle)) { mc.movetosyncdebris( n.get(), fromInshare, [deletePtr](NodeHandle, Error e) { LOG_debug << "[Sync::syncItem_checkBackupCloudNameClash] Sync " "backup duplicate delete to sync debris completed: " << e << " " << deletePtr->pathDeleting; if (e) deletePtr->failed = true; else deletePtr->succeeded = true; }, canChangeVault); } }); if (row.syncNode) { // Remember that the delete is going on, so we don't do anything else until that resolves // We will detach the synced-fsid side on final completion of this operation. If we do so // earier, the logic will evaluate that updated state too soon, perhaps resulting in downsync. row.syncNode->rare().removeNodeHere = deletePtr; } else { LOG_debug << "[Sync::syncItem_checkBackupCloudNameClash] No row.syncNode -> avoid assigning deletePtr to row.syncNode->rare().removeNodeHere"; } return true; } } return false; // no backup node removal needed } bool Sync::syncItem_checkFilenameClashes(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath) { // Avoid syncing nodes that have multiple clashing names // Except if we previously had a folder (just itself) synced, allow recursing into that one. if (!row.fsClashingNames.empty() || !row.cloudClashingNames.empty()) { if (row.isNoName()) { SYNC_verbose_timed << syncname << "no name Multiple name clashes here. Excluding this node from sync for now." << logTriplet(row, fullPath); return false; } if (syncItem_checkBackupCloudNameClash(row, parentRow, fullPath)) { return false; } if (row.syncNode) row.syncNode->setContainsConflicts(true, false, false); else parentRow.syncNode->setContainsConflicts(false, true, false); if (row.cloudNode && row.syncNode && row.fsNode && row.syncNode->type == FOLDERNODE && row.cloudNode->handle == row.syncNode->syncedCloudNodeHandle && row.fsNode->fsid != UNDEF && row.fsNode->fsid == row.syncNode->fsid_lastSynced) { SYNC_verbose << syncname << "Name clashes at this already-synced folder. We will sync nodes below though." << logTriplet(row, fullPath); return false; } //// Is this clash due to multiple ignore files being present in the cloud? //auto isIgnoreFileClash = [](const SyncRow& row) { // // Any clashes in the cloud? // if (row.cloudClashingNames.empty()) // return false; // // Any clashes on the local disk? // if (!row.fsClashingNames.empty()) // return false; // if (!(row.fsNode || row.syncNode)) // return false; // // Row represents an ignore file? // return row.isIgnoreFile(); //}; //if (isIgnoreFileClash(row)) // return false; SYNC_verbose_timed << syncname << "Multiple name clashes here. Excluding this node from sync for now." << logTriplet(row, fullPath); if (row.syncNode) { row.syncNode->scanAgain = TREE_RESOLVED; row.syncNode->checkMovesAgain = TREE_RESOLVED; row.syncNode->syncAgain = TREE_RESOLVED; } row.itemProcessed = true; row.suppressRecursion = true; return true; } return false; } bool Sync::syncItem_checkDownloadCompletion(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath) { assert(syncs.onSyncThread()); auto downloadPtr = std::dynamic_pointer_cast(row.syncNode->transferSP); if (!downloadPtr) return true; ProgressingMonitor monitor(*this, row, fullPath); if (downloadPtr->wasTerminated) { const bool keepSyncItem = handleTerminatedDownloads(row, fullPath, *downloadPtr, monitor); downloadPtr->terminatedReasonAlreadyKnown = true; return keepSyncItem; } if (auto onlyMtimeUpdated = downloadPtr->wasFileTransferCompleted && downloadPtr->wasDistributed; onlyMtimeUpdated) { assert(downloadPtr->attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly); SYNC_verbose << syncname << "Download setmtime change only at " << logTriplet(row, fullPath); assert((downloadPtr->mtimeAppliedOnDisk && row.syncNode->realScannedFingerprint == row.syncNode->scannedFingerprint) || (!downloadPtr->mtimeAppliedOnDisk && row.syncNode->realScannedFingerprint.equalExceptMtime( row.syncNode->scannedFingerprint))); [[maybe_unused]] const bool isNewFsNode = row.fsNode->fingerprint.mtime == downloadPtr->mtime; assert(isNewFsNode || row.fsNode->fingerprint.equalExceptMtime(*downloadPtr)); if (row.syncNode->syncedFingerprint.isvalid) { assert(row.syncNode->syncedFingerprint.size == row.fsNode->fingerprint.size); assert(row.syncNode->syncedFingerprint.crc == row.fsNode->fingerprint.crc); assert(!isNewFsNode || (row.syncNode->syncedFingerprint.mtime != row.fsNode->fingerprint.mtime)); assert( row.syncNode->syncedFingerprint.equalExceptMtime(row.syncNode->scannedFingerprint)); } assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(*syncs.fsaccess, fullPath.localPath, *downloadPtr)); SYNC_verbose << syncname << "Download complete (setmtime change only), file completed in final destination." << logTriplet(row, fullPath); row.syncNode->resetTransfer(nullptr); row.fsNode->fingerprint.mtime = downloadPtr->mtime; row.syncNode->syncedFingerprint = row.fsNode->fingerprint; row.syncNode->scannedFingerprint = row.fsNode->fingerprint; if (downloadPtr->mtimeAppliedOnDisk) { // realScannedFingerprint remains the actual filesystem value, // as mtime was applied we need to update it. row.syncNode->realScannedFingerprint = row.fsNode->fingerprint; } statecacheadd(row.syncNode); row.syncNode->recordMtimeOnlyOperation(); // Throttle future MAC computations assert(downloadPtr.use_count() == 1); // Sanity return true; } if (downloadPtr->wasFileTransferCompleted) { assert(downloadPtr->downloadDistributor); // Convenience. auto& fsAccess = *syncs.fsaccess; // Clarity. auto& targetPath = fullPath.localPath; // Try and move/rename the downloaded file into its new home. bool nameTooLong = false; bool transientError = false; if (row.fsNode && downloadPtr->okToOverwriteFF.isvalid) { if (row.fsNode->fingerprint != downloadPtr->okToOverwriteFF) { LOG_debug << syncname << "Sync download cannot overwrite unexpected file at " << logTriplet(row, fullPath); monitor.noResult(); return true; // continue matching and see if we stall due to changes on both sides } } std::function afterDistributed = []() { }; // Have we downloaded an ignore file? if (row.isIgnoreFile()) { // Convenience. auto noLogging = FSLogging::noLogging; // Was the existing ignore file (if any) hidden? if (syncs.fsaccess->isFileHidden(targetPath, noLogging)) afterDistributed = std::bind(FileSystemAccess::setFileHidden, std::cref(targetPath), noLogging); } if (downloadPtr->downloadDistributor->distributeTo(targetPath, fsAccess, FileDistributor::MoveReplacedFileToSyncDebris, transientError, nameTooLong, this)) { assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(fsAccess, targetPath, *downloadPtr)); // Perform after-distribution task. afterDistributed(); // Move was successful. SYNC_verbose << syncname << "Download complete, moved file to final destination." << logTriplet(row, fullPath); // Download was moved into place. downloadPtr->wasDistributed = true; // No longer necessary as the transfer's complete. row.syncNode->resetTransfer(nullptr); // Let the engine know the file exists, even if it hasn't detected it yet. // // This is necessary as filesystem events may be delayed. if (auto fsNode = FSNode::fromPath(fsAccess, targetPath, true, FSLogging::logOnError)) // skip case check. We will check for exact match ourselves { if (fsNode->localname != targetPath.leafName()) { row.syncNode->localFSCannotStoreThisName = true; row.syncNode->rare().localFSRenamedToThisName = fsNode->localname; return true; // carry on with checkItem() - this syncNode will cause a stall until cloud rename } // Make this new fsNode part of our sync data structure parentRow.fsAddedSiblings.emplace_back(std::move(*fsNode)); row.fsNode = &parentRow.fsAddedSiblings.back(); row.syncNode->slocalname = row.fsNode->cloneShortname(); } else { row.syncNode->localFSCannotStoreThisName = true; return true; // carry on with checkItem() - this syncNode will cause a stall until cloud rename } // Mark the row as synced with the original Node downloaded, so that // we can chain any cloud moves/renames that occurred in the meantime row.syncNode->setSyncedFsid(row.fsNode->fsid, syncs.localnodeBySyncedFsid, row.fsNode->localname, row.fsNode->cloneShortname()); row.syncNode->realScannedFingerprint = row.fsNode->fingerprint; row.fsNode->fingerprint.mtime = downloadPtr->mtime; row.syncNode->syncedFingerprint = row.fsNode->fingerprint; // It has been scanned previously to receive syncItem_checkDownloadCompletion. // At scannedFingerprint we have a fingerprint with mtime from file system. // Set mtime that is received at download if (row.syncNode->scannedFingerprint == row.syncNode->realScannedFingerprint) { row.syncNode->scannedFingerprint.mtime = row.fsNode->fingerprint.mtime; } if (row.syncNode->syncedFingerprint != row.syncNode->realScannedFingerprint) { SYNC_verbose << syncname << "mtime hasn't been set correctly at fs file (usually Android)"; } row.syncNode->setSyncedNodeHandle(downloadPtr->h); statecacheadd(row.syncNode); } else if (nameTooLong) { SYNC_verbose << syncname << "Download complete but the target's name is too long: " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::DownloadIssue, true, true, {downloadPtr->h, fullPath.cloudPath}, {}, {downloadPtr->downloadDistributor->distributeFromPath()}, {fullPath.localPath, PathProblem::NameTooLongForFilesystem})); // Leave the transfer intact so we don't reattempt the download. // Also allow the syncItem logic to continue, to resolve via user change, eg delete the cloud node return true; } else { // (Transient?) error while moving download into place. SYNC_verbose_timed << syncname << "Download complete, but filesystem move error." << logTriplet(row, fullPath); // Let the monitor know what we're up to. monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::DownloadIssue, true, true, {downloadPtr->h, fullPath.cloudPath}, {}, {downloadPtr->getLocalname()}, {fullPath.localPath, PathProblem::FilesystemErrorDuringOperation})); // Also allow the syncItem logic to continue, to resolve via user change, eg delete the cloud node return true; } } return true; // carry on with checkItem() } bool Sync::handleTerminatedDownloads(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) { switch (downloadFile.mError) { case API_EKEY: return handleTerminatedDownloadsDueMAC(row, fullPath, downloadFile, monitor); case API_EBLOCKED: return handleTerminatedDownloadsDueBlocked(row, fullPath, downloadFile, monitor); case API_EWRITE: return handleTerminatedDownloadsDueWritePerms(row, fullPath, downloadFile); default: return handleTerminatedDownloadsDueUnknown(row, fullPath, downloadFile, monitor); } } bool Sync::handleTerminatedDownloadsDueMAC(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const { if (!downloadFile.terminatedReasonAlreadyKnown) { SYNC_verbose << syncname << "Download was terminated due to MAC verification failure: " << logTriplet(row, fullPath); } monitor.waitingLocal( downloadFile.getLocalname(), SyncStallEntry(SyncWaitReason::DownloadIssue, true, true, {downloadFile.h, fullPath.cloudPath, PathProblem::MACVerificationFailure}, {}, {downloadFile.getLocalname(), PathProblem::MACVerificationFailure}, {fullPath.localPath})); const bool keepStalling = row.cloudNode && row.cloudNode->handle == downloadFile.h; return !keepStalling; } bool Sync::handleTerminatedDownloadsDueBlocked(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const { if (!downloadFile.terminatedReasonAlreadyKnown) { SYNC_verbose << syncname << "Download was terminated due to file being blocked (taken down because of ToS): " << logTriplet(row, fullPath); } monitor.waitingCloud( fullPath.cloudPath, SyncStallEntry(SyncWaitReason::DownloadIssue, true, true, {downloadFile.h, fullPath.cloudPath, PathProblem::CloudNodeIsBlocked}, {}, {}, {})); return true; } bool Sync::handleTerminatedDownloadsDueWritePerms(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile) { if (!downloadFile.terminatedReasonAlreadyKnown) { SYNC_verbose << syncname << "Download was terminated due to API_EWRITE (problem with the temporary " "directory?): " << logTriplet(row, fullPath); } tmpfa.reset(); // remove the download record so we re-evaluate what to do row.syncNode->resetTransfer(nullptr); return true; } bool Sync::handleTerminatedDownloadsDueUnknown(const SyncRow& row, const SyncPath& fullPath, const SyncDownload_inClient& downloadFile, ProgressingMonitor& monitor) const { if (!downloadFile.terminatedReasonAlreadyKnown) { SYNC_verbose << syncname << "Download was terminated due to an unhandled reason (error: " << downloadFile.mError << ") " << logTriplet(row, fullPath); } monitor.waitingCloud( fullPath.cloudPath, SyncStallEntry(SyncWaitReason::DownloadIssue, true, true, {downloadFile.h, fullPath.cloudPath, PathProblem::UnknownDownloadIssue}, {}, {}, {})); assert(false && "Unhandled situation! Investigate the reason and handle it properly"); const bool cloudNodeHasChanged = row.cloudNode && row.cloudNode->handle != downloadFile.h; const bool localFileIsNewer = row.fsNode && row.cloudNode && row.fsNode->fingerprint.mtime > row.cloudNode->fingerprint.mtime; const bool executeRestOfSyncItem = cloudNodeHasChanged || localFileIsNewer; return executeRestOfSyncItem; } struct DifferentValueDetector_nodetype { nodetype_t value = TYPE_UNKNOWN; // returns false if any different values are detected bool combine(nodetype_t v) { if (value == TYPE_UNKNOWN) { value = v; return true; } else if (v == TYPE_UNKNOWN) { return true; } else return v == value; } }; bool Sync::syncItem(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc) { CodeCounter::ScopeTimer rst(syncs.mClient.performanceStats.syncItem); assert(syncs.onSyncThread()); if (row.syncNode && row.fsNode && (row.fsNode->type == TYPE_UNKNOWN || row.fsNode->fsid == UNDEF || (row.fsNode->type == FILENODE && !row.fsNode->fingerprint.isvalid))) { SYNC_verbose_timed << "File lost permissions and we can't identify or fingerprint it anymore: " << logTriplet(row, fullPath); ProgressingMonitor monitor(*this, row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FileIssue, false, false, {}, {}, {fullPath.localPath, PathProblem::CannotFingerprintFile}, {})); return false; } if (row.isNoName()) { LOG_debug << syncname << "[syncItem] No name triplets here. " << "Excluding this triplet from sync for now. " << logTriplet(row, fullPath); return true; } // Check for files vs folders, we can't upload a file over a folder etc. // Turns out we need to let moves be detected first, hence this block is moved from // there, otherwise a delete of a moved node could happen // Hence this block is moved from syncItem_checkMoves DifferentValueDetector_nodetype typeDiffDetect; if ((row.cloudNode && !typeDiffDetect.combine(row.cloudNode->type)) || (row.syncNode && !typeDiffDetect.combine(row.syncNode->type)) || (row.fsNode && !typeDiffDetect.combine(row.fsNode->type))) { if ((row.cloudNode && row.cloudNode->type == TYPE_DONOTSYNC) || (row.fsNode && row.fsNode->type == TYPE_DONOTSYNC)) { // don't do anything for these- eg. files auto generated by win/mac filesystem browsers // Note that historic syncs may have many of these in the cloud already return true; } SYNC_verbose << syncname << "File vs folder detected in this row: " << fullPath.localPath; // we already know these are not move-involved. So, allow resetting the type of the syncNode if (row.syncNode) { nodetype_t resetType = TYPE_UNKNOWN; if (!row.fsNode && row.cloudNode) { SYNC_verbose << syncname << "Resetting sync node type for no fsNode: " << row.cloudNode->type << fullPath.localPath; resetType = row.cloudNode->type; } if (!row.cloudNode && row.fsNode) { SYNC_verbose << syncname << "Resetting sync node type for no cloudNode: " << row.fsNode->type << fullPath.localPath; resetType = row.fsNode->type; } if (row.cloudNode && row.fsNode && row.cloudNode->type == row.fsNode->type) { SYNC_verbose << syncname << "Resetting sync node type to cloud/fs type: " << row.cloudNode->type << fullPath.localPath; resetType = row.cloudNode->type; } if (resetType != TYPE_UNKNOWN) { row.syncNode->type = resetType; row.syncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, row.syncNode->localname, row.syncNode->cloneShortname()); row.syncNode->setSyncedNodeHandle(NodeHandle()); statecacheadd(row.syncNode); return false; } } bool cloudSideChange = row.cloudNode && row.syncNode && row.cloudNode->type != row.syncNode->type; ProgressingMonitor monitor(*this, row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FolderMatchedAgainstFile, true, cloudSideChange, {row.cloudHandleOpt(), fullPath.cloudPath}, {}, {fullPath.localPath}, {})); return false; } unsigned confirmDeleteCount = 0; if (row.syncNode) { // reset the count pre-emptively in case we don't choose SRT_XSX confirmDeleteCount = row.syncNode->confirmDeleteCount; if (confirmDeleteCount > 0) { row.syncNode->confirmDeleteCount = 0; } if (row.syncNode->certainlyOrphaned) { SYNC_verbose << "Removing certainly orphaned LN. " << logTriplet(row, fullPath); delete row.syncNode; row.syncNode = nullptr; if (!row.cloudNode && !row.fsNode) return false; } } // check for cases in progress that we shouldn't be re-evaluating yet if (auto* s = row.syncNode) { // Any rare fields? if (s->hasRare()) { // Move pending? if (!s->rare().movePendingFrom.expired()) { // Don't do anything until the move has completed. return false; } // Move in progress? if (auto& moveFromHere = s->rare().moveFromHere) { if (s->rare().moveToHere == moveFromHere && (moveFromHere->succeeded || moveFromHere->failed)) { // rename of this node (case insensitive but case changed)? moveFromHere.reset(); s->rare().moveToHere.reset(); s->updateMoveInvolvement(); } else if (moveFromHere->failed || moveFromHere->syncCodeProcessedResult) { // Move's completed. moveFromHere.reset(); s->updateMoveInvolvement(); } else if (moveFromHere.use_count() == 1) { SYNC_verbose << "Removing orphaned moveFromHere pointer at: " << logTriplet(row, fullPath); moveFromHere.reset(); s->updateMoveInvolvement(); } else { // Move's still underway. return false; } } // Is this row (effectively) excluded? if (s->exclusionState() != ES_INCLUDED) { // Is it a move target? if (s->rare().moveToHere) { assert(!s->rare().moveToHere->failed); assert(!s->rare().moveToHere->syncCodeProcessedResult); assert(s->rare().moveToHere->succeeded); // Necessary as excluded rows may not reach CSF. resolve_checkMoveComplete(row, parentRow, fullPath); } } // Unlink in progress? if (!s->rare().unlinkHere.expired()) { // Unlink's still underway. return false; } } // as it turns out, this is too early. // we need to wait for resolve_rowMatched to recognise it // otherwise there may be more renames to process first //s->checkTransferCompleted(row, parentRow, fullPath); // Is the row excluded? if (s->exclusionState() == ES_EXCLUDED) { // Can we remove the node from memory? auto removable = true; // ignore files cannot be ignored assert(!s->isIgnoreFile()); // Let transfers complete. removable &= !s->transferSP; // Keep the node (as ignored) but purge the children if (removable) { // Extra sanity. assert(!s->rareRO().moveFromHere); assert(!s->rareRO().moveToHere); if (!s->children.empty()) { LOG_debug << syncname << "syncItem removing child LocalNodes from excluded " << s->getLocalPath(); vector cs; cs.resize(s->children.size()); for (auto& i : s->children) { cs.push_back(i.second); } for (auto p : cs) { delete p; } } } return true; // consider it synced (ie, do not revisit) } } // Check blocked status. Todo: figure out use blocked flag clearing if (!row.syncNode && row.fsNode && ( row.fsNode->isBlocked || row.fsNode->type == TYPE_UNKNOWN) && parentRow.syncNode->exclusionState(row.fsNode->localname, TYPE_UNKNOWN, -1) == ES_INCLUDED) { // so that we can checkForScanBlocked() immediately below if (!makeSyncNode_fromFS(row, parentRow, fullPath, false)) { row.suppressRecursion = true; row.itemProcessed = true; return false; } } if (row.syncNode && row.syncNode->checkForScanBlocked(row.fsNode)) { row.suppressRecursion = true; row.itemProcessed = true; return false; } if (row.syncNode && row.syncNode->transferSP) { if (!syncItem_checkDownloadCompletion(row, parentRow, fullPath)) { return false; } } auto rowType = row.type(); if (row.syncNode && rowType != SRT_XSX && row.syncNode->localFSCannotStoreThisName) { LocalPath fsReportPath; if (!row.syncNode->rareRO().localFSRenamedToThisName.empty()) { fsReportPath = fullPath.localPath.parentPath(); fsReportPath.appendWithSeparator(row.syncNode->rareRO().localFSRenamedToThisName, true); } ProgressingMonitor monitor(*this, row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FileIssue, false, false, {row.cloudHandleOpt(), fullPath.cloudPath, PathProblem::FilesystemCannotStoreThisName}, {}, {fsReportPath, PathProblem::FilesystemCannotStoreThisName}, {})); return false; } auto createStallIssueEread = [&]() -> void { ProgressingMonitor monitor(*this, row, fullPath); monitor.waitingLocal( fullPath.localPath, SyncStallEntry(SyncWaitReason::FileIssue, false, false, {}, {}, {fullPath.localPath, PathProblem::CannotFingerprintFile}, {})); }; switch (rowType) { case SRT_CSF: { CodeCounter::ScopeTimer csfTime(syncs.mClient.performanceStats.syncItemCSF); // Are we part of a move and was our source a download-in-progress? resolve_checkMoveDownloadComplete(row, fullPath); // all three exist; compare // Use async MAC computation for mtime-only differences (non-blocking) auto [fsCloudEqualRes, fcMacLocal, fcMacRemote, mismatch] = syncEqualFsCloudExcludingMtimeAsync(syncs.mClient, *row.cloudNode, *row.fsNode, fullPath.localPath, *row.syncNode); if (fsCloudEqualRes == NODE_COMP_PENDING) { // MAC computation is in progress, come back later row.syncNode->setSyncAgain(true, false, false); return false; } if (fsCloudEqualRes == NODE_COMP_EREAD) { createStallIssueEread(); return false; } auto fsCloudJustMtimeChanged = fsCloudEqualRes == NODE_COMP_DIFFERS_MTIME; bool cloudSyncNodeEqual = syncEqual(*row.cloudNode, *row.syncNode); bool fsSyncNodeEqual = syncEqual(*row.fsNode, *row.syncNode); if (fsCloudEqualRes == NODE_COMP_EQUAL) { // success! - fsNode and CloudNode are equal (this row is synced) if (!cloudSyncNodeEqual || !fsSyncNodeEqual) { // syncNode is outdated, so update syncedFingerprint row.syncNode->syncedFingerprint = row.fsNode->fingerprint; assert(row.syncNode->syncedFingerprint == row.cloudNode->fingerprint); statecacheadd(row.syncNode); } return resolve_rowMatched(row, parentRow, fullPath, pflsc); } // Handle mtime-only difference in both sides. We need to establish a baseline by // setting syncedFingerprint to the older mtime side, so the newer side "wins" and syncs // over. if (fsCloudJustMtimeChanged && !cloudSyncNodeEqual && !fsSyncNodeEqual) { const bool localIsNewer = row.fsNode->fingerprint.mtime > row.cloudNode->fingerprint.mtime; // Files have same content (verified by MAC) but different mtime if (localIsNewer || isBackup()) { if (localIsNewer) { // Cloud is older -> local will sync up (cloud gets local's mtime) LOG_verbose << syncname << "CSF mtime-only diff: setting syncedFp to " "cloud (older). Local mtime will sync up." << logTriplet(row, fullPath); } else // isBackup { LOG_verbose << syncname << "CSF mtime-only diff: cloud is newer, but it is a backup, " "so setting syncedFp to cloud. Local mtime will sync up." << logTriplet(row, fullPath); } cloudSyncNodeEqual = true; } else { // Local is older -> cloud will sync down (local gets cloud's mtime) LOG_verbose << syncname << "CSF mtime-only diff: setting syncedFp to " "local (older). Cloud mtime will sync down." << logTriplet(row, fullPath); fsSyncNodeEqual = true; } } if (cloudSyncNodeEqual) { // fsNode changed, upload the change const auto attributeOnlyUpdate = fsCloudEqualRes == NODE_COMP_DIFFERS_MTIME ? SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly : (fsCloudEqualRes == NODE_COMP_DIFFERS_FP && mismatch == FingerprintMismatch::CrcOnly) ? SyncTransfer_inClient::AttributeOnlyUpdate::CrcOnly : SyncTransfer_inClient::AttributeOnlyUpdate::None; return resolve_upsync(row, parentRow, fullPath, pflsc, fcMacLocal, attributeOnlyUpdate); } if (fsSyncNodeEqual) { // cloudNode changed, download the change if (isBackup()) { LOG_warn << "CSF with cloud node change and this is a BACKUP!" << " Local file will be upsynced to fix the mismatched cloud node." << " Triplet: " << logTriplet(row, fullPath); assert(false && "CSF with cloud node change should not happen for a backup!"); } else { if (fcMacRemote == INVALID_META_MAC) { LOG_warn << "syncItem: CloudNode and FsNode are not equal but " "metamacCloudNode has not been calculated"; } return resolve_downsync( row, parentRow, fullPath, true, pflsc, fcMacRemote, fsCloudJustMtimeChanged ? SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly : SyncTransfer_inClient::AttributeOnlyUpdate::None); } } if (auto uploadPtr = threadSafeState->isNodeAnExpectedUpload(row.cloudNode->parentHandle, row.cloudNode->name)) { if (row.cloudNode->fingerprint == *uploadPtr && uploadPtr.get() == row.syncNode->transferSP.get()) { // we uploaded a file and the user already re-updated the local version of the // file SYNC_verbose << syncname << "Node is a recent upload, while the FSFile is already updated: " << fullPath.cloudPath << logTriplet(row, fullPath); row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->syncedFingerprint = row.cloudNode->fingerprint; row.syncNode->transferSP.reset(); return false; } } if (isBackup()) { // for backups, we only change the cloud LOG_warn << "CSF for a BACKUP with CloudNode != SyncNode != FSNode -> resolve upsync " "to avoid user intervention" << " " << logTriplet(row, fullPath); return resolve_upsync(row, parentRow, fullPath, pflsc, fcMacLocal, fsCloudJustMtimeChanged ? SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly : SyncTransfer_inClient::AttributeOnlyUpdate::None); } // both changed, so we can't decide without the user's help return resolve_userIntervention(row, fullPath); } case SRT_XSF: { CodeCounter::ScopeTimer xsfTime(syncs.mClient.performanceStats.syncItemXSF); if (row.syncNode->type == TYPE_DONOTSYNC || row.isLocalOnlyIgnoreFile() || row.isNoName()) { // we do not upload do-not-sync files (eg. system+hidden, on windows) return true; } if (!row.syncNode->syncedCloudNodeHandle.isUndef() && row.syncNode->fsid_lastSynced != UNDEF && row.syncNode->fsid_lastSynced == row.fsNode->fsid // on disk, content could be changed without an fsid change && syncEqual(*row.fsNode, *row.syncNode)) { if (isBackup()) { // for backups, we only change the cloud LOG_warn << "XSF with cloud node gone when this item was synced before and this is " "a BACKUP!" << " This will result on the backup being disabled." << " Triplet: " << logTriplet(row, fullPath); assert(false && "XSF - cloud item not present for a previously " "synced item should not happen for a backup!"); } else { // used to be fully synced and the fs side still has that version // remove in the fs (if not part of a move) return resolve_cloudNodeGone(row, parentRow, fullPath); } } // either // - cloud item did not exist before; upsync // - the fs item has changed too; upsync (this lets users recover from the both sides // changed state - user deletes the one they don't want anymore) return resolve_upsync(row, parentRow, fullPath, pflsc, INVALID_META_MAC, SyncTransfer_inClient::AttributeOnlyUpdate::None); } case SRT_CSX: { CodeCounter::ScopeTimer csxTime(syncs.mClient.performanceStats.syncItemCSX); // local item not present if (isBackup()) { // make the cloud the same as local return resolve_fsNodeGone(row, parentRow, fullPath); } // where we uploaded this node but the fsNode moved already, consider the cloud side synced. if (row.syncNode->fsid_lastSynced != UNDEF && row.syncNode->syncedCloudNodeHandle.isUndef() && syncEqual(*row.cloudNode, *row.syncNode)) { row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); return false; // let the next pass determine if it's a move } if (auto uploadPtr = threadSafeState->isNodeAnExpectedUpload(row.cloudNode->parentHandle, row.cloudNode->name)) { // The local file is no longer at the matching position // and we need to set it up to be moved/deleted correspondingly // Setting the synced fsid without a corresponding FSNode will cause move detection SYNC_verbose << syncname << "Node is a recent upload, sync node present, source FS file is no longer here: " << fullPath.cloudPath << logTriplet(row, fullPath); row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->setSyncedFsid(uploadPtr->sourceFsid, syncs.localnodeBySyncedFsid, uploadPtr->sourceLocalname, nullptr); row.syncNode->syncedFingerprint = row.cloudNode->fingerprint; row.syncNode->transferSP.reset(); return false; } else if (row.syncNode->fsid_lastSynced == UNDEF) { // fs item did not exist before; downsync return resolve_downsync(row, parentRow, fullPath, false, pflsc, INVALID_META_MAC, SyncTransfer_inClient::AttributeOnlyUpdate::None); } else if (row.syncNode->syncedCloudNodeHandle != row.cloudNode->handle) { // the cloud item has changed too; downsync (this lets users recover from both sides // changed state - user deletes the one they don't want anymore) return resolve_downsync(row, parentRow, fullPath, false, pflsc, INVALID_META_MAC, SyncTransfer_inClient::AttributeOnlyUpdate::None); } else { // It might be a removal; it might be that the local fs item was moved during upload and now the cloud node from the upload has appeared // resolve_fsNodeGone only removes if we know the fs item isn't anywhere else in the sync filesystem subtrees. if (row.syncNode->fsid_lastSynced != UNDEF && !row.syncNode->syncedCloudNodeHandle.isUndef() // for cloud nodes, same handle must be same content && row.syncNode->syncedCloudNodeHandle == row.cloudNode->handle) { // used to be fully synced and the cloud side still has that version // remove in the cloud (if not part of a move) return resolve_fsNodeGone(row, parentRow, fullPath); } LOG_debug << "interesting case, does it occur?"; assert(false && "This case is not expected to happen"); return false; } } case SRT_XSX: { CodeCounter::ScopeTimer xsxTime(syncs.mClient.performanceStats.syncItemXSX); // local and cloud disappeared; remove sync item also return resolve_delSyncNode(row, parentRow, fullPath, confirmDeleteCount); } case SRT_CXF: { CodeCounter::ScopeTimer cxfTime(syncs.mClient.performanceStats.syncItemCXF); // we have to check both, due to the size parameter auto cloudside = parentRow.exclusionState(row.fsNode->localname, row.fsNode->type, row.fsNode->fingerprint.size); auto localside = parentRow.exclusionState(row.fsNode->localname, row.cloudNode->type, row.cloudNode->fingerprint.size); // use fsNode's name for convenience, size is what matters if (cloudside == ES_EXCLUDED || localside == ES_EXCLUDED) { LOG_verbose << "CXF case is excluded by size: " << row.fsNode->fingerprint.size << " vs " << row.cloudNode->fingerprint.size << logTriplet(row, fullPath); return true; } if (cloudside != ES_INCLUDED || localside != ES_INCLUDED) { LOG_verbose << "Exclusion state unknown, come back later: " << int(cloudside) << " vs " << int(localside) << logTriplet(row, fullPath); return false; } if (isBackup()) { // create the sync node from local to do the upsync later return resolve_makeSyncNode_fromFS(row, parentRow, fullPath, false); } if (auto quickResult = quickFingerprintComparison(*row.cloudNode, *row.fsNode); quickResult.has_value()) { // Fingerprints fully match or differ in more than mtime (type, size, CRC) auto fsCloudEqualRes = std::get<0>(*quickResult); auto mismatch = std::get<3>(*quickResult); if (fsCloudEqualRes == NODE_COMP_EQUAL) { return resolve_makeSyncNode_fromFS(row, parentRow, fullPath, false); } else if (fsCloudEqualRes == NODE_COMP_DIFFERS_FP && mismatch == FingerprintMismatch::CrcOnly) { const auto& cloudFp = row.cloudNode->fingerprint; const auto& localFp = row.fsNode->fingerprint; if (const auto crcMismatchWasDueTo32bitOverflow = compareLegacyBuggySparseCrc(syncs.mClient, fullPath.localPath, localFp.size, cloudFp.crc); crcMismatchWasDueTo32bitOverflow) { LOG_warn << "CXF case, CRC mismatch due to buggy CRC of cloud Node. We first need " "to create syncnode, and in next iteration in CSF CRC will be " "detected as buggy and fingerprint from cloud node will be updated"; return resolve_makeSyncNode_fromCloud(row, parentRow, fullPath, false); } else { LOG_warn << "CXF case, CRC mismatch."; return resolve_userIntervention(row, fullPath); } } else { return resolve_userIntervention(row, fullPath); } } LOG_verbose << "CXF case with fingerprint match except mtime, creating LocalNode for " "async MAC verification in CSF. " << "Local mtime: " << row.fsNode->fingerprint.mtime << ", Cloud mtime: " << row.cloudNode->fingerprint.mtime << logTriplet(row, fullPath); return resolve_makeSyncNode_fromFS(row, parentRow, fullPath, false); } case SRT_XXF: { CodeCounter::ScopeTimer xxfTime(syncs.mClient.performanceStats.syncItemXXF); // Don't create a sync node for this file unless we know that it's included. if (parentRow.exclusionState(*row.fsNode) != ES_INCLUDED) return true; // Item exists locally only. Check if it was moved/renamed here, or Create // If creating, next run through will upload it return resolve_makeSyncNode_fromFS(row, parentRow, fullPath, false); } case SRT_CXX: { CodeCounter::ScopeTimer cxxTime(syncs.mClient.performanceStats.syncItemCXX); // Don't create sync nodes unless we know the row is included. if (parentRow.exclusionState(*row.cloudNode) != ES_INCLUDED) return true; // item exists remotely only return resolve_makeSyncNode_fromCloud(row, parentRow, fullPath, false); } default: { // Silence compiler warning. break; } } // switch // SRT_XXX (should not occur) // no entries - can occur when names clash, but should be caught above CodeCounter::ScopeTimer rstXXX(syncs.mClient.performanceStats.syncItemXXX); assert(false); return false; } bool Sync::resolve_checkMoveDownloadComplete(SyncRow& row, SyncPath& fullPath) { // Convenience. auto target = row.syncNode; // Are we part of an ongoing move? auto movePtr = target->rareRO().moveToHere; // No part of an ongoing move. if (!movePtr) return false; // Are we still associated with the move source? const NodeMatchByFSIDAttributes moveNodeAttributes{movePtr->sourceType, *movePtr->sourceFsfp, cloudRootOwningUser, movePtr->sourceFingerprint, FileFingerprint{}}; const auto [sourceExclusionUnknown, source] = syncs.findLocalNodeBySyncedFsid(movePtr->sourceFsid, moveNodeAttributes, fullPath.localPath); if (sourceExclusionUnknown) { LOG_debug << "In resolve_checkMoveDownloadComplete, download source's exclusion state is unknown. at: " << logTriplet(row, fullPath); } // No longer associated with the move source. if (!source) return false; // Sanity check. if (source->rareRO().moveFromHere != movePtr) { // This can happen if we see the user moved local node N to node P. // We start this corresponding cloud move. But in the meantime, // the user has locally moved N again LOG_debug << "Move source does not match the movePtr anymore. (" << source->getLocalPath() << ") at: " << logTriplet(row, fullPath); row.syncNode->rare().moveToHere->syncCodeProcessedResult = true; // a visit to the source node with the corresponding moveFromHere ptr will now remove it row.syncNode->rare().moveToHere.reset(); row.syncNode->updateMoveInvolvement(); return false; } // Is the source part of an ongoing download? auto download = std::dynamic_pointer_cast(source->transferSP); // Not part of an ongoing download. if (!download) return false; LOG_debug << "Completing move of in-progress download: " << logTriplet(row, fullPath); // Consider the move complete. source->moveContentTo(target, fullPath.localPath, true); source->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, source->localname, source->cloneShortname()); source->setSyncedNodeHandle(NodeHandle()); source->sync->statecacheadd(source); source->rare().moveFromHere->syncCodeProcessedResult = true; source->rare().moveFromHere.reset(); source->trimRareFields(); source->updateMoveInvolvement(); target->rare().moveToHere->syncCodeProcessedResult = true; target->rare().moveToHere.reset(); target->trimRareFields(); target->updateMoveInvolvement(); // Consider us synced with the local disk. target->setSyncedFsid(movePtr->sourceFsid, syncs.localnodeBySyncedFsid, row.fsNode->localname, row.fsNode->cloneShortname()); target->syncedFingerprint = movePtr->sourceFingerprint; target->sync->statecacheadd(target); // Terminate the transfer if we're not a match with the local disk. if (row.fsNode->fsid != movePtr->sourceFsid || row.fsNode->fingerprint != movePtr->sourceFingerprint) { LOG_debug << "Move-target no longer matches move-source: " << logTriplet(row, fullPath); target->resetTransfer(nullptr); } // We should now be good to go. return true; } bool Sync::resolve_checkMoveComplete(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath) { // Confirm that the move details are the same as recorded (LocalNodes may have changed or been deleted by now, etc. auto movePtr = row.syncNode->rare().moveToHere; LOG_debug << syncname << "Checking move source/target by fsid " << toHandle(movePtr->sourceFsid); const NodeMatchByFSIDAttributes moveNodeAttributes{movePtr->sourceType, *movePtr->sourceFsfp, cloudRootOwningUser, movePtr->sourceFingerprint, FileFingerprint{}}; const auto [sourceExclusionUnknown, sourceSyncNode] = syncs.findLocalNodeBySyncedFsid(movePtr->sourceFsid, moveNodeAttributes, fullPath.localPath); if (sourceExclusionUnknown) { LOG_debug << "In resolve_checkMoveComplete, move source's exclusion state is unknown. at: " << logTriplet(row, fullPath); } if (sourceSyncNode) { LOG_debug << syncname << "Sync cloud move/rename from : " << sourceSyncNode->getCloudPath(true) << " resolved here! " << logTriplet(row, fullPath); assert(sourceSyncNode == movePtr->sourcePtr); // remove fsid (and handle) from source node, so we don't detect // that as a move source anymore sourceSyncNode->setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, sourceSyncNode->localname, sourceSyncNode->cloneShortname()); sourceSyncNode->setSyncedNodeHandle(NodeHandle()); sourceSyncNode->sync->statecacheadd(sourceSyncNode); // Move all the LocalNodes under the source node to the new location // We can't move the source node itself as the recursive callers may be using it sourceSyncNode->moveContentTo(row.syncNode, fullPath.localPath, true); row.syncNode->setScanAgain(false, true, true, 0); sourceSyncNode->setScanAgain(true, false, false, 0); sourceSyncNode->rare().moveFromHere->syncCodeProcessedResult = true; sourceSyncNode->rare().moveFromHere.reset(); sourceSyncNode->trimRareFields(); sourceSyncNode->updateMoveInvolvement(); // If this node was repurposed for the move, rather than the normal case of creating a fresh one, we remove the old content if it was a folder // We have to do this after all processing of sourceSyncNode, in case the source was (through multiple operations) one of the subnodes about to be removed. // TODO: however, there is a risk of name collisions - probably we should use a multimap for LocalNode::children. for (auto& oldc : movePtr->priorChildrenToRemove) { for (auto& c : row.syncNode->children) { if (c.first == oldc.first && c.second == oldc.second) { delete c.second; // removes itself from the parent map break; } } } } else { // just alert us to this an double check the case in the debugger assert(false); } // regardless, make sure we don't get stuck row.syncNode->rare().moveToHere->syncCodeProcessedResult = true; row.syncNode->rare().moveToHere.reset(); row.syncNode->trimRareFields(); row.syncNode->updateMoveInvolvement(); return sourceSyncNode != nullptr; } bool Sync::resolve_rowMatched(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc) { assert(syncs.onSyncThread()); // these comparisons may need to be adjusted for UTF, escapes assert(row.syncNode->fsid_lastSynced != row.fsNode->fsid || 0 == compareUtf(row.syncNode->localname, true, row.fsNode->localname, true, mCaseInsensitive)); assert(row.syncNode->fsid_lastSynced == row.fsNode->fsid || 0 == compareUtf(row.syncNode->localname, true, row.fsNode->localname, true, mCaseInsensitive)); assert((!!row.syncNode->slocalname == !!row.fsNode->shortname) && (!row.syncNode->slocalname || (*row.syncNode->slocalname == *row.fsNode->shortname))); if (row.syncNode->fsid_lastSynced != row.fsNode->fsid || row.syncNode->syncedCloudNodeHandle != row.cloudNode->handle || row.syncNode->localname != row.fsNode->localname) { if (row.syncNode->hasRare() && row.syncNode->rare().moveToHere) { resolve_checkMoveComplete(row, parentRow, fullPath); } LOG_verbose << syncname << "Row is synced, setting fsid and nodehandle" << logTriplet(row, fullPath); if (row.syncNode->type == FOLDERNODE && row.syncNode->fsid_lastSynced != row.fsNode->fsid) { // a folder disappeared and was replaced by a different one - scan it all row.syncNode->setScanAgain(false, true, true, 0); } if (mCaseInsensitive && 0 == compareUtf(row.syncNode->localname, true, row.fsNode->localname, true, true) && 0 != compareUtf(row.syncNode->localname, true, row.fsNode->localname, true, false)) { SYNC_verbose << "Updating LocalNode localname case to match fs: " << row.fsNode->localname << " at " << logTriplet(row, fullPath); } row.syncNode->setSyncedFsid(row.fsNode->fsid, syncs.localnodeBySyncedFsid, row.fsNode->localname, row.fsNode->cloneShortname()); row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->syncedFingerprint = row.fsNode->fingerprint; if (row.syncNode->transferSP) { LOG_debug << "Clearing transfer for matched row at " << logTriplet(row, fullPath); row.syncNode->resetTransfer(nullptr); } if (mCaseInsensitive && !row.syncNode->namesSynchronized && 0 == compareUtf(row.cloudNode->name, true, row.fsNode->localname, true, false)) { // name is equal (taking escaping and case into account) so going forward, also propagate renames that only change case assert(row.fsNode->localname == row.syncNode->localname); row.syncNode->namesSynchronized = true; } statecacheadd(row.syncNode); ProgressingMonitor monitor(*this, row, fullPath); // not stalling } else { if (!pflsc.alreadySyncedCount) { // This line is too verbose when debugging large syncs. Instead, report the already-synced count in the containing folder SYNC_verbose_timed << syncname << "Row was already synced" << logTriplet(row, fullPath); } pflsc.alreadySyncedCount += 1; } if (row.syncNode->type == FILENODE) { row.syncNode->scanAgain = TREE_RESOLVED; row.syncNode->checkMovesAgain = TREE_RESOLVED; row.syncNode->syncAgain = TREE_RESOLVED; } return true; } bool Sync::resolve_makeSyncNode_fromFS(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced) { makeSyncNode_fromFS(row, parentRow, fullPath, considerSynced); // the row is not in sync, so we return false // future visits will make more steps towards getting in sync return false; } bool Sync::makeSyncNode_fromFS(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced) { // this version of the function returns true/false depending on whether it was able to make the SyncNode. assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); if (row.fsNode->type == FILENODE && !row.fsNode->fingerprint.isvalid) { SYNC_verbose_timed << "We can't create a LocalNode yet without a FileFingerprint: " << logTriplet(row, fullPath); // we couldn't get the file crc yet (opened by another proecess, etc) monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FileIssue, false, false, {}, {}, {fullPath.localPath, PathProblem::CannotFingerprintFile}, {})); return false; } // this really is a new node: add LOG_debug << syncname << "Creating LocalNode from FS with fsid " << toHandle(row.fsNode->fsid) << " at: " << fullPath.localPath << logTriplet(row, fullPath); assert(row.syncNode == nullptr); row.syncNode = new LocalNode(this); row.syncNode->init(row.fsNode->type, parentRow.syncNode, fullPath.localPath, row.fsNode->cloneShortname()); row.syncNode->setScannedFsid(row.fsNode->fsid, syncs.localnodeByScannedFsid, row.fsNode->localname, row.fsNode->fingerprint); if (row.fsNode->type == FILENODE) { row.syncNode->scannedFingerprint = row.fsNode->fingerprint; row.syncNode->realScannedFingerprint = row.fsNode->fingerprint; } if (considerSynced) { // we should be careful about considering synced, eg this might be a node moved from somewhere else SYNC_verbose << "Considering this node synced on fs side already: " << toHandle(row.fsNode->fsid); row.syncNode->setSyncedFsid(row.fsNode->fsid, syncs.localnodeBySyncedFsid, row.fsNode->localname, row.fsNode->cloneShortname()); row.syncNode->syncedFingerprint = row.fsNode->fingerprint; } if (row.syncNode->type > FILENODE) { row.syncNode->setScanAgain(false, true, true, 0); } statecacheadd(row.syncNode); // success making the LocalNode/SyncNode return true; } bool Sync::resolve_makeSyncNode_fromCloud(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, bool considerSynced) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); SYNC_verbose << syncname << "Creating LocalNode from Cloud at: " << fullPath.cloudPath << logTriplet(row, fullPath); assert(row.syncNode == nullptr); row.syncNode = new LocalNode(this); if (row.cloudNode->type == FILENODE) { row.syncNode->syncedFingerprint = row.cloudNode->fingerprint; } row.syncNode->init(row.cloudNode->type, parentRow.syncNode, fullPath.localPath, nullptr); if (auto uploadPtr = threadSafeState->isNodeAnExpectedUpload(row.cloudNode->parentHandle, row.cloudNode->name)) { // If we make a LocalNode for an upload we created ourselves, // it's because the local file is no longer at the matching position // and we need to set it up to be moved correspondingly // Setting the synced fsid without a corresponding FSNode will cause move detection SYNC_verbose << syncname << "Node is a recent upload, but source FS file is no longer here: " << fullPath.cloudPath << logTriplet(row, fullPath); row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->setSyncedFsid(uploadPtr->sourceFsid, syncs.localnodeBySyncedFsid, uploadPtr->sourceLocalname, nullptr); row.syncNode->syncedFingerprint = row.cloudNode->fingerprint; return false; } if (considerSynced) { assert(row.cloudNode->fingerprint.isvalid); row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); } if (row.syncNode->type > FILENODE) { row.syncNode->setSyncAgain(false, true, true); } statecacheadd(row.syncNode); row.syncNode->setSyncAgain(true, false, false); return false; } bool Sync::resolve_delSyncNode(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, unsigned deleteCounter) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); if (row.syncNode->hasRare()) { // We should never reach this function if pendingFrom is live. assert(row.syncNode->rareRO().movePendingFrom.expired()); if (row.syncNode->rare().moveToHere && row.syncNode->rare().moveToHere->inProgress()) { SYNC_verbose_timed << syncname << "Not deleting with still-moving/renaming source node to:" << logTriplet(row, fullPath); return false; } if (row.syncNode->rare().moveFromHere && !row.syncNode->rare().moveFromHere->syncCodeProcessedResult) { SYNC_verbose_timed << syncname << "Not deleting still-moving/renaming source node from:" << logTriplet(row, fullPath); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {NodeHandle(), "", PathProblem::DestinationPathInUnresolvedArea}, {fullPath.localPath}, {LocalPath(), PathProblem::DestinationPathInUnresolvedArea})); return false; } } // We need to be sure we really can delete this node. // The risk is that it may actually be part of a move // and the other end of the move // (ie a new LocalNode with matching fsid/nodehandle) // is only appearing in this traversal of the tree. // It may have appeared but hasn't been evaluated for moves. // We may not even have processed that new node/path yet. // To work around this, we don't delete on the first go // instead, we start a counter. If we decide that yes // this node should be deleted on two consecutive passes // then its ok, we've confirmed it really isn't part of a move. if (auto u = std::dynamic_pointer_cast(row.syncNode->transferSP)) { if (u->upsyncStarted && !u->wasUpsyncCompleted) { // if we delete the LocalNode now, then the appearance of the uploaded file will cause a download which would be incorrect // if it hadn't started putnodes, it would be ok to delete (which should also cancel the transfer) SYNC_verbose << "This LocalNode is a candidate for deletion, but was also uploading a file and putnodes is in progress." << logTriplet(row, fullPath); return false; } } // setting on the first pass, or restoring on subsequent (part of the auto-reset if it's no longer routed to _delSyncNode) row.syncNode->confirmDeleteCount = 1; if (deleteCounter == 0) { // make sure we've done a full pass including the rest of this iteration over the node trees, // to see if there is any other relevant info available (such as, this is a move rather than delete) SYNC_verbose << "This LocalNode is a candidate for deletion, we'll confirm on the next pass." << logTriplet(row, fullPath); // whenever this node is visited and we don't call resolve_delSyncNode(), this is reset to 0 // this prevents flop-flopping into and out of stall state if this is the only problem node return false; } if (row.syncNode->moveAppliedToLocal) { // we detected a cloud move from here, and applied the corresponding local move. Ok to delete this LocalNode. SYNC_verbose << syncname << "Deleting Localnode (moveAppliedToLocal)" << logTriplet(row, fullPath); } else if (row.syncNode->deletedFS) { // we detected a local deletion here, and applied that deletion in the cloud too. Ok to delete this LocalNode. SYNC_verbose << syncname << "Deleting Localnode (deletedFS)" << logTriplet(row, fullPath); } else if (mMovesWereComplete) { // Since moves are complete, we can remove this LocalNode now, it can't be part of any move anymore // Up to that point, we should not remove it or we won't be able to detect clashing moves of the same node. SYNC_verbose << syncname << "Deleting Localnode (movesWereComplete)" << logTriplet(row, fullPath); } else { // is the old Node or FSNode somewhere else now? If we can't find them, then this node can't have been moved bool fsNodeIsElsewhere = false; bool cloudNodeIsElsewhere = false; string fsElsewhereLocation; if (!mScanningWasComplete) { fsNodeIsElsewhere = true; } else if (row.syncNode->fsid_lastSynced != UNDEF) { const NodeMatchByFSIDAttributes syncNodeAttributes{row.syncNode->type, fsfp(), cloudRootOwningUser, row.syncNode->syncedFingerprint, FileFingerprint{}}; const auto [sourceFsidExclusionUnknown, fsElsewhere] = syncs.findLocalNodeByScannedFsid(row.syncNode->fsid_lastSynced, syncNodeAttributes, fullPath.localPath); if (fsElsewhere) { fsNodeIsElsewhere = true; fsElsewhereLocation = fsElsewhere->getCloudPath(false); SYNC_verbose << "LocalNode considered for deletion, but fsNode is elsewhere: " << fsElsewhere->getLocalPath() << logTriplet(row, fullPath); } else if (sourceFsidExclusionUnknown) { fsNodeIsElsewhere = true; SYNC_verbose << "LocalNode considered for deletion, but fsNode is elsewhere with " "unknown exclusion state: " << logTriplet(row, fullPath); } } CloudNode cloudNode; string cloudNodePath; bool isInTrash = false; bool nodeIsInActiveSync = false, nodeIsDefinitelyExcluded = false; bool found = syncs.lookupCloudNode(row.syncNode->syncedCloudNodeHandle, cloudNode, &cloudNodePath, &isInTrash, &nodeIsInActiveSync, &nodeIsDefinitelyExcluded, nullptr, Syncs::EXACT_VERSION); if (found && !isInTrash && nodeIsInActiveSync && !nodeIsDefinitelyExcluded) { cloudNodeIsElsewhere = true; SYNC_verbose_timed << "LocalNode considered for deletion, but cloud Node is elsewhere: " << cloudNodePath << logTriplet(row, fullPath); } if (fsNodeIsElsewhere && cloudNodeIsElsewhere && fsElsewhereLocation != cloudNodePath) { SYNC_verbose_timed << "LocalNode considered for deletion, but it is the source of moves to different locations: " << cloudNodePath << " " << fsElsewhereLocation << logTriplet(row, fullPath); // Moves are not complete yet so we can't be sure this node is not the source of two // inconsistent moves, one local and one remote. Keep for now until all moves are resolved. monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::DeleteWaitingOnMoves, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {}, {fullPath.localPath}, {LocalPath()})); return false; } } if (row.syncNode->deletedFS) { if (row.syncNode->type == FOLDERNODE) { LOG_debug << syncname << "Sync - local folder deletion detected: " << fullPath.localPath; } else { LOG_debug << syncname << "Sync - local file deletion detected: " << fullPath.localPath; } } // Are we deleting an ignore file? if (row.syncNode->isIgnoreFile()) { // Then make sure the parent's filters are cleared. parentRow.syncNode->clearFilters(); } SYNC_verbose << "Deleting LocalNode " << row.syncNode->syncedCloudNodeHandle << " " << toHandle(row.syncNode->fsid_lastSynced) << logTriplet(row, fullPath); // deletes itself and subtree, queues db record removal delete row.syncNode; row.syncNode = nullptr; return false; } namespace { void logUnreflectedTransferChanges( std::string_view context, const shared_ptr& transfer, const SyncTransfer_inClient::AttributeOnlyUpdate attributeOnlyUpdate, const int64_t metamac, const SyncRow& row, const SyncPath& fullPath) { if (!transfer) { return; } if (transfer->attributeOnlyUpdate.load() != attributeOnlyUpdate) { LOG_warn << context << ": attributeOnlyUpdate unreflected change from " << static_cast(transfer->attributeOnlyUpdate.load()) << " to " << static_cast(attributeOnlyUpdate) << " for " << transfer->getLocalname() << Sync::logTriplet(row, fullPath); } if (transfer->mMetaMac.has_value() && transfer->mMetaMac.value() != INVALID_META_MAC && metamac != INVALID_META_MAC && transfer->mMetaMac.value() != metamac) { LOG_warn << context << ": mMetaMac unreflected changed from " << transfer->mMetaMac.value() << " to " << metamac << " for " << transfer->getLocalname() << Sync::logTriplet(row, fullPath); } } } // anonymous namespace bool Sync::resolve_upsync(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, PerFolderLogSummaryCounts& pflsc, const int64_t metamac, const SyncTransfer_inClient::AttributeOnlyUpdate attributeOnlyUpdate) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); // Don't do anything unless we know the node's included. if (row.syncNode->exclusionState() != ES_INCLUDED) { // Unless the node was already uploading. if (!row.syncNode->transferSP) { // We'll revisit this row later if necessary. return true; } } // Convenience. auto deferred = [&](const char* message, bool (Syncs::*predicate)(const LocalPath&) const, PathProblem problem) { // Activity isn't deferred. if (!(syncs.*predicate)(fullPath.localPath)) return false; // Let debuggers know why weren't not performing the activity. LOG_debug << syncname << message << " " << fullPath.localPath << logTriplet(row, fullPath); // Try and attempt the action later. row.syncNode->setSyncAgain(false, true, false); // Emit a special stall for observers to detect. monitor.waitingLocal(fullPath.localPath, SyncStallEntry(SyncWaitReason::UploadIssue, false, false, {NodeHandle(), fullPath.cloudPath, problem}, {}, {fullPath.localPath, problem}, {})); // Activity's been deferred. return true; }; // deferred if (row.fsNode->type == FILENODE) { if (auto waitForUpsyncCompletion = !row.syncNode->transferResetUnlessMatched(PUT, row.fsNode->fingerprint, metamac); waitForUpsyncCompletion) { // if we are in the putnodes stage of a transfer though, then // wait for that to finish and then re-evaluate return false; } // upload the file if we're not already uploading it shared_ptr existingUpload = std::dynamic_pointer_cast(row.syncNode->transferSP); if (existingUpload && !existingUpload->upsyncStarted) { logUnreflectedTransferChanges("resolve_upsync", existingUpload, attributeOnlyUpdate, metamac, row, fullPath); // Keep the pending upload decision in sync while it's still waiting to be started // on the client thread. if (!existingUpload->wasStarted && existingUpload->attributeOnlyUpdate.load() != attributeOnlyUpdate) { existingUpload->attributeOnlyUpdate = attributeOnlyUpdate; } // keep the name and target folder details current: // if it's just a case change in a case insensitive name, use the updated // uppercase/lowercase bool onlyCaseChanged = mCaseInsensitive && row.cloudNode && 0 == compareUtf(row.cloudNode->name, true, row.fsNode->localname, true, true); // if we were already matched with a name that is not exactly the same as toName(), keep using it string nodeName = !row.cloudNode || onlyCaseChanged ? row.fsNode->localname.toName(*syncs.fsaccess) : row.cloudNode->name; if (nodeName != existingUpload->name) { LOG_debug << syncname << "Upload name changed, updating: " << existingUpload->name << " to " << nodeName << logTriplet(row, fullPath); existingUpload->name = nodeName; // todo: thread safety } // make sure the target folder for putnodes is current: if (parentRow.cloudNode && parentRow.cloudNode->handle == parentRow.syncNode->syncedCloudNodeHandle) { if (existingUpload->h != parentRow.cloudNode->handle) { LOG_debug << syncname << "Upload target folder changed, updating for putnodes. " << existingUpload->h << " to " << parentRow.cloudNode->handle << logTriplet(row, fullPath); existingUpload->h = parentRow.cloudNode->handle; } } else { LOG_debug << syncname << "Upload target folder changed and there's no handle, abandoning transfer " << logTriplet(row, fullPath); row.syncNode->transferSP.reset(); return false; } } if (!existingUpload) { // Don't bother restarting the upload if we're excluded. if (row.syncNode->exclusionState() != ES_INCLUDED) { // We'll revisit later if needed. return true; } // Sanity. assert(row.syncNode->parent); assert(row.syncNode->parent == parentRow.syncNode); if (parentRow.cloudNode && parentRow.cloudNode->handle == parentRow.syncNode->syncedCloudNodeHandle) { LOG_debug << syncname << "Sync - local file addition detected: " << fullPath.localPath; //if (checkIfFileIsChanging(*row.fsNode, fullPath.localPath)) //{ // LOG_debug << syncname // << "Waiting for file to stabilize before uploading: " // << fullPath.localPath.toPath(); // monitor.waitingLocal(fullPath.localPath, // LocalPath(), // string(), // SyncWaitReason::WaitingForFileToStopChanging); // return false; //} // Ask the controller if we should defer uploading this file. if (deferred("Upload deferred by controller", &Syncs::deferUpload, PathProblem::UploadDeferredByController)) return false; LOG_debug << syncname << "Uploading file " << fullPath.localPath << logTriplet(row, fullPath); assert(row.syncNode->scannedFingerprint.isvalid); // LocalNodes for files always have a valid fingerprint assert(row.syncNode->scannedFingerprint == row.fsNode->fingerprint); // if it's just a case change in a case insensitive name, use the updated // uppercase/lowercase bool onlyCaseChanged = mCaseInsensitive && row.cloudNode && 0 == compareUtf(row.cloudNode->name, true, row.fsNode->localname, true, true); // if we were already matched with a name that is not exactly the same as toName(), keep using it string nodeName = !row.cloudNode || onlyCaseChanged ? row.fsNode->localname.toName(*syncs.fsaccess) : row.cloudNode->name; auto upload = std::make_shared(parentRow.cloudNode->handle, fullPath.localPath, nodeName, row.fsNode->fingerprint, threadSafeState, row.fsNode->fsid, row.fsNode->localname, inshare, metamac, attributeOnlyUpdate); const NodeHandle displaceHandle = row.cloudNode ? row.cloudNode->handle : NodeHandle(); const bool uploadSentToClientQueue = row.syncNode->queueClientUpload( upload, UseLocalVersioningFlag, nodeName == ".megaignore", displaceHandle); // we'll take care of versioning ourselves ( we take over the // putnodes step below) if (uploadSentToClientQueue) { LOG_debug << syncname << "Sync - sending file " << fullPath.localPath; } else { LOG_debug << syncname << "[UploadThrottle] Sync - file exceeded the limit of uploads " "without throttling (" << syncs.maxUploadsBeforeThrottle() << ")" << ". Added to delayed queue: " << fullPath.localPath; } } else { SYNC_verbose_timed << syncname << "Parent cloud folder to upload to doesn't exist yet" << logTriplet(row, fullPath); row.syncNode->setSyncAgain(true, false, false); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::UploadIssue, false, false, {NodeHandle(), fullPath.cloudPath, PathProblem::ParentFolderDoesNotExist}, {}, {fullPath.localPath}, {})); } } else if (existingUpload->wasFileTransferCompleted && !existingUpload->upsyncStarted) { // We issue putnodes from the sync thread like this because localnodes may have moved/renamed in the meantime // And consider that the old target parent node may not even exist anymore // Should we defer the putnodes until later? if (deferred("Putnode deferred by controller", &Syncs::deferPutnode, PathProblem::PutnodeDeferredByController)) return false; existingUpload->upsyncStarted = true; SYNC_verbose << syncname << "Queueing putnodes for completed upload" << logTriplet(row, fullPath); threadSafeState->addExpectedUpload(parentRow.cloudNode->handle, existingUpload->name, existingUpload); NodeHandle displaceHandle = row.cloudNode ? row.cloudNode->handle : NodeHandle(); auto isInshare = inshare; std::function signalPutnodesBegin; if (parentRow.cloudNode && existingUpload->h != parentRow.cloudNode->handle) { LOG_verbose << "Adjusting the target folder for moved upload, was " << existingUpload->h << " now " << parentRow.cloudNode->handle; existingUpload->h = parentRow.cloudNode->handle; } bool canChangeVault = threadSafeState->mCanChangeVault; syncs.queueClient( [existingUpload, displaceHandle, isInshare, signalPutnodesBegin, canChangeVault]( MegaClient& mc, TransferDbCommitter&) { std::shared_ptr displaceNode = mc.nodeByHandle(displaceHandle); existingUpload->previousNode = displaceNode; if (displaceNode && mc.versions_disabled) { MegaClient* c = &mc; mc.movetosyncdebris(displaceNode.get(), isInshare, // after the old node is out of the way, we wll putnodes [c, existingUpload, signalPutnodesBegin](NodeHandle, Error){ if (signalPutnodesBegin) signalPutnodesBegin(*c); existingUpload->sendPutnodesOfUpload(c, NodeHandle()); }, canChangeVault); // putnodes will be executed after or simultaneous with the // move to sync debris return; } // the case where we are making versions, or not displacing something with the same name if (signalPutnodesBegin) signalPutnodesBegin(mc); existingUpload->sendPutnodesOfUpload(&mc, displaceNode ? displaceNode->nodeHandle() : NodeHandle()); }); } else if (existingUpload->wasUpsyncCompleted) { // Only reset the transfer if the putnode's completion hasn't been deferred. // This is necessary to prevent an infinite upload-loop in some cases. if (syncs.deferPutnodeCompletion(fullPath.localPath)) return false; assert(!existingUpload->upsyncFailed || existingUpload->upsyncResultHandle.isUndef()); if (existingUpload->upsyncFailed) { SYNC_verbose << syncname << "Upload from here failed, reset for reevaluation in resolve_upsync." << logTriplet(row, fullPath); row.syncNode->bypassThrottlingNextTime(syncs.maxUploadsBeforeThrottle()); } else { row.syncNode->increaseUploadCounter(); SYNC_verbose << syncname << "Putnodes complete. Detaching upload in resolve_upsync. [Num uploads: " << row.syncNode->uploadCounter() << "]" << logTriplet(row, fullPath); // Record mtime-only operation to throttle future MAC computations if (existingUpload->attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly) { row.syncNode->recordMtimeOnlyOperation(); } } row.syncNode->resetTransfer(nullptr); return false; // revisit in case of further changes } else if (existingUpload->upsyncStarted) { SYNC_verbose << syncname << "Upload's putnodes already in progress" << logTriplet(row, fullPath); } else { if (existingUpload->wasStarted) { if (const auto macStatus = checkPendingCloneMac(syncs.mClient, *existingUpload); macStatus == CloneMacStatus::Ready || macStatus == CloneMacStatus::Failed) { if (handleCloneMacStatusFromSyncThread(syncs, existingUpload, macStatus, existingUpload->name == ".megaignore", row.cloudNode ? row.cloudNode->handle : NodeHandle())) { return false; // Revisit after client processes } } } if (!pflsc.alreadyUploadingCount) { if (existingUpload->wasStarted) { SYNC_verbose << syncname << "Upload already in progress" << logTriplet(row, fullPath); } else { SYNC_verbose_timed << syncname << "Upload already waiting in the throttling queue" << logTriplet(row, fullPath); } } pflsc.alreadyUploadingCount += 1; } } else if (row.fsNode->type == FOLDERNODE) { if (row.syncNode->hasRare() && row.syncNode->rare().createFolderHere) { SYNC_verbose << syncname << "Create cloud folder already in progress" << logTriplet(row, fullPath); } else { if (parentRow.cloudNode) { // there can't be a matching cloud node in this row (for folders), so just toName() is correct string foldername = row.syncNode->toName_of_localname; LOG_verbose << syncname << "Creating cloud node for: " << fullPath.localPath << " as " << foldername << logTriplet(row, fullPath); // while the operation is in progress sync() will skip over the parent folder bool canChangeVault = threadSafeState->mCanChangeVault; NodeHandle targethandle = parentRow.cloudNode->handle; auto createFolderPtr = std::make_shared(row.fsNode->fsid); row.syncNode->rare().createFolderHere = createFolderPtr; syncs.queueClient( [foldername, targethandle, createFolderPtr, canChangeVault]( MegaClient& mc, TransferDbCommitter&) { vector nn(1); mc.putnodes_prepareOneFolder(&nn[0], foldername, canChangeVault); Pitag pitag{canChangeVault ? PitagPurpose::Backup : PitagPurpose::Sync, PitagTrigger::SyncAlgorithm, PitagNodeType::Folder, PitagTarget::NotApplicable, PitagImportSource::NotApplicable}; if (pitag.target == PitagTarget::NotApplicable) { if (std::shared_ptr parent = mc.nodeByHandle(targethandle)) { const bool inIncomingShare = parent->matchesOrHasAncestorMatching( [](const Node& node) { return node.inshare != nullptr; }); pitag.target = inIncomingShare ? PitagTarget::IncomingShare : PitagTarget::CloudDrive; } } mc.putnodes( targethandle, NoVersioning, std::move(nn), nullptr, 0, canChangeVault, {}, // customerIpPort [createFolderPtr](const Error& e, targettype_t, vector& v, bool /*targetOverride*/, int /*tag*/, const map& /*fileHandles*/) { if (!e && !v.empty()) { createFolderPtr->succeededHandle.set6byte(v[0].mAddedHandle); } if (createFolderPtr->succeededHandle.isUndef()) { createFolderPtr->failed = true; } }, pitag); }); } else { SYNC_verbose << "Delay creating cloud node until parent cloud node exists: " << fullPath.localPath << logTriplet(row, fullPath); row.syncNode->setSyncAgain(true, false, false); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::CannotCreateFolder, false, false, {NodeHandle(), fullPath.cloudPath, PathProblem::ParentFolderDoesNotExist}, {}, {fullPath.localPath}, {})); } } // we may not see some moves/renames until the entire folder structure is created. row.syncNode->setCheckMovesAgain(true, false, false); // todo: double check - might not be needed for the wait case? might cause a stall? } else if (row.fsNode->type == TYPE_DONOTSYNC) { // This is the sort of thing that we should not sync, but not complain about either // consider it synced. monitor.noResult(); return true; } else // unknown/special { monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::FileIssue, false, false, {NodeHandle(), fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::DetectedSpecialFile}, {})); } return false; } bool Sync::resolve_downsync(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath, [[maybe_unused]] bool alreadyExists, PerFolderLogSummaryCounts& pflsc, const int64_t metamac, const SyncTransfer_inClient::AttributeOnlyUpdate attributeOnlyUpdate) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); // Don't do anything unless we know the row's included. if (parentRow.exclusionState(*row.cloudNode) != ES_INCLUDED) { // But only if we weren't already downloading. if (!row.syncNode->transferSP) { // We'll revisit this row later when the filters are stable. return true; } } if (isBackup()) { // Backups must not change the local LOG_err << "Something triggered a download for a backup! Cancelling it"; assert(false && "Something triggered a download for a backup!"); row.syncNode->resetTransfer(nullptr); return false; } if (row.cloudNode->type == FILENODE) { // download the file if we're not already downloading // if (alreadyExists), we will move the target to the trash when/if download completes //todo: check if (!row.cloudNode->fingerprint.isvalid) { // if the cloud fingerprint is not valid, then the local mtime can't be set properly // and we'll have all sorts of matching problems, probably re-upload, etc // or the download will get cancelled for different fingerprint, and cycle, etc monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::DownloadIssue, false, true, {row.cloudNode->handle, fullPath.cloudPath, PathProblem::CloudNodeInvalidFingerprint}, {}, {parentRow.fsNode ? fullPath.localPath : LocalPath()}, {})); return false; } row.syncNode->transferResetUnlessMatched(GET, row.cloudNode->fingerprint, metamac); if (!row.syncNode->transferSP) { // Don't bother restarting the download if we're effectively excluded. if (row.syncNode->exclusionState() != ES_INCLUDED) { // We'll revisit this node later if necessary. return true; } } if (parentRow.fsNode) { auto existingDownload = std::dynamic_pointer_cast(row.syncNode->transferSP); if (!existingDownload) { LOG_debug << syncname << "Sync - remote file addition detected: " << row.cloudNode->handle << " " << fullPath.cloudPath; // Do we have enough space on disk for this file? { auto size = row.cloudNode->fingerprint.size; assert(size >= 0); if (syncs.fsaccess->availableDiskSpace(mLocalPath) <= size) { LOG_debug << syncname << "Insufficient space available for download: " << logTriplet(row, fullPath); { lock_guard guard(syncs.mSyncVecMutex); changestate(INSUFFICIENT_DISK_SPACE, false, true, true); syncs.mHeartBeatMonitor->updateOrRegisterSync(mUnifiedSync); } return false; } } // FIXME: to cover renames that occur during the // download, reconstruct localname in complete() LOG_debug << syncname << "Start sync download: " << row.cloudNode->handle << logTriplet(row, fullPath); LOG_debug << syncname << "Sync - requesting file " << fullPath.localPath; createDebrisTmpLockOnce(); bool downloadFirst = fullPath.localPath.leafName().toPath(false) == ".megaignore"; // download to tmpfaPath (folder debris/tmp). We will rename/mv it to correct location (updated if necessary) after that completes row.syncNode->queueClientDownload( std::make_shared(*row.cloudNode, fullPath.localPath, inshare, threadSafeState, row.fsNode ? row.fsNode->fingerprint : FileFingerprint(), metamac, attributeOnlyUpdate), downloadFirst); } // terminated and completed transfers are checked for early in syncItem() else { logUnreflectedTransferChanges("resolve_downsync", existingDownload, attributeOnlyUpdate, metamac, row, fullPath); if (!pflsc.alreadyDownloadingCount) { SYNC_verbose << syncname << "Download already in progress -> completed: " << existingDownload->wasFileTransferCompleted << " terminated: " << existingDownload->wasTerminated << " requester abandoned: " << existingDownload->wasRequesterAbandoned << " -> " << logTriplet(row, fullPath); } pflsc.alreadyDownloadingCount += 1; } } else { SYNC_verbose_timed << "Delay starting download until parent local folder exists: " << fullPath.cloudPath << logTriplet(row, fullPath); row.syncNode->setSyncAgain(true, false, false); monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::DownloadIssue, false, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::ParentFolderDoesNotExist}, {})); } } else // FOLDERNODE { assert(!alreadyExists); // if it did we would have matched it if (parentRow.fsNode) { LOG_verbose << syncname << "Sync - executing local folder creation at: " << fullPath.localPath << logTriplet(row, fullPath); assert(!isBackup()); if (syncs.fsaccess->mkdirlocal(fullPath.localPath, false, true)) { assert(row.syncNode); assert(row.syncNode->localname == fullPath.localPath.leafName()); // Update our records of what we know is on disk for this (parent) LocalNode. // This allows the next level of folders to be created too auto fa = syncs.fsaccess->newfileaccess(false); if (fa->fopen(fullPath.localPath, OPEN_RDONLY, FSLogging::logOnError)) { auto fsnode = FSNode::fromFOpened(*fa, fullPath.localPath, *syncs.fsaccess); // Mark other nodes with this FSID as having their FSID reused. syncs.setSyncedFsidReused(fsfp(), fsnode->fsid); syncs.setScannedFsidReused(fsfp(), fsnode->fsid); row.syncNode->localname = fsnode->localname; row.syncNode->slocalname = fsnode->cloneShortname(); // setting synced variables here means we can skip a scan of the parent folder, if just the one expected notification arrives for it row.syncNode->setSyncedNodeHandle(row.cloudNode->handle); row.syncNode->setSyncedFsid(fsnode->fsid, syncs.localnodeBySyncedFsid, fsnode->localname, fsnode->cloneShortname()); row.reassignFingerprints(); row.syncNode->setScannedFsid(fsnode->fsid, syncs.localnodeByScannedFsid, fsnode->localname, fsnode->fingerprint); statecacheadd(row.syncNode); // So that we can recurse into the new directory immediately. parentRow.fsAddedSiblings.emplace_back(std::move(*fsnode)); row.fsNode = &parentRow.fsAddedSiblings.back(); row.syncNode->setScanAgain(false, true, true, 0); row.syncNode->setSyncAgain(false, true, false); // set up to skip the fs notification from this folder creation parentRow.syncNode->expectedSelfNotificationCount += 1; // TODO: probably different platforms may have different counts, or it may vary, maybe some are skipped or double ups condensed? parentRow.syncNode->scanDelayUntil = std::max(parentRow.syncNode->scanDelayUntil, syncs.waiter->ds + 1); } else { LOG_warn << syncname << "Failed to fopen folder straight after creation - revisit in 5s. " << fullPath.localPath << logTriplet(row, fullPath); row.syncNode->setScanAgain(true, false, false, 50); } } else if (syncs.fsaccess->target_name_too_long) { LOG_warn << syncname << "Unable to create target folder as its name is too long " << fullPath.localPath << logTriplet(row, fullPath); assert(row.syncNode); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::CannotCreateFolder, true, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::NameTooLongForFilesystem}, {})); } else { // let's consider this case as blocked too, alert the user LOG_warn << syncname << "Unable to create folder " << fullPath.localPath << logTriplet(row, fullPath); assert(row.syncNode); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::CannotCreateFolder, false, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::FilesystemErrorDuringOperation}, {})); } } else { SYNC_verbose_timed << "Delay creating local folder until parent local folder exists: " << fullPath.localPath << logTriplet(row, fullPath); row.syncNode->setSyncAgain(true, false, false); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::CannotCreateFolder, false, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::ParentFolderDoesNotExist}, {})); } // we may not see some moves/renames until the entire folder structure is created. row.syncNode->setCheckMovesAgain(true, false, false); // todo: is this still right for the watiing case } return false; } bool Sync::resolve_userIntervention(SyncRow& row, SyncPath& fullPath) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); assert(!isBackup() && "resolve_userIntervention should never be called for a backup!"); if (row.syncNode) { bool immediateStall = true; if (row.syncNode->hasRare()) { if (row.syncNode->rare().moveFromHere) immediateStall = false; if (row.syncNode->rare().moveToHere) immediateStall = false; } if (row.syncNode->transferSP) { if (immediateStall) { // eg if it's a simple upload (no moves involved), and the cloud side changes to something different during upload // since it doesn't seem right if we detect the stall and yet we can see the upload keeps progressing row.syncNode->resetTransfer(nullptr); } } SYNC_verbose_timed << "Both sides mismatch since last sync! Fingerprint debug [size:mtime:CRC] : " << "Cloud -> " << row.cloudNode->fingerprint.fingerprintDebugString() << ". " << "SyncNode -> " << row.syncNode->syncedFingerprint.fingerprintDebugString() << ". " << "Local -> " << row.fsNode->fingerprint.fingerprintDebugString() << ". " << "Immediate: " << immediateStall << " at " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::LocalAndRemoteChangedSinceLastSyncedState_userMustChoose, immediateStall, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath}, {})); } else { SYNC_verbose_timed << "Both sides previously unsynced mismatch! Fingerprint debug [size:mtime:CRC] : " << "Cloud -> " << row.cloudNode->fingerprint.fingerprintDebugString() << ". " << "Local -> " << row.fsNode->fingerprint.fingerprintDebugString() << ". " << "At " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose, true, true, {row.cloudNode->handle, fullPath.cloudPath}, {}, {fullPath.localPath}, {})); } return false; } bool Sync::resolve_cloudNodeGone(SyncRow& row, SyncRow& parentRow, SyncPath& fullPath) { assert(!isBackup() && "This method is not allowed to be called on backups"); enum MoveType { // Not a possible move. MT_NONE, // Move is possibly pending. MT_PENDING, // Move is in progress. MT_UNDERWAY }; // MoveType auto isPossibleCloudMoveSource = [&](string& cloudPath) { // Is the move source an ignore file? if (row.syncNode->isIgnoreFile()) { // Then it's not subject to move processing. return MT_NONE; } // Is NO_NAMES ? Then ignore if (row.isNoName()) { // Then it's not subject to move processing. return MT_NONE; } CloudNode cloudNode; bool active = false; bool nodeIsDefinitelyExcluded = false; bool found = false; // Does the remote associated with this row exist elsewhere? found = syncs.lookupCloudNode(row.syncNode->syncedCloudNodeHandle, cloudNode, &cloudPath, nullptr, &active, &nodeIsDefinitelyExcluded, nullptr, Syncs::LATEST_VERSION_ONLY); // Remote doesn't exist under an active sync or is excluded. if (!found || !active || nodeIsDefinitelyExcluded) return MT_NONE; // Does the remote represent an ignore file? if (cloudNode.isIgnoreFile()) { // Then we know it can't be a move target. return MT_NONE; } // We need to discard NO_NAME nodes. // Not only the current cloudNode: it could happen that a node has been moved into a undecryptable/NO_NAMEd path. So we must check parents too. const std::string NO_KEY_SUFFIX = "NO_KEY"; if (cloudPath.find(NO_KEY_SUFFIX) != std::string::npos) { SYNC_verbose << syncname << "[cloudNodeGone] [isPossibleCloudMoveSource] There is a NO_KEY in the cloud node path. Look for NO_NAMEd nodes [cloudNode.name = '" << cloudNode.name << "', cloudPath = '" << cloudPath << "']"; do { if (cloudNode.name.empty()) { // Unnamed/undecryptable node, we know it cannot be considered a move target (for our local sync, so we need to discard the moving nodes and move the files to local debris) assert((cloudPath.length() >= NO_KEY_SUFFIX.length() && !cloudPath.compare(cloudPath.length() - NO_KEY_SUFFIX.length(), NO_KEY_SUFFIX.length(), NO_KEY_SUFFIX)) && "Cloud node name empty, but the path does not contain NO_KEY word"); SYNC_verbose << syncname << "[cloudNodeGone] Cloud Node is a NO_NAME/NO_KEY (undecryptable node) or a child of a NO_NAME/NO_KEY, it cannot be a move target!!!! " << logTriplet(row, fullPath); return MT_NONE; } if (cloudNode.parentType > FILENODE && !cloudNode.parentHandle.isUndef()) { SYNC_verbose << syncname << "[cloudNodeGone] [isPossibleCloudMoveSource] looking for NO_NAME parents [cloudNode.name = '" << cloudNode.name << "', cloudPath = '" << cloudPath << "']"; found = syncs.lookupCloudNode(cloudNode.parentHandle, cloudNode, &cloudPath, nullptr, &active, &nodeIsDefinitelyExcluded, nullptr, Syncs::LATEST_VERSION_ONLY); } else { found = false; } } while (found && active && !nodeIsDefinitelyExcluded); LOG_warn << "[cloudNodeGone] There is a NO_KEY word in the cloudPath, but no unnamed cloudNode has been found"; // This could happen, for example, we could have a file or folder named MARIANO_KEY } else { // Just in case the NO_KEY word changes if (cloudNode.name.empty()) { LOG_warn << "[cloudNodeGone] There isn't a NO_KEY word present in the cloudPath, " "but the cloudNode name is empty!"; assert(false && "There isn't a NO_KEY word present in the cloudPath, but the " "cloudNode name is empty!"); } } // Trim the rare fields. row.syncNode->trimRareFields(); // Is this row a known move source? if (auto& movePtr = row.syncNode->rareRO().moveFromHere) { // Has the move completed? if (!movePtr->syncCodeProcessedResult) { // Move is still underway. return MT_UNDERWAY; } } // It's in an active sync and it's not excluded return MT_PENDING; }; assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); string cloudPath; if (auto mt = isPossibleCloudMoveSource(cloudPath)) { row.syncNode->setCheckMovesAgain(true, false, false); row.syncNode->trimRareFields(); if (mt == MT_UNDERWAY) { SYNC_verbose_timed << syncname << "Node is a cloud move/rename source, move is under way: " << logTriplet(row, fullPath); row.suppressRecursion = true; } else { SYNC_verbose_timed << syncname << "Letting move destination node process this first (cloud node is at " << cloudPath << "): " << logTriplet(row, fullPath); } monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, true, {row.cloudHandleOpt(), fullPath.cloudPath}, {NodeHandle(), cloudPath}, {fullPath.localPath}, {LocalPath(), PathProblem::DestinationPathInUnresolvedArea})); } else if (row.syncNode->deletedFS) { SYNC_verbose << syncname << "FS item already removed: " << logTriplet(row, fullPath); monitor.noResult(); } else if (mMovesWereComplete) { if (movetolocaldebris(fullPath.localPath)) { LOG_debug << syncname << "Moved local item to local sync debris: " << fullPath.localPath << logTriplet(row, fullPath); row.syncNode->setScanAgain(true, false, false, 0); row.syncNode->scanAgain = TREE_RESOLVED; // don't let revisits do anything until the tree is cleaned up row.syncNode->deletedFS = true; } else { monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::CannotPerformDeletion, false, true, {NodeHandle(), fullPath.cloudPath, PathProblem::DeletedOrMovedByUser}, {}, {fullPath.localPath, PathProblem::MoveToDebrisFolderFailed}, {})); LOG_err << syncname << "Failed to move to local debris: " << fullPath.localPath; // todo: do we need some sort of delay before retry on the next go-round? } } else { // todo: but, nodes are always current before we call recursiveSync - shortcut this case for nodes? SYNC_verbose_timed << syncname << "Wait for scanning+moving to finish before removing local node: " << logTriplet(row, fullPath); row.syncNode->setSyncAgain(true, false, false); // make sure we revisit (but don't keep checkMoves set) if (parentRow.cloudNode) { monitor.waitingCloud(fullPath.cloudPath, SyncStallEntry( SyncWaitReason::DeleteWaitingOnMoves, false, true, {NodeHandle(), fullPath.cloudPath, PathProblem::DeletedOrMovedByUser}, {}, {fullPath.localPath}, {})); } else { monitor.noResult(); } // make sure we are not waiting for ourselves TODO: (or, would this be better done in step 2, recursion (for folders)?) row.syncNode->checkMovesAgain = TREE_RESOLVED; } row.suppressRecursion = true; row.recurseBelowRemovedCloudNode = true; return false; } std::pair Syncs::findLocalNodeBySyncedFsid( const handle fsid, const NodeMatchByFSIDAttributes& targetNodeAttributes, const LocalPath& originalPathForLogging, std::function extraCheck, std::function onFingerprintMismatchDuringPutnodes) const { assert(onSyncThread()); FindLocalNodeByFSIDPredicate predicate(fsid, ScannedOrSyncedContext::SYNCED, targetNodeAttributes, originalPathForLogging, extraCheck, onFingerprintMismatchDuringPutnodes); return findLocalNodeByFsid(localnodeBySyncedFsid, std::move(predicate)); } std::pair Syncs::findLocalNodeByScannedFsid(const handle fsid, const NodeMatchByFSIDAttributes& targetNodeAttributes, const LocalPath& originalPathForLogging, std::function extraCheck) const { assert(onSyncThread()); FindLocalNodeByFSIDPredicate predicate(fsid, ScannedOrSyncedContext::SCANNED, targetNodeAttributes, originalPathForLogging, extraCheck); return findLocalNodeByFsid(localnodeByScannedFsid, std::move(predicate)); } void Syncs::setSyncedFsidReused(const fsfp_t& fsfp, const handle fsid) { assert(onSyncThread()); for (auto range = localnodeBySyncedFsid.equal_range(fsid); range.first != range.second; ++range.first) { if (range.first->second->sync->fsfp() == fsfp) range.first->second->fsidSyncedReused = true; } } void Syncs::setScannedFsidReused(const fsfp_t& fsfp, const handle fsid) { assert(onSyncThread()); for (auto range = localnodeByScannedFsid.equal_range(fsid); range.first != range.second; ++range.first) { if (range.first->second->sync->fsfp() == fsfp) range.first->second->fsidScannedReused = true; } } bool Syncs::findLocalNodeByNodeHandle(NodeHandle h, LocalNode*& sourceSyncNodeOriginal, LocalNode*& sourceSyncNodeCurrent, bool& unsureDueToIncompleteScanning, bool& unsureDueToUnknownExclusionMoveSource) { // find where the node was (based on synced local file presence) // and where it is now (synced local file absent at the corresponding path) // consider these cases. // 1. normal move of cloud node. original location still has local file, move-to location (here) has none // Only one node found for that case, with local file. // 2. move of cloud node, and original local file was separately moved elsewhere. // Original location does not have the local file, new location has it (but that location is not here) // Two nodes found. sourceSyncNodeOriginal = nullptr; sourceSyncNodeCurrent = nullptr; unsureDueToIncompleteScanning = false; unsureDueToUnknownExclusionMoveSource = false; assert(onSyncThread()); if (h.isUndef()) return false; auto range = localnodeByNodeHandle.equal_range(h); for (auto it = range.first; it != range.second; ++it) { switch (it->second->exclusionState()) { case ES_INCLUDED: break; case ES_UNKNOWN: LOG_verbose << mClient.clientname << "findLocalNodeByNodeHandle - unknown exclusion with that handle " << h << " at: " << it->second->getLocalPath(); unsureDueToUnknownExclusionMoveSource = true; continue; case ES_EXCLUDED: continue; default: assert(false); continue; } // check the file/folder actually exists (with same fsid) on disk for this LocalNode LocalPath lp = it->second->getLocalPath(); if (it->second->fsid_lastSynced != UNDEF && it->second->fsid_lastSynced == fsaccess->fsidOf(lp, false, false, FSLogging::logExceptFileNotFound)) { sourceSyncNodeCurrent = it->second; } else { sourceSyncNodeOriginal = it->second; } } if (!sourceSyncNodeCurrent && sourceSyncNodeOriginal && sourceSyncNodeOriginal->fsid_lastSynced != UNDEF) { // see if we can find where the local side went, so we can report a move clash const NodeMatchByFSIDAttributes sourceSyncNodeOriginalAttributes{ sourceSyncNodeOriginal->type, sourceSyncNodeOriginal->sync->fsfp(), sourceSyncNodeOriginal->sync->cloudRootOwningUser, sourceSyncNodeOriginal->syncedFingerprint, FileFingerprint{}}; std::tie(unsureDueToUnknownExclusionMoveSource, sourceSyncNodeCurrent) = findLocalNodeByScannedFsid(sourceSyncNodeOriginal->fsid_lastSynced, sourceSyncNodeOriginalAttributes, sourceSyncNodeOriginal->getLocalPath()); if (unsureDueToUnknownExclusionMoveSource) { LOG_verbose << mClient.clientname << "findLocalNodeByNodeHandle - unknown exclusion after fsid lookup"; } if (!sourceSyncNodeCurrent && !sourceSyncNodeOriginal->sync->scanningWasComplete()) { unsureDueToIncompleteScanning = true; } } if (sourceSyncNodeCurrent && !sourceSyncNodeOriginal) { // normal case, simple cloud side move only. Current and original local location should be the same sourceSyncNodeOriginal = sourceSyncNodeCurrent; } return sourceSyncNodeCurrent && sourceSyncNodeOriginal; } std::optional Sync::checkIfFileIsChanging(const FSNode& fsNode, const LocalPath& fullPath) { assert(syncs.onSyncThread()); assert(fsNode.type == FILENODE); Syncs::FileChangingState& state = syncs.mFileChangingCheckState[fullPath]; const auto getResult = [wasInitialized = state.isInitialized()](const bool waitforupdate) -> std::optional { return wasInitialized ? std::optional{waitforupdate} : std::nullopt; }; const m_time_t currentsecs = m_time(); if (!state.updatedfileinitialts) { state.updatedfileinitialts = currentsecs; } const bool fileUpdatedBeforeNow = currentsecs >= state.updatedfileinitialts; if (!fileUpdatedBeforeNow) { LOG_warn << syncname << "checkIfFileIsChanging: File check started in the future"; syncs.mFileChangingCheckState.erase(fullPath); return getResult(false); } const bool isMaxDelayExceeded = currentsecs - state.updatedfileinitialts > Sync::FILE_UPDATE_MAX_DELAY_SECS; if (isMaxDelayExceeded) { syncs.queueClient( [](MegaClient& mc, TransferDbCommitter&) { mc.sendevent(99438, "Timeout waiting for file update", 0); }); syncs.mFileChangingCheckState.erase(fullPath); return getResult(false); } const auto currentfa = syncs.fsaccess->newfileaccess(false); if (const auto canOpenFile = currentfa->fopen(fullPath, FSLogging::logOnError); !canOpenFile) { if (currentfa->retry) { LOG_debug << syncname << "checkIfFileIsChanging: The file in the origin is temporarily blocked. " "Waiting..."; return getResult(true); } else { LOG_debug << syncname << "checkIfFileIsChanging: There isn't anything in the origin path"; syncs.mFileChangingCheckState.erase(fullPath); return getResult(false); } } LOG_debug << syncname << "checkIfFileIsChanging: File detected in the origin of a move [fsNode = '" << fsNode.localname << "', sourcePath = '" << fullPath.toPath(false) << "']"; if (const auto fileSizeCheckedInFuture = currentsecs < state.updatedfilets; fileSizeCheckedInFuture) { LOG_warn << syncname << "checkIfFileIsChanging: File size checked in the future, adjusting filets to " "currentsecs"; assert(false && "checkIfFileIsChanging: File size checked in the future!"); state.updatedfilets = currentsecs; } if (const auto fileSizeIsChanging = state.updatedfilesize != currentfa->size; fileSizeIsChanging) { LOG_verbose << "checkIfFileIsChanging: " << syncname << " currentsecs = " << currentsecs << " lastcheck = " << state.updatedfilets << " currentsize = " << currentfa->size << " lastsize = " << state.updatedfilesize; LOG_debug << "checkIfFileIsChanging: The file size has changed since the last check. Waiting " << Sync::FILE_UPDATE_DELAY_DS << " ds for " << fsNode.localname; state.updatedfilesize = currentfa->size; state.updatedfilets = currentsecs; return getResult(true); } if (const auto remainingTimeForStableFileSize = Sync::FILE_UPDATE_DELAY_DS - ((currentsecs - state.updatedfilets) * 10); remainingTimeForStableFileSize > 0) { LOG_verbose << syncname << "checkIfFileIsChanging: currentsecs = " << currentsecs << " lastcheck = " << state.updatedfilets << " currentsize = " << currentfa->size << " lastsize = " << state.updatedfilesize; LOG_debug << "checkIfFileIsChanging:" << syncname << "The file size changed too recently (" << (Sync::FILE_UPDATE_DELAY_DS - remainingTimeForStableFileSize) << " ds), needs to be unmodified for " << Sync::FILE_UPDATE_DELAY_DS << " ds. Waiting " << remainingTimeForStableFileSize << " ds..."; return getResult(true); } LOG_debug << syncname << "checkIfFileIsChanging: The file size seems stable"; if (const auto fileModifiedInTheFuture = currentsecs < currentfa->mtime; fileModifiedInTheFuture) { LOG_warn << syncname << "checkIfFileIsChanging: File modification time (mtime) belongs to the future " "(current time = " << currentsecs << ", file mtime = " << currentfa->mtime << ")"; syncs.mFileChangingCheckState.erase(fullPath); return getResult(false); } if (const auto remainingTimeForStableMtime = Sync::FILE_UPDATE_DELAY_DS - ((currentsecs - currentfa->mtime) * 10); remainingTimeForStableMtime > 0) { LOG_verbose << "checkIfFileIsChanging:" << syncname << "currentsecs = " << currentsecs << " mtime = " << currentfa->mtime; LOG_debug << "checkIfFileIsChanging:" << syncname << "File modified too recently too recently (" << (Sync::FILE_UPDATE_DELAY_DS - remainingTimeForStableMtime) << " ds), needs to be unmodified for " << Sync::FILE_UPDATE_DELAY_DS << " ds. Waiting " << remainingTimeForStableMtime << " ds..."; return getResult(true); } LOG_debug << syncname << "checkIfFileIsChanging: The file modification time (mtime) seems stable"; syncs.mFileChangingCheckState.erase(fullPath); return getResult(false); } bool Sync::resolve_fsNodeGone(SyncRow& row, SyncRow& /*parentRow*/, SyncPath& fullPath) { assert(syncs.onSyncThread()); ProgressingMonitor monitor(*this, row, fullPath); const auto [unsureOfMovedLocalNodeDueToUnknownExclusions, movedLocalNode] = std::invoke( [&syncs = std::as_const(syncs), &cloudRootOwningUser = std::as_const(cloudRootOwningUser), &fsfp = std::as_const(fsfp()), &syncNode = std::as_const(*row.syncNode), &fullPath = std::as_const(fullPath)]() -> std::pair { // Ignore files aren't subject to the usual move processing. if (!syncNode.fsidSyncedReused && syncNode.isIgnoreFile()) { auto extraCheck = [&syncNode](const LocalNode& n) { return &n != &syncNode && !n.isIgnoreFile(); }; const NodeMatchByFSIDAttributes syncNodeAttributes{syncNode.type, fsfp, cloudRootOwningUser, syncNode.syncedFingerprint, FileFingerprint{}}; return syncs.findLocalNodeByScannedFsid(syncNode.fsid_lastSynced, syncNodeAttributes, fullPath.localPath, std::move(extraCheck)); } return {false, nullptr}; }); if (unsureOfMovedLocalNodeDueToUnknownExclusions) { row.syncNode->setCheckMovesAgain(true, false, false); SYNC_verbose_timed << syncname << "This file/folder was probably moved, but the destination is currently exclusion-unknown: " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {NodeHandle(), movedLocalNode->getCloudPath(false), PathProblem::DestinationPathInUnresolvedArea}, {fullPath.localPath}, {movedLocalNode->getLocalPath()})); } else if (movedLocalNode) { // if we can find the place it moved to, we don't need to wait for scanning be complete row.syncNode->setCheckMovesAgain(true, false, false); if (row.syncNode->moveAppliedToLocal) { SYNC_verbose << syncname << "This file/folder was moved, it will be removed next pass: " << logTriplet(row, fullPath); } else if (row.syncNode->moveApplyingToLocal) { SYNC_verbose << syncname << "Node was our own cloud move source, move is propagating: " << logTriplet(row, fullPath); } else { SYNC_verbose_timed << syncname << "This file/folder was moved, letting destination node at " << movedLocalNode->getLocalPath() << " process this first: " << logTriplet(row, fullPath); } // todo: do we need an equivalent to row.recurseToScanforNewLocalNodesOnly = true; (in resolve_cloudNodeGone) monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::MoveOrRenameCannotOccur, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {NodeHandle(), movedLocalNode->getCloudPath(false), PathProblem::DestinationPathInUnresolvedArea}, {fullPath.localPath}, {movedLocalNode->getLocalPath()})); // make sure we do visit the parent folder of that node so the move can be processed if (movedLocalNode->parent && !movedLocalNode->parent->syncRequired()) { SYNC_verbose << syncname << "Ensuring we visit the move-target node parent: " << movedLocalNode->getLocalPath() << ". At " << logTriplet(row, fullPath); movedLocalNode->setSyncAgain(true, true, false); } } else if (!mScanningWasComplete && !row.isIgnoreFile()) // ignore files do not participate in move logic { SYNC_verbose_timed << syncname << "Wait for scanning to finish before confirming fsid " << toHandle(row.syncNode->fsid_lastSynced) << " deleted or moved: " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::DeleteOrMoveWaitingOnScanning, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::DeletedOrMovedByUser}, {})); } else if (mMovesWereComplete || row.isIgnoreFile()) // ignore files do not participate in move logic { if (!row.syncNode->rareRO().removeNodeHere) { // We need to be sure before sending to sync trash. If we have received // a lot of delete notifications, but not yet the corrsponding add that makes it a move // then it would be a mistake. Give the filesystem 2 seconds to deliver that one. // On windows at least, under some circumstance, it may first deliver many deletes for the subfolder in a reverse depth first order bool timeToBeSure = syncs.waiter->ds - lastFSNotificationTime > 20; if (timeToBeSure) { // What's this node's exclusion state? auto exclusionState = row.syncNode->exclusionState(); if (exclusionState == ES_INCLUDED) { // Row's included. LOG_debug << syncname << "Moving cloud item to cloud sync debris: " << fullPath.cloudPath << logTriplet(row, fullPath); bool fromInshare = inshare; auto debrisNodeHandle = row.cloudNode->handle; auto deletePtr = std::make_shared(); deletePtr->pathDeleting = fullPath.cloudPath; bool canChangeVault = threadSafeState->mCanChangeVault; syncs.queueClient( [debrisNodeHandle, fromInshare, deletePtr, canChangeVault]( MegaClient& mc, TransferDbCommitter&) { if (auto n = mc.nodeByHandle(debrisNodeHandle)) { if (n->parent && n->parent->type == FILENODE) { // if we decided to remove a file, but it turns out not to be // the latest version of that file, abandon the action // and let the sync recalculate LOG_debug << "Sync delete was out of date, there is a more " "recent version of the file. " << debrisNodeHandle << " " << n->displaypath(); return; } mc.movetosyncdebris( n.get(), fromInshare, [deletePtr](NodeHandle, Error e) { LOG_debug << "Sync delete to sync debris completed: " << e << " " << deletePtr->pathDeleting; if (e) deletePtr->failed = true; else deletePtr->succeeded = true; }, canChangeVault); } }); // Remember that the delete is going on, so we don't do anything else until that resolves // We will detach the synced-fsid side on final completion of this operation. If we do so // earier, the logic will evaluate that updated state too soon, perhaps resulting in downsync. row.syncNode->rare().removeNodeHere = deletePtr; } else if (exclusionState == ES_EXCLUDED) { // Row's excluded. auto& s = *row.syncNode; // Node's no longer associated with any file. s.setScannedFsid(UNDEF, syncs.localnodeByScannedFsid, LocalPath(), FileFingerprint()); s.scannedFingerprint = FileFingerprint(); s.setSyncedFsid(UNDEF, syncs.localnodeBySyncedFsid, s.localname, nullptr); // Persist above changes. statecacheadd(&s); } } else { SYNC_verbose << syncname << "Waiting to be sure before moving to cloud sync debris: " << fullPath.cloudPath << logTriplet(row, fullPath); } } else { SYNC_verbose << syncname << "Already moving cloud item to cloud sync debris: " << fullPath.cloudPath << logTriplet(row, fullPath); } } else { // in case it's actually a move and we just haven't visted the target node yet SYNC_verbose_timed << syncname << "Wait for moves to finish before confirming fsid " << toHandle(row.syncNode->fsid_lastSynced) << " deleted: " << logTriplet(row, fullPath); monitor.waitingLocal(fullPath.localPath, SyncStallEntry( SyncWaitReason::DeleteWaitingOnMoves, false, false, {row.cloudHandleOpt(), fullPath.cloudPath}, {}, {fullPath.localPath, PathProblem::DeletedOrMovedByUser}, {})); } // there's no folder so clear the flag so we don't stall row.syncNode->scanAgain = TREE_RESOLVED; row.syncNode->checkMovesAgain = TREE_RESOLVED; row.suppressRecursion = true; row.recurseBelowRemovedFsNode = true; row.syncNode->setSyncAgain(true, false, false); // make sure we revisit return false; } bool Sync::syncEqual(const CloudNode& n, const LocalNode& ln) { // return true if this node was previously synced, and the CloudNode fingerprint is equal to the fingerprint from then. // Assuming names already match // Not comparing nodehandle here. If they all match we set syncedCloudNodeHandle if (n.type != ln.type) return false; if (n.type != FILENODE) return true; assert(n.fingerprint.isvalid); return ln.syncedFingerprint.isvalid && n.fingerprint == ln.syncedFingerprint; // size, mtime, crc } bool Sync::syncEqual(const FSNode& fsn, const LocalNode& ln) { // return true if this node was previously synced, and the FSNode fingerprint is equal to the fingerprint from then. // Assuming names already match // Not comparing fsid here. If they all match then we set LocalNode's fsid if (fsn.type != ln.type) return false; if (fsn.type != FILENODE) return true; assert(fsn.fingerprint.isvalid); return ln.syncedFingerprint.isvalid && fsn.fingerprint == ln.syncedFingerprint; // size, mtime, crc } std::future Syncs::triggerPeriodicScanEarly(handle backupID) { // Cause periodic-scan syncs to scan now (waiting for the next periodic scan is impractical for tests) // For this backupId or for all periodic scan syncs if backupId == UNDEF assert(!onSyncThread()); auto indiscriminate = backupID == UNDEF; auto notifier = std::make_shared>(); auto result = notifier->get_future(); queueSync([backupID, indiscriminate, notifier, this]() { lock_guard guard(mSyncVecMutex); size_t count = 0; for (auto& us : mSyncVec) { auto* s = us->mSync.get(); if (!s) continue; if (!indiscriminate && us->mConfig.mBackupId != backupID) continue; if (us->mConfig.isScanOnly()) s->localroot->setScanAgain(false, true, true, 0); ++count; if (!indiscriminate) break; } notifier->set_value(count); }, "triggerPeriodicScanEarly"); return result; } void Syncs::triggerSync(const LocalPath& lp, bool scan) { assert(!onSyncThread()); if (mClient.fetchingnodes) return; // on start everything needs scan+sync anyway lock_guard g(triggerMutex); auto& entry = triggerLocalpaths[lp]; if (scan) entry = true; } void Syncs::triggerSync(NodeHandle h, bool recurse) { assert(!onSyncThread()); if (mClient.fetchingnodes) return; // on start everything needs scan+sync anyway lock_guard g(triggerMutex); auto& entry = triggerHandles[h]; if (recurse) entry = true; } bool Syncs::triggerHandlesPending() const { lock_guard g(triggerMutex); return !triggerHandles.empty(); } bool Syncs::triggerLocalpathsPending() const { lock_guard g(triggerMutex); return !triggerLocalpaths.empty(); } void Syncs::processTriggerLocalpaths() { // Mark nodes to be scanned because upload transfers failed. // This may save us trying to immediately start the same failed upload assert(onSyncThread()); map triggers; { lock_guard g(triggerMutex); triggers.swap(triggerLocalpaths); } lock_guard guard(mSyncVecMutex); if (mSyncVec.empty()) return; for (auto& lp : triggers) { for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { if (LocalNode* triggerLn = sync->localnodebypath(nullptr, lp.first, nullptr, nullptr, false)) { if (lp.second) { LOG_debug << "Scan trigger by path received for " << triggerLn->getLocalPath(); triggerLn->setScanAgain(false, true, false, 0); } else { LOG_debug << "Sync trigger by path received for " << triggerLn->getLocalPath(); triggerLn->setSyncAgain(false, true, false); } } } } } } void Syncs::processTriggerHandles() { assert(onSyncThread()); map triggers; { lock_guard g(triggerMutex); triggers.swap(triggerHandles); } { lock_guard guard(mSyncVecMutex); if (mSyncVec.empty()) return; } for (auto& t : triggers) { NodeHandle h = t.first; bool recurse = t.second; for (;;) { auto range = localnodeByNodeHandle.equal_range(h); if (range.first == range.second) { // corresponding sync node not found. // this could be a move target though, to a syncNode we have not created yet // go back up the (cloud) node tree to find an ancestor we can mark as needing sync checks CloudNode cloudNode; string cloudNodePath; bool isInTrash = false; bool found = lookupCloudNode(h, cloudNode, &cloudNodePath, &isInTrash, nullptr, nullptr, nullptr, Syncs::EXACT_VERSION); if (found && !isInTrash) { // if the parent is a file, then it's just old versions being mentioned in the actionpackets, ignore if (cloudNode.parentType > FILENODE && !cloudNode.parentHandle.isUndef()) { auto& syncs = *this; SYNC_verbose << mClient.clientname << "Trigger syncNode not found for " << cloudNodePath << ", will trigger parent"; recurse = true; h = cloudNode.parentHandle; continue; } } } else { // we are already being called with the handle of the parent of the thing that changed for (auto it = range.first; it != range.second; ++it) { auto& syncs = *this; SYNC_verbose << mClient.clientname << "Triggering sync flag for " << it->second->getLocalPath() << (recurse ? " recursive" : ""); it->second->setSyncAgain(false, true, recurse); } } break; } } } void Syncs::setdefaultfilepermissions(int permissions) { queueSync( [this, permissions]() { fsaccess->setdefaultfilepermissions(permissions); }, "setdefaultfilepermissions"); } void Syncs::setdefaultfolderpermissions(int permissions) { queueSync( [this, permissions]() { fsaccess->setdefaultfolderpermissions(permissions); }, "setdefaultfolderpermissions"); } #ifdef _WIN32 #define PATHSTRING(s) L ## s #else // _WIN32 #define PATHSTRING(s) s #endif // ! _WIN32 const LocalPath BACKUP_CONFIG_DIR = LocalPath::fromPlatformEncodedRelative(PATHSTRING(".megabackup")); #undef PATHSTRING const unsigned int NUM_CONFIG_SLOTS = 2; SyncConfigStore::SyncConfigStore(const LocalPath& dbPath, SyncConfigIOContext& ioContext) : mInternalSyncStorePath(dbPath) , mIOContext(ioContext) { assert(mInternalSyncStorePath.isAbsolute()); } SyncConfigStore::~SyncConfigStore() { assert(!dirty()); } void SyncConfigStore::markDriveDirty(const LocalPath& drivePath) { assert(drivePath.isAbsolute() || drivePath.empty()); // Drive should be known. assert(mKnownDrives.count(drivePath)); mKnownDrives[drivePath].dirty = true; } handle SyncConfigStore::driveID(const LocalPath& drivePath) const { auto i = mKnownDrives.find(drivePath); if (i != mKnownDrives.end()) return i->second.driveID; assert(false && "Drive should be known!"); return UNDEF; } bool SyncConfigStore::equal(const LocalPath& lhs, const LocalPath& rhs) const { return platformCompareUtf(lhs, false, rhs, false) == 0; } bool SyncConfigStore::dirty() const { for (auto& d : mKnownDrives) { if (d.second.dirty) return true; } return false; } LocalPath SyncConfigStore::dbPath(const LocalPath& drivePath) const { if (drivePath.empty()) { return mInternalSyncStorePath; } LocalPath dbPath = drivePath; dbPath.appendWithSeparator(BACKUP_CONFIG_DIR, false); return dbPath; } bool SyncConfigStore::driveKnown(const LocalPath& drivePath) const { assert(drivePath.isAbsolute() || drivePath.empty()); return mKnownDrives.count(drivePath) > 0; } vector SyncConfigStore::knownDrives() const { vector result; for (auto& i : mKnownDrives) { assert(i.first.empty() || i.first.isAbsolute()); result.emplace_back(i.first); } return result; } bool SyncConfigStore::removeDrive(const LocalPath& drivePath) { assert(drivePath.isAbsolute() || drivePath.empty()); return mKnownDrives.erase(drivePath) > 0; } error SyncConfigStore::read(const LocalPath& drivePath, SyncConfigVector& configs, bool isExternal) { assert(drivePath.empty() || drivePath.isAbsolute()); DriveInfo driveInfo; driveInfo.drivePath = drivePath; if (isExternal) { driveInfo.driveID = mIOContext.driveID(drivePath); if (driveInfo.driveID == UNDEF) { LOG_err << "Failed to retrieve drive ID for: " << drivePath; return API_EREAD; } } vector confSlots; auto result = mIOContext.getSlotsInOrder(dbPath(driveInfo.drivePath), confSlots); if (result == API_OK) { for (const auto& slot : confSlots) { result = read(driveInfo, configs, slot, isExternal); if (result == API_OK) { driveInfo.slot = (slot + 1) % NUM_CONFIG_SLOTS; break; } else { LOG_debug << "SyncConfigStore::read returned: " << int(result); } } } else { LOG_debug << "getSlotsInOrder returned: " << int(result); } if (result != API_EREAD) { mKnownDrives[drivePath] = driveInfo; } return result; } error SyncConfigStore::write(const LocalPath& drivePath, const SyncConfigVector& configs) { #ifndef NDEBUG for (const auto& config : configs) { assert(equal(config.mExternalDrivePath, drivePath)); } #endif // Drive should already be known. assert(mKnownDrives.count(drivePath)); auto& drive = mKnownDrives[drivePath]; // Always mark drives as clean. // This is to avoid us attempting to flush a failing drive forever. drive.dirty = false; if (configs.empty()) { error e = mIOContext.remove(dbPath(drive.drivePath)); if (e) { LOG_warn << "Unable to remove sync configs at: " << drivePath << " error " << e; } return e; } else { JSONWriter writer; mIOContext.serialize(configs, writer); error e = mIOContext.write(dbPath(drive.drivePath), writer.getstring(), drive.slot); if (e) { LOG_warn << "Unable to write sync configs at: " << drivePath << " error " << e; return API_EWRITE; } // start using a different slot (a different file) drive.slot = (drive.slot + 1) % NUM_CONFIG_SLOTS; // remove the existing slot (if any), since it is obsolete now mIOContext.remove(dbPath(drive.drivePath), drive.slot); return API_OK; } } error SyncConfigStore::read(DriveInfo& driveInfo, SyncConfigVector& configs, unsigned int slot, bool isExternal) { auto dbp = dbPath(driveInfo.drivePath); string data; if (mIOContext.read(dbp, data, slot) != API_OK) { LOG_debug << "mIOContext read failed"; return API_EREAD; } JSON reader(data); if (!mIOContext.deserialize(dbp, configs, reader, slot, isExternal)) { LOG_debug << "mIOContext deserialize failed"; return API_EREAD; } const auto& drivePath = driveInfo.drivePath; for (auto& config : configs) { config.mExternalDrivePath = drivePath; if (!drivePath.empty()) { // As it came from an external drive, the path is relative // but we didn't know that until now, for non-external it's absolute of course config.mLocalPath = LocalPath::fromRelativePath(config.mLocalPath.toPath(false)); config.mLocalPath.prependWithSeparator(drivePath); } } return API_OK; } auto SyncConfigStore::writeDirtyDrives(const SyncConfigVector& configs) -> DriveSet { DriveSet failed; for (auto& d : mKnownDrives) { if (!d.second.dirty) continue; const auto& drivePath = d.second.drivePath; SyncConfigVector v; for (auto& c : configs) { if (c.mExternalDrivePath == drivePath) { v.push_back(c); } } error e = write(drivePath, v); if (e) { LOG_err << "Could not write sync configs at " << drivePath << " error " << e; failed.emplace(drivePath); } } return failed; } const string SyncConfigIOContext::NAME_PREFIX = "megaclient_syncconfig_"; SyncConfigIOContext::SyncConfigIOContext(FileSystemAccess& fsAccess, const string& authKey, const string& cipherKey, const string& name, PrnGen& rng) : mCipher() , mFsAccess(fsAccess) , mName(LocalPath::fromRelativePath(NAME_PREFIX + name)) , mRNG(rng) , mSigner() { // Convenience. constexpr size_t KEYLENGTH = SymmCipher::KEYLENGTH; // These attributes *must* be sane. assert(authKey.size() == KEYLENGTH); assert(cipherKey.size() == KEYLENGTH); assert(name.size() == Base64Str::STRLEN); // Load the authentication key into our internal signer. mSigner.setkey(reinterpret_cast(authKey.data()), KEYLENGTH); // Load the encryption key into our internal cipher. mCipher.setkey(reinterpret_cast(cipherKey.data())); } SyncConfigIOContext::~SyncConfigIOContext() { } bool SyncConfigIOContext::deserialize(const LocalPath& dbPath, SyncConfigVector& configs, JSON& reader, unsigned int slot, bool isExternal) const { auto path = dbFilePath(dbPath, slot); LOG_debug << "Attempting to deserialize config DB: " << path; if (deserialize(configs, reader, isExternal)) { LOG_debug << "Successfully deserialized config DB: " << path; return true; } LOG_debug << "Unable to deserialize config DB: " << path; return false; } bool SyncConfigIOContext::deserialize(SyncConfigVector& configs, JSON& reader, bool isExternal) const { const auto TYPE_SYNCS = makeNameid("sy"); if (!reader.enterobject()) { return false; } for ( ; ; ) { switch (reader.getnameid()) { case EOO: return reader.leaveobject(); case TYPE_SYNCS: { if (!reader.enterarray()) { return false; } while (reader.enterobject()) { SyncConfig config; if (deserialize(config, reader, isExternal)) { configs.emplace_back(std::move(config)); } else { LOG_err << "Failed to deserialize a sync config"; assert(false); } reader.leaveobject(); } if (!reader.leavearray()) { return false; } break; } default: if (!reader.storeobject()) { return false; } break; } } } handle SyncConfigIOContext::driveID(const LocalPath& drivePath) const { handle result = UNDEF; readDriveId(mFsAccess, drivePath, result); return result; } FileSystemAccess& SyncConfigIOContext::fsAccess() const { return mFsAccess; } error SyncConfigIOContext::getSlotsInOrder(const LocalPath& dbPath, vector& confSlots) { using std::sort; using SlotTimePair = pair; // Glob for configuration directory. LocalPath globPath = dbPath; globPath.appendWithSeparator(mName, false); globPath.append(LocalPath::fromRelativePath(".?")); // Open directory for iteration. unique_ptr dirAccess(mFsAccess.newdiraccess()); if (!dirAccess->dopen(&globPath, nullptr, true)) { // Couldn't open directory for iteration. return API_ENOENT; } auto fileAccess = mFsAccess.newfileaccess(false); LocalPath filePath; vector slotTimes; nodetype_t type; // Iterate directory. while (dirAccess->dnext(globPath, filePath, false, &type)) { // Skip directories. if (type != FILENODE) { continue; } // Determine slot suffix. const char suffix = filePath.toPath(false).back(); // Skip invalid suffixes. if (!is_digit(static_cast(suffix))) { continue; } // Determine file's modification time. if (!fileAccess->fopen(filePath, FSLogging::logOnError)) { // Couldn't stat file. continue; } // Record this slot-time pair. unsigned int slot = static_cast(suffix - 0x30); // convert char to int slotTimes.emplace_back(slot, fileAccess->mtime); } // Sort the list of slot-time pairs. sort(slotTimes.begin(), slotTimes.end(), [](const SlotTimePair& lhs, const SlotTimePair& rhs) { // Order by descending modification time. if (lhs.second != rhs.second) { return lhs.second > rhs.second; } // Otherwise by descending slot. return lhs.first > rhs.first; }); // Transmit sorted list of slots to the caller. for (const auto& slotTime : slotTimes) { confSlots.emplace_back(slotTime.first); } return API_OK; } error SyncConfigIOContext::read(const LocalPath& dbPath, string& data, unsigned int slot) { // Generate path to the configuration file. LocalPath path = dbFilePath(dbPath, slot); LOG_debug << "Attempting to read config DB: " << path; // Try and open the file for reading. auto fileAccess = mFsAccess.newfileaccess(false); if (!fileAccess->fopen(path, OPEN_RDONLY, FSLogging::logOnError)) { // Couldn't open the file for reading. LOG_err << "Unable to open config DB for reading: " << path; return API_EREAD; } // Try and read the data from the file. string d; if (!fileAccess->fread(&d, static_cast(fileAccess->size), 0, 0x0, FSLogging::logOnError)) { // Couldn't read the file. LOG_err << "Unable to read config DB: " << path; return API_EREAD; } // Try and decrypt the data. if (!decrypt(d, data)) { // Couldn't decrypt the data. LOG_err << "Unable to decrypt config DB: " << path; return API_EREAD; } LOG_debug << "Config DB successfully read from disk: " << path << ": " << data; return API_OK; } error SyncConfigIOContext::remove(const LocalPath& dbPath, unsigned int slot) { LocalPath path = dbFilePath(dbPath, slot); if (mFsAccess.fileExistsAt(path) && // don't add error messages to the log when it's not an error !mFsAccess.unlinklocal(path)) { LOG_warn << "Unable to remove config DB: " << path; return API_EWRITE; } return API_OK; } error SyncConfigIOContext::remove(const LocalPath& dbPath) { vector confSlots; // What slots are present on disk? if (getSlotsInOrder(dbPath, confSlots) == API_ENOENT) { // None so nothing to do. return API_ENOENT; } bool result = true; // Remove the slots from disk. for (auto confSlot : confSlots) { result &= remove(dbPath, confSlot) == API_OK; } // Signal success only if all slots could be removed. return result ? API_OK : API_EWRITE; } void SyncConfigIOContext::serialize(const SyncConfigVector& configs, JSONWriter& writer) const { writer.beginobject(); writer.beginarray("sy"); for (const auto& config : configs) { serialize(config, writer); } writer.endarray(); writer.endobject(); } error SyncConfigIOContext::write(const LocalPath& dbPath, const string& data, unsigned int slot) { LocalPath path = dbPath; LOG_debug << "Attempting to write config DB: " << dbPath << " / " << slot; // Try and create the backup configuration directory. if (!(mFsAccess.mkdirlocal(path, false, false) || mFsAccess.target_exists)) { LOG_err << "Unable to create config DB directory: " << dbPath; // Couldn't create the directory and it doesn't exist. return API_EWRITE; } // Generate the rest of the path. path = dbFilePath(dbPath, slot); // Open the file for writing. auto fileAccess = mFsAccess.newfileaccess(false); if (!fileAccess->fopen(path, OPEN_WRONLY, FSLogging::logOnError)) { // Couldn't open the file for writing. LOG_err << "Unable to open config DB for writing: " << path; return API_EWRITE; } // Ensure the file is empty. if (!fileAccess->ftruncate()) { // Couldn't truncate the file. LOG_err << "Unable to truncate config DB: " << path; return API_EWRITE; } // Encrypt the configuration data. const string d = encrypt(data); // Write the encrypted configuration data. auto* bytes = reinterpret_cast(&d[0]); if (!fileAccess->fwrite(bytes, static_cast(d.size()), 0x0)) { // Couldn't write out the data. LOG_err << "Unable to write config DB: " << path; return API_EWRITE; } LOG_debug << "Config DB successfully written to disk: " << path << ": " << data; return API_OK; } LocalPath SyncConfigIOContext::dbFilePath(const LocalPath& dbPath, unsigned int slot) const { using std::to_string; LocalPath path = dbPath; path.appendWithSeparator(mName, false); path.append(LocalPath::fromRelativePath("." + to_string(slot))); return path; } bool SyncConfigIOContext::decrypt(const string& in, string& out) { // Handy constants. const size_t IV_LENGTH = SymmCipher::KEYLENGTH; const size_t MAC_LENGTH = 32; const size_t METADATA_LENGTH = IV_LENGTH + MAC_LENGTH; // Is the file too short to be valid? if (in.size() <= METADATA_LENGTH) { LOG_err << "Unable to decrypt JSON sync config: " << "File's too small (" << in.size() << " vs. " << METADATA_LENGTH << ")"; return false; } // For convenience (format: ) const byte* data = reinterpret_cast(&in[0]); const byte* iv = &data[in.size() - METADATA_LENGTH]; const byte* mac = &data[in.size() - MAC_LENGTH]; byte cmac[MAC_LENGTH]; // Compute HMAC on file. mSigner.add(data, in.size() - MAC_LENGTH); mSigner.get(cmac); // Is the file corrupt? if (memcmp(cmac, mac, MAC_LENGTH)) { LOG_err << "Unable to decrypt JSON sync config: " << "HMAC mismatch"; return false; } // Try and decrypt the file. return mCipher.cbc_decrypt_pkcs_padding(data, in.size() - METADATA_LENGTH, iv, &out); } bool SyncConfigIOContext::deserialize(SyncConfig& config, JSON& reader, bool isExternal) const { const auto TYPE_BACKUP_ID = makeNameid("id"); const auto TYPE_CHANGE_METHOD = makeNameid("cm"); const auto TYPE_ENABLED = makeNameid("en"); const auto TYPE_FILESYSTEM_FP = makeNameid("fp"); const auto TYPE_FILESYSTEM_FU = makeNameid("fu"); const auto TYPE_ROOT_FSID = makeNameid("rf"); const auto TYPE_LAST_ERROR = makeNameid("le"); const auto TYPE_LAST_WARNING = makeNameid("lw"); const auto TYPE_NAME = makeNameid("n"); const auto TYPE_SCAN_INTERVAL = makeNameid("si"); const auto TYPE_SOURCE_PATH = makeNameid("sp"); const auto TYPE_SYNC_TYPE = makeNameid("st"); const auto TYPE_TARGET_HANDLE = makeNameid("th"); const auto TYPE_TARGET_PATH = makeNameid("tp"); // Temporary storage. std::uint64_t fsFingerprint = 0; std::string fsUUID; for ( ; ; ) { switch (reader.getnameid()) { case EOO: // Populate fingerprint. config.mFilesystemFingerprint = fsfp_t(fsFingerprint, std::move(fsUUID)); // success if we reached the end of the object return *reader.pos == '}'; case TYPE_CHANGE_METHOD: config.mChangeDetectionMethod = static_cast(reader.getint32()); break; case TYPE_ENABLED: config.mEnabled = reader.getbool(); break; case TYPE_FILESYSTEM_FP: fsFingerprint = reader.getfsfp(); break; case TYPE_FILESYSTEM_FU: reader.storebinary(&fsUUID); break; case TYPE_ROOT_FSID: config.mLocalPathFsid = reader.gethandle(sizeof(handle)); break; case TYPE_LAST_ERROR: config.mError = static_cast(reader.getint32()); break; case TYPE_LAST_WARNING: config.mWarning = static_cast(reader.getint32()); break; case TYPE_NAME: reader.storebinary(&config.mName); break; case TYPE_SOURCE_PATH: { string sourcePath; reader.storebinary(&sourcePath); if (isExternal) { config.mLocalPath = LocalPath::fromRelativePath(sourcePath); } else { config.mLocalPath = LocalPath::fromAbsolutePath(sourcePath); } break; } case TYPE_SCAN_INTERVAL: config.mScanIntervalSec =reader.getuint32(); break; case TYPE_SYNC_TYPE: config.mSyncType = static_cast(reader.getint32()); break; case TYPE_BACKUP_ID: config.mBackupId = reader.gethandle(sizeof(handle)); break; case TYPE_TARGET_HANDLE: config.mRemoteNode = reader.getNodeHandle(); break; case TYPE_TARGET_PATH: reader.storebinary(&config.mOriginalPathOfRemoteRootNode); break; default: if (!reader.storeobject()) { return false; } break; } } } string SyncConfigIOContext::encrypt(const string& data) { byte iv[SymmCipher::KEYLENGTH]; // Generate initialization vector. mRNG.genblock(iv, sizeof(iv)); string d; // Encrypt file using IV. if (!mCipher.cbc_encrypt_pkcs_padding(&data, iv, &d)) { LOG_err << "Failed to encrypt file."; return d; } // Add IV to file. d.append(std::begin(iv), std::end(iv)); byte mac[32]; // Compute HMAC on file (including IV). mSigner.add(reinterpret_cast(&d[0]), d.size()); mSigner.get(mac); // Add HMAC to file. d.append(std::begin(mac), std::end(mac)); // We're done. return d; } void SyncConfigIOContext::serialize(const SyncConfig& config, JSONWriter& writer) const { auto sourcePath = config.mLocalPath.toPath(false); // Strip drive path from source. if (config.isExternal()) { auto drivePath = config.mExternalDrivePath.toPath(false); sourcePath.erase(0, drivePath.size()); } writer.beginobject(); writer.arg("id", config.mBackupId, sizeof(handle)); writer.arg_B64("sp", sourcePath); writer.arg_B64("n", config.mName); writer.arg_B64("tp", config.mOriginalPathOfRemoteRootNode); writer.arg_fsfp("fp", config.mFilesystemFingerprint.fingerprint()); writer.arg_B64("fu", config.mFilesystemFingerprint.uuid()); writer.arg("rf", config.mLocalPathFsid, sizeof(handle)); writer.arg("th", config.mRemoteNode); writer.arg("le", config.mError); writer.arg("lw", config.mWarning); writer.arg("st", config.mSyncType); writer.arg("en", config.mEnabled); writer.arg("cm", config.mChangeDetectionMethod); writer.arg("si", config.mScanIntervalSec); writer.endobject(); } std::vector> Syncs::getSyncVecCopy() const { assert(onSyncThread()); std::vector> auxSyncVec; { lock_guard guard(mSyncVecMutex); auxSyncVec = mSyncVec; } return auxSyncVec; } void Syncs::syncLoop() { syncThreadId = std::this_thread::get_id(); assert(onSyncThread()); std::condition_variable cv; std::mutex dummy_mutex; std::unique_lock dummy_lock(dummy_mutex); unsigned lastRecurseMs = 0; bool lastLoopEarlyExit = false; for (;;) { waiter->bumpds(); // Flush changes made to internal configs. syncConfigStoreFlush(); mHeartBeatMonitor->beat(); // Aim to wait at least one second between recursiveSync traversals, keep CPU down. // If traversals are very long, have a fair wait between (up to 5 seconds) // If something happens that means the sync needs attention, the waiter // should be woken up by a waiter->notify() call, and we break out of this wait if (!skipWait) { auto waitDs = 10 + std::min(lastRecurseMs, 10000)/200; if (mClient.statecurrent && waitDs != 10) { LOG_verbose << "starting sync wait, delay " << waitDs; } waiter->init(waitDs); waiter->wakeupby(fsaccess.get(), Waiter::NEEDEXEC); waiter->wait(); if (mClient.statecurrent && waitDs != 10) { LOG_verbose << "sync wait complete"; } } skipWait = false; lastRecurseMs = 0; fsaccess->checkevents(waiter.get()); // reset flag now, if it gets set then we speed back to processsing syncThreadActions mSyncFlags->earlyRecurseExitRequested = false; // execute any requests from the MegaClient waiter->bumpds(); for (auto& f: syncThreadActions.popAll()) { if (!f.first) { // null function is the signal to end the thread // Be sure to flush changes made to internal configs. syncConfigStoreFlush(); return; } if (!f.second.empty()) { LOG_debug << "Sync thread executing request: " << f.second; } f.first(); } waiter->bumpds(); // Make a copy of mSyncVec to avoid locking mutex so many time auto auxSyncVec = getSyncVecCopy(); // Process filesystem notifications. for (auto& us: auxSyncVec) { lock_guard guard(mSyncVecMutex); if (Sync* sync = us->mSync.get()) { if (sync->dirnotify) { sync->procscanq(); } } } processTriggerHandles(); processTriggerLocalpaths(); waiter->bumpds(); // We must have actionpacketsCurrent so that any LocalNode created can straight away indicate if it matched a Node // check this before we check if the sync root nodes exist etc, in case a mid-session fetchnodes is going on if (!mClient.actionpacketsCurrent) { if (mClient.statecurrent) { LOG_verbose << "not ready to sync recurse, actionpackets are changing nodes"; } continue; } // verify filesystem fingerprints, disable deviating syncs // (this covers mountovers, some device removals and some failures) for (auto& us: auxSyncVec) { NodeHandle remoteNodeHandle; { lock_guard guard(mSyncVecMutex); remoteNodeHandle = us->mConfig.mRemoteNode; } vector> sdsBackups; CloudNode cloudNode; string cloudRootPath; bool inTrash = false; unsigned rootDepth; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks bool foundRootNode = lookupCloudNode(remoteNodeHandle, cloudNode, &cloudRootPath, &inTrash, nullptr, nullptr, &rootDepth, Syncs::FOLDER_ONLY, nullptr, &sdsBackups); std::unique_lock syncVecMutexlock(mSyncVecMutex); if (processRemovingSyncBySds(*us.get(), foundRootNode, sdsBackups)) { continue; } else { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may // generate deadlocks; in this case // at`processPauseResumeSyncBySds->enableSyncByBackupId_inThread`, so we need to // release mutex before. syncVecMutexlock.unlock(); const auto success = foundRootNode && processPauseResumeSyncBySds(*us.get(), sdsBackups); syncVecMutexlock.lock(); if (success) { continue; } } if (Sync* sync = us->mSync.get()) { if (us->mConfig.mError != NO_SYNC_ERROR) { continue; } auto fa = fsaccess->newfileaccess(); if (fa->fopen(sync->localroot->localname, OPEN_RDONLY, FSLogging::logOnError, nullptr, true)) { if (fa->type != FOLDERNODE) { LOG_err << "Sync local root folder is not a folder: " << sync->localroot->localname; sync->changestate(INVALID_LOCAL_TYPE, false, true, true); mHeartBeatMonitor->updateOrRegisterSync(*us); continue; } else if (fa->fsid != sync->localroot->fsid_lastSynced) { LOG_err << "Sync local root folder fsid has changed for " << sync->localroot->localname << ": " << fa->fsid << " was: " << sync->localroot->fsid_lastSynced; sync->changestate(MISMATCH_OF_ROOT_FSID, false, true, true); mHeartBeatMonitor->updateOrRegisterSync(*us); continue; } } else { LOG_err << "Sync local root folder could not be opened: " << sync->localroot->localname; sync->changestate(LOCAL_PATH_UNAVAILABLE, false, true, true); mHeartBeatMonitor->updateOrRegisterSync(*us); continue; } auto expectedFsfp = sync->fsfp(); assert(expectedFsfp); if (expectedFsfp) { auto computedFsfp = fsaccess->fsFingerprint(sync->localroot->localname); if (computedFsfp && computedFsfp != expectedFsfp) { LOG_err << "Local filesystem mismatch. Previous: " << expectedFsfp.toString() << " Current: " << computedFsfp.toString(); sync->changestate(LOCAL_FILESYSTEM_MISMATCH, false, true, true); mHeartBeatMonitor->updateOrRegisterSync(*us); continue; } } sync->cloudRoot = cloudNode; sync->cloudRootPath = cloudRootPath; sync->mCurrentRootDepth = rootDepth; const bool remoteRootHasChanged = checkSyncRemoteLocationChange(us->mConfig, foundRootNode, sync->cloudRootPath); if (!foundRootNode) { LOG_err << "Detected sync root node no longer exists"; sync->changestate(REMOTE_NODE_NOT_FOUND, false, true, true); } else if (inTrash) { LOG_err << "Detected sync root node is now in trash"; sync->changestate(REMOTE_NODE_MOVED_TO_RUBBISH, false, true, true); } else if (remoteRootHasChanged) { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we // may generate // deadlocks; in this case at`lookupCloudNode` and explicit lock of // `nodeTreeMutex` some lines below, so we need to release mutex before. syncVecMutexlock.unlock(); manageRemoteRootLocationChange(*sync); // Lock `mSyncVecMutex` again syncVecMutexlock.lock(); } if (!us->mConfig.mEnabled && us->mConfig.mError != NO_SYNC_ERROR) { mHeartBeatMonitor->updateOrRegisterSync(*us); } } else if (us->mConfig.mRunState == SyncRunState::Suspend && (us->mConfig.mError == LOCAL_PATH_UNAVAILABLE || us->mConfig.mError == LOCAL_PATH_TEMPORARY_UNAVAILABLE)) { // If we shut the sync down before because the local path wasn't available (yet) // And it's safe to resume the sync because it's in Suspend (rather than disable) // then we can auto-restart it, if the path becomes available (eg, network drive was // slow to mount, user plugged in USB, etc) auto computedFsfp = fsaccess->fsFingerprint(us->mConfig.mLocalPath); auto expectedFsfp = us->mConfig.mFilesystemFingerprint; auto fa = fsaccess->newfileaccess(); if (fa->fopen(us->mConfig.mLocalPath, OPEN_RDONLY, FSLogging::logExceptFileNotFound, nullptr, true)) { if (fa->type != FOLDERNODE) { LOG_err << "Sync path is available again but is not a folder: " << us->mConfig.mLocalPath; } // todo: we don't keep the fsid of the root folder in config, but maybe we should? //else if (fa->fsid != us->mConfig.mLocalPathFsid) //{ // LOG_err << "Sync path is available again but is not the same folder, by fsid: " << us->mConfig.mLocalPath; //} else if (computedFsfp && expectedFsfp && computedFsfp != expectedFsfp) { LOG_err << "Sync path is available again but is not the same filesystem, by id. Old: " << expectedFsfp.toString() << " New: " << computedFsfp.toString() << " Path: " << us->mConfig.mLocalPath; } else { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks; in this case at `enableSyncByBackupId_inThread`, so we need to release mutex before. const auto auxBackupId = us->mConfig.mBackupId; syncVecMutexlock.unlock(); LOG_debug << "Auto-starting sync that was suspended when the local path " "was unavailable: " << us->mConfig.mLocalPath; enableSyncByBackupId_inThread(auxBackupId, false, nullptr, "", ""); } } } }; stopSyncsInErrorState(); // Clear the context if the associated sync is no longer active. mIgnoreFileFailureContext.reset(*this); const auto auxRecursiveSyncLastCompletedDs = mSyncFlags->recursiveSyncLastCompletedDs.load(); if (syncStallState && (waiter->ds < auxRecursiveSyncLastCompletedDs + 10) && (waiter->ds > auxRecursiveSyncLastCompletedDs) && !lastLoopEarlyExit && !auxSyncVec.empty()) { LOG_debug << "Don't process syncs too often in stall state"; continue; } bool earlyExit = false; auto recurseStart = std::chrono::high_resolution_clock::now(); CodeCounter::ScopeTimer rst(mClient.performanceStats.recursiveSyncTime); if (!lastLoopEarlyExit) { // we need one pass with recursiveSync() after scanning is complete, to be sure there are no moves left. const auto [scanningWasCompletePreviously, scanningWasCompleted] = checkSyncsScanningWasComplete_inThread(); mSyncFlags->scanningWasComplete.store(scanningWasCompleted); mSyncFlags->reachableNodesAllScannedLastPass.store( mSyncFlags->reachableNodesAllScannedThisPass.exchange(true) && !mSyncFlags->isInitialPass.load()); auto allSyncsMovesWereComplete = checkSyncsMovesWereComplete(); mSyncFlags->movesWereComplete.store(scanningWasCompletePreviously && allSyncsMovesWereComplete); const auto auxNoProgress{mSyncFlags->reachableNodesAllScannedLastPass.load()}; mSyncFlags->noProgress.store(auxNoProgress); if (auxNoProgress) mSyncFlags->stall.setNoProgress(); if (const bool scanningCompleted = mSyncFlags->scanningWasComplete.load(), reachableNodesAllScannedLastPass = mSyncFlags->reachableNodesAllScannedLastPass.load(), movesWereComplete = mSyncFlags->movesWereComplete.load(); !scanningWasCompletePreviously || !scanningCompleted || !reachableNodesAllScannedLastPass || !allSyncsMovesWereComplete || !movesWereComplete) { SYNCS_verbose_timed << "[SyncLoop] scanningWasCompletePreviously = " << scanningWasCompletePreviously << ", scanningWasComplete = " << scanningCompleted << ", reachableNodesAllScannedLastPass = " << reachableNodesAllScannedLastPass << ", allSyncsMovesWereComplete = " << allSyncsMovesWereComplete << ", movesWereComplete = " << movesWereComplete << ", noProgress = " << auxNoProgress; } } unsigned skippedForScanning = 0; for (auto& us: auxSyncVec) { std::unique_lock syncVecMutexlock(mSyncVecMutex); Sync* sync = us->mSync.get(); if (sync && !us->mConfig.mError) { // Does this sync rely on filesystem notifications? if (auto* notifier = sync->dirnotify.get()) { // Has it encountered a recoverable error? if (notifier->mErrorCount.load() > 0) { // Then issue a full scan. LOG_err << "Sync " << toHandle(sync->getConfig().mBackupId) << " had a filesystem notification buffer overflow. Triggering full scan."; // Reset the error counter. notifier->mErrorCount.store(0); // Rescan everything from the root down. sync->localroot->setScanAgain(false, true, true, 5); } string reason; // Has it encountered an unrecoverable error? if (notifier->getFailed(reason)) { // Then fail the sync. LOG_err << "Sync " << toHandle(sync->getConfig().mBackupId) << " notifications failed or were not available (reason: " << reason << ")"; sync->changestate(NOTIFICATION_SYSTEM_UNAVAILABLE, false, true, true); mHeartBeatMonitor->updateOrRegisterSync(*us); continue; } } else { // No notifier. // Is it time to issue a rescan? if (sync->syncscanbt.armed()) { // Issue full rescan. sync->localroot->setScanAgain(false, true, true, 0); // Translate interval into deciseconds. auto intervalDs = sync->getConfig().mScanIntervalSec * 10; // Queue next scan. sync->syncscanbt.backoff(intervalDs); } } { bool activeIncomplete = sync->mActiveScanRequestGeneral && !sync->mActiveScanRequestGeneral->completed(); bool unscannedIncomplete = sync->mActiveScanRequestUnscanned && !sync->mActiveScanRequestUnscanned->completed(); if ((activeIncomplete && unscannedIncomplete) || (activeIncomplete && sync->threadSafeState->neverScannedFolderCount.load() == 0) || (unscannedIncomplete && !sync->mActiveScanRequestGeneral)) { // Save CPU by not starting another recurse of the LocalNode tree // if a scan is not finished yet. Scans can take a fair while for large // folders since they also extract file fingerprints if not known yet. ++skippedForScanning; continue; } // make sure we don't have a LocalNode for the debris folder (delete if we have added one historically) if (LocalNode* debrisNode = sync->localroot->childbyname(&sync->localdebrisname)) { delete debrisNode; // cleans up its own entries in parent maps } // pathBuffer will have leafnames appended as we recurse SyncPath pathBuffer(*this, sync->localroot->localname, sync->cloudRootPath); FSNode rootFsNode(sync->localroot->getLastSyncedFSDetails()); SyncRow row{&sync->cloudRoot, sync->localroot.get(), &rootFsNode}; { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, // otherwise we may generate deadlocks; in this case at `recursiveSync` // `nodeTreeMutex` is locked, so we need to release `mSyncVecMutex` // // Also `mSyncVecMutex` cannot be locked before locking `mLocalNodeChangeMutex`, syncVecMutexlock.unlock(); // later we can make this lock much finer-grained std::lock_guard g(mLocalNodeChangeMutex); DBTableTransactionCommitter committer(sync->statecachetable); if (!sync->recursiveSync(row, pathBuffer, false, false, 0)) { earlyExit = true; } // Lock syncVecMutexlock again syncVecMutexlock.lock(); sync->cachenodes(); } } if (!us->mConfig.mFinishedInitialScanning && !sync->localroot->scanRequired()) { LOG_debug << "Finished initial sync scan at " << sync->localroot->getLocalPath(); us->mConfig.mFinishedInitialScanning = true; } // send stats to the app, per sync PerSyncStats counts; counts.scanning = sync->localroot->scanRequired(); counts.syncing = sync->localroot->mightHaveMoves() || sync->localroot->syncRequired(); sync->threadSafeState->getSyncNodeCounts(counts.numFiles, counts.numFolders); SyncTransferCounts stc = sync->threadSafeState->transferCounts(); counts.numUploads = static_cast(stc.mUploads.mPending); counts.numDownloads = static_cast(stc.mDownloads.mPending); if (us->lastReportedDisplayStats != counts) { mClient.app->syncupdate_stats(us->mConfig.mBackupId, counts); us->lastReportedDisplayStats = counts; } } } if (mTransferPauseFlagsChanged.exchange(false, std::memory_order_relaxed)) { for (auto& us: auxSyncVec) { lock_guard guard(mSyncVecMutex); mHeartBeatMonitor->updateOrRegisterSync(*us); } } #ifdef MEGA_MEASURE_CODE rst.complete(); #endif lastRecurseMs = unsigned(std::chrono::duration_cast( std::chrono::high_resolution_clock::now() - recurseStart).count()); const int noProgressCountLoggingFrequency = 500; // Log this every 500 counts const auto auxNoProgressCount = mSyncFlags->noProgressCount.load(); if (!skippedForScanning && !earlyExit && auxNoProgressCount && (auxNoProgressCount % noProgressCountLoggingFrequency == 0)) { LOG_verbose << "recursiveSync took ms: " << lastRecurseMs << (skippedForScanning ? " (" + std::to_string(skippedForScanning) + " skipped due to ongoing scanning)" : "") << (auxNoProgressCount ? " no progress count: " + std::to_string(auxNoProgressCount) : "") << (earlyExit ? " (earlyExit)" : ""); } waiter->bumpds(); mSyncFlags->recursiveSyncLastCompletedDs.store(waiter->ds); if (skippedForScanning > 0) { // avoid flip-flopping on stall state if the stalled paths were inside a sync that is still scanning earlyExit = true; } if (earlyExit) { unsetSyncsScanningWasComplete_inThread(); mSyncFlags->scanningWasComplete.store(false); mSyncFlags->reachableNodesAllScannedThisPass.store(false); } else { mSyncFlags->isInitialPass.store(false); // Process name conflicts processSyncConflicts(); // Process scanning updates bool anySyncScanning = isAnySyncScanning_inThread(); if (anySyncScanning != syncscanstate) { assert(onSyncThread()); syncscanstate = anySyncScanning; mClient.app->syncupdate_scanning(anySyncScanning); } // Process syncing updates bool anySyncBusy = isAnySyncSyncing(); if (anySyncBusy != syncBusyState) { assert(onSyncThread()); syncBusyState = anySyncBusy; mClient.app->syncupdate_syncing(anySyncBusy); } // Process stall issues processSyncStalls(); ++completedPassCount; } // Process throttling queue processDelayedUploads(); lastLoopEarlyExit = earlyExit; } } bool Syncs::isAnySyncSyncing() const { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { if (!us->mConfig.mError && (sync->localroot->scanRequired() || sync->localroot->mightHaveMoves() || sync->localroot->syncRequired())) { return true; } } } return false; } bool Syncs::isAnySyncScanning_inThread() const { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { if (sync->isSyncScanning()) { return true; } } } return false; } std::pair Syncs::checkSyncsScanningWasComplete_inThread() { assert(onSyncThread()); const auto scanningWasCompletePreviously = mSyncFlags->scanningWasComplete.load() && !mSyncFlags->isInitialPass.load(); lock_guard guard(mSyncVecMutex); bool allSyncsScanningWereComplete = true; for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { allSyncsScanningWereComplete &= sync->checkScanningWasComplete(); } } return {scanningWasCompletePreviously, allSyncsScanningWereComplete}; } void Syncs::unsetSyncsScanningWasComplete_inThread() { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { sync->unsetScanningWasComplete(); } } } bool Syncs::checkSyncsMovesWereComplete() { assert(onSyncThread()); lock_guard guard(mSyncVecMutex); bool allSyncsMovesWereComplete = true; for (auto& us : mSyncVec) { if (Sync* sync = us->mSync.get()) { allSyncsMovesWereComplete &= sync->checkMovesWereComplete(); } } return allSyncsMovesWereComplete; } void Syncs::setSyncsNeedFullSync(bool andFullScan, bool andReFingerprint, handle backupId) { assert(!onSyncThread()); queueSync([=](){ assert(onSyncThread()); lock_guard guard(mSyncVecMutex); for (auto & us : mSyncVec) { if ((us->mConfig.mBackupId == backupId || backupId == UNDEF) && us->mSync) { us->mSync->localroot->setSyncAgain(false, true, true); if (andFullScan) { us->mSync->localroot->setScanAgain(false, true, true, 0); if (andReFingerprint) { us->mSync->localroot->setSubtreeNeedsRefingerprint(); } } } } }, "setSyncsNeedFullSync"); } bool Syncs::conflictsDetected(SyncIDtoConflictInfoMap& conflicts) { assert(onSyncThread()); size_t totalConflicts{}; // Make a copy of mSyncVec to avoid locking mutex so many time auto auxSyncVec = getSyncVecCopy(); for (auto& us: auxSyncVec) { std::unique_lock syncVecMutexLock(mSyncVecMutex); if (Sync* sync = us->mSync.get(); sync && sync->localroot->conflictsDetected()) { auto [it, success] = conflicts.emplace(us->mConfig.mBackupId, list()); if (!success) { assert(false); LOG_err << "[Syncs::conflictsDetected()] cannot add entry at conflicts map " "with BackUpId: " << toHandle(us->mConfig.mBackupId); conflicts.clear(); break; } // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, // otherwise we may generate deadlocks; in this case at // `recursiveCollectNameConflicts->lookupCloudChildren` `nodeTreeMutex` is locked, // so we need to release `mSyncVecMutex` syncVecMutexLock.unlock(); sync->recursiveCollectNameConflicts(&it->second); // Lock `syncVecMutex` again syncVecMutexLock.lock(); totalConflicts += it->second.size(); } } // Disable sync conflicts update flag mClient.app->syncupdate_totalconflicts(false); // totalSyncConflicts is set by conflictsDetectedCount, whose count is limited by the previous // number of conflicts + 1, in order to avoid extra recursive operations as the full count is // not needed This updates the counter to the real number of conflicts, so we avoid incremental // updates later (from previous_conflicts_size + 1 to actual_conflicts_size) totalSyncConflicts.store(totalConflicts); return totalConflicts > 0; } size_t Syncs::conflictsDetectedCount(size_t limit) const { assert(onSyncThread()); size_t count = 0; // Make a copy of mSyncVec to avoid locking mutex so many time auto auxSyncVec = getSyncVecCopy(); for (auto& us : auxSyncVec) { std::unique_lock syncVecMutexLock(mSyncVecMutex); if (Sync* sync = us->mSync.get()) { if (sync->localroot->conflictsDetected()) { if (limit == 1) { count = limit; break; } // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, // otherwise we may generate deadlocks; in this case at // `recursiveCollectNameConflicts->lookupCloudChildren` `nodeTreeMutex` is locked, // so we need to release `mSyncVecMutex` syncVecMutexLock.unlock(); sync->recursiveCollectNameConflicts(nullptr, &count, limit ? &limit : nullptr); if (count >= limit) { break; } // Lock `syncVecMutex` again syncVecMutexLock.lock(); } } } return count; } void Syncs::collectSyncNameConflicts(handle backupId, std::function&& nc)> completion, bool completionInClient) { assert(!onSyncThread()); auto clientCompletion = [this, completion](list&& nc) { shared_ptr> ncptr(new list(std::move(nc))); queueClient( [completion, ncptr](MegaClient&, TransferDbCommitter&) { if (completion) completion(std::move(*ncptr)); }); }; auto finalcompletion = completionInClient ? clientCompletion : completion; queueSync([=]() { list nc; // Make a copy of mSyncVec to avoid locking mutex so many time auto auxSyncVec = getSyncVecCopy(); for (auto& us : auxSyncVec) { std::unique_lock syncVecMutexLock(mSyncVecMutex); if (us->mSync && (us->mConfig.mBackupId == backupId || backupId == UNDEF)) { // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, // otherwise we may generate deadlocks; in this case at // `recursiveCollectNameConflicts->lookupCloudChildren` `nodeTreeMutex` is // locked, so we need to release `mSyncVecMutex` syncVecMutexLock.unlock(); us->mSync->recursiveCollectNameConflicts(&nc); // Lock `syncVecMutex` again syncVecMutexLock.lock(); } } finalcompletion(std::move(nc)); }, "collectSyncNameConflicts"); } bool Syncs::stallsDetected(SyncStallInfo& stallInfo) { assert(onSyncThread()); { lock_guard guard(stallReportMutex); if (syncStallState) // If in stall state, there must be either lack of progress or immediate // alerts { for (auto& [id, syncStallInfoMap]: stallReport.syncStallInfoMaps) { if (syncStallInfoMap.hasProgressLack()) { for (auto& r: syncStallInfoMap.cloud) stallInfo.syncStallInfoMaps[id].cloud.insert(r); for (auto& r: syncStallInfoMap.local) stallInfo.syncStallInfoMaps[id].local.insert(r); } else { for (auto& r: syncStallInfoMap.cloud) { if (r.second.alertUserImmediately) stallInfo.syncStallInfoMaps[id].cloud.insert(r); } for (auto& r: syncStallInfoMap.local) { if (r.second.alertUserImmediately) stallInfo.syncStallInfoMaps[id].local.insert(r); } } } // Update totalSyncStalls just in case it is different since last check totalSyncStalls.store(stallInfo.size()); } } // Disable sync stalls update flag mClient.app->syncupdate_totalstalls(false); return syncStallState; } size_t Syncs::stallsDetectedCount() const { assert(onSyncThread()); if (!syncStallState) { return 0; } size_t reportableSize{0}; size_t stallReportSize{0}; { lock_guard guard(stallReportMutex); reportableSize = stallReport.reportableSize(); stallReportSize = stallReport.size(); } if (reportableSize <= 0) { LOG_warn << "[Syncs::stallsDetectedCount()] reportableSize (" << reportableSize << ") is not positive! [real size = " << stallReportSize << "]"; lock_guard guard(stallReportMutex); assert(hasImmediateStall( stallReport)); // If in sync stall state, there must be reportable size (unless there is // a custom hasImmediateStall predicate for tests) } return reportableSize; } bool Syncs::syncStallDetected(SyncStallInfo& si) const { assert(!onSyncThread()); lock_guard g(stallReportMutex); if (syncStallState) { si = stallReport; return true; } return false; } void Syncs::processSyncConflicts() { assert(onSyncThread()); bool conflictsNow = conflictsDetectedCount(1) > 0; // We only need to know if we have at least 1 conflict if (conflictsNow != syncConflictState) { assert(onSyncThread()); LOG_verbose << mClient.clientname << "New name conflicts state: " << conflictsNow; syncConflictState = conflictsNow; if (conflictsNow) { assert(!totalSyncConflicts.load()); auto conflictsCount = conflictsDetectedCount(); totalSyncConflicts.store(conflictsCount); lastSyncConflictsCount = std::chrono::steady_clock::now(); } else { totalSyncConflicts.store(0); } mClient.app->syncupdate_totalconflicts(false); mClient.app->syncupdate_conflicts(conflictsNow); LOG_warn << mClient.clientname << "Name conflicts state app notified: " << conflictsNow; } else if (conflictsNow && !mClient.app->isSyncStalledChanged() && ((std::chrono::steady_clock::now() - lastSyncConflictsCount) >= MIN_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT)) { auto minDelayBetweenConflictsCount = std::min(std::max(MIN_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT, std::chrono::milliseconds(totalSyncConflicts.load() * 10)), // 1 second for every 100 conflicts MAX_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT); if ((std::chrono::steady_clock::now() - lastSyncConflictsCount) >= minDelayBetweenConflictsCount) { auto updatedTotalSyncsConflict = conflictsDetectedCount(totalSyncConflicts.load() + 1); // We only need to know either if there are now less conflicts or at least one conflict more than before if (totalSyncConflicts.load() != updatedTotalSyncsConflict) { assert(onSyncThread()); mClient.app->syncupdate_totalconflicts(true); LOG_info << mClient.clientname << "Sync conflicting paths state app update notified [previousSyncConflictsTotal = " << totalSyncConflicts.load() << ", updatedTotalSyncsConflict = " << updatedTotalSyncsConflict << "]"; totalSyncConflicts.store(updatedTotalSyncsConflict); } lastSyncConflictsCount = std::chrono::steady_clock::now(); } } } void Syncs::processSyncStalls() { assert(onSyncThread()); bool stalled = syncStallState; { for (auto i = badlyFormedIgnoreFilePaths.begin(); i != badlyFormedIgnoreFilePaths.end(); ) { if (auto lp = i->lock()) { if (lp->sync) { auto& affectedBackupId = lp->sync->getConfig().mBackupId; assert(affectedBackupId != UNDEF); mSyncFlags->stall.waitingLocal(affectedBackupId, lp->localPath, SyncStallEntry( SyncWaitReason::FileIssue, true, false, {}, {}, {lp->localPath, PathProblem::IgnoreFileMalformed}, {})); } ++i; } else i = badlyFormedIgnoreFilePaths.erase(i); } // add scan-blocked paths to stall records for (auto i = scanBlockedPaths.begin(); i != scanBlockedPaths.end(); ) { if (auto sbp = i->lock()) { if (sbp->localNode->exclusionState() == ES_EXCLUDED) { // the user ignored the path in response to the stall item, probably i = scanBlockedPaths.erase(i); continue; } if (sbp->scanBlockedTimer.armed()) { if (sbp->folderUnreadable) { LOG_verbose << "Scan blocked timer elapsed, trigger folder rescan: " << sbp->scanBlockedLocalPath; sbp->localNode->setScanAgain(false, true, false, 0); sbp->scanBlockedTimer.backoff(); // wait increases exponentially (up to 10 mins) until we succeed } else { LOG_verbose << "Locked file fingerprint timer elapsed, trigger folder rescan: " << sbp->scanBlockedLocalPath; sbp->localNode->setScanAgain(false, true, false, 0); sbp->scanBlockedTimer.backoff(300); // limited wait (30s) as the user will expect it uploaded fairly soon after they stop editing it } } auto affectedBackupId = sbp->sync ? sbp->sync->getConfig().mBackupId : UNDEF; assert(affectedBackupId != UNDEF); if (sbp->folderUnreadable) { mSyncFlags->stall.waitingLocal(affectedBackupId, sbp->scanBlockedLocalPath, SyncStallEntry( SyncWaitReason::FileIssue, true, false, {}, {}, {sbp->scanBlockedLocalPath, PathProblem::FilesystemErrorListingFolder}, {})); } else { assert(sbp->filesUnreadable); LOG_verbose << "Locked file(s) fingerprint inside this path (resulting in scan blocked path): " << sbp->scanBlockedLocalPath; } ++i; } else i = scanBlockedPaths.erase(i); } // Update progress counters mSyncFlags->stall.updateNoProgress(); lock_guard g(stallReportMutex); // Update stall report with the stalls created during the loop stallReport.moveFromButKeepCountersAndClearObsoleteKeys(mSyncFlags->stall); // Check immediate stalls and progress lack stalls bool immediateStall = hasImmediateStall(stallReport); bool progressLackStall = stallReport.hasProgressLackStall() && mSyncFlags->reachableNodesAllScannedThisPass.load(); stalled = !stallReport.empty() && (immediateStall || progressLackStall); if (stalled) { for (auto& [syncId, syncStallInfoMap] : stallReport.syncStallInfoMaps) { bool syncProgressLackStall = syncStallInfoMap.hasProgressLack(); for (auto& p : syncStallInfoMap.cloud) if (syncProgressLackStall || isImmediateStall(p.second)) { SYNCS_verbose_timed << "Stall detected! stalled node path (" << syncWaitReasonDebugString(p.second.reason) << "): " << p.first << " [backupId = " << syncId << "]"; } for (auto& p : syncStallInfoMap.local) if (syncProgressLackStall || isImmediateStall(p.second)) { SYNCS_verbose_timed << "Stall detected! stalled local path (" << syncWaitReasonDebugString(p.second.reason) << "): " << p.first << " [backupId = " << syncId << "]"; } } } } if (stalled != syncStallState) { assert(onSyncThread()); LOG_verbose << mClient.clientname << "New stall state: " << stalled; syncStallState = stalled; if (stalled) { assert(!totalSyncStalls.load()); totalSyncStalls.store(stallsDetectedCount()); lastSyncStallsCount = std::chrono::steady_clock::now(); } else { totalSyncStalls.store(0); } mClient.app->syncupdate_totalstalls(false); mClient.app->syncupdate_stalled(stalled); LOG_warn << mClient.clientname << "Stall state app notified: " << stalled; } else if (stalled && !mClient.app->isSyncStalledChanged() && ((std::chrono::steady_clock::now() - lastSyncStallsCount) >= MIN_DELAY_BETWEEN_SYNC_STALLS_OR_CONFLICTS_COUNT)) { auto updatedTotalSyncsStalls = stallsDetectedCount(); if (totalSyncStalls.load() != updatedTotalSyncsStalls) { assert(onSyncThread()); mClient.app->syncupdate_totalstalls(true); LOG_warn << mClient.clientname << "Stall state app update notified [previousSyncStallsTotal = " << totalSyncStalls.load() << ", updatedTotalSyncsStalls = " << updatedTotalSyncsStalls << "]"; totalSyncStalls.store(updatedTotalSyncsStalls); } lastSyncStallsCount = std::chrono::steady_clock::now(); } } void Syncs::proclocaltree(LocalNode* n, LocalTreeProc* tp) { assert(onSyncThread()); if (n->type > FILENODE) { for (localnode_map::iterator it = n->children.begin(); it != n->children.end(); ) { assert(it->first == it->second->localname); LocalNode *child = it->second; it++; proclocaltree(child, tp); } } tp->proc(*n->sync->syncs.fsaccess, n); } bool Syncs::isCloudNodeInShare(const CloudNode& cn) { lock_guard g(mClient.nodeTreeMutex); std::shared_ptr n = mClient.mNodeManager.getNodeByHandle(cn.handle); return n && n->matchesOrHasAncestorMatching( [](const Node& node) -> bool { return node.inshare != nullptr; }); } bool Syncs::lookupCloudNode(NodeHandle h, CloudNode& cn, string* cloudPath, bool* isInTrash, bool* nodeIsInActiveSyncQuery, bool* nodeIsDefinitelyExcluded, unsigned* depth, WhichCloudVersion whichVersion, handle* owningUser, vector>* sdsBackups) { // we have to avoid doing these lookups when the client thread might be changing the Node tree // so we use the mutex to prevent access during that time - which is only actionpacket processing. assert(onSyncThread()); assert(!nodeIsDefinitelyExcluded || nodeIsInActiveSyncQuery); // if you ask if it's excluded, you must ask if it's in sync too if (h.isUndef()) return false; vector> activeSyncHandles; vector, Sync*>> activeSyncRoots; // `mSyncVecMutex` cannot be locked before locking `nodeTreeMutex`, otherwise we may generate // deadlocks lock_guard g(mClient.nodeTreeMutex); if (nodeIsInActiveSyncQuery) { *nodeIsInActiveSyncQuery = false; lock_guard guard(mSyncVecMutex); for (auto& us: mSyncVec) { if (us->mSync && !us->mConfig.mError) { activeSyncHandles.emplace_back(us->mConfig.mRemoteNode, us->mSync.get()); } } } if (nodeIsInActiveSyncQuery) { for (auto & rh : activeSyncHandles) { if (std::shared_ptr rn = mClient.mNodeManager.getNodeByHandle(rh.first)) { activeSyncRoots.emplace_back(rn, rh.second); } } } if (std::shared_ptr n = mClient.mNodeManager.getNodeByHandle(h)) { switch (whichVersion) { case EXACT_VERSION: break; case LATEST_VERSION: { std::shared_ptr m = n->latestFileVersion(); if (m != n) { auto& syncs = *this; SYNC_verbose << "Looking up Node " << n->nodeHandle() << " chose latest version " << m->nodeHandle(); n = m; } } break; case LATEST_VERSION_ONLY: if (n->type != FILENODE) break; if (n->parent && n->parent->type == FILENODE) return false; break; case FOLDER_ONLY: assert(n->type > FILENODE); break; } if (isInTrash) { *isInTrash = n->firstancestor()->nodeHandle() == mClient.mNodeManager.getRootNodeRubbish(); } if (cloudPath) *cloudPath = n->displaypath(); if (depth) *depth = n->depth(); if (sdsBackups) { // SDS values for full-syncs (account root node) are set in the *!sds attribute // instead of as an attribute of the backup/sync root node. if (mClient.mNodeManager.getRootNodeFiles().eq(h)) { *sdsBackups = getSdsBackupsFullSync(); } else { *sdsBackups = n->getSdsBackups(); } } cn = CloudNode(*n); if (nodeIsInActiveSyncQuery) { auto it = std::find_if(activeSyncRoots.begin(), activeSyncRoots.end(), [n](const pair, Sync *> &rn) { return n->isbelow(rn.first.get()); }); if (it != activeSyncRoots.end()) { *nodeIsInActiveSyncQuery = true; if (nodeIsDefinitelyExcluded) *nodeIsDefinitelyExcluded = isDefinitelyExcluded(*it, n); } } if (owningUser) { if (auto& inshare = n->firstancestor()->inshare) { *owningUser = inshare->user->userhandle; } else { *owningUser = mClient.me; } } return true; } return false; } bool Syncs::lookupCloudChildren(NodeHandle h, vector& cloudChildren) { // we have to avoid doing these lookups when the client thread might be changing the Node tree // so we use the mutex to prevent access during that time - which is only actionpacket processing. assert(onSyncThread()); lock_guard g(mClient.nodeTreeMutex); if (std::shared_ptr n = mClient.mNodeManager.getNodeByHandle(h)) { assert(n->type > FILENODE); assert(!n->parent || n->parent->type > FILENODE); sharedNode_list nl = mClient.mNodeManager.getChildren(n.get()); cloudChildren.reserve(nl.size()); for (auto& c : nl) { cloudChildren.push_back(*c); assert(cloudChildren.back().parentHandle == h); } return true; } return false; } bool Syncs::isDefinitelyExcluded(const pair, Sync*>& root, std::shared_ptr child) { // Make sure we're on the sync thread. assert(onSyncThread()); // Make sure we're looking at the latest version of child. child = child->latestFileVersion(); // Sanity. assert(child->isbelow(root.first.get())); // Determine the trail from root to child. vector> trail; for (auto node = child; node != root.first; node = node->parent) trail.emplace_back(node->nodeHandle(), node->displayname()); // Determine whether any step from root to child is definitely excluded. auto* parent = root.second->localroot.get(); auto i = trail.crbegin(); auto j = trail.crend(); for ( ; i != j; ++i) { // Does this node on the trail have a local node? auto* node = parent->findChildWithSyncedNodeHandle(i->first); // No node so we'll have to check by remote path. if (!node) break; // Name doesn't match so we'll have to check by remote path. if (node->toName_of_localname != i->second) break; // Children are definitely excluded if their parent is. if (node->exclusionState() == ES_EXCLUDED) return true; // Children are considered included if their parent's state is indeterminate. if (node->exclusionState() == ES_UNKNOWN) return false; // Node's included so check the next step along the trail. parent = node; } if (i == j) { // handle + name matched all the way down return false; } // Compute relative path from last parent to child. RemotePath cloudPath; for ( ; i != j; ++i) cloudPath.appendWithSeparator(i->second, false); // Would the child definitely be excluded? return parent->exclusionState(cloudPath, child->type, child->size) == ES_EXCLUDED; } Sync* Syncs::syncContainingPath(const LocalPath& path) { auto predicate = [&path](const UnifiedSync& us) { return us.mConfig.mLocalPath.isContainingPathOf(path); }; return syncMatching(std::move(predicate)); } Sync* Syncs::syncContainingPath(const string& path) { auto predicate = [&path](const UnifiedSync& us) { return IsContainingCloudPathOf( us.mConfig.mOriginalPathOfRemoteRootNode, path); }; return syncMatching(std::move(predicate)); } void Syncs::ignoreFileLoadFailure(const Sync& sync, const LocalPath& path) { // We should never be asked to report multiple ignore file failures. assert(mIgnoreFileFailureContext.mBackupID == UNDEF); // Record the failure. mIgnoreFileFailureContext.mBackupID = sync.getConfig().mBackupId; mIgnoreFileFailureContext.mPath = path; } bool Syncs::hasIgnoreFile(const SyncConfig& config) { // Should only be run from the sync thread. assert(onSyncThread()); // Is there an ignore file present in the cloud? { // Ensure we have exclusive access to the remote node tree. lock_guard guard(mClient.nodeTreeMutex); // Get our hands on the sync root. auto root = mClient.mNodeManager.getNodeByHandle(config.mRemoteNode); // The root can't contain anything if it doesn't exist. if (!root) return false; // Does the root contain an ignore file? if (root->hasChildWithName(IGNORE_FILE_NAME)) return true; } // Does the ignore file already exist on disk? auto fileAccess = fsaccess->newfileaccess(false); auto filePath = config.mLocalPath; filePath.appendWithSeparator(IGNORE_FILE_NAME, false); return fileAccess->isfile(filePath); } bool Sync::PerFolderLogSummaryCounts::report(string& s) { if (alreadySyncedCount) { s += " alreadySynced:" + std::to_string(alreadySyncedCount); } if (alreadyUploadingCount) { s += " alreadyUploading:" + std::to_string(alreadyUploadingCount); } if (alreadyDownloadingCount) { s += " alreadyDownloading:" + std::to_string(alreadyDownloadingCount); } return !s.empty(); } // Syncs Upload Throttling void Syncs::processDelayedUploads() { assertThrottlingManagerIsValid(); const auto queueUploadToClient = [this](DelayedSyncUpload&& delayedSyncUpload) { if (!delayedSyncUpload.mWeakUpload.lock()) { LOG_warn << "[UploadThrottle] Upload is no longer valid"; return; } queueClient( [delayedSyncUpload = std::move(delayedSyncUpload)](MegaClient& mc, TransferDbCommitter& committer) { if (auto upload = delayedSyncUpload.mWeakUpload.lock()) // Check again if upload is valid { LOG_debug << "[UploadThrottle] Sync - Sending delayed file " << upload->getLocalname(); clientUpload(mc, committer, upload, delayedSyncUpload.mVersioningOption, delayedSyncUpload.mQueueFirst, delayedSyncUpload.mOvHandleIfShortcut); } else { LOG_warn << "[UploadThrottle] Upload no longer valid inside queueClient"; } }); }; mThrottlingManager->processDelayedUploads(queueUploadToClient); } void Syncs::addToDelayedUploads(DelayedSyncUpload&& delayedUpload) { assertThrottlingManagerIsValid(); mThrottlingManager->addToDelayedUploads(std::move(delayedUpload)); } std::chrono::seconds Syncs::uploadCounterInactivityExpirationTime() const { assertThrottlingManagerIsValid(); return mThrottlingManager->uploadCounterInactivityExpirationTime(); } std::chrono::seconds Syncs::throttleUpdateRate() const { assertThrottlingManagerIsValid(); return mThrottlingManager->throttleUpdateRate(); } unsigned Syncs::maxUploadsBeforeThrottle() const { assertThrottlingManagerIsValid(); return mThrottlingManager->maxUploadsBeforeThrottle(); } void Syncs::setThrottleUpdateRate(const std::chrono::seconds throttleUpdateRate, std::function&& completion) { assert(!onSyncThread()); queueSync( [this, throttleUpdateRate, completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { assertThrottlingManagerIsValid(); const error result = mThrottlingManager->setThrottleUpdateRate(throttleUpdateRate) ? API_OK : API_EARGS; completionForClientWrapped(result); }, "setThrottleUpdateRate"); } void Syncs::setMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle, std::function&& completion) { assert(!onSyncThread()); queueSync( [this, maxUploadsBeforeThrottle, completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { assertThrottlingManagerIsValid(); const error result = mThrottlingManager->setMaxUploadsBeforeThrottle(maxUploadsBeforeThrottle) ? API_OK : API_EARGS; completionForClientWrapped(result); }, "setMaxUploadsBeforeThrottle"); } void Syncs::uploadThrottleValues( std::function&& completion) { assert(!onSyncThread()); queueSync( [this, completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { assertThrottlingManagerIsValid(); const auto updateRateInSecondsLimit = mThrottlingManager->throttleUpdateRate(); const auto maxUploadsBeforeThrottleLimit = mThrottlingManager->maxUploadsBeforeThrottle(); completionForClientWrapped(updateRateInSecondsLimit, maxUploadsBeforeThrottleLimit); }, "uploadThrottleValues"); } void Syncs::uploadThrottleValuesLimits(std::function&& completion) { assert(!onSyncThread()); queueSync( [this, completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { assertThrottlingManagerIsValid(); completionForClientWrapped(mThrottlingManager->throttleValueLimits()); }, "uploadThrottleValuesLimits"); } void Syncs::checkSyncUploadsThrottled(std::function&& completion) { assert(!onSyncThread()); queueSync( [this, completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { assertThrottlingManagerIsValid(); completionForClientWrapped(mThrottlingManager->anyDelayedUploads()); }, "checkSyncUploadsThrottled"); } void Syncs::setThrottlingManager(std::shared_ptr uploadThrottlingManager, std::function&& completion) { assert(!onSyncThread()); queueSync( [this, uploadThrottlingManager = std::move(uploadThrottlingManager), completionForClientWrapped = wrapToRunInClientThread(std::move(completion), FromAnyThread::yes)]() mutable { if (!uploadThrottlingManager) { completionForClientWrapped(API_EARGS); } LOG_debug << "[Syncs::setThrottlingManager] Setting a new Throttling Manager. All " "previous stored values will be lost"; mThrottlingManager = std::move(uploadThrottlingManager); completionForClientWrapped(API_OK); }, "setThrottlingManager"); } void Syncs::assertThrottlingManagerIsValid() const { assert(onSyncThread()); if (mThrottlingManager) return; LOG_err << "Syncs upload throttling manager is not valid and it should! It is expected to be " "initialized from Syncs constructor"; assert(false && "Syncs upload throttling manager should always be valid!!!"); } void Syncs::setSdsBackupsFullSync(const std::unique_ptr& sds) { lock_guard sdsGuard(mSdsBackupsFullSyncMutex); mSdsBackupsFullSync.clear(); if (sds) { // Map the sds records from the user attribute to the mSdsBackupsFullSync variable format for (const auto& [backupIdStr, backupStateStr]: *sds) { const handle backupId = stringToHandle(backupIdStr, MegaClient::BACKUPHANDLE); if (ISUNDEF(backupId)) { string msg = "Undefined backup ID in '*!sds' attr"; LOG_err << msg; assert(false && msg.c_str()); continue; } const auto backupState = stringToNumber(backupStateStr); if (!backupState) { string msg = "Invalid backup state \"" + backupStateStr + "\" in '*!sds' attr value for backup ID " + backupIdStr; LOG_err << msg; assert(false && msg.c_str()); continue; } mSdsBackupsFullSync.emplace_back(backupId, *backupState); } } } Syncs::SyncsDesiredStates Syncs::getSdsBackupsFullSync() const { lock_guard sdsGuard(mSdsBackupsFullSyncMutex); return mSdsBackupsFullSync; } } // namespace #endif sdk-10.11.0/src/syncfilter.cpp000066400000000000000000000717231516266226600161360ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "mega/filesystem.h" #include "mega/logging.h" #include "mega/syncfilter.h" #include "mega/utils.h" namespace mega { class Matcher; class Target; // For convenience. using MatcherPtr = std::unique_ptr; class SizeFilter { public: SizeFilter(); // True if this filter includes the size s. bool match(const std::uint64_t s) const; // Lower bound of filter. std::uint64_t lower; // Upper bound of filter. std::uint64_t upper; string debugDescription() const; }; // SizeFilter class StringFilter { public: virtual ~StringFilter() = default; // True if this filter is applicable to type. bool applicable(const nodetype_t type) const; // True if this filter is an inclusion. bool inclusion() const; // True if this filter is inheritable. // // I.e. Is the rule in effect in directories below the one where // it was defined? bool inheritable() const; // True if this filter matches the string pair p. virtual bool match(const RemotePathPair& p) const = 0; virtual string debugDescription() const = 0; protected: StringFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable); // True if this filter matches the string s. bool match(const string& s) const; protected: MatcherPtr mMatcher; const Target& mTarget; const bool mInclusion; const bool mInheritable; }; /* StringFilter */ class NameFilter : public StringFilter { public: NameFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable); bool match(const RemotePathPair& p) const override; string debugDescription() const override; }; /* NameFilter */ class PathFilter : public StringFilter { public: PathFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable); bool match(const RemotePathPair& p) const override; string debugDescription() const override; }; /* PathFilter */ class Matcher { public: virtual ~Matcher() = default; // True if this matcher matches the string s. virtual bool match(const string& s) const = 0; virtual string debugDescription() const = 0; protected: Matcher() = default; }; /* Matcher */ class GlobMatcher : public Matcher { public: GlobMatcher(const string& pattern, bool caseSensitive); // True if the wildcard pattern matches the string s. bool match(const string& s) const override; string debugDescription() const override; private: const string mPattern; const bool mCaseSensitive; }; /* GlobMatcher */ class RegexMatcher : public Matcher { public: RegexMatcher(const string& pattern, bool caseSensitive); // True if the regex pattern matches the string s. bool match(const string& s) const override; string debugDescription() const override; private: std::regex mRegexp; string mPattern; bool mCaseSensitive; }; /* RegexMatcher */ class Target { public: virtual ~Target() = default; // True if this target is applicable to type. virtual bool applicable(const nodetype_t type) const = 0; protected: Target() = default; }; /* Target */ class AllTarget : public Target { public: // Always returns true. bool applicable(const nodetype_t) const override; // Returns an AllTarget instance. static const AllTarget& instance(); private: AllTarget() = default; }; /* AllTarget */ class DirectoryTarget : public Target { public: // True if type is FOLDERNODE. bool applicable(const nodetype_t type) const override; // Returns a DirectoryTarget instance. static const DirectoryTarget& instance(); private: DirectoryTarget() = default; }; /* DirectoryTarget */ class FileTarget : public Target { public: // True if type is FILENODE. bool applicable(const nodetype_t type) const override; // Returns a FileTarget instance. static const FileTarget& instance(); private: FileTarget() = default; }; /* FileTarget */ class SymlinkTarget : public Target { public: // True if type is SYMLINK. bool applicable(const nodetype_t type) const override; // Returns a FileTarget instance. static const SymlinkTarget& instance(); private: SymlinkTarget() = default; }; /* FileTarget */ // Parses the size filter "text" and updates (creates) "filter." static bool add(const string& text, SizeFilterPtr& filter); // Parses the string filter "text" and adds it to the "filters" vector. static bool add(const string& text, StringFilterPtrVector& filters, bool& syncThisMegaignore); // Logs an invalid threshold error and returns false. static bool invalidThresholdsError(const SizeFilter& filter); // Logs a normalization error and return false. static FilterLoadResult normalizationError(const string& text); // Returns appropriate regex flags. static std::regex::flag_type regexFlags(const bool caseSensitive); // Logs a syntax error and returns false. static bool syntaxError(const string& text); // Uppercases the string text. static string toUpper(string text); // Logs an unrepresentable limit error and returns false. static bool unrepresentableLimitError(const string& text); // Predefined name exclusions. // // Extracted from MEGASync. const string_vector DefaultFilterChain::mPredefinedNameExclusions = { "Thumbs.db", "desktop.ini", "~*", ".*", "*~.*", "*.crdownload", // Avoid trigraph interpretation: ??- is ~. "*.sb-????????""-??????", "*.tmp" }; DefaultFilterChain::DefaultFilterChain() : mExcludedNames(mPredefinedNameExclusions) , mExcludedPaths() , mLock() , mLowerLimit(0u) , mUpperLimit(0u) { } DefaultFilterChain::DefaultFilterChain(DefaultFilterChain& other) : DefaultFilterChain() { lock_guard guard(other.mLock); mExcludedNames = other.mExcludedNames; mExcludedPaths = other.mExcludedPaths; mLowerLimit = other.mLowerLimit; mUpperLimit = other.mUpperLimit; } DefaultFilterChain& DefaultFilterChain::operator=(DefaultFilterChain& rhs) { if (this == &rhs) return *this; using std::adopt_lock; using std::lock; lock(mLock, rhs.mLock); lock_guard guardSelf(mLock, adopt_lock); lock_guard guardOther(rhs.mLock, adopt_lock); mExcludedNames = rhs.mExcludedNames; mExcludedPaths = rhs.mExcludedPaths; mLowerLimit = rhs.mLowerLimit; mUpperLimit = rhs.mUpperLimit; return *this; } bool DefaultFilterChain::create(const LocalPath& targetPath, bool appendName, FileSystemAccess& fsAccess, bool setSyncIgnoreFileFlag) { // Compute the path for the target's ignore file. auto filePath = targetPath; if (appendName) { filePath.appendWithSeparator(IGNORE_FILE_NAME, false); } // Get access to the filesystem. auto fileAccess = fsAccess.newfileaccess(false); // Try and open the file for writing. if (!fileAccess->fopen(filePath, OPEN_WRONLY, FSLogging::logOnError)) return false; // Generate the ignore file's content (including BOM). auto content = generate(targetPath, fsAccess, true, setSyncIgnoreFileFlag); // Write the content to disk. return fileAccess->fwrite((const byte*)content.data(), (unsigned)content.size(), 0); } void DefaultFilterChain::excludePath(const string &path) { lock_guard guard(mLock); auto temp = path; LocalPath::utf8_normalize(&temp); if (temp.empty()) return; auto localPath = LocalPath::fromAbsolutePath(std::move(temp)); LOG_debug << "Excluded path: " << localPath; mExcludedPaths.emplace_back(std::move(localPath)); } void DefaultFilterChain::lowerLimit(std::uint64_t lower) { lock_guard guard(mLock); mLowerLimit = lower; } void DefaultFilterChain::reset() { lock_guard guard(mLock); mExcludedNames = mPredefinedNameExclusions; mExcludedPaths.clear(); mLowerLimit = 0u; mUpperLimit = 0u; } void DefaultFilterChain::upperLimit(std::uint64_t limit) { lock_guard guard(mLock); mUpperLimit = limit; } vector DefaultFilterChain::applicablePaths(LocalPath targetPath) const { vector paths; // Determine which path exclusions are applicable to the target. for (auto& path : mExcludedPaths) { size_t index; // Does the path identify something below the target? if (!targetPath.isContainingPathOf(path, &index)) continue; // Path exclusions should be relative to the target. paths.emplace_back(path.subpathFrom(index)); } return paths; } string DefaultFilterChain::generate(const LocalPath& targetPath, FileSystemAccess& fsAccess, bool includeBOM, bool setSyncIgnoreFileFlag) const { lock_guard guard(mLock); ostringstream ostream; if (includeBOM) { // utf8-BOM ostream << string("\xEF\xBB\xBF", 3); } #ifdef WIN32 #define NL "\r\n"; #else #define NL "\n"; #endif if (setSyncIgnoreFileFlag) { ostream << "+sync:.megaignore" NL; } // Size filters. if (mLowerLimit) ostream << "exclude-smaller:" << mLowerLimit << NL; if (mUpperLimit) ostream << "exclude-larger:" << mUpperLimit << NL; // Name filters. for (auto& name : mExcludedNames) ostream << "-:" << name << NL; // Path filters. if (mExcludedPaths.empty()) return ostream.str(); auto paths = applicablePaths(targetPath); for (auto& path : toRemotePaths(paths, fsAccess)) ostream << "-p:" << path.toName(fsAccess) << NL; #undef NL return ostream.str(); } string_vector DefaultFilterChain::normalize(const string_vector& strings) const { string_vector result; for (auto& string : strings) { auto temp = string; LocalPath::utf8_normalize(&temp); if (!temp.empty()) result.emplace_back(std::move(temp)); } return result; } RemotePath DefaultFilterChain::toRemotePath(const LocalPath& path, FileSystemAccess& fsAccess) const { LocalPath component; RemotePath result; size_t index = 0u; // Translate each component into remote form. while (path.nextPathComponent(index, component)) result.appendWithSeparator(component.toName(fsAccess), false); return result; } vector DefaultFilterChain::toRemotePaths(const vector& paths, FileSystemAccess& fsAccess) const { vector result; // Translate each path into remote form. for (auto& path : paths) { auto temp = toRemotePath(path, fsAccess); // Skip empty paths. if (!temp.empty()) result.emplace_back(std::move(temp)); } return result; } void FilterChain::clear() { mFingerprint = FileFingerprint(); mSizeFilter.reset(); mStringFilters.clear(); } FilterLoadResult FilterChain::load(FileSystemAccess& fsAccess, const LocalPath& path) { // Create a file access so we can access the filesystem. auto fileAccess = fsAccess.newfileaccess(false); // Open the ignore file for reading. if (!fileAccess->fopen(path, OPEN_RDONLY, FSLogging::logOnError) || fileAccess->type != FILENODE) { // Couldn't open the file. Assume it's been deleted. LOG_info << "Could not load exclusions, file open failed at " << path; return FLR_DELETED; } // Ignore file exists so try and load it. auto result = load(*fileAccess); if (result == FLR_SUCCESS) { if (!mFingerprint.genfingerprint(fileAccess.get())) { result = FLR_FAILED; LOG_debug << "Failed to fingerprint .megaignore file after loading rules"; } } if (result != FLR_SUCCESS) { LOG_info << "Could not read or rule failure at " << path << " error " << (int)result; } else { LOG_info << "Loaded new/updated exclusion rules from: " << path; } return result; } FilterLoadResult FilterChain::load(FileAccess& fileAccess) { string_vector lines; // Read the filters, line by line. // Empty lines are omitted by readLines(...). if (!readLines(fileAccess, lines)) { LOG_info << "Could not load exclusions, readLines failed"; return FLR_FAILED; } // Temporay storage for newly loaded filters. StringFilterPtrVector stringFilters; SizeFilterPtr sizeFilter; // Add all the filters. for (const auto& line : lines) { // Mutable copy for normalization. string l = line; // Normalize the line. LocalPath::utf8_normalize(&l); // Were we able to normalize the line? if (l.empty()) { // Nope so report the error. return normalizationError(line); } // Skip comments. if (l[0] == '#') { continue; } // Try and add the filter. if (l[0] == 'e') { if (!add(l, sizeFilter)) { LOG_info << "Could not load exclusions, size filter add failed"; // Changes are not committed. return FLR_FAILED; } } else if (!add(l, stringFilters, mSyncThisMegaignore)) { LOG_info << "Could not load exclusions, string filter add failed"; return FLR_FAILED; } } // Move new filters into place. mStringFilters = std::move(stringFilters); mSizeFilter = std::move(sizeFilter); LOG_info << "New exclusion rules from file are as follows"; for (auto &e : mStringFilters) { LOG_info << "string filter: " << e->debugDescription(); } if (mSizeFilter) { LOG_info << "size filter: " << mSizeFilter->debugDescription(); } // Changes are committed. return FLR_SUCCESS; } ExclusionState FilterChain::match(const RemotePathPair& p, const nodetype_t type, const bool onlyInheritable) const { if (!mLoadSucceeded) return ES_UNKNOWN; auto i = mStringFilters.rbegin(); auto j = mStringFilters.rend(); for ( ; i != j; ++i) { if (onlyInheritable && !(*i)->inheritable()) { continue; } if ((*i)->applicable(type) && (*i)->match(p)) { return (*i)->inclusion() ? ES_INCLUDED : ES_EXCLUDED; } } return ES_UNMATCHED; } ExclusionState FilterChain::match(const m_off_t s) const { // Sanity. assert(s >= 0); if (!mLoadSucceeded) return ES_UNKNOWN; // Can't match if we have no filter. if (!mSizeFilter) { return ES_UNMATCHED; } // Silence warnings. const auto t = static_cast(s); // Attempt the match. return mSizeFilter->match(t) ? ES_INCLUDED : ES_EXCLUDED; } #ifdef _WIN32 const LocalPath IgnoreFileName::mLocalName = LocalPath::fromPlatformEncodedRelative(L".megaignore"); #else // _WIN32 const LocalPath IgnoreFileName::mLocalName = LocalPath::fromPlatformEncodedRelative(".megaignore"); #endif // ! _WIN32 const RemotePath IgnoreFileName::mRemoteName(".megaignore"); IgnoreFileName::operator const LocalPath&() const { return mLocalName; } IgnoreFileName::operator const RemotePath&() const { return mRemoteName; } IgnoreFileName::operator const string&() const { return mRemoteName; } bool IgnoreFileName::operator==(const LocalPath& rhs) const { return mLocalName == rhs; } bool IgnoreFileName::operator==(const RemotePath& rhs) const { return mRemoteName == rhs; } bool IgnoreFileName::operator==(const string& rhs) const { return mRemoteName == rhs; } SizeFilter::SizeFilter() : lower(0) , upper(std::numeric_limits::max()) { } // check if size is valid (included) bool SizeFilter::match(const std::uint64_t s) const { assert(lower != upper); if (lower > upper) { // in-range exclusion: [upper, lower] // valid sizes must be outside lower and upper return s < upper || s > lower; } // smaller than and/or greater than exclusion: [0, lower), (upper, max] // valid sizes must be between lower and upper return s >= lower && s <= upper; } string SizeFilter::debugDescription() const { return std::to_string(lower) + " " + std::to_string(upper); } bool StringFilter::applicable(const nodetype_t type) const { return mTarget.applicable(type); } bool StringFilter::inclusion() const { return mInclusion; } bool StringFilter::inheritable() const { return mInheritable; } StringFilter::StringFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable) : mMatcher(std::move(matcher)) , mTarget(target) , mInclusion(inclusion) , mInheritable(inheritable) { } bool StringFilter::match(const string& s) const { return mMatcher->match(s); } NameFilter::NameFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable) : StringFilter(std::move(matcher), target, inclusion, inheritable) { } bool NameFilter::match(const RemotePathPair& p) const { return StringFilter::match(p.first); } string NameFilter::debugDescription() const { string s = "name: " + mMatcher->debugDescription(); if (mInclusion) s += " (inclusion)"; if (!mInheritable) s += " (this folder only)"; return s; } PathFilter::PathFilter(MatcherPtr matcher, const Target& target, const bool inclusion, const bool inheritable) : StringFilter(std::move(matcher), target, inclusion, inheritable) { } bool PathFilter::match(const RemotePathPair& p) const { return StringFilter::match(p.second); } string PathFilter::debugDescription() const { string s = "path: " + mMatcher->debugDescription(); if (mInclusion) s += " (inclusion)"; if (!mInheritable) s += " (this folder only)"; return s; } GlobMatcher::GlobMatcher(const string &pattern, const bool caseSensitive) : mPattern(caseSensitive ? pattern : toUpper(pattern)) , mCaseSensitive(caseSensitive) { } bool GlobMatcher::match(const string& s) const { if (mCaseSensitive) { return wildcardMatch(s, mPattern); } return wildcardMatch(toUpper(s), mPattern); } string GlobMatcher::debugDescription() const { string s = mPattern; if (mCaseSensitive) { s += " (case sensitive)"; } return s; } RegexMatcher::RegexMatcher(const string& pattern, const bool caseSensitive) : mRegexp(pattern, regexFlags(caseSensitive)) , mPattern(pattern) , mCaseSensitive(caseSensitive) { } bool RegexMatcher::match(const string& s) const { return std::regex_match(s, mRegexp); } string RegexMatcher::debugDescription() const { string s = "regex: " + mPattern; if (mCaseSensitive) { s += " (case sensitive)"; } return s; } bool AllTarget::applicable(const nodetype_t) const { return true; } const AllTarget& AllTarget::instance() { static AllTarget instance; return instance; } bool DirectoryTarget::applicable(const nodetype_t type) const { return type == FOLDERNODE; } const DirectoryTarget& DirectoryTarget::instance() { static DirectoryTarget instance; return instance; } bool FileTarget::applicable(const nodetype_t type) const { return type == FILENODE; } const FileTarget& FileTarget::instance() { static FileTarget instance; return instance; } bool SymlinkTarget::applicable(const nodetype_t type) const { return type == TYPE_SYMLINK; } const SymlinkTarget& SymlinkTarget::instance() { static SymlinkTarget instance; return instance; } bool add(const string& text, SizeFilterPtr& filter) { // Handy constants. static const std::string excludeLarger = "exclude-larger"; static const std::string excludeSmaller = "exclude-smaller"; std::istringstream istream(text); std::string directive; // Extract the directive. std::getline(istream, directive, ':'); // Could we extract the directive? if (!istream.good()) { return syntaxError(text); } // Is the user specifying the upper bound? const auto larger = directive == excludeLarger; // Are they specifying the lower bound? if (!larger && directive != excludeSmaller) { // Neither lower nor upper bound. return syntaxError(text); } // Skip leading whitespace. while (is_space(static_cast(istream.peek()))) { istream.get(); } // Is the limit a bare number? if (!is_digit(static_cast(istream.peek()))) { // Limit isn't a number or has sign markers. return syntaxError(text); } std::uint64_t limit; // Extract the limit. istream >> limit; // Were we able to extract the limit? if (!istream) { return syntaxError(text); } // Has the user specified a unit suffix? if (!istream.eof()) { std::uint64_t shift = 0; switch (std::tolower(istream.get())) { case 'k': // Kilobytes. shift = 10; break; case 'm': // Megabytes. shift = 20; break; case 'g': // Gigabytes. shift = 30; break; case 't': // Terabytes. shift = 40; break; default: // Invalid! break; } // Did the user specify a valid suffix? if (!shift) return syntaxError(text); // Eat trailing whitespace. while (true) { auto character = istream.get(); if (!is_space(static_cast(character))) break; } // The stream should be exhausted. if (!istream.eof()) return syntaxError(text); // Can we actually represent the limit? const auto temp = limit << shift; if (limit != temp >> shift) { // Can't represent the limit. return unrepresentableLimitError(text); } // Shift the limit for real this time. limit <<= shift; } // Create the filter if necessary. if (!filter) { filter.reset(new SizeFilter()); } // Update the appropriate bound. if (larger) { filter->upper = limit; } else { filter->lower = limit; } // Make sure the thresholds the user has set are sane. if (filter->lower == filter->upper) return invalidThresholdsError(*filter); if (filter->lower > filter->upper) { LOG_debug << "[SizeFilter] Exclusion filter added: excluding files between " << filter->upper << " bytes and " << filter->lower << " bytes"; } return true; } bool add(const string& text, StringFilterPtrVector& filters, bool& syncThisMegaignore) { enum FilterType { FT_NAME, FT_PATH, FT_DEFAULT_BY_CONTENT }; /* FilterType */ enum MatchStrategy { MS_GLOB, MS_REGEXP }; /* MatchStrategy */ const char* m = text.c_str(); const Target* target; FilterType type; MatchStrategy strategy; bool caseSensitive = false; bool inclusion; bool inheritable = true; // What class of filter is this? switch (*m++) { case '+': // Inclusion filter. inclusion = true; break; case '-': // Exclusion filter. inclusion = false; break; default: // Invalid filter class. return syntaxError(text); } if (0 == strcmp(m, "sync:.megaignore")) { LOG_debug << "megaignore to be synced: " << inclusion; syncThisMegaignore = inclusion; return true; } // What kind of node does this filter apply to? switch (*m) { case 'a': // Applies to all node types. ++m; target = &AllTarget::instance(); break; case 'd': // Applies only to directories. ++m; target = &DirectoryTarget::instance(); break; case 'f': // Applies only to files. ++m; target = &FileTarget::instance(); break; case 's': // Applies only to symlinks. ++m; target = &SymlinkTarget::instance(); break; default: // Default applies to all node types. target = &AllTarget::instance(); break; } // What type of filter is this? switch (*m) { case 'N': // Local name filter. ++m; inheritable = false; type = FT_NAME; break; case 'n': // Subtree name filter. ++m; type = FT_NAME; break; case 'p': // Path filter. ++m; type = FT_PATH; break; default: // Default to subtree name filter, unless the user used / separators, in which case they will be expecting to exlcude sub-paths type = FT_DEFAULT_BY_CONTENT; break; } // What matching strategy does this filter use? switch (*m) { case 'G': // Case-sensitive glob match. caseSensitive = true; ++m; strategy = MS_GLOB; break; case 'g': // Case-insensitive glob match. ++m; strategy = MS_GLOB; break; case 'R': // Case-sensitive regexp match. caseSensitive = true; ++m; strategy = MS_REGEXP; break; case 'r': // Case-insensitive regexp match. ++m; strategy = MS_REGEXP; break; default: // Default to case-insensitive glob match. strategy = MS_GLOB; break; } // Make sure we're at the start of the pattern. if (*m++ != ':') { return syntaxError(text); } if (type == FT_DEFAULT_BY_CONTENT) { // if the user specifies something like -:Documents/MEGAsync, then we should treat that as a sub-path type = strchr(m, '/') ? FT_PATH : FT_NAME; } // Ignore trailing whitespace. const char* n = text.c_str() + text.size(); while (n > m && is_space(static_cast(*(n - 1)))) --n; // Is the pattern effectively empty? if (m > n) return syntaxError(text); // Create the filter's matcher. MatcherPtr matcher; // Extract the pattern. string pattern(m, static_cast(n - m)); try { switch (strategy) { case MS_GLOB: matcher.reset(new GlobMatcher(pattern, caseSensitive)); break; case MS_REGEXP: // This'll throw if the regex is malformed. matcher.reset(new RegexMatcher(pattern, caseSensitive)); break; } } catch (std::regex_error&) { return syntaxError(text); } // Create the filter. StringFilterPtr filter; switch (type) { case FT_NAME: filter.reset(new NameFilter(std::move(matcher), *target, inclusion, inheritable)); break; case FT_PATH: filter.reset(new PathFilter(std::move(matcher), *target, inclusion, inheritable)); break; case FT_DEFAULT_BY_CONTENT: assert(false); } // Add the filter to the chain. filters.emplace_back(std::move(filter)); return true; } bool invalidThresholdsError(const SizeFilter& filter) { LOG_verbose << "Invalid size thresholds: " << "Lower bound (" << filter.lower << ") is equal to upper bound (" << filter.upper << ")"; return false; } FilterLoadResult normalizationError(const string& text) { LOG_verbose << "Normalization error parsing: " << text; return FLR_FAILED; } std::regex::flag_type regexFlags(const bool caseSensitive) { using std::regex_constants::extended; using std::regex_constants::icase; using std::regex_constants::optimize; const std::regex::flag_type flags = extended | optimize; if (caseSensitive) { return flags; } return flags | icase; } bool syntaxError(const string& text) { LOG_verbose << ".megaignore Syntax error parsing: " << text; return false; } string toUpper(string text) { for (char& character : text) { character = (char)std::toupper((unsigned char)character); } return text; } bool unrepresentableLimitError(const string& text) { LOG_verbose << "Unrepresentable size limit: " << text; return false; } } /* mega */ sdk-10.11.0/src/syncinternals/000077500000000000000000000000001516266226600161325ustar00rootroot00000000000000sdk-10.11.0/src/syncinternals/syncinternals.cpp000066400000000000000000001517531516266226600215460ustar00rootroot00000000000000/** * @file syncinternals.cpp * @brief Class for internal operations of the sync engine. */ #include "mega/base64.h" #ifdef ENABLE_SYNC #include "mega/crypto/cryptopp.h" #include "mega/megaclient.h" #include "mega/sync.h" #include "mega/syncinternals/syncinternals.h" #include "mega/syncinternals/syncinternals_logging.h" #include "mega/utils.h" #include #include #include #include namespace mega { /***************************\ * FIND LOCAL NODE BY FSID * \***************************/ NodeMatchByFSIDResult areNodesMatchedByFsidEquivalent(const NodeMatchByFSIDAttributes& source, const NodeMatchByFSIDAttributes& target, const SourceNodeMatchByFSIDContext& sourceContext) { if (source.nodetype != target.nodetype) return NodeMatchByFSIDResult::DifferentTypes; if (sourceContext.isFsidReused) return NodeMatchByFSIDResult::SourceFsidReused; if (source.fsfp != target.fsfp) return NodeMatchByFSIDResult::DifferentFilesystems; if (target.owningUser != UNDEF && source.owningUser != target.owningUser) return NodeMatchByFSIDResult::DifferentOwners; switch (sourceContext.exclusionState) { case ES_INCLUDED: break; case ES_UNKNOWN: return NodeMatchByFSIDResult::SourceExclusionUnknown; case ES_EXCLUDED: return NodeMatchByFSIDResult::SourceIsExcluded; default: assert(false && "Invalid exclusion state for source node"); return NodeMatchByFSIDResult::SourceIsExcluded; // Default to exclusion on invalid state } if (auto sameFp = source.nodetype != FILENODE || target.fingerprint == source.fingerprint || target.fingerprint == source.realFingerprint; sameFp) { return NodeMatchByFSIDResult::Matched; } if (auto sameFpExceptMtime = target.fingerprint.equalExceptMtime(source.fingerprint) || target.fingerprint.equalExceptMtime(source.realFingerprint); sameFpExceptMtime) { return NodeMatchByFSIDResult::DifferentFingerprintOnlyMtime; } return NodeMatchByFSIDResult::DifferentFingerprint; } bool FindLocalNodeByFSIDPredicate::operator()(LocalNode& localNode) { if (mEarlyExit) return false; const NodeMatchByFSIDAttributes sourceNodeAttributes{ localNode.type, localNode.sync->fsfp(), localNode.sync->cloudRootOwningUser, getFingerprint(localNode), (mScannedOrSyncedCtxt == ScannedOrSyncedContext::SCANNED) ? localNode.realScannedFingerprint : localNode.syncedFingerprint}; const SourceNodeMatchByFSIDContext sourceContext{isFsidReused(localNode), localNode.exclusionState()}; const auto compRes = areNodesMatchedByFsidEquivalent(sourceNodeAttributes, mTargetNodeAttributes, sourceContext); switch (compRes) { case NodeMatchByFSIDResult::Matched: { if (!mExtraCheck || mExtraCheck(localNode)) { logMsg("found", localNode.getLocalPath()); return true; } return false; } case NodeMatchByFSIDResult::SourceExclusionUnknown: { mFoundExclusionUnknown = true; logMsg("unknown exclusion with that fsid", localNode.getLocalPath()); return false; } case NodeMatchByFSIDResult::DifferentFingerprint: case NodeMatchByFSIDResult::DifferentFingerprintOnlyMtime: { if (compRes == NodeMatchByFSIDResult::DifferentFingerprintOnlyMtime) { LOG_warn << "areNodesMatchedByFsidEquivalent: fingerprint differs only in mtime: " << localNode.getLocalPath().toPath(true); } if (mOnFingerprintMismatchDuringPutnodes) { if (const auto upload = std::dynamic_pointer_cast(localNode.transferSP); upload && upload->fingerprint() == mTargetNodeAttributes.fingerprint && upload->upsyncStarted) { // [SDK-5551_TODO]: see this subcase logMsg("source with same fsid excluded due to fingerprint mismatch has " "a putnodes operation ongoing, fsid", localNode.getLocalPath()); // Excluded source node has a putnodes operation in flight mOnFingerprintMismatchDuringPutnodes(&localNode); mEarlyExit = true; } } return false; } default: return false; } } void FindLocalNodeByFSIDPredicate::logMsg(const std::string& msg, const LocalPath& checkingLocalPath) const { const std::string prefix = mScannedOrSyncedCtxt == ScannedOrSyncedContext::SYNCED ? "findLocalNodeBySyncedFsid" : "findLocalNodeByScannedFsid"; LOG_verbose << prefix << " - " << msg << " " << toHandle(mFsid) << " at: " << checkingLocalPath << " checked from " << mOriginalPathForLogging; } // Finds a LocalNode by its File System ID (FSID) in a specified map. // The functionality is documented for the public method findLocalNodeByFsid() fsid_localnode_map::const_iterator findLocalNodeByFsid_if(const fsid_localnode_map& fsidLocalnodeMap, FindLocalNodeByFSIDPredicate& predicate) { if (predicate.fsid() == UNDEF) { LOG_debug << " - FSID is undef, skipping"; return std::end(fsidLocalnodeMap); } // Iterate over all nodes with the given FSID const auto [begin, end] = fsidLocalnodeMap.equal_range(predicate.fsid()); const auto it = std::find_if(begin, end, [&predicate](const auto& pair) -> bool { const auto& localNode = pair.second; if (!localNode || !localNode->sync) { assert(false && "Invalid LocalNode or its sync"); return false; } return predicate(*localNode); }); predicate.resetEarlyExit(); return it == end ? std::end(fsidLocalnodeMap) : it; } std::pair findLocalNodeByFsid(const fsid_localnode_map& fsidLocalnodeMap, FindLocalNodeByFSIDPredicate&& predicate) { const auto it = findLocalNodeByFsid_if(fsidLocalnodeMap, predicate); const auto& matchedNodePtr = it != std::end(fsidLocalnodeMap) ? it->second : nullptr; return {predicate.foundExclusionUnknown(), matchedNodePtr}; } /********************************\ * FIND NODE CANDIDATE TO CLONE * \********************************/ /** * @struct FindCloneNodeCandidatePredicate * @brief Predicate for identifying a suitable node candidate to be cloned. * * This struct encapsulates the logic required to determine if a Node in the cloud * matches the Fingerprint and Meta Mac of a local file being uploaded. It is used in * conjunction with std::find_if to search for clone candidates in a collection of * nodes. * * @see findCloneNodeCandidate_if */ struct FindCloneNodeCandidatePredicate { /** * @brief Reference to the MegaClient managing the synchronization process. */ MegaClient& mClient; /** * @brief Const reference to the upload task being processed. */ const SyncUpload_inClient& mUpload; /** * @brief Flag indicating if a candidate node with a zero key was found. * * If true, the predicate encountered a candidate node that matches the local file * but has an invalid key, which can be used to prevent it from being cloned. */ bool mFoundCandidateHasZeroKey{false}; /** * @brief Constructs a FindCloneNodeCandidatePredicate instance. * * @param client Reference to the MegaClient managing synchronization. * @param upload Const ref to the shared pointer to the upload task being processed. */ FindCloneNodeCandidatePredicate(MegaClient& client, const SyncUpload_inClient& upload): mClient(client), mUpload(upload) {} /** * @brief Evaluates if the provided Node is a suitable clone candidate. * * Checks if the node matches the local file in terms of Fingerprint, and meta MAC if * fingerprints only differs in mtime. If a match is found but the node has a zero key, it * returns true but it logs a warning and updates the mFoundCandidateHasZeroKey flag * accordingly. * * @param node The cloud node to evaluate. * @return True if a match is found, otherwise false. */ bool operator()(const Node& node) { static const std::string logPre{"FindCloneCandidate: "}; const auto nodePath = node.displayname(); if (!mUpload.mMetaMac.has_value() || mUpload.mMetaMac.value() == INVALID_META_MAC) { LOG_err << logPre << "mMetaMac " << (mUpload.mMetaMac.has_value() ? "has invalid meta MAC" : "is not set") << " for " << nodePath << " !! Skip cloning node"; assert(false && (("mMetaMac is not set for " + std::string(nodePath)).c_str())); return false; } const auto compRes = CompareNodeWithProvidedMacAndFpExcludingMtime(&node, mUpload, *mUpload.mMetaMac); const auto compResStr = nodeComparisonResultToStr(compRes); if (compRes != NODE_COMP_EQUAL && compRes != NODE_COMP_DIFFERS_MTIME) { LOG_err << logPre << compResStr << " -> return false [path = " << nodePath << "]"; return false; } if (node.hasZeroKey()) { LOG_warn << "Clone node key is a zero key!! Avoid cloning node [path = '" << nodePath << "', sourceLocalname = '" << mUpload.sourceLocalname << "']"; mClient.sendevent(99486, "Node has a zerokey"); mFoundCandidateHasZeroKey = true; } LOG_debug << logPre << compResStr << " -> return true [path = " << nodePath << "]"; return true; } /** * @brief Overloaded operator() to check the validity of a shared_ptr to a Node. */ bool operator()(const std::shared_ptr& node) { return node && operator()(*node); } }; std::shared_ptr findCloneNodeCandidate(MegaClient& mc, const SyncUpload_inClient& upload, const bool excludeMtime) { if (!upload.mMetaMac.has_value() || upload.mMetaMac.value() == INVALID_META_MAC) { LOG_warn << "findCloneNodeCandidate: mMetaMac " << (upload.mMetaMac.has_value() ? "has invalid meta MAC" : "is not set") << " for " << upload.getLocalname() << " !! Skip cloning node"; assert(false && ("mMetaMac is not set for " + upload.getLocalname().toPath(false)).c_str()); return nullptr; } FindCloneNodeCandidatePredicate predicate{ mc, upload, }; std::vector> matches; const auto candidates{mc.mNodeManager.getNodesByFingerprint(upload, excludeMtime)}; std::copy_if(candidates.begin(), candidates.end(), std::back_inserter(matches), [&](const auto& nodePtr) { return predicate(nodePtr) && !predicate.mFoundCandidateHasZeroKey; }); if (matches.empty()) return nullptr; // All nodes in `matches` vector have the same fingerprint and META_MAC than upload node std::shared_ptr candidateNode = matches.at(0); for (auto& n: matches) { // Break if we find a candidate in `matches` vector with same name and target node if (n->displayname() == upload.name && n->parentHandle() == upload.h) { candidateNode = n; break; } } return candidateNode; } /****************\ * SYNC UPLOADS * \****************/ CloneMacStatus initCloneCandidateMacComputation(MegaClient& mc, SyncUpload_inClient& upload); void clientUpload(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut) { assert(!upload->wasStarted); const auto startCloneOrFullUpload = [&mc, vo, queueFirst, ovHandleIfShortcut](std::shared_ptr u) { mc.syncs.queueClient( [uploadWptr = u->weak_from_this(), vo, queueFirst, ovHandleIfShortcut]( MegaClient& mc, TransferDbCommitter& committer) { if (auto u = uploadWptr.lock()) { const auto macState = initCloneCandidateMacComputation(mc, *u); processCloneMacResult(mc, committer, u, vo, queueFirst, ovHandleIfShortcut, macState); } }); }; auto mtimeCompletion = [&mc, ovHandleIfShortcut, startCloneOrFullUpload, uploadWptr = upload->weak_from_this()](NodeHandle h, Error e) { if (auto u = uploadWptr.lock()) { // Set `upSyncFailed` to `False` regardless of settAttr result. // - If setAttr succeeded => upSyncFailed must be `False` // - If setAttr failed we want to execute fallback mechanism (Full upload or Clone // Node), so we need to reset flag anyway u->upsyncFailed = false; if (auto setMtimeSucceeded = e == API_OK; setMtimeSucceeded) { // Let the engine know the setAttr has been completed successfully. u->upsyncResultHandle = h; u->wasUpsyncCompleted.store(true); return; } LOG_err << "clientUpload (Update mTime): Error(" << e << "), Node(" << toNodeHandle(h) << "). Falling back to Cloning node"; u->upsyncStarted = false; u->attributeOnlyUpdate = SyncTransfer_inClient::AttributeOnlyUpdate::None; if (!u->mMetaMac.has_value()) { LOG_err << "clientUpload (Update mTime): mMetaMac is not set for " << u->getLocalname() << " !!!! Skip cloning node"; assert(false && ("mMetaMac is not set for " + u->getLocalname().toPath(false)).c_str()); } else if (auto cloneNodeCandidate = findCloneNodeCandidate(mc, *u, true /*excludeMtime*/); cloneNodeCandidate) { u->cloneNode(mc, cloneNodeCandidate, ovHandleIfShortcut); return; } LOG_err << "clientUpload (Update mTime): No clone candidate found for Node(" << toNodeHandle(h) << "). Falling back to full upload transfer / cloning"; startCloneOrFullUpload(std::move(u)); } }; auto crcCompletion = [startCloneOrFullUpload, uploadWptr = upload->weak_from_this()](NodeHandle h, Error e) { if (auto u = uploadWptr.lock()) { u->upsyncFailed = false; if (e == API_OK) { u->upsyncResultHandle = h; u->wasUpsyncCompleted.store(true); return; } LOG_err << "clientUpload (Update CRC): Error(" << e << "), Node(" << toNodeHandle(h) << "). Falling back to full upload transfer / cloning"; u->upsyncStarted = false; u->attributeOnlyUpdate = SyncTransfer_inClient::AttributeOnlyUpdate::None; startCloneOrFullUpload(std::move(u)); } }; upload->wasStarted = true; const auto attributeOnlyUpdate = upload->attributeOnlyUpdate.load(); if (attributeOnlyUpdate == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly) { auto parent = mc.nodeByHandle(upload->h); auto node = parent ? mc.childnodebyname(parent.get(), upload->name.c_str()) : nullptr; if (node) { if (auto immediateResult = upload->updateNodeMtime(&mc, node, upload->mtime, std::move(mtimeCompletion)); immediateResult == API_OK) { // Set `true` even though no actual data transfer occurred, we're updating node's // mtime instead upload->wasFileTransferCompleted = true; upload->upsyncStarted = true; return; } LOG_warn << "clientUpload: UpdateMtime immediate error. Falling back to full upload " "transfer / Cloning node"; } else { LOG_warn << "clientUpload: " << (!parent ? "Parent Node not found" : "Cloud Node not found") << ". Falling back to full upload transfer / Cloning node"; assert(false); } } else if (attributeOnlyUpdate == SyncTransfer_inClient::AttributeOnlyUpdate::CrcOnly) { auto parent = mc.nodeByHandle(upload->h); auto node = parent ? mc.childnodebyname(parent.get(), upload->name.c_str()) : nullptr; if (node) { const auto& cloudFp = node->fingerprint(); const auto& localFp = upload->fingerprint(); if (cloudFp.isvalid && localFp.isvalid && cloudFp.size == localFp.size && cloudFp.mtime == localFp.mtime && !areCrcEqual(cloudFp.crc, localFp.crc)) { if (const auto crcMismatchWasDueTo32bitOverflow = compareLegacyBuggySparseCrc(mc, upload->getLocalname(), localFp.size, cloudFp.crc); crcMismatchWasDueTo32bitOverflow) { LOG_debug << "clientUpload: CRC mismatch explained by legacy 32-bit overflow. " "Updating fingerprint"; if (auto immediateResult = mc.updateNodeFingerprint(node, localFp, std::move(crcCompletion)); immediateResult == API_OK) { upload->wasFileTransferCompleted = true; upload->upsyncStarted = true; return; } LOG_warn << "clientUpload: UpdateCRC immediate error. Falling back to full " "upload transfer / cloning node"; } else { LOG_warn << "clientUpload: CRC mismatch not explained by legacy 32-bit " "overflow (FA/IA). Falling back to full upload"; } } } else { LOG_warn << "clientUpload: " << (!parent ? "Parent Node not found" : "Cloud Node not found") << ". Falling back to full upload transfer / Cloning node"; } } // If we didn't early-return, proceed with the normal clone/full-upload path. upload->attributeOnlyUpdate = SyncTransfer_inClient::AttributeOnlyUpdate::None; assert(!upload->macComputation); const auto macState = initCloneCandidateMacComputation(mc, *upload); processCloneMacResult(mc, committer, upload, vo, queueFirst, ovHandleIfShortcut, macState); } bool compareLegacyBuggySparseCrc(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, const FingerprintCrc& compareToCrc) { static const std::string logPre{"compareLegacyBuggySparseCrc result: "}; FingerprintCrc legacyCrc{}; if (const bool matchLegacyFA = computeLegacyBuggySparseCrcFA(mc, path, expectedSize, legacyCrc) && areCrcEqual(legacyCrc, compareToCrc); matchLegacyFA) { LOG_verbose << logPre << "matched by FA"; return true; } legacyCrc.fill(0); if (const bool matchLegacyIA = computeLegacyBuggySparseCrcIA(mc, path, expectedSize, legacyCrc) && areCrcEqual(legacyCrc, compareToCrc); matchLegacyIA) { LOG_verbose << logPre << "matched by IA"; return true; } LOG_verbose << logPre << "unmatched"; return false; } /******************\ * SYNC DOWNLOADS * \******************/ void clientDownload(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr download, const bool queueFirst) { if (download->attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly) { if (auto cloudNode = mc.nodeByHandle(download->h); cloudNode) { #ifdef __ANDROID__ constexpr bool allowMtimeFailure = true; #else constexpr bool allowMtimeFailure = false; #endif if (const auto setMtimeSuccess = mc.fsaccess->setmtimelocal(download->getLocalname(), cloudNode->mtime); setMtimeSuccess || allowMtimeFailure) { download->mtimeAppliedOnDisk = setMtimeSuccess; download->completed(nullptr, PUTNODES_SYNC); download->wasDistributed = true; LOG_debug << "clientDownload: setmtimelocal change only"; return; } // [SDK-5551_TODO]: in case of transient error, this could be improved to retry // setmtimelocal again before performing download transfer? bool transient_err = mc.fsaccess->transient_error; LOG_warn << "clientDownload: setmtimelocal failed with (" << (transient_err ? "Transient error" : "Non-transient error") << ")" << ". Falling back to full download transfer."; } else { LOG_warn << "clientDownload: Cloud node not found. Falling back to full download transfer."; assert(false); } } // Proceed with the download transfer. mc.startxfer(GET, download.get(), committer, false, queueFirst, false, NoVersioning, nullptr, mc.nextreqtag()); LOG_debug << "clientDownload: regular download started"; } /*************************************\ * SYNC COMPARISONS - IMPLEMENTATION * \*************************************/ /** * @brief Quick fingerprint comparison without MAC computation. * * Compares type, size, CRC, and mtime. Returns a conclusive result if possible, * or std::nullopt if only mtime differs (indicating MAC computation is needed). */ std::optional quickFingerprintComparison(const CloudNode& cn, const FSNode& fs) { static const std::string logPre{"quickFingerprintComparison: "}; const auto mismatchKind = [&]() -> FingerprintMismatch { if (cn.type != fs.type) { return FingerprintMismatch::Other; } if (cn.type != FILENODE) { return FingerprintMismatch::None; } const auto& a = fs.fingerprint; const auto& b = cn.fingerprint; if (a.size != b.size || !a.isvalid || !b.isvalid) { return FingerprintMismatch::Other; } const bool crcEqual = areCrcEqual(a.crc, b.crc); const bool sameMtime = a.mtime == b.mtime; if (crcEqual) { return sameMtime ? FingerprintMismatch::None : FingerprintMismatch::MtimeOnly; } return sameMtime ? FingerprintMismatch::CrcOnly : FingerprintMismatch::Other; }; // Different types -> cannot be equal if (cn.type != fs.type) { return FsCloudComparisonResult{NODE_COMP_DIFFERS_FP, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } // Folders don't need MAC comparison if (cn.type != FILENODE) { return FsCloudComparisonResult{NODE_COMP_EQUAL, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::None}; } // Check fingerprint (CRC, size, isValid) excluding mtime if (!fs.fingerprint.equalExceptMtime(cn.fingerprint)) { if (!fs.fingerprint.isvalid || !cn.fingerprint.isvalid) { LOG_warn << logPre << "fs isValid (" << fs.fingerprint.isvalid << "), cn isValid (" << cn.fingerprint.isvalid << ")"; assert(fs.fingerprint.isvalid && cn.fingerprint.isvalid); } return FsCloudComparisonResult{NODE_COMP_DIFFERS_FP, INVALID_META_MAC, INVALID_META_MAC, mismatchKind()}; } // Full fingerprint match including mtime -> equal // IMPORTANT: We accept fingerprint collision risk here to avoid expensive MAC computation if (fs.fingerprint.mtime == cn.fingerprint.mtime) { return FsCloudComparisonResult{NODE_COMP_EQUAL, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::None}; } // mtime differs but everything else matches -> MAC computation needed return std::nullopt; } /** * @brief Process one chunk of MAC computation on worker thread. * * Shared by CSF async MAC and clone candidate MAC computation. * Called from mAsyncQueue worker thread. */ void processChunkOnWorkerThread(std::weak_ptr weakMac, m_off_t chunkStart, m_off_t chunkEnd, std::shared_ptr chunkData, const std::string& logPrefix) { auto macComp = weakMac.lock(); if (!macComp) { LOG_debug << logPrefix << "Abandoned (owner gone)"; return; } // Create cipher and compute chunk MACs SymmCipher cipher; cipher.setkey(macComp->transferkey.data()); chunkmac_map chunkMacs; // Process using the MEGA chunk boundaries (128KB-1MB chunks) m_off_t pos = chunkStart; byte* bufPtr = chunkData.get(); while (pos < chunkEnd) { m_off_t chunkBoundary = ChunkedHash::chunkceil(pos, macComp->totalSize); m_off_t thisChunkEnd = std::min(chunkBoundary, chunkEnd); unsigned chunkSize = static_cast(thisChunkEnd - pos); // Compute MAC for this chunk m_off_t chunkId = ChunkedHash::chunkfloor(pos); bool finishesChunk = (thisChunkEnd == chunkBoundary) || (thisChunkEnd == macComp->totalSize); chunkMacs .ctr_encrypt(chunkId, &cipher, bufPtr, chunkSize, pos, macComp->ctriv, finishesChunk); bufPtr += chunkSize; pos = thisChunkEnd; } // Check if this was the last chunk if (chunkEnd >= macComp->totalSize) { // Compute final local MAC { std::lock_guard g(macComp->macsMutex); chunkMacs.copyEntriesTo(macComp->partialMacs); } int64_t localMac = macComp->partialMacs.macsmac(&cipher); LOG_debug << logPrefix << "Local MAC computed: " << localMac; macComp->setComplete(localMac); } else { // More chunks to process macComp->addChunkMacs(std::move(chunkMacs), chunkEnd); macComp->chunkInProgress.store(false, std::memory_order_release); LOG_verbose << logPrefix << "Chunk done, progress: " << chunkEnd << "/" << macComp->totalSize; } } /** * @brief Result of advanceMacComputation. */ enum class MacAdvanceResult { Pending, // Chunk queued or in progress Ready, // Local MAC is computed (check state->localMac) Obsolete, // Local file changed; discard state and rescan Failed // Error occurred }; /** * @brief Advance local file MAC computation by one chunk. * * This is the shared core for both CSF and clone candidate MAC computation. * It handles reading the next chunk and queueing it to the worker thread. * * @param mc MegaClient for file access and async queue. * @param state The MAC computation state (must have cipher params initialized). * Passed by value to ensure the state stays alive during the function, * even if another thread resets the original shared_ptr. * @param logPrefix Prefix for log messages. * @return MacAdvanceResult indicating current state. */ MacAdvanceResult advanceMacComputation(MegaClient& mc, std::shared_ptr state, const std::string& logPrefix) { if (!state) { return MacAdvanceResult::Failed; } // Check current state if (state->hasFailed()) { return MacAdvanceResult::Failed; } if (state->isReady()) { return MacAdvanceResult::Ready; } if (state->isChunkInProgress()) { return MacAdvanceResult::Pending; } // Empty file: compute local MAC directly without reading or queueing chunks if (state->totalSize == 0) { SymmCipher cipher; cipher.setkey(state->transferkey.data()); const int64_t localMac = state->partialMacs.macsmac(&cipher); LOG_debug << logPrefix << "Local MAC computed for empty file: " << localMac; state->setComplete(localMac); return MacAdvanceResult::Ready; } // Ready for next chunk - read and queue m_off_t readStart = state->currentPosition; m_off_t tentativeEnd = std::min(readStart + MacComputationState::BUFFER_SIZE, state->totalSize); // Round down to nearest MEGA chunk boundary (unless it's the file end) m_off_t readEnd; if (tentativeEnd >= state->totalSize) { readEnd = state->totalSize; } else { readEnd = ChunkedHash::chunkfloor(tentativeEnd); if (readEnd <= readStart) { readEnd = ChunkedHash::chunkceil(readStart, state->totalSize); } } m_off_t readSize = readEnd - readStart; if (readSize <= 0) { LOG_err << logPrefix << "Invalid read size: " << readSize; state->setFailed(); return MacAdvanceResult::Failed; } // Open file briefly with FILE_SHARE_DELETE auto fa = mc.fsaccess->newfileaccess(); if (!fa || !fa->fopenForMacRead(state->filePath, FSLogging::logOnError)) { LOG_debug << logPrefix << "Cannot open file: " << state->filePath; state->setFailed(); return MacAdvanceResult::Failed; } // Verify file size matches expected if (fa->size != state->totalSize) { LOG_debug << logPrefix << "File size changed: expected " << state->totalSize << ", got " << fa->size; fa->fclose(); return MacAdvanceResult::Obsolete; } // Read chunk into buffer auto chunkData = std::shared_ptr(new byte[static_cast(readSize) + SymmCipher::BLOCKSIZE]); memset(chunkData.get() + readSize, 0, SymmCipher::BLOCKSIZE); bool readOk = fa->frawread(chunkData.get(), static_cast(readSize), readStart, true, FSLogging::logOnError); fa->fclose(); if (!readOk) { LOG_debug << logPrefix << "Read failed at " << readStart << ": " << state->filePath; state->setFailed(); return MacAdvanceResult::Failed; } // Mark chunk in progress and queue to worker thread state->chunkInProgress.store(true, std::memory_order_release); std::weak_ptr weakMac = state; const std::string workerLogPre = logPrefix + "(worker): "; mc.mAsyncQueue.push( [weakMac, readStart, readEnd, chunkData, workerLogPre](SymmCipher&) { processChunkOnWorkerThread(weakMac, readStart, readEnd, chunkData, workerLogPre); }, true); LOG_verbose << logPrefix << "Queued chunk [" << readStart << "-" << readEnd << "]: " << state->filePath; return MacAdvanceResult::Pending; } MacComputationState::~MacComputationState() { if (throttleSlotAcquired) { mThrottle.releaseFile(); throttleSlotAcquired = false; LOG_verbose << "MacComputationState: Released throttle slot for " << filePath; } } namespace { // Default throttle window for mtime-only MAC computations. // Files with frequent mtime changes (like .eml on Windows) benefit from this delay. constexpr std::chrono::seconds MAC_THROTTLE_WINDOW{60}; /** * @brief Initialize MAC computation cipher params from cloud node. * * Extracts cipher key and IV from the node key. * Returns the expected MAC (for later comparison) or INVALID_META_MAC on failure. * * THREAD SAFETY: This function runs on the sync thread and must hold * nodeTreeMutex when accessing node data to prevent races with the client thread. */ int64_t initMacComputationFromNode(MegaClient& mc, const NodeHandle cloudNodeHandle, MacComputationState& macComp, const std::string& logPrefix) { // Lock the node tree to safely access node data from the sync thread. // The node key and type could change if the client thread is processing updates. std::lock_guard g(mc.nodeTreeMutex); auto node = mc.nodeByHandle(cloudNodeHandle); if (!node || node->type != FILENODE || node->nodekey().empty()) { LOG_debug << logPrefix << "Invalid cloud node"; return INVALID_META_MAC; } const auto& nodeKey = node->nodekey(); // Extract cipher key (first 16 bytes, XORed with second 16 bytes) memcpy(macComp.transferkey.data(), nodeKey.data(), SymmCipher::KEYLENGTH); SymmCipher::xorblock((const byte*)nodeKey.data() + SymmCipher::KEYLENGTH, macComp.transferkey.data()); // Extract IV and expected MAC from key const char* iva = nodeKey.data() + SymmCipher::KEYLENGTH; macComp.ctriv = MemAccess::get(iva); int64_t expectedMac = MemAccess::get(iva + sizeof(int64_t)); return expectedMac; } /** * @brief Async MAC computation for CSF case (synced files with mtime difference). * * Uses advanceMacComputation() as the shared core, with context validation * specific to CSF case (file/cloud node changes detection). */ FsCloudComparisonResult asyncMacComputation(MegaClient& mc, const CloudNode& cn, const FSNode& fs, const LocalPath& fsNodeFullPath, LocalNode& syncNode) { static const std::string logPre{"asyncMacComputation: "}; const auto restartAfterInvalidation = [&syncNode, &fsNodeFullPath]() -> FsCloudComparisonResult { LOG_debug << logPre << "MAC state invalidated, requesting rescan: " << fsNodeFullPath; syncNode.resetMacComputationIfAny(); syncNode.setScanAgain(true, false, false, 0); return {NODE_COMP_PENDING, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; }; // Check throttling first - skip if a recent mtime-only op just completed if (syncNode.shouldThrottleMacComputation(MAC_THROTTLE_WINDOW)) { LOG_verbose << logPre << "Throttled (recent mtime-only op): " << fsNodeFullPath; return {NODE_COMP_PENDING, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } // Check for existing computation if (syncNode.hasRare() && syncNode.rareRO().macComputation) { // Take a copy for consistency with clone candidate pattern (not strictly required // since sync core is single-threaded, but defensive and documents intent) auto macComp = syncNode.rare().macComputation; // Validate context is still current (handles moves, deletes, content changes) if (!macComp->contextMatches(fs.fsid, cn.handle, fs.fingerprint, cn.fingerprint)) { LOG_debug << logPre << "Context invalid, discarding: " << fsNodeFullPath; syncNode.resetMacComputationIfAny(); // Fall through to initiate new computation } else { // Context valid - advance or check result auto result = advanceMacComputation(mc, macComp, logPre); if (result == MacAdvanceResult::Ready) { // Local MAC ready - compare with expected (remote) MAC int64_t localMac = macComp->localMac; int64_t remoteMac = macComp->context ? macComp->context->expectedMac : INVALID_META_MAC; auto compRes = (localMac == remoteMac) ? NODE_COMP_DIFFERS_MTIME : NODE_COMP_DIFFERS_MAC; LOG_debug << logPre << "Complete: " << nodeComparisonResultToStr(compRes) << " [localMac=" << localMac << ", remoteMac=" << remoteMac << ", path='" << fsNodeFullPath << "']"; syncNode.resetMacComputationIfAny(); return {compRes, localMac, remoteMac, compRes == NODE_COMP_DIFFERS_MTIME ? FingerprintMismatch::MtimeOnly : FingerprintMismatch::Other}; } else if (result == MacAdvanceResult::Failed) { LOG_debug << logPre << "Failed: " << fsNodeFullPath; syncNode.resetMacComputationIfAny(); return {NODE_COMP_EREAD, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } else if (result == MacAdvanceResult::Obsolete) { return restartAfterInvalidation(); } else { // Pending - return and wait return {NODE_COMP_PENDING, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } } } // No existing computation - start new one // Check global throttle before starting new file computation if (!mc.syncs.macComputationThrottle().tryAcquireFile()) { LOG_verbose << logPre << "Throttle full, deferring: " << fsNodeFullPath << " [files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; return {NODE_COMP_PENDING, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } // Create new computation state const m_off_t fileSize = fs.fingerprint.size; auto macComp = std::make_shared(fileSize, fsNodeFullPath, mc.syncs.macComputationThrottle()); macComp->throttleSlotAcquired = true; // Store CSF context for validation macComp->context = MacComputationContext{}; macComp->context->localFp = fs.fingerprint; macComp->context->cloudFp = cn.fingerprint; macComp->context->cloudHandle = cn.handle; macComp->context->fsid = fs.fsid; // Initialize cipher params and get expected MAC int64_t expectedMac = initMacComputationFromNode(mc, cn.handle, *macComp, logPre); if (expectedMac == INVALID_META_MAC) { return {NODE_COMP_EARGS, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } macComp->context->expectedMac = expectedMac; // Store in syncNode syncNode.rare().macComputation = macComp; LOG_debug << logPre << "Initiating: " << fsNodeFullPath << " [size=" << fileSize << ", files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; // Advance computation (will queue first chunk) auto result = advanceMacComputation(mc, macComp, logPre); if (result == MacAdvanceResult::Obsolete) { return restartAfterInvalidation(); } if (result == MacAdvanceResult::Failed) { syncNode.resetMacComputationIfAny(); return {NODE_COMP_EREAD, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } // Mark initialization as complete (for consistency with clone candidate case, // even though the sync core case is single-threaded) macComp->setInitializationComplete(); return {NODE_COMP_PENDING, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::Other}; } } // anonymous namespace FsCloudComparisonResult syncEqualFsCloudExcludingMtimeAsync(MegaClient& mc, const CloudNode& cn, const FSNode& fs, const LocalPath& fsNodeFullPath, LocalNode& syncNode) { // Quick fingerprint comparison (no MAC needed if conclusive) if (auto quickResult = quickFingerprintComparison(cn, fs)) { return *quickResult; } // If there's a pending upload marked as mtime-only, skip re-computing MAC and trust the // previously validated content unless state changed. if (!syncNode.hasRare() || !syncNode.rareRO().macComputation) { if (auto pendingUpload = std::dynamic_pointer_cast(syncNode.transferSP); pendingUpload && pendingUpload->attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly && pendingUpload->fingerprint().equalExceptMtime(fs.fingerprint)) { // Optional sanity: warn if MetaMAC was not captured (unlikely). if (!pendingUpload->mMetaMac.has_value() || pendingUpload->mMetaMac.value() == INVALID_META_MAC) { LOG_verbose << "syncEqualFsCloudExcludingMtimeAsync: mtime-only pending upload " << "without valid mMetaMac (proceeding without recompute): " << fsNodeFullPath; } LOG_debug << "syncEqualFsCloudExcludingMtimeAsync: reuse mtime-only pending upload, " << "skipping MAC recomputation: " << fsNodeFullPath; return {NODE_COMP_DIFFERS_MTIME, INVALID_META_MAC, INVALID_META_MAC, FingerprintMismatch::MtimeOnly}; } } // mtime differs - need async MAC computation return asyncMacComputation(mc, cn, fs, fsNodeFullPath, syncNode); } /***********************************\ * CLONE CANDIDATE MAC COMPUTATION * \***********************************/ CloneMacStatus initCloneCandidateMacComputation(MegaClient& mc, SyncUpload_inClient& upload) { static const std::string logPre{"initCloneCandidateMac: "}; // Already have MAC - nothing to init if (upload.mMetaMac.has_value()) { return CloneMacStatus::Ready; } // Already have computation in progress if (upload.macComputation) { return CloneMacStatus::Pending; } // Get first potential candidate to extract cipher params auto candidates = mc.mNodeManager.getNodesByFingerprint(upload, true /*excludeMtime*/); if (candidates.empty()) { LOG_debug << logPre << "No candidates found: " << upload.getLocalname(); return CloneMacStatus::NoCandidates; } // Find first valid candidate for cipher params std::shared_ptr candidateNode; for (const auto& node: candidates) { if (node && node->type == FILENODE && !node->nodekey().empty()) { candidateNode = node; break; } } if (!candidateNode) { LOG_debug << logPre << "No valid candidate node: " << upload.getLocalname(); return CloneMacStatus::NoCandidates; } // Create computation state (no context needed - upload lifetime handles validity) const m_off_t fileSize = upload.size; auto macComp = std::make_shared(fileSize, upload.getLocalname(), mc.syncs.mMacComputationThrottle); upload.macComputation = macComp; // Store in upload, but keep local copy for safety // Initialize cipher params from candidate node // Using local macComp pointer ensures the object stays alive even if another thread // resets upload.macComputation during this function. const auto& nodeKey = candidateNode->nodekey(); macComp->cloneCandidateHandle = candidateNode->nodeHandle(); macComp->cloneCandidateNodeKey = nodeKey; memcpy(macComp->transferkey.data(), nodeKey.data(), SymmCipher::KEYLENGTH); SymmCipher::xorblock((const byte*)nodeKey.data() + SymmCipher::KEYLENGTH, macComp->transferkey.data()); const char* iva = nodeKey.data() + SymmCipher::KEYLENGTH; macComp->ctriv = MemAccess::get(iva); // Try to acquire throttle now; if not, leave computation set up and return Pending if (mc.syncs.macComputationThrottle().tryAcquireFile()) { macComp->throttleSlotAcquired = true; LOG_debug << logPre << "Initiating: " << upload.getLocalname() << " [size=" << fileSize << ", files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; // Start first chunk - pass local macComp to ensure object stays alive auto result = advanceMacComputation(mc, macComp, logPre); if (result == MacAdvanceResult::Failed || result == MacAdvanceResult::Obsolete) { LOG_debug << logPre << "Failed to start computation of MAC for " << upload.getLocalname(); upload.macComputation.reset(); return CloneMacStatus::Failed; } } else { LOG_verbose << logPre << "Throttle full, deferred start: " << upload.getLocalname() << " [files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; } // Mark initialization as complete - checkPendingCloneMac can now safely proceed macComp->setInitializationComplete(); return CloneMacStatus::Pending; // Computation created; may be waiting for throttle or running } CloneMacStatus checkPendingCloneMac(MegaClient& mc, SyncUpload_inClient& upload) { static const std::string logPre{"checkPendingCloneMac: "}; // Take a copy of the shared_ptr to ensure the MacComputationState stays alive // during this function, even if another thread resets upload.macComputation. // This also makes the null check and subsequent use atomic with respect to // other threads that might reset the shared_ptr. auto macComp = upload.macComputation; if (!macComp) { return CloneMacStatus::NoCandidates; } // Wait until initCloneCandidateMacComputation has finished setting up the computation. // This prevents races where we try to process/advance a computation that's still // being initialized on the client thread. if (!macComp->isInitializationComplete()) { return CloneMacStatus::Pending; } if (upload.mMetaMac.has_value()) { LOG_warn << logPre << "Already have MAC with macComputation still living: " << upload.getLocalname() << " [throttleSlotAcquired=" << macComp->throttleSlotAcquired << "]"; upload.macComputation.reset(); return CloneMacStatus::Ready; } // If we never acquired throttle, try now before advancing if (!macComp->throttleSlotAcquired) { if (!mc.syncs.macComputationThrottle().tryAcquireFile()) { LOG_verbose << logPre << "Throttle still full, pending: " << upload.getLocalname() << " [files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; return CloneMacStatus::Pending; } macComp->throttleSlotAcquired = true; LOG_debug << logPre << "Acquired throttle, resuming: " << upload.getLocalname() << " [files=" << mc.syncs.macComputationThrottle().currentFiles() << "]"; } // Validate that the original candidate still exists and has the same key if (!macComp->cloneCandidateHandle.isUndef()) { std::lock_guard g(mc.nodeTreeMutex); auto node = mc.nodeByHandle(macComp->cloneCandidateHandle); const bool valid = node && node->type == FILENODE && !node->nodekey().empty() && node->nodekey() == macComp->cloneCandidateNodeKey; if (!valid) { LOG_debug << logPre << "Candidate removed/changed, aborting computation of MAC for " << upload.getLocalname(); upload.macComputation.reset(); return CloneMacStatus::Failed; } } // Advance computation auto result = advanceMacComputation(mc, macComp, logPre); if (result == MacAdvanceResult::Ready) { // Local MAC computed - store it for findCloneNodeCandidate upload.mMetaMac.emplace(macComp->localMac); LOG_debug << logPre << "Complete: MAC=" << macComp->localMac << " [path='" << upload.getLocalname() << "']"; upload.macComputation.reset(); return CloneMacStatus::Ready; } else if (result == MacAdvanceResult::Failed) { LOG_debug << logPre << "Failed: " << upload.getLocalname(); upload.macComputation.reset(); return CloneMacStatus::Failed; } else if (result == MacAdvanceResult::Obsolete) { LOG_debug << logPre << "Obsolete: " << upload.getLocalname(); upload.macComputation.reset(); return CloneMacStatus::Failed; } // Pending return CloneMacStatus::Pending; } void processCloneMacResult(MegaClient& mc, TransferDbCommitter& committer, std::shared_ptr upload, const VersioningOption vo, const bool queueFirst, const NodeHandle ovHandleIfShortcut, const CloneMacStatus macStatus) { if (!upload) { return; } switch (macStatus) { case CloneMacStatus::Pending: // MAC computation in progress or throttled. // Leave wasStarted = true (already set above) and return. // The sync thread will check progress via checkPendingCloneMac in resolve_upsync. LOG_verbose << "processCloneMacResult: MAC computation pending: " << upload->getLocalname(); return; case CloneMacStatus::Ready: // MAC ready - proceed with clone candidate search (non-blocking helper). if (auto cloneNodeCandidate = findCloneNodeCandidate(mc, *upload, true /*excludeMtime*/)) { LOG_debug << "processCloneMacResult: Proceeding with clone of candidate node for " << upload->getLocalname(); upload->cloneNode(mc, cloneNodeCandidate, ovHandleIfShortcut); return; } LOG_warn << "processCloneMacResult: MAC was computed, but no clone candidate matched for " << upload->getLocalname() << " !! Falling back to full upload"; break; case CloneMacStatus::Failed: LOG_warn << "processCloneMacResult: MAC computation failed for " << upload->getLocalname() << " !! Falling back to full upload"; break; case CloneMacStatus::NoCandidates: LOG_warn << "processCloneMacResult: No clone candidates found for " << upload->getLocalname() << ". Falling back to full upload"; break; } upload->fullUpload(mc, committer, vo, queueFirst); } } // namespace mega #endif // ENABLE_SYNC sdk-10.11.0/src/syncinternals/syncuploadthrottlingfile.cpp000066400000000000000000000073131516266226600240020ustar00rootroot00000000000000/** * @file syncuploadthrottlingfile.cpp * @brief Class for UploadThrottlingFile. */ #ifdef ENABLE_SYNC #include "mega/syncinternals/syncuploadthrottlingfile.h" #include "mega/logging.h" namespace mega { void UploadThrottlingFile::increaseUploadCounter() { mUploadCounterLastTime = std::chrono::steady_clock::now(); if (mUploadCounter + 1 == std::numeric_limits::max()) { // Reset to 0 when max is about to be reached LOG_err << "[UploadThrottlingFile::increaseUploadCounter] The upload counter (" << mUploadCounter << ") is about to reach the max allowed. Value will be reset"; assert(false && "[UploadThrottlingFile::increaseUploadCounter] Upload counter is about " "to reach the max allowed!"); mUploadCounter = 0; } ++mUploadCounter; } bool UploadThrottlingFile::checkUploadThrottling( const unsigned maxUploadsBeforeThrottle, const std::chrono::seconds uploadCounterInactivityExpirationTime) { if (mBypassThrottlingNextTime) { mBypassThrottlingNextTime = false; return false; } if (const auto timeSinceLastUploadCounterProcess = std::chrono::steady_clock::now() - mUploadCounterLastTime; timeSinceLastUploadCounterProcess >= uploadCounterInactivityExpirationTime) { // Reset the upload counter if enough time has lapsed since last time. mUploadCounter = 0; mUploadCounterLastTime = std::chrono::steady_clock::now(); return false; } return mUploadCounter >= maxUploadsBeforeThrottle; } bool UploadThrottlingFile::handleAbortUpload(SyncUpload_inClient& upload, const bool transferDirectionNeedsToChange, const FileFingerprint& fingerprint, const unsigned maxUploadsBeforeThrottle, const LocalPath& transferPath) { if (upload.upsyncStarted) return false; if (transferDirectionNeedsToChange) return true; if (!upload.wasStarted) { assert(!upload.wasTerminated); if (upload.attributeOnlyUpdate.load() == SyncTransfer_inClient::AttributeOnlyUpdate::MtimeOnly && fingerprint.equalExceptMtime(upload) && fingerprint.mtime != upload.mtime) { LOG_verbose << "UploadThrottlingFile::handleAbortUpload: Updating fingerprint of " "queued upload with mtime-only change: " << transferPath << " to " << fingerprint.mtime; upload.updateFingerprintMtime(fingerprint.mtime); } else { LOG_verbose << "UploadThrottlingFile::handleAbortUpload: Updating fingerprint of " "queued upload: " << transferPath << " [attributeOnlyUpdate: " << static_cast(upload.attributeOnlyUpdate.load()) << "]"; upload.updateFingerprint(fingerprint); upload.attributeOnlyUpdate = SyncTransfer_inClient::AttributeOnlyUpdate::None; upload.mMetaMac = std::nullopt; } return false; } // If the upload is going to be aborted either due to a change while it was inflight or after a // failure, and the file was being throttled, let it start immediately next time. bypassThrottlingNextTime(maxUploadsBeforeThrottle); return true; } void UploadThrottlingFile::bypassThrottlingNextTime(const unsigned maxUploadsBeforeThrottle) { if (mUploadCounter >= maxUploadsBeforeThrottle) { mBypassThrottlingNextTime = true; } } } // namespace mega #endif // ENABLE_SYNC sdk-10.11.0/src/syncinternals/syncuploadthrottlingmanager.cpp000066400000000000000000000072351516266226600245000ustar00rootroot00000000000000/** * @file syncuploadthrottlingmanager.cpp * @brief Class for UploadThrottlingManager. */ #ifdef ENABLE_SYNC #include "mega/syncinternals/syncuploadthrottlingmanager.h" #include "mega/syncinternals/syncinternals_logging.h" #include namespace mega { template static bool valueIsOutOfRange(const T& value, const T& lower, const T& upper) { if (value == std::clamp(value, lower, upper)) return false; LOG_warn << "[UploadThrottle::valueIsOutOfRange] Value out of range (" << value << "). Must be >= " << lower << " and <= " << upper; return true; } bool UploadThrottlingManager::setThrottleUpdateRate(const std::chrono::seconds interval) { if (valueIsOutOfRange(interval.count(), THROTTLE_UPDATE_RATE_LOWER_LIMIT.count(), THROTTLE_UPDATE_RATE_UPPER_LIMIT.count())) { return false; } LOG_debug << "[UploadThrottle] Throttle update rate set to " << interval.count() << " secs"; mThrottleUpdateRate = interval; return true; } bool UploadThrottlingManager::setMaxUploadsBeforeThrottle(const unsigned maxUploadsBeforeThrottle) { if (valueIsOutOfRange(maxUploadsBeforeThrottle, MAX_UPLOADS_BEFORE_THROTTLE_LOWER_LIMIT, MAX_UPLOADS_BEFORE_THROTTLE_UPPER_LIMIT)) { return false; } LOG_debug << "[UploadThrottle] Num uploads before throttle set to " << maxUploadsBeforeThrottle; mMaxUploadsBeforeThrottle = maxUploadsBeforeThrottle; return true; } bool UploadThrottlingManager::checkProcessDelayedUploads() const { if (mDelayedQueue.empty()) { return false; } // Calculate adjusted interval const auto throttleUpdateRate = std::max(std::chrono::seconds(THROTTLE_UPDATE_RATE_LOWER_LIMIT), dynamicThrottleUpdateRate()); if (const auto timeSinceLastProcessedUploadInSeconds = timeSinceLastProcessedUpload(); timeSinceLastProcessedUploadInSeconds < throttleUpdateRate) { SYNCS_verbose_timed << "[UploadThrottle] Waiting to process delayed uploads [processing every " << throttleUpdateRate.count() << " secs, time lapsed since last process: " << timeSinceLastProcessedUploadInSeconds.count() << " secs, delayed uploads = " << mDelayedQueue.size() << "]"; return false; } return true; } void UploadThrottlingManager::processDelayedUploads( std::function&& completion) { if (!checkProcessDelayedUploads()) { return; } LOG_verbose << "[UploadThrottle] Processing delayed uploads. Queue size: " << mDelayedQueue.size(); bool delayedUploadProcessed{false}; while (!mDelayedQueue.empty() && !delayedUploadProcessed) { DelayedSyncUpload delayedUpload = std::move(mDelayedQueue.front()); mDelayedQueue.pop(); if (!delayedUpload.mWeakUpload.lock()) { LOG_warn << "[UploadThrottle] Upload is no longer valid. Skipping this task"; continue; } delayedUploadProcessed = true; resetLastProcessedTime(); completion(std::move(delayedUpload)); } } std::chrono::seconds calcDynamicThrottleUpdateRate(const std::chrono::seconds updateRateSeconds, const size_t delayedUploadsSize) { if (!delayedUploadsSize) return std::chrono::seconds{0}; return std::chrono::duration_cast( updateRateSeconds / std::sqrt(static_cast(delayedUploadsSize))); } } // namespace mega #endif // ENABLE_SYNC sdk-10.11.0/src/testhooks.cpp000066400000000000000000000015351516266226600157710ustar00rootroot00000000000000/** * @file mega/testhooks.cpp * @brief helper classes for causing/simulating conditions for various tests of the mega SDK code * * (c) 2013-2017 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/testhooks.h" namespace mega { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED MegaTestHooks globalMegaTestHooks; #endif } sdk-10.11.0/src/textchat.cpp000066400000000000000000001104131516266226600155660ustar00rootroot00000000000000/** * @file textchat.cpp * @brief Class for manipulating text chat * * (c) 2013-2022 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/textchat.h" #include "mega/utils.h" #include "mega/megaclient.h" #include "mega/base64.h" namespace mega { #ifdef ENABLE_CHAT bool ScheduledFlags::serialize(string& out) const { CacheableWriter w(out); w.serializeu32(static_cast(mFlags.to_ulong())); return true; } ScheduledFlags* ScheduledFlags::unserialize(const std::string &in) { if (in.empty()) { return nullptr; } uint32_t flagsNum = schedEmptyFlags; CacheableReader r(in); if (!r.unserializeu32(flagsNum)) { assert(false); LOG_err << "ScheduledFlags unserialization failed at field flagsNum"; return nullptr; } return new ScheduledFlags(flagsNum); } ScheduledRules::ScheduledRules(const int freq, const int interval, const m_time_t until, const rules_vector* byWeekDay, const rules_vector* byMonthDay, const rules_map* byMonthWeekDay) : mFreq(isValidFreq(freq) ? static_cast(freq) : FREQ_INVALID), mInterval(isValidInterval(interval) ? interval : INTERVAL_INVALID), mUntil(isValidUntil(until) ? until : mega_invalid_timestamp), mByWeekDay(byWeekDay ? std::make_unique(*byWeekDay) : nullptr), mByMonthDay(byMonthDay ? std::make_unique(*byMonthDay) : nullptr), mByMonthWeekDay(byMonthWeekDay ? std::make_unique(*byMonthWeekDay) : nullptr) {} ScheduledRules::ScheduledRules(const ScheduledRules* rules) : mFreq(isValidFreq(rules->freq()) ? rules->freq() : FREQ_INVALID), mInterval(isValidInterval(rules->interval()) ? rules->interval() : INTERVAL_INVALID), mUntil(rules->until()), mByWeekDay(rules->byWeekDay() ? std::make_unique(*rules->byWeekDay()) : nullptr), mByMonthDay(rules->byMonthDay() ? std::make_unique(*rules->byMonthDay()) : nullptr), mByMonthWeekDay(rules->byMonthWeekDay() ? std::make_unique(*rules->byMonthWeekDay()) : nullptr) {} const char* ScheduledRules::freqToString () const { switch (mFreq) { case 0: return "d"; case 1: return "w"; case 2: return "m"; default: return nullptr; } } bool ScheduledRules::equalTo(const mega::ScheduledRules *r) const { if (!r) { return false; } if (freq() != r->freq()) { return false; } if (interval() != r->interval()) { return false; } if (until() != r->until()) { return false; } if (byWeekDay() || r->byWeekDay()) { if (!byWeekDay() || !r->byWeekDay()) { return false; } if (*byWeekDay() != *r->byWeekDay()) { return false; } } if (byMonthDay() || r->byMonthDay()) { if (!byMonthDay() || !r->byMonthDay()) { return false; } if (*byMonthDay() != *r->byMonthDay()) { return false; } } if (mByMonthWeekDay || r->byMonthWeekDay()) { if (!byMonthWeekDay() || !r->byMonthWeekDay()) { return false; } if (*byMonthWeekDay() != *r->byMonthWeekDay()) { return false; } } return true; } int ScheduledRules::stringToFreq (const char* freq) { if (strcmp(freq, "d") == 0) { return FREQ_DAILY; } if (strcmp(freq, "w") == 0) { return FREQ_WEEKLY; } if (strcmp(freq, "m") == 0) { return FREQ_MONTHLY; } return FREQ_INVALID; } bool ScheduledRules::serialize(string& out) const { assert(isValid()); const bool hasInterval = isValidInterval(interval()); const bool hasUntil = isValidUntil(until()); const bool hasByWeekDay = byWeekDay() && !byWeekDay()->empty(); const bool hasByMonthDay = byMonthDay() && !byMonthDay()->empty(); const bool hasByMonthWeekDay = byMonthWeekDay() && !byMonthWeekDay()->empty(); CacheableWriter w(out); w.serializei32(freq()); w.serializeexpansionflags(hasInterval, hasUntil, hasByWeekDay, hasByMonthDay, hasByMonthWeekDay); if (hasInterval) { w.serializei32(interval()); } if (hasUntil) { w.serializei64(until()); } const auto serializeSmallIntVector = [&w](const rules_vector& v) { w.serializeu32(static_cast(v.size())); for (auto i: v) { w.serializei8(i); } }; if (hasByWeekDay) { serializeSmallIntVector(*byWeekDay()); } if (hasByMonthDay) { serializeSmallIntVector(*byMonthDay()); } if (hasByMonthWeekDay) { w.serializeu32(static_cast(byMonthWeekDay()->size()*2)); for (auto i: *byMonthWeekDay()) { w.serializei8(i.first); w.serializei8(i.second); } } return true; } ScheduledRules* ScheduledRules::unserialize(const string& in) { if (in.empty()) { return nullptr; } int freq = FREQ_INVALID; constexpr unsigned int flagsSize = 5; unsigned char expansions[8]; // must be defined with size 8 const auto logAndFail = [](const std::string& msg) -> ScheduledRules* { LOG_err << "Failure at schedule meeting rules unserialization " << msg; assert(false); return nullptr; }; CacheableReader r(in); if (!r.unserializei32(freq) || !r.unserializeexpansionflags(expansions, flagsSize)) { return logAndFail(std::string()); } const bool hasInterval = expansions[0] > 0; const bool hasUntil = expansions[1] > 0; const bool hasByWeekDay = expansions[2] > 0; const bool hasByMonthDay = expansions[3] > 0; const bool hasByMonthWeekDay = expansions[4] > 0; int interval = INTERVAL_INVALID; if (hasInterval && !r.unserializei32(interval)) { return logAndFail("interval"); } m_time_t until = mega_invalid_timestamp; if (hasUntil && !r.unserializei64(until)) { return logAndFail("until"); } const auto unserializeVector = [&r, &logAndFail](rules_vector& outVec, const string& errMsg) -> bool { uint32_t s = 0; if (!r.unserializeu32(s)) { return logAndFail(errMsg + " vector size") != nullptr; } outVec.reserve(s); for (size_t i = 0; i < s; ++i) { int8_t element = 0; if (r.unserializei8(element)) { outVec.emplace_back(element); } else { return logAndFail(errMsg) != nullptr; } } return true; }; rules_vector byWeekDay; if (hasByWeekDay && !unserializeVector(byWeekDay, "byWeekDay")) { return nullptr; } rules_vector byMonthDay; if (hasByMonthDay && !unserializeVector(byMonthDay, "byMonthDay")) { return nullptr; } rules_map byMonthWeekDay; if (hasByMonthWeekDay) { static const std::string name {"byMonthWeekDay"}; uint32_t auxSize = 0; if (!r.unserializeu32(auxSize)) { return logAndFail(name + " vector size"); } if (auxSize % 2) { return logAndFail(name + " odd vector size"); } auxSize /= 2; for (size_t i = 0; i < auxSize; ++i) { int8_t key = 0, value = 0; if (r.unserializei8(key) && r.unserializei8(value)) { byMonthWeekDay.emplace(key, value); } else { return logAndFail(name); } } } return new ScheduledRules(freq, hasInterval ? interval : -1, until, hasByWeekDay ? &byWeekDay : nullptr, hasByMonthDay ? &byMonthDay: nullptr, hasByMonthWeekDay ? &byMonthWeekDay: nullptr); } ScheduledMeeting::ScheduledMeeting(const handle chatid, const std::string &timezone, const m_time_t startDateTime, const m_time_t endDateTime, const std::string &title, const std::string &description, const handle organizerUserId, const handle schedId, const handle parentSchedId, const int cancelled, const std::string &attributes, const m_time_t overrides, const ScheduledFlags* flags, const ScheduledRules* rules) : mChatid(chatid), mOrganizerUserId(organizerUserId), mSchedId(schedId), mParentSchedId(parentSchedId), mTimezone(timezone), mStartDateTime(startDateTime), mEndDateTime(endDateTime), mTitle(title), mDescription(description), mAttributes(attributes), mOverrides(overrides), mCancelled(cancelled), mFlags(flags ? flags->copy() : nullptr), mRules(rules ? rules->copy() : nullptr) {} ScheduledMeeting::ScheduledMeeting(const ScheduledMeeting* sm) : mChatid(sm->chatid()), mOrganizerUserId(sm->organizerUserid()), mSchedId(sm->schedId()), mParentSchedId(sm->parentSchedId()), mTimezone(sm->timezone()), mStartDateTime(sm->startDateTime()), mEndDateTime(sm->endDateTime()), mTitle(sm->title()), mDescription(sm->description()), mAttributes(sm->attributes()), mOverrides(sm->overrides()), mCancelled(sm->cancelled()), mFlags(sm->flags() ? sm->flags()->copy() : nullptr), mRules(sm->rules() ? sm->rules()->copy() : nullptr) {} bool ScheduledMeeting::isValid() const { const std::string errMsg {"Invalid scheduled meeting "}; if (mSchedId == UNDEF) { LOG_warn << errMsg << "schedId. chatid: " << Base64Str(mChatid); return false; } const auto sId = schedId(); const auto logAndFail = [&errMsg, &sId](const string& msg) -> bool { LOG_warn << errMsg << msg << " . schedId: " << Base64Str(sId);; return false; }; if (chatid() == UNDEF) { return logAndFail("chatid"); } if (organizerUserid() == UNDEF) { return logAndFail("organizer user id"); } if (timezone().empty()) { return logAndFail("timezone"); } if (!MegaClient::isValidMegaTimeStamp(startDateTime())) { return logAndFail("StartDateTime"); } if (!MegaClient::isValidMegaTimeStamp(endDateTime())) { return logAndFail("EndDateTime"); } if (title().empty()) { return logAndFail("title"); } if (rules() && !rules()->isValid()) { return logAndFail("rules"); } if (overrides() != mega_invalid_timestamp && !MegaClient::isValidMegaTimeStamp(overrides())) { // overrides is an optional field so if it's not present, we will store mega_invalid_timestamp return logAndFail(std::string{"overrides: " + std::to_string(overrides())}); } return true; } bool ScheduledMeeting::equalTo(const ScheduledMeeting* sm) const { if (!sm) { return false; } if (parentSchedId() != sm->parentSchedId()) { return false; } if (timezone().compare(sm->timezone())) { return false; } if (startDateTime() != sm->startDateTime()) { return false; } if (endDateTime() != sm->endDateTime()) { return false; } if (title().compare(sm->title())) { return false; } if (description().compare(sm->description())) { return false; } if (attributes().compare(sm->attributes())) { return false; } if (overrides() != sm->overrides()) { return false; } if (cancelled() != sm->cancelled()) { return false; } if (flags() || sm->flags()) { if (flags() && !flags()->equalTo(sm->flags())) { return false; } if (sm->flags() && !sm->flags()->equalTo(flags())) { return false; } } if (rules() || sm->rules()) { if (rules() && !rules()->equalTo(sm->rules())) { return false; } if (sm->rules() && !sm->rules()->equalTo(rules())) { return false; } } return true; } bool ScheduledMeeting::serialize(string& out) const { if (schedId() == UNDEF) { LOG_warn << "ScheduledMeeting::serialize: Invalid scheduled meeting with an UNDEF schedId"; assert(false); return false; } const bool hasParentSchedId = parentSchedId() != UNDEF; const bool hasAttributes = !attributes().empty(); const bool hasOverrides = MegaClient::isValidMegaTimeStamp(overrides()); const bool hasCancelled = cancelled() > -1; const bool hasflags = flags() != nullptr; const bool hasRules = rules() != nullptr; CacheableWriter w(out); w.serializehandle(schedId()); w.serializehandle(organizerUserid()); w.serializestring(timezone()); w.serializei64(startDateTime()); w.serializei64(endDateTime()); w.serializestring(title()); w.serializestring(description()); w.serializeexpansionflags(hasParentSchedId, hasAttributes, hasOverrides, hasCancelled, hasflags, hasRules); if (hasParentSchedId) { w.serializehandle(parentSchedId());} if (hasAttributes) { w.serializestring(attributes()); } if (hasOverrides) { w.serializei64(overrides()); } if (hasCancelled) { w.serializei32(cancelled()); } if (hasflags) { std::string flagsStr; if (flags()->serialize(flagsStr)) { w.serializestring(flagsStr); } } if (hasRules) { std::string rulesStr; if (rules()->serialize(rulesStr)) { w.serializestring(rulesStr); } } return true; } ScheduledMeeting* ScheduledMeeting::unserialize(const string& in, const handle chatid) { if (in.empty()) { return nullptr; } handle schedId = UNDEF; handle organizerUserid = UNDEF; std::string timezone; m_time_t startDateTime = mega_invalid_timestamp; m_time_t endDateTime = mega_invalid_timestamp; std::string title; std::string description; constexpr unsigned int flagsSize = 6; unsigned char expansions[8]; // must be defined with size 8 const auto logAndFail = [](const string& /*msg*/) -> ScheduledMeeting* { LOG_err << "Failure at schedule meeting unserialization "; assert(false); return nullptr; }; CacheableReader r(in); if (!r.unserializehandle(schedId) || !r.unserializehandle(organizerUserid) || !r.unserializestring(timezone) || !r.unserializei64(startDateTime) || !r.unserializei64(endDateTime) || !r.unserializestring(title) || !r.unserializestring(description) || !r.unserializeexpansionflags(expansions, flagsSize)) { return logAndFail(""); } const bool hasParentSchedId = expansions[0] > 0; const bool hasAttributes = expansions[1] > 0; const bool hasOverrides = expansions[2] > 0; const bool hasCancelled = expansions[3] > 0; const bool hasflags = expansions[4] > 0; const bool hasRules = expansions[5] > 0; handle parentSchedId = UNDEF; if (hasParentSchedId && !r.unserializehandle(parentSchedId)) { return logAndFail("parent Schedule id"); } std::string attributes; if (hasAttributes && !r.unserializestring(attributes)) { return logAndFail("attributes"); } m_time_t overrides = mega_invalid_timestamp; if (hasOverrides && !r.unserializei64(overrides)) { return logAndFail("override"); } int cancelled = -1; if (hasCancelled && !r.unserializei32(cancelled)) { return logAndFail("cancelled"); } std::string flagsStr; std::unique_ptr flags; if (hasflags && r.unserializestring(flagsStr)) { flags.reset(ScheduledFlags::unserialize(flagsStr)); if (!flags) { return logAndFail("flags"); } } std::string rulesStr; std::unique_ptr rules; if (hasRules && r.unserializestring(rulesStr)) { rules.reset(ScheduledRules::unserialize(rulesStr)); if (!rules) { return logAndFail("rules"); } } return new ScheduledMeeting(chatid, timezone, startDateTime, endDateTime, title, description, organizerUserid, schedId, hasParentSchedId ? parentSchedId : UNDEF, hasCancelled ? cancelled : -1, attributes, overrides, flags.get(), rules.get()); } TextChat::TextChat(const bool publicChat) : mPublicChat(publicChat) { } bool TextChat::serialize(string *d) const { unsigned short ll; d->append((char*)&id, sizeof id); d->append((char*)&priv, sizeof priv); d->append((char*)&shard, sizeof shard); ll = (unsigned short)(userpriv ? userpriv->size() : 0); d->append((char*)&ll, sizeof ll); if (userpriv) { userpriv_vector::iterator it = userpriv->begin(); while (it != userpriv->end()) { handle uh = it->first; d->append((char*)&uh, sizeof uh); privilege_t privilege = it->second; d->append((char*)&privilege, sizeof(privilege)); it++; } } d->append((char*)&group, sizeof group); // title is a binary array ll = (unsigned short)title.size(); d->append((char*)&ll, sizeof ll); d->append(title.data(), ll); d->append((char*)&ou, sizeof ou); d->append((char*)&ts, sizeof(ts)); char hasAttachments = attachedNodes.size() != 0; d->append((char*)&hasAttachments, 1); d->append((char*)&flags, 1); char mode = mPublicChat ? 1 : 0; d->append((char*)&mode, 1); char hasUnifiedKey = unifiedKey.size() ? 1 : 0; d->append((char *)&hasUnifiedKey, 1); char meetingRoom = meeting ? 1 : 0; d->append((char*)&meetingRoom, 1); d->append((char*)&chatOptions, 1); char hasSheduledMeetings = !mScheduledMeetings.empty() ? 1 : 0; d->append((char*)&hasSheduledMeetings, 1); d->append("\0\0\0", 3); // additional bytes for backwards compatibility if (hasAttachments) { ll = (unsigned short)attachedNodes.size(); // number of nodes with granted access d->append((char*)&ll, sizeof ll); for (attachments_map::const_iterator it = attachedNodes.begin(); it != attachedNodes.end(); it++) { d->append((char*)&it->first, sizeof it->first); // nodehandle ll = (unsigned short)it->second.size(); // number of users with granted access to the node d->append((char*)&ll, sizeof ll); for (set::iterator ituh = it->second.begin(); ituh != it->second.end(); ituh++) { d->append((char*)&(*ituh), sizeof *ituh); // userhandle } } } if (hasUnifiedKey) { ll = (unsigned short) unifiedKey.size(); d->append((char *)&ll, sizeof ll); d->append((char*) unifiedKey.data(), unifiedKey.size()); } if (hasSheduledMeetings) { // serialize the number of scheduledMeetings ll = static_cast(mScheduledMeetings.size()); d->append((char *)&ll, sizeof ll); for (auto i = mScheduledMeetings.begin(); i != mScheduledMeetings.end(); i++) { std::string schedMeetingStr; if (i->second->serialize(schedMeetingStr)) { // records should fit in 64KB (unsigned short max), since the API restricts // the size of description/title to 4K/256 chars, but just in case it happened // to have a larger record, just throw an error if (schedMeetingStr.size() > std::numeric_limits::max()) { assert(false); LOG_err << "Scheduled meeting record too long. Skipping"; ll = 0; d->append((char *)&ll, sizeof ll); continue; } ll = static_cast(schedMeetingStr.size()); d->append((char *)&ll, sizeof ll); d->append((char *)schedMeetingStr.data(), schedMeetingStr.size()); } } } return true; } TextChat* TextChat::unserialize(class MegaClient *client, string *d) { handle id; privilege_t priv; int shard; std::unique_ptr userpriv; bool group; string title; // byte array handle ou; m_time_t ts; byte flags; char hasAttachments; attachments_map attachedNodes; bool publicchat; string unifiedKey; std::vector scheduledMeetingsStr; unsigned short ll; const char* ptr = d->data(); const char* end = ptr + d->size(); if (ptr + sizeof(handle) + sizeof(privilege_t) + sizeof(int) + sizeof(short) > end) { return NULL; } id = MemAccess::get(ptr); ptr += sizeof id; priv = MemAccess::get(ptr); ptr += sizeof priv; shard = MemAccess::get(ptr); ptr += sizeof shard; ll = MemAccess::get(ptr); ptr += sizeof ll; if (ll) { if (ptr + ll * (sizeof(handle) + sizeof(privilege_t)) > end) { return NULL; } userpriv = std::make_unique(); for (unsigned short i = 0; i < ll; i++) { handle uh = MemAccess::get(ptr); ptr += sizeof uh; privilege_t peerPrivilege = MemAccess::get(ptr); ptr += sizeof(peerPrivilege); userpriv->push_back(userpriv_pair(uh, peerPrivilege)); } if (priv == PRIV_RM) // clear peerlist if removed { userpriv.reset(); } } if (ptr + sizeof(bool) + sizeof(unsigned short) > end) { return NULL; } group = MemAccess::get(ptr); ptr += sizeof group; ll = MemAccess::get(ptr); ptr += sizeof ll; if (ll) { if (ptr + ll > end) { return NULL; } title.assign(ptr, ll); } ptr += ll; if (ptr + sizeof(handle) + sizeof(m_time_t) + sizeof(char) + 9 > end) { return NULL; } ou = MemAccess::get(ptr); ptr += sizeof ou; ts = MemAccess::get(ptr); ptr += sizeof(m_time_t); hasAttachments = MemAccess::get(ptr); ptr += sizeof hasAttachments; flags = MemAccess::get(ptr); ptr += sizeof(char); char mode = MemAccess::get(ptr); publicchat = (mode == 1); ptr += sizeof(char); char hasUnifiedKey = MemAccess::get(ptr); ptr += sizeof(char); char meetingRoom = MemAccess::get(ptr); ptr += sizeof(char); byte chatOptions = static_cast(MemAccess::get(ptr)); ptr += sizeof(char); char hasScheduledMeeting = MemAccess::get(ptr); ptr += sizeof(char); for (int i = 3; i--;) { if (ptr + MemAccess::get(ptr) < end) { ptr += MemAccess::get(ptr) + 1; } } if (hasAttachments) { unsigned short numNodes = 0; if (ptr + sizeof numNodes > end) { return NULL; } numNodes = MemAccess::get(ptr); ptr += sizeof numNodes; for (int i = 0; i < numNodes; i++) { handle h = UNDEF; unsigned short numUsers = 0; if (ptr + sizeof h + sizeof numUsers > end) { return NULL; } h = MemAccess::get(ptr); ptr += sizeof h; numUsers = MemAccess::get(ptr); ptr += sizeof numUsers; handle uh = UNDEF; if (ptr + (numUsers * sizeof(uh)) > end) { return NULL; } for (int j = 0; j < numUsers; j++) { uh = MemAccess::get(ptr); ptr += sizeof uh; attachedNodes[h].insert(uh); } } } if (hasUnifiedKey) { unsigned short keylen = 0; if (ptr + sizeof keylen > end) { return NULL; } keylen = MemAccess::get(ptr); ptr += sizeof keylen; if (ptr + keylen > end) { return NULL; } unifiedKey.assign(ptr, keylen); ptr += keylen; } if (hasScheduledMeeting) { // unserialize the number of scheduled meetings unsigned short schedMeetingsSize = 0; if (ptr + sizeof schedMeetingsSize > end) { return NULL; } schedMeetingsSize = MemAccess::get(ptr); ptr += sizeof schedMeetingsSize; for (auto i = 0; i < schedMeetingsSize; ++i) { unsigned short len = 0; if (ptr + sizeof len > end) { return NULL; } len = MemAccess::get(ptr); ptr += sizeof len; if (ptr + len > end) { return NULL; } std::string aux(ptr, len); scheduledMeetingsStr.emplace_back(aux); ptr += len; } } if (ptr < end) { return NULL; } vector> schedMeetings; for (const auto& i : scheduledMeetingsStr) { ScheduledMeeting* auxMeet = ScheduledMeeting::unserialize(i, id); if (!auxMeet) { LOG_err << "Failure at schedule meeting unserialization"; assert(auxMeet); return NULL; } schedMeetings.push_back(std::unique_ptr(auxMeet)); } TextChat*& chat = client->chats[id]; // use reference to pointer to avoid 3 searches instead of one if (!chat) { chat = new TextChat(publicchat); } else { LOG_warn << "Unserialized a chat already in RAM"; chat->changed = {}; } chat->id = id; chat->priv = priv; chat->shard = shard; chat->setUserPrivileges(userpriv.release()); chat->group = group; chat->title = title; chat->ou = ou; chat->resetTag(); chat->ts = ts; chat->flags = flags; chat->attachedNodes = attachedNodes; chat->unifiedKey = unifiedKey; chat->meeting = meetingRoom != 0; chat->chatOptions = chatOptions; for (auto& sm : schedMeetings) { chat->addSchedMeeting(std::move(sm), false /*notify*/); } return chat; } void TextChat::setChatId(handle newId) { id = newId; } handle TextChat::getChatId() const { return id; } void TextChat::setOwnPrivileges(privilege_t p) { priv = p; } privilege_t TextChat::getOwnPrivileges() const { return priv; } void TextChat::setShard(int sh) { shard = sh; } int TextChat::getShard() const { return shard; } void TextChat::addUserPrivileges(handle uid, privilege_t p) { if (!userpriv) { userpriv = std::make_unique(); } userpriv->emplace_back(uid, p); } bool TextChat::updateUserPrivileges(handle uid, privilege_t p) { if (!userpriv) { return false; } auto it = std::find_if(userpriv->begin(), userpriv->end(), [uid](const userpriv_pair& p) { return p.first == uid; }); if (it != userpriv->end()) { it->second = p; return true; } return false; } bool TextChat::removeUserPrivileges(handle uid) { if (!userpriv) { return false; } auto it = std::find_if(userpriv->begin(), userpriv->end(), [uid](const userpriv_pair& p) { return p.first == uid; }); if (it != userpriv->end()) { userpriv->erase(it); if (userpriv->empty()) { userpriv.reset(); } } return true; } void TextChat::setUserPrivileges(userpriv_vector* pvs) { userpriv.reset(pvs); } const userpriv_vector* TextChat::getUserPrivileges() const { return userpriv.get(); } void TextChat::setGroup(bool g) { group = g; } bool TextChat::getGroup() const { return group; } void TextChat::setTitle(const string& t) { title = t; } const string& TextChat::getTitle() const { return title; } void TextChat::setUnifiedKey(const string& uk) { unifiedKey = uk; } const string& TextChat::getUnifiedKey() const { return unifiedKey; } void TextChat::setOwnUser(handle u) { ou = u; } handle TextChat::getOwnUser() const { return ou; } void TextChat::setTs(m_time_t t) { ts = t; } m_time_t TextChat::getTs() const { return ts; } const attachments_map& TextChat::getAttachments() const { return attachedNodes; } handle_set TextChat::getUsersOfAttachment(handle a) const { auto ita = attachedNodes.find(a); if (ita != attachedNodes.end()) { return ita->second; } return handle_set(); } bool TextChat::isUserOfAttachment(handle a, handle uid) const { auto ita = attachedNodes.find(a); if (ita != attachedNodes.end()) { return ita->second.find(uid) != ita->second.end(); } return false; } void TextChat::addUserForAttachment(handle a, handle uid) { attachedNodes[a].insert(uid); } void TextChat::setMeeting(bool m) { meeting = m; } bool TextChat::getMeeting() const { return meeting; } byte TextChat::getChatOptions() const { return chatOptions; } bool TextChat::hasScheduledMeeting(handle smid) const { return mScheduledMeetings.find(smid) != mScheduledMeetings.end(); } const handle_set& TextChat::getSchedMeetingsChanged() const { return mSchedMeetingsChanged; } void TextChat::clearSchedMeetingsChanged() { mSchedMeetingsChanged.clear(); } const vector>& TextChat::getUpdatedOcurrences() const { return mUpdatedOcurrences; } void TextChat::setTag(int newTag) { if (tag != 0) // external changes prevail { tag = newTag; } } int TextChat::getTag() const { return tag; } void TextChat::resetTag() { tag = -1; } bool TextChat::setNodeUserAccess(handle h, handle uh, bool revoke) { if (revoke) { attachments_map::iterator uhit = attachedNodes.find(h); if (uhit != attachedNodes.end()) { uhit->second.erase(uh); if (uhit->second.empty()) { attachedNodes.erase(h); changed.attachments = true; } return true; } } else { attachedNodes[h].insert(uh); changed.attachments = true; return true; } return false; } bool TextChat::setFlags(byte newFlags) { if (flags == newFlags) { return false; } flags = newFlags; changed.flags = true; return true; } bool TextChat::isFlagSet(uint8_t offset) const { return (flags >> offset) & 1U; } void TextChat::clearUpdatedSchedMeetingOccurrences() { mUpdatedOcurrences.clear(); } void TextChat::addUpdatedSchedMeetingOccurrence(std::unique_ptr sm) { if (!sm) { return; } mUpdatedOcurrences.emplace_back(std::move(sm)); } const ScheduledMeeting* TextChat::getSchedMeetingById(handle meetingID) const { auto it = mScheduledMeetings.find(meetingID); if (it != mScheduledMeetings.end()) { return it->second.get(); } return nullptr; } const map>& TextChat::getSchedMeetings() const { return mScheduledMeetings; } bool TextChat::addSchedMeeting(std::unique_ptr sm, bool notify) { if (!sm) { assert(false); return false; } if (id != sm->chatid()) { LOG_err << "addSchedMeeting: scheduled meeting chatid: " << toHandle(sm->chatid()) << " doesn't match with expected one: " << toHandle(id); assert(false); return false; } handle schedId = sm->schedId(); if (mScheduledMeetings.find(schedId) != mScheduledMeetings.end()) { LOG_err << "addSchedMeeting: scheduled meeting with id: " << Base64Str(schedId) << " already exits"; return false; } mScheduledMeetings.emplace(schedId, std::move(sm)); if (notify) { mSchedMeetingsChanged.insert(schedId); } return true; } bool TextChat::removeSchedMeeting(handle schedId) { assert(schedId != UNDEF); if (mScheduledMeetings.find(schedId) == mScheduledMeetings.end()) { return false; } deleteSchedMeeting(schedId); return true; } void TextChat::removeSchedMeetingsList(const handle_set& schedList) { for_each(begin(schedList), end(schedList), [this](handle sm) { deleteSchedMeeting(sm); }); } handle_set TextChat::removeChildSchedMeetings(handle parentSchedId) { // remove all scheduled meeting whose parent is parentSchedId handle_set deletedChildren; for (auto it = mScheduledMeetings.begin(); it != mScheduledMeetings.end(); it++) { if (it->second->parentSchedId() == parentSchedId) { deletedChildren.insert(it->second->schedId()); } } for_each(begin(deletedChildren), end(deletedChildren), [this](handle sm) { deleteSchedMeeting(sm); }); return deletedChildren; } bool TextChat::updateSchedMeeting(std::unique_ptr sm) { assert(sm); auto it = mScheduledMeetings.find(sm->schedId()); if (it == mScheduledMeetings.end()) { LOG_err << "updateSchedMeeting: scheduled meeting with id: " << Base64Str(sm->schedId()) << " no longer exists"; return false; } // compare current scheduled meeting with received from API if (!sm->equalTo(it->second.get())) { mSchedMeetingsChanged.insert(sm->schedId()); it->second = std::move(sm); } return true; } bool TextChat::addOrUpdateSchedMeeting(std::unique_ptr sm, bool notify) { if (!sm) { LOG_err << "addOrUpdateSchedMeeting: invalid scheduled meeting provided"; assert(false); return false; } if (sm->schedId() == UNDEF) { LOG_err << "addOrUpdateSchedMeeting: invalid schedid"; assert(false); return false; } return mScheduledMeetings.find(sm->schedId()) == mScheduledMeetings.end() ? addSchedMeeting(std::move(sm), notify) : updateSchedMeeting(std::move(sm)); } ErrorCodes TextChat::setMode(bool pubChat) { if (mPublicChat == pubChat) { return API_EEXIST; } if (pubChat) { return API_EACCESS; } // trying to convert a chat from private into public LOG_debug << "TextChat::setMode: EKR enabled (private chat) for chat: " << Base64Str(id); mPublicChat = pubChat; changed.mode = true; return API_OK; } bool TextChat::publicChat() const { return mPublicChat; } bool TextChat::addOrUpdateChatOptions(int speakRequest, int waitingRoom, int openInvite) { if (!group) { LOG_err << "addOrUpdateChatOptions: trying to update chat options for a non groupal chat: " << toNodeHandle(id); assert(false); return false; } ChatOptions currentOptions(static_cast(chatOptions)); if (speakRequest != -1) { currentOptions.updateSpeakRequest(speakRequest != 0); } if (waitingRoom != -1) { currentOptions.updateWaitingRoom(waitingRoom != 0); } if (openInvite != -1) { currentOptions.updateOpenInvite(openInvite != 0); } if (!currentOptions.isValid()) { LOG_err << "addOrUpdateChatOptions: options value (" << currentOptions.value() <<") is out of range"; assert(false); return false; } if (chatOptions != currentOptions.value()) // just set as changed if value is different` { chatOptions = currentOptions.value(); changed.options = true; } return true; } bool TextChat::setFlag(bool value, uint8_t offset) { if (bool((flags >> offset) & 1U) == value) { return false; } flags ^= byte(1U << offset); changed.flags = true; return true; } #endif } //namespace sdk-10.11.0/src/thread/000077500000000000000000000000001516266226600145055ustar00rootroot00000000000000sdk-10.11.0/src/thread/cppthread.cpp000066400000000000000000000045631516266226600171730ustar00rootroot00000000000000/** * @file posix/wait.cpp * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ #include "mega/thread/cppthread.h" #if defined USE_CPPTHREAD #include #include #ifdef _WIN32 #pragma push_macro("NOMINMAX") #ifndef NOMINMAX #define NOMINMAX #endif #include #pragma pop_macro("NOMINMAX") #endif namespace mega { //thread CppThread::CppThread() { thread = NULL; } void CppThread::start(void *(*start_routine)(void*), void *parameter) { thread = new std::thread(start_routine, parameter); } void CppThread::join() { thread->join(); } bool CppThread::isCurrentThread() { return thread->get_id() == std::this_thread::get_id(); } unsigned long long CppThread::currentThreadId() { #ifdef _WIN32 return (unsigned long long) GetCurrentThreadId(); #else return (unsigned long long) &errno; #endif } CppThread::~CppThread() { delete thread; } //semaphore CppSemaphore::CppSemaphore() { count = 0; } void CppSemaphore::release() { std::unique_lock lock(mtx); count++; cv.notify_one(); } void CppSemaphore::wait() { std::unique_lock lock(mtx); cv.wait(lock, [this]{return count > 0;}); count--; } int CppSemaphore::timedwait(int milliseconds) { std::chrono::system_clock::time_point endTime = std::chrono::system_clock::now() + std::chrono::milliseconds(milliseconds); std::unique_lock lock(mtx); while (!count) { auto status = cv.wait_until(lock, endTime); if (status == std::cv_status::timeout) { return -1; } } count--; return 0; } CppSemaphore::~CppSemaphore() { } } // namespace #endif sdk-10.11.0/src/thread/posixthread.cpp000066400000000000000000000077641516266226600175610ustar00rootroot00000000000000/** * @file posix/wait.cpp * @brief POSIX event/timeout handling * * (c) 2013-2014 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. * * This file is also distributed under the terms of the GNU General * Public License, see http://www.gnu.org/copyleft/gpl.txt for details. */ #include "mega/thread/posixthread.h" #include "mega/logging.h" #include #include #ifdef USE_PTHREAD // Apparently this is defined by pthread.h, if that header had been included. // __struct_timespec_defined is defined in time.h for MinGW on Windows #if defined (__MINGW32__) && !defined(_TIMESPEC_DEFINED) && ! __struct_timespec_defined struct timespec { long long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; # define __struct_timespec_defined 1 #endif namespace mega { PosixThread::PosixThread() { thread = new pthread_t(); } void PosixThread::start(void *(*start_routine)(void*), void *parameter) { pthread_create(thread, NULL, start_routine, parameter); } void PosixThread::join() { pthread_join(*thread, NULL); } bool PosixThread::isCurrentThread() { return *thread == pthread_self(); } unsigned long long PosixThread::currentThreadId() { #if defined(_WIN32) && !defined(__WINPTHREADS_VERSION) return (unsigned long long) pthread_self().x; #else return (unsigned long long) pthread_self(); #endif } PosixThread::~PosixThread() { delete thread; } //PosixSemaphore PosixSemaphore::PosixSemaphore() { count = 0; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutex_init(&mtx, &attr); pthread_mutexattr_destroy(&attr); pthread_cond_init(&cv, NULL); } void PosixSemaphore::wait() { pthread_mutex_lock(&mtx); while (!count) { int ret = pthread_cond_wait(&cv,&mtx); if (ret) { pthread_mutex_unlock(&mtx); LOG_fatal << "Error in sem_wait: " << ret; return; } } count--; pthread_mutex_unlock(&mtx); } static inline void timespec_add_msec(struct timespec *tv, int milliseconds) { int seconds = milliseconds / 1000; int milliseconds_left = milliseconds % 1000; tv->tv_sec += seconds; tv->tv_nsec += milliseconds_left * 1000000; if (tv->tv_nsec >= 1000000000) { tv->tv_nsec -= 1000000000; tv->tv_sec++; } } int PosixSemaphore::timedwait(int milliseconds) { struct timespec ts; struct timeval now; int ret = gettimeofday(&now, NULL); //not Y2K38 safe :-D if (ret) { LOG_err << "Error in gettimeofday: " << ret; return -2; } ts.tv_sec = now.tv_sec; ts.tv_nsec = now.tv_usec * 1000; timespec_add_msec (&ts, milliseconds); pthread_mutex_lock(&mtx); while (!count) { int ret = pthread_cond_timedwait(&cv, &mtx, &ts); if (ret == ETIMEDOUT) { pthread_mutex_unlock(&mtx); return -1; } if (ret) { pthread_mutex_unlock(&mtx); LOG_err << "Unexpected error in pthread_cond_timedwait: " << ret; return -2; } } count--; pthread_mutex_unlock(&mtx); return 0; } void PosixSemaphore::release() { pthread_mutex_lock(&mtx); count++; int ret = pthread_cond_signal(&cv); if (ret) { LOG_fatal << "Unexpected error in pthread_cond_signal: " << ret; } pthread_mutex_unlock(&mtx); } PosixSemaphore::~PosixSemaphore() { pthread_mutex_destroy(&mtx); pthread_cond_destroy(&cv); } }// namespace #endif sdk-10.11.0/src/tlv.cpp000066400000000000000000000243621516266226600145560ustar00rootroot00000000000000#include "mega/tlv.h" #include "mega/logging.h" #include "mega/scoped_helpers.h" #include "mega/types.h" using namespace std; namespace mega { namespace tlv { unique_ptr> containerToRecords(const string& container, SymmCipher& key) { auto tlv = makeUniqueFrom(TLVstore::containerToTLVrecords(&container, &key)); return tlv ? std::make_unique>(tlv->moveMap()) : nullptr; } unique_ptr> containerToRecords(const string& container) { auto tlv = makeUniqueFrom(TLVstore::containerToTLVrecords(&container)); return tlv ? std::make_unique>(tlv->moveMap()) : nullptr; } unique_ptr recordsToContainer(std::map&& records, PrnGen& rng, SymmCipher& key) { TLVstore tlv; tlv.set(std::move(records)); return unique_ptr{tlv.tlvRecordsToContainer(rng, &key)}; } unique_ptr recordsToContainer(std::map&& records) { TLVstore tlv; tlv.set(std::move(records)); return unique_ptr{tlv.tlvRecordsToContainer()}; } //========================================= // Old implementation // Direct use should be avoided in new code string* TLVstore::tlvRecordsToContainer(PrnGen& rng, SymmCipher* key, encryptionsetting_t encSetting) { // decide nonce/IV and auth. tag lengths based on the `mode` unsigned ivlen = TLVstore::getIvlen(encSetting); unsigned taglen = TLVstore::getTaglen(encSetting); encryptionmode_t encMode = TLVstore::getMode(encSetting); if (!ivlen || !taglen || encMode == AES_MODE_UNKNOWN) { return NULL; } // serialize the TLV records std::unique_ptr container(tlvRecordsToContainer()); // generate IV array std::vector iv(ivlen); rng.genblock(iv.data(), ivlen); string cipherText; // encrypt the bytes using the specified mode if (encMode == AES_MODE_CCM) // CCM or GCM_BROKEN (same than CCM) { if (!key->ccm_encrypt(container.get(), iv.data(), ivlen, taglen, &cipherText)) { return nullptr; } } else if (encMode == AES_MODE_GCM) // then use GCM { if (!key->gcm_encrypt(container.get(), iv.data(), ivlen, taglen, &cipherText)) { return nullptr; } } string* result = new string; result->resize(1); result->at(0) = static_cast(encSetting); result->append((char*)iv.data(), ivlen); result->append((char*)cipherText.data(), cipherText.length()); // includes auth. tag return result; } string* TLVstore::tlvRecordsToContainer() { string* result = new string; size_t offset = 0; size_t length; for (TLV_map::iterator it = tlv.begin(); it != tlv.end(); it++) { // copy Type result->append(it->first); offset += it->first.length() + 1; // keep the NULL-char for Type string // set Length of value length = it->second.length(); if (length > 0xFFFF) { assert(it->first == "" && tlv.size() == 1 && "Only records with a single empty key can be larger"); LOG_warn << "Overflow of Length for TLV record: " << length; length = 0xFFFF; } result->resize(offset + 2); result->at(offset) = static_cast(length >> 8); result->at(offset + 1) = static_cast(length & 0xFF); offset += 2; // copy the Value result->append((char*)it->second.data(), it->second.length()); offset += it->second.length(); } return result; } bool TLVstore::get(const string& type, string& value) const { auto it = tlv.find(type); if (it == tlv.cend()) return false; value = it->second; return true; } const TLV_map* TLVstore::getMap() const { return &tlv; } TLV_map TLVstore::moveMap() { TLV_map moved; moved.swap(tlv); return moved; } vector* TLVstore::getKeys() const { vector* keys = new vector; for (string_map::const_iterator it = tlv.begin(); it != tlv.end(); it++) { keys->push_back(it->first); } return keys; } void TLVstore::set(const string& type, const string& value) { tlv[type] = value; } void TLVstore::set(TLV_map&& records) { tlv.swap(records); } void TLVstore::reset(const string& type) { tlv.erase(type); } size_t TLVstore::size() const { return tlv.size(); } unsigned TLVstore::getTaglen(int mode) { switch (mode) { case AES_CCM_10_16: case AES_CCM_12_16: case AES_GCM_12_16_BROKEN: case AES_GCM_12_16: return 16; case AES_CCM_10_08: case AES_GCM_10_08_BROKEN: case AES_GCM_10_08: return 8; default: // unknown block encryption mode return 0; } } unsigned TLVstore::getIvlen(int mode) { switch (mode) { case AES_CCM_12_16: case AES_GCM_12_16_BROKEN: case AES_GCM_12_16: return 12; case AES_CCM_10_08: case AES_GCM_10_08_BROKEN: case AES_CCM_10_16: case AES_GCM_10_08: return 10; default: // unknown block encryption mode return 0; } } encryptionmode_t TLVstore::getMode(int mode) { switch (mode) { case AES_CCM_12_16: case AES_GCM_12_16_BROKEN: case AES_CCM_10_16: case AES_CCM_10_08: case AES_GCM_10_08_BROKEN: return AES_MODE_CCM; case AES_GCM_12_16: case AES_GCM_10_08: return AES_MODE_GCM; default: // unknown block encryption mode return AES_MODE_UNKNOWN; } } TLVstore* TLVstore::containerToTLVrecords(const string* data) { if (data->empty()) { return NULL; } TLVstore* tlv = new TLVstore(); size_t offset = 0; string type; size_t typelen; string value; unsigned valuelen; size_t pos; size_t datalen = data->length(); // T is a C-string // L is an unsigned integer encoded in 2 bytes (aka. uint16_t) // V is a binary buffer of "L" bytes // if the size of the container is greater than the 65538 (T.1, L.2, V.>65535), // then the container is probably an authring whose value is greater than the // maximum length supported by 2 bytes. Since the T is an empty string for this // type of attributes, we can directly assign the value to the record // note: starting from Nov2022, if the size of the record is too large, // the Length will be truncated to indicate the maximum size 65535 = 0xFFFF size_t maxSize = 1 + 2 + 0xFFFF; if (datalen >= maxSize && !(*data)[0]) { tlv->set("", data->substr(3)); return tlv; } while (offset < datalen) { // get the length of the Type string pos = data->find('\0', offset); typelen = pos - offset; // if no valid TLV record in the container, but remaining bytes... if (pos == string::npos || offset + typelen + 3 > datalen) { delete tlv; return NULL; } // get the Type string type.assign((char*)&(data->data()[offset]), typelen); offset += typelen + 1; // +1: NULL character // get the Length of the value valuelen = static_cast((unsigned char)data->at(offset) << 8 | (unsigned char)data->at(offset + 1)); offset += 2; // if there's not enough data for value... if (offset + valuelen > datalen) { delete tlv; return NULL; } // get the Value value.assign((char*)&(data->data()[offset]), valuelen); // value may include NULL characters, read as a buffer offset += valuelen; // add it to the map tlv->set(type, value); } return tlv; } TLVstore* TLVstore::containerToTLVrecords(const string* data, SymmCipher* key) { if (data->empty()) { return NULL; } unsigned offset = 0; encryptionsetting_t encSetting = (encryptionsetting_t)data->at(offset); offset++; unsigned ivlen = TLVstore::getIvlen(encSetting); unsigned taglen = TLVstore::getTaglen(encSetting); encryptionmode_t encMode = TLVstore::getMode(encSetting); if (encMode == AES_MODE_UNKNOWN || !ivlen || !taglen || data->size() < offset + ivlen + taglen) { return NULL; } byte* iv = new byte[ivlen]; memcpy(iv, &(data->data()[offset]), ivlen); offset += ivlen; unsigned cipherTextLen = unsigned(data->length() - offset); string cipherText = data->substr(offset, cipherTextLen); unsigned clearTextLen = cipherTextLen - taglen; string clearText; bool decrypted = false; if (encMode == AES_MODE_CCM) // CCM or GCM_BROKEN (same than CCM) { decrypted = key->ccm_decrypt(&cipherText, iv, ivlen, taglen, &clearText); } else if (encMode == AES_MODE_GCM) // GCM { decrypted = key->gcm_decrypt(&cipherText, iv, ivlen, taglen, &clearText); } delete[] iv; if (!decrypted) // the decryption has failed (probably due to authentication) { return NULL; } else if (clearText .empty()) // If decryption succeeded but attribute is empty, generate an empty TLV { return new TLVstore(); } TLVstore* tlv = TLVstore::containerToTLVrecords(&clearText); if (!tlv) // 'data' might be affected by the legacy bug: strings encoded in UTF-8 instead of // Unicode { // retry TLV decoding after conversion from 'UTF-8 chars' to 'Unicode chars' LOG_warn << "Retrying TLV records decoding with UTF-8 patch"; string clearTextUnicode; if (!Utils::utf8toUnicode((const byte*)clearText.data(), clearTextLen, &clearTextUnicode)) { LOG_err << "Invalid UTF-8 encoding"; } else { tlv = TLVstore::containerToTLVrecords(&clearTextUnicode); } } return tlv; } } // namespace tlv } // namespace mega sdk-10.11.0/src/totp.cpp000066400000000000000000000255671516266226600147470ustar00rootroot00000000000000#include "mega/totp.h" #include "mega/logging.h" #include #include #include #include #include #include #include #include #include namespace mega::totp { using namespace std::chrono; // convenience in this compilation unit namespace { using CryptoPP::SecByteBlock; constexpr bool isValidBase32Digit(const char c) { return ('2' <= c && c <= '7'); } /** * @brief Check if c is in "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" */ bool isValidBase32Char(const char c) { return std::isalpha(c) != 0 || isValidBase32Digit(c); } static constexpr char PADDING_CHAR{'='}; constexpr bool isPaddingChar(const char c) { return c == '='; } /** * @brief Given a char returns its index in the string "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" * @warning The function assumes the input char is a valid one, if not, 0 is returned. */ unsigned base32CharValue(const char c) { if (isalpha(c) != 0) return static_cast((c - 1) % 32); if (isValidBase32Digit(c)) return static_cast(c - 24); assert(false); return 0; } /** * @brief Converts a Base32-encoded string into a SecByteBlock of bytes. * * This function decodes a Base32 string by mapping each character to its * corresponding 5-bit value. These 5-bit groups are accumulated into a buffer. * Whenever at least 8 bits are available in the buffer, the function extracts * the top 8 bits to form a byte, which is then appended to the output SecByteBlock. * Any remaining space in the SecByteBlock is filled with zeros. * * @param base32Str A string containing the Base32-encoded data. * @return SecByteBlock containing the decoded byte sequence. */ SecByteBlock toByteBlock(const std::string& base32Str) { SecByteBlock bytes(base32Str.size() * 5 / 8); // truncate size unsigned byteIndex = 0; const auto mapAndAppend = [&bytes, &byteIndex, bits = 0u, bitCount = 0u](const char c) mutable { if (isPaddingChar(c)) return; const auto cValue = base32CharValue(c); bits = (bits << 5) | cValue; bitCount += 5; if (bitCount >= 8) bytes[byteIndex++] = static_cast(bits >> (bitCount -= 8)); }; std::for_each(begin(base32Str), end(base32Str), mapAndAppend); std::fill(std::begin(bytes) + byteIndex, std::end(bytes), 0); // just in case return bytes; } /** * @brief Returns the expected number of bytes for the output token returned by the HMAC routine * depending on the used hashing algorithm. */ template constexpr size_t getHmacOutNBytes() { if constexpr (std::is_same_v) return 20; if constexpr (std::is_same_v) return 32; if constexpr (std::is_same_v) return 64; // Compilation error if not supported Hash algorithm } bool isLittleEndianHost() { constexpr std::uint32_t endianness = 0xdeadbeef; return *reinterpret_cast(&endianness) == 0xef; } /** * @brief Maps to what is denoted by HMAC-SHA-???(K, C) in RFC-4226 * (https://www.rfc-editor.org/rfc/rfc4226) * - K: `secret` * - C: `counter` * * To get the final token, truncate the result */ template std::vector hotpBytes(const SecByteBlock& secret, const std::int64_t counter) { std::array counterArray; std::memcpy(counterArray.data(), &counter, sizeof(counter)); if (isLittleEndianHost()) // Ensure big endian std::reverse(std::begin(counterArray), std::end(counterArray)); std::vector result(getHmacOutNBytes()); CryptoPP::HMAC mac; mac.SetKey(secret.data(), secret.size()); mac.CalculateDigest(&result[0], counterArray.data(), 8); return result; } /** * @brief Dispatcher to call the correct implementation depending on the given `hashAlgo` */ std::vector hotpBytes(const SecByteBlock& secret, const std::int64_t counter, const HashAlgorithm hashAlgo) { switch (hashAlgo) { case HashAlgorithm::SHA1: return hotpBytes(secret, counter); case HashAlgorithm::SHA256: return hotpBytes(secret, counter); case HashAlgorithm::SHA512: return hotpBytes(secret, counter); } // Silence compilation warning return hotpBytes(secret, counter); } /** * @brief Maps to what is denoted by HMAC-SHA-???(K, T) in the RFC-6238 * - K: `secret` * - T: The number of `timeStep`s passed in the given timeDelta * * To get the final token, truncate the result */ std::vector totpBytes(const SecByteBlock& secret, const seconds timeStep, const seconds timeDelta, const HashAlgorithm hashAlgo) { const std::int64_t timeCounter = timeDelta.count() / timeStep.count(); return hotpBytes(secret, timeCounter, hashAlgo); } /** * @brief See RFC-4226 Sec. 5.4. (https://www.rfc-editor.org/rfc/rfc4226) */ std::int32_t dynamicOffsetTruncation(const std::vector& hmac) { const auto offset = static_cast(hmac[hmac.size() - 1] & 0xf); const std::int32_t bincode = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) | ((hmac[offset + 2] & 0xff) << 8) | ((hmac[offset + 3] & 0xff) << 0); return bincode; } std::int32_t moduloReduction(const std::int32_t bincode, const unsigned nDigits) { if (nDigits == NDIGITS_IN_MAX_INT32) return bincode; constexpr std::array powersOf10upTo9{ 1, 10, 100, 1'000, 10'000, 100'000, 1'000'000, 10'000'000, 100'000'000, 1'000'000'000, }; return bincode % powersOf10upTo9.at(nDigits); } int32_t truncate(const std::vector& hmac, const unsigned nDigits) { return moduloReduction(dynamicOffsetTruncation(hmac), nDigits); } /** * @brief Converts the given `number` in a string, padding '0' at front until `nDigits` characters */ std::string to0PaddedStr(const int32_t number, const unsigned nDigits) { std::ostringstream oss; oss << std::setw(static_cast(nDigits)) << std::setfill('0') << number; return oss.str(); } bool areInputsValid(const std::string& base32Key, const seconds timeDelta, const unsigned nDigits, const seconds timeStep) { bool areValid = validateFields(base32Key, nDigits, timeStep, std::nullopt).none(); if (timeDelta.count() < 0) { LOG_err << "Invalid time delta (negative): " << timeDelta.count(); areValid = false; } return areValid; } seconds getRemainingValidTime(const seconds timeDelta, const seconds timeStep) { return timeStep - timeDelta % timeStep; } } size_t numberOfValidChars(const std::string_view base32Key) { return static_cast(std::count_if(begin(base32Key), end(base32Key), isValidBase32Char)); } bool isValidBase32Key(const std::string_view base32Key) { if (const auto pos = base32Key.find(PADDING_CHAR); pos != std::string_view::npos) { const auto endSlice = base32Key.substr(pos); return isValidBase32Key(base32Key.substr(0, pos)) && std::all_of(begin(endSlice), end(endSlice), isPaddingChar); } return std::all_of(begin(base32Key), end(base32Key), isValidBase32Char); } std::pair generateTOTP(const std::string& base32Key, const unsigned nDigits, const seconds timeStep, const HashAlgorithm hashAlgo, const system_clock::time_point t0, const system_clock::time_point tEval) { return generateTOTP(base32Key, duration_cast(tEval - t0), nDigits, timeStep, hashAlgo); } std::pair generateTOTP(const TotpParameters& totpParams, const std::chrono::system_clock::time_point t0, const std::chrono::system_clock::time_point tEval) { return generateTOTP(totpParams.base32Key, totpParams.nDigits, totpParams.expirationTime, totpParams.hashAlgo, t0, tEval); } std::pair generateTOTP(const std::string& base32Key, const seconds timeDelta, const unsigned nDigits, const seconds timeStep, const HashAlgorithm hashAlgo) { if (!areInputsValid(base32Key, timeDelta, nDigits, timeStep)) return {}; const auto byteBlockKey = toByteBlock(base32Key); return {to0PaddedStr(truncate(totpBytes(byteBlockKey, timeStep, timeDelta, hashAlgo), nDigits), nDigits), getRemainingValidTime(timeDelta, timeStep)}; } TotpValidationErrors validateFields(const std::optional base32Key, const std::optional nDigits, const std::optional exptime, const std::optional alg) { TotpValidationErrors totpValidationErrors{0}; if (base32Key && (base32Key->empty() || numberOfValidChars(*base32Key) == 0)) { if (base32Key->empty()) LOG_err << "Empty input shared secret"; else LOG_err << "The input has no valid chars"; totpValidationErrors[INVALID_TOTP_SHARED_SECRET] = true; } if (base32Key && !isValidBase32Key(*base32Key)) { LOG_err << "Input shared secret contains invalid characters: " << *base32Key; totpValidationErrors[INVALID_TOTP_SHARED_SECRET] = true; } if (nDigits && !isValidNDigits(*nDigits)) { LOG_err << "Invalid number of digits (allowed between " << MIN_ALLOWED_DIGITS_TOTP << " and " << MAX_ALLOWED_DIGITS_TOTP << "). Given: " << *nDigits; totpValidationErrors[INVALID_TOTP_NDIGITS] = true; } if (exptime && *exptime <= 0s) { LOG_err << "Invalid time step (expected value greater than 0): " << exptime->count(); totpValidationErrors[INVALID_TOTP_EXPT] = true; } if (alg && !charTohashAlgorithm(*alg)) { LOG_err << "Invalid time delta (negative): " << *alg; totpValidationErrors[INVALID_TOTP_ALG] = true; } return totpValidationErrors; } } sdk-10.11.0/src/transfer.cpp000066400000000000000000003446241516266226600156030ustar00rootroot00000000000000/** * @file transfer.cpp * @brief Pending/active up/download ordered by file fingerprint * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/transfer.h" #include "mega/base64.h" #include "mega/logging.h" #include "mega/mediafileattribute.h" #include "mega/megaapp.h" #include "mega/megaclient.h" #include "mega/sync.h" #include "mega/testhooks.h" #include "mega/transferslot.h" #include "mega/types.h" #include "mega/utils.h" #include "megawaiter.h" namespace mega { TransferCategory::TransferCategory(direction_t d, filesizetype_t s) : direction(d) , sizetype(s) { } TransferCategory::TransferCategory(Transfer* t) : direction(t->type) , sizetype(t->size > 131072 ? LARGEFILE : SMALLFILE) // Conservative starting point: 131072 is the smallest chunk, we will certainly only use one socket to upload/download { } unsigned TransferCategory::index() { assert(direction == GET || direction == PUT); assert(sizetype == LARGEFILE || sizetype == SMALLFILE); return 2 + direction * 2 + sizetype; } unsigned TransferCategory::directionIndex() { assert(direction == GET || direction == PUT); return direction; } Transfer::Transfer(MegaClient* cclient, direction_t ctype) : bt(cclient->rng, cclient->transferRetryBackoffs[ctype]) { type = ctype; client = cclient; size = 0; failcount = 0; pos = 0; ctriv = 0; metamac = 0; tag = 0; slot = NULL; asyncopencontext = NULL; setProgresscompleted(0); finished = false; lastaccesstime = 0; ultoken = NULL; priority = 0; state = TRANSFERSTATE_NONE; skipserialization = false; transfers_it = client->multi_transfers[type].end(); } // delete transfer with underlying slot, notify files Transfer::~Transfer() { auto keepDownloadTarget = false; TransferDbCommitter* committer = nullptr; if (client->tctable && client->tctable->getTransactionCommitter()) { committer = dynamic_cast(client->tctable->getTransactionCommitter()); assert(committer); } if (!uploadhandle.isUndef()) { client->fileAttributesUploading.erase(uploadhandle); } for (file_list::iterator it = files.begin(); it != files.end(); it++) { if (finished) { client->filecachedel(*it, nullptr); } (*it)->transfer = NULL; auto terminatedErrCode = API_OK; if (type == GET) { #ifdef ENABLE_SYNC if (auto dl = dynamic_cast(*it)) { assert((*it)->syncxfer); // Let's forward the error from previous possible failed() calls to the terminated // command instad of terminating with API_OK terminatedErrCode = dl->mError; // Keep sync downloads whose Mac failed, so the user can decide to keep them or not if (dl->mError == API_EKEY) { keepDownloadTarget = true; dl->setLocalname(localfilename); } } else #endif { assert(!(*it)->syncxfer); if (downloadDistributor) downloadDistributor->removeTarget(); } } // this File may be deleted by this call. So call after the tests above (*it)->terminated(terminatedErrCode); } if (!mOptimizedDelete) { if (transfers_it != client->multi_transfers[type].end()) { client->multi_transfers[type].erase(transfers_it); } client->transferlist.removetransfer(this); } if (slot) { delete slot; } if (asyncopencontext) { asyncopencontext.reset(); client->asyncfopens--; } if (finished) { if (type == GET && !localfilename.empty()) { if (!keepDownloadTarget) client->fsaccess->unlinklocal(localfilename); } client->transfercachedel(this, committer); } } bool Transfer::serialize(string *d) const { assert(localfilename.empty() || localfilename.isAbsolute()); unsigned short ll; d->append((const char*)&type, sizeof(type)); const auto& tmpstr = localfilename.serialize(); ll = (unsigned short)tmpstr.size(); d->append((char*)&ll, sizeof(ll)); d->append(tmpstr.data(), ll); d->append((const char*)&filekey.bytes, sizeof(filekey.bytes)); d->append((const char*)&ctriv, sizeof(ctriv)); d->append((const char*)&metamac, sizeof(metamac)); d->append((const char*)transferkey.data(), sizeof (transferkey)); chunkmacs.serialize(*d); if (!FileFingerprint::serialize(d)) { LOG_err << "Error serializing Transfer: Unable to serialize FileFingerprint"; return false; } if (!badfp.serialize(d)) { LOG_err << "Error serializing Transfer: Unable to serialize badfp"; return false; } d->append((const char*)&lastaccesstime, sizeof(lastaccesstime)); char hasUltoken; if (ultoken) { hasUltoken = 2; d->append((const char*)&hasUltoken, sizeof(char)); d->append((const char*)ultoken.get(), UPLOADTOKENLEN); } else { hasUltoken = 0; d->append((const char*)&hasUltoken, sizeof(char)); } // store raid URL string(s) in the same record as non-raid, 0-delimited in the case of raid std::string combinedUrls; for (std::vector::const_iterator i = tempurls.begin(); i != tempurls.end(); ++i) { if (i != tempurls.begin()) { combinedUrls += '\0'; // '\0' separator } combinedUrls.append(*i); } ll = (unsigned short)combinedUrls.size(); d->append((char*)&ll, sizeof(ll)); d->append(combinedUrls.data(), ll); char s = static_cast(state); d->append((const char*)&s, sizeof(s)); d->append((const char*)&priority, sizeof(priority)); CacheableWriter cw(*d); // version. Originally, 0. Version 1 adds expansion flags, which then work in the usual way cw.serializeu8(1); // 8 expansion flags, in the normal manner. First flag is for whether downloadFileHandle is // present. Second flag is for amount of discarded temp URLs. Third Flag is for marking if // localfilename is serialized as LocalPath cw.serializeexpansionflags(downloadFileHandle.isUndef() ? 0 : 1, 1, 1); if (!downloadFileHandle.isUndef()) { cw.serializeNodeHandle(downloadFileHandle); } cw.serializeu8(discardedTempUrlsSize); #ifdef DEBUG // very quick debug only double check string tempstr = *d; transfer_multimap tempmap[2]; unique_ptr t(unserialize(client, &tempstr, tempmap)); assert(t); assert(t->localfilename == localfilename); assert(t->tempurls == tempurls); assert(t->state == (state == TRANSFERSTATE_PAUSED ? TRANSFERSTATE_PAUSED : TRANSFERSTATE_NONE)); assert(t->priority == priority); assert(t->fingerprint() == fingerprint() || (!t->fingerprint().isvalid && !fingerprint().isvalid)); assert(t->badfp == badfp || (!t->badfp.isvalid && !badfp.isvalid)); assert(t->downloadFileHandle == downloadFileHandle); assert(t->discardedTempUrlsSize == discardedTempUrlsSize); #endif return true; } Transfer *Transfer::unserialize(MegaClient *client, string *d, transfer_multimap* multi_transfers) { CacheableReader r(*d); direction_t type; string filepath; if (!r.unserializedirection(type) || (type != GET && type != PUT) || !r.unserializestring(filepath)) { assert(false); LOG_err << "Transfer unserialization failed at field " << r.fieldnum; return nullptr; } unique_ptr t(new Transfer(client, type)); int8_t hasUltoken; // value 1 was for OLDUPLOADTOKENLEN, but that was from 2016 if (!r.unserializebinary(t->filekey.bytes.data(), sizeof(t->filekey)) || !r.unserializei64(t->ctriv) || !r.unserializei64(t->metamac) || !r.unserializebinary(t->transferkey.data(), SymmCipher::KEYLENGTH) || !r.unserializechunkmacs(t->chunkmacs) || !r.unserializefingerprint(*t) || !r.unserializefingerprint(t->badfp) || !r.unserializei64(t->lastaccesstime) || !r.unserializei8(hasUltoken) || (hasUltoken && hasUltoken != 2)) { LOG_err << "Transfer unserialization failed at field " << r.fieldnum; return nullptr; } if (hasUltoken) { t->ultoken.reset(new UploadToken); } unsigned char expansionflags[8] = { 0 }; std::string combinedUrls; int8_t state; int8_t version; if ((hasUltoken && !r.unserializebinary(t->ultoken->data(), UPLOADTOKENLEN)) || !r.unserializestring(combinedUrls) || !r.unserializei8(state) || !r.unserializeu64(t->priority) || !r.unserializei8(version) || (version > 0 && !r.unserializeexpansionflags(expansionflags, 3)) || (expansionflags[0] && !r.unserializeNodeHandle(t->downloadFileHandle)) || (expansionflags[1] && !r.unserializeu8(t->discardedTempUrlsSize))) { LOG_err << "Transfer unserialization failed at field " << r.fieldnum; return nullptr; } assert(!r.hasdataleft()); if (!filepath.empty()) { if (const bool isLocalPath = expansionflags[2] == 1; isLocalPath) { auto localPath = LocalPath::unserialize(filepath); if (localPath.has_value()) { t->localfilename = localPath.value(); } } else { t->localfilename = LocalPath::fromPlatformEncodedAbsolute(filepath); } } size_t ll = combinedUrls.size(); for (size_t p = 0; p < ll; ) { if (size_t n = combinedUrls.find('\0', p); n == std::string::npos) { t->tempurls.push_back(combinedUrls.substr(p)); p = ll; } else { t->tempurls.push_back(combinedUrls.substr(p, n - p)); p = n + 1; } assert(!t->tempurls.back().empty()); } assert(!t->discardedTempUrlsSize || !t->tempurls.empty()); if (!t->tempurls.empty() && t->tempurls.size() != 1 && t->tempurls.size() != RAIDPARTS) { LOG_err << "Transfer unserialization failed - temp URL incorrect components"; return nullptr; } if (state == TRANSFERSTATE_PAUSED) { LOG_debug << "Unserializing paused transfer"; t->state = TRANSFERSTATE_PAUSED; } m_off_t pCompleted{0}; t->chunkmacs.calcprogress(t->size, t->pos, pCompleted); t->setProgresscompleted(pCompleted); multi_transfers[type].insert(pair(t.get(), t.get())); return t.release(); } SymmCipher *Transfer::transfercipher() { return client->getRecycledTemporaryTransferCipher(transferkey.data()); } void Transfer::removeCancelledTransferFiles(TransferDbCommitter* committer) { // remove transfer files whose MegaTransfer associated has been cancelled (via cancel token) for (file_list::iterator it = files.begin(); it != files.end();) { file_list::iterator auxit = it++; if ((*auxit)->cancelToken.isCancelled()) { removeTransferFile(API_EINCOMPLETE, *auxit, committer); } } } void Transfer::removeTransferFile(error e, File* f, TransferDbCommitter* committer) { Transfer *transfer = f->transfer; client->filecachedel(f, committer); assert(*f->file_it == f); transfer->files.erase(f->file_it); client->app->file_removed(f, e); f->transfer = NULL; f->terminated(e); } void Transfer::removeAndDeleteSelf(transferstate_t finalState) { finished = true; state = finalState; client->app->transfer_removed(this); // this will also remove the transfer from internal lists etc. // those use a lazy delete (ie mark for later deletion) so that we don't invalidate iterators. delete this; } void Transfer::setProgresscompleted(const m_off_t p, const bool append) { progresscompleted = !append ? p : progresscompleted + p; DEBUG_TEST_HOOK_ON_PROGRESS_COMPLETED_UPDATE(progresscompleted); } // transfer attempt failed, notify all related files, collect request on // whether to abort the transfer, kill transfer if unanimous void Transfer::failed(const Error& e, TransferDbCommitter& committer, dstime timeleft) { bool defer = false; LOG_debug << "Transfer failed with error " << e; DEBUG_TEST_HOOK_DOWNLOAD_FAILED(e); if (e == API_EOVERQUOTA || e == API_EPAYWALL) { assert((e == API_EPAYWALL && !timeleft) || (type == PUT && !timeleft) || (type == GET && timeleft)); // overstorage only possible for uploads, overbandwidth for downloads if (!slot) { bt.backoff(timeleft ? timeleft : NEVER); client->activateoverquota(timeleft, (e == API_EPAYWALL)); client->app->transfer_failed(this, e, timeleft); ++client->performanceStats.transferTempErrors; } else { bool allForeignTargets = true; for (auto &file : files) { if (client->isPrivateNode(file->h)) { allForeignTargets = false; break; } } /* If all targets are foreign and there's not a bandwidth overquota, transfer must fail. * Otherwise we need to activate overquota. */ if (!timeleft && allForeignTargets) { client->app->transfer_failed(this, e); } else { bt.backoff(timeleft ? timeleft : NEVER); client->activateoverquota(timeleft, (e == API_EPAYWALL)); } } } else if (e == API_EARGS || (e == API_EBLOCKED && type == GET) || (e == API_ETOOMANY && type == GET && e.hasExtraInfo()) || (e == API_ESUBUSERKEYMISSING)) { assert(e != API_ESUBUSERKEYMISSING || type == PUT); client->app->transfer_failed(this, e); } else if (e != API_EBUSINESSPASTDUE) { bt.backoff(); state = TRANSFERSTATE_RETRYING; client->app->transfer_failed(this, e, timeleft); ++client->performanceStats.transferTempErrors; } for (file_list::iterator it = files.begin(); it != files.end();) { // Remove files with foreign targets, if transfer failed with a (foreign) storage overquota if (e == API_EOVERQUOTA && ((*it)->isFuseTransfer() || (!timeleft && client->isForeignNode((*it)->h)))) { removeTransferFile(e, *it++, &committer); continue; } /* * If the transfer failed with API_EARGS, the target handle is invalid. For a sync-transfer, * the actionpacket will eventually remove the target and the sync-engine will force to * disable the synchronization of the folder. For non-sync-transfers, remove the file directly. */ if (e == API_EARGS || (e == API_EBLOCKED && type == GET) || (e == API_ETOOMANY && type == GET && e.hasExtraInfo()) || (e == API_ESUBUSERKEYMISSING)) { File *f = (*it++); if (f->syncxfer && e == API_EARGS) { defer = true; } else { removeTransferFile(e, f, &committer); } continue; } if (((*it)->failed(e, client) && (e != API_EBUSINESSPASTDUE)) || (e == API_ENOENT // putnodes returned -9, file-storage server unavailable && type == PUT && tempurls.empty() && failcount < 16) ) { defer = true; } it++; } tempurls.clear(); if (type == PUT) { chunkmacs.clear(); setProgresscompleted(0); ultoken.reset(); pos = 0; if (slot && slot->fa) { if (!slot->fa->fopenSucceeded) { LOG_warn << "fopen failed for upload."; defer = false; } else if (slot->fa->mtime != mtime || slot->fa->size != size) { LOG_warn << "Modification detected during active upload. Size: " << size << " Mtime: " << mtime << " FaSize: " << slot->fa->size << " FaMtime: " << slot->fa->mtime; defer = false; } } } if (defer) { failcount++; delete slot; slot = NULL; client->transfercacheadd(this, &committer); LOG_debug << "Deferring transfer " << failcount << " during " << (bt.retryin() * 100) << " ms" << " [this = " << this << "]"; } else { LOG_debug << "Removing transfer" << " [this = " << this << "]"; state = TRANSFERSTATE_FAILED; finished = true; #ifdef ENABLE_SYNC if (e == API_EBUSINESSPASTDUE) { LOG_debug << "Disabling syncs on account of API_EBUSINESSPASTDUE error on transfer"; client->syncs.disableSyncs(ACCOUNT_EXPIRED, false, true); } #endif for (file_list::iterator it = files.begin(); it != files.end(); it++) { #ifdef ENABLE_SYNC if((*it)->syncxfer && e != API_EBUSINESSPASTDUE && e != API_EOVERQUOTA && e != API_EPAYWALL) { // Get the sync to check that folder again so it doesn't just recreate that same transfer LOG_debug << "Trigger sync parent path scan for failed transfer of " << (*it)->getLocalname(); client->syncs.triggerSync((*it)->getLocalname().parentPath(), type == PUT); } #endif client->app->file_removed(*it, e); } client->app->transfer_removed(this); ++client->performanceStats.transferFails; delete this; } } #ifdef USE_MEDIAINFO static uint32_t* fileAttributeKeyPtr(byte filekey[FILENODEKEYLENGTH]) { // returns the last half, beyond the actual key, ie the nonce+crc return (uint32_t*)(filekey + FILENODEKEYLENGTH / 2); } #endif void Transfer::addAnyMissingMediaFileAttributes([[maybe_unused]] Node* node, [[maybe_unused]] /*const*/ LocalPath& localpath) { assert(type == PUT || (node && node->type == FILENODE)); #ifdef USE_MEDIAINFO string ext; if (((type == PUT && size >= 16) || (node && node->nodekey().size() == FILENODEKEYLENGTH && node->size >= 16)) && client->fsaccess->getextension(localpath, ext) && MediaProperties::isMediaFilenameExt(ext) && !client->mediaFileInfo.mediaCodecsFailed) { // for upload, the key is in the transfer. for download, the key is in the node. uint32_t* attrKey = fileAttributeKeyPtr((type == PUT) ? filekey.bytes.data() : (byte*)node->nodekey().data()); if (type == PUT || !node->hasfileattribute(fa_media) || client->mediaFileInfo.timeToRetryMediaPropertyExtraction(node->fileattrstring, attrKey)) { // if we don't have the codec id mappings yet, send the request client->mediaFileInfo.requestCodecMappingsOneTime(client, LocalPath()); // always get the attribute string; it may indicate this version of the mediaInfo library was unable to interpret the file MediaProperties vp; vp.extractMediaPropertyFileAttributes(localpath, client->fsaccess.get()); if (type == PUT) { client->mediaFileInfo.queueMediaPropertiesFileAttributesForUpload(vp, attrKey, client, uploadhandle, this); } else { client->mediaFileInfo.sendOrQueueMediaPropertiesFileAttributesForExistingFile(vp, attrKey, client, node->nodeHandle()); } } } #endif } bool Transfer::isForSupport() const { return type == PUT && !files.empty() && files.back()->targetuser == MegaClient::SUPPORT_USER_HANDLE; } bool Transfer::addTransferStats() { if (!client) { LOG_err << "[Transfer::addTransferStats] called with a NULL MEGAclient"; assert(false && "[Transfer::addTransferStats] called with a NULL MEGAclient"); return false; } return client->mTransferStatsManager.addTransferStats(this); } void Transfer::collectAndPrintTransferStatsIfLimitReached() { if (!client) { LOG_err << "[Transfer::collectAndPrintTransferStatsIfLimitReached] called with a NULL " "MEGAclient"; assert( false && "[Transfer::collectAndPrintTransferStatsIfLimitReached] called with a NULL MEGAclient"); return; } client->mTransferStatsManager.collectAndPrintTransferStatsIfLimitReached(type); } void Transfer::discardTempUrlsIfNoDataDownloadedOrTimeoutReached( const direction_t transferDirection, const m_time_t currentTime) { DEBUG_TEST_HOOK_RESET_TRANSFER_LASTACCESSTIME(lastaccesstime) if (const auto discardTempURLs = (transferDirection == GET && !pos) || ((currentTime - lastaccesstime) >= TEMPURL_TIMEOUT_TS); !discardTempURLs) return; LOG_warn << "[Transfer::discardTempUrlsIfNoTransferDataOrTimeoutReached] Discarding temporary " "URL (pos = " << pos << ", lastaccesstime = " << lastaccesstime << ", currentTime = " << currentTime << ", diff (" << (currentTime - lastaccesstime) << ") >= TEMPURL_TIMEOUT_TS (" << TEMPURL_TIMEOUT_TS << ")"; switch (transferDirection) { case GET: { discardedTempUrlsSize = static_cast(tempurls.size()); break; } case PUT: { chunkmacs.clear(); setProgresscompleted(0); ultoken.reset(); pos = 0; break; } default: break; } tempurls.clear(); } void Transfer::adjustNonRaidedProgressIfNowIsRaided() { static constexpr auto logPre = "[Transfer::adjustNonRaidedProgressIfNowIsRaided] "; if (const auto fromNonRaidToRaidResumption = (discardedTempUrlsSize == 1 && tempurls.size() == RAIDPARTS); !fromNonRaidToRaidResumption) { return; } if (!slot) { LOG_warn << logPre << "Call with no TransferSlot!"; assert(false && "Call to Transfer::adjustNonRaidedProgressIfNowIsRaided with invalid TransferSlot!"); return; } LOG_debug << logPre << "Adjusting chunkmacs and transfer progress to discard non-contiguous data, as " "well as the contiguous data reminder to RAIDLINE"; chunkmac_map newChunkmacs; pos = chunkmacs.copyEntriesToUntilRaidlineBeforePos(pos, newChunkmacs); chunkmacs.swap(newChunkmacs); m_off_t sumOfPartialChunks{0}; m_off_t pCompleted{0}; chunkmacs.calcprogress(size, pos, pCompleted, &sumOfPartialChunks); setProgresscompleted(pCompleted); if (progresscompleted > size) { LOG_err << logPre << "Invalid transfer progress!"; pos = size; setProgresscompleted(size); } const auto progressContiguous = slot->updatecontiguousprogress(); LOG_debug << logPre << "Adjusted resumed transfer at " << pos << " Completed: " << progresscompleted << " Contiguous: " << progressContiguous << " Partial: " << sumOfPartialChunks << " Size: " << size << " ultoken: " << (ultoken != NULL); discardedTempUrlsSize = 0; } FileDistributor::TargetNameExistsResolution Transfer::toTargetNameExistsResolution(CollisionResolution resolution) { switch (resolution) { case CollisionResolution::Overwrite: return FileDistributor::TargetNameExistsResolution::OverwriteTarget; case CollisionResolution::RenameExistingToOldN: return FileDistributor::TargetNameExistsResolution::RenameExistingToOldN; case CollisionResolution::RenameNewWithN: // fall through default: return FileDistributor::TargetNameExistsResolution::RenameWithBracketedNumber; } } // transfer completion: copy received file locally, set timestamp(s), verify // fingerprint, notify app, notify files void Transfer::complete(TransferDbCommitter& committer) { CodeCounter::ScopeTimer ccst(client->performanceStats.transferComplete); static constexpr std::string_view fingerprintIssue = "[Fingerprint Issue] "; state = TRANSFERSTATE_COMPLETING; client->app->transfer_update(this); if (type == GET) { LOG_debug << client->clientname << "Download complete: " << (files.size() ? LOG_NODEHANDLE(files.front()->h) : "NO_FILES") << " " << files.size() << (files.size() ? files.front()->name : ""); bool transient_error = false; bool success; // disconnect temp file from slot... slot->fa.reset(); // FIXME: multiple overwrite race conditions below (make copies // from open file instead of closing/reopening!) // set timestamp (subsequent moves & copies are assumed not to alter mtime) success = client->fsaccess->setmtimelocal(localfilename, mtime); #ifdef __ANDROID__ bool andSuccessNonTransient{false}; #endif if (!success) { transient_error = client->fsaccess->transient_error; std::string msg = std::string{fingerprintIssue} + "setmtimelocal failed for: " + localfilename.toPath(false) + ", with " + (transient_error ? "transient error" : "non-transient error"); #ifdef __ANDROID__ msg.insert(0, "[AND] "); if (!transient_error) { // forcing to compare fingerprint without mtime (as it could not be set) some lines // below success = true; andSuccessNonTransient = true; } #endif LOG_warn << msg; } // try to catch failing cases in the debugger (seen on synology SMB drive after the file was moved to final destination) assert(FSNode::debugConfirmOnDiskFingerprintOrLogWhy(*client->fsaccess, localfilename, *this)); // verify integrity of file auto fa = client->fsaccess->newfileaccess(); FileFingerprint fingerprint; std::shared_ptr n; bool fixfingerprint = false; bool fixedfingerprint = false; bool syncxfer = false; for (file_list::iterator it = files.begin(); it != files.end(); it++) { if ((*it)->syncxfer) { syncxfer = true; } if (!fixedfingerprint && (n = client->nodeByHandle((*it)->h)) && !(this->EqualExceptValidFlag(*n))) { LOG_debug << fingerprintIssue << "Wrong fingerprint already fixed" << ". Transfer fingerprint: " << fingerprintDebugString() << ". Node fingerprint: " << n->fingerprintDebugString(); fixedfingerprint = true; } if (syncxfer && fixedfingerprint) { break; } } if (!fixedfingerprint && success && fa->fopen(localfilename, OPEN_RDONLY, FSLogging::logOnError)) { fingerprint.genfingerprint(fa.get()); bool sameFingerprint = (fingerprint == *(FileFingerprint*)this); #ifdef __ANDROID__ if (andSuccessNonTransient) { // In Android maybe we can't set mtime at download sameFingerprint = sameFingerprint || (fingerprint.size == size && memcmp(fingerprint.crc.data(), crc.data(), sizeof crc) == 0); } #endif if (isvalid && !sameFingerprint) { LOG_err << fingerprintIssue << "Fingerprint mismatch! Transfer fingerprint: " << fingerprintDebugString() << ". FA fingerprint: " << fingerprint.fingerprintDebugString() << ". badfp: " << badfp.fingerprintDebugString(); // enforce the verification of the fingerprint for sync transfers only if (syncxfer && (!badfp.isvalid || !(badfp == fingerprint))) { badfp = fingerprint; fa.reset(); chunkmacs.clear(); client->fsaccess->unlinklocal(localfilename); return failed(API_EWRITE, committer); } else { // We consider that mtime is different if the difference is >2 // due to the resolution of mtime in some filesystems (like FAT). // This check prevents changes in the fingerprint due to silent // errors in setmtimelocal (returning success but not setting the // modification time) that seem to happen in some Android devices. if (abs(mtime - fingerprint.mtime) <= FS_MTIME_TOLERANCE_SECS) { LOG_debug << fingerprintIssue << "Fixfingerprint set to true"; fixfingerprint = true; } else { LOG_warn << fingerprintIssue << "Silent failure in setmtimelocal"; } } } } else { if (syncxfer && !fixedfingerprint && success) { transient_error = fa->retry; LOG_debug << fingerprintIssue << "Unable to validate fingerprint " << transient_error; } } fa.reset(); char me64[12]; Base64::btoa((const byte*)&client->me, MegaClient::USERHANDLE, me64); if (!transient_error) { if (fingerprint.isvalid) { // set FileFingerprint on source node(s) if missing or invalid set nodes; for (file_list::iterator it = files.begin(); it != files.end(); it++) { if ((*it)->hprivate && !(*it)->hforeign && (n = client->nodeByHandle((*it)->h)) && nodes.find(n->nodehandle) == nodes.end()) { nodes.insert(n->nodehandle); if ((!n->isvalid || fixfingerprint) && !(fingerprint == *(FileFingerprint*)n.get()) && fingerprint.size == this->size) { attr_map attrUpdate; fingerprint.serializefingerprint(&attrUpdate['c']); // the fingerprint is still wrong, but.... // is it already being fixed? AttrMap pendingAttrs; if (!n->mPendingChanges.empty()) { pendingAttrs = n->attrs; n->mPendingChanges.forEachCommand([&pendingAttrs](Command* cmd) { if (auto cmdSetAttr = dynamic_cast(cmd)) { cmdSetAttr->applyUpdatesTo(pendingAttrs); } }); } LOG_debug << fingerprintIssue << "Fixing fingerprint values -> fixfingerprint = " << fixfingerprint << ". Transfer fingerprint: " << fingerprintDebugString() << ". FA fingerprint: " << fingerprint.fingerprintDebugString() << ". Node fingerprint: " << n->fingerprintDebugString(); if (pendingAttrs.hasDifferentValue('c', attrUpdate)) { LOG_debug << fingerprintIssue << "Fixing fingerprint"; client->setattr(n, std::move(attrUpdate), nullptr, false); } else { LOG_debug << fingerprintIssue << "Fingerprint already being fixed"; } } } } } // ...and place it in all target locations. first, update the files' // local target filenames, in case they have changed during the upload for (file_list::iterator it = files.begin(); it != files.end(); it++) { (*it)->updatelocalname(); } if (!downloadDistributor) { // we keep the old one in case there was a temporary_error previously downloadDistributor.reset(new FileDistributor(localfilename, files.size(), mtime, *this)); } set keys; // place file in all target locations - use up to one renames, copy // operations for the rest // remove and complete successfully completed files for (file_list::iterator it = files.begin(); it != files.end(); ) { if ((*it)->syncxfer) { // leave sync items for later, they will be passed to the sync thread ++it; continue; } transient_error = false; success = false; auto finalpath = (*it)->getLocalname(); // it may update the path to include (n) if there is a clash bool name_too_long = false; auto r = toTargetNameExistsResolution((*it)->getCollisionResolution()); success = downloadDistributor->distributeTo(finalpath, *client->fsaccess, r, transient_error, name_too_long, nullptr); if (success) { (*it)->setLocalname(finalpath); // so the app may report an accurate final name } else if (transient_error) { it++; continue; } if (success) { // set missing node attributes if ((*it)->hprivate && !(*it)->hforeign && (n = client->nodeByHandle((*it)->h))) { auto localname = (*it)->getLocalname(); if (!client->gfxdisabled && client->gfx && client->gfx->isgfx(localname) && keys.find(n->nodekey()) == keys.end() && // this file hasn't been processed yet client->checkaccess(n.get(), OWNER)) { keys.insert(n->nodekey()); // check if restoration of missing attributes failed in the past (no access) if (n->attrs.map.find('f') == n->attrs.map.end() || n->attrs.map['f'] != me64) { // check for missing imagery int missingattr = 0; if (!n->hasfileattribute(GfxProc::THUMBNAIL)) missingattr |= 1 << GfxProc::THUMBNAIL; if (!n->hasfileattribute(GfxProc::PREVIEW)) missingattr |= 1 << GfxProc::PREVIEW; if (missingattr) { client->gfx->gendimensionsputfa(localname, NodeOrUploadHandle(n->nodeHandle()), n->nodecipher(), missingattr); } addAnyMissingMediaFileAttributes(n.get(), localname); } } } } if (success) { // prevent deletion of associated Transfer object in completed() client->filecachedel(*it, &committer); client->app->file_complete(*it); (*it)->transfer = NULL; (*it)->completed(this, (*it)->syncxfer ? PUTNODES_SYNC : PUTNODES_APP); files.erase(it++); } else if (transient_error) { LOG_debug << "Transient error completing file"; it++; } else if (!(*it)->failed(API_EAGAIN, client)) { File* f = (*it); files.erase(it++); LOG_warn << "Unable to complete transfer due to a persistent error"; client->filecachedel(f, &committer); #ifdef ENABLE_SYNC if (f->syncxfer) { client->syncs.setSyncsNeedFullSync(false, false, UNDEF); } else #endif { downloadDistributor->removeTarget(); } client->app->file_removed(f, API_EWRITE); f->transfer = NULL; f->terminated(API_EWRITE); } else { failcount++; LOG_debug << "Persistent error completing file. Failcount: " << failcount; if (name_too_long) { LOG_warn << "Error is: name too long"; } it++; } } #ifdef ENABLE_SYNC for (file_list::iterator it = files.begin(); it != files.end(); ) { // now that the file itself is moved (if started as a manual download), // we can let the sync copy (or move) for the sync cases File* f = *it; // pass the distribution responsibility to the sync, for sync requested downloads if (f->syncxfer) { auto dl = dynamic_cast(f); assert(dl); dl->downloadDistributor = downloadDistributor; client->filecachedel(f, &committer); client->app->file_complete(f); f->transfer = NULL; f->completed(this, PUTNODES_SYNC); // sets wasCompleted == true, and the sync thread can then call the distributor it = files.erase(it); } else it++; } #endif // ENABLE_SYNC if (!files.size()) { // check if we should delete the download at downloaded path downloadDistributor.reset(); } } if (files.empty()) // all processed { state = TRANSFERSTATE_COMPLETED; assert(localfilename.isAbsolute()); finished = true; client->app->transfer_complete(this); localfilename.clear(); delete this; } else { // some files are still pending completion, close fa and set retry timer slot->fa.reset(); LOG_debug << "Files pending completion: " << files.size() << ". Waiting for a retry."; LOG_debug << "First pending file: " << files.front()->name; slot->retrying = true; slot->retrybt.backoff(11); } } else // type == PUT { LOG_debug << client->clientname << "Upload complete: " << (files.size() ? files.front()->name : "NO_FILES") << " " << files.size(); if (slot->fa) { slot->fa.reset(); } // files must not change during a PUT transfer for (file_list::iterator it = files.begin(); it != files.end(); ) { File *f = (*it); LocalPath localpath = f->getLocalname(); LOG_debug << "Verifying upload: " << localpath.toPath(false); auto fa = client->fsaccess->newfileaccess(); bool isOpen = fa->fopen(localpath, FSLogging::logOnError); if (!isOpen) { if (client->fsaccess->transient_error) { LOG_warn << "Retrying upload completion due to a transient error"; slot->retrying = true; slot->retrybt.backoff(11); return; } } if (const bool isNotOpenAndIsNotSyncxfer = (!f->syncxfer && !isOpen), // for syncs, it's ok if the file moved/renamed elsewhere since fingerprintChanged = isOpen && f->genfingerprint(fa.get()); isNotOpenAndIsNotSyncxfer || fingerprintChanged) { bool skipRemoveTransferFile = false; if (isNotOpenAndIsNotSyncxfer) { LOG_warn << "Deletion detected after upload"; } else { LOG_warn << fingerprintIssue << "Modification detected after upload! Path: " << localpath.toPath(false) << ". Transfer fingerprint: " << fingerprintDebugString() << ". FA fingerprint: " << f->fingerprintDebugString(); DEBUG_TEST_HOOK_FILEFINGERPRINT_USE_LEGACY_BUGGY_SPARSE_CRC( skipRemoveTransferFile); } it++; // the next line will remove the current item and invalidate that iterator if (skipRemoveTransferFile) { LOG_debug << fingerprintIssue << "Debug test hook filefingerprint using legacy buggy sparse crc was " "active. Skipping removeTransferFile and" << " mark transfer as successful. There can be fingerprint mismatches " "between IA and FA genfingerprints with buggy sparse crc calculation"; f->crc = crc; } else { removeTransferFile(API_EREAD, f, &committer); } } else { it++; } } if (!files.size()) { return failed(API_EREAD, committer); } if (!client->gfxdisabled) { // prepare file attributes for video/audio files if the file is suitable addAnyMissingMediaFileAttributes(NULL, localfilename); } // if this transfer is put on hold, do not complete client->checkfacompletion(uploadhandle, this, true); } } void Transfer::completefiles() { // notify all files and give them an opportunity to self-destruct vector &ids = client->pendingtcids[tag]; vector *pfs = NULL; #ifdef ENABLE_SYNC bool wakeSyncs = false; #endif // ENABLE_SYNC for (file_list::iterator it = files.begin(); it != files.end(); ) { File *f = (*it); ids.push_back(f->dbid); if (f->temporaryfile) { if (!pfs) { pfs = &client->pendingfiles[tag]; } pfs->push_back(f->getLocalname()); } client->app->file_complete(f); #ifdef ENABLE_SYNC if (f->syncxfer && type == PUT) { if (SyncUpload_inClient* put = dynamic_cast(f)) { // We are about to hand over responsibility for putnodes to the sync // However, if the sync gets shut down before that is sent, or the // operation turns out to be invalidated (eg. uploaded file deleted before putnodes) // then we must inform the app of the final transfer outcome. client->transferBackstop.remember(put->tag, put->selfKeepAlive); wakeSyncs = true; } } #endif // ENABLE_SYNC f->transfer = NULL; f->completed(this, f->syncxfer ? PUTNODES_SYNC : PUTNODES_APP); files.erase(it++); } ids.push_back(dbid); #ifdef ENABLE_SYNC if (wakeSyncs) { // for a sync that is only uploading, there's no other mechansim to wake it up early between tree recursions client->syncs.skipWait = true; client->syncs.waiter->notify(); } #endif // ENABLE_SYNC } DirectReadNode::DirectReadNode(MegaClient* client, handle h, bool isPublicHandle, SymmCipher* symmCipher, int64_t ctriv, const char* privateAuth, const char* publicAuth, const char* chatAuth) { this->client = client; this->isPublicHandle = isPublicHandle; this->h = h; if (privateAuth) { privateauth = privateAuth; } if (publicAuth) { publicauth = publicAuth; } if (chatAuth) { chatauth = chatAuth; } symmcipher = *symmCipher; this->ctriv = ctriv; retries = 0; size = 0; pendingcmd = NULL; dsdrn_it = client->dsdrns.end(); } DirectReadNode::~DirectReadNode() { schedule(NEVER); if (pendingcmd) { pendingcmd->cancel(); } for (dr_list::iterator it = reads.begin(); it != reads.end(); ) { delete *(it++); } client->hdrns.erase(hdrn_it); } void DirectReadNode::dispatch() { if (reads.empty()) { LOG_debug << "Removing DirectReadNode" << " [this = " << this << "]"; delete this; } else { for (dr_list::iterator it = reads.begin(); it != reads.end(); it++) { assert((*it)->drq_it == client->drq.end()); assert(!(*it)->drs); } schedule(DirectReadSlot::TIMEOUT_DS); if (!pendingcmd) { pendingcmd = new CommandDirectRead(client, this); client->queueCommand(pendingcmd); } } } // abort all active reads, remove pending reads and reschedule with app-supplied backoff void DirectReadNode::retry(const Error& e, dstime timeleft) { if (reads.empty()) { LOG_warn << "Removing DirectReadNode. No reads to retry" << " [this = " << this << "]"; delete this; return; } dstime minretryds = NEVER; retries++; LOG_warn << "[DirectReadNode::retry] Streaming transfer retry due to error " << e << " [this = " << this << "]"; if (client->autodownport) { client->usealtdownport = !client->usealtdownport; } // signal failure to app, obtain minimum desired retry time for (dr_list::iterator it = reads.begin(); it != reads.end(); ) { if ((*it)->hasValidCallback()) { (*it)->abort(); if (e) { LOG_debug << "[DirectReadNode::retry] Calling onFailure for DirectRead (" << (void*)(*it) << ")" << " [this = " << this << "]"; dstime retryds = (*it)->onFailure(e, retries, timeleft); if (retryds < minretryds && !(e == API_ETOOMANY && e.hasExtraInfo())) { minretryds = retryds; } } } else { // This situation should never happen client->sendevent(99472, "DirectRead detected with a null transfer"); } if (!(*it)->hasValidCallback()) // It may have been deleted after onFailure { // Transfer is deleted LOG_warn << "[DirectReadNode::retry] No appdata (transfer has been deleted) for this DirectRead (" << (void*)(*it) << "). Deleting affected DirectRead" << " [this = " << this << "]"; delete *(it++); } else it++; } if (reads.empty()) // Check again if there are DirectReads left to retry { LOG_warn << "Removing DirectReadNode. No reads left to retry" << " [this = " << this << "]"; delete this; return; } if (e == API_EOVERQUOTA && timeleft) { // don't retry at least until the end of the overquota state client->overquotauntil = Waiter::ds + timeleft; if (minretryds < timeleft) { minretryds = timeleft; } } else if (e == API_EPAYWALL) { minretryds = NEVER; } tempurls.clear(); if (!e || !minretryds) { // immediate retry desired dispatch(); } else { if (EVER(minretryds)) { // delayed retry desired schedule(minretryds); } else { // cancellation desired LOG_debug << "[DirectReadNode::retry] Removing DirectReadNode. Too many errors" << " [this = " << this << "]"; delete this; } } } void DirectReadNode::cmdresult(const Error &e, dstime timeleft) { pendingcmd = NULL; if (e == API_OK) { // feed all pending reads to the global read queue for (dr_list::iterator it = reads.begin(); it != reads.end(); it++) { DirectRead* dr = *it; assert(dr->drq_it == client->drq.end()); if (dr->drbuf.tempUrlVector().empty()) { // DirectRead starting m_off_t streamingMaxReqSize = dr->drMaxReqSize(); LOG_debug << "Direct read node size = " << dr->drn->size << ", streaming max request size: " << streamingMaxReqSize; dr->drbuf.setIsRaid(dr->drn->tempurls, dr->offset, dr->offset + dr->count, dr->drn->size, streamingMaxReqSize, false); } else { // URLs have been re-requested, eg. due to temp URL expiry. Keep any parts downloaded already dr->drbuf.updateUrlsAndResetPos(dr->drn->tempurls); } dr->drq_it = client->drq.insert(client->drq.end(), *it); } schedule(DirectReadSlot::TIMEOUT_DS); } else { retry(e, timeleft); } } void DirectReadNode::schedule(dstime deltads) { WAIT_CLASS::bumpds(); if (dsdrn_it != client->dsdrns.end()) { client->dsdrns.erase(dsdrn_it); } if (EVER(deltads)) { dsdrn_it = client->dsdrns.insert(pair(Waiter::ds + deltads, this)); } else { dsdrn_it = client->dsdrns.end(); } } DirectRead* DirectReadNode::enqueue(m_off_t offset, m_off_t count, int reqtag, DirectRead::Callback&& callback) { return new DirectRead(this, count, offset, reqtag, std::move(callback)); } size_t UnusedConn::getNum() const { return mNum; } bool UnusedConn::CanBeReused() const { return mReason == UN_NOT_ERR || mReason == UN_TEMPORARY_ERR; } bool UnusedConn::setUnused(const size_t num, const UnusedReason reason) { if (!isValidUnusedReason(reason)) { LOG_err << "setUnused: Invalid reason: " << reason; assert(false); return false; } if (num == mNum) { return false; } mNum = num; mReason = reason; return true; } void UnusedConn::clear() { mNum = 0; mReason = UN_NOT_ERR; } bool DirectReadSlot::processAnyOutputPieces() { bool continueDirectRead = true; std::shared_ptr outputPiece; while (continueDirectRead && (outputPiece = mDr->drbuf.getAsyncOutputBufferPointer(0))) { size_t len = outputPiece->buf.datalen(); mSpeed = mSpeedController.calculateSpeed(static_cast(len)); mMeanSpeed = mSpeedController.getMeanSpeed(); mDr->drn->client->httpio->updatedownloadspeed(static_cast(len)); if (mDr->hasValidCallback()) { mSlotThroughput.first += static_cast(len); auto lastDataTime = std::chrono::duration_cast(std::chrono::steady_clock::now() - mSlotStartTime).count(); mSlotThroughput.second = static_cast(lastDataTime); LOG_verbose << "DirectReadSlot -> Delivering assembled part ->" << "len = " << len << ", speed = " << mSpeed << ", meanSpeed = " << (mMeanSpeed / 1024) << " KB/s" << ", slotThroughput = " << ((calcThroughput(mSlotThroughput.first, mSlotThroughput.second) * 1000) / 1024) << " KB/s]" << " [this = " << this << "]"; continueDirectRead = mDr->onData(outputPiece->buf.datastart(), static_cast(len), mPos, mSpeed, mMeanSpeed); } else { LOG_err << "DirectReadSlot tried to deliver an assembled part, but the transfer doesn't exist anymore. Aborting" << " [this = " << this << "]"; mDr->drn->client->sendevent(99472, "DirectRead detected with a null transfer"); continueDirectRead = false; } mDr->drbuf.bufferWriteCompleted(0, true); if (continueDirectRead) { mPos += len; mDr->drn->partiallen += len; mDr->progress += len; mMinComparableThroughput = static_cast(len); } } return continueDirectRead; } bool DirectReadSlot::waitForPartsInFlight() const { return DirectReadSlot::WAIT_FOR_PARTS_IN_FLIGHT && isRaidedTransfer() && mWaitForParts; } unsigned DirectReadSlot::usedConnections() const { assert(isRaidedTransfer()); if (!isRaidedTransfer() || mReqs.empty()) { LOG_err << "DirectReadSlot -> usedConnections() being used when it shouldn't" << " [this = " << this << "]"; return 0; } return static_cast(mReqs.size()) - ((mUnusedConn.getNum() != static_cast(mReqs.size())) ? 1 : 0); } bool DirectReadSlot::resetConnection(size_t connectionNum) { LOG_debug << "DirectReadSlot [conn " << connectionNum << "] -> resetConnection" << " [this = " << this << "]"; assert(connectionNum < mReqs.size()); if (connectionNum >= mReqs.size()) { return false; } if (mReqs[connectionNum]) { mReqs[connectionNum]->disconnect(); mReqs[connectionNum]->status = REQ_READY; mThroughput[connectionNum].first = 0; mThroughput[connectionNum].second = 0; } mDr->drbuf.resetPart(static_cast(connectionNum)); return true; } unsigned DirectReadSlot::getMinSpeedPerConnBytesPerSec() const { const unsigned numParts = isRaidedTransfer() ? EFFECTIVE_RAIDPARTS : 1; const int minstrate = mDr->drn->client->minstreamingrate; // (0 == no limit, -1 == use default) if (minstrate < 0) { return (MIN_BYTES_PER_SECOND / numParts); } else if (minstrate == 0) { return 0; } else if (static_cast(minstrate) < numParts) { return 1; } else { return (static_cast(minstrate) / numParts); } } m_off_t DirectReadSlot::getThroughput(size_t connectionNum) const { assert(connectionNum < mReqs.size()); m_off_t connectionThroughPut = calcThroughput(mThroughput[connectionNum].first, mThroughput[connectionNum].second); return connectionThroughPut; } m_off_t DirectReadSlot::calcThroughput(m_off_t numBytes, m_off_t timeCount) const { m_off_t throughput = (numBytes && timeCount) ? numBytes / timeCount : 0; return throughput; } void DirectReadSlot::retryOnError(const size_t connectionNum, const int httpstatus, const unsigned errCode) { if (!isRaidedTransfer()) { LOG_debug << "DirectReadSlot::retryOnError: Retrying non-raid transfer"; retryEntireTransfer(API_EREAD); return; } assert(mReqs.size() == RAIDPARTS); const std::string logPre = "DirectReadSlot::retryOnError [Raided] [conn " + std::to_string(connectionNum) + "]: "; if (connectionNum >= mReqs.size()) { LOG_err << logPre << "invalid connectionNum (out of bounds)"; assert(false && "DirectReadSlot::retryOnError: invalid connectionNum (out of bounds)"); retryEntireTransfer(API_EREAD); return; } if (const auto& failedReq = mReqs[connectionNum]; !failedReq) { LOG_err << logPre << "invalid connectionNum (not found)"; assert(false && "DirectReadSlot::retryOnError: Cannot get Http req"); retryEntireTransfer(API_EREAD); return; } if (connectionNum == mUnusedConn.getNum()) { LOG_err << logPre << "DirectReadSlot::connectionNum provided matches the unused connectionNum."; assert(false && "DirectReadSlot::retryOnError: connectionNum is equal to unused"); retryEntireTransfer(API_EREAD); return; } const auto recvReason = UnusedConn::getReasonFromHttpStatus(httpstatus, errCode); if (recvReason != UnusedConn::UN_DEFINITIVE_ERR && recvReason != UnusedConn::UN_TEMPORARY_ERR) { LOG_err << logPre << "unexpected reason: " << recvReason << " httpstatus: " << httpstatus; assert(false && "DirectReadSlot::retryOnError: unexpected Httpstatus"); retryEntireTransfer(API_EREAD); return; } if (!unusedConnectionCanBeReused()) { LOG_debug << logPre << "we cannot replace failed part by unused one, as it's definitely failed. Retrying " "entire transfer"; retryEntireTransfer(API_EREAD); return; } const auto replamecementReason = recvReason == UnusedConn::UN_DEFINITIVE_ERR ? UnusedConn::ON_RAIDED_ERROR : UnusedConn::ON_RECOVERABLE_RAIDED_ERROR; replaceConnectionByUnusedInflight(connectionNum, replamecementReason, recvReason); } bool DirectReadSlot::isRaidedTransfer() const { return mDr->drbuf.isRaid(); } void DirectReadSlot::retryEntireTransfer(const Error& e, const dstime timeleft) { resetConnSwitchesCounters(std::chrono::steady_clock::now()); mUnusedConn.clear(); mDr->drn->retry(e, timeleft); } std::pair, size_t> DirectReadSlot::searchSlowConnsUnderThreshold() { const auto minSpeedPerConnBytesPerSec = getMinSpeedPerConnBytesPerSec(); if (!minSpeedPerConnBytesPerSec || !isRaidedTransfer()) { // if minstreamingrate == 0, no StreamingMinimumRate has been set // if non raided transfer there's just 1 connection return {{}, mReqs.size()}; } std::set slowConns; size_t slowestConnectionIndex = mReqs.size(); // init to `invalid` conn num m_off_t slowestThroughput = 0; // init slowest throughput for (size_t i = 0; i < mReqs.size(); ++i) { if (!mReqs[i] || i == mUnusedConn.getNum() || mReqs[i]->status != REQ_INFLIGHT) { continue; } LOG_debug << "searchSlowConnsUnderThreshold [" << i << "] Throughput: " << getThroughput(i) * 1000 << " B/s, minSpeedPerConn: " << minSpeedPerConnBytesPerSec << " B/s"; if (const auto isConnSpeedBelowMinThreshold = getThroughput(i) * 1000 < minSpeedPerConnBytesPerSec; isConnSpeedBelowMinThreshold) { slowConns.emplace(i); if (const m_off_t currentThroughput = getThroughput(i); !slowestThroughput || currentThroughput < slowestThroughput) { slowestConnectionIndex = i; slowestThroughput = currentThroughput; } } } return {slowConns, slowestConnectionIndex}; } bool DirectReadSlot::exitDueReqsOnFlight() const { // If there is any `valid` connection inflight we don't switch (we only switch when the // status is REQ_READY for all reqs to avoid disconnections) if (mNumReqsInflight == 0) return false; if (mNumReqsInflight > 1) return true; const auto it = std::find_if(std::begin(mReqs), std::end(mReqs), [](const auto& req) -> bool { return !req || req->status != REQ_READY; }); return it != std::end(mReqs); } bool DirectReadSlot::unusedConnectionCanBeReused() { return mUnusedConn.CanBeReused(); } void DirectReadSlot::replaceConnectionByUnusedInflight( const size_t newUnusedConnection, const UnusedConn::ConnReplacementReason replamecementReason, const UnusedConn::UnusedReason unusedReason) { if (!replaceConnectionByUnused(newUnusedConnection, replamecementReason, unusedReason)) return; decreaseReqsInflight(); if (mUnusedConnIncrementedInFlightReqs) { if (mNumReqsInflight > 0) decreaseReqsInflight(); mUnusedConnIncrementedInFlightReqs = false; } } bool DirectReadSlot::replaceConnectionByUnused( const size_t newUnusedConnection, const UnusedConn::ConnReplacementReason replamecementReason, const UnusedConn::UnusedReason unusedReason) { if (!isRaidedTransfer() || !unusedConnectionCanBeReused() || maxUnusedConnSwitchesReached(replamecementReason) || newUnusedConnection >= mReqs.size() || !mReqs[newUnusedConnection]) { LOG_err << "DirectReadSlot::replaceConnectionByUnused [conn " << newUnusedConnection << "]: " << " Cannot replace unused connection by " << newUnusedConnection; assert(false); return false; } const auto prevUnusedConnection = mUnusedConn.getNum(); LOG_debug << "DirectReadSlot::replaceConnectionByUnused: Replace conn [" << newUnusedConnection << "]" << " by unused conn [" << prevUnusedConnection << "]" << ". Replacement reason [" << replamecementReason << "], unused reason [" << unusedReason << "] [this = " << this << "]"; increaseUnusedConnSwitches(replamecementReason); mDr->drbuf.setUnusedRaidConnection(static_cast(newUnusedConnection)); resetConnection(prevUnusedConnection); mUnusedConn.setUnused(newUnusedConnection, unusedReason); resetConnection(newUnusedConnection); return true; } std::pair DirectReadSlot::searchSlowestAndFastestConns(const size_t connectionNum) const { assert(isMinComparableThroughputForThisConnection(connectionNum)); const size_t numReqs = mReqs.size(); size_t slowestConnection = connectionNum; size_t fastestConnection = connectionNum; bool minComparableThroughputForOtherConnection = true; for (size_t otherConnection = numReqs; (otherConnection-- > 0) && minComparableThroughputForOtherConnection;) { if ((otherConnection != connectionNum) && (otherConnection != mUnusedConn.getNum())) { const auto otherConnectionIsDone = isConnectionDone(otherConnection); const auto otherConnectionHasEnoughDataToCompare = isMinComparableThroughputForThisConnection(otherConnection); const auto compareCondition = otherConnectionHasEnoughDataToCompare && !otherConnectionIsDone; if (compareCondition) { m_off_t otherConnectionThroughput = getThroughput(otherConnection); m_off_t slowestConnectionThroughput = getThroughput(slowestConnection); m_off_t fastestConnectionThroughput = getThroughput(fastestConnection); if (otherConnectionThroughput < slowestConnectionThroughput) { slowestConnection = otherConnection; } if (otherConnectionThroughput > fastestConnectionThroughput) { fastestConnection = otherConnection; } } else // If we don't have enough throughput data for one connection, or any connection is // done (we shouldn't reset it at that point), then we just skip this { // Cannot compare... will need to wait slowestConnection = numReqs; fastestConnection = numReqs; minComparableThroughputForOtherConnection = false; } } } LOG_debug << "DirectReadSlot [conn " << connectionNum << "]" << " Test slow connection -> slowest connection = " << slowestConnection << ", fastest connection = " << fastestConnection << ", unused raid connection = " << mUnusedConn.getNum() << ", mMinComparableThroughput = " << (mMinComparableThroughput / 1024) << " KB/s" << " [this = " << this << "]"; return {slowestConnection, fastestConnection}; } bool DirectReadSlot::slowestConnTooSlowVsFastest(const size_t connectionNum, const size_t slowestConnection, const size_t fastestConnection) const { if (((slowestConnection == connectionNum) || ((slowestConnection != mReqs.size()) && (mReqs[slowestConnection]->status == REQ_READY))) && (fastestConnection != slowestConnection)) { m_off_t slowestConnectionThroughput = getThroughput(slowestConnection); m_off_t fastestConnectionThroughput = getThroughput(fastestConnection); if (fastestConnectionThroughput * SLOWEST_TO_FASTEST_THROUGHPUT_RATIO[0] > slowestConnectionThroughput * SLOWEST_TO_FASTEST_THROUGHPUT_RATIO[1]) { LOG_warn << "DirectReadSlot [conn " << connectionNum << "]" << " Connection " << slowestConnection << " is slow" << " [slowest speed = " << ((slowestConnectionThroughput * 1000 / 1024)) << " KB/s" << ", fastest speed = " << ((fastestConnectionThroughput * 1000 / 1024)) << " KB/s" << ", mMinComparableThroughput = " << (mMinComparableThroughput / 1024) << " KB/s]" << " [total slow connections switches = " << mNumConnSwitchesSlowestPart << "]" << " [current unused raid connection = " << mUnusedConn.getNum() << "]" << " [this = " << this << "]"; return true; } } return false; } bool DirectReadSlot::searchAndDisconnectSlowestConnection(const size_t connectionNum) { // If mUnusedConn has failed due to an error, we won't consider it valid for replacement assert(connectionNum < mReqs.size()); if (!isRaidedTransfer() || !unusedConnectionCanBeReused() || exitDueReqsOnFlight() || !mReqs[connectionNum] || (connectionNum == mUnusedConn.getNum()) || !isMinComparableThroughputForThisConnection(connectionNum)) { return false; } if (maxUnusedConnSwitchesReached(UnusedConn::CONN_SPEED_SLOWEST_PART)) { return false; } const auto [slowestConnection, fastestConnection] = searchSlowestAndFastestConns(connectionNum); if (slowestConnTooSlowVsFastest(connectionNum, slowestConnection, fastestConnection)) { if (replaceConnectionByUnused(slowestConnection, UnusedConn::CONN_SPEED_SLOWEST_PART, UnusedConn::UN_NOT_ERR)) { return true; } } return false; } bool DirectReadSlot::areAllReqsReadyOrInFlight() { return std::all_of(mReqs.begin(), mReqs.end(), [](const std::unique_ptr& req) { return req != nullptr && (req->status == REQ_READY || req->status == REQ_INFLIGHT); }); } bool DirectReadSlot::decreaseReqsInflight() { if (isRaidedTransfer()) { LOG_verbose << "Decreasing counter of total requests inflight: " << mNumReqsInflight << " - 1" << " [this = " << this << "]"; assert(mNumReqsInflight > 0); --mNumReqsInflight; if ((mUnusedConn.getNum() < mReqs.size()) && (mReqs[mUnusedConn.getNum()]->status != REQ_DONE) && (mNumReqsInflight == (static_cast(mReqs.size()) - usedConnections()))) { mNumReqsInflight = 0; mUnusedConnIncrementedInFlightReqs = false; } if (mNumReqsInflight == 0) { LOG_verbose << "Wait for parts set to false" << " [this = " << this << "]"; // waitForParts could be true at this point if there were connections with REQ_DONE status which didn't increase the inflight counter mWaitForParts = false; mMaxChunkSubmitted = 0; } return true; } return false; } bool DirectReadSlot::increaseReqsInflight() { if (isRaidedTransfer()) { LOG_verbose << "Increasing counter of total requests inflight: " << mNumReqsInflight << " + 1 = " << (mNumReqsInflight + 1) << " [this = " << this << "]"; assert(mNumReqsInflight < mReqs.size()); ++mNumReqsInflight; if (mNumReqsInflight == static_cast(mReqs.size())) { assert(!mWaitForParts); LOG_verbose << "Wait for parts set to true" << " [this = " << this << "]"; mWaitForParts = true; resetWatchdogPartialValues(); } return true; } return false; } bool DirectReadSlot::isConnectionDone(const size_t connectionNum) const { return mReqs[connectionNum] && (mReqs[connectionNum]->status == REQ_DONE || (mReqs[connectionNum]->pos == mDr->drbuf.transferSize(static_cast(connectionNum)))); } std::pair DirectReadSlot::getMinAndMeanSpeed(const dstime dsSinceLastWatch) { // if minstreamingrate is zero, there's no minimum acceptable rate for streaming. const m_off_t meanspeed = (10 * mDr->drn->partiallen) / dsSinceLastWatch; int minspeed = mDr->drn->client->minstreamingrate; if (minspeed < 0) { LOG_warn << "DirectReadSlot: Watchdog -> Set min speed as MIN_BYTES_PER_SECOND(" << MIN_BYTES_PER_SECOND << ") to compare with average speed." << " [this = " << this << "]"; minspeed = MIN_BYTES_PER_SECOND; } LOG_debug << "DirectReadSlot: Watchdog -> Mean speed: " << meanspeed << " B/s. Min speed: " << minspeed << " B/s [Partial len: " << mDr->drn->partiallen << ". Ds: " << dsSinceLastWatch << "]" << " [this = " << this << "]"; return {minspeed, meanspeed}; } void DirectReadSlot::resetWatchdogPartialValues() { mDr->drn->partiallen = 0; mDr->drn->partialstarttime = Waiter::ds; }; bool DirectReadSlot::watchOverDirectReadPerformance() { if (!mDr->hasValidCallback()) { LOG_err << "DirectReadSlot Watchdog: Transfer is already deleted." << " [this = " << this << "]"; mDr->drn->client->sendevent(99472, "DirectRead detected with a null transfer"); delete mDr; return true; } auto dsSinceLastWatch = Waiter::ds - mDr->drn->partialstarttime; if (dsSinceLastWatch <= MEAN_SPEED_INTERVAL_DS) { return false; } const auto [minTransferspeed, transferMeanspeed] = getMinAndMeanSpeed(dsSinceLastWatch); if (!minTransferspeed) { // no limits set by client, so no performance check is required LOG_verbose << "DirectReadSlot Watchdog: No minTransferspeed"; resetWatchdogPartialValues(); return false; } if (isAnyRaidedPartFailed()) { LOG_warn << "DirectReadSlot Watchdog: a raided part has already reported as failed. " "Skipping watchdog"; resetWatchdogPartialValues(); return false; } const auto [slowConns, slowestConnectionIndex] = searchSlowConnsUnderThreshold(); LOG_debug << "DirectReadSlot Watchdog: number of detected slow connections = " << slowConns.size(); if (slowConns.empty()) { assert(getMinSpeedPerConnBytesPerSec() && "If no minstrate is set, we should have already exited"); if (transferMeanspeed < minTransferspeed) { LOG_warn << "watchOverDirectReadPerformance: " << (!isRaidedTransfer() ? "Non Raided Transfer" : "Raided transfer, slowConns empty") << ", transferMeanspeed: " << transferMeanspeed << " B/s < " << " minTransferspeed: " << minTransferspeed << " B/s"; assert(!isRaidedTransfer() && "Raided transfer slowConns empty and transferMeanspeed < minTransferspeed"); retryEntireTransfer(API_EAGAIN); return true; } resetWatchdogPartialValues(); return false; } if (slowConns.size() <= MAX_SIMULTANEOUS_SLOW_RAIDED_CONNS) { if (const auto unusedConnNotReusable = !unusedConnectionCanBeReused() || maxUnusedConnSwitchesReached(UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD); unusedConnNotReusable || mNumReqsInflight < EFFECTIVE_RAIDPARTS) { if (mNumReqsInflight < EFFECTIVE_RAIDPARTS && areAllReqsReadyOrInFlight()) { LOG_warn << "DirectReadSlot Watchdog: Skipping retryEntireTransfer. Wait until " "requests with status REQ_INFLIGHT reach REQ_READY. Also, increase " "UnusedConnSwitches(TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD) to prevent " "this scenario from repeating indefinitely."; increaseUnusedConnSwitches(UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD); resetWatchdogPartialValues(); return false; } LOG_err << "DirectReadSlot Watchdog: [conn " << slowestConnectionIndex << "]: " << " Cannot replace unused connection by " << slowestConnectionIndex << " . Reason: unusedConnection cannot be reused or maxUnusedConnSwitchesReached"; retryEntireTransfer(API_EAGAIN); return true; } replaceConnectionByUnusedInflight(slowestConnectionIndex, UnusedConn::TRANSFER_OR_CONN_SPEED_UNDER_THRESHOLD, UnusedConn::UN_NOT_ERR); resetWatchdogPartialValues(); return false; } LOG_err << "DirectReadSlot Watchdog: [conn " << slowestConnectionIndex << "]: " << " Cannot replace unused connection by " << slowestConnectionIndex << " . Reason: too many slow connections detected"; retryEntireTransfer(API_EAGAIN); return true; } void DirectReadSlot::resetConnSwitchesCounters(const std::chrono::steady_clock::time_point& now) { mNumConnSwitchesSlowestPart = 0; mNumConnSwitchesBelowSpeedThreshold = 0; mNumConnSwitchesRecoverableError = 0; mNumConnDetectedBelowSpeedThreshold.clear(); mConnectionSwitchesLimitLastReset = now; } void DirectReadSlot::resetConnSwitchesCountersIfTimeoutExpired() { if (const auto now = std::chrono::steady_clock::now(); ((now - mConnectionSwitchesLimitLastReset) > CONNECTION_SWITCHES_LIMIT_RESET_TIME)) { resetConnSwitchesCounters(now); } } bool DirectReadSlot::isAnyRaidedPartFailed() const { if (!isRaidedTransfer()) { return false; } return std::any_of(std::begin(mReqs), std::end(mReqs), [](const std::unique_ptr& req) { return req && req->status == REQ_FAILURE; }); } bool DirectReadSlot::doio() { const auto isRaid = isRaidedTransfer(); unsigned minSpeedPerConnBytesPerSec = getMinSpeedPerConnBytesPerSec(); if (!minSpeedPerConnBytesPerSec) { minSpeedPerConnBytesPerSec = 1; // No limit (1 B/s) } if (isRaid) { // round up to a RAIDSECTOR divisible value minSpeedPerConnBytesPerSec = (minSpeedPerConnBytesPerSec + RAIDSECTOR - 1) & ~(static_cast(RAIDSECTOR) - 1); } resetConnSwitchesCountersIfTimeoutExpired(); for (unsigned int connectionNum = static_cast(mReqs.size()); connectionNum--;) { std::unique_ptr& req = mReqs[connectionNum]; bool isNotUnusedConnection = !isRaid || (connectionNum != mUnusedConn.getNum()); bool submitCondition = req && isNotUnusedConnection && (req->status == REQ_INFLIGHT || req->status == REQ_SUCCESS); if (submitCondition) { if (req->in.size()) { unsigned n = static_cast(req->in.size()); auto lastDataTime = std::chrono::duration_cast(std::chrono::steady_clock::now() - req->postStartTime).count(); m_off_t chunkTime = static_cast(lastDataTime) - mThroughput[connectionNum].second; unsigned minChunkSize; m_off_t maxChunkSize, aggregatedThroughput; if (req->status == REQ_INFLIGHT) { m_off_t updatedThroughput = calcThroughput(mThroughput[connectionNum].first + n, mThroughput[connectionNum].second + chunkTime) * 1000; m_off_t chunkThroughput = calcThroughput(static_cast(n), chunkTime) * 1000; aggregatedThroughput = (chunkThroughput + updatedThroughput) / 2; maxChunkSize = aggregatedThroughput; // 16KB as min chunk divisible size to submit. If the user's speed is even lower than 16KB/s per connection, then respect the minSpeedPerConnection. // This is to avoid small chunks to be assembled (if raid) and delivered. unsigned minChunkDivisibleSize = maxChunkSize < (16 * 1024) ? minSpeedPerConnBytesPerSec : 16 * 1024; // 16KB is divisible by RAIDSECTOR: // works for RAID and NON-RAID if (mMaxChunkSubmitted && maxChunkSize && ((std::max(static_cast(maxChunkSize), mMaxChunkSubmitted) / std::min(static_cast(maxChunkSize), mMaxChunkSubmitted)) == 1)) { // Avoid small chunks due to fragmentation caused by similar (but different) chunk sizes (compared to max submitted chunk size) maxChunkSize = mMaxChunkSubmitted; } minChunkSize = std::max(static_cast(maxChunkSize), minChunkDivisibleSize); n = (n >= minChunkSize) ? (n / minChunkDivisibleSize) * minChunkDivisibleSize : 0; } else { minChunkSize = 0; maxChunkSize = n; aggregatedThroughput = 0; } assert(!isRaidedTransfer() || (req->status == REQ_SUCCESS) || ((n % RAIDSECTOR) == 0)); if ((isRaidedTransfer() && (req->status != REQ_SUCCESS) && ((n % RAIDSECTOR) != 0))) { LOG_err << "DirectReadSlot [conn " << connectionNum << "] ERROR: (isRaid() && (req->status != REQ_SUCCESS) && ((n % " "RAIDSECTOR) != 0)" << " n = " << n << ", req->in.size = " << req->in.size() << ", req->status = " << req->status.load() << ", adapted maxChunkSize = " << maxChunkSize << ", mMaxChunkSize = " << mMaxChunkSize << ", submitted = " << mThroughput[connectionNum].first << " [this = " << this << "]"; } if (n) { mThroughput[connectionNum].first += static_cast(n); mThroughput[connectionNum].second += chunkTime; LOG_verbose << "DirectReadSlot [conn " << connectionNum << "] ->" << " FilePiece's going to be submitted: n = " << n << ", req->in.size = " << req->in.size() << ", req->in.capacity = " << req->in.capacity() << " [minChunkSize = " << minChunkSize << ", mMaxChunkSize = " << mMaxChunkSize << ", reqs.size = " << mReqs.size() << ", req->status = " << std::string(req->status == REQ_READY ? "REQ_READY" : req->status == REQ_INFLIGHT ? "REQ_INFLIGHT" : req->status == REQ_SUCCESS ? "REQ_SUCCESS" : "REQ_SOMETHING") << ", req->httpstatus = " << req->httpstatus << ", req->contentlength = " << req->contentlength << ", numReqsInflight = " << mNumReqsInflight << ", unusedRaidConnection = " << mUnusedConn.getNum() << "]" << " [chunk throughput = " << ((calcThroughput(static_cast(n), chunkTime) * 1000) / 1024) << " KB/s" << ", average throughput = " << (getThroughput(connectionNum) * 1000 / 1024) << " KB/s" << ", aggregated throughput = " << (aggregatedThroughput / 1024) << " KB/s" << ", maxChunkSize = " << (maxChunkSize / 1024) << " KBs]" << ", [req->pos_pre = " << (req->pos) << ", req->pos_now = " << (req->pos + n) << "]" << " [this = " << this << "]"; RaidBufferManager::FilePiece* np = new RaidBufferManager::FilePiece(req->pos, n); memcpy(np->buf.datastart(), req->in.c_str(), n); req->in.erase(0, n); req->contentlength -= n; req->bufpos = 0; req->pos += n; unsigned submittingConnection = isRaid ? connectionNum : 0; mDr->drbuf.submitBuffer(submittingConnection, np); if (n > mMaxChunkSubmitted) { mMaxChunkSubmitted = n; } } if (req->httpio) { req->httpio->lastdata = Waiter::ds; } req->lastdata = Waiter::ds; // we might have a raid-reassembled block to write now, or this very block in non-raid if (n && !processAnyOutputPieces()) { LOG_debug << "DirectReadSlot [conn " << connectionNum << "] Transfer is finished after processing pending output pieces. Removing DirectRead" << " [this = " << this << "]"; delete mDr; return true; } mDr->drn->schedule(DirectReadSlot::TEMPURL_TIMEOUT_DS); } if (req->status == REQ_SUCCESS && !req->in.size()) { decreaseReqsInflight(); req->status = REQ_READY; } } if (!req || req->status == REQ_READY) { bool waitForOthers = isRaid ? waitForPartsInFlight() : false; if (!waitForOthers) { if (searchAndDisconnectSlowestConnection(connectionNum)) { LOG_verbose << "DirectReadSlot [conn " << connectionNum << "] Continue DirectReadSlot loop after disconnecting slow connection " << mUnusedConn.getNum() << " [this = " << this << "]"; } bool newBufferSupplied = false, pauseForRaid = false; std::pair posrange = mDr->drbuf.nextNPosForConnection(connectionNum, newBufferSupplied, pauseForRaid); if (newBufferSupplied) { if (static_cast(connectionNum) == mUnusedConn.getNum()) { // Count the "unused connection" (restored by parity) as a req inflight, so we avoid to exec this piece of code needlessly increaseReqsInflight(); mUnusedConnIncrementedInFlightReqs = true; } // we might have a raid-reassembled block to write, or a previously loaded block, or a skip block to process. if (!processAnyOutputPieces()) { LOG_debug << "DirectReadSlot [conn " << connectionNum << "] Transfer is finished after processing pending output pieces (on new buffer supplied). Removing DirectRead" << " [this = " << this << "]"; delete mDr; return true; } } else if (!pauseForRaid) { if (posrange.first >= posrange.second) { if (req) { LOG_verbose << "DirectReadSlot [conn " << connectionNum << "] Request status set to DONE" << " [this = " << this << "]"; req->status = REQ_DONE; } bool allDone = true; for (size_t i = mReqs.size(); i--;) { if (mReqs[i] && mReqs[i]->status != REQ_DONE) { allDone = false; } } if (allDone) { LOG_debug << "DirectReadSlot [conn " << connectionNum << "] All requests are DONE: Delete read request and direct read slot" << " [this = " << this << "]"; // remove and delete completed read request, then remove slot delete mDr; return true; } } else { if (!mDr->hasValidCallback()) { LOG_err << "DirectReadSlot [conn " << connectionNum << "] There is a chunk request, but transfer is already deleted. This should never happen. Aborting" << " [this = " << this << "]"; mDr->drn->client->sendevent(99472, "DirectRead detected with a null transfer"); delete mDr; return true; } if (static_cast(connectionNum) == mUnusedConn.getNum()) { LOG_err << "DirectReadSlot [conn " << connectionNum << "] We are processing unused connection"; assert(false && "Processing unused connection when we should not!"); } if (!req) { mReqs[connectionNum] = std::make_unique(true); } if (!isRaidedTransfer()) { // Chunk size limit for non-raid: MAX_DELIVERY_CHUNK. // If the whole chunk is requested (file size), with the same request all the time, // the throughput could be too low for long periods of time, depending on the actual TCP congestion algorithm. posrange.second = std::min(posrange.second, posrange.first + DirectReadSlot::MAX_DELIVERY_CHUNK); } char buf[128]; snprintf(buf, sizeof(buf), "/%" PRIu64 "-", posrange.first); if (mDr->count) { snprintf(strchr(buf, 0), sizeof(buf) - strlen(buf), "%" PRIu64, posrange.second - 1); } req->pos = posrange.first; req->posturl = adjustURLPort(mDr->drbuf.tempURL(connectionNum)); req->posturl.append(buf); LOG_debug << "DirectReadSlot [conn " << connectionNum << "] Request chunk of size " << (posrange.second - posrange.first) << " (request status = " << req->status.load() << ")" << " [this = " << this << "]"; LOG_debug << req->getLogName() << "POST URL: " << req->posturl; mThroughput[connectionNum].first = 0; mThroughput[connectionNum].second = 0; req->in.reserve(mMaxChunkSize + (mMaxChunkSize/2)); req->post(mDr->drn->client); // status will go to inflight or fail LOG_verbose << req->getLogName() << "DirectReadSlot [conn " << connectionNum << "] POST done (new request status = " << req->status.load() << ")" << " [this = " << this << "]"; mDr->drbuf.transferPos(connectionNum) = posrange.second; increaseReqsInflight(); } } } } if (req && req->status == REQ_FAILURE) { LOG_warn << "DirectReadSlot [conn " << connectionNum << "] Request status is FAILURE [Request status = " << req->status.load() << ", HTTP status = " << req->httpstatus << "]" << ", ErrCode = " << req->mErrCode << "]" << " [this = " << this << "]"; onFailure(req, static_cast(connectionNum), req->mErrCode); return true; } if (watchOverDirectReadPerformance()) { LOG_debug << "DirectReadSlot [conn " << connectionNum << "] DirectReadSlot will be retried" << " [this = " << this << "]"; return true; } } return false; } void DirectReadSlot::onFailure(std::unique_ptr& req, const size_t connectionNum, const unsigned errCode) { if (!mDr->hasValidCallback()) { LOG_err << "DirectReadSlot [conn " << connectionNum << "] Request failed, but transfer is already deleted. Aborting" << " [this = " << this << "]"; mDr->drn->client->sendevent(99472, "DirectRead detected with a null transfer"); delete mDr; } else { if (auto isBwOverquotaErr = req->httpstatus == 509; isBwOverquotaErr) { LOG_warn << "DirectReadSlot Bandwidth overquota from storage server for streaming transfer" << " [this = " << this << "]"; dstime backoff = mDr->drn->client->overTransferQuotaBackoff(req.get()); retryEntireTransfer(API_EOVERQUOTA, backoff); } else { retryOnError(connectionNum, req->httpstatus, errCode); } } } // abort active read, remove from pending queue void DirectRead::abort() { delete drs; drs = NULL; if (drq_it != drn->client->drq.end()) { drn->client->drq.erase(drq_it); drq_it = drn->client->drq.end(); } } m_off_t DirectRead::drMaxReqSize() const { m_off_t numParts = drn->tempurls.size() == RAIDPARTS ? static_cast(EFFECTIVE_RAIDPARTS) : static_cast(drn->tempurls.size()); return std::max(drn->size / numParts, TransferSlot::MAX_REQ_SIZE); } void DirectRead::revokeCallback(void* appData) { assert(callback); DirectRead::CallbackParam param{std::in_place_type, appData}; callback(param); } bool DirectRead::onData(byte* buffer, m_off_t len, m_off_t theOffset, m_off_t speed, m_off_t meanSpeed) { assert(callback); DirectRead::CallbackParam param{std::in_place_type, buffer, len, theOffset, speed, meanSpeed}; callback(param); return std::get(param).ret; } dstime DirectRead::onFailure(const Error& e, int retry, dstime timeLeft) { assert(callback); DirectRead::CallbackParam param{std::in_place_type, e, retry, timeLeft}; callback(param); return std::get(param).ret; } bool DirectRead::hasValidCallback() { assert(callback); DirectRead::CallbackParam param{std::in_place_type}; callback(param); return std::get(param).ret; } DirectRead::DirectRead(DirectReadNode* cdrn, m_off_t ccount, m_off_t coffset, int creqtag, Callback&& callback): drbuf(this), callback(std::move(callback)) { LOG_debug << "[DirectRead::DirectRead] New DirectRead " << "[this = " << this << "]"; drn = cdrn; count = ccount; offset = coffset; progress = 0; reqtag = creqtag; drs = NULL; reads_it = drn->reads.insert(drn->reads.end(), this); if (!drn->tempurls.empty()) { // we already have tempurl(s): queue for immediate fetching m_off_t streamingMaxReqSize = drMaxReqSize(); LOG_debug << "Direct read start -> direct read node size = " << drn->size << ", streaming max request size: " << streamingMaxReqSize; drbuf.setIsRaid(drn->tempurls, offset, offset + count, drn->size, streamingMaxReqSize, false); drq_it = drn->client->drq.insert(drn->client->drq.end(), this); } else { // no tempurl yet or waiting for a retry drq_it = drn->client->drq.end(); } } DirectRead::~DirectRead() { LOG_debug << "Deleting DirectRead" << " [this = " << this << "]"; abort(); if (reads_it != drn->reads.end()) { drn->reads.erase(reads_it); } } std::string DirectReadSlot::adjustURLPort(std::string url) { if (Utils::startswith(url, "http:")) { size_t portendindex = url.find("/", 8); size_t portstartindex = url.find(":", 8); if (portendindex != string::npos) { if (portstartindex == string::npos) { if (mDr->drn->client->usealtdownport) { LOG_debug << "Enabling alternative port for streaming transfer"; url.insert(portendindex, ":8080"); } } else { if (!mDr->drn->client->usealtdownport) { LOG_debug << "Disabling alternative port for streaming transfer"; url.erase(portstartindex, portendindex - portstartindex); } } } } return url; } // request DirectRead's range via tempurl DirectReadSlot::DirectReadSlot(DirectRead* cdr) { LOG_debug << "[DirectReadSlot::DirectReadSlot] New DirectReadSlot [cdr = " << (void*)cdr << "]" << " [this = " << this << "]"; mDr = cdr; mPos = mDr->offset + mDr->progress; mDr->nextrequestpos = mPos; mSpeed = mMeanSpeed = 0; assert(mReqs.empty()); const auto numReqs = isRaidedTransfer() ? mDr->drbuf.tempUrlVector().size() : 1; assert(isRaidedTransfer() ? (numReqs == RAIDPARTS) : 1); for (size_t i = numReqs; i--; ) { mReqs.push_back(std::make_unique(true)); mReqs.back()->status = REQ_READY; mReqs.back()->type = REQ_BINARY; } LOG_verbose << "[DirectReadSlot::DirectReadSlot] Num requests: " << numReqs << " [this = " << this << "]"; mThroughput.resize(mReqs.size()); mUnusedConn.clear(); unsigned auxUnused = static_cast(mReqs.size()); if (isRaidedTransfer()) { const auto un = mDr->drbuf.getUnusedRaidConnection(); auxUnused = un < numReqs ? un : DEFAULT_UNUSED_CONN_INDEX; } LOG_verbose << "[DirectReadSlot::DirectReadSlot] Set initial unused raid connection to " << auxUnused << " [this = " << this << "]"; mDr->drbuf.setUnusedRaidConnection(auxUnused); mUnusedConn.setUnused(auxUnused, UnusedConn::UN_NOT_ERR); mNumConnDetectedBelowSpeedThreshold.clear(); mNumConnSwitchesSlowestPart = 0; mNumConnSwitchesRecoverableError = 0; mNumConnSwitchesBelowSpeedThreshold = 0; mNumReqsInflight = 0; mWaitForParts = false; mMaxChunkSubmitted = 0; mDrs_it = mDr->drn->client->drss.insert(mDr->drn->client->drss.end(), this); mDr->drn->partiallen = 0; mDr->drn->partialstarttime = Waiter::ds; mMaxChunkSize = static_cast(static_cast(DirectReadSlot::MAX_DELIVERY_CHUNK) / (mReqs.size() == static_cast(RAIDPARTS) ? (static_cast(EFFECTIVE_RAIDPARTS)) : mReqs.size())); if (isRaidedTransfer()) { mMaxChunkSize -= mMaxChunkSize % RAIDSECTOR; } mMinComparableThroughput = DirectReadSlot::DEFAULT_MIN_COMPARABLE_THROUGHPUT; mSlotStartTime = std::chrono::steady_clock::now(); mConnectionSwitchesLimitLastReset = std::chrono::steady_clock::now(); } DirectReadSlot::~DirectReadSlot() { mDr->drn->client->drss.erase(mDrs_it); LOG_debug << "Deleting DirectReadSlot" << " [this = " << this << "]"; } bool priority_comparator(const LazyEraseTransferPtr& i, const LazyEraseTransferPtr& j) { return (i.transfer ? i.transfer->priority : i.preErasurePriority) < (j.transfer ? j.transfer->priority : j.preErasurePriority); } TransferList::TransferList() { currentpriority = PRIORITY_START; } void TransferList::addtransfer(Transfer *transfer, TransferDbCommitter& committer, bool startFirst) { if (transfer->state != TRANSFERSTATE_PAUSED) { transfer->state = TRANSFERSTATE_QUEUED; } assert(transfer->type == PUT || transfer->type == GET); if (!transfer->priority) { if (startFirst && transfers[transfer->type].size()) { transfer_list::iterator dstit = transfers[transfer->type].begin(); transfer->priority = dstit->transfer->priority - PRIORITY_STEP; prepareIncreasePriority(transfer, transfers[transfer->type].end(), dstit, committer); transfers[transfer->type].push_front(transfer); } else { currentpriority += PRIORITY_STEP; transfer->priority = currentpriority; assert(!transfers[transfer->type].size() || transfers[transfer->type][transfers[transfer->type].size() - 1]->priority < transfer->priority); transfers[transfer->type].push_back(transfer); } client->transfercacheadd(transfer, &committer); } else { transfer_list::iterator it = std::lower_bound(transfers[transfer->type].begin(), transfers[transfer->type].end(), LazyEraseTransferPtr(transfer), priority_comparator); assert(it == transfers[transfer->type].end() || it->transfer->priority != transfer->priority); transfers[transfer->type].insert(it, transfer); } } void TransferList::removetransfer(Transfer *transfer) { transfer_list::iterator it; if (getIterator(transfer, it, true)) { transfers[transfer->type].erase(it); } } void TransferList::movetransfer(Transfer *transfer, Transfer *prevTransfer, TransferDbCommitter& committer) { transfer_list::iterator dstit; if (getIterator(prevTransfer, dstit)) { movetransfer(transfer, dstit, committer); } } void TransferList::movetransfer(Transfer *transfer, unsigned int position, TransferDbCommitter& committer) { transfer_list::iterator dstit; if (position >= transfers[transfer->type].size()) { dstit = transfers[transfer->type].end(); } else { dstit = transfers[transfer->type].begin() + position; } transfer_list::iterator it; if (getIterator(transfer, it)) { movetransfer(it, dstit, committer); } } void TransferList::movetransfer(Transfer *transfer, transfer_list::iterator dstit, TransferDbCommitter& committer) { transfer_list::iterator it; if (getIterator(transfer, it)) { movetransfer(it, dstit, committer); } } void TransferList::movetransfer(transfer_list::iterator it, transfer_list::iterator dstit, TransferDbCommitter& committer) { if (it == dstit) { LOG_warn << "Trying to move before the same transfer"; return; } if ((it + 1) == dstit) { LOG_warn << "Trying to move to the same position"; return; } Transfer *transfer = (*it); assert(transfer->type == PUT || transfer->type == GET); if (dstit == transfers[transfer->type].end()) { LOG_debug << "Moving transfer to the last position"; prepareDecreasePriority(transfer, it, dstit); transfers[transfer->type].erase(it); currentpriority += PRIORITY_STEP; transfer->priority = currentpriority; assert(!transfers[transfer->type].size() || transfers[transfer->type][transfers[transfer->type].size() - 1]->priority < transfer->priority); transfers[transfer->type].push_back(transfer); client->transfercacheadd(transfer, &committer); client->app->transfer_update(transfer); return; } int srcindex = int(std::distance(transfers[transfer->type].begin(), it)); int dstindex = int(std::distance(transfers[transfer->type].begin(), dstit)); LOG_debug << "Moving transfer from " << srcindex << " to " << dstindex; uint64_t prevpriority = 0; uint64_t nextpriority = 0; nextpriority = dstit->transfer->priority; if (dstit != transfers[transfer->type].begin()) { transfer_list::iterator previt = dstit - 1; prevpriority = previt->transfer->priority; } else { prevpriority = nextpriority - 2 * PRIORITY_STEP; } uint64_t newpriority = (prevpriority + nextpriority) / 2; LOG_debug << "Moving transfer between priority " << prevpriority << " and " << nextpriority << ". New: " << newpriority; if (prevpriority == newpriority) { LOG_warn << "There is no space for the move. Adjusting priorities."; int positions = dstindex; uint64_t fixedPriority = transfers[transfer->type][0]->priority - PRIORITY_STEP * (static_cast(positions) + 1); for (int i = 0; i < positions; i++) { Transfer* t = transfers[transfer->type][static_cast(i)]; LOG_debug << "Adjusting priority of transfer " << i << " to " << fixedPriority; t->priority = fixedPriority; client->transfercacheadd(t, &committer); client->app->transfer_update(t); fixedPriority += PRIORITY_STEP; } newpriority = fixedPriority; LOG_debug << "Fixed priority: " << fixedPriority; } transfer->priority = newpriority; if (srcindex > dstindex) { prepareIncreasePriority(transfer, it, dstit, committer); } else { prepareDecreasePriority(transfer, it, dstit); dstindex--; } transfers[transfer->type].erase(it); transfer_list::iterator fit = transfers[transfer->type].begin() + dstindex; assert(fit == transfers[transfer->type].end() || fit->transfer->priority != transfer->priority); transfers[transfer->type].insert(fit, transfer); client->transfercacheadd(transfer, &committer); client->app->transfer_update(transfer); } void TransferList::movetofirst(Transfer *transfer, TransferDbCommitter& committer) { movetransfer(transfer, transfers[transfer->type].begin(), committer); } void TransferList::movetofirst(transfer_list::iterator it, TransferDbCommitter& committer) { Transfer *transfer = (*it); movetransfer(it, transfers[transfer->type].begin(), committer); } void TransferList::movetolast(Transfer *transfer, TransferDbCommitter& committer) { movetransfer(transfer, transfers[transfer->type].end(), committer); } void TransferList::movetolast(transfer_list::iterator it, TransferDbCommitter& committer) { Transfer *transfer = (*it); movetransfer(it, transfers[transfer->type].end(), committer); } void TransferList::moveup(Transfer *transfer, TransferDbCommitter& committer) { transfer_list::iterator it; if (getIterator(transfer, it)) { if (it == transfers[transfer->type].begin()) { return; } transfer_list::iterator dstit = it - 1; movetransfer(it, dstit, committer); } } void TransferList::moveup(transfer_list::iterator it, TransferDbCommitter& committer) { if (it == transfers[it->transfer->type].begin()) { return; } transfer_list::iterator dstit = it - 1; movetransfer(it, dstit, committer); } void TransferList::movedown(Transfer *transfer, TransferDbCommitter& committer) { transfer_list::iterator it; if (getIterator(transfer, it)) { transfer_list::iterator dstit = it + 1; if (dstit == transfers[transfer->type].end()) { return; } dstit++; movetransfer(it, dstit, committer); } } void TransferList::movedown(transfer_list::iterator it, TransferDbCommitter& committer) { if (it == transfers[it->transfer->type].end()) { return; } transfer_list::iterator dstit = it + 1; movetransfer(it, dstit, committer); } error TransferList::pause(Transfer *transfer, bool enable, TransferDbCommitter& committer) { if (!transfer) { return API_ENOENT; } if ((enable && transfer->state == TRANSFERSTATE_PAUSED) || (!enable && transfer->state != TRANSFERSTATE_PAUSED)) { return API_OK; } if (!enable) { transfer->state = TRANSFERSTATE_QUEUED; transfer_list::iterator it; if (getIterator(transfer, it)) { prepareIncreasePriority(transfer, it, it, committer); } client->transfercacheadd(transfer, &committer); client->app->transfer_update(transfer); return API_OK; } if (transfer->state == TRANSFERSTATE_ACTIVE || transfer->state == TRANSFERSTATE_QUEUED || transfer->state == TRANSFERSTATE_RETRYING) { if (transfer->slot) { if (transfer->client->ststatus != STORAGE_RED || transfer->type == GET) { transfer->bt.arm(); } delete transfer->slot; transfer->slot = NULL; } transfer->state = TRANSFERSTATE_PAUSED; client->transfercacheadd(transfer, &committer); client->app->transfer_update(transfer); return API_OK; } return API_EFAILED; } auto TransferList::begin(direction_t direction) -> transfer_list::iterator { return transfers[direction].begin(); } auto TransferList::end(direction_t direction) -> transfer_list::iterator { return transfers[direction].end(); } bool TransferList::getIterator(Transfer *transfer, transfer_list::iterator& it, bool canHandleErasedElements) { assert(transfer); if (!transfer) { LOG_err << "Getting iterator of a NULL transfer"; return false; } assert(transfer->type == GET || transfer->type == PUT); if (transfer->type != GET && transfer->type != PUT) { LOG_err << "Getting iterator of wrong transfer type " << transfer->type; return false; } it = std::lower_bound(transfers[transfer->type].begin(canHandleErasedElements), transfers[transfer->type].end(canHandleErasedElements), LazyEraseTransferPtr(transfer), priority_comparator); if (it != transfers[transfer->type].end(canHandleErasedElements) && it->transfer == transfer) { return true; } return false; } std::array, 6> TransferList::nexttransfers(std::function& continuefunction, std::function& directionContinuefunction, TransferDbCommitter& committer) { std::array, 6> chosenTransfers; static direction_t putget[] = { PUT, GET }; for (direction_t direction : putget) { for (Transfer *transfer : transfers[direction]) { if (!transfer->slot) { // check for cancellation here before we go to the trouble of requesting a download/upload URL transfer->removeCancelledTransferFiles(&committer); if (transfer->files.empty()) { transfer->removeAndDeleteSelf(TRANSFERSTATE_CANCELLED); continue; } } // don't traverse the whole list if we already have as many as we are going to get if (!directionContinuefunction(direction)) break; bool continueLarge = true; bool continueSmall = true; if ((!transfer->slot && isReady(transfer)) || (transfer->asyncopencontext && transfer->asyncopencontext->finished)) { TransferCategory tc(transfer); if (tc.sizetype == LARGEFILE && continueLarge) { continueLarge = continuefunction(transfer); if (continueLarge) { chosenTransfers[tc.index()].push_back(transfer); } } else if (tc.sizetype == SMALLFILE && continueSmall) { continueSmall = continuefunction(transfer); if (continueSmall) { chosenTransfers[tc.index()].push_back(transfer); } } if (!continueLarge && !continueSmall) { break; } } } } return chosenTransfers; } Transfer *TransferList::transferat(direction_t direction, unsigned int position) { if (transfers[direction].size() > position) { return transfers[direction][position]; } return NULL; } void TransferList::prepareIncreasePriority(Transfer *transfer, transfer_list::iterator /*srcit*/, transfer_list::iterator dstit, TransferDbCommitter& committer) { assert(transfer->type == PUT || transfer->type == GET); if (dstit == transfers[transfer->type].end()) { return; } if (!transfer->slot && transfer->state != TRANSFERSTATE_PAUSED) { Transfer *lastActiveTransfer = NULL; for (transferslot_list::iterator it = client->tslots.begin(); it != client->tslots.end(); it++) { Transfer *t = (*it)->transfer; if (t && t->type == transfer->type && t->slot && t->state == TRANSFERSTATE_ACTIVE && t->priority > transfer->priority && (!lastActiveTransfer || t->priority > lastActiveTransfer->priority)) { lastActiveTransfer = t; } } if (lastActiveTransfer) { if (lastActiveTransfer->client->ststatus != STORAGE_RED || lastActiveTransfer->type == GET) { lastActiveTransfer->bt.arm(); } delete lastActiveTransfer->slot; lastActiveTransfer->slot = NULL; lastActiveTransfer->state = TRANSFERSTATE_QUEUED; client->transfercacheadd(lastActiveTransfer, &committer); client->app->transfer_update(lastActiveTransfer); } } } void TransferList::prepareDecreasePriority(Transfer *transfer, transfer_list::iterator it, transfer_list::iterator dstit) { assert(transfer->type == PUT || transfer->type == GET); if (transfer->slot && transfer->state == TRANSFERSTATE_ACTIVE) { transfer_list::iterator cit = it + 1; while (cit != transfers[transfer->type].end()) { if (!cit->transfer->slot && isReady(*cit)) { if (transfer->client->ststatus != STORAGE_RED || transfer->type == GET) { transfer->bt.arm(); } delete transfer->slot; transfer->slot = NULL; transfer->state = TRANSFERSTATE_QUEUED; break; } if (cit == dstit) { break; } cit++; } } } bool TransferList::isReady(Transfer *transfer) { return ((transfer->state == TRANSFERSTATE_QUEUED || transfer->state == TRANSFERSTATE_RETRYING) && transfer->bt.armed()); } } // namespace sdk-10.11.0/src/transferslot.cpp000066400000000000000000002351761516266226600165060ustar00rootroot00000000000000/** * @file transferslot.cpp * @brief Class for active transfer * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/transferslot.h" #include "mega/node.h" #include "mega/transfer.h" #include "mega/megaclient.h" #include "mega/command.h" #include "mega/base64.h" #include "mega/megaapp.h" #include "mega/utils.h" #include "mega/logging.h" #include "mega/raid.h" #include "mega/testhooks.h" namespace mega { TransferSlotFileAccess::TransferSlotFileAccess(std::unique_ptr&& p, Transfer* t) : transfer(t) { reset(std::move(p)); } TransferSlotFileAccess::~TransferSlotFileAccess() { reset(); } void TransferSlotFileAccess::reset(std::unique_ptr&& p) { fa = std::move(p); // transfer has no slot or slot has no fa: timer is enabled transfer->bt.enable(!!p); } double stats::TransferSlotStats::failedRequestRatio() const { assert(mNumFailedRequests <= mNumTotalRequests); if (!mNumFailedRequests || !mNumTotalRequests) { return 0; } return static_cast(mNumFailedRequests) / static_cast(mNumTotalRequests); } double stats::TransferSlotStats::averageConnectTime() const { assert(mTotalConnectTime != -1 || !mNumRequestsWithCalculatedLatency); if (mTotalConnectTime <= 0 || !mNumRequestsWithCalculatedLatency) { return 0; } return std::round(mTotalConnectTime / static_cast(mNumRequestsWithCalculatedLatency)); } double stats::TransferSlotStats::averageStartTransferTime() const { assert(mTotalStartTransferTime != -1 || !mNumRequestsWithCalculatedLatency); if (mTotalStartTransferTime <= 0 || !mNumRequestsWithCalculatedLatency) { return 0; } return std::round(mTotalStartTransferTime / static_cast(mNumRequestsWithCalculatedLatency)); } // transfer attempts are considered failed after XFERTIMEOUT deciseconds // without data flow const dstime TransferSlot::XFERTIMEOUT = 600; // max time without progress callbacks const dstime TransferSlot::PROGRESSTIMEOUT = 10; #if defined(__ANDROID__) || defined(USE_IOS) const m_off_t TransferSlot::MAX_REQ_SIZE = 2097152; // 2 MB #elif defined (_WIN32) || defined(HAVE_AIO_RT) const m_off_t TransferSlot::MAX_REQ_SIZE = 41943040; // 40 MB [Previous value: 16777216 -> 16 MB] #else const m_off_t TransferSlot::MAX_REQ_SIZE = 16777216; // 16 MB [Previous value: 4194304 -> 4 MB] #endif const m_off_t TransferSlot::MAX_REQ_SIZE_NEW_RAID = 2 * 1024 * 1024; // 2 MB for each raidpart const m_off_t TransferSlot::UPPER_FILESIZE_LIMIT_FOR_SMALLER_CHUNKS = 25 * 1024 * 1024; // 25 MB const m_off_t TransferSlot::MIN_FILESIZE_FOR_MULTIPLE_CONNECTIONS = 131072 + 1; // 128 KB + 1 -> legacy value const m_off_t TransferSlot::MAX_GAP_SIZE = 256 * 1024 * 1024; // 256 MB TransferSlot::TransferSlot(Transfer* ctransfer) : fa(ctransfer->client->fsaccess->newfileaccess(), ctransfer) , retrybt(ctransfer->client->rng, ctransfer->client->transferSlotsBackoff) { starttime = 0; lastprogressreport = 0; progressreported = 0; speed = meanSpeed = 0; lastdata = Waiter::ds; errorcount = 0; lasterror = API_OK; failure = false; retrying = false; connections = 0; asyncIO = NULL; pendingcmd = NULL; transfer = ctransfer; transfer->slot = this; transfer->state = TRANSFERSTATE_ACTIVE; slots_it = transfer->client->tslots.end(); maxRequestSize = MAX_REQ_SIZE; #if defined(_WIN32) MEMORYSTATUSEX statex; memset(&statex, 0, sizeof (statex)); statex.dwLength = sizeof (statex); if (GlobalMemoryStatusEx(&statex)) { LOG_debug << "[Windows] RAM stats. Free physical: " << statex.ullAvailPhys << " Free virtual: " << statex.ullAvailVirtual; if (statex.ullAvailPhys < 1073741824 // 1024 MB || statex.ullAvailVirtual < 1073741824) { if (statex.ullAvailPhys < 536870912 // 512 MB || statex.ullAvailVirtual < 536870912) { if (statex.ullAvailPhys < 268435456 // 256 MB || statex.ullAvailVirtual < 268435456) { maxRequestSize = 2097152; // 2 MB } else { maxRequestSize = 4194304; // 4 MB } } else { maxRequestSize = 8388608; // 8 MB } } } else { LOG_warn << "[Windows] Error getting RAM usage info"; } #endif } bool TransferSlot::createconnectionsonce() { // delay creating these until we know if it's raid or non-raid if (!(connections || reqs.size() || asyncIO)) { if (transferbuf.tempUrlVector().empty()) { return false; // too soon, we don't know raid / non-raid yet } connections = transferbuf.isRaid() ? RAIDPARTS : transfer->size >= MIN_FILESIZE_FOR_MULTIPLE_CONNECTIONS ? transfer->client->connections[transfer->type] : 1; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED if (transfer->size >= MIN_FILESIZE_FOR_MULTIPLE_CONNECTIONS && transferbuf.isNewRaid()) { DEBUG_TEST_HOOK_NUMBER_OF_CONNECTIONS(connections, transfer->client->connections[transfer->type]) } #endif LOG_debug << "Populating transfer slot with " << connections << " connections, max request size of " << maxRequestSize << " bytes [transferbuf.isNewRaid() = " << transferbuf.isNewRaid() << "] [isDownload = " << (transfer->type == GET) << "]"; reqs.resize(static_cast(connections)); mReqSpeeds.resize(static_cast(connections)); asyncIO = new AsyncIOContext*[static_cast(connections)](); if (transferbuf.isNewRaid()) { transfer->slot->initCloudRaid(transfer->client); } } return true; } // delete slot and associated resources, but keep transfer intact (can be // reused on a new slot) TransferSlot::~TransferSlot() { LOG_verbose << "[TransferSlot::~TransferSlot] BEGIN [cloudRaid = " << (void*)(cloudRaid.get()) << "]"; LOG_verbose << "Deleting TransferSlot"; if (transfer->type == GET && !transfer->finished && transfer->progresscompleted != transfer->size && !transfer->asyncopencontext) { bool cachetransfer = false; // need to save in cache if (fa && fa->asyncavailable()) { for (unsigned int i = 0; static_cast(i) < connections; i++) { if (reqs[i] && reqs[i]->status == REQ_ASYNCIO && asyncIO[i]) { asyncIO[i]->finish(); if (!asyncIO[i]->failed) { LOG_verbose << "[TransferSlot::~TransferSlot] Conn " << i << " : Async write succeeded (size: " << asyncIO[i]->dataBufferLen << ")"; transferbuf.bufferWriteCompleted(i, true); cachetransfer = true; } else { LOG_verbose << "[TransferSlot::~TransferSlot] Conn " << i << " : Async write failed (size: " << asyncIO[i]->dataBufferLen << ")"; transferbuf.bufferWriteCompleted(i, false); } reqs[i]->status = REQ_READY; } delete asyncIO[i]; asyncIO[i] = NULL; } // Open the file in synchonous mode fa.reset(transfer->client->fsaccess->newfileaccess()); if (!fa->fopen(transfer->localfilename, OPEN_WRONLY, FSLogging::logOnError)) { fa.reset(); } } for (unsigned int i = 0; static_cast(i) < connections; i++) { if (HttpReqDL *downloadRequest = static_cast(reqs[i].get())) { switch (static_cast(downloadRequest->status)) { case REQ_INFLIGHT: if (fa && downloadRequest && downloadRequest->status == REQ_INFLIGHT && downloadRequest->contentlength == downloadRequest->size && downloadRequest->bufpos >= SymmCipher::BLOCKSIZE) { HttpReq::http_buf_t* buf = downloadRequest->release_buf(); buf->end -= buf->datalen() % RAIDSECTOR; transferbuf.submitBuffer(i, new TransferBufferManager::FilePiece(downloadRequest->dlpos, buf)); // resets size & bufpos of downloadrequest. } break; case REQ_DECRYPTING: { LOG_info << "[TransferSlot::~TransferSlot] Conn " << i << " : Waiting for block decryption"; std::mutex finalizedMutex; std::unique_lock guard(finalizedMutex); auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); outputPiece->finalizedCV.wait(guard, [&](){ return outputPiece->finalized; }); downloadRequest->status = REQ_DECRYPTED; break; } default: break; } } } bool anyData = true; while (anyData) { anyData = false; for (unsigned int i = 0; static_cast(i) < connections; ++i) { // synchronous writes for all remaining outstanding data (for raid, there can be a sequence of output pieces. for non-raid, one piece per connection) // check each connection first and then all that were not yet on a connection auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); if (outputPiece) { if (!outputPiece->finalized) { SymmCipher *cipher = transfer->client->getRecycledTemporaryTransferCipher(transfer->transferkey.data()); outputPiece->finalize(true, transfer->size, transfer->ctriv, cipher, &transfer->chunkmacs); } anyData = true; if (fa && fa->fwrite(outputPiece->buf.datastart(), static_cast(outputPiece->buf.datalen()), outputPiece->pos)) { LOG_verbose << "[TransferSlot::~TransferSlot] Conn " << i << " : Sync write succeeded (size: " << outputPiece->buf.datalen() << ")"; transferbuf.bufferWriteCompleted(i, true); cachetransfer = true; } else { LOG_err << "[TransferSlot::~TransferSlot] Conn " << i << " : Error caching data at: " << outputPiece->pos << " (size: " << outputPiece->buf.datalen() << ")"; transferbuf.bufferWriteCompleted(i, false); // throws the data away so we can move on to the next one } } } } if (cloudRaid) { LOG_debug << "[TransferSlot::~TransferSlot] Stop cloudRaid"; cloudRaid->stop(); } if (cachetransfer) { transfer->client->transfercacheadd(transfer, nullptr); LOG_debug << "[TransferSlot::~TransferSlot] Cachetransfer. Completed: " << transfer->progresscompleted; } } processTransferStats(); transfer->slot = NULL; if (slots_it != transfer->client->tslots.end()) { // advance main loop iterator if deleting next in line if (transfer->client->slotit != transfer->client->tslots.end() && *transfer->client->slotit == this) { transfer->client->slotit++; } transfer->client->tslots.erase(slots_it); transfer->client->performanceStats.transferFinishes += 1; } if (pendingcmd) { pendingcmd->cancel(); } if (transfer->asyncopencontext) { transfer->asyncopencontext.reset(); transfer->client->asyncfopens--; } while (connections--) { delete asyncIO[connections]; } delete[] asyncIO; LOG_verbose << "[TransferSlot::~TransferSlot] END [cloudRaid = " << (void*)(cloudRaid.get()) << "]"; } void TransferSlot::processTransferStats() { const auto transferTypeStrV = std::invoke( [transferType = transfer->type, isRaid = (cloudRaid != nullptr)]() -> std::string_view { switch (transferType) { case PUT: return "[Upload]"; case GET: return (isRaid ? "[CloudRaid]" : "[Non CloudRaid]"); case API: return "[API]"; default: assert(false && "Unknown transfer type"); return "[Unknown]"; } }); LOG_verbose << "[TransferSlot::processTransferStats] " << transferTypeStrV << " Stats: Mean speed: " << (meanSpeed) << " B/s, Progress: " << transfer->progresscompleted << "/" << transfer->size << " (" << static_cast( std::round(static_cast(transfer->progresscompleted * 100) / static_cast(transfer->size))) << "%)" << ". TransferSlotStats: FailedRequestRatio = " << tsStats.failedRequestRatio() << ". Average connect time: " << tsStats.averageConnectTime() << " ms. Average start transfer time: " << tsStats.averageStartTransferTime() << " ms. Total requests = " << tsStats.mNumTotalRequests << " (with calculated latency: " << tsStats.mNumRequestsWithCalculatedLatency << "). Failed requests = " << tsStats.mNumFailedRequests << ". [Transfer->name = " << transfer->localfilename << "]" << " [cloudRaid = " << (void*)(cloudRaid.get()) << "]"; const auto addedToTransferStats = transfer->addTransferStats(); LOG_verbose << "[TransferSlot::processTransferStats] " << transferTypeStrV << " Stats for this transfer have " << (addedToTransferStats ? "" : "NOT ") << "been added to the global transfer stats"; if (addedToTransferStats) { transfer->collectAndPrintTransferStatsIfLimitReached(); } } void TransferSlot::toggleport(HttpReqXfer *req) { if (Utils::startswith(req->posturl, "http:")) { size_t portendindex = req->posturl.find("/", 8); size_t portstartindex = req->posturl.find(":", 8); if (portendindex != string::npos) { if (portstartindex == string::npos) { LOG_debug << "Enabling alternative port for chunk"; req->posturl.insert(portendindex, ":8080"); } else { LOG_debug << "Disabling alternative port for chunk"; req->posturl.erase(portstartindex, portendindex - portstartindex); } } } } // abort all HTTP connections void TransferSlot::disconnect() { for (int i = connections; i--;) { disconnect(static_cast(i)); } } // abort one HTTP connection void TransferSlot::disconnect(unsigned connectionNum) { if (reqs[connectionNum]) { disconnect(reqs[connectionNum]); } } // abort one HTTP req void TransferSlot::disconnect(const std::shared_ptr& req) { req->disconnect(); } int64_t TransferSlot::macsmac(chunkmac_map* m) { return m->macsmac(transfer->transfercipher()); } int64_t TransferSlot::macsmac_gaps(chunkmac_map* m, size_t g1, size_t g2, size_t g3, size_t g4) { return m->macsmac_gaps(transfer->transfercipher(), g1, g2, g3, g4); } bool TransferSlot::checkMetaMacWithMissingLateEntries() { // Due to an old bug, some uploads attached a MAC to the node that was missing some MAC entries // (even though the data was uploaded) - this occurred when a ultoken arrived but one other // final upload connection had not completed at the local end (even though it must have // completed at the server end). So the file's data is still complete in the cloud. // Here we check if the MAC is one of those with a missing entry (or a few if the connection had multiple chunks) // last 3 connections, up to 32MB (ie chunks) each, up to two completing after the one that delivered the ultoken size_t end = transfer->chunkmacs.size(); size_t finalN = std::min(32 * 3, end); // first check for the most likely - a single connection gap (or two but completely consecutive making a single gap) for (size_t countBack = 1; countBack <= finalN; ++countBack) { size_t start1 = end - countBack; for (size_t len1 = 1; len1 <= 64 && start1 + len1 <= end; ++len1) { if (transfer->metamac == macsmac_gaps(&transfer->chunkmacs, start1, start1 + len1, end, end)) { LOG_warn << "Found mac gaps were at " << start1 << " " << len1 << " from " << end; auto correctMac = macsmac(&transfer->chunkmacs); transfer->metamac = correctMac; // TODO: update the Node's key to be correct (needs some API additions before enabling) return true; } } } // now check for two separate pieces missing (much less likely) // limit to checking up to 16Mb pieces wtih up to 8Mb between to avoid excessive CPU // takes about 1 second on a fairly modest laptop for a 100Mb file (in a release build) finalN = std::min(16 * 2 + 8, transfer->chunkmacs.size()); for (size_t start1 = end - finalN; start1 < end; ++start1) { for (size_t len1 = 1; len1 <= 16 && start1 + len1 <= end; ++len1) { for (size_t start2 = start1 + len1 + 1; start2 < transfer->chunkmacs.size(); ++start2) { for (size_t len2 = 1; len2 <= 16 && start2 + len2 <= end; ++len2) { if (transfer->metamac == macsmac_gaps(&transfer->chunkmacs, start1, start1 + len1, start2, start2 + len2)) { LOG_warn << "Found mac gaps were at " << start1 << " " << len1 << " " << start2 << " " << len2 << " from " << end; auto correctMac = macsmac(&transfer->chunkmacs); transfer->metamac = correctMac; // TODO: update the Node's key to be correct (needs some API additions before enabling) return true; } } } } } return false; } bool TransferSlot::checkDownloadTransferFinished(TransferDbCommitter& committer, MegaClient* client) { if (transfer->progresscompleted == transfer->size) { // verify meta MAC if (!transfer->size || (macsmac(&transfer->chunkmacs) == transfer->metamac) || checkMetaMacWithMissingLateEntries()) { client->transfercacheadd(transfer, &committer); if (transfer->progresscompleted != progressreported) { progressreported = transfer->progresscompleted; lastdata = Waiter::ds; progress(); } transfer->complete(committer); } else { client->sendevent(99431, "MAC verification failed", 0); transfer->chunkmacs.clear(); transfer->failed(API_EKEY, committer); } return true; } return false; } bool TransferSlot::testForSlowRaidConnection(unsigned connectionNum, bool& incrementErrors) { if (transfer->type == GET && transferbuf.isRaid()) { // quick early check - if we were getting data but haven't for a while // then switch channels before we time out entirely (at the halfway-to-timeout mark) if ((Waiter::ds - reqs[connectionNum]->lastdata) > (XFERTIMEOUT / 2)) { LOG_warn << "Raid connection " << connectionNum << " has not received data for " << (XFERTIMEOUT / 2) << " deciseconds"; incrementErrors = true; return true; } if (!transferbuf.isUnusedRaidConection(connectionNum) // connection in use && mReqSpeeds[connectionNum].requestElapsedDs() > 50 // enough elapsed time to be considered && mRaidChannelSwapsForSlowness < 2) // no more than 2 swaps due to slown connections { m_off_t averageOtherRate = 0; unsigned otherCount = 0; for (unsigned j = RAIDPARTS; j--; ) { if (j != connectionNum && !transferbuf.isUnusedRaidConection(j)) { if (transferbuf.isRaidConnectionProgressBlocked(j) // this one can't continue because it would get too far ahead || (reqs[j] && reqs[j]->status == REQ_DONE)) // this one reached end of file { ++otherCount; averageOtherRate += mReqSpeeds[j].lastRequestMeanSpeed(); } else { return false; } } } averageOtherRate /= otherCount ? otherCount : 1; m_off_t thisRate = mReqSpeeds[connectionNum].lastRequestMeanSpeed(); if (thisRate < averageOtherRate / 2 // this is less than half of avg of other connections && averageOtherRate > 50 * 1024 // avg is more than 50KB/s && thisRate < 1024 * 1024) // this is less than 1MB/s { LOG_warn << "Raid connection " << connectionNum << " is much slower than its peers, with speed " << thisRate << " while they are managing " << averageOtherRate; mRaidChannelSwapsForSlowness += 1; incrementErrors = false; return true; } } } return false; } // file transfer state machine void TransferSlot::doio(MegaClient* client, TransferDbCommitter& committer) { CodeCounter::ScopeTimer pbt(client->performanceStats.transferslotDoio); if (!fa || (transfer->size && transfer->progresscompleted == transfer->size) || (transfer->type == PUT && transfer->ultoken)) { if (transfer->type == GET || transfer->ultoken) { if (fa && transfer->type == GET) { LOG_debug << "Verifying cached download"; // verify meta MAC if (macsmac(&transfer->chunkmacs) == transfer->metamac) { return transfer->complete(committer); } else { client->sendevent(99432, "MAC verification failed for cached download", 0); transfer->chunkmacs.clear(); return transfer->failed(API_EKEY, committer); } } // this is a pending completion, retry every 200 ms by default retrybt.backoff(2); retrying = true; return transfer->complete(committer); } else { client->sendevent(99410, "No upload token available", 0); return transfer->failed(API_EINTERNAL, committer); } } retrying = false; retrybt.reset(); // in case we don't delete the slot, and in case retrybt.next=1 transfer->state = TRANSFERSTATE_ACTIVE; if (!createconnectionsonce()) // don't use connections, reqs, or asyncIO before this point. { return; } dstime backoff = 0; m_off_t p = 0; bool earliestUploadCompleted = false; if (errorcount > 4) { LOG_warn << "Failed transfer: too many errors"; return transfer->failed(lasterror, committer); } // main loop over connections for (unsigned int i = static_cast(connections); i--;) { if (reqs[i]) { unsigned slowestStartConnection; if (transfer->type == GET && reqs[i]->contentlength == reqs[i]->size && !transferbuf.isNewRaid() && transferbuf.detectSlowestRaidConnection(i, slowestStartConnection)) { LOG_debug << "Connection " << slowestStartConnection << " is the slowest to reply, using the other 5."; reqs[slowestStartConnection].reset(); transferbuf.resetPart(slowestStartConnection); i = static_cast(connections); continue; } if (reqs[i]->status == REQ_FAILURE && reqs[i]->httpstatus == 200 && transfer->type == GET && transferbuf.isRaid()) // the request started out successfully, hence status==200 in the reply headers { // check if we got some data and the failure occured partway through the part chunk. If so, best not to waste it, convert to success case with less data HttpReqDL *downloadRequest = static_cast(reqs[i].get()); LOG_debug << "Connection " << i << " received " << downloadRequest->bufpos << " before failing, processing data."; if (downloadRequest->contentlength == downloadRequest->size && downloadRequest->bufpos >= RAIDSECTOR) { downloadRequest->bufpos -= downloadRequest->bufpos % RAIDSECTOR; // always on a raidline boundary downloadRequest->size = unsigned(downloadRequest->bufpos); transferbuf.transferPos(i) = downloadRequest->bufpos; downloadRequest->status = REQ_SUCCESS; } } switch (static_cast(reqs[i]->status)) { case REQ_INFLIGHT: { processRequestLatency(reqs[i]); mReqSpeeds[i].requestProgressed(reqs[i]->transferred(client)); p += reqs[i]->transferred(client); assert(reqs[i]->lastdata != NEVER); bool incrementErrors = false; if (transfer->type == GET && transferbuf.isRaid() && testForSlowRaidConnection(i, incrementErrors)) { // switch to 5 channel raid to avoid the slow/delayed connection. (or if already switched, try a different 5). If we already tried too many times then let the usual timeout occur if (tryRaidRecoveryFromHttpGetError(i, incrementErrors)) { LOG_warn << "Connection " << i << " is slow or stalled, trying the other 5 cloudraid connections"; reqs[i]->disconnect(); reqs[i]->status = REQ_READY; } } if (EVER(reqs[i]->lastdata) && reqs[i]->lastdata > lastdata) { // prevent overall timeout if all channels are busy with big chunks for a while lastdata = reqs[i]->lastdata; } break; } case REQ_SUCCESS: { processRequestLatency(reqs[i]); mReqSpeeds[i].requestProgressed(reqs[i]->size); if (client->orderdownloadedchunks && transfer->type == GET && !transferbuf.isRaid() && transfer->progresscompleted != static_cast(reqs[i].get())->dlpos) { LOG_debug << "Conn " << i << " : POSTPONING UNSORTED CHUNK"; // postponing unsorted chunk p += reqs[i]->size; break; } lastdata = Waiter::ds; transfer->lastaccesstime = m_time(); LOG_debug << "Conn " << i << " : Transfer request finished (" << connDirectionToStr(transfer->type) << ")" << " " << reqs[i]->pos << " - " << (reqs[i]->pos + reqs[i]->size) << " Size: " << reqs[i]->size << (transferbuf.isRaid() ? string(" Part progress: " + std::to_string(transferbuf.transferPos(i)) + "/" + std::to_string(transferbuf.raidPartSize(i, transfer->size))) : "") << " (" << (mReqSpeeds[i].lastRequestMeanSpeed() / 1024) << " KB/s)"; if (transfer->type == PUT) { // completed put transfers are signalled through the // return of the upload token if (reqs[i]->in.size() == UPLOADTOKENLEN) { LOG_debug << "Conn " << i << " : Upload token received"; transfer->ultoken.reset(new UploadToken); memcpy(transfer->ultoken.get(), reqs[i]->in.data(), UPLOADTOKENLEN); errorcount = 0; transfer->failcount = 0; // Before we perform the next loop, check we are not still thread-encrypting some other chunk. // The case where this is relevant is quite obscure: a chunk A was uploaded and processed server // side, but the program exited before receiving and committing those chunk macs. Now we are // running again and processing the resumed transfer. There isn't much of the file left to upload // and we have uploaded a small chunk and received the result before even finishing (re-)encrypting // chunk A. But we do need to include its chunkmacs in the mac-of-macs or we'll assign a wrong mac // to the Node, and think the file is corrupt on download. for (unsigned int j = static_cast(connections); j--;) { if (j != i && reqs[j] && reqs[j]->status == REQ_ENCRYPTING) { LOG_debug << "Conn " << i << " : Connection " << j << " is in REQ_ENCRYPTING status. Waiting for encryption of chunk so we know all chunk macs"; while (reqs[j]->status == REQ_ENCRYPTING) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } } } // any other connections that have not reported back yet, or we haven't processed yet, // must have completed also - make sure to include their chunk MACs in the mac-of-macs for (unsigned int j = static_cast(connections); j--;) { if (j != i && reqs[j] && (reqs[j]->status == REQ_INFLIGHT || reqs[j]->status == REQ_SUCCESS || reqs[j]->status == REQ_FAILURE // could be a network error getting the result, even though it succeeded server side || reqs[j]->status == REQ_PREPARED || reqs[j]->status == REQ_UPLOAD_PREPARED_BUT_WAIT)) { LOG_debug << "Conn " << i << " : Including chunk MACs from incomplete/unprocessed (at this end) from connection " << j; transfer->setProgresscompleted( static_cast(reqs[j]->size), true /*append*/); transfer->chunkmacs.finishedUploadChunks(static_cast(reqs[j].get())->mChunkmacs); } } transfer->chunkmacs.finishedUploadChunks(static_cast(reqs[i].get())->mChunkmacs); transfer->setProgresscompleted(static_cast(reqs[i]->size), true /*append*/); LOG_debug << "Conn " << i << " : Progress completed: " << transfer->progresscompleted << "/" << transfer->size; assert(transfer->progresscompleted == transfer->size); updatecontiguousprogress(); memcpy(&transfer->filekey.key, transfer->transferkey.data(), sizeof transfer->transferkey); transfer->filekey.iv_u64 = static_cast(transfer->ctriv); transfer->filekey.crc_u64 = static_cast(macsmac(&transfer->chunkmacs)); SymmCipher::xorblock(transfer->filekey.iv_bytes.data(), transfer->filekey.key.data()); client->transfercacheadd(transfer, &committer); if (transfer->progresscompleted != progressreported) { m_off_t diff = transfer->progresscompleted - progressreported; m_off_t naturalDiff = std::max(diff, 0); speed = mTransferSpeed.calculateSpeed(naturalDiff); meanSpeed = mTransferSpeed.getMeanSpeed(); LOG_verbose << "[TransferSlot::doio] [Upload] COMPLETED (with a diff)" << " Speed: " << (speed / 1024) << " KB/s. Mean speed: " << (meanSpeed / 1024) << " KB/s [diff = " << diff << "]" << " [progressreported = " << progressreported << ", transfer->progresscompleted = " << transfer->progresscompleted << "] [transfer->size = " << transfer->size << "] [transfer->name = " << transfer->localfilename << "]"; client->httpio->updateuploadspeed(naturalDiff); progressreported = transfer->progresscompleted; lastdata = Waiter::ds; progress(); } return transfer->complete(committer); } if (reqs[i]->in.size() != 1 || reqs[i]->in[0] != '0') { LOG_debug << "Conn " << i << " : Error uploading chunk: " << reqs[i]->in; error e = (error)atoi(reqs[i]->in.c_str()); DEBUG_TEST_HOOK_UPLOADCHUNK_FAILED(e); if (e == API_EKEY) { client->sendevent(99429, "Integrity check failed in upload", 0); lasterror = e; errorcount++; reqs[i]->status = REQ_PREPARED; break; } if (e == DAEMON_EFAILED) { // megad returning -4 should result in restarting the transfer LOG_warn << "Conn " << i << " : Upload piece failed with -4, the upload cannot be " "continued on that server"; string event = "Unexpected upload chunk confirmation length: " + std::to_string(reqs[i]->in.size()); client->sendevent(99441, event.c_str(), 0); // old-style -4 (from requests with c= instead // of d=) were/are reported as 99440 return transfer->failed(API_EAGAIN, committer); } // fail with returned error return transfer->failed(e, committer); } transfer->chunkmacs.finishedUploadChunks(static_cast(reqs[i].get())->mChunkmacs); transfer->setProgresscompleted(static_cast(reqs[i]->size), true /*append*/); LOG_debug << "Conn " << i << " : Progress completed: " << transfer->progresscompleted << "/" << transfer->size; updatecontiguousprogress(); if (transfer->progresscompleted == transfer->size) { LOG_err << "Conn " << i << " : Upload is completed, but no upload token was received"; client->sendevent(99409, "No upload token received", 0); return transfer->failed(API_EINTERNAL, committer); } errorcount = 0; transfer->failcount = 0; client->transfercacheadd(transfer, &committer); reqs[i]->status = REQ_READY; DEBUG_TEST_HOOK_UPLOADCHUNK_SUCCEEDED(transfer, committer); // this will return if the hook returns false // If this upload chunk is the earliest (lowest pos), then release the ones that were waiting // This scheme prevents us from too big a gap between earlierst and latest (which could cause a -4 reply) bool earliestInFlight = true; for (unsigned int j = static_cast(connections); j--;) { if (j != i && reqs[j] && (reqs[j]->status == REQ_INFLIGHT || reqs[j]->status == REQ_SUCCESS) && (reqs[j]->pos < reqs[i]->pos)) { earliestInFlight = false; } } if (earliestInFlight) { LOG_debug << "Connection " << i << " was the earliest upload piece and has now completed at " << reqs[i]->pos; earliestUploadCompleted = true; } } else // GET { HttpReqDL *downloadRequest = static_cast(reqs[i].get()); if (reqs[i]->size == reqs[i]->bufpos || downloadRequest->buffer_released) // downloadRequest->buffer_released being true indicates we're retrying this asyncIO { if (!downloadRequest->buffer_released) { LOG_debug << "Conn " << i << " : Submit buffer"; transferbuf.submitBuffer(i, new TransferBufferManager::FilePiece(downloadRequest->dlpos, downloadRequest->release_buf())); // resets size & bufpos. finalize() is taken care of in the transferbuf downloadRequest->buffer_released = true; } auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); if (outputPiece) { p += outputPiece->buf.datalen(); // p (and progressreported) needs to be updated with this value. If raid, it will also be increased with the data waiting to be recombined mRaidChannelSwapsForSlowness = 0; bool parallelNeeded = outputPiece->finalize(false, transfer->size, transfer->ctriv, transfer->transfercipher(), &transfer->chunkmacs); if (parallelNeeded) { // do full chunk (and chunk-remainder) decryption on a thread for throughput and to minimize mutex lock times. auto req = reqs[i]; // shared_ptr for shutdown safety auto transferkey = transfer->transferkey; auto ctriv = transfer->ctriv; auto filesize = transfer->size; req->status = REQ_DECRYPTING; client->mAsyncQueue.push([req, i, outputPiece, transferkey, ctriv, filesize](SymmCipher& sc) { sc.setkey(transferkey.data()); outputPiece->finalize(true, filesize, ctriv, &sc, nullptr); LOG_debug << "Conn " << i << " : REQ_DECRYPTED [parallel]"; req->status = REQ_DECRYPTED; }, false); // not discardable: if we downloaded the data, don't waste it - decrypt and write as much as we can to file } else { reqs[i]->status = REQ_DECRYPTED; LOG_debug << "Conn " << i << " : REQ_DECRYPTED"; } } else if (transferbuf.isRaid()) { reqs[i]->status = REQ_READY; // this connection has retrieved a part of the file, but we don't have enough to combine yet for full file output. This connection can start fetching the next piece of that part. } else { LOG_err << "Conn " << i << " : non-raid, if the request succeeded then we must have a piece to write to file, but there is no piece!!"; assert(false); // non-raid, if the request succeeded then we must have a piece to write to file. } } else { client->sendevent(99430, "Invalid chunk size", 0); LOG_warn << "Conn " << i << " : Invalid chunk size: " << reqs[i]->size << " - " << reqs[i]->bufpos; lasterror = API_EREAD; errorcount++; reqs[i]->status = REQ_PREPARED; break; } } break; } case REQ_UPLOAD_PREPARED_BUT_WAIT: { assert(transfer->type == PUT); break; } case REQ_DECRYPTING: { assert(transfer->type == GET); // this must return the same piece we are decrypting, since we have not asked the transferbuf to discard it yet. auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); assert(outputPiece && outputPiece->buf.datalen()); p += outputPiece->buf.datalen(); // This is part of the transferred bytes break; } case REQ_DECRYPTED: { assert(transfer->type == GET); // this must return the same piece we just decrypted, since we have not asked the transferbuf to discard it yet. auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); if (fa->asyncavailable()) { if (asyncIO[i]) { LOG_warn << "Conn " << i << " : Retrying failed async write"; delete asyncIO[i]; asyncIO[i] = NULL; } p += outputPiece->buf.datalen(); LOG_debug << "Conn " << i << " : Writing data asynchronously at " << outputPiece->pos << " to " << (static_cast(outputPiece->pos) + outputPiece->buf.datalen()) << " (size: " << outputPiece->buf.datalen() << ")"; asyncIO[i] = fa->asyncfwrite(outputPiece->buf.datastart(), static_cast(outputPiece->buf.datalen()), outputPiece->pos); reqs[i]->status = REQ_ASYNCIO; } else { if (fa->fwrite(outputPiece->buf.datastart(), static_cast(outputPiece->buf.datalen()), outputPiece->pos)) { LOG_verbose << "Conn " << i << " : Sync write succeeded (size: " << outputPiece->buf.datalen() << ")"; transferbuf.bufferWriteCompleted(i, true); errorcount = 0; transfer->failcount = 0; updatecontiguousprogress(); } else { LOG_err << "Conn " << i << " : Error saving finished chunk (size: " << outputPiece->buf.datalen() << ")"; if (!fa->retry) { transferbuf.bufferWriteCompleted(i, false); // discard failed data so we don't retry on slot deletion return transfer->failed(API_EWRITE, committer); } lasterror = API_EWRITE; backoff = 2; break; } if (checkDownloadTransferFinished(committer, client)) { return; } client->transfercacheadd(transfer, &committer); reqs[i]->status = REQ_READY; } } break; case REQ_ASYNCIO: if (asyncIO[i]->finished) { LOG_verbose << "Conn " << i << " : Processing finished async fs operation"; if (!asyncIO[i]->failed) { if (transfer->type == PUT) { LOG_verbose << "Conn " << i << " : Async read succeeded (size: " << asyncIO[i]->dataBufferLen << ")"; m_off_t npos = asyncIO[i]->posOfBuffer + asyncIO[i]->dataBufferLen; string finaltempurl = transferbuf.tempURL(i); if (client->usealtupport && Utils::startswith(finaltempurl, "http:")) { size_t index = finaltempurl.find("/", 8); if(index != string::npos && finaltempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } } auto pos = asyncIO[i]->posOfBuffer; auto req = reqs[i]; // shared_ptr so no object is deleted out from under the worker auto transferkey = transfer->transferkey; auto ctriv = transfer->ctriv; req->pos = pos; req->status = REQ_ENCRYPTING; client->mAsyncQueue.push([req, transferkey, ctriv, finaltempurl, pos, npos](SymmCipher& sc) { sc.setkey(transferkey.data()); req->prepare(finaltempurl.c_str(), &sc, static_cast(ctriv), pos, npos); req->status = REQ_PREPARED; }, true); // discardable - if the transfer or client are being destroyed, we won't be sending that data. } else { LOG_verbose << "Conn " << i << " : Async write succeeded (size: " << asyncIO[i]->dataBufferLen << ")"; transferbuf.bufferWriteCompleted(i, true); errorcount = 0; transfer->failcount = 0; updatecontiguousprogress(); if (checkDownloadTransferFinished(committer, client)) { return; } client->transfercacheadd(transfer, &committer); reqs[i]->status = REQ_READY; if (client->orderdownloadedchunks && !transferbuf.isRaid()) { // Check connections again looking for postponed chunks delete asyncIO[i]; asyncIO[i] = NULL; i = static_cast(connections); continue; } } delete asyncIO[i]; asyncIO[i] = NULL; } else { LOG_warn << "Conn " << i << " : Async operation failed (size: " << asyncIO[i]->dataBufferLen << "). Retry: " << asyncIO[i]->retry; if (!asyncIO[i]->retry) { transferbuf.bufferWriteCompleted(i, false); // discard failed data so we don't retry on slot deletion delete asyncIO[i]; asyncIO[i] = NULL; return transfer->failed(transfer->type == PUT ? API_EREAD : API_EWRITE, committer); } // retry shortly if (transfer->type == PUT) { lasterror = API_EREAD; reqs[i]->status = REQ_READY; } else { lasterror = API_EWRITE; reqs[i]->status = REQ_SUCCESS; } backoff = 2; } } else if (transfer->type == GET) { p += asyncIO[i]->dataBufferLen; } break; case REQ_FAILURE: { auto failValue = processRequestFailure(client, reqs[i], backoff, static_cast(i)); if (failValue.first != API_OK) { LOG_warn << "Conn " << i << " : Request Failed with code " << failValue.first << ". Transfer failed (backoff: " << failValue.second << ")"; // Error: transfer fail return transfer->failed(failValue.first, committer, failValue.second); } } break; default: ; } } if (!failure) { if (!reqs[i] || (reqs[i]->status == REQ_READY)) { bool newInputBufferSupplied = false; bool pauseConnectionInputForRaid = false; std::pair posrange = transferbuf.nextNPosForConnection(i, maxRequestSize, static_cast(connections), newInputBufferSupplied, pauseConnectionInputForRaid, client->httpio->uploadSpeed); if (posrange == std::make_pair(m_off_t{-1}, m_off_t{-1})) { LOG_warn << "Conn " << i << " : Received error position, transfer must be retried"; transfer->chunkmacs.clear(); return transfer->failed(API_EARGS, committer); } // we might have a raid-reassembled block to write, or a previously loaded block, or a skip block to process. bool newOutputBufferSupplied = false; auto outputPiece = transferbuf.getAsyncOutputBufferPointer(i); if (outputPiece && reqs[i]) { LOG_verbose << "Conn " << i << " : outputPiece && reqs[ " << i << "] -> REQ_SUCCESS (set up to do the actual write on the next loop, as if it was a retry)"; // set up to do the actual write on the next loop, as if it was a retry reqs[i]->status = REQ_SUCCESS; static_cast(reqs[i].get())->buffer_released = true; newOutputBufferSupplied = true; } if (newOutputBufferSupplied || newInputBufferSupplied || pauseConnectionInputForRaid) { LOG_verbose << "Conn " << i << " : process supplied block, or just wait until other connections catch up a bit"; // process supplied block, or just wait until other connections catch up a bit } else if (posrange.second > posrange.first || !transfer->size || (transfer->type == PUT && asyncIO[i])) { LOG_verbose << "Conn " << i << " : download/upload specified range"; // download/upload specified range if (!reqs[i]) { reqs[i].reset(transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL()); reqs[i]->setLogName(client->clientname + (transfer->type == PUT ? "U" : "D") + std::to_string(++client->transferHttpCounter) + " "); } bool prepare = true; if (transfer->type == PUT) { m_off_t pos = posrange.first; unsigned size = (unsigned)(posrange.second - pos); // No need to keep recopying already processed macs from prior uploads on this req[i] // For uploads, these are always on chunk boundaries so no need to worry about partials. static_cast(reqs[i].get())->mChunkmacs.clear(); if (fa->asyncavailable()) { if (asyncIO[i]) { LOG_warn << "Conn " << i << " : Retrying a failed read"; pos = asyncIO[i]->posOfBuffer; size = asyncIO[i]->dataBufferLen; posrange.second = pos + size; delete asyncIO[i]; asyncIO[i] = NULL; } asyncIO[i] = fa->asyncfread(reqs[i]->out, size, (-(int)size) & (SymmCipher::BLOCKSIZE - 1), pos, FSLogging::logOnError); reqs[i]->status = REQ_ASYNCIO; prepare = false; } else { if (!fa->fread(reqs[i]->out, size, (-(int)size) & (SymmCipher::BLOCKSIZE - 1), transfer->pos, FSLogging::logOnError)) { LOG_warn << "Conn " << i << " : Error preparing transfer: " << fa->retry; if (!fa->retry) { return transfer->failed(API_EREAD, committer); } // retry the read shortly backoff = 2; posrange.second = transfer->pos; prepare = false; } } } if (prepare) { prepareRequest(reqs[i], transferbuf.isNewRaid() ? std::string() : transferbuf.tempURL(i), posrange.first, posrange.second); } LOG_verbose << "Conn " << i << " : Request prepared. Pos: " << posrange.first << " to npos: " << posrange.second << ". Size: " << (posrange.second - posrange.first) << ". Current " << (transferbuf.isRaid() ? "raid part" : "transfer") << " pos: " << transferbuf.transferPos(i) << ". New " << (transferbuf.isRaid() ? "raid part" : "transfer") << " pos: " << (std::max(transferbuf.transferPos(i), posrange.second)) << (transferbuf.isRaid() ? string(". Part size: " + std::to_string(transferbuf.raidPartSize(i, transfer->size))) : "") << ". Transfer size: " << transfer->size; transferbuf.transferPos(i) = std::max(transferbuf.transferPos(i), posrange.second); } else if (reqs[i]) { LOG_verbose << "Conn " << i << " : REQUEST DONE -> REQ_DONE"; reqs[i]->status = REQ_DONE; if (transfer->type == GET) { // raid reassembly can have several chunks to complete at the end of the file - keep processing till they are all done outputPiece = transferbuf.getAsyncOutputBufferPointer(i); if (outputPiece) { LOG_verbose << "Conn " << i << " : raid reassembly still has chunks to complete -> change REQ_DONE to REQ_SUCCESS (set up to do the actual write on the next loop, as if it was a retry)"; // set up to do the actual write on the next loop, as if it was a retry reqs[i]->status = REQ_SUCCESS; static_cast(reqs[i].get())->buffer_released = true; } } } } } } if (transfer->type == PUT) { // Get the number of reqs in flight and the position of the earliest for... int numInflight = 0; m_off_t earliestPosInFlight = 0; for (unsigned int i = static_cast(connections); i--;) { if (reqs[i] && reqs[i]->status == REQ_INFLIGHT) { if (!numInflight || earliestPosInFlight > reqs[i]->pos) { earliestPosInFlight = reqs[i]->pos; } ++numInflight; } } // ...avoid a gap greater than 256MB between start-pos of the earliest and the end-pos of the latest request // (the request should wait, so the gap doesn't grow over that limit) for (unsigned int i = static_cast(connections); i--;) { if (reqs[i]) { if (reqs[i]->status == REQ_PREPARED && (numInflight && !earliestUploadCompleted && earliestPosInFlight + MAX_GAP_SIZE < (reqs[i]->pos + reqs[i]->size))) { LOG_debug << "Connection " << i << " delaying until earliest completes. pos=" << reqs[i]->pos; reqs[i]->status = REQ_UPLOAD_PREPARED_BUT_WAIT; } else if (reqs[i]->status == REQ_UPLOAD_PREPARED_BUT_WAIT && (!numInflight || earliestUploadCompleted)) { LOG_debug << "Connection " << i << " resumes. pos=" << reqs[i]->pos; reqs[i]->status = REQ_PREPARED; } } } } // Finally see if any requests are now fit to post for (unsigned int i = static_cast(connections); i--;) { if (reqs[i] && !failure) { if (!backoff) { if (reqs[i]->status == REQ_PREPARED) { if (transferbuf.isNewRaid()) { assert(cloudRaid != nullptr); LOG_verbose << "Conn " << i << " : balancedRequest Size: " << reqs[i]->size << ". Pos: " << reqs[i]->pos << ". Progressreported = " << progressreported << ", progresscompleted = " << transfer->progresscompleted << " [req = " << (void*)reqs[i].get() << "]"; if (!cloudRaid->balancedRequest(static_cast(i), transferbuf.tempUrlVector(), reqs[i]->size, reqs[i]->pos, reqs[i]->size)) { reqs[i]->status = REQ_FAILURE; } } else // if (!transferbuf.isNewRaid()) { processRequestPost(client, reqs[i]); } mReqSpeeds[i].requestStarted(); reqs[i]->minspeed = true; } if (transferbuf.isNewRaid()) { assert(cloudRaid != nullptr); if (reqs[i]->status == REQ_PREPARED || reqs[i]->status == REQ_INFLIGHT) { m_off_t raidReqProgress = 0; auto failValues = processRaidReq(i, raidReqProgress); if (failValues.first == API_OK) { if (raidReqProgress > 0) p += raidReqProgress; } else { LOG_debug << "[TransferSlot::doio] Conn " << i << " : Transfer failure after processing RaidReq. Error: " << failValues.first << ". Backoff: " << failValues.second; return transfer->failed(failValues.first, committer, failValues.second); } } } } } } m_off_t cloudRaidProgress = 0; if (transfer->type == GET) { // for Raid, additionally we need the raid data that's waiting to be recombined if (transferbuf.isRaid()) { cloudRaidProgress = transferbuf.progress(); } else if (transferbuf.isNewRaid()) { assert(cloudRaid != nullptr); cloudRaidProgress = cloudRaid->progress(); } p += cloudRaidProgress; } p += transfer->progresscompleted; if (p != progressreported || (Waiter::ds - lastprogressreport) > PROGRESSTIMEOUT) { if (p != progressreported) { m_off_t diff = p - progressreported; m_off_t naturalDiff = std::max(diff, 0); speed = mTransferSpeed.calculateSpeed(naturalDiff); meanSpeed = mTransferSpeed.getMeanSpeed(); if ((Waiter::ds % 50 == 0) || (diff < 0) || (p > transfer->size)) // every 5s { if (transferbuf.isRaid() || transferbuf.isNewRaid()) { LOG_verbose << "[TransferSlot::doio] [CloudRaid] Speed: " << (speed / 1024) << " KB/s. Mean speed: " << (meanSpeed / 1024) << " KB/s [diff = " << diff << "]" << " [cloudRaidProgress = " << cloudRaidProgress << ", new progressreported = " << p << ", last progressreported = " << progressreported << ", transfer->progresscompleted = " << transfer->progresscompleted << "] [transfer->size = " << transfer->size << "] [transfer->name = " << transfer->localfilename << "]"; } else { LOG_verbose << "[TransferSlot::doio] " << ((transfer->type == PUT) ? "[Upload]" : "[Non CloudRaid]") << " Speed: " << (speed / 1024) << " KB/s. Mean speed: " << (meanSpeed / 1024) << " KB/s [diff = " << diff << "]" << " [new progressreported = " << p << ", last progressreported = " << progressreported << ", transfer->progresscompleted = " << transfer->progresscompleted << "] [transfer->size = " << transfer->size << "] [transfer->name = " << transfer->localfilename << "]"; } assert(p <= transfer->size); } if (transfer->type == PUT) { client->httpio->updateuploadspeed(naturalDiff); } else { client->httpio->updatedownloadspeed(naturalDiff); } progressreported = p; lastdata = Waiter::ds; } lastprogressreport = Waiter::ds; progress(); } assert(lastdata != NEVER); if (Waiter::ds - lastdata >= XFERTIMEOUT && !failure) { LOG_warn << "Failed chunk(s) due to a timeout: no data moved for " << (XFERTIMEOUT/10) << " seconds" ; failure = true; bool changeport = false; bool startsWithHttp = Utils::startswith(transferbuf.tempURL(0), "http:"); if (transfer->type == GET && client->autodownport && startsWithHttp) { LOG_debug << "Automatically changing download port due to a timeout"; client->usealtdownport = !client->usealtdownport; changeport = true; } else if (transfer->type == PUT && client->autoupport && startsWithHttp) { LOG_debug << "Automatically changing upload port due to a timeout"; client->usealtupport = !client->usealtupport; changeport = true; } bool chunkfailed = false; for (unsigned int i = static_cast(connections); i--;) { if (reqs[i] && reqs[i]->status == REQ_INFLIGHT) { chunkfailed = true; client->setchunkfailed(&reqs[i]->posturl); reqs[i]->disconnect(); if (changeport) { toggleport(reqs[i].get()); } reqs[i]->status = REQ_PREPARED; } } if (!chunkfailed) { LOG_warn << "Transfer failed due to a timeout"; return transfer->failed(API_EAGAIN, committer); // either the (this) slot has been deleted, or the whole transfer including slot has been deleted } else { LOG_warn << "Chunk failed due to a timeout"; client->app->transfer_failed(transfer, API_EFAILED); ++client->performanceStats.transferTempErrors; } } if (!failure && backoff > 0) { retrybt.backoff(backoff); retrying = true; // we don't bother checking the `retrybt` before calling `doio` unless `retrying` is set. } } bool TransferSlot::tryRaidRecoveryFromHttpGetError(unsigned connectionNum, bool incrementErrors) { // If we are downloding a cloudraid file then we may be able to ignore one connection and download from the other 5. if (transferbuf.isRaid()) { if (transferbuf.tryRaidHttpGetErrorRecovery(connectionNum, incrementErrors)) { // transferbuf is now set up to try a new connection reqs[connectionNum]->status = REQ_READY; // if the file is nearly complete then some connections might have stopped, but need restarting as they could have skipped portions for (unsigned j = static_cast(connections); j--;) { if (reqs[j] && reqs[j]->status == REQ_DONE) { reqs[j]->status = REQ_READY; } } return true; } LOG_warn << "Cloudraid transfer failed, too many connection errors"; } return false; } // transfer progress notification to app and related files void TransferSlot::progress() { transfer->client->app->transfer_update(transfer); for (file_list::iterator it = transfer->files.begin(); it != transfer->files.end(); ++it) { (*it)->progress(); } } m_off_t TransferSlot::updatecontiguousprogress() { m_off_t contiguousProgress = transfer->chunkmacs.updateContiguousProgress(transfer->size); // Since that is updated, we may have a chance to consolidate the macsmac calculation so far also transfer->chunkmacs.updateMacsmacProgress(transfer->transfercipher()); if (!transferbuf.tempUrlVector().empty() && transferbuf.isRaid()) { LOG_debug << "Contiguous progress: " << contiguousProgress; } else { LOG_debug << "Contiguous progress: " << contiguousProgress << " (" << (transfer->pos - contiguousProgress) << ")"; } return contiguousProgress; } void TransferSlot::prepareRequest(const std::shared_ptr& httpReq, const string& tempURL, m_off_t pos, m_off_t npos) { string finaltempURL = tempURL; if (!finaltempURL.empty() && ((transfer->type == GET && transfer->client->usealtdownport) || (transfer->type == PUT && transfer->client->usealtupport)) && Utils::startswith(finaltempURL, "http:")) { size_t index = finaltempURL.find("/", 8); if (index != string::npos && finaltempURL.find(":", 8) == string::npos) { finaltempURL.insert(index, ":8080"); } } LOG_debug << "[TransferSlot::prepareRequest] pos = " << pos << ", npos = " << npos << ", tempURL = '" << finaltempURL << "'"; httpReq->prepare(finaltempURL.c_str(), transfer->transfercipher(), static_cast(transfer->ctriv), pos, npos); httpReq->pos = pos; httpReq->status = REQ_PREPARED; } void TransferSlot::processRequestPost(MegaClient* client, const std::shared_ptr& httpReq) { ++tsStats.mNumTotalRequests; httpReq->post(client); // status becomes either REQ_INFLIGHT or REQ_FAILED } std::pair TransferSlot::processRequestFailure(MegaClient* client, const std::shared_ptr& httpReq, dstime& backoff, int channel) { processRequestLatency(httpReq); ++tsStats.mNumFailedRequests; LOG_warn << "Conn " << channel << " : Failed chunk. HTTP status: " << httpReq->httpstatus << " [httpReq = " << (void*)httpReq.get() << "] [totalFailedRequests = " << tsStats.mNumFailedRequests << "]"; if (httpReq->httpstatus == 509) { LOG_warn << "Conn " << channel << " : Bandwidth overquota from storage server" << " [httpReq = " << (void*)httpReq.get() << "]"; dstime new_backoff = client->overTransferQuotaBackoff(httpReq.get()); return std::make_pair(API_EOVERQUOTA, new_backoff); } else if (httpReq->httpstatus == 429) { LOG_warn << "Conn " << channel << " : 429 - too many requests - backoff 5, REQ_PREPARED" << " [httpReq = " << (void*)httpReq.get() << "]"; // too many requests - back off a bit (may be added serverside at some point. Added here 202020623) backoff = 5; httpReq->status = REQ_PREPARED; } else if (httpReq->httpstatus == 503 && !transferbuf.isRaid() && !transferbuf.isNewRaid()) { LOG_warn << "Conn " << channel << " : 503 dor non raid... backoff and REQ_PREPARED" << " [httpReq = " << (void*)httpReq.get() << "]"; // for non-raid, if a file gets a 503 then back off as it may become available shortly backoff = 50; httpReq->status = REQ_PREPARED; } else if (httpReq->httpstatus == 403 || httpReq->httpstatus == 404 || (httpReq->httpstatus == 503 && (transferbuf.isRaid() || transferbuf.isNewRaid()))) { LOG_warn << "Conn " << channel << " : 403, 404 or 503... (if is new raid -> REQ_READY)" << " [httpReq = " << (void*)httpReq.get() << "]"; if (transferbuf.isNewRaid()) { httpReq->status = REQ_READY; } // - 404 means "malformed or expired URL" - can be immediately fixed by getting a fresh one from the API // - 503 means "the API gave you good information, but I don't have the file" - cannot be fixed (at least not immediately) by getting a fresh URL // for raid parts and 503, it's appropriate to try another raid source else if (!tryRaidRecoveryFromHttpGetError(static_cast(channel), true)) { return std::make_pair(API_EAGAIN, 0); } } else if (httpReq->httpstatus == 0 && (transferbuf.isNewRaid() || tryRaidRecoveryFromHttpGetError(static_cast(channel), true))) { LOG_warn << "Conn " << channel << " : status 0 NETWORK ERROR OR TIMEOUT -> (if is new raid -> REQ_READY)" << " [httpReq = " << (void*)httpReq.get() << "]"; if (transferbuf.isNewRaid()) { httpReq->status = REQ_READY; } // status 0 indicates network error or timeout; no headers received. // tryRaidRecoveryFromHttpGetError has switched to loading a different part instead of this one. } else { if (!failure) { LOG_verbose << "Conn " << channel << " : else if failure" << " [httpReq = " << (void*)httpReq.get() << "]"; failure = true; client->app->transfer_failed(transfer, API_EFAILED); client->setchunkfailed(&httpReq->posturl); ++client->performanceStats.transferTempErrors; } LOG_verbose << "Conn " << channel << " : from req_failure to REQ_PREPARED" << " [httpReq = " << (void*)httpReq.get() << "]"; httpReq->status = REQ_PREPARED; } return std::make_pair(API_OK, 0); } bool TransferSlot::initCloudRaid(MegaClient* client) { assert(transferbuf.isNewRaid()); if (!transferbuf.isNewRaid()) { return false; } if (cloudRaid == nullptr) { cloudRaid = std::make_shared(this, client, static_cast(reqs.size())); return cloudRaid->isShown(); } return cloudRaid->init(this, client, static_cast(reqs.size())); } std::pair TransferSlot::processRaidReq(size_t connection, m_off_t& raidReqProgress) { assert(transfer->type == GET); assert(cloudRaid && cloudRaid->isShown()); assert(!reqs.empty() && connection <= reqs.size()); const std::shared_ptr& httpReq = reqs[connection]; assert(httpReq != nullptr); // Process internal RaidReq IO cloudRaid->raidReqDoio(static_cast(connection)); auto failValues = cloudRaid->checkTransferFailure(); if (failValues.first != API_OK) { return failValues; } if (httpReq->status == REQ_PREPARED) { httpReq->bufpos = 0; httpReq->status = REQ_INFLIGHT; } byte* buf = httpReq->buf + httpReq->bufpos; m_off_t len = httpReq->size - httpReq->bufpos; assert((len > 0) && "len is 0 in processRaidReq"); // Get raid-assembled data raidReqProgress = static_cast(cloudRaid->readData(static_cast(connection), buf, len)); if (raidReqProgress > 0) { httpReq->bufpos += raidReqProgress; if (httpReq->bufpos == httpReq->size) { httpReq->status = REQ_SUCCESS; } } else if (raidReqProgress < 0) { LOG_debug << "[TransferSlot::processRaidReq] Conn " << connection << " : RaidReq FAILURE. [httpReq = " << (void*)httpReq.get() << "]"; httpReq->status = REQ_FAILURE; } httpReq->lastdata = Waiter::ds; if (httpReq->status == REQ_SUCCESS) { LOG_verbose << "[TransferSlot::processRaidReq] Conn " << connection << " : RaidReq SUCCESS. Progress: " << raidReqProgress << ". Removing RaidReq... [httpReq = " << (void*)httpReq.get() << "]"; cloudRaid->removeRaidReq(static_cast(connection)); } return cloudRaid->checkTransferFailure(); } void TransferSlot::processRequestLatency(const std::shared_ptr& req) { if (req->isLatencyProcessed) return; if (const auto valuesAreUninitialized = (req->mConnectTime < 0 || req->mStartTransferTime < 0); valuesAreUninitialized) return; tsStats.mTotalConnectTime += req->mConnectTime; tsStats.mTotalStartTransferTime += req->mStartTransferTime; ++tsStats.mNumRequestsWithCalculatedLatency; req->isLatencyProcessed = true; } } // namespace sdk-10.11.0/src/transferstats.cpp000066400000000000000000000356011516266226600166520ustar00rootroot00000000000000/** * @file transferstats.cpp * @brief Calculate and collect transfer metrics * * (c) 2024 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/transferstats.h" #include "mega/logging.h" #include "mega/transferslot.h" #include #include namespace mega::stats { // TransferData bool TransferStats::TransferData::checkDataStateValidity() const { std::string notValidAndErroneousValuesErrMsg; auto checkTransferFieldValidity = [¬ValidAndErroneousValuesErrMsg](const std::string& fieldName, const auto fieldValue) -> bool { if (fieldValue <= 0) { LOG_debug << "[TransferStats::checkPreconditions] " << fieldName << " for this transfer (" << fieldValue << ") is not valid"; if (fieldValue < 0) { // Fields can be 0 under certain conditions (even when we need to discard the // metrics) but, if they are lower than 0, then there is an error somewhere. notValidAndErroneousValuesErrMsg += ("Invalid " + std::string(fieldName) + " value (" + std::to_string(fieldValue) + "). "); } return false; } return true; }; bool transferFieldsAreValid = true; transferFieldsAreValid &= checkTransferFieldValidity("size", mSize); transferFieldsAreValid &= checkTransferFieldValidity("speed", mSpeed); transferFieldsAreValid &= checkTransferFieldValidity("latency", mLatency); assert(notValidAndErroneousValuesErrMsg.empty() && notValidAndErroneousValuesErrMsg.c_str()); return transferFieldsAreValid; } // Metrics std::string TransferStats::Metrics::toString(const std::string& separator) const { std::ostringstream oss; oss << std::fixed << std::setprecision(2); // Two decimal precision. // Use the endOfLine for each field to ensure proper formatting oss << "Transfer type: " << mTransferType << separator; oss << "Median Size: " << mMedianSize << separator; oss << "Contraharmonic Mean Size: " << mContraharmonicMeanSize << separator; oss << "Median Speed: " << mMedianSpeed << separator; oss << "Weighted Avg Speed: " << mWeightedAverageSpeed << separator; oss << "Max Speed: " << mMaxSpeed << separator; oss << "Avg Latency: " << mAvgLatency << separator; oss << "Failed Request Ratio: " << mFailedRequestRatio << separator; oss << "Raided Transfer Ratio: " << mRaidedTransferRatio; return oss.str(); } std::string TransferStats::Metrics::toJson() const { std::ostringstream oss; oss << std::fixed << std::setprecision(2); // Ensure two decimal precision for floating-point values const auto addField = [&oss](auto&& fieldName, auto&& value, const bool last = false) { oss << "\\\"" << fieldName << "\\\":\\\"" << value << "\\\""; if (!last) oss << ","; }; oss << R"(\"tm\":{)"; addField("t", mTransferType); addField("ml", mMedianSize); addField("wl", mContraharmonicMeanSize); addField("ms", (mMedianSpeed / 1024)); addField("ws", (mWeightedAverageSpeed / 1024)); addField("zs", (mMaxSpeed / 1024)); addField("al", mAvgLatency); addField("fr", mFailedRequestRatio); addField("rr", mRaidedTransferRatio, true); oss << R"(})"; return oss.str(); } // TransferStats bool TransferStats::addTransferData(TransferData&& transferData) { // Check all preconditions. if (!transferData.checkDataStateValidity()) { LOG_debug << "[TransferStats::addTransferStats] Some fields are not valid. Stats skipped " "for this transfer"; return false; } // Remove old transfers. const auto now = std::chrono::steady_clock::now(); while (!mTransfersData.empty() && (std::chrono::duration_cast( now - mTransfersData.front().mTimestamp) .count() > mMaxAgeSeconds)) { mTransfersData.pop_front(); } mUncollectedAndPrintedTransferData.first += 1; mUncollectedAndPrintedTransferData.second += transferData.mSize; // Update the timestamp and move the transferData to the collection. transferData.mTimestamp = now; mTransfersData.push_back(std::move(transferData)); // Remove excessive entries if necessary. if (mTransfersData.size() > mMaxEntries) { mTransfersData.pop_front(); } return true; } TransferStats::Metrics TransferStats::collectMetrics(const direction_t type) const { checkTransferTypeValidity(type); TransferStats::Metrics metrics = {}; if (mTransfersData.empty()) { return metrics; } // Set transfer type (PUT or GET). metrics.mTransferType = type; // Set the number of transfers used to calculate these metrics. metrics.mNumTransfers = mTransfersData.size(); // Declare sizes & speeds vectors and accumulated values. std::vector sizes; std::vector speeds; sizes.reserve(mTransfersData.size()); speeds.reserve(mTransfersData.size()); double totalLatency = 0.0; double totalFailedRequestRatios = 0.0; size_t totalRaidedTransfers = 0; for (const auto& transferData: mTransfersData) { sizes.push_back(transferData.mSize); speeds.push_back(transferData.mSpeed); // Directly accumulate values for latencies, failed request ratios, and raided transfers. totalLatency += transferData.mLatency; totalFailedRequestRatios += transferData.mFailedRequestRatio; totalRaidedTransfers += transferData.mIsRaided ? 1 : 0; } // Sort the vectors in place. std::sort(sizes.begin(), sizes.end()); std::sort(speeds.begin(), speeds.end()); // Calculate median and weighted averages. metrics.mMedianSize = calculateMedian(sizes); metrics.mContraharmonicMeanSize = calculateWeightedAverage(sizes, sizes); metrics.mMedianSpeed = calculateMedian(speeds); metrics.mWeightedAverageSpeed = calculateWeightedAverage(speeds, sizes); // Calculate max speed from the sorted speeds vector (last element is the max). metrics.mMaxSpeed = speeds.back(); // Calculate average latency. metrics.mAvgLatency = static_cast(std::round(totalLatency / static_cast(mTransfersData.size()))); // Calculate failed request ratio (with precision to 2 decimals). metrics.mFailedRequestRatio = std::round((totalFailedRequestRatios / static_cast(mTransfersData.size())) * 100.0) / 100.0; // Calculate raided transfer ratio (with precision to 2 decimals). if (type == GET) { metrics.mRaidedTransferRatio = std::round((static_cast(totalRaidedTransfers) / static_cast(mTransfersData.size())) * 100.0) / 100.0; } return metrics; } TransferStats::Metrics TransferStats::collectAndPrintMetrics(const direction_t type, const std::string& separator) { const auto metrics = collectMetrics(type); printMetrics(metrics, separator); mUncollectedAndPrintedTransferData = {}; return metrics; } // TransferStatsManager bool TransferStatsManager::addTransferStats(const Transfer* const transfer) { // Check preconditions. if (!checkTransferStateValidity(transfer)) { return false; } // Add transfer stats. TransferStats::TransferData transferData{transfer->size, transfer->slot->mTransferSpeed.getMeanSpeed(), transfer->slot->tsStats.averageStartTransferTime(), transfer->slot->tsStats.failedRequestRatio(), transfer->slot->transferbuf.isRaid() || transfer->slot->transferbuf.isNewRaid()}; std::lock_guard guard(mTransferStatsMutex); return transfer->type == PUT ? mUploadStatistics.addTransferData(std::move(transferData)) : mDownloadStatistics.addTransferData(std::move(transferData)); } std::string TransferStatsManager::metricsToJsonForTransferType(const direction_t type) const { const TransferStats::Metrics metrics = collectMetrics(type); return metrics.toJson(); } TransferStats::Metrics TransferStatsManager::collectMetrics(const direction_t type) const { checkTransferTypeValidity(type); std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.collectMetrics(type) : mDownloadStatistics.collectMetrics(type); } TransferStats::Metrics TransferStatsManager::collectAndPrintMetrics(const direction_t type, const std::string& separator) { checkTransferTypeValidity(type); LOG_info << (type == PUT ? "[UploadStatistics]" : "[DownloadStatistics]") << " Number of transfers: " << size(type) << ". Max entries: " << getMaxEntries(type) << ". Max age in seconds: " << getMaxAgeSeconds(type); std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.collectAndPrintMetrics(type, separator) : mDownloadStatistics.collectAndPrintMetrics(type, separator); } std::optional TransferStatsManager::collectAndPrintTransferStatsIfLimitReached(const direction_t type, const std::string& separator) { if (const auto [numTransfers, totalSize] = getUncollectedAndPrintedTransferData(type); (numTransfers < NUM_ENTRIES_FOR_LOGGING && totalSize < TOTAL_SIZE_FOR_LOGGING)) return {}; const auto metrics = collectAndPrintMetrics(type, separator); assert(getUncollectedAndPrintedTransferData(type) == TransferStats::UncollectedTransfersCounters{}); return metrics; } size_t TransferStatsManager::size(const direction_t type) const { checkTransferTypeValidity(type); std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.size() : mDownloadStatistics.size(); } size_t TransferStatsManager::getMaxEntries(const direction_t type) const { checkTransferTypeValidity(type); std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.getMaxEntries() : mDownloadStatistics.getMaxEntries(); } int64_t TransferStatsManager::getMaxAgeSeconds(const direction_t type) const { checkTransferTypeValidity(type); std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.getMaxAgeSeconds() : mDownloadStatistics.getMaxAgeSeconds(); } TransferStats::UncollectedTransfersCounters TransferStatsManager::getUncollectedAndPrintedTransferData(const direction_t type) const { std::lock_guard guard(mTransferStatsMutex); return type == PUT ? mUploadStatistics.getUncollectedAndPrintedTransferData() : mDownloadStatistics.getUncollectedAndPrintedTransferData(); } // Utils void printMetrics(const TransferStats::Metrics& metrics, const std::string& separator) { LOG_info << metrics.toString(separator); } m_off_t calculateMedian(const vector& sortedValues) { const size_t n = sortedValues.size(); if (n == 0) { return 0; } // If the number of elements is even, return the average of the two middle elements. if (n % 2 == 0) { return static_cast( std::round(static_cast(sortedValues[n / 2 - 1] + sortedValues[n / 2]) / 2.0)); } // If the number of elements is odd, return the middle element. return sortedValues[n / 2]; } m_off_t calculateWeightedAverage(const vector& values, const vector& weights) { if (values.size() != weights.size()) { LOG_warn << "[calculateWeightedAverage] Values size (" << values.size() << ") != weights size (" << weights.size() << "). Skipping"; assert(false && "[calculateWeightedAverage] Vector of values and vector of weights have " "different sizes"); return 0; } m_off_t weightedSum = 0; m_off_t totalWeight = 0; for (size_t i = 0; i < values.size(); ++i) { weightedSum += values[i] * weights[i]; totalWeight += weights[i]; } if (weightedSum == 0 || totalWeight == 0) { return 0; } return static_cast( std::round(static_cast(weightedSum) / static_cast(totalWeight))); } void checkTransferTypeValidity([[maybe_unused]] const direction_t type) { assert((type == PUT || type == GET) && "Only uploads (PUT) or downloads (GET) are allowed"); } bool checkTransferStateValidity(const Transfer* const transfer) { auto checkTransferStateCondition = [](bool transferStateCondition, const std::string& errorMsg, bool triggerAssert = true) -> bool { if (!transferStateCondition) { LOG_err << errorMsg; if (triggerAssert) assert(false && errorMsg.c_str()); return false; } return true; }; if (!checkTransferStateCondition(transfer != nullptr, "[checkTransferStateValidity] It is a NULL transfer")) { return false; } if (!checkTransferStateCondition( transfer->type == PUT || transfer->type == GET, "[checkTransferStateValidity] called with an invalid transfer type")) { return false; } if (!checkTransferStateCondition( transfer->slot != nullptr, "[checkTransferStateValidity] called with a NULL transfer slot")) { return false; } if (!checkTransferStateCondition( !transfer->tempurls.empty(), "[checkTransferStateValidity] This transfer didn't initialize the " "transferbuf, it will be discarded for stats", false)) { return false; } return true; } } // namespace mega::stats sdk-10.11.0/src/treeproc.cpp000066400000000000000000000065651516266226600156010ustar00rootroot00000000000000/** * @file treeproc.cpp * @brief Node tree processor * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/treeproc.h" #include "mega/megaclient.h" #include "mega/logging.h" namespace mega { // create share keys TreeProcShareKeys::TreeProcShareKeys(std::shared_ptr n, bool includeParentChain) : sn(n) , includeParentChain(includeParentChain) { } void TreeProcShareKeys::proc(MegaClient*, std::shared_ptr n) { snk.add(n, sn, includeParentChain); } void TreeProcShareKeys::get(Command* c) { snk.get(c); } void TreeProcForeignKeys::proc(MegaClient* client, std::shared_ptr n) { if (n->foreignkey) { client->nodekeyrewrite.push_back(n->nodehandle); n->foreignkey = false; } } // mark node as removed and notify void TreeProcDel::proc(MegaClient* client, std::shared_ptr n) { n->changed.removed = true; client->mNodeManager.notifyNode(n); handle userHandle = ISUNDEF(mOriginatingUser) ? n->owner : mOriginatingUser; if (userHandle != client->me && !client->loggedIntoFolder()) { client->useralerts.noteSharedNode(userHandle, n->type, 0, n.get()); } } void TreeProcDel::setOriginatingUser(const handle &handle) { mOriginatingUser = handle; } void TreeProcApplyKey::proc(MegaClient *client, std::shared_ptr n) { if (n->attrstring) { n->applykey(); if (!n->attrstring) { n->changed.attrs = true; client->mNodeManager.notifyNode(n); } } } void TreeProcCopy::allocnodes() { nn.resize(nc); allocated = true; } // determine node tree size (nn = NULL) or write node tree to new nodes array void TreeProcCopy::proc(MegaClient* client, std::shared_ptr n) { if (allocated) { client->putnodes_prepareCopy(nn, nc, n->type, n->nodehandle, n->parent ? n->parent->nodehandle : UNDEF, n->nodekey(), n->attrs, resetSensitive, false); } else nc++; } #ifdef ENABLE_SYNC LocalTreeProcMove::LocalTreeProcMove(Sync* sync) { newsync = sync; nc = 0; } void LocalTreeProcMove::proc(FileSystemAccess&, LocalNode* localnode) { if (newsync != localnode->sync) { localnode->sync->statecachedel(localnode); localnode->sync = newsync; newsync->statecacheadd(localnode); } nc++; } void LocalTreeProcUpdateTransfers::proc(FileSystemAccess&, LocalNode* localnode) { // Only updating the localname thread safe field. // Transfers are managed from the megaclient thread localnode->updateTransferLocalname(); } #endif } // namespace sdk-10.11.0/src/udp_socket.cpp000066400000000000000000000162661516266226600161150ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/udp_socket.h" #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include #include #include #else #include #include #include #include #include #include #endif #include #include using namespace std; using namespace std::chrono; using namespace std::chrono_literals; namespace mega { UdpSocket::UdpSocket(const string& remoteIP, int remotePort) { if (initializeSocketSupport()) { if (!createRemoteAddress(remoteIP, remotePort) || !openNonblockingSocket()) { cleanupSocketSupport(); } } } UdpSocket::~UdpSocket() { if (mSocket) { // Get here only if initialization has completely succeeded in constructor closeSocket(); cleanupSocketSupport(); } } bool UdpSocket::isIPv4() const { return mInetType == AF_INET; } UdpSocket::Communication UdpSocket::sendSyncMessage(const string& message) { if (!mSocket) { return {-1, "Socket not properly initialized"}; } if (sendtoWrapper(message) == -1) { return getSocketError(); } return {}; } UdpSocket::Communication UdpSocket::receiveSyncMessage(const high_resolution_clock::time_point& timeout) { if (!mSocket) { return {-1, "Socket not properly initialized"}; } for (;;) { char buffer[2048]; socklen_t addrSize = static_cast(mInetType == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); auto bytesReceived = recvfrom(mSocket, buffer, sizeof(buffer) - 1, 0, // flags getSockaddr(), &addrSize); if (bytesReceived > 0) { std::string response{buffer, static_cast(bytesReceived)}; return {0, response}; } else if (noDataYet() || !getSocketErrorCode()) { // No data available yet. We can waste time here up to given timeout, // but let's evaluate and try again after some decent interval if (high_resolution_clock::now() > timeout) return {-1, "Timeout"}; std::this_thread::sleep_for(1ms); } else // actual error { break; } } return getSocketError(); } bool UdpSocket::createRemoteAddress(const std::string& remoteIP, int remotePort) { bool remoteAddressOK = false; mRemoteAddress = std::make_unique>(); mInetType = remoteIP.find(':') == std::string::npos ? AF_INET : AF_INET6; if (mInetType == AF_INET) { // This should work with code for AF_INET6 after prefixing the IPv4 with "::ffff:". // But for some reasons it only worked on Linux and MacOS, // while on Windows it always returned 10049 (WSAEADDRNOTAVAIL) when sending. // The AF_INET-specific implementation below worked everywhere. *mRemoteAddress = sockaddr_in{}; sockaddr_in& addr = std::get(*mRemoteAddress); addr.sin_family = static_cast(mInetType); addr.sin_port = htons(static_cast(remotePort)); remoteAddressOK = inet_pton(mInetType, remoteIP.c_str(), &addr.sin_addr) == 1 && addr.sin_port; } else { *mRemoteAddress = sockaddr_in6{}; sockaddr_in6& addr = std::get(*mRemoteAddress); addr.sin6_family = static_cast(mInetType); addr.sin6_port = htons(static_cast(remotePort)); remoteAddressOK = inet_pton(mInetType, remoteIP.c_str(), &addr.sin6_addr) == 1 && addr.sin6_port; } return remoteAddressOK; } sockaddr* UdpSocket::getSockaddr() { sockaddr* addr = reinterpret_cast(std::get_if(mRemoteAddress.get())); if (!addr) addr = reinterpret_cast(std::get_if(mRemoteAddress.get())); assert(addr); return addr; } // // OS-specific implementations // bool UdpSocket::initializeSocketSupport() { #if defined(_WIN32) WSADATA wsaData; return WSAStartup(MAKEWORD(2, 2), &wsaData) == 0; #else return true; #endif } void UdpSocket::cleanupSocketSupport() { #if defined(_WIN32) WSACleanup(); #endif } bool UdpSocket::openNonblockingSocket() { mSocket = static_cast(socket(mInetType, SOCK_DGRAM, IPPROTO_UDP)); if (mSocket != -1) { // Set the socket to non-blocking mode #if defined(_WIN32) unsigned long mode = 1; // Non-blocking mode bool hadErrors = ioctlsocket(mSocket, FIONBIO, &mode) == -1; #else int flags = fcntl(mSocket, F_GETFL, 0); // Get current flags bool hadErrors = flags == -1 || fcntl(mSocket, F_SETFL, flags | O_NONBLOCK) == -1; #endif if (hadErrors) { closeSocket(); mSocket = 0; } } else { mSocket = 0; } return mSocket != 0; } bool UdpSocket::noDataYet() { #if defined(_WIN32) return WSAGetLastError() == WSAEWOULDBLOCK; #else return errno == EWOULDBLOCK; #endif } void UdpSocket::closeSocket() { #if defined(_WIN32) closesocket(mSocket); #else close(mSocket); #endif } intmax_t UdpSocket::sendtoWrapper(const std::string& message) { socklen_t addrSize = static_cast(mInetType == AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); return sendto(mSocket, message.c_str(), #if defined(_WIN32) static_cast(message.size()), #else message.size(), #endif 0, // flags getSockaddr(), addrSize); } UdpSocket::Communication UdpSocket::getSocketError() { #if defined(_WIN32) int errorCode = WSAGetLastError(); char* error_message = nullptr; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, 0, (LPSTR)&error_message, 0, nullptr); std::string messageCopy(error_message); LocalFree(error_message); return {errorCode, messageCopy}; #else return {errno, strerror(errno)}; #endif } int UdpSocket::getSocketErrorCode() { #if defined(_WIN32) return WSAGetLastError(); #else return errno; #endif } } // namespace mega sdk-10.11.0/src/udp_socket_tester.cpp000066400000000000000000000142101516266226600174660ustar00rootroot00000000000000/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/udp_socket_tester.h" #include "mega/dns_lookup_pseudomessage.h" #include "mega/udp_socket.h" #include "mega/utils.h" #include #include using namespace std; namespace mega { UdpSocketTester::UdpSocketTester(const string& ip, uint16_t port): mTestResults{port, {}, {}} { mSocket = std::make_unique(ip, port); } UdpSocketTester::~UdpSocketTester() = default; bool UdpSocketTester::startSuite(const TestSuite& suite) { if (mRunning) { return false; } mRunning = true; mTestResults.log.clear(); mTestResults.messageResults.clear(); mTestResults.messageResults.reserve(suite.totalMessageCount()); mShortMessage = suite.getShortMessage(); mLongMessage = suite.getLongMessage(); mDnsMessage = mSocket->isIPv4() ? suite.getDnsIPv4Message() : suite.getDnsIPv6Message(); for (uint16_t lp = 0, current = 0; lp < suite.loopCount; ++lp) { // send Short messages for (uint16_t s = 0; s < suite.shortMessageCount; ++s) { sendMessage(TestSuite::MessageType::SHORT, mShortMessage); sleepIfMultipleOf(++current, 10); } // send Long messages for (uint16_t l = 0; l < suite.longMessageCount; ++l) { sendMessage(TestSuite::MessageType::LONG, mLongMessage); sleepIfMultipleOf(++current, 10); } // send DNS messages for (uint16_t d = 0; d < suite.dnsMessageCount; ++d) { sendMessage(TestSuite::MessageType::DNS, mDnsMessage); sleepIfMultipleOf(++current, 10); } } return true; } void UdpSocketTester::sendMessage(UdpSocketTester::TestSuite::MessageType type, const string& message) { auto&& sendResult{mSocket->sendSyncMessage(message)}; mTestResults.messageResults.emplace_back(MessageResult{type, sendResult.code}); if (sendResult.code) { log(string("sending ") + static_cast(type) + " message", "[" + std::to_string(sendResult.code) + "] " + std::move(sendResult.message)); } } void UdpSocketTester::sleepIfMultipleOf(uint16_t multiFactor, uint16_t factor) { if (!(multiFactor % factor)) { std::this_thread::sleep_for(1ms); } } UdpSocketTester::SocketResults UdpSocketTester::getSocketResults(const chrono::high_resolution_clock::time_point& timeout) { // count expected replies int expectedReplyCount = 0; for (auto it = mTestResults.messageResults.begin(); it != mTestResults.messageResults.end(); ++it) { if (!it->errorCode) { it->errorCode = REPLY_NOT_RECEIVED; ++expectedReplyCount; } } for (int i = 0; i < expectedReplyCount; ++i) { auto&& reply{mSocket->receiveSyncMessage(timeout)}; if (!reply.code) { if (reply.message == mShortMessage) { confirmFirst(TestSuite::MessageType::SHORT); } else if (reply.message == mLongMessage) { confirmFirst(TestSuite::MessageType::LONG); } else if (reply.message == mDnsMessage) { confirmFirst(TestSuite::MessageType::DNS); } else { // log this but don't count it log("receiving reply", "Invalid message (hex): " + Utils::stringToHex(reply.message, true)); --i; } } else { log("receiving reply", "[" + std::to_string(reply.code) + "] " + reply.message); } } mRunning = false; return mTestResults; } void UdpSocketTester::confirmFirst(TestSuite::MessageType type) { auto it = std::find_if(mTestResults.messageResults.begin(), mTestResults.messageResults.end(), [type](const MessageResult& r) { return r.messageType == type && r.errorCode == REPLY_NOT_RECEIVED; }); if (it != mTestResults.messageResults.end()) { it->errorCode = 0; } } void UdpSocketTester::log(string&& action, string&& error) { auto& counter = mTestResults.log["Error " + std::move(action) + ", IPv" + (mSocket->isIPv4() ? '4' : '6') + ", port " + std::to_string(mTestResults.port) + ": " + std::move(error)]; ++counter; } UdpSocketTester::TestSuite::TestSuite(uint16_t loops, uint16_t shorts, uint16_t longs, uint16_t dnss, uint64_t userId): loopCount{loops}, shortMessageCount{shorts}, longMessageCount{longs}, dnsMessageCount{dnss} { // short test message static constexpr char magicS = '\x33'; // ASCII 51 mShortMessage = magicS + userIdToHex(userId); // long test message static constexpr char magicL = '\x51'; // ASCII 85 string prefix = magicL + userIdToHex(userId); static constexpr size_t MAX_MESSAGE_LENGTH = 1400u; static constexpr char RANDOM_PADDING_CHAR = 'P'; mLongMessage = prefix + string(MAX_MESSAGE_LENGTH - prefix.size(), RANDOM_PADDING_CHAR); // DNS test message for IPv4 static constexpr uint16_t RANDOM_DNS_MESSAGE_ID = 1234; mDnsIPv4Message = dns_lookup_pseudomessage::getForIPv4(userId, RANDOM_DNS_MESSAGE_ID); mDnsIPv6Message = dns_lookup_pseudomessage::getForIPv6(userId, RANDOM_DNS_MESSAGE_ID); } } // namespace mega sdk-10.11.0/src/user.cpp000066400000000000000000000643511516266226600147310ustar00rootroot00000000000000/** * @file user.cpp * @brief Class for manipulating user / contact data * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/user.h" #include "mega/base64.h" #include "mega/logging.h" #include "mega/megaclient.h" #include "mega/tlv.h" #include "mega/user_attribute_manager.h" namespace mega { User::User(const char* cemail): mAttributeManager{std::make_unique()} { userhandle = UNDEF; show = VISIBILITY_UNKNOWN; ctime = 0; pubkrequested = false; isTemporary = false; resetTag(); if (cemail) { email = cemail; } memset(&changed, 0, sizeof(changed)); } // Use explicit destructor here to allow using smart pointers with incomplete type in class // definition. User::~User() = default; bool User::mergeUserAttribute(attr_t type, const string_map& newValuesMap, string_map& destination) { bool modified = false; for (const auto &it : newValuesMap) { const string& key = it.first; const string& newValue = it.second; string currentValue; if (auto itD = destination.find(key); itD != destination.end() && !itD->second.empty()) { Base64::btoa(itD->second, currentValue); } if (newValue != currentValue) { if ((type == ATTR_ALIAS || type == ATTR_DEVICE_NAMES || type == ATTR_CC_PREFS || type == ATTR_APPS_PREFS) && newValue[0] == '\0') { // alias/deviceName/appPrefs being removed destination.erase(key); } else { destination[key] = Base64::atob(newValue); } modified = true; } } return modified; } bool User::serialize(string* d) const { unsigned char l; time_t ts; AttrMap attrmap; d->reserve(d->size() + 100 + attrmap.storagesize(10)); d->append((char*)&userhandle, sizeof userhandle); // FIXME: use m_time_t & Serialize64 instead ts = ctime; d->append((char*)&ts, sizeof ts); d->append((char*)&show, sizeof show); l = (unsigned char)email.size(); d->append((char*)&l, sizeof l); d->append(email.c_str(), l); mAttributeManager->serializeAttributeFormatVersion(*d); char bizMode = 0; if (mBizMode != BIZ_MODE_UNKNOWN) // convert number to ascii { bizMode = static_cast('0' + mBizMode); } d->append((char*)&bizMode, 1); d->append("\0\0\0\0\0", 6); // serialization of attributes mAttributeManager->serializeAttributes(*d); if (pubk.isvalid()) { pubk.serializekey(d, AsymmCipher::PUBKEY); } return true; } User* User::unserialize(MegaClient* client, string* d) { handle uh; time_t ts; visibility_t v; unsigned char l; string m; User* u; const char* ptr = d->data(); const char* end = ptr + d->size(); int i; if (ptr + sizeof(handle) + sizeof(time_t) + sizeof(visibility_t) + 2 > end) { return NULL; } uh = MemAccess::get(ptr); ptr += sizeof uh; // FIXME: use m_time_t & Serialize64 ts = MemAccess::get(ptr); ptr += sizeof ts; v = MemAccess::get(ptr); ptr += sizeof v; l = static_cast(*ptr++); if (l) { if (ptr + l > end) { return NULL; } m.assign(ptr, l); } ptr += l; if (ptr + sizeof(char) + sizeof(char) > end) { return NULL; } char attrVersion = UserAttributeManager::unserializeAttributeFormatVersion(ptr); char bizModeValue = MemAccess::get(ptr); ptr += sizeof(bizModeValue); BizMode bizMode; switch (bizModeValue) { case '0': bizMode = BIZ_MODE_SUBUSER; break; case '1': bizMode = BIZ_MODE_MASTER; break; default: bizMode = BIZ_MODE_UNKNOWN; break; } for (i = 6; i--;) { if (ptr + MemAccess::get(ptr) < end) { ptr += MemAccess::get(ptr) + 1; } } if (i >= 0) { return NULL; } u = client->finduser(uh, 1); if (!u) { return NULL; } client->mapuser(uh, m.c_str()); u->set(v, ts); u->resetTag(); u->mBizMode = bizMode; if (!u->unserializeAttributes(ptr, end, attrVersion)) { client->discarduser(uh); return nullptr; } // initialize private Ed25519 and Cu25519 from cache if (u->userhandle == client->me) { string prEd255, prCu255; const UserAttribute* keysAttribute = u->getAttribute(ATTR_KEYS); if (keysAttribute && !keysAttribute->isNotExisting()) { client->mKeyManager.setKey(client->key); if (client->mKeyManager.fromKeysContainer(keysAttribute->value())) { prEd255 = client->mKeyManager.privEd25519(); prCu255 = client->mKeyManager.privCu25519(); } } if (!client->mKeyManager.generation()) { const UserAttribute* attribute = u->getAttribute(ATTR_KEYRING); if (attribute && attribute->isValid()) { unique_ptr records{ tlv::containerToRecords(attribute->value(), client->key)}; if (records) { prEd255.swap((*records)[EdDSA::TLV_KEY]); prCu255.swap((*records)[ECDH::TLV_KEY]); } else { LOG_warn << "Failed to decrypt keyring from cache"; } } } if (prEd255.size()) { client->mEd255Key = new EdDSA(client->rng, (unsigned char*)prEd255.data()); if (!client->mEd255Key->initializationOK) { delete client->mEd255Key; client->mEd255Key = NULL; LOG_warn << "Failed to load chat key from local cache."; } else { LOG_info << "Signing key loaded from local cache."; } } if (prCu255.size()) { client->mX255Key = new ECDH(prCu255); if (!client->mX255Key->initializationOK) { delete client->mX255Key; client->mX255Key = NULL; LOG_warn << "Failed to load chat key from local cache."; } else { LOG_info << "Chat key successfully loaded from local cache."; } } } if ((ptr < end) && !u->pubk.setkey(AsymmCipher::PUBKEY, (byte*)ptr, int(end - ptr))) { client->discarduser(uh); return NULL; } return u; } bool User::unserializeAttributes(const char*& from, const char* upTo, char formatVersion) { return mAttributeManager->unserializeAttributes(from, upTo, formatVersion); } void User::removepkrs(MegaClient* client) { while (!pkrs.empty()) // protect any pending pubKey request { auto& pka = pkrs.front(); if (pka->cmd) { pka->cmd->invalidateUser(); } pka->proc(client, this); pkrs.pop_front(); } } void User::setAttribute(attr_t at, const string& value, const string& version) { setChanged(at); mAttributeManager->set(at, value, version); } bool User::updateAttributeIfDifferentVersion(attr_t at, const string& value, const string& version) { if (mAttributeManager->setIfNewVersion(at, value, version)) { setChanged(at); return true; } return false; } void User::setAttributeExpired(attr_t at) { if (mAttributeManager->setExpired(at)) { setChanged(at); } } const UserAttribute* User::getAttribute(attr_t at) const { return mAttributeManager->get(at); } void User::removeAttribute(attr_t at) { if (mAttributeManager->erase(at)) { setChanged(at); } } void User::removeAttributeUpdateVersion(attr_t at, const string& version) { if (mAttributeManager->eraseUpdateVersion(at, version)) { setChanged(at); } } void User::cacheNonExistingAttributes() { mAttributeManager->cacheNonExistingAttributes(); } string User::attr2string(attr_t type) { return UserAttributeManager::getName(type); } string User::attr2longname(attr_t type) { return UserAttributeManager::getLongName(type); } attr_t User::string2attr(const char* name) { return UserAttributeManager::getType(name); } int User::needversioning(attr_t at) { return UserAttributeManager::getVersioningEnabled(at); } char User::scope(attr_t at) { return UserAttributeManager::getScope(at); } bool User::isAuthring(attr_t at) { return (at == ATTR_AUTHRING || at == ATTR_AUTHCU255); } size_t User::getMaxAttributeSize(attr_t at) { return UserAttributeManager::getMaxSize(at); } bool User::mergePwdReminderData(int numDetails, const char *data, unsigned int size, string *newValue) { if (numDetails == 0) { return false; } // format: :::: string oldValue; if (data && size) { oldValue.assign(data, size); // ensure the old value has a valid format if (std::count(oldValue.begin(), oldValue.end(), ':') != 4 || oldValue.length() < 9) { oldValue = "0:0:0:0:0"; } } else // no existing value, set with default values and update it consequently { oldValue = "0:0:0:0:0"; } bool lastSuccess = (numDetails & PWD_LAST_SUCCESS) != 0; bool lastSkipped = (numDetails & PWD_LAST_SKIPPED) != 0; bool mkExported = (numDetails & PWD_MK_EXPORTED) != 0; bool dontShowAgain = (numDetails & PWD_DONT_SHOW) != 0; bool lastLogin = (numDetails & PWD_LAST_LOGIN) != 0; bool changed = false; // Timestamp for last successful validation of password in PRD m_time_t tsLastSuccess; size_t len = oldValue.find(":"); string buf = oldValue.substr(0, len) + "#"; // add character control '#' for conversion oldValue = oldValue.substr(len + 1); // skip ':' if (lastSuccess) { changed = true; tsLastSuccess = m_time(); } else { char *pEnd = NULL; tsLastSuccess = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastSuccess == LLONG_MAX || tsLastSuccess == LLONG_MIN) { tsLastSuccess = 0; changed = true; } } // Timestamp for last time the PRD was skipped m_time_t tsLastSkipped; len = oldValue.find(":"); buf = oldValue.substr(0, len) + "#"; oldValue = oldValue.substr(len + 1); if (lastSkipped) { tsLastSkipped = m_time(); changed = true; } else { char *pEnd = NULL; tsLastSkipped = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastSkipped == LLONG_MAX || tsLastSkipped == LLONG_MIN) { tsLastSkipped = 0; changed = true; } } // Flag for Recovery Key exported bool flagMkExported; len = oldValue.find(":"); if (len != 1) { return false; } buf = oldValue.substr(0, len) + "#"; oldValue = oldValue.substr(len + 1); if (mkExported && !(buf.at(0) == '1')) { flagMkExported = true; changed = true; } else { char *pEnd = NULL; long tmp = strtol(buf.data(), &pEnd, 10); if (*pEnd != '#' || (tmp != 0 && tmp != 1)) { flagMkExported = false; changed = true; } else { flagMkExported = tmp != 0; } } // Flag for "Don't show again" the PRD bool flagDontShowAgain; len = oldValue.find(":"); if (len != 1 || len + 1 == oldValue.length()) { return false; } buf = oldValue.substr(0, len) + "#"; oldValue = oldValue.substr(len + 1); if (dontShowAgain && !(buf.at(0) == '1')) { flagDontShowAgain = true; changed = true; } else { char *pEnd = NULL; long tmp = strtol(buf.data(), &pEnd, 10); if (*pEnd != '#' || (tmp != 0 && tmp != 1)) { flagDontShowAgain = false; changed = true; } else { flagDontShowAgain = tmp != 0; } } // Timestamp for last time user logged in m_time_t tsLastLogin = 0; len = oldValue.length(); if (lastLogin) { tsLastLogin = m_time(); changed = true; } else { buf = oldValue.substr(0, len) + "#"; char *pEnd = NULL; tsLastLogin = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastLogin == LLONG_MAX || tsLastLogin == LLONG_MIN) { tsLastLogin = 0; changed = true; } } std::stringstream value; value << tsLastSuccess << ":" << tsLastSkipped << ":" << flagMkExported << ":" << flagDontShowAgain << ":" << tsLastLogin; *newValue = value.str(); return changed; } m_time_t User::getPwdReminderData(int numDetail, const char *data, unsigned int size) { if (!numDetail || !data || !size) { return 0; } // format: :::: string value; value.assign(data, size); // ensure the value has a valid format if (std::count(value.begin(), value.end(), ':') != 4 || value.length() < 9) { return 0; } bool lastSuccess = (numDetail & PWD_LAST_SUCCESS) != 0; bool lastSkipped = (numDetail & PWD_LAST_SKIPPED) != 0; bool mkExported = (numDetail & PWD_MK_EXPORTED) != 0; bool dontShowAgain = (numDetail & PWD_DONT_SHOW) != 0; bool lastLogin = (numDetail & PWD_LAST_LOGIN) != 0; // Timestamp for last successful validation of password in PRD m_time_t tsLastSuccess; size_t len = value.find(":"); string buf = value.substr(0, len) + "#"; // add character control '#' for conversion value = value.substr(len + 1); // skip ':' if (lastSuccess) { char *pEnd = NULL; tsLastSuccess = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastSuccess == LLONG_MAX || tsLastSuccess == LLONG_MIN) { tsLastSuccess = 0; } return tsLastSuccess; } // Timestamp for last time the PRD was skipped m_time_t tsLastSkipped; len = value.find(":"); buf = value.substr(0, len) + "#"; value = value.substr(len + 1); if (lastSkipped) { char *pEnd = NULL; tsLastSkipped = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastSkipped == LLONG_MAX || tsLastSkipped == LLONG_MIN) { tsLastSkipped = 0; } return tsLastSkipped; } // Flag for Recovery Key exported len = value.find(":"); buf = value.substr(0, len) + "#"; value = value.substr(len + 1); if (mkExported) { char *pEnd = NULL; m_time_t flagMkExported = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || (flagMkExported != 0 && flagMkExported != 1)) { flagMkExported = 0; } return flagMkExported; } // Flag for "Don't show again" the PRD len = value.find(":"); buf = value.substr(0, len) + "#"; value = value.substr(len + 1); if (dontShowAgain) { char *pEnd = NULL; m_time_t flagDontShowAgain = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || (flagDontShowAgain != 0 && flagDontShowAgain != 1)) { flagDontShowAgain = 0; } return flagDontShowAgain; } // Timestamp for last time user logged in m_time_t tsLastLogin = 0; len = value.length(); if (lastLogin) { buf = value.substr(0, len) + "#"; char *pEnd = NULL; tsLastLogin = strtoll(buf.data(), &pEnd, 10); if (*pEnd != '#' || tsLastLogin == LLONG_MAX || tsLastLogin == LLONG_MIN) { tsLastLogin = 0; } return tsLastLogin; } return 0; } bool User::setChanged(attr_t at) { switch(at) { case ATTR_AVATAR: changed.avatar = true; break; case ATTR_FIRSTNAME: changed.firstname = true; break; case ATTR_LASTNAME: changed.lastname = true; break; case ATTR_AUTHRING: changed.authring = true; break; case ATTR_AUTHCU255: changed.authcu255 = true; break; case ATTR_LAST_INT: changed.lstint = true; break; case ATTR_ED25519_PUBK: changed.puEd255 = true; break; case ATTR_CU25519_PUBK: changed.puCu255 = true; break; case ATTR_SIG_RSA_PUBK: changed.sigPubk = true; break; case ATTR_SIG_CU255_PUBK: changed.sigCu255 = true; break; case ATTR_KEYRING: changed.keyring = true; break; case ATTR_COUNTRY: changed.country = true; break; case ATTR_BIRTHDAY: case ATTR_BIRTHMONTH: case ATTR_BIRTHYEAR: changed.birthday = true; break; case ATTR_LANGUAGE: changed.language = true; break; case ATTR_PWD_REMINDER: changed.pwdReminder = true; break; case ATTR_DISABLE_VERSIONS: changed.disableVersions = true; break; case ATTR_NO_CALLKIT: changed.noCallKit = true; break; case ATTR_CONTACT_LINK_VERIFICATION: changed.contactLinkVerification = true; break; case ATTR_RICH_PREVIEWS: changed.richPreviews = true; break; case ATTR_LAST_PSA: changed.lastPsa = true; break; case ATTR_RUBBISH_TIME: changed.rubbishTime = true; break; case ATTR_STORAGE_STATE: changed.storageState = true; break; case ATTR_GEOLOCATION: changed.geolocation = true; break; case ATTR_CAMERA_UPLOADS_FOLDER: changed.cameraUploadsFolder = true; break; case ATTR_MY_CHAT_FILES_FOLDER: changed.myChatFilesFolder = true; break; case ATTR_PUSH_SETTINGS: changed.pushSettings = true; break; case ATTR_ALIAS: changed.alias = true; break; case ATTR_UNSHAREABLE_KEY: changed.unshareablekey = true; break; case ATTR_DEVICE_NAMES: changed.devicenames = true; break; case ATTR_MY_BACKUPS_FOLDER: changed.myBackupsFolder = true; break; case ATTR_COOKIE_SETTINGS: changed.cookieSettings = true; break; case ATTR_JSON_SYNC_CONFIG_DATA: changed.jsonSyncConfigData = true; break; case ATTR_KEYS: changed.keys = true; changed.authring = true; break; case ATTR_APPS_PREFS: changed.aPrefs = true; break; case ATTR_CC_PREFS: changed.ccPrefs = true; break; case ATTR_ENABLE_TEST_NOTIFICATIONS: changed.enableTestNotifications = true; break; case ATTR_LAST_READ_NOTIFICATION: changed.lastReadNotification = true; break; case ATTR_LAST_ACTIONED_BANNER: changed.lastActionedBanner = true; break; case ATTR_ENABLE_TEST_SURVEYS: changed.enableTestSurveys = true; break; case ATTR_RECENT_CLEAR_TIMESTAMP: changed.recentClearTimestamp = true; break; default: return false; } return true; } void User::setTag(int newTag) { if (tag != 0) // external changes prevail { tag = newTag; } } int User::getTag() { return tag; } void User::resetTag() { tag = -1; } // update user attributes void User::set(visibility_t v, m_time_t ct) { show = v; ctime = ct; } string User::attributePrefixInTLV(attr_t type, bool modifier) { if (type == ATTR_DEVICE_NAMES && modifier) { return "ext:"; } return string(); } AuthRing::AuthRing(attr_t type, const string_map& authring): mType(type) { if (auto it = authring.find(""); it != authring.end()) { if (!deserialize(it->second)) { LOG_warn << "Excess data while deserializing Authring (TLV) of type: " << type; } } } AuthRing::AuthRing(attr_t type, const std::string &authValue) : mType(type) { if (!deserialize(authValue)) { LOG_warn << "Excess data while deserializing Authring (string) of type: " << type; } } bool AuthRing::deserialize(const string& authValue) { if (authValue.empty()) return true; handle userhandle; byte authFingerprint[20]; signed char authMethod = AUTH_METHOD_UNKNOWN; const char *ptr = authValue.data(); const char *end = ptr + authValue.size(); unsigned recordSize = 29; // while (ptr + recordSize <= end) { memcpy(&userhandle, ptr, sizeof(userhandle)); ptr += sizeof(userhandle); memcpy(authFingerprint, ptr, sizeof(authFingerprint)); ptr += sizeof(authFingerprint); memcpy(&authMethod, ptr, sizeof(authMethod)); ptr += sizeof(authMethod); mFingerprint[userhandle] = string((const char*) authFingerprint, sizeof(authFingerprint)); mAuthMethod[userhandle] = static_cast(authMethod); } return ptr == end; } std::string* AuthRing::serialize(PrnGen &rng, SymmCipher &key) const { string buf = serializeForJS(); string_map records{ {{}, std::move(buf)} }; return tlv::recordsToContainer(std::move(records), rng, key).release(); } string AuthRing::serializeForJS() const { string buf; map::const_iterator itFingerprint; map::const_iterator itAuthMethod; for (itFingerprint = mFingerprint.begin(), itAuthMethod = mAuthMethod.begin(); itFingerprint != mFingerprint.end() && itAuthMethod != mAuthMethod.end(); itFingerprint++, itAuthMethod++) { buf.append((const char *)&itFingerprint->first, sizeof(handle)); buf.append(itFingerprint->second); buf.append((const char *)&itAuthMethod->second, 1); } return buf; } bool AuthRing::isTracked(handle uh) const { return mAuthMethod.find(uh) != mAuthMethod.end(); } AuthMethod AuthRing::getAuthMethod(handle uh) const { AuthMethod authMethod = AUTH_METHOD_UNKNOWN; auto it = mAuthMethod.find(uh); if (it != mAuthMethod.end()) { authMethod = it->second; } return authMethod; } std::string AuthRing::getFingerprint(handle uh) const { string fingerprint; auto it = mFingerprint.find(uh); if (it != mFingerprint.end()) { fingerprint = it->second; } return fingerprint; } vector AuthRing::getTrackedUsers() const { vector users; for (auto &it : mFingerprint) { users.push_back(it.first); } return users; } void AuthRing::add(handle uh, const std::string &fingerprint, AuthMethod authMethod) { assert(mFingerprint.find(uh) == mFingerprint.end()); assert(mAuthMethod.find(uh) == mAuthMethod.end()); mFingerprint[uh] = fingerprint; mAuthMethod[uh] = authMethod; mNeedsUpdate = true; } void AuthRing::update(handle uh, AuthMethod authMethod) { mAuthMethod.at(uh) = authMethod; mNeedsUpdate = true; } attr_t AuthRing::keyTypeToAuthringType(attr_t at) { if (at == ATTR_ED25519_PUBK) { return ATTR_AUTHRING; } else if (at == ATTR_CU25519_PUBK) { return ATTR_AUTHCU255; } assert(false); return ATTR_UNKNOWN; } attr_t AuthRing::signatureTypeToAuthringType(attr_t at) { if (at == ATTR_SIG_CU255_PUBK) { return ATTR_AUTHCU255; } assert(false); return ATTR_UNKNOWN; } attr_t AuthRing::authringTypeToSignatureType(attr_t at) { if (at == ATTR_AUTHCU255) { return ATTR_SIG_CU255_PUBK; } assert(false); return ATTR_UNKNOWN; } std::string AuthRing::authMethodToStr(AuthMethod authMethod) { if (authMethod == AUTH_METHOD_SEEN) { return "seen"; } else if (authMethod == AUTH_METHOD_FINGERPRINT) { return "fingerprint comparison"; } else if (authMethod == AUTH_METHOD_SIGNATURE) { return "signature verified"; } return "unknown"; } string AuthRing::toString(const AuthRing &authRing) { auto uhVector = authRing.getTrackedUsers(); ostringstream result; for (auto& i : uhVector) { result << "\t[" << toHandle(i) << "] " << Base64::btoa(authRing.getFingerprint(i)) << " | " <(pubKey.size())); string result; hash.get(&result); result.erase(20); // keep only the most significant 160 bits if (hexadecimal) { return Utils::stringToHex(result); } return result; } bool AuthRing::isSignedKey() const { return mType != ATTR_AUTHRING; } bool AuthRing::areCredentialsVerified(handle uh) const { if (isSignedKey()) { return getAuthMethod(uh) == AUTH_METHOD_SIGNATURE; } else { return getAuthMethod(uh) == AUTH_METHOD_FINGERPRINT; } } } // namespace sdk-10.11.0/src/user_attribute.cpp000066400000000000000000000010721516266226600170030ustar00rootroot00000000000000#include "mega/user_attribute.h" #include "mega/user_attribute_definition.h" #include using namespace std; namespace mega { void UserAttribute::set(const string& value, const string& version) { mValue = value; // Version is stored even for attributes marked as not supporting it. // Notably "firstname" does come with version populated, but it is not used in case of update. mVersion = version; mState = State::VALID; } bool UserAttribute::useVersioning() const { return mDefinition.versioningEnabled(); } } // namespace sdk-10.11.0/src/user_attribute_definition.cpp000066400000000000000000000134521516266226600212200ustar00rootroot00000000000000#include "mega/user_attribute_definition.h" #include #include #include using namespace std; namespace mega { const UserAttributeDefinition* UserAttributeDefinition::get(attr_t at) { const auto& defs = getAllDefinitions(); const auto it = defs.find(at); return it == defs.end() ? nullptr : &it->second; } attr_t UserAttributeDefinition::getTypeForName(const string& name) { for (const auto& d: getAllDefinitions()) { if (d.second.name() == name) return d.first; } return ATTR_UNKNOWN; // attribute not recognized } UserAttributeDefinition::UserAttributeDefinition(string&& name, string&& longName, int customOptions): mName(std::move(name)), mLongName(std::move(longName)) { if (mName.empty()) { assert(!mName.empty()); return; } switch (mName[0]) { case ATTR_SCOPE_PUBLIC_UNENCRYPTED: case ATTR_SCOPE_PROTECTED_UNENCRYPTED: case ATTR_SCOPE_PRIVATE_UNENCRYPTED: case ATTR_SCOPE_PRIVATE_ENCRYPTED: case ATTR_SCOPE_BUSINESS_UNENCRYPTED: case ATTR_SCOPE_BUSINESS_ENCRYPTED: mScope = mName[0]; } bool hasModifier = mScope != ATTR_SCOPE_UNKNOWN && mName.size() > 1 && (mName[1] == '!' || mName[1] == '~'); mMaxSize = hasModifier ? MAX_USER_VAR_SIZE : MAX_USER_ATTRIBUTE_SIZE; mUseVersioning = !(customOptions & DISABLE_VERSIONING); // allow setting (only one) explicit scope when prefix did not contain it if (customOptions & MAKE_PROTECTED) { assert(mScope == ATTR_SCOPE_UNKNOWN && !(customOptions & MAKE_PRIVATE)); mScope = ATTR_SCOPE_PROTECTED_UNENCRYPTED; } else if (customOptions & MAKE_PRIVATE) { assert(mScope == ATTR_SCOPE_UNKNOWN && !(customOptions & MAKE_PROTECTED)); mScope = ATTR_SCOPE_PRIVATE_UNENCRYPTED; } } const unordered_map& UserAttributeDefinition::getAllDefinitions() { // Creating this map all at once should be fine in terms of complexity - populated once, and // most likely in a secondary thread. It also allows clean code and avoids having to write // attribute names multiple times. static unordered_map defs{ {ATTR_AVATAR, {"+a", "AVATAR", DISABLE_VERSIONING}}, {ATTR_FIRSTNAME, {"firstname", "FIRSTNAME", DISABLE_VERSIONING | MAKE_PROTECTED}}, {ATTR_LASTNAME, {"lastname", "LASTNAME", DISABLE_VERSIONING | MAKE_PROTECTED}}, {ATTR_AUTHRING, {"*!authring", "AUTHRING"}}, {ATTR_LAST_INT, {"*!lstint", "LAST_INT"}}, {ATTR_ED25519_PUBK, {"+puEd255", "ED25519_PUBK"}}, {ATTR_CU25519_PUBK, {"+puCu255", "CU25519_PUBK"}}, {ATTR_KEYRING, {"*keyring", "KEYRING"}}, {ATTR_SIG_RSA_PUBK, {"+sigPubk", "SIG_RSA_PUBK"}}, {ATTR_SIG_CU255_PUBK, {"+sigCu255", "SIG_CU255_PUBK"}}, {ATTR_COUNTRY, {"country", "COUNTRY", DISABLE_VERSIONING | MAKE_PRIVATE}}, {ATTR_BIRTHDAY, {"birthday", "BIRTHDAY", DISABLE_VERSIONING | MAKE_PRIVATE}}, {ATTR_BIRTHMONTH, {"birthmonth", "BIRTHMONTH", DISABLE_VERSIONING | MAKE_PRIVATE}}, {ATTR_BIRTHYEAR, {"birthyear", "BIRTHYEAR", DISABLE_VERSIONING | MAKE_PRIVATE}}, {ATTR_LANGUAGE, {"^!lang", "LANGUAGE", DISABLE_VERSIONING}}, {ATTR_PWD_REMINDER, {"^!prd", "PWD_REMINDER", DISABLE_VERSIONING}}, {ATTR_DISABLE_VERSIONS, {"^!dv", "DISABLE_VERSIONS", DISABLE_VERSIONING}}, {ATTR_CONTACT_LINK_VERIFICATION, {"^clv", "CONTACT_LINK_VERIFICATION"}}, {ATTR_RICH_PREVIEWS, {"*!rp", "RICH_PREVIEWS", DISABLE_VERSIONING}}, {ATTR_RUBBISH_TIME, {"^!rubbishtime", "RUBBISH_TIME", DISABLE_VERSIONING}}, {ATTR_LAST_PSA, {"^!lastPsa", "LAST_PSA", DISABLE_VERSIONING}}, {ATTR_STORAGE_STATE, {"^!usl", "STORAGE_STATE", DISABLE_VERSIONING}}, {ATTR_GEOLOCATION, {"*!geo", "GEOLOCATION", DISABLE_VERSIONING}}, {ATTR_CAMERA_UPLOADS_FOLDER, {"*!cam", "CAMERA_UPLOADS_FOLDER"}}, {ATTR_MY_CHAT_FILES_FOLDER, {"*!cf", "MY_CHAT_FILES_FOLDER", DISABLE_VERSIONING}}, {ATTR_PUSH_SETTINGS, {"^!ps", "PUSH_SETTINGS", DISABLE_VERSIONING}}, {ATTR_UNSHAREABLE_KEY, {"*~usk", "UNSHAREABLE_KEY"}}, {ATTR_ALIAS, {"*!>alias", "ALIAS"}}, {ATTR_AUTHCU255, {"*!authCu255", "AUTHCU255"}}, {ATTR_DEVICE_NAMES, {"*!dn", "DEVICE_NAMES"}}, {ATTR_MY_BACKUPS_FOLDER, {"^!bak", "MY_BACKUPS_FOLDER"}}, {ATTR_COOKIE_SETTINGS, {"^!csp", "COOKIE_SETTINGS", DISABLE_VERSIONING}}, {ATTR_JSON_SYNC_CONFIG_DATA, {"*~jscd", "JSON_SYNC_CONFIG_DATA"}}, {ATTR_NO_CALLKIT, {"^!nokit", "NO_CALLKIT", DISABLE_VERSIONING}}, {ATTR_KEYS, {"^!keys", "KEYS"}}, {ATTR_APPS_PREFS, {"*!aPrefs", "APPS_PREFS"}}, {ATTR_CC_PREFS, {"*!ccPref", "CC_PREFS"}}, {ATTR_VISIBLE_WELCOME_DIALOG, {"^!weldlg", "VISIBLE_WELCOME_DIALOG"}}, {ATTR_VISIBLE_TERMS_OF_SERVICE, {"^!tos", "VISIBLE_TERMS_OF_SERVICE"}}, {ATTR_PWM_BASE, {"pwmh", "PWM_BASE", DISABLE_VERSIONING | MAKE_PRIVATE}}, {ATTR_ENABLE_TEST_NOTIFICATIONS, {"^!tnotif", "ENABLE_TEST_NOTIFICATIONS"}}, {ATTR_LAST_READ_NOTIFICATION, {"^!lnotif", "LAST_READ_NOTIFICATION"}}, {ATTR_LAST_ACTIONED_BANNER, {"^!lbannr", "LAST_ACTIONED_BANNER"}}, {ATTR_ENABLE_TEST_SURVEYS, {"^!tsur", "ENABLE_TEST_SURVEYS", DISABLE_VERSIONING}}, {ATTR_SYNC_DESIRED_STATE, {"*!sds", "DEVICE_CENTER_SDS"}}, {ATTR_S4, {"s4", "S4", MAKE_PRIVATE}}, {ATTR_S4_CONTAINER, {"s4c", "S4_CONTAINER", MAKE_PRIVATE}}, {ATTR_DEV_OPT, {"^!devopt", "DEV_OPT"}}, {ATTR_RECENT_CLEAR_TIMESTAMP, {"*!rcts", "RECENT_CLEAR_TIMESTAMP", DISABLE_VERSIONING}}, }; return defs; } } // namespace sdk-10.11.0/src/user_attribute_manager.cpp000066400000000000000000000214721516266226600205030ustar00rootroot00000000000000#include "mega/user_attribute_manager.h" #include "mega/attrmap.h" // for AttrMap #include "mega/user_attribute_definition.h" #include "mega/utils.h" // for MemAccess #include #include using namespace std; namespace mega { void UserAttributeManager::set(attr_t at, const string& value, const string& version) { const UserAttributeDefinition* d = UserAttributeDefinition::get(at); if (!d) { assert(d); return; } UserAttribute& attr = mAttributes.try_emplace(at, *d).first->second; if (at == ATTR_AVATAR) // avatar is saved to disc, keep only its version { attr.set("", version); } else { attr.set(value, version); } } bool UserAttributeManager::setIfNewVersion(attr_t at, const string& value, const string& version) { const UserAttributeDefinition* d = UserAttributeDefinition::get(at); if (!d) { assert(d); return false; } auto insertResult = mAttributes.try_emplace(at, *d); UserAttribute& attr = insertResult.first->second; if (!insertResult.second && attr.version() == version) { if (attr.isExpired()) { /* * In case we previously has marked an attr as expired, due to a version mismatch, and * we later receive the same attr version that we keep with expired status, we should * restore it's valid status. * * This prevent that an expired attr get stucked with expired status. */ attr.setValid(); return true; } return false; } if (at == ATTR_AVATAR) // avatar is saved to disc, keep only its version { attr.set("", version); } else { attr.set(value, version); } return true; } bool UserAttributeManager::setNotExisting(attr_t at) { const UserAttributeDefinition* d = UserAttributeDefinition::get(at); if (!d) { assert(d); // undefined attribute return false; } UserAttribute& attr = mAttributes.try_emplace(at, *d).first->second; if (attr.isNotExisting()) return false; attr.setNotExisting(); return true; } bool UserAttributeManager::setExpired(attr_t at) { const auto it = mAttributes.find(at); if (it != mAttributes.end() && !it->second.isExpired()) { it->second.setExpired(); return true; } return false; } bool UserAttributeManager::isValid(attr_t at) const { auto itAttr = mAttributes.find(at); return itAttr != mAttributes.end() && itAttr->second.isValid(); } const UserAttribute* UserAttributeManager::get(attr_t at) const { auto itAttr = mAttributes.find(at); return itAttr == mAttributes.end() ? nullptr : &itAttr->second; } bool UserAttributeManager::erase(attr_t at) { if (mCacheNonExistingAttributes) { return setNotExisting(at); } else { return mAttributes.erase(at) > 0; } } bool UserAttributeManager::eraseUpdateVersion(attr_t at, const std::string& version) { auto itAttr{mAttributes.find(at)}; if (itAttr != mAttributes.end() && (itAttr->second.isValid() || itAttr->second.version() != version)) { bool notExisting = itAttr->second.isNotExisting(); itAttr->second.set("", version); notExisting ? itAttr->second.setNotExisting() : itAttr->second.setExpired(); return true; } return false; } void UserAttributeManager::serializeAttributeFormatVersion(string& appendTo) const { static constexpr char attributeFormatVersion = '2'; // Version 1: attributes are serialized along with its version // Version 2: size of attributes use 4B (uint32_t) instead of 2B (unsigned short) appendTo += attributeFormatVersion; } char UserAttributeManager::unserializeAttributeFormatVersion(const char*& from) { assert(from != nullptr); char attrVersion = *from; ++from; return attrVersion; } void UserAttributeManager::serializeAttributes(string& d) const { assert(mAttributes.size() <= numeric_limits::max()); // serialize attr count auto attrCount = std::count_if(mAttributes.begin(), mAttributes.end(), [](const auto& attr) { return attr.second.isValid(); }); assert(attrCount <= numeric_limits::max()); d += static_cast(attrCount); for (const auto& a: mAttributes) { if (!a.second.isValid()) continue; // serialize type d.append(reinterpret_cast(&a.first), sizeof(a.first)); // serialize value const string& value = a.second.value(); assert(value.size() <= numeric_limits::max()); uint32_t valueSize = static_cast(value.size()); d.append(reinterpret_cast(&valueSize), sizeof(valueSize)); d.append(value.data(), valueSize); // serialize version const string& version = a.second.version(); assert(version.size() <= numeric_limits::max()); uint16_t versionSize = static_cast(version.size()); d.append(reinterpret_cast(&versionSize), sizeof(versionSize)); if (versionSize) d.append(version.data(), versionSize); } } bool UserAttributeManager::unserializeAttributes(const char*& from, const char* upTo, char formatVersion) { if (formatVersion == '1' || formatVersion == '2') { if (from + sizeof(char) > upTo) return false; unsigned char attrCount = static_cast(*from++); size_t sizeLength = (formatVersion == '1') ? 2 : 4; // formatVersion == 1 -> size of value uses 2 bytes // formatVersion == 2 -> size of value uses 4 bytes for (int i = 0; i < attrCount; i++) { if (from + sizeof(attr_t) + sizeLength > upTo) return false; // type attr_t at = MemAccess::get(from); from += sizeof(at); // size of value uint32_t valueSize = formatVersion == '1' ? MemAccess::get(from) : MemAccess::get(from); from += sizeLength; if (from + valueSize + sizeof(uint16_t) > upTo) return false; // skip value for now auto* tmpVal = from; from += valueSize; // size of version uint16_t versionSize = MemAccess::get(from); from += sizeof(versionSize); auto* tmpVer = from; if (versionSize) { if (from + versionSize > upTo) return false; from += versionSize; } // keep the ones that were not already loaded (i.e. by `ug` for own user), or // have been removed if (!isValid(at)) { string value{tmpVal, valueSize}; string version{(versionSize ? string{tmpVer, versionSize} : string{})}; set(at, value, version); } } } else if (formatVersion == '\0') { // ignore user attributes in this format AttrMap attrmap; if (from >= upTo) return false; from = attrmap.unserialize(from, upTo); if (!from) return false; } return true; } string UserAttributeManager::getName(attr_t at) { const UserAttributeDefinition* ad = UserAttributeDefinition::get(at); return ad ? ad->name() : string{}; } string UserAttributeManager::getLongName(attr_t at) { const UserAttributeDefinition* ad = UserAttributeDefinition::get(at); return ad ? ad->longName() : string{}; } attr_t UserAttributeManager::getType(const string& name) { return UserAttributeDefinition::getTypeForName(name); } char UserAttributeManager::getScope(attr_t at) { const UserAttributeDefinition* ad = UserAttributeDefinition::get(at); return ad ? ad->scope() : static_cast(ATTR_SCOPE_UNKNOWN); } int UserAttributeManager::getVersioningEnabled(attr_t at) { static constexpr int VERSIONING_ENABLED_UNKNOWN = -1; if (at == ATTR_STORAGE_STATE) return VERSIONING_ENABLED_UNKNOWN; // help block putua for this attribute const UserAttributeDefinition* ad = UserAttributeDefinition::get(at); return ad ? ad->versioningEnabled() : VERSIONING_ENABLED_UNKNOWN; } size_t UserAttributeManager::getMaxSize(attr_t at) { const UserAttributeDefinition* ad = UserAttributeDefinition::get(at); return ad ? ad->maxSize() : UserAttributeDefinition::getDefaultMaxSize(); } } // namespace sdk-10.11.0/src/useralerts.cpp000066400000000000000000002631331516266226600161430ustar00rootroot00000000000000/** * @file usernotifications.cpp * @brief additional megaclient code for user notifications * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "mega/megaclient.h" #include "mega/useralerts.h" #include using std::to_string; using std::make_pair; namespace mega { bool shouldDropStalePaymentReminder(MegaClient& mc, const UserAlert::PaymentReminder& reminder) { auto proLevel = mc.mMyAccount.getProLevel(); const bool isPro = proLevel > AccountType::ACCOUNT_TYPE_FREE && proLevel != AccountType::ACCOUNT_TYPE_FEATURE; if (!isPro) { return false; } const m_time_t timeLeft = mc.mMyAccount.getTimeLeft(); if (timeLeft <= 0) { return false; } return reminder.expiryTime < m_time(); } UserAlertRaw::UserAlertRaw() : t(0) { } JSON UserAlertRaw::field(nameid nid) const { map::const_iterator i = fields.find(nid); JSON j; j.pos = i == fields.end() ? NULL : i->second.c_str(); return j; } bool UserAlertRaw::has(nameid nid) const { JSON j = field(nid); return j.pos != NULL; } int UserAlertRaw::getint(nameid nid, int default_value) const { JSON j = field(nid); return j.pos && j.isnumeric() ? int(j.getint()) : default_value; } int64_t UserAlertRaw::getint64(nameid nid, int64_t default_value) const { JSON j = field(nid); return j.pos && j.isnumeric() ? j.getint() : default_value; } handle UserAlertRaw::gethandle(nameid nid, int handlesize, handle default_value) const { JSON j = field(nid); byte buf[9] = { 0 }; return (j.pos && handlesize == Base64::atob(j.pos, buf, sizeof(buf))) ? MemAccess::get((const char*)buf) : default_value; } nameid UserAlertRaw::getnameid(nameid nid, nameid default_value) const { JSON j = field(nid); nameid id = 0; while (*j.pos) { id = (id << 8) + static_cast(*j.pos++); } return id ? id : default_value; } string UserAlertRaw::getstring(nameid nid, const char* default_value) const { JSON j = field(nid); return j.pos ? j.pos : default_value; } bool UserAlertRaw::gethandletypearray(nameid nid, vector& v) const { JSON j = field(nid); if (j.pos && j.enterarray()) { for (;;) { if (j.enterobject()) { handletype ht; ht.h = UNDEF; ht.t = -1; for (bool reading = true; reading;) { switch (j.getnameid()) { case makeNameid("h"): ht.h = j.gethandle(MegaClient::NODEHANDLE); break; case makeNameid("t"): ht.t = int(j.getint()); break; case EOO: reading = false; break; default: j.storeobject(NULL); } } v.push_back(ht); j.leaveobject(); } else { break; } } j.leavearray(); return true; } return false; } bool UserAlertRaw::getstringarray(nameid nid, vector& v) const { JSON j = field(nid); if (j.pos && j.enterarray()) { for (;;) { string s; if (j.storeobject(&s)) { v.push_back(s); } else { break; } } j.leavearray(); } return false; } UserAlertFlags::UserAlertFlags() : cloud_enabled(true) , contacts_enabled(true) , cloud_newfiles(true) , cloud_newshare(true) , cloud_delshare(true) , contacts_fcrin(true) , contacts_fcrdel(true) , contacts_fcracpt(true) { } UserAlertPendingContact::UserAlertPendingContact() : u(0) { } UserAlert::Base::Base(UserAlertRaw& un, unsigned int cid) { id = cid; type = un.t; m_time_t timeDelta = un.getint64(makeNameid("td"), 0); pst.timestamp = m_time() - timeDelta; pst.userHandle = un.gethandle(name_id::u, MegaClient::USERHANDLE, UNDEF); pst.userEmail = un.getstring('m', ""); tag = -1; } UserAlert::Base::Base(nameid t, handle uh, const string& email, m_time_t ts, unsigned int cid) { id = cid; type = t; pst.userHandle = uh; pst.userEmail = email; pst.timestamp = ts; tag = -1; } UserAlert::Base::~Base() { } void UserAlert::Base::updateEmail(MegaClient* mc) { if (User* u = mc->finduser(user())) { pst.userEmail = u->email; } } bool UserAlert::Base::checkprovisional(handle , MegaClient*) { return true; } void UserAlert::Base::text(string& header, string& title, MegaClient* mc) { // should be overridden updateEmail(mc); ostringstream s; s << "notification: type " << type << " time " << ts() << " user " << user() << " seen " << seen(); title = s.str(); header = email(); } bool UserAlert::Base::serialize(string* d) const { CacheableWriter w(*d); w.serializecompressedu64(type); // this will be unserialized in UserAlerts::unserializeAlert() w.serializecompressedi64(ts()); w.serializehandle(user()); w.serializestring(email()); w.serializebool(relevant()); w.serializebool(seen()); return true; } unique_ptr UserAlert::Base::readBase(CacheableReader& r) { auto p = std::make_unique(); if (r.unserializecompressedi64(p->timestamp) && r.unserializehandle(p->userHandle) && r.unserializestring(p->userEmail) && r.unserializebool(p->relevant) && r.unserializebool(p->seen)) { return p; } return nullptr; } unique_ptr UserAlert::Base::unserialize(std::string* d) { CacheableReader r(*d); if (auto p = readBase(r)) { r.eraseused(*d); return p; } return nullptr; } UserAlert::IncomingPendingContact::IncomingPendingContact(UserAlertRaw& un, unsigned int id) : Base(un, id) { mPcrHandle = un.gethandle('p', MegaClient::PCRHANDLE, UNDEF); pst.userHandle = mPcrHandle; // for backwards compatibility, due to legacy bug m_time_t dts = un.getint64(makeNameid("dts"), 0); m_time_t rts = un.getint64(makeNameid("rts"), 0); initTs(dts, rts); } UserAlert::IncomingPendingContact::IncomingPendingContact(m_time_t dts, m_time_t rts, handle p, const string& email, m_time_t timestamp, unsigned int id): Base(name_id::ipc, p, email, timestamp, id) // passing PCR's handle as the user's handle for backwards compatibility, due to legacy bug { mPcrHandle = p; initTs(dts, rts); } void UserAlert::IncomingPendingContact::initTs(m_time_t dts, m_time_t rts) { requestWasDeleted = dts != 0; requestWasReminded = rts != 0; if (requestWasDeleted) pst.timestamp = dts; else if (requestWasReminded) pst.timestamp = rts; } void UserAlert::IncomingPendingContact::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); if (requestWasDeleted) { title = "Cancelled their contact request"; // 7151 } else if (requestWasReminded) { title = "Reminder: You have a contact request"; // 7150 } else { title = "Sent you a contact request"; // 5851 } header = email(); } bool UserAlert::IncomingPendingContact::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializehandle(mPcrHandle); w.serializebool(requestWasDeleted); w.serializebool(requestWasReminded); w.serializeexpansionflags(); return true; } UserAlert::IncomingPendingContact* UserAlert::IncomingPendingContact::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } handle pcrHandle = 0; bool deleted = false; bool reminded = false; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializehandle(pcrHandle) && r.unserializebool(deleted) && r.unserializebool(reminded) && r.unserializeexpansionflags(expF, 0)) { auto* ipc = new IncomingPendingContact(0, 0, p->userHandle, p->userEmail, p->timestamp, id); ipc->mPcrHandle = pcrHandle; ipc->requestWasDeleted = deleted; ipc->requestWasReminded = reminded; ipc->setRelevant(p->relevant); ipc->setSeen(p->seen); return ipc; } return nullptr; } UserAlert::ContactChange::ContactChange(UserAlertRaw& un, unsigned int id) : Base(un, id) { action = un.getint(name_id::c, -1); pst.relevant = action >= 0 && action < 4; assert(action >= 0 && action < 4); } UserAlert::ContactChange::ContactChange(int c, handle uh, const string& email, m_time_t timestamp, unsigned int id): Base(name_id::c, uh, email, timestamp, id) { action = c; assert(action >= 0 && action < 4); } bool UserAlert::ContactChange::checkprovisional(handle ou, MegaClient* mc) { return ou != mc->me; } void UserAlert::ContactChange::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); if (action == 0) { title = "Deleted you as a contact"; // 7146 } else if (action == 1) { title = "Contact relationship established"; // 7145 } else if (action == 2) { title = "Account has been deleted/deactivated"; // 7144 } else if (action == 3) { title = "Blocked you as a contact"; //7143 } header = email(); } bool UserAlert::ContactChange::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializei32(action); w.serializeexpansionflags(); return true; } UserAlert::ContactChange* UserAlert::ContactChange::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } int act = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializeu32(reinterpret_cast(act)) && r.unserializeexpansionflags(expF, 0)) { auto* cc = new ContactChange(act, p->userHandle, p->userEmail, p->timestamp, id); cc->setRelevant(p->relevant); cc->setSeen(p->seen); return cc; } return nullptr; } UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(UserAlertRaw& un, unsigned int id) : Base(un, id) { action = un.getint('s', -1); pst.relevant = action >= 1 && action < 4; } UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id): Base(name_id::upci, uh, email, timestamp, id), action(s) {} void UserAlert::UpdatedPendingContactIncoming::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); if (action == 1) { title = "You ignored a contact request"; // 7149 } else if (action == 2) { title = "You accepted a contact request"; // 7148 } else if (action == 3) { title = "You denied a contact request"; // 7147 } header = email(); } bool UserAlert::UpdatedPendingContactIncoming::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializei32(action); w.serializeexpansionflags(); return true; } UserAlert::UpdatedPendingContactIncoming* UserAlert::UpdatedPendingContactIncoming::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } int act = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializeu32(reinterpret_cast(act)) && r.unserializeexpansionflags(expF, 0)) { auto* upci = new UpdatedPendingContactIncoming(act, p->userHandle, p->userEmail, p->timestamp, id); upci->setRelevant(p->relevant); upci->setSeen(p->seen); return upci; } return nullptr; } UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(UserAlertRaw& un, unsigned int id) : Base(un, id) { action = un.getint('s', -1); pst.relevant = action == 2 || action == 3; } UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id): Base(name_id::upco, uh, email, timestamp, id), action(s) {} void UserAlert::UpdatedPendingContactOutgoing::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); if (action == 2) { title = "Accepted your contact request"; // 5852 } else if (action == 3) { title = "Denied your contact request"; // 5853 } header = email(); } bool UserAlert::UpdatedPendingContactOutgoing::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializei32(action); w.serializeexpansionflags(); return true; } UserAlert::UpdatedPendingContactOutgoing* UserAlert::UpdatedPendingContactOutgoing::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } int act = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializeu32(reinterpret_cast(act)) && r.unserializeexpansionflags(expF, 0)) { auto* upco = new UpdatedPendingContactOutgoing(act, p->userHandle, p->userEmail, p->timestamp, id); upco->setRelevant(p->relevant); upco->setSeen(p->seen); return upco; } return nullptr; } UserAlert::NewShare::NewShare(UserAlertRaw& un, unsigned int id) : Base(un, id) { folderhandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF); } UserAlert::NewShare::NewShare(handle h, handle uh, const string& email, m_time_t timestamp, unsigned int id): Base(name_id::share, uh, email, timestamp, id) { folderhandle = h; } void UserAlert::NewShare::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); if (!email().empty()) { title = "New shared folder from " + email(); // 824 } else { title = "New shared folder"; // 825 } header = email(); } bool UserAlert::NewShare::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializehandle(folderhandle); w.serializeexpansionflags(); return true; } UserAlert::NewShare* UserAlert::NewShare::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } handle h = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializehandle(h) && r.unserializeexpansionflags(expF, 0)) { auto* ns = new NewShare(h, p->userHandle, p->userEmail, p->timestamp, id); ns->setRelevant(p->relevant); ns->setSeen(p->seen); return ns; } return nullptr; } UserAlert::DeletedShare::DeletedShare(UserAlertRaw& un, unsigned int id) : Base(un, id) { ownerHandle = un.gethandle('o', MegaClient::USERHANDLE, UNDEF); folderHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF); } UserAlert::DeletedShare::DeletedShare(handle uh, const string& email, handle ownerhandle, handle folderhandle, m_time_t ts, unsigned int id): Base(name_id::dshare, uh, email, ts, id) { ownerHandle = ownerhandle; folderHandle = folderhandle; } void UserAlert::DeletedShare::updateEmail(MegaClient* mc) { Base::updateEmail(mc); if (std::shared_ptr n = mc->nodebyhandle(folderHandle)) { folderPath = n->displaypath(); folderName = n->displayname(); } } void UserAlert::DeletedShare::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); ostringstream s; if (user() == ownerHandle) { if (!email().empty()) { s << "Access to folders shared by " << email() << " was removed"; // 7879 } else { s << "Access to folders was removed"; // 7880 } } else { if (!email().empty()) { s << "User " << email() << " has left the shared folder " << folderName; //19153 } else { s << "A user has left the shared folder " << folderName; //19154 } } title = s.str(); header = email(); } bool UserAlert::DeletedShare::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializehandle(folderHandle); w.serializestring(folderPath); w.serializestring(folderName); w.serializehandle(ownerHandle); w.serializeexpansionflags(); return true; } UserAlert::DeletedShare* UserAlert::DeletedShare::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } handle h = 0; string fp, fn; handle o = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializehandle(h) && r.unserializestring(fp) && r.unserializestring(fn) && r.unserializehandle(o) && r.unserializeexpansionflags(expF, 0)) { auto* ds = new DeletedShare(p->userHandle, p->userEmail, o, h, p->timestamp, id); ds->folderPath = fp; ds->folderName = fn; ds->setRelevant(p->relevant); ds->setSeen(p->seen); return ds; } return nullptr; } UserAlert::NewSharedNodes::NewSharedNodes(UserAlertRaw& un, unsigned int id) : Base(un, id) { vector f; un.gethandletypearray('f', f); parentHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF); for (size_t n = f.size(); n--; ) { if (f[n].t == FOLDERNODE) { folderNodeHandles.push_back(f[n].h); } else if (f[n].t == FILENODE) { fileNodeHandles.push_back(f[n].h); } // else should not be happening, we can add a sanity check } } UserAlert::NewSharedNodes::NewSharedNodes(handle uh, handle ph, m_time_t timestamp, unsigned int id, vector&& fileHandles, vector&& folderHandles): Base(name_id::put, uh, string(), timestamp, id), parentHandle(ph), fileNodeHandles(std::move(fileHandles)), folderNodeHandles(std::move(folderHandles)) { assert(!ISUNDEF(uh)); } void UserAlert::NewSharedNodes::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); ostringstream notificationText; // Get wording for the number of files and folders added const auto folderCount = folderNodeHandles.size(); const auto fileCount = fileNodeHandles.size(); if ((folderCount > 1) && (fileCount > 1)) { notificationText << folderCount << " folders and " << fileCount << " files"; } else if ((folderCount > 1) && (fileCount == 1)) { notificationText << folderCount << " folders and 1 file"; } else if ((folderCount == 1) && (fileCount > 1)) { notificationText << "1 folder and " << fileCount << " files"; } else if ((folderCount == 1) && (fileCount == 1)) { notificationText << "1 folder and 1 file"; } else if (folderCount > 1) { notificationText << folderCount << " folders"; } else if (fileCount > 1) { notificationText << fileCount << " files"; } else if (folderCount == 1) { notificationText << "1 folder"; } else if (fileCount == 1) { notificationText << "1 file"; } else { notificationText << "nothing"; } // Set wording of the title if (!email().empty()) { title = email() + " added " + notificationText.str(); } else if ((fileCount + folderCount) > 1) { title = notificationText.str() + " have been added"; } else { title = notificationText.str() + " has been added"; } header = email(); } bool UserAlert::NewSharedNodes::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializehandle(parentHandle); w.serializecompressedu64(fileNodeHandles.size()); for (auto& h : fileNodeHandles) { w.serializehandle(h); } w.serializecompressedu64(folderNodeHandles.size()); for (auto& h : folderNodeHandles) { w.serializehandle(h); } w.serializeexpansionflags(); return true; } UserAlert::NewSharedNodes* UserAlert::NewSharedNodes::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } handle ph = 0; CacheableReader r(*d); if (r.unserializehandle(ph)) { uint64_t n = 0; if (r.unserializecompressedu64(n)) { vector vh1(static_cast(n), 0); if (n) { for (auto& h1 : vh1) { if (!r.unserializehandle(h1)) { return nullptr; } } } n = 0; if (r.unserializecompressedu64(n)) { vector vh2(static_cast(n), 0); if (n) { for (auto& h2 : vh2) { if (!r.unserializehandle(h2)) { return nullptr; } } } unsigned char expF[8]; if (!r.unserializeexpansionflags(expF, 0)) { return nullptr; } auto* nsn = new NewSharedNodes(p->userHandle, ph, p->timestamp, id, std::move(vh1), std::move(vh2)); nsn->setRelevant(p->relevant); nsn->setSeen(p->seen); return nsn; } } } return nullptr; } UserAlert::RemovedSharedNode::RemovedSharedNode(UserAlertRaw& un, unsigned int id) : Base(un, id) { vector handlesAndNodeTypes; un.gethandletypearray('f', handlesAndNodeTypes); for (const auto& handleAndType: handlesAndNodeTypes) { nodeHandles.push_back(handleAndType.h); } } UserAlert::RemovedSharedNode::RemovedSharedNode(handle uh, m_time_t timestamp, unsigned int id, vector&& handles): Base(name_id::d, uh, string(), timestamp, id), nodeHandles(std::move(handles)) {} void UserAlert::RemovedSharedNode::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); ostringstream s; const auto itemsNumber = nodeHandles.size(); if (itemsNumber > 1) { s << "Removed " << itemsNumber << " items from a share"; // 8913 } else { s << "Removed item from shared folder"; // 8910 } title = s.str(); header = email(); } bool UserAlert::RemovedSharedNode::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializecompressedu64(nodeHandles.size()); for (auto& h : nodeHandles) { w.serializehandle(h); } w.serializeexpansionflags(); return true; } UserAlert::RemovedSharedNode* UserAlert::RemovedSharedNode::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } uint64_t n = 0; CacheableReader r(*d); if (r.unserializecompressedu64(n)) { vector vh(static_cast(n), 0); if (n) { for (auto& h : vh) { if (!r.unserializehandle(h)) { break; } } } unsigned char expF[8]; if (!r.unserializeexpansionflags(expF, 0)) { return nullptr; } auto* rsn = new RemovedSharedNode(p->userHandle, p->timestamp, id, std::move(vh)); rsn->setRelevant(p->relevant); rsn->setSeen(p->seen); return rsn; } return nullptr; } UserAlert::UpdatedSharedNode::UpdatedSharedNode(UserAlertRaw& un, unsigned int id) : Base(un, id) { vector handlesAndNodeTypes; un.gethandletypearray('f', handlesAndNodeTypes); for (const auto& handleAndType: handlesAndNodeTypes) { nodeHandles.push_back(handleAndType.h); } } UserAlert::UpdatedSharedNode::UpdatedSharedNode(handle uh, m_time_t timestamp, unsigned int id, vector&& handles): Base(name_id::u, uh, string(), timestamp, id), nodeHandles(std::move(handles)) {} void UserAlert::UpdatedSharedNode::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); header = email(); const auto itemsNumber = nodeHandles.size(); const string& itemText = (itemsNumber == 1) ? "" : "s"; title = "Updated " + to_string(itemsNumber) + " item" + itemText + " in shared folder"; } bool UserAlert::UpdatedSharedNode::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializecompressedu64(nodeHandles.size()); for (auto& h : nodeHandles) { w.serializehandle(h); } w.serializeexpansionflags(); return true; } UserAlert::UpdatedSharedNode* UserAlert::UpdatedSharedNode::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } uint64_t n = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializecompressedu64(n)) { vector vh(static_cast(n), 0); if (n) { for (auto& h : vh) { if (!r.unserializehandle(h)) { break; } } } if (!r.unserializeexpansionflags(expF, 0)) { return nullptr; } auto* usn = new UpdatedSharedNode(p->userHandle, p->timestamp, id, std::move(vh)); usn->setRelevant(p->relevant); usn->setSeen(p->seen); return usn; } return nullptr; } string UserAlert::Payment::getProPlanName() { switch (planNumber) { case ACCOUNT_TYPE_PROI: return "Pro I"; // 5819 case ACCOUNT_TYPE_PROII: return "Pro II"; // 6125 case ACCOUNT_TYPE_PROIII: return "Pro III"; // 6126 case ACCOUNT_TYPE_LITE: return "Pro Lite"; // 8413 case ACCOUNT_TYPE_BUSINESS: return "Business"; // 19530 case ACCOUNT_TYPE_PRO_FLEXI: return "Pro Flexi"; case ACCOUNT_TYPE_STARTER: return "Starter"; case ACCOUNT_TYPE_BASIC: return "Basic"; case ACCOUNT_TYPE_ESSENTIAL: return "Essential"; case ACCOUNT_TYPE_FREE: [[fallthrough]]; default: return "Free"; // 435 } } UserAlert::Payment::Payment(UserAlertRaw& un, unsigned int id) : Base(un, id) { success = 's' == un.getnameid('r', 0); planNumber = un.getint('p', 0); } UserAlert::Payment::Payment(bool s, int plan, m_time_t timestamp, unsigned int id, nameid paymentType) : Base(paymentType, UNDEF, "", timestamp, id) { success = s; planNumber = plan; } void UserAlert::Payment::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); ostringstream s; if (success) { s << "Your payment for the " << getProPlanName() << " plan was received."; // 7142 } else { s << "Your payment for the " << getProPlanName() << " plan was unsuccessful."; // 7141 } title = s.str(); header = "Payment info"; // 1230 } bool UserAlert::Payment::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializebool(success); w.serializei32(planNumber); w.serializeexpansionflags(); return true; } UserAlert::Payment* UserAlert::Payment::unserialize(string* d, unsigned id, nameid paymentType) { auto p = Base::unserialize(d); if (!p) { return nullptr; } bool s = false; int plan = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializebool(s) && r.unserializeu32(reinterpret_cast(plan)) && r.unserializeexpansionflags(expF, 0)) { auto* pmt = new Payment(s, plan, p->timestamp, id, paymentType); pmt->setRelevant(p->relevant); pmt->setSeen(p->seen); return pmt; } return nullptr; } UserAlert::PaymentReminder::PaymentReminder(UserAlertRaw& un, unsigned int id) : Base(un, id) { expiryTime = un.getint64(makeNameid("ts"), ts()); } UserAlert::PaymentReminder::PaymentReminder(m_time_t expiryts, unsigned int id): Base(name_id::pses, UNDEF, "", m_time(), id) { expiryTime = expiryts; } UserAlert::PaymentReminder::PaymentReminder(m_time_t creation, m_time_t expiry, unsigned int id): Base(name_id::pses, UNDEF, "", creation, id) { expiryTime = expiry; } void UserAlert::PaymentReminder::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); m_time_t now = m_time(); int days = int((expiryTime - now) / 86400); ostringstream s; if (expiryTime < now) { s << "Your PRO membership plan expired " << -days << (days == -1 ? " day" : " days") << " ago"; } else { s << "Your PRO membership plan will expire in " << days << (days == 1 ? " day." : " days."); // 8596, 8597 } title = s.str(); header = "PRO membership plan expiring soon"; // 8598 } bool UserAlert::PaymentReminder::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializecompressedi64(expiryTime); w.serializeexpansionflags(); return true; } UserAlert::PaymentReminder* UserAlert::PaymentReminder::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } m_time_t exp = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializecompressedi64(exp) && r.unserializeexpansionflags(expF, 0)) { auto* pmr = new PaymentReminder(exp, id); pmr->setRelevant(p->relevant); pmr->setSeen(p->seen); return pmr; } return nullptr; } UserAlert::Takedown::Takedown(UserAlertRaw& un, unsigned int id) : Base(un, id) { int n = un.getint(makeNameid("down"), -1); isTakedown = n == 1; isReinstate = n == 0; nodeHandle = un.gethandle('h', MegaClient::NODEHANDLE, UNDEF); pst.relevant = isTakedown || isReinstate; } UserAlert::Takedown::Takedown(bool down, bool reinstate, int /*t*/, handle nh, m_time_t timestamp, unsigned int id): Base(name_id::ph, UNDEF, "", timestamp, id) { isTakedown = down; isReinstate = reinstate; nodeHandle = nh; pst.relevant = isTakedown || isReinstate; } void UserAlert::Takedown::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); const char* typestring = "node"; string name; std::shared_ptr node = mc->nodebyhandle(nodeHandle); if (node) { if (node->type == FOLDERNODE) { typestring = "folder"; } else if (node->type == FILENODE) { typestring = "file"; } name = node->displaypath(); } if (name.empty()) { char buffer[12]; Base64::btoa((byte*)&(nodeHandle), MegaClient::NODEHANDLE, buffer); name = "handle "; name += buffer; } ostringstream s; if (isTakedown) { header = "Takedown notice"; //8521 s << "Your publicly shared " << typestring << " (" << name << ") has been taken down."; //8522 } else if (isReinstate) { header = "Takedown reinstated"; //8524 s << "Your taken down " << typestring << " (" << name << ") has been reinstated."; // 8523 } title = s.str(); } bool UserAlert::Takedown::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializebool(isTakedown); w.serializebool(isReinstate); w.serializehandle(nodeHandle); w.serializeexpansionflags(); return true; } UserAlert::Takedown* UserAlert::Takedown::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } bool takedown = false; bool reinstate = false; handle h = 0; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializebool(takedown) && r.unserializebool(reinstate) && r.unserializehandle(h) && r.unserializeexpansionflags(expF, 0)) { auto* td = new Takedown(takedown, reinstate, 0, h, p->timestamp, id); td->setRelevant(p->relevant); td->setSeen(p->seen); return td; } return nullptr; } UserAlert::SetTakedown::SetTakedown(UserAlertRaw& un, unsigned int id): Base(un, id) { int n = un.getint(MAKENAMEID2('t', 'd'), -1); isTakedown = n == 1; isReinstate = n == 0; reason = PublicLinkSet::apiCodeToDeletionReason(un.getint(MAKENAMEID1('c'), 0)); setId = un.gethandle('s', MegaClient::SETHANDLE, UNDEF); pst.relevant = isTakedown || isReinstate; } UserAlert::SetTakedown::SetTakedown(bool down, bool reinstate, PublicLinkSet::LinkDeletionReason downReason, handle sId, m_time_t timestamp, unsigned int id): Base(name_id::ass, UNDEF, "", timestamp, id), isTakedown{down}, isReinstate{reinstate}, reason{downReason}, setId{sId} { pst.relevant = isTakedown || isReinstate; } void UserAlert::SetTakedown::text(string& header, string& title, MegaClient* mc) { updateEmail(mc); const std::string name = std::invoke( [this, &mc]() -> std::string { const Set* set = mc->getSet(setId); if (set) { return set->name(); } char buffer[12]; Base64::btoa((byte*)&(setId), MegaClient::SETHANDLE, buffer); return std::string("set id ") + buffer; }); ostringstream s; if (isTakedown) { header = "Takedown notice"; s << "Your publicly shared set (" << name << ") has been taken down due to reason: " << PublicLinkSet::LinkDeletionReasonToString(reason) << "."; } else if (isReinstate) { header = "Takedown reinstated"; s << "Your taken down set (" << name << ") has been reinstated."; } title = s.str(); } bool UserAlert::SetTakedown::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializebool(isTakedown); w.serializebool(isReinstate); w.serializeu8(static_cast(reason)); w.serializehandle(setId); w.serializeexpansionflags(); return true; } UserAlert::SetTakedown* UserAlert::SetTakedown::unserialize(string* d, unsigned id) { auto p = Base::unserialize(d); if (!p) { return nullptr; } bool takedown = false; bool reinstate = false; uint8_t reasonId = 0; handle setId = UNDEF; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializebool(takedown) && r.unserializebool(reinstate) && r.unserializeu8(reasonId) && r.unserializehandle(setId) && r.unserializeexpansionflags(expF, 0)) { auto* td = new SetTakedown(takedown, reinstate, static_cast(reasonId), setId, p->timestamp, id); td->setRelevant(p->relevant); td->setSeen(p->seen); return td; } return nullptr; } #ifdef ENABLE_CHAT UserAlert::ScheduledMeetingAlert::ScheduledMeetingAlert(UserAlertRaw& un, unsigned int id): Base(un, id) { mChatid = un.gethandle(makeNameid("cid"), MegaClient::CHATHANDLE, UNDEF); if (mChatid == UNDEF) { LOG_err << "ScheduledMeetingAlert user alert ctor: invalid chatid"; assert(false); return; } mSchedMeetingHandle = un.gethandle(makeNameid("id"), MegaClient::CHATHANDLE, UNDEF); if (mSchedMeetingHandle == UNDEF) { LOG_err << "ScheduledMeetingAlert user alert ctor: invalid scheduled meeting id"; assert(false); return; } // optional param parent scheduled meeting id (just for child scheduled meetings) mParentSchedId = un.gethandle(makeNameid("p"), MegaClient::USERHANDLE, UNDEF); // optional param start date time (just for child scheduled meetings) mStartDateTime = un.getint64(makeNameid("o"), mega_invalid_timestamp); } std::ostream& UserAlert::operator<<(std::ostream& oss, const UserAlert::ScheduledMeetingAlert& sma) { oss << "\n\tChatid : " << toHandle(sma.mChatid) << "\n\tSched Meeting Id: " << toHandle(sma.mSchedMeetingHandle) << "\n\tParent Sched Meeting Id: " << toHandle(sma.mParentSchedId) << "\n\tMeeting start date time (overrides): " << sma.mStartDateTime; return oss; } string* UserAlert::ScheduledMeetingAlert::serializeScheduledMeeting(const userAlertsSubtype& subType, string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializeu8(subType); w.serializehandle(mChatid); w.serializehandle(mSchedMeetingHandle); w.serializehandle(mParentSchedId); w.serializei64(mStartDateTime); return d; } UserAlert::Base* UserAlert::ScheduledMeetingAlert::unserializeScheduledMeeting(string* d, unsigned id) { CacheableReader r(*d); auto b = Base::readBase(r); if (!b) { LOG_err << "unserializeScheduledMeeting: can't read UserAlert::Base"; assert(false); return nullptr; } uint8_t subType = subtype_invalid; if (!r.unserializeu8(subType)) { LOG_err << "unserializeScheduledMeeting: ill-formed mcsmp user alert (sub-type is not present)"; assert(false); return nullptr; } // common fields for both New and Update handle chatid = UNDEF; handle sm = UNDEF; handle parentSchedId = UNDEF; m_time_t overrides = mega_invalid_timestamp; unsigned char expF[8]; if (!r.unserializehandle(chatid) || !r.unserializehandle(sm) || !r.unserializehandle(parentSchedId) || !r.unserializei64(overrides)) { LOG_err << "unserialize failed"; return nullptr; } switch (subType) { case subtype_new_Sched: { if (r.unserializeexpansionflags(expF, 0)) { auto* nsm = new NewScheduledMeeting(b->userHandle, b->timestamp, id, chatid, sm, parentSchedId, overrides); nsm->setSeen(b->seen); nsm->setRelevant(b->relevant); return nsm; } else { LOG_err << "unserialize failed"; } break; } case subtype_upd_Sched: { uint64_t changes = 0; if (r.unserializeu64(changes)) { unique_ptr tcs; if (changes & UpdatedScheduledMeeting::Changeset::CHANGE_TYPE_TITLE) { string oldTitle, newTitle; if (r.unserializestring(oldTitle) && r.unserializestring(newTitle)) { tcs.reset(new UpdatedScheduledMeeting::Changeset::StrChangeset{oldTitle, newTitle}); } } unique_ptr tzcs; if (changes & UpdatedScheduledMeeting::Changeset::CHANGE_TYPE_TIMEZONE) { string oldTz, newTz; if (r.unserializestring(oldTz) && r.unserializestring(newTz)) { tzcs.reset( new UpdatedScheduledMeeting::Changeset::StrChangeset{oldTz, newTz}); } } unique_ptr sdcs; if (changes & UpdatedScheduledMeeting::Changeset::CHANGE_TYPE_STARTDATE) { m_time_t oldsd, newsd; if (r.unserializei64(oldsd) && r.unserializei64(newsd)) { sdcs.reset( new UpdatedScheduledMeeting::Changeset::TsChangeset{oldsd, newsd}); } } unique_ptr edcs; if (changes & UpdatedScheduledMeeting::Changeset::CHANGE_TYPE_ENDDATE) { m_time_t olded, newed; if (r.unserializei64(olded) && r.unserializei64(newed)) { edcs.reset( new UpdatedScheduledMeeting::Changeset::TsChangeset{olded, newed}); } } if (r.unserializeexpansionflags(expF, 0)) { auto* usm = new UpdatedScheduledMeeting(b->userHandle, b->timestamp, id, chatid, sm, parentSchedId, overrides, {changes, tcs, tzcs, sdcs, edcs}); usm->setRelevant(b->relevant); usm->setSeen(b->seen); return usm; } else { LOG_err << "unserializeexpansionflags failed"; } } else { LOG_err << "unserialize changes failed"; } break; } default: { LOG_err << "unserializeScheduledMeeting: invalid mcsmp user alert sub-type " << subType; assert(false); return nullptr; } } return nullptr; } bool UserAlert::NewScheduledMeeting::serialize(string* d) const { CacheableWriter{*serializeScheduledMeeting(subtype_new_Sched, d)}.serializeexpansionflags(); return true; } void UserAlert::NewScheduledMeeting::text(string& header, string& title, MegaClient* mc) { Base::updateEmail(mc); ostringstream oss; oss << "New Scheduled Meeting details:" << *this << "\n\tCreated by: " << pst.userEmail; header = "New Scheduled Meeting"; title = oss.str(); LOG_debug << title; } UserAlert::DeletedScheduledMeeting::DeletedScheduledMeeting(UserAlertRaw& un, unsigned int id) : Base(un, id) { mChatid = un.gethandle(makeNameid("cid"), MegaClient::CHATHANDLE, UNDEF); mSchedMeetingHandle = un.gethandle(makeNameid("id"), MegaClient::CHATHANDLE, UNDEF); if (mChatid == UNDEF) { assert(false); LOG_err << "DeletedScheduledMeeting user alert ctor: invalid scheduled chatid"; return; } if (mSchedMeetingHandle == UNDEF) { assert(false); LOG_err << "DeletedScheduledMeeting user alert ctor: invalid scheduled meeting id"; return; } } void UserAlert::DeletedScheduledMeeting::text(string& header, string& title, MegaClient* mc) { Base::updateEmail(mc); ostringstream oss; oss << "Deleted Scheduled Meeting details:" << "\n\tChatid: " << toHandle(mChatid) << "\n\tSched Meeting Id: " << toHandle(mSchedMeetingHandle) << "\n\tDeleted by: " << pst.userEmail; header = "Deleted Scheduled Meeting"; title = oss.str(); LOG_debug << title; } bool UserAlert::DeletedScheduledMeeting::serialize(string* d) const { Base::serialize(d); CacheableWriter w(*d); w.serializehandle(mChatid); w.serializehandle(mSchedMeetingHandle); w.serializeexpansionflags(); return true; } UserAlert::DeletedScheduledMeeting* UserAlert::DeletedScheduledMeeting::unserialize(string* d, unsigned id) { auto b = Base::unserialize(d); if (!b) return nullptr; handle chatid = UNDEF; handle sm = UNDEF; unsigned char expF[8]; CacheableReader r(*d); if (r.unserializehandle(chatid) && r.unserializehandle(sm) && r.unserializeexpansionflags(expF, 0)) { auto* dsm = new DeletedScheduledMeeting(b->userHandle, b->timestamp, id, chatid, sm); dsm->setSeen(b->seen); dsm->setRelevant(b->relevant); return dsm; } return nullptr; } UserAlert::UpdatedScheduledMeeting::UpdatedScheduledMeeting(UserAlertRaw& un, unsigned int id): ScheduledMeetingAlert(un, id) { JSON auxJson = un.field(makeNameid("cs")); if (auxJson.pos) { if (auxJson.enterobject()) { if (MegaClient::parseScheduledMeetingChangeset(&auxJson, &mUpdatedChangeset) != API_OK) { LOG_err << "UpdatedScheduledMeeting user alert ctor: error parsing cs array"; assert(false); return; } auxJson.leaveobject(); } else { assert(false); LOG_err << "UpdatedScheduledMeeting user alert ctor: Ill-formed user alert"; } } } void UserAlert::UpdatedScheduledMeeting::text(string& header, string& title, MegaClient* mc) { Base::updateEmail(mc); ostringstream oss; oss << "Updated Scheduled Meeting details:" << *this << "\n\tUpdated by: " << pst.userEmail; for (size_t changeBitPos = 0; changeBitPos < Changeset::CHANGE_TYPE_SIZE; ++changeBitPos) { uint64_t changeType = 1ull << changeBitPos; if (!mUpdatedChangeset.hasChanged(changeType)) continue; oss << "\n\t\t" << mUpdatedChangeset.changeToString(changeType) << " updated"; if (changeType == Changeset::CHANGE_TYPE_TITLE && mUpdatedChangeset.getUpdatedTitle()) { const auto& titleCS = mUpdatedChangeset.getUpdatedTitle(); oss << ": previous title |" << Base64::atob(titleCS->oldValue) << "| new title |" << Base64::atob(titleCS->newValue) << "|"; } if (changeType == Changeset::CHANGE_TYPE_TIMEZONE && mUpdatedChangeset.getUpdatedTimeZone()) { const auto& tzCS = mUpdatedChangeset.getUpdatedTimeZone(); oss << ": previous timezone |" << Base64::atob(tzCS->oldValue) << "| new timezone |" << Base64::atob(tzCS->newValue) << "|"; } if (changeType == Changeset::CHANGE_TYPE_STARTDATE && mUpdatedChangeset.getUpdatedStartDateTime()) { const auto& sdCS = mUpdatedChangeset.getUpdatedStartDateTime(); oss << ": previous startDateTime |" << sdCS->oldValue << "| new startDateTime |" << sdCS->newValue << "|"; } if (changeType == Changeset::CHANGE_TYPE_ENDDATE && mUpdatedChangeset.getUpdatedEndDateTime()) { const auto& edCS = mUpdatedChangeset.getUpdatedEndDateTime(); oss << ": previous endDateTime |" << edCS->oldValue << "| new endDateTime |" << edCS->newValue << "|"; } }; header = "Updated Scheduled Meeting"; title = oss.str(); LOG_debug << title; } bool UserAlert::UpdatedScheduledMeeting::serialize(string* d) const { CacheableWriter w(*serializeScheduledMeeting(subtype_upd_Sched, d)); w.serializeu64(static_cast(mUpdatedChangeset.getChanges())); if (mUpdatedChangeset.hasChanged(Changeset::CHANGE_TYPE_TITLE) && mUpdatedChangeset.getUpdatedTitle()) { const auto& titleCS = mUpdatedChangeset.getUpdatedTitle(); w.serializestring(titleCS->oldValue); w.serializestring(titleCS->newValue); } if (mUpdatedChangeset.hasChanged(Changeset::CHANGE_TYPE_TIMEZONE) && mUpdatedChangeset.getUpdatedTimeZone()) { const auto& tzCS = mUpdatedChangeset.getUpdatedTimeZone(); w.serializestring(tzCS->oldValue); w.serializestring(tzCS->newValue); } if (mUpdatedChangeset.hasChanged(Changeset::CHANGE_TYPE_STARTDATE) && mUpdatedChangeset.getUpdatedStartDateTime()) { const auto& sdCS = mUpdatedChangeset.getUpdatedStartDateTime(); w.serializei64(sdCS->oldValue); w.serializei64(sdCS->newValue); } if (mUpdatedChangeset.hasChanged(Changeset::CHANGE_TYPE_ENDDATE) && mUpdatedChangeset.getUpdatedEndDateTime()) { const auto& edCS = mUpdatedChangeset.getUpdatedEndDateTime(); w.serializei64(edCS->oldValue); w.serializei64(edCS->newValue); } w.serializeexpansionflags(); return true; } UserAlert::UpdatedScheduledMeeting::Changeset::Changeset(const std::bitset& _bs, unique_ptr& _titleCS, unique_ptr& _tzCS, unique_ptr& _sdCS, unique_ptr& _edCS) : mUpdatedFields(_bs), mUpdatedTitle(std::move(_titleCS)), mUpdatedTimeZone(std::move(_tzCS)), mUpdatedStartDateTime(std::move(_sdCS)), mUpdatedEndDateTime(std::move(_edCS)) { if (!invariant()) { LOG_err << "ScheduledMeetings: Ill-formed Changeset construction"; assert(false); } } string UserAlert::UpdatedScheduledMeeting::Changeset::changeToString(uint64_t changeType) const { switch (changeType) { case CHANGE_TYPE_TITLE: return "Title"; case CHANGE_TYPE_DESCRIPTION: return "Description"; case CHANGE_TYPE_CANCELLED: return "Cancelled"; case CHANGE_TYPE_TIMEZONE: return "TimeZone"; case CHANGE_TYPE_STARTDATE: return "StartDate"; case CHANGE_TYPE_ENDDATE: return "EndDate"; case CHANGE_TYPE_RULES: return "Rules"; default: return "Unexpected Field"; } } void UserAlert::UpdatedScheduledMeeting::Changeset::addChange(uint64_t changeType, UpdatedScheduledMeeting::Changeset::StrChangeset* sSet, UpdatedScheduledMeeting::Changeset::TsChangeset* tSet) { mUpdatedFields |= changeType; switch (changeType) { case CHANGE_TYPE_TITLE: if (sSet) { mUpdatedTitle.reset(new StrChangeset{sSet->oldValue, sSet->newValue}); } break; case CHANGE_TYPE_TIMEZONE: if (sSet) { mUpdatedTimeZone.reset(new StrChangeset{sSet->oldValue, sSet->newValue}); } break; case CHANGE_TYPE_STARTDATE: if (tSet) { mUpdatedStartDateTime.reset(new TsChangeset{tSet->oldValue, tSet->newValue}); } break; case CHANGE_TYPE_ENDDATE: if (tSet) { mUpdatedEndDateTime.reset(new TsChangeset{tSet->oldValue, tSet->newValue}); } break; case CHANGE_TYPE_DESCRIPTION: case CHANGE_TYPE_CANCELLED: case CHANGE_TYPE_RULES: break; default: mUpdatedFields &= ~changeType; break; } if (!invariant()) { LOG_err << "ScheduledMeetings: Ill-formed update changeset received"; assert(false); } } #endif UserAlerts::UserAlerts(MegaClient& cmc) : mc(cmc) , nextid(0) , begincatchup(false) , catchupdone(false) , catchup_last_timestamp(0) , lsn(UNDEF) , fsn(UNDEF) , lastTimeDelta(0) , provisionalmode(false) , notingSharedNodes(false) , ignoreNodesUnderShare(UNDEF) { } unsigned int UserAlerts::nextId() { return ++nextid; } void UserAlerts::ff::squash(const ff &rhs) { areNodeVersions(rhs.areNodeVersions()); std::for_each(std::begin(rhs.alertTypePerFileNode), std::end(rhs.alertTypePerFileNode), [this](const std::pair& p) { alertTypePerFileNode[p.first] = p.second; }); std::for_each(std::begin(rhs.alertTypePerFolderNode), std::end(rhs.alertTypePerFolderNode), [this](const std::pair& p) { alertTypePerFolderNode[p.first] = p.second; }); } bool UserAlerts::isUnwantedAlert(nameid type, int action) { using namespace UserAlert; if (type == name_id::put || type == name_id::share || type == name_id::dshare) { if (!flags.cloud_enabled) { return true; } } else if (type == name_id::c || type == name_id::ipc || type == name_id::upci || type == name_id::upco) { if (!flags.contacts_enabled) { return true; } } if (type == name_id::put) { return !flags.cloud_newfiles; } else if (type == name_id::share) { return !flags.cloud_newshare; } else if (type == name_id::dshare) { return !flags.cloud_delshare; } else if (type == name_id::ipc) { return !flags.contacts_fcrin; } else if (type == name_id::c) { return (action == -1 || action == 0) && !flags.contacts_fcrdel; } else if (type == name_id::upco) { return (action == -1 || action == 2) && !flags.contacts_fcracpt; } return false; } void UserAlerts::add(UserAlertRaw& un) { using namespace UserAlert; namespace u = UserAlert; Base* unb = NULL; switch (un.t) { case name_id::ipc: unb = new IncomingPendingContact(un, nextId()); break; case name_id::c: unb = new ContactChange(un, nextId()); break; case name_id::upci: unb = new UpdatedPendingContactIncoming(un, nextId()); break; case name_id::upco: unb = new UpdatedPendingContactOutgoing(un, nextId()); break; case name_id::share: unb = new u::NewShare(un, nextId()); break; case name_id::dshare: unb = new DeletedShare(un, nextId()); break; case name_id::put: unb = new NewSharedNodes(un, nextId()); break; case name_id::d: unb = new RemovedSharedNode(un, nextId()); break; case name_id::u: unb = new UpdatedSharedNode(un, nextId()); break; case name_id::psts: case name_id::psts_v2: unb = new Payment(un, nextId()); break; case name_id::pses: unb = new PaymentReminder(un, nextId()); break; case name_id::ph: unb = new Takedown(un, nextId()); break; case name_id::ass: unb = new SetTakedown(un, nextId()); break; #ifdef ENABLE_CHAT case name_id::mcsmp: { if (!un.has(makeNameid("cs"))) // if cs is not present, is a new scheduled meeting { unb = new NewScheduledMeeting(un, nextId()); } else { unb = new UpdatedScheduledMeeting(un, nextId()); } } break; case name_id::mcsmr: unb = new DeletedScheduledMeeting(un, nextId()); break; #endif default: unb = NULL; // If it's a notification type we do not recognise yet } if (unb) { add(unb); } } UserAlert::Base* UserAlerts::findAlertToCombineWith(const UserAlert::Base* a, nameid t) const { if (a->type == t) { auto ait = std::find_if(alerts.rbegin(), alerts.rend(), [](UserAlert::Base* b) { return !b->removed(); }); return ait != alerts.rend() && (*ait)->type == t ? *ait : nullptr; } return nullptr; } void UserAlerts::add(UserAlert::Base* unb) { // Alerts received by this function should be persisted when coming from sc50 and action packets, // but not when being just loaded from persistent db. // unb is either directly from notification json, or constructed from actionpacket. // We take ownership. if (unb && unb->type == name_id::pses) { auto* pmr = static_cast(unb); if (shouldDropStalePaymentReminder(mc, *pmr)) { if (unb->dbid) { unb->setRelevant(false); unb->setRemoved(); mc.persistAlert(unb); } delete unb; return; } } if (provisionalmode) { provisionals.push_back(unb); return; } if (!catchupdone && unb->ts() > catchup_last_timestamp) { catchup_last_timestamp = unb->ts(); } else if (catchupdone && unb->ts() < catchup_last_timestamp) { // this is probably a duplicate from the initial set, generated from normal sc packets LOG_warn << "discarding duplicate user alert of type " << unb->type; delete unb; return; } // attempt to combine with previous NewSharedNodes UserAlert::Base* cmb = findAlertToCombineWith(unb, name_id::put); if (cmb) { // If it's file/folders added, and the prior one is for the same user and within 5 mins then we can combine instead UserAlert::NewSharedNodes* np = dynamic_cast(unb); UserAlert::NewSharedNodes* op = dynamic_cast(cmb); if (np && op) { if (np->user() == op->user() && np->ts() - op->ts() < 300 && np->parentHandle == op->parentHandle && !ISUNDEF(np->parentHandle)) { op->fileNodeHandles.insert(end(op->fileNodeHandles), begin(np->fileNodeHandles), end(np->fileNodeHandles)); op->folderNodeHandles.insert(end(op->folderNodeHandles), begin(np->folderNodeHandles), end(np->folderNodeHandles)); LOG_debug << "Merged user alert, type " << np->type << " ts " << np->ts(); notifyAlert(op, false, 0); delete unb; return; } } } // attempt to combine with previous RemovedSharedNode cmb = findAlertToCombineWith(unb, name_id::d); if (cmb) { // If it's file/folders removed, and the prior one is for the same user and within 5 mins then we can combine instead UserAlert::RemovedSharedNode* nd = dynamic_cast(unb); UserAlert::RemovedSharedNode* od = dynamic_cast(cmb); if (nd && od) { if (nd->user() == od->user() && nd->ts() - od->ts() < 300) { od->nodeHandles.insert(end(od->nodeHandles), begin(nd->nodeHandles), end(nd->nodeHandles)); LOG_debug << "Merged user alert, type " << nd->type << " ts " << nd->ts(); notifyAlert(od, false, 0); delete unb; return; } } } // attempt to combine with previous UpdatedSharedNode cmb = findAlertToCombineWith(unb, name_id::u); if (cmb) { // If it's file/folders updated, and the prior one is for the same user and within 5 mins then we can combine instead UserAlert::UpdatedSharedNode* nd = dynamic_cast(unb); UserAlert::UpdatedSharedNode* od = dynamic_cast(cmb); if (nd && od) { if (nd->user() == od->user() && nd->ts() - od->ts() < 300) { od->nodeHandles.insert(end(od->nodeHandles), begin(nd->nodeHandles), end(nd->nodeHandles)); LOG_debug << "Merged user alert, type " << nd->type << " ts " << nd->ts(); notifyAlert(od, false, 0); delete unb; return; } } } // check for previous Payment-Reminder-s to ignore if (!alerts.empty() && (unb->type == name_id::psts || unb->type == name_id::psts_v2) && static_cast(unb)->success) { // if a successful payment is made then hide/remove any reminders received for (auto& a: alerts) { if (a->type == name_id::pses && a->ts() <= unb->ts()) { a->setRelevant(false); a->setRemoved(); // add it into useralertnotify for purgescalerts to process later // so it can be actually removed notifyAlert(a, a->seen(), 0); } } } // check for previous Payment that would invalidate a Payment-Reminder else if (!alerts.empty() && unb->type == name_id::pses) { // if a successful payment was made later, then ignore this reminder for (auto& a: alerts) { if ((a->type == name_id::psts || a->type == name_id::psts_v2) && static_cast(a)->success && a->ts() >= unb->ts()) { delete unb; return; } } } unb->updateEmail(&mc); alerts.push_back(unb); LOG_debug << "Added user alert, type " << alerts.back()->type << " ts " << alerts.back()->ts(); notifyAlert(unb, unb->seen(), 0); // do not touch seen here, but tag } void UserAlerts::purgeStalePaymentReminders() { for (auto* alert: alerts) { if (!alert || alert->removed() || alert->type != name_id::pses) { continue; } auto* pmr = static_cast(alert); if (!shouldDropStalePaymentReminder(mc, *pmr)) { continue; } alert->setRelevant(false); alert->setRemoved(); notifyAlert(alert, alert->seen(), 0); } } void UserAlerts::startprovisional() { provisionalmode = true; } void UserAlerts::evalprovisional(handle originatinguser) { provisionalmode = false; for (unsigned i = 0; i < provisionals.size(); ++i) { if (provisionals[i]->checkprovisional(originatinguser, &mc)) { add(provisionals[i]); } else { delete provisionals[i]; } } provisionals.clear(); } void UserAlerts::beginNotingSharedNodes() { notingSharedNodes = true; notedSharedNodes.clear(); } void UserAlerts::noteSharedNode(handle user, int type, m_time_t ts, Node* n, nameid alertType) { if (catchupdone && notingSharedNodes && (type == FILENODE || type == FOLDERNODE)) { assert(!ISUNDEF(user)); if (!ISUNDEF(ignoreNodesUnderShare) && (alertType != name_id::d)) { // don't make alerts on files/folders already in the new share for (Node* p = n; p != NULL; p = p->parent.get()) { if (p->nodehandle == ignoreNodesUnderShare) return; } } ff& f = notedSharedNodes[make_pair(user, n ? n->parenthandle : UNDEF)]; if (n && type == FOLDERNODE) { f.alertTypePerFolderNode[n->nodehandle] = alertType; } else if (n && type == FILENODE) { f.alertTypePerFileNode[n->nodehandle] = alertType; f.areNodeVersions(n && n->parent && n->parent->type == FILENODE); } // there shouldn't be any other types if (!f.timestamp || (ts && ts < f.timestamp)) { f.timestamp = ts; } } } bool UserAlerts::isConvertReadyToAdd(handle originatingUser) const { return catchupdone && notingSharedNodes && (originatingUser != mc.me); } void UserAlerts::convertNotedSharedNodes(bool added) { using namespace UserAlert; for (notedShNodesMap::iterator i = notedSharedNodes.begin(); i != notedSharedNodes.end(); ++i) { auto&& fileHandles = i->second.fileHandles(); auto&& folderHandles = i->second.folderHandles(); if (added) { add(new NewSharedNodes(i->first.first, i->first.second, i->second.timestamp, nextId(), std::move(fileHandles), std::move(folderHandles))); } else { std::move(folderHandles.begin(), folderHandles.end(), std::back_inserter(fileHandles)); add(new RemovedSharedNode(i->first.first, m_time(), nextId(), std::move(fileHandles))); } } } void UserAlerts::clearNotedSharedMembers() { notedSharedNodes.clear(); notingSharedNodes = false; ignoreNodesUnderShare = UNDEF; } // make a notification out of the shared nodes noted void UserAlerts::convertNotedSharedNodes(bool added, handle originatingUser) { if (isConvertReadyToAdd(originatingUser)) { convertNotedSharedNodes(added); } clearNotedSharedMembers(); } void UserAlerts::ignoreNextSharedNodesUnder(handle h) { ignoreNodesUnderShare = h; } pair UserAlerts::findNotedSharedNodeIn(handle nodeHandle, const notedShNodesMap& notedSharedNodesMap) const { auto it = find_if(begin(notedSharedNodesMap), end(notedSharedNodesMap), [nodeHandle](const pair, ff>& element) { const auto& fileAlertTypes = element.second.alertTypePerFileNode; const auto& folderAlertTypes = element.second.alertTypePerFolderNode; return (std::distance(fileAlertTypes.find(nodeHandle), end(fileAlertTypes)) || std::distance(folderAlertTypes.find(nodeHandle), end(folderAlertTypes))); }); return make_pair(it != end(notedSharedNodesMap), std::distance(begin(notedSharedNodesMap), it)); } bool UserAlerts::containsRemovedNodeAlert(handle nh, const UserAlert::Base* a) const { const UserAlert::RemovedSharedNode* delNodeAlert = dynamic_cast(a); if (!delNodeAlert) return false; return (find(begin(delNodeAlert->nodeHandles), end(delNodeAlert->nodeHandles), nh) != end(delNodeAlert->nodeHandles)); } UserAlert::NewSharedNodes* UserAlerts::eraseNodeHandleFromNewShareNodeAlert(handle nh, UserAlert::Base* a) { UserAlert::NewSharedNodes* nsna = dynamic_cast(a); if (nsna) { auto it = find(begin(nsna->fileNodeHandles), end(nsna->fileNodeHandles), nh); if (it != end(nsna->fileNodeHandles)) { nsna->fileNodeHandles.erase(it); return nsna; } // no need to check nsna->folderNodeHandles since folders do not support versioning } return nullptr; } UserAlert::RemovedSharedNode* UserAlerts::eraseNodeHandleFromRemovedSharedNode(handle nh, UserAlert::Base* a) { UserAlert::RemovedSharedNode* rsna = dynamic_cast(a); if (rsna) { auto it = find(begin(rsna->nodeHandles), end(rsna->nodeHandles), nh); if (it != end(rsna->nodeHandles)) { rsna->nodeHandles.erase(it); return rsna; } } return nullptr; } bool UserAlerts::isSharedNodeNotedAsRemoved(handle nodeHandleToFind) const { // check first in the stash return isSharedNodeNotedAsRemovedFrom(nodeHandleToFind, deletedSharedNodesStash) || isSharedNodeNotedAsRemovedFrom(nodeHandleToFind, notedSharedNodes); } bool UserAlerts::isSharedNodeNotedAsRemovedFrom(handle nodeHandleToFind, const notedShNodesMap& notedSharedNodesMap) const { using handletoalert_t = UserAlert::handle_alerttype_map_t; if (catchupdone && notingSharedNodes) { auto itToNotedSharedNodes = find_if(begin(notedSharedNodesMap), end(notedSharedNodesMap), [nodeHandleToFind](const pair, ff>& element) { const handletoalert_t& fileAlertTypes = element.second.alertTypePerFileNode; auto itToFileNodeHandleAndAlertType = fileAlertTypes.find(nodeHandleToFind); const handletoalert_t& folderAlertTypes = element.second.alertTypePerFolderNode; auto itToFolderNodeHandleAndAlertType = folderAlertTypes.find(nodeHandleToFind); bool isInFileNodes = ((itToFileNodeHandleAndAlertType != end(fileAlertTypes)) && (itToFileNodeHandleAndAlertType->second == name_id::d)); // shortcircuit in case it was already found bool isInFolderNodes = isInFileNodes || ((itToFolderNodeHandleAndAlertType != end(folderAlertTypes)) && (itToFolderNodeHandleAndAlertType->second == name_id::d)); return (isInFileNodes || isInFolderNodes); }); return itToNotedSharedNodes != end(notedSharedNodesMap); } return false; } bool UserAlerts::removeNotedSharedNodeFrom(notedShNodesMap::iterator itToStashedNodeToRemove, Node* nodeToRemove, notedShNodesMap& notedSharedNodesMap) { if (itToStashedNodeToRemove != end(notedSharedNodesMap)) { ff& f = itToStashedNodeToRemove->second; if (nodeToRemove->type == FOLDERNODE) { f.alertTypePerFolderNode.erase(nodeToRemove->nodehandle); } else if (nodeToRemove->type == FILENODE) { f.alertTypePerFileNode.erase(nodeToRemove->nodehandle); } // there shouldn't be any other type if (f.alertTypePerFolderNode.empty() && f.alertTypePerFileNode.empty()) { notedSharedNodesMap.erase(itToStashedNodeToRemove); } return true; } return false; } bool UserAlerts::removeNotedSharedNodeFrom(Node* n, notedShNodesMap& notedSharedNodesMap) { if (catchupdone && notingSharedNodes) { auto found = findNotedSharedNodeIn(n->nodehandle, notedSharedNodesMap); if (found.first) { auto it = notedSharedNodesMap.begin(); std::advance(it, found.second); return removeNotedSharedNodeFrom(it, n, notedSharedNodesMap); } } return false; } bool UserAlerts::setNotedSharedNodeToUpdate(Node* nodeToChange) { // noted nodes stash contains only deleted noted nodes, thus, we only check noted nodes map if (catchupdone && notingSharedNodes && !notedSharedNodes.empty()) { auto found = findNotedSharedNodeIn(nodeToChange->nodehandle, notedSharedNodes); if (!found.first) return false; auto itToNotedSharedNodes = notedSharedNodes.begin(); std::advance(itToNotedSharedNodes, found.second); if (itToNotedSharedNodes == end(notedSharedNodes)) return false; add(new UserAlert::UpdatedSharedNode(itToNotedSharedNodes->first.first, itToNotedSharedNodes->second.timestamp, nextId(), {nodeToChange->nodehandle})); if (removeNotedSharedNodeFrom(itToNotedSharedNodes, nodeToChange, notedSharedNodes)) { LOG_debug << "Node with node handle |" << nodeToChange->nodehandle << "| removed from annotated node add-alerts and update-alert created in its place"; } return true; } return false; } bool UserAlerts::isHandleInAlertsAsRemoved(handle nodeHandleToFind) const { std::function isAlertWithTypeRemoved = [nodeHandleToFind, this](UserAlert::Base* alertToCheck) { return !alertToCheck->removed() && containsRemovedNodeAlert(nodeHandleToFind, alertToCheck); }; std::string debug_msg = "Found removal-alert with nodehandle |" + std::to_string(nodeHandleToFind) + "| in "; // check in existing alerts if (find_if(begin(alerts), end(alerts), isAlertWithTypeRemoved) != end(alerts)) { LOG_debug << debug_msg << "alerts"; return true; } // check in existing notifications meant to become alerts if (find_if(begin(useralertnotify), end(useralertnotify), isAlertWithTypeRemoved) != end(useralertnotify)) { LOG_debug << debug_msg << "useralertnotify"; return true; } // check in annotated changes pending to become notifications to become alerts if (isSharedNodeNotedAsRemoved(nodeHandleToFind)) { LOG_debug << debug_msg << "stash or noted nodes"; return true; } return false; } void UserAlerts::removeNodeAlerts(Node* nodeToRemoveAlert) { if (!nodeToRemoveAlert) { LOG_err << "Unable to remove alerts for node. Empty Node* passed."; return; } // Remove nodehandle for NewShareNodes and/or RemovedSharedNodes, releasing the alert if gets empty handle nodeHandleToRemove = nodeToRemoveAlert->nodehandle; std::string debug_msg = "Suppressed alert for node with handle |" + toNodeHandle(nodeHandleToRemove) + "| found as a "; for (UserAlert::Base* alertToCheck : alerts) { if (auto pNewSN = eraseNodeHandleFromNewShareNodeAlert(nodeHandleToRemove, alertToCheck)) { LOG_debug << debug_msg << "new-alert type"; if (pNewSN->fileNodeHandles.empty() && pNewSN->folderNodeHandles.empty()) { pNewSN->setRemoved(); } notifyAlert(pNewSN, pNewSN->seen(), pNewSN->tag); } else if (auto pRemovedSN = eraseNodeHandleFromRemovedSharedNode(nodeHandleToRemove, alertToCheck)) { LOG_debug << debug_msg << "removal-alert type"; if (pRemovedSN->nodeHandles.empty()) { pRemovedSN->setRemoved(); } notifyAlert(pRemovedSN, pRemovedSN->seen(), pRemovedSN->tag); // do not touch seen or tag // was updated, so persist it } } // remove from annotated changes pending to become notifications to become alerts if (removeNotedSharedNodeFrom(nodeToRemoveAlert, deletedSharedNodesStash)) { LOG_debug << debug_msg << "removal-alert in the stash"; } if (removeNotedSharedNodeFrom(nodeToRemoveAlert, notedSharedNodes)) { LOG_debug << debug_msg << "new-alert in noted nodes"; } } void UserAlerts::setNewNodeAlertToUpdateNodeAlert(Node* nodeToUpdate) { if (!nodeToUpdate) { LOG_err << "Unable to set alert new-alert node to update-alert. Empty node* passed"; return; } // Remove nodehandle for NewShareNodes that are an update; if the alert is empty after the // removal, it must be released handle nodeHandleToUpdate = nodeToUpdate->nodehandle; std::string debug_msg = "New-alert replaced by update-alert for nodehandle |" + std::to_string(nodeHandleToUpdate) + "|"; vector newSNToConvertToUpdatedSN; for (UserAlert::Base* alertToCheck : alerts) { bool ret = false; if (auto pNewSN = eraseNodeHandleFromNewShareNodeAlert(nodeHandleToUpdate, alertToCheck)) { bool emptyAlert = pNewSN->fileNodeHandles.empty() && pNewSN->folderNodeHandles.empty(); LOG_debug << debug_msg << " there are " << (ret ? "no " : "") << " remaining alters for this folder"; if (emptyAlert) pNewSN->setRemoved(); newSNToConvertToUpdatedSN.push_back(pNewSN); } } // Create proper UpdateSharedNodes for(auto n : newSNToConvertToUpdatedSN) { // for an update alert, only files are relevant because folders are not versioned add(new UserAlert::UpdatedSharedNode(n->user(), n->ts(), nextId(), {nodeHandleToUpdate})); } newSNToConvertToUpdatedSN.clear(); // Remove NewSharedNode alert from noted node alerts if (setNotedSharedNodeToUpdate(nodeToUpdate)) { LOG_verbose << debug_msg << " new-alert found in noted nodes"; } } void UserAlerts::purgeNodeVersionsFromStash() { auto& stash = deletedSharedNodesStash; if (stash.empty()) return; std::vector vers; for(auto stashIt = std::begin(stash); stashIt != std::end(stash); ++stashIt) { if (stashIt->second.areNodeVersions()) vers.push_back(stashIt); } std::for_each(std::begin(vers), std::end(vers), [&stash](notedShNodesMap::const_iterator it) { stash.erase(it); }); } void UserAlerts::convertStashedDeletedSharedNodes() { if (deletedSharedNodesStash.empty()) return; notedSharedNodes = deletedSharedNodesStash; deletedSharedNodesStash.clear(); convertNotedSharedNodes(false); clearNotedSharedMembers(); LOG_debug << "Removal-alert noted-nodes stashed alert notifications converted to notifications"; } bool UserAlerts::isDeletedSharedNodesStashEmpty() const { return deletedSharedNodesStash.empty(); } void UserAlerts::stashDeletedNotedSharedNodes(handle originatingUser) { if (isConvertReadyToAdd(originatingUser)) { std::for_each(std::begin(notedSharedNodes), std::end(notedSharedNodes), [this](const std::pair, ff> p) { deletedSharedNodesStash[p.first].squash(p.second); }); } clearNotedSharedMembers(); LOG_verbose << "Removal-alert noted-nodes alert notifications stashed"; } // process server-client notifications bool UserAlerts::procsc_useralert(JSON& jsonsc) { for (;;) { switch (jsonsc.getnameid()) { case name_id::u: if (jsonsc.enterarray()) { for (;;) { UserAlertPendingContact ul; if (jsonsc.enterobject()) { bool inobject = true; while (inobject) { switch (jsonsc.getnameid()) { case name_id::u: ul.u = jsonsc.gethandle(MegaClient::USERHANDLE); break; case makeNameid("m"): jsonsc.storeobject(&ul.m); break; case makeNameid("m2"): if (jsonsc.enterarray()) { for (;;) { string s; if (jsonsc.storeobject(&s)) { ul.m2.push_back(s); } else { break; } } jsonsc.leavearray(); } break; case makeNameid("n"): jsonsc.storeobject(&ul.n); break; case EOO: inobject = false; } } jsonsc.leaveobject(); if (ul.u) { pendingContactUsers[ul.u] = ul; } } else { break; } } jsonsc.leavearray(); } break; case makeNameid("lsn"): lsn = jsonsc.gethandle(8); break; case makeNameid("fsn"): fsn = jsonsc.gethandle(8); break; case makeNameid("ltd"): // last notifcation seen time delta (or 0) lastTimeDelta = jsonsc.getint(); break; case EOO: for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i) { UserAlert::Base* b = *i; b->setSeen(b->ts() + lastTimeDelta < m_time()); if (b->email().empty() && b->user() != UNDEF) { map::iterator itContact = pendingContactUsers.find(b->user()); if (itContact != pendingContactUsers.end()) { b->setEmail(itContact->second.m); if (b->email().empty() && !itContact->second.m2.empty()) { b->setEmail(itContact->second.m2[0]); } } } } initscalerts(); begincatchup = false; catchupdone = true; return true; case name_id::c: // notifications if (jsonsc.enterarray()) { for (;;) { if (jsonsc.enterobject()) { UserAlertRaw un; bool inobject = true; while (inobject) { // 't' designates type - but it appears late in the packet nameid nid = jsonsc.getnameid(); switch (nid) { case makeNameid("t"): un.t = jsonsc.getnameid(); break; case EOO: inobject = false; break; default: // gather up the fields to interpret later as we don't know what type some are // until we get the 't' field which is late in the packet jsonsc.storeobject(&un.fields[nid]); } } if (!isUnwantedAlert(un.t, un.getint(name_id::c, -1))) { add(un); } jsonsc.leaveobject(); } else { break; } } jsonsc.leavearray(); break; } // fall through default: assert(false); if (!jsonsc.storeobject()) { LOG_err << "Error parsing sc user alerts"; begincatchup = false; catchupdone = true; // if we fail to get useralerts, continue anyway return true; } } } } void UserAlerts::acknowledgeAll() { // notify the API. Eg. on when user closes the useralerts list mc.queueCommand(new CommandSetLastAcknowledged(&mc)); } void UserAlerts::acknowledgeAllSucceeded() { for (auto& a : alerts) { if (!a->seen()) { notifyAlert(a, true, mc.reqtag); } } } void UserAlerts::onAcknowledgeReceived() { for (auto& a : alerts) { if (!a->seen()) { notifyAlert(a, true, 0); } } } void UserAlerts::clear() { useralertnotify.clear(); for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i) { delete *i; } alerts.clear(); begincatchup = false; catchupdone = false; catchup_last_timestamp = 0; lsn = UNDEF; fsn = UNDEF; lastTimeDelta = 0; nextid = 0; } UserAlerts::~UserAlerts() { clear(); } bool UserAlerts::unserializeAlert(string* d, uint32_t dbid) { nameid type = 0; CacheableReader r(*d); if (!r.unserializecompressedu64(type)) { return false; } r.eraseused(*d); UserAlert::Base* a = nullptr; switch (type) { case name_id::ipc: a = UserAlert::IncomingPendingContact::unserialize(d, nextId()); break; case name_id::c: a = UserAlert::ContactChange::unserialize(d, nextId()); break; case name_id::upci: a = UserAlert::UpdatedPendingContactIncoming::unserialize(d, nextId()); break; case name_id::upco: a = UserAlert::UpdatedPendingContactOutgoing::unserialize(d, nextId()); break; case name_id::share: a = UserAlert::NewShare::unserialize(d, nextId()); break; case name_id::dshare: a = UserAlert::DeletedShare::unserialize(d, nextId()); break; case name_id::put: a = UserAlert::NewSharedNodes::unserialize(d, nextId()); break; case name_id::d: a = UserAlert::RemovedSharedNode::unserialize(d, nextId()); break; case name_id::u: a = UserAlert::UpdatedSharedNode::unserialize(d, nextId()); break; case name_id::psts: case name_id::psts_v2: a = UserAlert::Payment::unserialize(d, nextId(), type); break; case name_id::pses: a = UserAlert::PaymentReminder::unserialize(d, nextId()); break; case name_id::ph: a = UserAlert::Takedown::unserialize(d, nextId()); break; case name_id::ass: a = UserAlert::SetTakedown::unserialize(d, nextId()); break; #ifdef ENABLE_CHAT case name_id::mcsmp: // this method disambiguates between NewScheduledMeeting and UpdatedScheduledMeeting a = UserAlert::ScheduledMeetingAlert::unserializeScheduledMeeting(d, nextId()); assert(a); break; case name_id::mcsmr: a = UserAlert::DeletedScheduledMeeting::unserialize(d, nextId()); break; #endif } if (a) { a->dbid = dbid; add(a); // takes ownership of a return true; } return false; } void UserAlerts::initscalerts() // called after sc50 response has been received { // Alerts are not critical. There is no need to break execution if db ops failed for some (rare) reason for (auto& a : alerts) { mc.persistAlert(a); } } void UserAlerts::purgescalerts() // called from MegaClient::notifypurge() { if (useralertnotify.empty()) { return; // don't just loop `alerts` every time } assert(catchupdone); trimAlertsToMaxCount(); // send notification for all current alerts, even if some overflowed already LOG_debug << "Notifying " << useralertnotify.size() << " user alerts"; mc.app->useralerts_updated(&useralertnotify[0], (int)useralertnotify.size()); for (auto a : useralertnotify) { mc.persistAlert(a); // persist to db (add/update/remove) if (a->removed()) { auto it = find(alerts.begin(), alerts.end(), a); assert(it != alerts.end()); alerts.erase(it); delete a; } else { a->notified = false; } } useralertnotify.clear(); } void UserAlerts::trimAlertsToMaxCount() { static constexpr size_t maxAlertCount = 200; // value mentioned in the requirements if (alerts.size() < maxAlertCount) return; size_t kept = 0; for (auto& a : alerts) { if (a->removed()) continue; // it's going to be removed, don't take it into account if (kept < maxAlertCount) { ++kept; } else { a->setRemoved(); notifyAlert(a, a->seen(), a->tag); } } } void UserAlerts::notifyAlert(UserAlert::Base *alert, bool seen, int tag) { // skip notifications until up to date if (!catchupdone) return; alert->setSeen(seen); alert->tag = tag; if (!alert->notified) { alert->notified = true; useralertnotify.push_back(alert); } } } // namespace sdk-10.11.0/src/utils.cpp000066400000000000000000004000571516266226600151100ustar00rootroot00000000000000/** * @file utils.cpp * @brief Mega SDK various utilities and helper classes * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/utils.h" #include "mega/base64.h" #include "mega/filesystem.h" #include "mega/logging.h" #include "mega/megaclient.h" #include "mega/serialize64.h" #include "mega/testhooks.h" #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) && defined(_MSC_VER) #include #endif #ifdef __APPLE__ #include #endif #ifdef WIN32 #include #else #include #include #endif // ! WIN32 namespace mega { std::atomic CancelToken::tokensCancelledCount{0}; string toNodeHandle(handle nodeHandle) { char base64Handle[12]; Base64::btoa((byte*)&(nodeHandle), MegaClient::NODEHANDLE, base64Handle); return string(base64Handle); } string toNodeHandle(NodeHandle nodeHandle) { return toNodeHandle(nodeHandle.as8byte()); } NodeHandle toNodeHandle(const byte* data) { NodeHandle ret; if (data) { handle h = 0; // most significant non-used-for-the-handle bytes must be zeroed memcpy(&h, data, MegaClient::NODEHANDLE); ret.set6byte(h); } return ret; } NodeHandle toNodeHandle(const std::string* data) { if(data) return toNodeHandle(reinterpret_cast(data->c_str())); return NodeHandle{}; } string toHandle(handle h) { char base64Handle[14]; Base64::btoa((byte*)&(h), sizeof h, base64Handle); return string(base64Handle); } handle stringToHandle(const std::string& b64String, const int handleSize) { if (b64String.empty()) return UNDEF; std::string binary; if (Base64::atob(b64String, binary) != handleSize) { assert(false); return UNDEF; } return *reinterpret_cast(binary.data()); } std::pair toTypeOfLink(nodetype_t type) { bool error = false; TypeOfLink lType = TypeOfLink::FOLDER; switch(type) { case FOLDERNODE: break; case FILENODE: lType = TypeOfLink::FILE; break; default: error = true; break; } return std::make_pair(error, lType); } std::ostream& operator<<(std::ostream& s, NodeHandle h) { return s << toNodeHandle(h); } SimpleLogger& operator<<(SimpleLogger& s, NodeHandle h) { return s << toNodeHandle(h); } SimpleLogger& operator<<(SimpleLogger& s, UploadHandle h) { return s << toHandle(h.h); } SimpleLogger& operator<<(SimpleLogger& s, NodeOrUploadHandle h) { if (h.isNodeHandle()) { return s << "nh:" << h.nodeHandle(); } else { return s << "uh:" << h.uploadHandle(); } } SimpleLogger& operator<<(SimpleLogger& s, const LocalPath& lp) { // when logging, do not normalize the string, or we can't diagnose failures to match differently encoded utf8 strings return s << lp.toPath(false); } string backupTypeToStr(BackupType type) { switch (type) { case BackupType::INVALID: return "INVALID"; case BackupType::TWO_WAY: return "TWO_WAY"; case BackupType::UP_SYNC: return "UP_SYNC"; case BackupType::DOWN_SYNC: return "DOWN_SYNC"; case BackupType::CAMERA_UPLOAD: return "CAMERA_UPLOAD"; case BackupType::MEDIA_UPLOAD: return "MEDIA_UPLOAD"; case BackupType::BACKUP_UPLOAD: return "BACKUP_UPLOAD"; } return "UNKNOWN"; } void AddHiddenFileAttribute([[maybe_unused]] mega::LocalPath& path) { #ifdef _WIN32 auto pathStr{path.asPlatformEncoded(false)}; WIN32_FILE_ATTRIBUTE_DATA fad; if (GetFileAttributesExW(pathStr.data(), GetFileExInfoStandard, &fad)) SetFileAttributesW(pathStr.data(), fad.dwFileAttributes | FILE_ATTRIBUTE_HIDDEN); #endif } void RemoveHiddenFileAttribute([[maybe_unused]] mega::LocalPath& path) { #ifdef _WIN32 auto pathStr{path.asPlatformEncoded(false)}; WIN32_FILE_ATTRIBUTE_DATA fad; if (GetFileAttributesExW(pathStr.data(), GetFileExInfoStandard, &fad)) SetFileAttributesW(pathStr.data(), fad.dwFileAttributes & ~FILE_ATTRIBUTE_HIDDEN); #endif } CacheableWriter::CacheableWriter(string& d) : dest(d) { } void CacheableWriter::serializebinary(byte* data, size_t len) { dest.append((char*)data, len); } void CacheableWriter::serializechunkmacs(const chunkmac_map& m) { m.serialize(dest); } void CacheableWriter::serializecstr(const char* field, bool storeNull) { unsigned short ll = (unsigned short)(field ? strlen(field) + (storeNull ? 1 : 0) : 0); dest.append((char*)&ll, sizeof(ll)); dest.append(field, ll); } void CacheableWriter::serializepstr(const string* field) { unsigned short ll = (unsigned short)(field ? field->size() : 0); dest.append((char*)&ll, sizeof(ll)); if (field) dest.append(field->data(), ll); } void CacheableWriter::serializestring(const std::wstring& field) { const unsigned short ll = static_cast(field.size() * sizeof(wchar_t)); dest.append(reinterpret_cast(&ll), sizeof(ll)); dest.append(reinterpret_cast(field.data()), ll); } void CacheableWriter::serializestring(const string& field) { unsigned short ll = (unsigned short)field.size(); dest.append((char*)&ll, sizeof(ll)); dest.append(field.data(), ll); } void CacheableWriter::serializestring_u32(const string& field) { uint32_t ll = (uint32_t)field.size(); dest.append((char*)&ll, sizeof(ll)); dest.append(field.data(), ll); } void CacheableWriter::serializecompressedu64(uint64_t field) { byte buf[sizeof field+1]; dest.append((const char*)buf, static_cast(Serialize64::serialize(buf, field))); } void CacheableWriter::serializei8(int8_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializei32(int32_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializei64(int64_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializeu64(uint64_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializeu32(uint32_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializeu16(uint16_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializeu8(uint8_t field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializehandle(handle field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializenodehandle(handle field) { dest.append((const char*)&field, MegaClient::NODEHANDLE); } void CacheableWriter::serializeNodeHandle(NodeHandle field) { serializenodehandle(field.as8byte()); } void CacheableWriter::serializebool(bool field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializebyte(byte field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializedouble(double field) { dest.append((char*)&field, sizeof(field)); } void CacheableWriter::serializeexpansionflags(bool b0, bool b1, bool b2, bool b3, bool b4, bool b5, bool b6, bool b7) { unsigned char b[8]; b[0] = b0; b[1] = b1; b[2] = b2; b[3] = b3; b[4] = b4; b[5] = b5; b[6] = b6; b[7] = b7; dest.append((char*)b, 8); } CacheableReader::CacheableReader(const string& d) : ptr(d.data()) , end(ptr + d.size()) , fieldnum(0) { } void CacheableReader::eraseused(string& d) { assert(end == d.data() + d.size()); d.erase(0, static_cast(ptr - d.data())); } bool CacheableReader::unserializecstr(string& s, bool removeNull) { if (ptr + sizeof(unsigned short) > end) { return false; } unsigned short len = MemAccess::get(ptr); ptr += sizeof(len); if (ptr + len > end) { return false; } if (len) { s.assign(ptr, len - (removeNull ? 1 : 0)); } ptr += len; fieldnum += 1; return true; } bool CacheableReader::unserializestring(std::wstring& s) { if (ptr + sizeof(unsigned short) > end) { return false; } const unsigned short len_bytes = MemAccess::get(ptr); ptr += sizeof(len_bytes); if (ptr + len_bytes > end) { return false; } if (len_bytes) { if (len_bytes % sizeof(wchar_t) != 0) { return false; } size_t wchar_count = len_bytes / sizeof(wchar_t); s.assign(reinterpret_cast(ptr), wchar_count); } ptr += len_bytes; fieldnum += 1; return true; } bool CacheableReader::unserializestring(string& s) { if (ptr + sizeof(unsigned short) > end) { return false; } unsigned short len = MemAccess::get(ptr); ptr += sizeof(len); if (ptr + len > end) { return false; } if (len) { s.assign(ptr, len); } ptr += len; fieldnum += 1; return true; } bool CacheableReader::unserializestring_u32(string& s) { if (ptr + sizeof(uint32_t) > end) { return false; } uint32_t len = MemAccess::get(ptr); ptr += sizeof(len); if (ptr + len > end) { return false; } if (len) { s.assign(ptr, len); } ptr += len; fieldnum += 1; return true; } bool CacheableReader::unserializebinary(byte* data, size_t len) { if (ptr + len > end) { return false; } memcpy(data, ptr, len); ptr += len; fieldnum += 1; return true; } void chunkmac_map::serialize(string& d) const { unsigned short ll = (unsigned short)size(); d.append((char*)&ll, sizeof(ll)); for (auto& it : mMacMap) { d.append((char*)&it.first, sizeof(it.first)); d.append((char*)&it.second, sizeof(it.second)); } } bool chunkmac_map::unserialize(const char*& ptr, const char* end) { unsigned short ll; if ((ptr + sizeof(ll) > end) || ptr + (ll = MemAccess::get(ptr)) * (sizeof(m_off_t) + sizeof(ChunkMAC)) + sizeof(ll) > end) { return false; } ptr += sizeof(ll); for (int i = 0; i < ll; i++) { m_off_t pos = MemAccess::get(ptr); ptr += sizeof(m_off_t); memcpy(&(mMacMap[pos]), ptr, sizeof(ChunkMAC)); ptr += sizeof(ChunkMAC); if (mMacMap[pos].isMacsmacSoFar()) { macsmacSoFarPos = pos; assert(i == 0); } else { assert(pos > macsmacSoFarPos); } } return true; } void chunkmac_map::calcprogress(m_off_t size, m_off_t& chunkpos, m_off_t& progresscompleted, m_off_t* sumOfPartialChunks) { chunkpos = 0; progresscompleted = 0; for (auto& it : mMacMap) { m_off_t chunkceil = ChunkedHash::chunkceil(it.first, size); if (it.second.isMacsmacSoFar()) { assert(chunkpos == 0); macsmacSoFarPos = it.first; chunkpos = chunkceil; progresscompleted = chunkceil; } else if (chunkpos == it.first && it.second.finished) { chunkpos = chunkceil; progresscompleted = chunkceil; } else if (it.second.finished) { m_off_t chunksize = chunkceil - ChunkedHash::chunkfloor(it.first); progresscompleted += chunksize; } else { progresscompleted += it.second.offset; // sum of completed portions if (sumOfPartialChunks) { *sumOfPartialChunks += it.second.offset; } } } setProgressContiguous(chunkpos); } m_off_t chunkmac_map::nextUnprocessedPosFrom(m_off_t pos) { assert(pos > macsmacSoFarPos); for (auto it = mMacMap.find(ChunkedHash::chunkfloor(pos)); it != mMacMap.end(); it = mMacMap.find(ChunkedHash::chunkfloor(pos))) { if (it->second.finished) { pos = ChunkedHash::chunkceil(pos); } else { pos += it->second.offset; break; } } return pos; } m_off_t chunkmac_map::expandUnprocessedPiece(m_off_t pos, m_off_t npos, m_off_t fileSize, m_off_t maxReqSize) { assert(pos > macsmacSoFarPos); for (auto it = mMacMap.find(npos); npos < fileSize && (npos - pos) < maxReqSize && (it == mMacMap.end() || it->second.notStarted()); it = mMacMap.find(npos)) { npos = ChunkedHash::chunkceil(npos, fileSize); } return npos; } m_off_t chunkmac_map::hasUnfinishedGap(m_off_t fileSize) { bool sawUnfinished = false; for (auto it = mMacMap.begin(); it != mMacMap.end(); ) { if (!it->second.finished) { sawUnfinished = true; } auto nextpos = ChunkedHash::chunkceil(it->first, fileSize); auto expected_it = mMacMap.find(nextpos); if (sawUnfinished && expected_it != mMacMap.end() && expected_it->second.finished) { return true; } ++it; if (it != expected_it) { sawUnfinished = true; } } return false; } void chunkmac_map::ctr_encrypt(m_off_t chunkid, SymmCipher *cipher, byte *chunkstart, unsigned chunksize, m_off_t startpos, int64_t ctriv, bool finishesChunk) { assert(chunkid == startpos); assert(startpos > macsmacSoFarPos); // encrypt is always done on whole chunks auto& chunk = mMacMap[chunkid]; cipher->ctr_crypt(chunkstart, unsigned(chunksize), startpos, static_cast(ctriv), chunk.mac, true, true); chunk.offset = 0; chunk.finished = finishesChunk; // when encrypting for uploads, only set finished after confirmation of the chunk uploading. } void chunkmac_map::ctr_decrypt(m_off_t chunkid, SymmCipher *cipher, byte *chunkstart, unsigned chunksize, m_off_t startpos, int64_t ctriv, bool finishesChunk) { assert(chunkid > macsmacSoFarPos); assert(startpos >= chunkid); assert(startpos + chunksize <= ChunkedHash::chunkceil(chunkid)); ChunkMAC& chunk = mMacMap[chunkid]; cipher->ctr_crypt(chunkstart, chunksize, startpos, static_cast(ctriv), chunk.mac, false, chunk.notStarted()); if (finishesChunk) { chunk.finished = true; chunk.offset = 0; } else { assert(startpos + chunksize < ChunkedHash::chunkceil(chunkid)); chunk.finished = false; chunk.offset += chunksize; } } void chunkmac_map::setProgressContiguous(const m_off_t p) { progresscontiguous = p; DEBUG_TEST_HOOK_ON_PROGRESS_CONTIGUOUS_UPDATE(progresscontiguous); } void chunkmac_map::swap(chunkmac_map& other) { mMacMap.swap(other.mMacMap); std::swap(macsmacSoFarPos, other.macsmacSoFarPos); std::swap(progresscontiguous, other.progresscontiguous); DEBUG_TEST_HOOK_ON_PROGRESS_CONTIGUOUS_UPDATE(progresscontiguous); } void chunkmac_map::finishedUploadChunks(chunkmac_map& macs) { for (auto& m : macs.mMacMap) { assert(m.first > macsmacSoFarPos); assert(mMacMap.find(m.first) == mMacMap.end() || !mMacMap[m.first].isMacsmacSoFar()); m.second.finished = true; mMacMap[m.first] = m.second; LOG_verbose << "Upload chunk completed: " << m.first; } } bool chunkmac_map::finishedAt(m_off_t pos) { assert(pos > macsmacSoFarPos); auto pcit = mMacMap.find(pos); return pcit != mMacMap.end() && pcit->second.finished; } m_off_t chunkmac_map::updateContiguousProgress(m_off_t fileSize) { assert(progresscontiguous > macsmacSoFarPos); while (finishedAt(progresscontiguous)) { const auto p = ChunkedHash::chunkceil(progresscontiguous, fileSize); setProgressContiguous(p); } return progresscontiguous; } void chunkmac_map::updateMacsmacProgress(SymmCipher *cipher) { bool updated = false; while (macsmacSoFarPos + 1024 * 1024 * 5 < progresscontiguous // never go past contiguous-from-start section && size() > 32 * 3 + 5) // leave enough room for the mac-with-late-gaps corrective calculation to occur { if (mMacMap.begin()->second.isMacsmacSoFar()) { auto it = mMacMap.begin(); auto& calcSoFar = it->second; auto& next = (++it)->second; assert(it->first == ChunkedHash::chunkfloor(it->first)); SymmCipher::xorblock(next.mac, calcSoFar.mac); cipher->ecb_encrypt(calcSoFar.mac); memcpy(next.mac, calcSoFar.mac, sizeof(next.mac)); macsmacSoFarPos = it->first; next.offset = unsigned(-1); assert(next.isMacsmacSoFar()); mMacMap.erase(mMacMap.begin()); } else if (mMacMap.begin()->first == 0 && finishedAt(0)) { auto& first = mMacMap.begin()->second; byte mac[SymmCipher::BLOCKSIZE] = { 0 }; SymmCipher::xorblock(first.mac, mac); cipher->ecb_encrypt(mac); memcpy(first.mac, mac, sizeof(mac)); first.offset = unsigned(-1); assert(first.isMacsmacSoFar()); macsmacSoFarPos = 0; } updated = true; } if (updated) { LOG_verbose << "Macsmac calculation advanced to " << mMacMap.begin()->first; } } void chunkmac_map::copyEntriesTo(chunkmac_map& other) { for (auto& e : mMacMap) { assert(e.first > macsmacSoFarPos); other.mMacMap[e.first] = e.second; } } m_off_t chunkmac_map::copyEntriesToUntilRaidlineBeforePos(m_off_t maxPos, chunkmac_map& other) { static constexpr auto logPre = "[chunkmac_map::copyEntriesToUntilRaidlineBeforePos] "; maxPos = ChunkedHash::chunkfloor(maxPos); while (maxPos > 0 && (maxPos % RAIDLINE != 0)) { LOG_debug << logPre << "Wrong maxPos not padded to RAIDLINE: maxPos = " << maxPos << ", RAIDLINE = " << RAIDLINE << ", mod = " << (maxPos % RAIDLINE); maxPos -= (maxPos % RAIDLINE); maxPos = ChunkedHash::chunkfloor(maxPos); if (maxPos % RAIDLINE != 0) { LOG_debug << logPre << "maxPos still not padded to RAIDLINE: pos = " << maxPos << ", RAIDLINE = " << RAIDLINE << ", mod = " << (maxPos % RAIDLINE); } } LOG_debug << logPre << "Final maxPos = " << maxPos; if (maxPos == 0) return 0; for (auto& e: mMacMap) { if (e.first >= maxPos) { LOG_debug << logPre << "chunk (" << e.first << ") exceeding maxPos (maxPos = " << maxPos << "), break"; break; } if (!e.second.finished) { LOG_debug << logPre << "chunk (" << e.first << ") not finished (offset = " << e.second.offset << ") (maxPos = " << maxPos << "), break"; break; } other.mMacMap[e.first] = e.second; } return maxPos; } void chunkmac_map::copyEntryTo(m_off_t pos, chunkmac_map& other) { assert(pos > macsmacSoFarPos); mMacMap[pos] = other.mMacMap[pos]; } void chunkmac_map::debugLogOuputMacs() { for (auto& it : mMacMap) { LOG_debug << "macs: " << it.first << " " << Base64Str(it.second.mac) << " " << it.second.finished; } } // coalesce block macs into file mac int64_t chunkmac_map::macsmac(SymmCipher *cipher) { byte mac[SymmCipher::BLOCKSIZE] = { 0 }; for (auto& it : mMacMap) { if (it.second.isMacsmacSoFar()) { assert(it.first == mMacMap.begin()->first); memcpy(mac, it.second.mac, sizeof(mac)); } else { assert(it.first == ChunkedHash::chunkfloor(it.first)); SymmCipher::xorblock(it.second.mac, mac); cipher->ecb_encrypt(mac); } } uint32_t* m = (uint32_t*)mac; m[0] ^= m[1]; m[1] = m[2] ^ m[3]; return MemAccess::get((const char*)mac); } int64_t chunkmac_map::macsmac_gaps(SymmCipher *cipher, size_t g1, size_t g2, size_t g3, size_t g4) { byte mac[SymmCipher::BLOCKSIZE] = { 0 }; size_t n = 0; for (auto it = mMacMap.begin(); it != mMacMap.end(); it++, n++) { if (it->second.isMacsmacSoFar()) { memcpy(mac, it->second.mac, sizeof(mac)); for (m_off_t pos = 0; pos <= it->first; pos = ChunkedHash::chunkceil(pos)) { ++n; } } else { if ((n >= g1 && n < g2) || (n >= g3 && n < g4)) continue; assert(it->first == ChunkedHash::chunkfloor(it->first)); SymmCipher::xorblock(it->second.mac, mac); cipher->ecb_encrypt(mac); } } uint32_t* m = (uint32_t*)mac; m[0] ^= m[1]; m[1] = m[2] ^ m[3]; return MemAccess::get((const char*)mac); } bool CacheableReader::unserializechunkmacs(chunkmac_map& m) { if (m.unserialize(ptr, end)) // ptr is adjusted by reference { fieldnum += 1; return true; } return false; } bool CacheableReader::unserializefingerprint(FileFingerprint& fp) { if (auto newfp = fp.unserialize(ptr, end)) // ptr is adjusted by reference { fp = *newfp; fieldnum += 1; return true; } return false; } bool CacheableReader::unserializecompressedu64(uint64_t& field) { int fieldSize; if ((fieldSize = Serialize64::unserialize((byte*)ptr, static_cast(end - ptr), &field)) < 0) { LOG_err << "Serialize64 unserialization failed - malformed field"; return false; } else { ptr += fieldSize; } return true; } bool CacheableReader::unserializei8(int8_t& field) { if (ptr + sizeof(int8_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(int8_t); fieldnum += 1; return true; } bool CacheableReader::unserializei32(int32_t& field) { if (ptr + sizeof(int32_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(int32_t); fieldnum += 1; return true; } bool CacheableReader::unserializei64(int64_t& field) { if (ptr + sizeof(int64_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(int64_t); fieldnum += 1; return true; } bool CacheableReader::unserializeu16(uint16_t &field) { if (ptr + sizeof(uint16_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(uint16_t); fieldnum += 1; return true; } bool CacheableReader::unserializeu32(uint32_t& field) { if (ptr + sizeof(uint32_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(uint32_t); fieldnum += 1; return true; } bool CacheableReader::unserializeu8(uint8_t& field) { if (ptr + sizeof(uint8_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(uint8_t); fieldnum += 1; return true; } bool CacheableReader::unserializeu64(uint64_t& field) { if (ptr + sizeof(uint64_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(uint64_t); fieldnum += 1; return true; } bool CacheableReader::unserializehandle(handle& field) { if (ptr + sizeof(handle) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(handle); fieldnum += 1; return true; } bool CacheableReader::unserializenodehandle(handle& field) { if (ptr + MegaClient::NODEHANDLE > end) { return false; } field = 0; memcpy((char*)&field, ptr, MegaClient::NODEHANDLE); ptr += MegaClient::NODEHANDLE; fieldnum += 1; return true; } bool CacheableReader::unserializeNodeHandle(NodeHandle& field) { handle h; if (!unserializenodehandle(h)) return false; field.set6byte(h); return true; } bool CacheableReader::unserializebool(bool& field) { if (ptr + sizeof(bool) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(bool); fieldnum += 1; return true; } bool CacheableReader::unserializebyte(byte& field) { if (ptr + sizeof(byte) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(byte); fieldnum += 1; return true; } bool CacheableReader::unserializedouble(double& field) { if (ptr + sizeof(double) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(double); fieldnum += 1; return true; } bool CacheableReader::unserializeexpansionflags(unsigned char field[8], unsigned usedFlagCount) { if (ptr + 8 > end) { return false; } memcpy(field, ptr, 8); for (unsigned i = usedFlagCount; i < 8; i++) { if (field[i]) { LOG_err << "Unserialization failed in expansion flags, invalid version detected. Fieldnum: " << fieldnum; return false; } } ptr += 8; fieldnum += 1; return true; } bool CacheableReader::unserializedirection(direction_t& field) { // TODO: this one should be removed when we next update the transfer db format. sizeof(direction_t) is not the same for all compilers. And could even change if someone edits the enum if (ptr + sizeof(direction_t) > end) { return false; } field = MemAccess::get(ptr); ptr += sizeof(direction_t); fieldnum += 1; return true; } /** * @brief Encrypts a string after padding it to block length. * * Note: With an IV, only use the first 8 bytes. * * @param data Data buffer to be encrypted. Encryption is done in-place, * so cipher text will be in `data` afterwards as well. * @param key AES key for encryption. * @param iv Optional initialisation vector for encryption. Will use a * zero IV if not given. If `iv` is a zero length string, a new IV * for encryption will be generated and available through the reference. * @return true if encryption was successful. */ bool PaddedCBC::encrypt(PrnGen &rng, string* data, SymmCipher* key, string* iv) { if (iv) { // Make a new 8-byte IV, if the one passed is zero length. if (iv->size() == 0) { byte* buf = new byte[8]; rng.genblock(buf, 8); iv->append((char*)buf); delete [] buf; } // Truncate a longer IV to its first 8 bytes. if (iv->size() > 8) { iv->resize(8); } // Bring up the IV size to BLOCKSIZE. iv->resize(key->BLOCKSIZE); } // Pad to block size and encrypt. data->append("E"); data->resize((data->size() + key->BLOCKSIZE - 1) & ~(static_cast(key->BLOCKSIZE) - 1), 'P'); byte* dd = reinterpret_cast(const_cast(data->data())); // make sure it works for pre-C++17 compilers bool encrypted = iv ? key->cbc_encrypt(dd, data->size(), reinterpret_cast(iv->data())) : key->cbc_encrypt(dd, data->size()); // Truncate IV back to the first 8 bytes only.. if (iv) { iv->resize(8); } return encrypted; } /** * @brief Decrypts a string and strips the padding. * * Note: With an IV, only use the first 8 bytes. * * @param data Data buffer to be decrypted. Decryption is done in-place, * so plain text will be in `data` afterwards as well. * @param key AES key for decryption. * @param iv Optional initialisation vector for encryption. Will use a * zero IV if not given. * @return true if decryption was successful. */ bool PaddedCBC::decrypt(string* data, SymmCipher* key, string* iv) { if (iv) { // Truncate a longer IV to its first 8 bytes. if (iv->size() > 8) { iv->resize(8); } // Bring up the IV size to BLOCKSIZE. iv->resize(key->BLOCKSIZE); } if ((data->size() & (key->BLOCKSIZE - 1))) { return false; } // Decrypt and unpad. byte* dd = reinterpret_cast(const_cast(data->data())); // make sure it works for pre-C++17 compilers bool encrypted = iv ? key->cbc_decrypt(dd, data->size(), reinterpret_cast(iv->data())) : key->cbc_decrypt(dd, data->size()); if (!encrypted) { return false; } size_t p = data->find_last_of('E'); if (p == string::npos) { return false; } data->resize(p); return true; } // start of chunk m_off_t ChunkedHash::chunkfloor(m_off_t p) { m_off_t cp, np; cp = 0; for (unsigned i = 1; i <= 8; i++) { np = cp + i * SEGSIZE; if ((p >= cp) && (p < np)) { return cp; } cp = np; } return ((p - cp) & - (8 * SEGSIZE)) + cp; } // end of chunk (== start of next chunk) m_off_t ChunkedHash::chunkceil(m_off_t p, m_off_t limit) { m_off_t cp, np; cp = 0; for (unsigned i = 1; i <= 8; i++) { np = cp + i * SEGSIZE; if ((p >= cp) && (p < np)) { return (limit < 0 || np < limit) ? np : limit; } cp = np; } np = ((p - cp) & - (8 * SEGSIZE)) + cp + 8 * SEGSIZE; return (limit < 0 || np < limit) ? np : limit; } // cryptographic signature generation/verification HashSignature::HashSignature(Hash* h) { hash = h; } HashSignature::~HashSignature() { delete hash; } void HashSignature::add(const byte* data, unsigned len) { hash->add(data, len); } unsigned HashSignature::get(AsymmCipher* privk, byte* sigbuf, unsigned sigbuflen) { string h; hash->get(&h); return privk->rawdecrypt((const byte*)h.data(), h.size(), sigbuf, sigbuflen); } bool HashSignature::checksignature(AsymmCipher* pubk, const byte* sig, unsigned len) { string h, s; unsigned size; hash->get(&h); s.resize(h.size()); size = pubk->rawencrypt(sig, len, (byte*)s.data(), s.size()); if (!size) { return 0; } if (size < h.size()) { // left-pad with 0 s.insert(0, h.size() - size, 0); s.resize(h.size()); } return s == h; } PayCrypter::PayCrypter(PrnGen &rng) : rng(rng) { rng.genblock(keys, ENC_KEY_BYTES + MAC_KEY_BYTES); encKey = keys; hmacKey = keys+ENC_KEY_BYTES; rng.genblock(iv, IV_BYTES); } void PayCrypter::setKeys(const byte *newEncKey, const byte *newHmacKey, const byte *newIv) { memcpy(encKey, newEncKey, ENC_KEY_BYTES); memcpy(hmacKey, newHmacKey, MAC_KEY_BYTES); memcpy(iv, newIv, IV_BYTES); } bool PayCrypter::encryptPayload(const string *cleartext, string *result) { //Check parameters if(!cleartext || !result) { return false; } //AES-CBC encryption string encResult; SymmCipher sym(encKey); if (!sym.cbc_encrypt_pkcs_padding(cleartext, iv, &encResult)) { return false; } //Prepare the message to authenticate (IV + cipher text) string toAuthenticate((char *)iv, IV_BYTES); toAuthenticate.append(encResult); //HMAC-SHA256 HMACSHA256 hmacProcessor(hmacKey, MAC_KEY_BYTES); hmacProcessor.add((byte *)toAuthenticate.data(), toAuthenticate.size()); result->resize(32); hmacProcessor.get((byte *)result->data()); //Complete the result (HMAC + IV - ciphertext) result->append((char *)iv, IV_BYTES); result->append(encResult); return true; } bool PayCrypter::rsaEncryptKeys(const string *cleartext, const byte *pubkdata, int pubkdatalen, string *result, bool randompadding) { //Check parameters if(!cleartext || !pubkdata || !result) { return false; } //Create an AsymmCipher with the public key AsymmCipher asym; asym.setkey(AsymmCipher::PUBKEY, pubkdata, pubkdatalen); //Prepare the message to encrypt (2-byte header + clear text) string keyString; keyString.append(1, static_cast(cleartext->size() >> 8)); keyString.append(1, static_cast(cleartext->size())); keyString.append(*cleartext); //Save the length of the valid message size_t keylen = keyString.size(); //Resize to add padding keyString.resize(asym.getKey(AsymmCipher::PUB_PQ).ByteCount() - 2); //Add padding if(randompadding) { rng.genblock((byte *)keyString.data() + keylen, keyString.size() - keylen); } //RSA encryption result->resize(static_cast(pubkdatalen)); result->resize(asym.rawencrypt((byte *)keyString.data(), keyString.size(), (byte *)result->data(), result->size())); //Complete the result (2-byte header + RSA result) size_t reslen = result->size(); result->insert(0, 1, static_cast(reslen >> 8)); result->insert(1, 1, static_cast(reslen)); return true; } bool PayCrypter::hybridEncrypt(const string *cleartext, const byte *pubkdata, int pubkdatalen, string *result, bool randompadding) { if(!cleartext || !pubkdata || !result) { return false; } //Generate the payload string payloadString; encryptPayload(cleartext, &payloadString); //RSA encryption string rsaKeyCipher; string keysString; keysString.assign((char *)keys, ENC_KEY_BYTES + MAC_KEY_BYTES); rsaEncryptKeys(&keysString, pubkdata, pubkdatalen, &rsaKeyCipher, randompadding); //Complete the result *result = rsaKeyCipher + payloadString; return true; } size_t Utils::utf8SequenceSize(unsigned char c) { int aux = static_cast(c); if (aux >= 0 && aux <= 127) return 1; else if ((aux & 0xE0) == 0xC0) return 2; else if ((aux & 0xF0) == 0xE0) return 3; else if ((aux & 0xF8) == 0xF0) return 4; else { LOG_err << "Malformed UTF-8 sequence, interpret character " << c << " as literal"; return 1; } } string Utils::toUpperUtf8(const string& text) { string result; auto n = utf8proc_ssize_t(text.size()); auto d = text.data(); for (;;) { utf8proc_int32_t c; auto nn = utf8proc_iterate((utf8proc_uint8_t *)d, n, &c); if (nn == 0) break; assert(nn <= n); d += nn; n -= nn; c = utf8proc_toupper(c); char buff[8]; auto charLen = utf8proc_encode_char(c, (utf8proc_uint8_t *)buff); result.append(buff, static_cast(charLen)); } return result; } string Utils::toLowerUtf8(const string& text) { string result; auto n = utf8proc_ssize_t(text.size()); auto d = text.data(); for (;;) { utf8proc_int32_t c; auto nn = utf8proc_iterate((utf8proc_uint8_t *)d, n, &c); if (nn == 0) break; assert(nn <= n); d += nn; n -= nn; c = utf8proc_tolower(c); char buff[8]; auto charLen = utf8proc_encode_char(c, (utf8proc_uint8_t *)buff); result.append(buff, static_cast(charLen)); } return result; } bool Utils::utf8toUnicode(const uint8_t *src, unsigned srclen, string *result) { uint8_t utf8cp1; uint8_t utf8cp2; int32_t unicodecp; if (!srclen) { result->clear(); return true; } byte *res = new byte[srclen]; unsigned rescount = 0; unsigned i = 0; while (i < srclen) { utf8cp1 = src[i++]; if (utf8cp1 < 0x80) { res[rescount++] = utf8cp1; } else { if (i < srclen) { utf8cp2 = src[i++]; // check codepoints are valid if ((utf8cp1 == 0xC2 || utf8cp1 == 0xC3) && utf8cp2 >= 0x80 && utf8cp2 <= 0xBF) { unicodecp = ((utf8cp1 & 0x1F) << 6) + (utf8cp2 & 0x3F); res[rescount++] = static_cast(unicodecp & 0xFF); } else { // error: one of the two-bytes UTF-8 char is not a valid UTF-8 char delete [] res; return false; } } else { // error: last byte indicates a two-bytes UTF-8 char, but only one left delete [] res; return false; } } } result->assign((const char*)res, rescount); delete [] res; return true; } std::string Utils::stringToHex(const std::string& input, bool spaceBetweenBytes) { static const char* const lut = "0123456789ABCDEF"; size_t len = input.length(); std::string output; output.reserve(2 * len + (spaceBetweenBytes ? len : 0)); for (size_t i = 0; i < len; ++i) { const unsigned char c = static_cast(input[i]); output.push_back(lut[c >> 4]); output.push_back(lut[c & 15]); if (spaceBetweenBytes && i + 1 < len) { output.push_back(' '); } } return output; } std::string Utils::hexToString(const std::string &input) { static const char* const lut = "0123456789ABCDEF"; size_t len = input.length(); if (len & 1) throw std::invalid_argument("odd length"); std::string output; output.reserve(len / 2); for (size_t i = 0; i < len; i += 2) { char a = input[i]; const char* p = std::lower_bound(lut, lut + 16, a); if (*p != a) throw std::invalid_argument("not a hex digit"); char b = input[i + 1]; const char* q = std::lower_bound(lut, lut + 16, b); if (*q != b) throw std::invalid_argument("not a hex digit"); output.push_back(static_cast(((p - lut) << 4) | (q - lut))); } return output; } uint64_t Utils::hexStringToUint64(const std::string &input) { uint64_t output; std::stringstream outputStream; outputStream << std::hex << input; outputStream >> output; return output; } std::string Utils::uint64ToHexString(uint64_t input) { std::stringstream outputStream; outputStream << std::hex << std::setfill('0') << std::setw(16) << input; std::string output = outputStream.str(); return output; } int Utils::icasecmp(const std::string& lhs, const std::string& rhs) { return icasecmp(lhs.c_str(), rhs.c_str()); } int Utils::icasecmp(const char* lhs, const char* rhs) { assert(lhs); assert(rhs); #ifdef _WIN32 return _stricmp(lhs, rhs); #else // _WIN32 return strcasecmp(lhs, rhs); #endif // ! _WIN32 } int Utils::icasecmp(const std::wstring& lhs, const std::wstring& rhs) { return icasecmp(lhs.c_str(), rhs.c_str()); } int Utils::icasecmp(const wchar_t* lhs, const wchar_t* rhs) { assert(lhs); assert(rhs); #ifdef _WIN32 return _wcsicmp(lhs, rhs); #else // _WIN32 return wcscasecmp(lhs, rhs); #endif // ! _WIN32 } int Utils::icasecmp(const std::string& lhs, const std::string& rhs, const size_t length) { assert(lhs.size() >= length); assert(rhs.size() >= length); #ifdef _WIN32 return _strnicmp(lhs.c_str(), rhs.c_str(), length); #else // _WIN32 return strncasecmp(lhs.c_str(), rhs.c_str(), length); #endif // ! _WIN32 } int Utils::icasecmp(const std::wstring& lhs, const std::wstring& rhs, const size_t length) { assert(lhs.size() >= length); assert(rhs.size() >= length); #ifdef _WIN32 return _wcsnicmp(lhs.c_str(), rhs.c_str(), length); #else // _WIN32 return wcsncasecmp(lhs.c_str(), rhs.c_str(), length); #endif // ! _WIN32 } int Utils::pcasecmp(const std::string& lhs, const std::string& rhs, const size_t length) { assert(lhs.size() >= length); assert(rhs.size() >= length); #ifdef _WIN32 return icasecmp(lhs, rhs, length); #else // _WIN32 return lhs.compare(0, length, rhs, 0, length); #endif // ! _WIN32 } int Utils::pcasecmp(const std::wstring& lhs, const std::wstring& rhs, const size_t length) { assert(lhs.size() >= length); assert(rhs.size() >= length); #ifdef _WIN32 return icasecmp(lhs, rhs, length); #else // _WIN32 return lhs.compare(0, length, rhs, 0, length); #endif // ! _WIN32 } std::string Utils::replace(const std::string& str, char search, char replacement) { string r; for (std::string::size_type o = 0;;) { std::string::size_type i = str.find(search, o); if (i == string::npos) { r.append(str.substr(o)); break; } r.append(str.substr(o, i-o)); r += replacement; o = i + 1; } return r; } std::string Utils::replace(const std::string& str, const std::string& search, const std::string& replacement) { if (search.empty()) return str; string r; for (std::string::size_type o = 0;;) { std::string::size_type i = str.find(search, o); if (i == string::npos) { r.append(str.substr(o)); break; } r.append(str.substr(o, i - o)); r += replacement; o = i + search.length(); } return r; } bool Utils::hasenv(const std::string &key) { [[maybe_unused]] const auto [_, hasValue] = getenv(key); return hasValue; } std::string Utils::getenv(const std::string& key, const std::string& def) { const auto [value, hasValue] = getenv(key); return hasValue ? value : def; } std::pair Utils::getenv(const std::string& key) { #ifdef WIN32 // on Windows the charset is not UTF-8 by default std::array buf; wstring keyW; LocalPath::path2local(&key, &keyW); const auto foundSize = ::GetEnvironmentVariable(keyW.c_str(), buf.data(), static_cast(buf.size())); // Not found if (foundSize == 0) { return {"", false}; } // Found string ret; wstring input(buf.data(), foundSize); LocalPath::local2path(&input, &ret, false); return {std::move(ret), true}; #else if (const char* value = ::getenv(key.c_str())) { return {value, true}; } // Not found return {"", false}; #endif } void Utils::setenv(const std::string& key, const std::string& value) { #ifdef WIN32 std::wstring keyW; LocalPath::path2local(&key, &keyW); std::wstring valueW; LocalPath::path2local(&value, &valueW); // on Windows the charset is not UTF-8 by default SetEnvironmentVariable(keyW.c_str(), valueW.c_str()); // ::getenv() reads the process environment not calling the operating system _putenv_s(key.c_str(), value.c_str()); #else ::setenv(key.c_str(), value.c_str(), true); #endif } void Utils::unsetenv(const std::string& key) { #ifdef WIN32 std::wstring keyW; LocalPath::path2local(&key, &keyW); SetEnvironmentVariable(keyW.c_str(), L""); // ::getenv() reads the process environment not calling the operating system _putenv_s(key.c_str(), ""); // removes the env var #else ::unsetenv(key.c_str()); #endif } std::string Utils::join(const std::vector& items, const std::string& with) { string r; bool first = true; for (const string& str : items) { if (!first) r.append(with); r.append(str); first = false; } return r; } template bool Utils::startswith(const std::basic_string& str, const std::basic_string& start) { if (str.length() < start.length()) return false; return memcmp(str.data(), start.data(), start.length() * sizeof(T)) == 0; } template bool Utils::startswith(const std::string&, const std::string&); template bool Utils::startswith(const std::wstring&, const std::wstring&); template const T* Utils::startswith(const T* str, const T* start) { if (!str || !start) { return nullptr; } while (*str == *start) { if (*str == 0) { return str; } str++; start++; } return *start == 0 ? str : nullptr; } template const char* Utils::startswith(const char*, const char*); template const wchar_t* Utils::startswith(const wchar_t*, const wchar_t*); template bool Utils::endswith(const T* str, size_t strLen, const T* suffix, size_t sfxLen) { if (strLen < sfxLen) { return false; } if (!str || !suffix) { return false; } const T* end = str + strLen; const T* start = end - sfxLen; while (start < end) { if (*start != *suffix) { return false; } start++; suffix++; } return true; } template bool Utils::endswith(const char*, size_t, const char*, size_t); template bool Utils::endswith(const wchar_t*, size_t, const wchar_t*, size_t); bool Utils::endswith(const std::string &str, char chr) { return str.length() >= 1 && chr == str.back(); } const std::string Utils::_trimDefaultChars(" \t\r\n\0", 5); // space, \t, \0, \r, \n // return string with trimchrs removed from front and back of given string str string Utils::trim(const string& str, const string& trimchrs) { string::size_type s = str.find_first_not_of(trimchrs); if (s == string::npos) return ""; string::size_type e = str.find_last_not_of(trimchrs); if (e == string::npos) return ""; // impossible return str.substr(s, e - s + 1); } std::string Utils::getIcuVersion() { return U_ICU_VERSION; } struct tm* m_localtime(m_time_t ttime, struct tm *dt) { // works for 32 or 64 bit time_t time_t t = static_cast(ttime); #ifdef _WIN32 localtime_s(dt, &t); #else localtime_r(&t, dt); #endif return dt; } struct tm* m_gmtime(m_time_t ttime, struct tm *dt) { // works for 32 or 64 bit time_t time_t t = static_cast(ttime); #ifdef _WIN32 gmtime_s(dt, &t); #else gmtime_r(&t, dt); #endif return dt; } m_time_t m_time(m_time_t* tt ) { // works for 32 or 64 bit time_t time_t t = time(NULL); if (tt) { *tt = t; } return t; } m_time_t m_mktime(struct tm* stm) { // works for 32 or 64 bit time_t return mktime(stm); } dstime m_clock_getmonotonictimeDS() { using namespace std::chrono; auto timeMs = duration_cast(steady_clock::now().time_since_epoch()); return duration(timeMs).count() / 100; } m_time_t m_mktime_UTC(const struct tm *src) { struct tm dst = *src; m_time_t t = 0; #if defined(_MSC_VER) || defined(__MINGW32__) t = mktime(&dst); TIME_ZONE_INFORMATION TimeZoneInfo; GetTimeZoneInformation(&TimeZoneInfo); t += TimeZoneInfo.Bias * 60 - dst.tm_isdst * 3600; #elif _WIN32 #error "localtime is not thread safe in this compiler; please use a later one" #else //POSIX t = mktime(&dst); t += dst.tm_gmtoff - dst.tm_isdst * 3600; #endif return t; } extern time_t stringToTimestamp(string stime, date_time_format_t format) { if ((format == FORMAT_SCHEDULED_COPY && stime.size() != 14) || (format == FORMAT_ISO8601 && stime.size() != 15)) { return 0; } if (format == FORMAT_ISO8601) { stime.erase(8, 1); // remove T from stime (20220726T133000) } struct tm dt; memset(&dt, 0, sizeof(struct tm)); #ifdef _WIN32 for (size_t i = 0; i < stime.size(); i++) { if ( (stime.at(i) < '0') || (stime.at(i) > '9') ) { return 0; //better control of this? } } dt.tm_year = atoi(stime.substr(0,4).c_str()) - 1900; dt.tm_mon = atoi(stime.substr(4,2).c_str()) - 1; dt.tm_mday = atoi(stime.substr(6,2).c_str()); dt.tm_hour = atoi(stime.substr(8,2).c_str()); dt.tm_min = atoi(stime.substr(10,2).c_str()); dt.tm_sec = atoi(stime.substr(12,2).c_str()); #else strptime(stime.c_str(), "%Y%m%d%H%M%S", &dt); #endif if (format == FORMAT_SCHEDULED_COPY) { // let mktime interprete if time has Daylight Saving Time flag correction // TODO: would this work cross platformly? At least I believe it'll be consistent with localtime. Otherwise, we'd need to save that dt.tm_isdst = -1; return (mktime(&dt))*10; // deciseconds } else { // user manually selects a date and a time to start the scheduled meeting in a specific time zone (independent fields on API) // so users should take into account daylight saving for the time zone they specified // this method should convert the specified string dateTime into Unix timestamp (UTC) dt.tm_isdst = 0; return mktime(&dt); // seconds } } std::string rfc1123_datetime( time_t time ) { struct tm * timeinfo; char buffer [80]; timeinfo = gmtime(&time); strftime (buffer, 80, "%a, %d %b %Y %H:%M:%S GMT",timeinfo); return buffer; } string webdavurlescape(const string &value) { ostringstream escaped; escaped.fill('0'); escaped << std::hex; for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { string::value_type c = (*i); if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~' || c == '/' || c == ':') { escaped << c; } else { escaped << std::uppercase; escaped << '%' << std::setw(2) << int((unsigned char) c); escaped << std::nouppercase; } } return escaped.str(); } string escapewebdavchar(const char c) { static bool unintitialized = true; static std::map escapesec; if (unintitialized) { escapesec[33] = "!"; // ! //For some reason &Exclamation; was not properly handled (crashed) by gvfsd-dav escapesec[34] = """; // " escapesec[37] = "%"; // % escapesec[38] = "&"; // & escapesec[39] = "'"; // ' escapesec[43] = "&add;"; // + escapesec[60] = "<"; // < escapesec[61] = "="; // = //For some reason &equal; was not properly handled (crashed) by gvfsd-dav escapesec[62] = ">"; // > escapesec[160] = " "; //NO-BREAK SPACE escapesec[161] = "¡"; //INVERTED EXCLAMATION MARK escapesec[162] = "¢"; //CENT SIGN escapesec[163] = "£"; //POUND SIGN escapesec[164] = "¤"; //CURRENCY SIGN escapesec[165] = "¥"; //YEN SIGN escapesec[166] = "¦"; //BROKEN BAR escapesec[167] = "§"; //SECTION SIGN escapesec[168] = "¨"; //DIAERESIS escapesec[169] = "©"; //COPYRIGHT SIGN escapesec[170] = "ª"; //FEMININE ORDINAL INDICATOR escapesec[171] = "«"; //LEFT-POINTING DOUBLE ANGLE QUOTATION MARK escapesec[172] = "¬"; //NOT SIGN escapesec[173] = "­"; //SOFT HYPHEN escapesec[174] = "®"; //REGISTERED SIGN escapesec[175] = "¯"; //MACRON escapesec[176] = "°"; //DEGREE SIGN escapesec[177] = "±"; //PLUS-MINUS SIGN escapesec[178] = "²"; //SUPERSCRIPT TWO escapesec[179] = "³"; //SUPERSCRIPT THREE escapesec[180] = "´"; //ACUTE ACCENT escapesec[181] = "µ"; //MICRO SIGN escapesec[182] = "¶"; //PILCROW SIGN escapesec[183] = "·"; //MIDDLE DOT escapesec[184] = "¸"; //CEDILLA escapesec[185] = "¹"; //SUPERSCRIPT ONE escapesec[186] = "º"; //MASCULINE ORDINAL INDICATOR escapesec[187] = "»"; //RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK escapesec[188] = "¼"; //VULGAR FRACTION ONE QUARTER escapesec[189] = "½"; //VULGAR FRACTION ONE HALF escapesec[190] = "¾"; //VULGAR FRACTION THREE QUARTERS escapesec[191] = "¿"; //INVERTED QUESTION MARK escapesec[192] = "À"; //LATIN CAPITAL LETTER A WITH GRAVE escapesec[193] = "Á"; //LATIN CAPITAL LETTER A WITH ACUTE escapesec[194] = "Â"; //LATIN CAPITAL LETTER A WITH CIRCUMFLEX escapesec[195] = "Ã"; //LATIN CAPITAL LETTER A WITH TILDE escapesec[196] = "Ä"; //LATIN CAPITAL LETTER A WITH DIAERESIS escapesec[197] = "Å"; //LATIN CAPITAL LETTER A WITH RING ABOVE escapesec[198] = "Æ"; //LATIN CAPITAL LETTER AE escapesec[199] = "Ç"; //LATIN CAPITAL LETTER C WITH CEDILLA escapesec[200] = "È"; //LATIN CAPITAL LETTER E WITH GRAVE escapesec[201] = "É"; //LATIN CAPITAL LETTER E WITH ACUTE escapesec[202] = "Ê"; //LATIN CAPITAL LETTER E WITH CIRCUMFLEX escapesec[203] = "Ë"; //LATIN CAPITAL LETTER E WITH DIAERESIS escapesec[204] = "Ì"; //LATIN CAPITAL LETTER I WITH GRAVE escapesec[205] = "Í"; //LATIN CAPITAL LETTER I WITH ACUTE escapesec[206] = "Î"; //LATIN CAPITAL LETTER I WITH CIRCUMFLEX escapesec[207] = "Ï"; //LATIN CAPITAL LETTER I WITH DIAERESIS escapesec[208] = "Ð"; //LATIN CAPITAL LETTER ETH escapesec[209] = "Ñ"; //LATIN CAPITAL LETTER N WITH TILDE escapesec[210] = "Ò"; //LATIN CAPITAL LETTER O WITH GRAVE escapesec[211] = "Ó"; //LATIN CAPITAL LETTER O WITH ACUTE escapesec[212] = "Ô"; //LATIN CAPITAL LETTER O WITH CIRCUMFLEX escapesec[213] = "Õ"; //LATIN CAPITAL LETTER O WITH TILDE escapesec[214] = "Ö"; //LATIN CAPITAL LETTER O WITH DIAERESIS escapesec[215] = "×"; //MULTIPLICATION SIGN escapesec[216] = "Ø"; //LATIN CAPITAL LETTER O WITH STROKE escapesec[217] = "Ù"; //LATIN CAPITAL LETTER U WITH GRAVE escapesec[218] = "Ú"; //LATIN CAPITAL LETTER U WITH ACUTE escapesec[219] = "Û"; //LATIN CAPITAL LETTER U WITH CIRCUMFLEX escapesec[220] = "Ü"; //LATIN CAPITAL LETTER U WITH DIAERESIS escapesec[221] = "Ý"; //LATIN CAPITAL LETTER Y WITH ACUTE escapesec[222] = "Þ"; //LATIN CAPITAL LETTER THORN escapesec[223] = "ß"; //LATIN SMALL LETTER SHARP S escapesec[224] = "à"; //LATIN SMALL LETTER A WITH GRAVE escapesec[225] = "á"; //LATIN SMALL LETTER A WITH ACUTE escapesec[226] = "â"; //LATIN SMALL LETTER A WITH CIRCUMFLEX escapesec[227] = "ã"; //LATIN SMALL LETTER A WITH TILDE escapesec[228] = "ä"; //LATIN SMALL LETTER A WITH DIAERESIS escapesec[229] = "å"; //LATIN SMALL LETTER A WITH RING ABOVE escapesec[230] = "æ"; //LATIN SMALL LETTER AE escapesec[231] = "ç"; //LATIN SMALL LETTER C WITH CEDILLA escapesec[232] = "è"; //LATIN SMALL LETTER E WITH GRAVE escapesec[233] = "é"; //LATIN SMALL LETTER E WITH ACUTE escapesec[234] = "ê"; //LATIN SMALL LETTER E WITH CIRCUMFLEX escapesec[235] = "ë"; //LATIN SMALL LETTER E WITH DIAERESIS escapesec[236] = "ì"; //LATIN SMALL LETTER I WITH GRAVE escapesec[237] = "í"; //LATIN SMALL LETTER I WITH ACUTE escapesec[238] = "î"; //LATIN SMALL LETTER I WITH CIRCUMFLEX escapesec[239] = "ï"; //LATIN SMALL LETTER I WITH DIAERESIS escapesec[240] = "ð"; //LATIN SMALL LETTER ETH escapesec[241] = "ñ"; //LATIN SMALL LETTER N WITH TILDE escapesec[242] = "ò"; //LATIN SMALL LETTER O WITH GRAVE escapesec[243] = "ó"; //LATIN SMALL LETTER O WITH ACUTE escapesec[244] = "ô"; //LATIN SMALL LETTER O WITH CIRCUMFLEX escapesec[245] = "õ"; //LATIN SMALL LETTER O WITH TILDE escapesec[246] = "ö"; //LATIN SMALL LETTER O WITH DIAERESIS escapesec[247] = "÷"; //DIVISION SIGN escapesec[248] = "ø"; //LATIN SMALL LETTER O WITH STROKE escapesec[249] = "ù"; //LATIN SMALL LETTER U WITH GRAVE escapesec[250] = "ú"; //LATIN SMALL LETTER U WITH ACUTE escapesec[251] = "û"; //LATIN SMALL LETTER U WITH CIRCUMFLEX escapesec[252] = "ü"; //LATIN SMALL LETTER U WITH DIAERESIS escapesec[253] = "ý"; //LATIN SMALL LETTER Y WITH ACUTE escapesec[254] = "þ"; //LATIN SMALL LETTER THORN escapesec[255] = "ÿ"; //LATIN SMALL LETTER Y WITH DIAERESIS escapesec[338] = "Œ"; //LATIN CAPITAL LIGATURE OE escapesec[339] = "œ"; //LATIN SMALL LIGATURE OE escapesec[352] = "Š"; //LATIN CAPITAL LETTER S WITH CARON escapesec[353] = "š"; //LATIN SMALL LETTER S WITH CARON escapesec[376] = "Ÿ"; //LATIN CAPITAL LETTER Y WITH DIAERESIS escapesec[402] = "ƒ"; //LATIN SMALL LETTER F WITH HOOK escapesec[710] = "ˆ"; //MODIFIER LETTER CIRCUMFLEX ACCENT escapesec[732] = "˜"; //SMALL TILDE escapesec[913] = "Α"; //GREEK CAPITAL LETTER ALPHA escapesec[914] = "Β"; //GREEK CAPITAL LETTER BETA escapesec[915] = "Γ"; //GREEK CAPITAL LETTER GAMMA escapesec[916] = "Δ"; //GREEK CAPITAL LETTER DELTA escapesec[917] = "Ε"; //GREEK CAPITAL LETTER EPSILON escapesec[918] = "Ζ"; //GREEK CAPITAL LETTER ZETA escapesec[919] = "Η"; //GREEK CAPITAL LETTER ETA escapesec[920] = "Θ"; //GREEK CAPITAL LETTER THETA escapesec[921] = "Ι"; //GREEK CAPITAL LETTER IOTA escapesec[922] = "Κ"; //GREEK CAPITAL LETTER KAPPA escapesec[923] = "Λ"; //GREEK CAPITAL LETTER LAMDA escapesec[924] = "Μ"; //GREEK CAPITAL LETTER MU escapesec[925] = "Ν"; //GREEK CAPITAL LETTER NU escapesec[926] = "Ξ"; //GREEK CAPITAL LETTER XI escapesec[927] = "Ο"; //GREEK CAPITAL LETTER OMICRON escapesec[928] = "Π"; //GREEK CAPITAL LETTER PI escapesec[929] = "Ρ"; //GREEK CAPITAL LETTER RHO escapesec[931] = "Σ"; //GREEK CAPITAL LETTER SIGMA escapesec[932] = "Τ"; //GREEK CAPITAL LETTER TAU escapesec[933] = "Υ"; //GREEK CAPITAL LETTER UPSILON escapesec[934] = "Φ"; //GREEK CAPITAL LETTER PHI escapesec[935] = "Χ"; //GREEK CAPITAL LETTER CHI escapesec[936] = "Ψ"; //GREEK CAPITAL LETTER PSI escapesec[937] = "Ω"; //GREEK CAPITAL LETTER OMEGA escapesec[945] = "α"; //GREEK SMALL LETTER ALPHA escapesec[946] = "β"; //GREEK SMALL LETTER BETA escapesec[947] = "γ"; //GREEK SMALL LETTER GAMMA escapesec[948] = "δ"; //GREEK SMALL LETTER DELTA escapesec[949] = "ε"; //GREEK SMALL LETTER EPSILON escapesec[950] = "ζ"; //GREEK SMALL LETTER ZETA escapesec[951] = "η"; //GREEK SMALL LETTER ETA escapesec[952] = "θ"; //GREEK SMALL LETTER THETA escapesec[953] = "ι"; //GREEK SMALL LETTER IOTA escapesec[954] = "κ"; //GREEK SMALL LETTER KAPPA escapesec[955] = "λ"; //GREEK SMALL LETTER LAMDA escapesec[956] = "μ"; //GREEK SMALL LETTER MU escapesec[957] = "ν"; //GREEK SMALL LETTER NU escapesec[958] = "ξ"; //GREEK SMALL LETTER XI escapesec[959] = "ο"; //GREEK SMALL LETTER OMICRON escapesec[960] = "π"; //GREEK SMALL LETTER PI escapesec[961] = "ρ"; //GREEK SMALL LETTER RHO escapesec[962] = "ς"; //GREEK SMALL LETTER FINAL SIGMA escapesec[963] = "σ"; //GREEK SMALL LETTER SIGMA escapesec[964] = "τ"; //GREEK SMALL LETTER TAU escapesec[965] = "υ"; //GREEK SMALL LETTER UPSILON escapesec[966] = "φ"; //GREEK SMALL LETTER PHI escapesec[967] = "χ"; //GREEK SMALL LETTER CHI escapesec[968] = "ψ"; //GREEK SMALL LETTER PSI escapesec[969] = "ω"; //GREEK SMALL LETTER OMEGA escapesec[977] = "ϑ"; //GREEK THETA SYMBOL escapesec[978] = "ϒ"; //GREEK UPSILON WITH HOOK SYMBOL escapesec[982] = "ϖ"; //GREEK PI SYMBOL escapesec[8194] = " "; //EN SPACE escapesec[8195] = " "; //EM SPACE escapesec[8201] = " "; //THIN SPACE escapesec[8204] = "‌"; //ZERO WIDTH NON-JOINER escapesec[8205] = "‍"; //ZERO WIDTH JOINER escapesec[8206] = "‎"; //LEFT-TO-RIGHT MARK escapesec[8207] = "‏"; //RIGHT-TO-LEFT MARK escapesec[8211] = "–"; //EN DASH escapesec[8212] = "—"; //EM DASH escapesec[8213] = "―"; //HORIZONTAL BAR escapesec[8216] = "‘"; //LEFT SINGLE QUOTATION MARK escapesec[8217] = "’"; //RIGHT SINGLE QUOTATION MARK escapesec[8218] = "‚"; //SINGLE LOW-9 QUOTATION MARK escapesec[8220] = "“"; //LEFT DOUBLE QUOTATION MARK escapesec[8221] = "”"; //RIGHT DOUBLE QUOTATION MARK escapesec[8222] = "„"; //DOUBLE LOW-9 QUOTATION MARK escapesec[8224] = "†"; //DAGGER escapesec[8225] = "‡"; //DOUBLE DAGGER escapesec[8226] = "•"; //BULLET escapesec[8230] = "…"; //HORIZONTAL ELLIPSIS escapesec[8240] = "‰"; //PER MILLE SIGN escapesec[8242] = "′"; //PRIME escapesec[8243] = "″"; //DOUBLE PRIME escapesec[8249] = "‹"; //SINGLE LEFT-POINTING ANGLE QUOTATION MARK escapesec[8250] = "›"; //SINGLE RIGHT-POINTING ANGLE QUOTATION MARK escapesec[8254] = "‾"; //OVERLINE escapesec[8260] = "⁄"; //FRACTION SLASH escapesec[8364] = "€"; //EURO SIGN escapesec[8465] = "ℑ"; //BLACK-LETTER CAPITAL I escapesec[8472] = "℘"; //SCRIPT CAPITAL P escapesec[8476] = "ℜ"; //BLACK-LETTER CAPITAL R escapesec[8482] = "™"; //TRADE MARK SIGN escapesec[8501] = "ℵ"; //ALEF SYMBOL escapesec[8592] = "←"; //LEFTWARDS ARROW escapesec[8593] = "↑"; //UPWARDS ARROW escapesec[8594] = "→"; //RIGHTWARDS ARROW escapesec[8595] = "↓"; //DOWNWARDS ARROW escapesec[8596] = "↔"; //LEFT RIGHT ARROW escapesec[8629] = "↵"; //DOWNWARDS ARROW WITH CORNER LEFTWARDS escapesec[8656] = "⇐"; //LEFTWARDS DOUBLE ARROW escapesec[8657] = "⇑"; //UPWARDS DOUBLE ARROW escapesec[8658] = "⇒"; //RIGHTWARDS DOUBLE ARROW escapesec[8659] = "⇓"; //DOWNWARDS DOUBLE ARROW escapesec[8660] = "⇔"; //LEFT RIGHT DOUBLE ARROW escapesec[8704] = "∀"; //FOR ALL escapesec[8706] = "∂"; //PARTIAL DIFFERENTIAL escapesec[8707] = "∃"; //THERE EXISTS escapesec[8709] = "∅"; //EMPTY SET escapesec[8711] = "∇"; //NABLA escapesec[8712] = "∈"; //ELEMENT OF escapesec[8713] = "∉"; //NOT AN ELEMENT OF escapesec[8715] = "∋"; //CONTAINS AS MEMBER escapesec[8719] = "∏"; //N-ARY PRODUCT escapesec[8721] = "∑"; //N-ARY SUMMATION escapesec[8722] = "−"; //MINUS SIGN escapesec[8727] = "∗"; //ASTERISK OPERATOR escapesec[8730] = "√"; //SQUARE ROOT escapesec[8733] = "∝"; //PROPORTIONAL TO escapesec[8734] = "∞"; //INFINITY escapesec[8736] = "∠"; //ANGLE escapesec[8743] = "∧"; //LOGICAL AND escapesec[8744] = "∨"; //LOGICAL OR escapesec[8745] = "∩"; //INTERSECTION escapesec[8746] = "∪"; //UNION escapesec[8747] = "∫"; //INTEGRAL escapesec[8756] = "∴"; //THEREFORE escapesec[8764] = "∼"; //TILDE OPERATOR escapesec[8773] = "≅"; //APPROXIMATELY EQUAL TO escapesec[8776] = "≈"; //ALMOST EQUAL TO escapesec[8800] = "≠"; //NOT EQUAL TO escapesec[8801] = "≡"; //IDENTICAL TO escapesec[8804] = "≤"; //LESS-THAN OR EQUAL TO escapesec[8805] = "≥"; //GREATER-THAN OR EQUAL TO escapesec[8834] = "⊂"; //SUBSET OF escapesec[8835] = "⊃"; //SUPERSET OF escapesec[8836] = "⊄"; //NOT A SUBSET OF escapesec[8838] = "⊆"; //SUBSET OF OR EQUAL TO escapesec[8839] = "⊇"; //SUPERSET OF OR EQUAL TO escapesec[8853] = "⊕"; //CIRCLED PLUS escapesec[8855] = "⊗"; //CIRCLED TIMES escapesec[8869] = "⊥"; //UP TACK escapesec[8901] = "⋅"; //DOT OPERATOR escapesec[8968] = "⌈"; //LEFT CEILING escapesec[8969] = "⌉"; //RIGHT CEILING escapesec[8970] = "⌊"; //LEFT FLOOR escapesec[8971] = "⌋"; //RIGHT FLOOR escapesec[9001] = "⟨"; //LEFT-POINTING ANGLE BRACKET escapesec[9002] = "⟩"; //RIGHT-POINTING ANGLE BRACKET escapesec[9674] = "◊"; //LOZENGE escapesec[9824] = "♠"; //BLACK SPADE SUIT escapesec[9827] = "♣"; //BLACK CLUB SUIT escapesec[9829] = "♥"; //BLACK HEART SUIT escapesec[9830] = "♦"; //BLACK DIAMOND SUIT unintitialized = false; } if (escapesec.find(c) != escapesec.end()) { return escapesec[c]; } return string(1,c); } string webdavnameescape(const string &value) { ostringstream escaped; for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { escaped << escapewebdavchar(*i); } return escaped.str(); } void tolower_string(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), [](char c) {return static_cast(::tolower(c)); }); } #ifdef __APPLE__ int macOSmajorVersion() { char releaseStr[256]; size_t size = sizeof(releaseStr); if (!sysctlbyname("kern.osrelease", releaseStr, &size, NULL, 0) && size > 0) { if (strchr(releaseStr,'.')) { char *token = strtok(releaseStr, "."); if (token) { errno = 0; char *endPtr = NULL; long majorVersion = strtol(token, &endPtr, 10); if (endPtr != token && errno != ERANGE && majorVersion >= INT_MIN && majorVersion <= INT_MAX) { return int(majorVersion); } } } } return -1; } #endif CacheableStatus::CacheableStatus(mega::CacheableStatus::Type type, int64_t value) : mType(type) , mValue(value) { } CacheableStatus* CacheableStatus::unserialize(class MegaClient *client, const std::string& data) { int64_t typeBuf; int64_t value; CacheableReader reader(data); if (!reader.unserializei64(typeBuf)) { return nullptr; } if (!reader.unserializei64(value)) { return nullptr; } CacheableStatus::Type type = static_cast(typeBuf); client->mCachedStatus.loadCachedStatus(type, value); return client->mCachedStatus.getPtr(type); } bool CacheableStatus::serialize(std::string* data) const { CacheableWriter writer{*data}; writer.serializei64(mType); writer.serializei64(mValue); return true; } int64_t CacheableStatus::value() const { return mValue; } CacheableStatus::Type CacheableStatus::type() const { return mType; } void CacheableStatus::setValue(const int64_t value) { mValue = value; } std::string CacheableStatus::typeToStr() { return CacheableStatus::typeToStr(mType); } std::string CacheableStatus::typeToStr(CacheableStatus::Type type) { switch (type) { case STATUS_UNKNOWN: return "unknown"; case STATUS_STORAGE: return "storage"; case STATUS_BUSINESS: return "business"; case STATUS_BLOCKED: return "blocked"; case STATUS_PRO_LEVEL: return "pro-level"; case STATUS_FEATURE_LEVEL: return "feature-level"; default: return "undefined"; } } [[nodiscard]] m_off_t legacySparseOffset32Bug(const m_off_t size, const unsigned lane, const unsigned block) noexcept { const auto sizeU64 = static_cast(size); const auto clampMax = sizeU64 - LEGACY_CRC_WINDOW_BYTES; const std::uint32_t sz32 = static_cast(sizeU64); const std::uint32_t idx32 = static_cast(lane * LEGACY_CRC_BLOCKS_PER_LANE + block); // 0..127 const std::uint32_t numer = static_cast( (sz32 - static_cast(LEGACY_CRC_WINDOW_BYTES)) * idx32); // wraps const std::uint32_t off32 = LEGACY_SPARSE_DENOM ? (numer / LEGACY_SPARSE_DENOM) : 0; const auto off64 = static_cast(off32); return static_cast(off64 > clampMax ? clampMax : off64); } [[nodiscard]] bool computeLegacyBuggySparseCrcFA(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, std::array& crcOut) { if (expectedSize <= static_cast(LEGACY_CRC_WINDOW_BYTES) || static_cast(expectedSize) <= LEGACY_OVERFLOW_MIN_SIZE) { return false; } auto fa = mc.fsaccess->newfileaccess(); if (!fa || !fa->fopen(path, FSLogging::logOnError) || fa->type != FILENODE) { return false; } // Only proceed if the file still matches what the sync thread decided to act on. if (fa->size != expectedSize) { return false; } if (!fa->openf(FSLogging::logOnError)) { return false; } std::array block{}; std::int32_t crcval = 0; for (unsigned lane = 0; lane < LEGACY_CRC_LANES; ++lane) { HashCRC32 crc32; for (unsigned j = 0; j < LEGACY_CRC_BLOCKS_PER_LANE; ++j) { const auto offset = legacySparseOffset32Bug(expectedSize, lane, j); if (!fa->frawread(block.data(), static_cast(block.size()), offset, true, FSLogging::logOnError)) { fa->closef(); return false; } crc32.add(block.data(), static_cast(block.size())); } crc32.get(reinterpret_cast(&crcval)); crcOut[lane] = static_cast(htonl(static_cast(crcval))); } fa->closef(); return true; } [[nodiscard]] bool computeLegacyBuggySparseCrcIA(MegaClient& mc, const LocalPath& path, const m_off_t expectedSize, std::array& crcOut) { auto fa = mc.fsaccess->newfileaccess(); if (!fa || !fa->fopen(path, FSLogging::logOnError) || fa->type != FILENODE) return false; if (fa->size != expectedSize) return false; if (!fa->openf(FSLogging::logOnError)) return false; std::array block{}; std::int32_t crcval = 0; m_off_t current = 0; // logical "current" used by the IA algorithm m_off_t stream = 0; // actual forward-only position (UnixStreamAccess::mOffset) for (unsigned lane = 0; lane < 4; ++lane) { HashCRC32 crc32; for (unsigned j = 0; j < 32; ++j) { const m_off_t offset = legacySparseOffset32Bug(expectedSize, lane, j); // forward-only skip based on (offset - current) for (m_off_t fullstep = offset - current; fullstep > 0;) { const unsigned step = fullstep > UINT_MAX ? UINT_MAX : (unsigned)fullstep; stream += (m_off_t)step; // what is->read(nullptr, step) does fullstep -= (m_off_t)step; } // IA updates current to offset even if it went backwards current += (offset - current); // current = offset // read happens at STREAM position, not at "current" if (stream < 0 || stream + (m_off_t)block.size() > expectedSize) { fa->closef(); return false; } if (!fa->frawread(block.data(), (unsigned long)block.size(), stream, true, FSLogging::logOnError)) { fa->closef(); return false; } stream += (m_off_t)block.size(); current += (m_off_t)block.size(); crc32.add(block.data(), (unsigned)block.size()); } crc32.get(reinterpret_cast(&crcval)); crcOut[lane] = (std::int32_t)htonl((std::uint32_t)crcval); } fa->closef(); return true; } bool areCrcEqual(const FingerprintCrc& lhs, const FingerprintCrc& rhs) { return std::memcmp(lhs.data(), rhs.data(), sizeof(lhs)) == 0; } std::pair generateMetaMac(SymmCipher& cipher, FileAccess& ifAccess, const int64_t iv, std::optional pathStr) { using clock = std::chrono::steady_clock; auto start = clock::now(); FileInputStream isAccess(&ifAccess); auto res = generateMetaMac(cipher, isAccess, iv); auto end = clock::now(); auto durationUs = std::chrono::duration_cast(end - start).count(); double durationSec = static_cast(durationUs) / 1'000'000.0; std::ostringstream oss; oss << std::fixed << std::setprecision(6) << durationSec; const std::string p = pathStr.has_value() ? (" for: " + pathStr.value()) : ""; LOG_debug << "generateMetaMac: MAC computed in " << oss.str() << " (s)" << p; return res; } std::pair generateMetaMac(SymmCipher &cipher, InputStreamAccess &isAccess, const int64_t iv) { static const unsigned int SZ_1024K = 1l << 20; static const unsigned int SZ_128K = 128l << 10; auto buffer = std::make_unique(SZ_1024K + SymmCipher::BLOCKSIZE); chunkmac_map chunkMacs; unsigned int chunkLength = 0; m_off_t current = 0; m_off_t remaining = isAccess.size(); while (remaining > 0) { chunkLength = std::min(chunkLength + SZ_128K, static_cast(std::min(remaining, SZ_1024K))); if (!isAccess.read(&buffer[0], chunkLength)) return std::make_pair(false, 0l); memset(&buffer[chunkLength], 0, SymmCipher::BLOCKSIZE); chunkMacs.ctr_encrypt(current, &cipher, buffer.get(), chunkLength, current, iv, true); current += chunkLength; remaining -= chunkLength; DEBUG_TEST_HOOK_MAC_GENERATION_CHUNK_READ(current); } return std::make_pair(true, chunkMacs.macsmac(&cipher)); } bool areEqualNodesByMetaMac(const std::string& nodeKey_a, const std::string& nodeKey_b) { if (nodeKey_a.size() != FILENODEKEYLENGTH || nodeKey_b.size() != FILENODEKEYLENGTH) { LOG_err << "areEqualNodesByMetaMac expects valid node keys. nodeKey_a size =" << nodeKey_a.size() << ", nodeKey_b size =" << nodeKey_b.size(); assert(false); return false; } const char* iva_a = &nodeKey_a[SymmCipher::KEYLENGTH]; auto remoteMac_a = MemAccess::get(iva_a + sizeof(int64_t)); const char* iva_b = &nodeKey_b[SymmCipher::KEYLENGTH]; auto remoteMac_b = MemAccess::get(iva_b + sizeof(int64_t)); return remoteMac_a == remoteMac_b; } MacComparisonResult CompareLocalFileMetaMacWithNodeKey(FileAccess* fa, const std::string& nodeKey, int type, std::optional pathStr) { MacComparisonResult result; SymmCipher cipher; const char* iva = &nodeKey[SymmCipher::KEYLENGTH]; int64_t remoteIv = MemAccess::get(iva); result.remoteMac = MemAccess::get(iva + sizeof(int64_t)); cipher.setkey((byte*)&nodeKey[0], type); auto [succeeded, calcMac] = generateMetaMac(cipher, *fa, remoteIv, pathStr); result.errorCode = succeeded ? 0 : fa->errorcode; // 0 = success, non-zero = OS error code result.localMac = succeeded ? calcMac : INVALID_META_MAC; result.areEqualMacs = succeeded && (calcMac == result.remoteMac); return result; } bool CompareLocalFileMetaMacWithNode(FileAccess* fa, Node* node) { return CompareLocalFileMetaMacWithNodeKey(fa, node->nodekey(), node->type, node->displaypath()) .areEqualMacs; } std::pair genLocalAndRemoteMetaMac(FileAccess* fa, const std::string& nodeKey, int type, std::optional pathStr) { SymmCipher cipher; const char* iva = &nodeKey[SymmCipher::KEYLENGTH]; int64_t remoteIv = MemAccess::get(iva); int64_t remoteMac = MemAccess::get(iva + sizeof(int64_t)); cipher.setkey((byte*)&nodeKey[0], type); auto [succeeded, calcMac] = generateMetaMac(cipher, *fa, remoteIv, pathStr); int64_t localMac = succeeded ? calcMac : INVALID_META_MAC; return {localMac, remoteMac}; } std::string nodeComparisonResultToStr(const node_comparison_result result) { switch (result) { case NODE_COMP_EREAD: return "NODE_COMP_EREAD"; case NODE_COMP_EARGS: return "NODE_COMP_EARGS"; case NODE_COMP_PENDING: return "NODE_COMP_PENDING"; case NODE_COMP_EQUAL: return "NODE_COMP_EQUAL"; case NODE_COMP_DIFFERS_FP: return "NODE_COMP_DIFFERS_FP"; case NODE_COMP_DIFFERS_MAC: return "NODE_COMP_DIFFERS_MAC"; case NODE_COMP_DIFFERS_MTIME: return "NODE_COMP_DIFFERS_MTIME"; default: return "NODE_COMP_UNKNOWN"; } } node_comparison_result CompareMacAndFpExcludingMtime(const FileFingerprint& fp1, const FileFingerprint& fp2, const int64_t metamac1, const int64_t metamac2) { // It doesn't make sense to call this method with invalid metamacs assert(metamac1 != INVALID_META_MAC && metamac2 != INVALID_META_MAC); // Fingerprint comparison excluding mtime if (!fp1.equalExceptMtime(static_cast(fp2))) { if (!fp1.isvalid || !fp2.isvalid) { LOG_warn << "CompareMacAndFpExcludingMtime: fp1 isValid (" << fp1.isvalid << "), fp2 isValid (" << fp2.isvalid << ")"; } return NODE_COMP_DIFFERS_FP; } // IMPORTANT: we need to compare METAMACs even if entire Fingerprint Match (2 Items could differ // just in a few bytes, and FPs could match but METAMAC differ) auto areEqualMacs = metamac1 == metamac2; if (!areEqualMacs) { // It doesn't matter that FPs are equal as METAMAC comparison show that they are // different items return NODE_COMP_DIFFERS_MAC; } return fp1.mtime == fp2.mtime ? NODE_COMP_EQUAL : NODE_COMP_DIFFERS_MTIME; } node_comparison_result CompareNodeWithProvidedMacAndFpExcludingMtime(const Node* node, const FileFingerprint& fp, const int64_t precalcMetamac) { if (!node || node->type != FILENODE || node->nodekey().empty()) { return NODE_COMP_EARGS; } // Fingerprint comparison excluding mtime if (!fp.equalExceptMtime(static_cast(*node))) { if (!node->isvalid || !fp.isvalid) { LOG_warn << "CompareNodeWithProvidedFpAndMac: node isValid (" << node->isvalid << "), fp isValid (" << fp.isvalid << ")"; } return NODE_COMP_DIFFERS_FP; } // IMPORTANT: we need to compare METAMACs even if entire Fingerprint Match (2 Items could differ // just in a few bytes, and FPs could match but METAMAC differ) const char* iva = &node->nodekey()[SymmCipher::KEYLENGTH]; const int64_t remoteMac = MemAccess::get(iva + sizeof(int64_t)); auto areEqualMacs = precalcMetamac == remoteMac; const auto areEqualMtimes = fp.mtime == node->mtime; LOG_debug << "[CompareNodeWithProvidedMacAndFpExcludingMtime] areEqualMacs = " << areEqualMacs << ", areEqualMtimes = " << areEqualMtimes; if (!areEqualMacs) { // It doesn't matter that FPs are equal as METAMAC comparison show that they are // different items return NODE_COMP_DIFFERS_MAC; } return fp.mtime == node->mtime ? NODE_COMP_EQUAL : NODE_COMP_DIFFERS_MTIME; } std::pair CompareLocalFileWithNodeMacAndFpExludingMtime(class MegaClient& client, const LocalPath& path, const FileFingerprint& fp, const Node* node) { if (!node || node->type != FILENODE || node->nodekey().empty()) { return {NODE_COMP_EARGS, INVALID_META_MAC}; } // Fingerprint comparison excluding mtime if (!fp.equalExceptMtime(static_cast(*node))) { if (!node->isvalid || !fp.isvalid) { LOG_warn << "CompareLocalFileWithNodeFpAndMac: node isValid (" << node->isvalid << "), fp isValid (" << fp.isvalid << ")"; } // Fingerprints differ in any of these fields (CRC, Size, isValid) // They may also differ in mtime (it's not relevant for this case as FPs also differs in // something else) return {NODE_COMP_DIFFERS_FP, INVALID_META_MAC}; } // IMPORTANT: we need to compare METAMACs even if entire Fingerprint Match (2 Items could differ // just in few bytes, and FPs could match but METAMAC differ) if (auto fa = client.fsaccess->newfileaccess(); fa && fa->fopen(path, OPEN_RDONLY, FSLogging::logOnError) && fa->type == FILENODE) { LOG_debug << "[CompareLocalFileWithNodeFpAndMac] comparing macs BEGIN..."; MacComparisonResult macResult = CompareLocalFileMetaMacWithNodeKey(fa.get(), node->nodekey(), node->type, path.toPath(false)); auto sameMtime = fp.mtime == node->mtime; LOG_debug << "[CompareLocalFileWithNodeFpAndMac] comparing macs END... [sameMtime = " << sameMtime << ", areEqualMacs = " << macResult.areEqualMacs << ", errorCode = " << macResult.errorCode << "]"; if (sameMtime) { if (macResult.areEqualMacs) { LOG_debug << "Node found with same Fp and MAC than local file. [Path: " << path.toPath(false) << "]"; } else { // Build enriched event message with extra diagnostic fields: // Format: "msg [nodeHandle,nodeFp,localMac,remoteMac,errorCode]" // errorCode: 0 = successful MAC computation (real mismatch), // non-zero = OS error during read (e.g., EIO, ERROR_HANDLE_EOF) std::ostringstream oss; oss << "Node found with same Fp but different MAC than local file" << " [" << toNodeHandle(node->nodehandle) << "," << node->fingerprintDebugString() << ",0x" << std::hex << macResult.localMac << ",0x" << std::hex << macResult.remoteMac << "," << std::dec << macResult.errorCode << "]"; LOG_debug << oss.str() << " [Path: " << path.toPath(false) << "]"; client.sendevent(800036, oss.str().c_str()); } } if (!macResult.areEqualMacs) { // It doesn't matter that FPs are equal as METAMAC comparison show that they are // different items return {NODE_COMP_DIFFERS_MAC, macResult.localMac}; } auto compRes = sameMtime ? NODE_COMP_EQUAL : NODE_COMP_DIFFERS_MTIME; return {compRes, macResult.localMac}; } LOG_warn << "CompareLocalFileWithNodeFpAndMac: cannot read local file: " << path.toPath(false); return {NODE_COMP_EREAD, INVALID_META_MAC}; } void MegaClientAsyncQueue::push(std::function f, bool discardable) { if (mThreads.empty()) { if (f) { f(mZeroThreadsCipher); } } else { { std::lock_guard g(mMutex); mQueue.emplace_back(discardable, std::move(f)); } mConditionVariable.notify_one(); } } MegaClientAsyncQueue::MegaClientAsyncQueue(Waiter& w, unsigned threadCount) : mWaiter(w) { for (unsigned i = threadCount; i--;) { try { mThreads.emplace_back([this]() { asyncThreadLoop(); }); } catch (std::system_error& e) { LOG_err << "Failed to start worker thread: " << e.what(); break; } } LOG_debug << "MegaClient Worker threads running: " << mThreads.size(); } MegaClientAsyncQueue::~MegaClientAsyncQueue() { clearDiscardable(); push(nullptr, false); mConditionVariable.notify_all(); LOG_warn << "~MegaClientAsyncQueue() joining threads"; for (auto& t : mThreads) { t.join(); } LOG_warn << "~MegaClientAsyncQueue() ends"; } void MegaClientAsyncQueue::clearDiscardable() { std::lock_guard g(mMutex); auto newEnd = std::remove_if(mQueue.begin(), mQueue.end(), [](Entry& entry){ return entry.discardable; }); mQueue.erase(newEnd, mQueue.end()); } void MegaClientAsyncQueue::asyncThreadLoop() { SymmCipher cipher; for (;;) { std::function f; { std::unique_lock g(mMutex); mConditionVariable.wait(g, [this]() { return !mQueue.empty(); }); assert(!mQueue.empty()); f = std::move(mQueue.front().f); if (!f) return; // nullptr is not popped, and causes all the threads to exit mQueue.pop_front(); } f(cipher); mWaiter.notify(); } } bool islchex_high(const int c) { // this one constrains two characters to the 0..127 range return (c >= '0' && c <= '7'); } bool islchex_low(const int c) { // this one is the low nibble, unconstrained return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'); } std::string getSafeUrl(const std::string &posturl) { #if !defined(__clang__) && defined(__GNUC__) && __GNUC__ <= 4 string safeurl; safeurl.append(posturl); #else string safeurl = posturl; #endif size_t sid = safeurl.find("sid="); if (sid != string::npos) { sid += 4; size_t end = safeurl.find("&", sid); if (end == string::npos) { end = safeurl.size(); } safeurl.replace(sid, end - sid, end - sid, 'X'); } size_t authKey = safeurl.find("&n="); if (authKey != string::npos) { authKey += 3/*&n=*/ + 8/*public handle*/; size_t end = safeurl.find("&", authKey); if (end == string::npos) { end = safeurl.size(); } safeurl.replace(authKey, end - authKey, end - authKey, 'X'); } return safeurl; } bool readLines(FileAccess& ifAccess, string_vector& destination) { FileInputStream isAccess(&ifAccess); return readLines(isAccess, destination); } bool readLines(InputStreamAccess& isAccess, string_vector& destination) { const auto length = static_cast(isAccess.size()); std::string input(length, '\0'); return isAccess.read((byte*)input.data(), length) && readLines(input, destination); } bool readLines(const std::string& input, string_vector& destination) { const char *current = input.data(); const char *end = current + input.size(); // we assume utf8. Skip the BOM if there is one if (input.size() > 2 && static_cast(current[0]) == 0xEF && static_cast(current[1]) == 0xBB && static_cast(current[2]) == 0xBF) { current += 3; } while (current < end && (*current == '\r' || *current == '\n')) { ++current; } while (current < end) { const char *delim = current; const char *whitespace = current; while (delim < end && *delim != '\r' && *delim != '\n') { ++delim; whitespace += is_space(static_cast(*whitespace)); } if (delim != whitespace) { destination.emplace_back(current, delim); } while (delim < end && (*delim == '\r' || *delim == '\n')) { ++delim; } current = delim; } return true; } bool wildcardMatch(const string& text, const string& pattern) { return wildcardMatch(text.c_str(), pattern.c_str()); } bool wildcardMatch(const char *pszString, const char *pszMatch) // cf. http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=1680&lngWId=3 { const char *cp = nullptr; const char *mp = nullptr; while ((*pszString) && (*pszMatch != '*')) { if ((*pszMatch != *pszString) && (*pszMatch != '?')) { return false; } pszMatch++; pszString++; } while (*pszString) { if (*pszMatch == '*') { if (!*++pszMatch) { return true; } mp = pszMatch; cp = pszString + 1; } else if ((*pszMatch == *pszString) || (*pszMatch == '?')) { pszMatch++; pszString++; } else { pszMatch = mp; pszString = cp++; } } while (*pszMatch == '*') { pszMatch++; } return !*pszMatch; } const char* syncWaitReasonDebugString(SyncWaitReason r) { switch(r) { case SyncWaitReason::NoReason: return "NoReason"; case SyncWaitReason::FileIssue: return "FileIssue"; case SyncWaitReason::MoveOrRenameCannotOccur: return "MoveOrRenameCannotOccur"; case SyncWaitReason::DeleteOrMoveWaitingOnScanning: return "DeleteOrMoveWaitingOnScanning"; case SyncWaitReason::DeleteWaitingOnMoves: return "DeleteWaitingOnMoves"; case SyncWaitReason::UploadIssue: return "UploadIssue"; case SyncWaitReason::DownloadIssue: return "DownloadIssue"; case SyncWaitReason::CannotCreateFolder: return "CannotCreateFolder"; case SyncWaitReason::CannotPerformDeletion: return "CannotPerformDeletion"; case SyncWaitReason::SyncItemExceedsSupportedTreeDepth: return "SyncItemExceedsSupportedTreeDepth"; case SyncWaitReason::FolderMatchedAgainstFile: return "FolderMatchedAgainstFile"; case SyncWaitReason::LocalAndRemoteChangedSinceLastSyncedState_userMustChoose: return "BothChangedSinceLastSynced"; case SyncWaitReason::LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose: return "LocalAndRemotePreviouslyUnsyncedDiffer"; case SyncWaitReason::NamesWouldClashWhenSynced: return "NamesWouldClashWhenSynced"; case SyncWaitReason::SyncWaitReason_LastPlusOne: break; } return ""; } const char* syncPathProblemDebugString(PathProblem r) { switch (r) { case PathProblem::NoProblem: return "NoProblem"; case PathProblem::FileChangingFrequently: return "FileChangingFrequently"; case PathProblem::IgnoreRulesUnknown: return "IgnoreRulesUnknown"; case PathProblem::DetectedHardLink: return "DetectedHardLink"; case PathProblem::DetectedSymlink: return "DetectedSymlink"; case PathProblem::DetectedSpecialFile: return "DetectedSpecialFile"; case PathProblem::DifferentFileOrFolderIsAlreadyPresent: return "DifferentFileOrFolderIsAlreadyPresent"; case PathProblem::ParentFolderDoesNotExist: return "ParentFolderDoesNotExist"; case PathProblem::FilesystemErrorDuringOperation: return "FilesystemErrorDuringOperation"; case PathProblem::NameTooLongForFilesystem: return "NameTooLongForFilesystem"; case PathProblem::CannotFingerprintFile: return "CannotFingerprintFile"; case PathProblem::DestinationPathInUnresolvedArea: return "DestinationPathInUnresolvedArea"; case PathProblem::MACVerificationFailure: return "MACVerificationFailure"; case PathProblem::UnknownDownloadIssue: return "UnknownDownloadIssue"; case PathProblem::DeletedOrMovedByUser: return "DeletedOrMovedByUser"; case PathProblem::FileFolderDeletedByUser: return "FileFolderDeletedByUser"; case PathProblem::MoveToDebrisFolderFailed: return "MoveToDebrisFolderFailed"; case PathProblem::IgnoreFileMalformed: return "IgnoreFileMalformed"; case PathProblem::FilesystemErrorListingFolder: return "FilesystemErrorListingFolder"; case PathProblem::WaitingForScanningToComplete: return "WaitingForScanningToComplete"; case PathProblem::WaitingForAnotherMoveToComplete: return "WaitingForAnotherMoveToComplete"; case PathProblem::SourceWasMovedElsewhere: return "SourceWasMovedElsewhere"; case PathProblem::FilesystemCannotStoreThisName: return "FilesystemCannotStoreThisName"; case PathProblem::CloudNodeInvalidFingerprint: return "CloudNodeInvalidFingerprint"; case PathProblem::CloudNodeIsBlocked: return "CloudNodeIsBlocked"; case PathProblem::PutnodeDeferredByController: return "PutnodeDeferredByController"; case PathProblem::PutnodeCompletionDeferredByController: return "PutnodeCompletionDeferredByController"; case PathProblem::PutnodeCompletionPending: return "PutnodeCompletionPending"; case PathProblem::UploadDeferredByController: return "UploadDeferredByController"; case PathProblem::DetectedNestedMount: return "DetectedNestedMount"; case PathProblem::PathProblem_LastPlusOne: break; } return ""; }; UploadHandle UploadHandle::next() { do { // Since we start with UNDEF, the first update would overwrite the whole handle and at least 1 byte further, causing data corruption if (h == UNDEF) h = 0; byte* ptr = (byte*)(&h + 1); while (!++*--ptr); } while ((h & 0xFFFF000000000000) == 0 || // if the top two bytes were all 0 then it could clash with NodeHandles h == UNDEF); return *this; } handle generateDriveId(PrnGen& rng) { handle driveId; rng.genblock((byte *)&driveId, sizeof(driveId)); driveId |= static_cast(m_time(nullptr)); return driveId; } error readDriveId(FileSystemAccess& fsAccess, const char* pathToDrive, handle& driveId) { if (pathToDrive && strlen(pathToDrive)) return readDriveId(fsAccess, LocalPath::fromAbsolutePath(pathToDrive), driveId); driveId = UNDEF; return API_EREAD; } error readDriveId(FileSystemAccess& fsAccess, const LocalPath& pathToDrive, handle& driveId) { assert(!pathToDrive.empty()); driveId = UNDEF; auto path = pathToDrive; path.appendWithSeparator(LocalPath::fromRelativePath(".megabackup"), false); path.appendWithSeparator(LocalPath::fromRelativePath("drive-id"), false); auto fileAccess = fsAccess.newfileaccess(false); if (!fileAccess->fopen(path, OPEN_RDONLY, FSLogging::logExceptFileNotFound)) { // This case is valid when only checking for file existence return API_ENOENT; } if (!fileAccess->frawread((byte*)&driveId, sizeof(driveId), 0, false, FSLogging::logOnError)) { LOG_err << "Unable to read drive-id from file: " << path; return API_EREAD; } return API_OK; } error writeDriveId(FileSystemAccess& fsAccess, const char* pathToDrive, handle driveId) { auto path = LocalPath::fromAbsolutePath(pathToDrive); path.appendWithSeparator(LocalPath::fromRelativePath(".megabackup"), false); // Try and create the backup configuration directory if (!(fsAccess.mkdirlocal(path, false, false) || fsAccess.target_exists)) { LOG_err << "Unable to create config DB directory: " << path; // Couldn't create the directory and it doesn't exist. return API_EWRITE; } path.appendWithSeparator(LocalPath::fromRelativePath("drive-id"), false); // Open the file for writing auto fileAccess = fsAccess.newfileaccess(false); if (!fileAccess->fopen(path, OPEN_WRONLY, FSLogging::logOnError)) { LOG_err << "Unable to open file to write drive-id: " << path; return API_EWRITE; } // Write the drive-id to file if (!fileAccess->fwrite((byte*)&driveId, sizeof(driveId), 0)) { LOG_err << "Unable to write drive-id to file: " << path; return API_EWRITE; } return API_OK; } int platformGetRLimitNumFile() { #ifndef WIN32 struct rlimit rl{0,0}; if (0 < getrlimit(RLIMIT_NOFILE, &rl)) { auto e = errno; LOG_err << "Error calling getrlimit: " << e; return -1; } return int(rl.rlim_cur); #else LOG_err << "Code for calling getrlimit is not available yet (or not relevant) on this platform"; return -1; #endif } bool platformSetRLimitNumFile([[maybe_unused]] int newNumFileLimit) { #ifndef WIN32 struct rlimit rl{0,0}; if (0 < getrlimit(RLIMIT_NOFILE, &rl)) { auto e = errno; LOG_err << "Error calling getrlimit: " << e; return false; } else { LOG_info << "rlimit for NOFILE before change is: " << rl.rlim_cur << ", " << rl.rlim_max; if (newNumFileLimit < 0) { rl.rlim_cur = rl.rlim_max; } else { rl.rlim_cur = rlim_t(newNumFileLimit); if (rl.rlim_cur > rl.rlim_max) { LOG_info << "Requested rlimit (" << newNumFileLimit << ") will be replaced by maximum allowed value (" << rl.rlim_max << ")"; rl.rlim_cur = rl.rlim_max; } } if (0 < setrlimit(RLIMIT_NOFILE, &rl)) { auto e = errno; LOG_err << "Error calling setrlimit: " << e; return false; } else { LOG_info << "rlimit for NOFILE is: " << rl.rlim_cur; } } return true; #else LOG_err << "Code for calling setrlimit is not available yet (or not relevant) on this platform"; return false; #endif } void debugLogHeapUsage() { #ifdef DEBUG #ifdef WIN32 _CrtMemState state; _CrtMemCheckpoint(&state); LOG_debug << "MEM use. Heap: " << state.lTotalCount << " highwater: " << state.lHighWaterCount << " _FREE_BLOCK/" << state.lCounts[_FREE_BLOCK] << "/" << state.lSizes[_FREE_BLOCK] << " _NORMAL_BLOCK/" << state.lCounts[_NORMAL_BLOCK] << "/" << state.lSizes[_NORMAL_BLOCK] << " _CRT_BLOCK/" << state.lCounts[_CRT_BLOCK] << "/" << state.lSizes[_CRT_BLOCK] << " _IGNORE_BLOCK/" << state.lCounts[_IGNORE_BLOCK] << "/" << state.lSizes[_IGNORE_BLOCK] << " _CLIENT_BLOCK/" << state.lCounts[_CLIENT_BLOCK] << "/" << state.lSizes[_CLIENT_BLOCK]; #endif #endif } bool haveDuplicatedValues(const string_map& readableVals, const string_map& b64Vals) { return any_of(readableVals.begin(), readableVals.end(), [&b64Vals](const string_map::value_type& p1) { return any_of(b64Vals.begin(), b64Vals.end(), [&p1](const string_map::value_type& p2) { return p1.first != p2.first && p1.second == Base64::atob(p2.second); }); }); } void SyncTransferCount::operator-=(const SyncTransferCount& rhs) { auto updateVal = [](auto& dest, const auto v, const std::string& msg) { using T = std::decay_t; static_assert(std::is_unsigned::value, "dest debe ser unsigned"); if (v > dest) { LOG_err << "SyncTransferCount::operator-=. Underflow for " << msg; dest = 0; assert(false); return; } dest -= v; }; updateVal(mCompleted, rhs.mCompleted, "mCompleted"); updateVal(mCompletedBytes, rhs.mCompletedBytes, "mCompletedBytes"); updateVal(mPending, rhs.mPending, "mPending"); updateVal(mPendingBytes, rhs.mPendingBytes, "mPendingBytes"); } bool SyncTransferCount::operator==(const SyncTransferCount& rhs) const { return mCompleted == rhs.mCompleted && mCompletedBytes == rhs.mCompletedBytes && mPending == rhs.mPending && mPendingBytes == rhs.mPendingBytes; } bool SyncTransferCount::operator!=(const SyncTransferCount& rhs) const { return !(*this == rhs); } void SyncTransferCount::clearPendingValues() { mPending = 0; mPendingBytes = 0; } void SyncTransferCounts::operator-=(const SyncTransferCounts& rhs) { mDownloads -= rhs.mDownloads; mUploads -= rhs.mUploads; } bool SyncTransferCounts::operator==(const SyncTransferCounts& rhs) const { return mDownloads == rhs.mDownloads && mUploads == rhs.mUploads; } bool SyncTransferCounts::operator!=(const SyncTransferCounts& rhs) const { return !(*this == rhs); } double SyncTransferCounts::progress(m_off_t inflightProgress) const { auto pending = mDownloads.mPendingBytes + mUploads.mPendingBytes; if (!pending) return 1.0; // 100% auto completed = mDownloads.mCompletedBytes + mUploads.mCompletedBytes + static_cast(inflightProgress); auto progress = static_cast(completed) / static_cast(completed + pending); return std::min(1.0, progress); } m_off_t SyncTransferCounts::pendingTransferBytes() const { return static_cast(mDownloads.mPendingBytes + mUploads.mPendingBytes); } void SyncTransferCounts::clearPendingValues() { mDownloads.clearPendingValues(); mUploads.clearPendingValues(); } #ifdef WIN32 // get the Windows error message in UTF-8 std::string winErrorMessage(DWORD error) { if (error == 0xFFFFFFFF) error = GetLastError(); LPWSTR lpMsgBuf = nullptr; if (!FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPWSTR)&lpMsgBuf, // FORMAT_MESSAGE_ALLOCATE_BUFFER treats the buffer like a pointer 0, NULL)) { // Handle the error. return "[Unknown error " + std::to_string(error) + "]"; } std::wstring wstr(lpMsgBuf); // Free the buffer. LocalFree(lpMsgBuf); std::string r; LocalPath::local2path(&wstr, &r, false); // remove trailing \r\n return Utils::trim(r); } void reportWindowsError(const std::string& message, DWORD error) { if (error == 0xFFFFFFFF) error = GetLastError(); // in case streaming touches the operating system LOG_err << message << ": " << error << ": " << winErrorMessage(error); } #else void reportError(const std::string& message, int aerrno) { if (aerrno == -1) aerrno = errno; // in case streaming touches the operating system LOG_err << message << ": " << aerrno << ": " << strerror(aerrno); } #endif string connDirectionToStr(mega::direction_t directionType) { switch (directionType) { case GET: return "GET"; case PUT: return "PUT"; case API: return "API"; default: return "UNKNOWN"; } } std::string_view toString(const PasswordEntryError err) { switch (err) { case PasswordEntryError::OK: return "Ok"; case PasswordEntryError::PARSE_ERROR: return "Parse error"; case PasswordEntryError::MISSING_PASSWORD: return "Missing password"; case PasswordEntryError::MISSING_NAME: return "Missing name"; case PasswordEntryError::MISSING_TOTP_SHARED_SECRET: return "Missing totp shared secret"; case PasswordEntryError::INVALID_TOTP_SHARED_SECRET: return "Invalid totp shared secret"; case PasswordEntryError::MISSING_TOTP_NDIGITS: return "Missing totp ndigits"; case PasswordEntryError::INVALID_TOTP_NDIGITS: return "Invalid totp ndigits"; case PasswordEntryError::MISSING_TOTP_EXPT: return "Missing totp expt"; case PasswordEntryError::INVALID_TOTP_EXPT: return "Invalid totp expt"; case PasswordEntryError::MISSING_TOTP_HASH_ALG: return "Missing totp hash alg"; case PasswordEntryError::INVALID_TOTP_HASH_ALG: return "Invalid totp hash alg"; case PasswordEntryError::MISSING_CREDIT_CARD_NUMBER: return "Missing credit card number"; case PasswordEntryError::INVALID_CREDIT_CARD_NUMBER: return "Invalid credit card number"; case PasswordEntryError::INVALID_CREDIT_CARD_CVV: return "Invalid credit card cvv (card validation value)"; case PasswordEntryError::INVALID_CREDIT_CARD_EXPIRATION_DATE: return "Invalid credit card expiration date"; } assert(false); return "Unknown error"; } const char* toString(retryreason_t reason) { switch (reason) { #define DEFINE_RETRY_CLAUSE(index, name) case name: return #name; DEFINE_RETRY_REASONS(DEFINE_RETRY_CLAUSE) #undef DEFINE_RETRY_CLAUSE } assert(false && "Unknown retry reason"); return "RETRY_UNKNOWN"; } bool is_space(unsigned int ch) { return std::isspace(static_cast(ch)) != 0; } bool is_digit(unsigned int ch) { return std::isdigit(static_cast(ch)) != 0; } std::string escapeWildCards(const std::string& pattern) { std::string newString; newString.reserve(pattern.size()); bool isEscaped = false; for (const char& character : pattern) { if ((character == WILDCARD_MATCH_ONE || character == WILDCARD_MATCH_ALL) && !isEscaped) { newString.push_back(ESCAPE_CHARACTER); } newString.push_back(character); isEscaped = character == ESCAPE_CHARACTER && !isEscaped; } return newString; } TextPattern::TextPattern(const std::string& text): mText{text} { recalcPattern(); } TextPattern::TextPattern(const char* text) { if (text) { mText = text; recalcPattern(); } } void TextPattern::recalcPattern() { if (mText.empty() || isOnlyWildCards(mText)) { mPattern.clear(); return; } mPattern = WILDCARD_MATCH_ALL + mText + WILDCARD_MATCH_ALL; } bool TextPattern::isOnlyWildCards(const std::string& text) { return std::all_of(std::begin(text), std::end(text), [](auto&& c) -> bool { return c == WILDCARD_MATCH_ALL; }); } std::set::iterator getTagPosition(std::set& tokens, const std::string& pattern, bool stripAccents) { return std::find_if( tokens.begin(), tokens.end(), [&](const std::string& token) { return likeCompare(pattern.c_str(), token.c_str(), ESCAPE_CHARACTER, stripAccents); }); } bool foldCaseAccentEqual(uint32_t codePoint1, uint32_t codePoint2, bool stripAccents) { // 8 is big enough decompose one unicode point using Buffer = std::array; // convenience. auto options = UTF8PROC_CASEFOLD | UTF8PROC_COMPOSE | UTF8PROC_NULLTERM | UTF8PROC_STABLE; // Strip accents if desired. if (stripAccents) { options |= UTF8PROC_STRIPMARK; } auto foldCaseAccent = [options](uint32_t codePoint, Buffer& buff) { return utf8proc_decompose_char((utf8proc_int32_t)codePoint, buff.data(), static_cast(buff.size()), static_cast(options), nullptr); }; Buffer buf1{0}; Buffer buf2{0}; if (foldCaseAccent(codePoint1, buf1) >= 0 && foldCaseAccent(codePoint2, buf2) >= 0) { return buf1 == buf2; } // Fallback if fold case and accent above has errors, better than we couldn't search return u_foldCase(codePoint1, U_FOLD_CASE_DEFAULT) == u_foldCase(codePoint1, U_FOLD_CASE_DEFAULT); } // This code has been taken from sqlite repository (https://www.sqlite.org/src/file?name=ext/icu/icu.c) /* ** This lookup table is used to help decode the first byte of ** a multi-byte UTF8 character. It is copied here from SQLite source ** code file utf8.c. */ static const unsigned char icuUtf8Trans1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, }; #define SQLITE_ICU_READ_UTF8(zIn, c) \ c = *(zIn++); \ if (c>=0xc0){ \ c = icuUtf8Trans1[c-0xc0]; \ while ((*zIn & 0xc0)==0x80){ \ c = (c<<6) + (0x3f & *(zIn++)); \ } \ } #define SQLITE_ICU_SKIP_UTF8(zIn) \ assert(*zIn); \ if (*(zIn++)>=0xc0){ \ while ((*zIn & 0xc0)==0x80){zIn++;} \ } int icuLikeCompare(const uint8_t* zPattern, // LIKE pattern const uint8_t* zString, // The UTF-8 string to compare against const UChar32 uEsc, // The escape character const bool stripAccents) // Whether we should strip accents { // Define Linux wildcards static const uint32_t MATCH_ONE = static_cast(WILDCARD_MATCH_ONE); static const uint32_t MATCH_ALL = static_cast(WILDCARD_MATCH_ALL); int prevEscape = 0; //True if the previous character was uEsc while (1) { // Read (and consume) the next character from the input pattern. uint32_t uPattern; SQLITE_ICU_READ_UTF8(zPattern, uPattern); if(uPattern == 0) break; /* There are now 4 possibilities: ** ** 1. uPattern is an unescaped match-all character "*", ** 2. uPattern is an unescaped match-one character "?", ** 3. uPattern is an unescaped escape character, or ** 4. uPattern is to be handled as an ordinary character */ if (uPattern == MATCH_ALL && !prevEscape && uPattern != (uint32_t)uEsc) { // Case 1 uint8_t c; // Skip any MATCH_ALL or MATCH_ONE characters that follow a // MATCH_ALL. For each MATCH_ONE, skip one character in the // test string while ((c = *zPattern) == MATCH_ALL || c == MATCH_ONE) { if (c == MATCH_ONE) { if (*zString == 0) return 0; SQLITE_ICU_SKIP_UTF8(zString); } zPattern++; } if (*zPattern == 0) return 1; while (*zString) { if (icuLikeCompare(zPattern, zString, uEsc, stripAccents)) { return 1; } SQLITE_ICU_SKIP_UTF8(zString); } return 0; } else if (uPattern == MATCH_ONE && !prevEscape && uPattern != (uint32_t)uEsc) { // Case 2 if( *zString==0 ) return 0; SQLITE_ICU_SKIP_UTF8(zString); } else if (uPattern == (uint32_t)uEsc && !prevEscape) { // Case 3 prevEscape = 1; } else { // Case 4 uint32_t uString; SQLITE_ICU_READ_UTF8(zString, uString); if (!foldCaseAccentEqual(uString, uPattern, stripAccents)) { return 0; } prevEscape = 0; } } return *zString == 0; } bool likeCompare(const char* pattern, const char* str, const UChar32 esc, bool stripAccents) { return static_cast(icuLikeCompare(reinterpret_cast(pattern), reinterpret_cast(str), esc, stripAccents)); } // Get the current process ID unsigned long getCurrentPid() { #ifdef WIN32 return GetCurrentProcessId(); #else return static_cast(getpid()); #endif } template auto extensionOf(const StringType& path, std::string& extension) -> typename std::enable_if::value, bool>::type { // Ensure destination is empty. extension.clear(); // Try and determine where the file's extension begins. auto i = path.find_last_of('.'); // File doesn't contain any extension. if (i == path.npos) return false; // Assume remainder of path is a valid extension. extension.reserve(path.size() - i); // Copy extension from path, making sure each character is lowercased. while (i < path.size()) { // Latch character. auto character = static_cast(path[i++]); // Invalid extension character. if (character < '.' || character > 'z') return extension.clear(), false; // Push lowercase character. extension.push_back(character | ' '); } // Let the caller know we extracted the path's extension. return true; } template auto extensionOf(const StringType& path) -> typename std::enable_if::value, std::string>::type { std::string extension; extensionOf(path, extension); return extension; } // So getExtension(...)'s definition doesn't have to be in the headers. template bool extensionOf(const std::string&, std::string&); template bool extensionOf(const std::wstring&, std::string&); template std::string extensionOf(const std::string&); template std::string extensionOf(const std::wstring&); SplitResult split(const char* begin, const char* end, char delimiter) { SplitResult result; // Assume string doesn't contain the delimiter. result.first.first = begin; result.first.second = static_cast(end - begin); result.second.first = nullptr; result.second.second = 0; // Search for the delimiter. auto* current = std::find(begin, end, delimiter); // String contains the delimiter. if (current != end) { // Tweak result as necessary. result.first.second = static_cast(current - begin); result.second.first = current; result.second.second = static_cast(end - current); } // Return result to caller. return result; } SplitResult split(const char* begin, std::size_t size, char delimiter) { return split(begin, begin + size, delimiter); } SplitResult split(const std::string& value, char delimiter) { return split(value.data(), value.size(), delimiter); } using CollatorPtr = std::unique_ptr; // Set up a collator for numeric (natural) sorting, so it behaves the same as // the web client collator instance Intl.Collator('co', {numeric: true}). // For reference, see the Google V8 engine function JSCollator::New in commit 3b9350b6fc0. // Use English locale on all platforms: such as avoid en_US_POSIX default locale on iOS. // UCOL_CASE_FIRST is decided by English locale which is UCOL_LOWER_FIRST [[nodiscard]] static CollatorPtr createCollator() { UErrorCode ec = U_ZERO_ERROR; CollatorPtr collator{icu::Collator::createInstance(icu::Locale::getEnglish(), ec)}; if (U_FAILURE(ec)) { LOG_err << "ICU::collator fail to createInstance: " << ec; return nullptr; } collator->setStrength(icu::Collator::TERTIARY); // Enable numeric ordering, E.g. 2 < 12 collator->setAttribute(UCOL_NUMERIC_COLLATION, UCOL_ON, ec); if (U_FAILURE(ec)) { LOG_err << "ICU::collator fail to setAttribute UCOL_NUMERIC_COLLATION: " << ec; return nullptr; } return collator; } static int naturalsorting_compare(icu::StringPiece i, icu::StringPiece j) { static_assert(UCOL_EQUAL == 0 && UCOL_GREATER == 1 && UCOL_LESS == -1, "UCollationResult not expected"); // Thread local for multithread safety and performance const static thread_local CollatorPtr collator{createCollator()}; if (!collator) { assert(false && "No collator"); // Fallback return i.compare(j); } UErrorCode ec{U_ZERO_ERROR}; auto result = static_cast(collator->compareUTF8(i, j, ec)); if (U_FAILURE(ec)) { assert(false && "compareUTF8 error"); // Fallback return i.compare(j); } // Additionally, StringPiece::compare two strings if they are numeric natural equal for // cases: 0, 00, 01, 001, a0, a00, 0a00, 00a0. return result != 0 ? result : i.compare(j); } int naturalsorting_compare(const char* i, const char* j) { return naturalsorting_compare(icu::StringPiece{i}, icu::StringPiece{j}); } int naturalsorting_compare(const char* i, int iSize, const char* j, int jSize) { return naturalsorting_compare(icu::StringPiece{i, static_cast(iSize)}, icu::StringPiece{j, static_cast(jSize)}); } std::string ensureAsteriskSurround(std::string str) { if (str.empty()) return "*"; if (str.front() != '*') str.insert(str.begin(), '*'); if (str.back() != '*') str.push_back('*'); return str; } size_t fileExtensionDotPosition(const std::string& fileName) { if (size_t dotPos = fileName.rfind('.'); dotPos == std::string::npos) return fileName.size(); else return dotPos; } std::string getThisThreadIdStr() { std::stringstream ss; ss << std::this_thread::get_id(); return ss.str(); } storagestatus_t getStorageStatusFromString(const std::string& storageStatusStr) { if (storageStatusStr.empty()) { return STORAGE_GREEN; } const auto storageStatusOpt = stringToNumber(storageStatusStr); if (!storageStatusOpt) { LOG_err << "[getStorageStatusFromString] error: cannot parse storage status from value = " << storageStatusStr; return STORAGE_UNKNOWN; } const auto storageStatus = static_cast(*storageStatusOpt); switch (storageStatus) { case STORAGE_RED: case STORAGE_ORANGE: case STORAGE_GREEN: return storageStatus; default: return STORAGE_UNKNOWN; } } std::optional isCaseInsensitive(const LocalPath& path, FileSystemAccess* fsaccess) { static constexpr auto logPre = "[Util - determineCaseInsenstivity] "; auto da = std::unique_ptr(fsaccess->newdiraccess()); auto lp = path; if (da->dopen(&lp, NULL, false)) { LocalPath leafName; nodetype_t dirEntryType; while (da->dnext(lp, leafName, false, &dirEntryType)) { auto uc = Utils::toUpperUtf8(leafName.toPath(false)); auto lc = Utils::toLowerUtf8(leafName.toPath(false)); if (uc == lc) continue; auto lpuc = path; auto lplc = path; lpuc.appendWithSeparator(LocalPath::fromRelativePath(uc), true); lplc.appendWithSeparator(LocalPath::fromRelativePath(lc), true); LOG_debug << logPre << "Testing sync case sensitivity with " << lpuc << " vs " << lplc; auto fa1 = fsaccess->newfileaccess(); auto fa2 = fsaccess->newfileaccess(); LOG_verbose << logPre << "Opening " << lpuc; bool opened1 = fa1->fopen(lpuc, OPEN_RDONLY, FSLogging::logExceptFileNotFound, nullptr, false, true); LOG_verbose << logPre << "Opened " << lpuc << " with result: " << opened1 << ". Closing..."; fa1->closef(); LOG_verbose << logPre << "Closed " << lpuc; LOG_verbose << logPre << "Opening " << lplc; bool opened2 = fa2->fopen(lplc, OPEN_RDONLY, FSLogging::logExceptFileNotFound, nullptr, false, true); LOG_verbose << logPre << "Opened " << lplc << " with result: " << opened2 << ". Closing..."; fa2->closef(); LOG_verbose << logPre << "Closed " << lplc; opened1 = opened1 && fa1->fsidvalid; opened2 = opened2 && fa2->fsidvalid; if (!opened1 && !opened2) { LOG_verbose << logPre << "Neither " << lpuc << " nor " << lplc << " were opened or both fsid were invalid. Continue... [fa1->fsidvalid = " << fa1->fsidvalid << ", fa2->fsidvalid = " << fa2->fsidvalid << "]"; continue; } if (opened1 != opened2) { LOG_verbose << logPre << "Either " << lpuc << " or " << lplc << " were not opened or the fsid were invalid. Return false. " "[fa1->fsidvalid = " << fa1->fsidvalid << ", fa2->fsidvalid = " << fa2->fsidvalid << "]"; return false; } LOG_verbose << logPre << "Return fa1->fsidvalid(" << fa1->fsidvalid << ") && fa2->fsidvalid(" << fa2->fsidvalid << ") && fa1->fsid(" << fa1->fsid << ") == fa2->fsid(" << fa2->fsid << ")"; return fa1->fsidvalid && fa2->fsidvalid && fa1->fsid == fa2->fsid; } } else { LOG_debug << logPre << path << " could not be opened"; } return std::nullopt; } PitagPurpose pitagPurposeFromChar(char c) { switch (c) { case '.': return PitagPurpose::Unknown; case 'U': return PitagPurpose::Upload; case 'F': return PitagPurpose::CreateFolder; case 'I': return PitagPurpose::Import; case 'C': return PitagPurpose::Copy; case 'c': return PitagPurpose::CopyInternal; case 'S': return PitagPurpose::Sync; case 'B': return PitagPurpose::Backup; case 'P': return PitagPurpose::Password; case 'f': return PitagPurpose::Fuse; case 'H': return PitagPurpose::Helpdesk; default: return PitagPurpose::Unknown; } } PitagTrigger pitagTriggerFromChar(char c) { switch (c) { case '.': return PitagTrigger::NotApplicable; case 'p': return PitagTrigger::Picker; case 'd': return PitagTrigger::DragAndDrop; case 'c': return PitagTrigger::Camera; case 's': return PitagTrigger::Scanner; case 'a': return PitagTrigger::SyncAlgorithm; case 'S': return PitagTrigger::ShareFromApp; case 'C': return PitagTrigger::CameraCapture; case 'e': return PitagTrigger::ExplorerExtension; case 'v': return PitagTrigger::VoiceRecorder; default: return PitagTrigger::NotApplicable; } } PitagNodeType pitagNodeTypeFromChar(char c) { switch (c) { case '.': return PitagNodeType::NotApplicable; case 'F': return PitagNodeType::Folder; case 'f': return PitagNodeType::File; default: return PitagNodeType::NotApplicable; } } PitagTarget pitagTargetFromChar(char c) { switch (c) { case '.': return PitagTarget::NotApplicable; case 'D': return PitagTarget::CloudDrive; case 'c': return PitagTarget::Chat1To1; case 'C': return PitagTarget::ChatGroup; case 's': return PitagTarget::NoteToSelf; case 'i': return PitagTarget::IncomingShare; case 'M': return PitagTarget::MultipleChats; default: return PitagTarget::NotApplicable; } } PitagImportSource pitagImportSourceFromChar(char c) { switch (c) { case '.': return PitagImportSource::NotApplicable; case 'F': return PitagImportSource::FolderLink; case 'f': return PitagImportSource::FileLink; case 'A': return PitagImportSource::AlbumLink; case 'D': return PitagImportSource::CloudDrive; case 'c': return PitagImportSource::Chat1To1; case 'C': return PitagImportSource::ChatGroup; case 's': return PitagImportSource::NoteToSelf; case 'i': return PitagImportSource::IncomingShare; default: return PitagImportSource::NotApplicable; } } std::string pitagToString(const Pitag& pitag) { std::string pitagString; pitagString += getEnumValue(pitag.purpose); pitagString += getEnumValue(pitag.trigger); pitagString += getEnumValue(pitag.nodeType); pitagString += getEnumValue(pitag.target); pitagString += getEnumValue(pitag.importSource); return pitagString; } MEGA_API std::optional pitagFromString(const std::string& pitagString) { if (pitagString.size() != 5) { return std::nullopt; } Pitag pitag; pitag.purpose = pitagPurposeFromChar(pitagString[0]); pitag.trigger = pitagTriggerFromChar(pitagString[1]); pitag.nodeType = pitagNodeTypeFromChar(pitagString[2]); pitag.target = pitagTargetFromChar(pitagString[3]); pitag.importSource = pitagImportSourceFromChar(pitagString[4]); if (pitag.purpose == PitagPurpose::Unknown && pitagString[0] != getEnumValue(PitagPurpose::Unknown)) { return std::nullopt; } if (pitag.trigger == PitagTrigger::NotApplicable && pitagString[1] != getEnumValue(PitagTrigger::NotApplicable)) { return std::nullopt; } if (pitag.nodeType == PitagNodeType::NotApplicable && pitagString[2] != getEnumValue(PitagNodeType::NotApplicable)) { return std::nullopt; } if (pitag.target == PitagTarget::NotApplicable && pitagString[3] != getEnumValue(PitagTarget::NotApplicable)) { return std::nullopt; } if (pitag.importSource == PitagImportSource::NotApplicable && pitagString[4] != getEnumValue(PitagImportSource::NotApplicable)) { return std::nullopt; } return pitag; } } // namespace mega sdk-10.11.0/src/waiterbase.cpp000066400000000000000000000022071516266226600160710ustar00rootroot00000000000000/** * @file waiterbase.cpp * @brief Generic waiter interface * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/waiter.h" #include "mega/utils.h" namespace mega { std::atomic Waiter::ds{0}; std::mutex Waiter::dsMutex; // update monotonously increasing timestamp in deciseconds void Waiter::bumpds() { std::lock_guard lock(dsMutex); ds = m_clock_getmonotonictimeDS(); } void Waiter::init(dstime timeout) { maxds = timeout; } // add events to wakeup criteria void Waiter::wakeupby(EventTrigger* et, int flags) { et->addevents(this, flags); } } // namespace sdk-10.11.0/src/win32/000077500000000000000000000000001516266226600142005ustar00rootroot00000000000000sdk-10.11.0/src/win32/console.cpp000066400000000000000000001035051516266226600163520ustar00rootroot00000000000000/** * @file win32/console.cpp * @brief Win32 console I/O * * (c) 2013-2018 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "megaapi.h" #include #include #include #include #include #include #include #include namespace mega { using namespace std; #ifdef NO_READLINE std::string WinConsole::toUtf8String(const std::wstring& ws, UINT codepage) { std::string s; s.resize((ws.size() + 1) * 4); int nchars = WideCharToMultiByte(codepage, 0, ws.data(), int(ws.size()), (LPSTR)s.data(), int(s.size()), NULL, NULL); s.resize(nchars); return s; } std::wstring WinConsole::toUtf16String(const std::string& s, UINT codepage) { std::wstring ws; ws.resize(s.size() + 1); int nwchars = MultiByteToWideChar(codepage, 0, s.data(), int(s.size()), (LPWSTR)ws.data(), int(ws.size())); ws.resize(nwchars); return ws; } inline static bool wicmp(wchar_t a, wchar_t b) { return(towupper(a) == towupper(b)); } struct Utf8Rdbuf : public streambuf { HANDLE h; UINT codepage = CP_UTF8; UINT failover_codepage = CP_UTF8; std::ofstream logfile; WinConsole::logstyle logstyle = WinConsole::no_log; WinConsole* wc; bool log(const string& localfile, WinConsole::logstyle ls) { logfile.close(); logstyle = WinConsole::no_log; if (localfile.empty() && ls == WinConsole::no_log) { return true; } logfile.open(localfile.c_str(), ios::out | ios::binary | ios::trunc); if (logfile.fail() || !logfile.is_open()) { return false; } logstyle = ls; return true; } Utf8Rdbuf(HANDLE ch, WinConsole* w) : h(ch), wc(w) {} streamsize xsputn(const char* s, streamsize n) { DWORD bn = DWORD(_Pnavail()), written = 0; string s8(pbase(), bn); pbump(-int(bn)); s8.append(s, size_t(n)); if (logstyle == WinConsole::utf8_log) { logfile << s8; } wstring ws = WinConsole::toUtf16String(s8); if (logstyle == WinConsole::utf16_log) { logfile.write((const char*)ws.data(), ws.size() * sizeof(wchar_t)); } else if (logstyle == WinConsole::codepage_log) { logfile << WinConsole::toUtf8String(ws, codepage); } if (wc) { wc->retractPrompt(); } BOOL b = WriteConsoleW(h, ws.data(), DWORD(ws.size()), &written, NULL); if (!b) { // The font can't display some characters (fails on windows 7 - windows 10 not so much but just in case). // Output those that we can and indicate the others. // If the user selects a suitable font then there should not be any failures. for (unsigned i = 0; i < ws.size(); ++i) { b = WriteConsoleW(h, ws.data() + i, 1, &written, NULL); if (!b && failover_codepage != codepage) { // for raster fonts, we can have a second go with another code page, translating directly from utf16 if (SetConsoleOutputCP(failover_codepage)) { b = WriteConsoleW(h, ws.data() + i, 1, &written, NULL); SetConsoleOutputCP(codepage); } } if (!b) { wostringstream wos; wos << L""; wstring str = wos.str(); WriteConsoleW(h, str.data(), DWORD(str.size()), &written, NULL); } } } return n; } int overflow(int c) { char cc = char(c); xsputn(&cc, 1); return c; } }; void ConsoleModel::addInputChar(wchar_t c) { insertPos = std::clamp(insertPos, 0, buffer.size()); if (c == 13) { buffer.push_back(c); insertPos = buffer.size(); newlinesBuffered = true; consoleNewlineNeeded = true; searchingHistory = false; historySearchString.clear(); } else { if (searchingHistory) { historySearchString.push_back(c); updateHistoryMatch(searchingHistoryForward, false); } else { buffer.insert(insertPos, 1, c); insertPos += 1; } redrawInputLineNeeded = true; } #ifdef HAVE_AUTOCOMPLETE autocompleteState.active = false; #endif } void ConsoleModel::getHistory(int index, int offset) { if (inputHistory.empty() && offset == 1) { buffer.clear(); newlinesBuffered = false; } else { index = std::clamp(index, 0, (int)inputHistory.size() - 1) + (enteredHistory ? offset : (offset == -1 ? -1 : 0)); if (index < 0 || index >= (int)inputHistory.size()) { return; } inputHistoryIndex = index; buffer = inputHistory[inputHistoryIndex]; enteredHistory = true; newlinesBuffered = false; } insertPos = buffer.size(); redrawInputLineNeeded = true; } void ConsoleModel::searchHistory(bool forwards) { if (!searchingHistory) { searchingHistory = true; searchingHistoryForward = forwards; historySearchString.clear(); } else { updateHistoryMatch(forwards, true); } redrawInputLineNeeded = true; } void ConsoleModel::updateHistoryMatch(bool forwards, bool increment) { bool checking = false; for (unsigned i = 0; i < inputHistory.size()*2; ++i) { size_t index = forwards ? inputHistory.size()*2 - i - 1 : i; index %= inputHistory.size(); checking = checking || !enteredHistory || index == inputHistoryIndex; if (checking && !(enteredHistory && increment && index == inputHistoryIndex)) { auto iter = std::search(inputHistory[index].begin(), inputHistory[index].end(), historySearchString.begin(), historySearchString.end(), wicmp); if (iter != inputHistory[index].end()) { inputHistoryIndex = index; enteredHistory = true; buffer = inputHistory[index]; insertPos = buffer.size(); newlinesBuffered = false; redrawInputLineNeeded = true; break; } } } } void ConsoleModel::deleteHistorySearchChars(size_t n) { if (n == 0) { searchingHistory = false; } else { n = std::min(n, historySearchString.size()); historySearchString.erase(historySearchString.size() - n, n); updateHistoryMatch(searchingHistoryForward, false); } redrawInputLineNeeded = true; } void ConsoleModel::redrawInputLine(int p) { insertPos = std::clamp(p, 0, buffer.size()); redrawInputLineNeeded = true; } void ConsoleModel::autoComplete(bool forwards, unsigned consoleWidth) { #ifdef HAVE_AUTOCOMPLETE if (autocompleteSyntax) { if (!autocompleteState.active) { std::string u8line = WinConsole::toUtf8String(buffer); size_t u8InsertPos = WinConsole::toUtf8String(buffer.substr(0, insertPos)).size(); autocompleteState = autocomplete::autoComplete(u8line, u8InsertPos, autocompleteSyntax, unixCompletions); if (autocompleteFunction) { // also get additional app specific options, and merge std::vector appcompletions = autocompleteFunction(WinConsole::toUtf8String(getInputLineToCursor())); autocomplete::ACState acs; acs.words.push_back(autocompleteState.originalWord); acs.completions.swap(autocompleteState.completions); for (auto& c : appcompletions) { acs.addCompletion(c.s, c.caseInsensitive, c.couldExtend); } autocompleteState.completions.swap(acs.completions); autocompleteState.tidyCompletions(); } autocompleteState.active = true; } autocomplete::applyCompletion(autocompleteState, forwards, consoleWidth, redrawInputLineConsoleFeedback); buffer = WinConsole::toUtf16String(autocompleteState.line); newlinesBuffered = false; size_t u16InsertPos = WinConsole::toUtf16String(autocompleteState.line.substr(0, autocompleteState.wordPos.second)).size(); insertPos = std::clamp(u16InsertPos, 0, buffer.size()); redrawInputLineNeeded = true; } #endif } static bool isWordBoundary(size_t i, const std::wstring s) { return i == 0 || i >= s.size() || is_space(s[i - 1]) && !is_space(s[i + 1]); } int ConsoleModel::detectWordBoundary(int start, bool forward) { start = static_cast(std::clamp(start, 0, buffer.size())); do { start += (forward ? 1 : -1); } while (!isWordBoundary(start, buffer)); return start; } void ConsoleModel::deleteCharRange(int start, int end) { start = static_cast(std::clamp(start, 0, buffer.size())); end = static_cast(std::clamp(end, 0, buffer.size())); if (start < end) { buffer.erase(start, end - start); newlinesBuffered = buffer.find(13) != string::npos; redrawInputLine(start); } } void ConsoleModel::performLineEditingAction(lineEditAction action, unsigned consoleWidth) { #ifdef HAVE_AUTOCOMPLETE if (action != AutoCompleteForwards && action != AutoCompleteBackwards) { autocompleteState.active = false; } #endif if (action != HistorySearchForward && action != HistorySearchBackward && action != DeleteCharLeft && action != ClearLine) { searchingHistory = false; } int pos = static_cast(insertPos); int bufSize = static_cast(buffer.size()); switch (action) { case CursorLeft: redrawInputLine(pos - 1); break; case CursorRight: redrawInputLine(pos + 1); break; case CursorStart: redrawInputLine(0); break; case CursorEnd: redrawInputLine(bufSize); break; case WordLeft: redrawInputLine((int)detectWordBoundary(pos, false)); break; case WordRight: redrawInputLine((int)detectWordBoundary(pos, true)); break; case HistoryUp: getHistory((int)inputHistoryIndex, 1); break; case HistoryDown: getHistory((int)inputHistoryIndex, -1); break; case HistoryStart: getHistory((int)inputHistory.size() - 1, 0); break; case HistoryEnd: getHistory(0, 0); break; case HistorySearchForward: searchHistory(true); break; case HistorySearchBackward: searchHistory(false); break; case ClearLine: searchingHistory ? deleteHistorySearchChars(0) : deleteCharRange(0, bufSize); break; case DeleteCharLeft: searchingHistory ? deleteHistorySearchChars(1) : deleteCharRange(pos - 1, pos); break; case DeleteCharRight: deleteCharRange(pos, pos + 1); break; case DeleteWordLeft: deleteCharRange(detectWordBoundary(pos, false), pos); break; case DeleteWordRight: deleteCharRange(pos, detectWordBoundary(pos, true)); break; case AutoCompleteForwards: autoComplete(true, consoleWidth); break; case AutoCompleteBackwards: autoComplete(false, consoleWidth); break; } } bool ConsoleModel::checkForCompletedInputLine(std::wstring& ws) { auto newlinePos = std::find(buffer.begin(), buffer.end(), 13); if (newlinePos != buffer.end()) { ws.assign(buffer.begin(), newlinePos); buffer.erase(buffer.begin(), newlinePos + 1); insertPos = 0; newlinesBuffered = buffer.find(13) != string::npos; bool sameAsLastCommand = !inputHistory.empty() && inputHistory[0] == ws; bool sameAsChosenHistory = !inputHistory.empty() && inputHistoryIndex >= 0 && inputHistoryIndex < inputHistory.size() && inputHistory[inputHistoryIndex] == ws; if (echoOn && !sameAsLastCommand && !ws.empty()) { if (inputHistory.size() + 1 > MaxHistoryEntries) { inputHistory.pop_back(); } inputHistory.push_front(ws); inputHistoryIndex = sameAsChosenHistory ? inputHistoryIndex + 1 : -1; } enteredHistory = false; return true; } newlinesBuffered = false; return false; } std::wstring ConsoleModel::getInputLineToCursor() { insertPos = std::clamp(insertPos, 0, buffer.size()); return buffer.substr(0, insertPos); } #endif WinConsole::WinConsole() { #ifdef NO_READLINE hInput = GetStdHandle(STD_INPUT_HANDLE); hOutput = GetStdHandle(STD_OUTPUT_HANDLE); DWORD dwMode; GetConsoleMode(hInput, &dwMode); SetConsoleMode(hInput, dwMode & ~(ENABLE_MOUSE_INPUT)); FlushConsoleInputBuffer(hInput); blockingConsolePeek = false; #endif } WinConsole::~WinConsole() { #ifdef NO_READLINE if (rdbuf) { std::cout.rdbuf(oldrb1); std::cerr.rdbuf(oldrb2); delete rdbuf; } #endif } #ifdef NO_READLINE string WinConsole::getConsoleFont(COORD& size) { CONSOLE_FONT_INFOEX cfi; memset(&cfi, 0, sizeof(cfi)); cfi.cbSize = sizeof(cfi); GetCurrentConsoleFontEx(hOutput, FALSE, &cfi); wstring wname = cfi.FaceName; string name = WinConsole::toUtf8String(wname); if (!(cfi.FontFamily & TMPF_TRUETYPE) && (wname.size() < 6 || name.find("?") != string::npos)) { // the name is garbled on win 7, try to compensate name = "Terminal"; } size = cfi.dwFontSize; return name; } void WinConsole::getShellCodepages(UINT& codepage, UINT& failover_codepage) { if (rdbuf) { codepage = rdbuf->codepage; failover_codepage = rdbuf->failover_codepage; } else { codepage = GetConsoleOutputCP(); failover_codepage = codepage; } } bool WinConsole::setShellConsole(UINT codepage, UINT failover_codepage) { // Call this if your console app is taking live input, with the user editing commands on screen, similar to cmd or powershell // Ideally we would work in unicode all the time (with codepage = CP_UTF8). However in windows 7 for example, with raster // font selected, the o symbol with diacritic (U+00F3) is not output correctly. So we offer the option to attempt output // a second time in a 'failover' codepage, or to output in a single codepage only. The user has control with the 'codepage' command. // use cases covered // utf8 output with std::cout (since we already use cout so much and it's compatible with other platforms) // unicode input with windows ReadConsoleInput api // drag and drop filenames from explorer to the console window // copy and paste unicode filenames from 'ls' output into your next command // upload and download unicode/utf-8 filenames to/from Mega // input a unicode/utf8 password without displaying anything // normal cmd window type editing, including autocomplete (with runtime selectable unix style or dos style, default to local platform rules) // the console must have a suitable font selected for the characters to diplay properly BOOL ok = SetConsoleCP(codepage); ok = ok && SetConsoleOutputCP(codepage); if (!ok) { codepage = CP_UTF8; failover_codepage = GetOEMCP(); SetConsoleCP(codepage); SetConsoleOutputCP(codepage); } // skip the historic complexities of output modes etc, our own rdbuf can write direct to console if (!rdbuf) { rdbuf = new Utf8Rdbuf(hOutput, this); oldrb1 = std::cout.rdbuf(rdbuf); oldrb2 = std::cerr.rdbuf(rdbuf); } rdbuf->codepage = codepage; rdbuf->failover_codepage = failover_codepage; return ok; } #ifdef HAVE_AUTOCOMPLETE void WinConsole::setAutocompleteSyntax(autocomplete::ACN a) { model.autocompleteSyntax = a; } void WinConsole::setAutocompleteFunction(std::function(string)> f) { model.autocompleteFunction = f; } #endif void WinConsole::setAutocompleteStyle(bool unix) { model.unixCompletions = unix; } bool WinConsole::getAutocompleteStyle() const { return model.unixCompletions; } HANDLE WinConsole::inputAvailableHandle() { // returns a handle that will be signalled when there is console input to process (ie records available for PeekConsoleInput) // client can wait on this handle with other handles as higher priority return hInput; } bool WinConsole::consolePeek() { return blockingConsolePeek?consolePeekBlocking():consolePeekNonBlocking(); } bool WinConsole::consolePeekNonBlocking() { std::cout << std::flush; // Read keypreses up to the first newline (or multiple newlines if bool checkPromptOnce = true; for (;;) { INPUT_RECORD ir; DWORD nRead; BOOL ok = PeekConsoleInput(hInput, &ir, 1, &nRead); // peek first so we never wait assert(ok); if (!nRead) { break; } bool isCharacterGeneratingKeypress = ir.EventType == 1 && ir.Event.KeyEvent.uChar.UnicodeChar != 0 && (ir.Event.KeyEvent.bKeyDown || // key press (!ir.Event.KeyEvent.bKeyDown && ((ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) || ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU))); // key release that emits a unicode char if (isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered)) { break; } ok = ReadConsoleInputW(hInput, &ir, 1, &nRead); // discard the event record assert(ok); assert(nRead == 1); ConsoleModel::lineEditAction action = interpretLineEditingKeystroke(ir); if ((action != ConsoleModel::nullAction || isCharacterGeneratingKeypress) && checkPromptOnce) { redrawPromptIfLoggingOccurred(); checkPromptOnce = false; } if (action != ConsoleModel::nullAction) { CONSOLE_SCREEN_BUFFER_INFO sbi; ok = GetConsoleScreenBufferInfo(hOutput, &sbi); assert(ok); unsigned consoleWidth = ok ? sbi.dwSize.X : 50; model.performLineEditingAction(action, consoleWidth); } else if (isCharacterGeneratingKeypress) { for (int i = ir.Event.KeyEvent.wRepeatCount; i--; ) { model.addInputChar(ir.Event.KeyEvent.uChar.UnicodeChar); } if (model.newlinesBuffered) { break; } } } if (model.redrawInputLineNeeded && model.echoOn) { #ifdef HAVE_AUTOCOMPLETE redrawInputLine(&model.redrawInputLineConsoleFeedback); #else redrawInputLine(); #endif } if (model.consoleNewlineNeeded) { DWORD written = 0; #ifndef NDEBUG BOOL b = #endif WriteConsoleW(hOutput, L"\n", 1, &written, NULL); assert(b && written == 1); } model.redrawInputLineNeeded = false; model.consoleNewlineNeeded = false; return model.newlinesBuffered; } bool WinConsole::consolePeekBlocking() { std::cout << std::flush; // Read keypreses up to the first newline (or multiple newlines if bool checkPromptOnce = true; bool isCharacterGeneratingKeypress = false; INPUT_RECORD ir; DWORD nRead; if (!ReadConsoleInputW(hInput, &ir, 1, &nRead)) // discard the event record { return false; } irs.push_back(ir); isCharacterGeneratingKeypress = ir.EventType == 1 && ir.Event.KeyEvent.uChar.UnicodeChar != 0 && (ir.Event.KeyEvent.bKeyDown || // key press (!ir.Event.KeyEvent.bKeyDown && ((ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) || ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU))); // key release that emits a unicode char if (!(isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered))) { while(!irs.empty()) { INPUT_RECORD& firstRecord = irs.front(); ConsoleModel::lineEditAction action = interpretLineEditingKeystroke(firstRecord); if ((action != ConsoleModel::nullAction || isCharacterGeneratingKeypress) && checkPromptOnce) { redrawPromptIfLoggingOccurred(); checkPromptOnce = false; } if (action != ConsoleModel::nullAction) { CONSOLE_SCREEN_BUFFER_INFO sbi; BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi); assert(ok); unsigned consoleWidth = ok ? sbi.dwSize.X : 50; model.performLineEditingAction(action, consoleWidth); } else if (isCharacterGeneratingKeypress) { for (int i = firstRecord.Event.KeyEvent.wRepeatCount; i--;) { model.addInputChar(firstRecord.Event.KeyEvent.uChar.UnicodeChar); } if (model.newlinesBuffered) // todo: address case where multiple newlines were added from this one record (as we may get stuck in wait()) { irs.pop_front(); break; } } irs.pop_front(); } } if (model.redrawInputLineNeeded && model.echoOn) { #ifdef HAVE_AUTOCOMPLETE redrawInputLine(&model.redrawInputLineConsoleFeedback); #else redrawInputLine(); #endif } if (model.consoleNewlineNeeded) { DWORD written = 0; #ifndef NDEBUG BOOL b = #endif WriteConsoleW(hOutput, L"\n", 1, &written, NULL); assert(b && written == 1); } model.redrawInputLineNeeded = false; model.consoleNewlineNeeded = false; return model.newlinesBuffered; } ConsoleModel::lineEditAction WinConsole::interpretLineEditingKeystroke(INPUT_RECORD &ir) { if (ir.EventType == 1 && ir.Event.KeyEvent.bKeyDown) { bool ctrl = ir.Event.KeyEvent.dwControlKeyState & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED); bool shift = ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED; switch (ir.Event.KeyEvent.wVirtualKeyCode) { case VK_LEFT: return ctrl ? ConsoleModel::WordLeft : ConsoleModel::CursorLeft; case VK_RIGHT: return ctrl ? ConsoleModel::WordRight : ConsoleModel::CursorRight; case VK_UP: return ConsoleModel::HistoryUp; case VK_DOWN: return ConsoleModel::HistoryDown; case VK_PRIOR: return ConsoleModel::HistoryStart; // pageup case VK_NEXT: return ConsoleModel::HistoryEnd; // pagedown case VK_HOME: return ConsoleModel::CursorStart; case VK_END: return ConsoleModel::CursorEnd; case VK_DELETE: return ConsoleModel::DeleteCharRight; case VK_INSERT: return ConsoleModel::Paste; // the OS takes care of this; we don't see it case VK_CONTROL: break; case VK_SHIFT: break; case 's': case 'S': return ctrl ? (shift ? ConsoleModel::HistorySearchBackward : ConsoleModel::HistorySearchForward) : ConsoleModel::nullAction; case 'r': case 'R': return ctrl ? (shift ? ConsoleModel::HistorySearchForward : ConsoleModel::HistorySearchBackward) : ConsoleModel::nullAction; default: switch (ir.Event.KeyEvent.uChar.UnicodeChar) { case '\b': return ConsoleModel::DeleteCharLeft; case '\t': return shift ? ConsoleModel::AutoCompleteBackwards : ConsoleModel::AutoCompleteForwards; case VK_ESCAPE: return ConsoleModel::ClearLine; default: break; } break; } } return ConsoleModel::nullAction; } #ifdef HAVE_AUTOCOMPLETE void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autocompleteFeedback = nullptr) #else void WinConsole::redrawInputLine() #endif { CONSOLE_SCREEN_BUFFER_INFO sbi; #ifdef HAVE_AUTOCOMPLETE if (autocompleteFeedback && !autocompleteFeedback->stringgrid.empty()) { promptRetracted = true; cout << "\n" << std::flush; for (auto& r : autocompleteFeedback->stringgrid) { int x = 0; for (unsigned c = 0; c < r.size(); ++c) { cout << r[c] << std::flush; if (c + 1 == r.size()) { cout << "\n" << std::flush; } else { x += autocompleteFeedback->columnwidths[c]; // to make the grid nice in the presence of unicode characters that are sometimes double-width glyphs, we set the X coordinate explicitly BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi); if (ok && sbi.dwCursorPosition.X < x) { sbi.dwCursorPosition.X = short(x); SetConsoleCursorPosition(hOutput, sbi.dwCursorPosition); } } } } autocompleteFeedback->stringgrid.clear(); autocompleteFeedback->columnwidths.clear(); } #endif BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi); assert(ok); if (ok) { std::string sprompt = model.searchingHistory ? ("history-" + std::string(model.searchingHistoryForward ? "F:'" : "R:'") + toUtf8String(model.historySearchString) + "'> ") : currentPrompt; std::wstring wprompt = toUtf16String(sprompt); if (long(wprompt.size() + model.buffer.size() + 1) < sbi.dwSize.X || !model.echoOn) { inputLineOffset = 0; } else { // scroll the line if the cursor reaches the end, or moves back within 15 of the start size_t showleft = 15; if (inputLineOffset + showleft >= model.insertPos) { inputLineOffset = model.insertPos - std::min(showleft, model.insertPos); } else if (wprompt.size() + model.insertPos + 1 >= inputLineOffset + sbi.dwSize.X) { inputLineOffset = wprompt.size() + model.insertPos + 1 - sbi.dwSize.X; } } size_t width = std::max(wprompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor std::unique_ptr line(new CHAR_INFO[width]); for (size_t i = width; i--; ) { line[i].Attributes = sbi.wAttributes; if (i < inputLineOffset) { line[i].Char.UnicodeChar = ' '; } else if (inputLineOffset && i + 1 == inputLineOffset + wprompt.size()) { line[i].Char.UnicodeChar = '|'; line[i].Attributes |= FOREGROUND_INTENSITY | FOREGROUND_GREEN; line[i].Attributes &= ~(FOREGROUND_RED | FOREGROUND_BLUE); } else if (i < inputLineOffset + wprompt.size()) { line[i].Char.UnicodeChar = wprompt[i - inputLineOffset]; line[i].Attributes |= FOREGROUND_INTENSITY; } else if (i < wprompt.size() + model.buffer.size() && model.echoOn) { line[i].Char.UnicodeChar = model.buffer[i - wprompt.size()]; } else { line[i].Char.UnicodeChar = ' '; } } SMALL_RECT screenarea2{ 0, sbi.dwCursorPosition.Y, sbi.dwSize.X, sbi.dwCursorPosition.Y }; ok = WriteConsoleOutputW(hOutput, line.get(), COORD{ SHORT(width), 1 }, COORD{ SHORT(inputLineOffset), 0 }, &screenarea2); assert(ok); COORD cpos{ SHORT(wprompt.size() + model.insertPos - inputLineOffset), sbi.dwCursorPosition.Y }; ok = SetConsoleCursorPosition(hOutput, cpos); assert(ok); promptRetracted = false; } } void WinConsole::retractPrompt() { if (currentPrompt.size() && !promptRetracted) { CONSOLE_SCREEN_BUFFER_INFO sbi; BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi); assert(ok); //if (0 == memcmp(&knownCursorPos, &sbi.dwCursorPosition, sizeof(COORD))) { size_t width = std::max(currentPrompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor std::unique_ptr line(new CHAR_INFO[width]); for (size_t i = width; i--; ) { line[i].Attributes = sbi.wAttributes; line[i].Char.UnicodeChar = ' '; } SMALL_RECT screenarea2{ 0, sbi.dwCursorPosition.Y, sbi.dwSize.X, sbi.dwCursorPosition.Y }; ok = WriteConsoleOutputW(hOutput, line.get(), COORD{ SHORT(width), 1 }, COORD{ SHORT(inputLineOffset), 0 }, &screenarea2); assert(ok); COORD cpos{ 0, sbi.dwCursorPosition.Y }; ok = SetConsoleCursorPosition(hOutput, cpos); assert(ok); promptRetracted = true; } } } wstring WinConsole::getInputLineToCursor() { return model.getInputLineToCursor(); } bool WinConsole::consoleGetch(wchar_t& c) { // todo: remove this function once we don't need to support readline for any version of megacli on windows if (consolePeek()) { c = model.buffer.front(); model.buffer.erase(0, 1); model.newlinesBuffered = model.buffer.find(13) != string::npos; return true; } return false; } #endif void WinConsole::readpwchar(char* pw_buf, int pw_buf_size, int* pw_buf_pos, char** line) { #ifdef NO_READLINE // todo: remove/stub this function once we don't need to support readline for any version of megacli on windows wchar_t c; if (consoleGetch(c)) // only processes once newline is buffered, so no backspace processing needed { if (c == 13) { *line = _strdup(toUtf8String(wstring(&c, 1)).c_str()); memset(pw_buf, 0, pw_buf_size); } else if (*pw_buf_pos + 2 <= pw_buf_size) { *(wchar_t*)(pw_buf + *pw_buf_pos) = c; *pw_buf_pos += 2; } } #else char c; DWORD cread; if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &cread, NULL) == 1) { if ((c == 8) && *pw_buf_pos) { (*pw_buf_pos)--; } else if (c == 13) { *line = (char*)malloc(*pw_buf_pos + 1); memcpy(*line, pw_buf, *pw_buf_pos); (*line)[*pw_buf_pos] = 0; } else if (*pw_buf_pos < pw_buf_size) { pw_buf[(*pw_buf_pos)++] = c; } } #endif } void WinConsole::setecho(bool echo) { #ifdef NO_READLINE model.echoOn = echo; #else HANDLE hCon = GetStdHandle(STD_INPUT_HANDLE); DWORD mode; GetConsoleMode(hCon, &mode); if (echo) { mode |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT; } else { mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); } SetConsoleMode(hCon, mode); #endif } #ifdef NO_READLINE void WinConsole::redrawPromptIfLoggingOccurred() { if (promptRetracted) { redrawInputLine(); } } void WinConsole::updateInputPrompt(const std::string& newprompt) { cout << std::flush; currentPrompt = newprompt; redrawInputLine(); } char* WinConsole::checkForCompletedInputLine() { if (rdbuf && rdbuf->logstyle != WinConsole::no_log) { rdbuf->logfile << flush; } redrawPromptIfLoggingOccurred(); if (consolePeek()) { std::wstring ws; if (model.checkForCompletedInputLine(ws)) { if (rdbuf && rdbuf->logstyle == WinConsole::utf16_log) { std::wstring wprompt = toUtf16String(currentPrompt); rdbuf->logfile.write((const char*)wprompt.data(), wprompt.size() * sizeof(wchar_t)); rdbuf->logfile.write((const char*)ws.data(), ws.size() * sizeof(wchar_t)); rdbuf->logfile.write((const char*)(const wchar_t*)L"\n", sizeof(wchar_t)); } string u8s = toUtf8String(ws); if (rdbuf && rdbuf->logstyle == WinConsole::utf8_log) { rdbuf->logfile << currentPrompt << u8s << "\n"; } else if (rdbuf && rdbuf->logstyle == WinConsole::codepage_log) { rdbuf->logfile << currentPrompt << toUtf8String(ws, rdbuf->codepage) << "\n"; } currentPrompt.clear(); return _strdup(u8s.c_str()); } } return NULL; } void WinConsole::clearScreen() { CONSOLE_SCREEN_BUFFER_INFO csbi; BOOL ok = GetConsoleScreenBufferInfo(hOutput, &csbi); assert(ok); if (ok) { // Fill the entire buffer with spaces DWORD count; ok = FillConsoleOutputCharacter(hOutput, (TCHAR) ' ', csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count); assert(ok); // Fill the entire buffer with the current colors and attributes ok = FillConsoleOutputAttribute(hOutput, csbi.wAttributes, csbi.dwSize.X *csbi.dwSize.Y, { 0, 0 }, &count); assert(ok); } ok = SetConsoleCursorPosition(hOutput, { 0, 0 }); assert(ok); currentPrompt.clear(); } void WinConsole::outputHistory() { for (size_t i = model.inputHistory.size(); i--; ) { std::cout << toUtf8String(model.inputHistory[i]) << std::endl; } } bool WinConsole::log(const std::string& filename, logstyle logstyle) { return rdbuf->log(filename, logstyle); } #endif } // namespace sdk-10.11.0/src/win32/consolewaiter.cpp000066400000000000000000000045431516266226600175700ustar00rootroot00000000000000/** * @file win32/consolewaiter.cpp * @brief Win32 event/timeout handling, listens for console input * * (c) 2013-2016 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "megaconsolewaiter.h" #include "megaconsole.h" namespace mega { WinConsoleWaiter::WinConsoleWaiter(WinConsole* con) #ifdef NO_READLINE : console(con) #endif { #ifndef NO_READLINE DWORD dwMode; hInput = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(hInput, &dwMode); SetConsoleMode(hInput, dwMode & ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT)); FlushConsoleInputBuffer(hInput); #endif } // wait for events (socket, I/O completion, timeout + application events) // ds specifies the maximum amount of time to wait in deciseconds (or // NEVER if no timeout scheduled) int WinConsoleWaiter::wait() { int r; #ifdef NO_READLINE if (console) { addhandle(console->inputAvailableHandle(), HAVESTDIN); } #else addhandle(hInput, 0); #endif // aggregated wait r = WinWaiter::wait(); // is it a network- or filesystem-triggered wakeup? if (r) { #ifdef NO_READLINE if (console) { // don't let console processing be locked out when the SDK core is busy if (WAIT_OBJECT_0 == WaitForSingleObjectEx(console->inputAvailableHandle(), 0, FALSE)) { r |= HAVESTDIN; } } #endif return r; } #ifdef NO_READLINE if (console && console->consolePeek()) { return HAVESTDIN; } #else // FIXME: improve this gruesome nonblocking console read-simulating kludge if (_kbhit()) { return HAVESTDIN; } // this assumes that the user isn't typing too fast INPUT_RECORD ir[1024]; DWORD dwNum; ReadConsoleInput(hInput, ir, 1024, &dwNum); #endif return 0; } } // namespace sdk-10.11.0/src/win32/drivenotifywin.cpp000066400000000000000000000233361516266226600177730ustar00rootroot00000000000000/** * @file win32/drivenotifywin.cpp * @brief Mega SDK various utilities and helper classes * * (c) 2013-2020 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifdef USE_DRIVE_NOTIFICATIONS #include #include #include #include // ComPtr #include #include "mega/drivenotify.h" using namespace std; using namespace Microsoft::WRL; // ComPtr namespace mega { // Class containing COM initialization code, and common property reading code for WMI. // Not really useful on its own. class WinWmi { public: // Remember to call CoUninitialize() if this succeeded. static bool InitializeCom(); // Remember to call (*ppLocator)->Release() and (*ppService)->Release() if this succeeded. static bool GetWbemService(IWbemLocator** ppLocator, IWbemServices** ppService); static DriveInfo GetVolumeProperties(IWbemClassObject* pQueryObject); static uint32_t GetUi32Property(IWbemClassObject* pQueryObject, const wstring& name); static wstring GetStringProperty(IWbemClassObject* pQueryObject, const wstring& name); }; // // DriveNotifyWin ///////////////////////////////////////////// void DriveNotifyWin::doInThread() { // init com if (!WinWmi::InitializeCom()) return; // must be done in the context of this thread IWbemLocator* pLocator = nullptr; IWbemServices* pService = nullptr; if (!WinWmi::GetWbemService(&pLocator, &pService)) { CoUninitialize(); return; } // BSTR is wchar_t*. Use the latter to avoid including even more obscure headers. wchar_t foolBstrWql[] = L"WQL"; wchar_t* bstrWql = foolBstrWql; // avoid compiler warning // build query wchar_t foolBstrQuery[] = L"Select * From __InstanceOperationEvent " L" Within 3 Where" L" TargetInstance isa 'Win32_LogicalDisk'" L" and (__CLASS='__InstanceCreationEvent'" // added a drive L" or __CLASS='__InstanceDeletionEvent')"; // removed a drive wchar_t* bstrQuery = foolBstrQuery; // avoid compiler warning // run query IEnumWbemClassObject* pEnumerator = nullptr; HRESULT result = pService->ExecNotificationQuery( bstrWql, // strQueryLanguage bstrQuery, // strQuery WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, // lFlags nullptr, // pCtx &pEnumerator); // ppEnum if (FAILED(result) || !pEnumerator) { pService->Release(); pLocator->Release(); CoUninitialize(); return; } // fetch results IWbemClassObject* pQueryObject = nullptr; // keep it outside the loop, to *not* be initialized every time ULONG returnedObjectCount = 0; while (pEnumerator && !shouldStop()) { // poll for one event at a time result = pEnumerator->Next(500 /*ms*/, 1, &pQueryObject, &returnedObjectCount); if (!returnedObjectCount) continue; // no event so far // get event properties VARIANT evPropVariant; VariantInit(&evPropVariant); CIMTYPE propType = CIM_ILLEGAL; long propFlavor = 0; // determine the event type const wstring& eventClass = WinWmi::GetStringProperty(pQueryObject, L"__CLASS"); if (eventClass.empty()) continue; // ignore any errors EventType eventType = eventClass == L"__InstanceCreationEvent" ? DRIVE_CONNECTED_EVENT : eventClass == L"__InstanceDeletionEvent" ? DRIVE_DISCONNECTED_EVENT : UNKNOWN_EVENT; if (eventType == UNKNOWN_EVENT) continue; // ignore // get the object containing the reference to the drive properties object result = pQueryObject->Get(L"TargetInstance", 0, &evPropVariant, &propType, &propFlavor); if (FAILED(result)) continue; // ignore any errors if (propType != CIM_OBJECT || evPropVariant.vt != VT_UNKNOWN) { VariantClear(&evPropVariant); continue; } // get the object containing drive properties IUnknown* targetInst = evPropVariant.punkVal; ComPtr pDriveInfo; result = targetInst->QueryInterface(IID_IWbemClassObject, (void**)pDriveInfo.GetAddressOf()); if (FAILED(result)) { VariantClear(&evPropVariant); continue; } // get drive properties DriveInfo p = WinWmi::GetVolumeProperties(pDriveInfo.Get()); p.connected = eventType == DRIVE_CONNECTED_EVENT; add(std::move(p)); VariantClear(&evPropVariant); } if (pQueryObject) pQueryObject->Release(); if (pEnumerator) pEnumerator->Release(); pService->Release(); pLocator->Release(); CoUninitialize(); } // // WinWmi ///////////////////////////////////////////// DriveInfo WinWmi::GetVolumeProperties(IWbemClassObject* pQueryObject) { DriveInfo v; // DeviceID // string v.mountPoint = GetStringProperty(pQueryObject, L"DeviceID"); // ProviderName // string v.location = GetStringProperty(pQueryObject, L"ProviderName"); // VolumeSerialNumber // string v.volumeSerialNumber = GetStringProperty(pQueryObject, L"VolumeSerialNumber"); // Size // string (yup, string representation of a large number) v.size = GetStringProperty(pQueryObject, L"Size"); // Description // string v.description = GetStringProperty(pQueryObject, L"Description"); // DriveType // uint32 v.driveType = GetUi32Property(pQueryObject, L"DriveType"); // MediaType // uint32 v.mediaType = GetUi32Property(pQueryObject, L"MediaType"); return v; } wstring WinWmi::GetStringProperty(IWbemClassObject* pQueryObject, const wstring& name) { VARIANT propVariant; VariantInit(&propVariant); wstring propStr; HRESULT result = pQueryObject->Get(name.c_str(), 0, &propVariant, nullptr, nullptr); if (FAILED(result)) return propStr; if (propVariant.vt != VT_NULL) { propStr = propVariant.bstrVal; VariantClear(&propVariant); } return propStr; } uint32_t WinWmi::GetUi32Property(IWbemClassObject* pQueryObject, const wstring& name) { VARIANT propVariant; VariantInit(&propVariant); HRESULT result = pQueryObject->Get(name.c_str(), 0, &propVariant, nullptr, nullptr); if (FAILED(result)) return 0; uint32_t prop = propVariant.vt == VT_NULL ? 0 : propVariant.uintVal; VariantClear(&propVariant); return prop; } bool WinWmi::InitializeCom() { HRESULT result = CoInitializeEx(0, COINIT_APARTMENTTHREADED); if (FAILED(result)) return false; result = CoInitializeSecurity( nullptr, // pSecDesc -1, // cAuthSvc (COM authentication) nullptr, // asAuthSvc nullptr, // pReserved1 RPC_C_AUTHN_LEVEL_DEFAULT, // dwAuthnLevel RPC_C_IMP_LEVEL_IMPERSONATE, // dwImpLevel nullptr, // pAuthList EOAC_NONE, // dwCapabilities nullptr // Reserved ); if (FAILED(result) && result != RPC_E_TOO_LATE) { CoUninitialize(); return false; } return true; } bool WinWmi::GetWbemService(IWbemLocator** ppLocator, IWbemServices** ppService) { HRESULT result = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, reinterpret_cast(ppLocator)); if (FAILED(result)) return false; // BSTR is wchar_t*. Use the latter to avoid including even more obscure headers. wchar_t foolBstr[] = L"ROOT\\CIMV2"; wchar_t* bstr = foolBstr; // avoid compiler warning result = (*ppLocator)->ConnectServer( bstr, // strNetworkResource nullptr, // strUser nullptr, // strPassword nullptr, // strLocale 0, // lSecurityFlags nullptr, // strAuthority nullptr, // pCtx ppService // ppNamespace ); if (FAILED(result)) { (*ppLocator)->Release(); return false; } result = CoSetProxyBlanket( *ppService, // pProxy RPC_C_AUTHN_WINNT, // dwAuthnSvc RPC_C_AUTHZ_NONE, // dwAuthzSvc nullptr, // pServerPrincName RPC_C_AUTHN_LEVEL_CALL, // dwAuthnLevel RPC_C_IMP_LEVEL_IMPERSONATE, // dwImpLevel nullptr, // pAuthInfo EOAC_NONE // dwCapabilities ); if (FAILED(result)) { (*ppService)->Release(); (*ppLocator)->Release(); return false; } return true; } } // namespace #endif // USE_DRIVE_NOTIFICATIONS sdk-10.11.0/src/win32/fs.cpp000066400000000000000000002226101516266226600153170ustar00rootroot00000000000000/** * @file win32/fs.cpp * @brief Win32 filesystem/directory access/notification * * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include #include #include #include "mega.h" #include #include #if defined(_WIN32) #include #endif namespace mega { class ScopedFileHandle { public: ScopedFileHandle() : mHandle(INVALID_HANDLE_VALUE) { } ScopedFileHandle(HANDLE handle) : mHandle(handle) { } MEGA_DISABLE_COPY(ScopedFileHandle); ScopedFileHandle(ScopedFileHandle&& other) : mHandle(other.mHandle) { other.mHandle = INVALID_HANDLE_VALUE; } ~ScopedFileHandle() { if (mHandle != INVALID_HANDLE_VALUE) CloseHandle(mHandle); } ScopedFileHandle& operator=(ScopedFileHandle&& rhs) { using std::swap; ScopedFileHandle temp(std::move(rhs)); swap(*this, temp); return *this; } operator bool() const { return mHandle != INVALID_HANDLE_VALUE; } HANDLE get() const { return mHandle; } void reset(HANDLE handle) { operator=(ScopedFileHandle(handle)); } void reset() { operator=(ScopedFileHandle()); } private: HANDLE mHandle; }; void FileSystemAccess::setMinimumDirectoryPermissions(int) { } void FileSystemAccess::setMinimumFilePermissions(int) { } int FileSystemAccess::isFileHidden(const LocalPath& path, FSLogging logWhen) { // Try and determine the file's current attributes. auto attributes = GetFileAttributesW(path.asPlatformEncoded(false).c_str()); // Successfully retrieved the file's attributes. if (attributes != INVALID_FILE_ATTRIBUTES) return (attributes & FILE_ATTRIBUTE_HIDDEN) > 0; // Why couldn't we get the file's attributes? auto error = GetLastError(); // Log the error, if necessary. if (logWhen.doLog(error)) { LOG_warn << "Unable to retrieve file attributes: path: " << path << ", error code: " << error << ", error message: " << getErrorMessage(error); } // Couldn't retrieve the file's attributes. return -1; } bool FileSystemAccess::setFileHidden(const LocalPath& path, FSLogging logWhen) { // Try and retrieve the file's current attributes. auto pathStr{path.asPlatformEncoded(false)}; auto attributes = GetFileAttributesW(pathStr.c_str()); // File's already marked as hidden. if ((attributes & FILE_ATTRIBUTE_HIDDEN)) return true; // File's now marked as hidden. if (attributes != INVALID_FILE_ATTRIBUTES && SetFileAttributesW(pathStr.c_str(), attributes | FILE_ATTRIBUTE_HIDDEN)) return true; // Why couldn't we get (or set) the file's attributes? auto error = GetLastError(); // Log error, if necessary. if (logWhen.doLog(error)) { LOG_warn << "Unable to set file attributes: path: " << path << ", error code: " << error << ", error message: " << getErrorMessage(error); } // Couldn't set the file's hidden attribute. return false; } int platformCompareUtf(const string& p1, bool unescape1, const string& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, true); } int platformCompareUtf(const string& p1, bool unescape1, const LocalPath& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, true); } int platformCompareUtf(const LocalPath& p1, bool unescape1, const string& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, true); } int platformCompareUtf(const LocalPath& p1, bool unescape1, const LocalPath& p2, bool unescape2) { return compareUtf(p1, unescape1, p2, unescape2, true); } WinFileAccess::WinFileAccess(Waiter *w) : FileAccess(w) { hFile = INVALID_HANDLE_VALUE; hFind = INVALID_HANDLE_VALUE; fsidvalid = false; } WinFileAccess::~WinFileAccess() { fclose(); } bool WinFileAccess::setSparse() { // Can't set a file as sparse if it isn't open. if (hFile == INVALID_HANDLE_VALUE) return false; // Try and mark the file as a sparse file. return DeviceIoControl(hFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, nullptr, nullptr); } auto WinFileAccess::getFileSize() const -> std::optional> { // File isn't open. if (hFile == INVALID_HANDLE_VALUE) return std::nullopt; // Retrieve information about this file. // // buffer specifies where this information should be stored. // kind specifies what kind of information we're interested in. // // returns false if we couldn't retrieve the information. auto getInfo = [this](auto& buffer, auto kind) { if (GetFileInformationByHandleEx(hFile, kind, &buffer, sizeof(buffer))) return true; auto error = GetLastError(); LOG_err << "GetFileInformationByHandleEx failed. Error: " << error; return false; }; // getInfo FILE_COMPRESSION_INFO compression; // Couldn't retrieve the file's compression info. if (!getInfo(compression, FileCompressionInfo)) return std::nullopt; FILE_STANDARD_INFO standard; // Couldn't retrieve the file's standard info. if (!getInfo(standard, FileStandardInfo)) return std::nullopt; // These casts prevent us having to assemble a u64 value by hand. auto allocatedSize = static_cast(compression.CompressedFileSize.QuadPart); auto reportedSize = static_cast(standard.EndOfFile.QuadPart); return std::make_pair(allocatedSize, reportedSize); } bool WinFileAccess::sysread(void* buffer, unsigned long length, m_off_t offset, bool* cretry) { // Sanity. assert(buffer || !length); assert(hFile != INVALID_HANDLE_VALUE); // Keeps logic simple. if (!cretry) cretry = &retry; // Convenience. auto length_ = static_cast(length); auto offset_ = static_cast(offset); // So we can read from a particular offset without races. OVERLAPPED overlapped{}; // Specify where we should read from. overlapped.Offset = offset_ & UINT32_MAX; overlapped.OffsetHigh = offset_ >> 32; // Tracks how many bytes we were able to read. DWORD numRead{}; // Assume the read fails due to a persistent error. *cretry = false; // Couldn't perform read. if (!ReadFile(hFile, buffer, length_, &numRead, &overlapped)) { // Latch the error. auto error = GetLastError(); errorcode = static_cast(error); // Let the caller know if it's worth retrying the read. *cretry = WinFileSystemAccess::istransient(error); // Leave a trail of what went wrong. LOG_err << "ReadFile failed. Error: " << error; // Let the caller know the read failed. return false; } // Read was successful but didn't get everything we asked for. if (length != numRead) { errorcode = ERROR_HANDLE_EOF; // Leave a trail. LOG_err << "ReadFile failed (dwRead) " << numRead << " - " << length; // Let the caller know the read failed. return false; } // Read was successful. return true; } void WinFileAccess::fclose() { if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); assert(hFind == INVALID_HANDLE_VALUE); } else if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); } hFile = INVALID_HANDLE_VALUE; hFind = INVALID_HANDLE_VALUE; } bool WinFileAccess::fwrite(const void* buffer, unsigned long length, m_off_t offset, unsigned long* numWritten, bool* cretry) { // Sanity. assert(buffer || !length); assert(offset >= 0); // Keeps logic simple. if (!cretry) cretry = &retry; auto numWritten_ = 0ul; if (!numWritten) numWritten = &numWritten_; // Convenience. auto offset_ = static_cast(offset); // So we can explicitly specify where the write should begin. OVERLAPPED overlapped{}; // Specify where the write should begin. overlapped.Offset = offset_ & UINT32_MAX; overlapped.OffsetHigh = offset_ >> 32; // Assume the write fails due to a persistent error. *cretry = false; // Couldn't perform the write. if (!WriteFile(hFile, buffer, length, numWritten, &overlapped)) { // Latch error. auto error = GetLastError(); // Should the caller retry the write? *cretry = WinFileSystemAccess::istransient(error); // Leave a trail. LOG_err << "WriteFile failed. Error: " << error; // Let the caller know the write failed. return false; } // Write succeeded but didn't write everything we specified. if (length != *numWritten) { // Leave a trail. LOG_err << "WriteFile failed (dwWritten) " << *numWritten << " - " << length; // Let the caller know the write failed. return false; } // Couldn't flush write to storage. if (!FlushFileBuffers(hFile)) { // Latch error. auto error = GetLastError(); // Should the caller retry the write? *cretry = WinFileSystemAccess::istransient(error); // Trail. LOG_err << "FlushFileBuffers failed. Error: " << error; // Let the caller know the write failed. return false; } // Let the caller know the write was successful. return true; } bool WinFileAccess::ftruncate(m_off_t newSize) { assert(newSize >= 0); auto& position = reinterpret_cast(newSize); // Set the file pointer to the start of the file. if (SetFilePointerEx(hFile, position, nullptr, FILE_BEGIN)) { // Truncate the file. if (SetEndOfFile(hFile)) { return true; } } // Why couldn't we truncate the file? auto error = GetLastError(); // Latch the error. errorcode = error; // Is it a transient error? retry = WinFileSystemAccess::istransient(error); return false; } m_time_t FileTime_to_POSIX(FILETIME* ft) { LARGE_INTEGER date; date.HighPart = ft->dwHighDateTime; date.LowPart = ft->dwLowDateTime; // remove the diff between 1970 and 1601 and convert back from 100-nanoseconds to seconds int64_t t = date.QuadPart - 11644473600000 * 10000; // clamp if (t < 0) return 0; t /= 10000000; FileSystemAccess::captimestamp(&t); return t; } bool WinFileAccess::fstat(m_time_t& modified, m_off_t& fileSize) { BY_HANDLE_FILE_INFORMATION info; // Try and retrieve information the currently open file. if (!GetFileInformationByHandle(hFile, &info)) { // Couldn't get information about the file. auto error = GetLastError(); // Latch the error. errorcode = error; // Was the error transient? retry = WinFileSystemAccess::istransient(error); // Let the caller know we couldn't get the file's info. return false; } LARGE_INTEGER temp; temp.LowPart = info.nFileSizeLow; temp.HighPart = info.nFileSizeHigh; modified = FileTime_to_POSIX(&info.ftLastWriteTime); fileSize = temp.QuadPart; // Let the caller know we've retrieved the file's info. return true; } bool WinFileAccess::sysstat(m_time_t* modificationTime, m_off_t* fileSize, FSLogging fsl) { assert(!nonblocking_localname.empty()); WIN32_FILE_ATTRIBUTE_DATA fad; type = TYPE_UNKNOWN; if (!GetFileAttributesExW(nonblocking_localname.asPlatformEncoded(false).c_str(), GetFileExInfoStandard, (LPVOID)&fad)) { DWORD e = GetLastError(); if (fsl.doLog(e)) { LOG_warn << "Unable to stat: GetFileAttributesExW('" << nonblocking_localname << "'): error code: " << e << ": " << WinFileSystemAccess::getErrorMessage(e); } errorcode = e; retry = WinFileSystemAccess::istransient(e); return false; } errorcode = 0; if (SimpleLogger::getLogLevel() >= logDebug && skipattributes(fad.dwFileAttributes)) { LOG_debug << "Incompatible attributes (" << fad.dwFileAttributes << ") for file " << nonblocking_localname; } if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { type = FOLDERNODE; retry = false; return false; } retry = false; type = FILENODE; *modificationTime = FileTime_to_POSIX(&fad.ftLastWriteTime); *fileSize = ((m_off_t)fad.nFileSizeHigh << 32) + (m_off_t)fad.nFileSizeLow; return true; } bool WinFileAccess::sysopen(bool async, FSLogging fsl) { assert(hFile == INVALID_HANDLE_VALUE); assert(!nonblocking_localname.empty()); if (hFile != INVALID_HANDLE_VALUE) { sysclose(); } hFile = CreateFileW(nonblocking_localname.asPlatformEncoded(false).c_str(), GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, async ? FILE_FLAG_OVERLAPPED : 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD e = GetLastError(); errorcode = e; if (fsl.doLog(errorcode)) { LOG_err << "Unable to open file '" << nonblocking_localname << "': (CreateFileW). Error code: " << e << ": " << WinFileSystemAccess::getErrorMessage(e); } retry = WinFileSystemAccess::istransient(e); return false; } return true; } void WinFileAccess::sysclose() { assert(!nonblocking_localname.empty()); assert(hFile != INVALID_HANDLE_VALUE); if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } } WinAsyncIOContext::WinAsyncIOContext() : AsyncIOContext() { overlapped = NULL; } WinAsyncIOContext::~WinAsyncIOContext() { LOG_verbose << "Deleting WinAsyncIOContext"; finish(); } void WinAsyncIOContext::finish() { if (overlapped) { if (!finished) { LOG_debug << "Synchronously waiting for async operation"; AsyncIOContext::finish(); } delete overlapped; overlapped = NULL; } assert(finished); } AsyncIOContext *WinFileAccess::newasynccontext() { return new WinAsyncIOContext(); } VOID WinFileAccess::asyncopfinished(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { WinAsyncIOContext *context = (WinAsyncIOContext *)(lpOverlapped->hEvent); context->failed = dwErrorCode || dwNumberOfBytesTransfered != context->dataBufferLen; if (!context->failed) { if (context->op == AsyncIOContext::READ) { memset(context->dataBuffer + context->dataBufferLen, 0, context->pad); LOG_verbose << "Async read finished OK"; } else { LOG_verbose << "Async write finished OK"; } } else { LOG_warn << "Async operation finished with error: " << dwErrorCode; } context->retry = WinFileSystemAccess::istransient(dwErrorCode); context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } } bool WinFileAccess::asyncavailable() { return true; } void WinFileAccess::asyncsysopen(AsyncIOContext *context) { const auto flag = AsyncIOContext::toOpenFlag(context->access); context->failed = !fopen_impl(context->openPath, flag, FSLogging::logOnError, true, nullptr, false, false, nullptr); context->retry = retry; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } } void WinFileAccess::asyncsysread(AsyncIOContext *context) { if (!context) { return; } WinAsyncIOContext *winContext = dynamic_cast(context); if (!winContext) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } return; } OVERLAPPED *overlapped = new OVERLAPPED; memset(overlapped, 0, sizeof (OVERLAPPED)); overlapped->Offset = winContext->posOfBuffer & 0xFFFFFFFF; overlapped->OffsetHigh = (winContext->posOfBuffer >> 32) & 0xFFFFFFFF; overlapped->hEvent = winContext; winContext->overlapped = overlapped; if (!ReadFileEx(hFile, winContext->dataBuffer, (DWORD)winContext->dataBufferLen, overlapped, asyncopfinished)) { DWORD e = GetLastError(); winContext->retry = WinFileSystemAccess::istransient(e); winContext->failed = true; winContext->finished = true; winContext->overlapped = NULL; delete overlapped; LOG_warn << "Async read failed at startup: " << e; if (winContext->userCallback) { winContext->userCallback(winContext->userData); } } } void WinFileAccess::asyncsyswrite(AsyncIOContext *context) { if (!context) { return; } WinAsyncIOContext *winContext = dynamic_cast(context); if (!winContext) { context->failed = true; context->retry = false; context->finished = true; if (context->userCallback) { context->userCallback(context->userData); } return; } OVERLAPPED *overlapped = new OVERLAPPED; memset(overlapped, 0, sizeof (OVERLAPPED)); overlapped->Offset = winContext->posOfBuffer & 0xFFFFFFFF; overlapped->OffsetHigh = (winContext->posOfBuffer >> 32) & 0xFFFFFFFF; overlapped->hEvent = winContext; winContext->overlapped = overlapped; if (!WriteFileEx(hFile, winContext->dataBuffer, (DWORD)winContext->dataBufferLen, overlapped, asyncopfinished)) { DWORD e = GetLastError(); winContext->retry = WinFileSystemAccess::istransient(e); winContext->failed = true; winContext->finished = true; winContext->overlapped = NULL; delete overlapped; LOG_warn << "Async write failed at startup: " << e; if (winContext->userCallback) { winContext->userCallback(winContext->userData); } } } // update local name void WinFileAccess::updatelocalname(const LocalPath& name, bool force) { if (force || !nonblocking_localname.empty()) { nonblocking_localname = name; } } // true if attribute set should not be considered for syncing // (SYSTEM files are only synced if they are not HIDDEN) bool WinFileAccess::skipattributes(DWORD dwAttributes) { return (dwAttributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_OFFLINE)) || (dwAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) == (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN); } // emulates Linux open-directory-as-file semantics // FIXME #1: How to open files and directories with a single atomic // CreateFile() operation without first looking at the attributes? // FIXME #2: How to convert a CreateFile()-opened directory directly to a hFind // without doing a FindFirstFile()? bool WinFileAccess::fopen(const LocalPath& name, OpenFlag flag, FSLogging fsl, DirAccess* iteratingDir, bool ignoreAttributes, bool skipcasecheck, LocalPath* actualLeafNameIfDifferent) { fopenSucceeded = fopen_impl(name, flag, fsl, false, iteratingDir, ignoreAttributes, skipcasecheck, actualLeafNameIfDifferent, false); return fopenSucceeded; } bool WinFileAccess::fopenForMacRead(const LocalPath& path, FSLogging fsl) { // Open with FILE_SHARE_DELETE to allow the file to be moved/deleted while we're computing MAC fopenSucceeded = fopen_impl(path, OpenFlag::OPEN_RDONLY, fsl, false, nullptr, false, false, nullptr, true); return fopenSucceeded; } bool WinFileAccess::fopen_impl(const LocalPath& namePath, OpenFlag flag, FSLogging fsl, bool async, DirAccess* iteratingDir, bool ignoreAttributes, bool skipcasecheck, LocalPath* actualLeafNameIfDifferent, bool shareDelete) { WIN32_FIND_DATA fad = { 0 }; assert(hFile == INVALID_HANDLE_VALUE); BY_HANDLE_FILE_INFORMATION bhfi = { 0 }; const bool read = openRead(flag); const bool write = openWrite(flag); if (write) { type = FILENODE; } else { // fill in the `fad` file attribute data in the most efficient way available for its case if (iteratingDir) { fad = static_cast(iteratingDir)->currentItemAttributes; } else { auto namePathStr{namePath.asPlatformEncoded(false)}; HANDLE h = namePathStr.size() > 1 ? FindFirstFileExW(namePathStr.c_str(), FindExInfoStandard, &fad, FindExSearchNameMatch, NULL, 0) : INVALID_HANDLE_VALUE; if (h != INVALID_HANDLE_VALUE) { // success so `fad` is set FindClose(h); } else { WIN32_FILE_ATTRIBUTE_DATA fatd; if (!GetFileAttributesExW(namePathStr.c_str(), GetFileExInfoStandard, (LPVOID)&fatd)) { DWORD e = GetLastError(); // this is an expected case so no need to log. the FindFirstFileEx did not find the file, // GetFileAttributesEx is only expected to find it if it's a network share point // LOG_debug << "Unable to get the attributes of the file. Error code: " << e; retry = WinFileSystemAccess::istransient(e); return false; } else { LOG_debug << "Possible root of network share"; skipcasecheck = true; fad.dwFileAttributes = fatd.dwFileAttributes; fad.ftCreationTime = fatd.ftCreationTime; fad.ftLastAccessTime = fatd.ftLastAccessTime; fad.ftLastWriteTime = fatd.ftLastWriteTime; fad.nFileSizeHigh = fatd.nFileSizeHigh; fad.nFileSizeLow = fatd.nFileSizeLow; } } } if (actualLeafNameIfDifferent) { auto actualFilename = LocalPath::fromPlatformEncodedRelative(wstring(fad.cFileName)); if (actualFilename != namePath.leafName()) { *actualLeafNameIfDifferent = std::move(actualFilename); } } if (!skipcasecheck) { LocalPath filename = namePath.leafName(); if (const auto filenameStr = filename.asPlatformEncoded(false); filenameStr != std::wstring(fad.cFileName) && filenameStr != std::wstring(fad.cAlternateFileName) && filenameStr != L"." && filenameStr != L"..") { LOG_warn << "fopen failed due to invalid case: '" << filename << "' vs '" << LocalPath::fromPlatformEncodedRelative(wstring(fad.cFileName)) << "'"; retry = false; return false; } } // ignore symlinks - they would otherwise be treated as moves // also, ignore some other obscure filesystem object categories if (!ignoreAttributes && skipattributes(fad.dwFileAttributes)) { if (SimpleLogger::getLogLevel() >= logDebug) { LOG_debug << "Excluded: " << namePath << " Attributes: " << fad.dwFileAttributes; } retry = false; return false; } type = (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FOLDERNODE : FILENODE; if (fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { if (WinFileSystemAccess::checkForSymlink(namePath)) { mIsSymLink = true; } } } auto desiredAccess = 0u; // Caller's interested in reading. if (read) desiredAccess |= GENERIC_READ; // Caller's interested in writing. if (write) desiredAccess |= GENERIC_WRITE; // (race condition between GetFileAttributesEx()/FindFirstFile() possible - // fixable with the current Win32 API?) DWORD shareMode = FILE_SHARE_WRITE | FILE_SHARE_READ; if (shareDelete) { shareMode |= FILE_SHARE_DELETE; // Allow file to be moved/deleted while open (used for MAC // computation) } hFile = CreateFileW(namePath.asPlatformEncoded(false).c_str(), desiredAccess, shareMode, NULL, !write ? OPEN_EXISTING : OPEN_ALWAYS, (type == FOLDERNODE) ? FILE_FLAG_BACKUP_SEMANTICS : (async ? FILE_FLAG_OVERLAPPED : 0), NULL); // FIXME: verify that keeping the directory opened quashes the possibility // of a race condition between CreateFile() and FindFirstFile() if (hFile == INVALID_HANDLE_VALUE) { DWORD e = GetLastError(); if (fsl.doLog(e)) { LOG_err << "Unable to open file. '" << namePath << "' error code : " << e << " : " << WinFileSystemAccess::getErrorMessage(e); } errorcode = e; retry = WinFileSystemAccess::istransient(e); return false; } mtime = FileTime_to_POSIX(&fad.ftLastWriteTime); if (!write) { fsidvalid = GetFileInformationByHandle(hFile, &bhfi) != FALSE; if (fsidvalid) fsid = ((handle)bhfi.nFileIndexHigh << 32) | (handle)bhfi.nFileIndexLow; } if (type == FOLDERNODE) { LocalPath withStar = namePath; withStar.appendWithSeparator(LocalPath::fromPlatformEncodedRelative(L"*"), true); hFind = FindFirstFileW(withStar.asPlatformEncoded(false).c_str(), &ffd); if (hFind == INVALID_HANDLE_VALUE) { DWORD e = GetLastError(); LOG_debug << "Unable to open folder. Error code: " << e; retry = WinFileSystemAccess::istransient(e); return false; } CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; retry = false; return true; } if (!write) { size = ((m_off_t)fad.nFileSizeHigh << 32) + (m_off_t)fad.nFileSizeLow; } return true; } WinFileSystemAccess::WinFileSystemAccess() { } WinFileSystemAccess::~WinFileSystemAccess() { #ifdef ENABLE_SYNC assert(!dirnotifys.size()); #endif } bool WinFileSystemAccess::cwd(LocalPath& path) const { DWORD nRequired = GetCurrentDirectoryW(0, nullptr); if (!nRequired) { return false; } wstring buf; buf.resize(nRequired); DWORD nWritten = GetCurrentDirectoryW(nRequired, &buf[0]); while (!buf.empty() && buf.back() == 0) { buf.pop_back(); } path = LocalPath::fromPlatformEncodedAbsolute(std::move(buf)); return nWritten > 0; } bool WinFileSystemAccess::hardLink(const LocalPath& source, const LocalPath& target) { if (!CreateHardLinkW(target.asPlatformEncoded(false).c_str(), source.asPlatformEncoded(false).c_str(), nullptr)) { LOG_warn << "Unable to create hard link from " << source << " to " << target << ". Error code was: " << GetLastError(); return false; } return true; } bool WinFileSystemAccess::istransient(DWORD e) { return e == ERROR_TOO_MANY_OPEN_FILES || e == ERROR_NOT_ENOUGH_MEMORY || e == ERROR_OUTOFMEMORY || e == ERROR_LOCK_VIOLATION || e == ERROR_SHARING_VIOLATION; } bool WinFileSystemAccess::istransientorexists(DWORD e) { target_exists = e == ERROR_FILE_EXISTS || e == ERROR_ALREADY_EXISTS; return istransient(e); } void WinFileSystemAccess::addevents(Waiter*, int) {} // write short name of the last path component to sname bool WinFileSystemAccess::getsname(const LocalPath& namePath, LocalPath& snamePath) const { assert(namePath.isAbsolute()); const std::wstring& name = namePath.asPlatformEncoded(false); std::wstring sname = snamePath.asPlatformEncoded(false); DWORD r = DWORD(name.size()); sname.resize(r); DWORD rr = GetShortPathNameW(name.data(), const_cast(sname.data()), r); sname.resize(rr); if (rr >= r) { rr = GetShortPathNameW(name.data(), const_cast(sname.data()), rr); sname.resize(rr); } if (!rr) { DWORD e = GetLastError(); LOG_warn << "Unable to get short path name: " << namePath << ". Error code: " << e; snamePath.clear(); return false; } // we are only interested in the path's last component wchar_t* ptr; ptr = wcsrchr(const_cast(sname.data()), L'\\'); if (!ptr) { ptr = wcsrchr(const_cast(sname.data()), L':'); } if (ptr) { sname.erase(0, ptr - sname.data() + 1); } snamePath = LocalPath::fromPlatformEncodedRelative(std::move(sname)); return !snamePath.empty(); } // FIXME: if a folder rename fails because the target exists, do a top-down // recursive copy/delete bool WinFileSystemAccess::renamelocal(const LocalPath& oldnamePath, const LocalPath& newnamePath, bool replace) { assert(oldnamePath.isAbsolute()); assert(newnamePath.isAbsolute()); bool r = !!MoveFileExW(oldnamePath.asPlatformEncoded(false).c_str(), newnamePath.asPlatformEncoded(false).c_str(), replace ? MOVEFILE_REPLACE_EXISTING : 0); if (!r) { DWORD e = GetLastError(); target_name_too_long = isPathError(e) && exists(oldnamePath) && exists(newnamePath.parentPath()); transient_error = istransientorexists(e); if (!target_exists || !skip_targetexists_errorreport) { LOG_warn << "Unable to move file: " << oldnamePath << " to " << newnamePath << ". Error code: " << e; } } return r; } bool WinFileSystemAccess::copylocal(const LocalPath& oldnamePath, const LocalPath& newnamePath, m_time_t) { assert(oldnamePath.isAbsolute()); assert(newnamePath.isAbsolute()); bool r = !!CopyFileW(oldnamePath.asPlatformEncoded(false).c_str(), newnamePath.asPlatformEncoded(false).c_str(), FALSE); if (!r) { DWORD e = GetLastError(); LOG_debug << "Unable to copy file. Error code: " << e; target_name_too_long = isPathError(e) && exists(oldnamePath) && exists(newnamePath.parentPath()); transient_error = istransientorexists(e); } return r; } bool WinFileSystemAccess::rmdirlocal(const LocalPath& namePath) { assert(namePath.isAbsolute()); bool r = !!RemoveDirectoryW(namePath.asPlatformEncoded(false).data()); if (!r) { DWORD e = GetLastError(); LOG_debug << "Unable to delete folder. Error code: " << e; transient_error = istransient(e); } return r; } bool WinFileSystemAccess::unlinklocal(const LocalPath& namePath) { assert(namePath.isAbsolute()); bool r = !!DeleteFileW(namePath.asPlatformEncoded(false).data()); if (!r) { DWORD e = GetLastError(); LOG_debug << "Unable to delete file. Error code: " << e; transient_error = istransient(e); } return r; } // delete all files and folders contained in the specified folder // (does not recurse into mounted devices) void WinFileSystemAccess::emptydirlocal(const LocalPath& nameParam, dev_t basedev) { assert(nameParam.isAbsolute()); HANDLE hDirectory, hFind; dev_t currentdev; WIN32_FILE_ATTRIBUTE_DATA fad; if (!GetFileAttributesExW(nameParam.asPlatformEncoded(false).c_str(), GetFileExInfoStandard, (LPVOID)&fad) || !(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { return; } hDirectory = CreateFileW(nameParam.asPlatformEncoded(false).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hDirectory == INVALID_HANDLE_VALUE) { // discard not accessible folders return; } BY_HANDLE_FILE_INFORMATION fi; if (!GetFileInformationByHandle(hDirectory, &fi)) { currentdev = 0; } else { currentdev = fi.dwVolumeSerialNumber + 1; } CloseHandle(hDirectory); if (basedev && currentdev != basedev) { // discard folders on different devices return; } LocalPath namePath = nameParam; bool removed; for (;;) { // iterate over children and delete removed = false; WIN32_FIND_DATAW ffd; { auto newPath = namePath; newPath.appendWithSeparator(LocalPath::fromRelativePath("*"), true); hFind = FindFirstFileW(newPath.asPlatformEncoded(false).c_str(), &ffd); } if (hFind == INVALID_HANDLE_VALUE) { break; } bool morefiles = true; while (morefiles) { if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || *ffd.cFileName != '.' || (ffd.cFileName[1] && ((ffd.cFileName[1] != '.') || ffd.cFileName[2])))) { auto newPath = namePath; newPath.appendWithSeparator(LocalPath::fromPlatformEncodedRelative(ffd.cFileName), true); if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { emptydirlocal(newPath, currentdev); removed |= !!RemoveDirectoryW(newPath.asPlatformEncoded(false).c_str()); } else { removed |= !!DeleteFileW(newPath.asPlatformEncoded(false).c_str()); } } morefiles = FindNextFileW(hFind, &ffd); } FindClose(hFind); if (!removed) { break; } } } bool WinFileSystemAccess::mkdirlocal(const LocalPath& namePath, bool hidden, bool logAlreadyExistsError) { assert(namePath.isAbsolute()); const std::wstring& name = namePath.asPlatformEncoded(false); bool r = !!CreateDirectoryW(name.data(), NULL); if (!r) { DWORD e = GetLastError(); target_name_too_long = isPathError(e) && exists(namePath.parentPath()); transient_error = istransientorexists(e); target_exists = e == ERROR_ALREADY_EXISTS; if (!target_exists || logAlreadyExistsError) { LOG_debug << "Unable to create folder. Error code: " << e << " for: " << namePath; } } else if (hidden) { DWORD a = GetFileAttributesW(name.data()); if (a != INVALID_FILE_ATTRIBUTES) { SetFileAttributesW(name.data(), a | FILE_ATTRIBUTE_HIDDEN); } } return r; } std::pair WinFileSystemAccess::getmtimelocal(const LocalPath& namePath) { assert(namePath.isAbsolute()); FILETIME lwt; HANDLE hFile; hFile = CreateFileW(namePath.asPlatformEncoded(false).data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD e = GetLastError(); transient_error = istransient(e); LOG_warn << "Error opening file to get mtime: " << namePath << " error: " << e; return {false, 0}; } if (!GetFileTime(hFile, NULL, NULL, &lwt)) { DWORD e = GetLastError(); transient_error = istransient(e); LOG_warn << "Error getting file mtime: " << namePath << " error: " << e; return {false, 0}; } CloseHandle(hFile); return {true, FileTime_to_POSIX(&lwt)}; } bool WinFileSystemAccess::setmtimelocal(const LocalPath& namePath, m_time_t mtime) { assert(namePath.isAbsolute()); FILETIME lwt; LONGLONG ll; HANDLE hFile; hFile = CreateFileW(namePath.asPlatformEncoded(false).data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD e = GetLastError(); transient_error = istransient(e); LOG_warn << "Error opening file to change mtime: " << e; return false; } ll = (mtime + 11644473600) * 10000000; lwt.dwLowDateTime = (DWORD)ll; lwt.dwHighDateTime = ll >> 32; int r = !!SetFileTime(hFile, NULL, NULL, &lwt); if (!r) { DWORD e = GetLastError(); transient_error = istransient(e); LOG_warn << "Error changing mtime: " << e; } CloseHandle(hFile); return r != 0; } bool WinFileSystemAccess::chdirlocal(LocalPath& namePath) const { assert(namePath.isAbsolute()); int r = SetCurrentDirectoryW(namePath.asPlatformEncoded(false).c_str()); return r != 0; } bool WinFileSystemAccess::expanselocalpath(const LocalPath& pathArg, LocalPath& absolutepathArg) { return expandLocalPathFileSystem(pathArg, absolutepathArg); } bool WinFileSystemAccess::exists(const LocalPath& path) const { auto attributes = GetFileAttributesW(path.asPlatformEncoded(false).c_str()); return attributes != INVALID_FILE_ATTRIBUTES; } bool WinFileSystemAccess::isPathError(DWORD error) const { return error == ERROR_DIRECTORY || error == ERROR_FILE_NOT_FOUND || error == ERROR_FILENAME_EXCED_RANGE || error == ERROR_INVALID_NAME || error == ERROR_PATH_NOT_FOUND; } void WinFileSystemAccess::osversion(string* u, bool includeArchExtraInfo) const { char buf[128]; typedef LONG MEGANTSTATUS; typedef struct _MEGAOSVERSIONINFOW { DWORD dwOSVersionInfoSize; DWORD dwMajorVersion; DWORD dwMinorVersion; DWORD dwBuildNumber; DWORD dwPlatformId; WCHAR szCSDVersion[ 128 ]; // Maintenance string for PSS usage } MEGARTL_OSVERSIONINFOW, *PMEGARTL_OSVERSIONINFOW; typedef MEGANTSTATUS (WINAPI* RtlGetVersionPtr)(PMEGARTL_OSVERSIONINFOW); MEGARTL_OSVERSIONINFOW version = { 0 }; HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); if (hMod) { RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)(void*)GetProcAddress(hMod, "RtlGetVersion"); if (RtlGetVersion) { RtlGetVersion(&version); } } snprintf(buf, sizeof(buf), "Windows %d.%d.%d", version.dwMajorVersion, version.dwMinorVersion, version.dwBuildNumber); if (includeArchExtraInfo) { BOOL isWOW = FALSE; BOOL callSucceeded = IsWow64Process(GetCurrentProcess(), &isWOW); if (callSucceeded && isWOW) { strcat(buf, "/64"); // if the app 32/64 bit matches the OS, then no need to specify the OS separately, so we only need to cover the WOW 32 bit on 64 bit case. } } u->append(buf); } void WinFileSystemAccess::statsid(string *id) const { LONG hr; HKEY hKey = NULL; hr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Cryptography", 0, KEY_QUERY_VALUE #ifdef KEY_WOW64_64KEY | KEY_WOW64_64KEY #else | 0x0100 #endif , &hKey); if (hr == ERROR_SUCCESS) { WCHAR pszData[256]; DWORD cbData = sizeof(pszData); hr = RegQueryValueExW(hKey, L"MachineGuid", NULL, NULL, (LPBYTE)pszData, &cbData); if (hr == ERROR_SUCCESS) { std::wstring localdata(pszData); string utf8data; LocalPath::local2path(&localdata, &utf8data, true); // true because that was the case historically id->append(utf8data); } RegCloseKey(hKey); } } fsfp_t FileSystemAccess::fsFingerprint(const LocalPath& path) const { // Convenience. static auto failed = []() { auto error = GetLastError(); LOG_err << "Unable to determine volume ID: " << getErrorMessage(error); return fsfp_t(); }; // failed // Try and open the specified file. ScopedFileHandle handle = CreateFileW(path.asPlatformEncoded(false).c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); // Couldn't open the specified file. if (!handle) return failed(); BY_HANDLE_FILE_INFORMATION info; // Try and retrieve information about the specified file. if (!GetFileInformationByHandle(handle.get(), &info)) return failed(); // Convert serial number to a string. std::ostringstream ostream; ostream << std::hex << std::setfill('0') << std::setw(16) << info.dwVolumeSerialNumber; // Return ID to caller. return fsfp_t(info.dwVolumeSerialNumber + 1, ostream.str()); } #ifdef ENABLE_SYNC bool WinFileSystemAccess::fsStableIDs(const LocalPath& path) const { TCHAR volume[MAX_PATH + 1]; if (GetVolumePathNameW(path.asPlatformEncoded(false).c_str(), volume, MAX_PATH + 1)) { TCHAR fs[MAX_PATH + 1] = { 0, }; BOOL gotVolInfo = GetVolumeInformation(volume, NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1); if (!gotVolInfo) { // Maybe it's a subst drive (created using something like "subst a: c:\Source" DOS command). // In such cases, the volume path might include additional characters after ":\\", e.g. "C:\\SomeFolder". // To resolve that, we truncate the volume path to end after ":\\" and retry. wchar_t* volSep = wcsstr(volume, L":\\"); if (volSep && *(volSep + 2)) { *(volSep + 2) = L'\0'; // Truncate the volume path gotVolInfo = GetVolumeInformation(volume, NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1); } } if (gotVolInfo) { LOG_info << "Filesystem type: " << LocalPath::fromPlatformEncodedRelative(std::wstring(fs)); return _wcsicmp(fs, L"FAT") && _wcsicmp(fs, L"FAT32") && _wcsicmp(fs, L"exFAT"); } } LOG_err << "Failed to get filesystem type for path: '" << path << "'. Error code: " << GetLastError(); assert(false); return true; } VOID CALLBACK WinDirNotify::completion(DWORD dwErrorCode, DWORD dwBytes, LPOVERLAPPED lpOverlapped) { assert( std::this_thread::get_id() == smNotifierThread->get_id()); WinDirNotify *dirnotify = (WinDirNotify*)lpOverlapped->hEvent; if (!dirnotify->mOverlappedExit && dwErrorCode != ERROR_OPERATION_ABORTED) { dirnotify->process(dwBytes); } else { dirnotify->mOverlappedEnabled = false; } } void WinDirNotify::process(DWORD bytesTransferred) { assert( std::this_thread::get_id() == smNotifierThread->get_id()); if (!bytesTransferred) { // No bytes delivered indicates the OS could not deliver some notifications. // Maybe it ran out of buffer (maybe we were too slow) // Incrementing mErrorCount will cause a full rescan of the sync // We used to send an additional notification with localnode and empty path to // trigger it but that is not needed anymore int errCount = ++mErrorCount; LOG_err << "Empty filesystem notification: " << (localrootnode ? localrootnode->localname.asPlatformEncoded(false).c_str() : L"NULL") << " errors: " << errCount; // reissue request for notifications readchanges(); } else { assert(bytesTransferred >= offsetof(FILE_NOTIFY_INFORMATION, FileName)); // 3 uint32_t. The filename can be entirely absent, with the // filename length field 0 (via samba share from qnap device) string processbuf; if (bytesTransferred <= 4096) { processbuf = notifybuf; // even under high load, usually the buffer is under 4k. } else { processbuf.swap(notifybuf); // use existing buffer, a new one will be allocated for receiving } char* ptr = (char*)processbuf.data(); readchanges(); // ensure accuracy of the notification timestamps WAIT_CLASS::bumpds(); // we trust the OS to always return conformant data for (;;) { FILE_NOTIFY_INFORMATION* fni = (FILE_NOTIFY_INFORMATION*)ptr; #ifdef ENABLE_SYNC //LOG_verbose << "FS notification: " << fni->Action << " " << LocalPath::fromPlatformEncoded(std::wstring(fni->FileName, fni->FileNameLength / sizeof(fni->FileName[0]))).toPath(); //FILE_ACTION_RENAMED_OLD_NAME: we let this one through now. Eg for move of folder out of the sync. Though, we should also get FILE_ACTION_MODIFIED for its containing folder. Belt & braces. auto scanRequirement = fni->Action == FILE_ACTION_MODIFIED // FILE_ACTION_MODIFIED: for directories, if an entry was added or removed. Or if accessibilty permissions of this folder changed. ? Notification::FOLDER_NEEDS_SELF_SCAN : Notification::NEEDS_PARENT_SCAN; notify(fsEventq, localrootnode, scanRequirement, LocalPath::fromPlatformEncodedRelative(std::wstring(fni->FileName, fni->FileNameLength / sizeof(fni->FileName[0])))); #endif if (!fni->NextEntryOffset) { break; } ptr += fni->NextEntryOffset; } } clientWaiter->notify(); } // request change notifications on the subtree under hDirectory void WinDirNotify::readchanges() { assert( std::this_thread::get_id() == smNotifierThread->get_id()); if (notifybuf.size() != 65534) { // Use 65534 for the buffer size because (from doco): // ReadDirectoryChangesW fails with ERROR_INVALID_PARAMETER when the buffer length is greater than 64 KB and the application is // monitoring a directory over the network. This is due to a packet size limitation with the underlying file sharing protocols. notifybuf.resize(65534); } auto readRet = ReadDirectoryChangesW(hDirectory, (LPVOID)notifybuf.data(), (DWORD)notifybuf.size(), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY, // so we can know if we lose/gain access to a scan-blocked folder &dwBytes, &overlapped, completion); if (readRet) { setFailed(0, ""); mOverlappedEnabled = true; } else { mOverlappedEnabled = false; DWORD e = GetLastError(); LOG_warn << "ReadDirectoryChanges not available. Error code: " << e << " errors: " << mErrorCount.load(); if (e == ERROR_NOTIFY_ENUM_DIR && mErrorCount < 10) { // notification buffer overflow mErrorCount++; readchanges(); } else { // permanent failure - switch to scanning mode setFailed(e, "Fatal error returned by ReadDirectoryChangesW"); } } } std::mutex WinDirNotify::smNotifyMutex; std::atomic WinDirNotify::smNotifierCount{0}; HANDLE WinDirNotify::smEventHandle = NULL; std::deque> WinDirNotify::smQueue; std::unique_ptr WinDirNotify::smNotifierThread; void WinDirNotify::notifierThreadFunction() { LOG_debug << "Filesystem notify thread started"; bool recheck = false; for (;;) { if (!recheck) { WaitForSingleObjectEx(smEventHandle, INFINITE, TRUE); // alertable, so filesystem notify callbacks can occur on this thread during this time. ResetEvent(smEventHandle); } recheck = false; std::function f; { std::unique_lock g(smNotifyMutex); if (!smQueue.empty()) { f = std::move(smQueue.front()); if (!f) break; // nullptr to cause the thread to exit smQueue.pop_front(); } } if (f) { f(); recheck = true; } } LOG_debug << "Filesystem notify thread stopped"; } WinDirNotify::WinDirNotify(LocalNode& root, const LocalPath& rootPath, WinFileSystemAccess* owner, Waiter* waiter) : DirNotify(rootPath) { assert(rootPath.isAbsolute()); fsaccess = owner; fsaccess->dirnotifys.insert(this); clientWaiter = waiter; #ifdef ENABLE_SYNC localrootnode = &root; #endif // ENABLE_SYNC { // If this is the first Notifier created, start the thread that queries the OS for notifications. std::lock_guard g(smNotifyMutex); if (++smNotifierCount == 1) { smQueue.clear(); smEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL); // One thread to notify them all smNotifierThread.reset(new std::thread([](){ // Process directory enumeration requests. notifierThreadFunction(); })); } } ZeroMemory(&overlapped, sizeof(overlapped)); overlapped.hEvent = this; mOverlappedEnabled = false; mOverlappedExit = false; // ReadDirectoryChangesW: If you opened the file using the short name, you can receive change notifications for the short name. (so make sure it's a long name) std::wstring longname; std::wstring name{localbasepath.asPlatformEncoded(false)}; auto r = name.size() + 20; longname.resize(r); auto rr = GetLongPathNameW(name.data(), const_cast(longname.data()), DWORD(r)); longname.resize(rr); if (rr >= r) { rr = GetLongPathNameW(name.data(), const_cast(longname.data()), rr); longname.resize(rr); } if ((hDirectory = CreateFileW(longname.data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL)) != INVALID_HANDLE_VALUE) { setFailed(0, ""); // So we know when we've asked the system for directory notifications. std::promise requested; { std::lock_guard g(smNotifyMutex); smQueue.push_back([&requested, this](){ // Ask the system to report directory change notifications. readchanges(); // Let queuing thread know we've asked the system for notifications. requested.set_value(); }); } // Let notification thread know there's work to do. SetEvent(smEventHandle); // Wait until the notification thread has processed our request. requested.get_future().get(); } else { int err = GetLastError(); setFailed(err, "CreateFileW was unable to open the folder"); LOG_err << "Unable to initialize filesystem notifications. Error: " << err; } } WinDirNotify::~WinDirNotify() { mOverlappedExit = true; if (hDirectory != INVALID_HANDLE_VALUE) { if (mOverlappedEnabled) { { std::lock_guard g(smNotifyMutex); smQueue.push_back([this](){ CancelIo(hDirectory); }); } SetEvent(smEventHandle); while (mOverlappedEnabled) { SleepEx(10, true); } } CloseHandle(hDirectory); } fsaccess->dirnotifys.erase(this); { if (--smNotifierCount == 0) { { std::lock_guard g(smNotifyMutex); smQueue.push_back(nullptr); } SetEvent(smEventHandle); smNotifierThread->join(); smNotifierThread.reset(); CloseHandle(smEventHandle); { std::lock_guard g(smNotifyMutex); smQueue.clear(); } } } } #endif // ENABLE_SYNC std::unique_ptr WinFileSystemAccess::newfileaccess(bool /*followSymLinks*/) { return std::unique_ptr(new WinFileAccess(waiter)); } bool WinFileSystemAccess::getlocalfstype(const LocalPath& path, FileSystemType& type) const { using std::wstring; // Where is the volume containing our file mounted? wstring mountPoint(MAX_PATH + 1, L'\0'); if (!GetVolumePathNameW(path.asPlatformEncoded(false).c_str(), const_cast(mountPoint.data()), MAX_PATH + 1)) { return type = FS_UNKNOWN, false; } // Get the name of the volume's filesystem. wstring filesystemName(MAX_PATH + 1, L'\0'); DWORD volumeFlags = 0; // What kind of filesystem is the volume using? if (GetVolumeInformationW(mountPoint.c_str(), nullptr, 0, nullptr, nullptr, &volumeFlags, &filesystemName[0], MAX_PATH + 1)) { // Assume we can't find a matching filesystem. type = FS_UNKNOWN; if (!wcscmp(filesystemName.c_str(), L"NTFS")) { type = FS_NTFS; } else if (!wcscmp(filesystemName.c_str(), L"FAT32")) { type = FS_FAT32; } else if (!wcscmp(filesystemName.c_str(), L"exFAT")) { type = FS_EXFAT; } return true; } // We couldn't get any information on the volume. return type = FS_UNKNOWN, false; } unique_ptr WinFileSystemAccess::newdiraccess() { return unique_ptr(new WinDirAccess()); } #ifdef ENABLE_SYNC DirNotify* WinFileSystemAccess::newdirnotify(LocalNode& root, const LocalPath& rootPath, Waiter* notificationWaiter) { return new WinDirNotify(root, rootPath, this, notificationWaiter); } #endif bool WinFileSystemAccess::issyncsupported(const LocalPath& localpathArg, bool& isnetwork, SyncError& syncError, SyncWarning& syncWarning) { static const wchar_t* VBoxSharedFolderFS = L"VBoxSharedFolderFS"; std::wstring path, fsname; bool result = true; isnetwork = false; syncError = NO_SYNC_ERROR; syncWarning = NO_SYNC_WARNING; path.resize(MAX_PATH * sizeof(WCHAR)); fsname.resize(MAX_PATH * sizeof(WCHAR)); if (GetVolumePathNameW(localpathArg.asPlatformEncoded(false).data(), const_cast(path.data()), MAX_PATH) && GetVolumeInformationW((LPCWSTR)path.data(), NULL, 0, NULL, NULL, NULL, (LPWSTR)fsname.data(), MAX_PATH)) { if (Utils::startswith(fsname, VBoxSharedFolderFS)) { LOG_warn << "VBoxSharedFolderFS is not supported because it doesn't provide ReadDirectoryChanges() nor unique file identifiers"; syncError = VBOXSHAREDFOLDER_UNSUPPORTED; result = false; } else if ((Utils::startswith(fsname, L"FAT") || Utils::startswith(fsname, L"exFAT"))) // TODO: have these checks for !windows too { LOG_warn << "You are syncing a local folder formatted with a FAT filesystem. " "That filesystem has deficiencies managing big files and modification times " "that can cause synchronization problems (e.g. when daylight saving changes), " "so it's strongly recommended that you only sync folders formatted with more " "reliable filesystems like NTFS (more information at https://help.mega.nz/megasync/syncing.html#can-i-sync-fat-fat32-partitions-under-windows."; syncWarning = LOCAL_IS_FAT; } else if (Utils::startswith(fsname, L"HGFS")) { LOG_warn << "You are syncing a local folder shared with VMWare. Those folders do not support filesystem notifications " "so MEGAsync will have to be continuously scanning to detect changes in your files and folders. " "Please use a different folder if possible to reduce the CPU usage."; syncWarning = LOCAL_IS_HGFS; } } if (GetDriveTypeW(path.data()) == DRIVE_REMOTE) { LOG_debug << "Network folder detected"; isnetwork = true; } string utf8fsname; LocalPath::local2path(&fsname, &utf8fsname, false); LOG_debug << "Filesystem type: " << utf8fsname; return result; } bool reuseFingerprint(const FSNode& lhs, const FSNode& rhs) { // it might look like fingerprint.crc comparison has been missed out here // but that is intentional. The point is to avoid re-fingerprinting files // when we rescan a folder, if we already have good info about them and // nothing we can see from outside the file had changed, // mtime is the same, and no notifications arrived // for this particular file. return lhs.type == rhs.type && lhs.fsid == rhs.fsid && lhs.fingerprint.mtime == rhs.fingerprint.mtime && lhs.fingerprint.size == rhs.fingerprint.size; }; bool WinFileSystemAccess::checkForSymlink(const LocalPath& lp) { ScopedFileHandle rightTypeHandle = CreateFileW(lp.asPlatformEncoded(false).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; union rightSizeBuffer { REPARSE_DATA_BUFFER rdb; char pad[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; } rdb; DWORD bytesReturned = 0; if (rightTypeHandle.get() != INVALID_HANDLE_VALUE && DeviceIoControl( rightTypeHandle.get(), FSCTL_GET_REPARSE_POINT, NULL, 0, &rdb, sizeof(rdb), &bytesReturned, NULL)) { return rdb.rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK; } return false; } ScanResult WinFileSystemAccess::directoryScan(const LocalPath& path, handle expectedFsid, map& known, std::vector& results, [[maybe_unused]] bool followSymLinks, unsigned& nFingerprinted) { assert(path.isAbsolute()); assert(!followSymLinks && "Symlinks are not supported on Windows!"); ScopedFileHandle rightTypeHandle = CreateFileW(path.asPlatformEncoded(false).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (rightTypeHandle.get() == INVALID_HANDLE_VALUE) { LOG_warn << "Failed to directoryScan, no handle for: " << path; return SCAN_INACCESSIBLE; } BY_HANDLE_FILE_INFORMATION bhfi = { 0 }; if (!GetFileInformationByHandle(rightTypeHandle.get(), &bhfi)) { LOG_warn << "Failed to directoryScan, no info for: " << path; return SCAN_INACCESSIBLE; } auto folderFsid = ((handle)bhfi.nFileIndexHigh << 32) | (handle)bhfi.nFileIndexLow; if (folderFsid != expectedFsid) { LOG_warn << "Failed to directoryScan, mismatch on expected FSID: " << path; return SCAN_FSID_MISMATCH; } if (!(bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { LOG_warn << "Failed to directoryScan, not a directory: " << path; return SCAN_INACCESSIBLE; } alignas(8) byte bytes[1024 * 10]; if (GetFileInformationByHandleEx( rightTypeHandle.get(), FileIdBothDirectoryRestartInfo, // starts the listing from the beginning bytes, sizeof(bytes))) { do { bool loopDone = false; for (byte* ptr = bytes; !loopDone;) { auto info = reinterpret_cast(ptr); if (!info->NextEntryOffset) loopDone = true; else ptr += info->NextEntryOffset; FSNode result; result.localname = LocalPath::fromPlatformEncodedRelative(wstring(info->FileName, info->FileNameLength/2)); std::wstring localNameStr = result.localname.asPlatformEncoded(false); assert(localNameStr.back() != 0); if (localNameStr == L"." || localNameStr == L"..") { continue; } // Are we dealing with a reparse point? if ((info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { auto filePath = path; filePath.appendWithSeparator(result.localname, false); LOG_warn << "directoryScan: " << "Encountered a reparse point: " << filePath; // Provide basic information about the reparse point. result.fingerprint.mtime = FileTime_to_POSIX((FILETIME*)&info->LastWriteTime); result.fingerprint.size = (m_off_t)info->EndOfFile.QuadPart; result.fsid = (handle)info->FileId.QuadPart; result.isSymlink = checkForSymlink(filePath); result.type = result.isSymlink ? TYPE_SYMLINK : TYPE_SPECIAL; results.emplace_back(std::move(result)); // Process the next directory entry. continue; } // For now at least, do the same as the old system: ignore system+hidden. // desktop.ini seem to be at least one problem solved this way, they are // (at least sometimes) unopenable // anyway, so no valid fingerprint can be extracted. if (WinFileAccess::skipattributes(info->FileAttributes)) { result.type = TYPE_DONOTSYNC; LOG_debug << "do-not-sync path identified by attributes: " << path; } else { result.type = (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FOLDERNODE : FILENODE; } result.fsid = (handle) info->FileId.QuadPart; result.fingerprint.mtime = FileTime_to_POSIX((FILETIME*)&info->LastWriteTime); result.fingerprint.size = (m_off_t)info->EndOfFile.QuadPart; if (info->ShortNameLength > 0) { wstring wstr(info->ShortName, info->ShortNameLength/2); assert(wstr.back() != 0); if (wstr != result.localname.asPlatformEncoded(false)) { result.shortname.reset(new LocalPath(LocalPath::fromPlatformEncodedRelative(std::move(wstr)))); } } if (result.shortname && *result.shortname == result.localname) { result.shortname.reset(); } //// Warn about symlinks. //if (result.isSymlink) //{ // LOG_debug << "Interrogated path is a symlink: " // << path.toPath(); //} if (result.type == FOLDERNODE) { memset(result.fingerprint.crc.data(), 0, sizeof(result.fingerprint.crc)); } else if (result.type == FILENODE) { // Fingerprint the file if it's new or changed // (caller has to not supply 'known' items we aready know changed in case mtime+size is still a match) auto it = known.find(result.localname); if (it != known.end() && reuseFingerprint(it->second, result)) { result.fingerprint = std::move(it->second.fingerprint); known.erase(it); } else { LocalPath p = path; p.appendWithSeparator(result.localname, false); auto fa = newfileaccess(); if (fa->fopen(p, OPEN_RDONLY, FSLogging::logOnError)) { result.fingerprint.genfingerprint(fa.get()); nFingerprinted += 1; } else { // The file may be opened exclusively by another process // In this case, the fingerprint (the crc portion) is invalid (for now) } } } results.push_back(std::move(result)); } } while (GetFileInformationByHandleEx( rightTypeHandle.get(), FileIdBothDirectoryInfo, // continues but does not restart bytes, sizeof(bytes))); } auto err = GetLastError(); if (err != ERROR_NO_MORE_FILES) { LOG_err << "Failed in directoryScan, error " << err; return SCAN_INACCESSIBLE; } return SCAN_SUCCESS; } bool WinDirAccess::dopen(LocalPath* nameArg, FileAccess* f, bool glob) { assert(nameArg || f); assert(!(glob && f)); if (f) { if ((hFind = ((WinFileAccess*)f)->hFind) != INVALID_HANDLE_VALUE) { ffd = ((WinFileAccess*)f)->ffd; ((WinFileAccess*)f)->hFind = INVALID_HANDLE_VALUE; } } else { std::wstring name = nameArg->asPlatformEncoded(false); if (!glob) { if (!name.empty() && name.back() != L'\\') { name.append(L"\\"); } name.append(L"*"); } hFind = FindFirstFileW(name.c_str(), &ffd); if (glob) { if (nameArg->getLeafnameByteIndex()) { globbase = nameArg->parentPath(); } else { globbase.clear(); } } } ffdvalid = hFind != INVALID_HANDLE_VALUE; if (!ffdvalid) { return false; } return true; } // FIXME: implement followsymlinks bool WinDirAccess::dnext(LocalPath& /*path*/, LocalPath& nameArg, bool /*followsymlinks*/, nodetype_t* type) { for (;;) { if (ffdvalid && !WinFileAccess::skipattributes(ffd.dwFileAttributes) && (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || *ffd.cFileName != '.' || (ffd.cFileName[1] && ((ffd.cFileName[1] != '.') || ffd.cFileName[2])))) { nameArg = LocalPath::fromPlatformEncodedRelative( std::wstring{ffd.cFileName, wcslen(ffd.cFileName)}); if (!globbase.empty()) { nameArg.prependWithSeparator(globbase); } if (type) { *type = (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FOLDERNODE : FILENODE; } ffdvalid = false; currentItemAttributes = ffd; return true; } else { if (ffdvalid && SimpleLogger::getLogLevel() >= logDebug) { if (*ffd.cFileName != '.' && (ffd.cFileName[1] && ((ffd.cFileName[1] != '.') || ffd.cFileName[2]))) LOG_debug << "Excluded: " << ffd.cFileName << " Attributes: " << ffd.dwFileAttributes; } } ffdvalid = FindNextFileW(hFind, &ffd) != FALSE; if (!ffdvalid) { return false; } } } WinDirAccess::WinDirAccess() { ffdvalid = false; hFind = INVALID_HANDLE_VALUE; } WinDirAccess::~WinDirAccess() { if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); } } m_off_t WinFileSystemAccess::availableDiskSpace(const LocalPath& drivePath) { m_off_t maximumBytes = std::numeric_limits::max(); ULARGE_INTEGER numBytes; if (!GetDiskFreeSpaceExW(drivePath.asPlatformEncoded(false).c_str(), &numBytes, nullptr, nullptr)) { auto result = GetLastError(); LOG_warn << "Unable to retrieve available disk space for: " << drivePath << ". Error code was: " << result; return maximumBytes; } if (numBytes.QuadPart >= (uint64_t)maximumBytes) return maximumBytes; return (m_off_t)numBytes.QuadPart; } std::string FileSystemAccess::getErrorMessage(int error) { return winErrorMessage(error); } bool FSLogging::isFileNotFound(int error) { return error == ERROR_FILE_NOT_FOUND; } } // namespace sdk-10.11.0/src/win32/gfx/000077500000000000000000000000001516266226600147645ustar00rootroot00000000000000sdk-10.11.0/src/win32/gfx/worker/000077500000000000000000000000001516266226600162755ustar00rootroot00000000000000sdk-10.11.0/src/win32/gfx/worker/comms.cpp000066400000000000000000000120651516266226600201230ustar00rootroot00000000000000#include "mega/win32/gfx/worker/comms.h" #include "mega/logging.h" #include "mega/filesystem.h" using std::chrono::milliseconds; namespace mega { namespace gfx { namespace win_utils { std::wstring toFullPipeName(const std::string& name) { const std::string pipeName = "\\\\.\\pipe\\" + name; std::wstring pipeNameW; LocalPath::path2local(&pipeName, &pipeNameW); return pipeNameW; } } WinOverlapped::WinOverlapped() { mOverlapped.Offset = 0; mOverlapped.OffsetHigh = 0; mOverlapped.hEvent = CreateEvent(NULL, // default security attribute TRUE, // manual-reset event FALSE, // initial state = non signaled NULL); // unnamed event object if (mOverlapped.hEvent == NULL) { LOG_err << "CreateEvent failed. error code=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); } } WinOverlapped::~WinOverlapped() { if (mOverlapped.hEvent) { CloseHandle(mOverlapped.hEvent); } } OVERLAPPED* WinOverlapped::data() { return &mOverlapped; } std::pair WinOverlapped::waitForCompletion(DWORD waitMs) { const auto waitResult = WaitForSingleObject(mOverlapped.hEvent, waitMs); switch (waitResult) { case WAIT_OBJECT_0: return {std::error_code{}, ""}; case WAIT_TIMEOUT: return {std::make_error_code(std::errc::timed_out), "wait timeout: " + std::to_string(waitMs)}; case WAIT_ABANDONED: return {std::make_error_code(std::errc::not_connected), "wait abandoned"}; case WAIT_FAILED: { const auto lastError = GetLastError(); return {std::make_error_code(std::errc::not_connected), "wait failed error " + std::to_string(lastError) + " " + mega::winErrorMessage(lastError)}; } default: return {std::make_error_code(std::errc::not_connected), "wait error"}; } } NamedPipe::NamedPipe(NamedPipe&& other) { this->mPipeHandle = other.mPipeHandle; this->mName = std::move(other.mName); other.mPipeHandle = INVALID_HANDLE_VALUE; } NamedPipe::~NamedPipe() { if (mPipeHandle != INVALID_HANDLE_VALUE) { CloseHandle(mPipeHandle); } } bool NamedPipe::doWrite(const void* data, size_t n, milliseconds timeout) { auto writeOverlappedFn = [this, n, data](OVERLAPPED* overlapped){ DWORD written; auto result = WriteFile( mPipeHandle, // pipe handle data, // message static_cast(n), // message length &written, // bytes written overlapped); // overlapped return result; }; return doOverlappedOperation(writeOverlappedFn, timeout, "write"); } bool NamedPipe::doRead(void* out, size_t n, milliseconds timeout) { auto readOverlappedFn = [this, n, out](OVERLAPPED* overlapped){ DWORD cbRead = 0; bool result = ReadFile( mPipeHandle, // pipe handle out, // buffer to receive reply static_cast(n), // size of buffer &cbRead, // number of bytes read overlapped); // overlapped return result; }; return doOverlappedOperation(readOverlappedFn, timeout, "read"); } bool NamedPipe::doOverlappedOperation(std::function op, milliseconds timeout, const std::string& opStr) { if (mPipeHandle == INVALID_HANDLE_VALUE) { return false; } WinOverlapped overlapped{}; if (!overlapped.isValid()) { return false; } // Call Op. if (op(overlapped.data())) { return true; // Completed } else if (const auto lastError = GetLastError(); lastError != ERROR_IO_PENDING) // Fail with other errors { LOG_err << mName << ": " << opStr << " pipe failed. error=" << lastError << " " << mega::winErrorMessage(lastError); return false; } // Wait DWORD waitTimeout = timeout.count() < 0 ? INFINITE : static_cast(timeout.count()); if (const auto& [error, errorText] = overlapped.waitForCompletion(waitTimeout); error) { LOG_verbose << mName << ": " << opStr << " " << errorText; return false; } // Get result DWORD numberOfBytesTransferred = 0; bool success = GetOverlappedResult(mPipeHandle, overlapped.data(), &numberOfBytesTransferred, false /*bWait*/); if (!success) { const auto lastError = GetLastError(); LOG_err << mName << ": " << opStr << " pipe fail to complete error=" << lastError << " " << mega::winErrorMessage(lastError); return false; } // Completed return true; } } // namespace } sdk-10.11.0/src/win32/gfx/worker/comms_client.cpp000066400000000000000000000057531516266226600214670ustar00rootroot00000000000000#include "mega/logging.h" #include "mega/win32/gfx/worker/comms_client.h" #include "mega/gfx/worker/comms.h" #include "mega/win32/gfx/worker/comms.h" namespace mega { namespace gfx { class ClientNamedPipe : public NamedPipe { public: ClientNamedPipe(HANDLE h) : NamedPipe(h, "client") {} private: Type type() const { return Type::Client; } }; GfxCommunicationsClient::GfxCommunicationsClient(const std::string& pipeName) : mPipeName(pipeName) { } std::pair GfxCommunicationsClient::doConnect(LPCTSTR pipeName) { constexpr DWORD waitTimeoutMs{3000}; CommError error = CommError::ERR; HANDLE hPipe = INVALID_HANDLE_VALUE; while (1) { hPipe = CreateFile( pipeName, // pipe name GENERIC_READ | // read and write access GENERIC_WRITE, 0, // no sharing NULL, // default security attributes OPEN_EXISTING, // opens existing pipe FILE_FLAG_OVERLAPPED, // flag and attributes NULL); // no template file // Break if the pipe handle is valid. if (hPipe != INVALID_HANDLE_VALUE) { error = CommError::OK; break; } // Exit if an error other than ERROR_PIPE_BUSY occurs. if (const auto lastError = GetLastError(); lastError != ERROR_PIPE_BUSY) { LOG_err << "Could not open pipe. Error Code=" << lastError << " " << mega::winErrorMessage(lastError); error = toCommError(lastError); break; } // Server is busy. Wait and get one instance. Continue to connect if (WaitNamedPipe(pipeName, waitTimeoutMs)) continue; if (const auto lastError = GetLastError(); lastError == ERROR_SEM_TIMEOUT) { LOG_warn << "WaitNamedPipe: timed out."; error = CommError::TIMEOUT; break; } else { LOG_err << "WaitNamedPipe Error Code=" << lastError << " " << mega::winErrorMessage(lastError); error = toCommError(lastError); break; } } return {error, hPipe}; } std::pair> GfxCommunicationsClient::connect() { const auto fullPipeName = win_utils::toFullPipeName(mPipeName); auto [error, hPipe] = doConnect(fullPipeName.c_str()); // Error if (error != CommError::OK) { return {error, nullptr}; } // Success assert(hPipe != INVALID_HANDLE_VALUE); return {CommError::OK, std::make_unique(hPipe)}; } CommError GfxCommunicationsClient::toCommError(DWORD winError) const { switch (winError) { case ERROR_SUCCESS: return CommError::OK; case ERROR_FILE_NOT_FOUND: return CommError::NOT_EXIST; case ERROR_BROKEN_PIPE: // Usually mean the other side closed the handle return CommError::CLOSED; default: return CommError::ERR; } } } } sdk-10.11.0/src/win32/net.cpp000066400000000000000000000013031516266226600154670ustar00rootroot00000000000000/** * @file mega/win32/net.cpp * @brief POSIX network access layer (using cURL) * * (c) 2013-2015 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "../posix/net.cpp" sdk-10.11.0/src/win32/waiter.cpp000066400000000000000000000063211516266226600162010ustar00rootroot00000000000000/** * @file win32/wait.cpp * @brief Win32 event/timeout handling * * (c) 2013-2014 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "megawaiter.h" namespace mega { WinWaiter::WinWaiter() { externalEvent = CreateEvent(NULL, FALSE, FALSE, NULL); } WinWaiter::~WinWaiter() { CloseHandle(externalEvent); } // wait for events (socket, I/O completion, timeout + application events) // ds specifies the maximum amount of time to wait in deciseconds (or // NEVER if no timeout scheduled) (this assumes that the second call to // addhandle() was coming from the network layer) int WinWaiter::wait() { int r = 0; addhandle(externalEvent, NEEDEXEC); if (index <= MAXIMUM_WAIT_OBJECTS) { assert(!handles.empty()); DWORD dwWaitResult = WaitForMultipleObjectsEx( static_cast(index), &handles.front(), FALSE, (maxds > static_cast(std::numeric_limits::max() / 100)) ? std::numeric_limits::max() : static_cast(maxds * 100), TRUE); assert(dwWaitResult != WAIT_FAILED); #ifdef MEGA_MEASURE_CODE if (dwWaitResult == WAIT_TIMEOUT && maxds > 0) ++performanceStats.waitTimedoutNonzero; else if (dwWaitResult == WAIT_TIMEOUT && maxds == 0) ++performanceStats.waitTimedoutZero; else if (dwWaitResult == WAIT_IO_COMPLETION) ++performanceStats.waitIOCompleted; else if (dwWaitResult >= WAIT_OBJECT_0) ++performanceStats.waitSignalled; #endif if ((dwWaitResult == WAIT_TIMEOUT) || (dwWaitResult == WAIT_IO_COMPLETION) || maxds == 0 || (dwWaitResult == WAIT_FAILED)) { r |= NEEDEXEC; } if ((dwWaitResult >= WAIT_OBJECT_0) && (dwWaitResult < WAIT_OBJECT_0 + flags.size())) { r |= flags[dwWaitResult - WAIT_OBJECT_0]; } } else { assert(false); // alert developers that we're hitting the limit r |= NEEDEXEC; } index = 0; return r; } // add handle to the list - must not be called twice with the same handle // return true if handle added bool WinWaiter::addhandle(HANDLE handle, int flag) { assert(handles.size() == flags.size() && handles.size() >= index); #ifdef DEBUG for (size_t i = index; i--; ) { // double check we only add one of each handle assert(handles[i] != handle); } #endif if (index < handles.size()) { handles[index] = handle; flags[index] = flag; } else { handles.push_back(handle); flags.push_back(flag); } ++index; return true; } void WinWaiter::notify() { SetEvent(externalEvent); } } // namespace sdk-10.11.0/tests/000077500000000000000000000000001516266226600136115ustar00rootroot00000000000000sdk-10.11.0/tests/CMakeLists.txt000066400000000000000000000026011516266226600163500ustar00rootroot00000000000000# Build ALL tests (unit + integration) as C++20 without touching SDK (which stays on C++17) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # Common interface target for test tools add_library(test_tools STATIC) add_library(MEGA::test_tools ALIAS test_tools) target_sources(test_tools PRIVATE gtest_common.h sdk_test_data_provider.h sdk_test_utils.h stdfs.h gtest_common.cpp sdk_test_data_provider.cpp sdk_test_utils.cpp ) target_link_libraries(test_tools PRIVATE MEGA::SDKlib) target_include_directories(test_tools PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) # Common interface target for configurations and libraries for the SDK and MEGAchat tests. add_library(test_common INTERFACE) add_library(MEGA::test_common ALIAS test_common) # Look for the libraries needed for both unit tests and integration tests for the SDK and MEGAchat. if(VCPKG_ROOT) find_package(GTest CONFIG REQUIRED) target_link_libraries(test_common INTERFACE GTest::gmock GTest::gtest) else() pkg_check_modules(gtest REQUIRED IMPORTED_TARGET gtest) pkg_check_modules(gmock REQUIRED IMPORTED_TARGET gmock) target_link_libraries(test_common INTERFACE PkgConfig::gmock PkgConfig::gtest) endif() if(ENABLE_SDKLIB_TESTS) # This file is also loaded for MEGAchat tests. add_subdirectory(integration) add_subdirectory(unit) endif() sdk-10.11.0/tests/README.md000066400000000000000000000023161516266226600150720ustar00rootroot00000000000000`tests` is the root level directory for SDK testing. Unit and integration tests are contained within the `unit` and `integration` directories, respectively. Note that unit tests do NOT do I/O and are very fast whereas integration tests typically require I/O, are slower, and harder to maintain. Unit and integration tests require the google test framework: https://github.com/google/googletest Building gtest and the tests itself depends very much on your OS and your build system of choice. We currently support CMake. You can run all tests or just a subset of the tests by supplying a filter to the test executable, e.g., `./test_unit --gtest_filter=Crypto*` Unit and integration tests are organized in such a way that the filename should match the name of the contained test suite. A single test file should only contain a single test suite. E.g., `Crypto_test.cpp` should only contain tests like `TEST(Crypto, blahblah)`. This makes test discovery more efficient. Any testing framework code should live inside the `mt` namespace (= mega testing). The `tool` directory contains standalone test applications that must be run manually. The `python` directory contains work-in-progress system tests written in python. sdk-10.11.0/tests/gtest_common.cpp000066400000000000000000000743541516266226600170300ustar00rootroot00000000000000#include "gtest_common.h" #include #include #include using namespace std; const string& getDefaultLogName(); // implement this separately for SDK tests, MEGAchat tests etc. namespace mega { /// class ProcessWithInterceptedOutput /// bool ProcessWithInterceptedOutput::run(const vector& args, const unordered_map& env) { // only run if not already running or if it finished assert(!mProc || mProc->getPid() == -1 || mProc->hasStatus()); if (mProc && mProc->getPid() != -1 && !mProc->hasStatus()) { return false; } // clean-up for previous run mProc = std::make_unique(); mOutBuffer.clear(); mErrBuffer.clear(); mExitReported = false; clearBeforeRun(); // other clean-up for derived classes Process::DataReaderFunc interceptOut = [this](const unsigned char* data, size_t len) { intercept(data, len, mOutBuffer, [this](string&& line) { onOutLine(std::move(line)); }); }; Process::DataReaderFunc interceptErr = [this](const unsigned char* data, size_t len) { intercept(data, len, mErrBuffer, [this](string&& line) { onErrLine(std::move(line)); }); }; return mProc->run(args, env, interceptOut, interceptErr); } bool ProcessWithInterceptedOutput::finishedRunning() { if (!mProc || mProc->getPid() == -1) { return false; } // "flushing" child process is mandatory; otherwise it might never report having exited; // should probably be part of Process::isAlive() mProc->flush(); return !mProc->isAlive(); } int ProcessWithInterceptedOutput::getExitCode() { if (!mProc || mProc->getPid() == -1) { return -1; } if (!mProc->hasStatus()) { mProc->flush(); mProc->wait(); // not relevant if it did not start or failed or succeeded } // dump any remaining output if (!mOutBuffer.empty()) { onOutLine(std::move(mOutBuffer)); mOutBuffer.clear(); } if (!mErrBuffer.empty()) { onErrLine(std::move(mErrBuffer)); mErrBuffer.clear(); } // react to the way it exited if (!mExitReported) { mExitReported = true; onExit(); } return mProc->hasExited() ? mProc->getExitCode() : mProc->getTerminatingSignal(); } int ProcessWithInterceptedOutput::getPid() const { return mProc ? mProc->getPid() : -1; } void ProcessWithInterceptedOutput::onOutLine(string&& line) { std::cout << line << std::endl; } void ProcessWithInterceptedOutput::onErrLine(string&& line) { std::cerr << line << std::endl; } void ProcessWithInterceptedOutput::intercept(const unsigned char* data, size_t len, std::string& buffer, std::function onLine) { if (!data || !len) { return; } buffer.append(reinterpret_cast(data), len); for (string::size_type lineStart = 0, lineEnd = buffer.find('\n', lineStart); ; lineStart = lineEnd + 1, lineEnd = buffer.find('\n', lineStart)) { // process only lines ending in separator if (lineEnd == string::npos) { // leave the rest of the line in the buffer, just in case it doesn't finish with '\n' buffer.erase(0, lineStart); break; } string line = buffer.substr(lineStart, lineEnd - lineStart); if (!line.empty() && line.back() == '\r') // Windows: \r\n { line.pop_back(); } onLine(std::move(line)); } } /// class GTestListProc /// void GTestListProc::onOutLine(string&& line) { // react to lines like: // // SuiteFoo. // TestBar // DISABLED_TestBazz if (Utils::startswith(line, '[')) return; // skip lines with other info // test suite if (!Utils::startswith(line, ' ')) { // name of test suite if (!line.empty() && !isalpha(line.front())) { std::cerr << "ERROR: Test suite name was invalid: " << line << std::endl; return; } mCurrentSuite = line; ++mTestSuiteCount; return; } if (mCurrentSuite.empty()) { std::cerr << "ERROR: Test suite name should have been present until now" << std::endl; return; } // Remove parameterized test suffix. { // Does this test have a suffix? auto i = line.find_first_of('#'); // Test has a suffix. if (i != std::string::npos) line.erase(i); } string testCase = Utils::trim(line); // count of disabled tests if (Utils::startswith(testCase, "DISABLED_")) { ++mDisabledTestCount; return; } mTestsToRun.push_back(mCurrentSuite + testCase); } /// class GTestProc /// bool GTestProc::run(const vector& args, const unordered_map& env, size_t workerIdx, string&& name) { mWorkerIdx = workerIdx; mTestName = std::move(name); mStatus = TestStatus::NOT_STARTED; if (ProcessWithInterceptedOutput::run(args, env)) { mStatus = TestStatus::RUNNING; return true; } printToScreen(std::cout, "Failed to run " + mTestName); return false; } void GTestProc::onOutLine(std::string&& line) { // show lines between // [ RUN ] // and // [ OK ] or [ FAILED ] // completely ignore some lines as it's supposed to run only a single test if (line.empty() || line.find("[LOGGER] ========== Application startup ===========") != string::npos || Utils::startswith(line, "[========]") || Utils::startswith(line, "Note: Google Test filter = ") || Utils::startswith(line, "[----------]") || Utils::startswith(line, "[==========]") || Utils::startswith(line, "[ PASSED ]") || (Utils::startswith(line, "[ FAILED ]") && !mOutputIsRelevant) || line == " 1 FAILED TEST") { return; } printToScreen(std::cout, line); if (Utils::startswith(line, "[ RUN ]")) { mOutputIsRelevant = true; mRelevantOutput += line + '\n'; } else if (mOutputIsRelevant) { mRelevantOutput += line + '\n'; if (Utils::startswith(line, "[ OK ]")) { mStatus = TestStatus::TEST_PASSED; mOutputIsRelevant = false; } else if (Utils::startswith(line, "[ FAILED ]")) { mStatus = TestStatus::TEST_FAILED; mOutputIsRelevant = false; } } } void GTestProc::onErrLine(std::string&& line) { if (Utils::startswith(line, "================") || line.find("W: rops_pt_init_destroy_netlink: netlink bind failed") != string::npos) // skip annoying but harmless LWS warning { return; } if (mHideMemLeaks) { // attempt to hide [false-positive] memory leaks, as they make the output unusable if (line.find("==ERROR: LeakSanitizer: detected memory leaks") != string::npos) { mIncomingMemLeaks = true; return; } if (Utils::startswith(line, "SUMMARY: AddressSanitizer:")) { mIncomingMemLeaks = false; return; } if (mIncomingMemLeaks || line.empty()) { return; } } mRelevantOutput += line + '\n'; printToScreen(std::cerr, line); } void GTestProc::onExit() { if (mStatus != TestStatus::RUNNING) { // test reported when it finished, as it should have, no need to augument the log for possible crashes return; } mStatus = TestStatus::CRASHED; string msg("[ FAILED ] " + mTestName + " CRASHED"); printToScreen(std::cout, msg); const string& workerLog = getWorkerLog(); ofstream testLog(workerLog, ios_base::app); if (testLog.is_open()) { testLog << mRelevantOutput << msg << std::endl; } else { printToScreen(std::cout, "Could not open " + workerLog + " to append relevant output after crash."); } } void GTestProc::printToScreen(std::ostream& screen, const std::string& msg) const { screen << getCurrentTimestamp(true) << " #" << mWorkerIdx << ' ' << msg << std::endl; } string GTestProc::getWorkerLog() const { const string& log = (mStatus == TestStatus::NOT_STARTED || mCustomPathForPid.empty()) ? getLogFileName(mWorkerIdx, mTestName) : mCustomPathForPid + std::to_string(getPid()) + '/' + getLogFileName(mWorkerIdx, mTestName); return log; } /// class RuntimeArgValues /// RuntimeArgValues::RuntimeArgValues(vector&& args, vector>&& accEnvVars) { if (accEnvVars.empty()) { assert(!accEnvVars.empty()); return; } mAccEnvVars.swap(accEnvVars); string emailTemplate; for (auto it = args.begin(); it != args.end();) { if (Utils::startswith(*it, "--#")) { // commented out args, e.g. --#INSTANCES:3 it = args.erase(it); continue; } string arg = Utils::toUpperUtf8(*it); if (arg == "--HELP") { mRunMode = TestRunMode::HELP; return; } if (Utils::startswith(arg, "--EMAIL-POOL:")) { emailTemplate = it->substr(13); // keep original string, not in CAPS it = args.erase(it); // not passed to subprocesses continue; } else if (Utils::startswith(arg, "--INSTANCES:")) { assert(mRunMode == TestRunMode::INVALID); mInstanceCount = atoi(arg.substr(12).c_str()); // valid interval: [0, maxWorkerCount] if (mInstanceCount > maxWorkerCount || (!mInstanceCount && arg != "--INSTANCES:0")) { std::cerr << "Invalid runtime parameter: " << *it << "\nMaximum allowed value: " << maxWorkerCount << std::endl; mRunMode = TestRunMode::INVALID; return; // leave current instance as invalid } mRunMode = mInstanceCount ? TestRunMode::MAIN_PROCESS_WITH_WORKERS : TestRunMode::MAIN_PROCESS_ONLY; it = args.erase(it); // not passed to subprocesses continue; } else if (Utils::startswith(arg, "--INSTANCE:")) // used only internally by subprocesses { assert(mRunMode == TestRunMode::INVALID); mCurrentInstance = atoi(arg.substr(11).c_str()); // valid interval: [0, maxWorkerCount) if (mCurrentInstance >= maxWorkerCount || (!mCurrentInstance && arg != "--INSTANCE:0")) { std::cerr << "Invalid runtime parameter: " << *it << std::endl; mRunMode = TestRunMode::INVALID; return; // leave current instance as invalid } mRunMode = TestRunMode::WORKER_PROCESS; } else if (Utils::startswith(arg, "--APIURL:")) { mApiUrl = it->substr(9); // keep original string, not in CAPS if (!mApiUrl.empty() && mApiUrl.back() != '/') mApiUrl += '/'; } else if (Utils::startswith(arg, "--USERAGENT:")) { mUserAgent = it->substr(12); // keep original string, not in CAPS } else if (Utils::startswith(arg, "--GTEST_FILTER=")) { mGtestFilterIdx = it - args.begin(); } else if (arg == "--GTEST_LIST_TESTS") { assert(mRunMode == TestRunMode::INVALID); mRunMode = TestRunMode::LIST_ONLY; return; } else if (arg == "--HIDE_WORKER_MEM_LEAKS") { mHideWorkerMemLeaks = true; it = args.erase(it); // not passed to subprocesses continue; } ++it; } if (!validateRequirements(emailTemplate)) { mRunMode = TestRunMode::INVALID; return; } if (isWorker()) { mTestName = args[mGtestFilterIdx].substr(15); } else if (!isMainProcWithWorkers()) { mRunMode = TestRunMode::MAIN_PROCESS_ONLY; } mArgs = std::move(args); } bool RuntimeArgValues::validateRequirements(const string& emailTemplate) { // Env var for first password must always be set if (!Utils::hasenv(mAccEnvVars[0].second)) { std::cerr << "Missing required $" << mAccEnvVars[0].second << " env var" << std::endl; return false; } // Break email template, if any bool hasTemplate = false; if (!isWorker()) { if (!emailTemplate.empty()) { hasTemplate = breakTemplate(emailTemplate); if (!hasTemplate) { std::cerr << "Invalid runtime parameter --EMAIL-POOL:" << emailTemplate << "\nMust be a template like foo+bar-{1-15}@mega.co.nz" << std::endl; return false; } } else { const string& teplt = Utils::getenv(mAccEnvVars[0].first, ""); if (teplt.empty()) { std::cerr << "Missing both $" << mAccEnvVars[0].first << " env var and --EMAIL-POOL runtime parameter" << std::endl; return false; } hasTemplate = breakTemplate(teplt); } } if (hasTemplate) { size_t instanceCountFromTemplate = (std::get<2>(mEmailTemplateValues) - std::get<1>(mEmailTemplateValues) + 1) / getAccountsPerInstance(); if (!instanceCountFromTemplate) { const string& t = emailTemplate.empty() ? Utils::getenv(mAccEnvVars[0].first, "") : emailTemplate; std::cerr << "Invalid email template " << t << ", 0 instances allowed" << std::endl; return false; } if (mInstanceCount && instanceCountFromTemplate < mInstanceCount) { std::cerr << "WARNING: Not enough accounts in email template (" << instanceCountFromTemplate << ") for requested instances (" << mInstanceCount << ")." << "\nRunning with maximum " << instanceCountFromTemplate << " instances instead.\n" << std::endl; mInstanceCount = instanceCountFromTemplate; } return true; } if (isWorker() && mGtestFilterIdx == SIZE_MAX) { std::cerr << "Missing --gtest_filter runtime parameter for instance " << mCurrentInstance << std::endl; return false; } if (isMainProcWithWorkers() && mInstanceCount > 1u) { // if it received --INSTANCES but not an email template, then it will run tests in a single worker process std::cerr << "WARNING: No email template found to run " << mInstanceCount << " instances,\n"; std::cerr << "Continuing with sequential run in 1 separate instance instead." << std::endl; mInstanceCount = 1u; } // Validate the rest of env vars for (size_t i = 1; i < getAccountsPerInstance(); ++ i) { const auto& a = mAccEnvVars[i]; if (!Utils::hasenv(a.first) || !Utils::hasenv(a.second)) { std::cerr << "Both $" << a.first << " and $" << a.second << " env vars must be defined" << std::endl; return false; } } return true; } void RuntimeArgValues::printHelp() const { string firstAccDescr = mAccEnvVars.empty() ? "env var of the first account" : ('$' + mAccEnvVars[0].first + " env var"); static const string patternExample{ "foo+bar-{1-15}@mega.co.nz" }; // runtime options cout << "Options are case insensitive.\n"; cout << buildAlignedHelpString("--INSTANCES:", {"Run n tests in parallel, each in its own process. In order to achieve that, an email pattern", "will be required and will be taken from --EMAIL-POOL argument or " + firstAccDescr + '.', "If no email pattern was found, it will behave as if n==1, thus run with a single worker", "process and use credentials from the same env vars required by running without this arg."}) << '\n'; cout << buildAlignedHelpString("--EMAIL-POOL:", {"Email address pattern used to extract the required test accounts. Must be of the form", patternExample}) << '\n'; cout << buildAlignedHelpString("--APIURL:", {"Custom base URL to use for contacting the server; overwrites default url."}) << '\n'; cout << buildAlignedHelpString("--USERAGENT:", {"Custom HTTP User-Agent"}) << '\n'; cout << buildAlignedHelpString("--#", {"Commented out argument, ignored"}) << '\n'; cout << buildAlignedHelpString("--GTEST_FILTER=", {"Set tests to execute; can be ':'-separated list, with * or other wildcards", "e.g. --GTEST_FILTER=SuiteFoo.TestBar:*TestBazz"}) << '\n'; cout << buildAlignedHelpString("--GTEST_LIST_TESTS", {"List tests compiled with this executable; consider --GTEST_FILTER if received."}) << '\n'; cout << buildAlignedHelpString("--HIDE_WORKER_MEM_LEAKS", {"Hide memory leaks printed by debugger while running with --INSTANCES."}) << '\n'; printCustomOptions(); // env vars cout << '\n'; cout << "Environment variables:\n"; for (size_t i = 0u; i < getAccountsPerInstance(); ++i) { string numeralStr = std::to_string(i); switch ((i + 1) % 10) { case 1: numeralStr += "st"; break; case 2: numeralStr += "nd"; break; case 3: numeralStr += "rd"; break; default: numeralStr += "th"; break; } const string& accVarName = mAccEnvVars[i].first; string defaultAccDescr = "Email address for " + numeralStr + " MEGA account"; const vector& accVarDescr = (i > 0) ? vector{defaultAccDescr} : vector{"[required or pass --EMAIL-POOL:] " + defaultAccDescr + ", or pattern; can be", "overwritten by the command line argument. When running concurrently using --instances, it must contain", "{min - max}, e.g: " + patternExample + " to set all MEGA account email addresses"}; cout << buildAlignedHelpString(" $" + accVarName, accVarDescr) << '\n'; const string& pwdVarName = mAccEnvVars[i].second; string defaultPwdDescr = "Passsword for " + numeralStr + " MEGA account,"; const vector& pwdVarDescr = (i > 0) ? vector{defaultPwdDescr + " defaults to the password of the first mega account when not set"} : vector{"[required] " + defaultPwdDescr + " becomes the default for unset passwords of other accounts"}; cout << buildAlignedHelpString(" $" + pwdVarName, pwdVarDescr) << '\n'; } printCustomEnvVars(); } string RuntimeArgValues::buildAlignedHelpString(const string& var, const std::vector& descr) { static size_t colW = 28u; // 28 characters from the start of row until the description size_t spacesAfterVar = var.size() >= colW ? 1 : colW - var.size(); string str = std::accumulate(descr.begin(), descr.end(), string{}, [](const string& a, const string& b) { return a + (a.size() ? '\n' + string(colW, ' ') : "") + b; }); return var + string(spacesAfterVar, ' ') + str; } vector RuntimeArgValues::getArgsForWorker(const string& test, size_t spIdx) const { assert(isMainProcWithWorkers()); if (!isMainProcWithWorkers()) return {}; if (test.empty()) return mArgs; // remove? auto args = mArgs; string gtestFilter = "--gtest_filter=" + test; if (mGtestFilterIdx < args.size()) { args[mGtestFilterIdx] = std::move(gtestFilter); } else { args.emplace_back(std::move(gtestFilter)); } args.emplace_back("--INSTANCE:" + std::to_string(spIdx)); return args; } unordered_map RuntimeArgValues::getEnvVarsForWorker(size_t idx) const { assert(isMainProcWithWorkers() || isMainProcOnly()); // when it did not receive an email template don't overwrite env vars if (!usingTemplate() || (!isMainProcWithWorkers() && !isMainProcOnly())) return {}; size_t first = std::get<1>(mEmailTemplateValues) + getAccountsPerInstance() * idx; size_t last = first + getAccountsPerInstance() - 1u; if (last > std::get<2>(mEmailTemplateValues)) return {}; unordered_map envVars(getAccountsPerInstance()); for (size_t i = 0; i < getAccountsPerInstance(); ++i) { envVars[mAccEnvVars[i].first] = std::get<0>(mEmailTemplateValues) + std::to_string(first + i) + std::get<3>(mEmailTemplateValues); // the first password will be duplicated for the rest of accounts static const string pswd{ Utils::getenv(mAccEnvVars[0].second, "")}; if (!i && pswd.empty()) { return envVars; } else { envVars[mAccEnvVars[i].second] = pswd; } } return envVars; } bool RuntimeArgValues::breakTemplate(const std::string& tplt) { // Supported templates: // "(prefix){(first)-(last)}(suffix)" // "(prefix){(first)..(last)}(suffix)" static regex emailRegex("(.*)[{](\\d+)(-|\\.\\.)(\\d+)[}](.*)"); smatch matches; if (regex_search(tplt, matches, emailRegex) && matches.size() >= 6) { size_t first = atoi(matches[2].str().c_str()); // only digits, e.g. 1 size_t last = atoi(matches[4].str().c_str()); // only digits, e.g. 100 if (first > 0u && last > first && !matches[1].str().empty() && !matches[5].str().empty()) { mEmailTemplateValues = std::make_tuple(matches[1].str(), first, last, matches[5].str()); return true; } } return false; } bool RuntimeArgValues::usingTemplate() const { return std::get<2>(mEmailTemplateValues); } string RuntimeArgValues::getLog() const { return getLogFileName(mCurrentInstance, mTestName); } /// class GTestParallelRunner /// GTestParallelRunner::GTestParallelRunner(RuntimeArgValues&& commonArgs): mCommonArgs(std::move(commonArgs)) { #ifdef WIN32 // JobObject to manage the subprocess mJobObject = CreateJobObject(nullptr, nullptr); if (mJobObject) { JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {}; // Processes in the job object will be terminated // when the handle is closed info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; SetInformationJobObject(mJobObject, JobObjectExtendedLimitInformation, &info, sizeof(info)); } #endif } GTestParallelRunner::~GTestParallelRunner() { #ifdef WIN32 if (mJobObject) { CloseHandle(mJobObject); } #endif } int GTestParallelRunner::run() { mFinalResult = 0; mPassedTestCount = 0u; mFailedTests.clear(); mPidDumps.clear(); mStartTime = chrono::system_clock::now(); assert(mCommonArgs.isMainProcWithWorkers()); if (!mCommonArgs.isMainProcWithWorkers() || !findTests()) { return 1; } std::cout << "[==========] Running " << mTestsToRun.size() << " tests from " << mTestSuiteCount << " test suites." << std::endl; // assign 1 test to 1 subprocess size_t procIdx = SIZE_MAX; while (!mTestsToRun.empty()) { if (procIdx == SIZE_MAX) { for (procIdx = getNexatAvailableInstance(); procIdx == SIZE_MAX; procIdx = getNexatAvailableInstance()) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); // not for too long, to collect output } } string testName(std::move(mTestsToRun.front())); mTestsToRun.pop_front(); if (runTest(procIdx, std::move(testName))) { procIdx = SIZE_MAX; // get new value in next loop } } // wait for remaining tests to finish vector stillRunning(mRunningGTests.size()); std::iota(stillRunning.begin(), stillRunning.end(), 0); // fill it with 0,1,... while (!stillRunning.empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); // collect output from running workers for (auto it = stillRunning.begin(); it != stillRunning.end();) { GTestProc& t = mRunningGTests[*it]; if (t.finishedRunning()) { processFinishedTest(t); it = stillRunning.erase(it); } else { ++it; } } } summary(); return mFinalResult; } bool GTestParallelRunner::findTests() { vector args = { mCommonArgs.getExecutable(), "--gtest_list_tests", mCommonArgs.getFilter(), "--gtest_print_time=0", "--no-log-cout" }; GTestListProc proc; if (!proc.run(args, {}) || proc.getExitCode()) { std::cerr << mCommonArgs.getExecutable() << " --gtest_list_tests " << mCommonArgs.getFilter() << " failed" << std::endl; return false; } mTestsToRun = proc.getTestsToRun(); mTestSuiteCount = proc.getTestSuiteCount(); mTotalTestCount = mTestsToRun.size(); mDisabledTestCount = proc.getDisabledTestCount(); if (!mTotalTestCount) { std::cerr << mCommonArgs.getExecutable() << " --gtest_list_tests " << mCommonArgs.getFilter() << " found 0 tests to run" << std::endl; summary(); return false; } return true; } size_t GTestParallelRunner::getNexatAvailableInstance() { if (mRunningGTests.size() < mCommonArgs.getInstanceCount()) { return mRunningGTests.size(); } for (auto& i : mRunningGTests) { GTestProc& testProcess = i.second; if (testProcess.finishedRunning()) { processFinishedTest(testProcess); return i.first; } } return SIZE_MAX; // invalid } bool GTestParallelRunner::runTest(size_t workerIdx, string&& name) { auto procArgs = mCommonArgs.getArgsForWorker(name, workerIdx); auto envVars = mCommonArgs.getEnvVarsForWorker(workerIdx); GTestProc& testProcess = mRunningGTests[workerIdx]; testProcess.setCustomPathForPid(mWorkerOutPath); testProcess.hideMemLeaks(mCommonArgs.hidingWorkerMemLeaks()); bool running = testProcess.run(procArgs, envVars, workerIdx, std::move(name)); #ifdef WIN32 // Assign running process to the Job Object if (mJobObject && running) { HANDLE proc = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, false, testProcess.getPid()); if (proc) { AssignProcessToJobObject(mJobObject, proc); CloseHandle(proc); } } #endif return running; } void GTestParallelRunner::processFinishedTest(GTestProc& test) { int err = test.getExitCode(); // waits if not finished yet if (!err || test.passed()) { // Unfortunately the underlying process can return 1 even if it actually ran fine. // That will happen when running under debugger and reporting memory leaks (LeakSanitizer // reports false-positive mem leaks when secondary processes are forked during execution). ++mPassedTestCount; } else { mFailedTests.push_back(test.getTestName()); if (test.crashed()) { mPidDumps.push_back(test.getPid()); } mFinalResult = 1; } // concatenate individual log to main log const string& individualLog = test.getWorkerLog(); ifstream infile(individualLog); if (!infile.is_open()) { std::cout << "Could not open " << individualLog << " for reading" << std::endl; return; } const string& mainLog = getLogFileName(); ofstream outfile(mainLog, ios_base::app); if (!outfile.is_open()) { std::cout << "Could not open " << mainLog << " for writing" << std::endl; return; } outfile << infile.rdbuf(); } void GTestParallelRunner::summary() { /* * Example: * * [==========] 26 tests from 2 test suites ran. (5820142 ms total) * [ PASSED ] 24 tests. * [ FAILED ] 2 tests, listed below: * [ FAILED ] SuiteFoo.TestBar * [ FAILED ] SuiteBazz.TestFred * * 2 FAILED TESTS * * YOU HAVE 3 DISABLED TESTS */ using namespace std::chrono; auto timeSpent = duration_cast(system_clock::now() - mStartTime).count(); std::cout << "[==========] " << (mPassedTestCount + mFailedTests.size()) << " tests from " << mTestSuiteCount << " test suites ran. (" << timeSpent << " ms total)\n" << "[ PASSED ] " << mPassedTestCount << " tests.\n"; if (!mFailedTests.empty()) { std::cout << "[ FAILED ] " << mFailedTests.size() << " tests, listed below:\n"; for (const auto& t : mFailedTests) { std::cout << "[ FAILED ] " << t << '\n'; } std::cout << "\n " << mFailedTests.size() << " FAILED TESTS\n"; } if (mDisabledTestCount) { std::cout << '\n'; std::cout << " YOU HAVE " << mDisabledTestCount << " DISABLED TESTS\n"; } if (!mPidDumps.empty()) { std::cout << '\n'; for_each(mPidDumps.begin(), mPidDumps.end(), [](int p) { std::cout << "<< PROCESS SIGNALED >> (PID:" << p << ")\n"; }); } std::cout << std::endl; } std::string getLogFileName(size_t useIdx, std::string useDescription) { if (useDescription.empty()) return getDefaultLogName(); std::replace(useDescription.begin(), useDescription.end(), '/', '_'); return Utils::replace(getDefaultLogName(), ".", '.' + std::to_string(useIdx) + '.' + std::move(useDescription) + '.'); } std::string getCurrentTimestamp(bool includeDate) { using std::chrono::system_clock; auto currentTime = system_clock::now(); constexpr const auto buffSz = 80; char buffer[buffSz]; auto transformed = currentTime.time_since_epoch().count() / 1000000; auto millis = transformed % 1000; m_time_t tt = system_clock::to_time_t ( currentTime ); struct tm timeinfo; m_localtime(tt, &timeinfo); string fmt = "%H:%M:%S"; if (includeDate) fmt = "%Y-%m-%d_" + fmt; size_t timeStrSz = strftime(buffer, buffSz, fmt.c_str(), &timeinfo); snprintf(buffer + timeStrSz, buffSz - timeStrSz, ":%03d", (int)millis); return std::string(buffer); } } // namespace mega sdk-10.11.0/tests/gtest_common.h000066400000000000000000000173251516266226600164700ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include "mega/process.h" namespace mega { /// class ProcessWithInterceptedOutput /// /// Run a process and intercept its stdout and stderr. /// This is useful for deriving from it, and reacting to each out/err line. By default /// it simply outputs them to console. class ProcessWithInterceptedOutput { public: virtual ~ProcessWithInterceptedOutput() = default; bool run(const std::vector& args, const std::unordered_map& env); bool finishedRunning(); // false when not started or still running int getExitCode(); // 0 for success, -1 when not started int getPid() const; protected: virtual void clearBeforeRun() {} // override for member cleanup virtual void onOutLine(std::string&& line); virtual void onErrLine(std::string&& line); virtual void onExit() {} private: void intercept(const unsigned char* data, size_t len, std::string& buffer, std::function onLine); std::unique_ptr mProc; std::string mOutBuffer; std::string mErrBuffer; bool mExitReported = false; }; /// class GTestListProc /// /// Build a list of tests and capture a few other details from the output of a program /// built with GTest (a.k.a. googletest) library and ran with '--gtest_list_tests'. class GTestListProc : public ProcessWithInterceptedOutput { public: std::deque getTestsToRun() const { return mTestsToRun; } size_t getTestSuiteCount() const { return mTestSuiteCount; } size_t getDisabledTestCount() const { return mDisabledTestCount; } private: void clearBeforeRun() override { mTestsToRun.clear(); mTestSuiteCount = mDisabledTestCount = 0u; mCurrentSuite.clear(); } void onOutLine(std::string&& line) override; std::deque mTestsToRun; size_t mTestSuiteCount = 0u; std::string mCurrentSuite; size_t mDisabledTestCount = 0u; }; /// class GTestProc /// /// Run a single GTest (a.k.a. googletest) and interpret its output. class GTestProc : public ProcessWithInterceptedOutput { public: bool run(const std::vector& args, const std::unordered_map& env, size_t workerIdx, std::string&& name); bool passed() const { return mStatus == TestStatus::TEST_PASSED; } bool crashed() const { return mStatus == TestStatus::CRASHED; } std::string getRelevantOutput() { return finishedRunning() ? mRelevantOutput : std::string(); } const std::string& getTestName() const { return mTestName; } std::string getWorkerLog() const; void setCustomPathForPid(const std::string& path) { mCustomPathForPid = path; } void hideMemLeaks(bool hide) { mHideMemLeaks = hide; } private: void clearBeforeRun() override { mRelevantOutput.clear(); mOutputIsRelevant = false; } void onOutLine(std::string&& line) override; void onErrLine(std::string&& line) override; void onExit() override; void printToScreen(std::ostream& screen, const std::string& msg) const; std::string mTestName; size_t mWorkerIdx = 0u; std::string mCustomPathForPid; enum class TestStatus { NOT_STARTED, RUNNING, TEST_PASSED, TEST_FAILED, CRASHED, }; TestStatus mStatus = TestStatus::NOT_STARTED; std::string mRelevantOutput; bool mOutputIsRelevant = false; bool mHideMemLeaks = false; // leave memory leaks in printouts or filter them out bool mIncomingMemLeaks = false; }; /// class RuntimeArgValues /// /// Parse and normalize runtime arguments for main process and worker processes. class RuntimeArgValues { public: RuntimeArgValues(std::vector&& args, std::vector>&& accEnvVars); virtual ~RuntimeArgValues() = default; bool isValid() const { return mRunMode != TestRunMode::INVALID; } bool isListOnly() const { return mRunMode == TestRunMode::LIST_ONLY; } bool isMainProcOnly() const { return mRunMode == TestRunMode::MAIN_PROCESS_ONLY; } bool isMainProcWithWorkers() const { return mRunMode == TestRunMode::MAIN_PROCESS_WITH_WORKERS; } bool isWorker() const { return mRunMode == TestRunMode::WORKER_PROCESS; } bool isHelp() const { return mRunMode == TestRunMode::HELP; } void printHelp() const; std::string getLog() const; size_t getInstanceCount() const { return mInstanceCount; } const std::string& getCustomApiUrl() const { return mApiUrl; } const std::string& getCustomUserAget() const { return mUserAgent; } std::vector getArgsForWorker(const std::string& testToRun, size_t subprocIdx) const; std::unordered_map getEnvVarsForWorker(size_t subprocIdx) const; std::string getExecutable() const { return mArgs.empty() ? std::string() : mArgs[0]; } std::string getFilter() const { return mGtestFilterIdx < mArgs.size() ? mArgs[mGtestFilterIdx] : std::string(); } inline size_t getAccountsPerInstance() const { return mAccEnvVars.size(); } bool hidingWorkerMemLeaks() const { return mHideWorkerMemLeaks; } protected: std::vector mArgs; // filled only in main process virtual void printCustomOptions() const {} virtual void printCustomEnvVars() const {} static string buildAlignedHelpString(const string& var, const std::vector& descr); private: bool validateRequirements(const std::string& emailTemplate); bool breakTemplate(const std::string& tplt); bool usingTemplate() const; size_t mInstanceCount = 0u; std::tuple mEmailTemplateValues; // extracted from "foo+bar-{1-15}@mega.co.nz" size_t mCurrentInstance = SIZE_MAX; std::string mTestName; std::string mApiUrl; std::string mUserAgent; size_t mGtestFilterIdx = SIZE_MAX; // avoid a search bool mHideWorkerMemLeaks = false; enum class TestRunMode { INVALID, LIST_ONLY, MAIN_PROCESS_ONLY, MAIN_PROCESS_WITH_WORKERS, // pass --INSTANCES and use an email template WORKER_PROCESS, // spawned by the main process, ran with --INSTANCE HELP, // show Help only }; TestRunMode mRunMode = TestRunMode::INVALID; std::vector> mAccEnvVars; static constexpr size_t maxWorkerCount = 256u; // reasonable limit used for validation only, not really a constraint }; /// class GTestParallelRunner /// /// Run the tests requested by runtime args 1-by-1 in worker processes, collect and /// interpret their output, and determine the final status. class GTestParallelRunner { public: GTestParallelRunner(RuntimeArgValues&& commonArgs); ~GTestParallelRunner(); int run(); void useWorkerOutputPathForPid(std::string&& basePath) { mWorkerOutPath.swap(basePath); } private: bool findTests(); size_t getNexatAvailableInstance(); bool runTest(size_t workerIdx, std::string&& name); void processFinishedTest(GTestProc& test); void summary(); RuntimeArgValues mCommonArgs; std::string mWorkerOutPath; std::deque mTestsToRun; std::map mRunningGTests; int mFinalResult = 0; #ifdef WIN32 HANDLE mJobObject = nullptr; #endif // summary std::chrono::time_point mStartTime; size_t mTestSuiteCount = 0u; size_t mTotalTestCount = 0u; size_t mPassedTestCount = 0u; std::vector mFailedTests; size_t mDisabledTestCount = 0u; std::vector mPidDumps; }; std::string getLogFileName(size_t useIdx = SIZE_MAX, std::string useDescription = std::string()); std::string getCurrentTimestamp(bool includeDate = false); } // namespace mega sdk-10.11.0/tests/integration/000077500000000000000000000000001516266226600161345ustar00rootroot00000000000000sdk-10.11.0/tests/integration/CMakeLists.txt000066400000000000000000000073251516266226600207030ustar00rootroot00000000000000add_executable(test_integration) target_sources(test_integration PRIVATE easy_curl.h env_var_accounts.h SdkTest_test.h SdkTestNodesSetUp.h SdkTestSyncNodesOperations.h SdkTestSyncPrevalidation.h test.h integration_test_utils.h mock_listeners.h sdk_test_share.h easy_curl.cpp env_var_accounts.cpp main.cpp SdkTest_test.cpp SdkTestGetRecentActions_test.cpp SdkTestNodesSetUp.cpp SdkTestSyncNodesOperations.cpp SdkTestFilter_test.cpp SdkTestCreateAccount_test.cpp SdkTestTransferStats_test.cpp sdk_test_user_alerts.cpp sdk_test_user_attributes.cpp SdkTestSyncRootOperations_test.cpp SdkTestSyncUploadThrottling_test.cpp SdkTestChangeLocalSyncRoot_test.cpp SdkTestSyncPrevalidation.cpp SdkTestSyncPrevalidation_test.cpp Sync_test.cpp one_question_survey_test.cpp integration_test_utils.cpp DisableBackupSync_test.cpp SdkTestTransferMaxSpeeds_test.cpp sdk_test_file_path.cpp sdk_test_node_tags.cpp sdk_test_node_tags.h sdk_test_network.cpp sdk_test_sync_upload_operations_test.cpp backup_test_utils.h sdk_test_backup_upload_operations_test.cpp SdkTestDeviceCenter_test.cpp SdkTestSyncLocalOperations_test.cpp SdkTestSyncNodeAttribute_test.cpp SdkTestCaseInsensitiveCollision_test.cpp backup_sync_operations_test.cpp sdk_test_lockless_cs_channel.cpp sdk_test_pitag.cpp SdkTestFolderController_test.cpp SdkTestGetChildrenLexicographic_test.cpp sdk_test_share.cpp sdk_test_share_nested.cpp passwordManager/SdkTestPasswordManager.cpp passwordManager/SdkTestPasswordManager.h passwordManager/SdkTestPasswordManagerBaseNode_test.cpp passwordManager/SdkTestPasswordManagerImport_test.cpp passwordManager/SdkTestPasswordManagerPassFolderCRUD_test.cpp passwordManager/SdkTestPasswordManagerPassNodeCRUD_test.cpp passwordManager/SdkTestPasswordManagerCreditCardNodeCRUD_test.cpp ) target_sources_conditional(test_integration FLAG USE_LIBUV PRIVATE sdk_server_test_utils.h sdk_server_test_utils.cpp sdk_test_ftp_server.cpp sdk_test_http_server.cpp ) target_sources_conditional(test_integration FLAG ENABLE_ISOLATED_GFX PRIVATE IsolatedGfx_test.cpp ) # Load common test sources. include(common/common.cmake) # Load File Service test sources. include(file_service/file_service.cmake) # Link with SDKlib target_link_libraries(test_integration PRIVATE MEGA::SDKlib) # Link with the common and tools interface libraries for the tests. target_link_libraries(test_integration PRIVATE MEGA::test_tools MEGA::test_common ) target_include_directories(test_integration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) # Adjust compilation flags for warnings and errors target_platform_compile_options( TARGET test_integration WINDOWS /bigobj /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET test_integration WINDOWS /WX UNIX $<$: -Werror> ) endif() # Integration tests require the following files to work file(GLOB TESTING_AUX_FILES "test-data/*") add_custom_command( TARGET test_integration POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${TESTING_AUX_FILES} $ COMMENT "Copying test files for integration tests." ) if(TARGET gfxworker) add_custom_command( TARGET test_integration POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $ COMMENT "Copying gfxworker for integration tests." ) endif() sdk-10.11.0/tests/integration/DisableBackupSync_test.cpp000066400000000000000000000204661516266226600232350ustar00rootroot00000000000000/** * @file * @brief This file contains tests that involve operations on disabled backup syncs. */ #ifdef ENABLE_SYNC #include "gtest_common.h" #include "integration/mock_listeners.h" #include "integration_test_utils.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" #include #include #include namespace fs = std::filesystem; using namespace sdk_test; using namespace testing; /** * @class DisableBackupSync * @brief Test fixture that creates a backup sync, waits until all the files have been uploaded and * then disables it. */ class DisableBackupSync: public SdkTest { public: static constexpr auto MAX_TIMEOUT = 3min; // Timeout for operations in this tests suite static constexpr auto TIME_DELTA_CONSECUTIVE_TRIES = 10s; // Time to wait between consecutive // calls in a waitFor or some sleeps void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); createInitialLocalFiles(); mBackupId = backupFolder(megaApi[0].get(), path_u8string(getLocalTmpDir())); ASSERT_NE(mBackupId, UNDEF); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); ASSERT_TRUE(disableSync(megaApi[0].get(), mBackupId)); } void TearDown() override { if (mBackupId != UNDEF) { removeSync(megaApi[0].get(), mBackupId); } SdkTest::TearDown(); } /** * @brief Waits until all direct successors from both remote and local roots of the sync match. * Name and fingerprint are compared for the match. * * Asserts false if a timeout is exceeded. */ void waitForSyncToMatchCloudAndLocal() const { const auto areLocalAndCloudSynched = [this]() -> bool { const auto [childrenCloudNamesAndFingerprints, _] = getCloudFirstChildrenNamesAndFingerprints(megaApi[0].get(), getSync()->getMegaHandle()); const auto childrenLocalNamesAndFingerprints = getLocalFirstChildrenNamesAndFingerprints(megaApi[0].get()); return childrenCloudNamesAndFingerprints && Value(childrenLocalNamesAndFingerprints, UnorderedElementsAreArray(*childrenCloudNamesAndFingerprints)); }; ASSERT_TRUE(waitFor(areLocalAndCloudSynched, MAX_TIMEOUT, TIME_DELTA_CONSECUTIVE_TRIES)); } /** * @brief Resume the sync, wait for local and cloud to match assert it is running and there are * no stall issues. */ void resumeAndValidateOK() const { ASSERT_TRUE(resumeSync(megaApi[0].get(), getBackupId())); std::this_thread::sleep_for(TIME_DELTA_CONSECUTIVE_TRIES); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_TRUE(getStalls(megaApi[0].get()).empty()); } /** * @brief Waits until the node at the given path has the given size. * * @param nodePath The path to the node relative to the backup root path * @param fileSize The expected size for that node */ void waitForCloudNodeToMatchSize(const std::string_view nodePath, const int64_t nodeSize) const { const auto confirmSize = [nodePath, nodeSize, this]() -> bool { const auto node = getNodeByPath(nodePath); return node && node->getSize() == nodeSize; }; waitFor(confirmSize, MAX_TIMEOUT, TIME_DELTA_CONSECUTIVE_TRIES); } /** * @brief Get the node inside the backup in the given relative path * * Returns nullptr if it is not found or there is no valid backup sync running. */ std::unique_ptr getNodeByPath(const std::string_view path) const { const auto backup = getSync(); if (!backup) return nullptr; const std::string filePath = backup->getLastKnownMegaFolder() + "/"s + std::string(path); return std::unique_ptr(megaApi[0]->getNodeByPath(filePath.c_str())); } /** * @brief Get the names of the first successors in the current local sync root. */ std::vector getLocalFirstChildrenNames() const { return getLocalFirstChildrenNames_if(getLocalSyncRoot().value_or(getLocalTmpDir()), [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } /** * @brief Get the names and fingerprints of the first successors in the current local sync root. */ std::vector getLocalFirstChildrenNamesAndFingerprints(MegaApi* megaApi) const { return getLocalFirstChildrenNamesAndFingerprints_if( megaApi, getLocalSyncRoot().value_or(getLocalTmpDir()), [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } handle getBackupId() const { return mBackupId; } /** * @brief Returns the initiated sync object */ std::unique_ptr getSync() const { return std::unique_ptr(megaApi[0]->getSyncByBackupId(mBackupId)); } /** * @brief Returns the current sync state if initiated */ std::optional getSyncRunState() const { const auto sync = getSync(); if (!sync) return {}; return sync->getRunState(); } /** * @brief Returns the current path the sync is using as root. If there is no sync, nullopt is * returned */ std::optional getLocalSyncRoot() const { const auto sync = getSync(); if (!sync) return {}; return sync->getLocalFolder(); } static const fs::path& getLocalTmpDir() { // Prevent parallel test from the same suite writing to the same dir thread_local const fs::path localTmpDir{"./DISABLE_BACKUP_SYNC_AUX_TMP_DIR_" + getThisThreadIdStr()}; return localTmpDir; } private: LocalTempDir tmpDir{getLocalTmpDir()}; handle mBackupId{UNDEF}; void createInitialLocalFiles() const { sdk_test::createFile(getLocalTmpDir() / "testFile", 1); } }; /** * @brief DisableBackupSync.RemoveLocalFile: * - Remove file while the sync is disabled * - Resume it * - Confirm the file gets deleted on the cloud and the backup keeps running */ TEST_F(DisableBackupSync, RemoveLocalFile) { fs::remove(getLocalTmpDir() / "testFile"); ASSERT_NO_FATAL_FAILURE(resumeAndValidateOK()); ASSERT_TRUE(getLocalFirstChildrenNames().empty()); } /** * @brief DisableBackupSync.ModifyLocalFile: * - Remove file while the sync is disabled * - Resume it * - Confirm the file gets updated on the cloud and the backup keeps running */ TEST_F(DisableBackupSync, ModifyLocalFile) { sdk_test::createFile(getLocalTmpDir() / "testFile", 5); ASSERT_NO_FATAL_FAILURE(resumeAndValidateOK()); ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testFile")); ASSERT_NO_FATAL_FAILURE(waitForCloudNodeToMatchSize("testFile", 5)); } /** * @brief DisableBackupSync.CreateNewLocalFile: * - Create a new file while the sync is disabled * - Resume it * - Confirm the file gets uploaded to the cloud and the backup keeps running */ TEST_F(DisableBackupSync, CreateNewLocalFile) { sdk_test::createFile(getLocalTmpDir() / "testFile2", 1); ASSERT_NO_FATAL_FAILURE(resumeAndValidateOK()); ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testFile", "testFile2")); } /** * @brief DisableBackupSync.RenameLocalFile: * - Rename a local file while the sync is disabled. * - Resume the sync. * - Confirm that the move is detected and the backup keeps running. */ TEST_F(DisableBackupSync, RenameLocalFile) { fs::rename(getLocalTmpDir() / "testFile", getLocalTmpDir() / "testFile2"); ASSERT_NO_FATAL_FAILURE(resumeAndValidateOK()); ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testFile2")); } #endif sdk-10.11.0/tests/integration/IsolatedGfx_test.cpp000066400000000000000000000200471516266226600221130ustar00rootroot00000000000000#include "sdk_test_utils.h" #include "SdkTest_test.h" #include namespace { using FIBITMAPPtr = std::unique_ptr; FIBITMAPPtr load(const fs::path& image, FREE_IMAGE_FORMAT fif) { #ifdef _WIN32 return FIBITMAPPtr{FreeImage_LoadU(fif, image.c_str(), 0), &FreeImage_Unload}; #else return FIBITMAPPtr{FreeImage_Load(fif, image.c_str(), 0), &FreeImage_Unload}; #endif } bool isTransparency(const fs::path& image, FREE_IMAGE_FORMAT fif) { if (const auto dib = load(image, fif); dib) { return FreeImage_IsTransparent(dib.get()) == TRUE; } else { LOG_err << "Failed to load image"; return false; } } // Get meta data tag count. -1 if there are errors int getMetadataCount(const fs::path& image, FREE_IMAGE_FORMAT fif) { if (const auto dib = load(image, fif); dib) { return static_cast(FreeImage_GetMetadataCount(FIMD_EXIF_MAIN, dib.get())); } else { LOG_err << "Failed to load image"; return -1; } }; } class SdkTestIsolatedGfx : public SdkTest { protected: void SetUp() override; void TearDown() override; static constexpr const char* SOURCE = "test-data/gfx-processing-crash/SNC-2462__17D1439.tif"; static constexpr const char* CRASH_IMAGE = "crash.tif"; static constexpr const char* CRASH_THUMBNAIL = "crash_thumbnail.jpg"; static constexpr const char* CRASH_PREVIEW = "crash_preview.jpg"; static constexpr const char* INVALID_IMAGE = "invalid.jpg"; static constexpr const char* INVALID_THUMBNAIL = "invalid_thumbnail.jpg"; static constexpr const char* GOOD_IMAGE = "logo.png"; static constexpr const char* GOOD_THUMBNAIL = "logo_thumbnail.png"; static constexpr const char* GOOD_PREVIEW = "logo_preview.png"; static constexpr const char* TRANSPARENCY_IMAGE = "transparency.png"; static constexpr const char* TRANSPARENCY_THUMBNAIL = "transparency_thumbnail.png"; static constexpr const char* TRANSPARENCY_PREVIEW = "transparency_preview.jpg"; static constexpr const char* ORIENTATION_IMAGE = "orientation.jpg"; static constexpr const char* ORIENTATION_THUMBNAIL = "orientation_thumbnail.jpg"; static constexpr const char* ORIENTATION_PREVIEW = "orientation_preview.jpg"; }; void SdkTestIsolatedGfx::SetUp() { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); } void SdkTestIsolatedGfx::TearDown() { SdkTest::TearDown(); } /** * @brief GfxProcessingContinueSuccessfullyAfterCrash * 1. create thumbnail successfully * 2. create thumbnail and preview of a image which causes a gfx process crash. * 3. create preview still successfully after the crash * 4. create thumbnail of a not valid image expects false. * * @ Note: * Basically a createThumbnail/createPreview might fail due to the following reason: * * 1. The GFX process was already crashed (not running), therefore the error is * the pipe couldn't be connected * 2. The GFX process crashed while processing, therefore the error is others. * * For the 1st case, we'll retry so it is handled. For the 2nd case, we don't retry as * we don't want to retry processing bad images which cause a crash. We have problems * here because gfxworker process uses multiple thread model. * When it is processing multiple GFX calls and crashes, we don't know which call is * processing bad images. So simply all calls are not retried. * When the previous call results in a crash, the following immediate call may still * connect to the pipe as the crash takes time to shutdown the whole process. Therefore * the second call is dropped as well though it should be retried. * * It has been discussed and we don't want to deal with these known problem at the moment * as we want to start with simple. It happens rarely and the side effect is limited (thumbnail lost). * We'll improve it when we find it is necessary. */ TEST_F(SdkTestIsolatedGfx, GfxProcessingContinueSuccessfullyAfterCrash) { LOG_info << "___TEST GfxProcessingContinueSuccessfullyAfterCrash"; MegaApi* api = megaApi[0].get(); // 1. Create a thumbnail successfully ASSERT_TRUE(getFileFromArtifactory(std::string{"test-data/"} + GOOD_IMAGE, GOOD_IMAGE)); ASSERT_TRUE(api->createThumbnail(GOOD_IMAGE, GOOD_THUMBNAIL)) << "create thumbnail should succeed"; // 2. Create thumbnail and preview of a image which result in a crash // the image is selected by testing, thus not guaranteed. we'd either // find another media file or need another alternative if it couldn't // consistently result in a crash // Get the test media file fs::path destination{CRASH_IMAGE}; ASSERT_TRUE(getFileFromArtifactory(SOURCE, destination)); ASSERT_TRUE(fs::exists(destination)); // Gfx process would crash due to the bad media file ASSERT_FALSE(api->createThumbnail(CRASH_IMAGE, CRASH_THUMBNAIL)); ASSERT_FALSE(api->createPreview(CRASH_IMAGE, CRASH_PREVIEW)); // Don't make a call too quickly. Workaround: see note in test case desription std::this_thread::sleep_for(std::chrono::milliseconds{200}); // 3. Create a preview successfully ASSERT_TRUE(api->createPreview(GOOD_IMAGE, GOOD_PREVIEW)) << "create preview should succeed"; // 4. Create thumbnail of a not valid image ASSERT_TRUE(getFileFromArtifactory(std::string{"test-data/"} + INVALID_IMAGE, INVALID_IMAGE)); ASSERT_FALSE(api->createThumbnail(INVALID_IMAGE, INVALID_THUMBNAIL)) << "create invalid image's thumbnail should fail"; LOG_info << "___TEST GfxProcessingContinueSuccessfullyAfterCrash end___"; } TEST_F(SdkTestIsolatedGfx, ThumbnailSupportTransparency) { LOG_info << "___TEST ThumbnailSupportTransparency"; // Download test data ASSERT_TRUE( getFileFromArtifactory(std::string{"test-data/"} + TRANSPARENCY_IMAGE, TRANSPARENCY_IMAGE)); // Thumbnail and preview MegaApi* api = megaApi[0].get(); ASSERT_TRUE(api->createThumbnail(TRANSPARENCY_IMAGE, TRANSPARENCY_THUMBNAIL)) << "create thumbnail should succeed"; ASSERT_TRUE(api->createPreview(TRANSPARENCY_IMAGE, TRANSPARENCY_PREVIEW)) << "create preview should succeed"; // Use this to ensure FreeImage library is initialized for once [[maybe_unused]] const auto a = ::mega::makeUniqueFrom(MegaGfxProvider::createInternalInstance()); // Check all are transparency images ASSERT_TRUE(isTransparency(fs::path{TRANSPARENCY_IMAGE}, FIF_PNG)); ASSERT_EQ(FreeImage_GetFileType(TRANSPARENCY_THUMBNAIL, 0), FIF_PNG); ASSERT_TRUE(isTransparency(fs::path{TRANSPARENCY_THUMBNAIL}, FIF_PNG)); ASSERT_EQ(FreeImage_GetFileType(TRANSPARENCY_PREVIEW, 0), FIF_JPEG); ASSERT_FALSE(isTransparency(fs::path{TRANSPARENCY_PREVIEW}, FIF_JPEG)); LOG_info << "___TEST ThumbnailSupportTransparency end___"; } TEST_F(SdkTestIsolatedGfx, MetaDataIsRemoved) { LOG_info << "___TEST MetaDataIsRemoved"; // Download test data ASSERT_TRUE( getFileFromArtifactory(std::string{"test-data/"} + ORIENTATION_IMAGE, ORIENTATION_IMAGE)); // Thumbnail and preview MegaApi* api = megaApi[0].get(); ASSERT_TRUE(api->createThumbnail(ORIENTATION_IMAGE, ORIENTATION_THUMBNAIL)) << "create thumbnail should succeed"; ASSERT_TRUE(api->createPreview(ORIENTATION_IMAGE, ORIENTATION_PREVIEW)) << "create preview should succeed"; // Use this to ensure FreeImage library is initialized for once [[maybe_unused]] const auto a = ::mega::makeUniqueFrom(MegaGfxProvider::createInternalInstance()); // The original image has more than one EXIF data ASSERT_GT(getMetadataCount(fs::path{ORIENTATION_IMAGE}, FIF_JPEG), 1); // The thumbnail and preview has none EXIF data ASSERT_EQ(getMetadataCount(fs::path{ORIENTATION_PREVIEW}, FIF_JPEG), 0); ASSERT_EQ(getMetadataCount(fs::path{ORIENTATION_THUMBNAIL}, FIF_JPEG), 0); LOG_info << "___TEST MetaDataIsRemoved end___"; } sdk-10.11.0/tests/integration/SdkTestCaseInsensitiveCollision_test.cpp000066400000000000000000000422571516266226600261630ustar00rootroot00000000000000/** * @brief Test collision behavior uploading files for case insensitive systems * */ #include "integration_test_utils.h" #include "mega/utils.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" class SdkTestCapitalisationCollision: public SdkTest { public: void SetUp() override; void TearDown() override; /** * @brief Create and download folders * @param remoteNames Remote folder names (created by the test) * @param localNames Local folder names after download * @param collisionResolution Method to resolve conflicts */ void testCapitalisationFolderCollision(const std::vector& remoteNames, const std::set& localNames, const int collisionResolution); /** * @brief Create a Folder with two subfolders and download them * Remote names should create a conflict when they are downloaded * As folder is downloaded and we don't know the order of the subfolders, we check for the * existence of the suffix * @param remoteNames Remote folder names (created by the test) * @param collisionResolution Method to resolve conflicts */ void testCapitalisationDownloadFolderWithCollision(const std::vector& remoteNames, const size_t numElementsWithSuffix, const size_t numElementsWithoutSuffix, const int collisionResolution); /** * @brief Create and download two files * @param remoteNames Remote file names (created by the test) * @param localNames Local file names after download * @param collisionResolution Method to resolve conflicts */ void testCapitalisationFile(const std::vector& remoteNames, const std::set& localNames, const int collisionResolution); bool mIsCaseInsensitive{false}; private: std::filesystem::path mFolderDestination; }; void SdkTestCapitalisationCollision::SetUp() { SdkTest::SetUp(); mFolderDestination = fs::current_path() / "Destination"; std::filesystem::create_directory(mFolderDestination); std::unique_ptr<::mega::FileSystemAccess> fileSystemAccess = ::mega::createFSA(); LocalPath path = LocalPath::fromAbsolutePath(path_u8string(fs::current_path())); auto value = isCaseInsensitive(path, fileSystemAccess.get()); mIsCaseInsensitive = value.has_value() ? value.value() : false; } void SdkTestCapitalisationCollision::TearDown() { SdkTest::TearDown(); std::filesystem::remove_all(mFolderDestination); } void SdkTestCapitalisationCollision::testCapitalisationFolderCollision( const std::vector& remoteNames, const std::set& localNames, const int collisionResolution) { ASSERT_GE(remoteNames.size(), 2u); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode.get()); MegaHandle hfolder1 = createFolder(0, remoteNames[0].c_str(), rootnode.get()); ASSERT_NE(hfolder1, INVALID_HANDLE); std::unique_ptr n1(megaApi[0]->getNodeByHandle(hfolder1)); ASSERT_TRUE(n1.get()); MegaHandle hfolder2 = createFolder(0, remoteNames[1].c_str(), rootnode.get()); ASSERT_NE(hfolder2, INVALID_HANDLE); std::unique_ptr n2(megaApi[0]->getNodeByHandle(hfolder2)); ASSERT_TRUE(n2.get()); auto errCode = sdk_test::downloadNode(megaApi[0].get(), n1.get(), mFolderDestination, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, collisionResolution); ASSERT_EQ(errCode, API_OK); errCode = sdk_test::downloadNode(megaApi[0].get(), n2.get(), mFolderDestination, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, collisionResolution); ASSERT_EQ(errCode, API_OK); size_t numElements = 0; for (const auto& element: std::filesystem::directory_iterator(mFolderDestination)) { numElements++; const auto fileName = path_u8string(element.path().filename()); ASSERT_NE(localNames.find(fileName), localNames.end()) << "Unexpected file name: " << fileName; } ASSERT_EQ(numElements, localNames.size()); } void SdkTestCapitalisationCollision::testCapitalisationDownloadFolderWithCollision( const std::vector& remoteNames, const size_t numElementsWithSuffix, const size_t numElementsWithoutSuffix, const int collisionResolution) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode.get()); MegaHandle hTest = createFolder(0, "Test", rootnode.get()); std::unique_ptr testNode(megaApi[0]->getNodeByHandle(hTest)); ASSERT_TRUE(testNode.get()); MegaHandle hfolder1 = createFolder(0, remoteNames[0].c_str(), testNode.get()); ASSERT_NE(hfolder1, INVALID_HANDLE); MegaHandle hfolder2 = createFolder(0, remoteNames[1].c_str(), testNode.get()); ASSERT_NE(hfolder2, INVALID_HANDLE); auto errCode = sdk_test::downloadNode(megaApi[0].get(), testNode.get(), mFolderDestination, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, collisionResolution); ASSERT_EQ(errCode, API_OK); size_t elementsWithSuffix = 0; size_t elementsWithoutSuffix = 0; std::string suffix{}; switch (collisionResolution) { case MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN: suffix = ".old1"; break; case MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N: suffix = "(1)"; break; case MegaTransfer::COLLISION_RESOLUTION_OVERWRITE: suffix = "No suffix"; break; } for (const auto& element: std::filesystem::directory_iterator(mFolderDestination / "Test")) { if (path_u8string(element.path().filename()).find(suffix) != std::string::npos) { elementsWithSuffix++; } else { elementsWithoutSuffix++; } } ASSERT_EQ(elementsWithoutSuffix, numElementsWithoutSuffix); ASSERT_EQ(elementsWithSuffix, numElementsWithSuffix); } void SdkTestCapitalisationCollision::testCapitalisationFile( const std::vector& remoteNames, const std::set& localNames, const int collisionResolution) { std::string fileName{"f.txt"}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode.get()); ASSERT_TRUE(createFile(fileName, false)) << "Couldn't create " << fileName; MegaHandle uploadedNode1 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode1, fileName.c_str(), rootnode.get(), remoteNames[0].c_str(), ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr n1(megaApi[0]->getNodeByHandle(uploadedNode1)); ASSERT_TRUE(n1.get()); MegaHandle uploadedNode2 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode2, fileName.c_str(), rootnode.get(), remoteNames[1].c_str(), ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr n2(megaApi[0]->getNodeByHandle(uploadedNode2)); ASSERT_TRUE(n2.get()); auto errCode = sdk_test::downloadNode(megaApi[0].get(), n1.get(), mFolderDestination, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, collisionResolution); ASSERT_EQ(errCode, API_OK); errCode = sdk_test::downloadNode(megaApi[0].get(), n2.get(), mFolderDestination, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, collisionResolution); ASSERT_EQ(errCode, API_OK); size_t numElements = 0; for (const auto& element: std::filesystem::directory_iterator(mFolderDestination)) { numElements++; const auto fileName = path_u8string(element.path().filename()); ASSERT_NE(localNames.find(fileName), localNames.end()) << "Unexpected file name: " << fileName; } ASSERT_EQ(numElements, localNames.size()); } /** * @brief TEST_F CapitalisationCollistionFileOldN * * Steps: * - Upload two files with names "File.txt" and "FILE.TXT" * - Download with conflict resolution EXISTING_TO_OLDN * - Check result "File.old1.txt" and "FILE.TXT" */ TEST_F(SdkTestCapitalisationCollision, CapitalisationCollistionFileOldN) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"File.old1.txt", "FILE.TXT"}; } else { localNames = {"File.txt", "FILE.TXT"}; } std::vector remoteNames{"File.txt", "FILE.TXT"}; testCapitalisationFile(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN); } /** * @brief TEST_F CapitalisationCollistionFileNewWithN * * Steps: * - Upload two files with names "File.txt" and "FILE.TXT" * - Download with conflict resolution NEW_WITH_N * - Check result "File.txt" and "FILE (1).TXT" */ TEST_F(SdkTestCapitalisationCollision, CapitalisationCollistionFileNewWithN) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"File.txt", "FILE (1).TXT"}; } else { localNames = {"File.txt", "FILE.TXT"}; } std::vector remoteNames{"File.txt", "FILE.TXT"}; testCapitalisationFile(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); } /** * @brief TEST_F CapitalisationCollistionFileOverwrite * * Steps: * - Upload two files with names "File.txt" and "FILE.TXT" * - Download with conflict resolution OVERWRITE * - Check result "FILE.TXT" */ TEST_F(SdkTestCapitalisationCollision, CapitalisationCollistionFileOverwrite) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"FILE.TXT"}; } else { localNames = {"File.txt", "FILE.TXT"}; } std::vector remoteNames{"File.txt", "FILE.TXT"}; testCapitalisationFile(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE); } /** * @brief TEST_F FolderCapitalisationCollistionNewWithN * * Steps: * - Create in the cloud two folders with names "Folder" and "FOLDER" * - Download with conflict resolution NEW_WITH_N * - Check result "Folder" and "FOLDER (1)" */ TEST_F(SdkTestCapitalisationCollision, FolderCapitalisationCollistionNewWithN) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"Folder", "FOLDER (1)"}; } else { localNames = {"Folder", "FOLDER"}; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationFolderCollision(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); } /** * @brief TEST_F FolderCapitalisationCollistionOldN * * Steps: * - Create in the cloud two folders with names "Folder" and "FOLDER" * - Download with conflict resolution EXISTING_TO_OLDN * - Check result "Folder.old1" and "FOLDER" */ TEST_F(SdkTestCapitalisationCollision, FolderCapitalisationCollistionOldN) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"Folder.old1", "FOLDER"}; } else { localNames = {"Folder", "FOLDER"}; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationFolderCollision(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN); } /** * @brief TEST_F FolderCapitalisationCollistionOverwrite * * Steps: * - Create in the cloud two folders with names "Folder" and "FOLDER" * - Download with conflict resolution OVERWRITE * - Check result "Folder" */ TEST_F(SdkTestCapitalisationCollision, FolderCapitalisationCollistionOverwrite) { std::set localNames; if (mIsCaseInsensitive) { localNames = {"Folder"}; } else { localNames = {"Folder", "FOLDER"}; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationFolderCollision(remoteNames, localNames, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE); } /** * @brief TEST_F DownloadFolderWithCapitalisationCollisionNewWithN * * Steps: * - Create in the cloud a Folder "Test" with two subfolders with names "Folder" and "FOLDER" * - Download with "Test" conflict resolution NEW_WITH_N * - Check result (two folders, one with suffix (1) order is not guaranteed) */ TEST_F(SdkTestCapitalisationCollision, DownloadFolderWithCapitalisationCollisionNewWithN) { size_t numElementWithSuffix = 0; size_t numElementWithoutSuffix = 0; if (mIsCaseInsensitive) { numElementWithSuffix = 1; numElementWithoutSuffix = 1; } else { numElementWithoutSuffix = 2; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationDownloadFolderWithCollision(remoteNames, numElementWithSuffix, numElementWithoutSuffix, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); } /** * @brief TEST_FDownloadFolderWithCapitalisationCollisionOldN * * Steps: * - Create in the cloud a Folder "Test" with two subfolders with names "Folder" and "FOLDER" * - Download with "Test" conflict resolution EXISTING_TO_OLDN * - Check result (two folders, one with suffix (.old1) order is not guaranteed) */ TEST_F(SdkTestCapitalisationCollision, DownloadFolderWithCapitalisationCollisionOldN) { size_t numElementWithSuffix = 0; size_t numElementWithoutSuffix = 0; if (mIsCaseInsensitive) { numElementWithSuffix = 1; numElementWithoutSuffix = 1; } else { numElementWithoutSuffix = 2; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationDownloadFolderWithCollision( remoteNames, numElementWithSuffix, numElementWithoutSuffix, MegaTransfer::COLLISION_RESOLUTION_EXISTING_TO_OLDN); } /** * @brief TEST_F DownloadFolderWithCapitalisationCollisionOverwrite * * Steps: * - Create in the cloud a Folder "Test" with two subfolders with names "Folder" and "FOLDER" * - Download with "Test" conflict resolution OVERWRITE * - Check result (one folder, order is not guaranteed) */ TEST_F(SdkTestCapitalisationCollision, DownloadFolderWithCapitalisationCollisionOverwrite) { size_t numElementWithSuffix = 0; size_t numElementWithoutSuffix = 0; if (mIsCaseInsensitive) { numElementWithoutSuffix = 1; } else { numElementWithoutSuffix = 2; } std::vector remoteNames{"Folder", "FOLDER"}; testCapitalisationDownloadFolderWithCollision(remoteNames, numElementWithSuffix, numElementWithoutSuffix, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE); } sdk-10.11.0/tests/integration/SdkTestChangeLocalSyncRoot_test.cpp000066400000000000000000001233371516266226600250530ustar00rootroot00000000000000/** * @file * @brief This file contains tests for the public interfaces available to modify the local root of a * sync. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "megautils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTestNodesSetUp.h" #include #include using namespace sdk_test; using namespace testing; /** * @class SdkTestSyncLocalRootChange * @brief Test fixture designed to test the feature that allows changing the local root of a sync. */ class SdkTestSyncLocalRootChange: public SdkTestNodesSetUp { public: static constexpr auto MAX_TIMEOUT = 3min; // Timeout for operations in this tests suite void SetUp() override { SdkTestNodesSetUp::SetUp(); mBackupId = syncFolder(megaApi[0].get(), path_u8string(getLocalTmpDir()), getNodeByPath("dir1/")->getHandle()); ASSERT_NE(mBackupId, UNDEF) << "API Error adding a new sync"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); } void TearDown() override { if (mBackupId != UNDEF) { ASSERT_NO_FATAL_FAILURE(removeSync(megaApi[0].get(), mBackupId)); } SdkTestNodesSetUp::TearDown(); } /** * @brief Build a simple file tree. dir1 for sync and dir2 as auxiliary node */ const std::vector& getElements() const override { static const std::vector ELEMENTS{ DirNodeInfo("dir1") .addChild(FileNodeInfo("testFile").setSize(1)) .addChild(FileNodeInfo("testCommonFile")) .addChild(FileNodeInfo("testFile1")), DirNodeInfo("dir2")}; return ELEMENTS; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_SYNC_LOCAL_ROOT_CHANGE_AUX_DIR"}; return dirName; } /** * @brief We don't want different creation times */ bool keepDifferentCreationTimes() override { return false; } /** * @brief Waits until all direct successors from both remote and local roots of the sync match. * Name and fingerprint are compared for the match by default. * * Asserts false if a timeout is exceeded. * * @param useFingerprint If false, the nodes are matched only using the name. */ void waitForSyncToMatchCloudAndLocal(const bool useFingerprint = true) const { const auto areLocalAndCloudSynched = [this, useFingerprint]() -> bool { if (!useFingerprint) { const auto childrenCloudNames = getCloudFirstChildrenNames(megaApi[0].get(), getSync()->getMegaHandle()); const auto childrenLocalNames = getLocalFirstChildrenNames(); return childrenCloudNames && Value(childrenLocalNames, UnorderedElementsAreArray(*childrenCloudNames)); } else { const auto [childrenCloudNamesAndFingerprints, _] = getCloudFirstChildrenNamesAndFingerprints(megaApi[0].get(), getSync()->getMegaHandle()); const auto childrenLocalNamesAndFingerprints = getLocalFirstChildrenNamesAndFingerprints(megaApi[0].get()); return childrenCloudNamesAndFingerprints && Value(childrenLocalNamesAndFingerprints, UnorderedElementsAreArray(*childrenCloudNamesAndFingerprints)); } }; ASSERT_TRUE(waitFor(areLocalAndCloudSynched, MAX_TIMEOUT, 10s)); } /** * @brief Returns a vector with the names of the first successor files/directories inside the * local root. * * Hidden files (starting with .) and the debris folder are excluded */ std::vector getLocalFirstChildrenNames() const { return getLocalFirstChildrenNames_if(getLocalSyncRoot().value_or(getLocalTmpDir()), [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } /** * @brief Returns a vector with the names and fingerprints of the first successor * files/directories inside the local root. * * Hidden files (starting with .) and the debris folder are excluded */ std::vector getLocalFirstChildrenNamesAndFingerprints(MegaApi* megaApi) const { return getLocalFirstChildrenNamesAndFingerprints_if( megaApi, getLocalSyncRoot().value_or(getLocalTmpDir()), [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } /** * @brief Returns the identifier to get the sync from megaApi */ handle getBackupId() const { return mBackupId; } /** * @brief Returns the initiated sync object */ std::unique_ptr getSync() const { return std::unique_ptr(megaApi[0]->getSyncByBackupId(mBackupId)); } /** * @brief Returns the current sync state if initiated */ std::optional getSyncRunState() const { const auto sync = getSync(); if (!sync) return {}; return sync->getRunState(); } /** * @brief Returns the current path the sync is using as root. If there is no sync, nullopt is * returned */ std::optional getLocalSyncRoot() const { const auto sync = getSync(); if (!sync) return {}; return sync->getLocalFolder(); } /** * @brief Where should we put our sync locally? */ static const fs::path& getLocalTmpDir() { // Prevent parallel test from the same suite writing to the same dir thread_local const fs::path localTmpDir{"./SDK_TEST_SYNC_LOCAL_ROOT_CHANGE_AUX_LOCAL_DIR_" + getThisThreadIdStr()}; return localTmpDir; } /** * @brief Removes the node located at the give relative path */ void removeRemoteNode(const std::string& path) { const auto node = getNodeByPath(path); ASSERT_EQ(API_OK, doDeleteNode(0, node.get())); } /** * @brief Changes the local root of the sync and expects the operation to success. */ void changeLocalSyncRootNoErrors(const std::filesystem::path& newRootPath) const { NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_OK); const auto rootPath = path_u8string(newRootPath); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); ASSERT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Sets up three files inside the given directory. These are: * - testCommonFile: An exact copy of the file created originally in the cloud * - testFile: A file with the same name as the one in the cloud originally but different * contents (2 bytes of data) * - testFile2: Complete new file */ void prepareSimilarRoot(const std::filesystem::path& newRootPath) const { const auto currentRoot = getLocalSyncRoot(); ASSERT_TRUE(currentRoot); ASSERT_THAT(getLocalFirstChildrenNames(), IsSupersetOf({"testCommonFile", "testFile", "testFile1"})); // Exact copy (including the mtime) const auto source = *currentRoot / "testCommonFile"; const auto destination = newRootPath / "testCommonFile"; std::filesystem::copy(source, destination, std::filesystem::copy_options::overwrite_existing); const auto mod_time = fs::last_write_time(source); fs::last_write_time(destination, mod_time); // Empty different file std::ofstream test2(newRootPath / "testFile2", std::ios::binary); // Same name different content std::ofstream test(newRootPath / "testFile", std::ios::binary); std::vector buffer(2, 0); test.write(buffer.data(), static_cast(buffer.size())); } /** * @brief Check that the current local root of the sync has the contents specified by the * prepareSimilarRoot method */ void checkCurrentLocalMatchesSimilar() const { const auto currentRoot = getLocalSyncRoot(); ASSERT_TRUE(currentRoot); ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testCommonFile", "testFile2", "testFile")); ASSERT_EQ(std::filesystem::file_size(*currentRoot / "testFile"), 2); } /** * @brief Ensures the current local root of the sync matches the state expected after mirroring * original contests + the ones specified by prepareSimilarRoot. This includes a stall issue * with "testFile" */ void checkCurrentLocalMatchesMirror() const { ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testFile", "testCommonFile", "testFile1", "testFile2")); ASSERT_TRUE(sdk_test::waitForSyncStallState(megaApi[0].get())); ASSERT_NO_FATAL_FAILURE(thereIsAStall("testFile")); } /** * @brief Ensures there is a stall issue involving the file with the given name. * * The expected reason for the stall is: LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose */ void thereIsAStall(const std::string_view fileName) const { const auto stalls = sdk_test::getStalls(megaApi[0].get()); ASSERT_EQ(stalls.size(), 1); ASSERT_TRUE(stalls[0]); const auto& stall = *stalls[0]; EXPECT_THAT(stall.path(false, 0), EndsWith(fileName)); EXPECT_THAT( stall.reason(), MegaSyncStall::SyncStallReason::LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose); ASSERT_FALSE(HasNonfatalFailure()); } void moveLocalTmpDir(const std::filesystem::path& newLocation) { ASSERT_TRUE(mTempLocalDir.move(newLocation)) << "Error moving local tmp dir"; } protected: handle mBackupId{UNDEF}; LocalTempDir mTempLocalDir{getLocalTmpDir()}; }; /** * @brief SdkTestSyncLocalRootChange.ArgumentErrors : Validate the input error code paths. */ TEST_F(SdkTestSyncLocalRootChange, ArgumentErrors) { static const std::string logPre{"SdkTestSyncLocalRootChange.ArgumentErrors : "}; { LOG_verbose << logPre << "Giving undef backupId and undef remote handle"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, NO_SYNC_ERROR); megaApi[0]->changeSyncLocalRoot(UNDEF, nullptr, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } const std::filesystem::path newRootPath{"./newLocaRootPathForTests/"}; const LocalTempDir newRootDir(newRootPath); const auto newRootAbsPath = path_u8string(std::filesystem::absolute(newRootPath)); { LOG_verbose << logPre << "Giving undef backupId and good new root path"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, _); megaApi[0]->changeSyncLocalRoot(UNDEF, newRootAbsPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving non existent backupId and good remote handle"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, UNKNOWN_ERROR); megaApi[0]->changeSyncLocalRoot(getNodeHandleByPath("dir1"), newRootAbsPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving good backupId and a path to a file"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EACCESS, INVALID_LOCAL_TYPE); const auto filePath = path_u8string(std::filesystem::absolute(getLocalTmpDir() / "testFile")); megaApi[0]->changeSyncLocalRoot(getBackupId(), filePath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving good backupId and a path to non existent dir"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_ENOENT, LOCAL_PATH_UNAVAILABLE); const auto nonExsitsPath = path_u8string(std::filesystem::absolute("./NoExistsDir/")); megaApi[0]->changeSyncLocalRoot(getBackupId(), nonExsitsPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving good backupId and path to the already synced root"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, LOCAL_PATH_SYNC_COLLISION); const auto rootPath = path_u8string(std::filesystem::absolute(getLocalTmpDir())); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } // Just make sure that after all the attempts the sync is still running fine ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); } /** * @brief SdkTestSyncLocalRootChange.ErrorNestedSyncs : Validate error code paths triggered when * trying to set the new root to a directory that is part of an existing sync */ TEST_F(SdkTestSyncLocalRootChange, ErrorNestedSyncs) { static const std::string logPre{"SdkTestSyncLocalRootChange.ErrorNestedSyncs : "}; LOG_verbose << logPre << "Creating a new sync between auxTmpDirForNewSync/ and dir2/"; const LocalTempDir tmpDir{"./auxTmpDirForNewSync/"}; const LocalTempDir tmpSubDir{"./auxTmpDirForNewSync/subdir"}; const auto dir2BackupId = syncFolder(megaApi[0].get(), path_u8string(tmpDir.getPath()), getNodeByPath("dir2/")->getHandle()); ASSERT_NE(dir2BackupId, UNDEF) << "API Error adding a new sync"; { LOG_verbose << logPre << "Moving local root to another sync root"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, LOCAL_PATH_SYNC_COLLISION); const auto rootPath = path_u8string(tmpDir.getPath()); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Moving local root to a subdir inside another sync"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, LOCAL_PATH_SYNC_COLLISION); const auto rootPath = path_u8string(tmpSubDir.getPath()); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } } /** * @brief SdkTestSyncLocalRootChange.ErrorNestedSyncSymLink : * 1. Change the root of the sync to a symlink pointing to the original root * 2. Change the root of the sync to a symlink pointing to a root of another sync */ TEST_F(SdkTestSyncLocalRootChange, ErrorNestedSyncSymLink) { static const std::string logPre{"SdkTestSyncLocalRootChange.ErrorNestedSyncSymLink : "}; LOG_verbose << logPre << "Creating a new sync between auxTmpDirErrorNestedSyncSymLink/ and dir2/"; const LocalTempDir tmpDir{"./auxTmpDirErrorNestedSyncSymLink/"}; const auto dir2BackupId = syncFolder(megaApi[0].get(), path_u8string(tmpDir.getPath()), getNodeByPath("dir2/")->getHandle()); ASSERT_NE(dir2BackupId, UNDEF) << "API Error adding a new sync"; { LOG_verbose << logPre << "Changing the root to a symlink pointing to the original root"; std::filesystem::path linkName{"./symLinkToOriginal"}; std::filesystem::create_directory_symlink(getLocalTmpDir(), linkName); NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, LOCAL_PATH_SYNC_COLLISION); const auto rootPath = path_u8string(linkName); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); std::filesystem::remove(linkName); } { LOG_verbose << logPre << "Changing the root to a symlink pointing to the root of another sync"; std::filesystem::path linkName{"./symLinkToSecondSync"}; std::filesystem::create_directory_symlink(tmpDir.getPath(), linkName); NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, LOCAL_PATH_SYNC_COLLISION); const auto rootPath = path_u8string(linkName); megaApi[0]->changeSyncLocalRoot(getBackupId(), rootPath.c_str(), &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); std::filesystem::remove(linkName); } } /** * @brief SdkTestSyncLocalRootChange.OKSyncRunningToEmptyRoot: Change the root of a running sync to * an empty directory. Ensure the new .debris is properly created. */ TEST_F(SdkTestSyncLocalRootChange, OKSyncRunningToEmptyRoot) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKSyncRunningToEmptyRoot : "}; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Moving local root to an empty new root"; const LocalTempDir tmpDir{"./auxTmpDirOKSyncRunningToEmptyRoot/"}; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Waiting for local to match cloud"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations: Empty dir (local has preference)"; EXPECT_THAT(getLocalFirstChildrenNames_if(tmpDir.getPath()), testing::UnorderedElementsAre(".megaignore")); // Create a file and remove it in the cloud to force debris creation LOG_verbose << logPre << "Creating new file and removing from cloud to force .debris"; const std::string testFileName{"testTempFile.txt"}; LocalTempFile f{tmpDir.getPath() / testFileName, 0}; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); removeRemoteNode("dir1/" + testFileName); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations: Empty + .debris"; EXPECT_THAT(getLocalFirstChildrenNames_if(tmpDir.getPath()), testing::UnorderedElementsAre(".megaignore", DEBRISFOLDER)); } /** * @brief SdkTestSyncLocalRootChange.OKSyncRunningPauseAndResume: Change the root of a running sync * and ensure everything works as expected after pausing and resuming. */ TEST_F(SdkTestSyncLocalRootChange, OKSyncRunningPauseAndResume) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKSyncRunningPauseAndResume : "}; LOG_verbose << logPre << "Moving local root to an empty new root"; const LocalTempDir tmpDir{"./auxTmpDirOKSyncRunningToEmptyRoot/"}; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Suspending the sync"; ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Creating a new file locally"; const std::string testFileName{"testTempFile.txt"}; LocalTempFile f{tmpDir.getPath() / testFileName, 0}; LOG_verbose << logPre << "Resuming the sync"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Checking the new file uploads"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); EXPECT_THAT(getLocalFirstChildrenNames_if(tmpDir.getPath()), testing::UnorderedElementsAre(".megaignore", testFileName)); } /** * @brief SdkTestSyncLocalRootChange.OKSyncRunningToSimilarRoot: Change the root of a running sync * to a directory that contains different files: * - One exactly the same as in the previous root * - One different * - One with same name and different contents * - It misses one that was in previous root * * The final state prioritize local new root * */ TEST_F(SdkTestSyncLocalRootChange, OKSyncRunningToSimilarRoot) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKSyncRunningToSimilarRoot : "}; LOG_verbose << logPre << "Preparing new root with similar contents"; const LocalTempDir tmpDir{"./auxTmpOKSyncRunningToSimilarRoot/"}; prepareSimilarRoot(tmpDir.getPath()); LOG_verbose << logPre << "Changing the root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesSimilar()); } /** * @brief SdkTestSyncLocalRootChange.OKSyncSuspendedToSimilarRoot: Same as * OKSyncRunningToSimilarRoot but changing the root while the sync is suspended, then it is resumed * and wait to validate expectations. */ TEST_F(SdkTestSyncLocalRootChange, OKSyncSuspendedToSimilarRoot) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKSyncSuspendedToSimilarRoot : "}; LOG_verbose << logPre << "Preparing new root with similar contents"; const LocalTempDir tmpDir{"./auxTmpOKSyncSuspendedToSimilarRoot/"}; prepareSimilarRoot(tmpDir.getPath()); LOG_verbose << logPre << "Suspending the sync"; ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Changing the root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Resuming the sync"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesSimilar()); } /** * @brief SdkTestSyncLocalRootChange.OKSyncDisabledToSimilarRoot: Same as * OKSyncRunningToSimilarRoot but changing the root while the sync is disabled, then it is enabled * and wait to validate expectations. * * NOTE: In this case, the final state must be a mirror between cloud and local. */ TEST_F(SdkTestSyncLocalRootChange, OKSyncDisabledToSimilarRoot) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKSyncDisabledToSimilarRoot : "}; LOG_verbose << logPre << "Preparing new root with similar contents"; const LocalTempDir tmpDir{"./auxTmpOKSyncDisabledToSimilarRoot/"}; prepareSimilarRoot(tmpDir.getPath()); LOG_verbose << logPre << "Disable the sync"; ASSERT_TRUE(sdk_test::disableSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_DISABLED}); LOG_verbose << logPre << "Changing the root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_DISABLED}); LOG_verbose << logPre << "Resuming the sync"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); // Do not use fingerprints for the match. An stall is expected. ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal(false)); LOG_verbose << logPre << "Validating expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesMirror()); } #ifndef WIN32 /** * @brief SdkTestSyncLocalRootChange.OKSyncRunningMoveRootAndReassing: * 1. Move the root directory of a running sync to a different location * 2. Check that it gets suspended * 3. Reassign the root to the new location * 4. Sync can be resumed and everything stays as it was * NOTE: This test does not apply to windows because windows will block the rename operation on the * root while the sync is running (the directory is opened by the sync engine). We should pause the * sync before the rename but that scenario falls into the domain of other tests. */ TEST_F(SdkTestSyncLocalRootChange, OKSyncRunningMoveRootAndReassing) { static const std::string logPre{ "SdkTestSyncLocalRootChange.OKSyncRunningMoveRootAndReassing : "}; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Renaming local root"; const auto newRoot = getLocalTmpDir().parent_path() / "TestDirOKSyncRunningMoveRootAndReassing"; ASSERT_NO_FATAL_FAILURE(moveLocalTmpDir(newRoot)); LOG_verbose << logPre << "Waiting for the sync to be disabled"; ASSERT_TRUE(waitFor( [this]() { return getSyncRunState() == std::optional{MegaSync::RUNSTATE_SUSPENDED}; }, MAX_TIMEOUT, 10s)); LOG_verbose << logPre << "Change sync root to new location"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(newRoot)); LOG_verbose << logPre << "Enabling the sync"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); // Move the directory back to where it was moveLocalTmpDir(getLocalTmpDir()); } #endif /** * @brief SdkTestSyncLocalRootChange.SymLinkAsRootChangeWhereItPointsTo: * * Use case: we have a sync that uses a symlink as local root and that symlink changes where it is * pointing to. That should invalidate the sync and with the MegaApi::changeSyncLocalRoot we should * be able to solve the issue. * * 1. Create the sync with a symlink as root. symlink pointing to -> "originalRoot" that has some * files inside. * 2. Suspend the sync (we need this to run the test on windows) * 3. Create a new empty directory * 4. Move the symlink to point to the new directory. symlink pointing to -> "tempEmtpyTestDir" * 5. Try to resume the Sync. An error (MISMATCH_OF_ROOT_FSID) is expected. * 6. Change the root to "./symlink". Although it is the same path stored in the sync, it points to * a different directory so this should fix the issue. * 7. Enable the sync. It should work now. * 8. Validate final state (empty, local has preference). */ #ifdef __APPLE__ // Disabled due to SDK-4472 TEST_F(SdkTestSyncLocalRootChange, DISABLED_SymLinkAsRootChangeWhereItPointsTo) #else TEST_F(SdkTestSyncLocalRootChange, SymLinkAsRootChangeWhereItPointsTo) #endif { static const std::string logPre{ "SdkTestSyncLocalRootChange.SymLinkAsRootChangeWhereItPointsTo : "}; // We need to remove the original sync because we cannot create a new one pointing to a symlink // that points to the root of an existing sync LOG_verbose << logPre << "Removing the original sync"; ASSERT_NO_FATAL_FAILURE(removeSync(megaApi[0].get(), mBackupId)); LOG_verbose << logPre << "Creating a symlink to the original root"; std::filesystem::path linkName{"./symLinkToOriginal"}; const MrProper defer{[&linkName]() { std::filesystem::remove(linkName); }}; std::filesystem::create_directory_symlink(getLocalTmpDir(), linkName); LOG_verbose << logPre << "Creating a new sync with the symlink as root"; mBackupId = syncFolder(megaApi[0].get(), path_u8string(linkName), getNodeByPath("dir1/")->getHandle()); ASSERT_NE(mBackupId, UNDEF) << "API Error adding a new sync"; LOG_verbose << logPre << "Waiting for cloud/local content to match and local nodes are created"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); static constexpr auto WAIT_TIME_TO_CREATE_LOCAL_NODES{20s}; std::this_thread::sleep_for(WAIT_TIME_TO_CREATE_LOCAL_NODES); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Suspending the sync"; ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Creating an empty dir and moving the symlink to point to it"; static const std::string tmpEmptyDirName{"./tempEmtpyTestDir"}; const LocalTempDir tmp{tmpEmptyDirName}; std::filesystem::remove(linkName); std::filesystem::create_directory_symlink(tmp.getPath(), linkName); LOG_verbose << logPre << "Trying to resume the sync expecting an error (MISMATCH_OF_ROOT_FSID)"; NiceMock reqListener{megaApi[0].get()}; reqListener.setErrorExpectations(API_EFAILED, MISMATCH_OF_ROOT_FSID); megaApi[0]->setSyncRunState(getBackupId(), MegaSync::SyncRunningState::RUNSTATE_RUNNING, &reqListener); ASSERT_TRUE(reqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Change local root to the symlink again"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(linkName)); LOG_verbose << logPre << "We should be able to resume the sync now"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; LOG_verbose << logPre << "Waiting for local to match cloud"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); EXPECT_THAT(getLocalFirstChildrenNames_if(tmp.getPath()), testing::UnorderedElementsAre(".megaignore")); } /** * @brief SdkTestSyncLocalRootChange.ChangeRootToSameNameDifferentFsid: * * Use case: The path used as local root sill exists but it is a different directory (different * fsid). The MegaApi::changeSyncLocalRoot method allows to refresh internally the fsid of the local * root. * * 1. Suspend the sync (we need this to run the test on windows) * 2. Rename the original root to "TestChangeRoot" * 3. Create a new empty dir in the same path as the original root * 4. Try to resume the Sync. An error (MISMATCH_OF_ROOT_FSID) is expected. * 5. Change the root to "originalPath". This should fix the mismatch fsid issue * 6. Enable the sync. It should work now. * 7. Validate final state (empty, local has preference). */ #ifdef __APPLE__ // Disabled due to SDK-4472 TEST_F(SdkTestSyncLocalRootChange, DISABLED_ChangeRootToSameNameDifferentFsid) #else TEST_F(SdkTestSyncLocalRootChange, ChangeRootToSameNameDifferentFsid) #endif { static const std::string logPre{ "SdkTestSyncLocalRootChange.ChangeRootToSameNameDifferentFsid : "}; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Renaming local root"; const auto originalRoot = getLocalTmpDir(); const auto newRoot = getLocalTmpDir().parent_path() / "TestChangeRootToSameNameDifferentFsid"; // On windows we need to wait until the filesystem notify thread stops once the sync is // suspended. That's why we need multiple attempts to move the directory ASSERT_TRUE(sdk_test::waitFor( [this, &newRoot]() -> bool { return mTempLocalDir.move(newRoot); }, MAX_TIMEOUT)) << "Error moving root directory"; LOG_verbose << logPre << "Try to resume the sync in the same path but different dir"; const LocalTempDir tmpNewRootSameName{originalRoot}; NiceMock reqListener{megaApi[0].get()}; reqListener.setErrorExpectations(API_EFAILED, MISMATCH_OF_ROOT_FSID); megaApi[0]->setSyncRunState(getBackupId(), MegaSync::SyncRunningState::RUNSTATE_RUNNING, &reqListener); ASSERT_TRUE(reqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); LOG_verbose << logPre << "Change the root to the same path and try to resume again"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(originalRoot)); ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; LOG_verbose << logPre << "Waiting for local to match cloud"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); EXPECT_THAT(getLocalFirstChildrenNames_if(tmpNewRootSameName.getPath()), testing::UnorderedElementsAre(".megaignore")); LOG_verbose << logPre << "Stopping the sync and moving tmp dir to its original path"; ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_SUSPENDED}); } /** * @brief SdkTestSyncLocalRootChange.OKChangRootToASymLink: Change root to a symlink to an empty * directory. Validate final state. */ TEST_F(SdkTestSyncLocalRootChange, OKChangRootToASymLink) { static const std::string logPre{"SdkTestSyncLocalRootChange.OKChangRootToASymLink : "}; const LocalTempDir tmpDir{"./auxTmpDirOKChangRootToASymLink/"}; std::filesystem::path linkName{"./symLinkToEmpty"}; const MrProper defer{[&linkName]() { std::filesystem::remove(linkName); }}; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); std::filesystem::create_directory_symlink(tmpDir.getPath(), linkName); LOG_verbose << logPre << "Moving local root to an empty new root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(linkName)); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Waiting for local to match cloud"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations: Empty dir (local has preference)"; EXPECT_THAT(getLocalFirstChildrenNames_if(tmpDir.getPath()), testing::UnorderedElementsAre(".megaignore")); } class SdkTestBackupSyncLocalRootChange: public SdkTestSyncLocalRootChange { public: void SetUp() override { SdkTestNodesSetUp::SetUp(); createAuxFiles(); mBackupId = backupFolder(megaApi[0].get(), path_u8string(getLocalTmpDir()), "myBackup"); ASSERT_NE(mBackupId, UNDEF) << "API Error adding a new backup sync"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); } /** * @brief We don't need nodes on the cloud for backups */ const std::vector& getElements() const override { static const std::vector ELEMENTS{}; return ELEMENTS; } void createAuxFiles() { auxFiles.emplace_back(getLocalTmpDir() / "testFile", 1); auxFiles.emplace_back(getLocalTmpDir() / "testCommonFile", 0); auxFiles.emplace_back(getLocalTmpDir() / "testFile1", 0); } enum class StopAction { disable, pause }; /** * @brief Disables or pauses the current backup, then changes the local root to a new directory * with similar contents. The backup is resumed and the final state is validated. * * @param action Specifies how the backup has to be stopped */ void changeRootToSimilarWhileStop(const StopAction action) { const auto* testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); const std::string suiteName = testInfo->test_suite_name(); const std::string testName = testInfo->name(); const std::string logPrefix = suiteName + "." + testName + " : "; LOG_verbose << logPrefix << "Preparing new root with similar contents"; const LocalTempDir tmpDir{"./auxTmp" + testName}; prepareSimilarRoot(tmpDir.getPath()); MegaSync::SyncRunningState expectedRunState; switch (action) { case StopAction::pause: { LOG_verbose << logPrefix << "Suspending the backup sync"; ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), getBackupId())) << "Error suspending the sync"; expectedRunState = MegaSync::RUNSTATE_SUSPENDED; break; } case StopAction::disable: { LOG_verbose << logPrefix << "Disable the backup sync"; ASSERT_TRUE(sdk_test::disableSync(megaApi[0].get(), getBackupId())) << "Error disabling the sync"; expectedRunState = MegaSync::RUNSTATE_DISABLED; break; } } ASSERT_EQ(getSyncRunState(), std::optional{expectedRunState}); LOG_verbose << logPrefix << "Changing the root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{expectedRunState}); LOG_verbose << logPrefix << "Resuming the backup sync"; ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), getBackupId())) << "Error resuming the sync"; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPrefix << "Validating expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesSimilar()); } private: std::vector auxFiles; }; /** * @brief Change the root of the backup to an empty local dir. * Expectations -> final state = empty * The name of the backup and the remote root node do not change */ TEST_F(SdkTestBackupSyncLocalRootChange, OKChangeRootToEmpty) { static const std::string logPre{"SdkTestBackupSyncLocalRootChange.OKChangeRootToEmpty : "}; ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Moving local root to an empty new root"; const LocalTempDir tmpDir{"./auxTmpDirOKChangeRootToEmpty/"}; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); LOG_verbose << logPre << "Waiting for local to match cloud"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations: Empty dir (local has preference)"; EXPECT_THAT(getLocalFirstChildrenNames_if(tmpDir.getPath()), testing::UnorderedElementsAre(".megaignore")); const auto backup = getSync(); ASSERT_TRUE(backup); EXPECT_STREQ(backup->getName(), "myBackup"); EXPECT_THAT(backup->getLastKnownMegaFolder(), EndsWith("myBackup")); } /** * @brief SdkTestBackupSyncLocalRootChange.OKBackupRunningToSimilarRoot: Change the root of a * running backup sync to a directory that contains different files: * - One exactly the same as in the previous root * - One different * - One with same name and different contents * - It misses one that was in previous root * * The final state prioritize the new local root */ TEST_F(SdkTestBackupSyncLocalRootChange, OKBackupRunningToSimilarRoot) { static const std::string logPre{ "SdkTestBackupSyncLocalRootChange.OKBackupRunningToSimilarRoot : "}; LOG_verbose << logPre << "Preparing new root with similar contents"; const LocalTempDir tmpDir{"./auxTmpOKBackupRunningToSimilarRoot/"}; prepareSimilarRoot(tmpDir.getPath()); LOG_verbose << logPre << "Changing the root"; ASSERT_NO_FATAL_FAILURE(changeLocalSyncRootNoErrors(tmpDir.getPath())); ASSERT_EQ(getSyncRunState(), std::optional{MegaSync::RUNSTATE_RUNNING}); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Validating expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesSimilar()); } /** * @brief SdkTestBackupSyncLocalRootChange.OKBackupSuspendedToSimilarRoot: Same as * OKBackupRunningToSimilarRoot but changing the root while the backup is suspended, then it is * resumed and waits to validate expectations. */ TEST_F(SdkTestBackupSyncLocalRootChange, OKBackupSuspendedToSimilarRoot) { ASSERT_NO_FATAL_FAILURE(changeRootToSimilarWhileStop(StopAction::pause)); } /** * @brief SdkTestBackupSyncLocalRootChange.OKBackupDisabledToSimilarRoot: Same as * OKBackupRunningToSimilarRoot but changing the root while the backup is disabled, then it is * enabled and waits to validate expectations. */ TEST_F(SdkTestBackupSyncLocalRootChange, OKBackupDisabledToSimilarRoot) { ASSERT_NO_FATAL_FAILURE(changeRootToSimilarWhileStop(StopAction::disable)); } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestCreateAccount_test.cpp000066400000000000000000000450711516266226600237300ustar00rootroot00000000000000/** * @file SdkTestCreateAccount_test.cpp * @brief This file defines some tests that check account creation with different types of clients */ #include "env_var_accounts.h" #include "megaapi.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" #include using namespace sdk_test; /** * @class SdkTesCreateAccount * @brief An abstract class that provides a template fixture/test suite to test account creation * with different client types * */ class SdkTestCreateAccount: public SdkTest, virtual public ::testing::WithParamInterface { public: void TearDown() override; void doCreateAccountTest(const std::string& testName, int clientType); }; std::string getLinkFromMailbox(const std::string& exe, // Python const std::string& script, // email_processor.py const std::string& realAccount, // user const std::string& realPswd, // password for user@host.domain const std::string& toAddr, // user+testnewaccount@host.domain const std::string& intent, // confirm / delete const std::chrono::steady_clock::time_point& timeOfEmail) { using namespace std::chrono; // Just for this little scope std::string command = exe + " \"" + script + "\" \"" + realAccount + "\" \"" + realPswd + "\" \"" + toAddr + "\" " + intent; std::string output; // Wait for the link to be sent constexpr seconds delta = 10s; constexpr minutes maxTimeout = 10min; seconds spentTime = 0s; for (; spentTime < maxTimeout && output.empty(); spentTime += delta) { WaitMillisec(duration_cast(delta).count()); // get time interval to look for emails, add some seconds to account for delays related to // the python script call constexpr seconds safetyDelay = 5s; const auto attemptTime = steady_clock::now(); seconds timeSinceEmail = duration_cast(attemptTime - timeOfEmail) + safetyDelay; // Run Python script output = runProgram(command + ' ' + std::to_string(timeSinceEmail.count()), PROG_OUTPUT_TYPE::TEXT); } LOG_debug << "Time spent trying to get the email: " << spentTime.count() << "s"; // Print whatever was fetched from the mailbox LOG_debug << "Link from email (" << intent << "): " << (output.empty() ? "[empty]" : output); // Validate the link constexpr char expectedLinkPrefix[] = "https://"; return output.substr(0, sizeof(expectedLinkPrefix) - 1) == expectedLinkPrefix ? output : std::string(); } std::string getUniqueAlias() { // use n random chars int n = 4; std::string alias; auto t = std::time(nullptr); srand((unsigned int)t); for (int i = 0; i < n; ++i) { alias += static_cast('a' + rand() % 26); } // add a timestamp auto tm = *std::localtime(&t); std::ostringstream oss; oss << std::put_time(&tm, "%Y%m%d%H%M%S"); alias += oss.str(); return alias; } void SdkTestCreateAccount::TearDown() { releaseMegaApi(1); releaseMegaApi(2); if (!megaApi.empty() && megaApi[0]) { releaseMegaApi(0); } out() << "Teardown done, test exiting"; } /* * doCreateAccountTest(testName, clientType) * * testName * - Name of the concrete test invoking this function. * * clientType * - The type of the client we want to perform this test on. * * This function tests the creation of a new account for a random user. * - Create account and send confirmation link * - Logout and resume the create-account process * - Extract confirmation link from the mailbox * - Use the link to confirm the account * * - Request a reset password link * - Confirm the reset password * * - Login to the new account * - Request cancel account link * - Extract cancel account link from the mailbox * - Use the link to cancel the account */ void SdkTestCreateAccount::doCreateAccountTest(const std::string& testName, int clientType) { LOG_info << "___TEST " << testName << "____"; // Make sure the new account details have been set up const auto bufRealEmail = Utils::getenv("MEGA_REAL_EMAIL", ""); // user@host.domain const auto bufRealPswd = Utils::getenv("MEGA_REAL_PWD", ""); // email password of user@host.domain fs::path bufScript = getLinkExtractSrciptPath(); ASSERT_TRUE(!bufRealEmail.empty() && !bufRealPswd.empty()) << "MEGA_REAL_EMAIL, MEGA_REAL_PWD env vars must all be defined"; // test that Python 3 was installed std::string pyExe = "python"; { const std::string pyOpt = " -V"; const std::string pyExpected = "Python 3."; std::string output = runProgram(pyExe + pyOpt, PROG_OUTPUT_TYPE::TEXT); // Python -V if (output.substr(0, pyExpected.length()) != pyExpected) { pyExe += "3"; output = runProgram(pyExe + pyOpt, PROG_OUTPUT_TYPE::TEXT); // Python3 -V ASSERT_EQ(pyExpected, output.substr(0, pyExpected.length())) << "Python 3 was not found."; } LOG_debug << "Using " << output; } megaApi.resize(1); mApi.resize(1); ASSERT_NO_FATAL_FAILURE(configureTestInstance(0, bufRealEmail, bufRealPswd, true, clientType)); // create the account // ------------------ LOG_debug << testName << ": Start account creation"; const std::string realEmail(bufRealEmail); // user@host.domain std::string::size_type pos = realEmail.find('@'); const std::string realAccount = realEmail.substr(0, pos); // user [[maybe_unused]] const auto [testEmail, _] = getEnvVarAccounts().getVarValues(0); const std::string newTestAcc = realAccount + '+' + testEmail.substr(0, testEmail.find("@")) + '+' + getUniqueAlias() + realEmail.substr(pos); // user+testUser+rand20210919@host.domain LOG_info << "Creating Mega account " << newTestAcc; const char* origTestPwd = "TestPswd!@#$"; // maybe this should be logged too, changed later // save point in time for account init std::chrono::time_point timeOfConfirmEmail = std::chrono::steady_clock::now(); // Create an ephemeral session internally and send a confirmation link to email ASSERT_EQ( API_OK, synchronousCreateAccount(0, newTestAcc.c_str(), origTestPwd, "MyFirstname", "MyLastname")); if (clientType == MegaApi::CLIENT_TYPE_PASSWORD_MANAGER) { RequestTracker rt{megaApi[0].get()}; megaApi[0]->getPasswordManagerBase(&rt); EXPECT_EQ(API_OK, rt.waitForResult()) << "Getting Password Manager Base node through shortcut failed"; EXPECT_NE(nullptr, rt.request); EXPECT_NE(INVALID_HANDLE, rt.request->getNodeHandle()) << "Invalid Password Manager Base node retrieved"; } LOG_debug << testName << ": Logout and resume"; // Logout from ephemeral session and resume session ASSERT_NO_FATAL_FAILURE(locallogout()); ASSERT_EQ(API_OK, synchronousResumeCreateAccount(0, mApi[0].getSid().c_str())); // Get confirmation link from the email { LOG_debug << testName << ": Get confirmation link from email"; std::string conformLink = getLinkFromMailbox(pyExe, bufScript.string(), realAccount, bufRealPswd, newTestAcc, MegaClient::confirmLinkPrefix(), timeOfConfirmEmail); ASSERT_FALSE(conformLink.empty()) << "Confirmation link was not found."; LOG_debug << testName << ": Confirm account"; // create another connection to confirm the account megaApi.resize(2); mApi.resize(2); ASSERT_NO_FATAL_FAILURE( configureTestInstance(1, bufRealEmail, bufRealPswd, true, clientType)); PerApi& initialConn = mApi[0]; initialConn.resetlastEvent(); // Use confirmation link ASSERT_EQ(API_OK, synchronousConfirmSignupLink(1, conformLink.c_str())); // check for event triggered by 'uec' action packet received after the confirmation EXPECT_TRUE(WaitFor( [&initialConn]() { return initialConn.lastEventsContain(MegaEvent::EVENT_CONFIRM_USER_EMAIL); }, 10000)) << "EVENT_CONFIRM_USER_EMAIL event triggered by 'uec' action packet was not received"; } // Login to the new account { LOG_debug << testName << ": Login to the new account"; std::unique_ptr loginTracker = std::make_unique(megaApi[0].get()); megaApi[0]->login(newTestAcc.c_str(), origTestPwd, loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account " << newTestAcc.c_str(); } // fetchnodes // needed internally to fill in user details, including email { LOG_debug << testName << ": fetch nodes from new account"; std::unique_ptr fetchnodesTracker = std::make_unique(megaApi[0].get()); megaApi[0]->fetchNodes(fetchnodesTracker.get()); ASSERT_EQ(API_OK, fetchnodesTracker->waitForResult()) << " Failed to fetchnodes for account " << newTestAcc.c_str(); } // test resetting the password // --------------------------- LOG_debug << testName << ": Start reset password"; std::chrono::time_point timeOfResetEmail = chrono::steady_clock::now(); ASSERT_EQ(synchronousResetPassword(0, newTestAcc.c_str(), true), MegaError::API_OK) << "resetPassword failed"; // Get password reset link from the mailbox const char* newTestPwd = "PassAndGotHerPhoneNumber!#$**!"; { LOG_debug << testName << ": Get password reset link from email"; std::string recoverink = getLinkFromMailbox(pyExe, bufScript.string(), realAccount, bufRealPswd, newTestAcc, MegaClient::recoverLinkPrefix(), timeOfResetEmail); ASSERT_FALSE(recoverink.empty()) << "Recover account link was not found."; LOG_debug << testName << ": Confirm reset password"; char* masterKey = megaApi[0]->exportMasterKey(); ASSERT_EQ(synchronousConfirmResetPassword(0, recoverink.c_str(), newTestPwd, masterKey), MegaError::API_OK) << "confirmResetPassword failed"; } // Login using new password { LOG_debug << testName << ": Login with new password"; std::unique_ptr loginTracker = std::make_unique(megaApi[0].get()); megaApi[0]->login(newTestAcc.c_str(), newTestPwd, loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account after change password with new password " << newTestAcc.c_str(); } // fetchnodes - needed internally to fill in user details, to allow cancelAccount() to work { LOG_debug << testName << ": Fetching nodes"; std::unique_ptr fetchnodesTracker = std::make_unique(megaApi[0].get()); megaApi[0]->fetchNodes(fetchnodesTracker.get()); ASSERT_EQ(API_OK, fetchnodesTracker->waitForResult()) << " Failed to fetchnodes after change password for account " << newTestAcc.c_str(); } // test changing the email (check change with auxiliar instance) // ----------------------- LOG_debug << testName << ": Start email change"; // login with auxiliar instance LOG_debug << testName << ": Login auxiliar account"; megaApi.resize(2); mApi.resize(2); ASSERT_NO_FATAL_FAILURE(configureTestInstance(1, newTestAcc, newTestPwd, true, clientType)); { std::unique_ptr loginTracker = std::make_unique(megaApi[1].get()); megaApi[1]->login(newTestAcc.c_str(), newTestPwd, loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to auxiliar account "; } LOG_debug << testName << ": Send change email request"; const std::string changedTestAcc = Utils::replace(newTestAcc, "@", "-new@"); std::chrono::time_point timeOfChangeEmail = chrono::steady_clock::now(); ASSERT_EQ(synchronousChangeEmail(0, changedTestAcc.c_str()), MegaError::API_OK) << "changeEmail failed"; { LOG_debug << testName << ": Get change email link from email inbox"; std::string changelink = getLinkFromMailbox(pyExe, bufScript.string(), realAccount, bufRealPswd, changedTestAcc, MegaClient::verifyLinkPrefix(), timeOfChangeEmail); ASSERT_FALSE(changelink.empty()) << "Change email account link was not found."; LOG_debug << testName << ": Confirm email change"; ASSERT_STRCASEEQ(newTestAcc.c_str(), std::unique_ptr{megaApi[0]->getMyEmail()}.get()) << "email changed prematurely"; ASSERT_EQ(synchronousConfirmChangeEmail(0, changelink.c_str(), newTestPwd), MegaError::API_OK) << "confirmChangeEmail failed"; } { // Check if our own email is updated after receive ug at auxiliar instance LOG_debug << testName << ": Check email is updated"; std::unique_ptr userDataTracker = std::make_unique(megaApi[1].get()); megaApi[1]->getUserData(userDataTracker.get()); ASSERT_EQ(API_OK, userDataTracker->waitForResult()) << " Failed to get user data at auxiliar account"; ASSERT_EQ(changedTestAcc, std::unique_ptr{megaApi[1]->getMyEmail()}.get()) << "Email update error at auxiliar account"; logout(1, false, maxTimeout); } // Login using new email ASSERT_STRCASEEQ(changedTestAcc.c_str(), std::unique_ptr{megaApi[0]->getMyEmail()}.get()) << "email not changed correctly"; { LOG_debug << testName << ": Login with new email"; std::unique_ptr loginTracker = std::make_unique(megaApi[0].get()); megaApi[0]->login(changedTestAcc.c_str(), newTestPwd, loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account after change email with new email " << changedTestAcc.c_str(); } // fetchnodes - needed internally to fill in user details, to allow cancelAccount() to work { LOG_debug << testName << ": Fetching nodes"; std::unique_ptr fetchnodesTracker = std::make_unique(megaApi[0].get()); megaApi[0]->fetchNodes(fetchnodesTracker.get()); ASSERT_EQ(API_OK, fetchnodesTracker->waitForResult()) << " Failed to fetchnodes after change password for account " << changedTestAcc.c_str(); } ASSERT_STRCASEEQ(changedTestAcc.c_str(), std::unique_ptr{megaApi[0]->getMyEmail()}.get()) << "my email not set correctly after changed"; // delete the account // ------------------ // Request cancel account link LOG_debug << testName << ": Start deleting account"; std::chrono::time_point timeOfDeleteEmail = std::chrono::steady_clock::now(); { LOG_debug << testName << ": Request account cancel"; std::unique_ptr cancelLinkTracker = std::make_unique(megaApi[0].get()); megaApi[0]->cancelAccount(cancelLinkTracker.get()); ASSERT_EQ(API_OK, cancelLinkTracker->waitForResult()) << " Failed to request cancel link for account " << changedTestAcc.c_str(); } // Get cancel account link from the mailbox { LOG_debug << testName << ": Get cancel link from email"; std::string deleteLink = getLinkFromMailbox(pyExe, bufScript.string(), realAccount, bufRealPswd, changedTestAcc, MegaClient::cancelLinkPrefix(), timeOfDeleteEmail); ASSERT_FALSE(deleteLink.empty()) << "Cancel account link was not found."; // Use cancel account link LOG_debug << testName << ": Confirm cancel link"; std::unique_ptr useCancelLinkTracker = std::make_unique(megaApi[0].get()); megaApi[0]->confirmCancelAccount(deleteLink.c_str(), newTestPwd, useCancelLinkTracker.get()); // Allow API_ESID beside API_OK, due to the race between sc and cs channels ASSERT_PRED3( [](int t, int v1, int v2) { return t == v1 || t == v2; }, useCancelLinkTracker->waitForResult(), API_OK, API_ESID) << " Failed to confirm cancel account " << changedTestAcc.c_str(); } } /** * @brief Test_P CreateAccount * * Tests account creation for any client type client. * * See doCreateAccountTest(...). */ TEST_P(SdkTestCreateAccount, CreateAccount) { int clientType = GetParam(); ASSERT_NO_FATAL_FAILURE(doCreateAccountTest("SdkTestVPNCreateAccount", clientType)); } INSTANTIATE_TEST_SUITE_P(CreateAccount, SdkTestCreateAccount, ::testing::Values(MegaApi::CLIENT_TYPE_DEFAULT, MegaApi::CLIENT_TYPE_VPN, MegaApi::CLIENT_TYPE_PASSWORD_MANAGER)); sdk-10.11.0/tests/integration/SdkTestDeviceCenter_test.cpp000066400000000000000000000232131516266226600235420ustar00rootroot00000000000000/** * @file SdkTestDeviceCenter_test.cpp * @brief Test Device Center operations on full-account syncs */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" using namespace sdk_test; using namespace testing; /** * @brief Test fixture which initializates two sessions of the same account * * It offers functionality to perform operations from the Device Center. * * It initializes 2 MegaApi instance, the first (index 0) plays the role of the main device while * the second (index 1) is used as the remote Device Center. * */ class SdkTestDeviceCenter: public SdkTest { public: static constexpr auto MAX_TIMEOUT = 3min; handle mBackupID{UNDEF}; void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_NO_FATAL_FAILURE(ensureAccountDeviceNamesAttrExists(megaApi[0].get())); // Initialize a second session with the same credentials ASSERT_NO_FATAL_FAILURE(initializeSecondSession()); } bool resumeFromDeviceCenter(error expectedError = API_OK) { return doChangeFromDeviceCenter(DeviceCenterOperations::RESUME, expectedError); } bool pauseFromDeviceCenter(error expectedError = API_OK) { return doChangeFromDeviceCenter(DeviceCenterOperations::PAUSE, expectedError); } bool deleteFromDeviceCenter(error expectedError = API_OK, MegaHandle destination = INVALID_HANDLE) { return doChangeFromDeviceCenter(DeviceCenterOperations::REMOVE, expectedError, destination); } const fs::path& getLocalFolder() { return localFolder.getPath(); } bool waitForSyncStateFromMain(const MegaSync::SyncRunningState runState) { return waitForSyncState(megaApi[0].get(), mBackupID, runState, MegaSync::NO_SYNC_ERROR) != nullptr; } private: std::string localFolderName = getFilePrefix() + "dir"; LocalTempDir localFolder{fs::current_path() / localFolderName}; void initializeSecondSession() { megaApi.resize(megaApi.size() + 1); mApi.resize(mApi.size() + 1); configureTestInstance(1, mApi[0].email, mApi[0].pwd); NiceMock loginTracker(megaApi[1].get()); megaApi[1]->login(mApi[1].email.c_str(), mApi[1].pwd.c_str(), &loginTracker); ASSERT_TRUE(loginTracker.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Second session login failed"; NiceMock fetchNodesTracker(megaApi[1].get()); megaApi[1]->fetchNodes(&fetchNodesTracker); ASSERT_TRUE(fetchNodesTracker.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Second session fetch nodes failed"; } // Internal values to define the operations in the Device Center enum class DeviceCenterOperations { PAUSE, RESUME, REMOVE }; bool doChangeFromDeviceCenter(const DeviceCenterOperations operation, error expectedError = API_OK, MegaHandle destination = INVALID_HANDLE) { NiceMock reqTracker{megaApi[1].get()}; reqTracker.setErrorExpectations(expectedError); switch (operation) { case DeviceCenterOperations::PAUSE: megaApi[1]->pauseFromBC(mBackupID, &reqTracker); break; case DeviceCenterOperations::RESUME: megaApi[1]->resumeFromBC(mBackupID, &reqTracker); break; case DeviceCenterOperations::REMOVE: megaApi[1]->removeFromBC(mBackupID, destination, &reqTracker); break; } return reqTracker.waitForFinishOrTimeout(MAX_TIMEOUT); } }; class SdkTestDeviceCenterFullSync: public SdkTestDeviceCenter { public: void SetUp() override { SdkTestDeviceCenter::SetUp(); ASSERT_NO_FATAL_FAILURE(setupFullSync()); } void TearDown() override { if (const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync) { removeSync(megaApi[0].get(), mBackupID); } SdkTestDeviceCenter::TearDown(); } private: void setupFullSync() { LOG_debug << "Creating a full account sync"; const unique_ptr rootnode{megaApi[0]->getRootNode()}; mBackupID = syncFolder(megaApi[0].get(), path_u8string(getLocalFolder()), rootnode->getHandle()); ASSERT_NE(mBackupID, INVALID_HANDLE) << "Invalid full-sync ID"; } }; /** * @brief Exercises the pause, resume and remove Device Center operations from a second session */ TEST_F(SdkTestDeviceCenterFullSync, FullSyncOperations) { static const auto logPre{getLogPrefix()}; // Pause the sync from the second session LOG_debug << logPre << "Pause full-sync from the Device Center"; ASSERT_TRUE(pauseFromDeviceCenter()) << "Failed to pause full-sync from the second session"; ASSERT_TRUE(waitForSyncStateFromMain(MegaSync::RUNSTATE_SUSPENDED)) << "Full-sync not paused after 30 seconds"; // Wait a while (for the *!sds user attr to be updated and propagated in response). std::this_thread::sleep_for(std::chrono::seconds{5}); // Resume the sync from the second session LOG_debug << logPre << "Resume full-sync from the Device Center"; ASSERT_TRUE(resumeFromDeviceCenter()) << "Failed to resume full-sync from the second session"; ASSERT_TRUE(waitForSyncStateFromMain(MegaSync::RUNSTATE_RUNNING)) << "Full-sync not resumed after 30 seconds"; // Wait a while (for the *!sds user attr to be updated and propagated in response). std::this_thread::sleep_for(std::chrono::seconds{5}); // Delete the sync from the second session LOG_debug << logPre << "Remove full-sync from the Device Center"; NiceMock listener; const auto hasExpectedId = Pointee(Property(&MegaSync::getBackupId, mBackupID)); std::promise removed; EXPECT_CALL(listener, onSyncDeleted(_, hasExpectedId)) .WillOnce( [&removed] { removed.set_value(); }); megaApi[0]->addListener(&listener); ASSERT_TRUE(deleteFromDeviceCenter()) << "Failed to delete full-sync from the second session"; ASSERT_EQ(removed.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready) << "Full-sync still exists after 3 minutes"; megaApi[0]->removeListener(&listener); } /** * @brief Test fixture to test Backups from the Device Center. * * It configures a backup from the first account and a folder to potentially stotre backups once * removed. * * It inherits functionality to perform operations from the Device Center using a secondary account. */ class SdkTestDeviceCenterBackup: public SdkTestDeviceCenter { public: void SetUp() override { SdkTestDeviceCenter::SetUp(); ASSERT_NO_FATAL_FAILURE(setupBackup()); ASSERT_NO_FATAL_FAILURE(setupDestinationDirectory()); } void TearDown() override { if (const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync) { removeSync(megaApi[0].get(), mBackupID); } SdkTestDeviceCenter::TearDown(); } void duplicateDestinationBackupFolder() { // Get parent folder const unique_ptr parentFolder{ megaApi[0]->getNodeByHandle(mDestinationFolderHandle)}; ASSERT_TRUE(parentFolder); // Create a folder in the destination with the same name as the backup const MegaHandle newFolder = createFolder(0, mBackupName.c_str(), parentFolder.get()); ASSERT_NE(newFolder, INVALID_HANDLE) << "Invalid destination folder handle"; // Ensure that the second client can see the new folder unique_ptr newFolderNode; waitFor( [&api = megaApi[1], newFolder, &newFolderNode]() { newFolderNode.reset(api->getNodeByHandle(newFolder)); return newFolderNode != nullptr; }, 120s); ASSERT_TRUE(newFolderNode) << "Second account can't see the new folder."; } bool deleteFromDeviceCenterAndArchive(error expectedError) { return deleteFromDeviceCenter(expectedError, mDestinationFolderHandle); } private: const fs::path mDestinationFolderName{"BackupArchive"}; const std::string mBackupName{"myBackup"}; MegaHandle mDestinationFolderHandle{INVALID_HANDLE}; void setupBackup() { LOG_debug << "Creating a backup"; mBackupID = backupFolder(megaApi[0].get(), path_u8string(getLocalFolder()), mBackupName); ASSERT_NE(mBackupID, INVALID_HANDLE) << "Invalid Backup ID"; } void setupDestinationDirectory() { const unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode) << "Account root node not available."; mDestinationFolderHandle = createFolder(0, path_u8string(mDestinationFolderName).c_str(), rootnode.get()); ASSERT_NE(mDestinationFolderHandle, INVALID_HANDLE) << "Invalid destination folder handle"; } }; TEST_F(SdkTestDeviceCenterBackup, RemoveDestinationClash) { static const auto logPre{getLogPrefix()}; LOG_debug << logPre << "Duplicate destination folder to cause a clash."; duplicateDestinationBackupFolder(); LOG_debug << logPre << "Try to remove backup from the second session and move the data to the destination."; ASSERT_TRUE(deleteFromDeviceCenterAndArchive(API_EEXIST)) << "Backups should not have been removed."; } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestFilter_test.cpp000066400000000000000000001072461516266226600224400ustar00rootroot00000000000000/** * @file SdkTestFilter_test.cpp * @brief This file defines some tests that involve interactions with the MegaSearchFilter * object. This includes operations like: * - Searching nodes with different filters * - Ordering search results with different criteria * - Apply different kinds of conditions in the filters */ #include "megaapi.h" #include "megautils.h" #include "sdk_test_utils.h" #include "SdkTestNodesSetUp.h" #include #include #include using namespace std::chrono_literals; using namespace sdk_test; /** * @class SdkTestOrder * @brief Test suite designed to test the ordering of results from search and getChildren methods * */ class SdkTestOrder: public SdkTestNodesSetUp { const std::vector& getElements() const override { static const std::vector ELEMENTS{ FileNodeInfo("testFile1").setLabel(MegaNode::NODE_LBL_RED), DirNodeInfo("Dir1") .setLabel(MegaNode::NODE_LBL_PURPLE) .setFav(true) .addChild(FileNodeInfo("testFile2") .setLabel(MegaNode::NODE_LBL_ORANGE) .setFav(true) .setSize(15) .setMtime(100s) .setSensitive(true)) .addChild(FileNodeInfo("testFile3") .setLabel(MegaNode::NODE_LBL_YELLOW) .setSize(35) .setMtime(500s)) .addChild(DirNodeInfo("Dir11") .setLabel(MegaNode::NODE_LBL_YELLOW) .addChild(FileNodeInfo("testFile4"))), DirNodeInfo("Dir2").setSensitive(true).addChild(FileNodeInfo("testFile5") .setLabel(MegaNode::NODE_LBL_BLUE) .setFav(true) .setSize(20) .setMtime(200s)), FileNodeInfo("testFile6").setFav(true).setSize(10).setMtime(300s), FileNodeInfo("TestFile5Uppercase"), }; return ELEMENTS; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_ORDER_AUX_DIR"}; return dirName; } }; /** * @brief Helper parameterized matcher that checks if the test container contains all the elements * in the same order. * * Example: * std::vector arg {1, 5, 7, 8}; * ASSERT_THAT(arg, ContainsInOrder(std::vector {1, 7, 8})); * ASSERT_THAT(arg, testing::Not(ContainsInOrder(std::vector {1, 7, 5}))); * ASSERT_THAT(arg, testing::Not(ContainsInOrder(std::vector {1, 7, 7, 8}))); * */ MATCHER_P(ContainsInOrder, elements, "") { if (elements.size() > arg.size()) return false; return std::all_of( elements.begin(), elements.end(), [currentEntry = arg.begin(), allEntriesEnd = arg.cend()](const auto& element) mutable { while (currentEntry != allEntriesEnd && *currentEntry != element) ++currentEntry; return currentEntry++ != allEntriesEnd; }); } /** * @brief SdkTestOrder.SdkGetNodesInOrder * * Tests all the sorting options available for the MegaApi.search method. */ TEST_F(SdkTestOrder, SdkGetNodesInOrder) { using testing::AllOf; using testing::NotNull; using testing::UnorderedElementsAreArray; // Load the default filter to search from ROOT_TEST_NODE_DIR std::unique_ptr filteringInfo(getDefaultfilter()); // Default (ORDER_NONE -> Undefined) std::unique_ptr searchResults(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), UnorderedElementsAreArray(getAllNodesNames())) << "Unexpected sorting for ORDER_NONE"; // Alphabetical, dirs first std::vector expected{"Dir1", "Dir2", "Dir11", "testFile1", "testFile5", "TestFile5Uppercase", "testFile6"}; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC"; // Alphabetical inverted, dirs first (reverse independently) std::reverse(expected.begin(), expected.begin() + 3); std::reverse(expected.begin() + 3, expected.end()); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_DESC"; // By size, dirs first. Ties break by natural // sorting expected = { "Dir11", "Dir2", "Dir1", "testFile1", // 0 "testFile4", // 0 "TestFile5Uppercase", // 0 "testFile6", // 10 "testFile2", // 15 "testFile5", // 20 "testFile3" // 35 }; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_SIZE_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_ASC"; // By size inverted, dirs first std::reverse(expected.begin(), expected.begin() + 3); std::reverse(expected.begin() + 3, expected.end()); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_SIZE_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_DESC"; // By creation time, dirs first expected = {"Dir1", "Dir11", "testFile1", "testFile3", "testFile5", "testFile6"}; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_CREATION_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_ASC"; // By creation inverted std::reverse(expected.begin() + 2, expected.end()); std::reverse(expected.begin(), expected.begin() + 2); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_CREATION_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_DES"; // By modification time, dirs first but ordered naturally expected = { "Dir1", "Dir2", "Dir11", "testFile3", // 500 s ago "testFile6", // 300 s ago "testFile5", // 200 s ago "testFile2", // 100 s ago "testFile1", // Undef (upload time) }; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_MODIFICATION_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_ASC"; // By modification inverted std::reverse(expected.begin(), expected.begin() + 3); std::reverse(expected.begin() + 3, expected.end()); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_MODIFICATION_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_DES"; // By label, dirs and natural sort expected = { "testFile1", // Red (1) "testFile2", // Orange (2) "Dir11", // Yellow (3) "testFile3", // Yellow (3) "testFile5", // Blue (5) "Dir1", // Purple (6) "Dir2", // Nothing "testFile4", // Nothing "testFile6" // Nothing }; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_LABEL_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_ASC"; expected = { "Dir1", // Purple (6) "testFile5", // Blue (5) "Dir11", // Yellow (3) "testFile3", // Yellow (3) "testFile2", // Orange (2) "testFile1", // Red (1) "Dir2", // Nothing "testFile4", // Nothing "testFile6" // Nothing }; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_LABEL_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_DESC"; // By fav, dirs and natural sort expected = { "Dir1", // fav "testFile5", // fav "testFile6", // fav "Dir2", // not fav "Dir11", // not fav "testFile1", // not fav "TestFile5Uppercase", // not fav }; searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_FAV_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_ASC"; // By fav inverted, dirs first std::rotate(expected.begin(), expected.begin() + 3, expected.end()); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_FAV_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_DESC"; } /** * @brief SdkTestOrder.SdkGetVersions * * Tests if file versioning is properly working. */ TEST_F(SdkTestOrder, SdkGetVersions) { MegaHandle fileHandle = 0; const auto remoteDir = "/SDK_TEST_ORDER_AUX_DIR"; auto dirNode = megaApi[0]->getNodeByPath(remoteDir); const auto fileName = "testFile1"; ASSERT_TRUE(createFile(fileName, false)); const auto uploadVersions = 3; // First version is already uploaded during the setup. for (auto i = 1; i < uploadVersions; i++) { appendToFile(fileName, 20); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName, dirNode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); } const auto fileNode(megaApi[0]->getNodeByHandle(fileHandle)); ASSERT_TRUE(fileNode) << "Unable to retrieve the file handle"; const auto versions(megaApi[0]->getVersions(fileNode)); ASSERT_EQ(versions->size(), uploadVersions); const auto versionCount = megaApi[0]->getNumVersions(fileNode); ASSERT_EQ(versionCount, uploadVersions); auto session = dumpSession(0); locallogout(0); resumeSession(session, 0); fetchnodes(0); const auto remoteFileNode(megaApi[0]->getNodeByPath("/SDK_TEST_ORDER_AUX_DIR/testFile1")); ASSERT_TRUE(remoteFileNode) << "Unable to retrieve the remote file handle"; const auto versionsAfterResume(megaApi[0]->getVersions(remoteFileNode)); ASSERT_EQ(versionsAfterResume->size(), uploadVersions); const auto versionCountAfterResume = megaApi[0]->getNumVersions(remoteFileNode); ASSERT_EQ(versionCountAfterResume, uploadVersions); } /** * @brief SdkTestOrder.SdkGetNodesInOrder * * Tests all the sorting options available for the MegaApi.getChildren method. */ TEST_F(SdkTestOrder, SdkGetChildrenInOrder) { using testing::AllOf; using testing::NotNull; using testing::UnorderedElementsAreArray; // Load the default filter to getChildren from ROOT_TEST_NODE_DIR std::unique_ptr filteringInfo(getDefaultfilter()); // Alphabetical, dirs first std::vector expected{"testFile1", "Dir1", "Dir2", "TestFile5Uppercase", "testFile6"}; // Default (ORDER_NONE -> Undefined) std::unique_ptr children(megaApi[0]->getChildren(filteringInfo.get())); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), UnorderedElementsAreArray(expected)) << "Unexpected sorting for ORDER_NONE"; expected = {"Dir1", "Dir2", "testFile1", "TestFile5Uppercase", "testFile6"}; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC getChildren with parent"; // Alphabetical inverted, dirs first (reverse independently) std::reverse(expected.begin(), expected.begin() + 2); std::reverse(expected.begin() + 2, expected.end()); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_DESC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_DEFAULT_DESC getChildren with parent"; // By size, dirs first (but not relevant order for now as size is 0). Ties break by natural // sorting expected = { "Dir2", "Dir1", "testFile1", // 0 "TestFile5Uppercase", // 0 "testFile6", // 10 }; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_SIZE_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_SIZE_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_ASC getChildren with parent"; // By size inverted, dirs first std::reverse(expected.begin(), expected.begin() + 2); std::reverse(expected.begin() + 2, expected.end()); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_SIZE_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_DESC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_SIZE_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_SIZE_DESC getChildren with parent"; // By creation time, dirs first expected = {"Dir1", "testFile1", "testFile6"}; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_CREATION_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_CREATION_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_ASC getChildren with parent"; // By creation inverted std::reverse(expected.begin() + 1, expected.end()); std::reverse(expected.begin(), expected.begin() + 1); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_CREATION_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_DES"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_CREATION_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_CREATION_DESC getChildren with parent"; // By modification time, dirs first but ordered naturally ASC expected = { "Dir1", "Dir2", "testFile6", // 300 s ago "testFile1", // Undef (upload time) }; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_MODIFICATION_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_ASC"; children.reset( megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_MODIFICATION_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_ASC getChildren with parent"; std::reverse(expected.begin(), expected.begin() + 2); std::reverse(expected.begin() + 2, expected.end()); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_MODIFICATION_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_DES"; children.reset( megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_MODIFICATION_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_MODIFICATION_DESC getChildren with parent"; // By label, dirs and natural sort expected = { "testFile1", // Red (1) "Dir1", // Purple (6) "Dir2", // Nothing "TestFile5Uppercase", // Nothing "testFile6", // Nothing }; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_LABEL_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_LABEL_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_ASC getChildren with parent"; expected = { "Dir1", // Purple (6) "testFile1", // Red (1) "Dir2", // Nothing "TestFile5Uppercase", // Nothing "testFile6" // Nothing }; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_LABEL_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_DESC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_LABEL_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_LABEL_DESC getChildren with parent"; // By fav, dirs first. Ties broken by natural sort expected = { "Dir1", // fav "testFile6", // fav "Dir2", // not fav "testFile1", // not fav "TestFile5Uppercase", // not fav }; children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_FAV_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_FAV_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_ASC getChildren with parent"; // By fav inverted, dirs first std::rotate(expected.begin(), expected.begin() + 2, expected.end()); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_FAV_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_DESC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_FAV_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; EXPECT_THAT(toNamesVector(*children), ContainsInOrder(expected)) << "Unexpected sorting for ORDER_FAV_DESC getChildren with parent"; } /** * @class SdkTestFilter * @brief Test suite to test some filtering options for the searching methods * */ class SdkTestFilter: public SdkTestNodesSetUp { const std::vector& getElements() const override { static const std::vector ELEMENTS{ FileNodeInfo("testFile1") .setDescription("This is a test description") .setTags({"foo", "bar"}) .setFav(true), DirNodeInfo("Dir1") .setSensitive(true) .addChild(FileNodeInfo("testFile2") .setDescription("description of file 2") .setTags({"bar", "testTag"})) .addChild(FileNodeInfo("F3").setFav(true)), }; return ELEMENTS; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_FILTER_AUX_DIR"}; return dirName; } }; /** * @brief SdkTestFilter.SdkFilterByFav * * Filter search results by favourite */ TEST_F(SdkTestFilter, SdkFilterByFav) { using testing::NotNull; using testing::UnorderedElementsAreArray; const auto allNodesNames = getAllNodesNames(); // By fav: All std::unique_ptr filteringInfo(getDefaultfilter()); filteringInfo->byFavourite(MegaSearchFilter::BOOL_FILTER_DISABLED); std::unique_ptr searchResults(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), UnorderedElementsAreArray(allNodesNames)) << "Unexpected filtering results for byFavourite(BOOL_FILTER_DISABLED)"; // By fav: Only favs filteringInfo = getDefaultfilter(); filteringInfo->byFavourite(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); std::vector expectedFavs{ "testFile1", // fav "F3", // fav }; searchResults.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), UnorderedElementsAreArray(expectedFavs)) << "Unexpected filtering results for byFavourite(BOOL_FILTER_ONLY_TRUE)"; // By fav: Only not favs (non-favs + favs = all) filteringInfo = getDefaultfilter(); filteringInfo->byFavourite(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); searchResults.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; auto obtainedNonFavs = toNamesVector(*searchResults); EXPECT_EQ(obtainedNonFavs.size() + expectedFavs.size(), allNodesNames.size()) << "The number of non fav nodes + fav nodes is different from the total number of nodes"; std::for_each( obtainedNonFavs.begin(), obtainedNonFavs.end(), [&allFavs = std::as_const(expectedFavs), &all = std::as_const(allNodesNames)](const auto& noFav) { EXPECT_THAT(all, testing::Contains(noFav)) << "byFavourite(BOOL_FILTER_ONLY_FALSE) gave an node that should not exist"; EXPECT_THAT(allFavs, testing::Not(testing::Contains(noFav))) << "byFavourite(BOOL_FILTER_ONLY_FALSE) gave a favourite node as result"; }); } /** * @brief SdkTestFilter.SdkFilterBySensitivity * * Filter search results by sensitivity * * @note To get only nodes marked as sensitive use BOOL_FILTER_ONLY_FALSE * @note To get only nodes that are not sensitive and do not have any sensitive ancestors use * BOOL_FILTER_ONLY_TRUE */ TEST_F(SdkTestFilter, SdkFilterBySensitivity) { using testing::NotNull; using testing::UnorderedElementsAreArray; const auto allNodesNames = getAllNodesNames(); std::unique_ptr filteringInfo(getDefaultfilter()); std::vector expectedSens{"Dir1"}; filteringInfo = getDefaultfilter(); filteringInfo->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); std::unique_ptr searchResults(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), UnorderedElementsAreArray(expectedSens)) << "Unexpected filtering results for bySensitivity(BOOL_FILTER_ONLY_FALSE)"; auto expectedNoSens = getAllNodesNames(); expectedNoSens.erase(std::remove_if(expectedNoSens.begin(), expectedNoSens.end(), [](const auto& e) { static const std::array sens{"Dir1", "testFile2", "F3"}; return std::find(sens.begin(), sens.end(), e) != sens.end(); }), expectedNoSens.end()); filteringInfo = getDefaultfilter(); filteringInfo->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); searchResults.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), UnorderedElementsAreArray(expectedNoSens)) << "Unexpected filtering results for bySensitivity(BOOL_FILTER_ONLY_TRUE)"; } /** * @brief SdkTestFilter.SdkAndOrSwitchCombinaion * * Filter search results by text conditions (name, description, tags) combining conditions with AND * or OR logic operations. */ TEST_F(SdkTestFilter, SdkAndOrSwitchCombinaion) { using testing::NotNull; using testing::UnorderedElementsAreArray; // Load the default filter std::unique_ptr filteringInfo(getDefaultfilter()); //// Combine by AND (this is the default already tested in other tests) // Two nodes matching tag but only one matches description filteringInfo->useAndForTextQuery(true); filteringInfo->byTag("bar"); filteringInfo->byDescription("test"); std::unique_ptr results(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray({"testFile1"})); // Two nodes match name but none matches description filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(true); filteringInfo->byName("testFile"); filteringInfo->byDescription("Foo"); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_EQ(results->size(), 0); // No filters should return all filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(true); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray(getAllNodesNames())); //// Combine by OR // Two nodes matching tag but only one matches description filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(false); filteringInfo->byTag("bar"); filteringInfo->byDescription("test"); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray({"testFile1", "testFile2"})); // One node matches tag and other matches description filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(false); filteringInfo->byTag("testTag"); filteringInfo->byDescription("test"); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray({"testFile1", "testFile2"})); // Two nodes match name, none matches description filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(false); filteringInfo->byName("testFile"); filteringInfo->byDescription("Foo"); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray({"testFile1", "testFile2"})); // No filters should return all filteringInfo = getDefaultfilter(); filteringInfo->useAndForTextQuery(false); results.reset(megaApi[0]->search(filteringInfo.get())); ASSERT_THAT(results, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*results), UnorderedElementsAreArray(getAllNodesNames())); } /** * @class SdkTestUnicodeSort * @brief Test suite to test unicode strings sort * */ class SdkTestUnicodeSort: public SdkTestNodesSetUp { const std::vector& getElements() const override { static const std::vector ELEMENTS{ FileNodeInfo("11.txt"), FileNodeInfo("#.txt"), FileNodeInfo("中文.txt"), FileNodeInfo("😼.txt"), FileNodeInfo("१.txt"), FileNodeInfo("一.txt"), FileNodeInfo("2.txt"), FileNodeInfo("cafe.txt"), FileNodeInfo("file2.txt"), FileNodeInfo("1.txt"), FileNodeInfo("Cafe.txt"), FileNodeInfo("अमन.txt.txt"), FileNodeInfo("file1.txt"), FileNodeInfo("cáfe.txt"), FileNodeInfo("file11.txt"), FileNodeInfo("test.txt"), FileNodeInfo("中文1.txt"), FileNodeInfo("☺️.txt"), FileNodeInfo("{}.txt"), }; return ELEMENTS; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_UNICODE_SORT_AUX_DIR"}; return dirName; } }; /** * @brief SdkTestUnicodeSort.GetChildrenSortedByName * * Get children nodes and sort them by name */ TEST_F(SdkTestUnicodeSort, GetChildrenSortedByName) { using testing::ElementsAreArray; using testing::NotNull; std::unique_ptr filteringInfo(getDefaultfilter()); std::vector expected{ "{}.txt", "#.txt", "☺️.txt", "😼.txt", "१.txt", "1.txt", "2.txt", "11.txt", "cafe.txt", "Cafe.txt", "cáfe.txt", "file1.txt", "file2.txt", "file11.txt", "test.txt", "अमन.txt.txt", "一.txt", "中文.txt", "中文1.txt", }; std::unique_ptr children( megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; ASSERT_THAT(toNamesVector(*children), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; ASSERT_THAT(toNamesVector(*children), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC getChildren with parent"; std::reverse(expected.begin(), expected.end()); children.reset(megaApi[0]->getChildren(filteringInfo.get(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() returned a nullptr"; ASSERT_THAT(toNamesVector(*children), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC"; children.reset(megaApi[0]->getChildren(getRootTestDirectory(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(children, NotNull()) << "getChildren() with parent returned a nullptr"; ASSERT_THAT(toNamesVector(*children), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC getChildren with parent"; } /** * @brief SdkTestUnicodeSort.SearchSortedByName * * Search nodes and sort them by name */ TEST_F(SdkTestUnicodeSort, SearchSortedByName) { using testing::ElementsAreArray; using testing::NotNull; std::unique_ptr filteringInfo(getDefaultfilter()); std::vector expected{ "{}.txt", "#.txt", "☺️.txt", "😼.txt", "१.txt", "1.txt", "2.txt", "11.txt", "cafe.txt", "Cafe.txt", "cáfe.txt", "file1.txt", "file2.txt", "file11.txt", "test.txt", "अमन.txt.txt", "一.txt", "中文.txt", "中文1.txt", }; std::unique_ptr searchResults( megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_ASC"; std::reverse(expected.begin(), expected.end()); searchResults.reset(megaApi[0]->search(filteringInfo.get(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_THAT(searchResults, NotNull()) << "search() returned a nullptr"; EXPECT_THAT(toNamesVector(*searchResults), ElementsAreArray(expected)) << "Unexpected sorting for ORDER_DEFAULT_DESC"; } sdk-10.11.0/tests/integration/SdkTestFolderController_test.cpp000066400000000000000000000134441516266226600244660ustar00rootroot00000000000000/** * @file SdkTestFoldercontroller_test.cpp * @brief This file defines tests related to the folder controller functionality. */ #include "mock_listeners.h" #include "SdkTest_test.h" using namespace testing; class SdkTestFolderController: public SdkTest { public: void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); rootNode.reset(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); } void createLocalTree() { removeLocalTree(); // Expand to a more complex structure when needed fs::create_directories(localFolderPath); ASSERT_TRUE(createFile(path_u8string(localFolderPath / localFileName), false)); } void removeLocalTree() { if (fs::exists(localFolderPath)) { std::error_code ignoredEc; fs::remove_all(localFolderPath, ignoredEc); } ASSERT_FALSE(fs::exists(localFolderPath)); } const std::string& getLocalFolderName() const { return localFolderName; } const std::string& getFileName() const { return localFileName; } const unique_ptr& getRootNode() const { return rootNode; } private: const std::string localFolderName = getFilePrefix() + "baseDir"; const std::string localFileName = "fileTest"; // One (any) file in the tree structure const fs::path localFolderPath = fs::current_path() / localFolderName; unique_ptr rootNode; }; /** * Check propagation of appData to files of folder transfers */ TEST_F(SdkTestFolderController, AppData) { const std::string testAppData = "myAppData"; static const std::string logPre{getLogPrefix()}; LOG_info << logPre << "starting"; std::unique_ptr> listener; // Add a listener and expectations on the transfers: // - A specific file should be uploaded once. Store its appData in the promise // - A specific file should be downloaded once. Store its appData in the promise listener.reset(new NiceMock{megaApi[0].get()}); std::promise appDataUploadTransfer; std::promise appDataDownloadTransfer; const auto matchFileName = Pointee(Property(&MegaTransfer::getFileName, EndsWith(getFileName()))); const auto matchUpload = Pointee(Property(&MegaTransfer::getType, MegaTransfer::TYPE_UPLOAD)); const auto matchDownload = Pointee(Property(&MegaTransfer::getType, MegaTransfer::TYPE_DOWNLOAD)); EXPECT_CALL(*listener.get(), onTransferStart).Times(AnyNumber()); EXPECT_CALL(*listener.get(), onTransferStart(_, AllOf(matchFileName, matchUpload))) .WillOnce( [&appDataUploadTransfer](MegaApi*, MegaTransfer* transfer) { if (transfer->getAppData()) { appDataUploadTransfer.set_value(transfer->getAppData()); } else { appDataUploadTransfer.set_value(""); } }); EXPECT_CALL(*listener.get(), onTransferStart(_, AllOf(matchFileName, matchDownload))) .WillOnce( [&appDataDownloadTransfer](MegaApi*, MegaTransfer* transfer) { if (transfer->getAppData()) { appDataDownloadTransfer.set_value(transfer->getAppData()); } else { appDataDownloadTransfer.set_value(""); } }); megaApi[0]->addListener(listener.get()); LOG_info << logPre << "Testing appData during a folder upload"; createLocalTree(); MegaHandle remoteFolderHandle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &remoteFolderHandle, getLocalFolderName().c_str(), getRootNode().get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, testAppData.c_str(), false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/ )) << "Failed to upload a folder"; auto futureAppData = appDataUploadTransfer.get_future(); ASSERT_EQ(futureAppData.wait_for(1s), std::future_status::ready) << "Expected file not uploaded"; ASSERT_EQ(testAppData, futureAppData.get()) << "appData has not been correctly propagated to the upload subtransfers"; LOG_info << logPre << "Testing appData during a folder download"; removeLocalTree(); unique_ptr remoteFolderNode{megaApi[0]->getNodeByHandle(remoteFolderHandle)}; ASSERT_TRUE(remoteFolderNode); ASSERT_EQ(MegaError::API_OK, doStartDownload(0, remoteFolderNode.get(), getLocalFolderName().c_str(), nullptr /*customName*/, testAppData.c_str(), false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE, false /* undelete */ )) << "Failed to download a folder"; futureAppData = appDataDownloadTransfer.get_future(); ASSERT_EQ(futureAppData.wait_for(1s), std::future_status::ready) << "Expected file not downloaded"; ASSERT_EQ(testAppData, futureAppData.get()) << "appData has not been correctly propagated to the download subtransfers"; } sdk-10.11.0/tests/integration/SdkTestGetChildrenLexicographic_test.cpp000066400000000000000000000107471516266226600261040ustar00rootroot00000000000000#include "megaapi.h" #include "SdkTestNodesSetUp.h" #include #include #include #include using namespace sdk_test; using namespace testing; using namespace std::string_view_literals; class SdkTestGetChildrenLexicographic: public SdkTestNodesSetUp { const std::vector& getElements() const override { static const std::vector ELEMENTS{ FileNodeInfo("a"), DirNodeInfo("a"), FileNodeInfo("a1"), FileNodeInfo("a2"), FileNodeInfo("a11"), FileNodeInfo("A1"), FileNodeInfo("A2"), FileNodeInfo("B"), FileNodeInfo("b"), DirNodeInfo("b"), // Same name, different types FileNodeInfo("ab"), FileNodeInfo("aB"), }; return ELEMENTS; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_GETCHILDRENLEXICOGRAPHIC"}; return dirName; } public: MegaHandle getRootDirHandle() const { return getRootTestDirectory()->getHandle(); } const auto& expectedInOrder() { static constexpr std::array expected{ "A1"sv, "A2"sv, "B"sv, "a"sv, "a/"sv, "a1"sv, "a11"sv, "a2"sv, "aB"sv, "ab"sv, "b"sv, "b/"sv, }; return expected; } }; static std::vector toNamesVectorWithSlashForDirs(MegaNodeList* nodes) { std::vector result; result.reserve(static_cast(nodes->size())); const auto getNameWithSlash = [](auto* node) -> std::string { if (node->getType() == MegaNode::TYPE_FOLDER) return node->getName() + "/"s; return node->getName(); }; for (int i = 0; i < nodes->size(); ++i) { result.emplace_back(getNameWithSlash(nodes->get(i))); } return result; } TEST_F(SdkTestGetChildrenLexicographic, CommonCases) { const auto expected = expectedInOrder(); { // Validate order const auto searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle()); EXPECT_THAT(toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(expectedInOrder())); } { // Validate limit const auto searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle(), nullptr, 5); EXPECT_THAT(toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(expected.begin(), expected.begin() + 5)); } { // Validate offset alone MegaSearchLexicographicalOffset offset{"a"}; // With no type should start at a1 auto searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle(), nullptr, 0, offset); EXPECT_THAT( toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(std::find(begin(expected), end(expected), "a1"sv), expected.end())); offset.mLastType = MegaNode::TYPE_FILE; // Now a/ should be contained searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle(), nullptr, 0, offset); EXPECT_THAT( toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(std::find(begin(expected), end(expected), "a/"sv), expected.end())); } { // Validate a real case: 1. Take first n elements 2. Resume from last result static constexpr int N_FIRST{4}; // so the end is between "a" and "a/" // First query auto searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle(), nullptr, N_FIRST); EXPECT_THAT(toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(expected.begin(), expected.begin() + N_FIRST)); // Rest auto* lastNode = searchResults->get(searchResults->size() - 1); MegaSearchLexicographicalOffset offset{lastNode->getName(), lastNode->getType(), lastNode->getHandle()}; searchResults = megaApi[0]->listChildNodesLexicographically(getRootDirHandle(), nullptr, 0, offset); EXPECT_THAT(toNamesVectorWithSlashForDirs(searchResults), ElementsAreArray(expected.begin() + N_FIRST, expected.end())); } } sdk-10.11.0/tests/integration/SdkTestGetRecentActions_test.cpp000066400000000000000000001311721516266226600244070ustar00rootroot00000000000000/** * @file tests/SdkTestGetRecentActions_test.cpp * @brief Integration tests for getRecentActionsAsync and getRecentActionById. */ #include "mega/utils.h" #include "megautils.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" #include #include #include using namespace std; namespace { // Build a recent-action id matching the server/client token format: // dayStart|windowStart|windowEnd|user|parent|media|update|excludeSensitives std::string buildRecentActionId(const std::string& dayStart, const std::string& windowStart, const std::string& windowEnd, const std::string& userHandle, const std::string& parentHandle, const std::string& media, const std::string& update, const std::string& excludeSensitives) { return dayStart + "|" + windowStart + "|" + windowEnd + "|" + userHandle + "|" + parentHandle + "|" + media + "|" + update + "|" + excludeSensitives; } std::string replaceToken(const std::string& id, size_t tokenIndex, const std::string& value) { auto tokens = splitString>(id, '|'); if (tokenIndex >= tokens.size()) { return id; } tokens[tokenIndex] = value; std::string out; for (size_t i = 0; i < tokens.size(); ++i) { if (i) { out.push_back('|'); } out.append(tokens[i]); } return out; } // Find the bucket that contains a given filename. // Returns nullptr if not found. const MegaRecentActionBucket* getBucketContainingFilename(const MegaRecentActionBucketList& buckets, const std::string& filename) { const auto bucketsAsVec = bucketsToVector(buckets); for (size_t i = 0; i < bucketsAsVec.size(); ++i) { const auto& bucketFiles = bucketsAsVec[i]; if (std::find(bucketFiles.begin(), bucketFiles.end(), filename) != bucketFiles.end()) { return buckets.get(static_cast(i)); } } return nullptr; } // Return the id of the bucket that contains a given filename. // Returns an empty string if not found. std::string getRecentActionIdContainingFilename(const MegaRecentActionBucketList& buckets, const std::string& filename) { const MegaRecentActionBucket* bucket = getBucketContainingFilename(buckets, filename); return (bucket && bucket->getId()) ? bucket->getId() : std::string{}; } // Fetch recent action buckets. Records a failure and returns nullptr on error. std::unique_ptr fetchRecentBuckets(MegaApi* api, unsigned maxdays, unsigned maxcount, bool excludeSensitives = true) { RequestTracker tracker(api); api->getRecentActionsAsync(maxdays, maxcount, excludeSensitives, &tracker); if (tracker.waitForResult() != API_OK || !tracker.request || !tracker.request->getRecentActions()) { ADD_FAILURE() << "getRecentActionsAsync failed"; return nullptr; } return std::unique_ptr{tracker.request->getRecentActions()->copy()}; } // Call getRecentActionById and assert API_OK with exactly one bucket returned. // Records a failure and returns nullptr on any mismatch. std::unique_ptr queryByIdAssertOk(MegaApi* api, const std::string& id) { RequestTracker tracker(api); api->getRecentActionById(id.c_str(), &tracker); const int rc = tracker.waitForResult(); if (rc != API_OK) { ADD_FAILURE() << "getRecentActionById(" << id << ") returned " << rc << ", expected API_OK"; return nullptr; } if (!tracker.request || !tracker.request->getRecentActions() || tracker.request->getRecentActions()->size() != 1) { ADD_FAILURE() << "getRecentActionById did not return exactly 1 bucket"; return nullptr; } return std::unique_ptr{tracker.request->getRecentActions()->copy()}; } // Call getRecentActionById(id, excludeSensitives) overload, assert the expected error code, // and return the bucket list (nullptr when the call did not succeed with API_OK). std::unique_ptr queryByIdWithFlag(MegaApi* api, const std::string& id, bool excludeSensitives, int expectedCode) { RequestTracker tracker(api); api->getRecentActionById(id.c_str(), excludeSensitives, &tracker); const int rc = tracker.waitForResult(); EXPECT_EQ(rc, expectedCode) << "getRecentActionById(" << id << ", " << excludeSensitives << ") returned " << rc << ", expected " << expectedCode; if (rc == API_OK && tracker.request) { EXPECT_EQ(tracker.request->getFlag(), excludeSensitives); } if (rc != API_OK || !tracker.request || !tracker.request->getRecentActions() || tracker.request->getRecentActions()->size() != 1) { return nullptr; } return std::unique_ptr{tracker.request->getRecentActions()->copy()}; } } /** * Integration test for getRecentActionsAsync and clearRecentActionHistory. * * Scenario setup: * - Two sessions for the same account are opened (megaApi[0] and megaApi[1]). * - Four distinct files are uploaded in sequence with 1-second gaps to * guarantee deterministic bucket ordering: * file1.txt (empty) → file1.txt.bkp1 → file1.txt.bkp2 * → file1.txt (updated) → file2.txt → file2.txt (updated) * - file1.txt is marked sensitive after its second upload. * * Sections covered: * 1. Input validation – days=0 and maxnodes=0 must return API_EARGS. * 2. Full listing – all buckets returned without sensitive exclusion; * verified from both sessions of the same account. * 3. maxnodes cap – only the most-recent bucket is returned when the * node limit is reached before the second bucket. * 4. Sensitive filter – file1.txt (sensitive) is absent when * excludeSensitives=true; rest of the bucket shape * is unchanged. Verified via both the parameterised * and the simple (default-exclusion) overloads. * 5. maxnodes + filter – combined: only the first filtered bucket returned. * 6. clearRecentActionHistory happy path – actions older than 'now' are * hidden; the clear timestamp is persisted as a user * attribute and propagated to the second session via * an SC action packet followed by an automatic fetch. * 7. Post-clear listing – a new upload made after the clear appears in * results while the earlier uploads remain hidden. * 8. Invalid clear timestamps – values 0 and -1 are rejected (API_EARGS) * and must not overwrite the stored timestamp. * 9. Re-login session – a freshly logged-in session (megaApi[2]) picks up * the persisted clear timestamp via getuserdata and * returns the correct post-clear result set. */ TEST_F(SdkTest, SdkRecentActionsTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Open a second session with the same account credentials so we can later // verify that clearRecentActionHistory propagates across sessions. loginSameAccountsForTest(0); const auto updloadFile = [this, rootnode = std::unique_ptr(megaApi[0]->getRootNode())]( const std::string& fname, const std::string_view contents) { sdk_test::LocalTempFile f(fname, contents); auto node = sdk_test::uploadFile(megaApi[0].get(), fname, rootnode.get()); ASSERT_TRUE(node) << "Cannot upload " << fname; }; // --- Section 1: Build the file history used throughout the test --- // The expected bucket layout after all uploads (newest bucket first): // bucket 0: {file2.txt, file1.txt} (updates, share a time window) // bucket 1: {file1.txt.bkp2, file1.txt.bkp1} (original uploads) // file1.txt is marked sensitive, which exercises the exclusion filter. const std::string filename1 = "file1.txt"; const std::string filename1bkp1 = filename1 + ".bkp1"; const std::string filename1bkp2 = filename1 + ".bkp2"; const std::string filename2 = "file2.txt"; // Delays are added to ensure ordering in recent actions LOG_debug << "# SdkRecentsTest: uploading file " << filename1; updloadFile(filename1, ""); WaitMillisec(1000); LOG_debug << "# SdkRecentsTest: uploading file " << filename1bkp1; updloadFile(filename1bkp1, ""); WaitMillisec(1000); LOG_debug << "# SdkRecentsTest: uploading file " << filename1bkp2; updloadFile(filename1bkp2, ""); WaitMillisec(1000); // Re-uploading filename1 with different content creates a new version and // moves it into a newer time window together with the subsequent file2.txt. LOG_debug << "# SdkRecentsTest: updating file " << filename1; updloadFile(filename1, "update"); WaitMillisec(1000); // Mark file1.txt as sensitive so it can be hidden by the exclusion filter. LOG_debug << "# SdkRecentsTest: Marking file " << filename1 << " as sensitive"; std::unique_ptr f1node(megaApi[0]->getNodeByPath(("/" + filename1).c_str())); ASSERT_NE(f1node, nullptr); ASSERT_EQ(synchronousSetNodeSensitive(0, f1node.get(), true), API_OK) << "Error marking file as sensitive"; LOG_debug << "# SdkRecentsTest: uploading file " << filename2; updloadFile(filename2, ""); WaitMillisec(1000); LOG_debug << "# SdkRecentsTest: updating file " << filename2; updloadFile(filename2, "update"); // Ensure the second session (megaApi[1]) is fully synced before querying. synchronousCatchup(1, maxTimeout); const auto getRecentActionBuckets = [this](unsigned int index, unsigned days, unsigned maxnodes, bool optExcludeSensitives, ::mega::ErrorCodes expectedCode, vector& expectedVec, bool simple = false) { RequestTracker tracker(megaApi[index].get()); simple ? megaApi[index]->getRecentActionsAsync(days, maxnodes, &tracker) : megaApi[index]->getRecentActionsAsync(days, maxnodes, optExcludeSensitives, &tracker); ASSERT_EQ(tracker.waitForResult(), expectedCode); if (expectedCode != API_OK) { return; } std::unique_ptr buckets{ tracker.request->getRecentActions()->copy()}; ASSERT_TRUE(buckets != nullptr); EXPECT_TRUE(bucketsToVector(*buckets) == expectedVec); }; // --- Section 2: Input validation --- // days=0 is not a valid lookback window; both overloads must reject it. vector expectedEmpty = {}; LOG_debug << "# SdkRecentsTest: Get all recent actions with invalid days=0"; getRecentActionBuckets(0, 0, 10, false, API_EARGS, expectedEmpty); getRecentActionBuckets(0, 0, 10, false, API_EARGS, expectedEmpty, true); // maxnodes=0 would return no nodes, which is meaningless; both overloads must reject it. LOG_debug << "# SdkRecentsTest: Get all recent actions with invalid maxnodes=0"; getRecentActionBuckets(0, 1, 0, false, API_EARGS, expectedEmpty); getRecentActionBuckets(0, 1, 0, false, API_EARGS, expectedEmpty, true); // --- Section 3: Full listing without sensitive exclusion --- // Both sessions of the same account must return the identical bucket layout. LOG_debug << "# SdkRecentsTest: Get all recent actions (no exclusion)"; vector expectedInclude = {{filename2, filename1}, {filename1bkp2, filename1bkp1}}; getRecentActionBuckets(0, 1, 10, false, API_OK, expectedInclude); getRecentActionBuckets(1, 1, 10, false, API_OK, expectedInclude); // --- Section 4: maxnodes cap (no exclusion) --- // With maxnodes=2 the result is truncated: only the most-recent two nodes // (both in bucket 0) are returned, so bucket 1 disappears entirely. LOG_debug << "# SdkRecentsTest: Get 2 recent actions (no exclusion)"; vector expectedIncludeMax2 = {{filename2, filename1}}; getRecentActionBuckets(0, 1, 2, false, API_OK, expectedIncludeMax2); // --- Section 5: Sensitive exclusion --- // file1.txt is sensitive, so the first bucket loses it and becomes a // single-node bucket {file2.txt}. The second bucket is unaffected. // The simple overload uses excludeSensitives=true by default. LOG_debug << "# SdkRecentsTest: Get recent actions excluding sensitive nodes"; vector expectedExclude = {{filename2}, {filename1bkp2, filename1bkp1}}; getRecentActionBuckets(0, 1, 10, true, API_OK, expectedExclude); getRecentActionBuckets(0, 1, 10, true, API_OK, expectedExclude, true); // --- Section 6: maxnodes cap + sensitive exclusion --- // maxnodes=1 with exclusion: only {file2.txt} survives; the second bucket // is dropped because the node quota is exhausted after the first node. LOG_debug << "# SdkRecentsTest: Get 1 recent actions excluding sensitive nodes"; vector expectedExcludeMax1 = {{filename2}}; getRecentActionBuckets(0, 1, 1, true, API_OK, expectedExcludeMax1); // --- Section 7: clearRecentActionHistory happy path --- // Sleep so the recorded 'now' timestamp is strictly greater than the // most-recent action timestamp, guaranteeing all prior actions are hidden. WaitMillisec(1000); const auto setClearRecentsUpTo = [this](MegaTimeStamp timestamp, ::mega::ErrorCodes expectedCode) { // set user attributes to ensure SDK is working properly before clearing recents RequestTracker trackerSetAttr(megaApi[0].get()); megaApi[0]->clearRecentActionHistory(timestamp, &trackerSetAttr); ASSERT_EQ(trackerSetAttr.waitForResult(), expectedCode); EXPECT_EQ(trackerSetAttr.request->getNumber(), timestamp); }; const auto verifyClearRecentsUpTo = [this](unsigned index, m_time_t timestamp) { // get user attributes to ensure SDK is working properly after clearing recents RequestTracker trackerGetAttr(megaApi[index].get()); megaApi[index]->getUserAttribute(MegaApi::USER_ATTR_RECENT_CLEAR_TIMESTAMP, &trackerGetAttr); ASSERT_EQ(trackerGetAttr.waitForResult(), API_OK); EXPECT_EQ(trackerGetAttr.request->getNumber(), timestamp); }; m_time_t now = m_time(); // Reset update counters so we can count exactly how many attribute-change // notifications each session receives after the clear. int& recentClearTimeUpdatedCount0 = mApi[0].recentClearTimeUpdatedCount = 0; int& recentClearTimeUpdatedCount1 = mApi[1].recentClearTimeUpdatedCount = 0; LOG_debug << "# SdkRecentsTest: Clear recent actions up to now"; setClearRecentsUpTo(now, API_OK); // After clearing, the primary session must see an empty bucket list and the // stored attribute must equal the timestamp we just set. LOG_debug << "# SdkRecentsTest: Get all recent actions after clear"; getRecentActionBuckets(0, 1, 10, false, API_OK, expectedEmpty); verifyClearRecentsUpTo(0, now); // The secondary session should receive exactly two notifications: // 1. An SC action packet that propagates the attribute change in real-time. // 2. An automatic re-fetch triggered by the SDK once the new value is seen. // wait for 2 update, 1 is for SC action packet, 1 is for automatically fetching ASSERT_TRUE(WaitFor( [&recentClearTimeUpdatedCount1]() { return recentClearTimeUpdatedCount1 == 2; }, 10 * 1000)); LOG_debug << "# SdkRecentsTest: the second account fetched the attribute automatically after clear"; // The secondary session must now also return an empty bucket list and have // the same clear timestamp persisted locally. getRecentActionBuckets(1, 1, 10, false, API_OK, expectedEmpty); verifyClearRecentsUpTo(1, now); // The primary session generates only the SC action-packet notification (no // self-triggered re-fetch), so its counter stays at 1. // only 1 SC action packet EXPECT_EQ(recentClearTimeUpdatedCount0, 1); EXPECT_EQ(recentClearTimeUpdatedCount1, 2); // --- Section 8: Post-clear listing --- // A new upload after the clear must appear in results even though all // earlier uploads are hidden by the clear timestamp. updloadFile(filename1, "update after clear"); LOG_debug << "# SdkRecentsTest: Get all recent actions after clear and one new action"; vector expectedAfterClear = {{filename1}}; getRecentActionBuckets(0, 1, 10, false, API_OK, expectedAfterClear); // --- Section 9: Invalid clear timestamp validation --- // Zero and negative timestamps are not valid Unix epoch values; both must // be rejected without modifying the stored clear timestamp. LOG_debug << "# SdkRecentsTest: Clear recent actions up to invalid value 0"; setClearRecentsUpTo(0, API_EARGS); verifyClearRecentsUpTo(0, now); LOG_debug << "# SdkRecentsTest: Clear recent actions up to invalid value -1"; setClearRecentsUpTo(-1, API_EARGS); verifyClearRecentsUpTo(0, now); // --- Section 10: Re-login session picks up persisted state --- // A freshly logged-in session must obtain the clear timestamp via the // initial getuserdata call and therefore return the same post-clear result. LOG_debug << "# SdkRecentsTest: Get all recent actions after login and getuserdata"; loginSameAccountsForTest(0); getRecentActionBuckets(2, 1, 10, false, API_OK, expectedAfterClear); verifyClearRecentsUpTo(2, now); CASE_info << "finished"; } /** * Happy-path validation for getRecentActionById. * * Uploads one file, retrieves the generated bucket id, verifies that the id * encodes the correct metadata, then queries getRecentActionById and confirms * the returned bucket matches the original. */ TEST_F(SdkTest, SdkRecentActionByIdTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const std::string filename = "recent_action_by_id.txt"; { sdk_test::LocalTempFile f(filename, "recent action by id"); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), filename, rootNode.get())); } WaitMillisec(1000); // Locate the bucket that contains our file. auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10); ASSERT_TRUE(buckets); const MegaRecentActionBucket* bucket = getBucketContainingFilename(*buckets, filename); ASSERT_NE(bucket, nullptr) << filename << " not found in recent buckets"; ASSERT_NE(bucket->getId(), nullptr); const std::string id{bucket->getId()}; // Verify the id encodes the expected bucket metadata (owner / parent / flags). const auto idTokens = splitString>(id, '|'); ASSERT_EQ(idTokens.size(), 8u); EXPECT_EQ(idTokens[3], std::string(Base64Str(megaApi[0]->getMyUserHandleBinary()))); EXPECT_EQ(idTokens[4], std::string(Base64Str(bucket->getParentHandle()))); EXPECT_EQ(idTokens[5], bucket->isMedia() ? "1" : "0"); EXPECT_EQ(idTokens[6], bucket->isUpdate() ? "1" : "0"); EXPECT_EQ(idTokens[7], "1"); // excludeSensitives=true by default // Round-trip: getRecentActionById must return the exact same bucket. auto result = queryByIdAssertOk(megaApi[0].get(), id); ASSERT_TRUE(result); EXPECT_STREQ(result->get(0)->getId(), id.c_str()); const auto originalVec = bucketsToVector(*buckets); EXPECT_NE(std::find(originalVec.begin(), originalVec.end(), bucketsToVector(*result)[0]), originalVec.end()); CASE_info << "finished"; } /** * Negative/edge-input validation for getRecentActionById. * * Scenario: * - Send malformed ids (bad token count, invalid day/window, invalid handles, * invalid boolean flags) and verify parser-level rejection. * - Send well-formed ids that do not match existing data and verify lookup miss. * * Expected: * - Malformed inputs -> API_EARGS * - Well-formed but non-existing inputs -> API_ENOENT */ TEST_F(SdkTest, SdkRecentActionByIdAbnormalTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const std::string userHandleB64{ Base64Str(megaApi[0]->getMyUserHandleBinary())}; const std::string parentHandleB64{Base64Str(rootNode->getHandle())}; const std::string otherUserHandleB64{ Base64Str(static_cast(1))}; const auto expectResult = [this](const char* id, int expectedCode) { RequestTracker tracker(megaApi[0].get()); megaApi[0]->getRecentActionById(id, &tracker); ASSERT_EQ(tracker.waitForResult(), expectedCode) << "Unexpected result for id: " << (id ? id : ""); }; const auto expectEargs = [&expectResult](const char* id) { expectResult(id, API_EARGS); }; const auto expectEnoent = [&expectResult](const char* id) { expectResult(id, API_ENOENT); }; // Format/parser validation (EARGS) expectEargs(nullptr); // null id expectEargs(""); // empty id expectEargs("bad|id"); // too few tokens expectEargs("1|0|6||UNDEF|1|0|0"); // empty user handle token expectEargs("1|0|6||||||"); // multiple empty tokens expectEargs(buildRecentActionId("abc", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // non-numeric dayStart expectEargs(buildRecentActionId("0", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // dayStart must be > 0 expectEargs(buildRecentActionId("-1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // negative dayStart expectEargs( buildRecentActionId("12345678901", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // dayStart length overflow expectEargs(buildRecentActionId("1", "100", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // windowStart length > 2 expectEargs(buildRecentActionId("1", "", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // empty windowStart expectEargs(buildRecentActionId("1", "-1", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // negative windowStart expectEargs(buildRecentActionId("1", "0", "100", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // windowEnd length > 2 expectEargs(buildRecentActionId("1", "0", "", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // empty windowEnd expectEargs(buildRecentActionId("1", "0", "-1", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // negative windowEnd expectEargs(buildRecentActionId("1", "3", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // invalid windowStart expectEargs(buildRecentActionId("1", "0", "3", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // invalid windowEnd expectEargs(buildRecentActionId("1", "0", "25", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // windowEnd beyond 24h expectEargs(buildRecentActionId("1", "6", "0", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // reversed time window expectEargs(buildRecentActionId("1", "6", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // empty time window (start == end) expectEargs(buildRecentActionId("1", "6", "18", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // valid time window but not aligned to 6h boundaries (e.g. 0-6, // 6-12, 12-18, 18-24) expectEargs(buildRecentActionId("1", "0", "6", "", parentHandleB64, "1", "0", "0") .c_str()); // empty user handle expectEargs(buildRecentActionId("1", "0", "6", "UNDEF", parentHandleB64, "1", "0", "0") .c_str()); // user handle cannot be UNDEF expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, "", "1", "0", "0") .c_str()); // empty parent handle expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, "UNDEF", "1", "0", "0") .c_str()); // parent handle cannot be UNDEF expectEargs(buildRecentActionId("1", "0", "6", "@@@@", parentHandleB64, "1", "0", "0") .c_str()); // invalid base64 user handle expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, "@@@@", "1", "0", "0") .c_str()); // invalid base64 parent handle expectEargs(buildRecentActionId("1", "0", "6", "A", parentHandleB64, "1", "0", "0") .c_str()); // invalid user handle length expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, "A", "1", "0", "0") .c_str()); // invalid parent handle length expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "yes", "0", "0") .c_str()); // media must be 0/1 expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "2", "0", "0") .c_str()); // media out of range expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "-1", "0", "0") .c_str()); // media negative expectEargs( buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "maybe", "0") .c_str()); // update must be 0/1 expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "-1", "0") .c_str()); // update negative expectEargs( buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "maybe") .c_str()); // excludeSensitives must be 0/1 expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "-1") .c_str()); // excludeSensitives negative expectEargs(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "") .c_str()); // empty excludeSensitives expectEargs((buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") + "|extra") .c_str()); // Well-formed ids that do not match any existing bucket (ENOENT) expectEnoent(buildRecentActionId("1", "0", "6", userHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // valid shape, no matching bucket expectEnoent( buildRecentActionId("1", "0", "6", otherUserHandleB64, parentHandleB64, "1", "0", "0") .c_str()); // non-existing/other owner CASE_info << "finished"; } /** * Filter-branch coverage for getRecentActionById. * * Mutates one id token at a time to force each server-side filter branch and * confirms that any mismatch yields API_ENOENT while the original id resolves. * Covers: owner, parent, time window, media flag, update flag, and excludeSensitives. */ TEST_F(SdkTest, SdkRecentActionByIdFilterMatchTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); // Upload to a sub-folder so the parent handle is distinct from the root. const auto folderHandle = createFolder(0, "recent_action_parent", rootNode.get()); ASSERT_NE(folderHandle, UNDEF); std::unique_ptr parentFolder(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(parentFolder); { const std::string name = "recent_action_filter.txt"; sdk_test::LocalTempFile f(name, "content"); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), name, parentFolder.get())); WaitMillisec(1000); auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10); ASSERT_TRUE(buckets); const std::string id = getRecentActionIdContainingFilename(*buckets, name); ASSERT_FALSE(id.empty()) << name << " not found in recent buckets"; const auto idTokens = splitString>(id, '|'); ASSERT_EQ(idTokens.size(), 8u); const auto expectCode = [this](const std::string& queryId, int expectedCode) { RequestTracker tracker(megaApi[0].get()); megaApi[0]->getRecentActionById(queryId.c_str(), &tracker); EXPECT_EQ(tracker.waitForResult(), expectedCode) << "id=" << queryId; }; expectCode(id, API_OK); // baseline // token[3]: owner mismatch std::string mismachOwner{Base64Str(static_cast(1))}; expectCode(replaceToken(id, 3, mismachOwner), API_ENOENT); // token[4]: parent mismatch (switch to root, which differs from the upload folder) const std::string rootHandleB64{Base64Str(rootNode->getHandle())}; ASSERT_NE(rootHandleB64, idTokens[4]); expectCode(replaceToken(id, 4, rootHandleB64), API_ENOENT); // token[1]/[2]: time window mismatch (shift to a disjoint 6-hour window) const int windowStart = std::stoi(idTokens[1]); const int disjointStart = (windowStart + 6) % 24; expectCode(replaceToken(replaceToken(id, 1, std::to_string(disjointStart)), 2, std::to_string(disjointStart + 6)), API_ENOENT); // token[5]: media flag mismatch expectCode(replaceToken(id, 5, idTokens[5] == "1" ? "0" : "1"), API_ENOENT); // token[6]: update flag mismatch expectCode(replaceToken(id, 6, idTokens[6] == "1" ? "0" : "1"), API_ENOENT); } // token[7]: excludeSensitives mismatch — requires a real sensitive node. { const std::string sensitiveName = "recent_action_sensitive.txt"; sdk_test::LocalTempFile sf(sensitiveName, "sensitive"); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), sensitiveName, rootNode.get())); std::unique_ptr sensitiveNode( megaApi[0]->getNodeByPath(("/" + sensitiveName).c_str())); ASSERT_TRUE(sensitiveNode); ASSERT_EQ(synchronousSetNodeSensitive(0, sensitiveNode.get(), true), API_OK); WaitMillisec(1000); // Fetch with excludeSensitives=false so the sensitive file appears. auto allBuckets = fetchRecentBuckets(megaApi[0].get(), 1, 10, /*excludeSensitives=*/false); ASSERT_TRUE(allBuckets); const std::string sensitiveId = getRecentActionIdContainingFilename(*allBuckets, sensitiveName); ASSERT_FALSE(sensitiveId.empty()) << sensitiveName << " not found in recent buckets"; RequestTracker trackerOk(megaApi[0].get()); megaApi[0]->getRecentActionById(sensitiveId.c_str(), &trackerOk); EXPECT_EQ(trackerOk.waitForResult(), API_OK); // id with excludeSensitives=0 resolves RequestTracker trackerMismatch(megaApi[0].get()); megaApi[0]->getRecentActionById(replaceToken(sensitiveId, 7, "1").c_str(), &trackerMismatch); EXPECT_EQ(trackerMismatch.waitForResult(), API_ENOENT); // flipped to 1 → no match } CASE_info << "finished"; } /** * Cross-session and cross-account isolation validation. * * - Same account, two sessions (megaApi[0] and megaApi[2]): both resolve the id * and return identical bucket payloads. * - Different account (megaApi[1]): cannot resolve the id (API_ENOENT). * - After session catchup: id remains resolvable for the second session. */ TEST_F(SdkTest, SdkRecentActionByIdCrossAccountConsistencyTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // megaApi[2] = second session logged in with the same credentials as megaApi[0]. loginSameAccountsForTest(0); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const std::string filename = "recent_action_cross_account.txt"; { sdk_test::LocalTempFile f(filename, "cross account"); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), filename, rootNode.get())); } WaitMillisec(1000); auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10); ASSERT_TRUE(buckets); const std::string id = getRecentActionIdContainingFilename(*buckets, filename); ASSERT_FALSE(id.empty()) << filename << " not found in recent buckets"; // Same account, session 0: resolve id. auto session0Result = queryByIdAssertOk(megaApi[0].get(), id); ASSERT_TRUE(session0Result); // Same account, session 1: resolve id and verify payload matches session 0. auto session1Result = queryByIdAssertOk(megaApi[2].get(), id); ASSERT_TRUE(session1Result); EXPECT_EQ(bucketsToVector(*session0Result), bucketsToVector(*session1Result)); // Different account must not resolve a foreign id. { RequestTracker tracker(megaApi[1].get()); megaApi[1]->getRecentActionById(id.c_str(), &tracker); EXPECT_EQ(tracker.waitForResult(), API_ENOENT); } // After catchup, same-account session remains consistent. synchronousCatchup(2, maxTimeout); { RequestTracker tracker(megaApi[2].get()); megaApi[2]->getRecentActionById(id.c_str(), &tracker); EXPECT_EQ(tracker.waitForResult(), API_OK); } CASE_info << "finished"; } /** * Positive path for non-default bucket types. * * 1) media=1 – uploading a .mp4 file and a .jpg file produces a media bucket. * 2) update=1 – uploading the same filename twice produces a versioned bucket. * 3) Multi-file bucket – two files in the same folder/window share one bucket id. */ TEST_F(SdkTest, SdkRecentActionByIdBucketVariantsTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const auto upload = [this](const std::string& fname, std::string_view contents, MegaNode* parent) { sdk_test::LocalTempFile f(fname, contents); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), fname, parent)) << "Cannot upload " << fname; }; // Helper: fetch buckets, find the one containing `fname`, assert the given token value. const auto verifyTokenAndQuery = [this](const std::string& fname, size_t tokenIndex, const std::string& expectedTokenValue) -> std::string { auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10); if (!buckets) { return {}; } const std::string id = getRecentActionIdContainingFilename(*buckets, fname); if (id.empty()) { ADD_FAILURE() << fname << " not found in recent buckets"; return {}; } const auto tokens = splitString>(id, '|'); EXPECT_EQ(tokens.size(), 8u); if (tokens.size() > tokenIndex) { EXPECT_EQ(tokens[tokenIndex], expectedTokenValue) << "token[" << tokenIndex << "] mismatch"; } return id; }; // --- media=1: .mp4 and .jpg file produces a media bucket --- { const std::string mp4File = "recent_action_media.mp4"; upload(mp4File, "fake video content", rootNode.get()); WaitMillisec(1000); const std::string photoFile = "recent_action_photo.jpg"; upload(photoFile, "fake photo content", rootNode.get()); WaitMillisec(1000); const std::string mp4Id = verifyTokenAndQuery(mp4File, 5, "1"); // token[5] = media ASSERT_FALSE(mp4Id.empty()); const std::string photoId = verifyTokenAndQuery(photoFile, 5, "1"); // token[5] = media ASSERT_FALSE(photoId.empty()); ASSERT_TRUE(mp4Id == photoId) << "Both files should share the same media bucket"; auto result = queryByIdAssertOk(megaApi[0].get(), mp4Id); ASSERT_TRUE(result); const std::vector expectedFiles{photoFile, mp4File}; EXPECT_EQ(bucketsToVector(*result)[0], expectedFiles); } // --- update=1: same filename uploaded twice produces a versioned node --- { const std::string name = "recent_action_versioned.txt"; upload(name, "version 1", rootNode.get()); WaitMillisec(500); upload(name, "version 2", rootNode.get()); WaitMillisec(1000); const std::string id = verifyTokenAndQuery(name, 6, "1"); // token[6] = update ASSERT_FALSE(id.empty()); auto result = queryByIdAssertOk(megaApi[0].get(), id); ASSERT_TRUE(result); const std::vector expectedFiles{name}; EXPECT_EQ(bucketsToVector(*result)[0], expectedFiles); } // --- multi-file: two files uploaded to the same folder share one bucket --- { const auto folderHandle = createFolder(0, "recent_action_multi", rootNode.get()); ASSERT_NE(folderHandle, UNDEF); std::unique_ptr folder(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(folder); const std::string nameA = "recent_action_multi_a.txt"; const std::string nameB = "recent_action_multi_b.txt"; upload(nameA, "content a", folder.get()); upload(nameB, "content b", folder.get()); WaitMillisec(1000); auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10); ASSERT_TRUE(buckets); const std::string idA = getRecentActionIdContainingFilename(*buckets, nameA); const std::string idB = getRecentActionIdContainingFilename(*buckets, nameB); ASSERT_FALSE(idA.empty()) << nameA << " not found"; EXPECT_EQ(idA, idB) << "Both files should share the same bucket"; auto result = queryByIdAssertOk(megaApi[0].get(), idA); ASSERT_TRUE(result); const auto nodeNames = bucketsToVector(*result)[0]; EXPECT_EQ(nodeNames.size(), 2u); EXPECT_NE(std::find(nodeNames.begin(), nodeNames.end(), nameA), nodeNames.end()); EXPECT_NE(std::find(nodeNames.begin(), nodeNames.end(), nameB), nodeNames.end()); } CASE_info << "finished"; } /** * Validates getRecentActionById(id, excludeSensitives, listener) — the overload that lets the * caller override the excludeSensitives flag embedded in the bucket id. * * Scenario: * 1. Upload a file to a dedicated subfolder and mark it sensitive. * fetchRecentBuckets(excludeSensitives=false) -> sensitiveId ends with "|0". * - new API with excludeSensitives=false -> API_OK (param matches id flag; getFlag()=false) * - new API with excludeSensitives=true -> API_ENOENT (param overrides id's 0; file filtered) * * 2. Upload a non-sensitive file to a different subfolder. * fetchRecentBuckets(excludeSensitives=true) -> nonSensitiveId ends with "|1". * - new API with excludeSensitives=true -> API_OK (param matches id flag; getFlag()=true) * - new API with excludeSensitives=false -> API_OK (param overrides id's 1; file still visible) */ TEST_F(SdkTest, SdkRecentActionByIdExcludeSensitivesOverrideTest) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); // Creates a folder under rootNode and returns the corresponding MegaNode. // Records a failure and returns nullptr on error. const auto createFolderNode = [this, &rootNode](const char* name) -> std::unique_ptr { const auto handle = createFolder(0, name, rootNode.get()); if (handle == UNDEF) { ADD_FAILURE() << "createFolder failed for " << name; return nullptr; } std::unique_ptr node(megaApi[0]->getNodeByHandle(handle)); if (!node) { ADD_FAILURE() << "getNodeByHandle failed for " << name; return nullptr; } return node; }; // Fetches recent buckets with the given excludeSensitives flag and returns the id. const auto fetchIdForFile = [this](const std::string& filename, bool excludeSensitives) -> std::string { auto buckets = fetchRecentBuckets(megaApi[0].get(), 1, 10, excludeSensitives); if (!buckets) return {}; return getRecentActionIdContainingFilename(*buckets, filename); }; // Verifies that the given id has 8 tokens and that token[7] encodes the excludeSensitives flag const auto checkIdToken = [](const std::string& id, bool excludeSensitives) { const auto tokens = splitString>(id, '|'); ASSERT_EQ(tokens.size(), 8u); ASSERT_EQ(tokens[7], excludeSensitives ? "1" : "0"); }; // Calls queryByIdWithFlag and, when API_OK is expected, verifies that the returned bucket // contains the given filename. const auto assertQueryByIdWithFlagContains = [this](const std::string& id, bool excludeSensitives, int expectedCode, const std::string& filename) { auto result = queryByIdWithFlag(megaApi[0].get(), id, excludeSensitives, expectedCode); if (expectedCode != API_OK) return; ASSERT_TRUE(result); const auto nodeNames = bucketsToVector(*result)[0]; ASSERT_NE(std::find(nodeNames.begin(), nodeNames.end(), filename), nodeNames.end()); }; // --- Part 1: sensitive-only bucket --- // Upload a file to a dedicated subfolder, then mark it sensitive. auto sensitiveFolder = createFolderNode("recent_action_sensitive_folder"); ASSERT_TRUE(sensitiveFolder); const std::string sensitiveName = "recent_action_override_sensitive.txt"; { sdk_test::LocalTempFile f(sensitiveName, "sensitive content"); auto sensitiveNode = sdk_test::uploadFile(megaApi[0].get(), sensitiveName, sensitiveFolder.get()); ASSERT_TRUE(sensitiveNode) << "Cannot upload " << sensitiveName; ASSERT_EQ(synchronousSetNodeSensitive(0, sensitiveNode.get(), true), API_OK); } // Fetch with excludeSensitives=false so the sensitive file's bucket appears. // The resulting id has token[7] == "0". const std::string sensitiveId = fetchIdForFile(sensitiveName, false); ASSERT_FALSE(sensitiveId.empty()); checkIdToken(sensitiveId, false); // excludeSensitives=false (matches id): bucket is found. assertQueryByIdWithFlagContains(sensitiveId, false, API_OK, sensitiveName); // excludeSensitives=true (overrides id's "0"): sensitive file is filtered → ENOENT. assertQueryByIdWithFlagContains(sensitiveId, true, API_ENOENT, sensitiveName); // --- Part 2: non-sensitive bucket --- // Upload a non-sensitive file to a different subfolder. auto normalFolder = createFolderNode("recent_action_normal_folder"); ASSERT_TRUE(normalFolder); const std::string normalName = "recent_action_override_normal.txt"; { sdk_test::LocalTempFile f(normalName, "normal content"); ASSERT_TRUE(sdk_test::uploadFile(megaApi[0].get(), normalName, normalFolder.get())); } // Fetch with excludeSensitives=true; the non-sensitive file appears. // The resulting id has token[7] == "1". const std::string normalId = fetchIdForFile(normalName, true); ASSERT_FALSE(normalId.empty()); checkIdToken(normalId, true); // excludeSensitives=true (matches id): bucket is found. assertQueryByIdWithFlagContains(normalId, true, API_OK, normalName); // excludeSensitives=false (overrides id's "1"): filter relaxed, non-sensitive file still // visible. assertQueryByIdWithFlagContains(normalId, false, API_OK, normalName); CASE_info << "finished"; }sdk-10.11.0/tests/integration/SdkTestNodesSetUp.cpp000066400000000000000000000120671516266226600222010ustar00rootroot00000000000000#include "SdkTestNodesSetUp.h" void SdkTestNodesSetUp::SetUp() { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_NO_FATAL_FAILURE(createRootTestDir()); createNodes(getElements(), rootTestDirNode.get()); } std::vector SdkTestNodesSetUp::getAllNodesNames() const { std::vector result; std::for_each(getElements().begin(), getElements().end(), [&result](const auto& el) { const auto partial = getNodeNames(el); result.insert(result.end(), partial.begin(), partial.end()); }); return result; } std::unique_ptr SdkTestNodesSetUp::getDefaultfilter() const { std::unique_ptr filteringInfo(MegaSearchFilter::createInstance()); filteringInfo->byLocationHandle(rootTestDirNode->getHandle()); return filteringInfo; } std::unique_ptr SdkTestNodesSetUp::getNodeByPath(const std::string& path) const { const auto testPath = convertToTestPath(path); return std::unique_ptr(megaApi[0]->getNodeByPath(testPath.c_str())); } MegaHandle SdkTestNodesSetUp::getNodeHandleByPath(const std::string& path) const { if (const auto megaNode = getNodeByPath(path); megaNode) return megaNode->getHandle(); return UNDEF; } void SdkTestNodesSetUp::createRootTestDir() { const std::unique_ptr rootnode(megaApi[0]->getRootNode()); rootTestDirNode = createRemoteDir(getRootTestDir(), rootnode.get()); ASSERT_NE(rootTestDirNode, nullptr) << "Unable to create root node at " + getRootTestDir(); } void SdkTestNodesSetUp::createNodes(const std::vector& elements, MegaNode* rootnode) { for (const auto& element: elements) { if (keepDifferentCreationTimes()) { std::this_thread::sleep_for(1s); // Make sure creation time is different } std::visit( [this, rootnode](const auto& nodeInfo) { createNode(nodeInfo, rootnode); }, element); } } void SdkTestNodesSetUp::createNode(const sdk_test::FileNodeInfo& fileInfo, MegaNode* rootnode) { bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); sdk_test::LocalTempFile localFile( LocalPath::fromRelativePath(fileInfo.name).asPlatformEncoded(true), fileInfo.size); MegaHandle file1Handle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file1Handle, fileInfo.name.c_str(), rootnode, nullptr /*fileName*/, fileInfo.mtime, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); // important to reset resetOnNodeUpdateCompletionCBs(); std::unique_ptr nodeFile(megaApi[0]->getNodeByHandle(file1Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot get the node for the updated file (error: " << mApi[0].lastError << ")"; setNodeAdditionalAttributes(fileInfo, nodeFile); } void SdkTestNodesSetUp::createNode(const sdk_test::DirNodeInfo& dirInfo, MegaNode* rootnode) { auto dirNode = createRemoteDir(dirInfo.name, rootnode); ASSERT_TRUE(dirNode) << "Unable to create directory node with name: " << dirInfo.name; setNodeAdditionalAttributes(dirInfo, dirNode); createNodes(dirInfo.childs, dirNode.get()); } std::unique_ptr SdkTestNodesSetUp::createRemoteDir(const std::string& dirName, MegaNode* rootnode) { bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); auto folderHandle = createFolder(0, dirName.c_str(), rootnode); if (folderHandle == INVALID_HANDLE) { return {}; } waitForResponse(&check); std::unique_ptr dirNode(megaApi[0]->getNodeByHandle(folderHandle)); resetOnNodeUpdateCompletionCBs(); return dirNode; } void SdkTestNodesSetUp::setNodeTag(const std::unique_ptr& node, const std::string& tag) { RequestTracker trackerAddTag(megaApi[0].get()); megaApi[0]->addNodeTag(node.get(), tag.c_str(), &trackerAddTag); ASSERT_EQ(trackerAddTag.waitForResult(), API_OK); } void SdkTestNodesSetUp::setNodeDescription(const std::unique_ptr& node, const std::string& description) { RequestTracker trackerSetDescription(megaApi[0].get()); megaApi[0]->setNodeDescription(node.get(), description.c_str(), &trackerSetDescription); ASSERT_EQ(trackerSetDescription.waitForResult(), API_OK); } sdk-10.11.0/tests/integration/SdkTestNodesSetUp.h000066400000000000000000000137161516266226600216500ustar00rootroot00000000000000/** * @file * @brief This header defines the SdkTestNodesSetUp fixtures to be used as base class for tests. */ #ifndef INCLUDE_INTEGRATION_SDKTESTNODESSETUP_H_ #define INCLUDE_INTEGRATION_SDKTESTNODESSETUP_H_ #include "sdk_test_utils.h" #include "SdkTest_test.h" /** * @class SdkTestNodesSetUp * @brief An abstract class that provides a template fixture/test suite to setup an account with a * certain node tree. * * @note This class is thought to only modify anything during the initialization and destruction, * i.e., only read and const operations are implemented once the object is being used. * * Child classes need to implement: * - getRootTestDir(): Returns a string that will be the name of a directory that will be created * inside which all the nodes will be created. This prevents collisions with other test suites. * - getElements(): Returns a vector of NodeInfo object that will be used to create the nodes. * * Also, optionally: * - keepDifferentCreationTimes() * */ class SdkTestNodesSetUp: public virtual SdkTest { private: std::unique_ptr rootTestDirNode; public: virtual const std::string& getRootTestDir() const = 0; virtual const std::vector& getElements() const = 0; /** * @brief Determines if we should wait 1 second between node creation to keep different creation * times. * * Note: The base implementation returns true by default. Overload this method if you don't need * different creation times. * * @return true if we should wait, false otherwise. */ virtual bool keepDifferentCreationTimes() { return true; } /** * @brief Give an path relative to the root node created for the tests (the one named with * getRootTestDir()), returns the fixed absolute path in the cloud. */ std::string convertToTestPath(const std::string& path) const { return "/" + getRootTestDir() + "/" + path; } void SetUp() override; /** * @brief Get a vector with all the names of the nodes created inside the getRootTestDir() */ std::vector getAllNodesNames() const; /** * @brief Get a filter with the byLocationHandle set up properly to point to the root directory * for the tests suite (getRootTestDir()) */ std::unique_ptr getDefaultfilter() const; /** * @brief get a raw pointer to the root node for this test (the one created in root of the * account with the name given by getRootTestDir()). * * The class retains the ownership of the object. */ MegaNode* getRootTestDirectory() const { return rootTestDirNode.get(); } /** * @brief Given the path relative to the root of the test dir, returns the MegaNode with that * path (if exists, nullptr otherwise) */ std::unique_ptr getNodeByPath(const std::string& path) const; /** * @brief Given the path relative to the root of the test dir, returns the handle of the node * with that path if exists, UNDEF otherwise. */ MegaHandle getNodeHandleByPath(const std::string& path) const; protected: /** * @brief Create the getRootTestDir() and sets store it internally */ void createRootTestDir(); /** * @brief Creates the file tree given by the vector of NodeInfo starting from the rootnode * * @param elements NodeInfo vector to create * @param rootnode A pointer to the root node */ void createNodes(const std::vector& elements, MegaNode* rootnode); /** * @brief Creates a file node as a child of the rootnode using the input info. */ void createNode(const sdk_test::FileNodeInfo& fileInfo, MegaNode* rootnode); /** * @brief Creates a directory node as a child of the rootnode using the input info. */ void createNode(const sdk_test::DirNodeInfo& dirInfo, MegaNode* rootnode); /** * @brief Aux method to create a directory node with the given name inside the given rootnode * * NOTE: You must check that the output value is not a nullptr. If it is, there was a failure in * the creation. */ std::unique_ptr createRemoteDir(const std::string& dirName, MegaNode* rootnode); /** * @brief Sets special info such as fav, label, tags or description for a given node */ template void setNodeAdditionalAttributes(const sdk_test::NodeCommonInfo& nodeInfo, const std::unique_ptr& node) { ASSERT_NE(node, nullptr) << "Trying to set attributes of an invalide node pointer."; // Fav ASSERT_EQ(API_OK, synchronousSetNodeFavourite(0, node.get(), nodeInfo.fav)) << "Error setting fav"; // Label if (nodeInfo.label) { ASSERT_EQ(API_OK, synchronousSetNodeLabel(0, node.get(), static_cast(*nodeInfo.label))) << "Error setting label"; } else { ASSERT_EQ(API_OK, synchronousResetNodeLabel(0, node.get())) << "Error resetting label"; } // Sensitivity if (nodeInfo.sensitive) { ASSERT_EQ(API_OK, synchronousSetNodeSensitive(0, node.get(), true)) << "Error setting sensitive node"; } // Set tags std::for_each(std::begin(nodeInfo.tags), std::end(nodeInfo.tags), [this, &node](auto&& tag) { ASSERT_NO_FATAL_FAILURE(setNodeTag(node, tag)); }); // Description ASSERT_NO_FATAL_FAILURE(setNodeDescription(node, nodeInfo.description)); } void setNodeTag(const std::unique_ptr& node, const std::string& tag); void setNodeDescription(const std::unique_ptr& node, const std::string& description); }; #endif // INCLUDE_INTEGRATION_SDKTESTNODESSETUP_H_ sdk-10.11.0/tests/integration/SdkTestSyncLocalOperations_test.cpp000066400000000000000000000444601516266226600251440ustar00rootroot00000000000000/** * @file * @brief This file is expected to contain tests involving sync root paths (local * and remote), e.g., what happens when the remote root of a sync gets deleted. */ #include #ifdef ENABLE_SYNC #include "mock_listeners.h" #include "SdkTestSyncNodesOperations.h" #include using namespace sdk_test; using namespace testing; namespace { bool thereIsRenamedNode(const MegaNodeList* const nodes, const std::string_view targetName) { if (!nodes) return false; const auto nodeIsRenamed = [targetName](auto* const node) { return node && node->getName() == targetName && node->hasChanged(MegaNode::CHANGE_TYPE_NAME); }; for (auto i = nodes->size(); i--;) { if (nodeIsRenamed(nodes->get(i))) { return true; } } return false; } } // namespace /** * @class SdkTestSyncRootOperations * @brief Test fixture designed to test operations involving sync root local and remote paths. */ class SdkTestSyncLocalOperations: public SdkTestSyncNodesOperations { public: static constexpr std::string_view source{"test.cvj"}; static constexpr std::string_view target{"test.bak"}; const std::vector& getElements() const override { // To ensure "testCommonFile" is identical in both dirs static const std::vector ELEMENTS{ DirNodeInfo(DEFAULT_SYNC_REMOTE_PATH) .addChild(FileNodeInfo(std::string{target}).setSize(1000)) .addChild(FileNodeInfo(std::string{source}).setSize(900))}; return ELEMENTS; } void renameAndCreate() const { using clock = std::chrono::steady_clock; const auto nodeRenamed = [](const MegaNodeList* nodes) -> bool { return thereIsRenamedNode(nodes, target); }; // Track putnodes complete (move) std::promise finishedRename; NiceMock mockNodesListener{megaApi[0].get()}; EXPECT_CALL(mockNodesListener, onNodesUpdate).Times(AnyNumber()); EXPECT_CALL(mockNodesListener, onNodesUpdate(_, Truly(nodeRenamed))) .WillOnce( [&finishedRename] { finishedRename.set_value(clock::now()); }); // Track new test.cvj upload. On first update, wait until move ends const auto ExpectedTransfer = Pointer(Property(&MegaTransfer::getFileName, source)); const auto OkError = Pointer(Property(&MegaError::getErrorCode, API_OK)); std::promise finishedTransfer; MockTransferListener mockTransferListener{megaApi[0].get()}; EXPECT_CALL(mockTransferListener, onTransferStart(_, ExpectedTransfer)).Times(1); EXPECT_CALL(mockTransferListener, onTransferUpdate(_, ExpectedTransfer)).Times(AnyNumber()); EXPECT_CALL(mockTransferListener, onTransferFinish(_, ExpectedTransfer, OkError)) .WillOnce( [&finishedTransfer] { finishedTransfer.set_value(clock::now()); }); megaApi[0]->addListener(&mockNodesListener); megaApi[0]->addListener(&mockTransferListener); std::filesystem::rename(localTmpPath() / source, localTmpPath() / target); sdk_test::createRandomFile(localTmpPath() / source, 900); auto rnFut = finishedRename.get_future(); auto futureRenameStatus = rnFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureRenameStatus, std::future_status::ready) << "Timeout rename"; auto trFut = finishedTransfer.get_future(); auto futureStatus = trFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureStatus, std::future_status::ready) << "Timeout transfer"; megaApi[0]->removeListener(&mockNodesListener); megaApi[0]->removeListener(&mockTransferListener); // Verify expectations on the mocks. If any expectation failed, this returns false. bool nodesOk = ::testing::Mock::VerifyAndClearExpectations(&mockNodesListener); bool transferOk = ::testing::Mock::VerifyAndClearExpectations(&mockTransferListener); ASSERT_TRUE(nodesOk) << "Expectations on nodes listener failed."; ASSERT_TRUE(transferOk) << "Expectations on transfer listener failed."; const auto transferFinishTime = trFut.get(); const auto renameFinishTime = rnFut.get(); ASSERT_GT(transferFinishTime, renameFinishTime) << "Test is invalid, putnodes ended after the transfer finished"; std::this_thread::sleep_for(5s); } void disableSyncDeleteLocalFilesAndWaitForRedownloading() { static constexpr std::string_view logPre{ "disableSyncDeleteLocalFilesAndWaitForRedownloading : "}; LOG_verbose << logPre << "Disabling the sync"; ASSERT_NO_FATAL_FAILURE(disableSync()); LOG_verbose << logPre << "Deleting local files (source and target)"; std::filesystem::remove(localTmpPath() / source); std::filesystem::remove(localTmpPath() / target); std::this_thread::sleep_for(2s); LOG_verbose << logPre << "Setting transfer expectations"; using clock = std::chrono::steady_clock; const auto ExpectedTransfer1 = Pointer(Property(&MegaTransfer::getFileName, source)); const auto ExpectedTransfer2 = Pointer(Property(&MegaTransfer::getFileName, target)); const auto OkError1 = Pointer(Property(&MegaError::getErrorCode, API_OK)); const auto OkError2 = Pointer(Property(&MegaError::getErrorCode, API_OK)); std::promise finishedTransfer1; std::promise finishedTransfer2; MockTransferListener mockTransferListener{megaApi[0].get()}; EXPECT_CALL(mockTransferListener, onTransferStart(_, ExpectedTransfer1)).Times(1); EXPECT_CALL(mockTransferListener, onTransferUpdate(_, ExpectedTransfer1)) .Times(AnyNumber()); EXPECT_CALL(mockTransferListener, onTransferFinish(_, ExpectedTransfer1, OkError1)) .WillOnce( [&finishedTransfer1] { finishedTransfer1.set_value(clock::now()); }); EXPECT_CALL(mockTransferListener, onTransferStart(_, ExpectedTransfer2)).Times(1); EXPECT_CALL(mockTransferListener, onTransferUpdate(_, ExpectedTransfer2)) .Times(AnyNumber()); EXPECT_CALL(mockTransferListener, onTransferFinish(_, ExpectedTransfer2, OkError2)) .WillOnce( [&finishedTransfer2] { finishedTransfer2.set_value(clock::now()); }); megaApi[0]->addListener(&mockTransferListener); LOG_verbose << logPre << "Resuming the sync"; ASSERT_NO_FATAL_FAILURE(resumeSync()); LOG_verbose << logPre << "Ensuring sync is running on " << DEFAULT_SYNC_REMOTE_PATH; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning(DEFAULT_SYNC_REMOTE_PATH)); LOG_verbose << logPre << "Waiting for downloads"; ASSERT_EQ(finishedTransfer1.get_future().wait_for(COMMON_TIMEOUT), std::future_status::ready); ASSERT_EQ(finishedTransfer2.get_future().wait_for(COMMON_TIMEOUT), std::future_status::ready); LOG_verbose << logPre << "Sync-downloads completed!"; LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Waiting for sync completed!"; } void renameAndCreateExtended(bool disableSyncAndCheckHashesAfterRedownload) { using clock = std::chrono::steady_clock; const auto nodeRenamed = [](const MegaNodeList* nodes) -> bool { return thereIsRenamedNode(nodes, target); }; // Track putnodes complete (move) std::promise finishedRename; NiceMock mockNodesListener{megaApi[0].get()}; EXPECT_CALL(mockNodesListener, onNodesUpdate).Times(AnyNumber()); EXPECT_CALL(mockNodesListener, onNodesUpdate(_, Truly(nodeRenamed))) .WillOnce( [&finishedRename] { finishedRename.set_value(clock::now()); }); // Track new test.cvj upload. On first update, wait until move ends const auto ExpectedTransfer = Pointer(Property(&MegaTransfer::getFileName, source)); const auto OkError = Pointer(Property(&MegaError::getErrorCode, API_OK)); std::promise finishedTransfer; std::promise gotFileName; std::promise errorTransfer; MockTransferListener mockTransferListener{megaApi[0].get()}; EXPECT_CALL(mockTransferListener, onTransferStart(_, ExpectedTransfer)).Times(1); EXPECT_CALL(mockTransferListener, onTransferUpdate(_, ExpectedTransfer)).Times(AnyNumber()); EXPECT_CALL(mockTransferListener, onTransferFinish(_, ExpectedTransfer, _)) .WillOnce( [&finishedTransfer, &gotFileName, &errorTransfer](::mega::MegaApi*, ::mega::MegaTransfer* t, ::mega::MegaError* e) { finishedTransfer.set_value(clock::now()); LOG_debug << "[mockTransferFinish::onTransferFinish] t->getFileName = '" << t->getFileName() << "', t->getPath = '" << t->getPath() << "'"; gotFileName.set_value(t->getFileName()); errorTransfer.set_value((error)e->getErrorCode()); }); megaApi[0]->addListener(&mockNodesListener); megaApi[0]->addListener(&mockTransferListener); const auto sourceOriginalHash = hashFileHex(localTmpPath() / source); const auto targetOriginalHash = hashFileHex(localTmpPath() / target); std::filesystem::rename(localTmpPath() / source, localTmpPath() / target); const std::filesystem::path sourcePath = localTmpPath() / source; sdk_test::createRandomFile(sourcePath, 950); const auto sourceNewHash = hashFileHex(localTmpPath() / source); auto rnFut = finishedRename.get_future(); auto futureRnStatus = rnFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureRnStatus, std::future_status::ready) << "Timeout rename"; auto trFut = finishedTransfer.get_future(); auto trFilenameFut = gotFileName.get_future(); auto errorTransferFut = errorTransfer.get_future(); auto futureStatus = trFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureStatus, std::future_status::ready) << "Timeout transfer"; futureStatus = trFilenameFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureStatus, std::future_status::ready) << "Timeout transfer get name"; futureStatus = errorTransferFut.wait_for(COMMON_TIMEOUT); ASSERT_EQ(futureStatus, std::future_status::ready) << "Timeout transfer get error"; megaApi[0]->removeListener(&mockNodesListener); megaApi[0]->removeListener(&mockTransferListener); // Verify expectations on the mocks. If any expectation failed, this returns false. bool nodesOk = ::testing::Mock::VerifyAndClearExpectations(&mockNodesListener); bool transferOk = ::testing::Mock::VerifyAndClearExpectations(&mockTransferListener); ASSERT_TRUE(nodesOk) << "Expectations on nodes listener failed."; ASSERT_TRUE(transferOk) << "Expectations on transfer listener failed."; const auto transferFinishTime = trFut.get(); const auto remoteName = trFilenameFut.get(); const auto transferError = errorTransferFut.get(); const auto renameFinishTime = rnFut.get(); ASSERT_GT(transferFinishTime, renameFinishTime) << "Test is invalid, putnodes ended after the transfer finished"; fs::path uploadedPath = localTmpPath() / remoteName; EXPECT_EQ(uploadedPath, sourcePath); const auto sourceCurrentHash = hashFileHex(localTmpPath() / source); const auto targetCurrentHash = hashFileHex(localTmpPath() / target); LOG_debug << "SourceOriginalHash: " << sourceOriginalHash << " [SourceCurrentHash: " << sourceCurrentHash << "]"; LOG_debug << "TargetOriginalHash: " << targetOriginalHash << " [TargetCurrentHash: " << targetCurrentHash << "]"; LOG_debug << "SourceNewHash: " << sourceNewHash << " [SourceCurrentHash: " << sourceCurrentHash << "]"; EXPECT_EQ(sourceOriginalHash, targetCurrentHash); EXPECT_NE(sourceOriginalHash, targetOriginalHash); EXPECT_EQ(sourceNewHash, sourceCurrentHash); EXPECT_NE(sourceNewHash, sourceOriginalHash); EXPECT_NE(sourceNewHash, targetOriginalHash); ASSERT_EQ(transferError, API_OK); std::this_thread::sleep_for(5s); if (disableSyncAndCheckHashesAfterRedownload) { ASSERT_NO_FATAL_FAILURE(disableSyncDeleteLocalFilesAndWaitForRedownloading()); const auto sourceCurrentHashAfterFreshDownload = hashFileHex(localTmpPath() / source); const auto targetCurrentHashAfterFreshDownload = hashFileHex(localTmpPath() / target); LOG_debug << "Checking hashes of source (" << source << ") and target (" << target << ") after disabling the sync + deleting local files + resuming the sync + " "sync-downloading source and target + calculate fresh hash for each"; LOG_debug << "SourceOriginalHash: " << sourceOriginalHash << " [SourceCurrentHash: " << sourceCurrentHash << "]"; LOG_debug << "TargetOriginalHash: " << targetOriginalHash << " [TargetCurrentHash: " << targetCurrentHash << "]"; LOG_debug << "SourceNewHash: " << sourceNewHash << " [SourceCurrentHash: " << sourceCurrentHash << "]"; EXPECT_EQ(sourceOriginalHash, targetCurrentHashAfterFreshDownload); EXPECT_NE(sourceOriginalHash, targetOriginalHash); EXPECT_EQ(sourceNewHash, sourceCurrentHashAfterFreshDownload); EXPECT_NE(sourceNewHash, sourceOriginalHash); EXPECT_NE(sourceNewHash, targetOriginalHash); } } }; /** * @brief Renames A to B (B already exists, so it's replaced) and creates a new A. After the move * and the transfer finish, repeat the operation. * * The first time that a move operation takes place, the sync debris folder is not created yet, * affecting the sequence of requests sent to the API: * 1. The request to move (rename) the node to-be-displaced along with the request will be sent * to create the daily SyncDebris. * 2. Action packets are received, node to-be-displaced is not yet fully updated as there are now * 2 duplicated nodes in the cloud, the renamed one and the old one that still needs to be sent to * debris. * 3. After receiving the action packets, the request to move to debris the node-to-be-displaced * will be sent. * 4. Immediately after, the move operation completion will be checked: the row.cloudNode still * has the old handle (as it not has yet been moved to debris, that cloudNode is outdated). So the * move operation is reset for evaluation. * 5. The operation to move the node-to-be-displaced to debris will be finished, but the * checkMoves will wait a bit (it considers the file is still changing, as it has stats that it * didn't have before). * 6. When the checkMoves takes place again, all the move operation in the cloud has been * completed, so it doesn't need to start a move operation again. * * The second time a move operation takes place, the sync debris is created already: * 1. The request to move (rename) the node-to-be-displaced will the request to move the * node-to-be-displaced to the daily SyncDebris. * 2. Action packets are received, updating the current cloud nodes accordingly, and the * displaced node with the previous handle does not exist anymore. * 3. Immediately after, the move operation completion will be checked: the row.cloudNode has the * updated handle. * 4. The move operation is completed from the sync engine: it takes all the data from the * sourceSyncNode, including the transfer in flight, and marks the row as synced. * * Expectations are that only one upload transfer (the one to create the new A) is started in each * iteration: * 1. First iteration, there is a move operation which is cancelled. The upload transfer is never * moved to another sync node. * 2. Second iteration, the move operation is completed from the sync engine, and the upload * transfer is moved to the target sync node (B). The fix must prevent this from happening for this * scenario, avoiding a new upload to be started again from the new file A. */ TEST_F(SdkTestSyncLocalOperations, RenameAndCreateNew) { static const auto logPre{getLogPrefix()}; LOG_debug << logPre << "Starting"; for (const auto i: range(2)) { LOG_debug << logPre << "rename n" << (i + 1); ASSERT_NO_FATAL_FAILURE(renameAndCreate()) << "Unexpected behaviour on the rename & create operation n" << i; } LOG_debug << logPre << "Finishing"; } /** * @brief RenameAndCreateNew test in "hard mode". * * 1. Uses random data. * 2. Extended expectations for onTransferFinish, getting file names and delaying the error * checking. * 3. Calculates SHA256 of each file before, after and current (current = in the very moment of * calling it, generally after some transfers or intermediate operations). * 4. Final check at the end: disables the sync, deletes the local source and target files, resumes * the sync so the files are sync-downloaded from the cloud, calculates the SHA-256 of each and * checks that it is the expected one. */ TEST_F(SdkTestSyncLocalOperations, RenameAndCreateNewWithExtendedExpectations) { static const auto logPre{getLogPrefix()}; LOG_debug << logPre << "Starting"; for (const auto i: range(2)) { LOG_debug << logPre << "rename n" << (i + 1); ASSERT_NO_FATAL_FAILURE(renameAndCreateExtended(static_cast(i))) << "Unexpected behaviour on the rename & create operation n" << i; } LOG_debug << logPre << "Finishing"; } #endif sdk-10.11.0/tests/integration/SdkTestSyncNodeAttribute_test.cpp000066400000000000000000000101441516266226600246070ustar00rootroot00000000000000/** * @file SdkTestSyncNodeAttribute.cpp * @brief This file is expected to contain SdkTestSyncNodeAttributes class definition and tests. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mega/utils.h" #include "megautils.h" #include "sdk_test_utils.h" #include "SdkTestSyncNodesOperations.h" #include using namespace sdk_test; using namespace testing; class SdkTestSyncNodeAttributes: public SdkTestSyncNodesOperations { public: const std::vector& getElements() const override { static const std::vector ELEMENTS{DirNodeInfo(DEFAULT_SYNC_REMOTE_PATH) .addChild(FileNodeInfo("test.txt") .setSize(10) .setFav(true) .setDescription("description") .setTags({"tag1", "tag2"}))}; return ELEMENTS; } /** * @brief Waits until the test file got sync to cloud - checking if size is same. * * Asserts false if a timeout is exceeded. */ void waitForFileToSync(const std::string_view fileName) { const auto areLocalAndCloudSynched = [this, fileName]() -> bool { const string filePath = "dir1/" + string(fileName); const auto syncNode = getNodeByPath(filePath); const auto syncNodeSize = static_cast(megaApi[0]->getSize(syncNode.get())); return fs::file_size(getLocalTmpDir() / std::string(fileName)) == syncNodeSize; }; ASSERT_TRUE(waitFor(areLocalAndCloudSynched, COMMON_TIMEOUT, 10s)); } }; /** * @brief SdkTestSyncNodeAttributes.VerifyAttributeAfterSync * * Check file attributes consistency after sync completes */ TEST_F(SdkTestSyncNodeAttributes, VerifyAttributeAfterSync) { static const auto logPre = getLogPrefix(); const auto remoteFilePath = "/SDK_TEST_SYNC_NODE_OPERATIONS_AUX_DIR/dir1/test.txt"; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Check if the contents match expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesOriginal("dir1")); const auto megaNode = megaApi[0]->getNodeByPath(remoteFilePath, nullptr); const auto description = megaNode->getDescription(); const auto lable = megaNode->getLabel(); const auto fav = megaNode->isFavourite(); const auto tags = megaNode->getTags(); LOG_verbose << logPre << "Update the existing file size by appending extra data locally"; const auto localTestFilePath = getLocalTmpDir() / "test.txt"; appendToFile(localTestFilePath, 20); LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Waiting for sync remote and local nodes to have the same size"; ASSERT_NO_FATAL_FAILURE(waitForFileToSync("test.txt")); const auto afterSync = megaApi[0]->getNodeByPath(remoteFilePath, nullptr); const auto asDescription = afterSync->getDescription() == nullptr ? "" : afterSync->getDescription(); const auto asLable = afterSync->getLabel(); const auto asFav = afterSync->isFavourite(); const auto asTags = afterSync->getTags(); ASSERT_STREQ(description, asDescription) << "Description attribute mismatched after sync"; ASSERT_EQ(lable, asLable) << "Lable attribute mismatched after sync"; ASSERT_EQ(fav, asFav) << "Favourite attribute mismatched after sync"; ASSERT_EQ(tags->size(), asTags->size()) << "Node tags are not same after sync"; for (int i = 0; i < tags->size(); i++) { ASSERT_STREQ(tags->get(i), asTags->get(i)) << "Tag attribute mismatched after sync"; } } #endif sdk-10.11.0/tests/integration/SdkTestSyncNodesOperations.cpp000066400000000000000000000321751516266226600241230ustar00rootroot00000000000000/** * @file SdkTestSyncNodesOperations.cpp * @brief This file is expected to contain SdkTestSyncNodesOperations class definition. */ #ifdef ENABLE_SYNC #include "SdkTestSyncNodesOperations.h" #include "integration_test_utils.h" #include "mega/utils.h" #include "megautils.h" #include "sdk_test_utils.h" #include #include using namespace sdk_test; using namespace testing; const std::string SdkTestSyncNodesOperations::DEFAULT_SYNC_REMOTE_PATH{"dir1"}; void SdkTestSyncNodesOperations::SetUp() { SdkTestNodesSetUp::SetUp(); if (createSyncOnSetup()) { ASSERT_NO_FATAL_FAILURE( initiateSync(getLocalTmpDirU8string(), DEFAULT_SYNC_REMOTE_PATH, mBackupId)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); } } void SdkTestSyncNodesOperations::TearDown() { if (mBackupId != UNDEF) { ASSERT_TRUE(removeSync(megaApi[0].get(), mBackupId)); } SdkTestNodesSetUp::TearDown(); } const std::vector& SdkTestSyncNodesOperations::getElements() const { // To ensure "testCommonFile" is identical in both dirs static const auto currentTime = std::chrono::system_clock::now(); static const std::vector ELEMENTS{ DirNodeInfo(DEFAULT_SYNC_REMOTE_PATH) .addChild(FileNodeInfo("testFile").setSize(1)) .addChild(FileNodeInfo("testCommonFile").setMtime(currentTime)) .addChild(FileNodeInfo("testFile1")), DirNodeInfo("dir2") .addChild(FileNodeInfo("testFile").setSize(2)) .addChild(FileNodeInfo("testCommonFile").setMtime(currentTime)) .addChild(FileNodeInfo("testFile2"))}; return ELEMENTS; } const std::string& SdkTestSyncNodesOperations::getRootTestDir() const { static const std::string dirName{"SDK_TEST_SYNC_NODE_OPERATIONS_AUX_DIR"}; return dirName; } const fs::path& SdkTestSyncNodesOperations::localTmpPath() { // Prevent parallel test from the same suite writing to the same dir thread_local const fs::path localTmpDir{"./SDK_TEST_SYNC_NODE_OPERATIONS_AUX_LOCAL_DIR_" + getThisThreadIdStr()}; return localTmpDir; } const fs::path& SdkTestSyncNodesOperations::getLocalTmpDir() const { return mTempLocalDir.getPath(); } std::string SdkTestSyncNodesOperations::getLocalTmpDirU8string() const { return path_u8string(getLocalTmpDir()); } std::unique_ptr SdkTestSyncNodesOperations::getSync() const { return std::unique_ptr(megaApi[0]->getSyncByBackupId(mBackupId)); } fs::path SdkTestSyncNodesOperations::makeProcessTempDir(const std::string& dirName) const { static std::atomic counter{0}; const auto uniqueSuffix = getThisThreadIdStr() + "_" + std::to_string(counter.fetch_add(1)); const auto tempDir = fs::path("tmp") / (dirName + "_" + uniqueSuffix); fs::create_directories(tempDir); return tempDir; } void SdkTestSyncNodesOperations::moveRemoteNode(const std::string& sourcePath, const std::string& destPath) { const auto source = getNodeByPath(sourcePath); const auto dest = getNodeByPath(destPath); ASSERT_EQ(API_OK, doMoveNode(0, nullptr, source.get(), dest.get())); } void SdkTestSyncNodesOperations::renameRemoteNode(const std::string& sourcePath, const std::string& newName) { const auto source = getNodeByPath(sourcePath); ASSERT_EQ(API_OK, doRenameNode(0, source.get(), newName.c_str())); } void SdkTestSyncNodesOperations::removeRemoteNode(const std::string& path) { const auto node = getNodeByPath(path); ASSERT_EQ(API_OK, doDeleteNode(0, node.get())); } void SdkTestSyncNodesOperations::ensureSyncNodeIsRunning(const std::string& path) { const auto syncNode = getNodeByPath(path); ASSERT_TRUE(syncNode); const auto sync = megaApi[0]->getSyncByNode(syncNode.get()); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); } void SdkTestSyncNodesOperations::suspendSync() { ASSERT_TRUE(sdk_test::suspendSync(megaApi[0].get(), mBackupId)) << "Error when trying to suspend the sync"; } void SdkTestSyncNodesOperations::disableSync() { ASSERT_TRUE(sdk_test::disableSync(megaApi[0].get(), mBackupId)) << "Error when trying to disable the sync"; } void SdkTestSyncNodesOperations::resumeSync() { ASSERT_TRUE(sdk_test::resumeSync(megaApi[0].get(), mBackupId)) << "Error when trying to resume the sync"; } void SdkTestSyncNodesOperations::ensureSyncLastKnownMegaFolder(const std::string& path) { std::unique_ptr sync(megaApi[0]->getSyncByBackupId(getBackupId())); ASSERT_TRUE(sync); ASSERT_EQ(sync->getLastKnownMegaFolder(), convertToTestPath(path)); } void SdkTestSyncNodesOperations::initiateSync(const std::string& localPath, const std::string& remotePath, MegaHandle& backupId) { LOG_verbose << "SdkTestSyncNodesOperations : Initiate sync"; backupId = sdk_test::syncFolder(megaApi[0].get(), localPath, getNodeByPath(remotePath)->getHandle()); ASSERT_NE(backupId, UNDEF); } void SdkTestSyncNodesOperations::waitForSyncToMatchCloudAndLocal() { const auto areLocalAndCloudSynched = [this]() -> bool { const auto [childrenCloudNamesAndFingerprints, _] = getCloudFirstChildrenNamesAndFingerprints(megaApi[0].get(), getSync()->getMegaHandle()); const auto childrenLocalNamesAndFingerprints = getLocalFirstChildrenNamesAndFingerprints(megaApi[0].get()); return childrenCloudNamesAndFingerprints && Value(childrenLocalNamesAndFingerprints, UnorderedElementsAreArray(*childrenCloudNamesAndFingerprints)); }; ASSERT_TRUE(waitFor(areLocalAndCloudSynched, COMMON_TIMEOUT, 10s)); } void SdkTestSyncNodesOperations::waitForSyncToMatchCloudAndLocalExhaustive( const bool useFingerprint) { const auto areLocalAndCloudSynchedExhaustive = [this, useFingerprint]() -> bool { return checkSyncRecursively(getSync()->getMegaHandle(), "", useFingerprint); }; ASSERT_TRUE(waitFor(areLocalAndCloudSynchedExhaustive, COMMON_TIMEOUT, 10s)); } bool SdkTestSyncNodesOperations::checkSyncRecursively(MegaHandle parentHandle, const std::string& localPath, const bool useFingerprint) { std::unique_ptr childrenNodeList; const auto subPath = localPath.empty() ? std::nullopt : std::make_optional(localPath); if (!useFingerprint) { auto [childrenCloudNames, nodeList] = getCloudFirstChildren(megaApi[0].get(), parentHandle); if (!childrenCloudNames.has_value() || !nodeList) { return false; } const auto localChildrenNames = getLocalFirstChildrenNames(subPath); if (!Value(localChildrenNames, UnorderedElementsAreArray(childrenCloudNames.value()))) { return false; } childrenNodeList = std::move(nodeList); } else { auto [childrenCloudNamesAndFingerprints, nodeList] = getCloudFirstChildrenNamesAndFingerprints(megaApi[0].get(), parentHandle); if (!childrenCloudNamesAndFingerprints.has_value() || !nodeList) { return false; } const auto localChildrenNamesAndFingerprints = getLocalFirstChildrenNamesAndFingerprints(megaApi[0].get(), subPath); if (!Value(localChildrenNamesAndFingerprints, UnorderedElementsAreArray(childrenCloudNamesAndFingerprints.value()))) { return false; } childrenNodeList = std::move(nodeList); } for (int i = 0; i < childrenNodeList->size(); ++i) { auto childNode = childrenNodeList->get(i); if (!childNode) { return false; } std::string childLocalPath = localPath.empty() ? childNode->getName() : localPath + "/" + childNode->getName(); if (childNode->isFolder() && !checkSyncRecursively(childNode->getHandle(), childLocalPath, useFingerprint)) { return false; } } return true; } void SdkTestSyncNodesOperations::checkCurrentLocalMatchesOriginal( const std::string_view cloudDirName) { const auto& originals = getElements(); const auto it = std::find_if(std::begin(originals), std::end(originals), [&cloudDirName](const auto& node) { return getNodeName(node) == cloudDirName; }); ASSERT_NE(it, std::end(originals)) << cloudDirName << ": directory not found in original elements"; const auto* dirNode = std::get_if(&(*it)); ASSERT_TRUE(dirNode) << "The found original element is not a directory"; using ChildNameSize = std::pair>; // Get info from original cloud std::vector childOriginalInfo; std::transform(std::begin(dirNode->childs), std::end(dirNode->childs), std::back_inserter(childOriginalInfo), [](const auto& child) -> ChildNameSize { return std::visit(overloaded{[](const DirNodeInfo& dir) -> ChildNameSize { return {dir.name, {}}; }, [](const FileNodeInfo& file) -> ChildNameSize { return {file.name, file.size}; }}, child); }); // Get info from current local std::vector childLocalInfo; std::filesystem::directory_iterator children{getLocalTmpDir()}; std::for_each(begin(children), end(children), [&childLocalInfo](const std::filesystem::path& path) { const auto name = path.filename().string(); if (name.front() == '.' || name == DEBRISFOLDER) return; if (std::filesystem::is_directory(path)) childLocalInfo.push_back({name, {}}); else childLocalInfo.push_back( {name, static_cast(std::filesystem::file_size(path))}); }); ASSERT_THAT(childLocalInfo, UnorderedElementsAreArray(childOriginalInfo)); } void SdkTestSyncNodesOperations::thereIsAStall(const std::string_view fileName) const { const auto stalls = sdk_test::getStalls(megaApi[0].get()); ASSERT_EQ(stalls.size(), 1); ASSERT_TRUE(stalls[0]); const auto& stall = *stalls[0]; ASSERT_THAT(stall.path(false, 0), EndsWith(fileName)); ASSERT_THAT( stall.reason(), MegaSyncStall::SyncStallReason::LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose); } void SdkTestSyncNodesOperations::checkCurrentLocalMatchesMirror() const { ASSERT_THAT(getLocalFirstChildrenNames(), UnorderedElementsAre("testFile", "testCommonFile", "testFile1", "testFile2")); ASSERT_TRUE(sdk_test::waitForSyncStallState(megaApi[0].get())); ASSERT_NO_FATAL_FAILURE(thereIsAStall("testFile")); } std::vector SdkTestSyncNodesOperations::getLocalFirstChildrenNames(std::optional subPath) const { fs::path pathObj = subPath.has_value() ? getLocalTmpDir() / subPath.value() : getLocalTmpDir(); return sdk_test::getLocalFirstChildrenNames_if(pathObj, [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } std::vector SdkTestSyncNodesOperations::getLocalFirstChildrenNamesAndFingerprints( MegaApi* megaApi, std::optional subPath) const { fs::path pathObj = subPath.has_value() ? getLocalTmpDir() / subPath.value() : getLocalTmpDir(); return sdk_test::getLocalFirstChildrenNamesAndFingerprints_if(megaApi, pathObj, [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncNodesOperations.h000066400000000000000000000147221516266226600235660ustar00rootroot00000000000000/** * @file SdkTestSyncNodesOperations.h * @brief This file is expected to contain SdkTestSyncNodesOperations declaration. */ #ifdef ENABLE_SYNC #ifndef INCLUDE_INTEGRATION_SDKTESTSYNCNODEOPERATIONS_H_ #define INCLUDE_INTEGRATION_SDKTESTSYNCNODEOPERATIONS_H_ #include "megautils.h" #include "SdkTestNodesSetUp.h" namespace sdk_test { /** * @class SdkTestSyncNodesOperations * @brief Implementation of SdkTestNodesSetUp that can be used for different test suites testing * syncs and node operations. * * @note As a reminder, everything is done inside the remote node named by getRootTestDir() which * means that all the methods involving a remote "path" are relative to that root test dir. */ class SdkTestSyncNodesOperations: public SdkTestNodesSetUp { public: static constexpr auto COMMON_TIMEOUT = 3min; static const std::string DEFAULT_SYNC_REMOTE_PATH; void SetUp() override; void TearDown() override; virtual bool createSyncOnSetup() const { return true; } /** * @brief Build a simple file tree */ const std::vector& getElements() const override; const std::string& getRootTestDir() const override; /** * @brief We don't want different creation times */ bool keepDifferentCreationTimes() override { return false; } /** * @brief Where should we put our sync locally? */ const fs::path& getLocalTmpDir() const; /** * @brief Get a UTF-8 string from getLocalTmpDir(). */ std::string getLocalTmpDirU8string() const; /** * @brief Returns the identifier to get the sync from the megaApi */ handle getBackupId() const { return mBackupId; } /** * @brief Returns the current sync state */ std::unique_ptr getSync() const; /** * @brief Moves the cloud node that is in the relative path "sourcePath" to the relative * "destPath" */ void moveRemoteNode(const std::string& sourcePath, const std::string& destPath); /** * @brief Renames the remote node located at sourcePath with the new given name */ void renameRemoteNode(const std::string& sourcePath, const std::string& newName); /** * @brief Removes the node located at the give relative path */ void removeRemoteNode(const std::string& path); /** * @brief Asserts there is a sync pointing to the remote relative path and that it is in * RUNSTATE_RUNNING */ void ensureSyncNodeIsRunning(const std::string& path); void suspendSync(); void disableSync(); void resumeSync(); /** * @brief Asserts that the sync last known remote folder matches with the one give relative path */ void ensureSyncLastKnownMegaFolder(const std::string& path); void initiateSync(const std::string& localPath, const std::string& remotePath, MegaHandle& backupId); /** * @brief Waits until all direct successors from both remote and local roots of the sync match. * * Name and fingerprint are compared for the match. * * Asserts false if a timeout is exceeded. */ void waitForSyncToMatchCloudAndLocal(); /** * @brief Waits until all levels of the sync tree match between cloud and local. * * This is an exhaustive check that recursively verifies synchronization at every level. * Name and fingerprint are compared for the match by default. * Asserts false if a timeout is exceeded. * * @param useFingerprint If false, the nodes are matched only using the name. */ void waitForSyncToMatchCloudAndLocalExhaustive(const bool useFingerprint = true); void checkCurrentLocalMatchesOriginal(const std::string_view cloudDirName); /** * @brief Asserts that there are 2 stall issues pointing to local paths that end with the given * names and their reason is LocalAndRemotePreviouslyUnsyncedDiffer_userMustChoose. * * Useful to validate mirroring state between dir1 and dir2. */ void thereIsAStall(const std::string_view fileName) const; /** * @brief Asserts that the local sync directory contains all the files matching a mirroring * state (all the files in dir1 merged with those in dir2) */ void checkCurrentLocalMatchesMirror() const; /** * @brief Returns a vector with the names of the first successor files/directories inside the * specified local directory. * * Hidden files (starting with . are excluded) * @param subPath Optional subdirectory path relative to the local sync root. If not provided, * uses the root. */ std::vector getLocalFirstChildrenNames(std::optional subPath = std::nullopt) const; /** * @brief Returns a vector with the names and fingerprints of the first successor * files/directories inside the specified local directory. * * Hidden files (starting with . are excluded) * @param megaApi Api to be used to calculate the local file fingerprint. * @param subPath Optional subdirectory path relative to the local sync root. If not provided, * uses the root. */ std::vector getLocalFirstChildrenNamesAndFingerprints( MegaApi* megaApi, std::optional subPath = std::nullopt) const; /** * @brief Recursively checks if cloud and local sync match at all levels. * * Names and optinally fingerprints are used for the match. * * @param parentHandle The cloud parent node handle to check * @param localPath The local path to check (empty string for root) * @param useFingerprint If true, the nodes are matched using the fingerprint too. * @return true if all levels match, false otherwise */ bool checkSyncRecursively(MegaHandle parentHandle, const std::string& localPath, const bool useFingerprint); protected: /** * @brief Constructs a tmp path using the thread id for thread safety. */ static const fs::path& localTmpPath(); /** * @brief Creates a temporary directory inside the process folder. * The directory name is made unique per thread. */ fs::path makeProcessTempDir(const std::string& dirName) const; LocalTempDir mTempLocalDir{localTmpPath()}; handle mBackupId{UNDEF}; }; } // namespace sdk_test #endif // INCLUDE_INTEGRATION_SDKTESTSYNCNODEOPERATIONS_H_ #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncPrevalidation.cpp000066400000000000000000000206251516266226600236050ustar00rootroot00000000000000/** * @file SdkTestSyncPrevalidation.cpp * @brief This file is expected to contain SdkTestSyncPrevalidation class definition. */ #ifdef ENABLE_SYNC #include "SdkTestSyncPrevalidation.h" #include "integration/mock_listeners.h" using namespace sdk_test; using namespace testing; namespace { using namespace std::chrono_literals; constexpr auto MAX_TIMEOUT = 3min; struct SyncFolderParams { MegaSync::SyncType syncType; std::string localRootPath; std::string backupName; MegaHandle remoteRootHandle; std::string driveRootIfExternal; }; SyncFolderResult syncFolderRequestWithExpectations( MegaApi* const megaApi, SyncFolderExpectations&& expectedValues, std::function&)>&& syncRequest) { if (!megaApi) return {UNDEF, false}; NiceMock rl{megaApi}; handle backupId = UNDEF; auto setBackupId = [&backupId](const MegaRequest& req) { backupId = req.getParentHandle(); }; rl.setErrorExpectations(expectedValues.expectedError, expectedValues.expectedSyncError, expectedValues.expectedReqType, std::move(setBackupId)); syncRequest(rl); const auto finished = rl.waitForFinishOrTimeout(MAX_TIMEOUT); return {backupId, finished}; } SyncFolderResult syncFolderRequest(MegaApi* const megaApi, SyncFolderParams&& params, SyncFolderExpectations&& expectedValues) { auto syncRequest = [&megaApi, params = std::move(params)](auto& requestListener) { megaApi->syncFolder(params.syncType, params.localRootPath, params.backupName, params.remoteRootHandle, params.driveRootIfExternal, &requestListener); }; return syncFolderRequestWithExpectations(megaApi, std::move(expectedValues), std::move(syncRequest)); } SyncFolderResult prevalidateSyncFolderRequest(MegaApi* const megaApi, SyncFolderParams&& params, SyncFolderExpectations&& expectedValues) { auto syncRequest = [&megaApi, params = std::move(params)](auto& requestListener) { megaApi->prevalidateSyncFolder(params.syncType, params.localRootPath, params.backupName, params.remoteRootHandle, params.driveRootIfExternal, &requestListener); }; return syncFolderRequestWithExpectations(megaApi, std::move(expectedValues), std::move(syncRequest)); } SyncFolderResult syncFolderWithExpects(MegaApi* const megaApi, const std::string& localRootPath, const MegaHandle remoteRootHandle, SyncFolderExpectations&& expectedValues) { SyncFolderParams params{MegaSync::TYPE_TWOWAY, localRootPath, "", remoteRootHandle, ""}; return syncFolderRequest(megaApi, std::move(params), std::move(expectedValues)); } SyncFolderResult backupFolderWithExpects(MegaApi* const megaApi, const std::string& localRootPath, const std::string& backupName, SyncFolderExpectations&& expectedValues) { SyncFolderParams params{MegaSync::TYPE_BACKUP, localRootPath, backupName, UNDEF, ""}; return syncFolderRequest(megaApi, std::move(params), std::move(expectedValues)); } SyncFolderResult prevalidateSyncWithExpects(MegaApi* const megaApi, const std::string& localRootPath, const MegaHandle remoteRootHandle, SyncFolderExpectations&& expectedValues) { SyncFolderParams params{MegaSync::TYPE_TWOWAY, localRootPath, "", remoteRootHandle, ""}; return prevalidateSyncFolderRequest(megaApi, std::move(params), std::move(expectedValues)); } SyncFolderResult prevalidateBackupWithExpects(MegaApi* const megaApi, const std::string& localRootPath, const std::string& backupName, SyncFolderExpectations&& expectedValues) { SyncFolderParams params{MegaSync::TYPE_BACKUP, localRootPath, backupName, UNDEF, ""}; return prevalidateSyncFolderRequest(megaApi, std::move(params), std::move(expectedValues)); } } // namespace // SdkTestSyncPrevalidation const std::string SdkTestSyncPrevalidation::DEFAULT_BACKUP_NAME{"myBackup"}; void SdkTestSyncPrevalidation::createSyncOrBackup( SyncFolderExpectations&& expectedValues, std::function&& completion) { const auto expectedOk = (expectedValues.expectedError == API_OK && expectedValues.expectedSyncError == MegaSync::NO_SYNC_ERROR); if (expectedOk) { ASSERT_EQ(mBackupId, UNDEF); } const auto [backupId, result] = completion(std::move(expectedValues)); mBackupId = backupId; ASSERT_TRUE(result); if (expectedOk) { ASSERT_NE(mBackupId, UNDEF); } else { ASSERT_EQ(mBackupId, UNDEF); } } void SdkTestSyncPrevalidation::createSync(SyncFolderExpectations&& expectedValues, const std::string& remotePath) { auto completion = [this, &remotePath](SyncFolderExpectations&& expectedValues) -> SyncFolderResult { return syncFolderWithExpects(megaApi[0].get(), getLocalTmpDirU8string(), getNodeHandleByPath(remotePath), std::move(expectedValues)); }; ASSERT_NO_FATAL_FAILURE(createSyncOrBackup(std::move(expectedValues), std::move(completion))); } void SdkTestSyncPrevalidation::createBackup(SyncFolderExpectations&& expectedValues) { auto completion = [this](SyncFolderExpectations&& expectedValues) -> SyncFolderResult { return backupFolderWithExpects(megaApi[0].get(), getLocalTmpDirU8string(), DEFAULT_BACKUP_NAME, std::move(expectedValues)); }; ASSERT_NO_FATAL_FAILURE(createSyncOrBackup(std::move(expectedValues), std::move(completion))); } void SdkTestSyncPrevalidation::prevalidateSyncOrBackup( SyncFolderExpectations&& expectedValues, std::function&& completion) { const auto [backupId, result] = completion(std::move(expectedValues)); ASSERT_TRUE(result); ASSERT_EQ(backupId, UNDEF); } void SdkTestSyncPrevalidation::prevalidateSync(SyncFolderExpectations&& expectedValues, const std::string& remotePath) { auto completion = [this, &remotePath](SyncFolderExpectations&& expectedValues) -> SyncFolderResult { return prevalidateSyncWithExpects(megaApi[0].get(), getLocalTmpDirU8string(), getNodeHandleByPath(remotePath), std::move(expectedValues)); }; ASSERT_NO_FATAL_FAILURE( prevalidateSyncOrBackup(std::move(expectedValues), std::move(completion))); } void SdkTestSyncPrevalidation::prevalidateBackup(SyncFolderExpectations&& expectedValues) { auto completion = [this](SyncFolderExpectations&& expectedValues) -> SyncFolderResult { return prevalidateBackupWithExpects(megaApi[0].get(), getLocalTmpDirU8string(), DEFAULT_BACKUP_NAME, std::move(expectedValues)); }; ASSERT_NO_FATAL_FAILURE( prevalidateSyncOrBackup(std::move(expectedValues), std::move(completion))); } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncPrevalidation.h000066400000000000000000000045711516266226600232540ustar00rootroot00000000000000/** * @file SdkTestSyncPrevalidation.h * @brief This file is expected to contain SdkTestSyncPrevalidation declaration. */ #ifdef ENABLE_SYNC #ifndef INCLUDE_INTEGRATION_SDKTESTSYNCPREVALIDATION_H_ #define INCLUDE_INTEGRATION_SDKTESTSYNCPREVALIDATION_H_ #include "SdkTestSyncNodesOperations.h" namespace sdk_test { /** * @brief Contains the retrieved backupId and the requestWasFinished flag. * If the request didn't met the expectations, requestWasFinished would be false. */ using SyncFolderResult = std::pair; /** * @brief Struct containing the expectations for the MegaApi::syncFolder() / * MegaApi::prevalidateSyncFolder() requests. */ struct SyncFolderExpectations { int expectedReqType{MegaRequest::TYPE_ADD_SYNC}; error expectedError{API_OK}; int expectedSyncError{MegaSync::NO_SYNC_ERROR}; }; /** * @class SdkTestSyncPrevalidation * @brief Test fixture designed to test MegaApi::syncFolder() and MegaApi::prevalidateSyncFolder(). * * @note The methods to create a sync/backup store the backupId as part of the state. Any * sync/backup created with these methods should be removed if a new one is going to be created * within the same test case. Otherwise there's no need to remove them as that's done as part of the * TearDown. */ class SdkTestSyncPrevalidation: public SdkTestSyncNodesOperations { public: bool createSyncOnSetup() const override { return false; } void createSync(SyncFolderExpectations&& expectedValues = {}, const std::string& remotePath = DEFAULT_SYNC_REMOTE_PATH); void createBackup(SyncFolderExpectations&& expectedValues = {}); void prevalidateSync(SyncFolderExpectations&& expectedValues, const std::string& remotePath = DEFAULT_SYNC_REMOTE_PATH); void prevalidateBackup(SyncFolderExpectations&& expectedValues); private: static const std::string DEFAULT_BACKUP_NAME; void createSyncOrBackup(SyncFolderExpectations&& expectedValues, std::function&& completion); void prevalidateSyncOrBackup( SyncFolderExpectations&& expectedValues, std::function&& completion); }; } // namespace sdk_test #endif // INCLUDE_INTEGRATION_SDKTESTSYNCPREVALIDATION_H_ #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncPrevalidation_test.cpp000066400000000000000000000141431516266226600246420ustar00rootroot00000000000000/** * @file SdkTestSyncPrevalidation_test.cpp * @brief This file is expected to contain the SdkTestSyncPrevalidation test cases. * * Test cases testing failures cover one possible failure for different code flows; i.e., some * failures can happen during precondition checks, others are specific for sync type (like an error * returned from MegaClient::prepareBackup()), and others are part of the * MegaClient::checkSyncConfig(). */ #ifdef ENABLE_SYNC #include "SdkTestSyncPrevalidation.h" using namespace sdk_test; /** * @test SdkTestSyncPrevalidation.PrevalidateSyncOK * * 1. Prevalidates a sync that should work correctly. * 2. Creates the sync afterwards for double-checking: it should work as well. */ TEST_F(SdkTestSyncPrevalidation, PrevalidateSyncOK) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Prevalidating sync"; SyncFolderExpectations prevalidateExpectations{MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, API_OK, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(prevalidateSync(std::move(prevalidateExpectations))); LOG_verbose << logPre << "Sync prevalidated OK. Creating sync: it should work as well"; ASSERT_NO_FATAL_FAILURE(createSync()); } /** * @test SdkTestSyncPrevalidation.PrevalidateSyncFailureAlreadyExists * * 1. Creates a sync. * 2. Prevalidates the sync: it should fail as it already exists. * 3. Tries to create the sync afterwards for double checking: it should fail as well. */ TEST_F(SdkTestSyncPrevalidation, PrevalidateSyncFailureAlreadyExists) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Creating sync"; ASSERT_NO_FATAL_FAILURE(createSync()); LOG_verbose << logPre << "Prevalidating sync over an existing sync: should fail"; SyncFolderExpectations prevalidateExpectations{MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, API_EEXIST, MegaSync::ACTIVE_SYNC_SAME_PATH}; ASSERT_NO_FATAL_FAILURE(prevalidateSync(std::move(prevalidateExpectations))); LOG_verbose << logPre << "Trying to create a sync over an existing sync: should have same result as with " "prevalidation"; SyncFolderExpectations expectations{MegaRequest::TYPE_ADD_SYNC, API_EEXIST, MegaSync::ACTIVE_SYNC_SAME_PATH}; ASSERT_NO_FATAL_FAILURE(createSync(std::move(expectations))); } /** * @test SdkTestSyncPrevalidation.PrevalidateSyncFailureNoRemotePath * * 1. Prevalidates a sync with a remote path that doesn't exist. * 2. Tries to create the sync afterwards for double checking: it should fail as well. */ TEST_F(SdkTestSyncPrevalidation, PrevalidateSyncFailureNoRemotePath) { static const auto logPre = getLogPrefix(); const std::string fakeRemotePath{"fakePath"}; LOG_verbose << logPre << "Prevalidating sync over with a non existing remote path"; SyncFolderExpectations prevalidateExpectations{MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, API_EARGS, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(prevalidateSync(std::move(prevalidateExpectations), fakeRemotePath)); LOG_verbose << logPre << "Trying to create a sync with a non existing remote path: should have same " "result as with prevalidation"; SyncFolderExpectations expectations{MegaRequest::TYPE_ADD_SYNC, API_EARGS, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(createSync(std::move(expectations), fakeRemotePath)); } /** * @test SdkTestSyncPrevalidation.PrevalidateBackupOK * * 1. Prevalidates a backup that should work correctly. * 2. Creates the backup afterwards for double-checking: it should work as well. */ TEST_F(SdkTestSyncPrevalidation, PrevalidateBackupOK) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Prevalidating backup"; SyncFolderExpectations prevalidateExpectations{MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, API_OK, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(prevalidateBackup(std::move(prevalidateExpectations))); LOG_verbose << logPre << "Backup prevalidated OK. Creating sync: it should work as well"; ASSERT_NO_FATAL_FAILURE(createBackup()); } /** * @test SdkTestSyncPrevalidation.PrevalidateSyncFailureAlreadyExists * * 1. Creates a backup. * 2. Prevalidates the backup: it should fail as it already exists. * 3. Tries to create the backup afterwards for double checking: it should fail as well. * * @note Unlike PrevalidateSyncFailureAlreadyExists, whose checks are done at * MegaClient::checkSyncConfig(), this logic is checked within MegaClient::preparebackup() (called * before checkSyncConfig()). */ TEST_F(SdkTestSyncPrevalidation, PrevalidateBackupFailureAlreadyExists) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Creating backup"; ASSERT_NO_FATAL_FAILURE(createBackup()); LOG_verbose << logPre << "Prevalidating backup over an existing backup: should fail"; SyncFolderExpectations prevalidateExpectations{MegaRequest::TYPE_ADD_SYNC_PREVALIDATION, API_EACCESS, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(prevalidateBackup(std::move(prevalidateExpectations))); LOG_verbose << logPre << "Trying to create a backup over an existing backup: should have same result as with " "prevalidation"; SyncFolderExpectations expectations{MegaRequest::TYPE_ADD_SYNC, API_EACCESS, MegaSync::NO_SYNC_ERROR}; ASSERT_NO_FATAL_FAILURE(createBackup(std::move(expectations))); } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncRootOperations_test.cpp000066400000000000000000000456531516266226600250420ustar00rootroot00000000000000/** * @file * @brief This file is expected to contain tests involving sync root paths (local * and remote), e.g., what happens when the remote root of a sync gets deleted. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mega/utils.h" #include "megautils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTestSyncNodesOperations.h" #include using namespace sdk_test; using namespace testing; /** * @class SdkTestSyncRootOperations * @brief Test fixture designed to test operations involving sync root local and remote paths. */ class SdkTestSyncRootOperations: public SdkTestSyncNodesOperations { public: static constexpr auto MAX_TIMEOUT = COMMON_TIMEOUT; // Timeout for operations in this tests suite enum class MoveOp { MOVE, RENAME }; void moveRemoteRootAndWaitForSyncUpdate(const std::string& sourcePath, const std::string& destPath, const MoveOp rename = MoveOp::MOVE) { // Expectations std::string expectedNewRootPath = destPath; if (rename == MoveOp::MOVE && destPath.back() == '/') expectedNewRootPath += sourcePath; const auto hasGoodName = Pointee(Property(&MegaSync::getLastKnownMegaFolder, StrEq(convertToTestPath(expectedNewRootPath)))); const auto hasGoodRunState = Pointee(Property(&MegaSync::getRunState, MegaSync::RUNSTATE_RUNNING)); std::promise renameFinished; NiceMock ml; EXPECT_CALL(ml, onSyncRemoteRootChanged(_, AllOf(hasGoodName, hasGoodRunState))) .WillOnce( [&renameFinished] { renameFinished.set_value(); }); // Code execution megaApi[0]->addListener(&ml); MrProper clean{[&api = megaApi[0], &ml]() { api->removeListener(&ml); }}; switch (rename) { case MoveOp::MOVE: ASSERT_NO_FATAL_FAILURE(moveRemoteNode(sourcePath, destPath)); break; case MoveOp::RENAME: ASSERT_NO_FATAL_FAILURE(renameRemoteNode(sourcePath, destPath)); break; } // Wait for finish ASSERT_EQ(renameFinished.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready) << "The sync root movement didn't take place within 3 mins"; } void changeRemoteRootNodeAndWaitForSyncUpdate(const std::string& destRemotePath) { const auto newRootHandle = getNodeHandleByPath(destRemotePath); ASSERT_NE(newRootHandle, UNDEF); // Expectations on the request listener NiceMock mockReqListener{megaApi[0].get()}; mockReqListener.setErrorExpectations(API_OK, _, MegaRequest::TYPE_CHANGE_SYNC_ROOT); // Code execution megaApi[0]->changeSyncRemoteRoot(getBackupId(), newRootHandle, &mockReqListener); // Wait for everything to finish mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT); } }; TEST_F(SdkTestSyncRootOperations, MoveRemoteRoot) { static const std::string logPre{"SdkTestSyncRootOperations.MoveRemoteRoot : "}; // The state of the sync shouldn't change so we will be checking that all across the test ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); ASSERT_NO_FATAL_FAILURE(ensureSyncLastKnownMegaFolder("dir1")); LOG_verbose << logPre << "Rename remote root from dir1 to dir1moved"; ASSERT_NO_FATAL_FAILURE( moveRemoteRootAndWaitForSyncUpdate("dir1", "dir1moved", MoveOp::RENAME)); // Now the sync should be running on the moved dir ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1moved")); ASSERT_NO_FATAL_FAILURE(ensureSyncLastKnownMegaFolder("dir1moved")); LOG_verbose << logPre << "Move the remote root (put dir1moved inside dir2)"; ASSERT_NO_FATAL_FAILURE(moveRemoteRootAndWaitForSyncUpdate("dir1moved", "dir2/", MoveOp::MOVE)); // Now the sync should be running on the moved dir ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2/dir1moved")); ASSERT_NO_FATAL_FAILURE(ensureSyncLastKnownMegaFolder("dir2/dir1moved")); } TEST_F(SdkTestSyncRootOperations, RemoveRemoteRoot) { static const std::string logPre{"SdkTestSyncRootOperations.RemoveRemoteRoot : "}; // We expect the sync to stop if the remote root node gets deleted ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Remove remote root (dir1)"; ASSERT_NO_FATAL_FAILURE(removeRemoteNode("dir1")); const auto sync = waitForSyncState(megaApi[0].get(), getBackupId(), MegaSync::RUNSTATE_SUSPENDED, MegaSync::REMOTE_NODE_NOT_FOUND); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(sync->getError(), MegaSync::REMOTE_NODE_NOT_FOUND); } TEST_F(SdkTestSyncRootOperations, MoveSyncToAnotherSync) { static const std::string logPre{"SdkTestSyncRootOperations.MoveSyncToAnotherSync : "}; // Moving a sync to another sync should disable it LOG_verbose << logPre << "Create a new sync in dir2"; std::string tempLocalDir2Name = path_u8string(getLocalTmpDir()) + "2"; LocalTempDir tempLocalDir2{tempLocalDir2Name}; MegaHandle dir2SyncId; ASSERT_NO_FATAL_FAILURE(initiateSync(tempLocalDir2Name, "dir2/", dir2SyncId)); // Make sure it is removed after exiting the scope const auto autoRemove = MrProper( [&dir2SyncId, &api = megaApi[0]]() { ASSERT_TRUE(removeSync(api.get(), dir2SyncId)); }); ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2")); LOG_verbose << logPre << "Moving dir1 inside dir2"; ASSERT_NO_FATAL_FAILURE(moveRemoteNode("dir1", "dir2/")); LOG_verbose << logPre << "Waiting for dir1 to be disable as it is inside another sync"; const auto sync = waitForSyncState(megaApi[0].get(), getBackupId(), MegaSync::RUNSTATE_SUSPENDED, MegaSync::ACTIVE_SYNC_ABOVE_PATH); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(sync->getError(), MegaSync::ACTIVE_SYNC_ABOVE_PATH); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootErrors * * Tests multiple error paths when calling changeSyncRemoteRoot */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootErrors) { static const std::string logPre{"SdkTestSyncRootOperations.ChangeSyncRemoteRootErrors : "}; { LOG_verbose << logPre << "Giving undef backupId and undef remote handle"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, _); megaApi[0]->changeSyncRemoteRoot(UNDEF, UNDEF, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } const auto newRootHandle = getNodeHandleByPath("dir2"); ASSERT_NE(newRootHandle, UNDEF); { LOG_verbose << logPre << "Giving undef backupId and good remote handle"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, _); megaApi[0]->changeSyncRemoteRoot(UNDEF, newRootHandle, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving non existent backupId and good remote handle"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, UNKNOWN_ERROR); megaApi[0]->changeSyncRemoteRoot(newRootHandle, newRootHandle, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving good backupId and a handle to a file node"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EACCESS, INVALID_REMOTE_TYPE); const auto fileHandle = getNodeHandleByPath("dir1/testFile"); ASSERT_NE(fileHandle, UNDEF); megaApi[0]->changeSyncRemoteRoot(getBackupId(), fileHandle, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_verbose << logPre << "Giving good backupId and handle to already synced root"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EEXIST, ACTIVE_SYNC_SAME_PATH); const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); megaApi[0]->changeSyncRemoteRoot(getBackupId(), dir1Handle, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } // Just make sure that after all the attempts the sync is still running fine ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootErrorOnBackup * * Checks that changing the remote root of a backup returns an error (not allowed operation). */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootErrorOnBackup) { static const std::string logPre{ "SdkTestSyncRootOperations.ChangeSyncRemoteRootErrorOnBackup : "}; LOG_verbose << logPre << "Create a backup"; LocalTempDir tmpDir{"auxChangeSyncRemoteRootErrorOnBackupDir"}; const auto backupId = backupFolder(megaApi[0].get(), path_u8string(tmpDir.getPath())); ASSERT_NE(backupId, UNDEF) << "Error initiating the backup"; const MrProper defer{[backupId, &api = megaApi[0]]() { removeSync(api.get(), backupId); }}; LOG_verbose << logPre << "Wait for the backup to enter in RUNNING state"; const auto backup = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(backup) << "Unable to get the backup in RUNNING state"; LOG_verbose << logPre << "Trying to change the remote root of a backup sync"; NiceMock mockListener{megaApi[0].get()}; mockListener.setErrorExpectations(API_EARGS, UNKNOWN_ERROR); const auto dir1Handle = getNodeHandleByPath("dir2"); ASSERT_TRUE(dir1Handle); megaApi[0]->changeSyncRemoteRoot(backupId, dir1Handle, &mockListener); EXPECT_TRUE(mockListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootOK * * Changes the remote root node of the running sync and validates the final state (which is expected * to mimic the state of the new root) */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootOK) { static const std::string logPre{"SdkTestSyncRootOperations.ChangeSyncRemoteRootOK : "}; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Changing sync remote root to point dir2"; ASSERT_NO_FATAL_FAILURE(changeRemoteRootNodeAndWaitForSyncUpdate("dir2")); LOG_verbose << logPre << "Ensuring sync is running on dir2"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2")); LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Check if the contents match expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesOriginal("dir2")); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenSyncPausedOK * * Same as SdkTestSyncRootOperations.ChangeSyncRemoteRootOK but the change is applied on a paused * sync. Once the change is done, the sync gets resumed and the final state is validated. */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootWhenSyncPausedOK) { static const std::string logPre{ "SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenSyncPausedOK : "}; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Suspending the sync"; ASSERT_NO_FATAL_FAILURE(suspendSync()); LOG_verbose << logPre << "Changing sync remote root to point dir2"; ASSERT_NO_FATAL_FAILURE(changeRemoteRootNodeAndWaitForSyncUpdate("dir2")); LOG_verbose << logPre << "Resuming the sync"; ASSERT_NO_FATAL_FAILURE(resumeSync()); LOG_verbose << logPre << "Ensuring sync is running on dir2"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2")); LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Checking the final state"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesOriginal("dir2")); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenSyncDisableOK * * Changes the remote root node of a sync that has been disabled. Then it is resumed and the final * state is validated. * * @note In this case, as the local nodes database is removed after disabling, a mirroring is * expected after resuming. */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootWhenSyncDisableOK) { static const std::string logPre{ "SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenSyncDisableOK : "}; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Disabling the sync"; ASSERT_NO_FATAL_FAILURE(disableSync()); LOG_verbose << logPre << "Changing sync remote root to point dir2"; ASSERT_NO_FATAL_FAILURE(changeRemoteRootNodeAndWaitForSyncUpdate("dir2")); LOG_verbose << logPre << "Resuming the sync"; ASSERT_NO_FATAL_FAILURE(resumeSync()); LOG_verbose << logPre << "Ensuring sync is running on dir2"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2")); LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; // Check without fingerprints. There is an expected stall and fingerprints will not match // ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive(false)); LOG_verbose << logPre << "Checking the final state"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesMirror()); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootPersistsAfterDisable * * Changes the remote root node of the running sync, suspends it, resumes it and validates the final * state (which is expected to mimic the state of the new root) */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootPersistsAfterDisabled) { static const std::string logPre{"SdkTestSyncRootOperations.ChangeSyncRemoteRootOK : "}; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Changing sync remote root to point dir2"; ASSERT_NO_FATAL_FAILURE(changeRemoteRootNodeAndWaitForSyncUpdate("dir2")); LOG_verbose << logPre << "Suspending the sync"; ASSERT_NO_FATAL_FAILURE(suspendSync()); LOG_verbose << logPre << "Resuming the sync"; ASSERT_NO_FATAL_FAILURE(resumeSync()); LOG_verbose << logPre << "Ensuring sync is running on dir2"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir2")); LOG_verbose << logPre << "Waiting for sync remote and local roots to have the same content"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); LOG_verbose << logPre << "Check if the contents match expectations"; ASSERT_NO_FATAL_FAILURE(checkCurrentLocalMatchesOriginal("dir2")); } /** * @brief SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenTransfersInProgress * * Similar to ChangeSyncRemoteRootOK but we must detect a transfer being cancelled and the file that * was being transferred will be removed as it is not in the new cloud root. * * 1. We create a file locally * 2. Wait until the transfer starts * 3. Call the changeSyncRemoteRoot method * 4. Expect the transfer to terminate * 5. Validate final state with the new root */ TEST_F(SdkTestSyncRootOperations, ChangeSyncRemoteRootWhenTransfersInProgress) { static const std::string logPre{ "SdkTestSyncRootOperations.ChangeSyncRemoteRootWhenTransfersInProgress : "}; LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Setting up the mock listener"; const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); const std::string_view newFileName{"test_file_new.txt"}; const auto isMyFile = Pointee(Property(&MegaTransfer::getPath, EndsWith(newFileName))); const auto isUpload = Pointee(Property(&MegaTransfer::getType, MegaTransfer::TYPE_UPLOAD)); const auto isBelowDir1 = Pointee(Property(&MegaTransfer::getParentHandle, dir1Handle)); const auto isExpectedError = Pointee(Property(&MegaError::getErrorCode, API_EINCOMPLETE)); NiceMock mockListener{megaApi[0].get()}; std::promise fileStartedUpload; EXPECT_CALL(mockListener, onTransferStart).Times(AnyNumber()); EXPECT_CALL(mockListener, onTransferStart(_, AllOf(isMyFile, isUpload, isBelowDir1))) .WillOnce( [&fileStartedUpload] { fileStartedUpload.set_value(); }); std::promise transferTerminated; EXPECT_CALL(mockListener, onTransferFinish).Times(AnyNumber()); EXPECT_CALL(mockListener, onTransferFinish(_, AllOf(isMyFile, isUpload, isBelowDir1), isExpectedError)) .WillOnce( [&transferTerminated] { transferTerminated.set_value(); }); // Register the listener megaApi[0]->addListener(&mockListener); LOG_verbose << logPre << "Create the new file locally"; const auto newFilePath = getLocalTmpDir() / newFileName; LocalTempFile tempFile{newFilePath, 1000}; LOG_verbose << logPre << "Waiting until transfer starts"; ASSERT_EQ(fileStartedUpload.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready) << "The upload didn't start within 3 mins"; LOG_verbose << logPre << "Changing sync remote root to point dir2"; ASSERT_NO_FATAL_FAILURE(changeRemoteRootNodeAndWaitForSyncUpdate("dir2")); LOG_verbose << logPre << "Waiting transfer to be terminated with error"; ASSERT_EQ(transferTerminated.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready) << "The upload didn't start within 3 mins"; } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestSyncUploadThrottling_test.cpp000066400000000000000000001357431516266226600253560ustar00rootroot00000000000000/** * @file SdkTestSyncUploadThrottling_test.cpp * @brief This file is expected to contain tests involving syncs upload throttling. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mega/syncinternals/syncuploadthrottlingmanager.h" #include "mega/utils.h" #include "megautils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTestSyncNodesOperations.h" #include using namespace sdk_test; using namespace testing; /** * @brief Mock class for IUploadThrottlingManager. * * The purpose is to trigger expectations on different calls of IUploadThrottlingManager and then * forward calls to real implementations of the interface, such as the UploadThrottlingManager used * in the Syncs class. */ class MockUploadThrottlingManager: public IUploadThrottlingManager { public: MOCK_METHOD(void, addToDelayedUploads, (DelayedSyncUpload && delayedUpload), (override)); MOCK_METHOD(void, processDelayedUploads, (std::function && completion), (override)); MOCK_METHOD(bool, setThrottleUpdateRate, (const std::chrono::seconds interval), (override)); MOCK_METHOD(bool, setMaxUploadsBeforeThrottle, (const unsigned maxUploadsBeforeThrottle), (override)); MOCK_METHOD(bool, anyDelayedUploads, (), (const, override)); MOCK_METHOD(std::chrono::seconds, uploadCounterInactivityExpirationTime, (), (const, override)); MOCK_METHOD(std::chrono::seconds, throttleUpdateRate, (), (const, override)); MOCK_METHOD(unsigned, maxUploadsBeforeThrottle, (), (const, override)); MOCK_METHOD(ThrottleValueLimits, throttleValueLimits, (), (const, override)); MOCK_METHOD(std::chrono::seconds, timeSinceLastProcessedUpload, (), (const, override)); }; namespace { /** * @brief Forwards the necessary MockUploadThrottlingManager methods to use the * UploadThrottlingManager methods. */ void forwardThrottlingMethods( const std::shared_ptr& mockUploadThrottlingManager, const std::shared_ptr& uploadThrottlingManager) { ON_CALL(*mockUploadThrottlingManager, addToDelayedUploads(_)) .WillByDefault( [uploadThrottlingManager](DelayedSyncUpload&& delayedUpload) { uploadThrottlingManager->addToDelayedUploads(std::move(delayedUpload)); }); ON_CALL(*mockUploadThrottlingManager, processDelayedUploads(_)) .WillByDefault( [uploadThrottlingManager](std::function&& completion) { uploadThrottlingManager->processDelayedUploads(std::move(completion)); }); ON_CALL(*mockUploadThrottlingManager, anyDelayedUploads()) .WillByDefault( [uploadThrottlingManager]() { return uploadThrottlingManager->anyDelayedUploads(); }); ON_CALL(*mockUploadThrottlingManager, uploadCounterInactivityExpirationTime()) .WillByDefault( [uploadThrottlingManager]() { return uploadThrottlingManager->uploadCounterInactivityExpirationTime(); }); ON_CALL(*mockUploadThrottlingManager, throttleUpdateRate()) .WillByDefault( [uploadThrottlingManager]() { return uploadThrottlingManager->throttleUpdateRate(); }); ON_CALL(*mockUploadThrottlingManager, maxUploadsBeforeThrottle()) .WillByDefault( [uploadThrottlingManager]() { return uploadThrottlingManager->maxUploadsBeforeThrottle(); }); } /** * @brief Helper struct to be used when triggering sync-upload actions and waiting on transfer * request events. */ struct UploadWaitConfig { static constexpr auto TOLERANCE_SECONDS_FOR_STARTING_UPLOADS{ 30s}; // Time enough for the sync loop to be called, process queueClient() and start the // upload. static constexpr auto DEFAULT_MIN_WAIT_FOR_TRANSFER_START{0s}; static constexpr auto DEFAULT_MAX_WAIT_FOR_TRANSFER_FINISH{150s}; // The minimum expected time to reach onTransferStart(). Zero for no minimum std::chrono::seconds minWaitForTransferStart{DEFAULT_MIN_WAIT_FOR_TRANSFER_START}; // The maximum extra time (added to minWaitForTransferStart) expected to reach onTransferStart() std::chrono::seconds maxWaitForTransferStartFromMinWait{TOLERANCE_SECONDS_FOR_STARTING_UPLOADS}; // The maximum expected time to complete the transfer after it has started std::chrono::seconds waitForTransferFinish{DEFAULT_MAX_WAIT_FOR_TRANSFER_FINISH}; }; /** * @brief Sets up expectations on transfer requests through the MockTransferListener. * * @param uploadStarted The promised to be resolved upon onTransferStart() * @param uploadFinished The promised to be resolved upon onTransferFinish() */ void setupMockListenerExpectations(MockTransferListener& mockListener, const std::string_view fileName, const MegaHandle parentNodeHandle, std::promise& uploadStarted, std::promise& uploadFinished) { const auto isMyFile = Pointee(Property(&MegaTransfer::getPath, EndsWith(fileName))); const auto isUpload = Pointee(Property(&MegaTransfer::getType, MegaTransfer::TYPE_UPLOAD)); const auto isBelowDir = Pointee(Property(&MegaTransfer::getParentHandle, parentNodeHandle)); const auto isOkError = Pointee(Property(&MegaError::getErrorCode, API_OK)); EXPECT_CALL(mockListener, onTransferStart(_, AllOf(isMyFile, isUpload, isBelowDir))) .WillOnce( [&uploadStarted] { uploadStarted.set_value(); }); EXPECT_CALL(mockListener, onTransferFinish(_, AllOf(isMyFile, isUpload, isBelowDir), isOkError)) .WillOnce( [&uploadFinished] { uploadFinished.set_value(); }); } /** * @brief Helper method to edit a file and wait for it to be uploaded. * * @param uploadStarted Promise that should be resolved when the transfer request starts * (onTransferStart). * @param uploadFinished Promise that should be resolved when the transfer request finishes * (onTransferFinish). * @param config The configurable time wait values. * * @see UploadWaitConfig. */ void editFileAndWaitForUpload(std::promise& uploadStarted, std::promise& uploadFinished, std::function&& fileAction, const UploadWaitConfig& config = {}) { // 1) Call the completion fileAction function to perform now edits or changes in the file. LOG_verbose << "[editFileAndWaitForUpload] Set timeBeforeFileAction to now [minWaitForTransferStart = " << config.minWaitForTransferStart.count() << " secs, maxWaitForTransferStartFromMinWait = " << config.maxWaitForTransferStartFromMinWait.count() << " secs]"; const auto timeBeforeFileAction = std::chrono::steady_clock::now(); fileAction(); // 2) Wait for upload events. const auto waitForTransferStart = config.minWaitForTransferStart + config.maxWaitForTransferStartFromMinWait; ASSERT_EQ(uploadStarted.get_future().wait_for(waitForTransferStart), std::future_status::ready); ASSERT_FALSE( config.minWaitForTransferStart.count() && (std::chrono::steady_clock::now() - timeBeforeFileAction < config.minWaitForTransferStart)) << "The upload started before the minimum time expected after editing the file. " "Expected min: " << config.minWaitForTransferStart.count() << " secs. Started at: " << std::chrono::duration_cast(std::chrono::steady_clock::now() - timeBeforeFileAction) .count() << " secs."; ASSERT_EQ(uploadFinished.get_future().wait_for(config.waitForTransferFinish), std::future_status::ready); } /** * @brief Helper method to call editFileAndWaitForUpload() with a scoped MockTransferListener with * expectations. * * @param MegaHandle The handle of the parent directory to upload the file to. * @see editFileAndWaitForUpload() for the other params. */ void editFileAndWaitForUploadScoped(MegaApi* const api, const std::string_view fileName, const MegaHandle parentNodeHandle, std::function&& fileAction, const UploadWaitConfig& config = {}) { NiceMock mockListener{api}; std::promise uploadStarted; std::promise uploadFinished; setupMockListenerExpectations(mockListener, fileName, parentNodeHandle, uploadStarted, uploadFinished); api->addListener(&mockListener); editFileAndWaitForUpload(uploadStarted, uploadFinished, std::move(fileAction), config); } } // namespace /** * @class SdkTestSyncUploadThrottling * @brief Test fixture designed to test operations involving sync upload throttling. */ class SdkTestSyncUploadThrottling: public SdkTestSyncNodesOperations { private: static constexpr std::chrono::seconds DEFAULT_THROTTLE_UPDATE_RATE{60}; static constexpr unsigned DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE{2}; public: static constexpr auto MAX_TIMEOUT{COMMON_TIMEOUT}; // Timeout for operations in this tests suite /** * @brief Creates a real and mocked throttling manager and uses the mocked one for the sync * engine. * * 1. Creates the real throttling manager. * 2. Creates the mocked throttling manager. * 3. Calls createAndSetThrottlingManagers() * 4. Returns the real and mocked upload throttling manager for operations in tests. */ std::pair, std::shared_ptr>> createAndSetThrottlingManagers() const { const auto uploadThrottlingManager = std::make_shared(); const auto mockUploadThrottlingManager = std::make_shared>(); EXPECT_NO_FATAL_FAILURE( setThrottlingManagers(uploadThrottlingManager, mockUploadThrottlingManager)); if (HasFatalFailure()) return {}; return {uploadThrottlingManager, mockUploadThrottlingManager}; } /** * @brief Prepares the real and mocked throttling manager and use the mocked one for the sync * engine. * * 1. Gets the throttle value limits from the real throttling manager. * 2. Use the lower limits for the configurable values. * 3. Forwards all necessary mocked methods for tests to use the methods from the real * throttling manager. * 4. Sets the mocked manager to be used on Syncs. */ void setThrottlingManagers( const std::shared_ptr& uploadThrottlingManager, const std::shared_ptr& mockUploadThrottlingManager) const { ASSERT_TRUE(uploadThrottlingManager->setThrottleUpdateRate(DEFAULT_THROTTLE_UPDATE_RATE)); ASSERT_TRUE(uploadThrottlingManager->setMaxUploadsBeforeThrottle( DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE)); LOG_debug << "[SdkTestSyncUploadThrottling] throttleUpdateRate: " << DEFAULT_THROTTLE_UPDATE_RATE.count() << " secs, maxUploadsBeforeThrottle: " << DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE; forwardThrottlingMethods(mockUploadThrottlingManager, uploadThrottlingManager); ASSERT_NO_FATAL_FAILURE( setThrottlingManager(megaApi[0]->getClient(), mockUploadThrottlingManager)); } /** * @brief Sets a IUploadThrottlingManager to be used by the Syncs object. */ void setThrottlingManager(MegaClient* const client, std::shared_ptr throttlingManager) const { std::promise setThrottlingManagerPromise; client->setSyncUploadThrottlingManager(throttlingManager, [&setThrottlingManagerPromise](const error e) { EXPECT_EQ(e, API_OK); setThrottlingManagerPromise.set_value(); }); ASSERT_EQ(setThrottlingManagerPromise.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready) << "The upload throttling manager set operation has timed out"; } /** * @brief Calls MegaApi::getSyncUploadThrottleUpperLimits or * MegaApi::getSyncUploadThrottleLowerLimits to obtain the limits for the * ISyncUploadThrottlingManager configurable values and compare them with the expected ones. * * @param upperLimits True to retrieve the upper limits * (MegaApi::getSyncUploadThrottleUpperLimits), False for the lower limits * (MegaApi::getSyncUploadThrottleLowerLimits) * @param expectedThrottleUpdateRateLimit The throttleUpdateRateLimit value (default) that is * expected to match with the one returned by the MegaApi. * @param expectedMaxUploadsBeforeThrottleLimit The maxUploadsBeforeThrottleLimit value * (default) that is expected to match with the one returned by the MegaApi. */ void validateThrottleValueLimits(MegaApi* const api, const bool upperLimits, const std::chrono::seconds expectedThrottleUpdateRateLimit, const unsigned expectedMaxUploadsBeforeThrottleLimit) { NiceMock mockReqListener{api}; EXPECT_CALL(mockReqListener, onRequestFinish) .Times(1) .WillOnce( [&mockReqListener, &expectedThrottleUpdateRateLimit, &expectedMaxUploadsBeforeThrottleLimit](::mega::MegaApi*, ::mega::MegaRequest* req, ::mega::MegaError* err) { const bool matchesType = sdk_test::checkAndExpectThat( req->getType(), MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_LIMITS); const bool matchesError = sdk_test::checkAndExpectThat(err->getErrorCode(), API_OK); const bool matchesThrottleUpdateRateLimit = sdk_test::checkAndExpectThat(req->getNumber(), expectedThrottleUpdateRateLimit.count()); const bool matchesMaxUploadsBeforeThrottleLimit = sdk_test::checkAndExpectThat( req->getTotalBytes(), static_cast(expectedMaxUploadsBeforeThrottleLimit)); mockReqListener.markAsFinished(matchesType && matchesError && matchesThrottleUpdateRateLimit && matchesMaxUploadsBeforeThrottleLimit); }); if (upperLimits) { megaApi[0]->getSyncUploadThrottleUpperLimits(&mockReqListener); } else { megaApi[0]->getSyncUploadThrottleLowerLimits(&mockReqListener); } ASSERT_TRUE(mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Calls MegaApi::setThrottleUpdateRate with parametrizable limits and expected * errors. * * @param maxUploadsBeforeThrottle The limit of allowed uploads before throttling the file. * @param expectedError The expected error for the MegaApi::setSyncMaxUploadsBeforeThrottle * result. */ void setThrottleUpdateRate(MegaApi* const api, const std::chrono::seconds throttleUpdateRate, const error expectedError) { NiceMock mockReqListener{api}; mockReqListener.setErrorExpectations(expectedError, _, MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES); megaApi[0]->setSyncUploadThrottleUpdateRate( static_cast(throttleUpdateRate.count()), &mockReqListener); ASSERT_TRUE(mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Calls MegaApi::setSyncMaxUploadsBeforeThrottle with parametrizable limits and expected * errors. * * @param maxUploadsBeforeThrottle The limit of allowed uploads before throttling the file. * @param expectedError The expected error for the MegaApi::setSyncMaxUploadsBeforeThrottle * result. */ void setMaxUploadsBeforeThrottle(MegaApi* const api, const unsigned maxUploadsBeforeThrottle, const error expectedError) { NiceMock mockReqListener{api}; mockReqListener.setErrorExpectations(expectedError, _, MegaRequest::TYPE_SET_SYNC_UPLOAD_THROTTLE_VALUES); megaApi[0]->setSyncMaxUploadsBeforeThrottle(maxUploadsBeforeThrottle, &mockReqListener); ASSERT_TRUE(mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Calls MegaApi::checkSyncUploadsThrottled with parametrizable flag * expectAnyDelayedUploads. * * @param expectAnyDelayedUploads The expected flag returned from the call: true for delayed * uploads waiting to be uploaded, false otherwise. */ void checkSyncUploadsThrottled(const bool expectAnyDelayedUploads) { NiceMock mockReqListener{megaApi[0].get()}; EXPECT_CALL(mockReqListener, onRequestFinish) .Times(1) .WillOnce( [&mockReqListener, expectAnyDelayedUploads](::mega::MegaApi*, ::mega::MegaRequest* req, ::mega::MegaError* err) { const bool matchesType = sdk_test::checkAndExpectThat( req->getType(), MegaRequest::TYPE_CHECK_SYNC_UPLOAD_THROTTLED_ELEMENTS); const bool matchesError = sdk_test::checkAndExpectThat(err->getErrorCode(), API_OK); const bool matchesAnyDelayedUploads = sdk_test::checkAndExpectThat(req->getFlag(), expectAnyDelayedUploads); mockReqListener.markAsFinished(matchesType && matchesError && matchesAnyDelayedUploads); }); megaApi[0]->checkSyncUploadsThrottled(&mockReqListener); ASSERT_TRUE(mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Creates and edits a file and let it sync-upload the max number of times before * throttle. * * 1. Creates the file and let it up-sync. * 2. Edits the file and let it up-sync maxUploadsBeforeThrottle - 1. * * @param tempFile The LocalTempFile shared_ptr that will be re-created and used to create and * edit the file. * @param MegaHandle The handle of the directory to upload the file to. * @param maxUploadsBeforeThrottle The limit of allowed uploads before throttling the file. */ void doUnthrottledUploads(std::shared_ptr& tempFile, const std::string_view newFileName, const fs::path& newFilePath, const MegaHandle dirHandle, const unsigned maxUploadsBeforeThrottle) { if (maxUploadsBeforeThrottle == 0) { LOG_debug << "[doUnthrottledUploads] Max uploads before throttle is 0. There cannot be " "any unthrottled upload"; return; } // Wait for the created file to be uploaded. This upload must be unthrottled. editFileAndWaitForUploadScoped(megaApi[0].get(), newFileName, dirHandle, [&tempFile, &newFilePath]() { tempFile = std::make_shared(newFilePath, 1000); }); // Now we'll do (maxBeforeThrottle - 1) edits to trigger sync-uploads that should also be // unthrottled. const unsigned unthrottledEdits = maxUploadsBeforeThrottle - 1; // File edit action to be executed within editFileAndWaitForUploadScoped(). const auto fileEditAction = [&tempFile = std::as_const(tempFile)]() { tempFile->appendData(100); }; for (const auto i: range(unthrottledEdits)) { LOG_debug << "[doUnthrottledUploads] Doing unthrottled edit #" << i + 1; editFileAndWaitForUploadScoped(megaApi[0].get(), newFileName, dirHandle, fileEditAction); } } }; /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_GetThrottleValues * * Test MegaApi::getSyncUploadThrottleUpperLimits to get the current throttle values. * We just check that the method is called correctly and the operation finishes with API_OK. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_GetThrottleValues) { static const auto logPre = getLogPrefix(); NiceMock mockReqListener{megaApi[0].get()}; mockReqListener.setErrorExpectations(API_OK, _, MegaRequest::TYPE_GET_SYNC_UPLOAD_THROTTLE_VALUES); megaApi[0]->getSyncUploadThrottleValues(&mockReqListener); ASSERT_TRUE(mockReqListener.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_GetThrottleValuesLowerLimits * * Test MegaApi::getSyncUploadThrottleLowerLimits to get the lower limits for the configurable * throttle values. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_GetThrottleValuesLowerLimits) { static const auto logPre = getLogPrefix(); const auto throttlingManager = std::make_unique(); const auto throttleValueLimits = throttlingManager->throttleValueLimits(); const bool upperLimits = false; ASSERT_NO_FATAL_FAILURE( validateThrottleValueLimits(megaApi[0].get(), upperLimits, throttleValueLimits.throttleUpdateRateLowerLimit, throttleValueLimits.maxUploadsBeforeThrottleLowerLimit)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_GetThrottleValuesUpperLimits * * Test MegaApi::getSyncUploadThrottleUpperLimits to get the upper limits for the configurable * throttle values. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_GetThrottleValuesUpperLimits) { static const auto logPre = getLogPrefix(); const auto throttlingManager = std::make_unique(); const auto throttleValueLimits = throttlingManager->throttleValueLimits(); const bool upperLimits = true; ASSERT_NO_FATAL_FAILURE( validateThrottleValueLimits(megaApi[0].get(), upperLimits, throttleValueLimits.throttleUpdateRateUpperLimit, throttleValueLimits.maxUploadsBeforeThrottleUpperLimit)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_SetThrottleUpdateRate_ValidValue * * Test MegaApi::setThrottleUpdateRate with a valid value. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetThrottleUpdateRate_ValidValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NO_FATAL_FAILURE(setThrottleUpdateRate(megaApi[0].get(), throttleValueLimits.throttleUpdateRateLowerLimit, API_OK)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_SetThrottleUpdateRate_InvalidLowerValue * * Test MegaApi::setThrottleUpdateRate with an invalid value which is below the lower limit. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetThrottleUpdateRate_InvalidLowerValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NE(throttleValueLimits.maxUploadsBeforeThrottleLowerLimit, 0); ASSERT_NO_FATAL_FAILURE(setThrottleUpdateRate(megaApi[0].get(), throttleValueLimits.throttleUpdateRateLowerLimit - std::chrono::seconds(1), API_EARGS)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_SetThrottleUpdateRate_InvalidUpperValue * * Test MegaApi::setThrottleUpdateRate with an invalid value which is above the upper limit. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetThrottleUpdateRate_InvalidUpperValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NO_FATAL_FAILURE(setThrottleUpdateRate(megaApi[0].get(), throttleValueLimits.throttleUpdateRateUpperLimit + std::chrono::seconds(1), API_EARGS)); } /** * @brief SdkTestSyncUploadThrottling.TestPublicInterfaces_SetMaxUploadsBeforeThrottle_ValidValue * * Test MegaApi::setMaxUploadsBeforeThrottle with a valid value. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetMaxUploadsBeforeThrottle_ValidValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NO_FATAL_FAILURE( setMaxUploadsBeforeThrottle(megaApi[0].get(), throttleValueLimits.maxUploadsBeforeThrottleLowerLimit, API_OK)); } /** * @brief * SdkTestSyncUploadThrottling.TestPublicInterfaces_SetMaxUploadsBeforeThrottle_InvalidLowerValue * * Test MegaApi::setMaxUploadsBeforeThrottle with an invalid value which is below the lower limit. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetMaxUploadsBeforeThrottle_InvalidLowerValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NE(throttleValueLimits.maxUploadsBeforeThrottleLowerLimit, 0); ASSERT_NO_FATAL_FAILURE( setMaxUploadsBeforeThrottle(megaApi[0].get(), throttleValueLimits.maxUploadsBeforeThrottleLowerLimit - 1, API_EARGS)); } /** * @brief * SdkTestSyncUploadThrottling.TestPublicInterfaces_SetMaxUploadsBeforeThrottle_InvalidUpperValue * * Test MegaApi::setMaxUploadsBeforeThrottle with an invalid value which is above the upper limit. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_SetMaxUploadsBeforeThrottle_InvalidUpperValue) { static const auto logPre = getLogPrefix(); const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); ASSERT_NO_FATAL_FAILURE( setMaxUploadsBeforeThrottle(megaApi[0].get(), throttleValueLimits.maxUploadsBeforeThrottleUpperLimit + 1, API_EARGS)); } /** * @brief * SdkTestSyncUploadThrottling.TestPublicInterfaces_CheckSyncUploadsThrottled_NoPendingDelayedUploads * * Test MegaApi::checkSyncUploadsThrottled when there the delayed uploads collection is empty. It * should return false. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_CheckSyncUploadsThrottled_NoPendingDelayedUploads) { static const auto logPre = getLogPrefix(); constexpr bool expectAnyDelayedUploads{false}; ASSERT_NO_FATAL_FAILURE(checkSyncUploadsThrottled(expectAnyDelayedUploads)); } /** * @brief * SdkTestSyncUploadThrottling.TestPublicInterfaces_CheckSyncUploadsThrottled_PendingDelayedUploads * * Test MegaApi::checkSyncUploadsThrottled when there the delayed uploads collection is not empty. * It should return true. */ TEST_F(SdkTestSyncUploadThrottling, TestPublicInterfaces_CheckSyncUploadsThrottled_PendingDelayedUploads) { static const auto logPre = getLogPrefix(); // Add some delayed uploads to a custom UploadThrottlingManager and then inject it to the // client. const auto uploadThrottlingManager = std::make_shared(); DelayedSyncUpload delayedSyncUpload{nullptr, {}, {}, {}}; uploadThrottlingManager->addToDelayedUploads(std::move(delayedSyncUpload)); setThrottlingManager(megaApi[0]->getClient(), uploadThrottlingManager); constexpr bool expectAnyDelayedUploads{true}; ASSERT_NO_FATAL_FAILURE(checkSyncUploadsThrottled(expectAnyDelayedUploads)); } /** * @brief SdkTestSyncUploadThrottling.UploadUnthrottledFile * * Create a file and edit it the max number of times allowed before being throttled. * * 1. Create a file and let it upsync. This counts as one time in the internal counters. * 2. Edit the file and let it upsync the max number of times allowed to be uploaded unthrottled * (counting the first upload upon creating the file). */ TEST_F(SdkTestSyncUploadThrottling, UploadUnthrottledFile) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Creating real and mocked upload manager"; const auto throttlingManagers = createAndSetThrottlingManagers(); const auto& uploadThrottlingManager = throttlingManagers.first; const auto& mockUploadThrottlingManager = throttlingManagers.second; ASSERT_TRUE(uploadThrottlingManager); ASSERT_TRUE(mockUploadThrottlingManager); const unsigned maxUploadsBeforeThrottle = uploadThrottlingManager->maxUploadsBeforeThrottle(); LOG_verbose << logPre << "Get the dir path node handle"; const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); LOG_verbose << logPre << "Prepare the new file locally"; const std::string_view newFileName{"test_file_new.txt"}; const auto newFilePath = getLocalTmpDir() / newFileName; std::shared_ptr tempFile; LOG_verbose << logPre << "Prepare expectations and the file so it is created and uploaded and then edit " "it for further unthrottled uploads until reaching the maxUploadsBeforeThrottle(" << maxUploadsBeforeThrottle << ") threshold"; EXPECT_CALL(*mockUploadThrottlingManager, addToDelayedUploads(_)).Times(0); doUnthrottledUploads(tempFile, newFileName, newFilePath, dir1Handle, maxUploadsBeforeThrottle); } /** * @brief SdkTestSyncUploadThrottling.UploadThrottledFile * * Upload a delayed (throttled) file twice. * For this, the test edits a file enough times to be throttled and adds expectations regarding * throttling times and methods to be called. * * 1. Edit a file and let it upsync enough times to be throttled upon next sync-upload. * 2. Add expectations and reset the lastProcessedTime counter right before editing the file. That * way we can have more accurate expectations regarding the upload start based on throttling update * rate. * 3. Edit the file again and let it be added to the throttled uploads. * 4. Wait for it to finish and upload it again. Both times the upload must have been throttled. */ TEST_F(SdkTestSyncUploadThrottling, UploadThrottledFile) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Creating real and mocked upload manager"; const auto throttlingManagers = createAndSetThrottlingManagers(); const auto& uploadThrottlingManager = throttlingManagers.first; const auto& mockUploadThrottlingManager = throttlingManagers.second; ASSERT_TRUE(uploadThrottlingManager); ASSERT_TRUE(mockUploadThrottlingManager); const auto updateRateSeconds = uploadThrottlingManager->throttleUpdateRate(); const auto maxUploadsBeforeThrottle = uploadThrottlingManager->maxUploadsBeforeThrottle(); LOG_verbose << logPre << "Get the dir path node handle"; const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); LOG_verbose << logPre << "Prepare the new file locally"; const std::string_view newFileName{"test_file_new.txt"}; const auto newFilePath = getLocalTmpDir() / newFileName; std::shared_ptr tempFile; LOG_verbose << logPre << "Edit and upload the file until reaching the maxUploadsBeforeThrottle(" << maxUploadsBeforeThrottle << ") threshold"; doUnthrottledUploads(tempFile, newFileName, newFilePath, dir1Handle, maxUploadsBeforeThrottle); LOG_verbose << logPre << "Reset last processed time for throttling: next upload should happen " "approximately after updateRateSeconds"; uploadThrottlingManager->resetLastProcessedTime(); for (const auto i: range(2)) { LOG_verbose << logPre << "Prepare and edit the file for the next upload (num: " << (maxUploadsBeforeThrottle + i) << ") which must be throttled "; EXPECT_CALL(*mockUploadThrottlingManager, processDelayedUploads(_)).Times(AtLeast(1)); InSequence seq; // ensure order from here: addToDelayedQueue -> (process the delayed upload) // -> transfer requests. EXPECT_CALL(*mockUploadThrottlingManager, addToDelayedUploads(_)).Times(Exactly(1)); const auto minTimeToStartUpload = std::invoke( [&]() -> std::chrono::seconds { const auto diff = (updateRateSeconds - uploadThrottlingManager->timeSinceLastProcessedUpload()) - std::chrono::seconds(i); // Subtract an additional margin/tolerance value. return std::max(std::chrono::seconds(0), diff); }); // Define the edit action to be executed within editFileAndWaitForUploadScoped(). const auto fileEditAction = [&tempFile = std::as_const(tempFile)]() { tempFile->appendData(100); }; // Finally edit the file and wait for upload and meeting expectations. editFileAndWaitForUploadScoped( megaApi[0].get(), newFileName, dir1Handle, fileEditAction, UploadWaitConfig{std::chrono::seconds(minTimeToStartUpload)}); } } /** * @brief SdkTestSyncUploadThrottling.UploadSeveralThrottledFiles * * Similar to SdkTestSyncUploadThrottling.UploadThrottledFile but with two files, checking that the * throttle logic is handled correctly for different transfers. * * 1a. Edit a file1 and let it upsync enough times to be throttled upon next sync-upload. * 1b. Do the same with a file2. * 2a. Edit the file1 again and let it be added to the throttled uploads. * 2b. When this happens, edit file2 so it get throttled too. Add expectations taking into account * that the throttling time for this file2 to start is twice the throttle update rate, as file1 * needs to be processed first. */ TEST_F(SdkTestSyncUploadThrottling, UploadSeveralThrottledFiles) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Creating real and mocked upload manager"; const auto throttlingManagers = createAndSetThrottlingManagers(); const auto& uploadThrottlingManager = throttlingManagers.first; const auto& mockUploadThrottlingManager = throttlingManagers.second; ASSERT_TRUE(uploadThrottlingManager); ASSERT_TRUE(mockUploadThrottlingManager); const auto updateRateSeconds = uploadThrottlingManager->throttleUpdateRate(); const auto maxUploadsBeforeThrottle = uploadThrottlingManager->maxUploadsBeforeThrottle(); LOG_verbose << logPre << "Get the dir path node handle"; const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); LOG_verbose << logPre << "Prepare the new file1 locally"; const std::string_view newFile1Name{"test_file1_new.txt"}; const auto newFile1Path = getLocalTmpDir() / newFile1Name; std::shared_ptr tempFile1; LOG_verbose << logPre << "Edit and upload the file1 until reaching the maxUploadsBeforeThrottle(" << maxUploadsBeforeThrottle << ") threshold"; doUnthrottledUploads(tempFile1, newFile1Name, newFile1Path, dir1Handle, maxUploadsBeforeThrottle); LOG_verbose << logPre << "Prepare the new file2 locally"; const std::string_view newFile2Name{"test_file2_new.txt"}; const auto newFile2Path = getLocalTmpDir() / newFile2Name; std::shared_ptr tempFile2; LOG_verbose << logPre << "Edit and upload the file2 until reaching the maxUploadsBeforeThrottle(" << maxUploadsBeforeThrottle << ") threshold"; doUnthrottledUploads(tempFile2, newFile2Name, newFile2Path, dir1Handle, maxUploadsBeforeThrottle); LOG_verbose << logPre << "Prepare expectations and limits"; EXPECT_CALL(*mockUploadThrottlingManager, processDelayedUploads(_)).Times(AtLeast(2)); EXPECT_CALL(*mockUploadThrottlingManager, addToDelayedUploads(_)).Times(Exactly(2)); // Prepare file edit action for file1. When the file1 is editted, it will signal file1Edited // promise. std::promise file1Edited; const auto file1EditAction = [&tempFile1, &uploadThrottlingManager, &file1Edited]() { uploadThrottlingManager ->resetLastProcessedTime(); // Reset the last processed time so we ensure that the file1 // will need to wait the throttleUpdateRate time and use it // as the expectation. tempFile1->appendData(100); // Simulate editing the file. file1Edited.set_value(); // Signal that file1 has been edited. }; // Prepare file edit action for file2. When file1Edited is resolved, file2 will be edited // afterwards, so it will be the second task in the delayed uploads queue. const auto file2EditAction = [&tempFile2, &file1Edited]() { // Wait until file1 has been edited. // Give 10secs as max, but it should be almost immediate. ASSERT_FALSE(file1Edited.get_future().wait_for(std::chrono::seconds(10)) != std::future_status::ready) << "The file1 wasn't edited within the timeout"; // Simulate editing file2. tempFile2->appendData(100); }; const size_t numQueuedFiles = 2; const auto adjustedUpdateRateSecondsWithTwoQueuedFiles = calcDynamicThrottleUpdateRate(updateRateSeconds, numQueuedFiles); // Wait config values for task 1. const auto uploadWaitConfigTask1 = std::invoke( [&adjustedUpdateRateSecondsWithTwoQueuedFiles]() -> UploadWaitConfig { UploadWaitConfig uploadWaitConfig{}; uploadWaitConfig.minWaitForTransferStart = adjustedUpdateRateSecondsWithTwoQueuedFiles; return uploadWaitConfig; }); // Define wait config values for task 2. const auto uploadWaitConfigTask2 = std::invoke( [&updateRateSeconds, &adjustedUpdateRateSecondsWithTwoQueuedFiles]() -> UploadWaitConfig { UploadWaitConfig uploadWaitConfig{}; uploadWaitConfig.minWaitForTransferStart = std::chrono::seconds(adjustedUpdateRateSecondsWithTwoQueuedFiles + updateRateSeconds - std::chrono::seconds(1)); uploadWaitConfig.maxWaitForTransferStartFromMinWait = UploadWaitConfig::TOLERANCE_SECONDS_FOR_STARTING_UPLOADS * 2; return uploadWaitConfig; }); // Prepare expectations for task 1. NiceMock mockListener{ megaApi[0].get()}; // Global mocked listener for both tasks. std::promise upload1Started; std::promise upload1Finished; setupMockListenerExpectations(mockListener, newFile1Name, dir1Handle, upload1Started, upload1Finished); // Prepare expectations for task 2. std::promise upload2Started; std::promise upload2Finished; setupMockListenerExpectations(mockListener, newFile2Name, dir1Handle, upload2Started, upload2Finished); megaApi[0]->addListener(&mockListener); // First file upload task. LOG_verbose << logPre << "Prepare and edit the file1 for the next upload which must be throttled"; auto task1 = std::async(std::launch::async, [&]() { editFileAndWaitForUpload(upload1Started, upload1Finished, file1EditAction, uploadWaitConfigTask1); }); // Second file upload task. LOG_debug << logPre << "Prepare and edit the file2 for the next upload which must be throttled. The " "file2 will be edited right afterwards file1 so it gets enqueued after it"; auto task2 = std::async(std::launch::async, [&]() { editFileAndWaitForUpload(upload2Started, upload2Finished, file2EditAction, uploadWaitConfigTask2); }); // Wait for both tasks to complete. task1.get(); task2.get(); } /** * @brief SdkTestSyncUploadThrottling.UploadThrottledFilePauseSyncAndUploadItUnthrottled * * 1. Edit a file and let it upsync enough times to be throttled upon next sync-upload. * 2. Edit the file again and let it be added to the throttled uploads. * 3. Pause the sync before the delayed upload starts. * 4. Resume the sync. * 5. Checks that the former delayed upload is now triggered and uploaded without throttling. */ TEST_F(SdkTestSyncUploadThrottling, UploadThrottledFilePauseSyncAndUploadItUnthrottled) { static const auto logPre = getLogPrefix(); LOG_verbose << logPre << "Ensuring sync is running on dir1"; ASSERT_NO_FATAL_FAILURE(ensureSyncNodeIsRunning("dir1")); LOG_verbose << logPre << "Creating real and mocked upload manager"; const auto throttlingManagers = createAndSetThrottlingManagers(); const auto& uploadThrottlingManager = throttlingManagers.first; const auto& mockUploadThrottlingManager = throttlingManagers.second; ASSERT_TRUE(uploadThrottlingManager); ASSERT_TRUE(mockUploadThrottlingManager); const auto updateRateSeconds = uploadThrottlingManager->throttleUpdateRate(); const auto maxUploadsBeforeThrottle = uploadThrottlingManager->maxUploadsBeforeThrottle(); LOG_verbose << logPre << "Get the dir path node handle"; const auto dir1Handle = getNodeHandleByPath("dir1"); ASSERT_NE(dir1Handle, UNDEF); LOG_verbose << logPre << "Prepare the new file locally"; const std::string_view newFileName{"test_file_new.txt"}; const auto newFilePath = getLocalTmpDir() / newFileName; std::shared_ptr tempFile; LOG_verbose << logPre << "Edit and upload the file until reaching the maxUploadsBeforeThrottle(" << maxUploadsBeforeThrottle << ") threshold"; doUnthrottledUploads(tempFile, newFileName, newFilePath, dir1Handle, maxUploadsBeforeThrottle); LOG_verbose << logPre << "Prepare and edit the file for the next upload which must be throttled"; EXPECT_CALL(*mockUploadThrottlingManager, processDelayedUploads(_)).Times(AnyNumber()); EXPECT_CALL(*mockUploadThrottlingManager, addToDelayedUploads(_)).Times(Exactly(1)); // Define the file edit action to be executed within editFileAndWaitForUpload(). const auto fileEditAction = [&tempFile = std::as_const(tempFile), &uploadThrottlingManager, updateRateSeconds, this]() { uploadThrottlingManager->resetLastProcessedTime(); // This will ensure that the throttle // time is more or less updateRateSeconds // when calling resetLastProcessedTime(). tempFile->appendData(100); std::this_thread::sleep_for( std::chrono::seconds(updateRateSeconds / 3)); // Wait a bit before suspending the sync. LOG_verbose << logPre << "Pausing the sync"; ASSERT_NO_FATAL_FAILURE(suspendSync()); LOG_verbose << logPre << "Resuming the sync"; ASSERT_NO_FATAL_FAILURE(resumeSync()); LOG_verbose << logPre << "Waiting for the upload to resume and finish"; }; editFileAndWaitForUploadScoped(megaApi[0].get(), newFileName, dir1Handle, fileEditAction); } #endif // ENABLE_SYNC sdk-10.11.0/tests/integration/SdkTestTransferMaxSpeeds_test.cpp000066400000000000000000000254551516266226600246120ustar00rootroot00000000000000/** * @file * @brief This file defines some tests for validating changes in the max download/upload speed * limits for transfers */ #include "integration/integration_test_utils.h" #include "integration/mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" #include using namespace sdk_test; /** * @class TransferProgressReporter * @brief A helper functor to pass as callback to the expectations on onTransferUpdate. */ struct TransferProgressReporter { public: /** * @brief A factor used to validate received updates on the speed: * receivedSpeed <= MAX_PERMITTED_SPEED_FACTOR * targetMaxSpeed * @note Why 3? For the current state of the code a factor of 2 caused the tests to fail on * macos some times. With this value we pass the tests successfully and confirm that the bug is * not present any more. */ static constexpr double MAX_PERMITTED_SPEED_FACTOR = 3.; /** * @brief The fraction of the given expectedTime to wait before starting to apply the * checks on the received speed updates. Useful to wait for some initial stabilization of the * values. */ static constexpr double STABILIZATION_TIME_FRACTION = 0.2; TransferProgressReporter(const std::chrono::seconds expectedTime, const unsigned targetMaxSpeed): mStartTime{std::chrono::steady_clock::now()}, mExpectedTime{expectedTime}, mTargetMaxSpeed{targetMaxSpeed} {} /** * @brief Validate expectations on the received transfer updates */ void operator()(MegaApi*, MegaTransfer* transfer) { if (stabilizing()) return; const auto speed = transfer->getSpeed(); EXPECT_LE(speed, static_cast(MAX_PERMITTED_SPEED_FACTOR * mTargetMaxSpeed)) << "Received a transfer update with a speed outside of the accepted range"; } private: std::chrono::steady_clock::time_point mStartTime; std::chrono::seconds mExpectedTime{0}; unsigned mTargetMaxSpeed{0}; bool stabilizing() const { const auto secondsSinceStart = std::chrono::steady_clock::now() - mStartTime; return secondsSinceStart < mExpectedTime * STABILIZATION_TIME_FRACTION; } }; class SdkTestTransferMaxSpeeds: public SdkTest { public: static constexpr auto MAX_TIMEOUT = 3min; // Timeout for some operations in this tests suite void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); } void TearDown() override { megaApi[0]->setMaxUploadSpeed(-1); megaApi[0]->setMaxDownloadSpeed(-1); SdkTest::TearDown(); } /** * @brief Performs an upload limiting the speed to the given value. Monitorices the progress * using TransferProgressReporter. * * @param expectedTime The amount of time in seconds that the transfer is expected to take if it * goes at max speed. * @param maxSpeed The max speed set for the transfer in bytes per second * @param filePath The path to the file that will be uploaded * * @return The total time taken for the transfer to complete in seconds if the upload succeeded */ std::optional performAndMonitorUpload(const std::chrono::seconds expectedTime, const unsigned maxSpeed, const fs::path& filePath) const { LOG_debug << getLogPrefix() << "Setting upload speed limit"; if (const bool setLimitSucced = megaApi[0]->setMaxUploadSpeed(maxSpeed); !setLimitSucced) { EXPECT_TRUE(setLimitSucced) << "Error setting upload max speed"; return {}; } const auto starter = [&filePath, this](auto* transferListener) { const std::string fileName{getFilePrefix() + ".txt"}; MegaUploadOptions uploadOptions; uploadOptions.fileName = fileName; uploadOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; const auto localPath = filePath.string(); megaApi[0]->startUpload(localPath, rootNode.get(), nullptr, &uploadOptions, transferListener); }; return performAndMonitorTransferAux(expectedTime, maxSpeed, std::move(starter)); } /** * @brief Performs a download limiting the speed to the given value. Monitorices the progress * using TransferProgressReporter. * * @param expectedTime The amount of time in seconds that the transfer is expected to take if it * goes at max speed. * @param maxSpeed The max speed set for the transfer in bytes per second * @param nodeToDownload The node that will be downloaded * * @return The total time taken for the transfer to complete in seconds if the upload succeeded */ std::optional performAndMonitorDownload(const std::chrono::seconds expectedTime, const unsigned maxSpeed, MegaNode* nodeToDownload) const { LOG_debug << getLogPrefix() << "Setting download speed limit"; if (const bool setLimitSucced = megaApi[0]->setMaxDownloadSpeed(maxSpeed); !setLimitSucced) { EXPECT_TRUE(setLimitSucced) << "Error setting download max speed"; return {}; } const auto starter = [nodeToDownload, this](auto* transferListener) { megaApi[0]->startDownload( nodeToDownload, "./", nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, transferListener); }; return performAndMonitorTransferAux(expectedTime, maxSpeed, std::move(starter)); } private: /** * @brief Auxiliary method to handle both uploads and downloads */ template std::optional performAndMonitorTransferAux(const std::chrono::seconds expectedTime, const unsigned maxSpeed, TransferStarter&& transferStarter) const { const auto logPre{getLogPrefix()}; LOG_debug << logPre << "Starting the transfer"; testing::NiceMock mtl{megaApi[0].get()}; std::chrono::steady_clock::time_point startTime; EXPECT_CALL(mtl, onTransferStart) .WillOnce( [&startTime] { startTime = std::chrono::steady_clock::now(); }); std::chrono::steady_clock::time_point endTime; EXPECT_CALL(mtl, onTransferFinish) .WillOnce( [&mtl, &endTime] { endTime = std::chrono::steady_clock::now(); mtl.markAsFinished(); }); EXPECT_CALL(mtl, onTransferUpdate) .WillRepeatedly(TransferProgressReporter(expectedTime, maxSpeed)); transferStarter(&mtl); LOG_debug << logPre << "Waiting for the transfer to finish"; if (const bool transferFinished = mtl.waitForFinishOrTimeout(MAX_TIMEOUT); !transferFinished) { EXPECT_TRUE(transferFinished) << "The transfer didn't finish successfully in the given time window"; return {}; } return std::chrono::duration_cast(endTime - startTime); } }; /** * @brief SdkTestTransferMaxSpeeds.MaxUploadSpeed * * Validates the MegaApi::setMaxUploadSpeed public method by: * - Uploading a file * - Track the received onTransferUpdate callbacks and check if the reported speed is reasonable * for the given limit * - At the end, check if the upload took a reasonable amount of time * * @note This test sets a low limit, so it is almost guaranteed that the transfer is throttled. * However, as this might not be the case in jenkins, the strongest test conditions are validated on * the side were the speed limit is highly exceeded. */ TEST_F(SdkTestTransferMaxSpeeds, MaxUploadSpeed) { constexpr auto MAX_SPEED_BYTES_PER_SECOND{10000u}; constexpr auto EXPECTED_TIME_FOR_TRANSFER{40s}; constexpr auto FILE_SIZE = MAX_SPEED_BYTES_PER_SECOND * EXPECTED_TIME_FOR_TRANSFER.count(); LOG_debug << getLogPrefix() << "Create the file to be uploaded"; const fs::path filePath{getFilePrefix() + ".txt"}; LocalTempFile f(filePath, FILE_SIZE); const auto requiredTime = performAndMonitorUpload(EXPECTED_TIME_FOR_TRANSFER, MAX_SPEED_BYTES_PER_SECOND, filePath); ASSERT_TRUE(requiredTime) << "Something went wrong during the upload"; EXPECT_GE(requiredTime->count(), EXPECTED_TIME_FOR_TRANSFER.count() / TransferProgressReporter::MAX_PERMITTED_SPEED_FACTOR) << "The transfer took shorter than expected to complete"; } /** * @brief SdkTestTransferMaxSpeeds.MaxDownloadSpeed * * Same as SdkTestTransferMaxSpeeds.MaxUploadSpeed but for downloads. * In this case we test for two different max limits. One below 100KB and other above. This is done * because that limit sets a different buffer size in libcurl. */ TEST_F(SdkTestTransferMaxSpeeds, MaxDownloadSpeed) { constexpr auto EXPECTED_TIME_FOR_TRANSFER{40s}; for (const auto maxSpeedBytesPerSecond: {10000u, 200000u}) { const unsigned fileSize = maxSpeedBytesPerSecond * EXPECTED_TIME_FOR_TRANSFER.count(); LOG_debug << getLogPrefix() << "Uploading file to be downloaded after. Size: " << fileSize; const std::string fileName{getFilePrefix() + std::to_string(maxSpeedBytesPerSecond) + ".txt"}; const auto nodeToDownload = sdk_test::uploadFile(megaApi[0].get(), LocalTempFile{fileName, fileSize}); ASSERT_TRUE(nodeToDownload); const auto requiredTime = performAndMonitorDownload(EXPECTED_TIME_FOR_TRANSFER, maxSpeedBytesPerSecond, nodeToDownload.get()); ASSERT_TRUE(requiredTime) << "Something went wrong during the download"; EXPECT_GE(requiredTime->count(), EXPECTED_TIME_FOR_TRANSFER.count() / TransferProgressReporter::MAX_PERMITTED_SPEED_FACTOR) << "The transfer took shorter than expected to complete"; } } sdk-10.11.0/tests/integration/SdkTestTransferStats_test.cpp000066400000000000000000000332611516266226600240110ustar00rootroot00000000000000/** * @file SdkTestTransferStats_test.cpp * @brief This file defines some tests for testing transfer stats (uploads & downloads). */ #include "sdk_test_utils.h" #include "SdkTest_test.h" #include namespace { /** * @brief Calculate specific metrics values that can be expected. * * @param transferType The type of transfer: PUT for uploads, GET for downloads. * @param sizes A vector with the file sizes. * @param raidedTransferRatio The ratio of raided files per transfer. * * @return A stats::TransferStats::Metrics object with the transfer type, median size, * contraharmonic mean and raided transfer ratio. */ stats::TransferStats::Metrics calculateExpectedMetrics(const direction_t transferType, const std::vector& sizes, const double raidedTransferRatio = 0.0) { stats::TransferStats::Metrics metrics; // Assign the transfer type (PUT or GET). EXPECT_TRUE(transferType == PUT || transferType == GET); metrics.mTransferType = transferType; // Assign number of transfers. metrics.mNumTransfers = sizes.size(); // Calculate the median size. auto sortedSizes = sizes; std::sort(sortedSizes.begin(), sortedSizes.end()); metrics.mMedianSize = stats::calculateMedian(sortedSizes); // Calculate the contraharmonic mean (sizes weighted by their own sizes). metrics.mContraharmonicMeanSize = stats::calculateWeightedAverage(sizes, sizes); // Set RAID transfer ratio. metrics.mRaidedTransferRatio = raidedTransferRatio; return metrics; } /** * @brief Compare the expected Metrics with the metrics obtained from the TransferStatsManager. * * For medianSpeed, weightedAverageSpeed, maxSpeed, avgLatency, and failedRequestRatio, * we perform some light checks, as those are not fully predictable. * * @param expected The expected values for TransferStats::Metrics. * @param actual The TransferStats::Metrics object retrieved from the * MegaClient::TransferStatsManager. * */ void compareMetrics(const stats::TransferStats::Metrics& expected, const stats::TransferStats::Metrics& actual) { if (expected.mNumTransfers != actual.mNumTransfers) { LOG_warn << "Expected number of transfers (" << expected.mNumTransfers << ") does not match with actual value (" << actual.mNumTransfers << "). Skipping comparison"; return; } EXPECT_EQ(expected.mTransferType, actual.mTransferType); EXPECT_EQ(expected.mMedianSize, actual.mMedianSize); EXPECT_EQ(expected.mContraharmonicMeanSize, actual.mContraharmonicMeanSize); EXPECT_GT(actual.mMedianSpeed, 0); EXPECT_GE(actual.mWeightedAverageSpeed, actual.mMedianSpeed); EXPECT_GE(actual.mMaxSpeed, actual.mMedianSpeed); EXPECT_GT(actual.mAvgLatency, 0); EXPECT_LT(actual.mAvgLatency, 150000); EXPECT_GE(actual.mFailedRequestRatio, 0.0); EXPECT_LE(actual.mFailedRequestRatio, 1.1); EXPECT_EQ(expected.mRaidedTransferRatio, actual.mRaidedTransferRatio); }; } // namespace /** * @class SdkTestTransferStats * @brief Fixture for test suite to test Transfer Stats. * */ class SdkTestTransferStats: public SdkTest { public: /** * @brief Extra time from transfer request completion to the TransferSlot constructor, where the * transfer stats are collected. */ static constexpr std::chrono::milliseconds TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION{ 1500}; /** * @brief Wrapper to upload files with the necessary parameters. * * @param rootNode The ROOTNODE of the Cloud. * @param uploadFileName The name of the file. * @param content The contents of the file. * * @return A pointer to the MegaNode object created from the Cloud. */ MegaNode* uploadFileForStats(MegaNode* const rootNode, const std::string_view uploadFileName, const std::string_view content) { MegaHandle fileHandle = 0; sdk_test::LocalTempFile testTempFile(uploadFileName.data(), content); EXPECT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, uploadFileName.data(), rootNode, nullptr, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, true, false, nullptr)) << "Cannot upload " << uploadFileName; std::this_thread::sleep_for(TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION); return megaApi[0]->getNodeByHandle(fileHandle); } /** * @brief Wrapper to download files with the necessary parameters. * * @param node The cloud node with the file info to download. * @param downloadFileName The name of the file. */ void downloadFileForStats(MegaNode* const node, const std::string_view downloadFileName) { ASSERT_EQ( MegaError::API_OK, doStartDownload(0, node, downloadFileName.data(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */)) << "Cannot download " << downloadFileName; std::this_thread::sleep_for(TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION); }; }; /** * @test SdkTestTransferStats * * Upload and download regular files and a CloudRAID file, * collect Transfer Metrics and check expected results. * * Note: We don't compare upload metrics before to ensure * that upload and downloads metrics are separated and * were not mixed up by the TransferStatsManager. * * 1. UPLOAD AND DOWNLOAD TWO FILES TO COLLECT TRANSFER STATS. * 1.1 Upload files. * 1.2 Download both files. * 2. COLLECT AND COMPARE UPLOADS AND DOWNLOADS METRICS. * 2.1 Define sizes of uploaded and regular downloaded files. * 2.2 Collect metrics. * 2.3 Define expected metrics for uploads and compare results. * 2.4 Define expected metrics for the regular downloads and compare results. * 3. CHECK DOWNLOAD TRANSFER STATS INCLUDING A NEW CLOUDRAID FILE. * 3.1 Download a CloudRAID file. * 3.2 Define expected metrics after RAID download. * 3.3 Collect metrics for downloads including the CloudRAID file and compare results. */ TEST_F(SdkTestTransferStats, SdkTestTransferStats) { LOG_info << "___TEST SdkTestTransferStats"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); // 1. UPLOAD AND DOWNLOAD TWO FILES TO COLLECT TRANSFER STATS. // 1.1 Upload files. constexpr std::string_view file1content = "Current content 1"; std::unique_ptr testFileNode1( uploadFileForStats(rootNode.get(), "test1.txt", file1content)); ASSERT_TRUE(testFileNode1); constexpr std::string_view file2content = "Current content 2 - longer"; std::unique_ptr testFileNode2( uploadFileForStats(rootNode.get(), "test2.txt", file2content)); ASSERT_TRUE(testFileNode2); // 1.2. Download both files. downloadFileForStats(testFileNode1.get(), DOTSLASH "downfile1.txt"); downloadFileForStats(testFileNode2.get(), DOTSLASH "downfile2.txt"); // 2. COLLECT AND COMPARE UPLOADS AND DOWNLOADS METRICS. // 2.1 Define sizes of uploaded and regular downloaded files. constexpr m_off_t file1size = file1content.size(); // size = 17 bytes constexpr m_off_t file2size = file2content.size(); // size = 26 bytes const std::vector regularFileSizes = {file1size, file2size}; const stats::TransferStats::UncollectedTransfersCounters unCollectedDataExpectations{ regularFileSizes.size(), std::accumulate(regularFileSizes.begin(), regularFileSizes.end(), m_off_t{0})}; // Give enough time for the TransferSlot constructor to be called, when the stats are collected. std::this_thread::sleep_for(std::chrono::seconds(2)); // 2.2 Collect metrics. auto* const client{megaApi[0]->getClient()}; LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for UPLOADS"; ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT), unCollectedDataExpectations); const stats::TransferStats::Metrics uploadMetrics = client->mTransferStatsManager.collectAndPrintMetrics(PUT); LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for DOWNLOADS"; ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(GET), unCollectedDataExpectations); const stats::TransferStats::Metrics downloadMetrics1 = client->mTransferStatsManager.collectAndPrintMetrics(GET); // 2.3 Define expected metrics for uploads and compare results. const stats::TransferStats::Metrics expectedUploadMetrics = calculateExpectedMetrics(PUT, regularFileSizes); compareMetrics(expectedUploadMetrics, uploadMetrics); // 2.4 Define expected metrics for the regular downloads and compare results. const stats::TransferStats::Metrics expectedDownloadMetrics1 = calculateExpectedMetrics(GET, regularFileSizes); compareMetrics(expectedDownloadMetrics1, downloadMetrics1); // 3. CHECK DOWNLOAD TRANSFER STATS INCLUDING A NEW CLOUDRAID FILE. // 3.1 Download a CloudRAID file. { // https://mega.app/file/JzckQJ6L#X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA std::string url100MB = "/#!JzckQJ6L!X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA"; const auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + url100MB, rootNode.get()); std::unique_ptr nimported{megaApi[0]->getNodeByHandle(importHandle)}; constexpr std::string_view downloadFileName3{DOTSLASH "downfile3.cloudraided.sdktest"}; deleteFile(downloadFileName3.data()); downloadFileForStats(nimported.get(), DOTSLASH "downfile3.cloudraided.sdktest"); deleteFile(downloadFileName3.data()); } // 3.2 Define expected metrics after RAID download. constexpr m_off_t raidFileSize = 100 * 1024 * 1024; // 100MB const std::vector allFileSizes = {file1size, file2size, raidFileSize}; const stats::TransferStats::Metrics expectedDownloadMetrics2 = calculateExpectedMetrics(GET, allFileSizes, 0.33); // 1 out of 3 is RAID, with 2 decimal precision // 3.3 Collect metrics for downloads including the CloudRAID file and compare results. LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for DOWNLOADS after " "CLOUDRAID download"; ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(GET), (stats::TransferStats::UncollectedTransfersCounters{size_t{1}, raidFileSize})); const stats::TransferStats::Metrics downloadMetrics2 = client->mTransferStatsManager.collectAndPrintMetrics(GET); compareMetrics(expectedDownloadMetrics2, downloadMetrics2); } /** * @test SdkTestTransferStatsLogging * * Tests that TransferStats::collectAndPrintMetrics() have been called automatically after * TransferStatsManager::NUM_ENTRIES_FOR_LOGGING transfers. * * 1. Uploads NUM_ENTRIES_FOR_LOGGING-1 regular files * 2. Checks that the uncollectedAndPrinted transfer data is equal to the accumulated data for those * NUM_ENTRIES_FOR_LOGGING-1 transfers. * 3. Uploads 1 extra file. * 4. Checks that the uncollectedAndPrinted transfer data values are now zero. */ TEST_F(SdkTestTransferStats, SdkTestTransferStatsLogging) { static const auto logPre = getLogPrefix(); LOG_info << "___TEST " << logPre; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const auto rootNode = std::unique_ptr(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); constexpr std::string_view fileName{"test1.txt"}; constexpr std::string_view baseContent{"Current content "}; constexpr auto numUploads{stats::TransferStatsManager::NUM_ENTRIES_FOR_LOGGING - 1}; size_t totalExpectedBytes = 0; for (const auto i: range(numUploads)) { const auto fileContent = std::string(baseContent) + std::to_string(i); totalExpectedBytes += fileContent.size(); LOG_debug << logPre << "Upload file " << (i + 1); ASSERT_TRUE( std::unique_ptr(uploadFileForStats(rootNode.get(), fileName, fileContent))); } auto* const client = megaApi[0]->getClient(); ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT), (stats::TransferStats::UncollectedTransfersCounters{ numUploads, static_cast(totalExpectedBytes)})); LOG_debug << logPre << "Upload last file (" << (numUploads + 1) << ")"; ASSERT_TRUE( std::unique_ptr(uploadFileForStats(rootNode.get(), fileName, baseContent))); ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT), stats::TransferStats::UncollectedTransfersCounters{}); } sdk-10.11.0/tests/integration/SdkTest_test.cpp000066400000000000000000037472531516266226600213050ustar00rootroot00000000000000/** * @file tests/sdk_test.cpp * @brief Mega SDK test file * * (c) 2015 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ #include "SdkTest_test.h" #include "../stdfs.h" #include "env_var_accounts.h" #include "gmock/gmock-matchers.h" #include "gtest_common.h" #include "integration_test_utils.h" #include "mega/account.h" #include "mega/filesystem.h" #include "mega/scoped_helpers.h" #include "mega/testhooks.h" #include "mega/types.h" #include "megaapi.h" #include "megautils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "test.h" #include #include #include #include #include #include #include #include #include #include #if !defined(WIN32) && defined(ENABLE_ISOLATED_GFX) #include "mega/posix/gfx/worker/socket_utils.h" using ::mega::gfx::SocketUtils; #endif #define SSTR( x ) static_cast< const std::ostringstream & >( \ ( std::ostringstream() << std::dec << x ) ).str() using namespace std; std::unique_ptr<::mega::FileSystemAccess> fileSystemAccess = ::mega::createFSA(); #ifdef _WIN32 DWORD ThreadId() { return GetCurrentThreadId(); } #else pthread_t ThreadId() { return pthread_self(); } #endif const char* cwd() { // for windows and linux static char path[1024]; const char* ret; #ifdef _WIN32 #define getcwd _getcwd ret = _getcwd(path, sizeof path); #undef getcwd #else ret = getcwd(path, sizeof path); #endif assert(ret); return ret; } bool fileexists(const std::string& fn) { #ifdef _WIN32 fs::path p = u8path_compat(fn); return fs::exists(p); #else struct stat buffer; return (stat(fn.c_str(), &buffer) == 0); #endif } void copyFile(std::string& from, std::string& to) { LocalPath f = LocalPath::fromAbsolutePath(from); LocalPath t = LocalPath::fromAbsolutePath(to); fileSystemAccess->copylocal(f, t, m_time()); } bool removeFile(std::string& filename) { LocalPath f = LocalPath::fromAbsolutePath(filename); return fileSystemAccess->unlinklocal(f); } std::string megaApiCacheFolder(int index) { std::string p(cwd()); #ifdef _WIN32 p += "\\"; #else p += "/"; #endif p += "sdk_test_mega_cache_" + to_string(index); if (!fileexists(p)) { #ifdef _WIN32 #ifndef NDEBUG bool success = #endif fs::create_directory(p); assert(success); #else mkdir(p.c_str(), S_IRWXU); assert(fileexists(p)); #endif } else { std::unique_ptr da(fileSystemAccess->newdiraccess()); auto lp = LocalPath::fromAbsolutePath(p); if (!da->dopen(&lp, nullptr, false)) { throw std::runtime_error( "Cannot open existing mega API cache folder " + p + " please check permissions or delete it so a new one can be created"); } } return p; } bool SdkTest::WaitFor(const std::function& predicate, unsigned timeoutMs) { const unsigned sleepMs = 100; unsigned totalMs = 0; do { if (predicate()) return true; WaitMillisec(sleepMs); totalMs += sleepMs; } while (totalMs < timeoutMs); return false; } enum { USERALERT_ARRIVAL_MILLISEC = 1000 }; #ifdef _WIN32 #include "mega/autocomplete.h" #include #endif void cleanUp(::mega::MegaApi* megaApi, const fs::path &basePath); // helper functions and struct/classes namespace { bool buildLocalFolders(fs::path targetfolder, const string& prefix, int n, int recurselevel, int filesperfolder) { fs::path p = targetfolder / u8path_compat(prefix); if (!fs::create_directory(p)) return false; for (int i = 0; i < filesperfolder; ++i) { string filename = "file" + to_string(i) + "_" + prefix; fs::path fp = p / u8path_compat(filename); ofstream fs(fp/*, ios::binary*/); fs << filename; } if (recurselevel > 0) { for (int i = 0; i < n; ++i) { if (!buildLocalFolders(p, prefix + "_" + to_string(i), n, recurselevel - 1, filesperfolder)) return false; } } return true; } bool createLocalFile(fs::path path, const char *name, int byteSize = 0) { if (!name) { return false; } fs::path fp = path / u8path_compat(name); ofstream fs(fp/*, ios::binary*/); if (byteSize) { fs.seekp((byteSize << 10) - 1); } fs << name; return true; } // // Get a new endpoint name without conflicts with any running instances // under the following situations: // 1. Jenkins can run multiple test jobs at the same time // 2. A test job can run tests in parallel // Use current process ID so names are unique between different jobs (processes) // Use a static incremental counter so names are unique in the same job (process) std::string newEndpointName() { static std::atomic_int counter{0}; int current = counter++; std::ostringstream oss; oss << "test_integration_" << getCurrentPid() << "_" << current; return oss.str(); } std::string executableName(const std::string& name) { #ifdef WIN32 return name + ".exe"; #else return name; #endif } MegaApiTestPointer newMegaApi(const char* appKey, const char* basePath, const char* userAgent, unsigned workerThreadCount, const int clientType = MegaApi::CLIENT_TYPE_DEFAULT) { #ifdef ENABLE_ISOLATED_GFX const auto gfxworkerPath = sdk_test::getTestDataDir() / executableName("gfxworker"); const auto endpointName = newEndpointName(); std::unique_ptr provider{ MegaGfxProvider::createIsolatedInstance(endpointName.c_str(), gfxworkerPath.string().c_str()) }; return MegaApiTestPointer{new MegaApiTest(appKey, provider.get(), basePath, userAgent, workerThreadCount, clientType), MegaApiTestDeleter{endpointName}}; #else return MegaApiTestPointer{ new MegaApiTest(appKey, basePath, userAgent, workerThreadCount, clientType), MegaApiTestDeleter{""}}; #endif } enum class HasIcon { YES, NO }; void validateNotification(const MegaNotification* notification, int64_t id, HasIcon hasIcon) { ASSERT_EQ(notification->getID(), id); ASSERT_STRNE(notification->getTitle(), ""); ASSERT_STRNE(notification->getDescription(), ""); ASSERT_STRNE(notification->getImageName(), ""); if (hasIcon == HasIcon::NO) { ASSERT_STREQ(notification->getIconName(), ""); } else { ASSERT_STRNE(notification->getIconName(), ""); } ASSERT_STRNE(notification->getImagePath(), ""); ASSERT_NE(notification->getStart(), 0); ASSERT_NE(notification->getEnd(), 0); ASSERT_THAT(notification->getCallToAction1(), ::testing::NotNull()); ASSERT_NE(notification->getCallToAction1()->size(), 0); ASSERT_THAT(notification->getCallToAction2(), ::testing::NotNull()); ASSERT_NE(notification->getCallToAction2()->size(), 0); }; } namespace mega { std::ostream& operator<<(std::ostream& os, const ::mega::MegaNodeList& nodeList) { os << "["; for (int i = 0; i < nodeList.size(); i++) { const char* name = nodeList.get(i)->getName(); i == 0 ? (os << name) : (os << ", " << name); } os << "]"; return os; } } std::map gSessionIDs; MegaApiTest::MegaApiTest(const char* appKey, const char* basePath, const char* userAgent, unsigned workerThreadCount, const int clientType): MegaApi(appKey, basePath, userAgent, workerThreadCount, clientType) { } MegaApiTest::MegaApiTest(const char* appKey, MegaGfxProvider* provider, const char* basePath, const char* userAgent, unsigned workerThreadCount, const int clientType): MegaApi(appKey, provider, basePath, userAgent, workerThreadCount, clientType) { } MegaClient* MegaApiTest::getClient() { return pImpl->getMegaClient(); } void MegaApiTestDeleter::operator()(MegaApiTest* p) const { delete p; // Clean up the socket file if it has been created and only after MegaApiTest is deleted. // Reason: the GfxIsolatedProcess is desctructed in the subclass MegaApi // Another alernative is to clean up the socket file in the GfxIsolatedProcess destructor. // However it might clean up a socket file created by a new GfxIsolatedProcess is a same // name is used alghouth it seems be rare. #if !defined(WIN32) && defined(ENABLE_ISOLATED_GFX) if (mEndpointName.empty()) return; if (std::error_code errorCode = SocketUtils::removeSocketFile(mEndpointName)) { LOG_err << "Failed to remove socket path " << mEndpointName << ": " << errorCode.message(); } #endif } void SdkTest::SetUp() { SdkTestBase::SetUp(); } void SdkTest::TearDown() { out() << "Test done, teardown starts"; LOG_info << "# SdkTest::TearDown - resetting accounts to initial level"; if (!mAccountsRestorer.empty()) LOG_info << "## resetting " << mAccountsRestorer.size() << " accounts"; mAccountsRestorer.clear(); // do some cleanup LOG_info << "___ Cleaning up test (TearDown()) ___"; Cleanup(); for (unsigned i = 0; i < megaApi.size(); ++i) { releaseMegaApi(i); } out() << "Teardown done, test exiting"; } std::pair SdkTest::getTestSuiteAndName() const { const auto* testInfo = ::testing::UnitTest::GetInstance()->current_test_info(); if (testInfo == nullptr) { assert(testInfo && "This is expected to be called from a test"); return {}; } return {testInfo->test_suite_name(), testInfo->name()}; } std::string SdkTest::getLogPrefix() const { const auto [suite, name] = getTestSuiteAndName(); return suite + "." + name + " : "; } std::string SdkTest::getFilePrefix() const { const auto [suite, name] = getTestSuiteAndName(); return suite + "_" + name + "_"; } void SdkTest::cleanupPerApiBackupsMonitorMap(const unsigned i) { if (megaApi[i]) { mApi[i].clearBackupsMonitorMap(); } else { LOG_warn << "[SdkTest::Cleanup]: BackupsMonitorMap Could not be properly cleared as " "megaApi instance didn't exists at this point. It will be cleared when " "mApi instance is destroyed"; } } void SdkTest::Cleanup() { LOG_debug << "[SdkTest::Cleanup]"; std::set skipChats; mCleanupSuccess = true; cleanupLocalFiles(); for (unsigned nApi = 0; nApi < mApi.size(); ++nApi) { cleanupPerApiBackupsMonitorMap(nApi); if (!megaApi[nApi] || !megaApi[nApi]->isLoggedIn()) { continue; } cleanupCatchupWithApi(static_cast(nApi), cleanupCatchupTimeoutSecs); #ifdef ENABLE_CHAT cleanupSchedMeetings(nApi); cleanupChatLinks(nApi, skipChats); cleanupChatrooms(nApi); #endif #ifdef ENABLE_SYNC cleanupSyncs(nApi); #endif cleanupContactRequests(nApi); // we have deciced to remove contacts before removing shares to minimize the API_LOCKS due // to SDK implicit commands cleanupContacts(nApi); cleanupShares(nApi); cleanupNodeLinks(nApi); cleanupNodes(nApi); } LOG_debug << "[SdkTest::Cleanup]: " << (mCleanupSuccess ? "Finished successfully" : "Failed"); EXPECT_TRUE(mCleanupSuccess) << "[SdkTest::Cleanup]: Mark test as failed"; } void SdkTest::setTestAccountsToFree(unsigned int nApi) { const auto prefix = getLogPrefix(); LOG_info << prefix << "Account " << nApi; auto& client = *megaApi[nApi]; // Check account level const auto accLevelRes = getAccountLevel(client); ASSERT_EQ(result(accLevelRes), API_OK) << prefix << "getAccountLevel error"; // Already free if (value(accLevelRes).plan == MegaAccountDetails::ACCOUNT_TYPE_FREE) { return; } if (!gFreeAccounts) { mAccountsRestorer.push_back(accountLevelRestorer(megaApi, nApi)); } ASSERT_EQ(demoteToFree(client), API_OK) << prefix << "Account couldn't be reset to free"; // Need to refresh SDK's account status and let SDK knows the change ASSERT_EQ(result(getAccountDetails(client)), API_OK) << prefix << "getAccountDetails error"; } int SdkTest::getApiIndex(MegaApi* api) { int apiIndex = -1; for (int i = int(megaApi.size()); i--;) if (megaApi[static_cast(i)].get() == api) apiIndex = i; if (apiIndex == -1) { LOG_warn << "Instance of MegaApi not recognized"; // this can occur during MegaApi deletion due to callbacks on shutdown } return apiIndex; } bool SdkTest::getApiIndex(MegaApi* api, size_t& apindex) { for (size_t i = 0; i < megaApi.size(); i++) { if (megaApi[i].get() == api) { apindex = i; return true; } } LOG_warn << "Instance of MegaApi not recognized"; // this can occur during MegaApi deletion due to callbacks on shutdown return false; } void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e) { auto type = request->getType(); if (type == MegaRequest::TYPE_DELETE) { return; } int index = getApiIndex(api); if (index < 0) return; size_t apiIndex = static_cast(index); mApi[apiIndex].lastError = e->getErrorCode(); // there could be a race on these getting set? LOG_info << "lastError (by request) for MegaApi " << apiIndex << ": " << mApi[apiIndex].lastError; switch(type) { case MegaRequest::TYPE_GET_ATTR_USER: if (mApi[apiIndex].lastError == API_OK) { if (request->getParamType() == MegaApi::USER_ATTR_DEVICE_NAMES || request->getParamType() == MegaApi::USER_ATTR_ALIAS) { mApi[apiIndex].setAttributeValue(request->getName() ? request->getName() : ""); } else if (request->getParamType() == MegaApi::USER_ATTR_MY_BACKUPS_FOLDER) { mApi[apiIndex].lastSyncBackupId = request->getNodeHandle(); } else if (request->getParamType() == MegaApi::USER_ATTR_APPS_PREFS) { mApi[apiIndex].mStringMap.reset(request->getMegaStringMap()->copy()); } else if (request->getParamType() == MegaApi::USER_ATTR_CC_PREFS) { mApi[apiIndex].mStringMap.reset(request->getMegaStringMap()->copy()); } else if (request->getParamType() != MegaApi::USER_ATTR_AVATAR) { mApi[apiIndex].setAttributeValue(request->getText() ? request->getText() : ""); } } if (request->getParamType() == MegaApi::USER_ATTR_AVATAR) { if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].setAttributeValue("Avatar changed"); } if (mApi[apiIndex].lastError == API_ENOENT) { mApi[apiIndex].setAttributeValue("Avatar not found"); } } break; #ifdef ENABLE_CHAT case MegaRequest::TYPE_CHAT_CREATE: if (mApi[apiIndex].lastError == API_OK) { MegaTextChat *chat = request->getMegaTextChatList()->get(0)->copy(); mApi[apiIndex].chatid = chat->getHandle(); mApi[apiIndex].chats[mApi[apiIndex].chatid].reset(chat); } break; case MegaRequest::TYPE_CHAT_INVITE: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].chatid = request->getNodeHandle(); if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end()) { MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid].get(); MegaHandle uh = request->getParentHandle(); int priv = request->getAccess(); unique_ptr privsbuf{new userpriv_vector}; const MegaTextChatPeerList *privs = chat->getPeerList(); if (privs) { for (int i = 0; i < privs->size(); i++) { if (privs->getPeerHandle(i) != uh) { privsbuf->push_back(userpriv_pair(privs->getPeerHandle(i), (privilege_t) privs->getPeerPrivilege(i))); } } } privsbuf->push_back(userpriv_pair(uh, (privilege_t) priv)); privs = new MegaTextChatPeerListPrivate(privsbuf.get()); chat->setPeerList(privs); delete privs; } else { LOG_err << "Trying to remove a peer from unknown chat"; } } break; case MegaRequest::TYPE_CHAT_REMOVE: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].chatid = request->getNodeHandle(); if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end()) { MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid].get(); MegaHandle uh = request->getParentHandle(); std::unique_ptr privsbuf{new userpriv_vector}; const MegaTextChatPeerList *privs = chat->getPeerList(); if (privs) { for (int i = 0; i < privs->size(); i++) { if (privs->getPeerHandle(i) != uh) { privsbuf->push_back(userpriv_pair(privs->getPeerHandle(i), (privilege_t) privs->getPeerPrivilege(i))); } } } privs = new MegaTextChatPeerListPrivate(privsbuf.get()); chat->setPeerList(privs); delete privs; } else { LOG_err << "Trying to remove a peer from unknown chat"; } } break; case MegaRequest::TYPE_CHAT_URL: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].setChatLink(request->getLink()); } break; #endif case MegaRequest::TYPE_CREATE_ACCOUNT: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].setSid(request->getSessionKey()); } break; case MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].setStringLists(request->getMegaStringListMap()->copy()); } break; case MegaRequest::TYPE_FOLDER_INFO: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].mFolderInfo.reset(request->getMegaFolderInfo()->copy()); } break; case MegaRequest::TYPE_FETCH_TIMEZONE: mApi[apiIndex].tzDetails.reset(mApi[apiIndex].lastError == API_OK ? request->getMegaTimeZoneDetails()->copy() : nullptr); break; case MegaRequest::TYPE_GET_USER_EMAIL: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].email = request->getEmail(); } break; case MegaRequest::TYPE_ACCOUNT_DETAILS: mApi[apiIndex].accountDetails.reset(mApi[apiIndex].lastError == API_OK ? request->getMegaAccountDetails() : nullptr); break; case MegaRequest::TYPE_BACKUP_PUT: mApi[apiIndex].setBackupId(request->getParentHandle()); break; case MegaRequest::TYPE_GET_ATTR_NODE: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].setFavNodes(request->getMegaHandleList()->copy()); } break; case MegaRequest::TYPE_GET_PRICING: mApi[apiIndex].mMegaPricing.reset(mApi[apiIndex].lastError == API_OK ? request->getPricing() : nullptr); mApi[apiIndex].mMegaCurrency.reset(mApi[apiIndex].lastError == API_OK ? request->getCurrency() : nullptr); break; #ifdef ENABLE_CHAT case MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING: if (mApi[apiIndex].lastError == API_OK && request->getMegaScheduledMeetingList() && request->getMegaScheduledMeetingList()->size() == 1) { const auto sched = request->getMegaScheduledMeetingList()->at(0); mApi[apiIndex].chatid = sched->chatid(); mApi[apiIndex].schedId = sched->schedId(); mApi[apiIndex].schedUpdated = true; } break; case MegaRequest::TYPE_DEL_SCHEDULED_MEETING: if (mApi[apiIndex].lastError == API_OK) { mApi[apiIndex].schedUpdated = true; mApi[apiIndex].schedId = request->getParentHandle(); } break; #endif } // set this flag always the latest, since it is used to unlock the wait // for requests results, so we want data to be collected first mApi[apiIndex].requestFlags[request->getType()] = true; } void SdkTest::onTransferStart(MegaApi*, MegaTransfer* transfer) { onTransferStart_progress = transfer->getTransferredBytes(); if (onTransferStartCustomCb) { onTransferStartCustomCb(transfer); } } void SdkTest::onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e) { int tempApiIndex = getApiIndex(api); if (tempApiIndex < 0) return; size_t apiIndex = static_cast(tempApiIndex); mApi[apiIndex].transferFlags[transfer->getType()] = true; mApi[apiIndex].lastError = e->getErrorCode(); // todo: change the rest of the transfer test code // to use lastTransferError instead. mApi[apiIndex].lastTransferError = e->getErrorCode(); // there could be a race on these getting set? LOG_info << "lastError (by transfer) for MegaApi " << apiIndex << ": " << mApi[apiIndex].lastError; onTranferFinishedCount += 1; // Transfer stats. // We need to access the MegaTransferPrivate because the stats // are not part of the public interface so we need to retrieve the Transfer object. if (auto transferPrivate = dynamic_cast(transfer)) { auto internalTransfer = transferPrivate->getTransfer(); if (internalTransfer && internalTransfer->slot) { onTransferFinish_transferStats = internalTransfer->slot->tsStats; LOG_debug << "[SdkTest::onTransferFinish] Stats: FailedRequestRatio = " << onTransferFinish_transferStats.failedRequestRatio << " [totalRequests = " << onTransferFinish_transferStats.numTotalRequests << ", failedRequests = " << onTransferFinish_transferStats.numFailedRequests << "]"; } } } void SdkTest::onTransferUpdate(MegaApi*, MegaTransfer* transfer) { onTransferUpdate_progress = transfer->getTransferredBytes(); onTransferUpdate_filesize = transfer->getTotalBytes(); } void SdkTest::onAccountUpdate(MegaApi* api) { int apiIndex = getApiIndex(api); if (apiIndex < 0) return; mApi[static_cast(apiIndex)].accountUpdated = true; } void SdkTest::onUsersUpdate(MegaApi* api, MegaUserList *users) { int apiIndex = getApiIndex(api); if (apiIndex < 0) return; if (!users) return; auto& currentPerApi = mApi[static_cast(apiIndex)]; for (int i = 0; i < users->size(); i++) { MegaUser *u = users->get(i); if (u->hasChanged(MegaUser::CHANGE_TYPE_AVATAR) || u->hasChanged(MegaUser::CHANGE_TYPE_FIRSTNAME) || u->hasChanged(MegaUser::CHANGE_TYPE_LASTNAME)) { currentPerApi.userUpdated = true; if (u->hasChanged(MegaUser::CHANGE_TYPE_FIRSTNAME) && !u->isOwnChange()) { currentPerApi.userFirstNameUpdated = true; } } else { // Contact is removed from main account currentPerApi.requestFlags[MegaRequest::TYPE_REMOVE_CONTACT] = true; currentPerApi.userUpdated = true; } if (u->hasChanged(MegaUser::CHANGE_TYPE_RECENT_CLEAR_TIMESTAMP)) { ++currentPerApi.recentClearTimeUpdatedCount; } currentPerApi.callCustomCallbackCheck(u->getHandle()); } } void SdkTest::onNodesUpdate(MegaApi* api, MegaNodeList *nodes) { size_t apiIndex = 0; if (getApiIndex(api, apiIndex) && mApi[apiIndex].mOnNodesUpdateCompletion) { mApi[apiIndex].mOnNodesUpdateCompletion(apiIndex, nodes); // nodes owned by SDK and valid until return } } void SdkTest::onSetsUpdate(MegaApi* api, MegaSetList* sets) { int apiIndex = getApiIndex(api); if (apiIndex < 0 || !sets || !sets->size()) return; mApi[static_cast(apiIndex)].setUpdated = true; } void SdkTest::onSetElementsUpdate(MegaApi* api, MegaSetElementList* elements) { int apiIndex = getApiIndex(api); if (apiIndex < 0 || !elements || !elements->size()) return; for (unsigned int i = 0; i < elements->size(); ++i) { if (!elements->get(i)->getChanges()) { LOG_err << "GlobalListener::onSetElementsUpdate no change received for elements[" << i << "]"; return; } } mApi[static_cast(apiIndex)].setElementUpdated = true; } void SdkTest::onContactRequestsUpdate(MegaApi* api, MegaContactRequestList*) { int apiIndex = getApiIndex(api); if (apiIndex < 0) return; mApi[static_cast(apiIndex)].contactRequestUpdated = true; } void SdkTest::onUserAlertsUpdate(MegaApi* api, MegaUserAlertList* alerts) { int apiIndex = getApiIndex(api); if (apiIndex < 0) return; mApi[static_cast(apiIndex)].userAlertList.reset(alerts ? alerts->copy() : nullptr); mApi[static_cast(apiIndex)].userAlertsUpdated = true; } #ifdef ENABLE_CHAT void SdkTest::onChatsUpdate(MegaApi *api, MegaTextChatList *chats) { int tempApiIndex = getApiIndex(api); if (tempApiIndex < 0) return; size_t apiIndex = static_cast(tempApiIndex); MegaTextChatList *list = NULL; if (chats) { list = chats->copy(); } else { list = megaApi[apiIndex]->getChatList(); } for (int i = 0; i < list->size(); i++) { handle chatid = list->get(static_cast(i))->getHandle(); if (mApi[apiIndex].chats.find(chatid) != mApi[apiIndex].chats.end()) { mApi[apiIndex].chats[chatid].reset(list->get(static_cast(i))->copy()); } else { mApi[apiIndex].chats[chatid].reset(list->get(static_cast(i))->copy()); } } delete list; mApi[apiIndex].chatUpdated = true; mApi[apiIndex].callCustomCallbackCheck(mApi[apiIndex].megaApi->getMyUserHandleBinary()); } void SdkTest::cleanupChatLinks(const unsigned int nApi, std::set& skipChats) { const std::string prefix{"SdkTest::Cleanup(RemoveChatLinks)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; unique_ptr chats(megaApi[nApi]->getChatList()); for (int i = 0u; i < chats->size(); ++i) { const MegaTextChat* c = chats->get(static_cast(i)); if (!c || skipChats.find(c->getHandle()) != skipChats.end()) { continue; } const auto numPeers = c->getPeerList() ? c->getPeerList()->size() : 0; if (auto processChat = c->isPublicChat() && c->getOwnPrivilege() == PRIV_MODERATOR && (numPeers || c->isGroup()); !processChat) { continue; } RequestTracker rt(megaApi[nApi].get()); megaApi[nApi]->chatLinkQuery(c->getHandle(), &rt); if (const auto e = rt.waitForResult(); e == API_OK) { RequestTracker rtD(megaApi[nApi].get()); megaApi[nApi]->chatLinkDelete(c->getHandle(), &rtD); if (auto errCld = rtD.waitForResult(); errCld != API_OK && errCld != API_ENOENT && errCld != API_EACCESS) { const string errDetails = "Error deleting chatlink for chat (" + string{Base64Str(c->getHandle())} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), errCld, localCleanupSuccess); } } else { if (e == API_ENOENT || e == API_EACCESS) { skipChats.emplace(c->getHandle()); continue; } const string errDetails = "Error getting chat link for chat (" + string{Base64Str(c->getHandle())} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), e, true /*localCleanupSuccess*/); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupChatrooms(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(RemoveChatrooms)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; unique_ptr chats(megaApi[nApi]->getChatList()); for (int i = 0u; i < chats->size(); ++i) { const MegaTextChat* c = chats->get(static_cast(i)); if (!c || !c->isGroup() || c->getOwnPrivilege() < PRIV_RO) { continue; } RequestTracker rt(megaApi[nApi].get()); megaApi[nApi]->removeFromChat(c->getHandle(), INVALID_HANDLE, &rt); if (const auto e = rt.waitForResult(); e != API_OK && e != API_ENOENT && e != API_EACCESS) { const string errDetails = "Error removing myself from " + string(c->isGroup() ? "group" : "1on1") + " chat (" + string{Base64Str(c->getHandle())} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), e, localCleanupSuccess); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::createChat(bool group, MegaTextChatPeerList* peers, bool isPublicChat, int timeout) { size_t apiIndex = 0; PerApi& target = mApi[apiIndex]; target.requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false; if (!isPublicChat) { megaApi[apiIndex]->createChat(group, peers); } else { const std::unique_ptr userKeyMap(MegaStringMap::createInstance()); const std::unique_ptr ownHandleB64{megaApi[apiIndex]->getMyUserHandle()}; userKeyMap->set(ownHandleB64.get(), Base64::btoa("dummy_owner_key").data()); const auto peerHandleB64 = Base64Str(peers->getPeerHandle(0)); userKeyMap->set(peerHandleB64, Base64::btoa("dummy_peer_key").data()); megaApi[apiIndex]->createPublicChat(peers, userKeyMap.get()); } waitForResponse(&target.requestFlags[MegaRequest::TYPE_CHAT_CREATE], static_cast(timeout)); if (timeout) { ASSERT_TRUE(target.requestFlags[MegaRequest::TYPE_CHAT_CREATE]) << "Chat creation not finished after " << timeout << " seconds"; } ASSERT_EQ(API_OK, target.lastError) << "Chat creation failed (error: " << mApi[static_cast(apiIndex)].lastError << ")"; } void SdkTest::testChat(bool isPublicChat) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // --- Send a new contact request --- string message = "Hi contact. This is a testing message"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; // if there were too many invitations within a short period of time, the invitation can be // rejected by the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in // the last 50 days) // --- Accept a contact invitation --- ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated)) // at the target side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); // --- Check list of available chats --- (fetch is done at SetUp()) size_t numChats = mApi[0].chats.size(); // permanent chats cannot be deleted, so they're kept forever // --- Create a group chat --- handle h = megaApi[1]->getMyUserHandleBinary(); MegaTextChatPeerList* peers = MegaTextChatPeerList::createInstance(); // new MegaTextChatPeerListPrivate(); peers->addPeer(h, PRIV_STANDARD); bool group = true; mApi[1].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false; ASSERT_NO_FATAL_FAILURE(createChat(group, peers, isPublicChat)); ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE])) << "Cannot create a new chat"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Chat creation failed (error: " << mApi[0].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[1].chatUpdated)) // at the target side (auxiliar account) << "Chat update not received after " << maxTimeout << " seconds"; MegaHandle chatid = mApi[0].chatid; // set at onRequestFinish() of chat creation request delete peers; // check the new chat information ASSERT_EQ(mApi[0].chats.size(), ++numChats) << "Unexpected received number of chats"; ASSERT_TRUE(mApi[1].chatUpdated) << "The peer didn't receive notification of the chat creation"; // --- Remove a peer from the chat --- mApi[1].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE] = false; megaApi[0]->removeFromChat(chatid, h); ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE])) << "Chat remove failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Removal of chat peer failed (error: " << mApi[0].lastError << ")"; int numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0; ASSERT_EQ(numpeers, 0) << "Wrong number of peers in the list of peers"; ASSERT_TRUE(waitForResponse(&mApi[1].chatUpdated)) // at the target side (auxiliar account) << "Didn't receive notification of the peer removal after " << maxTimeout << " seconds"; // --- Invite a contact to a chat --- mApi[1].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE] = false; if (!isPublicChat) { megaApi[0]->inviteToChat(chatid, h, PRIV_STANDARD); } else { megaApi[0]->inviteToPublicChat(chatid, h, PRIV_STANDARD, "test_key"); } ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE])) << "Chat invitation failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Invitation of chat peer failed (error: " << mApi[0].lastError << ")"; numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0; ASSERT_EQ(numpeers, 1) << "Wrong number of peers in the list of peers"; ASSERT_TRUE(waitForResponse(&mApi[1].chatUpdated)) // at the target side (auxiliar account) << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds"; // --- Get the user-specific URL for the chat --- mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL] = false; megaApi[0]->getUrlChat(chatid); ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL])) << "Retrieval of chat URL failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Retrieval of chat URL failed (error: " << mApi[0].lastError << ")"; // --- Update Permissions of an existing peer in the chat mApi[1].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS] = false; megaApi[0]->updateChatPermissions(chatid, h, PRIV_RO); ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS])) << "Update chat permissions failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Update of chat permissions failed (error: " << mApi[0].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[1].chatUpdated)) // at the target side (auxiliar account) << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds"; // --- Archive the chat mApi[0].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_ARCHIVE] = false; ASSERT_NO_FATAL_FAILURE(megaApi[0]->archiveChat(chatid, 1)); ASSERT_EQ(API_OK, mApi[0].lastError) << "Archive failed (error: " << mApi[0].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_ARCHIVE])) << "Cannot archive a chat"; ASSERT_TRUE(waitForResponse(&mApi[0].chatUpdated)) << "Chat update not received after " << maxTimeout << " seconds"; if (!isPublicChat) { // --- Create 1on1 chat with self megaApi[0]->changeApiUrl("https://staging.api.mega.co.nz/"); mApi[0].chatUpdated = false; mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false; ASSERT_NO_FATAL_FAILURE(createChat(false, nullptr)); ASSERT_TRUE(waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE])) << "Cannot create a new chat with self"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Chat-with-self creation failed (error: " << mApi[0].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[0].chatUpdated)) // at the target side (auxiliar account) << "Chat update not received after " << maxTimeout << " seconds"; } } /** * @brief Aux function to get a set of handles from a list of peers */ static std::set peerListToHandleSet(const MegaTextChatPeerList *peers) { std::set result; for (int i = 0 ; i < peers->size(); ++i) { result.insert(peers->getPeerHandle(i)); } return result; } MegaHandle SdkTest::createChatWithChecks(const unsigned int creatorIndex, const std::vector& invitedIndices, const bool group, const bool isPublicChat, const unsigned int timeout_sec) { std::unique_ptr invitedPeers(MegaTextChatPeerList::createInstance()); std::set allParticipantsHandles{ mApi[creatorIndex].megaApi->getMyUserHandleBinary()}; for (auto ind: invitedIndices) { auto uh = mApi[ind].megaApi->getMyUserHandleBinary(); invitedPeers->addPeer(uh, PRIV_STANDARD); allParticipantsHandles.insert(uh); } // Function to check that a chat is created with the given specs auto isChatOK = [group, &allParticipantsHandles](const MegaTextChat& chat, const MegaHandle receiverHandle) -> bool { if (chat.isGroup() != group) { return false; } const MegaTextChatPeerList* receivedPeers = chat.getPeerList(); if (!receivedPeers || static_cast(receivedPeers->size()) != allParticipantsHandles.size() - 1) { return false; } auto participantsHandle = peerListToHandleSet(receivedPeers); participantsHandle.insert(receiverHandle); return participantsHandle == allParticipantsHandles; }; // Register a callback and a boolean for each participant std::vector>, bool>> customChecksAndResults( allParticipantsHandles.size()); std::vector allParticipantsIndices(invitedIndices); allParticipantsIndices.push_back(creatorIndex); for (unsigned int i = 0; i < allParticipantsIndices.size(); ++i) { customChecksAndResults.push_back({std::make_shared>(), false}); auto userInd = allParticipantsIndices[i]; MegaHandle receiverHandle = mApi[userInd].megaApi->getMyUserHandleBinary(); auto customCheck = customChecksAndResults.back().first; *customCheck = [this, userInd, i, receiverHandle, &customChecksAndResults, &isChatOK]() { const auto& chats = mApi[userInd].chats; customChecksAndResults[i].second = std::any_of( chats.begin(), chats.end(), [receiverHandle, &isChatOK](const auto& pair) { return isChatOK(*pair.second, receiverHandle); }); }; mApi[userInd].customCallbackCheck[receiverHandle] = customCheck; } // Check that the chatid is properly set in the onRequestFinish callback. Set initial value mApi[creatorIndex].chatid = INVALID_HANDLE; if (!isPublicChat) { megaApi[creatorIndex]->createChat(group, invitedPeers.get()); } else { const std::unique_ptr userKeyMap(MegaStringMap::createInstance()); const std::unique_ptr ownHandleB64{megaApi[creatorIndex]->getMyUserHandle()}; userKeyMap->set(ownHandleB64.get(), Base64::btoa("dummy_owner_key").data()); const auto peerHandleB64 = Base64Str(invitedPeers->getPeerHandle(0)); userKeyMap->set(peerHandleB64, Base64::btoa("dummy_peer_key").data()); megaApi[creatorIndex]->createPublicChat(invitedPeers.get(), userKeyMap.get()); } bool hasRequestFinished = waitForEvent( [this, creatorIndex]() { return mApi[creatorIndex].chatid != INVALID_HANDLE; }, timeout_sec); if (!hasRequestFinished) { EXPECT_TRUE(false) << "Chat creation onRequestFinish not called after " << timeout_sec << "seconds"; return INVALID_HANDLE; } for (unsigned int i = 0; i < allParticipantsIndices.size(); ++i) { if (!waitForResponse(&customChecksAndResults[i].second, timeout_sec)) { EXPECT_TRUE(false) << "Chat update not received for user " << allParticipantsIndices[i] << " after " << timeout_sec << " seconds"; return INVALID_HANDLE; } } return mApi[creatorIndex].chatid; } MegaHandle SdkTest::getCommander() { MegaHandle handle; // In open chats the access is granted to a special user (API_USER / COMMANDER = gTxFhlOd_LQ) in // order to allow any current/future participant and/or any previewer to access to the node. std::string commander{"gTxFhlOd_LQ"}; commander = Base64::atob(commander); memcpy(&handle, commander.data(), sizeof(handle)); return handle; } void SdkTest::testGiveRemoveChatAccess(bool isPublicChat) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); const unsigned int host = 0; const unsigned int guest = 1; mApi[host].chats.clear(); mApi[guest].chats.clear(); // Send and accept a new contact request string message = "Hi contact. This is a testing message"; inviteTestAccount(host, guest, message); // Create chat between new contacts MegaHandle chatId = createChatWithChecks(0, {1}, true, isPublicChat); ASSERT_NE(chatId, INVALID_HANDLE) << "Something went wrong when creating the group chat room"; // Update test file ASSERT_TRUE(createFile(PUBLICFILE.c_str(), false)) << "Couldn't create " << PUBLICFILE; std::unique_ptr rootnode{megaApi[host]->getRootNode()}; ASSERT_NE(rootnode.get(), nullptr); MegaHandle fileHandle = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(host, &fileHandle, PUBLICFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr fileNode(megaApi[host]->getNodeByHandle(fileHandle)); // Grant access to guest MegaHandle targetUserHandle = isPublicChat ? getCommander() : megaApi[guest]->getMyUser()->getHandle(); ASSERT_FALSE(megaApi[host]->hasAccessToAttachment(chatId, fileHandle, targetUserHandle)); RequestTracker requestTrackerGrantAccess(megaApi[host].get()); megaApi[host]->grantAccessInChat(chatId, fileNode.get(), targetUserHandle, &requestTrackerGrantAccess); ASSERT_EQ(API_OK, requestTrackerGrantAccess.waitForResult()); ASSERT_TRUE(megaApi[host]->hasAccessToAttachment(chatId, fileHandle, targetUserHandle)); // Remove access to guest RequestTracker requestTrackerRemoveAccess(megaApi[host].get()); megaApi[host]->removeAccessInChat(chatId, fileNode.get(), targetUserHandle, &requestTrackerRemoveAccess); ASSERT_EQ(API_OK, requestTrackerRemoveAccess.waitForResult()); ASSERT_FALSE(megaApi[host]->hasAccessToAttachment(chatId, fileHandle, targetUserHandle)); } #endif void SdkTest::onEvent(MegaApi* s, MegaEvent* event) { int index = getApiIndex(s); if (index >= 0) // it can be -1 when tests are being destroyed { mApi[static_cast(index)].receiveEvent(event); LOG_debug << index << " Received event " << event->getType(); } } void SdkTest::login(unsigned int apiIndex, int timeout) { // Only for instances already configured. ASSERT_GT(mApi.size(), apiIndex); ASSERT_FALSE(mApi[apiIndex].email.empty()); ASSERT_FALSE(mApi[apiIndex].pwd.empty()); auto tracker{ asyncRequestLogin(apiIndex, mApi[apiIndex].email.c_str(), mApi[apiIndex].pwd.c_str())}; auto loginResult{tracker->waitForResult(timeout)}; ASSERT_EQ(API_OK, loginResult) << "Login failure account #" << apiIndex << ": " << MegaError::getErrorString(loginResult); } void SdkTest::fetchnodes(unsigned int apiIndex, int timeout) { RequestTracker rt(megaApi[apiIndex].get()); mApi[apiIndex].megaApi->fetchNodes(&rt); ASSERT_EQ(API_OK, rt.waitForResult(timeout)) << "Fetchnodes failed or took more than " << timeout << " seconds"; } void SdkTest::logout(unsigned int apiIndex, [[maybe_unused]] bool keepSyncConfigs, int timeout) { mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT] = false; #ifdef ENABLE_SYNC mApi[apiIndex].megaApi->logout(keepSyncConfigs, this); #else mApi[apiIndex].megaApi->logout(this); #endif gSessionIDs[apiIndex] = "invalid"; EXPECT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT], static_cast(timeout))) << "Logout failed after " << timeout << " seconds"; // if the connection was closed before the response of the request was received, the result is ESID if (mApi[apiIndex].lastError == API_ESID) mApi[apiIndex].lastError = API_OK; EXPECT_EQ(API_OK, mApi[apiIndex].lastError) << "Logout failed (error: " << mApi[apiIndex].lastError << ")"; } char* SdkTest::dumpSession(unsigned apiIndex) { return megaApi[apiIndex]->dumpSession(); } void SdkTest::locallogout(unsigned apiIndex) { auto logoutErr = doRequestLocalLogout(apiIndex); ASSERT_EQ(API_OK, logoutErr) << "Local logout failed (error: " << logoutErr << ")"; } void SdkTest::resumeSession(const char *session, unsigned apiIndex) { ASSERT_EQ(API_OK, synchronousFastLogin(apiIndex, session, this)) << "Resume session failed (error: " << mApi[apiIndex].lastError << ")"; } void SdkTest::purgeTree(unsigned int apiIndex, MegaNode *p, bool depthfirst) { MegaHandle owner = megaApi[apiIndex]->getMyUserHandleBinary(); std::unique_ptr children{megaApi[apiIndex]->getChildren(p)}; for (int i = 0; i < children->size(); i++) { MegaNode *n = children->get(i); // removing the folder removes the children anyway if (depthfirst && n->isFolder()) purgeTree(apiIndex, n); if (owner != n->getOwner()) continue; string nodepath = n->getName() ? n->getName() : ""; auto result = synchronousRemove(apiIndex, n); if (result == API_EEXIST || result == API_ENOENT) { LOG_warn << "node " << nodepath << " was already removed in api " << apiIndex << ", detected by error code " << result; result = API_OK; } ASSERT_EQ(API_OK, result) << "API " << apiIndex << ": Failed to remove node " << nodepath; } } void SdkTest::cleanupContacts(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(RemoveContacts)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; auto myEmail(std::unique_ptr{megaApi[nApi]->getMyEmail()}); if (!myEmail || !std::strlen(myEmail.get())) { const string errDetails = "Error retrieving email for own account(" + string{Base64Str(megaApi[nApi]->getMyUserHandleBinary())} + ")"; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), API_EINTERNAL, true /*localCleanupSuccess*/); return; } const string myEmailStr{myEmail.get()}; std::unique_ptr contacts{megaApi[nApi]->getContacts()}; for (int i = 0; i < contacts->size(); i++) { const auto contactEmail = contacts->get(i)->getEmail(); // sometimes the email is an empty string (!) if (!contactEmail || !*contactEmail) { continue; } if (areCredentialsVerified(nApi, contactEmail)) { resetCredentials(nApi, contactEmail); } const string contactEmailStr{contactEmail}; if (contacts->get(i)->getVisibility() == MegaUser::VISIBILITY_HIDDEN) continue; if (const auto result = synchronousRemoveContact(nApi, contacts->get(i)); result != API_OK && result != API_EEXIST) { const string errDetails = "Could not remove contact (" + contactEmailStr + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), result, localCleanupSuccess); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupShares(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(RemoveShares)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; auto myEmail(std::unique_ptr{megaApi[nApi]->getMyEmail()}); if (!myEmail || !std::strlen(myEmail.get())) { const string errDetails = "Error retrieving email for own account(" + string{Base64Str(megaApi[nApi]->getMyUserHandleBinary())} + ")"; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), API_EINTERNAL, true /*localCleanupSuccess*/); return; } const string myEmailStr{myEmail.get()}; // Delete any inshares unique_ptr inshares(megaApi[nApi]->getInSharesList()); for (int i = 0; i < inshares->size(); ++i) { LOG_debug << prefix << "megaApi[" << nApi << "] [InShare = " << i << "] Inshare detected!"; auto inshare = inshares->get(i); if (!inshare) { LOG_debug << prefix << "megaApi[" << nApi << "] [InShare = " << i << "] MegaShare object is null, skipping..."; continue; } if (auto email = inshare->getUser(); email) { LOG_debug << prefix << "megaApi[" << nApi << "] [InShare = " << i << "] Removing inshare's contact ('" << string(myEmailStr + email) << "')..."; if (unique_ptr shareUser(megaApi[nApi]->getContact(email)); shareUser) { if (auto result = synchronousRemoveContact(nApi, shareUser.get()); result != API_OK && result != API_EEXIST) { const string errDetails = "[Inshare = " + std::to_string(i) + "] Error removing inshare's contact (" + std::string{email} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), result, localCleanupSuccess); } } } LOG_debug << prefix << "megaApi[" << nApi << "] [InShare = " << i << "] Removing inshare..."; if (unique_ptr n(megaApi[nApi]->getNodeByHandle(inshare->getNodeHandle())); n) { RequestTracker rt(megaApi[nApi].get()); megaApi[nApi]->remove(n.get(), &rt); if (const auto res = rt.waitForResult(300); res != API_OK && res != API_EACCESS) { const string errDetails = "Removal of inshare folder (" + string{Base64Str(n->getHandle())} + +") failed or took more than 5 minutes"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), res, localCleanupSuccess); } } } // Delete any outshares unique_ptr outshares(megaApi[nApi]->getOutShares()); for (int i = 0; i < outshares->size(); ++i) { LOG_debug << prefix << "megaApi[" << nApi << "] [OutShare = " << i << "] OutShare detected!"; auto os = outshares->get(i); if (!os) { LOG_debug << prefix << "megaApi[" << nApi << "] [OutShare = " << i << "] MegaShare object is null, skipping..."; continue; } if (auto email = os->getUser()) { LOG_debug << prefix << "megaApi[" << nApi << "] [OutShare = " << i << "] Removing outshare's contact ('" << string(myEmailStr + email) << "')..."; if (unique_ptr shareUser(megaApi[nApi]->getContact(email)); shareUser) { auto result = synchronousRemoveContact(nApi, shareUser.get()); if (result != API_OK && result != API_EEXIST) { const string errDetails = "Removal of outshare's contact (" + string{email} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), result, localCleanupSuccess); } } } LOG_debug << prefix << "megaApi[" << nApi << "] [OutShare = " << i << "] Removing outshare..."; if (unique_ptr n(megaApi[nApi]->getNodeByHandle(os->getNodeHandle())); n) { RequestTracker rt(megaApi[nApi].get()); megaApi[nApi]->share(n.get(), os->getUser(), MegaShare::ACCESS_UNKNOWN, &rt); if (const auto res = rt.waitForResult(300); res != API_OK && res != API_ENOENT) { const string errDetails = "Removal of outshare folder (" + string{Base64Str(n->getHandle())} + +") failed or took more than 5 minutes"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), res, localCleanupSuccess); } } } // Delete Sets and their public links unique_ptr sets(megaApi[nApi]->getSets()); for (unsigned i = 0u; i < sets->size(); ++i) { const MegaSet* s = sets->get(i); if (s->isExported()) { if (const auto resDisable = doDisableExportSet(nApi, s->id()); resDisable != API_OK) { const string errDetails = "Stop sharing a set (" + std::to_string(i) + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), resDisable, localCleanupSuccess); } } if (const auto resRemove = doRemoveSet(nApi, s->id()); resRemove != API_OK) { const string errDetails = "Removing a set (" + std::to_string(i) + ")"; bool iterationCleanupSuccess{true}; if (resRemove != API_EACCESS) { localCleanupSuccess = iterationCleanupSuccess = false; } printCleanupErrMsg(prefix, errDetails, static_cast(nApi), resRemove, iterationCleanupSuccess); } } // finally, double check we got rid of all inshares and outshares if (!WaitFor( [this, nApi]() { return unique_ptr(megaApi[nApi]->getOutShares())->size() == 0; }, 20 * 1000)) { localCleanupSuccess = false; printCleanupErrMsg(prefix, "Some outshares were not removed", static_cast(nApi), API_EINTERNAL, localCleanupSuccess); } if (!WaitFor( [this, nApi]() { return unique_ptr(megaApi[nApi]->getPendingOutShares())->size() == 0; }, 20 * 1000)) { localCleanupSuccess = false; printCleanupErrMsg(prefix, "Some pending outshares were not removed", static_cast(nApi), API_EINTERNAL, localCleanupSuccess); } if (!WaitFor( [this, nApi]() { return unique_ptr(megaApi[nApi]->getUnverifiedOutShares())->size() == 0; }, 20 * 1000)) { localCleanupSuccess = false; printCleanupErrMsg(prefix, "Some unverified outshares were not removed", static_cast(nApi), API_EINTERNAL, localCleanupSuccess); } if (!WaitFor( [this, nApi]() { return unique_ptr(megaApi[nApi]->getUnverifiedInShares())->size() == 0; }, 20 * 1000)) { localCleanupSuccess = false; printCleanupErrMsg(prefix, "Some unverified inshares were not removed", static_cast(nApi), API_EINTERNAL, localCleanupSuccess); } if (!WaitFor( [this, nApi]() { return unique_ptr(megaApi[nApi]->getInSharesList())->size() == 0; }, 20 * 1000)) { localCleanupSuccess = false; printCleanupErrMsg(prefix, "Some inshares were not removed", static_cast(nApi), API_EINTERNAL, localCleanupSuccess); } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupNodeLinks(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(Remove node links)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; unique_ptr nodeLinks(megaApi[nApi]->getPublicLinks()); for (int i = 0; i < nodeLinks->size(); ++i) { if (auto res = doDisableExport(nApi, nodeLinks->get(i)); res != API_OK) { const string errDetails = "Disabling node public link (" + std::to_string(i) + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(i), res, localCleanupSuccess); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupNodes(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(Remove nodes)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; // Remove nodes in Cloud & Rubbish purgeTree(nApi, std::unique_ptr{megaApi[nApi]->getRootNode()}.get(), false); purgeTree(nApi, std::unique_ptr{megaApi[nApi]->getRubbishNode()}.get(), false); #ifdef ENABLE_SYNC purgeVaultTree(nApi, std::unique_ptr{megaApi[nApi]->getVaultNode()}.get()); #endif auto getFolderInfo = [this, prefix, &localCleanupSuccess](unsigned nApi, MegaNode* n, uint64_t& nodesIn) { if (auto res = synchronousFolderInfo(nApi, n); res != MegaError::API_OK && res != MegaError::API_ENOENT) { const string errDetails = "Cannot get Folder Info for rootnode"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), res, localCleanupSuccess); } else { nodesIn = static_cast(mApi[nApi].mFolderInfo->getNumFiles() + mApi[nApi].mFolderInfo->getNumFolders() + mApi[nApi].mFolderInfo->getNumVersions()); } }; // Some tests finish logged in but without call to fetch nodes root nodes are undefined // yet uint64_t nodesInRoot = 0; if (std::unique_ptr rootNode(megaApi[nApi]->getRootNode()); rootNode) { getFolderInfo(nApi, rootNode.get(), nodesInRoot); } uint64_t nodesInRubbishBin = 0; if (std::unique_ptr rubbishbinNode(megaApi[nApi]->getRubbishNode()); rubbishbinNode) { getFolderInfo(nApi, rubbishbinNode.get(), nodesInRubbishBin); } uint64_t nodesInVault = 0; if (std::unique_ptr vaultNode(megaApi[nApi]->getVaultNode()); vaultNode) { getFolderInfo(nApi, vaultNode.get(), nodesInVault); } if (nodesInRoot > 0 || nodesInRubbishBin > 0 || nodesInVault > 0) { LOG_warn << "Clean up for instance " << nApi << " hasn't finished properly. Nodes at root node: " << nodesInRoot << " Nodes at rubbish bin: " << nodesInRubbishBin << " Nodes at vault: " << nodesInVault; } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupContactRequests(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(RemoveContactRequests)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; std::unique_ptr crl{megaApi[nApi]->getOutgoingContactRequests()}; for (int i = 0; i < crl->size(); i++) { const MegaContactRequest* cr = crl->get(i); if (const auto targetEmail = cr->getTargetEmail(); !targetEmail || !std::strlen(targetEmail)) { continue; } if (const auto resOut = synchronousInviteContact(nApi, cr->getTargetEmail(), "Test cleanup removing outgoing contact request", MegaContactRequest::INVITE_ACTION_DELETE); resOut != API_OK && resOut != API_EARGS) { localCleanupSuccess = false; string errDetails = "Error removing outgoing contact request (" + std::string(cr->getTargetEmail() ? cr->getTargetEmail() : "") + ")"; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), resOut, localCleanupSuccess); } } crl.reset(megaApi[nApi]->getIncomingContactRequests()); for (int i = 0; i < crl->size(); i++) { const MegaContactRequest* cr = crl->get(i); if (const auto sourceEmail = cr->getTargetEmail(); !sourceEmail || !std::strlen(sourceEmail)) { continue; } if (const auto resIn = synchronousReplyContactRequest(nApi, cr, MegaContactRequest::REPLY_ACTION_DENY); resIn != API_OK && resIn != API_EARGS) { string errDetails = "Error removing incoming contact request (" + std::string(cr->getSourceEmail() ? cr->getSourceEmail() : "") + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), resIn, localCleanupSuccess); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::cleanupLocalFiles() { const std::string prefix{"SdkTest::Cleanup(RemoveLocalFiles)"}; LOG_debug << "# " << prefix; deleteFile(UPFILE); deleteFile(DOWNFILE); deleteFile(PUBLICFILE); deleteFile(AVATARDST); LOG_debug << "# " << prefix << ": Finished]"; } #ifdef ENABLE_SYNC void SdkTest::cleanupSyncs(const unsigned int nApi) { const std::string prefix{"SdkTest::Cleanup(RemoveSyncs)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; auto& m = megaApi[nApi]; auto syncs = unique_ptr(m->getSyncs()); for (int i = syncs->size(); i--;) { std::unique_ptr syncTracker( std::unique_ptr(new RequestTracker(m.get()))); m->removeSync(syncs->get(i)->getBackupId(), syncTracker.get()); if (const auto syncRemoveResult = syncTracker->waitForResult(); syncRemoveResult != API_OK) { const string errDetails = "Failed to remove sync (" + string{Base64Str(syncs->get(i)->getBackupId())} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), syncRemoveResult, localCleanupSuccess); } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } void SdkTest::purgeVaultTree(unsigned int apiIndex, MegaNode* vault) { std::unique_ptr vc{megaApi[apiIndex]->getChildren(vault)}; EXPECT_LE(vc->size(), MAX_VAULT_CHILDREN) << "purgeVaultTree: Vault node contains more than " << MAX_VAULT_CHILDREN << " children"; const auto getVaultNodeHandle = [this, &apiIndex](const int type) -> MegaHandle { RequestTracker rt{megaApi[apiIndex].get()}; megaApi[apiIndex]->getUserAttribute(type, &rt); return rt.waitForResult() == API_OK ? rt.request->getNodeHandle() : UNDEF; }; MegaHandle hBackups = getVaultNodeHandle(MegaApi::USER_ATTR_MY_BACKUPS_FOLDER); if (auto myBackups = std::unique_ptr{megaApi[apiIndex]->getNodeByHandle(hBackups)}; myBackups) { std::unique_ptr devices{megaApi[apiIndex]->getChildren(myBackups.get())}; for (int i = 0; i < devices->size(); ++i) { std::unique_ptr backupRoots{ megaApi[apiIndex]->getChildren(devices->get(i))}; for (int j = 0; j < backupRoots->size(); ++j) { RequestTracker rt(megaApi[apiIndex].get()); const auto backup = backupRoots->get(j); megaApi[apiIndex]->moveOrRemoveDeconfiguredBackupNodes(backup->getHandle(), INVALID_HANDLE, &rt); const string backupName = backup->getName() ? backup->getName() : ""; const auto res = rt.waitForResult(); LOG_err << "purgeVaultTree: Could not remove Backup, " << backupName << "(" << Base64Str(backup->getHandle()) << "). ErrCode(" << MegaError::getErrorString(res) << ")"; if (res != API_OK && res != API_ENOENT && res != API_EARGS) { EXPECT_EQ(rt.waitForResult(), API_OK) << "purgeVaultTree: Could not remove Backup, " << backupName << "(" << Base64Str(backup->getHandle()) << ")"; } } } } // Get password manager base with user attribute instead of MegaApi::getPasswordManagerBase to // avoid create password manager base if it doesn't exist MegaHandle pwdBaseHandle = getVaultNodeHandle(MegaApi::USER_ATTR_PWM_BASE); if (auto passwordManagerBase = std::unique_ptr{megaApi[apiIndex]->getNodeByHandle(pwdBaseHandle)}; passwordManagerBase) { purgeTree(apiIndex, passwordManagerBase.get()); } } #endif bool SdkTest::waitForResponse(bool *responseReceived, unsigned int timeout) { return waitForEvent([responseReceived]() { return *responseReceived; }, timeout); } bool SdkTest::waitForEvent(std::function method, unsigned int timeout) { timeout *= 1000000; // convert to micro-seconds unsigned int tWaited = 0; // microseconds bool connRetried = false; while(!method()) { WaitMillisec(pollingT / 1000); if (timeout) { tWaited += pollingT; if (tWaited >= timeout) { return false; // timeout is expired } // if no response after 2 minutes... else if (!connRetried && tWaited > (pollingT * 240)) { megaApi[0]->retryPendingConnections(true); if (megaApi.size() > 1 && megaApi[1] && megaApi[1]->isLoggedIn()) { megaApi[1]->retryPendingConnections(true); } connRetried = true; } } } return true; // response is received } bool SdkTest::synchronousTransfer(unsigned apiIndex, int type, std::function f, unsigned int timeout) { auto& flag = mApi[apiIndex].transferFlags[type]; flag = false; f(); auto result = waitForResponse(&flag, timeout); EXPECT_TRUE(result) << "Transfer (type " << type << ") not finished yet after " << timeout << " seconds"; if (!result) mApi[apiIndex].lastError = LOCAL_ETIMEOUT; // local timeout if (!result) mApi[apiIndex].lastTransferError = LOCAL_ETIMEOUT; // local timeout TODO: switch all transfer code to use lastTransferError . Some still uses lastError return result; } bool SdkTest::synchronousRequest(unsigned apiIndex, int type, std::function f, unsigned int timeout) { auto& flag = mApi[apiIndex].requestFlags[type]; flag = false; f(); auto result = waitForResponse(&flag, timeout); EXPECT_TRUE(result) << "Request (type " << type << ") failed after " << timeout << " seconds"; if (!result) mApi[apiIndex].lastError = LOCAL_ETIMEOUT; return result; } bool SdkTest::synchronousRequestIgnoreErr(unsigned apiIndex, int type, std::function f, unsigned int timeout) { auto& flag = mApi[apiIndex].requestFlags[type]; flag = false; f(); auto result = waitForResponse(&flag, timeout); if (!result) { LOG_err << "Request (type " << type << ") failed after " << timeout << " seconds"; mApi[apiIndex].lastError = LOCAL_ETIMEOUT; } return result; } void SdkTest::onNodesUpdateCheck(size_t apiIndex, MegaHandle target, MegaNodeList* nodes, int change, bool& flag) { // if change == -1 this method just checks if we have received onNodesUpdate for the node specified in target // For CHANGE_TYPE_NEW the target is invalid handle because the handle is yet unkown ASSERT_TRUE(nodes && mApi.size() > apiIndex && (target != INVALID_HANDLE || (target == INVALID_HANDLE && change == MegaNode::CHANGE_TYPE_NEW))); for (int i = 0; i < nodes->size(); i++) { MegaNode* n = nodes->get(i); if ((n->getHandle() == target && (n->hasChanged(static_cast(change)) || change == -1)) || (target == INVALID_HANDLE && change == MegaNode::CHANGE_TYPE_NEW && n->hasChanged(static_cast(change)))) { flag = true; } } }; bool SdkTest::createFile(string filename, bool largeFile, string content) { // Convenience. constexpr auto KiB = 1024ul; constexpr auto MiB = 1024ul * KiB; auto limit = 2000ul; // Caller wants to generate a large file. if (largeFile) limit = MiB + static_cast(rand()) % MiB; std::string temp; temp.reserve(content.size() * limit); // Generate file content. while (limit--) temp.append(content); // Write the file to disk. try { sdk_test::createFile(u8path_compat(filename), temp); return true; } catch (const std::runtime_error& err) { LOG_err << err.what(); return false; } } int64_t SdkTest::getFilesize(string filename) { struct stat stat_buf; int rc = stat(filename.c_str(), &stat_buf); return rc == 0 ? int64_t(stat_buf.st_size) : int64_t(-1); } void SdkTest::deleteFile(string filename) { fs::path p = u8path_compat(filename); std::error_code ignoredEc; fs::remove(p, ignoredEc); } void SdkTest::deleteFolder(string foldername) { fs::path p = u8path_compat(foldername); std::error_code ignoredEc; fs::remove_all(p, ignoredEc); } void SdkTest::fetchNodesForAccountsSequentially(const unsigned howMany) { for (unsigned index = 0; index < howMany; ++index) { out() << "Fetching nodes for account " << index; auto tracker = asyncRequestFetchnodes(index); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to fetchnodes for account " << index; ASSERT_EQ(MegaError::API_OK, synchronousDoUpgradeSecurity(index)); LOG_debug << "fetchNodesForAccountsSequentially: Catching up with API with account index(" << index << ")"; cleanupCatchupWithApi(index, cleanupCatchupTimeoutSecs); } } void SdkTest::getAccountsForTest(const unsigned howMany, const bool fetchNodes, const int clientType, const std::string& apiServer) { const std::string prefix{"SdkTest::getAccountsForTest()"}; const auto maxAccounts = getEnvVarAccounts().size(); EXPECT_TRUE(howMany > 0) << prefix << "invalid number of test account to setup " << howMany << " is < 0"; EXPECT_TRUE(howMany <= maxAccounts) << prefix << "too many test accounts requested " << howMany << " is > " << maxAccounts; megaApi.resize(howMany); mApi.resize(howMany); out() << "Test setting up for " << howMany << " accounts "; for (unsigned index = 0; index < howMany; ++index) { const auto [email, pass] = getEnvVarAccounts().getVarValues(index); ASSERT_FALSE(email.empty() || pass.empty()); static const bool checkCredentials = true; // default value configureTestInstance(index, email, pass, checkCredentials, clientType); if (!apiServer.empty()) { megaApi[index]->changeApiUrl(apiServer.c_str()); } std::unique_ptr tracker; if (!gResumeSessions || gSessionIDs[index].empty() || gSessionIDs[index] == "invalid") { out() << "Logging into account #" << index << ": " << mApi[index].email; tracker = asyncRequestLogin(index, mApi[index].email.c_str(), mApi[index].pwd.c_str()); } else { out() << "Resuming session for account #" << index; tracker = asyncRequestFastLogin(index, gSessionIDs[index].c_str()); } auto loginResult = tracker->waitForResult(); ASSERT_EQ(API_OK, loginResult) << prefix << " Failed to establish a login/session for account #" << index << ": " << mApi[index].email << ": " << MegaError::getErrorString(loginResult); gSessionIDs[index] = "invalid"; // default if (gResumeSessions && megaApi[index]->isLoggedIn() == FULLACCOUNT) { if (auto p = unique_ptr(megaApi[index]->dumpSession())) { gSessionIDs[index] = p.get(); } } if (fetchNodes) { out() << "Fetching nodes for account " << index; auto fntracker = asyncRequestFetchnodes(index); ASSERT_EQ(API_OK, fntracker->waitForResult()) << " Failed to fetchnodes for account " << index; ASSERT_EQ(MegaError::API_OK, synchronousDoUpgradeSecurity(index)); LOG_debug << "fetchNodesForAccountsSequentially: Catching up with API with account index(" << index << ")"; cleanupCatchupWithApi(index, cleanupCatchupTimeoutSecs); } auto rt = std::make_unique(megaApi[index].get()); megaApi[index]->getUserAttribute(37 /*ATTR_KEYS*/, rt.get()); rt->waitForResult(); const std::string b64Value{rt->request->getText()}; const std::string binValue = Base64::atob(b64Value); if (binValue.size() > MAX_USER_VAR_SIZE - 512) // limit almost exceeded, tests will start failing soon { out() << "Account " << std::unique_ptr{megaApi[index]->getMyEmail()}.get() << " has a ^!keys of " << binValue.size() << " bytes"; out() << "Please, DevOps, park this account"; ASSERT_FALSE(true); } setTestAccountsToFree(index); } // In case the last test exited without cleaning up (eg, debugging etc) Cleanup(); out() << "Test setup done, test starts"; } void SdkTest::configureTestInstance(unsigned index, const string& email, const string& pass, bool checkCredentials, const int clientType) { ASSERT_GT(mApi.size(), index) << "Invalid mApi size"; ASSERT_GT(megaApi.size(), index) << "Invalid megaApi size"; if(checkCredentials) { mApi[index].email = email; mApi[index].pwd = pass; const auto& [emailVarName, passVarName] = getEnvVarAccounts().getVarNames(index); ASSERT_FALSE(mApi[index].email.empty()) << "Set test account " << index << " username at the environment variable $" << emailVarName; ASSERT_FALSE(mApi[index].pwd.empty()) << "Set test account " << index << " password at the environment variable $" << passVarName; } megaApi[index] = newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(static_cast(index)).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT), clientType); mApi[index].megaApi = megaApi[index].get(); // helps with restoring logging after tests that fiddle with log level mApi[index].megaApi->setLogLevel(MegaApi::LOG_LEVEL_MAX); megaApi[index]->setLoggingName(to_string(index).c_str()); megaApi[index]->addListener(this); // TODO: really should be per api } void SdkTest::releaseMegaApi(unsigned int apiIndex) { if (mApi.size() <= apiIndex) { return; } assert(megaApi[apiIndex].get() == mApi[apiIndex].megaApi); if (mApi[apiIndex].megaApi) { if (mApi[apiIndex].megaApi->isLoggedIn()) { LOG_debug << "releaseMegaApi: Catching up with API with account index(" << apiIndex << ")"; cleanupCatchupWithApi(apiIndex, cleanupCatchupTimeoutSecs); if (!gResumeSessions) ASSERT_NO_FATAL_FAILURE( logout(apiIndex, false, maxTimeout) ); else ASSERT_NO_FATAL_FAILURE( locallogout(apiIndex) ); } megaApi[apiIndex].reset(); mApi[apiIndex].megaApi = NULL; } } void SdkTest::loginSameAccountsForTest(unsigned copyIndex) { ASSERT_GT(mApi.size(), copyIndex) << "Invalid copy index" << copyIndex << " for mApi size " << mApi.size(); const std::string prefix{"SdkTest::loginSameAccountsForTest()"}; LOG_debug << prefix << ": establishing second session with same credentials"; mApi.resize(mApi.size() + 1); megaApi.resize(megaApi.size() + 1); const unsigned index = static_cast(megaApi.size() - 1); const string email = mApi[copyIndex].email; const string pass = mApi[copyIndex].pwd; configureTestInstance(index, email, pass, true, MegaApi::CLIENT_TYPE_DEFAULT); std::unique_ptr tracker; tracker = asyncRequestLogin(index, mApi[index].email.c_str(), mApi[index].pwd.c_str()); auto loginResult = tracker->waitForResult(); ASSERT_EQ(API_OK, loginResult) << prefix << ": Failed to establish a login/session for account" << ": " << mApi[index].email << ": " << MegaError::getErrorString(loginResult); auto fntracker = asyncRequestFetchnodes(index); ASSERT_EQ(API_OK, fntracker->waitForResult()) << prefix << ": Failed to fetchnodes for account " << mApi[index].email; ASSERT_EQ(MegaError::API_OK, synchronousDoUpgradeSecurity(index)); cleanupCatchupWithApi(index, cleanupCatchupTimeoutSecs); } void SdkTest::inviteTestAccount(const unsigned invitorIndex, const unsigned inviteIndex, const string& message) { //--- Add account as contact --- mApi[inviteIndex].contactRequestUpdated = false; std::unique_ptr contact(mApi[invitorIndex].megaApi->getContact(mApi[inviteIndex].email.c_str())); if (contact) { if (contact->getVisibility() == MegaUser::VISIBILITY_VISIBLE) { LOG_warn << mApi[inviteIndex].email.c_str() << " is inviting " << mApi[inviteIndex].email.c_str() << " but they are already contacts"; } else if (contact->getVisibility() == MegaUser::VISIBILITY_HIDDEN) { LOG_info << mApi[inviteIndex].email.c_str() << " is inviting " << mApi[inviteIndex].email.c_str() << " They were contacts in the past"; } } // Watcher for the new contact visibility bool contactRightVisibility = false; auto visibilityCheck = std::make_shared>( [this, &invitorIndex, &inviteIndex, &contactRightVisibility]() { std::unique_ptr contact( mApi[invitorIndex].megaApi->getContact(mApi[inviteIndex].email.c_str())); contactRightVisibility = contact && contact->getVisibility() == MegaUser::VISIBILITY_VISIBLE; }); MegaHandle invitedUserHandler = mApi[inviteIndex].megaApi->getMyUserHandleBinary(); mApi[invitorIndex].customCallbackCheck[invitedUserHandler] = visibilityCheck; ASSERT_NO_FATAL_FAILURE(inviteContact(invitorIndex, mApi[inviteIndex].email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[inviteIndex].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE(getContactRequest(inviteIndex, false)); mApi[invitorIndex].contactRequestUpdated = mApi[inviteIndex].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(mApi[inviteIndex].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT, inviteIndex)); ASSERT_TRUE(waitForResponse(&mApi[inviteIndex].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[invitorIndex].contactRequestUpdated)) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[inviteIndex].cr.reset(); bool hasExpectedVisibility = waitForResponse(&contactRightVisibility); if (!hasExpectedVisibility) { std::unique_ptr contact(mApi[invitorIndex].megaApi->getContact(mApi[inviteIndex].email.c_str())); ASSERT_TRUE(contact) << "Invalid contact after " << maxTimeout << " seconds"; ASSERT_EQ(contact->getVisibility(), MegaUser::VISIBILITY_VISIBLE) << "Invalid contact visibility after " << maxTimeout << " seconds"; ASSERT_TRUE(hasExpectedVisibility) << "The contact has the correct visibility but the timeout of " << maxTimeout << " seconds was exceeded"; } } void SdkTest::inviteContact(const unsigned apiIndex, const string& email, const string& message, const int action) { ASSERT_EQ(API_OK, synchronousInviteContact(apiIndex, email.c_str(), message.c_str(), action)) << "Contact invitation failed"; } void SdkTest::replyContact(MegaContactRequest* cr, int action, const unsigned apiIndex) { ASSERT_EQ(API_OK, synchronousReplyContactRequest(apiIndex, cr, action)) << "Contact reply failed"; } int SdkTest::removeContact(unsigned apiIndex, string email) { unique_ptr u(megaApi[apiIndex]->getContact(email.c_str())); if (!u) { out() << "Trying to remove user " << email << " from contacts for megaapi " << apiIndex << " but the User does not exist"; return API_EINTERNAL; } if (u->getVisibility() != MegaUser::VISIBILITY_VISIBLE) { out() << "Contact " << email << " was already non-visible, not sending any command to API for megaapi " << apiIndex << ". visibility: " << u->getVisibility(); return API_EINTERNAL; } auto result = synchronousRemoveContact(apiIndex, u.get()); if (result == API_EEXIST) { LOG_warn << "Contact " << email << " was already removed in api " << apiIndex; result = API_OK; } EXPECT_EQ(API_OK, result) << "Contact deletion of " << email << " failed on api " << apiIndex; return result; } void SdkTest::verifyCredentials(unsigned apiIndex, string email) { unique_ptr usr(megaApi[apiIndex]->getContact(email.c_str())); ASSERT_NE(nullptr, usr.get()) << "User " << email << " not found at apiIndex " << apiIndex; ASSERT_EQ(MegaError::API_OK, synchronousVerifyCredentials(apiIndex, usr.get())); } void SdkTest::resetCredentials(unsigned apiIndex, string email) { unique_ptr usr(megaApi[apiIndex]->getContact(email.c_str())); ASSERT_NE(nullptr, usr.get()) << "User " << email << " not found at apiIndex " << apiIndex; ASSERT_EQ(MegaError::API_OK, synchronousResetCredentials(apiIndex, usr.get())); } bool SdkTest::areCredentialsVerified(unsigned apiIndex, string email) { unique_ptr usr(megaApi[apiIndex]->getContact(email.c_str())); EXPECT_NE(nullptr, usr.get()) << "User " << email << " not found at apiIndex " << apiIndex; return megaApi[apiIndex]->areCredentialsVerified(usr.get()); } #ifdef ENABLE_CHAT void SdkTest::createChatScheduledMeeting(const unsigned apiIndex, MegaHandle& chatid) { struct SchedMeetingData { MegaHandle chatId = INVALID_HANDLE, schedId = INVALID_HANDLE; std::string timeZone, title, description; MegaTimeStamp startDate, endDate, overrides, newStartDate, newEndDate; bool cancelled, newCancelled; std::shared_ptr flags; std::shared_ptr rules; } smd; std::unique_ptr contact(mApi[0].megaApi->getContact(mApi[1].email.c_str())); if (!contact || contact->getVisibility() != MegaUser::VISIBILITY_VISIBLE) { inviteTestAccount(0, 1, "Hi contact. This is a test message"); } std::unique_ptr myUser(megaApi[apiIndex + 1]->getMyUser()); ASSERT_TRUE(myUser) << "Cannot retrieve my own user"; MegaHandle secondaryAccountHandle = myUser->getHandle(); MegaHandle auxChatid = UNDEF; for (const auto &it: mApi[apiIndex].chats) { if (!it.second->isGroup() || it.second->getOwnPrivilege() != MegaTextChatPeerList::PRIV_MODERATOR || !it.second->getPeerList()) { continue; } const auto peerList = it.second->getPeerList(); for (int i = 0; i < peerList->size(); ++i) { if (peerList->getPeerHandle(i) == secondaryAccountHandle) { auxChatid = it.first; break; } } } if (auxChatid == UNDEF) // create chatroom with moderator privileges { mApi[apiIndex].chatUpdated = false; std::unique_ptr peers(MegaTextChatPeerList::createInstance()); peers->addPeer(megaApi[apiIndex + 1]->getMyUserHandleBinary(), PRIV_STANDARD); ASSERT_NO_FATAL_FAILURE(createChat(true, peers.get())); ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE])) << "Cannot create a new chat"; ASSERT_EQ(API_OK, mApi[apiIndex].lastError) << "Chat creation failed (error: " << mApi[apiIndex].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[apiIndex + 1].chatUpdated)) // at the target side (auxiliar account) << "Chat update not received after " << maxTimeout << " seconds"; auxChatid = mApi[apiIndex].chatid; // set at onRequestFinish() of chat creation request } // create MegaScheduledFlags std::shared_ptr flags(MegaScheduledFlags::createInstance()); flags->importFlagsValue(1); // create MegaScheduledRules std::shared_ptr<::mega::MegaIntegerList> byWeekDay(::mega::MegaIntegerList::createInstance()); byWeekDay->add(1); byWeekDay->add(3); byWeekDay->add(5); std::shared_ptr rules(MegaScheduledRules::createInstance(MegaScheduledRules::FREQ_WEEKLY, MegaScheduledRules::INTERVAL_INVALID, MEGA_INVALID_TIMESTAMP, byWeekDay.get(), nullptr, nullptr)); smd.startDate = m_time(); smd.endDate = m_time() + 3600; smd.title = "ScheduledMeeting_" + std::to_string(1); smd.description = "Description" + smd.title; smd.timeZone = "Europe/Madrid"; smd.flags = flags; smd.rules = rules; std::unique_ptr sm(MegaScheduledMeeting::createInstance(auxChatid, UNDEF /*schedId*/, UNDEF /*parentSchedId*/, megaApi[apiIndex]->getMyUserHandleBinary() /*organizerUserId*/, false /*cancelled*/, "Europe/Madrid", smd.startDate, smd.endDate, smd.title.c_str(), smd.description.c_str(), nullptr /*attributes*/, MEGA_INVALID_TIMESTAMP /*overrides*/, flags.get(), rules.get())); mApi[apiIndex].schedUpdated = false; mApi[apiIndex].schedId = UNDEF; megaApi[apiIndex]->createOrUpdateScheduledMeeting(sm.get(), nullptr/*chatTitle*/); ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING])) << "Cannot create a new scheduled meeting"; ASSERT_EQ(API_OK, mApi[apiIndex].lastError) << "Scheduled meeting creation failed (error: " << mApi[apiIndex].lastError << ")"; ASSERT_TRUE(waitForResponse(&mApi[apiIndex].schedUpdated)) // at the target side (auxiliar account) << "Scheduled meeting update not received after " << maxTimeout << " seconds"; ASSERT_NE(mApi[apiIndex].schedId, UNDEF) << "Scheduled meeting id received is not valid "; chatid = auxChatid; } void SdkTest::updateScheduledMeeting(const unsigned apiIndex, MegaHandle& chatid) { const auto isValidChat = [](const MegaTextChat* chat) -> bool { if (!chat) { return false; } return chat->isGroup() && chat->getOwnPrivilege() == MegaTextChatPeerList::PRIV_MODERATOR && chat->getScheduledMeetingList() && chat->getScheduledMeetingList()->size(); }; const MegaTextChat* chat = nullptr; auto it = mApi[apiIndex].chats.find(chatid); if (chatid == UNDEF || it == mApi[apiIndex].chats.end() || !isValidChat(it->second.get())) { for (const auto& auxit: mApi[apiIndex].chats) { if (isValidChat(auxit.second.get())) { chatid = auxit.second->getHandle(); chat = auxit.second.get(); break; } } } else { chat = it->second.get(); } ASSERT_NE(chat, nullptr) << "Invalid chat"; ASSERT_NE(chat->getScheduledMeetingList(), nullptr) << "Chat doesn't have scheduled meetings"; ASSERT_NE(chat->getScheduledMeetingList()->at(0), nullptr) << "Invalid scheduled meeting"; const MegaScheduledMeeting* aux = chat->getScheduledMeetingList()->at(0); std::unique_ptr rules(aux->rules()); std::unique_ptr flags(aux->flags()); std::unique_ptr sm( MegaScheduledMeeting::createInstance(aux->chatid(), aux->schedId(), aux->parentSchedId(), aux->organizerUserid(), aux->cancelled(), aux->timezone(), aux->startDateTime(), aux->endDateTime(), (std::string(aux->title()) + "_updated").c_str(), (std::string(aux->description()) + "_updated").c_str(), aux->attributes(), MEGA_INVALID_TIMESTAMP /*overrides*/, flags.get(), rules.get())); std::unique_ptrtracker (new RequestTracker(megaApi[apiIndex].get())); megaApi[apiIndex]->createOrUpdateScheduledMeeting(sm.get(), nullptr/*chatTitle*/, tracker.get()); tracker->waitForResult(); } #endif void SdkTest::shareFolder(MegaNode* n, const char* email, int action, unsigned apiIndex) { auto shareFolderErr = synchronousShare(apiIndex, n, email, action); if (shareFolderErr == API_EKEY) { ASSERT_EQ(API_OK, doOpenShareDialog(apiIndex, n)) << "Creating new share key failed. " << "User: " << email << " Action: " << action; ASSERT_EQ(API_OK, synchronousShare(apiIndex, n, email, action)) << "Folder sharing failed (share key created!). " << "User: " << email << " Action: " << action; } else { ASSERT_EQ(API_OK, shareFolderErr) << "Folder sharing failed. " << "User: " << email << " Action: " << action; } } string SdkTest::createPublicLink(unsigned apiIndex, MegaNode *n, m_time_t expireDate, int timeout, bool isFreeAccount, bool writable, bool megaHosted) { RequestTracker rt(megaApi[apiIndex].get()); mApi[apiIndex].megaApi->exportNode(n, expireDate, writable, megaHosted, &rt); rt.waitForResult(timeout); if (!expireDate || !isFreeAccount) { EXPECT_EQ(API_OK, rt.result.load()) << "Public link creation failed (error: " << mApi[apiIndex].lastError << ")"; } else { bool res = API_OK != rt.result && rt.result != LOCAL_ETIMEOUT; EXPECT_TRUE(res) << "Public link creation with expire time on free account (" << mApi[apiIndex].email << ") succeed, and it mustn't"; } return rt.getLink(); } MegaHandle SdkTest::importPublicLink(unsigned apiIndex, string link, MegaNode *parent) { RequestTracker rt(megaApi[apiIndex].get()); mApi[apiIndex].megaApi->importFileLink(link.c_str(), parent, &rt); EXPECT_EQ(API_OK, rt.waitForResult()) << "Public link import failed"; return rt.getNodeHandle(); } unique_ptr SdkTest::getPublicNode(unsigned apiIndex, string link) { RequestTracker rt(megaApi[apiIndex].get()); mApi[apiIndex].megaApi->getPublicNode(link.c_str(), &rt); EXPECT_EQ(API_OK, rt.waitForResult()) << "Public link retrieval failed"; return rt.getPublicMegaNode(); } MegaHandle SdkTest::removePublicLink(unsigned apiIndex, MegaNode *n) { RequestTracker rt(megaApi[apiIndex].get()); mApi[apiIndex].megaApi->disableExport(n, &rt); EXPECT_EQ(API_OK, rt.waitForResult()) << "Public link removal failed"; return rt.getNodeHandle(); } void SdkTest::getContactRequest(unsigned int apiIndex, bool outgoing, int expectedSize) { unique_ptr crl; unsigned timeoutMs = 8000; if (outgoing) { auto predicate = [&]() { crl.reset(mApi[apiIndex].megaApi->getOutgoingContactRequests()); return crl->size() == expectedSize; }; ASSERT_TRUE(WaitFor(predicate, timeoutMs)) << "Too many outgoing contact requests in account: " << apiIndex; } else { auto predicate = [&]() { crl.reset(mApi[apiIndex].megaApi->getIncomingContactRequests()); return crl->size() == expectedSize; }; ASSERT_TRUE(WaitFor(predicate, timeoutMs)) << "Too many incoming contact requests in account: " << apiIndex; } if (!expectedSize) return; mApi[apiIndex].cr.reset(crl->get(0)->copy()); } std::pair SdkTest::createRemoteFolder(const unsigned int apiIndex, const char* name, MegaNode* parent, const int timeout) { RequestTracker tracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createFolder(name, parent, &tracker); return {tracker.waitForResult(timeout), tracker.request->getNodeHandle()}; } MegaHandle SdkTest::createFolder(unsigned int apiIndex, const char *name, MegaNode *parent, int timeout) { RequestTracker tracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createFolder(name, parent, &tracker); if (auto createfolderResult = tracker.waitForResult(timeout); createfolderResult != API_OK) { EXPECT_EQ(API_OK, createfolderResult) << "API " << apiIndex << ": Failed to create folder " << name; return UNDEF; } return tracker.request->getNodeHandle(); } #if 0 // SMS verification was deprecated. This function should be removed in the future, // along with the rest of the code dealing with the deprecated functionality. void SdkTest::getCountryCallingCodes(const int /*timeout*/) { unsigned int apiIndex = 0; ASSERT_EQ(API_OK, synchronousGetCountryCallingCodes(apiIndex, this)) << "Get country calling codes failed"; } #endif void SdkTest::getUserAttribute(MegaUser* u, int type, int /*timeout*/, int apiIndex) { mApi[static_cast(apiIndex)].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; int err; if (type == MegaApi::USER_ATTR_AVATAR) { err = synchronousGetUserAvatar(static_cast(apiIndex), u, AVATARDST.c_str()); } else { err = synchronousGetUserAttribute(static_cast(apiIndex), u, type); } bool result = (err == API_OK) || (err == API_ENOENT); ASSERT_TRUE(result) << "User attribute retrieval failed (error: " << err << ")"; } void SdkTest::synchronousMediaUpload(unsigned int apiIndex, int64_t fileSize, const char* filename, const char* fileEncrypted, const char* fileOutput, const char* fileThumbnail, const char* filePreview) { // Create a "media upload" instance std::unique_ptr req(MegaBackgroundMediaUpload::createInstance(megaApi[apiIndex].get())); // Request a media upload URL auto err = synchronousMediaUploadRequestURL(apiIndex, fileSize, req.get(), nullptr); ASSERT_EQ(API_OK, err) << "Cannot request media upload URL (error: " << err << ")"; // Get the generated media upload URL std::unique_ptr url(req->getUploadURL()); ASSERT_NE(nullptr, url) << "Got NULL media upload URL"; ASSERT_NE('\0', url[0]) << "Got empty media upload URL"; // encrypt file contents with the file key and get URL suffix std::unique_ptr suffix(req->encryptFile(filename, 0, &fileSize, fileEncrypted, false)); ASSERT_NE(nullptr, suffix) << "Got NULL suffix after encryption"; std::unique_ptr fingerprint(megaApi[apiIndex]->getFingerprint(filename)); // PUT thumbnail and preview if params exists if (fileThumbnail) { ASSERT_EQ(true, megaApi[apiIndex]->createThumbnail(filename, fileThumbnail)); ASSERT_EQ(API_OK, doPutThumbnail(apiIndex, req.get(), fileThumbnail)) << "ERROR putting thumbnail"; } if (filePreview) { ASSERT_EQ(true, megaApi[apiIndex]->createPreview(filename, filePreview)); ASSERT_EQ(API_OK, doPutPreview(apiIndex, req.get(), filePreview)) << "ERROR putting preview"; } std::unique_ptr rootnode(megaApi[apiIndex]->getRootNode()); string finalurl(url.get()); if (suffix) finalurl.append(suffix.get()); string binaryUploadToken; synchronousHttpPOSTFile(finalurl, fileEncrypted, binaryUploadToken); ASSERT_NE(binaryUploadToken.size(), 0u); ASSERT_GT(binaryUploadToken.size(), 3u) << "POST failed, fa server error: " << binaryUploadToken; std::unique_ptr base64UploadToken(megaApi[0]->binaryToBase64(binaryUploadToken.data(), binaryUploadToken.length())); err = synchronousMediaUploadComplete(apiIndex, req.get(), fileOutput, rootnode.get(), fingerprint.get(), nullptr, base64UploadToken.get(), nullptr); ASSERT_EQ(API_OK, err) << "Cannot complete media upload (error: " << err << ")"; } void SdkTest::synchronousMediaUploadIncomplete(unsigned int apiIndex, int64_t fileSize, const char* filename, const char* fileEncrypted, std::string& fingerprint, std::string& string64UploadToken, std::string& string64FileKey) { // Create a "media upload" instance std::unique_ptr req( dynamic_cast( MegaBackgroundMediaUpload::createInstance(megaApi[apiIndex].get()))); // Request a media upload URL auto err = synchronousMediaUploadRequestURL(apiIndex, fileSize, req.get(), nullptr); ASSERT_EQ(API_OK, err) << "Cannot request media upload URL (error: " << err << ")"; // Get the generated media upload URL std::unique_ptr url(req->getUploadURL()); ASSERT_NE(nullptr, url) << "Got NULL media upload URL"; ASSERT_NE('\0', url[0]) << "Got empty media upload URL"; // encrypt file contents and get URL suffix std::unique_ptr suffix(req->encryptFile(filename, 0, &fileSize, fileEncrypted, false)); ASSERT_NE(nullptr, suffix) << "Got NULL suffix after encryption"; // generated by FileFingerprint::serializefingerprint() plus MegaNodePrivate::addAppPrefixToFingerprint() fingerprint = megaApi[apiIndex]->getFingerprint(fileEncrypted); string finalurl(url.get()); finalurl.append(suffix.get()); string binaryUploadToken; synchronousHttpPOSTFile(finalurl, fileEncrypted, binaryUploadToken); ASSERT_GT(binaryUploadToken.size(), 3u) << "POST failed, fa server error: " << binaryUploadToken; string64UploadToken = megaApi[apiIndex]->binaryToBase64(binaryUploadToken.data(), binaryUploadToken.length()); string64FileKey = megaApi[apiIndex]->binaryToBase64(reinterpret_cast(req->filekey), FILENODEKEYLENGTH); } auto getAccountLevel(MegaApi& client) -> Expected { const string prefix{"getAccountLevel"}; // Try and retrieve the user's account details. auto details = getAccountDetails(client); // Couldn't get account details. if (auto result = ::result(details); result != API_OK) { LOG_err << prefix << "Unexpected error for account(" << string{client.getMyEmail() ? client.getMyEmail() : ""} << ") trying to getAccountDetails. Err(" << result << ")"; return result; } // Latch the user's plan. auto& planRes = std::get>(details); if (!planRes) { LOG_err << prefix << "getAccountDetails succeeded but cannot retrieve MegaAccountDetails for account(" << string{client.getMyEmail() ? client.getMyEmail() : ""} << ")"; return Error(API_EINTERNAL); } auto plan = planRes->getProLevel(); if (plan == MegaAccountDetails::ACCOUNT_TYPE_FREE) return AccountLevel(0, plan); // Try and get pricing information. auto pricing = getPricing(client); // Couldn't get pricing information. if (auto result = ::result(pricing); result != API_OK) { LOG_err << prefix << "Unexpected error for account(" << string{client.getMyEmail() ? client.getMyEmail() : ""} << ") trying to getPricing. Err(" << result << ")"; return result; } // Convenience. auto& priceDetailRes = std::get>(pricing); if (!priceDetailRes) { LOG_err << prefix << "getPricing succeeded but cannot retrieve MegaPricing for account(" << string{client.getMyEmail() ? client.getMyEmail() : ""} << ")"; return Error(API_EINTERNAL); } // Locate the user's plan. for (auto i = 0, j = priceDetailRes->getNumProducts(); i < j; ++i) { // Found the user's plan. if (plan == priceDetailRes->getProLevel(i)) { // Return plan and its length. return AccountLevel(priceDetailRes->getMonths(i), plan); } } // Couldn't locate the user's plan. return API_ENOENT; } auto getAccountDetails(MegaApi& client) -> Expected> { // So we can wait for the client's result. RequestTracker tracker(&client); // Ask client for account details. client.getAccountDetails(&tracker); // Couldn't get the client's account details. if (auto result = tracker.waitForResult(); result != API_OK) return result; // Return account details to caller. return makeUniqueFrom(tracker.request->getMegaAccountDetails()); } auto getPricing(MegaApi& client) -> Expected> { // So we can wait for the client's result. RequestTracker tracker(&client); // Ask client for plan pricing information, client.getPricing(&tracker); // Couldn't get pricing plans. if (auto result = tracker.waitForResult(); result != API_OK) return result; // Return pricing plans to caller. return makeUniqueFrom(tracker.request->getPricing()); } auto accountLevelRestorer(MegaApi& client) -> ScopedDestructor { // Assume we can't retrieve the account level. std::function destructor = []() {}; // Try and retrieve the user's current account level. auto accountLevel = getAccountLevel(client); // Couldn't retrieve account level. if (auto result = ::result(accountLevel); result != API_OK) { // Leave a trail if we couldn't get the account level. EXPECT_EQ(result, API_OK) << "Couldn't retrieve account level: " << result; // Return destructor to caller. return destructor; } // Build a destructor that will restore the user's account level. destructor = [&client, level = std::get(accountLevel)]() { // Try and restore the user's account level. auto result = setAccountLevel(client, level.plan, level.months, nullptr); EXPECT_EQ(result, API_OK) << "Couldn't restore account level: " << result; }; // Return destructor to caller. return destructor; } ScopedDestructor accountLevelRestorer(std::vector& clients, unsigned int idx) { std::function destructor = []() {}; auto accountLevel = getAccountLevel(*clients[idx]); if (auto result = ::result(accountLevel); result != API_OK) { EXPECT_EQ(result, API_OK) << "Couldn't retrieve account " << idx << " level"; return destructor; } destructor = [&clients, idx, level = std::get(accountLevel)]() { auto result = setAccountLevel(*clients[idx], level.plan, level.months, nullptr); EXPECT_EQ(result, API_OK) << "Couldn't restore account " << idx << " level"; }; return destructor; } auto createDirectory(MegaApi& client, const MegaNode& parent, const std::string& name) -> Expected> { using sdk_test::waitFor; RequestTracker tracker(&client); client.createFolder(name.c_str(), const_cast(&parent), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) { return result; } MegaNode* directory = nullptr; MegaHandle directoryHandle = tracker.request->getNodeHandle(); waitFor( [&]() { return (directory = client.getNodeByHandle(directoryHandle)) != nullptr; }, std::chrono::milliseconds(defaultTimeoutMs)); if (!directory) { return LOCAL_ETIMEOUT; } return makeUniqueFrom(directory); } auto scopedToPro(MegaApi& client) -> Expected { // Make sure client's plan alterations are temporary. auto restorer = accountLevelRestorer(client); // Try and elevate client to a pro pricing plan. auto result = setAccountLevel(client, MegaAccountDetails::ACCOUNT_TYPE_PROI, 1, nullptr); // Couldn't elevate client to a pro pricing plan. if (result != API_OK) return result; // Return restorer to caller. return restorer; } auto demoteToFree(MegaApi& client) -> Error { return setAccountLevel(client, MegaAccountDetails::ACCOUNT_TYPE_FREE, 0, nullptr); } auto exportNode(MegaApi& client, const MegaNode& node, std::optional expirationDate) -> Expected { RequestTracker tracker(&client); client.exportNode(const_cast(&node), expirationDate.value_or(-1), false, false, &tracker); if (auto result = tracker.waitForResult(); result != API_OK) { return result; } return tracker.request->getLink(); } auto importNode(MegaApi& client, const std::string& link, const MegaNode& parent) -> Expected> { using sdk_test::waitFor; RequestTracker tracker(&client); client.importFileLink(link.c_str(), const_cast(&parent), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) { return result; } MegaNode* node = nullptr; waitFor( [&]() -> bool { return (node = client.getNodeByHandle(tracker.request->getNodeHandle())) != nullptr; }, std::chrono::milliseconds(defaultTimeoutMs)); if (!node) { return LOCAL_ETIMEOUT; } return makeUniqueFrom(node); } ///////////////////////////__ Tests using SdkTest __////////////////////////////////// /** * @brief TEST_F SdkTestCreateEphmeralPlusPlusAccount * * It tests the creation of a new account for a random user. * - Create account */ TEST_F(SdkTest, SdkTestCreateEphmeralPlusPlusAccount) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST Create ephemeral account plus plus___"; // Create an ephemeral plus plus session internally synchronousCreateEphemeralAccountPlusPlus(0, "MyFirstname", "MyLastname"); ASSERT_EQ(API_OK, mApi[0].lastError) << "Account creation failed (error: " << mApi[0].lastError << ")"; // Wait, for 10 seconds, for the pdf to be imported std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; constexpr int deltaMs = 200; for (int i = 0; i <= 10000 && !megaApi[0]->getNumChildren(rootnode.get()); i += deltaMs) { WaitMillisec(deltaMs); } // Logout from ephemeral plus plus session and resume session ASSERT_NO_FATAL_FAILURE(locallogout()); synchronousResumeCreateAccountEphemeralPlusPlus(0, mApi[0].getSid().c_str()); ASSERT_EQ(API_OK, mApi[0].lastError) << "Account creation failed after resume (error: " << mApi[0].lastError << ")"; gSessionIDs[0] = "invalid"; } bool veryclose(double a, double b) { double diff = b - a; double denom = fabs(a) + fabs(b); if (denom == 0) { return diff == 0; } double ratio = fabs(diff / denom); return ratio * 1000000 < 1; } TEST_F(SdkTest, SdkTestKillSession) { // Convenience. using MegaAccountSessionPtr = std::unique_ptr; // Make sure environment variable are restored. auto accounts = makeScopedValue(getEnvVarAccounts(), EnvVarAccounts{2, {"MEGA_EMAIL", "MEGA_PWD"}}); // prevent reusing a session for the wrong client gSessionIDs[1] = "invalid"; // Get two sessions for the same account. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Confirm they really are using the same account unique_ptr client0userhandle(megaApi[0]->getMyUserHandle()); unique_ptr client1userhandle(megaApi[1]->getMyUserHandle()); ASSERT_EQ(string(client0userhandle.get()), string(client1userhandle.get())); // Make sure the sessions aren't reused. gSessionIDs[0] = "invalid"; gSessionIDs[1] = "invalid"; // Get our hands on the second client's session. MegaHandle sessionHandle = UNDEF; auto result = synchronousGetExtendedAccountDetails(1, true); ASSERT_EQ(result, API_OK) << "GetExtendedAccountDetails failed (error: " << result << ")"; int matches = 0; for (int i = 0; i < mApi[1].accountDetails->getNumSessions(); ) { MegaAccountSessionPtr session; session.reset(mApi[1].accountDetails->getSession(i++)); if (session->isAlive() && session->isCurrent()) { sessionHandle = session->getHandle(); matches += 1; } } if (matches > 1) { // kill the other sessions so that we succeed on the next test run synchronousKillSession(0, INVALID_HANDLE); } ASSERT_EQ(matches, 1) << "There were more alive+current sessions for client 1 than expected. Those should have been killed now for the next run"; // Were we able to retrieve the second client's session handle? ASSERT_NE(sessionHandle, UNDEF) << "Unable to get second client's session handle."; // Kill the second client's session (via the first.) result = synchronousKillSession(0, sessionHandle); ASSERT_EQ(result, API_OK) << "Unable to kill second client's session (error: " << result << ")"; // Wait for the second client to become logged out (to confirm it does). ASSERT_TRUE(WaitFor([&]() { return mApi[1].megaApi->isLoggedIn() == 0; }, 80 * 1000)); // Log out the primary account. logout(0, false, maxTimeout); gSessionIDs[0] = "invalid"; } /** * @brief Test that verifies behavior when uploading duplicated files. * * - TEST1: upload localPath with filename `testfile to Folder1 => Node1 created with NodeVersion_1 * - TEST2: upload localPath again to Folder1 => No remote action required * - TEST3: upload localPathAux with filename `testfile to Folder1 => NodeVersion_2 added to Node1 * - TEST4: upload localPath with filename `testfile` to Folder1 => NodeVersion_3 added to Node1 * - TEST5: upload localPath again to Folder1 => No remote action required * - TEST6: upload localPath with filename `testfile_copy to Folder1 => Perform remote copy, Node2 * created with NodeVersion_1 * - TEST7: upload localPathAux to Folder2 => Perform remote copy, Node3 created with NodeVersion_1 * - TEST8: upload localPathAux with filename `testfile_copy_2` to Folder2 => Perform remote copy, * Node4 created with NodeVersion_1 */ TEST_F(SdkTest, SdkTestUploadDuplicatedFiles) { const std::string logPre{"TEST SdkTestUploadDuplicatedFiles: "}; LOG_info << logPre << "#### Test preconditions. get accounts ####"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << logPre << "#### Test preconditions. create required local folders/files ####"; unsigned idx = 0; std::unique_ptr rootnode{megaApi[idx]->getRootNode()}; ASSERT_TRUE(rootnode) << logPre << "Cannot get root node for account: " << idx; const std::string rootFolderName{"testfolder1"}; auto [errCode, fh] = createRemoteFolder(0, rootFolderName.c_str(), rootnode.get()); ASSERT_TRUE(errCode == API_OK && fh != INVALID_HANDLE) << "Cannot create " << rootFolderName; auto [errCode1, fh1] = createRemoteFolder(0, rootFolderName.c_str(), rootnode.get()); ASSERT_EQ(errCode1, API_EEXIST) << "Unexpected ErrCode upon creating same folder again: " << rootFolderName; std::unique_ptr folderNode1(megaApi[0]->getNodeByHandle(fh1)); ASSERT_TRUE(folderNode1) << "Cannot retrieve folderNode1"; std::unique_ptr rootFolderNode{megaApi[idx]->getNodeByHandle(fh)}; ASSERT_TRUE(rootFolderNode) << logPre << "Cannot get " << rootFolderName << " node for account: " << idx; ASSERT_EQ(megaApi[idx]->getNumChildren(rootFolderNode.get()), 0); auto [errCode2, fh2] = createRemoteFolder(0, rootFolderName.c_str(), folderNode1.get()); ASSERT_EQ(errCode2, API_OK) << "Unexpected ErrCode upon creating same folder name inside previous one: " << rootFolderName << "/" << rootFolderName; rootFolderNode.reset(megaApi[idx]->getNodeByHandle(fh)); ASSERT_TRUE(rootFolderNode) << logPre << "Cannot get " << rootFolderName << " node for account: " << idx; ASSERT_EQ(megaApi[idx]->getNumChildren(rootFolderNode.get()), 1) << "Unexpected number of children"; string localPath = "testfile"; string localPathAux = "testfile_copy"; ASSERT_TRUE(createFile(localPath, false)) << "Couldn't create " << localPath; ASSERT_NO_FATAL_FAILURE(copyFile(localPath, localPathAux)) << "Couldn't copy " << localPath << "into" << localPathAux; sdk_test::appendToFile(fs::path(localPathAux), 20000); MegaHandle previousHandle = UNDEF; auto uploadFile = [this, &logPre, &previousHandle, idx](MegaNode* parent, const string& localPath, const string& fileName, const string& msg, const int expErr, const int expParentChildren, const int expNversions, const bool expFullUpload, const bool expSameNodeFound) { LOG_debug << logPre << msg; MegaHandle h = UNDEF; const auto [errCode, transferSpeed, transferMeanSpeed] = doStartUploadWithSpeed(idx, &h, localPath.c_str(), parent, fileName.c_str(), ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/); ASSERT_EQ(errCode, expErr) << msg << " unexpected error: " << errCode << "(" << MegaError::getErrorString(errCode) << ")"; ASSERT_EQ(megaApi[idx]->getNumChildren(parent), expParentChildren) << "unexpected number of children"; ASSERT_TRUE(previousHandle != h || expSameNodeFound) << "Unexpected upload node handle for " << (expFullUpload ? "expected full upload. " : "expected clone node. ") << "Upload handle: (" << string{Base64Str(h)} + "), " << "Expected handle: (" << string{Base64Str(previousHandle)} + ")"; previousHandle = h; std::unique_ptr nn(megaApi[idx]->getNodeByHandle(h)); ASSERT_TRUE(nn) << "Cannot retrieve node"; ASSERT_EQ(megaApi[idx]->getNumVersions(nn.get()), expNversions); if (expFullUpload) { ASSERT_GT(transferSpeed, 0) << msg << " full file upload expected, but transferspeed is Zero"; ASSERT_GT(transferMeanSpeed, 0) << msg << " full file upload expected, but transferMeanSpeed is Zero"; } else { ASSERT_LE(transferSpeed, 0) << msg << " unexpected full file upload, but transferSpeed is greater than Zero"; ASSERT_LE(transferMeanSpeed, 0) << msg << " unexpected full file upload, but transferMeanSpeed is greater than Zero"; } }; std::string msg = "#### TEST1: upload localPath with filename `testfile to Folder1 => Node1 " "created with NodeVersion_1 ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPath, "testfile", msg, API_OK, 2 /*expParentChildren*/, 1 /*expNversions*/, true /*expFullUpload*/, false /*expSameNodeFound*/)); msg = "#### TEST2: upload localPath again to Folder1 => No remote action " "required ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPath, "testfile", msg, API_OK, 2 /*expParentChildren*/, 1 /*expNversions*/, false /*expFullUpload*/, true /*expSameNodeFound*/)); msg = "#### TEST3: upload localPathAux with filename `testfile to Folder1 => " "NodeVersion_2 added to Node1 ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPathAux, "testfile", msg, API_OK, 2 /*expParentChildren*/, 2 /*expNversions*/, true /*expFullUpload*/, false /*expSameNodeFound*/)); msg = "#### TEST4: upload localPath with filename `testfile` to Folder1 => NodeVersion_3 added " "to Node1 ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPath, "testfile", msg, API_OK, 2 /*expParentChildren*/, 3 /*expNversions*/, false /*expFullUpload*/, false /*expSameNodeFound*/)); msg = "#### TEST5: upload localPath again to Folder1 => No remote action " "required ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPath, "testfile", msg, API_OK, 2 /*expParentChildren*/, 3 /*expNversions*/, false /*expFullUpload*/, true /*expSameNodeFound*/)); msg = "#### TEST6: upload localPath with filename `testfile_copy to Folder1 => " "Perform remote copy, Node2 created with NodeVersion_1 ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootFolderNode.get(), localPath, "testfile_copy", msg, API_OK, 3 /*expParentChildren*/, 1 /*expNversions*/, false /*expFullUpload*/, false /*expSameNodeFound*/)); msg = "#### TEST7: upload localPathAux to Folder2 => Perform remote copy, Node3 created with " "NodeVersion_1" "####"; folderNode1.reset(megaApi[0]->getNodeByHandle(fh2)); ASSERT_TRUE(folderNode1) << logPre << "Cannot get " << rootFolderName << "/" << rootFolderName << " node for account: " << idx; ASSERT_NO_FATAL_FAILURE(uploadFile(folderNode1.get(), localPathAux, "testfile_copy", msg, API_OK, 1 /*expParentChildren*/, 1 /*expNversions*/, false /*expFullUpload*/, false /*expSameNodeFound*/)); msg = "#### TEST8: upload localPathAux with filename `testfile_copy_2` to Folder2 => Perform " "remote copy, Node4 created with NodeVersion_1 ####"; folderNode1.reset(megaApi[0]->getNodeByHandle(fh2)); ASSERT_TRUE(folderNode1) << logPre << "Cannot get " << rootFolderName << "/" << rootFolderName << " node for account: " << idx; ASSERT_NO_FATAL_FAILURE(uploadFile(folderNode1.get(), localPathAux, "testfile_copy_2", msg, API_OK, 2 /*expParentChildren*/, 1 /*expNversions*/, false /*expFullUpload*/, false /*expSameNodeFound*/)); } /** * @brief TEST_F SdkTestUploadMacReadError * * Tests that event 800036 correctly reports a read error during MAC computation * when attempting to deduplicate an upload. Uses the onMacGenerationChunkRead hook * to truncate the file mid-computation, simulating an I/O error. * * Flow: * 1. Upload a file to create a node with fingerprint + MAC * 2. Set hook to truncate file during MAC computation (after first chunk) * 3. Try to re-upload the same file (triggers fingerprint match + MAC comparison) * 4. Hook truncates file during MAC computation, causing read error * 5. Upload should proceed as full upload (can't deduplicate due to MAC read error) */ #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED TEST_F(SdkTest, SdkTestUploadMacReadError) { const auto logPre = getLogPrefix(); LOG_info << logPre << "Starting test"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode) << logPre << "Cannot get root node"; // Create a file large enough to trigger multiple chunk reads in MAC computation // MAC uses 128KB chunks, so we need >128KB to have multiple chunks const std::string testFileName = "mac_read_error_test.bin"; constexpr size_t fileSize = 256 * 1024; // 256KB = 2 MAC chunks // Create test file using sdk_test utility sdk_test::createFile(fs::path(testFileName), fileSize); ASSERT_TRUE(fs::exists(testFileName)) << logPre << "Test file doesn't exist"; ASSERT_EQ(fs::file_size(testFileName), fileSize) << logPre << "Test file has wrong size"; // Step 1: Upload the file first to create a node in the cloud LOG_info << logPre << "Uploading original file"; MegaHandle uploadedNode = UNDEF; ASSERT_EQ(API_OK, doStartUpload(0, &uploadedNode, testFileName.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << logPre << "Failed to upload original file"; std::unique_ptr originalNode(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_TRUE(originalNode) << logPre << "Cannot get uploaded node"; LOG_info << logPre << "Original file uploaded, handle: " << toNodeHandle(uploadedNode); // Step 2: Set up the hook to truncate the file during MAC computation bool hookTriggered = false; const std::string fullPath = fs::absolute(testFileName).string(); globalMegaTestHooks.onMacGenerationChunkRead = [&](const m_off_t currentOffset) { // Only trigger once, after reading some data but before finishing if (!hookTriggered && currentOffset > 0) { hookTriggered = true; LOG_info << logPre << "Hook triggered at offset " << currentOffset << ", truncating file to simulate read error"; // Truncate the file to cause subsequent reads to fail // This simulates file modification/truncation between fopen and read try { const auto truncSize = static_cast(currentOffset / 2); fs::resize_file(fullPath, truncSize); LOG_info << logPre << "File truncated to " << truncSize << " bytes"; } catch (const std::exception& e) { LOG_err << logPre << "Failed to truncate file: " << e.what(); } } }; // Cleanup hook on exit MrProper hookCleanup( [&]() { globalMegaTestHooks.onMacGenerationChunkRead = nullptr; }); // Restore the file to original size for the re-upload attempt // (fingerprint computed during startUpload should match the original) sdk_test::createFile(fs::path(testFileName), fileSize); ASSERT_EQ(fs::file_size(testFileName), fileSize) << logPre << "Test file has wrong size after recreate"; // Step 3: Try to upload the same file again // This should trigger fingerprint match -> MAC comparison -> read error (due to hook) LOG_info << logPre << "Attempting re-upload (should trigger MAC comparison)"; MegaHandle reuploadedNode = UNDEF; // Note: The upload may succeed with a full upload (not clone) due to MAC read error, // or it might be reported as a clone if the comparison happened before our hook const auto result = doStartUpload(0, &reuploadedNode, testFileName.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/); LOG_info << logPre << "Re-upload completed with result: " << result << ", hook triggered: " << (hookTriggered ? "yes" : "no"); // The test passes if: // 1. The hook was triggered (proving MAC computation happened), OR // 2. The upload succeeded (deduplication worked before hook triggered) // Either outcome is acceptable - the key is that the system handles read errors gracefully EXPECT_TRUE(hookTriggered || result == API_OK) << logPre << "Hook was not triggered and upload failed unexpectedly"; // Cleanup: remove test file and remote nodes fs::remove(testFileName); if (reuploadedNode != UNDEF && reuploadedNode != uploadedNode) { std::unique_ptr node(megaApi[0]->getNodeByHandle(reuploadedNode)); if (node) { doDeleteNode(0, node.get()); } } doDeleteNode(0, originalNode.get()); LOG_info << logPre << "Test completed"; } #endif // MEGASDK_DEBUG_TEST_HOOKS_ENABLED /** * @brief TEST_F SdkTestNodeAttributes * * */ TEST_F(SdkTest, SdkTestNodeAttributes) { LOG_info << "___TEST Node attributes___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1, false)) << "Couldn't create " << UPFILE; FileFingerprint ffp; { auto fsa = std::make_unique(); auto fa = fsa->newfileaccess(); ASSERT_TRUE(fa->fopen(LocalPath::fromAbsolutePath(filename1.c_str()), FSLogging::logOnError)); ASSERT_TRUE(ffp.genfingerprint(fa.get())); } MegaHandle uploadedNode = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode, filename1.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr n1(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_TRUE(!!n1) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; // ___ also try upload with the overload that specifies an mtime ___ auto test_mtime = m_time() - 3600; // one hour ago MegaHandle uploadedNode_mtime = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode_mtime, filename1.c_str(), rootnode.get(), (filename1+"_mtime").c_str(), // upload to a different name test_mtime, // specify mtime nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file 2"; std::unique_ptr n1_mtime(megaApi[0]->getNodeByHandle(uploadedNode_mtime)); ASSERT_TRUE(!!n1_mtime) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; ASSERT_EQ(test_mtime, n1_mtime->getModificationTime()) << "Could not set the mtime of a file upload"; ASSERT_EQ(ffp.mtime, n1->getModificationTime()) << "Normal file upload did not get the right mtime of the file"; megaApi[0]->log(2, "test postlog", __FILE__, __LINE__); // set several values that the requests will need to consolidate, some will be in the same batch megaApi[0]->setCustomNodeAttribute(n1.get(), "custom1", "value1"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom1", "value12"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom1", "value13"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom2", "value21"); WaitMillisec(100); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom2", "value22"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom2", "value23"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom3", "value31"); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom3", "value32"); RequestTracker requestTracker(megaApi[0].get()); megaApi[0]->setCustomNodeAttribute(n1.get(), "custom3", "value33", &requestTracker); // Wait for the last set node attribute request before performing the get. ASSERT_EQ(API_OK, requestTracker.waitForResult()); n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); ASSERT_STREQ("value13", n1->getCustomAttr("custom1")); ASSERT_STREQ("value23", n1->getCustomAttr("custom2")); ASSERT_STREQ("value33", n1->getCustomAttr("custom3")); // ___ Set invalid coordinates of a node (out of range) ___ ASSERT_EQ(API_EARGS, synchronousSetNodeCoordinates(0, n1.get(), -1523421.8719987255814, +6349.54)) << "Unexpected error setting invalid node coordinates"; // ___ Set invalid coordinates of a node (out of range) ___ ASSERT_EQ(API_EARGS, synchronousSetNodeCoordinates(0, n1.get(), -160.8719987255814, +49.54)) << "Unexpected error setting invalid node coordinates"; // ___ Set invalid coordinates of a node (out of range) ___ ASSERT_EQ(API_EARGS, synchronousSetNodeCoordinates(0, n1.get(), MegaNode::INVALID_COORDINATE, +69.54)) << "Unexpected error trying to reset only one coordinate"; // ___ Set coordinates of a node ___ double lat = -51.8719987255814; double lon = +179.54; ASSERT_EQ(API_OK, synchronousSetNodeCoordinates(0, n1.get(), lat, lon)) << "Cannot set node coordinates"; n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); // do same conversions to lose the same precision int buf = int(((lat + 90) / 180) * 0xFFFFFF); double res = -90 + 180 * (double) buf / 0xFFFFFF; ASSERT_EQ(res, n1->getLatitude()) << "Latitude value does not match"; buf = int((lon == 180) ? 0 : (lon + 180) / 360 * 0x01000000); res = -180 + 360 * (double) buf / 0x01000000; ASSERT_EQ(res, n1->getLongitude()) << "Longitude value does not match"; // ___ Set coordinates of a node to origin (0,0) ___ lon = 0; lat = 0; ASSERT_EQ(API_OK, synchronousSetNodeCoordinates(0, n1.get(), 0, 0)) << "Cannot set node coordinates"; n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); // do same conversions to lose the same precision buf = int(((lat + 90) / 180) * 0xFFFFFF); res = -90 + 180 * (double) buf / 0xFFFFFF; ASSERT_EQ(res, n1->getLatitude()) << "Latitude value does not match"; ASSERT_EQ(lon, n1->getLongitude()) << "Longitude value does not match"; // ___ Set coordinates of a node to border values (90,180) ___ lat = 90; lon = 180; ASSERT_EQ(API_OK, synchronousSetNodeCoordinates(0, n1.get(), lat, lon)) << "Cannot set node coordinates"; n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; bool value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon)); ASSERT_TRUE(value_ok) << "Longitude value does not match"; // ___ Set coordinates of a node to border values (-90,-180) ___ lat = -90; lon = -180; ASSERT_EQ(API_OK, synchronousSetNodeCoordinates(0, n1.get(), lat, lon)) << "Cannot set node coordinates"; n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon)); ASSERT_TRUE(value_ok) << "Longitude value does not match"; // ___ Reset coordinates of a node ___ lat = lon = MegaNode::INVALID_COORDINATE; synchronousSetNodeCoordinates(0, n1.get(), lat, lon); n1.reset(megaApi[0]->getNodeByHandle(n1->getHandle())); ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match"; ASSERT_EQ(lon, n1->getLongitude()) << "Longitude value does not match"; // ****************** also test shareable / unshareable versions: ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(0, true, true, true)) << "Cannot get account details"; // ___ set the coords (shareable) lat = -51.8719987255814; lon = +179.54; ASSERT_EQ(API_OK, synchronousSetNodeCoordinates(0, n1.get(), lat, lon)) << "Cannot set node coordinates"; // ___ get a link to the file node string nodelink = createPublicLink(0, n1.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); // ___ import the link auto importHandle = importPublicLink(1, nodelink, std::unique_ptr{megaApi[1]->getRootNode()}.get()); std::unique_ptr nimported{megaApi[1]->getNodeByHandle(importHandle)}; ASSERT_TRUE(veryclose(lat, nimported->getLatitude())) << "Latitude " << n1->getLatitude() << " value does not match " << lat; ASSERT_TRUE(veryclose(lon, nimported->getLongitude())) << "Longitude " << n1->getLongitude() << " value does not match " << lon; // ___ remove the imported node, for a clean next test ASSERT_EQ(API_OK, synchronousRemove(1, nimported.get())) << "Cannot remove a node"; // ___ again but unshareable this time - totally separate new node - set the coords (unshareable) string filename2 = "a"+UPFILE; ASSERT_TRUE(createFile(filename2, false)) << "Couldn't create " << filename2; MegaHandle uploadedNodeHande = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNodeHande, filename2.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr n2(megaApi[0]->getNodeByHandle(uploadedNodeHande)); ASSERT_NE(n2.get(), ((void*)NULL)) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; lat = -5 + -51.8719987255814; lon = -5 + +179.54; mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE] = false; megaApi[0]->setUnshareableNodeCoordinates(n2.get(), lat, lon); waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE]); ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot set unshareable node coordinates (error: " << mApi[0].lastError << ")"; // ___ confirm this user can read them std::unique_ptr selfread(megaApi[0]->getNodeByHandle(n2->getHandle())); ASSERT_TRUE(veryclose(lat, selfread->getLatitude())) << "Latitude " << n2->getLatitude() << " value does not match " << lat; ASSERT_TRUE(veryclose(lon, selfread->getLongitude())) << "Longitude " << n2->getLongitude() << " value does not match " << lon; // ___ get a link to the file node string nodelink2 = createPublicLink(0, n2.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); // ___ import the link importHandle = importPublicLink(1, nodelink2, std::unique_ptr{megaApi[1]->getRootNode()}.get()); nimported = std::unique_ptr{megaApi[1]->getNodeByHandle(importHandle)}; ASSERT_TRUE(nimported != nullptr); // ___ confirm other user cannot read them lat = nimported->getLatitude(); lon = nimported->getLongitude(); ASSERT_EQ(MegaNode::INVALID_COORDINATE, lat) << "Latitude value does not match"; ASSERT_EQ(MegaNode::INVALID_COORDINATE, lon) << "Longitude value does not match"; // exercise all the cases for 'l' command: // delete existing link on node bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(n2->getHandle(), MegaNode::CHANGE_TYPE_PUBLIC_LINK, check); ASSERT_EQ(API_OK, doDisableExport(0, n2.get())); waitForResponse(&check); resetOnNodeUpdateCompletionCBs(); // create on existing node, no link yet ASSERT_EQ(API_OK, doExportNode(0, n2.get(), 0, false, false)); // create on existing node, with link already (different command response) ASSERT_EQ(API_OK, doExportNode(0, n2.get(), 0, false, false)); // create on non existent node ASSERT_EQ(API_EARGS, doExportNode(0, nullptr, 0, false, false)); } TEST_F(SdkTest, SdkTestExerciseOtherCommands) { LOG_info << "___TEST SdkTestExerciseOtherCommands___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); /*bool HttpReqCommandPutFA::procresult(Result r) bool CommandGetFA::procresult(Result r) bool CommandAttachFA::procresult(Result r) bool CommandPutFileBackgroundURL::procresult(Result r) bool CommandPutNodes::procresult(Result r) bool CommandDelVersions::procresult(Result r) bool CommandKillSessions::procresult(Result r) bool CommandEnumerateQuotaItems::procresult(Result r) bool CommandPurchaseAddItem::procresult(Result r) bool CommandPurchaseCheckout::procresult(Result r) bool CommandPutMultipleUAVer::procresult(Result r) bool CommandPutUAVer::procresult(Result r) bool CommandDelUA::procresult(Result r) bool CommandSendDevCommand::procresult(Result r) bool CommandGetUserEmail::procresult(Result r) bool CommandGetMiscFlags::procresult(Result r) bool CommandQueryTransferQuota::procresult(Result r) bool CommandGetUserTransactions::procresult(Result r) bool CommandGetUserPurchases::procresult(Result r) bool CommandGetUserSessions::procresult(Result r) bool CommandSetMasterKey::procresult(Result r) bool CommandCreateEphemeralSession::procresult(Result r) bool CommandResumeEphemeralSession::procresult(Result r) bool CommandCancelSignup::procresult(Result r) bool CommandWhyAmIblocked::procresult(Result r) bool CommandSendSignupLink2::procresult(Result r) bool CommandConfirmSignupLink2::procresult(Result r) bool CommandSetKeyPair::procresult(Result r) bool CommandSubmitPurchaseReceipt::procresult(Result r) bool CommandCreditCardStore::procresult(Result r) bool CommandCreditCardQuerySubscriptions::procresult(Result r) bool CommandCreditCardCancelSubscriptions::procresult(Result r) bool CommandCopySession::procresult(Result r) bool CommandGetPaymentMethods::procresult(Result r) bool CommandSendReport::procresult(Result r) bool CommandSupportTicket::procresult(Result r) bool CommandCleanRubbishBin::procresult(Result r) bool CommandGetRecoveryLink::procresult(Result r) bool CommandQueryRecoveryLink::procresult(Result r) bool CommandGetPrivateKey::procresult(Result r) bool CommandConfirmRecoveryLink::procresult(Result r) bool CommandConfirmCancelLink::procresult(Result r) bool CommandResendVerificationEmail::procresult(Result r) bool CommandResetSmsVerifiedPhoneNumber::procresult(Result r) bool CommandValidatePassword::procresult(Result r) bool CommandGetEmailLink::procresult(Result r) bool CommandConfirmEmailLink::procresult(Result r) bool CommandGetVersion::procresult(Result r) bool CommandGetLocalSSLCertificate::procresult(Result r) bool CommandChatGrantAccess::procresult(Result r) bool CommandChatRemoveAccess::procresult(Result r) bool CommandChatTruncate::procresult(Result r) bool CommandChatSetTitle::procresult(Result r) bool CommandChatPresenceURL::procresult(Result r) bool CommandRegisterPushNotification::procresult(Result r) bool CommandArchiveChat::procresult(Result r) bool CommandSetChatRetentionTime::procresult(Result r) bool CommandRichLink::procresult(Result r) bool CommandChatLink::procresult(Result r) bool CommandChatLinkURL::procresult(Result r) bool CommandChatLinkClose::procresult(Result r) bool CommandChatLinkJoin::procresult(Result r) bool CommandGetMegaAchievements::procresult(Result r) bool CommandMediaCodecs::procresult(Result r) bool CommandContactLinkCreate::procresult(Result r) bool CommandContactLinkQuery::procresult(Result r) bool CommandContactLinkDelete::procresult(Result r) bool CommandKeepMeAlive::procresult(Result r) bool CommandMultiFactorAuthSetup::procresult(Result r) bool CommandMultiFactorAuthCheck::procresult(Result r) bool CommandMultiFactorAuthDisable::procresult(Result r) bool CommandGetPSA::procresult(Result r) bool CommandSetLastAcknowledged::procresult(Result r) bool CommandSMSVerificationSend::procresult(Result r) bool CommandSMSVerificationCheck::procresult(Result r) bool CommandFolderLinkInfo::procresult(Result r) bool CommandBackupPut::procresult(Result r) bool CommandBackupPutHeartBeat::procresult(Result r) bool CommandBackupRemove::procresult(Result r)*/ } /** * @brief TEST_F SdkTestResumeSession * * It creates a local cache, logs out of the current session and tries to resume it later. */ TEST_F(SdkTest, SdkTestResumeSession) { LOG_info << "___TEST Resume session___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE( locallogout() ); ASSERT_NO_FATAL_FAILURE( resumeSession(session.get()) ); ASSERT_NO_FATAL_FAILURE( fetchnodes(0) ); } /** * @brief TEST_F SdkTestNodeOperations * * It performs different operations with nodes, assuming the Cloud folder is empty at the beginning. * * - Create a new folder * - Rename a node * - Copy a node * - Get child nodes of given node * - Get child node by name * - Get node by path * - Get node by name * - Move a node * - Get parent node * - Move a node to Rubbish bin * - Remove a node */ TEST_F(SdkTest, SdkTestNodeOperations) { LOG_info << "___TEST Node operations___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // --- Create a new folder --- MegaNode *rootnode = megaApi[0]->getRootNode(); char name1[64] = "New folder"; auto nh = createFolder(0, name1, rootnode); ASSERT_NE(nh, UNDEF); // --- Rename a node --- MegaNode *n1 = megaApi[0]->getNodeByHandle(nh); strcpy(name1, "Folder renamed"); ASSERT_EQ(API_OK, doRenameNode(0, n1, name1)); // --- Copy a node --- MegaNode *n2; char name2[64] = "Folder copy"; MegaHandle nodeCopiedHandle = UNDEF; ASSERT_EQ(API_OK, doCopyNode(0, &nodeCopiedHandle, n1, rootnode, name2)) << "Cannot create a copy of a node"; n2 = megaApi[0]->getNodeByHandle(nodeCopiedHandle); // --- Get child nodes --- MegaNodeList *children; children = megaApi[0]->getChildren(rootnode); EXPECT_EQ(megaApi[0]->getNumChildren(rootnode), children->size()) << "Wrong number of child nodes"; ASSERT_LE(2, children->size()) << "Wrong number of children nodes found"; EXPECT_STREQ(name2, children->get(0)->getName()) << "Wrong name of child node"; // "Folder copy" EXPECT_STREQ(name1, children->get(1)->getName()) << "Wrong name of child node"; // "Folder rename" delete children; // --- Get child node by name --- MegaNode *n3; n3 = megaApi[0]->getChildNode(rootnode, name2); bool null_pointer = (n3 == NULL); EXPECT_FALSE(null_pointer) << "Child node by name not found"; // ASSERT_EQ(n2->getHandle(), n3->getHandle()); This test may fail due to multiple nodes with the same name // --- Get node by path --- char path[128] = "/Folder copy"; MegaNode *n4; n4 = megaApi[0]->getNodeByPath(path); null_pointer = (n4 == NULL); EXPECT_FALSE(null_pointer) << "Node by path not found"; // --- Search for a node --- std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName("copy"); filterResults->byLocationHandle(rootnode->getHandle()); std::unique_ptr nlist(megaApi[0]->search(filterResults.get())); ASSERT_EQ(1, nlist->size()); EXPECT_EQ(n4->getHandle(), nlist->get(0)->getHandle()) << "Search node by pattern failed"; // --- Move a node --- ASSERT_EQ(API_OK, doMoveNode(0, nullptr, n1, n2)) << "Cannot move node"; // --- Get parent node --- MegaNode *n5; n5 = megaApi[0]->getParentNode(n1); ASSERT_EQ(n2->getHandle(), n5->getHandle()) << "Wrong parent node"; // --- Send to Rubbish bin --- std::unique_ptr rubbishNode(megaApi[0]->getRubbishNode()); ASSERT_EQ(API_OK, doMoveNode(0, nullptr, n2, rubbishNode.get())) << "Cannot move node to Rubbish bin"; // -- Test node movement to Rubbish bin with a file conatining public link -- auto sn = createFolder(0, "ShareIt", rootnode); ASSERT_NE(sn, UNDEF); sdk_test::LocalTempFile fLinkFile("testlink.txt", 1); MegaHandle sharedFileHandle = INVALID_HANDLE; std::unique_ptr containerNode(megaApi[0]->getNodeByHandle(sn)); // Upload a file to a container folder ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &sharedFileHandle, "testlink.txt", containerNode.get(), "testlink.txt", ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, false, false, nullptr)) << "Cannot update the test file"; ASSERT_NE(sharedFileHandle, INVALID_HANDLE); { // -- Test public link creation --- std::unique_ptr fileNode(megaApi[0]->getNodeByHandle(sharedFileHandle)); string publicLink = createPublicLink(0, fileNode.get(), 0, 1, false); ASSERT_TRUE(publicLink.length() > 0) << "Failed to crate public link for test file"; std::unique_ptr sharedFileNode(megaApi[0]->getNodeByHandle(sharedFileHandle)); ASSERT_NE(INVALID_HANDLE, sharedFileNode.get()->getPublicHandle()) << "Failed to crate public link for test file"; } // -- Move the contaner folder (hence the file) to Rubbish bin -- ASSERT_EQ(API_OK, doMoveNode(0, nullptr, containerNode.get(), rubbishNode.get())) << "Cannot move node to Rubbish bin"; { // -- Test if link has been removed after moving to Rubbish bin -- std::unique_ptr sharedFileNode(megaApi[0]->getNodeByHandle(sharedFileHandle)); ASSERT_TRUE((sharedFileNode.get())->getPublicLink() == nullptr) << "Failed to remove public link for test file after moving to Rubbish bin"; ASSERT_EQ(INVALID_HANDLE, sharedFileNode.get()->getPublicHandle()) << "Failed to remove public link for test file after moving to Rubbish bin"; } // --- Remove a node --- ASSERT_EQ(API_OK, synchronousRemove(0, n2)) << "Cannot remove a node"; delete rootnode; delete n1; delete n2; delete n3; delete n4; delete n5; } class SdkTestDownload: public SdkTest {}; /** * This test tries to download a File node into a local folder, that already contains a folder with * the same name as downloaded file. * * Note: We call MegaApi::startDownload with collisionCheck(COLLISION_CHECK_ASSUMEDIFFERENT) and * collisionResolution(COLLISION_RESOLUTION_OVERWRITE), so transfer will be retried sometimes by SDK * and finally will fail with API_EWRITE. */ TEST_F(SdkTestDownload, ConflictFolderExistingName) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << cwd(); fs::path basePath = fs::current_path(); const std::string itemName{"testItem"}; std::unique_ptr rootNode{megaApi[0]->getRootNode()}; LOG_debug << "#### TEST1: Create Folder in local FS ####"; sdk_test::LocalTempDir d(basePath / itemName); LOG_debug << "#### TEST2: Create File in cloud drive ####"; const auto newNode = sdk_test::uploadFile(megaApi[0].get(), sdk_test::LocalTempFile{basePath / itemName / itemName, 1}, rootNode.get()); ASSERT_TRUE(newNode) << "Cannot create node in Cloud Drive"; LOG_debug << "#### TEST3: Download file at dir with Folder with same name ####"; const auto errCode = sdk_test::downloadNode(megaApi[0].get(), newNode.get(), basePath / itemName, false, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE); ASSERT_TRUE(errCode.has_value()) << "test_utils(downloadFile) has returned nullopt"; ASSERT_EQ(*errCode, API_EWRITE) << "test_utils(downloadFile) has returned unexpected errorCode: " << errCode.has_value(); CASE_info << "finished"; } /** * This test tries to download a File node into a local folder, that already contains a file with * the same name as downloaded file. * * We download with collisionCheck(COLLISION_CHECK_FINGERPRINT) and * collisionResolution(COLLISION_RESOLUTION_NEW_WITH_N), so transfer will completed successfully * with skipping download as fingerprints match. */ TEST_F(SdkTestDownload, ConflictFileExistingName) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const std::string itemName{"testItem"}; const fs::path basePath = fs::current_path(); LOG_debug << "#### TEST1: Create Folder and File in local FS " << path_u8string(basePath) << "####"; constexpr long long FILE_SIZE = 1; const sdk_test::LocalTempDir d{basePath / itemName}; const sdk_test::LocalTempFile f{basePath / itemName / itemName, static_cast(FILE_SIZE)}; LOG_debug << "#### TEST2: Create File in cloud drive ####"; const std::unique_ptr rootNode{megaApi[0]->getRootNode()}; const auto newNode = sdk_test::uploadFile(megaApi[0].get(), f.getPath(), rootNode.get()); ASSERT_TRUE(newNode) << "Cannot create node in Cloud Drive"; LOG_debug << "#### TEST3: Download file at dir with a file with same name ####"; std::shared_ptr transfer; auto onTransferFinish = [&transfer](::mega::MegaApi*, ::mega::MegaTransfer* t, ::mega::MegaError*) { if (t) transfer.reset(t->copy()); }; const auto errCode = sdk_test::downloadNode(megaApi[0].get(), newNode.get(), basePath / itemName, true, 180s /*timeout*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, onTransferFinish); ASSERT_EQ(*errCode, API_OK); ASSERT_THAT(transfer, ::testing::NotNull()); ASSERT_EQ(transfer->getState(), MegaTransfer::STATE_COMPLETED); ASSERT_EQ(transfer->getTotalBytes(), FILE_SIZE); // Check transferred bytes to confirm download is skipped ASSERT_EQ(transfer->getTransferredBytes(), 0); CASE_info << "finished"; } /** * @brief TEST_F SdkTestTransfers * * It performs different operations related to transfers in both directions: up and down. * * - Uploads an empty directory * - Starts an upload transfer and cancel it * - Starts an upload transfer, pause it, check the unique id, resume it and complete it * - Get node by fingerprint * - Get size of a node * - Download a file */ TEST_F(SdkTest, SdkTestTransfers) { LOG_info << "___TEST Transfers___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << cwd(); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); // --- Upload an empty folder --- auto createAndUploadEmptyFolder = [this](::mega::MegaTransferListener* uploadListener1) -> fs::path { if (!uploadListener1) { return fs::path{}; } fs::path p = fs::current_path() / "upload_folder_mega_auto_test_sdk"; if (fs::exists(p) && !fs::remove(p)) { return fs::path{}; } if (!fs::create_directory(p)) { return fs::path{}; } MegaUploadOptions uploadOptions; uploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto root = std::unique_ptr{megaApi[0]->getRootNode()}; const auto localPath = p.string(); megaApi[0]->startUpload(localPath, root.get(), nullptr, &uploadOptions, uploadListener1); return p; }; auto uploadListener1 = std::make_shared(megaApi[0].get()); uploadListener1->selfDeleteOnFinalCallback = uploadListener1; auto p = createAndUploadEmptyFolder(uploadListener1.get()); ASSERT_FALSE(p.empty()) << "Upload empty folder: error creating local empty folder"; ASSERT_EQ(uploadListener1->waitForResult(), API_OK) << "Upload empty folder: error uploading empty folder"; ASSERT_NE(uploadListener1->resultNodeHandle, ::mega::INVALID_HANDLE) << "Upload empty folder: node handle received in onTransferFinish is invalid"; EXPECT_TRUE(fs::remove(p)) << "Upload empty folder: error cleaning empty dir resource " << p; // --- Cancel a transfer --- MegaNode* rootnode = megaApi[0]->getRootNode(); string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1)) << "Couldn't create " << filename1; TransferTracker ttc(megaApi[0].get()); MegaUploadOptions cancelOptions; cancelOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(filename1, rootnode, nullptr, &cancelOptions, &ttc); ASSERT_EQ(API_OK, synchronousCancelTransfers(0, MegaTransfer::TYPE_UPLOAD)); ASSERT_EQ(API_EINCOMPLETE, ttc.waitForResult()); // --- Setup a global listener to capture dbid and tag on next transfer --- testing::NiceMock mockGlobalListener{megaApi[0].get()}; std::promise> dbidAndTagOnStart; EXPECT_CALL(mockGlobalListener, onTransferStart) .WillOnce( [&dbidAndTagOnStart](MegaApi*, MegaTransfer* transfer) { if (transfer) dbidAndTagOnStart.set_value({transfer->getUniqueId(), transfer->getTag()}); else dbidAndTagOnStart.set_value({0, -1}); }); megaApi[0]->addListener(&mockGlobalListener); // --- Upload a file (part 1) --- TransferTracker tt(megaApi[0].get()); mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; MegaUploadOptions uploadOptions2; uploadOptions2.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(filename1, rootnode, nullptr, &uploadOptions2, &tt); /*MegaTransferListener*/ // do not wait yet for completion // --- Pause a transfer --- mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false; megaApi[0]->pauseTransfers(true, MegaTransfer::TYPE_UPLOAD); ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) ) << "Pause of transfers failed after " << maxTimeout << " seconds"; EXPECT_EQ(API_OK, mApi[0].lastError) << "Cannot pause transfer (error: " << mApi[0].lastError << ")"; EXPECT_TRUE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not paused"; // --- Get dbid and tag of first transfer since started listening --- auto transferListerResult = dbidAndTagOnStart.get_future(); ASSERT_EQ(transferListerResult.wait_for(std::chrono::seconds(maxTimeout)), std::future_status::ready) << "Timeout for the start upload"; megaApi[0]->removeListener(&mockGlobalListener); // not needed any longer const auto [transferUniqueId, transferTag] = transferListerResult.get(); ASSERT_NE(transferTag, -1) << "Missing transfer param for onTransferStart event"; std::unique_ptr transferByUniqueId{ megaApi[0]->getTransferByUniqueId(transferUniqueId)}; ASSERT_TRUE(transferByUniqueId) << "No transfer found with unique Id " << transferUniqueId; EXPECT_EQ(transferTag, transferByUniqueId->getTag()) << "Retrieved transfer doesn't match expected tag"; transferByUniqueId.reset(megaApi[0]->getTransferByUniqueId(transferUniqueId + 1)); EXPECT_FALSE(transferByUniqueId) << "This use case doesn't expect any other active or in pause transfers"; // --- Resume a transfer --- mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false; megaApi[0]->pauseTransfers(false, MegaTransfer::TYPE_UPLOAD); ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) ) << "Resumption of transfers after pause has failed after " << maxTimeout << " seconds"; EXPECT_EQ(API_OK, mApi[0].lastError) << "Cannot resume transfer (error: " << mApi[0].lastError << ")"; EXPECT_FALSE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not resumed"; // --- Upload a file (part 2) --- ASSERT_EQ(API_OK,tt.waitForResult()) << "Cannot upload file (error: " << mApi[0].lastError << ")"; std::unique_ptr n1(megaApi[0]->getNodeByHandle(tt.resultNodeHandle)); bool null_pointer = (n1.get() == NULL); ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")"; ASSERT_STREQ(filename1.c_str(), n1->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; ASSERT_EQ(API_OK, doSetFileVersionsOption(0, false)); // false = not disabled // Upload a file over an existing one to make a version { ofstream f(filename1); f << "edited"; } ASSERT_EQ(API_OK, doStartUpload(0, nullptr, filename1.c_str(), rootnode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); // Upload a file over an existing one to make a version { ofstream f(filename1); f << "edited2"; } ASSERT_EQ(API_OK, doStartUpload(0, nullptr, filename1.c_str(), rootnode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); // copy a node with versions to a new name (exercises the multi node putndoes_result) std::unique_ptr nodeToCopy1(megaApi[0]->getNodeByPath(("/" + filename1).c_str())); ASSERT_EQ(API_OK, doCopyNode(0, nullptr, nodeToCopy1.get(), rootnode, "some_other_name")); // put original filename1 back fs::remove(filename1); ASSERT_TRUE(createFile(filename1)) << "Couldn't create " << filename1; ASSERT_EQ(API_OK, doStartUpload(0, nullptr, filename1.c_str(), rootnode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); n1.reset(megaApi[0]->getNodeByPath(("/" + filename1).c_str())); // --- Get node by fingerprint (needs to be a file, not a folder) --- const char* fingerprint = n1->getFingerprint(); MegaNode* n2 = megaApi[0]->getNodeByFingerprint(fingerprint); null_pointer = (n2 == NULL); EXPECT_FALSE(null_pointer) << "Node by fingerprint not found"; // ASSERT_EQ(n2->getHandle(), n4->getHandle()); This test may fail due to multiple nodes with the same name // --- Get the size of a file --- int64_t filesize = getFilesize(filename1); int64_t nodesize = megaApi[0]->getSize(n2); EXPECT_EQ(filesize, nodesize) << "Wrong size of uploaded file"; // --- Download a file --- string filename2 = DOTSLASH + DOWNFILE; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n2, filename2.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download transfer failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; MegaNode *n3 = megaApi[0]->getNodeByHandle(n2->getHandle()); null_pointer = (n3 == NULL); ASSERT_FALSE(null_pointer) << "Cannot download node"; ASSERT_EQ(n2->getHandle(), n3->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")"; // --- Upload a 0-bytes file --- string filename3 = EMPTYFILE; FILE *fp = fopen(filename3.c_str(), "w"); fclose(fp); MegaHandle uploadedNodeHande = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNodeHande, filename3.c_str(), rootnode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; MegaNode *n4 = megaApi[0]->getNodeByHandle(uploadedNodeHande); null_pointer = (n4 == NULL); ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")"; ASSERT_STREQ(filename3.c_str(), n4->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; // --- Download a 0-byte file --- filename3 = DOTSLASH + EMPTYFILE; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n4, filename3.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) ) << "Download 0-byte file failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; MegaNode *n5 = megaApi[0]->getNodeByHandle(n4->getHandle()); null_pointer = (n5 == NULL); ASSERT_FALSE(null_pointer) << "Cannot download node"; ASSERT_EQ(n4->getHandle(), n5->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")"; delete rootnode; delete n2; delete n3; delete n4; delete n5; } /** * @brief TEST_F SdkTestUndelete * * Undelete files that have been completely removed and their node no longer exists in the online account * * - Validate the account - undelete can only work with a PRO account * - Upload a file * - Unlink the file * - Undelete the file */ TEST_F(SdkTest, SdkTestUndelete) { LOG_info << "___TEST Undelete___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "# Set " << megaApi[0]->getMyEmail() << " account to Pro I plan"; auto restorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer), API_OK); LOG_info << cwd(); // --- Upload a file --- ASSERT_TRUE(createFile(UPFILE, false)) << "Couldn't create " << UPFILE; std::unique_ptr rootnode(megaApi[0]->getRootNode()); MegaHandle uploadedNodeHande = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNodeHande, UPFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << UPFILE; std::unique_ptr fileNode(megaApi[0]->getNodeByHandle(uploadedNodeHande)); ASSERT_TRUE(fileNode) << "Cannot upload file (error: " << mApi[0].lastError << ")"; ASSERT_STREQ(UPFILE.c_str(), fileNode->getName()) << "Uploaded file with wrong name"; // --- Download the file --- string fileToDownload = UPFILE + "_download"; TransferTracker downloadNodeTracker(megaApi[0].get()); megaApi[0]->startDownload(fileNode.get(), fileToDownload.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/, &downloadNodeTracker); EXPECT_EQ(downloadNodeTracker.waitForResult(), API_OK) << "Failed to download n2."; // --- Unlink the file --- RequestTracker removeNodeTracker(megaApi[0].get()); megaApi[0]->remove(fileNode.get(), &removeNodeTracker); EXPECT_EQ(removeNodeTracker.waitForResult(), API_OK) << "Failed to remove n2."; // --- Undelete the file --- string fileToUndelete = UPFILE + "_undeleted"; TransferTracker undeleteNodeTracker(megaApi[0].get()); megaApi[0]->startDownload(fileNode.get(), fileToUndelete.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, true /*undelete*/, &undeleteNodeTracker); EXPECT_EQ(undeleteNodeTracker.waitForResult(), API_OK) << "Failed to undelete n2."; } /** * @brief TEST_F SdkTestContacts * * Creates an auxiliar 'MegaApi' object to interact with the main MEGA account. * * - Invite a contact * = Ignore the invitation * - Delete the invitation * * - Invite a contact * = Deny the invitation * * - Invite a contact * = Accept the invitation * * - Modify firstname * = Check firstname of a contact * = Set master key as exported * = Get preferred language * - Load avatar * = Check avatar of a contact * - Delete avatar * = Check non-existing avatar of a contact * * - Remove contact * * TODO: * - Invite a contact not registered in MEGA yet (requires validation of account) * - Remind an existing invitation (requires 2 weeks wait) */ TEST_F(SdkTest, SdkTestContacts) { LOG_info << "___TEST Contacts___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + AVATARSRC, AVATARSRC)); // --- Check my email and the email of the contact --- EXPECT_STRCASEEQ(mApi[0].email.c_str(), std::unique_ptr{megaApi[0]->getMyEmail()}.get()); EXPECT_STRCASEEQ(mApi[1].email.c_str(), std::unique_ptr{megaApi[1]->getMyEmail()}.get()); // --- Send a new contact request --- string message = "Hi contact. This is a testing message"; mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); // if there were too many invitations within a short period of time, the invitation can be rejected by // the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in the last 50 days) // --- Check the sent contact request --- ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true) ); ASSERT_STREQ(message.c_str(), mApi[0].cr->getSourceMessage()) << "Message sent is corrupted"; ASSERT_STRCASEEQ(mApi[0].email.c_str(), mApi[0].cr->getSourceEmail()) << "Wrong source email"; ASSERT_STRCASEEQ(mApi[1].email.c_str(), mApi[0].cr->getTargetEmail()) << "Wrong target email"; ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[0].cr->getStatus()) << "Wrong contact request status"; ASSERT_TRUE(mApi[0].cr->isOutgoing()) << "Wrong direction of the contact request"; mApi[0].cr.reset(); // --- Check received contact request --- ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); // There isn't message when a user invites the same user too many times, to avoid spamming if (mApi[1].cr->getSourceMessage()) { ASSERT_STREQ(message.c_str(), mApi[1].cr->getSourceMessage()) << "Message received is corrupted"; } ASSERT_STRCASEEQ(mApi[0].email.c_str(), mApi[1].cr->getSourceEmail()) << "Wrong source email"; ASSERT_STREQ(NULL, mApi[1].cr->getTargetEmail()) << "Wrong target email"; // NULL according to MegaApi documentation ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[1].cr->getStatus()) << "Wrong contact request status"; ASSERT_FALSE(mApi[1].cr->isOutgoing()) << "Wrong direction of the contact request"; mApi[1].cr.reset(); // --- Ignore received contact request --- ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_IGNORE) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request update not received after " << maxTimeout << " seconds"; // Ignoring a PCR does not generate actionpackets for the account sending the invitation mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); mApi[1].cr.reset(); // --- Cancel the invitation --- message = "I don't wanna be your contact anymore"; mApi[0].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_DELETE) ); ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (auxiliar account), where the deletion is checked << "Contact request update not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); mApi[0].cr.reset(); // --- Remind a contact invitation (cannot until 2 weeks after invitation/last reminder) --- // mApi[1].contactRequestUpdated = false; // megaApi->inviteContact(mApi[1].email.c_str(), message.c_str(), MegaContactRequest::INVITE_ACTION_REMIND); // waitForResponse(&mApi[1].contactRequestUpdated, 0); // only at auxiliar account, where the deletion is checked // ASSERT_TRUE(mApi[1].contactRequestUpdated) << "Contact invitation reminder not received after " << timeout << " seconds"; // --- Invite a new contact (again) --- mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; // --- Deny a contact invitation --- ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_DENY) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); mApi[0].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); mApi[1].cr.reset(); // --- Invite a new contact (again) --- mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; // --- Accept a contact invitation --- ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) ); mApi[0].cr.reset(); ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) ); mApi[1].cr.reset(); // --- Modify firstname --- string firstname1 = "My firstname1"; // change it twice to make sure we get a change notification (in case it was already the first one) string firstname2 = "My firstname2"; mApi[1].userUpdated = false; ASSERT_EQ(API_OK, synchronousSetUserAttribute(0, MegaApi::USER_ATTR_FIRSTNAME, firstname1.c_str())); ASSERT_EQ(API_OK, synchronousSetUserAttribute(0, MegaApi::USER_ATTR_FIRSTNAME, firstname2.c_str())); // --- Check firstname of a contact MegaUser *u = megaApi[0]->getMyUser(); bool null_pointer = (u == NULL); ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_FIRSTNAME)); ASSERT_EQ(firstname2, mApi[1].getAttributeValue()) << "Firstname is wrong"; delete u; // --- Set master key already as exported u = megaApi[0]->getMyUser(); mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER] = false; megaApi[0]->masterKeyExported(); ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER]) ); ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_PWD_REMINDER, maxTimeout, 0)); string pwdReminder = mApi[0].getAttributeValue(); size_t offset = pwdReminder.find(':'); offset = pwdReminder.find(':', offset+1); ASSERT_EQ( pwdReminder.at(offset+1), '1' ) << "Password reminder attribute not updated"; delete u; // --- Get language preference u = megaApi[0]->getMyUser(); string langCode = "es"; ASSERT_EQ(API_OK, synchronousSetUserAttribute(0, MegaApi::USER_ATTR_LANGUAGE, langCode.c_str())); ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_LANGUAGE, maxTimeout, 0)); string language = mApi[0].getAttributeValue(); ASSERT_TRUE(!strcmp(langCode.c_str(), language.c_str())) << "Language code is wrong"; delete u; // --- Load avatar --- ASSERT_TRUE(fileexists(AVATARSRC)) << "File " +AVATARSRC+ " is needed in folder " << cwd(); mApi[1].userUpdated = false; ASSERT_EQ(API_OK,synchronousSetAvatar(0, nullptr)); ASSERT_EQ(API_OK,synchronousSetAvatar(0, AVATARSRC.c_str())); ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account) << "User attribute update not received after " << maxTimeout << " seconds"; // --- Get avatar of a contact --- u = megaApi[0]->getMyUser(); null_pointer = (u == NULL); ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; mApi[1].setAttributeValue(""); ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_AVATAR)); ASSERT_EQ( "Avatar changed", mApi[1].getAttributeValue()) << "Failed to change avatar"; int64_t filesizeSrc = getFilesize(AVATARSRC); int64_t filesizeDst = getFilesize(AVATARDST); ASSERT_EQ(filesizeDst, filesizeSrc) << "Received avatar differs from uploaded avatar"; delete u; // --- Delete avatar --- mApi[1].userUpdated = false; ASSERT_EQ(API_OK, synchronousSetAvatar(0, nullptr)); ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account) << "User attribute update not received after " << maxTimeout << " seconds"; // --- Get non-existing avatar of a contact --- u = megaApi[0]->getMyUser(); null_pointer = (u == NULL); ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email; mApi[1].setAttributeValue(""); ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_AVATAR)); ASSERT_EQ("Avatar not found", mApi[1].getAttributeValue()) << "Failed to remove avatar"; delete u; // --- Delete an existing contact --- ASSERT_EQ(API_OK, removeContact(0, mApi[1].email) ); u = megaApi[0]->getContact(mApi[1].email.c_str()); null_pointer = (u == NULL); ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[1].email; ASSERT_EQ(MegaUser::VISIBILITY_HIDDEN, u->getVisibility()) << "New contact is still visible"; delete u; } TEST_F(SdkTest, SdkTestAppsPrefs) { testPrefs("___TEST AppsPrefs___", MegaApi::USER_ATTR_APPS_PREFS); } TEST_F(SdkTest, SdkTestCcPrefs) { testPrefs("___TEST CcPrefs___", MegaApi::USER_ATTR_CC_PREFS); } void SdkTest::testPrefs(const std::string& title, int type) { const auto comparePrefs = [](const MegaStringMap* currentMap, const MegaStringMap* testMap) -> bool { if (!currentMap || !testMap) return false; std::unique_ptr currentKeys(currentMap->getKeys()); std::unique_ptr testKeys(testMap->getKeys()); if (!currentKeys || !testKeys) { return false; } for (int i = 0; i < testMap->size(); ++i) { // search the same key in both maps to check that pair matches with current user attr pair const char* testKey = currentKeys->get(i); const char* aVal = currentMap->get(testKey); const char* bVal = testMap->get(testKey); if (!aVal || !bVal || strcmp(aVal, bVal)) { return false; } } return true; }; const auto isPrefsUpdated = [this, &comparePrefs, type](const MegaStringMap* uprefs) -> bool { std::unique_ptr u(megaApi[0]->getMyUser()); EXPECT_TRUE(u) << "Can't get own user"; EXPECT_NO_FATAL_FAILURE(getUserAttribute(u.get(), type, maxTimeout, 0)); EXPECT_TRUE(comparePrefs(mApi[0].mStringMap.get(), uprefs)) << "ERR"; return true; }; const auto fetchPrefs = [this, type](const unsigned int index) -> int { std::unique_ptr u(megaApi[index]->getMyUser()); if (!u) { return API_ENOENT; } mApi[index].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; return synchronousGetUserAttribute(index, u.get(), type); }; LOG_info << title; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // fetch for current attr value static constexpr char keyname[] = "key1"; const unsigned int index = 0; const int res = fetchPrefs(index); ASSERT_TRUE(res == API_ENOENT || res == API_OK); // set value for attr (overwrite any posible value that could exists for keyname) std::unique_ptr newPrefs(MegaStringMap::createInstance()); std::string val = std::to_string(m_time()); unique_ptr valB64(MegaApi::binaryToBase64(val.data(), val.size())); newPrefs->set(keyname, valB64.get()); ASSERT_EQ(API_OK, synchronousSetUserAttribute(index, type, newPrefs.get())); // logout and login releaseMegaApi(index); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // check attr value is expected after logout/login ASSERT_TRUE(isPrefsUpdated(newPrefs.get())) << ""; // set value for attr again (overwrite latest value for keyname) val = std::to_string(m_time()); valB64.reset(MegaApi::binaryToBase64(val.data(), val.size())); newPrefs->set(keyname, valB64.get()); ASSERT_EQ(API_OK, synchronousSetUserAttribute(index, type, newPrefs.get())); // check attr value is expected ASSERT_TRUE(isPrefsUpdated(newPrefs.get())) << ""; } bool SdkTest::checkAlert(int apiIndex, const string& title, const string& path) { bool ok = false; for (int i = 0; !ok && i < 10; ++i) { MegaUserAlertList* list = mApi[static_cast(apiIndex)].megaApi->getUserAlerts(); if (list->size() > 0) { MegaUserAlert* a = list->get(list->size() - 1); ok = !strcasecmp(title.c_str(), a->getTitle()) && !strcasecmp(path.c_str(), a->getPath()) && !ISUNDEF(a->getNodeHandle()); if (!ok && i == 9) { EXPECT_STRCASEEQ(title.c_str(), a->getTitle()); EXPECT_STRCASEEQ(path.c_str(), a->getPath()); EXPECT_NE(a->getNodeHandle(), UNDEF); } } delete list; if (!ok) { LOG_info << "Waiting some more for the alert"; WaitMillisec(USERALERT_ARRIVAL_MILLISEC); } } return ok; } void SdkTest::printCleanupErrMsg(const string& prefix, const string& errDetails, const unsigned accountIdx, const int errCode, const bool localCleanupSuccess) const { const string errMsg = " " + prefix + " [CLEANUP_ERR] " + (localCleanupSuccess ? "[ ] " : "[TF] ") + "Account [" + std::to_string(accountIdx) + "]: " + mApi[accountIdx].email + ". " + errDetails + ". ErrCode (" + std::to_string(errCode) + "): " + MegaError::getErrorString(errCode); LOG_err << errMsg; } #ifdef ENABLE_CHAT void SdkTest::cleanupSchedMeetings(const unsigned nApi) { const std::string prefix{"SdkTest::Cleanup(CancelSchedMeetings)"}; LOG_debug << "# " << prefix; bool localCleanupSuccess{true}; unique_ptr chats(megaApi[nApi]->getChatList()); for (int i = 0u; i < chats->size(); ++i) { const MegaTextChat* c = chats->get(static_cast(i)); if (!c || c->getOwnPrivilege() != MegaTextChatPeerList::PRIV_MODERATOR || !c->getScheduledMeetingList() || !c->getScheduledMeetingList()->size()) { continue; } const char* chatTitle = c->getTitle(); auto schedList = std::unique_ptr(c->getScheduledMeetingList()->copy()); for (unsigned long j = 0; j < schedList->size(); ++j) { if (const MegaScheduledMeeting* auxSm = schedList->at(j); auxSm && !auxSm->cancelled()) { std::unique_ptr rules(auxSm->rules()); std::unique_ptr flags(auxSm->flags()); std::unique_ptr sm( MegaScheduledMeeting::createInstance(auxSm->chatid(), auxSm->schedId(), auxSm->parentSchedId(), auxSm->organizerUserid(), true /*cancelled*/, auxSm->timezone(), auxSm->startDateTime(), auxSm->endDateTime(), auxSm->title(), auxSm->description(), auxSm->attributes(), MEGA_INVALID_TIMESTAMP /*overrides*/, flags.get(), rules.get())); std::unique_ptr tracker(new RequestTracker(megaApi[nApi].get())); megaApi[nApi]->createOrUpdateScheduledMeeting(sm.get(), chatTitle, tracker.get()); if (auto reqResult = tracker->waitForResult(); reqResult != API_OK && reqResult != API_ENOENT && reqResult != API_EACCESS) { const string errDetails = "Error cancelling scheduled meeting for chat (" + string{Base64Str(auxSm->chatid())} + ")"; localCleanupSuccess = false; printCleanupErrMsg(prefix, errDetails, static_cast(nApi), reqResult, localCleanupSuccess); } } } } updateCleanupStatus(localCleanupSuccess); LOG_debug << "# " << prefix << (localCleanupSuccess ? ": OK" : ": Finished with errors"); } #endif bool SdkTest::checkAlert(int apiIndex, const string& title, handle h, int64_t n, MegaHandle mh) { bool ok = false; for (int i = 0; !ok && i < 10; ++i) { MegaUserAlertList* list = megaApi[static_cast(apiIndex)]->getUserAlerts(); if (list->size() > 0) { MegaUserAlert* a = list->get(list->size() - 1); ok = title == a->getTitle() && a->getNodeHandle() == h; if (n != -1) ok = ok && a->getNumber(0) == n; if (mh != INVALID_HANDLE) ok = ok && a->getHandle(0) == mh; if (!ok && i == 9) { EXPECT_STRCASEEQ(a->getTitle(), title.c_str()); EXPECT_EQ(a->getNodeHandle(), h); if (n != -1) { EXPECT_EQ(a->getNumber(0), n); } if (mh != INVALID_HANDLE) { EXPECT_EQ(a->getHandle(0), mh); } } } delete list; if (!ok) { LOG_info << "Waiting some more for the alert"; WaitMillisec(USERALERT_ARRIVAL_MILLISEC); } } return ok; } class SdkTestShares : public SdkTest { protected: void SetUp() override; void TearDown() override; void createNodeTrees(); MegaHandle getHandle(const std::string& path) const; void verifyCredentials(unsigned sharerIndex, const PerApi* sharer, unsigned shareeIndex, const PerApi* sharee); void createNewContactAndVerify(); void createOutgoingShare(MegaHandle hfolder); void getInshare(MegaHandle hfolder); void createOnePublicLink(MegaHandle hfolder, std::string& nodeLink); void importPublicLink(const std::string& nodeLink, MegaHandle* importedNodeHandle = nullptr); void revokeOutShares(MegaHandle hfolder); void revokePublicLink(MegaHandle hfolder); /** * @brief Makes a copy of the node located at the sourceNodePath path in the inshare folder of * the mSharee account and puts it in destNodeName inside the mSharee root node * * NOTE: This method uses ASSERT_* macros * NOTE: This method assumes you have called the getInshare method */ void copyNode(const unsigned int accountId, const MegaHandle sourceNodeHandle, const MegaHandle destNodeHandle, const std::string& destName, MegaHandle* copiedNodeHandle = nullptr); /** * @brief Same as copySharedFolderToOwnCloud but invoking move instead of copy on sourceNodePath */ void moveNodeToOwnCloud(const std::string& sourceNodePath, const std::string& destNodeName, MegaHandle* copiedNodeHandle = nullptr); std::unordered_map mHandles; // Sharer account static constexpr unsigned mSharerIndex{0}; PerApi* mSharer{nullptr}; MegaApiTest* mSharerApi{nullptr}; // Sharee account static constexpr unsigned mShareeIndex{1}; PerApi* mSharee{nullptr}; MegaApiTest* mShareeApi{nullptr}; // Guest account static constexpr unsigned mGuestIndex{2}; PerApi* mGuest{nullptr}; MegaApiTest* mGuestApi{nullptr}; std::string mGuestEmail; std::string mGuestPass; }; void SdkTestShares::SetUp() { SdkTest::SetUp(); // Accounts for sharer and sharee ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Guest for accessing the public link, No login in SetUp const auto [email, pass] = getEnvVarAccounts().getVarValues(mGuestIndex); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(mGuestIndex + 1); megaApi.resize(mGuestIndex + 1); configureTestInstance(mGuestIndex, email, pass); // Convenience mSharer = &mApi[mSharerIndex]; mSharee = &mApi[mShareeIndex]; mGuest = &mApi[mGuestIndex]; mSharerApi = megaApi[mSharerIndex].get(); mShareeApi = megaApi[mShareeIndex].get(); mGuestApi = megaApi[mGuestIndex].get(); mGuestEmail = email; mGuestPass = pass; } void SdkTestShares::TearDown() { SdkTest::TearDown(); } MegaHandle SdkTestShares::getHandle(const std::string& path) const { return mHandles.at(path); } void SdkTestShares::verifyCredentials(unsigned sharerIndex, const PerApi* sharer, unsigned shareeIndex, const PerApi* sharee) { if (!gManualVerification) return; if (!areCredentialsVerified(sharerIndex, sharee->email)) { ASSERT_NO_FATAL_FAILURE(SdkTest::verifyCredentials(sharerIndex, sharee->email)); } if (!areCredentialsVerified(shareeIndex, sharer->email)) { ASSERT_NO_FATAL_FAILURE(SdkTest::verifyCredentials(shareeIndex, sharer->email)); } } void SdkTestShares::createNewContactAndVerify() { // Invite const string message = "Hi contact. Let's share some stuff"; mSharee->contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(mSharerIndex, mSharee->email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mSharee->contactRequestUpdated, 10u)) << "Contact request creation not received by the sharee after 10 seconds"; // Get the the contact request ASSERT_NO_FATAL_FAILURE(getContactRequest(mShareeIndex, false)); // Accept the request mSharer->contactRequestUpdated = false; mSharer->contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mSharee->cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mSharee->contactRequestUpdated, 10u)) << "Contact request creation not received by the sharee after 10 seconds"; ASSERT_TRUE(waitForResponse(&mSharer->contactRequestUpdated, 10u)) << "Contact request creation not received by the sharer after 10 seconds"; mSharer->cr.reset(); // Verify credential ASSERT_NO_FATAL_FAILURE(verifyCredentials(mSharerIndex, mSharer, mShareeIndex, mSharee)); } void SdkTestShares::createOutgoingShare(MegaHandle hfolder) { std::unique_ptr node{mSharerApi->getNodeByHandle(hfolder)}; ASSERT_TRUE(node); // Create a new outgoing share bool inshareCheck = false; bool outshareCheck = false; mSharer->mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder, MegaNode::CHANGE_TYPE_OUTSHARE, outshareCheck); mSharee->mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder, MegaNode::CHANGE_TYPE_INSHARE, inshareCheck); ASSERT_NO_FATAL_FAILURE( shareFolder(node.get(), mSharee->email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&outshareCheck)) << "Node update not received by the sharer after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&inshareCheck)) << "Node update not received by the sharee after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // Important to reset ASSERT_TRUE(outshareCheck); ASSERT_TRUE(inshareCheck); // Check the outgoing share const std::unique_ptr shareList{mSharerApi->getOutShares()}; ASSERT_EQ(1, shareList->size()) << "Outgoing share failed"; const auto share = shareList->get(0); ASSERT_EQ(MegaShare::ACCESS_FULL, share->getAccess()) << "Wrong access level of outgoing share"; ASSERT_EQ(hfolder, share->getNodeHandle()) << "Wrong node handle of outgoing share"; ASSERT_STRCASEEQ(mSharee->email.c_str(), share->getUser()) << "Wrong email address of outgoing share"; // Get an updated version of the node node.reset(mSharerApi->getNodeByHandle(hfolder)); ASSERT_TRUE(node->isShared()) << "Wrong sharing information at outgoing share"; ASSERT_TRUE(node->isOutShare()) << "Wrong sharing information at outgoing share"; int accessLevel = mSharerApi->getAccess(hfolder); ASSERT_EQ(accessLevel, MegaShare::ACCESS_OWNER) << "Wrong access level for the shared folder handle"; accessLevel = mSharerApi->getAccess(node.get()); ASSERT_EQ(accessLevel, MegaShare::ACCESS_OWNER) << "Wrong access level for the shared folder node"; } // Get and Check only one incoming share void SdkTestShares::getInshare(MegaHandle hfolder) { const std::unique_ptr shareList{megaApi[1]->getInSharesList()}; ASSERT_EQ(1, shareList->size()) << "Incoming share not received in auxiliar account"; // Wait for the inshare node to be decrypted auto descryptedPred = [this, hfolder]() { return std::unique_ptr(mShareeApi->getNodeByHandle(hfolder)) ->isNodeKeyDecrypted(); }; ASSERT_TRUE(WaitFor(descryptedPred, 60 * 1000)); const std::unique_ptr contact{mShareeApi->getContact(mSharer->email.c_str())}; const std::unique_ptr inshareNodes{mShareeApi->getInShares(contact.get())}; ASSERT_EQ(1, inshareNodes->size()) << "Incoming share not received in auxiliar account"; const auto thisInshareNode = inshareNodes->get(0); ASSERT_EQ(hfolder, thisInshareNode->getHandle()) << "Wrong node handle of incoming share"; ASSERT_STREQ("sharedfolder", thisInshareNode->getName()) << "Wrong folder name of incoming share"; ASSERT_EQ(API_OK, mShareeApi->checkAccessErrorExtended(thisInshareNode, MegaShare::ACCESS_FULL) ->getErrorCode()) << "Wrong access level of incoming share"; ASSERT_TRUE(thisInshareNode->isInShare()) << "Wrong sharing information at incoming share"; ASSERT_TRUE(thisInshareNode->isShared()) << "Wrong sharing information at incoming share"; int accessLevel = mShareeApi->getAccess(hfolder); ASSERT_EQ(accessLevel, MegaShare::ACCESS_FULL) << "Wrong access level for the shared folder handle"; accessLevel = mShareeApi->getAccess(thisInshareNode); ASSERT_EQ(accessLevel, MegaShare::ACCESS_FULL) << "Wrong access level for the shared folder node"; } void SdkTestShares::createOnePublicLink(MegaHandle hfolder, std::string& nodeLink) { std::unique_ptr nfolder{mSharerApi->getNodeByHandle(hfolder)}; ASSERT_TRUE(nfolder); const bool isFreeAccount = mSharer->accountDetails->getProLevel() == MegaAccountDetails::ACCOUNT_TYPE_FREE; // Create a public link nodeLink = createPublicLink(mSharerIndex, nfolder.get(), 0, maxTimeout, isFreeAccount); // Get a fresh snapshot of the node and check it's actually exported nfolder.reset(mSharerApi->getNodeByHandle(hfolder)); ASSERT_TRUE(nfolder); ASSERT_TRUE(nfolder->isExported()) << "Node is not exported, must be exported"; ASSERT_FALSE(nfolder->isTakenDown()) << "Public link is taken down, it mustn't"; ASSERT_STREQ(nodeLink.c_str(), std::unique_ptr(nfolder->getPublicLink()).get()) << "Wrong public link from MegaNode"; // Regenerate the same link should not trigger a new request ASSERT_EQ(nodeLink, createPublicLink(mSharerIndex, nfolder.get(), 0, maxTimeout, isFreeAccount)) << "Wrong public link after link update"; } void SdkTestShares::importPublicLink(const std::string& nodeLink, MegaHandle* importedNodeHandle) { // Login to the folder and fetchnodes auto loginFolderTracker = asyncRequestLoginToFolder(mGuestIndex, nodeLink.c_str()); ASSERT_EQ(loginFolderTracker->waitForResult(), API_OK) << "Failed to login to folder " << nodeLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(mGuestIndex)); // Authorize the node std::unique_ptr folderNodeToImport{mGuestApi->getRootNode()}; ASSERT_TRUE(folderNodeToImport) << "Failed to get folder node to import from link " << nodeLink; std::unique_ptr authorizedFolderNode{ mGuestApi->authorizeNode(folderNodeToImport.get())}; ASSERT_TRUE(authorizedFolderNode) << "Failed to authorize folder node from link " << nodeLink; ASSERT_TRUE(authorizedFolderNode->getChildren()) << "Authorized folder node children list is null but it should not"; ASSERT_EQ(mGuestApi->getNumChildren(folderNodeToImport.get()), authorizedFolderNode->getChildren()->size()) << "Different number of child nodes after authorizing the folder node"; // Logout the folder ASSERT_NO_FATAL_FAILURE(logout(mGuestIndex, false, 20)); // Login with guest and fetch nodes auto loginTracker = asyncRequestLogin(mGuestIndex, mGuestEmail.c_str(), mGuestPass.c_str()); ASSERT_EQ(loginTracker->waitForResult(), API_OK) << "Failed to login with " << mGuestEmail; ASSERT_NO_FATAL_FAILURE(fetchnodes(mGuestIndex)); // Copy(import) the public folder (authorized) to the root of the account std::unique_ptr rootNode{mGuestApi->getRootNode()}; RequestTracker nodeCopyTracker{mGuestApi}; mGuestApi->copyNode(authorizedFolderNode.get(), rootNode.get(), nullptr, &nodeCopyTracker); ASSERT_EQ(nodeCopyTracker.waitForResult(), API_OK) << "Failed to copy node to import"; std::unique_ptr importedNode{ mGuestApi->getNodeByPath(authorizedFolderNode->getName(), rootNode.get())}; ASSERT_TRUE(importedNode) << "Imported node not found"; if (authorizedFolderNode->getChildren()->size()) { std::unique_ptr authorizedImportedNode( mGuestApi->authorizeNode(importedNode.get())); EXPECT_TRUE(authorizedImportedNode) << "Failed to authorize imported node"; EXPECT_TRUE(authorizedImportedNode->getChildren()) << "Authorized imported node children list is null but it should not"; ASSERT_EQ(authorizedFolderNode->getChildren()->size(), authorizedImportedNode->getChildren()->size()) << "Not all child nodes have been imported"; } if (importedNodeHandle) *importedNodeHandle = importedNode->getHandle(); } // Revoke access to an outgoing shares void SdkTestShares::revokeOutShares(MegaHandle hfolder) { const std::unique_ptr node{mSharerApi->getNodeByHandle(hfolder)}; ASSERT_TRUE(node); bool inshareCheck = false; bool outshareCheck = false; mSharer->mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder, MegaNode::CHANGE_TYPE_OUTSHARE, outshareCheck); mSharee->mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder, MegaNode::CHANGE_TYPE_REMOVED, inshareCheck); ASSERT_NO_FATAL_FAILURE( shareFolder(node.get(), mSharee->email.c_str(), MegaShare::ACCESS_UNKNOWN)); ASSERT_TRUE(waitForResponse(&outshareCheck)) // at the target side (main account) << "Node update not received by the sharer after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&inshareCheck)) // at the target side (auxiliar account) << "Node update not received by the sharee after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_TRUE(outshareCheck); ASSERT_TRUE(inshareCheck); const std::unique_ptr sl{mSharerApi->getOutShares()}; ASSERT_EQ(0, sl->size()) << "Outgoing share revocation failed"; } void SdkTestShares::revokePublicLink(MegaHandle hfolder) { // Remove std::unique_ptr node{mSharerApi->getNodeByHandle(hfolder)}; ASSERT_TRUE(node); const MegaHandle removedLinkHandle = removePublicLink(mSharerIndex, node.get()); // Get a fresh node and check node.reset(mSharerApi->getNodeByHandle(removedLinkHandle)); ASSERT_TRUE(node); ASSERT_FALSE(node->isPublic()) << "Public link removal failed (still public)"; } void SdkTestShares::copyNode(const unsigned int accountId, const MegaHandle sourceNodeHandle, const MegaHandle destNodeHandle, const std::string& destName, MegaHandle* copiedNodeHandle) { auto& api = accountId == mShareeIndex ? mShareeApi : mSharerApi; std::unique_ptr source = std::unique_ptr( sourceNodeHandle == INVALID_HANDLE ? api->getRootNode() : api->getNodeByHandle(sourceNodeHandle)); std::unique_ptr dest = std::unique_ptr( destNodeHandle == INVALID_HANDLE ? api->getRootNode() : api->getNodeByHandle(destNodeHandle)); auto result = doCopyNode(accountId, copiedNodeHandle, source.get(), dest.get(), destName.c_str()); ASSERT_EQ(result, API_OK) << "Error copying file"; if (copiedNodeHandle) { ASSERT_NE(*copiedNodeHandle, INVALID_HANDLE) << "The copied file handle was not set properly"; } } void SdkTestShares::moveNodeToOwnCloud(const std::string& sourceNodePath, const std::string& destNodeName, MegaHandle* movedNodeHandle) { std::unique_ptr source{mShareeApi->getNodeByHandle(getHandle(sourceNodePath))}; std::unique_ptr dest{mShareeApi->getRootNode()}; auto result = doMoveNode(mShareeIndex, movedNodeHandle, source.get(), dest.get(), destNodeName.c_str()); ASSERT_EQ(result, API_OK); } // Initialize a test scenario : create some folders/files to share // Create some nodes to share // |--sharedfolder // |--subfolder // |--file.txt // |--file.txt void SdkTestShares::createNodeTrees() { const std::unique_ptr rootnode{mSharerApi->getRootNode()}; const MegaHandle hfolder = mHandles["/sharedfolder"] = createFolder(mSharerIndex, "sharedfolder", rootnode.get()); ASSERT_NE(hfolder, UNDEF); const std::unique_ptr node{mSharerApi->getNodeByHandle(hfolder)}; ASSERT_TRUE(node); const MegaHandle subfolder = mHandles["/sharedfolder/subfolder"] = createFolder(mSharerIndex, "subfolder", node.get()); ASSERT_NE(subfolder, UNDEF); // Create a local file ASSERT_TRUE(createFile("file.txt", false)) << "Couldn't create " << "file.txt"; // Create a node /sharefolder/file.txt by uploading MegaHandle hfile = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(mSharerIndex, &hfile, "file.txt", node.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; mHandles["/sharedfolder/file.txt"] = hfile; // Create a node /sharedfolder/subfolder/file.txt by uploading ASSERT_EQ(MegaError::API_OK, doStartUpload(mSharerIndex, &hfile, "file.txt", node.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; mHandles["/sharedfolder/subfolder/file.txt"] = hfile; } /** * @brief TEST_F SdkTestShares2 * * - Create and upload some folders and files to User1 account * - Create a new contact to share to * - Share a folder with User2 * - Check the outgoing share from User1 * - Check the incoming share to User2 * - Check that User2 (sharee) cannot tag the incoming share as favourite * - Check that User1 (sharer) can tag the outgoing share as favourite * - Get file name and fingerprint from User1 * - Search by file name for User2 * - Search by fingerprint for User2 * - User2 add file * - Check that User1 has received the change * - User1 remove file * - Locallogout from User2 and login with session * - Check that User2 no longer sees the removed file */ TEST_F(SdkTest, SdkTestShares2) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // --- Create some nodes to share --- // |--Shared-folder // |--subfolder // |--file.txt // |--file.txt std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; static constexpr char foldername1[] = "Shared-folder"; MegaHandle hfolder1 = createFolder(0, foldername1, rootnode.get()); ASSERT_NE(hfolder1, UNDEF) << "Cannot create " << foldername1; std::unique_ptr n1{ megaApi[0]->getNodeByHandle(hfolder1) }; ASSERT_NE(n1, nullptr); static constexpr char foldername2[] = "subfolder"; MegaHandle hfolder2 = createFolder(0, foldername2, n1.get()); ASSERT_NE(hfolder2, UNDEF) << "Cannot create " << foldername2; createFile(PUBLICFILE.c_str(), false); // not a large file since don't need to test transfers here MegaHandle hfile1 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile1, PUBLICFILE.c_str(), n1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; MegaHandle hfile2 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile2, PUBLICFILE.c_str(), std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; // --- Create a new contact to share to --- string message = "Hi contact. Let's share some stuff"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); // --- Verify credentials in both accounts --- if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } // --- Share a folder with User2 --- MegaHandle nodeHandle = n1->getHandle(); bool check1, check2; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeHandle, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeHandle, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Check the outgoing share from User1 --- std::unique_ptr sl{ megaApi[0]->getOutShares() }; ASSERT_EQ(1, sl->size()) << "Outgoing share failed"; MegaShare* s = sl->get(0); n1.reset(megaApi[0]->getNodeByHandle(hfolder1)); // get an updated version of the node ASSERT_EQ(MegaShare::ACCESS_FULL, s->getAccess()) << "Wrong access level of outgoing share"; ASSERT_EQ(hfolder1, s->getNodeHandle()) << "Wrong node handle of outgoing share"; ASSERT_STRCASEEQ(mApi[1].email.c_str(), s->getUser()) << "Wrong email address of outgoing share"; ASSERT_TRUE(n1->isShared()) << "Wrong sharing information at outgoing share"; ASSERT_TRUE(n1->isOutShare()) << "Wrong sharing information at outgoing share"; // --- Check the incoming share to User2 --- sl.reset(megaApi[1]->getInSharesList()); ASSERT_EQ(1, sl->size()) << "Incoming share not received in auxiliar account"; // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &n1]() { return unique_ptr(megaApi[1]->getNodeByHandle(n1->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); std::unique_ptr contact(megaApi[1]->getContact(mApi[0].email.c_str())); std::unique_ptr nl(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account"; MegaNode* n = nl->get(0); ASSERT_EQ(hfolder1, n->getHandle()) << "Wrong node handle of incoming share"; ASSERT_STREQ(foldername1, n->getName()) << "Wrong folder name of incoming share"; ASSERT_EQ(MegaError::API_OK, megaApi[1]->checkAccessErrorExtended(n, MegaShare::ACCESS_FULL)->getErrorCode()) << "Wrong access level of incoming share"; ASSERT_TRUE(n->isInShare()) << "Wrong sharing information at incoming share"; ASSERT_TRUE(n->isShared()) << "Wrong sharing information at incoming share"; // --- Check that User2 (sharee) cannot tag the incoming share as favourite --- auto errU2SetFavourite = synchronousSetNodeFavourite(1, n, true); ASSERT_EQ(API_EACCESS, errU2SetFavourite) << " synchronousSetNodeFavourite by the sharee should return API_EACCESS (returned error: " << errU2SetFavourite << ")"; // --- Check that User2 (sharee) cannot tag an inner inshare folder as favourite --- std::unique_ptr subfolderNode {megaApi[1]->getNodeByHandle(hfolder2)}; auto errU2SetFavourite2 = synchronousSetNodeFavourite(1, subfolderNode.get(), true); ASSERT_EQ(API_EACCESS, errU2SetFavourite2) << " synchronousSetNodeFavourite by the sharee should return API_EACCESS (returned error: " << errU2SetFavourite << ")"; // --- Check that User1 (sharer) can tag the outgoing share as favourite --- auto errU1SetFavourite = synchronousSetNodeFavourite(0, n, true); ASSERT_EQ(API_OK, errU1SetFavourite) << " synchronousSetNodeFavourite by the sharer failed (error: " << errU1SetFavourite << ")"; // --- Check that User1 (sharer) can tag an inner outshare folder as favourite --- auto errU1SetFavourite2 = synchronousSetNodeFavourite(0, subfolderNode.get(), true); ASSERT_EQ(API_OK, errU1SetFavourite2) << " synchronousSetNodeFavourite by the sharer failed (error: " << errU1SetFavourite << ")"; // --- Get file name and fingerprint from User1 account --- unique_ptr nfile2(megaApi[0]->getNodeByHandle(hfile2)); ASSERT_NE(nfile2, nullptr) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; const char* fileNameToSearch = nfile2->getName(); const char* fingerPrintToSearch = nfile2->getFingerprint(); // --- Search by fingerprint for User2 --- unique_ptr fingerPrintList(megaApi[1]->getNodesByFingerprint(fingerPrintToSearch)); ASSERT_EQ(fingerPrintList->size(), 2) << "Node count by fingerprint is wrong"; // the same file was uploaded twice, with differernt paths bool found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == hfile2) { found = true; break; } } ASSERT_TRUE(found); // --- Search by file name for User2 --- std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName(fileNameToSearch); std::unique_ptr searchList(megaApi[1]->search(filterResults.get())); ASSERT_EQ(searchList->size(), 2) << "Node count by file name is wrong"; // the same file was uploaded twice, to differernt paths ASSERT_TRUE((searchList->get(0)->getHandle() == hfile1 && searchList->get(1)->getHandle() == hfile2) || (searchList->get(0)->getHandle() == hfile2 && searchList->get(1)->getHandle() == hfile1)) << "Node handles are not the expected ones"; // --- User2 add file --- // |--Shared-folder // |--subfolder // |--by_user_2.txt static constexpr char fileByUser2[] = "by_user_2.txt"; createFile(fileByUser2, false); // not a large file since don't need to test transfers here MegaHandle hfile2U2 = 0; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); ASSERT_EQ(MegaError::API_OK, doStartUpload(1, &hfile2U2, fileByUser2, std::unique_ptr{megaApi[1]->getNodeByHandle(hfolder2)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; ASSERT_TRUE(waitForResponse(&check1)) << "Node update not received on client 1 after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) << "Node update not received on client 0 after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Check that User1 has received the change --- std::unique_ptrnU2{ megaApi[0]->getNodeByHandle(hfile2U2) }; // get an updated version of the node ASSERT_TRUE(nU2 && string(fileByUser2) == nU2->getName()) << "Finding node by handle failed"; // --- Locallogout from User1 and login with session --- string session = unique_ptr(dumpSession()).get(); locallogout(); auto tracker = asyncRequestFastLogin(0, session.c_str()); PerApi& target0 = mApi[0]; target0.resetlastEvent(); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session for account " << 0; fetchnodes(0, maxTimeout); ASSERT_TRUE(WaitFor([&target0](){ return target0.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; // --- User1 remove file --- ASSERT_EQ(MegaError::API_OK, synchronousRemove(0, nfile2.get())) << "Error while removing file " << nfile2->getName(); // --- Locallogout from User2 and login with session --- session = unique_ptr(megaApi[1]->dumpSession()).get(); auto logoutErr = doRequestLocalLogout(1); ASSERT_EQ(MegaError::API_OK, logoutErr) << "Local logout failed (error: " << logoutErr << ")"; PerApi& target1 = mApi[1]; target1.resetlastEvent(); // clear any previous EVENT_NODES_CURRENT auto trackerU2 = asyncRequestFastLogin(1, session.c_str()); ASSERT_EQ(API_OK, trackerU2->waitForResult()) << " Failed to establish a login/session for account " << 1; fetchnodes(1, maxTimeout); // make sure that client is up to date (upon logout, recent changes might not be committed to DB) ASSERT_TRUE(WaitFor([&target1](){ return target1.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; // --- Check that User2 no longer sees the removed file --- std::unique_ptr nremoved{ megaApi[1]->getNodeByHandle(hfile2) }; // get an updated version of the node ASSERT_EQ(nremoved, nullptr) << " Failed to see the file was removed"; } /** * @brief TEST_F SdkTestShares * * Initialize a test scenario by: * * - Creating/uploading some folders/files to share * - Creating a new contact to share to * * Performs different operations related to sharing: * * - Share a folder with an existing contact * - Check the correctness of the outgoing share * - Check the reception and correctness of the incoming share * - Move a shared file (not owned) to Rubbish bin * - Add some subfolders * - Share a nested folder with same contact * - Check the reception and correctness of the incoming nested share * - Stop share main in share * - Check correctness of the account size * - Share the main in share again * - Check correctness of the account size * - Stop share nested inshare * - Check correctness of the account size * - Modify the access level * - Sharee leaves the inshare * - Share again the main folder * - Revoke the access to the share * - Share a folder with a non registered email * - Check the correctness of the pending outgoing share * - Create a file public link * - Import a file public link * - Get a node from a file public link * - Remove a public link * - Create a folder public link * - Import folder public link */ TEST_F(SdkTest, SdkTestShares) { LOG_info << "___TEST Shares___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Initialize a test scenario : create some folders/files to share // Create some nodes to share // |--Shared-folder // |--subfolder // |--file.txt // |--file.txt std::unique_ptr rootnode{megaApi[0]->getRootNode()}; char foldername1[64] = "Shared-folder"; MegaHandle hfolder1 = createFolder(0, foldername1, rootnode.get()); ASSERT_NE(hfolder1, UNDEF); std::unique_ptr n1(megaApi[0]->getNodeByHandle(hfolder1)); ASSERT_NE(n1.get(), nullptr); unsigned long long inSharedNodeCount = 1; char foldername2[64] = "subfolder"; MegaHandle hfolder2 = createFolder(0, foldername2, std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder1)}.get()); ASSERT_NE(hfolder2, UNDEF); ++inSharedNodeCount; // not a large file since don't need to test transfers here ASSERT_TRUE(createFile(PUBLICFILE.c_str(), false)) << "Couldn't create " << PUBLICFILE.c_str(); MegaHandle hfile1 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile1, PUBLICFILE.c_str(), std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder1)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; ++inSharedNodeCount; MegaHandle hfile2 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile2, PUBLICFILE.c_str(), std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; ++inSharedNodeCount; // --- Download authorized node from another account --- MegaNode *nNoAuth = megaApi[0]->getNodeByHandle(hfile1); int transferError = doStartDownload(1, nNoAuth, "unauthorized_node", nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/); bool hasFailed = (transferError != API_OK); ASSERT_TRUE(hasFailed) << "Download of node without authorization successful! (it should fail): " << transferError; MegaNode *nAuth = megaApi[0]->authorizeNode(nNoAuth); // make sure target download file doesn't already exist: deleteFile("authorized_node"); transferError = doStartDownload(1, nAuth, "authorized_node", nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/); ASSERT_EQ(API_OK, transferError) << "Cannot download authorized node (error: " << mApi[1].lastError << ")"; delete nNoAuth; delete nAuth; // Initialize a test scenario: create a new contact to share to and verify credentials string message = "Hi contact. Let's share some stuff"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); EXPECT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated, 10u) ) // at the target side (auxiliar account) << "Contact request creation not received after 10 seconds"; EXPECT_NO_FATAL_FAILURE( getContactRequest(1, false) ); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; EXPECT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); EXPECT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated, 10u) ) // at the target side (auxiliar account) << "Contact request creation not received after 10 seconds"; EXPECT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated, 10u) ) // at the source side (main account) << "Contact request creation not received after 10 seconds"; mApi[1].cr.reset(); if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } auto ownedNodeCount = megaApi[1]->getAccurateNumNodes(); // upload a file, just to test node counters bool check1; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); ASSERT_EQ(MegaError::API_OK, doStartUpload(1, nullptr, PUBLICFILE.data(), std::unique_ptr{megaApi[1]->getRootNode()}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; ASSERT_TRUE(waitForResponse(&check1)) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); auto nodeCountAfterNewOwnedFile = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + 1, nodeCountAfterNewOwnedFile); ownedNodeCount = nodeCountAfterNewOwnedFile; ASSERT_EQ(check1, true); // --- Create a new outgoing share --- bool check2; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE( shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL) ); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Check the outgoing share --- auto sl = std::unique_ptr{megaApi[0]->getOutShares()}; ASSERT_EQ(1, sl->size()) << "Outgoing share failed"; // Test another interface sl.reset(megaApi[0]->getOutShares(n1.get())); ASSERT_EQ(1, sl->size()) << "Outgoing share failed"; MegaShare* s = sl->get(0); n1.reset(megaApi[0]->getNodeByHandle(hfolder1)); // get an updated version of the node ASSERT_EQ(MegaShare::ACCESS_FULL, s->getAccess()) << "Wrong access level of outgoing share"; ASSERT_EQ(hfolder1, s->getNodeHandle()) << "Wrong node handle of outgoing share"; ASSERT_STRCASEEQ(mApi[1].email.c_str(), s->getUser()) << "Wrong email address of outgoing share"; ASSERT_TRUE(n1->isShared()) << "Wrong sharing information at outgoing share"; ASSERT_TRUE(n1->isOutShare()) << "Wrong sharing information at outgoing share"; // --- Check the incoming share --- sl.reset(megaApi[1]->getInSharesList()); ASSERT_EQ(1, sl->size()) << "Incoming share not received in auxiliar account"; // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &n1]() { return unique_ptr(megaApi[1]->getNodeByHandle(n1->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); std::unique_ptr contact(megaApi[1]->getContact(mApi[0].email.c_str())); auto nl = std::unique_ptr{megaApi[1]->getInShares(contact.get())}; ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account"; MegaNode* n = nl->get(0); ASSERT_EQ(hfolder1, n->getHandle()) << "Wrong node handle of incoming share"; ASSERT_STREQ(foldername1, n->getName()) << "Wrong folder name of incoming share"; ASSERT_EQ(API_OK, megaApi[1]->checkAccessErrorExtended(n, MegaShare::ACCESS_FULL)->getErrorCode()) << "Wrong access level of incoming share"; ASSERT_TRUE(n->isInShare()) << "Wrong sharing information at incoming share"; ASSERT_TRUE(n->isShared()) << "Wrong sharing information at incoming share"; auto nodeCountAfterInShares = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInShares); // --- Move share file from different subtree, same file and fingerprint --- // Pre-requisite, the movement finds a file with same name and fp at target folder // Since the source and target folders belong to different trees, it will attempt to copy+delete // (hfile1 copied to rubbish, renamed to "copy", copied back to hfolder2, move // Since there is a file with same name and fingerprint, it will skip the copy and will do delete mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); MegaHandle copiedNodeHandle = INVALID_HANDLE; ASSERT_EQ(API_OK, doCopyNode(1, &copiedNodeHandle, std::unique_ptr(megaApi[1]->getNodeByHandle(hfile2)).get(), std::unique_ptr(megaApi[1]->getNodeByHandle(hfolder1)).get(), "copy")) << "Copying shared file (not owned) to same place failed"; EXPECT_TRUE( waitForResponse(&check1, 10u) ) // at the target side (main account) << "Node update not received after 10 seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); ++inSharedNodeCount; EXPECT_EQ(check1, true); ASSERT_EQ(check2, true); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); MegaHandle copiedNodeHandleInRubbish = INVALID_HANDLE; std::unique_ptr rubbishNode(megaApi[1]->getRubbishNode()); std::unique_ptr copiedNode(megaApi[1]->getNodeByHandle(copiedNodeHandle)); ASSERT_EQ(API_OK, doCopyNode(1, &copiedNodeHandleInRubbish, copiedNode.get(), rubbishNode.get())) << "Copying shared file (not owned) to Rubbish bin failed"; ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); ++ownedNodeCount; ASSERT_EQ(check1, true); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(copiedNodeHandle, MegaNode::CHANGE_TYPE_REMOVED, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(copiedNodeHandle, MegaNode::CHANGE_TYPE_REMOVED, check2); MegaHandle copyAndDeleteNodeHandle = INVALID_HANDLE; copiedNode.reset(megaApi[0]->getNodeByHandle(copiedNodeHandle)); EXPECT_EQ(API_OK, doMoveNode(1, ©AndDeleteNodeHandle, copiedNode.get(), rubbishNode.get())) << "Moving shared file, same name and fingerprint"; ASSERT_EQ(megaApi[1]->getNodeByHandle(copiedNodeHandle), nullptr) << "Move didn't delete source file"; ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); --inSharedNodeCount; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Move shared file (not owned) to Rubbish bin --- MegaHandle movedNodeHandle = UNDEF; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfile2, MegaNode::CHANGE_TYPE_REMOVED, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfile2, MegaNode::CHANGE_TYPE_REMOVED, check2); ASSERT_EQ(API_OK, doMoveNode(1, &movedNodeHandle, std::unique_ptr(megaApi[0]->getNodeByHandle(hfile2)).get(), rubbishNode.get())) << "Moving shared file (not owned) to Rubbish bin failed"; ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); --inSharedNodeCount; ++ownedNodeCount; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Test that file in Rubbish bin can be restored --- // Different handle! the node must have been copied due to differing accounts std::unique_ptr nodeMovedFile(megaApi[1]->getNodeByHandle(movedNodeHandle)); ASSERT_EQ(nodeMovedFile->getRestoreHandle(), hfolder2) << "Incorrect restore handle for file in Rubbish Bin"; // check the corresponding user alert ASSERT_TRUE(checkAlert(1, "New shared folder from " + mApi[0].email, mApi[0].email + ":Shared-folder")); // add folders under the share char foldernameA[64] = "dummyname1"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); MegaHandle dummyhandle1 = createFolder(0, foldernameA, std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}.get()); ASSERT_NE(dummyhandle1, UNDEF); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); char foldernameB[64] = "dummyname2"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); MegaHandle dummyhandle2 = createFolder(0, foldernameB, std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}.get()); ASSERT_NE(dummyhandle2, UNDEF); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); inSharedNodeCount += 2; unsigned long long nodesAtFolderDummyname2 = 1; // Take account own node ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); auto nodeCountAfterInSharesAddedDummyFolders = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInSharesAddedDummyFolders); // check the corresponding user alert EXPECT_TRUE(checkAlert(1, mApi[0].email + " added 2 folders", std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}->getHandle(), 2, dummyhandle1)); // add 2 more files to the share mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, PUBLICFILE.data(), std::unique_ptr{megaApi[0]->getNodeByHandle(dummyhandle1)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); ++inSharedNodeCount; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, PUBLICFILE.data(), std::unique_ptr{megaApi[0]->getNodeByHandle(dummyhandle2)}.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ++inSharedNodeCount; ++nodesAtFolderDummyname2; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); auto nodeCountAfterInSharesAddedDummyFile = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInSharesAddedDummyFile); // move a folder outside share mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyhandle1, MegaNode::CHANGE_TYPE_PARENT, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyhandle1, MegaNode::CHANGE_TYPE_REMOVED, check2); std::unique_ptr dummyNode1(megaApi[0]->getNodeByHandle(dummyhandle1)); megaApi[0]->moveNode(dummyNode1.get(), rootnode.get()); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); inSharedNodeCount -= 2; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); auto nodeCountAfterInSharesRemovedDummyFolder1 = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInSharesRemovedDummyFolder1); // add a nested share std::unique_ptr dummyNode2(megaApi[0]->getNodeByHandle(dummyhandle2)); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyhandle2, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyhandle2, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(dummyNode2.get(), mApi[1].email.data(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // number of nodes should not change, because this node is a nested share auto nodeCountAfterInSharesAddedNestedSubfolder = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInSharesAddedNestedSubfolder); // Stop share main folder (Shared-folder) mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(n1->getHandle(), MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(n1->getHandle(), MegaNode::CHANGE_TYPE_REMOVED, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.data(), MegaShare::ACCESS_UNKNOWN)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // number of nodes own cloud + nodes at nested in-share auto nodeCountAfterRemoveMainInshare = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + nodesAtFolderDummyname2, nodeCountAfterRemoveMainInshare); // Share again main folder (Shared-folder) mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(n1->getHandle(), MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(n1->getHandle(), MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.data(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // number of nodes own cloud + nodes at nested in-share auto nodeCountAfterShareN1 = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterShareN1); // remove nested share mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyNode2->getHandle(), MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyNode2->getHandle(), MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(dummyNode2.get(), mApi[1].email.data(), MegaShare::ACCESS_UNKNOWN)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // number of nodes should not change, because this node was a nested share auto nodeCountAfterInSharesRemovedNestedSubfolder = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterInSharesRemovedNestedSubfolder); // --- Modify the access level of an outgoing share --- mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(std::unique_ptr(megaApi[0]->getNodeByHandle(hfolder1)).get(), mApi[1].email.c_str(), MegaShare::ACCESS_READWRITE) ); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); contact.reset(megaApi[1]->getContact(mApi[0].email.c_str())); nl.reset(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account"; n = nl->get(0); ASSERT_EQ(API_OK, megaApi[1]->checkAccessErrorExtended(n, MegaShare::ACCESS_READWRITE)->getErrorCode()) << "Wrong access level of incoming share"; // --- Sharee leaves the inshare --- // Testing APs caused by actions done in the sharee account. unique_ptr inshareRootNode(megaApi[1]->getNodeByHandle(hfolder1)); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_REMOVED, check2); ASSERT_NO_FATAL_FAILURE(doDeleteNode(1, inshareRootNode.get())); // Delete an inshare root node to leave the inconming share ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); sl.reset(megaApi[0]->getOutShares()); ASSERT_EQ(0, sl->size()) << "Leaving the inshare failed. Outshare is still active in the first account."; contact.reset(megaApi[1]->getContact(mApi[0].email.c_str())); nl.reset(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(0, nl->size()) << "Leaving the inshare failed. Inshare is still active in the second account."; // Number of nodes should be the ones in the account only. auto nodeCountAfterShareeLeavesShare = megaApi[1]->getNumNodes(); ASSERT_EQ(ownedNodeCount, nodeCountAfterShareeLeavesShare); // --- Share again the main folder --- mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.data(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); sl.reset(megaApi[0]->getOutShares()); ASSERT_EQ(1, sl->size()) << "Outgoing share failed. Sharing again after sharee left the share."; // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &n1]() { return unique_ptr(megaApi[1]->getNodeByHandle(n1->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); contact.reset(megaApi[1]->getContact(mApi[0].email.c_str())); nl.reset(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(1, nl->size()) << "Incoming share failed. Sharing again after sharee left the share."; // Number of nodes restored after sharing again. auto nodeCountAfterShareAgainIfShareeLeaves = megaApi[1]->getNumNodes(); ASSERT_EQ(ownedNodeCount + inSharedNodeCount, nodeCountAfterShareAgainIfShareeLeaves); // --- Revoke access to an outgoing share --- mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_REMOVED, check2); ASSERT_NO_FATAL_FAILURE( shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_UNKNOWN) ); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); sl.reset(megaApi[0]->getOutShares()); ASSERT_EQ(0, sl->size()) << "Outgoing share revocation failed"; // Test another interface sl.reset(megaApi[0]->getOutShares(n1.get())); ASSERT_EQ(0, sl->size()) << "Outgoing share revocation failed"; contact.reset(megaApi[1]->getContact(mApi[0].email.c_str())); nl.reset(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(0, nl->size()) << "Incoming share revocation failed"; // check the corresponding user alert { MegaUserAlertList* list = megaApi[1]->getUserAlerts(); ASSERT_TRUE(list->size() > 0); MegaUserAlert* a = list->get(list->size() - 1); ASSERT_STRCASEEQ(a->getTitle(), ("Access to folders shared by " + mApi[0].email + " was removed").c_str()); ASSERT_STRCASEEQ(a->getPath(), (mApi[0].email + ":Shared-folder").c_str()); ASSERT_NE(a->getNodeHandle(), UNDEF); delete list; } auto nodeCountAfterRevokedSharesAccess = megaApi[1]->getAccurateNumNodes(); ASSERT_EQ(ownedNodeCount, nodeCountAfterRevokedSharesAccess); // --- Get pending outgoing shares --- char emailfake[64]; srand(unsigned(time(NULL))); snprintf(emailfake, sizeof(emailfake), "%d@nonexistingdomain.com", rand()%1000000); // carefull, antispam rejects too many tries without response for the same address auto node = std::unique_ptr{megaApi[0]->getNodeByHandle(hfolder2)}; mApi[0].contactRequestUpdated = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder2, MegaNode::CHANGE_TYPE_PENDINGSHARE, check1); ASSERT_NO_FATAL_FAILURE(shareFolder(node.get(), emailfake, MegaShare::ACCESS_FULL)); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account) << "Contact request update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); sl.reset(megaApi[0]->getPendingOutShares(node.get())); ASSERT_EQ(1, sl->size()) << "Pending outgoing share failed"; // Test another interface sl.reset(megaApi[0]->getOutShares(node.get())); ASSERT_EQ(1, sl->size()) << "Pending outgoing share failed"; s = sl->get(0); node.reset(megaApi[0]->getNodeByHandle(s->getNodeHandle())); ASSERT_FALSE(node->isShared()) << "Node is already shared, must be pending"; ASSERT_FALSE(node->isOutShare()) << "Node is already shared, must be pending"; ASSERT_FALSE(node->isInShare()) << "Node is already shared, must be pending"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(dummyNode1->getHandle(), MegaNode::CHANGE_TYPE_PENDINGSHARE, check1); ASSERT_NO_FATAL_FAILURE( shareFolder(dummyNode1.get(), emailfake, MegaShare::ACCESS_FULL) ); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); sl.reset(megaApi[0]->getPendingOutShares()); ASSERT_EQ(2, sl->size()) << "Pending outgoing share failed"; // Test another interface sl.reset(megaApi[0]->getOutShares()); ASSERT_EQ(2, sl->size()) << "Pending outgoing share failed"; // --- Create a file public link --- ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(0, true, true, true)) << "Cannot get account details"; std::unique_ptr nfile1{megaApi[0]->getNodeByHandle(hfile1)}; string nodelink3 = createPublicLink(0, nfile1.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); // The created link is stored in this->link at onRequestFinish() // Get a fresh snapshot of the node and check it's actually exported nfile1 = std::unique_ptr{megaApi[0]->getNodeByHandle(hfile1)}; ASSERT_TRUE(nfile1->isExported()) << "Node is not exported, must be exported"; ASSERT_FALSE(nfile1->isTakenDown()) << "Public link is taken down, it mustn't"; // Make sure that search functionality finds it std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName(nfile1->getName()); filterResults->byLocation(MegaApi::SEARCH_TARGET_PUBLICLINK); std::unique_ptr foundByLink(megaApi[0]->search(filterResults.get())); ASSERT_TRUE(foundByLink); ASSERT_EQ(foundByLink->size(), 1); ASSERT_EQ(foundByLink->get(0)->getHandle(), nfile1->getHandle()); // Regenerate the same link should not trigger a new request nfile1 = std::unique_ptr{megaApi[0]->getNodeByHandle(hfile1)}; string nodelink4 = createPublicLink(0, nfile1.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); ASSERT_STREQ(nodelink3.c_str(), nodelink4.c_str()) << "Wrong public link after link update"; // Try to update the expiration time of an existing link (only for PRO accounts are allowed, otherwise -11 string nodelinkN = createPublicLink(0, nfile1.get(), m_time() + 30*86400, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); nfile1 = std::unique_ptr{megaApi[0]->getNodeByHandle(hfile1)}; if (mApi[0].accountDetails->getProLevel() == 0) { ASSERT_EQ(0, nfile1->getExpirationTime()) << "Expiration time successfully set, when it shouldn't"; } ASSERT_FALSE(nfile1->isExpired()) << "Public link is expired, it mustn't"; // --- Import a file public link --- auto importHandle = importPublicLink(0, nodelink4, rootnode.get()); MegaNode *nimported = megaApi[0]->getNodeByHandle(importHandle); ASSERT_STREQ(nfile1->getName(), nimported->getName()) << "Imported file with wrong name"; ASSERT_EQ(rootnode->getHandle(), nimported->getParentHandle()) << "Imported file in wrong path"; // --- Get node from file public link --- auto nodeUP = getPublicNode(1, nodelink4); ASSERT_TRUE(nodeUP && nodeUP->isPublic()) << "Cannot get a node from public link"; // --- Remove a public link --- MegaHandle removedLinkHandle = removePublicLink(0, nfile1.get()); nfile1 = std::unique_ptr{megaApi[0]->getNodeByHandle(removedLinkHandle)}; ASSERT_FALSE(nfile1->isPublic()) << "Public link removal failed (still public)"; delete nimported; // --- Create a folder public link --- std::unique_ptr nfolder1(megaApi[0]->getNodeByHandle(hfolder1)); string nodelink5 = createPublicLink(0, nfolder1.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); // The created link is stored in this->link at onRequestFinish() // Get a fresh snapshot of the node and check it's actually exported nfolder1.reset(megaApi[0]->getNodeByHandle(hfolder1)); ASSERT_TRUE(nfolder1->isExported()) << "Node is not exported, must be exported"; ASSERT_FALSE(nfolder1->isTakenDown()) << "Public link is taken down, it mustn't"; nfolder1.reset(megaApi[0]->getNodeByHandle(hfolder1)); ASSERT_STREQ(nodelink5.c_str(), std::unique_ptr(nfolder1->getPublicLink()).get()) << "Wrong public link from MegaNode"; // Make sure that search functionality finds it filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(nfolder1->getName()); filterResults->byLocation(MegaApi::SEARCH_TARGET_PUBLICLINK); foundByLink.reset(megaApi[0]->search(filterResults.get())); ASSERT_TRUE(foundByLink); ASSERT_EQ(foundByLink->size(), 1); ASSERT_EQ(foundByLink->get(0)->getHandle(), nfolder1->getHandle()); // Regenerate the same link should not trigger a new request string nodelink6 = createPublicLink(0, nfolder1.get(), 0, maxTimeout, mApi[0].accountDetails->getProLevel() == 0); ASSERT_STREQ(nodelink5.c_str(), nodelink6.c_str()) << "Wrong public link after link update"; // --- Import folder public link --- const auto [email, pass] = getEnvVarAccounts().getVarValues(2); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(3); megaApi.resize(3); configureTestInstance(2, email, pass); auto loginFolderTracker = asyncRequestLoginToFolder(2, nodelink6.c_str()); ASSERT_EQ(loginFolderTracker->waitForResult(), API_OK) << "Failed to login to folder " << nodelink6; ASSERT_NO_FATAL_FAILURE(fetchnodes(2)); std::unique_ptr folderNodeToImport(megaApi[2]->getRootNode()); ASSERT_TRUE(folderNodeToImport) << "Failed to get folder node to import from link " << nodelink6; std::unique_ptr authorizedFolderNode(megaApi[2]->authorizeNode(folderNodeToImport.get())); ASSERT_TRUE(authorizedFolderNode) << "Failed to authorize folder node from link " << nodelink6; ASSERT_TRUE(authorizedFolderNode->getChildren()) << "Authorized folder node children list is null but it should not"; ASSERT_EQ(megaApi[2]->getNumChildren(folderNodeToImport.get()), authorizedFolderNode->getChildren()->size()) << "Different number of child nodes after authorizing the folder node"; logout(2, false, 20); auto loginTracker = asyncRequestLogin(2, email.c_str(), pass.c_str()); ASSERT_EQ(loginTracker->waitForResult(), API_OK) << "Failed to login with " << email; ASSERT_NO_FATAL_FAILURE(fetchnodes(2)); std::unique_ptr rootNode2(megaApi[2]->getRootNode()); RequestTracker nodeCopyTracker(megaApi[2].get()); megaApi[2]->copyNode(authorizedFolderNode.get(), rootNode2.get(), nullptr, &nodeCopyTracker); EXPECT_EQ(nodeCopyTracker.waitForResult(), API_OK) << "Failed to copy node to import"; std::unique_ptr importedNode(megaApi[2]->getNodeByPath(authorizedFolderNode->getName(), rootNode2.get())); EXPECT_TRUE(importedNode) << "Imported node not found"; std::unique_ptr authorizedImportedNode(megaApi[2]->authorizeNode(importedNode.get())); EXPECT_TRUE(authorizedImportedNode) << "Failed to authorize imported node"; EXPECT_TRUE(authorizedImportedNode->getChildren()) << "Authorized imported node children list is null but it should not"; ASSERT_EQ(authorizedFolderNode->getChildren()->size(), authorizedImportedNode->getChildren()->size()) << "Not all child nodes have been imported"; } /** * @brief TEST_F SdkTest.LoginToWritableFolderThenCreateSubfolder * * - Login 1 account * - Create a folder * - Create a public writable link to folder * - Setup guest account without login for accessing the public link * - Login guest account to public link * - Check for user alerts (should not be any, including from sc50) * - Create subfolder in the folder with writable link * - Confirm that guest account has seen the newly created subfolder * - Check again for user alerts (should still not be any) */ TEST_F(SdkTest, LoginToWritableFolderThenCreateSubfolder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Host: create a folder std::unique_ptr hostRoot{megaApi[0]->getRootNode()}; static constexpr char folderName[] = "Writable-link-folder"; MegaHandle folderHandle = createFolder(0, folderName, hostRoot.get()); ASSERT_NE(UNDEF, folderHandle) << "API 0: Failed to create " << folderName; std::unique_ptr folderNode{megaApi[0]->getNodeByHandle(folderHandle)}; ASSERT_THAT(folderNode, ::testing::NotNull()); // Host: get acount details for Pro level RequestTracker accountDetailsTracker{megaApi[0].get()}; megaApi[0]->getSpecificAccountDetails(false, false, true, -1, &accountDetailsTracker); ASSERT_EQ(API_OK, accountDetailsTracker.waitForResult()) << "API 0: Failed to get account details"; // Host: create a writable link to folder const bool isFreeAccount = mApi[0].accountDetails->getProLevel() == MegaAccountDetails::ACCOUNT_TYPE_FREE; string nodeLink = createPublicLink(0, folderNode.get(), 0, maxTimeout, isFreeAccount, true /*writable*/); // Guest: setup without login for accessing the public link unsigned guestIdx = 1; const auto [email, pass] = getEnvVarAccounts().getVarValues(guestIdx); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(guestIdx + 1); megaApi.resize(guestIdx + 1); configureTestInstance(guestIdx, email, pass); // Guest: login to writable folder RequestTracker loginToFolderTracker{megaApi[guestIdx].get()}; megaApi[guestIdx]->loginToFolder(nodeLink.c_str(), &loginToFolderTracker); ASSERT_EQ(API_OK, loginToFolderTracker.waitForResult()) << "API 1 (guest): Failed to login to folder " << nodeLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(guestIdx)); // Guest: make sure it got no user alerts, including any from sc50 unsigned sc50Timeout = 10; // seconds ASSERT_FALSE(waitForResponse(&mApi[guestIdx].userAlertsUpdated, sc50Timeout)) << "API 1 (guest): sc50 alerts after login received"; ASSERT_EQ(mApi[guestIdx].userAlertList, nullptr) << "sc50 of guest logged into folder"; unique_ptr userAlerts(megaApi[guestIdx]->getUserAlerts()); ASSERT_TRUE(userAlerts); ASSERT_EQ(userAlerts->size(), 0); // Guest: confirm root node of folder link std::unique_ptr guestRoot{megaApi[guestIdx]->getRootNode()}; ASSERT_EQ(folderHandle, guestRoot->getHandle()); // Guest: attempt to create subfolder in writable folder static constexpr char subfolderName[] = "Writable-link-subfolder"; RequestTracker createSubfolderTracker(megaApi[guestIdx].get()); megaApi[guestIdx]->createFolder(subfolderName, guestRoot.get(), &createSubfolderTracker); ASSERT_EQ(API_EACCESS, createSubfolderTracker.waitForResult()) << "API 1 (guest): Managed to create " << subfolderName; // Guest: reset node updates mApi[guestIdx].nodeUpdated = false; mApi[guestIdx].mOnNodesUpdateCompletion = [guestIdx, &guest = mApi[guestIdx]](size_t apiIndex, MegaNodeList*) { if (guestIdx == apiIndex) guest.nodeUpdated = true; }; // Host: create subfolder in writable folder MegaHandle subfolderHandle = createFolder(0, subfolderName, folderNode.get()); ASSERT_NE(UNDEF, subfolderHandle) << "API 0: Failed to create " << subfolderName; // Guest: Wait for node update (replacement for fetchnodes()) ASSERT_TRUE(waitForResponse(&mApi[guestIdx].nodeUpdated)) << "API 1 (guest): Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // Guest: confirm the newly created node std::unique_ptr subfolder{megaApi[guestIdx]->getNodeByHandle(subfolderHandle)}; ASSERT_THAT(subfolder, ::testing::NotNull()) << "API 1 (guest): Failed to find " << subfolderName; // Guest: check again that it got no user alerts ASSERT_FALSE(mApi[guestIdx].userAlertsUpdated) << "API 1 (guest): alerts received"; ASSERT_EQ(mApi[guestIdx].userAlertList, nullptr) << "sc50"; userAlerts.reset(megaApi[guestIdx]->getUserAlerts()); ASSERT_TRUE(userAlerts); ASSERT_EQ(userAlerts->size(), 0); } /** * @brief TEST_F TestPublicFolderLinksWithShares * * 1 - create share * 2 - create folder link on same share * 3 - remove folder link * 4 - remove share * 5 - create folder link * 6 - remove folder link * */ TEST_F(SdkTestShares, TestPublicFolderLinksWithShares) { LOG_info << "___TEST TestPublicFolderLinksWithShares"; ASSERT_NO_FATAL_FAILURE(createNodeTrees()); const MegaHandle hfolder = getHandle("/sharedfolder"); // Create share on the folder ASSERT_NO_FATAL_FAILURE(createNewContactAndVerify()); ASSERT_NO_FATAL_FAILURE(createOutgoingShare(hfolder)); ASSERT_NO_FATAL_FAILURE(getInshare(hfolder)); // Create a folder public link on the shared folder ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(mSharerIndex, true, true, true)) << "Cannot get account details"; std::string nodeLink; ASSERT_NO_FATAL_FAILURE(createOnePublicLink(hfolder, nodeLink)); ASSERT_NO_FATAL_FAILURE(importPublicLink(nodeLink)); ASSERT_NO_FATAL_FAILURE(revokePublicLink(hfolder)); // Revoke share on the folder ASSERT_NO_FATAL_FAILURE(revokeOutShares(hfolder)); // Create the folder public link on the folder after revoking ASSERT_NO_FATAL_FAILURE(createOnePublicLink(hfolder, nodeLink)); ASSERT_NO_FATAL_FAILURE(importPublicLink(nodeLink)); ASSERT_NO_FATAL_FAILURE(revokePublicLink(hfolder)); } /** * @brief TEST_F SdkTestShares.TestForeingNodeImportRemoveSensitiveFlag * * 1 - User 0 creates node tree and marks one file as sensitive * 2 - User 1 imports that folder via meeting link -> No sensitive expected * 3 - User 0 shares folder with User 1 -> User 1 sees sensitive node * 4 - User 1 copies to own cloud -> No sensitive in the copy * 5 - User 0 copies sensitive file with other name in the shared -> Copy keeps sensitive. * 6 - User 1 does the same -> Copy removes sensitive * 7 - User 1 moves to own cloud -> No sensitive expected * 8 - User 1 tags the moved node as sensitive and copies back to shared -> No sensitive expected * */ TEST_F(SdkTestShares, TestForeingNodeImportRemoveSensitiveFlag) { const auto getSensNodes = [](const auto& api, MegaHandle handle) { std::unique_ptr filter(MegaSearchFilter::createInstance()); filter->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); filter->byLocationHandle(handle); std::unique_ptr sensNodes(api->search(filter.get())); return sensNodes; }; LOG_info << "___TEST TestForeingNodeImportRemoveSensitiveFlag"; LOG_debug << "## Creating node tree in user 0 cloud"; ASSERT_NO_FATAL_FAILURE(createNodeTrees()); LOG_debug << "## Marking node as sensitive"; // Mark one file as sensitive std::unique_ptr sensFile{ mSharerApi->getNodeByHandle(getHandle("/sharedfolder/file.txt"))}; ASSERT_EQ(API_OK, synchronousSetNodeSensitive(mSharerIndex, sensFile.get(), true)); // We test first the share via public link to ensure we go through the code path where the node // to import is not already in our cloud LOG_debug << "## User 0 creates a public link to share"; const MegaHandle hfolder = getHandle("/sharedfolder"); ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(mSharerIndex, true, true, true)) << "Cannot get account details"; std::string nodeLink; ASSERT_NO_FATAL_FAILURE(createOnePublicLink(hfolder, nodeLink)); LOG_debug << "## User 1 imports public link"; MegaHandle importedNodeHandle = INVALID_HANDLE; ASSERT_NO_FATAL_FAILURE(importPublicLink(nodeLink, &importedNodeHandle)); ASSERT_NE(importedNodeHandle, INVALID_HANDLE); // Check there is no sensitive nodes in the imported node LOG_debug << "## Checking user 1 sees no sensitive files in the imported folder"; std::unique_ptr sensNodes = getSensNodes(mShareeApi, importedNodeHandle); EXPECT_EQ(sensNodes->size(), 0) << "Got sensitive nodes after importing from public link while this property is expected " "to be cleared in the process"; LOG_debug << "## Sharing the folder with user 1"; ASSERT_NO_FATAL_FAILURE(createNewContactAndVerify()); ASSERT_NO_FATAL_FAILURE(createOutgoingShare(hfolder)); ASSERT_NO_FATAL_FAILURE(getInshare(hfolder)); LOG_debug << "## Checking user 1 sees a sensitive file"; sensNodes = getSensNodes(mShareeApi, hfolder); ASSERT_EQ(sensNodes->size(), 1); ASSERT_STREQ(sensNodes->get(0)->getName(), "file.txt"); LOG_debug << "## User 1 copies folder with sensitive file into own cloud"; MegaHandle copyHandle = INVALID_HANDLE; ASSERT_NO_FATAL_FAILURE(copyNode(mShareeIndex, getHandle("/sharedfolder"), INVALID_HANDLE, "copied_shared", ©Handle)); LOG_debug << "## Checking user 1 sees no sensitive files in the copied node"; sensNodes = getSensNodes(mShareeApi, copyHandle); EXPECT_EQ(sensNodes->size(), 0) << "Got sensitive nodes after importing from shared folder while this property is expected " "to be cleared in the process"; LOG_debug << "## User 0 copies the sensitive file into the same folder with different name"; MegaHandle sharerCopyHandle = INVALID_HANDLE; ASSERT_NO_FATAL_FAILURE(copyNode(mSharerIndex, getHandle("/sharedfolder/file.txt"), getHandle("/sharedfolder"), "file_copied_by_sharer.txt", &sharerCopyHandle)); LOG_debug << "## Checking the copy keeps the sensitive flag"; std::unique_ptr dest{mSharerApi->getNodeByHandle(sharerCopyHandle)}; ASSERT_TRUE(dest->isMarkedSensitive()) << "Copying a sensitive node within a shared folder by the owner resets the attribute"; LOG_debug << "## User 1 copies the sensitive file into the same folder with different name"; MegaHandle shareeCopyHandle = INVALID_HANDLE; ASSERT_NO_FATAL_FAILURE(copyNode(mShareeIndex, getHandle("/sharedfolder/file.txt"), getHandle("/sharedfolder"), "file_copied_by_sharee.txt", &shareeCopyHandle)); LOG_debug << "## Checking the copy resets the sensitive flag"; dest.reset(mShareeApi->getNodeByHandle(shareeCopyHandle)); ASSERT_FALSE(dest->isMarkedSensitive()) << "Copying a sensitive node within a shared folder by the sharee must reset sensitive"; LOG_debug << "## User 1 copies sens to exact same place and name"; ASSERT_NO_FATAL_FAILURE(copyNode(mShareeIndex, sharerCopyHandle, getHandle("/sharedfolder"), "file_copied_by_sharer.txt", ©Handle)); LOG_debug << "## Checking the copy resets the sensitive flag"; dest.reset(mShareeApi->getNodeByHandle(shareeCopyHandle)); EXPECT_FALSE(dest->isMarkedSensitive()) << "Copying a sensitive node to the same place by the sharee must reset sensitive"; LOG_debug << "## User 1 moves sensitive file from shared folder to own cloud"; MegaHandle movedHandle = INVALID_HANDLE; ASSERT_NO_FATAL_FAILURE( moveNodeToOwnCloud("/sharedfolder/file.txt", "moved_file.txt", &movedHandle)); ASSERT_NE(movedHandle, INVALID_HANDLE); LOG_debug << "## Checking the move resets the sensitive flag"; std::unique_ptr movedNode{mShareeApi->getNodeByHandle(movedHandle)}; ASSERT_FALSE(movedNode->isMarkedSensitive()) << "Moved node from shared folder kept the sensitive label"; LOG_debug << "## User 1 marks it again as sensitive and copies it back to the shared folder"; ASSERT_EQ(API_OK, synchronousSetNodeSensitive(mShareeIndex, movedNode.get(), true)); movedNode.reset(mShareeApi->getNodeByHandle(movedHandle)); ASSERT_TRUE(movedNode->isMarkedSensitive()) << "There was an error setting sensitive node"; ASSERT_NO_FATAL_FAILURE(copyNode(mShareeIndex, movedHandle, getHandle("/sharedfolder"), "copied_back_sensitive_file.txt", ©Handle)); LOG_debug << "## Checking the copy resets the sensitive flag"; dest.reset(mShareeApi->getNodeByHandle(copyHandle)); ASSERT_FALSE(dest->isMarkedSensitive()) << "The copy from sharee cloud to shared folder does nor reset the sensitive attribute"; } TEST_F(SdkTest, SdkTestShareKeys) { LOG_info << "___TEST ShareKeys___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(3)); // make sure users B and C have no inshares (since before this test was started) for (unsigned apiIdx = 1; apiIdx <= 2; ++apiIdx) { unique_ptr inShares(megaApi[apiIdx]->getInSharesList()); for (int i = 0; i < inShares->size(); ++i) { // leave share MegaShare* s = inShares->get(i); unique_ptr n(megaApi[apiIdx]->getNodeByHandle(s->getNodeHandle())); ASSERT_EQ(API_OK, synchronousRemove(apiIdx, n.get())); } } // Three user scenario, with nested shares and new nodes created that need keys to be shared to the other users. // User A creates folder and shares it with user B // User A creates folders / subfolder and shares it with user C // When user C adds files to subfolder, does B receive the keys ? unique_ptr rootnodeA(megaApi[0]->getRootNode()); unique_ptr rootnodeB(megaApi[1]->getRootNode()); unique_ptr rootnodeC(megaApi[2]->getRootNode()); ASSERT_TRUE(rootnodeA &&rootnodeB &&rootnodeC); auto nh = createFolder(0, "share-folder-A", rootnodeA.get()); ASSERT_NE(nh, UNDEF); unique_ptr shareFolderA(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(!!shareFolderA); nh = createFolder(0, "sub-folder-A", shareFolderA.get()); ASSERT_NE(nh, UNDEF); unique_ptr subFolderA(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(!!subFolderA); // Initialize a test scenario: create a new contact to share to and verify credentials ASSERT_EQ(API_OK, synchronousInviteContact(0, mApi[1].email.c_str(), "SdkTestShareKeys contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_EQ(API_OK, synchronousInviteContact(0, mApi[2].email.c_str(), "SdkTestShareKeys contact request A to C", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(WaitFor([this]() {return unique_ptr(megaApi[1]->getIncomingContactRequests())->size() == 1 && unique_ptr(megaApi[2]->getIncomingContactRequests())->size() == 1;}, 60000)); ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); ASSERT_NO_FATAL_FAILURE(getContactRequest(2, false)); ASSERT_EQ(API_OK, synchronousReplyContactRequest(1, mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_EQ(API_OK, synchronousReplyContactRequest(2, mApi[2].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); WaitMillisec(3000); if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(0, mApi[2].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[2].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} if (!areCredentialsVerified(2, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(2, mApi[0].email));} } ASSERT_EQ(unsigned(unique_ptr(megaApi[1]->getInSharesList())->size()), 0u); ASSERT_EQ(unsigned(unique_ptr(megaApi[2]->getInSharesList())->size()), 0u); ASSERT_NO_FATAL_FAILURE(shareFolder(shareFolderA.get(), mApi[1].email.c_str(), MegaShare::ACCESS_READ)); ASSERT_TRUE(WaitFor([this]() { return unique_ptr(megaApi[1]->getInSharesList())->size() == 1; }, 60000)); ASSERT_NO_FATAL_FAILURE(shareFolder(subFolderA.get(), mApi[2].email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(WaitFor([this]() { return unique_ptr(megaApi[2]->getInSharesList())->size() == 1; }, 60000)); ASSERT_EQ(unsigned(unique_ptr(megaApi[1]->getInSharesList())->size()), 1u); ASSERT_EQ(unsigned(unique_ptr(megaApi[2]->getInSharesList())->size()), 1u); // Wait for the inshare nodes to be decrypted ASSERT_TRUE(WaitFor([this, &shareFolderA]() { return unique_ptr(megaApi[1]->getNodeByHandle(shareFolderA->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); ASSERT_TRUE(WaitFor([this, &subFolderA]() { return unique_ptr(megaApi[2]->getNodeByHandle(subFolderA->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); unique_ptr c1(megaApi[1]->getContact(mApi[0].email.c_str())); unique_ptr c2(megaApi[2]->getContact(mApi[0].email.c_str())); unique_ptr nl1(megaApi[1]->getInShares(c1.get())); unique_ptr nl2(megaApi[2]->getInShares(c2.get())); ASSERT_EQ(1, nl1->size()); ASSERT_EQ(1, nl2->size()); MegaNode* receivedShareNodeB = nl1->get(0); MegaNode* receivedShareNodeC = nl2->get(0); ASSERT_NE(createFolder(2, "folderByC1", receivedShareNodeC), UNDEF); ASSERT_NE(createFolder(2, "folderByC2", receivedShareNodeC), UNDEF); ASSERT_TRUE(WaitFor([this, &subFolderA]() { unique_ptr aView(megaApi[0]->getChildren(subFolderA.get())); return aView->size() == 2; }, 60000)); WaitMillisec(10000); // make it shorter once we do actually get the keys (seems to need a bug fix) // can A see the added folders? unique_ptr aView(megaApi[0]->getChildren(subFolderA.get())); ASSERT_EQ(2, aView->size()); ASSERT_STREQ(aView->get(0)->getName(), "folderByC1"); ASSERT_STREQ(aView->get(1)->getName(), "folderByC2"); // Can B see the added folders? unique_ptr bView(megaApi[1]->getChildren(receivedShareNodeB)); ASSERT_EQ(1, bView->size()); ASSERT_STREQ(bView->get(0)->getName(), "sub-folder-A"); unique_ptr bView2(megaApi[1]->getChildren(bView->get(0))); ASSERT_EQ(2, bView2->size()); ASSERT_STREQ(bView2->get(0)->getName(), "NO_KEY"); // TODO: This is technically not correct but a current side effect of avoiding going back to the servers frequently - to be fixed soon. For now choose the value that matches production ASSERT_STREQ(bView2->get(1)->getName(), "NO_KEY"); } string localpathToUtf8Leaf(const LocalPath& itemlocalname) { return itemlocalname.leafName().toPath(false); } LocalPath fspathToLocal(const fs::path& p) { std::string path = path_u8string(p); return LocalPath::fromAbsolutePath(path); } // TODO: SDK-1505 #ifndef __APPLE__ TEST_F(SdkTest, SdkTestFolderIteration) #else TEST_F(SdkTest, DISABLED_SdkTestFolderIteration) #endif { for (int testcombination = 0; testcombination < 2; testcombination++) { bool openWithNameOrUseFileAccess = testcombination == 0; error_code ec; if (fs::exists("test_SdkTestFolderIteration")) { fs::remove_all("test_SdkTestFolderIteration", ec); ASSERT_TRUE(!ec) << "could not remove old test folder"; } fs::create_directory("test_SdkTestFolderIteration", ec); ASSERT_TRUE(!ec) << "could not create test folder"; fs::path iteratePath = fs::current_path() / "test_SdkTestFolderIteration"; // make a directory fs::create_directory(iteratePath / "folder"); // make a file { ofstream f(path_u8string((iteratePath / "file.txt")).c_str()); f << "file content"; } // make some content to test the glob flag { fs::create_directory(iteratePath / "glob1folder"); fs::create_directory(iteratePath / "glob2folder"); ofstream f1(path_u8string((iteratePath / "glob1file.txt")).c_str()); ofstream f2(path_u8string((iteratePath / "glob2file.txt")).c_str()); f1 << "file content"; f2 << "file content"; } unsigned glob_entries = 4; // make a symlink to a folder (not recoginised by our dnext() on windows currently) fs::create_directory_symlink(iteratePath / "folder", iteratePath / "folderlink", ec); ASSERT_TRUE(!ec) << "could not create folder symlink"; // make a symlinnk to a file fs::create_symlink(iteratePath / "file.txt", iteratePath / "filelink.txt", ec); ASSERT_TRUE(!ec) << "could not create folder symlink"; // note on windows: symlinks are excluded by skipAttributes for FILE_ATTRIBUTE_REPARSE_POINT (also see https://docs.microsoft.com/en-us/windows/win32/fileio/determining-whether-a-directory-is-a-volume-mount-point) struct FileAccessFields { m_off_t size = -2; m_time_t mtime = 2; handle fsid = 3; bool fsidvalid = false; nodetype_t type = nodetype_t::TYPE_UNKNOWN; bool mIsSymLink = false; bool retry = false; int errorcode = -998; FileAccessFields() = default; FileAccessFields(const FileAccess& f) { size = f.size; mtime = f.mtime; fsid = f.fsid; fsidvalid = f.fsidvalid; type = f.type; mIsSymLink = f.mIsSymLink; retry = f.retry; errorcode = f.errorcode; } bool operator == (const FileAccessFields& f) const { if (size != f.size) { EXPECT_EQ(size, f.size); return false; } if (mtime != f.mtime) { EXPECT_EQ(mtime, f.mtime); return false; } if (!mIsSymLink) { // do we need fsid to be correct for symlink? Seems on mac plain vs iterated differ if (fsid != f.fsid) { EXPECT_EQ(fsid, f.fsid); return false; } } if (fsidvalid != f.fsidvalid) { EXPECT_EQ(fsidvalid, f.fsidvalid); return false; } if (type != f.type) { EXPECT_EQ(type, f.type); return false; } if (mIsSymLink != f.mIsSymLink) { EXPECT_EQ(mIsSymLink, f.mIsSymLink); return false; } if (retry != f.retry) { EXPECT_EQ(retry, f.retry); return false; } if (errorcode != f.errorcode) { EXPECT_EQ(errorcode, f.errorcode); return false; } return true; } }; // capture results from the ways of gettnig the file info std::map plain_fopen; std::map iterate_fopen; std::map plain_follow_fopen; std::map iterate_follow_fopen; auto fsa = std::make_unique(); auto localdir = fspathToLocal(iteratePath); std::unique_ptr fopen_directory(fsa->newfileaccess(false)); // false = don't follow symlinks ASSERT_TRUE(fopen_directory->fopen(localdir, OPEN_RDONLY, FSLogging::logOnError)); // now open and iterate the directory, not following symlinks (either by name or fopen'd directory) std::unique_ptr da(fsa->newdiraccess()); if (da->dopen(openWithNameOrUseFileAccess ? &localdir : NULL, openWithNameOrUseFileAccess ? NULL : fopen_directory.get(), false)) { nodetype_t type; LocalPath itemlocalname; while (da->dnext(localdir, itemlocalname, false, &type)) { string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname); std::unique_ptr plain_fopen_fa(fsa->newfileaccess(false)); std::unique_ptr iterate_fopen_fa(fsa->newfileaccess(false)); LocalPath localpath = localdir; localpath.appendWithSeparator(itemlocalname, true); ASSERT_TRUE(plain_fopen_fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)); plain_fopen[leafNameUtf8] = *plain_fopen_fa; ASSERT_TRUE(iterate_fopen_fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError, da.get())); iterate_fopen[leafNameUtf8] = *iterate_fopen_fa; } } std::unique_ptr fopen_directory2(fsa->newfileaccess(true)); // true = follow symlinks ASSERT_TRUE(fopen_directory2->fopen(localdir, OPEN_RDONLY, FSLogging::logOnError)); // now open and iterate the directory, following symlinks (either by name or fopen'd directory) std::unique_ptr da_follow(fsa->newdiraccess()); if (da_follow->dopen(openWithNameOrUseFileAccess ? &localdir : NULL, openWithNameOrUseFileAccess ? NULL : fopen_directory2.get(), false)) { nodetype_t type; LocalPath itemlocalname; while (da_follow->dnext(localdir, itemlocalname, true, &type)) { string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname); std::unique_ptr plain_follow_fopen_fa(fsa->newfileaccess(true)); std::unique_ptr iterate_follow_fopen_fa(fsa->newfileaccess(true)); LocalPath localpath = localdir; localpath.appendWithSeparator(itemlocalname, true); ASSERT_TRUE( plain_follow_fopen_fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError)); plain_follow_fopen[leafNameUtf8] = *plain_follow_fopen_fa; ASSERT_TRUE(iterate_follow_fopen_fa->fopen(localpath, OPEN_RDONLY, FSLogging::logOnError, da_follow.get())); iterate_follow_fopen[leafNameUtf8] = *iterate_follow_fopen_fa; } } #ifdef WIN32 std::set plain_names { "folder", "file.txt" }; // currently on windows, any type of symlink is ignored when iterating directories std::set follow_names { "folder", "file.txt"}; #else std::set plain_names { "folder", "file.txt" }; std::set follow_names { "folder", "file.txt", "folderlink", "filelink.txt" }; #endif ASSERT_EQ(plain_fopen.size(), plain_names.size() + glob_entries); ASSERT_EQ(iterate_fopen.size(), plain_names.size() + glob_entries); ASSERT_EQ(plain_follow_fopen.size(), follow_names.size() + glob_entries); ASSERT_EQ(iterate_follow_fopen.size(), follow_names.size() + glob_entries); for (auto& name : follow_names) { bool expected_non_follow = plain_names.find(name) != plain_names.end(); bool issymlink = name.find("link") != string::npos; if (expected_non_follow) { ASSERT_TRUE(plain_fopen.find(name) != plain_fopen.end()) << name; ASSERT_TRUE(iterate_fopen.find(name) != iterate_fopen.end()) << name; auto& plain = plain_fopen[name]; auto& iterate = iterate_fopen[name]; ASSERT_EQ(plain, iterate) << name; ASSERT_TRUE(plain.mIsSymLink == issymlink); } ASSERT_TRUE(plain_follow_fopen.find(name) != plain_follow_fopen.end()) << name; ASSERT_TRUE(iterate_follow_fopen.find(name) != iterate_follow_fopen.end()) << name; auto& plain_follow = plain_follow_fopen[name]; auto& iterate_follow = iterate_follow_fopen[name]; ASSERT_EQ(plain_follow, iterate_follow) << name; ASSERT_TRUE(plain_follow.mIsSymLink == issymlink); } //ASSERT_EQ(plain_fopen["folder"].size, 0); size field is not set for folders ASSERT_EQ(plain_fopen["folder"].type, FOLDERNODE); ASSERT_EQ(plain_fopen["folder"].fsidvalid, true); ASSERT_EQ(plain_fopen["folder"].mIsSymLink, false); ASSERT_EQ(plain_fopen["file.txt"].size, 12); ASSERT_EQ(plain_fopen["file.txt"].fsidvalid, true); ASSERT_EQ(plain_fopen["file.txt"].type, FILENODE); ASSERT_EQ(plain_fopen["file.txt"].mIsSymLink, false); // on windows and mac and linux, without the follow flag on, directory iteration does not report symlinks (currently) // // //ASSERT_EQ(plain_fopen["folder"].size, 0); size field is not set for folders // ASSERT_EQ(plain_fopen["folderlink"].type, FOLDERNODE); // ASSERT_EQ(plain_fopen["folderlink"].fsidvalid, true); // ASSERT_EQ(plain_fopen["folderlink"].mIsSymLink, true); // // ASSERT_EQ(plain_fopen["filelink.txt"].size, 12); // ASSERT_EQ(plain_fopen["filelink.txt"].fsidvalid, true); // ASSERT_EQ(plain_fopen["filelink.txt"].type, FILENODE); // ASSERT_EQ(plain_fopen["filelink.txt"].mIsSymLink, true); // ASSERT_TRUE(plain_fopen.find("folderlink") == plain_fopen.end()); ASSERT_TRUE(plain_fopen.find("filelink.txt") == plain_fopen.end()); // check the glob flag auto localdirGlob = fspathToLocal(iteratePath / "glob1*"); std::unique_ptr da2(fsa->newdiraccess()); if (da2->dopen(&localdirGlob, NULL, true)) { nodetype_t type; LocalPath itemlocalname; set remainingExpected { "glob1folder", "glob1file.txt" }; while (da2->dnext(localdir, itemlocalname, true, &type)) { string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname); ASSERT_EQ(leafNameUtf8.substr(0, 5), string("glob1")); ASSERT_TRUE(remainingExpected.find(leafNameUtf8) != remainingExpected.end()); remainingExpected.erase(leafNameUtf8); } ASSERT_EQ(remainingExpected.size(), 0u); } } } /** * @brief TEST_F SdkTestConsoleAutocomplete * * Run various tests confirming the console autocomplete will work as expected * */ #ifdef _WIN32 bool cmp(const autocomplete::CompletionState& c, std::vector& s) { bool result = true; if (c.completions.size() != s.size()) { result = false; } else { std::sort(s.begin(), s.end()); for (size_t i = c.completions.size(); i--; ) { if (c.completions[i].s != s[i]) { result = false; break; } } } if (!result) { for (size_t i = 0; i < c.completions.size() || i < s.size(); ++i) { out() << (i < s.size() ? s[i] : "") << "/" << (i < c.completions.size() ? c.completions[i].s : ""); } } return result; } TEST_F(SdkTest, SdkTestConsoleAutocomplete) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); using namespace autocomplete; { std::unique_ptr p(new Either); p->Add(sequence(text("cd"))); p->Add(sequence(text("lcd"))); p->Add(sequence(text("ls"), opt(flag("-R")))); p->Add(sequence(text("lls"), opt(flag("-R")), param("folder"))); ACN syntax(std::move(p)); { auto r = autoComplete("", 0, syntax, false); std::vector e{ "cd", "lcd", "ls", "lls" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("l", 1, syntax, false); std::vector e{ "lcd", "ls", "lls" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ll", 2, syntax, false); std::vector e{ "lls" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls", 3, syntax, false); std::vector e{ "lls" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls ", 4, syntax, false); std::vector e{ "" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls -", 5, syntax, false); std::vector e{ "-R" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("x", 1, syntax, false); std::vector e{}; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("x ", 2, syntax, false); std::vector e{}; ASSERT_TRUE(cmp(r, e)); } } ::mega::NodeHandle megaCurDir; auto client{megaApi[0]->getClient()}; std::unique_ptr p(new Either); p->Add(sequence(text("cd"))); p->Add(sequence(text("lcd"))); p->Add(sequence(text("ls"), opt(flag("-R")), opt(ACN(new MegaFS(true, true, client, &megaCurDir, ""))))); p->Add(sequence(text("lls"), opt(flag("-R")), opt(ACN(new LocalFS(true, true, ""))))); ACN syntax(std::move(p)); error_code e; fs::remove_all("test_autocomplete_files", e); fs::create_directory("test_autocomplete_files"); fs::path old_cwd = fs::current_path(); fs::current_path("test_autocomplete_files"); fs::create_directory("dir1"); fs::create_directory("dir1\\sub11"); fs::create_directory("dir1\\sub12"); fs::create_directory("dir2"); fs::create_directory("dir2\\sub21"); fs::create_directory("dir2\\sub22"); fs::create_directory("dir2a"); fs::create_directory("dir2a\\dir space"); fs::create_directory("dir2a\\dir space\\next"); fs::create_directory("dir2a\\dir space2"); fs::create_directory("dir2a\\nospace"); { auto r = autoComplete("ls -R", 5, syntax, false); std::vector e{"-R"}; ASSERT_TRUE(cmp(r, e)); } // dos style file completion, local fs CompletionTextOut s; { auto r = autoComplete("lls ", 4, syntax, false); std::vector e{ "dir1", "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir1"); } { auto r = autoComplete("lls di", 6, syntax, false); std::vector e{ "dir1", "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2", 8, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2a", 9, syntax, false); std::vector e{ "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2 something after", 8, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2something immeditely after", 8, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2\\", 9, syntax, false); std::vector e{ "dir2\\sub21", "dir2\\sub22" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2\\.\\", 11, syntax, false); std::vector e{ "dir2\\.\\sub21", "dir2\\.\\sub22" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2\\..", 11, syntax, false); std::vector e{ "dir2\\.." }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("lls dir2\\..\\", 12, syntax, false); std::vector e{ "dir2\\..\\dir1", "dir2\\..\\dir2", "dir2\\..\\dir2a" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir1"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir2"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir2a"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir1"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir2a"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir2"); } { auto r = autoComplete("lls dir2a\\", 10, syntax, false); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls dir2a\\nospace"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls \"dir2a\\dir space2\""); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls \"dir2a\\dir space\""); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "lls dir2a\\nospace"); } { auto r = autoComplete("lls \"dir\"1\\", 11, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"dir1\\sub11\""); } { auto r = autoComplete("lls dir1\\\"..\\dir2\\\"", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"dir1\\..\\dir2\\sub21\""); } { auto r = autoComplete("lls c:\\prog", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"c:\\Program Files\""); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"c:\\Program Files (x86)\""); } { auto r = autoComplete("lls \"c:\\program files \"", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"c:\\Program Files (x86)\""); } // unix style completions, local fs { auto r = autoComplete("lls ", 4, syntax, true); std::vector e{ "dir1\\", "dir2\\", "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir"); } { auto r = autoComplete("lls di", 6, syntax, true); std::vector e{ "dir1\\", "dir2\\", "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir"); } { auto r = autoComplete("lls dir2", 8, syntax, true); std::vector e{ "dir2\\", "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2"); } { auto r = autoComplete("lls dir2a", 9, syntax, true); std::vector e{ "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2a\\"); } { auto r = autoComplete("lls dir2 something after", 8, syntax, true); std::vector e{ "dir2\\", "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2 something after"); } { auto r = autoComplete("lls dir2asomething immediately after", 9, syntax, true); std::vector e{ "dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2a\\something immediately after"); } { auto r = autoComplete("lls dir2\\", 9, syntax, true); std::vector e{ "dir2\\sub21\\", "dir2\\sub22\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\sub2"); auto rr = autoComplete("lls dir2\\sub22", 14, syntax, true); applyCompletion(rr, true, 100, s); ASSERT_EQ(rr.line, "lls dir2\\sub22\\"); } { auto r = autoComplete("lls dir2\\.\\", 11, syntax, true); std::vector e{ "dir2\\.\\sub21\\", "dir2\\.\\sub22\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\.\\sub2"); } { auto r = autoComplete("lls dir2\\..", 11, syntax, true); std::vector e{ "dir2\\..\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\"); } { auto r = autoComplete("lls dir2\\..\\", 12, syntax, true); std::vector e{ "dir2\\..\\dir1\\", "dir2\\..\\dir2\\", "dir2\\..\\dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir"); } { auto r = autoComplete("lls dir2\\..\\", 12, syntax, true); std::vector e{ "dir2\\..\\dir1\\", "dir2\\..\\dir2\\", "dir2\\..\\dir2a\\" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls dir2\\..\\dir"); } { auto r = autoComplete("lls dir2a\\d", 11, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"dir2a\\dir space\""); auto rr = autoComplete("lls \"dir2a\\dir space\"\\", std::string::npos, syntax, false); applyCompletion(rr, true, 100, s); ASSERT_EQ(rr.line, "lls \"dir2a\\dir space\\next\""); } { auto r = autoComplete("lls \"dir\"1\\", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"dir1\\sub1\""); } { auto r = autoComplete("lls dir1\\\"..\\dir2\\\"", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"dir1\\..\\dir2\\sub2\""); } { auto r = autoComplete("lls c:\\prog", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls c:\\program"); } { auto r = autoComplete("lls \"c:\\program files \"", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls \"c:\\program files (x86)\\\""); } { auto r = autoComplete("lls 'c:\\program files '", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "lls 'c:\\program files (x86)\\'"); } // mega dir setup MegaNode *rootnode = megaApi[0]->getRootNode(); auto nh = createFolder(0, "test_autocomplete_megafs", rootnode); ASSERT_NE(nh, UNDEF); MegaNode *n0 = megaApi[0]->getNodeByHandle(nh); megaCurDir = NodeHandle().set6byte(nh); nh = createFolder(0, "dir1", n0); ASSERT_NE(nh, UNDEF); MegaNode *n1 = megaApi[0]->getNodeByHandle(nh); ASSERT_NE(createFolder(0, "sub11", n1), UNDEF); ASSERT_NE(createFolder(0, "sub12", n1), UNDEF); nh = createFolder(0, "dir2", n0); ASSERT_NE(nh, UNDEF); MegaNode *n2 = megaApi[0]->getNodeByHandle(nh); ASSERT_NE(createFolder(0, "sub21", n2), UNDEF); ASSERT_NE(createFolder(0, "sub22", n2), UNDEF); nh = createFolder(0, "dir2a", n0); ASSERT_NE(nh, UNDEF); MegaNode *n3 = megaApi[0]->getNodeByHandle(nh); nh = createFolder(0, "dir space", n3); ASSERT_NE(nh, UNDEF); MegaNode *n31 = megaApi[0]->getNodeByHandle(nh); ASSERT_NE(createFolder(0, "dir space2", n3), UNDEF); ASSERT_NE(createFolder(0, "nospace", n3), UNDEF); ASSERT_NE(createFolder(0, "next", n31), UNDEF); // dos style mega FS completions { auto r = autoComplete("ls ", std::string::npos, syntax, false); std::vector e{ "dir1", "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir1"); } { auto r = autoComplete("ls di", std::string::npos, syntax, false); std::vector e{ "dir1", "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2", std::string::npos, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2a", std::string::npos, syntax, false); std::vector e{ "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2 something after", 7, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2something immeditely after", 7, syntax, false); std::vector e{ "dir2", "dir2a" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2/", std::string::npos, syntax, false); std::vector e{ "dir2/sub21", "dir2/sub22" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2/./", std::string::npos, syntax, false); std::vector e{ "dir2/./sub21", "dir2/./sub22" }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2/..", std::string::npos, syntax, false); std::vector e{ "dir2/.." }; ASSERT_TRUE(cmp(r, e)); } { auto r = autoComplete("ls dir2/../", std::string::npos, syntax, false); std::vector e{ "dir2/../dir1", "dir2/../dir2", "dir2/../dir2a" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir1"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir2"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir2a"); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir1"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir2a"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir2"); } { auto r = autoComplete("ls dir2a/", std::string::npos, syntax, false); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls dir2a/nospace"); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls \"dir2a/dir space2\""); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls \"dir2a/dir space\""); applyCompletion(r, false, 100, s); ASSERT_EQ(r.line, "ls dir2a/nospace"); } { auto r = autoComplete("ls \"dir\"1/", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"dir1/sub11\""); } { auto r = autoComplete("ls dir1/\"../dir2/\"", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"dir1/../dir2/sub21\""); } { auto r = autoComplete("ls /test_autocomplete_meg", std::string::npos, syntax, false); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls /test_autocomplete_megafs"); } // unix style mega FS completions { auto r = autoComplete("ls ", std::string::npos, syntax, true); std::vector e{ "dir1/", "dir2/", "dir2a/" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir"); } { auto r = autoComplete("ls di", std::string::npos, syntax, true); std::vector e{ "dir1/", "dir2/", "dir2a/" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir"); } { auto r = autoComplete("ls dir2", std::string::npos, syntax, true); std::vector e{ "dir2/", "dir2a/" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2"); } { auto r = autoComplete("ls dir2a", std::string::npos, syntax, true); std::vector e{ "dir2a/" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2a/"); } { auto r = autoComplete("ls dir2 something after", 7, syntax, true); std::vector e{ "dir2/", "dir2a/" }; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2 something after"); } { auto r = autoComplete("ls dir2asomething immediately after", 8, syntax, true); std::vector e{"dir2a/"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2a/something immediately after"); } { auto r = autoComplete("ls dir2/", std::string::npos, syntax, true); std::vector e{"dir2/sub21/", "dir2/sub22/"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/sub2"); auto rr = autoComplete("ls dir2/sub22", std::string::npos, syntax, true); applyCompletion(rr, true, 100, s); ASSERT_EQ(rr.line, "ls dir2/sub22/"); } { auto r = autoComplete("ls dir2/./", std::string::npos, syntax, true); std::vector e{"dir2/./sub21/", "dir2/./sub22/"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/./sub2"); } { auto r = autoComplete("ls dir2/..", std::string::npos, syntax, true); std::vector e{"dir2/../"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../"); } { auto r = autoComplete("ls dir2/../", std::string::npos, syntax, true); std::vector e{"dir2/../dir1/", "dir2/../dir2/", "dir2/../dir2a/"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir"); } { auto r = autoComplete("ls dir2/../", std::string::npos, syntax, true); std::vector e{"dir2/../dir1/", "dir2/../dir2/", "dir2/../dir2a/"}; ASSERT_TRUE(cmp(r, e)); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls dir2/../dir"); } { auto r = autoComplete("ls dir2a/d", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"dir2a/dir space\""); auto rr = autoComplete("ls \"dir2a/dir space\"/", std::string::npos, syntax, false); applyCompletion(rr, true, 100, s); ASSERT_EQ(rr.line, "ls \"dir2a/dir space/next\""); } { auto r = autoComplete("ls \"dir\"1/", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"dir1/sub1\""); } { auto r = autoComplete("ls dir1/\"../dir2/\"", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"dir1/../dir2/sub2\""); } { auto r = autoComplete("ls /test_autocomplete_meg", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls /test_autocomplete_megafs/"); r = autoComplete(r.line + "dir2a", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls /test_autocomplete_megafs/dir2a/"); r = autoComplete(r.line + "d", std::string::npos, syntax, true); applyCompletion(r, true, 100, s); ASSERT_EQ(r.line, "ls \"/test_autocomplete_megafs/dir2a/dir space\""); } fs::current_path(old_cwd); } #endif #ifdef ENABLE_CHAT /** * @brief TEST_F SdkTestChat * * Initialize a test scenario by: * * - Setting a new contact to chat with * * Performs different operations related to chats: * * - Fetch the list of available chats * - Create a group chat * - Remove a peer from the chat * - Invite a contact to a chat * - Get the user-specific URL for the chat * - Update permissions of an existing peer in a chat */ TEST_F(SdkTest, SdkTestChat) { CASE_info << "started"; testChat(false); CASE_info << "finished"; } TEST_F(SdkTest, SdkTestPublicChat) { CASE_info << "started"; testChat(true); CASE_info << "finished"; } #endif TEST_F(SdkTest, SdkTestFolderInfo) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); auto f1node = createDirectory(*megaApi[0], *rootNode, "folder1"); ASSERT_EQ(result(f1node), API_OK); ASSERT_TRUE(value(f1node)); auto node = createDirectory(*megaApi[0], *value(f1node), "folder1.1"); ASSERT_EQ(result(node), API_OK); ASSERT_TRUE(value(node)); ASSERT_TRUE(createFile(UPFILE, false)); // local file MegaHandle fileHande = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHande, UPFILE.c_str(), value(node).get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHande, UPFILE.c_str(), value(f1node).get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, value(f1node).get())); auto info = mApi[0].mFolderInfo.get(); ASSERT_EQ(info->getNumFiles(), 2); ASSERT_EQ(info->getNumFolders(), 1); } class myMIS : public MegaInputStream { public: int64_t size; ifstream ifs; myMIS(const char* filename) : ifs(filename, ios::binary) { ifs.seekg(0, ios::end); size = ifs.tellg(); ifs.seekg(0, ios::beg); } virtual int64_t getSize() { return size; } virtual bool read(char *buffer, size_t size) { if (buffer) { ifs.read(buffer, static_cast(size)); } else { ifs.seekg(static_cast(size), ios::cur); } return !ifs.fail(); } }; TEST_F(SdkTest, SdkTestFingerprint) { LOG_info << "___TEST fingerprint stream/file___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int filesizes[] = { 10, 100, 1000, 10000, 100000, 10000000 }; string expected[] = { "DAQoBAMCAQQDAgEEAwAAAAAAAAQAypo7", "DAWQjMO2LBXoNwH_agtF8CX73QQAypo7", "EAugDFlhW_VTCMboWWFb9VMIxugQAypo7", "EAhAnWCqOGBx0gGOWe7N6wznWRAQAypo7", "GA6CGAQFLOwb40BGchttx22PvhZ5gQAypo7", "GA4CWmAdW1TwQ-bddEIKTmSDv0b2QQAypo7", }; auto fsa = std::make_unique(); string name = "testfile"; LocalPath localname = LocalPath::fromAbsolutePath(name); int value = 0x01020304; for (int i = sizeof filesizes / sizeof filesizes[0]; i--; ) { { ofstream ofs(name.c_str(), ios::binary); char s[8192]; ofs.rdbuf()->pubsetbuf(s, sizeof s); for (auto j = static_cast(filesizes[i]) / sizeof(value); j--;) ofs.write((char*)&value, sizeof(value)); ofs.write((char*)&value, static_cast(filesizes[i]) % sizeof(value)); } fsa->setmtimelocal(localname, 1000000000); string streamfp, filefp; { m_time_t mtime = 0; { auto nfa = fsa->newfileaccess(); nfa->fopen(localname, FSLogging::logOnError); mtime = nfa->mtime; } myMIS mis(name.c_str()); streamfp.assign(std::unique_ptr(megaApi[0]->getFingerprint(&mis, mtime)).get()); } filefp = std::unique_ptr(megaApi[0]->getFingerprint(name.c_str())).get(); ASSERT_EQ(streamfp, filefp); ASSERT_EQ(streamfp, expected[i]); } } static void incrementFilename(string& s) { if (s.size() > 2) { if (is_digit(static_cast(s[s.size() - 2])) || !is_digit(static_cast(s[s.size() - 1]))) { s += "00"; } else { s[s.size() - 1] = static_cast(s[s.size()-1] + 1); if (s[s.size() - 1] > '9') { s[s.size() - 1] = static_cast(s[s.size()-1] - 1); s[s.size() - 2] = static_cast(s[s.size()-2] + 1); } } } } struct second_timer { m_time_t t; m_time_t pause_t; second_timer() { t = m_time(); } void reset () { t = m_time(); } void pause() { pause_t = m_time(); } void resume() { t += m_time() - pause_t; } size_t elapsed() { return size_t(m_time() - t); } }; namespace mega { class DebugTestHook { private: static constexpr int DEFAULT_COUNTDOWN = -1; static constexpr bool DEFAULT_BOOL = false; static constexpr m_off_t DEFAULT_PROGRESS = 0; struct HttpOutcome { int http{}; unsigned curl{}; bool failed{}; }; public: struct ObservedFailures { std::map byHttpStatus; std::map byCurlCode; unsigned totalFailed{0}; }; static inline int countdownToOverquota{DEFAULT_COUNTDOWN}; static inline int countdownTo404{DEFAULT_COUNTDOWN}; static inline int countdownTo403{DEFAULT_COUNTDOWN}; static inline int countdownTo429{DEFAULT_COUNTDOWN}; static inline int countdownTo503{DEFAULT_COUNTDOWN}; static inline int countdownTo200WithErrCode{DEFAULT_COUNTDOWN}; static inline int countdownToTimeout{DEFAULT_COUNTDOWN}; static inline bool isRaid{DEFAULT_BOOL}; static inline bool isRaidKnown{DEFAULT_BOOL}; static inline m_off_t testProgressCompleted{DEFAULT_PROGRESS}; static inline m_off_t testProgressContiguous{DEFAULT_PROGRESS}; static inline std::vector sHttpOutcomes; static void resetValues() { countdownToOverquota = DEFAULT_COUNTDOWN; countdownTo404 = DEFAULT_COUNTDOWN; countdownTo403 = DEFAULT_COUNTDOWN; countdownTo429 = DEFAULT_COUNTDOWN; countdownTo503 = DEFAULT_COUNTDOWN; countdownTo200WithErrCode = DEFAULT_COUNTDOWN; countdownToTimeout = DEFAULT_COUNTDOWN; isRaid = DEFAULT_BOOL; isRaidKnown = DEFAULT_BOOL; testProgressCompleted = DEFAULT_PROGRESS; testProgressContiguous = DEFAULT_PROGRESS; sHttpOutcomes.clear(); } static void onSetIsRaid_morechunks(::mega::RaidBufferManager* tbm) { unsigned oldvalue = tbm->raidLinesPerChunk; unsigned minDivisorSize = 4 * 1024 * 1024; // raidLinesPerChunk is defined by MAX_REQ_SIZE value, which depends on the system -> division factor of 4 for different max_req_sizes unsigned divideBy = std::max(static_cast(TransferSlot::MAX_REQ_SIZE / minDivisorSize), static_cast(1)); tbm->raidLinesPerChunk = tbm->raidLinesPerChunk / divideBy; tbm->disableAvoidSmallLastRequest(); LOG_info << "adjusted raidlinesPerChunk from " << oldvalue << " to " << tbm->raidLinesPerChunk << " and set AvoidSmallLastRequest flag to false"; } static void onHttpReqFinished(const int httpStatus, const unsigned curlCode, const bool failed) { LOG_info << "onHttpReqFinished: httpStatus=" << httpStatus << ", curlCode=" << curlCode << ", failed=" << failed; sHttpOutcomes.push_back({httpStatus, curlCode, failed}); } static bool onHttpReqFinishedWithSimulatedError(HttpReq& req) { LOG_info << "SIMULATING HTTP GET " << req.httpstatus << (req.mErrCode ? " with error CURLcode " + std::to_string(req.mErrCode) : ""); if (req.httpstatus == 200 && !req.mErrCode) { LOG_err << "SIMULATING HTTP GET 200 with no error code -> return false"; return false; } req.status = REQ_FAILURE; onHttpReqFinished(req.httpstatus, req.mErrCode, true); return true; } static bool onHttpReqPost509(HttpReq* req) { if (!req || req->type != REQ_BINARY || countdownToOverquota-- != 0) return false; req->httpstatus = 509; req->timeleft = 30; // in seconds LOG_info << "SIMULATING OVERQUOTA"; return onHttpReqFinishedWithSimulatedError(*req); } static bool onHttpReqPostError(HttpReq* req) { if (!req) return false; if (req->type == REQ_BINARY) { if (countdownTo404-- == 0) { req->httpstatus = 404; return onHttpReqFinishedWithSimulatedError(*req); } if (countdownTo403-- == 0) { req->httpstatus = 403; return onHttpReqFinishedWithSimulatedError(*req); } if (countdownTo429-- == 0) { req->httpstatus = 429; return onHttpReqFinishedWithSimulatedError(*req); } if (countdownTo503-- == 0) { req->httpstatus = 503; return onHttpReqFinishedWithSimulatedError(*req); } if (countdownTo200WithErrCode-- == 0) { req->httpstatus = 200; req->mErrCode = 56; return onHttpReqFinishedWithSimulatedError(*req); } } return false; } static bool onHttpReqPostTimeout(HttpReq* req) { if (req->type == REQ_BINARY) { if (countdownToTimeout-- == 0) { req->lastdata = Waiter::ds; req->status = REQ_INFLIGHT; LOG_info << "SIMULATING HTTP TIMEOUT (timeout period begins now)"; return true; } } return false; } static void onSetIsRaid(::mega::RaidBufferManager* tbm) { isRaid = tbm->isRaid() || tbm->isNewRaid(); isRaidKnown = true; onSetIsRaid_morechunks(tbm); } static void onLimitMaxReqSize(m_off_t& maxReqSize) // Only valid for TransferBufferManager { auto oldMaxRequestSize = maxReqSize; maxReqSize = std::min(maxReqSize, 1024 * 1024); LOG_info << "onLimitMaxReqSize: adjusted maxRequestSize from " << oldMaxRequestSize << " to " << maxReqSize; } static void onHookNumberOfConnections(int& connections, int clientNumberOfConnections) // Only valid for TransferBufferManager { LOG_info << "onLimitMaxReqSize: adjusted number of connections from " << connections << " to " << clientNumberOfConnections; connections = clientNumberOfConnections; } static void onHookDownloadRequestSingleUrl(bool& singleUrl) { LOG_info << "onHookDownloadRequestSingleUrl: set current singleUrl value (" << singleUrl << ") to true"; singleUrl = true; } static void onHookResetTransferLastAccessTime(m_time_t& lastAccessTime) { LOG_info << "onHookResetTransferLastAccessTime: reset current lastAccessTime value (" << lastAccessTime << ") to 0"; lastAccessTime = 0; } static void onProgressCompletedUpdate(const m_off_t p) { if (p) { // ignore ProgressCompleted reset(0) testProgressCompleted = p; } LOG_info << "onProgressCompletedUpdate:(" << p << ")"; } static void onProgressContiguousUpdate(const m_off_t p) { if (p) { // ignore ProgressContiguous reset(0) testProgressContiguous = p; } LOG_info << "onProgressContiguousUpdate:(" << p << ")"; } static bool resetForTests() { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks = MegaTestHooks(); // remove any callbacks set in other tests resetValues(); return true; #else return false; #endif } static void onSetIsRaid_smallchunks10(::mega::RaidBufferManager* tbm) { tbm->raidLinesPerChunk = 10; } static ObservedFailures observedFailures() { ObservedFailures observedFailures; for (const auto& o: sHttpOutcomes) { if (!o.failed) continue; ++observedFailures.totalFailed; if (o.http) ++observedFailures.byHttpStatus[o.http]; if (o.curl) ++observedFailures.byCurlCode[static_cast(o.curl)]; } return observedFailures; } }; } /** * @brief TEST_F SdkTestCloudraidTransfers * * - # Test1: Download our well-known cloudraid file with standard settings * - # Test2: Download our well-known cloudraid file, but this time with small chunk sizes and * periodically pausing and unpausing * - # Test3: Download our well-known cloudraid file, but this time with small chunk sizes and * periodically destrying the megaApi object, then recreating and Resuming (with session token) */ #ifdef DEBUG TEST_F(SdkTest, SdkTestCloudraidTransfers) { LOG_info << "___TEST Cloudraid transfers___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Try and retrieve the user's current account level. auto accountLevel = getAccountLevel(*megaApi[0]); // Couldn't retrieve account level. if (auto result = ::result(accountLevel); result != API_OK) { // Leave a trail if we couldn't get the account level. ASSERT_EQ(result, API_OK) << "Couldn't retrieve account level: " << result; } AccountLevel level = value(accountLevel); // Try and elevate client to a pro pricing plan. auto result = setAccountLevel(*megaApi[0], MegaAccountDetails::ACCOUNT_TYPE_PROI, 1, nullptr); // Couldn't elevate client to a pro pricing plan. ASSERT_EQ(result, API_OK) << "Unable to upgrade the account" << result; // Restore the account back to normal once test completed. std::shared_ptr restorer( &level, [this](AccountLevel* level) { // Try and restore the user's account level. auto result = setAccountLevel(*megaApi[0], level->plan, level->months, nullptr); EXPECT_EQ(result, API_OK) << "Couldn't restore account level: " << result; }); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; std::unique_ptr rootnode(megaApi[0]->getRootNode()); auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, rootnode.get()); MegaHandle imported_file_handle = importHandle; std::unique_ptr nimported(megaApi[0]->getNodeByHandle(imported_file_handle)); string filename = DOTSLASH "cloudraid_downloaded_file.sdktest"; deleteFile(filename.c_str()); LOG_debug << "#### Test1: Download our well-known cloudraid file with standard settings ####"; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download cloudraid transfer failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; LOG_debug << "#### Test2(SdkTestCloudraidTransfers): Download our well-known cloudraid file, " "but this time with small chunk sizes and periodically pausing and unpausing ####"; incrementFilename(filename); deleteFile(filename.c_str()); // smaller chunk sizes so we can get plenty of pauses #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onSetIsRaid = ::mega::DebugTestHook::onSetIsRaid_morechunks; globalMegaTestHooks.onLimitMaxReqSize = ::mega::DebugTestHook::onLimitMaxReqSize; globalMegaTestHooks.onHookNumberOfConnections = ::mega::DebugTestHook::onHookNumberOfConnections; #endif ASSERT_EQ(API_OK, doSetMaxConnections(0, 2)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_debug << "For raidTests: client max connections set to 2"; // plain cloudraid download { onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); m_off_t lastprogress = 0, pausecount = 0; second_timer t; while (t.elapsed() < 60 && (onTransferUpdate_filesize == 0 || onTransferUpdate_progress < onTransferUpdate_filesize)) { if (onTransferUpdate_progress > lastprogress) { megaApi[0]->pauseTransfers(true); pausecount += 1; WaitMillisec(100); megaApi[0]->pauseTransfers(false); lastprogress = onTransferUpdate_progress; } WaitMillisec(100); } ASSERT_LT(t.elapsed(), 60u) << "timed out downloading cloudraid file"; ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); ASSERT_GE(pausecount, 3); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 30))<< "Download cloudraid transfer with pauses failed"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; } incrementFilename(filename); deleteFile(filename.c_str()); LOG_debug << "#### Test3(SdkTestCloudraidTransfers): Download our well-known cloudraid file, " "but this time with small " "chunk sizes and periodically destrying the megaApi object, then recreating and " "Resuming (with session token)####"; // plain cloudraid download { megaApi[0]->setMaxDownloadSpeed(1024 * 1024); mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); std::string sessionId = unique_ptr(megaApi[0]->dumpSession()).get(); onTransferUpdate_progress = 0;// updated in callbacks onTransferUpdate_filesize = 0; m_off_t lastprogress = 0; unsigned exitresumecount = 0; second_timer t; auto initialOnTranferFinishedCount = onTranferFinishedCount; auto lastOnTranferFinishedCount = onTranferFinishedCount; while (t.elapsed() < static_cast(maxTimeout / 2) && onTranferFinishedCount < initialOnTranferFinishedCount + 2) { if (onTranferFinishedCount > lastOnTranferFinishedCount) { t.reset(); lastOnTranferFinishedCount = onTranferFinishedCount; deleteFile(filename.c_str()); onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; lastprogress = 0; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); } else if (onTransferUpdate_progress > lastprogress + onTransferUpdate_filesize/10 ) { if (exitresumecount < 3*(onTranferFinishedCount - initialOnTranferFinishedCount + 1)) { megaApi[0].reset(); exitresumecount += 1; WaitMillisec(100); megaApi[0] = newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT)); mApi[0].megaApi = megaApi[0].get(); megaApi[0]->addListener(this); megaApi[0]->setMaxDownloadSpeed(1024 * 1024); ASSERT_EQ(API_OK, doSetMaxConnections(0, 2)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_debug << "For raidTests: client max connections set to 2"; t.pause(); ASSERT_NO_FATAL_FAILURE(resumeSession(sessionId.c_str())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); t.resume(); lastprogress = onTransferUpdate_progress; } } WaitMillisec(1); } ASSERT_EQ(initialOnTranferFinishedCount + 2, onTranferFinishedCount) << onTranferFinishedCount << "transfers finished, but we expected " << initialOnTranferFinishedCount + 2; ASSERT_EQ(onTransferUpdate_progress, onTransferUpdate_filesize) << "Expected onTransferUpdate_progress: " << onTransferUpdate_progress << ", doesn't match with onTransferUpdate_filesize: " << onTransferUpdate_filesize; ASSERT_GE(exitresumecount, 6u); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 1)) << "Download cloudraid transfer with pauses failed"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; } ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } #endif // Cloudraid test helper structures and functions #ifdef DEBUG // Some HTTP requests may fail due to transient errors. // Those are "unexpected" for the test, but // "acceptable" as they may happen even when everything else is working fine. The test should not // pass if those errors are present, but it can be retried with a limit. // // Http status 0: no respons was received from the server. // -> with CURLcode (error) 28: connection timeout. It may happen if the client network is saturated // or the connection is idle and the OS (especially mobile) need resources. // -> with CURLcode (error) 7: the connection could not be established. It may happen if the server // is temporary unavailable, or some network issue with the client. struct CloudraidFailureAnalysis { unsigned seen404{0}; unsigned seen403{0}; unsigned unexpected{0}; unsigned acceptableUnexpected{0}; unsigned totalFailed{0}; static inline std::vector acceptableUnexpectedCurlCode = {28, 7}; }; struct CloudraidTestConfig { unsigned maxRetries{0}; unsigned maxAcceptableUnexpectedPerRun{0}; unsigned maxExpectedFailedRequests{0}; unsigned baseRequestCount{0}; bool hasExpectedFailures{false}; std::string testName{}; }; // Helper functions for cloudraid test analysis inline CloudraidFailureAnalysis analyzeCloudraidFailures(const CloudraidTestConfig& config, const std::string& logPrefix, const SdkTest::SdkTestTransferStats& transferStats) { CloudraidFailureAnalysis analysis{}; analysis.totalFailed = static_cast(transferStats.numFailedRequests); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED const auto obs = DebugTestHook::observedFailures(); analysis.seen404 = obs.byHttpStatus.count(404) ? obs.byHttpStatus.at(404) : 0u; analysis.seen403 = obs.byHttpStatus.count(403) ? obs.byHttpStatus.at(403) : 0u; analysis.unexpected = (analysis.totalFailed > config.maxExpectedFailedRequests) ? (analysis.totalFailed - config.maxExpectedFailedRequests) : 0u; for (const auto& curlCode: CloudraidFailureAnalysis::acceptableUnexpectedCurlCode) { analysis.acceptableUnexpected += obs.byCurlCode.count(static_cast(curlCode)) ? obs.byCurlCode.at(static_cast(curlCode)) : 0u; } std::ostringstream oss; oss << "Observed failures: totalFailed=" << analysis.totalFailed << " 404=" << analysis.seen404 << " 403=" << analysis.seen403 << " unexpected=" << analysis.unexpected << " acceptable=" << analysis.acceptableUnexpected << " | byHTTP:"; for (const auto& kv: obs.byHttpStatus) oss << " " << kv.first << "=" << kv.second; oss << " | byCURL:"; for (const auto& kv: obs.byCurlCode) oss << " " << kv.first << "=" << kv.second; LOG_debug << logPrefix << oss.str(); #else if (config.hasExpectedFailures) { analysis.seen404 = 1u; analysis.seen403 = 1u; } analysis.acceptableUnexpected = 0u; #endif return analysis; } inline void verifyCloudraidSuccessExpectations(const CloudraidFailureAnalysis& analysis, const CloudraidTestConfig& config, const m_off_t filesize, const m_off_t progress, const SdkTest::SdkTestTransferStats& transferStats) { EXPECT_GT(filesize, 0u); EXPECT_EQ(progress, filesize); if (config.hasExpectedFailures) { EXPECT_LT(DebugTestHook::countdownTo404, 0); EXPECT_LT(DebugTestHook::countdownTo403, 0); EXPECT_EQ(analysis.seen404, 1u) << "Expected one HTTP 404 failure but none observed"; EXPECT_EQ(analysis.seen403, 1u) << "Expected one HTTP 403 failure but none observed"; } EXPECT_EQ(transferStats.numTotalRequests, config.baseRequestCount + config.maxExpectedFailedRequests); EXPECT_EQ(transferStats.numFailedRequests, config.maxExpectedFailedRequests); if (transferStats.numFailedRequests > 0) { const auto expectedFailedRequestRatio = static_cast(transferStats.numFailedRequests) / static_cast(transferStats.numTotalRequests); EXPECT_NEAR(transferStats.failedRequestRatio, expectedFailedRequestRatio, 1e-6); } else { EXPECT_EQ(transferStats.failedRequestRatio, 0.0); } } class CloudraidTestRunner { public: explicit CloudraidTestRunner(const CloudraidTestConfig& config, const std::string& logPrefix): m_config(config), m_logPrefix(logPrefix) {} // Static helper to create a shared download function static std::function createDownloadFunction(SdkTest* test, MegaNode* node, const std::string& filename, const std::string& logPrefix, std::function additionalAssertions = nullptr) { return [test, node, filename, logPrefix, additionalAssertions]() { constexpr int timeoutSecs = 180; test->onTransferUpdate_progress = 0; test->onTransferUpdate_filesize = 0; test->mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; const auto t0 = std::chrono::steady_clock::now(); test->megaApi[0]->startDownload( node, filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); ASSERT_TRUE( test->waitForResponse(&test->mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], timeoutSecs)) << logPrefix << "timeout after " << timeoutSecs << " secs"; ASSERT_EQ(API_OK, test->mApi[0].lastError) << "Cannot download the cloudraid file (error: " << test->mApi[0].lastError << ")"; const auto t1 = std::chrono::steady_clock::now(); const auto ms = std::chrono::duration_cast(t1 - t0).count(); LOG_debug << logPrefix << "downloadTime = " << ms << " ms, size = " << node->getSize() << " [speed = " << (node->getSize() / std::max(ms, 1)) << " B/s]"; // Run any additional test-specific assertions if (additionalAssertions) { additionalAssertions(); } }; } bool runTestWithRetries(std::function resetAttempt, std::function runDownloadAndWait, const SdkTest::SdkTestTransferStats& transferStats, const m_off_t& filesize, const m_off_t& progress) { bool passed = false; for (unsigned attempt = 0; attempt <= m_config.maxRetries && !passed; ++attempt) { LOG_debug << m_logPrefix << "Running test attempt " << (attempt + 1) << " of " << m_config.maxRetries; resetAttempt(); runDownloadAndWait(); const auto analysis = analyzeCloudraidFailures(m_config, m_logPrefix, transferStats); if (analysis.totalFailed != m_config.maxExpectedFailedRequests) { LOG_debug << m_logPrefix << "Failed attempt " << (attempt + 1) << ", expected " << m_config.maxExpectedFailedRequests << " failed requests but observed " << analysis.totalFailed; EXPECT_GT(transferStats.failedRequestRatio, 0.0); EXPECT_LE(transferStats.failedRequestRatio, 1.0); EXPECT_EQ(transferStats.numTotalRequests, m_config.baseRequestCount + analysis.totalFailed); const bool allUnexpectedAcceptable = (analysis.acceptableUnexpected == analysis.unexpected); const bool underCap = (analysis.acceptableUnexpected <= m_config.maxAcceptableUnexpectedPerRun); if (allUnexpectedAcceptable && underCap && attempt < m_config.maxRetries) { LOG_debug << m_logPrefix << "Transient failure(s) were detected (e.g. cURL 28) in failed " "attempt and " "conditions were met for a retry. Retrying... " "[analysis.acceptableUnexpected=" << analysis.acceptableUnexpected << ", allUnexpectedAcceptable=" << allUnexpectedAcceptable << ", maxAcceptableUnexpectedPerRun=" << m_config.maxAcceptableUnexpectedPerRun << "]"; continue; } else { LOG_debug << m_logPrefix << "Failed attempt did not meet conditions for a retry. Aborting... " "[analysis.acceptableUnexpected=" << analysis.acceptableUnexpected << ", allUnexpectedAcceptable=" << allUnexpectedAcceptable << ", maxAcceptableUnexpectedPerRun=" << m_config.maxAcceptableUnexpectedPerRun << "]"; } break; } // Success case - verify all expectations verifyCloudraidSuccessExpectations(analysis, m_config, filesize, progress, transferStats); passed = true; } return passed; } private: const CloudraidTestConfig m_config; const std::string m_logPrefix; }; #endif /** * @brief TEST_F SdkTestCloudraidTransferWithConnectionFailures * * Download a cloudraid file but with a connection failing with http errors 404 and 403. The * download should recover from the problems in 5 channel mode * */ #ifdef DEBUG TEST_F(SdkTest, SdkTestCloudraidTransferWithConnectionFailures) { static const auto logPre = getLogPrefix(); LOG_info << logPre << "___TEST Cloudraid transfers with connection failures___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; std::unique_ptr rootnode{megaApi[0]->getRootNode()}; auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, rootnode.get()); std::unique_ptr nimported{megaApi[0]->getNodeByHandle(importHandle)}; const std::string filename = DOTSLASH "cloudraid_downloaded_file.sdktest"; megaApi[0]->setMaxDownloadSpeed(1024 * 1024); ASSERT_EQ(API_OK, doSetMaxConnections(0, 2)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_debug << logPre << "For raidTests: client max connections set to 2"; // Configure test parameters const CloudraidTestConfig config{ 2, // maxRetries 5, // maxAcceptableUnexpectedPerRun 2, // maxExpectedFailedRequests 35, // baseRequestCount true, // hasExpectedFailures "ConnectionFailures" // testName }; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPostError; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks; globalMegaTestHooks.onLimitMaxReqSize = DebugTestHook::onLimitMaxReqSize; globalMegaTestHooks.onHookNumberOfConnections = DebugTestHook::onHookNumberOfConnections; globalMegaTestHooks.onHttpReqFinish = DebugTestHook::onHttpReqFinished; #endif auto resetAttempt = [this, &filename]() { DebugTestHook::resetValues(); DebugTestHook::countdownTo404 = 5; DebugTestHook::countdownTo403 = 12; deleteFile(filename.c_str()); }; auto runDownloadAndWait = CloudraidTestRunner::createDownloadFunction(this, nimported.get(), filename, logPre); CloudraidTestRunner runner{config, logPre}; const bool passed = runner.runTestWithRetries(resetAttempt, runDownloadAndWait, onTransferFinish_transferStats, onTransferUpdate_filesize, onTransferUpdate_progress); EXPECT_TRUE(passed); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } TEST_F(SdkTest, SdkTestCloudraidTransferBestCase) { LOG_info << "___TEST Cloudraid transfers best case___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); auto restorer1 = scopedToPro(*megaApi[1]); ASSERT_EQ(result(restorer1), API_OK); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; // https://mega.app/file/JzckQJ6L#X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA std::string url100MB = "/#!JzckQJ6L!X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA"; auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + url100MB, rootnode.get()); std::unique_ptr nimported{megaApi[0]->getNodeByHandle(importHandle)}; string filename = DOTSLASH "cloudraid_downloaded_file.sdktest"; deleteFile(filename.c_str()); // plain cloudraid download { onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; const auto& downloadStartTime = std::chrono::system_clock::now(); megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); unsigned int transfer_timeout_in_seconds = 180; ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], transfer_timeout_in_seconds)) << "Cloudraid download Best Case (without forced errors) time out (180 seconds)"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")"; const auto& downloadEndTime = std::chrono::system_clock::now(); auto downloadTime = std::chrono::duration_cast(downloadEndTime - downloadStartTime).count(); LOG_debug << "[SdkTestCloudRaidTransferBestCase] downloadTime = " << downloadTime << " ms, size = " << nimported->getSize() << "" << " [speed = " << (((nimported->getSize() / downloadTime) * 1000) / 1024) << " KB/s]"; ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); } } #endif /** * @brief TEST_F SdkTestCloudraidTransferWithSingleChannelTimeouts * * Download a cloudraid file but with a connection failing after a timeout. The download should recover from the problems in 5 channel mode * */ #ifdef DEBUG TEST_F(SdkTest, SdkTestCloudraidTransferWithSingleChannelTimeouts) { static const auto logPre = getLogPrefix(); LOG_info << logPre << "___TEST Cloudraid transfers with single channel timeouts___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; std::unique_ptr rootnode{megaApi[0]->getRootNode()}; auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, rootnode.get()); std::unique_ptr nimported{megaApi[0]->getNodeByHandle(importHandle)}; const std::string filename = DOTSLASH "cloudraid_downloaded_file.sdktest"; // Configure test parameters for timeout scenario const CloudraidTestConfig config{ 2, // maxRetries 5, // maxAcceptableUnexpectedPerRun 0, // maxExpectedFailedRequests - No expected failures for timeout test 36, // baseRequestCount - Base request count for this file/chunking + 1 timed out false, // hasExpectedFailures - No expected failures like 404/403 "SingleChannelTimeouts" // testName }; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPostTimeout; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks; globalMegaTestHooks.onLimitMaxReqSize = ::mega::DebugTestHook::onLimitMaxReqSize; globalMegaTestHooks.onHookNumberOfConnections = ::mega::DebugTestHook::onHookNumberOfConnections; #endif ASSERT_EQ(API_OK, doSetMaxConnections(0, 2)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_info << logPre << "For raidTests: client max connections set to 2"; auto resetAttempt = [this, &filename]() { DebugTestHook::resetValues(); DebugTestHook::countdownToTimeout = 15; deleteFile(filename.c_str()); }; auto runDownloadAndWait = CloudraidTestRunner::createDownloadFunction( this, nimported.get(), filename, logPre, []() { ASSERT_LT(DebugTestHook::countdownToTimeout, 0); }); CloudraidTestRunner runner{config, logPre}; const bool passed = runner.runTestWithRetries(resetAttempt, runDownloadAndWait, onTransferFinish_transferStats, onTransferUpdate_filesize, onTransferUpdate_progress); EXPECT_TRUE(passed); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } void SdkTest::testCloudRaidTransferResume(const bool fromNonRaid, const std::string& logPre) { LOG_info << logPre << "BEGIN"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_debug << logPre << "Promote account to PRO plan"; const auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; std::unique_ptr rootnode{megaApi[0]->getRootNode()}; LOG_debug << logPre << "Get CloudRAID file from public link"; const auto importRaidHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, rootnode.get()); unique_ptr cloudRaidNode{megaApi[0]->getNodeByHandle(importRaidHandle)}; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED [[maybe_unused]] m_off_t tProgressCompletedPreResume{0}; [[maybe_unused]] m_off_t tProgressContiguousPreResume{0}; globalMegaTestHooks.onProgressCompletedUpdate = ::mega::DebugTestHook::onProgressCompletedUpdate; globalMegaTestHooks.onProgressContiguousUpdate = ::mega::DebugTestHook::onProgressContiguousUpdate; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks; globalMegaTestHooks.onLimitMaxReqSize = DebugTestHook::onLimitMaxReqSize; globalMegaTestHooks.onHookNumberOfConnections = DebugTestHook::onHookNumberOfConnections; if (fromNonRaid) { globalMegaTestHooks.onHookDownloadRequestSingleUrl = DebugTestHook::onHookDownloadRequestSingleUrl; } #endif ASSERT_EQ(API_OK, doSetMaxConnections(0, 2)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_debug << logPre << "Client max connections set to 2"; LOG_debug << logPre << "Clean up any existing file, limit speed, start download"; const auto downloadedFile = std::string(DOTSLASH "cloudraid_downloaded_file.sdktest"); deleteFile(downloadedFile.c_str()); megaApi[0]->setMaxDownloadSpeed(2000000); onTransferUpdate_progress = 0; TransferTracker rdt(megaApi[0].get()); megaApi[0]->startDownload(cloudRaidNode.get(), downloadedFile.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false /* undelete */, &rdt /*listener*/); second_timer timer; static constexpr m_off_t pauseThreshold{9000000}; // Wait until partial download or timeout while (!rdt.finished && timer.elapsed() < 120 && onTransferUpdate_progress < pauseThreshold) { WaitMillisec(200); } ASSERT_FALSE(rdt.finished) << "Download ended too early, with " << rdt.waitForResult(); ASSERT_GT(onTransferUpdate_progress, 0) << "Nothing was downloaded"; // 2. Logout LOG_debug << logPre << "Local logout while the transfer is in flight"; std::unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); const auto result = rdt.waitForResult(); ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE) << "Download interrupted with unexpected code: " << result; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED tProgressCompletedPreResume = DebugTestHook::testProgressCompleted; tProgressContiguousPreResume = DebugTestHook::testProgressContiguous; [[maybe_unused]] m_off_t tProgressCompletedAfterResume{0}; [[maybe_unused]] m_off_t tProgressContiguousAfterResume{0}; [[maybe_unused]] std::atomic exitFlagAfterResume{false}; DebugTestHook::testProgressCompleted = 0; DebugTestHook::testProgressContiguous = 0; onTransferStartCustomCb = [&tProgressCompletedAfterResume, &tProgressContiguousAfterResume, &exitFlagAfterResume](MegaTransfer* t) -> void { if (t) { tProgressCompletedAfterResume = t->getTransferredBytes(); } tProgressContiguousAfterResume = DebugTestHook::testProgressContiguous; exitFlagAfterResume = true; }; if (fromNonRaid) { globalMegaTestHooks.onHookDownloadRequestSingleUrl = nullptr; globalMegaTestHooks.onHookResetTransferLastAccessTime = ::mega::DebugTestHook::onHookResetTransferLastAccessTime; } #endif LOG_debug << logPre << "Resume session"; onTransferStart_progress = 0; ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED ASSERT_TRUE(WaitFor(std::bind( [](const std::atomic& flag) { // wait for onTransferStart after login + fetchnodes return flag.load(); }, std::cref(exitFlagAfterResume)), 60000)); ASSERT_EQ(tProgressCompletedPreResume, tProgressCompletedAfterResume) << "Progress complete mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; ASSERT_EQ(tProgressContiguousPreResume, tProgressContiguousAfterResume) << "Progress contiguous mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; #endif ASSERT_EQ(API_OK, doSetMaxConnections(0, 4)) << "doSetMaxConnections failed or took more than 1 minute"; LOG_debug << logPre << "Client max connections set to 4"; LOG_debug << logPre << "Check transfer resumption after resuming session"; timer.reset(); unique_ptr transfers(megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)); while ((!transfers || !transfers->size()) && timer.elapsed() < 20) { WaitMillisec(100); transfers.reset(megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)); } ASSERT_EQ(transfers->size(), 1U) << "Download ended before resumption was checked, " "or was not resumed after 20 seconds"; ASSERT_GT(onTransferStart_progress, 0) << "Download appears to have been restarted instead of resumed"; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED if (fromNonRaid) { globalMegaTestHooks.onHookResetTransferLastAccessTime = nullptr; } #endif LOG_debug << logPre << "Let the download finish completely"; megaApi[0]->setMaxDownloadSpeed(-1); static constexpr size_t maxAllowedToFinishDownload{120}; while (transfers && transfers->size() && timer.elapsed() < maxAllowedToFinishDownload) { WaitMillisec(500); transfers.reset(megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)); } ASSERT_TRUE(!transfers || !transfers->size()) << "Download did not finish after " << maxAllowedToFinishDownload << " seconds"; // Test hooks must be reset ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; LOG_info << logPre << "FINISH"; } /** * @brief TEST_F SdkTestCloudraidTransferResume * * Tests resumption for raid file download. */ TEST_F(SdkTest, SdkTestCloudraidTransferResume) { static const auto logPre = getLogPrefix(); constexpr bool fromNonRaid{false}; testCloudRaidTransferResume(fromNonRaid, logPre); } /** * @brief TEST_F SdkTestCloudraidTransferResumeFromNonRaid * * Tests resumption from a non-raided download that is now raided and resumed with CloudRAID logic. */ TEST_F(SdkTest, SdkTestCloudraidTransferResumeFromNonRaid) { static const auto logPre = getLogPrefix(); constexpr bool fromNonRaid{true}; testCloudRaidTransferResume(fromNonRaid, logPre); } #endif /** * @brief TEST_F SdkTestOverquotaNonCloudraid * * Induces a simulated overquota error during a conventional download. Confirms the download stops, pauses, and resumes. * */ #ifdef DEBUG TEST_F(SdkTest, SdkTestOverquotaNonCloudraid) { LOG_info << "___TEST SdkTestOverquotaNonCloudraid"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); //for (int i = 0; i < 1000; ++i) { ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; // make a file to download, and upload so we can pull it down std::unique_ptr rootnode{megaApi[0]->getRootNode()}; deleteFile(UPFILE); ASSERT_TRUE(createFile(UPFILE, true)) << "Couldn't create " << UPFILE; MegaHandle uploadedNodeHandle = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNodeHandle, UPFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Upload transfer failed"; std::unique_ptr n1{megaApi[0]->getNodeByHandle(uploadedNodeHandle)}; ASSERT_NE(n1.get(), ((::mega::MegaNode *)NULL)); // set up to simulate 509 error DebugTestHook::isRaid = false; DebugTestHook::isRaidKnown = false; DebugTestHook::countdownToOverquota = 3; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPost509; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid; #endif // download - we should see a 30 second pause for 509 processing in the middle string filename2 = DOTSLASH + DOWNFILE; deleteFile(filename2); mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(n1.get(), filename2.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); // get to 30 sec pause point second_timer t; while (t.elapsed() < 30 && DebugTestHook::countdownToOverquota >= 0) { WaitMillisec(1000); } ASSERT_TRUE(DebugTestHook::isRaidKnown); ASSERT_FALSE(DebugTestHook::isRaid); // ok so now we should see no more http requests sent for 30 seconds. Test 20 for reliable testing int originalcount = DebugTestHook::countdownToOverquota; second_timer t2; while (t2.elapsed() < 20) { WaitMillisec(1000); } ASSERT_TRUE(DebugTestHook::countdownToOverquota == originalcount); // Now wait for the file to finish ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download transfer failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; ASSERT_LT(DebugTestHook::countdownToOverquota, 0); ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; //cout << "Passed round " << i; } } #endif /** * @brief TEST_F SdkTestOverquotaNonCloudraid * * use the hooks to simulate an overquota condition while running a raid download transfer, and check the handling * */ #ifdef DEBUG TEST_F(SdkTest, SdkTestOverquotaCloudraid) { LOG_info << "___TEST SdkTestOverquotaCloudraid"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, std::unique_ptr(megaApi[0]->getRootNode()).get()); std::unique_ptr nimported(megaApi[0]->getNodeByHandle(importHandle)); // set up to simulate 509 error DebugTestHook::isRaid = false; DebugTestHook::isRaidKnown = false; DebugTestHook::countdownToOverquota = 8; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPost509; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid; #endif // download - we should see a 30 second pause for 509 processing in the middle string filename2 = DOTSLASH + DOWNFILE; deleteFile(filename2); mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported.get(), filename2.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); // get to 30 sec pause point second_timer t; while (t.elapsed() < 30 && DebugTestHook::countdownToOverquota >= 0) { WaitMillisec(1000); } ASSERT_TRUE(DebugTestHook::isRaidKnown); ASSERT_TRUE(DebugTestHook::isRaid); // ok so now we should see no more http requests sent for 30 seconds. Test 20 for reliablilty int originalcount = DebugTestHook::countdownToOverquota; second_timer t2; while (t2.elapsed() < 20) { WaitMillisec(1000); } ASSERT_EQ(DebugTestHook::countdownToOverquota, originalcount); // Now wait for the file to finish ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600)) << "Download transfer failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")"; ASSERT_LT(DebugTestHook::countdownToOverquota, 0); ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } #endif struct CheckStreamedFile_MegaTransferListener : public MegaTransferListener { typedef ::mega::byte byte; std::atomic mFinished{false}; MegaApi* mApi; size_t reserved; size_t receiveBufPos; size_t file_start_offset; byte* receiveBuf; bool completedSuccessfully; bool completedUnsuccessfully; MegaError* completedUnsuccessfullyError; byte* compareDecryptedData; bool comparedEqual; m_off_t numFailedRequests{}; CheckStreamedFile_MegaTransferListener(MegaApi* const megaApi, const size_t receiveStartPoint, const size_t receiveSizeExpected, byte* const fileCompareData): mApi(megaApi), reserved(0), receiveBufPos(0), file_start_offset(0), receiveBuf(NULL), completedSuccessfully(false), completedUnsuccessfully(false), completedUnsuccessfullyError(NULL), compareDecryptedData(fileCompareData), comparedEqual(true) { file_start_offset = receiveStartPoint; reserved = receiveSizeExpected; receiveBuf = new byte[reserved]; compareDecryptedData = fileCompareData; } ~CheckStreamedFile_MegaTransferListener() { if (!mFinished) { assert(mApi); mApi->removeTransferListener(this); } delete[] receiveBuf; } void onTransferStart(MegaApi*, MegaTransfer*) override {} void onTransferFinish(MegaApi*, MegaTransfer*, MegaError* error) override { if (error && error->getErrorCode() != API_OK) { ((error->getErrorCode() == API_EARGS && reserved == 0) ? completedSuccessfully : completedUnsuccessfully) = true; completedUnsuccessfullyError = error->copy(); } else { if (compareDecryptedData && 0 != memcmp(receiveBuf, compareDecryptedData + file_start_offset, receiveBufPos)) comparedEqual = false; completedSuccessfully = true; } mFinished = true; } void onTransferUpdate(MegaApi*, MegaTransfer*) override {} void onTransferTemporaryError(MegaApi *api, MegaTransfer * /*transfer*/, MegaError* error) override { ++numFailedRequests; ostringstream msg; msg << "onTransferTemporaryError: " << (error ? error->getErrorString() : "NULL"); api->log(MegaApi::LOG_LEVEL_WARNING, msg.str().c_str()); } bool onTransferData(MegaApi*, MegaTransfer*, char* buffer, size_t size) override { assert(receiveBufPos + size <= reserved); memcpy(receiveBuf + receiveBufPos, buffer, size); receiveBufPos += size; if (compareDecryptedData && 0 != memcmp(receiveBuf, compareDecryptedData + file_start_offset, receiveBufPos)) comparedEqual = false; return true; } }; CheckStreamedFile_MegaTransferListener* StreamRaidFilePart(MegaApi* megaApi, m_off_t start, m_off_t end, bool raid, bool smallpieces, MegaNode* raidFileNode, MegaNode*nonRaidFileNode, ::mega::byte* filecomparedata) { assert((raid ? raidFileNode : nonRaidFileNode)); LOG_info << "stream test ---------------------------------------------------" << start << " to " << end << "(len " << end - start << ") " << (raid ? " RAID " : " non-raid ") << (raid ? (smallpieces ? " smallpieces " : "normalpieces") : ""); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onSetIsRaid = smallpieces ? &DebugTestHook::onSetIsRaid_smallchunks10 : NULL; #endif CheckStreamedFile_MegaTransferListener* p = new CheckStreamedFile_MegaTransferListener(megaApi, size_t(start), size_t(end - start), filecomparedata); megaApi->setStreamingMinimumRate(0); megaApi->startStreaming(raid ? raidFileNode : nonRaidFileNode, start, end - start, p); return p; } /** * @brief TEST_F SdkTestCloudraidStreamingSoakTest * * Stream random portions of the well-known file for 10 minutes, while randomly varying * raid / non-raid * front/end/middle (especial attention to first and last raidlines, and varying start/end within a raidline) * large piece / small piece * small raid chunk sizes (so small pieces of file don't just load in one request per connection) / normal sizes * */ TEST_F(SdkTest, SdkTestCloudraidStreamingSoakTest) { LOG_info << "___TEST SdkTestCloudraidStreamingSoakTest"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; #endif // ensure we have our standard raid test file auto importHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, std::unique_ptr{megaApi[0]->getRootNode()}.get()); MegaNode *nimported = megaApi[0]->getNodeByHandle(importHandle); MegaNode *rootnode = megaApi[0]->getRootNode(); // get the file, and upload as non-raid string filename2 = DOTSLASH + DOWNFILE; deleteFile(filename2); mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; megaApi[0]->startDownload(nimported, filename2.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD])) << "Setup transfer failed after " << maxTimeout << " seconds"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot download the initial file (error: " << mApi[0].lastError << ")"; char raidchar = 0; char nonraidchar = 'M'; string filename3 = filename2; incrementFilename(filename3); filename3 += ".neverseenbefore"; deleteFile(filename3); copyFile(filename2, filename3); { fstream fs(filename3.c_str(), ios::in | ios::out | ios::binary); raidchar = (char)fs.get(); fs.seekg(0); fs.put('M'); // we have to edit the file before upload, as Mega is too clever and will skip actual upload otherwise fs.flush(); } // actual upload MegaHandle uploadedNodeHandle = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNodeHandle, filename3.c_str(), rootnode, nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Upload transfer failed"; MegaNode *nonRaidNode = megaApi[0]->getNodeByHandle(uploadedNodeHandle); int64_t filesize = getFilesize(filename2); std::ifstream compareDecryptedFile(filename2.c_str(), ios::binary); std::vector<::mega::byte> compareDecryptedData(static_cast(filesize)); compareDecryptedFile.read((char*)compareDecryptedData.data(), filesize); m_time_t starttime = m_time(); int seconds_to_test_for = 60; //gRunningInCI ? 60 : 60 * 10; // ok loop for 10 minutes (one munite under jenkins) srand(unsigned(starttime)); int randomRunsDone = 0; m_off_t randomRunsBytes = 0; for (; m_time() - starttime < seconds_to_test_for; ++randomRunsDone) { int testtype = rand() % 10; int smallpieces = rand() % 2; int nonraid = rand() % 4 == 1; compareDecryptedData[0] = ::mega::byte(nonraid ? nonraidchar : raidchar); m_off_t start = RAIDLINE, end = RAIDLINE; if (testtype < 3) // front of file { start = std::max(RAIDLINE, rand() % 5 * 10240 - 1024); end = start + rand() % 5 * 10240; } else if (testtype == 3) // within 1, 2, or 3 raidlines { start = std::max(RAIDLINE, rand() % 5 * 10240 - 1024); end = start + rand() % (3 * RAIDLINE); } else if (testtype < 8) // end of file { end = std::min(32620740, 32620740 + RAIDLINE - rand() % (2 * RAIDLINE)); start = end - rand() % 5 * 10240; } else if (testtype == 8) // 0 size [seems this is not allowed at intermediate layer now - EARGS] { start = rand() % 32620740; end = start; } else // decent piece of the file { int pieceSize = 50000; //gRunningInCI ? 50000 : 5000000; start = std::max(RAIDLINE, rand() % pieceSize); int n = pieceSize / (smallpieces ? 100 : 1); end = start + n + rand() % n; } // seems 0 size not allowed now - make sure we get at least 1 byte if (start == end) { if (start > RAIDLINE) start -= 1; else end += 1; } randomRunsBytes += end - start; LOG_info << "beginning stream test, " << start << " to " << end << "(len " << end - start << ") " << (nonraid ? " non-raid " : " RAID ") << (!nonraid ? (smallpieces ? " smallpieces " : "normalpieces") : ""); megaApi[0]->setStreamingMinimumRate(0); CheckStreamedFile_MegaTransferListener* p = StreamRaidFilePart(megaApi[0].get(), start, end, !nonraid, smallpieces != 0, nimported, nonRaidNode, compareDecryptedData.data()); for (unsigned i = 0; p->comparedEqual; ++i) { WaitMillisec(100); if (p->completedUnsuccessfully) { ASSERT_FALSE(p->completedUnsuccessfully) << " on random run " << randomRunsDone << ", download failed: " << start << " to " << end << ", " << (nonraid?"nonraid":"raid") << ", " << (smallpieces?"small pieces":"normal size pieces") << ", reported error: " << (p->completedUnsuccessfullyError ? p->completedUnsuccessfullyError->getErrorCode() : 0) << " " << (p->completedUnsuccessfullyError ? p->completedUnsuccessfullyError->getErrorString() : "NULL"); break; } else if (p->completedSuccessfully) { break; } else if (i > maxTimeout * 10) { ASSERT_TRUE(i <= maxTimeout * 10) << "download took too long, more than " << maxTimeout << " seconds. Is the free transfer quota exhausted?"; break; } } ASSERT_TRUE(p->comparedEqual); delete p; } ASSERT_GT(randomRunsDone, 10 /*(gRunningInCI ? 10 : 100)*/ ); ostringstream msg; msg << "Streaming test downloaded " << randomRunsDone << " samples of the file from random places and sizes, " << randomRunsBytes << " bytes total"; megaApi[0]->log(MegaApi::LOG_LEVEL_DEBUG, msg.str().c_str()); delete nimported; delete nonRaidNode; delete rootnode; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; #endif } TEST_F(SdkTest, SdkTestStreamingRaidedTransferWithConnectionFailures) { LOG_info << "___TEST Streaming Raided Transfer With Connection Failures___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_NE(rootnode.get(), nullptr) << "Cannot retrieve RootNode"; auto importRaidHandle = importPublicLink(0, MegaClient::getMegaURL() + PUBLIC_IMAGE_URL, rootnode.get()); std::shared_ptr cloudRaidNode{megaApi[0]->getNodeByHandle(importRaidHandle)}; ASSERT_NE(rootnode.get(), nullptr) << "Cannot get CloudRaidNode node from public link"; megaApi[0]->setMaxDownloadSpeed(0); auto startStreaming = [cloudRaidNode, this](int cd404, int cd403, int cd429, int cd503, int cd200Err, m_off_t nFailedReqs, const int streamingMinimumRateBps = 0, const long long downloadLimitBps = -1, unsigned int transfer_timeout_in_seconds = 180) { ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPostError; globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks; globalMegaTestHooks.onLimitMaxReqSize = ::mega::DebugTestHook::onLimitMaxReqSize; globalMegaTestHooks.onHookNumberOfConnections = ::mega::DebugTestHook::onHookNumberOfConnections; #endif megaApi[0]->setStreamingMinimumRate(streamingMinimumRateBps); megaApi[0]->setMaxDownloadSpeed(downloadLimitBps); mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; DebugTestHook::countdownTo404 = cd404; DebugTestHook::countdownTo403 = cd403; DebugTestHook::countdownTo429 = cd429; DebugTestHook::countdownTo503 = cd503; DebugTestHook::countdownTo200WithErrCode = cd200Err; std::unique_ptr p( StreamRaidFilePart(megaApi[0].get(), RAIDLINE, cloudRaidNode->getSize(), true /*raid*/, false, cloudRaidNode.get(), nullptr, nullptr)); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], transfer_timeout_in_seconds)) << "Cloudraid download with 404 and 403 errors time out (180 seconds)"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot finish streaming download for the cloudraid file (error: " << mApi[0].lastError << ")"; ASSERT_TRUE(cd404 < 0 || DebugTestHook::countdownTo404 < 0) << ""; ASSERT_TRUE(cd403 < 0 || DebugTestHook::countdownTo403 < 0) << ""; ASSERT_TRUE(cd429 < 0 || DebugTestHook::countdownTo404 < 0) << ""; ASSERT_EQ(p->numFailedRequests, nFailedReqs) << "Unexpected number of retries for streaming download"; }; LOG_debug << "#### Test1: Streaming Download, no forced errors. No transfer retry ####"; startStreaming(-1 /*cd404*/, -1 /*cd403*/, -1 /*cd429*/, -1 /*cd503*/, -1 /*cd200Err*/, 0 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test2: Streaming Download, forcing 1 Raided Part Failure (404). No transfer " "retry ####"; startStreaming(2 /*cd404*/, -1 /*cd403*/, -1 /*cd429*/, -1 /*cd503*/, -1 /*cd200Err*/, 0 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test3: Streaming Download forcing 2 Raided Parts Failures(403 | 503)." "Transfer will be retried (onTransferTemporaryError received) ####"; startStreaming(-1 /*cd404*/, 2 /*cd403*/, -1 /*cd429*/, 2 /*cd503*/, -1 /*cd200Err*/, 1 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test4: Streaming Download limiting min streaming rate and max download " "speed, no forced errors. No transfer retry ####"; startStreaming(-1 /*cd404*/, -1 /*cd403*/, -1 /*cd429*/, -1 /*cd503*/, -1 /*cd200Err*/, 0 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test5: Streaming Download limiting min streaming rate and max download " "speed, forcing 1 Raided Part Failure (429). No transfer retry ####"; startStreaming(-1 /*cd404*/, -1 /*cd403*/, 2 /*cd429*/, -1 /*cd503*/, -1 /*cd200Err*/, 0 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test6: Streaming Download limiting min streaming rate and max download " "speed, forcing 2 Raided Parts Failures (403 | 503). Transfer will be retried " "(onTransferTemporaryError received) ####"; startStreaming(-1 /*cd404*/, 2 /*cd403*/, -1 /*cd429*/, 2 /*cd503*/, -1 /*cd200Err*/, 1 /*nFailedReqs*/, 30000 /*streamingMinimumRateBps*/, 300000 /*downloadLimitBps*/, 180 /*timeout*/); LOG_debug << "#### Test7: Streaming Download, forcing 1 Raided Part Failure (200 with " "ErrCode). No transfer " "retry ####"; startStreaming(-1 /*cd404*/, -1 /*cd403*/, -1 /*cd429*/, -1 /*cd503*/, 2 /*cd200Err*/, 0 /*nFailedReqs*/, 0 /*streamingMinimumRateBps*/, -1 /*downloadLimitBps*/, 180 /*timeout*/); LOG_info << "___TEST Streaming Raided Transfer With Connection Failures. Tests cases completed___"; ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } TEST_F(SdkTest, SdkTestStreamingRaidedTransferBestCase) { LOG_info << "___TEST Streaming Raided Transfer Best Case___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_NE(rootnode.get(), nullptr) << "Cannot retrieve RootNode"; // https://mega.app/file/JzckQJ6L#X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA std::string url100MB = "/#!JzckQJ6L!X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA"; auto importRaidHandle = importPublicLink(0, MegaClient::getMegaURL() + url100MB, rootnode.get()); std::shared_ptr cloudRaidNode{megaApi[0]->getNodeByHandle(importRaidHandle)}; ASSERT_NE(rootnode.get(), nullptr) << "Cannot get CloudRaidNode node from public link"; ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false; std::unique_ptr p( StreamRaidFilePart(megaApi[0].get(), 0, cloudRaidNode->getSize(), true /*raid*/, false, cloudRaidNode.get(), nullptr, nullptr)); ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with 404 and 403 errors time out (180 seconds)"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot finish streaming download for the cloudraid file (error: " << mApi[0].lastError << ")"; LOG_info << "___TEST Streaming Raided Transfer Best Case. Tests cases completed___"; ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode"; } /** * @brief SdkTestStreaming * * Verifies on-demand file streaming: * - Create a 50MB local file with random content * - Read three 1MB slices (head/middle/tail) from disk * - Upload the file * - Stream the same slices from the backend and assert byte-for-byte equality */ TEST_F(SdkTest, SdkTestStreaming) { LOG_info << "___TEST SdkTestStreaming___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); static const size_t kFileSize = 50ull * 1024 * 1024; // 50 MB static const size_t kChunkSize = 1ull * 1024 * 1024; // 1 MB static const size_t kMidOffset = 20ull * 1024 * 1024; // 20 MB static const size_t kTailOffset = kFileSize - kChunkSize; // last 1MB safely within file // Create local random file fs::path filePath = fs::current_path() / PUBLICFILE; sdk_test::LocalTempFile localFile(filePath, kFileSize); auto fsAccess = std::make_unique(); LocalPath localPath = fspathToLocal(filePath); // Read three slices from disk std::unique_ptr fa(fsAccess->newfileaccess(false)); ASSERT_TRUE(fa->fopen(localPath, OPEN_RDONLY, FSLogging::logOnError)) << "Failed to open local file"; std::string bufHead; bufHead.reserve(kChunkSize); std::string bufMid; bufMid.reserve(kChunkSize); std::string bufTail; bufTail.reserve(kChunkSize); ASSERT_TRUE(fa->fread(&bufHead, kChunkSize, 0, 0, FSLogging::logOnError)) << "Read head slice failed"; ASSERT_TRUE(fa->fread(&bufMid, kChunkSize, 0, kMidOffset, FSLogging::logOnError)) << "Read middle slice failed"; ASSERT_TRUE(fa->fread(&bufTail, kChunkSize, 0, kTailOffset, FSLogging::logOnError)) << "Read tail slice failed"; // Upload File std::unique_ptr rootnode(megaApi[0]->getRootNode()); MegaHandle uploadedNode{UNDEF}; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode, path_u8string(filePath).c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr node(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_TRUE(!!node) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; auto streamNode = [&node, this](const std::string& expected, int64_t start, int64_t size) { int err{API_OK}; std::string receivedBuffer; receivedBuffer.reserve(kChunkSize); testing::NiceMock mtl{megaApi[0].get()}; EXPECT_CALL(mtl, onTransferData) .WillRepeatedly( [&receivedBuffer](MegaApi*, MegaTransfer*, char* buffer, size_t size) { receivedBuffer.append(buffer, size); return true; }); EXPECT_CALL(mtl, onTransferFinish) .WillOnce( [&mtl, &err](MegaApi*, MegaTransfer*, MegaError* error) { err = error ? error->getErrorCode() : API_EINTERNAL; mtl.markAsFinished(); }); megaApi[0]->startStreaming(node.get(), start, size, &mtl); if (!mtl.waitForFinishOrTimeout(180s /*timeout*/)) { LOG_err << "TimeOut streaming slice"; return false; } EXPECT_EQ(receivedBuffer.size(), expected.size()); bool equalContain = receivedBuffer == expected; EXPECT_TRUE(equalContain) << "Initial Position: " << start; return err == API_OK && equalContain; }; ASSERT_TRUE(streamNode(bufHead, 0, static_cast(kChunkSize))); ASSERT_TRUE( streamNode(bufMid, static_cast(kMidOffset), static_cast(kChunkSize))); ASSERT_TRUE( streamNode(bufTail, static_cast(kTailOffset), static_cast(kChunkSize))); } #if !USE_FREEIMAGE TEST_F(SdkTest, DISABLED_SdkHttpReqCommandPutFATest) #else TEST_F(SdkTest, SdkHttpReqCommandPutFATest) #endif { LOG_info << "___TEST SdkHttpReqCommandPutFATest___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); // SCENARIO 1: Upload image file and check thumbnail and preview std::unique_ptr rootnode(megaApi[0]->getRootNode()); MegaHandle uploadResultHandle = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadResultHandle, IMAGEFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; std::unique_ptr n1(megaApi[0]->getNodeByHandle(uploadResultHandle)); ASSERT_NE(n1, nullptr); ASSERT_STREQ(IMAGEFILE.c_str(), n1->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; // Get the thumbnail of the uploaded image std::string thumbnailPath = THUMBNAIL; ASSERT_EQ(API_OK, doGetThumbnail(0, n1.get(), thumbnailPath.c_str())); // Get the preview of the uploaded image std::string previewPath = PREVIEW; ASSERT_EQ(API_OK, doGetPreview(0, n1.get(), previewPath.c_str())); // SCENARIO 2: Request FA upload URLs (thumbnail and preview) int64_t fileSize_thumbnail = 2295; int64_t fileSize_preview = 2376; // Request a thumbnail upload URL std::string thumbnailURL; ASSERT_EQ(API_OK, doGetThumbnailUploadURL(0, thumbnailURL, n1->getHandle(), fileSize_thumbnail, true)) << "Cannot request thumbnail upload URL"; ASSERT_FALSE(thumbnailURL.empty()) << "Got empty thumbnail upload URL"; // Request a preview upload URL std::string previewURL; ASSERT_EQ(API_OK, doGetPreviewUploadURL(0, previewURL, n1->getHandle(), fileSize_preview, true)) << "Cannot request preview upload URL"; ASSERT_FALSE(previewURL.empty()) << "Got empty preview upload URL"; } TEST_F(SdkTest, SdkMediaImageUploadTest) { LOG_info << "___TEST MediaUploadRequestURL___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); unsigned int apiIndex = 0; int64_t fileSize = 1304; const char* outputImage = "newlogo.png"; synchronousMediaUpload(apiIndex, fileSize, IMAGEFILE.c_str(), IMAGEFILE_C.c_str(), outputImage #if USE_FREEIMAGE ,THUMBNAIL.c_str(), PREVIEW.c_str() #endif ); } TEST_F(SdkTest, SdkMediaUploadTest) { LOG_info << "___TEST MediaUploadRequestURL___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); unsigned int apiIndex = 0; int64_t fileSize = 10000; string filename = UPFILE; ASSERT_TRUE(createFile(filename, false)) << "Couldnt create " << filename; const char* outputFile = "newfile.txt"; synchronousMediaUpload(apiIndex, fileSize, filename.c_str(), DOWNFILE.c_str(), outputFile); } TEST_F(SdkTest, SdkGetPricing) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST GetPricing___"; auto err = synchronousGetPricing(0); ASSERT_TRUE(err == API_OK) << "Get pricing failed (error: " << err << ")"; ASSERT_TRUE(strcmp(mApi[0].mMegaCurrency->getCurrencyName(), "EUR") == 0) << "Unexpected currency"; ASSERT_STREQ(mApi[0].mMegaCurrency->getLocalCurrencyName(), "") << "Local currency was not expected"; ASSERT_GT(mApi[0].mMegaPricing->getNumProducts(), 0) << "No products available"; for (int i = 0; i < mApi[0].mMegaPricing->getNumProducts(); ++i) { ASSERT_TRUE(mApi[0].mMegaPricing->getDescription(i)) << "Product description is empty"; ASSERT_GT(mApi[0].mMegaPricing->getTestCategory(i), 0) << "Invalid value for test category in product \"" << mApi[0].mMegaPricing->getDescription(i) << "\""; } // Foce local currency to USD. err = synchronousGetPricing(0, "US"); ASSERT_TRUE(err == API_OK) << "Get pricing in USD failed (error: " << err << ")"; ASSERT_STREQ(mApi[0].mMegaCurrency->getLocalCurrencyName(), "USD") << "No USD local currency found."; } TEST_F(SdkTest, SdkGetBanners) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST GetBanners___"; auto rt = asyncGetBanners(0); auto err = rt->waitForResult(); ASSERT_THAT(err, testing::AnyOf(API_OK, API_ENOENT)) << "Get banners failed (error: " << err << ")"; if (err != API_OK) { return; } auto banners = rt->request->getMegaBannerList(); ASSERT_TRUE(banners); EXPECT_GT(banners->size(), 0); for (int n = 0; n < banners->size(); ++n) { auto banner = banners->get(n); EXPECT_GT(banner->getId(), 0); EXPECT_NE(banner->getTitle(), nullptr); EXPECT_NE(banner->getDescription(), nullptr); EXPECT_NE(banner->getImage(), nullptr); EXPECT_NE(banner->getUrl(), nullptr); EXPECT_NE(banner->getBackgroundImage(), nullptr); EXPECT_NE(banner->getImageLocation(), nullptr); EXPECT_GE(banner->getVariant(), 0); EXPECT_NE(banner->getButton(), nullptr); } } TEST_F(SdkTest, SdkSimpleCommands) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SimpleCommands___"; // fetchTimeZone() test auto err = synchronousFetchTimeZone(0); ASSERT_EQ(API_OK, err) << "Fetch time zone failed (error: " << err << ")"; ASSERT_TRUE(mApi[0].tzDetails && mApi[0].tzDetails->getNumTimeZones()) << "Invalid Time Zone details"; // some simple validation // getABTestValue() -- logged in. ASSERT_GE(megaApi[0]->getABTestValue("devtest"), 1u); ASSERT_EQ(megaApi[0]->getABTestValue("devtest_inexistent_flag"), 0u); // getFlag() std::unique_ptr flagAB{ megaApi[0]->getFlag("devtest") }; ASSERT_EQ(flagAB->getType(), static_castgetType())>(MegaFlag::FLAG_TYPE_AB_TEST)); ASSERT_GE(flagAB->getGroup(), 1u); std::unique_ptr flagF{ megaApi[0]->getFlag("dmca") }; ASSERT_THAT( flagF->getType(), ::testing::AnyOf(static_castgetType())>(MegaFlag::FLAG_TYPE_AB_TEST), static_castgetType())>(MegaFlag::FLAG_TYPE_FEATURE))); ASSERT_GE(flagF->getGroup(), 1u); logout(0, false, maxTimeout); gSessionIDs[0] = "invalid"; // getMiscFlags() -- not logged in err = synchronousGetMiscFlags(0); ASSERT_EQ(API_OK, err) << "Get misc flags failed (error: " << err << ")"; // getABTestValue() -- not logged in ASSERT_EQ(megaApi[0]->getABTestValue("devtest"), 0u); // getUserEmail() test ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr user(megaApi[0]->getMyUser()); ASSERT_TRUE(!!user); // some simple validation err = synchronousGetUserEmail(0, user->getHandle()); ASSERT_EQ(API_OK, err) << "Get user email failed (error: " << err << ")"; ASSERT_NE(mApi[0].email.find('@'), std::string::npos); // some simple validation // cleanRubbishBin() test (accept both success and already empty statuses) err = synchronousCleanRubbishBin(0); ASSERT_TRUE(err == API_OK || err == API_ENOENT) << "Clean rubbish bin failed (error: " << err << ")"; // getMiscFlags() -- not logged in logout(0, false, maxTimeout); gSessionIDs[0] = "invalid"; err = synchronousGetMiscFlags(0); ASSERT_EQ(API_OK, err) << "Get misc flags failed (error: " << err << ")"; auto validateString = [](const char* value) { ASSERT_NE(value, nullptr); ASSERT_NE(*value, '\0'); }; RequestTracker listener{megaApi[0].get()}; megaApi[0]->getMyIp(&listener); ASSERT_EQ(listener.waitForResult(), API_OK); ASSERT_NO_FATAL_FAILURE(validateString(listener.request->getName())); // Country code ASSERT_NO_FATAL_FAILURE(validateString(listener.request->getText())); // IP address} } TEST_F(SdkTest, SdkHeartbeatCommands) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST HeartbeatCommands___"; std::vector> backupNameToBackupId; // setbackup test fs::path localtestroot = makeNewTestRoot(); string localFolder = localtestroot.string(); std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; int backupType = BackupType::CAMERA_UPLOAD; int state = 1; int subState = 3; size_t numBackups = 3; vector backupNames {"/SdkBackupNamesTest1", "/SdkBackupNamesTest2", "/SdkBackupNamesTest3" }; vector folderNames {"CommandBackupPutTest1", "CommandBackupPutTest2", "CommandBackupPutTest3" }; vector targetNodes; // create remote folders for each backup for (size_t i = 0; i < numBackups; i++) { auto h = createFolder(0, folderNames[i].c_str(), rootnode.get()); ASSERT_NE(h, UNDEF); targetNodes.push_back(h); } // set all backups, only wait for completion of the third one size_t lastIndex = numBackups - 1; for (size_t i = 0; i < lastIndex; i++) { megaApi[0]->setBackup(backupType, targetNodes[i], localFolder.c_str(), backupNames[i].c_str(), state, subState, new OneShotListener([&](MegaError& e, MegaRequest& r) { if (e.getErrorCode() == API_OK) { backupNameToBackupId.emplace_back(r.getName(), r.getParentHandle()); } })); } auto err = synchronousSetBackup(0, [&](MegaError& e, MegaRequest& r) { if (e.getErrorCode() == API_OK) { backupNameToBackupId.emplace_back(r.getName(), r.getParentHandle()); } }, backupType, targetNodes[lastIndex], localFolder.c_str(), backupNames[lastIndex].c_str(), state, subState); ASSERT_EQ(API_OK, err) << "setBackup failed (error: " << err << ")"; ASSERT_EQ(backupNameToBackupId.size(), numBackups) << "setBackup didn't register all the backups"; // update backup err = synchronousUpdateBackup(0, mApi[0].getBackupId(), MegaApi::BACKUP_TYPE_INVALID, UNDEF, nullptr, nullptr, -1, -1); ASSERT_EQ(API_OK, err) << "updateBackup failed (error: " << err << ")"; // now remove all backups, only wait for completion of the third one // (automatically updates the user's attribute, removing the entry for the backup id) for (size_t i = 0; i < lastIndex; i++) { megaApi[0]->removeBackup(backupNameToBackupId[i].second); } synchronousRemoveBackup(0, backupNameToBackupId[lastIndex].second); // add a backup again err = synchronousSetBackup(0, [&](MegaError& e, MegaRequest& r) { if (e.getErrorCode() == API_OK) backupNameToBackupId.emplace_back(r.getName(), r.getParentHandle()); }, backupType, targetNodes[0], localFolder.c_str(), backupNames[0].c_str(), state, subState); ASSERT_EQ(API_OK, err) << "setBackup failed (error: " << err << ")"; // check heartbeat err = synchronousSendBackupHeartbeat(0, mApi[0].getBackupId(), 1, 10, 1, 1, 0, targetNodes[0]); ASSERT_EQ(API_OK, err) << "sendBackupHeartbeat failed (error: " << err << ")"; // --- negative test cases --- // register the same backup twice: should work fine err = synchronousSetBackup(0, [&](MegaError& e, MegaRequest& r) { if (e.getErrorCode() == API_OK) backupNameToBackupId.emplace_back(r.getName(), r.getParentHandle()); }, backupType, targetNodes[0], localFolder.c_str(), backupNames[0].c_str(), state, subState); ASSERT_EQ(API_OK, err) << "setBackup failed (error: " << err << ")"; // update a removed backup: should throw an error err = synchronousRemoveBackup(0, mApi[0].getBackupId(), nullptr); ASSERT_EQ(API_OK, err) << "removeBackup failed (error: " << err << ")"; err = synchronousUpdateBackup(0, mApi[0].getBackupId(), BackupType::INVALID, UNDEF, nullptr, nullptr, -1, -1); ASSERT_EQ(API_OK, err) << "updateBackup for deleted backup should succeed now, and revive the record. But, error: " << err; // We can't test this, as reviewer wants an assert to fire for EARGS //// create a backup with a big status: should report an error //err = synchronousSetBackup(0, // nullptr, // backupType, targetNodes[0], localFolder.c_str(), backupNames[0].c_str(), 255/*state*/, subState); //ASSERT_NE(API_OK, err) << "setBackup failed (error: " << err << ")"; } TEST_F(SdkTest, SdkFavouriteNodes) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SDKFavourites___"; unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); auto nh = createFolder(0, "folder-A", rootnodeA.get()); ASSERT_NE(nh, UNDEF); unique_ptr folderA(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(!!folderA); std::string subFolder = "sub-folder-A"; nh = createFolder(0, subFolder.c_str(), folderA.get()); ASSERT_NE(nh, UNDEF); unique_ptr subFolderA(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(!!subFolderA); string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1, false)) << "Couldn't create " << filename1; MegaHandle h = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &h, filename1.c_str(), subFolderA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr n1(megaApi[0]->getNodeByHandle(h)); bool null_pointer = (n1.get() == nullptr); ASSERT_FALSE(null_pointer) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; auto err = synchronousSetNodeFavourite(0, subFolderA.get(), true); err = synchronousSetNodeFavourite(0, n1.get(), true); err = synchronousGetFavourites(0, subFolderA.get(), 0); ASSERT_EQ(API_OK, err) << "synchronousGetFavourites (error: " << err << ")"; ASSERT_EQ(mApi[0].getFavNodeCount(), 2u) << "synchronousGetFavourites failed..."; err = synchronousGetFavourites(0, nullptr, 1); ASSERT_EQ(mApi[0].getFavNodeCount(), 1u) << "synchronousGetFavourites failed..."; unique_ptr favNode(megaApi[0]->getNodeByHandle(mApi[0].getFavNode(0))); ASSERT_EQ(favNode->getName(), subFolder) << "synchronousGetFavourites failed with node passed nullptr"; LOG_debug << "\t# Set versioned node as favourite"; std::set tmpFileNames = {"n1", "n2", "n3", "n4"}; MegaHandle fileHandle = INVALID_HANDLE; std::for_each(std::begin(tmpFileNames), std::end(tmpFileNames), [this, &fileHandle, folder = std::unique_ptr(megaApi[0]->getRootNode())] (const auto& localFileName) { static int vNum = 1; createFile(localFileName, false, std::to_string(vNum++)); const auto prevHandle = fileHandle; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, localFileName.c_str(), folder.get(), "versionedFileName.txt", ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload test file version #" << vNum; if (prevHandle == INVALID_HANDLE) { ASSERT_NE(fileHandle, INVALID_HANDLE) << "Invalid handle retrieved for newly uploaded file"; } else { ASSERT_NE(fileHandle, prevHandle) << "Already existing handle received"; } deleteFile(localFileName); }); unique_ptr versionedFileNode(megaApi[0]->getNodeByHandle(fileHandle)); ASSERT_TRUE(versionedFileNode); unique_ptr allVersions(megaApi[0]->getVersions(versionedFileNode.get())); ASSERT_EQ(allVersions->size(), tmpFileNames.size()); ASSERT_EQ(MegaError::API_OK, synchronousSetNodeFavourite(0, versionedFileNode.get(), true)) << "Setting favourite attribute for versioned file failed"; const int howMany = 0; // all nodes ASSERT_EQ(MegaError::API_OK, synchronousGetFavourites(0, nullptr /*from Root*/, howMany)); ASSERT_EQ(mApi[0].getFavNodeCount(), 3u) << "Error counting new versioned node set as favourite"; } // tests for Sensntive files flag on files and folders // includes tests of MegaApi::search() with filters TEST_F(SdkTest, SdkSensitiveNodes) { LOG_info << "___TEST SDKSensitive___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); // / // folder-A/ // top shared // abFile1.png // acSensitiveFile.png <- sensitive // sub-folder-A/ <- sensitive // aaLogo.png string folderAName = "folder-A"; MegaHandle nh = createFolder(0, folderAName.c_str(), rootnodeA.get()); ASSERT_NE(nh, UNDEF); unique_ptr folderA(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(!!folderA); string subFolderAName = "sub-folder-A"; MegaHandle snh = createFolder(0, subFolderAName.c_str(), folderA.get()); ASSERT_NE(snh, UNDEF); unique_ptr subFolderA(megaApi[0]->getNodeByHandle(snh)); ASSERT_TRUE(!!subFolderA); // all 3 files have "a" in the name string filename1 = "aaLogo.png"; MegaHandle fh = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fh, IMAGEFILE.c_str(), subFolderA.get(), filename1.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr thefile(megaApi[0]->getNodeByHandle(fh)); bool null_pointer = (thefile.get() == nullptr); ASSERT_FALSE(null_pointer) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")"; string nsfilename = "abFile1.png"; MegaHandle fh2 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fh2, IMAGEFILE.c_str(), folderA.get(), nsfilename.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr nsfile(megaApi[0]->getNodeByHandle(fh2)); string sfilename = "acSensitiveFile.png"; MegaHandle fh3 = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fh3, IMAGEFILE.c_str(), folderA.get(), sfilename.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr sfile(megaApi[0]->getNodeByHandle(fh3)); // setuip sharing from ASSERT_EQ(API_OK, synchronousInviteContact(0, mApi[1].email.c_str(), "SdkSensitiveNodes contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(WaitFor([this]() {return unique_ptr(megaApi[1]->getIncomingContactRequests())->size() == 1; }, 60000)); ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); ASSERT_EQ(API_OK, synchronousReplyContactRequest(1, mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); // Verify credentials in both accounts if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } ASSERT_EQ(unsigned(unique_ptr(megaApi[1]->getInSharesList())->size()), 0u); unique_ptr user1(megaApi[1]->getContact(mApi[0].email.c_str())); { unique_ptr nl2(megaApi[1]->getInShares(user1.get())); ASSERT_EQ(nl2->size(), 0); // should be no shares } ASSERT_NO_FATAL_FAILURE(shareFolder(folderA.get(), mApi[1].email.c_str(), MegaShare::ACCESS_READ)); ASSERT_TRUE(WaitFor([this]() { return unique_ptr(megaApi[1]->getInSharesList())->size() == 1; }, 60*1000)); ASSERT_EQ(unsigned(unique_ptr(megaApi[1]->getInSharesList())->size()), 1u); // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &folderA]() { return unique_ptr(megaApi[1]->getNodeByHandle(folderA->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); unique_ptr user(megaApi[1]->getContact(mApi[0].email.c_str())); unique_ptr nl1(megaApi[1]->getInShares(user.get())); synchronousSetNodeSensitive(0, sfile.get(), true); synchronousSetNodeSensitive(0, subFolderA.get(), true); function anyShares = [&]() { unique_ptr nl2(megaApi[1]->getInShares(user1.get())); return nl2->size() != 0; }; ASSERT_TRUE(WaitFor(anyShares, 30 * 1000)); // 30 sec unique_ptr nl2(megaApi[1]->getInShares(user1.get())); ASSERT_EQ(nl2->size(), 1); ASSERT_EQ(nl2->get(0)->isMarkedSensitive(), false); function sharedSubFolderSensitive = [&]() { unique_ptr sharedSubFolderA(megaApi[1]->getNodeByPath(subFolderAName.c_str(), nl2->get(0))); if (!sharedSubFolderA.get()) return false; return sharedSubFolderA->isMarkedSensitive(); }; ASSERT_TRUE(WaitFor(sharedSubFolderSensitive, 60 * 1000)); // share has gained attributes unique_ptr sharedSubFolderA(megaApi[1]->getNodeByPath(subFolderAName.c_str(), nl2->get(0))); ASSERT_TRUE(sharedSubFolderA) << "Share " << nl2->get(0)->getName() << '/' << subFolderAName << " not found"; ASSERT_EQ(sharedSubFolderA->isMarkedSensitive(), true) << "Share " << nl2->get(0)->getName() << '/' << subFolderAName << " found but not sensitive"; // --------------------------------------------------------------------------------------------------------------------------- subFolderA.reset(megaApi[0]->getNodeByPath((string("/") + folderAName + "/" + subFolderAName).c_str(), unique_ptr(megaApi[0]->getRootNode()).get())); ASSERT_TRUE(!!subFolderA); ASSERT_TRUE(subFolderA->isMarkedSensitive()); bool msen = subFolderA->isMarkedSensitive(); ASSERT_EQ(msen, true); bool sen = megaApi[0]->isSensitiveInherited(subFolderA.get()); ASSERT_EQ(sen, true); sen = megaApi[0]->isSensitiveInherited(thefile.get()); ASSERT_EQ(sen, true); sen = megaApi[0]->isSensitiveInherited(sfile.get()); ASSERT_EQ(sen, true); sen = megaApi[0]->isSensitiveInherited(nsfile.get()); ASSERT_EQ(sen, false); sen = megaApi[0]->isSensitiveInherited(folderA.get()); ASSERT_EQ(sen, false); sen = megaApi[0]->isSensitiveInherited(rootnodeA.get()); ASSERT_EQ(sen, false); // inherited sensitive flag // specifeid searh string std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName("logo"); filterResults->byLocationHandle(rootnodeA->getHandle()); std::unique_ptr list(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("logo"); filterResults->byLocationHandle(rootnodeA->getHandle()); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 0); // inherited sensitive flag // no specifeid searh string filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(rootnodeA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 3); ASSERT_EQ(list->get(0)->getName(), filename1); ASSERT_EQ(list->get(1)->getName(), nsfilename); ASSERT_EQ(list->get(2)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(rootnodeA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); ASSERT_EQ(list->get(0)->getName(), nsfilename); // no node, specifeid searh string: SEARCH_TARGET_ALL: getNodesByMimeType() filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 3); ASSERT_EQ(list->get(0)->getName(), filename1); ASSERT_EQ(list->get(1)->getName(), nsfilename); ASSERT_EQ(list->get(2)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); // non sensitive files (recursive exclude) ASSERT_EQ(list->get(0)->getName(), nsfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->byCategory(MegaApi::FILE_TYPE_AUDIO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 0); // no node, specifid search string: SEARCH_TARGET_ROOTNODE filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 3); ASSERT_EQ(list->get(0)->getName(), filename1); ASSERT_EQ(list->get(1)->getName(), nsfilename); ASSERT_EQ(list->get(2)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); // non sensitive files (recursive exclude) ASSERT_EQ(list->get(0)->getName(), nsfilename); // no node, specified search string: SEARCH_TARGET_ALL main non recursive // folderA filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocationHandle(folderA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 2); ASSERT_EQ(list->get(0)->getName(), nsfilename); ASSERT_EQ(list->get(1)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocationHandle(folderA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); // non sensitive files (recursive exclude) ASSERT_EQ(list->get(0)->getName(), nsfilename); // no node, specified search string: main non recursive // subfolderA filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocationHandle(subFolderA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); ASSERT_EQ(list->get(0)->getName(), filename1); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocationHandle(subFolderA->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 0); // non sensitive files (recursive exclude) // no node, specifid search string: SEARCH_TARGET_INSHARE filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[1]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 3); ASSERT_EQ(list->get(0)->getName(), filename1); ASSERT_EQ(list->get(1)->getName(), nsfilename); ASSERT_EQ(list->get(2)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[1]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); // non sensitive files (recursive exclude) ASSERT_EQ(list->get(0)->getName(), nsfilename); // no node, specifid search string: SEARCH_TARGET_OUTSHARE filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 3); ASSERT_EQ(list->get(0)->getName(), filename1); ASSERT_EQ(list->get(1)->getName(), nsfilename); ASSERT_EQ(list->get(2)->getName(), sfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("a"); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); // non sensitive files (recursive exclude) ASSERT_EQ(list->get(0)->getName(), nsfilename); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byCategory(MegaApi::FILE_TYPE_OTHERS); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 0); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); filterResults->byLocationHandle(subFolderA->getHandle()); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 0); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); filterResults->byLocationHandle(folderA->getHandle()); list.reset(megaApi[0]->getChildren(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 2); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocation(MegaApi::SEARCH_TARGET_ROOTNODE); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_FALSE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 2); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(folderA->getHandle()); filterResults->bySensitivity(MegaSearchFilter::BOOL_FILTER_ONLY_TRUE); list.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(list->size(), 1); } TEST_F(SdkTest, SdkDeviceNames) { /// Run this before other tests that use device name, like SdkBackupFolder ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkDeviceNames___"; // test setter/getter for current device name string deviceName = string("SdkDeviceNamesTest_") + getCurrentTimestamp(true); ASSERT_EQ(API_OK, doSetDeviceName(0, nullptr, deviceName.c_str())) << "setDeviceName failed"; RequestTracker getDeviceNameTracker1(megaApi[0].get()); megaApi[0]->getDeviceName(nullptr, &getDeviceNameTracker1); ASSERT_EQ(getDeviceNameTracker1.waitForResult(), API_OK); ASSERT_TRUE(getDeviceNameTracker1.request->getName()); ASSERT_EQ(deviceName, getDeviceNameTracker1.request->getName()); ASSERT_TRUE(getDeviceNameTracker1.request->getMegaStringMap()); // test getting current device name when it was not set ASSERT_EQ(API_OK, doSetDeviceName(0, nullptr, "")) << "removing current device name failed"; RequestTracker getDeviceNameTracker2(megaApi[0].get()); megaApi[0]->getDeviceName(nullptr, &getDeviceNameTracker2); ASSERT_EQ(getDeviceNameTracker2.waitForResult(), API_ENOENT); ASSERT_FALSE(getDeviceNameTracker2.request->getName()); ASSERT_TRUE(getDeviceNameTracker2.request->getMegaStringMap()); // test getting all device names, when current device name was not set RequestTracker noNameTracker(megaApi[0].get()); megaApi[0]->getUserAttribute(MegaApi::USER_ATTR_DEVICE_NAMES, &noNameTracker); ASSERT_EQ(API_OK, noNameTracker.waitForResult()) << "getUserAttribute failed when name of current device was not set"; ASSERT_FALSE(noNameTracker.request->getName()) << "getUserAttribute set some bogus name for current device"; ASSERT_TRUE(noNameTracker.request->getMegaStringMap()); // test getting all device names, when current device name was set ASSERT_EQ(API_OK, doSetDeviceName(0, nullptr, deviceName.c_str())) << "setDeviceName failed"; RequestTracker getDeviceNameTracker3(megaApi[0].get()); megaApi[0]->getUserAttribute(MegaApi::USER_ATTR_DEVICE_NAMES, &getDeviceNameTracker3); ASSERT_EQ(API_OK, getDeviceNameTracker3.waitForResult()); ASSERT_FALSE(getDeviceNameTracker3.request->getName()); ASSERT_TRUE(getDeviceNameTracker3.request->getMegaStringMap()); } TEST_F(SdkTest, SdkBackupFolder) { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED MrProper defer( [] { globalMegaTestHooks.onHookDeviceId = nullptr; }); // set a fixed device id for testing globalMegaTestHooks.onHookDeviceId = [](std::string& deviceId) { const auto [email, _] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty()); deviceId = "TEST_DEVICE_ID_" + email; }; #endif ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST BackupFolder___"; // get timestamp string timestamp = getCurrentTimestamp(true); #ifdef ENABLE_SYNC // Make sure My Backups folder was created ASSERT_NO_FATAL_FAILURE(syncTestEnsureMyBackupsRemoteFolderExists(0)); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED MegaHandle mh = mApi[0].lastSyncBackupId; #endif // Create a test root directory fs::path localBasePath = makeNewTestRoot(); ASSERT_NO_FATAL_FAILURE(cleanUp(megaApi[0].get(), localBasePath)); // request to backup a folder fs::path localFolderPath = localBasePath / "LocalBackedUpFolder"; fs::create_directories(localFolderPath); const auto testFile = localFolderPath / UPFILE; ASSERT_TRUE(createFile(testFile.string(), false)) << "Failed to create file " << testFile.string(); const string backupNameStr = string("RemoteBackupFolder_") + timestamp; const char* backupName = backupNameStr.c_str(); MegaHandle newSyncRootNodeHandle = UNDEF; int err = synchronousSyncFolder(0, &newSyncRootNodeHandle, MegaSync::TYPE_BACKUP, path_u8string(localFolderPath).c_str(), backupName, INVALID_HANDLE, nullptr); ASSERT_TRUE(err == API_OK) << "Backup folder failed (error: " << err << ")"; handle bkpId = mApi[0].lastSyncBackupId; // verify node attribute std::unique_ptr backupNode(megaApi[0]->getNodeByHandle(newSyncRootNodeHandle)); const char* deviceIdFromNode = backupNode->getDeviceId(); ASSERT_TRUE(!deviceIdFromNode || !*deviceIdFromNode); unique_ptr actualRemotePath{megaApi[0]->getNodePathByNodeHandle(newSyncRootNodeHandle)}; ASSERT_TRUE(actualRemotePath); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED // verify remote path std::unique_ptr deviceIdHash{megaApi[0]->getDeviceId()}; ASSERT_TRUE(deviceIdHash); unique_ptr myBackupsFolder{megaApi[0]->getNodePathByNodeHandle(mh)}; ASSERT_TRUE(myBackupsFolder); string expectedRemotePath = string(myBackupsFolder.get()) + '/' + string(deviceIdHash.get()) + '/' + backupName; ASSERT_STREQ(expectedRemotePath.c_str(), actualRemotePath.get()) << "Wrong remote path for backup"; #endif // So we can detect when the node database has been committed. PerApi& target = mApi[0]; target.resetlastEvent(); // Verify that the sync was added unique_ptr newBkp(megaApi[0]->getSyncByBackupId(bkpId)); ASSERT_TRUE(newBkp); ASSERT_EQ(newBkp->getType(), MegaSync::TYPE_BACKUP); ASSERT_EQ(newBkp->getMegaHandle(), newSyncRootNodeHandle); ASSERT_STREQ(newBkp->getName(), backupName); ASSERT_STREQ(newBkp->getLastKnownMegaFolder(), actualRemotePath.get()); ASSERT_TRUE(newBkp->getRunState() == MegaSync::RUNSTATE_RUNNING) << "Backup instance found but not active."; // Wait for the node database to be updated. // If nothing changed, there won't be an update //ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 8192)); // Verify sync after logout / login string session = unique_ptr(dumpSession()).get(); locallogout(); auto tracker = asyncRequestFastLogin(0, session.c_str()); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session for account " << 0; target.resetlastEvent(); fetchnodes(0, maxTimeout); // auto-resumes one active backup ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_SYNCS_RESTORED); }, 10000)); // Verify the sync again newBkp.reset(megaApi[0]->getSyncByBackupId(bkpId)); ASSERT_TRUE(newBkp); ASSERT_EQ(newBkp->getType(), MegaSync::TYPE_BACKUP); ASSERT_EQ(newBkp->getMegaHandle(), newSyncRootNodeHandle); ASSERT_STREQ(newBkp->getName(), backupName); ASSERT_STREQ(newBkp->getLastKnownMegaFolder(), actualRemotePath.get()); ASSERT_TRUE(newBkp->getRunState() == MegaSync::RUNSTATE_RUNNING) << "Backup instance found but not active after logout & login."; // make sure that client is up to date (upon logout, recent changes might not be committed to DB, // which may result on the new node not being available yet). size_t times = 10; while (times--) { if (target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT)) break; std::this_thread::sleep_for(std::chrono::seconds{1}); } ASSERT_TRUE(target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT)) << "Timeout expired to receive actionpackets"; // disable backup RequestTracker disableBkpTracker(megaApi[0].get()); megaApi[0]->setSyncRunState(bkpId, MegaSync::RUNSTATE_DISABLED, &disableBkpTracker); ASSERT_EQ(API_OK, disableBkpTracker.waitForResult()); // remove local file from backup constexpr auto retryTimeout = 1000; ASSERT_TRUE(WaitFor( [testFile]() { std::error_code ec; fs::remove(testFile, ec); if (!ec) { return true; } return false; }, retryTimeout)) << "Failed to remove file " << testFile.string(); // enable backup RequestTracker enableBkpTracker(megaApi[0].get()); megaApi[0]->setSyncRunState(bkpId, MegaSync::RUNSTATE_RUNNING, &enableBkpTracker); ASSERT_EQ(API_OK, enableBkpTracker.waitForResult()); // Remove registered backup RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->removeSync(bkpId, &removeTracker); ASSERT_EQ(API_OK, removeTracker.waitForResult()); RequestTracker removeNodesTracker(megaApi[0].get()); megaApi[0]->moveOrRemoveDeconfiguredBackupNodes(newBkp->getMegaHandle(), INVALID_HANDLE, &removeNodesTracker); ASSERT_EQ(API_OK, removeNodesTracker.waitForResult()); newBkp.reset(megaApi[0]->getSyncByBackupId(bkpId)); ASSERT_FALSE(newBkp) << "Registered backup was not removed"; // Request to backup another folder // this time, the remote folder structure is already there fs::path localFolderPath2 = localBasePath / "LocalBackedUpFolder2"; fs::create_directories(localFolderPath2); const string backupName2Str = string("RemoteBackupFolder2_") + timestamp; const char* backupName2 = backupName2Str.c_str(); err = synchronousSyncFolder(0, nullptr, MegaSync::TYPE_BACKUP, path_u8string(localFolderPath2).c_str(), backupName2, INVALID_HANDLE, nullptr); ASSERT_TRUE(err == API_OK) << "Backup folder 2 failed (error: " << err << ")"; bkpId = mApi[0].lastSyncBackupId; newBkp.reset(megaApi[0]->getSyncByBackupId(bkpId)); ASSERT_TRUE(newBkp) << "Sync not found for second backup"; // Create remote folder to be used as destination when removing second backup std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); auto nhrb = createFolder(0, "DestinationOfRemovedBackup", remoteRootNode.get()); ASSERT_NE(nhrb, UNDEF) << "Error creating remote DestinationOfRemovedBackup"; std::unique_ptr remoteDestNode(megaApi[0]->getNodeByHandle(nhrb)); ASSERT_NE(remoteDestNode.get(), nullptr) << "Error getting remote node of DestinationOfRemovedBackup"; std::unique_ptr destChildren(megaApi[0]->getChildren(remoteDestNode.get())); ASSERT_TRUE(!destChildren || !destChildren->size()); // Remove second backup, using the option to move the contents rather than delete them RequestTracker removeTracker2(megaApi[0].get()); megaApi[0]->removeSync(bkpId, &removeTracker2); ASSERT_EQ(API_OK, removeTracker2.waitForResult()); RequestTracker moveNodesTracker(megaApi[0].get()); megaApi[0]->moveOrRemoveDeconfiguredBackupNodes(newBkp->getMegaHandle(), nhrb, &moveNodesTracker); ASSERT_EQ(API_OK, moveNodesTracker.waitForResult()); newBkp.reset(megaApi[0]->getSyncByBackupId(bkpId)); ASSERT_FALSE(newBkp) << "Sync not removed for second backup"; destChildren.reset(megaApi[0]->getChildren(remoteDestNode.get())); ASSERT_TRUE(destChildren && destChildren->size() == 1); ASSERT_STREQ(destChildren->get(0)->getName(), backupName2); #endif } #ifdef ENABLE_SYNC /** * @brief TEST_F SdkBackupMoveOrDelete * * It tests the creation and removal of Backups * * Pre-requisites: * - This test will use 2 clients (C0 and C1) logged in to the same account * * Test cases: * - Test1(SdkBackupMoveOrDelete). Create a backup from C0 * - Test2(SdkBackupMoveOrDelete). Request backup removal (and delete its contents) from C1 * - Test3(SdkBackupMoveOrDelete). Create a backup from C0 * - Test4(SdkBackupMoveOrDelete). Request backup removal (and move its contents) from C1 * - Test5(SdkBackupMoveOrDelete). Create a sync from C0 * - Test6(SdkBackupMoveOrDelete). Request sync stop from C1 */ TEST_F(SdkTest, SdkBackupMoveOrDelete) { using Sl = SyncListener; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST BackupMoveOrDelete___"; Sl sl0; MegaListenerDeregisterer mld0(megaApi[0].get(), &sl0); string timestamp = getCurrentTimestamp(true); // Make sure My Backups folder was created ASSERT_NO_FATAL_FAILURE(syncTestEnsureMyBackupsRemoteFolderExists(0)); LOG_debug << "### Test1(SdkBackupMoveOrDelete). Create a backup from C1 ###"; // Create local contents to back up fs::path localFolderPath = fs::current_path() / "LocalBackupFolder"; std::error_code ec; fs::remove_all(localFolderPath, ec); ASSERT_FALSE(fs::exists(localFolderPath)); fs::create_directories(localFolderPath); string bkpFile = "bkpFile"; ASSERT_TRUE(createLocalFile(localFolderPath, bkpFile.c_str())); // Create a backup const string backupNameStr = string("RemoteBackupFolder_") + timestamp; MegaHandle backupRootNodeHandle = INVALID_HANDLE; int err = synchronousSyncFolder(0, &backupRootNodeHandle, MegaSync::TYPE_BACKUP, path_u8string(localFolderPath).c_str(), backupNameStr.c_str(), INVALID_HANDLE, nullptr); ASSERT_EQ(err, API_OK) << "Backup failed"; ASSERT_NE(backupRootNodeHandle, INVALID_HANDLE) << "Invalid root handle for backup"; // Get backup id unique_ptr allSyncs{ megaApi[0]->getSyncs() }; MegaHandle backupId = INVALID_HANDLE; for (int i = 0; allSyncs && i < allSyncs->size(); ++i) { MegaSync* megaSync = allSyncs->get(i); if (megaSync->getType() == MegaSync::TYPE_BACKUP && megaSync->getMegaHandle() == backupRootNodeHandle) { ASSERT_STREQ(megaSync->getName(), backupNameStr.c_str()) << "New backup had wrong name"; // Make sure the sync's actually active. ASSERT_EQ(megaSync->getRunState(), MegaSync::RUNSTATE_RUNNING) << "Backup found but not active."; backupId = megaSync->getBackupId(); break; } } ASSERT_NE(backupId, INVALID_HANDLE) << "Backup could not be found"; LOG_debug << "### Test2(SdkBackupMoveOrDelete). Request backup removal (and delete its " "contents) from C2 ###"; // Use another connection with the same credentials megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(1).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT))); auto& differentApi = *megaApi.back(); differentApi.addListener(this); PerApi pa; // make a copy pa.email = mApi.back().email; pa.pwd = mApi.back().pwd; mApi.push_back(std::move(pa)); auto& differentApiDtls = mApi.back(); differentApiDtls.megaApi = &differentApi; size_t differentApiIdx = megaApi.size() - 1; auto loginTracker = asyncRequestLogin(static_cast(differentApiIdx), differentApiDtls.email.c_str(), differentApiDtls.pwd.c_str()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to establish a login/session for account " << differentApiIdx; loginTracker = asyncRequestFetchnodes(static_cast(differentApiIdx)); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to fetch nodes for account " << differentApiIdx; sl0.mRecvCbs[Sl::SyncDeleted] = false; RequestTracker removeBackupTracker(megaApi[differentApiIdx].get()); megaApi[differentApiIdx]->removeFromBC(backupId, INVALID_HANDLE, &removeBackupTracker); ASSERT_EQ(removeBackupTracker.waitForResult(), API_OK) << "Failed to remove backup and delete its contents"; ASSERT_TRUE(WaitFor( [&sl0]() { return sl0.mRecvCbs[Sl::SyncDeleted].load(); }, 120000)) << "onSyncDeleted not received for C0"; // Wait for this client to receive the backup removal request auto syncCfgRemoved = [this, &backupId]() { unique_ptr s(megaApi[0]->getSyncByBackupId(backupId)); return !s; }; ASSERT_TRUE(syncCfgRemoved()) << "Original API could still see the removed backup"; // Wait for the backup to be removed from remote storage auto bkpDeleted = [this, backupRootNodeHandle]() { std::unique_ptr deletedNode(megaApi[0]->getNodeByHandle(backupRootNodeHandle)); return !deletedNode; }; ASSERT_TRUE(WaitFor(bkpDeleted, 60000)) << "Backup not removed after 60 seconds"; LOG_debug << "### Test3(SdkBackupMoveOrDelete). Create a backup from C1 ###"; backupRootNodeHandle = INVALID_HANDLE; err = synchronousSyncFolder(0, &backupRootNodeHandle, MegaSync::TYPE_BACKUP, path_u8string(localFolderPath).c_str(), backupNameStr.c_str(), INVALID_HANDLE, nullptr); ASSERT_EQ(err, API_OK) << "Second backup failed"; ASSERT_NE(backupRootNodeHandle, INVALID_HANDLE) << "Invalid root handle for 2nd backup"; // Create move destination MegaNode *rootnode = megaApi[0]->getRootNode(); string moveDstName = "bkpMoveDest"; MegaHandle moveDest = createFolder(0, moveDstName.c_str(), rootnode); ASSERT_NE(moveDest, INVALID_HANDLE); std::unique_ptr moveDestNode(megaApi[0]->getNodeByHandle(moveDest)); ASSERT_TRUE(moveDestNode) << "Node missing for remote folder " << moveDstName; // Get 2nd backup id allSyncs.reset(megaApi[0]->getSyncs()); backupId = INVALID_HANDLE; for (int i = 0; allSyncs && i < allSyncs->size(); ++i) { MegaSync* megaSync = allSyncs->get(i); if (megaSync->getType() == MegaSync::TYPE_BACKUP && megaSync->getMegaHandle() == backupRootNodeHandle) { ASSERT_STREQ(megaSync->getName(), backupNameStr.c_str()) << "2nd backup had wrong name"; // Make sure the sync's actually active. ASSERT_TRUE(megaSync->getRunState() == MegaSync::RUNSTATE_RUNNING) << "2nd backup found but not active."; backupId = megaSync->getBackupId(); break; } } ASSERT_NE(backupId, INVALID_HANDLE) << "2nd backup could not be found"; // Wait for other API to see the backup destination auto bkpDestOK = [this, differentApiIdx, &moveDest]() { unique_ptr bd(megaApi[differentApiIdx]->getNodeByHandle(moveDest)); return bd != nullptr; }; ASSERT_TRUE(WaitFor(bkpDestOK, 60000)) << "Other API could not see the backup destination after 60 seconds"; LOG_debug << "### Test4(SdkBackupMoveOrDelete). Request backup removal (and move its contents) " "from C2 ###"; sl0.mRecvCbs[Sl::SyncDeleted] = false; // Request backup removal (and move its contents) from a different connection RequestTracker removeBackupTracker2(megaApi[static_cast(differentApiIdx)].get()); megaApi[differentApiIdx]->removeFromBC(backupId, moveDest, &removeBackupTracker2); ASSERT_EQ(removeBackupTracker2.waitForResult(), API_OK) << "Failed to remove 2nd backup and move its contents"; ASSERT_TRUE(WaitFor( [&sl0]() { return sl0.mRecvCbs[Sl::SyncDeleted].load(); }, 120000)) << "onSyncDeleted not received for C0"; ASSERT_TRUE(syncCfgRemoved()) << "Original API could still see the 2nd removed backup"; // Wait for the contents of the 2nd backup to be moved in remote storage auto bkpMoved = [this, backupRootNodeHandle, &moveDestNode]() { std::unique_ptr destChildren(megaApi[0]->getChildren(moveDestNode.get())); return destChildren && destChildren->size() == 1 && destChildren->get(0)->getHandle() == backupRootNodeHandle; }; ASSERT_TRUE(WaitFor(bkpMoved, 60000)) << "2nd backup not moved after 60 seconds"; LOG_debug << "### Test5(SdkBackupMoveOrDelete). Create a sync from C1 ###"; // Create a sync backupRootNodeHandle = INVALID_HANDLE; err = synchronousSyncFolder(0, &backupRootNodeHandle, MegaSync::TYPE_TWOWAY, path_u8string(localFolderPath).c_str(), nullptr, moveDest, nullptr); ASSERT_EQ(err, API_OK) << "Sync failed"; ASSERT_NE(backupRootNodeHandle, INVALID_HANDLE) << "Invalid root handle for sync"; // Get backup id of the sync allSyncs.reset(megaApi[0]->getSyncs()); backupId = INVALID_HANDLE; for (int i = 0; allSyncs && i < allSyncs->size(); ++i) { MegaSync* megaSync = allSyncs->get(i); if (megaSync->getType() == MegaSync::TYPE_TWOWAY && megaSync->getMegaHandle() == backupRootNodeHandle) { // Make sure the sync's actually active. ASSERT_TRUE(megaSync->getRunState() == MegaSync::RUNSTATE_RUNNING) << "Sync found but not active."; backupId = megaSync->getBackupId(); break; } } ASSERT_NE(backupId, INVALID_HANDLE) << "Sync could not be found"; LOG_debug << "### Test6(SdkBackupMoveOrDelete). Request sync stop from C2 ###"; sl0.mRecvCbs[Sl::SyncDeleted] = false; RequestTracker stopSyncTracker(megaApi[differentApiIdx].get()); megaApi[differentApiIdx]->removeFromBC(backupId, INVALID_HANDLE, &stopSyncTracker); ASSERT_EQ(stopSyncTracker.waitForResult(), API_OK) << "Failed to stop sync"; ASSERT_TRUE(WaitFor( [&sl0]() { return sl0.mRecvCbs[Sl::SyncDeleted].load(); }, 120000)) << "onSyncDeleted not received for C0"; ASSERT_TRUE(syncCfgRemoved()) << "Original API could still see the removed sync"; fs::remove_all(localFolderPath, ec); } TEST_F(SdkTest, SdkBackupPauseResume) { LOG_info << "___TEST BackupPauseResume___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const string timestamp = getCurrentTimestamp(true); // Make sure My Backups folder was created ASSERT_NO_FATAL_FAILURE(syncTestEnsureMyBackupsRemoteFolderExists(0)); // Create local contents vector folders = {fs::current_path() / "LocalFolderPauseResume", fs::current_path() / "LocalSyncFolder"}; for (const auto& localFolder: folders) { std::error_code ec; fs::remove_all(localFolder, ec); ASSERT_FALSE(fs::exists(localFolder)); fs::create_directories(localFolder); ASSERT_TRUE(createLocalFile(localFolder, "bkpFile")); } const string localBackupFolder = path_u8string(folders[0]); const string localSyncFolder = path_u8string(folders[1]); // Create a backup, and get its id const string backupNameStr = string("RemoteBackupFolder_") + timestamp; ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_BACKUP, localBackupFolder.c_str(), backupNameStr.c_str(), INVALID_HANDLE, nullptr)) << "Initial connection: Failed to create a Backup"; MegaHandle idOfBackup = mApi[0].lastSyncBackupId; ASSERT_NE(idOfBackup, INVALID_HANDLE) << "Initial connection: invalid Backup id"; // Create a sync, and get its id unique_ptr rootnode{megaApi[0]->getRootNode()}; MegaHandle syncDest = createFolder(0, "syncDest", rootnode.get()); ASSERT_NE(syncDest, INVALID_HANDLE); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, localSyncFolder.c_str(), nullptr, syncDest, nullptr)) << "Initial connection: Failed to create a Sync"; MegaHandle idOfSync = mApi[0].lastSyncBackupId; ASSERT_NE(idOfSync, INVALID_HANDLE) << "Initial connection: invalid Sync id"; auto testRunState = [&apis = megaApi](MegaHandle backupId, int desiredState) { unique_ptr s(apis[0]->getSyncByBackupId(backupId)); return s && s->getRunState() == desiredState; }; // Wait for the backup to be in RUNNING state ASSERT_TRUE(WaitFor( [idOfBackup, desiredState = MegaSync::RUNSTATE_RUNNING, testRunState]() { return testRunState(idOfBackup, desiredState); }, 60000)) << "Initial connection: backup not Running (started) after 60 seconds"; // Wait for the sync to be in RUNNING state ASSERT_TRUE(WaitFor( [idOfSync, desiredState = MegaSync::RUNSTATE_RUNNING, testRunState]() { return testRunState(idOfSync, desiredState); }, 60000)) << "Initial connection: sync not Running (started) after 60 seconds"; // Create a second connection with the same credentials megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(1).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT))); megaApi.back()->addListener(this); PerApi pa; // make a copy pa.email = mApi.back().email; pa.pwd = mApi.back().pwd; mApi.push_back(std::move(pa)); auto& differentApiDtls = mApi.back(); differentApiDtls.megaApi = megaApi.back().get(); { bool& fetchNodesDone = mApi[1].requestFlags[MegaRequest::TYPE_FETCH_NODES] = false; unique_ptr loginTracker = std::make_unique(megaApi[1].get()); megaApi[1]->login(differentApiDtls.email.c_str(), differentApiDtls.pwd.c_str(), loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << "Second connection: Failed to login"; ASSERT_NO_FATAL_FAILURE(fetchnodes(1)) << "Second connection: Failed to request fetch nodes"; ASSERT_TRUE(WaitFor( [fetchNodesDone]() { return fetchNodesDone; }, 60000)) << "Second connection: fetch nodes not done after 60 seconds"; } // Commands for the Backup { // Second connection: Pause backup RequestTracker pauseBackupTracker(megaApi[1].get()); megaApi[1]->pauseFromBC(idOfBackup, &pauseBackupTracker); ASSERT_EQ(pauseBackupTracker.waitForResult(), API_OK) << "Second connection: Failed to Pause backup"; // Initial connection: wait for backup to be Paused ASSERT_TRUE(WaitFor( [idOfBackup, testRunState]() { return testRunState(idOfBackup, MegaSync::RUNSTATE_SUSPENDED); }, 120000)) << "Initial connection: backup not Paused after 120 seconds"; // Wait a while (for the sds attr to be updated and propagated). // Without this, resuming will fail sometimes. std::this_thread::sleep_for(std::chrono::seconds{5}); // Second connection: Resume backup RequestTracker resumeBackupTracker(megaApi[1].get()); megaApi[1]->resumeFromBC(idOfBackup, &resumeBackupTracker); ASSERT_EQ(resumeBackupTracker.waitForResult(), API_OK) << "Second connection: Failed to Resume backup"; // Initial connection: wait for backup to be Resumed ASSERT_TRUE(WaitFor( [idOfBackup, testRunState]() { return testRunState(idOfBackup, MegaSync::RUNSTATE_RUNNING); }, 120000)) << "Initial connection: backup not Running (resumed) after 120 seconds"; // Clean-up RequestTracker removeBackupTracker(megaApi[0].get()); megaApi[0]->removeSync(idOfBackup, &removeBackupTracker); ASSERT_EQ(removeBackupTracker.waitForResult(), API_OK) << "Initial connection: Failed to remove backup"; } // Commands for the Sync { // Second connection: Pause sync RequestTracker pauseSyncTracker(megaApi[1].get()); megaApi[1]->pauseFromBC(idOfSync, &pauseSyncTracker); ASSERT_EQ(pauseSyncTracker.waitForResult(), API_OK) << "Second connection: Failed to Pause sync"; // Initial connection: wait for sync to be Paused ASSERT_TRUE(WaitFor( [idOfSync, testRunState]() { return testRunState(idOfSync, MegaSync::RUNSTATE_SUSPENDED); }, 120000)) << "Initial connection: sync not Paused after 120 seconds"; // Wait a while (for the sds attr to be updated and propagated). // Without this, resuming will fail sometimes. std::this_thread::sleep_for(std::chrono::seconds{5}); // Second connection: Resume sync RequestTracker resumeSyncTracker(megaApi[1].get()); megaApi[1]->resumeFromBC(idOfSync, &resumeSyncTracker); ASSERT_EQ(resumeSyncTracker.waitForResult(), API_OK) << "Second connection: Failed to Resume sync"; // Initial connection: wait for sync to be Resumed ASSERT_TRUE(WaitFor( [idOfSync, testRunState]() { return testRunState(idOfSync, MegaSync::RUNSTATE_RUNNING); }, 120000)) << "Initial connection: sync not Running (resumed) after 120 seconds"; // Clean-up RequestTracker removeSyncTracker(megaApi[0].get()); megaApi[0]->removeSync(idOfSync, &removeSyncTracker); ASSERT_EQ(removeSyncTracker.waitForResult(), API_OK) << "Initial connection: Failed to remove sync"; } std::error_code ec; fs::remove_all(folders[0], ec); fs::remove_all(folders[1], ec); } TEST_F(SdkTest, SdkExternalDriveFolder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkExternalDriveFolder___"; // dummy path to drive fs::path basePath = makeNewTestRoot(); fs::path pathToDrive = basePath / "ExtDrive"; fs::create_directory(pathToDrive); std::string pathToDriveStr = path_u8string(pathToDrive); // attempt to set the name of an external drive to the name of current device (if the latter was already set) string deviceName; if (doGetDeviceName(0, &deviceName, nullptr) == API_OK && !deviceName.empty()) { ASSERT_EQ(API_EEXIST, doSetDriveName(0, pathToDriveStr.c_str(), deviceName.c_str())) << "Ext-drive name was set to current device name: " << deviceName; } // drive name string driveName = "SdkExternalDriveTest_" + getCurrentTimestamp(true); // set drive name auto err = doSetDriveName(0, pathToDriveStr.c_str(), driveName.c_str()); ASSERT_EQ(API_OK, err) << "setDriveName failed (error: " << err << ")"; // attempt to set the same name to another drive fs::path pathToDrive2 = basePath / "ExtDrive2"; fs::create_directory(pathToDrive2); std::string pathToDriveStr2 = path_u8string(pathToDrive2); err = doSetDriveName(0, pathToDriveStr2.c_str(), driveName.c_str()); ASSERT_EQ(API_EEXIST, err) << "setDriveName allowed duplicated name " << driveName << ". Should not have."; // get drive name string driveNameFromCloud; err = doGetDriveName(0, &driveNameFromCloud, pathToDriveStr.c_str()); ASSERT_EQ(API_OK, err) << "getDriveName failed (error: " << err << ")"; ASSERT_EQ(driveNameFromCloud, driveName) << "getDriveName returned incorrect value"; // Make sure My Backups folder was created ASSERT_NO_FATAL_FAILURE(syncTestEnsureMyBackupsRemoteFolderExists(0)); MegaHandle mh = mApi[0].lastSyncBackupId; // add backup string bkpName = "Bkp"; const fs::path& pathToBkp = pathToDrive / bkpName; fs::create_directory(pathToBkp); std::string pathToBkpStr = path_u8string(pathToBkp); MegaHandle backupFolderHandle = UNDEF; err = synchronousSyncFolder(0, &backupFolderHandle, MegaSync::SyncType::TYPE_BACKUP, pathToBkpStr.c_str(), nullptr, INVALID_HANDLE, pathToDriveStr.c_str()); ASSERT_EQ(API_OK, err) << "sync folder failed (error: " << err << ")"; auto backupId = mApi[0].lastSyncBackupId; // get device id of the external drive handle driveId; err = readDriveId(*fileSystemAccess, LocalPath::fromAbsolutePath(pathToDriveStr).toPath(false).c_str(), driveId); ASSERT_EQ(API_OK, err) << "Add backup (external): failed to read drive id"; // create the device id from the drive id string deviceId(Base64Str(driveId).chars); // Verify that the remote path was created as expected unique_ptr myBackupsFolder{ megaApi[0]->getNodePathByNodeHandle(mh) }; string expectedRemotePath = string(myBackupsFolder.get()) + '/' + deviceId + '/' + bkpName; unique_ptr actualRemotePath{ megaApi[0]->getNodePathByNodeHandle(backupFolderHandle) }; ASSERT_EQ(expectedRemotePath, actualRemotePath.get()) << "Wrong remote path for backup"; // disable backup std::unique_ptr backupNode(megaApi[0]->getNodeByHandle(backupFolderHandle)); err = synchronousSetSyncRunState(0, backupId, MegaSync::RUNSTATE_DISABLED); ASSERT_EQ(API_OK, err) << "Disable sync failed (error: " << err << ")"; // remove backup err = synchronousRemoveSync(0, backupId); ASSERT_EQ(MegaError::API_OK, err) << "Remove sync failed (error: " << err << ")"; ASSERT_EQ(MegaError::API_OK, synchronousRemoveBackupNodes(0, backupFolderHandle)); // reset DriveName value, before a future test err = doSetDriveName(0, pathToDriveStr.c_str(), ""); ASSERT_EQ(API_OK, err) << "setDriveName failed when resetting (error: " << err << ")"; // attempt to get drive name (after being deleted) err = doGetDriveName(0, nullptr, pathToDriveStr.c_str()); ASSERT_EQ(API_ENOENT, err) << "getDriveName not failed as it should (error: " << err << ")"; } #endif void SdkTest::syncTestEnsureMyBackupsRemoteFolderExists(unsigned apiIdx) { mApi[apiIdx].lastSyncBackupId = UNDEF; int err = synchronousGetUserAttribute(apiIdx, MegaApi::USER_ATTR_MY_BACKUPS_FOLDER); EXPECT_TRUE(err == MegaError::API_OK || err == MegaError::API_ENOENT) << "Failed to get USER_ATTR_MY_BACKUPS_FOLDER"; if (mApi[apiIdx].lastSyncBackupId == UNDEF) { const char* folderName = "My Backups"; mApi[apiIdx].userUpdated = false; int err = synchronousSetMyBackupsFolder(apiIdx, folderName); EXPECT_EQ(err, MegaError::API_OK) << "Failed to set backups folder to " << folderName; EXPECT_TRUE(waitForResponse(&mApi[apiIdx].userUpdated)) << "User attribute update not received after " << maxTimeout << " seconds"; unique_ptr myUser(megaApi[apiIdx]->getMyUser()); err = synchronousGetUserAttribute(apiIdx, myUser.get(), MegaApi::USER_ATTR_MY_BACKUPS_FOLDER); EXPECT_EQ(err, MegaError::API_OK) << "Failed to get user attribute USER_ATTR_MY_BACKUPS_FOLDER"; } EXPECT_NE(mApi[apiIdx].lastSyncBackupId, UNDEF); unique_ptr n(megaApi[apiIdx]->getNodeByHandle(mApi[apiIdx].lastSyncBackupId)); ASSERT_NE(n, nullptr) << "syncTestMyBackupsRemoteFolder: My Backups Folder could not be retrieved"; } void SdkTest::resetOnNodeUpdateCompletionCBs() { for_each(begin(mApi), end(mApi), [](PerApi& api) { if (api.mOnNodesUpdateCompletion) api.mOnNodesUpdateCompletion = nullptr; }); } void SdkTest::cleanupCatchupWithApi(const unsigned apiIndex, const unsigned int timeoutSecs) { const std::string prefix{"cleanupCatchupWithApi"}; if (!megaApi[apiIndex]->getClient()->scsn.ready()) { LOG_debug << prefix << ". Skipping catching up with API for account (" << apiIndex << "). scsn not ready"; return; } const std::string msg = prefix + ". Catching up with API with account index(" + std::to_string(apiIndex) + ")"; LOG_debug << msg; const auto res = synchronousCatchupIgnoreErr(apiIndex, timeoutSecs); LOG_warn << msg << (res == API_OK ? " finished successfully" : " . Failed, ErrCode(" + std::to_string(res) + "). " + string(MegaError::getErrorString(res))); } onNodesUpdateCompletion_t SdkTest::createOnNodesUpdateLambda(const MegaHandle& hfolder, int change, bool& flag) { flag = false; return [this, hfolder, change, &flag](size_t apiIndex, MegaNodeList* nodes) { onNodesUpdateCheck(apiIndex, hfolder, nodes, change, flag); }; } TEST_F(SdkTest, SdkUserAlias) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkUserAlias___"; // setup MegaHandle uh = UNDEF; if (auto u = std::unique_ptr(megaApi[0]->getMyUser())) { uh = u->getHandle(); } else { ASSERT_TRUE(false) << "Cannot find the MegaUser for email: " << mApi[0].email; } if (uh == UNDEF) { ASSERT_TRUE(false) << "failed to get user handle for email:" << mApi[0].email; } // test setter/getter string alias = "UserAliasTest"; auto err = synchronousSetUserAlias(0, uh, alias.c_str()); ASSERT_EQ(API_OK, err) << "setUserAlias failed (error: " << err << ")"; err = synchronousGetUserAlias(0, uh); ASSERT_EQ(API_OK, err) << "getUserAlias failed (error: " << err << ")"; ASSERT_EQ(mApi[0].getAttributeValue(), alias) << "getUserAlias returned incorrect value"; // test setter/getter for different value alias = "UserAliasTest_changed"; err = synchronousSetUserAlias(0, uh, alias.c_str()); ASSERT_EQ(API_OK, err) << "setUserAlias failed (error: " << err << ")"; err = synchronousGetUserAlias(0, uh); ASSERT_EQ(API_OK, err) << "getUserAlias failed (error: " << err << ")"; ASSERT_EQ(mApi[0].getAttributeValue(), alias) << "getUserAlias returned incorrect value"; } #if 0 // SMS verification was deprecated. This test should be removed in the future, // along with the rest of the code dealing with the deprecated functionality. TEST_F(SdkTest, SdkGetCountryCallingCodes) { LOG_info << "___TEST SdkGetCountryCallingCodes___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); getCountryCallingCodes(); ASSERT_GT(mApi[0].getStringListCount(), 0u); // sanity check a few country codes const MegaStringList* const nz = mApi[0].getStringList("NZ"); ASSERT_NE(nullptr, nz); ASSERT_EQ(1, nz->size()); ASSERT_EQ(0, strcmp("64", nz->get(0))); const MegaStringList* const de = mApi[0].getStringList("DE"); ASSERT_NE(nullptr, de); ASSERT_EQ(1, de->size()); ASSERT_EQ(0, strcmp("49", de->get(0))); } #endif TEST_F(SdkTest, DISABLED_invalidFileNames) { LOG_info << "___TEST invalidFileNames___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); auto aux = LocalPath::fromAbsolutePath(path_u8string(fs::current_path())); #if defined (__linux__) || defined (__ANDROID__) if (fileSystemAccess->getlocalfstype(aux) == FS_EXT) { // Escape set of characters and check if it's the expected one const char *name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", fs::current_path().c_str()); ASSERT_TRUE (!strcmp(name, "!\"#$%&'()*+,-.%2f:;<=>?@[\\]^_`{|}~")); delete [] name; // Unescape set of characters and check if it's the expected one name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d" "%2e%2f%30%31%32%33%34%35%36%37" "%38%39%3a%3b%3c%3d%3e%3f%40%5b" "%5c%5d%5e%5f%60%7b%7c%7d%7e", fs::current_path().c_str()); ASSERT_TRUE(!strcmp(name, "%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e" "/%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e" "%3f%40%5b%5c%5d%5e%5f%60%7b%7c%7d%7e")); delete [] name; } #elif defined (__APPLE__) || defined (USE_IOS) if (fileSystemAccess->getlocalfstype(aux) == FS_APFS || fileSystemAccess->getlocalfstype(aux) == FS_HFS) { // Escape set of characters and check if it's the expected one const char *name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", fs::current_path().c_str()); ASSERT_TRUE (!strcmp(name, "!\"#$%&'()*+,-./%3a;<=>?@[\\]^_`{|}~")); delete [] name; // Unescape set of characters and check if it's the expected one name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d" "%2e%2f%30%31%32%33%34%35%36%37" "%38%39%3a%3b%3c%3d%3e%3f%40%5b" "%5c%5d%5e%5f%60%7b%7c%7d%7e", fs::current_path().c_str()); ASSERT_TRUE(!strcmp(name, "%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e" "%2f%30%31%32%33%34%35%36%37%38%39:%3b%3c%3d%3e" "%3f%40%5b%5c%5d%5e%5f%60%7b%7c%7d%7e")); delete [] name; } #elif defined(_WIN32) || defined(_WIN64) if (fileSystemAccess->getlocalfstype(aux) == FS_NTFS) { // Escape set of characters and check if it's the expected one const char* name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", path_u8string(fs::current_path()).c_str()); ASSERT_TRUE (!strcmp(name, "!%22#$%&'()%2a+,-.%2f%3a;%3c=%3e%3f@[%5c]^_`{%7c}~")); delete [] name; // Unescape set of characters and check if it's the expected one name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d" "%2e%2f%30%31%32%33%34%35%36%37" "%38%39%3a%3b%3c%3d%3e%3f%40%5b" "%5c%5d%5e%5f%60%7b%7c%7d%7e", path_u8string(fs::current_path()).c_str()); ASSERT_TRUE(!strcmp(name, "%21\"%23%24%25%26%27%28%29*%2b%2c%2d" "%2e/%30%31%32%33%34%35%36%37" "%38%39:%3b<%3d>?%40%5b" "\\%5d%5e%5f%60%7b|%7d%7e")); delete [] name; } #endif // Maps filename unescaped (original) to filename escaped (expected result): f%2ff => f/f std::unique_ptr fileNamesStringMap = std::unique_ptr{MegaStringMap::createInstance()}; fs::path uploadPath = fs::current_path() / "upload_invalid_filenames"; if (fs::exists(uploadPath)) { fs::remove_all(uploadPath); } fs::create_directories(uploadPath); for (int i = 0x01; i <= 0xA0; i++) { // skip [0-9] [A-Z] [a-z] if ((i >= 0x30 && i <= 0x39) || (i >= 0x41 && i <= 0x5A) || (i >= 0x61 && i <= 0x7A)) { continue; } // Create file with unescaped character ex: f%5cf char unescapedName[6]; snprintf(unescapedName, sizeof(unescapedName), "f%%%02xf", i); if (createLocalFile(uploadPath, unescapedName)) { const char* unescapedFileName = megaApi[0]->unescapeFsIncompatible(unescapedName, path_u8string(uploadPath).c_str()); fileNamesStringMap->set(unescapedName, unescapedFileName); delete [] unescapedFileName; } // Create another file with the original character if supported f\f if ((i >= 0x01 && i <= 0x20) || (i >= 0x7F && i <= 0xA0)) { // Skip control characters continue; } char escapedName[4]; snprintf(escapedName, sizeof(escapedName), "f%cf", i); const char* escapedFileName = megaApi[0]->escapeFsIncompatible(escapedName, path_u8string(uploadPath).c_str()); if (escapedFileName && !strcmp(escapedName, escapedFileName)) { // Only create those files with supported characters, those ones that need unescaping // has been created above if (createLocalFile(uploadPath, escapedName)) { const char* unescapedFileName = megaApi[0]->unescapeFsIncompatible(escapedName, path_u8string(uploadPath).c_str()); fileNamesStringMap->set(escapedName, unescapedFileName); delete [] unescapedFileName; } } delete [] escapedFileName; } TransferTracker uploadListener(megaApi[0].get()); MegaUploadOptions uploadOptions; uploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto root = std::unique_ptr{megaApi[0]->getRootNode()}; const auto uploadLocalPath = uploadPath.string(); megaApi[0]->startUpload(uploadLocalPath, root.get(), nullptr, &uploadOptions, &uploadListener); ASSERT_EQ(API_OK, uploadListener.waitForResult()); ::mega::unique_ptr n(megaApi[0]->getNodeByPath("/upload_invalid_filenames")); ASSERT_TRUE(n.get()); ::mega::unique_ptr authNode(megaApi[0]->authorizeNode(n.get())); ASSERT_TRUE(authNode.get()); MegaNodeList *children(authNode->getChildren()); ASSERT_TRUE(children && children->size()); for (int i = 0; i < children->size(); i++) { MegaNode *child = children->get(i); const char *uploadedName = child->getName(); const char* uploadedNameEscaped = megaApi[0]->escapeFsIncompatible(child->getName(), path_u8string(uploadPath).c_str()); const char *expectedName = fileNamesStringMap->get(uploadedNameEscaped); delete [] uploadedNameEscaped; // Conditions to check if uploaded fileName is correct: // 1) Escaped uploaded filename must be found in fileNamesStringMap (original filename found) // 2) Uploaded filename must be equal than the expected value (original filename unescaped) ASSERT_TRUE (uploadedName && expectedName && !strcmp(uploadedName, expectedName)); } // Download files fs::path downloadPath = fs::current_path() / "download_invalid_filenames"; if (fs::exists(downloadPath)) { fs::remove_all(downloadPath); } fs::create_directories(downloadPath); TransferTracker downloadListener(megaApi[0].get()); megaApi[0]->startDownload( authNode.get(), path_u8string(downloadPath).c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener); ASSERT_EQ(API_OK, downloadListener.waitForResult()); for (fs::directory_iterator itpath (downloadPath); itpath != fs::directory_iterator(); ++itpath) { std::string downloadedName = path_u8string(itpath->path().filename()); if (!downloadedName.compare(".") || !downloadedName.compare("..")) { continue; } // Conditions to check if downloaded fileName is correct: // download filename must be found in fileNamesStringMap (original filename found) ASSERT_TRUE(fileNamesStringMap->get(downloadedName.c_str())); } #ifdef WIN32 // double check a few well known paths ASSERT_EQ(fileSystemAccess->getlocalfstype(LocalPath::fromAbsolutePath("c:")), FS_NTFS); ASSERT_EQ(fileSystemAccess->getlocalfstype(LocalPath::fromAbsolutePath("c:\\")), FS_NTFS); ASSERT_EQ(fileSystemAccess->getlocalfstype(LocalPath::fromAbsolutePath("C:\\")), FS_NTFS); ASSERT_EQ(fileSystemAccess->getlocalfstype(LocalPath::fromAbsolutePath("C:\\Program Files")), FS_NTFS); ASSERT_EQ(fileSystemAccess->getlocalfstype( LocalPath::fromAbsolutePath("c:\\Program Files\\Windows NT")), FS_NTFS); #endif } TEST_F(SdkTest, EscapesReservedCharacters) { // Set up necessary accounts. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const string input = "\r\\/:?\"<>|*"; // Generate expected string. ostringstream osstream; for (auto& character : input) { osstream << "%" << std::hex << std::setfill('0') << std::setw(2) << +character; } // Escape input string. unique_ptr output(megaApi[0]->escapeFsIncompatible(input.c_str(), nullptr)); // Was the string escaped as expected? ASSERT_NE(output.get(), nullptr); ASSERT_STREQ(output.get(), osstream.str().c_str()); } TEST_F(SdkTest, EscapesReservedCharactersOnDownload) { // a/b/c!.txt static const string fileName = "a%2fb%2fc!.txt"; // Set up necessary accounts. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // For convenience. MegaApi* api = megaApi[0].get(); // Get root node. unique_ptr root(api->getRootNode()); ASSERT_NE(root, nullptr); // Create file to upload containing escaped characters. deleteFile(fileName); createFile(fileName); // Upload the file. ASSERT_EQ(API_OK, doStartUpload(0, nullptr, fileName.c_str(), root.get(), nullptr, 0, nullptr, false, false, nullptr)); // Delete the file, we're done with it. deleteFile(fileName); // Check file exists in the cloud. root.reset(api->authorizeNode(root.get())); ASSERT_NE(root, nullptr); MegaNodeList* children = root->getChildren(); ASSERT_NE(children, nullptr); MegaNode* child = children->get(0); ASSERT_NE(child, nullptr); ASSERT_STREQ(child->getName(), "a/b/c!.txt"); // Download the file. string targetPath = path_u8string(fs::current_path()); targetPath.append(1, LocalPath::localPathSeparator_utf8); ASSERT_EQ(API_OK, doStartDownload(0, child, targetPath.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_OVERWRITE /*collisionResolution*/, false /*undelete*/)); // Was the filename correctly escaped on download? ASSERT_TRUE(fileexists(fileName)); deleteFile(fileName); } TEST_F(SdkTest, UnescapesReservedCharacters) { // Set up necessary accounts. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); string input = "\\/:?\"<>|*%5a%21"; string input_unescaped = "\\/:?\"<>|*Z!"; // Escape input string. unique_ptr escaped(megaApi[0]->escapeFsIncompatible(input.c_str(), nullptr)); ASSERT_NE(escaped.get(), nullptr); // Unescape the escaped string. unique_ptr unescaped(megaApi[0]->unescapeFsIncompatible(escaped.get(), nullptr)); // Was the string unescaped as expected? (round trip causes %5a to be unescaped now) ASSERT_NE(unescaped.get(), nullptr); ASSERT_STREQ(input_unescaped.c_str(), unescaped.get()); } TEST_F(SdkTest, UnescapesReservedCharactersOnUpload) { // a/b/c!.txt static const string fileName = "a%2fb%2fc!.txt"; // Set up necessary accounts. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // For convenience. MegaApi* api = megaApi[0].get(); // Get root node. unique_ptr root(api->getRootNode()); ASSERT_NE(root, nullptr); // Create file to upload containing escaped characters. deleteFile(fileName); createFile(fileName); // Upload the file. ASSERT_EQ(API_OK, doStartUpload(0, nullptr, fileName.c_str(), root.get(), nullptr, 0, nullptr, false, false, nullptr)); // Delete the file, we're done with it. deleteFile(fileName); // Check if the file's name was correctly unescaped. root.reset(api->authorizeNode(root.get())); ASSERT_NE(root, nullptr); MegaNodeList* children = root->getChildren(); ASSERT_NE(children, nullptr); MegaNode* child = children->get(0); ASSERT_NE(child, nullptr); ASSERT_STREQ(child->getName(), "a/b/c!.txt"); } TEST_F(SdkTest, RecursiveUploadWithLogout) { LOG_info << "___TEST RecursiveUploadWithLogout___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // this one used to cause a double-delete // make new folders (and files) in the local filesystem - approx 90 fs::path p = fs::current_path() / "uploadme_mega_auto_test_sdk"; if (fs::exists(p)) { fs::remove_all(p); } fs::create_directories(p); ASSERT_TRUE(buildLocalFolders(path_u8string(p).c_str(), "newkid", 3, 2, 10)); string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1, false)) << "Couldnt create " << filename1; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, filename1.c_str(), std::unique_ptr{megaApi[0]->getRootNode()}.get(), path_u8string(p.filename()).c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; // first check that uploading a folder to overwrite a file fails auto uploadListener1 = std::make_shared(megaApi[0].get()); uploadListener1->selfDeleteOnFinalCallback = uploadListener1; MegaUploadOptions folderUploadOptions; folderUploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootForFolder = std::unique_ptr{megaApi[0]->getRootNode()}; const auto folderLocalPath = p.string(); megaApi[0]->startUpload(folderLocalPath, rootForFolder.get(), nullptr, &folderUploadOptions, uploadListener1.get()); ASSERT_EQ(uploadListener1->waitForResult(), API_EEXIST); // remove the file so nothing is in the way anymore ASSERT_EQ(MegaError::API_OK, doDeleteNode(0, std::unique_ptr{ megaApi[0]->getNodeByPath( ((std::string("/") + path_u8string(p.filename()))).c_str())} .get())) << "Cannot delete a test node"; int currentMaxUploadSpeed = megaApi[0]->getMaxUploadSpeed(); ASSERT_EQ(true, megaApi[0]->setMaxUploadSpeed(1)); // set a small value for max upload speed (bytes per second) // start uploading // uploadListener may have to live after this function exits if the logout test below fails auto uploadListener = std::make_shared(megaApi[0].get()); uploadListener->selfDeleteOnFinalCallback = uploadListener; MegaUploadOptions uploadOptions3; uploadOptions3.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootForLogout = std::unique_ptr{megaApi[0]->getRootNode()}; const auto logoutLocalPath = p.string(); megaApi[0]->startUpload(logoutLocalPath, rootForLogout.get(), nullptr, &uploadOptions3, uploadListener.get()); WaitMillisec(500); // logout while the upload (which consists of many transfers) is ongoing gSessionIDs[0].clear(); #ifdef ENABLE_SYNC ASSERT_EQ(API_OK, doRequestLogout(0, false)); #else ASSERT_EQ(API_OK, doRequestLogout(0)); #endif gSessionIDs[0] = "invalid"; int result = uploadListener->waitForResult(); ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE); auto tracker = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session for account " << 0; ASSERT_EQ(true, megaApi[0]->setMaxUploadSpeed(currentMaxUploadSpeed)); // restore previous max upload speed (bytes per second) } TEST_F(SdkTest, RecursiveDownloadWithLogout) { LOG_info << "___TEST RecursiveDownloadWithLogout"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto restorer0 = scopedToPro(*megaApi[0]); ASSERT_EQ(result(restorer0), API_OK); // this one used to cause a double-delete // make new folders (and files) in the local filesystem - approx 130 - we must upload in order to have something to download fs::path uploadpath = fs::current_path() / "uploadme_mega_auto_test_sdk"; fs::path downloadpath = fs::current_path() / "downloadme_mega_auto_test_sdk"; std::error_code ec; fs::remove_all(uploadpath, ec); fs::remove_all(downloadpath, ec); ASSERT_TRUE(!fs::exists(uploadpath)); ASSERT_TRUE(!fs::exists(downloadpath)); fs::create_directories(uploadpath); ASSERT_TRUE(buildLocalFolders(path_u8string(uploadpath).c_str(), "newkid", 3, 2, 10)); out() << " uploading tree so we can download it"; // upload all of those TransferTracker uploadListener(megaApi[0].get()); MegaUploadOptions bulkUploadOptions; bulkUploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; const auto uploadFolderPath = uploadpath.string(); megaApi[0]->startUpload(uploadFolderPath, rootNode.get(), nullptr, &bulkUploadOptions, &uploadListener); ASSERT_EQ(API_OK, uploadListener.waitForResult()); int currentMaxDownloadSpeed = megaApi[0]->getMaxDownloadSpeed(); ASSERT_EQ(true, megaApi[0]->setMaxDownloadSpeed(1)); // set a small value for max download speed (bytes per second) out() << " checking download of folder to overwrite file fails"; ASSERT_TRUE(createFile(path_u8string(downloadpath), false)) << "Couldn't create " << downloadpath << " as a file"; // ok now try the download to overwrite file TransferTracker downloadListener1(megaApi[0].get()); std::unique_ptr nodeToDownload(megaApi[0]->getNodeByPath("/uploadme_mega_auto_test_sdk")); megaApi[0]->startDownload( nodeToDownload.get(), path_u8string(downloadpath).c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener1); ASSERT_TRUE(downloadListener1.waitForResult() == API_EEXIST); fs::remove_all(downloadpath, ec); out() << " downloading tree and logout while it's ongoing"; // ok now try the download TransferTracker downloadListener2(megaApi[0].get()); nodeToDownload.reset(megaApi[0]->getNodeByPath("/uploadme_mega_auto_test_sdk")); megaApi[0]->startDownload( nodeToDownload.get(), path_u8string(downloadpath).c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener2); for (int i = 1000; i-- && !downloadListener2.started; ) WaitMillisec(1); ASSERT_TRUE(downloadListener2.started); ASSERT_TRUE(!downloadListener2.finished); // logout while the download (which consists of many transfers) is ongoing #ifdef ENABLE_SYNC ASSERT_EQ(API_OK, doRequestLogout(0, false)); #else ASSERT_EQ(API_OK, doRequestLogout(0)); #endif gSessionIDs[0] = "invalid"; int result = downloadListener2.waitForResult(); ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE); fs::remove_all(uploadpath, ec); fs::remove_all(downloadpath, ec); auto tracker = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session for account " << 0; ASSERT_EQ(true, megaApi[0]->setMaxDownloadSpeed(currentMaxDownloadSpeed)); // restore previous max download speed (bytes per second) } TEST_F(SdkTest, DuplicatedDownloadTransferInFlight) { const std::string prefix = {"SdkTest, DuplicatedDownloadTransferInFlight"}; LOG_debug << prefix << "[TC1]: Test setup stage"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const std::string filename{"file_to_download.dat"}; constexpr size_t fileSizeBytes = 2 * 1024 * 1024; const sdk_test::LocalTempFile localFile(fs::current_path() / filename, fileSizeBytes); const sdk_test::LocalTempDir localTargetFolder(fs::current_path() / "download_target_folder"); auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << prefix << "Cannot retrieve root node"; LOG_debug << prefix << "[TC2]: Upload source file"; MegaHandle uploadedNodeHandle = UNDEF; const auto [errCode, transferSpeed, transferMeanSpeed] = doStartUploadWithSpeed(0, &uploadedNodeHandle, path_u8string(localFile.getPath()).c_str(), rootNode.get(), nullptr, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/); ASSERT_EQ(API_OK, errCode) << prefix << "Failed to upload: " << filename; ASSERT_NE(UNDEF, uploadedNodeHandle) << prefix << "Invalid uploaded node handle: " << toHandle(uploadedNodeHandle); auto downloadNode = std::unique_ptr{megaApi[0]->getNodeByHandle(uploadedNodeHandle)}; ASSERT_TRUE(downloadNode) << prefix << "Cannot retrieve node to download"; LOG_debug << prefix << "[TC3]: Limit download speed and start first download"; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); TransferTracker firstDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(downloadNode.get(), path_u8string(localTargetFolder.getPath()).c_str(), nullptr, nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &firstDownloadTracker); std::this_thread::sleep_for(std::chrono::seconds{1}); LOG_debug << prefix << "[TC4]: Wait for first download to be in flight"; ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); ASSERT_FALSE(firstDownloadTracker.finished); LOG_debug << prefix << "[TC5]: Start duplicated download to the same destination"; TransferTracker secondDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(downloadNode.get(), path_u8string(localTargetFolder.getPath()).c_str(), nullptr, nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &secondDownloadTracker); ASSERT_EQ(API_EEXIST, secondDownloadTracker.waitForResult()) << prefix << "Unexpected error for duplicated download transfer finished"; LOG_debug << prefix << "[TC6]: Restore download speed and wait for original transfer"; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_EQ(API_OK, firstDownloadTracker.waitForResult()) << prefix << "Download transfer finished with error"; } TEST_F(SdkTest, DuplicatedDownloadTransferInFlightByMetaMac) { const std::string prefix{"SdkTest::DuplicatedDownloadTransferInFlightByMetaMac. "}; LOG_debug << prefix << "[TC1]: Test setup stage"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const std::string filename{"file_to_download.dat"}; const std::string copiedRemoteName{"copied_file_to_download.dat"}; const std::string outputName{"destination_file.bin"}; const std::string localFolderName{"local_target_folder"}; constexpr size_t fileSizeBytes = 2 * 1024 * 1024; const sdk_test::LocalTempFile localFile(fs::current_path() / filename, fileSizeBytes); const sdk_test::LocalTempDir localTargetFolder(fs::current_path() / (localFolderName)); auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << prefix << "Cannot retrieve root node"; LOG_debug << prefix << "[TC2]: Create remote folder for the upload and copied node"; const std::string remoteFolderName{"cloud_target_folder"}; const auto remoteFolderHandle = createFolder(0, remoteFolderName.c_str(), rootNode.get()); ASSERT_NE(UNDEF, remoteFolderHandle) << prefix << "Cannot create remote folder"; auto remoteFolder = std::unique_ptr{megaApi[0]->getNodeByHandle(remoteFolderHandle)}; ASSERT_TRUE(remoteFolder) << prefix << "Cannot retrieve remote folder"; LOG_debug << prefix << "[TC3]: Upload source file"; MegaHandle uploadedNodeHandle = UNDEF; auto uploadResult = doStartUploadWithSpeed(0, &uploadedNodeHandle, path_u8string(localFile.getPath()).c_str(), remoteFolder.get(), nullptr, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/); ASSERT_EQ(API_OK, std::get<0>(uploadResult)) << prefix << "Failed to upload source file"; ASSERT_NE(UNDEF, uploadedNodeHandle) << prefix << "Invalid uploaded node handle"; auto originalNode = std::unique_ptr{megaApi[0]->getNodeByHandle(uploadedNodeHandle)}; ASSERT_TRUE(originalNode) << prefix << "Cannot retrieve original node"; LOG_debug << prefix << "[TC4]: Copy uploaded node to create a second node with the same content(MetaMAC)"; MegaHandle copiedNodeHandle = UNDEF; ASSERT_EQ(API_OK, doCopyNode(0, &copiedNodeHandle, originalNode.get(), remoteFolder.get(), copiedRemoteName.c_str())) << prefix << "Failed to create copied node"; ASSERT_NE(UNDEF, copiedNodeHandle) << prefix << "Invalid copied node handle"; auto copiedNode = std::unique_ptr{megaApi[0]->getNodeByHandle(copiedNodeHandle)}; ASSERT_TRUE(copiedNode) << prefix << "Cannot retrieve copied node"; const std::string targetFolderPath = localTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; const auto startDownloadToSameOutput = [this, &targetFolderPath, &outputName](MegaNode* node, TransferTracker* tracker) { megaApi[0]->startDownload(node, targetFolderPath.c_str(), outputName.c_str(), nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, tracker); }; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); LOG_debug << prefix << "[TC5]: Start first download"; TransferTracker firstDownloadTracker(megaApi[0].get()); startDownloadToSameOutput(originalNode.get(), &firstDownloadTracker); ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); ASSERT_FALSE(firstDownloadTracker.finished); LOG_debug << prefix << "[TC6]: Start second download with different node handle but same MetaMAC"; TransferTracker secondDownloadTracker(megaApi[0].get()); startDownloadToSameOutput(copiedNode.get(), &secondDownloadTracker); ASSERT_EQ(API_EEXIST, secondDownloadTracker.waitForResult()) << prefix << "Same MetaMAC with different node handle should be rejected"; LOG_debug << prefix << "[TC7]: Restore download speed and wait for original transfer"; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_EQ(API_OK, firstDownloadTracker.waitForResult()) << prefix << "Original download transfer finished with error"; } TEST_F(SdkTest, DownloadTransferDeduplicationNonDuplicateScenarios) { const std::string prefix{"SdkTest::DownloadTransferDeduplicationNonDuplicateScenarios. "}; LOG_debug << prefix << "[TC1]: Test setup stage"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); constexpr size_t fileSizeBytes = 2 * 1024 * 1024; const sdk_test::LocalTempFile sharedLocalFile(fs::current_path() / ("shared_file.dat"), fileSizeBytes); const sdk_test::LocalTempFile distinctLocalFile(fs::current_path() / ("distinct_file.dat"), fileSizeBytes); auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << prefix << "Cannot retrieve root node"; LOG_debug << prefix << "[TC2]: Create remote folder and upload shared source file"; const std::string remoteFolderName{"duplicated_downloads_allowed"}; const auto remoteFolderHandle = createFolder(0, remoteFolderName.c_str(), rootNode.get()); ASSERT_NE(UNDEF, remoteFolderHandle) << prefix << "Cannot create remote folder"; auto remoteFolder = std::unique_ptr{megaApi[0]->getNodeByHandle(remoteFolderHandle)}; ASSERT_TRUE(remoteFolder) << prefix << "Cannot retrieve remote folder"; MegaHandle sharedNodeHandle = UNDEF; auto sharedUploadResult = doStartUploadWithSpeed(0, &sharedNodeHandle, path_u8string(sharedLocalFile.getPath()).c_str(), remoteFolder.get(), "shared_remote.dat", ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, false, false, nullptr); ASSERT_EQ(API_OK, std::get<0>(sharedUploadResult)) << prefix << "Failed to upload shared file"; ASSERT_NE(UNDEF, sharedNodeHandle) << prefix << "Invalid shared node handle"; auto sharedNode = std::unique_ptr{megaApi[0]->getNodeByHandle(sharedNodeHandle)}; ASSERT_TRUE(sharedNode) << prefix << "Cannot retrieve shared node"; LOG_debug << prefix << "[TC3]: Copy shared_file node to get a copy with the same content (MetaMAC)"; MegaHandle copiedNodeHandle = UNDEF; ASSERT_EQ( API_OK, doCopyNode(0, &copiedNodeHandle, sharedNode.get(), remoteFolder.get(), "copied_remote.dat")) << prefix << "Failed to create copied node"; ASSERT_NE(UNDEF, copiedNodeHandle) << prefix << "Invalid copied node handle"; auto copiedNode = std::unique_ptr{megaApi[0]->getNodeByHandle(copiedNodeHandle)}; ASSERT_TRUE(copiedNode) << prefix << "Cannot retrieve copied node"; LOG_debug << prefix << "[TC4]: Upload distinct-content file"; MegaHandle distinctNodeHandle = UNDEF; auto distinctUploadResult = doStartUploadWithSpeed(0, &distinctNodeHandle, path_u8string(distinctLocalFile.getPath()).c_str(), remoteFolder.get(), "distinct_remote.dat", ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, false, false, nullptr); ASSERT_EQ(API_OK, std::get<0>(distinctUploadResult)) << prefix << "Failed to upload distinct file"; ASSERT_NE(UNDEF, distinctNodeHandle) << prefix << "Invalid distinct node handle"; auto distinctNode = std::unique_ptr{megaApi[0]->getNodeByHandle(distinctNodeHandle)}; ASSERT_TRUE(distinctNode) << prefix << "Cannot retrieve distinct node"; { LOG_debug << prefix << "[TC5]: Same MetaMAC to different destinations should be allowed"; const sdk_test::LocalTempDir firstTargetFolder(fs::current_path() / ("download_target_folder_allowed_a")); const sdk_test::LocalTempDir secondTargetFolder(fs::current_path() / ("download_target_folder_allowed_b")); const std::string outputName{"shared-output.bin"}; const std::string firstTargetFolderPath = firstTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; const std::string secondTargetFolderPath = secondTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); TransferTracker firstDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(sharedNode.get(), firstTargetFolderPath.c_str(), outputName.c_str(), nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &firstDownloadTracker); ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); TransferTracker secondDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(copiedNode.get(), secondTargetFolderPath.c_str(), outputName.c_str(), nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &secondDownloadTracker); ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_EQ(API_OK, firstDownloadTracker.waitForResult()) << prefix << "Same MetaMAC to first destination failed"; ASSERT_EQ(API_OK, secondDownloadTracker.waitForResult()) << prefix << "Same MetaMAC to second destination failed"; ASSERT_TRUE(fs::exists(firstTargetFolder.getPath() / outputName)); ASSERT_TRUE(fs::exists(secondTargetFolder.getPath() / outputName)); } { LOG_debug << prefix << "[TC6]: Same node with different custom names should be allowed"; const sdk_test::LocalTempDir localTargetFolder(fs::current_path() / ("download_target_folder_custom_names")); const std::string targetFolderPath = localTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); TransferTracker firstDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(sharedNode.get(), targetFolderPath.c_str(), "custom-a.bin", nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &firstDownloadTracker); ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); TransferTracker secondDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(sharedNode.get(), targetFolderPath.c_str(), "custom-b.bin", nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &secondDownloadTracker); ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_EQ(API_OK, firstDownloadTracker.waitForResult()) << prefix << "First customName download failed"; ASSERT_EQ(API_OK, secondDownloadTracker.waitForResult()) << prefix << "Second customName download failed"; ASSERT_TRUE(fs::exists(localTargetFolder.getPath() / "custom-a.bin")); ASSERT_TRUE(fs::exists(localTargetFolder.getPath() / "custom-b.bin")); } { LOG_debug << prefix << "[TC7]: Different-content nodes should not be treated as duplicates"; const sdk_test::LocalTempDir localTargetFolder(fs::current_path() / ("download_target_folder_distinct_nodes")); const std::string targetFolderPath = localTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); TransferTracker firstDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(sharedNode.get(), targetFolderPath.c_str(), nullptr, nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &firstDownloadTracker); ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); TransferTracker secondDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(distinctNode.get(), targetFolderPath.c_str(), nullptr, nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &secondDownloadTracker); ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_EQ(API_OK, firstDownloadTracker.waitForResult()) << prefix << "First distinct-content download failed"; ASSERT_EQ(API_OK, secondDownloadTracker.waitForResult()) << prefix << "Second distinct-content download failed"; ASSERT_TRUE(fs::exists(localTargetFolder.getPath() / sharedNode->getName())); ASSERT_TRUE(fs::exists(localTargetFolder.getPath() / distinctNode->getName())); } } TEST_F(SdkTest, DuplicatedDownloadTransferAfterRelogin) { const std::string prefix{"SdkTest::DuplicatedDownloadTransferAfterRelogin. "}; LOG_debug << prefix << "[TC1]: Test setup stage"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const std::string filename{"relogin_file_to_download.dat"}; const std::string outputName{"relogin-output.bin"}; constexpr size_t fileSizeBytes = 2 * 1024 * 1024; const sdk_test::LocalTempFile localFile(fs::current_path() / filename, fileSizeBytes); const sdk_test::LocalTempDir localTargetFolder(fs::current_path() / ("download_target_folder_relogin")); const std::string targetFolderPath = localTargetFolder.getPath().string() + LocalPath::localPathSeparator_utf8; auto rootNode = std::unique_ptr{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << prefix << "Cannot retrieve root node"; LOG_debug << prefix << "[TC2]: Create remote folder and upload source file"; const std::string remoteFolderName{"duplicated_downloads_relogin"}; const auto remoteFolderHandle = createFolder(0, remoteFolderName.c_str(), rootNode.get()); ASSERT_NE(UNDEF, remoteFolderHandle) << prefix << "Cannot create remote folder"; auto remoteFolder = std::unique_ptr{megaApi[0]->getNodeByHandle(remoteFolderHandle)}; ASSERT_TRUE(remoteFolder) << prefix << "Cannot retrieve remote folder"; MegaHandle uploadedNodeHandle = UNDEF; auto uploadResult = doStartUploadWithSpeed(0, &uploadedNodeHandle, path_u8string(localFile.getPath()).c_str(), remoteFolder.get(), nullptr, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, false, false, nullptr); ASSERT_EQ(API_OK, std::get<0>(uploadResult)) << prefix << "Failed to upload source file"; ASSERT_NE(UNDEF, uploadedNodeHandle) << prefix << "Invalid uploaded node handle"; auto downloadNode = std::unique_ptr{megaApi[0]->getNodeByHandle(uploadedNodeHandle)}; ASSERT_TRUE(downloadNode) << prefix << "Cannot retrieve node to download"; LOG_debug << prefix << "[TC3]: Start first download and leave it in flight"; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); TransferTracker firstDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(downloadNode.get(), targetFolderPath.c_str(), outputName.c_str(), nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &firstDownloadTracker); LOG_debug << prefix << "[TC4]: Wait for the first download to be in flight"; ASSERT_TRUE(WaitFor( [&firstDownloadTracker]() { return firstDownloadTracker.started.load(); }, defaultTimeout * 1000)); ASSERT_FALSE(firstDownloadTracker.finished); LOG_debug << prefix << "[TC5]: Dump session and perform local logout"; std::unique_ptr session(dumpSession()); ASSERT_TRUE(session) << prefix << "Cannot dump session"; ASSERT_NO_FATAL_FAILURE(locallogout()); LOG_debug << prefix << "[TC6]: Verify in-flight transfer is interrupted by logout"; const int interruptedResult = firstDownloadTracker.waitForResult(); ASSERT_TRUE(interruptedResult == API_EACCESS || interruptedResult == API_EINCOMPLETE) << prefix << "Interrupted download returned unexpected result: " << interruptedResult; LOG_debug << prefix << "[TC7]: Resume session and fetch nodes"; ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(1)); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); LOG_debug << prefix << "[TC8]: Wait for the resumed download transfer to appear"; ASSERT_TRUE(WaitFor( [this]() { auto transfers = std::unique_ptr{ megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)}; return transfers && transfers->size() > 0; }, defaultTimeout * 1000)) << prefix << "No resumed download transfer was observed after relogin"; downloadNode.reset(megaApi[0]->getNodeByHandle(uploadedNodeHandle)); ASSERT_TRUE(downloadNode) << prefix << "Cannot retrieve node after relogin"; LOG_debug << prefix << "[TC9]: Start duplicate download after relogin"; TransferTracker duplicatedDownloadTracker(megaApi[0].get()); megaApi[0]->startDownload(downloadNode.get(), targetFolderPath.c_str(), outputName.c_str(), nullptr, false, nullptr, MegaTransfer::COLLISION_CHECK_FINGERPRINT, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, false, &duplicatedDownloadTracker); LOG_debug << prefix << "[TC10]: Verify duplicate download is rejected after relogin"; ASSERT_EQ(API_EEXIST, duplicatedDownloadTracker.waitForResult()) << prefix << "Duplicated download should be rejected after relogin too"; LOG_debug << prefix << "[TC11]: Allow resumed transfer to finish"; ASSERT_TRUE(megaApi[0]->setMaxDownloadSpeed(-1)); ASSERT_TRUE(WaitFor( [this]() { auto transfers = std::unique_ptr{ megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)}; return !transfers || transfers->size() == 0; }, defaultTimeout * 1000)) << prefix << "Resumed download did not complete within the expected time"; } TEST_F(SdkTest, QueryAds) { LOG_info << "___TEST QueryAds"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr tr = asyncQueryAds(0, MegaApi::ADS_FORCE_ADS, INVALID_HANDLE); ASSERT_EQ(API_OK, tr->waitForResult()) << "Query Ads failed"; } TEST_F(SdkTest, FetchAds) { LOG_info << "___TEST FetchAds"; LOG_debug << "\t# Test suite 1: Fetching ads with non-ads account"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr stringList = std::unique_ptr(MegaStringList::createInstance()); std::unique_ptr tr = asyncFetchAds(0, MegaApi::ADS_FORCE_ADS, stringList.get(), INVALID_HANDLE); ASSERT_EQ(API_EARGS, tr->waitForResult()) << "Fetch Ads succeeded with invalid arguments"; // Test the invalid Ad code const std::string dummyAd{"dummyAdUnit"}; stringList->add(dummyAd.c_str()); tr = asyncFetchAds(0, MegaApi::ADS_FORCE_ADS, stringList.get(), INVALID_HANDLE); ASSERT_EQ(API_OK, tr->waitForResult()) << "Fetch Ads request failed when it wasn't expected"; ASSERT_TRUE(tr->request); auto adsLink = tr->request->getMegaStringMap(); ASSERT_TRUE(adsLink && adsLink->size() == 1); ASSERT_STREQ(adsLink->get(dummyAd.c_str()), "-9") << "Fetch Ads should have received -9 for dummy Ad case"; tr = asyncQueryAds(0, MegaApi::ADS_DEFAULT, INVALID_HANDLE); ASSERT_EQ(API_OK, tr->waitForResult()) << "Query Ads request failed when it wasn't expected"; const int showAd = tr->request->getNumDetails(); LOG_debug << "Account 0 " << megaApi[0]->getMyUserHandle() << " (" << megaApi[0]->getMyEmail() << ") Show Ads (QueryAds, 0- Should show Ads, 1-Should not show ads): " << showAd; if (showAd == 0) { // Show Ads const char valiAdSlot[] = "ANDFB"; stringList.reset(MegaStringList::createInstance()); stringList->add(valiAdSlot); tr = asyncFetchAds(0, MegaApi::ADS_DEFAULT, stringList.get(), INVALID_HANDLE); ASSERT_EQ(API_OK, tr->waitForResult()) << "Fetch Ads failed when it was expected to receive Ads"; const MegaStringMap* ads = tr->request->getMegaStringMap(); ASSERT_TRUE(ads) << "Fetch Ads should have received Ads link"; } else { // Do not show ad - Try fetching ad with default flag tr = asyncFetchAds(0, MegaApi::ADS_DEFAULT, stringList.get(), INVALID_HANDLE); ASSERT_EQ(API_ENOENT, tr->waitForResult()) << "Fetch Ads didn't fail when it was expected to (correct Ad case)"; const MegaStringMap* ads = tr->request->getMegaStringMap(); ASSERT_FALSE(ads) << "Fetch Ads should have been nullptr to expected error code in `request`"; } stringList.reset(MegaStringList::createInstance()); stringList->add(dummyAd.c_str()); tr = asyncFetchAds(0, MegaApi::ADS_DEFAULT, stringList.get(), INVALID_HANDLE); const auto ab_adse = megaApi[0]->getFlag("adse", false); const auto ab_adsi = megaApi[0]->getFlag("adsi", false); LOG_debug << "Account 0 " << megaApi[0]->getMyUserHandle() << " (" << megaApi[0]->getMyEmail() << ") ab_adse: " << ab_adse->getGroup() << " ab_adsi: " << ab_adsi->getGroup(); const bool isUserAllowedToFetchAds = ab_adse->getGroup() > 0u || ab_adsi->getGroup() > 0u; // Check if ads are enable for the account by default or through AB test/feature flags. if (isUserAllowedToFetchAds || (showAd == 0)) { ASSERT_EQ(API_OK, tr->waitForResult()) << "Fetch Ads request failed when it wasn't expected"; ASSERT_TRUE(tr->request); auto ads = tr->request->getMegaStringMap(); ASSERT_TRUE(ads && ads->size() == 1); ASSERT_STREQ(ads->get(dummyAd.c_str()), "-9") << "Fetch Ads should have received -9 for dummy Ad case"; } else { ASSERT_EQ(API_ENOENT, tr->waitForResult()) << "Fetch Ads didn't fail when it was expected to (dummy Ad case)"; const MegaStringMap* ads = tr->request->getMegaStringMap(); ASSERT_FALSE(ads) << "Fetch Ads should have been nullptr due to expected error code `request`"; const char valiAdSlot[] = "ANDFB"; stringList.reset(MegaStringList::createInstance()); stringList->add(valiAdSlot); tr = asyncFetchAds(0, MegaApi::ADS_DEFAULT, stringList.get(), INVALID_HANDLE); ASSERT_EQ(API_ENOENT, tr->waitForResult()) << "Fetch Ads didn't fail when it was expected to (correct Ad case)"; ads = tr->request->getMegaStringMap(); ASSERT_FALSE(ads) << "Fetch Ads should have been nullptr to expected error code in `request`"; } // TODO: LOG_debug << "\t# Test suite 2: Fetching ads with containing-ads account"; } void cleanUp(::mega::MegaApi* megaApi, const fs::path &basePath) { #ifdef ENABLE_SYNC unique_ptr allSyncs{ megaApi->getSyncs() }; for (int i = 0; i < allSyncs->size(); ++i) { RequestTracker rt1(megaApi); megaApi->removeSync(allSyncs->get(i)->getBackupId(), &rt1); ASSERT_EQ(API_OK, rt1.waitForResult()); if (allSyncs->get(i)->getType() == MegaSync::TYPE_BACKUP) { RequestTracker rt2(megaApi); megaApi->moveOrRemoveDeconfiguredBackupNodes(allSyncs->get(i)->getMegaHandle(), INVALID_HANDLE, &rt2); const string backupName = allSyncs->get(i)->getName() ? allSyncs->get(i)->getName() : ""; const auto res = rt2.waitForResult(); LOG_err << "cleanUp: Could not remove Backup, " << backupName << "(" << Base64Str(allSyncs->get(i)->getBackupId()) << "). ErrCode(" << MegaError::getErrorString(res) << ")"; if (res != API_OK && res != API_ENOENT && res != API_EARGS) { ASSERT_EQ(API_OK, res) << "cleanUp: Could not remove Backup, " << backupName << "(" << Base64Str(allSyncs->get(i)->getBackupId()) << ")"; } } } #endif std::unique_ptr baseNode{ megaApi->getNodeByPath((std::string("/") + path_u8string(basePath)).c_str())}; if (baseNode) { RequestTracker removeTracker(megaApi); megaApi->remove(baseNode.get(), &removeTracker); ASSERT_EQ(API_OK, removeTracker.waitForResult()); } std::unique_ptr binNode{megaApi->getNodeByPath("//bin")}; if (binNode) { unique_ptr cs(megaApi->getChildren(binNode.get())); for (int i = 0; i < (cs ? cs->size() : 0); ++i) { RequestTracker removeTracker(megaApi); megaApi->remove(cs->get(i), &removeTracker); ASSERT_EQ(API_OK, removeTracker.waitForResult()); } } std::error_code ignoredEc; fs::remove_all(basePath, ignoredEc); } #ifdef ENABLE_SYNC using sdk_test::waitForSyncState; TEST_F(SdkTest, SyncBasicOperations) { // What we are going to test here: // - add syncs // - add sync that fails // - disable a sync // - disable a sync that fails // - disable a disabled sync // - Enable a sync // - Enable a sync that fails // - Enable an enabled sync // - Remove a sync // - Remove a sync that doesn't exist // - Remove a removed sync LOG_info << "___TEST SyncBasicOperations___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); fs::path basePath = "SyncBasicOperations"; std::string syncFolder1 = "sync1"; std::string syncFolder2 = "sync2"; std::string syncFolder3 = "sync3"; fs::path basePath1 = basePath / syncFolder1; fs::path basePath2 = basePath / syncFolder2; fs::path basePath3 = basePath / syncFolder3; const auto localPath1 = fs::current_path() / basePath1; const auto localPath2 = fs::current_path() / basePath2; const auto localPath3 = fs::current_path() / basePath3; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); // Create local directories and a files. fs::create_directories(localPath1); ASSERT_TRUE(createFile(path_u8string((localPath1 / "fileTest1")), false)); fs::create_directories(localPath2); ASSERT_TRUE(createFile(path_u8string((localPath2 / "fileTest2")), false)); fs::create_directories(localPath3); LOG_verbose << "SyncBasicOperations : Creating the remote folders to be synced to."; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); // Sync 1 auto nh = createFolder(0, syncFolder1.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode1(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode1.get(), nullptr); // Sync 2 nh = createFolder(0, syncFolder2.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode2(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode2.get(), nullptr); // Sync 3 nh = createFolder(0, syncFolder3.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode3(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode3.get(), nullptr); LOG_verbose << "SyncRemoveRemoteNode : Add syncs"; // Sync 1 const auto lp1 = path_u8string(localPath1); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp1.c_str(), nullptr, remoteBaseNode1->getHandle(), nullptr)) << "API Error adding a new sync"; ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode1.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); // Sync2 const auto lp2 = path_u8string(localPath2); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp2.c_str(), nullptr, remoteBaseNode2->getHandle(), nullptr)) << "API Error adding a new sync"; ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); std::unique_ptr sync2 = waitForSyncState(megaApi[0].get(), remoteBaseNode2.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync2 && sync2->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); handle backupId = sync->getBackupId(); handle backupId2 = sync2->getBackupId(); LOG_verbose << "SyncRemoveRemoteNode : Add syncs that fail"; { const auto lp3 = path_u8string(localPath3); ASSERT_EQ(API_EEXIST, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp3.c_str(), nullptr, remoteBaseNode1->getHandle(), nullptr)); // Remote node is currently synced. ASSERT_EQ(MegaSync::ACTIVE_SYNC_SAME_PATH, mApi[0].lastSyncError); ASSERT_EQ(API_EEXIST, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp3.c_str(), nullptr, remoteBaseNode2->getHandle(), nullptr)); // Remote node is currently synced. ASSERT_EQ(MegaSync::ACTIVE_SYNC_SAME_PATH, mApi[0].lastSyncError); const auto lp4 = path_u8string(localPath3 / fs::path("xxxyyyzzz")); ASSERT_EQ(API_ENOENT, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp4.c_str(), nullptr, remoteBaseNode3->getHandle(), nullptr)); // Local resource doesn't exists. ASSERT_EQ(MegaSync::LOCAL_PATH_UNAVAILABLE, mApi[0].lastSyncError); } LOG_verbose << "SyncRemoveRemoteNode : Disable a sync"; // Sync 1 ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, backupId, MegaSync::RUNSTATE_DISABLED)); sync = waitForSyncState(megaApi[0].get(), remoteBaseNode1.get(), MegaSync::RUNSTATE_DISABLED, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_DISABLED); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); // Sync 2 ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, sync2->getBackupId(), MegaSync::RUNSTATE_DISABLED)); sync2 = waitForSyncState(megaApi[0].get(), remoteBaseNode2.get(), MegaSync::RUNSTATE_DISABLED, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync2 && sync2->getRunState() == MegaSync::RUNSTATE_DISABLED); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); LOG_verbose << "SyncRemoveRemoteNode : Disable disabled syncs"; ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, sync->getBackupId(), MegaSync::RUNSTATE_DISABLED)); // Currently disabled. ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, backupId, MegaSync::RUNSTATE_DISABLED)); // Currently disabled. LOG_verbose << "SyncRemoveRemoteNode : Enable Syncs"; // Sync 1 ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, backupId, MegaSync::RUNSTATE_RUNNING)); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); sync = waitForSyncState(megaApi[0].get(), remoteBaseNode1.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); // Sync 2 ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, sync2->getBackupId(), MegaSync::RUNSTATE_RUNNING)); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); sync2 = waitForSyncState(megaApi[0].get(), remoteBaseNode2.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync2 && sync2->getRunState() == MegaSync::RUNSTATE_RUNNING); LOG_verbose << "SyncRemoveRemoteNode : Enable syncs that fail"; { ASSERT_EQ(API_ENOENT, synchronousSetSyncRunState(0, static_cast(999999), MegaSync::RUNSTATE_RUNNING)); // Hope it doesn't exist. ASSERT_EQ(MegaSync::UNKNOWN_ERROR, mApi[0].lastSyncError); // MegaApi.h specifies that this contains the error code (not the tag) ASSERT_EQ(API_OK, synchronousSetSyncRunState( 0, sync2->getBackupId(), MegaSync::RUNSTATE_RUNNING)); // Currently enabled, already running. ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); // since the sync is active, we should see its real state, and it should not have had any error code stored in it } LOG_verbose << "SyncRemoveRemoteNode : Remove Syncs"; // Sync 1 ASSERT_EQ(API_OK, synchronousRemoveSync(0, backupId)) << "API Error removing the sync"; sync.reset(megaApi[0]->getSyncByNode(remoteBaseNode1.get())); ASSERT_EQ(nullptr, sync.get()); // Sync 2 ASSERT_EQ(API_OK, synchronousRemoveSync(0, sync2->getBackupId())) << "API Error removing the sync"; // Keep sync2 not updated. Will be used later to test another removal attemp using a non-updated object. LOG_verbose << "SyncRemoveRemoteNode : Remove Syncs that fail"; { ASSERT_EQ( API_ENOENT, synchronousRemoveSync(0, static_cast(9999999))); // Hope id doesn't exist ASSERT_EQ(API_ENOENT, synchronousRemoveSync(0, backupId)); // already removed. ASSERT_EQ(API_ENOENT, synchronousRemoveSync(0, backupId2)); // already removed. } ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); } TEST_F(SdkTest, SyncIsNodeSyncable) { LOG_info << "___TEST SyncIsNodeSyncable___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); fs::path basePath = "SyncIsNodeSyncable"; std::string syncFolder1 = "sync1"; std::string syncFolder2 = "sync2"; // <-- synced std::string syncFolder2a = "2a"; std::string syncFolder2b = "2b"; std::string syncFolder3 = "sync3"; fs::path basePath1 = basePath / syncFolder1; fs::path basePath2 = basePath / syncFolder2; fs::path basePath2a = basePath / syncFolder2 / syncFolder2a; fs::path basePath2b = basePath / syncFolder2 / syncFolder2b; fs::path basePath3 = basePath / syncFolder3; const auto localPath1 = fs::current_path() / basePath1; const auto localPath2 = fs::current_path() / basePath2; const auto localPath2a = fs::current_path() / basePath2a; const auto localPath2b = fs::current_path() / basePath2b; const auto localPath3 = fs::current_path() / basePath3; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); // Create local directories and a files. fs::create_directories(localPath1); ASSERT_TRUE(createFile(path_u8string((localPath1 / "fileTest1")), false)); fs::create_directories(localPath2); ASSERT_TRUE(createFile(path_u8string((localPath2 / "fileTest2")), false)); fs::create_directories(localPath2a); ASSERT_TRUE(createFile(path_u8string((localPath2a / "fileTest2a")), false)); fs::create_directories(localPath2b); ASSERT_TRUE(createFile(path_u8string((localPath2b / "fileTest2b")), false)); fs::create_directories(localPath3); LOG_verbose << "Sync.IsNodeSyncable: Creating the remote folders to be synced to."; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); // SyncIsNodeSyncable MegaHandle nh = createFolder(0, basePath.string().c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); // Sync 1 nh = createFolder(0, syncFolder1.c_str(), remoteBaseNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode1(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode1.get(), nullptr); // Sync 2 nh = createFolder(0, syncFolder2.c_str(), remoteBaseNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode2(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode2.get(), nullptr); // Sync 3 nh = createFolder(0, syncFolder3.c_str(), remoteBaseNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode3(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode3.get(), nullptr); // Sync 2a nh = createFolder(0, syncFolder2a.c_str(), remoteBaseNode2.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode2a(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode2a.get(), nullptr); // Sync 2b nh = createFolder(0, syncFolder2b.c_str(), remoteBaseNode2.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode2b(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode2b.get(), nullptr); MegaHandle handle2 = INVALID_HANDLE; int err = synchronousSyncFolder(0, &handle2, MegaSync::SyncType::TYPE_TWOWAY, path_u8string(localPath2).c_str(), "sync test", remoteBaseNode2.get()->getHandle(), nullptr); /// ASSERT_TRUE(err == API_OK) << "Backup folder 2 failed (error: " << err << ")"; std::unique_ptr node3(megaApi[0].get()->getNodeByPath((string("/") + Utils::replace(basePath3.string(), '\\', '/')).c_str())); ASSERT_NE(node3.get(), (MegaNode*)NULL); unique_ptr error(megaApi[0]->isNodeSyncableWithError(node3.get())); ASSERT_EQ(error->getErrorCode(), API_OK); ASSERT_EQ(error->getSyncError(), NO_SYNC_ERROR); std::unique_ptr node2a(megaApi[0].get()->getNodeByPath((string("/") + Utils::replace(basePath2a.string(), '\\', '/')).c_str())); // on Windows path separator is \ but API takes / ASSERT_NE(node2a.get(), (MegaNode*)NULL); error.reset(megaApi[0]->isNodeSyncableWithError(node2a.get())); ASSERT_EQ(error->getErrorCode(), API_EEXIST); ASSERT_EQ(error->getSyncError(), ACTIVE_SYNC_ABOVE_PATH); std::unique_ptr baseNode(megaApi[0].get()->getNodeByPath((string("/") + Utils::replace(basePath.string(), '\\', '/')).c_str())); // on Windows path separator is \ but API takes / ASSERT_NE(baseNode.get(), (MegaNode*)NULL); error.reset(megaApi[0]->isNodeSyncableWithError(baseNode.get())); ASSERT_EQ(error->getErrorCode(), API_EEXIST); ASSERT_EQ(error->getSyncError(), ACTIVE_SYNC_BELOW_PATH); } TEST_F(SdkTest, SyncResumptionAfterFetchNodes) { LOG_info << "___TEST SyncResumptionAfterFetchNodes___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // This test has several issues: // 1. Remote nodes may not be committed to the sctable database in time for fetchnodes which // then fails adding syncs because the remotes are missing. For this reason we wait until // we receive the EVENT_COMMIT_DB event after transferring the nodes. // 2. Syncs are deleted some time later leading to error messages (like local fingerprint mismatch) // if we don't wait for long enough after we get called back. A sync only gets flagged but // is deleted later. const std::string session = unique_ptr(dumpSession()).get(); const fs::path basePath = "SyncResumptionAfterFetchNodes"; const auto sync1Path = fs::current_path() / basePath / "sync1"; // stays active const auto sync2Path = fs::current_path() / basePath / "sync2"; // will be made inactive const auto sync3Path = fs::current_path() / basePath / "sync3"; // will be deleted const auto sync4Path = fs::current_path() / basePath / "sync4"; // stays active ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); SyncListener syncListener0; MegaListenerDeregisterer mld1(megaApi[0].get(), &syncListener0); fs::create_directories(sync1Path); fs::create_directories(sync2Path); fs::create_directories(sync3Path); fs::create_directories(sync4Path); PerApi& target = mApi[0]; target.resetlastEvent(); // transfer the folder and its subfolders TransferTracker uploadListener(megaApi[0].get()); MegaUploadOptions syncUploadOptions; syncUploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto syncRoot = std::unique_ptr(megaApi[0]->getRootNode()); const auto basePathStr = basePath.string(); megaApi[0]->startUpload(basePathStr, syncRoot.get(), nullptr, &syncUploadOptions, &uploadListener); ASSERT_EQ(API_OK, uploadListener.waitForResult()); // loop until we get a commit to the sctable to ensure we cached the new remote nodes ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 10000)); auto megaNode = [this, &basePath](const std::string& p) { const auto path = "/" + path_u8string(basePath) + "/" + p; return std::unique_ptr{megaApi[0]->getNodeByPath(path.c_str())}; }; auto syncFolder = [this, &megaNode](const fs::path& p) -> handle { RequestTracker syncTracker(megaApi[0].get()); auto node = megaNode(path_u8string(p.filename())); megaApi[0]->syncFolder(MegaSync::TYPE_TWOWAY, path_u8string(p).c_str(), nullptr, node ? node->getHandle() : INVALID_HANDLE, nullptr, &syncTracker); EXPECT_EQ(API_OK, syncTracker.waitForResult()); return syncTracker.request->getParentHandle(); }; auto disableSyncByBackupId = [this](handle backupId) { RequestTracker syncTracker(megaApi[0].get()); megaApi[0]->setSyncRunState(backupId, MegaSync::RUNSTATE_DISABLED, &syncTracker); ASSERT_EQ(API_OK, syncTracker.waitForResult()); }; auto resumeSyncByBackupId = [this](handle backupId) { RequestTracker syncTracker(megaApi[0].get()); megaApi[0]->setSyncRunState(backupId, MegaSync::RUNSTATE_RUNNING, &syncTracker); ASSERT_EQ(API_OK, syncTracker.waitForResult()); }; auto removeSyncByBackupId = [this](handle backupId) { RequestTracker syncTracker(megaApi[0].get()); megaApi[0]->removeSync(backupId, &syncTracker); ASSERT_EQ(API_OK, syncTracker.waitForResult()); }; auto checkSyncOK = [this, &megaNode](const fs::path& p) { auto node = megaNode(path_u8string(p.filename())); //return std::unique_ptr{megaApi[0]->getSyncByNode(node.get())} != nullptr; //disabled syncs are not OK but foundable LOG_verbose << "checkSyncOK " << path_u8string(p.filename()) << " node found: " << bool(node); std::unique_ptr sync{megaApi[0]->getSyncByNode(node.get())}; LOG_verbose << "checkSyncOK " << path_u8string(p.filename()) << " sync found: " << bool(sync); if (!sync) return false; LOG_verbose << "checkSyncOK " << path_u8string(p.filename()) << " sync is: " << sync->getLocalFolder(); LOG_verbose << "checkSyncOK " << path_u8string(p.filename()) << " runState: " << sync->getRunState(); return sync->getRunState() == MegaSync::RUNSTATE_RUNNING; }; auto checkSyncDisabled = [this, &megaNode](const fs::path& p) { auto node = megaNode(path_u8string(p.filename())); std::unique_ptr sync{megaApi[0]->getSyncByNode(node.get())}; if (!sync) return false; return sync->getRunState() == MegaSync::RUNSTATE_DISABLED; }; auto reloginViaSession = [this, &session, &syncListener0]() { locallogout(); // only logs out 0 syncListener0.clear(); //loginBySessionId(0, session); auto tracker = asyncRequestFastLogin(0, session.c_str()); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session for account " << 0; }; LOG_verbose << " SyncResumptionAfterFetchNodes : syncying folders"; handle backupId1 = syncFolder(sync1Path); handle backupId2 = syncFolder(sync2Path); handle backupId3 = syncFolder(sync3Path); handle backupId4 = syncFolder(sync4Path); ASSERT_TRUE(checkSyncOK(sync1Path)); ASSERT_TRUE(checkSyncOK(sync2Path)); ASSERT_TRUE(checkSyncOK(sync3Path)); ASSERT_TRUE(checkSyncOK(sync4Path)); LOG_verbose << " SyncResumptionAfterFetchNodes : disabling sync 2"; disableSyncByBackupId(backupId2); LOG_verbose << " SyncResumptionAfterFetchNodes : disabling sync 4"; disableSyncByBackupId(backupId4); LOG_verbose << " SyncResumptionAfterFetchNodes : removing sync"; removeSyncByBackupId(backupId3); // wait for the sync removals to actually take place std::this_thread::sleep_for(std::chrono::seconds{3}); ASSERT_TRUE(checkSyncOK(sync1Path)); ASSERT_TRUE(checkSyncDisabled(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_TRUE(checkSyncDisabled(sync4Path)); reloginViaSession(); ASSERT_FALSE(checkSyncOK(sync1Path)); ASSERT_FALSE(checkSyncOK(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_FALSE(checkSyncOK(sync4Path)); target.resetlastEvent(); fetchnodes(0, maxTimeout); // auto-resumes two active syncs ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_SYNCS_RESTORED); }, 10000)); WaitMillisec(1000); // give them a chance to start on the sync thread ASSERT_TRUE(checkSyncOK(sync1Path)); ASSERT_FALSE(checkSyncOK(sync2Path)); ASSERT_TRUE(checkSyncDisabled(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_FALSE(checkSyncOK(sync4Path)); ASSERT_TRUE(checkSyncDisabled(sync4Path)); // check if we can still resume manually LOG_verbose << " SyncResumptionAfterFetchNodes : resuming syncs"; resumeSyncByBackupId(backupId2); resumeSyncByBackupId(backupId4); ASSERT_TRUE(checkSyncOK(sync1Path)); ASSERT_TRUE(checkSyncOK(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_TRUE(checkSyncOK(sync4Path)); // check if resumeSync re-activated the sync reloginViaSession(); ASSERT_FALSE(checkSyncOK(sync1Path)); ASSERT_FALSE(checkSyncOK(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_FALSE(checkSyncOK(sync4Path)); target.resetlastEvent(); fetchnodes(0, maxTimeout); // auto-resumes three active syncs ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_SYNCS_RESTORED); }, 10000)); WaitMillisec(1000); // give them a chance to start on the sync thread ASSERT_TRUE(checkSyncOK(sync1Path)); ASSERT_TRUE(checkSyncOK(sync2Path)); ASSERT_FALSE(checkSyncOK(sync3Path)); ASSERT_TRUE(checkSyncOK(sync4Path)); LOG_verbose << " SyncResumptionAfterFetchNodes : removing syncs"; removeSyncByBackupId(backupId1); removeSyncByBackupId(backupId2); removeSyncByBackupId(backupId4); // wait for the sync removals to actually take place std::this_thread::sleep_for(std::chrono::seconds{5}); ASSERT_FALSE(syncListener0.hasAnyErrors()); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); } TEST_F(SdkTest, MidSessionEtoomanyWithSync) { LOG_info << "___TEST MidSessionEtoomanyWithSync___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); fs::path basePath = "MidSessionEtoomanyWithSync"; std::string syncFolder = "sync"; fs::path syncPath = basePath / syncFolder; const auto localPath = fs::current_path() / syncPath; fs::create_directories(localPath); // Creating the remote folder to be synced to std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, syncFolder.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folder"; std::unique_ptr remoteSyncNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteSyncNode.get(), nullptr); // Start the sync const auto lp = path_u8string(localPath); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp.c_str(), nullptr, remoteSyncNode->getHandle(), nullptr)) << "API Error adding a new sync"; ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteSyncNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); // Create a local folder and wait for the propagation of the change mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, mApi[0].nodeUpdated); std::string folderName = "new-folder-for-test"; const auto folderPath = fs::current_path() / syncPath / folderName; fs::create_directories(folderPath); ASSERT_TRUE(waitForResponse(&mApi[0].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[0].nodeUpdated = false; // Check that the new remote folder has been created std::unique_ptr remoteFolderNode(megaApi[0]->getNodeByPath(folderName.c_str(), remoteSyncNode.get())); ASSERT_NE(remoteFolderNode.get(), nullptr); nh = remoteFolderNode->getHandle(); fs::path localFolderPath = syncPath / folderName; ASSERT_EQ(fs::exists(localFolderPath), true); // Secondary instance with the same account to force an ETOOMANY action packet const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(2); megaApi.resize(2); configureTestInstance(1, email, pass); // The secondary instance needs to use staging to send a devcommand megaApi[1]->changeApiUrl("https://staging.api.mega.co.nz/"); auto loginTracker = std::make_unique(megaApi[1].get()); megaApi[1]->login(email.c_str(), pass.c_str(), loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account " << email; PerApi& target = mApi[0]; target.resetlastEvent(); auto devCommandTracker = std::make_unique(megaApi[1].get()); megaApi[1]->sendDevCommand("fr", nullptr, devCommandTracker.get()); auto errorCode = devCommandTracker->waitForResult(); ASSERT_TRUE(errorCode == API_OK || errorCode == API_EACCESS) << " Error in devcommand " << errorCode; if (errorCode == API_EACCESS) { LOG_warn << "Devcommand not authorized for your IP, using fetchnodes() instead"; megaApi[0]->fetchNodes(); } else { LOG_verbose << "Devcommand succeeded"; ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_RELOADING); }, 10000)); } // The standard timeout for a fetchnodes is currently 5 minutes (see SdkTest::fetchnodes) ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 300000)); // Check that the synced node is still there after the reload std::unique_ptr previousFolder(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(previousFolder != nullptr); // Delete the local folder and wait for the propagation of the change mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nh, MegaNode::CHANGE_TYPE_PARENT, mApi[0].nodeUpdated); deleteFolder(path_u8string(folderPath)); ASSERT_TRUE(waitForResponse(&mApi[0].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[0].nodeUpdated = false; // Check that the sync is still active sync = waitForSyncState(megaApi[0].get(), remoteSyncNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); // Check that the deletion of the node has been propagated to the remote filesystem std::unique_ptr remoteFolderNodeCheck(megaApi[0]->getNodeByPath(folderName.c_str(), remoteSyncNode.get())); ASSERT_EQ(remoteFolderNodeCheck.get(), nullptr); } TEST_F(SdkTest, MidSessionFetchnodes) { LOG_info << "___TEST MidSessionFetchnodes___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); for (int i = 0; i < 5; ++i) { MegaNode *rootnode = megaApi[0]->getRootNode(); auto nh = createFolder(0, ("new-folder-for-test" + std::to_string(i)).c_str(), rootnode); megaApi[0]->invalidateCache(); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // we should have caught up on actionpackets to the point that this one is present again std::unique_ptr justCreatedNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(justCreatedNode != nullptr); } } /** * @brief TEST_F SyncPersistence * * Testing configured syncs persitence */ TEST_F(SdkTest, SyncPersistence) { // What we are going to test here: // - locallogut -> Syncs kept // - logout with setKeepSyncsAfterLogout(true) -> Syncs kept // - logout -> Syncs removed LOG_info << "___TEST SyncPersistence___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure session ID is invalidated. gSessionIDs[0] = "invalid"; fs::path basePath = "SyncPersistence"; const auto localPath = fs::current_path() / basePath; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); // Create local directory and file. fs::create_directories(localPath); ASSERT_TRUE(createFile(path_u8string((localPath / "fileTest1")), false)); LOG_verbose << "SyncPersistence : Creating remote folder"; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); PerApi& target = mApi[0]; target.resetlastEvent(); auto nh = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); // make sure there are no outstanding cs requests in case // "Postponing DB commit until cs requests finish" // means our Sync's cloud Node is not in the db ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 10000)); LOG_verbose << "SyncPersistence : Enabling sync"; ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, path_u8string(localPath).c_str(), nullptr, remoteBaseNode->getHandle(), nullptr)) << "API Error adding a new sync"; std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); handle backupId = sync->getBackupId(); ASSERT_NE(backupId, UNDEF); std::string remoteFolder(sync->getLastKnownMegaFolder()); // Check if a locallogout keeps the sync configured. std::string session = unique_ptr(dumpSession()).get(); ASSERT_NO_FATAL_FAILURE(locallogout()); auto trackerFastLogin = asyncRequestFastLogin(0, session.c_str()); ASSERT_EQ(API_OK, trackerFastLogin->waitForResult()) << " Failed to establish a login/session for account " << 0; target.resetlastEvent(); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // wait for the event that says all syncs (if any) have been reloaded ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_SYNCS_RESTORED); }, 40000)); // 40 seconds because we've seen the first `sc` not respond for 10 seconds sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(remoteFolder, string(sync->getLastKnownMegaFolder())); // perform fetchnodes (via megaapi_impl) while nodes are already loaded // and a sync is running // and check that the Nodes don't seem to disappear while it happens // (similar dealing with an ETOOMANY error) // just so we are exercising most of that code path somewhere RequestTracker fnrt(megaApi[0].get()); megaApi[0]->invalidateCache(); megaApi[0]->fetchNodes(&fnrt); while (!fnrt.finished) { // actually we can't check for the node yet - we may load a treecache that // doesn't include it. We have to wait until actionpackets catch up //std::unique_ptr remoteBaseNode2(megaApi[0]->getNodeByHandle(nh)); //if (!remoteBaseNode2.get()) //{ // remoteBaseNode2.reset(); //} //ASSERT_NE(remoteBaseNode2.get(), (MegaNode*)nullptr); WaitMillisec(10); } // fetchnodes result is only called after statecurrent, which should mean // the last actionpacket indicated it was the last. megaApi[0]->removeRequestListener(&fnrt); std::unique_ptr remoteBaseNode2(megaApi[0]->getNodeByHandle(nh)); if (!remoteBaseNode2) { // see if more actionpackets bring it back (even though the last one did not have ir:1) for (int i = 0; i < 10; ++i) { WaitMillisec(1000); remoteBaseNode2.reset(megaApi[0]->getNodeByHandle(nh)); if (remoteBaseNode2) { // this does currently occur. commenting for now but we should bring it back once the API delivers ir:1 correctly //ASSERT_FALSE(true) << "extra actionpackets delivered missing node after the server said there were no more"; // at least we are now up to date break; } } } remoteBaseNode2.reset(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode2.get(), (MegaNode*)nullptr); // Check if a logout with keepSyncsAfterLogout keeps the sync configured. ASSERT_NO_FATAL_FAILURE(logout(0, true, maxTimeout)); size_t syncCount = static_cast(unique_ptr(megaApi[0]->getSyncs())->size()); ASSERT_EQ(syncCount, 0u); gSessionIDs[0] = "invalid"; auto trackerLogin = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, trackerLogin->waitForResult()) << " Failed to establish a login/session for account " << 0; target.resetlastEvent(); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_SYNCS_RESTORED); }, 10000)); //sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_DISABLED, MegaSync::LOGGED_OUT); sync.reset(megaApi[0]->getSyncByBackupId(backupId)); ASSERT_TRUE(sync != nullptr); ASSERT_EQ(MegaSync::SyncRunningState(sync->getRunState()), MegaSync::RUNSTATE_DISABLED); ASSERT_EQ(MegaSync::Error(sync->getError()), MegaSync::LOGGED_OUT); ASSERT_EQ(remoteFolder, string(sync->getLastKnownMegaFolder())); // Check if a logout without keepSyncsAfterLogout doesn't keep the sync configured. ASSERT_NO_FATAL_FAILURE(logout(0, false, maxTimeout)); gSessionIDs[0] = "invalid"; trackerLogin = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, trackerLogin->waitForResult()) << " Failed to establish a login/session for account " << 0; ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); sync.reset(megaApi[0]->getSyncByBackupId(backupId)); ASSERT_EQ(sync, nullptr); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); } /** * @brief TEST_F SyncPaths * * Testing non ascii paths and symlinks */ TEST_F(SdkTest, SyncPaths) { // What we are going to test here: // - Check paths with non ascii chars and check that sync works. // - (No WIN32) Add a sync with non canonical path and check that it works, // that symlinks are not followed and that sync path collision with // symlinks involved works. LOG_info << "___TEST SyncPaths___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); string basePathStr = "SyncPaths-синхронизация"; string fileNameStr = "fileTest1-файл"; fs::path basePath = u8path_compat(basePathStr.c_str()); const auto localPath = fs::current_path() / basePath; fs::path filePath = localPath / u8path_compat(fileNameStr.c_str()); fs::path fileDownloadPath = fs::current_path() / u8path_compat(fileNameStr.c_str()); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), "symlink_1A")); deleteFile(path_u8string(fileDownloadPath)); // Create local directories std::error_code ignoredEc; fs::remove_all(localPath, ignoredEc); fs::create_directory(localPath); fs::create_directory(localPath / "level_1A"); fs::create_directory_symlink(localPath / "level_1A", localPath / "symlink_1A"); fs::create_directory_symlink(localPath / "level_1A", fs::current_path() / "symlink_1A"); LOG_verbose << "SyncPaths : Creating remote folder " << path_u8string(basePath); std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folder " << path_u8string(basePath); std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr) << "Error getting node for remote folder " << path_u8string(basePath); LOG_verbose << "SyncPaths : Creating sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, path_u8string(localPath).c_str(), nullptr, remoteBaseNode->getHandle(), nullptr)) << "SyncPaths : Error creating sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync) << "SyncPaths : Error reaching RUNNING state for sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); LOG_verbose << "SyncPaths : Give the sync a few seconds before adding a new file (1)"; WaitMillisec(waitForSyncsMs); LOG_verbose << "SyncPaths : Adding a file and checking if it is synced: " << path_u8string(filePath); ASSERT_TRUE(createFile(path_u8string(filePath), false)) << "Couldn't create " << path_u8string(filePath); auto remoteFile = "/" + string(remoteBaseNode->getName()) + "/" + fileNameStr; std::unique_ptr remoteNode; WaitFor([this, &remoteNode, &remoteFile]() -> bool { remoteNode.reset(megaApi[0]->getNodeByPath(remoteFile.c_str())); return (remoteNode.get() != nullptr); },50*1000); ASSERT_NE(remoteNode.get(), nullptr) << "Failed (1) to get node for " << remoteFile << ", uploaded from " << path_u8string(filePath); LOG_verbose << "SyncPaths : File " << path_u8string(filePath) << " is successfully synced to " << remoteFile << ". Downloading the remote file"; ASSERT_EQ( MegaError::API_OK, doStartDownload(0, remoteNode.get(), path_u8string(fileDownloadPath).c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/)); ASSERT_TRUE(fileexists(path_u8string(fileDownloadPath))); deleteFile(path_u8string(fileDownloadPath)); #if !defined(__APPLE__) LOG_verbose << "SyncPaths : Check that symlinks are not synced"; const string remotePathOfSymlink("/" + string(remoteBaseNode->getName()) + "/symlink_1A"); std::unique_ptr remoteNodeSym(megaApi[0]->getNodeByPath(remotePathOfSymlink.c_str())); ASSERT_FALSE(remoteNodeSym) << "Remote node found for symlink, at " << remotePathOfSymlink; nh = createFolder(0, "symlink_1A", remoteRootNode.get()); const string& folderNamedLikeSymlink = remotePathOfSymlink; ASSERT_NE(nh, UNDEF) << "Error creating remote folder " << folderNamedLikeSymlink; remoteNodeSym.reset(megaApi[0]->getNodeByHandle(nh)); ASSERT_TRUE(remoteNodeSym) << "Error getting node of " << folderNamedLikeSymlink; #ifndef WIN32 { LOG_verbose << "SyncPaths : Check that symlinks are considered when creating a sync"; ASSERT_EQ(API_EARGS, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, path_u8string(fs::current_path() / "symlink_1A").c_str(), nullptr, remoteNodeSym->getHandle(), nullptr)) << "Sync with local path being a symlink to a folder already synced should have failed"; ASSERT_EQ(MegaSync::LOCAL_PATH_SYNC_COLLISION, mApi[0].lastSyncError) << "Sync with local path in another sync should have ended with " << MegaSync::LOCAL_PATH_SYNC_COLLISION; } #endif // Disable the first one, create again the one with the symlink, check that it is working and check if the first fails when enabled. LOG_verbose << "SyncPaths : Disable sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); auto tagID = sync->getBackupId(); ASSERT_EQ(API_OK, synchronousSetSyncRunState(0, tagID, MegaSync::RUNSTATE_DISABLED)) << "API Error disabling sync"; sync = waitForSyncState(megaApi[0].get(), tagID, MegaSync::RUNSTATE_DISABLED, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_DISABLED); std::string localSymlinkToSync = path_u8string((fs::current_path() / "symlink_1A")); LOG_verbose << "SyncPaths : Create sync from: " << localSymlinkToSync << " to remote path: " << remoteNodeSym->getName(); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, localSymlinkToSync.c_str(), nullptr, remoteNodeSym->getHandle(), nullptr)) << "Error adding sync with local path " << localSymlinkToSync << " and remote " << folderNamedLikeSymlink; std::unique_ptr syncSym = waitForSyncState(megaApi[0].get(), remoteNodeSym.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(syncSym) << "Error getting sync in RUNNING state; local path " << localSymlinkToSync << " and remote " << folderNamedLikeSymlink;; ASSERT_EQ(syncSym->getRunState(), MegaSync::RUNSTATE_RUNNING); LOG_verbose << "SyncPaths : Give the sync a few seconds before adding a new file (2)"; WaitMillisec(waitForSyncsMs); // Now that we have a sync whose root folder is a symlink, add a file to the path that the symlink points to, and check if it is synced auto fileToCreate = localPath / "level_1A" / u8path_compat(fileNameStr.c_str()); LOG_verbose << "SyncPaths : Adding a file and checking if it is synced: " << path_u8string(fileToCreate); ASSERT_TRUE(createFile(path_u8string(fileToCreate), false)) << "failed to create local file " << fileToCreate; remoteFile = "/" + string(remoteNodeSym->getName()) + "/" + fileNameStr; WaitFor([this, &remoteNode, &remoteFile]() -> bool { remoteNode.reset(megaApi[0]->getNodeByPath(remoteFile.c_str())); return (remoteNode.get() != nullptr); },50*1000); ASSERT_TRUE(remoteNode) << "Failed (2) to get remote node for " << remoteFile << " uploaded from " << path_u8string(fileToCreate); LOG_verbose << "SyncPaths : File " << path_u8string(fileToCreate) << " is successfully synced to " << remoteFile << ". Downloading the remote file"; ASSERT_EQ( MegaError::API_OK, doStartDownload(0, remoteNode.get(), path_u8string(fileDownloadPath).c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/)); ASSERT_TRUE(fileexists(path_u8string(fileDownloadPath))); deleteFile(path_u8string(fileDownloadPath)); #ifndef WIN32 { LOG_verbose << "SyncPaths : Check that we cannot enable again the no-symlink sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); ASSERT_EQ(API_EARGS, synchronousSetSyncRunState(0, tagID, MegaSync::RUNSTATE_RUNNING)) << "API Error enabling a sync"; ASSERT_EQ(MegaSync::LOCAL_PATH_SYNC_COLLISION, mApi[0].lastSyncError); } #endif #endif LOG_verbose << "SyncPaths : All done. Cleaning up"; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), "symlink_1A")); } /** * @brief TEST_F SearchByPathOfType * * Testing search nodes by path of specified type */ TEST_F(SdkTest, SearchByPathOfType) { LOG_info << "___TEST SearchByPathOfType___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootNode{ megaApi[0]->getRootNode() }; string duplicateName = "fileAndFolderName"; // Upload test file MegaHandle fileInRoot = INVALID_HANDLE; ASSERT_TRUE(createFile(duplicateName, false)) << "Couldn't create file " << duplicateName; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileInRoot, duplicateName.c_str(), rootNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; // Test not found cases: { for (auto type : {MegaNode::TYPE_FILE, MegaNode::TYPE_FOLDER, MegaNode::TYPE_UNKNOWN}) { for (auto pathToNonExisting : {"this/does/not/exist", "/this/does/not/exist", "./thisdoesnotexist", "thisdoesnotexist"}) { std::unique_ptr fileNode{ megaApi[0]->getNodeByPathOfType(pathToNonExisting, nullptr, type)}; ASSERT_FALSE(fileNode); } } } // Test file search using relative path std::unique_ptr fileNode{ megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get()) }; ASSERT_TRUE(fileNode) << "Could not find node for file " << duplicateName; ASSERT_EQ(fileNode->getHandle(), fileInRoot); ASSERT_EQ(fileNode->getType(), MegaNode::TYPE_FILE); ASSERT_STREQ(fileNode->getName(), duplicateName.c_str()); fileNode.reset(megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get(), MegaNode::TYPE_FILE)); ASSERT_TRUE(fileNode) << "Could not find node for file " << duplicateName; ASSERT_EQ(fileNode->getHandle(), fileInRoot); ASSERT_EQ(fileNode->getType(), MegaNode::TYPE_FILE); ASSERT_STREQ(fileNode->getName(), duplicateName.c_str()); fileNode.reset(megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get(), MegaNode::TYPE_FOLDER)); ASSERT_FALSE(fileNode) << "Found node for file while explicitly searching for folder " << duplicateName; // Create test folder auto folderInRoot = createFolder(0, duplicateName.c_str(), rootNode.get()); ASSERT_NE(folderInRoot, INVALID_HANDLE) << "Error creating remote folder " << duplicateName; // Test search using relative path std::unique_ptr folderNode{ megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get()) }; ASSERT_TRUE(folderNode) << "Could not find node for folder " << duplicateName; ASSERT_EQ(folderNode->getHandle(), folderInRoot); ASSERT_EQ(folderNode->getType(), MegaNode::TYPE_FOLDER); ASSERT_STREQ(folderNode->getName(), duplicateName.c_str()); fileNode.reset(megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get(), MegaNode::TYPE_FILE)); ASSERT_TRUE(fileNode) << "Could not find node for file " << duplicateName; ASSERT_EQ(fileNode->getHandle(), fileInRoot); ASSERT_EQ(fileNode->getType(), MegaNode::TYPE_FILE); ASSERT_STREQ(fileNode->getName(), duplicateName.c_str()); folderNode.reset(megaApi[0]->getNodeByPathOfType(duplicateName.c_str(), rootNode.get(), MegaNode::TYPE_FOLDER)); ASSERT_TRUE(folderNode) << "Could not find node for folder " << duplicateName; ASSERT_EQ(folderNode->getHandle(), folderInRoot); ASSERT_EQ(folderNode->getType(), MegaNode::TYPE_FOLDER); ASSERT_STREQ(folderNode->getName(), duplicateName.c_str()); // Test search using absolute path string absolutePath = '/' + duplicateName; folderNode.reset(megaApi[0]->getNodeByPathOfType(absolutePath.c_str())); ASSERT_TRUE(folderNode) << "Could not find node for folder " << absolutePath; ASSERT_EQ(folderNode->getHandle(), folderInRoot); ASSERT_EQ(folderNode->getType(), MegaNode::TYPE_FOLDER); ASSERT_STREQ(folderNode->getName(), duplicateName.c_str()); fileNode.reset(megaApi[0]->getNodeByPathOfType(absolutePath.c_str(), nullptr, MegaNode::TYPE_FILE)); ASSERT_TRUE(fileNode) << "Could not find node for file " << absolutePath; ASSERT_EQ(fileNode->getHandle(), fileInRoot); ASSERT_EQ(fileNode->getType(), MegaNode::TYPE_FILE); ASSERT_STREQ(fileNode->getName(), duplicateName.c_str()); folderNode.reset(megaApi[0]->getNodeByPathOfType(absolutePath.c_str(), nullptr, MegaNode::TYPE_FOLDER)); ASSERT_TRUE(folderNode) << "Could not find node for folder " << absolutePath; ASSERT_EQ(folderNode->getHandle(), folderInRoot); ASSERT_EQ(folderNode->getType(), MegaNode::TYPE_FOLDER); ASSERT_STREQ(folderNode->getName(), duplicateName.c_str()); } /** * @brief TEST_F SyncOQTransitions * * Testing OQ Transitions */ TEST_F(SdkTest, SyncOQTransitions) { // What we are going to test here: // - Online transitions: Sync is disabled when in OQ and enabled after OQ // - Offline transitions: Sync is disabled when in OQ and enabled after OQ // - Enabling a sync temporarily disabled. LOG_info << "___TEST SyncOQTransitions___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); string fileNameStr = "fileTest"; fs::path basePath = "SyncOQTransitions"; fs::path fillPath = "OQFolder"; const auto localPath = fs::current_path() / basePath; fs::path filePath = localPath / u8path_compat(fileNameStr.c_str()); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), fillPath)); // Create local directory fs::create_directories(localPath); LOG_verbose << "SyncOQTransitions : Creating remote folder"; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); nh = createFolder(0, path_u8string(fillPath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote fillPath"; std::unique_ptr remoteFillNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteFillNode.get(), nullptr); LOG_verbose << "SyncOQTransitions : Creating sync"; ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, path_u8string(localPath).c_str(), nullptr, remoteBaseNode->getHandle(), nullptr)) << "API Error adding a new sync"; std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); handle backupId = sync->getBackupId(); LOG_verbose << "SyncOQTransitions : Filling up storage space"; auto importHandle = importPublicLink( 0, MegaClient::getMegaURL() + "/file/gzlQ3DIY#Ak-OW4MP7lhnQxP9nzBU1bOP45xr_7sXnIz8YYqOBUg", remoteFillNode.get()); std::unique_ptr remote1GBFile(megaApi[0]->getNodeByHandle(importHandle)); ASSERT_NO_FATAL_FAILURE(synchronousGetSpecificAccountDetails(0, true, false, false)); // Get account size. ASSERT_NE(mApi[0].accountDetails, nullptr); auto filesNeeded = static_cast(mApi[0].accountDetails->getStorageMax() / remote1GBFile->getSize()) + 1; for (int i=1; i < filesNeeded; i++) { ASSERT_EQ(API_OK, doCopyNode(0, nullptr, remote1GBFile.get(), remoteFillNode.get(), (remote1GBFile->getName() + to_string(i)).c_str())); } std::unique_ptr last1GBFileNode(megaApi[0]->getChildNode(remoteFillNode.get(), (remote1GBFile->getName() + to_string(filesNeeded-1)).c_str())); { LOG_verbose << "SyncOQTransitions : Check that Sync is disabled due to OQ."; ASSERT_NO_FATAL_FAILURE(synchronousGetSpecificAccountDetails(0, true, false, false)); // Needed to ensure we know we are in OQ sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::STORAGE_OVERQUOTA); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(MegaSync::STORAGE_OVERQUOTA, sync->getError()); LOG_verbose << "SyncOQTransitions : Check that Sync could not be enabled while disabled due to OQ."; ASSERT_EQ(API_EFAILED, synchronousSetSyncRunState(0, backupId, MegaSync::RUNSTATE_RUNNING)) << "API Error enabling a sync"; sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::STORAGE_OVERQUOTA); // fresh snapshot of sync state ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(MegaSync::STORAGE_OVERQUOTA, sync->getError()); } LOG_verbose << "SyncOQTransitions : Free up space and check that Sync is not active again."; ASSERT_EQ(API_OK, synchronousRemove(0, last1GBFileNode.get())); ASSERT_NO_FATAL_FAILURE(synchronousGetSpecificAccountDetails(0, true, false, false)); // Needed to ensure we know we are not in OQ sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::STORAGE_OVERQUOTA); // of course the error stays as OverQuota. Sync still not re-enabled. ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_SUSPENDED); LOG_verbose << "SyncOQTransitions : Share big files folder with another account."; ASSERT_EQ(API_OK, synchronousInviteContact(0, mApi[1].email.c_str(), "SyncOQTransitions contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(WaitFor([this]() { return unique_ptr(megaApi[1]->getIncomingContactRequests())->size() == 1; }, 60*1000)); ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); ASSERT_EQ(API_OK, synchronousReplyContactRequest(1, mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } ASSERT_NO_FATAL_FAILURE(shareFolder(remoteFillNode.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(WaitFor([this]() { return unique_ptr(megaApi[1]->getInSharesList())->size() == 1; }, 60*1000)); // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &remoteFillNode]() { return unique_ptr(megaApi[1]->getNodeByHandle(remoteFillNode->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); unique_ptr contact(megaApi[1]->getContact(mApi[0].email.c_str())); unique_ptr nodeList(megaApi[1]->getInShares(contact.get())); ASSERT_EQ(nodeList->size(), 1); MegaNode* inshareNode = nodeList->get(0); // Wait for the outshare to be added to the sharer's node by the action packets ASSERT_TRUE(WaitFor([this, &remoteFillNode]() { return unique_ptr(megaApi[0]->getNodeByHandle(remoteFillNode->getHandle()))->isOutShare(); }, 60*1000)); // Make sure that search functionality finds them std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName(path_u8string(fillPath).c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); std::unique_ptr outShares(megaApi[0]->search(filterResults.get())); ASSERT_TRUE(outShares); ASSERT_EQ(outShares->size(), 1); ASSERT_EQ(outShares->get(0)->getHandle(), remoteFillNode->getHandle()); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(path_u8string(fillPath).c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); std::unique_ptr inShares(megaApi[1]->search(filterResults.get())); ASSERT_TRUE(inShares); ASSERT_EQ(inShares->size(), 1); ASSERT_EQ(inShares->get(0)->getHandle(), remoteFillNode->getHandle()); LOG_verbose << "SyncOQTransitions : Check for transition to OQ while offline."; std::string session = unique_ptr(dumpSession()).get(); ASSERT_NO_FATAL_FAILURE(locallogout()); std::unique_ptr remote1GBFile2nd(megaApi[1]->getChildNode(inshareNode, remote1GBFile->getName())); ASSERT_EQ(API_OK, doCopyNode(1, nullptr, remote1GBFile2nd.get(), inshareNode, (remote1GBFile2nd->getName() + to_string(filesNeeded-1)).c_str())); { ASSERT_NO_FATAL_FAILURE(resumeSession(session.c_str())); // sync not actually resumed here though (though it would be if it was still enabled) ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); ASSERT_NO_FATAL_FAILURE(synchronousGetSpecificAccountDetails(0, true, false, false)); // Needed to ensure we know we are in OQ sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::STORAGE_OVERQUOTA); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(MegaSync::STORAGE_OVERQUOTA, sync->getError()); } LOG_verbose << "SyncOQTransitions : Check for transition from OQ while offline."; ASSERT_NO_FATAL_FAILURE(locallogout()); std::unique_ptr toRemoveNode(megaApi[1]->getChildNode(inshareNode, (remote1GBFile->getName() + to_string(filesNeeded-1)).c_str())); ASSERT_EQ(API_OK, synchronousRemove(1, toRemoveNode.get())); ASSERT_NO_FATAL_FAILURE(resumeSession(session.c_str())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); ASSERT_NO_FATAL_FAILURE(synchronousGetSpecificAccountDetails(0, true, false, false)); // Needed to ensure we know we are no longer in OQ sync = waitForSyncState(megaApi[0].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::STORAGE_OVERQUOTA); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_SUSPENDED); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), fillPath)); } /** * @brief TEST_F SyncImage * * Testing the upsync of an image and verifying that we can successfully retrieve the thumbnail and preview. */ #if !USE_FREEIMAGE TEST_F(SdkTest, DISABLED_SyncImage) #else TEST_F(SdkTest, SyncImage) #endif { LOG_info << "___TEST SyncImage___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); string basePathStr = "SyncImage"; string fileNameStr = IMAGEFILE; fs::path basePath = u8path_compat(basePathStr.c_str()); const auto localPath = fs::current_path() / basePath; fs::path filePath = localPath / u8path_compat(fileNameStr.c_str()); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); // Create local directories std::error_code ignoredEc; fs::remove_all(localPath, ignoredEc); fs::create_directory(localPath); LOG_verbose << "SyncImage : Creating remote folder " << path_u8string(basePath); std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folder " << path_u8string(basePath); std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr) << "Error getting node for remote folder " << path_u8string(basePath); LOG_verbose << "SyncImage : Creating sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, path_u8string(localPath).c_str(), nullptr, remoteBaseNode->getHandle(), nullptr)) << "SyncImage : Error creating sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync) << "SyncImage : Error reaching RUNNING state for sync with local path " << path_u8string(localPath) << " and remote " << path_u8string(basePath); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); LOG_verbose << "SyncImage : Give the sync a few seconds before adding a new file"; WaitMillisec(waitForSyncsMs); LOG_verbose << "SyncImage : Adding the image file and checking if it is synced: " << path_u8string(filePath); ASSERT_TRUE(getFileFromArtifactory("test-data/" + fileNameStr, filePath)); auto remoteFile = "/" + string(remoteBaseNode->getName()) + "/" + fileNameStr; std::unique_ptr remoteNode; WaitFor([this, &remoteNode, &remoteFile]() -> bool { remoteNode.reset(megaApi[0]->getNodeByPath(remoteFile.c_str())); return (remoteNode.get() != nullptr); },50*1000); ASSERT_NE(remoteNode.get(), nullptr) << "Failed to get node for " << remoteFile << ", uploaded from " << path_u8string(filePath); // Get the thumbnail and preview of the uploaded image LOG_verbose << "SyncImage : Image file " << path_u8string(filePath) << " is successfully synced to " << remoteFile << ". Checking the thumbnail and preview"; ASSERT_EQ(API_OK, doGetThumbnail(0, remoteNode.get(), THUMBNAIL.c_str())); ASSERT_EQ(API_OK, doGetPreview(0, remoteNode.get(), PREVIEW.c_str())); LOG_verbose << "SyncImage : All done. Cleaning up"; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), basePath)); } #endif // ENABLE_SYNC /** * @brief TEST_F StressTestSDKInstancesOverWritableFolders * * Testing multiple SDK instances working in parallel */ // dgw: This test will consistently fail on Linux unless we raise the // maximum number of open file descriptors. // // This is necessary as a great many PosixWaiters are created for each // API object. Each waiter requires us to create a pipe pair. // // As such, we quickly exhaust the default limit on descriptors. // // If we raise the limit, the test will run but will still encounter // other limits, say memory exhaustion. TEST_F(SdkTest, DISABLED_StressTestSDKInstancesOverWritableFoldersOverWritableFolders) { // What we are going to test here: // - Creating multiple writable folders // - Login and fetch nodes in separated MegaApi instances // and hence in multiple SDK instances running in parallel. LOG_info << "___TEST StressTestSDKInstancesOverWritableFolders___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::string baseFolder = "StressTestSDKInstancesOverWritableFoldersFolder"; unsigned numFolders = 90; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), baseFolder)); LOG_verbose << "StressTestSDKInstancesOverWritableFolders : Creating remote folder"; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, baseFolder.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); // create subfolders ... for (unsigned index = 0; index < numFolders; index++) { string subFolderPath = string("subfolder_").append(SSTR(index)); nh = createFolder(0, subFolderPath.c_str(), remoteBaseNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote subfolder"; std::unique_ptr remoteSubFolderNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteSubFolderNode.get(), nullptr); // ... with a file in it string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1, false)) << "Couldnt create " << filename1; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, filename1.c_str(), remoteSubFolderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; } auto howMany = numFolders; std::vector> trackers; trackers.resize(howMany); std::vector exportedFolderApis; exportedFolderApis.resize(howMany); std::vector exportedLinks; exportedLinks.resize(howMany); std::vector authKeys; authKeys.resize(howMany); // export subfolders for (unsigned index = 0; index < howMany; index++) { string subFolderPath = string("subfolder_").append(SSTR(index)); std::unique_ptr remoteSubFolderNode(megaApi[0]->getNodeByPath(subFolderPath.c_str(), remoteBaseNode.get())); ASSERT_NE(remoteSubFolderNode.get(), nullptr); // ___ get a link to the file node string nodelink = createPublicLink(0, remoteSubFolderNode.get(), 0, maxTimeout, false /*mApi[0].accountDetails->getProLevel() == 0)*/, true /*writable*/); // The created link is stored in this->link at onRequestFinish() LOG_verbose << "StressTestSDKInstancesOverWritableFolders : " << subFolderPath << " link = " << nodelink; exportedLinks[index] = nodelink; std::unique_ptr nexported(megaApi[0]->getNodeByHandle(remoteSubFolderNode->getHandle())); ASSERT_NE(nexported.get(), nullptr); if (nexported) { if (nexported->getWritableLinkAuthKey()) { string authKey(nexported->getWritableLinkAuthKey()); ASSERT_FALSE(authKey.empty()); authKeys[index] = authKey; } } } // create apis to exported folders for (unsigned index = 0; index < howMany; index++) { exportedFolderApis[static_cast(index)] = newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(static_cast(index) + 10).c_str(), USER_AGENT.c_str(), static_cast(THREADS_PER_MEGACLIENT)); // reduce log level to something beareable exportedFolderApis[static_cast(index)]->setLogLevel(MegaApi::LOG_LEVEL_WARNING); } // login to exported folders for (unsigned index = 0; index < howMany; index++) { string nodelink = exportedLinks[index]; string authKey = authKeys[index]; out() << "login to exported folder " << index; trackers[static_cast(index)] = asyncRequestLoginToFolder(exportedFolderApis[static_cast(index)].get(), nodelink.c_str(), authKey.c_str()); } // wait for login to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fetchnodes for accout " << index; } // perform parallel fetchnodes for each for (unsigned index = 0; index < howMany; ++index) { out() << "Fetching nodes for account " << index; trackers[index] = asyncRequestFetchnodes(exportedFolderApis[static_cast(index)].get()); } // wait for fetchnodes to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fetchnodes for accout " << index; } // In case the last test exited without cleaning up (eg, debugging etc) Cleanup(); } /** * @brief TEST_F StressTestSDKInstancesOverWritableFolders * * Testing multiple SDK instances working in parallel */ TEST_F(SdkTest, WritableFolderSessionResumption) { // What we are going to test here: // - Creating multiple writable folders // - Login and fetch nodes in separated MegaApi instances // and hence in multiple SDK instances running in parallel. LOG_info << "___TEST WritableFolderSessionResumption___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::string baseFolder = "WritableFolderSessionResumption"; unsigned numFolders = 1; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), baseFolder)); LOG_verbose << "WritableFolderSessionResumption : Creating remote folder"; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, baseFolder.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); // create subfolders ... for (unsigned index = 0 ; index < numFolders; index++ ) { string subFolderPath = string("subfolder_").append(SSTR(index)); nh = createFolder(0, subFolderPath.c_str(), remoteBaseNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote subfolder"; std::unique_ptr remoteSubFolderNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteSubFolderNode.get(), nullptr); // ... with a file in it string filename1 = UPFILE; ASSERT_TRUE(createFile(filename1, false)) << "Couldnt create " << filename1; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, filename1.c_str(), remoteSubFolderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; } auto howMany = numFolders; std::vector> trackers; trackers.resize(howMany); std::vector exportedFolderApis; exportedFolderApis.resize(howMany); std::vector exportedLinks; exportedLinks.resize(howMany); std::vector authKeys; authKeys.resize(howMany); std::vector sessions; sessions.resize(howMany); // export subfolders for (unsigned index = 0 ; index < howMany; index++ ) { string subFolderPath = string("subfolder_").append(SSTR(index)); std::unique_ptr remoteSubFolderNode(megaApi[0]->getNodeByPath(subFolderPath.c_str(), remoteBaseNode.get())); ASSERT_NE(remoteSubFolderNode.get(), nullptr); // ___ get a link to the file node string nodelink = createPublicLink(0, remoteSubFolderNode.get(), 0, maxTimeout, false /*mApi[0].accountDetails->getProLevel() == 0)*/, true /*writable*/); // The created link is stored in this->link at onRequestFinish() LOG_verbose << "WritableFolderSessionResumption : " << subFolderPath << " link = " << nodelink; exportedLinks[index] = nodelink; std::unique_ptr nexported(megaApi[0]->getNodeByHandle(remoteSubFolderNode->getHandle())); ASSERT_NE(nexported.get(), nullptr); if (nexported) { if (nexported->getWritableLinkAuthKey()) { string authKey(nexported->getWritableLinkAuthKey()); ASSERT_FALSE(authKey.empty()); authKeys[index] = authKey; } } } ASSERT_NO_FATAL_FAILURE( logout(0, false, maxTimeout) ); gSessionIDs[0] = "invalid"; // create apis to exported folders for (unsigned index = 0 ; index < howMany; index++ ) { exportedFolderApis[index] = newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(static_cast(index) + 10).c_str(), USER_AGENT.c_str(), static_cast(THREADS_PER_MEGACLIENT)); // reduce log level to something beareable exportedFolderApis[index]->setLogLevel(MegaApi::LOG_LEVEL_WARNING); } // login to exported folders for (unsigned index = 0 ; index < howMany; index++ ) { string nodelink = exportedLinks[index]; string authKey = authKeys[index]; out() << logTime() << "login to exported folder " << index; trackers[index] = asyncRequestLoginToFolder(exportedFolderApis[index].get(), nodelink.c_str(), authKey.c_str()); } // wait for login to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fetchnodes for account " << index; } // perform parallel fetchnodes for each for (unsigned index = 0; index < howMany; ++index) { out() << logTime() << "Fetching nodes for account " << index; trackers[index] = asyncRequestFetchnodes(exportedFolderApis[index].get()); } // wait for fetchnodes to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fetchnodes for account " << index; } // get session for (unsigned index = 0 ; index < howMany; index++ ) { out() << logTime() << "dump session of exported folder " << index; sessions[index] = unique_ptr(exportedFolderApis[index]->dumpSession()).get(); } // local logout for (unsigned index = 0 ; index < howMany; index++ ) { out() << logTime() << "local logout of exported folder " << index; trackers[index] = asyncRequestLocalLogout(exportedFolderApis[index].get()); } // wait for logout to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to local logout for folder " << index; } // resume session for (unsigned index = 0 ; index < howMany; index++ ) { out() << logTime() << "fast login to exported folder " << index; trackers[index] = asyncRequestFastLogin(exportedFolderApis[index].get(), sessions[index].c_str()); } // wait for fast login to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fast login for folder " << index; } // perform parallel fetchnodes for each for (unsigned index = 0; index < howMany; ++index) { out() << logTime() << "Fetching nodes for account " << index; trackers[index] = asyncRequestFetchnodes(exportedFolderApis[index].get()); } // wait for fetchnodes to complete: for (unsigned index = 0; index < howMany; ++index) { ASSERT_EQ(API_OK, trackers[index]->waitForResult()) << " Failed to fetchnodes for account " << index; } // get root node to confirm all went well for (unsigned index = 0; index < howMany; ++index) { std::unique_ptr root{exportedFolderApis[index]->getRootNode()}; ASSERT_TRUE(root != nullptr); } // In case the last test exited without cleaning up (eg, debugging etc) Cleanup(); } /** * @brief TEST_F SdkTargetOverwriteTest * * Testing to upload a file into an inshare with read only privileges. * API must put node into rubbish bin, instead of fail putnodes with API_EACCESS */ TEST_F(SdkTest, SdkTargetOverwriteTest) { LOG_info << "___TEST SdkTargetOverwriteTest___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); //--- Add secondary account as contact --- string message = "Hi contact. Let's share some stuff"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) ); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) ); ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } //--- Create a new folder in cloud drive --- std::unique_ptr rootnode{megaApi[0]->getRootNode()}; char foldername1[64] = "Shared-folder"; MegaHandle hfolder1 = createFolder(0, foldername1, rootnode.get()); ASSERT_NE(hfolder1, UNDEF); std::unique_ptr n1(megaApi[0]->getNodeByHandle(hfolder1)); ASSERT_NE(n1.get(), nullptr); // --- Create a new outgoing share --- bool check1, check2; // reset flags expected to be true in asserts below mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_READWRITE)); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &n1]() { return unique_ptr(megaApi[1]->getNodeByHandle(n1->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); std::unique_ptr sl(megaApi[1]->getInSharesList(::MegaApi::ORDER_NONE)); ASSERT_EQ(1, sl->size()) << "Incoming share not received in auxiliar account"; MegaShare *share = sl->get(0); ASSERT_TRUE(share->getNodeHandle() == n1->getHandle()) << "Wrong inshare handle: " << Base64Str(share->getNodeHandle()) << ", expected: " << Base64Str( n1->getHandle()); ASSERT_TRUE(share->getAccess() >=::MegaShare::ACCESS_READWRITE) << "Insufficient permissions: " << MegaShare::ACCESS_READWRITE << " over created share"; // important to reset resetOnNodeUpdateCompletionCBs(); // --- Create local file and start upload from secondary account into inew InShare --- onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; mApi[1].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; std::string fileName = std::to_string(time(nullptr)); ASSERT_TRUE(createLocalFile(fs::current_path(), fileName.c_str(), 1024)); fs::path fp = fs::current_path() / fileName; TransferTracker tt(megaApi[1].get()); MegaUploadOptions shareUploadOptions; shareUploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; const auto filePath = fp.string(); megaApi[1]->startUpload(filePath, n1.get(), nullptr, &shareUploadOptions, &tt); /*MegaTransferListener*/ // --- Pause transfer, revoke out-share permissions for secondary account and resume transfer --- megaApi[1]->pauseTransfers(true); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfolder1, MegaNode::CHANGE_TYPE_REMOVED, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_UNKNOWN)); ASSERT_TRUE( waitForResponse(&check1) ) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE( waitForResponse(&check2) ) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; megaApi[1]->pauseTransfers(false); // --- Wait for transfer completion // in fact we get EACCESS - maybe this API feature is not migrated to live yet? ASSERT_EQ(API_OK, ErrorCodes(tt.waitForResult(600))) << "Upload transfer failed"; ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- Check that node has been created in rubbish bin --- std::unique_ptr n (mApi[1].megaApi->getNodeByHandle(tt.resultNodeHandle)); ASSERT_TRUE(n) << "Error retrieving new created node"; std::unique_ptr rubbishNode (mApi[1].megaApi->getRubbishNode()); ASSERT_TRUE(rubbishNode) << "Error retrieving rubbish bin node"; ASSERT_TRUE(n->getParentHandle() == rubbishNode->getHandle()) << "Error: new node parent handle: " << Base64Str(n->getParentHandle()) << " doesn't match with rubbish bin node handle: " << Base64Str(rubbishNode->getHandle()); // --- Clean rubbish bin for secondary account --- auto err = synchronousCleanRubbishBin(1); ASSERT_TRUE(err == API_OK || err == API_ENOENT) << "Clean rubbish bin failed (error: " << err << ")"; } /** * @brief TEST_F SdkTestAudioFileAttributes * * Tests extracting thumbnail and other metadata for uploaded audio file. * */ #ifndef USE_MEDIAINFO TEST_F(SdkTest, DISABLED_SdkTestAudioFileAttributes) #else TEST_F(SdkTest, SdkTestAudioFileAttributes) #endif { LOG_info << "___TEST Audio thumbnail and metadata___"; ASSERT_TRUE(getFileFromArtifactory("test-data/" + AUDIOFILE, AUDIOFILE)); ASSERT_NO_FATAL_FAILURE(getAccountsForTest()); std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, nullptr, AUDIOFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload test file " << AUDIOFILE; std::unique_ptr node(megaApi[0]->getNodeByPath(AUDIOFILE.c_str(), rootnode.get())); ASSERT_TRUE(node); ASSERT_EQ(node->getDuration(), 2) << "Duration of the audio file is not correct."; #if defined(USE_FREEIMAGE) ASSERT_TRUE(node->hasPreview() && node->hasThumbnail()); #endif } /** * @brief TEST_F SearchNodesByCreationTime * * Test filtering nodes by ctime in search() and getChildren() * */ TEST_F(SdkTest, SearchNodesByCreationTime) { LOG_info << "___TEST SearchNodesByCreationTime___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); unique_ptr rootnode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnode); string folderName = "TestCTime_Folder.Foo"; MegaHandle folderHandle = createFolder(0, folderName.c_str(), rootnode.get()); ASSERT_NE(folderHandle, INVALID_HANDLE); unique_ptr folderNode(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(folderNode); int64_t folderCTime = folderNode->getCreationTime(); std::this_thread::sleep_for(std::chrono::milliseconds{1500}); // avoid nodes having identical CTime string fileName = "TestCTime_File.bar"; ASSERT_TRUE(createFile(fileName, false)); MegaHandle fileHandle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName.c_str(), folderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); unique_ptr fileNode(megaApi[0]->getNodeByHandle(fileHandle)); ASSERT_TRUE(fileNode); int64_t fileCTime = fileNode->getCreationTime(); ASSERT_NE(folderCTime, fileCTime) << "Test file and folder have the same creation time"; // getChildren() unique_ptr f(MegaSearchFilter::createInstance()); f->byName("TestCTime_*"); f->byLocationHandle(folderHandle); unique_ptr results(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName); f->byCreationTime(fileCTime, fileCTime); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 0) << results->get(0)->getName(); f->byCreationTime(fileCTime - 1, fileCTime + 1); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName); // cope with time differences in remote FS and local FS const int64_t& olderCTime = folderCTime < fileCTime ? folderCTime : fileCTime; const string& olderName = folderCTime < fileCTime ? folderName : fileName; const int64_t& newerCTime = folderCTime > fileCTime ? folderCTime : fileCTime; const string& newerName = folderCTime > fileCTime ? folderName : fileName; // search() f->byLocationHandle(INVALID_HANDLE); f->byCreationTime(0, 0); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 2); f->byCreationTime(olderCTime, newerCTime); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 0); f->byCreationTime(olderCTime - 1, newerCTime + 1); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 2); f->byCreationTime(0, newerCTime); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), olderName); f->byCreationTime(olderCTime, 0); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), newerName); deleteFile(fileName); } /** * @brief TEST_F SearchNodesByModificationTime * * Test filtering nodes by mtime in search() and getChildren() * */ TEST_F(SdkTest, SearchNodesByModificationTime) { LOG_info << "___TEST SearchNodesByModificationTime___"; /// / /// TestMTime_Folder.Foo/ /// TestMTime_File1.bar /// TestMTime_File2.bar /// TestMTime_FileAtRoot.bar ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // TestCTime_Folder.Foo unique_ptr rootnode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnode); string folderName = "TestMTime_Folder.Foo"; MegaHandle folderHandle = createFolder(0, folderName.c_str(), rootnode.get()); ASSERT_NE(folderHandle, INVALID_HANDLE); unique_ptr folderNode(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(folderNode); // TestCTime_Folder.Foo / TestMTime_File1.bar string fileName1 = "TestMTime_File1.bar"; ASSERT_TRUE(createFile(fileName1, false)); MegaHandle fileHandle1 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle1, fileName1.c_str(), folderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName1; ASSERT_NE(fileHandle1, INVALID_HANDLE); unique_ptr fileNode1(megaApi[0]->getNodeByHandle(fileHandle1)); ASSERT_TRUE(fileNode1); int64_t fileMTime1Old = fileNode1->getModificationTime(); ASSERT_NE(fileMTime1Old, 0) << "Invalid modification time for file " << fileName1; std::this_thread::sleep_for(std::chrono::milliseconds{1500}); // avoid nodes having identical MTime // modify file { ofstream f(fileName1); f << "update "; f.close(); } ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle1, fileName1.c_str(), folderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload modified " << fileName1; ASSERT_NE(fileHandle1, INVALID_HANDLE); fileNode1.reset(megaApi[0]->getNodeByHandle(fileHandle1)); ASSERT_TRUE(fileNode1); int64_t fileMTime1 = fileNode1->getModificationTime(); ASSERT_NE(fileMTime1, 0) << "Invalid modification time after update for file " << fileName1; ASSERT_NE(fileMTime1Old, fileMTime1) << "Test file has the same Modification time after being updated"; std::this_thread::sleep_for(std::chrono::milliseconds{1500}); // avoid nodes having identical MTime // modify file { ofstream f(fileName1); f << "update "; f.close(); } // TestCTime_Folder.Foo / TestMTime_File2.bar string fileName2 = "TestMTime_File2.bar"; MegaHandle fileHandle2 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle2, fileName1.c_str(), folderNode.get(), fileName2.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName2; ASSERT_NE(fileHandle2, INVALID_HANDLE); unique_ptr fileNode2(megaApi[0]->getNodeByHandle(fileHandle2)); ASSERT_TRUE(fileNode2); int64_t fileMTime2 = fileNode2->getModificationTime(); ASSERT_NE(fileMTime2, 0) << "Invalid modification time for file 2 " << fileName2; std::this_thread::sleep_for(std::chrono::milliseconds{2200}); // avoid nodes having identical MTime // modify file { ofstream f(fileName1); f << "update "; f.close(); } // TestMTime_FileAtRoot.bar string fileNameAtRoot = "TestMTime_FileAtRoot.bar"; MegaHandle fileHandleAtRoot = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandleAtRoot, fileName1.c_str(), rootnode.get(), fileNameAtRoot.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName1 << " at root"; ASSERT_NE(fileHandleAtRoot, INVALID_HANDLE); unique_ptr fileNodeAtRoot(megaApi[0]->getNodeByHandle(fileHandleAtRoot)); ASSERT_TRUE(fileNodeAtRoot); int64_t fileMTimeR = fileNodeAtRoot->getModificationTime(); ASSERT_NE(fileMTimeR, 0) << "Invalid modification time for file at root " << fileName1; // getChildren() unique_ptr f(MegaSearchFilter::createInstance()); f->byName("TestMTime_*"); f->byLocationHandle(rootnode->getHandle()); unique_ptr results(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), folderName); // folders come first ASSERT_EQ(results->get(1)->getName(), fileNameAtRoot); f->byModificationTime(fileMTimeR - 1, 0); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileNameAtRoot); f->byLocationHandle(folderHandle); f->byModificationTime(fileMTime1 - 1, 0); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName1); ASSERT_EQ(results->get(1)->getName(), fileName2); f->byModificationTime(0, fileMTime2); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName1); f->byModificationTime(fileMTime1, fileMTime1); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 0) << "Found " << results->get(0)->getName(); f->byModificationTime(fileMTime1 - 1, fileMTime2 + 1); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName1); ASSERT_EQ(results->get(1)->getName(), fileName2); // getChildren(), repeat last using pagination unique_ptr p(MegaSearchPage::createInstance(0, 1)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName1); p.reset(MegaSearchPage::createInstance(0, 2)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName1); ASSERT_EQ(results->get(1)->getName(), fileName2); p.reset(MegaSearchPage::createInstance(0, 3)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName1); ASSERT_EQ(results->get(1)->getName(), fileName2); p.reset(MegaSearchPage::createInstance(1, 1)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName2); p.reset(MegaSearchPage::createInstance(1, 2)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName2); p.reset(MegaSearchPage::createInstance(2, 14)); results.reset(megaApi[0]->getChildren(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 0); // search() f->byLocationHandle(INVALID_HANDLE); f->byModificationTime(fileMTime1 - 100, fileMTimeR + 1); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(results->size(), 3); ASSERT_EQ(results->get(0)->getName(), fileName1); ASSERT_EQ(results->get(1)->getName(), fileName2); ASSERT_EQ(results->get(2)->getName(), fileNameAtRoot); f->byModificationTime(fileMTimeR, fileMTimeR); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 0); f->byModificationTime(fileMTimeR - 1, fileMTimeR + 1); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileNameAtRoot); f->byModificationTime(0, fileMTime2); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName1); f->byModificationTime(fileMTime1, 0); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC)); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName2); ASSERT_EQ(results->get(1)->getName(), fileNameAtRoot); // search(), repeat last using pagination p.reset(MegaSearchPage::createInstance(0, 1)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileName2); p.reset(MegaSearchPage::createInstance(0, 2)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName2); ASSERT_EQ(results->get(1)->getName(), fileNameAtRoot); p.reset(MegaSearchPage::createInstance(0, 3)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName2); ASSERT_EQ(results->get(1)->getName(), fileNameAtRoot); p.reset(MegaSearchPage::createInstance(1, 1)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileNameAtRoot); p.reset(MegaSearchPage::createInstance(1, 2)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), fileNameAtRoot); p.reset(MegaSearchPage::createInstance(2, 14)); results.reset(megaApi[0]->search(f.get(), MegaApi::ORDER_DEFAULT_ASC, nullptr, p.get())); ASSERT_EQ(results->size(), 0); deleteFile(fileName1); } /** * @brief TEST_F SearchNodesByNodeType * * Test filtering nodes by node type in search() and getChildren() * */ TEST_F(SdkTest, SearchNodesByNodeType) { LOG_info << "___TEST SearchNodesByNodeType___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); unique_ptr rootnode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnode); string folderName = "SearchByNodeType_Folder.Foo"; MegaHandle folderHandle = createFolder(0, folderName.c_str(), rootnode.get()); ASSERT_NE(folderHandle, INVALID_HANDLE); unique_ptr folderNode(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(folderNode); string fileName = "SearchByNodeType_File.bar"; ASSERT_TRUE(createFile(fileName, false)); MegaHandle fileHandle1 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle1, fileName.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName << " to root"; ASSERT_NE(fileHandle1, INVALID_HANDLE); unique_ptr fileNode1(megaApi[0]->getNodeByHandle(fileHandle1)); ASSERT_TRUE(fileNode1); MegaHandle fileHandle2 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle2, fileName.c_str(), folderNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName << " to " << folderName; ASSERT_NE(fileHandle2, INVALID_HANDLE); unique_ptr fileNode2(megaApi[0]->getNodeByHandle(fileHandle2)); ASSERT_TRUE(fileNode2); // getChildren() unique_ptr f(MegaSearchFilter::createInstance()); f->byName("SearchByNodeType_*"); f->byLocationHandle(rootnode->getHandle()); unique_ptr results(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 2); f->byNodeType(MegaNode::TYPE_FOLDER); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), folderName); f->byNodeType(MegaNode::TYPE_FILE); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getHandle(), fileHandle1); f->byLocationHandle(folderHandle); f->byNodeType(MegaNode::TYPE_UNKNOWN); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getHandle(), fileHandle2); f->byNodeType(MegaNode::TYPE_FOLDER); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 0); f->byNodeType(MegaNode::TYPE_FILE); results.reset(megaApi[0]->getChildren(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getHandle(), fileHandle2); // search() f->byLocationHandle(INVALID_HANDLE); f->byNodeType(MegaNode::TYPE_UNKNOWN); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 3); f->byNodeType(MegaNode::TYPE_FOLDER); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getName(), folderName); f->byNodeType(MegaNode::TYPE_FILE); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 2); ASSERT_EQ(results->get(0)->getName(), fileName); ASSERT_EQ(results->get(1)->getName(), fileName); f->byLocationHandle(folderHandle); f->byNodeType(MegaNode::TYPE_UNKNOWN); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getHandle(), fileHandle2); f->byNodeType(MegaNode::TYPE_FOLDER); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 0); f->byNodeType(MegaNode::TYPE_FILE); results.reset(megaApi[0]->search(f.get())); ASSERT_EQ(results->size(), 1); ASSERT_EQ(results->get(0)->getHandle(), fileHandle2); deleteFile(fileName); } /** * ___SdkNodesOnDemand___ * Steps: * - Configure variables to set Account2 data equal to Account1 * - login in both clients * - Client1 creates tree directory with 2 levels and some files at last level * - Check Folder info of root node from client 1 and client 2 * - Look for fingerprint and name in both clients * - Locallogout from client 1 * - Client 2 remove a node * - Client 2 check if node is present by fingerprint * - Client 1 login with session * - Check nodes by fingerprint * - Check folder info of root node from client 1 * - Check if we recover children correctly * - Remove a folder with some files * - Check Folder info of root node from client 1 and client 2 * - Move a folder to rubbish bin * - Check Folder info for root node and rubbish bin * - Locallogout and login from client 1 * - Check nodes by fingerprint without nodes in RAM */ TEST_F(SdkTest, SdkNodesOnDemand) { LOG_info << "___TEST SdkNodesOnDemand___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // --- Load User B as account 1 const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(2); megaApi.resize(2); configureTestInstance(1, email, pass); // index 1 = User B auto loginTracker = std::make_unique(megaApi[1].get()); megaApi[1]->login(email.c_str(), pass.c_str(), loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account " << email; ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); unique_ptr rootnodeB(megaApi[1]->getRootNode()); ASSERT_TRUE(rootnodeB); ASSERT_EQ(rootnodeA->getHandle(), rootnodeB->getHandle()); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; std::unique_ptr initialFolderInfo1(mApi[0].mFolderInfo->copy()); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rootnodeB.get())) << "Cannot get Folder Info"; std::unique_ptr initialFolderInfo2(mApi[1].mFolderInfo->copy()); ASSERT_EQ(initialFolderInfo1->getNumFiles(), initialFolderInfo2->getNumFiles()); ASSERT_EQ(initialFolderInfo1->getNumFolders(), initialFolderInfo2->getNumFolders()); ASSERT_EQ(initialFolderInfo1->getCurrentSize(), initialFolderInfo2->getCurrentSize()); ASSERT_EQ(initialFolderInfo1->getNumVersions(), initialFolderInfo2->getNumVersions()); ASSERT_EQ(initialFolderInfo1->getVersionsSize(), initialFolderInfo2->getVersionsSize()); // --- UserA Create tree directory --- // 3 Folders in level 1 // 4 Folders in level 2 for every folder from level 1 // 5 files in every folders from level 2 std::string folderLevel1 = "Folder"; int numberFolderLevel1 = 3; std::string folderLevel2 = "SubFolder"; int numberFolderLevel2 = 4; std::string fileName = "File"; int numberFiles = 5; std::string fileNameToSearch; std::string fingerPrintToSearch; std::string fingerPrintToRemove; MegaHandle nodeHandle = INVALID_HANDLE; MegaHandle parentHandle = INVALID_HANDLE; std::set childrenHandles; MegaHandle nodeToRemove = INVALID_HANDLE; int indexFolderToMove = 0; MegaHandle handleFolderToMove = INVALID_HANDLE; int64_t accountSize = 0; bool check1, check2; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); for (int i = 0; i < numberFolderLevel1; i++) { check1 = false; std::string folderName = folderLevel1 + "_" + std::to_string(i); auto nodeFirstLevel = createFolder(0, folderName.c_str(), rootnodeA.get()); ASSERT_NE(nodeFirstLevel, UNDEF); unique_ptr folderFirstLevel(megaApi[0]->getNodeByHandle(nodeFirstLevel)); ASSERT_TRUE(folderFirstLevel); waitForResponse(&check1); // Wait until receive nodes updated at client 2 // Save handle from folder that it's going to move to rubbish bin if (i == indexFolderToMove) { handleFolderToMove = nodeFirstLevel; } for (int j = 0; j < numberFolderLevel2; j++) { check1 = false; std::string subFolder = folderLevel2 +"_" + std::to_string(i) + "_" + std::to_string(j); auto nodeSecondLevel = createFolder(0, subFolder.c_str(), folderFirstLevel.get()); ASSERT_NE(nodeSecondLevel, UNDEF); unique_ptr subFolderSecondLevel(megaApi[0]->getNodeByHandle(nodeSecondLevel)); ASSERT_TRUE(subFolderSecondLevel); waitForResponse(&check1); // Wait until receive nodes updated at client 2 // Save handle from folder that it's going to be request children if (j == numberFolderLevel2 - 2) { parentHandle = subFolderSecondLevel->getHandle(); } // Save handle from folder that it's going to be removed if (j == numberFolderLevel2 - 3) { nodeToRemove = subFolderSecondLevel->getHandle(); } for (int k = 0; k < numberFiles; k++) { check1 = false; string filename2 = fileName + "_" + std::to_string(i) + "_" + std::to_string(j) + "_" + std::to_string(k); string content = "test_" + std::to_string(i) + "_" + std::to_string(j) + "_" + std::to_string(k); createFile(filename2, false, content); MegaHandle mh = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &mh, filename2.data(), subFolderSecondLevel.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; unique_ptr nodeFile(megaApi[0]->getNodeByHandle(mh)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; waitForResponse(&check1); // Wait until receive nodes updated at client 2 // Save fingerprint, name and handle for a file if (i == (numberFolderLevel1 - 1) && j == (numberFolderLevel2 - 1) && k == (numberFiles -1)) { fileNameToSearch = nodeFile->getName(); fingerPrintToSearch = nodeFile->getFingerprint(); nodeHandle = nodeFile->getHandle(); } if (i == (numberFolderLevel1 - 1) && j == (numberFolderLevel2 - 1) && k == (numberFiles - 2)) { fingerPrintToRemove = nodeFile->getFingerprint(); } // Save children handle from a folder if (j == numberFolderLevel2 - 2) { childrenHandles.insert(nodeFile->getHandle()); } accountSize += nodeFile->getSize(); deleteFile(filename2); } } } // important to reset resetOnNodeUpdateCompletionCBs(); accountSize += initialFolderInfo1->getCurrentSize(); ASSERT_NE(nodeToRemove, INVALID_HANDLE) << "nodeToRemove is not set"; ASSERT_NE(handleFolderToMove, INVALID_HANDLE) << "folderToMove is not set"; // --- UserA and UserB check number of files std::unique_ptr parent(megaApi[0]->getNodeByHandle(parentHandle)); ASSERT_NE(parent.get(), nullptr); ASSERT_EQ(numberFiles, megaApi[0]->getNumChildFiles(parent.get())); parent.reset(megaApi[1]->getNodeByHandle(parentHandle)); ASSERT_NE(parent.get(), nullptr); ASSERT_EQ(numberFiles, megaApi[1]->getNumChildFiles(parent.get())); // --- UserA and UserB check number of folders ASSERT_EQ(numberFolderLevel1, megaApi[0]->getNumChildFolders(rootnodeA.get())); ASSERT_EQ(numberFolderLevel1, megaApi[1]->getNumChildFolders(rootnodeB.get())); std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(rootnodeA->getHandle()); filterResults->byNodeType(MegaNode::TYPE_FOLDER); std::unique_ptr rootChildrenList(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(rootChildrenList->size(), numberFolderLevel1); // --- UserA Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; int numberTotalOfFiles = numberFolderLevel1 * numberFolderLevel2 * numberFiles + initialFolderInfo1->getNumFiles(); ASSERT_EQ(mApi[0].mFolderInfo->getNumFiles(), numberTotalOfFiles) << "Incorrect number of Files"; int numberTotalOfFolders = numberFolderLevel1 * numberFolderLevel2 + numberFolderLevel1 + initialFolderInfo1->getNumFolders(); ASSERT_EQ(mApi[0].mFolderInfo->getNumFolders(), numberTotalOfFolders) << "Incorrect number of Folders"; ASSERT_EQ(mApi[0].mFolderInfo->getCurrentSize(), accountSize) << "Incorrect account Size"; // --- UserB Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rootnodeB.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFiles(), numberTotalOfFiles) << "Incorrect number of Files"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFolders(), numberTotalOfFolders) << "Incorrect number of Folders"; ASSERT_EQ(mApi[1].mFolderInfo->getCurrentSize(), accountSize) << "Incorrect account Size"; // --- UserA get node by fingerprint --- unique_ptr fingerPrintList(megaApi[0]->getNodesByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(fingerPrintList->size(), 0); bool found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == nodeHandle) { found = true; break; } } ASSERT_TRUE(found); // --- UserA get node by fingerprint (loaded in RAM) --- unique_ptr nodeSameFingerPrint(megaApi[0]->getNodeByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(nodeSameFingerPrint.get(), nullptr); // --- UserA get node by name --- filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(fileNameToSearch.c_str()); std::unique_ptr searchList(megaApi[0]->search(filterResults.get())); ASSERT_EQ(searchList->size(), 1); ASSERT_EQ(searchList->get(0)->getHandle(), nodeHandle); // --- UserB get node by fingerprint --- fingerPrintList.reset(megaApi[1]->getNodesByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(fingerPrintList->size(), 0); found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == nodeHandle) { found = true; break; } } ASSERT_TRUE(found); // --- UserB get node by name --- filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(fileNameToSearch.c_str()); searchList.reset(megaApi[1]->search(filterResults.get())); ASSERT_EQ(searchList->size(), 1); ASSERT_EQ(searchList->get(0)->getHandle(), nodeHandle); // --- UserA logout std::unique_ptr session(megaApi[0]->dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); // --- UserB remove a node and try to find it by fingerprint check1 = false; ASSERT_GT(fingerPrintToRemove.size(), 0u); fingerPrintList.reset(megaApi[1]->getNodesByFingerprint(fingerPrintToRemove.c_str())); int nodesWithFingerPrint = fingerPrintList->size(); // Number of nodes with same fingerprint ASSERT_GT(nodesWithFingerPrint, 0); MegaHandle handleFingerprintRemove = fingerPrintList->get(0)->getHandle(); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(handleFingerprintRemove, MegaNode::CHANGE_TYPE_REMOVED, check1); unique_ptrnode(megaApi[1]->getNodeByHandle(handleFingerprintRemove)); ASSERT_EQ(API_OK, synchronousRemove(1, node.get())); waitForResponse(&check1); // Wait until receive nodes updated at client 2 nodesWithFingerPrint--; // Decrease the number of nodes with same fingerprint fingerPrintList.reset(megaApi[1]->getNodesByFingerprint(fingerPrintToRemove.c_str())); ASSERT_EQ(fingerPrintList->size(), nodesWithFingerPrint); // important to reset resetOnNodeUpdateCompletionCBs(); numberTotalOfFiles--; accountSize -= node->getSize(); PerApi& target = mApi[0]; target.resetlastEvent(); // clear any previous EVENT_NODES_CURRENT // --- UserA login with session ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // make sure that client is up to date (upon logout, recent changes might not be committed to DB) ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; // --- UserA Check if find removed node by fingerprint fingerPrintList.reset(megaApi[0]->getNodesByFingerprint(fingerPrintToRemove.c_str())); ASSERT_EQ(fingerPrintList->size(), nodesWithFingerPrint); // --- UserA Check folder info from root node --- rootnodeA.reset(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFiles(), numberTotalOfFiles) << "Incorrect number of Files"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFolders(), numberTotalOfFolders) << "Incorrect number of Folders"; ASSERT_EQ(mApi[0].mFolderInfo->getCurrentSize(), accountSize) << "Incorrect account Size"; // --- UserA get node by fingerprint (Without nodes in RAM) --- nodeSameFingerPrint.reset(megaApi[0]->getNodeByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(nodeSameFingerPrint.get(), nullptr); // --- UserA get nodes by fingerprint, some of them are loaded in RAM fingerPrintList.reset(megaApi[0]->getNodesByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(fingerPrintList->size(), 0); found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == nodeHandle) { found = true; break; } } ASSERT_TRUE(found); // --- UserA get nodes by fingerprint, all of them are loaded in RAM fingerPrintList.reset(megaApi[0]->getNodesByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(fingerPrintList->size(), 0); found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == nodeHandle) { found = true; break; } } ASSERT_TRUE(found); // --- UserA check children --- if (parentHandle != INVALID_HANDLE) // Get children { unique_ptrnode(megaApi[0]->getNodeByHandle(parentHandle)); ASSERT_NE(node, nullptr); std::unique_ptr childrenList(megaApi[0]->getChildren(node.get())); ASSERT_GT(childrenList->size(), 0); for (int childIndex = 0; childIndex < childrenList->size(); childIndex++) { ASSERT_NE(childrenHandles.find(childrenList->get(childIndex)->getHandle()), childrenHandles.end()); } filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(node->getHandle()); filterResults->byNodeType(MegaNode::TYPE_FILE); std::unique_ptr fileChildrenList( megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(fileChildrenList->size(), childrenList->size()); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(node->getHandle()); filterResults->byNodeType(MegaNode::TYPE_FOLDER); std::unique_ptr folderChildrenList( megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(folderChildrenList->size(), 0); } // --- UserA remove a folder --- mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeToRemove, MegaNode::CHANGE_TYPE_REMOVED, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeToRemove, MegaNode::CHANGE_TYPE_REMOVED, check2); node.reset(megaApi[0]->getNodeByHandle(nodeToRemove)); ASSERT_NE(node, nullptr); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, node.get())) << "Cannot get Folder Info"; std::unique_ptr removedFolder(mApi[0].mFolderInfo->copy()); ASSERT_EQ(API_OK, synchronousRemove(0, node.get())); node.reset(megaApi[0]->getNodeByHandle(nodeToRemove)); ASSERT_EQ(node, nullptr); waitForResponse(&check1); // Wait until receive nodes updated at client 1 // --- UserA Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFiles(), numberTotalOfFiles - removedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFolders(), numberTotalOfFolders - (removedFolder->getNumFolders() + 1)) << "Incorrect number of Folders"; ASSERT_EQ(mApi[0].mFolderInfo->getCurrentSize(), accountSize - removedFolder->getCurrentSize()) << "Incorrect account Size"; waitForResponse(&check2); // Wait until receive nodes updated at client 2 // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- UserB Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rootnodeB.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFiles(), numberTotalOfFiles - removedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFolders(), numberTotalOfFolders - (removedFolder->getNumFolders() + 1)) << "Incorrect number of Folders"; ASSERT_EQ(mApi[1].mFolderInfo->getCurrentSize(), accountSize - removedFolder->getCurrentSize()) << "Incorrect account Size"; unique_ptr nodeToMove(megaApi[0]->getNodeByHandle(handleFolderToMove)); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, nodeToMove.get())) << "Cannot get Folder Info from node to Move"; std::unique_ptr movedFolder(mApi[0].mFolderInfo->copy()); unique_ptr rubbishBinA(megaApi[1]->getRubbishNode()); ASSERT_TRUE(rubbishBinA); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(handleFolderToMove, MegaNode::CHANGE_TYPE_PARENT, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(handleFolderToMove, MegaNode::CHANGE_TYPE_PARENT, check2); mApi[0].requestFlags[MegaRequest::TYPE_MOVE] = false; megaApi[0]->moveNode(nodeToMove.get(), rubbishBinA.get()); ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_MOVE]) ) << "Move operation failed after " << maxTimeout << " seconds"; ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot move node (error: " << mApi[0].lastError << ")"; waitForResponse(&check1); // Wait until receive nodes updated at client 1 waitForResponse(&check2); // Wait until receive nodes updated at client 2 // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // --- UserA Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFiles(), numberTotalOfFiles - removedFolder->getNumFiles() - movedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFolders(), numberTotalOfFolders - (removedFolder->getNumFolders() + 1) - (movedFolder->getNumFolders() + 1)) << "Incorrect number of Folders"; ASSERT_EQ(mApi[0].mFolderInfo->getCurrentSize(), accountSize - removedFolder->getCurrentSize() - movedFolder->getCurrentSize()) << "Incorrect account Size"; // --- UserB Check folder info from root node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rootnodeB.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFiles(), numberTotalOfFiles - removedFolder->getNumFiles() - movedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFolders(), numberTotalOfFolders - (removedFolder->getNumFolders() + 1) - (movedFolder->getNumFolders() + 1)) << "Incorrect number of Folders"; ASSERT_EQ(mApi[1].mFolderInfo->getCurrentSize(), accountSize - removedFolder->getCurrentSize() - movedFolder->getCurrentSize()) << "Incorrect account Size"; // --- UserA Check folder info from rubbish node --- ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rubbishBinA.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFiles(), movedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[0].mFolderInfo->getNumFolders(), movedFolder->getNumFolders() + 1) << "Incorrect number of Folders"; ASSERT_EQ(mApi[0].mFolderInfo->getCurrentSize(), movedFolder->getCurrentSize()) << "Incorrect account Size"; // --- UserB Check folder info from rubbish node --- unique_ptr rubbishBinB(megaApi[1]->getRubbishNode()); ASSERT_TRUE(rubbishBinB); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rubbishBinB.get())) << "Cannot get Folder Info"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFiles(), movedFolder->getNumFiles()) << "Incorrect number of Files"; ASSERT_EQ(mApi[1].mFolderInfo->getNumFolders(), movedFolder->getNumFolders() + 1) << "Incorrect number of Folders"; ASSERT_EQ(mApi[1].mFolderInfo->getCurrentSize(), movedFolder->getCurrentSize()) << "Incorrect account Size"; ASSERT_NO_FATAL_FAILURE(locallogout()); // --- UserA login with session target.resetlastEvent(); // clear any previous EVENT_NODES_CURRENT ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // make sure that client is up to date (upon logout, recent changes might not be committed to DB) ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; // --- UserA get nodes by fingerprint, none of them are loaded in RAM fingerPrintList.reset(megaApi[0]->getNodesByFingerprint(fingerPrintToSearch.c_str())); ASSERT_NE(fingerPrintList->size(), 0); found = false; for (int i = 0; i < fingerPrintList->size(); i++) { if (fingerPrintList->get(i)->getHandle() == nodeHandle) { found = true; break; } } ASSERT_TRUE(found); } /** * SdkNodesOnDemandVersions * Steps: * - Configure variables to set Account2 data equal to Account1 * - login in both clients * - Client 1 File and after add a modification of that file (version) * - Check Folder info of root node from client 1 and client 2 */ TEST_F(SdkTest, SdkNodesOnDemandVersions) { LOG_info << "___TEST SdkNodesOnDemandVersions"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // --- Load User B as account 1 const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(2); megaApi.resize(2); configureTestInstance(1, email, pass); // index 1 = User B auto loginTracker = std::make_unique(megaApi[1].get()); megaApi[1]->login(email.c_str(), pass.c_str(), loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account " << email; ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); unique_ptr rootnodeB(megaApi[1]->getRootNode()); ASSERT_TRUE(rootnodeB); ASSERT_EQ(rootnodeA->getHandle(), rootnodeB->getHandle()); std::string fileName = "file"; bool check1, check2; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); string content1 = "test_1"; createFile(fileName, false, content1); MegaHandle fh = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fh, fileName.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; unique_ptr nodeFile(megaApi[0]->getNodeByHandle(fh)); synchronousSetNodeSensitive(0, nodeFile.get(), true); synchronousSetNodeFavourite(0, nodeFile.get(), true); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; long long size1 = nodeFile->getSize(); waitForResponse(&check1); // Wait until receive nodes updated at client 1 waitForResponse(&check2); // Wait until receive nodes updated at client 2 ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); deleteFile(fileName); // important to reset resetOnNodeUpdateCompletionCBs(); // check no versions exist yet in either client { unique_ptr n1(megaApi[0]->getNodeByPath(("/" + fileName).c_str())); unique_ptr n2(megaApi[1]->getNodeByPath(("/" + fileName).c_str())); ASSERT_TRUE(n1 && !megaApi[0]->hasVersions(n1.get())); ASSERT_TRUE(n2 && !megaApi[1]->hasVersions(n2.get())); ASSERT_TRUE(n1 && 1 == megaApi[0]->getNumVersions(n1.get())); ASSERT_TRUE(n2 && 1 == megaApi[1]->getNumVersions(n2.get())); } // upload a file to replace the last one in the root of client 0 // of course client 1 will see the same new file (and the old file becomes a version, if versioning is on. Built with ENABLE_SYNC or not is irrelevant) mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check2); string content2 = "test_2"; createFile(fileName, false, content2); fh = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fh, fileName.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; nodeFile.reset(megaApi[0]->getNodeByHandle(fh)); long long size2 = nodeFile->getSize(); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")"; waitForResponse(&check1); // Wait until receive nodes updated at client 1 waitForResponse(&check2); // Wait until receive nodes updated at client 2 ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); deleteFile(fileName); // important to reset resetOnNodeUpdateCompletionCBs(); // check both client now know the file has versons { unique_ptr n1(megaApi[0]->getNodeByPath(("/" + fileName).c_str())); unique_ptr n2(megaApi[1]->getNodeByPath(("/" + fileName).c_str())); ASSERT_TRUE(n1 && megaApi[0]->hasVersions(n1.get())); ASSERT_TRUE(n2 && megaApi[1]->hasVersions(n2.get())); ASSERT_TRUE(n1 && 2 == megaApi[0]->getNumVersions(n1.get())); ASSERT_TRUE(n2 && 2 == megaApi[1]->getNumVersions(n2.get())); } nodeFile.reset(megaApi[0]->getNodeByHandle(fh)); unique_ptr list(megaApi[0]->getChildren(nodeFile.get())); // null unique_ptr vlist(megaApi[0]->getVersions(nodeFile.get())); MegaNode *n0 = vlist->get(0); MegaNode* n1 = vlist->get(1); ASSERT_TRUE(n0->isFavourite()); ASSERT_TRUE(n0->isMarkedSensitive()); ASSERT_TRUE(n1->isFavourite()); ASSERT_TRUE(n1->isMarkedSensitive()); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(0, rootnodeA.get())) << "Cannot get Folder Info"; std::unique_ptr initialFolderInfo1(mApi[0].mFolderInfo->copy()); ASSERT_EQ(initialFolderInfo1->getNumFiles(), 1); ASSERT_EQ(initialFolderInfo1->getNumFolders(), 0); ASSERT_EQ(initialFolderInfo1->getNumVersions(), 1); ASSERT_EQ(initialFolderInfo1->getCurrentSize(), size2); ASSERT_EQ(initialFolderInfo1->getVersionsSize(), size1); ASSERT_EQ(MegaError::API_OK, synchronousFolderInfo(1, rootnodeB.get())) << "Cannot get Folder Info"; std::unique_ptr initialFolderInfo2(mApi[1].mFolderInfo->copy()); ASSERT_EQ(initialFolderInfo1->getNumFiles(), initialFolderInfo2->getNumFiles()); ASSERT_EQ(initialFolderInfo1->getNumFolders(), initialFolderInfo2->getNumFolders()); ASSERT_EQ(initialFolderInfo1->getNumVersions(), initialFolderInfo2->getNumVersions()); ASSERT_EQ(initialFolderInfo1->getCurrentSize(), initialFolderInfo2->getCurrentSize()); ASSERT_EQ(initialFolderInfo1->getVersionsSize(), initialFolderInfo2->getVersionsSize()); } /** * @brief TEST_F SdkTestSetsAndElements * * Tests creating, modifying and removing Sets and Elements. */ TEST_F(SdkTest, SdkTestSetsAndElements) { LOG_info << "___TEST Sets and Elements___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // 1. Create Set // 2. Update Set name // 3. Upload test files // 4. Add Element // 5. Update Element order // 6. Update Element name // 7. Add an element with an already added node (-12 expected) // 8. Remove Element // 9. Add/remove bulk elements // 10. Logout / login // 11. Remove all Sets // Use another connection with the same credentials megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(1).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT))); auto& differentApi = *megaApi.back(); differentApi.addListener(this); PerApi pa; // make a copy pa.email = mApi.back().email; pa.pwd = mApi.back().pwd; mApi.push_back(std::move(pa)); auto& differentApiDtls = mApi.back(); differentApiDtls.megaApi = &differentApi; int differentApiIdx = int(megaApi.size() - 1); auto loginTracker = asyncRequestLogin(static_cast(differentApiIdx), differentApiDtls.email.c_str(), differentApiDtls.pwd.c_str()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to establish a login/session for account " << differentApiIdx; loginTracker = asyncRequestFetchnodes(static_cast(differentApiIdx)); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to fetch nodes for account " << differentApiIdx; // 1. Create Set string name = U8("Set name ideograms: 讓我們打破這個"); // "讓我們打破這個" differentApiDtls.setUpdated = false; MegaSet* newSet = nullptr; int err = doCreateSet(0, &newSet, name.c_str(), MegaSet::SET_TYPE_ALBUM); ASSERT_EQ(err, API_OK); unique_ptr s1p(newSet); ASSERT_NE(s1p, nullptr); ASSERT_NE(s1p->id(), INVALID_HANDLE); ASSERT_EQ(s1p->name(), name); ASSERT_NE(s1p->ts(), 0); ASSERT_NE(s1p->cts(), 0) << "Create-timestamp of a Set was not set"; ASSERT_NE(s1p->user(), INVALID_HANDLE); MegaHandle sh = s1p->id(); int64_t setCrTs = s1p->cts(); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set create AP not received after " << maxTimeout << " seconds"; unique_ptr s2p(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); ASSERT_EQ(s2p->id(), s1p->id()); ASSERT_EQ(s2p->name(), name); ASSERT_EQ(s2p->ts(), s1p->ts()); ASSERT_EQ(s2p->cts(), s1p->cts()) << "Create-timestamp of a Set differed in Action Packet"; ASSERT_EQ(s2p->user(), s1p->user()); // Clear Set name differentApiDtls.setUpdated = false; err = doUpdateSetName(0, nullptr, sh, ""); ASSERT_EQ(err, API_OK); unique_ptr s1clearname(megaApi[0]->getSet(sh)); ASSERT_NE(s1clearname, nullptr); ASSERT_STREQ(s1clearname->name(), ""); ASSERT_EQ(s1clearname->cts(), setCrTs) << "Create-timestamp of a Set has changed after name change"; // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set update AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); ASSERT_STREQ(s2p->name(), ""); ASSERT_EQ(s2p->cts(), setCrTs) << "Create-timestamp of a Set has changed after name change AP"; // 2. Update Set name MegaHandle shu = INVALID_HANDLE; name += U8(" updated"); differentApiDtls.setUpdated = false; err = doUpdateSetName(0, &shu, sh, name.c_str()); ASSERT_EQ(err, API_OK); ASSERT_EQ(shu, sh); unique_ptr s1up(megaApi[0]->getSet(shu)); ASSERT_NE(s1up, nullptr); ASSERT_EQ(s1up->id(), sh); ASSERT_EQ(s1up->name(), name); ASSERT_EQ(s1up->user(), s1p->user()); //ASSERT_NE(s1up->ts(), s1p->ts()); // apparently this is not always updated // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set update AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); ASSERT_EQ(s2p->name(), name); ASSERT_EQ(s2p->ts(), s1up->ts()); ASSERT_EQ(s2p->cts(), s1up->cts()); // 3. Upload test files std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; ASSERT_TRUE(createFile(UPFILE, false)) << "Couldn't create " << UPFILE; MegaHandle uploadedNode = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode, UPFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; string filename2 = UPFILE + "2"; ASSERT_TRUE(createFile(filename2, false)) << "Couldn't create " << filename2; MegaHandle uploadedNode2 = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode2, filename2.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Could not upload test file " << filename2; // 4. Add Element string elattrs = U8("Element name emoji: 📞🎉❤️"); // "📞🎉❤️" differentApiDtls.setElementUpdated = false; MegaSetElementList* newElls = nullptr; err = doCreateSetElement(0, &newElls, sh, uploadedNode, elattrs.c_str()); ASSERT_EQ(err, API_OK); unique_ptr els(newElls); ASSERT_NE(els, nullptr); ASSERT_EQ(els->size(), 1u); ASSERT_EQ(els->get(0)->node(), uploadedNode); ASSERT_EQ(els->get(0)->setId(), sh); ASSERT_EQ(els->get(0)->name(), elattrs); ASSERT_NE(els->get(0)->ts(), 0); ASSERT_EQ(els->get(0)->order(), 1000); MegaHandle eh = els->get(0)->id(); unique_ptr elp(megaApi[0]->getSetElement(sh, eh)); ASSERT_NE(elp, nullptr); ASSERT_EQ(elp->id(), eh); ASSERT_EQ(elp->node(), uploadedNode); ASSERT_EQ(elp->setId(), sh); ASSERT_EQ(elp->name(), elattrs); ASSERT_NE(elp->ts(), 0); ASSERT_EQ(elp->order(), 1000); // first default value, according to specs unsigned elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 1u); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element add AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); unique_ptr els2(differentApi.getSetElements(sh)); ASSERT_NE(els2, nullptr); ASSERT_EQ(els2->size(), els->size()); unique_ptr elp2(differentApi.getSetElement(sh, eh)); ASSERT_NE(elp2, nullptr); ASSERT_EQ(elp2->id(), elp->id()); ASSERT_EQ(elp2->node(), elp->node()); ASSERT_EQ(elp2->setId(), elp->setId()); ASSERT_EQ(elp2->name(), elattrs); ASSERT_EQ(elp2->ts(), elp->ts()); ASSERT_EQ(elp2->order(), elp->order()); elCount = differentApi.getSetElementCount(sh); ASSERT_EQ(elCount, 1u); // Move element's file to Rubbish Bin std::unique_ptr elementNode(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_TRUE(elementNode) << "File node of Element not found"; std::unique_ptr rubbishNode(megaApi[0]->getRubbishNode()); ASSERT_TRUE(rubbishNode) << "Rubbish Bin node not found"; ASSERT_EQ(API_OK, doMoveNode(0, nullptr, elementNode.get(), rubbishNode.get())) << "Couldn't move node to Rubbish Bin"; els2.reset(megaApi[0]->getSetElements(sh)); ASSERT_EQ(els2->size(), 1u) << "Wrong all Element-s, including Rubbish Bin (1 file moved to Rubbish)"; elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 1u) << "Wrong Element count, including Rubbish Bin (1 file moved to Rubbish)"; els2.reset(megaApi[0]->getSetElements(sh, false)); ASSERT_EQ(els2->size(), 0u) << "Wrong all Element-s, excluding Rubbish Bin (1 file moved to Rubbish)"; elCount = megaApi[0]->getSetElementCount(sh, false); ASSERT_EQ(elCount, 0u) << "Wrong Element count, excluding Rubbish Bin (1 file moved to Rubbish)"; // Restore Element's file from Rubbish Bin ASSERT_EQ(API_OK, doMoveNode(0, nullptr, elementNode.get(), rootnode.get())) << "Couldn't restore node from Rubbish Bin"; els2.reset(megaApi[0]->getSetElements(sh)); ASSERT_EQ(els2->size(), 1u) << "Wrong all Element-s, including Rubbish Bin (no files in Rubbish)"; elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 1u) << "Wrong Element count, including Rubbish Bin (no files in Rubbish)"; els2.reset(megaApi[0]->getSetElements(sh, false)); ASSERT_EQ(els2->size(), 1u) << "Wrong all Element-s, excluding Rubbish Bin (no files in Rubbish)"; elCount = megaApi[0]->getSetElementCount(sh, false); ASSERT_EQ(elCount, 1u) << "Wrong Element count, excluding Rubbish Bin (no files in Rubbish)"; // Clear Element name differentApiDtls.setElementUpdated = false; err = doUpdateSetElementName(0, nullptr, sh, eh, ""); ASSERT_EQ(err, API_OK); unique_ptr elclearname(megaApi[0]->getSetElement(sh, eh)); ASSERT_NE(elclearname, nullptr); ASSERT_STREQ(elclearname->name(), ""); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element update AP not received after " << maxTimeout << " seconds"; elp2.reset(differentApi.getSetElement(sh, eh)); ASSERT_NE(elp2, nullptr); ASSERT_STREQ(elp2->name(), ""); // Add cover to Set differentApiDtls.setUpdated = false; err = doPutSetCover(0, nullptr, sh, eh); ASSERT_EQ(err, API_OK); s1up.reset(megaApi[0]->getSet(sh)); ASSERT_EQ(s1up->name(), name); ASSERT_EQ(s1up->cover(), eh); ASSERT_EQ(megaApi[0]->getSetCover(sh), eh); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set cover update AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); ASSERT_EQ(s2p->name(), name); ASSERT_EQ(s2p->cover(), eh); // Remove cover from Set differentApiDtls.setUpdated = false; err = doPutSetCover(0, nullptr, sh, INVALID_HANDLE); ASSERT_EQ(err, API_OK); s1up.reset(megaApi[0]->getSet(sh)); ASSERT_EQ(s1up->name(), name); ASSERT_EQ(s1up->cover(), INVALID_HANDLE); ASSERT_EQ(megaApi[0]->getSetCover(sh), INVALID_HANDLE); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set cover removal AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); ASSERT_EQ(s2p->name(), name); ASSERT_EQ(s2p->cover(), INVALID_HANDLE); // 5. Update Element order MegaHandle el1 = INVALID_HANDLE; int64_t order = 222; differentApiDtls.setElementUpdated = false; err = doUpdateSetElementOrder(0, &el1, sh, eh, order); ASSERT_EQ(err, API_OK); ASSERT_EQ(el1, eh); unique_ptr elu1p(megaApi[0]->getSetElement(sh, eh)); ASSERT_NE(elu1p, nullptr); ASSERT_EQ(elu1p->id(), eh); ASSERT_EQ(elu1p->node(), uploadedNode); ASSERT_EQ(elu1p->setId(), sh); ASSERT_STREQ(elu1p->name(), ""); ASSERT_EQ(elu1p->order(), order); ASSERT_NE(elu1p->ts(), 0); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element order change AP not received after " << maxTimeout << " seconds"; elp2.reset(differentApi.getSetElement(sh, eh)); ASSERT_NE(elp2, nullptr); ASSERT_STREQ(elp2->name(), ""); ASSERT_EQ(elp2->order(), elu1p->order()); // 6. Update Element name MegaHandle el2 = INVALID_HANDLE; elattrs += U8(" updated"); differentApiDtls.setElementUpdated = false; err = doUpdateSetElementName(0, &el2, sh, eh, elattrs.c_str()); ASSERT_EQ(err, API_OK); ASSERT_EQ(el2, eh); elu1p.reset(megaApi[0]->getSetElement(sh, eh)); ASSERT_EQ(elu1p->id(), eh); ASSERT_EQ(elu1p->name(), elattrs); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element name change AP not received after " << maxTimeout << " seconds"; elp2.reset(differentApi.getSetElement(sh, eh)); ASSERT_NE(elp2, nullptr); ASSERT_EQ(elp2->name(), elattrs); // 7. Add an element with an already added node (-12 expected) string elattrs1b = U8("Another element name emoji: 📞🎉❤️"); // "📞🎉❤️" newElls = nullptr; err = doCreateSetElement(0, &newElls, sh, uploadedNode, elattrs1b.c_str()); ASSERT_EQ(err, API_EEXIST) << "Adding another SetElement with the same node as already existing SetElement"; elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 1u); // 8. Remove Element differentApiDtls.setElementUpdated = false; err = doRemoveSetElement(0, sh, eh); ASSERT_EQ(err, API_OK); elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 0u); elp.reset(megaApi[0]->getSetElement(sh, eh)); ASSERT_EQ(elp, nullptr); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element remove AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(sh)); ASSERT_NE(s2p, nullptr); els2.reset(differentApi.getSetElements(sh)); ASSERT_EQ(els2->size(), 0u); elp2.reset(differentApi.getSetElement(sh, eh)); ASSERT_EQ(elp2, nullptr); // 9. Add/remove bulk elements // Add 3; only the first will succeed differentApiDtls.setElementUpdated = false; string elattrs2 = elattrs + U8(" bulk2"); elattrs += U8(" bulk1"); newElls = nullptr; MegaIntegerList* newElErrs = nullptr; unique_ptr newElFileHandles(MegaHandleList::createInstance()); newElFileHandles->addMegaHandle(uploadedNode); newElFileHandles->addMegaHandle(INVALID_HANDLE); newElFileHandles->addMegaHandle(uploadedNode); unique_ptr newElNames(MegaStringList::createInstance()); newElNames->add(elattrs.c_str()); newElNames->add(elattrs2.c_str()); newElNames->add(elattrs.c_str()); err = doCreateBulkSetElements(0, &newElls, &newElErrs, sh, newElFileHandles.get(), newElNames.get()); els.reset(newElls); unique_ptr elErrs(newElErrs); ASSERT_EQ(err, API_OK); ASSERT_NE(newElls, nullptr); ASSERT_EQ(newElls->size(), 1u); ASSERT_EQ(newElls->get(0)->name(), elattrs); eh = newElls->get(0)->id(); ASSERT_NE(eh, INVALID_HANDLE); ASSERT_NE(newElErrs, nullptr); ASSERT_EQ(newElErrs->size(), 3); ASSERT_EQ(newElErrs->get(0), API_OK); ASSERT_EQ(newElErrs->get(1), API_EARGS); // API_EARGS because sending an empty key error takes precedence over sending INVALID_HANDLE for eid error (API_ENOENT) ASSERT_EQ(newElErrs->get(2), API_EEXIST); unique_ptr newEl(megaApi[0]->getSetElement(sh, eh)); ASSERT_NE(newEl, nullptr); ASSERT_EQ(newEl->id(), eh); ASSERT_EQ(newEl->name(), elattrs); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element add AP not received after " << maxTimeout << " seconds"; // Remove 2, only the first one will succeed differentApiDtls.setElementUpdated = false; unique_ptr removedElIds(MegaHandleList::createInstance()); removedElIds->addMegaHandle(eh); removedElIds->addMegaHandle(INVALID_HANDLE); MegaIntegerList* removedElErrs = nullptr; err = doRemoveBulkSetElements(0, &removedElErrs, sh, removedElIds.get()); elErrs.reset(removedElErrs); ASSERT_EQ(err, API_OK); ASSERT_NE(removedElErrs, nullptr); ASSERT_EQ(removedElErrs->size(), 2); ASSERT_EQ(removedElErrs->get(0), API_OK); ASSERT_EQ(removedElErrs->get(1), API_ENOENT); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element remove AP not received after " << maxTimeout << " seconds"; // Add 2 more; both will succeed differentApiDtls.setElementUpdated = false; newElls = nullptr; newElErrs = nullptr; newElFileHandles.reset(MegaHandleList::createInstance()); newElFileHandles->addMegaHandle(uploadedNode); newElFileHandles->addMegaHandle(uploadedNode2); newElNames.reset(MegaStringList::createInstance()); string namebulk11 = elattrs + "1"; newElNames->add(namebulk11.c_str()); string namebulk12 = elattrs + "2"; newElNames->add(namebulk12.c_str()); err = doCreateBulkSetElements(0, &newElls, &newElErrs, sh, newElFileHandles.get(), newElNames.get()); els.reset(newElls); ASSERT_EQ(err, API_OK); ASSERT_NE(newElls, nullptr); ASSERT_EQ(newElls->size(), 2u); ASSERT_EQ(newElls->get(1)->name(), namebulk12); MegaHandle ehBulk = newElls->get(1)->id(); ASSERT_NE(ehBulk, INVALID_HANDLE); const MegaSetElement* elp_b4lo = newElls->get(0); ASSERT_NE(elp_b4lo, nullptr); ASSERT_EQ(elp_b4lo->name(), namebulk11); ehBulk = elp_b4lo->id(); ASSERT_NE(ehBulk, INVALID_HANDLE); ASSERT_NE(newElErrs, nullptr); ASSERT_EQ(newElErrs->size(), 2); ASSERT_EQ(newElErrs->get(0), API_OK); ASSERT_EQ(newElErrs->get(1), API_OK); elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 2u); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setElementUpdated)) << "Element add AP not received after " << maxTimeout << " seconds"; // create a dummy folder, just to trigger a local db commit before locallogout (which triggers a ROLLBACK) PerApi& target = mApi[0]; target.resetlastEvent(); MegaHandle hDummyFolder = createFolder(0, "DummyFolder_TriggerDbCommit", rootnode.get()); ASSERT_NE(hDummyFolder, INVALID_HANDLE); ASSERT_TRUE(WaitFor([&target]() { return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 8192)); // 10. Logout / login unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); s1p.reset(megaApi[0]->getSet(sh)); ASSERT_EQ(s1p, nullptr); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // load cached Sets s1p.reset(megaApi[0]->getSet(sh)); ASSERT_NE(s1p, nullptr); ASSERT_EQ(s1p->id(), sh); ASSERT_EQ(s1p->user(), s1up->user()); ASSERT_EQ(s1p->ts(), s1up->ts()); ASSERT_EQ(s1p->cts(), s1up->cts()); ASSERT_EQ(s1p->name(), name); elCount = megaApi[0]->getSetElementCount(sh); ASSERT_EQ(elCount, 2u) << "Wrong Element count after resumeSession"; unique_ptr ellp(megaApi[0]->getSetElement(sh, ehBulk)); ASSERT_NE(ellp, nullptr); ASSERT_EQ(ellp->id(), elp_b4lo->id()); ASSERT_EQ(ellp->node(), elp_b4lo->node()); ASSERT_EQ(ellp->setId(), elp_b4lo->setId()); ASSERT_EQ(ellp->ts(), elp_b4lo->ts()); ASSERT_EQ(ellp->name(), namebulk11); // 11. Remove all Sets unique_ptr sets(megaApi[0]->getSets()); unique_ptr sets2(differentApi.getSets()); ASSERT_EQ(sets->size(), sets2->size()); for (unsigned i = 0; i < sets->size(); ++i) { handle setId = sets->get(i)->id(); differentApiDtls.setUpdated = false; err = doRemoveSet(0, setId); ASSERT_EQ(err, API_OK); s1p.reset(megaApi[0]->getSet(setId)); ASSERT_EQ(s1p, nullptr); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtls.setUpdated)) << "Set remove AP not received after " << maxTimeout << " seconds"; s2p.reset(differentApi.getSet(setId)); ASSERT_EQ(s2p, nullptr); } sets.reset(megaApi[0]->getSets()); ASSERT_EQ(sets->size(), 0u); sets2.reset(differentApi.getSets()); ASSERT_EQ(sets2->size(), 0u); } /** * @brief TEST_F SdkTestSetsAndElementsPublicLink * * Tests creating, modifying and removing Sets and Elements. */ TEST_F(SdkTest, SdkTestSetsAndElementsPublicLink) { LOG_info << "___TEST Sets and Elements Public Link___"; // U1: Create set // U1: Upload test file // U1: Add Element to Set // U1: Check if Set is exported // U1: Enable Set export (creates public link) // U1: Check if Set is exported // U1: Update Set name and verify Set is still exported // U1: Logout / login to retrieve Set // U1: Check if Set is exported // U1: Get public Set URL // U1: Fetch Public Set and start Public Set preview mode // U1: Stop Public Set preview mode // U2: Attempt to fetch public Set using wrong key // U2: Fetch public Set and start preview mode // U2: Download foreign Set Element in preview set mode // U2: Stop Public Set preview mode // U2: Download foreign Set Element not in preview set mode (-11 and -9 expected) // U1: Disable Set export (invalidates public link) // U1: Get public Set URL (-9 expected) // U1: Sync fetch public Set on non-exported Set (using previously valid link), nullptr expected // U1: Remove all Sets ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Use another connection with the same credentials as U1 MegaApi* differentApiPtr = nullptr; PerApi* differentApiDtlsPtr = nullptr; unsigned userIdx = 0; megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(static_cast(userIdx)).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT))); differentApiPtr = &(*megaApi.back()); differentApiPtr->addListener(this); PerApi pa; // make a copy auto& aux = mApi[userIdx]; pa.email = aux.email; pa.pwd = aux.pwd; mApi.push_back(std::move(pa)); differentApiDtlsPtr = &(mApi.back()); differentApiDtlsPtr->megaApi = differentApiPtr; unsigned difApiIdx = static_cast(megaApi.size() - 1); auto loginTracker = asyncRequestLogin(difApiIdx, differentApiDtlsPtr->email.c_str(), differentApiDtlsPtr->pwd.c_str()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to establish a login/session for account " << difApiIdx; loginTracker = asyncRequestFetchnodes(difApiIdx); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to fetch nodes for account " << difApiIdx; LOG_debug << "# U1: Create set"; const string name = U8("qq-001"); MegaSet* newSet = nullptr; differentApiDtlsPtr->setUpdated = false; ASSERT_EQ(API_OK, doCreateSet(0, &newSet, name.c_str(), MegaSet::SET_TYPE_ALBUM)); ASSERT_NE(newSet, nullptr); const unique_ptr s1p(newSet); const MegaHandle sh = s1p->id(); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive create Set AP on U1's secondary client"; LOG_debug << "# U1: Upload test file"; userIdx = 0; unique_ptr rootnode{megaApi[userIdx]->getRootNode()}; ASSERT_TRUE(createFile(UPFILE, false)) << "Couldn't create " << UPFILE; MegaHandle uploadedNode = INVALID_HANDLE; ASSERT_EQ(API_OK, doStartUpload(userIdx, &uploadedNode, UPFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; LOG_debug << "# U1: Add Element to Set"; userIdx = 0; differentApiDtlsPtr->setElementUpdated = false; const string elattrs = U8("Element name emoji: 🐧"); MegaSetElementList* newEll = nullptr; ASSERT_EQ(API_OK, doCreateSetElement(userIdx, &newEll, sh, uploadedNode, elattrs.c_str())); ASSERT_NE(newEll, nullptr); const unique_ptr els(newEll); const MegaHandle eh = els->get(0)->id(); const unique_ptr elp(megaApi[userIdx]->getSetElement(sh, eh)); ASSERT_NE(elp, nullptr); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setElementUpdated)) << "Failed to receive add Element update AP on U1's secondary"; LOG_debug << "# U1: Check if Set is exported"; ASSERT_FALSE(megaApi[0]->isExportedSet(sh)) << "Set should not be public yet"; LOG_debug << "# U1: Enable Set export (creates public link)"; userIdx = 0; ASSERT_FALSE(megaApi[userIdx]->isExportedSet(sh)); MegaSet* exportedSet = nullptr; string exportedSetURL; differentApiDtlsPtr->setUpdated = false; ASSERT_EQ(API_OK, doExportSet(userIdx, &exportedSet, exportedSetURL, sh)); bool isExpectedToBeExported = true; ASSERT_FALSE(exportedSetURL.empty()); unique_ptr s1pEnabledExport(exportedSet); LOG_debug << "\tChecking Set from export request"; const auto lIsSameSet = [&s1p](const MegaSet* s, bool isExported) { ASSERT_NE(s, nullptr); ASSERT_EQ(s1p->id(), s->id()); ASSERT_STREQ(s1p->name(), s->name()); ASSERT_EQ(isExported, s->isExported()); ASSERT_NE(s->ts(), 0); }; ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pEnabledExport.get(), isExpectedToBeExported)); s1pEnabledExport.reset(megaApi[userIdx]->getSet(sh)); LOG_debug << "\tChecking Set from MegaApi::getSet"; ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pEnabledExport.get(), isExpectedToBeExported)); // test action packets ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive export Set update AP on U1's secondary client"; s1pEnabledExport.reset(differentApiPtr->getSet(sh)); LOG_debug << "\tChecking Set from MegaApi::getSet for differentApi (AKA U1 in a different client)"; ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pEnabledExport.get(), isExpectedToBeExported)); // test shortcut LOG_debug << "\tChecking export enable shortcut"; exportedSet = nullptr; ASSERT_EQ(API_OK, doExportSet(userIdx, &exportedSet, exportedSetURL, sh)); s1pEnabledExport.reset(exportedSet); ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pEnabledExport.get(), isExpectedToBeExported)); LOG_debug << "# U1: Check if Set is exported"; ASSERT_TRUE(megaApi[0]->isExportedSet(sh)) << "Set should already be public"; LOG_debug << "# U1: Update Set name and verify Set is still exported"; userIdx = 0; differentApiDtlsPtr->setUpdated = false; const string updatedName = name + U8(" 手"); ASSERT_EQ(API_OK, doUpdateSetName(userIdx, nullptr, sh, updatedName.c_str())); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive shared Set name updated AP on U1's secondary client"; ASSERT_TRUE(megaApi[userIdx]->isExportedSet(sh)) << "Set should still be public after the update"; // reset to previous name to keep using existing original cached Set for validation differentApiDtlsPtr->setUpdated = false; PerApi& target = mApi[userIdx]; target.resetlastEvent(); // So we can detect when the node database has been committed. ASSERT_EQ(API_OK, doUpdateSetName(userIdx, nullptr, sh, name.c_str())); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive shared Set name reset updated AP on U1's secondary client"; // Wait for the database to be updated (note: even if commit is triggered by 1st update, the 2nd update // has been applied already, so the DB will store the final value) ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, maxTimeout*1000)) << "Failed to receive commit to DB event related to Set name update"; LOG_debug << "# U1: Logout / login to retrieve Set"; userIdx = 0; isExpectedToBeExported = true; unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(userIdx)); // load cached Sets unique_ptr reloadedSessionSet(megaApi[userIdx]->getSet(sh)); ASSERT_NO_FATAL_FAILURE(lIsSameSet(reloadedSessionSet.get(), isExpectedToBeExported)); const auto lIsSameElement = [&elp](const MegaSetElement* el) { ASSERT_EQ(el->id(), elp->id()); ASSERT_EQ(el->node(), elp->node()); ASSERT_STREQ(el->name(), elp->name()); ASSERT_EQ(el->ts(), elp->ts()); ASSERT_EQ(el->order(), elp->order()); }; unique_ptr reloadedSessionElement(megaApi[userIdx]->getSetElement(sh, eh)); ASSERT_NO_FATAL_FAILURE(lIsSameElement(reloadedSessionElement.get())); LOG_debug << "# U1: Check if Set is exported"; ASSERT_TRUE(megaApi[0]->isExportedSet(sh)) << "Set should still be public after session resumption"; LOG_debug << "# U1: Get public Set URL"; const auto lCheckSetLink = [this, sh](int expectedResult) { bool isSuccessExpected = expectedResult == API_OK; unique_ptr publicSetLink(megaApi[0]->getPublicLinkForExportedSet(sh)); if (isSuccessExpected) ASSERT_NE(publicSetLink.get(), nullptr); else ASSERT_EQ(publicSetLink.get(), nullptr); }; ASSERT_NO_FATAL_FAILURE(lCheckSetLink(API_OK)); LOG_debug << "# U1: Fetch Public Set and start Public Set preview mode"; userIdx = 0; isExpectedToBeExported = true; const auto lIsSameElementList = [&els, &lIsSameElement](const MegaSetElementList* ell) { ASSERT_NE(ell, nullptr); ASSERT_EQ(ell->size(), els->size()); ASSERT_NO_FATAL_FAILURE(lIsSameElement(ell->get(0))); }; const auto lFetchCurrentSetInPreviewMode = [this, &lIsSameSet, &lIsSameElementList](int apiIdx, int isSuccessExpected) { unique_ptr s(megaApi[static_cast(apiIdx)]->getPublicSetInPreview()); unique_ptr ell( megaApi[static_cast(apiIdx)]->getPublicSetElementsInPreview()); if (isSuccessExpected) { ASSERT_NO_FATAL_FAILURE(lIsSameSet(s.get(), true)); ASSERT_NO_FATAL_FAILURE(lIsSameElementList(ell.get())); } else { ASSERT_EQ(s, nullptr); ASSERT_EQ(ell, nullptr); } }; const auto lFetchPublicSet = [this, &exportedSetURL, &lIsSameSet, &lIsSameElementList, &lFetchCurrentSetInPreviewMode] (int apiIdx, bool isSetExportExpected) { MegaSet* exportedSet = nullptr; MegaSetElementList* exportedEls = nullptr; const auto reqResult = doFetchPublicSet(static_cast(apiIdx), &exportedSet, &exportedEls, exportedSetURL.c_str()); unique_ptr s(exportedSet); unique_ptr els(exportedEls); if (isSetExportExpected) { ASSERT_EQ(reqResult, API_OK); ASSERT_NO_FATAL_FAILURE(lIsSameSet(s.get(), isSetExportExpected)); ASSERT_NO_FATAL_FAILURE(lIsSameElementList(els.get())); } else { ASSERT_NE(reqResult, API_OK); ASSERT_EQ(s.get(), nullptr); ASSERT_EQ(els.get(), nullptr); } ASSERT_EQ(megaApi[static_cast(apiIdx)]->inPublicSetPreview(), isSetExportExpected); ASSERT_NO_FATAL_FAILURE(lFetchCurrentSetInPreviewMode(apiIdx, isSetExportExpected)); }; ASSERT_NO_FATAL_FAILURE(lFetchPublicSet(0, isExpectedToBeExported)); LOG_debug << "# U1: Stop Public Set preview mode"; userIdx = 0; megaApi[userIdx]->stopPublicSetPreview(); ASSERT_FALSE(megaApi[userIdx]->inPublicSetPreview()); ASSERT_NO_FATAL_FAILURE(lFetchCurrentSetInPreviewMode(static_cast(userIdx), false)); LOG_debug << "# U2: Attempt to fetch public Set using wrong key"; string exportedSetURL_wrongKey = exportedSetURL; exportedSetURL_wrongKey.replace(exportedSetURL_wrongKey.find('#'), string::npos, "#aaaaaaaaaaaaaaaaaaaaaa"); ASSERT_EQ(doFetchPublicSet(1, nullptr, nullptr, exportedSetURL_wrongKey.c_str()), API_EKEY); LOG_debug << "# U2: Fetch public Set and start preview mode"; userIdx = 1; ASSERT_NO_FATAL_FAILURE(lFetchPublicSet(static_cast(userIdx), isExpectedToBeExported)); // test shortcut LOG_debug << "\tTesting fetch shortcut (same public Set in a row)"; ASSERT_NO_FATAL_FAILURE(lFetchPublicSet(static_cast(userIdx), isExpectedToBeExported)); LOG_debug << "# U2: Download foreign Set Element in preview set mode"; unique_ptr foreignNode; const auto lFetchForeignNode = [this, &foreignNode, &uploadedNode, &elp](int expectedResult) { ASSERT_EQ(elp->node(), uploadedNode); MegaNode* fNode = nullptr; ASSERT_EQ(expectedResult, doGetPreviewElementNode(1, &fNode, elp->id())); foreignNode.reset(fNode); if (expectedResult == API_OK) { ASSERT_NE(foreignNode, nullptr); unique_ptr sourceNode(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_TRUE(sourceNode) << "Failed to find source file (should never happed)"; ASSERT_STREQ(foreignNode->getName(), sourceNode->getName()) << "File names did not match"; ASSERT_EQ(foreignNode->getSize(), sourceNode->getSize()) << "File size did not match"; ASSERT_EQ(foreignNode->getOwner(), sourceNode->getOwner()) << "File owner did not match"; ASSERT_STREQ(foreignNode->getFingerprint(), sourceNode->getFingerprint()) << "File fingerprint did not match"; ASSERT_STREQ(foreignNode->getFileAttrString(), sourceNode->getFileAttrString()) << "File attrs did not match"; ASSERT_EQ(foreignNode->getCreationTime(), sourceNode->getCreationTime()) << "Node creation time did not match"; ASSERT_EQ(foreignNode->getModificationTime(), sourceNode->getModificationTime()) << "\tForeign node's mtime inconsistent"; } else { ASSERT_EQ(foreignNode, nullptr); } }; const auto lDownloadForeignElement = [this](int expectedResult, MegaNode* validForeignNode) { string downloadPath = path_u8string(fs::current_path() / UPFILE.c_str()); if (fs::exists(downloadPath)) fs::remove(downloadPath); ASSERT_EQ(expectedResult, doStartDownload(1, validForeignNode, downloadPath.c_str(), // trims from end to first separator nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /*undelete*/)); fs::remove(downloadPath); }; ASSERT_NO_FATAL_FAILURE(lFetchForeignNode(API_OK)); ASSERT_NO_FATAL_FAILURE(lDownloadForeignElement(API_OK, foreignNode.get())); LOG_debug << "# U2: Stop Public Set preview mode"; userIdx = 1; megaApi[userIdx]->stopPublicSetPreview(); ASSERT_FALSE(megaApi[userIdx]->inPublicSetPreview()); ASSERT_NO_FATAL_FAILURE(lFetchCurrentSetInPreviewMode(static_cast(userIdx), false)); LOG_debug << "# U2: Download foreign Set Element not in preview set mode (-11 and -9 expected)"; ASSERT_NO_FATAL_FAILURE(lFetchForeignNode(API_EACCESS)); ASSERT_NO_FATAL_FAILURE(lDownloadForeignElement(API_ENOENT, foreignNode.get())); LOG_debug << "# U1: Disable Set export (invalidates public link)"; userIdx = 0; ASSERT_TRUE(megaApi[userIdx]->isExportedSet(sh)); differentApiDtlsPtr->setUpdated = false; ASSERT_EQ(API_OK, doDisableExportSet(userIdx, sh)); isExpectedToBeExported = false; unique_ptr s1pDisabledExport(megaApi[userIdx]->getSet(sh)); ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pDisabledExport.get(), isExpectedToBeExported)); // wait for action packets on both APIs (disable updates through APs) ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive disable export Set update AP on U1's secondary client"; s1pDisabledExport.reset(differentApiPtr->getSet(sh)); ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pDisabledExport.get(), isExpectedToBeExported)); // test shortcut on disable export LOG_debug << "\tChecking export disable shortcut"; exportedSet = nullptr; ASSERT_EQ(API_OK, doDisableExportSet(userIdx, sh)); s1pDisabledExport.reset(megaApi[userIdx]->getSet(sh)); ASSERT_NO_FATAL_FAILURE(lIsSameSet(s1pDisabledExport.get(), isExpectedToBeExported)); LOG_debug << "# U1: Check if Set is exported"; ASSERT_FALSE(megaApi[0]->isExportedSet(sh)); LOG_debug << "# U1: Get public Set URL (expect -9)"; ASSERT_NO_FATAL_FAILURE(lCheckSetLink(API_ENOENT)); LOG_debug << "# U1: Fetch public Set on non-exported Set (using previously valid link)"; userIdx = 0; ASSERT_NO_FATAL_FAILURE(lFetchPublicSet(static_cast(userIdx), isExpectedToBeExported)); ASSERT_FALSE(megaApi[userIdx]->inPublicSetPreview()) << "Public Set preview mode should not be active"; LOG_debug << "# U1: Remove all Sets"; userIdx = 0; unique_ptr sets(megaApi[userIdx]->getSets()); for (unsigned i = 0; i < sets->size(); ++i) { differentApiDtlsPtr->setUpdated = false; handle setId = sets->get(i)->id(); ASSERT_EQ(API_OK, doRemoveSet(userIdx, setId)); unique_ptr s(megaApi[userIdx]->getSet(setId)); ASSERT_EQ(s, nullptr); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive deleted Set AP on U1's secondary client"; } sets.reset(megaApi[static_cast(userIdx)]->getSets()); ASSERT_EQ(sets->size(), 0u); } /** * @brief TEST_F SdkTestSetsAndElementsSetTypes * * Tests creating all possible Set types. */ TEST_F(SdkTest, SdkTestSetsAndElementsSetTypes) { LOG_info << "___TEST Sets and Elements Set Types___"; // U1: Create set with type MegaSet::SET_TYPE_PHOTOS // U1: Create set with type MegaSet::SET_TYPE_VIDEOS // U1: Create set with a type out of range // U1: Logout / login to retrieve Sets // U1: Check existing Sets and types // U1: Remove all Sets ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Use another connection with the same credentials as U1 (i.e. fake a different client for AP processing) MegaApi* differentApiPtr = nullptr; PerApi* differentApiDtlsPtr = nullptr; const int userIdx = 0; megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(userIdx).c_str(), USER_AGENT.c_str(), unsigned(THREADS_PER_MEGACLIENT))); differentApiPtr = &(*megaApi.back()); differentApiPtr->addListener(this); PerApi pa; // make a copy auto& aux = mApi[userIdx]; pa.email = aux.email; pa.pwd = aux.pwd; mApi.push_back(std::move(pa)); differentApiDtlsPtr = &(mApi.back()); differentApiDtlsPtr->megaApi = differentApiPtr; const unsigned int difApiIdx = static_cast(megaApi.size() - 1); differentApiPtr->setLoggingName(to_string(difApiIdx).c_str()); auto loginTracker = asyncRequestLogin(difApiIdx, differentApiDtlsPtr->email.c_str(), differentApiDtlsPtr->pwd.c_str()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to establish a login/session for account " << difApiIdx; loginTracker = asyncRequestFetchnodes(difApiIdx); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to fetch nodes for account " << difApiIdx; LOG_debug << "# U1: Create set with type MegaSet::SET_TYPE_PHOTOS"; const string albumName = U8("qq-001"); MegaSet* newSet = nullptr; differentApiDtlsPtr->setUpdated = false; ASSERT_EQ(API_OK, doCreateSet(0, &newSet, albumName.c_str(), MegaSet::SET_TYPE_ALBUM)); ASSERT_NE(newSet, nullptr); const unique_ptr setAsPhotoAlbum(newSet); const MegaHandle albumHandle = setAsPhotoAlbum->id(); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive create Set AP on U1's secondary client for photo album creation"; LOG_debug << "# U1: Create set with type MegaSet::SET_TYPE_VIDEOS"; const string playlistName = U8("gg-001"); newSet = nullptr; differentApiDtlsPtr->setUpdated = false; PerApi& target = mApi[userIdx]; target.resetlastEvent(); // So we can detect when the node database has been committed. ASSERT_EQ(API_OK, doCreateSet(0, &newSet, playlistName.c_str(), MegaSet::SET_TYPE_PLAYLIST)); ASSERT_NE(newSet, nullptr); const unique_ptr setAsVideoPlaylist(newSet); const MegaHandle playlistHandle = setAsVideoPlaylist->id(); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive create Set AP on U1's secondary client for video playlist creation"; // Wait for the database to be updated (note: even if commit is triggered by 1st update, the 2nd update // has been applied already, so the DB will store the final value) ASSERT_TRUE(WaitFor([&target](){ return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, maxTimeout*1000)) << "Failed to receive commit to DB event related to latest Set stored"; LOG_debug << "# U1: Create Sets with invalid types"; const string invalidSetName = U8("failure-001"); newSet = nullptr; const int maxRange = 255; // std::numeric_limits::max() const int minRange = 0; // std::numeric_limits::min() ASSERT_NE(API_OK, doCreateSet(0, &newSet, invalidSetName.c_str(), maxRange)); ASSERT_EQ(newSet, nullptr); ASSERT_NE(API_OK, doCreateSet(0, &newSet, invalidSetName.c_str(), maxRange + 1)); ASSERT_EQ(newSet, nullptr); ASSERT_NE(API_OK, doCreateSet(0, &newSet, invalidSetName.c_str(), minRange - 1)); ASSERT_EQ(newSet, nullptr); LOG_debug << "# U1: Logout / login to retrieve Sets"; unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(userIdx)); // load cached Sets LOG_debug << "# U1: Check Sets types loaded from local cache"; constexpr std::string_view noLocalDBMsg{"The set was not in the cached memory"}; unique_ptr reloadedSessionAlbumSet(megaApi[userIdx]->getSet(albumHandle)); ASSERT_NE(reloadedSessionAlbumSet, nullptr) << "Photo album: " << noLocalDBMsg; ASSERT_EQ(reloadedSessionAlbumSet->type(), setAsPhotoAlbum->type()); unique_ptr reloadedSessionPlaylistSet(megaApi[userIdx]->getSet(playlistHandle)); ASSERT_NE(reloadedSessionPlaylistSet, nullptr) << "Playlist: " << noLocalDBMsg; ASSERT_EQ(reloadedSessionPlaylistSet->type(), setAsVideoPlaylist->type()); LOG_debug << "# U1: Remove all Sets"; unique_ptr sets(megaApi[userIdx]->getSets()); for (unsigned i = 0; i < sets->size(); ++i) { differentApiDtlsPtr->setUpdated = false; handle setId = sets->get(i)->id(); ASSERT_EQ(API_OK, doRemoveSet(userIdx, setId)); unique_ptr s(megaApi[userIdx]->getSet(setId)); ASSERT_EQ(s, nullptr); ASSERT_TRUE(waitForResponse(&differentApiDtlsPtr->setUpdated)) << "Failed to receive deleted Set AP on U1's secondary client"; } sets.reset(megaApi[userIdx]->getSets()); ASSERT_EQ(sets->size(), 0u); } /** * @brief TEST_F SdkUserAlerts * * Generate User Alerts and check that they are received and acknowledged as expected. * * Generated so far: * IncomingPendingContact -- request created * ContactChange -- contact request accepted * NewShare * RemovedSharedNode * NewSharedNodes * UpdatedSharedNode * UpdatedSharedNode (combined with previous one) * DeletedShare * ContactChange -- contact deleted * NewScheduledMeeting * UpdatedScheduledMeeting * * Not generated: * UpdatedPendingContactIncoming skipped (requires a 2 week wait) * UpdatedPendingContactOutgoing skipped (requires a 2 week wait) * Payment skipped (out of user control) * PaymentReminder skipped (out of user control) * Takedown skipped (out of user control) * SetTakedown skipped (out of user control) */ TEST_F(SdkTest, SdkUserAlerts) { LOG_info << "___TEST SdkUserAlerts___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); unsigned A1idx = 0; unsigned B1idx = 1; // Alerts generated in this test should be compared with: // - db persisted ones; // - the ones received through sc50 request. // However, for now we kept code dealing with sc50 disabled because sc50 request times out very often in Jenkins, // and has various inconsistencies (some depending on the shard handling the sc50 request). #define MEGA_TEST_SC50_ALERTS 0 #if MEGA_TEST_SC50_ALERTS // B2 (uses the same credentials as B1) // Create this here, in order to keep valid references to the others throughout the entire test megaApi.emplace_back(newMegaApi(APP_KEY.c_str(), megaApiCacheFolder(B1idx).c_str(), string(USER_AGENT+"SC50").c_str(), unsigned(THREADS_PER_MEGACLIENT))); auto& B2 = *megaApi.back(); B2.addListener(this); PerApi pa; // make a copy pa.email = mApi.back().email; pa.pwd = mApi.back().pwd; mApi.push_back(std::move(pa)); auto& B2dtls = mApi.back(); B2dtls.megaApi = &B2; #endif // A1 auto& A1dtls = mApi[A1idx]; auto& A1 = *megaApi[A1idx]; // B1 auto& B1dtls = mApi[B1idx]; auto& B1 = *megaApi[B1idx]; vector> bkpAlerts; // used for comparing existing alerts with persisted ones vector> bkpSc50Alerts; // used for comparing existing alerts with ones received through sc50 (useful only when enabled) // IncomingPendingContact -- request created //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Send a contact request --- A1dtls.contactRequestUpdated = B1dtls.contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(A1idx, B1dtls.email, "test: A1 invited B1", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&A1dtls.contactRequestUpdated)) << "Contact request creation not received by A1 after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&B1dtls.contactRequestUpdated)) << "Contact request creation not received by B1 after " << maxTimeout << " seconds"; B1dtls.cr.reset(); ASSERT_NO_FATAL_FAILURE(getContactRequest(B1idx, false)); ASSERT_NE(B1dtls.cr, nullptr); // IncomingPendingContact -- request created ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about contact request creation not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "IncomingPendingContact -- request created"; int count = 0; const MegaUserAlert* a = nullptr; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "IncomingPendingContact -- request created"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "IncomingPendingContact -- request created"; ASSERT_STRCASEEQ(a->getTitle(), "Sent you a contact request") << "IncomingPendingContact -- request created"; ASSERT_GT(a->getId(), 0u) << "IncomingPendingContact -- request created"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_INCOMINGPENDINGCONTACT_REQUEST) << "IncomingPendingContact -- request created"; ASSERT_STREQ(a->getTypeString(), "NEW_CONTACT_REQUEST") << "IncomingPendingContact -- request created"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "IncomingPendingContact -- request created"; ASSERT_NE(a->getTimestamp(0), 0) << "IncomingPendingContact -- request created"; ASSERT_FALSE(a->isOwnChange()) << "IncomingPendingContact -- request created"; ASSERT_EQ(a->getPcrHandle(), B1dtls.cr->getHandle()) << "IncomingPendingContact -- request created"; bkpAlerts.emplace_back(a->copy()); bkpSc50Alerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about contact request creation not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "IncomingPendingContact -- request created"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // ContactChange -- contact request accepted //-------------------------------------------- // reset User Alerts for A1 A1dtls.userAlertsUpdated = false; A1dtls.userAlertList.reset(); // --- Accept contact request --- A1dtls.contactRequestUpdated = B1dtls.contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(B1dtls.cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&A1dtls.contactRequestUpdated)) << "Contact request accept not received by A1 after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&B1dtls.contactRequestUpdated)) << "Contact request accept not received by B1 after " << maxTimeout << " seconds"; B1dtls.cr.reset(); // ContactChange -- contact request accepted ASSERT_TRUE(waitForResponse(&A1dtls.userAlertsUpdated)) << "Alert about contact request accepted not received by A1 after " << maxTimeout << " seconds"; ASSERT_NE(A1dtls.userAlertList, nullptr) << "ContactChange -- contact request accepted"; count = 0; a = nullptr; for (int i = 0; i < A1dtls.userAlertList->size(); ++i) { if (A1dtls.userAlertList->get(i)->isRemoved()) continue; a = A1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "ContactChange -- contact request accepted"; ASSERT_STRCASEEQ(a->getEmail(), B1dtls.email.c_str()) << "ContactChange -- contact request accepted"; ASSERT_STRCASEEQ(a->getTitle(), "Contact relationship established") << "ContactChange -- contact request accepted"; ASSERT_GT(a->getId(), 0u) << "ContactChange -- contact request accepted"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_CONTACTCHANGE_CONTACTESTABLISHED) << "ContactChange -- contact request accepted"; ASSERT_STREQ(a->getTypeString(), "CONTACT_ESTABLISHED") << "ContactChange -- contact request accepted"; ASSERT_STRCASEEQ(a->getHeading(), B1dtls.email.c_str()) << "ContactChange -- contact request accepted"; ASSERT_NE(a->getTimestamp(0), 0) << "ContactChange -- contact request accepted"; ASSERT_FALSE(a->isOwnChange()) << "ContactChange -- contact request accepted"; ASSERT_EQ(a->getUserHandle(), B1.getMyUserHandleBinary()) << "ContactChange -- contact request accepted"; ASSERT_FALSE(a->getSeen()); A1dtls.userAlertsUpdated = false; A1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(A1idx), API_OK); ASSERT_TRUE(waitForResponse(&A1dtls.userAlertsUpdated)) << "Alert about contact request change not received by A1 after " << maxTimeout << " seconds"; ASSERT_NE(A1dtls.userAlertList, nullptr) << "Contact change -- contact request accepted"; for (int i = 0; i < A1dtls.userAlertList->size(); ++i) { if (A1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(A1dtls.userAlertList->get(i)->getSeen()); } // received by A1, do not keep it for comparing with B2's sc50 if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } // create some folders / files to share // Create some nodes to share // |--Shared-folder // |--subfolder // |--file.txt // PUBLICFILE // |--file1.txt // UPFILE std::unique_ptr rootnode{ A1.getRootNode() }; char sharedFolder[] = "Shared-folder"; MegaHandle hSharedFolder = createFolder(A1idx, sharedFolder, rootnode.get()); ASSERT_NE(hSharedFolder, UNDEF); std::unique_ptr nSharedFolder(A1.getNodeByHandle(hSharedFolder)); ASSERT_NE(nSharedFolder, nullptr); char subfolder[] = "subfolder"; MegaHandle hSubfolder = createFolder(A1idx, subfolder, nSharedFolder.get()); ASSERT_NE(hSubfolder, UNDEF); // not a large file since don't need to test transfers here ASSERT_TRUE(createFile(PUBLICFILE.c_str(), false)) << "Couldn't create " << PUBLICFILE.c_str(); MegaHandle hPublicfile = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(A1idx, &hPublicfile, PUBLICFILE.c_str(), nSharedFolder.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr nSubfolder(A1.getNodeByHandle(hSubfolder)); ASSERT_NE(nSubfolder, nullptr); std::unique_ptr rootA1(A1.getRootNode()); ASSERT_NE(rootA1, nullptr); ASSERT_TRUE(createFile(UPFILE.c_str(), false)) << "Couldn't create " << UPFILE.c_str(); MegaHandle hUpfile = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(A1idx, &hUpfile, UPFILE.c_str(), rootA1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; #ifdef ENABLE_CHAT // NewScheduledMeeting //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); A1dtls.schedId = UNDEF; A1dtls.chatid = UNDEF; A1dtls.requestFlags[MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING] = false; MegaHandle chatid = UNDEF; createChatScheduledMeeting(0, chatid); ASSERT_NE(chatid, UNDEF) << "Invalid chat"; waitForResponse(&A1dtls.requestFlags[MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING], maxTimeout); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about scheduled meeting creation not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "Scheduled meeting created"; count = 0; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { a = B1dtls.userAlertList->get(i); if (a->isRemoved()) continue; count++; } ASSERT_EQ(count, 1) << "NewScheduledMeeting"; ASSERT_EQ(A1dtls.chatid, chatid) << "Scheduled meeting could not be created, unexpected chatid"; ASSERT_NE(A1dtls.schedId, UNDEF) << "Scheduled meeting could not be created, invalid scheduled meeting id"; bkpAlerts.emplace_back(a->copy()); // UpdateScheduledMeeting //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); A1dtls.schedId = UNDEF; A1dtls.chatid = UNDEF; A1dtls.requestFlags[MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING] = false; updateScheduledMeeting(0, chatid); ASSERT_NE(chatid, UNDEF) << "Invalid chat"; waitForResponse(&A1dtls.requestFlags[MegaRequest::TYPE_ADD_UPDATE_SCHEDULED_MEETING], maxTimeout); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about scheduled meeting update not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "Scheduled meeting created"; count = 0; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { a = B1dtls.userAlertList->get(i); if (a->isRemoved()) continue; count++; } ASSERT_EQ(count, 1) << "UpdateScheduledMeeting"; ASSERT_EQ(A1dtls.chatid, chatid) << "Scheduled meeting could not be updated, unexpected chatid"; ASSERT_NE(A1dtls.schedId, UNDEF) << "Scheduled meeting could not be updated, invalid scheduled meeting id"; bkpAlerts.emplace_back(a->copy()); #endif // NewShare //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Create a new outgoing share --- A1dtls.nodeUpdated = B1dtls.nodeUpdated = false; // reset flags expected to be true in asserts below A1dtls.mOnNodesUpdateCompletion = [&A1dtls, A1idx](size_t apiIndex, MegaNodeList*) { if (A1idx == apiIndex) A1dtls.nodeUpdated = true; }; B1dtls.mOnNodesUpdateCompletion = [&B1dtls, B1idx](size_t apiIndex, MegaNodeList*) { if (B1idx == apiIndex) B1dtls.nodeUpdated = true; }; ASSERT_NO_FATAL_FAILURE(shareFolder(nSharedFolder.get(), B1dtls.email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&A1dtls.nodeUpdated)) // at the target side (main account) << "Node update not received by A1 after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&B1dtls.nodeUpdated)) // at the target side (auxiliar account) << "Node update not received by B1 after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); // Wait for node to be decrypted in B account ASSERT_TRUE(WaitFor([&B1, hSharedFolder]() { std::unique_ptr inshareNode(B1.getNodeByHandle(hSharedFolder)); return inshareNode && inshareNode->isNodeKeyDecrypted(); }, 60*1000)) << "Cannot decrypt inshare in B account."; // NewShare ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about new share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "NewShare"; count = 0; a = nullptr; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(B1dtls.userAlertList->size(), 1) << "NewShare"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "NewShare"; string title = "New shared folder from " + A1dtls.email; ASSERT_STRCASEEQ(a->getTitle(), title.c_str()) << "NewShare"; ASSERT_GT(a->getId(), 0u) << "NewShare"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_NEWSHARE) << "NewShare"; ASSERT_STREQ(a->getTypeString(), "NEW_SHARE") << "NewShare"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "NewShare"; ASSERT_NE(a->getTimestamp(0), 0) << "NewShare"; ASSERT_FALSE(a->isOwnChange()) << "NewShare"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "NewShare"; ASSERT_EQ(a->getNodeHandle(), nSharedFolder->getHandle()) << "NewShare"; if (string(a->getName()) != "NO_KEY") // Share key may not yet be available when the user alert is created { ASSERT_STRCASEEQ(a->getPath(), string(A1dtls.email + ':' + sharedFolder).c_str()) << "NewShare"; ASSERT_STREQ(a->getName(), sharedFolder) << "NewShare"; } else { ASSERT_STRCASEEQ(a->getPath(), string(A1dtls.email + ":NO_KEY").c_str()) << "NewShare"; } bkpAlerts.emplace_back(a->copy()); bkpSc50Alerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about new share creation not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "NewShare"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // RemovedSharedNode //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Move shared sub-folder (owned) to Root --- ASSERT_EQ(API_OK, doMoveNode(A1idx, nullptr, nSubfolder.get(), rootA1.get())) << "Moving subfolder out of (owned) share failed"; // RemovedSharedNode ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about removed shared node not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "RemovedSharedNode"; count = 0; a = nullptr; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "RemovedSharedNode"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "RemovedSharedNode"; title = "Removed item from shared folder"; ASSERT_STRCASEEQ(a->getTitle(), title.c_str()) << "RemovedSharedNode"; ASSERT_GT(a->getId(), 0u) << "RemovedSharedNode"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_REMOVEDSHAREDNODES) << "RemovedSharedNode"; ASSERT_STREQ(a->getTypeString(), "NODES_IN_SHARE_REMOVED") << "RemovedSharedNode"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "RemovedSharedNode"; ASSERT_NE(a->getTimestamp(0), 0) << "RemovedSharedNode"; ASSERT_FALSE(a->isOwnChange()) << "RemovedSharedNode"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "RemovedSharedNode"; ASSERT_EQ(a->getNumber(0), 1) << "RemovedSharedNode"; //bkpAlerts.emplace_back(a->copy()); // removed internally (combined to "update" later?) bkpSc50Alerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about remove share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "RemovedSharedNode"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // NewSharedNodes //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Move sub-folder from Root (owned) back to share --- ASSERT_EQ(API_OK, doMoveNode(A1idx, nullptr, nSubfolder.get(), nSharedFolder.get())) << "Moving sub-folder from Root (owned) to share failed"; // Notifies and suppresses the previous RemovedSharedNode alert ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Suppressed remove share alert not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "Suppressed RemovedSharedNode"; ASSERT_EQ(B1dtls.userAlertList->size(), 1) << "Suppressed RemovedSharedNode"; ASSERT_EQ(B1dtls.userAlertList->get(0)->getType(), MegaUserAlert::TYPE_REMOVEDSHAREDNODES) << "Suppressed RemovedSharedNode"; ASSERT_TRUE(B1dtls.userAlertList->get(0)->isRemoved()) << "Suppressed RemovedSharedNode"; // --- Move file from Root (owned) to share --- B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); std::unique_ptr nfile2(A1.getNodeByHandle(hUpfile)); ASSERT_NE(nfile2, nullptr); ASSERT_EQ(API_OK, doMoveNode(A1idx, nullptr, nfile2.get(), nSubfolder.get())) << "Moving file from Root (owned) to shared folder failed"; // NewSharedNodes ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about node added to share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "NewSharedNodes"; count = 0; a = nullptr; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { a = B1dtls.userAlertList->get(i); if (a->isRemoved()) continue; count++; } ASSERT_EQ(count, 1) << "NewSharedNodes"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "NewSharedNodes"; title = A1dtls.email + " added 1 file"; ASSERT_STRCASEEQ(a->getTitle(), title.c_str()) << "NewSharedNodes"; ASSERT_GT(a->getId(), 0u) << "NewSharedNodes"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_NEWSHAREDNODES) << "NewSharedNodes"; ASSERT_STREQ(a->getTypeString(), "NEW_NODES_IN_SHARE") << "NewSharedNodes"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "NewSharedNodes"; ASSERT_NE(a->getTimestamp(0), 0) << "NewSharedNodes"; ASSERT_FALSE(a->isOwnChange()) << "NewSharedNodes"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "NewSharedNodes"; ASSERT_EQ(a->getNumber(0), 0) << "NewSharedNodes"; // folder count ASSERT_EQ(a->getNumber(1), 1) << "NewSharedNodes"; // file count ASSERT_EQ(a->getNodeHandle(), hSubfolder) << "NewSharedNodes"; // parent handle ASSERT_EQ(a->getHandle(0), hUpfile) << "NewSharedNodes"; //bkpAlerts.emplace_back(a->copy()); // removed internally (combined to "update" later?) bkpSc50Alerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about new share creation not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "NewSharedNodes"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // UpdatedSharedNode //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Modify shared file --- { ofstream f(UPFILE); f << "edited"; } // Upload a file over an existing one to update ASSERT_EQ(API_OK, doStartUpload(0, nullptr, UPFILE.c_str(), nSubfolder.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); // UpdatedSharedNode ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about node updated in share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "UpdatedSharedNode"; count = 0; a = nullptr; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "UpdatedSharedNode"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "UpdatedSharedNode"; ASSERT_STRCASEEQ(a->getTitle(), "Updated 1 item in shared folder") << "UpdatedSharedNode"; ASSERT_GT(a->getId(), 0u) << "UpdatedSharedNode"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_UPDATEDSHAREDNODES) << "UpdatedSharedNode"; ASSERT_STREQ(a->getTypeString(), "NODES_IN_SHARE_UPDATED") << "UpdatedSharedNode"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "UpdatedSharedNode"; ASSERT_NE(a->getTimestamp(0), 0) << "UpdatedSharedNode"; ASSERT_FALSE(a->isOwnChange()) << "UpdatedSharedNode"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "UpdatedSharedNode"; ASSERT_EQ(a->getNumber(0), 1) << "UpdatedSharedNode"; // item count ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about update share node not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "UpdatedSharedNode"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // this will be combined with the next one, do not keep it for comparison or validation // UpdatedSharedNode -- combined with the previous one //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Modify shared file --- { ofstream f(UPFILE); f << " AND edited again"; } // Upload a file over an existing one to update ASSERT_EQ(API_OK, doStartUpload(0, nullptr, UPFILE.c_str(), nSubfolder.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)); // UpdatedSharedNode (combined) ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about node updated, again, in share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "UpdatedSharedNode (combined)"; count = 0; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "UpdatedSharedNode (combined)"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "UpdatedSharedNode (combined)"; ASSERT_STRCASEEQ(a->getTitle(), "Updated 2 items in shared folder") << "UpdatedSharedNode (combined)"; ASSERT_GT(a->getId(), 0u) << "UpdatedSharedNode (combined)"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_UPDATEDSHAREDNODES) << "UpdatedSharedNode (combined)"; ASSERT_STREQ(a->getTypeString(), "NODES_IN_SHARE_UPDATED") << "UpdatedSharedNode (combined)"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "UpdatedSharedNode (combined)"; ASSERT_NE(a->getTimestamp(0), 0) << "UpdatedSharedNode (combined)"; ASSERT_FALSE(a->isOwnChange()) << "UpdatedSharedNode (combined)"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "UpdatedSharedNode (combined)"; ASSERT_EQ(a->getNumber(0), 2) << "UpdatedSharedNode (combined)"; // item count bkpAlerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about update share node not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "UpdatedSharedNode"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } //bkpSc50Alerts.emplace_back(a->copy()); // not generated by API, which sends only [combined] NewSharedNodes #if MEGA_TEST_SC50_ALERTS // B2 B2dtls.userAlertsUpdated = false; B2dtls.userAlertList.reset(); int B2idx = int(megaApi.size() - 1); auto loginTrackerB2 = asyncRequestLogin(B2idx, B2dtls.email.c_str(), B2dtls.pwd.c_str()); ASSERT_EQ(API_OK, loginTrackerB2->waitForResult()) << " Failed to establish a login/session for account " << B2idx << " (B2)"; fetchnodes(B2idx); // get sc50 alerts after login unsigned sc50Timeout = 120; // seconds ASSERT_TRUE(waitForResponse(&B2dtls.userAlertsUpdated, sc50Timeout)) << "sc50 alerts after login not received by B2 after " << sc50Timeout << " seconds"; ASSERT_EQ(B2dtls.userAlertList, nullptr) << "sc50"; unique_ptr sc50Alerts(B2.getUserAlerts()); ASSERT_TRUE(sc50Alerts); ASSERT_GT(sc50Alerts->size(), 0); // validate sc50 alerts (go backwards) // assumption was that sc50 alerts are ordered as they were generated int skipped = 0; // there are more sc50 alerts than kept locally for (size_t j = bkpSc50Alerts.size(); j ; --j) { const auto* bkp = bkpSc50Alerts[j - 1].get(); const auto* sal = sc50Alerts->get(sc50Alerts->size() - 1 - (int)bkpSc50Alerts.size() + (int)j - skipped); // apparently, sc50 will contain some extra alerts that are not part of the backup, so skip them (for now) if (bkp->getType() == MegaUserAlert::TYPE_NEWSHARE && sal->getType() == MegaUserAlert::TYPE_NEWSHAREDNODES) { // skip this sc50 alert // TODO: after sc50 inconsistencies have been addressed in API, make sure this is the expected behavior, and not a bug in the sc50 alerts skipped++; sal = sc50Alerts->get(sc50Alerts->size() - 1 - (int)(bkpSc50Alerts.size() - j) - skipped); } if (bkp->getType() == MegaUserAlert::TYPE_NEWSHARE && sal->getType() == MegaUserAlert::TYPE_REMOVEDSHAREDNODES) { // skip this sc50 alert // TODO: after sc50 inconsistencies have been addressed in API, make sure this is the expected behavior, and not a bug in the sc50 alerts skipped++; sal = sc50Alerts->get(sc50Alerts->size() - 1 - (int)(bkpSc50Alerts.size() - j) - skipped); } ASSERT_EQ(bkp->getType(), sal->getType()) << "sc50 alerts: " << bkp->getTypeString() << " vs " << sal->getTypeString(); ASSERT_STREQ(bkp->getTypeString(), sal->getTypeString()) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(bkp->getUserHandle(), sal->getUserHandle()) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(bkp->getNodeHandle(), sal->getNodeHandle()) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(bkp->getPcrHandle(), sal->getPcrHandle()) << "sc50 alerts: " << sal->getTypeString(); ASSERT_STRCASEEQ(bkp->getEmail(), sal->getEmail()) << "sc50 alerts: " << sal->getTypeString(); if (sal->getPath()) // the node might not be there when sc50 alerts arrive { ASSERT_STRCASEEQ(bkp->getPath(), sal->getPath()) << "sc50 alerts: " << sal->getTypeString(); } if (sal->getName()) // the node might not be there when sc50 alerts arrive { ASSERT_STREQ(bkp->getName(), sal->getName()) << "sc50 alerts: " << sal->getTypeString(); } ASSERT_STRCASEEQ(bkp->getHeading(), sal->getHeading()) << "sc50 alerts: " << sal->getTypeString(); if (sal->getType() == MegaUserAlert::TYPE_NEWSHAREDNODES) { // this is a special case, as API does not generate UpdatedSharedNode alerts, // but only [combines them into previous] NewSharedNodes string ttl = bkp->getEmail() + string(" added 3 files"); ASSERT_STRCASEEQ(sal->getTitle(), ttl.c_str()) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(sal->getNumber(0), 0) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(sal->getNumber(1), 3) << "sc50 alerts: " << sal->getTypeString(); ASSERT_NE(sal->getHandle(0), UNDEF) << "sc50 alerts: " << sal->getTypeString(); ASSERT_NE(sal->getHandle(1), UNDEF) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(sal->getHandle(2), bkp->getHandle(0)) << "sc50 alerts: " << sal->getTypeString(); } else { ASSERT_STRCASEEQ(bkp->getTitle(), sal->getTitle()) << "sc50 alerts: " << sal->getTypeString(); if (sal->getType() == MegaUserAlert::TYPE_REMOVEDSHAREDNODES) { // this is another special caase // TODO: after sc50 inconsistencies have been addressed in API, make sure this is the expected behavior, and not a bug in the sc50 alert ASSERT_EQ(0, sal->getNumber(0)) << "sc50 alerts: " << sal->getTypeString(); } else { ASSERT_EQ(bkp->getNumber(0), sal->getNumber(0)) << "sc50 alerts: " << sal->getTypeString(); } ASSERT_EQ(bkp->getNumber(1), sal->getNumber(1)) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(bkp->getHandle(0), sal->getHandle(0)) << "sc50 alerts: " << sal->getTypeString(); ASSERT_EQ(bkp->getHandle(1), sal->getHandle(1)) << "sc50 alerts: " << sal->getTypeString(); } //ASSERT_EQ(bkp->getTimestamp(0), sal->getTimestamp(0)) << "sc50 alerts"; // this will not match; sc50 timestamp is calculated ASSERT_STRCASEEQ(bkp->getString(0), sal->getString(0)) << "sc50 alerts: " << sal->getTypeString(); } #endif // DeletedShare //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Revoke access to an outgoing share --- A1dtls.nodeUpdated = B1dtls.nodeUpdated = false; // reset flags expected to be true in asserts below A1dtls.mOnNodesUpdateCompletion = [&A1dtls, A1idx](size_t apiIndex, MegaNodeList*) { if (A1idx == apiIndex) A1dtls.nodeUpdated = true; }; B1dtls.mOnNodesUpdateCompletion = [&B1dtls, B1idx](size_t apiIndex, MegaNodeList*) { if (B1idx == apiIndex) B1dtls.nodeUpdated = true; }; ASSERT_NO_FATAL_FAILURE(shareFolder(nSharedFolder.get(), B1dtls.email.c_str(), MegaShare::ACCESS_UNKNOWN)); ASSERT_TRUE(waitForResponse(&A1dtls.nodeUpdated)) // at the target side (main account) << "Node update not received by A1 after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&B1dtls.nodeUpdated)) // at the target side (auxiliar account) << "Node update not received by B1 after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); // DeletedShare ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about deleted share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "DeletedShare"; count = 0; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "DeletedShare"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "DeletedShare"; title = "Access to folders shared by " + A1dtls.email + " was removed"; ASSERT_STRCASEEQ(a->getTitle(), title.c_str()) << "DeletedShare"; ASSERT_GT(a->getId(), 0u) << "DeletedShare"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_DELETEDSHARE) << "DeletedShare"; ASSERT_STREQ(a->getTypeString(), "SHARE_UNSHARED") << "DeletedShare"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "DeletedShare"; ASSERT_NE(a->getTimestamp(0), 0) << "DeletedShare"; ASSERT_FALSE(a->isOwnChange()) << "DeletedShare"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "DeletedShare"; ASSERT_EQ(a->getNodeHandle(), nSharedFolder->getHandle()) << "DeletedShare"; string path = A1dtls.email + ':' + sharedFolder; ASSERT_STRCASEEQ(a->getPath(), path.c_str()) << "DeletedShare"; ASSERT_STREQ(a->getName(), sharedFolder) << "DeletedShare"; ASSERT_EQ(a->getNumber(0), 1) << "DeletedShare"; bkpAlerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about delete share not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "DeletedShare"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // Reset credentials before removing contacts if (gManualVerification) { if (areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(resetCredentials(0, mApi[1].email));} if (areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(resetCredentials(1, mApi[0].email));} } // ContactChange -- contact deleted //-------------------------------------------- // reset User Alerts for B1 B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); // --- Delete an existing contact --- ASSERT_EQ(API_OK, removeContact(0, B1dtls.email)); // ContactChange -- contact deleted ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about contact removal not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "ContactChange -- contact deleted"; count = 0; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; a = B1dtls.userAlertList->get(i); count++; } ASSERT_EQ(count, 1) << "ContactChange -- contact deleted"; ASSERT_STRCASEEQ(a->getEmail(), A1dtls.email.c_str()) << "ContactChange -- contact deleted"; ASSERT_STRCASEEQ(a->getTitle(), "Deleted you as a contact") << "ContactChange -- contact deleted"; ASSERT_GT(a->getId(), 0u) << "ContactChange -- contact deleted"; ASSERT_EQ(a->getType(), MegaUserAlert::TYPE_CONTACTCHANGE_DELETEDYOU) << "ContactChange -- contact deleted"; ASSERT_STREQ(a->getTypeString(), "CONTACT_DISCONNECTED") << "ContactChange -- contact deleted"; ASSERT_STRCASEEQ(a->getHeading(), A1dtls.email.c_str()) << "ContactChange -- contact deleted"; ASSERT_NE(a->getTimestamp(0), 0) << "ContactChange -- contact deleted"; ASSERT_FALSE(a->isOwnChange()) << "ContactChange -- contact deleted"; ASSERT_EQ(a->getUserHandle(), A1.getMyUserHandleBinary()) << "ContactChange -- contact deleted"; bkpAlerts.emplace_back(a->copy()); ASSERT_FALSE(a->getSeen()); B1dtls.userAlertsUpdated = false; B1dtls.userAlertList.reset(); ASSERT_EQ(doAckUserAlerts(B1idx), API_OK); ASSERT_TRUE(waitForResponse(&B1dtls.userAlertsUpdated)) << "Alert about contact deleted not received by B1 after " << maxTimeout << " seconds"; ASSERT_NE(B1dtls.userAlertList, nullptr) << "ContactChange -- contact deleted"; for (int i = 0; i < B1dtls.userAlertList->size(); ++i) { if (B1dtls.userAlertList->get(i)->isRemoved()) continue; ASSERT_TRUE(B1dtls.userAlertList->get(i)->getSeen()); } // create a dummy folder, just to trigger a local db commit before locallogout (which triggers a ROLLBACK) std::unique_ptr rootnodeB1{ B1.getRootNode() }; PerApi& target = mApi[static_cast(B1idx)]; target.resetlastEvent(); MegaHandle hDummyFolder = createFolder(B1idx, "DummyFolder_TriggerDbCommit", rootnodeB1.get()); ASSERT_NE(hDummyFolder, INVALID_HANDLE); ASSERT_TRUE(WaitFor([&target]() { return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 8192)); // save session for B1 unique_ptr B1session(B1.dumpSession()); auto logoutErr = doRequestLocalLogout(B1idx); ASSERT_EQ(API_OK, logoutErr) << "Local logout failed (error: " << logoutErr << ") for account " << B1idx << " (B1)"; // resume session for B1 ASSERT_EQ(API_OK, synchronousFastLogin(B1idx, B1session.get(), this)) << "Resume session failed for B1 (error: " << B1dtls.lastError << ")"; ASSERT_NO_FATAL_FAILURE(fetchnodes(B1idx)); unique_ptr persistedAlerts(B1.getUserAlerts()); ASSERT_TRUE(persistedAlerts); if (persistedAlerts->size() != (int)bkpAlerts.size()) { // for debugging purpose string alertTypes = "\nPersisted Alerts: { "; for (int i = 0; i < persistedAlerts->size(); ++i) alertTypes += std::to_string(persistedAlerts->get(i)->getType()) + ' '; alertTypes += " }\nBacked up Alerts: { "; for (size_t i = 0u; i < bkpAlerts.size(); ++i) alertTypes += std::to_string(bkpAlerts[i]->getType()) + ' '; alertTypes += " }"; LOG_err << "Persisted Alerts differ from Backed up ones:" << alertTypes; } ASSERT_EQ(persistedAlerts->size(), (int)bkpAlerts.size()); // B1 will not get sc50 alerts, due to its useragent // sort persisted alerts in the same order as backed up ones; timestamp is not enough because there can be clashes vector sortedPersistedAlerts(bkpAlerts.size(), nullptr); for (int i = 0; i < persistedAlerts->size(); ++i) { const auto* pal = persistedAlerts->get(i); auto it = find_if(bkpAlerts.begin(), bkpAlerts.end(), [pal](const unique_ptr& a) { return a->getTimestamp(0) == pal->getTimestamp(0) && a->getType() == pal->getType(); }); if (it != bkpAlerts.end()) { sortedPersistedAlerts[static_cast(it - bkpAlerts.begin())] = pal; } } // validate persisted alerts for (size_t j = 0; j < bkpAlerts.size(); ++j) { const auto* bkp = bkpAlerts[j].get(); const auto* pal = sortedPersistedAlerts[j]; ASSERT_TRUE(pal) << "Test error: some alerts were not persited or got lost while sorting: " << bkp->getTypeString(); ASSERT_EQ(bkp->getType(), pal->getType()) << "persisted alerts: " << bkp->getTypeString() << " vs " << pal->getTypeString(); ASSERT_STREQ(bkp->getTypeString(), pal->getTypeString()) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getUserHandle(), pal->getUserHandle()) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getNodeHandle(), pal->getNodeHandle()) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getPcrHandle(), pal->getPcrHandle()) << "persisted alerts: " << pal->getTypeString(); ASSERT_STRCASEEQ(bkp->getEmail(), pal->getEmail()) << "persisted alerts: " << pal->getTypeString(); if (pal->getPath()) // the node might no longer be there after persisted alerts have been loaded { ASSERT_STRCASEEQ(bkp->getPath(), pal->getPath()) << "persisted alerts: " << pal->getTypeString(); } if (pal->getName()) // the node might no longer be there after persisted alerts have been loaded { ASSERT_STREQ(bkp->getName(), pal->getName()) << "persisted alerts: " << pal->getTypeString(); } ASSERT_STRCASEEQ(bkp->getHeading(), pal->getHeading()) << "persisted alerts: " << pal->getTypeString(); ASSERT_STRCASEEQ(bkp->getTitle(), pal->getTitle()) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getNumber(0), pal->getNumber(0)) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getNumber(1), pal->getNumber(1)) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getTimestamp(0), pal->getTimestamp(0)) << "persisted alerts: " << pal->getTypeString(); ASSERT_STRCASEEQ(bkp->getString(0), pal->getString(0)) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getHandle(0), pal->getHandle(0)) << "persisted alerts: " << pal->getTypeString(); ASSERT_EQ(bkp->getHandle(1), pal->getHandle(1)) << "persisted alerts: " << pal->getTypeString(); } } /** * ___SdkVersionManagement___ * Steps: * - Create 2 folders * - Upload several versions of the same file to first folder * - Move file with versions to second folder * - Move second folder to first folder * - Remove current version * - Remove oldest version * - Remove version in the middle * - Remove node in the middle (and all previous versions) * - Remove all versions across entire account; will keep only last version * - Delete a version by the API when limit was reached (chain must have 100 versions) */ TEST_F(SdkTest, SdkVersionManagement) { LOG_info << "___TEST SdkVersionManagement"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); doSetFileVersionsOption(0, false); // enable versioning auto& api = megaApi[0]; unique_ptr rootNode(api->getRootNode()); // Create 2 folders bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string folder1 = "Folder1"; auto folder1Handle = createFolder(0, folder1.c_str(), rootNode.get()); ASSERT_NE(folder1Handle, UNDEF); waitForResponse(&check); unique_ptr folder1Node(api->getNodeByHandle(folder1Handle)); ASSERT_TRUE(folder1Node); check = false; std::string folder2 = "Folder2"; auto folder2Handle = createFolder(0, folder2.c_str(), rootNode.get()); ASSERT_NE(folder2Handle, UNDEF); waitForResponse(&check); unique_ptr folder2Node(api->getNodeByHandle(folder2Handle)); ASSERT_TRUE(folder2Node); resetOnNodeUpdateCompletionCBs(); auto upldSingleVersion = [this](const string& name, int version, MegaNode* folderNode, MegaHandle* fh) { string localName = name + '_' + std::to_string(version); createFile(localName, false, std::to_string(version)); int result = doStartUpload(0, fh, localName.c_str(), folderNode, name.c_str() /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/); deleteFile(localName); return result; }; auto upldVersions = [upldSingleVersion](const string& name, int versions, MegaNode* folderNode, MegaHandle* fh) { #define UPLOAD_SINGLE_THREAD 1 #if UPLOAD_SINGLE_THREAD for (int i = 0; i < versions - 1; ++i) { ASSERT_EQ(upldSingleVersion(name, i + 1, folderNode, nullptr), API_OK); } ASSERT_EQ(upldSingleVersion(name, versions, folderNode, fh), API_OK); #else // This would be very nice to have. Unfortunately crashes occur while running multiple threads. assert(versions); std::vector tpool(std::min(6u, std::thread::hardware_concurrency())); std::vector results(tpool.size(), 0); for (size_t i = 0; i < versions - 1; ++i) { if (i >= tpool.size()) { tpool[i%tpool.size()].join(); if (results[i] != API_OK) { // retry another version? //++versions; } } tpool[i % tpool.size()] = std::thread([&name, i, folderNode, &r = results[i], upldSingleVersion]() { r = upldSingleVersion(name, i + 1, folderNode, nullptr); }); } for (size_t i = 0; i < (versions - 1) % tpool.size(); ++i) { tpool[i].join(); EXPECT_EQ(results[i], API_OK) << "Version upload failed"; } int r = upldSingleVersion(name, versions, folderNode, fh); EXPECT_EQ(r, API_OK) << "Version upload failed"; #endif }; // Upload several versions of the same file to first folder const int verCount = 10; MegaHandle fileHandle = 0; ASSERT_NO_FATAL_FAILURE(upldVersions(UPFILE, verCount, folder1Node.get(), &fileHandle)); ASSERT_NE(fileHandle, INVALID_HANDLE); unique_ptr fileNode(api->getNodeByHandle(fileHandle)); ASSERT_TRUE(fileNode); unique_ptr allVersions(api->getVersions(fileNode.get())); ASSERT_EQ(allVersions->size(), verCount); ASSERT_EQ(fileNode->getHandle(), allVersions->get(0)->getHandle()); // Move file with versions to second folder ASSERT_EQ(API_OK, doMoveNode(0, &fileHandle, fileNode.get(), folder2Node.get())) << "Cannot move file"; string destinationPath = '/' + folder2 + '/' + UPFILE; for (int i = 0; i < allVersions->size(); ++i) { unique_ptr filePath(api->getNodePath(allVersions->get(i))); ASSERT_STREQ(destinationPath.c_str(), filePath.get()) << "Wrong file path (1) for version " << (i + 1); destinationPath += '/' + UPFILE; } // Move second folder to first folder ASSERT_EQ(API_OK, doMoveNode(0, &folder2Handle, folder2Node.get(), folder1Node.get())) << "Cannot move folder"; destinationPath = '/' + folder1 + '/' + folder2 + '/' + UPFILE; for (int i = 0; i < allVersions->size(); ++i) { unique_ptr filePath(api->getNodePath(allVersions->get(i))); ASSERT_STREQ(destinationPath.c_str(), filePath.get()) << "Wrong file path (2) for version " << (i + 1); destinationPath += '/' + UPFILE; } folder2Node.reset(api->getNodeByHandle(folder2Handle)); ASSERT_TRUE(folder2Node); // Remove current version ASSERT_EQ(API_OK, doRemoveVersion(0, allVersions->get(0))); int verRemoved = 1; unique_ptr versionsAfterRemoval(api->getVersions(allVersions->get(1))); ASSERT_EQ(versionsAfterRemoval->size(), verCount - verRemoved); for (int i = 0; i < versionsAfterRemoval->size(); ++i) { ASSERT_EQ(versionsAfterRemoval->get(i)->getHandle(), allVersions->get(i + 1)->getHandle()) << "i = " << i; } // Remove oldest version ASSERT_EQ(API_OK, doRemoveVersion(0, allVersions->get(verCount - 1))); ++verRemoved; versionsAfterRemoval.reset(api->getVersions(allVersions->get(1))); ASSERT_EQ(versionsAfterRemoval->size(), verCount - verRemoved); for (int i = 0; i < versionsAfterRemoval->size(); ++i) { ASSERT_EQ(versionsAfterRemoval->get(i)->getHandle(), allVersions->get(i + 1)->getHandle()) << "i = " << i; } // Remove version in the middle ASSERT_GT(versionsAfterRemoval->size(), 2) << "Not enough versions to test further"; int middle = (verCount + 1) / 2; ASSERT_EQ(API_OK, doRemoveVersion(0, allVersions->get(middle))); ++verRemoved; versionsAfterRemoval.reset(api->getVersions(allVersions->get(1))); ASSERT_EQ(versionsAfterRemoval->size(), verCount - verRemoved); for (int i = 0; i < versionsAfterRemoval->size(); ++i) { int j = i < middle - 1 ? 1 : 2; ASSERT_EQ(versionsAfterRemoval->get(i)->getHandle(), allVersions->get(i + j)->getHandle()) << "i = " << i; } // Remove node in the middle (and all previous versions) ASSERT_GT(versionsAfterRemoval->size(), 2) << "Not enough versions to test further"; middle = (versionsAfterRemoval->size() + 1) / 2; ASSERT_EQ(API_OK, doDeleteNode(0, versionsAfterRemoval->get(middle))); versionsAfterRemoval.reset(api->getVersions(allVersions->get(1))); ASSERT_EQ(versionsAfterRemoval->size(), middle); for (int i = 0; i < versionsAfterRemoval->size(); ++i) { ASSERT_EQ(versionsAfterRemoval->get(i)->getHandle(), allVersions->get(i + 1)->getHandle()) << "i = " << i; } // Remove all versions across entire account; will keep only last version ASSERT_GT(versionsAfterRemoval->size(), 1) << "Not enough versions to test further"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(versionsAfterRemoval->get(1)->getHandle(), MegaNode::CHANGE_TYPE_REMOVED, check); ASSERT_EQ(API_OK, doRemoveVersions(0)); waitForResponse(&check); resetOnNodeUpdateCompletionCBs(); versionsAfterRemoval.reset(api->getVersions(allVersions->get(1))); ASSERT_EQ(versionsAfterRemoval->size(), 1); ASSERT_EQ(versionsAfterRemoval->get(0)->getHandle(), allVersions->get(1)->getHandle()); // Delete a version by the API when limit was reached (chain must have 100 versions) doSetFileVersionsOption(0, false); // enable versioning int verLimit = 102; ASSERT_NO_FATAL_FAILURE(upldVersions(UPFILE, verLimit, folder1Node.get(), &fileHandle)); fileNode.reset(api->getNodeByHandle(fileHandle)); allVersions.reset(api->getVersions(fileNode.get())); ASSERT_EQ(allVersions->size(), verLimit); // upload one more version ASSERT_EQ(upldSingleVersion(UPFILE, verLimit + 1, folder1Node.get(), &fileHandle), API_OK); fileNode.reset(api->getNodeByHandle(fileHandle)); allVersions.reset(api->getVersions(fileNode.get())); ASSERT_EQ(allVersions->size(), verLimit); } /** * ___SdkGetNodesByName___ * Steps: * - Create tree structure * - Get node with exact name (all cloud) * - Get node with name in upper case (all cloud) * - Get node with a string with wild cards (all cloud) * - Get node with a string with wild cards and lower case (all cloud) * - Get node with a string with wild card (all cloud) * - Get node with exact name searching in a folder * - Get node with exact name searching in a folder (no recursive) * - Get node with exact name searching in a folder (no recursive) -> mismatch * - Get node with exact name searching in a folder (node is a folder) * - Get node with a string with wild cards in a folder (no recursive) * - Create contacts * - Share folder * - Get nodes by name recursively inside in share * - Get in shares by name (no recursive) * - Get in shares by name (no recursive) -> mismatch * - Get nodes by name recursively inside out share * - Get out share by name (no recursive) * - Get out share by name (no recursive) -> mismatch * - Get nodes with utf8 characters insensitive case * - Get nodes with accent insensitive case */ TEST_F(SdkTest, SdkGetNodesByName) { LOG_info << "___TEST SdkGetNodesByName"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); unique_ptr rootnode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnode); // Check if exists nodes with that name in the cloud std::string stringSearch = "check"; std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byName("check"); std::unique_ptr nodeList(megaApi[0]->search(filterResults.get())); int nodesWithTest = nodeList->size(); bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string folder = "Folder1"; auto folderHandle = createFolder(0, folder.c_str(), rootnode.get()); ASSERT_NE(folderHandle, UNDEF); waitForResponse(&check); unique_ptr folder1(megaApi[0]->getNodeByHandle(folderHandle)); ASSERT_TRUE(folder1); resetOnNodeUpdateCompletionCBs(); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string folder1_1 = "Folder1_1Check"; auto folder1_1Handle = createFolder(0, folder1_1.c_str(), folder1.get()); ASSERT_NE(folderHandle, UNDEF); waitForResponse(&check); unique_ptr folder1_1Test(megaApi[0]->getNodeByHandle(folder1_1Handle)); ASSERT_TRUE(folder1_1Test); resetOnNodeUpdateCompletionCBs(); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file1 = "file1Check"; createFile(file1, false); MegaHandle file1Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file1Handle, file1.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file1); // important to reset resetOnNodeUpdateCompletionCBs(); unique_ptr nodeFile(megaApi[0]->getNodeByHandle(file1Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 1 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file2 = "file2Check"; createFile(file2, false); MegaHandle file2Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file2Handle, file2.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file2); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file2Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 2 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file3 = "file3Check"; createFile(file3, false); MegaHandle file3Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file3Handle, file3.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file3); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file3Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 3 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file4 = "file4Check"; createFile(file4, false); MegaHandle file4Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file4Handle, file4.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file4); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file4Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 4 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].nodeUpdated = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, mApi[0].nodeUpdated); std::string file5 = "file5Check"; createFile(file5, false); MegaHandle file5Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file5Handle, file5.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file5); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file5Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 5 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file6 = "file6Check"; createFile(file6, false); MegaHandle file6Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file6Handle, file6.data(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file6); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file6Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 6 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file7 = "file7Check"; createFile(file7, false); MegaHandle file7Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file7Handle, file7.data(), folder1_1Test.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file7); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file7Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 7 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string file8 = "file8Check"; createFile(file8, false); MegaHandle file8Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &file8Handle, file8.data(), folder1_1Test.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(file8); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(file8Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 8 node for scenario (error: " << mApi[0].lastError << ")"; mApi[0].nodeUpdated = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, mApi[0].nodeUpdated); std::string fileUtf8 = "ñ01amÚ"; createFile(fileUtf8, false); MegaHandle fileUtf8Handle = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileUtf8Handle, fileUtf8.data(), folder1.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; waitForResponse(&check); deleteFile(fileUtf8); // important to reset resetOnNodeUpdateCompletionCBs(); nodeFile.reset(megaApi[0]->getNodeByHandle(fileUtf8Handle)); ASSERT_NE(nodeFile, nullptr) << "Cannot initialize 5 node for scenario (error: " << mApi[0].lastError << ")"; // Tree structure // Root node // - Folder1 // - Folder1_1Check // - file7Check // - file8Check // - file1Check // - file2Check // - file3Check // - file4Check // - file5Check // - ñ01amÚ // - file6Test stringSearch = file1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), file1Handle); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("FILE2CHECK"); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), file2Handle); stringSearch = "file*Check"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 8) << *nodeList; stringSearch = "file*check"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 8) << *nodeList; stringSearch = "*check"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 9 + nodesWithTest) << *nodeList; stringSearch = file1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(folder1->getHandle()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), file1Handle); stringSearch = file1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocationHandle(folder1->getHandle()); nodeList.reset(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), file1Handle); stringSearch = file7; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(folder1->getHandle()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(nodeList->size(), 0) << *nodeList; stringSearch = folder1_1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(folder1->getHandle()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), folder1_1Handle); stringSearch = std::string("file*check"); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byLocationHandle(folder1->getHandle()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(nodeList->size(), 5) << *nodeList; // --- Create contact relationship --- std::string message = "Hi contact. Let's share some stuff"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } // --- Share a folder with User2 --- bool check1, check2; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(folderHandle, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(folderHandle, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(folder1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); // Wait for the inshare node to be decrypted ASSERT_TRUE(WaitFor([this, &folder1]() { return unique_ptr(megaApi[1]->getNodeByHandle(folder1->getHandle()))->isNodeKeyDecrypted(); }, 60*1000)); // --- Test search in shares --- stringSearch = file8; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); nodeList.reset(megaApi[1]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; stringSearch = "FILE*check"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); nodeList.reset(megaApi[1]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 7) << *nodeList; stringSearch = folder1_1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); nodeList.reset(megaApi[1]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), folder1_1Handle); stringSearch = "folder*"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_INSHARE); nodeList.reset(megaApi[1]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 2) << *nodeList; // --- Test search out shares --- stringSearch = file8; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; stringSearch = "FILE*check"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 7) << *nodeList; stringSearch = folder1_1; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), folder1_1Handle); stringSearch = "folder*"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocation(MegaApi::SEARCH_TARGET_OUTSHARE); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 2) << *nodeList; // --- Test strings with UTF-8 characters stringSearch = "Ñ01am"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), fileUtf8Handle); stringSearch = "ñ01am"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), fileUtf8Handle); // No recursive search stringSearch = "Ñ01am"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); filterResults->byLocationHandle(folder1->getHandle()); nodeList.reset(megaApi[0]->getChildren(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), fileUtf8Handle); stringSearch = "01amú"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), fileUtf8Handle); // --- Test strings with UTF-8 characters stringSearch = "n01am"; filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName(stringSearch.c_str()); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), fileUtf8Handle); filterResults.reset(MegaSearchFilter::createInstance()); filterResults->byName("FILè2CHèCK"); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), file2Handle); } void SdkTest::testResumableTrasfers(const std::string& data, const size_t timeoutInSecs) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED [[maybe_unused]] m_off_t tProgressCompletedPreResume{0}; [[maybe_unused]] m_off_t tProgressContiguousPreResume{0}; globalMegaTestHooks.onProgressCompletedUpdate = ::mega::DebugTestHook::onProgressCompletedUpdate; globalMegaTestHooks.onProgressContiguousUpdate = ::mega::DebugTestHook::onProgressContiguousUpdate; #endif // 1. Create ~16 MB file // 2. Upload file, with speed limit // 3. Logout / Login // 4. Check upload resumption // 5. Finish upload // 6. Download file, with speed limit // 7. Logout / Login // 8. Check download resumption // 1. Create ~data size MB file std::ofstream file(u8path_compat(UPFILE), ios::out); ASSERT_TRUE(file) << "Couldn't create " << UPFILE; for (int i = 0; i < 1000000; i++) { file << data; } ASSERT_EQ(file.tellp(), data.size() * 1000000) << "Wrong size for test file"; file.close(); // 2. Upload file, with speed limit RequestTracker ct(megaApi[0].get()); megaApi[0]->setMaxConnections(1, &ct); ASSERT_EQ(API_OK, ct.waitForResult(60)) << "setMaxConnections() failed or took more than 1 minute"; std::unique_ptr rootnode{megaApi[0]->getRootNode()}; megaApi[0]->setMaxUploadSpeed(2000000); onTransferUpdate_progress = 0; TransferTracker ut(megaApi[0].get()); MegaUploadOptions resumeUploadOptions; resumeUploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(std::string{UPFILE}, rootnode.get(), nullptr, &resumeUploadOptions, &ut /*listener*/); second_timer timer; m_off_t pauseThreshold = 9000000; while (!ut.finished && timer.elapsed() < timeoutInSecs && onTransferUpdate_progress < pauseThreshold) { WaitMillisec(200); } ASSERT_FALSE(ut.finished) << "Upload ended too early, with " << ut.waitForResult(); ASSERT_GT(onTransferUpdate_progress, 0) << "Nothing was uploaded"; // 3. Logout / Login unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); int result = ut.waitForResult(); ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE) << "Upload interrupted with unexpected code: " << result; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED tProgressCompletedPreResume = DebugTestHook::testProgressCompleted; tProgressContiguousPreResume = DebugTestHook::testProgressContiguous; [[maybe_unused]] m_off_t tProgressCompletedAfterResume{0}; [[maybe_unused]] m_off_t tProgressContiguousAfterResume{0}; [[maybe_unused]] std::atomic exitFlagAfterResume{false}; DebugTestHook::testProgressCompleted = 0; DebugTestHook::testProgressContiguous = 0; onTransferStartCustomCb = [&tProgressCompletedAfterResume, &tProgressContiguousAfterResume, &exitFlagAfterResume](MegaTransfer* t) -> void { if (t) { tProgressCompletedAfterResume = t->getTransferredBytes(); } tProgressContiguousAfterResume = DebugTestHook::testProgressContiguous; exitFlagAfterResume = true; }; #endif ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED ASSERT_TRUE(WaitFor(std::bind( [](const std::atomic& flag) { // wait for onTransferStart after login + fetchnodes return flag.load(); }, std::cref(exitFlagAfterResume)), 60000)); ASSERT_EQ(tProgressCompletedPreResume, tProgressCompletedAfterResume) << "Progress complete mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; ASSERT_EQ(tProgressContiguousPreResume, tProgressContiguousAfterResume) << "Progress contiguous mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; #endif // 4. Check upload resumption timer.reset(); unique_ptr transfers(megaApi[0]->getTransfers(MegaTransfer::TYPE_UPLOAD)); while ((!transfers || !transfers->size()) && timer.elapsed() < 20) { WaitMillisec(100); transfers.reset(megaApi[0]->getTransfers(MegaTransfer::TYPE_UPLOAD)); } ASSERT_EQ(transfers->size(), 1) << "Upload ended before resumption was checked, or was not resumed after 20 seconds"; MegaTransfer* upl = transfers->get(0); long long uplBytes = upl->getTransferredBytes(); ASSERT_GT(uplBytes, pauseThreshold / 2) << "Upload appears to have been restarted instead of resumed"; // 5. Finish upload megaApi[0]->setMaxUploadSpeed(-1); timer.reset(); unique_ptr cloudNode( megaApi[0]->getNodeByPathOfType(UPFILE.c_str(), rootnode.get(), MegaNode::TYPE_FILE)); size_t maxAllowedToFinishUpload = timeoutInSecs; while (!cloudNode && timer.elapsed() < maxAllowedToFinishUpload) { WaitMillisec(500); cloudNode.reset( megaApi[0]->getNodeByPathOfType(UPFILE.c_str(), rootnode.get(), MegaNode::TYPE_FILE)); } ASSERT_TRUE(cloudNode) << "Upload did not finish after " << maxAllowedToFinishUpload << " seconds"; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED tProgressCompletedPreResume = 0; tProgressContiguousPreResume = 0; #endif // 6. Download file, with speed limit string downloadedFile = DOTSLASH + DOWNFILE; megaApi[0]->setMaxDownloadSpeed(2000000); onTransferUpdate_progress = 0; timer.reset(); TransferTracker dt(megaApi[0].get()); megaApi[0]->startDownload( cloudNode.get(), downloadedFile.c_str(), nullptr /*fileName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &dt /*listener*/); while (!dt.finished && timer.elapsed() < timeoutInSecs && onTransferUpdate_progress < pauseThreshold) { WaitMillisec(200); } ASSERT_FALSE(dt.finished) << "Download ended too early, with " << dt.waitForResult(); ASSERT_GT(onTransferUpdate_progress, 0) << "Nothing was downloaded"; // 7. Logout / Login session.reset(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); result = dt.waitForResult(); ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE) << "Download interrupted with unexpected code: " << result; #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED tProgressCompletedPreResume = DebugTestHook::testProgressCompleted; tProgressContiguousPreResume = DebugTestHook::testProgressContiguous; tProgressCompletedAfterResume = 0; tProgressContiguousAfterResume = 0; exitFlagAfterResume = false; DebugTestHook::testProgressCompleted = 0; DebugTestHook::testProgressContiguous = 0; onTransferStartCustomCb = [&tProgressCompletedAfterResume, &tProgressContiguousAfterResume, &exitFlagAfterResume](MegaTransfer* t) -> void { if (t) { tProgressCompletedAfterResume = t->getTransferredBytes(); } tProgressContiguousAfterResume = DebugTestHook::testProgressContiguous; exitFlagAfterResume = true; }; #endif ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED ASSERT_TRUE(WaitFor(std::bind( [](const std::atomic& flag) { // wait for onTransferStart after login + fetchnodes return flag.load(); }, std::cref(exitFlagAfterResume)), 60000)); ASSERT_EQ(tProgressCompletedPreResume, tProgressCompletedAfterResume) << "Progress complete mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; ASSERT_EQ(tProgressContiguousPreResume, tProgressContiguousAfterResume) << "Progress contiguous mismatch between logout and onTransferStart values (it shouldn't " "have changed)"; #endif // 8. Check download resumption timer.reset(); transfers.reset(megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)); while ((!transfers || !transfers->size()) && timer.elapsed() < 20) { WaitMillisec(100); transfers.reset(megaApi[0]->getTransfers(MegaTransfer::TYPE_DOWNLOAD)); } ASSERT_EQ(transfers->size(), 1) << "Download ended before resumption was checked, or was not resumed after 20 seconds"; MegaTransfer* dnl = transfers->get(0); long long dnlBytes = dnl->getTransferredBytes(); ASSERT_GT(dnlBytes, pauseThreshold / 2) << "Download appears to have been restarted instead of resumed"; megaApi[0]->setMaxDownloadSpeed(-1); } TEST_F(SdkTest, SdkTestUploads) { LOG_info << "___TEST Test Uploads___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Make sure our clients are working with pro plans. auto accountRestorer = scopedToPro(*megaApi[0]); ASSERT_EQ(result(accountRestorer), API_OK); const auto rootnode = std::unique_ptr{megaApi[0]->getRootNode()}; constexpr auto fileSize = 160000000; unsigned idx = 0; const auto create16MBFile = [&]() { // Add idx to the end of file to ensure that all files are created with different content, // otherwise SDK won't perform a full upload ASSERT_LT(++idx, 10) << "Index value out of bounds"; deleteFile(UPFILE.c_str()); std::ofstream file(u8path_compat(UPFILE), ios::out); ASSERT_TRUE(file) << "Couldn't create " << UPFILE; constexpr auto numLines = 10000000; const string lineStr = "160MB test file" + std::to_string(idx); // 16 characters for (int l = 0; l < numLines; ++l) { file << lineStr; } const auto filePos = file.tellp(); ASSERT_EQ(filePos, fileSize) << "Wrong size for test file"; file.close(); }; const auto setMaxConnections = [&](const auto maxConnections) { ASSERT_EQ(API_OK, doSetMaxConnections(0, maxConnections)); int gMaxConnections{-1}; int gDirection{-1}; ASSERT_EQ(API_OK, doGetMaxUploadConnections(0, gDirection, gMaxConnections)); ASSERT_EQ(gMaxConnections, maxConnections); ASSERT_EQ(gDirection, PUT); }; const auto uploadFile = [&](const int maxConnections) { LOG_debug << "[SdkTestUploads] Test run with maxConnections: " << maxConnections; ASSERT_NO_FATAL_FAILURE(create16MBFile()); ASSERT_NO_FATAL_FAILURE(setMaxConnections(maxConnections)); onTransferUpdate_progress = 0; onTransferUpdate_filesize = 0; mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; const auto& uploadStartTime = std::chrono::system_clock::now(); TransferTracker ut(megaApi[0].get()); MegaUploadOptions uploadOptions; uploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(std::string{UPFILE}, rootnode.get(), nullptr, &uploadOptions, &ut /*listener*/); unsigned int transfer_timeout_in_seconds = 180; ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD], transfer_timeout_in_seconds)) << "Transfer upload time out (180 seconds)"; ASSERT_EQ(API_OK, mApi[0].lastError) << "Cannot upload the test file (error: " << mApi[0].lastError << ")"; const auto& uploadEndTime = std::chrono::system_clock::now(); auto uploadTime = std::chrono::duration_cast(uploadEndTime - uploadStartTime) .count(); LOG_debug << "[SdkTestUploads] uploadTime = " << uploadTime << " ms, size = " << fileSize << ", maxConnections = " << maxConnections << " [speed = " << (((fileSize / uploadTime) * 1000) / 1024) << " KB/s]"; ASSERT_GE(onTransferUpdate_filesize, 0u); ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize); }; const std::vector maxConnectionsVector = {6, 12, 16}; ASSERT_NO_FATAL_FAILURE( std::for_each(maxConnectionsVector.begin(), maxConnectionsVector.end(), uploadFile)); } /** * @brief TEST_F SdkResumableTrasfers * * Tests resumption for file upload and download. */ TEST_F(SdkTest, SdkResumableTrasfers) { auto genStr = [](const size_t len) -> std::string { const std::string base = std::to_string(len) + " MB test file. "; std::string result; result.reserve(len); while (result.size() < len) { result += base; } result.resize(len); return result; }; // Note: testResumableTrasfers limits maxConnections and max Upload/Download speed auto i = 0; const std::map files = {{16, 120}, {19, 240}, {24, 300}}; for (const auto& [fileSize, timeout]: files) { auto data = genStr(fileSize); LOG_info << "___TEST Resumable Trasfers. Iteration (" << ++i << ") FileSize (" << data.size() << " MB)___"; ASSERT_NO_FATAL_FAILURE(testResumableTrasfers(data, timeout)); } } auto makeScopedDefaultPermissions(MegaApi& api, int directory, int file) { auto previousDirectory = api.getDefaultFolderPermissions(); auto previousFile = api.getDefaultFilePermissions(); api.setDefaultFolderPermissions(directory); api.setDefaultFilePermissions(file); return makeScopedDestructor( [=, &api]() { api.setDefaultFolderPermissions(previousDirectory); api.setDefaultFilePermissions(previousFile); }); } auto makeScopedMinimumPermissions(int directory, int file) { using FSA = FileSystemAccess; FSA::setMinimumDirectoryPermissions(directory); FSA::setMinimumFilePermissions(file); return makeScopedDestructor( []() { FSA::setMinimumDirectoryPermissions(0700); FSA::setMinimumFilePermissions(0600); }); } /** * @brief Test file permissions for a download when using megaApi->setDefaultFilePermissions. * * - Test 1: Control test. Default file permissions (0600). * Expected: successful download and successul file opening for reading and writing. * - Test 2: Change file permissions: 0400. Only for reading. * Expected successful download, unsuccessful file opening for reading and writing (only for reading) * - Test 3: Change file permissions: 0700. Read, write and execute. * Expected: successful download and successul file opening for reading and writing. */ TEST_F(SdkTest, SdkTestFilePermissions) { LOG_info << "___TEST SdkTestFilePermissions___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode(megaApi[0]->getRootNode()); // Create a new file string filename = "file_permissions_test.sdktest"; ASSERT_TRUE(createFile(filename, false)) << "Couldn't create test file: '" << filename << "'"; // Upload the file fs::path uploadPath = fs::current_path() / filename; TransferTracker uploadListener(megaApi[0].get()); MegaUploadOptions uploadOptions; uploadOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootUpload = std::unique_ptr{megaApi[0]->getRootNode()}; const auto uploadFilePath = uploadPath.string(); megaApi[0]->startUpload(uploadFilePath, rootUpload.get(), nullptr, &uploadOptions, &uploadListener); ASSERT_EQ(API_OK, uploadListener.waitForResult()); std::unique_ptr nimported(megaApi[0]->getNodeByHandle(uploadListener.resultNodeHandle)); // Delete the local file deleteFile(filename.c_str()); auto downloadFile = [this, &nimported, &filename]() { TransferTracker downloadListener(megaApi[0].get()); megaApi[0]->startDownload(nimported.get(), filename.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener); return downloadListener.waitForResult(); }; auto openFile = [&filename](OpenFlag flag) -> bool { auto fsa = std::make_unique(); fs::path filePath = fs::current_path() / filename.c_str(); LocalPath localfilePath = fspathToLocal(filePath); std::unique_ptr plain_fopen_fa(fsa->newfileaccess(false)); return plain_fopen_fa->fopen(localfilePath, flag, FSLogging::logOnError); }; // TEST 1: Control test. Default file permissions (0600). // Expected: successful download and successul file opening for reading and writing. ASSERT_EQ(API_OK, downloadFile()); ASSERT_TRUE(openFile(OPEN_RDWR)) << "Couldn't open file for read|write"; deleteFile(filename.c_str()); auto minimumPermissions = makeScopedMinimumPermissions(0700, 0400); // TEST 2: Change file permissions: 0400. Only for reading. // Expected successful download, unsuccessful file opening for reading and writing (only for reading) { auto permissions = makeScopedDefaultPermissions(*megaApi[0], 0700, 0400); ASSERT_EQ(API_OK, downloadFile()); ASSERT_TRUE(openFile(OPEN_RDONLY)) << "Couldn't open file for read"; #ifdef _WIN32 // Files should be able to be opened: posix file permissions don't have any effect on // Windows. ASSERT_TRUE(openFile(OPEN_RDWR)) << "Couldn't open files for read|write"; #else ASSERT_FALSE(openFile(OPEN_RDWR)) << "Could open files for read|write, while it shouldn't due to permissions"; #endif deleteFile(filename.c_str()); } // TEST 3: Change file permissions: 0700. Read, write and execute. // Expected: successful download and successul file opening for reading and writing. { auto permissions = makeScopedDefaultPermissions(*megaApi[0], 0700, 0700); ASSERT_EQ(API_OK, downloadFile()); ASSERT_TRUE(openFile(OPEN_RDWR)) << "Couldn't open files for read|write"; deleteFile(filename.c_str()); } } /** * @brief Test folder permissions for a download when using megaApi->setDefaultFolderPermissions. * * Note: folder downloads use MegaFolderDownloadController, which has its own FileAccess object. * * - Test 1. Control test. Default folder permissions. Default file permissions. * Expected a successful download and no issues when accessing the folder. * - Test 2. TEST 2. Change folder permissions: only read (0400). Default file permissions (0600). * Folder permissions: 0400. Expected to fail with API_EINCOMPLETE (-13): request incomplete because it can't write on resource (affecting children, not the parent folder downloaded). * Still, if there is any file children inside the folder, it won't be able able to be opened for reading and writing or even for reading only (because of the folder permissions: lack of the execution perm). * - Test 3: Restore folder permissions. Change file permissions: only read. * Folder permissions: 0700. Expected a successful download and no issues when accessing the folder. * File permissions: 0400. Expected result: cannot open files for R and W (perm: 0400 -> only read). * - Test 4: Default folder permissions. Restore file permissions. * Folder permissions: 0700. Expected a successful download and no issues when accessing the folder. * File permissions: 0600. Expected result: Can open files for R and W (perm: 0600 -> r and w). */ TEST_F(SdkTest, SdkTestFolderPermissions) { LOG_info << "___TEST SdkTestFolderPermissions___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Create a new folder string foldername = "folder_permissions_test.sdktest.folder"; fs::path folderpath = fs::current_path() / foldername; std::error_code ec; fs::remove_all(folderpath, ec); ASSERT_TRUE(!fs::exists(folderpath)) << "Directory already exists (and still exists after trying to remove it): '" << path_u8string(folderpath) << "'"; fs::create_directories(folderpath); // Create a new file inside the new directory string filename = "file_permissions_test.sdktest"; fs::path fileInFolderPath = folderpath / filename; ASSERT_TRUE(createFile(path_u8string(fileInFolderPath), false)) << "Couldn't create test file in directory: '" << path_u8string(fileInFolderPath) << "'"; // Upload the folder TransferTracker uploadListener(megaApi[0].get()); MegaUploadOptions folderUploadOpts; folderUploadOpts.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; auto rootForFolderPerms = std::unique_ptr{megaApi[0]->getRootNode()}; const auto folderUploadPath = folderpath.string(); megaApi[0]->startUpload(folderUploadPath, rootForFolderPerms.get(), nullptr, &folderUploadOpts, &uploadListener); ASSERT_EQ(API_OK, uploadListener.waitForResult()); std::unique_ptr nimported(megaApi[0]->getNodeByHandle(uploadListener.resultNodeHandle)); int nimportedNumChildren = megaApi[0]->getNumChildren(nimported.get()); EXPECT_EQ(nimportedNumChildren, 1) << "This folder should have 1 children (the file inside the folder) but it doesn't. Num children: '" << nimportedNumChildren << "'"; // Delete the local folder deleteFolder(foldername); auto downloadFolder = [this, &nimported, &foldername]() { TransferTracker downloadListener(megaApi[0].get()); megaApi[0]->startDownload(nimported.get(), foldername.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener); return downloadListener.waitForResult(); }; auto openFolderAndFiles = [this, &foldername, &nimported](OpenFlag flag) -> bool { auto fsa = std::make_unique(); fs::path dirPath = fs::current_path() / foldername.c_str(); auto localDirPath = fspathToLocal(dirPath); bool openResult = true; std::unique_ptr diropen_da(fsa->newdiraccess()); if (diropen_da->dopen(&localDirPath, nullptr, false)) { std::unique_ptr childrenList(megaApi[0]->getChildren(nimported.get())); for (int childIndex = 0; (childIndex < childrenList->size()); childIndex++) { if (childrenList->get(childIndex)->isFile()) { auto filesa = std::make_unique(); fs::path filePath = dirPath / childrenList->get(childIndex)->getName(); auto localfilePath = fspathToLocal(filePath); std::unique_ptr plain_fopen_fa(filesa->newfileaccess(false)); openResult &= plain_fopen_fa->fopen(localfilePath, flag, FSLogging::logOnError); } } } return openResult; }; // TEST 1. Control test. Default folder permissions. Default file permissions. // Expected a successful download and no issues when accessing the folder. ASSERT_EQ(API_OK, downloadFolder()); ASSERT_TRUE(openFolderAndFiles(OPEN_RDWR)) << "Couldn't open files for read|write"; deleteFolder(foldername.c_str()); auto minimumPermissions = makeScopedMinimumPermissions(0400, 0400); // TEST 2. Change folder permissions: only read (0400). Default file permissions (0600). // Folder permissions: 0400. Expected to fail with API_EINCOMPLETE (-13): request incomplete because it can't write on resource (affecting children, not the parent folder downloaded). // Still, if there is any file children inside the folder, it won't be able able to be opened for reading and writing or even for reading only (because of the folder permissions: lack of the execution perm). { auto permissions = makeScopedDefaultPermissions(*megaApi[0], 0400, 0600); #ifdef _WIN32 // Folder and files should be able to be opened: posix file/folder permissions don't have // any effect on Windows. ASSERT_EQ(API_OK, downloadFolder()); ASSERT_TRUE(openFolderAndFiles(OPEN_RDONLY)) << "Couldn't open files for read"; ASSERT_TRUE(openFolderAndFiles(OPEN_RDWR)) << "Couldn't open files for read|write"; #else ASSERT_EQ(API_EINCOMPLETE, downloadFolder()) << "Download should have failed as there are not enough permissions to write in the " "folder"; ASSERT_FALSE(openFolderAndFiles(OPEN_RDONLY)) << "Could open files for read, while it shouldn't due to permissions"; ASSERT_FALSE(openFolderAndFiles(OPEN_RDWR)) << "Could open files for read|write, while it shouldn't due to permissions"; #endif deleteFolder(foldername.c_str()); } // TEST 3. Restore folder permissions. Change file permissions: only read. // Folder permissions: 0700. Expected a successful download and no issues when accessing the folder. // File permissions: 0400. Expected result: cannot open files for R and W (perm: 0400 -> only read). { auto permissions = makeScopedDefaultPermissions(*megaApi[0], 0700, 0400); ASSERT_EQ(API_OK, downloadFolder()); ASSERT_TRUE(openFolderAndFiles(OPEN_RDONLY)) << "Couldn't open files for read"; #ifdef _WIN32 ASSERT_TRUE(openFolderAndFiles(OPEN_RDWR)) << "Couldn't open files for read|write"; #else ASSERT_FALSE(openFolderAndFiles(OPEN_RDWR)) << "Could open files for read|write, while it shouldn't due to permissions"; #endif deleteFolder(foldername.c_str()); } // TEST 4. Default folder permissions. Restore file permissions. // Folder permissions: 0700. Expected a successful download and no issues when accessing the folder. // File permissions: 0600. Expected result: Can open files for R and W (perm: 0600 -> r and w). { auto permissions = makeScopedDefaultPermissions(*megaApi[0], 0700, 0600); ASSERT_EQ(API_OK, downloadFolder()); ASSERT_TRUE(openFolderAndFiles(OPEN_RDWR)) << "Couldn't open files for read|write"; deleteFolder(foldername.c_str()); } } TEST_F(SdkTest, GetRecommendedProLevel) { // see also unit test MegaApi.MegaApiImpl_calcRecommendedProLevel in ..>unit>MegaApi_test.cpp ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int level = -1; int err = synchronousGetRecommendedProLevel(0, level); ASSERT_EQ(err, API_OK) << "Get Recommended Pro Level failed: " << MegaError::getErrorString(err); err = synchronousGetPricing(0); ASSERT_EQ(err, API_OK) << "Get Pricing failed: " << MegaError::getErrorString(err); bool starterAvailable = false; for (int i = 0; i < mApi[0].mMegaPricing->getNumProducts(); ++i) { if (mApi[0].mMegaPricing->getProLevel(i) == MegaAccountDetails::ACCOUNT_TYPE_STARTER) { starterAvailable = true; break; } } ASSERT_TRUE(starterAvailable) << "Starter plan not available !"; ASSERT_EQ(level, MegaAccountDetails::ACCOUNT_TYPE_STARTER); } /** * @brief Test JourneyID Tracking support and ViewID generation * * - Test JourneyID functionality (obtained from "ug"/"gmf" commands) * and ViewID generation - Values used for tracking on API requests. * - Ref: SDK-2768 - User Journey Tracking Support * * TEST ViewID: Generate a ViewID and check the hex string obtained. * * Tests JourneyID: * Test 1: JourneyID before login (retrieved from "gmf" command) * Test 2: JourneyID after login (must be the same as the one loaded and cached from "gmf" command) * TEST 3: Full logout and login from a fresh instance - cache file is deleted - new JourneyID retrieved from "ug" command * TEST 4: Unset tracking flag. * TEST 5: Update journeyID with a new hex string - must keep the previous one - must set tracking flag. * TEST 6: Update journeyID with another hex string - must keep the previous one - no values must be updated. * TEST 7: Update journeyID with an empty string - tracking flag must be unset. * TEST 8: Local logout, resume session and do a fetch nodes request - New JID value from Api: JourneyID must be the same, but now tracking flag must be set * TEST 9: Full logout and login from the same instance - JourneyID is reset - * A new JourneyID should be retrieved from the next "ug" command after login: must be different from the original retrieved on TEST 1A. * */ TEST_F(SdkTest, SdkTestJourneyTracking) { LOG_info << "___TEST SdkTestJourneyTracking___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // We need to access the MegaClient to test JourneyID functionality according to specifications. // JID values from ug/gmf commands affect the behavior (set/unset tracking flag, update JourneyID::mJidValue if it's empty, etc.) // We don't have TestInstruments or any other mechanism to change the command response results, so we cannot test this just with regular requests on the intermmediate layer. // Finally, JourneyID is used internally on the MegaClient, it's never shared with the apps, so we need to check its value directly from MegaClient. auto client{megaApi[0]->getClient()}; //==================|| // Test ViewID || //==================|| // Generate a ViewID (16-char hex string) auto viewIdCstr = megaApi[0]->generateViewId(); auto viewId = string(viewIdCstr); ASSERT_FALSE (viewId.empty()) << "Invalid hex string for generated viewId - it's empty"; constexpr size_t HEX_STRING_SIZE = 16; ASSERT_TRUE (viewId.size() == HEX_STRING_SIZE) << "Invalid hex string size for generated viewId (" << viewId.size() << ") - expected (" << HEX_STRING_SIZE << ") [ViewID: '" << viewId << "']"; delete viewIdCstr; //=====================|| // Test JourneyID || //=====================|| // TEST 1: JourneyID before login (retrieved from "gmf" command) auto initialJourneyId = client->getJourneyId(); // Check the actual JourneyID before the logout ASSERT_FALSE(initialJourneyId.empty()) << "There should be a valid initial JourneyID from the first login";; // Logout and GetMiscFlags() logout(0, false, maxTimeout); ASSERT_TRUE(client->getJourneyId().empty()) << "There shouldn't be any valid journeyId value after a full logout - values must be reset and cache file deleted"; gSessionIDs[0] = "invalid"; auto err = synchronousGetMiscFlags(0); ASSERT_EQ(API_OK, err) << "Get misc flags failed (error: " << err << ")"; // Get a new JourneyId from "gmf" command auto journeyIdGmf = client->getJourneyId(); ASSERT_FALSE(initialJourneyId == journeyIdGmf) << "The initial JourneyId (loaded from cache) cannot be equal to the new Journeyid (obtained from \"gmf\" command)"; // TEST 2: JourneyID after login - must be the same than the one loaded and cached after "gmf" command auto trackerLogin1 = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, trackerLogin1->waitForResult()) << " Failed to establish a login/session for account " << 0; auto journeyIdAfterFirstLogin = client->getJourneyId(); ASSERT_TRUE(journeyIdGmf == journeyIdAfterFirstLogin) << "JourneyId value after login must be the same than the previous journeyId loaded from cache"; // Full logout and login from a fresh instance logout(0, false, maxTimeout); auto journeyIdAfterLogout = client->getJourneyId(); ASSERT_TRUE(journeyIdAfterLogout.empty()) << "Wrong value returned from client->getJourneyId(). It should be empty, as JourneyID values are reset and cached file is deleted"; gSessionIDs[0] = "invalid"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // New impl and client - we need to update pointers client = megaApi[0]->getClient(); // TEST 3: Check JourneyID after login from a fresh instance with no cache file // A new JourneyID should have been retrieved from the initial "ug" command auto journeyId = client->getJourneyId(); ASSERT_FALSE (journeyId.empty()) << "Invalid hex string for generated journeyId - it's empty"; ASSERT_TRUE (journeyId.size() == MegaClient::JourneyID::HEX_STRING_SIZE) << "Invalid hex string size for generated journeyId (" << journeyId.size() << ") - expected (" << MegaClient::JourneyID::HEX_STRING_SIZE << ")"; ASSERT_TRUE (client->trackJourneyId()) << "Wrong value for client->trackJourneyId() (false) - expected true: it has a value and tracking flag is ON"; ASSERT_FALSE(journeyId == journeyIdAfterFirstLogin) << "JourneyId value after second login (obtained from \"ug\" command) cannot be the same as the previous journeyId value (obtained from \"gmf\" command - reset from cache)"; // Lambda function for checking JourneyID values after updates // ref_id: A numeric value to identify the call // trackJourneyId: Whether client->trackJourneyId() should return true (stored value, tracking flag is set) or false (tracking flag is unset) auto checkJourneyId = [client, &journeyId] (int ref_id, bool trackJourneyId) { auto actualJourneyId = client->getJourneyId(); ASSERT_TRUE (actualJourneyId == journeyId) << "[Jid " << ref_id << "] Wrong value for actual journeyId" << "(" << actualJourneyId << "). Expected to be equal to original journeyID (" << journeyId << ")"; if (trackJourneyId) { ASSERT_TRUE (client->trackJourneyId()) << "[Jid " << ref_id << "] Wrong value for client->trackJourneyId() - expected TRUE: it has a value and tracking flag must be set"; } else { ASSERT_FALSE (client->trackJourneyId()) << "[Jid " << ref_id << "] Wrong value for client->trackJourneyId() - expected FALSE: it has a value, but tracking flag must be unset"; } }; // TEST 4: Unset tracking flag // JourneyID must still be valid, but tracking flag must be set ASSERT_TRUE (client->setJourneyId("")) << "Wrong returned value for setJourneyId(\"\") - expected TRUE (updated): tracking flag should've been unset"; checkJourneyId(4, false); // TEST 5: Update journeyID with a new hex string - must keep the previous one - must set tracking flag ASSERT_TRUE (client->setJourneyId("FF00FF00FF00FF00")) << "Wrong result for client->setJourneyId(\"FF00FF00FF00FF00\") - expected TRUE (updated): tracking flag should've been set"; checkJourneyId(5, true); // TEST 6: Update journeyID with a another hex string - must keep the previous one - no changes, no cache updates -> setJourneyId should return false ASSERT_FALSE (client->setJourneyId("0000000000000001")) << "Wrong result for client->setJourneyId(\"0000000000000001\") - expected FALSE (not updated): neither journeyId value nor tracking flag should've been updated"; checkJourneyId(6, true); // TEST 7: Update journeyID with an empty string - tracking flag must be unset ASSERT_TRUE (client->setJourneyId("")) << "Wrong result for client->setJourneyId(\"\") - expected TRUE: tracking flag should've been unset"; checkJourneyId(7, false); // TEST 8: Locallogout, resume session and request to fetch nodes - New JID value from Api: JourneyID must be the same, but now tracking flag must be set unique_ptr session(dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); checkJourneyId(8, true); // TEST 9: Full logout and login from the same instance - values must be reset and cache file deleted // A new JourneyID value should be retrieved from the next "ug" command after login ASSERT_NO_FATAL_FAILURE(logout(0, false, maxTimeout)); gSessionIDs[0] = "invalid"; auto trackerLogin2 = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); ASSERT_EQ(API_OK, trackerLogin2->waitForResult()) << " Failed to establish a login/session for account " << 0; ASSERT_TRUE(client->getJourneyId().empty()) << "Wrong value returned from client->getJourneyId(). It should be empty, as JourneyID values are reset and cached file is deleted"; ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // This will get a new JourneyID auto newJourneyId = client->getJourneyId(); // New value should be different from the original JourneyID ASSERT_FALSE(newJourneyId == journeyId) << "Wrong result when comparing newJourneyId and old JourneyId. They should be different after a full logout - values are reset and cached file is deleted"; journeyId = newJourneyId; // Update journeyId reference (captured in lambda functions) checkJourneyId(9, true); } /** * Make sure instances of RequestTracker disconnect themselves from the API. * * FIXME: Should be a unit test rather than an integration test. */ TEST_F(SdkTest, SdkTestListenerRemovedWhenRequestTrackerDestroyed) { // Get our hands on a client we can play with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Convenience. auto* api = megaApi[0].get(); // Instantiate request tracker. auto* tracker = new RequestTracker(api, nullptr); // Register tracker with the API. api->addRequestListener(tracker); // Destroy tracker. delete tracker; // Tracker should no longer be associated with the API. ASSERT_FALSE(api->removeRequestListener(tracker)); } /** * SdkTestGetNodeByMimetype * Steps: * - Create files (test.txt, test.sh, test.pdf, test.json, test.ods, test.doc, test.org, test.png, * test.mp3, test.mp4, test.err, test(without extension)) * - Search for files of type text(.txt) * - Search for files of type program(.sh) * - Search for files of type pdf(.pdf) * - Search for files of type document(.txt, .doc, .org) * - Search for files of type misc(.json) * - Search for files of type org(.org) * - Search for files of type spreadsheet(.ods) * - Search for files of type unknown(.err) * - Search for files of type audio(.mp3) * - Search for files of type video(.mp4) * - Search for files of type image(.png) * - Search for files of type all docs(.txt, .pdf, .ods, .doc, .org) * - Search for files of type all visual media(.png, .mp4) * - Search for files of type others(test.err, test(without extension)) */ TEST_F(SdkTest, SdkTestGetNodeByMimetype) { LOG_info << "___TEST Get Node By Mimetypes___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_NE(rootnode.get(), nullptr); ASSERT_TRUE(createFile(PUBLICFILE.c_str(), false)) << "Couldn't create " << PUBLICFILE; const char imageFile[] = "test.png"; ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, imageFile)) << "Cannot get " << IMAGEFILE << " from artifactory"; const char audioFile[] = "test.mp3"; ASSERT_TRUE(getFileFromArtifactory("test-data/" + AUDIOFILE, audioFile)) << "Cannot get " << AUDIOFILE << " from artifactory"; const char videoFile[] = "test.mp4"; ASSERT_TRUE(getFileFromArtifactory("test-data/" + VIDEOFILE, videoFile)) << "Cannot get " << VIDEOFILE << " from artifactory"; auto imageNode = sdk_test::uploadFile(megaApi[0].get(), fs::path(imageFile), rootnode.get()); ASSERT_TRUE(imageNode) << "Cannot upload " << imageFile; auto audioNode = sdk_test::uploadFile(megaApi[0].get(), fs::path(audioFile), rootnode.get()); ASSERT_TRUE(audioNode) << "Cannot upload " << audioFile; auto videoNode = sdk_test::uploadFile(megaApi[0].get(), fs::path(videoFile), rootnode.get()); ASSERT_TRUE(videoNode) << "Cannot upload " << videoFile; auto publicFilePath = fs::path(PUBLICFILE); const char txtFile[] = "test.txt"; auto txtNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), txtFile); ASSERT_TRUE(txtNode) << "Cannot upload" << txtFile; const char codeFile[] = "test.sh"; auto codeNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), codeFile); ASSERT_TRUE(codeNode) << "Cannot upload " << codeFile; const char pdfFile[] = "test.pdf"; auto pdfNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), pdfFile); ASSERT_TRUE(pdfNode) << "Cannot upload " << pdfFile; const char jsonFile[] = "test.json"; auto jsonNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), jsonFile); ASSERT_TRUE(jsonNode) << "Cannot upload " << jsonFile; const char spreadsheetFile[] = "test.ods"; auto spreadsheetNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), spreadsheetFile); ASSERT_TRUE(spreadsheetNode) << "Cannot upload " << spreadsheetFile; const char documentFile[] = "test.doc"; auto documentNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), documentFile); ASSERT_TRUE(documentNode) << "Cannot upload " << documentFile; const char orgFile[] = "test.org"; auto orgNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), orgFile); ASSERT_TRUE(orgNode) << "Cannot upload " << orgFile; const char unkownExtensionFile[] = "test.err"; auto unknownExtensionNode = sdk_test::uploadFile(megaApi[0].get(), publicFilePath, rootnode.get(), unkownExtensionFile); ASSERT_TRUE(unknownExtensionNode) << "Cannot upload " << unkownExtensionFile; const char withouExtensionFile[] = "test"; RequestTracker nodeCopyTracker(megaApi[0].get()); megaApi[0]->copyNode(unknownExtensionNode.get(), rootnode.get(), withouExtensionFile, &nodeCopyTracker); ASSERT_EQ(API_OK, nodeCopyTracker.waitForResult()) << "Could not copy " << unkownExtensionFile << " as " << withouExtensionFile; MegaHandle handleWithoutExtensionFile = nodeCopyTracker.getNodeHandle(); std::unique_ptr filterResults(MegaSearchFilter::createInstance()); filterResults->byCategory(MegaApi::FILE_TYPE_PROGRAM); std::unique_ptr nodeList(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), codeNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PDF); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), pdfNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_DOCUMENT); nodeList.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_DESC)); ASSERT_EQ(nodeList->size(), 3) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), txtNode->getHandle()); ASSERT_EQ(nodeList->get(1)->getHandle(), orgNode->getHandle()); ASSERT_EQ(nodeList->get(2)->getHandle(), documentNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_MISC); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), jsonNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_SPREADSHEET); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), spreadsheetNode->getHandle()); filterResults->byCategory( MegaApi::FILE_TYPE_ALL_DOCS); // any of {DOCUMENT, PDF, PRESENTATION, SPREADSHEET} nodeList.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); // order Alphabetical asc ASSERT_EQ(nodeList->size(), 5) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), documentNode->getHandle()); ASSERT_EQ(nodeList->get(1)->getHandle(), spreadsheetNode->getHandle()); ASSERT_EQ(nodeList->get(2)->getHandle(), orgNode->getHandle()); ASSERT_EQ(nodeList->get(3)->getHandle(), pdfNode->getHandle()); ASSERT_EQ(nodeList->get(4)->getHandle(), txtNode->getHandle()); filterResults->byCategory( MegaApi::FILE_TYPE_OTHERS); // none of {PHOTO, VIDEO, AUDIO, MISC, PROGRAM, DOCUMENT, PDF, // PRESENTATION, SPREADSHEET} nodeList.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); // order Alphabetical asc ASSERT_EQ(nodeList->size(), 2) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), handleWithoutExtensionFile); ASSERT_EQ(nodeList->get(1)->getHandle(), unknownExtensionNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_AUDIO); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), audioNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_PHOTO); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), imageNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_VIDEO); nodeList.reset(megaApi[0]->search(filterResults.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), videoNode->getHandle()); filterResults->byCategory(MegaApi::FILE_TYPE_ALL_VISUAL_MEDIA); // any of {PHOTO, VIDEO} nodeList.reset(megaApi[0]->search(filterResults.get(), MegaApi::ORDER_DEFAULT_ASC)); // order Alphabetical asc ASSERT_EQ(nodeList->size(), 2) << *nodeList; ASSERT_EQ(nodeList->get(0)->getHandle(), videoNode->getHandle()); ASSERT_EQ(nodeList->get(1)->getHandle(), imageNode->getHandle()); deleteFile(PUBLICFILE); deleteFile(imageFile); deleteFile(audioFile); deleteFile(videoFile); } /** * @brief TEST_F SdkTestMegaVpnCredentials * * Tests the MEGA VPN functionality. * This test is valid for both FREE and PRO testing accounts. * If the testing account is FREE, the request results are adjusted to the API error expected in those cases. * * 0) DELETE existing credentials. Generally, none should be present. * 1) GET the MEGA VPN regions. * 2) Choose one of the regions above to PUT a new VPN credential. It should return: * - The SlotID where the credential has been created. * - The User Public Key. * - The credential string to be used for VPN connection. * 3-a) Check the MEGA VPN credentials. They should be valid. * 3-b) Check nonexistent MEGA VPN credentials. They should be invalid. * 4) GET the MEGA VPN credentials. Check the related fields for the returned slotID: * - IPv4 and IPv6 * - DeviceID * - ClusterID * - Cluster Public Key * 5) DELETE the MEGA VPN credentials associated with the slotID used above. * 6) DELETE the MEGA VPN credentials from an unoccupied slot. * 7) DELETE the MEGA VPN credentials from an invalid slot. */ TEST_F(SdkTest, SdkTestMegaVpnCredentials) { LOG_info << "___TEST SdkTestMegaVpnCredentials"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_EQ(API_ENOENT, doDelVpnCredential(0, 149)) << "Wrong error when deleting VPN credentials from unused slotID 149"; ASSERT_EQ(API_ENOENT, doDelVpnCredential(0, 200)) << "Wrong error when deleting VPN credentials from unused slotID 200"; ASSERT_EQ(API_ENOENT, doDelVpnCredential(0, 3000)) << "Wrong error when deleting VPN credentials from unused slotID 3000"; int result; // 0) Delete any existing credentials (most times there won't be any) { std::unique_ptr megaVpnCredentials; result = doGetVpnCredentials(0, megaVpnCredentials); if (result == API_OK) { ASSERT_NE(megaVpnCredentials, nullptr); // Get SlotIDs { std::unique_ptr slotIDsList; slotIDsList.reset(megaVpnCredentials->getSlotIDs()); ASSERT_NE(slotIDsList, nullptr); ASSERT_NE(slotIDsList->size(), 0); for (int i = 0; i < slotIDsList->size(); ++i) { int slotID = static_cast(slotIDsList->get(i)); result = doDelVpnCredential(0, slotID); ASSERT_EQ(API_OK, result) << "deleting the VPN credentials from the slotID " << slotID << " failed"; } } // Check credentials again result = doGetVpnCredentials(0, megaVpnCredentials); ASSERT_EQ(API_ENOENT, result) << "there should not be any credentials left after deleting them"; ASSERT_EQ(megaVpnCredentials, nullptr) << "credentials have been deleted, but MegaVpnCredentials object is not NULL"; } } // 1) Get VPN regions to choose one of them { std::unique_ptr vpnRegions; std::unique_ptr vpnRegionsDetailed; result = doGetVpnRegions(0, vpnRegions, vpnRegionsDetailed); ASSERT_EQ(API_OK, result) << "getting the VPN regions failed"; ASSERT_TRUE(vpnRegions) << "list of VPN regions is NULL"; ASSERT_TRUE(vpnRegions->size()) << "list of VPN regions is empty"; const char* vpnRegion = vpnRegions->get(0); // Select the first vpn region ASSERT_TRUE(vpnRegion) << "VPN region is NULL"; ASSERT_TRUE(*vpnRegion) << "VPN region is EMPTY"; auto testDetailedRegionList = [](const std::unique_ptr& regionList) { const MegaVpnRegion* region = regionList->get(0); ASSERT_THAT(region, testing::NotNull()); ASSERT_STRNE(region->getName(), ""); std::unique_ptr clusters{region->getClusters()}; ASSERT_THAT(clusters, testing::NotNull()); ASSERT_GT(clusters->size(), 0); std::unique_ptr clusterIDs{clusters->getKeys()}; ASSERT_THAT(clusterIDs, testing::NotNull()); ASSERT_GT(clusterIDs->size(), 0); std::unique_ptr firstCluster{clusters->get(clusterIDs->get(0))}; ASSERT_THAT(firstCluster, testing::NotNull()); ASSERT_THAT(firstCluster->getHost(), testing::NotNull()); std::unique_ptr clusterDns{firstCluster->getDns()}; ASSERT_THAT(clusterDns, testing::NotNull()); ASSERT_GT(clusterDns->size(), 0); std::unique_ptr adBlockingDns{firstCluster->getAdBlockingDns()}; ASSERT_THAT(adBlockingDns, testing::NotNull()); ASSERT_GT(adBlockingDns->size(), 0); ASSERT_STRNE(region->getCountryCode(), ""); ASSERT_STRNE(region->getCountryName(), ""); }; ASSERT_THAT(vpnRegionsDetailed, testing::NotNull()); ASSERT_EQ(static_cast(vpnRegionsDetailed->size()), vpnRegions->size()); const MegaVpnRegion* region = vpnRegionsDetailed->get(0); ASSERT_THAT(region, testing::NotNull()); ASSERT_STREQ(region->getName(), vpnRegion); ASSERT_NO_FATAL_FAILURE(testDetailedRegionList(vpnRegionsDetailed)); // Get the PRO level for the testing account ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(0, true, true, true)) << "Cannot get account details"; bool isProAccount = (mApi[0].accountDetails->getProLevel() != MegaAccountDetails::ACCOUNT_TYPE_FREE); // 2) Put VPN credential on the chosen region int slotID = -1; std::string userPubKey; std::string newCredential; result = doPutVpnCredential(0, slotID, userPubKey, newCredential, vpnRegion); if (isProAccount) { ASSERT_EQ(API_OK, result) << "adding a new VPN credential failed"; ASSERT_TRUE(slotID > 0) << "slotID should be greater than 0"; size_t expectedPubKeyB64Size = ((ECDH::PUBLIC_KEY_LENGTH * 4) + 2) / 3; // URL-safe B64 length (no trailing '=') ASSERT_EQ(userPubKey.size(), expectedPubKeyB64Size) << "User Public Key does not have the expected size"; ASSERT_FALSE(newCredential.empty()) << "VPN Credential data is EMPTY"; // 3-a) Check the VPN credentials result = doCheckVpnCredential(0, userPubKey.c_str()); ASSERT_EQ(API_OK, result) << "checking the VPN credentials failed"; } else { ASSERT_EQ(API_EACCESS, result) << "adding a new VPN credential on a free account return wrong error"; ASSERT_EQ(slotID, 0); ASSERT_EQ(API_EARGS, doDelVpnCredential(0, slotID)) << "deleting non-existing VPN credentials from slotID " << slotID << " returned wrong error"; } // 3-b) Check nonexistent VPN credentials { string nonexistentUserPK = "obI7rWzm3qVQL5zOxHzv2XFHsP1kOOTR1mE7NluVjDM"; result = doCheckVpnCredential(0, nonexistentUserPK.c_str()); ASSERT_EQ(API_EACCESS, result) << "checking the VPN credentials with a nonexistent User Public Key should have returned API_EACCESS"; } // 4) Get VPN credentials and search for the credential associated with the returned SlotID { std::unique_ptr megaVpnCredentials; result = doGetVpnCredentials(0, megaVpnCredentials); if (isProAccount) { ASSERT_EQ(API_OK, result) << "getting the VPN credentials failed"; ASSERT_TRUE(megaVpnCredentials != nullptr) << "MegaVpnCredentials is NULL"; // Get SlotIDs - it should not be empty { std::unique_ptr slotIDsList; slotIDsList.reset(megaVpnCredentials->getSlotIDs()); ASSERT_TRUE(slotIDsList) << "MegaIntegerList of slotIDs is NULL"; ASSERT_EQ(slotIDsList->size(), 1) << "slotIDs list should have 1 credential"; } // Get IPv4 const char* ipv4 = megaVpnCredentials->getIPv4(slotID); ASSERT_TRUE(ipv4) << "IPv4 value not found for SlotID: " << slotID; ASSERT_TRUE(*ipv4) << "IPv4 value is empty for SlotID: " << slotID; // Get IPv6 const char* ipv6 = megaVpnCredentials->getIPv6(slotID); ASSERT_TRUE(ipv6) << "IPv6 value not found for SlotID: " << slotID; ASSERT_TRUE(*ipv6) << "IPv6 value is empty for SlotID: " << slotID; // Get DeviceID (it must be a valid pointer, but it can be empty if there's no associated deviceID) const char* deviceID = megaVpnCredentials->getDeviceID(slotID); ASSERT_TRUE(deviceID) << "deviceID not found for SlotID: " << slotID; // Get ClusterID int clusterID = megaVpnCredentials->getClusterID(slotID); ASSERT_TRUE(clusterID >= 0) << "clusterID should be a positive value. SlotID: " << slotID; // Get Cluster Public Key const char* clusterPublicKey = megaVpnCredentials->getClusterPublicKey(clusterID); ASSERT_TRUE(clusterPublicKey) << "Cluster Public Key not found for ClusterID: " << clusterID; ASSERT_TRUE(*clusterPublicKey) << "Cluster Public Key is empty for ClusterID: " << clusterID; // Check VPN regions, they should not be empty std::unique_ptr vpnRegionsFromCredentials; vpnRegionsFromCredentials.reset(megaVpnCredentials->getVpnRegions()); ASSERT_TRUE(vpnRegionsFromCredentials) << "list of VPN regions is NULL"; ASSERT_TRUE(vpnRegionsFromCredentials->size()) << "list of VPN regions is empty"; std::unique_ptr vpnRegionsDetailed{ megaVpnCredentials->getVpnRegionsDetailed()}; ASSERT_THAT(vpnRegionsDetailed, testing::NotNull()) << "list of detailed VPN regions was null"; ASSERT_EQ(static_cast(vpnRegionsDetailed->size()), vpnRegionsFromCredentials->size()); const MegaVpnRegion* regionDetailed = vpnRegionsDetailed->get(0); ASSERT_THAT(regionDetailed, testing::NotNull()); ASSERT_STREQ(regionDetailed->getName(), vpnRegionsFromCredentials->get(0)); ASSERT_NO_FATAL_FAILURE(testDetailedRegionList(vpnRegionsDetailed)); } else { ASSERT_EQ(API_ENOENT, result) << "getting the VPN credentials for a free account returned wrong error"; ASSERT_FALSE(megaVpnCredentials) << "MegaVpnCredentials is NOT NULL for a free account"; ASSERT_EQ(slotID, 0); ASSERT_EQ(API_EARGS, doDelVpnCredential(0, slotID)) << "deleting non-existing VPN credentials from slotID " << slotID << " returned wrong error"; } } if (isProAccount) { // 5) Delete VPN credentials from an occupied slot result = doDelVpnCredential(0, slotID); ASSERT_EQ(API_OK, result) << "deleting the VPN credentials from the slotID " << slotID << " failed"; // Check again the VPN credentials, they should be invalid now result = doCheckVpnCredential(0, userPubKey.c_str()); ASSERT_EQ(API_EACCESS, result) << "VPN credentials are still valid after being deleted"; // Use the same slotID (it should be empty now) for the next test } else { slotID = 1; // Test the 1st slotID for a FREE account (must be empty) } // 6) Delete VPN credentials from an unoccupied slot. Expecting ENOENT: SlotID is empty result = doDelVpnCredential(0, slotID); ASSERT_EQ(API_ENOENT, result) << "deleting the VPN credentials from unused slotID " << slotID << " returned wrong error"; // 7) Delete VPN credentials from an invalid slot. Expecting EARGS: SlotID is not valid slotID = -1; result = doDelVpnCredential(0, slotID); ASSERT_EQ(API_EARGS, result) << "deleting the VPN credential from the invalid slotID " << slotID << " didn't return the expected error value"; } } #ifdef ENABLE_SYNC /** * @brief TEST_F SdkTestMoveToSyncDebris * - add syncs with folder and file * - wait to check if file has been upload to cloud * - move file to sync debris folder * - wait to check file has been moved to sync debris cloud folder * - remove sync */ TEST_F(SdkTest, SdkTestMoveToSyncDebris) { LOG_info << "___TEST SdkTestMoveToSyncDebris___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); fs::path base = "SdkTestMoveToSyncDebris"; std::string syncFolder = "sync1"; fs::path basePath = base / syncFolder; const auto localPath = fs::current_path() / basePath; ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), base)); // Create local directories and a files. fs::create_directories(localPath); std::string fileName = "fileTest1"; const auto filePath = localPath / fs::path(fileName); ASSERT_TRUE(createFile(path_u8string(filePath), false)); LOG_verbose << "Creating the remote folders to be synced to."; std::unique_ptr remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); auto nh = createFolder(0, syncFolder.c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode1(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode1.get(), nullptr); LOG_verbose << "Add syncs"; bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); const auto lp = path_u8string(localPath); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp.c_str(), nullptr, remoteBaseNode1->getHandle(), nullptr)) << "API Error adding a new sync"; ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode1.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); waitForResponse(&check); resetOnNodeUpdateCompletionCBs(); std::unique_ptr file(megaApi[0]->getChildNode(remoteBaseNode1.get(), fileName.c_str())); ASSERT_NE(file, nullptr); handle backupId = sync->getBackupId(); // Move file to local sync debris folder ASSERT_EQ(API_OK, syncMoveToDebris(0, path_u8string(filePath).c_str(), backupId)); check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(file->getHandle(), MegaNode::CHANGE_TYPE_PARENT, check); waitForResponse(&check); resetOnNodeUpdateCompletionCBs(); LOG_verbose << "SyncRemoveRemoteNode : Remove Syncs that fail"; { ASSERT_EQ(API_OK, synchronousRemoveSync(0, backupId)); // already removed. } ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), base)); } #endif // ENABLE_SYNC /** * @brief SdkTesResumeSessionInFolderLinkDeleted * * Resume session in a folder link deleted and expect to be logged out. */ TEST_F(SdkTest, SdkTesResumeSessionInFolderLinkDeleted) { // Configure folder owner test instance unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); // Create folder const unsigned int folderOwnerApiIndex{0}; std::unique_ptr rootNode{megaApi[folderOwnerApiIndex]->getRootNode()}; ASSERT_THAT(rootNode, ::testing::NotNull()); const std::string folderName{"Test"}; auto folderNodeHandle{createFolder(folderOwnerApiIndex, folderName.c_str(), rootNode.get())}; ASSERT_NE(folderNodeHandle, UNDEF); std::unique_ptr folderNode{megaApi[folderOwnerApiIndex]->getNodeByHandle(folderNodeHandle)}; ASSERT_THAT(folderNode, ::testing::NotNull()); // Create folder link const auto folderLink{createPublicLink(folderOwnerApiIndex, folderNode.get(), 0, maxTimeout, false)}; // Configure folder visitor test instance numberOfTestInstances = 2; mApi.resize(numberOfTestInstances); megaApi.resize(numberOfTestInstances); unsigned int folderVisitorApiIndex{1}; const bool checkCredentials{false}; configureTestInstance(folderVisitorApiIndex, "", "", checkCredentials); // Login into folder link auto requestTracker{asyncRequestLoginToFolder(folderVisitorApiIndex, folderLink.c_str())}; ASSERT_EQ(requestTracker->waitForResult(), API_OK) << "Failed to login to folder " << folderLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(folderVisitorApiIndex)); // Get session unique_ptr session{megaApi[folderVisitorApiIndex]->dumpSession()}; // Local logout locallogout(folderVisitorApiIndex); // Remove folder link removePublicLink(folderOwnerApiIndex, folderNode.get()); // Login into folder link const auto requestFlagType{MegaRequest::TYPE_LOGOUT}; auto& requestFlag{mApi[folderVisitorApiIndex].requestFlags[requestFlagType]}; requestFlag = false; ASSERT_EQ(synchronousFastLogin(folderVisitorApiIndex, session.get(), this), API_OK); ASSERT_NO_FATAL_FAILURE(fetchnodes(folderVisitorApiIndex)); const unsigned int timeoutInSeconds{60}; ASSERT_TRUE(waitForResponse(&requestFlag, timeoutInSeconds)) << "Logout did not happen after " << timeoutInSeconds << " seconds"; } class SdkTestAvatar : public SdkTest { protected: unsigned int mApiIndex{0}; std::unique_ptr mUser; fs::path mDstAvatarPath{sdk_test::getTestDataDir()/AVATARDST}; const std::string PATH_SEPARATOR{LocalPath::localPathSeparator_utf8}; public: void SetUp() override { // Configure test instance unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); // Get user mUser.reset(megaApi[mApiIndex]->getMyUser()); ASSERT_THAT(mUser, ::testing::NotNull()); // Set avatar ASSERT_TRUE(getFileFromArtifactory("test-data/" + AVATARSRC, AVATARSRC)); ASSERT_EQ(API_OK, synchronousSetAvatar(mApiIndex, AVATARSRC.c_str())); } void TearDown() override { // Remove avatar ASSERT_EQ(API_OK, synchronousSetAvatar(mApiIndex, nullptr)); // Check the avatar was removed mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_ENOENT, synchronousGetUserAvatar(mApiIndex, mUser.get(), mDstAvatarPath.string().c_str())); } }; /** * @brief SdkTestGetAvatarIntoAFile * * Get avatar into a file. */ TEST_F(SdkTestAvatar, SdkTestGetAvatarIntoAFile) { // Get avatar mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_OK, synchronousGetUserAvatar(mApiIndex, mUser.get(), mDstAvatarPath.string().c_str())); // Check avatar in local filesystem ASSERT_TRUE(fs::exists(mDstAvatarPath)); // Remove avatar from local filesystem ASSERT_TRUE(fs::remove(mDstAvatarPath)); } /** * @brief SdkTestGetAvatarIntoADirectoryEndingWithSlash * * Get avatar into a directory ending with slash. */ TEST_F(SdkTestAvatar, SdkTestGetAvatarIntoADirectoryEndingWithSlash) { // Get avatar std::string dstAvatarPath{sdk_test::getTestDataDir().string()}; dstAvatarPath.append(PATH_SEPARATOR); ASSERT_THAT(dstAvatarPath, ::testing::EndsWith(PATH_SEPARATOR)); mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_OK, synchronousGetUserAvatar(mApiIndex, mUser.get(), dstAvatarPath.c_str())); // Check avatar in local filesystem dstAvatarPath.append(mUser->getEmail()); dstAvatarPath.append("0.jpg"); ASSERT_TRUE(fs::exists(dstAvatarPath)); // Remove avatar from local filesystem ASSERT_TRUE(fs::remove(dstAvatarPath)); } /** * @brief SdkTestGetAvatarIntoADirectoryNotEndingWithSlash * * Get avatar into a directory not ending with slash. */ TEST_F(SdkTestAvatar, SdkTestGetAvatarIntoADirectoryNotEndingWithSlash) { // Get avatar std::string dstAvatarPath{sdk_test::getTestDataDir().string()}; ASSERT_THAT(dstAvatarPath, ::testing::Not(::testing::EndsWith(PATH_SEPARATOR))); mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_EWRITE, synchronousGetUserAvatar(mApiIndex, mUser.get(), dstAvatarPath.c_str())); } /** * @brief SdkTestGetAvatarIntoAnEmptyPath * * Get avatar into an empty path. */ TEST_F(SdkTestAvatar, SdkTestGetAvatarIntoAnEmptyPath) { // Get avatar mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_EARGS, synchronousGetUserAvatar(mApiIndex, mUser.get(), "")); } /** * @brief SdkTestGetAvatarIntoANullPath * * Get avatar into a null path. */ TEST_F(SdkTestAvatar, SdkTestGetAvatarIntoANullPath) { // Get avatar mApi[mApiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false; ASSERT_EQ(API_EARGS, synchronousGetUserAvatar(mApiIndex, mUser.get(), nullptr)); } /** * @brief Set and get Welcome dialog visibility */ TEST_F(SdkTest, SetGetVisibleWelcomeDialog) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr requestTrackerGetOriginalVisibleWelcomeDialog{ asyncRequestGetVisibleWelcomeDialog(apiIndex)}; ASSERT_THAT(requestTrackerGetOriginalVisibleWelcomeDialog->waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const auto originalVisibleWelcomeDialog{ requestTrackerGetOriginalVisibleWelcomeDialog->getFlag()}; const auto newVisibleWelcomeDialog{!originalVisibleWelcomeDialog}; ASSERT_EQ(API_OK, synchronousSetVisibleWelcomeDialog(apiIndex, newVisibleWelcomeDialog)); std::unique_ptr requestTrackerGetNewVisibleWelcomeDialog{ asyncRequestGetVisibleWelcomeDialog(apiIndex)}; ASSERT_EQ(API_OK, requestTrackerGetNewVisibleWelcomeDialog->waitForResult()); ASSERT_EQ(newVisibleWelcomeDialog, requestTrackerGetNewVisibleWelcomeDialog->getFlag()); ASSERT_EQ(API_OK, synchronousSetVisibleWelcomeDialog(apiIndex, originalVisibleWelcomeDialog)); std::unique_ptr requestTrackerGetRestoredVisibleWelcomeDialog{ asyncRequestGetVisibleWelcomeDialog(apiIndex)}; ASSERT_EQ(API_OK, requestTrackerGetRestoredVisibleWelcomeDialog->waitForResult()); ASSERT_EQ(originalVisibleWelcomeDialog, requestTrackerGetRestoredVisibleWelcomeDialog->getFlag()); } /** * @brief Create node tree with empty parent node */ TEST_F(SdkTest, CreateNodeTreeWithEmptyParentNode) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; // Create node tree std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, nullptr, nullptr, nullptr)}; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, nullptr, nodeTree.get())); } /** * @brief Create node tree with empty node tree */ TEST_F(SdkTest, CreateNodeTreeWithEmptyNodeTree) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, parentNode.get(), nullptr)); } /** * @brief Create node tree with malformed node tree */ TEST_F(SdkTest, CreateNodeTreeWithMalformedNodeTree) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree with both child-tree and upload-data std::unique_ptr nodeTreeChild{MegaNodeTree::createInstance(nullptr, nullptr, nullptr, nullptr)}; std::unique_ptr completeUploadData{ MegaCompleteUploadData::createInstance(nullptr, nullptr, nullptr)}; std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nodeTreeChild.get(), nullptr, nullptr, completeUploadData.get())}; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, parentNode.get(), nodeTree.get())); } /** * @brief Create node tree with source-handle and child-tree */ TEST_F(SdkTest, CreateNodeTreeWithSourceHandleAndChildTree) { const unsigned int numberOfTestInstances{ 1 }; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{ 0 }; std::unique_ptr parentNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree with source-handle and child-tree MegaHandle sourceHandle = parentNode->getHandle(); // not important, any dummy (but valid) value will do std::unique_ptr childTree{ MegaNodeTree::createInstance(nullptr, nullptr, nullptr, nullptr) }; std::unique_ptr treeWithSourceAndChild{ MegaNodeTree::createInstance(childTree.get(), nullptr, nullptr, nullptr, sourceHandle) }; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, parentNode.get(), treeWithSourceAndChild.get())); } /** * @brief Create node tree with source-handle and upload-data */ TEST_F(SdkTest, CreateNodeTreeWithSourceHandleAndUploadData) { const unsigned int numberOfTestInstances{ 1 }; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{ 0 }; std::unique_ptr parentNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree with both source-handle and upload-data MegaHandle sourceHandle = parentNode->getHandle(); // not important, any dummy (but valid) value will do std::unique_ptr uploadData{ MegaCompleteUploadData::createInstance(nullptr, nullptr, nullptr) }; std::unique_ptr treeWithSourceAndUploadData{ MegaNodeTree::createInstance(nullptr, nullptr, nullptr, uploadData.get(), sourceHandle) }; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, parentNode.get(), treeWithSourceAndUploadData.get())); } /** * @brief Create node tree with one directory without name */ TEST_F(SdkTest, CreateNodeTreeWithOneDirectoryWithoutName) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, nullptr, nullptr, nullptr)}; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, parentNode.get(), nodeTree.get())); } /** * @brief Create node tree with directory as source * * Attempt to copy directory/ * to directory_copy/ */ TEST_F(SdkTest, CreateNodeTreeWithDirectoryAsSource) { const unsigned int numberOfTestInstances{ 1 }; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{ 0 }; std::unique_ptr rootNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_THAT(rootNode, ::testing::NotNull()); // Create directory const std::string directoryName{ "directory" }; MegaHandle directoryHandle = createFolder(apiIndex, directoryName.c_str(), rootNode.get()); ASSERT_NE(directoryHandle, INVALID_HANDLE); std::unique_ptr directoryNode{ megaApi[apiIndex]->getNodeByHandle(directoryHandle) }; ASSERT_THAT(directoryNode, ::testing::NotNull()); // attempt to copy a source directory string directoryNameCopy = directoryName + "_copy"; std::unique_ptr directoryTreeCopy{ MegaNodeTree::createInstance(nullptr, directoryNameCopy.c_str(), nullptr, nullptr, directoryHandle) }; ASSERT_EQ(API_EARGS, synchronousCreateNodeTree(apiIndex, rootNode.get(), directoryTreeCopy.get())); } /** * @brief Create node tree with one directory and no S4 attribute * * directory/ */ TEST_F(SdkTest, CreateNodeTreeWithOneDirectoryAndNoS4Attribute) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree const std::string directoryName{"directory"}; std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, directoryName.c_str(), nullptr, nullptr)}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), nodeTree.get(), &requestTracker); nodeTree.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Check result const auto directoryNodeHandle{resultNodeTree->getNodeHandle()}; std::unique_ptr directoryNode{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandle)}; ASSERT_THAT(directoryNode, ::testing::NotNull()); ASSERT_STREQ(directoryName.c_str(), directoryNode->getName()); ASSERT_STREQ("", directoryNode->getS4()); } /** * @brief Create node tree with one directory and S4 attribute * * directory/ */ TEST_F(SdkTest, CreateNodeTreeWithOneDirectoryAndS4Attribute) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree const std::string directoryName{"directory"}; const std::string s4AttributeValue{"value"}; std::unique_ptr nodeTree{MegaNodeTree::createInstance(nullptr, directoryName.c_str(), s4AttributeValue.c_str(), nullptr)}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), nodeTree.get(), &requestTracker); nodeTree.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Check result const auto directoryNodeHandle{resultNodeTree->getNodeHandle()}; std::unique_ptr directoryNode{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandle)}; ASSERT_THAT(directoryNode, ::testing::NotNull()); ASSERT_STREQ(directoryName.c_str(), directoryNode->getName()); ASSERT_STREQ(s4AttributeValue.c_str(), directoryNode->getS4()); } template std::time_t timePointToTimeT(T timePoint) { using namespace std::chrono; // In C++17, time_point used system_clock on POSIX and a custom TrivialClock in VS, which had different // epoch start. The latter had no way to convert a timestamp to time_t (like system_clock::to_time_t()). // This was improved in C++20, but we're not there yet. // With no portable way of converting time_point to time_t, let's try this workaround: auto portableTimePoint = time_point_cast(timePoint - T::clock::now() + system_clock::now()); return system_clock::to_time_t(portableTimePoint); } /** * @brief Create node tree with one file * * logo.png */ TEST_F(SdkTest, CreateNodeTreeWithOneFile) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; // File upload ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); const auto fileSize{getFilesize(IMAGEFILE)}; std::string fingerprint; std::string string64UploadToken; std::string string64FileKey; ASSERT_NO_FATAL_FAILURE(synchronousMediaUploadIncomplete(apiIndex, fileSize, IMAGEFILE.c_str(), IMAGEFILE_C.c_str(), fingerprint, string64UploadToken, string64FileKey)); // Create node tree std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); std::unique_ptr completeUploadData{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str())}; std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, IMAGEFILE.c_str(), nullptr, completeUploadData.get())}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), nodeTree.get(), &requestTracker); nodeTree.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Check result const auto fileNodeHandle{resultNodeTree->getNodeHandle()}; std::unique_ptr fileNode{megaApi[apiIndex]->getNodeByHandle(fileNodeHandle)}; ASSERT_THAT(fileNode, ::testing::NotNull()); ASSERT_STREQ(IMAGEFILE.c_str(), fileNode->getName()); ASSERT_EQ(fileSize, fileNode->getSize()); // Check "mega" fingerprint ASSERT_STREQ(fileNode->getFingerprint(), fingerprint.c_str()); // Check that mtime was kept auto modtime = std::filesystem::last_write_time(IMAGEFILE_C); auto modtime_t = timePointToTimeT(modtime); ASSERT_EQ(modtime_t, fileNode->getModificationTime()); } /** * @brief Create node tree to copy existing source file */ TEST_F(SdkTest, CreateNodeTreeToCopyExistingSource) { const unsigned int numberOfTestInstances{ 1 }; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{ 0 }; // File upload std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; ASSERT_TRUE(createFile(UPFILE, false)) << "Couldn't create " << UPFILE; MegaHandle uploadedNode = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadedNode, UPFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << UPFILE; std::unique_ptr newNode(megaApi[0]->getNodeByHandle(uploadedNode)); ASSERT_THAT(newNode, ::testing::NotNull()); // Create a copy of the already existing file string fileCopy = UPFILE + "_copy"; const std::string s4AttributeValue{"value"}; std::unique_ptr nodeTree{MegaNodeTree::createInstance(nullptr, fileCopy.c_str(), s4AttributeValue.c_str(), nullptr, newNode->getHandle())}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(rootnode.get(), nodeTree.get(), &requestTracker); rootnode.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); std::unique_ptr newNodeCopy{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree->getNodeHandle()) }; ASSERT_THAT(newNodeCopy, ::testing::NotNull()); ASSERT_STREQ(newNodeCopy->getName(), fileCopy.c_str()); ASSERT_STREQ(newNodeCopy->getS4(), s4AttributeValue.c_str()); ASSERT_EQ(newNodeCopy->getSize(), newNode->getSize()); } /** * @brief Create node tree with multiple levels of directories * * directory_0/directory_1/directory_2/ */ TEST_F(SdkTest, CreateNodeTreeWithMultipleLevelsOfDirectories) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree const std::string directoryNameLevel2{"directory_2"}; std::unique_ptr nodeTreeLevel2{ MegaNodeTree::createInstance(nullptr, directoryNameLevel2.c_str(), nullptr, nullptr)}; const std::string directoryNameLevel1{"directory_1"}; std::unique_ptr nodeTreeLevel1{ MegaNodeTree::createInstance(nodeTreeLevel2.get(), directoryNameLevel1.c_str(), nullptr, nullptr)}; const std::string directoryNameLevel0{"directory_0"}; std::unique_ptr nodeTreeLevel0{ MegaNodeTree::createInstance(nodeTreeLevel1.get(), directoryNameLevel0.c_str(), nullptr, nullptr)}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), nodeTreeLevel0.get(), &requestTracker); nodeTreeLevel0.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Check result const auto directoryNodeHandleLevel0{resultNodeTree->getNodeHandle()}; std::unique_ptr directoryNodeLevel0{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel0)}; ASSERT_THAT(directoryNodeLevel0, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel0.c_str(), directoryNodeLevel0->getName()); const auto directoryNodeHandleLevel1{resultNodeTree->getNodeTreeChild()->getNodeHandle()}; std::unique_ptr directoryNodeLevel1{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel1)}; ASSERT_THAT(directoryNodeLevel1, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel1.c_str(), directoryNodeLevel1->getName()); ASSERT_EQ(directoryNodeLevel1->getParentHandle(), directoryNodeLevel0->getHandle()); const auto directoryNodeHandleLevel2{resultNodeTree->getNodeTreeChild()->getNodeTreeChild()->getNodeHandle()}; std::unique_ptr directoryNodeLevel2{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel2)}; ASSERT_THAT(directoryNodeLevel2, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel2.c_str(), directoryNodeLevel2->getName()); ASSERT_EQ(directoryNodeLevel2->getParentHandle(), directoryNodeLevel1->getHandle()); } /** * @brief Create node tree with multiple levels of directories and one file at the end * * directory_0/directory_1/directory_2/logo.png */ TEST_F(SdkTest, CreateNodeTreeWithMultipleLevelsOfDirectoriesAndOneFileAtTheEnd) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; std::unique_ptr parentNode{megaApi[apiIndex]->getRootNode()}; ASSERT_THAT(parentNode, ::testing::NotNull()); // File upload ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); const auto fileSize{getFilesize(IMAGEFILE)}; std::string fingerprint; std::string string64UploadToken; std::string string64FileKey; ASSERT_NO_FATAL_FAILURE(synchronousMediaUploadIncomplete(apiIndex, fileSize, IMAGEFILE.c_str(), IMAGEFILE_C.c_str(), fingerprint, string64UploadToken, string64FileKey)); // Create node tree std::unique_ptr completeUploadData{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str())}; std::unique_ptr nodeTreeLevel3{ MegaNodeTree::createInstance(nullptr, IMAGEFILE.c_str(), nullptr, completeUploadData.get())}; const std::string directoryNameLevel2{"directory_2"}; std::unique_ptr nodeTreeLevel2{ MegaNodeTree::createInstance(nodeTreeLevel3.get(), directoryNameLevel2.c_str(), nullptr, nullptr)}; const std::string directoryNameLevel1{"directory_1"}; std::unique_ptr nodeTreeLevel1{ MegaNodeTree::createInstance(nodeTreeLevel2.get(), directoryNameLevel1.c_str(), nullptr, nullptr)}; const std::string directoryNameLevel0{"directory_0"}; std::unique_ptr nodeTreeLevel0{ MegaNodeTree::createInstance(nodeTreeLevel1.get(), directoryNameLevel0.c_str(), nullptr, nullptr)}; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), nodeTreeLevel0.get(), "192.168.0.0:0000", // dummy IP just to test sending "cip" &requestTracker); nodeTreeLevel0.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Check result const auto directoryNodeHandleLevel0{resultNodeTree->getNodeHandle()}; std::unique_ptr directoryNodeLevel0{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel0)}; ASSERT_THAT(directoryNodeLevel0, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel0.c_str(), directoryNodeLevel0->getName()); const auto directoryNodeHandleLevel1{resultNodeTree->getNodeTreeChild()->getNodeHandle()}; std::unique_ptr directoryNodeLevel1{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel1)}; ASSERT_THAT(directoryNodeLevel1, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel1.c_str(), directoryNodeLevel1->getName()); const auto directoryNodeHandleLevel2{resultNodeTree->getNodeTreeChild()->getNodeTreeChild()->getNodeHandle()}; std::unique_ptr directoryNodeLevel2{ megaApi[apiIndex]->getNodeByHandle(directoryNodeHandleLevel2)}; ASSERT_THAT(directoryNodeLevel2, ::testing::NotNull()); ASSERT_STREQ(directoryNameLevel2.c_str(), directoryNodeLevel2->getName()); const auto fileNodeHandle{resultNodeTree->getNodeTreeChild()->getNodeTreeChild()->getNodeTreeChild()->getNodeHandle()}; std::unique_ptr fileNode{megaApi[apiIndex]->getNodeByHandle(fileNodeHandle)}; ASSERT_THAT(fileNode, ::testing::NotNull()); ASSERT_STREQ(IMAGEFILE.c_str(), fileNode->getName()); ASSERT_EQ(fileSize, fileNode->getSize()); // Check that fileHandle was populated when file node was created Base64Str b64FileHandle{fileNode->getHandle()}; MegaStringMap* fileHandles = requestTracker.request->getMegaStringMap(); ASSERT_THAT(fileHandles, ::testing::NotNull()); ASSERT_EQ(fileHandles->size(), 1); ASSERT_THAT(fileHandles->get(b64FileHandle), ::testing::NotNull()); // Check that fileHandle was populated when file download url was fetched RequestTracker tracker{megaApi[apiIndex].get()}; megaApi[apiIndex]->getDownloadUrl(fileNode.get(), true, true, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); MegaStringMap* fileHandle = tracker.request->getMegaStringMap(); ASSERT_THAT(fileHandle, ::testing::NotNull()); ASSERT_EQ(fileHandle->size(), 1); ASSERT_THAT(fileHandle->get(b64FileHandle), ::testing::NotNull()); ASSERT_STREQ(fileHandle->get(b64FileHandle), fileHandles->get(b64FileHandle)); } /** * @brief Create node tree version using identical upload data. * * The handle of the existing file should be returned, and no new version should be added. */ TEST_F(SdkTest, CreateNodeTreeVersionUsingIdenticalUploadData) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const unsigned apiIndex = 0; // Prepare the file to upload createFile(PUBLICFILE.c_str(), false); const int64_t fileSize = getFilesize(PUBLICFILE); string encryptedFile = PUBLICFILE + "_encr"; std::string fingerprint; std::string string64UploadToken; std::string string64FileKey; // Upload file, incomplete ASSERT_NO_FATAL_FAILURE(synchronousMediaUploadIncomplete(apiIndex, fileSize, PUBLICFILE.c_str(), encryptedFile.c_str(), fingerprint, string64UploadToken, string64FileKey)); // Prepare destination std::unique_ptr parentNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree from uploaded data std::unique_ptr uploadData{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str()) }; std::unique_ptr fileTreeFromData { MegaNodeTree::createInstance(nullptr, PUBLICFILE.c_str(), nullptr, uploadData.get()) }; RequestTracker requestTrackerFirstTree(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), fileTreeFromData.get(), &requestTrackerFirstTree); ASSERT_THAT(requestTrackerFirstTree.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree1 = requestTrackerFirstTree.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree1, ::testing::NotNull()); ASSERT_NE(resultNodeTree1->getNodeHandle(), INVALID_HANDLE); // Ensure there's only 1 version std::unique_ptr fileNode{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree1->getNodeHandle()) }; ASSERT_THAT(fileNode, ::testing::NotNull()); std::unique_ptr allVersions{ megaApi[apiIndex]->getVersions(fileNode.get()) }; ASSERT_THAT(allVersions, ::testing::NotNull()); ASSERT_EQ(allVersions->size(), 1); // Attempt to create another node tree from the same data std::unique_ptr uploadData2{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str()) }; std::unique_ptr fileTreeFromData2 { MegaNodeTree::createInstance(nullptr, PUBLICFILE.c_str(), nullptr, uploadData2.get()) }; RequestTracker requestTrackerSecondTree(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), fileTreeFromData2.get(), &requestTrackerSecondTree); ASSERT_THAT(requestTrackerSecondTree.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree2 = requestTrackerSecondTree.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree2, ::testing::NotNull()); // Confirm there's still only 1 version std::unique_ptr fileNode2{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree2->getNodeHandle()) }; ASSERT_THAT(fileNode2, ::testing::NotNull()); std::unique_ptr allVersions2{ megaApi[apiIndex]->getVersions(fileNode2.get()) }; ASSERT_THAT(allVersions2, ::testing::NotNull()); ASSERT_EQ(allVersions2->size(), 1); ASSERT_EQ(resultNodeTree2->getNodeHandle(), resultNodeTree1->getNodeHandle()); } /** * @brief Create node tree version using identical source file. * * The handle of the existing file should be returned, and no new version should be added. */ TEST_F(SdkTest, CreateNodeTreeVersionUsingIdenticalSourceFile) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const unsigned apiIndex = 0; // File upload std::unique_ptr rootNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_TRUE(createFile(UPFILE, false)) << "Couldn't create " << UPFILE; MegaHandle upHandle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &upHandle, UPFILE.c_str(), rootNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << UPFILE; ASSERT_NE(upHandle, INVALID_HANDLE); // Ensure there's only 1 version std::unique_ptr upNode(megaApi[0]->getNodeByHandle(upHandle)); ASSERT_THAT(upNode, ::testing::NotNull()); std::unique_ptr allVersions{ megaApi[apiIndex]->getVersions(upNode.get()) }; ASSERT_THAT(allVersions, ::testing::NotNull()); ASSERT_EQ(allVersions->size(), 1); // Make a copy of the file string fileCopy = UPFILE + "_copy"; RequestTracker nodeCopyTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->copyNode(upNode.get(), rootNode.get(), fileCopy.c_str(), &nodeCopyTracker); ASSERT_EQ(API_OK, nodeCopyTracker.waitForResult()); ASSERT_NE(nodeCopyTracker.getNodeHandle(), INVALID_HANDLE); std::unique_ptr upNodeCopy(megaApi[0]->getNodeByHandle(nodeCopyTracker.getNodeHandle())); // Create a new version of the initial file using its copy as source std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, UPFILE.c_str(), nullptr, nullptr, upNodeCopy->getHandle()) }; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(rootNode.get(), nodeTree.get(), &requestTracker); nodeTree.reset(); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Confirm there's still only 1 version std::unique_ptr upNodeVersion{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree->getNodeHandle()) }; ASSERT_THAT(upNodeVersion, ::testing::NotNull()); std::unique_ptr allVersions2{ megaApi[apiIndex]->getVersions(upNodeVersion.get()) }; ASSERT_THAT(allVersions2, ::testing::NotNull()); ASSERT_EQ(allVersions2->size(), 1); ASSERT_EQ(upNodeVersion->getHandle(), upNode->getHandle()); } /** * @brief Create node tree version using different upload data. * * A new handle should be returned, a new version should be added, and the handle of * the existing file should correspond to the previous version. */ TEST_F(SdkTest, CreateNodeTreeVersionUsingDifferentUploadData) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const unsigned apiIndex = 0; // Prepare the file to upload createFile(PUBLICFILE.c_str(), false); const int64_t fileSize = getFilesize(PUBLICFILE); string encryptedFile = PUBLICFILE + "_encr"; std::string fingerprint; std::string string64UploadToken; std::string string64FileKey; // Upload file, incomplete ASSERT_NO_FATAL_FAILURE(synchronousMediaUploadIncomplete(apiIndex, fileSize, PUBLICFILE.c_str(), encryptedFile.c_str(), fingerprint, string64UploadToken, string64FileKey)); // Prepare destination std::unique_ptr parentNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_THAT(parentNode, ::testing::NotNull()); // Create node tree from uploaded data std::unique_ptr uploadData{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str()) }; std::unique_ptr fileTreeFromData { MegaNodeTree::createInstance(nullptr, PUBLICFILE.c_str(), nullptr, uploadData.get()) }; RequestTracker requestTrackerFirstTree(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), fileTreeFromData.get(), &requestTrackerFirstTree); ASSERT_THAT(requestTrackerFirstTree.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree1 = requestTrackerFirstTree.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree1, ::testing::NotNull()); ASSERT_NE(resultNodeTree1->getNodeHandle(), INVALID_HANDLE); // Ensure there's only 1 version std::unique_ptr fileNode{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree1->getNodeHandle()) }; ASSERT_THAT(fileNode, ::testing::NotNull()); std::unique_ptr allVersions{ megaApi[apiIndex]->getVersions(fileNode.get()) }; ASSERT_THAT(allVersions, ::testing::NotNull()); ASSERT_EQ(allVersions->size(), 1); // Upload file again, incomplete ASSERT_NO_FATAL_FAILURE(synchronousMediaUploadIncomplete(apiIndex, fileSize, PUBLICFILE.c_str(), encryptedFile.c_str(), fingerprint, string64UploadToken, string64FileKey)); // Create node tree from the new data std::unique_ptr uploadData2{ MegaCompleteUploadData::createInstance(fingerprint.c_str(), string64UploadToken.c_str(), string64FileKey.c_str()) }; std::unique_ptr fileTreeFromData2 { MegaNodeTree::createInstance(nullptr, PUBLICFILE.c_str(), nullptr, uploadData2.get()) }; RequestTracker requestTrackerSecondTree(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(parentNode.get(), fileTreeFromData2.get(), &requestTrackerSecondTree); ASSERT_THAT(requestTrackerSecondTree.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree2 = requestTrackerSecondTree.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree2, ::testing::NotNull()); ASSERT_NE(resultNodeTree2->getNodeHandle(), INVALID_HANDLE); // Confirm there are 2 versions now std::unique_ptr fileNode2{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree2->getNodeHandle()) }; ASSERT_THAT(fileNode2, ::testing::NotNull()); std::unique_ptr allVersions2{ megaApi[apiIndex]->getVersions(fileNode2.get()) }; ASSERT_THAT(allVersions2, ::testing::NotNull()); ASSERT_EQ(allVersions2->size(), 2); ASSERT_EQ(allVersions2->get(1)->getHandle(), resultNodeTree1->getNodeHandle()); ASSERT_EQ(allVersions2->get(0)->getHandle(), resultNodeTree2->getNodeHandle()); ASSERT_NE(resultNodeTree2->getNodeHandle(), resultNodeTree1->getNodeHandle()); } /** * @brief Create node tree version using different source file. * * A new handle should be returned, a new version should be added, and the handle of * the existing file should correspond to the previous version. */ TEST_F(SdkTest, CreateNodeTreeVersionUsingDifferentSourceFile) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const unsigned apiIndex = 0; // File upload std::unique_ptr rootNode{ megaApi[apiIndex]->getRootNode() }; ASSERT_TRUE(createFile(UPFILE, false, "UPFILE")) << "Couldn't create " << UPFILE; MegaHandle upHandle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &upHandle, UPFILE.c_str(), rootNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << UPFILE; ASSERT_NE(upHandle, INVALID_HANDLE); // Ensure there's only 1 version std::unique_ptr upNode(megaApi[0]->getNodeByHandle(upHandle)); ASSERT_THAT(upNode, ::testing::NotNull()); std::unique_ptr allVersions{ megaApi[apiIndex]->getVersions(upNode.get()) }; ASSERT_THAT(allVersions, ::testing::NotNull()); ASSERT_EQ(allVersions->size(), 1); // File 2 upload ASSERT_TRUE(createFile(PUBLICFILE, false, "PUBLICFILE")) << "Couldn't create " << PUBLICFILE; MegaHandle pubHandle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &pubHandle, PUBLICFILE.c_str(), rootNode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << PUBLICFILE; ASSERT_NE(pubHandle, INVALID_HANDLE); // Create a new version of the initial file using a different file as source std::unique_ptr nodeTree{ MegaNodeTree::createInstance(nullptr, UPFILE.c_str(), nullptr, nullptr, pubHandle) }; RequestTracker requestTracker(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(rootNode.get(), nodeTree.get(), &requestTracker); ASSERT_THAT(requestTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const MegaNodeTree* resultNodeTree = requestTracker.request->getMegaNodeTree(); ASSERT_THAT(resultNodeTree, ::testing::NotNull()); // Confirm there are 2 versions now std::unique_ptr upNodeVersion{ megaApi[apiIndex]->getNodeByHandle(resultNodeTree->getNodeHandle()) }; ASSERT_THAT(upNodeVersion, ::testing::NotNull()); std::unique_ptr allVersions2{ megaApi[apiIndex]->getVersions(upNodeVersion.get()) }; ASSERT_THAT(allVersions2, ::testing::NotNull()); ASSERT_EQ(allVersions2->size(), 2); ASSERT_EQ(allVersions2->get(1)->getHandle(), upHandle); ASSERT_EQ(allVersions2->get(0)->getHandle(), upNodeVersion->getHandle()); ASSERT_NE(upNodeVersion->getHandle(), upHandle); } #ifdef ENABLE_SYNC /** * ___RemoveInshareElementToSynDebris___ * Steps: * - Two users userA and userB * - UserA creates a tree * - UserA shares this tree with userB * - UserB creates a sync with the inshare * - UserB removes part of the tree * Result: * - Removed sub-tree is added a useB SyncDebris folder * - Sub-tree is removed from inshare */ TEST_F(SdkTest, RemoveInshareElementToSynDebris) { const MrProper cleanUp( [this]() { // It's a good practise to unregister listeners as soon as test finish for (unsigned i = 0; i < mApi.size(); ++i) { cleanupPerApiBackupsMonitorMap(i); } }); MegaHandle testBackupID{INVALID_HANDLE}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // --- Create some nodes to share --- // |--Shared-folder // |--Shared-subfolder // |--file1.txt // |--file2.txt std::unique_ptr rootnode{ megaApi[0]->getRootNode() }; static constexpr char foldername1[] = "Shared-folder"; MegaHandle hfolder1 = createFolder(0, foldername1, rootnode.get()); ASSERT_NE(hfolder1, UNDEF) << "Cannot create " << foldername1; std::unique_ptr n1{ megaApi[0]->getNodeByHandle(hfolder1) }; ASSERT_NE(n1, nullptr); static constexpr char foldername2[] = "Shared-subfolder"; MegaHandle hfolder2 = createFolder(0, foldername2, n1.get()); ASSERT_NE(hfolder2, UNDEF) << "Cannot create " << foldername1; std::unique_ptr n2{ megaApi[0]->getNodeByHandle(hfolder2) }; ASSERT_NE(n2, nullptr); createFile(PUBLICFILE.c_str(), false); MegaHandle hfile1 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile1, PUBLICFILE.c_str(), n2.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; createFile(UPFILE.c_str(), false); MegaHandle hfile2 = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &hfile2, UPFILE.c_str(), n2.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a second test file"; // --- Create a new contact to share to --- string message = "Hi contact. Let's share some stuff"; mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(0, mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) // at the target side (auxiliar account) << "Contact request creation not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) // at the source side (main account) << "Contact request creation not received after " << maxTimeout << " seconds"; mApi[1].cr.reset(); // --- Verify credentials in both accounts --- if (gManualVerification) { if (!areCredentialsVerified(0, mApi[1].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email));} if (!areCredentialsVerified(1, mApi[0].email)) {ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email));} } // --- Share a folder with User2 --- MegaHandle nodeHandle = n1->getHandle(); bool check1, check2; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeHandle, MegaNode::CHANGE_TYPE_OUTSHARE, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeHandle, MegaNode::CHANGE_TYPE_INSHARE, check2); ASSERT_NO_FATAL_FAILURE(shareFolder(n1.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node update not received after " << maxTimeout << " seconds"; // important to reset resetOnNodeUpdateCompletionCBs(); ASSERT_EQ(check1, true); ASSERT_EQ(check2, true); fs::path localBasePath = makeNewTestRoot(); fs::path localFolderPath = localBasePath / "SyncFolder"; fs::create_directories(localFolderPath); MegaHandle newSyncRootNodeHandle = UNDEF; const unsigned monIdx{1}; int err = synchronousSyncFolder(monIdx, &newSyncRootNodeHandle, MegaSync::TYPE_TWOWAY, path_u8string(localFolderPath).c_str(), nullptr, hfolder1, nullptr); ASSERT_TRUE(err == API_OK) << "Backup folder failed (error: " << err << ")"; std::unique_ptr syncFolder(megaApi[monIdx]->getNodeByHandle(hfolder1)); std::unique_ptr sync = waitForSyncState(megaApi[monIdx].get(), syncFolder.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); testBackupID = sync->getBackupId(); ASSERT_NO_FATAL_FAILURE(mApi[monIdx].addsyncMonitor(testBackupID)) << "Cannot add sync monitor for AccountIdx: " << monIdx << ", and BackupId: " << testBackupID; fs::path folder2Path = localFolderPath / foldername2; fs::path filePath1 = folder2Path / PUBLICFILE; fs::path filePath2 = folder2Path / UPFILE; // Wait until both files have been created at local path ASSERT_TRUE(waitForEvent( [filePath1, filePath2]() { ifstream f1(path_u8string(filePath1).c_str()); ifstream f2(path_u8string(filePath2).c_str()); if (f1.good() && f2.good()) { return true; } return false; })) << "Files haven't been download at local path"; // Wait one of both files have been deleted from inshare. Copy operation has to be executed before deleting ASSERT_TRUE(mApi[monIdx].waitForBackupSyncUpToDate(testBackupID)); mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfile1, MegaNode::CHANGE_TYPE_REMOVED, check1); mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(hfile1, MegaNode::CHANGE_TYPE_REMOVED, check2); deleteFolder(path_u8string(folder2Path)); ASSERT_TRUE(waitForResponse(&check2)) // at the target side (auxiliar account) << "Node hasn't been removed after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&check1)) // at the target side (main account) << "Node hasn't been removed after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // Check file exits at SyncDebris folder std::unique_ptr rubbishbin(megaApi[1]->getRubbishNode()); std::unique_ptr filter(MegaSearchFilter::createInstance()); filter->byLocationHandle(rubbishbin->getHandle()); filter->byName(PUBLICFILE.c_str()); std::unique_ptr nodes(megaApi[1]->search(filter.get())); ASSERT_EQ(nodes->size(), 1) << "Invalid number of nodes at rubbisbin (debris folder)"; std::unique_ptr path(megaApi[1]->getNodePath(nodes->get(0))); std::string stringPath(path.get()); ASSERT_NE(stringPath.find("SyncDebris"), std::string::npos); ASSERT_EQ(API_OK, synchronousRemoveSync(1, sync->getBackupId())); } #endif /** * @brief Set and get Terms of Service visibility */ TEST_F(SdkTest, SetGetVisibleTermsOfService) { const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); const unsigned int apiIndex{0}; const bool defaultTermsOfService = true; RequestTracker requestTrackerFirstGet(megaApi[apiIndex].get()); megaApi[apiIndex]->getVisibleTermsOfService(&requestTrackerFirstGet); ASSERT_THAT(requestTrackerFirstGet.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); ASSERT_EQ(defaultTermsOfService, requestTrackerFirstGet.getFlag()); const bool modifiedTermsOfService = !defaultTermsOfService; RequestTracker requestTrackerFirstSet(megaApi[apiIndex].get()); megaApi[apiIndex]->setVisibleTermsOfService(modifiedTermsOfService, &requestTrackerFirstSet); ASSERT_EQ(API_OK, requestTrackerFirstSet.waitForResult()); RequestTracker requestTrackerSecondGet(megaApi[apiIndex].get()); megaApi[apiIndex]->getVisibleTermsOfService(&requestTrackerSecondGet); ASSERT_EQ(API_OK, requestTrackerSecondGet.waitForResult()); ASSERT_EQ(modifiedTermsOfService, requestTrackerSecondGet.getFlag()); RequestTracker requestTrackerSecondSet(megaApi[apiIndex].get()); megaApi[apiIndex]->setVisibleTermsOfService(defaultTermsOfService, &requestTrackerSecondSet); ASSERT_EQ(API_OK, requestTrackerSecondSet.waitForResult()); RequestTracker requestTrackerThirdGet(megaApi[apiIndex].get()); megaApi[apiIndex]->getVisibleTermsOfService(&requestTrackerThirdGet); ASSERT_EQ(API_OK, requestTrackerThirdGet.waitForResult()); ASSERT_EQ(defaultTermsOfService, requestTrackerThirdGet.getFlag()); } #ifdef ENABLE_CHAT /** * @brief Give and remove access to download a file from a chat */ TEST_F(SdkTest, SdkTestGiveRemoveChatAccess) { CASE_info << "started"; testGiveRemoveChatAccess(false); CASE_info << "finished"; } /** * @brief Give and remove access to download a file from a public chat */ TEST_F(SdkTest, SdkTestGiveRemovePublicChatAccess) { CASE_info << "started"; testGiveRemoveChatAccess(true); CASE_info << "finished"; } #endif TEST_F(SdkTest, GetFileFromArtifactorySuccessfully) { const std::string relativeUrl{"test-data/gfx-processing-crash/default_irradiance.dds"}; const fs::path output{"default_irradiance.dds"}; ASSERT_TRUE(getFileFromArtifactory(relativeUrl, output)); ASSERT_TRUE(fs::exists(output)); fs::remove(output); } TEST_F(SdkTest, GenerateRandomCharsPassword) { LOG_debug << "### Test characters-based random passwords generation"; bool useUpper = false; bool useDigits = false; bool useSymbols = false; const unsigned int minLength = 8, maxLength = 64; const unsigned int length = 10; LOG_debug << "# Test out-of-bounds password generation request"; std::unique_ptr pwd{ MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, minLength - 1)}; ASSERT_FALSE(pwd); pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, maxLength + 1)); ASSERT_FALSE(pwd); const auto validatePassword = [&useUpper, &useDigits, &useSymbols] (const std::string& pwd) -> bool { bool lowerFound = false; bool upperFound = false; bool digitFound = false; bool symbolFound = false; const std::set validSymbols {'!','@','#','$','%','^','&','*','(',')'}; for (auto c : pwd) { if (!upperFound && std::isupper(c)) { if (!useUpper) return false; upperFound = true; } if (!digitFound && std::isdigit(c)) { if (!useDigits) return false; digitFound = true; } if (!symbolFound && validSymbols.count(c)) { if (!useSymbols) return false; symbolFound = true; } if (!lowerFound && std::islower(c)) lowerFound = true; } return lowerFound && (useUpper == upperFound) && (useDigits == digitFound) && (useSymbols == symbolFound); }; LOG_debug << "\t# Test only lower case characters"; useUpper = useDigits = useSymbols = false; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower and upper case characters only"; useDigits = useSymbols = false; useUpper = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower and digits only"; useUpper = useSymbols = false; useDigits = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower and symbols only"; useUpper = useDigits = false; useSymbols = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower, upper, and digits"; useSymbols = false; useUpper = useDigits = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower, upper, and symbols"; useDigits = false; useUpper = useSymbols = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower, digits, and symbols"; useUpper = false; useDigits = useSymbols = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); LOG_debug << "\t# Test lower, upper, digits, and symbols"; useUpper = useDigits = useSymbols = true; pwd.reset(MegaApi::generateRandomCharsPassword(useUpper, useDigits, useSymbols, length)); ASSERT_TRUE(pwd); ASSERT_TRUE(std::strlen(pwd.get()) == length); ASSERT_TRUE(validatePassword(pwd.get())) << "Invalid generated password " << pwd.get(); } /** * @brief Enable test-notifications by setting their IDs in "^!tnotif". * Get enabled-notifications (from cmd("ug")."notifs"). * Get the complete notifications (using cmd("gnotif")). * Set and get the last-read-notification ("^!lnotif"). * Set and get the last-actioned-banner ("^!lbannr"). */ TEST_F(SdkTest, DynamicMessageNotifs) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Sending a null list of test-notifications should fail RequestTracker nullNotifsTracker(megaApi[0].get()); megaApi[0]->enableTestNotifications(nullptr, &nullNotifsTracker); ASSERT_EQ(nullNotifsTracker.waitForResult(), API_EARGS); // Clear any test-notifications that may be leftovers from previous tests unique_ptr ids{ MegaIntegerList::createInstance() }; RequestTracker clearNotifsTracker(megaApi[0].get()); megaApi[0]->enableTestNotifications(ids.get(), &clearNotifsTracker); // clear "^!tnotif" ASSERT_EQ(clearNotifsTracker.waitForResult(), API_OK); // Clear last-read-notification RequestTracker clearLastReadNotifTracker(megaApi[0].get()); megaApi[0]->setLastReadNotification(0, &clearLastReadNotifTracker); // clear "^!lnotif" ASSERT_EQ(clearLastReadNotifTracker.waitForResult(), API_OK); // Clear last-actioned-banner RequestTracker clearLastActionedBannerTracker(megaApi[0].get()); megaApi[0]->setLastActionedBanner(0, &clearLastActionedBannerTracker); // clear "^!lbannr" ASSERT_EQ(clearLastActionedBannerTracker.waitForResult(), API_OK); // Get last-read-notification (not previously set) RequestTracker getLastReadNotifTracker(megaApi[0].get()); megaApi[0]->getLastReadNotification(&getLastReadNotifTracker); // get "^!lnotif" ASSERT_EQ(getLastReadNotifTracker.waitForResult(), API_OK); ASSERT_EQ(static_cast(getLastReadNotifTracker.request->getNumber()), 0u); // Fetch user data ("ug" command), and cache IDs of enabled-notifications (ug.notifs). RequestTracker userDataTracker(megaApi[0].get()); megaApi[0]->getUserData(&userDataTracker); ASSERT_EQ(userDataTracker.waitForResult(), API_OK); // Get IDs of enabled-notifications, which are composed of // - IDs of test-notifications: IDs of notifications that should already exist, enabled as // requested by the user; // - IDs of always-enabled notifications: IDs of notifications that already exist and are // always enabled regardless of not being requested by the user. unique_ptr defaultNotifs{ megaApi[0]->getEnabledNotifications() }; // get IDs cached fom ug.notifs ASSERT_THAT(defaultNotifs, ::testing::NotNull()); ASSERT_GE(defaultNotifs->size(), 0); // Get the complete notifications RequestTracker gnotifTracker(megaApi[0].get()); megaApi[0]->getNotifications(&gnotifTracker); // send "gnotif" and process its response ASSERT_THAT(gnotifTracker.waitForResult(), ::testing::AnyOf(::testing::Eq(API_OK), ::testing::Eq(API_ENOENT))); const auto* notificationList = gnotifTracker.request->getMegaNotifications(); ASSERT_THAT(notificationList, ::testing::NotNull()); ASSERT_EQ(notificationList->size(), 0u); // Enable some test-notifications. // IDs 1,2,3,4,5 have been reserved to be "^!tnotif" only notifications. // Notifications with IDs 1~4 existed at the time of writing this test // Notification with ID 2 has icon // Notification with ID 9 has render modes ('m') ids->add(1); // add notification with ID 1 ids->add(2); // add notification with ID 2 ids->add(9); // add notification with ID 9 ids->add(numeric_limits::max() - 1); // dummy RequestTracker notifsTracker(megaApi[0].get()); megaApi[0]->enableTestNotifications(ids.get(), ¬ifsTracker); // set "^!tnotif" ASSERT_EQ(notifsTracker.waitForResult(), API_OK); // Fetch user data ("ug" command), and cache IDs of enabled-notifications (ug.notifs). RequestTracker userDataTracker2(megaApi[0].get()); megaApi[0]->getUserData(&userDataTracker2); // get ug.notifs again ASSERT_EQ(userDataTracker2.waitForResult(), API_OK); // Get IDs of enabled-notifications unique_ptr enabledNotifs{ megaApi[0]->getEnabledNotifications() }; ASSERT_THAT(enabledNotifs, ::testing::NotNull()); ASSERT_EQ( enabledNotifs->size(), 3); // only IDs of existing notifications will be there, dummy IDs will not be included ASSERT_EQ(enabledNotifs->get(0), 1); ASSERT_EQ(enabledNotifs->get(1), 2); ASSERT_EQ(enabledNotifs->get(2), 9); // Get the complete notifications (corresponding only to existing IDs) RequestTracker gnotifTracker2(megaApi[0].get()); megaApi[0]->getNotifications(&gnotifTracker2); // send "gnotif" and process its response ASSERT_EQ(gnotifTracker2.waitForResult(), API_OK); const auto* notificationList2 = gnotifTracker2.request->getMegaNotifications(); ASSERT_THAT(notificationList2, ::testing::NotNull()); ASSERT_EQ(notificationList2->size(), 3u); // validate complete notifications ASSERT_NO_FATAL_FAILURE(validateNotification(notificationList2->get(0), 1, HasIcon::NO)); ASSERT_NO_FATAL_FAILURE(validateNotification(notificationList2->get(1), 2, HasIcon::YES)); ASSERT_NO_FATAL_FAILURE(validateNotification(notificationList2->get(2), 9, HasIcon::NO)); unique_ptr renderModes{notificationList2->get(2)->getRenderModes()}; ASSERT_THAT(renderModes, ::testing::NotNull()); ASSERT_GT(renderModes->size(), 0); for (int i = 0; i < renderModes->size(); ++i) { unique_ptr fields{ notificationList2->get(2)->getRenderModeFields(renderModes->get(i))}; ASSERT_THAT(fields, ::testing::NotNull()); if (string{"btp"} == renderModes->get(i)) { ASSERT_EQ(fields->size(), 2); ASSERT_THAT(fields->get("href"), ::testing::NotNull()); ASSERT_THAT(fields->get("img"), ::testing::NotNull()); } else if (string{"brp"} == renderModes->get(i)) { ASSERT_EQ(fields->size(), 2); ASSERT_THAT(fields->get("href"), ::testing::NotNull()); ASSERT_THAT(fields->get("img"), ::testing::NotNull()); } else if (string{"bti"} == renderModes->get(i)) { ASSERT_EQ(fields->size(), 1); ASSERT_THAT(fields->get("src"), ::testing::NotNull()); } else if (string{"bri"} == renderModes->get(i)) { ASSERT_EQ(fields->size(), 1); ASSERT_THAT(fields->get("src"), ::testing::NotNull()); } } // Set last-read-notification const uint32_t lastReadNotifId = numeric_limits::max() - 2; // dummy value RequestTracker setLastReadNotifTracker(megaApi[0].get()); megaApi[0]->setLastReadNotification(lastReadNotifId, &setLastReadNotifTracker); // set "^!lnotif" ASSERT_EQ(setLastReadNotifTracker.waitForResult(), API_OK); // Get last-read-notification RequestTracker getLastReadNotifTracker2(megaApi[0].get()); megaApi[0]->getLastReadNotification(&getLastReadNotifTracker2); // get "^!lnotif" ASSERT_EQ(getLastReadNotifTracker2.waitForResult(), API_OK); ASSERT_EQ(static_cast(getLastReadNotifTracker2.request->getNumber()), lastReadNotifId); // Clear a previusly set last-read-notification RequestTracker clearLastReadNotifTracker2(megaApi[0].get()); megaApi[0]->setLastReadNotification(0, &clearLastReadNotifTracker2); // clear "^!lnotif" ASSERT_EQ(clearLastReadNotifTracker2.waitForResult(), API_OK); // Set last-actioned-banner const uint32_t lastActionedBannerId = numeric_limits::max() - 3; // dummy value RequestTracker setLastActionedBannerTracker(megaApi[0].get()); megaApi[0]->setLastActionedBanner(lastActionedBannerId, &setLastActionedBannerTracker); // set "^!lbannr" ASSERT_EQ(setLastActionedBannerTracker.waitForResult(), API_OK); // Get last-actioned-banner RequestTracker getLastActionedBannerTracker2(megaApi[0].get()); megaApi[0]->getLastActionedBanner(&getLastActionedBannerTracker2); // get "^!lbannr" ASSERT_EQ(getLastActionedBannerTracker2.waitForResult(), API_OK); ASSERT_EQ(static_cast(getLastActionedBannerTracker2.request->getNumber()), lastActionedBannerId); // Clear a previously set last-actioned-banner RequestTracker clearLastActionedBannerTracker2(megaApi[0].get()); megaApi[0]->setLastActionedBanner(0, &clearLastActionedBannerTracker2); // clear "^!lbannr" ASSERT_EQ(clearLastActionedBannerTracker2.waitForResult(), API_OK); // Clear test-notifications ids.reset(MegaIntegerList::createInstance()); RequestTracker clearNotifsTracker2(megaApi[0].get()); megaApi[0]->enableTestNotifications(ids.get(), &clearNotifsTracker2); // clear "^!tnotif" ASSERT_EQ(clearNotifsTracker2.waitForResult(), API_OK); // Fetch user data ("ug" command), and cache IDs of enabled-notifications (ug.notifs). RequestTracker userDataTracker3(megaApi[0].get()); megaApi[0]->getUserData(&userDataTracker3); ASSERT_EQ(userDataTracker3.waitForResult(), API_OK); // Get IDs of enabled-notifications defaultNotifs.reset(megaApi[0]->getEnabledNotifications()); // get cached value of ug.notifs ASSERT_THAT(defaultNotifs, ::testing::NotNull()); ASSERT_EQ(defaultNotifs->size(), 0); } /** * @brief SdkNodeDescription * Steps: * - Create file and upload * - Set description * - Locallogout * - Resume * - Check description * - Update description * - Remove description * - Update the contents of the file to create a new version * - Check the description is retained across versions * */ TEST_F(SdkTest, SdkNodeDescription) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkNodeDescription___"; unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); string filename = "test.txt"; sdk_test::LocalTempFile testTempFile(filename, 0); MegaHandle mh = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &mh, filename.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; auto changeNodeDescription = [this](MegaHandle nodeHandle, const char* description) { bool check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nodeHandle, MegaNode::CHANGE_TYPE_DESCRIPTION, check); RequestTracker trackerSetDescription(megaApi[0].get()); std::unique_ptr testNode(megaApi[0]->getNodeByHandle(nodeHandle)); megaApi[0]->setNodeDescription(testNode.get(), description, &trackerSetDescription); ASSERT_EQ(trackerSetDescription.waitForResult(), API_OK); ASSERT_TRUE(waitForResponse(&check)) << "Node hasn't updated description after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); std::unique_ptr node(megaApi[0]->getNodeByHandle(nodeHandle)); ASSERT_TRUE(node); const char* nodeDescription = node->getDescription(); if (description == nullptr || nodeDescription == nullptr) { ASSERT_EQ(description, nodeDescription); return; } ASSERT_STREQ(description, node->getDescription()); }; // Set description std::string description{"This is a test description to search in its content"}; changeNodeDescription(mh, description.c_str()); std::string descriptionFilter{"search"}; std::unique_ptr filter(MegaSearchFilter::createInstance()); filter->byDescription(descriptionFilter.c_str()); std::unique_ptr nodeList(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; std::unique_ptr filterChildren(MegaSearchFilter::createInstance()); filterChildren->byDescription(descriptionFilter.c_str()); filterChildren->byLocationHandle(rootnodeA->getHandle()); nodeList.reset(megaApi[0]->getChildren(filterChildren.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; std::string descriptionFilterNoFind{"searchin"}; filter->byDescription(descriptionFilterNoFind.c_str()); nodeList.reset(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 0) << *nodeList; filterChildren->byDescription(descriptionFilterNoFind.c_str()); nodeList.reset(megaApi[0]->getChildren(filterChildren.get())); ASSERT_EQ(nodeList->size(), 0) << *nodeList; std::string descriptionWithoutCapitalLetter("this"); filter->byDescription(descriptionWithoutCapitalLetter.c_str()); nodeList.reset(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; std::string descriptionWithAccent("thiŚ"); filter->byDescription(descriptionWithAccent.c_str()); nodeList.reset(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; auto& target = mApi[0]; std::unique_ptr session(dumpSession()); locallogout(0); resumeSession(session.get()); target.resetlastEvent(); fetchnodes(0); // make sure that client is up to date (upon logout, recent changes might not be committed to DB) ASSERT_TRUE(WaitFor( [&target]() { return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; std::unique_ptr node(megaApi[0]->getNodeByHandle(mh)); ASSERT_TRUE(node); ASSERT_EQ(description, node->getDescription()); // Update description changeNodeDescription(mh, "Description modified"); changeNodeDescription(mh, "Description with line break\n Other line\n and other more"); // Remove description changeNodeDescription(mh, nullptr); const std::string lastDescription{ "This is a description with *stars* to test if it's found correctly"}; changeNodeDescription(mh, lastDescription.c_str()); MegaHandle nodeCopiedHandle = UNDEF; ASSERT_EQ(API_OK, doCopyNode(0, &nodeCopiedHandle, node.get(), rootnodeA.get(), "test2.txt")) << "Cannot create a copy of a node"; changeNodeDescription(nodeCopiedHandle, "This is a description without stars to test if it's found correctly"); filter->byDescription("stars"); nodeList.reset(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 2) << *nodeList; filter->byDescription("*star"); nodeList.reset(megaApi[0]->search(filter.get())); ASSERT_EQ(nodeList->size(), 1) << *nodeList; LOG_debug << "[SdkTest::SdkNodeDescription] Changing the contents of test.txt to force a new version"; const std::string newVersionFileName = "test_new_version.txt"; sdk_test::LocalTempFile fNewVersion(newVersionFileName, 1); MegaHandle mhNew = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &mhNew, newVersionFileName.c_str(), rootnodeA.get(), filename.c_str(), ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot update the test file"; ASSERT_NE(mhNew, INVALID_HANDLE); std::unique_ptr oldNode(megaApi[0]->getNodeByHandle(mh)); ASSERT_NE(oldNode, nullptr); std::unique_ptr newNode(megaApi[0]->getNodeByHandle(mhNew)); ASSERT_NE(newNode, nullptr); // Check there are two versions for the file unique_ptr allVersions(megaApi[0]->getVersions(newNode.get())); ASSERT_EQ(allVersions->size(), 2); EXPECT_STREQ(oldNode->getDescription(), newNode->getDescription()) << "Description is not maintained after file update"; EXPECT_STREQ(oldNode->getDescription(), lastDescription.c_str()) << "The description of the old version has changed"; } /** * @brief Test returned value by MegaApi::getNumNodesAtCacheLRU * Steps: * - Check intial number of nodes at LRU cache * - Set cache LRU limit 500 * - Add a new file and create 100 copies * - Check number of nodes at cache LRU * - Reduce size at cache LRU * - Check number of nodes at cache LRU * - Increase cache LRU size (60) * - Check number of nodes at cache LRU * - Copy same node 20 times more * - Check number of nodes at cache LRU */ TEST_F(SdkTest, SdkCacheLRU) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkCacheLRU___"; unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); uint64_t initialNumberNodes = megaApi[0]->getNumNodesAtCacheLRU(); string filename = "test.txt"; createFile(filename, false); MegaHandle mh = 0; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &mh, filename.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; std::unique_ptr node(megaApi[0]->getNodeByHandle(mh)); megaApi[0]->setLRUCacheSize(500); unsigned numberOfCopies = 100; for (unsigned i = 0; i < numberOfCopies; ++i) { MegaHandle newNodeHandle; std::string newName{filename + std::to_string(i)}; ASSERT_EQ(API_OK, doCopyNode(0, &newNodeHandle, node.get(), rootnodeA.get(), newName.c_str())); } uint64_t numNodeCacheLRU = megaApi[0]->getNumNodesAtCacheLRU(); ASSERT_EQ(numNodeCacheLRU, numberOfCopies + 1 + initialNumberNodes); // 101 -> initial node + 100 copies uint64_t cacheLRUSize = 50; megaApi[0]->setLRUCacheSize(cacheLRUSize); numNodeCacheLRU = megaApi[0]->getNumNodesAtCacheLRU(); ASSERT_EQ(numNodeCacheLRU, cacheLRUSize); uint64_t cacheLRUNewSize = 60; megaApi[0]->setLRUCacheSize(cacheLRUNewSize); numNodeCacheLRU = megaApi[0]->getNumNodesAtCacheLRU(); ASSERT_EQ(numNodeCacheLRU, cacheLRUSize); for (unsigned i = 0; i < 20; ++i) { MegaHandle newNodeHandle; std::string newName{filename + std::to_string(numberOfCopies + i)}; ASSERT_EQ(API_OK, doCopyNode(0, &newNodeHandle, node.get(), rootnodeA.get(), newName.c_str())); } numNodeCacheLRU = megaApi[0]->getNumNodesAtCacheLRU(); ASSERT_EQ(numNodeCacheLRU, cacheLRUNewSize); } /** * @brief SdkTestVPN * * Test that MEGA VPN app receives Action Packets. */ TEST_F(SdkTest, SdkTestVPN) { LOG_info << "___TEST SdkTestVPN"; // Login first client ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // get First Name string origName = "testingName"; // default value in case it is not set { RequestTracker getNameTracker(megaApi[0].get()); megaApi[0]->getUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, &getNameTracker); ErrorCodes res = getNameTracker.waitForResult(); if (res != API_ENOENT) { ASSERT_EQ(res, API_OK) << "Failed to get First Name"; ASSERT_THAT(getNameTracker.request->getText(), ::testing::NotNull()); origName = getNameTracker.request->getText(); } } // Prepare VPN client with the same account mApi.resize(2); megaApi.resize(2); const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_NO_FATAL_FAILURE(configureTestInstance(1, email, pass, true, MegaApi::CLIENT_TYPE_VPN)); // Login VPN client { RequestTracker loginTracker(megaApi[1].get()); megaApi[1]->login(mApi[1].email.c_str(), mApi[1].pwd.c_str(), &loginTracker); ASSERT_EQ(loginTracker.waitForResult(), API_OK) << "VPN client: failed to login"; bool& fetchnodesDone = mApi[1].requestFlags[MegaRequest::TYPE_FETCH_NODES] = false; ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); ASSERT_TRUE(WaitFor([&fetchnodesDone]() { return fetchnodesDone; }, 60 * 1000)) << "VPN client: fetchnodesDone not received"; // test resume-session while at it unique_ptr session(dumpSession(1)); ASSERT_NO_FATAL_FAILURE(locallogout(1)); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get(), 1)); ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); } // update First Name in default client string newName = origName + "_upd"; bool& nameUpdated = mApi[1].userFirstNameUpdated = false; { RequestTracker setNameTracker(megaApi[0].get()); megaApi[0]->setUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, newName.c_str(), &setNameTracker); ASSERT_EQ(setNameTracker.waitForResult(), API_OK) << "Default client: failed to update First Name"; // wait for VPN client to receive the name update ASSERT_TRUE(WaitFor([&nameUpdated]() { return nameUpdated; }, 60 * 1000)) << "VPN client: AP about updated First Name not received"; // get First Name from VPN client and confirm the update nameUpdated = false; // to be ignored this time; is set after getting UA RequestTracker getNameTracker(megaApi[1].get()); megaApi[1]->getUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, &getNameTracker); ASSERT_EQ(getNameTracker.waitForResult(), API_OK) << "VPN client: failed to get updated First Name"; ASSERT_THAT(getNameTracker.request->getText(), ::testing::NotNull()); ASSERT_EQ(newName, getNameTracker.request->getText()); WaitFor([&nameUpdated]() { return nameUpdated; }, 5 * 1000); // to be ignored } // reset First Name to original value { // truncate remmnants of older test failures while (origName.length() > 4 && !origName.compare(origName.length() - 4, 4, "_upd")) origName.erase(origName.length() - 4); nameUpdated = false; RequestTracker setNameTracker(megaApi[0].get()); megaApi[0]->setUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, origName.c_str(), &setNameTracker); ASSERT_EQ(setNameTracker.waitForResult(), API_OK) << "Default client: failed to set original First Name"; // wait for VPN client to receive the name update ASSERT_TRUE(WaitFor([&nameUpdated]() { return nameUpdated; }, 60 * 1000)) << "VPN client: AP for reset First Name not received"; // get First Name from VPN client and confirm the reset RequestTracker getNameTracker(megaApi[1].get()); megaApi[1]->getUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, &getNameTracker); ASSERT_EQ(getNameTracker.waitForResult(), API_OK) << "VPN client: failed to get reset First Name"; ASSERT_THAT(getNameTracker.request->getText(), ::testing::NotNull()); ASSERT_EQ(origName, getNameTracker.request->getText()); } } /** * @brief Test checks deleting user attributes * Steps: * - Set firstname attribute to make sure it exists * - Delete firstname attribute * - Get firstname attribute to check it does not exist anymore * - Try to delete firstname attribute to get ENOENT response */ TEST_F(SdkTest, SdkDeleteUserAttribute) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); string firstname = "testingName"; ASSERT_EQ(API_OK, synchronousSetUserAttribute(0, MegaApi::USER_ATTR_FIRSTNAME, firstname.c_str())); RequestTracker deleteAttributeTracker(megaApi[0].get()); megaApi[0]->deleteUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, &deleteAttributeTracker); ASSERT_EQ(API_OK, deleteAttributeTracker.waitForResult()); ASSERT_EQ(API_ENOENT, synchronousGetUserAttribute(0, MegaApi::USER_ATTR_FIRSTNAME)); RequestTracker secondDeleteAttributeTracker(megaApi[0].get()); megaApi[0]->deleteUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, &secondDeleteAttributeTracker); ASSERT_EQ(API_ENOENT, secondDeleteAttributeTracker.waitForResult()); ASSERT_EQ(API_OK, synchronousSetUserAttribute(0, MegaApi::USER_ATTR_FIRSTNAME, firstname.c_str())); } TEST_F(SdkTest, GetFeaturePlans) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST GetFeaturePlans___"; int err = synchronousGetPricing(0); ASSERT_EQ(err, API_OK) << "synchronousGetPricing() failed: " << MegaError::getErrorString(err); for (int i = 0; i < mApi[0].mMegaPricing->getNumProducts(); ++i) { if (mApi[0].mMegaPricing->isFeaturePlan(i)) { ASSERT_NE(mApi[0].mMegaPricing->getHandle(i), INVALID_HANDLE); ASSERT_EQ(mApi[0].mMegaPricing->getProLevel(i), 99999); ASSERT_NE(mApi[0].mMegaPricing->getMonths(i), 0); ASSERT_NE(mApi[0].mMegaPricing->getAmount(i), 0); ASSERT_STRNE(mApi[0].mMegaPricing->getDescription(i), ""); std::unique_ptr features{ mApi[0].mMegaPricing->getFeatures(i) }; ASSERT_NE(features->size(), 0); ASSERT_STRNE(mApi[0].mMegaPricing->getIosID(i), ""); ASSERT_STRNE(mApi[0].mMegaPricing->getAndroidID(i), ""); ASSERT_NE(mApi[0].mMegaPricing->getAmountMonth(i), 0); } } } /** * @brief GetActivePlansAndFeatures */ TEST_F(SdkTest, GetActivePlansAndFeatures) { LOG_info << "___TEST GetActivePlansAndFeaturess___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); RequestTracker accDetailsTracker(megaApi[0].get()); megaApi[0]->getAccountDetails(&accDetailsTracker); ASSERT_EQ(accDetailsTracker.waitForResult(), API_OK) << "Failed to get account details"; std::unique_ptr accountDetails( accDetailsTracker.request->getMegaAccountDetails()); ASSERT_TRUE(accountDetails) << "Missing account details"; int proLevel = MegaAccountDetails::ACCOUNT_TYPE_FREE; set featuresGranted; for (int i = 0; i < accountDetails->getNumPlans(); ++i) { std::unique_ptr plan(accountDetails->getPlan(i)); std::unique_ptr features(plan->getFeatures()); for (int j = 0; j < features->size(); ++j) { // Acumulate granted features featuresGranted.emplace(features->get(j)); } if (plan->isProPlan()) { ASSERT_EQ(proLevel, MegaAccountDetails::ACCOUNT_TYPE_FREE) << "More than one PRO plan has been received"; proLevel = plan->getAccountLevel(); ASSERT_GT(proLevel, MegaAccountDetails::ACCOUNT_TYPE_FREE) << "PRO level is ACCOUNT_TYPE_FREE"; ASSERT_NE(proLevel, MegaAccountDetails::ACCOUNT_TYPE_FEATURE) << "PRO plan is a feature plan"; ASSERT_EQ(proLevel, accountDetails->getProLevel()) << "PRO level of the plan does not match the PRO account level"; } else // Feature plan { ASSERT_EQ(plan->getAccountLevel(), MegaAccountDetails::ACCOUNT_TYPE_FEATURE) << "Feature plan has not a feature account level"; ASSERT_GT(features->size(), 0) << "Feature plan does not grant any feature"; } } // Compare features contained in the plans with the features received for the account. ASSERT_EQ(featuresGranted.size(), accountDetails->getNumActiveFeatures()) << "Features in active plans don't match the number of features of the account"; m_time_t currTime = m_time(); for (int i = 0; i < accountDetails->getNumActiveFeatures(); ++i) { std::unique_ptr feature(accountDetails->getActiveFeature(i)); ASSERT_GE(feature->getExpiry(), currTime) << "Received an expired feature"; string featureId(std::unique_ptr(feature->getId()).get()); ASSERT_NE(featuresGranted.find(featureId), featuresGranted.end()) << "Feature " << featureId << " is not present in any plan"; } } /** * @brief SdkTestSharesWhenMegaHosted * * - Create a folder * - Create a writable, mega-hosted link to the folder * - Confirm that an encryption-key was used for the share-key (sent via "l"."sk") */ TEST_F(SdkTest, SdkTestSharesWhenMegaHosted) { LOG_info << "___TEST SharesWhenMegaHosted___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Create some nodes to share // |--Shared-folder std::unique_ptr rootNode{megaApi[0]->getRootNode()}; MegaHandle hFolder = createFolder(0, "Shared-folder", rootNode.get()); ASSERT_NE(hFolder, UNDEF); std::unique_ptr nFolder(megaApi[0]->getNodeByHandle(hFolder)); ASSERT_THAT(nFolder, ::testing::NotNull()); RequestTracker rt(megaApi[0].get()); megaApi[0]->exportNode(nFolder.get(), 0, true /*writable*/, true /*megaHosted*/, &rt); ASSERT_EQ(rt.waitForResult(), API_OK); // Test that encryption-key was used for "sk" (share-key) sent via "l" command ASSERT_THAT(rt.request->getPassword(), ::testing::NotNull()); string b64Key{rt.request->getPassword()}; string binKey = Base64::atob(b64Key); ASSERT_FALSE(binKey.empty()); } TEST_F(SdkTest, SdkTestRestoreNodeVersion) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); LOG_info << "___TEST SdkTestRestoreNodeVersion___"; const auto addTagToNode = [&api = megaApi[0]](const std::unique_ptr& node, const std::string& tag) { RequestTracker trackerAddTag(api.get()); api->addNodeTag(node.get(), tag.c_str(), &trackerAddTag); ASSERT_EQ(trackerAddTag.waitForResult(), API_OK); }; const auto setNodeDescription = [&api = megaApi[0]](const std::unique_ptr& node, const std::string& description) { RequestTracker trackerSetDescription(api.get()); api->setNodeDescription(node.get(), description.c_str(), &trackerSetDescription); ASSERT_EQ(trackerSetDescription.waitForResult(), API_OK); }; LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Creating root node"; std::unique_ptr rootnodeA(megaApi[0]->getRootNode()); ASSERT_TRUE(rootnodeA); LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Uploading first version of the file"; const std::string originalContent{"Original content"}; MegaHandle originalHandle = 0; { const std::string filename = "test.txt"; sdk_test::LocalTempFile testTempFile(filename, originalContent); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &originalHandle, filename.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; } LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Setting metadata for first version"; std::unique_ptr originalNodeVersion(megaApi[0]->getNodeByHandle(originalHandle)); ASSERT_TRUE(originalNodeVersion); ASSERT_NO_FATAL_FAILURE(addTagToNode(originalNodeVersion, "originaltag")); ASSERT_NO_FATAL_FAILURE(setNodeDescription(originalNodeVersion, "original description")); LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Uploading second version of the file"; MegaHandle currentHandle = 0; { const std::string filename = "test.txt"; sdk_test::LocalTempFile testTempFile(filename, "Current content"); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, ¤tHandle, filename.data(), rootnodeA.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload a test file"; } LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Setting metadata for second version"; std::unique_ptr currentNodeVersion(megaApi[0]->getNodeByHandle(currentHandle)); ASSERT_TRUE(currentNodeVersion); ASSERT_NO_FATAL_FAILURE(addTagToNode(currentNodeVersion, "currenttag")); ASSERT_NO_FATAL_FAILURE(setNodeDescription(currentNodeVersion, "current description")); LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Restoring first version"; RequestTracker trackerRestoreVersion(megaApi[0].get()); megaApi[0]->restoreVersion(originalNodeVersion.get(), &trackerRestoreVersion); ASSERT_EQ(trackerRestoreVersion.waitForResult(), API_OK); LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Check description remains the same"; std::unique_ptr restoredNode(megaApi[0]->getNodeByPath("/test.txt")); ASSERT_TRUE(restoredNode); EXPECT_STREQ(restoredNode->getDescription(), "current description"); LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Check tags remain the same"; std::unique_ptr tags(restoredNode->getTags()); ASSERT_TRUE(tags); const auto tagsVec = stringListToVector(*tags); EXPECT_THAT(tagsVec, testing::UnorderedElementsAreArray({"originaltag", "currenttag"})); // Check the contents have changed to original LOG_debug << "[SdkTest::SdkTestRestoreNodeVersion] Check the contents has been restored"; const std::string downloadFileName{DOTSLASH "downfile.txt"}; TransferTracker downloadListener(megaApi[0].get()); megaApi[0]->startDownload( restoredNode.get(), downloadFileName.c_str(), nullptr /*customName*/, nullptr /*appData*/, false /*startFirst*/, nullptr /*cancelToken*/, MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/, MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */, false /* undelete */, &downloadListener); ASSERT_EQ(downloadListener.waitForResult(), API_OK); // Read file std::ifstream file{downloadFileName}; ASSERT_TRUE(file); const std::string contents{std::istreambuf_iterator(file), std::istreambuf_iterator()}; // Check contents ASSERT_EQ(contents, originalContent); } /** * @brief TEST_F SdkRemoveTempFilesUponUploadTransfers * * Tests that file uploads transfers started with isSourceTemporary flag finally removes temporary * files in local filesystem * * # Test1 Upload file F1 * # Test2 Upload file F1 again * # Test3 Upload file F2 with same fingerprint than F1 in cloud (Node copy) * # Test4 Upload file F1 (modified) with different fingerprint than F1 in cloud * # Test5 Upload file F3 and cancel transfer */ TEST_F(SdkTest, SdkRemoveTempFilesUponUploadTransfers) { LOG_info << "___TEST SdkRemoveTempFilesUponUploadTransfers___"; constexpr int accIdx{0}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::shared_ptr rootnode(megaApi[accIdx]->getRootNode()); ASSERT_TRUE(!!rootnode) << "Cannot retrieve rootnode"; auto uploadFile = [&](MegaNode* n, const fs::path& filePath, bool cancelTransfer = false) { TransferTracker uploadListener(megaApi[accIdx].get()); MegaUploadOptions tempFileOptions; tempFileOptions.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; tempFileOptions.isSourceTemporary = true; const auto localPath = filePath.string(); megaApi[accIdx]->startUpload(localPath, n, nullptr, &tempFileOptions, &uploadListener); if (cancelTransfer) { ASSERT_EQ(API_OK, synchronousCancelTransfers(accIdx, MegaTransfer::TYPE_UPLOAD)); ASSERT_EQ(API_EINCOMPLETE, uploadListener.waitForResult()); } else { ASSERT_EQ(API_OK, uploadListener.waitForResult()) << "Cannot upload local file: " << filePath; std::unique_ptr n1( megaApi[accIdx]->getNodeByHandle(uploadListener.resultNodeHandle)); ASSERT_TRUE(!!n1) << "Cannot get node in cloud drive file: " << toHandle(uploadListener.resultNodeHandle); } ASSERT_TRUE(uploadListener.mTempFileRemoved) << "Temporary file couldn't be removed: " << filePath; ASSERT_TRUE(!fs::exists(filePath)) << "File still exists locally: " << filePath; }; auto modifyFile = [](const fs::path& filePath, const std::string& text) { std::ofstream f(filePath, std::ios::app); f << text; f.close(); }; LOG_debug << "### Test1 (SdkRemoveTempFilesUponUploadTransfers) Upload file F1 ####"; const fs::path f1Path = fs::current_path() / u8path_compat("file1.txt"); ASSERT_TRUE(createFile(path_u8string(f1Path))) << "Couldn't create " << f1Path; ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f1Path)); LOG_debug << "### Test2 (SdkRemoveTempFilesUponUploadTransfers) Upload file F1 again ####"; ASSERT_TRUE(createFile(path_u8string(f1Path))) << "Couldn't create " << f1Path; const fs::path f2Path = fs::current_path() / u8path_compat("file2.txt"); sdk_test::copyFileFromTestData(f1Path, f2Path); ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f1Path)); LOG_debug << "### Test3 (SdkRemoveTempFilesUponUploadTransfers) Upload file F2 with same " "fingerprint than F1 (Node copy) ####"; ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f2Path)); LOG_debug << "### Test4 (SdkRemoveTempFilesUponUploadTransfers) Upload file F1 (modified) with " "different fingerprint than F1 in cloud ####"; ASSERT_TRUE(createFile(path_u8string(f1Path))) << "Couldn't create " << f1Path; ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f1Path)); modifyFile(f1Path, "Update"); ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f1Path)); LOG_debug << "### Test5 (SdkRemoveTempFilesUponUploadTransfers) Upload file F3 and cancel " "transfer ####"; const fs::path f3Path = fs::current_path() / u8path_compat("file3.txt"); ASSERT_TRUE(createFile(path_u8string(f3Path), true)) << "Couldn't create " << f3Path; ASSERT_NO_FATAL_FAILURE(uploadFile(rootnode.get(), f3Path, true)); } #ifdef ENABLE_SYNC /** * @brief SdkTestRemoveVersionsFromSync * * - Create a sync with a file * - Update the file content (sync engine add a new version) * - Remove all versions from the account * - Check file sync state is STATE_SYNCED */ TEST_F(SdkTest, SdkTestRemoveVersionsFromSync) { LOG_info << "___TEST SdkTestRemoveVersionsFromSync"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::string syncFolder{"sync1"}; fs::path syncFolderPath = syncFolder; const auto localSyncFolderPath = fs::current_path() / syncFolderPath; // Create local directories and a files. fs::create_directories(localSyncFolderPath); LOG_verbose << "SdkTestRemoveVersionsFromSync : Creating the remote folders to be synced to."; std::unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_NE(rootNode.get(), nullptr); auto nh = createFolder(0, syncFolder.c_str(), rootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote folders"; std::unique_ptr remoteBaseNode(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); LOG_verbose << "SdkTestRemoveVersionsFromSync : Add syncs"; auto lp = path_u8string(localSyncFolderPath); ASSERT_EQ(API_OK, synchronousSyncFolder(0, nullptr, MegaSync::TYPE_TWOWAY, lp.c_str(), nullptr, remoteBaseNode->getHandle(), nullptr)) << "API Error adding a new sync"; ASSERT_EQ(MegaSync::NO_SYNC_ERROR, mApi[0].lastSyncError); std::unique_ptr sync = waitForSyncState(megaApi[0].get(), remoteBaseNode.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync && sync->getRunState() == MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(MegaSync::NO_SYNC_ERROR, sync->getError()); handle backupId{sync->getBackupId()}; LOG_verbose << "SdkTestRemoveVersionsFromSync : Create a file"; bool check{false}; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); std::string name{"fileTest"}; std::string fileName{path_u8string(localSyncFolderPath / name)}; ASSERT_TRUE(createFile(fileName, false)); ASSERT_TRUE(waitForResponse(&check)) << "Node update not received on client 0 after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); auto checkSyncState = [this](const std::string& fileName) { static unsigned int waitSyncedState = 40; waitForEvent( [this, fileName]() { std::string path{fileName}; #ifdef _WIN32 auto utf8ToUtf16 = [](const char* utf8data, std::string* utf16string) { // Check if input is valid if (utf8data == nullptr) { utf16string->clear(); return; } // Get the length required for the UTF-16 buffer int utf16Length = MultiByteToWideChar(CP_UTF8, 0, utf8data, -1, nullptr, 0); // If the length is 0, an error occurred if (utf16Length == 0) { std::cerr << "Error converting UTF-8 to UTF-16." << std::endl; utf16string->clear(); return; } // Create a buffer to hold the UTF-16 characters std::wstring utf16buffer(utf16Length, 0); // Perform the conversion from UTF-8 to UTF-16 MultiByteToWideChar(CP_UTF8, 0, utf8data, -1, &utf16buffer[0], utf16Length); // Convert the UTF-16 wide string to a std::string by copying the raw bytes utf16string->assign(reinterpret_cast(utf16buffer.data()), utf16buffer.size() * sizeof(wchar_t)); }; std::string utf8String{std::move(path)}; path.clear(); utf8ToUtf16(utf8String.c_str(), &path); #endif return MegaApi::STATE_SYNCED == megaApi[0]->syncPathState(&path); }, waitSyncedState); }; LOG_verbose << "SdkTestRemoveVersionsFromSync : wait file is syncronized"; checkSyncState(fileName); check = false; mApi[0].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, check); // modify file { ofstream f{fileName}; f << "update "; f.close(); } ASSERT_TRUE(waitForResponse(&check)) << "Node update not received on client 0 after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); LOG_verbose << "SdkTestRemoveVersionsFromSync : Wait file is syncronized after modification"; checkSyncState(fileName); std::unique_ptr children{megaApi[0]->getChildren(remoteBaseNode.get())}; ASSERT_EQ(children->size(), 1); MegaNode* node{children->get(0)}; ASSERT_TRUE(node); // Check number of versions ASSERT_EQ(megaApi[0]->getNumVersions(node), 2); LOG_verbose << "SdkTestRemoveVersionsFromSync : Remove all versions"; unique_ptr rt = std::make_unique(megaApi[0].get()); megaApi[0]->removeVersions(rt.get()); ASSERT_EQ(rt->waitForResult(), API_OK); ASSERT_EQ(megaApi[0]->getNumVersions(node), 1); // Check if file is at synced state. None state change should be generated checkSyncState(fileName); LOG_verbose << "SdkTestRemoveVersionsFromSync : Remove syncs"; rt = std::make_unique(megaApi[0].get()); megaApi[0]->removeSync(backupId, rt.get()); ASSERT_EQ(rt->waitForResult(), API_OK); ASSERT_NO_FATAL_FAILURE(cleanUp(this->megaApi[0].get(), syncFolderPath)); } #endif /** * @brief SdkTest.CreditCardCancelSubscriptions * * - Limited test for canceling a subscription using multiple reasons * - Create helper instances * - Call relevant MegaApi interaface which should fail in a predefined manner */ TEST_F(SdkTest, CreditCardCancelSubscriptions) { LOG_info << "___TEST SdkTest.CreditCardCancelSubscriptions___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); unique_ptr reason1{ MegaCancelSubscriptionReason::create("Some reason", "22.n")}; unique_ptr reason2{ MegaCancelSubscriptionReason::create("Other reason", "99.MCMLXIV")}; unique_ptr reasons{ MegaCancelSubscriptionReasonList::create()}; reasons->add(reason1.get()); reasons->add(reason2.get()); // Cancel dummy subscription { RequestTracker listener{megaApi[0].get()}; megaApi[0]->creditCardCancelSubscriptions( reasons.get(), "Dummy subscription ID", MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO, &listener); ASSERT_EQ(listener.waitForResult(), API_EARGS); } // Cancel all subscriptions (no-op for free account) { RequestTracker listener{megaApi[0].get()}; megaApi[0]->creditCardCancelSubscriptions( reasons.get(), "", MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO, &listener); ASSERT_EQ(listener.waitForResult(), API_OK); } // Cancel all subscriptions using null reason list (no-op for free account) { RequestTracker listener{megaApi[0].get()}; megaApi[0]->creditCardCancelSubscriptions( static_cast( nullptr), // passing 'nullptr' is ambiguous nullptr, // id MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO, &listener); ASSERT_EQ(listener.waitForResult(), API_OK); } // Cancel all subscriptions using null char pointer (no-op for free account) { RequestTracker listener{megaApi[0].get()}; megaApi[0]->creditCardCancelSubscriptions( static_cast(nullptr), // passing 'nullptr' is ambiguous nullptr, // id MegaApi::CREDIT_CARD_CANCEL_SUBSCRIPTIONS_CAN_CONTACT_NO, &listener); ASSERT_EQ(listener.waitForResult(), API_OK); } } TEST_F(SdkTest, SdkTestSetAccountLevel) { // Make sure we can transition between account levels. auto check = [](MegaApi& api, int months, int plan) { // Try and set the account level. auto result = setAccountLevel(api, plan, months, nullptr); EXPECT_EQ(result, API_OK) << "Couldn't set account level: " << result; // Make sure the account level actually changed. if (result == API_OK) { // Try and get the client's account level. auto level = getAccountLevel(api); // Couldn't get account level. if (result = ::result(level); result != API_OK) { EXPECT_EQ(result, API_OK) << "Couldn't retrieve account level: " << result; return result; } // Make sure the account level actually changed. EXPECT_EQ(value(level).months, months); EXPECT_EQ(value(level).plan, plan); } return result; }; // check // Get an account for us to play with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Convenience. constexpr auto FREE = MegaAccountDetails::ACCOUNT_TYPE_FREE; constexpr auto PRO = MegaAccountDetails::ACCOUNT_TYPE_PROI; // Convenience. auto& api = *megaApi[0]; // Make sure any modifications we make are reversed. auto restorer = accountLevelRestorer(api); // Make sure we can change to a free plan. EXPECT_EQ(check(api, 0, FREE), API_OK); // Make sure we can change to a pro plan. EXPECT_EQ(check(api, 1, PRO), API_OK); } TEST_F(SdkTest, FailsWhenThumbnailIsTooLarge) { // Convenience. using NodePtr = std::unique_ptr; using ::mega::MegaApi; using sdk_test::LocalTempFile; // Clarity. constexpr auto KiB = 1024u; constexpr auto MiB = 1024u * KiB; // Make sure an account is ready for us to use. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Convenience. auto& client = *megaApi[0]; // Get our hands on the user's root node. NodePtr root(client.getRootNode()); // Make sure we could retrieve the root node. ASSERT_TRUE(root.get()) << "Couldn't retrieve a reference to the user's root node"; // Create a file for us to upload to the cloud. LocalTempFile content(u8path_compat("content"), 16 * MiB); // Upload the file to the cloud so we have a node to play with. TransferTracker tracker(&client); MegaUploadOptions uploadOptions; uploadOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; client.startUpload(std::string{"content"}, root.get(), nullptr, &uploadOptions, &tracker); // Wait for the upload to complete. ASSERT_EQ(tracker.waitForResult(), API_OK) << "Couldn't upload file to cloud"; // Get our hands on our newly created node. NodePtr node(client.getNodeByHandle(tracker.resultNodeHandle)); // Make sure our node exists in the cloud. ASSERT_TRUE(node.get()) << "Couldn't retrieve a reference to our newly uploaded file"; // Try and add our file's content as a thumbnail. // // This should fail as thumbnails must be < 16MiB. ASSERT_EQ(setThumbnail(client, node.get(), "content"), API_EARGS); } Error SdkTest::acceptInvitation(MegaApi& client, const MegaContactRequest& invitation) { // So we can wait for the invitation to be accepted. RequestTracker tracker(&client); // Tell the client to accept the invitation. client.replyContactRequest(&invitation, MegaContactRequest::REPLY_ACTION_ACCEPT, &tracker); // Couldn't accept the invitation. if (auto result = tracker.waitForResult(); result != API_OK) { return result; } // Convenience. const std::string sender = invitation.getSourceEmail(); // Wait for the new contact to be added. auto added = WaitFor( [&]() { return hasContact(client, sender) != nullptr; }, defaultTimeoutMs); // Let the caller know whether the invitation was accepted. return added ? API_OK : LOCAL_ETIMEOUT; } Error SdkTest::befriend(MegaApi& client0, MegaApi& client1) { // Users are already friends. if (hasContact(client0, client1.getMyEmail())) return API_OK; // Send user1 an invitation. auto [invitation, invitationSent] = sendInvitationTo(client0, client1); // Couldn't send the invitation. if (invitationSent != API_OK) return invitationSent; // Accept user0's invitation. return acceptInvitation(client1, *invitation); } auto SdkTest::hasContact(MegaApi& client, const std::string& email) -> std::unique_ptr { // Convenience. constexpr auto VISIBLE = MegaUser::VISIBILITY_VISIBLE; // Check if email is a contact. auto contact = makeUniqueFrom(client.getContact(email.c_str())); // email's an active contact. if (contact && contact->getVisibility() == VISIBLE) { return contact; } // email's not an active contact. return nullptr; } auto SdkTest::hasReceivedInvitationFrom(MegaApi& client, const std::string& email) -> std::unique_ptr { // True if an invitation is an incoming invitation from email. auto sentFrom = [&email](const auto& invitation) { return !invitation.isOutgoing() && Utils::icasecmp(invitation.getSourceEmail(), email.c_str()) == 0; }; // sentFrom // Try and find an incoming invitation from email. return findInvitation(client, &MegaApi::getIncomingContactRequests, std::move(sentFrom)); } auto SdkTest::hasSentInvitationTo(MegaApi& client, const std::string& email) -> std::unique_ptr { // True if an invitation is an incoming invitation from email. auto sentTo = [&email](const auto& invitation) { return invitation.isOutgoing() && Utils::icasecmp(invitation.getTargetEmail(), email.c_str()) == 0; }; // sentFrom // Try and find an outgoing invitation to email. return findInvitation(client, &MegaApi::getOutgoingContactRequests, std::move(sentTo)); } Error SdkTest::removeContact(MegaApi& client, const std::string& email) { // Do we even know this contact? auto contact = hasContact(client, email); // Don't know the contact. if (!contact || contact->getVisibility() == MegaUser::VISIBILITY_HIDDEN) { return API_ENOENT; } RequestTracker tracker(&client); // Try and remove the contact. client.removeContact(contact.get(), &tracker); // Let the caller know if the contact was removed. return tracker.waitForResult(); } Error SdkTest::removeContact(MegaApi& client0, MegaApi& client1) { // Try and break the contact relationship. auto result = removeContact(client0, client1.getMyEmail()); // Couldn't break the contact relationship. if (result != API_OK) { return result; } // Wait for the contacts to be purged. auto purged = WaitFor( [&]() { return !hasContact(client0, client1.getMyEmail()) && !hasContact(client1, client0.getMyEmail()); }, defaultTimeoutMs); // Let the caller know if the relationship was broken. return purged ? API_OK : LOCAL_ETIMEOUT; } Error SdkTest::sendInvitationTo(MegaApi& client, const std::string& email) { // So we can wait for our request to complete. RequestTracker tracker(&client); // Ask the client to send the user an invitation. client.inviteContact(email.c_str(), "", MegaContactRequest::INVITE_ACTION_ADD, &tracker); // Let caller know whether the invitation was sent. return tracker.waitForResult(); } auto SdkTest::sendInvitationTo(MegaApi& client0, MegaApi& client1) -> SendInvitationToResult { // Convenience. const std::string email0 = client0.getMyEmail(); const std::string email1 = client1.getMyEmail(); // Couldn't send an invitation to client1. if (auto result = sendInvitationTo(client0, email1); result != API_OK) { return std::make_pair(nullptr, result); } std::unique_ptr invitation; // Wait for both clients to recieve the invitation. WaitFor( [&]() { return hasSentInvitationTo(client0, email1) && (invitation = hasReceivedInvitationFrom(client1, email0)); }, defaultTimeoutMs); // Invitation was never received. if (!invitation) { return std::make_pair(nullptr, LOCAL_ETIMEOUT); } // Invitation was received. return std::make_pair(std::move(invitation), API_OK); } /** * @brief TEST_F TestPublicFolderLinkLogin * * Test setup: * - Create a folder (Sharee user) * - Create a public link for that folder (Sharee user) * * Test steps: * - Login using the folder link (Guest user) * - Expect fetch to be done with MODE_API * - Do local logout (Guest user) * - Login again using the folder link and tryToResumeFolderLinkFromCache as true(Guest user) * - Expect fetch to be done with MODE_DB * - Do local logout (Guest user) * - Login again using the folder link and tryToResumeFolderLinkFromCache as false (Guest user) * - Expect fetch to be done with MODE_API * */ TEST_F(SdkTestShares, TestPublicFolderLinkLogin) { // Test setup ASSERT_NO_FATAL_FAILURE(createNodeTrees()); const MegaHandle hfolder = getHandle("/sharedfolder"); ASSERT_EQ(API_OK, synchronousGetSpecificAccountDetails(mSharerIndex, true, true, true)) << "Cannot get account details"; std::string nodeLink; ASSERT_NO_FATAL_FAILURE(createOnePublicLink(hfolder, nodeLink)); // Test steps bool tryToResumeFolderLinkFromCache = false; auto loginFolderTracker = asyncRequestLoginToFolder(mGuestIndex, nodeLink.c_str(), nullptr, tryToResumeFolderLinkFromCache); ASSERT_EQ(loginFolderTracker->waitForResult(), API_OK) << "Failed to login to folder " << nodeLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(mGuestIndex)); EXPECT_EQ(megaApi[mGuestIndex]->getClient()->fnstats.mode, FetchNodesStats::MODE_API); ASSERT_NO_FATAL_FAILURE(locallogout(mGuestIndex)); tryToResumeFolderLinkFromCache = true; loginFolderTracker = asyncRequestLoginToFolder(mGuestIndex, nodeLink.c_str(), nullptr, tryToResumeFolderLinkFromCache); ASSERT_EQ(loginFolderTracker->waitForResult(), API_OK) << "Failed to login to folder " << nodeLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(mGuestIndex)); EXPECT_EQ(megaApi[mGuestIndex]->getClient()->fnstats.mode, FetchNodesStats::MODE_DB); ASSERT_NO_FATAL_FAILURE(locallogout(mGuestIndex)); tryToResumeFolderLinkFromCache = false; loginFolderTracker = asyncRequestLoginToFolder(mGuestIndex, nodeLink.c_str(), nullptr, tryToResumeFolderLinkFromCache); ASSERT_EQ(loginFolderTracker->waitForResult(), API_OK) << "Failed to login to folder " << nodeLink; ASSERT_NO_FATAL_FAILURE(fetchnodes(mGuestIndex)); EXPECT_EQ(megaApi[mGuestIndex]->getClient()->fnstats.mode, FetchNodesStats::MODE_API); // Cleanup ASSERT_NO_FATAL_FAILURE(logout(mGuestIndex, false, 20)); } TEST_F(SdkTest, ExportNodeWithExpiryDate) { // Get an account for us to play with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Convenience. auto& client = *megaApi[0]; // Make sure any plan changed are reversed. auto restorer = accountLevelRestorer(client); // Make sure the backend thinks we have a free account. EXPECT_EQ(demoteToFree(client), API_OK); // Get our hands on this account's root node. auto root = makeUniqueFrom(client.getRootNode()); ASSERT_NE(root, nullptr); // Create a directory for us to try and export. auto node = createDirectory(client, *root, "d"); ASSERT_EQ(result(node), API_OK); // Get tomorrow's time stamp. auto tomorrow = ([]() { auto now = std::chrono::system_clock::now(); auto tomorrow = now + std::chrono::hours(24); return std::chrono::system_clock::to_time_t(tomorrow); })(); // Trying to export a node with an expiry date should fail. EXPECT_EQ(result(exportNode(client, *value(node), tomorrow)), API_EACCESS); // Unless, of course, the account is a *pro account* :) ASSERT_EQ(setAccountLevel(client, MegaAccountDetails::ACCOUNT_TYPE_PROI, 1, nullptr), API_OK); // Exporting a node with an expiry date should now succeed. auto link = exportNode(client, *value(node), tomorrow); ASSERT_EQ(result(link), API_OK); } void SdkTest::testHashcash(const bool logoutDuringLoging = false) { const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty() || pass.empty()); megaApi.resize(1); mApi.resize(1); configureTestInstance(0, email, pass, true, MegaApi::CLIENT_TYPE_DEFAULT); std::string ua = "HashcashDemo"; megaApi[0]->getClient()->httpio->setuseragent(&ua); megaApi[0]->changeApiUrl("https://staging.api.mega.co.nz/"); #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED std::mutex m; std::condition_variable cv; bool hashcashCalculationStarted = false; const MrProper cleanUp( []() { globalMegaTestHooks.onHashcashCalculationStarted = nullptr; ASSERT_TRUE(DebugTestHook::resetForTests()); }); if (logoutDuringLoging) { ASSERT_TRUE(DebugTestHook::resetForTests()); globalMegaTestHooks.onHashcashCalculationStarted = [&]() { std::lock_guard lock(m); hashcashCalculationStarted = true; cv.notify_one(); }; } #endif std::unique_ptr tracker; if (!gResumeSessions || gSessionIDs[0].empty() || gSessionIDs[0] == "invalid") { out() << "Starting new session of account #0: " << mApi[0].email; tracker = asyncRequestLogin(0, mApi[0].email.c_str(), mApi[0].pwd.c_str()); } else { out() << "Resuming session of account #0"; tracker = asyncRequestFastLogin(0, gSessionIDs[0].c_str()); } if (logoutDuringLoging) { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED // Wait until we observe the hashcash challenge (HTTP 402) during login { std::unique_lock lock(m); ASSERT_TRUE(cv.wait_for(lock, std::chrono::seconds(10), [&] { return hashcashCalculationStarted; })) << "Did not observe hashcash calculation start during login"; } #else std::this_thread::sleep_for(std::chrono::seconds(2)); #endif const auto startTime = std::chrono::steady_clock::now(); ASSERT_NO_FATAL_FAILURE(locallogout()); LOG_debug << "[testHashcash] Locallogout during logging completed in " << std::chrono::duration_cast( std::chrono::steady_clock::now() - startTime) .count() << " ms"; } auto loginResult = tracker->waitForResult(); ErrorCodes loginErrorExpected = logoutDuringLoging ? API_EACCESS : API_OK; ASSERT_EQ(loginErrorExpected, loginResult) << " Login error " << loginResult << " for account " << mApi[0].email; megaApi[0]->getClient()->httpio->setuseragent(&USER_AGENT); // stop hashcash, speed up cleanup } TEST_F(SdkTest, HashCash) { ASSERT_NO_FATAL_FAILURE(SdkTest::testHashcash()); } TEST_F(SdkTest, HashCashAbortDueToLogout) { ASSERT_NO_FATAL_FAILURE(SdkTest::testHashcash(true)); } /** * @brief SdkTestRemovePublicLinkSet * * - Login client 1 and client 2 with same account * - Create a set * - Generate a public link * - Resume session with client 1 and check if Set is recover properly * - Remove public link * - Resume session with client 1 and check if Set is recover properly * - Generate a public link again * - Resume session with client 1 and check if Set is recover properly */ TEST_F(SdkTest, SdkTestRemovePublicLinkSet) { LOG_info << "___TEST SdkTestRemovePublicLinkSet"; static const unsigned long primaryClientIdx{0}; static const unsigned long secondaryClientIdx{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Client 2 is other client from user 1 const auto [email, pass] = getEnvVarAccounts().getVarValues(0); ASSERT_FALSE(email.empty() || pass.empty()); mApi.resize(2); megaApi.resize(2); configureTestInstance(secondaryClientIdx, email, pass); // index 1 = User B auto loginTracker = std::make_unique(megaApi[secondaryClientIdx].get()); megaApi[secondaryClientIdx]->login(email.c_str(), pass.c_str(), loginTracker.get()); ASSERT_EQ(API_OK, loginTracker->waitForResult()) << " Failed to login to account " << email; ASSERT_NO_FATAL_FAILURE(fetchnodes(secondaryClientIdx)); const MrProper cleanUp( [this]() { // release secondary instance to avoid failure at tear down releaseMegaApi(secondaryClientIdx); }); LOG_debug << "# Create set"; const string name = "Set-test"; mApi[secondaryClientIdx].setUpdated = false; RequestTracker trackerCreateSet{megaApi[primaryClientIdx].get()}; megaApi[primaryClientIdx]->createSet(name.c_str(), MegaSet::SET_TYPE_ALBUM, &trackerCreateSet); ASSERT_EQ(trackerCreateSet.waitForResult(), API_OK); const MegaHandle sh = trackerCreateSet.request->getMegaSet()->id(); ASSERT_TRUE(waitForResponse(&mApi[secondaryClientIdx].setUpdated)); std::unique_ptr setSecondAccount{megaApi[secondaryClientIdx]->getSet(sh)}; ASSERT_TRUE(setSecondAccount); LOG_debug << "Set handle: " << Base64Str(sh); auto exportSet = [this, sh]() { std::unique_ptr set{megaApi[primaryClientIdx]->getSet(sh)}; mApi[secondaryClientIdx].setUpdated = false; RequestTracker trackerExportSet{megaApi[primaryClientIdx].get()}; megaApi[primaryClientIdx]->exportSet(set->id(), &trackerExportSet); ASSERT_EQ(trackerExportSet.waitForResult(), API_OK); ASSERT_TRUE(waitForResponse(&mApi[secondaryClientIdx].setUpdated)); MegaSet* exportedSet = trackerExportSet.request->getMegaSet(); ASSERT_TRUE(exportedSet->isExported()); ASSERT_EQ(exportedSet->id(), set->id()); ASSERT_TRUE(exportedSet->getLinkDeletionReason() == MegaSet::DELETION_LINK_NO_REMOVED); std::unique_ptr setSecondAccount{megaApi[secondaryClientIdx]->getSet(sh)}; ASSERT_TRUE(setSecondAccount); ASSERT_TRUE(setSecondAccount->getLinkDeletionReason() == MegaSet::DELETION_LINK_NO_REMOVED); }; auto disableExportSet = [this, sh]() { std::unique_ptr set{megaApi[primaryClientIdx]->getSet(sh)}; mApi[secondaryClientIdx].setUpdated = false; RequestTracker trackerDisableSet{megaApi[primaryClientIdx].get()}; megaApi[primaryClientIdx]->disableExportSet(sh, &trackerDisableSet); ASSERT_EQ(trackerDisableSet.waitForResult(), API_OK); ASSERT_TRUE(waitForResponse(&mApi[secondaryClientIdx].setUpdated)); MegaSet* noExportedSet = megaApi[primaryClientIdx]->getSet(sh); ASSERT_FALSE(noExportedSet->isExported()); ASSERT_EQ(noExportedSet->id(), set->id()); std::unique_ptr setSecondAccount{megaApi[secondaryClientIdx]->getSet(sh)}; ASSERT_TRUE(setSecondAccount); ASSERT_FALSE(setSecondAccount->isExported()); }; auto checkDeletionReasonAfterResumeSession = [this, sh](bool exported) { PerApi& target = mApi[primaryClientIdx]; target.resetlastEvent(); std::unique_ptr session(megaApi[primaryClientIdx]->dumpSession()); ASSERT_NO_FATAL_FAILURE(locallogout()); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get())); ASSERT_NO_FATAL_FAILURE(fetchnodes(primaryClientIdx)); // make sure that client is up to date (upon logout, recent changes might not be committed // to DB) ASSERT_TRUE(WaitFor( [&target]() { return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; std::unique_ptr setPrimaryAccount{megaApi[primaryClientIdx]->getSet(sh)}; ASSERT_TRUE(setPrimaryAccount); ASSERT_EQ(setPrimaryAccount->isExported(), exported); }; LOG_debug << "# Check if Set is exported (false)"; ASSERT_FALSE(megaApi[primaryClientIdx]->isExportedSet(sh)) << "Set should not be public yet"; LOG_debug << "# Enable Set export (creates public link)"; ASSERT_NO_FATAL_FAILURE(exportSet()); LOG_debug << "# Check state after resume session 1"; checkDeletionReasonAfterResumeSession(true); LOG_debug << "# Disable public link"; ASSERT_NO_FATAL_FAILURE(disableExportSet()); LOG_debug << "# Check state after resume session 2"; checkDeletionReasonAfterResumeSession(false); LOG_debug << "# Enable Set export again"; ASSERT_NO_FATAL_FAILURE(exportSet()); LOG_debug << "# Check state after resume session 3"; checkDeletionReasonAfterResumeSession(true); } /** * @test SdkTestGetThumbnailUsingNodeAndHandle * @brief Verifies that thumbnails retrieved via MegaNode and MegaHandle are identical. * * Steps: * - Upload an image to the cloud. * - Retrieve its thumbnail using MegaNode. * - Retrieve the same thumbnail using MegaHandle. * - Compare the two thumbnail files byte by byte. */ TEST_F(SdkTest, SdkTestGetThumbnailUsingNodeAndHandle) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + IMAGEFILE, IMAGEFILE)); // Upload image file std::unique_ptr rootnode(megaApi[0]->getRootNode()); MegaHandle uploadResultHandle = UNDEF; ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &uploadResultHandle, IMAGEFILE.c_str(), rootnode.get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")"; // Get thumbnail using MegaNode std::unique_ptr n1(megaApi[0]->getNodeByHandle(uploadResultHandle)); std::string megaNodeThumbnailPath = THUMBNAIL; ASSERT_EQ(API_OK, doGetThumbnail(0, n1.get(), megaNodeThumbnailPath.c_str())); // Get thumbnail using the MegaHandle std::string megaHandleThumbnailPath = std::string(THUMBNAIL).insert(THUMBNAIL.rfind(".png"), "2"); ASSERT_EQ(API_OK, doGetThumbnail(0, uploadResultHandle, megaHandleThumbnailPath.c_str())); // Check both images are equal std::ifstream file1(megaNodeThumbnailPath, std::ios::binary); std::ifstream file2(megaHandleThumbnailPath, std::ios::binary); ASSERT_TRUE(file1.is_open()) << "Failed to open " << megaNodeThumbnailPath; ASSERT_TRUE(file2.is_open()) << "Failed to open " << megaHandleThumbnailPath; std::vector buffer1((std::istreambuf_iterator(file1)), {}); std::vector buffer2((std::istreambuf_iterator(file2)), {}); ASSERT_EQ(buffer1.size(), buffer2.size()) << "Thumbnail sizes differ"; ASSERT_EQ(buffer1, buffer2) << "Thumbnail contents differ"; } /** * @brief SdkTest.SdkTestUploadNodeAttribute * * Tests if node attributes consistency on file uploading as follows * Uploading same file again - Node attribute should be kept * Uploading updated file content - Node attribute should be kept * Uploading the same file with different name - Node attributes should not be copied from previous * node except fingerprint * */ TEST_F(SdkTest, SdkTestUploadNodeAttribute) { // Get an account for us to play with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // Convenience. auto& client = *megaApi[0]; // Get our hands on this account's root node. auto root = makeUniqueFrom(client.getRootNode()); ASSERT_NE(root, nullptr); // Create a directory for us to try and export. auto dirNode = createDirectory(client, *root, "UploadDirTest"); ASSERT_EQ(result(dirNode), API_OK); MegaHandle fileHandle = 0; const auto fileName = "testFileAttr.txt"; ASSERT_TRUE(createFile(fileName, false)); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName, value(dirNode).get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); auto fileNode = client.getNodeByPath("/UploadDirTest/testFileAttr.txt"); ASSERT_EQ(API_OK, synchronousSetNodeFavourite(0, fileNode, true)) << "Error setting fav"; ASSERT_EQ(API_OK, synchronousSetNodeLabel(0, fileNode, 4)) << "Error setting label"; // Re-upload the same file with same content. ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName, value(dirNode).get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); fileNode = client.getNodeByPath("/UploadDirTest/testFileAttr.txt"); ASSERT_EQ(fileNode->getLabel(), 4) << "Node label is not retained after re-uploading the file"; ASSERT_EQ(fileNode->isFavourite(), true) << "Favourite flag is not retained after re-uploading the file"; // Let's update the file and upload again. sdk_test::appendToFile(fs::path(fileName), 20000); ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName, value(dirNode).get(), nullptr /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); fileNode = client.getNodeByPath("/UploadDirTest/testFileAttr.txt"); ASSERT_EQ(fileNode->getLabel(), 4) << "Node label is not retained after updating the file"; ASSERT_EQ(fileNode->isFavourite(), true) << "Favourite flag is not retained after updating the file"; bool hasFingerprint = !!fileNode->getFingerprint(); // Upload the same file with different name. ASSERT_EQ(MegaError::API_OK, doStartUpload(0, &fileHandle, fileName, value(dirNode).get(), "testFileAttr_1.txt" /*fileName*/, ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Cannot upload " << fileName; ASSERT_NE(fileHandle, INVALID_HANDLE); fileNode = client.getNodeByPath("/UploadDirTest/testFileAttr_1.txt"); ASSERT_NE(fileNode->getLabel(), 4) << "Node label is copied for for renamed file upload"; ASSERT_NE(fileNode->isFavourite(), true) << "Favourite flag is copied for for renamed file upload"; if (hasFingerprint) { ASSERT_NE(fileNode->getFingerprint(), nullptr) << "Finger print has been copied"; } } class SdkTestNodeGpsCoordinates: public SdkTest { protected: const unsigned int mApiIndex{0}; MegaHandle mNodeHandle{INVALID_HANDLE}; std::unique_ptr mNode; struct GpsCoordinates { double latitude; double longitude; }; const GpsCoordinates mGpsCoordinates{40.966095795138365, -5.662973159866294}; public: void SetUp() override { SdkTest::SetUp(); // Configure test instance const unsigned int numberOfTestInstances{1}; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(numberOfTestInstances)); // Upload file std::unique_ptr rootNode{megaApi[mApiIndex]->getRootNode()}; ASSERT_THAT(rootNode.get(), ::testing::NotNull()); const std::string filename{"test.txt"}; ASSERT_TRUE(createFile(filename, false, "")); ASSERT_EQ(doStartUpload(mApiIndex, &mNodeHandle, filename.c_str(), rootNode.get(), nullptr, MegaApi::INVALID_CUSTOM_MOD_TIME, nullptr, false, false, nullptr), MegaError::API_OK); ASSERT_NE(mNodeHandle, INVALID_HANDLE); // Get node mNode.reset(megaApi[mApiIndex]->getNodeByHandle(mNodeHandle)); ASSERT_THAT(mNode.get(), ::testing::NotNull()); } }; TEST_F(SdkTestNodeGpsCoordinates, SetUnshareableNodeCoordinatesWithNullNode) { std::unique_ptr requestTracker{ asyncSetUnshareableNodeCoordinates(mApiIndex, nullptr, mGpsCoordinates.latitude, mGpsCoordinates.longitude)}; ASSERT_EQ(requestTracker->waitForResult(), API_EARGS); } TEST_F(SdkTestNodeGpsCoordinates, SetUnshareableNodeCoordinatesWithNode) { std::unique_ptr requestTracker{ asyncSetUnshareableNodeCoordinates(mApiIndex, mNode.get(), mGpsCoordinates.latitude, mGpsCoordinates.longitude)}; ASSERT_EQ(requestTracker->waitForResult(), API_OK); // Check if the user can read the GPS coordinates std::unique_ptr node(megaApi[mApiIndex]->getNodeByHandle(mNode->getHandle())); ASSERT_TRUE(veryclose(node->getLatitude(), mGpsCoordinates.latitude)); ASSERT_TRUE(veryclose(node->getLongitude(), mGpsCoordinates.longitude)); } TEST_F(SdkTestNodeGpsCoordinates, SetUnshareableNodeCoordinatesWithNodeHandle) { std::unique_ptr requestTracker{ asyncSetUnshareableNodeCoordinates(mApiIndex, mNodeHandle, mGpsCoordinates.latitude, mGpsCoordinates.longitude)}; ASSERT_EQ(requestTracker->waitForResult(), API_OK); // Check if the user can read the GPS coordinates std::unique_ptr node(megaApi[mApiIndex]->getNodeByHandle(mNode->getHandle())); ASSERT_TRUE(veryclose(node->getLatitude(), mGpsCoordinates.latitude)); ASSERT_TRUE(veryclose(node->getLongitude(), mGpsCoordinates.longitude)); } TEST_F(SdkTest, EstablishContactRelationship) { // Convenience. using testing::AnyOf; // We need at least two clients to work with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Convenience. auto& client0 = *megaApi[0]; auto& client1 = *megaApi[1]; // Make sure the clients aren't already friends. ASSERT_THAT(removeContact(client0, client1), AnyOf(API_ENOENT, API_OK)); // Try and send a contact invitation. auto [invitation, invitationSent] = sendInvitationTo(client0, client1); // Make sure the invitation was received. ASSERT_EQ(invitationSent, API_OK); // Try and accept the invitation. ASSERT_EQ(acceptInvitation(client1, *invitation), API_OK); } TEST_F(SdkTest, EstablishContactRelationshipAutomatically) { // Convenience. using testing::AnyOf; // We need at least two clients to work with. ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Convenience. auto& client0 = *megaApi[0]; auto& client1 = *megaApi[1]; // Make sure the clients aren't already friends. ASSERT_THAT(removeContact(client0, client1), AnyOf(API_ENOENT, API_OK)); // Try and send an invitation from client0 to client1. auto [invitation0, invitation0Sent] = sendInvitationTo(client0, client1); // Make sure that invitation was sent. ASSERT_EQ(invitation0Sent, API_OK); // Try and send an invitation from client1 to client1. auto [invitation1, invitation1Sent] = sendInvitationTo(client1, client0); // Make sure that invitation fails: there's already an incoming PCR ASSERT_EQ(invitation1Sent, API_EEXIST); } /** * @test SdkTransferCopyRemote * @brief Verifies that remote copy transfers are correctly handled, * * Steps: * 1. Create a temporary 50 MB file locally. * 2. Upload the file to a remote folder using MegaApi::startUpload(). * 3. Repeat the upload with a different name to verify multiple transfer handling. * 4. Perform a local logout while an upload is in progress, then log in again. * 5. Confirm that pending or completed transfers are properly finalized or resumed. * * Expected Results: * - At the end of the test, three nodes are present in the target folder, * confirming that all transfer recovery paths have completed successfully. */ TEST_F(SdkTest, SdkTransferCopyRemote) { LOG_info << "___TEST SdkTransferCopyRemote___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); static const size_t kFileSize = 50ull * 1024 * 1024; // 50 MB // Create local random file fs::path filePath = fs::current_path() / PUBLICFILE; sdk_test::LocalTempFile localFile(filePath, kFileSize); std::unique_ptr rootnode(megaApi[0]->getRootNode()); auto [errCode, fh] = createRemoteFolder(0, "Folder", rootnode.get()); ASSERT_EQ(errCode, API_OK) << "Unexpected ErrCode upon creating Folder: "; std::unique_ptr folderNode{megaApi[0]->getNodeByHandle(fh)}; auto uploadFile = [filePath, &folderNode, this](const char* uploadName = nullptr, bool localLogout = false) { testing::NiceMock mockGlobalListener{megaApi[0].get()}; const MrProper cleanUp( [&mockGlobalListener, this]() { ::testing::Mock::VerifyAndClearExpectations(&mockGlobalListener); megaApi[0]->removeListener(&mockGlobalListener); }); // --- Setup a global listener to capture dbid and tag on next transfer --- std::promise dbidAndTagOnStart; EXPECT_CALL(mockGlobalListener, onTransferStart) .WillOnce( [&dbidAndTagOnStart](MegaApi*, MegaTransfer* transfer) { dbidAndTagOnStart.set_value({transfer->getUniqueId()}); }); std::promise transferFinishPromise; EXPECT_CALL(mockGlobalListener, onTransferFinish) .WillOnce( [&transferFinishPromise](MegaApi*, MegaTransfer*, MegaError*) { transferFinishPromise.set_value(); }); megaApi[0]->addListener(&mockGlobalListener); mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false; MegaUploadOptions uploadOpts; uploadOpts.fileName = uploadName ? uploadName : ""; uploadOpts.mtime = ::mega::MegaApi::INVALID_CUSTOM_MOD_TIME; const auto uploadLocalPath = filePath.string(); megaApi[0]->startUpload(uploadLocalPath, folderNode.get(), nullptr, &uploadOpts, nullptr); /*MegaTransferListener*/ // --- Get dbid and tag of first transfer since started listening --- auto startTransferListerResult = dbidAndTagOnStart.get_future(); ASSERT_EQ(startTransferListerResult.wait_for(std::chrono::seconds(maxTimeout)), std::future_status::ready) << "Timeout for the start upload"; const auto transferUniqueId = startTransferListerResult.get(); ASSERT_NE(transferUniqueId, 0) << "Missing transferUniqueId param for onTransferStart event"; if (localLogout) { string session = unique_ptr(dumpSession()).get(); locallogout(0); PerApi& target = mApi[0]; target.resetlastEvent(); auto tracker = asyncRequestFastLogin(0, session.c_str()); ASSERT_EQ(API_OK, tracker->waitForResult()) << " Failed to establish a login/session"; ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // make sure that client is up to date (upon logout, recent changes might not be // committed to DB) ASSERT_TRUE(WaitFor( [&target]() { return target.lastEventsContain(MegaEvent::EVENT_NODES_CURRENT); }, 10000)) << "Timeout expired to receive actionpackets"; } auto finishTransferListerResult = transferFinishPromise.get_future(); ASSERT_EQ(finishTransferListerResult.wait_for(std::chrono::seconds(maxTimeout)), std::future_status::ready) << "Timeout for the finish upload"; }; int numFiles = 1; ASSERT_NO_FATAL_FAILURE(uploadFile()); std::unique_ptr children{megaApi[0]->getChildren(folderNode.get())}; ASSERT_TRUE(children); ASSERT_EQ(children->size(), numFiles); ASSERT_NO_FATAL_FAILURE(uploadFile("NewName")); numFiles = 2; children.reset(megaApi[0]->getChildren(folderNode.get())); ASSERT_TRUE(children); ASSERT_EQ(children->size(), numFiles); ASSERT_NO_FATAL_FAILURE(uploadFile("NewNewName", true)); // When a local logout is executed, all unfinished transfers are notified // through an onTransferFinish callback with error -11. In this situation, // there are three possible scenarios: // // 1. The logout occurs after the transfer has completed successfully. // The onTransferFinish event is received normally. // // 2. The logout occurs after the PUT command has been sent, but before // receiving the server response. On the next login, the SDK will // receive the action package notifying the creation of the new node, // and the pending transfer will be discarded. // // 3. The logout occurs before the PUT command is sent. On the next login, // the SDK will issue the PUT command again, and the transfer will // complete normally. // // The simplest way to verify that everything has been executed correctly // is to check that there are three resulting items at the end of the process. unsigned int timeOut = 30; numFiles = 3; ASSERT_TRUE(waitForEvent( [this, numFiles, &folderNode]() -> bool { std::unique_ptr children{megaApi[0]->getChildren(folderNode.get())}; if (children) { return children->size() == numFiles; } return false; }, timeOut)); } /** * @test SdkUtqaMobileOffer * @brief Verify mobile offer are received * * Steps: * - Set value at devOpt user attribute (In staging API add mobile offert at * utqa command) * - Get pricing (utqa command) * - Check if some product has mobile offers */ TEST_F(SdkTest, SdkUtqaMobileOffer) { CASE_info << " Start"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1, true, MegaApi::CLIENT_TYPE_DEFAULT, "https://staging.api.mega.co.nz/")); uint32_t index{0}; using namespace testing; std::string attributeValue{"{\"utqamo\":1}"}; ASSERT_NO_FATAL_FAILURE(setDevOptUserAttribute(attributeValue, index)); auto pricing = getPricing(*megaApi[index]); ASSERT_EQ(::result(pricing), API_OK) << "Error at getPricing"; auto& priceDetailRes = ::value(pricing); ASSERT_TRUE(priceDetailRes) << "No princing objectes received"; bool mobileOffer{false}; std::string mobileOfferTitle; for (int i = 0, j = priceDetailRes->getNumProducts(); i < j; ++i) { // Found the user's plan. if (mobileOffer = priceDetailRes->hasMobileOffers(i); mobileOffer) { mobileOfferTitle = priceDetailRes->getMobileOfferId(i); break; } } ASSERT_TRUE(mobileOffer) << "Received at least one mobile offer"; ASSERT_TRUE(mobileOfferTitle.size()) << "Title has contain"; CASE_info << " Finished"; } TEST_F(SdkTest, SdkTestGetPricingAndGetDiscountCode) { CASE_info << " Start"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1, true, MegaApi::CLIENT_TYPE_DEFAULT, "https://staging.api.mega.co.nz/")); uint32_t index{0}; std::string attributeValue = {"{\"insdis\":1}"}; ASSERT_NO_FATAL_FAILURE(setDevOptUserAttribute(attributeValue, index)); auto pricing = getPricing(*megaApi[index]); ASSERT_EQ(::result(pricing), API_OK) << "Error at getPricing"; auto& priceDetailRes = ::value(pricing); bool discountCodeFound{false}; for (int i = 0, j = priceDetailRes->getNumProducts(); i < j; ++i) { if (!priceDetailRes->hasDiscount(i)) { continue; } CASE_info << " Product " << i << " has discount "; discountCodeFound = true; ASSERT_NE(priceDetailRes->getDiscountCode(i), nullptr) << "No discount code received for product " << i; ASSERT_NE(priceDetailRes->getDiscountName(i), nullptr) << "No discount name received for product " << i; ASSERT_GE(priceDetailRes->getDiscountGroup(i), 0) << "No discount group received for product " << i; ASSERT_GE(priceDetailRes->getDiscountMonths(i), 0) << "No discount months received for product " << i; ASSERT_GE(priceDetailRes->getDiscountPercentage(i), 0) << "No discount percentage received for product " << i; std::string discountCode = priceDetailRes->getDiscountCode(i); RequestTracker listener{megaApi[0].get()}; megaApi[index]->getDiscountCodeInformation(discountCode.c_str(), &listener); ASSERT_EQ(listener.waitForResult(), API_OK) << "Error at getDiscountCodeInformation for code " << discountCode; auto codeInfo = listener.request->getMegaDiscountCodeInfo(); ASSERT_EQ(std::string(codeInfo->getCode()), discountCode) << "Discount code mismatch at getDiscountCodeInformation for code " << discountCode; ASSERT_EQ(codeInfo->getMonths(), priceDetailRes->getDiscountMonths(i)) << "Discount months mismatch at getDiscountCodeInformation for code " << discountCode; ASSERT_EQ(codeInfo->getPercentageDiscount(), priceDetailRes->getDiscountPercentage(i)) << "Discount percentage mismatch at getDiscountCodeInformation for code " << discountCode; ASSERT_GE(codeInfo->getMultiDiscount(), 0); ASSERT_GT(codeInfo->getEuroTotalPrice(), 0) << "No euro total price at getDiscountCodeInformation for code " << discountCode; ASSERT_GT(codeInfo->getEuroDiscountAmount(), 0) << "No euro discount amount at getDiscountCodeInformation for code " << discountCode; ASSERT_GT(codeInfo->getEuroDiscountedTotalPrice(), 0) << "No euro discounted total price at getDiscountCodeInformation for code " << discountCode; ASSERT_GT(codeInfo->getEuroDiscountedMonthlyPrice(), 0) << "No euro discounted monthly price at getDiscountCodeInformation for code " << discountCode; } ASSERT_TRUE(discountCodeFound) << "No discount code found in pricing"; CASE_info << " Finished"; } void SdkTest::setDevOptUserAttribute(const string& value, uint32_t index) const { using namespace testing; NiceMock rlSetUserAttribute{megaApi[index].get()}; std::promise setUserAttributeRequest; // Check if it has correct value to avoid to set it again auto [_, v] = getDevOptUserAttribute(index); ASSERT_FALSE(HasNonfatalFailure()); if (v && v.value() == value) { return; } EXPECT_CALL(rlSetUserAttribute, onRequestFinish) .Times(1) .WillOnce( [&setUserAttributeRequest](::mega::MegaApi*, ::mega::MegaRequest* req, ::mega::MegaError* err) { EXPECT_TRUE(req) << "Null MegaRequest"; EXPECT_TRUE(err) << "Null MegaError"; if (err && err->getErrorCode() == API_OK) { setUserAttributeRequest.set_value(true); return; } setUserAttributeRequest.set_value(false); }); megaApi[index]->setUserAttribute(MegaApi::USER_ATTR_DEV_OPT, value.c_str(), &rlSetUserAttribute); auto resultsetUserAttributeRequest = setUserAttributeRequest.get_future(); ASSERT_EQ(resultsetUserAttributeRequest.wait_for(sdk_test::MAX_TIMEOUT), std::future_status::ready) << "Attribute not received "; ASSERT_TRUE(resultsetUserAttributeRequest.get()) << "Not possible set User attribute USER_ATTR_DEV_OPT"; } SdkTest::GetDevOptAttrResult SdkTest::getDevOptUserAttribute(uint32_t index) const { using namespace testing; NiceMock rlGetUserAttribute{megaApi[index].get()}; std::promise getUserAttributeRequest; EXPECT_CALL(rlGetUserAttribute, onRequestFinish) .Times(1) .WillOnce( [&getUserAttributeRequest](::mega::MegaApi*, ::mega::MegaRequest* req, ::mega::MegaError* err) { EXPECT_TRUE(req) << "Null MegaRequest"; EXPECT_TRUE(err) << "Null MegaError"; auto error = err->getErrorCode(); if (err && req && error == API_OK) { std::string text{Base64::atob(req->getText())}; getUserAttributeRequest.set_value({error, text}); return; } getUserAttributeRequest.set_value({error, std::nullopt}); }); megaApi[index]->getUserAttribute(MegaApi::USER_ATTR_DEV_OPT, &rlGetUserAttribute); auto resultGetUserAttributeRequest = getUserAttributeRequest.get_future(); auto status = resultGetUserAttributeRequest.wait_for(sdk_test::MAX_TIMEOUT); if (status != future_status::ready) { EXPECT_EQ(status, std::future_status::ready); return {LOCAL_ETIMEOUT, std::nullopt}; } const auto& [error, value] = resultGetUserAttributeRequest.get(); EXPECT_TRUE(error == API_OK || error == API_ENOENT); return {error, value}; } sdk-10.11.0/tests/integration/SdkTest_test.h000066400000000000000000002601671516266226600207410ustar00rootroot00000000000000/** * @file tests/sdk_test.cpp * @brief Mega SDK test file * * (c) 2015 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #ifndef SDKTEST_TEST_H #define SDKTEST_TEST_H #include "../include/megaapi.h" #include "../include/megaapi_impl.h" #include "gtest/gtest.h" #include "mega.h" #include "mega/scoped_helpers.h" #include "mock_listeners.h" #include "sdk_test_data_provider.h" #include "test.h" #include #include #include #include #include #include #ifndef WIN32 #define DOTSLASH "./" #else #define DOTSLASH ".\\" #endif using namespace mega; using ::testing::Test; // IMPORTANT: the main account must be empty (Cloud & Rubbish) before starting the test and it will be purged at exit. // Both main and auxiliar accounts shouldn't be contacts yet and shouldn't have any pending contact requests. // Set your login credentials as environment variables: $MEGA_EMAIL and $MEGA_PWD (and $MEGA_EMAIL_AUX / $MEGA_PWD_AUX for shares * contacts) static const unsigned int pollingT = 500000; // (microseconds) to check if response from server is received static const unsigned int maxTimeout = 600; // Maximum time (seconds) to wait for response from server static const unsigned int defaultTimeout = 60; // Normal time for most operations (seconds) to wait for response from server static const unsigned int defaultTimeoutMs = defaultTimeout * 1000; static const unsigned int waitForSyncsMs = 4000; // Time to wait after a sync has been created and before adding new files to it constexpr unsigned int cleanupCatchupTimeoutSecs = 15; #ifdef ENABLE_SYNC /** * @brief Wrapper struct of MegaListener to get all information related to a MEGA account * - make sure callbacks are consistent - added() first, nothing after deleted(), etc. * * @note: map by tag for now, should be backupId when that is available */ struct SyncListener: MegaListener { enum callbacks_t { SyncFileStateChanged, SyncAdded, SyncDeleted, SyncStateChanged, SyncRemoteRootChanged, GlobalSyncStateChanged, CountCbs, }; enum syncstate_t { nonexistent, added, deleted }; /** * Array of flags that informs, when SyncListener callbacks have been received */ std::array, CountCbs> mRecvCbs{}; std::map stateMap; syncstate_t& state(MegaSync* sync) { if (stateMap.find(sync->getBackupId()) == stateMap.end()) { stateMap[sync->getBackupId()] = nonexistent; } return stateMap[sync->getBackupId()]; } std::vector mErrors; bool anyErrors = false; bool hasAnyErrors() { for (auto& s: mErrors) { out() << "SyncListener error: " << s; } return anyErrors; } void check(bool b, std::string e = std::string()) { if (!b) { anyErrors = true; if (!e.empty()) { mErrors.push_back(e); out() << "SyncListener added error: " << e; } } } void clear() { // session was logged out (locally) stateMap.clear(); } void onSyncFileStateChanged(MegaApi*, MegaSync* /*sync*/, std::string* /*localPath*/, int /*newState*/) override { // probably too frequent to output // out() << "onSyncFileStateChanged " << sync << newState; mRecvCbs[SyncFileStateChanged] = true; } void onSyncAdded(MegaApi*, MegaSync* sync) override { out() << "onSyncAdded " << toHandle(sync->getBackupId()); check(sync->getBackupId() != UNDEF, "sync added with undef backup Id"); check(state(sync) == nonexistent); state(sync) = added; mRecvCbs[SyncAdded] = true; } void onSyncDeleted(MegaApi*, MegaSync* sync) override { out() << "onSyncDeleted " << toHandle(sync->getBackupId()); check(state(sync) != nonexistent && state(sync) != deleted); state(sync) = nonexistent; mRecvCbs[SyncDeleted] = true; } void onSyncStateChanged(MegaApi*, MegaSync* sync) override { out() << "onSyncStateChanged " << toHandle(sync->getBackupId()) << " runState: " << sync->getRunState(); check(sync->getBackupId() != UNDEF, "onSyncStateChanged with undef backup Id"); // MegaApi doco says: "Notice that adding a sync will not cause onSyncStateChanged to be // called." And also: "for changes that imply other callbacks, expect that the SDK will call // onSyncStateChanged first, so that you can update your model only using this one." check(state(sync) != nonexistent); mRecvCbs[SyncStateChanged] = true; } void onSyncRemoteRootChanged(MegaApi*, MegaSync* sync) override { out() << "onSyncRemoteRootChanged " << toHandle(sync->getBackupId()) << " new Remote root: " << sync->getLastKnownMegaFolder(); mRecvCbs[SyncRemoteRootChanged] = true; } void onGlobalSyncStateChanged(MegaApi*) override { // just too frequent for out() really // out() << "onGlobalSyncStateChanged "; mRecvCbs[GlobalSyncStateChanged] = true; } }; /** * @brief The MegaListenerDeregisterer Struct * - register the listener on constructions * - deregister on destruction (ie, whenever we exit the function - we may exit early if a test * fails */ struct MegaListenerDeregisterer { MegaApi* api = nullptr; MegaListener* listener; MegaListenerDeregisterer(MegaApi* a, SyncListener* l): api(a), listener(l) { api->addListener(listener); } ~MegaListenerDeregisterer() { api->removeListener(listener); } }; #endif struct TransferTracker : public ::mega::MegaTransferListener { std::atomic started = { false }; std::atomic finished = { false }; std::atomic result = { ErrorCodes::API_EINTERNAL }; std::promise promiseResult; MegaApi *mApi; std::future futureResult; std::shared_ptr selfDeleteOnFinalCallback; bool mTempFileRemoved{false}; MegaHandle resultNodeHandle = UNDEF; m_off_t mTransferSpeed{-1}; m_off_t mTransferMeanSpeed{-1}; TransferTracker(MegaApi *api): mApi(api), futureResult(promiseResult.get_future()) { } ~TransferTracker() override { if (!finished) { assert(mApi); mApi->removeTransferListener(this); } } void onTransferStart(MegaApi*, MegaTransfer* transfer) override { // called back on a different thread LOG_debug << "TransferTracker::onTransferStart callback received -> set started true for " << (transfer && transfer->getFileName() ? transfer->getFileName() : ""); started = true; } void onTransferFinish(MegaApi*, MegaTransfer* transfer, MegaError* error) override { LOG_debug << "TransferTracker::onTransferFinish callback received. Result: " << error->getErrorCode() << " for " << (transfer->getFileName() ? transfer->getFileName() : ""); mTempFileRemoved = static_cast(transfer->getStage()); // called back on a different thread resultNodeHandle = transfer->getNodeHandle(); mTransferSpeed = transfer->getSpeed(); mTransferMeanSpeed = transfer->getMeanSpeed(); LOG_debug << "TransferTracker::onTransferFinish - tSpeed = " << mTransferSpeed << ", tMeanSpeed = " << mTransferMeanSpeed; result = static_cast(error->getErrorCode()); finished = true; // this local version still valid even after we self-delete std::promise local_promise = std::move(promiseResult); if (selfDeleteOnFinalCallback) { // this class can be used as a local on the stack, or constructed on the heap. // for the stack case, this object will be destroyed after the wait completes // but for the heap case, that is usually chosen so that deletion can occur on completion // or whenever the last needed reference is deleted. So for that case, // set the selfDeleteOnFinalCallback to be a shared_ptr to this object. selfDeleteOnFinalCallback.reset(); // self-delete } // let the test main thread know it can now continue local_promise.set_value(result); } ErrorCodes waitForResult(int seconds = defaultTimeout, bool unregisterListenerOnTimeout = true) { // running on test's main thread if (std::future_status::ready != futureResult.wait_for(std::chrono::seconds(seconds))) { assert(mApi); if (unregisterListenerOnTimeout) { mApi->removeTransferListener(this); } return static_cast(LOCAL_ETIMEOUT); // local timeout } return futureResult.get(); } }; typedef std::function OnReqFinish; struct RequestTracker : public ::mega::MegaRequestListener { std::atomic started = { false }; std::atomic finished = { false }; std::atomic result = { ErrorCodes::API_EINTERNAL }; std::promise promiseResult; MegaApi *mApi; unique_ptr request; OnReqFinish onFinish; RequestTracker(MegaApi *api, OnReqFinish finish = nullptr) : mApi(api) , onFinish(finish) { } ~RequestTracker() override { if (!finished) { assert(mApi); mApi->removeRequestListener(this); } } void onRequestStart(MegaApi*, MegaRequest*) override { started = true; } void onRequestFinish(MegaApi*, MegaRequest* request, MegaError* e) override { if (onFinish) onFinish(*e, *request); result = ErrorCodes(e->getErrorCode()); this->request.reset(request->copy()); assert(this->request->getType() != MegaRequest::TYPE_ADD_SYNC || this->request->getNumDetails() <= SyncError::NO_SYNC_ERROR || this->request->getNumDetails() == e->getSyncError()); finished = true; promiseResult.set_value(static_cast(result)); } ErrorCodes waitForResult(int seconds = maxTimeout, bool unregisterListenerOnTimeout = true) { auto f = promiseResult.get_future(); if (std::future_status::ready != f.wait_for(std::chrono::seconds(seconds))) { assert(mApi); if (unregisterListenerOnTimeout) { mApi->removeRequestListener(this); } return static_cast(LOCAL_ETIMEOUT); // local timeout } return f.get(); } MegaHandle getNodeHandle() { // if the operation succeeded and supplies a node handle if (request) return request->getNodeHandle(); return INVALID_HANDLE; } string getLink() { // if the operation succeeded and supplies a link if (request && request->getLink()) return request->getLink(); return ""; } unique_ptr getPublicMegaNode() { if (request) return unique_ptr(request->getPublicMegaNode()); return nullptr; } bool getFlag() { return request ? request->getFlag() : false; } int getParamType() { return request ? request->getParamType() : -999; } int getNumber() { return request ? static_cast(request->getNumber()) : -999; } template static unique_ptr async(MegaApi& api, void (MegaApi::*mf)(Params...), Args&&... args) { static_assert(sizeof...(Args) + 1 == sizeof...(Params)); auto rt = std::make_unique(&api); (api.*mf)(std::forward(args)..., rt.get()); return rt; } }; struct OneShotListener : public ::mega::MegaRequestListener { // on request completion, executes the lambda and deletes itself. std::function mFunc; OneShotListener(std::function f): mFunc(f) {} void onRequestFinish(MegaApi*, MegaRequest* request, MegaError* e) override { mFunc(*e, *request); delete this; } }; using onNodesUpdateCompletion_t = std::function; class MegaApiTest: public MegaApi { public: MegaApiTest(const char* appKey, const char* basePath = nullptr, const char* userAgent = nullptr, unsigned workerThreadCount = 1, const int clientType = MegaApi::CLIENT_TYPE_DEFAULT); MegaApiTest(const char* appKey, MegaGfxProvider* provider, const char* basePath = nullptr, const char* userAgent = nullptr, unsigned workerThreadCount = 1, const int clientType = MegaApi::CLIENT_TYPE_DEFAULT); MegaClient* getClient(); }; class MegaApiTestDeleter { public: MegaApiTestDeleter(const std::string& endpointName): mEndpointName{endpointName} {}; MegaApiTestDeleter(): MegaApiTestDeleter(""){}; void operator()(MegaApiTest* p) const; private: std::string mEndpointName; }; // Poor man's expected. template using Expected = std::variant; template struct IsExpected: std::false_type {}; // IsExpected template struct IsExpected>: std::true_type {}; // IsExpected> template static constexpr auto IsExpectedV = IsExpected::value; template using RemoveCVRef = std::remove_cv>; template using RemoveCVRefT = typename RemoveCVRef::type; template Error result(const Expected& expected) { if (auto* result = std::get_if<0>(&expected)) return *result; return API_OK; } template>>> decltype(auto) value(T&& expected) { #ifndef NDEBUG if (auto res = result(expected); res != API_OK) { LOG_err << "value: unexpected result: " << res; assert(res == API_OK && "value: unexpected result"); } #endif return std::get<1>(std::forward(expected)); } using MegaApiTestPointer = std::unique_ptr; // Fixture class with common code for most of tests class SdkTest: public SdkTestBase, public MegaListener, public MegaRequestListener, MegaTransferListener, MegaLogger { public: static constexpr auto COMMON_TIMEOUT = 3min; const string APP_KEY = "8QxzVRxD"; const string PUBLICFILE = "file.txt"; const string UPFILE = "file1.txt"; const string DOWNFILE = "file2.txt"; const string EMPTYFILE = "empty-file.txt"; const string IMAGEFILE = "logo.png"; const string VIDEOFILE = "sample_video.mp4"; const string AUDIOFILE = "test_cover_png.mp3"; const string& AVATARSRC = IMAGEFILE; const string AVATARDST = "deleteme.png"; const string IMAGEFILE_C = "logo.encrypted.png"; const string THUMBNAIL = "logo_thumbnail.png"; const string PREVIEW = "logo_preview.png"; const string PUBLIC_IMAGE_URL = "/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns"; // gitleaks:allow struct SyncUptoDate { MegaHandle mBackupID{INVALID_HANDLE}; std::atomic mIsUpToDate{false}; std::promise mSyncUpToDatePms; std::future mSyncFut; testing::NiceMock mMslStats; void resetStatus() { mSyncUpToDatePms = std::promise(); mSyncFut = std::future(mSyncUpToDatePms.get_future()); } SyncUptoDate(MegaApi* megaApi): mMslStats(testing::NiceMock(megaApi)) { resetStatus(); } }; struct PerApi { MegaApi* megaApi = nullptr; string email; string pwd; int lastError; int lastTransferError; // flags to monitor the completion of requests/transfers bool requestFlags[MegaRequest::TOTAL_OF_REQUEST_TYPES]; // to be removed due to race conditions bool transferFlags[MegaTransfer::TYPE_LOCAL_HTTP_DOWNLOAD]; std::unique_ptr cr; std::unique_ptr tzDetails; std::unique_ptr accountDetails; std::unique_ptr mStringMap; std::unique_ptr mMegaPricing; std::unique_ptr mMegaCurrency; // variables to monitor when sync is upToDate std::map> mBackupsMonitor; // flags to monitor the updates of nodes/users/sets/set-elements/PCRs due to actionpackets bool userUpdated; bool userFirstNameUpdated = false; bool setUpdated; bool setElementUpdated; bool contactRequestUpdated{false}; bool accountUpdated; bool nodeUpdated; // flag to check specific updates for a node (upon onNodesUpdate) int recentClearTimeUpdatedCount = 0; // number of clear recent history updates (upon UserUpdate) // A map to store custom functions to be called inside callbacks std::map>> customCallbackCheck; bool userAlertsUpdated; std::unique_ptr userAlertList; // unique_ptr to custom functions that will be called upon reception of MegaApi callbacks onNodesUpdateCompletion_t mOnNodesUpdateCompletion; std::unique_ptr mFolderInfo; int lastSyncError; handle lastSyncBackupId = 0; #ifdef ENABLE_CHAT bool chatUpdated; // flags to monitor the updates of chats due to actionpackets bool schedUpdated; // flags to monitor the updates of scheduled meetings due to actionpackets map> chats; // runtime cache of fetched/updated chats MegaHandle chatid; // last chat added MegaHandle schedId; // last scheduled meeting added #endif /** * @brief Add sync monitor and configure EXPECT calls to notify when sync is up to date */ void addsyncMonitor(const MegaHandle backupID) { ASSERT_TRUE(mBackupsMonitor.find(backupID) == mBackupsMonitor.end()) << "mBackupsMonitor already contains an entry for BackupId: " << toHandle(backupID); auto [it, succeeded] = mBackupsMonitor.emplace(backupID, std::unique_ptr(new SyncUptoDate(megaApi))); ASSERT_TRUE(succeeded) << "addsyncMonitor: Could not add entry at mBackupsMonitor for BackupId: " << toHandle(backupID); auto& msl = it->second->mMslStats; EXPECT_CALL(msl, onSyncStatsUpdated(testing::_, testing::_)) .WillRepeatedly( [this, backupID](MegaApi*, MegaSyncStats* stats) { if (backupID != UNDEF && stats && stats->getBackupId() == backupID && stats->getUploadCount() == 0 && !stats->isScanning() && !stats->isSyncing()) { auto it = mBackupsMonitor.find(backupID); if (it == mBackupsMonitor.end() || it->second->mIsUpToDate) { return; } it->second->mIsUpToDate = true; it->second->mSyncUpToDatePms.set_value(); } }); megaApi->addListener(&msl); } /** * @brief Clear BackupsMonitorMap and Unregister SyncListeners stored in BackupsMonitorMap * items */ void clearBackupsMonitorMap() { for (auto it = mBackupsMonitor.begin(); it != mBackupsMonitor.end();) { megaApi->removeListener(&it->second->mMslStats); it = mBackupsMonitor.erase(it); } } /** * @brief Wait for Sync is up to date. */ bool waitForBackupSyncUpToDate(const MegaHandle backupID) { auto it = mBackupsMonitor.find(backupID); if (it == mBackupsMonitor.end()) { LOG_err << "waitForBackupSyncUpToDate: No entry found at mBackupsMonitor with BackupId" << toHandle(backupID); return false; } return it->second->mSyncFut.wait_for(COMMON_TIMEOUT) == std::future_status::ready; }; /** * @brief Ensures that the access to the customCallbackCheck map and the posterior function * call is properly managed. */ void callCustomCallbackCheck(const MegaHandle userHandle) { auto it = customCallbackCheck.find(userHandle); if (it == customCallbackCheck.end()) { return; } auto funPtr = it->second.lock(); if (funPtr) { (*funPtr)(); } else { customCallbackCheck.erase(it); } } void receiveEvent(MegaEvent* e) { if (!e) return; lock_guard g(getResourceMutex()); lastEvent.reset(e->copy()); lastEvents.insert(e->getType()); } void resetlastEvent() { lock_guard g(getResourceMutex()); lastEvent.reset(); lastEvents.clear(); } bool lastEventsContain(int type) const { lock_guard g(getResourceMutex()); return lastEvents.find(type) != lastEvents.end(); } void setSid(const string& s) { sid = s; } const string& getSid() const { return sid; } void setAttributeValue(const string& v) { attributeValue = v; } const string& getAttributeValue() const { return attributeValue; } void setChatLink(const string& c) { chatlink = c; } const string& getChatLink() const { return chatlink; } void setBackupId(MegaHandle b) { mBackupId = b; } MegaHandle getBackupId() const { return mBackupId; } void setFavNodes(const MegaHandleList* f) { mMegaFavNodeList.reset(f); } unsigned getFavNodeCount() const { return mMegaFavNodeList ? mMegaFavNodeList->size() : 0u; } MegaHandle getFavNode(unsigned i) const { return mMegaFavNodeList->size() > i ? mMegaFavNodeList->get(i) : INVALID_HANDLE; } void setStringLists(const MegaStringListMap* s) { stringListMap.reset(s); } unsigned getStringListCount() const { return stringListMap ? static_cast(stringListMap->size()) : 0u; } const MegaStringList* getStringList(const char* key) const { return stringListMap ? stringListMap->get(key) : nullptr; } void setStringTable(const MegaStringTable* s) { stringTable.reset(s); } int getStringTableSize() const { return stringTable ? stringTable->size() : 0; } const MegaStringList* getStringTableRow(int i) { return stringTable ? stringTable->get(i) : nullptr; } private: mutex& getResourceMutex() const { if (!resourceMtx) resourceMtx.reset(new mutex); return *resourceMtx.get(); } // a single mutex will do fine in tests mutable shared_ptr resourceMtx; shared_ptr lastEvent; // not used though; should it be removed? set lastEvents; // relevant values received in response of requests string sid; string attributeValue; string chatlink; // not really used anywhere, should it be removed ? MegaHandle mBackupId = UNDEF; shared_ptr stringListMap; shared_ptr mMegaFavNodeList; shared_ptr stringTable; }; constexpr static unsigned MAX_VAULT_CHILDREN = 2; std::vector mApi; std::vector megaApi; std::vector mAccountsRestorer; std::function onTransferStartCustomCb; m_off_t onTransferStart_progress; m_off_t onTransferUpdate_progress; m_off_t onTransferUpdate_filesize; unsigned onTranferFinishedCount = 0; bool mCleanupSuccess{true}; void updateCleanupStatus(const bool stageSucceeded) { if (!mCleanupSuccess || stageSucceeded) return; mCleanupSuccess = stageSucceeded; } struct SdkTestTransferStats { m_off_t numFailedRequests{}; m_off_t numTotalRequests{}; double failedRequestRatio{}; SdkTestTransferStats& operator=(const stats::TransferSlotStats& transferSlotStats) { numFailedRequests = transferSlotStats.mNumFailedRequests; numTotalRequests = transferSlotStats.mNumTotalRequests; failedRequestRatio = transferSlotStats.failedRequestRatio(); return *this; } }; SdkTestTransferStats onTransferFinish_transferStats{}; protected: #define CASE_info LOG_info << getLogPrefix() void SetUp() override; void TearDown() override; void cleanupPerApiBackupsMonitorMap(const unsigned i); void Cleanup(); void setTestAccountsToFree(unsigned int nApi); int getApiIndex(MegaApi* api); bool getApiIndex(MegaApi* api, size_t& apindex); bool checkAlert(int apiIndex, const string& title, const string& path); bool checkAlert(int apiIndex, const string& title, handle h, int64_t n = -1, MegaHandle mh = INVALID_HANDLE); void testPrefs(const std::string& title, int type); void testRecents(const std::string& title, bool useSensitiveExclusion); void testCloudRaidTransferResume(const bool fromNonRaid, const std::string& logPre); void testResumableTrasfers(const std::string& data, const size_t timeoutInSecs); void testHashcash(const bool logoutDuringLogin); void printCleanupErrMsg(const string& prefix, const string& errDetails, const unsigned accountIdx, const int errCode, const bool localCleanupSuccess) const; #ifdef ENABLE_CHAT void cleanupSchedMeetings(const unsigned nApi); #endif void syncTestEnsureMyBackupsRemoteFolderExists(unsigned apiIdx); void onRequestStart(MegaApi*, MegaRequest*) override {} void onRequestUpdate(MegaApi*, MegaRequest*) override {} void onRequestFinish(MegaApi* api, MegaRequest* request, MegaError* e) override; void onRequestTemporaryError(MegaApi*, MegaRequest*, MegaError*) override {} void onTransferStart(MegaApi *api, MegaTransfer *transfer) override; void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e) override; void onTransferUpdate(MegaApi *api, MegaTransfer *transfer) override; void onTransferTemporaryError(MegaApi*, MegaTransfer*, MegaError*) override {} void onUsersUpdate(MegaApi* api, MegaUserList *users) override; void onAccountUpdate(MegaApi *api) override; void onNodesUpdate(MegaApi* api, MegaNodeList *nodes) override; void onSetsUpdate(MegaApi *api, MegaSetList *sets) override; void onSetElementsUpdate(MegaApi *api, MegaSetElementList *elements) override; void onContactRequestsUpdate(MegaApi* api, MegaContactRequestList* requests) override; void onUserAlertsUpdate(MegaApi* api, MegaUserAlertList* alerts) override; #ifdef ENABLE_SYNC void onSyncFileStateChanged(MegaApi*, MegaSync*, string* /*filePath*/, int /*newState*/) override {} void onSyncStateChanged(MegaApi*, MegaSync*) override {} void onSyncRemoteRootChanged(MegaApi*, MegaSync*) override {} void onGlobalSyncStateChanged(MegaApi*) override {} void cleanupSyncs(const unsigned int nApi); void purgeVaultTree(unsigned int apiIndex, MegaNode *vault); #endif #ifdef ENABLE_CHAT void onChatsUpdate(MegaApi *api, MegaTextChatList *chats) override; void cleanupChatLinks(const unsigned int nApi, std::set& skipChats); void cleanupChatrooms(const unsigned int nApi); #endif void onEvent(MegaApi* api, MegaEvent *event) override; void resetOnNodeUpdateCompletionCBs(); void cleanupCatchupWithApi(const unsigned int apiIndex, const unsigned int timeoutSecs); onNodesUpdateCompletion_t createOnNodesUpdateLambda(const MegaHandle&, int, bool& flag); public: void login(unsigned int apiIndex, int timeout = maxTimeout); //void loginBySessionId(unsigned int apiIndex, const std::string& sessionId, int timeout = maxTimeout); void fetchnodes(unsigned int apiIndex, int timeout = 300); void logout(unsigned int apiIndex, bool keepSyncConfigs, int timeout); char* dumpSession(unsigned apiIndex = 0); void locallogout(unsigned apiIndex = 0); void resumeSession(const char *session, unsigned apiIndex = 0); void purgeTree(unsigned int apiIndex, MegaNode *p, bool depthfirst = true); void cleanupContacts(const unsigned int nApi); void cleanupShares(const unsigned int nApi); void cleanupNodeLinks(const unsigned int nApi); void cleanupNodes(const unsigned int nApi); void cleanupContactRequests(const unsigned int nApi); void cleanupLocalFiles(); bool waitForResponse(bool *responseReceived, unsigned int timeout = maxTimeout); bool waitForEvent(std::function method, unsigned int timeout = maxTimeout); static bool WaitFor(const std::function& predicate, unsigned timeoutMs); bool synchronousRequest(unsigned apiIndex, int type, std::function f, unsigned int timeout = maxTimeout); bool synchronousRequestIgnoreErr(unsigned apiIndex, int type, std::function f, unsigned int timeout = maxTimeout); bool synchronousTransfer(unsigned apiIndex, int type, std::function f, unsigned int timeout = maxTimeout); // *** WARNING *** THESE FUNCTIONS RETURN VALUE ARE SUBJECT TO RACE CONDITIONS // convenience functions - template args just make it easy to code, no need to copy all the exact argument types with listener defaults etc. To add a new one, just copy a line and change the flag and the function called. // WARNING: any sort of race can result in the lastError being set from some other command - better to use the listener based ones in the next list below template int synchronousCatchup(unsigned apiIndex, unsigned int timeoutSecs, Args... args) { if (megaApi[apiIndex]->isEphemeralPlusPlus()) { return API_OK; } synchronousRequest( apiIndex, MegaRequest::TYPE_CATCHUP, [this, apiIndex, args...]() { megaApi[apiIndex]->catchup(args...); }, timeoutSecs); return mApi[apiIndex].lastError; } template int synchronousCatchupIgnoreErr(unsigned apiIndex, unsigned int timeoutSecs, Args... args) { if (megaApi[apiIndex]->isEphemeralPlusPlus()) { return API_OK; } synchronousRequestIgnoreErr( apiIndex, MegaRequest::TYPE_CATCHUP, [this, apiIndex, args...]() { megaApi[apiIndex]->catchup(args...); }, timeoutSecs); return mApi[apiIndex].lastError; } template int synchronousCreateEphemeralAccountPlusPlus(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->createEphemeralAccountPlusPlus(args...); }); return mApi[apiIndex].lastError; } template int synchronousResumeCreateAccountEphemeralPlusPlus(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->resumeCreateAccountEphemeralPlusPlus(args...); }); return mApi[apiIndex].lastError; } template int synchronousCreateAccount(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->createAccount(args...); }); return mApi[apiIndex].lastError; } template int synchronousResumeCreateAccount(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CREATE_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->resumeCreateAccount(args...); }); return mApi[apiIndex].lastError; } template int synchronousConfirmSignupLink(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CONFIRM_ACCOUNT, [this, apiIndex, args...]() { megaApi[apiIndex]->confirmAccount(args...); }); return mApi[apiIndex].lastError; } template int synchronousFastLogin(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_LOGIN, [this, apiIndex, args...]() { megaApi[apiIndex]->fastLogin(args...); }); return mApi[apiIndex].lastError; } // SMS verification was deprecated. This function should be removed in the future, // along with the rest of the code dealing with the deprecated functionality. // template int synchronousGetCountryCallingCodes(unsigned apiIndex, Args... // args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES, [this, // apiIndex, args...]() { megaApi[apiIndex]->getCountryCallingCodes(args...); }); return // mApi[apiIndex].lastError; } template int synchronousGetUserAvatar(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserAvatar(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetUserAttribute(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserAttribute(args...); }); return mApi[apiIndex].lastError; } template int synchronousSetNodeCoordinates(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SET_ATTR_NODE, [this, apiIndex, args...]() { megaApi[apiIndex]->setNodeCoordinates(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetSpecificAccountDetails(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_ACCOUNT_DETAILS, [this, apiIndex, args...]() { megaApi[apiIndex]->getSpecificAccountDetails(args...); }); return mApi[apiIndex].lastError; } template int synchronousMediaUploadRequestURL(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_BACKGROUND_UPLOAD_URL, [this, apiIndex, args...]() { megaApi[apiIndex]->backgroundMediaUploadRequestUploadURL(args...); }); return mApi[apiIndex].lastError; } template int synchronousMediaUploadComplete(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_COMPLETE_BACKGROUND_UPLOAD, [this, apiIndex, args...]() { megaApi[apiIndex]->backgroundMediaUploadComplete(args...); }); return mApi[apiIndex].lastError; } template int synchronousFetchTimeZone(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_FETCH_TIMEZONE, [this, apiIndex, args...]() { megaApi[apiIndex]->fetchTimeZone(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetMiscFlags(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_MISC_FLAGS, [this, apiIndex, args...]() { megaApi[apiIndex]->getMiscFlags(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetUserEmail(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_USER_EMAIL, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserEmail(args...); }); return mApi[apiIndex].lastError; } template int synchronousCleanRubbishBin(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_CLEAN_RUBBISH_BIN, [this, apiIndex, args...]() { megaApi[apiIndex]->cleanRubbishBin(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetExtendedAccountDetails(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_ACCOUNT_DETAILS, [this, apiIndex, args...]() { megaApi[apiIndex]->getExtendedAccountDetails(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetBanners(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_BANNERS, [this, apiIndex, args...]() { megaApi[apiIndex]->getBanners(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetPricing(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_PRICING, [this, apiIndex, args...]() { megaApi[apiIndex]->getPricing(args...); }); return mApi[apiIndex].lastError; } template int synchronousUpdateBackup(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_BACKUP_PUT, [this, apiIndex, args...]() { megaApi[apiIndex]->updateBackup(args...); }); return mApi[apiIndex].lastError; } template int synchronousRemoveBackup(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_BACKUP_REMOVE, [this, apiIndex, args...]() { megaApi[apiIndex]->removeBackup(args...); }); return mApi[apiIndex].lastError; } template int synchronousSendBackupHeartbeat(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_BACKUP_PUT_HEART_BEAT, [this, apiIndex, args...]() { megaApi[apiIndex]->sendBackupHeartbeat(args...); }); return mApi[apiIndex].lastError; } template int synchronousSetMyBackupsFolder(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SET_MY_BACKUPS, [this, apiIndex, args...]() { megaApi[apiIndex]->setMyBackupsFolder(args...); }); return mApi[apiIndex].lastError; } template int synchronousSetUserAlias(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_SET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->setUserAlias(args...); }); return mApi[apiIndex].lastError; } template int synchronousGetUserAlias(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_GET_ATTR_USER, [this, apiIndex, args...]() { megaApi[apiIndex]->getUserAlias(args...); }); return mApi[apiIndex].lastError; } template int synchronousFolderInfo(unsigned apiIndex, Args... args) { synchronousRequest(apiIndex, MegaRequest::TYPE_FOLDER_INFO, [this, apiIndex, args...]() { megaApi[apiIndex]->getFolderInfo(args...); }); return mApi[apiIndex].lastError; } // do not add functions using this pattern, see comment at top of this stanza // *** USE THESE ONES INSTEAD *** // convenience functions - make a request and wait for the result via listener, return the result code. To add new functions to call, just copy the line template std::unique_ptr asyncQueryAds(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->queryAds(args..., rt.get()); return rt; } template std::unique_ptr asyncFetchAds(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->fetchAds(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestLogin(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->login(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestFastLogin(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->fastLogin(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestFastLogin(int apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[static_cast(apiIndex)].get()); megaApi[static_cast(apiIndex)]->fastLogin(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestFastLogin(MegaApi* api, requestArgs... args) { auto rt = std::make_unique(api); api->fastLogin(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestLoginToFolder(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->loginToFolder(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestLoginToFolder(MegaApi* api, requestArgs... args) { auto rt = std::make_unique(api); api->loginToFolder(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestLocalLogout(MegaApi* api, requestArgs... args) { auto rt = std::make_unique(api); api->localLogout(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestFetchnodes(unsigned apiIndex, requestArgs... args) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->fetchNodes(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestFetchnodes(MegaApi* api, requestArgs... args) { auto rt = std::make_unique(api); api->fetchNodes(args..., rt.get()); return rt; } template std::unique_ptr asyncRequestGetVisibleWelcomeDialog(unsigned apiIndex) { auto rt = std::make_unique(megaApi[apiIndex].get()); megaApi[apiIndex]->getVisibleWelcomeDialog(rt.get()); return rt; } template std::unique_ptr asyncSetUnshareableNodeCoordinates(unsigned int apiIndex, requestArgs... args) { auto requestTracker{std::make_unique(megaApi[apiIndex].get())}; megaApi[apiIndex]->setUnshareableNodeCoordinates(args..., requestTracker.get()); return requestTracker; } template std::unique_ptr asyncGetBanners(unsigned int apiIndex, requestArgs... args) { auto requestTracker{std::make_unique(megaApi[apiIndex].get())}; megaApi[apiIndex]->getBanners(args..., requestTracker.get()); return requestTracker; } template int doGetDeviceName(unsigned apiIndex, string* dvc, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getDeviceName(args..., &rt); auto e = rt.waitForResult(); if (dvc && e == API_OK) *dvc = rt.request->getName(); return e; } template int doSetDeviceName(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setDeviceName(args..., &rt); return rt.waitForResult(); } template int doGetDriveName(unsigned apiIndex, string* drv, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getDriveName(args..., &rt); auto e = rt.waitForResult(); if (drv && e == API_OK) *drv = rt.request->getName(); return e; } template int doSetDriveName(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setDriveName(args..., &rt); return rt.waitForResult(); } template int doRequestLogout(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->logout(args..., &rt); return rt.waitForResult(); } template int doRequestLocalLogout(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->localLogout(args..., &rt); return rt.waitForResult(); } int doStartUpload(unsigned apiIndex, MegaHandle* newNodeHandleResult, const char* localPath, MegaNode* parent, const char* fileName, int64_t mtime, const char* appData, bool isSourceTemporary, bool startFirst, MegaCancelToken* cancelToken) { TransferTracker tt(megaApi[apiIndex].get()); MegaUploadOptions uploadOptions; if (fileName) { uploadOptions.fileName = fileName; } uploadOptions.mtime = mtime; uploadOptions.appData = appData; uploadOptions.isSourceTemporary = isSourceTemporary; uploadOptions.startFirst = startFirst; const std::string pathStr = localPath ? localPath : ""; megaApi[apiIndex]->startUpload(pathStr, parent, cancelToken, &uploadOptions, &tt); auto e = tt.waitForResult(); if (newNodeHandleResult) *newNodeHandleResult = tt.resultNodeHandle; return e; } std::tuple doStartUploadWithSpeed(unsigned apiIndex, MegaHandle* newNodeHandleResult, const char* localPath, MegaNode* parent, const char* fileName, int64_t mtime, const char* appData, bool isSourceTemporary, bool startFirst, MegaCancelToken* cancelToken) { TransferTracker tt(megaApi[apiIndex].get()); MegaUploadOptions uploadOptions; if (fileName) { uploadOptions.fileName = fileName; } uploadOptions.mtime = mtime; uploadOptions.appData = appData; uploadOptions.isSourceTemporary = isSourceTemporary; uploadOptions.startFirst = startFirst; const std::string pathStr = localPath ? localPath : ""; megaApi[apiIndex]->startUpload(pathStr, parent, cancelToken, &uploadOptions, &tt); auto e = tt.waitForResult(); if (newNodeHandleResult) *newNodeHandleResult = tt.resultNodeHandle; return {e, tt.mTransferSpeed, tt.mTransferMeanSpeed}; } template int doStartDownload(unsigned apiIndex, requestArgs... args) { TransferTracker tt(megaApi[apiIndex].get()); megaApi[apiIndex]->startDownload(args..., &tt); auto e = tt.waitForResult(); return e;} template int doSetFileVersionsOption(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setFileVersionsOption(args..., &rt); return rt.waitForResult(); } template int doRemoveVersion(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeVersion(args..., &rt); return rt.waitForResult(); } template int doRemoveVersions(unsigned apiIndex) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeVersions(&rt); return rt.waitForResult(); } template int doMoveNode(unsigned apiIndex, MegaHandle* movedNodeHandle, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->moveNode(args..., &rt); rt.waitForResult(); if (movedNodeHandle) *movedNodeHandle = rt.getNodeHandle(); return rt.result; } template int doCopyNode(unsigned apiIndex, MegaHandle* newNodeResult, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->copyNode(args..., &rt); rt.waitForResult(); if (newNodeResult) *newNodeResult = rt.getNodeHandle(); return rt.result; } template int doRenameNode(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->renameNode(args..., &rt); return rt.waitForResult(); } template int doDeleteNode(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->remove(args..., &rt); return rt.waitForResult(); } template int doGetThumbnail(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getThumbnail(args..., &rt); return rt.waitForResult(); } template int doGetPreview(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getPreview(args..., &rt); return rt.waitForResult(); } template int doGetThumbnailUploadURL(unsigned apiIndex, std::string& url, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getThumbnailUploadURL(args..., &rt); rt.waitForResult(); url = rt.request->getName(); return rt.result; } template int doGetPreviewUploadURL(unsigned apiIndex, std::string& url, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getPreviewUploadURL(args..., &rt); rt.waitForResult(); url = rt.request->getName(); return rt.result; } template int doPutThumbnail(unsigned apiIndex, MegaBackgroundMediaUpload* mbmu, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->putThumbnail(mbmu, args..., &rt); rt.waitForResult(); mbmu->setThumbnail(rt.getNodeHandle()); return rt.result; } template int doPutPreview(unsigned apiIndex, MegaBackgroundMediaUpload* mbmu, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->putPreview(mbmu, args..., &rt); rt.waitForResult(); mbmu->setPreview(rt.getNodeHandle()); return rt.result; } template int doAckUserAlerts(unsigned apiIndex) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->acknowledgeUserAlerts(&rt); rt.waitForResult(); return rt.result; } template int doOpenShareDialog(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->openShareDialog(args..., &rt); rt.waitForResult(); return rt.result; } template int synchronousDoUpgradeSecurity(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->upgradeSecurity(args..., &rt); rt.waitForResult(); return rt.result; } #ifdef ENABLE_SYNC template int synchronousSyncFolder(unsigned apiIndex, MegaHandle* newSyncRootNodeResult, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->syncFolder(args..., &rt); rt.waitForResult(); mApi[apiIndex].lastSyncError = rt.request ? rt.request->getNumDetails() : -888; // request was not set ??? mApi[apiIndex].lastSyncBackupId = rt.request ? rt.request->getParentHandle() : UNDEF; if (newSyncRootNodeResult) *newSyncRootNodeResult = rt.getNodeHandle(); return rt.result; } template int synchronousRemoveSync(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeSync(args..., &rt); return rt.waitForResult(); } template int synchronousRemoveBackupNodes(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->moveOrRemoveDeconfiguredBackupNodes(args..., INVALID_HANDLE, &rt); return rt.waitForResult(); } template int synchronousSetSyncRunState(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setSyncRunState(args..., &rt); rt.waitForResult(); mApi[apiIndex].lastSyncError = rt.request->getNumDetails(); return rt.result; } #endif // ENABLE_SYNC template int synchronousKillSession(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->killSession(args..., &rt); return rt.waitForResult(); } template int synchronousSetBackup(unsigned apiIndex, OnReqFinish f, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get(), f); megaApi[apiIndex]->setBackup(args..., &rt); return rt.waitForResult(); } template int doExportNode(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->exportNode(args..., &rt); return rt.waitForResult(); } template int doDisableExport(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->disableExport(args..., &rt); return rt.waitForResult(); } template int synchronousSetNodeFavourite(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setNodeFavourite(args..., &rt); return rt.waitForResult(); } template int synchronousSetNodeLabel(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setNodeLabel(args..., &rt); return rt.waitForResult(); } template int synchronousResetNodeLabel(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->resetNodeLabel(args..., &rt); return rt.waitForResult(); } template int synchronousGetFavourites(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getFavourites(args..., &rt); return rt.waitForResult(); } template int synchronousSetNodeSensitive(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setNodeSensitive(args..., &rt); return rt.waitForResult(); } template int synchronousInviteContact(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->inviteContact(args..., &rt); return rt.waitForResult(); } template int synchronousReplyContactRequest(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->replyContactRequest(args..., &rt); return rt.waitForResult(); } template int synchronousVerifyCredentials(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->verifyCredentials(args..., &rt); return rt.waitForResult(); } template int synchronousResetCredentials(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->resetCredentials(args..., &rt); return rt.waitForResult(); } template int synchronousRemove(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->remove(args..., &rt); return rt.waitForResult(); } template int doCreateSet(unsigned apiIndex, MegaSet** s, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->createSet(args..., &rt); rt.waitForResult(); if (s && rt.request->getMegaSet()) *s = rt.request->getMegaSet()->copy(); return rt.result; } template int doUpdateSetName(unsigned apiIndex, MegaHandle* id, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->updateSetName(args..., &rt); rt.waitForResult(); if (id) *id = rt.request->getParentHandle(); return rt.result; } template int doPutSetCover(unsigned apiIndex, MegaHandle* id, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->putSetCover(args..., &rt); rt.waitForResult(); if (id) *id = rt.request->getParentHandle(); return rt.result; } template int doRemoveSet(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeSet(args..., &rt); rt.waitForResult(); return rt.result; } template int doCreateBulkSetElements(unsigned apiIndex, MegaSetElementList** els, MegaIntegerList** errs, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->createSetElements(args..., &rt); rt.waitForResult(); if (els && rt.request->getMegaSetElementList()) *els = rt.request->getMegaSetElementList()->copy(); if (errs && rt.request->getMegaIntegerList()) *errs = rt.request->getMegaIntegerList()->copy(); return rt.result; } template int doCreateSetElement(unsigned apiIndex, MegaSetElementList** ell, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->createSetElement(args..., &rt); rt.waitForResult(); if (ell && rt.request->getMegaSetElementList()) *ell = rt.request->getMegaSetElementList()->copy(); return rt.result; } template int doUpdateSetElementName(unsigned apiIndex, MegaHandle* eid, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->updateSetElementName(args..., &rt); rt.waitForResult(); if (eid) *eid = rt.request->getParentHandle(); return rt.result; } template int doUpdateSetElementOrder(unsigned apiIndex, MegaHandle* eid, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->updateSetElementOrder(args..., &rt); rt.waitForResult(); if (eid) *eid = rt.request->getParentHandle(); return rt.result; } template int doRemoveBulkSetElements(unsigned apiIndex, MegaIntegerList** errs, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeSetElements(args..., &rt); rt.waitForResult(); if (errs && rt.request->getMegaIntegerList()) *errs = rt.request->getMegaIntegerList()->copy(); return rt.result; } template int doRemoveSetElement(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeSetElement(args..., &rt); rt.waitForResult(); return rt.result; } template int doExportSet(unsigned apiIndex, MegaSet** s, string& url, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->exportSet(args..., &rt); rt.waitForResult(); if (rt.result == API_OK) { if (s) *s = rt.request->getMegaSet()->copy(); if (rt.request->getLink()) url.assign(rt.request->getLink()); } return rt.result; } template int doDisableExportSet(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->disableExportSet(args..., &rt); rt.waitForResult(); return rt.result; } template int doFetchPublicSet(unsigned apiIndex, MegaSet** s, MegaSetElementList** els, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->fetchPublicSet(args..., &rt); rt.waitForResult(); if (rt.result == API_OK) { if (s) *s = rt.request->getMegaSet()->copy(); if (els) *els = rt.request->getMegaSetElementList()->copy(); } return rt.result; } template int doGetPreviewElementNode(unsigned apiIndex, MegaNode** n, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getPreviewElementNode(args..., &rt); rt.waitForResult(); if (n && rt.result == API_OK) *n = rt.request->getPublicMegaNode(); // ownership received (it's a copy) return rt.result; } template int synchronousCancelTransfers(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->cancelTransfers(args..., &rt); return rt.waitForResult(); } template int synchronousSetUserAttribute(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setUserAttribute(args..., &rt); return rt.waitForResult(); } template int synchronousSetAvatar(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setAvatar(args..., &rt); return rt.waitForResult(); } template int synchronousRemoveContact(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->removeContact(args..., &rt); return rt.waitForResult(); } template int synchronousShare(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->share(args..., &rt); return rt.waitForResult(); } template int synchronousResetPassword(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->resetPassword(args..., &rt); return rt.waitForResult(); } template int synchronousConfirmResetPassword(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->confirmResetPassword(args..., &rt); return rt.waitForResult(); } template int synchronousGetRecommendedProLevel(unsigned apiIndex, int& recommendedLevel, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getRecommendedProLevel(args..., &rt); int err = rt.waitForResult(); if (err == API_OK) recommendedLevel = (int)rt.request->getNumber(); return err; } template int synchronousChangeEmail(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->changeEmail(args..., &rt); return rt.waitForResult(); } template int synchronousConfirmChangeEmail(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->confirmChangeEmail(args..., &rt); return rt.waitForResult(); } #ifdef ENABLE_SYNC template int syncMoveToDebris(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->moveToDebris(args..., &rt); return rt.waitForResult(); } #endif // ENABLE_SYNC template int synchronousSetVisibleWelcomeDialog(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setVisibleWelcomeDialog(args..., &rt); return rt.waitForResult(); } template int synchronousCreateNodeTree(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->createNodeTree(args..., &rt); return rt.waitForResult(); } // Checkup methods called from MegaApi callbacks void onNodesUpdateCheck(size_t apiIndex, MegaHandle target, MegaNodeList* nodes, int change, bool& flag); bool createFile(string filename, bool largeFile = true, string content = "test "); int64_t getFilesize(string filename); void deleteFile(string filename); void deleteFolder(string foldername); // peform fetchnodes for all test involved accounts sequentially to avoid API locks void fetchNodesForAccountsSequentially(const unsigned howMany); void getAccountsForTest(const unsigned howMany = 1, const bool fetchNodes = true, const int clientType = MegaApi::CLIENT_TYPE_DEFAULT, const std::string& apiServer = std::string{}); void configureTestInstance(unsigned index, const std::string& email, const std::string& pass, bool checkCredentials = true, const int clientType = MegaApi::CLIENT_TYPE_DEFAULT); void releaseMegaApi(unsigned int apiIndex); // void loginSameAccountsForTest(unsigned copyIndex); void inviteTestAccount(const unsigned invitorIndex, const unsigned inviteIndex, const string &message); void inviteContact(unsigned apiIndex, const string &email, const string& message, const int action); void replyContact(MegaContactRequest* cr, int action, const unsigned apiIndex = 1); int removeContact(unsigned apiIndex, string email); void getUserAttribute(MegaUser *u, int type, int timeout = maxTimeout, int accountIndex = 1); void verifyCredentials(unsigned apiIndex, string email); void resetCredentials(unsigned apiIndex, string email); bool areCredentialsVerified(unsigned apiIndex, string email); void shareFolder(MegaNode* n, const char* email, int action, unsigned apiIndex = 0); #ifdef ENABLE_CHAT void createChatScheduledMeeting(const unsigned apiIndex, MegaHandle& chatid); void updateScheduledMeeting(const unsigned apiIndex, MegaHandle& chatid); #endif string createPublicLink(unsigned apiIndex, MegaNode *n, m_time_t expireDate, int timeout, bool isFreeAccount, bool writable = false, bool megaHosted = false); MegaHandle importPublicLink(unsigned apiIndex, string link, MegaNode *parent); unique_ptr getPublicNode(unsigned apiIndex, string link); MegaHandle removePublicLink(unsigned apiIndex, MegaNode *n); void getContactRequest(unsigned int apiIndex, bool outgoing, int expectedSize = 1); std::pair createRemoteFolder(const unsigned int apiIndex, const char* name, MegaNode* parent, const int timeout = maxTimeout); MegaHandle createFolder(unsigned int apiIndex, const char *name, MegaNode *parent, int timeout = maxTimeout); // SMS verification was deprecated. This function should be removed in the future, // along with the rest of the code dealing with the deprecated functionality. // void getCountryCallingCodes(int timeout = maxTimeout); void explorePath(int account, MegaNode* node, int& files, int& folders); void synchronousMediaUpload(unsigned int apiIndex, int64_t fileSize, const char* filename, const char* fileEncrypted, const char* fileOutput, const char* fileThumbnail = nullptr, const char* filePreview = nullptr); void synchronousMediaUploadIncomplete(unsigned int apiIndex, int64_t fileSize, const char* filename, const char* fileEncrypted, std::string& fingerprint, std::string& string64UploadToken, std::string& string64FileKey); #ifdef ENABLE_CHAT void createChat(bool group, MegaTextChatPeerList* peers, bool isPublicChat = false, int timeout = maxTimeout); /** * @brief Creates a chat room from the mApi[creatorIndex] account waiting for all the events to * finish before returning. It uses EXPECT in the implementation to check everything finished * properly and print error messages in case something is wrong. This means you don't need to * call this method with ASSERT_NO_FATAL_FAILURE but you need to check that te return value is * not equal to INVALID_HANDLE. * * @param creatorIndex The index of the account to call the creatChat method from * @param invitedIndices A vector with the indices of the accounts that will be invited to the * chat. creatorIndex should not be inside the vector. * @param group If true a group chat room is created, else a 1on1 * @param timeout_sec The max time to wait for each response in seconds. 10 minutes by default * @return The chatId of the created chat room. INVALID_HANDLE if something went wrong. */ MegaHandle createChatWithChecks(const unsigned int creatorIndex, const std::vector& invitedIndices, const bool group, const bool isPublicChat, const unsigned int timeout_sec = maxTimeout); MegaHandle getCommander(); void testChat(bool isPublicChat); void testGiveRemoveChatAccess(bool isPublicChat); #endif template ErrorCodes doSetMaxConnections(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->setMaxConnections(args..., &rt); return rt.waitForResult(); } template ErrorCodes doGetMaxUploadConnections(const unsigned apiIndex, int& direction, int& maxConnections, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getMaxUploadConnections(args..., &rt); const auto err = rt.waitForResult(); direction = err == API_OK ? rt.getParamType() : -999; maxConnections = err == API_OK ? rt.getNumber() : -999; return err; } template ErrorCodes doGetMaxDownloadConnections(const unsigned apiIndex, int& direction, int& maxConnections, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getMaxUploadConnections(args..., &rt); const auto err = rt.waitForResult(); direction = err == API_OK ? rt.getParamType() : -999; maxConnections = err == API_OK ? rt.getNumber() : -999; return err; } /* MegaVpnCredentials */ template int doGetVpnRegions(unsigned apiIndex, unique_ptr& vpnRegions, unique_ptr& vpnRegionsDetailed, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getVpnRegions(args..., &rt); auto e = rt.waitForResult(); auto vpnRegionsFromRequest = rt.request->getMegaStringList() ? rt.request->getMegaStringList()->copy() : nullptr; vpnRegions.reset(vpnRegionsFromRequest); vpnRegionsDetailed.reset(rt.request->getMegaVpnRegionsDetailed() ? rt.request->getMegaVpnRegionsDetailed()->copy() : nullptr); return e; } template int doGetVpnCredentials(unsigned apiIndex, unique_ptr& vpnCredentials, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->getVpnCredentials(args..., &rt); auto e = rt.waitForResult(); auto vpnCredentialsFromRequest = rt.request->getMegaVpnCredentials() ? rt.request->getMegaVpnCredentials()->copy() : nullptr; vpnCredentials.reset(vpnCredentialsFromRequest); return e; } template int doPutVpnCredential(unsigned apiIndex, int& slotID, std::string& userPubKey, std::string& newCredential, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->putVpnCredential(args..., &rt); auto e = rt.waitForResult(); slotID = static_cast(rt.request->getNumber()); userPubKey = rt.request->getPassword() ? rt.request->getPassword() : ""; // User Public Key used to register the VPN credentials newCredential = rt.request->getSessionKey() ? rt.request->getSessionKey() : ""; // Credential string for conf file return e; } template int doDelVpnCredential(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->delVpnCredential(args..., &rt); return rt.waitForResult(); } template int doCheckVpnCredential(unsigned apiIndex, requestArgs... args) { RequestTracker rt(megaApi[apiIndex].get()); megaApi[apiIndex]->checkVpnCredential(args..., &rt); return rt.waitForResult(); } /* MegaVpnCredentials END */ template int setThumbnail(MegaApi& client, Arguments... arguments) { RequestTracker tracker(&client); client.setThumbnail(arguments..., &tracker); return tracker.waitForResult(); } // Checks if a function is a valid invitation list accessor function. template static constexpr auto IsInvitationListAccessorV = std::is_invocable_r_v; // Checks if a function is a valid invitation predicate function. template static constexpr auto IsInvitationPredicateV = std::is_invocable_r_v; /** * Accept the specified contact invitation. * * @param client * The client that should accept the contact request. * * @param invitation * The invitation the client should accept. * * @return * API_OK if client could accept the invitation. */ Error acceptInvitation(MegaApi& client, const MegaContactRequest& invitation); /* * Establish a friendship between two clients. * * @return * API_OK if the friendship could be established. */ Error befriend(MegaApi& client0, MegaApi& client1); /** * Search a list of invitations for an invitation satisfying some predicate. * * @param invitations * The list of invitations to search. * * @param invitationOK * The predicate an invitation must satisfy to be returned. * * @return * The first invitation satisfying predicate, if any. */ template auto findInvitation(const MegaContactRequestList& invitations, InvitationPredicate invitationOK) -> std::enable_if_t, std::unique_ptr> { // Try and find an invitation satisfying our predicate. for (auto i = 0, j = invitations.size(); i < j; ++i) { // Found a suitable invitation! if (auto* invitation = invitations.get(i); std::invoke(invitationOK, *invitation)) { return makeUniqueFrom(invitation->copy()); } } // Couldn't find a suitable invitation. return nullptr; } /** * Search a list of invitations for an invitation satisfying some predicate. * * @param getInvitations * An accessor specifying which invitation list we want to search. * * @param invitationOK * The predicate an invitation must satisfy to be returned. * * @return * The first invitation satisfying predicate, if any. */ template auto findInvitation(MegaApi& client, InvitationListAccessor getInvitations, InvitationPredicate invitationOK) -> std::enable_if_t && IsInvitationPredicateV, std::unique_ptr> { // Get invitation list. auto* invitations = std::invoke(getInvitations, &client); // No invitations. if (!invitations || invitations->size() == 0) { return nullptr; } // Try and find an invitation satisfying our predicate. return findInvitation(*invitations, std::move(invitationOK)); } /** * Check if email is a contact. * * @param client * The client (user) whose contacts we will search. * * @param email * The contact we are searching for. */ auto hasContact(MegaApi& client, const std::string& email) -> std::unique_ptr; /** * Check if a client has received a contact invite from some user. * * @apram client * The client that should've received an invitation. * * @param email * The user that should've sent us the invitation. * * @return * A matching invitation, if found. */ auto hasReceivedInvitationFrom(MegaApi& client, const std::string& email) -> std::unique_ptr; /* * Check if this client has sent a contact invite to some user. * * @param client * The client that should've sent the invitation. * * @param email * The user that we've sent the invitation to. * * @return * A matching invitation, if found. */ auto hasSentInvitationTo(MegaApi& client, const std::string& email) -> std::unique_ptr; /** * Remove a contact. * * @param client * The client that wants to remove this contact. * * @param email * The contact we want to remove. * * @returns * API_OK if the contact was removed. */ Error removeContact(MegaApi& client, const std::string& email); /** * Break a contact relationship between two clients. * * @return * API_ENOENT if no contact relationship exists. * API_OK if the contact relationship was successfully broken. */ Error removeContact(MegaApi& client0, MegaApi& client1); /** * Send a contact invitation from one user to another. * * @param client * The client that should send the invitation. * * @param email * The user that we are inviting to be a contact. * * @return * API_OK if the invitation was sent successfully. */ Error sendInvitationTo(MegaApi& client, const std::string& email); /** * Send a contact invitation from one client to another. * * @param client0 * The client that will be sending the invitation. * * @param client1 * The client that will be receiving the invitation. * * @return * {invitation, API_OK} on success. * {nullptr, ?} on failure. */ using SendInvitationToResult = std::pair, Error>; auto sendInvitationTo(MegaApi& client0, MegaApi& client1) -> SendInvitationToResult; /** * @brief Returns the current tests suite and case name. * @example calling this inside `TEST_F(SdkTest, Foo)` returns `std::pair {"SdkTest", "Foo"}` */ std::pair getTestSuiteAndName() const; /** * @brief Returns an identifier of the current test. It can be used to prefix log messages. * @example calling this inside `TEST_F(SdkTest, Foo)` returns `"SdkTest.Foo : "` */ std::string getLogPrefix() const; /** * @brief Gets a file name prefix unique for the test case * @example calling this inside `TEST_F(SdkTest, Foo)` returns `"SdkTest_Foo_"` */ std::string getFilePrefix() const; /** * @brief Set value for ^!devopt user attribute * * Format is a json, per example: * {"utqamo":1} * * @param value * @param index MegaApi instance index */ void setDevOptUserAttribute(const std::string& value, uint32_t index) const; using GetDevOptAttrResult = std::pair>; /** * @brief Get value for ^!devopt user attribute * @param index MegaApi instance index * @return {error, attribute value} */ GetDevOptAttrResult getDevOptUserAttribute(uint32_t index) const; }; /** * @brief * Return an object that will restore client's plan on destruction. * * @param client * The client whose plan we want to restore. * * @return * An object that will restore client's plan on destruction. */ auto accountLevelRestorer(MegaApi& client) -> ScopedDestructor; /** * @brief * Return an object that will restore client's plan on destruction. * * @param clients * Vector of pointers to valid/active MegaApi instances to perform the restoration * * @param idx * Index of the MegaApi instance to use at restoration execution * * @return * An object that will restore client's plan on destruction. */ ScopedDestructor accountLevelRestorer(std::vector& clients, unsigned int idx); /** * @brief * Create a directory with a given name under a specified parent. * * @param client * The client who should create the directory. * * @param parent * The directory where our new directory will live. * * @param name * The name of the directory we are creating. * * @return * An Error if the directory couldn't be created. * An std::unique_ptr if the directory was created. */ auto createDirectory(MegaApi& client, const MegaNode& parent, const std::string& name) -> Expected>; /** * @brief * Elevate client to a pro pricing plan. * * @param client * The client who we want to elevate to a pro pricing plan. * * @return * An account level restorer on success. * An error on failure. */ auto scopedToPro(MegaApi& client) -> Expected; /** * @brief * Demote client to a free plan. * * @param client * The client who we want to demote to a free plan. * * @return * An error on failure or succuss. */ auto demoteToFree(MegaApi& client) -> Error; /** * @brief * Export the specified node. * * @param client * The client that should export the node. * * @param node * The node we want to export. * * @param expirationDate * When should the node's resulting public link expire? * * @return * A string (the node's public link) if the node could be exported * An error if the node couldn't be exported. */ auto exportNode(MegaApi& client, const MegaNode& node, std::optional expirationDate = std::nullopt) -> Expected; /** * @brief * Retrieve a client's account details. * * @param client * The client whose account details we want to retrieve. * * @return * A pointer to an account details instance on success. * An error on failure. */ auto getAccountDetails(MegaApi& client) -> Expected>; /** * Describes a client's current account level. */ struct AccountLevel { AccountLevel(int months, int plan): months(months), plan(plan) {} // How long the client's current pricing is active. int months; // The client's current pricing plan. int plan; }; // AccountLevel /** * @brief * Retrieve a client's current account level. * * @param client * The client whose account level we want to retrieve. * * @return * An account level object instance on success. * An error on failure. */ auto getAccountLevel(MegaApi& client) -> Expected; /** * @brief * Retrieve available pricing plans. * * @param client * The client who should request the available pricing plans. * * @return * A pointer to a pricing object instance on success. * An error on failure. */ auto getPricing(MegaApi& client) -> Expected>; /** * Import a node into this account via public link. * * @param client * The client who is importing the node. * * @param link * The public link of the node we want to import. * * @param parent * Where the node should be imported. * * @return * An Error if the node could not be imported. * An std::unique_ptr if the node was imported. */ auto importNode(MegaApi& client, const std::string& link, const MegaNode& parent) -> Expected>; /** * @brief * Set a client's account level. * * @param client * The client whose account level we want to set. * * @param args * Arguments suitable for MegaApi::sendSetAccountLevelDevCommand(...). * * @return * API_OK on success. */ template Error setAccountLevel(MegaApi& client, requestArgs... args) { // So we can wait for the client's result. RequestTracker tracker(&client); // Try and set the user's account level. client.sendSetAccountLevelDevCommand(args..., &tracker); // Return client's result to caller. return tracker.waitForResult(); } #endif // SDKTEST_TEST_H sdk-10.11.0/tests/integration/Sync_test.cpp000066400000000000000000023506221516266226600206250ustar00rootroot00000000000000/** * @file tests/synctests.cpp * @brief Mega SDK test file * * (c) 2018 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ // Many of these tests are still being worked on. #include "env_var_accounts.h" #include "gmock/gmock.h" #include "gtest_common.h" #include "mega/scoped_helpers.h" #include "mega/syncinternals/syncuploadthrottlingmanager.h" #include "mega/testhooks.h" #include "mega/tlv.h" #include "mega/user_attribute.h" #include "test.h" #define DEFAULTWAIT std::chrono::seconds(20) using namespace ::mega; using namespace ::std; // we are on SRW branch #define SRW_NEEDED_FOR_THIS_ONE #ifdef ENABLE_SYNC template shared_promise makeSharedPromise() { return shared_promise(new promise()); } // Convenience. auto makeScopedStallPredicateResetter(StandardClient& client) { return makeScopedDestructor( [&client]() { client.setHasImmediateStall(nullptr); client.setIsImmediateStall(nullptr); }); } auto makeScopedSyncPauser(StandardClient& client, handle id) { auto result = client.setSyncPausedByBackupId(id, true); EXPECT_TRUE(result) << "Couldn't pause sync " << toHandle(id); return makeScopedDestructor( [&client, id]() { auto result = client.setSyncPausedByBackupId(id, false); EXPECT_TRUE(result) << "Couldn't resume sync " << toHandle(id); }); } bool suppressfiles = false; // dgw: TODO: Perhaps make this a runtime parameter? #define SCAN_INTERVAL_SEC 3600 bool adjustLastModificationTime(const fs::path& path, int adjustment) { using std::chrono::seconds; std::error_code ec; // Retrieve the file's current modification time. auto current = fs::last_write_time(path, ec); // Bail if we couldn't retrieve the time. if (ec) return false; // Update the modification time. fs::last_write_time(path, current + seconds(adjustment), ec); // Let the caller know whether we succeeded. return !ec; } // Creates a temporary directory in the current path fs::path makeTmpDir(const int maxTries = 1000) { const auto cwd = fs::current_path(); std::random_device dev; std::mt19937 prng{dev()}; std::uniform_int_distribution rand{0}; fs::path path; for (int i = 0;; ++i) { std::ostringstream os; os << std::hex << rand(prng); path = cwd / os.str(); if (fs::create_directory(path)) { break; } if (i == maxTries) { throw std::runtime_error{"Couldn't create tmp dir"}; } } return path; } // Copies a file while maintaining the write time. void copyFile(const fs::path& source, const fs::path& target) { assert(fs::is_regular_file(source)); const auto tmpDir = makeTmpDir(); const auto tmpFile = tmpDir / "copied_file"; fs::copy_file(source, tmpFile); fs::last_write_time(tmpFile, fs::last_write_time(source)); fs::rename(tmpFile, target); fs::remove(tmpDir); } string leafname(const string& p) { auto n = p.find_last_of("/"); return n == string::npos ? p : p.substr(n+1); } string parentpath(const string& p) { auto n = p.find_last_of("/"); return n == string::npos ? "" : p.substr(0, n-1); } struct StandardClient; bool CatchupClients(StandardClient* c1, StandardClient* c2 = nullptr, StandardClient* c3 = nullptr); #ifdef _WIN32 bool createFile(const fs::path& path, const void* data, const size_t data_length) { // Convenience. auto create = [&](DWORD disposition, DWORD flags) { return CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, disposition, flags, nullptr); }; // create auto invalid = INVALID_HANDLE_VALUE; // Try and truncate any existing file. auto handle = create(TRUNCATE_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT); // File may not exist. if (handle == invalid) { auto error = GetLastError(); // File exists but we couldn't truncate it. if (error != ERROR_FILE_NOT_FOUND) { LOG_debug << "Unable to truncate data file: " << path_u8string(path) << ". Error was: " << error; return false; } // Try creating the file directly. handle = create(CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL); // Couldn't create the file. if (handle == invalid) { // Latch error. error = GetLastError(); // Let everyone know why we failed. LOG_debug << "Unable to create data file: " << path_u8string(path) << ". Error was: " << error; // Let caller know we failed. return false; } } // Convenience. auto* m = static_cast(data); auto* n = m + data_length; // Try and write the file's data to disk. while (m != n) { auto remaining = static_cast(n - m); auto written = 0ul; // Couldn't write the file's data to disk. if (!WriteFile(handle, m, remaining, &written, nullptr)) { // Latch error. auto error = GetLastError(); // Let debuggers know why we failed. LOG_debug << "Unable to write data to file: " << path_u8string(path) << ". Error was: " << error; // Close handle and return failure to caller. return CloseHandle(handle), false; } // Move buffer position forward. m += written; } // Try and flush changes to disk. auto result = FlushFileBuffers(handle); // Couldn't flush changes to disk. if (!result) { // Latch error. auto error = GetLastError(); // Let debuggers know why we failed. LOG_debug << "Couldn't flush file to disk: " << path_u8string(path) << ". Error was: " << error; } // Release handle. CloseHandle(handle); // Return result to caller. return result; } #else // _WIN32 bool createFile(const fs::path &path, const void *data, const size_t data_length) { ofstream ostream(path, ios::binary); LOG_verbose << "Creating local data file at " << path_u8string(path) << ", length " << data_length; ostream.write(reinterpret_cast(data), static_cast(data_length)); return ostream.good(); } #endif // ! _WIN32 bool createFile(const fs::path &path, const std::string &data) { return createFile(path, data.data(), data.size()); } bool createFile(const fs::path& path, const std::string& data, std::chrono::seconds delta) { if (!createFile(path, data)) return false; std::error_code result; auto current = fs::last_write_time(path, result); if (result) return false; fs::last_write_time(path, current + delta, result); return !result; } std::string randomData(const std::size_t length) { std::vector data(length); std::generate_n(data.begin(), data.size(), [](){ return (uint8_t)std::rand(); }); return std::string((const char*)data.data(), data.size()); } Model::ModelNode::ModelNode(const ModelNode& other) : type(other.type) , mCloudName() , mFsName() , name(other.name) , content(other.content) , kids() , parent() , changed(other.changed) { for (auto& child : other.kids) { addkid(child->clone()); } } Model::ModelNode& Model::ModelNode::fsName(const string& name) { return mFsName = name, *this; } const string& Model::ModelNode::fsName() const { return mFsName.empty() ? name : mFsName; } Model::ModelNode& Model::ModelNode::cloudName(const string& name) { return mCloudName = name, *this; } const string& Model::ModelNode::cloudName() const { return mCloudName.empty() ? name : mCloudName; } void Model::ModelNode::generate(const fs::path& path, bool force) { const fs::path ourPath = path / fsName(); if (type == file) { if (changed || force) { ASSERT_TRUE(createFile(ourPath, content)) << "Couldn't generate model file: " << path_u8string(ourPath); changed = false; } } else { fs::create_directory(ourPath); for (auto& child : kids) { child->generate(ourPath, force); } } } string Model::ModelNode::path() const { string s; for (auto p = this; p; p = p->parent) s = "/" + p->name + s; return s; } string Model::ModelNode::fsPath() const { string s; for (auto p = this; p; p = p->parent) s = "/" + p->fsName() + s; return s; } Model::ModelNode* Model::ModelNode::addkid() { return addkid(std::make_unique()); } Model::ModelNode* Model::ModelNode::addkid(unique_ptr&& p) { p->parent = this; kids.emplace_back(std::move(p)); return kids.back().get(); } bool Model::ModelNode::typematchesnodetype(nodetype_t nodetype) const { switch (type) { case file: return nodetype == FILENODE; case folder: return nodetype == FOLDERNODE; } return false; } void Model::ModelNode::print(string prefix) { out() << prefix << name; prefix.append(name).append("/"); for (const auto &in: kids) { in->print(prefix); } } std::unique_ptr Model::ModelNode::clone() { return std::make_unique(*this); } Model::Model() : root(makeModelSubfolder("root")) { } Model::Model(const Model& other) : root(other.root->clone()) { } Model& Model::operator=(const Model& rhs) { Model temp(rhs); swap(temp); return *this; } Model::ModelNode* Model::addfile(const string& path, const string& content) { auto* node = addnode(path, ModelNode::file); node->content = content; node->changed = true; return node; } Model::ModelNode* Model::addfile(const string& path) { return addfile(path, path); } Model::ModelNode* Model::addfolder(const string& path) { return addnode(path, ModelNode::folder); } Model::ModelNode* Model::addnode(const string& path, ModelNode::nodetype type) { ModelNode* child; ModelNode* node = root.get(); string name; size_t current = 0; size_t end = path.size(); while (current < end) { size_t delimiter = path.find('/', current); if (delimiter == path.npos) { break; } name = path.substr(current, delimiter - current); if (!(child = childnodebyname(node, name))) { child = node->addkid(); child->name = name; child->type = ModelNode::folder; } assert(child->type == ModelNode::folder); current = delimiter + 1; node = child; } assert(current < end); name = path.substr(current); if (!(child = childnodebyname(node, name))) { child = node->addkid(); child->name = name; child->type = type; } assert(child->type == type); return child; } Model::ModelNode* Model::copynode(const string& src, const string& dst) { const ModelNode* source = findnode(src); ModelNode* destination = addnode(dst, source->type); destination->content = source->content; destination->kids.clear(); for (auto& child : source->kids) { destination->addkid(child->clone()); } return destination; } unique_ptr Model::makeModelSubfolder(const string& utf8Name) { unique_ptr n(new ModelNode); n->name = utf8Name; return n; } unique_ptr Model::makeModelSubfile(const string& utf8Name, string content) { unique_ptr n(new ModelNode); n->name = utf8Name; n->type = ModelNode::file; n->content = content.empty() ? utf8Name : std::move(content); return n; } unique_ptr Model::buildModelSubdirs(const string& prefix, int n, int recurselevel, int filesperdir) { if (suppressfiles) filesperdir = 0; unique_ptr nn = makeModelSubfolder(prefix); for (int i = 0; i < filesperdir; ++i) { nn->addkid(makeModelSubfile("file" + to_string(i) + "_" + prefix)); } if (recurselevel > 0) { for (int i = 0; i < n; ++i) { unique_ptr sn = buildModelSubdirs(prefix + "_" + to_string(i), n, recurselevel - 1, filesperdir); sn->parent = nn.get(); nn->addkid(std::move(sn)); } } return nn; } Model::ModelNode* Model::childnodebyname(ModelNode* n, const std::string& s) { for (auto& m : n->kids) { if (m->name == s) { return m.get(); } } return nullptr; } Model::ModelNode* Model::findnode(string path, ModelNode* startnode) { ModelNode* n = startnode ? startnode : root.get(); while (n && !path.empty()) { auto pos = path.find("/"); n = childnodebyname(n, path.substr(0, pos)); path.erase(0, pos == string::npos ? path.size() : pos + 1); } return n; } unique_ptr Model::removenode(const string& path) { ModelNode* n = findnode(path); if (n && n->parent) { unique_ptr extracted; ModelNode* parent = n->parent; auto newend = std::remove_if(parent->kids.begin(), parent->kids.end(), [&extracted, n](unique_ptr& v) { if (v.get() == n) return extracted = std::move(v), true; else return false; }); parent->kids.erase(newend, parent->kids.end()); return extracted; } return nullptr; } bool Model::movenode(const string& sourcepath, const string& destpath) { ModelNode* source = findnode(sourcepath); ModelNode* dest = findnode(destpath); if (source && source && source->parent && dest) { auto replaced_node = removenode(destpath + "/" + source->name); unique_ptr n; ModelNode* parent = source->parent; auto newend = std::remove_if(parent->kids.begin(), parent->kids.end(), [&n, source](unique_ptr& v) { if (v.get() == source) return n = std::move(v), true; else return false; }); parent->kids.erase(newend, parent->kids.end()); if (n) { dest->addkid(std::move(n)); return true; } } return false; } bool Model::movetosynctrash(unique_ptr&& node, const string& syncrootpath) { ModelNode* syncroot; if (!(syncroot = findnode(syncrootpath))) { return false; } ModelNode* trash; if (!(trash = childnodebyname(syncroot, DEBRISFOLDER))) { auto uniqueptr = makeModelSubfolder(DEBRISFOLDER); trash = uniqueptr.get(); syncroot->addkid(std::move(uniqueptr)); } char today[50]; auto rawtime = time(NULL); strftime(today, sizeof today, "%F", localtime(&rawtime)); ModelNode* dayfolder; if (!(dayfolder = findnode(today, trash))) { auto uniqueptr = makeModelSubfolder(today); dayfolder = uniqueptr.get(); trash->addkid(std::move(uniqueptr)); } dayfolder->addkid(std::move(node)); return true; } bool Model::movetosynctrash(const string& path, const string& syncrootpath) { if (auto node = removenode(path)) return movetosynctrash(std::move(node), syncrootpath); return false; } void Model::ensureLocalDebrisTmpLock(const string& syncrootpath) { // if we've downloaded a file then it's put in debris/tmp initially, and there is a lock file if (ModelNode* syncroot = findnode(syncrootpath)) { ModelNode* trash; if (!(trash = childnodebyname(syncroot, DEBRISFOLDER))) { auto uniqueptr = makeModelSubfolder(DEBRISFOLDER); trash = uniqueptr.get(); trash->fsOnly = true; syncroot->addkid(std::move(uniqueptr)); } ModelNode* tmpfolder; if (!(tmpfolder = findnode("tmp", trash))) { auto uniqueptr = makeModelSubfolder("tmp"); tmpfolder = uniqueptr.get(); trash->addkid(std::move(uniqueptr)); } ModelNode* lockfile; if (!(lockfile = findnode("lock", tmpfolder))) { tmpfolder->addkid(makeModelSubfile("lock")); } } } bool Model::removesynctrash(const string& syncrootpath, const string& subpath) { if (subpath.empty()) { return removenode(syncrootpath + "/" + DEBRISFOLDER) != nullptr; } else { char today[50]; auto rawtime = time(NULL); strftime(today, sizeof today, "%F", localtime(&rawtime)); return removenode(syncrootpath + "/" + DEBRISFOLDER + "/" + today + "/" + subpath) != nullptr; } } void Model::emulate_rename(std::string nodepath, std::string newname) { auto node = findnode(nodepath); ASSERT_TRUE(!!node); if (node) node->name = newname; } void Model::emulate_move(std::string nodepath, std::string newparentpath) { auto removed = removenode(newparentpath + "/" + leafname(nodepath)); ASSERT_TRUE(movenode(nodepath, newparentpath)); } void Model::emulate_copy(std::string nodepath, std::string newparentpath) { auto node = findnode(nodepath); auto newparent = findnode(newparentpath); ASSERT_TRUE(!!node); ASSERT_TRUE(!!newparent); newparent->addkid(node->clone()); } void Model::emulate_rename_copy(std::string nodepath, std::string newparentpath, std::string newname) { auto node = findnode(nodepath); auto newparent = findnode(newparentpath); ASSERT_TRUE(!!node); ASSERT_TRUE(!!newparent); auto newnode = node->clone(); newnode->name = newname; newparent->addkid(std::move(newnode)); } void Model::emulate_delete(std::string nodepath) { auto removed = removenode(nodepath); // ASSERT_TRUE(!!removed); } void Model::generate(const fs::path& path, bool force) { fs::create_directories(path); for (auto& child : root->kids) { child->generate(path, force); } } void Model::swap(Model& other) { using std::swap; swap(root, other.root); } bool waitonresults(future* r1 = nullptr, future* r2 = nullptr, future* r3 = nullptr, future* r4 = nullptr) { if (r1) r1->wait(); if (r2) r2->wait(); if (r3) r3->wait(); if (r4) r4->wait(); return (!r1 || r1->get()) && (!r2 || r2->get()) && (!r3 || r3->get()) && (!r4 || r4->get()); } atomic next_request_tag{ 1 << 30 }; class StateCacheValidator { public: StateCacheValidator() = default; // Compare a local node tree against the loaded state cache. bool compare(const LocalNode& root) const { // Tracks nodes to be compared. list pending(1, &root); // Nodes that've been processed. std::set processed; // Compare nodes against the state cache. while (!pending.empty()) { // Get our hands on the node. auto& node = *pending.front(); // Node's being processed. pending.pop_front(); // Track which IDs we've processed. if (node.dbid) processed.emplace(node.dbid); // Does the node represent a file? if (node.type == FILENODE) { // Is it consistent with the cache? if (!matchFile(node)) return false; // No further work necessary. continue; } // Node represents a directory. // // Is it consistent with the cache? if (!matchDirectory(node)) return false; // Queue children. for (auto& childIt : node.children) { // But only those that've been written to disk. if (childIt.second->dbid) pending.emplace_back(childIt.second); } } if (processed.size() != mNodeMap.size()) { LOG_verbose << "StateCacheValidator::compare: processed node list size(" << processed.size() << ") does not match with mNodeMap size(" << mNodeMap.size() << ")"; return false; } return true; } // Load a state cache from disk. bool load(MegaClient& client, const string& name) { constexpr auto flags = DB_OPEN_FLAG_TRANSACTED; if (!client.dbaccess) return false; // Convenience. auto& dbAccess = *client.dbaccess; auto& fsAccess = *client.fsaccess; auto& rng = client.rng; // Try and open the state cache. DbTablePtr db(dbAccess.open(rng, fsAccess, name, flags, nullptr)); // Try and load the state cache. return db && read(*db, client.key); } // Translate the loaded state cache into a DOT diagram. string toDot() const { // No nodes? No graph. if (mNodeMap.empty()) return "digraph {}\n"; ostringstream ostream; // Emit DOT prologue. ostream << "digraph {\n"; // Emit root node. ostream << "\t0 [ label=\"root\" ];\n"; // Emit nodes. for (auto& i : mNodeMap) { auto& node = *i.second; // Node. ostream << "\t" << node.dbid << " [ label=\"" << node.localname.toPath(false) << "\" ];\n"; // Parent link. ostream << "\t" << node.dbid << " -> " << node.parentID << ";\n"; } // Emit DOT epilogue. ostream << "}\n"; return ostream.str(); } private: struct StateCacheNode : public LocalNodeCore { // Dummy implementation. bool serialize(string* destination) const override { return write(*destination, parentID); } // Useful for debugging purposes. uint32_t parentID = 0u; }; // StateCacheNode // Convenience. using StateCacheNodePtr = unique_ptr; // Check if a directory node is consistent with the cache. bool matchDirectory(const LocalNode& node) const { static const std::set empty; assert(node.dbid || !node.parent); assert(node.parent || !node.dbid); if (node.parent && !matchGeneral(node)) { LOG_verbose << "matchDirectory: node's attributes are not consistent with cache"; return false; } // What children does this node have in the cache? auto* children = ∅ if (mNodeChildMap.count(node.dbid)) children = &mNodeChildMap.at(node.dbid); // Is the node's parent-child linkage consistent with the cache? size_t nChildren = 0; for (auto& childIt : node.children) { auto& child = *childIt.second; // Skip children that haven't been written to disk. if (!child.dbid) continue; // Is the child present in the cache? if (!children->count(child.dbid)) { LOG_verbose << "matchDirectory: " << node.getLocalPath().toPath(false) << ", child not present in the cache(" << child.getLocalPath().toPath(false) << ")"; return false; } ++nChildren; } // Is the node's child count consistent with the cache? if (children->size() != nChildren) { LOG_verbose << "matchDirectory: " << node.getLocalPath().toPath(false) << ", children size (" << children->size() << ") does not match with nChildren value (" << nChildren << ")"; return false; } return true; } // Check if a file node is consistent with the cache. bool matchFile(const LocalNode& node) const { assert(node.dbid); assert(node.parent); // Are the node's attributes consistent with the cache? if (!matchGeneral(node)) return false; const std::string nodeReportStr{"matchFile: " + node.getLocalPath().toPath(false) + ". "}; // Convenience. auto& entry = *mNodeMap.at(node.dbid); // This attribute is only meaningful for files. auto& lfp = node.syncedFingerprint; auto& rfp = entry.syncedFingerprint; if (lfp.isvalid != rfp.isvalid) { LOG_verbose << nodeReportStr << "LocalNode lfp.isvalid (" << lfp.isvalid << ") does not match with rfp.isvalid value (" << rfp.isvalid << ")"; return false; } if (lfp.isvalid && lfp != rfp) { LOG_verbose << nodeReportStr << "LocalNode lfp (" << lfp.isvalid << ") does not match with rfp value (" << rfp.isvalid << ")"; return false; } return true; } // Check if a node is consistent with the cache. bool matchGeneral(const LocalNode& node) const { assert(node.dbid); assert(node.parent); std::string nodeReportStr{"matchGeneral: " < node.getLocalPath().toPath(false) + ". "}; // Is the node in the cache? if (!mNodeMap.count(node.dbid)) { LOG_verbose << nodeReportStr + "Node is not in cache"; return false; } // Convenience. auto& entry = *mNodeMap.at(node.dbid); // Compare serialized attributes. if (node.type != entry.type) { LOG_verbose << nodeReportStr << "LocalNode type (" << node.type << ") does not match with StateCacheNode value (" << entry.type << ")"; return false; } if (node.parent->dbid != entry.parentID) { LOG_verbose << nodeReportStr << "LocalNode parentID (" << node.parent->dbid << ") does not match with StateCacheNode value (" << entry.parentID << ")"; return false; } if (node.fsid_lastSynced != entry.fsid_lastSynced) { LOG_verbose << nodeReportStr << "LocalNode fsid_lastSynced (" << node.fsid_lastSynced << ") does not match with StateCacheNode value (" << entry.fsid_lastSynced << ")"; return false; } if (node.localname != entry.localname) { LOG_verbose << nodeReportStr << "LocalNode localname (" << node.localname << ") does not match with StateCacheNode value (" << entry.localname << ")"; return false; } if (!node.slocalname != !entry.slocalname) { LOG_verbose << nodeReportStr << "LocalNode !slocalname (" << !node.slocalname << ") does not match with StateCacheNode value (" << !entry.slocalname << ")"; return false; } if (node.slocalname && *node.slocalname != *entry.slocalname) { LOG_verbose << nodeReportStr << "LocalNode slocalname (" << *node.slocalname << ") does not match with StateCacheNode value (" << *entry.slocalname << ")"; return false; } if (node.syncedCloudNodeHandle != entry.syncedCloudNodeHandle) { LOG_verbose << nodeReportStr << "LocalNode syncedCloudNodeHandle (" << Base64Str(node.syncedCloudNodeHandle.as8byte()) << ") does not match with StateCacheNode value (" << Base64Str(entry.syncedCloudNodeHandle.as8byte()) << ")"; return false; } return true; } // Read a state cache's contents into memory. bool read(DbTable& db, SymmCipher& key) { // Convenience. using std::make_unique; using std::swap; // Parent-child relationships. map> children; // Database ID of a serialized node. uint32_t id; // Serialized metadata representing a node. string metadata; // Deserialized nodes. map nodes; // Tracks pending parents. set pending; // Make sure we're reading from the start. db.rewind(); // Read and deserialize metadata from the state cache. while (db.next(&id, &metadata, &key)) { auto node = std::make_unique(); // Try and deserialize the node. if (!node->read(metadata, node->parentID)) return false; // Inject database ID. node->dbid = id; // Orphaned children are reunited with their parent. pending.erase(id); // Convenience. auto parentID = node->parentID; // Establish parent-child link. children[parentID].emplace(id); // Add node to the database. nodes.emplace(id, std::move(node)); // Potential orphan? if (!parentID || !mNodeMap.count(parentID)) continue; // Track potential orphans. pending.emplace(parentID); } // Have all children been linked? assert(pending.empty()); // Swap loaded metadata into place. swap(children, mNodeChildMap); swap(nodes, mNodeMap); return true; } // Tracks the parent-child relationships of loaded nodes. map> mNodeChildMap; // Node metadata loaded from the state cache. map mNodeMap; }; // StateCacheValidator CloudItem::CloudItem(const Node* node) : CloudItem(*node) { } CloudItem::CloudItem(const Node& node) : mNodeHandle(node.nodeHandle()) , mPath() , mFromRoot(false) { } CloudItem::CloudItem(const string& path, bool fromRoot) : CloudItem(path.c_str(), fromRoot) { } CloudItem::CloudItem(const char* path, bool fromRoot) : mNodeHandle() , mPath(path) , mFromRoot(fromRoot) { if (mFromRoot && path && *path == '/') mPath.erase(0, 1); } CloudItem::CloudItem(const NodeHandle& nodeHandle) : mNodeHandle(nodeHandle) , mPath() , mFromRoot() { } CloudItem::CloudItem(handle nodeHandle) : CloudItem(NodeHandle().set6byte(nodeHandle)) { } std::shared_ptr CloudItem::resolve(StandardClient& client) const { if (!mNodeHandle.isUndef()) return client.client.nodeByHandle(mNodeHandle); auto root = client.gettestbasenode(); if (mFromRoot) root = client.getcloudrootnode(); return client.drillchildnodebyname(root, mPath); } StandardClientInUse ClientManager::getCleanStandardClient(int loginIndex, fs::path workingFolder) { EXPECT_GE(loginIndex, 0) << "ClientManager::getCleanStandardClient(): negative client index requested"; EXPECT_LE(loginIndex, getEnvVarAccounts().size()) << "ClientManager::getCleanStandardClient(): invalid client index requested"; for (auto i = clients[loginIndex].begin(); i != clients[loginIndex].end(); ++i) { if (!i->inUse) { i->ptr->cleanupForTestReuse(loginIndex); i->ptr->fsBasePath = i->ptr->ensureDir(workingFolder / u8path_compat(i->name)); return StandardClientInUse(i); } } // otherwise, make a new one string clientname = std::to_string(loginIndex) + "_" + std::to_string(clients[loginIndex].size()); fs::path localAccountRoot = makeReusableClientFolder(clientname); shared_ptr c( new StandardClient(localAccountRoot, "client" + clientname, workingFolder)); clients[loginIndex].push_back(StandardClientInUseEntry(false, c, clientname, loginIndex)); const auto& [emailVarName, passVarName] = getEnvVarAccounts().getVarNames(static_cast(loginIndex)); c->login_reset(emailVarName, passVarName, false, false); c->cleanupForTestReuse(loginIndex); return StandardClientInUse(--clients[loginIndex].end()); } ClientManager::~ClientManager() { clear(); } void ClientManager::clear() { if (clients.empty()) return; while (clients.size()) { LOG_debug << "Shutting down ClientManager, remaining: " << clients.size(); clients.erase(clients.begin()); } LOG_debug << "ClientManager shutdown complete"; } bool StandardSyncController::call(const Callback& callback, const LocalPath& path) const { std::lock_guard guard(mLock); if (callback) return callback(u8path_compat(path.toPath(false))); return false; } void StandardSyncController::set(Callback& callback, Callback value) { std::lock_guard guard(mLock); callback = std::move(value); } bool StandardSyncController::deferPutnode(const LocalPath& path) const { return call(mDeferPutnode, path); } bool StandardSyncController::deferPutnodeCompletion(const LocalPath& path) const { return call(mDeferPutnodeCompletion, path); } bool StandardSyncController::deferUpload(const LocalPath& path) const { return call(mDeferUpload, path); } void StandardSyncController::setDeferPutnodeCallback(Callback callback) { set(mDeferPutnode, std::move(callback)); } void StandardSyncController::setDeferPutnodeCompletionCallback(Callback callback) { set(mDeferPutnodeCompletion, std::move(callback)); } void StandardSyncController::setDeferUploadCallback(Callback callback) { set(mDeferUpload, std::move(callback)); } void StandardClient::ResultProc::prepresult(resultprocenum rpe, int tag, std::function&& requestfunc, std::function&& f, handle h) { if (rpe != COMPLETION) { lock_guard g(mtx); auto& perTypeTags = m[rpe]; assert(perTypeTags.find(tag) == perTypeTags.end()); perTypeTags.emplace(tag, id_callback(std::move(f), tag, h)); } std::lock_guard lg(client.clientMutex); assert(tag > 0); int oldtag = client.client.reqtag; client.client.reqtag = tag; requestfunc(); client.client.reqtag = oldtag; LOG_debug << "tag-result prepared for operation " << rpe << " tag " << tag; client.client.waiter->notify(); } void StandardClient::ResultProc::processresult(resultprocenum rpe, error e, handle, int tag) { if (tag == 0 && rpe != CATCHUP) { //out() << "received notification of SDK initiated operation " << rpe << " tag " << tag; // too many of those to output return; } if (tag < (2 << 30)) { out() << "ignoring callback from SDK internal sync operation " << rpe << " tag " << tag; return; } lock_guard g(mtx); auto& entry = m[rpe]; if (rpe == CATCHUP) { while (!entry.empty()) { entry.begin()->second.f(e); entry.erase(entry.begin()); } return; } if (entry.empty()) { //out() << client.client.clientname // << "received notification of operation type " << rpe << " completion but we don't have a record of it. tag: " << tag; return; } auto it = entry.find(tag); if (it == entry.end()) { out() << client.client.clientname << "tag not found for operation completion of " << rpe << " tag " << tag; return; } if (it->second.f(e)) { entry.erase(it); } } string StandardClient::ensureDir(const fs::path& p) { fs::create_directories(p); string result = path_u8string(p); if (result.back() != fs::path::preferred_separator) { result += fs::path::preferred_separator; } return result; } void StandardClient::setMinimumUploadThrottleSettings() { const auto uploadThrottlingManager = std::make_shared(); const auto throttleValueLimits = uploadThrottlingManager->throttleValueLimits(); const auto throttleUpdateRate = throttleValueLimits.throttleUpdateRateLowerLimit; if (!uploadThrottlingManager->setThrottleUpdateRate(throttleUpdateRate)) { LOG_warn << "[StandardClient::setMinimumUploadThrottleSettings] Operation to set the " "upload throttle update rate to " << throttleUpdateRate.count() << " secs has failed"; return; } const auto maxUploadsBeforeThrottle = throttleValueLimits.maxUploadsBeforeThrottleUpperLimit; if (!uploadThrottlingManager->setMaxUploadsBeforeThrottle(maxUploadsBeforeThrottle)) { LOG_warn << "[StandardClient::setMinimumUploadThrottleSettings] Operation to set the max " "uploads before throttle to " << maxUploadsBeforeThrottle << " has failed"; return; } LOG_debug << "[StandardClient::setMinimumUploadThrottleSettings] throttleUpdateRate: " << throttleUpdateRate.count() << " secs, maxUploadsBeforeThrottle: " << maxUploadsBeforeThrottle; std::promise setThrottlingManagerPromise; client.setSyncUploadThrottlingManager(uploadThrottlingManager, [&setThrottlingManagerPromise](const error e) { setThrottlingManagerPromise.set_value(e); }); auto setThrottlingManagerFuture = setThrottlingManagerPromise.get_future(); if (setThrottlingManagerFuture.wait_for(DEFAULTWAIT) != std::future_status::ready) { LOG_warn << "[StandardClient::setMinimumUploadThrottleSettings] Operation to update the " "upload throttling manager with " "minimum values has timed out!"; return; } if (const auto result = setThrottlingManagerFuture.get(); result != API_OK) { LOG_warn << "[StandardClient::setThrottleUpdateRateToLowestValue] Operation to update " "the upload throttling manager failed with " << result; } } StandardClient::StandardClient(const fs::path& basepath, const string& name, const fs::path& workingFolder): waiter(new WAIT_CLASS), #ifdef GFX_CLASS gfx(std::make_unique()), #endif client_dbaccess_path(ensureDir(basepath / name)), httpio(new CurlHttpIO), client(this, waiter, httpio.get(), #ifdef DBACCESS_CLASS new DBACCESS_CLASS(LocalPath::fromAbsolutePath(client_dbaccess_path)), #else NULL, #endif #ifdef GFX_CLASS &gfx, #else NULL, #endif USER_AGENT.c_str(), THREADS_PER_MEGACLIENT), clientname(name + " "), resultproc(*this), clientthread( [this]() { threadloop(); }) { client.clientname = clientname + " "; client.syncs.mDetailedSyncLogging = true; g_netLoggingOn = true; #ifdef GFX_CLASS gfx.startProcessingThread(); #endif if (workingFolder.empty()) { fsBasePath = basepath / u8path_compat(name); } else { fsBasePath = ensureDir(workingFolder / u8path_compat(name)); } // SyncTests want to skip backup restrictions, so they are not // restricted to the path "Vault/My backups//" client.syncs.mBackupRestrictionsEnabled = false; setMinimumUploadThrottleSettings(); } StandardClient::~StandardClient() { LOG_debug << "StandardClient exiting"; // Make sure logout completes before we escape. logout(false); LOG_debug << "~StandardClient final logout complete"; clientthreadexit = true; waiter->notify(); clientthread.join(); LOG_debug << "~StandardClient end of function (work thread joined)"; } void StandardClient::localLogout() { auto result = thread_do([](MegaClient& mc, PromiseBoolSP result) { mc.locallogout(false, true); result->set_value(true); }, __FILE__, __LINE__); // Make sure logout completes before we escape. result.get(); } bool StandardClient::logout(bool keepSyncsConfigFile) { auto result = thread_do([=](MegaClient& client, PromiseBoolSP result) { client.logout(keepSyncsConfigFile, [=](error e) { result->set_value(e == API_OK); }); }, __FILE__, __LINE__); if (result.wait_for(DEFAULTWAIT) == future_status::timeout) return false; return result.get(); } string StandardClient::lp(LocalNode* ln) { return ln->getLocalPath().toName(*client.fsaccess); } void StandardClient::onCallback() { lastcb = chrono::steady_clock::now(); }; void StandardClient::sync_added(const SyncConfig& config) { onCallback(); if (logcb) { lock_guard guard(om); out() << clientname << "sync_added(): id: " << toHandle(config.mBackupId); } if (onAutoResumeResult) { onAutoResumeResult(config); } } void StandardClient::sync_removed(const SyncConfig& config) { onCallback(); if (logcb) { lock_guard guard(om); out() << clientname << "sync_removed(): id: " << toHandle(config.mBackupId); } if (onRemovedSync) { onRemovedSync(config); } } void StandardClient::syncs_restored(SyncError syncError) { lock_guard g(om); out() << clientname << "sync restore complete: " << SyncConfig::syncErrorToStr(syncError); received_syncs_restored = true; } void StandardClient::nodes_updated(sharedNode_vector* nodes, int numNodes) { if (!nodes) { out() << clientname << "nodes_updated: total reset. total node count now: " << numNodes; return; } if (logcb) { lock_guard g(om); if (numNodes > 1) // output root of sync (the second node) for tracing { out() << clientname << "nodes_updated: received " << numNodes << " including " << nodes->at(0)->displaypath() << " " << nodes->at(1)->displaypath(); } else { out() << clientname << "nodes_updated: received " << numNodes << " including " << nodes->at(0)->displaypath(); } } received_node_actionpackets = true; nodes_updated_cv.notify_all(); } bool StandardClient::waitForNodesUpdated(unsigned numSeconds) { mutex nodes_updated_cv_mutex; std::unique_lock g(nodes_updated_cv_mutex); nodes_updated_cv.wait_for(g, std::chrono::seconds(numSeconds), [&](){ return received_node_actionpackets.load(); }); return received_node_actionpackets; } void StandardClient::syncupdate_stateconfig(const SyncConfig& config) { onCallback(); if (logcb) { lock_guard g(om); out() << clientname << "syncupdate_stateconfig() " << toHandle(config.mBackupId); } if (mOnSyncStateConfig) mOnSyncStateConfig(config); } void StandardClient::syncupdate_treestate(const SyncConfig& config, const LocalPath& localPath, treestate_t treeState, nodetype_t type) { onCallback(); if (logcb) { lock_guard g(om); out() << clientname << "syncupdate_stateconfig() " << toHandle(config.mBackupId); } if (mOnSyncTreeState) mOnSyncTreeState(config, localPath, treeState, type); } void StandardClient::useralerts_updated(UserAlert::Base** /*alerts*/, int numAlerts) { if (logcb) { lock_guard guard(om); out() << clientname << "useralerts_updated: received " << numAlerts; } received_user_alerts = true; user_alerts_updated_cv.notify_all(); } bool StandardClient::waitForUserAlertsUpdated(unsigned numSeconds) { std::mutex mutex; std::unique_lock guard(mutex); user_alerts_updated_cv.wait_for(guard, std::chrono::seconds(numSeconds), [&] { return received_user_alerts.load(); }); return received_user_alerts; } void StandardClient::users_updated(User** users , int size) { if (!users) // All users have changed, notification just after fetchnodes { return; } // If none lambda is register with createsOnUserUpdateLamda, any user action package generate and event for stop waiting period if (!mCheckUserChange) { lock_guard g(user_actionpackets_mutex); received_user_actionpackets = true; user_updated_cv.notify_all(); } else { std::lock_guard g(mUserActionPackageMutex); for (int i = 0; i < size; i++) { if (mCheckUserChange(users[i])) { lock_guard g(user_actionpackets_mutex); received_user_actionpackets = true; user_updated_cv.notify_all(); } } } } bool StandardClient::waitForUserUpdated(unsigned int numSeconds) { std::unique_lock guard(user_actionpackets_mutex); user_updated_cv.wait_for(guard, std::chrono::seconds(numSeconds), [&] { return received_user_actionpackets.load(); }); return received_user_actionpackets; } void StandardClient::createsOnUserUpdateLamda(std::function onUserUpdateLambda) { std::lock_guard g(mUserActionPackageMutex); mCheckUserChange = onUserUpdateLambda; received_user_actionpackets = false; } void StandardClient::removeOnUserUpdateLamda() { std::lock_guard g(mUserActionPackageMutex); mCheckUserChange = nullptr; received_user_actionpackets = false; } void StandardClient::syncupdate_scanning(bool b) { if (logcb) { onCallback(); lock_guard g(om); out() << clientname << "syncupdate_scanning()" << b; } } void StandardClient::syncupdate_conflicts(bool state) { if (logcb) { onCallback(); lock_guard guard(om); out() << clientname << "syncupdate_conflicts()" << state; } mConflictsDetected.store(state); if (mOnConflictsDetected) mOnConflictsDetected(state); } void StandardClient::syncupdate_stalled(bool state) { if (logcb) { onCallback(); lock_guard g(om); out() << clientname << "syncupdate_stalled()" << state; } mStallDetected.store(state); if (mOnStall) mOnStall(state); } void StandardClient::syncupdate_totalconflicts(bool state) { if (logcb) { onCallback(); lock_guard guard(om); out() << clientname << "syncupdate_totalconflicts()" << state; } mTotalConflictsUpdated.store(state); if (mOnTotalConflictsUpdate) mOnTotalConflictsUpdate(state); } void StandardClient::syncupdate_totalstalls(bool state) { if (logcb) { onCallback(); lock_guard g(om); out() << clientname << "syncupdate_totalstalls()" << state; } mTotalStallsUpdated.store(state); if (mOnTotalStallsUpdate) mOnTotalStallsUpdate(state); } bool StandardClient::isUserAttributeSet(attr_t attr, unsigned int numSeconds, error& err) { std::recursive_mutex attr_cv_mutex; std::condition_variable_any user_attribute_updated_cv; bool attrIsSet = false; std::atomic_bool replyReceived{false}; auto completionErr = [&](error e) { std::lock_guard g(attr_cv_mutex); err = e; LOG_debug << "attr: " << attr << " error: " << err; replyReceived = true; user_attribute_updated_cv.notify_one(); }; auto completionBytes = [&](::mega::byte*, unsigned, attr_t) { std::lock_guard g(attr_cv_mutex); err = API_OK; LOG_debug << "attr: " << attr << " is set"; replyReceived = true; attrIsSet = true; user_attribute_updated_cv.notify_one(); }; auto completionTLV = [&](unique_ptr, attr_t) { std::lock_guard g(attr_cv_mutex); err = API_OK; LOG_debug << "attr: " << attr << " is set"; replyReceived = true; attrIsSet = true; user_attribute_updated_cv.notify_one(); }; std::unique_lock g(attr_cv_mutex); resultproc.prepresult(COMPLETION, ++next_request_tag, [&](){ client.getua(client.ownuser(), attr, -1, completionErr, completionBytes, completionTLV); }, nullptr); user_attribute_updated_cv.wait_for(g, std::chrono::seconds(numSeconds), [&replyReceived](){ return replyReceived.load(); }); return attrIsSet; } bool StandardClient::waitForAttrMyBackupIsSet(unsigned int numSeconds, bool& newBackupIsSet) { error err = API_EINTERNAL; bool attrMyBackupFolderIsSet = isUserAttributeSet(attr_t::ATTR_MY_BACKUPS_FOLDER, numSeconds, err); if (err != API_ENOENT) { return attrMyBackupFolderIsSet; } // If attribute is not set, it's going to established const char* folderName = "My Backups"; attrMyBackupFolderIsSet = false; std::recursive_mutex attrMyBackup_cv_mutex; std::condition_variable_any user_attribute_backup_updated_cv; std::atomic_bool replyReceived{false}; std::unique_lock g(attrMyBackup_cv_mutex); resultproc.prepresult(COMPLETION, ++next_request_tag, [&](){ client.setbackupfolder(folderName, client.reqtag, [&](Error e) { std::lock_guard g(attrMyBackup_cv_mutex); if (e == API_OK) { attrMyBackupFolderIsSet = true; newBackupIsSet = true; } else { LOG_err << "Error setting backup folder user attribute"; } replyReceived = true; user_attribute_backup_updated_cv.notify_one(); }); }, nullptr); user_attribute_backup_updated_cv.wait_for(g, std::chrono::seconds(numSeconds), [&replyReceived](){ return replyReceived.load(); }); if (!attrMyBackupFolderIsSet) // Error setting backup folder { return false; } // Check if attribute has been established properly return isUserAttributeSet(attr_t::ATTR_MY_BACKUPS_FOLDER, numSeconds, err); } void StandardClient::file_added(File* file) { if (mOnFileAdded) { mOnFileAdded(*file); } } void StandardClient::file_complete(File* file) { if (mOnFileComplete) { mOnFileComplete(*file); } } void StandardClient::notify_retry(dstime /*when*/, retryreason_t reason) { onCallback(); lock_guard guard(om); mRetryTracker.track(clientname, reason); } void StandardClient::request_error(error e) { onCallback(); if (!logcb) return; lock_guard guard(om); out() << clientname << " request_error: " << e; } void StandardClient::request_response_progress(m_off_t a, m_off_t b) { onCallback(); if (!logcb) return; lock_guard guard(om); out() << clientname << " request_response_progress: " << a << " " << b; } void StandardClient::updateClientDowaitDs(const dstime lastClientDoWait) { if (lastClientDoWait <= 0) return; lock_guard guard(om); mClientDowaitDs += lastClientDoWait; } dstime StandardClient::consumeClientDowaitDs(const dstime timeGranularity) { if (timeGranularity <= 0) { LOG_err << "[StandardClient::consumeClientDowaitDs] timeGranularity (" << timeGranularity << ") should be above 0"; assert(false && "[StandardClient::consumeClientDowaitDs] timeGranularity should be above 0"); return 0; } lock_guard guard(om); const auto clientDowaitDsToReturn = mClientDowaitDs / timeGranularity; mClientDowaitDs -= clientDowaitDsToReturn; return clientDowaitDsToReturn; } void StandardClient::resetClientDowaitDs() { lock_guard guard(om); mClientDowaitDs = 0; } void StandardClient::threadloop() try { while (!clientthreadexit) { int r; client.waiter->bumpds(); dstime t1 = client.waiter->ds.load(); { std::lock_guard lg(clientMutex); client.waiter->bumpds(); dstime t1a = client.waiter->ds.load(); if (t1a - t1 > 20) LOG_debug << "lock for preparewait took ds: " << t1a - t1; r = client.preparewait(); } assert(r == 0 || r == Waiter::NEEDEXEC); client.waiter->bumpds(); dstime t2 = client.waiter->ds.load(); if (t2 - t1 > 20) LOG_debug << "lock and preparewait took ds: " << t2 - t1; if (!r) { r |= client.dowait(); assert(r == 0 || r == Waiter::NEEDEXEC); } client.waiter->bumpds(); dstime t3 = client.waiter->ds.load(); const auto doWaitTimeDs = t3 - t2; updateClientDowaitDs(doWaitTimeDs); if (doWaitTimeDs >= 10) LOG_debug << "dowait took ds: " << doWaitTimeDs; std::lock_guard lg(clientMutex); client.waiter->bumpds(); dstime t3a = client.waiter->ds.load(); if (t3a - t3 > 20) LOG_debug << "lock for exec took ds: " << t3a - t3; r |= client.checkevents(); assert(r == 0 || r == Waiter::NEEDEXEC); client.waiter->bumpds(); dstime t4 = client.waiter->ds.load(); if (t4 - t3a > 20) LOG_debug << "checkevents took ds: " << t4 - t3a; { client.waiter->bumpds(); auto start = client.waiter->ds.load(); std::lock_guard g(functionDoneMutex); string sourcefile; int sourceline = -1; if (nextfunctionMC) { sourcefile = nextfunctionMC_sourcefile; sourceline = nextfunctionMC_sourceline; nextfunctionMC_sourcefile = ""; nextfunctionMC_sourceline = -1; nextfunctionMC(); nextfunctionMC = nullptr; functionDone.notify_all(); r |= Waiter::NEEDEXEC; } if (nextfunctionSC) { sourcefile = nextfunctionSC_sourcefile; sourceline = nextfunctionSC_sourceline; nextfunctionSC_sourcefile = ""; nextfunctionSC_sourceline = -1; nextfunctionSC(); nextfunctionSC = nullptr; functionDone.notify_all(); r |= Waiter::NEEDEXEC; } client.waiter->bumpds(); auto end = client.waiter->ds.load(); if (end - start > 200) { // note that in Debug builds (for windows at least), prep for logging in can take 15 seconds in pbkdf2.DeriveKey LOG_err << "test functions passed to be executed on the client thread should queue work but not wait for the results themselves. Waited ms: " << end-start << " in " << sourcefile << " line " << sourceline; //assert(false); } } client.waiter->bumpds(); dstime t5 = client.waiter->ds.load(); if (t5 - t4 > 20) LOG_debug << "injected functions took ds: " << t5 - t4; if ((r & Waiter::NEEDEXEC)) { client.exec(); } client.waiter->bumpds(); dstime t6 = client.waiter->ds.load(); if (t6 - t5 > 20) LOG_debug << "exec took ds: " << t6 - t5; } // shut down on the same thread, otherwise any ongoing async I/O fails to complete (on windows) client.locallogout(false, true); out() << clientname << " thread exiting naturally"; } catch (std::exception& e) { out() << clientname << " thread exception, StandardClient " << clientname << " terminated: " << e.what(); } catch (...) { out() << clientname << " thread exception, StandardClient " << clientname << " terminated"; } void StandardClient::preloginFromEnv(const string& userenv, PromiseBoolSP pb) { const string user = Utils::getenv(userenv, ""); ASSERT_FALSE(user.empty()); resultproc.prepresult(PRELOGIN, ++next_request_tag, [&](){ client.prelogin(user.c_str()); }, [pb](error e) { pb->set_value(!e); return true; }); } void StandardClient::loginFromEnv(const string& userenv, const string& pwdenv, PromiseBoolSP pb) { const string user = Utils::getenv(userenv, ""); const string pwd = Utils::getenv(pwdenv, ""); ASSERT_FALSE(user.empty()); ASSERT_FALSE(pwd.empty()); ::mega::byte pwkey[SymmCipher::KEYLENGTH]; resultproc.prepresult(LOGIN, ++next_request_tag, [&](){ if (client.accountversion == 1) { if (error e = client.pw_key(pwd.c_str(), pwkey)) { ASSERT_TRUE(false) << "login error: " << e; } else { client.login(user.c_str(), pwkey); } } else if (client.accountversion == 2 && !salt.empty()) { client.login2(user.c_str(), pwd.c_str(), &salt); } else { ASSERT_TRUE(false) << "Login unexpected error"; } }, [pb](error e) { pb->set_value(!e); return true; }); } void StandardClient::loginFromSession(const string& session, PromiseBoolSP pb) { resultproc.prepresult(LOGIN, ++next_request_tag, [&](){ client.login(session); }, [pb](error e) { pb->set_value(!e); return true; }); } #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) void StandardClient::sendDeferredAndReset() { auto futureResult = thread_do( [&](StandardClient&, PromiseBoolSP pb) { client.reqs.deferRequests = nullptr; client.reqs.sendDeferred(); pb->set_value(true); }, __FILE__, __LINE__); futureResult.get(); } #endif bool StandardClient::copy(const CloudItem& source, const CloudItem& target, const string& name, VersioningOption versioningPolicy) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.copy(source, target, name, std::move(result), versioningPolicy); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::minutes(2)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::copy(const CloudItem& source, const CloudItem& target, VersioningOption versioningPolicy) { return copy(source, target, string(), versioningPolicy); } void StandardClient::copy(const CloudItem& source, const CloudItem& target, string name, PromiseBoolSP result, VersioningOption versioningPolicy) { auto sourceNode = source.resolve(*this); EXPECT_TRUE(sourceNode); if (!sourceNode) return result->set_value(false); auto targetNode = target.resolve(*this); EXPECT_TRUE(targetNode); if (!targetNode || targetNode->type == FILENODE) return result->set_value(false); // Make sure name always contains something valid. if (name.empty()) name = sourceNode->displayname(); // Make sure name is normalized. LocalPath::utf8_normalize(&name); TreeProcCopy proc; // Figure out how many nodes we need to copy. client.proctree(sourceNode, &proc, false, true); // Allocate and populate nodes. proc.allocnodes(); client.proctree(sourceNode, &proc, false, true); // We need the original node's handle if we're using versioning. std::shared_ptr victimNode; if (versioningPolicy != NoVersioning) victimNode = client.childnodebyname(targetNode.get(), name.c_str(), true); if (victimNode) proc.nn[0].ovhandle = victimNode->nodeHandle(); proc.nn[0].parenthandle = UNDEF; // Populate attributes of copied node. { SymmCipher key; // Load key. key.setkey((const ::mega::byte*)proc.nn[0].nodekey.data(), sourceNode->type); // Copy existing attributes. AttrMap attrs = sourceNode->attrs; // Upate the node's name. attrs.map['n'] = std::move(name); // Generate attribute string. string attrstring; attrs.getjson(&attrstring); // Update node's attribute string. client.makeattr(&key, proc.nn[0].attrstring, attrstring.c_str()); } auto completion = [=](const Error& error) { LOG_debug << "Putnodes request completed: " << error; EXPECT_EQ(error, API_OK); result->set_value(error == API_OK); }; LOG_debug << "Scheduling putnodes request now..."; client.putnodes(targetNode->nodeHandle(), versioningPolicy, std::move(proc.nn), nullptr, 0, false, {}, // customerIpPort BasicPutNodesCompletion(std::move(completion))); } bool StandardClient::putnodes(const CloudItem& parent, VersioningOption versioningPolicy, std::vector&& nodes) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.putnodes(parent, versioningPolicy, std::move(nodes), std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(40)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::putnodes(const CloudItem& parent, VersioningOption versioningPolicy, std::vector&& nodes, PromiseBoolSP result) { auto node = parent.resolve(*this); EXPECT_TRUE(node); if (!node) return result->set_value(false); auto completion = BasicPutNodesCompletion([result](const Error& e) { LOG_debug << "Putnodes request completed: " << e; EXPECT_EQ(e, API_OK); result->set_value(e == API_OK); }); LOG_debug << "Scheduling putnodes request now..."; client.putnodes(node->nodeHandle(), versioningPolicy, std::move(nodes), nullptr, 0, false, {}, // customerIpPort std::move(completion)); } void StandardClient::uploadFolderTree_recurse(handle parent, handle& h, const fs::path& p, vector& newnodes) { NewNode n; client.putnodes_prepareOneFolder(&n, path_u8string(p.filename()), false); handle thishandle = n.nodehandle = h++; n.parenthandle = parent; newnodes.emplace_back(std::move(n)); for (fs::directory_iterator i(p); i != fs::directory_iterator(); ++i) { if (fs::is_directory(*i)) { uploadFolderTree_recurse(thishandle, h, *i, newnodes); } } } void StandardClient::uploadFolderTree(fs::path p, CloudItem item, PromiseBoolSP pb) { auto completion = BasicPutNodesCompletion([pb](const Error& e) { pb->set_value(!e); }); resultproc.prepresult(COMPLETION, ++next_request_tag, [&](){ // Resolve target node. auto target = item.resolve(*this); // Couldn't locate target node. if (!target) return pb->set_value(false); vector newnodes; handle h = 1; uploadFolderTree_recurse(UNDEF, h, p, newnodes); client.putnodes(target->nodeHandle(), NoVersioning, std::move(newnodes), nullptr, 0, false, {}, // customerIpPort std::move(completion)); }, nullptr); } void StandardClient::downloadFile(const CloudItem& item, const fs::path& destination, PromiseBoolSP result) { auto node = item.resolve(*this); if (!node) return result->set_value(false); unique_ptr file(new FileGet()); file->h = node->nodeHandle(); file->hprivate = true; file->setLocalname(LocalPath::fromAbsolutePath(path_u8string(destination))); file->name = node->displayname(); file->result = std::move(result); reinterpret_cast(*file) = *node; TransferDbCommitter committer(client.tctable); error r = API_OK; client.startxfer(GET, file.get(), committer, false, false, false, NoVersioning, &r, client.nextreqtag()); EXPECT_EQ(r , API_OK); if (r != API_OK) return file->result->set_value(false); file.release(); } bool StandardClient::downloadFile(const CloudItem& item, const fs::path& destination) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.downloadFile(item, destination, result); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::uploadFolderTree(fs::path p, const CloudItem& item) { auto promise = makeSharedPromise(); auto future = promise->get_future(); uploadFolderTree(p, item, std::move(promise)); return future.get(); } void StandardClient::uploadFile(const fs::path& path, const string& name, const Node* parent, TransferDbCommitter& committer, std::function&& completion, VersioningOption vo) { unique_ptr file(new FilePut(std::move(completion))); file->h = parent->nodeHandle(); file->setLocalname(LocalPath::fromAbsolutePath(path_u8string(path))); file->name = name; error result = API_OK; client.startxfer(PUT, file.release(), committer, false, false, false, vo, &result, client.nextreqtag()); EXPECT_EQ(result, API_OK); } void StandardClient::uploadFile(const fs::path& path, const string& name, const Node* parent, std::function&& completion, VersioningOption vo) { resultproc.prepresult(COMPLETION, ++next_request_tag, [&]() { TransferDbCommitter committer(client.tctable); uploadFile(path, name, parent, committer, std::move(completion), vo); }, nullptr); } bool StandardClient::uploadFile(const fs::path& path, const string& name, const CloudItem& parent, int timeoutSeconds, VersioningOption vo) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP pb) { auto parentNode = parent.resolve(client); if (!parentNode) return pb->set_value(false); client.uploadFile(path, name, parentNode.get(), [pb](bool b){ pb->set_value(b); }, vo); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(timeoutSeconds)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::uploadFile(const fs::path& path, const CloudItem& parent, int timeoutSeconds, VersioningOption vo) { return uploadFile(path, path_u8string(path.filename()), parent, timeoutSeconds, vo); } void StandardClient::uploadFilesInTree_recurse(const Node* target, const fs::path& p, std::atomic& inprogress, TransferDbCommitter& committer, VersioningOption vo) { if (fs::is_regular_file(p)) { ++inprogress; uploadFile( p, path_u8string(p.filename()), target, committer, [&inprogress](bool) { --inprogress; }, vo); } else if (fs::is_directory(p)) { if (auto newtarget = client.childnodebyname(target, path_u8string(p.filename()).c_str())) { for (fs::directory_iterator i(p); i != fs::directory_iterator(); ++i) { uploadFilesInTree_recurse(newtarget.get(), *i, inprogress, committer, vo); } } } } bool StandardClient::uploadFilesInTree(fs::path p, const CloudItem& n2, VersioningOption vo) { std::atomic_int inprogress(0); std::shared_ptr targetNode = nullptr; { lock_guard guard(clientMutex); targetNode = n2.resolve(*this); } // The target node should always exist. EXPECT_TRUE(targetNode); if (!targetNode && !inprogress) return false; auto startedFlag = thread_do([&inprogress, this, targetNode, p, vo](StandardClient& client, PromiseBoolSP result) { TransferDbCommitter committer(client.client.tctable); uploadFilesInTree_recurse(targetNode.get(), p, inprogress, committer, vo); result->set_value(true); }, __FILE__, __LINE__); startedFlag.get(); // 30 seconds, that doesn't immediately time out if you stop in the debugger for a while for (int i = 0; i < 300 && inprogress.load(); ++i) { WaitMillisec(100); } EXPECT_EQ(inprogress.load(), 0) << "Failed to uploadFilesInTree within 30 seconds"; return inprogress.load() == 0; } void StandardClient::uploadFile(const fs::path& sourcePath, const string& targetName, const CloudItem& parent, std::function completion, const VersioningOption versioningPolicy) { struct Put : public File { void completed(Transfer* transfer, putsource_t source) { // Sanity. assert(source == PUTNODES_APP); // For purposes of capturing. std::function completion = std::move(mCompletion); // So we can hook the result of putnodes. auto trampoline = [completion](const Error& result, targettype_t, vector&, bool, int /*tag*/, const map& /*fileHandles*/) { EXPECT_EQ(result, API_OK); completion(result); }; // Kick off the putnodes request. sendPutnodesOfUpload(transfer->client, transfer->uploadhandle, "", *transfer->ultoken, transfer->filekey, source, NodeHandle(), std::move(trampoline), nullptr, false); // it's a putnodes from app, not from a sync // Destroy ourselves. delete this; } void terminated(error result) { EXPECT_FALSE(true); // Let the completion function know we've failed. mCompletion(result); // Destroy ourselves. delete this; } // Who to call when the upload completes. std::function mCompletion; }; // Put // Make sure we have exclusive access to the client. lock_guard guard(clientMutex); auto parentNode = parent.resolve(*this); if (!parentNode) return completion(API_ENOENT); // Create a file to represent and track our upload. auto file = std::make_unique(); // Populate necessary fields. file->h = parentNode->nodeHandle(); file->mCompletion = std::move(completion); file->name = targetName; file->setLocalname(LocalPath::fromAbsolutePath(path_u8string(sourcePath))); // Kick off the upload. Client takes ownership of file. TransferDbCommitter committer(client.tctable); error result = API_OK; client.startxfer(PUT, file.get(), committer, false, false, false, versioningPolicy, &result, client.nextreqtag()); EXPECT_EQ(result, API_OK); if (result != API_OK) return file->mCompletion(result); file.release(); } void StandardClient::uploadFile(const fs::path& sourcePath, const CloudItem& parent, std::function completion, const VersioningOption versioningPolicy) { uploadFile(sourcePath, path_u8string(sourcePath.filename()), parent, std::move(completion), versioningPolicy); } void StandardClient::fetchnodes(bool noCache, bool loadSyncs, bool reloadingMidSession, PromiseBoolSP pb) { resultproc.prepresult(FETCHNODES, ++next_request_tag, [&](){ client.fetchnodes(noCache, loadSyncs, reloadingMidSession); }, [this, pb](error e) { if (e) { pb->set_value(false); } else { TreeProcPrintTree tppt; client.proctree(client.nodeByHandle(client.mNodeManager.getRootNodeFiles()), &tppt); if (onFetchNodes) { onFetchNodes(*this, pb); } else { pb->set_value(true); } } onFetchNodes = nullptr; return true; }); } bool StandardClient::fetchnodes(bool noCache, bool loadSyncs, bool reloadingMidSession) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.fetchnodes(noCache, loadSyncs, reloadingMidSession, result); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(180)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) { LOG_warn << "Timed out waiting for fetchnodes"; return false; } return result.get(); } NewNode StandardClient::makeSubfolder(const string& utf8Name) { NewNode newnode; client.putnodes_prepareOneFolder(&newnode, utf8Name, false); return newnode; } void StandardClient::catchup(std::function completion) { auto init = std::bind(&MegaClient::catchup, &client); auto fini = [completion](error e) { LOG_debug << "catchup(...) request completed: " << e; EXPECT_EQ(e, API_OK); if (e) out() << "catchup reports: " << e; LOG_debug << "Calling catchup(...) completion function..."; completion(e); return true; }; LOG_debug << "Sending catchup(...) request..."; resultproc.prepresult(CATCHUP, ++next_request_tag, std::move(init), std::move(fini)); } void StandardClient::catchup(PromiseBoolSP pb) { catchup([pb](error e) { pb->set_value(!e); }); } unsigned StandardClient::deleteTestBaseFolder(bool mayNeedDeleting) { auto result = thread_do([=](StandardClient& client, PromiseUnsignedSP result) { client.deleteTestBaseFolder(mayNeedDeleting, false, std::move(result)); }, __FILE__, __LINE__); return result.get(); } void StandardClient::deleteTestBaseFolder(bool mayNeedDeleting, bool deleted, PromiseUnsignedSP result) { std::shared_ptr root = client.nodeByHandle(client.mNodeManager.getRootNodeFiles()); if (!root) { out() << "err: deleteTestBaseFolder. base folder not found, as root was not found!"; result->set_value(0); return; } if (std::shared_ptr basenode = client.childnodebyname(root.get(), "mega_test_sync", false); basenode && !basenode->changed.removed) // ensure it isn't already marked as removed { if (mayNeedDeleting) { auto completion = [this, result](NodeHandle, Error e) { EXPECT_EQ(e, API_OK); if (e) out() << "debug: deleteTestBaseFolder. delete of test base folder reply reports: " << e; deleteTestBaseFolder(false /*mayNeedDeleting*/, true /*deleted*/, result); }; resultproc.prepresult( COMPLETION, ++next_request_tag, [=, this]() { client.unlink(basenode.get(), false, 0, false, std::move(completion)); }, nullptr); return; } out() << "err: deleteTestBaseFolder. base folder found, but not expected, failing"; result->set_value(0); return; } out() << "debug: deleteTestBaseFolder. base folder not found, wasn't present or delete " "successful"; result->set_value(deleted ? 2 : 1); } void StandardClient::ensureTestBaseFolder(bool mayneedmaking, PromiseBoolSP pb) { if (std::shared_ptr root = client.nodeByHandle(client.mNodeManager.getRootNodeFiles())) { if (std::shared_ptr basenode = client.childnodebyname(root.get(), "mega_test_sync", false)) { out() << clientname << "ensureTestBaseFolder node found"; if (basenode->type == FOLDERNODE) { basefolderhandle = basenode->nodehandle; //out() << clientname << " Base folder: " << Base64Str(basefolderhandle); //parentofinterest = Base64Str(basefolderhandle); out() << clientname << "ensureTestBaseFolder ok"; pb->set_value(true); return; } } else if (mayneedmaking) { vector nn(1); nn[0] = makeSubfolder("mega_test_sync"); resultproc.prepresult(PUTNODES, ++next_request_tag, [&](){ client.putnodes(root->nodeHandle(), NoVersioning, std::move(nn), nullptr, client.reqtag, false); }, [pb, this](error e){ out() << clientname << "ensureTestBaseFolder putnodes completed with: " << e; ensureTestBaseFolder(false, pb); return true; }); out() << clientname << "ensureTestBaseFolder sending putnodes"; return; } out() << clientname << "ensureTestBaseFolder unexpected case"; // but can occur if we look too early because a late actionpacket from prior tests made us think it was time to check } else { out() << clientname << "no file root handle"; } pb->set_value(false); } NewNode* StandardClient::buildSubdirs(list& nodes, const string& prefix, int n, int recurselevel) { nodes.emplace_back(makeSubfolder(prefix)); auto& nn = nodes.back(); nn.nodehandle = nodes.size(); if (recurselevel > 0) { for (int i = 0; i < n; ++i) { buildSubdirs(nodes, prefix + "_" + to_string(i), n, recurselevel - 1)->parenthandle = nn.nodehandle; } } return &nn; } bool StandardClient::makeCloudSubdirs(const string& prefix, int depth, int fanout) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.makeCloudSubdirs(prefix, depth, fanout, result); }, __FILE__, __LINE__); return result.get(); } void StandardClient::makeCloudSubdirs(const string& prefix, int depth, int fanout, PromiseBoolSP pb, const string& atpath) { assert(basefolderhandle != UNDEF); std::list nodes; NewNode* nn = buildSubdirs(nodes, prefix, fanout, depth); nn->parenthandle = UNDEF; nn->ovhandle = NodeHandle(); std::shared_ptr atnode = client.nodebyhandle(basefolderhandle); if (atnode && !atpath.empty()) { atnode = drillchildnodebyname(atnode, atpath); } if (!atnode) { out() << "path not found: " << atpath; pb->set_value(false); } else { auto nodearray = vector(nodes.size()); size_t i = 0; for (auto n = nodes.begin(); n != nodes.end(); ++n, ++i) { nodearray[i] = std::move(*n); } auto completion = [pb, this](const Error& e, targettype_t, vector& nodes, bool, int /*tag*/, const map& /*fileHandles*/) { lastPutnodesResultFirstHandle = nodes.empty() ? UNDEF : nodes[0].mAddedHandle; pb->set_value(!e); }; int tag = ++next_request_tag; resultproc.prepresult( COMPLETION, tag, [&]() { client.putnodes(atnode->nodeHandle(), NoVersioning, std::move(nodearray), nullptr, tag, false, {}, // customerIpPort std::move(completion)); }, nullptr); } } SyncConfig StandardClient::syncConfigByBackupID(handle backupID) const { SyncConfig c; bool found = client.syncs.syncConfigByBackupId(backupID, c); if (!found) assert(found); return c; } bool StandardClient::syncSet(handle backupId, SyncInfo& info) const { SyncConfig c; auto found = client.syncs.syncConfigByBackupId(backupId, c); EXPECT_TRUE(found) << "Unable to find sync with backup ID: " << toHandle(backupId); if (found) { info.h = c.mRemoteNode; info.localpath = c.getLocalPath().toPath(false); info.remotepath = c.mOriginalPathOfRemoteRootNode; // bit of a hack return true; } return false; } StandardClient::SyncInfo StandardClient::syncSet(handle backupId) { SyncInfo result; out() << "looking up BackupId " << toHandle(backupId); bool found = syncSet(backupId, result); if (!found) assert(found); return result; } StandardClient::SyncInfo StandardClient::syncSet(handle backupId) const { return const_cast(*this).syncSet(backupId); } std::shared_ptr StandardClient::getcloudrootnode() { return client.nodeByHandle(client.mNodeManager.getRootNodeFiles()); } std::shared_ptr StandardClient::gettestbasenode() { return client.childnodebyname(getcloudrootnode().get(), "mega_test_sync", false); } std::shared_ptr StandardClient::getcloudrubbishnode() { return client.nodeByHandle(client.mNodeManager.getRootNodeRubbish()); } std::shared_ptr StandardClient::getsyncdebrisnode() { return drillchildnodebyname(getcloudrubbishnode(), "SyncDebris"); } std::shared_ptr StandardClient::drillchildnodebyname(std::shared_ptr n, const string& path) { for (size_t p = 0; n && p < path.size(); ) { auto pos = path.find("/", p); if (pos == string::npos) pos = path.size(); n = client.childnodebyname(n.get(), path.substr(p, pos - p).c_str(), false); p = pos == string::npos ? path.size() : pos + 1; } return n; } vector > StandardClient::drillchildnodesbyname(Node* n, const string& path) { auto pos = path.find("/"); if (pos == string::npos) { return client.childnodesbyname(n, path.c_str(), false); } else { vector > results, subnodes = client.childnodesbyname(n, path.c_str(), false); for (size_t i = subnodes.size(); i--; ) { if (subnodes[i]->type != FILENODE) { vector > v = drillchildnodesbyname(subnodes[i].get(), path.substr(pos + 1)); results.insert(results.end(), v.begin(), v.end()); } } return results; } } handle StandardClient::setupBackup_mainthread(const string& rootPath) { SyncOptions options; options.drivePath = string(1, '\0'); options.isBackup = true; options.uploadIgnoreFile = false; return setupBackup_mainthread(rootPath, options); } handle StandardClient::setupBackup_mainthread(const string& rootPath, const SyncOptions& syncOptions) { auto result = thread_do([&](StandardClient& client, PromiseHandleSP result) { client.setupBackup_inThread(rootPath, syncOptions, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return UNDEF; return result.get(); } void StandardClient::setupBackup_inThread(const string& rootPath, const SyncOptions& syncOptions, PromiseHandleSP result) { auto ec = std::error_code(); auto rootPath_ = fsBasePath / u8path_compat(rootPath); // Try and create the local sync root. fs::create_directories(rootPath_, ec); EXPECT_FALSE(ec); if (ec) return result->set_value(UNDEF); fs::path excludePath_; // Translate exclude path if necessary. if (!syncOptions.excludePath.empty()) { excludePath_ = fsBasePath / u8path_compat(syncOptions.excludePath); } // Create a suitable sync config. SyncConfig config(LocalPath::fromAbsolutePath(path_u8string(rootPath_)), rootPath, NodeHandle(), string(), fsfp_t(), LocalPath(), true, SyncConfig::TYPE_BACKUP); client.preparebackup(config, [result, this](Error err, SyncConfig sc, MegaClient::UndoFunction revertOnError){ if (err != API_OK) { result->set_value(UNDEF); } else { client.addsync( std::move(sc), [revertOnError, result](error e, SyncError, handle h) { if (e && revertOnError) revertOnError(nullptr); result->set_value(e ? UNDEF : h); }, "", ""); } }); } handle StandardClient::setupSync_mainthread(const string& rootPath, const CloudItem& remoteItem, const bool isBackup, const bool uploadIgnoreFile, const string& drivePath) { SyncOptions options; options.drivePath = drivePath; options.isBackup = isBackup; options.uploadIgnoreFile = uploadIgnoreFile; return setupSync_mainthread(rootPath, remoteItem, options); } handle StandardClient::setupSync_mainthread(const string& rootPath, const CloudItem& remoteItem, const SyncOptions& syncOptions) { auto result = thread_do([&](StandardClient& client, PromiseHandleSP result) { client.setupSync_inThread(rootPath, remoteItem, syncOptions, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return UNDEF; return result.get(); } void StandardClient::setupSync_inThread(const string& rootPath, const CloudItem& remoteItem, const SyncOptions& syncOptions, PromiseHandleSP result) { // Helpful sentinel. static const string internalDrive(1, '\0'); // Check if node is (or is contained by) an in-share. auto isShare = [](const Node* node) { for ( ; node; node = node->parent.get()) { if (node->type != FOLDERNODE) continue; if (node->inshare) return true; } return false; }; auto remoteNode = remoteItem.resolve(*this); EXPECT_TRUE(remoteNode); if (!remoteNode) return result->set_value(UNDEF); auto ec = std::error_code(); auto rootPath_ = fsBasePath / u8path_compat(rootPath); // Try and create the local sync root. fs::create_directories(rootPath_, ec); EXPECT_FALSE(ec); if (ec) return result->set_value(UNDEF); fs::path drivePath_; // Populate drive root if necessary. if (syncOptions.drivePath != internalDrive) { // Path should be valid as syncs must be contained by their drive. drivePath_ = fsBasePath / u8path_compat(syncOptions.drivePath); // Read drive ID if present... auto fsAccess = client.fsaccess.get(); auto id = UNDEF; auto path = path_u8string(drivePath_); auto result_ = readDriveId(*fsAccess, path.c_str(), id); // Generate one if not... if (result_ == API_ENOENT) { id = generateDriveId(client.rng); result_ = writeDriveId(*fsAccess, path.c_str(), id); } EXPECT_EQ(result_, API_OK); } fs::path excludePath_; // Translate exclude path if necessary. if (!syncOptions.excludePath.empty()) { excludePath_ = fsBasePath / u8path_compat(syncOptions.excludePath); } // For purposes of capturing. auto isBackup = syncOptions.isBackup; auto remoteHandle = remoteNode->nodeHandle(); auto remoteIsShare = isShare(remoteNode.get()); auto remotePath = string(remoteNode->displaypath()); // Called when it's time to actually add the sync. auto completion = [=, this](error e) { LOG_debug << "Starting to add sync: " << e; // Make sure our caller completed successfully. EXPECT_EQ(e, API_OK); if (e != API_OK) return result->set_value(UNDEF); // Convenience. constexpr auto BACKUP = SyncConfig::TYPE_BACKUP; constexpr auto TWOWAY = SyncConfig::TYPE_TWOWAY; // Generate config for the new sync. auto config = SyncConfig(LocalPath::fromAbsolutePath(path_u8string(rootPath_)), path_u8string(rootPath_), remoteHandle, remotePath, fsfp_t(), LocalPath(), true, isBackup ? BACKUP : TWOWAY); // Sanity check. EXPECT_TRUE(remoteIsShare || remotePath.substr(0, 1) == "/") << "config.mOriginalPathOfRemoteRootNode: " << remotePath; // Are we dealing with an external backup sync? if (!drivePath_.empty()) { // Then make sure we specify where the external drive can be found. config.mExternalDrivePath = LocalPath::fromAbsolutePath(path_u8string(drivePath_)); } if (gScanOnly) { config.mChangeDetectionMethod = CDM_PERIODIC_SCANNING; config.mScanIntervalSec = SCAN_INTERVAL_SEC; } auto completion = [result](error e, SyncError se, handle id) { EXPECT_EQ(e, API_OK); EXPECT_NE(id, UNDEF); EXPECT_EQ(se, NO_SYNC_ERROR); result->set_value(id); }; LOG_debug << "Asking engine to add the sync..."; LOG_debug << "Local sync root will be: " << config.mLocalPath.toPath(false); if (!drivePath_.empty()) { LOG_debug << "External drive will be: " << config.mExternalDrivePath.toPath(false); } auto logName = syncOptions.logName; if (logName.empty()) { logName = rootPath + " "; } client.addsync(std::move(config), std::move(completion), logName, path_u8string(excludePath_)); }; // Do we need to upload an ignore file? #ifdef SRW_NEEDED_FOR_THIS_ONE if (syncOptions.uploadIgnoreFile) { auto ignorePath = fsBasePath / ".megaignore"; // Create the ignore file. auto created = createFile(ignorePath, "+sync:.megaignore"); EXPECT_TRUE(created); if (!created) return result->set_value(UNDEF); LOG_debug << "Uploading initial megaignore file..."; // Upload the ignore file. uploadFile(ignorePath, remoteNode.get(), std::move(completion)); // Completion function will continue the work. return; } #endif LOG_debug << "Making sure we've received latest cloud changes..."; // Make sure the client's received all its action packets. //catchup(std::move(completion)); WaitMillisec(1000); completion(API_OK); WaitMillisec(1000); } void StandardClient::importSyncConfigs(string configs, PromiseBoolSP result) { auto completion = [result](error e) { result->set_value(!e); }; client.importSyncConfigs(configs.c_str(), std::move(completion)); } bool StandardClient::importSyncConfigs(string configs) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.importSyncConfigs(configs, result); }, __FILE__, __LINE__); return result.get(); } string StandardClient::exportSyncConfigs() { auto result = thread_do([](MegaClient& client, PromiseStringSP result) { auto configs = client.syncs.exportSyncConfigs(); result->set_value(configs); }, __FILE__, __LINE__); return result.get(); } void StandardClient::delSync_inthread(handle backupId, PromiseBoolSP result) { client.syncs.deregisterThenRemoveSyncById(backupId, [=](Error error) { result->set_value(error == API_OK); }); } bool StandardClient::recursiveConfirm(Model::ModelNode* mn, Node* n, int& descendants, const string& identifier, int depth, bool& firstreported, bool expectFail, bool skipIgnoreFile) { // top level names can differ so we don't check those if (!mn || !n) return false; if (depth) { if (!CloudNameLess().equal(mn->cloudName(), n->displayname())) { out() << "Node name mismatch: " << mn->path() << " " << n->displaypath(); return false; } } if (!mn->typematchesnodetype(n->type)) { out() << "Node type mismatch: " << mn->path() << ":" << mn->type << " " << n->displaypath() << ":" << n->type; return false; } if (n->type == FILENODE) { // not comparing any file versioning (for now) return true; } multimap ms; for (auto& m : mn->kids) { if (m->fsOnly) continue; if (skipIgnoreFile && IGNORE_FILE_NAME == m->cloudName()) continue; ms.emplace(m->cloudName(), m.get()); } multimap ns; sharedNode_list children = client.getChildren(n); for (auto& n2 : children) { if (skipIgnoreFile && std::string(IGNORE_FILE_NAME) == n2->displayname()) continue; ns.emplace(n2->displayname(), n2.get()); } int matched = 0; vector matchedlist; for (auto m_iter = ms.begin(); m_iter != ms.end(); ) { if (!depth && m_iter->first == DEBRISFOLDER) { m_iter = ms.erase(m_iter); // todo: add checks of the remote debris folder later continue; } auto er = ns.equal_range(m_iter->first); auto next_m = m_iter; ++next_m; bool any_equal_matched = false; for (auto i = er.first; i != er.second; ++i) { int rdescendants = 0; if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1, firstreported, expectFail, skipIgnoreFile)) { ++matched; matchedlist.push_back(m_iter->first); ns.erase(i); ms.erase(m_iter); descendants += rdescendants; any_equal_matched = true; break; } } if (!any_equal_matched) { break; } m_iter = next_m; } if (ns.empty() && ms.empty()) { descendants += matched; return true; } else if (!firstreported && !expectFail) { ostringstream ostream; firstreported = true; ostream << clientname << " " << identifier << " after matching " << matched << " child nodes ["; for (auto& ml : matchedlist) ostream << ml << " "; ostream << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; for (auto& m : ms) ostream << " " << m.first; ostream << " and unmatched remote nodes:"; for (auto& i : ns) ostream << " " << i.first; out() << ostream.str(); EXPECT_TRUE(false) << ostream.str(); }; return false; } auto StandardClient::equal_range_utf8EscapingCompare(multimap& ns, const string& cmpValue, bool unescapeValue, bool unescapeMap, bool caseInsensitive) -> std::pair::iterator, multimap::iterator> { // first iter not less than cmpValue auto iter1 = ns.begin(); while (iter1 != ns.end() && compareUtf(iter1->first, unescapeMap, cmpValue, unescapeValue, caseInsensitive) < 0) ++iter1; // second iter greater then cmpValue auto iter2 = iter1; while (iter2 != ns.end() && compareUtf(iter2->first, unescapeMap, cmpValue, unescapeValue, caseInsensitive) <= 0) ++iter2; return {iter1, iter2}; } bool StandardClient::recursiveConfirm(Model::ModelNode* mn, LocalNode* n, int& descendants, const string& identifier, int depth, bool& firstreported, bool expectFail, bool skipIgnoreFile) { // top level names can differ so we don't check those if (!mn || !n) return false; if (depth) { if (0 != compareUtf(mn->fsName(), true, n->localname, true, false)) { out() << "LocalNode name mismatch: " << mn->fsPath() << " " << n->localname.toPath(false); return false; } } if (!mn->typematchesnodetype(n->type)) { out() << "LocalNode type mismatch: " << mn->fsPath() << ":" << mn->type << " " << n->localname.toPath(false) << ":" << n->type; return false; } auto localpath = n->getLocalPath().toName(*client.fsaccess); string n_localname = n->localname.toName(*client.fsaccess); if (n_localname.size() && n->parent) { EXPECT_EQ(compareUtf(mn->fsName(), true, n->localname, true, false), 0) << "Localnode's localname vs model node fsname mismatch: '" << n->localname.toPath(false) << "', '" << mn->fsName() << "'"; } if (localNodesMustHaveNodes) { if (n->syncedCloudNodeHandle.isUndef()) { EXPECT_TRUE(!n->syncedCloudNodeHandle.isUndef()) << "expected synced non-undef handle at localnode: " << n->getLocalPath().toPath(false); return false; } if (!client.nodeByHandle(n->syncedCloudNodeHandle)) { EXPECT_TRUE(!!client.nodeByHandle(n->syncedCloudNodeHandle)) << "expected synced handle that looks up node at localnode: " << n->getLocalPath().toPath(false); return false; } } std::shared_ptr syncedNode = client.nodeByHandle(n->syncedCloudNodeHandle); if (depth && syncedNode) { EXPECT_EQ(compareUtf(mn->cloudName(), false, syncedNode->displayname(), false, false), 0) << "Localnode's associated Node vs model node name mismatch: '" << syncedNode->displayname() << "', '" << mn->cloudName() << "'"; } if (depth && mn->parent) { EXPECT_EQ(mn->parent->type, Model::ModelNode::folder); EXPECT_EQ(n->parent->type, FOLDERNODE); string parentpath = n->parent->getLocalPath().toName(*client.fsaccess); EXPECT_EQ(localpath.substr(0, parentpath.size()), parentpath); } std::shared_ptr parentSyncedNode = n->parent ? client.nodeByHandle(n->parent->syncedCloudNodeHandle) : nullptr; if (syncedNode && n->parent && parentSyncedNode) { string p = syncedNode->displaypath(); string pp = parentSyncedNode->displaypath(); EXPECT_EQ(p.substr(0, pp.size()), pp); EXPECT_EQ(parentSyncedNode, syncedNode->parent); } multimap ms; multimap ns; for (auto& m : mn->kids) { if (m->fsOnly) continue; if (skipIgnoreFile && m->fsName() == IGNORE_FILE_NAME) continue; ms.emplace(m->fsName(), m.get()); } for (auto& n2 : n->children) { if (skipIgnoreFile && n2.second->isIgnoreFile()) continue; ns.emplace(n2.second->localname.toPath(false), n2.second); // todo: should LocalNodes marked as deleted actually have been removed by now? } int matched = 0; vector matchedlist; for (auto m_iter = ms.begin(); m_iter != ms.end(); ) { if (!depth && m_iter->first == DEBRISFOLDER) { m_iter = ms.erase(m_iter); // todo: are there LocalNodes representing the trash? continue; } auto er = equal_range_utf8EscapingCompare(ns, m_iter->first, true, true, n->sync->mCaseInsensitive); //auto er = ns.equal_range(m_iter->first); auto next_m = m_iter; ++next_m; bool any_equal_matched = false; for (auto i = er.first; i != er.second; ++i) { int rdescendants = 0; if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1, firstreported, expectFail, skipIgnoreFile)) { ++matched; matchedlist.push_back(m_iter->first); ns.erase(i); ms.erase(m_iter); descendants += rdescendants; any_equal_matched = true; break; } } if (!any_equal_matched) { break; } m_iter = next_m; } if (ns.empty() && ms.empty()) { return true; } else if (!firstreported && !expectFail) { ostringstream ostream; firstreported = true; ostream << clientname << " " << identifier << " after matching " << matched << " child nodes ["; for (auto& ml : matchedlist) ostream << ml << " "; ostream << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; for (auto& m : ms) ostream << " " << m.first; ostream << " and unmatched LocalNodes:"; for (auto& i : ns) ostream << " " << i.first; out() << ostream.str(); EXPECT_TRUE(false) << ostream.str(); }; return false; } bool StandardClient::recursiveConfirm(Model::ModelNode* mn, fs::path p, int& descendants, const string& identifier, int depth, bool ignoreDebris, bool& firstreported, bool expectFail, bool skipIgnoreFile) { struct Comparator { bool operator()(const string& lhs, const string& rhs) const { return compare(lhs, rhs) < 0; } int compare(const string& lhs, const string& rhs) const { return compareUtf(lhs, true, rhs, true, false); } }; // Comparator static Comparator comparator; if (!mn) return false; if (depth) { if (comparator.compare(path_u8string(p.filename()), mn->fsName())) { out() << "filesystem name mismatch: " << mn->path() << " " << p; return false; } } nodetype_t pathtype = fs::is_directory(p) ? FOLDERNODE : fs::is_regular_file(p) ? FILENODE : TYPE_UNKNOWN; if (!mn->typematchesnodetype(pathtype)) { out() << "Path type mismatch: " << mn->path() << ":" << mn->type << " " << path_u8string(p) << ":" << pathtype; return false; } if (pathtype == FILENODE && path_u8string(p.filename()) != "lock") { if (localFSFilesThatMayDiffer.find(p) == localFSFilesThatMayDiffer.end()) { ifstream fs(p, ios::binary); std::vector buffer; buffer.resize(mn->content.size() + 1024); fs.read(reinterpret_cast(buffer.data()), static_cast(buffer.size())); EXPECT_EQ(size_t(fs.gcount()), mn->content.size()) << " file is not expected size " << p; EXPECT_TRUE(!memcmp(buffer.data(), mn->content.data(), mn->content.size())) << " file data mismatch " << p; } } if (pathtype != FOLDERNODE) { return true; } multimap ms; multimap ps; for (auto& m : mn->kids) { if (skipIgnoreFile && m->fsName() == IGNORE_FILE_NAME) continue; ms.emplace(m->fsName(), m.get()); } for (fs::directory_iterator pi(p); pi != fs::directory_iterator(); ++pi) { auto name = path_u8string(pi->path().filename()); if (skipIgnoreFile && name == IGNORE_FILE_NAME) continue; ps.emplace(std::move(name), pi->path()); } if (ignoreDebris && depth == 0) { ms.erase(DEBRISFOLDER); ps.erase(DEBRISFOLDER); } else if (depth == 1 && mn->name == DEBRISFOLDER) { ms.erase("tmp"); ps.erase("tmp"); } else if (depth == 0) { // with ignore files, most tests now involve a download somewhere which means debris/tmp is created. // it only matters if the content of these differs, absence or empty is effectively the same if (ms.find(DEBRISFOLDER) == ms.end()) { auto d = mn->addkid(); d->name = DEBRISFOLDER; d->type = Model::ModelNode::folder; ms.emplace(DEBRISFOLDER, d); } if (ps.find(DEBRISFOLDER) == ps.end()) { auto pdeb = p / fs::path(DEBRISFOLDER); fs::create_directory(pdeb); ps.emplace(DEBRISFOLDER, pdeb); } } int matched = 0; vector matchedlist; for (auto m_iter = ms.begin(); m_iter != ms.end(); ) { auto er = ps.equal_range(m_iter->first); auto next_m = m_iter; ++next_m; bool any_equal_matched = false; for (auto i = er.first; i != er.second; ++i) { int rdescendants = 0; if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1, ignoreDebris, firstreported, expectFail, skipIgnoreFile)) { ++matched; matchedlist.push_back(m_iter->first); ps.erase(i); ms.erase(m_iter); descendants += rdescendants; any_equal_matched = true; break; } } if (!any_equal_matched) { break; } m_iter = next_m; } //if (ps.size() == 1 && !mn->parent && ps.begin()->first == DEBRISFOLDER) //{ // ps.clear(); //} if (ps.empty() && ms.empty()) { return true; } else if (!firstreported && !expectFail) { ostringstream ostream; firstreported = true; ostream << clientname << " " << identifier << " after matching " << matched << " child nodes ["; for (auto& ml : matchedlist) ostream << ml << " "; ostream << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; for (auto& m : ms) ostream << " " << m.first; ostream << " and unmatched filesystem paths:"; for (auto& i : ps) ostream << " " << i.second.filename(); ostream << " in " << p; out() << ostream.str(); EXPECT_TRUE(false) << ostream.str(); }; return false; } Sync* StandardClient::syncByBackupId(handle backupId) { return client.syncs.runningSyncByBackupIdForTests(backupId); } bool StandardClient::setSyncPausedByBackupId(handle id, bool pause) { PromiseBoolSP result = makeSharedPromise(); if (pause) { disableSync(id, NO_SYNC_ERROR, false /* enabled */, true /* keepSyncDB */, result); } else { client.syncs.enableSyncByBackupId( id, false, [result](error e, SyncError, handle) { result->set_value(!e); }, id > 0, ""); } return debugTolerantWaitOnFuture(result->get_future(), 45); } void StandardClient::enableSyncByBackupId(handle id, PromiseBoolSP result, const string& logname) { client.syncs.enableSyncByBackupId( id, true, [result](error e, SyncError, handle) { result->set_value(!e); }, id > 0, logname); } bool StandardClient::enableSyncByBackupId(handle id, const string& logname) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.enableSyncByBackupId(id, result, logname); }, __FILE__, __LINE__); return result.get(); } handle StandardClient::backupIdForSyncPath(fs::path path) { auto localPath = LocalPath::fromAbsolutePath(path_u8string(path)); auto configs = client.syncs.getConfigs(false); for (auto& sc : configs) { if (sc.mLocalPath == localPath) return sc.mBackupId; } return UNDEF; } bool StandardClient::confirmModel_mainthread(handle id, Model::ModelNode* mRoot, Node* rRoot, bool expectFail, bool skipIgnoreFile) { auto result = thread_do( [=](StandardClient& client, PromiseBoolSP result) { result->set_value(client.confirmModel(id, mRoot, rRoot, expectFail, skipIgnoreFile)); }, __FILE__, __LINE__); return result.get(); } bool StandardClient::confirmModel_mainthread(handle id, Model::ModelNode* mRoot, LocalNode* lRoot, bool expectFail, bool skipIgnoreFile) { auto result = thread_do( [=](StandardClient& client, PromiseBoolSP result) { result->set_value(client.confirmModel(id, mRoot, lRoot, expectFail, skipIgnoreFile)); }, __FILE__, __LINE__); return result.get(); } bool StandardClient::confirmModel_mainthread(handle id, Model::ModelNode* mRoot, fs::path lRoot, bool ignoreDebris, bool expectFail, bool skipIgnoreFile) { auto result = thread_do( [=](StandardClient& client, PromiseBoolSP result) { result->set_value(client.confirmModel(id, mRoot, lRoot, ignoreDebris, expectFail, skipIgnoreFile)); }, __FILE__, __LINE__); return result.get(); } bool StandardClient::confirmModel(handle id, Model::ModelNode* mRoot, Node* rRoot, bool expectFail, bool skipIgnoreFile) { string name = "Sync " + toHandle(id); int descendents = 0; bool reported = false; if (!recursiveConfirm(mRoot, rRoot, descendents, name, 0, reported, expectFail, skipIgnoreFile)) { out() << clientname << " syncid " << toHandle(id) << " comparison against remote nodes failed"; return false; } return true; } bool StandardClient::confirmModel(handle id, Model::ModelNode* mRoot, LocalNode* lRoot, bool expectFail, bool skipIgnoreFile) { string name = "Sync " + toHandle(id); int descendents = 0; bool reported = false; if (!recursiveConfirm(mRoot, lRoot, descendents, name, 0, reported, expectFail, skipIgnoreFile)) { out() << clientname << " syncid " << toHandle(id) << " comparison against LocalNodes failed"; return false; } return true; } bool StandardClient::confirmModel(handle id, Model::ModelNode* mRoot, fs::path lRoot, bool ignoreDebris, bool expectFail, bool skipIgnoreFile) { string name = "Sync " + toHandle(id); int descendents = 0; bool reported = false; if (!recursiveConfirm(mRoot, lRoot, descendents, name, 0, ignoreDebris, reported, expectFail, skipIgnoreFile)) { out() << clientname << " syncid " << toHandle(id) << " comparison against local filesystem failed"; return false; } return true; } bool StandardClient::confirmModel(handle backupId, Model::ModelNode* mnode, const int confirm, const bool ignoreDebris, bool expectFail, bool skipIgnoreFile) { SyncInfo si; if (!syncSet(backupId, si)) { out() << clientname << " backupId " << toHandle(backupId) << " not found "; return false; } // compare model against nodes representing remote state if ((confirm & CONFIRM_REMOTE) && !confirmModel(backupId, mnode, client.nodeByHandle(si.h).get(), expectFail, skipIgnoreFile)) { return false; } // Get our hands on the sync. auto* sync = syncByBackupId(backupId); // compare model against LocalNodes if (sync) { if ((confirm & CONFIRM_LOCALNODE) && !confirmModel(backupId, mnode, sync->localroot.get(), expectFail, skipIgnoreFile)) { return false; } } // compare model against local filesystem if ((confirm & CONFIRM_LOCALFS) && !confirmModel(backupId, mnode, si.localpath, ignoreDebris, expectFail, skipIgnoreFile)) { return false; } #ifdef SRW_NEEDED_FOR_THIS_ONE // Does this sync have a state cache? if (!sync) return true; string statecachename = sync->getConfig().getSyncDbStateCacheName(sync->localroot->fsid_lastSynced, sync->getConfig().mRemoteNode, client.me); StateCacheValidator validator; // Try and load the state cache. EXPECT_TRUE(validator.load(client, statecachename)) << "Sync " << toHandle(backupId) << ": Unable to load state cache: " << statecachename; // Does the state cache accurately reflect the LNT in memory? EXPECT_TRUE(validator.compare(*sync->localroot)) << "Sync " << toHandle(backupId) << ": State cache mismatch."; #endif return true; } void StandardClient::prelogin_result(int, string*, string* salt, error e) { out() << clientname << " Prelogin: " << e; if (!e) { this->salt = *salt; } resultproc.processresult(PRELOGIN, e, UNDEF, client.restag); } void StandardClient::login_result(error e) { out() << clientname << " Login: " << e; resultproc.processresult(LOGIN, e, UNDEF, client.restag); } void StandardClient::fetchnodes_result(const Error& e) { out() << clientname << " Fetchnodes: " << e; resultproc.processresult(FETCHNODES, e, UNDEF, client.restag); } bool StandardClient::setattr(const CloudItem& item, attr_map&& updates) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) mutable { client.setattr(std::move(item), std::move(updates), result); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(90)); // allow for up to 60 seconds of unexpected -3s on all channels EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::setattr(const CloudItem& item, attr_map&& updates, PromiseBoolSP result) { resultproc.prepresult( COMPLETION, ++next_request_tag, [=, this]() { auto node = item.resolve(*this); if (!node) return result->set_value(false); client.setattr( node, attr_map(updates), [result](NodeHandle, error e) { result->set_value(!e); }, false); }, nullptr); } bool StandardClient::rename(const CloudItem& item, const string& newName) { return setattr(item, attr_map('n', newName)); } void StandardClient::unlink_result(handle h, error e) { resultproc.processresult(UNLINK, e, h, client.restag); } void StandardClient::putnodes_result(const Error& e, targettype_t, vector& nn, bool /*targetOverride*/, int tag, const map& /*fileHandles*/) { resultproc.processresult(PUTNODES, e, nn.empty() ? UNDEF : nn[0].mAddedHandle, tag); } void StandardClient::catchup_result() { resultproc.processresult(CATCHUP, error(API_OK), UNDEF, client.restag); } void StandardClient::disableSync(handle id, SyncError error, bool enabled, bool keepSyncDB, PromiseBoolSP result) { client.syncs.disableSyncByBackupId(id, error, enabled, keepSyncDB, [result](){ result->set_value(true); }); } bool StandardClient::disableSync(handle id, SyncError error, bool enabled, bool keepSyncDB) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.disableSync(id, error, enabled, keepSyncDB, result); }, __FILE__, __LINE__); return result.get(); } void StandardClient::deleteremote(const CloudItem& item, PromiseBoolSP result) { auto node = item.resolve(*this); if (!node) return result->set_value(false); client.unlink(node.get(), false, 0, false, [result](NodeHandle, Error e) { result->set_value(e == API_OK); }); } bool StandardClient::deleteremote(const CloudItem& item) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.deleteremote(item, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::deleteremotedebris() { return withWait([&](PromiseBoolSP result) { deleteremotedebris(result); }); } void StandardClient::deleteremotedebris(PromiseBoolSP result) { if (auto debris = getsyncdebrisnode()) { deleteremotenodes({debris}, std::move(result)); } else { result->set_value(true); } } void StandardClient::deleteremotenodes(vector > ns, PromiseBoolSP pb) { if (ns.empty()) { pb->set_value(true); } else { for (size_t i = ns.size(); i--; ) { auto completion = [i, pb](NodeHandle, Error e) { if (!i) pb->set_value(!e); }; resultproc.prepresult(COMPLETION, ++next_request_tag, [&](){ client.unlink(ns[i].get(), false, 0, false, std::move(completion)); }, nullptr); } } } bool StandardClient::movenode(const CloudItem& source, const CloudItem& target, const string& newName) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.movenode(source, target, newName, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::movenode(const CloudItem& source, const CloudItem& target, const string& newName, PromiseBoolSP result) { auto sourceNode = source.resolve(*this); if (!sourceNode) return result->set_value(false); auto targetNode = target.resolve(*this); if (!targetNode) return result->set_value(false); auto completion = [result](NodeHandle, Error e) { result->set_value(e == API_OK); }; client.rename(sourceNode, targetNode, SYNCDEL_NONE, NodeHandle(), newName.empty() ? nullptr : newName.c_str(), false, std::move(completion)); } void StandardClient::movenodetotrash(string path, PromiseBoolSP pb) { std::shared_ptr n = drillchildnodebyname(gettestbasenode(), path); std::shared_ptr p = getcloudrubbishnode(); if (n && p && n->parent) { resultproc.prepresult( COMPLETION, ++next_request_tag, [pb, n, p, this]() { client.rename(n, p, SYNCDEL_NONE, NodeHandle(), nullptr, false, [pb](NodeHandle, Error e) { pb->set_value(!e); }); }, nullptr); return; } out() << "node or rubbish or node parent not found"; pb->set_value(false); } void StandardClient::exportnode(std::shared_ptr n, int del, m_time_t expiry, bool writable, bool megaHosted, promise& pb) { resultproc.prepresult( COMPLETION, ++next_request_tag, [&]() { error e = client.exportnode(n, del, expiry, writable, megaHosted, client.reqtag, [&](Error e, handle, handle, string&&) { pb.set_value(e); }); if (e) { pb.set_value(e); } }, nullptr); // no need to match callbacks with requests when we use completion functions } void StandardClient::getpubliclink(Node* n, int del, m_time_t expiry, bool writable, bool megaHosted, promise& pb) { resultproc.prepresult( COMPLETION, ++next_request_tag, [&]() { client.requestPublicLink(n, del, expiry, writable, megaHosted, client.reqtag, [&](Error e, handle, handle, string&&) { pb.set_value(e); }); }, nullptr); } void StandardClient::waitonsyncs(chrono::seconds d) { auto start = chrono::steady_clock::now(); for (;;) { bool any_add_del = client.syncs.syncBusyState; thread_do( [&any_add_del, this](StandardClient&, PromiseBoolSP pb) { if (!client.multi_transfers[GET].empty() || !client.multi_transfers[PUT].empty()) { any_add_del = true; } pb->set_value(true); }, __FILE__, __LINE__) .get(); if (any_add_del || debugging) { start = chrono::steady_clock::now(); } if (((chrono::steady_clock::now() - start) > d) && ((chrono::steady_clock::now() - lastcb) > d)) { break; } WaitMillisec(500); } } void StandardClient::syncproblemsDetected(SyncProblems& problems) { PromiseBoolSP pb(new promise()); client.syncs.syncRun( [&]() { problems.mStallsDetected = client.syncs.stallsDetected(problems.mStalls); problems.mConflictsDetected = client.syncs.conflictsDetected(problems.mConflictsMap); pb->set_value(true); }, "StandardClient::syncproblemsDetected"); EXPECT_TRUE(debugTolerantWaitOnFuture(pb->get_future(), 45)); } bool StandardClient::conflictsDetected(list& conflicts) { PromiseBoolSP pb(new promise()); bool result = false; client.syncs.syncRun( [&]() { SyncIDtoConflictInfoMap auxconflicts; result = client.syncs.conflictsDetected(auxconflicts); for (auto& conflict: auxconflicts) { conflicts.insert(conflicts.end(), conflict.second.begin(), conflict.second.end()); } pb->set_value(true); }, "StandardClient::conflictsDetected"); EXPECT_TRUE(debugTolerantWaitOnFuture(pb->get_future(), 45)); return result; } bool StandardClient::stallsDetected(SyncStallInfoTests& stalls) { PromiseBoolSP pb(new promise()); bool result = false; client.syncs.syncRun([&](){ SyncStallInfo syncStalls; result = client.syncs.stallsDetected(syncStalls); result |= !stalls.empty(); if (result) { stalls.extractFrom(syncStalls); } pb->set_value(true); }, "StandardClient::stallsDetected"); EXPECT_TRUE(debugTolerantWaitOnFuture(pb->get_future(), 45)); return result; } bool StandardClient::syncStallDetected(SyncStallInfoTests& si) const { si.clear(); SyncStallInfo syncStalls; if (client.syncs.syncStallDetected(syncStalls)) { si.extractFrom(syncStalls); return true; } return false; } bool StandardClient::login_reset(bool noCache) { return login_reset("MEGA_EMAIL", "MEGA_PWD", noCache); } bool StandardClient::login_reset(const string& user, const string& pw, bool noCache, bool resetBaseCloudFolder) { received_user_alerts = false; future p1; p1 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.preloginFromEnv(user, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "preloginFromEnv failed"; return false; } p1 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.loginFromEnv(user, pw, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "loginFromEnv failed"; return false; } p1 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.fetchnodes(noCache, true, false, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "fetchnodes failed"; return false; } EXPECT_TRUE(waitForUserAlertsUpdated(30)); p1 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.upgradeSecurity(pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "upgrading security failed"; return false; } if (resetBaseCloudFolder) { if (deleteTestBaseFolder(true) == 0) { out() << "deleteTestBaseFolder failed"; return false; } p1 = thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.ensureTestBaseFolder(true, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "ensureTestBaseFolder failed"; return false; } } return true; } bool StandardClient::resetBaseFolderMulticlient(StandardClient* c2, StandardClient* c3, StandardClient* c4) { auto resetActionPacketFlags = [this, c2, c3, c4]() { received_node_actionpackets = false; if (c2) c2->received_node_actionpackets = false; if (c3) c3->received_node_actionpackets = false; if (c4) c4->received_node_actionpackets = false; }; auto waitForActionPackets = [this, c2, c3, c4]() { if (!waitForNodesUpdated(45)) return false; if (c2 && !c2->waitForNodesUpdated(45)) return false; if (c3 && !c3->waitForNodesUpdated(45)) return false; if (c4 && !c4->waitForNodesUpdated(45)) return false; return true; }; resetActionPacketFlags(); switch (deleteTestBaseFolder(true)) { case 0: out() << "deleteTestBaseFolder failed"; return false; case 2: if (!waitForActionPackets()) { out() << "No actionpacket received in at least one client for base folder deletion."; return false; } break; default: break; } resetActionPacketFlags(); auto p1 = thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.ensureTestBaseFolder(true, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "ensureTestBaseFolder failed"; return false; } if (!waitForActionPackets()) { out() << "No actionpacket received in at least one client for base folder creation"; return false; } auto checkOtherClient = [this](StandardClient* c, bool finalcheck) { if (c) { auto p1 = c->thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.ensureTestBaseFolder(false, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { if (finalcheck) { out() << "ensureTestBaseFolder c2 failed"; } return false; } if (c->basefolderhandle != basefolderhandle) { if (finalcheck) { out() << "base folder handle mismatch with c2"; } return false; } } return true; }; // although we waited for actionpackets, it's possible that wait was satisfied by some late actionpacket from a prior test. // wait a bit longer if it's not there already for (int i = 60; i--; ) { if (checkOtherClient(c2, false) && checkOtherClient(c3, false) && checkOtherClient(c4, false)) return true; WaitMillisec(1000); } if (!checkOtherClient(c2, true)) return false; if (!checkOtherClient(c3, true)) return false; if (!checkOtherClient(c4, true)) return false; return true; } std::function RequestsCompleted() { return [](StandardClient& client) { return client.requestsCompleted(); }; } std::function TransfersCompleted(direction_t type) { return [type](StandardClient& client) { return client.transfersCompleted(type); }; } void StandardClient::cleanupForTestReuse(int loginIndex) { // remove .megaignore.default in case other tests left one lying around auto defaultignorepath = client.dbaccess->rootPath(); defaultignorepath.appendWithSeparator(LocalPath::fromRelativePath(".megaignore.default"), false); std::error_code ec; fs::remove(defaultignorepath.toPath(false), ec); if (client.nodeByPath("/abort_jenkins_test_run")) { [[maybe_unused]] const auto [user, _] = getEnvVarAccounts().getVarValues(static_cast(loginIndex)); cout << "Detected node /abort_jenkins_test_run in account " << user << ", aborting test run" << endl; out() << "Detected node /abort_jenkins_test_run in account " << user << ", aborting test run"; WaitMillisec(100); exit(1); } LOG_debug << clientname << "cleaning syncs for client reuse"; future p1; p1 = thread_do( [=, this](StandardClient& sc, PromiseBoolSP pb) { sc.client.syncs.prepareForLogout(false, [this, pb]() { // 3rd param true to "load" (zero) syncs again so // store is ready for the test client.syncs.locallogout(true, false, true); pb->set_value(true); }); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "removeSelectedSyncs failed"; } assert(client.syncs.getConfigs(false).empty()); // delete everything from Vault std::atomic requestcount{0}; p1 = thread_do( [=, &requestcount](StandardClient& sc, PromiseBoolSP) { if (auto vault = sc.client.nodeByHandle(sc.client.mNodeManager.getRootNodeVault())) { for (auto n: sc.client.mNodeManager.getChildren(vault.get())) { LOG_debug << "vault child: " << n->displaypath(); for (auto& n2: sc.client.mNodeManager.getChildren(n.get())) { LOG_debug << "Unlinking: " << n2->displaypath(); ++requestcount; sc.client.unlink(n2.get(), false, 0, true, [&requestcount](NodeHandle, Error) { --requestcount; }); } } } }, __FILE__, __LINE__); // delete everything from //bin p1 = thread_do( [=, &requestcount](StandardClient& sc, PromiseBoolSP) { if (auto bin = sc.client.nodeByHandle(sc.client.mNodeManager.getRootNodeRubbish())) { for (auto n: sc.client.mNodeManager.getChildren(bin.get())) { LOG_debug << "Unlinking from bin: " << n->displaypath(); ++requestcount; sc.client.unlink(n.get(), false, 0, false, [&requestcount](NodeHandle, Error) { --requestcount; }); } } }, __FILE__, __LINE__); int limit = 100; while (requestcount.load() > 0 && --limit) { WaitMillisec(100); } mStallDetected.store(false); mConflictsDetected.store(false); mTotalStallsUpdated.store(false); mTotalConflictsUpdated.store(false); // Nuke any custom exclusion rules. client.syncs.mNewSyncFilterChain.reset(); // Make sure any throttles are reset. setDownloadSpeed(0); setUploadSpeed(0); // Make sure any event handlers are released. // TODO: May require synchronization? mOnConflictsDetected = nullptr; mOnFileAdded = nullptr; mOnFileComplete = nullptr; mOnStall = nullptr; mOnSyncStateConfig = nullptr; mOnTransferAdded = nullptr; onTransferCompleted = nullptr; mOnTotalConflictsUpdate = nullptr; mOnTotalStallsUpdate = nullptr; #ifndef NDEBUG // match the conditon in the header mOnMoveBegin = nullptr; #endif LOG_debug << clientname << "cleaning transfers for client reuse"; future p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { CancelToken cancelled(true); int direction[] = { PUT, GET }; for (int d = 0; d < 2; ++d) { for (auto& it : sc.client.multi_transfers[direction[d]]) { for (auto& it2 : it.second->files) { if (!it2->syncxfer) { it2->cancelToken = cancelled; } } } } pb->set_value(true); }, __FILE__, __LINE__); if (!waitonresults(&p2)) { out() << "transfer removal failed"; } // wait for completion of ongoing transfers, up to 30s waitFor(TransfersCompleted(GET), std::chrono::seconds(30)); waitFor(TransfersCompleted(PUT), std::chrono::seconds(30)); LOG_debug << clientname << "transfers cleaned"; // wait further for reqs to finish if any are queued, up to 30s waitFor(TransfersCompleted(PUT), std::chrono::seconds(30)); // check transfers were canceled successfully if (transfersCompleted(GET) && transfersCompleted(PUT)) { LOG_debug << clientname << "transfers cleaned successfully"; } else { LOG_err << clientname << "Failed to clean transfers at cleanupForTestReuse():" << " put: " << client.multi_transfers[PUT].size() << " get: " << client.multi_transfers[GET].size(); } // wait for cmds in flight and queued, up to 120s if (!requestsCompleted()) { LOG_debug << clientname << "waiting for requests to finish"; waitFor(RequestsCompleted(), std::chrono::seconds(120)); } // check any pending command was completed if (requestsCompleted()) { LOG_debug << clientname << "requests cleaned successfully"; } else { LOG_err << clientname << "Failed to clean pending commands at cleanupForTestReuse():" << " pending: " << client.reqs.readyToSend() << " inflight: " << client.reqs.cmdsInflight(); } } bool StandardClient::login_reset_makeremotenodes(const string& prefix, int depth, int fanout, bool noCache) { return login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", prefix, depth, fanout, noCache); } bool StandardClient::login_reset_makeremotenodes(const string& user, const string& pw, const string& prefix, int depth, int fanout, bool noCache) { if (!login_reset(user, pw, noCache)) { out() << "login_reset failed"; return false; } future p1 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.makeCloudSubdirs(prefix, depth, fanout, pb); }, __FILE__, __LINE__); if (!waitonresults(&p1)) { out() << "makeCloudSubdirs failed"; return false; } return true; } bool StandardClient::login(const string& user, const string& pw) { future p; p = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.preloginFromEnv(user, pb); }, __FILE__, __LINE__); if (!waitonresults(&p)) return false; p = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.loginFromEnv(user, pw, pb); }, __FILE__, __LINE__); return waitonresults(&p); } bool StandardClient::login_fetchnodes(const string& user, const string& pw, bool makeBaseFolder, bool noCache) { received_user_alerts = false; future p2; p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.preloginFromEnv(user, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.loginFromEnv(user, pw, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.fetchnodes(noCache, true, false, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; EXPECT_TRUE(waitForUserAlertsUpdated(30)); p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.upgradeSecurity(pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; p2 = thread_do([makeBaseFolder](StandardClient& sc, PromiseBoolSP pb) { sc.ensureTestBaseFolder(makeBaseFolder, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; return true; } bool StandardClient::login_fetchnodesFromSession(const string& session) { future p2; p2 = thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.loginFromSession(session, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; p2 = thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.fetchnodes(false, true, false, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; p2 = thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.ensureTestBaseFolder(false, pb); }, __FILE__, __LINE__); if (!waitonresults(&p2)) return false; return true; } bool StandardClient::delSync_mainthread(handle backupId) { future fb = thread_do([=](StandardClient& mc, PromiseBoolSP pb) { mc.delSync_inthread(backupId, pb); }, __FILE__, __LINE__); return fb.get(); } bool StandardClient::confirmModel_mainthread(Model::ModelNode* mnode, handle backupId, bool ignoreDebris, int confirm, bool expectFail, bool skipIgnoreFile) { return thread_do([=](StandardClient& sc, PromiseBoolSP pb) { pb->set_value(sc.confirmModel(backupId, mnode, confirm, ignoreDebris, expectFail, skipIgnoreFile)); }, __FILE__, __LINE__).get(); } bool StandardClient::match(handle id, const Model::ModelNode* source) { if (!source) return false; auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.match(id, source, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::match(handle id, const Model::ModelNode* source, PromiseBoolSP result) { SyncInfo info; auto found = syncSet(id, info); EXPECT_TRUE(found); if (!found) return result->set_value(false); const auto destination = client.nodeByHandle(info.h); EXPECT_TRUE(destination); result->set_value(destination && match(*destination, *source)); } bool StandardClient::match(NodeHandle handle, const Model::ModelNode* source) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.match(handle, source, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::match(NodeHandle handle, const Model::ModelNode* source, PromiseBoolSP result) { EXPECT_TRUE(source); if (!source) return result->set_value(false); auto node = client.nodeByHandle(handle); EXPECT_TRUE(node); result->set_value(node && match(*node, *source)); } bool StandardClient::waitFor(std::function predicate, std::chrono::seconds timeout, std::chrono::milliseconds sleepIncrement) { resetClientDowaitDs(); auto total = std::chrono::milliseconds(0); out() << "Waiting for predicate to match..."; for (;;) { if (predicate(*this)) { out() << "Predicate has matched!"; return true; } if (const auto clientDowaitSecs = consumeClientDowaitDs(10); clientDowaitSecs > 0 && timeout < std::chrono::seconds(300)) { LOG_debug << "Adding " << clientDowaitSecs << " secs to timeout"; timeout += std::chrono::seconds(clientDowaitSecs); } if (total >= timeout) { out() << "Timed out waiting for predicate to match."; return false; } std::this_thread::sleep_for(sleepIncrement); total += sleepIncrement; } } bool StandardClient::match(const Node& destination, const Model::ModelNode& source) const { list> pending; auto matched = true; pending.emplace_back(&destination, &source); for ( ; !pending.empty(); pending.pop_front()) { const auto& dn = *pending.front().first; const auto& sn = *pending.front().second; // Nodes must have matching types. if (!sn.typematchesnodetype(dn.type)) { LOG_debug << "Cloud model/type mismatch: " << dn.displaypath() << "(" << dn.type << ")" << " vs. " << sn.path() << "(" << sn.type << ")"; matched = false; continue; } // Files require no further processing. if (dn.type == FILENODE) continue; map dc; map sc; set dd; set sd; // Index children for pairing. sharedNode_list children = dn.client->getChildren(&dn); for (auto& child : children) { string name = child->displayname(); // Duplicate already reported? if (dd.count(name)) { LOG_debug << "Cloud name conflict: " << child->displaypath(); continue; } auto result = dc.emplace(child->displayname(), child.get()); // Didn't exist? No duplicate. if (result.second) continue; LOG_debug << "Cloud name conflict: " << child->displaypath(); // Remmber the duplicate (name conflict.) dc.erase(result.first); dd.emplace(name); matched = false; } for (const auto& child : sn.kids) { auto name = child->cloudName(); // Duplicate already reported? if (dd.count(name)) { LOG_debug << "Model node excluded due to cloud duplicates: " << child->path(); continue; } if (sd.count(name)) { LOG_debug << "Model name conflict: " << child->path(); continue; } auto result = sc.emplace(child->cloudName(), child.get()); // Didn't exist? No duplicate. if (result.second) continue; LOG_debug << "Model name conflict: " << child->path(); // Remember the duplicate. dc.erase(name); sc.erase(result.first); sd.emplace(name); matched = false; } // Pair children. for (const auto& s : sc) { // Skip the debris folder if it appears in the root. if (&sn == &source) { if (CloudNameLess::equal(s.first, DEBRISFOLDER)) { continue; } } // Does this node have a pair in the destination? auto d = dc.find(s.first); matched &= d != dc.end(); // If not then there can be no match. if (d == dc.end()) { LOG_debug << "Model node has no pair in cloud: " << s.second->path(); continue; } // Queue pair for more detailed matching. pending.emplace_back(d->second, s.second); // Consider the destination node paired. dc.erase(d); } // Can't have a match if we couldn't pair all destination nodes. matched &= dc.empty(); // Log which nodes we couldn't match. for (const auto& d : dc) { LOG_debug << "Cloud node has no pair in the model: " << d.second->displaypath(); } } return matched; } bool StandardClient::backupOpenDrive(const fs::path& drivePath) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.backupOpenDrive(drivePath, std::move(result)); }, __FILE__, __LINE__); return result.get(); } void StandardClient::backupOpenDrive(const fs::path& drivePath, PromiseBoolSP result) { auto localDrivePath = LocalPath::fromAbsolutePath(path_u8string(drivePath)); client.syncs.backupOpenDrive(localDrivePath, [result](Error e){ result->set_value(e == API_OK); }); } void StandardClient::triggerPeriodicScanEarly(handle backupID) { client.syncs.triggerPeriodicScanEarly(backupID).get(); } void StandardClient::checkSyncProblems(const handle backupId, const int /*backupIdsCount*/, const unsigned int totalExpectedConflicts, const LocalPath& localPath, const std::string& f1, const std::string& f2) { SyncProblems problems; syncproblemsDetected(problems); auto itCn = problems.mConflictsMap.find(backupId); ASSERT_NE(itCn, problems.mConflictsMap.end()) << "BackupId (" << toHandle(backupId) << ") not found in ConflictsMap"; auto& conflicts = itCn->second; ASSERT_EQ(conflicts.size(), totalExpectedConflicts) << "Unexpected ConflictsMap size"; const auto isExpectedConflict = [&localPath, &f1, &f2](const NameConflict& nc) { return nc.localPath == localPath && std::find(nc.clashingLocalNames.begin(), nc.clashingLocalNames.end(), LocalPath::fromRelativePath(f1)) != nc.clashingLocalNames.end() && std::find(nc.clashingLocalNames.begin(), nc.clashingLocalNames.end(), LocalPath::fromRelativePath(f2)) != nc.clashingLocalNames.end(); }; using namespace testing; EXPECT_THAT(conflicts, Contains(Truly(isExpectedConflict))); } void StandardClient::createHardLink(const fs::path& src, const fs::path& dst, LocalPath& sourcePath, LocalPath& targetPath) { auto fsAccess = client.fsaccess.get(); sourcePath = LocalPath::fromAbsolutePath(path_u8string(src)); targetPath = LocalPath::fromAbsolutePath(path_u8string(dst)); ASSERT_TRUE(fsAccess->hardLink(sourcePath, targetPath)); } void StandardClient::unlinklocal(const LocalPath& localPath) { bool nonTransientErrorFound{false}; const auto result = waitFor( [&](StandardClient& sc) { if (!sc.client.fsaccess->unlinklocal(localPath)) { nonTransientErrorFound = !sc.client.fsaccess->transient_error; return nonTransientErrorFound; } return true; }, DEFAULTWAIT); ASSERT_TRUE(result && !nonTransientErrorFound); } void StandardClient::checkStallIssues(const handle /*backupId*/, const unsigned int expectedStalls, LocalPath& sourcePath, LocalPath& targetPath) { SyncProblems problems; syncproblemsDetected(problems); ASSERT_EQ(problems.mStalls.syncStallInfoMaps.size(), expectedStalls) << "Unexpected syncStallInfoMaps size"; SyncStallInfoTests stalls; stalls.extractFrom(problems.mStalls); ASSERT_FALSE(stalls.local.empty()) << "No stall issues detected"; const auto isExpectedStall = [&sourcePath, &targetPath](const SyncStallEntry& sr) -> bool { return sr.localPath1.localPath == sourcePath && sr.localPath2.localPath == targetPath && sr.reason == SyncWaitReason::FileIssue && sr.localPath1.problem == PathProblem::DetectedHardLink && sr.localPath2.problem == PathProblem::DetectedHardLink; }; using namespace testing; EXPECT_THAT(stalls.local, Contains(Pair(_, Truly(isExpectedStall)))) << "Expected stall issue could not be found"; } handle StandardClient::getNodeHandle(const CloudItem& item) { auto result = thread_do([&](StandardClient& client, PromiseHandleSP result) { client.getNodeHandle(item, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::getNodeHandle(const CloudItem& item, PromiseHandleSP result) { if (auto node = item.resolve(*this)) return result->set_value(node->nodehandle); result->set_value(UNDEF); } FileFingerprint StandardClient::fingerprint(const fs::path& fsPath) { // Convenience. auto& fsAccess = *client.fsaccess; // Needed so we can access the filesystem. auto fileAccess = fsAccess.newfileaccess(false); // Translate input path into something useful. auto path = LocalPath::fromAbsolutePath(path_u8string(fsPath)); FileFingerprint fingerprint; // Try and open file for reading. if (fileAccess->fopen(path, OPEN_RDONLY, FSLogging::logOnError)) { // Generate fingerprint. fingerprint.genfingerprint(fileAccess.get()); } return fingerprint; } vector StandardClient::fingerprints(const string& path) { vector results; // Get our hands on the root node. auto root = gettestbasenode(); if (!root) return results; // Locate the specified node. auto node = drillchildnodebyname(root, path); if (!node) return results; // Directories have no fingerprints. if (node->type != FILENODE) return results; // Extract the fingerprints from the version chain. results.emplace_back(*node); auto nodes = client.mNodeManager.getChildren(node.get()); while (!nodes.empty()) { node = nodes.front(); results.emplace_back(*node); nodes = client.mNodeManager.getChildren(node.get()); } // Pass fingerprints to caller. return results; } void StandardClient::ipcr(handle id, ipcactions_t action, PromiseBoolSP result) { client.updatepcr(id, action, [=](error e, ipcactions_t) { result->set_value(!e); }); } bool StandardClient::ipcr(handle id, ipcactions_t action) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { client.ipcr(id, action, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::ipcr(handle id) { auto result = thread_do([=](StandardClient& client, PromiseBoolSP result) { auto i = client.client.pcrindex.find(id); auto j = client.client.pcrindex.end(); result->set_value(i != j && !i->second->isoutgoing); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::opcr(const string& email, opcactions_t action, PromiseHandleSP result) { auto completion = [=](handle h, error e, opcactions_t) { result->set_value(!e ? h : UNDEF); }; client.setpcr(email.c_str(), action, nullptr, nullptr, UNDEF, std::move(completion)); } handle StandardClient::opcr(const string& email, opcactions_t action) { auto result = thread_do([&](StandardClient& client, PromiseHandleSP result) { client.opcr(email, action, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::opcr(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { for (auto& i : client.client.pcrindex) { if (i.second->targetemail == email) return result->set_value(i.second->isoutgoing); } result->set_value(false); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::iscontact(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { for (auto &i : client.client.users) { if (i.second.email == email) return result->set_value(i.second.show == VISIBLE); } result->set_value(false); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::isverified(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { User* u = client.client.finduser(email.c_str()); if (u) { result->set_value(client.client.areCredentialsVerified(u->userhandle)); } else { result->set_value(false); } }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::verifyCredentials(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { User* u = client.client.finduser(email.c_str()); if (u) { result->set_value(client.client.verifyCredentials(u->userhandle, nullptr) == API_OK); } else { result->set_value(false); } }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } bool StandardClient::resetCredentials(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { User* u = client.client.finduser(email.c_str()); if (u) { result->set_value(client.client.resetCredentials(u->userhandle, nullptr) == API_OK); } else { result->set_value(false); } }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::rmcontact(const string& email, PromiseBoolSP result) { client.removecontact(email.c_str(), HIDDEN, [=](error e) { result->set_value(!e); }); } bool StandardClient::rmcontact(const string& email) { auto result = thread_do([&](StandardClient& client, PromiseBoolSP result) { client.rmcontact(email, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(std::chrono::seconds(45)); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return false; return result.get(); } void StandardClient::opensharedialog(const CloudItem& item, PromiseErrorSP result) { auto node = item.resolve(*this); if (!node) return result->set_value(API_ENOENT); client.openShareDialog(node.get(), [result](Error e) { result->set_value(e); }); } Error StandardClient::opensharedialog(const CloudItem& item) { auto result = thread_do([&](StandardClient& client, PromiseErrorSP result) { client.opensharedialog(item, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return LOCAL_ETIMEOUT; return result.get(); } void StandardClient::share(const CloudItem& item, const string& email, accesslevel_t permissions, PromiseErrorSP result) { auto node = item.resolve(*this); if (!node) return result->set_value(API_ENOENT); auto completion = [result](Error e, bool) { result->set_value(e); }; // completion client.setshare(node, email.c_str(), permissions, false, nullptr, ++next_request_tag, std::move(completion)); } Error StandardClient::share(const CloudItem& item, const string& email, accesslevel_t permissions) { auto result = thread_do([&](StandardClient& client, PromiseErrorSP result) { client.share(item, email, permissions, std::move(result)); }, __FILE__, __LINE__); auto status = result.wait_for(DEFAULTWAIT); EXPECT_NE(status, future_status::timeout); if (status == future_status::timeout) return LOCAL_ETIMEOUT; return result.get(); } void StandardClient::upgradeSecurity(PromiseBoolSP result) { client.upgradeSecurity([=](error e) { result->set_value(!e); }); } void StandardClient::setHasImmediateStall(HasImmediateStallPredicate predicate) { std::lock_guard guard(clientMutex); client.syncs.setHasImmediateStall(std::move(predicate)); } void StandardClient::setIsImmediateStall(IsImmediateStallPredicate predicate) { std::lock_guard guard(clientMutex); client.syncs.setIsImmediateStall(std::move(predicate)); } void StandardClient::setSyncController(SyncControllerPtr controller) { std::lock_guard guard(clientMutex); client.syncs.setSyncController(std::move(controller)); } void StandardClient::setDownloadSpeed(m_off_t downloadSpeed) { std::lock_guard guard(clientMutex); client.setmaxdownloadspeed(downloadSpeed); } void StandardClient::setUploadSpeed(m_off_t uploadSpeed) { std::lock_guard guard(clientMutex); client.setmaxuploadspeed(uploadSpeed); } void StandardClient::prepareOneFolder(NewNode* node, const std::string& name, bool canChangeVault) { prepareOneFolder(node, name.c_str(), canChangeVault); } void StandardClient::prepareOneFolder(NewNode* node, const char* name, bool canChangeVault) { std::lock_guard guard(clientMutex); client.putnodes_prepareOneFolder(node, name, canChangeVault); } bool StandardClient::requestsCompleted() const { // Acquire client mutex. std::lock_guard guard(clientMutex); // Check if any requests are inflight or queued. return !(client.reqs.cmdsInflight() || client.reqs.readyToSend()); } bool StandardClient::transfersCompleted(direction_t type) const { // Acquire client mutex. std::lock_guard guard(clientMutex); // Check if transfers have completed. return client.multi_transfers[type].empty(); } using SyncWaitPredicate = std::function; // Useful predicates. SyncWaitPredicate SyncDisabled(handle id) { return [id](StandardClient& client) { return client.syncByBackupId(id) == nullptr; }; } SyncWaitPredicate SyncStallState(bool state) { return [state](StandardClient& client) { return client.mStallDetected == state; }; } SyncWaitPredicate SyncConflictState(bool state) { return [state](StandardClient& client) { return client.mConflictsDetected == state; }; } SyncWaitPredicate SyncTotalStallsStateUpdate(bool state) { return [state](StandardClient& client) { return client.mTotalStallsUpdated == state; }; } SyncWaitPredicate SyncTotalConflictsStateUpdate(bool state) { return [state](StandardClient& client) { return client.mTotalConflictsUpdated == state; }; } SyncWaitPredicate SyncRemoteMatch(const CloudItem& item, const Model::ModelNode* source) { return [=](StandardClient& client) { return client.thread_do([&](StandardClient& client, PromiseBoolSP result) { if (auto node = item.resolve(client)) return client.match(node->nodeHandle(), source, std::move(result)); result->set_value(false); }, __FILE__, __LINE__).get(); }; } SyncWaitPredicate SyncRemoteMatch(const CloudItem& item, const Model& source) { return SyncRemoteMatch(item, source.root.get()); } SyncWaitPredicate SyncRemoteNodePresent(const CloudItem& item) { return [item](StandardClient& client) { return client .thread_do( [&](StandardClient& client, PromiseBoolSP result) { result->set_value(item.resolve(client) != nullptr); }, __FILE__, __LINE__) .get(); }; } SyncWaitPredicate SyncScanState(bool expected) { return [=](StandardClient& client) { return client.client.syncs.syncscanstate == expected; }; } struct SyncWaitResult { bool syncStalled = false; SyncStallInfoTests stall; }; bool noSyncStalled(vector& v) { for (auto& e : v) { if (e.syncStalled) return false; } return true; } using WaitOnSyncsEndCondition = std::function; vector waitonsyncs(WaitOnSyncsEndCondition&& endCondition, StandardClient* c1 = nullptr, StandardClient* c2 = nullptr, StandardClient* c3 = nullptr, StandardClient* c4 = nullptr) { LOG_debug << "waitonsyncs starts"; auto totalTimeoutStart = chrono::steady_clock::now(); auto startNoActivity = chrono::steady_clock::now(); auto startNoSyncing = chrono::steady_clock::now(); auto startStalled = chrono::steady_clock::now(); bool stallStarted = false; vector v{ c1, c2, c3, c4 }; vector result{SyncWaitResult(), SyncWaitResult(), SyncWaitResult(), SyncWaitResult()}; for (auto sClient: v) { if (sClient) sClient->resetClientDowaitDs(); } for (;;) { bool any_activity = false; bool any_still_syncing = false; bool any_running_at_all = false; bool any_stalled = false; bool any_stalled_with_pending_reqs_or_scanning = false; for (const auto i: range(v.size())) { if (!v[i]) continue; v[i]->thread_do( [&](StandardClient& mc, PromiseBoolSP pb) { const auto anyTransfersOrCmds = !mc.client.transferlist.transfers[GET].empty() || !mc.client.transferlist.transfers[PUT].empty() || mc.client.reqs.cmdsInflight() || mc.client.syncs.anySyncHasPendingTransfersThreadSafeState(); if (result[i].syncStalled = mc.syncStallDetected(result[i].stall) || mc.client.syncs.syncConflictState; result[i].syncStalled) { any_stalled = true; any_stalled_with_pending_reqs_or_scanning |= (anyTransfersOrCmds || mc.client.syncs.syncscanstate); if (!stallStarted) { stallStarted = true; startStalled = chrono::steady_clock::now(); } } else { any_running_at_all |= mc.client.syncs.mNumSyncsActive > 0; any_activity |= (anyTransfersOrCmds || mc.client.syncs.syncBusyState); any_still_syncing |= mc.client.syncs.syncBusyState; } pb->set_value(true); }, __FILE__, __LINE__) .get(); } if (!any_stalled) { stallStarted = false; } if (!any_running_at_all) { const auto millisecsToWait = any_stalled_with_pending_reqs_or_scanning ? std::chrono::milliseconds(20000) : std::chrono::milliseconds(3500); const auto stallexit = stallStarted && chrono::steady_clock::now() - startStalled > millisecsToWait; if (stallexit) { // don't drop out of the wait if we are only stalled briefly. // we've seen this happen if a .megaignore is not read properly - // size is known, but read operations returns too few bytes, without error (in RenameReplaceIgnoreFile, on windows) LOG_debug << " waitonsyncs detected syncs stalled for " << millisecsToWait.count() << " ms"; } if (!stallStarted || stallexit) { LOG_debug << " waitonsyncs finished since no non-stalled clients have any running syncs"; return result; } } if (any_activity || StandardClient::debugging) { startNoActivity = chrono::steady_clock::now(); } if (any_still_syncing || StandardClient::debugging) { startNoSyncing = chrono::steady_clock::now(); } auto noActivityExtraTimeForTimeout = std::chrono::seconds(0); auto minNoActivityTime = std::chrono::milliseconds(6 * 60 * 1000); auto minNoSyncingTime = std::chrono::milliseconds(6 * 60 * 1000); for (auto vn : v) if (vn) { auto t1 = chrono::duration_cast(chrono::steady_clock::now() - startNoActivity); auto t2 = chrono::duration_cast(chrono::steady_clock::now() - vn->lastcb); if (t1 < minNoActivityTime) minNoActivityTime = t1; if (t2 < minNoActivityTime) minNoActivityTime = t2; auto t3 = chrono::duration_cast(chrono::steady_clock::now() - startNoSyncing); if (t3 < minNoSyncingTime) minNoSyncingTime = t3; noActivityExtraTimeForTimeout += std::chrono::seconds(vn->consumeClientDowaitDs(10)); } if (endCondition(minNoActivityTime, minNoSyncingTime, noActivityExtraTimeForTimeout)) { return result; } WaitMillisec(400); if ((chrono::steady_clock::now() - totalTimeoutStart) > std::chrono::minutes(5)) { out() << "waitonsyncs waiting for syncing to stop timed out at 5 minutes"; return result; } } } void printStallIssues(StandardClient& sc) { SyncStallInfoTests stalls; sc.stallsDetected(stalls); auto printStallIssue = [](const bool local, const std::string& pathstr, SyncStallEntry& stall) { LOG_debug << "[Stall issue - " << (local ? "waitingForLocal" : "waitingForCloud") << "]: " << pathstr << "\n\t\t- Reason (" << static_cast(stall.reason) << ")" << "\n\t\t- cloudPath1 (" << stall.cloudPath1.debugReport() << ")" << "\n\t\t- cloudPath2 (" << stall.cloudPath2.debugReport() << ")" << "\n\t\t- localPath1 (" << stall.localPath1.debugReport() << ")" << "\n\t\t- localPath2 (" << stall.localPath2.debugReport() << ")"; }; LOG_debug << "Printing cloud stall issues:"; std::for_each(stalls.cloud.begin(), stalls.cloud.end(), [&printStallIssue](auto& s) { printStallIssue(true, s.first, s.second); }); LOG_debug << "Printing local stall issues:"; std::for_each(stalls.local.begin(), stalls.local.end(), [&printStallIssue](auto& s) { printStallIssue(true, s.first.toPath(false), s.second); }); } vector waitonsyncs(const std::chrono::seconds timeout = std::chrono::seconds(4), StandardClient* c1 = nullptr, StandardClient* c2 = nullptr, StandardClient* c3 = nullptr, StandardClient* c4 = nullptr) { auto endCondition = [&timeout](const std::chrono::milliseconds noActivityTime, const std::chrono::milliseconds noSyncingTime, const std::chrono::seconds noActivityExtraTimeForTimeout) { const auto noActivityTimeout = std::min(timeout + noActivityExtraTimeForTimeout, std::chrono::minutes(4)); const auto result = noActivityTime >= noActivityTimeout && noSyncingTime >= timeout; if (result) { LOG_debug << "waitonsyncs complete after " << noActivityTime.count() << " ms of no activity and " << noSyncingTime.count() << " ms of no syncing [timeout = " << timeout.count() << " secs, noActivityExtraTimeForTimeout = " << noActivityExtraTimeForTimeout.count() << " secs]"; } return result; }; return waitonsyncs(std::move(endCondition), c1, c2, c3, c4); } bool StandardClient::waitForSyncTotalStallsStateUpdateTrue(const std::chrono::seconds timeout) { if (const auto totalStallsStateUpdateResult = waitFor(SyncTotalStallsStateUpdate(true), timeout); !totalStallsStateUpdateResult) { LOG_warn << "SyncTotalStallsStateUpdate(true) was false, it could be due to a change of " "stall state that was temporary false due to pending scans, and then set to " "true again when there only was 1 stall. Checking we are in stall state..."; return mStallDetected; } return true; } mutex StandardClient::om; bool StandardClient::debugging = false; //std::atomic fileSizeCount = 20; bool createNameFile(const fs::path &p, const string &filename) { return createFile(p / u8path_compat(filename), filename.data(), filename.size()); } bool createFileWithTimestamp(const fs::path &path, const std::string &data, const fs::path& tmpCreationLocation, const fs::file_time_type ×tamp) { // Create the file at a neutral location first so we can set the timestamp without a sync noticing the wrong timestamp first bool result = createFile(tmpCreationLocation / path.filename(), data); if (result) { fs::last_write_time(tmpCreationLocation / path.filename(), timestamp); // Now that it has the proper mtime, move it to the correct location error_code rename_error; fs::rename(tmpCreationLocation / path.filename(), path, rename_error); EXPECT_TRUE(!rename_error) << rename_error; result = !rename_error; } return result; } bool buildLocalFolders(fs::path targetfolder, const string& prefix, int n, int recurselevel, int filesperfolder) { if (suppressfiles) filesperfolder = 0; fs::path p = targetfolder / u8path_compat(prefix); if (!fs::create_directory(p)) return false; for (int i = 0; i < filesperfolder; ++i) { string filename = "file" + to_string(i) + "_" + prefix; createNameFile(p, filename); //int thisSize = (++fileSizeCount)/2; //for (int j = 0; j < thisSize; ++j) fs << ('0' + j % 10); } if (recurselevel > 0) { for (int i = 0; i < n; ++i) { if (!buildLocalFolders(p, prefix + "_" + to_string(i), n, recurselevel - 1, filesperfolder)) return false; } } return true; } void renameLocalFolders(fs::path targetfolder, const string& newprefix) { std::list toRename; for (fs::directory_iterator i(targetfolder); i != fs::directory_iterator(); ++i) { if (fs::is_directory(i->path())) { renameLocalFolders(i->path(), newprefix); } toRename.push_back(i->path()); } for (auto p : toRename) { auto newpath = path_u8string(p.parent_path() / u8path_compat(newprefix + path_u8string(p.filename()))); fs::rename(p, newpath); } } #ifdef __linux__ bool createSpecialFiles(fs::path targetfolder, const string& prefix, int n = 1) { fs::path p = targetfolder; for (int i = 0; i < n; ++i) { string filename = "file" + to_string(i) + "_" + prefix; fs::path fp = p / u8path_compat(filename); int fdtmp = openat(AT_FDCWD, p.c_str(), O_RDWR|O_CLOEXEC|O_TMPFILE, 0600); if (const auto wrBytes = write(fdtmp, filename.data(), filename.size()); wrBytes < 0) { cerr << " Error writing file " << filename; return false; } stringstream fdproc; fdproc << "/proc/self/fd/"; fdproc << fdtmp; int r = linkat(AT_FDCWD, fdproc.str().c_str() , AT_FDCWD, fp.c_str(), AT_SYMLINK_FOLLOW); if (r) { cerr << " errno =" << errno; return false; } close(fdtmp); } return true; } #endif class SyncFingerprintCollisionTest : public SdkTestBase { public: fs::path testRootFolder; SyncFingerprintCollisionTest() : client0() , client1() , model0() , model1() , arbitraryFileLength(16384) { testRootFolder = makeNewTestRoot(); client0 = std::make_unique(testRootFolder, "c0"); client1 = std::make_unique(testRootFolder, "c1"); client0->logcb = true; client1->logcb = true; } ~SyncFingerprintCollisionTest() { } void SetUp() override { SdkTestBase::SetUp(); SimpleLogger::setLogLevel(logVerbose); ASSERT_TRUE(client0->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "d", 1, 2)); ASSERT_TRUE(client1->login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(client0->basefolderhandle, client1->basefolderhandle); model0.root->addkid(model0.buildModelSubdirs("d", 2, 1, 0)); model1.root->addkid(model1.buildModelSubdirs("d", 2, 1, 0)); // Make sure the client's agree on the cloud's state before proceeding. { auto root = client0->gettestbasenode(); ASSERT_NE(root, nullptr); auto predicate = SyncRemoteMatch(*root, model0.root.get()); ASSERT_TRUE(client0->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(client1->waitFor(predicate, DEFAULTWAIT)); } startSyncs(); waitOnSyncs(); confirmModels(); } void addModelFile(Model &model, const std::string &directory, const std::string &file, const std::string &content) { auto *node = model.findnode(directory); ASSERT_NE(node, nullptr); node->addkid(model.makeModelSubfile(file, content)); } void confirmModel(StandardClient &client, Model &model, handle backupId) { ASSERT_TRUE(client.confirmModel_mainthread(model.findnode("d"), backupId)); } void confirmModels() { LOG_debug << "Confirm model client 0..."; confirmModel(*client0, model0, backupId0); LOG_debug << "Confirm model client 1..."; confirmModel(*client1, model1, backupId1); LOG_debug << "After confirming models..."; } const fs::path localRoot0() const { return client0->syncSet(backupId0).localpath; } const fs::path localRoot1() const { return client1->syncSet(backupId1).localpath; } void prepareForNodeUpdates() { client0->received_node_actionpackets = false; client1->received_node_actionpackets = false; } void startSyncs() { backupId0 = client0->setupSync_mainthread("s0", "d", false, true); ASSERT_NE(backupId0, UNDEF); backupId1 = client1->setupSync_mainthread("s1", "d", false, false); ASSERT_NE(backupId1, UNDEF); } void waitForNodeUpdates() { ASSERT_TRUE(client0->waitForNodesUpdated(30)); ASSERT_TRUE(client1->waitForNodesUpdated(30)); } void waitOnSyncs() { waitonsyncs(chrono::seconds(4), client0.get(), client1.get()); } handle backupId0 = UNDEF; handle backupId1 = UNDEF; std::unique_ptr client0; std::unique_ptr client1; Model model0; Model model1; const std::size_t arbitraryFileLength; }; /* SyncFingerprintCollision */ TEST_F(SyncFingerprintCollisionTest, DifferentMacSameName) { static const std::string logPre{"SyncFingerprintCollisionTest.DifferentMacSameName : "}; auto data0 = randomData(arbitraryFileLength); auto data1 = data0; const auto path0 = localRoot0() / "d_0" / "a"; const auto path1 = localRoot0() / "d_1" / "a"; prepareForNodeUpdates(); ASSERT_TRUE(createFile(path0, data0)); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); waitOnSyncs(); LOG_debug << logPre << "0. Alter MAC but leave fp untouched"; data1[0x41] = static_cast(~data1[0x41]); // Prepare the file outside of the sync's view. auto stamp = fs::last_write_time(path0); ASSERT_TRUE( createFileWithTimestamp(client0->fsBasePath / "a", data1, client0->fsBasePath, stamp)); LOG_debug << logPre << "0. Move file after altering MAC"; prepareForNodeUpdates(); fs::rename(client0->fsBasePath / "a", path1); LOG_debug << logPre << "0. File moved after altering MAC"; client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); waitOnSyncs(); LOG_debug << logPre << "0. Adding model files and confirming models"; addModelFile(model0, "d/d_0", "a", data0); addModelFile(model0, "d/d_1", "a", data1); addModelFile(model1, "d/d_0", "a", data0); addModelFile(model1, "d/d_1", "a", data1); model1.ensureLocalDebrisTmpLock("d"); confirmModels(); LOG_debug << logPre << "0. Models confirmed after adding model files"; LOG_debug << logPre << "1. Data change keeping mtime"; data1[0x41] = static_cast(~data1[0x42]); { std::unique_ptr fa(std::make_unique()); auto [succeed, oldMtime] = fa->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(path1))); ASSERT_TRUE(succeed); std::ofstream file(path1, std::ios::binary); file.write(data1.data(), static_cast(data1.size())); file.close(); ASSERT_TRUE(fa->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(path1)), oldMtime)); auto [succeed2, newMtime] = fa->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(path1))); ASSERT_TRUE(succeed2); ASSERT_EQ(oldMtime, newMtime); } waitOnSyncs(); // Update model0 data to data1 so local content matches // Do not update model1 because we expect the previous change NOT to be reflected in the cloud, // so model1 shouldn't have synced it LOG_debug << logPre << "1. Confirm models after data change"; model0.findnode("d/d_1/a")->content = data1; confirmModels(); LOG_debug << logPre << "1. Models confirmed after data change keeping mtime"; LOG_debug << logPre << "2. Change mtime local"; prepareForNodeUpdates(); std::unique_ptr mFsAccess; mFsAccess = std::make_unique(); mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(path1)), m_time(nullptr)); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); waitOnSyncs(); // Now we expect this change to have been reflected in the cloud and c1 should have reflected // the change LOG_debug << logPre << "2. Confirm models after changing mtime local"; model1.movetosynctrash("d/d_1/a", "d"); addModelFile(model1, "d/d_1", "a", data1); confirmModels(); LOG_debug << logPre << "2. Models confirmed after changing mtime local"; LOG_debug << logPre << "3. Change mtime local again, this time fp differs in mtime and MACs are equals, " "there shall be no download but a setmtime change"; prepareForNodeUpdates(); mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(path1)), m_time(nullptr)); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); waitOnSyncs(); LOG_debug << logPre << "3. Confirm models after changing mtime keeping same MAC"; confirmModels(); LOG_debug << logPre << "3. Models confirmed after changing mtime keeping same MAC"; } TEST_F(SyncFingerprintCollisionTest, DifferentMacDifferentName) { auto data0 = randomData(arbitraryFileLength); auto data1 = data0; const auto path0 = localRoot0() / "d_0" / "a"; const auto path1 = localRoot0() / "d_0" / "b"; prepareForNodeUpdates(); ASSERT_TRUE(createFile(path0, data0)); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); // Process any further changes. waitOnSyncs(); // Alter MAC but leave fingerprint untouched. data1[0x41] = static_cast(~data1[0x41]); // Prepare the file outside of the engine's view. auto stamp = fs::last_write_time(path0); ASSERT_TRUE(createFileWithTimestamp(client0->fsBasePath / "a", data1, client0->fsBasePath, stamp)); prepareForNodeUpdates(); fs::rename(client0->fsBasePath / "a", path1); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); // Wait for the engine to process our change. waitOnSyncs(); addModelFile(model0, "d/d_0", "a", data0); addModelFile(model0, "d/d_0", "b", data1); addModelFile(model1, "d/d_0", "a", data0); addModelFile(model1, "d/d_0", "b", data1); model1.ensureLocalDebrisTmpLock("d"); confirmModels(); } TEST_F(SyncFingerprintCollisionTest, SameMacDifferentName) { auto data0 = randomData(arbitraryFileLength); const auto path0 = localRoot0() / "d_0" / "a"; const auto path1 = localRoot0() / "d_0" / "b"; prepareForNodeUpdates(); ASSERT_TRUE(createFile(path0, data0)); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); waitOnSyncs(); // Build the file somewhere the sync won't notice. auto stamp = fs::last_write_time(path0); ASSERT_TRUE(createFileWithTimestamp(client0->fsBasePath / "b", data0, client0->fsBasePath, stamp)); // Move file into place. prepareForNodeUpdates(); fs::rename(client0->fsBasePath / "b", path1); client0->triggerPeriodicScanEarly(backupId0); waitForNodeUpdates(); // Wait for the engine to process our changes. waitOnSyncs(); addModelFile(model0, "d/d_0", "a", data0); addModelFile(model0, "d/d_0", "b", data0); addModelFile(model1, "d/d_0", "a", data0); addModelFile(model1, "d/d_0", "b", data0); model1.ensureLocalDebrisTmpLock("d"); confirmModels(); } class SyncTest : public SdkTestBase { public: // Sets up the test case. void SetUp() override { SdkTestBase::SetUp(); LOG_info << "____TEST SetUp: " << ::testing::UnitTest::GetInstance()->current_test_info()->name(); SimpleLogger::setLogLevel(logVerbose); } // Tears down the test case. void TearDown() override { LOG_info << "____TEST TearDown: " << ::testing::UnitTest::GetInstance()->current_test_info()->name(); } }; // SqliteDBTest TEST_F(SyncTest, BasicSync_DelRemoteFolder) { // delete a remote folder and confirm the client sending the request and another also synced both correctly update the disk fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // delete something remotely and let sync catch up clientA1->received_node_actionpackets = false; clientA2->received_node_actionpackets = false; ASSERT_TRUE(clientA1->deleteremote("f/f_2/f_2_1")); ASSERT_TRUE(clientA1->waitForNodesUpdated(60)); ASSERT_TRUE(clientA2->waitForNodesUpdated(60)); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); // check everything matches in both syncs (model has expected state of remote and local) ASSERT_TRUE(model.movetosynctrash("f/f_2/f_2_1", "f")); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_DelLocalFolder) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); auto checkpath = path_u8string(clientA1->syncSet(backupId1).localpath); out() << "checking paths " << checkpath; for(auto& p: fs::recursive_directory_iterator(TestFS::GetTestFolder())) { out() << "checking path is present: " << path_u8string(p.path()); } // delete something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code e; auto nRemoved = fs::remove_all(clientA1->syncSet(backupId1).localpath / "f_2" / "f_2_1", e); ASSERT_TRUE(!e) << "remove failed " << path_u8string((clientA1->syncSet(backupId1).localpath / "f_2" / "f_2_1")) << " error " << e; ASSERT_GT(static_cast(nRemoved), 0u) << e; clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(model.movetosynctrash("f/f_2/f_2_1", "f")); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); ASSERT_TRUE(model.removesynctrash("f")); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); } TEST_F(SyncTest, BasicSync_MoveLocalFolderPlain) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(8), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // reload the Nodes from server, similar to an ETOOMANY error, // just so we are exercising most of that code path somewhere clientA1->fetchnodes(false, false, true); clientA1->waitFor([&](StandardClient& sc) { return sc.client.actionpacketsCurrent.load(); }, std::chrono::seconds(60)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); out() << "----- making sync change to test, now -----"; clientA1->received_node_actionpackets = false; clientA2->received_node_actionpackets = false; // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code rename_error; fs::rename(clientA1->syncSet(backupId1).localpath / "f_2" / "f_2_1", clientA1->syncSet(backupId1).localpath / "f_2_1", rename_error); ASSERT_TRUE(!rename_error) << rename_error; clientA1->triggerPeriodicScanEarly(backupId1); // client1 should send a rename command to the API // both client1 and client2 should receive the corresponding actionpacket ASSERT_TRUE(clientA1->waitForNodesUpdated(60)) << " no actionpacket received in clientA1 for rename"; ASSERT_TRUE(clientA2->waitForNodesUpdated(60)) << " no actionpacket received in clientA2 for rename"; out() << "----- wait for actionpackets ended -----"; // sync activity should not take much longer after that. waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(model.movenode("f/f_2/f_2_1", "f")); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_MoveLocalFolderBetweenSyncs) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 auto clientA3 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 auto receivedDeviceIdName = [](User* actionPackageUser, User* ownUser) { assert(actionPackageUser && ownUser); if (actionPackageUser->userhandle == ownUser->userhandle && actionPackageUser->changed.devicenames) { return true; } return false; }; User* ownUserClient2 = clientA2->client.ownuser(); // Register a callback to be used when users_updated is called. This callBack is used to stop waiting period at waitForUserUpdated // removeOnUserUpdateLamda should be called to unregister clientA2->createsOnUserUpdateLamda([ownUserClient2, receivedDeviceIdName](User* user) { return receivedDeviceIdName(user, ownUserClient2); }); User* ownUserClient3 = clientA3->client.ownuser(); // Register a callback to be used when users_updated is called. This callBack is used to stop waiting period at waitForUserUpdated // removeOnUserUpdateLamda should be called to unregister clientA3->createsOnUserUpdateLamda([ownUserClient3, receivedDeviceIdName](User* user) { return receivedDeviceIdName(user, ownUserClient3); }); clientA2->removeOnUserUpdateLamda(); clientA3->removeOnUserUpdateLamda(); // ATTR_MY_BACKUPS_FOLDER is only set once, if it exists, it shouldn't be set again bool backUpIsSet = false; ASSERT_TRUE(clientA1->waitForAttrMyBackupIsSet(60, backUpIsSet)) << "Error User attr My Back Folder isn't establised client1"; if (backUpIsSet) // only wait for action package if atribute has been set { auto receivedBackUpId = [](User* actionPackageUser, User* ownUser) { assert(actionPackageUser && ownUser); if (actionPackageUser->userhandle == ownUser->userhandle && actionPackageUser->changed.myBackupsFolder) { return true; } return false; }; User* ownUserClient2 = clientA2->client.ownuser(); // Register a callback to be used when users_updated is called. This callBack is used to stop waiting period at waitForUserUpdated // removeOnUserUpdateLamda should be called to unregister clientA2->createsOnUserUpdateLamda([ownUserClient2, receivedBackUpId](User* user) { return receivedBackUpId(user, ownUserClient2); }); User* ownUserClient3 = clientA3->client.ownuser(); // Register a callback to be used when users_updated is called. This callBack is used to stop waiting period at waitForUserUpdated // removeOnUserUpdateLamda should be called to unregister clientA3->createsOnUserUpdateLamda([ownUserClient3, receivedBackUpId](User* user) { return receivedBackUpId(user, ownUserClient3); }); ASSERT_TRUE(clientA2->waitForUserUpdated(60)) << "User update doesn't arrive at client2 (backup folder)"; ASSERT_TRUE(clientA3->waitForUserUpdated(60)) << "User update doesn't arrive at client3 (backup folder)"; clientA2->removeOnUserUpdateLamda(); clientA3->removeOnUserUpdateLamda(); // Check attribute has been set correctly backUpIsSet = false; ASSERT_TRUE(clientA2->waitForAttrMyBackupIsSet(60, backUpIsSet)) << "Error User attr My Back Folder isn't establised client2"; ASSERT_EQ(backUpIsSet, false); // It has already set backUpIsSet = false; ASSERT_TRUE(clientA3->waitForAttrMyBackupIsSet(60, backUpIsSet)) << "Error User attr My Back Folder isn't establised client3"; ASSERT_EQ(backUpIsSet, false); // It has already set } ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2, clientA3)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2, clientA3)); // Avoid races between clients. { auto path = clientA1->fsBasePath / ".megaignore"; // Create bare ignore file. ASSERT_TRUE(createFile(path, "+sync:.megaignore")); // Upload ignore file to each sync root. ASSERT_TRUE(clientA1->uploadFile(path, "f")); ASSERT_TRUE(clientA1->uploadFile(path, "f/f_0")); ASSERT_TRUE(clientA1->uploadFile(path, "f/f_2")); } // set up sync for A1 and A2, it should build matching local folders handle backupId11 = clientA1->setupSync_mainthread("sync1", "f/f_0", false, false); ASSERT_NE(backupId11, UNDEF); handle backupId12 = clientA1->setupSync_mainthread("sync2", "f/f_2", false, false); ASSERT_NE(backupId12, UNDEF); handle backupId21 = clientA2->setupSync_mainthread("syncA2_1", "f/f_0", false, false); ASSERT_NE(backupId21, UNDEF); handle backupId22 = clientA2->setupSync_mainthread("syncA2_2", "f/f_2", false, false); ASSERT_NE(backupId22, UNDEF); handle backupId31 = clientA3->setupSync_mainthread("syncA3", "f", false, false); ASSERT_NE(backupId31, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2, clientA3); clientA1->logcb = clientA2->logcb = clientA3->logcb = true; // also set up backups and move between backup/sync (to check vw:1 flag is set appropriately both ways) handle backupId3b1 = clientA3->setupBackup_mainthread("backup1"); ASSERT_NE(backupId3b1, UNDEF); handle backupId3b2 = clientA3->setupBackup_mainthread("backup2"); ASSERT_NE(backupId3b2, UNDEF); std::error_code fs_error; fs::create_directory(clientA3->syncSet(backupId3b1).localpath / "backup1subfolder", fs_error); ASSERT_TRUE(!fs_error) << fs_error; fs::create_directory(clientA3->syncSet(backupId3b2).localpath / "backup2subfolder", fs_error); ASSERT_TRUE(!fs_error) << fs_error; waitonsyncs(std::chrono::seconds(4), clientA1, clientA2, clientA3); // Create models. Model backup1model; Model backup2model; backup1model.addfolder("backup1subfolder"); backup2model.addfolder("backup2subfolder"); ASSERT_TRUE(clientA3->confirmModel_mainthread(backup1model.root.get(), backupId3b1)); ASSERT_TRUE(clientA3->confirmModel_mainthread(backup2model.root.get(), backupId3b2)); // Create Sync models. Model modelF; Model modelF0; Model modelF2; // f modelF.root->addkid(modelF.buildModelSubdirs("f", 3, 3, 0)); modelF.ensureLocalDebrisTmpLock("f"); // f_0 modelF0.root->addkid(modelF.findnode("f/f_0")->clone()); modelF0.ensureLocalDebrisTmpLock("f_0"); // f_2 modelF2.root->addkid(modelF.findnode("f/f_2")->clone()); modelF2.ensureLocalDebrisTmpLock("f_2"); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(modelF0.findnode("f_0"), backupId11)); ASSERT_TRUE(clientA1->confirmModel_mainthread(modelF2.findnode("f_2"), backupId12)); ASSERT_TRUE(clientA2->confirmModel_mainthread(modelF0.findnode("f_0"), backupId21)); ASSERT_TRUE(clientA2->confirmModel_mainthread(modelF2.findnode("f_2"), backupId22)); ASSERT_TRUE(clientA3->confirmModel_mainthread(modelF.findnode("f"), backupId31)); LOG_debug << "----- making sync change to test, now -----"; clientA1->received_node_actionpackets = false; clientA2->received_node_actionpackets = false; clientA3->received_node_actionpackets = false; // move a folder form one local synced folder to another local synced folder and see if we sync correctly and catch up in A2 and A3 (mover and observer syncs) error_code rename_error; fs::path path1 = clientA1->syncSet(backupId11).localpath / "f_0_1"; fs::path path2 = clientA1->syncSet(backupId12).localpath / "f_2_1" / "f_2_1_0" / "f_0_1"; fs::rename(path1, path2, rename_error); ASSERT_TRUE(!rename_error) << rename_error; clientA1->triggerPeriodicScanEarly(backupId11); clientA1->triggerPeriodicScanEarly(backupId12); // client1 should send a rename command to the API // both client1 and client2 should receive the corresponding actionpacket ASSERT_TRUE(clientA1->waitForNodesUpdated(30)) << " no actionpacket received in clientA1 for rename"; ASSERT_TRUE(clientA2->waitForNodesUpdated(30)) << " no actionpacket received in clientA2 for rename"; ASSERT_TRUE(clientA3->waitForNodesUpdated(30)) << " no actionpacket received in clientA3 for rename"; // let them catch up waitonsyncs(std::chrono::seconds(4), clientA1, clientA2, clientA3); // Update models. modelF.movenode("f/f_0/f_0_1", "f/f_2/f_2_1/f_2_1_0"); modelF2.findnode("f_2/f_2_1/f_2_1_0")->addkid(modelF0.removenode("f_0/f_0_1")); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(modelF0.findnode("f_0"), backupId11)); ASSERT_TRUE(clientA1->confirmModel_mainthread(modelF2.findnode("f_2"), backupId12)); ASSERT_TRUE(clientA2->confirmModel_mainthread(modelF0.findnode("f_0"), backupId21)); ASSERT_TRUE(clientA2->confirmModel_mainthread(modelF2.findnode("f_2"), backupId22)); ASSERT_TRUE(clientA3->confirmModel_mainthread(modelF.findnode("f"), backupId31)); // now try moving between syncs and backups fs::rename(clientA3->syncSet(backupId3b1).localpath / "backup1subfolder", clientA3->syncSet(backupId31).localpath / "backup1subfolder", fs_error); ASSERT_TRUE(!fs_error) << fs_error; fs::rename(clientA3->syncSet(backupId31).localpath / "f_2", clientA3->syncSet(backupId3b1).localpath / "f_2", fs_error); ASSERT_TRUE(!fs_error) << fs_error; waitonsyncs(std::chrono::seconds(4), clientA1, clientA2, clientA3); backup1model.root->addkid(modelF.removenode("f/f_2")); modelF.findnode("f")->addkid(backup1model.removenode("backup1subfolder")); ASSERT_TRUE(clientA3->confirmModel_mainthread(backup1model.root.get(), backupId3b1)); ASSERT_TRUE(clientA3->confirmModel_mainthread(modelF.findnode("f"), backupId31)); } TEST_F(SyncTest, BasicSync_RenameLocalFile) { static auto TIMEOUT = std::chrono::seconds(4); const fs::path root = makeNewTestRoot(); auto client0 = g_clientManager->getCleanStandardClient(0, root); // user 1 client 1 auto client1 = g_clientManager->getCleanStandardClient(0, root); // user 1 client 2 ASSERT_TRUE(client0->resetBaseFolderMulticlient(client1)); ASSERT_TRUE(client0->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(client0, client1)); // Log callbacks. client0->logcb = true; client1->logcb = true; // Log clients in. ///ASSERT_TRUE(client0.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "x", 0, 0)); ///ASSERT_TRUE(client1.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(client0->basefolderhandle, client1->basefolderhandle); // Set up syncs. handle backupId0 = client0->setupSync_mainthread("s0", "x", false, true); ASSERT_NE(backupId0, UNDEF); handle backupId1 = client1->setupSync_mainthread("s1", "x", false, false); ASSERT_NE(backupId1, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, client0, client1); // Add x/f. ASSERT_TRUE(createNameFile(client0->syncSet(backupId0).localpath, "f")); client0->triggerPeriodicScanEarly(backupId0); // Wait for sync to complete. waitonsyncs(TIMEOUT, client0, client1); // Confirm model. Model model1, model2; model1.root->addkid(model1.makeModelSubfolder("x")); model1.findnode("x")->addkid(model1.makeModelSubfile("f")); model2.root->addkid(model2.makeModelSubfolder("x")); model2.findnode("x")->addkid(model2.makeModelSubfile("f")); model2.ensureLocalDebrisTmpLock("x"); // since it downloaded f (uploaded by sync 1) ASSERT_TRUE(client0->confirmModel_mainthread(model1.findnode("x"), backupId0)); ASSERT_TRUE(client1->confirmModel_mainthread(model2.findnode("x"), backupId1, true)); // Rename x/f to x/g. fs::rename(client0->syncSet(backupId0).localpath / "f", client0->syncSet(backupId0).localpath / "g"); client0->triggerPeriodicScanEarly(backupId0); // Wait for sync to complete. waitonsyncs(TIMEOUT, client0, client1); // Update and confirm model. model1.findnode("x/f")->name = "g"; model2.findnode("x/f")->name = "g"; ASSERT_TRUE(client0->confirmModel_mainthread(model1.findnode("x"), backupId0)); ASSERT_TRUE(client1->confirmModel_mainthread(model2.findnode("x"), backupId1, true)); } #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED TEST_F(SyncTest, TransferCountProgress) { const std::string logPre = "SyncTest.TransferCountProgress: "; LOG_debug << logPre + "#### Test preparation ####"; auto clientA1 = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); clientA1->logcb = true; ASSERT_TRUE(clientA1->resetBaseFolderMulticlient()); ASSERT_TRUE(clientA1->makeCloudSubdirs("sync1", 0, 0)); ASSERT_TRUE(CatchupClients(clientA1)); std::atomic transferReportFlag{false}; std::atomic transferReportProgressFlag{false}; m_off_t transferReportInflightProgress{0}; m_off_t transferReportPendingBytes{0}; double transferReportProgress{0}; double targetProgressAux{0}; unsigned transferFinished{0}; unsigned targetTransferFinishedCount{3}; globalMegaTestHooks.onTransferReportProgress = [&](const double p, const m_off_t fp, const m_off_t pb) { transferReportProgress = p; transferReportInflightProgress = fp; transferReportPendingBytes = pb; transferReportProgressFlag = true; }; clientA1->onTransferCompleted = [&](Transfer*) { if (++transferFinished == targetTransferFinishedCount) { clientA1->onTransferCompleted = nullptr; targetProgressAux = transferReportProgress; transferReportProgressFlag = false; transferReportFlag = true; } }; LOG_debug << logPre + "#### Add and start sync ####"; const auto backupId1 = clientA1->setupSync_mainthread("sync1", "sync1", false, false); ASSERT_NE(backupId1, UNDEF) << "Cannot add sync"; const auto SYNCROOT = clientA1->syncSet(backupId1).localpath; LOG_debug << logPre + "#### Build model. ####"; Model model1; model1.addfolder("A"); model1.generate(SYNCROOT); clientA1->triggerPeriodicScanEarly(backupId1); constexpr auto TIMEOUT = chrono::seconds(30); waitonsyncs(TIMEOUT, clientA1); LOG_debug << logPre + "#### Set max upload speed to 100Kb/s ####"; clientA1->client.setmaxuploadspeed(100000); LOG_debug << logPre + "#### Add some files in local FS. ####"; constexpr size_t KB_IN_BYTES{104857u}; constexpr size_t MB_IN_BYTES{1048576u}; unsigned totalFilesAdded{0}; auto addFile = [SYNCROOT, &totalFilesAdded](Model& model, const string& path, const size_t s) { auto data = randomData(s); model.addfile(path, data); ASSERT_TRUE(createFile(SYNCROOT / path, data)); totalFilesAdded++; }; addFile(model1, "A/fxya1", KB_IN_BYTES); addFile(model1, "A/fxya2", KB_IN_BYTES); addFile(model1, "A/fxya3", KB_IN_BYTES); addFile(model1, "A/fxya4", MB_IN_BYTES); addFile(model1, "A/fxya5", MB_IN_BYTES); addFile(model1, "A/fxya6", MB_IN_BYTES); auto checkTransferProgress = [&]() { LOG_debug << logPre + "#### Wait until first " << targetTransferFinishedCount << " transfers finish ####"; clientA1->waitFor( [&transferReportFlag](StandardClient&) { return transferReportFlag.load(); }, std::chrono::seconds(30)); ASSERT_EQ(transferFinished, targetTransferFinishedCount); auto numPendingTransfers = totalFilesAdded - transferFinished; LOG_debug << logPre + "#### Wait until next reportCounts progress is received ####"; clientA1->waitFor( [&transferReportProgressFlag](StandardClient&) { return transferReportProgressFlag.load(); }, std::chrono::seconds(6)); const auto initialPending = static_cast(MB_IN_BYTES * numPendingTransfers); ASSERT_GE(initialPending, transferReportPendingBytes); const auto initialCompleted = static_cast(KB_IN_BYTES * transferFinished); const auto newCompleted = initialPending - transferReportPendingBytes; const auto completed = initialCompleted + newCompleted; const auto pending = initialPending - newCompleted; ASSERT_EQ(pending, transferReportPendingBytes); ASSERT_EQ((completed + pending), (initialPending + initialCompleted)); const auto currentProgress = static_cast(completed + transferReportInflightProgress) / static_cast(completed + pending); ASSERT_EQ(transferReportProgress, currentProgress) << ". Unexpected value for reportCounts progress"; LOG_debug << logPre + "#### Set max upload speed unlimited ####"; clientA1->client.setmaxuploadspeed(-1); }; checkTransferProgress(); LOG_debug << logPre + "#### Wait on sync. ####"; waitonsyncs(TIMEOUT, clientA1); LOG_debug << logPre + "#### Confirm model. ####"; ASSERT_TRUE(clientA1->confirmModel_mainthread(model1.findnode("" /*root*/), backupId1)); } #endif TEST_F(SyncTest, BasicSync_AddLocalFolder) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClientInUse clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 StandardClientInUse clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA2->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model1, model2; model1.root->addkid(model1.buildModelSubdirs("f", 3, 3, 0)); model2.root->addkid(model2.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model1.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model2.findnode("f"), backupId2)); // make new folders (and files) in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) ASSERT_TRUE(buildLocalFolders(clientA1->syncSet(backupId1).localpath / "f_2", "newkid", 2, 2, 2)); clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up // two minutes should be long enough to get past API_ETEMPUNAVAIL == -18 for sync2 downloading the files uploaded by sync1 // 4 seconds was too short sometimes, sync2 not caught up yet, due to a few consecutive -3 for `g` waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // check everything matches (model has expected state of remote and local) model1.findnode("f/f_2")->addkid(model1.buildModelSubdirs("newkid", 2, 2, 2)); model2.findnode("f/f_2")->addkid(model2.buildModelSubdirs("newkid", 2, 2, 2)); model2.ensureLocalDebrisTmpLock("f"); // since we downloaded files ASSERT_TRUE(clientA1->confirmModel_mainthread(model1.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model2.findnode("f"), backupId2)); } // todo: add this test once the sync can keep up with file system notifications - at the moment // it's too slow because we wait for the cloud before processing the next layer of files+folders. // So if we add enough changes to exercise the notification queue, we can't check the results because // it's far too slow at the syncing stage. TEST_F(SyncTest, BasicSync_MassNotifyFromLocalFolderTree) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClientInUse clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient()); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 0, 0)); ASSERT_TRUE(CatchupClients(clientA1)); //ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 0, 0)); //ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); //ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); //ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); waitonsyncs(std::chrono::seconds(4), clientA1/*, &clientA2*/); //clientA1.logcb = clientA2.logcb = true; // Create a directory tree in one sync, it should be synced to the cloud and back to the other // Create enough files and folders that we put a strain on the notification logic: 3k entries ASSERT_TRUE(buildLocalFolders(clientA1->syncSet(backupId1).localpath, "initial", 0, 0, 16000)); clientA1->triggerPeriodicScanEarly(backupId1); //waitonsyncs(std::chrono::seconds(10), &clientA1 /*, &clientA2*/); std::this_thread::sleep_for(std::chrono::seconds(5)); // wait until the notify queues subside, it shouldn't take too long. Limit of 5 minutes auto startTime = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - startTime < std::chrono::seconds(5 * 60)) { size_t remaining = 0; auto result0 = clientA1->thread_do([&](StandardClient &sc, PromiseBoolSP p) { remaining += sc.client.syncs.syncscanstate ? 1 : 0; p->set_value(true); }, __FILE__, __LINE__); result0.get(); if (!remaining) break; std::this_thread::sleep_for(std::chrono::seconds(1)); } Model model; model.root->addkid(model.buildModelSubdirs("initial", 0, 0, 16000)); clientA1->waitFor([&](StandardClient&){ return clientA1->transfersAdded.load() > 0; }, std::chrono::seconds(60)); // give it a chance to create all the nodes. // check everything matches (just local since it'll still be uploading files) clientA1->localNodesMustHaveNodes = false; ASSERT_TRUE(clientA1->confirmModel_mainthread(model.root.get(), backupId1, false, StandardClient::CONFIRM_LOCAL)); //ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); ASSERT_GT(clientA1->transfersAdded.load(), 0u); clientA1->transfersAdded = 0; // rename all those files and folders, put a strain on the notify system again. // Also, no downloads (or uploads) should occur as a result of this. // renameLocalFolders(clientA1.syncSet(backupId1).localpath, "renamed_"); // let them catch up //waitonsyncs(std::chrono::seconds(10), &clientA1 /*, &clientA2*/); // rename is too slow to check, even just in localnodes, for now. //ASSERT_EQ(clientA1.transfersAdded.load(), 0u); //Model model2; //model2.root->addkid(model.buildModelSubdirs("renamed_initial", 0, 0, 100)); //// check everything matches (model has expected state of remote and local) //ASSERT_TRUE(clientA1.confirmModel_mainthread(model2.root.get(), 1)); ////ASSERT_TRUE(clientA2.confirmModel_mainthread(model2.findnode("f"), 2)); } /* this one is too slow for regular testing with the current algorithm TEST_F(SyncTest, BasicSync_MAX_NEWNODES1) { // create more nodes than we can upload in one putnodes. // this tree is 5x5 and the algorithm ends up creating nodes one at a time so it's pretty slow (and doesn't hit MAX_NEWNODES as a result) fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); // make new folders in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) assert(MegaClient::MAX_NEWNODES < 3125); ASSERT_TRUE(buildLocalFolders(clientA1.syncSet(backupId1).localpath, "g", 5, 5, 0)); // 5^5=3125 leaf folders, 625 pre-leaf etc // let them catch up waitonsyncs(std::chrono::seconds(30), &clientA1, &clientA2); // check everything matches (model has expected state of remote and local) model.findnode("f")->addkid(model.buildModelSubdirs("g", 5, 5, 0)); ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); } */ /* this one is too slow for regular testing with the current algorithm TEST_F(SyncTest, BasicSync_MAX_NEWNODES2) { // create more nodes than we can upload in one putnodes. // this tree is 5x5 and the algorithm ends up creating nodes one at a time so it's pretty slow (and doesn't hit MAX_NEWNODES as a result) fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); // make new folders in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) assert(MegaClient::MAX_NEWNODES < 3000); ASSERT_TRUE(buildLocalFolders(clientA1.syncSet(backupId1).localpath, "g", 3000, 1, 0)); // let them catch up waitonsyncs(std::chrono::seconds(30), &clientA1, &clientA2); // check everything matches (model has expected state of remote and local) model.findnode("f")->addkid(model.buildModelSubdirs("g", 3000, 1, 0)); ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); } */ TEST_F(SyncTest, BasicSync_MoveExistingIntoNewLocalFolder) { // historic case: in the local filesystem, create a new folder then move an existing file/folder into it fs::path localtestroot = makeNewTestRoot(); StandardClientInUse clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 StandardClientInUse clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(DEFAULTWAIT, clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // make new folder in the local filesystem ASSERT_TRUE(buildLocalFolders(clientA1->syncSet(backupId1).localpath, "new", 1, 0, 0)); // move an already synced folder into it error_code rename_error; fs::path path1 = clientA1->syncSet(backupId1).localpath / "f_2"; // / "f_2_0" / "f_2_0_0"; fs::path path2 = clientA1->syncSet(backupId1).localpath / "new" / "f_2"; // "f_2_0_0"; fs::rename(path1, path2, rename_error); ASSERT_TRUE(!rename_error) << rename_error; clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // check everything matches (model has expected state of remote and local) auto f = model.makeModelSubfolder("new"); f->addkid(model.removenode("f/f_2")); // / f_2_0 / f_2_0_0")); model.findnode("f")->addkid(std::move(f)); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_MoveSeveralExistingIntoDeepNewLocalFolders) { // historic case: in the local filesystem, create a new folder then move an existing file/folder into it fs::path localtestroot = makeNewTestRoot(); StandardClientInUse clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 StandardClientInUse clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 3, 3)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(DEFAULTWAIT, clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // make new folder tree in the local filesystem ASSERT_TRUE(buildLocalFolders(clientA1->syncSet(backupId1).localpath, "new", 3, 3, 3)); model.findnode("f")->addkid(model.buildModelSubdirs("new", 3, 3, 3)); clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // move already synced folders to serveral parts of it - one under another moved folder too error_code rename_error; fs::rename(clientA1->syncSet(backupId1).localpath / "f_0", clientA1->syncSet(backupId1).localpath / "new" / "new_0" / "new_0_1" / "new_0_1_2" / "f_0", rename_error); ASSERT_TRUE(!rename_error) << rename_error; fs::rename(clientA1->syncSet(backupId1).localpath / "f_1", clientA1->syncSet(backupId1).localpath / "new" / "new_1" / "new_1_2" / "f_1", rename_error); ASSERT_TRUE(!rename_error) << rename_error; fs::rename(clientA1->syncSet(backupId1).localpath / "f_2", clientA1->syncSet(backupId1).localpath / "new" / "new_1" / "new_1_2" / "f_1" / "f_1_2" / "f_2", rename_error); ASSERT_TRUE(!rename_error) << rename_error; clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // check everything matches (model has expected state of remote and local) model.findnode("f/new/new_0/new_0_1/new_0_1_2")->addkid(model.removenode("f/f_0")); model.findnode("f/new/new_1/new_1_2")->addkid(model.removenode("f/f_1")); model.findnode("f/new/new_1/new_1_2/f_1/f_1_2")->addkid(model.removenode("f/f_2")); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); model.ensureLocalDebrisTmpLock("f"); // since we downloaded files ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) // for sendDeferred() TEST_F(SyncTest, BasicSync_MoveTwiceLocallyButCloudMoveRequestDelayed) { fs::path localtestroot = makeNewTestRoot(); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); Model m; m.addfolder("a"); m.addfolder("b"); m.addfolder("c"); m.generate(c->fsBasePath / "s"); handle backupId = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(backupId, UNDEF); waitonsyncs(std::chrono::seconds(4), c); ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), backupId)); c->logcb = true; fs::path path1 = c->syncSet(backupId).localpath; LOG_info << "Preventing move reqs being sent, then making local move for sync code to upsync"; // nothing can be sent, same as network disconnected c->thread_do([](StandardClient& client, PromiseBoolSP result) { client.client.reqs.deferRequests = [](Command*) { return true; }; result->set_value(true); }, __FILE__, __LINE__).get(); error_code fs_error; fs::rename(path1 / "a", path1 / "b" / "a", fs_error); ASSERT_TRUE(!fs_error) << fs_error; WaitMillisec(2000); fs::create_directory(path1 / "new", fs_error); ASSERT_TRUE(!fs_error) << fs_error; LOG_info << "Moving folder `a` a second time, into a new folder, while the first move has not yet completed"; fs::rename(path1 / "b" / "a", path1 / "new" / "a", fs_error); ASSERT_TRUE(!fs_error) << fs_error; WaitMillisec(2000); LOG_info << "Allowing move reqs to continue"; c->sendDeferredAndReset(); waitonsyncs(std::chrono::seconds(4), c); m.addfolder("new"); m.addfolder("new/a"); m.removenode("a"); ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), backupId)); } #endif /* not expected to work yet TEST_F(SyncTest, BasicSync_SyncDuplicateNames) { fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset("MEGA_EMAIL", "MEGA_PWD")); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); NewNode* nodearray = new NewNode[3]; nodearray[0] = *clientA1.makeSubfolder("samename"); nodearray[1] = *clientA1.makeSubfolder("samename"); nodearray[2] = *clientA1.makeSubfolder("Samename"); clientA1.resultproc.prepresult(StandardClient::PUTNODES, [this](error e) { }); clientA1.client.putnodes(clientA1.basefolderhandle, nodearray, 3); // set up syncs, they should build matching local folders ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) Model model; model.root->addkid(model.makeModelSubfolder("samename")); model.root->addkid(model.makeModelSubfolder("samename")); model.root->addkid(model.makeModelSubfolder("Samename")); ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); }*/ TEST_F(SyncTest, BasicSync_RemoveLocalNodeBeforeSessionResume) { fs::path localtestroot = makeNewTestRoot(); auto pclientA1 = std::make_unique(localtestroot, "clientA1"); // user 1 client 1 // don't use client manager as this client gets replaced StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); pclientA1->received_node_actionpackets = false; clientA2.received_node_actionpackets = false; // set up sync for A1, it should build matching local folders handle backupId1 = pclientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); // actually for this one, we don't expect actionpackets because all the cloud nodes are already present before the sync starts // ASSERT_TRUE(pclientA1->waitForNodesUpdated(30)) << " no actionpacket received in clientA1"; // ASSERT_TRUE(clientA2.waitForNodesUpdated(30)) << " no actionpacket received in clientA2"; waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); pclientA1->logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); // save session string session; pclientA1->client.dumpsession(session); // logout (but keep caches) fs::path sync1path = pclientA1->syncSet(backupId1).localpath; pclientA1->localLogout(); pclientA1->received_syncs_restored = false; // remove local folders error_code e; ASSERT_TRUE(fs::remove_all(sync1path / "f_2", e) != static_cast(-1)) << e; // resume session, see if nodes and localnodes get in sync pclientA1.reset(new StandardClient(localtestroot, "clientA1")); ASSERT_TRUE(pclientA1->login_fetchnodesFromSession(session)); // wait for normal sync resumes to complete pclientA1->waitFor([&](StandardClient& sc){ return sc.received_syncs_restored.load(); }, std::chrono::seconds(30)); waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(model.movetosynctrash("f/f_2", "f")); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); ASSERT_TRUE(model.removesynctrash("f")); ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); } /* not expected to work yet TEST_F(SyncTest, BasicSync_RemoteFolderCreationRaceSamename) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder // SN tagging needed for this one fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset("MEGA_EMAIL", "MEGA_PWD")); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); // set up sync for both, it should build matching local folders (empty initially) ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // now have both clients create the same remote folder structure simultaneously. We should end up with just one copy of it on the server and in both syncs future p1 = clientA1.thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.makeCloudSubdirs("f", 3, 3, pb); }); future p2 = clientA2.thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.makeCloudSubdirs("f", 3, 3, pb); }); ASSERT_TRUE(waitonresults(&p1, &p2)); // let them catch up waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); // check everything matches (model has expected state of remote and local) Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); }*/ /* not expected to work yet TEST_F(SyncTest, BasicSync_LocalFolderCreationRaceSamename) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder // SN tagging needed for this one fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset("MEGA_EMAIL", "MEGA_PWD")); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); // set up sync for both, it should build matching local folders (empty initially) ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // now have both clients create the same folder structure simultaneously. We should end up with just one copy of it on the server and in both syncs future p1 = clientA1.thread_do([=](StandardClient& sc, PromiseBoolSP pb) { buildLocalFolders(sc.syncSet(backupId1).localpath, "f", 3, 3, 0); pb->set_value(true); }); future p2 = clientA2.thread_do([=](StandardClient& sc, PromiseBoolSP pb) { buildLocalFolders(sc.syncSet(backupId2).localpath, "f", 3, 3, 0); pb->set_value(true); }); ASSERT_TRUE(waitonresults(&p1, &p2)); // let them catch up waitonsyncs(std::chrono::seconds(30), &clientA1, &clientA2); // check everything matches (model has expected state of remote and local) Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); }*/ TEST_F(SyncTest, BasicSync_ResumeSyncFromSessionAfterNonclashingLocalAndRemoteChanges ) { fs::path localtestroot = makeNewTestRoot(); unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 // don't use client manager as this client gets replaced StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); // set up sync for A1, it should build matching local folders handle backupId1 = pclientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); pclientA1->logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) Model model1, model2; model1.root->addkid(model1.buildModelSubdirs("f", 3, 3, 0)); model2.root->addkid(model2.buildModelSubdirs("f", 3, 3, 0)); ASSERT_TRUE(pclientA1->confirmModel_mainthread(model1.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model2.findnode("f"), backupId2)); out() << "********************* save session A1"; string session; pclientA1->client.dumpsession(session); out() << "********************* logout A1 (but keep caches on disk)"; fs::path sync1path = pclientA1->syncSet(backupId1).localpath; pclientA1->localLogout(); out() << "********************* add remote folders via A2"; future p1 = clientA2.thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.makeCloudSubdirs("newremote", 2, 2, pb, "f/f_1/f_1_0"); }, __FILE__, __LINE__); model1.findnode("f/f_1/f_1_0")->addkid(model1.buildModelSubdirs("newremote", 2, 2, 0)); model2.findnode("f/f_1/f_1_0")->addkid(model2.buildModelSubdirs("newremote", 2, 2, 0)); ASSERT_TRUE(waitonresults(&p1)); out() << "********************* remove remote folders via A2"; ASSERT_TRUE(clientA2.deleteremote("f/f_0")); model1.movetosynctrash("f/f_0", "f"); model2.movetosynctrash("f/f_0", "f"); out() << "********************* add local folders in A1"; ASSERT_TRUE(buildLocalFolders(sync1path / "f_1/f_1_2", "newlocal", 2, 2, 2)); model1.findnode("f/f_1/f_1_2")->addkid(model1.buildModelSubdirs("newlocal", 2, 2, 2)); model2.findnode("f/f_1/f_1_2")->addkid(model2.buildModelSubdirs("newlocal", 2, 2, 2)); out() << "********************* remove local folders in A1"; error_code e; ASSERT_TRUE(fs::remove_all(sync1path / "f_2", e) != static_cast(-1)) << e; model1.removenode("f/f_2"); model2.movetosynctrash("f/f_2", "f"); out() << "********************* get sync2 activity out of the way"; waitonsyncs(std::chrono::seconds(4), &clientA2); out() << "********************* resume A1 session (with sync), see if A2 nodes and localnodes get in sync again"; pclientA1.reset(new StandardClient(localtestroot, "clientA1")); ASSERT_TRUE(pclientA1->login_fetchnodesFromSession(session)); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); // wait for normal sync resumes to complete // wait bumped to 40 seconds here because we've seen a case where the 2nd client didn't receive actionpackets for 30 seconds pclientA1->waitFor([&](StandardClient& sc){ return sc.received_syncs_restored.load(); }, std::chrono::seconds(40)); waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); out() << "********************* check everything matches (model has expected state of remote and local)"; ASSERT_TRUE(pclientA1->confirmModel_mainthread(model1.findnode("f"), backupId1)); model2.ensureLocalDebrisTmpLock("f"); // since we downloaded files ASSERT_TRUE(clientA2.confirmModel_mainthread(model2.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_ResumeSyncFromSessionAfterClashingLocalAddRemoteDelete) { fs::path localtestroot = makeNewTestRoot(); unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 // don't use client manager as this client gets replaced StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = pclientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); pclientA1->logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); // save session A1 string session; pclientA1->client.dumpsession(session); fs::path sync1path = pclientA1->syncSet(backupId1).localpath; // logout A1 (but keep caches on disk) pclientA1->localLogout(); // remove remote folder via A2 ASSERT_TRUE(clientA2.deleteremote("f/f_1")); // add local folders in A1 on disk folder ASSERT_TRUE(buildLocalFolders(sync1path / "f_1/f_1_2", "newlocal", 2, 2, 2)); // get sync2 activity out of the way waitonsyncs(std::chrono::seconds(4), &clientA2); // resume A1 session (with sync), see if A2 nodes and localnodes get in sync again pclientA1.reset(new StandardClient(localtestroot, "clientA1")); ASSERT_TRUE(pclientA1->login_fetchnodesFromSession(session)); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); // wait for normal sync resumes to complete pclientA1->waitFor([&](StandardClient& sc){ return sc.received_syncs_restored.load(); }, std::chrono::seconds(30)); waitonsyncs(chrono::seconds(4), pclientA1.get(), &clientA2); // Sync rework update: for now at least, delete wins in this case //ASSERT_EQ(waitResult[0].syncStalled, true); //ASSERT_EQ(1, waitResult[0].stalledNodePaths.size()); //ASSERT_EQ(1, waitResult[0].stalledLocalPaths.size()); Model modelLocal1; modelLocal1.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); modelLocal1.findnode("f/f_1/f_1_2")->addkid(model.buildModelSubdirs("newlocal", 2, 2, 2)); //pclientA1->localNodesMustHaveNodes = false; ASSERT_TRUE(modelLocal1.movetosynctrash("f/f_1", "f")); Model modelRemote1; modelRemote1.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); ASSERT_TRUE(modelRemote1.movetosynctrash("f/f_1", "f")); ASSERT_TRUE(pclientA1->confirmModel_mainthread(modelLocal1.findnode("f"), backupId1, false, StandardClient::CONFIRM_LOCAL)); ASSERT_TRUE(pclientA1->confirmModel_mainthread(modelRemote1.findnode("f"), backupId1, false, StandardClient::CONFIRM_REMOTE)); //ASSERT_TRUE(modelRemote1.removesynctrash("f", "f_1/f_1_2/newlocal")); ASSERT_TRUE(clientA2.confirmModel_mainthread(modelRemote1.findnode("f"), backupId2)); } #ifdef SRW_NEEDED_FOR_THIS_ONE TEST_F(SyncTest, BasicSync_ResumeSyncFromSessionAfterContractoryLocalAndRemoteMoves) { fs::path localtestroot = makeNewTestRoot(); unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = pclientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), pclientA1.get(), &clientA2); pclientA1->logcb = clientA2.logcb = true; auto client1LocalSyncRoot = pclientA1->syncSet(backupId1).localpath; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2, true)); // save session A1 string session; pclientA1->client.dumpsession(session); fs::path sync1path = pclientA1->syncSet(backupId1).localpath; // logout A1 (but keep caches on disk) pclientA1->localLogout(); // move f_0 into f_1 remote ASSERT_TRUE(clientA2.movenode("f/f_0", "f/f_1")); // move f_1 into f_0 locally error_code rename_error; fs::rename(client1LocalSyncRoot / "f_1", client1LocalSyncRoot / "f_0" / "f_1", rename_error); ASSERT_TRUE(!rename_error) << rename_error; // get sync2 activity out of the way waitonsyncs(std::chrono::seconds(4), &clientA2); // resume A1 session (with sync), see if A2 nodes and localnodes get in sync again pclientA1.reset(new StandardClient(localtestroot, "clientA1")); ASSERT_TRUE(pclientA1->login_fetchnodesFromSession(session)); ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); vector waitResult = waitonsyncs(chrono::seconds(4), pclientA1.get(), &clientA2); ASSERT_EQ(waitResult[0].syncStalled, true); ASSERT_EQ(2u, waitResult[0].stall.cloud.size()); // for now at least, reporting source and destination nodes for each move ASSERT_EQ(2u, waitResult[0].stall.local.size()); ASSERT_EQ(waitResult[0].stall.cloud.begin()->first, "/mega_test_sync/f/f_0"); ASSERT_EQ(waitResult[0].stall.cloud.rbegin()->first, "/mega_test_sync/f/f_1/f_0"); ASSERT_EQ(waitResult[0].stall.local.begin()->first.toPath(false), path_u8string((client1LocalSyncRoot / "f_0" / "f_1"))); ASSERT_EQ(waitResult[0].stall.local.rbegin()->first.toPath(false), path_u8string((client1LocalSyncRoot / "f_1"))); } #endif TEST_F(SyncTest, CmdChecks_RRAttributeAfterMoveNode) { fs::path localtestroot = makeNewTestRoot(); unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 // don't use client manager as this client gets replaced ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 3, 3)); std::shared_ptr f = pclientA1->drillchildnodebyname(pclientA1->gettestbasenode(), "f"); handle original_f_handle = f->nodehandle; handle original_f_parent_handle = f->parent->nodehandle; // make sure there are no 'f' in the rubbish auto fv = pclientA1->drillchildnodesbyname(pclientA1->getcloudrubbishnode().get(), "f"); future fb = pclientA1->thread_do([fv](StandardClient& sc, PromiseBoolSP pb) { sc.deleteremotenodes(fv, pb); }, __FILE__, __LINE__); ASSERT_TRUE(waitonresults(&fb)); f = pclientA1->drillchildnodebyname(pclientA1->getcloudrubbishnode(), "f"); ASSERT_TRUE(f == nullptr || f->changed.removed); // remove remote folder via A2 future p1 = pclientA1->thread_do([](StandardClient& sc, PromiseBoolSP pb) { sc.movenodetotrash("f", pb); }, __FILE__, __LINE__); ASSERT_TRUE(waitonresults(&p1)); WaitMillisec(3000); // allow for attribute delivery too f = pclientA1->drillchildnodebyname(pclientA1->getcloudrubbishnode(), "f"); ASSERT_TRUE(f != nullptr); // check the restore-from-trash handle got set, and correctly nameid rrname = AttrMap::string2nameid("rr"); ASSERT_EQ(f->nodehandle, original_f_handle); ASSERT_EQ(f->attrs.map[rrname], string(Base64Str(original_f_parent_handle))); ASSERT_EQ(f->attrs.map[rrname], string(Base64Str(pclientA1->gettestbasenode()->nodehandle))); // move it back ASSERT_TRUE(pclientA1->movenode(f->nodehandle, pclientA1->basefolderhandle)); WaitMillisec(3000); // allow for attribute delivery too // check it's back and the rr attribute is gone f = pclientA1->drillchildnodebyname(pclientA1->gettestbasenode(), "f"); ASSERT_TRUE(f != nullptr); ASSERT_EQ(f->attrs.map[rrname], string()); } #ifdef __linux__ TEST_F(SyncTest, BasicSync_SpecialCreateFile) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 2, 2)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 2, 2, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1.setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); // make new folders (and files) in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) ASSERT_TRUE(createSpecialFiles(clientA1.syncSet(backupId1).localpath / "f_0", "newkid", 2)); for (int i = 0; i < 2; ++i) { string filename = "file" + to_string(i) + "_" + "newkid"; model.findnode("f/f_0")->addkid(model.makeModelSubfile(filename)); } clientA1.triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, &clientA1, &clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), backupId1)); model.ensureLocalDebrisTmpLock("f"); // since we downloaded files ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); } #endif #ifdef SRW_NEEDED_FOR_THIS_ONE TEST_F(SyncTest, BasicSync_moveAndDeleteLocalFile) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClientInUse clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 StandardClientInUse clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 1, 1)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 1, 1, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code rename_error; fs::rename(clientA1->syncSet(backupId1).localpath / "f_0", clientA1->syncSet(backupId1).localpath / "renamed", rename_error); ASSERT_TRUE(!rename_error) << rename_error; fs::remove(clientA1->syncSet(backupId1).localpath / "renamed"); clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // check everything matches (model has expected state of remote and local) ASSERT_TRUE(model.movetosynctrash("f/f_0", "f")); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); ASSERT_TRUE(model.removesynctrash("f")); ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); } #endif namespace { string makefa(const string& name, int fakecrc, int mtime) { AttrMap attrs; attrs.map['n'] = name; FileFingerprint ff; ff.crc[0] = ff.crc[1] = ff.crc[2] = ff.crc[3] = fakecrc; ff.mtime = mtime; ff.serializefingerprint(&attrs.map['c']); string attrjson; attrs.getjson(&attrjson); return attrjson; } Node* makenode(MegaClient& mc, NodeHandle parent, ::mega::nodetype_t type, m_off_t size, handle owner, const string& attrs, ::mega::byte* key) { static handle handlegenerator = 10; auto newnode = new Node(mc, NodeHandle().set6byte(++handlegenerator), parent, type, size, owner, nullptr, 1); newnode->setkey(key); newnode->attrstring.reset(new string); SymmCipher sc; sc.setkey(key, type); mc.makeattr(&sc, newnode->attrstring, attrs.c_str()); int attrlen = int(newnode->attrstring->size()); string base64attrstring; base64attrstring.resize(static_cast(attrlen * 4 / 3 + 4)); base64attrstring.resize(Base64::btoa((::mega::byte*)newnode->attrstring->data(), newnode->attrstring->size(), (char*)base64attrstring.data())); *newnode->attrstring = base64attrstring; return newnode; } } // anonymous TEST_F(SyncTest, PutnodesForMultipleFolders) { fs::path localtestroot = makeNewTestRoot(); auto standardclient = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(standardclient->resetBaseFolderMulticlient()); ASSERT_TRUE(CatchupClients(standardclient)); vector newnodes(4); standardclient->prepareOneFolder(&newnodes[0], "folder1", false); standardclient->prepareOneFolder(&newnodes[1], "folder2", false); standardclient->prepareOneFolder(&newnodes[2], "folder2.1", false); standardclient->prepareOneFolder(&newnodes[3], "folder2.2", false); newnodes[1].nodehandle = newnodes[2].parenthandle = newnodes[3].parenthandle = 2; auto targethandle = standardclient->client.mNodeManager.getRootNodeFiles(); std::atomic putnodesDone{false}; standardclient->resultproc.prepresult( StandardClient::PUTNODES, ++next_request_tag, [&]() { standardclient->client.putnodes(targethandle, NoVersioning, std::move(newnodes), nullptr, standardclient->client.reqtag, false); }, [&putnodesDone](error) { putnodesDone = true; return true; }); while (!putnodesDone) { WaitMillisec(100); } std::shared_ptr cloudRoot = standardclient->client.nodeByHandle(targethandle); ASSERT_TRUE(nullptr != standardclient->drillchildnodebyname(cloudRoot, "folder1")); ASSERT_TRUE(nullptr != standardclient->drillchildnodebyname(cloudRoot, "folder2")); ASSERT_TRUE(nullptr != standardclient->drillchildnodebyname(cloudRoot, "folder2/folder2.1")); ASSERT_TRUE(nullptr != standardclient->drillchildnodebyname(cloudRoot, "folder2/folder2.2")); } // this test fails frequently on develop due to race conditions with commands vs actionpackets on develop, re-enable after merging sync rework (which has SIC removed) TEST_F(SyncTest, DISABLED_ExerciseCommands) { fs::path localtestroot = makeNewTestRoot(); StandardClient standardclient(localtestroot, "ExerciseCommands"); ASSERT_TRUE(standardclient.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD", true)); // Using this set setup to execute commands direct in the SDK Core // so that we can test things that the MegaApi interface would // disallow or shortcut. // make sure it's a brand new folder future p1 = standardclient.thread_do([=](StandardClient& sc, PromiseBoolSP pb) { sc.makeCloudSubdirs("testlinkfolder_brandnew3", 1, 1, pb); }, __FILE__, __LINE__); ASSERT_TRUE(waitonresults(&p1)); assert(standardclient.lastPutnodesResultFirstHandle != UNDEF); std::shared_ptr n2 = standardclient.client.nodebyhandle(standardclient.lastPutnodesResultFirstHandle); out() << "Testing make public link for node: " << n2->displaypath(); // try to get a link on an existing unshared folder promise pe1, pe1a, pe2, pe3, pe4; standardclient.getpubliclink(n2.get(), 0, 0, false, false, pe1); ASSERT_TRUE(debugTolerantWaitOnFuture(pe1.get_future(), 45)); ASSERT_EQ(API_EACCESS, pe1.get_future().get()); // create on existing node standardclient.exportnode(n2, 0, 0, false, false, pe1a); ASSERT_TRUE(debugTolerantWaitOnFuture(pe1a.get_future(), 45)); ASSERT_EQ(API_OK, pe1a.get_future().get()); // get link on existing shared folder node, with link already (different command response) standardclient.getpubliclink(n2.get(), 0, 0, false, false, pe2); ASSERT_TRUE(debugTolerantWaitOnFuture(pe2.get_future(), 45)); ASSERT_EQ(API_OK, pe2.get_future().get()); // delete existing link on node standardclient.getpubliclink(n2.get(), 1, 0, false, false, pe3); ASSERT_TRUE(debugTolerantWaitOnFuture(pe3.get_future(), 45)); ASSERT_EQ(API_OK, pe3.get_future().get()); // create on non existent node n2->nodehandle = UNDEF; standardclient.getpubliclink(n2.get(), 0, 0, false, false, pe4); ASSERT_TRUE(debugTolerantWaitOnFuture(pe4.get_future(), 45)); ASSERT_EQ(API_EACCESS, pe4.get_future().get()); } #ifndef _WIN32_SUPPORTS_SYMLINKS_IT_JUST_NEEDS_TURNING_ON TEST_F(SyncTest, BasicSync_CreateAndDeleteLink) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "f", 1, 1)); ASSERT_TRUE(clientA2.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 1, 1, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1.setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2.setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), &clientA1, &clientA2); clientA1.logcb = clientA2.logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code linkage_error; fs::create_symlink(clientA1.syncSet(backupId1).localpath / "f_0", clientA1.syncSet(backupId1).localpath / "linked", linkage_error); ASSERT_TRUE(!linkage_error) << linkage_error; // let them catch up waitonsyncs(DEFAULTWAIT, &clientA1, &clientA2); // Wait for the engine to signal a stall as symlinks are unsupported. ASSERT_TRUE(clientA1.waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure the engine stalled for the right reason. { SyncStallInfoTests stalls; auto reason = SyncWaitReason::FileIssue; auto problem = PathProblem::DetectedSymlink; ASSERT_TRUE(clientA1.syncStallDetected(stalls)); ASSERT_FALSE(stalls.empty()); ASSERT_FALSE(stalls.local.empty()); ASSERT_EQ(stalls.local.begin()->second.reason, reason); ASSERT_EQ(stalls.local.begin()->second.localPath1.problem, problem); } // Check client 2 is unaffected ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); // Resolve the stall by removing the symlink. fs::remove(clientA1.syncSet(backupId1).localpath / "linked"); // Wait for the stall to resolve. ASSERT_TRUE(clientA1.waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the syncs to process any changes. waitonsyncs(DEFAULTWAIT, &clientA1, &clientA2); // Check client 2 is unaffected ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_CreateRenameAndDeleteLink) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 1, 1)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 1, 1, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code linkage_error; fs::create_symlink(clientA1->syncSet(backupId1).localpath / "f_0", clientA1->syncSet(backupId1).localpath / "linked", linkage_error); ASSERT_TRUE(!linkage_error) << linkage_error; // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Wait for the engine to detect a stall. ASSERT_TRUE(clientA1->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we're stalling for the right reason. { SyncStallInfoTests info; auto reason = SyncWaitReason::FileIssue; auto problem = PathProblem::DetectedSymlink; ASSERT_TRUE(clientA1->syncStallDetected(info)); ASSERT_FALSE(info.empty()); ASSERT_FALSE(info.local.empty()); ASSERT_EQ(info.local.begin()->second.reason, reason); ASSERT_EQ(info.local.begin()->second.localPath1.problem, problem); } //check client 2 is unaffected ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Rename the link. fs::rename(clientA1->syncSet(backupId1).localpath / "linked", clientA1->syncSet(backupId1).localpath / "linkrenamed", linkage_error); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Make sure we don't recover from the stall. ASSERT_FALSE(clientA1->waitFor(SyncStallState(false), chrono::seconds(4))); // Make sure we're only reporting one symlink stall. { SyncStallInfoTests info; auto reason = SyncWaitReason::FileIssue; auto problem = PathProblem::DetectedSymlink; ASSERT_TRUE(clientA1->syncStallDetected(info)); ASSERT_FALSE(info.empty()); ASSERT_EQ(info.local.size(), 1u); ASSERT_EQ(info.local.begin()->second.reason, reason); ASSERT_EQ(info.local.begin()->second.localPath1.problem, problem); } //check client 2 is unaffected ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Resolve the stall by removing the symlink. fs::remove(clientA1->syncSet(backupId1).localpath / "linkrenamed"); // Wait for the stall to resolve. ASSERT_TRUE(clientA1->waitFor(SyncStallState(false), DEFAULTWAIT)); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); //check client 2 is unaffected ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } // what is supposed to happen for this one? It seems that the `linked` symlink is no longer ignored on windows? client2 is affected! TEST_F(SyncTest, BasicSync_CreateAndReplaceLinkLocally) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 1, 2)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 2, 1, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code linkage_error; fs::create_symlink(clientA1->syncSet(backupId1).localpath / "f_0", clientA1->syncSet(backupId1).localpath / "linked", linkage_error); ASSERT_TRUE(!linkage_error) << linkage_error; clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); // Wait for the engine to detect a stall. ASSERT_TRUE(clientA1->waitFor(SyncStallState(true), std::chrono::seconds(4))); // Make sure the client stalled for the reason we think. { SyncStallInfoTests info; ASSERT_TRUE(clientA1->syncStallDetected(info)); ASSERT_FALSE(info.empty()); ASSERT_FALSE(info.local.empty()); ASSERT_EQ(info.local.begin()->second.reason, SyncWaitReason::FileIssue); } //check client 2 is unaffected ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Replace the link with a directory. fs::remove(clientA1->syncSet(backupId1).localpath / "linked"); fs::rename(clientA1->syncSet(backupId1).localpath / "f_1", clientA1->syncSet(backupId1).localpath / "linked", linkage_error); ASSERT_TRUE(!linkage_error) << linkage_error; // Let the engine synchronize any changes. waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Update the model. model.findnode("f/f_1")->name = "linked"; // Check that the directory has been uploaded. ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Replace the directory with a file. fs::remove(clientA1->syncSet(backupId1).localpath / "linked"); clientA1->triggerPeriodicScanEarly(backupId1); // Wait for the engine to synchronize changes. waitonsyncs(DEFAULTWAIT, clientA1, clientA2); ASSERT_TRUE(createNameFile(clientA1->syncSet(backupId1).localpath, "linked")); clientA1->triggerPeriodicScanEarly(backupId1); // Wait for the engine to synchronize changes. waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Update the model. auto linkedNode = model.removenode("f/linked"); model.findnode("f")->addkid(model.makeModelSubfile("linked")); // Check that the changes have been synchronized. ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); // Necessary as clientA2 has downloaded files. model.movetosynctrash(std::move(linkedNode), "f"); model.ensureLocalDebrisTmpLock("f"); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); } TEST_F(SyncTest, BasicSync_CreateAndReplaceLinkUponSyncDown) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 auto clientA2 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 2 ASSERT_TRUE(clientA1->resetBaseFolderMulticlient(clientA2)); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 1, 1)); ASSERT_TRUE(CatchupClients(clientA1, clientA2)); ASSERT_EQ(clientA1->basefolderhandle, clientA2->basefolderhandle); Model model; model.root->addkid(model.buildModelSubdirs("f", 1, 1, 0)); // set up sync for A1, it should build matching local folders handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); handle backupId2 = clientA2->setupSync_mainthread("sync2", "f", false, false); ASSERT_NE(backupId2, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1, clientA2); clientA1->logcb = clientA2->logcb = true; // check everything matches (model has expected state of remote and local) ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) error_code linkage_error; fs::create_symlink(clientA1->syncSet(backupId1).localpath / "f_0", clientA1->syncSet(backupId1).localpath / "linked", linkage_error); ASSERT_TRUE(!linkage_error) << linkage_error; clientA1->triggerPeriodicScanEarly(backupId1); // let them catch up waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Wait for the engine to stall. ASSERT_TRUE(clientA1->waitFor(SyncStallState(true), DEFAULTWAIT)); // Check client 2 is unaffected ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Upload a file to the web via A2. ASSERT_TRUE(createFile(clientA2->syncSet(backupId2).localpath / "linked", "linked")); model.addfile("f/linked", "linked"); clientA2->triggerPeriodicScanEarly(backupId2); // Wait for A2's file to hit the cloud. ASSERT_TRUE(clientA2->waitFor(SyncRemoteNodePresent("f/linked"), DEFAULTWAIT)); // Remove the symlink from A1. fs::remove(clientA1->syncSet(backupId1).localpath / "linked"); clientA1->triggerPeriodicScanEarly(backupId1); // Wait for the stall to resolve. ASSERT_TRUE(clientA1->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the engine to process changes. waitonsyncs(DEFAULTWAIT, clientA1, clientA2); // Confirm models. ASSERT_TRUE(clientA2->confirmModel_mainthread(model.findnode("f"), backupId2)); // Needed as A1 has downloaded files. model.ensureLocalDebrisTmpLock("f"); // Check A1 has downloaded A2's upload. ASSERT_TRUE(clientA1->confirmModel_mainthread(model.findnode("f"), backupId1)); } #endif TEST_F(SyncTest, BasicSync_NewVersionsCreatedWhenFilesModified) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = std::chrono::seconds(4); auto c = g_clientManager->getCleanStandardClient(0, TESTROOT); CatchupClients(c); // Log callbacks. c->logcb = true; // Fingerprints for each revision. vector fingerprints; // Log client in. //ASSERT_TRUE(c.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "x", 0, 0)); ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync. const auto id = c->setupSync_mainthread("s", "x", false, true); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Create and populate model. Model model; model.addfile("f", "a"); model.generate(SYNCROOT); // Keep track of fingerprint. fingerprints.emplace_back(c->fingerprint(SYNCROOT / "f")); ASSERT_TRUE(fingerprints.back().isvalid); c->triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Check that the file made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Create a new revision of f. model.addfile("f", "b"); model.generate(SYNCROOT); // Update fingerprint. fingerprints.emplace_back(c->fingerprint(SYNCROOT / "f")); ASSERT_TRUE(fingerprints.back().isvalid); c->triggerPeriodicScanEarly(id); // Wait for change to propagate. waitonsyncs(TIMEOUT, c); // Validate model. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Create yet anothet revision of f. model.addfile("f", "c"); model.generate(SYNCROOT); // Update fingerprint. fingerprints.emplace_back(c->fingerprint(SYNCROOT / "f")); ASSERT_TRUE(fingerprints.back().isvalid); c->triggerPeriodicScanEarly(id); // Wait for change to propagate. waitonsyncs(TIMEOUT, c); // Validate model. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Get our hands on f's node. auto f = c->drillchildnodebyname(c->gettestbasenode(), "x/f"); ASSERT_TRUE(f); // Validate the version chain. auto i = fingerprints.crbegin(); auto matched = true; while (f && i != fingerprints.crend()) { matched &= *f == *i++; sharedNode_list children = c->client.getChildren(f.get()); f = children.empty() ? nullptr : children.front(); } matched &= !f && i == fingerprints.crend(); ASSERT_TRUE(matched); } #ifdef SRW_NEEDED_FOR_THIS_ONE TEST_F(SyncTest, DetectsAndReportsNameClashes) { const auto TESTFOLDER = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse client = g_clientManager->getCleanStandardClient(0, TESTFOLDER); ASSERT_TRUE(client->resetBaseFolderMulticlient()); ASSERT_TRUE(client->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(client)); // Needed so that we can create files with the same name. client->client.versions_disabled = true; // Populate local filesystem. const auto root = client->fsBasePath / "s"; fs::create_directories(root / "d" / "e"); createNameFile(root / "d", "f0"); createNameFile(root / "d", "f%30"); // Start the sync. handle backupId1 = client->setupSync_mainthread("s", "x", false, true); ASSERT_NE(backupId1, UNDEF); // Give the client time to synchronize. waitonsyncs(TIMEOUT, client); // Were any conflicts detected? ASSERT_TRUE(client->waitFor(SyncConflictState(true), TIMEOUT)); ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(false), TIMEOUT)); // First state change - not new updates should be notified // Can we obtain a list of the conflicts? list conflicts; ASSERT_TRUE(client->conflictsDetected(conflicts)); ASSERT_EQ(conflicts.size(), 1u); // Create another name clashing conflict. createNameFile(root / "d" / "e", "g0"); createNameFile(root / "d" / "e", "g%30"); // Give the client time to synchronize. waitonsyncs(TIMEOUT, client); // Were the new conflicts detected? ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); // Has the list of conflicts changed? conflicts.clear(); ASSERT_TRUE(client->conflictsDetected(conflicts)); ASSERT_EQ(conflicts.size(), 2u); ASSERT_EQ(conflicts.back().localPath, LocalPath::fromRelativePath("d").prependNewWithSeparator(client->syncByBackupId(backupId1)->localroot->localname)); ASSERT_EQ(conflicts.back().clashingLocalNames.size(), 2u); // Helpers. auto localConflictDetected = [](const NameConflict& nc, const LocalPath& name) { auto i = nc.clashingLocalNames.begin(); auto j = nc.clashingLocalNames.end(); return std::find(i, j, name) != j; }; ASSERT_TRUE(localConflictDetected(conflicts.back(), LocalPath::fromRelativePath("f%30"))); ASSERT_TRUE(localConflictDetected(conflicts.back(), LocalPath::fromRelativePath("f0"))); ASSERT_EQ(conflicts.back().clashingCloud.size(), 0u); // Conflicts Update flag should be disabled now ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(false), TIMEOUT)); client->triggerPeriodicScanEarly(backupId1); // Resolve the f0 / f%30 conflict. ASSERT_TRUE(fs::remove(root / "d" / "f%30")); // Give the sync some time to think. waitonsyncs(TIMEOUT, client); // We should still detect conflicts, and get an update due for having 1 conflict instead of the previous 2. ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); // Has the list of conflicts changed? conflicts.clear(); ASSERT_TRUE(client->conflictsDetected(conflicts)); ASSERT_GE(conflicts.size(), 1u); ASSERT_EQ(conflicts.front().localPath, LocalPath::fromRelativePath("e") .prependNewWithSeparator(LocalPath::fromRelativePath("d")) .prependNewWithSeparator(client->syncByBackupId(backupId1)->localroot->localname)); ASSERT_EQ(conflicts.front().clashingLocalNames.size(), 2u); ASSERT_TRUE(localConflictDetected(conflicts.front(), LocalPath::fromRelativePath("g%30"))); ASSERT_TRUE(localConflictDetected(conflicts.front(), LocalPath::fromRelativePath("g0"))); ASSERT_EQ(conflicts.front().clashingCloud.size(), 0u); // Conflicts Update flag should be disabled now ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(false), TIMEOUT)); // Resolve the g / g%30 conflict. ASSERT_TRUE(fs::remove(root / "d" / "e" / "g%30")); client->triggerPeriodicScanEarly(backupId1); // Give the sync some time to think. waitonsyncs(TIMEOUT, client); // No conflicts should be reported. ASSERT_TRUE(client->waitFor(SyncConflictState(false), TIMEOUT)); // Is the list of conflicts empty? conflicts.clear(); ASSERT_FALSE(client->conflictsDetected(conflicts)); ASSERT_EQ(conflicts.size(), 0u); // Create a remote name clash. auto node = client->drillchildnodebyname(client->gettestbasenode(), "x/d"); ASSERT_TRUE(!!node); ASSERT_TRUE(client->uploadFile(root / "d" / "f0", "h", node.get())); ASSERT_TRUE(client->uploadFile(root / "d" / "f0", "h", node.get())); // Let the client attempt to synchronize. waitonsyncs(TIMEOUT, client); // Have we detected any conflicts? ASSERT_TRUE(client->waitFor(SyncConflictState(true), TIMEOUT)); conflicts.clear(); ASSERT_TRUE(client->conflictsDetected(conflicts)); // Does our list of conflicts include remotes? ASSERT_GE(conflicts.size(), 1u); ASSERT_EQ(conflicts.front().cloudPath, string("/mega_test_sync/x/d")); ASSERT_EQ(conflicts.front().clashingCloud.size(), 2u); ASSERT_EQ(conflicts.front().clashingCloud[0].name, string("h")); ASSERT_EQ(conflicts.front().clashingCloud[1].name, string("h")); ASSERT_EQ(conflicts.front().clashingLocalNames.size(), 0u); // Conflicts Update flag should be disabled now ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(false), TIMEOUT)); // Resolve the remote conflict. ASSERT_TRUE(client->deleteremote("x/d/h")); // Wait for the client to process our changes. waitonsyncs(TIMEOUT, client); conflicts.clear(); client->conflictsDetected(conflicts); ASSERT_EQ(0u, conflicts.size()); // Conflicts should be resolved. ASSERT_TRUE(client->waitFor(SyncConflictState(false), TIMEOUT)); conflicts.clear(); ASSERT_FALSE(client->conflictsDetected(conflicts)); } /** * @brief TEST_F DetectsAndReportsSyncProblems * * Tests Synchronization when there are sync problems (name conflicts and stall issues). * * # Test1: generate two name conflicts in sync folder 1 * - U1: creates a directory tree in cloud drive (/x1) * - U1: creates a tree in local FS (root/s/d1) * - U1: creates a local file (root/s/d1/f0) * - U1: creates a local file (root/s/d1/f%30) * - U1: synchronizes d1 with x1 * - U1: wait for syncs * - U1 checks if name conflict has been detected and it's the expected one * - U1: creates a local file (root/s/d/f10) * - U1: creates a local file (root/s/d/f1%30) * - U1: synchronizes d1 with x1 * - U1: wait for syncs * - U1 checks if name conflict has been detected and it's the expected one * * # Test2: generate two name conflicts in sync folder 2 * - U1: creates a directory tree in cloud drive (/x2) * - U1: creates a tree in local FS (root/s/d2) * - U1: creates a local file (root/s/d2/f0) * - U1: creates a local file (root/s/d2/f%30) * - U1: synchronizes d2 with x2 * - U1: wait for syncs * - U1 checks if name conflict has been detected and it's the expected one * - U1: creates a local file (root/s/d2/f10) * - U1: creates a local file (root/s/d2/f1%30) * - U1: synchronizes d2 with x2 * - U1: wait for syncs * - U1 checks if name conflict has been detected and it's the expected one * * # Test3: generate a stall issue in sync folder 3 * - U1: creates a directory tree in cloud drive (/x3) * - U1: creates a tree in local FS (root/s/d3) * - U1: creates a local file (root/s/d3/n0) * - U1: creates a hardlink from n1 into path (root/s/d3/e/n5) * - U1: wait for syncs and check if stall issue has been detected and it's the expected one * * # Test4: generate a stall issue in sync folder 4 * - U1: creates a directory tree in cloud drive (/x4) * - U1: creates a tree in local FS (root/s/d4) * - U1: creates a local file (root/s/d4/n0) * - U1: creates a hardlink from n1 into path (root/s/d4/e/n5) * - U1: wait for syncs and check if stall issue has been detected and it's the expected one */ TEST_F(SyncTest, DetectsAndReportsSyncProblems) { const auto TESTFOLDER = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(8); StandardClientInUse client = g_clientManager->getCleanStandardClient(0, TESTFOLDER); client->client.versions_disabled = true; // allowing creating files with the same name. ASSERT_TRUE(client->resetBaseFolderMulticlient()); const std::string rootdir = "s"; const auto root = client->fsBasePath / rootdir; LOG_debug << "#### Test1(DetectsAndReportsSyncProblems): generate two name conflicts in sync " "folder 1 ####"; const std::string ldir1 = "d1"; const std::string rdir1 = "x1"; fs::create_directories(root / ldir1); ASSERT_TRUE(client->makeCloudSubdirs(rdir1, 0, 0)); ASSERT_TRUE(CatchupClients(client)); const handle backupId1 = client->setupSync_mainthread(path_u8string((root / ldir1)), rdir1, false, true); ASSERT_NE(backupId1, UNDEF) << "Invalid BackupId"; const std::string f11 = "f0"; const std::string f12 = "f%30"; createNameFile(root / ldir1, f11); createNameFile(root / ldir1, f12); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitFor(SyncConflictState(true), TIMEOUT)) << "Name conflicts were not detected"; ASSERT_TRUE( client->waitFor(SyncTotalConflictsStateUpdate(false), TIMEOUT)); // First state change - not new updates should be notified auto& ln1 = client->syncByBackupId(backupId1)->localroot->localname; ASSERT_NO_FATAL_FAILURE(client->checkSyncProblems(backupId1, 1u, 1u, ln1, f11, f12)); const std::string f13 = "f10"; const std::string f14 = "f1%30"; createNameFile(root / ldir1, f13); createNameFile(root / ldir1, f14); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); ASSERT_NO_FATAL_FAILURE(client->checkSyncProblems(backupId1, 1u, 2u, ln1, f13, f14)); LOG_debug << "#### Test2(DetectsAndReportsSyncProblems): generate two name conflicts in sync " "folder 2 ####"; // Create directory tree required for test const std::string ldir2 = "d2"; const std::string rdir2 = "x2"; fs::create_directories(root / ldir2); ASSERT_TRUE(client->makeCloudSubdirs(rdir2, 0, 0)); ASSERT_TRUE(CatchupClients(client)); const handle backupId2 = client->setupSync_mainthread(path_u8string((root / ldir2)), rdir2, false, true); ASSERT_NE(backupId2, UNDEF) << "Invalid BackupId"; const std::string f21 = "f0"; const std::string f22 = "f%30"; createNameFile(root / ldir2, f21); createNameFile(root / ldir2, f22); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); auto& ln2 = client->syncByBackupId(backupId2)->localroot->localname; ASSERT_NO_FATAL_FAILURE(client->checkSyncProblems(backupId2, 2u, 1u, ln2, f21, f22)); const std::string f23 = "f10"; const std::string f24 = "f1%30"; createNameFile(root / ldir2, f23); createNameFile(root / ldir2, f24); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); ASSERT_NO_FATAL_FAILURE(client->checkSyncProblems(backupId2, 2u, 2u, ln2, f23, f24)); LOG_debug << "#### Test3(DetectsAndReportsSyncProblems): generate a stall issue in sync folder " "1 ####"; const std::string ldir3 = "d3"; const std::string rdir3 = "x3"; fs::create_directories(root / ldir3); ASSERT_TRUE(client->makeCloudSubdirs(rdir3, 0, 0)); ASSERT_TRUE(CatchupClients(client)); const handle backupId3 = client->setupSync_mainthread(path_u8string((root / ldir3)), rdir3, false, true); ASSERT_NE(backupId3, UNDEF) << "Invalid BackupId"; fs::create_directories(root / ldir3 / "e"); constexpr auto transferTimeout{30s}; auto createFileAndWaitForUploadOf = [&](const fs::path& path, const std::string& name, std::chrono::seconds timeout) { std::promise p; auto f = p.get_future(); std::atomic_bool done{false}; auto filePath = path / name; client->mOnSyncTreeState = [&p, &done, filePath](const SyncConfig&, const LocalPath& localPath, treestate_t treeState, nodetype_t) { if (localPath.toPath(false) != path_u8string(filePath) || treeState != treestate_t::TREESTATE_SYNCED) { return; } if (done.exchange(true)) { LOG_err << "Promise has been already resolved"; return; } p.set_value(); }; EXPECT_TRUE(createFile(filePath, name)); auto status = f.wait_for(timeout); client->mOnSyncTreeState = nullptr; return status == std::future_status::ready; }; std::string fileNameTest3{"n0"}; ASSERT_TRUE(createFileAndWaitForUploadOf(root / ldir3, fileNameTest3, transferTimeout)); waitonsyncs(TIMEOUT, client); LocalPath sPath1; LocalPath tPath1; client->createHardLink(root / ldir3 / fileNameTest3, root / ldir3 / "e" / "n5", sPath1, tPath1); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); ASSERT_TRUE( client->waitFor(SyncTotalStallsStateUpdate(false), TIMEOUT)); // First time stall state, the update flag should be unset ASSERT_NO_FATAL_FAILURE(client->checkStallIssues(backupId3, 1u, sPath1, tPath1)); LOG_debug << "#### Test4(DetectsAndReportsSyncProblems): generate a stall issue in sync folder " "2 ####"; const std::string ldir4 = "d4"; const std::string rdir4 = "x4"; fs::create_directories(root / ldir4); ASSERT_TRUE(client->makeCloudSubdirs(rdir4, 0, 0)); ASSERT_TRUE(CatchupClients(client)); const handle backupId4 = client->setupSync_mainthread(path_u8string((root / ldir4)), rdir4, false, true); ASSERT_NE(backupId4, UNDEF) << "Invalid BackupId"; fs::create_directories(root / ldir4 / "e"); std::string fileNameTest4{"n0"}; ASSERT_TRUE(createFileAndWaitForUploadOf(root / ldir4, fileNameTest4, transferTimeout)); waitonsyncs(TIMEOUT, client); LocalPath sPath2; LocalPath tPath2; client->createHardLink(root / ldir4 / fileNameTest4, root / ldir4 / "e" / "n5", sPath2, tPath2); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->waitForSyncTotalStallsStateUpdateTrue(TIMEOUT)); ASSERT_NO_FATAL_FAILURE(client->checkStallIssues(backupId4, 2u, sPath2, tPath2)); } #endif #ifdef SRW_NEEDED_FOR_THIS_ONE TEST_F(SyncTest, DoesntDownloadFilesWithClashingNames) { const auto TESTFOLDER = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cu = g_clientManager->getCleanStandardClient(0, TESTFOLDER); StandardClientInUse cd = g_clientManager->getCleanStandardClient(0, TESTFOLDER); // Log callbacks. cu->logcb = true; cd->logcb = true; ASSERT_TRUE(cd->resetBaseFolderMulticlient(cu)); ASSERT_TRUE(cd->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cu)); // Populate cloud. { // Needed so that we can create files with the same name. cu->client.versions_disabled = true; // Create local test hierarchy. const auto root = cu->fsBasePath / "x"; // d will be duplicated and generate a clash. fs::create_directories(root / "d"); // dd will be singular, no clash. fs::create_directories(root / "dd"); // f will be duplicated and generate a clash. ASSERT_TRUE(createNameFile(root, "f")); // ff will be singular, no clash. ASSERT_TRUE(createNameFile(root, "ff")); auto node = cu->drillchildnodebyname(cu->gettestbasenode(), "x"); ASSERT_TRUE(!!node); // Upload d twice, generate clash. ASSERT_TRUE(cu->uploadFolderTree(root / "d", node.get())); ASSERT_TRUE(cu->uploadFolderTree(root / "d", node.get())); // Upload dd once. ASSERT_TRUE(cu->uploadFolderTree(root / "dd", node.get())); // Upload f twice, generate clash. ASSERT_TRUE(cu->uploadFile(root / "f", node.get())); ASSERT_TRUE(cu->uploadFile(root / "f", node.get())); // Upload ff once. ASSERT_TRUE(cu->uploadFile(root / "ff", node.get())); } ASSERT_TRUE(CatchupClients(cd, cu)); // Add and start sync. handle backupId1 = cd->setupSync_mainthread("sd", "x", false, false); ASSERT_NE(backupId1, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, cd); // Populate and confirm model. Model model; // d and f are missing due to name collisions in the cloud. model.root->addkid(model.makeModelSubfolder("x")); model.findnode("x")->addkid(model.makeModelSubfolder("dd")); model.findnode("x")->addkid(model.makeModelSubfile("ff")); // Needed because we've downloaded files. model.ensureLocalDebrisTmpLock("x"); // Confirm the model. ASSERT_TRUE(cd->confirmModel_mainthread( model.findnode("x"), backupId1, false, StandardClient::CONFIRM_LOCAL)); // Resolve the name collisions. ASSERT_TRUE(cd->deleteremote("x/d")); ASSERT_TRUE(cd->deleteremote("x/f")); // Wait for the sync to update. waitonsyncs(TIMEOUT, cd); // Confirm that d and f have now been downloaded. model.findnode("x")->addkid(model.makeModelSubfolder("d")); model.findnode("x")->addkid(model.makeModelSubfile("f")); // Local FS, Local Tree and Remote Tree should now be consistent. ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); } // TODO: re-enable after sync rework is merged TEST_F(SyncTest, DoesntUploadFilesWithClashingNames) { const auto TESTFOLDER = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cd = g_clientManager->getCleanStandardClient(0, TESTFOLDER); StandardClientInUse cu = g_clientManager->getCleanStandardClient(0, TESTFOLDER); // Log callbacks. cd->logcb = true; cu->logcb = true; // Log in client. ASSERT_TRUE(cd->resetBaseFolderMulticlient(cu)); ASSERT_TRUE(cd->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cu)); ASSERT_EQ(cd->basefolderhandle, cu->basefolderhandle); // Populate the local filesystem. const auto root = cu->fsBasePath / "su"; // Make sure clashing directories are skipped. fs::create_directories(root / "d0"); fs::create_directories(root / "d%30"); // Make sure other directories are uploaded. fs::create_directories(root / "d1"); // Make sure clashing files are skipped. createNameFile(root, "f0"); createNameFile(root, "f%30"); // Make sure other files are uploaded. createNameFile(root, "f1"); createNameFile(root / "d1", "f0"); // Start the syncs. handle backupId1 = cd->setupSync_mainthread("sd", "x", false, true); handle backupId2 = cu->setupSync_mainthread("su", "x", false, false); ASSERT_NE(backupId1, UNDEF); ASSERT_NE(backupId2, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, cu, cd); // Populate and confirm model. Model model; model.root->addkid(model.makeModelSubfolder("root")); model.findnode("root")->addkid(model.makeModelSubfolder("d1")); model.findnode("root")->addkid(model.makeModelSubfile("f1")); model.findnode("root/d1")->addkid(model.makeModelSubfile("f0")); model.ensureLocalDebrisTmpLock("root"); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("root"), backupId1)); // Remove the clashing nodes. fs::remove_all(root / "d0"); fs::remove_all(root / "f0"); cu->triggerPeriodicScanEarly(backupId2); // Wait for the sync to complete. waitonsyncs(TIMEOUT, cd, cu); // Confirm that d0 and f0 have been downloaded. model.findnode("root")->addkid(model.makeModelSubfolder("d0")); model.findnode("root")->addkid(model.makeModelSubfile("f0", "f%30")); ASSERT_TRUE(cu->confirmModel_mainthread(model.findnode("root"), backupId2, true)); } #endif TEST_F(SyncTest, RemotesWithControlCharactersSynchronizeCorrectly) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cd = g_clientManager->getCleanStandardClient(0, TESTROOT); StandardClientInUse cu = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. cd->logcb = true; cu->logcb = true; // Log in client. ASSERT_TRUE(cu->resetBaseFolderMulticlient(cd)); ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cu)); // Populate cloud. { // Log in client and clear remote contents. auto node = cu->drillchildnodebyname(cu->gettestbasenode(), "x"); ASSERT_TRUE(!!node); // Create some directories containing control characters. vector nodes(2); // Only some platforms will escape BEL. cu->prepareOneFolder(&nodes[0], "d\7", false); cu->prepareOneFolder(&nodes[1], "d", false); ASSERT_TRUE(cu->putnodes(node->nodeHandle(), NoVersioning, std::move(nodes))); // Do the same but with some files. auto root = cu->fsBasePath / "x"; fs::create_directories(root); // Placeholder name. ASSERT_TRUE(createNameFile(root, "f")); // Upload files. ASSERT_TRUE(cu->uploadFile(root / "f", "f\7", node.get())); ASSERT_TRUE(cu->uploadFile(root / "f", node.get())); } // Add and start sync. handle backupId1 = cd->setupSync_mainthread("sd", "x", false, false); ASSERT_NE(backupId1, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, cd); // Populate and confirm model. Model model; model.addfolder("x/d\7")->mFsName = "d%07"; model.addfolder("x/d"); model.addfile("x/f\7", "f")->mFsName = "f%07"; model.addfile("x/f", "f"); // Needed because we've downloaded files. model.ensureLocalDebrisTmpLock("x"); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Remotely remove d\7. ASSERT_TRUE(cd->deleteremote("x/d\7")); ASSERT_TRUE(model.movetosynctrash("x/d\7", "x")); // Locally remove f\7. auto syncRoot = cd->fsBasePath / "sd"; #ifdef _WIN32 ASSERT_TRUE(fs::remove(syncRoot / "f%07")); #else /* _WIN32 */ ASSERT_TRUE(fs::remove(syncRoot / "f\7")); #endif /* ! _WIN32 */ ASSERT_TRUE(!!model.removenode("x/f\7")); cd->triggerPeriodicScanEarly(backupId1); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, cd); // Confirm models. ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Locally create some files with control escapes in their names. #ifdef _WIN32 ASSERT_TRUE(fs::create_directories(syncRoot / "dd%07")); ASSERT_TRUE(createFile(syncRoot / "ff%07", "ff")); #else ASSERT_TRUE(fs::create_directories(syncRoot / "dd\7")); ASSERT_TRUE(createFile(syncRoot / "ff\7", "ff")); #endif /* ! _WIN32 */ // Wait for synchronization to complete. waitonsyncs(TIMEOUT, cd); // Update and confirm models. model.addfolder("x/dd\7")->fsName("dd%07"); model.addfile("x/ff\7", "ff")->fsName("ff%07"); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); } // this test contains tests for % being escaped from cloud->local which we are undoing for now on this branch TEST_F(SyncTest, DISABLED_RemotesWithEscapesSynchronizeCorrectly) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cu = g_clientManager->getCleanStandardClient(0, TESTROOT); StandardClientInUse cd = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. cd->logcb = true; cu->logcb = true; // Log in client. ASSERT_TRUE(cu->resetBaseFolderMulticlient(cd)); ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cu)); // Populate cloud. { // Build test hierarchy. const auto root = cu->fsBasePath / "x"; // Escapes will not be decoded as we're uploading directly. fs::create_directories(root / "d0"); fs::create_directories(root / "d%30"); ASSERT_TRUE(createNameFile(root, "f0")); ASSERT_TRUE(createNameFile(root, "f%30")); auto node = cu->drillchildnodebyname(cu->gettestbasenode(), "x"); ASSERT_TRUE(!!node); // Upload directories. ASSERT_TRUE(cu->uploadFolderTree(root / "d0", node.get())); ASSERT_TRUE(cu->uploadFolderTree(root / "d%30", node.get())); // Upload files. ASSERT_TRUE(cu->uploadFile(root / "f0", node.get())); ASSERT_TRUE(cu->uploadFile(root / "f%30", node.get())); } // Add and start sync. handle backupId1 = cd->setupSync_mainthread("sd", "x", false, false); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, cd); // Populate and confirm local fs. Model model; model.addfolder("x/d0"); model.addfolder("x/d%30")->fsName("d%2530"); model.addfile("x/f0", "f0"); model.addfile("x/f%30", "f%30")->fsName("f%2530"); // Needed as we've downloaded files. model.ensureLocalDebrisTmpLock("x"); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Locally remove an escaped node. const auto syncRoot = cd->syncSet(backupId1).localpath; fs::remove_all(syncRoot / "d%2530"); ASSERT_TRUE(!!model.removenode("x/d%30")); // Remotely remove an escaped file. ASSERT_TRUE(cd->deleteremote("x/f%30")); ASSERT_TRUE(model.movetosynctrash("x/f%30", "x")); // Wait for sync up to complete. waitonsyncs(TIMEOUT, cd); // Confirm models. ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); } // this test contains tests for % being escaped with %25 from cloud->local TEST_F(SyncTest, RemotesWithEscapedEscapesSynchronizeCorrectly) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cu = g_clientManager->getCleanStandardClient(0, TESTROOT); StandardClientInUse cd = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. cd->logcb = true; cu->logcb = true; // Log in client. ASSERT_TRUE(cu->resetBaseFolderMulticlient(cd)); ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cu)); // Populate cloud. { // Build test hierarchy. const auto root = cu->fsBasePath / "x"; // Escapes will not be decoded as we're uploading directly. fs::create_directories(root / "d0"); fs::create_directories(root / "d%2530"); ASSERT_TRUE(createNameFile(root, "f0")); ASSERT_TRUE(createNameFile(root, "f%2530")); auto node = cu->drillchildnodebyname(cu->gettestbasenode(), "x"); ASSERT_TRUE(!!node); // Upload directories. ASSERT_TRUE(cu->uploadFolderTree(root / "d0", node.get())); ASSERT_TRUE(cu->uploadFolderTree(root / "d%2530", node.get())); // Upload files. ASSERT_TRUE(cu->uploadFile(root / "f0", node.get())); ASSERT_TRUE(cu->uploadFile(root / "f%2530", node.get())); } // Add and start sync. handle backupId1 = cd->setupSync_mainthread("sd", "x", false, false); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, cd); // Populate and confirm local fs. Model model; model.addfolder("x/d0"); model.addfolder("x/d%2530"); model.addfile("x/f0", "f0"); model.addfile("x/f%2530", "f%2530"); // Needed as we've downloaded files. model.ensureLocalDebrisTmpLock("x"); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Locally remove an escaped node. const auto syncRoot = cd->syncSet(backupId1).localpath; fs::remove_all(syncRoot / "d%2530"); ASSERT_TRUE(!!model.removenode("x/d%2530")); // Remotely remove an escaped file. ASSERT_TRUE(cd->deleteremote("x/f%2530")); ASSERT_TRUE(model.movetosynctrash("x/f%2530", "x")); // Wait for sync up to complete. waitonsyncs(TIMEOUT, cd); // Confirm models. ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Locally create some files with escapes in their names. { // Bogus escapes. ASSERT_TRUE(fs::create_directories(syncRoot / "dd%")); model.addfolder("x/dd%"); ASSERT_TRUE(createNameFile(syncRoot, "ff%")); model.addfile("x/ff%", "ff%"); // Sane character escapes. ASSERT_TRUE(fs::create_directories(syncRoot / "dd%31")); model.addfolder("x/dd1")->fsName("dd%31"); ASSERT_TRUE(createNameFile(syncRoot, "ff%31")); model.addfile("x/ff1", "ff%31")->fsName("ff%31"); } // Wait for synchronization to complete. waitonsyncs(TIMEOUT, cd); // Confirm model. ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Let's try with escaped control sequences. ASSERT_TRUE(fs::create_directories(syncRoot / "dd%250a")); model.addfolder("x/dd%0a")->fsName("dd%250a"); ASSERT_TRUE(createNameFile(syncRoot, "ff%250a")); model.addfile("x/ff%0a", "ff%250a")->fsName("ff%250a"); // Wait for sync and confirm model. waitonsyncs(TIMEOUT, cd); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); // Remotely delete the nodes with control sequences. ASSERT_TRUE(cd->deleteremote("x/dd%0a")); model.movetosynctrash("x/dd%0a", "x"); ASSERT_TRUE(cd->deleteremote("x/ff%0a")); model.movetosynctrash("x/ff%0a", "x"); // Wait for sync and confirm model. waitonsyncs(TIMEOUT, cd); ASSERT_TRUE(cd->confirmModel_mainthread(model.findnode("x"), backupId1)); } TEST_F(SyncTest, BasicSyncExportImport) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = chrono::seconds(4); // Sync client. unique_ptr cx(new StandardClient(TESTROOT, "cx")); // Log callbacks. cx->logcb = true; // Log in client. ASSERT_TRUE(cx->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 1, 3)); // Create and start syncs. auto id0 = cx->setupSync_mainthread("s0", "s/s_0", false, false); ASSERT_NE(id0, UNDEF); auto id1 = cx->setupSync_mainthread("s1", "s/s_1", false, false); ASSERT_NE(id1, UNDEF); auto id2 = cx->setupSync_mainthread("s2", "s/s_2", false, false); ASSERT_NE(id2, UNDEF); // Get our hands on the sync's local root. auto root0 = cx->syncSet(id0).localpath; auto root1 = cx->syncSet(id1).localpath; auto root2 = cx->syncSet(id2).localpath; // Give the syncs something to synchronize. Model model0; Model model1; Model model2; model0.addfile("d0/f0"); model0.addfile("f0"); model0.generate(root0); model1.addfile("d0/f0"); model1.addfile("d0/f1"); model1.addfile("d1/f0"); model1.addfile("d1/f1"); model1.generate(root1); model2.addfile("f0"); model2.addfile("f1"); model2.generate(root2); cx->triggerPeriodicScanEarly(id0); cx->triggerPeriodicScanEarly(id1); cx->triggerPeriodicScanEarly(id2); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, cx.get()); // Make sure everything was uploaded okay. ASSERT_TRUE(cx->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(cx->confirmModel_mainthread(model1.root.get(), id1)); ASSERT_TRUE(cx->confirmModel_mainthread(model2.root.get(), id2)); // Export the syncs. auto configs = cx->exportSyncConfigs(); ASSERT_FALSE(configs.empty()); // Log out client, don't keep caches. cx.reset(); // Recreate client. cx.reset(new StandardClient(TESTROOT, "cx")); // Log client back in. ASSERT_TRUE(cx->login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Import the syncs. ASSERT_TRUE(cx->importSyncConfigs(std::move(configs))); // Determine the imported sync's backup IDs. id0 = cx->backupIdForSyncPath(root0); ASSERT_NE(id0, UNDEF); id1 = cx->backupIdForSyncPath(root1); ASSERT_NE(id1, UNDEF); id2 = cx->backupIdForSyncPath(root2); ASSERT_NE(id2, UNDEF); // Make sure nothing's changed since we exported the syncs. ASSERT_TRUE(cx->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(cx->confirmModel_mainthread(model1.root.get(), id1)); ASSERT_TRUE(cx->confirmModel_mainthread(model2.root.get(), id2)); // Make some changes. model0.addfile("d0/f1"); model0.generate(root0); model1.addfile("f0"); model1.generate(root1); model2.addfile("d0/d0f0"); model2.generate(root2); // Imported syncs should be disabled. // So, we're waiting for the syncs to do precisely nothing. waitonsyncs(TIMEOUT, cx.get()); // Confirm should fail. ASSERT_FALSE(cx->confirmModel_mainthread(model0.root.get(), id0, false, StandardClient::Confirm::CONFIRM_ALL, true)); ASSERT_FALSE(cx->confirmModel_mainthread(model1.root.get(), id1, false, StandardClient::Confirm::CONFIRM_ALL, true)); ASSERT_FALSE(cx->confirmModel_mainthread(model2.root.get(), id2, false, StandardClient::Confirm::CONFIRM_ALL, true)); // Enable the imported syncs. ASSERT_TRUE(cx->enableSyncByBackupId(id0, "sync0 ")); ASSERT_TRUE(cx->enableSyncByBackupId(id1, "sync1 ")); ASSERT_TRUE(cx->enableSyncByBackupId(id2, "sync2 ")); // Wait for sync to complete. waitonsyncs(TIMEOUT, cx.get()); // Changes should now be in the cloud. ASSERT_TRUE(cx->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(cx->confirmModel_mainthread(model1.root.get(), id1)); ASSERT_TRUE(cx->confirmModel_mainthread(model2.root.get(), id2)); } TEST_F(SyncTest, RenameReplaceFileBetweenSyncs) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c0 = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c0->logcb = true; // Log in client. ASSERT_TRUE(c0->resetBaseFolderMulticlient()); ASSERT_TRUE(c0->makeCloudSubdirs("s0", 0, 0)); ASSERT_TRUE(c0->makeCloudSubdirs("s1", 0, 0)); ASSERT_TRUE(CatchupClients(c0)); //ASSERT_TRUE(c0->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s0", 0, 0)); // Set up syncs. const auto id0 = c0->setupSync_mainthread("s0", "s0", false, false); ASSERT_NE(id0, UNDEF); const auto id1 = c0->setupSync_mainthread("s1", "s1", false, false); ASSERT_NE(id1, UNDEF); // Convenience. const auto SYNCROOT0 = c0->fsBasePath / "s0"; const auto SYNCROOT1 = c0->fsBasePath / "s1"; // Set up models. Model model0; Model model1; model0.addfile("f0", "x"); model0.generate(SYNCROOT0); c0->triggerPeriodicScanEarly(id0); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); // Move s0/f0 to s1/f0. model1 = model0; fs::rename(SYNCROOT0 / "f0", SYNCROOT1 / "f0"); // Replace s0/f0. model0.removenode("f0"); model0.addfile("f0", "y"); ASSERT_TRUE(createFile(SYNCROOT0 / "f0", "y")); c0->triggerPeriodicScanEarly(id0); c0->triggerPeriodicScanEarly(id1); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); // Disable s0. ASSERT_TRUE(c0->disableSync(id0, NO_SYNC_ERROR, false, true)); // Make sure s0 is disabled. ASSERT_TRUE(createFile(SYNCROOT0 / "f1", "z")); c0->triggerPeriodicScanEarly(id0); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread( model0.root.get(), id0, false, StandardClient::CONFIRM_REMOTE)); // Move s1/f0 to s0/f2. model1.removenode("f0"); fs::rename(SYNCROOT1 / "f0", SYNCROOT0 / "f2"); // Replace s1/f0. model1.addfile("f0", "q"); ASSERT_TRUE(createFile(SYNCROOT1 / "f0", "q")); c0->triggerPeriodicScanEarly(id0); c0->triggerPeriodicScanEarly(id1); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread( model0.root.get(), id0, false, StandardClient::CONFIRM_REMOTE)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); } TEST_F(SyncTest, RenameReplaceFileWithinSync) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Populate model. Model m; // Will be rename-replaced to /ft. m.addfile("fs"); // Will be rename-replaced down to /dd/dt/ft. m.addfile("dd/fs"); m.addfolder("dd/dt"); // Will be rename-replaced up to /du/ft. m.addfile("du/ds/fs"); // Populate local filesystem. m.generate(c->fsBasePath / "s"); // Add and start sync. auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, c); // Make sure the initial sync was successful. ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); // Rename/replace across siblings. // // Models this case: // echo fs > fs // // mv fs ft && echo x > fs // { // Locally move fs to ft. fs::rename(c->fsBasePath / "s" / "fs", c->fsBasePath / "s" / "ft"); m.findnode("fs")->name = "ft"; // Replace fs. ASSERT_TRUE(createFile(c->fsBasePath / "s" / "fs", "x")); m.addfile("fs", "x"); // For periodic scanning. c->triggerPeriodicScanEarly(id); // Wait for the change to be synchronized. waitonsyncs(TIMEOUT, c); // Was the change correctly synchronized? ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); } // Rename/replace down the hierarchy. // // Models this case: // mkdir -p dd/dt // echo dd/fs > dd/fs // // mv dd/fs dd/dt/ft && echo x > dd/fs // { // Move /dd/fs to /dd/dt/ft. fs::rename(c->fsBasePath / "s" / "dd" / "fs", c->fsBasePath / "s" / "dd" / "dt" / "ft"); m.movenode("dd/fs", "dd/dt"); m.findnode("dd/dt/fs")->name = "ft"; // Replace /dd/fs. ASSERT_TRUE(createFile(c->fsBasePath / "s" / "dd" / "fs", "x")); m.addfile("dd/fs", "x"); // For periodic scanning. c->triggerPeriodicScanEarly(id); // Wait for the change to be synchronized. waitonsyncs(TIMEOUT, c); // Was the change correctly synchronized? ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); } // Rename/replace up the hierarchy. // // Models this case: // mkdir -p du/ds // echo du/ds/fs > du/ds/fs // // mv du/ds/fs du/fs && echo x > du/ds/fs // { // Move du/ds/fs to du/ft. fs::rename(c->fsBasePath / "s" / "du" / "ds" / "fs", c->fsBasePath / "s" / "du" / "ft"); m.movenode("du/ds/fs", "du"); m.findnode("du/fs")->name = "ft"; // Replace du/ds/fs. ASSERT_TRUE(createFile(c->fsBasePath / "s" / "du" / "ds" / "fs", "x")); m.addfile("du/ds/fs", "x"); // For periodic scanning. c->triggerPeriodicScanEarly(id); // Wait for the change to be synchronized. waitonsyncs(TIMEOUT, c); // Was the change correctly synchronized? ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); } } // TODO: re-enable after sync rework is merged TEST_F(SyncTest, RenameReplaceFolderBetweenSyncs) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c0 = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c0->logcb = true; // Log in client. ASSERT_TRUE(c0->resetBaseFolderMulticlient()); ASSERT_TRUE(c0->makeCloudSubdirs("s0", 0, 0)); ASSERT_TRUE(c0->makeCloudSubdirs("s1", 0, 0)); ASSERT_TRUE(CatchupClients(c0)); // Set up syncs. const auto id0 = c0->setupSync_mainthread("s0", "s0", false, false); ASSERT_NE(id0, UNDEF); const auto id1 = c0->setupSync_mainthread("s1", "s1", false, false); ASSERT_NE(id1, UNDEF); // Convenience. const auto SYNCROOT0 = c0->fsBasePath / "s0"; const auto SYNCROOT1 = c0->fsBasePath / "s1"; // Set up models. Model model0; Model model1; model0.addfile("d0/f0"); model0.generate(SYNCROOT0); c0->triggerPeriodicScanEarly(id0); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); // Move s0/d0 to s1/d0. (and replace) model1 = model0; fs::rename(SYNCROOT0 / "d0", SYNCROOT1 / "d0"); // Replace s0/d0. model0.removenode("d0/f0"); fs::create_directories(SYNCROOT0 / "d0"); c0->triggerPeriodicScanEarly(id0); c0->triggerPeriodicScanEarly(id1); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); // Disable s0. ASSERT_TRUE(c0->disableSync(id0, NO_SYNC_ERROR, false, true)); // Make sure s0 is disabled. fs::create_directories(SYNCROOT0 / "d1"); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread( model0.root.get(), id0, false, StandardClient::CONFIRM_REMOTE)); // Move s1/d0 to s0/d2. model1.removenode("d0/f0"); fs::rename(SYNCROOT1 / "d0", SYNCROOT0 / "d2"); // Replace s1/d0. fs::create_directories(SYNCROOT1 / "d0"); c0->triggerPeriodicScanEarly(id0); c0->triggerPeriodicScanEarly(id1); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm models. ASSERT_TRUE(c0->confirmModel_mainthread( model0.root.get(), id0, false, StandardClient::CONFIRM_REMOTE)); ASSERT_TRUE(c0->confirmModel_mainthread(model1.root.get(), id1)); } TEST_F(SyncTest, RenameReplaceFolderWithinSync) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c0 = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c0->logcb = true; // Log in client. ASSERT_TRUE(c0->resetBaseFolderMulticlient()); ASSERT_TRUE(c0->makeCloudSubdirs("s0", 0, 0)); ASSERT_TRUE(CatchupClients(c0)); // Set up sync. const auto id = c0->setupSync_mainthread("s0", "s0", false, false); ASSERT_NE(id, UNDEF); // Populate local FS. const auto SYNCROOT = c0->fsBasePath / "s0"; Model model; model.addfile("d1/f0"); model.generate(SYNCROOT); c0->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(DEFAULTWAIT, c0); // Confirm model. ASSERT_TRUE(c0->confirmModel_mainthread(model.root.get(), id)); // Rename /d1 to /d2. // This tests the case where the target is processed after the source. model.addfolder("d2"); model.movenode("d1/f0", "d2"); fs::rename(SYNCROOT / "d1", SYNCROOT / "d2"); // Replace /d1. fs::create_directories(SYNCROOT / "d1"); c0->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm model. ASSERT_TRUE(c0->confirmModel_mainthread(model.root.get(), id)); // Rename /d2 to /d0. // This tests the case where the target is processed before the source. model.addfolder("d0"); model.movenode("d2/f0", "d0"); fs::rename(SYNCROOT / "d2", SYNCROOT / "d0"); // Replace /d2. fs::create_directories(SYNCROOT / "d2"); c0->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c0); // Confirm model. ASSERT_TRUE(c0->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, SyncIncompatibleMoveStallsAndResolutions) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("x", 1, 3)); ASSERT_TRUE(CatchupClients(c)); // Add and start syncs. auto id0 = c->setupSync_mainthread("s0", "x/x_0", false, false); ASSERT_NE(id0, UNDEF); auto SYNC0 = c->syncSet(id0); auto id1 = c->setupSync_mainthread("s1", "x/x_1", false, false); ASSERT_NE(id1, UNDEF); auto SYNC1 = c->syncSet(id1); auto id2 = c->setupSync_mainthread("s2", "x/x_2", false, false); ASSERT_NE(id2, UNDEF); auto SYNC2 = c->syncSet(id2); // Create and populate models. Model model0; model0.root->addkid(model0.buildModelSubdirs("d", 8, 2, 0)); model0.generate(SYNC0.localpath, true); Model model1; model1.root->addkid(model1.buildModelSubdirs("d", 1, 1, 2)); model1.generate(SYNC1.localpath, true); Model model2; model2.root->addkid(model2.buildModelSubdirs("d", 1, 1, 2)); model2.generate(SYNC2.localpath, true); c->triggerPeriodicScanEarly(id0); c->triggerPeriodicScanEarly(id1); c->triggerPeriodicScanEarly(id2); // Wait for the engine to process changes. auto waitResult = waitonsyncs(TIMEOUT, c); // Wait for the engine to signal a stall. ASSERT_TRUE(noSyncStalled(waitResult)); c->setSyncPausedByBackupId(id0, true); c->setSyncPausedByBackupId(id1, true); c->setSyncPausedByBackupId(id2, true); std::ofstream remoteFile("remoteFile"); remoteFile << "remoteFile"; remoteFile.close(); // case 0: make some crossed-over moves fs::rename(SYNC0.localpath / "d" / "d_0", SYNC0.localpath / "d" / "d_1" / "d_0"); c->movenode("x/x_0/d/d_1", "x/x_0/d/d_0"); // case 1: update the local and remote file differently while paused (to resolve remotely) std::ofstream fstream1(SYNC1.localpath / "d" / "file0_d", ios_base::app); fstream1 << " plus local change"; fstream1.close(); ASSERT_TRUE(c->uploadFile("remoteFile", "file0_d", "x/x_1/d", 30, ClaimOldVersion)); // case 2: update the local and remote file differently while paused (to resolve locally) std::ofstream fstream2(SYNC2.localpath / "d" / "file0_d", ios_base::app); fstream2 << " plus local change"; fstream2.close(); ASSERT_TRUE(c->uploadFile("remoteFile", "file0_d", "x/x_2/d", 30, ClaimOldVersion)); c->setSyncPausedByBackupId(id0, false); c->setSyncPausedByBackupId(id1, false); c->setSyncPausedByBackupId(id2, false); c->triggerPeriodicScanEarly(id0); c->triggerPeriodicScanEarly(id1); c->triggerPeriodicScanEarly(id2); // Be absolutely sure we've stalled. (stall is across all syncs - todo: figure out if each one contains a stall) ASSERT_TRUE(c->waitFor(SyncStallState(true), DEFAULTWAIT)); // resolve case 0: Make it possible for the sync to resolve the stall. fs::rename( SYNC0.localpath / "d" / "d_1" / "d_0", SYNC0.localpath / "d" / "d_0"); model0.movenode("d/d_1", "d/d_0"); // resolve case 1: remove remote, local should replace it LOG_info << "test removes x/x_1/d/file0_d to resolve stall in sync1"; c->deleteremote("x/x_1/d/file0_d"); model1.findnode("d/file0_d")->content = "file0_d plus local change"; // resolve case 2: remove local, remote should replace it fs::remove(SYNC2.localpath / "d" / "file0_d"); model2.findnode("d/file0_d")->content = "remoteFile"; model2.ensureLocalDebrisTmpLock(""); // due to download temp location c->triggerPeriodicScanEarly(id0); c->triggerPeriodicScanEarly(id2); LOG_debug << "Wait for the sync to exit the stall state."; ASSERT_TRUE(c->waitFor(SyncStallState(false), chrono::seconds(40))); LOG_debug << "Make sure the sync's completed its processing."; waitResult = waitonsyncs(chrono::seconds(5), c); ASSERT_TRUE(noSyncStalled(waitResult)); LOG_debug << "now the sync should have unstalled and resolved the stalled cases"; // Confirm state ASSERT_TRUE(c->confirmModel_mainthread(model0.root.get(), id0)); ASSERT_TRUE(c->confirmModel_mainthread(model1.root.get(), id1)); ASSERT_TRUE(c->confirmModel_mainthread(model2.root.get(), id2)); } TEST_F(SyncTest, DownloadedDirectoriesHaveFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Create /d in the cloud. { vector nodes(1); // Initialize new node. c->prepareOneFolder(&nodes[0], "d", false); // Get our hands on the sync root. auto root = c->drillchildnodebyname(c->gettestbasenode(), "s"); ASSERT_TRUE(root); // Create new node in the cloud. ASSERT_TRUE(c->putnodes(root->nodeHandle(), NoVersioning, std::move(nodes))); } // Add and start sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Confirm /d has made it to disk. Model model; model.addfolder("d"); ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Trigger a filesystem notification. model.addfile("d/f", "x"); ASSERT_TRUE(createFile(SYNCROOT / "d" / "f", "x")); c->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Confirm /d/f made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, FilesystemWatchesPresentAfterResume) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); auto c = std::make_unique(TESTROOT, "c"); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Add and start sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Build model and populate filesystem. Model model; model.addfolder("d0/d0d0"); model.generate(SYNCROOT); c->triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c.get()); // Make sure directories made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Logout / Resume. { string session; // Save session. c->client.dumpsession(session); // Logout (taking care to preserve the caches.) c->localLogout(); // Recreate client. c.reset(new StandardClient(TESTROOT, "c")); // Hook onAutoResumeResult callback. promise notify; c->onAutoResumeResult = [&](const SyncConfig&) { notify.set_value(); }; // Resume session. ASSERT_TRUE(c->login_fetchnodesFromSession(session)); // Wait for the sync to be resumed. ASSERT_TRUE(debugTolerantWaitOnFuture(notify.get_future(), 45)); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c.get()); // Make sure everything's as we left it. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } c->received_node_actionpackets = false; // Trigger some filesystem notifications. { model.addfile("f", "f"); ASSERT_TRUE(createFile(SYNCROOT / "f", "f")); model.addfile("d0/d0f", "d0f"); ASSERT_TRUE(createFile(SYNCROOT / "d0" / "d0f", "d0f")); model.addfile("d0/d0d0/d0d0f", "d0d0f"); ASSERT_TRUE(createFile(SYNCROOT / "d0" / "d0d0" / "d0d0f", "d0d0f")); } ASSERT_TRUE(c->waitForNodesUpdated(30)) << " no actionpacket received"; c->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c.get()); // Did the new files make it to the cloud? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, MoveTargetHasFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); auto c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Set up sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Build model and populate filesystem. Model model; model.addfile(".megaignore", "+sync:.megaignore"); model.addfolder("d0/dq"); model.addfolder("d1"); model.addfolder("d2/dx"); model.generate(SYNCROOT); c->triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Wait for everything to reach the cloud. { auto root = c->gettestbasenode(); ASSERT_NE(root, nullptr); root = c->drillchildnodebyname(root, "s"); ASSERT_NE(root, nullptr); auto predicate = SyncRemoteMatch(root.get(), model.root.get()); ASSERT_TRUE(c->waitFor(std::move(predicate), DEFAULTWAIT)); } // Confirm directories have hit the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Local move. { // d0/dq -> d1/dq (ascending.) model.movenode("d0/dq", "d1"); fs::rename(SYNCROOT / "d0" / "dq", SYNCROOT / "d1" / "dq"); // d2/dx -> d1/dx (descending.) model.movenode("d2/dx", "d1"); fs::rename(SYNCROOT / "d2" / "dx", SYNCROOT / "d1" / "dx"); } c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Make sure movement has propagated to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Trigger some filesystem notifications. model.addfile("d1/dq/fq", "q"); model.addfile("d1/dx/fx", "x"); ASSERT_TRUE(createFile(SYNCROOT / "d1" / "dq" / "fq", "q")); ASSERT_TRUE(createFile(SYNCROOT / "d1" / "dx" / "fx", "x")); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Have the files made it up to the cloud? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // So we can detect whether we've received packets for the below. c->received_node_actionpackets = false; // Remotely move. { StandardClient cr(TESTROOT, "cr"); // Log in client. ASSERT_TRUE(cr.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // d1/dq -> d2/dq (ascending.) model.movenode("d1/dq", "d2"); ASSERT_TRUE(cr.movenode("s/d1/dq", "s/d2")); // d1/dx -> d0/dx (descending.) model.movenode("d1/dx", "d0"); ASSERT_TRUE(cr.movenode("s/d1/dx", "s/d0")); } // Wait for the client to receive action packets for the above change. ASSERT_TRUE(c->waitForNodesUpdated(30)); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Make sure movements occured on disk. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Trigger some filesystem notifications. model.removenode("d2/dq/fq"); model.removenode("d0/dx/fx"); fs::remove(SYNCROOT / "d2" / "dq" / "fq"); fs::remove(SYNCROOT / "d0" / "dx" / "fx"); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Make sure removes propagated to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } // TODO: re-enable after sync rework is merged TEST_F(SyncTest, DeleteReplaceReplacementHasFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto ROOT = c->syncSet(id).localpath; // Populate filesystem. Model model; model.addfolder("dx/f"); model.generate(ROOT); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Make sure the directory's been uploaded to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Remove/replace the directory. fs::remove_all(ROOT / "dx"); fs::create_directory(ROOT / "dx"); c->triggerPeriodicScanEarly(id); // Wait for all notifications to be processed. waitonsyncs(TIMEOUT, c); // Make sure the new directory is in the cloud. model.removenode("dx/f"); ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Add a file in the new directory so we trigger a notification. out() << "creating file dx/g"; model.addfile("dx/g", "g"); ASSERT_TRUE(createFile(ROOT / "dx" / "g", "g")); c->triggerPeriodicScanEarly(id); // Wait for notifications to be processed. waitonsyncs(TIMEOUT, c); // Check if g has been uploaded. // If it hasn't, we probably didn't receive a notification from the filesystem. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, RenameReplaceSourceAndTargetHaveFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(8); auto c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Build model and populate filesystem. Model model; model.addfolder("dq"); model.addfolder("dz"); model.generate(SYNCROOT); c->triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Make sure directories have made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Rename /dq -> /dr (ascending), replace /dq. model.addfolder("dr"); fs::rename(SYNCROOT / "dq", SYNCROOT / "dr"); fs::create_directories(SYNCROOT / "dq"); // Rename /dz -> /dy (descending), replace /dz. model.addfolder("dy"); fs::rename(SYNCROOT / "dz", SYNCROOT / "dy"); fs::create_directories(SYNCROOT / "dz"); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Make sure moves made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Make sure rename targets still receive notifications. model.addfile("dr/fr", "r"); model.addfile("dy/fy", "y"); ASSERT_TRUE(createFile(SYNCROOT / "dr" / "fr", "r")); ASSERT_TRUE(createFile(SYNCROOT / "dy" / "fy", "y")); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Did the files make it to the cloud? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Make sure (now replaced) rename sources still receive notifications. model.addfile("dq/fq", "q"); model.addfile("dz/fz", "z"); LOG_debug << " --- Creating files fq and fz now ----"; ASSERT_TRUE(createFile(SYNCROOT / "dq" / "fq", "q")); ASSERT_TRUE(createFile(SYNCROOT / "dz" / "fz", "z")); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Did the files make it to the cloud? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, RenameTargetHasFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync. const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); const auto SYNCROOT = c->syncSet(id).localpath; // Build model and populate filesystem. Model model; model.addfolder("dq"); model.addfolder("dz"); model.generate(SYNCROOT); c->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Confirm model. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Locally rename. { // - dq -> dr (ascending) model.removenode("dq"); model.addfolder("dr"); fs::rename(SYNCROOT / "dq", SYNCROOT / "dr"); // - dz -> dy (descending) model.removenode("dz"); model.addfolder("dy"); fs::rename(SYNCROOT / "dz", SYNCROOT / "dy"); } c->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Make sure rename has hit the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Make sure rename targets receive notifications. model.addfile("dr/f", "x"); model.addfile("dy/f", "y"); ASSERT_TRUE(createFile(SYNCROOT / "dr" / "f", "x")); ASSERT_TRUE(createFile(SYNCROOT / "dy" / "f", "y")); c->triggerPeriodicScanEarly(id); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Check file has made it to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Remotely rename. { StandardClientInUse cr = g_clientManager->getCleanStandardClient(0, TESTROOT); auto root = cr->gettestbasenode(); ASSERT_TRUE(root); // dr -> ds (ascending.) model.removenode("dr"); model.addfile("ds/f", "x"); auto dr = cr->drillchildnodebyname(root, "s/dr"); ASSERT_TRUE(dr); ASSERT_TRUE(cr->setattr(dr.get(), attr_map('n', "ds"))); // dy -> dx (descending.) model.removenode("dy"); model.addfile("dx/f", "y"); auto dy = cr->drillchildnodebyname(root, "s/dy"); ASSERT_TRUE(dy); ASSERT_TRUE(cr->setattr(dy.get(), attr_map('n', "dx"))); } // it can take a while for APs to arrive (or to be sent) WaitMillisec(4000); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Confirm move has occured locally. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); c->received_node_actionpackets = false; // Check that /ds and /dx receive notifications. model.removenode("ds/f"); model.removenode("dx/f"); fs::remove(SYNCROOT / "ds" / "f"); fs::remove(SYNCROOT / "dx" / "f"); c->triggerPeriodicScanEarly(id); ASSERT_TRUE(c->waitForNodesUpdated(30)) << " no actionpacket received in c"; // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Confirm remove has hit the cloud. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, ReplaceParentWithEmptyChild) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); handle id; Model model; string session; StandardClient c(TESTROOT, "c"); // Log callbacks. c.logcb = true; // Log in client. ASSERT_TRUE(c.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Populate initial filesystem. { // Add and start sync. id = c.setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Build model and populate filesystem. model.addfolder("0/1/2/3"); model.addfolder("4/5/6/7"); model.generate(c.syncSet(id).localpath); c.triggerPeriodicScanEarly(id); // Wait for the sync to complete. waitonsyncs(TIMEOUT, &c); // Make sure the tree made it to the cloud. ASSERT_TRUE(c.confirmModel_mainthread(model.root.get(), id)); // Temporarily disable the sync. // // The rationale for this is that we want to make sure that // the cloud changes below are visible to this client before // it starts trying synchronize stuff. ASSERT_TRUE(c.disableSync(id, NO_SYNC_ERROR, true, true)); } // Remotely replace 4 with 4/5/6/7. { model.movetosynctrash("4", ""); model.addfolder("4"); // New client so we can alter the cloud without resuming syncs. StandardClient cr(TESTROOT, "cr"); // Log callbacks. cr.logcb = true; // Log in client. ASSERT_TRUE(cr.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Replace 4 with 4/5/6/7. ASSERT_TRUE(cr.movenode("s/4/5/6/7", "s")); ASSERT_TRUE(cr.deleteremote("s/4")); ASSERT_TRUE(cr.rename("s/7", "4")); // Wait for our primary client to see cr's changes. ASSERT_TRUE(c.waitFor(SyncRemoteMatch("s", model.root.get()), TIMEOUT)); } // Locally replace 0 with 0/1/2/3. { model.removenode("0"); model.addfolder("0"); fs::rename(c.fsBasePath / "s" / "0" / "1" / "2" / "3", c.fsBasePath / "s" / "3"); fs::remove_all(c.fsBasePath / "s" / "0"); fs::rename(c.fsBasePath / "s" / "3", c.fsBasePath / "s" / "0"); } // Resume our sync. ASSERT_TRUE(c.enableSyncByBackupId(id, "c")); // Wait for the sync to complete. waitonsyncs(TIMEOUT, &c); // Is the cloud as we expect? ASSERT_TRUE(c.confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, RootHasFilesystemWatch) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Set up sync const auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Trigger some filesystem notifications. Model model; model.addfolder("d0"); model.addfile("f0"); model.generate(c->syncSet(id).localpath); c->triggerPeriodicScanEarly(id); // Wait for sync to complete. waitonsyncs(TIMEOUT, c); // Confirm models. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } struct TwoWaySyncSymmetryCase { enum SyncType { type_twoWay, type_backupSync, type_numTypes }; enum Action { action_rename, action_moveWithinSync, action_moveOutOfSync, action_moveIntoSync, action_delete, action_numactions }; enum MatchState { match_exact, // the sync destination has the exact same file/folder at the same relative path match_older, // the sync destination has an older file/folder at the same relative path match_newer, // the sync destination has a newer file/folder at the same relative path match_absent }; // the sync destination has no node at the same relative path SyncType syncType = type_twoWay; Action action = action_rename; bool selfChange = false; // changed by our own client or another bool up = false; // or down - sync direction bool file = false; // or folder. Which one this test changes bool isExternal = false; bool pauseDuringAction = false; Model localModel; Model remoteModel; handle backupId = UNDEF; bool printTreesBeforeAndAfter = false; struct State { StandardClient& steadyClient; StandardClient& resumeClient; StandardClient& nonsyncClient; fs::path localBaseFolderSteady; fs::path localBaseFolderResume; std::string remoteBaseFolder = "twoway"; // leave out initial / so we can drill down from root node std::string first_test_name; fs::path first_test_initiallocalfolders; State(StandardClient& ssc, StandardClient& rsc, StandardClient& sc2) : steadyClient(ssc), resumeClient(rsc), nonsyncClient(sc2) {} }; State& state; TwoWaySyncSymmetryCase(State& wholestate) : state(wholestate) {} std::string typeName() { switch (syncType) { case type_twoWay: return "twoWay_"; case type_backupSync: return isExternal ? "external_backup_" : "internal_backup_"; default: assert(false); return ""; } } std::string actionName() { switch (action) { case action_rename: return "rename"; case action_moveWithinSync: return "move"; case action_moveOutOfSync: return "moveOut"; case action_moveIntoSync: return "moveIn"; case action_delete: return "delete"; default: assert(false); return ""; } } std::string matchName(MatchState m) { switch (m) { case match_exact: return "exact"; case match_older: return "older"; case match_newer: return "newer"; case match_absent: return "absent"; } return "bad enum"; } std::string name() { return typeName() + actionName() + (up?"_up" : "_down") + (selfChange?"_self":"_other") + (file?"_file":"_folder") + (pauseDuringAction?"_resumed":"_steady"); } fs::path localTestBasePathSteady; fs::path localTestBasePathResume; std::string remoteTestBasePath; Model& sourceModel() { return up ? localModel : remoteModel; } Model& destinationModel() { return up ? remoteModel : localModel; } StandardClient& client1() { return pauseDuringAction ? state.resumeClient : state.steadyClient; } StandardClient& changeClient() { return selfChange ? client1() : state.nonsyncClient; } fs::path localTestBasePath() { return pauseDuringAction ? localTestBasePathResume : localTestBasePathSteady; } bool CopyLocalTree(const fs::path& destination, const fs::path& source) try { using PathPair = std::pair; // Assume we've already copied if the destination exists. if (fs::exists(destination)) return true; std::list pending; pending.emplace_back(destination, source); for (; !pending.empty(); pending.pop_front()) { const auto& dst = pending.front().first; const auto& src = pending.front().second; // Assume target directory doesn't exist. fs::create_directories(dst); // Iterate over source directory's children. auto i = fs::directory_iterator(src); auto j = fs::directory_iterator(); for ( ; i != j; ++i) { auto from = i->path(); auto to = dst / from.filename(); // If it's a file, copy it and preserve its modification time. if (fs::is_regular_file(from)) { // Copy the file. fs::copy_file(from, to); // Preserve modification time. fs::last_write_time(to, fs::last_write_time(from)); // Process next child. continue; } // If it's not a file, it must be a directory. assert(fs::is_directory(from)); // So, create it! fs::create_directories(to); // And copy its content. pending.emplace_back(to, from); } } return true; } catch (...) { return false; } void makeMtimeFile(std::string name, int mtime_delta, Model& m1, Model& m2) { createNameFile(state.first_test_initiallocalfolders, name); auto initial_mtime = fs::last_write_time(state.first_test_initiallocalfolders / name); fs::last_write_time(localTestBasePath() / name, initial_mtime + std::chrono::seconds(mtime_delta)); fs::rename(state.first_test_initiallocalfolders / name, state.first_test_initiallocalfolders / "f" / name); // move it after setting the time to be 100% sure the sync sees it with the adjusted mtime only m1.findnode("f")->addkid(m1.makeModelSubfile(name)); m2.findnode("f")->addkid(m2.makeModelSubfile(name)); } PromiseBoolSP cloudCopySetupPromise = makeSharedPromise(); // prepares a local folder for testing, which will be two-way synced before the test void SetupForSync() { // Prepare Cloud { remoteTestBasePath = state.remoteBaseFolder + "/" + name(); auto& client = changeClient(); auto root = client.gettestbasenode(); ASSERT_NE(root, nullptr); root = client.drillchildnodebyname(root, state.remoteBaseFolder); ASSERT_NE(root, nullptr); auto from = client.drillchildnodebyname(root, "initial"); ASSERT_NE(from, nullptr); ASSERT_TRUE(client.copy(from.get(), root.get(), name())); } // Prepare Local Filesystem { localTestBasePathSteady = state.localBaseFolderSteady / name(); localTestBasePathResume = state.localBaseFolderResume / name(); auto from = state.nonsyncClient.fsBasePath / "twoway" / "initial"; ASSERT_TRUE(CopyLocalTree(localTestBasePathResume, from)); ASSERT_TRUE(CopyLocalTree(localTestBasePathSteady, from)); ASSERT_TRUE(CopyLocalTree(state.localBaseFolderResume / "initial", from)); ASSERT_TRUE(CopyLocalTree(state.localBaseFolderSteady / "initial", from)); } // Prepare models. { localModel.root->addkid(localModel.buildModelSubdirs("f", 2, 2, 2)); localModel.root->addkid(localModel.buildModelSubdirs("outside", 2, 1, 1)); localModel.addfile("f/.megaignore", "+sync:.megaignore"); localModel.addfile("f/file_older_1", "file_older_1"); localModel.addfile("f/file_older_2", "file_older_2"); localModel.addfile("f/file_newer_1", "file_newer_1"); localModel.addfile("f/file_newer_2", "file_newer_2"); remoteModel = localModel; } } bool isBackup() const { return syncType == type_backupSync; } bool isExternalBackup() const { return isExternal && isBackup(); } bool isInternalBackup() const { return !isExternal && isBackup(); } bool shouldRecreateOnResume() const { if (pauseDuringAction) { return isExternalBackup(); } return false; } bool shouldDisableSync() const { if (up) { return false; } if (pauseDuringAction) { return isInternalBackup(); } return isBackup(); } bool shouldUpdateDestination() const { return up || !isBackup(); } bool shouldUpdateModel() const { return up || !pauseDuringAction || !isExternalBackup(); } fs::path localSyncRootPath() { return localTestBasePath() / "f"; } string remoteSyncRootPath() { return remoteTestBasePath + "/f"; } std::shared_ptr remoteSyncRoot() { std::shared_ptr root = client1().client.nodebyhandle(client1().basefolderhandle); std::string remoteRootPath = remoteSyncRootPath(); if (!root) { LOG_err << name() << " root is NULL, local sync root:" << path_u8string(localSyncRootPath()) << " remote sync root:" << remoteRootPath; return nullptr; } std::shared_ptr n = client1().drillchildnodebyname(root, remoteRootPath); if (!n) { LOG_err << "remote sync root is NULL, local sync root:" << root->displaypath() << " remote sync root:" << remoteRootPath; } return n; } void SetupTwoWaySync() { ASSERT_NE(remoteSyncRoot(), nullptr); string basePath = path_u8string(client1().fsBasePath); string drivePath = string(1, '\0'); string sourcePath = path_u8string(localSyncRootPath()); string targetPath = remoteSyncRootPath(); if (isExternalBackup()) { drivePath = path_u8string(localTestBasePath()); drivePath.erase(0, basePath.size() + 1); } sourcePath.erase(0, basePath.size() + 1); SyncOptions options; options.drivePath = drivePath; options.isBackup = isBackup(); options.logName = sourcePath + "/" + name() + " "; options.uploadIgnoreFile = false; backupId = client1().setupSync_mainthread(sourcePath, targetPath, options); ASSERT_NE(backupId, UNDEF); } void PauseTwoWaySync() { if (shouldRecreateOnResume()) { client1().delSync_mainthread(backupId); } } void ResumeTwoWaySync() { if (shouldRecreateOnResume()) { ASSERT_NO_FATAL_FAILURE(SetupTwoWaySync()); } } void remote_rename(std::string nodepath, std::string newname, bool updatemodel, bool reportaction, bool deleteTargetFirst) { std::lock_guard g(changeClient().clientMutex); if (deleteTargetFirst) remote_delete(parentpath(nodepath) + "/" + newname, updatemodel, reportaction, true); // in case the target already exists if (updatemodel) remoteModel.emulate_rename(nodepath, newname); std::shared_ptr testRoot = changeClient().client.nodebyhandle(client1().basefolderhandle); std::shared_ptr n = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); ASSERT_TRUE(!!n); if (reportaction) out() << name() << " action: remote rename " << n->displaypath() << " to " << newname; attr_map updates('n', newname); auto e = changeClient().client.setattr(n, std::move(updates), nullptr, false); ASSERT_EQ(API_OK, error(e)); } void remote_move(std::string nodepath, std::string newparentpath, bool updatemodel, bool reportaction, bool deleteTargetFirst) { std::lock_guard g(changeClient().clientMutex); if (deleteTargetFirst) remote_delete(newparentpath + "/" + leafname(nodepath), updatemodel, reportaction, true); // in case the target already exists if (updatemodel) remoteModel.emulate_move(nodepath, newparentpath); std::shared_ptr testRoot = changeClient().client.nodebyhandle(changeClient().basefolderhandle); std::shared_ptr n1 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); std::shared_ptr n2 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + newparentpath); ASSERT_TRUE(!!n1); ASSERT_TRUE(!!n2); if (reportaction) out() << name() << " action: remote move " << n1->displaypath() << " to " << n2->displaypath(); auto e = changeClient().client.rename(n1, n2, SYNCDEL_NONE, NodeHandle(), nullptr, false, nullptr); ASSERT_EQ(API_OK, e); } void remote_copy(std::string nodepath, std::string newparentpath, bool updatemodel, bool reportaction) { std::lock_guard g(changeClient().clientMutex); if (updatemodel) remoteModel.emulate_copy(nodepath, newparentpath); std::shared_ptr testRoot = changeClient().client.nodebyhandle(changeClient().basefolderhandle); std::shared_ptr n1 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); std::shared_ptr n2 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + newparentpath); ASSERT_TRUE(!!n1); ASSERT_TRUE(!!n2); if (reportaction) out() << name() << " action: remote copy " << n1->displaypath() << " to " << n2->displaypath(); TreeProcCopy tc; changeClient().client.proctree(n1, &tc, false, true); tc.allocnodes(); changeClient().client.proctree(n1, &tc, false, true); tc.nn[0].parenthandle = UNDEF; SymmCipher key; AttrMap attrs; string attrstring; key.setkey((const ::mega::byte*)tc.nn[0].nodekey.data(), n1->type); attrs = n1->attrs; attrs.getjson(&attrstring); client1().client.makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); changeClient().client.putnodes(n2->nodeHandle(), NoVersioning, std::move(tc.nn), nullptr, ++next_request_tag, false); } void remote_renamed_copy(std::string nodepath, std::string newparentpath, string newname, bool updatemodel, bool reportaction) { std::lock_guard g(changeClient().clientMutex); if (updatemodel) { remoteModel.emulate_rename_copy(nodepath, newparentpath, newname); } std::shared_ptr testRoot = changeClient().client.nodebyhandle(changeClient().basefolderhandle); std::shared_ptr n1 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); std::shared_ptr n2 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + newparentpath); ASSERT_TRUE(!!n1); ASSERT_TRUE(!!n2); if (reportaction) out() << name() << " action: remote rename + copy " << n1->displaypath() << " to " << n2->displaypath() << " as " << newname; TreeProcCopy tc; changeClient().client.proctree(n1, &tc, false, true); tc.allocnodes(); changeClient().client.proctree(n1, &tc, false, true); tc.nn[0].parenthandle = UNDEF; SymmCipher key; AttrMap attrs; string attrstring; key.setkey((const ::mega::byte*)tc.nn[0].nodekey.data(), n1->type); attrs = n1->attrs; LocalPath::utf8_normalize(&newname); attrs.map['n'] = newname; attrs.getjson(&attrstring); client1().client.makeattr(&key, tc.nn[0].attrstring, attrstring.c_str()); changeClient().client.putnodes(n2->nodeHandle(), NoVersioning, std::move(tc.nn), nullptr, ++next_request_tag, false); } void remote_renamed_move(std::string nodepath, std::string newparentpath, string newname, bool updatemodel, bool reportaction) { std::lock_guard g(changeClient().clientMutex); if (updatemodel) { remoteModel.emulate_rename_copy(nodepath, newparentpath, newname); } std::shared_ptr testRoot = changeClient().client.nodebyhandle(changeClient().basefolderhandle); std::shared_ptr n1 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); std::shared_ptr n2 = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + newparentpath); ASSERT_TRUE(!!n1); ASSERT_TRUE(!!n2); if (reportaction) out() << name() << " action: remote rename + move " << n1->displaypath() << " to " << n2->displaypath() << " as " << newname; error e = changeClient().client.rename(n1, n2, SYNCDEL_NONE, NodeHandle(), newname.c_str(), false, nullptr); EXPECT_EQ(e, API_OK); } void remote_delete(std::string nodepath, bool updatemodel, bool reportaction, bool mightNotExist) { std::lock_guard g(changeClient().clientMutex); std::shared_ptr testRoot = changeClient().client.nodebyhandle(changeClient().basefolderhandle); std::shared_ptr n = changeClient().drillchildnodebyname(testRoot, remoteTestBasePath + "/" + nodepath); if (mightNotExist && !n) return; // eg when checking to remove an item that is a move target but there isn't one ASSERT_TRUE(!!n); if (reportaction) out() << name() << " action: remote delete " << n->displaypath(); if (updatemodel) remoteModel.emulate_delete(nodepath); auto e = changeClient().client.unlink(n.get(), false, ++next_request_tag, false); ASSERT_TRUE(!e); } fs::path fixSeparators(std::string p) { for (auto& c : p) if (c == '/') c = fs::path::preferred_separator; return u8path_compat(p); } void local_rename(std::string path, std::string newname, bool updatemodel, bool reportaction, bool deleteTargetFirst) { if (deleteTargetFirst) local_delete(parentpath(path) + "/" + newname, updatemodel, reportaction, true); // in case the target already exists if (updatemodel) localModel.emulate_rename(path, newname); fs::path p1(localTestBasePath()); p1 /= fixSeparators(path); fs::path p2 = p1.parent_path() / newname; if (reportaction) out() << name() << " action: local rename " << p1 << " to " << p2; std::error_code ec; for (int i = 0; i < 5; ++i) { fs::rename(p1, p2, ec); if (!ec) break; WaitMillisec(100); } if (!pauseDuringAction) client1().triggerPeriodicScanEarly(backupId); ASSERT_TRUE(!ec) << "local_rename " << p1 << " to " << p2 << " failed: " << ec.message(); } void local_move(std::string from, std::string to, bool updatemodel, bool reportaction, bool deleteTargetFirst) { if (deleteTargetFirst) local_delete(to + "/" + leafname(from), updatemodel, reportaction, true); if (updatemodel) localModel.emulate_move(from, to); fs::path p1(localTestBasePath()); fs::path p2(localTestBasePath()); p1 /= fixSeparators(from); p2 /= fixSeparators(to); p2 /= p1.filename(); // non-existing file in existing directory case if (reportaction) out() << name() << " action: local move " << p1 << " to " << p2; std::error_code ec; fs::rename(p1, p2, ec); if (ec) { fs::remove_all(p2, ec); fs::rename(p1, p2, ec); } if (!pauseDuringAction) client1().triggerPeriodicScanEarly(backupId); ASSERT_TRUE(!ec) << "local_move " << p1 << " to " << p2 << " failed: " << ec.message(); } void local_copy(std::string from, std::string to, bool updatemodel, bool reportaction) { if (updatemodel) localModel.emulate_copy(from, to); fs::path p1(localTestBasePath()); fs::path p2(localTestBasePath()); p1 /= fixSeparators(from); p2 /= fixSeparators(to); if (reportaction) out() << name() << " action: local copy " << p1 << " to " << p2; std::error_code ec; fs::copy(p1, p2, ec); if (!pauseDuringAction) client1().triggerPeriodicScanEarly(backupId); ASSERT_TRUE(!ec) << "local_copy " << p1 << " to " << p2 << " failed: " << ec.message(); } void local_delete(std::string path, bool updatemodel, bool reportaction, bool mightNotExist) { fs::path p(localTestBasePath()); p /= fixSeparators(path); if (mightNotExist && !fs::exists(p)) return; if (reportaction) out() << name() << " action: local_delete " << p; std::error_code ec; fs::remove_all(p, ec); if (!pauseDuringAction) client1().triggerPeriodicScanEarly(backupId); ASSERT_TRUE(!ec) << "local_delete " << p << " failed: " << ec.message(); if (updatemodel) localModel.emulate_delete(path); } void source_rename(std::string nodepath, std::string newname, bool updatemodel, bool reportaction, bool deleteTargetFirst) { if (up) local_rename(nodepath, newname, updatemodel, reportaction, deleteTargetFirst); else remote_rename(nodepath, newname, updatemodel, reportaction, deleteTargetFirst); } void source_move(std::string nodepath, std::string newparentpath, bool updatemodel, bool reportaction, bool deleteTargetFirst) { if (up) local_move(nodepath, newparentpath, updatemodel, reportaction, deleteTargetFirst); else remote_move(nodepath, newparentpath, updatemodel, reportaction, deleteTargetFirst); } void source_copy(std::string nodepath, std::string newparentpath, bool updatemodel, bool reportaction) { if (up) local_copy(nodepath, newparentpath, updatemodel, reportaction); else remote_copy(nodepath, newparentpath, updatemodel, reportaction); } void source_delete(std::string nodepath, bool updatemodel, bool reportaction = false) { if (up) local_delete(nodepath, updatemodel, reportaction, false); else remote_delete(nodepath, updatemodel, reportaction, false); } void fileMayDiffer(std::string filepath) { fs::path p(localTestBasePath()); p /= fixSeparators(filepath); client1().localFSFilesThatMayDiffer.insert(p); out() << "File may differ: " << p; } // Two-way sync has been started and is stable. Now perform the test action enum ModifyStage { Prepare, MainAction }; void PrintLocalTree(fs::path p) { out() << p; if (fs::is_directory(p)) { for (auto i = fs::directory_iterator(p); i != fs::directory_iterator(); ++i) { PrintLocalTree(*i); } } } void PrintLocalTree(const LocalNode& node) { out() << node.getLocalPath().toPath(false); if (node.type == FILENODE) return; for (const auto& childIt : node.children) { PrintLocalTree(*childIt.second); } } void PrintRemoteTree(Node* n, string prefix = "") { prefix += string("/") + n->displayname(); out() << prefix; if (n->type == FILENODE) return; for (auto& c : client1().client.getChildren(n)) { PrintRemoteTree(c.get(), prefix); } } void PrintModelTree(Model::ModelNode* n, string prefix = "") { prefix += string("/") + n->name; out() << prefix; if (n->type == Model::ModelNode::file) return; for (auto& c : n->kids) { PrintModelTree(c.get(), prefix); } } void Modify(ModifyStage stage) { bool prep = stage == Prepare; bool act = stage == MainAction; if (prep) out() << "Preparing action "; if (act) out() << "Executing action "; if (prep && printTreesBeforeAndAfter) { out() << " ---- local filesystem initial state ----"; PrintLocalTree(fs::path(localTestBasePath())); if (auto* sync = client1().syncByBackupId(backupId)) { out() << " ---- local node tree initial state ----"; PrintLocalTree(*sync->localroot); } out() << " ---- remote node tree initial state ----"; std::shared_ptr testRoot = client1().client.nodebyhandle(changeClient().basefolderhandle); if (std::shared_ptr n = client1().drillchildnodebyname(testRoot, remoteTestBasePath)) { PrintRemoteTree(n.get()); } } switch (action) { case action_rename: if (prep) { } else if (act) { if (file) { source_rename("f/f_0/file0_f_0", "file0_f_0_renamed", shouldUpdateModel(), true, true); if (shouldUpdateDestination()) { destinationModel().emulate_rename("f/f_0/file0_f_0", "file0_f_0_renamed"); } } else { source_rename("f/f_0", "f_0_renamed", shouldUpdateModel(), true, false); if (shouldUpdateDestination()) { destinationModel().emulate_rename("f/f_0", "f_0_renamed"); } } } break; case action_moveWithinSync: if (prep) { } else if (act) { if (file) { source_move("f/f_1/file0_f_1", "f/f_0", shouldUpdateModel(), true, false); if (shouldUpdateDestination()) { destinationModel().emulate_move("f/f_1/file0_f_1", "f/f_0"); } } else { source_move("f/f_1", "f/f_0", shouldUpdateModel(), true, false); if (shouldUpdateDestination()) { destinationModel().emulate_move("f/f_1", "f/f_0"); } } } break; case action_moveOutOfSync: if (prep) { } else if (act) { if (file) { source_move("f/f_0/file0_f_0", "outside", shouldUpdateModel(), false, false); if (shouldUpdateDestination()) { destinationModel().emulate_delete("f/f_0/file0_f_0"); } } else { source_move("f/f_0", "outside", shouldUpdateModel(), false, false); if (shouldUpdateDestination()) { destinationModel().emulate_delete("f/f_0"); } } } break; case action_moveIntoSync: if (prep) { } else if (act) { if (file) { source_move("outside/file0_outside", "f/f_0", shouldUpdateModel(), false, false); if (shouldUpdateDestination()) { destinationModel().emulate_copy("outside/file0_outside", "f/f_0"); } } else { source_move("outside", "f/f_0", shouldUpdateModel(), false, false); if (shouldUpdateDestination()) { destinationModel().emulate_delete("f/f_0/outside"); destinationModel().emulate_copy("outside", "f/f_0"); } } } break; case action_delete: if (prep) { } else if (act) { if (file) { source_delete("f/f_0/file0_f_0", shouldUpdateModel(), true); if (shouldUpdateDestination()) { destinationModel().emulate_delete("f/f_0/file0_f_0"); } } else { source_delete("f/f_0", shouldUpdateModel(), true); if (shouldUpdateDestination()) { destinationModel().emulate_delete("f/f_0"); } } } break; default: ASSERT_TRUE(false); } } void CheckSetup(State&, bool initial) { if (!initial && printTreesBeforeAndAfter) { out() << " ---- local filesystem before change ----"; PrintLocalTree(fs::path(localTestBasePath())); if (auto* sync = client1().syncByBackupId(backupId)) { out() << " ---- local node tree before change ----"; PrintLocalTree(*sync->localroot); } out() << " ---- remote node tree before change ----"; std::shared_ptr testRoot = client1().client.nodebyhandle(changeClient().basefolderhandle); if (std::shared_ptr n = client1().drillchildnodebyname(testRoot, remoteTestBasePath)) { PrintRemoteTree(n.get()); } } if (!initial) out() << "Checking setup state (should be no changes in twoway sync source): "<< name(); // confirm source is unchanged after setup (Two-way is not sending changes to the wrong side) bool localfs = client1().confirmModel(backupId, localModel.findnode("f"), StandardClient::CONFIRM_LOCALFS, true, false, false); // todo: later enable debris checks bool localnode = client1().confirmModel(backupId, localModel.findnode("f"), StandardClient::CONFIRM_LOCALNODE, true, false, false); // todo: later enable debris checks bool remote = client1().confirmModel(backupId, remoteModel.findnode("f"), StandardClient::CONFIRM_REMOTE, true, false, false); // todo: later enable debris checks EXPECT_EQ(localfs, localnode); EXPECT_EQ(localnode, remote); EXPECT_TRUE(localfs && localnode && remote) << " failed in " << name(); } void WaitSetup() { const std::chrono::seconds maxWaitSeconds(120); const std::chrono::milliseconds checkInterval(5000); auto remoteIsReady = [this](StandardClient& client){ int descendents1 = 0, descendents2 = 0; bool reported1 = false, reported2 = false; return client.recursiveConfirm(remoteModel.findnode("f"), client.drillchildnodebyname(client.gettestbasenode(), remoteTestBasePath + "/f").get(), descendents1, name(), 0, reported1, true, false) && client.recursiveConfirm(remoteModel.findnode("outside"), client.drillchildnodebyname(client.gettestbasenode(), remoteTestBasePath + "/outside").get(), descendents2, name(), 0, reported2, true, false); }; EXPECT_TRUE(state.resumeClient.waitFor(remoteIsReady, maxWaitSeconds, checkInterval)); EXPECT_TRUE(state.steadyClient.waitFor(remoteIsReady, maxWaitSeconds, checkInterval)); EXPECT_TRUE(state.nonsyncClient.waitFor(remoteIsReady, maxWaitSeconds, checkInterval)); } // Two-way sync is stable again after the change. Check the results. bool finalResult = false; void CheckResult(State&) { Sync* sync = client1().syncByBackupId(backupId); std::function printTrees = [&]() { out() << " ---- local filesystem after sync of change ----"; PrintLocalTree(fs::path(localTestBasePath())); if (sync && sync->localroot) { out() << " ---- local node tree after sync of change ----"; PrintLocalTree(*sync->localroot); } out() << " ---- remote node tree after sync of change ----"; std::shared_ptr testRoot = client1().client.nodebyhandle(changeClient().basefolderhandle); if (std::shared_ptr n = client1().drillchildnodebyname(testRoot, remoteTestBasePath)) { PrintRemoteTree(n.get()); } out() << " ---- expected sync destination (model) ----"; auto n = destinationModel().findnode("f"); if (n) PrintModelTree(n); }; if (printTreesBeforeAndAfter) { printTrees(); // Don't saturate the logs if we're already displaying the trees. printTrees = nullptr; } out() << "Checking twoway sync "<< name(); auto confirmedOk = true; if (shouldDisableSync()) { bool lfs = client1().confirmModel(backupId, localModel.findnode("f"), localSyncRootPath(), true, false, false); bool rnt = client1().confirmModel(backupId, remoteModel.findnode("f"), remoteSyncRoot().get(), false, false); EXPECT_EQ(sync, nullptr) << "Sync isn't disabled: " << name(); EXPECT_TRUE(lfs) << "Couldn't confirm LFS: " << name(); EXPECT_TRUE(rnt) << "Couldn't confirm RNT: " << name(); confirmedOk &= lfs && rnt; finalResult = sync == nullptr; finalResult &= confirmedOk; } else { EXPECT_NE(sync, (Sync*)nullptr); bool localfs = client1().confirmModel(backupId, localModel.findnode("f"), StandardClient::CONFIRM_LOCALFS, true, false, false); // todo: later enable debris checks bool localnode = client1().confirmModel(backupId, localModel.findnode("f"), StandardClient::CONFIRM_LOCALNODE, true, false, false); // todo: later enable debris checks bool remote = client1().confirmModel(backupId, remoteModel.findnode("f"), StandardClient::CONFIRM_REMOTE, true, false, false); // todo: later enable debris checks EXPECT_EQ(localfs, localnode); EXPECT_EQ(localnode, remote); EXPECT_TRUE(localfs && localnode && remote) << " failed in " << name(); confirmedOk &= localfs && localnode && remote; finalResult = sync && confirmedOk; } // Show the trees if there's been a mismatch. if (printTreesBeforeAndAfter && printTrees && !confirmedOk) { printTrees(); } } }; bool CatchupClients(StandardClient* c1, StandardClient* c2, StandardClient* c3) { out() << "Catching up"; auto pb1 = makeSharedPromise(); auto pb2 = makeSharedPromise(); auto pb3 = makeSharedPromise(); if (c1) c1->catchup(pb1); if (c2) c2->catchup(pb2); if (c3) c3->catchup(pb3); auto f1 = pb1->get_future(); auto f2 = pb2->get_future(); auto f3 = pb3->get_future(); if (c1 && f1.wait_for(DEFAULTWAIT) == future_status::timeout) return false; if (c2 && f2.wait_for(DEFAULTWAIT) == future_status::timeout) return false; if (c3 && f3.wait_for(DEFAULTWAIT) == future_status::timeout) return false; EXPECT_TRUE((!c1 || f1.get()) && (!c2 || f2.get()) && (!c3 || f3.get())); out() << "Caught up"; return true; } void PrepareForSync(StandardClient& client) { auto local = client.fsBasePath / "twoway" / "initial"; fs::create_directories(local); ASSERT_TRUE(buildLocalFolders(local, "f", 2, 2, 2)); ASSERT_TRUE(buildLocalFolders(local, "outside", 2, 1, 1)); constexpr auto delta = std::chrono::seconds(3600); // Initial ignore file. auto ignoreFilePath = local / "f" / ".megaignore"; ASSERT_TRUE(createFile(ignoreFilePath, "+sync:.megaignore")); ASSERT_TRUE(createFile(local / "f" / ".megaignore", "+sync:.megaignore")); ASSERT_TRUE(createFile(local / "f" / "file_older_1", "file_older_1", -delta)); ASSERT_TRUE(createFile(local / "f" / "file_older_2", "file_older_2", -delta)); ASSERT_TRUE(createFile(local / "f" / "file_newer_1", "file_newer_1", delta)); ASSERT_TRUE(createFile(local / "f" / "file_newer_2", "file_newer_2", delta)); auto remote = client.drillchildnodebyname(client.gettestbasenode(), "twoway"); ASSERT_NE(remote, nullptr); // Upload initial ignore file. ASSERT_TRUE(client.uploadFile(ignoreFilePath, remote.get())); // Upload initial sync contents. ASSERT_TRUE(client.uploadFolderTree(local, remote.get())); ASSERT_TRUE(client.uploadFilesInTree(local, remote.get())); } bool WaitForRemoteMatch(map& testcases, chrono::seconds timeout) { auto check = [&]() { auto i = testcases.begin(); auto j = testcases.end(); for ( ; i != j; ++i) { auto& testcase = i->second; if (testcase.pauseDuringAction) continue; auto& client = testcase.client1(); auto& id = testcase.backupId; auto& model = testcase.remoteModel; if (!client.match(id, model.findnode("f"))) { out() << "Cloud/model misatch: " << client.client.clientname << ": " << testcase.name(); return false; } } return true; }; auto total = std::chrono::milliseconds(0); constexpr auto sleepIncrement = std::chrono::milliseconds(500); do { if (check()) { out() << "Cloud/model matched."; return true; } out() << "Waiting for cloud/model match..."; std::this_thread::sleep_for(sleepIncrement); total += sleepIncrement; } while (total < timeout); if (!check()) { out() << "Timed out waiting for cloud/model match."; return false; } return true; } TEST_F(SyncTest, TwoWay_Highlevel_Symmetries) { // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder fs::path localtestroot = makeNewTestRoot(); StandardClient clientA2(localtestroot, "clientA2"); ASSERT_TRUE(clientA2.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "twoway", 0, 0, true)); PrepareForSync(clientA2); StandardClient clientA1Steady(localtestroot, "clientA1S"); StandardClient clientA1Resume(localtestroot, "clientA1R"); ASSERT_TRUE(clientA1Steady.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD", false, true)); ASSERT_TRUE(clientA1Resume.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD", false, true)); fs::create_directory(clientA1Steady.fsBasePath / u8path_compat("twoway")); fs::create_directory(clientA1Resume.fsBasePath / u8path_compat("twoway")); fs::create_directory(clientA2.fsBasePath / u8path_compat("twoway")); TwoWaySyncSymmetryCase::State allstate(clientA1Steady, clientA1Resume, clientA2); allstate.localBaseFolderSteady = clientA1Steady.fsBasePath / u8path_compat("twoway"); allstate.localBaseFolderResume = clientA1Resume.fsBasePath / u8path_compat("twoway"); std::map cases; static set tests = { // investigating why this one fails sometimes in jenkins MR jobs //"internal_backup_delete_down_self_file_steady" //"internal_backup_rename_up_self_folder_steady", //"twoWay_move_up_other_file_steady", //"twoWay_move_up_other_folder_steady", //"twoWay_moveIn_up_other_file_steady" }; // tests for (int syncType = TwoWaySyncSymmetryCase::type_numTypes; syncType--; ) { //if (syncType != TwoWaySyncSymmetryCase::type_backupSync) continue; for (int selfChange = 0; selfChange < 2; ++selfChange) { //if (!selfChange) continue; for (int up = 0; up < 2; ++up) { // if (!up) continue; // We don't allow changes in the cloud for a backup sync if (up == 0 && syncType == TwoWaySyncSymmetryCase::type_backupSync) continue; for (int action = 0; action < (int)TwoWaySyncSymmetryCase::action_numactions; ++action) { //if (action != TwoWaySyncSymmetryCase::action_moveIntoSync) continue; //if (action != TwoWaySyncSymmetryCase::action_rename) continue; //if (action != TwoWaySyncSymmetryCase::action_delete) continue; for (int file = 0; file < 2; ++file) { //if (!file) continue; for (int isExternal = 0; isExternal < 2; ++isExternal) { if (isExternal && syncType != TwoWaySyncSymmetryCase::type_backupSync) { continue; } for (int pauseDuringAction = 0; pauseDuringAction < 2; ++pauseDuringAction) { //if (pauseDuringAction) continue; // we can't make changes if the client is not running if (pauseDuringAction && selfChange) continue; TwoWaySyncSymmetryCase testcase(allstate); testcase.syncType = TwoWaySyncSymmetryCase::SyncType(syncType); testcase.selfChange = selfChange != 0; testcase.up = up != 0; testcase.action = TwoWaySyncSymmetryCase::Action(action); testcase.file = file != 0; testcase.isExternal = isExternal != 0; testcase.pauseDuringAction = pauseDuringAction != 0; testcase.printTreesBeforeAndAfter = !tests.empty(); if (tests.empty() || tests.count(testcase.name()) > 0) { auto name = testcase.name(); cases.emplace(name, std::move(testcase)); } } } } } } } } out() << "Creating initial local files/folders for " << cases.size() << " sync test cases"; for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.SetupForSync()); } out() << "Waiting intial state to be ready and clients are updated with actionpackets"; for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.WaitSetup()); } out() << "Setting up each sub-test's Two-way sync of 'f'"; for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.SetupTwoWaySync()); } out() << "Letting all " << cases.size() << " Two-way syncs run"; waitonsyncs(DEFAULTWAIT, &clientA1Steady, &clientA1Resume); CatchupClients(&clientA1Steady, &clientA1Resume, &clientA2); waitonsyncs(DEFAULTWAIT, &clientA1Steady, &clientA1Resume); out() << "Checking intial state"; for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.CheckSetup(allstate, true)); } // make changes in destination to set up test for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.Modify(TwoWaySyncSymmetryCase::Prepare)); } ASSERT_NO_FATAL_FAILURE(CatchupClients(&clientA1Steady, &clientA1Resume, &clientA2)); out() << "Letting all " << cases.size() << " Two-way syncs run"; waitonsyncs(std::chrono::seconds(15), &clientA1Steady, &clientA1Resume, &clientA2); out() << "Checking Two-way source is unchanged"; for (auto& testcase : cases) { ASSERT_NO_FATAL_FAILURE(testcase.second.CheckSetup(allstate, false)); } int paused = 0; for (auto& testcase : cases) { if (testcase.second.pauseDuringAction) { testcase.second.PauseTwoWaySync(); ++paused; } } // save session and local log out A1R to set up for resume string session; clientA1Resume.client.dumpsession(session); clientA1Resume.localLogout(); auto remainingResumeSyncs = clientA1Resume.client.syncs.getConfigs(false); ASSERT_EQ(0u, remainingResumeSyncs.size()); if (paused) { out() << "Paused " << paused << " Two-way syncs"; WaitMillisec(1000); } clientA1Steady.logcb = clientA1Resume.logcb = clientA2.logcb = true; out() << "Performing action "; for (auto& testcase : cases) { testcase.second.Modify(TwoWaySyncSymmetryCase::MainAction); } waitonsyncs(std::chrono::seconds(15), &clientA1Steady, &clientA2); // leave out clientA1Resume as it's 'paused' (locallogout'd) for now ASSERT_NO_FATAL_FAILURE(CatchupClients(&clientA1Steady, &clientA2)); waitonsyncs(std::chrono::seconds(15), &clientA1Steady, &clientA2); // leave out clientA1Resume as it's 'paused' (locallogout'd) for now // resume A1R session (with sync), see if A2 nodes and localnodes get in sync again clientA1Resume.received_syncs_restored = false; ASSERT_TRUE(clientA1Resume.login_fetchnodesFromSession(session)); ASSERT_EQ(clientA1Resume.basefolderhandle, clientA2.basefolderhandle); // wait for normal sync resumes to complete clientA1Resume.waitFor([&](StandardClient& sc) { return sc.received_syncs_restored.load(); }, std::chrono::seconds(30)); // now resume remainder - some are external syncs int resumed = 0; for (auto& testcase : cases) { if (testcase.second.pauseDuringAction) { ASSERT_NO_FATAL_FAILURE(testcase.second.ResumeTwoWaySync()); ++resumed; } } if (resumed) { out() << "Resumed " << resumed << " Two-way syncs"; WaitMillisec(3000); } out() << "Waiting for remote changes to make it to clients..."; EXPECT_TRUE(WaitForRemoteMatch(cases, chrono::seconds(64))); // 64 because the jenkins machines can be slow out() << "Letting all " << cases.size() << " Two-way syncs run"; waitonsyncs(std::chrono::seconds(15), &clientA1Steady, &clientA1Resume, &clientA2); ASSERT_NO_FATAL_FAILURE(CatchupClients(&clientA1Steady, &clientA1Resume, &clientA2)); waitonsyncs(std::chrono::seconds(15), &clientA1Steady, &clientA1Resume, &clientA2); out() << "Checking local and remote state in each sub-test"; for (auto& testcase : cases) { testcase.second.CheckResult(allstate); } int succeeded = 0, failed = 0; for (auto& testcase : cases) { if (testcase.second.finalResult) ++succeeded; else { out() << "failed: " << testcase.second.name(); ++failed; } } out() << "Succeeded: " << succeeded << " Failed: " << failed; // Clear tree-state cache. { StandardClient cC(localtestroot, "cC"); ASSERT_TRUE(cC.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD", false, true)); } } TEST_F(SyncTest, MoveExistingIntoNewDirectoryWhilePaused) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = chrono::seconds(4); Model model; fs::path root; string session; handle id; // Initial setup. { StandardClient c(TESTROOT, "c"); // don't use clientManager as we re-use the client "c" for its directory // Log in client. ASSERT_TRUE(c.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Add and start sync. id = c.setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Squirrel away for later use. root = path_u8string(c.syncSet(id).localpath); // Populate filesystem. model.addfolder("a"); model.addfolder("c"); model.generate(root); c.triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, &c); // Make sure everything arrived safely. ASSERT_TRUE(c.confirmModel_mainthread(model.root.get(), id)); // Save the session so we can resume later. c.client.dumpsession(session); // Log out client, taking care to keep caches. c.localLogout(); } StandardClient c(TESTROOT, "c"); // Add a new hierarchy to be scanned. model.addfolder("b"); model.generate(root); // Move c under b. fs::rename(root / "c", root / "b" / "c"); // Update the model. model.movenode("c", "b"); // Hook onAutoResumeResult callback. promise notify; c.mOnSyncStateConfig = [¬ify](const SyncConfig& config) { if (config.mRunState == SyncRunState::Run) notify.set_value(); }; // Log in client resuming prior session. ASSERT_TRUE(c.login_fetchnodesFromSession(session)); // Wait for the sync to be resumed. ASSERT_TRUE(debugTolerantWaitOnFuture(notify.get_future(), 45)); // Wait for the sync to catch up. waitonsyncs(TIMEOUT, &c); // Were the changes propagated? ASSERT_TRUE(c.confirmModel_mainthread(model.root.get(), id)); } class BackupClient : public StandardClient { public: BackupClient(const fs::path& basePath, const string& name) : StandardClient(basePath, name) , mOnFileAdded() { } void file_added(File* file) override { StandardClient::file_added(file); if (mOnFileAdded) mOnFileAdded(*file); } using FileAddedCallback = std::function; FileAddedCallback mOnFileAdded; }; // Client TEST_F(SyncTest, MonitoringExternalBackupRestoresInMirroringMode) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(8); // Model. Model m; // Sync Root Handle. NodeHandle rootHandle; // Session ID. string sessionID; // Sync Backup ID. handle id; { StandardClient cb(TESTROOT, "cb"); // can not use ClientManager as both these clients must refer to their filesystem as "cb" // even though there are two of them // Log callbacks. cb.logcb = true; // Log in client. ASSERT_TRUE(cb.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Create some files to synchronize. m.addfile("d/f"); m.addfile("f"); m.addfile(".megaignore", "+sync:.megaignore"); m.generate(cb.fsBasePath / "s"); // Add and start sync. id = cb.setupSync_mainthread("s", "s", true, true, ""); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitonsyncs(TIMEOUT, &cb); // Give the engine some time to actually upload the files. ASSERT_TRUE(cb.waitFor(SyncRemoteMatch("s", m.root.get()), TIMEOUT)); // Make sure everything made it to the cloud. ASSERT_TRUE(cb.confirmModel_mainthread(m.root.get(), id)); // Get our hands on the sync's root handle. rootHandle = cb.syncSet(id).h; // Record this client's session. cb.client.dumpsession(sessionID); // Log out the client. cb.localLogout(); } StandardClient cb(TESTROOT, "cb"); cb.logcb = true; // Log in client. ASSERT_TRUE(cb.login_fetchnodesFromSession(sessionID)); // Make a change in the cloud. { vector node(1); cb.prepareOneFolder(&node[0], "g", false); ASSERT_TRUE(cb.putnodes(rootHandle, NoVersioning, std::move(node))); } // Restore the backup sync. ASSERT_TRUE(cb.backupOpenDrive(cb.fsBasePath)); // Re-enable the sync. ASSERT_TRUE(cb.enableSyncByBackupId(id, "cb")); // Wait for the mirror to complete. waitonsyncs(TIMEOUT, &cb); // Cloud should mirror the local disk. (ie, g should be gone in the cloud) ASSERT_TRUE(cb.confirmModel_mainthread(m.root.get(), id)); } TEST_F(SyncTest, MonitoringExternalBackupResumesInMirroringMode) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); StandardClientInUse cb = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. cb->logcb = true; // Log in client. ASSERT_TRUE(cb->resetBaseFolderMulticlient()); ASSERT_TRUE(cb->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cb)); // Create some files to be synchronized. Model m; m.addfile("d/f"); m.addfile("f"); m.generate(cb->fsBasePath / "s"); // Add and start sync. auto id = cb->setupSync_mainthread("s", "s", true, true, ""); ASSERT_NE(id, UNDEF); // Wait for the mirror to complete. waitonsyncs(TIMEOUT, cb); // Make sure everything arrived safe and sound. ASSERT_TRUE(cb->confirmModel_mainthread(m.root.get(), id)); // Disable the sync. ASSERT_TRUE(cb->disableSync(id, NO_SYNC_ERROR, true, true)); // Make sure the sync's config is as we expect. { auto config = cb->syncConfigByBackupID(id); // Disabled external backups are always considered "user-disabled." // That is, the user must consciously decide to resume these syncs. ASSERT_EQ(config.mEnabled, false); } // Make a change in the cloud. { vector node(1); cb->prepareOneFolder(&node[0], "g", false); auto rootHandle = cb->syncSet(id).h; ASSERT_TRUE(cb->putnodes(rootHandle, NoVersioning, std::move(node))); } // Re-enable the sync. ASSERT_TRUE(cb->enableSyncByBackupId(id, "")); // Wait for the mirror to complete. waitonsyncs(TIMEOUT, cb); // Cloud should mirror the disk. ASSERT_TRUE(cb->confirmModel_mainthread(m.root.get(), id)); } TEST_F(SyncTest, MirroringInternalBackupResumesInMirroringMode) { const auto TESTROOT = makeNewTestRoot(); const auto TIMEOUT = chrono::seconds(4); // Session ID. string sessionID; // Sync Backup ID. handle id{UNDEF}; // Sync Root Handle. NodeHandle rootHandle; // Model. Model m; StandardClientInUse cf = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. cf->logcb = true; // Log in client. ASSERT_TRUE(cf->resetBaseFolderMulticlient()); ASSERT_TRUE(cf->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cf)); // Check manual resume. { BackupClient cb(TESTROOT, "cb"); // can not use ClientManager as is a BackupClient and // "cb" is re-used later // Log callbacks. cb.logcb = true; // Log client in. ASSERT_TRUE(cb.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Set upload throttle. // // This is so that we can disable the sync before it transitions // to the monitoring state. cb.setUploadSpeed(1); // Give the sync something to backup. m.addfile("d/f", randomData(16384)); m.addfile("f", randomData(16384)); m.generate(cb.fsBasePath / "s"); // Disable the sync when it starts uploading a file. cb.mOnFileAdded = [&cb, &id, &TIMEOUT](File&) { // the upload has been set super slow so there's loads of time. // get the single sync SyncConfig config; const auto getBackupResult = cb.waitFor( [&id, &config](StandardClient& backupClient) { return id != UNDEF && backupClient.client.syncs.syncConfigByBackupId(id, config); }, TIMEOUT); ASSERT_TRUE(getBackupResult) << "BackupId: " << id << ". SyncVec is empty: " << (cb.client.syncs.mNumSyncsActive == 0); // Make sure the sync's in mirroring mode. ASSERT_EQ(config.mBackupId, id); ASSERT_EQ(config.mSyncType, SyncConfig::TYPE_BACKUP); // Disable the sync. cb.client.syncs.disableSyncs(NO_SYNC_ERROR, true, true); // Callback's done its job. cb.mOnFileAdded = nullptr; }; // Add and start sync. id = cb.setupSync_mainthread("s", "s", true, false); ASSERT_NE(id, UNDEF); // Let the sync mirror. waitonsyncs(TIMEOUT, &cb); // Make sure the sync's been disabled. ASSERT_FALSE(cb.syncByBackupId(id)); // Make sure it's still in mirror mode. { auto config = cb.syncConfigByBackupID(id); ASSERT_EQ(config.mEnabled, true); ASSERT_EQ(config.mError, UNLOADING_SYNC); } // Get our hands on sync root's cloud handle. rootHandle = cb.syncSet(id).h; ASSERT_TRUE(!rootHandle.isUndef()); // Make some changes to the cloud. vector node(1); cf->prepareOneFolder(&node[0], "g", false); ASSERT_TRUE(cf->putnodes(rootHandle, NoVersioning, std::move(node))); // Log out the client when we try and upload a file. std::promise waiter; cb.mOnFileAdded = [&cb, &waiter, &id](File&) { // get the single sync SyncConfig config; ASSERT_TRUE(cb.client.syncs.syncConfigByBackupId(id, config)); // Make sure we're mirroring. ASSERT_EQ(config.mBackupId, id); ASSERT_EQ(config.mSyncType, SyncConfig::TYPE_BACKUP); // Notify the waiter. waiter.set_value(); // Callback's done its job. cb.mOnFileAdded = nullptr; }; // Resume the backup. ASSERT_TRUE(cb.enableSyncByBackupId(id, "")); // Wait for the sync to try and upload a file. ASSERT_TRUE(debugTolerantWaitOnFuture(waiter.get_future(), 45)); // Save the session ID. cb.client.dumpsession(sessionID); // Log out the client. cb.localLogout(); } // Create a couple new nodes. { vector nodes(2); cf->prepareOneFolder(&nodes[0], "h0", false); cf->prepareOneFolder(&nodes[1], "h1", false); ASSERT_TRUE(cf->putnodes(rootHandle, NoVersioning, std::move(nodes))); } // Check automatic resume. StandardClient cb(TESTROOT, "cb"); // Log callbacks. cb.logcb = true; // So we can pause execution until al the syncs are restored. promise notifier; // Hook the OnSyncStateConfig callback. cb.mOnSyncStateConfig = [&](const SyncConfig& config) { // Let waiters know we've restored the sync. if (config.mRunState == SyncRunState::Run) notifier.set_value(); }; // Log in client, resuming prior session. ASSERT_TRUE(cb.login_fetchnodesFromSession(sessionID)); // Wait for the sync to be restored. ASSERT_NE(notifier.get_future().wait_for(std::chrono::seconds(8)), future_status::timeout); // Check config has been resumed. ASSERT_TRUE(cb.syncByBackupId(id)); // Just let the sync mirror, Marge! waitonsyncs(TIMEOUT, &cb); // The cloud should match the local disk precisely. ASSERT_TRUE(cb.confirmModel_mainthread(m.root.get(), id)); } #ifdef DEBUG class BackupBehavior : public ::testing::Test { public: void doTest(const string& initialContent, const string& updatedContent); }; // BackupBehavior void BackupBehavior::doTest(const string& initialContent, const string& updatedContent) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClient cu(TESTROOT, "cu"); // Log callbacks. cu.logcb = true; // Log in uploader client. ASSERT_TRUE(cu.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Add and start a backup sync. const auto idU = cu.setupSync_mainthread("su", "s", true, false); ASSERT_NE(idU, UNDEF); // Add a file for the engine to synchronize. Model m; m.addfile("f", initialContent); m.generate(cu.fsBasePath / "su"); cu.triggerPeriodicScanEarly(idU); // Wait for the engine to process and upload the file. waitonsyncs(TIMEOUT, &cu); // Make sure the file made it to the cloud. ASSERT_TRUE(cu.confirmModel_mainthread(m.root.get(), idU)); // Update file maintaining the mtime { const auto mtime = fs::last_write_time(cu.fsBasePath / "su" / "f"); // Update the file's content. m.addfile("f", updatedContent); // Write the file. m.generate(cu.fsBasePath / "su", true); fs::last_write_time(cu.fsBasePath / "su" / "f", mtime); cu.triggerPeriodicScanEarly(idU); } // Wait for the engine to process the change. waitonsyncs(TIMEOUT, &cu); // Make sure the sync hasn't been disabled. { auto config = cu.syncConfigByBackupID(idU); ASSERT_EQ(config.mEnabled, true); ASSERT_EQ(config.mError, NO_SYNC_ERROR); } // Check that the file's been uploaded to the cloud. { StandardClient cd(TESTROOT, "cd"); // Log in client. ASSERT_TRUE(cd.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Add and start a new sync. auto idD = cd.setupSync_mainthread("sd", "s", false, false); ASSERT_NE(idD, UNDEF); // Wait for the sync to complete. waitonsyncs(TIMEOUT, &cd); // Make sure we haven't uploaded anything. ASSERT_TRUE(cu.confirmModel_mainthread(m.root.get(), idU)); // Necessary since we've downloaded a file. m.ensureLocalDebrisTmpLock(""); // Check that we've downloaded what we should've. ASSERT_TRUE(cd.confirmModel_mainthread(m.root.get(), idD)); } } TEST_F(BackupBehavior, SameMTimeSmallerCRC) { // File's small enough that the content is the CRC. auto initialContent = string("f"); auto updatedContent = string("e"); doTest(initialContent, updatedContent); } TEST_F(BackupBehavior, SameMTimeSmallerSize) { auto initialContent = string("ff"); auto updatedContent = string("f"); doTest(initialContent, updatedContent); } #endif // DEBUG TEST_F(SyncTest, RemoteReplaceDirectory) { auto TESTROOT = makeNewTestRoot(); // Sync client. StandardClient c(TESTROOT, "c"); // Log callbacks. c.logcb = true; // Log in client. ASSERT_TRUE(c.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Add and start sync. auto id = c.setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Populate local filesystem. Model m; m.addfile("x/d/f"); m.addfile("x/d/g"); m.addfile("d/f"); m.addfile("d/g"); //m.addfile("d/h"); m.generate(c.syncSet(id).localpath); c.triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(chrono::seconds(4), &c); // Make sure everything made it to the cloud. ASSERT_TRUE(c.confirmModel_mainthread(m.root.get(), id)); // Replace d/f with f. { StandardClient cr(TESTROOT, "cr"); // Log callbacks. cr.logcb = true; // Log client in. ASSERT_TRUE(cr.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Get our hands on x/d's node. auto node = cr.drillchildnodebyname(cr.gettestbasenode(), "s/x/d"); ASSERT_NE(node, nullptr); { c.received_node_actionpackets = false; // Move d to x/d. ASSERT_TRUE(cr.movenode("s/d", "s/x")); // Wait for c to receive necessary action packets. ASSERT_TRUE(c.waitForNodesUpdated(8)); // Wait for c to stall. ASSERT_TRUE(c.waitFor(SyncStallState(true), chrono::seconds(15))); c.received_node_actionpackets = false; // Remove the original x/d. ASSERT_TRUE(cr.deleteremote(node.get())); // Wait for c to receive cr's changes. ASSERT_TRUE(c.waitForNodesUpdated(8)); // Wait for c to recover from the stall. ASSERT_TRUE(c.waitFor(SyncStallState(false), chrono::seconds(15))); } // Update model. m.movetosynctrash("x/d", ""); m.movenode("d", "x"); } // Wait for sync to complete. waitonsyncs(chrono::seconds(4), &c); // Did the sync complete successfully? ASSERT_TRUE(c.confirmModel_mainthread(m.root.get(), id)); } TEST_F(SyncTest, RemoteReplaceFile) { auto TESTROOT = makeNewTestRoot(); // Sync client. StandardClient c(TESTROOT, "c"); // Log callbacks. c.logcb = true; // Log in client. ASSERT_TRUE(c.login_reset_makeremotenodes("MEGA_EMAIL", "MEGA_PWD", "s", 0, 0)); // Add and start sync. auto id = c.setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Populate local filesystem. Model m; m.addfile("d/f"); m.addfile("f"); m.generate(c.syncSet(id).localpath); c.triggerPeriodicScanEarly(id); // Wait for initial sync to complete. waitonsyncs(chrono::seconds(4), &c); // Make sure everything made it to the cloud. ASSERT_TRUE(c.confirmModel_mainthread(m.root.get(), id)); c.received_node_actionpackets = false; // Replace d/f with f. { StandardClient cr(TESTROOT, "cr"); // Log callbacks. cr.logcb = true; // Log client in. ASSERT_TRUE(cr.login_fetchnodes("MEGA_EMAIL", "MEGA_PWD")); // Get our hands on d/f's node. auto node = cr.drillchildnodebyname(cr.gettestbasenode(), "s/d/f"); ASSERT_NE(node, nullptr); { // Move /f to /d/f. ASSERT_TRUE(cr.movenode("s/f", "s/d")); // Wait for c to stall. ASSERT_TRUE(c.waitFor(SyncStallState(true), chrono::seconds(15))); // Remove the original /d/f. ASSERT_TRUE(cr.deleteremote(node.get())); // Wait for c to recover from the stall. ASSERT_TRUE(c.waitFor(SyncStallState(false), chrono::seconds(15))); } // Update model. m.movetosynctrash("d/f", ""); m.movenode("f", "d"); } ASSERT_TRUE(c.waitForNodesUpdated(30)) << " no actionpacket received in c"; // Wait for sync to complete. waitonsyncs(chrono::seconds(4), &c); // Did the sync complete successfully? ASSERT_TRUE(c.confirmModel_mainthread(m.root.get(), id)); } class FilterFixture : public ::testing::Test { public: struct LocalFSModel : public Model { LocalFSModel() = default; LocalFSModel(const Model& other) : Model(other) { } LocalFSModel &operator=(const Model& other) { Model::operator=(other); return *this; } }; /* LocalFSModel */ struct LocalNodeModel : public Model { LocalNodeModel() = default; LocalNodeModel(const Model& other) : Model(other) { } LocalNodeModel& operator=(const Model& other) { Model::operator=(other); return *this; } }; /* LocalNodeModel */ struct RemoteNodeModel : public Model { RemoteNodeModel() = default; RemoteNodeModel(const Model& other) : Model(other) { } RemoteNodeModel& operator=(const Model& other) { Model::operator=(other); return *this; } }; /* RemoteNodeModel */ FilterFixture() : filterFixureTestRoot(makeNewTestRoot()) , cd(g_clientManager->getCleanStandardClient(0, filterFixureTestRoot)) , cdu(g_clientManager->getCleanStandardClient(0, filterFixureTestRoot)) , cu(g_clientManager->getCleanStandardClient(0, filterFixureTestRoot)) { cd->logcb = true; cdu->logcb = true; cu->logcb = true; } void SetUp() override { ASSERT_TRUE(cd->resetBaseFolderMulticlient(cdu, cu)); } bool confirm(StandardClient& client, const handle id, LocalFSModel& model, const bool ignoreDebris = true, const bool ignoreIgnoreFile = false) { return client.confirmModel_mainthread( model.root.get(), id, ignoreDebris, StandardClient::CONFIRM_LOCALFS, false, ignoreIgnoreFile); } bool confirm(StandardClient& client, const handle id, LocalNodeModel& model, const bool ignoreDebris = true, const bool ignoreIgnoreFile = false) { return client.confirmModel_mainthread( model.root.get(), id, ignoreDebris, StandardClient::CONFIRM_LOCALNODE, false, ignoreIgnoreFile); } bool confirm(StandardClient& client, const handle id, Model& model, const bool ignoreDebris = true, const bool ignoreIgnoreFile = false) { return client.confirmModel_mainthread( model.root.get(), id, ignoreDebris, StandardClient::CONFIRM_ALL, false, ignoreIgnoreFile); } bool confirm(StandardClient& client, const handle id, RemoteNodeModel& model, const bool ignoreDebris = true, const bool ignoreIgnoreFile = false) { return client.confirmModel_mainthread( model.root.get(), id, ignoreDebris, StandardClient::CONFIRM_REMOTE, false, ignoreIgnoreFile); } string debrisFilePath(const string& debrisName, const string& path) const { ostringstream ostream; ostream << debrisName << "/" << todaysDate() << "/" << path; return ostream.str(); } fs::path root(StandardClient& client) const { return client.fsBasePath; } handle setupSync(StandardClient& client, const string& localFolder, const string& remoteFolder, bool uploadIgnoreFirst = true, bool legacyExclusionsEligible = false) { SyncOptions options; options.legacyExclusionsEligible = legacyExclusionsEligible; options.uploadIgnoreFile = uploadIgnoreFirst; return client.setupSync_mainthread(localFolder, remoteFolder, options); } string todaysDate() const { size_t minimumLength = strlen("yyyy-mm-dd"); string result(minimumLength + 1, 'X'); time_t rawTime = time(nullptr); tm* localTime = localtime(&rawTime); strftime(&result[0], result.size(), "%F", localTime); result.resize(minimumLength); return result; } void waitOnSyncs(StandardClient* c0, StandardClient* c1 = nullptr, StandardClient* c2 = nullptr) { static chrono::seconds timeout(16); waitonsyncs(timeout, c0, c1, c2); } fs::path filterFixureTestRoot; // download client. StandardClientInUse cd; // download / upload client. StandardClientInUse cdu; // upload client. StandardClientInUse cu; }; /* FilterFixture */ TEST_F(FilterFixture, AlreadySyncedFilterIsLoaded) { LocalFSModel localFS; RemoteNodeModel remoteTree; ASSERT_TRUE(cdu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Populate local filesystem. localFS.addfile(".megaignore", "-:f\n-:g\n+sync:.megaignore"); localFS.addfile("f"); localFS.addfile("g"); localFS.addfile("h"); localFS.generate(root(cdu) / "root"); // Populate cloud filesystem. { auto base = cdu->gettestbasenode(); auto x = cdu->drillchildnodebyname(base, "x"); // Upload .megaignore. ASSERT_TRUE(cdu->uploadFile(root(*cdu) / "root" / ".megaignore", *x)); // Upload f. ASSERT_TRUE(cdu->uploadFile(root(*cdu) / "root" / "f", *x)); } // Client shouldn't upload g as it will be excluded. remoteTree = localFS; remoteTree.removenode("g"); // Client shouldn't download f as it will be excluded. localFS.removenode("f"); fs::remove(root(*cdu) / "root" / "f"); // Add and start sync. const auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for the sync to complete. waitOnSyncs(cdu); // Confirm the models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(FilterFixture, CaseSensitiveFilter) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up filesystem. localFS.addfile("a/f"); localFS.addfile("a/g"); localFS.addfile("b/F"); localFS.addfile("b/G"); localFS.addfile(".megaignore", "-G:f\n-:g\n+sync:.megaignore"); // Set up remote tree. remoteTree = localFS; remoteTree.removenode("a/f"); remoteTree.removenode("a/g"); remoteTree.removenode("b/G"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); localFS.generate(root(*cu) / "root"); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(FilterFixture, ExclusionSpecifiedWhenSyncAdded) { // Make sure the sync root exists in the cloud. ASSERT_TRUE(cdu->makeCloudSubdirs("s", 0, 0)); // Make sure we've received action packets before proceeding. ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Convenience. auto rootPath = cdu->fsBasePath / "s"; // Populate the local filesystem. LocalFSModel localFS; localFS.addfolder("q"); localFS.addfolder("r"); localFS.generate(rootPath); // Setup the sync. auto id = UNDEF; { SyncOptions options; // s/q should be excluded by default. options.excludePath = path_u8string((u8path_compat("s") / u8path_compat("q"))); // Add and start the sync. id = cdu->setupSync_mainthread("s", "s", options); ASSERT_NE(id, UNDEF); } // Wait for the initial sync to complete. waitOnSyncs(cdu); // Make sure nothing's changed on disk. ASSERT_TRUE(confirm(*cdu, id, localFS, true, true)); // Make sure s/q wasn't uploaded to the cloud. RemoteNodeModel remoteTree; remoteTree.addfolder("r"); ASSERT_TRUE(confirm(*cdu, id, remoteTree, true, true)); } TEST_F(FilterFixture, FilterChangeWhileDownloading) { // Random file data. const auto data = randomData(16384); // Ignore file data. const string ignoreFile = "-:f\n+sync:.megaignore"; // Set up cloud. { // Build and generate model. Model model; model.addfile(".megaignore", "+sync:.megaignore"); model.addfile("f", data); model.generate(root(*cu) / "root"); // Create sync root. ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm model. ASSERT_TRUE(confirm(*cu, id, model)); // Terminate the sync. ASSERT_TRUE(cu->delSync_mainthread(id)); // Wait for the clients to agree on the state of the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile(".megaignore", ignoreFile); localFS.addfile("f", data); // Set up remote model. remoteTree = localFS; // Set download speed limit at 1kbps. cdu->setDownloadSpeed(1024); // So we know when f has started transferring. std::atomic downloadingf{false}; // Exclude "f" once it begins downloading. cdu->mOnFileAdded = [&](File& file) { string name; file.displayname(&name); if (name != "f") return; // Let the completion callback know we've started downloading f. downloadingf.store(true); // now we write over .megaignore to make it ignore the file f that is downloading ASSERT_TRUE(createFile(root(*cdu) / "root" / ".megaignore", ignoreFile.data(), ignoreFile.size())); cdu->triggerPeriodicScanEarly(UNDEF); }; // Remove download limit once .megaignore is uploaded. cdu->mOnFileComplete = [&](File& file) { // Wait until we've started uploading f. if (!downloadingf.load()) return; string name; // What transfer has completed? file.displayname(&name); ASSERT_TRUE(name == ".megaignore" || cdu->client.getmaxdownloadspeed() == 0); // Reset the speed limit when the ignore file has been uploaded. if (name == ".megaignore") cdu->client.setmaxdownloadspeed(0); }; // Add and start sync. auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cdu); // we expect the download to have been removed once we ignored the node localFS.removenode("f"); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(FilterFixture, FilterChangeWhileUploading) { // Random file data. const auto data = randomData(16384); // Ignore file data. const string ignoreFile = "-:f\n+sync:.megaignore"; LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile("f", data); localFS.generate(root(*cdu) / "root"); localFS.addfile(".megaignore", ignoreFile); // Set up remote tree. remoteTree = localFS; // Create sync root. ASSERT_TRUE(cdu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Set upload speed limit to 1kbps. cdu->setUploadSpeed(1024); cdu->mOnFileAdded = [&](File& file) { string name; file.displayname(&name); // remove speed limit when .megaignore starts uploading. if (name == ".megaignore") { cdu->client.setmaxuploadspeed(0); } // create .megaignore when f starts uploading. if (name == "f") { ASSERT_TRUE(createFile(root(*cdu) / "root" / ".megaignore", ignoreFile.data(), ignoreFile.size())); cdu->triggerPeriodicScanEarly(UNDEF); } }; // Add and start sync. auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitonsyncs(std::chrono::seconds(30), cdu); // 16 was too short, when an upload failed and retried, succeeded on 2nd go but did not get to putnodes in time // we expect the upload to have been removed once we ignored the node remoteTree.removenode("f"); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(FilterFixture, NameFilter) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", // exclude all *.n* in the tree. "-:*.n*\n" // include all *.ni in the tree. "+:*.ni\n" // include all *.nN in the root. "+N:*.nN\n" // exclude all *.X* in the root. "-N:*.X*\n" // include all *.Xi in the root. "+N:*.Xi\n" "+sync:.megaignore"); // excluded by -:*.n* localFS.addfile("d/df.n"); // included by +:*.ni localFS.addfile("d/df.ni"); // excluded by -:*.n* localFS.addfile("d/df.nN"); // included as no matching exclusion rule. localFS.addfile("d/df.X"); // excluded by -:*.n* localFS.addfile("f.n"); // included by +:*.ni localFS.addfile("f.ni"); // excluded by -:*.n* localFS.addfile("f.nN"); // excluded by -N:*.X* localFS.addfile("f.X"); // included by +N:*.Xi localFS.addfile("f.Xi"); // excluded by -:*.n* localFS.addfile("d.n/f.ni"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("d/df.n"); remoteTree.removenode("d/df.nN"); remoteTree.removenode("f.n"); remoteTree.removenode("f.X"); remoteTree.removenode("d.n"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(FilterFixture, OrderDependentFilter) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local filesystem. localFS.addfile(".megaignore", "-:a*\n+:ab*\n-:abc*\n+:abcd*\n+sync:.megaignore"); localFS.addfile("a"); localFS.addfile("ab"); localFS.addfile("abc"); localFS.addfile("abcd"); localFS.generate(root(*cu) / "root"); // a, abc are excluded from the remote node tree. remoteTree = localFS; remoteTree.removenode("a"); remoteTree.removenode("abc"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(FilterFixture, PathFilter) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", // exclude path d*/d* "-p:d*/d*\n" // include path di*/di* "+p:di*/di*\n" // include path dL "+p:dL\n" // include everything under dJ "+p:dJ*\n" "+sync:.megaignore"); // excluded by -p:d*/d* localFS.addfile("d/d/f"); // included as no matching rule. localFS.addfile("d/f"); // included by +p:di*/di* localFS.addfile("di/di/f"); // included as no matching rule. localFS.addfile("di/f"); // excluded by -p:d*/d* localFS.addfile("dL/d/f"); // included by +p:dL localFS.addfile("dL/f"); // included by +p:dJ* localFS.addfile("dJ/d/f"); localFS.addfile("dJ/f"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("d/d"); remoteTree.removenode("dL/d"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(FilterFixture, TargetSpecificFilter) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local filesystem. { const string ignoreFile = // Exclude directories matching *a. "-d:*a\n" // Exclude files matching *b. "-f:*b\n" // Exclude anything matching *c. "-:*c\n" // Include everything containing an x. "+:*x*\n" "+sync:.megaignore"; localFS.addfile("da/fa", "fa"); localFS.addfile("da/fb", "fb"); localFS.addfile("da/fc", "fc"); localFS.addfile("da/fxb", "fxb"); localFS.addfile("da/fxc", "fxc"); localFS.addfile(".megaignore", ignoreFile); localFS.addfile("fa"); localFS.addfile("fb"); localFS.addfile("fxb"); localFS.addfile("fc"); localFS.addfile("fxc"); localFS.copynode("da", "db"); localFS.copynode("da", "dc"); localFS.copynode("da", "dxa"); localFS.copynode("da", "dxc"); localFS.generate(root(*cu) / "root"); } // Set up rmote node tree. remoteTree = localFS; // Excluded by -d:*a remoteTree.removenode("da"); // Excluded by -f:*b remoteTree.removenode("db/fb"); remoteTree.removenode("dxa/fb"); remoteTree.removenode("dxc/fb"); remoteTree.removenode("fb"); // Excluded by -:*c remoteTree.removenode("db/fc"); remoteTree.removenode("dc"); remoteTree.removenode("dxa/fc"); remoteTree.removenode("dxc/fc"); remoteTree.removenode("fc"); // Log in the client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start the sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } class FilterFailureFixture : public FilterFixture { }; // FilterFailureFixture TEST_F(FilterFailureFixture, ResolveBrokenIgnoreFile) { // Convenience. const auto TIMEOUT = std::chrono::seconds(8); Model model0; Model model1; // Log in client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 1, 2)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Populate models. model0.addfile(".megaignore", "+sync:.megaignore"); model0.generate(root(*cdu) / "s0"); model1.addfile(".megaignore", "+sync:.megaignore"); model1.addfile("f0"); model1.generate(root(*cdu) / "s1"); // Add and start syncs. auto id0 = setupSync(*cdu, "s0", "cdu/cdu_0", false); ASSERT_NE(id0, UNDEF); WaitMillisec(5000); // give it a chance to upload .megaignore before we start the 2nd sync auto id1 = setupSync(*cdu, "s1", "cdu/cdu_1", false); ASSERT_NE(id1, UNDEF); // Wait for the initial sync to complete. waitOnSyncs(cdu); // Make sure everything's as we expect. ASSERT_TRUE(confirm(*cdu, id0, model0)); ASSERT_TRUE(confirm(*cdu, id1, model1)); // Break the ignore file. model0.addfile(".megaignore", "bad"); model0.generate(root(*cdu) / "s0"); cdu->triggerPeriodicScanEarly(id0); // Wait for the stall to be recognized. ASSERT_TRUE(cdu->waitFor(SyncStallState(true), TIMEOUT)); // Pause the sync that owns the broken ignore file. ASSERT_TRUE(cdu->setSyncPausedByBackupId(id0, true)); // Stall should be resolved. ASSERT_TRUE(cdu->waitFor(SyncStallState(false), TIMEOUT)); // Check that the second sync is once again operating. model1.removenode("f0"); fs::remove(root(*cdu) / "s1" / "f0"); // Wait for the sync to complete. cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // Was the change synchronized? ASSERT_TRUE(confirm(*cdu, id1, model1)); // Unpause the sync that owns the broken ignore file. ASSERT_TRUE(cdu->setSyncPausedByBackupId(id0, false)); // The engine should stall again. ASSERT_TRUE(cdu->waitFor(SyncStallState(true), TIMEOUT)); // Disable the stalled sync. ASSERT_TRUE(cdu->disableSync(id0, NO_SYNC_ERROR, false, true)); // The engine should no longer be stalled. ASSERT_TRUE(cdu->waitFor(SyncStallState(false), TIMEOUT)); // Re-add f0 and see if it gets uploaded. model1.addfile("f0"); model1.generate(root(*cdu) / "s1"); // Give the sync some time to process changes. cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // File should only be uploaded is s1 is operational. ASSERT_TRUE(confirm(*cdu, id1, model1)); // Re-enable the disabled sync. ASSERT_TRUE(cdu->enableSyncByBackupId(id0, "s0 ")); // The engine should stall again. ASSERT_TRUE(cdu->waitFor(SyncStallState(true), TIMEOUT)); // Removing the sync should resolve the issue. ASSERT_TRUE(cdu->delSync_mainthread(id0)); // Engine should no longer be stalled. ASSERT_TRUE(cdu->waitFor(SyncStallState(false), TIMEOUT)); // Add a new file just to make sure the second sync is operating. model1.addfile("f1"); model1.generate(root(*cdu) / "s1"); // Give the engine some time to process our change. cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // f1 should exist in the cloud if the second sync is running. ASSERT_TRUE(confirm(*cdu, id1, model1)); } TEST_F(FilterFailureFixture, TriggersStall) { Model model; // Set up the local filesystem. model.addfile(".megaignore", "exclude-larger:4\nexclude-smaller:4\n+sync:.megaignore"); model.generate(root(*cu) / "root"); // Log in the client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Hook the stall event. std::promise notifier; cu->mOnStall = [&](bool stalled) { // Only notify the waiter when we become stalled. if (!stalled) return; // Notify the waiter. notifier.set_value(); // Detach the callback. cu->mOnStall = nullptr; }; // Add and start the sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for the engine to detect a stall. ASSERT_NE(notifier.get_future().wait_for(chrono::seconds(8)), future_status::timeout); // Was the ignore file correctly reported as the stall's cause? SyncStallInfoTests stalls; // Retrieve a list of stall causes from the client. ASSERT_TRUE(cu->syncStallDetected(stalls)); // Make sure a stall was actually stored. ASSERT_FALSE(stalls.local.empty()); auto& entry = *stalls.local.begin(); // Was an ignore file behind the stall? ASSERT_EQ(entry.first.leafName(), IGNORE_FILE_NAME); // Was a load failure the cause of the stall? ASSERT_EQ(entry.second.reason, SyncWaitReason::FileIssue); ASSERT_EQ(entry.second.localPath1.problem, PathProblem::IgnoreFileMalformed); } class LocalToCloudFilterFixture : public FilterFixture { public: string debrisFilePath(const string& path) const { return FilterFixture::debrisFilePath("SyncDebris", path); } }; /* LocalToCloudFilterFixture */ TEST_F(LocalToCloudFilterFixture, AcceptableFilterNameClash) { const auto TIMEOUT = std::chrono::seconds(16); // Log in, taking care to reset the cloud's content. ASSERT_TRUE(cu->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Populate the local filesystem. Model model; model.addfile("dl/fe"); model.addfile("dl/fi"); model.addfile(".megaignore", "-:fe\n+sync:.megaignore"); model.generate(cu->fsBasePath / "s"); // Populate the cloud. { // Convenience. auto ignoreFilePath = cu->fsBasePath / ".megaignore"; // Create an ignore file for us to upload. ASSERT_TRUE(createFile(ignoreFilePath, "+sync:.megaignore")); // Upload the ignore file twice so to create a remote name clash. ASSERT_TRUE(cu->uploadFile(ignoreFilePath, "s")); ASSERT_TRUE(cu->uploadFile(ignoreFilePath, "s")); } // Add and start a sync. auto id = setupSync(*cu, "s", "s", false); ASSERT_NE(id, UNDEF); // Give the engine some time to detect the name clash. waitOnSyncs(cu); // Wait for the engine to detect the name clash. ASSERT_TRUE(cu->waitFor(SyncConflictState(true), TIMEOUT)); // Make sure the clash is due to the ignore files. { list conflicts; // Ask the engine what clashes it has detected. ASSERT_TRUE(cu->conflictsDetected(conflicts)); // Clashes present? ASSERT_FALSE(conflicts.empty()); const auto& conflict = conflicts.front(); // Present at the sync root? ASSERT_EQ(conflict.cloudPath, "/mega_test_sync/s"); // Two clashing names? ASSERT_EQ(conflict.clashingCloud.size(), 2u); // Both ignore files? ASSERT_EQ(conflict.clashingCloud[0].name, IGNORE_FILE_NAME); ASSERT_EQ(conflict.clashingCloud[1].name, IGNORE_FILE_NAME); } // Check that the engine uploaded what we expect. auto root = cu->gettestbasenode(); // Get our hands on the cloud root. root = cu->drillchildnodebyname(root, "s"); ASSERT_NE(root, nullptr); // Check that the directory "dl" has been uploaded. auto dl = cu->drillchildnodebyname(root, "dl"); ASSERT_NE(dl, nullptr); // Check that the file "dl/fi" has been uploaded. ASSERT_NE(cu->drillchildnodebyname(dl, "fi"), nullptr); // Check that the file "dl/fe" has been skipped. ASSERT_EQ(cu->drillchildnodebyname(dl, "fe"), nullptr); // Locally removing the ignore file should clear any loaded filters. fs::remove(cu->fsBasePath / "s" / ".megaignore"); // Give the engine some time to react to our changes. waitOnSyncs(cu); // Add a new file for the engine to try and synchronize. model.addfile("dl/fx"); model.removenode(".megaignore"); model.generate(cu->fsBasePath / "s"); // Give the engine some time to process our changes. waitOnSyncs(cu); // "dl/fx" should not be synchronized. // // This is because there is no local ignore file present but there are // two ignore files in the cloud and the engine can't decide which of // the two it should download and read. // // That is, the filter state is undefined. // // The engine will not synchronize anything until the issue is resolved. ASSERT_EQ(cu->drillchildnodebyname(dl, "fx"), nullptr); // Remove one of the ignore files in the cloud. ASSERT_TRUE(cu->deleteremote("s/.megaignore")); // Wait for the engine to process the changes. waitOnSyncs(cu); // Wait for the name clash to be resolved. ASSERT_TRUE(cu->waitFor(SyncConflictState(false), TIMEOUT)); // Ignore file's been downloaded. model.addfile(".megaignore", "+sync:.megaignore"); model.ensureLocalDebrisTmpLock(""); // No filters should be in effect and all files should be synchronized. ASSERT_TRUE(confirm(*cu, id, model, false)); } TEST_F(LocalToCloudFilterFixture, DoesntDownloadIgnoredNodes) { // Set up cloud. { Model model; model.addfile("d/f"); model.addfile(".megaignore", "+sync:.megaignore"); model.addfile("f"); model.addfile("g", string(16, '!')); model.generate(root(*cu) / "root"); ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); auto id = setupSync(*cu, "root", "x", false); ASSERT_NE(id, UNDEF); waitOnSyncs(cu); // Make sure everything was uploaded. ASSERT_TRUE(confirm(*cu, id, model)); // Remove the sync. ASSERT_TRUE(cu->delSync_mainthread(id)); // Wait for the clients to agree on the state of the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } // Set up local FS. LocalFSModel localFS; localFS.addfile(".megaignore", "-:d\n-:f\nexclude-larger:15\n+sync:.megaignore"); localFS.generate(root(*cd) / "root"); // Set up local and remote trees. RemoteNodeModel remoteTree = localFS; remoteTree.addfile("d/f"); remoteTree.addfile("f"); remoteTree.addfile("g", string(16, '!')); // Add and start sync. auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cd); ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, DoesntMoveIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("0/fx"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfolder("1"); localFS.generate(root(*cu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Filter out 0/fx. localFS.addfile("0/.megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Wait for the ignore file to be processed. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // 0/fx should remain in the cloud. // 1/fx should be added to the cloud. remoteTree = localFS; remoteTree.copynode("0/fx", "1/fx"); // 0/fx should become 1/fx in both local models. localFS.copynode("0/fx", "1/fx"); localFS.removenode("0/fx"); // Rename 0/fx to 1/fx. fs::rename(root(*cu) / "root" / "0"/ "fx", root(*cu) / "root" / "1"/ "fx"); // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, DoesntRenameIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("fx"); localFS.generate(root(*cu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Filter out fx. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // fu should be added to the cloud. // fx should remain in the cloud. remoteTree = localFS; remoteTree.copynode("fx", "fu"); // fx should beecome fu in both local models. localFS.copynode("fx", "fu"); localFS.removenode("fx"); // Rename fx to fu. fs::rename(root(*cu) / "root" / "fx", root(*cu) / "root" / "fu"); // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, DoesntRubbishIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("fx"); localFS.generate(root(*cu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Filter out fx. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // fx should remain in the cloud. remoteTree = localFS; // fx should no longer be visible in ether local model. localFS.removenode("fx"); // Remove fx from the FS. ASSERT_TRUE(fs::remove(root(*cu) / "root" / "fx")); // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, DoesntUploadIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("db/.megaignore", "exclude-smaller:8\nexclude-larger:16\n+sync:.megaignore"); localFS.addfile("db/fe0", randomData(7)); localFS.addfile("db/fe1", randomData(17)); localFS.addfile("db/fi0", randomData(8)); localFS.addfile("db/fi1", randomData(16)); localFS.addfile("dl/.megaignore", "exclude-smaller:16\n+sync:.megaignore"); localFS.addfile("dl/fe", randomData(15)); localFS.addfile("dl/fi", randomData(16)); localFS.addfile("du/.megaignore", "exclude-larger:16\n+sync:.megaignore"); localFS.addfile("du/fe", randomData(17)); localFS.addfile("du/fi", randomData(16)); localFS.addfolder("dx"); localFS.addfile("fu"); localFS.addfile("fx"); localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote tree remoteTree = localFS; remoteTree.removenode("db/fe0"); remoteTree.removenode("db/fe1"); remoteTree.removenode("dl/fe"); remoteTree.removenode("dr/fe0"); remoteTree.removenode("dr/fe1"); remoteTree.removenode("du/fe"); remoteTree.removenode("dx"); remoteTree.removenode("fx"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm model expectations. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Update du/fi so that it violates the size filter. localFS.addfile("du/fi", randomData(32768)); localFS.generate(root(*cu) / "root"); // Wait for the change to be synchronized. // // This is expected as size filters have no effect if a file that // would be excluded exists locally and in the cloud. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Remove the file locally. localFS.removenode("du/fi"); fs::remove(root(*cu) / "root" / "du" / "fi"); // It should also be removed in the cloud due to above. remoteTree.removenode("du/fi"); // Wait for the sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Everything as we expect? ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, ExcludedIgnoreFile) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Populate local filesystem. localFS.addfile(".megaignore", "-N:f\n-:.megaignore\n+sync:.megaignore"); localFS.addfile("d/.megaignore", "-:f\n+sync:.megaignore"); localFS.addfile("d/f"); localFS.addfile("d/g"); localFS.addfile("e/.megaignore", "-:g\n+sync:.megaignore"); localFS.addfile("e/f"); localFS.addfile("e/g"); localFS.addfile("f/.megaignore", "+sync:.megaignore"); localFS.generate(root(*cu) / "root"); remoteTree = localFS; // Excluded by /.megaignore. remoteTree.removenode("f"); // Excluded by /d/.megaignore. remoteTree.removenode("d/f"); // Excluded by /e/.megaignore. remoteTree.removenode("e/g"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. const auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitOnSyncs(cu); // Check everything that should've been uploaded, was. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Remove the root ignore file. localFS.removenode(".megaignore"); remoteTree.removenode(".megaignore"); fs::remove(root(*cu) / "root" / ".megaignore"); // Which will cause these to be uploaded. remoteTree.addfile("f/.megaignore", "+sync:.megaignore"); // Wait for the sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Check that the newly included ignore files were uploaded. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterAdded) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("fu"); localFS.addfile("fx"); localFS.generate(root(*cu) / "root"); // Setup local and remote trees. remoteTree = localFS; // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for and confirm sync. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Add filter. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.addfile("fxx"); localFS.generate(root(*cu) / "root"); // fxx should not be visible in remote tree. remoteTree = localFS; remoteTree.removenode("fxx"); // Wait for and confirm sync. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterChanged) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:*y\nexclude-larger:2\n\n+sync:.megaignore"); localFS.addfile("fx"); localFS.addfile("fy"); localFS.addfile("fz", "xxx"); localFS.generate(root(*cu) / "root"); // fy should not be present in the remote tree. remoteTree = localFS; remoteTree.removenode("fy"); remoteTree.removenode("fz"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for and confirm sync. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Update filter. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // f[xyz] should both be present in remote tree. remoteTree = localFS; // Wait for and confirm sync. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Create a new folder, d, and move the ignore file into it. localFS.addfolder("d"); localFS.generate(root(*cu) / "root"); localFS.movenode(".megaignore", "d"); remoteTree.addfolder("d"); remoteTree.movenode(".megaignore", "d"); fs::rename(root(*cu) / "root" / ".megaignore", root(*cu) / "root" / "d" / ".megaignore"); // Wait for and confirm sync. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move the ignore file back to the root. localFS.movenode("d/.megaignore", ""); remoteTree.movenode("d/.megaignore", ""); fs::rename(root(*cu) / "root" / "d" / ".megaignore", root(*cu) / "root" / ".megaignore"); // Remove fx locally. localFS.removenode("fx"); fs::remove(root(*cu) / "root" / "fx"); // Wait for and confirm sync. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterDeferredChange) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("0/.megaignore", "-:f\n+sync:.megaignore"); localFS.addfile("0/f"); localFS.addfile("1/.megaignore", "-:g\n+sync:.megaignore"); localFS.addfile("1/g"); localFS.addfile(".megaignore", "-:?\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("0"); remoteTree.removenode("1"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Change 0/.megaignore. // Filter reload will be deferred. localFS.addfile("0/.megaignore", "#-:f\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Remove 1/.megaignore. // Filter clear will be deferred. localFS.removenode("1/.megaignore"); ASSERT_TRUE(fs::remove(root(*cu) / "root" / "1" / ".megaignore")); // Wait for sync. // This should be a no-op as our changes are to ignored nodes. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); cu->received_node_actionpackets = false; // Remove .megaignore. // This should perform any pending filter reloads. ASSERT_TRUE(fs::remove(root(*cu) / "root" / ".megaignore")); cu->triggerPeriodicScanEarly(id); ASSERT_TRUE(cu->waitForNodesUpdated(30)) << " no actionpacket received in cu for remove"; // Update models. localFS.removenode(".megaignore"); remoteTree = localFS; // Wait for sync. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterMovedAcrossHierarchy) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("0/.megaignore", "-:x\n+sync:.megaignore"); localFS.addfile("0/u"); localFS.addfile("0/x"); localFS.addfile("1/u"); localFS.addfile("1/x"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("0/x"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move 0/.megaignore to 1. fs::rename(root(*cu) / "root" / "0" / ".megaignore", root(*cu) / "root" / "1" / ".megaignore"); localFS.movenode("0/.megaignore", "1"); // Update local and remote trees. remoteTree = localFS; // Wait for synchronization. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterMovedBetweenSyncs) { LocalFSModel s0LocalFS; LocalFSModel s1LocalFS; RemoteNodeModel s0RemoteTree; RemoteNodeModel s1RemoteTree; // Sync 0 { // Set up local FS. s0LocalFS.addfile(".megaignore", "+sync:.megaignore"); s0LocalFS.addfile("d/.megaignore", "-:x\n+sync:.megaignore"); s0LocalFS.addfile("d/x"); s0LocalFS.generate(root(*cdu) / "s0"); // Set up remote tree. s0RemoteTree = s0LocalFS; s0RemoteTree.removenode("d/x"); } // Sync 1 { // Set up local FS. s1LocalFS.addfile(".megaignore", "+sync:.megaignore"); s1LocalFS.addfile("d/x"); s1LocalFS.generate(root(*cdu) / "s1"); // Set up remote tree. s1RemoteTree = s1LocalFS; } // Create sync directories. { // Will be freed by putnodes_result(...). vector nodes(2); cdu->prepareOneFolder(&nodes[0], "s0", false); cdu->prepareOneFolder(&nodes[1], "s1", false); std::shared_ptr root = cdu->gettestbasenode(); ASSERT_TRUE(cdu->putnodes(root->nodeHandle(), NoVersioning, std::move(nodes))); ASSERT_TRUE(cdu->drillchildnodebyname(root, "s0")); ASSERT_TRUE(cdu->drillchildnodebyname(root, "s1")); } // Add and start syncs. auto id0 = setupSync(*cdu, "s0", "s0", false); ASSERT_NE(id0, UNDEF); WaitMillisec(5000); // give it a chance to upload .megaignore before we start the 2nd sync auto id1 = setupSync(*cdu, "s1", "s1", false); ASSERT_NE(id1, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id0, s0LocalFS)); ASSERT_TRUE(confirm(*cdu, id0, s0RemoteTree)); ASSERT_TRUE(confirm(*cdu, id1, s1LocalFS)); ASSERT_TRUE(confirm(*cdu, id1, s1RemoteTree)); // Move cdu/s0/d/.megaignore to cdu/s1/d/.megaignore. fs::rename(root(*cdu) / "s0" / "d" / ".megaignore", root(*cdu) / "s1" / "d" / ".megaignore"); // Wait for synchronization to complete. cdu->triggerPeriodicScanEarly(id0); cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // .megaignore no longer exists in cdu/s0. // as a consequence, cdu/s0/x is no longer ignored. s0LocalFS.removenode("d/.megaignore"); s0RemoteTree.removenode("d/.megaignore"); s0RemoteTree.addfile("d/x"); // .megaignore has been added to cdu/s1/d. // as a consequence, cdu/s1/d/x is now ignored. s1LocalFS.addfile("d/.megaignore", "-:x\n+sync:.megaignore"); s1RemoteTree = s1LocalFS; // Confirm models. ASSERT_TRUE(confirm(*cdu, id0, s0LocalFS)); ASSERT_TRUE(confirm(*cdu, id0, s0RemoteTree)); ASSERT_TRUE(confirm(*cdu, id1, s1LocalFS)); ASSERT_TRUE(confirm(*cdu, id1, s1RemoteTree)); // Add a new .megaignore to cdu/s0/d. // Add cdu/s0/d/y for it to ignore. s0LocalFS.addfile("d/.megaignore", "-:y\n+sync:.megaignore"); s0LocalFS.addfile("d/y"); s0LocalFS.generate(root(*cdu) / "s0"); s0RemoteTree = s0LocalFS; s0RemoteTree.removenode("d/y"); // Add cdu/s1/y. s1LocalFS.addfile("d/y"); s1LocalFS.generate(root(*cdu) / "s1"); s1RemoteTree.addfile("d/y"); // Wait for synchronization to complete. cdu->triggerPeriodicScanEarly(id0); cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id0, s0LocalFS)); ASSERT_TRUE(confirm(*cdu, id0, s0RemoteTree)); ASSERT_TRUE(confirm(*cdu, id1, s1LocalFS)); ASSERT_TRUE(confirm(*cdu, id1, s1RemoteTree)); // Move cdu/s0/d/.megaignore to cdu/s1/d/.megaignore. fs::rename(root(*cdu) / "s0" / "d" / ".megaignore", root(*cdu) / "s1" / "d" / ".megaignore"); // Wait for synchronization to complete. cdu->triggerPeriodicScanEarly(id0); cdu->triggerPeriodicScanEarly(id1); waitOnSyncs(cdu); // .megaignore no longer exists in cdu/s0/d. // as a consequence, cdu/s0/d/y is no longer ignored. s0LocalFS.removenode("d/.megaignore"); s0RemoteTree.removenode("d/.megaignore"); s0RemoteTree.addfile("d/y"); // cdu/s1/d/.megaignore has been overwritten. // as a consequence, cdu/s1/d/x is no longer ignored. // as a consequence, cdu/s1/d/y is ignored. s1LocalFS.addfile("d/.megaignore", "-:y\n+sync:.megaignore"); s1RemoteTree = s1LocalFS; // Confirm models. ASSERT_TRUE(confirm(*cdu, id0, s0LocalFS)); ASSERT_TRUE(confirm(*cdu, id0, s0RemoteTree)); ASSERT_TRUE(confirm(*cdu, id1, s1LocalFS)); ASSERT_TRUE(confirm(*cdu, id1, s1RemoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterMovedDownHierarchy) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:x\n+sync:.megaignore"); localFS.addfile("0/u"); localFS.addfile("0/x"); localFS.addfile("1/u"); localFS.addfile("1/x"); localFS.addfolder("2/0"); localFS.addfile("2/.megaignore", "-:.megaignore\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("0/x"); remoteTree.removenode("1/x"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move 0/.megaignore to root. fs::rename(root(*cu) / "root" / ".megaignore", root(*cu) / "root" / "0" / ".megaignore"); localFS.movenode(".megaignore", "0"); // 1/x is now visible in the local and remote trees. remoteTree = localFS; remoteTree.removenode("0/x"); // Move 2/.megaignore to 2/0/.megaignore. localFS.movenode("2/.megaignore", "2/0"); remoteTree.movenode("2/.megaignore", "2/0"); fs::rename(root(*cu) / "root" / "2" / ".megaignore", root(*cu) / "root" / "2" / "0" / ".megaignore"); // Wait for synchronization. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterMovedIntoExcluded) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile(".megaignore", "-:d*\n-:f*\n\n+sync:.megaignore"); localFS.addfile("d/g"); localFS.addfile("f"); localFS.generate(root(*cu) / "root"); // Only the ignore file is visible in the cloud. remoteTree = localFS; remoteTree.removenode("d"); remoteTree.removenode("f"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move ignore file into excluded directory. localFS.movenode(".megaignore", "d"); fs::rename(root(*cu) / "root" / ".megaignore", root(*cu) / "root" / "d" / ".megaignore"); // Remote tree is the same as FS. remoteTree = localFS; // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterMovedUpHierarchy) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("0/.megaignore", "-:x\n+sync:.megaignore"); localFS.addfile("0/u"); localFS.addfile("0/x"); localFS.addfile("1/u"); localFS.addfile("1/x"); localFS.addfile("2/0/.megaignore", "-:.megaignore\n+sync:.megaignore"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote trees. remoteTree = localFS; remoteTree.removenode("0/x"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move 0/.megaignore to root. fs::rename(root(*cu) / "root" / "0" / ".megaignore", root(*cu) / "root" / ".megaignore"); localFS.removenode(".megaignore"); localFS.movenode("0/.megaignore", ""); // All nodes except for 0/x are visible in the remote tree. remoteTree = localFS; remoteTree.removenode("0/x"); // Move 2/0/.megaignore to 2/.megaignore. localFS.movenode("2/0/.megaignore", "2"); remoteTree.movenode("2/0/.megaignore", "2"); fs::rename(root(*cu) / "root" / "2" / "0" / ".megaignore", root(*cu) / "root" / "2" / ".megaignore"); // Wait for synchronization. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterNameClash) { auto TIMEOUT = std::chrono::seconds(16); // Log in the client and clear the cloud. ASSERT_TRUE(cu->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); Model model; // Populate the local filesystem. model.addfolder("d"); model.addfile(".megaignore", "+sync:.megaignore"); model.addfile(".megaignore", "#")->fsName(".%6degaignore"); model.generate(cu->fsBasePath / "s"); // Add and start a sync. auto id = setupSync(*cu, "s", "s", false); ASSERT_NE(id, UNDEF); // Give the engine some time to detect the name conflicts. waitOnSyncs(cu); // Wait for the engine to detect the name conflicts. ASSERT_TRUE(cu->waitFor(SyncConflictState(true), TIMEOUT)); list conflicts; // Ask the engine what name conflicts it has detected. ASSERT_TRUE(cu->conflictsDetected(conflicts)); // Make sure the list of conflicts isn't empty. ASSERT_FALSE(conflicts.empty()); // Make sure the conflcit is due to the ignore files. const auto& conflict = conflicts.front(); // Detected at sync root? ASSERT_EQ(conflict.localPath.toPath(false), path_u8string((cu->fsBasePath / "s"))); // Two local name clashes detected? ASSERT_EQ(conflict.clashingLocalNames.size(), 2u); // Both ignore files? { auto isIgnoreFile = [](const LocalPath& name) { // No ambiguity, thanks. const LocalPath& ignoreFileName = IGNORE_FILE_NAME; // Compare the name, taking care to decode any escapes. return !platformCompareUtf(name, true, ignoreFileName, false); }; ASSERT_TRUE(isIgnoreFile(conflict.clashingLocalNames[0])); ASSERT_TRUE(isIgnoreFile(conflict.clashingLocalNames[1])); } // Make sure we didn't upload the "d" directory. ASSERT_EQ(cu->drillchildnodebyname(cu->gettestbasenode(), "s/d"), nullptr); } TEST_F(LocalToCloudFilterFixture, FilterOverwritten) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.addfile("fu"); localFS.addfile("fx"); localFS.addfile("megaignore", "-:*u\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; remoteTree.removenode("fx"); // Log in to client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move megaignore over .megaignore fs::rename(root(*cu) / "root" / "megaignore", root(*cu) / "root" / ".megaignore"); localFS.removenode(".megaignore"); localFS.copynode("megaignore", ".megaignore"); localFS.removenode("megaignore"); // f[ux] should be visible in the remote tree. remoteTree = localFS; // Wait for synchronization to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, FilterRemoved) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.addfile("fx"); localFS.generate(root(*cu) / "root"); // Setup remote trees. remoteTree = localFS; remoteTree.removenode("fx"); // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for and confirm sync. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Remove filter from FS. localFS.removenode(".megaignore"); ASSERT_TRUE(fs::remove(root(*cu) / "root" / ".megaignore")); // fx should be present in remote tree. remoteTree = localFS; // Wait for and confirm sync. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, MoveToIgnoredRubbishesRemote) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("1/.megaignore", "-:f\nexclude-larger:1\n\n+sync:.megaignore"); localFS.addfile("0/f", "f"); localFS.addfile("0/g", "gg"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Ensure remote debris is clear. ASSERT_TRUE(cu->deleteremotedebris()); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Move 0/f to 1/f. fs::rename(root(*cu) / "root" / "0" / "f", root(*cu) / "root" / "1" / "f"); localFS.movenode("0/f", "1"); // Move 0/g to 1/g. fs::rename(root(*cu) / "root" / "0" / "g", root(*cu) / "root" / "1" / "g"); localFS.movenode("0/g", "1"); // Neither 0/f or 1 are present in local or remote tree. remoteTree = localFS; remoteTree.removenode("1/f"); remoteTree.removenode("1/g"); // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Verify that 0/f was moved into the remote debris. std::shared_ptr u = cu->drillchildnodebyname(cu->getcloudrubbishnode(), debrisFilePath("f")); ASSERT_TRUE(u); } TEST_F(LocalToCloudFilterFixture, OverwriteExcluded) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile("d/f"); localFS.addfile("d/.megaignore", "+:f\n+sync:.megaignore"); localFS.addfile("f"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Remote tree is consistent with FS. remoteTree = localFS; // Log in client. ASSERT_TRUE(cdu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Add ignore file. localFS.addfile(".megaignore", "-:f\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Remote is consistent with FS. remoteTree = localFS; // Wait for sync to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Move x/d/f (where it is not ignored) to x/f (where it is ignored) (overwriting the f that was there already). fs::rename(root(*cdu) / "root" / "d" / "f", root(*cdu) / "root" / "f"); // Update models. localFS.removenode("f"); localFS.movenode("d/f", ""); remoteTree.removenode("d/f"); // Wait for sync to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(LocalToCloudFilterFixture, RenameToIgnoredRubbishesRemote) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:x\n+sync:.megaignore"); localFS.addfile("u"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Ensure remote debris is clear. ASSERT_TRUE(cu->deleteremotedebris()); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete and confirm models. waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Rename u to x. fs::rename(root(*cu) / "root" / "u", root(*cu) / "root" / "x"); localFS.copynode("u", "x"); localFS.removenode("u"); // u is no longer present in remote tree. remoteTree.removenode("u"); // Wait for sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Confirm models. ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); // Verify that u was moved into the remote debris. std::shared_ptr u = cu->drillchildnodebyname(cu->getcloudrubbishnode(), debrisFilePath("u")); ASSERT_TRUE(u); } TEST_F(LocalToCloudFilterFixture, RenameReplaceIgnoreFile) { // Log in client. ASSERT_TRUE(cu->makeCloudSubdirs("cu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cu, "root", "cu", false); ASSERT_NE(id, UNDEF); auto root = cu->syncSet(id).localpath; // Populate local filesystem. Model model; model.addfile("d0/.megaignore", "+sync:.megaignore"); model.addfile("d1/.megaignore", "+sync:.megaignore"); model.addfile(".megaignore", "+:.*\n+sync:.megaignore"); model.generate(root); // Wait for initial sync to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Make sure our hierarchy made it the cloud. ASSERT_TRUE(confirm(*cu, id, model)); // Rename/Replace d0/.megaignore { // Rename .megaignore -> f model.addfile("d0/f", "+sync:.megaignore"); fs::rename(root / "d0" / ".megaignore", root / "d0" / "f"); // Replace .megaignore model.findnode("d0/.megaignore")->content = "-:x\n+sync:.megaignore"; ASSERT_TRUE(createFile(root / "d0" / ".megaignore", "-:x\n+sync:.megaignore")); } // Wait for synchronization to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Did the changes make it to the cloud? ASSERT_TRUE(confirm(*cu, id, model)); // Rename/Replace d1/.megaignore { // Rename .megaignore -> .f model.addfile("d1/.f", "+sync:.megaignore"); fs::rename(root / "d1" / ".megaignore", root / "d1" / ".f"); // Replace .megaignore model.findnode("d1/.megaignore")->content = "-:y\n+sync:.megaignore"; ASSERT_TRUE(createFile(root / "d1" / ".megaignore", "-:y\n+sync:.megaignore")); } // Wait for synchronization to complete. cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); // Did the changes make it to the cloud? ASSERT_TRUE(confirm(*cu, id, model)); // Make sure the ignore files were actually reloaded. LocalFSModel localFS = model; RemoteNodeModel remoteTree = model; localFS.addfile("d0/x", "x"); localFS.addfile("d1/y", "y"); ASSERT_TRUE(createFile(root / "d0" / "x", "x")); ASSERT_TRUE(createFile(root / "d1" / "y", "y")); cu->triggerPeriodicScanEarly(id); waitOnSyncs(cu); ASSERT_TRUE(confirm(*cu, id, localFS)); ASSERT_TRUE(confirm(*cu, id, remoteTree)); } class CloudToLocalFilterFixture : public FilterFixture { public: string debrisFilePath(const string& path) const { return FilterFixture::debrisFilePath(MEGA_DEBRIS_FOLDER, path); } }; /* CloudToLocalFilterFixture */ TEST_F(CloudToLocalFilterFixture, DoesntDownloadIgnoredNodes) { RemoteNodeModel remoteTree; // Set up cloud. { // Convenience. auto lRoot = root(*cu) / "x"; auto rRoot = cu->gettestbasenode(); // Populate filesystem. // (fe = file excluded, fi = file included) remoteTree.addfile(".megaignore", "-:f\n+sync:.megaignore"); remoteTree.addfile("d/f"); remoteTree.addfile("d/g"); remoteTree.addfile("db/.megaignore", "exclude-smaller:8\nexclude-larger:16"); remoteTree.addfile("db/fe0", randomData(7)); remoteTree.addfile("db/fe1", randomData(17)); remoteTree.addfile("db/fi0", randomData(8)); remoteTree.addfile("db/fi1", randomData(16)); remoteTree.addfile("dl/.megaignore", "exclude-smaller:16"); remoteTree.addfile("dl/fe", randomData(15)); remoteTree.addfile("dl/fi", randomData(16)); remoteTree.addfile("du/.megaignore", "exclude-larger:16"); remoteTree.addfile("du/fe", randomData(17)); remoteTree.addfile("du/fi", randomData(16)); remoteTree.addfile("dr/.megaignore", "exclude-smaller:16\nexclude-larger:8"); // exclude in-range [8-16] remoteTree.addfile("dr/fe0", randomData(8)); remoteTree.addfile("dr/fe1", randomData(16)); remoteTree.addfile("dr/fe2", randomData(9)); remoteTree.addfile("dr/fe3", randomData(15)); remoteTree.addfile("dr/fi0", randomData(7)); remoteTree.addfile("dr/fi1", randomData(17)); remoteTree.addfile("dr/fi2", randomData(0)); remoteTree.addfile("f"); remoteTree.addfile("g"); remoteTree.generate(lRoot); // Create directories. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); // Upload files. ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it to the cloud. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Set up models. LocalFSModel localFS = remoteTree; localFS.removenode("d/f"); localFS.removenode("db/fe0"); localFS.removenode("db/fe1"); localFS.removenode("dl/fe"); localFS.removenode("dr/fe0"); localFS.removenode("dr/fe1"); localFS.removenode("dr/fe2"); localFS.removenode("dr/fe3"); localFS.removenode("du/fe"); localFS.removenode("f"); // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Change du/fi's size such that it violates the size filter. { auto data = randomData(24); localFS.addfile("du/fi", data); remoteTree.addfile("du/fi", data); ASSERT_TRUE(createFile(root(*cd) / "root" / "du" / "fi", data)); } // Wait for the change to hit the cloud. waitOnSyncs(cd); // Everything as we'd expect? ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Make sure cdu is aware of du/fi. ASSERT_TRUE(cdu->waitFor(SyncRemoteMatch("x", remoteTree.root.get()), DEFAULTWAIT)); // Remove du/fi in the cloud. { remoteTree.removenode("du/fi"); ASSERT_TRUE(cdu->deleteremote("x/du/fi")); } // Removal should propagate down. localFS.removenode("du/fi"); // Wait for the change to propagate. waitOnSyncs(cd); // Everything as we'd expect? ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, DoesntMoveIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("d/fx"); localFS.generate(root(*cdu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "cdu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Filter out fx. localFS.addfile(".megaignore", "-:*x\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // fx should remain in the cloud. remoteTree = localFS; // Wait for sync to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Move cdu/d/fx to cdu/fx. { // Make sure cu is aware of d/fx. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cu->waitFor(std::move(predicate), DEFAULTWAIT)); // Move the node. ASSERT_TRUE(cu->movenode("cdu/d/fx", "cdu")); // Update models. remoteTree.movenode("d/fx", ""); } // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, DoesntRenameIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("x"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "cdu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Filter out fx. localFS.addfile(".megaignore", "-:x\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // x should remain in the cloud. remoteTree = localFS; // Wait for sync to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Rename cdu/x to cdu/y. { // Make sure cu is aware of x. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cu->waitFor(std::move(predicate), DEFAULTWAIT)); // Rename x to y. ASSERT_TRUE(cu->setattr("cdu/x", attr_map('n', "y"))); } // Update models. localFS.addfile("y", "x"); remoteTree.copynode("x", "y"); remoteTree.removenode("x"); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, DoesntRubbishIgnoredNodes) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.addfile("x"); localFS.generate(root(*cdu) / "root"); // Setup remote note tree. remoteTree = localFS; // Log in the client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "cdu", false); ASSERT_NE(id, UNDEF); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Filter out fx. localFS.addfile(".megaignore", "-:x\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // x should remain in the cloud. remoteTree = localFS; // Wait for sync to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Remove cdu/x. { // Make sure cu is aware of x. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cu->waitFor(std::move(predicate), DEFAULTWAIT)); // Delete x. ASSERT_TRUE(cu->deleteremote("cdu/x")); } // Update models. remoteTree.removenode("x"); // Wait for sync to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, DoesntUploadIgnoredNodes) { const string ignoreFile = "-:da\n-:f*\nexclude-smaller:2\n\n+sync:.megaignore"; // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Populate filesystem. model.addfile(".megaignore", ignoreFile); model.generate(lRoot); // Create directories. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); // Upload files. ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Wait for everyone to agree on the state of the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Set up models. LocalFSModel localFS; RemoteNodeModel remoteTree; localFS.addfile("da/f.txt", "daf"); localFS.addfile("da/g.txt", "dag"); localFS.addfile("db/f.txt", "dbf"); localFS.addfile("db/g.txt", "dbg"); localFS.addfile("f.txt", "rf"); localFS.addfile("g.txt", "rg"); localFS.addfile("h.txt", "!"); localFS.generate(root(*cd) / "root"); localFS.addfile(".megaignore", ignoreFile); remoteTree = localFS; remoteTree.removenode("da"); remoteTree.removenode("db/f.txt"); remoteTree.removenode("f.txt"); remoteTree.removenode("h.txt"); // Add and start sync. auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, ExcludedIgnoreFile) { Model model; // Populate cloud. { // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Populate filesystem. model.addfile("0/.megaignore", "-:f\n+sync:.megaignore"); model.addfile("0/f"); model.addfile("1/.megaignore", "#"); model.addfile(".megaignore", "-:1\n-:.megaignore\n+sync:.megaignore"); model.generate(lRoot); // Create directories. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); // Upload files ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Wait for the clients to agree on the cloud's state. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Local model should exclude /0/f and /1. localFS = model; localFS.removenode("0/f"); localFS.removenode("1"); // Remote model should be unchanged. remoteTree = model; // Make sure local sync root exists. fs::create_directories(root(*cdu) / "root"); // Add and start sync. const auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for initial sync to complete. waitOnSyncs(cdu); // Did we download what we expected? ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Make some remote changes. { // Should allow download of 1. localFS.removenode(".megaignore"); remoteTree.removenode(".megaignore"); ASSERT_TRUE(cd->deleteremote("x/.megaignore")); localFS.addfile("1/.megaignore", "#"); } // Wait for remote changes to reach us. waitOnSyncs(cdu); // Everything as we expect? ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterAdded) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local fs. localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cu)); localFS.addfile("d/x"); localFS.generate(root(*cu) / "root"); // Setup remote tree. remoteTree = localFS; // Log in "upload" client. ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Upload the initial ignore file. // // This is to avoid a race between clients. ASSERT_TRUE(cu->uploadFile(root(*cu) / ".megaignore", "x")); // Wait for cd to become aware of the new ignore file. { auto predicate = SyncRemoteNodePresent("x/.megaignore"); ASSERT_TRUE(cd->waitFor(std::move(predicate), DEFAULTWAIT)); } // Add and start syncs. auto cuId = setupSync(*cu, "root", "x", false); ASSERT_NE(cuId, UNDEF); auto cdId = setupSync(*cd, "root", "x", false); ASSERT_NE(cdId, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd, cu); // Confirm models. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Add d/.megaignore to "upload" client. localFS.addfile("d/.megaignore", "-:x\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // d/.megaignore's now in the cloud. remoteTree = localFS; // Wait for synchronization to complete. cu->triggerPeriodicScanEarly(cuId); waitOnSyncs(cu, cd); // Confirm models. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Remove x/d/x in the cloud. { // Wait for cdu to become aware of d/x. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(std::move(predicate), DEFAULTWAIT)); // Remove d/x. ASSERT_TRUE(cdu->deleteremote("x/d/x")); // Update model. remoteTree.removenode("d/x"); } // Wait for sync to complete. waitOnSyncs(cu, cd); // x/x should remain locally. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterChanged) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile(".megaignore", "-:x\n+sync:.megaignore"); localFS.generate(root(*cu)); localFS.addfile("x"); localFS.addfile("y"); localFS.generate(root(*cu) / "root"); // Set up remote tree. remoteTree = localFS; remoteTree.removenode("x"); // Log in the "uploader" client. ASSERT_TRUE(cu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Upload the initial ignore file. // // This is to avoid a race between clients. ASSERT_TRUE(cu->uploadFile(root(*cu) / ".megaignore", "x")); // Wait for cd to become aware of the ignore file. { auto predicate = SyncRemoteNodePresent("x/.megaignore"); ASSERT_TRUE(cd->waitFor(std::move(predicate), DEFAULTWAIT)); } // Add and start syncs. auto cuId = setupSync(*cu, "root", "x", false); ASSERT_NE(cuId, UNDEF); fs::create_directories(root(*cd) / "root"); auto cdId = setupSync(*cd, "root", "x", false); ASSERT_NE(cdId, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cu, cd); // Confirm models. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); // x is not present under cd. localFS.removenode("x"); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Update ignore file on uploader side. localFS.addfile(".megaignore", "-:y\n+sync:.megaignore"); localFS.generate(root(*cu) / "root"); // Update models. localFS.addfile("x"); // Remote contains everything. remoteTree = localFS; // Wait for synchronization to complete. cu->triggerPeriodicScanEarly(cuId); waitOnSyncs(cu, cd); // Confirm models. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Delete x/y in the cloud. { // Make sure cdu is up to date with respect to the cloud. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(std::move(predicate), DEFAULTWAIT)); // Remove x/y. ASSERT_TRUE(cdu->deleteremote("x/y")); // Update model. remoteTree.removenode("y"); } // Wait for synchronization to complete. waitOnSyncs(cd, cu); // x/y should remain locally. ASSERT_TRUE(confirm(*cu, cuId, localFS)); ASSERT_TRUE(confirm(*cu, cuId, remoteTree)); ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterDeferredChange) { Model model; // Convenience. const auto cuLocalRoot = root(*cu) / "x"; const auto cuCloudRoot = cu->gettestbasenode(); // Set up remote model. model.addfile(".megaignore", "-:d\n+sync:.megaignore"); model.addfile("d/.megaignore", "-:x\n+sync:.megaignore"); model.addfile("d/x"); model.addfile("d/y"); model.generate(cuLocalRoot); // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(cuLocalRoot, cuCloudRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(cuLocalRoot, cuCloudRoot.get())); // Wait for cd to receive cu's changes. { auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); } // Set up local FS. LocalFSModel localFS; localFS.addfile(".megaignore", "-:d\n+sync:.megaignore"); // Set up remote model. RemoteNodeModel remoteTree = model; // Add sync and start sync. fs::create_directories(root(*cd) / "root"); auto cdId = setupSync(*cd, "root", "x", false); ASSERT_NE(cdId, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Change x/d/.megaignore to include x and exclude y. { // Make sure cu's seen cd's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cu->waitFor(std::move(predicate), DEFAULTWAIT)); // Update the ignore file. model.addfile("d/.megaignore", "-:y\n+sync:.megaignore"); model.generate(cuLocalRoot); // Upload the updated file. ASSERT_TRUE(cu->uploadFile(cuLocalRoot / "d" / ".megaignore", "x/d", 30, ClaimOldVersion)); } // Wait for synchronization to complete. waitOnSyncs(cd); // Update models. remoteTree = model; // Confirm downloader models. ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); // Remove x/.megaignore so to allow x/d. model.removenode(".megaignore"); ASSERT_TRUE(cu->deleteremote("x/.megaignore")); // Wait for synchronization to complete. waitOnSyncs(cd); // Update models. localFS = model; localFS.removenode("d/y"); remoteTree = model; // Confirm downloader models. ASSERT_TRUE(confirm(*cd, cdId, localFS)); ASSERT_TRUE(confirm(*cd, cdId, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterMovedAcrossHierarchy) { // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Setup model. model.addfile("a/.megaignore", "-:fa\n+sync:.megaignore"); model.addfile("a/fa"); model.addfile("b/fa"); model.addfile(".megaignore", "+sync:.megaignore"); model.generate(lRoot); // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it to the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Make sure everyone agrees on the cloud's state. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("a/.megaignore", "-:fa\n+sync:.megaignore"); localFS.addfile("b/fa"); localFS.addfile(".megaignore", "+sync:.megaignore"); // Setup remote tree. remoteTree = localFS; remoteTree.addfile("a/fa"); // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Move x/a/.megaignore to x/b/.megaignore. { // Make sure cdu seems cd's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(std::move(predicate), DEFAULTWAIT)); // Move a/.megaignore to b. ASSERT_TRUE(cdu->movenode("x/a/.megaignore", "x/b")); } // Wait for sync. waitOnSyncs(cd); // Update models. // a/.megaignore -> b/.megaignore. // a/fa should become included. // b/fa should become excluded. localFS.addfile("a/fa"); localFS.movenode("a/.megaignore", "b"); remoteTree.movenode("a/.megaignore", "b"); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterMovedDownHierarchy) { // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Setup model. model.addfile(".megaignore", "-:fa\n+sync:.megaignore"); model.addfile("a/fa"); model.addfile("b/fa"); model.addfolder("c/d"); model.addfile("c/.megaignore", "-:.megaignore\n+sync:.megaignore"); model.generate(lRoot); // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it to the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Make sure everyone agrees on the cloud's state. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:fa\n+sync:.megaignore"); localFS.addfolder("a"); localFS.addfolder("b"); localFS.addfolder("c/d"); localFS.addfile("c/.megaignore", "-:.megaignore\n+sync:.megaignore"); // Setup remote tree. remoteTree = localFS; remoteTree.addfile("a/fa"); remoteTree.addfile("b/fa"); // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Move ignore files. { // Make sure cdu is up to date with cd's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(std::move(predicate), DEFAULTWAIT)); // Move x/.megaignore to x/a/.megaignore. ASSERT_TRUE(cdu->movenode("x/.megaignore", "x/a")); // Move x/c/.megaignore to x/c/d/.megaignore. ASSERT_TRUE(cdu->movenode("x/c/.megaignore", "x/c/d")); } // Wait for sync. waitOnSyncs(cd); // Update models. // .megaignore -> a/.megaignore. // c/.megaignore -> c/d/.megaignore. // a/fa should remain excluded. // b/fa should become included. localFS.addfile("b/fa"); localFS.movenode(".megaignore", "a"); localFS.movenode("c/.megaignore", "c/d"); remoteTree = localFS; remoteTree.addfile("a/fa"); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterMovedIntoExcluded) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Setup models. model.addfile(".megaignore", "-:d*\n-:f*\n\n+sync:.megaignore"); localFS = model; model.addfile("d/g"); model.addfile("f"); model.generate(lRoot); remoteTree = model; // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it to the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Make sure everyone agrees on the cloud's state. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Move x/.megaignore into x/d/.megaignore. { // Make sure cdu sees cd's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); // Move the ignore file. ASSERT_TRUE(cdu->movenode("x/.megaignore", "x/d")); // Update the model. remoteTree.movenode(".megaignore", "d"); } // Wait for sync to complete. waitOnSyncs(cd); // Confirm models. localFS = remoteTree; ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterMovedUpHierarchy) { // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Setup model. model.addfile("a/.megaignore", "-:fa\n+sync:.megaignore"); model.addfile("a/fa"); model.addfile("b/fa"); model.addfile("c/d/.megaignore", "-:.megaignore\n+sync:.megaignore"); model.addfile(".megaignore", "+sync:.megaignore"); model.generate(lRoot); // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it to the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Make sure everyone's seeing the same thing. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile("a/.megaignore", "-:fa\n+sync:.megaignore"); localFS.addfile("b/fa"); localFS.addfile("c/d/.megaignore", "-:.megaignore\n+sync:.megaignore"); localFS.addfile(".megaignore", "+sync:.megaignore"); // Setup remote tree. remoteTree = localFS; remoteTree.addfile("a/fa"); // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Move x/a/.megaignore to x/.megaignore. { auto pauser = makeScopedSyncPauser(*cd, id); // Move x/a/.megaignore to x/.megaignore. localFS.removenode(".megaignore"); localFS.copynode("a/.megaignore", ".megaignore"); ASSERT_TRUE(cdu->copy("x/a/.megaignore", "x", ClaimOldVersion)); // Remove x/a/.megaignore. localFS.removenode("a/.megaignore"); remoteTree.removenode("a/.megaignore"); ASSERT_TRUE(cdu->deleteremote("x/a/.megaignore")); // Wait for everyone to agree on the cloud's state. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for the engine to process the changes. waitOnSyncs(cd); // Check that the ignore file has been "moved." ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); { auto pauser = makeScopedSyncPauser(*cd, id); remoteTree.removenode("b/fa"); // Remove x/b/fa. // // The change shouldn't be actioned as the file became excluded by // the "move" above. ASSERT_TRUE(cdu->deleteremote("x/b/fa")); // Move x/cd/.megaignore up a level. // // Change should be actioned as ignore files cannot be excluded. localFS.movenode("c/d/.megaignore", "c"); remoteTree.movenode("c/d/.megaignore", "c"); ASSERT_TRUE(cdu->movenode("x/c/d/.megaignore", "x/c")); // Wait for all clients to see the changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for the engine to process above changes. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, FilterNameClash) { auto TIMEOUT = std::chrono::seconds(16); // Set up cloud. { // Create the cloud root. ASSERT_TRUE(cdu->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Convenience. auto ignoreFilePath = cdu->fsBasePath / ".megaignore"; // Create a dummy ignore file for us to upload. ASSERT_TRUE(createFile(ignoreFilePath, "#")); // Upload the ignore file, twice. // This is so we have a cloud name clash. ASSERT_TRUE(cdu->uploadFile(ignoreFilePath, "s")); ASSERT_TRUE(cdu->uploadFile(ignoreFilePath, "s")); // Wait for everyone to see the new ignore file. auto predicate = SyncRemoteNodePresent("s/.megaignore"); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Make sure local root exists. fs::create_directories(cdu->fsBasePath / "s"); // Create a directory for the engine to synchronize. fs::create_directories(cdu->fsBasePath / "s" / "d"); // Add and start a new sync. auto id = setupSync(*cdu, "s", "s", false); ASSERT_NE(id, UNDEF); // Give the engine some time to detect the name conflict. waitOnSyncs(cdu); // Wait for the engine to detect a name conflict. ASSERT_TRUE(cdu->waitFor(SyncConflictState(true), TIMEOUT)); std::list conflicts; // Ask the engine which name conflicts were detected. ASSERT_TRUE(cdu->conflictsDetected(conflicts)); // Make sure the list of conflicts isn't empty. ASSERT_FALSE(conflicts.empty()); // Make sure the conflicts are due to the ignore files. const auto& conflict = conflicts.front(); // Detected at the sync root? ASSERT_EQ(conflict.cloudPath, "/mega_test_sync/s"); // Two remote name clashes detected? ASSERT_EQ(conflict.clashingCloud.size(), 2u); // Both ignore files? ASSERT_EQ(conflict.clashingCloud[0].name, IGNORE_FILE_NAME); ASSERT_EQ(conflict.clashingCloud[1].name, IGNORE_FILE_NAME); // Make sure the engine didn't upload the "d" directory. ASSERT_EQ(cdu->drillchildnodebyname(cdu->gettestbasenode(), "s/d"), nullptr); } TEST_F(CloudToLocalFilterFixture, FilterNameClashMultiple) { auto TIMEOUT = std::chrono::seconds(16); // Set up cloud. { // Create the cloud root. ASSERT_TRUE(cdu->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Convenience. auto ignoreFilePath = cdu->fsBasePath / ".megaignore"; // Create a dummy ignore file for us to upload. ASSERT_TRUE(createFile(ignoreFilePath, "#")); // Upload the ignore file, twice. // This is so we have a cloud name clash. ASSERT_TRUE(cdu->uploadFile(ignoreFilePath, "s")); ASSERT_TRUE(cdu->uploadFile(ignoreFilePath, "s")); // Wait for everyone to see the new ignore file. auto predicate = SyncRemoteNodePresent("s/.megaignore"); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Make sure local root exists. fs::create_directories(cdu->fsBasePath / "s"); // Create a directory for the engine to synchronize. fs::create_directories(cdu->fsBasePath / "s" / "d"); // Add and start a new sync. auto id = setupSync(*cdu, "s", "s", false); ASSERT_NE(id, UNDEF); // Give the engine some time to detect the name conflict. waitOnSyncs(cdu); // Wait for the engine to detect a name conflict. ASSERT_TRUE(cdu->waitFor(SyncConflictState(true), TIMEOUT)); // Create a second name conflict. const string otherFile = "otherFile"; { // Convenience. auto otherFilePath = cdu->fsBasePath / otherFile; // Create a dummy file for us to upload. ASSERT_TRUE(createFile(otherFilePath, "&")); // Upload the other file, twice. // This is so we have another cloud name clash. ASSERT_TRUE(cdu->uploadFile(otherFilePath, "s")); ASSERT_TRUE(cdu->uploadFile(otherFilePath, "s")); // Wait for everyone to see the new other file. auto predicate = SyncRemoteNodePresent("s/otherFile"); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Give the engine some time to detect the new name conflict. waitOnSyncs(cdu); // Wait for the engine to detect a new name conflict. ASSERT_TRUE(cdu->waitFor(SyncTotalConflictsStateUpdate(true), TIMEOUT)); std::list conflicts; // Ask the engine which name conflicts were detected. ASSERT_TRUE(cdu->conflictsDetected(conflicts)); // Make sure there are two conflicts (for .megaignore and for otherFile). ASSERT_EQ(conflicts.size(), 2u); auto checkConflict = [](const NameConflict& conflict, const std::string& expectedFileName) { // Detected at the sync root? ASSERT_EQ(conflict.cloudPath, "/mega_test_sync/s"); // Two remote name clashes detected? ASSERT_EQ(conflict.clashingCloud.size(), 2u); // Both expected files? ASSERT_EQ(conflict.clashingCloud[0].name, expectedFileName); ASSERT_EQ(conflict.clashingCloud[1].name, expectedFileName); }; // Check the first conflict const auto& conflict = conflicts.front(); checkConflict(conflict, IGNORE_FILE_NAME); conflicts.pop_front(); // Check the second conflict const auto& conflict2 = conflicts.front(); checkConflict(conflict2, otherFile); // Make sure the engine didn't upload the "d" directory. ASSERT_EQ(cdu->drillchildnodebyname(cdu->gettestbasenode(), "s/d"), nullptr); } TEST_F(CloudToLocalFilterFixture, FilterRemoved) { // Set up cloud. { Model model; // Convenience. const auto lRoot = root(*cu) / "x"; const auto rRoot = cu->gettestbasenode(); // Setup model. model.addfile(".megaignore", "-:fa\n+sync:.megaignore"); model.addfile("fa"); model.generate(lRoot); // Upload tree. ASSERT_TRUE(cu->uploadFolderTree(lRoot, rRoot.get())); ASSERT_TRUE(cu->uploadFilesInTree(lRoot, rRoot.get())); // Make sure everything made it up to the cloud. auto predicate = SyncRemoteMatch("x", model.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Make sure everyone agrees on the cloud's state. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); } LocalFSModel localFS; RemoteNodeModel remoteTree; // Setup local FS. localFS.addfile(".megaignore", "-:fa\n+sync:.megaignore"); // Setup remote tree. remoteTree = localFS; remoteTree.addfile("fa"); // Add and start sync. fs::create_directories(root(*cd) / "root"); auto id = setupSync(*cd, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cd); // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); // Remove x/.megaignore. { // MAke sure cdu sees cd's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cdu->waitFor(std::move(predicate), DEFAULTWAIT)); // Remove x/.megaignore. ASSERT_TRUE(cdu->deleteremote("x/.megaignore")); } // Wait for sync. waitOnSyncs(cd); // Update models. // .megaignore -> gone. // fa should now be included. localFS.removenode(".megaignore"); localFS.addfile("fa"); remoteTree = localFS; // Confirm models. ASSERT_TRUE(confirm(*cd, id, localFS)); ASSERT_TRUE(confirm(*cd, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, MoveToIgnoredRubbishesRemote) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile("f"); localFS.addfile("g"); localFS.addfile("x/.megaignore", "exclude-smaller:2\n+sync:.megaignore"); localFS.addfile(".megaignore", "-:d\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Set up remote tree. remoteTree = localFS; // Log in client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "cdu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Create the directory d. { auto pauser = makeScopedSyncPauser(*cdu, id); vector nodes(1); cdu->prepareOneFolder(&nodes[0], "d", false); ASSERT_TRUE(cdu->putnodes("cdu", NoVersioning, std::move(nodes))); remoteTree.addfolder("d"); // Wait for all the clients to see cdu's change. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for the sync to process the new directory. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); { auto pauser = makeScopedSyncPauser(*cdu, id); // Move f to d/f. ASSERT_TRUE(cdu->movenode("cdu/f", "cdu/d")); // Move g to x/g. ASSERT_TRUE(cdu->movenode("cdu/g", "cdu/x")); // f and g should have been moved into the local debris. localFS.copynode("f", debrisFilePath("f")); localFS.removenode("f"); // f has moved to d/f in the cloud. remoteTree.movenode("f", "d"); localFS.copynode("g", debrisFilePath("g")); localFS.removenode("g"); // g has moved to x/g in the cloud. remoteTree.movenode("g", "x"); // Wait for all the clients to receive the changes. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS, false)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, OverwriteExcluded) { RemoteNodeModel remoteTree; // Set up local FS. LocalFSModel localFS; localFS.addfile("d/f"); localFS.addfile("d/.megaignore", "+:f\n+sync:.megaignore"); localFS.addfile("f"); localFS.addfile(".megaignore", "+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Cloud should be consistent with FS. remoteTree = localFS; // Log in client. ASSERT_TRUE(cdu->makeCloudSubdirs("x", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "x", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Add an ignore file. localFS.addfile(".megaignore", "-:f\n+sync:.megaignore"); localFS.generate(root(*cdu) / "root"); // Remote's consistent with FS. remoteTree = localFS; // Wait for synchronization to complete. cdu->triggerPeriodicScanEarly(id); waitOnSyncs(cdu); // Confirm. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Move x/d/f to x, overwriting x/f. { auto pauser = makeScopedSyncPauser(*cdu, id); // Make sure cd has seen cdu's changes. auto predicate = SyncRemoteMatch("x", remoteTree.root.get()); ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); // Get a fix on x/f. auto node = cd->drillchildnodebyname(cd->gettestbasenode(), "x/f"); ASSERT_TRUE(node); // Move x/d/f to x. ASSERT_TRUE(cd->movenode("x/d/f", "x")); // Remove original x/f. ASSERT_TRUE(cd->deleteremote(node.get())); // Update models. localFS.removenode("d/f"); remoteTree.removenode("f"); remoteTree.movenode("d/f", ""); // Wait for all the clients to see our changes. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(CloudToLocalFilterFixture, RenameToIgnoredRubbishesRemote) { LocalFSModel localFS; RemoteNodeModel remoteTree; // Set up local FS. localFS.addfile(".megaignore", "-:y\n+sync:.megaignore"); localFS.addfile("x"); localFS.generate(root(*cdu) / "root"); // Set up remote tree. remoteTree = localFS; // Log in client. ASSERT_TRUE(cdu->makeCloudSubdirs("cdu", 0, 0)); ASSERT_TRUE(CatchupClients(cd, cdu, cu)); // Add and start sync. auto id = setupSync(*cdu, "root", "cdu", false); ASSERT_NE(id, UNDEF); // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); // Rename cdu/x to cdu/y. { auto pauser = makeScopedSyncPauser(*cdu, id); // Make sure cu's seen cdu's changes. auto predicate = SyncRemoteMatch("cdu", remoteTree.root.get()); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); // Rename x to y. ASSERT_TRUE(cu->rename("cdu/x", "y")); // x will be moved into the local debris. localFS.copynode("x", debrisFilePath("x")); localFS.removenode("x"); // x has become y in the cloud. remoteTree.copynode("x", "y"); remoteTree.removenode("x"); // Make sure all clients have seen our changes. ASSERT_TRUE(cd->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cdu->waitFor(predicate, DEFAULTWAIT)); ASSERT_TRUE(cu->waitFor(predicate, DEFAULTWAIT)); } // Wait for synchronization to complete. waitOnSyncs(cdu); // Confirm models. ASSERT_TRUE(confirm(*cdu, id, localFS, false)); ASSERT_TRUE(confirm(*cdu, id, remoteTree)); } TEST_F(SyncTest, StallsWhenDownloadTargetHasLongName) { const string DIRECTORY_NAME(256, 'd'); const string FILE_NAME(256, 'f'); auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync. auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, c); // Create a directory for the engine to synchronize. { vector node(1); c->prepareOneFolder(&node[0], DIRECTORY_NAME, false); ASSERT_TRUE(c->putnodes("s", NoVersioning, std::move(node))); } // Wait for the engine to process remote changes. waitonsyncs(TIMEOUT, c); // Wait for the engine to stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Retrieve a list of stalls from the client.] SyncStallInfoTests stalls; ASSERT_TRUE(c->syncStallDetected(stalls)); // Make sure the stall record is populated. ASSERT_FALSE(stalls.local.empty()); ASSERT_TRUE(stalls.cloud.empty()); // Was the stall due to the directory with the insane name? { auto localPath = c->fsBasePath / "s" / DIRECTORY_NAME; auto cloudPath = "/mega_test_sync/s/" + DIRECTORY_NAME; ASSERT_EQ(stalls.local.begin()->first.toPath(false), path_u8string(localPath)); ASSERT_TRUE(stalls.local.begin()->second.localPath2.localPath.empty()); ASSERT_EQ(stalls.local.begin()->second.cloudPath1.cloudPath, cloudPath); ASSERT_EQ(stalls.local.begin()->second.reason, SyncWaitReason::CannotCreateFolder); ASSERT_EQ(stalls.local.begin()->second.localPath1.problem, PathProblem::NameTooLongForFilesystem); } // Does the stall resolve if we remove the directory? ASSERT_TRUE(c->deleteremote("s/" + DIRECTORY_NAME)); // Wait for the stall to resolve. ASSERT_TRUE(c->waitFor(SyncStallState(false), 2 * TIMEOUT)); // Wait for the engine to process remote changes. waitonsyncs(TIMEOUT, c); // Upload a file for the engine to synchronize. ASSERT_TRUE(c->uploadFile(c->fsBasePath / "s" / ".megaignore", string(FILE_NAME), "s")); // Give the engine some time to synchronize the file. waitonsyncs(TIMEOUT, c); // Wait for the engine to stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Retrieve a list of stalls from the client.] ASSERT_TRUE(c->syncStallDetected(stalls)); // Make sure the stall record is populated. ASSERT_FALSE(stalls.local.empty()); ASSERT_TRUE(stalls.cloud.empty()); // Was the stall due to the file with the insane name? { auto localPath = c->fsBasePath / "s" / FILE_NAME; auto cloudPath = "/mega_test_sync/s/" + FILE_NAME; auto& sr = stalls.local.begin()->second; ASSERT_TRUE(sr.localPath1.localPath.toPath(false).find(".getxfer") != string::npos); ASSERT_EQ(sr.localPath2.localPath.toPath(false), path_u8string(localPath)); ASSERT_EQ(sr.cloudPath1.cloudPath, cloudPath); ASSERT_EQ(sr.cloudPath2.cloudPath, string("")); ASSERT_EQ(sr.reason, SyncWaitReason::DownloadIssue); ASSERT_EQ(sr.localPath2.problem, PathProblem::NameTooLongForFilesystem); } // Does the stall resolve if we remove the file? ASSERT_TRUE(c->deleteremote("s/" + FILE_NAME)); // Wait for the stall to resolve. ASSERT_TRUE(c->waitFor(SyncStallState(false), 2 * TIMEOUT)); } TEST_F(SyncTest, StallsWhenMoveTargetHasLongName) { const string FILE_NAME(256, 'f'); auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Add and start sync-> auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Create a tree for the engine to synchronize. Model model; model.addfile("d/f"); model.generate(c->fsBasePath / "s"); // Wait for synchronization to complete. waitonsyncs(TIMEOUT, c); // Was the file uploaded? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Rename the file so that it has a really, really long name. ASSERT_TRUE(c->rename("s/d/f", FILE_NAME)); // Give the engine some time to react to remote changes. waitonsyncs(TIMEOUT, c); // Wait for the engine to stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Acquire stall records from the client. SyncStallInfoTests stalls; ASSERT_TRUE(c->syncStallDetected(stalls)); // Is the stall record populated? ASSERT_TRUE(stalls.local.empty()); ASSERT_FALSE(stalls.cloud.empty()); auto& sr = stalls.cloud.begin()->second; // Was the stall due to the rename? ASSERT_EQ(sr.localPath1.localPath.toPath(false), path_u8string((c->fsBasePath / "s" / "d" / "f"))); ASSERT_EQ(sr.localPath2.localPath.toPath(false), path_u8string((c->fsBasePath / "s" / "d" / FILE_NAME))); ASSERT_EQ(sr.cloudPath1.cloudPath, "/mega_test_sync/s/d/f"); ASSERT_EQ(sr.cloudPath2.cloudPath, "/mega_test_sync/s/d/" + FILE_NAME); ASSERT_EQ(sr.reason, SyncWaitReason::MoveOrRenameCannotOccur); ASSERT_EQ(sr.localPath2.problem, PathProblem::NameTooLongForFilesystem); // Correcting the name should resolve the stall. ASSERT_TRUE(c->rename("s/d/" + FILE_NAME, "ff")); // Wait for the stall to resolve. ASSERT_TRUE(c->waitFor(SyncStallState(false), 2 * TIMEOUT)); // Wait for the engine to complete the rename. waitonsyncs(TIMEOUT, c); // Was the rename propagated? model.findnode("d/f")->name = "ff"; ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Move (and rename) the file such that it has a long name. ASSERT_TRUE(c->movenode("s/d/ff", "s", FILE_NAME)); // Wait for the engine to process remote changes. waitonsyncs(TIMEOUT, c); // Wait for the engine to stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Retrieve stall records from the client. ASSERT_TRUE(c->syncStallDetected(stalls)); // Is the stall record actually populated? ASSERT_EQ(stalls.cloud.size(), static_cast(2)); ASSERT_TRUE(stalls.local.empty()); auto cloud_sr_it = stalls.cloud.begin(); auto& cloud_sr1 = cloud_sr_it->second; // First stall: DestinationPathInUnresolvedArea // Correct paths reported? ASSERT_EQ(cloud_sr1.cloudPath1.cloudPath, "/mega_test_sync/s/d/" + FILE_NAME); ASSERT_EQ(cloud_sr1.cloudPath2.cloudPath, "/mega_test_sync/s/" + FILE_NAME); ASSERT_EQ(cloud_sr1.localPath1.localPath.toPath(false), path_u8string((c->fsBasePath / "s" / "d" / "ff"))); // Correct reasons reported? ASSERT_EQ(cloud_sr1.reason, SyncWaitReason::MoveOrRenameCannotOccur); ASSERT_EQ(cloud_sr1.localPath2.problem, PathProblem::DestinationPathInUnresolvedArea); // Second stall: NameTooLongForFilesystem ++cloud_sr_it; auto& cloud_sr2 = cloud_sr_it->second; // Correct paths reported? ASSERT_EQ(cloud_sr2.localPath1.localPath.toPath(false), path_u8string((c->fsBasePath / "s" / "d" / "ff"))); ASSERT_EQ(cloud_sr2.localPath2.localPath.toPath(false), path_u8string((c->fsBasePath / "s" / FILE_NAME))); ASSERT_EQ(cloud_sr2.cloudPath1.cloudPath, "/mega_test_sync/s/d/ff"); ASSERT_EQ(cloud_sr2.cloudPath2.cloudPath, "/mega_test_sync/s/" + FILE_NAME); // Correct reasons reported? ASSERT_EQ(cloud_sr2.reason, SyncWaitReason::MoveOrRenameCannotOccur); ASSERT_EQ(cloud_sr2.localPath2.problem, PathProblem::NameTooLongForFilesystem); // Renaming the file to something sane should resolve the stall. ASSERT_TRUE(c->rename("s/" + FILE_NAME, "fff")); // Wait for the stall to be resolved. ASSERT_TRUE(c->waitFor(SyncStallState(false), 2 * TIMEOUT)); // Wait for the engine to process the move. waitonsyncs(TIMEOUT, c); // Did the engine action the move? model.findnode("d/ff")->name = "fff"; model.movenode("d/fff", ""); ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } // SDK-1515: Edit and move, move and edit files with same fsid. TEST_F(SyncTest, BasicSync_EditAndMove_MoveAndEdit) { // std::fylesystem::path const fs::path fsTestRoot = makeNewTestRoot(); // Path components const string testFolder = "TestBaseFolder"; const string origFolder = "OrigFolder"; const string destFolder = "DestFolder"; // Files const string editMoveFile = "editThenMoveFile.txt"; const string moveEditFile = "moveThenEditFile.txt"; const std::chrono::seconds TIMEOUT = std::chrono::seconds(15); StandardClientInUse actor = g_clientManager->getCleanStandardClient(0, fsTestRoot); // Log callbacks. actor->logcb = true; // Log in client. ASSERT_TRUE(actor->resetBaseFolderMulticlient()); ASSERT_TRUE(actor->makeCloudSubdirs(testFolder, 0, 0)); ASSERT_TRUE(CatchupClients(actor)); // Sync threads handle actorBackupId = actor->setupSync_mainthread( testFolder, testFolder, false, false ); ASSERT_NE(actorBackupId, UNDEF); Model actorModel; // Confirm model. actorModel.addfile(origFolder + "/" + editMoveFile); actorModel.addfile(origFolder + "/" + moveEditFile); actorModel.addfolder(destFolder); // Model hierarchy on FS actorModel.generate(actor->fsBasePath / testFolder ); waitonsyncs(TIMEOUT, actor); auto testRootNode = actorModel.findnode( "", nullptr ); ASSERT_NE(nullptr, testRootNode); ASSERT_TRUE( actor->confirmModel_mainthread( testRootNode , actorBackupId)); // Retrieve fsid from file to check it remains the same later // @param path fs path auto getFSID = [&](const fs::path path) -> handle { auto fsAccess = actor->client.fsaccess->newfileaccess(false); auto localPath = LocalPath::fromAbsolutePath(path.string()); fsAccess->fopen(localPath, OPEN_RDONLY, FSLogging::logExceptFileNotFound); return fsAccess->fsid; }; auto initialEditMoveFSId = getFSID(actor->fsBasePath / testFolder / origFolder / editMoveFile); ASSERT_NE(initialEditMoveFSId, UNDEF); auto initialMoveEditFSId = getFSID(actor->fsBasePath / testFolder / origFolder / moveEditFile); ASSERT_NE(initialMoveEditFSId, UNDEF); // Are distinct files ASSERT_NE( initialEditMoveFSId, initialMoveEditFSId ); // Update the content of file // @param modelNode to the file to change // @param fs path to file // @param aditionalContent to append to file auto updateFile = [](Model::ModelNode* modelNode, const fs::path& path, const string& additionalContent) { //auto fileModelNodePtr = actorModel.findnode( pathToFile ); std::ofstream alterFile(path, std::ios::app | std::ios::out); ASSERT_TRUE( alterFile.good()); alterFile << additionalContent; modelNode->content += additionalContent; size_t alteredFileSize = static_cast(alterFile.tellp()); alterFile.close(); ASSERT_EQ(modelNode->content.size(), alteredFileSize); }; // Modify the first testing file. Content on Model updateFile( actorModel.findnode( origFolder + "/" + editMoveFile), actor->fsBasePath / testFolder / origFolder / editMoveFile, "New content for first edit then move file" ); // std::filesystem move file on FS auto initialFilePath = actor->fsBasePath / testFolder / origFolder / editMoveFile; auto newFilePath = actor->fsBasePath / testFolder / destFolder / editMoveFile; fs::rename( initialFilePath, newFilePath ); // Move file on model ASSERT_TRUE( actorModel.movenode( origFolder + "/" + editMoveFile, destFolder )); // Second file move first then modify // std::filesystem move file on FS initialFilePath = actor->fsBasePath / testFolder / origFolder / moveEditFile; newFilePath = actor->fsBasePath / testFolder / destFolder / moveEditFile; fs::rename( initialFilePath, newFilePath ); //// Move file on model ASSERT_TRUE( actorModel.movenode( origFolder + "/" + moveEditFile, destFolder )); updateFile( actorModel.findnode(destFolder + "/" + moveEditFile), actor->fsBasePath / testFolder / destFolder / moveEditFile, "New content for second move then edit file" ); waitonsyncs(TIMEOUT, actor); ASSERT_TRUE( actor->confirmModel_mainthread( testRootNode , actorBackupId)); // Check the fsid of the test files are still the originals auto finalEditMoveFSId = getFSID(actor->fsBasePath / testFolder / destFolder / editMoveFile); ASSERT_EQ(initialEditMoveFSId, finalEditMoveFSId); auto finalMoveEditFSId = getFSID(actor->fsBasePath / testFolder / destFolder / moveEditFile); ASSERT_EQ(initialMoveEditFSId, finalMoveEditFSId); } TEST_F(SyncTest, BasicSync_RapidLocalChangesWhenUploadCompletes) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); auto c = g_clientManager->getCleanStandardClient(0, TESTROOT); ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Log callbacks. c->logcb = true; // Populate model and local filesystem. Model m; m.addfile(".megaignore", "+sync:.megaignore"); m.addfile("f", randomData(1024)); m.generate(c->fsBasePath / "s"); // Add and start a sync. auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, c); // Was the initial sync successful? ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); // Hook the transfer completed callback. // // The idea here is that we start hammering the engine with a bunch of // file change notifications, the goal of which is to confuse the engine // such that it no longer knows which side is "current." c->onTransferCompleted = [&c](Transfer*) { // Only call us once. c->onTransferCompleted = nullptr; }; // Make a local change for the sync to upload. m.addfile("f", randomData(1024)); m.generate(c->fsBasePath / "s"); // wait for transfer completion and then cause a whole lot more updates for (int i = 0; i < 20000; ++i) { if (c->onTransferCompleted == nullptr) break; WaitMillisec(1); } // Update the file in a tight loop, while the sync runs. for (auto i = 0; i < 32; ++i) { m.addfile("f", randomData(1024)); m.generate(c->fsBasePath / "s"); std::this_thread::sleep_for(std::chrono::milliseconds(500)); } // Wait for the engine to process the change. waitonsyncs(TIMEOUT, c); SyncStallInfoTests stalls; // Engine should not have stalled ASSERT_FALSE(c->syncStallDetected(stalls)); // check we ended up with the final file ASSERT_TRUE(c->confirmModel_mainthread(m.root.get(), id)); } TEST_F(SyncTest, MaximumTreeDepthBehavior) { auto TIMEOUT = std::chrono::seconds(16); // Allocate a new client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud is clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Get our hands on the cloud root. auto root = client->gettestbasenode(); ASSERT_NE(root, nullptr); // Create a deep hierarchy in the cloud. string remoteRootPath = "00"; { vector nodes(Sync::MAX_CLOUD_DEPTH - root->depth() - 2); client->prepareOneFolder(&nodes[0], "00", false); nodes[0].nodehandle = 1; nodes[0].parenthandle = UNDEF; for (auto i = 1u; i < nodes.size(); ++i) { string buffer(2, '0'); snprintf(&buffer[0], 3, "%02x", i); remoteRootPath.append(1, '/'); remoteRootPath.append(buffer); client->prepareOneFolder(&nodes[i], std::move(buffer), false); nodes[i].nodehandle = i + 1; nodes[i].parenthandle = i; } client->received_node_actionpackets = false; ASSERT_TRUE(client->putnodes(root->nodeHandle(), NoVersioning, std::move(nodes))); ASSERT_TRUE(client->waitForNodesUpdated(16)); } // Add and start a new sync. auto id = client->setupSync_mainthread("00", remoteRootPath, false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure we haven't stalled. SyncStallInfoTests stalls; ASSERT_FALSE(client->syncStallDetected(stalls)); // Add a new directory. // // Trying to synchronize this directory should trigger a stall as // doing so would violate the node depth limit. fs::create_directories(client->fsBasePath / "00" / "x"); // Wait for the engine to detect a stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); // Make sure the engine's actually recorded the stall. ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_FALSE(stalls.empty()); // Check that the stall is due to the deep hierarchy. ASSERT_EQ(stalls.local.begin()->second.reason, SyncWaitReason::SyncItemExceedsSupportedTreeDepth); // Removing the directory should correct the stall. fs::remove_all(client->fsBasePath / "00" / "x"); // Wait for the stall to resolve. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); } TEST_F(SyncTest, StallsWhenEncounteringHardLink) { auto TIMEOUT = std::chrono::seconds(16); // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the client's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure the sync root exists in the cloud. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate model (and local filesystem.) Model model; model.addfile("f0"); model.addfile("f1"); model.generate(client->fsBasePath / "s"); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure everything got uploaded. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Create a hard link to f0. LocalPath sourcePath; LocalPath targetPath; { auto source = client->fsBasePath / "s" / "f0"; auto target = client->fsBasePath / "s" / "f2"; ASSERT_NO_FATAL_FAILURE(client->createHardLink(source, target, sourcePath, targetPath)); } // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Wait for the engine to detect a stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); // Make sure we've actually stalled. SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); // Check that we've stalled for the right reason. ASSERT_FALSE(stalls.local.empty()); auto& sr = stalls.local.begin()->second; ASSERT_EQ(sr.localPath1.localPath, sourcePath); ASSERT_EQ(sr.localPath2.localPath, targetPath); ASSERT_EQ(sr.reason, SyncWaitReason::FileIssue); ASSERT_EQ(sr.localPath1.problem, PathProblem::DetectedHardLink); ASSERT_EQ(sr.localPath2.problem, PathProblem::DetectedHardLink); // Check if we can resolve the stall by removing the hardlink. ASSERT_NO_FATAL_FAILURE(client->unlinklocal(targetPath)); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Make sure nothing's changed in the cloud. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, MultipleStallsWhenEncounteringHardLink) { auto TIMEOUT = std::chrono::seconds(16); // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the client's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure the sync root exists in the cloud. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate model (and local filesystem.) Model model; model.addfile("f0"); model.addfile("f1"); model.addfile("d1/f2"); model.addfile("d2/f3"); model.addfile("d3/f4"); model.generate(client->fsBasePath / "s"); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure everything got uploaded. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Create a hard link to f0. LocalPath sourcePath; LocalPath targetPath; { const auto source = client->fsBasePath / "s" / "f0"; const auto target = client->fsBasePath / "s" / "d1" / "f5"; ASSERT_NO_FATAL_FAILURE(client->createHardLink(source, target, sourcePath, targetPath)); } // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Wait for the engine to detect a stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); ASSERT_TRUE(client->waitFor(SyncTotalStallsStateUpdate(false), TIMEOUT)); // First time stall state, the update flag should be unset // Make sure we've actually stalled. SyncStallInfoTests stalls; ASSERT_TRUE(client->stallsDetected(stalls)); // Check that we've stalled for the right reason. ASSERT_FALSE(stalls.local.empty()); auto& sr = stalls.local.begin()->second; ASSERT_EQ(sr.localPath1.localPath, sourcePath); ASSERT_EQ(sr.localPath2.localPath, targetPath); ASSERT_EQ(sr.reason, SyncWaitReason::FileIssue); ASSERT_EQ(sr.localPath1.problem, PathProblem::DetectedHardLink); ASSERT_EQ(sr.localPath2.problem, PathProblem::DetectedHardLink); // Create another hard link to f1. LocalPath sourcePath2; LocalPath targetPath2; { auto source = client->fsBasePath / "s" / "d2" / "f3"; auto target = client->fsBasePath / "s" / "d3" / "f6"; ASSERT_NO_FATAL_FAILURE(client->createHardLink(source, target, sourcePath2, targetPath2)); } // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Wait for the engine to detect a stall update ASSERT_TRUE(client->waitForSyncTotalStallsStateUpdateTrue(TIMEOUT)); // Make sure the stalls collection is updated stalls.clear(); ASSERT_TRUE(client->stallsDetected(stalls)); // Check that we've stalled for the right reason. ASSERT_EQ(stalls.local.size(), 2u); auto itLocalStalls = stalls.local.begin(); auto& sr2 = itLocalStalls->second; ASSERT_EQ(sr2.localPath1.localPath, sourcePath); ASSERT_EQ(sr2.localPath2.localPath, targetPath); ASSERT_EQ(sr2.reason, SyncWaitReason::FileIssue); ASSERT_EQ(sr2.localPath1.problem, PathProblem::DetectedHardLink); ASSERT_EQ(sr2.localPath2.problem, PathProblem::DetectedHardLink); ++itLocalStalls; auto& sr3 = itLocalStalls->second; ASSERT_EQ(sr3.localPath1.localPath, sourcePath2); ASSERT_EQ(sr3.localPath2.localPath, targetPath2); ASSERT_EQ(sr3.reason, SyncWaitReason::FileIssue); ASSERT_EQ(sr3.localPath1.problem, PathProblem::DetectedHardLink); ASSERT_EQ(sr3.localPath2.problem, PathProblem::DetectedHardLink); // The stalls update flag should be false by now ASSERT_TRUE(client->waitFor(SyncTotalStallsStateUpdate(false), TIMEOUT)); // Check if we can resolve the first stall by removing the hardlink. ASSERT_NO_FATAL_FAILURE(client->unlinklocal(targetPath)); // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Wait for the stall state to be updated. We should still have one stall (updated as we had two stalls before). ASSERT_TRUE(client->waitForSyncTotalStallsStateUpdateTrue(TIMEOUT)); // Make sure the stalls collection is updated stalls.clear(); ASSERT_TRUE(client->stallsDetected(stalls)); // Check that we've stalled for the right reason. ASSERT_EQ(stalls.local.size(), 1u); itLocalStalls = stalls.local.begin(); auto& sr4 = itLocalStalls->second; ASSERT_EQ(sr4.localPath1.localPath, sourcePath2); ASSERT_EQ(sr4.localPath2.localPath, targetPath2); ASSERT_EQ(sr4.reason, SyncWaitReason::FileIssue); ASSERT_EQ(sr4.localPath1.problem, PathProblem::DetectedHardLink); ASSERT_EQ(sr4.localPath2.problem, PathProblem::DetectedHardLink); // Check if we can resolve the second stall by removing the hardlink. ASSERT_NO_FATAL_FAILURE(client->unlinklocal(targetPath2)); // Wait for the engine to process our changes. waitonsyncs(TIMEOUT, client); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); ASSERT_TRUE(client->waitFor(SyncTotalStallsStateUpdate(false), TIMEOUT)); // Make sure nothing's changed in the cloud. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); } #ifndef _WIN32 TEST_F(SyncTest, ChangingDirectoryPermissions) { auto TIMEOUT = chrono::seconds(16); // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure the sync root exists in the cloud. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Prepare model (and local filesystem.) Model model; model.addfile("d/f"); model.generate(client->fsBasePath / "s"); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure everything made it to the cloud. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); const auto dPath = path_u8string((client->fsBasePath / "s" / "d")); // Use PermissionsHandler to handle directory permissions PermissionHandler dirPermissionHandler(dPath); ASSERT_TRUE(dirPermissionHandler.originalPermissionsAvailable()) << "Failed to retrieve original permissions for directory"; // Remove execute permissions from the directory ASSERT_TRUE(dirPermissionHandler.removePermissions(fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec)) << "Execution permissions could not be removed"; client->triggerPeriodicScanEarly(id); // Wait for the engine to detect a stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we've stalled for the right reason. SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_TRUE(stalls.cloud.empty()); ASSERT_EQ(stalls.local.size(), 1u); ASSERT_EQ(stalls.local.begin()->second.reason, SyncWaitReason::FileIssue); // Make sure d/f is still present in the cloud. ASSERT_TRUE(client->waitFor(SyncRemoteNodePresent("s/d/f"), TIMEOUT)); // Restore execute permissions to the directory. ASSERT_TRUE(dirPermissionHandler.restorePermissions()) << "Restoring execution permissions failed!!"; client->triggerPeriodicScanEarly(id); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); // Remove read permissions from the directory. ASSERT_TRUE(dirPermissionHandler.removePermissions(fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read)) << "Read permissions could not be removed"; client->triggerPeriodicScanEarly(id); // Wait for the engine to detect a stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); // Make sure we've stalled for the right reason. ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_TRUE(stalls.cloud.empty()); ASSERT_EQ(stalls.local.size(), 1u); ASSERT_EQ(stalls.local.begin()->second.reason, SyncWaitReason::FileIssue); // Make sure d/f is still present in the cloud. ASSERT_TRUE(client->waitFor(SyncRemoteNodePresent("s/d/f"), TIMEOUT)); // Restore read permissions. ASSERT_TRUE(dirPermissionHandler.restorePermissions()) << "Restoring read permissions failed!!"; client->triggerPeriodicScanEarly(id); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); } TEST_F(SyncTest, StallsOnSpecialFile) { auto TIMEOUT = chrono::seconds(16); // Allocate a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure a sync root exists in the cloud. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Prepare model (local filesystem.) Model model; model.generate(client->fsBasePath / "s"); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure everything's as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Create a pipe for the engine to stall on. const auto fPath = path_u8string((client->fsBasePath / "s" / "f")); ASSERT_EQ(mkfifo(fPath.c_str(), S_IRUSR | S_IWUSR), 0); #ifdef __APPLE__ // Trigger another event so that we detect the FIFO. model.addfile("g"); model.generate(client->fsBasePath / "s"); #endif // __APPLE__ client->triggerPeriodicScanEarly(id); // Wait for the engine to stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); // Check that it stalled due to the pipe. SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_TRUE(stalls.cloud.empty()); ASSERT_EQ(stalls.local.size(), 1u); ASSERT_EQ(stalls.local.begin()->second.reason, SyncWaitReason::FileIssue); // Remove the pipe. ASSERT_EQ(unlink(fPath.c_str()), 0); client->triggerPeriodicScanEarly(id); // Wait for the stall to resolve. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); } #endif // ! _WIN32 TEST_F(SyncTest, ExistingCloudMoveTargetMovedToDebrisWhenSynced) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Populate local tree. Model model; model.addfile(".megaignore", "+sync:.megaignore"); model.addfile("fx"); model.addfile("fy"); model.generate(c->fsBasePath / "s"); // Add and start sync-> auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Make sure initial sync succeeded. ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); // Pause the sync so the changes below are seen as being atomic-> ASSERT_TRUE(c->setSyncPausedByBackupId(id, true)); // Rename fx -> fy. { // Get our hands on the original fy. auto root = c->gettestbasenode(); ASSERT_NE(root, nullptr); auto fy = c->drillchildnodebyname(root, "s/fy"); ASSERT_NE(fy, nullptr); // Rename fx -> fy. model.movetosynctrash("fy", ""); model.findnode("fx")->name = "fy"; c->received_node_actionpackets = false; ASSERT_TRUE(c->rename("s/fx", "fy")); ASSERT_TRUE(c->waitForNodesUpdated(16)); // Remove original fy. c->received_node_actionpackets = false; ASSERT_TRUE(c->deleteremote(fy.get())); ASSERT_TRUE(c->waitForNodesUpdated(16)); } // Unpause the sync-> ASSERT_TRUE(c->setSyncPausedByBackupId(id, false)); // Wait for engine to process changes. waitonsyncs(TIMEOUT, c); // Did we move the original fy into the debris? ASSERT_TRUE(c->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, StallsWhenExistingCloudMoveTargetUnknown) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Populate local tree. Model local; local.addfile(".megaignore", "+sync:.megaignore"); local.addfile("fx"); local.generate(c->fsBasePath / "s"); // Add and start sync-> auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Make sure initial sync succeeded. ASSERT_TRUE(c->confirmModel_mainthread(local.root.get(), id)); // Pause the sync so below changes appear atomically. ASSERT_TRUE(c->setSyncPausedByBackupId(id, true)); // Make sure we receive action packets. c->received_node_actionpackets = false; // Rename fx to fy. ASSERT_TRUE(c->rename("s/fx", "fy")); ASSERT_TRUE(c->waitForNodesUpdated(16u)); // Add foreign fy. local.addfile("fy"); local.generate(c->fsBasePath / "s"); // The paused sync should have that LocalNode marked as needing scanning WaitMillisec(1000); // Unpause the sync so that the engine processes our changes. ASSERT_TRUE(c->setSyncPausedByBackupId(id, false)); // Wait for the engine to process the changes. waitonsyncs(TIMEOUT, c); // Wait for the engine to report a stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Make sure we stalled for the reason we expect. SyncStallInfoTests stalls; ASSERT_TRUE(c->syncStallDetected(stalls)); // Correct number of stalls? ASSERT_EQ(stalls.local.size(), 1u); // Make sure the move hasn't occured on the local filesystem. ASSERT_TRUE(fs::exists(c->fsBasePath / "s" / "fx")); } TEST_F(SyncTest, StallsWhenExistingCloudMoveTargetUnsynced) { auto TESTROOT = makeNewTestRoot(); auto TIMEOUT = std::chrono::seconds(8); StandardClientInUse c = g_clientManager->getCleanStandardClient(0, TESTROOT); // Log callbacks. c->logcb = true; // Log in client. ASSERT_TRUE(c->resetBaseFolderMulticlient()); ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); ASSERT_TRUE(CatchupClients(c)); // Populate local tree. Model local; local.addfile(".megaignore", "+sync:.megaignore"); local.addfile("fx"); local.addfile("fy"); local.generate(c->fsBasePath / "s"); // Create a file for later use outside of the sync tree. ASSERT_TRUE(createFile(c->fsBasePath / "fy", "fy")); // Add and start sync-> auto id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for initial sync to complete. waitonsyncs(TIMEOUT, c); // Make sure the sync succeeded. ASSERT_TRUE(c->confirmModel_mainthread(local.root.get(), id)); // So that the engine perceives below changes as atomic-> ASSERT_TRUE(c->setSyncPausedByBackupId(id, true)); // Remotely rename fx -> fy. { // Get our hands on the original fy. auto root = c->gettestbasenode(); ASSERT_NE(root, nullptr); auto fy = c->drillchildnodebyname(root, "s/fy"); ASSERT_NE(fy, nullptr); // Rename fx -> fy. c->received_node_actionpackets = false; ASSERT_TRUE(c->rename("s/fx", "fy")); ASSERT_TRUE(c->waitForNodesUpdated(16)); // Remove original fy. c->received_node_actionpackets = false; ASSERT_TRUE(c->deleteremote(fy.get())); ASSERT_TRUE(c->waitForNodesUpdated(16)); } // Locally move external fy into place. fs::rename(c->fsBasePath / "fy", c->fsBasePath / "s" / "fy"); // The paused sync should have that LocalNode marked as needing scanning WaitMillisec(1000); // Unpause the sync so the engine processes our changes. ASSERT_TRUE(c->setSyncPausedByBackupId(id, false)); // Wait for engine to process changes. waitonsyncs(TIMEOUT, c); // Wait for engine to stall. ASSERT_TRUE(c->waitFor(SyncStallState(true), TIMEOUT)); // Retrieve stalls from the engine. SyncStallInfoTests stalls; ASSERT_TRUE(c->syncStallDetected(stalls)); // Correct number of stalls? ASSERT_EQ(stalls.local.size(), 1u); // Make sure we haven't moved fx on the local filesystem. ASSERT_TRUE(fs::exists(c->fsBasePath / "s" / "fx")); } #ifndef NDEBUG TEST_F(SyncTest, MovedSyncedFileWhileDownloadInProgress) { // checks upload also: // - establish file f synced in the root of a sync // - update f in the cloud, causing the sync to download it // - simultaneous with the download, locally move f to d/f inside the sync // we expect the updated f's download to end up at d/f locally auto TIMEOUT = std::chrono::seconds(16); promise waiter; // New client with folder s that will be the sync auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); ASSERT_TRUE(client->resetBaseFolderMulticlient()); ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Prepare the sync contents: s/d (directory) and s/f (file) Model model; model.addfolder("d"); model.addfile("f", randomData(16384)); model.generate(client->fsBasePath / "s"); // Keep a log of expected file fingerprints. vector fingerprints; fingerprints.emplace_back(client->fingerprint(client->fsBasePath / "s" / "f")); ASSERT_TRUE(fingerprints.back().isvalid); // Make the sync, and upsync auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); waitonsyncs(TIMEOUT, client); ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Throttle download speed so that we can move the local file before the // engine finishes downloading the latest changes from the cloud. client->setDownloadSpeed(4); // functor to detect the sync starting moves auto onMoveBeginHandler = [&client, &waiter](const LocalPath&, const LocalPath&) { waiter.set_value(); client->mOnMoveBegin = nullptr; }; // functor to detct the sync adding downloads auto detectDownloadStart = [&client, &waiter](Transfer& transfer) { if (transfer.type != GET) return; waiter.set_value(); client->mOnTransferAdded = nullptr; }; client->mOnTransferAdded = detectDownloadStart; // Update f in the cloud (manual upload, making a version chain, and causing the sync to download it) { auto data = randomData(16384); auto path = client->fsBasePath / "f"; // (this is outside s) (outside the sync) // Create a temporary file to upload. ASSERT_TRUE(createFile(path, data)); // Upload the file. ASSERT_TRUE(client->uploadFile(path, "s", 30, ClaimOldVersion)); // Update model. model.findnode("f")->content = data; // Keep track of the file's new fingerprint. fingerprints.emplace_back(client->fingerprint(path)); ASSERT_TRUE(fingerprints.back().isvalid); } // s/f is updated in cloud, is a new file with 1 version // Wait for the engine to add the download. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); // the download of f just started, super slow. Prep to detect move waiter = promise(); client->mOnMoveBegin = onMoveBeginHandler; // Move the local s/f to s/d/f and wait (download is already started) fs::rename(client->fsBasePath / "s" / "f", client->fsBasePath / "s" / "d" / "f"); // Wait for the engine to kick off the move. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); // Remove the throttle, let download and move race each other client->setDownloadSpeed(0); waitonsyncs(TIMEOUT, client); // We expect to find the downloaded file at s/d/f even though // initially it was downloading to s/f (this checks file content too) model.movenode("f", "d"); ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id, true)); // --------- new sub-test. // upload a new version of file f and overwrite at s/d/f. Race the download against a move back to s/f // Prep to detect download starting waiter = promise(); client->mOnTransferAdded = detectDownloadStart; client->setDownloadSpeed(4); // Overwrite s/d/f in the cloud with new file data. // (this will trigger the sync to start a download of it) { auto data = randomData(16384); auto path = client->fsBasePath / "f"; // (this is outside s) (outside the sync) // Create a temporary file to upload. ASSERT_TRUE(createFile(path, data)); // Upload the file. ASSERT_TRUE(client->uploadFile(path, "s/d", 30, ClaimOldVersion)); // Update model. model.findnode("d/f")->content = data; // Keep track of the file's new fingerprint. fingerprints.emplace_back(client->fingerprint(path)); ASSERT_TRUE(fingerprints.back().isvalid); } // Wait for the sync download of s/d/f to begin. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); // Prep to detect the sync cloud move back to s/f waiter = promise(); client->mOnMoveBegin = onMoveBeginHandler; // Move s/d/f back to s/f. fs::rename(client->fsBasePath / "s" / "d" / "f", client->fsBasePath / "s" / "f"); // Wait for the sync move to begin. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); // let the download and the move race each other client->setDownloadSpeed(0); waitonsyncs(TIMEOUT, client); // check the results: new file only at s/f (checks file content) model.movenode("d/f", ""); ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id, true)); // --------- new sub-test. // Repeat the test one more time so we can check stall state. // upload a new version of file f and overwrite at s/f. // Race the sync download against a move back to s/d/f // this time we Also overwrite different local file at s/d/f after the move // and that's where the stall comes from // Prep to detect download starting waiter = promise(); client->mOnTransferAdded = detectDownloadStart; client->setDownloadSpeed(4); // Overwrite a new version of s/f in the cloud. { auto data = randomData(16384); auto path = client->fsBasePath / "f"; // Create a temporary file to upload. ASSERT_TRUE(createFile(path, data)); // Upload the file. ASSERT_TRUE(client->uploadFile(path, "s", 30, ClaimOldVersion)); // Update model. model.findnode("f")->content = data; // Keep track of the file's new fingerprint. fingerprints.emplace_back(client->fingerprint(path)); ASSERT_TRUE(fingerprints.back().isvalid); } // Wait for the download to start. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); // New move waiter that uploads waiter = promise(); client->mOnMoveBegin = [&client, &waiter](const LocalPath&, const LocalPath&) { // Now we cause the stall // The sync just detected the local file moved to s/d/f and started a cloud move for it // We overwrite this local s/d/f file with new data. // The sync would want to upload it to s/d/f. // But that would clash with the move of the old file from s/f to s/d/f auto data = randomData(16384); ASSERT_TRUE(createFile(client->fsBasePath / "s" / "d" / "f", data)); waiter.set_value(); client->mOnMoveBegin = nullptr; }; // Move local s/f back into s/d/f. fs::rename(client->fsBasePath / "s" / "f", client->fsBasePath / "s" / "d" / "f"); // Wait for the engine to detect and kick off the mOnMoveBegin. ASSERT_NE(waiter.get_future().wait_for(TIMEOUT), future_status::timeout); client->setDownloadSpeed(0); waitonsyncs(TIMEOUT, client); // We should detect a stall: // download started from s/f // upload started from s/d/f // cloud moving s/f to s/d/f // so we should have an upload and download occurring at s/d/f, different new file content at both ASSERT_TRUE(client->waitFor(SyncStallState(true), TIMEOUT)); // Resolve the stall by removing the local file. fs::remove(client->fsBasePath / "s" / "d" / "f"); // Wait for the stall to resolve. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); // Wait for the engine to synchronize changes. waitonsyncs(TIMEOUT, client); // Make sure everything downloaded okay. model.movenode("f", "d"); ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id, true)); // Check that the version chain is as we expect. { // Get the node's fingerprints. auto fp = client->fingerprints("s/d/f"); // Make sure we have the number we expect. ASSERT_EQ(fp.size(), 4u); // Check that they agree with our log. ASSERT_TRUE(std::equal(fingerprints.crbegin(), fingerprints.crend(), fp.cbegin())); } } #endif // ! NDEBUG using StallEntryPredicate = std::function; SyncWaitPredicate SyncHasLocalStallMatching(StallEntryPredicate predicate) { return [predicate](StandardClient& client) { SyncStallInfoTests stalls; // Engine hasn't signalled any stalls. if (!client.syncStallDetected(stalls)) return false; // Search for a matching local stall. for (const auto& record : stalls.local) { if (predicate(record.second)) return true; } // Couldn't find a matching local stall. return false; }; } SyncWaitPredicate SyncHasLocalStallMatching(SyncWaitReason reason, const fs::path& path0, PathProblem problem0, const fs::path& path1 = fs::path(), PathProblem problem1 = PathProblem::NoProblem) { return SyncHasLocalStallMatching( [=](const SyncStallEntry& entry) { return entry.reason == reason && entry.localPath1.problem == problem0 && entry.localPath2.problem == problem1 && entry.localPath1.localPath.toPath(false) == path_u8string(path0) && entry.localPath2.localPath.toPath(false) == path_u8string(path1); }); } static bool isDeferredPathProblem(PathProblem problem) { return problem >= PathProblem::PutnodeDeferredByController && problem <= PathProblem::UploadDeferredByController; } static bool isDeferredStall(const SyncStallEntry& entry) { return isDeferredPathProblem(entry.localPath1.problem) || isDeferredPathProblem(entry.localPath2.problem); } static bool hasDeferredStall(const SyncStallInfo& stalls) { for (const auto& stalledSyncs : stalls.syncStallInfoMaps) { for (const auto& record : stalledSyncs.second.local) { if (isDeferredStall(record.second)) return true; } } return false; } TEST_F(SyncTest, MoveJustAsPutNodesSent) { auto TIMEOUT = std::chrono::seconds(16); // Get our hands on a clean client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud is clear. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure a remote sync root exists. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate local filesystem. Model model; LOG_info << "test adds file f, content 'x'. Will cause upload"; model.addfolder("d"); model.addfile("f", "x"); model.generate(client->fsBasePath / "s"); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(TIMEOUT, client); // Make sure our hierarchy made it into the cloud. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Instantiate controller. auto controller = std::make_shared(); // Inject controller. client->setSyncController(controller); // Signal "deferred" stalls immediately. client->setIsImmediateStall(isDeferredStall); client->setHasImmediateStall(hasDeferredStall); // Make sure original stall predicates are restored, no matter what. auto resetter = makeScopedStallPredicateResetter(*client); // Inhibit completion of s/f's putnodes. controller->setDeferPutnodeCompletionCallback([&](const fs::path& path) { return client->fsBasePath / "s" / "f" == path; }); LOG_info << "test updates file f, content 'y'. Will cause upload"; // Make a change to s/f. model.addfile("f", "y"); model.generate(client->fsBasePath / "s"); // Wait for the engine to detect our intentional stall. auto predicate = SyncHasLocalStallMatching(SyncWaitReason::UploadIssue, client->fsBasePath / "s" / "f", PathProblem::PutnodeCompletionDeferredByController); ASSERT_TRUE(client->waitFor(std::move(predicate), DEFAULTWAIT)); LOG_info << "test Move the file elsewhere locally : s/f to s/d/f"; // Move s/f to s/d/f while the putnode's completion is pending. fs::rename(client->fsBasePath / "s" / "f", client->fsBasePath / "s" / "d" / "f"); model.movenode("f", "d"); // Wait for the engine to signal a stall due to the move. // // The move can't complete because the move-source has an outstanding // putnodes request. // // The rationale behind this behavior is simply that we can't really // "rewind" a putnodes that we've sent. We have to let it complete. predicate = SyncHasLocalStallMatching(SyncWaitReason::MoveOrRenameCannotOccur, client->fsBasePath / "s" / "f", PathProblem::PutnodeCompletionPending, client->fsBasePath / "s" / "d" / "f"); ASSERT_TRUE(client->waitFor(std::move(predicate), DEFAULTWAIT)); // Now that we know the move has been detected, we can allow the // engine to complete the pending putnode request. controller->setDeferPutnodeCompletionCallback(nullptr); // Wait for the engine to return to normal operation. ASSERT_TRUE(client->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for syncing to complete. waitonsyncs(TIMEOUT, client); // Make sure everything's as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Try the same test in reverse. // // That is, process the move target first. // Inhibit completion of s/d/f's putnodes. controller->setDeferPutnodeCompletionCallback([&](const fs::path& path) { return client->fsBasePath / "s" / "d" / "f" == path; }); LOG_info << "test File d/f already exists - update content from `y` to `z`. Sync will upload, ending in putnodes"; model.addfile("d/f", "z"); model.generate(client->fsBasePath / "s"); // Wait for the engine to detect our intentional stall. predicate = SyncHasLocalStallMatching(SyncWaitReason::UploadIssue, client->fsBasePath / "s" / "d" / "f", PathProblem::PutnodeCompletionDeferredByController); ASSERT_TRUE(client->waitFor(std::move(predicate), DEFAULTWAIT)); LOG_info << "test Moves d/f into its parent folder. Sync will generate another putnodes, somewhat contrary to the other"; model.movenode("d/f", ""); fs::rename(client->fsBasePath / "s" / "d" / "f", client->fsBasePath / "s" / "f"); // Wait for the engine to signal a stall due to the move. predicate = SyncHasLocalStallMatching(SyncWaitReason::MoveOrRenameCannotOccur, client->fsBasePath / "s" / "d" / "f", PathProblem::PutnodeCompletionPending, client->fsBasePath / "s" / "f"); ASSERT_TRUE(client->waitFor(std::move(predicate), DEFAULTWAIT)); // Now that the move's been recognized, allow the putnodes to complete. controller->setDeferPutnodeCompletionCallback(nullptr); // Wait for the engine to return to normal operation. ASSERT_TRUE(client->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for syncing to complete. waitonsyncs(TIMEOUT, client); // Check that the client is as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, RemovedJustAsPutNodesSent) { auto TIMEOUT = std::chrono::seconds(16); // Get our hands on a clean client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud is clear. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Make sure a remote sync root exists. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Add and start sync. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Populate local filesystem. Model model; LOG_info << "test adds local file s/f (containing x)"; model.addfile("f", "x"); model.generate(client->fsBasePath / "s"); waitonsyncs(TIMEOUT, client); // Check if the cloud is as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Instantiate controller. auto controller = std::make_shared(); // Inject controller. client->setSyncController(controller); // Signal "deferred" stalls immediately. client->setIsImmediateStall(isDeferredStall); client->setHasImmediateStall(hasDeferredStall); // Make sure original stall predicates are restored, no matter what. auto resetter = makeScopedStallPredicateResetter(*client); // Inhibit completion of s/f's putnodes. controller->setDeferPutnodeCompletionCallback([&](const fs::path& path) { return client->fsBasePath / "s" / "f" == path; }); LOG_info << "test replaces local file content s/f (with y. was: x)"; model.addfile("f", "y"); model.generate(client->fsBasePath / "s"); // Wait for the engine to detect our intentional stall. auto predicate = SyncHasLocalStallMatching(SyncWaitReason::UploadIssue, client->fsBasePath / "s" / "f", PathProblem::PutnodeCompletionDeferredByController); ASSERT_TRUE(client->waitFor(std::move(predicate), TIMEOUT)); LOG_info << "test deletes local file s/f"; // Remove s/f while its putnodes is still in progress. model.removenode("f"); fs::remove(client->fsBasePath / "s" / "f"); // Give the engine a little time to recognize the change. WaitMillisec(2 * 1000); // Let s/f's putnodes complete. controller->setDeferPutnodeCompletionCallback(nullptr); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); // Wait for the engine to finish syncing. waitonsyncs(TIMEOUT, client); // Check if the cloud is as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Try the same scenario when the file hasn't previously been synchronized. // Inhibit completion of s/g's putnodes. controller->setDeferPutnodeCompletionCallback([&](const fs::path& path) { return client->fsBasePath / "s" / "g" == path; }); LOG_info << "test adds local file s/g (content: x)"; model.addfile("g", "x"); model.generate(client->fsBasePath / "s"); // Wait for the engine to stall. predicate = SyncHasLocalStallMatching(SyncWaitReason::UploadIssue, client->fsBasePath / "s" / "g", PathProblem::PutnodeCompletionDeferredByController); ASSERT_TRUE(client->waitFor(std::move(predicate), TIMEOUT)); LOG_info << "test removes local file s/g (content: x)"; // Remove g. model.removenode("g"); fs::remove(client->fsBasePath / "s" / "g"); // Give the engine a little time to detect the change. WaitMillisec(2 * 1000); // Remove the block on s/g's putnodes. controller->setDeferPutnodeCompletionCallback(nullptr); // Wait for the stall to be resolved. ASSERT_TRUE(client->waitFor(SyncStallState(false), TIMEOUT)); // Wait for the engine to synchronize our changes. waitonsyncs(TIMEOUT, client); // Make sure the cloud is as we expect. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, UndecryptableSharesBehavior) { const auto TESTROOT = makeNewTestRoot(); StandardClient client0(TESTROOT, "client0"); StandardClient client1(TESTROOT, "client1"); StandardClient client2(TESTROOT, "client2"); // Log in the clients. ASSERT_TRUE(client0.login_reset("MEGA_EMAIL", "MEGA_PWD")); ASSERT_TRUE(client1.login_reset("MEGA_EMAIL_AUX", "MEGA_PWD_AUX")); ASSERT_TRUE(client2.login_reset("MEGA_EMAIL_AUX2", "MEGA_PWD_AUX2")); // Make sure our "contacts" know about each other. { // Convenience predicate. auto contactRequestReceived = [](handle id) { return [id](StandardClient& client) { return client.ipcr(id); }; }; auto contactRequestFnished = [](const string& email) { return [&email](StandardClient& client) { return !client.opcr(email); }; }; auto contactVerificationFinished = [](const string& email) { return [&email](StandardClient& client) { return client.isverified(email); }; }; // Convenience helper. auto contactAdd = [&](StandardClient& client, const string& name) { // Get our hands on the contact's email. const string email = Utils::getenv(name, ""); // Get main client email. const string email0 = Utils::getenv("MEGA_EMAIL", ""); // Are we already associated with this contact? if (client0.iscontact(email)) { // Then remove them. ASSERT_TRUE(client0.rmcontact(email)); } // Remove pending contact request, if any. if (client0.opcr(email)) { ASSERT_TRUE(client0.opcr(email, OPCA_DELETE)); } // Add the contact. auto id = client0.opcr(email, OPCA_ADD); ASSERT_NE(id, UNDEF) << "Undef id when trying to add the contact! email = '" << email << "'"; // Wait for the contact to receive the request. ASSERT_TRUE(client.waitFor(contactRequestReceived(id), DEFAULTWAIT)); // Accept the contact request. ASSERT_TRUE(client.ipcr(id, IPCA_ACCEPT)); // Wait for the response to reach first client ASSERT_TRUE(client0.waitFor(contactRequestFnished(email), DEFAULTWAIT)); // Verify contact credentials if they are not if (gManualVerification) { if (!client0.isverified(email)) { ASSERT_TRUE(client0.verifyCredentials(email)); } if (!client.isverified(email0)) { ASSERT_TRUE(client.verifyCredentials(email0)); } // Wait for contact verification ASSERT_TRUE(client0.waitFor(contactVerificationFinished(email), DEFAULTWAIT)); ASSERT_TRUE(client.waitFor(contactVerificationFinished(email0), DEFAULTWAIT)); } }; // Introduce the contacts to each other. ASSERT_NO_FATAL_FAILURE(contactAdd(client1, "MEGA_EMAIL_AUX")); ASSERT_NO_FATAL_FAILURE(contactAdd(client2, "MEGA_EMAIL_AUX2")); } Model model; // Populate the local filesystem. model.addfile("t/f"); model.addfile("u/f"); model.addfile("v/f"); model.addfile("f"); model.addfile(".megaignore", "+sync:.megaignore"); model.generate(client1.fsBasePath / "s"); // Get our hands on the remote test root. auto r = client0.gettestbasenode(); ASSERT_NE(r, nullptr); // Populate the remote test root. { auto sPath = client1.fsBasePath / "s"; ASSERT_TRUE(client0.uploadFolderTree(sPath, r.get())); ASSERT_TRUE(client0.uploadFilesInTree(sPath, r.get())); } NodeHandle sh; // Get our hands on the remote sync root. { auto s = client0.drillchildnodebyname(r, "s"); ASSERT_NE(s, nullptr); sh = s->nodeHandle(); } // Share the test root with client 1. ASSERT_EQ(client0.opensharedialog(*r), API_OK); ASSERT_EQ(client0.share(*r, Utils::getenv("MEGA_EMAIL_AUX", ""), FULL), API_OK); ASSERT_TRUE(client1.waitFor(SyncRemoteNodePresent(*r), std::chrono::seconds(90))); // Reset to avoid keeping a shared_ptr // All shared_ptr have to be reseted before logout (avoid assert failure at Node::~Node) r.reset(); // Share the sync root with client 2. ASSERT_EQ(client0.opensharedialog(sh), API_OK); ASSERT_EQ(client0.share(sh, Utils::getenv("MEGA_EMAIL_AUX2", ""), FULL), API_OK); ASSERT_TRUE(client2.waitFor(SyncRemoteNodePresent(sh), std::chrono::seconds(90))); // Add and start a new sync. auto id = UNDEF; // Add the sync. id = client1.setupSync_mainthread("s", sh, false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(DEFAULTWAIT, &client1); // Make sure the clients all agree with what's in the cloud. ASSERT_TRUE(client1.confirmModel_mainthread(model.root.get(), id)); ASSERT_TRUE(client2.waitFor(SyncRemoteMatch(sh, model.root.get()), DEFAULTWAIT)); // Log out the sharing client so that it doesn't maintain keys. ASSERT_TRUE(client0.logout(false)); // Make a couple changes to client1's sync via client2. { // Nodes from client2's perspective. auto xs = client2.client.nodeByHandle(sh); ASSERT_NE(xs, nullptr); auto xt = client2.client.childnodebyname(xs.get(), "t"); ASSERT_NE(xt, nullptr); auto xu = client2.client.childnodebyname(xs.get(), "u"); ASSERT_NE(xu, nullptr); auto xv = client2.client.childnodebyname(xs.get(), "v"); ASSERT_NE(xv, nullptr); // Create a new directory w under s. { vector node(1); client1.received_node_actionpackets = false; client2.prepareOneFolder(&node[0], "w", false); ASSERT_TRUE(client2.putnodes(xs->nodeHandle(), NoVersioning, std::move(node))); ASSERT_TRUE(client1.waitForNodesUpdated(30)); } // Get our hands on w from client 2's perspective. auto xw = client2.client.childnodebyname(xs.get(), "w"); ASSERT_NE(xw, nullptr); // Be certain that client 1 can see w. ASSERT_TRUE(client1.waitFor(SyncRemoteNodePresent(*xw), DEFAULTWAIT)); // Let the engine try and process the change. waitonsyncs(DEFAULTWAIT, &client1); // Move t, u and v under w. client1.received_node_actionpackets = false; model.movenode("t", "w"); // from SRW ASSERT_TRUE(client2.movenode(xt->nodehandle, xw->nodehandle)); ASSERT_TRUE(client1.waitForNodesUpdated(30)); client1.received_node_actionpackets = false; model.movenode("u", "w"); // from SRW ASSERT_TRUE(client2.movenode(xu->nodehandle, xw->nodehandle)); ASSERT_TRUE(client1.waitForNodesUpdated(30)); client1.received_node_actionpackets = false; model.movenode("v", "w"); // from SRW ASSERT_TRUE(client2.movenode(xv->nodehandle, xw->nodehandle)); ASSERT_TRUE(client1.waitForNodesUpdated(30)); } // Wait for client 1, it must not generate a stall ASSERT_FALSE(client1.waitFor(SyncStallState(true), DEFAULTWAIT)); // Temporarily log out client 1. // // Undecrpytable nodes won't be serialized. string session; client1.client.dumpsession(session); client1.localLogout(); // Hook resume callback. promise notify; // resumption probably triggers a sync_added() callback too, which can // be tracked by onAutoResumeResult, instead of by the onSyncStateConfig client1.mOnSyncStateConfig = [&](const SyncConfig& config) { if (config.mRunState != SyncRunState::Run) return; notify.set_value(); client1.mOnSyncStateConfig = nullptr; }; // Log the client back in. ASSERT_TRUE(client1.login_fetchnodesFromSession(session)); // Wait for the sync to resume. ASSERT_NE(notify.get_future().wait_for(DEFAULTWAIT), future_status::timeout); // Give the sync some time to process changes. waitonsyncs(DEFAULTWAIT, &client1); // Wait for the engine once again to ensure it does not generate a stall. ASSERT_FALSE(client1.waitFor(SyncStallState(true), DEFAULTWAIT)); } TEST_F(SyncTest, CloudHorizontalMoveChain) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("q.txt"); model.addfile("r.txt"); model.addfile("s.txt"); model.addfile(".megaignore", "+sync:.megaignore"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto qh = client->getNodeHandle("s/q.txt"); ASSERT_NE(qh, UNDEF); auto rh = client->getNodeHandle("s/r.txt"); ASSERT_NE(rh, UNDEF); auto sh = client->getNodeHandle("s/s.txt"); ASSERT_NE(sh, UNDEF); // Rotate the cloud files: x -> y -> z -> x { auto pauser = makeScopedSyncPauser(*client, id); auto root = client->fsBasePath / "s"; model.findnode("s.txt")->name = "t.txt"; ASSERT_TRUE(client->rename(sh, "t.txt")); model.findnode("r.txt")->name = "s.txt"; ASSERT_TRUE(client->rename(rh, "s.txt")); model.findnode("q.txt")->name = "r.txt"; ASSERT_TRUE(client->rename(qh, "r.txt")); // Wait for our changes to hit the cloud. ASSERT_TRUE(client->waitFor(SyncRemoteMatch("s", model), DEFAULTWAIT)); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was synchronized correctly. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Make sure the nodes were truly moved. EXPECT_EQ(qh, client->getNodeHandle("s/r.txt")); EXPECT_EQ(rh, client->getNodeHandle("s/s.txt")); EXPECT_EQ(sh, client->getNodeHandle("s/t.txt")); } TEST_F(SyncTest, CloudHorizontalMoveCycle) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("x.txt"); model.addfile("y.txt"); model.addfile("z.txt"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto xh = client->getNodeHandle("s/x.txt"); ASSERT_NE(xh, UNDEF); auto yh = client->getNodeHandle("s/y.txt"); ASSERT_NE(yh, UNDEF); auto zh = client->getNodeHandle("s/z.txt"); ASSERT_NE(zh, UNDEF); // Rotate the cloud files: x -> y -> z -> x { auto pauser = makeScopedSyncPauser(*client, id); client->received_node_actionpackets = false; ASSERT_TRUE(client->rename(zh, "x.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); client->received_node_actionpackets = false; ASSERT_TRUE(client->rename(yh, "z.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); client->received_node_actionpackets = false; ASSERT_TRUE(client->rename(xh, "y.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Wait for the engine to stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we've stalled for the right reasons. { SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_EQ(stalls.cloud.size(), 3u); std::set problems; std::set reasons; for (auto& i : stalls.cloud) { reasons.emplace(i.second.reason); problems.emplace(i.second.cloudPath1.problem); problems.emplace(i.second.cloudPath2.problem); problems.emplace(i.second.localPath1.problem); problems.emplace(i.second.localPath2.problem); } problems.erase(PathProblem::NoProblem); ASSERT_EQ(problems.size(), 1u); ASSERT_EQ(*problems.begin(), PathProblem::WaitingForAnotherMoveToComplete); ASSERT_EQ(reasons.size(), 1u); ASSERT_EQ(*reasons.begin(), SyncWaitReason::MoveOrRenameCannotOccur); } // Make sure nothing's changed on disk. EXPECT_TRUE(client->confirmModel_mainthread( model.root.get(), id, false, StandardClient::CONFIRM_LOCALFS)); // Make sure the nodes haven't been moved in the cloud. EXPECT_EQ(xh, client->getNodeHandle("s/y.txt")); EXPECT_EQ(yh, client->getNodeHandle("s/z.txt")); EXPECT_EQ(zh, client->getNodeHandle("s/x.txt")); } TEST_F(SyncTest, CloudVerticalMoveChain) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("q.txt"); model.addfile("0/r.txt"); model.addfile("0/1/s.txt"); model.addfolder("0/1/2"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto qh = client->getNodeHandle("s/q.txt"); ASSERT_NE(qh, UNDEF); auto rh = client->getNodeHandle("s/0/r.txt"); ASSERT_NE(rh, UNDEF); auto sh = client->getNodeHandle("s/0/1/s.txt"); ASSERT_NE(sh, UNDEF); // Rotate the local files: q -> 0/r -> 0/1/s -> 0/1/2/t { auto pauser = makeScopedSyncPauser(*client, id); auto node = model.removenode("0/1/s.txt"); node->name = "t.txt"; model.findnode("0/1/2")->addkid(std::move(node)); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(sh, "s/0/1/2", "t.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); node = model.removenode("0/r.txt"); node->name = "s.txt"; model.findnode("0/1")->addkid(std::move(node)); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(rh, "s/0/1", "s.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); node = model.removenode("q.txt"); node->name = "r.txt"; model.findnode("0")->addkid(std::move(node)); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(qh, "s/0", "r.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure the moves were synchronized correctly. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Make sure the nodes have actually been moved. EXPECT_EQ(qh, client->getNodeHandle("s/0/r.txt")); EXPECT_EQ(rh, client->getNodeHandle("s/0/1/s.txt")); EXPECT_EQ(sh, client->getNodeHandle("s/0/1/2/t.txt")); } TEST_F(SyncTest, CloudVerticalMoveCycle) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("x.txt", "x"); model.addfile("0/y.txt", "y"); model.addfile("0/1/z.txt", "z"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto xh = client->getNodeHandle("s/x.txt"); ASSERT_NE(xh, UNDEF); auto yh = client->getNodeHandle("s/0/y.txt"); ASSERT_NE(yh, UNDEF); auto zh = client->getNodeHandle("s/0/1/z.txt"); ASSERT_NE(zh, UNDEF); // Rotate the local files: x -> 0/y -> 0/1/z -> x { auto pauser = makeScopedSyncPauser(*client, id); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(yh, "s/0/1", "z.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(xh, "s/0", "y.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); client->received_node_actionpackets = false; ASSERT_TRUE(client->movenode(zh, "s", "x.txt")); ASSERT_TRUE(client->waitForNodesUpdated(16)); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Wait for the engine to stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we've stalled for the right reasons. { SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_EQ(stalls.cloud.size(), 3u); std::set problems; std::set reasons; for (auto& i : stalls.cloud) { reasons.emplace(i.second.reason); problems.emplace(i.second.cloudPath1.problem); problems.emplace(i.second.cloudPath2.problem); problems.emplace(i.second.localPath1.problem); problems.emplace(i.second.localPath2.problem); } problems.erase(PathProblem::NoProblem); ASSERT_EQ(problems.size(), 1u); ASSERT_EQ(*problems.begin(), PathProblem::WaitingForAnotherMoveToComplete); ASSERT_EQ(reasons.size(), 1u); ASSERT_EQ(*reasons.begin(), SyncWaitReason::MoveOrRenameCannotOccur); } // Make sure nothing's changed on the local filesystem. ASSERT_TRUE(client->confirmModel_mainthread( model.root.get(), id, false, StandardClient::CONFIRM_LOCALFS)); // Make sure the cloud remains the way we made it. EXPECT_EQ(xh, client->getNodeHandle("s/0/y.txt")); EXPECT_EQ(yh, client->getNodeHandle("s/0/1/z.txt")); EXPECT_EQ(zh, client->getNodeHandle("s/x.txt")); } TEST_F(SyncTest, LocalHorizontalMoveChain) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("q.txt"); model.addfile("r.txt"); model.addfile("s.txt"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto qh = client->getNodeHandle("s/q.txt"); ASSERT_NE(qh, UNDEF); auto rh = client->getNodeHandle("s/r.txt"); ASSERT_NE(rh, UNDEF); auto sh = client->getNodeHandle("s/s.txt"); ASSERT_NE(sh, UNDEF); // Rotate the local files: x -> y -> z -> x { auto pauser = makeScopedSyncPauser(*client, id); auto root = client->fsBasePath / "s"; model.findnode("s.txt")->name = "t.txt"; fs::rename(root / "s.txt", root / "t.txt"); model.findnode("r.txt")->name = "s.txt"; fs::rename(root / "r.txt", root / "s.txt"); model.findnode("q.txt")->name = "r.txt"; fs::rename(root / "q.txt", root / "r.txt"); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure the engine correctly performed the moves. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Nothing should've moved in the cloud. EXPECT_EQ(qh, client->getNodeHandle("s/r.txt")); EXPECT_EQ(rh, client->getNodeHandle("s/s.txt")); EXPECT_EQ(sh, client->getNodeHandle("s/t.txt")); } TEST_F(SyncTest, LocalHorizontalMoveCycle) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("x.txt"); model.addfile("y.txt"); model.addfile("z.txt"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto xh = client->getNodeHandle("s/x.txt"); ASSERT_NE(xh, UNDEF); auto yh = client->getNodeHandle("s/y.txt"); ASSERT_NE(yh, UNDEF); auto zh = client->getNodeHandle("s/z.txt"); ASSERT_NE(zh, UNDEF); // Rotate the local files: x -> y -> z -> x { auto pauser = makeScopedSyncPauser(*client, id); auto root = client->fsBasePath / "s"; model.findnode("z.txt")->name = "w.txt"; fs::rename(root / "z.txt", root / "w.txt"); model.findnode("y.txt")->name = "z.txt"; fs::rename(root / "y.txt", root / "z.txt"); model.findnode("x.txt")->name = "y.txt"; fs::rename(root / "x.txt", root / "y.txt"); model.findnode("w.txt")->name = "x.txt"; fs::rename(root / "w.txt", root / "x.txt"); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Wait for the engine to stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we've stalled for the right reasons. { SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_EQ(stalls.local.size(), 3u); std::set problems; std::set reasons; for (auto& i : stalls.local) { reasons.emplace(i.second.reason); problems.emplace(i.second.cloudPath1.problem); problems.emplace(i.second.cloudPath2.problem); problems.emplace(i.second.localPath1.problem); problems.emplace(i.second.localPath2.problem); } problems.erase(PathProblem::NoProblem); ASSERT_EQ(problems.size(), 1u); ASSERT_EQ(*problems.begin(), PathProblem::WaitingForAnotherMoveToComplete); ASSERT_EQ(reasons.size(), 1u); ASSERT_EQ(*reasons.begin(), SyncWaitReason::MoveOrRenameCannotOccur); } // Nothing should've moved in the cloud. EXPECT_EQ(xh, client->getNodeHandle("s/x.txt")); EXPECT_EQ(yh, client->getNodeHandle("s/y.txt")); EXPECT_EQ(zh, client->getNodeHandle("s/z.txt")); } TEST_F(SyncTest, LocalVerticalMoveChain) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("q.txt"); model.addfile("0/r.txt"); model.addfile("0/1/s.txt"); model.addfolder("0/1/2"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto qh = client->getNodeHandle("s/q.txt"); ASSERT_NE(qh, UNDEF); auto rh = client->getNodeHandle("s/0/r.txt"); ASSERT_NE(rh, UNDEF); auto sh = client->getNodeHandle("s/0/1/s.txt"); ASSERT_NE(sh, UNDEF); // Rotate the local files: q -> 0/r -> 0/1/s -> 0/1/2/t. { auto pauser = makeScopedSyncPauser(*client, id); auto root = client->fsBasePath / "s"; auto node = model.removenode("0/1/s.txt"); node->name = "t.txt"; model.findnode("0/1/2")->addkid(std::move(node)); fs::rename(root / "0" / "1" / "s.txt", root / "0" / "1" / "2" / "t.txt"); node = model.removenode("0/r.txt"); node->name = "s.txt"; model.findnode("0/1")->addkid(std::move(node)); fs::rename(root / "0" / "r.txt", root / "0" / "1" / "s.txt"); node = model.removenode("q.txt"); node->name = "r.txt"; model.findnode("0")->addkid(std::move(node)); fs::rename(root / "q.txt", root / "0" / "r.txt"); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was synchronized correctly. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Make sure the nodes were truly moved. EXPECT_EQ(qh, client->getNodeHandle("s/0/r.txt")); EXPECT_EQ(rh, client->getNodeHandle("s/0/1/s.txt")); EXPECT_EQ(sh, client->getNodeHandle("s/0/1/2/t.txt")); } TEST_F(SyncTest, LocalVerticalMoveCycle) { // Get our hands on a client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); // Create a directory for us to sync against. ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); // Populate the local filesystem. Model model; model.addfile("x.txt", "x"); model.addfile("0/y.txt", "y"); model.addfile("0/1/z.txt", "z"); model.generate(client->fsBasePath / "s"); // Synchronize the files to the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the synchronization to complete. waitonsyncs(DEFAULTWAIT, client); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); // Get our hands on the handles of the files we uploaded. auto xh = client->getNodeHandle("s/x.txt"); ASSERT_NE(xh, UNDEF); auto yh = client->getNodeHandle("s/0/y.txt"); ASSERT_NE(yh, UNDEF); auto zh = client->getNodeHandle("s/0/1/z.txt"); ASSERT_NE(zh, UNDEF); // Rotate the local files: x -> 0/y -> 0/1/z -> x { auto pauser = makeScopedSyncPauser(*client, id); auto root = client->fsBasePath / "s"; fs::rename(root / "0" / "1" / "z.txt", root / "z.txt"); model.findnode("0/1/z.txt")->content = "y"; fs::rename(root / "0" / "y.txt", root / "0" / "1" / "z.txt"); model.findnode("0/y.txt")->content = "x"; fs::rename(root / "x.txt", root / "0" / "y.txt"); model.findnode("x.txt")->content = "z"; fs::rename(root / "z.txt", root / "x.txt"); } // Wait for the engine to process our changes. waitonsyncs(DEFAULTWAIT, client); // Wait for the engine to stall. ASSERT_TRUE(client->waitFor(SyncStallState(true), DEFAULTWAIT)); // Make sure we've stalled for the right reasons. { SyncStallInfoTests stalls; ASSERT_TRUE(client->syncStallDetected(stalls)); ASSERT_EQ(stalls.local.size(), 3u); std::set problems; std::set reasons; for (auto& i : stalls.local) { reasons.emplace(i.second.reason); problems.emplace(i.second.cloudPath1.problem); problems.emplace(i.second.cloudPath2.problem); problems.emplace(i.second.localPath1.problem); problems.emplace(i.second.localPath2.problem); } problems.erase(PathProblem::NoProblem); ASSERT_EQ(problems.size(), 1u); ASSERT_EQ(*problems.begin(), PathProblem::WaitingForAnotherMoveToComplete); ASSERT_EQ(reasons.size(), 1u); ASSERT_EQ(*reasons.begin(), SyncWaitReason::MoveOrRenameCannotOccur); } // Make sure nothign changed in the cloud. EXPECT_EQ(xh, client->getNodeHandle("s/x.txt")); EXPECT_EQ(yh, client->getNodeHandle("s/0/y.txt")); EXPECT_EQ(zh, client->getNodeHandle("s/0/1/z.txt")); } TEST_F(SyncTest, SyncUtf8DifferentlyNormalized1) { fs::path localtestroot = makeNewTestRoot(); auto client = g_clientManager->getCleanStandardClient(0, localtestroot); ASSERT_TRUE(client->resetBaseFolderMulticlient()); ASSERT_TRUE(client->makeCloudSubdirs("s", 0, 0)); string name1 = "\x73\x6f\x75\x68\x6c\x61\x73\x20\x73\x20\x6b\x6f\x70\x69\xc3\xad\x20\x43\x50"; // from cloud - therefore, this is the normalized form we use string name2 = "\x73\x6f\x75\x68\x6c\x61\x73\x20\x73\x20\x6b\x6f\x70\x69\x69\xcc\x81\x20\x43\x50"; // form from Mac FS fs::path n1_utf8 = u8path_compat(name1); fs::path n2_utf8 = u8path_compat(name2); string convertedback_1 = path_u8string(n1_utf8); string convertedback_2 = path_u8string(n2_utf8); ASSERT_EQ(convertedback_1.size(), name1.size()); ASSERT_EQ(convertedback_2.size(), name2.size()); LocalPath::utf8_normalize(&convertedback_1); LocalPath::utf8_normalize(&convertedback_2); ASSERT_EQ(convertedback_1, convertedback_2); ASSERT_EQ(convertedback_2, name1); // Make a cloud node with one normalization ASSERT_TRUE(createNameFile(localtestroot, name1)); ASSERT_TRUE(client->uploadFile(localtestroot / n1_utf8, "s")); auto root = client->fsBasePath / "s"; // Same file in the sync locally, but name encoded differently fs::create_directory(root); fs::copy(localtestroot / n1_utf8, root / n2_utf8); // Set up the sync and see how it deals with those (may vary by platform, Mac auto normalizes filesnames for example (with some other normalization than we chose)) auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); WaitMillisec(1000); // Wait for the synchronization to complete. auto waitResult = waitonsyncs(std::chrono::seconds(5), client); if (auto stallsDetected = !noSyncStalled(waitResult); stallsDetected) { printStallIssues(*client); ASSERT_TRUE(!stallsDetected) << "stall issues detected"; } Model model; model.addfile(name1)->fsName(name2); // Make sure everything was uploaded successfully. ASSERT_TRUE(client->confirmModel_mainthread(model.root.get(), id)); } TEST_F(SyncTest, RemoveSync) { // Set up a client fs::path localtestroot = makeNewTestRoot(); auto clientA1 = g_clientManager->getCleanStandardClient(0, localtestroot); // user 1 client 1 // Create remote dir ASSERT_TRUE(clientA1->resetBaseFolderMulticlient()); ASSERT_TRUE(clientA1->makeCloudSubdirs("f", 0, 0)); // create sync handle backupId1 = clientA1->setupSync_mainthread("sync1", "f", false, true); ASSERT_NE(backupId1, UNDEF); waitonsyncs(std::chrono::seconds(4), clientA1); auto* sync = clientA1->syncByBackupId(backupId1); ASSERT_NE(sync, nullptr); // Delete the sync and make sure it is disable clientA1->onRemovedSync = [](const SyncConfig& config) { ASSERT_EQ(config.mRunState, SyncRunState::Disable); }; clientA1->delSync_mainthread(backupId1); } #ifdef _WIN32 TEST_F(SyncTest, IgnoreFilesShouldBeHidden) { // Get our hands on a new client. auto client = g_clientManager->getCleanStandardClient(0, makeNewTestRoot()); // Make sure the cloud's clean. ASSERT_TRUE(client->resetBaseFolderMulticlient()); Model model; // Compute sync path for convenience. auto rootPath = client->fsBasePath / "s"; // Populate the model with a couple ignore files. model.addfile(".megaignore", "#s\n+sync:.megaignore"); model.addfile("sd0/sd0d0/.megaignore", "#sd0d0\n+sync:.megaignore"); // Populate the local disk. model.generate(rootPath); // Upload test tree to the cloud. ASSERT_TRUE(client->uploadFolderTree(rootPath, "")); ASSERT_TRUE(client->uploadFilesInTree(rootPath, "")); // Add a couple more ignore files. // // We'll consider these files to have been created by the user. model.addfile("sd1/sd1d0/.megaignore", "#sd1d0\n+sync:.megaignore"); model.generate(rootPath); // Remove .megaignore and sd0/.megaignore. // // We want these to be created by the sync engine. std::error_code error; ASSERT_TRUE(fs::remove(rootPath / ".megaignore", error)); ASSERT_FALSE(error); ASSERT_TRUE(fs::remove(rootPath / "sd0" / "sd0d0" / ".megaignore", error)); ASSERT_FALSE(error); // Synchronize our local directory with the cloud. auto id = client->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the sync to complete. waitonsyncs(DEFAULTWAIT, client); // For convenience: Make sure we check ignore files, too. auto confirm = [&]() { return client->confirmModel_mainthread(model.root.get(), id, false, StandardClient::CONFIRM_ALL, false, false); }; // confirm // Make sure everything's where it should be. ASSERT_TRUE(confirm()); // Verify that .megaignore and sd0/.megaignore are marked as hidden. // // These files were created directly by the sync engine so it has the // right to mark them as it pleases. EXPECT_TRUE(isFileHidden(rootPath / ".megaignore")); EXPECT_TRUE(isFileHidden(rootPath / "sd0" / "sd0d0" / ".megaignore")); // sd1/.megaignore should remain unchanged. // // As the sync engine didn't create this file, we have to assume that // the user themselves did and if so, that they may have explicitly // marked that file as being visible. EXPECT_FALSE(isFileHidden(rootPath / "sd1" / "sd1d0" / ".megaignore")); // Moving an ignore file should not change whether it is hidden. model.copynode("sd0/sd0d0/.megaignore", "sd0/.megaignore"); model.movetosynctrash("sd0/sd0d0/.megaignore", ""); model.movenode("sd1/sd1d0/.megaignore", "sd1"); // Regardless of whether the file was moved in the cloud... ASSERT_TRUE(client->movenode("s/sd0/sd0d0/.megaignore", "s/sd0")); // Or on the local disk. fs::rename(rootPath / "sd1" / "sd1d0" / ".megaignore", rootPath / "sd1" / ".megaignore", error); ASSERT_FALSE(error); // Wait for the engine to recognize and process our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure everything is as it should be. ASSERT_TRUE(confirm()); // Should remain hidden as the engine created it. EXPECT_TRUE(isFileHidden(rootPath / "sd0" / ".megaignore")); // Should remain visible as the user created it. EXPECT_FALSE(isFileHidden(rootPath / "sd1" / ".megaignore")); // Downloading a new version of an ignore file shouldn't change whether it is visible. ASSERT_TRUE(client->uploadFile(rootPath / "sd1" / ".megaignore", ".megaignore", "s/sd1", static_cast(DEFAULTWAIT.count()), ClaimOldVersion)); // Wait for the engine to synchronize our changes. waitonsyncs(DEFAULTWAIT, client); // Make sure everything is as we expect. ASSERT_TRUE(confirm()); // Ignore file's visibility shouldn't have changed. EXPECT_FALSE(isFileHidden(rootPath / "sd1" / ".megaignore")); } #endif // _WIN32 class ContradictoryMoveFixture : public ::testing::Test { public: ContradictoryMoveFixture() : Test() , c(g_clientManager->getCleanStandardClient(0, makeNewTestRoot())) , mc() , mf() , id(UNDEF) { } void SetUp() override { // Make sure the cloud's clean. ASSERT_TRUE(c->resetBaseFolderMulticlient()); // Make sure we have something to sync against in the cloud. ASSERT_TRUE(c->makeCloudSubdirs("s", 0, 0)); // Populate local model. mf.addfolder("da"); mf.addfolder("db"); mf.addfolder("dc"); mf.addfile(".megaignore", "+sync:.megaignore"); mf.generate(c->fsBasePath / "s"); // Initially cloud matches disk. mc = mf; // Add and start a new sync. id = c->setupSync_mainthread("s", "s", false, false); ASSERT_NE(id, UNDEF); // Wait for the initial sync to complete. waitonsyncs(DEFAULTWAIT, c); ASSERT_TRUE(c->waitFor(SyncStallState(false), DEFAULTWAIT)); out() << "sync set up and we are not stalled"; // Make sure everything made it safely to the cloud. ASSERT_TRUE(c->confirmModel_mainthread(mc.root.get(), id)); // Create a contradictory move scenario. { auto pauser = makeScopedSyncPauser(*c, id); // Remotely move da -> db. mc.movenode("da", "db"); ASSERT_TRUE(c->movenode("s/da", "s/db")); ASSERT_TRUE(c->waitFor(SyncRemoteMatch("s", mc.root.get()), DEFAULTWAIT)); // Locally move db -> da. std::error_code result; mf.movenode("db", "da"); fs::rename(c->fsBasePath / "s" / "db", c->fsBasePath / "s" / "da" / "db", result); ASSERT_FALSE(result); } // Wait for a stall to be signalled. ASSERT_TRUE(c->waitFor(SyncStallState(true), DEFAULTWAIT)); ASSERT_TRUE(c->confirmModel_mainthread(mc.root.get(), id, true, StandardClient::CONFIRM_REMOTE)); ASSERT_TRUE(c->confirmModel_mainthread(mf.root.get(), id, true, StandardClient::CONFIRM_LOCALFS)); out() << "ContradictoryMoveFixture is SetUp, with da in db remotely, db in da locally, and sync c stalled"; } // Client we're using to perform the tests. StandardClientInUse c; // Models representing the state of our client. Model mc; Model mf; // ID of the sync we're using to perform our test. handle id; }; // ContradictoryMoveFixture TEST_F(ContradictoryMoveFixture, MoveLocally) { out() << "ContradictoryMoveFixture MoveRemotely resolving by moving db back to the sync root locally"; // undo the local side of the clash std::error_code err; fs::rename(c->fsBasePath / "s" / "da" / "db", c->fsBasePath / "s" / "db", err); ASSERT_TRUE(!err); // Wait for the stall to temporarily resolve. ASSERT_TRUE(c->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the engine to become idle. auto result = waitonsyncs(std::chrono::seconds(5), c); // both should now be in the target state, given by cloud-side model EXPECT_TRUE(!result.front().syncStalled); EXPECT_TRUE(c->confirmModel_mainthread(mc.root.get(), id, false, StandardClient::CONFIRM_REMOTE, false, false)); EXPECT_TRUE(c->confirmModel_mainthread(mc.root.get(), id, false, StandardClient::CONFIRM_LOCALFS, false, false)); } TEST_F(ContradictoryMoveFixture, MoveRemotely) { out() << "ContradictoryMoveFixture MoveRemotely resolving by moving da back to the sync root remotely"; // undo the cloud side of the clash ASSERT_TRUE(c->movenode("s/db/da", "s")); // Wait for the stall to resolve. ASSERT_TRUE(c->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the engine to become idle. auto result = waitonsyncs(std::chrono::seconds(5), c); // both should now be in the target state, given by file-side model EXPECT_TRUE(!result.front().syncStalled); EXPECT_TRUE(c->confirmModel_mainthread(mf.root.get(), id, false, StandardClient::CONFIRM_REMOTE, false, false)); EXPECT_TRUE(c->confirmModel_mainthread(mf.root.get(), id, false, StandardClient::CONFIRM_LOCALFS, false, false)); } TEST_F(ContradictoryMoveFixture, ResolveLocally) { out() << "ContradictoryMoveFixture MoveRemotely resolving by moving db back and da into it, locally"; // Manually make the disk look like the cloud. { auto pauser = makeScopedSyncPauser(*c, id); std::error_code result; // Move da/db -> db. mf.movenode("da/db", ""); fs::rename(c->fsBasePath / "s" / "da" / "db", c->fsBasePath / "s" / "db", result); ASSERT_FALSE(result); // Move da -> db/da. mf.movenode("da", "db"); fs::rename(c->fsBasePath / "s" / "da", c->fsBasePath / "s" / "db" / "da", result); ASSERT_FALSE(result); } // Wait for the stall to be resolved. ASSERT_TRUE(c->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the engine to idle. auto result = waitonsyncs(DEFAULTWAIT, c); // Make sure no stalls were detected. EXPECT_FALSE(result.front().syncStalled); // Make sure the state of the sync is as we expect. EXPECT_TRUE(c->confirmModel_mainthread(mf.root.get(), id)); } TEST_F(ContradictoryMoveFixture, ResolveRemotely) { out() << "ContradictoryMoveFixture MoveRemotely resolving by moving da back and db into it, remotely"; // Manually make the cloud look like the disk. { auto pauser = makeScopedSyncPauser(*c, id); // Move db/da -> da. mc.movenode("db/da", ""); ASSERT_TRUE(c->movenode("s/db/da", "s")); // Move db -> da/db. mc.movenode("db", "da"); ASSERT_TRUE(c->movenode("s/db", "s/da")); // Wait for the client to receive our changes. ASSERT_TRUE(c->waitFor(SyncRemoteMatch("s", mc.root.get()), DEFAULTWAIT)); } // Wait for the stall to be resolved. ASSERT_TRUE(c->waitFor(SyncStallState(false), DEFAULTWAIT)); // Wait for the engine to idle. auto result = waitonsyncs(DEFAULTWAIT, c); // Make sure no stalls were detected. EXPECT_FALSE(result.front().syncStalled); // Make sure the state of the sync is as we expect. EXPECT_TRUE(c->confirmModel_mainthread(mf.root.get(), id)); } #endif sdk-10.11.0/tests/integration/backup_sync_operations_test.cpp000066400000000000000000000121201516266226600244370ustar00rootroot00000000000000/** * @file backup_sync_operations_test.cpp * @brief This file contains tests for the public interfaces available to manage backups or syncs, * stop them and archive or remove a deconfigured backup. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mock_listeners.h" #include "SdkTest_test.h" using namespace sdk_test; using namespace testing; /** * @brief SdkTestBackup class implementing basic operations for Backups and Syncs (to be extended). * It initializes one testing account and ensures that the device name is configured. */ class SdkTestBackupSync: public SdkTest { private: const fs::path mLocalFolderName{getFilePrefix() + "dir"}; const LocalTempDir mLocalFolder{fs::current_path() / mLocalFolderName}; protected: MegaHandle mBackupID{INVALID_HANDLE}; const std::string mBackupName{"myBackup"}; public: static constexpr auto mMaxTimeout{3min}; void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); } const fs::path& getLocalFolder() const { return mLocalFolder.getPath(); } void setupBackupSync() { LOG_debug << "Creating a backup"; ASSERT_EQ(mBackupID, INVALID_HANDLE) << "There is already a backup/sync created."; mBackupID = backupFolder(megaApi[0].get(), path_u8string(getLocalFolder()), mBackupName); ASSERT_NE(mBackupID, INVALID_HANDLE) << "Invalid Backup ID"; } void removeSync() { ASSERT_NE(mBackupID, INVALID_HANDLE) << "Cant't remove backup/sync. Invalid Backup ID"; if (const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync) { ASSERT_TRUE(::removeSync(megaApi[0].get(), mBackupID)); } } }; /** * @brief The SdkTestBackupOperations class * * Test fixture that creates a backup and a destination directory to archive backups when moved. * It offers functionality to test a clash when archiving a backup. */ class SdkTestBackupOperations: public SdkTestBackupSync { private: MegaHandle mBackupRootHandle{INVALID_HANDLE}; const fs::path mDestinationFolderName{"BackupArchive"}; MegaHandle mDestinationFolderHandle{INVALID_HANDLE}; public: void SetUp() override { SdkTestBackupSync::SetUp(); ASSERT_NO_FATAL_FAILURE(setupBackupSync()); ASSERT_NO_FATAL_FAILURE(setupDestinationDirectory()); const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; ASSERT_TRUE(sync); mBackupRootHandle = sync->getMegaHandle(); } void TearDown() override { if (const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync) { ::removeSync(megaApi[0].get(), mBackupID); } SdkTestBackupSync::TearDown(); } void setupDestinationDirectory() { unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode) << "Account root node not available."; mDestinationFolderHandle = createFolder(0, path_u8string(mDestinationFolderName).c_str(), rootnode.get()); ASSERT_NE(mDestinationFolderHandle, INVALID_HANDLE) << "Invalid destination folder handle"; } void duplicateDestinationBackupFolder() { // Get parent folder const unique_ptr parentFolder{ megaApi[0]->getNodeByHandle(mDestinationFolderHandle)}; ASSERT_TRUE(parentFolder); // Create a folder in the destination with the same name as the backup const MegaHandle newFolder = createFolder(0, mBackupName.c_str(), parentFolder.get()); ASSERT_NE(newFolder, INVALID_HANDLE) << "Invalid destination folder handle"; } bool removeBackupNode(error expectedError = API_OK) { return moveOrRemoveBackupNode(expectedError); } bool archiveBackupNode(error expectedError = API_OK) { return moveOrRemoveBackupNode(expectedError, mDestinationFolderHandle); } private: bool moveOrRemoveBackupNode(error expectedError = API_OK, MegaHandle destination = INVALID_HANDLE) { NiceMock reqTracker{megaApi[0].get()}; reqTracker.setErrorExpectations(expectedError); megaApi[0]->moveOrRemoveDeconfiguredBackupNodes(mBackupRootHandle, destination, &reqTracker); return reqTracker.waitForFinishOrTimeout(mMaxTimeout); } }; TEST_F(SdkTestBackupOperations, RemoveDestinationClash) { static const auto logPre{getLogPrefix()}; LOG_debug << logPre << "Duplicate destination folder to cause a clash."; duplicateDestinationBackupFolder(); LOG_debug << logPre << "Remove backup sync"; removeSync(); LOG_debug << logPre << "Try to move backup root node to the cloud"; ASSERT_TRUE(archiveBackupNode(API_EEXIST)) << "Desination node should already exist and fail."; LOG_debug << logPre << "Remove backup contents"; ASSERT_TRUE(removeBackupNode()) << "Can't remove backup contents."; } #endif sdk-10.11.0/tests/integration/backup_test_utils.h000066400000000000000000000071041516266226600220330ustar00rootroot00000000000000/** * @file backup_test_utils.h * @brief This file defines a test fixture that involves basic operations on backup syncs. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mock_listeners.h" #include "SdkTest_test.h" using namespace sdk_test; using namespace testing; /** * @class SdkTestBackup * @brief Test fixture that allow to perform create, suspend, resume, and remove a sync backup. * * - The local folder is created in the current working directory with the name: * `TestSuite_TestName_dir`. * - The backup name in the cloud is set with the name: `myBackup`. */ class SdkTestBackup: public SdkTest { public: static constexpr auto mMaxTimeout{3min}; void SetUp() override { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); auto [succeeded, name] = ensureMyBackupsFolderExists(megaApi[0].get(), mMyBackupsFolderName); ASSERT_TRUE(succeeded) << "`My Backups` Folder could not be created/retrieved"; if (name != mMyBackupsFolderName) { mMyBackupsFolderName = name; myBackupsNameCustom = true; LOG_debug << "`My backups` folder has a custom name: " << name; } } void createBackupSync() { ASSERT_EQ(mBackupID, INVALID_HANDLE) << "There is already a backup/sync created."; mBackupID = backupFolder(megaApi[0].get(), path_u8string(getLocalFolderPath()), mBackupName); ASSERT_NE(mBackupID, INVALID_HANDLE) << "Cannot create Backup sync. Invalid Backup ID"; } void removeBackupSync() { if (const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync) { ASSERT_TRUE(::removeSync(megaApi[0].get(), mBackupID)) << "Cannot remove backup sync" << ". BackupID (" << toHandle(mBackupID) << ")"; mBackupID = INVALID_HANDLE; } } void suspendBackupSync() { ASSERT_NE(mBackupID, INVALID_HANDLE) << "Cant't suspend backup/sync. Invalid Backup ID"; ASSERT_TRUE(setSyncRunState(megaApi[0].get(), mBackupID, MegaSync::SyncRunningState::RUNSTATE_SUSPENDED)) << "Cannot suspend backup sync" << ". BackupID (" << toHandle(mBackupID) << ")"; } void resumeBackupSync() { ASSERT_NE(mBackupID, INVALID_HANDLE) << "Cant't resume backup/sync. Invalid Backup ID"; ASSERT_TRUE(setSyncRunState(megaApi[0].get(), mBackupID, MegaSync::SyncRunningState::RUNSTATE_RUNNING)) << "Cannot resume backup sync" << ". BackupID (" << toHandle(mBackupID) << ")"; } const fs::path& getLocalFolderPath() const { return mLocalTmpDir.getPath(); } MegaHandle getBackupId() const { return mBackupID; } std::string getBackupName() const { return mBackupName; } std::string getMyBackupsFolderName() const { return mMyBackupsFolderName; } bool hasMyBackupsFolderCustomName() const { return myBackupsNameCustom; } private: MegaHandle mBackupID{INVALID_HANDLE}; bool myBackupsNameCustom{false}; const std::string defaultBackupsName = "My Backups"; std::string mMyBackupsFolderName{defaultBackupsName}; const std::string mBackupName{"myBackup"}; const fs::path mLocalFolderName{getFilePrefix() + "dir"}; const LocalTempDir mLocalTmpDir{fs::current_path() / mLocalFolderName}; }; #endifsdk-10.11.0/tests/integration/common/000077500000000000000000000000001516266226600174245ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/client.cpp000066400000000000000000000471041516266226600214140ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { using namespace common; class Client::Uploader { // Called when a directory has been made or a file uploaded. void completed(std::unique_lock lock, ErrorOr result); void completed(ErrorOr result); // Called when a directory has been made. void made(Path& path, ErrorOr result); // Makes a new direcory. void make(const Path& path, NodeHandle parentHandle); // Uploads a file. void upload(Path path, NodeHandle parentHandle); // Called when a file has been uploaded. void uploaded(const Path& path, ErrorOr result); // What client's performing our uploads? Client& mClient; // Serializes access to members. std::mutex mLock; // Signalled when the upload has completed. std::promise mNotifier; // What directories are being made? std::atomic mPendingDirectories; // What files are being uploaded? std::map mPendingFiles; // Tracks overall result of the upload. std::atomic mResult; public: Uploader(Client& client); // Uploads the directory tree. ErrorOr operator()(const std::string& name, NodeHandle parentHandle, Path path); }; // Uploader ErrorOr Client::handle(NodeHandle parent, const std::string& name) const { return client().handle(parent, name); } void Client::makeDirectory(MakeDirectoryCallback callback, const std::string& name, NodeHandle parentHandle) { // Called when our directory has been made. auto made = [this](MakeDirectoryCallback& callback, ErrorOr result) { // Invokes our callback in a safe context. auto wrapper = [](MakeDirectoryCallback& callback, ErrorOr result, const Task& task) { // Client's being torn down. if (task.cancelled()) return callback(unexpected(API_EINCOMPLETE)); // Couldn't make the directory. if (!result) return callback(unexpected(result.error())); // Directory's been made. callback(result->mHandle); }; // wrapper // Invoke the callback in a safe context. execute(std::bind(std::move(wrapper), std::move(callback), std::move(result), std::placeholders::_1)); }; // result // Try and make the directory. client().makeDirectory(std::bind(std::move(made), std::move(callback), std::placeholders::_1), name, parentHandle); } ErrorOr Client::uploadFile(const std::string& name, NodeHandle parentHandle, const Path& path) { // Get the handle of any existing child with this name. auto handle = this->handle(parentHandle, name); // Create the upload. auto upload = client().upload(LocalPath(), name, parentHandle, path); // So we can wait for the upload's result. auto notifier = makeSharedPromise>(); // Called when our file's been bound. BoundCallback bound = [notifier](auto result) { notifier->set_value(result); }; // bound // Called when our file's data has been uploaded. UploadCallback uploaded = [bound, handle, notifier](auto result) { // Couldn't upload the file's data. if (!result) return notifier->set_value(unexpected(result.error())); // Bind the file. (*result)(std::move(bound), handle.valueOr(NodeHandle())); }; // uploaded // Try and upload the file. upload->begin(std::move(uploaded)); // Return the upload's result to the caller. return waitFor(notifier->get_future()); } Client::Client(const std::string&, const Path& databasePath, const Path& storagePath): mNodesCurrent(false), mNodesCurrentCV(), mNodesCurrentLock(), mDatabasePath(databasePath), mStoragePath(storagePath) {} Client::~Client() {} void Client::nodesCurrent(bool nodesCurrent) { // Safely set the value of mNodesCurrent. { std::lock_guard guard(mNodesCurrentLock); mNodesCurrent = nodesCurrent; } // Notify waiters. if (nodesCurrent) mNodesCurrentCV.notify_all(); } ErrorOr> Client::childNames(CloudPath path) const { // Try and resolve the parent's handle. auto parentHandle = path.resolve(*this); // Parent doesn't exist. if (!parentHandle) return unexpected(parentHandle.error()); // Retrieve the names of the parent's children. return client().childNames(*parentHandle); } void Client::desynchronize(::mega::handle id) { client().desynchronize(id); } Task Client::execute(std::function function) { // Sanity. assert(function); // Queue the function for execution. return client().execute(std::move(function)); } ErrorOr Client::get(CloudPath parentPath, const std::string& name) const { // Try and resolve the parent's handle. auto parentHandle = parentPath.resolve(*this); // Parent doens't exist. if (!parentHandle) return unexpected(parentHandle.error()); // Try and get info about the specified child. return client().get(*parentHandle, name); } ErrorOr Client::get(CloudPath path) const { auto handle = path.resolve(*this); if (handle) return client().get(*handle); return unexpected(handle.error()); } ErrorOr Client::get(NodeHandle handle, bool isPrivate, const void* key, std::size_t keyLength, const char* privateAuth, const char* publicAuth) { // So we can signal when we've retrieved the file's information. auto notifier = makeSharedPromise>(); // Called when we've retrieved the file's information. auto retrieved = [notifier](ErrorOr result) { notifier->set_value(std::move(result)); }; // retrieved // Try and retrieve the file's information. get(std::move(retrieved), handle, isPrivate, key, keyLength, privateAuth, publicAuth); // Return the file's information to our caller. return waitFor(notifier->get_future()); } auto Client::getPublicLink(CloudPath path) -> ErrorOr { // Try and resolve the path to a node handle. auto handle = path.resolve(*this); // Couldn't resolve the path to a node handle. if (!handle) return unexpected(handle.error()); // So we can signal when the link has been retrieved. auto notifier = makeSharedPromise>(); // Called when the link has been retrieved. auto linked = [notifier](ErrorOr result) { notifier->set_value(std::move(result)); }; // linked // Ask the client to get (or create) this node's public link. getPublicLink(std::move(linked), *handle); // Return the link to our caller. return waitFor(notifier->get_future()); } ErrorOr Client::handle(CloudPath parentPath, const std::string& name) const { // Resolve the parent's handle. auto parentHandle = parentPath.resolve(*this); // Parent doesn't exist. if (!parentHandle) return unexpected(parentHandle.error()); // Try and retrieve the child's handle. return client().handle(*parentHandle, name); } ErrorOr Client::handle(const std::string& path) const { // Try and locate the specified node. auto info = client().lookup(RemotePath(path), rootHandle()); // Found the specified node. if (info) return info->mHandle; // Couldn't locate the specified node. return unexpected(info.error()); } auto Client::keyData(CloudPath path, bool authorize) -> ErrorOr const { // Try and resolve the path to a node handle. auto handle = path.resolve(*this); // Path references a valid node. if (handle) return client().keyData(*handle, authorize); // Path doesn't reference a valid node. return unexpected(handle.error()); } Error Client::login(std::size_t accountIndex) { if (accountIndex >= getEnvVarAccounts().size()) return API_EFAILED; // Extract email, password from environment. const auto [email, password] = getEnvVarAccounts().getVarValues(accountIndex); // Email and/or password isn't present. if (email.empty() || password.empty()) return API_EFAILED; // Try and log the user in. return login(email, password); } ErrorOr Client::makeDirectory(const std::string& name, CloudPath parent) { assert(!name.empty()); auto parentHandle = parent.resolve(*this); if (!parentHandle) return unexpected(parentHandle.error()); auto result = client().makeDirectory(name, *parentHandle); if (result) return result->mHandle; return unexpected(result.error()); } Error Client::move(const std::string& name, CloudPath source, CloudPath target) { // Sanity. assert(!name.empty()); auto sourceHandle = source.resolve(*this); auto targetHandle = target.resolve(*this); // Source doesn't exist. if (!sourceHandle) return sourceHandle.error(); // Target doesn't exist. if (!targetHandle) return targetHandle.error(); // Move the node. return client().move(name, *sourceHandle, *targetHandle); } auto Client::partialDownload(PartialDownloadCallback& callback, CloudPath path, std::uint64_t length, std::uint64_t offset) -> ErrorOr { auto handle = path.resolve(*this); if (handle) return client().partialDownload(callback, *handle, length, offset); return unexpected(handle.error()); } auto Client::partialDownload(PartialDownloadCallback& callback, PublicLink link, std::uint64_t length, std::uint64_t offset) -> ErrorOr { // Try and extract the file's handle and key from the link. auto keyAndHandle = parsePublicLink(link); // Couldn't parse the link. if (!keyAndHandle) return unexpected(keyAndHandle.error()); // Convenience. auto& [handle, key] = *keyAndHandle; // Try and get information about the file. auto info = get(handle, false, key.data(), key.size(), {}, {}); // Couldn't get information about the file. if (!info) return unexpected(info.error()); // Convenience. auto size = static_cast(info->mSize); // Sanitize offset and length. offset = std::min(offset, size); length = std::min(length, size - offset); // Instantiate and populate node key data. NodeKeyData keyData; keyData.mIsPublicHandle = true; keyData.mKeyAndIV = key; // Return partial download to our caller. return client().partialDownload(callback, handle, keyData, length, offset); } Error Client::remove(CloudPath path) { auto handle = path.resolve(*this); if (handle) return client().remove(*handle); return handle.error(); } Error Client::removeAll(CloudPath path) { auto handle = path.resolve(*this); if (handle) return client().removeAll(*handle); return handle.error(); } Error Client::replace(CloudPath source, CloudPath target) { auto sourceHandle = source.resolve(*this); auto targetHandle = target.resolve(*this); // Source doesn't exist. if (!sourceHandle) return sourceHandle.error(); // Target doesn't exist. if (!targetHandle) return targetHandle.error(); // Replace target with source. return client().replace(*sourceHandle, *targetHandle); } ErrorOr Client::storageInfo() { return client().storageInfo(); } const Path& Client::storagePath() const { return mStoragePath; } auto Client::synchronize(const Path& path, CloudPath target) -> std::tuple<::mega::handle, Error, SyncError> { return client().synchronize(path.localPath(), target.resolve(*this).valueOr(NodeHandle())); } ErrorOr Client::upload(const std::string& name, CloudPath parent, const Path& path) { // Sanity. assert(!name.empty()); // Resolve parent node. auto parentHandle = parent.resolve(*this); // Parent doesn't exist. if (!parentHandle) return unexpected(parentHandle.error()); std::error_code error; // What kind of entity are we uploading? auto status = fs::status(path, error); // Couldn't determine type of entity. if (error) return unexpected(API_EREAD); // Upload the entity. switch (status.type()) { case fs::file_type::directory: return Uploader(*this)(name, *parentHandle, path); case fs::file_type::regular: return uploadFile(name, *parentHandle, path); default: break; } // Can't upload something that isn't a directory or file. return unexpected(API_EARGS); } ErrorOr Client::upload(const std::string& content, const std::string& name, CloudPath parent) try { // Find out where we can create temporary files. auto temporaryPath = fs::temp_directory_path(); // Create a temporary file for us to upload. File temporary(content, name, temporaryPath); // Upload the file to the cloud. return upload(name, std::move(parent), temporary.path()); } catch (...) { return unexpected(API_EFAILED); } ErrorOr Client::upload(CloudPath parent, const Path& path) { return upload(path.localPath().leafName().toPath(false), parent, path); } Error Client::waitForNodesCurrent(TimePoint when) { // Acquire lock. std::unique_lock lock(mNodesCurrentLock); // Checks if our view of the cloud is current. auto isCurrent = [&]() { return mNodesCurrent; }; // isCurrent // Wait until when for our view to become current. if (!mNodesCurrentCV.wait_until(lock, when, isCurrent)) return API_EFAILED; // Our view of the cloud is current. return API_OK; } void Client::Uploader::completed(std::unique_lock lock, ErrorOr result) { // Operation couldn't be completed. if (!result && mResult == API_OK) { // Latch error. mResult = result.error(); // Try and cancel any pending uploads. for (auto& f: mPendingFiles) f.second->cancel(); } // Some directories are still being made. if (mPendingDirectories) return; // Some files are still being uploaded. if (!mPendingFiles.empty()) return; // Release lock. lock.unlock(); // Upload's complete. mNotifier.set_value(); } void Client::Uploader::completed(ErrorOr result) { completed(std::unique_lock(mLock), std::move(result)); } void Client::Uploader::made(Path& path, ErrorOr result) { // Sanity. assert(mPendingDirectories); // Attempt to make the directory has completed. --mPendingDirectories; // Couldn't make the directory. if (!result) return completed(std::move(result)); // Some other operation couldn't complete. if (mResult != API_OK) return completed(std::move(result)); std::error_code error; // Try and open the directory for iteration. auto i = fs::directory_iterator(path, error); auto j = fs::directory_iterator(); // Couldn't open directory for iteration. if (error) return completed(unexpected(API_EREAD)); // Upload this directory's content. for (; i != j; ++i) { auto path_ = i->path(); auto type = i->status().type(); switch (type) { case fs::file_type::directory: make(std::move(path_), *result); break; case fs::file_type::regular: upload(std::move(path_), *result); break; default: break; } } // Directory's been made. completed(std::move(result)); } void Client::Uploader::make(const Path& path, NodeHandle parentHandle) { // Record that a directory is being made. ++mPendingDirectories; // Try and make the directory. mClient.makeDirectory(std::bind(&Uploader::made, this, path, std::placeholders::_1), path_u8string(path.path().filename()), parentHandle); } void Client::Uploader::upload(Path path, NodeHandle parentHandle) { // Acquire lock. std::unique_lock lock(mLock); // Create the upload. auto upload = mClient.client().upload(LocalPath(), path_u8string(path.path().filename()), parentHandle, path); // Record that a file is being uploaded. auto i = mPendingFiles.emplace(path, upload); // Sanity. assert(i.second); // So we can use our uploaded method as a callback. BoundCallback uploaded = std::bind(&Uploader::uploaded, this, std::move(path), std::placeholders::_1); // Try and upload the file. upload->begin(std::move(uploaded)); // Silence compiler. static_cast(i); } void Client::Uploader::uploaded(const Path& path, ErrorOr result) { // Acquire lock. std::unique_lock lock(mLock); // Upload's completed. auto count = mPendingFiles.erase(path); // Sanity. assert(count); // Silence compiler. static_cast(count); // Report the file's been uploaded. completed(std::move(lock), std::move(result)); } Client::Uploader::Uploader(Client& client): mClient(client), mLock(), mNotifier(), mPendingDirectories{1u}, mPendingFiles(), mResult{API_OK} {} ErrorOr Client::Uploader::operator()(const std::string& name, NodeHandle parentHandle, Path path) { // Try and make the root directory. auto handle = mClient.makeDirectory(name, parentHandle); // Couldn't make the root directory. if (!handle) return handle; // Try and upload the root directory's content. made(path, *handle); // Wait for the upload to complete. mNotifier.get_future().get(); // Couldn't upload the root's contents. if (mResult != API_OK) return unexpected(mResult.load()); // The root's content has been uploaded. return handle; } Client::PublicLink::PublicLink(const std::string& link): mLink(link) {} const std::string& Client::PublicLink::get() const { return mLink; } Client::SessionToken::SessionToken(const std::string& value): mValue(value) {} const std::string& Client::SessionToken::get() const { return mValue; } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/cloud_path.cpp000066400000000000000000000011311516266226600222460ustar00rootroot00000000000000#include #include #include namespace mega { namespace common { namespace testing { CloudPath::CloudPath(const std::string& path): mHandle(), mPath(path) {} CloudPath::CloudPath(const char* path): mHandle(), mPath(path) {} CloudPath::CloudPath(NodeHandle handle): mHandle(handle), mPath() {} ErrorOr CloudPath::resolve(const Client& client) const { if (mHandle.isUndef()) return client.handle(mPath); return mHandle; } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/common.cmake000066400000000000000000000047111516266226600217210ustar00rootroot00000000000000target_include_directories(test_integration PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/common ) target_link_libraries(test_integration PRIVATE MEGA::CommonHeaderPaths ) target_sources(test_integration PRIVATE common/mega/common/testing/client.h common/mega/common/testing/client_forward.h common/mega/common/testing/cloud_path.h common/mega/common/testing/cloud_path_forward.h common/mega/common/testing/directory.h common/mega/common/testing/file.h common/mega/common/testing/model.h common/mega/common/testing/model_forward.h common/mega/common/testing/path.h common/mega/common/testing/path_forward.h common/mega/common/testing/printers.h common/mega/common/testing/real_client.h common/mega/common/testing/single_client_test.h common/mega/common/testing/test.h common/mega/common/testing/utility.h common/mega/common/testing/watchdog.h ) target_sources(test_integration PRIVATE common/client.cpp common/cloud_path.cpp common/directory.cpp common/file.cpp common/model.cpp common/partial_download_tests.cpp common/path.cpp common/printers.cpp common/real_client.cpp common/utility.cpp common/watchdog.cpp ) target_include_directories(test_integration PRIVATE $<$:${CMAKE_CURRENT_SOURCE_DIR}/common/posix> $<$:${CMAKE_CURRENT_SOURCE_DIR}/common/windows> ) target_sources_conditional(test_integration FLAG UNIX PRIVATE common/posix/utility.cpp ) target_sources_conditional(test_integration FLAG WIN32 PRIVATE common/windows/utility.cpp ) sdk-10.11.0/tests/integration/common/directory.cpp000066400000000000000000000012231516266226600221320ustar00rootroot00000000000000#include #include namespace mega { namespace common { namespace testing { Directory::Directory(const std::string& name, const Path& parentPath): mPath(parentPath.path() / u8path_compat(name)) { fs::create_directories(mPath.path()); } Directory::Directory(const std::string& name): mPath(name) {} Directory::~Directory() { std::error_code error; fs::remove_all(mPath.path(), error); if (!error) return; LOG_warn << "Unable to remove directory at: " << mPath.localPath(); } const Path& Directory::path() const { return mPath; } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/file.cpp000066400000000000000000000020661516266226600210530ustar00rootroot00000000000000#include #include #include namespace mega { namespace common { namespace testing { File::File(const std::string& content, const std::string& name, const Path& parentPath): mPath(parentPath.path() / u8path_compat(name)) { std::ofstream ostream; // Throw on failure. ostream.exceptions(std::ios::badbit | std::ios::failbit); // Open file for writing. ostream.open(mPath.string(), std::ios::binary | std::ios::trunc); // Write data to the file. ostream.write(content.data(), static_cast(content.size())); // Flush content to disk. ostream.flush(); } File::File(const std::string& content, const std::string& name): File(content, name, fs::current_path()) {} File::~File() { std::error_code error; // Try and remove the file. fs::remove(mPath, error); if (!error) return; LOG_warn << "Unable to remove file at: " << mPath.localPath(); } const Path& File::path() const { return mPath; } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/mega/000077500000000000000000000000001516266226600203355ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/mega/common/000077500000000000000000000000001516266226600216255ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/mega/common/testing/000077500000000000000000000000001516266226600233025ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/mega/common/testing/client.h000066400000000000000000000270301516266226600247330ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { class Client { public: class PublicLink; private: // Responsible for uploading directory trees. class Uploader; // Get our hands on the client's high level interface. virtual common::Client& client() const = 0; // Retrieve the handle associated with the specified child. common::ErrorOr handle(NodeHandle parent, const std::string& name) const; using MakeDirectoryCallback = std::function)>; // Create a directory in the cloud. void makeDirectory(MakeDirectoryCallback callback, const std::string& name, NodeHandle parentHandle); // Upload a file to the cloud. common::ErrorOr uploadFile(const std::string& name, NodeHandle parentHandle, const common::testing::Path& path); // Is our view of the cloud current? bool mNodesCurrent; // Signalled when our view of the cloud is current. std::condition_variable mNodesCurrentCV; // Necessary to wait on mNodesCurrentCV. std::mutex mNodesCurrentLock; protected: Client(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); // Retrieve information about a foreign node. using GetCallback = std::function)>; virtual void get(GetCallback callback, NodeHandle handle, bool isPrivate, const void* key, std::size_t keyLength, const char* privateAuth, const char* publicAuth) = 0; // Get (or create) a public link for the specified node. using GetPublicLinkCallback = std::function)>; virtual void getPublicLink(GetPublicLinkCallback callback, NodeHandle handle) = 0; // Specify whether our view of the cloud is current. void nodesCurrent(bool nodesCurrent); // Extract the public node handle and decryption key from a public link. virtual auto parsePublicLink(const PublicLink& link) -> common::ErrorOr> = 0; // Where should we create our databases? const common::testing::Path mDatabasePath; // Where should we create our test files? const common::testing::Path mStoragePath; public: // Represents an individual contact. class Contact { protected: Contact() = default; public: virtual ~Contact() = default; // Remove the contact. virtual Error remove() = 0; // Has this contact been verified? virtual bool verified() const = 0; // Verify the contact. virtual Error verify() = 0; }; // Contact // Represents an invitation to be friends. class Invite { protected: Invite() = default; public: virtual ~Invite() = default; // Accept the invitation. virtual Error accept() = 0; // Cancel the invitation. virtual Error cancel() = 0; // Decline the invitation. virtual Error decline() = 0; }; // Invite // Represents a public link. class PublicLink { // The link's actual URI. std::string mLink; public: explicit PublicLink(const std::string& link); // Retrieve the link's actual URI. const std::string& get() const; }; // PublicLink // Represents a session token. class SessionToken { // The session token's actual value. std::string mValue; public: explicit SessionToken(const std::string& value); // Retrieve this session token's actual value. const std::string& get() const; }; // SessionToken // Convenience. using Clock = std::chrono::steady_clock; using ContactPtr = std::unique_ptr; using Handle = ::mega::handle; using InvitePtr = std::unique_ptr; template using Duration = std::chrono::duration; using TimePoint = Clock::time_point; virtual ~Client(); // Retrieve the names of this node's children. common::ErrorOr> childNames(common::testing::CloudPath path) const; // Is the specified user a contact? virtual auto contact(const std::string& email) const -> ContactPtr = 0; // Remove a sync previously created with synchronize(...) void desynchronize(::mega::handle id); // What is the email of the currenty logged in user? virtual std::string email() const = 0; // Execute some function on the client thread. common::Task execute(std::function function); // Retrieve information about a specific child. common::ErrorOr get(common::testing::CloudPath parentPath, const std::string& name) const; // Retrieve information about a node. common::ErrorOr get(common::testing::CloudPath path) const; // Retrieve information about a foreign node. common::ErrorOr get(NodeHandle handle, bool isPrivate, const void* key, std::size_t keyLength, const char* privateAuth, const char* publicAuth); // Get (or create) a public lionk for the specified node. auto getPublicLink(common::testing::CloudPath path) -> ErrorOr; // Query what a child's node handle is. common::ErrorOr handle(common::testing::CloudPath parentPath, const std::string& name) const; // Retrieve the handle of the node at the specified path. common::ErrorOr handle(const std::string& path) const; // Query whether a node has file attributes. virtual bool hasFileAttribute(NodeHandle handle, fatype type) const = 0; // Send a friendship invite to the specified user. virtual auto invite(const std::string& email) -> common::ErrorOr = 0; // Is a friendship invite associated with the specified user? virtual auto invited(const std::string& email) const -> InvitePtr = 0; // Retrieve a node's key data. auto keyData(CloudPath path, bool authorize) -> ErrorOr const; // Try and log the specified user in. virtual Error login(const std::string& email, const std::string& password) = 0; // Try and log into a user specified in the environment. Error login(std::size_t accountIndex); // Try and log into a directory via public link. virtual Error login(const PublicLink& link) = 0; // Try and log the user into an existing session. virtual Error login(const SessionToken& sessionToken) = 0; // Check if the user is logged in. virtual sessiontype_t loggedIn() const = 0; // Try and log the user out. virtual Error logout(bool keepSession) = 0; // Create a directory in the cloud. common::ErrorOr makeDirectory(const std::string& name, common::testing::CloudPath parent); // Move a node in the cloud. Error move(const std::string& name, common::testing::CloudPath source, common::testing::CloudPath target); // Download part of a local file from the cloud. auto partialDownload(common::PartialDownloadCallback& callback, common::testing::CloudPath path, std::uint64_t length, std::uint64_t offset) -> common::ErrorOr; // Download part of a foreign file from the cloud. auto partialDownload(common::PartialDownloadCallback& callback, PublicLink link, std::uint64_t length, std::uint64_t offset) -> common::ErrorOr; // Reload the cloud tree. virtual Error reload() = 0; // Remove a node in the cloud. Error remove(common::testing::CloudPath path); // Remove all children beneath the specified node. Error removeAll(common::testing::CloudPath path); // Replace a node in the cloud. Error replace(common::testing::CloudPath source, common::testing::CloudPath target); // Retrieve the handle of the root node. virtual NodeHandle rootHandle() const = 0; // Retrieve this user's session token. virtual auto sessionToken() const -> ErrorOr = 0; // Set the client's maximum download speed. virtual m_off_t setDownloadSpeed(m_off_t speed) = 0; // Set the client's maximum upload speed. virtual m_off_t setUploadSpeed(m_off_t speed) = 0; // Share a directory with another user. virtual Error share(const std::string& email, common::testing::CloudPath path, accesslevel_t permissions) = 0; // Check if a directory has already been shared with the specified user. virtual bool shared(const std::string& email, common::testing::CloudPath path, accesslevel_t permissions) const = 0; // Retrieve storage statistics from the cloud. common::ErrorOr storageInfo(); // Where are we storing our files const common::testing::Path& storagePath() const; // Synchronize a local tree against some location in the cloud. auto synchronize(const common::testing::Path& path, common::testing::CloudPath target) -> std::tuple<::mega::handle, Error, SyncError>; // Upload a directory tree or file to the cloud. common::ErrorOr upload(const std::string& name, common::testing::CloudPath parent, const common::testing::Path& path); common::ErrorOr upload(const std::string& content, const std::string& name, common::testing::CloudPath parent); common::ErrorOr upload(common::testing::CloudPath parent, const common::testing::Path& path); // Specify whether files should be versioned. virtual void useVersioning(bool useVersioning) = 0; // Wait until when for our view of the cloud to be current. Error waitForNodesCurrent(TimePoint when); // Wait for our view of the cloud to be current. template Error waitForNodesCurrent(Duration delay) { return waitForNodesCurrent(Clock::now() + delay); } }; // Client } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/client_forward.h000066400000000000000000000002701516266226600264540ustar00rootroot00000000000000#pragma once #include namespace mega { namespace common { namespace testing { class Client; using ClientPtr = std::unique_ptr; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/cloud_path.h000066400000000000000000000014401516266226600255740ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { namespace testing { class CloudPath { NodeHandle mHandle; std::string mPath; public: CloudPath() = default; CloudPath(const CloudPath& other) = default; CloudPath(CloudPath&& other) = default; CloudPath(const std::string& path); CloudPath(const char* path); CloudPath(NodeHandle handle); ~CloudPath() = default; CloudPath& operator=(const CloudPath& rhs) = default; CloudPath& operator=(CloudPath&& rhs) = default; ErrorOr resolve(const Client& client) const; }; // CloudPath } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/cloud_path_forward.h000066400000000000000000000001741516266226600273230ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace testing { class CloudPath; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/directory.h000066400000000000000000000005731516266226600254640ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { namespace testing { class Directory { Path mPath; public: Directory(const std::string& name, const Path& parentPath); Directory(const std::string& name); ~Directory(); const Path& path() const; }; // Directory } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/file.h000066400000000000000000000006331516266226600243740ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { namespace testing { class File { Path mPath; public: File(const std::string& content, const std::string& name, const Path& parentPath); File(const std::string& content, const std::string& name); ~File(); const Path& path() const; }; // File } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/model.h000066400000000000000000000136001516266226600245530ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { class Model { public: class DirectoryNode; class FileNode; class Node; // Convenience. using DirectoryNodePtr = std::unique_ptr; using FileNodePtr = std::unique_ptr; using NodePtr = std::unique_ptr; using NodeMap = std::map; // Describes an entity in the model. class Node { protected: Node(std::string name); // Swap this node's attributes with anothers. void swap(Node& other); public: virtual ~Node() = default; // Create a copy of this node. virtual auto copy() -> NodePtr = 0; // Does this node represent a directory? auto directory() const -> const DirectoryNode*; virtual auto directory() -> DirectoryNode*; // Does this node represent a file? auto file() const -> const FileNode*; virtual auto file() -> FileNode*; // Check if this node matches another. virtual bool match(const std::string& path, const Node& rhs) const = 0; // Populate path with the contents of this node. virtual void populate(fs::path path) const = 0; // When was this node last modified? DateTime mModified; // The name of the node. std::string mName; }; // Node // Describes a directory in the model. class DirectoryNode: public Node { // Who are this directory's children? NodeMap mChildren; public: DirectoryNode(std::string name); DirectoryNode(const DirectoryNode& other); DirectoryNode(DirectoryNode&& other); auto operator=(DirectoryNode&& rhs) -> DirectoryNode&; // Add a child to this directory. auto add(NodePtr child) -> Node*; // Who are this directory's children? auto children() const -> const NodeMap&; // Create a copy of this directory. auto copy() -> NodePtr override; // Return a reference to this directory. auto directory() -> DirectoryNode* override; // Create a directory based on the content of the cloud. static auto from(const Client& client, NodeInfo info) -> NodePtr; // Create a directory based on the content of path. static auto from(const fs::path& path) -> NodePtr; // Locate a child in this directory. auto get(const std::string& name) const -> const Node*; auto get(const std::string& name) -> Node*; // Check if this node matches another. bool match(const std::string& path, const Node& rhs) const override; // Populate path with the contents of this directory. void populate(fs::path path) const override; // Remove a child from this directory. auto remove(const std::string& name) -> NodePtr; // Swap this directory's attributes with another. void swap(DirectoryNode& other); }; // DirectoryNode // Describes a file in the model. class FileNode: public Node { public: FileNode(std::string name); // Create a copy of this file. auto copy() -> NodePtr override; // Return a reference to this file. auto file() -> FileNode* override; // Create a file based on the content of the cloud. static auto from(const Client& client, NodeInfo info) -> NodePtr; // Create a file based on the content of path. static auto from(const fs::path& path) -> NodePtr; // Check if this node matches another. bool match(const std::string& path, const Node& rhs) const override; // Populate path with the contents of this file. void populate(fs::path path) const override; // What is this file's content? std::string mContent; // How large is this file? std::uint64_t mSize; }; // FileNode Model(); Model(const Model& other); Model(Model&& other); Model& operator=(const Model& rhs); Model& operator=(Model&& rhs); // Add a node to the model. auto add(NodePtr child, const std::string& parentPath) -> Node*; // Create a new directory node. static auto directory(const std::string& name) -> DirectoryNodePtr; // Create a new file node. static auto file(const std::string& name, const std::string& content) -> FileNodePtr; static auto file(const std::string& name) -> FileNodePtr; // Build a model based on the contents of the cloud. static Model from(const Client& client, CloudPath path); // Build a model based on the contents of path. static Model from(const Path& path); // Generate a model. static Model generate(const std::string& prefix, std::size_t height, std::size_t numDirectories, std::size_t numFiles); // Locate a node in the model. auto get(const std::string& path) const -> const Node*; auto get(const std::string& path) -> Node*; // Check if this model matches another. bool match(const Model& rhs) const; // Populate path with the contents of this model. void populate(const Path& path) const; // Remove a node from the model. auto remove(const std::string& path) -> NodePtr; // Swap this model's content with another. void swap(Model& other); private: // The root directory of this model. DirectoryNode mRoot; }; // Model // Swap one model with another. void swap(Model& lhs, Model& rhs); } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/model_forward.h000066400000000000000000000002141516266226600262740ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace testing { class Model; class ModelVisitor; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/path.h000066400000000000000000000020651516266226600244120ustar00rootroot00000000000000#pragma once #include #include #include #include namespace mega { namespace common { namespace testing { class Path { fs::path mPath; public: Path() = default; Path(const Path& other) = default; Path(Path&& other) = default; Path(const LocalPath& path); Path(const fs::path& path); Path(const std::string& path); Path(const char* path); Path& operator/=(const Path& rhs); Path operator/(const Path& rhs) const; Path& operator=(const Path& rhs) = default; Path& operator=(Path&& rhs) = default; bool operator==(const Path& rhs) const; bool operator<(const Path& rhs) const; bool operator!=(const Path& rhs) const; operator LocalPath() const; operator fs::path() const; operator std::string() const; LocalPath localPath() const; fs::path path() const; std::string string() const; }; // Path std::ostream& operator<<(std::ostream& ostream, const Path& path); } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/path_forward.h000066400000000000000000000001671516266226600261370ustar00rootroot00000000000000#pragma once namespace mega { namespace common { namespace testing { class Path; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/printers.h000066400000000000000000000003171516266226600253220ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { std::ostream& operator<<(std::ostream& ostream, const DateTime& value); } // fuse } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/real_client.h000066400000000000000000000136211516266226600257370ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { class RealClient: public virtual Client, protected MegaApp { class RealContact; class RealInvite; // What kind of function handles a request's response? using RequestCallback = std::function; // What kind of a request are we waiting for? enum RequestType { RT_CATCHUP, RT_FETCH }; // RequestType // [type, tag] should uniquely identify a request. using RequestKey = std::pair; // Associates a callback with a pending request. using RequestCallbackMap = std::map; // Retrieve this client's high-level interface. common::Client& client() const override; // What is the email of the currenty logged in user? std::string email() const override; // Try and retrieve a description of the user's cloud content. Error fetch(bool ignoreCache); // Called when we receive a response for a fetch request. void fetchnodes_result(const Error& result) override; // Is a friendship invite associated with the specified user? auto invited(const std::string& email, std::unique_lock& lock) const -> InvitePtr; // Try and login the specified user. Error login(const std::string& email, const std::string& password, const std::string& salt); // Peforms client activity. void loop(); // Called when the client emits a "nodes current" event. void nodes_current() override; // Extract the public node handle and decryption key from a public link. auto parsePublicLink(const PublicLink& link) -> common::ErrorOr> override; // Prepare the specified node for sharing. Error openShareDialog(NodeHandle handle); // Try and retrieve the user's salt. common::ErrorOr prelogin(const std::string& email); // Called when a request has completed. void requestCompleted(RequestKey key, Error result); // Set the client's transfer speed. m_off_t setTransferSpeed(m_off_t (MegaClient::*get)(), bool (MegaClient::*set)(m_off_t), m_off_t speed); // Check if a directory has already been shared with the specified user. bool shared(const std::string& email, NodeHandle handle, accesslevel_t permissions) const; protected: // Retrieve information about a foreign node. void get(GetCallback callback, NodeHandle handle, bool isPrivate, const void* key, std::size_t keyLength, const char* privateAuth, const char* publicAuth) override; // Get (or create) a public link for the specified node. void getPublicLink(GetPublicLinkCallback callback, NodeHandle handle) override; // The actual client. std::unique_ptr mClient; // Serializes access to mClient. mutable std::mutex mClientLock; private: // Signals when the client's worker thread should terminate. std::atomic mClientTerminate; // The client's worker thread. std::thread mClientThread; // How the client performs HTTP requests. std::unique_ptr mHTTPIO; // Tracks pending requests. RequestCallbackMap mPendingRequests; // Generates thumbnails for media files. std::unique_ptr mGfxProc; protected: // How the client waits for activity to occur. std::shared_ptr mWaiter; public: RealClient(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); virtual ~RealClient(); // Is the specified user a contact? auto contact(const std::string& email) const -> ContactPtr override; // Query whether a node has file attributes. bool hasFileAttribute(NodeHandle handle, fatype type) const override; // Send a friendship invite to the specified user. auto invite(const std::string& email) -> common::ErrorOr override; // Is a friendship invite associated with the specified user? auto invited(const std::string& email) const -> InvitePtr override; // Try and log the specified user in. Error login(const std::string& email, const std::string& password) override; // Try and log into a directory via public link. Error login(const PublicLink& link) override; // Try and log the user into an existing session. Error login(const SessionToken& sessionToken) override; // Check if the user is logged in. sessiontype_t loggedIn() const override; // Try and log the user out. Error logout(bool keepSession) override; // Reload the cloud tree. Error reload() override; // Retrieve the handle of the root node. NodeHandle rootHandle() const override; // Retrieve this user's session token. auto sessionToken() const -> ErrorOr override; // Set the client's maximum download speed. m_off_t setDownloadSpeed(m_off_t speed) override; // Set the client's maximum upload speed. m_off_t setUploadSpeed(m_off_t speed) override; // Share a directory with another user. Error share(const std::string& email, common::testing::CloudPath path, accesslevel_t permissions) override; // Check if a directory has already been shared with the specified user. bool shared(const std::string& email, common::testing::CloudPath path, accesslevel_t permissions) const override; // Specify whether files should be versioned. void useVersioning(bool useVersioning) override; }; // RealClient } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/single_client_test.h000066400000000000000000000022661516266226600273370ustar00rootroot00000000000000#pragma once #include #include namespace mega { namespace common { namespace testing { template class SingleClientTest: public Test { public: using Client = typename Test::Client; using ClientPtr = typename Test::ClientPtr; // Perform fixture-wide setup. static void SetUpTestSuite() { Test::SetUpTestSuite(); // Create our test client. mClient = SingleClientTest::CreateClient("read-write"); ASSERT_TRUE(mClient); // Log in the client. ASSERT_EQ(mClient->login(0), API_OK); } // Perform instance-specific setup. void SetUp() override { // Make sure our client is sane. ASSERT_TRUE(mClient); } // Perform fixture-wide teardown. static void TearDownTestSuite() { // Clean up our client. mClient.reset(); } // The client we're using to interact with the cloud. static ClientPtr mClient; }; // SingleClientTest template typename SingleClientTest::ClientPtr SingleClientTest::mClient; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/test.h000066400000000000000000000054501516266226600244360ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace mega { namespace common { namespace testing { template class Test: public ::testing::Test { // Where will our clients put their databases? static Path mDatabasePath; // Where will out clients put their local state? static Path mStoragePath; public: // Convenience. using Client = typename Traits::AbstractClient; using ClientPtr = std::unique_ptr; // Create a client. static ClientPtr CreateClient(const std::string& name) { // Convenience. using std::filesystem::create_directories; // Where should this client store its databases? auto databasePath = mDatabasePath.path() / name; // Where should this client store its local state? auto storagePath = mStoragePath.path() / name; // So we know whether the directories were created. std::error_code error; create_directories(databasePath, error); // Couldn't create database path. if (error) return nullptr; create_directories(storagePath, error); // Couldn't create storage path. if (error) return nullptr; // Convenience. using ConcreteClient = typename Traits::ConcreteClient; // Return client to caller. return std::make_unique(name, databasePath, storagePath); } // Perform fixture-wide setup. static void SetUpTestSuite() { // Compute paths. auto rootPath = makeNewTestRoot() / u8path_compat(Traits::mName); // Where should our clients store their databases? mDatabasePath = rootPath / "db"; // Where can our tests store temporary state? mScratchPath = rootPath / "scratch"; // Where should our clients store their state? mStoragePath = rootPath / "storage"; std::error_code error; // Make sure our paths exist. auto paths = {&mDatabasePath, &mScratchPath, &mStoragePath}; for (const auto* path: paths) { std::filesystem::create_directories(*path, error); ASSERT_FALSE(error); } } // How long should we wait for something to happen? static const std::chrono::minutes mDefaultTimeout; // Where should we store temporary state? static common::testing::Path mScratchPath; }; // Test template Path Test::mDatabasePath; template const std::chrono::minutes Test::mDefaultTimeout(1); template Path Test::mScratchPath; template Path Test::mStoragePath; } // testing } // common } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/utility.h000066400000000000000000000132331516266226600251600ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { struct FileFingerprint; namespace common { namespace testing { template bool allOf(Container&& container, Predicate predicate) { return std::all_of(std::begin(container), std::end(container), std::move(predicate)); } template bool anyOf(Container&& container, Predicate predicate) { return std::any_of(std::begin(container), std::end(container), std::move(predicate)); } template auto befriend(Client& client0, Client& client1, std::chrono::duration timeout) -> std::enable_if_t, Error> { // Both clients should be logged in. if (client0.loggedIn() != FULLACCOUNT) return API_EARGS; if (client1.loggedIn() != FULLACCOUNT) return API_EARGS; auto email0 = client0.email(); auto email1 = client1.email(); // The clients shouldn't be logged in as the same user. if (email0 == email1) return API_EARGS; auto contact0 = client0.contact(email1); auto contact1 = client1.contact(email0); // The users aren't friends. if (!contact0 && !contact1) { // Try and send a friend invitation. auto invited = client0.invite(email1); // Couldn't send the friend invite. if (!invited) return invited.error(); // Wait for our invitation to be received. auto invite = waitFor( [&]() { return client1.invited(email0); }, timeout, nullptr); // Invite wasn't received. if (!invite) return LOCAL_ETIMEOUT; // Try and accept the invitation. auto accepted = invite->accept(); // Couldn't accept the invitation. if (accepted != API_OK) return accepted; // Wait for friendship to be confirmed. auto confirmed = waitFor( [&]() { contact0 = client0.contact(email1); contact1 = client1.contact(email0); return contact0 && contact1; }, timeout); // Couldn't confirm friendship. if (!confirmed) return LOCAL_ETIMEOUT; } // Contacts should be visible at this point. assert(contact0); assert(contact1); // Verifies a contact, if necessary. auto verify = [&](auto& contact) { // Try and verify the contact. auto result = contact.verify(); // Couldn't verify the contact. if (result != API_OK) return result; // Wait for verification to complete. auto verified = waitFor( [&]() { return contact.verified(); }, timeout); // Verification timed out. if (!verified) return Error(LOCAL_ETIMEOUT); // Verification's complete. return Error(API_OK); }; // verify // Try and verify friendship. auto verified = verify(*contact0); if (verified == API_OK) return verify(*contact1); return verified; } ErrorOr fingerprint(const std::string& content, std::chrono::system_clock::time_point modified); ErrorOr fingerprint(const Path& path); template void forEach(Container&& container, Function function) { std::for_each(std::begin(container), std::end(container), std::move(function)); } DateTime lastWriteTime(const Path& path, std::error_code& result); DateTime lastWriteTime(const Path& path); void lastWriteTime(const Path path, const DateTime& modified, std::error_code& result); void lastWriteTime(const Path& path, const DateTime& modified); std::string randomBytes(std::size_t length); std::string randomName(); template()())> auto waitUntil(Predicate&& predicate, std::chrono::steady_clock::time_point when, Result defaultValue = Result()) -> decltype(predicate()) { // How long should we wait between tests? constexpr auto step = std::chrono::milliseconds(256); // Wait until when for predicate to be satisifed. while (true) { // Convenience. auto now = std::chrono::steady_clock::now(); // Predicate is satisfied. if (auto result = predicate()) return result; // Predicate's taken too long to be satisfied. if (now >= when) return defaultValue; // When should we test the predicate again? auto next = std::min(now + step, when); // Sleep until then. std::this_thread::sleep_until(next); } } template()())> auto waitFor(Predicate&& predicate, std::chrono::duration timeout, Result defaultValue = Result()) -> decltype(predicate()) { return waitUntil(std::move(predicate), std::chrono::steady_clock::now() + timeout, defaultValue); } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/mega/common/testing/watchdog.h000066400000000000000000000026201516266226600252530ustar00rootroot00000000000000#pragma once #include #include #include namespace mega { namespace common { namespace testing { class Watchdog { Logger& mLogger; TaskExecutor mExecutor; Task mTask; public: Watchdog(Logger& logger); // Arm the watchdog to expire at some point in time. void arm(std::chrono::steady_clock::time_point when); // Arm the watchdog to expire at some point in the future. template void arm(std::chrono::duration when) { arm(std::chrono::steady_clock::now() + when); } // Disarm the watchdog. void disarm(); }; // Watchdog class ScopedWatch { Watchdog* mWatchdog; public: ScopedWatch(Watchdog& watchdog, std::chrono::steady_clock::time_point when): mWatchdog(&watchdog) { mWatchdog->arm(when); } template ScopedWatch(Watchdog& watchdog, std::chrono::duration when): ScopedWatch(watchdog, std::chrono::steady_clock::now() + when) {} ScopedWatch(const ScopedWatch& other) = delete; ~ScopedWatch() { if (mWatchdog) mWatchdog->disarm(); } ScopedWatch& operator=(const ScopedWatch& rhs) = delete; void release() { mWatchdog = nullptr; } }; // ScopedWatch } // testing } // common } // mega sdk-10.11.0/tests/integration/common/model.cpp000066400000000000000000000415711516266226600212400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { static Model::DirectoryNodePtr generate(const std::string& prefix, std::size_t height, std::size_t numDirectories, std::size_t numFiles); static bool mismatch(const std::string& path, const std::string& type); static const std::string MISMATCH_EXIST_LEFT = "E<"; static const std::string MISMATCH_EXIST_RIGHT = ">E"; static const std::string MISMATCH_CONTENT = "CN"; static const std::string MISMATCH_MODIFIED = "MT"; static const std::string MISMATCH_SIZE = "SZ"; static const std::string MISMATCH_TYPE = "TY"; Model::Node::Node(std::string name): mModified(), mName(std::move(name)) {} void Model::Node::swap(Node& other) { using std::swap; swap(mModified, other.mModified); swap(mName, other.mName); } auto Model::Node::directory() const -> const DirectoryNode* { return const_cast(this)->directory(); } auto Model::Node::directory() -> DirectoryNode* { return nullptr; } auto Model::Node::file() const -> const FileNode* { return const_cast(this)->file(); } auto Model::Node::file() -> FileNode* { return nullptr; } Model::DirectoryNode::DirectoryNode(std::string name): Node(std::move(name)), mChildren() {} Model::DirectoryNode::DirectoryNode(const DirectoryNode& other): Node(other), mChildren() { // Copy other's children. for (auto& c: other.mChildren) add(c.second->copy()); } Model::DirectoryNode::DirectoryNode(DirectoryNode&& other): Node(std::move(other)), mChildren(std::move(other.mChildren)) {} auto Model::DirectoryNode::operator=(DirectoryNode&& rhs) -> DirectoryNode& { DirectoryNode temp(std::move(rhs)); swap(temp); return *this; } auto Model::DirectoryNode::add(NodePtr child) -> Node* { // Sanity. assert(child); assert(!child->mName.empty()); // Safety. auto name = child->mName; // Try and add the child to our map. auto result = mChildren.emplace(std::move(name), std::move(child)); // Child's been added. if (result.second) return result.first->second.get(); // Child already exists with this name. return nullptr; } auto Model::DirectoryNode::children() const -> const NodeMap& { return mChildren; } auto Model::DirectoryNode::copy() -> NodePtr { return std::make_unique(*this); } auto Model::DirectoryNode::directory() -> DirectoryNode* { return this; } auto Model::DirectoryNode::from(const Client& client, NodeInfo info) -> NodePtr { // Instantiate a new directory. auto directory = std::make_unique(std::move(info.mName)); // Determine the names of this node's children. auto childNames = client.childNames(CloudPath(info.mHandle)).valueOr({}); // Populate directory from the cloud. for (const auto& name: childNames) { // Retrieve information about this child. auto info_ = client.get(info.mHandle, name); // Child doesn't exist. if (!info_) continue; if (info_->mIsDirectory) directory->add(DirectoryNode::from(client, std::move(*info_))); else directory->add(FileNode::from(client, std::move(*info_))); } // Return directory to caller. return directory; } auto Model::DirectoryNode::from(const fs::path& path) -> NodePtr { // What's the name of this directory? auto name = path_u8string(path.filename()); // Instantiate a new directory. auto directory = std::make_unique(std::move(name)); auto i = fs::directory_iterator(path); auto j = fs::directory_iterator(); // Populate directory from disk. for (; i != j; ++i) { switch (i->status().type()) { case fs::file_type::directory: directory->add(DirectoryNode::from(i->path())); break; case fs::file_type::regular: directory->add(FileNode::from(i->path())); default: break; } } return directory; } auto Model::DirectoryNode::get(const std::string& name) const -> const Node* { return const_cast(this)->get(name); } auto Model::DirectoryNode::get(const std::string& name) -> Node* { // Try and locate the specified child. auto i = mChildren.find(name); // Child exists. if (i != mChildren.end()) return i->second.get(); // Child doesn't exist. return nullptr; } bool Model::DirectoryNode::match(const std::string& path, const Node& rhs) const { // Is rhs a directory? auto rhsDirectory = rhs.directory(); // Rhs isn't a directory. if (!rhsDirectory) return mismatch(path, MISMATCH_TYPE); std::set childNames; // Retrieve names of children. for (auto& child: mChildren) childNames.emplace(child.first); for (auto& child: rhsDirectory->mChildren) childNames.emplace(child.first); // Assume our children are matched. auto matched = true; // Try and match children. for (auto& child: mChildren) { // Compute child path. auto childPath = path + child.first + "/"; // Mark child as having been visited. childNames.erase(child.first); // Check if child exists on the right. auto rhsChild = rhsDirectory->get(child.first); // Child only exists on the left. if (!rhsChild) { // Emit mismatch message. matched &= mismatch(childPath, MISMATCH_EXIST_LEFT); // Try and match next child. continue; } // Try and match child. matched &= child.second->match(childPath, *rhsChild); } // All children have been processed. if (childNames.empty()) return matched; // Emit a mismatch message for children that exist only on the right. for (auto& name: childNames) mismatch(path + name + "/", MISMATCH_EXIST_RIGHT); return false; } void Model::DirectoryNode::populate(fs::path path) const { // Add our name to the path. path /= u8path_compat(mName); // Make sure we exist on disk. fs::create_directories(path); // Populate the new directory with our children. for (auto& c: mChildren) c.second->populate(path); } auto Model::DirectoryNode::remove(const std::string& name) -> NodePtr { // Try and locate the specified child. auto i = mChildren.find(name); // Child doesn't exist. if (i == mChildren.end()) return nullptr; // Extract the child. auto child = std::move(i->second); // Remove the child from our map. mChildren.erase(i); // Return child to caller. return child; } void Model::DirectoryNode::swap(DirectoryNode& other) { using std::swap; Node::swap(other); swap(mChildren, other.mChildren); } Model::FileNode::FileNode(std::string name): Node(std::move(name)), mContent(), mSize(0) {} auto Model::FileNode::copy() -> NodePtr { return std::make_unique(*this); } auto Model::FileNode::file() -> FileNode* { return this; } auto Model::FileNode::from(const Client&, NodeInfo info) -> NodePtr { // Instantiate file. auto file = std::make_unique(std::move(info.mName)); // Convenience. using std::chrono::system_clock; // Latch modification time. file->mModified = info.mModified; // Latch size. file->mSize = static_cast(info.mSize); // Return file to caller. return file; } auto Model::FileNode::from(const fs::path& path) -> NodePtr { // What is our name? auto name = path_u8string(path.filename()); // Instantiate file. auto file = std::make_unique(std::move(name)); // Latch modification time. file->mModified = lastWriteTime(path); // Determine the file's size. file->mSize = fs::file_size(path); // File's empty. if (!file->mSize) return file; // Try and open file for reading. std::ifstream istream(path_u8string(path), std::ios::binary); // Expand buffer. file->mContent.resize(static_cast(file->mSize)); // Convenience. auto size_ = static_cast(file->mSize); // Try and read content from disk. istream.read(&file->mContent[0], size_); // File's been read from disk. if (istream.gcount() == size_) return file; // Couldn't read the file from disk. std::ostringstream ostream; ostream << "Couldn't read \"" << path << "\" from disk"; throw std::runtime_error(ostream.str()); } bool Model::FileNode::match(const std::string& path, const Node& rhs) const { // Is rhs a file? auto rhsFile = rhs.file(); // Rhs isn't a file. if (!rhsFile) return mismatch(path, MISMATCH_TYPE); // Modification time is different. if (mModified != rhsFile->mModified) return mismatch(path, MISMATCH_MODIFIED); // Size is different. if (mSize != rhsFile->mSize) return mismatch(path, MISMATCH_SIZE); // Only compare content if available on both sides. if (mContent.empty() || rhsFile->mContent.empty()) return true; // Content is different. if (mContent != rhsFile->mContent) return mismatch(path, MISMATCH_CONTENT); // File's matched. return true; } void Model::FileNode::populate(fs::path path) const { // Compute our path. path /= u8path_compat(mName); // Convenience. constexpr auto flags = std::ios::binary | std::ios::trunc; constexpr auto mask = std::ios::badbit | std::ios::failbit; std::ofstream ostream; // Make sure we throw if we can't create the file. ostream.exceptions(mask); // Try and open the file. ostream.open(path_u8string(path), flags); // Try and write our content to disk. ostream.write(mContent.data(), static_cast(mContent.size())); // Make sure our content has been written. ostream.flush(); // Close the file. ostream.close(); // Set the file's modification time. lastWriteTime(path, mModified); } Model::Model(): mRoot("") {} Model::Model(const Model& other): mRoot(other.mRoot) {} Model::Model(Model&& other): mRoot(std::move(other.mRoot)) {} Model& Model::operator=(const Model& rhs) { Model temp(rhs); swap(temp); return *this; } Model& Model::operator=(Model&& rhs) { Model temp(std::move(rhs)); swap(temp); return *this; } auto Model::add(NodePtr child, const std::string& parentPath) -> Node* { // Sanity. assert(child); assert(!child->mName.empty()); // Try and locate the specified parent. auto parentNode = get(parentPath); // Parent doesn't exist. if (!parentNode) return nullptr; auto parentDirectory = parentNode->directory(); // Parent's not a directory. if (!parentDirectory) return nullptr; // Try and add the child to the directory. return parentDirectory->add(std::move(child)); } auto Model::directory(const std::string& name) -> DirectoryNodePtr { auto directory = std::make_unique(name); directory->mModified = std::chrono::system_clock::now(); return directory; } auto Model::file(const std::string& name, const std::string& content) -> FileNodePtr { // Convenience. using Clock = std::chrono::system_clock; auto file = std::make_unique(name); file->mContent = content; file->mSize = content.size(); // Make sure time is consistent with SDK. file->mModified = Clock::from_time_t(Clock::to_time_t(Clock::now())); return file; } auto Model::file(const std::string& name) -> FileNodePtr { return file(name, name); } Model Model::from(const Client& client, CloudPath path) { // Retrieve the directory's info. auto info = client.get(std::move(path)); // Directory doesn't exist. if (!info) return Model(); // Directory isn't a directory. if (!info->mIsDirectory) throw std::runtime_error("Path doesn't specify a directory"); Model model; // Populate the model. model.mRoot.add(DirectoryNode::from(client, std::move(*info))); return model; } Model Model::from(const Path& path) { // Directory doesn't exist. if (!fs::exists(path)) return Model(); // Directory isn't a directory. if (!fs::is_directory(path)) throw std::runtime_error("Path doesn't specify a directory"); Model model; // Populate the model. model.mRoot.add(DirectoryNode::from(path)); return model; } Model Model::generate(const std::string& prefix, std::size_t height, std::size_t numDirectories, std::size_t numFiles) { // Sanity. assert(!prefix.empty()); Model model; // Model contains no content. if (!height) return model; // Generate root node. auto root = testing::generate(prefix, height - 1, numDirectories, numFiles); // Add root node. model.mRoot.add(std::move(root)); // Return model. return model; } auto Model::get(const std::string& path) const -> const Node* { return const_cast(this)->get(path); } auto Model::get(const std::string& path) -> Node* { // Start traversal from the root node. auto* parent = &mRoot; // Skip leading separators. auto m = path.find_first_not_of('/'); // Iterate over path fragments. while (m < path.size()) { // Find end of current path fragment. auto n = path.find_first_of('/', m + 1); // Compute fragment. auto fragment = path.substr(m, n - m); // Try and locate child. auto* child = parent->get(fragment); // Child doesn't exist or we've processed the last fragment. if (!child || n == path.npos) return child; // Move to start of next fragment. m = path.find_first_not_of('/', n + 1); // Ignore trailing separators. if (m == path.npos) return child; // Child should be a directory. parent = child->directory(); // Child isn't a directory. if (!parent) return nullptr; } return parent; } bool Model::match(const Model& rhs) const { return mRoot.match("/", rhs.mRoot); } void Model::populate(const Path& path) const { mRoot.populate(path); } auto Model::remove(const std::string& path) -> NodePtr { // Where does the child's name end? auto n = path.find_last_not_of('/'); // Path is nothing but separators. if (n == path.npos) return nullptr; // Where does the child's name begin? auto m = path.find_last_of('/', n - 1); // Entire path is the name of a child. if (m == path.npos) return mRoot.remove(path.substr(0, n + 1)); // Try and locate the child's parent. auto* parent = get(path.substr(0, m)); // Parent doesn't exist. if (!parent) return nullptr; auto* parentDirectory = parent->directory(); // Parent isn't a directory. if (!parentDirectory) return nullptr; // Try and remove the child. return parentDirectory->remove(path.substr(m + 1, n - m)); } void Model::swap(Model& other) { using std::swap; swap(mRoot, other.mRoot); } void swap(Model& lhs, Model& rhs) { lhs.swap(rhs); } Model::DirectoryNodePtr generate(const std::string& prefix, std::size_t height, std::size_t numDirectories, std::size_t numFiles) { // Create a directory. auto directory = Model::directory(prefix); // Directory contains no children. if (!height) return directory; // Create subdirectories. for (auto d = 0u; d < numDirectories; ++d) { // Compute subdirectory name. auto name = prefix + "d" + std::to_string(d); // Generate subdirectory. auto subdirectory = generate(name, height - 1, numDirectories, numFiles); // Add subdirectory. directory->add(std::move(subdirectory)); } // Create files. for (auto f = 0u; f < numFiles; ++f) { // Compute file name. auto name = prefix + "f" + std::to_string(f); // Add file. directory->add(Model::file(name)); } // Return directory. return directory; } bool mismatch(const std::string& path, const std::string& type) { LOG_debug << "Mismatch " << type << ": " << path; return false; } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/partial_download_tests.cpp000066400000000000000000000372521516266226600247060ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { struct PartialDownloadTestTraits { using AbstractClient = Client; using ConcreteClient = RealClient; static constexpr const char* mName = "partial_download"; }; // PartialDownloadTestTraits struct PartialDownloadTests: public SingleClientTest { // Perform instance-specific setup. void SetUp() override; // Perform fixture-wide setup. static void SetUpTestSuite(); // The content of the file we want to partially download. static std::string mFileContent; // The handle of the file we want to partially download. static NodeHandle mFileHandle; }; // PartialDownloadTests class PartialDownloadCallback: public common::PartialDownloadCallback { // The content we managed to download. std::string mContent; // The download this callback relates to. // // Should be set only for some cancellation tests. common::PartialDownloadWeakPtr mDownload; // The result of our download. std::promise mResult; // Called when the download has completed. void completed(Error result) override { mResult.set_value(result); } // Called when we've received some content. auto data(const void* buffer, std::uint64_t, std::uint64_t length) -> std::variant override { // Convenience. auto* buffer_ = static_cast(buffer); // Copy the content we've received for later validation. mContent.insert(mContent.end(), buffer_, buffer_ + length); // Cancel the download if it's been injected. if (auto download = mDownload.lock()) return Abort(); // Continue the download. return Continue(); } // Called when the download has experienced some failure. auto failed(Error result, int retries) -> std::variant override { // Failure isn't due to a retryable error. if (!retryable(result)) return Abort(); // Or if we've already retried the download too many times. if (retries >= 5) return Abort(); // Retry the download. return Retry(deciseconds(20)); } // Check if result is a retryable error. bool retryable(const Error& result) const { // Client's being torn down or the download has been cancelled. if (result == API_EINCOMPLETE) return false; // File's been taken down because it breached our terms and conditions. if (result == API_ETOOMANY && result.hasExtraInfo()) return false; // Retry all other failures. return true; } public: // Return a reference to our downloaded content. const std::string& content() const { return mContent; } // Specify which download this callback relates to. void download(common::PartialDownloadPtr download) { mDownload = std::move(download); } // Return the result of our download. Error result() { return common::waitFor(mResult.get_future()); } }; // PartialDownloadCallback // For clarity. static std::uint64_t operator""_KiB(unsigned long long value); static std::uint64_t operator""_MiB(unsigned long long value); std::string PartialDownloadTests::mFileContent; NodeHandle PartialDownloadTests::mFileHandle; using common::testing::File; using common::testing::randomBytes; using common::testing::randomName; TEST_F(PartialDownloadTests, DISABLED_measure_average_fetch_times) { // Lets us fetch a file without actually storing its data anywhere. class FetchCallback: public PartialDownloadCallback { // Who should we notify when the download completes. std::promise mNotifier; // Called when the download has completed. void completed(Error result) override { mNotifier.set_value(result); } // Called when we've received some content. auto data(const void*, std::uint64_t, std::uint64_t) -> std::variant override { return Continue(); } public: FetchCallback(std::promise notifier): mNotifier(std::move(notifier)) {} }; // FetchCallback // Maximum read size specified as a power of two. constexpr auto maximumReadSize = 24ul; // Minimum read size specified as a power of two. constexpr auto minimumReadSize = 8ul; // Sanity. static_assert(maximumReadSize > minimumReadSize); // How many sizes are we testing? constexpr auto numReadSizes = maximumReadSize - minimumReadSize + 1; // How many times we should sample a given read size. constexpr auto numSamplesPerReadSize = 10ul; // Convenience. using std::chrono::milliseconds; // Allocate space to store our measurements. std::array measurements{}; // Try and create a file for us to test against. auto handle = mClient->upload(randomBytes((1 << maximumReadSize) + 4096), randomName(), "/y"); // Make sure we could create our test file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Measure the average fetch time for each read size. for (auto i = 0ul; i < numReadSizes; ++i) { // Get a reference to this read size's measurements. auto& measurement = measurements[i]; // Compute our read size. auto size = 1ul << (i + minimumReadSize); // Measure the average fetch time for a given read size. for (auto j = 0ul; j < numSamplesPerReadSize; ++j) { // Convenience. using std::chrono::duration_cast; using std::chrono::milliseconds; using std::chrono::steady_clock; // So we can signal when our fetch has completed. std::promise notifier; // So we can wait until our fetch has completed. auto waiter = notifier.get_future(); // So we can receive updates as our fetch progresses. FetchCallback callback(std::move(notifier)); // Try and create a partial download for our test file. auto download = mClient->partialDownload(callback, *handle, size, 0); // Make sure we could create a partial download. ASSERT_EQ(download.errorOr(API_OK), API_OK); // Figure out when this sample began. auto began = steady_clock::now(); // Start the download. (*download)->begin(); // Wait for the fetch to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), std::future_status::timeout); // Make sure the fetch was successful. ASSERT_EQ(waiter.get(), API_OK); // How much time did the fetch take? auto elapsed = duration_cast(steady_clock::now() - began); LOG_debug << size << " sample #" << j << " took " << elapsed.count() << " millisecond(s)."; // Add our fetch time to this size's measurement. measurement += static_cast(elapsed.count()); } // Compute this read size's average fetch time. measurement /= numSamplesPerReadSize; } // Display profile results. for (auto i = 0ul; i < numReadSizes; ++i) { LOG_debug << "Average fetch time for " << (1ul << (i + minimumReadSize)) << " is " << measurements[i] << " millisecond(s)"; } } TEST_F(PartialDownloadTests, cancel_completed_fails) { PartialDownloadCallback callback; // Create a download. auto download = mClient->partialDownload(callback, mFileHandle, 1_KiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Begin the download. (*download)->begin(); // Wait for the download to complete. EXPECT_EQ(callback.result(), API_OK); // Make sure you can't cancel a download that's already completed. EXPECT_FALSE((*download)->cancel()); } TEST_F(PartialDownloadTests, cancel_on_download_destruction_succeeds) { PartialDownloadCallback callback; // Create a download. auto download = mClient->partialDownload(callback, mFileHandle, 1_MiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Make sure the download isn't completed before we can cancel it. mClient->setDownloadSpeed(4096); // Try and download the entire file. (*download)->begin(); // Destroy the download. download->reset(); // Wait for the download to complete. EXPECT_EQ(callback.result(), API_EINCOMPLETE); } TEST_F(PartialDownloadTests, cancel_during_data_succeeds) { PartialDownloadCallback callback; // Create a download. auto download = mClient->partialDownload(callback, mFileHandle, 1_MiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Specify which download our callback is associated with. callback.download(*download); // Begin the download. (*download)->begin(); // Wait for the download to complete. EXPECT_EQ(callback.result(), API_EINCOMPLETE); } TEST_F(PartialDownloadTests, cancel_on_logout_succeeds) { // Create a client that we can destroy. auto client = CreateClient("partial_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(0), API_OK); PartialDownloadCallback callback; // Create a download. auto download = client->partialDownload(callback, mFileHandle, 1_MiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Make sure the download isn't completed before we can cancel it. client->setDownloadSpeed(4096); // Try and download the entire file. (*download)->begin(); // Logout the client. EXPECT_EQ(client->logout(true), API_OK); // Wait for the download to complete. ASSERT_EQ(callback.result(), API_EINCOMPLETE); // The download consider itself cancelled. EXPECT_TRUE((*download)->cancelled()); // And completed. EXPECT_TRUE((*download)->completed()); } TEST_F(PartialDownloadTests, cancel_succeeds) { PartialDownloadCallback callback; // Try and create a download for us to cancel. auto download = mClient->partialDownload(callback, mFileHandle, 1_MiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Downloads are cancellable until they've been completed. EXPECT_TRUE((*download)->cancellable()); // Make sure the download isn't completed before we can cancel it. mClient->setDownloadSpeed(4096); // Try and download the entire file. (*download)->begin(); // Try and cancel the download. EXPECT_TRUE((*download)->cancel()); // Wait for the download to complete. EXPECT_EQ(callback.result(), API_EINCOMPLETE); // The download should report itself as cancelled. EXPECT_TRUE((*download)->cancelled()); // And completed. EXPECT_TRUE((*download)->completed()); } TEST_F(PartialDownloadTests, download_directory_fails) { PartialDownloadCallback callback; // You shouldn't be able to download a directory. auto download = mClient->partialDownload(callback, "/y", 1_MiB, 0); ASSERT_EQ(download.errorOr(API_OK), API_FUSE_EISDIR); } TEST_F(PartialDownloadTests, download_public_file_succeeds) { // Try and get a public link for our file. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Create a new client with which to download the file. auto client = CreateClient("partial_" + randomName()); // Try and log the client in. ASSERT_EQ(client->login(1), API_OK); // Processes download events. PartialDownloadCallback callback; // Try and create a new partial download. auto download = client->partialDownload(callback, *link, mFileContent.size(), 0); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Try and download the file's content. (*download)->begin(); // Wait for the download to complete. ASSERT_EQ(callback.result(), API_OK); // Make sure the content is what we expect it to be. ASSERT_EQ(mFileContent.size(), callback.content().size()); ASSERT_FALSE(mFileContent.compare(callback.content())); } TEST_F(PartialDownloadTests, download_succeeds) { // Download some content from our test file. auto download = [](std::uint64_t begin, std::uint64_t end, std::uint64_t length) { // Sanity. ASSERT_LE(begin, end); PartialDownloadCallback callback; // Try and create a new partial download. auto download = mClient->partialDownload(callback, mFileHandle, end - begin, begin); ASSERT_EQ(download.errorOr(API_OK), API_OK); // Try and download some content from our test file. (*download)->begin(); // Wait for our download to complete. ASSERT_EQ(callback.result(), API_OK); // The download should report itself as completed. EXPECT_TRUE((*download)->completed()); auto& content = callback.content(); // Convenience. auto begin_ = static_cast(begin); auto length_ = static_cast(length); // Make sure the content we downloaded is as we expected. ASSERT_EQ(content.size(), length_); ASSERT_FALSE(mFileContent.compare(begin_, length_, content)); }; // download // Try and download some data from the beginning of the file. EXPECT_NO_FATAL_FAILURE(download(0, 256_KiB, 256_KiB)); // Try and download some data from the middle of the file. EXPECT_NO_FATAL_FAILURE(download(256_KiB, 768_KiB, 512_KiB)); // Try and download some data from the end of the file. EXPECT_NO_FATAL_FAILURE(download(768_KiB, 1_MiB, 256_KiB)); // Make sure a download's range is properly clamped. EXPECT_NO_FATAL_FAILURE(download(768_KiB, 2_MiB, 256_KiB)); // Make sure zero-length ranges are handled properly. EXPECT_NO_FATAL_FAILURE(download(0, 0, 0)); EXPECT_NO_FATAL_FAILURE(download(1_MiB, 1_MiB, 0)); EXPECT_NO_FATAL_FAILURE(download(1_MiB, 2_MiB, 0)); } void PartialDownloadTests::SetUp() { SingleClientTest::SetUp(); // Make sure downloads proceed at full speed. mClient->setDownloadSpeed(0); } void PartialDownloadTests::SetUpTestSuite() { // Convenience. using ::testing::AnyOf; // Make sure our clients are set up. SingleClientTest::SetUpTestSuite(); // Make sure the test root is clean. ASSERT_THAT(mClient->remove("/y"), AnyOf(API_FUSE_ENOTFOUND, API_OK)); // Recreate the test root. auto rootHandle = mClient->makeDirectory("y", "/"); ASSERT_EQ(rootHandle.errorOr(API_OK), API_OK); // Generate content for our test file. mFileContent = randomBytes(1_MiB); // Create a file so we can upload our content to the cloud. File file(mFileContent, randomName(), mScratchPath); // Upload the file to the cloud. auto fileHandle = mClient->upload(*rootHandle, file.path()); ASSERT_EQ(fileHandle.errorOr(API_OK), API_OK); // Latch the file's handle for later use. mFileHandle = *fileHandle; } std::uint64_t operator""_KiB(unsigned long long value) { return value * 1024; } std::uint64_t operator""_MiB(unsigned long long value) { return value * 1024_KiB; } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/path.cpp000066400000000000000000000025431516266226600210700ustar00rootroot00000000000000#include #include namespace mega { namespace common { namespace testing { Path::Path(const LocalPath& path): mPath(path.toPath(false)) {} Path::Path(const fs::path& path): mPath(path) {} Path::Path(const std::string& path): Path(u8path_compat(path)) {} Path::Path(const char* path): Path(u8path_compat(path)) {} Path& Path::operator/=(const Path& rhs) { mPath /= rhs.mPath; return *this; } Path Path::operator/(const Path& rhs) const { return Path(*this) /= rhs; } bool Path::operator==(const Path& rhs) const { return mPath == rhs.mPath; } bool Path::operator<(const Path& rhs) const { return mPath < rhs.mPath; } bool Path::operator!=(const Path& rhs) const { return mPath != rhs.mPath; } Path::operator LocalPath() const { return localPath(); } Path::operator fs::path() const { return path(); } Path::operator std::string() const { return string(); } LocalPath Path::localPath() const { if (mPath.empty()) return LocalPath(); return LocalPath::fromAbsolutePath(path_u8string(mPath)); } fs::path Path::path() const { return mPath; } std::string Path::string() const { return path_u8string(mPath); } std::ostream& operator<<(std::ostream& ostream, const Path& path) { return ostream << path.string(); } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/posix/000077500000000000000000000000001516266226600205665ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/posix/utility.cpp000066400000000000000000000014251516266226600227770ustar00rootroot00000000000000#include #include #include #include #include namespace mega { namespace common { namespace testing { DateTime lastWriteTime(const Path& path, std::error_code& result) { struct stat attributes; if (!stat(path.path().c_str(), &attributes)) return attributes.st_mtime; result = std::error_code(errno, std::system_category()); return DateTime(); } void lastWriteTime(const Path path, const DateTime& modified, std::error_code& result) { struct utimbuf times = {modified, modified}; // times if (utime(path.path().c_str(), ×)) result = std::error_code(errno, std::system_category()); } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/printers.cpp000066400000000000000000000003461516266226600220010ustar00rootroot00000000000000#include #include namespace mega { namespace common { std::ostream& operator<<(std::ostream& ostream, const DateTime& value) { return ostream << toString(value); } } // common } // mega sdk-10.11.0/tests/integration/common/real_client.cpp000066400000000000000000000754431516266226600224260ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include // clang-format off #include "megawaiter.h" // clang-format on #include #include #include namespace mega { namespace common { namespace testing { using namespace common; using namespace common::testing; using namespace file_service; static std::unique_ptr createGfxProc() { auto provider = IGfxProvider::createInternalGfxProvider(); if (!provider) { return nullptr; } auto gfx = std::make_unique(std::move(provider)); if (gfx) gfx->startProcessingThread(); return gfx; } class RealClient::RealContact: public Contact { // Do we need to verify this contact? bool mustVerify(std::unique_lock& lock) const; // What client contains this contact? RealClient& mClient; // The contact's email. std::string mEmail; // The contact's ID. Handle mID; public: RealContact(RealClient& client, const User& user); // Remove the contact. Error remove() override; // Has this contact been verified? bool verified() const override; // Verify the contact. Error verify() override; }; // RealContact class RealClient::RealInvite: public Invite { // Perform an operation on an invite we've received. Error execute(ipcactions_t action); // Perform an operation on an invite we've sent. Error execute(opcactions_t action); // What client contains this invite? RealClient& mClient; // What invite does this instance represent? Handle mID; public: RealInvite(RealClient& client, Handle id); // Accept the invitation. Error accept() override; // Cancel the invitation. Error cancel() override; // Decline the invitation. Error decline() override; }; // RealInvite common::Client& RealClient::client() const { return mClient->mClientAdapter; } std::string RealClient::email() const { std::lock_guard guard(mClientLock); if (auto* user = mClient->finduser(mClient->me, 0)) return user->email; return std::string(); } Error RealClient::fetch(bool ignoreCache) { // So we can wait for the client's result. auto notifier = makeSharedPromise(); // Ask client to describe our cloud content. { std::lock_guard guard(mClientLock); // Transmits client's result to notifier. auto completion = [notifier](error result) { notifier->set_value(result); }; // completion // Generate a key uniquely identifying this request. auto key = std::make_pair(RT_FETCH, mClient->nextreqtag()); // Move completion function into request map. auto result = mPendingRequests.emplace(std::move(key), std::move(completion)); // Sanity. assert(result.second); // Silence compiler. static_cast(result); // Description isn't current until some time after fetch. nodesCurrent(false); // Ask the client to describe our cloud content. mClient->fetchnodes(ignoreCache, !mClient->syncsAlreadyLoadedOnStatecurrent, false); // Let the client know it has work to do. mClient->waiter->notify(); } // Return client's result to caller. return waitFor(notifier->get_future()); } void RealClient::fetchnodes_result(const Error& error) { requestCompleted(std::make_pair(RT_FETCH, mClient->restag), error); } auto RealClient::invited(const std::string& email, std::unique_lock&) const -> InvitePtr { // Compares two characters case insensitively. auto characterEquals = [](std::uint8_t lhs, std::uint8_t rhs) { return std::tolower(lhs) == std::tolower(rhs); }; // characterEquals // Compares two strings case insensitively. auto stringEquals = [&](const std::string& lhs, const std::string& rhs) { return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), characterEquals); }; // stringEquals // Convenience. auto& self = const_cast(*this); for (auto& i: mClient->pcrindex) { // Convenience. auto& request = *i.second; // Request received from email or sent to email. if ((request.isoutgoing && stringEquals(request.targetemail, email)) || stringEquals(request.originatoremail, email)) return std::make_unique(self, request.id); } // No matching request. return nullptr; } Error RealClient::login(const std::string& email, const std::string& password, const std::string& salt) { // So we can wait for the client's result. auto notifier = makeSharedPromise(); // Transmits the client's result to our notifier. auto completion = [notifier](error result) { notifier->set_value(result); }; // completion // Ask the client to log the user in. do { std::lock_guard guard(mClientLock); // Convenience. auto& accountVersion = mClient->accountversion; // Unknown account version. if (!accountVersion || accountVersion > 2) return API_EINTERNAL; // User has a V1 account. if (accountVersion == 1) { byte passwordKey[SymmCipher::KEYLENGTH]; // Compute password key. auto result = mClient->pw_key(password.c_str(), passwordKey); // Couldn't compute password key. if (result != API_OK) return result; // Try and log the user in. mClient->login(email.c_str(), passwordKey, nullptr, std::move(completion)); // Let the client know it has work to do. mClient->waiter->notify(); break; } // User has a V2 account but has no salt. if (salt.empty()) return API_EINTERNAL; // Try and log the user in. mClient->login2(email.c_str(), password.c_str(), &salt, nullptr, std::move(completion)); // Let the client know it has work to do. mClient->waiter->notify(); } while (0); // Wait for the client's result. return waitFor(notifier->get_future()); } void RealClient::loop() { LOG_verbose << "Client thread started"; // Acquire lock. std::unique_lock lock(mClientLock); while (!mClientTerminate) { // Check whether the client needs any attention. auto result = mClient->preparewait(); // Release lock. lock.unlock(); // Wait for activity, if necessary. if (!result) result = mClient->dowait(); // Acquire lock. lock.lock(); // Check if any activity has occcured. result |= mClient->checkevents(); // Give client control if necessary. if ((result & Waiter::NEEDEXEC)) mClient->exec(); } // Make sure the client's shut down. mClient->locallogout(false, true); LOG_verbose << "Client thread stopped"; } void RealClient::nodes_current() { nodesCurrent(true); } auto RealClient::parsePublicLink(const PublicLink& link) -> ErrorOr> { // Will store the file's public handle. ::mega::handle handle; // Will store the file's decryption key. std::string key(FILENODEKEYLENGTH, '\0'); // For simplicity, let's assume the link denotes a file. constexpr auto linkType = TypeOfLink::FILE; // Make sure no one else is messing with the client. std::lock_guard guard(mClientLock); // Try and destructure the link. auto result = mClient->parsepubliclink(link.get().data(), handle, reinterpret_cast(key.data()), linkType); // Couldn't destructure the link. if (result != API_OK) return unexpected(result); // Return the file's handle and key to our caller. return std::make_pair(NodeHandle().set6byte(handle), std::move(key)); } Error RealClient::openShareDialog(NodeHandle handle) { std::unique_lock lock(mClientLock); // Try and locate the specified node. auto node = mClient->nodeByHandle(handle); // Node doesn't exist. if (!node) return API_ENOENT; auto notifier = makeSharedPromise(); // Called when we've opened a share dialog. auto opened = [notifier](Error result) { notifier->set_value(result); }; // opened // Ask the client to open a share dialog. mClient->openShareDialog(node.get(), std::move(opened)); // Make sure the client's awake to perform our task. mClient->waiter->notify(); // Release the lock so the client can process our task. lock.unlock(); // Pass result to caller. return waitFor(notifier->get_future()); } ErrorOr RealClient::prelogin(const std::string& email) { // So we can wait for the client's result. auto notifier = makeSharedPromise>(); // Transmits the client's result to our notifier. auto completion = [notifier](error result, std::string* salt) { // Couldn't perform prelogin. if (result != API_OK) return notifier->set_value(unexpected(result)); // Transmit salt to caller. notifier->set_value(*salt); }; // completion // Ask the client to perform prelogin. { std::lock_guard guard(mClientLock); mClient->prelogin( email.c_str(), std::bind(std::move(completion), std::placeholders::_4, std::placeholders::_3)); // Let the client know it has work to do. mClient->waiter->notify(); } // Return client's result to caller. return waitFor(notifier->get_future()); } void RealClient::requestCompleted(RequestKey key, Error result) { auto i = mPendingRequests.find(key); // Sanity. assert(i != mPendingRequests.end()); // Latch request callback. auto callback = std::move(i->second); // Remove request from map. mPendingRequests.erase(i); // Forward result to callback. callback(result); } m_off_t RealClient::setTransferSpeed(m_off_t (MegaClient::*get)(), bool (MegaClient::*set)(m_off_t), m_off_t speed) { // Sanity. assert(get); assert(set); // Make sure we have exclusive access to the client. std::lock_guard guard(mClientLock); // Save the client's current transfer speed. auto current = (*mClient.*get)(); // Set the client's transfer speed. (*mClient.*set)(speed); // Return the client's previous transfer speed. return current; } bool RealClient::shared(const std::string& email, NodeHandle handle, accesslevel_t permissions) const { // Sanity. assert(!email.empty()); assert(!handle.isUndef()); // Acquire client lock. std::lock_guard guard(mClientLock); // Try and retrieve a reference to the specified node. auto node = mClient->nodeByHandle(handle); // Node doesn't exist so it can't be shared. if (!node) return false; // Scans "shares" for the specified user. auto scan = [&](const share_map& shares) { // Search shares for the specified user. for (auto& s: shares) { // Convenience. const auto& share = *s.second; // Mismatched access level. if (share.access != permissions) continue; // Found a share to an established contact. if (share.user && share.user->email == email) return true; // Sanity. assert(share.pcr); // Found a share to a pending contact. if (share.pcr->targetemail == email) return true; } // No matching share to specified user. return false; }; // scan // Have we shared our node with the specified user? return (node->outshares && scan(*node->outshares)) || (node->pendingshares && scan(*node->pendingshares)); } void RealClient::get(GetCallback callback, NodeHandle handle, bool isPrivate, const void* key, std::size_t keyLength, const char* privateAuth, const char* publicAuth) { // Sanity. assert(callback); assert(key); assert(keyLength); // Acquire client lock. std::lock_guard guard(mClientLock); // Called when we've retrieved the node's information. auto retrieved = [](GetCallback& callback, NodeHandle handle, std::string* name, const Error& result, std::int64_t size) { // Couldn't retrieve file information. if (result != API_OK) return callback(unexpected(result)), true; // Couldn't retrieve the file's name. if (!name) return callback(unexpected(API_EFAILED)), true; // Build a minimal description for the node. NodeInfo info; info.mHandle = handle; info.mIsDirectory = false; info.mName = *name; info.mPermissions = RDONLY; info.mSize = size; // Pass description to our waiter. callback(std::move(info)); // Let our caller know that we've handled the response. return true; }; // retrieved // Instantiate a request for this file's information. auto request = std::make_unique(mClient.get(), static_cast(key), keyLength, false, handle.as8byte(), isPrivate, privateAuth, publicAuth, nullptr, false, true, std::bind(std::move(retrieved), std::move(callback), handle, std::placeholders::_4, std::placeholders::_1, std::placeholders::_2)); // Ask the client to execute this request. mClient->queueCommand(request.get()); // Client now owns the request. request.release(); // Let the client know it has work to do. mClient->waiter->notify(); } void RealClient::getPublicLink(GetPublicLinkCallback callback, NodeHandle handle) { // Sanity. assert(callback); // Make sure nothing else is messing with the client. std::lock_guard guard(mClientLock); // Try and get our hands on the node's description. auto node = mClient->nodeByHandle(handle); // Couldn't get the node's description. if (!node) return callback(unexpected(API_ENOENT)); // Called when the node's public link has been retrieved. auto linked = [this](GetPublicLinkCallback& callback, auto linkHandle, auto nodeHandle, auto result) { // Couldn't retrieve the node's public link. if (result != API_OK) return callback(unexpected(result)); // Try and get our hands on the node. auto node = mClient->nodeByHandle(nodeHandle); // Node's no longer present. if (!node) return callback(unexpected(API_ENOENT)); // Convenience. auto linkFormat = mClient->mNewLinkFormat; auto linkType = mClient->validTypeForPublicURL(node->type); // Node's a directory. if (node->type != FILENODE) { // Which doesn't have a share key. if (!node->sharekey) return callback(unexpected(API_EKEY)); // Generate public link URI. auto key = Base64Str(node->sharekey->key); auto link = mClient->publicLinkURL(linkFormat, linkType, linkHandle, key); // Pass public link to waiter. return callback(PublicLink(link)); } // Generate public link URI. auto key = Base64Str(node->nodekey().data()); auto link = mClient->publicLinkURL(linkFormat, linkType, linkHandle, key); // Pass public link to waiter. return callback(PublicLink(link)); }; // linked // Ask the client to get (or create) this node's public link. mClient->exportnode(std::move(node), false, 0, false, false, 0, std::bind(std::move(linked), std::move(callback), std::placeholders::_3, handle, std::placeholders::_1)); // Let the client know it has work to do. mClient->waiter->notify(); } RealClient::RealClient(const std::string& clientName, const Path& databasePath, const Path& storagePath): Client(clientName, databasePath, storagePath), MegaApp(), mClient(), mClientLock(), mClientTerminate{false}, mClientThread(), mHTTPIO(new CurlHttpIO()), mPendingRequests(), mGfxProc(createGfxProc()), mWaiter(std::make_shared()) { // Sanity. assert(!clientName.empty()); // Instantiate the client. mClient.reset(new MegaClient(this, mWaiter, mHTTPIO.get(), new DBACCESS_CLASS(databasePath), mGfxProc.get(), USER_AGENT.c_str(), THREADS_PER_MEGACLIENT)); // Make sure the client has a recognizable name. mClient->clientname = clientName + " "; // Instantiate the client's worker thread. mClientThread = std::thread(&RealClient::loop, this); LOG_verbose << "Client constructed"; } RealClient::~RealClient() { // Try and log the client out. logout(false); LOG_verbose << "Waiting for client thread to stop"; // Let the client thread know it needs to terminate. mClientTerminate = true; // Wake the client up if it's sleeping. mClient->waiter->notify(); // Wait for the client thread to terminate. mClientThread.join(); // Destroy client. mClient.reset(); LOG_verbose << "Client destroyed"; } auto RealClient::contact(const std::string& email) const -> ContactPtr { std::lock_guard guard(mClientLock); // Convenience. auto& self = const_cast(*this); // Try and locate the specified user. auto* user = mClient->finduser(email.c_str(), 0); // User's a contact. if (user && user->show == VISIBLE) return std::make_unique(self, *user); // User's not a contact. return nullptr; } bool RealClient::hasFileAttribute(NodeHandle handle, fatype type) const { std::lock_guard guard(mClientLock); // Try and locate the specified node. auto node = mClient->nodeByHandle(handle); // Node doesn't exist. if (!node) return false; return node->hasfileattribute(type) != 0; } auto RealClient::invite(const std::string& email) -> ErrorOr { std::unique_lock lock(mClientLock); // Already sent an invite to or recieved an invite from email. if (auto invite = invited(email, lock)) return invite; auto notifier = makeSharedPromise>(); // Called when the invitation has been sent. auto invited = [=, this](Handle id, Error result, opcactions_t) { // Invitation was sent. if (result == API_OK) return notifier->set_value(std::make_unique(*this, id)); // Couldn't send invitation. notifier->set_value(unexpected(result)); }; // invited // Send the invite. mClient->setpcr(email.c_str(), OPCA_ADD, nullptr, nullptr, UNDEF, std::move(invited)); // Release lock. lock.unlock(); // Return result to caller. return waitFor(notifier->get_future()); } auto RealClient::invited(const std::string& email) const -> InvitePtr { std::unique_lock guard(mClientLock); return invited(email, guard); } Error RealClient::login(const std::string& email, const std::string& password) { // Try and retrieve the user's salt. auto salt = prelogin(email); // Couldn't get the user's salt. if (!salt) return salt.error(); // Try and log the user in. auto result = login(email, password, *salt); // Couldn't log the user in. if (result != API_OK) return result; // Try and retrieve a description of the user's cloud content. result = fetch(true); // Couldn't get a description of the cloud. if (result != API_OK) return result; // Convenience. using std::chrono::seconds; // Wait for our view of the cloud to be up to date. return waitForNodesCurrent(seconds(8)); } Error RealClient::login(const PublicLink& link) { // Make sure the public link is valid. { // Make sure no one else is messing with the client. std::lock_guard guard(mClientLock); // Make sure the public link is valid. auto result = mClient->folderaccess(link.get().data(), nullptr, false); // Public link isn't valid. if (result != API_OK) return result; } // Couldn't get a description of the directory's content. if (auto result = fetch(true); result != API_OK) return result; // Wait for our view of the directory to be up to date. return waitForNodesCurrent(std::chrono::seconds(8)); } Error RealClient::login(const SessionToken& sessionToken) { auto notifier = makeSharedPromise(); // So we can wait for the client's result. { std::lock_guard guard(mClientLock); // Called when the user's been logged in. auto completion = [notifier](error result) { notifier->set_value(result); }; // result // Try and log in the user. mClient->login(sessionToken.get(), std::move(completion)); // Let the client know it has work to do. mClient->waiter->notify(); } // Wait for the client's result. auto result = waitFor(notifier->get_future()); // Couldn't log the user in. if (result != API_OK) return result; // Try and retrieve a description of the user's cloud content. result = fetch(false); // Couldn't get a description of the cloud. if (result != API_OK) return result; // Convenience. using std::chrono::seconds; // Wait for our view of the cloud to be up to date. return waitForNodesCurrent(seconds(8)); } sessiontype_t RealClient::loggedIn() const { std::lock_guard guard(mClientLock); return mClient->loggedin(); } Error RealClient::logout(bool keepSession) { // So we can receive a result from the client. auto notifier = makeSharedPromise(); // Try and log the user out. { std::lock_guard guard(mClientLock); // User isn't logged in. if (mClient->loggedin() == NOTLOGGEDIN) return API_OK; // User wants to keep the session intact. if (keepSession) return mClient->locallogout(false, true), API_OK; // Transmits result to notifier. auto completion = [notifier](error result) { notifier->set_value(result); }; // completion // Try and log the user out. mClient->logout(false, std::move(completion)); } // Return client's result to caller. return waitFor(notifier->get_future()); } Error RealClient::reload() { return fetch(false); } NodeHandle RealClient::rootHandle() const { std::lock_guard guard(mClientLock); return mClient->mNodeManager.getRootNodeFiles(); } auto RealClient::sessionToken() const -> ErrorOr { std::lock_guard guard(mClientLock); std::string token; if (mClient->dumpsession(token)) return SessionToken(token); return unexpected(API_EFAILED); } m_off_t RealClient::setDownloadSpeed(m_off_t speed) { return setTransferSpeed(&MegaClient::getmaxdownloadspeed, &MegaClient::setmaxdownloadspeed, speed); } m_off_t RealClient::setUploadSpeed(m_off_t speed) { return setTransferSpeed(&MegaClient::getmaxuploadspeed, &MegaClient::setmaxuploadspeed, speed); } Error RealClient::share(const std::string& email, CloudPath path, accesslevel_t permissions) { // Sanity. assert(!email.empty()); // Resolve directory path to a handle. auto handle = path.resolve(*this); // Directory doesn't exist. if (!handle) return handle.error(); // Have we already shared this node with email? if (shared(email, *handle, permissions)) return API_OK; // Couldn't prepare node for sharing. if (auto result = openShareDialog(*handle)) return result; // Open share dialog if necessary. auto notifier = makeSharedPromise(); auto shared = [notifier](Error result, bool) { notifier->set_value(result); }; // shared std::unique_lock lock(mClientLock); auto node = mClient->nodeByHandle(*handle); // Node no longer exists. if (!node) return API_ENOENT; // Try and create share. mClient->setshare(std::move(node), email.c_str(), permissions, false, nullptr, mClient->nextreqtag(), std::move(shared)); // Make sure the client's awake. mClient->waiter->notify(); lock.unlock(); // Return result to caller. return waitFor(notifier->get_future()); } bool RealClient::shared(const std::string& email, CloudPath path, accesslevel_t permissions) const { // Sanity. assert(!email.empty()); // Resolve path to a node handle. auto handle = path.resolve(*this); // Can't share a node that doesn't exist. if (!handle) return false; // Have we shared node with the user? return shared(email, *handle, permissions); } void RealClient::useVersioning(bool useVersioning) { // Convenience. auto& versionsDisabled = mClient->versions_disabled; // Versioning state hasn't changed. if (versionsDisabled == !useVersioning) return; // Versioning state has changed. versionsDisabled = !useVersioning; } Error RealClient::RealInvite::execute(ipcactions_t action) { auto notifier = makeSharedPromise(); auto executed = [notifier](error result, ipcactions_t) { notifier->set_value(result); }; // executed std::unique_lock lock(mClient.mClientLock); mClient.mClient->updatepcr(mID, action, std::move(executed)); mClient.mClient->waiter->notify(); lock.unlock(); return waitFor(notifier->get_future()); } Error RealClient::RealInvite::execute(opcactions_t) { auto notifier = makeSharedPromise(); auto executed = [notifier](Handle, error result, opcactions_t) { notifier->set_value(result); }; // executed std::unique_lock lock(mClient.mClientLock); mClient.mClient->setpcr(nullptr, OPCA_DELETE, nullptr, nullptr, mID, std::move(executed)); mClient.mClient->waiter->notify(); lock.unlock(); return waitFor(notifier->get_future()); } bool RealClient::RealContact::mustVerify(std::unique_lock&) const { return mClient.mClient->mKeyManager.getManualVerificationFlag(); } RealClient::RealContact::RealContact(RealClient& client, const User& user): Contact(), mClient(client), mEmail(user.email), mID(user.userhandle) {} Error RealClient::RealContact::remove() { auto notifier = makeSharedPromise(); auto removed = [notifier](error result) { notifier->set_value(result); }; // removed std::unique_lock lock(mClient.mClientLock); auto result = mClient.mClient->removecontact(mEmail.c_str(), HIDDEN, std::move(removed)); if (result != API_OK) return result; lock.unlock(); return waitFor(notifier->get_future()); } bool RealClient::RealContact::verified() const { std::unique_lock guard(mClient.mClientLock); if (mustVerify(guard)) return mClient.mClient->areCredentialsVerified(mID); return true; } Error RealClient::RealContact::verify() { // So we can wait for the client's result if necessary. auto notifier = makeSharedPromise(); // Check if we we need to verify the contact. { std::unique_lock guard(mClient.mClientLock); // Convenience. auto& client = *mClient.mClient; // Do we need to verify the contact? if (!mustVerify(guard)) return API_OK; // Has the contact already been verified? if (client.areCredentialsVerified(mID)) return API_OK; // Called when the contact has been verified. auto verified = [notifier](Error result) { notifier->set_value(result); }; // verified // Try and verify the contact. auto result = client.verifyCredentials(mID, std::move(verified)); // Client's returned an immediate failure. if (result != API_OK) return result; // Let the client know it has work to do. client.waiter->notify(); } // Return the client's result to the caller. return waitFor(notifier->get_future()); } RealClient::RealInvite::RealInvite(RealClient& client, Handle id): Invite(), mClient(client), mID(id) {} Error RealClient::RealInvite::accept() { return execute(IPCA_ACCEPT); } Error RealClient::RealInvite::cancel() { return execute(OPCA_DELETE); } Error RealClient::RealInvite::decline() { return execute(IPCA_DENY); } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/utility.cpp000066400000000000000000000076431516266226600216450ustar00rootroot00000000000000#include "mega/base64.h" #include "mega/types.h" #include #include #include #include #include #include #include #include #include namespace mega { namespace common { namespace testing { class StandardInputStream: public InputStreamAccess { // What stream are we reading from? std::istream& mStream; // How much data does this stream contain? const m_off_t mSize; public: StandardInputStream(std::istream& stream, m_off_t size); // Read count bytes from this stream into buffer. bool read(byte* buffer, unsigned int count) override; // How many bytes are available for reading? m_off_t size() override; }; // StreamAccess // Convenience. using namespace fs; using namespace std::chrono; ErrorOr fingerprint(const std::string& content, system_clock::time_point modified) { // Convenience. auto modified_ = system_clock::to_time_t(modified); auto size_ = static_cast(content.size()); std::istringstream isstream(content, std::ios::binary); // Wrap input stream. StandardInputStream istream(isstream, size_); // Try and generate a fingerprint. FileFingerprint fingerprint; fingerprint.genfingerprint(&istream, modified_); // Couldn't generate fingerprint. if (!fingerprint.isvalid) return unexpected(API_EREAD); // Return fingerprint to caller. return fingerprint; } ErrorOr fingerprint(const Path& path) { std::error_code error; // Try and retrieve the file's modification time. auto modified = lastWriteTime(path, error); // Couldn't determine when the file was modified. if (error) return unexpected(API_EREAD); // Try and determine the file's size. auto size = file_size(path.path(), error); // Can't get the file's size. if (error) return unexpected(API_EREAD); // Open the file for reading. std::ifstream ifstream(path.string(), std::ios::binary); // Couldn't open the file for reading. if (!ifstream.is_open()) return unexpected(API_EREAD); // Convenience. auto size_ = static_cast(size); // Wrap the input stream. StandardInputStream istream(ifstream, size_); FileFingerprint fingerprint; // Try and generate a fingerprint. fingerprint.genfingerprint(&istream, modified); // Couldn't generate the fingerprint. if (!fingerprint.isvalid) return unexpected(API_EREAD); // Return fingerprint to caller. return fingerprint; } DateTime lastWriteTime(const Path& path) { auto error = std::error_code(); auto result = lastWriteTime(path, error); if (!error) return result; throw fs::filesystem_error("Couldn't retrieve modification time", path.path(), error); } void lastWriteTime(const Path& path, const DateTime& modified) { std::error_code error; lastWriteTime(path, modified, error); if (!error) return; throw fs::filesystem_error("Couldn't set modification time", path.path(), error); } std::string randomBytes(std::size_t length) { static std::mutex mutex; static PrnGen rng; std::lock_guard guard(mutex); return rng.genstring(length); } std::string randomName() { return Base64::btoa(randomBytes(16)); } StandardInputStream::StandardInputStream(std::istream& stream, m_off_t size): InputStreamAccess(), mStream(stream), mSize(size) {} bool StandardInputStream::read(byte* buffer, unsigned int count) { assert(buffer); assert(count); auto count_ = static_cast(count); mStream.read(reinterpret_cast(buffer), count_); return mStream.gcount() == count_; } m_off_t StandardInputStream::size() { return mSize; } } // testing } // fuse } // mega sdk-10.11.0/tests/integration/common/watchdog.cpp000066400000000000000000000020151516266226600217260ustar00rootroot00000000000000#include #include #include namespace mega { namespace common { namespace testing { Watchdog::Watchdog(Logger& logger): mLogger(logger), mExecutor( []() { TaskExecutorFlags flags; flags.mMaxWorkers = 1; return flags; }(), mLogger), mTask() {} void Watchdog::arm(std::chrono::steady_clock::time_point when) { // Disarm the watchdog. disarm(); // Re-arm the watchdog. mTask = mExecutor.execute( [this](const Task& task) { // Watchdog's being torn down. if (task.cancelled()) return; // Log a friendly message. Log1(mLogger, "Watchdog timed out", logError); // Kill the program. std::abort(); }, when, true); } void Watchdog::disarm() { // Disarm the watchdog. mTask.cancel(); mTask.reset(); } } // testing } // common } // mega sdk-10.11.0/tests/integration/common/windows/000077500000000000000000000000001516266226600211165ustar00rootroot00000000000000sdk-10.11.0/tests/integration/common/windows/utility.cpp000066400000000000000000000024511516266226600233270ustar00rootroot00000000000000#include #include #include #include #include namespace mega { namespace common { namespace testing { using platform::Handle; DateTime lastWriteTime(const Path& path, std::error_code& result) { WIN32_FILE_ATTRIBUTE_DATA attributes; if (GetFileAttributesExW(path.path().c_str(), GetFileExInfoStandard, &attributes)) return attributes.ftLastWriteTime; result = std::error_code(GetLastError(), std::system_category()); return DateTime(); } void lastWriteTime(const Path path, const DateTime& modified, std::error_code& result) { FILETIME modified_ = modified; Handle<> handle(CreateFileW(path.path().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr)); if (handle && SetFileTime(handle.get(), nullptr, nullptr, &modified_)) return; result = std::error_code(GetLastError(), std::system_category()); } } // testing } // common } // mega sdk-10.11.0/tests/integration/easy_curl.cpp000066400000000000000000000025341516266226600206320ustar00rootroot00000000000000#include "easy_curl.h" #include #include namespace sdk_test { EasyCurl::~EasyCurl() { if (mCurl) { curl_easy_cleanup(mCurl); } } EasyCurl::EasyCurl(EasyCurl&& other): mCurl(std::exchange(other.mCurl, nullptr)) {} EasyCurl::EasyCurl(): mCurl(curl_easy_init()) { if (!mCurl) throw std::runtime_error("curl_easy_init returns null"); } EasyCurl& EasyCurl::operator=(EasyCurl&& other) { if (this != &other) { using std::swap; swap(other.mCurl, mCurl); } return *this; } CURL* EasyCurl::curl() const { return mCurl; } EasyCurlSlist::EasyCurlSlist(): mSlist(nullptr) {} EasyCurlSlist::~EasyCurlSlist() { if (mSlist) { curl_slist_free_all(mSlist); } } EasyCurlSlist::EasyCurlSlist(EasyCurlSlist&& other) noexcept: mSlist(std::exchange(other.mSlist, nullptr)) {} EasyCurlSlist& EasyCurlSlist::operator=(EasyCurlSlist&& other) noexcept { if (this != &other) { std::swap(mSlist, other.mSlist); } return *this; } bool EasyCurlSlist::append(const std::string& value) { curl_slist* newSlist = curl_slist_append(mSlist, value.c_str()); if (!newSlist) { return false; } mSlist = newSlist; return true; } curl_slist* EasyCurlSlist::slist() const { return mSlist; } } // namespace sdk_test sdk-10.11.0/tests/integration/easy_curl.h000066400000000000000000000060111516266226600202710ustar00rootroot00000000000000#pragma once #include "mega/types.h" #include #include #include #include namespace sdk_test { /** * @brief RAII wrapper for libcurl's CURL handle * * EasyCurl provides a safe, modern C++ interface for managing libcurl's CURL handles. * It ensures proper cleanup of resources and follows RAII principles. * * This class is move-only to prevent accidental copying of CURL handles. */ class EasyCurl final { public: /** * @brief Destructor - cleans up the CURL handle */ ~EasyCurl(); /** * @brief constructor - init libcurl's CURL handle */ EasyCurl(); /** * @brief Copy constructor - deleted to prevent copying */ EasyCurl(const EasyCurl&) = delete; /** * @brief Copy assignment operator - deleted to prevent copying */ EasyCurl& operator=(const EasyCurl&) = delete; /** * @brief Move constructor * @param other The EasyCurl object to move from */ explicit EasyCurl(EasyCurl&& other); /** * @brief Move assignment operator * @param other The EasyCurl object to move from * @return Reference to this object */ EasyCurl& operator=(EasyCurl&& other); /** * @brief Get the underlying CURL handle * @return Raw pointer to the CURL handle */ CURL* curl() const; private: CURL* mCurl{nullptr}; ///< The underlying libcurl handle }; /** * @brief RAII wrapper for libcurl's curl_slist handle * * EasyCurl provides a safe, modern C++ interface for managing libcurl's curl_slist handles. * It ensures proper cleanup of resources and follows RAII principles. * * This class is move-only to prevent accidental copying of curl_slist handles. */ class EasyCurlSlist final { public: /** * @brief Constructs an empty EasyCurlSlist */ EasyCurlSlist(); /** * @brief Destructor - cleans up the curl_slist handle */ ~EasyCurlSlist(); /** * @brief Copy constructor - deleted to prevent copying */ EasyCurlSlist(const EasyCurlSlist&) = delete; /** * @brief Copy assignment operator - deleted to prevent copying */ EasyCurlSlist& operator=(const EasyCurlSlist&) = delete; /** * @brief Move constructor * @param other The EasyCurlSlist to move from */ EasyCurlSlist(EasyCurlSlist&& other) noexcept; /** * @brief Move assignment operator * @param other The EasyCurlSlist to move from * @return Reference to this EasyCurlSlist */ EasyCurlSlist& operator=(EasyCurlSlist&& other) noexcept; /** * @brief Appends a single value to the curl_slist * @param value The string value to append * @return true on success, false on failure */ bool append(const std::string& value); /** * @brief Get the underlying curl_slist handle * @return Raw pointer to the curl_slist handle */ curl_slist* slist() const; private: curl_slist* mSlist{nullptr}; ///< The underlying libcurl curl_slist handle }; } // namespace sdk_test sdk-10.11.0/tests/integration/env_var_accounts.cpp000066400000000000000000000020231516266226600221740ustar00rootroot00000000000000#include "env_var_accounts.h" #include "mega/utils.h" using mega::Utils; EnvVarAccounts::EnvVarAccounts(std::initializer_list values) :mAccounts{values} { } EnvVarAccounts::EnvVarAccounts(size_t count, const ValueType& value) :mAccounts(count, value) { } EnvVarAccounts& getEnvVarAccounts() { static EnvVarAccounts accounts { {"MEGA_EMAIL", "MEGA_PWD"}, {"MEGA_EMAIL_AUX", "MEGA_PWD_AUX"}, {"MEGA_EMAIL_AUX2", "MEGA_PWD_AUX2"}, }; return accounts; } EnvVarAccounts::ValueType EnvVarAccounts::getVarValues(size_t i) const { assert(i < mAccounts.size()); const auto& [email, pass] = mAccounts.at(i); return {Utils::getenv(email, ""), Utils::getenv(pass, "")}; } EnvVarAccounts::NameType EnvVarAccounts::getVarNames(size_t i) const { assert(i < mAccounts.size()); return mAccounts.at(i); } std::vector EnvVarAccounts::cloneVarNames() const { return mAccounts; } size_t EnvVarAccounts::size() const { return mAccounts.size(); } sdk-10.11.0/tests/integration/env_var_accounts.h000066400000000000000000000032771516266226600216550ustar00rootroot00000000000000#pragma once #include #include /** * @brief A list of environment variable name pairs ( email and password) for accounts. */ class EnvVarAccounts { public: // Email and pass pair using ValueType = std::pair; using NameType = std::pair; /** * @brief EnvVarAccounts initialzes with a list of value * @param values The list of values */ EnvVarAccounts(std::initializer_list values); /** * @brief EnvVarAccounts initializes with count copies of the value * @param count The count of copies * @param value The value of copies */ EnvVarAccounts(size_t count, const ValueType& value); /** * @brief getVarValues gets a specified account's email and password * from environment variables * * @param i Specify which account. It needs to be in the range of size(). * @return The pair of email and password values */ ValueType getVarValues(size_t i) const; /** * @brief getVarNames gets a specified account's environment variable * names for its email and password * * @param i Specify which account. It needs to be in the range of size(). * @return The pair of environment variables for the email and password */ NameType getVarNames(size_t i) const; /** * @brief cloneVarNames returns a copy of the list of environment variable * name pairs ( email and password) of the accounts. * * @return a copy of the list */ std::vector cloneVarNames() const; size_t size() const; private: std::vector mAccounts; }; EnvVarAccounts& getEnvVarAccounts(); sdk-10.11.0/tests/integration/file_service/000077500000000000000000000000001516266226600205735ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/client.cpp000066400000000000000000000054321516266226600225610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include namespace mega { namespace file_service { namespace testing { using common::ErrorOr; using common::NodeInfo; using common::NodeKeyData; using common::testing::CloudPath; using common::testing::Path; Client::Client(const std::string& clientName, const Path& databasePath, const Path& storagePath): common::testing::Client(clientName, databasePath, storagePath) {} Client::~Client() {} auto Client::fileAdd(const PublicLink& link) -> FileServiceResultOr { // Try and extract the file's handle and decryption key from the link. auto handleAndKey = parsePublicLink(link); // Couldn't extract the file's handle or decryption key. if (!handleAndKey) return unexpected(FILE_SERVICE_UNEXPECTED); // Convenience. auto& [handle, keyAndIV] = *handleAndKey; // Try and get information about the file. auto info = get(handle, false, keyAndIV.data(), keyAndIV.size(), {}, {}); // Couldn't get information about the file. if (!info) return unexpected(FILE_SERVICE_UNEXPECTED); // Populate the file's node key data. NodeKeyData keyData; keyData.mIsPublicHandle = true; keyData.mKeyAndIV = std::move(keyAndIV); // Convenience. auto size = static_cast(info->mSize); // Try and add the file to the service. return fileService().add(handle, keyData, size); } auto Client::fileCreate(NodeHandle parent, const std::string& name) -> FileServiceResultOr { return fileService().create(parent, name); } auto Client::fileInfo(CloudPath path) const -> FileServiceResultOr { return fileInfo(FileID::from(path.resolve(*this).valueOr(NodeHandle()))); } auto Client::fileInfo(FileID id) const -> FileServiceResultOr { return fileService().info(id); } auto Client::fileOpen(FileID id) const -> FileServiceResultOr { return fileService().open(id); } auto Client::fileOpen(CloudPath parentPath, const std::string& name) const -> FileServiceResultOr { return fileService().open(parentPath.resolve(*this).valueOr(NodeHandle()), name); } auto Client::fileOpen(CloudPath path) const -> FileServiceResultOr { return fileOpen(FileID::from(path.resolve(*this).valueOr(NodeHandle()))); } } // testing } // common } // mega sdk-10.11.0/tests/integration/file_service/file_service.cmake000066400000000000000000000014601516266226600242350ustar00rootroot00000000000000target_include_directories(test_integration PRIVATE file_service) target_link_libraries(test_integration PRIVATE MEGA::FileServiceHeaderPaths) target_sources(test_integration PRIVATE file_service/mega/file_service/testing/integration/client.h file_service/mega/file_service/testing/integration/client_forward.h file_service/mega/file_service/testing/integration/real_client.h file_service/mega/file_service/testing/integration/scoped_file_event_observer.h ) target_sources(test_integration PRIVATE file_service/client.cpp file_service/file_service_tests.cpp file_service/real_client.cpp ) sdk-10.11.0/tests/integration/file_service/file_service_tests.cpp000066400000000000000000003732131516266226600251710ustar00rootroot00000000000000#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 namespace mega { namespace file_service { // Convenience. std::ostream& operator<<(std::ostream& ostream, const FileLocation& location) { return ostream << "{name: " << location.mName << ", parent: " << toNodeHandle(location.mParentHandle) << "}"; } // Teach gtest how to print file IDs. void PrintTo(const FileID& id, std::ostream* ostream) { *ostream << toString(id); } // Teach gtest how to print our file event instances. void PrintTo(const FileFlushEvent& event, std::ostream* ostream) { *ostream << "Flush {handle: " << toNodeHandle(event.mHandle) << ", id: " << toString(event.mID) << "}"; } void PrintTo(const FileMoveEvent& event, std::ostream* ostream) { *ostream << "Move {from: " << event.mFrom << ", to: " << event.mTo << ", id: " << toString(event.mID) << "}"; } void PrintTo(const FileRemoveEvent& event, std::ostream* ostream) { *ostream << "Remove {id: " << toString(event.mID) << ", replaced: " << event.mReplaced << "}"; } void PrintTo(const FileTouchEvent& event, std::ostream* ostream) { *ostream << "Touch {id: " << toString(event.mID) << ", modified: " << event.mModified << "}"; } void PrintTo(const FileTruncateEvent& event, std::ostream* ostream) { // Assume no part of the file has been "cut off." std::string range = "[]"; // Actually did cut off some part of the file. if (event.mRange) range = toString(*event.mRange); *ostream << "Truncate {range: " << range << ", id: " << toString(event.mID) << ", size: " << event.mSize << "}"; } void PrintTo(const FileWriteEvent& event, std::ostream* ostream) { *ostream << "Write {range: " << event.mRange << ", id: " << toString(event.mID) << "}"; } void PrintTo(const FileEvent& event, std::ostream* ostream) { std::visit( [ostream](const auto& event) { PrintTo(event, ostream); }, event); } namespace testing { // Convenience. using common::Expected; using common::makeSharedPromise; using common::NodeKeyData; using common::now; using common::unexpected; using common::testing::Path; using common::testing::randomBytes; using common::testing::randomName; using common::testing::ScopedWatch; using common::testing::SingleClientTest; using common::testing::waitFor; using common::testing::Watchdog; using ::testing::AnyOf; using ::testing::ElementsAre; using testing::observe; using ::testing::UnorderedElementsAreArray; // Forward declaration so we can keep things ordered. template struct FutureResult; template using FutureResultT = typename FutureResult::type; template struct IsAsynchronousFunctionCall; template constexpr auto IsAsynchronousFunctionCallV = IsAsynchronousFunctionCall::value; template struct IsFuture; template constexpr auto IsFutureV = IsFuture::value; // Determine the value returned by an asynchronous function call. template struct AsynchronousFunctionCallResult { // Ensure that function(parameters, ...) is an asynchronous call. static_assert(IsAsynchronousFunctionCallV); // Convenience. using result = std::invoke_result_t; using type = FutureResultT; }; // AsynchronousFunctionCallResult template using AsynchronousFunctionCallResultT = typename AsynchronousFunctionCallResult::type; // Determine the value returned by an std::future. template struct FutureResult {}; // FutureResult template struct FutureResult> { using type = T; }; // FutureResult> template struct GenerateFailure; template struct GenerateFailure> { static auto value() { return unexpected(GenerateFailure::value()); } }; // GenerateFailure> template<> struct GenerateFailure { static auto value() { return FILE_FAILED; } }; // GenerateFailure template<> struct GenerateFailure { static auto value() { return FILE_SERVICE_UNEXPECTED; } }; // GenerateFailure template constexpr auto GenerateFailureV = GenerateFailure::value(); // Check if function(parameters, ...) is an asynchronous function call. template struct IsAsynchronousFunctionCall: public std::conjunction, IsFuture>> {}; // IsAsynchronousFunctionCall // Check whether T is an std::future. template struct IsFuture: public std::false_type {}; // IsFuture template struct IsFuture>: public std::true_type {}; // IsFuture> // Convenience. constexpr auto timeout = std::future_status::timeout; struct FileServiceTestTraits { using AbstractClient = Client; using ConcreteClient = RealClient; static constexpr const char* mName = "file_service"; }; // FileServiceTestTraits class FileServiceTests: public SingleClientTest { public: // Perform instance-specific setup. void SetUp() override; // Perform fixture-wide setup. static void SetUpTestSuite(); // Perform instance-specific teardown. void TearDown() override; // The content of the file we want to read. static std::string mFileContent; // The handle of the file we want to read. static NodeHandle mFileHandle; // The name of the file we want to read. static std::string mFileName; // The handle of our test root directory. static NodeHandle mRootHandle; // Terminates the program if we encounter a deadlock. static Watchdog mWatchdog; }; // FUSEPartialDownloadTests // For clarity. constexpr std::uint64_t operator""_KiB(unsigned long long value) { return value * 1024; } constexpr std::uint64_t operator""_MiB(unsigned long long value) { return value * 1024_KiB; } // Append content to the end of the specified file. static auto append(const void* buffer, File file, std::uint64_t length) -> std::future; // Compare content. static bool compare(const std::string& computed, const std::string& expected, std::uint64_t offset, std::uint64_t length); // Execute an asynchronous request synchronously. template auto execute(Function&& function, Parameters&&... arguments) -> std::enable_if_t, AsynchronousFunctionCallResultT>; // Fetch all of a file's content from the cloud. static auto fetch(File file) -> std::future; // Wait until all fetches have been completed. static auto fetchBarrier(File file) -> std::future; // Flush a file's modified content to the cloud. static auto flush(File file) -> std::future; // Purge a file from the service. static auto purge(File file) -> std::future; // Read some content from the specified file. static auto read(File file, std::uint64_t offset, std::uint64_t length) -> std::future>; // As above but a partial read is allowed. static auto readOnce(File file, std::uint64_t offset, std::uint64_t length) -> std::future>; // Reclaim a file's storage. static auto reclaim(File file) -> std::future>; // Reclaim zero or more files managed by client. static auto reclaimAll(ClientPtr& client) -> std::future>; // Remove a file. static auto remove(File file) -> std::future; // Update the specified file's modification time. static auto touch(File file, std::int64_t modified) -> std::future; // Truncate the specified file to a particular size. static auto truncate(File file, std::uint64_t newSize) -> std::future; // Write some content to the specified file. static auto write(const void* buffer, File file, std::uint64_t offset, std::uint64_t length) -> std::future; std::string FileServiceTests::mFileContent; NodeHandle FileServiceTests::mFileHandle; std::string FileServiceTests::mFileName; NodeHandle FileServiceTests::mRootHandle; Watchdog FileServiceTests::mWatchdog(logger()); static const FileServiceOptions DefaultOptions; static const FileServiceOptions DisableReadahead = { DefaultOptions.mMaximumRangeRetries, 0u, 0u, DefaultOptions.mRangeRetryBackoff, DefaultOptions.mReclaimAgeThreshold, DefaultOptions.mReclaimBatchSize, DefaultOptions.mReclaimDelay, DefaultOptions.mReclaimPeriod, DefaultOptions.mReclaimSizeThreshold}; // DisableReadahead static constexpr auto MaxTestRunTime = std::chrono::minutes(15); static constexpr auto MaxTestSetupTime = std::chrono::minutes(15); TEST_F(FileServiceTests, DISABLED_measure_average_linear_read_time) { // How large should the test file be? constexpr auto fileSize = 16_MiB; // How many samples should we perform? constexpr auto numSamples = 10ul; // How large should each individual read be? constexpr auto readSize = 8_KiB; // Try and create a test file for us to read from. auto handle = mClient->upload(randomBytes(fileSize), randomName(), mRootHandle); // Make sure we could create our test file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Average read time for the entire file. std::uint64_t averageFileReadTime = 0ul; // Average read time for each range. std::uint64_t averageRangeReadTime = 0ul; // Figure out how long it takes to linearly read all our file. for (auto i = 0ul; i < numSamples; ++i) { // Try and open our file for reading. auto file = mClient->fileOpen(*handle); // Make sure we could open our file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Linearly read the entire file. for (auto offset = 0ul; offset < fileSize; offset += readSize) { // Convenience. using std::chrono::duration_cast; using std::chrono::milliseconds; using std::chrono::steady_clock; // Track when this read began. auto began = steady_clock::now(); // Try and read some data. auto data = execute(read, *file, offset, readSize); // Track when the read finished. auto elapsed = steady_clock::now() - began; // Make sure the read succeeded. ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Convenience. auto elapsedMs = static_cast(duration_cast(elapsed).count()); // For curiosity. FSDebugF("Range read time: %s: %" PRIi64 " millisecond(s).", toString(FileRange(offset, offset + readSize)).c_str(), elapsedMs); // Make sure our measurements don't overflow. ASSERT_GE(UINT64_MAX - elapsedMs, averageFileReadTime); ASSERT_GE(UINT64_MAX - elapsedMs, averageRangeReadTime); // Update our measurements. averageFileReadTime += elapsedMs; averageRangeReadTime += elapsedMs; } } // Calculate the average time it took to read the entire file. averageFileReadTime /= numSamples; // Calculate the average time it took to read each range. averageRangeReadTime /= (fileSize / readSize) * numSamples; // Log our findings. FSDebugF("Average linear file read time: %" PRIu64 " millisecond(s)", averageFileReadTime); FSDebugF("Average linear range read time: %" PRIu64 " millisecond(s)", averageRangeReadTime); } TEST_F(FileServiceTests, add_external_succeeds) { // Get a public link for our test directory. auto link = mClient->getPublicLink(mRootHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Create a client responsible for accessing our test directory. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client into our test directory. ASSERT_EQ(client->login(*link), API_OK); // Retrieve our file's key data. auto keyData = client->keyData(mFileHandle, true); ASSERT_EQ(keyData.errorOr(API_OK), API_OK); // Log out of the test directory. ASSERT_EQ(client->logout(false), API_OK); // Log client into a account distinct from mClient. ASSERT_EQ(client->login(1), API_OK); // Try and add the file to the client. auto id = client->fileService().add(mFileHandle, *keyData, mFileContent.size()); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); } TEST_F(FileServiceTests, add_fails_with_invalid_file_key) { NodeKeyData keyData; keyData.mIsPublicHandle = false; keyData.mKeyAndIV.resize(FILENODEKEYLENGTH - 1); auto id = mClient->fileService().add(mRootHandle, keyData, 0); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_INVALID_FILE_KEY); keyData.mKeyAndIV.resize(FILENODEKEYLENGTH + 1); id = mClient->fileService().add(mRootHandle, keyData, 0); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_INVALID_FILE_KEY); } TEST_F(FileServiceTests, add_public_succeeds) { // Create a new client. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(1), API_OK); // Retrieve public link for our test file. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // We should be able to add mClient's test file to client. auto id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // We should get a useful error if we add the same file multiple times. id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_ALREADY_EXISTS); } TEST_F(FileServiceTests, append_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Open file for writing. auto file = mClient->fileOpen(mFileHandle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Get our hands on the file's attributes. auto info = file->info(); // Convenience. FileRange range(info.size() - 64_KiB, info.size() - 32_KiB); // Read some data just before the end of the file. { // Convenience. auto offset = range.mBegin; auto length = range.mEnd - range.mBegin; // Perform the read. auto result = execute(read, *file, offset, length); // Make sure the read completed successfully. ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(result->size(), static_cast(length)); // Reads shouldn't dirty a file. ASSERT_FALSE(info.dirty()); } // Events that we expect to receive. FileEventVector expected; // Store events emitted for our file. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Generate some data for us to append to the file. auto computed = randomBytes(32_KiB); // Latch the file's access time, modification time and size. auto modified = info.modified(); auto size = info.size(); // Try and append the data to the end of the file. ASSERT_EQ(execute(append, computed.data(), *file, computed.size()), FILE_SUCCESS); expected.emplace_back(FileWriteEvent{FileRange(size, size + computed.size()), info.id()}); // The file should now have two ranges. ASSERT_THAT(file->ranges(), ElementsAre(range, FileRange(size, size + computed.size()))); // Make sure the file's attributes have been updated. ASSERT_TRUE(info.dirty()); ASSERT_GE(info.accessed(), modified); ASSERT_GE(info.modified(), modified); ASSERT_EQ(info.size(), size + computed.size()); // Latch current modification time and size. modified = info.modified(); size = info.size(); // Append again to make sure contigous ranges are extended. ASSERT_EQ(execute(append, computed.data(), *file, computed.size()), FILE_SUCCESS); expected.emplace_back(FileWriteEvent{FileRange(size, size + computed.size()), info.id()}); ASSERT_GE(info.modified(), modified); ASSERT_EQ(info.size(), size + computed.size()); ASSERT_THAT(file->ranges(), ElementsAre(range, FileRange(size - computed.size(), size + computed.size()))); // Make sure we received the events we expected. ASSERT_TRUE(fileObserver.match(expected, mDefaultTimeout)); ASSERT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, cloud_file_removed_when_parent_removed) { // Create a tree we can mess with. auto d0 = mClient->makeDirectory(randomName(), mRootHandle); ASSERT_EQ(d0.errorOr(API_OK), API_OK); auto d1 = mClient->makeDirectory(randomName(), *d0); ASSERT_EQ(d1.errorOr(API_OK), API_OK); auto d0f = mClient->upload(randomBytes(512), randomName(), *d0); ASSERT_EQ(d0f.errorOr(API_OK), API_OK); auto d1f = mClient->upload(randomBytes(512), randomName(), *d1); ASSERT_EQ(d1f.errorOr(API_OK), API_OK); // What events do we expect to receive? struct { FileEventVector file0; FileEventVector file1; FileEventVector service; } expected; // Open d0f and d1f. auto file0 = mClient->fileOpen(*d0f); ASSERT_EQ(file0.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); auto file1 = mClient->fileOpen(*d1f); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver0 = observe(*file0); auto fileObserver1 = observe(*file1); auto serviceObserver = observe(mClient->fileService()); // Remove d0 and by proxy, d0f, d1 and d1f. ASSERT_EQ(mClient->remove(*d0), API_OK); expected.file0.emplace_back(FileRemoveEvent{file0->info().id(), false}); expected.file1.emplace_back(FileRemoveEvent{file1->info().id(), false}); expected.service.emplace_back(expected.file0.back()); expected.service.emplace_back(expected.file1.back()); // Make sure our files are marked as removed. EXPECT_TRUE(waitFor( [&]() { return file0->info().removed() && file1->info().removed(); }, mDefaultTimeout)); EXPECT_TRUE(file0->info().removed()); EXPECT_TRUE(file1->info().removed()); // Make sure we received remove events. EXPECT_TRUE(fileObserver0.match(expected.file0, mDefaultTimeout)); EXPECT_TRUE(fileObserver1.match(expected.file1, mDefaultTimeout)); // UnorderedElementsAreArray(...) necessary as order is unpredictable. EXPECT_THAT(expected.service, UnorderedElementsAreArray(serviceObserver.events())); } TEST_F(FileServiceTests, cloud_file_removed_when_removed_in_cloud) { // What events do we expect to receive? FileEventVector expected; // Create a test file in the cloud. auto handle = mClient->upload(randomBytes(512), randomName(), mRootHandle); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open our test file. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Remove the file from the cloud. ASSERT_EQ(mClient->remove(*handle), API_OK); expected.emplace_back(FileRemoveEvent{file->info().id(), false}); // Make sure our file's been marked as removed. EXPECT_TRUE(waitFor( [&]() { return file->info().removed(); }, mDefaultTimeout)); EXPECT_TRUE(file->info().removed()); // And that we received a remove event. EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, cloud_file_removed_when_replaced_by_cloud_add) { // So we have a stable name. auto name = randomName(); // Create a test file in the cloud. auto handle = mClient->upload(randomBytes(512), name, mRootHandle); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open our test file. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Add a directory with the same name and parent as our file. auto directory = mClient->makeDirectory(name, mRootHandle); ASSERT_EQ(directory.errorOr(API_OK), API_OK); // Make sure our file's been marked as removed. EXPECT_TRUE(waitFor( [&]() { return file->info().removed(); }, mDefaultTimeout)); EXPECT_TRUE(file->info().removed()); // And that we received a remove event. FileEventVector expected; expected.emplace_back(FileRemoveEvent{file->info().id(), true}); EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, cloud_file_removed_when_replaced_by_new_version) { // So we have a stable name. auto name = randomName(); // Create an initial version of name in the cloud. auto handle0 = mClient->upload(randomBytes(512), name, mRootHandle); ASSERT_EQ(handle0.errorOr(API_OK), API_OK); // Open our file. auto file = mClient->fileOpen(*handle0); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Upload a new version of our file. auto handle1 = mClient->upload(randomBytes(512), name, mRootHandle); ASSERT_EQ(handle1.errorOr(API_OK), API_OK); // Make sure the client sees our new file. EXPECT_TRUE(waitFor( [&]() { return mClient->get(*handle1); }, mDefaultTimeout)); auto info0 = mClient->get(*handle0); auto info1 = mClient->get(*handle1); // For a better log message in Jenkins. EXPECT_EQ(info0.errorOr(API_OK), API_OK); EXPECT_EQ(info1.errorOr(API_OK), API_OK); if (HasFailure()) return; // Make sure version relationship has been updated correctly. EXPECT_EQ(info0->mParentHandle, info1->mHandle); // Make sure our file has been marked as removed. EXPECT_TRUE(file->info().removed()); // And that we received a remove event. FileEventVector expected; expected.emplace_back(FileRemoveEvent{file->info().id(), true}); EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, create_fails_when_file_already_exists) { // Generate a name for a local file. auto name = randomName(); // Create a local file for us to collide with. auto file0 = mClient->fileCreate(mRootHandle, name); ASSERT_EQ(file0.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // You can't create a file if it already exists in the cloud. auto file1 = mClient->fileCreate(mRootHandle, mFileName); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_ALREADY_EXISTS); // You can't create a file if it already exists locally. file1 = mClient->fileCreate(mRootHandle, name); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_ALREADY_EXISTS); } TEST_F(FileServiceTests, create_fails_when_name_is_empty) { // Files must have a valid name. auto file = mClient->fileCreate(mRootHandle, std::string()); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_INVALID_NAME); } TEST_F(FileServiceTests, create_fails_when_parent_doesnt_exist) { // Files must have a valid parent. auto file = mClient->fileCreate(NodeHandle(), randomName()); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_PARENT_DOESNT_EXIST); } TEST_F(FileServiceTests, create_fails_when_parent_is_a_file) { // A file's parent must be a directory. auto file = mClient->fileCreate(mFileHandle, randomName()); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_PARENT_IS_A_FILE); } TEST_F(FileServiceTests, create_flush_succeeds) { // Generate a name for our file. auto name = randomName(); // Create a new file. auto file = mClient->fileCreate(mRootHandle, name); // Make sure the file was created. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Generate some data for us to write to the file. auto expected = randomBytes(128_KiB); // Write data to the file. ASSERT_EQ(execute(write, expected.data(), *file, 0, 128_KiB), FILE_SUCCESS); // Try and flush the file to the cloud. auto handle = [file = std::move(file)]() mutable -> FileResultOr { // What events do we expect to receive? FileEventVector wanted; // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Convenience. auto info = file->info(); // Latch the file's access and modification time. auto accessed = info.accessed(); auto modified = info.modified(); // Try and flush the file to the cloud. auto result = execute(flush, *file); // Couldn't flush the file to the cloud. if (result != FILE_SUCCESS) return unexpected(result); // Make sure the file's access time has been bumped. EXPECT_GE(info.accessed(), accessed); // And that the file's modification time is unchanged. EXPECT_EQ(info.modified(), modified); // Make sure we received the events we expected. wanted.emplace_back(FileFlushEvent{info.handle(), info.id()}); EXPECT_TRUE(fileObserver.match(wanted, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(wanted, mDefaultTimeout)); // One or more of our expectations failed. if (HasFailure()) return unexpected(FILE_FAILED); // Return the file's handle. return file->info().handle(); }(); // Make sure we were able to flush the file to the cloud. ASSERT_EQ(handle.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Check that the file exists in the cloud. auto node = mClient->get(mRootHandle, name); ASSERT_EQ(node.errorOr(API_OK), API_OK); ASSERT_EQ(node->mHandle, *handle); // Reopen the file. file = mClient->fileOpen(*handle); // Make sure the file was opened successfully. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the data we uploaded was the data we wrote. auto computed = execute(read, *file, 0, 128_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_FALSE(computed->compare(expected)); } TEST_F(FileServiceTests, create_succeeds) { FileID id0; // Create a file and latch its ID. { // Try and create a file. auto file = mClient->fileCreate(mRootHandle, randomName()); // Make sure the file was created. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure we can get information about the file. auto info0 = file->info(); auto info1 = mClient->fileInfo(info0.id()); ASSERT_EQ(info1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(info0, *info1); // Make sure the file isn't associated with any node. EXPECT_TRUE(info0.handle().isUndef()); // And that the file's empty. EXPECT_EQ(info0.size(), 0u); // And that its access and modification time have been set. EXPECT_EQ(info0.accessed(), info0.modified()); // Latch the file's ID. id0 = info0.id(); // Remove the file from storage. ASSERT_EQ(execute(remove, *file), FILE_SUCCESS); } // Make sure the file's been purged from storage. auto info = mClient->fileInfo(id0); ASSERT_EQ(info.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_UNKNOWN_FILE); // Try and create a new file. auto file1 = mClient->fileCreate(mRootHandle, randomName()); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure our original file's ID was recycled. EXPECT_EQ(file1->info().id(), id0); // Create a new file. auto file2 = mClient->fileCreate(mRootHandle, randomName()); ASSERT_EQ(file2.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure it has a newly generated ID. EXPECT_NE(file2->info().id(), id0); } TEST_F(FileServiceTests, create_write_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Create a new file. auto file = mClient->fileCreate(mRootHandle, randomName()); // Make sure the file was created. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Events we expect to receive. FileEventVector expected; // So we can track what events were emitted for our file. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Generate some data for us to write to the file. auto data = randomBytes(64_KiB); // Try and write data to the file. ASSERT_EQ(execute(write, data.data(), *file, 128_KiB, 64_KiB), FILE_SUCCESS); expected.emplace_back(FileWriteEvent{FileRange(128_KiB, 192_KiB), file->info().id()}); // Make sure the file's size is correct. ASSERT_EQ(file->info().size(), 192_KiB); // The file should have one range starting from the beginning of the file. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 192_KiB))); // All data before what we wrote should be zeroed. auto computed = execute(read, *file, 0, 128_KiB); // Make sure the read succeeded. ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure the data we read is nothing but zeroes. ASSERT_EQ(computed->find_first_not_of('\0'), std::string::npos); // We should be able to read back what we wrote. computed = execute(read, *file, 128_KiB, 64_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(data, *computed); // Write more data to the file. ASSERT_EQ(execute(write, data.data(), *file, 320_KiB, 64_KiB), FILE_SUCCESS); expected.emplace_back(FileWriteEvent{FileRange(320_KiB, 384_KiB), file->info().id()}); // Make sure the file's size is correct. ASSERT_EQ(file->info().size(), 384_KiB); // We should still have a single range. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 384_KiB))); // We should be able to read back what we wrote. computed = execute(read, *file, 320_KiB, 64_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(data, *computed); // Make sure we received the events we were expecting. ASSERT_TRUE(fileObserver.match(expected, mDefaultTimeout)); ASSERT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, fetch_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Open a file for reading. auto file = mClient->fileOpen(mFileHandle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read some ranges from the file. ASSERT_EQ(execute(read, *file, 256_KiB, 256_KiB).errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(execute(read, *file, 768_KiB, 128_KiB).errorOr(FILE_SUCCESS), FILE_SUCCESS); // Reads shouldn't dirty a file. ASSERT_FALSE(file->info().dirty()); // Make sure two ranges are active. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(256_KiB, 512_KiB), FileRange(768_KiB, 896_KiB))); // Try and fetch the rest of the file's content. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Fetching shouldn't dirty a file. ASSERT_FALSE(file->info().dirty()); // We should now have a single range. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 1_MiB))); } TEST_F(FileServiceTests, file_destroyed_on_client_thread_during_read_callback_succeeds) { // Open our test file for reading. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure readahead is disabled. mClient->fileService().options(DisableReadahead); // Make sure the client doesn't complete the download in one hit. mClient->setDownloadSpeed(8192); // Try and read all of the file's data. auto waiter = [file = std::move(*file)]() mutable { // Transmits read result to our waiter. auto notifier = makeSharedPromise(); // Try and read all of the file's data. // // Note that our callback will only be executed once. file.read( [file, notifier](auto&& result) mutable { // Called to notify our waiter on the client's thread. auto notify = [](auto&, auto& notifier, auto result, auto&) { notifier->set_value(result); }; // notify // Notify our waiter from the client's thread. mClient->execute(std::bind(std::move(notify), std::move(file), std::move(notifier), result.errorOr(FILE_SUCCESS), std::placeholders::_1)); }, 0, mFileContent.size()); // Return a waiter to our caller. return notifier->get_future(); }(); // Wait for the read (and destruction of file) to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); } TEST_F(FileServiceTests, flush_cancel_on_client_logout_succeeds) { // Create a client that we can safely logout. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(0), API_OK); // Upload content so we have a file we can safely mess with. auto handle = client->upload(randomBytes(512_KiB), randomName(), "/z"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file so we can modify it. auto file = client->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Truncate the file so it's considered modified. ASSERT_EQ(execute(truncate, *file, 256_KiB), FILE_SUCCESS); // Retrieve the file's remaining content. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Make sure the upload doesn't complete before we logout the client. client->setUploadSpeed(4096); // Try and flush our local changes. auto waiter = flush(std::move(*file)); // Log out the client. EXPECT_EQ(client->logout(true), API_OK); // Wait for the flush to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); // Make sure the flush was cancelled. ASSERT_EQ(waiter.get(), FILE_CANCELLED); } TEST_F(FileServiceTests, flush_cancel_on_file_destruction_succeeds) { // Upload content so we have a file we can safely mess with. auto handle = mClient->upload(randomBytes(512_KiB), randomName(), "/z"); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file so we can modify it. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Initiate truncate and fetch requests. auto truncated = truncate(*file, 256_KiB); auto fetched = fetch(*file); // Make sure our truncate request completed succesfully. ASSERT_NE(truncated.wait_for(mDefaultTimeout), timeout); ASSERT_EQ(truncated.get(), FILE_SUCCESS); // Make sure our fetch request completed successfully. ASSERT_NE(fetched.wait_for(mDefaultTimeout), timeout); ASSERT_EQ(fetched.get(), FILE_SUCCESS); // Make sure the upload doesn't complete before we can the file. mClient->setUploadSpeed(4096); // Flush the file. auto waiter = [](File file) { // So we can wait for the request to complete. auto notifier = makeSharedPromise(); // Try and flush our changes to the cloud. file.flush( [notifier](FileResult result) { return notifier->set_value(result); }); // Return waiter to our caller. return notifier->get_future(); }(std::move(*file)); // Wait for the flush to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); // Make sure the flush was cancelled. ASSERT_EQ(waiter.get(), FILE_CANCELLED); } TEST_F(FileServiceTests, flush_removed_file_fails) { // Create a file we can safely remove. auto handle = mClient->upload(randomBytes(512_KiB), randomName(), mRootHandle); // Make sure we could create our file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file. auto file = mClient->fileOpen(*handle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Retrieve the file's data. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Remove the file from the cloud. ASSERT_EQ(mClient->remove(*handle), API_OK); // Touch the file so that it has been modified. ASSERT_EQ(execute(touch, *file, now() + 1), FILE_SUCCESS); // You shouldn't be able to flush a file that has been removed. ASSERT_EQ(execute(flush, *file), FILE_REMOVED); } TEST_F(FileServiceTests, flush_succeeds) { // Generate content for us to mutate. auto initial = randomBytes(512_KiB); // Upload content so we have a file we can safely mess with. auto oldHandle = mClient->upload(initial, randomName(), "/z"); ASSERT_EQ(oldHandle.errorOr(API_OK), API_OK); // Open the file we uploaded. auto oldFile = mClient->fileOpen(*oldHandle); ASSERT_EQ(oldFile.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Generate some content for us to write to the file. auto content = randomBytes(128_KiB); // Write content to our file. ASSERT_EQ(execute(write, content.data(), *oldFile, 128_KiB, 128_KiB), FILE_SUCCESS); ASSERT_EQ(execute(write, content.data(), *oldFile, 384_KiB, 128_KiB), FILE_SUCCESS); // Make sure the file's been marked as dirty. ASSERT_TRUE(oldFile->info().dirty()); // Keep track of the file's expected content. auto expected = initial; expected.replace(128_KiB, 128_KiB, content); expected.replace(384_KiB, 128_KiB, content); // Flush our modifications to the cloud. { // So we can receive file events. auto fileObserver = observe(*oldFile); auto serviceObserver = observe(mClient->fileService()); // The file events we expect to receive. FileEventVector wanted; // Latch the file's ID. auto id = oldFile->info().id(); // Flush our modifications to the cloud. ASSERT_EQ(execute(flush, *oldFile), FILE_SUCCESS); // Make sure the file's ID hasn't changed. ASSERT_EQ(oldFile->info().id(), id); // Make sure the file's no longer marked as dirty. ASSERT_FALSE(oldFile->info().dirty()); // Make sure we received a flush event. wanted.emplace_back(FileFlushEvent{oldFile->info().handle(), id}); EXPECT_TRUE(fileObserver.match(wanted, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(wanted, mDefaultTimeout)); } // Latch the file's new handle. auto newHandle = oldFile->info().handle(); // Make sure the file's handle has changed. ASSERT_NE(newHandle, *oldHandle); // Make sure our updated file's in the cloud. EXPECT_TRUE(waitFor( [&]() { return mClient->get(newHandle); }, mDefaultTimeout)); // Better log message in Jenkins. EXPECT_EQ(mClient->get(newHandle).errorOr(API_OK), API_OK); if (HasFailure()) return; // Make sure the file hasn't been marked as removed. ASSERT_FALSE(oldFile->info().removed()); // Make sure we can get information about the file using its new handle. { auto info = mClient->fileInfo(newHandle); ASSERT_EQ(info.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(info->id(), oldFile->info().id()); } // Remove the file from storage. ASSERT_EQ(execute(purge, std::move(*oldFile)), FILE_SUCCESS); // newHandle and oldHandle now represent distinct files. auto newFile = mClient->fileOpen(newHandle); ASSERT_EQ(newFile.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); oldFile = mClient->fileOpen(*oldHandle); ASSERT_EQ(oldFile.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure newFile and oldFile are distinct. ASSERT_NE(newFile->info().id(), oldFile->info().id()); // Make sure oldFile's content is unchanged. auto computed = execute(read, *oldFile, 0, 512_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(computed, initial); // Make sure newFile's content includes our changes. computed = execute(read, *newFile, 0, 512_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(computed, expected); // Touch the file so that it's considered modified. ASSERT_EQ(execute(touch, *newFile, newFile->info().modified() - 1), FILE_SUCCESS); // Disable versioning. // // The reason for this is that when we upload a new version of our file, // the original version will first be removed and we want to make sure // the node event logic properly handles this situation. mClient->useVersioning(false); // What events do we expect to receive? FileEventVector wanted; // So we can receive file events. auto fileObserver = observe(*newFile); auto serviceObserver = observe(mClient->fileService()); // Flush our file to the cloud. ASSERT_EQ(execute(flush, *newFile), FILE_SUCCESS); // Make sure our file's handle has changed. oldHandle = newHandle; newHandle = newFile->info().handle(); ASSERT_NE(oldHandle, newHandle); // Make sure we received a flush event. wanted.emplace_back(FileFlushEvent{newHandle, newFile->info().id()}); EXPECT_TRUE(fileObserver.match(wanted, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(wanted, mDefaultTimeout)); // Make sure our updated file is in the cloud. EXPECT_TRUE(waitFor( [&]() { return mClient->get(newHandle); }, mDefaultTimeout)); EXPECT_EQ(mClient->get(newHandle).errorOr(API_OK), API_OK); if (HasFailure()) return; // Make sure our file hasn't been marked as removed. ASSERT_FALSE(newFile->info().removed()); } TEST_F(FileServiceTests, foreign_files_are_read_only) { // Create a foreign client. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(1), API_OK); // Get the public link for mClient's test file. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Add mClient's test file to client. auto id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Open the added file. auto file = client->fileOpen(*id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Generate some data for us to write to the file. auto data = randomBytes(512); // Convenience. auto info = [info = file->info()]() { return std::make_tuple(info.dirty(), info.modified(), info.size()); }; // info // Latch the file's modification time and size. auto before = info(); // You shouldn't be able to append data to a foreign file. EXPECT_EQ(execute(append, data.data(), *file, data.size()), FILE_READONLY); // Make sure the file's information hasn't changed. EXPECT_EQ(before, info()); // You shouldn't be able to write data to a foreign file. EXPECT_EQ(execute(write, data.data(), *file, 0, data.size()), FILE_READONLY); // Make sure the file's information hasn't changed. EXPECT_EQ(before, info()); // You shouldn't be able to touch a foreign file. EXPECT_EQ(execute(touch, *file, 0l), FILE_READONLY); // Make sure the file's information hasn't changed. EXPECT_EQ(before, info()); // You shouldn't be able to truncate a foreign file. EXPECT_EQ(execute(truncate, *file, 0ul), FILE_READONLY); // Make sure the file's information hasn't changed. EXPECT_EQ(before, info()); // You should be able to flush a file but it'll be a no-op. EXPECT_EQ(execute(flush, *file), FILE_SUCCESS); } TEST_F(FileServiceTests, inactive_file_moved) { // For later reference. auto name0 = randomName(); // Create a file in the cloud that we can move freely. auto handle = mClient->upload(randomBytes(512), name0, mRootHandle); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file so that the service is aware of it. ASSERT_EQ(mClient->fileOpen(*handle).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto observer = observe(mClient->fileService()); auto name1 = randomName(); // Move the file in the cloud. ASSERT_EQ(mClient->move(name1, *handle, mRootHandle), API_OK); // Wait for the client to recognize the move. EXPECT_TRUE(waitFor( [&]() { return !mClient->get(mRootHandle, name0) && mClient->get(mRootHandle, name1); }, mDefaultTimeout)); EXPECT_EQ(mClient->get(mRootHandle, name0).errorOr(API_OK), API_FUSE_ENOTFOUND); EXPECT_EQ(mClient->get(mRootHandle, name1).errorOr(API_OK), API_OK); if (HasFailure()) return; // Reopen the file. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the file's location has been updated. auto location = file->info().location(); ASSERT_TRUE(location); EXPECT_EQ(location->mName, name1); EXPECT_EQ(location->mParentHandle, mRootHandle); // And that we received a move event. FileEventVector expected; expected.emplace_back(FileMoveEvent{FileLocation{name0, mRootHandle}, FileLocation{name1, mRootHandle}, FileID::from(*handle)}); EXPECT_TRUE(observer.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, inactive_file_removed) { // Create a file that we can remove. auto handle = mClient->upload(randomBytes(512), randomName(), mRootHandle); ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file so that the service is aware of it. ASSERT_EQ(mClient->fileOpen(*handle).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto observer = observe(mClient->fileService()); // Remove the file from the cloud. ASSERT_EQ(mClient->remove(*handle), API_OK); // Wait for the client to realize the file's been removed. EXPECT_TRUE(waitFor( [&]() { return mClient->get(*handle).errorOr(API_OK) == API_ENOENT; }, mDefaultTimeout)); EXPECT_EQ(mClient->get(*handle).errorOr(API_OK), API_ENOENT); if (HasFailure()) return; // Make sure we received a removed event. FileEventVector expected; expected.emplace_back(FileRemoveEvent{FileID::from(*handle), false}); EXPECT_TRUE(observer.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, inactive_file_replaced) { // For later reference. auto name0 = randomName(); auto name1 = randomName(); // Create a file that we can move. auto handle = mClient->upload(randomBytes(512), name0, mRootHandle); ASSERT_EQ(handle.errorOr(API_OK), API_OK); FileID id; // Get the ID of a new local file. { // Create a new local file. auto file = mClient->fileCreate(mRootHandle, name1); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Latch the new file's ID. id = file->info().id(); } // So we can receive file events. auto observer = observe(mClient->fileService()); // Move our cloud file such that it replaces our inactive local file. ASSERT_EQ(mClient->move(name1, *handle, mRootHandle), API_OK); // Wait for the client to recognize the move. EXPECT_TRUE(waitFor( [&]() { return !mClient->get(mRootHandle, name0) && mClient->get(mRootHandle, name1); }, mDefaultTimeout)); EXPECT_EQ(mClient->get(mRootHandle, name0).errorOr(API_OK), API_FUSE_ENOTFOUND); EXPECT_EQ(mClient->get(mRootHandle, name1).errorOr(API_OK), API_OK); // Make sure we received a remove event for our inactive file. FileEventVector expected; expected.emplace_back(FileRemoveEvent{id, true}); EXPECT_TRUE(observer.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, info_directory_fails) { // Can't get info about a directory. EXPECT_EQ(mClient->fileInfo("/z").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_UNKNOWN_FILE); } TEST_F(FileServiceTests, info_unknown_fails) { // Can't get info about a file the service isn't managing. EXPECT_EQ(mClient->fileInfo(mFileHandle).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_UNKNOWN_FILE); } TEST_F(FileServiceTests, info_succeeds) { // Convenience. using std::chrono::seconds; // Open our test file. auto file = mClient->fileOpen(mFileHandle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Latch the file's access time. auto accessed = file->info().accessed(); // Move time forward. std::this_thread::sleep_for(seconds(1)); // Get our hands on the file's information. auto info = mClient->fileInfo(mFileHandle); // Make sure we could get our hands on the file's information. ASSERT_EQ(info.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the file's access time hasn't changed. ASSERT_EQ(info->accessed(), accessed); } TEST_F(FileServiceTests, local_file_removed_when_parent_removed) { // Create a directory tree for us to play with. auto d0 = mClient->makeDirectory(randomName(), mRootHandle); ASSERT_EQ(d0.errorOr(API_OK), API_OK); auto d1 = mClient->makeDirectory(randomName(), *d0); ASSERT_EQ(d1.errorOr(API_OK), API_OK); // Make sure the directories are visible to our client. EXPECT_TRUE(waitFor( [&]() { return mClient->get(*d0) && mClient->get(*d1); }, mDefaultTimeout)); // For better log messages on Jenkins. EXPECT_EQ(mClient->get(*d0).errorOr(API_OK), API_OK); EXPECT_EQ(mClient->get(*d1).errorOr(API_OK), API_OK); if (HasFailure()) return; // Create a couple test files. auto d0f = mClient->fileCreate(*d0, randomName()); ASSERT_EQ(d0f.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); auto d1f = mClient->fileCreate(*d1, randomName()); ASSERT_EQ(d1f.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver0 = observe(*d0f); auto fileObserver1 = observe(*d1f); auto serviceObserver = observe(mClient->fileService()); // Remove d0 and by proxy, d0f, d1 and d1f. ASSERT_EQ(mClient->remove(*d0), API_OK); // Make sure the directories are no longer visible to our client. EXPECT_TRUE(waitFor( [&]() { return !mClient->get(*d0) && !mClient->get(*d1); }, mDefaultTimeout)); // Better log messages. EXPECT_EQ(mClient->get(*d0).errorOr(API_OK), API_ENOENT); EXPECT_EQ(mClient->get(*d1).errorOr(API_OK), API_ENOENT); if (HasFailure()) return; // Make sure our files have been marked as removed. EXPECT_TRUE(d0f->info().removed()); EXPECT_TRUE(d1f->info().removed()); // And that we received remove events. FileEventVector expected; expected.emplace_back(FileRemoveEvent{d0f->info().id(), false}); EXPECT_TRUE(fileObserver0.match(expected, mDefaultTimeout)); expected.emplace_back(FileRemoveEvent{d1f->info().id(), false}); // UnorderedElementsAreArray(...) necessary as order is unpredictable. EXPECT_THAT(expected, UnorderedElementsAreArray(serviceObserver.events())); expected.erase(expected.begin()); EXPECT_TRUE(fileObserver1.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, local_file_removed_when_replaced_by_cloud_add) { // Generate a name for our file. auto name = randomName(); // Create a local file. auto file = mClient->fileCreate(mRootHandle, name); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Add a directory with the same name and parent as our file. auto directory = mClient->makeDirectory(name, mRootHandle); ASSERT_EQ(directory.errorOr(API_OK), API_OK); // Make sure our client's aware of the new directory. EXPECT_TRUE(waitFor( [&]() { return mClient->get(*directory); }, mDefaultTimeout)); // For better messages on Jenkins. EXPECT_EQ(mClient->get(*directory).errorOr(API_OK), API_OK); if (HasFailure()) return; // Make sure our file has been marked as removed. ASSERT_TRUE(file->info().removed()); // And that we received a remove event. FileEventVector expected; expected.emplace_back(FileRemoveEvent{file->info().id(), true}); EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, local_file_removed_when_replaced_by_cloud_move) { // So we have a stable name. auto fileName0 = randomName(); // Create a local test file. auto file0 = mClient->fileCreate(mRootHandle, fileName0); ASSERT_EQ(file0.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // As above. auto fileName1 = randomName(); // Add a new file to the cloud. auto handle0 = mClient->upload(randomBytes(512), fileName1, mRootHandle); ASSERT_EQ(handle0.errorOr(API_OK), API_OK); // Open the file. auto file1 = mClient->fileOpen(*handle0); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver0 = observe(*file0); auto fileObserver1 = observe(*file1); auto serviceObserver = observe(mClient->fileService()); // Move file1 so it replaces file0. ASSERT_EQ(mClient->move(fileName0, *handle0, mRootHandle), API_OK); // Make sure the client recognizes the move. EXPECT_TRUE(waitFor( [&]() { return !mClient->get(mRootHandle, fileName1) && mClient->get(mRootHandle, fileName0); }, mDefaultTimeout)); EXPECT_EQ(mClient->get(mRootHandle, fileName1).errorOr(API_OK), API_FUSE_ENOTFOUND); EXPECT_EQ(mClient->get(mRootHandle, fileName0).errorOr(API_OK), API_OK); if (HasFailure()) return; // Make sure file0 has been marked as removed. EXPECT_TRUE(file0->info().removed()); // And that we received a remove event. struct { FileEventVector file0; FileEventVector file1; FileEventVector service; } expected; expected.file0.emplace_back(FileRemoveEvent{file0->info().id(), true}); expected.file1.emplace_back(FileMoveEvent{FileLocation{fileName1, mRootHandle}, FileLocation{fileName0, mRootHandle}, file1->info().id()}); expected.service.emplace_back(expected.file0.back()); expected.service.emplace_back(expected.file1.back()); EXPECT_TRUE(fileObserver0.match(expected.file0, mDefaultTimeout)); EXPECT_TRUE(fileObserver1.match(expected.file1, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected.service, mDefaultTimeout)); } TEST_F(FileServiceTests, location_updated_when_moved_in_cloud) { // Generate a name for our file. auto name = randomName(); // Upload a file that we can move. auto handle = mClient->upload(randomBytes(512), name, mRootHandle); // Make sure we could upload the file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Try and open the file. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the file's location is correct. auto location = file->info().location(); ASSERT_TRUE(location); EXPECT_EQ(location->mName, name); ASSERT_EQ(location->mParentHandle, mRootHandle); // Expected new location. FileLocation newLocation{randomName(), mRootHandle}; // Sanity. ASSERT_NE(location, newLocation); // Move the file in the cloud. ASSERT_EQ(mClient->move(newLocation.mName, *handle, mRootHandle), API_OK); // Our file's location should change. EXPECT_TRUE(waitFor( [&]() { return file->info().location() == newLocation; }, mDefaultTimeout)); // Make sure the file's location has updated. EXPECT_EQ(file->info().location(), newLocation); // And that we received a move event. FileEventVector expected; expected.emplace_back( FileMoveEvent{std::move(*location), std::move(newLocation), file->info().id()}); EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, open_by_path_fails_when_file_is_a_directory) { EXPECT_EQ(mClient->fileOpen("/", "z").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_IS_A_DIRECTORY); } TEST_F(FileServiceTests, open_by_path_fails_when_file_is_unknown) { EXPECT_EQ(mClient->fileOpen("/z", "q").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); } TEST_F(FileServiceTests, open_by_path_fails_when_name_is_empty) { EXPECT_EQ(mClient->fileOpen("/z", "").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_INVALID_NAME); } TEST_F(FileServiceTests, open_by_path_fails_when_parent_is_a_file) { EXPECT_EQ(mClient->fileOpen(mFileHandle, "x").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_PARENT_IS_A_FILE); } TEST_F(FileServiceTests, open_by_path_fails_when_parent_is_unknown) { EXPECT_EQ(mClient->fileOpen("/bogus", "x").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_PARENT_DOESNT_EXIST); } TEST_F(FileServiceTests, open_by_path_succeeds) { auto file = mClient->fileOpen("/z", mFileName); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); EXPECT_EQ(file->info().handle(), mFileHandle); } TEST_F(FileServiceTests, open_directory_fails) { // Can't open a directory. EXPECT_EQ(mClient->fileOpen("/z").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_IS_A_DIRECTORY); } TEST_F(FileServiceTests, open_file_succeeds) { // We should be able to open a file. auto file = mClient->fileOpen(mFileHandle); EXPECT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // We should be able to get information about that file. auto fileInfo = mClient->fileInfo(mFileHandle); EXPECT_EQ(fileInfo.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Files are initially clean. ASSERT_FALSE(fileInfo->dirty()); // Get our hands on the node's information. auto nodeInfo = mClient->get(mFileHandle); ASSERT_EQ(nodeInfo.errorOr(API_OK), API_OK); // Make sure the file's information matches the node's. EXPECT_EQ(fileInfo->id(), FileID::from(mFileHandle)); EXPECT_EQ(fileInfo->modified(), nodeInfo->mModified); EXPECT_EQ(fileInfo->size(), static_cast(nodeInfo->mSize)); } TEST_F(FileServiceTests, open_unknown_fails) { // Can't open a file that doesn't exist. EXPECT_EQ(mClient->fileOpen("/bogus").errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); } TEST_F(FileServiceTests, purge_foreign_file_succeeds) { // Create a foreign client. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log in the client. ASSERT_EQ(client->login(1), API_OK); // Get a link to mClient's test file. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Add mClient's test file to our foreign client. auto id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Open the test file, fetch its content and purge it from the service. { // Open the test file. auto file = client->fileOpen(*id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Retrieve the file's data. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Mark the file as removed. ASSERT_EQ(execute(purge, *file), FILE_SUCCESS); } // Make sure the file's been purged from the service. ASSERT_EQ(client->fileOpen(*id).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); } TEST_F(FileServiceTests, read_cancel_on_client_logout_succeeds) { // Create a client that we can safely logout. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(0), API_OK); // Disable readahead. client->fileService().options(DisableReadahead); // Open a file for reading. auto file = client->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the read doesn't complete before we logout the client. client->setDownloadSpeed(4096); // Kick off a read. auto waiter = read(std::move(*file), 512_KiB, 256_KiB); // Log out the client. EXPECT_EQ(client->logout(true), API_OK); // Wait for the read to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); // Make sure the read was cancelled. EXPECT_EQ(waiter.get().errorOr(FILE_SUCCESS), FILE_CANCELLED); } TEST_F(FileServiceTests, read_cancel_on_file_destruction_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Open a file for reading. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure the read doesn't complete before the file's destroyed. mClient->setDownloadSpeed(4096); // Begin the read, taking care to drop our file reference. auto waiter = readOnce(std::move(*file), 768_KiB, 256_KiB); // Wait for the read to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); // Make sure the read was cancelled. EXPECT_EQ(waiter.get().errorOr(FILE_SUCCESS), FILE_CANCELLED); } TEST_F(FileServiceTests, read_extension_succeeds) { // No minimum read size, extend if another range is <= 32K distant. mClient->fileService().options(FileServiceOptions{DefaultOptions.mMaximumRangeRetries, 32_KiB, 0u, DefaultOptions.mRangeRetryBackoff}); // Open a file for reading. auto file = mClient->fileOpen(mFileHandle); // Make sure the file was opened successfully. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read two ranges, leaving a 128KiB hole between them. auto data = execute(read, *file, 0, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); data = execute(read, *file, 192_KiB, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure we have the ranges we expect. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 64_KiB), FileRange(192_KiB, 256_KiB))); // Read another range, right in the hole we created before. data = execute(read, *file, 96_KiB, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure our range was expanded to fill the hole. ASSERT_EQ(execute(fetchBarrier, *file), FILE_SUCCESS); ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 256_KiB))); // Read another range, just beyond the extension threshold. data = execute(read, *file, 289_KiB, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure the range wasn't extended. ASSERT_EQ(execute(fetchBarrier, *file), FILE_SUCCESS); ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 256_KiB), FileRange(289_KiB, 353_KiB))); // Perform a read to make sure we extend to the left. data = execute(read, *file, 385_KiB, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(execute(fetchBarrier, *file), FILE_SUCCESS); ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 256_KiB), FileRange(289_KiB, 449_KiB))); // Perform another read to create another hole. data = execute(read, *file, 640_KiB, 64_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Perform a read to make sure we extend to the right. data = execute(read, *file, 576_KiB, 32_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(execute(fetchBarrier, *file), FILE_SUCCESS); ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 256_KiB), FileRange(289_KiB, 449_KiB), FileRange(576_KiB, 704_KiB))); // Fill remaining holes via extension. data = execute(read, *file, 272_KiB, 8_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); data = execute(read, *file, 481_KiB, 63_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // We should now have a single range. ASSERT_EQ(execute(fetchBarrier, *file), FILE_SUCCESS); ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 704_KiB))); } TEST_F(FileServiceTests, read_external_succeeds) { // Get a public link for our test directory. auto link = mClient->getPublicLink(mRootHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Create a client responsible for accessing our test directory. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client into our test directory. ASSERT_EQ(client->login(*link), API_OK); // Retrieve our file's key data. auto keyData = client->keyData(mFileHandle, true); ASSERT_EQ(keyData.errorOr(API_OK), API_OK); // Log out of the test directory. ASSERT_EQ(client->logout(false), API_OK); // Log client into a account distinct from mClient. ASSERT_EQ(client->login(1), API_OK); // Try and add the file to the client. auto id = client->fileService().add(mFileHandle, *keyData, mFileContent.size()); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Try and open the file. auto file = client->fileOpen(*id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Try and read the file's content. auto computed = execute(read, *file, 0, mFileContent.size()); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure we read what we expected. ASSERT_TRUE(compare(*computed, mFileContent, 0, mFileContent.size())); } TEST_F(FileServiceTests, read_foreign_succeeds) { // Create a new client. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log the client in. ASSERT_EQ(client->login(1), API_OK); // Get our test file's public link. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Add mClient's test file to client. auto id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Open the file. auto file = client->fileOpen(*id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Try and read the file's content. auto computed = execute(read, *file, 0, mFileContent.size()); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure we've read what we expected. ASSERT_TRUE(compare(*computed, mFileContent, 0, mFileContent.size())); } TEST_F(FileServiceTests, read_removed_file_succeeds) { // Create a file for us to play with. auto handle = mClient->upload(randomBytes(512_KiB), randomName(), mRootHandle); // Make sure we could create our file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file for reading. auto file = mClient->fileOpen(*handle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Disable readahead. mClient->fileService().options(DisableReadahead); // Read some data from the file. auto data0 = execute(read, *file, 0, 256_KiB); // Make sure the read succeeded. ASSERT_EQ(data0.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Remove the file from the cloud. ASSERT_EQ(mClient->remove(*handle), API_OK); // Make sure we can read data we've already retrieved. auto data1 = execute(read, *file, 0, 256_KiB); ASSERT_EQ(data1.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_FALSE(data0->compare(*data1)); // Reading new data should fail. data1 = execute(read, *file, 256_KiB, 256_KiB); ASSERT_EQ(data1.errorOr(FILE_SUCCESS), FILE_REMOVED); } TEST_F(FileServiceTests, read_size_extension_succeeds) { // Minimum read size is 64KiB, everything else are defaults. mClient->fileService().options(FileServiceOptions{DefaultOptions.mMaximumRangeRetries, DefaultOptions.mMinimumRangeDistance, 64_KiB, DefaultOptions.mRangeRetryBackoff}); // Open a file for reading. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read 4K from the file. auto data = execute(read, *file, 0, 4_KiB); ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(static_cast(data->size()), 4_KiB); // Make sure the read's size was extended. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 64_KiB))); } TEST_F(FileServiceTests, read_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Open a file for reading. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Latch the file's access time. auto accessed = file->info().accessed(); // We should be able to read 64KiB from the beginning of the file. auto result = execute(read, *file, 0, 64_KiB); // Make sure the file's access time have been bumped. EXPECT_GE(file->info().accessed(), accessed); // Make sure the read completed successfully. ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); // And that it provided the data we expected. EXPECT_TRUE(compare(*result, mFileContent, 0, 64_KiB)); // Make sure the range is considered to be in storage. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 64_KiB))); // Latch the file's access time. accessed = file->info().accessed(); // Read another 64KiB. result = execute(read, *file, 64_KiB, 64_KiB); // Make sure the file's access time have been bumped. EXPECT_GE(file->info().accessed(), accessed); // Make sure the read completed successfully. ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); // And that it provided the data we expected. EXPECT_TRUE(compare(*result, mFileContent, 64_KiB, 64_KiB)); // We should have one 128KiB range in storage. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 128_KiB))); // Latch the file's access time. accessed = file->info().accessed(); // Kick off two reads in parallel. auto waiter0 = read(*file, 128_KiB, 64_KiB); auto waiter1 = read(*file, 192_KiB, 64_KiB); // Wait for our reads to complete. ASSERT_NE(waiter0.wait_for(mDefaultTimeout), timeout); ASSERT_NE(waiter1.wait_for(mDefaultTimeout), timeout); // Make sure the file's access time have been bumped. EXPECT_GE(file->info().accessed(), accessed); // Make sure both reads succeeded. auto result0 = waiter0.get(); auto result1 = waiter1.get(); EXPECT_EQ(result0.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_EQ(result1.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_FALSE(HasFailure()); // Make sure both reads gave us what we expected. EXPECT_TRUE(compare(*result0, mFileContent, 128_KiB, 64_KiB)); EXPECT_TRUE(compare(*result1, mFileContent, 192_KiB, 64_KiB)); // We should have one 256KiB range in storage. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 256_KiB))); // Make sure we correctly handle like identical reads. waiter0 = read(*file, 256_KiB, 64_KiB); waiter1 = read(*file, 256_KiB, 64_KiB); // Wait for both reads to complete. ASSERT_NE(waiter0.wait_for(mDefaultTimeout), timeout); ASSERT_NE(waiter1.wait_for(mDefaultTimeout), timeout); // Make sure both reads succeeded. result0 = waiter0.get(); result1 = waiter1.get(); EXPECT_EQ(result0.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_EQ(result1.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure we read what we expected. EXPECT_TRUE(compare(*result0, mFileContent, 256_KiB, 64_KiB)); EXPECT_TRUE(compare(*result1, mFileContent, 256_KiB, 64_KiB)); // Latch the file's access time. accessed = file->info().accessed(); // Make sure zero length reads are handled correctly. result = execute(read, *file, 0, 0); ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_TRUE(result->empty()); // Zero length reads shouldn't change a file's access time. EXPECT_EQ(file->info().accessed(), accessed); // Make sure reads are clamped. result = execute(read, *file, 768_KiB, 512_KiB); ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_TRUE(compare(*result, mFileContent, 768_KiB, 256_KiB)); // Make sure the file's access time have been bumped. EXPECT_GE(file->info().accessed(), accessed); // Reads should never dirty a file. ASSERT_FALSE(file->info().dirty()); } TEST_F(FileServiceTests, read_write_sequence) { // Try and open our test file. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Generate some data for us to write to the file. auto expected = randomBytes(512_KiB); // Disable readahead. mClient->fileService().options(DisableReadahead); // Make sure our initial read doesn't complete too quickly. mClient->setDownloadSpeed(4096); // Initiate a requset to read all of the file's data. auto read0 = readOnce(*file, 0, 1_MiB); // Initiate a request to overwrite all of the file's data. auto write = testing::write(expected.data(), *file, 0, expected.size()); // Initiate a request to read some of the file's new data. auto read1 = read(*file, 0, expected.size()); // Allow our reads to complete quickly. mClient->setDownloadSpeed(0); // Wait for our requests to complete. EXPECT_NE(read0.wait_for(mDefaultTimeout), timeout); EXPECT_NE(write.wait_for(mDefaultTimeout), timeout); EXPECT_NE(read1.wait_for(mDefaultTimeout), timeout); // One or more requests timed out. if (HasFailure()) return; // Make sure all of our requests succeeded. auto readResult0 = read0.get(); auto readResult1 = read1.get(); auto writeResult = write.get(); EXPECT_EQ(readResult0.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_EQ(readResult1.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_EQ(writeResult, FILE_SUCCESS); // One or more requests failed. if (HasFailure()) return; // The first read should return the file's original data. EXPECT_FALSE(mFileContent.compare(0, readResult0->size(), *readResult0)); // The second read should return the file's updated data. EXPECT_EQ(expected.size(), readResult1->size()); EXPECT_FALSE(expected.compare(*readResult1)); } TEST_F(FileServiceTests, reclaim_all_succeeds) { // Handles of our test files. std::vector handles; // Create some test files for us to play with. for (auto i = 0; i < 4; ++i) { // Generate some data for our file. auto data = randomBytes(1_MiB); // Generate a name for our file. auto name = randomName(); // Try and upload our test file. auto handle = mClient->upload(data, name, mRootHandle); // Make sure the upload succeeded. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Remember the file's node handle. handles.emplace_back(*handle); } // Tracks each file that we've opened. std::vector files; // We'll be modifying these options later. auto options = DisableReadahead; // Disable readahead. // // This is necessary to ensure we read only as much as specified. mClient->fileService().options(options); // Open, read and modify each file. for (auto handle: handles) { // Try and open the file. auto file = mClient->fileOpen(handle); // Make sure we could open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read some data from the file. auto data = execute(read, *file, 0, 512_KiB); // Make sure the read succeeded. ASSERT_EQ(data.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Modify the file. ASSERT_EQ(execute(write, data->data(), *file, 0, 32_KiB), FILE_SUCCESS); // Make sure the service doesn't purge the file. files.emplace_back(std::move(*file)); } // Determine how much storage the service is using. auto usedBefore = mClient->fileService().storageUsed(); ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure we're using only as much as we read. ASSERT_EQ(*usedBefore, 512_KiB * files.size()); // Try and reclaim some storage. // // This should have no effect as we've specified no quota. auto reclaimed = execute(reclaimAll, mClient); ASSERT_EQ(reclaimed.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(reclaimed.valueOr(0ul), 0ul); // Make sure no storage was reclaimed. auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(*usedAfter, *usedBefore); // Let the service know it should store no more than 544K. options.mReclaimSizeThreshold = 544_KiB; // Reclaim files that haven't been accessed for three hours. options.mReclaimAgeThreshold = std::chrono::hours(3); mClient->fileService().options(options); // Try and reclaim storage. // // This should also have no effect as we accessed the files recently. reclaimed = execute(reclaimAll, mClient); ASSERT_EQ(reclaimed.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(reclaimed.valueOr(0ul), 0ul); // Make sure no storage was reclaimed. usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(*usedBefore, *usedAfter); // Let the service know it can reclaim files regardless of access time. options.mReclaimAgeThreshold = std::chrono::hours(0); // Reclaim a single file at a time. options.mReclaimBatchSize = 1; mClient->fileService().options(options); // Try and reclaim storage. reclaimed = execute(reclaimAll, mClient); ASSERT_EQ(reclaimed.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure storage was reclaimed. usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_LT(*usedAfter, *usedBefore); ASSERT_EQ(*reclaimed, *usedBefore - *usedAfter); // For later comparison. usedBefore = usedAfter; // Try and reclaim storage again. // // This should have no effect as we're already below the quota. reclaimed = execute(reclaimAll, mClient); ASSERT_EQ(reclaimed.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(reclaimed.valueOr(0ul), 0ul); // Make sure no more storage was reclaimed. usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(*usedAfter, *usedBefore); } TEST_F(FileServiceTests, reclaim_cancel_on_file_destruction_succeeds) { // Open the test file. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Fetch the file's content. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Reclaim the file's storage. auto waiter = reclaim(std::move(*file)); // Wait for the reclamation to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); // Reopen the file. file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Fetch the file's content (if necessary.) ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Mark the file as modified (to force a flush.) ASSERT_EQ(execute(touch, *file, 0), FILE_SUCCESS); // Reclaim the file's storage. waiter = reclaim(std::move(*file)); // Wait for reclamation to complete. ASSERT_NE(waiter.wait_for(mDefaultTimeout), timeout); } TEST_F(FileServiceTests, reclaim_concurrent_succeeds) { // Disable readahead and reclamation. mClient->fileService().options( []() { auto options = DisableReadahead; options.mReclaimSizeThreshold = 0; return options; }()); // Open our test file. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Make sure all of the file's content is present on disk. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Make sure the file's actually taking space on disk. auto usedBefore = mClient->fileService().storageUsed(); ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(*usedBefore, mFileContent.size()); // Initiate several concurrent reclaim requests. using ReclaimResult = decltype(reclaim(std::declval())); using ReclaimResultVector = std::vector; ReclaimResultVector reclamations; for (auto i = 0; i < 8; ++i) reclamations.emplace_back(reclaim(*file)); // Wait for all reclamations to complete. while (!reclamations.empty()) { ASSERT_NE(reclamations.back().wait_for(mDefaultTimeout), timeout); ASSERT_EQ(reclamations.back().get().errorOr(FILE_SUCCESS), FILE_SUCCESS); reclamations.pop_back(); } // Make sure disk space has actually been reclaimed. EXPECT_TRUE(waitFor( [&]() { return mClient->fileService().storageUsed().valueOr(UINT64_MAX) == 0; }, mDefaultTimeout)); auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_LT(*usedAfter, *usedBefore); ASSERT_EQ(*usedAfter, 0ul); } TEST_F(FileServiceTests, reclaim_foreign_file_succeeds) { // Create a foreign client. auto client = CreateClient("file_service_" + randomName()); ASSERT_TRUE(client); // Log in the client. ASSERT_EQ(client->login(1), API_OK); // Get a link to mClient's test file. auto link = mClient->getPublicLink(mFileHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Add mClient's test file to our foreign client. auto id = client->fileAdd(*link); ASSERT_EQ(id.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Open the test file. auto file = client->fileOpen(*id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Retrieve the file's data. ASSERT_EQ(execute(fetch, *file), FILE_SUCCESS); // Figure out how much space our file is using on disk. auto allocated = file->info().allocatedSize(); // Make sure the file's footprint is what we expect it is. ASSERT_EQ(allocated, mFileContent.size()); // Reclaim the file's storage. auto reclaimed = execute(reclaim, *file); ASSERT_EQ(reclaimed.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(*reclaimed, mFileContent.size()); // Make sure the file's storage footprint has decreased. EXPECT_EQ(file->info().allocatedSize(), 0u); } TEST_F(FileServiceTests, reclaim_periodic_succeeds) { // Convenience. using std::chrono::hours; using std::chrono::minutes; using std::chrono::seconds; // Keeps track of our test files. std::vector files; // Disable readahead and reclamation. auto options = DisableReadahead; options.mReclaimSizeThreshold = 0; mClient->fileService().options(options); // Create a few files for us to test with. for (auto i = 0; i < 4; ++i) { // Generate some data for our test file. auto data = randomBytes(1_MiB); // Generate a name for our test file. auto name = randomName(); // Try and upload our test file to the cloud. auto handle = mClient->upload(data, name, mRootHandle); // Make sure the upload succeeded. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Try and open our test file. auto file = mClient->fileOpen(*handle); // Make sure we could open our file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read some data from the file. ASSERT_EQ(execute(read, *file, 0, 512_KiB).errorOr(FILE_SUCCESS), FILE_SUCCESS); // Make sure the service doesn't prematurely purge our file. files.emplace_back(std::move(*file)); } // Make sure our storage footprint is as we expect. auto usedBefore = mClient->fileService().storageUsed(); ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_EQ(*usedBefore, 512_KiB * files.size()); // Enable storage reclamation. options.mReclaimAgeThreshold = hours(0); options.mReclaimPeriod = seconds(15); options.mReclaimSizeThreshold = 512_KiB; mClient->fileService().options(options); // Wait for storage to be reclaimed. EXPECT_TRUE(waitFor( [&]() { return mClient->fileService().storageUsed().valueOr(0) == 512_KiB; }, minutes(5))); // So we get useful logs. auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); EXPECT_LT(*usedAfter, *usedBefore); EXPECT_EQ(*usedAfter, 512_KiB); } TEST_F(FileServiceTests, reclaim_single_succeeds) { // Generate data for us to write to the cloud. auto expected = randomBytes(512_KiB); // Create a file that we can modify. auto handle = mClient->upload(expected, randomName(), mRootHandle); // Make sure we could create our test file. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // Open the file for IO. auto file = mClient->fileOpen(*handle); // Make sure we could open our file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read some data from the file. auto computed = execute(read, *file, 0, 64_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); computed = execute(read, *file, 128_KiB, 64_KiB); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Write some data to the file. ASSERT_EQ(execute(write, expected.data(), *file, 256_KiB, 64_KiB), FILE_SUCCESS); // Update our expected buffer. expected.replace(256_KiB, 64_KiB, expected, 0, 64_KiB); // Find out how much space the service is currently using. auto usedBefore = mClient->fileService().storageUsed(); ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Try and reclaim the file's storage. { // For later comparison. auto allocatedBefore = file->info().allocatedSize(); // Try and reclaim storage. auto reclaimed = execute(reclaim, *file); // Make sure reclamation succeeded. ASSERT_EQ(reclaimed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // Convenience. auto allocatedAfter = file->info().allocatedSize(); // Make sure we actually reclaimed space. ASSERT_LT(allocatedAfter, allocatedBefore); ASSERT_EQ(*reclaimed, allocatedBefore - allocatedAfter); // Make sure our file's attributes were updated correctly. ASSERT_EQ(file->info().reportedSize(), 0u); ASSERT_EQ(file->info().size(), expected.size()); } // Make sure a new copy of our file has been uploaded to the cloud. auto info = mClient->get(file->info().handle()); ASSERT_EQ(info.errorOr(API_OK), API_OK); ASSERT_NE(*handle, info->mHandle); // Make sure we actually reclaimed space. auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_LT(*usedAfter, *usedBefore); // Make sure we can read all of the file's data. computed = execute(read, *file, 0, 512_KiB); // Make sure the read completed. ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); // And that the data we read was correct. ASSERT_TRUE(*computed == expected); } TEST_F(FileServiceTests, remove_local_succeeds) { // Records the ID of the file created directly below. FileID id; // Records how much storage space our test file occupied. FileServiceResultOr usedBefore; // Create and then remove a local file. { // Generate a name for our file. auto name = randomName(); // Create a local file. auto file0 = mClient->fileCreate(mRootHandle, name); // Make sure we could create the file. ASSERT_EQ(file0.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Latch the file's ID. id = file0->info().id(); // Generate some data to write to our file. auto data = randomBytes(512_KiB); // Write some data to the file. ASSERT_EQ(execute(write, data.data(), *file0, 0, data.size()), FILE_SUCCESS); // Figure out how much space our file's using. usedBefore = mClient->fileService().storageUsed(); // Make sure we could determine how much space our file was using. ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // What events do we expect to receive? FileEventVector expected; // So we can receive file events. auto fileObserver = observe(*file0); auto serviceObserver = observe(mClient->fileService()); // Remove the file. ASSERT_EQ(execute(remove, *file0), FILE_SUCCESS); expected.emplace_back(FileRemoveEvent{id, false}); // Make sure the file's been marked as removed. ASSERT_TRUE(file0->info().removed()); // Make sure we received a remove event. EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); // Make sure we can't get a new reference to a removed file. ASSERT_EQ(mClient->fileInfo(id).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_UNKNOWN_FILE); ASSERT_EQ(mClient->fileOpen(id).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); // Make sure we can create another file at the same location. auto file1 = mClient->fileCreate(mRootHandle, name); // Make sure we could create our file. ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Mark that file for removal, too. ASSERT_EQ(execute(remove, *file1), FILE_SUCCESS); } // Make sure the file's been removed. auto file = mClient->fileOpen(id); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); // Make sure storage space has been recovered. auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_LT(*usedAfter, *usedBefore); } TEST_F(FileServiceTests, remove_cloud_succeeds) { // Generate a name for our file. auto name = randomName(); // Upload a file to the cloud. auto handle = mClient->upload(randomBytes(512_KiB), name, mRootHandle); // Make sure our file was uploaded. ASSERT_EQ(handle.errorOr(API_OK), API_OK); // How much storage space our file used before it was removed. FileServiceResultOr usedBefore; // Open the file, read some data and then remove it. { // Try and open our file. auto file0 = mClient->fileOpen(*handle); // Make sure we could open the file. ASSERT_EQ(file0.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Latch the file's ID. auto id = file0->info().id(); // Read all data from the file. ASSERT_EQ(execute(fetch, *file0), FILE_SUCCESS); // Figure out how much storage space our file's using. usedBefore = mClient->fileService().storageUsed(); ASSERT_EQ(usedBefore.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // What events do we expect to receive? FileEventVector expected; // So we can receive file events. auto fileObserver = observe(*file0); auto serviceObserver = observe(mClient->fileService()); // Remove the file. ASSERT_EQ(execute(remove, *file0), FILE_SUCCESS); expected.emplace_back(FileRemoveEvent{id, false}); // Make sure the file's been removed. ASSERT_TRUE(waitFor( [&]() { return mClient->get(*handle).errorOr(API_OK) == API_ENOENT && file0->info().removed(); }, mDefaultTimeout)); // Make sure we received a remove event. EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); EXPECT_EQ(mClient->get(*handle).errorOr(API_OK), API_ENOENT); EXPECT_TRUE(file0->info().removed()); // Make sure we can't get a new reference to a removed file. ASSERT_EQ(mClient->fileInfo(id).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_UNKNOWN_FILE); ASSERT_EQ(mClient->fileOpen(id).errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); // Make sure we can create a new file at the same location. auto file1 = mClient->fileCreate(mRootHandle, name); ASSERT_EQ(file1.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Mark that file for removal, too. ASSERT_EQ(execute(remove, *file1), FILE_SUCCESS); } // Make sure the file's been removed. auto file = mClient->fileOpen(*handle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_FILE_DOESNT_EXIST); // Make sure storage space has been recovered. auto usedAfter = mClient->fileService().storageUsed(); ASSERT_EQ(usedAfter.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); ASSERT_LT(*usedAfter, *usedBefore); } TEST_F(FileServiceTests, touch_succeeds) { // Open a file for modification. auto file = mClient->fileOpen(mFileHandle); // Make sure the file was opened okay. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Events we expect to receive. FileEventVector expected; // So we can keep track of our file's events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Get our hands on the file's attributes. auto info = file->info(); // Files should be clean initially. ASSERT_FALSE(info.dirty()); // Latch the file's current access and modification time. auto accessed = info.accessed(); auto modified = info.modified(); // Try and update the file's modification time. ASSERT_EQ(execute(touch, *file, modified - 1), FILE_SUCCESS); expected.emplace_back(FileTouchEvent{file->info().id(), modified - 1}); // Make sure the file's now considered dirty. EXPECT_TRUE(info.dirty()); // Make sure the file's access time has been updated. EXPECT_GE(info.accessed(), accessed); EXPECT_GE(info.accessed(), info.modified()); // Make sure the file's modification time was updated. EXPECT_EQ(info.modified(), modified - 1); // Make sure we received an event. ASSERT_TRUE(fileObserver.match(expected, mDefaultTimeout)); ASSERT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, truncate_with_ranges_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // Open the file for truncation. auto file = mClient->fileOpen(mFileHandle); // Make sure the file was opened. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Reads a range from the file. auto read = [&](std::uint64_t offset, std::uint64_t length) { return execute(testing::read, *file, offset, length); }; // read // Download a range from the file. auto fetch = [&](std::uint64_t offset, std::uint64_t length) { return read(offset, length).errorOr(FILE_SUCCESS); }; // fetch // Truncate the file to a particular size. auto truncate = [&](std::uint64_t newSize) { // The events we expect to receive. FileEventVector expected; // So we can receive events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Get our hands on the file's attributes. auto info = file->info(); // Latch the file's current size. auto size = info.size(); // Determine whether the file should become dirty. auto dirty = newSize != size; // Latch the file's current access time. auto accessed = info.accessed(); // Latch the file's current modification time. auto modified = info.modified(); // Initiate the truncate request. auto result = execute(testing::truncate, *file, newSize); // Truncate failed. if (result != FILE_SUCCESS) return result; // We should only receive events if the file's size changed. if (dirty) { // Assume the file's size is increasing. FileTruncateEvent event{std::nullopt, info.id(), newSize}; // File's size is actually decreasing. if (newSize < size) event.mRange.emplace(newSize, size); expected.emplace_back(event); } // Make sure the file's attributes have been updated. EXPECT_EQ(info.dirty(), dirty); EXPECT_GE(info.accessed(), accessed); EXPECT_GE(info.modified(), modified); EXPECT_EQ(info.size(), newSize); // Make sure we received our expected events. EXPECT_TRUE(fileObserver.match(expected, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); // One of the above expectations wasn't met. if (HasFailure()) return FILE_FAILED; // Let the caller know the truncation was successful. return result; }; // truncate // Read a few ranges from the file. EXPECT_EQ(fetch(32_KiB, 32_KiB), FILE_SUCCESS); EXPECT_EQ(fetch(96_KiB, 32_KiB), FILE_SUCCESS); EXPECT_EQ(fetch(160_KiB, 32_KiB), FILE_SUCCESS); // Make sure we have the ranges we requested. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(32_KiB, 64_KiB), FileRange(96_KiB, 128_KiB), FileRange(160_KiB, 192_KiB))); // Truncate the file to 256KiB. ASSERT_EQ(truncate(256_KiB), FILE_SUCCESS); // Existing ranges should be unchanged. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(32_KiB, 64_KiB), FileRange(96_KiB, 128_KiB), FileRange(160_KiB, 192_KiB))); // Truncate the file to 160KiB. ASSERT_EQ(truncate(160_KiB), FILE_SUCCESS); // The range [160, 192] should have been removed. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(32_KiB, 64_KiB), FileRange(96_KiB, 128_KiB))); // Truncate the file to 112KiB. ASSERT_EQ(truncate(112_KiB), FILE_SUCCESS); // The range [96, 128] should become [96, 112]. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(32_KiB, 64_KiB), FileRange(96_KiB, 112_KiB))); // Extend the file to 256KiB. ASSERT_EQ(truncate(256_KiB), FILE_SUCCESS); // The range [96, 112] should become [96, 256]. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(32_KiB, 64_KiB), FileRange(96_KiB, 256_KiB))); } TEST_F(FileServiceTests, truncate_without_ranges_succeeds) { // Open the file for truncation. auto file = mClient->fileOpen(mFileHandle); // Make sure the file was opened. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // The events we expect to receive. FileEventVector expected; // So we can receive file events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Get our hands on the file's attributes. auto info = file->info(); // Files should be clean initially. ASSERT_FALSE(info.dirty()); // Make sure the file has no active ranges. ASSERT_EQ(file->ranges().size(), 0u); // Latch the file's current modification time and size. auto modified = info.modified(); auto size = info.size(); // We should be able to reduce the file's size. ASSERT_EQ(execute(truncate, *file, size / 2), FILE_SUCCESS); expected.emplace_back(FileTruncateEvent{FileRange(size / 2, size), info.id(), size / 2}); // Mak sure the file's become dirty. EXPECT_TRUE(info.dirty()); // Make sure the file's modification time and size were updated. EXPECT_GE(info.modified(), modified); EXPECT_EQ(info.size(), size / 2); // The file should still have no active ranges. EXPECT_EQ(file->ranges().size(), 0u); // Latch the file's current modification time. modified = info.modified(); // We should be able to grow the file's size. ASSERT_EQ(execute(truncate, *file, size), FILE_SUCCESS); expected.emplace_back(FileTruncateEvent{std::nullopt, info.id(), size}); // Make sure the file's attributes were updated. EXPECT_GE(info.modified(), modified); EXPECT_EQ(info.size(), size); // There should be a single active range. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(size / 2, size))); // Make sure we can still read the file's content. auto result = execute(read, *file, 0, size); // Make sure the read succeeded. ASSERT_EQ(result.errorOr(FILE_SUCCESS), FILE_SUCCESS); ASSERT_EQ(result->size(), static_cast(size)); // Convenience. auto length = size / 2; auto npos = std::string::npos; // Make sure we read what we expected. EXPECT_EQ(mFileContent.compare(0, length, *result, 0, length), 0); EXPECT_EQ(result->find_first_not_of('\0', length), npos); // Make sure we received the events we expected. ASSERT_TRUE(fileObserver.match(expected, mDefaultTimeout)); ASSERT_TRUE(serviceObserver.match(expected, mDefaultTimeout)); } TEST_F(FileServiceTests, write_cancels_orphan_reads) { // Open our test file. auto file = mClient->fileOpen(mFileHandle); ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Disable readahead. mClient->fileService().options(DisableReadahead); // Generate some data to write to our file. auto expected = randomBytes(512_KiB); // Initiate a read of the file's data. // // We're not using the read(...) helper function in this case because we // don't really care if we get all of the data we've asked for. auto read = readOnce(*file, 0, 1_MiB); // Write our generated data to the file. // // This should cancel an orphan read that resulted when our read above // completed with only a subset of the requested data. ASSERT_EQ(execute(write, expected.data(), *file, 0, expected.size()), FILE_SUCCESS); // Make sure our read completed. ASSERT_NE(read.wait_for(mDefaultTimeout), timeout); // Make sure the data we read was valid. auto computed = read.get(); ASSERT_EQ(computed.errorOr(FILE_SUCCESS), FILE_SUCCESS); EXPECT_FALSE(mFileContent.compare(0, computed->size(), *computed)); } TEST_F(FileServiceTests, write_succeeds) { // Disable readahead. mClient->fileService().options(DisableReadahead); // File content that's updated as we write. auto expected = mFileContent; // Open a file for writing. auto file = mClient->fileOpen(mFileHandle); // Make sure we could actually open the file. ASSERT_EQ(file.errorOr(FILE_SERVICE_SUCCESS), FILE_SERVICE_SUCCESS); // Read content from the file and make sure it matches our expectations. auto read = [&](std::uint64_t offset, std::uint64_t length) { // Try and read content from our file. auto result = execute(testing::read, *file, offset, length); // Read failed. if (!result) return result.error(); // Content isn't what we expected. if (!compare(*result, expected, offset, length)) return FILE_FAILED; // Content satisfies our expectations. return FILE_SUCCESS; }; // read // Write content to our file. auto write = [&](const void* content, std::uint64_t offset, std::uint64_t length) { // Events we want to receive. FileEventVector wanted; // So we can receive events. auto fileObserver = observe(*file); auto serviceObserver = observe(mClient->fileService()); // Get our hands on the file's information. auto info = file->info(); // Latch the file's current access time. auto accessed = info.accessed(); // Latch the file's current modification time and size. auto modified = info.modified(); // Try and write content to our file. auto result = execute(testing::write, content, *file, offset, length); // Write failed. if (result != FILE_SUCCESS) return result; wanted.emplace_back(FileWriteEvent{FileRange(offset, offset + length), info.id()}); // Compute size of local file content. auto size = std::max(expected.size(), offset + length); // Extend local file content as necessary. expected.resize(static_cast(size)); // Copy written content into our local file content buffer. std::memcpy(&expected[offset], content, length); // Make sure the file's become dirty. EXPECT_TRUE(info.dirty()); // Make sure the file's access time has been updated. EXPECT_GE(info.accessed(), accessed); // Make sure the file's modification time hasn't gone backwards. EXPECT_GE(info.modified(), modified); // Make sure the file's size has been updated correctly. EXPECT_EQ(info.size(), size); // Make sure we received the events we wanted. EXPECT_TRUE(fileObserver.match(wanted, mDefaultTimeout)); EXPECT_TRUE(serviceObserver.match(wanted, mDefaultTimeout)); // One or more of our expectations weren't satisfied. if (HasFailure()) return FILE_FAILED; // Let our caller know the write was successful. return FILE_SUCCESS; }; // write // Generate some content for us to write to the file. auto computed = randomBytes(256u * 1024); // Write 64KiB to the file. ASSERT_EQ(write(computed.data(), 64_KiB, 64_KiB), FILE_SUCCESS); // Make sure we can read back what we wrote. ASSERT_EQ(read(64_KiB, 64_KiB), FILE_SUCCESS); // And that the file has a single range. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(64_KiB, 128_KiB))); // Read 128KiB from the file. // // This should cause us to download the first 64KiB of the file. ASSERT_EQ(read(0, 128_KiB), FILE_SUCCESS); // We should have a single 128KiB range. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 128_KiB))); // Read 128KiB from the file. // // This should cause the download of two new ranges. ASSERT_EQ(read(192_KiB, 64_KiB), FILE_SUCCESS); ASSERT_EQ(read(320_KiB, 64_KiB), FILE_SUCCESS); // We should now have three ranges. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 128_KiB), FileRange(192_KiB, 256_KiB), FileRange(320_KiB, 384_KiB))); // Write 192KiB to the file. ASSERT_EQ(write(computed.data(), 160_KiB, 192_KiB), FILE_SUCCESS); // We should now have two ranges. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 128_KiB), FileRange(160_KiB, 384_KiB))); // Read 384KiB from the file. // // This should cause us to download another range. ASSERT_EQ(read(0, 384_KiB), FILE_SUCCESS); // Which should coalesce with all the other ranges. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 384_KiB))); // Make sure writes can extend the file's size. ASSERT_EQ(write(computed.data(), 2_MiB, 64_KiB), FILE_SUCCESS); // We should now have two ranges. ASSERT_THAT(file->ranges(), ElementsAre(FileRange(0, 384_KiB), FileRange(1024_KiB, 2112_KiB))); // Make sure we can read back what we wrote. ASSERT_EQ(read(2_MiB, 64_KiB), FILE_SUCCESS); } void FileServiceTests::SetUp() { ScopedWatch watch(mWatchdog, MaxTestRunTime); SingleClientTest::SetUp(); // Make sure the service's options are in a known state. mClient->fileService().options(DefaultOptions); // Make sure the service contains no lingering data. ASSERT_EQ(mClient->fileService().purge(), FILE_SERVICE_SUCCESS); // Make sure transfers proceed at full speed. mClient->setDownloadSpeed(0); mClient->setUploadSpeed(0); // Make sure the client uses versioning unless disabled explicitly. mClient->useVersioning(true); // Don't disarm the watchdog. watch.release(); } void FileServiceTests::SetUpTestSuite() { ScopedWatch watch(mWatchdog, MaxTestSetupTime); SingleClientTest::SetUpTestSuite(); // Make sure the test root is clean. ASSERT_THAT(mClient->remove("/z"), AnyOf(API_FUSE_ENOTFOUND, API_OK)); // Recreate the test root. auto rootHandle = mClient->makeDirectory("z", "/"); ASSERT_EQ(rootHandle.errorOr(API_OK), API_OK); // Generate content for our test file. mFileContent = randomBytes(1_MiB); // Generate a name for our test file. mFileName = randomName(); // Upload our content to the cloud. auto fileHandle = mClient->upload(mFileContent, mFileName, *rootHandle); ASSERT_EQ(fileHandle.errorOr(API_OK), API_OK); // Latch the file's handle for later use. mFileHandle = *fileHandle; // Latch the root handle for later use. mRootHandle = *rootHandle; // Generate link. auto link = mClient->getPublicLink(*rootHandle); ASSERT_EQ(link.errorOr(API_OK), API_OK); // Make sure the service logs *everything*. logger().logLevel(logDebug); } void FileServiceTests::TearDown() { // Disarm the watchdog. mWatchdog.disarm(); } auto append(const void* buffer, File file, std::uint64_t length) -> std::future { // So we can signal when the request has completed. auto notifier = makeSharedPromise(); // So our caller can wait until the request is completed. auto waiter = notifier->get_future(); // Execute an append request. file.append( buffer, [=](FileResult result) { notifier->set_value(result); }, length); // Return waiter to our caller. return waiter; } bool compare(const std::string& computed, const std::string& expected, std::uint64_t offset, std::uint64_t length) { // Convenience. std::uint64_t size = expected.size(); // Offset and/or length is out of bounds. if (offset + length > size) return false; // Content is smaller than expected. if (computed.size() != length) return false; // Make sure the content matches our file. return !expected.compare(offset, length, computed); } template auto execute(Function&& function, Parameters&&... arguments) -> std::enable_if_t, AsynchronousFunctionCallResultT> { // Execute function to kick off our request. auto waiter = std::invoke(std::forward(function), std::forward(arguments)...); // Figure out the function's result type. using Result = decltype(waiter.get()); // Request timed out. if (waiter.wait_for(std::chrono::minutes(4)) == timeout) return Result(GenerateFailure::value()); // Return result to our caller. return waiter.get(); } auto fetch(File file) -> std::future { // So we can signal when the request has completed. auto notifier = makeSharedPromise(); // So our caller can wait until the request has completed. auto waiter = notifier->get_future(); // Execute the fetch request. file.fetch( [=](auto result) { notifier->set_value(result); }); // Return waiter to our caller. return waiter; } auto fetchBarrier(File file) -> std::future { // So we can signal when all fetches have completed. auto notifier = makeSharedPromise(); // So our caller can wait until all fetches have completed. auto waiter = notifier->get_future(); // Execute the fetch request. file.fetchBarrier( [=]() { notifier->set_value(FILE_SUCCESS); }); // Return waiter to our caller. return waiter; } auto flush(File file) -> std::future { // So we can signal when the flush has completed. auto notifier = makeSharedPromise(); // So our caller can wait until the request has completed. auto waiter = notifier->get_future(); // Execute the flush request. file.flush( [file, notifier](auto result) { notifier->set_value(result); }); // Return waiter to our caller. return waiter; } auto purge(File file) -> std::future { // So we can notify our caller when the file has been purged. auto notifier = makeSharedPromise(); // So our caller can wait for the file to be purged. auto waiter = notifier->get_future(); // Try and purge the file. file.purge( [notifier](auto result) { notifier->set_value(result); }); // Return the waiter to our caller. return waiter; } auto read(File file, std::uint64_t offset, std::uint64_t length) -> std::future> { struct ReadContext; // Convenience. using ReadContextPtr = std::shared_ptr; // Tracks state necessary for our read. struct ReadContext { ReadContext(File file, std::uint64_t length): mBuffer(), mFile(std::move(file)), mLength(length), mNotifier(), mOffset(0u) {} // Called when we've received content. void onRead(ReadContextPtr& context, FileResultOr result) { // Couldn't read content. if (!result) return mNotifier.set_value(unexpected(result.error())); // Convenience. auto& source = result->mSource; // No more content to read. if (!result->mLength) return mNotifier.set_value(std::move(mBuffer)); // Extend buffer as needed. mBuffer.resize(mOffset + result->mLength); // Try and copy the content to our buffer. auto [count, _] = source.read(&mBuffer[mOffset], 0, result->mLength); // Couldn't copy the content to our buffer. if (count != result->mLength) return mNotifier.set_value(unexpected(FILE_FAILED)); // Bump our offset and length. mOffset += count; mLength -= count; // Read remaining content, if any. mFile.read( std::bind(&ReadContext::onRead, this, std::move(context), std::placeholders::_1), result->mOffset + count, mLength); } // Where we'll store content. std::string mBuffer; // What file are we reading from? File mFile; // How much content are we reading? std::uint64_t mLength; // Who should we notify when the read is complete? std::promise> mNotifier; // Where in the file are we reading from? std::uint64_t mOffset; }; // ReadContext // Create a context to track our read state. auto context = std::make_shared(file, length); // Get our hands on the context's future. auto waiter = context->mNotifier.get_future(); // Kick off the read. file.read( std::bind(&ReadContext::onRead, context.get(), std::move(context), std::placeholders::_1), offset, length); // Return waiter to our caller. return waiter; } auto readOnce(File file, std::uint64_t offset, std::uint64_t length) -> std::future> { // So we can signal when the read has completed. auto notifier = makeSharedPromise>(); // So our caller can wait until the read has completed. auto waiter = notifier->get_future(); // Try and perform the read. file.read( [notifier](auto&& result) { // Couldn't read any data. if (!result) return notifier->set_value(unexpected(result.error())); std::string buffer; // Preallocate necessary buffer space. buffer.resize(result->mLength); // Try and copy data into our buffer. auto [count, _] = result->mSource.read(buffer.data(), 0, result->mLength); // Couldn't copy all of the data into our buffer. if (count < result->mLength) return notifier->set_value(unexpected(FILE_FAILED)); // Transmit buffer to our waiter. notifier->set_value(std::move(buffer)); }, offset, length); // Return the waiter to our caller. return waiter; } auto reclaim(File file) -> std::future> { // So we can notify our waiter when the request completes. auto notifier = makeSharedPromise>(); // So our caller can wait until the request completes. auto waiter = notifier->get_future(); // Try and reclaim this file's storage. file.reclaim( [notifier](FileResultOr result) { notifier->set_value(result); }); // Return the waiter to our caller. return waiter; } auto reclaimAll(ClientPtr& client) -> std::future> { // Sanity. assert(client); // So we can notify our client when files have been reclaimed. auto notifier = makeSharedPromise>(); // So our caller can wait until files have been reclaimed. auto waiter = notifier->get_future(); // Try and reclaim zero or more files. client->fileService().reclaim( [notifier](auto result) { notifier->set_value(result); }); // Return the waiter to our caller. return waiter; } auto remove(File file) -> std::future { // So we can notify our caller when the file has been removed. auto notifier = makeSharedPromise(); // So our caller can wait for the file to be removed. auto waiter = notifier->get_future(); // Try and remove the file. file.remove( [notifier](auto result) { notifier->set_value(result); }, false); // Return the waiter to our caller. return waiter; } auto touch(File file, std::int64_t modified) -> std::future { // So we can notify our waiter when the request completes. auto notifier = makeSharedPromise(); // So our caller can wait until the request completes. auto waiter = notifier->get_future(); // Try and touch the file. file.touch( [notifier](FileResult result) { notifier->set_value(result); }, modified); // Return the waiter to our caller. return waiter; } auto truncate(File file, std::uint64_t newSize) -> std::future { // So we can notify our waiter when the request completes. auto notifier = makeSharedPromise(); // So our caller can wait until the request completes. auto waiter = notifier->get_future(); // Try and truncate the file. file.truncate( [notifier](FileResult result) { notifier->set_value(result); }, newSize); // Return the waiter to our caller. return waiter; } auto write(const void* buffer, File file, std::uint64_t offset, std::uint64_t length) -> std::future { struct WriteContext; // Convenience. using WriteContextPtr = std::shared_ptr; // Tracks state necessary for our write. struct WriteContext { WriteContext(const void* buffer, File file, std::uint64_t length): mBuffer(static_cast(buffer)), mFile(std::move(file)), mLength(length) {} // Called when we've written file content. void onWrite(WriteContextPtr& context, FileResultOr result) { // Couldn't write content. if (!result) return mNotifier.set_value(result.error()); // No more content to write. if (!result->mLength) return mNotifier.set_value(FILE_SUCCESS); // Bump buffer and length. mBuffer += result->mLength; mLength -= result->mLength; // Write remaning content, if any. mFile.write( mBuffer, std::bind(&WriteContext::onWrite, this, std::move(context), std::placeholders::_1), result->mLength + result->mOffset, mLength); } // The content we want to write. const std::uint8_t* mBuffer; // What file we should write content to. File mFile; // How much content we should write to our file. std::uint64_t mLength; // Who should we notify when the write is complete? std::promise mNotifier; }; // WriteContext // Create a context to track our write state. auto context = std::make_shared(buffer, file, length); // Get our hands on the context's future. auto waiter = context->mNotifier.get_future(); // Kick off the write. file.write( buffer, std::bind(&WriteContext::onWrite, context.get(), std::move(context), std::placeholders::_1), offset, length); // Return waiter to our caller. return waiter; } } // testing } // file_service } // mega sdk-10.11.0/tests/integration/file_service/mega/000077500000000000000000000000001516266226600215045ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/mega/file_service/000077500000000000000000000000001516266226600241435ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/000077500000000000000000000000001516266226600256205ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/integration/000077500000000000000000000000001516266226600301435ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/integration/client.h000066400000000000000000000034741516266226600316020ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include #include #include namespace mega { class NodeHandle; namespace file_service { namespace testing { class Client: public virtual common::testing::Client { protected: Client(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); public: virtual ~Client(); // Add a file referenced by a public link to the File Service. auto fileAdd(const PublicLink& link) -> FileServiceResultOr; // Create a new file that is to be managed by the File Service. auto fileCreate(NodeHandle parent, const std::string& name) -> FileServiceResultOr; // Retrieve information about a file managed by the File Service. auto fileInfo(FileID id) const -> FileServiceResultOr; auto fileInfo(common::testing::CloudPath path) const -> FileServiceResultOr; // Open a file managed by the File Service. auto fileOpen(FileID id) const -> FileServiceResultOr; auto fileOpen(common::testing::CloudPath parentPath, const std::string& name) const -> FileServiceResultOr; auto fileOpen(common::testing::CloudPath path) const -> FileServiceResultOr; // Get our hands on the client's File Service interface. virtual FileService& fileService() const = 0; }; // Client } // testing } // common } // mega sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/integration/client_forward.h000066400000000000000000000003041516266226600333130ustar00rootroot00000000000000#pragma once #include namespace mega { namespace file_service { namespace testing { class Client; using ClientPtr = std::unique_ptr; } // testing } // file_service } // mega sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/integration/real_client.h000066400000000000000000000013261516266226600325770ustar00rootroot00000000000000#pragma once #include #include #include #include #include namespace mega { namespace file_service { namespace testing { class RealClient: public Client, public common::testing::RealClient { public: RealClient(const std::string& clientName, const common::testing::Path& databasePath, const common::testing::Path& storagePath); ~RealClient(); // Get our hands on the client's File Service interface. FileService& fileService() const override; }; // RealClient } // testing } // file_service } // mega scoped_file_event_observer.h000066400000000000000000000072771516266226600356360ustar00rootroot00000000000000sdk-10.11.0/tests/integration/file_service/mega/file_service/testing/integration#pragma once #include #include #include #include #include #include #include #include namespace mega { namespace file_service { namespace testing { namespace detail { // Convenience. using common::Expected; template struct IsFileEventObserverID: std::false_type {}; // IsFileEventObserverID template<> struct IsFileEventObserverID: std::true_type {}; // IsFileEventObserverID template struct IsFileEventObserverID>: std::true_type {}; // IsFileEventObserverID> template constexpr auto IsFileEventObserverIDV = IsFileEventObserverID::value; template using DetectAddObserver = decltype(std::declval().addObserver(std::declval())); template using DetectRemoveObserver = decltype(std::declval().removeObserver(std::declval())); template using HasAddObserver = IsFileEventObserverID>; template using HasRemoveObserver = IsNotNoneSuch>; template using IsFileEventSource = std::conjunction, HasRemoveObserver>; template constexpr auto IsFileEventSourceV = IsFileEventSource::value; } // detail // Convenience. using detail::IsFileEventSource; using detail::IsFileEventSourceV; template class ScopedFileEventObserver { template auto extract(common::Expected id) { return id.value(); } auto extract(FileEventObserverID id) { return id; } template friend auto observe(S& source) -> std::enable_if_t, ScopedFileEventObserver>; ScopedFileEventObserver(Source& source): mID(), mSource(&source) { mID = extract(source.addObserver( [this](auto& event) { std::lock_guard guard(mEventsLock); mEvents.emplace_back(event); return FILE_EVENT_OBSERVER_KEEP; })); } // What events has this observer received? FileEventVector mEvents; // Serializes access to mEvents. mutable std::mutex mEventsLock; // The ID of our event observer. FileEventObserverID mID; // The event source that our observer is observing. Source* mSource; public: ScopedFileEventObserver(ScopedFileEventObserver&& other) = delete; ~ScopedFileEventObserver() { if (mSource) mSource->removeObserver(mID); } ScopedFileEventObserver& operator=(ScopedFileEventObserver&& rhs) = delete; FileEventVector events() const { std::lock_guard guard(mEventsLock); return mEvents; } template bool match(const FileEventVector& expected, std::chrono::duration period) const { return waitFor( [&]() { std::lock_guard guard(mEventsLock); return expected == mEvents; }, period); } }; // ScopedFileEventObserver template auto observe(Source& source) -> std::enable_if_t, ScopedFileEventObserver> { return ScopedFileEventObserver(source); } } // testing } // file_service } // mega sdk-10.11.0/tests/integration/file_service/real_client.cpp000066400000000000000000000013441516266226600235620ustar00rootroot00000000000000#include #include #include namespace mega { namespace file_service { namespace testing { using common::testing::Path; RealClient::RealClient(const std::string& clientName, const Path& databasePath, const Path& storagePath): common::testing::Client(clientName, databasePath, storagePath), Client(clientName, databasePath, storagePath), common::testing::RealClient(clientName, databasePath, storagePath) {} RealClient::~RealClient() {} FileService& RealClient::fileService() const { return mClient->mFileService; } } // testing } // file_service } // mega sdk-10.11.0/tests/integration/integration_test_utils.cpp000066400000000000000000000433431516266226600234510ustar00rootroot00000000000000#include "integration_test_utils.h" #include "gtest_common.h" #include "integration/mock_listeners.h" #include "mega/logging.h" #include "megautils.h" #include "sdk_test_utils.h" #include namespace sdk_test { using namespace mega; using namespace testing; #ifdef ENABLE_SYNC /** * @brief Aux implementation to generalize on how to get the sync */ static std::unique_ptr<::mega::MegaSync> waitForSyncState(MegaSync::SyncRunningState runState, MegaSync::Error err, std::function&& syncGetter) { std::unique_ptr sync; waitFor( [syncGetter = std::move(syncGetter), &sync, runState, err]() -> bool { sync.reset(syncGetter()); return (sync && sync->getRunState() == runState && sync->getError() == err); }, 30s); if (sync) { bool areTheExpectedStateAndError = sync->getRunState() == runState && sync->getError() == err; LOG_debug << "sync exists with the " << (areTheExpectedStateAndError ? "expected" : "UNEXPECTED") << " state: " << sync->getRunState() << " and error: " << sync->getError(); return areTheExpectedStateAndError ? std::move(sync) : nullptr; } else { LOG_debug << "sync is null"; return nullptr; // signal that the sync never reached the expected/required state } } std::unique_ptr<::mega::MegaSync> waitForSyncState(MegaApi* megaApi, MegaNode* remoteNode, MegaSync::SyncRunningState runState, MegaSync::Error err) { return waitForSyncState(runState, err, [&megaApi, &remoteNode]() -> MegaSync* { return megaApi->getSyncByNode(remoteNode); }); } std::unique_ptr<::mega::MegaSync> waitForSyncState(MegaApi* megaApi, handle backupID, MegaSync::SyncRunningState runState, MegaSync::Error err) { return waitForSyncState(runState, err, [&megaApi, backupID]() -> MegaSync* { return megaApi->getSyncByBackupId(backupID); }); } bool waitForSyncStallState(MegaApi* const megaApi) { const auto isSyncStalledPred = [&megaApi]() -> bool { return isSyncStalled(megaApi); }; return waitFor(isSyncStalledPred, 10s); } static handle createSyncAux(MegaApi* megaApi, const MegaSync::SyncType syncType, const std::string& localRootPath, const MegaHandle remoteRootHandle, const std::string& backupName) { if (!megaApi) return UNDEF; if (syncType == MegaSync::TYPE_BACKUP && remoteRootHandle != UNDEF) return UNDEF; using namespace testing; NiceMock rl{megaApi}; handle backupId = UNDEF; auto setBackupId = [&backupId](const MegaRequest& req) { backupId = req.getParentHandle(); }; rl.setErrorExpectations(API_OK, MegaSync::NO_SYNC_ERROR, MegaRequest::TYPE_ADD_SYNC, std::move(setBackupId)); megaApi->syncFolder(syncType, localRootPath.c_str(), backupName.empty() ? nullptr : backupName.c_str(), remoteRootHandle, nullptr, &rl); rl.waitForFinishOrTimeout(MAX_TIMEOUT); if (backupId == UNDEF) return UNDEF; std::unique_ptr sync = waitForSyncState(megaApi, backupId, MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); if (!sync) return UNDEF; return backupId; } handle syncFolder(MegaApi* megaApi, const std::string& localRootPath, const MegaHandle remoteRootHandle) { return createSyncAux(megaApi, MegaSync::TYPE_TWOWAY, localRootPath, remoteRootHandle, ""); } handle backupFolder(MegaApi* megaApi, const std::string& localRootPath, const std::string& backupName) { return createSyncAux(megaApi, MegaSync::TYPE_BACKUP, localRootPath, UNDEF, backupName); } bool removeSync(MegaApi* megaApi, const handle backupID) { NiceMock reqListener{megaApi}; reqListener.setErrorExpectations(API_OK); megaApi->removeSync(backupID, &reqListener); return reqListener.waitForFinishOrTimeout(MAX_TIMEOUT); } bool setSyncRunState(MegaApi* megaApi, const handle backupID, const MegaSync::SyncRunningState state) { NiceMock reqListener{megaApi}; reqListener.setErrorExpectations(API_OK); megaApi->setSyncRunState(backupID, state, &reqListener); return reqListener.waitForFinishOrTimeout(MAX_TIMEOUT); } bool resumeSync(MegaApi* megaApi, const handle backupID) { return setSyncRunState(megaApi, backupID, MegaSync::SyncRunningState::RUNSTATE_RUNNING); } bool suspendSync(MegaApi* megaApi, const handle backupID) { return setSyncRunState(megaApi, backupID, MegaSync::SyncRunningState::RUNSTATE_SUSPENDED); } bool disableSync(MegaApi* megaApi, const handle backupID) { return setSyncRunState(megaApi, backupID, MegaSync::SyncRunningState::RUNSTATE_DISABLED); } bool isSyncStalled(MegaApi* const megaApi) { if (megaApi == nullptr) return false; return megaApi->isSyncStalled(); } std::vector> getStalls(MegaApi* megaApi) { if (megaApi == nullptr) return {}; NiceMock reqList{megaApi}; const auto expectedErr = Pointee(Property(&MegaError::getErrorCode, API_OK)); std::vector> stalls; EXPECT_CALL(reqList, onRequestFinish(_, _, expectedErr)) .WillOnce( [&stalls, &reqList](MegaApi*, MegaRequest* request, MegaError*) { if (auto list = request->getMegaSyncStallList(); list != nullptr) stalls = toSyncStallVector(*list); reqList.markAsFinished(); }); megaApi->getMegaSyncStallList(&reqList); if (!reqList.waitForFinishOrTimeout(MAX_TIMEOUT)) return {}; return stalls; } #endif std::pair>, std::unique_ptr> getCloudFirstChildren(MegaApi* megaApi, const MegaHandle nodeHandle) { if (!megaApi || nodeHandle == UNDEF) return {std::nullopt, nullptr}; std::unique_ptr rootNode{megaApi->getNodeByHandle(nodeHandle)}; if (!rootNode) return {std::nullopt, nullptr}; std::unique_ptr childrenNodeList{megaApi->getChildren(rootNode.get())}; if (!childrenNodeList) return {std::nullopt, nullptr}; auto namesVector = toNamesVector(*childrenNodeList); if (namesVector.size() != static_cast(childrenNodeList->size())) { assert(false && "getCloudFirstChildren: invalid names vector size "); return {std::nullopt, nullptr}; } return {namesVector, std::move(childrenNodeList)}; } std::optional> getCloudFirstChildrenNames(MegaApi* megaApi, const MegaHandle nodeHandle) { if (!megaApi || nodeHandle == UNDEF) return {}; std::unique_ptr rootNode{megaApi->getNodeByHandle(nodeHandle)}; if (!rootNode) return {}; std::unique_ptr childrenNodeList{megaApi->getChildren(rootNode.get())}; if (!childrenNodeList) return {}; return toNamesVector(*childrenNodeList); } std::pair>, std::unique_ptr> getCloudFirstChildrenNamesAndFingerprints(MegaApi* megaApi, const MegaHandle nodeHandle) { if (!megaApi || nodeHandle == UNDEF) return {std::nullopt, nullptr}; std::unique_ptr rootNode{megaApi->getNodeByHandle(nodeHandle)}; if (!rootNode) return {std::nullopt, nullptr}; std::unique_ptr childrenNodeList{megaApi->getChildren(rootNode.get())}; if (!childrenNodeList) return {std::nullopt, nullptr}; auto namesFpsVector = toNamesAndFingerprintVector(*childrenNodeList); if (namesFpsVector.size() != static_cast(childrenNodeList->size())) { assert(false && "getCloudFirstChildrenNameAndFingerprint: invalid names and size vector length "); return {std::nullopt, nullptr}; } return {std::move(namesFpsVector), std::move(childrenNodeList)}; } void getDeviceNames(MegaApi* megaApi, std::unique_ptr& output) { NiceMock rl{megaApi}; const auto expectedErr = Pointee(Property(&MegaError::getErrorCode, API_OK)); EXPECT_CALL(rl, onRequestFinish(_, _, expectedErr)) .WillOnce( [&output, &rl](MegaApi*, MegaRequest* req, MegaError*) { output.reset(req->getMegaStringMap()->copy()); rl.markAsFinished(); }); megaApi->getUserAttribute(MegaApi::USER_ATTR_DEVICE_NAMES, &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(3min)); } std::pair getMyBackupsFolder(MegaApi* megaApi) { MegaHandle h = UNDEF; NiceMock rl{megaApi}; const auto expectedErr = Pointee(Property(&MegaError::getErrorCode, API_OK)); EXPECT_CALL(rl, onRequestFinish(_, _, expectedErr)) .WillOnce( [&h, &rl](MegaApi*, MegaRequest* req, MegaError*) { h = req->getNodeHandle(); rl.markAsFinished(); }); megaApi->getUserAttribute(MegaApi::USER_ATTR_MY_BACKUPS_FOLDER, &rl); const auto res = rl.waitForFinishOrTimeout(3min); return {res, h}; } std::pair setMyBackupsFolder(MegaApi* megaApi, const std::string& name) { MegaHandle h = UNDEF; NiceMock rl{megaApi}; const auto expectedErr = Pointee(Property(&MegaError::getErrorCode, API_OK)); EXPECT_CALL(rl, onRequestFinish(_, _, expectedErr)) .WillOnce( [&h, &rl](MegaApi*, MegaRequest* req, MegaError*) { h = req->getNodeHandle(); rl.markAsFinished(); }); megaApi->setMyBackupsFolder(name.c_str(), &rl); const auto res = rl.waitForFinishOrTimeout(3min); return {res, h}; } void ensureAccountDeviceNamesAttrExists(MegaApi* megaApi) { std::unique_ptr devices; getDeviceNames(megaApi, devices); if (devices && devices->size() != 0) return; // Set device names attr in case is not set const std::string deviceName = "Jenkins " + getCurrentTimestamp(true); const std::string deviceId = megaApi->getDeviceId(); devices->set(deviceId.c_str(), deviceName.c_str()); NiceMock rl{megaApi}; rl.setErrorExpectations(API_OK); megaApi->setUserAttribute(MegaApi::USER_ATTR_DEVICE_NAMES, devices.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } std::pair ensureMyBackupsFolderExists(MegaApi* megaApi, const std::string& name) { auto getMyBackupsNode = [megaApi](MegaHandle h) -> std::pair { std::unique_ptr myBackupsNode; if (myBackupsNode.reset(megaApi->getNodeByHandle(h)); myBackupsNode) { return {true, myBackupsNode->getName()}; } return {false, {}}; }; if (auto [succeeded, h] = getMyBackupsFolder(megaApi); succeeded && h != UNDEF) { return getMyBackupsNode(h); } if (auto [succeeded, h] = setMyBackupsFolder(megaApi, name); succeeded && h != UNDEF) { return getMyBackupsNode(h); } return {false, {}}; } std::optional downloadNode(MegaApi* megaApi, MegaNode* node, const std::filesystem::path& fsPath, bool pathIsFolder, const std::chrono::seconds timeoutInSecs, const int collisionCheck, const int collisionResolution, TransferFinishCallback transferFinishCallback, const char* customName, const char* appData, const bool startFirst, MegaCancelToken* cancelToken, const bool undelete) { if (!megaApi || !node) { LOG_err << "test_utils(downloadFile): EARGS"; return std::nullopt; } std::optional err{std::nullopt}; testing::NiceMock mtl{megaApi}; EXPECT_CALL(mtl, onTransferFinish) .WillOnce( [&mtl, &err, &transferFinishCallback](MegaApi* megaApi, MegaTransfer* t, MegaError* error) { if (transferFinishCallback) transferFinishCallback(megaApi, t, error); err = error ? error->getErrorCode() : API_EINTERNAL; mtl.markAsFinished(); }); std::string downLoadPath{path_u8string(fsPath)}; if (pathIsFolder && downLoadPath.back() != std::filesystem::path::preferred_separator) { downLoadPath.push_back(std::filesystem::path::preferred_separator); } megaApi->startDownload(node, downLoadPath.c_str(), customName, appData, startFirst, cancelToken, collisionCheck, collisionResolution, undelete, &mtl); if (!mtl.waitForFinishOrTimeout(timeoutInSecs)) { LOG_err << "test_utils(downloadFile): waitForFinishOrTimeout timeout expired"; return std::nullopt; } return err; } std::unique_ptr uploadFile(MegaApi* megaApi, const std::filesystem::path& localPath, MegaNode* parentNode, const char* fileName) { testing::NiceMock mtl{megaApi}; handle nodeHandle = UNDEF; EXPECT_CALL(mtl, onTransferFinish) .WillOnce( [&mtl, &nodeHandle](MegaApi*, MegaTransfer* transfer, MegaError* error) { nodeHandle = transfer->getNodeHandle(); mtl.markAsFinished(error->getErrorCode() == API_OK); }); std::unique_ptr fallbackParent; MegaNode* uploadParent = parentNode; if (!uploadParent) { fallbackParent.reset(megaApi->getRootNode()); uploadParent = fallbackParent.get(); } MegaUploadOptions uploadOptions; if (fileName) { uploadOptions.fileName = fileName; } uploadOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; const auto pathString = path_u8string(localPath); megaApi->startUpload(pathString, uploadParent, nullptr, &uploadOptions, &mtl); EXPECT_TRUE(mtl.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Error uploading file: " << localPath; if (nodeHandle == UNDEF) return nullptr; return std::unique_ptr(megaApi->getNodeByHandle(nodeHandle)); } std::unique_ptr uploadFile(MegaApi* megaApi, LocalTempFile&& file, MegaNode* parentNode, const char* fileName) { return uploadFile(megaApi, file.getPath(), parentNode, fileName); } handle createPasswordNode(MegaApi* megaApi, const std::string& name, const MegaNode::PasswordNodeData* data, const handle parentNodeHandle) { NiceMock rl; handle newPwdNodeHandle{UNDEF}; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE, [&newPwdNodeHandle](const MegaRequest& req) { newPwdNodeHandle = req.getNodeHandle(); }); megaApi->createPasswordNode(name.c_str(), data, parentNodeHandle, &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Password node not properly generated. Name: " << name; return newPwdNodeHandle; } handle createCreditCardNode(::mega::MegaApi* megaApi, const std::string& name, const ::mega::MegaNode::CreditCardNodeData* data, const ::mega::handle parentNodeHandle) { NiceMock rl; handle newPwdNodeHandle{UNDEF}; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE, [&newPwdNodeHandle](const MegaRequest& req) { newPwdNodeHandle = req.getNodeHandle(); }); megaApi->createCreditCardNode(name.c_str(), data, parentNodeHandle, &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)) << "CreditCard node not properly generated. Name: " << name; return newPwdNodeHandle; } } sdk-10.11.0/tests/integration/integration_test_utils.h000066400000000000000000000263571516266226600231240ustar00rootroot00000000000000/** * @file * @brief File aimed to contain utilities for integration tests where objects from megaapi.h are * required. For examples, a function to wait for a sync state to change. * * These utilities extend the ones defined in the more general level for the tests * (sdk_test_utils.h) so the namespace is extended (sdk_test). */ #ifndef INCLUDE_INTEGRATION_INTEGRATION_TEST_UTILS_H_ #define INCLUDE_INTEGRATION_INTEGRATION_TEST_UTILS_H_ #include "gmock/gmock.h" #include "mega/types.h" #include "megaapi.h" #include "sdk_test_utils.h" #include #include #include namespace sdk_test { using namespace std::chrono_literals; static constexpr auto MAX_TIMEOUT = 3min; // Timeout for operations in this file #ifdef ENABLE_SYNC /** * @brief Waits for the sync state to be set to a given value and with a given error during a * certain amount of time (30 seconds). * * @param megaApi The api from where to get the sync object * @param remoteNode The root remote node the sync is tracking * @param runState The expected run state to match * @param err The expected error code to match * @return std::unique_ptr If the sync matches de expected state within that time, the * function returns the sync object. Otherwise nullptr. */ std::unique_ptr<::mega::MegaSync> waitForSyncState(::mega::MegaApi* megaApi, ::mega::MegaNode* remoteNode, ::mega::MegaSync::SyncRunningState runState, ::mega::MegaSync::Error err); /** * @brief Overloaded implementation where the sync is obtained by the backup id instead of by the * remote root node. */ std::unique_ptr<::mega::MegaSync> waitForSyncState(::mega::MegaApi* megaApi, ::mega::handle backupID, ::mega::MegaSync::SyncRunningState runState, ::mega::MegaSync::Error err); /** * @brief Waits for the sync stall state to be set certain amount of time (10 seconds). */ bool waitForSyncStallState(::mega::MegaApi* const megaApi); /** * @brief Synchronously start a TWO_WAY sync between the given local path and the remote node with * the given handle. * * It will also wait until the new sync is in RUNSTATE_RUNNING state. * * @param megaApi The api to request the sync creation * @param localRootPath The local root to sync * @param remoteRootHandle The handle of the remote node to sync * @return The backupId of the new sync. */ ::mega::handle syncFolder(::mega::MegaApi* megaApi, const std::string& localRootPath, const ::mega::MegaHandle remoteRootHandle); /** * @brief Synchronously start a BACKUP sync with the given local path * * It will also wait until the new sync is in RUNSTATE_RUNNING state. * * @param megaApi The api to request the sync creation * @param localRootPath The local path to backup * @param backupName The name of the backup. By default, it will use the name of the root directory * @return The backupId of the new sync. */ ::mega::handle backupFolder(::mega::MegaApi* megaApi, const std::string& localRootPath, const std::string& backupName = ""); /** * @brief Synchronously removes the sync with the given backupId * * @return true if the operation succeed, false otherwise */ bool removeSync(::mega::MegaApi* megaApi, const ::mega::handle backupID); /** * @brief Synchronously change the running state of the sync with the given backupId * * @return true if the operation succeed, false otherwise */ bool setSyncRunState(::mega::MegaApi* megaApi, const ::mega::handle backupID, const ::mega::MegaSync::SyncRunningState state); /** * @brief Synchronously resume the sync with the given backupId * * @return true if the operation succeed, false otherwise */ bool resumeSync(::mega::MegaApi* megaApi, const ::mega::handle backupID); /** * @brief Synchronously suspend the sync with the given backupId * * @return true if the operation succeed, false otherwise */ bool suspendSync(::mega::MegaApi* megaApi, const ::mega::handle backupID); /** * @brief Synchronously disable the sync with the given backupId * * @return true if the operation succeed, false otherwise */ bool disableSync(::mega::MegaApi* megaApi, const ::mega::handle backupID); /** * @brief Check MegaApi flags for sync stall state. */ bool isSyncStalled(::mega::MegaApi* const megaApi); /** * @brief Get a vector with all the reported stalls. */ std::vector> getStalls(::mega::MegaApi* megaApi); #endif /** * @brief Get a pair formed by a vector with the names of the nodes that are children of the node * with the given handle and a unique ptr to MegaNodeList. * * If any of the operations to get the nodes fails, a pair formed by nullopt and nullptr is * returned. */ std::pair>, std::unique_ptr<::mega::MegaNodeList>> getCloudFirstChildren(::mega::MegaApi* megaApi, const ::mega::MegaHandle nodeHandle); /** * @brief Get a vector with the names of the nodes that are children of the node with the given * handle. * * If any of the operations to get the nodes fails, a nullopt is returned. */ std::optional> getCloudFirstChildrenNames(::mega::MegaApi* megaApi, const ::mega::MegaHandle nodeHandle); /** * @brief Get a pair formed by a vector with the names and fingerprints of the nodes that are * children of the node with the given handle and a unique ptr to MegaNodeList. * * If any of the operations to get the nodes fails, a pair formed by nullopt and nullptr is * returned. */ std::pair>, std::unique_ptr<::mega::MegaNodeList>> getCloudFirstChildrenNamesAndFingerprints(::mega::MegaApi* megaApi, const ::mega::MegaHandle nodeHandle); /** * @brief Get the map resulting from invoking * MegaApi::getUserAttribute(MegaApi::USER_ATTR_DEVICE_NAMES) and put it in the output parameter. * * This function `ASSERT`s on the result from the internal request and also it asserts false if the * timeout is exceeded while waiting for it. */ void getDeviceNames(::mega::MegaApi* megaApi, std::unique_ptr<::mega::MegaStringMap>& output); /** * @brief Get the special folder for backups (`My Backups`) * @return a pair with the result of retrieving MegaApi::USER_ATTR_MY_BACKUPS_FOLDER attr and the * handle of node in case it exists */ std::pair getMyBackupsFolder(::mega::MegaApi* megaApi); /** * @brief Creates the special folder for backups (`My backups`) * It creates a new folder inside the Vault rootnode and later stores the node's * handle in a user's attribute, MegaApi::USER_ATTR_MY_BACKUPS_FOLDER. * * Apps should first check if this folder exists already, by calling * MegaApi::getUserAttribute for the corresponding attribute. * * @param name Localized name for "My backups" folder * @return a pair with the result of setting MegaApi::USER_ATTR_MY_BACKUPS_FOLDER attr and the * handle of Node in case it could be created * * @see MegaApi::setMyBackupsFolder for more details */ std::pair setMyBackupsFolder(::mega::MegaApi* megaApi, const std::string& name); /** * @brief Ensures there is at least one device visible to the given megaApi instance. This is * required to enable the creation of backup syncs for instance. * * If there are no devices, a new one is created with the name "Jenkins " + timestamp */ void ensureAccountDeviceNamesAttrExists(::mega::MegaApi* megaApi); /** * @brief Ensures that special folder for backups (`My backups`) exists. * * In case `My backups` folder doesn't exists, this function will create it, and return the name of * the folder, otherwise this method just returns the folder name * * @param name Localized name for "My backups" folder in case it doesn't exists and we need to * create it * @return a pair with the result of setting/retrieving MegaApi::USER_ATTR_MY_BACKUPS_FOLDER attr * and the name of the folder in case it exists * * @see `setMyBackupsFolder` and `getMyBackupsFolder` */ std::pair ensureMyBackupsFolderExists(::mega::MegaApi* megaApi, const std::string& name); /** * @brief Returns true if value matches the given matcher and also executes EXPECT_THAT */ template bool checkAndExpectThat(const T& value, const MatcherT& matcher) { bool matched = ::testing::Value(value, matcher); EXPECT_THAT(value, matcher); return matched; } using TransferFinishCallback = std::function; /** * @brief Downloads a file from MEGA * @see MegaApi::startDownload for more details * @return a Numeric errCode corresponding to MegaError received at * MegaTransferListener::onTransferFinish, or nullopt if onTransferFinish is never called. * Note: If onTransferFinish is called but MegaError is not valid API_EINTERNAL will be returned. */ std::optional downloadNode( ::mega::MegaApi* megaApi, ::mega::MegaNode* node, const std::filesystem::path& fsPath, bool pathIsFolder, const std::chrono::seconds timeoutInSecs = MAX_TIMEOUT, const int collisionCheck = ::mega::MegaTransfer::COLLISION_CHECK_FINGERPRINT, const int collisionResolution = ::mega::MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N, TransferFinishCallback transferFinishCallback = nullptr, const char* customName = nullptr, const char* appData = nullptr, const bool startFirst = false, ::mega::MegaCancelToken* cancelToken = nullptr, const bool undelete = false); /** * @brief Uploads the file in the given path to the given parentNode. */ std::unique_ptr<::mega::MegaNode> uploadFile(::mega::MegaApi* megaApi, const std::filesystem::path& localPath, ::mega::MegaNode* parentNode = nullptr, const char* fileName = nullptr); /** * @brief Overloaded version to upload a temp file. */ std::unique_ptr<::mega::MegaNode> uploadFile(::mega::MegaApi* megaApi, LocalTempFile&& file, ::mega::MegaNode* parentNode = nullptr, const char* fileName = nullptr); ::mega::handle createPasswordNode(::mega::MegaApi* megaApi, const std::string& name, const ::mega::MegaNode::PasswordNodeData* data, const ::mega::handle parentNodeHandle); ::mega::handle createCreditCardNode(::mega::MegaApi* megaApi, const std::string& name, const ::mega::MegaNode::CreditCardNodeData* data, const ::mega::handle parentNodeHandle); } #endif // INCLUDE_INTEGRATION_INTEGRATION_TEST_UTILS_H_ sdk-10.11.0/tests/integration/main.cpp000066400000000000000000000564351516266226600176010ustar00rootroot00000000000000#include "mega.h" #include "mega/filesystem.h" #include "gtest_common.h" #include #include #ifdef WIN32 #include #endif #include "sdk_test_utils.h" #include "test.h" #include "env_var_accounts.h" #include #include // If running in Jenkins, we use its working folder. But for local manual testing, use a convenient location std::string getLocalTestFolder() { const std::string folderName{"mega_tests"}; #ifdef WIN32 return std::string("c:\\tmp\\") + folderName; #else // Should always find HOME on Posix, but use "." as backup return Utils::getenv("HOME", ".") + "/" + folderName; #endif } fs::path LINK_EXTRACT_SCRIPT = "email_processor.py"; const string& getDefaultLogName() { static const string LOG_NAME{ "test_integration.log" }; return LOG_NAME; } bool gWriteLog = false; string gLogName = getDefaultLogName(); bool gResumeSessions = false; bool gScanOnly = false; // will be used in SRW bool gManualVerification=false; bool gFreeAccounts = false; // force accounts used in tests to remain free level after finishing std::string USER_AGENT = "Integration Tests with GoogleTest framework"; void WaitMillisec(unsigned n) { #ifdef _WIN32 if (n > 1000) { for (int i = 0; i < 10; ++i) { // better for debugging, with breakpoints, pauses, etc Sleep(n/10); } } else { Sleep(n); } #else usleep(n * 1000); #endif } string runProgram(const string& command, PROG_OUTPUT_TYPE ot) { FILE* pPipe = #ifdef _WIN32 _popen(command.c_str(), "rt"); #else popen(command.c_str(), "r"); #endif if (!pPipe) { LOG_err << "Failed to run command\n" << command; return string(); } // Read pipe until file ends or error occurs. string output; char psBuffer[128]; while (!feof(pPipe) && !ferror(pPipe)) { switch (ot) { case PROG_OUTPUT_TYPE::TEXT: { if (fgets(psBuffer, 128, pPipe)) { output += psBuffer; } break; } case PROG_OUTPUT_TYPE::BINARY: { size_t lastRead = fread(psBuffer, 1, sizeof(psBuffer), pPipe); if (lastRead) { output.append(psBuffer, lastRead); } } } // end switch() } if (ferror(pPipe)) { LOG_err << "Failed to read full command output."; } #ifdef _WIN32 _pclose(pPipe); #else pclose(pPipe); // docs don't _guarantee_ handling null stream #endif return output; } string loadfile(const string& filename) { string filedata; ifstream f(filename, ios::binary); f.seekg(0, std::ios::end); filedata.resize(unsigned(f.tellg())); f.seekg(0, std::ios::beg); f.read(const_cast(filedata.data()), static_cast(filedata.size())); return filedata; } #ifdef WIN32 void synchronousHttpPOSTData(const string& url, const string& senddata, string& responsedata) { LOG_info << "Sending file to " << url << ", size: " << senddata.size(); BOOL bResults = TRUE; HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; // Use WinHttpOpen to obtain a session handle. hSession = WinHttpOpen(L"testmega/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); WCHAR szURL[8192]; WCHAR szHost[256]; URL_COMPONENTS urlComp = { sizeof urlComp }; urlComp.lpszHostName = szHost; urlComp.dwHostNameLength = sizeof szHost / sizeof *szHost; urlComp.dwUrlPathLength = (DWORD)-1; urlComp.dwSchemeLength = (DWORD)-1; if (MultiByteToWideChar(CP_UTF8, 0, url.c_str(), -1, szURL, sizeof szURL / sizeof *szURL) && WinHttpCrackUrl(szURL, 0, 0, &urlComp)) { if ((hConnect = WinHttpConnect(hSession, szHost, urlComp.nPort, 0))) { hRequest = WinHttpOpenRequest(hConnect, L"POST", urlComp.lpszUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, (urlComp.nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0); } } // Send a Request. if (hRequest) { WinHttpSetTimeouts(hRequest, 58000, 58000, 0, 0); LPCWSTR pwszHeaders = L"Content-Type: application/octet-stream"; // HTTPS connection: ignore certificate errors, send no data yet DWORD flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof flags); if (WinHttpSendRequest(hRequest, pwszHeaders, DWORD(wcslen(pwszHeaders)), (LPVOID)senddata.data(), (DWORD)senddata.size(), (DWORD)senddata.size(), NULL)) { } } DWORD dwSize = 0; // End the request. if (bResults) bResults = WinHttpReceiveResponse(hRequest, NULL); // Continue to verify data until there is nothing left. if (bResults) do { // Verify available data. dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError()); size_t offset = responsedata.size(); responsedata.resize(offset + dwSize); ZeroMemory(responsedata.data() + offset, dwSize); DWORD dwDownloaded = 0; if (!WinHttpReadData(hRequest, responsedata.data() + offset, dwSize, &dwDownloaded)) printf("Error %u in WinHttpReadData.\n", GetLastError()); } while (dwSize > 0); // Report errors. if (!bResults) printf("Error %d has occurred.\n", GetLastError()); // Close open handles. if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession); } #endif void synchronousHttpPOSTFile(const string& url, const string& filepath, string& responsedata) { #ifdef WIN32 synchronousHttpPOSTData(url, loadfile(filepath), responsedata); #else string command = "curl -s --data-binary @"; command.append(filepath).append(" ").append(url); responsedata = runProgram(command, PROG_OUTPUT_TYPE::BINARY); #endif } LogStream::~LogStream() { auto data = mBuffer.str(); // Always write messages via standard logger. LOG_debug << data; } std::string logTime() { return getCurrentTimestamp(); } LogStream out() { return LogStream(); } class TestMegaLogger : public Logger { mutex logMutex; public: static bool writeCout; void log(const char* /*time*/, int loglevel, const char* source, const char* message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, unsigned numberMessages = 0 #endif ) override { std::ostringstream os; os << "["; os << getCurrentTimestamp(); #ifdef ENABLE_LOG_PERFORMANCE os << "] " << SimpleLogger::toStr(static_cast(loglevel)) << ": "; if (message) { os << message; } // we can have the message AND the direct messages for (unsigned i = 0; i < numberMessages; ++i) os.write(directMessages[i], static_cast(directMessagesSizes[i])); #else os << "] " << SimpleLogger::toStr(static_cast(loglevel)) << ": " << message; #endif if (source) { os << " (" << source << ")"; } os << std::endl; lock_guard g(logMutex); if (loglevel <= SimpleLogger::getLogLevel()) { if (gWriteLog) { if (!mLogFile.is_open()) { mLogFile.open(gLogName, ios::app); } mLogFile << os.str() << std::flush; } else { bool output = writeCout; #ifdef _WIN32 if (IsDebuggerPresent()) output = false; #endif // _WIN32 if (output) std::cout << os.str() << std::flush; } #ifdef _WIN32 // Always show the logging in the output window in VS, very useful to see what's going on as the tests run // (with the high level --log output visible in the app's own console window) OutputDebugStringA(os.str().c_str()); #endif // _WIN32 } } void close() { mLogFile.close(); } private: std::ofstream mLogFile; }; bool TestMegaLogger::writeCout = true; class GTestLogger : public ::testing::EmptyTestEventListener { static void toLog(const std::string& message) { out() << "GTEST: " << message; } public: void OnTestEnd(const ::testing::TestInfo& info) override { std::string result = "FAILED"; if (info.result()->Passed()) { result = "PASSED"; } out() << "GTEST: " << result << " " << info.test_case_name() << "." << info.name(); RequestRetryRecorder::instance().report(toLog); } void OnTestPartResult(const ::testing::TestPartResult& result) override { using namespace ::testing; if (result.type() == TestPartResult::kSuccess) return; std::string file = "unknown"; std::string line; if (result.file_name()) { file = result.file_name(); } if (result.line_number() >= 0) { line = std::to_string(result.line_number()) + ":"; } out() << "GTEST: " << file << ":" << line << " Failure"; std::istringstream istream(result.message()); for (std::string s; std::getline(istream, s); ) { out() << "GTEST: " << s; } RequestRetryRecorder::instance().report(toLog); } void OnTestStart(const ::testing::TestInfo& info) override { out() << "GTEST: RUNNING " << info.test_case_name() << "." << info.name(); } }; // GTestLogger class RequestRetryReporter : public ::testing::EmptyTestEventListener { static void toStandardOutput(const std::string& message) { std::cout << message << std::endl; } public: void OnTestEnd(const ::testing::TestInfo&) override { RequestRetryRecorder::instance().report(toStandardOutput); } void OnTestPartResult(const ::testing::TestPartResult& result) override { using ::testing::TestPartResult; // Only write report if the test failed. if (result.type() == TestPartResult::kSuccess) RequestRetryRecorder::instance().report(toStandardOutput); } }; // RequestRetryReporter // Let us log even during post-test shutdown TestMegaLogger megaLogger; #ifdef ENABLE_SYNC // destroy g_clientManager while the logging is still active ClientManager* g_clientManager = nullptr; #endif // ENABLE_SYNC RequestRetryRecorder* RequestRetryRecorder::mInstance = nullptr; class SdkRuntimeArgValues : public RuntimeArgValues { public: SdkRuntimeArgValues(std::vector&& args, std::vector>&& envVars) : RuntimeArgValues(std::move(args), std::move(envVars)) { if (isHelp() || isListOnly() || !isValid()) { return; } for (auto it = mArgs.begin(); it != mArgs.end();) { string arg = Utils::toUpperUtf8(*it); if (arg == "--LOG") { gWriteLog = true; } else if (arg == "--CI") { // options for continuous integration gWriteLog = true; } else if (arg == "--RESUMESESSIONS") { gResumeSessions = true; } else if (arg == "--SCANONLY") { gScanOnly = true; } else if (arg == "--FREEACCOUNTS") { gFreeAccounts = true; } ++it; } } protected: void printCustomOptions() const override { cout << buildAlignedHelpString("--LOG", {"Write output to log file"}) << '\n'; cout << buildAlignedHelpString("--CI", {"Include all 'Continuous Integration' options (same as --LOG)"}) << '\n'; cout << buildAlignedHelpString("--RESUMESESSIONS", {"Resume previous account sessions, instead of full logins"}) << '\n'; cout << buildAlignedHelpString("--SCANONLY", {"Scan synced folders periodically instead of use file system notifications"}) << '\n'; cout << buildAlignedHelpString( "--FREEACCOUNTS", {"Test accounts used will remain at free account level at the end of the tests"}) << '\n'; } void printCustomEnvVars() const override { cout << buildAlignedHelpString(" $MEGA_REAL_EMAIL", {"mega.co.nz email account to recevied account creation emails"}) << '\n'; cout << buildAlignedHelpString(" $MEGA_REAL_PWD", {"Password for Mega email account"}) << '\n'; cout << buildAlignedHelpString(" $WORKSPACE", {"Where to base tests, defaults to " + getLocalTestFolder() + " when not set"}) << '\n'; } }; // Make sure any megafs mounts are nuked before and after a run. class ScopedAbortMounts { // Nuke all megafs mounts on this machine. void abort() { // Convenience. using namespace ::mega::fuse; // Where would our mounts be mounted? auto workspace = path_u8string(TestFS::GetBaseFolder()); // Nuke everything. auto result = Service::abort([&](const std::string& path) { // Make sure path is present under the workspace. return path.size() > workspace.size() && !path.compare(0, workspace.size(), workspace); }); // The mounts have been nuked. if (result == MOUNT_SUCCESS) return; // Couldn't nuke the mounts. LOG_warn << "Couldn't abort FUSE mounts: " << toString(result); } // Whether we should abort any mounts. bool mAbort; public: // Nuke the mounts when our tests start. ScopedAbortMounts(bool abort) : mAbort(abort) { } // And just before they end. ~ScopedAbortMounts() { if (mAbort) abort(); } }; // ScopedAbortMounts int main (int argc, char *argv[]) { SdkRuntimeArgValues argVals(vector(argv, argv + argc), getEnvVarAccounts().cloneVarNames()); if (argVals.isHelp()) { argVals.printHelp(); return 0; } if (!argVals.isValid()) { std::cout << "No tests executed (invalid arguments)." << std::endl; return -1; } if (argVals.isListOnly()) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); // returns 0 (success) or 1 (failed tests) } gLogName = argVals.getLog(); // set accordingly for worker or main process remove(gLogName.c_str()); // Abort any stale mounts. ScopedAbortMounts abort(!argVals.isWorker()); if (argVals.isMainProcWithWorkers()) { // Don't run tests, only manage subprocesses. // To get here run with --INSTANCES:2 [--EMAIL-POOL:foo+bar-{1-28}@mega.nz] // If --EMAIL-POOL runtime arg is missing, email template will be taken from MEGA_EMAIL. // Password for all emails built from template will be taken from MEGA_PWD env var. // If it did not get an email template, it'll use a single subprocess with the existing env // vars. GTestParallelRunner pr(std::move(argVals)); string testBase = path_u8string((TestFS::GetBaseFolder() / "pid_")); // see TestFS::GetProcessFolder() pr.useWorkerOutputPathForPid(std::move(testBase)); return pr.run(); } if (!argVals.getCustomApiUrl().empty()) { g_APIURL_default = argVals.getCustomApiUrl(); } if (!argVals.getCustomUserAget().empty()) { USER_AGENT = argVals.getCustomUserAget(); } if (argVals.isMainProcOnly()) { // Env vars might need to be set, for example when an email template was used auto envVars = argVals.getEnvVarsForWorker(0); for (const auto& env : envVars) { Utils::setenv(env.first, env.second); } } // So we can track how often requests are retried. RequestRetryRecorder retryRecorder; try { #ifdef ENABLE_SYNC // destroy g_clientManager while the logging is still active, and before global destructors (for things like mutexes) run ClientManager clientManager; g_clientManager = &clientManager; #endif // ENABLE_SYNC sdk_test::setTestDataDir(fs::absolute(fs::path(argv[0]).parent_path())); SimpleLogger::setLogLevel(logVerbose); SimpleLogger::setOutputClass(&megaLogger); MegaApi::setLogJSON(MegaApi::getLogJSON() | MegaApi::JSON_LOG_CHUNK_RECEIVED); // delete old test folders, created during previous runs TestFS testFS; testFS.ClearProcessFolder(); testFS.ChangeToProcessFolder(); #if defined(__APPLE__) // our waiter uses select which only supports file number <=1024. // by limiting max open files to 1024, we might able to know this error precisely platformSetRLimitNumFile(1024); #endif // __APPLE__ // Add listeners. { auto& listeners = testing::UnitTest::GetInstance()->listeners(); // Emit request retries to screen. listeners.Append(new RequestRetryReporter()); // Emit test events to a log file. if (gWriteLog) listeners.Append(new GTestLogger()); } ::testing::InitGoogleTest(&argc, argv); int gtestRet = RUN_ALL_TESTS(); #if defined(USE_OPENSSL) && !defined(OPENSSL_IS_BORINGSSL) if (CurlHttpIO::sslMutexes) { int numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; ++i) { delete CurlHttpIO::sslMutexes[i]; } delete [] CurlHttpIO::sslMutexes; } #endif #ifdef ENABLE_SYNC g_clientManager->clear(); #endif megaLogger.close(); return gtestRet; } catch (exception& e) { cerr << argv[0] << ": exception: " << e.what() << endl; return -1; } } /* ** TestFS implementation */ using namespace std; fs::path TestFS::GetBaseFolder() { return fs::path{Utils::getenv("WORKSPACE", getLocalTestFolder())}; } fs::path TestFS::GetProcessFolder() { fs::path testBase = GetBaseFolder() / ("pid_" + std::to_string(getCurrentPid())); return testBase; } fs::path TestFS::GetTestFolder() { fs::path testpath = GetProcessFolder() / "test"; return testpath; } fs::path TestFS::GetTrashFolder() { return GetProcessFolder() / "trash"; } void TestFS::DeleteFolder(fs::path folder) { // rename folder, so that tests can still create one and add to it error_code ec; fs::path oldpath(folder); fs::path newpath(folder); for (int i = 10; i--; ) { newpath += "_del"; // this can be improved later if needed fs::rename(oldpath, newpath, ec); if (!ec) break; } // if renaming failed, then there's nothing to delete if (ec) { // report failures, other than the case when it didn't exist if (ec != errc::no_such_file_or_directory) { out() << "Renaming " << oldpath << " to " << newpath << " failed." << ec.message(); } return; } // delete folder in a separate thread m_cleaners.emplace_back(thread([=]() mutable // ...mostly for fun, to avoid declaring another ec { fs::remove_all(newpath, ec); if (ec) { out() << "Deleting " << folder << " failed." << ec.message(); } })); } TestFS::~TestFS() { for_each(m_cleaners.begin(), m_cleaners.end(), [](thread& t) { t.join(); }); } void TestFS::ClearProcessFolder() { fs::path base = GetProcessFolder(); if (!fs::exists(base)) return; FSACCESS_CLASS fsaccess; unique_ptr dir(fsaccess.newdiraccess()); LocalPath lbase = LocalPath::fromAbsolutePath(base.string()); lbase.appendWithSeparator(LocalPath::fromRelativePath("*"), false); if (!dir->dopen(&lbase, nullptr, true)) throw runtime_error("Can not read directory '" + lbase.toPath(false) + "'"); LocalPath nameArg; nodetype_t ntype = TYPE_UNKNOWN; while (dir->dnext(lbase, nameArg, true, &ntype)) { if (ntype == FILENODE) { fs::remove(nameArg.toPath(false)); } else { fs::remove_all(nameArg.toPath(false)); } } } void TestFS::ChangeToProcessFolder() { fs::path base = GetProcessFolder(); fs::create_directories(base); fs::current_path(base); } void moveToTrash(const fs::path& p) { fs::path trashpath(TestFS::GetTrashFolder()); fs::create_directory(trashpath); fs::path newpath = trashpath / p.filename(); int errcount = 0; for (int i = 2; errcount < 20; ++i) { if (!fs::exists(p)) break; const std::string newname = path_u8string(p.filename().stem()) + "_" + std::to_string(i) + path_u8string(p.extension()); newpath = trashpath / fs::path(newname); if (!fs::exists(newpath)) { std::error_code e; fs::rename(p, newpath, e); if (e) { LOG_err << "Failed to trash-rename " << path_u8string(p) << " to " << path_u8string(newpath) << ": " << e.message(); WaitMillisec(500); errcount += 1; } else break; } } } fs::path makeNewTestRoot() { fs::path p = TestFS::GetTestFolder(); if (fs::exists(p)) { moveToTrash(p); } std::error_code e; bool b = fs::create_directories(p, e); if (!b) { out() << "Failed to create base directory for test at: " << path_u8string(p) << ", error: " << e.message(); } assert(b); return p; } fs::path makeReusableClientFolder(const string& subfolder) { fs::path p = TestFS::GetProcessFolder() / ("clients_" + std::to_string(getCurrentPid())) / subfolder; #ifndef NDEBUG bool b = #endif fs::create_directories(p); assert(b); return p; } bool SdkTestBase::clearProcessFolderEachTest = false; void SdkTestBase::SetUp() { Test::SetUp(); TestFS::ChangeToProcessFolder(); if (clearProcessFolderEachTest) { // for testing that tests are independent, slow as NOD database deleted TestFS::ClearProcessFolder(); } // Reset request retry statistics. RequestRetryRecorder::instance().reset(); } fs::path getLinkExtractSrciptPath() { return sdk_test::getTestDataDir() / LINK_EXTRACT_SCRIPT; } bool isFileHidden(const LocalPath& path) { return FileSystemAccess::isFileHidden(path) != 0; } bool isFileHidden(const fs::path& path) { return isFileHidden(LocalPath::fromAbsolutePath(path_u8string(path))); } sdk-10.11.0/tests/integration/mock_listeners.h000066400000000000000000000242611516266226600213330ustar00rootroot00000000000000/** * @file * @brief This file contains definition of convenient mock listeners */ #ifndef INCLUDE_INTEGRATION_MOCK_LISTENERS_H_ #define INCLUDE_INTEGRATION_MOCK_LISTENERS_H_ #include "integration/integration_test_utils.h" #include "megaapi.h" #include #include #include /** * @class SynchronizationHelper * @brief A helper class to implement mock classes involved in async operations. * * In summary, the class has a promise and a method (markAsFinished) that allows to set the value of * the promise. Then, it provides method for waiting for the promise to finish (waitForFinish and * waitForFinishOrTimeout). * */ class SynchronizationHelper { public: SynchronizationHelper(): finishedFuture(finishedPromise.get_future()) {} void waitForFinish() { finishedFuture.wait(); } /** * @brief Waits for the promise to finish for the given amount of time. * * @param duration The time to wait. You can use std::chrono literals such as 3min * @return true if the promise finishes successfully (see `markAsFinished`) within the given * duration, false otherwise. */ template bool waitForFinishOrTimeout(const std::chrono::duration& duration) { auto status = finishedFuture.wait_for(duration); return status == std::future_status::ready && finishedFuture.get(); } void markAsFinished(const bool succeeded = true) { finishedPromise.set_value(succeeded); } private: std::promise finishedPromise; std::future finishedFuture; }; /** * @class MockRequestListener * @brief A mock class for MegaRequestListener * * By default, the underlying promise is marked as finished once the onRequestFinish method gets * called so remember to wait for finish before going out of scope. */ class MockRequestListener: public ::mega::MegaRequestListener, public SynchronizationHelper { public: MockRequestListener(::mega::MegaApi* megaApi = nullptr): mMegaApi{megaApi} { ON_CALL(*this, onRequestFinish) .WillByDefault(::testing::Invoke(this, &MockRequestListener::defaultOnRequestFinish)); } ~MockRequestListener() { if (mMegaApi) mMegaApi->removeRequestListener(this); } MOCK_METHOD(void, onRequestStart, (::mega::MegaApi * api, ::mega::MegaRequest* request), (override)); MOCK_METHOD(void, onRequestUpdate, (::mega::MegaApi * api, ::mega::MegaRequest* request), (override)); MOCK_METHOD(void, onRequestTemporaryError, (::mega::MegaApi * api, ::mega::MegaRequest* request, ::mega::MegaError* error), (override)); MOCK_METHOD(void, onRequestFinish, (::mega::MegaApi*, ::mega::MegaRequest*, ::mega::MegaError*), (override)); /** * @brief Set expectations on the error codes and request type when onRequestFinish gets * invoked * * @param reqErrorMatcher A matcher for the value returned by MegaRequest::getErrorCode * @param syncErrorMatcher A matcher for the value returned by MegaRequest::getSyncError. It * will match any error by default. * @param reqTypeMatcher A matcher for the value returned by MegaRequest::getType. It * will match any type by default. */ void setErrorExpectations( const testing::Matcher reqErrorMatcher, const testing::Matcher syncErrorMatcher = testing::_, const testing::Matcher reqTypeMatcher = testing::_, std::function&& captureValues = nullptr) { // We override the default using namespace testing; EXPECT_CALL(*this, onRequestFinish) .Times(1) .WillOnce( [reqErrorMatcher, syncErrorMatcher, reqTypeMatcher, capture = std::move(captureValues), this](::mega::MegaApi*, ::mega::MegaRequest* req, ::mega::MegaError* err) { EXPECT_TRUE(req) << "Null MegaRequest"; EXPECT_TRUE(err) << "Null MegaError"; if (!req || !err) return markAsFinished(false); bool ok = sdk_test::checkAndExpectThat(req->getType(), reqTypeMatcher); ok = ok && sdk_test::checkAndExpectThat(err->getErrorCode(), reqErrorMatcher); ok = ok && sdk_test::checkAndExpectThat(err->getSyncError(), syncErrorMatcher); if (ok && capture) capture(*req); markAsFinished(ok); }); } private: ::mega::MegaApi* mMegaApi{nullptr}; void defaultOnRequestFinish(::mega::MegaApi*, ::mega::MegaRequest*, ::mega::MegaError* err) { markAsFinished(err->getErrorCode() == ::mega::API_OK); } }; /** * @class MockNodesUpdateListener * @brief A simple mock listener to track nodes updates */ class MockNodesUpdateListener: public ::mega::MegaListener { public: MockNodesUpdateListener(::mega::MegaApi* megaApi = nullptr): mMegaApi{megaApi} {} MOCK_METHOD(void, onNodesUpdate, (::mega::MegaApi * api, ::mega::MegaNodeList* nodes), (override)); ~MockNodesUpdateListener() { if (mMegaApi) mMegaApi->removeListener(this); } private: ::mega::MegaApi* mMegaApi{nullptr}; }; /** * @class MockSyncListener * @brief Mock listener only implementing methods for transfers * */ class MockTransferListener: public ::mega::MegaListener { public: MockTransferListener(::mega::MegaApi* megaApi = nullptr): mMegaApi{megaApi} {} ~MockTransferListener() { if (mMegaApi) mMegaApi->removeListener(this); } MOCK_METHOD(void, onTransferFinish, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, ::mega::MegaError* error), (override)); MOCK_METHOD(void, onTransferStart, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer), (override)); MOCK_METHOD(void, onTransferUpdate, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer), (override)); MOCK_METHOD(void, onTransferTemporaryError, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, ::mega::MegaError* error), (override)); private: ::mega::MegaApi* mMegaApi{nullptr}; }; class MockMegaTransferListener: public ::mega::MegaTransferListener, public SynchronizationHelper { public: MockMegaTransferListener(::mega::MegaApi* megaApi = nullptr): mMegaApi{megaApi} { ON_CALL(*this, onTransferFinish) .WillByDefault( ::testing::Invoke(this, &MockMegaTransferListener::defaultOnTransferFinish)); } ~MockMegaTransferListener() { if (mMegaApi) mMegaApi->removeTransferListener(this); } MOCK_METHOD(void, onTransferStart, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer), (override)); MOCK_METHOD(void, onTransferFinish, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, ::mega::MegaError* error), (override)); MOCK_METHOD(void, onTransferUpdate, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer), (override)); MOCK_METHOD(void, onFolderTransferUpdate, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, int stage, uint32_t foldercount, uint32_t createdfoldercount, uint32_t filecount, const char* currentFolder, const char* currentFileLeafname), (override)); MOCK_METHOD(void, onTransferTemporaryError, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, ::mega::MegaError* error), (override)); MOCK_METHOD(bool, onTransferData, (::mega::MegaApi * api, ::mega::MegaTransfer* transfer, char* buffer, size_t size), (override)); private: ::mega::MegaApi* mMegaApi; void defaultOnTransferFinish(::mega::MegaApi*, ::mega::MegaTransfer*, ::mega::MegaError* err) { markAsFinished(err->getErrorCode() == ::mega::API_OK); } }; #ifdef ENABLE_SYNC /** * @class MockSyncListener * @brief Mock listener only implementing methods for syncs * */ class MockSyncListener: public ::mega::MegaListener { public: MockSyncListener(::mega::MegaApi* megaApi = nullptr): mMegaApi{megaApi} {} ~MockSyncListener() { if (mMegaApi) mMegaApi->removeListener(this); } MOCK_METHOD( void, onSyncFileStateChanged, (::mega::MegaApi * api, ::mega::MegaSync* sync, std::string* localPath, int newState), (override)); MOCK_METHOD(void, onSyncAdded, (::mega::MegaApi * api, ::mega::MegaSync* sync), (override)); MOCK_METHOD(void, onSyncDeleted, (::mega::MegaApi * api, ::mega::MegaSync* sync), (override)); MOCK_METHOD(void, onSyncStateChanged, (::mega::MegaApi * api, ::mega::MegaSync* sync), (override)); MOCK_METHOD(void, onSyncStatsUpdated, (::mega::MegaApi * api, ::mega::MegaSyncStats* syncStats), (override)); MOCK_METHOD(void, onGlobalSyncStateChanged, (::mega::MegaApi * api), (override)); MOCK_METHOD(void, onSyncRemoteRootChanged, (::mega::MegaApi * api, ::mega::MegaSync* sync), (override)); MOCK_METHOD(void, onRequestFinish, (::mega::MegaApi*, ::mega::MegaRequest*, ::mega::MegaError*), (override)); private: ::mega::MegaApi* mMegaApi{nullptr}; }; #endif // ENABLE_SYNC #endif // INCLUDE_INTEGRATION_MOCK_LISTENERS_H_ sdk-10.11.0/tests/integration/one_question_survey_test.cpp000066400000000000000000000411351516266226600240300ustar00rootroot00000000000000#include "SdkTest_test.h" #include class OneQuestionSurveyTest: public SdkTest { protected: struct Survey { unsigned int triggerActionId; // Survey handle handle h{UNDEF}; // Maximum allowed value in the survey response. unsigned int maxResponse{0}; // Name of an image to be display std::string image; // Content of the question std::string content; }; void SetUp() override; std::set toIntegerSet(const MegaIntegerList* list) const; handle toHandle(const char* handleInB64) const; std::unique_ptr toMegaHandleList(const std::vector& handles) const; void getOneActiveSurvey(unsigned int triggerActionId, Survey& survey) const; std::unique_ptr enableTestSurveys(const std::vector& handles) const; std::unique_ptr getSurvey(unsigned int triggerActionId) const; std::unique_ptr getActiveSurveyTriggerActions() const; std::unique_ptr answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const std::string& response, const std::string& comment) const; static std::string getOqsDataComments(const int idx) { switch (idx) { case 1: return "Very bad"; case 2: return "Bad"; case 3: return "Normal"; case 4: return "Good"; case 5: return "Very good"; default: return "Invalid value"; } } /** * @brief Generates a survey response and comment based on the trigger action ID and rating. * * This function generates a pair of strings: a response and a comment. The response is the * string representation of the provided numeric rating value, and the comment is obtained from * the `getOqsDataComments` function based on the rating. * * @param triggerActionId The ID representing the action that triggers the survey generation. * @param rating The rating provided by the user, which is converted to a string for the * response. * * @return A `std::pair` containing: * - The response string representing the rating. * - The comment string generated based on the rating. * * @note If the `triggerActionId` is not equal to `MegaApi::ACT_END_UPLOAD`, the function will * return an empty response and comment pair. */ std::pair generateUploadSurveyInfo(const unsigned int triggerActionId, const int rating = -1); Survey mTextSurvey; Survey mIntegerSurvey; }; // // To streamline the test case, two pre-configured test surveys should be // utilized. These surveys are set up to be returned by the API with priority // when they are enabled for testing. The details are as follows: // // Text Response Test Survey (a survey with 0 maxResponse): // Trigger Action ID: 1 // Survey Handle: zqdkqTtOtGc // Integer Response Test Survey (a survey with positive maxResponse): // Trigger Action ID: 2 // Survey Handle: j-r9sea9qW4 // // Only the trigger action ID and handle need to be tested; other fields can be ignored. void OneQuestionSurveyTest::SetUp() { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); // A test survey configured for end upload trigger event mTextSurvey.triggerActionId = MegaApi::ACT_END_UPLOAD; mTextSurvey.h = toHandle("zqdkqTtOtGc"); // A test survey configured for end meeting trigger event mIntegerSurvey.triggerActionId = MegaApi::ACT_END_MEETING; mIntegerSurvey.h = toHandle("j-r9sea9qW4"); } std::set OneQuestionSurveyTest::toIntegerSet(const MegaIntegerList* list) const { set result; if (!list || list->size() == 0) return result; for (int i = 0; i < list->size(); ++i) { result.emplace(static_cast(list->get(i))); } return result; } std::unique_ptr OneQuestionSurveyTest::toMegaHandleList(const std::vector& handles) const { std::unique_ptr list{MegaHandleList::createInstance()}; for (const auto& handle: handles) { list->addMegaHandle(handle); } return list; } handle OneQuestionSurveyTest::toHandle(const char* handleInB64) const { handle surveyHandle{UNDEF}; Base64::atob(handleInB64, reinterpret_cast<::mega::byte*>(&surveyHandle), MegaClient::SURVEYHANDLE); return surveyHandle; } void OneQuestionSurveyTest::getOneActiveSurvey(unsigned int triggerActionId, OneQuestionSurveyTest::Survey& survey) const { auto tracker = getSurvey(triggerActionId); ASSERT_EQ(tracker->waitForResult(), API_OK); const auto& request = tracker->request; survey.triggerActionId = triggerActionId; survey.h = request->getNodeHandle(); survey.maxResponse = static_cast(request->getNumDetails()); survey.image = request->getFile() ? std::string{request->getFile()} : ""; survey.content = request->getText() ? std::string{request->getText()} : ""; } std::unique_ptr OneQuestionSurveyTest::enableTestSurveys(const std::vector& handles) const { return RequestTracker::async(*megaApi[0], &MegaApi::enableTestSurveys, toMegaHandleList(handles).get()); } std::unique_ptr OneQuestionSurveyTest::getSurvey(unsigned int triggerActionId) const { return RequestTracker::async(*megaApi[0], &MegaApi::getSurvey, triggerActionId); } std::unique_ptr OneQuestionSurveyTest::getActiveSurveyTriggerActions() const { return RequestTracker::async(*megaApi[0], &MegaApi::getActiveSurveyTriggerActions); } std::unique_ptr OneQuestionSurveyTest::answerSurvey(MegaHandle surveyHandle, unsigned int triggerActionId, const std::string& response, const std::string& comment) const { return RequestTracker::async(*megaApi[0], &MegaApi::answerSurvey, surveyHandle, triggerActionId, response.c_str(), comment.c_str()); } std::pair OneQuestionSurveyTest::generateUploadSurveyInfo(const unsigned int triggerActionId, const int rating) { if (triggerActionId == MegaApi::ACT_END_UPLOAD) { const std::string response = numberToString(rating); const std::string comment = getOqsDataComments(rating); return {response, comment}; } return {}; } TEST_F(OneQuestionSurveyTest, RetrieveSurveyWithNonExistentActionIdShouldFail) { LOG_info << "___TEST OneQuestionSurveyTest::RetrieveSurveyWithNonExistentActionIdShouldFail"; // Attempting to retrieve a survey with a non-existent trigger action ID should fail. ASSERT_EQ(getSurvey(99999u)->waitForResult(), API_ENOENT); } /** * @brief TEST_F OneQuestionSurveyTest.AnswerUploadResponseSurveyShouldSucceed * * Tests OneQuestionSurvey for type ACT_END_UPLOAD * * # Test1: U1 - Retrieving the text response survey's trigger action ID should be successful. * # Test2: U1 - Retrieving the text response survey (with 0 maxResponse) should be successful. * # Test3: U1 - Answers survey with wrong parameters (Wrong response param) * # Test4: U1 - Answers survey with wrong parameters (Wrong response param) * # Test5: U1 - Answers survey with wrong parameters (Wrong rating value) * # Test6: U1 - Answers survey successfully * # Test7: U1 - Answers survey successfully (with empty comment param) */ TEST_F(OneQuestionSurveyTest, AnswerUploadResponseSurveyShouldSucceed) { LOG_info << "___TEST OneQuestionSurveyTest::AnswerResponseSurveyShouldSucceed"; // Enable testing for pre-configured text response survey should be successfully ASSERT_EQ(enableTestSurveys({mTextSurvey.h})->waitForResult(), API_OK); LOG_debug << "#### Test1(AnswerUploadResponseSurveyShouldSucceed): U1 - Retrieving the text " "response survey's trigger action ID should be successful. ####"; auto triggersTracker = getActiveSurveyTriggerActions(); ASSERT_EQ(triggersTracker->waitForResult(), API_OK); const auto triggers = toIntegerSet(triggersTracker->request->getMegaIntegerList()); ASSERT_TRUE(triggers.count(mTextSurvey.triggerActionId)); LOG_debug << "#### Test2(AnswerUploadResponseSurveyShouldSucceed): Retrieving the text response " "survey (with 0 maxResponse) should be successful. ####"; Survey textSurvey; ASSERT_NO_FATAL_FAILURE(getOneActiveSurvey(mTextSurvey.triggerActionId, textSurvey)); ASSERT_EQ(textSurvey.h, mTextSurvey.h); ASSERT_EQ(textSurvey.maxResponse, 0); LOG_debug << "#### Test3(AnswerUploadResponseSurveyShouldSucceed): U1 - Answers survey with " "wrong parameters (Wrong response param). ####"; ASSERT_EQ( answerSurvey(textSurvey.h, textSurvey.triggerActionId, "Awesome", "")->waitForResult(), API_EARGS); LOG_debug << "#### Test4(AnswerUploadResponseSurveyShouldSucceed): U1 - Answers survey with " "wrong parameters (Wrong response param). ####"; ASSERT_EQ(answerSurvey(textSurvey.h, textSurvey.triggerActionId, "6 Star!", "Awesome") ->waitForResult(), API_EARGS); LOG_debug << "#### Test5(AnswerUploadResponseSurveyShouldSucceed): U1 - Answers survey with " "wrong parameters (Wrong rating value). ####"; auto [r1, c1] = generateUploadSurveyInfo(textSurvey.triggerActionId, 7); ASSERT_EQ(answerSurvey(textSurvey.h, textSurvey.triggerActionId, r1.c_str(), c1.c_str()) ->waitForResult(), API_EARGS); LOG_debug << "#### Test6(AnswerUploadResponseSurveyShouldSucceed): Test6: U1 - Answers survey " "successfully. ####"; auto [r2, c2] = generateUploadSurveyInfo(textSurvey.triggerActionId, 5); ASSERT_EQ(answerSurvey(textSurvey.h, textSurvey.triggerActionId, r2.c_str(), c2.c_str()) ->waitForResult(), API_OK); LOG_debug << "#### Test7(AnswerUploadResponseSurveyShouldSucceed): U1 - Test7: U1 - Answers " "survey successfully (with empty comment param). ####"; auto [r3, _] = generateUploadSurveyInfo(textSurvey.triggerActionId, 3); ASSERT_EQ( answerSurvey(textSurvey.h, textSurvey.triggerActionId, r3.c_str(), "")->waitForResult(), API_OK); // Clearing testing surveys should be successful ASSERT_EQ(enableTestSurveys({})->waitForResult(), API_OK); } TEST_F(OneQuestionSurveyTest, AnswerEndCallResponseSurveyShouldSucceed) { LOG_info << "___TEST OneQuestionSurveyTest::AnswerEndCallResponseSurveyShouldSucceed"; // Enable testing for pre-configured integer response survey should be successfully ASSERT_EQ(enableTestSurveys({mIntegerSurvey.h})->waitForResult(), API_OK); // Retrieving the integer response survey's trigger action ID should be successful. auto triggersTracker = getActiveSurveyTriggerActions(); ASSERT_EQ(triggersTracker->waitForResult(), API_OK); const auto triggers = toIntegerSet(triggersTracker->request->getMegaIntegerList()); ASSERT_TRUE(triggers.count(mIntegerSurvey.triggerActionId)); // Retrieving the integer response survey (with positive maxResponse) should be successful. Survey integerSurvey; ASSERT_NO_FATAL_FAILURE(getOneActiveSurvey(mIntegerSurvey.triggerActionId, integerSurvey)); ASSERT_EQ(integerSurvey.h, mIntegerSurvey.h); ASSERT_GT(integerSurvey.maxResponse, 0); // Different answers ASSERT_EQ( answerSurvey(integerSurvey.h, integerSurvey.triggerActionId, "1", "")->waitForResult(), API_OK); ASSERT_EQ(answerSurvey(mIntegerSurvey.h, mIntegerSurvey.triggerActionId, std::to_string(integerSurvey.maxResponse), "Awesome") ->waitForResult(), API_OK); // Clearing testing surveys should be successful ASSERT_EQ(enableTestSurveys({})->waitForResult(), API_OK); } TEST_F(OneQuestionSurveyTest, AnswerTextSurveyWronglyShouldFail) { LOG_info << "___TEST OneQuestionSurveyTest::AnswerTextSurveyWronglyShouldFail"; // Enable testing for pre-configured text response survey should be successfully ASSERT_EQ(enableTestSurveys({mTextSurvey.h})->waitForResult(), API_OK); // Retrieving the text response survey's trigger action ID should be successful. auto triggersTracker = getActiveSurveyTriggerActions(); ASSERT_EQ(triggersTracker->waitForResult(), API_OK); const auto triggers = toIntegerSet(triggersTracker->request->getMegaIntegerList()); ASSERT_TRUE(triggers.count(mTextSurvey.triggerActionId)); // Retrieving the text response survey (with 0 maxResponse) should be successful. Survey textSurvey; ASSERT_NO_FATAL_FAILURE(getOneActiveSurvey(mTextSurvey.triggerActionId, textSurvey)); ASSERT_EQ(textSurvey.h, mTextSurvey.h); ASSERT_EQ(textSurvey.maxResponse, 0); // Answer using the wrong trigger action ID const unsigned int wrongTriggerActionId = textSurvey.triggerActionId + 1; ASSERT_EQ(answerSurvey(textSurvey.h, wrongTriggerActionId, "awesome", "")->waitForResult(), API_ENOENT); // Answer using the wrong handle const handle wrongHandle = textSurvey.h + 1; ASSERT_EQ(answerSurvey(wrongHandle, textSurvey.triggerActionId, "awesome", "")->waitForResult(), API_EARGS); // Answer using empty response ASSERT_EQ(answerSurvey(textSurvey.h, textSurvey.triggerActionId, "", "")->waitForResult(), API_EARGS); // Clearing testing surveys should be successful ASSERT_EQ(enableTestSurveys({})->waitForResult(), API_OK); } TEST_F(OneQuestionSurveyTest, AnswerIntegerSurveyWronglyShouldFail) { LOG_info << "___TEST OneQuestionSurveyTest::AnswerIntegerSurveyWronglyShouldFail"; // Enable testing for pre-configured integer response survey should be successfully ASSERT_EQ(enableTestSurveys({mIntegerSurvey.h})->waitForResult(), API_OK); // Retrieving the integer response survey's trigger action ID should be successful. auto triggersTracker = getActiveSurveyTriggerActions(); ASSERT_EQ(triggersTracker->waitForResult(), API_OK); const auto triggers = toIntegerSet(triggersTracker->request->getMegaIntegerList()); ASSERT_TRUE(triggers.count(mIntegerSurvey.triggerActionId)); // Retrieving the integer response survey (with positive maxResponse) should be successful. Survey integerSurvey; ASSERT_NO_FATAL_FAILURE(getOneActiveSurvey(mIntegerSurvey.triggerActionId, integerSurvey)); ASSERT_EQ(integerSurvey.h, mIntegerSurvey.h); ASSERT_GT(integerSurvey.maxResponse, 0); // Answer using the wrong trigger action ID const unsigned int wrongTriggerActionId = integerSurvey.triggerActionId + 1; ASSERT_EQ(answerSurvey(integerSurvey.h, wrongTriggerActionId, "1", "")->waitForResult(), API_ENOENT); // Answer using the wrong handle const handle wrongHandle = integerSurvey.h + 1; ASSERT_EQ(answerSurvey(wrongHandle, integerSurvey.triggerActionId, "1", "")->waitForResult(), API_EARGS); // Answer using empty response ASSERT_EQ(answerSurvey(integerSurvey.h, integerSurvey.triggerActionId, "", "")->waitForResult(), API_EARGS); // Answer using non integer response ASSERT_EQ( answerSurvey(integerSurvey.h, integerSurvey.triggerActionId, "nonint", "")->waitForResult(), API_EARGS); // Answer using a response which is out of (0..maxResponse] range ASSERT_EQ( answerSurvey(integerSurvey.h, integerSurvey.triggerActionId, "0", "")->waitForResult(), API_EARGS); ASSERT_EQ(answerSurvey(integerSurvey.h, integerSurvey.triggerActionId, std::to_string(integerSurvey.maxResponse + 1), "") ->waitForResult(), API_EARGS); // Clearing testing surveys should be successful ASSERT_EQ(enableTestSurveys({})->waitForResult(), API_OK); } sdk-10.11.0/tests/integration/passwordManager/000077500000000000000000000000001516266226600212715ustar00rootroot00000000000000sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManager.cpp000066400000000000000000000026221516266226600263760ustar00rootroot00000000000000#include "SdkTestPasswordManager.h" #include "mock_listeners.h" #include using namespace testing; void SdkTestPasswordManager::SetUp() { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1, true, MegaApi::CLIENT_TYPE_PASSWORD_MANAGER)); ASSERT_NE(megaApi[0], nullptr); mApi = megaApi[0].get(); ASSERT_NO_FATAL_FAILURE(initPasswordManagerBase()); ASSERT_NO_FATAL_FAILURE(removePWMNodes()); } void SdkTestPasswordManager::TearDown() { removePWMNodes(); SdkTest::TearDown(); } void SdkTestPasswordManager::initPasswordManagerBase() { testing::NiceMock rl{mApi}; const auto captureHandle = [this](const MegaRequest& req) { mPWMBaseNodeHandle = req.getNodeHandle(); }; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_CREATE_PASSWORD_MANAGER_BASE, std::move(captureHandle)); RequestTracker rtPasswordManagerBase(megaApi[0].get()); megaApi[0]->getPasswordManagerBase(&rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); ASSERT_NE(mPWMBaseNodeHandle, UNDEF); } void SdkTestPasswordManager::removePWMNodes() { const std::unique_ptr pwmBaseNode(megaApi[0]->getNodeByHandle(mPWMBaseNodeHandle)); ASSERT_TRUE(pwmBaseNode); ASSERT_NO_FATAL_FAILURE(purgeTree(0, pwmBaseNode.get(), false)); } sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManager.h000066400000000000000000000020531516266226600260410ustar00rootroot00000000000000/** * @file * @brief Definition for the SdkTestPasswordManager class, designed to be used as base class for * test fixtures involving password manager use cases. */ #ifndef INCLUDE_PASSWORDMANAGER_SDKTESTPASSWORDMANAGER_H_ #define INCLUDE_PASSWORDMANAGER_SDKTESTPASSWORDMANAGER_H_ #include "SdkTest_test.h" /** * @class SdkTestPasswordManager * @brief Base class to be used in password manager test cases. * * Sets up a password manager account with a valid base node. */ class SdkTestPasswordManager: public SdkTest { public: static constexpr auto MAX_TIMEOUT{3min}; void SetUp() override; void TearDown() override; handle getBaseHandle() const { return mPWMBaseNodeHandle; } std::unique_ptr getBaseNode() const { return std::unique_ptr(mApi->getNodeByHandle(getBaseHandle())); } protected: handle mPWMBaseNodeHandle{UNDEF}; MegaApi* mApi{}; void initPasswordManagerBase(); void removePWMNodes(); }; #endif // INCLUDE_PASSWORDMANAGER_SDKTESTPASSWORDMANAGER_H_ sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManagerBaseNode_test.cpp000066400000000000000000000026731516266226600310440ustar00rootroot00000000000000/** * @file * @brief Tests for operations on the base node for the password manager */ #include "mock_listeners.h" #include "SdkTestPasswordManager.h" #include using namespace testing; class SdkTestPasswordManagerBaseNode: public SdkTestPasswordManager {}; TEST_F(SdkTestPasswordManagerBaseNode, GetPWMBaseNode) { LOG_debug << getLogPrefix() << "get Password Manager Base node by handle"; ASSERT_NE(getBaseNode(), nullptr) << "Error retrieving MegaNode for Password Base with handle " << toNodeHandle(getBaseHandle()); } TEST_F(SdkTestPasswordManagerBaseNode, GetPWMBaseNodeByUserAttr) { LOG_debug << getLogPrefix() << "get Password Manager Base via get user's attribute command"; testing::NiceMock rl{mApi}; handle reqHandle{UNDEF}; const auto captureHandle = [&reqHandle](const MegaRequest& req) { reqHandle = req.getNodeHandle(); }; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_GET_ATTR_USER, std::move(captureHandle)); mApi->getUserAttribute(MegaApi::USER_ATTR_PWM_BASE, &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); ASSERT_EQ(reqHandle, getBaseHandle()) << "Mismatch in user attribute pwmh retrieved"; } TEST_F(SdkTestPasswordManagerBaseNode, DeletePWMBaseNode) { const auto baseNode = getBaseNode(); ASSERT_NE(nullptr, baseNode); ASSERT_EQ(API_EARGS, doDeleteNode(0, baseNode.get())); } sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManagerCreditCardNodeCRUD_test.cpp000066400000000000000000000351411516266226600326500ustar00rootroot00000000000000/** * @file * @brief Test CRUD operation on credit card nodes */ #include "mega/totp.h" #include "mock_listeners.h" #include "SdkTestPasswordManager.h" #include using namespace testing; // Convenience using CreditCardNodeData = MegaNode::CreditCardNodeData; /** * @brief Helper matcher to compare two const char* (including nullptr check) */ MATCHER_P(CCharEq, expected, "const char* is eq to " + std::string{expected ? expected : "nullptr"}) { if (expected == nullptr || arg == nullptr) return expected == arg; return ExplainMatchResult(StrEq(expected), arg, result_listener); } /** * @brief Helper matcher for CreditCardNodeData pointers */ MATCHER_P(CreditCardNodeDataEquals, expected, "Matches CreditCardNodeData object") { if (expected == nullptr || arg == nullptr) { *result_listener << "Expected: " << (expected ? "non-null" : "nullptr") << ", but got: " << (arg ? "non-null" : "nullptr"); return expected == arg; } const auto check = [&result_listener](const char* exp, const char* val, const std::string_view fieldName) -> bool { if (!ExplainMatchResult(CCharEq(exp), val, result_listener)) { *result_listener << "\nMismatch in field '" << fieldName << "': expected [" << (exp ? exp : "nullptr") << "], but got [" << (val ? val : "nullptr") << "]"; return false; } return true; }; bool eq = true; eq &= check(expected->cardNumber(), arg->cardNumber(), "cardNumber"); eq &= check(expected->notes(), arg->notes(), "notes"); eq &= check(expected->cardHolderName(), arg->cardHolderName(), "cardHolderName"); eq &= check(expected->cvv(), arg->cvv(), "cvv"); eq &= check(expected->expirationDate(), arg->expirationDate(), "expirationDate"); return eq; } class SdkTestPasswordManagerCreditCardNodeCRUD: public SdkTestPasswordManager { public: handle createCreditCardNode(const std::string& name = {}, const CreditCardNodeData* data = nullptr) { const auto nameFinal = name.empty() ? getFilePrefix() : name; const auto* dataFinal = data ? data : predefinedCreditCardDataOwned(); return sdk_test::createCreditCardNode(mApi, nameFinal, dataFinal, getBaseHandle()); } handle createDummyPasswordNode() { auto pwdData = std::unique_ptr{ MegaNode::PasswordNodeData::createInstance("password", nullptr, nullptr, nullptr, nullptr)}; return sdk_test::createPasswordNode(mApi, getFilePrefix().c_str(), pwdData.get(), getBaseHandle()); } /** * @brief Returns a pointer to a default password data owned by the fixture */ const CreditCardNodeData* predefinedCreditCardDataOwned() const { static const auto defaultData{predefinedCreditCardData()}; return defaultData.get(); } std::unique_ptr predefinedCreditCardData() const { return std::unique_ptr{ CreditCardNodeData::createInstance("123456789", "notes", "TEST CARD HOLDER NAME", "123", "02/24")}; } std::unique_ptr emptyCreditCardData() const { return std::unique_ptr{ CreditCardNodeData::createInstance(nullptr, nullptr, nullptr, nullptr, nullptr)}; } void updateCreditCardNode(const handle nh, const CreditCardNodeData* data) { NiceMock rl; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updateCreditCardNode(nh, data, &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Checks that the password data of the node with the given handle matches the one * provided */ void validateCreditCardNodeData(const handle nh, const CreditCardNodeData* data) { std::unique_ptr retrievedNode{mApi->getNodeByHandle(nh)}; ASSERT_TRUE(retrievedNode); ASSERT_TRUE(retrievedNode->isCreditCardNode()); std::unique_ptr retrievedData{retrievedNode->getCreditCardData()}; ASSERT_THAT(retrievedData.get(), CreditCardNodeDataEquals(data)); } }; TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, CreateNewCreditCardNode) { static const auto logPre = getLogPrefix(); const auto mnBase = getBaseNode(); ASSERT_NE(mnBase, nullptr); LOG_debug << logPre << "Checking node is not present already"; const auto creditCardNodeName = getFilePrefix(); std::unique_ptr node{mApi->getChildNode(mnBase.get(), creditCardNodeName.c_str())}; ASSERT_EQ(node, nullptr) << "There was already a password manager node with the name " << creditCardNodeName << ". We can't test node creation"; LOG_debug << logPre << "Creating new node"; const auto newCreditCardNodeHandle = createCreditCardNode(creditCardNodeName); LOG_debug << logPre << "Validating new node"; ASSERT_NE(newCreditCardNodeHandle, UNDEF); const std::unique_ptr newCcNode{mApi->getNodeByHandle(newCreditCardNodeHandle)}; ASSERT_NE(newCcNode, nullptr) << "New node could not be retrieved"; ASSERT_TRUE(newCcNode->isPasswordManagerNode()); ASSERT_TRUE(newCcNode->isCreditCardNode()); ASSERT_FALSE(mApi->isPasswordManagerNodeFolder(newCcNode->getHandle())); LOG_debug << logPre << "Validating node name and data"; EXPECT_STREQ(newCcNode->getName(), creditCardNodeName.c_str()); std::unique_ptr receivedCreditCardData{newCcNode->getCreditCardData()}; EXPECT_THAT(receivedCreditCardData.get(), CreditCardNodeDataEquals(predefinedCreditCardDataOwned())); } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, CreateNewCreditCardNodeWithEmptyField) { static const auto logPre = getLogPrefix(); auto ccData = emptyCreditCardData(); ccData->setCardNumber("123456789"); ccData->setNotes(""); LOG_debug << logPre << "Creating new Credit Card Node"; const auto newCcNodeHandle = createCreditCardNode({}, ccData.get()); LOG_debug << logPre << "Getting created Credit Card Node"; ASSERT_NE(newCcNodeHandle, UNDEF); const std::unique_ptr newCcNode{mApi->getNodeByHandle(newCcNodeHandle)}; ASSERT_NE(newCcNode, nullptr) << "New node could not be retrieved"; LOG_debug << logPre << "Validating node name and data"; std::unique_ptr receivedCreditCardData{newCcNode->getCreditCardData()}; ccData->setNotes(nullptr); EXPECT_THAT(receivedCreditCardData.get(), CreditCardNodeDataEquals(ccData.get())); } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, CopyCreditCardNode) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating new node to be cloned"; const auto newCcNodeHandle = createCreditCardNode(); ASSERT_NE(newCcNodeHandle, UNDEF); const std::unique_ptr newCcNode{mApi->getNodeByHandle(newCcNodeHandle)}; ASSERT_NE(newCcNode, nullptr) << "New node could not be retrieved"; LOG_debug << logPre << "Clonning the node"; std::unique_ptr clonedNode{newCcNode->copy()}; std::unique_ptr clonedcCData{clonedNode->getCreditCardData()}; LOG_debug << logPre << "Validating cloned node"; ASSERT_TRUE(clonedNode->isCreditCardNode()); ASSERT_FALSE(mApi->isPasswordManagerNodeFolder(clonedNode->getHandle())); EXPECT_STREQ(clonedNode->getName(), getFilePrefix().c_str()); EXPECT_THAT(clonedcCData.get(), CreditCardNodeDataEquals(predefinedCreditCardDataOwned())); } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, CreateErrorSameName) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a password node node"; const auto newPwdNodeHandle = createDummyPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Expecting error when creating a credit card node with the same name " " as the previous password node"; NiceMock rl; rl.setErrorExpectations(API_EEXIST, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createCreditCardNode(getFilePrefix().c_str(), predefinedCreditCardDataOwned(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, CreateErrorArgs) { static const auto logPre = getLogPrefix(); { LOG_debug << logPre << "#### Test1: Creating a node with invalid arguments, expecting API_EARGS ####"; NiceMock rl; rl.setErrorExpectations(API_EARGS, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createCreditCardNode(nullptr, nullptr, INVALID_HANDLE, &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test2: Creating a node with invalid card number, expecting API_EARGS ####"; NiceMock rl; const auto cCData = predefinedCreditCardData(); cCData->setCardNumber("A12345"); rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createCreditCardNode(getFilePrefix().c_str(), cCData.get(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test3: Creating a node with invalid cvv, expecting API_EARGS ####"; NiceMock rl; const auto cCData = predefinedCreditCardData(); cCData->setCvv("A12"); rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createCreditCardNode(getFilePrefix().c_str(), cCData.get(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test4: Creating a node with invalid expiration date, expecting API_EARGS ####"; NiceMock rl; const auto cCData = predefinedCreditCardData(); cCData->setExpirationDate("15/03"); // invalid month rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createCreditCardNode(getFilePrefix().c_str(), cCData.get(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, UpdateAllFields) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newCcNodeHandle = createCreditCardNode(); ASSERT_NE(newCcNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the notes"; const auto cCData = emptyCreditCardData(); cCData->setCardNumber("456789"); cCData->setNotes("Updated Notes (2)"); cCData->setCvv("987"); cCData->setCardHolderName("NEW CARD HOLDER NAME"); cCData->setExpirationDate(""); // set this field to empty updateCreditCardNode(newCcNodeHandle, cCData.get()); LOG_debug << logPre << "Validating data"; cCData->setExpirationDate(nullptr); validateCreditCardNodeData(newCcNodeHandle, cCData.get()); } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, UpdateErrorArgs) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newCcNodeHandle = createCreditCardNode(); ASSERT_NE(newCcNodeHandle, UNDEF); { LOG_debug << logPre << "#### Test1: Updating Credit Card Node with empty data ####"; const auto emptyData = emptyCreditCardData(); NiceMock rl; rl.setErrorExpectations(API_EARGS, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updateCreditCardNode(newCcNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test2: Updating Credit Card Node with invalid credit card number ####"; const auto emptyData = emptyCreditCardData(); emptyData->setCardNumber("A12345"); NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updateCreditCardNode(newCcNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test3: Updating Credit Card Node with invalid cvv ####"; const auto emptyData = emptyCreditCardData(); emptyData->setCvv("A12"); NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updateCreditCardNode(newCcNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test4: Updating Credit Card Node with invalid expiration date ####"; const auto emptyData = emptyCreditCardData(); emptyData->setExpirationDate("01-02"); NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updateCreditCardNode(newCcNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } } TEST_F(SdkTestPasswordManagerCreditCardNodeCRUD, DeleteCreditCardNode) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newCcNodeHandle = createCreditCardNode(); ASSERT_NE(newCcNodeHandle, UNDEF); std::unique_ptr retrievedCcNode{mApi->getNodeByHandle(newCcNodeHandle)}; ASSERT_NE(retrievedCcNode, nullptr); LOG_debug << logPre << "Deleting the node"; ASSERT_EQ(API_OK, doDeleteNode(0, retrievedCcNode.get())); retrievedCcNode.reset(mApi->getNodeByHandle(newCcNodeHandle)); ASSERT_EQ(nullptr, retrievedCcNode.get()); } sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManagerImport_test.cpp000066400000000000000000000162711516266226600306350ustar00rootroot00000000000000/** * @file SdkTestPasswordManager_test.cpp * @brief This file defines some tests for testing password manager functionalities */ #include "integration/mock_listeners.h" #include "megaapi.h" #include "megautils.h" #include "sdk_test_utils.h" #include "SdkTestPasswordManager.h" #include /** * @class SdkTestPasswordManagerImport * @brief Fixture for test suite to test password manager import functionality * */ class SdkTestPasswordManagerImport: public SdkTestPasswordManager { public: static constexpr auto MAX_TIMEOUT{3min}; using BadEntries = std::map; using ImportPassFileResult = std::variant; /** * @brief Imports the passwords contained in the file at the given path * * @return std::variant that can be: * - long long: An error code different from API_OK. This means that the request didn't finish * as expected. There are two specific error codes that are set by this wrapper method: * + API_ETEMPUNAVAIL: When the request didn't finish on time * + API_EKEY: Request finished with API_OK but the getMegaStringIntegerMap returned nullptr * - BadEntries: A map with the entries that were not properly parsed from the file and an * associated error code. */ ImportPassFileResult importPasswordsFromFile(const fs::path& filePath) const { testing::NiceMock rl{megaApi[0].get()}; ImportPassFileResult result{API_ETEMPUNAVAIL}; EXPECT_CALL(rl, onRequestFinish) .WillOnce( [&result, &rl](MegaApi*, MegaRequest* req, MegaError* err) { if (err->getErrorCode() == API_OK) { MegaStringIntegerMap* stringIntegerList = req->getMegaStringIntegerMap(); if (stringIntegerList) result = stringIntegerMapToMap(*stringIntegerList); else result = API_EKEY; } else result = err->getErrorCode(); rl.markAsFinished(); }); megaApi[0]->importPasswordsFromFile(path_u8string(filePath).c_str(), MegaApi::IMPORT_PASSWORD_SOURCE_GOOGLE, getBaseHandle(), &rl); rl.waitForFinishOrTimeout(MAX_TIMEOUT); return result; } /** * @brief Same as previous overload but taking a LocalTempFile */ ImportPassFileResult importPasswordsFromFile(sdk_test::LocalTempFile&& file) const { return importPasswordsFromFile(file.getPath()); } /** * @brief Returns a vector with the names of the password nodes hanging from the password * manager base node. In this context, these are the nodes that were successfully imported. */ std::vector getImportedPassNodesNames() const { std::unique_ptr list{megaApi[0]->getChildren(getBaseNode().get())}; if (!list) return {}; return toNamesVector(*list); } }; /** * @brief SdkTestPasswordManagerImport.SdkTestImportPasswordAllEntriesOk */ TEST_F(SdkTestPasswordManagerImport, SdkTestImportPasswordAllEntriesOk) { LOG_debug << "# Create csv file"; constexpr std::string_view fileContents{R"(name,url,username,password,note foo.com,https://foo.com/,tx,"hola""""\""\"".,,", hello.co,https://hello.co/,hello,hello.1234,Description with ñ test.com,https://test.com/,test3,"hello.12,34", test.com,https://test.com/,txema,hel\nlo.1234,"" test2.com,https://test2.com/,test,hello.1234, )"}; const auto fname = "test.csv"; LOG_debug << "# Import google csv file"; const auto badEntriesVar = importPasswordsFromFile({fname, fileContents}); const auto badEntries = std::get_if(&badEntriesVar); ASSERT_TRUE(badEntries) << "Something went wrong importing the file"; ASSERT_TRUE(badEntries->empty()); ASSERT_THAT(getImportedPassNodesNames(), testing::UnorderedElementsAre("foo.com", "hello.co", "test.com", "test.com (1)", "test2.com")); } /** * @brief SdkTestPasswordManagerImport.SdkTestImportPasswordFails * * - Import file with invalid path * - Import empty file */ TEST_F(SdkTestPasswordManagerImport, SdkTestImportPasswordFails) { LOG_debug << "# Import google csv file - null path"; ASSERT_EQ(importPasswordsFromFile(""), ImportPassFileResult{API_EREAD}); LOG_debug << "# Import google csv file - empty file"; const std::string fname = "test.csv"; ASSERT_EQ(importPasswordsFromFile({fname, 0}), ImportPassFileResult{API_EACCESS}); } /** * @brief SdkTestPasswordManagerImport.SdkTestImportPasswordSomeRowsWrong */ TEST_F(SdkTestPasswordManagerImport, SdkTestImportPasswordSomeRowsWrong) { LOG_debug << "# Create csv file"; constexpr std::string_view fileContents{R"(name,url,username,password,note name,https://foo.com/,username,password,note name2,https://foo.com/,username,,note name3,username,password,note ,https://foo.com/,username,password,note )"}; const std::string fname = "test.csv"; const auto badEntriesVar = importPasswordsFromFile({fname, fileContents}); const auto badEntries = std::get_if(&badEntriesVar); ASSERT_TRUE(badEntries) << "Something went wrong importing the file"; ASSERT_EQ(*badEntries, (BadEntries{ {"name2,https://foo.com/,username,,note", MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD}, {"name3,username,password,note", MegaApi::IMPORTED_PASSWORD_ERROR_PARSER}, {",https://foo.com/,username,password,note", MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGNAME}, })); ASSERT_THAT(getImportedPassNodesNames(), testing::UnorderedElementsAre("name")); } /** * @brief SdkTestPasswordManagerImport.SdkTestImportPasswordAllRowsWrong */ TEST_F(SdkTestPasswordManagerImport, SdkTestImportPasswordAllRowsWrong) { LOG_debug << "# Create csv file"; constexpr std::string_view fileContents{R"(name,url,username,password,note name2,https://foo.com/,username,,note name3,username,password,note ,https://foo.com/,username,password,note )"}; const std::string fname = "test.csv"; const auto badEntriesVar = importPasswordsFromFile({fname, fileContents}); const auto badEntries = std::get_if(&badEntriesVar); ASSERT_TRUE(badEntries) << "Something went wrong importing the file"; ASSERT_EQ(*badEntries, (BadEntries{ {"name2,https://foo.com/,username,,note", MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGPASSWORD}, {"name3,username,password,note", MegaApi::IMPORTED_PASSWORD_ERROR_PARSER}, {",https://foo.com/,username,password,note", MegaApi::IMPORTED_PASSWORD_ERROR_MISSINGNAME}, })); ASSERT_TRUE(getImportedPassNodesNames().empty()); } sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManagerPassFolderCRUD_test.cpp000066400000000000000000000046611516266226600321030ustar00rootroot00000000000000/** * @file * @brief Test CRUD operation on password folders */ #include "mock_listeners.h" #include "SdkTestPasswordManager.h" #include using namespace testing; TEST_F(SdkTestPasswordManager, CreateNewPassFolderNode) { const auto mnBase = getBaseNode(); const auto folderName = getFilePrefix(); ASSERT_NE(INVALID_HANDLE, createFolder(0, folderName.c_str(), mnBase.get())); } class SdkTestPasswordManagerPassFolderCRUD: public SdkTestPasswordManager { public: void SetUp() override { SdkTestPasswordManager::SetUp(); const auto mnBase = getBaseNode(); const auto folderName = getFolderName(); folderHandle = createFolder(0, folderName.c_str(), mnBase.get()); ASSERT_NE(INVALID_HANDLE, folderHandle); } void TearDown() override { if (const auto folder = getFolderNode(); folder) doDeleteNode(0, folder.get()); SdkTestPasswordManager::TearDown(); } std::string getFolderName() const { return getFilePrefix(); } handle getFolderHandle() const { return folderHandle; } std::unique_ptr getFolderNode() const { return std::unique_ptr{mApi->getNodeByHandle(getFolderHandle())}; } private: handle folderHandle{INVALID_HANDLE}; }; TEST_F(SdkTestPasswordManagerPassFolderCRUD, GetPassFolder) { const auto mnPNFolder = getFolderNode(); ASSERT_NE(nullptr, mnPNFolder); ASSERT_TRUE(mApi->isPasswordManagerNodeFolder(mnPNFolder->getHandle())); ASSERT_EQ(getFolderName(), mnPNFolder->getName()); } TEST_F(SdkTestPasswordManagerPassFolderCRUD, RenameFolderName) { const char* updatedFolderName = "UpdatedPNF"; auto folder = getFolderNode(); static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Renaming folder"; ASSERT_NE(nullptr, folder); ASSERT_EQ(API_OK, doRenameNode(0, folder.get(), updatedFolderName)); LOG_debug << logPre << "Validating new name"; folder = getFolderNode(); ASSERT_NE(nullptr, folder); ASSERT_TRUE(mApi->isPasswordManagerNodeFolder(folder->getHandle())); ASSERT_STREQ(updatedFolderName, folder->getName()); } TEST_F(SdkTestPasswordManagerPassFolderCRUD, DeleteFolder) { auto folder = getFolderNode(); ASSERT_TRUE(folder); ASSERT_EQ(API_OK, doDeleteNode(0, folder.get())); folder.reset(mApi->getNodeByHandle(getFolderHandle())); ASSERT_EQ(nullptr, folder); } sdk-10.11.0/tests/integration/passwordManager/SdkTestPasswordManagerPassNodeCRUD_test.cpp000066400000000000000000001027511516266226600315540ustar00rootroot00000000000000/** * @file * @brief Test CRUD operation on password nodes (not folders) */ #include "mega/totp.h" #include "mock_listeners.h" #include "SdkTestPasswordManager.h" #include using namespace testing; // Convenience using PasswordNodeData = MegaNode::PasswordNodeData; using TotpData = MegaNode::PasswordNodeData::TotpData; /** * @brief Helper matcher to compare two const char* (including nullptr check) */ MATCHER_P(CCharEq, expected, "const char* is eq to " + std::string{expected ? expected : "nullptr"}) { if (expected == nullptr || arg == nullptr) return expected == arg; return ExplainMatchResult(StrEq(expected), arg, result_listener); } /** * @brief Helper matcher for TotpData */ MATCHER_P(TotpDataEquals, expected, "Matches TotpData object") { if (expected == nullptr || arg == nullptr) { *result_listener << "Expected: " << (expected ? "non-null" : "nullptr") << ", but got: " << (arg ? "non-null" : "nullptr"); return expected == arg; } const auto check = [&result_listener](const char* exp, const char* val, const std::string_view fieldName) -> bool { if (!ExplainMatchResult(CCharEq(exp), val, result_listener)) { *result_listener << "\nMismatch in field '" << fieldName << "': expected [" << (exp ? exp : "nullptr") << "], but got [" << (val ? val : "nullptr") << "]"; return false; } return true; }; bool eq = true; eq &= check(expected->sharedSecret(), arg->sharedSecret(), "shared secret"); eq &= ExplainMatchResult(Eq(expected->expirationTime()), arg->expirationTime(), result_listener); eq &= ExplainMatchResult(Eq(expected->hashAlgorithm()), arg->hashAlgorithm(), result_listener); eq &= ExplainMatchResult(Eq(expected->nDigits()), arg->nDigits(), result_listener); return eq; } /** * @brief Helper matcher for PasswordNodeData pointers */ MATCHER_P(PasswordNodeDataEquals, expected, "Matches PasswordNodeData object") { if (expected == nullptr || arg == nullptr) { *result_listener << "Expected: " << (expected ? "non-null" : "nullptr") << ", but got: " << (arg ? "non-null" : "nullptr"); return expected == arg; } const auto check = [&result_listener](const char* exp, const char* val, const std::string_view fieldName) -> bool { if (!ExplainMatchResult(CCharEq(exp), val, result_listener)) { *result_listener << "\nMismatch in field '" << fieldName << "': expected [" << (exp ? exp : "nullptr") << "], but got [" << (val ? val : "nullptr") << "]"; return false; } return true; }; bool eq = true; eq &= check(expected->password(), arg->password(), "password"); eq &= check(expected->notes(), arg->notes(), "notes"); eq &= check(expected->url(), arg->url(), "url"); eq &= check(expected->userName(), arg->userName(), "userName"); eq &= ExplainMatchResult(TotpDataEquals(expected->totpData()), arg->totpData(), result_listener); return eq; } class SdkTestPasswordManagerPassNodeCRUD: public SdkTestPasswordManager { public: handle createPasswordNode(const std::string& name = {}, const PasswordNodeData* data = nullptr) { const auto nameFinal = name.empty() ? getFilePrefix() : name; const auto* dataFinal = data ? data : predefinedPwdDataOwned(); return sdk_test::createPasswordNode(mApi, nameFinal, dataFinal, getBaseHandle()); } /** * @brief Returns a pointer to a default password data owned by the fixture */ const PasswordNodeData* predefinedPwdDataOwned() const { static const auto defaultData{predefinedPwdData()}; return defaultData.get(); } std::unique_ptr predefinedPwdTotpData() const { std::unique_ptr totpData{ TotpData::createInstance("abcd", 20, TotpData::HASH_ALGO_SHA256, 8)}; return totpData; } std::unique_ptr emptyPwdTotpData() const { std::unique_ptr totpData{TotpData::createInstance(nullptr, TotpData::TOTPNULLOPT, TotpData::TOTPNULLOPT, TotpData::TOTPNULLOPT)}; return totpData; } std::unique_ptr predefinedPwdData() const { auto totpData = predefinedPwdTotpData(); return std::unique_ptr{PasswordNodeData::createInstance("12},\" '34", "notes", "url", "userName", totpData.get())}; } std::unique_ptr emptyPwdData() const { return std::unique_ptr{ PasswordNodeData::createInstance(nullptr, nullptr, nullptr, nullptr, nullptr)}; } std::unique_ptr getCustomTotpData(std::unique_ptr&& pwdData, std::function modifyTotpData) { std::unique_ptr totpData; if (auto auxTotpData = pwdData->totpData(); !auxTotpData) { totpData = emptyPwdTotpData(); } else { totpData.reset(auxTotpData->copy()); } modifyTotpData(*totpData); pwdData->setTotpData(totpData.get()); return std::move(pwdData); } void updatePwdNode(const handle nh, const PasswordNodeData* data) { NiceMock rl; rl.setErrorExpectations(API_OK, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(nh, data, &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Checks that the password data of the node with the given handle matches the one * provided */ void validatePwdNodeData(const handle nh, const PasswordNodeData* data) { std::unique_ptr retrievedNode{mApi->getNodeByHandle(nh)}; ASSERT_TRUE(retrievedNode); ASSERT_TRUE(retrievedNode->isPasswordNode()); std::unique_ptr retrievedData{retrievedNode->getPasswordData()}; ASSERT_THAT(retrievedData.get(), PasswordNodeDataEquals(data)); } }; TEST_F(SdkTestPasswordManagerPassNodeCRUD, CreateNewPassNode) { static const auto logPre = getLogPrefix(); const auto mnBase = getBaseNode(); ASSERT_NE(mnBase, nullptr); LOG_debug << logPre << "Checking node is not present already"; const auto pwdNodeName = getFilePrefix(); std::unique_ptr node{mApi->getChildNode(mnBase.get(), pwdNodeName.c_str())}; ASSERT_EQ(node, nullptr) << "There was already a password node with the name " << pwdNodeName << ". We can't test node creation"; LOG_debug << logPre << "Creating new node"; const auto newPwdNodeHandle = createPasswordNode(pwdNodeName); LOG_debug << logPre << "Validating new node"; ASSERT_NE(newPwdNodeHandle, UNDEF); const std::unique_ptr newPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(newPwdNode, nullptr) << "New node could not be retrieved"; ASSERT_TRUE(newPwdNode->isPasswordManagerNode()); ASSERT_TRUE(newPwdNode->isPasswordNode()); ASSERT_FALSE(mApi->isPasswordManagerNodeFolder(newPwdNode->getHandle())); LOG_debug << logPre << "Validating node name and data"; EXPECT_STREQ(newPwdNode->getName(), pwdNodeName.c_str()); std::unique_ptr receivedPwdData{newPwdNode->getPasswordData()}; EXPECT_THAT(receivedPwdData.get(), PasswordNodeDataEquals(predefinedPwdDataOwned())); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, CreateNewPassNodeWithEmptyField) { static const auto logPre = getLogPrefix(); auto pwdData = emptyPwdData(); pwdData->setPassword("ABCD"); pwdData->setNotes(""); const auto pwdNodeName = getFilePrefix(); LOG_debug << logPre << "Creating new Password Node"; const auto newPwdNodeHandle = createPasswordNode(pwdNodeName, pwdData.get()); LOG_debug << logPre << "Getting created Password Node"; ASSERT_NE(newPwdNodeHandle, UNDEF); const std::unique_ptr newPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(newPwdNode, nullptr) << "New node could not be retrieved"; ASSERT_TRUE(newPwdNode->isPasswordNode()); LOG_debug << logPre << "Validating node name and data"; EXPECT_STREQ(newPwdNode->getName(), pwdNodeName.c_str()); std::unique_ptr receivedPwdData{newPwdNode->getPasswordData()}; pwdData->setNotes(nullptr); EXPECT_THAT(receivedPwdData.get(), PasswordNodeDataEquals(pwdData.get())); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, CopyPassNode) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating new node to be cloned"; const auto pwdNodeName = getFilePrefix(); const auto newPwdNodeHandle = createPasswordNode(pwdNodeName); ASSERT_NE(newPwdNodeHandle, UNDEF); const std::unique_ptr newPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(newPwdNode, nullptr) << "New node could not be retrieved"; LOG_debug << logPre << "Clonning the node"; std::unique_ptr clonedNode{newPwdNode->copy()}; std::unique_ptr clonedPwdData{clonedNode->getPasswordData()}; LOG_debug << logPre << "Validating cloned node"; ASSERT_TRUE(clonedNode->isPasswordNode()); ASSERT_FALSE(mApi->isPasswordManagerNodeFolder(clonedNode->getHandle())); EXPECT_STREQ(clonedNode->getName(), pwdNodeName.c_str()); EXPECT_THAT(clonedPwdData.get(), PasswordNodeDataEquals(predefinedPwdDataOwned())); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, CreateErrorSameName) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto pwdNodeName = getFilePrefix(); const auto newPwdNodeHandle = createPasswordNode(pwdNodeName); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Expecting error when creating a node with the same name"; NiceMock rl; rl.setErrorExpectations(API_EEXIST, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createPasswordNode(pwdNodeName.c_str(), predefinedPwdDataOwned(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, CreateErrorArgs) { static const auto logPre = getLogPrefix(); { LOG_debug << logPre << "#### Test1: Creating a node with invalid arguments, expecting API_EARGS ####"; NiceMock rl; rl.setErrorExpectations(API_EARGS, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createPasswordNode(nullptr, nullptr, INVALID_HANDLE, &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test2: Creating a node with empty password, expecting API_EARGS ####"; NiceMock rl; const auto pwdData = predefinedPwdData(); pwdData->setPassword(""); rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createPasswordNode(getFilePrefix().c_str(), pwdData.get(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } { LOG_debug << logPre << "#### Test3: Creating a node with incomplete TOTP data, expecting API_EARGS ####"; NiceMock rl; const auto pwdData = predefinedPwdData(); const auto totpData = predefinedPwdTotpData(); totpData->setNdigits(TotpData::TOTPNULLOPT); pwdData->setTotpData(totpData.get()); rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_CREATE_PASSWORD_NODE); mApi->createPasswordNode(getFilePrefix().c_str(), pwdData.get(), getBaseHandle(), &rl); EXPECT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } } TEST_F(SdkTestPasswordManagerPassNodeCRUD, GetPassNodeByHandle) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Getting node by handle"; const std::unique_ptr retrievedPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(retrievedPwdNode, nullptr); ASSERT_EQ(retrievedPwdNode->getHandle(), newPwdNodeHandle); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateRenameNode) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); std::unique_ptr newPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(newPwdNode, nullptr); LOG_debug << logPre << "Renaming the node"; const char* newName = "SecondPwd"; ASSERT_EQ(API_OK, doRenameNode(0, newPwdNode.get(), newName)); LOG_debug << logPre << "Validating node new name and data"; newPwdNode.reset(mApi->getNodeByHandle(newPwdNodeHandle)); ASSERT_NE(nullptr, newPwdNode.get()); ASSERT_TRUE(newPwdNode->isPasswordNode()); ASSERT_STREQ(newName, newPwdNode->getName()); std::unique_ptr pwdData{newPwdNode->getPasswordData()}; EXPECT_THAT(pwdData.get(), PasswordNodeDataEquals(predefinedPwdDataOwned())); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateChangeJustPwdFromSameData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the password"; const auto pwdData = predefinedPwdData(); pwdData->setPassword("5678"); updatePwdNode(newPwdNodeHandle, pwdData.get()); LOG_debug << logPre << "Validating data"; validatePwdNodeData(newPwdNodeHandle, pwdData.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateChangeJustPwdToEmpty) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating with empty data"; const auto emptyData = emptyPwdData(); emptyData->setPassword(""); NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(newPwdNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateChangeJustNotesFromEmptyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the notes"; const auto pwdData = emptyPwdData(); pwdData->setNotes("Updated Notes"); updatePwdNode(newPwdNodeHandle, pwdData.get()); LOG_debug << logPre << "Validating data"; const auto pwdDataToCompare = predefinedPwdData(); pwdDataToCompare->setNotes(pwdData->notes()); validatePwdNodeData(newPwdNodeHandle, pwdDataToCompare.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateChangeJustURLFromEmtpyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the url"; const auto pwdData = emptyPwdData(); pwdData->setUrl("Updated url"); updatePwdNode(newPwdNodeHandle, pwdData.get()); LOG_debug << logPre << "Validating data"; const auto pwdDataToCompare = predefinedPwdData(); pwdDataToCompare->setUrl(pwdData->url()); validatePwdNodeData(newPwdNodeHandle, pwdDataToCompare.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateChangeJustUserNameFromEmtpyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the userName"; const auto pwdData = emptyPwdData(); pwdData->setUserName("Updated userName"); updatePwdNode(newPwdNodeHandle, pwdData.get()); LOG_debug << logPre << "Validating data"; const auto pwdDataToCompare = predefinedPwdData(); pwdDataToCompare->setUserName(pwdData->userName()); validatePwdNodeData(newPwdNodeHandle, pwdDataToCompare.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateErrorNoData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating with invalid data"; NiceMock rl; rl.setErrorExpectations(API_EARGS, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(newPwdNodeHandle, nullptr, &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateErrorEmptyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating with empty data"; const auto emptyData = emptyPwdData(); NiceMock rl; rl.setErrorExpectations(API_EARGS, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(newPwdNodeHandle, emptyData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, DeletePwdNode) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); std::unique_ptr retrievedPwdNode{mApi->getNodeByHandle(newPwdNodeHandle)}; ASSERT_NE(retrievedPwdNode, nullptr); LOG_debug << logPre << "Deleting the node"; ASSERT_EQ(API_OK, doDeleteNode(0, retrievedPwdNode.get())); retrievedPwdNode.reset(mApi->getNodeByHandle(newPwdNodeHandle)); ASSERT_EQ(nullptr, retrievedPwdNode.get()); } /** * @brief Ensures that TotpData must be valid (shared secret must be present) to add totp field on a * password node that had no totp information stored. */ TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataFromNullErrorMissingMandatoryData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node with no totp data"; auto pwdData = predefinedPwdData(); pwdData->setTotpData(nullptr); const auto newPwdNodeHandle = createPasswordNode({}, pwdData.get()); LOG_debug << logPre << "Preparing totp data with no number of digits"; pwdData = emptyPwdData(); const auto totpData = predefinedPwdTotpData(); totpData->setNdigits(TotpData::TOTPNULLOPT); pwdData->setTotpData(totpData.get()); LOG_debug << logPre << "Update node expecting an error"; NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(newPwdNodeHandle, pwdData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); } /** * @brief Same operation as UpdateTotpDataFromNullError but passing valid totp data */ TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataFromNullOk) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node with no totp data"; auto pwdData = predefinedPwdData(); pwdData->setTotpData(nullptr); const auto newPwdNodeHandle = createPasswordNode({}, pwdData.get()); LOG_debug << logPre << "Preparing valid totp data with no shared secret"; pwdData = emptyPwdData(); const auto totpData = predefinedPwdTotpData(); pwdData->setTotpData(totpData.get()); LOG_debug << logPre << "Update node"; ASSERT_NO_FATAL_FAILURE(updatePwdNode(newPwdNodeHandle, pwdData.get())); LOG_debug << logPre << "Validating data"; validatePwdNodeData(newPwdNodeHandle, predefinedPwdData().get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataNDigitsFromSameData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the Ndigits"; const auto pwdData = getCustomTotpData(predefinedPwdData(), [](auto& totpData) { totpData.setNdigits(9); }); updatePwdNode(newPwdNodeHandle, pwdData.get()); LOG_debug << logPre << "Validating data"; validatePwdNodeData(newPwdNodeHandle, pwdData.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataAlgorithmFromEmptyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the Ndigits"; const auto pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setHashAlgorithm(TotpData::HASH_ALGO_SHA512); }); ASSERT_NO_FATAL_FAILURE(updatePwdNode(newPwdNodeHandle, pwdData.get())); LOG_debug << logPre << "Validating data"; const auto pwdDataPred = getCustomTotpData(predefinedPwdData(), [](auto& totpData) { totpData.setHashAlgorithm(TotpData::HASH_ALGO_SHA512); }); validatePwdNodeData(newPwdNodeHandle, pwdDataPred.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataExptFromEmptyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the Expt"; const auto pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setExpirationTime(120); }); ASSERT_NO_FATAL_FAILURE(updatePwdNode(newPwdNodeHandle, pwdData.get())); LOG_debug << logPre << "Validating data"; const auto pwdDataPred = getCustomTotpData(predefinedPwdData(), [](auto& totpData) { totpData.setExpirationTime(120); }); validatePwdNodeData(newPwdNodeHandle, pwdDataPred.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataShseFromEmptyData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the Shared secret"; const auto pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setSharedSecret("3456"); }); ASSERT_NO_FATAL_FAILURE(updatePwdNode(newPwdNodeHandle, pwdData.get())); LOG_debug << logPre << "Validating data"; const auto pwdDataPred = getCustomTotpData(predefinedPwdData(), [](auto& totpData) { totpData.setSharedSecret("3456"); }); validatePwdNodeData(newPwdNodeHandle, pwdDataPred.get()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, UpdateTotpDataWithWrongValues) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Updating just the Shared secret"; auto pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setSharedSecret("1234"); }); auto expectFailureOnPwdDataUpdate = [this, &pwdData, &newPwdNodeHandle]() { NiceMock rl; rl.setErrorExpectations(API_EAPPKEY, _, MegaRequest::TYPE_UPDATE_PASSWORD_NODE); mApi->updatePasswordNode(newPwdNodeHandle, pwdData.get(), &rl); ASSERT_TRUE(rl.waitForFinishOrTimeout(MAX_TIMEOUT)); }; EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); LOG_debug << logPre << "#### Test1: Update Totpdata with invalid Shared secret ####"; pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setSharedSecret("5=34"); }); EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); LOG_debug << logPre << "#### Test2: Update Totpdata with invalid Ndigits ####"; pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setNdigits(40); }); EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); LOG_debug << logPre << "#### Test3: Update Totpdata with Ndigits equal Zero ####"; pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setNdigits(0); }); EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); LOG_debug << logPre << "#### Test4: Update Totpdata with expiration time equal Zero ####"; pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setExpirationTime(0); }); EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); LOG_debug << logPre << "#### Test4: Update Totpdata with invalid hash algorithm ####"; pwdData = getCustomTotpData(emptyPwdData(), [](auto& totpData) { totpData.setHashAlgorithm(100); }); EXPECT_NO_FATAL_FAILURE(expectFailureOnPwdDataUpdate()); } TEST_F(SdkTestPasswordManagerPassNodeCRUD, DeleteTotpData) { static const auto logPre = getLogPrefix(); LOG_debug << logPre << "Creating a node"; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); LOG_debug << logPre << "Remove Totp data"; std::unique_ptr totpData{TotpData::createRemovalInstance()}; const auto pwdData = emptyPwdData(); pwdData->setTotpData(totpData.get()); ASSERT_NO_FATAL_FAILURE(updatePwdNode(newPwdNodeHandle, pwdData.get())); const auto predPwdData = predefinedPwdData(); predPwdData->setTotpData(nullptr); validatePwdNodeData(newPwdNodeHandle, predPwdData.get()); } TEST(SdkTestPasswordManagerTotpValidation, ValidateTotpFields) { static const auto logPre = "SdkTestPasswordManagerTotpValidation::ValidateTotpFields: "; std::unique_ptr totpData{ TotpData::createInstance("abcd", 20, TotpData::HASH_ALGO_SHA256, 8)}; ASSERT_TRUE(totpData) << "Cannot create TotpData instance"; LOG_debug << logPre << "#### Test1 Validate Totp data with all valid fields"; std::unique_ptr val(totpData->getValidation()); ASSERT_TRUE(val) << "Cannot get TotpData validation"; EXPECT_TRUE(val->isValidForCreate()); EXPECT_TRUE(val->isValidForUpdate()); LOG_debug << logPre << "#### Test2 Validate Totp data with all the fields wrong"; totpData.reset(TotpData::createInstance("1234", 0, 100, 5)); val.reset(totpData->getValidation()); ASSERT_TRUE(val) << "Cannot get TotpData validation"; EXPECT_TRUE(!val->isValidForCreate()); EXPECT_TRUE(!val->isValidForUpdate()); EXPECT_TRUE(!val->sharedSecretValid()); EXPECT_TRUE(!val->algorithmValid()); EXPECT_TRUE(!val->nDigitsValid()); EXPECT_TRUE(!val->expirationTimeValid()); LOG_debug << logPre << "#### Test3 Validate Totp data with valid fields for update but not for creation"; totpData.reset(TotpData::createInstance(nullptr, 10, TotpData::TOTPNULLOPT, 6)); val.reset(totpData->getValidation()); ASSERT_TRUE(val) << "Cannot get TotpData validation"; EXPECT_TRUE(!val->isValidForCreate()); EXPECT_TRUE(val->isValidForUpdate()); EXPECT_TRUE(val->sharedSecretValid()); EXPECT_TRUE(val->algorithmValid()); EXPECT_TRUE(val->nDigitsValid()); EXPECT_TRUE(val->expirationTimeValid()); LOG_debug << logPre << "#### Test4 Validate Totp data with wrong NDigits"; totpData.reset(TotpData::createInstance("abcd", 20, TotpData::HASH_ALGO_SHA256, 11)); val.reset(totpData->getValidation()); ASSERT_TRUE(val) << "Cannot get TotpData validation"; EXPECT_TRUE(!val->isValidForCreate()); EXPECT_TRUE(!val->isValidForUpdate()); EXPECT_TRUE(val->sharedSecretValid()); EXPECT_TRUE(val->algorithmValid()); EXPECT_TRUE(val->expirationTimeValid()); EXPECT_TRUE(!val->nDigitsValid()); LOG_debug << logPre << "#### Test5 Validate Totp data with missing fields"; totpData.reset(TotpData::createInstance("abcd", TotpData::TOTPNULLOPT, TotpData::TOTPNULLOPT, TotpData::TOTPNULLOPT)); val.reset(totpData->getValidation()); ASSERT_TRUE(val) << "Cannot get TotpData validation"; EXPECT_TRUE(!val->isValidForCreate()); EXPECT_TRUE(val->isValidForUpdate()); EXPECT_TRUE(val->sharedSecretExist()); EXPECT_TRUE(val->sharedSecretValid()); EXPECT_TRUE(!val->algorithmExist()); EXPECT_TRUE(val->algorithmValid()); EXPECT_TRUE(!val->expirationTimeExist()); EXPECT_TRUE(val->expirationTimeValid()); EXPECT_TRUE(!val->nDigitsExist()); EXPECT_TRUE(val->nDigitsValid()); } class SdkTestPasswordManagerGenerateTotpFromMegaApi: public SdkTestPasswordManagerPassNodeCRUD {}; TEST_F(SdkTestPasswordManagerGenerateTotpFromMegaApi, Errors) { static const auto logPre = getLogPrefix(); const auto pwdData = predefinedPwdData(); pwdData->setTotpData(nullptr); const auto newPwdNodeHandle = createPasswordNode({}, pwdData.get()); ASSERT_NE(newPwdNodeHandle, UNDEF); { LOG_debug << logPre << "Undef handle"; const auto [e, tokenLife] = mApi->generateTotpTokenFromNode(UNDEF); EXPECT_EQ(e, API_EARGS); } { LOG_debug << logPre << "Unexisting node with the given handle"; const auto [e, tokenLife] = mApi->generateTotpTokenFromNode(newPwdNodeHandle + 1); EXPECT_EQ(e, API_ENOENT); } { LOG_debug << logPre << "Node with no totp data"; const auto [e, tokenLife] = mApi->generateTotpTokenFromNode(newPwdNodeHandle); EXPECT_EQ(e, API_EKEY); } } TEST_F(SdkTestPasswordManagerGenerateTotpFromMegaApi, Ok) { const auto generateToken = [totpData = predefinedPwdTotpData()]() { return totp::generateTOTP(totpData->sharedSecret(), static_cast(totpData->nDigits()), std::chrono::seconds{totpData->expirationTime()}, totp::HashAlgorithm::SHA256); }; const auto newPwdNodeHandle = createPasswordNode(); ASSERT_NE(newPwdNodeHandle, UNDEF); auto tokenLifeValid = generateToken(); const auto [e, tokenLife] = mApi->generateTotpTokenFromNode(newPwdNodeHandle); ASSERT_EQ(e, API_OK); if (tokenLifeValid.first != tokenLife.token) // we could have run out of time, refresh { const auto oldLifetime = tokenLifeValid.second; tokenLifeValid = generateToken(); // Preserve the lifetime to compare with the other one tokenLifeValid.second = oldLifetime; } EXPECT_EQ(tokenLifeValid.first, tokenLife.token); const auto timeDiff = std::abs(tokenLifeValid.second.count() - tokenLife.remainingLifeTimeSeconds); EXPECT_LE(timeDiff, 1) << "There mustn't be more than 1s difference between totp token generation"; } sdk-10.11.0/tests/integration/sdk_server_test_utils.cpp000066400000000000000000000023271516266226600232720ustar00rootroot00000000000000#include "sdk_server_test_utils.h" #include "integration_test_utils.h" unique_ptr SdkServerTest::createFolder(unsigned int apiIndex, const std::string& name, MegaNode* parent) { if (parent == nullptr) { return nullptr; } MegaHandle handle = SdkTest::createFolder(apiIndex, name.c_str(), parent); if (handle == UNDEF) { return nullptr; } return std::unique_ptr(megaApi[apiIndex]->getNodeByHandle(handle)); } unique_ptr SdkServerTest::createFolder(unsigned int apiIndex, const std::string& name) { auto rootNode = unique_ptr(megaApi[apiIndex]->getRootNode()); return createFolder(apiIndex, name, rootNode.get()); } unique_ptr SdkServerTest::uploadFile(unsigned int apiIndex, const std::string& name, const std::string& contents, MegaNode* parent) { deleteFile(name); sdk_test::LocalTempFile f(name, contents); return sdk_test::uploadFile(megaApi[apiIndex].get(), name, parent); } sdk-10.11.0/tests/integration/sdk_server_test_utils.h000066400000000000000000000011441516266226600227330ustar00rootroot00000000000000#pragma once #include "SdkTest_test.h" class SdkServerTest: public SdkTest { protected: unique_ptr createFolder(unsigned int apiIndex, const std::string& name, MegaNode* parent); unique_ptr createFolder(unsigned int apiIndex, const std::string& name); unique_ptr uploadFile(unsigned int apiIndex, const std::string& name, const std::string& contents, MegaNode* parent = nullptr); };sdk-10.11.0/tests/integration/sdk_test_backup_upload_operations_test.cpp000066400000000000000000001005531516266226600266570ustar00rootroot00000000000000/** * @file sdk_test_backup_upload_operations_test.cpp * @brief This file defines a test fixture that involves backup syncs in terms of files upload. */ #ifdef ENABLE_SYNC #include "backup_test_utils.h" using namespace sdk_test; using namespace testing; /** * @class SdkTestBackupUploadsOperations * @brief Test fixture for validating backup syncs in terms of files upload. */ class SdkTestBackupUploadsOperations: public SdkTestBackup { public: static constexpr auto COMMON_TIMEOUT = 3min; std::unique_ptr mFsAccess; /** * @brief Sets the cleanup function to be executed during TearDown. * * If a custom cleanup function is provided, it will be used. * Otherwise, a default one will be set. * * @example: * - example1 (default cleanupFunction): * auto cleanup = setCleanupFunction(); * - example2 (custom cleanupFunction): * auto cleanup = setCleanupFunction([this](){ * // custom cleanup function code * }); * * @note: is mandatory calling this method at the beginning of each test of this file, otherwise * test will fail at teardown. The reason behind is to enforce setting an appropriate cleanup * function for each test. * * @param customCleanupFunction Optional custom cleanup function. * @return The cleanup function that was set. */ std::unique_ptr setCleanupFunction(std::function customCleanupFunction = nullptr) { mCleanupFunctionSet = true; if (customCleanupFunction) { return std::make_unique(customCleanupFunction); } else { return std::make_unique( [this]() { cleanDefaultListeners(); }); } } void cleanDefaultListeners() { removeBackupSync(); if (mMtl) { megaApi[0]->removeListener(mMtl.get()); mMtl.reset(); } if (mMslStats) { megaApi[0]->removeListener(mMslStats.get()); mMslStats.reset(); } if (mMslFiles) { megaApi[0]->removeListener(mMslFiles.get()); mMslFiles.reset(); } } /** * @brief Creates a local file and waits until it is backed up. * @param localFilePathAbs Absolute path to the local file to be created. * @param contents The file contents to be written. * @param customMtime Optional custom modification time. * @param expectFullUpload If true, expects a transfer to occur (onTransferStart/Finish). * If false, expects only sync state change (file may be cloned from existing node). * Default is true for backward compatibility. * @return Pair (success, shared pointer to the created LocalTempFile). */ std::pair> createLocalFileAndWaitForSync(const fs::path& localFilePathAbs, const std::string_view contents, std::optional customMtime, bool expectFullUpload = true); /** * @brief Moves deconfigured backup nodes into a cloud folder. */ void moveDeconfiguredBackupNodesToCloud(); /** * @brief Resets local variables that tracks when backup sync is up-to-date. */ void resetOnSyncStatsUpdated(); /** * @brief Waits until the backup sync is up-to-date state. * @return True if sync was up to date within the timeout, false otherwise. */ bool waitForBackupSyncUpToDate() const; /** * @brief Confirms that local and cloud models are identical. */ void confirmModels() const; std::shared_ptr addSyncListenerTracker(const std::string& s) { TestMutexGuard g(trackerMutex); return mSyncListenerTrackers.add(s); } std::shared_ptr getSyncListenerTrackerByPath(const std::string& s) { TestMutexGuard g(trackerMutex); return mSyncListenerTrackers.getByPath(s); } std::shared_ptr addTransferListenerTracker(const std::string& s) { TestMutexGuard g(trackerMutex); return mTransferListenerTrackers.add(s); } std::shared_ptr getTransferListenerTrackerByPath(const std::string& s) { TestMutexGuard g(trackerMutex); return mTransferListenerTrackers.getByPath(s); } protected: void SetUp() override; void TearDown() override; /** * @brief Creates the `archive` destination directory in the cloud used to store deconfigured * backup nodes. */ void createArchiveDestinationFolder(); /** * @brief Returns the names of the first-level children in the local backup folder. * @note Hidden files and DEBRISFOLDER are ignored * @return Vector with the names of local files and directories. */ std::vector getLocalFirstLevelChildrenNames() const; /** * @brief Gets the handle of the `archive` destination folder in the cloud. * @return Handle of the `archive` destination folder. */ MegaHandle getArchiveDestinationFolderHandle() const; /** * @brief Gets the handle of the backup root folder in the cloud. * @return Handle of the backup root folder. */ MegaHandle getBackupRootHandle() const; /** * @brief Recursively checks that the local and cloud models match. * @param parentHandle handle of the cloud parent node. * @param localPath Local relative path corresponding to the parent node. * @return True if both models match, false otherwise. */ bool checkSyncRecursively(const MegaHandle parentHandle, std::optional localPath) const; shared_ptr createLocalFile(const fs::path& filePath, const std::string_view contents, std::optional customMtime); private: mutable std::recursive_timed_mutex trackerMutex; using TestMutexGuard = std::unique_lock; std::unique_ptr> mMtl; std::unique_ptr> mMslStats; std::unique_ptr> mMslFiles; SyncItemTrackerManager mSyncListenerTrackers; SyncItemTrackerManager mTransferListenerTrackers; MegaHandle mBackupRootHandle{INVALID_HANDLE}; MegaHandle mCloudArchiveBackupFolderHandle{INVALID_HANDLE}; const fs::path mCloudArchiveBackupFolderName{"BackupArchive"}; std::atomic mIsUpToDate{false}; std::shared_ptr> mSyncUpToDatePms; std::unique_ptr> mSyncFut; bool mCleanupFunctionSet{false}; }; // class SdkTestBackupUploadsOperations std::pair> SdkTestBackupUploadsOperations::createLocalFileAndWaitForSync( const fs::path& localFilePathAbs, const std::string_view contents, std::optional customMtime, bool expectFullUpload) { if (!mMtl) { LOG_err << "createLocalFileAndWaitForSync: invalid transfer listener"; return {false, nullptr}; } if (!mMslFiles) { LOG_err << "createLocalFileAndWaitForSync: invalid sync listener"; return {false, nullptr}; } // Always add transfer tracker to verify behavior (transfer vs clone) auto tt = addTransferListenerTracker(localFilePathAbs.string()); if (!tt) { LOG_err << "Cannot add TransferListenerTracker for: " << localFilePathAbs.string(); return {false, nullptr}; } auto st = addSyncListenerTracker(localFilePathAbs.string()); if (!st) { LOG_err << "Cannot add SyncListenerTracker for: " << localFilePathAbs.string(); return {false, nullptr}; } auto localFile = createLocalFile(localFilePathAbs, contents, customMtime); // Wait for sync state change (always expected) auto [stFutStatus, stErrCode] = st->waitForCompletion(COMMON_TIMEOUT); if (stFutStatus != std::future_status::ready) { LOG_err << "Sync state change not received for: " << localFilePathAbs.u8string(); return {false, nullptr}; } if (stErrCode != API_OK) { LOG_err << "Sync failed for: " << localFilePathAbs.u8string(); return {false, nullptr}; } // Wait for transfer with appropriate timeout: // - Full upload: wait with COMMON_TIMEOUT for completion // - Clone: wait briefly (30s) and expect timeout (no transfer should occur) const auto transferTimeout = expectFullUpload ? COMMON_TIMEOUT : std::chrono::seconds(30); auto [ttFutStatus, ttErrCode] = tt->waitForCompletion(transferTimeout); const auto expectedTransferStatus = expectFullUpload ? std::future_status::ready : std::future_status::timeout; if (ttFutStatus != expectedTransferStatus) { LOG_err << "Unexpected transfer status for: " << localFilePathAbs.u8string() << " [expectFullUpload: " << expectFullUpload << "]"; return {false, nullptr}; } // Verify transfer start count matches expectation const int expectedTransferStartCount = expectFullUpload ? 1 : 0; if (tt->transferStartCount.load() != expectedTransferStartCount) { LOG_err << "Transfer started count mismatch for: " << localFilePathAbs.u8string() << " [expected: " << expectedTransferStartCount << ", actual: " << tt->transferStartCount.load() << "]"; return {false, nullptr}; } // Verify transfer succeeded if we expected a full upload if (expectFullUpload && ttErrCode != API_OK) { LOG_err << "Transfer failed for: " << localFilePathAbs.u8string(); return {false, nullptr}; } return {true, localFile}; } void SdkTestBackupUploadsOperations::moveDeconfiguredBackupNodesToCloud() { NiceMock reqListener{megaApi[0].get()}; reqListener.setErrorExpectations(API_OK); megaApi[0]->moveOrRemoveDeconfiguredBackupNodes(getBackupRootHandle(), getArchiveDestinationFolderHandle(), &reqListener); ASSERT_TRUE(reqListener.waitForFinishOrTimeout(MAX_TIMEOUT)) << ""; } void SdkTestBackupUploadsOperations::resetOnSyncStatsUpdated() { mSyncUpToDatePms.reset(new std::promise()); mSyncFut.reset(new std::future(mSyncUpToDatePms->get_future())); mIsUpToDate = false; } bool SdkTestBackupUploadsOperations::waitForBackupSyncUpToDate() const { if (!mMslStats) return false; return mSyncFut->wait_for(COMMON_TIMEOUT) == std::future_status::ready; } void SdkTestBackupUploadsOperations::confirmModels() const { const auto areLocalAndCloudSyncedExhaustive = [this]() -> bool { return checkSyncRecursively(getBackupRootHandle(), nullopt); }; ASSERT_TRUE(waitFor(areLocalAndCloudSyncedExhaustive, COMMON_TIMEOUT, 10s)); } void SdkTestBackupUploadsOperations::SetUp() { SdkTestBackup::SetUp(); mFsAccess = std::make_unique(); ASSERT_NO_FATAL_FAILURE(createBackupSync()); ASSERT_NO_FATAL_FAILURE(createArchiveDestinationFolder()); const std::unique_ptr sync{megaApi[0]->getSyncByBackupId(getBackupId())}; ASSERT_TRUE(sync); mBackupRootHandle = sync->getMegaHandle(); // add transfer listener mMtl.reset(new NiceMock(megaApi[0].get())); EXPECT_CALL(*mMtl, onTransferStart) .WillRepeatedly( [this](::mega::MegaApi*, ::mega::MegaTransfer* t) { if (!t || !t->getPath()) { return; } auto element = getTransferListenerTrackerByPath(t->getPath()); if (!element) return; ASSERT_EQ(++element->transferStartCount, 1) << "Unexpected times onTransferStart has been called: " << t->getPath(); }); EXPECT_CALL(*mMtl, onTransferFinish) .WillRepeatedly( [this](::mega::MegaApi*, ::mega::MegaTransfer* t, ::mega::MegaError* e) { if (!t || !t->getPath()) { return; } auto element = getTransferListenerTrackerByPath(t->getPath()); if (!element || !e) return; ASSERT_TRUE(!element->getActionCompleted()) << "onTransferFinish has been previously received: " << t->getPath(); element->setActionCompleted(); element->setActionCompletedPms(e->getErrorCode()); }); megaApi[0]->addListener(mMtl.get()); // add sync listener and add EXPECT(S) mMslStats.reset(new NiceMock(megaApi[0].get())); EXPECT_CALL(*mMslStats.get(), onSyncStatsUpdated(_, _)) .WillRepeatedly( [this](MegaApi*, MegaSyncStats* stats) { if (stats->getBackupId() == getBackupId() && stats->getUploadCount() == 0 && !stats->isScanning() && !stats->isSyncing() && mSyncUpToDatePms && !mIsUpToDate) { mIsUpToDate = true; mSyncUpToDatePms->set_value(); } }); megaApi[0]->addListener(mMslStats.get()); mMslFiles.reset(new NiceMock(megaApi[0].get())); EXPECT_CALL(*mMslFiles.get(), onSyncFileStateChanged(_, _, _, _)) .WillRepeatedly( [this](MegaApi*, MegaSync* sync, std::string* localPath, int newState) { if (sync && sync->getBackupId() == getBackupId() && newState == MegaApi::STATE_SYNCED && localPath) { auto element = getSyncListenerTrackerByPath(*localPath); if (!element || element->getActionCompleted()) return; element->setActionCompleted(); element->setActionCompletedPms(API_OK); } }); megaApi[0]->addListener(mMslFiles.get()); } void SdkTestBackupUploadsOperations::TearDown() { ASSERT_TRUE(mCleanupFunctionSet) << getLogPrefix() << "(TearDown). cleanupfunction has not been properly set by " "calling `setCleanupFunction()`."; ASSERT_TRUE(!mMtl) << getLogPrefix() << "(TearDown). Transfer listener has not been unregistered yet"; ASSERT_TRUE(!mMslStats) << getLogPrefix() << "(TearDown). Sync listener has not been unregistered yet"; removeBackupSync(); SdkTestBackup::TearDown(); } void SdkTestBackupUploadsOperations::createArchiveDestinationFolder() { unique_ptr rootnode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootnode) << "setupDestinationDirectory: Account root node not available."; mCloudArchiveBackupFolderHandle = createFolder(0, path_u8string(mCloudArchiveBackupFolderName).c_str(), rootnode.get()); ASSERT_NE(mCloudArchiveBackupFolderHandle, INVALID_HANDLE) << "setupDestinationDirectory: Invalid destination folder handle"; } std::vector SdkTestBackupUploadsOperations::getLocalFirstLevelChildrenNames() const { fs::path localFolderPath = getLocalFolderPath(); return sdk_test::getLocalFirstChildrenNames_if(localFolderPath, [](const std::string& name) { return name.front() != '.' && name != DEBRISFOLDER; }); } MegaHandle SdkTestBackupUploadsOperations::getArchiveDestinationFolderHandle() const { return mCloudArchiveBackupFolderHandle; } MegaHandle SdkTestBackupUploadsOperations::getBackupRootHandle() const { return mBackupRootHandle; } bool SdkTestBackupUploadsOperations::checkSyncRecursively( const MegaHandle parentHandle, std::optional localPath) const { auto [childrenCloudNames, childrenNodeList] = getCloudFirstChildren(megaApi[0].get(), parentHandle); if (!childrenCloudNames.has_value() || !childrenNodeList) { return false; } const auto localChildrenNames = getLocalFirstLevelChildrenNames(); if (!Value(localChildrenNames, UnorderedElementsAreArray(childrenCloudNames.value()))) { return false; } for (int i = 0; i < childrenNodeList->size(); ++i) { auto childNode = childrenNodeList->get(i); if (!childNode) { return false; } const std::string childLocalPath = !localPath.has_value() ? childNode->getName() : localPath.value() + "/" + childNode->getName(); if (childNode->isFolder() && !checkSyncRecursively(childNode->getHandle(), childLocalPath)) { return false; } } return true; } shared_ptr SdkTestBackupUploadsOperations::createLocalFile(const fs::path& filePath, const std::string_view contents, std::optional customMtime) { return std::make_shared(filePath, contents, customMtime); } /** * @test SdkTestBackupUploadsOperations.BasicTest * * 1. Create multiple local file in the backup directory and ensure it's synced. * 2. Suspend the backup sync and move backup nodes to the cloud. * 3. Confirm that local and remote models match. */ TEST_F(SdkTestBackupUploadsOperations, BasicTest) { static const auto logPre{getLogPrefix()}; LOG_verbose << logPre << "#### Test body started ####"; // Add cleanup function to unregister listeners as soon as test fail/finish const auto cleanup = setCleanupFunction(); // Reset MockSyncListener related promise/future resetOnSyncStatsUpdated(); auto localBasePath{fs::absolute(getLocalFolderPath())}; LOG_debug << logPre << "#### TC1 Creating local file `file1` in Backup dir ####"; auto [res1, localFile1] = createLocalFileAndWaitForSync(localBasePath / "file1", "abcde", fs::file_time_type::clock::now()); ASSERT_TRUE(res1) << "Cannot create local file `file1`"; LOG_debug << "#### TC2 wait until all files (in Backup folder) have been synced ####"; ASSERT_TRUE(waitForBackupSyncUpToDate()); LOG_debug << logPre << "#### TC3 Ensure local and cloud drive structures matches ####"; ASSERT_NO_FATAL_FAILURE(confirmModels()); LOG_verbose << logPre << "#### Test finished ####"; } /** * @test SdkTestBackupUploadsOperations.NodesRemoteCopyUponResumingBackup * * 1. Create multiple local files in the backup directory and ensure they are synced. * 2. Suspend the backup sync and move backup nodes to the cloud. * 3. Remove the suspended sync, then set up the backup sync again. * 4. Resume backup sync and ensure files are synced (remote copy must be done) * 5. Confirm that local and remote models match. */ TEST_F(SdkTestBackupUploadsOperations, NodesRemoteCopyUponResumingBackup) { std::shared_ptr> auxMtl; static const auto logPre{getLogPrefix()}; LOG_verbose << logPre << "#### Test body started ####"; // Add cleanup function to unregister listeners as soon as test fail/finish const auto cleanup = setCleanupFunction( [this, auxMtl]() { cleanDefaultListeners(); if (auxMtl) { megaApi[0]->removeListener(auxMtl.get()); } }); constexpr unsigned numFiles{3}; auto localBasePath{fs::absolute(getLocalFolderPath())}; std::vector> localFiles; auto mtime = fs::file_time_type::clock::now(); for (unsigned i = 1; i <= numFiles; ++i) { auto auxMtime = mtime + std::chrono::seconds(MIN_ALLOW_MTIME_DIFFERENCE * i); std::string filename = "file" + std::to_string(i); LOG_debug << logPre << "#### TC " << std::to_string(i) << " Creating local file `" << filename << "` in Backup dir ####"; // First file triggers a full upload. Subsequent files with same content // but different mtime will be cloned (no transfer expected). const bool expectFullUpload = (i == 1); auto [res, localFile] = createLocalFileAndWaitForSync(localBasePath / filename, "abcde", auxMtime, expectFullUpload); ASSERT_TRUE(res) << "Cannot create local file `" << filename << "`"; localFiles.push_back(localFile); } LOG_debug << logPre << "#### TC4 suspending sync ####"; ASSERT_NO_FATAL_FAILURE(suspendBackupSync()); LOG_debug << logPre << "#### TC5 moving backup nodes to Cloud ####"; ASSERT_NO_FATAL_FAILURE(moveDeconfiguredBackupNodesToCloud()); LOG_debug << logPre << "#### TC6 removing suspending ####"; removeBackupSync(); LOG_debug << logPre << "#### TC7 setup sync (again) ####"; createBackupSync(); resetOnSyncStatsUpdated(); // Clone Put nodes (no transfer is created) auxMtl.reset(new NiceMock(megaApi[0].get())); EXPECT_CALL(*auxMtl.get(), onTransferStart).Times(0); EXPECT_CALL(*auxMtl.get(), onTransferFinish).Times(0); megaApi[0]->addListener(auxMtl.get()); LOG_debug << logPre << "#### TC8 resuming sync ####"; ASSERT_NO_FATAL_FAILURE(resumeBackupSync()); LOG_debug << logPre << "#### TC9 wait until all files (in Backup folder) have been synced ####"; ASSERT_TRUE(waitForBackupSyncUpToDate()); LOG_debug << logPre << "#### TC10 ensure local and cloud drive models match ####"; ASSERT_NO_FATAL_FAILURE(confirmModels()); LOG_verbose << logPre << "#### Test finished ####"; } /** * @test SdkTestBackupUploadsOperations.UpdateNodeMtime * * 1. Create a local file in the backup directory and ensure it is synced. * 2. Wait until the backup sync is up to date. * 3. Update mtime to local file and wait for notification that confirms change. * 4. Confirm that local and remote (cloud) models match. */ TEST_F(SdkTestBackupUploadsOperations, UpdateNodeMtime) { static const auto logPre{getLogPrefix()}; LOG_verbose << logPre << "#### Test body started ####"; // Add cleanup function to unregister listeners as soon as test fail/finish const auto cleanup = setCleanupFunction(); // Reset MockSyncListener related promise/future resetOnSyncStatsUpdated(); auto localBasePath{fs::absolute(getLocalFolderPath())}; LOG_debug << logPre << "#### TC1 Creating local file `file1` in Backup dir ####"; auto [res1, localFile1] = createLocalFileAndWaitForSync(localBasePath / "file1", "abcde", fs::file_time_type::clock::now()); ASSERT_TRUE(res1) << "Cannot create local file `file1`"; LOG_debug << "#### TC2 wait until all files (in Backup folder) have been synced ####"; ASSERT_TRUE(waitForBackupSyncUpToDate()); resetOnSyncStatsUpdated(); std::unique_ptr backupSync(megaApi[0]->getSyncByBackupId(getBackupId())); ASSERT_TRUE(backupSync) << "Cannot get backup sync"; std::unique_ptr backupNode(megaApi[0]->getNodeByHandle(backupSync->getMegaHandle())); ASSERT_TRUE(backupNode) << "Cannot get backup sync"; std::unique_ptr fileNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "file1", FILENODE)); ASSERT_TRUE(fileNode) << "Cannot get file node"; bool mTimeChangeRecv{false}; mApi[0].mOnNodesUpdateCompletion = [&mTimeChangeRecv, oldMtime = fileNode->getModificationTime(), targetNodeHandle = fileNode->getHandle()](size_t, MegaNodeList* nodes) { ASSERT_TRUE(nodes) << "Invalid meganode list received"; for (int i = 0; i < nodes->size(); ++i) { MegaNode* n = nodes->get(i); if (n && n->getHandle() == targetNodeHandle && n->hasChanged(static_cast(MegaNode::CHANGE_TYPE_ATTRIBUTES)) && oldMtime != n->getModificationTime()) { mTimeChangeRecv = true; } } }; LOG_debug << logPre << "#### TC3 Update mtime to local file ####"; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(localBasePath / "file1")), m_time(nullptr)); ASSERT_TRUE(waitForResponse(&mTimeChangeRecv)) << "No mtime change received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // important to reset LOG_debug << logPre << "#### TC4 Ensure local and cloud drive structures matches ####"; ASSERT_NO_FATAL_FAILURE(confirmModels()); LOG_verbose << logPre << "#### Test finished ####"; } /** * @test SdkTestBackupUploadsOperations.getnodesByFingerprintNoMtime * * 1. Create '3' local files in the backup directory and ensure they are synced. * 2. Validate `getNodesByFingerprint` and `getNodesByFingerprintIgnoringMtime` results * 3. Modify the mtime of local file (idx_0) setting mtime of local file (idx_2) and wait for sync. * 4. Modify the mtime of local file (idx_1) setting mtime of local file (idx_2) and wait for sync. * 5. Validate `getNodesByFingerprint` and `getNodesByFingerprintIgnoringMtime` results * 6. Confirm that local and remote (cloud) models match. */ TEST_F(SdkTestBackupUploadsOperations, getnodesByFingerprintNoMtime) { static const auto logPre{getLogPrefix()}; LOG_verbose << logPre << "#### Test body started ####"; // Add cleanup function to unregister listeners as soon as test fail/finish const auto cleanup = setCleanupFunction(); std::unique_ptr backupSync(megaApi[0]->getSyncByBackupId(getBackupId())); ASSERT_TRUE(backupSync) << "Cannot get backup sync"; std::unique_ptr backupNode(megaApi[0]->getNodeByHandle(backupSync->getMegaHandle())); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; constexpr unsigned numFiles{3}; auto localBasePath{fs::absolute(getLocalFolderPath())}; std::vector, fs::file_time_type>> localFiles; std::vector filenames; LOG_debug << logPre << "#### TC1: create (" << numFiles << ") local files and wait until back up has been completed ####"; auto mtime{fs::file_time_type::clock::now()}; for (unsigned i = 1; i <= numFiles; ++i) { const auto auxMtime{mtime + std::chrono::seconds(MIN_ALLOW_MTIME_DIFFERENCE * i)}; std::string fn = "file" + std::to_string(i); filenames.emplace_back(fn); LOG_debug << logPre << "#### TC1." << std::to_string(i) << " Creating local file `" << fn << "` in Backup dir ####"; std::this_thread::sleep_for(std::chrono::milliseconds(50)); // First file triggers a full upload. Subsequent files with same content // but different mtime will be cloned (no transfer expected). const bool expectFullUpload = (i == 1); auto [res, localFile] = createLocalFileAndWaitForSync(localBasePath / fn, "abcde", auxMtime, expectFullUpload); ASSERT_TRUE(res) << "Cannot sync local file `" << fn << "`"; localFiles.push_back({localFile, auxMtime}); } LOG_debug << logPre << "#### TC2: getNodesByFingerprint with and without mtime ####"; std::unique_ptr nl; std::vector> nodes; nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[0].c_str(), FILENODE)); nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[1].c_str(), FILENODE)); nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[2].c_str(), FILENODE)); for (size_t i = 0; i < nodes.size(); ++i) { auto& n = nodes.at(i); ASSERT_TRUE(n) << "Invalid node with index(" << std::to_string(i) << ")"; ASSERT_TRUE(n->getFingerprint()) << "Invalid fingerprint for node(" << toNodeHandle(n->getHandle()) << ")"; auto auxfp = n->getFingerprint(); nl.reset(megaApi[0]->getNodesByFingerprint(auxfp)); ASSERT_EQ(nl->size(), 1) << "TC2.1(" << std::to_string(i) << "): getNodesByFingerprint(" << auxfp << ") Unexpected node count"; nl.reset(megaApi[0]->getNodesByFingerprintIgnoringMtime(n->getFingerprint())); ASSERT_EQ(nl->size(), nodes.size()) << "TC2.2(" << std::to_string(i) << "): getNodesByFingerprintIgnoringMtime(" << auxfp << ") Unexpected node count"; } auto updateNodeMtime = [this](MegaHandle nodeHandle, const LocalPath& path, int64_t oldMtime, int64_t newMtime) { bool mTimeChangeRecv{false}; mApi[0].mOnNodesUpdateCompletion = [&mTimeChangeRecv, oldMtime, nodeHandle](size_t, MegaNodeList* nodes) { ASSERT_TRUE(nodes) << "Invalid meganode list received"; for (int i = 0; i < nodes->size(); ++i) { MegaNode* n = nodes->get(i); if (n && n->getHandle() == nodeHandle && n->hasChanged(static_cast(MegaNode::CHANGE_TYPE_ATTRIBUTES)) && oldMtime != n->getModificationTime()) { mTimeChangeRecv = true; } } }; mFsAccess->setmtimelocal(path, newMtime); ASSERT_TRUE(waitForResponse(&mTimeChangeRecv)) << "No mtime change received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // important to reset }; LOG_debug << logPre << "#### TC3 update localNode (idx_0) mtime (with mtime of idx_2) and wait for sync ####"; auto h = nodes.at(0)->getHandle(); auto path = LocalPath::fromAbsolutePath(path_u8string(localFiles.at(0).first->getPath())); auto oldMtime = nodes.at(0)->getModificationTime(); auto newMtime = nodes.at(2)->getModificationTime(); updateNodeMtime(h, path, oldMtime, newMtime); LOG_debug << logPre << "#### TC4 update localNode (idx_1) mtime (with mtime of idx_2) and wait for sync ####"; h = nodes.at(1)->getHandle(); path = LocalPath::fromAbsolutePath(path_u8string(localFiles.at(1).first->getPath())); oldMtime = nodes.at(1)->getModificationTime(); newMtime = nodes.at(2)->getModificationTime(); updateNodeMtime(h, path, oldMtime, newMtime); LOG_debug << logPre << "#### TC5: getNodesByFingerprint with and without mtime (Now 3 nodes should have " "same mtime) ####"; nodes.clear(); nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[0].c_str(), FILENODE)); nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[1].c_str(), FILENODE)); nodes.emplace_back( megaApi[0]->getChildNodeOfType(backupNode.get(), filenames[2].c_str(), FILENODE)); for (auto& n: nodes) { nl.reset(megaApi[0]->getNodesByFingerprint(n->getFingerprint())); ASSERT_EQ(nl->size(), nodes.size()) << "(getNodesByFingerprint) Unexpected node count by FP1"; nl.reset(megaApi[0]->getNodesByFingerprintIgnoringMtime(n->getFingerprint())); ASSERT_EQ(nl->size(), nodes.size()) << "(getNodesByFingerprintIgnoringMtime) Unexpected node count by FP1"; } LOG_debug << logPre << "#### TC4 Ensure local and cloud drive structures matches ####"; ASSERT_NO_FATAL_FAILURE(confirmModels()); LOG_verbose << logPre << "#### Test finished ####"; } #endif sdk-10.11.0/tests/integration/sdk_test_file_path.cpp000066400000000000000000000166521516266226600225050ustar00rootroot00000000000000/** * @file sdk_test_file_path.cpp * @brief This file defines tests related to path handling functions */ #include "sdk_test_utils.h" #include "SdkTestNodesSetUp.h" #include #include using namespace sdk_test; /** * @class SdkTestPath * @brief Test suite for path handling functions * */ class SdkTestPath: public SdkTestNodesSetUp { public: const std::vector& getElements() const override { static const std::vector testNodes{ FileNodeInfo("rootTestFile"), DirNodeInfo("dir1") .addChild(FileNodeInfo("testFile1")) #if !defined(WIN32) // Windows does not allow ':' character in file names .addChild(FileNodeInfo("testFile1:")) .addChild(FileNodeInfo("test:File1")) .addChild(FileNodeInfo(":testFile1")), FileNodeInfo("rootTestFile:"), FileNodeInfo("rootTest:File"), FileNodeInfo(":rootTestFile"), DirNodeInfo("dir2:") .addChild(DirNodeInfo("dir3:").addChild(FileNodeInfo("testFile3:"))) .addChild(FileNodeInfo("testFile2:")) .addChild(FileNodeInfo("test:File2")) .addChild(FileNodeInfo(":testFile2")) .addChild(FileNodeInfo("testFile2")), #endif }; return testNodes; } const std::string& getRootTestDir() const override { static const std::string dirName{"SDK_TEST_PATH"}; return dirName; } const std::vector getAllNodesHandles() { std::vector result; std::function collectHandles = [&](MegaNode* node) { if (!node) return; result.push_back(node->getHandle()); std::unique_ptr children(megaApi[0].get()->getChildren(node)); if (children) { for (int i = 0; i < children->size(); ++i) { collectHandles(children->get(i)); } } }; collectHandles(getRootTestDirectory()); return result; } }; /** * @brief SdkTestPath.GetNodeByPathResolvesPathFromGetNodePath * * Verifies that a node retrieved by handle can return its path using getNodePath * and then resolved back to the original handle using getNodeByPath. * * Steps for each handle defined in the suite: * 1. Get a node by handle. * 2. Get the node path by using getNodePath. * 3. Escape colons in the path (required to use getNodeByPath when the paths has colons). * 4. Use getNodeByPath to resolve the escaped path. * 5. Confirm that the resolved node has the same handle as the original. */ TEST_F(SdkTestPath, GetNodeByPathResolvesPathFromGetNodePath) { auto handles = getAllNodesHandles(); for (auto handle: handles) { std::unique_ptr node(megaApi[0]->getNodeByHandle(handle)); ASSERT_NE(node, nullptr) << "Failed to retrieve node by handle."; auto path{megaApi[0]->getNodePath(node.get())}; ASSERT_NE(path, nullptr) << "Failed to retrieve node by node handle."; auto escapedPath{std::regex_replace(path, std::regex(":"), "\\:")}; std::unique_ptr fromPath(megaApi[0]->getNodeByPath(escapedPath.c_str())); ASSERT_NE(fromPath, nullptr) << "Failed to retrieve node by path."; EXPECT_EQ(fromPath->getHandle(), handle) << escapedPath; } } /** * @brief SdkTestPath.GetNodeByPathResolvesPathFromGetNodePathByNodeHandle * * Verifies that a path obtained from a handle using getNodePathByNodeHandle * can be resolved back to the original handle using getNodeByPath. * * Steps for each handle defined in the suite: * 1. Get the node path by using getNodePathByNodeHandle. * 2. Escape colons in the path (required to use getNodeByPath when the path has colons). * 3. Use getNodeByPath to resolve the escaped path. * 4. Confirm that the resolved node has the same handle as the original. */ TEST_F(SdkTestPath, GetNodeByPathResolvesPathFromGetNodePathByNodeHandle) { auto handles = getAllNodesHandles(); for (auto handle: handles) { auto path{megaApi[0]->getNodePathByNodeHandle(handle)}; ASSERT_NE(path, nullptr) << "Failed to retrieve node by handle."; auto escapedPath{std::regex_replace(path, std::regex(":"), "\\:")}; std::unique_ptr fromPath(megaApi[0]->getNodeByPath(escapedPath.c_str())); ASSERT_NE(fromPath, nullptr) << "Failed to retrieve node by path."; EXPECT_EQ(fromPath->getHandle(), handle) << escapedPath; } } /** * @brief SdkTestPath.GetNodeByPathOfTypeResolvesPathFromGetNodePath * * Verifies that a node retrieved by handle can return its path using getNodePath * and then be resolved back to the original handle using getNodeByPathOfType. * * Steps for each handle defined in the suite: * 1. Get a node by handle. * 2. Get the node path by using getNodePath. * 3. Escape colons in the path (required to use getNodeByPathOfType when the path has colons). * 4. Use getNodeByPathOfType to resolve the escaped path, providing the node's type. * 5. Confirm that the resolved node has the same handle as the original. */ TEST_F(SdkTestPath, GetNodeByPathOfTypeResolvesPathFromGetNodePath) { auto handles = getAllNodesHandles(); for (auto handle: handles) { std::unique_ptr node(megaApi[0]->getNodeByHandle(handle)); ASSERT_NE(node, nullptr) << "Failed to retrieve node by handle."; auto path{megaApi[0]->getNodePath(node.get())}; ASSERT_NE(path, nullptr) << "Failed to retrieve node by node handle."; auto escapedPath{std::regex_replace(path, std::regex(":"), "\\:")}; std::unique_ptr fromPath( megaApi[0]->getNodeByPathOfType(escapedPath.c_str(), nullptr, node->getType())); ASSERT_NE(fromPath, nullptr) << "Failed to retrieve node by path and type."; EXPECT_EQ(fromPath->getHandle(), handle) << escapedPath; } } /** * @brief SdkTestPath.GetNodeByPathOfTypeResolvesPathFromGetNodePathByNodeHandle * * Verifies that a path obtained from a handle using getNodePathByNodeHandle * can be resolved back to the original handle using getNodeByPathOfType. * * Steps for each handle defined in the suite: * 1. Get a node by handle (to retrieve the type). * 2. Get the node path by using getNodePathByNodeHandle. * 3. Escape colons in the path (required to use getNodeByPathOfType when the path has colons). * 4. Use getNodeByPathOfType to resolve the escaped path, providing the node's type. * 5. Confirm that the resolved node has the same handle as the original. */ TEST_F(SdkTestPath, GetNodeByPathOfTypeResolvesPathFromGetNodePathByNodeHandle) { auto handles = getAllNodesHandles(); for (auto handle: handles) { std::unique_ptr node(megaApi[0]->getNodeByHandle(handle)); ASSERT_NE(node, nullptr) << "Failed to retrieve node by handle."; auto path{megaApi[0]->getNodePathByNodeHandle(handle)}; ASSERT_NE(path, nullptr) << "Failed to retrieve path by handle."; auto escapedPath{std::regex_replace(path, std::regex(":"), "\\:")}; std::unique_ptr fromPath( megaApi[0]->getNodeByPathOfType(escapedPath.c_str(), nullptr, node->getType())); ASSERT_NE(fromPath, nullptr) << "Failed to retrieve node by path and type."; EXPECT_EQ(fromPath->getHandle(), handle) << escapedPath; } } sdk-10.11.0/tests/integration/sdk_test_ftp_server.cpp000066400000000000000000000015251516266226600227220ustar00rootroot00000000000000#include "SdkTest_test.h" #include /** * Test for FTP server using port 0, which also consist of: * - start two FTP servers from a thread and no ports conflicting * - stop FTP servers from a different thread, to allow TSAN to report any data races */ TEST_F(SdkTest, FtpServerCanUsePort0) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2, false)); ASSERT_TRUE(megaApi[0]->ftpServerStart(true, 0)); ASSERT_TRUE(megaApi[1]->ftpServerStart(true, 0)); ASSERT_TRUE(megaApi[0]->ftpServerIsRunning()); ASSERT_TRUE(megaApi[1]->ftpServerIsRunning()); std::async(std::launch::async, [&api = megaApi]() { api[0]->ftpServerStop(); api[1]->ftpServerStop(); }) .get(); CASE_info << "finished"; } sdk-10.11.0/tests/integration/sdk_test_http_server.cpp000066400000000000000000001463031516266226600231140ustar00rootroot00000000000000/** * @brief Mega SDK test file for server implementations (TCP, HTTP) * * This test suite includes HTTP server functionality tests, stability tests, * and error handling tests. Tests include positive cases, negative cases, * edge cases, and stress tests. * * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "easy_curl.h" #include "mega/common/testing/utility.h" #include "mega/utils.h" #include "mock_listeners.h" #include "sdk_server_test_utils.h" #include "sdk_test_utils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace ::mega; using namespace ::std; using ::mega::common::testing::randomBytes; using sdk_test::EasyCurl; using sdk_test::EasyCurlSlist; using sdk_test::LocalTempFile; namespace { std::optional scopedHttpServer(MegaApi* api) { if (!api) return std::nullopt; if (!api->httpServerStart(true, 0)) return std::nullopt; if (!api->httpServerIsRunning()) return std::nullopt; return makeScopedDestructor( [api]() { api->httpServerStop(); }); } std::string baseURL(int port) { return "http://localhost:" + std::to_string(port) + "/"; } std::string extractEndpointFromUrl(const std::string& url) { // extract :// from url: http://127.0.0.1:4443/... or // https://[::1]:4443/... size_t schemeEndPos = url.find("://"); if (schemeEndPos == string::npos) { return ""; } size_t hostStartPos = schemeEndPos + 3; size_t hostEndPos = url.find('/', hostStartPos); if (hostEndPos == string::npos) { return url; } return url.substr(0, hostEndPos); } class SdkHttpServerTest: public SdkServerTest {}; /** * Helper class for HTTP client requests */ class HttpClient { public: static inline const std::string EmptyRange = {}; enum class BodyMode { WithBody, WithoutBody }; struct Response { int statusCode; map headers; std::string body; curl_off_t contentLength; }; static Response get(const std::string& url, const std::string& range = EmptyRange, const map& headers = {}) { return performRequest(url, "GET", range, headers, "", BodyMode::WithBody); } static Response post(const std::string& url, const map& headers = {}, const string& body = "") { return performRequest(url, "POST", EmptyRange, headers, body, BodyMode::WithBody); } static Response put(const std::string& url, const map& headers = {}, const string& body = "") { return performRequest(url, "PUT", EmptyRange, headers, body, BodyMode::WithBody); } static Response del(const std::string& url, const map& headers = {}) { return performRequest(url, "DELETE", EmptyRange, headers, "", BodyMode::WithBody); } static Response head(const std::string& url, const map& headers = {}) { return performRequest(url, "HEAD", EmptyRange, headers, "", BodyMode::WithoutBody); } private: static bool appendHttpHeaders(EasyCurlSlist& easyCurlSlist, const std::map& headers) { for (const auto& header: headers) { std::string headerStr = header.first + ": " + header.second; if (!easyCurlSlist.append(headerStr)) { return false; } } return true; } static Response performRequest(const std::string& url, const std::string& method, const std::string& rangeHeader = EmptyRange, const map& headers = {}, const string& body = "", BodyMode bodyMode = BodyMode::WithBody) { Response response; auto easyCurl = EasyCurl(); auto easyCurlSlist = EasyCurlSlist(); auto curl = easyCurl.curl(); curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); if (method != "GET" && method != "HEAD") { curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method.c_str()); } if (bodyMode == BodyMode::WithoutBody) { curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); } else { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); } if (!body.empty()) { curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(body.size())); } else { curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(body.size())); } if (!appendHttpHeaders(easyCurlSlist, headers)) { LOG_err << "Failed to append HTTP headers"; response.statusCode = 0; response.contentLength = -1; return response; } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, easyCurlSlist.slist()); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response); curl_easy_setopt(curl, CURLOPT_NOPROXY, "*"); if (!rangeHeader.empty()) { curl_easy_setopt(curl, CURLOPT_RANGE, rangeHeader.c_str()); } CURLcode res = curl_easy_perform(curl); if (res == CURLE_OK) { curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode); curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &response.contentLength); } else { LOG_err << "CURL error for " << method << " " << url << ": " << curl_easy_strerror(res) << " (code: " << res << ")"; response.statusCode = 0; response.contentLength = -1; response.body.clear(); response.headers.clear(); } return response; } static size_t writeCallback(void* contents, size_t size, size_t nmemb, Response* response) { size_t totalSize = size * nmemb; response->body.append(static_cast(contents), totalSize); return totalSize; } static size_t headerCallback(void* contents, size_t size, size_t nmemb, Response* response) { size_t totalSize = size * nmemb; std::string headerLine(static_cast(contents), totalSize); if (headerLine == "\r\n") { // end of header block return totalSize; } if (headerLine.rfind("HTTP/", 0) == 0) { // status line return totalSize; } size_t colonPos = headerLine.find(':'); if (colonPos != std::string::npos) { std::string headerName = headerLine.substr(0, colonPos); headerName = Utils::toLowerUtf8(headerName); std::string headerValue = headerLine.substr(colonPos + 1); // Trim whitespace headerValue = Utils::trim(headerValue); response->headers[headerName] = headerValue; } return totalSize; } }; TEST_F(SdkHttpServerTest, HttpServerStartStop) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test starting HTTP server with default port ASSERT_TRUE(megaApi[0]->httpServerStart(true, 0)); // Verify server is running EXPECT_GT(megaApi[0]->httpServerIsRunning(), 0); // Test stopping HTTP server (returns void) megaApi[0]->httpServerStop(); // Verify server is no longer running EXPECT_FALSE(megaApi[0]->httpServerIsRunning()); CASE_info << "finished"; } /** * Test for HTTP server using port 0, which also consist of: * - start two HTTP servers from a thread and no ports conflicting * - stop HTTP servers from a different thread, to allow TSAN to report any data races */ TEST_F(SdkHttpServerTest, HttpServerCanUsePort0) { CASE_info << "started"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2, false)); ASSERT_TRUE(megaApi[0]->httpServerStart(true, 0)); ASSERT_TRUE(megaApi[1]->httpServerStart(true, 0)); ASSERT_TRUE(megaApi[0]->httpServerIsRunning()); ASSERT_TRUE(megaApi[1]->httpServerIsRunning()); std::async(std::launch::async, [&api = megaApi]() { api[0]->httpServerStop(); api[1]->httpServerStop(); }) .get(); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, HttpServerStartNotOnLocal) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test starting server not limited to localhost ASSERT_TRUE(megaApi[0]->httpServerStart(false, 0)); EXPECT_GT(megaApi[0]->httpServerIsRunning(), 0); EXPECT_FALSE(megaApi[0]->httpServerIsLocalOnly()); megaApi[0]->httpServerStop(); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, HttpServerIPv6) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test starting server with IPv6 support ASSERT_TRUE(megaApi[0]->httpServerStart(true, 0, false, nullptr, nullptr, true)); EXPECT_GT(megaApi[0]->httpServerIsRunning(), 0); megaApi[0]->httpServerStop(); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, EnableOfflineAttribute) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test enabling offline attribute megaApi[0]->httpServerEnableOfflineAttribute(true); EXPECT_TRUE(megaApi[0]->httpServerIsOfflineAttributeEnabled()); megaApi[0]->httpServerEnableOfflineAttribute(false); EXPECT_FALSE(megaApi[0]->httpServerIsOfflineAttributeEnabled()); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, httpServerEnableSubtitlesSupport) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test enabling subtitles support megaApi[0]->httpServerEnableSubtitlesSupport(true); EXPECT_TRUE(megaApi[0]->httpServerIsSubtitlesSupportEnabled()); megaApi[0]->httpServerEnableSubtitlesSupport(false); EXPECT_FALSE(megaApi[0]->httpServerIsSubtitlesSupportEnabled()); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, httpServerSetMaxBufferSize) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test setting and getting max buffer size int testSize = 2 * 1024 * 1024; // 2 MB megaApi[0]->httpServerSetMaxBufferSize(testSize); EXPECT_EQ(testSize, megaApi[0]->httpServerGetMaxBufferSize()); CASE_info << "finished"; } TEST_F(SdkHttpServerTest, httpServerSetMaxOutputSize) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Test setting and getting max output size int testSize = 1 * 1024 * 1024; // 1 MB megaApi[0]->httpServerSetMaxOutputSize(testSize); EXPECT_EQ(testSize, megaApi[0]->httpServerGetMaxOutputSize()); CASE_info << "finished"; } // test httpServerEnableFolderServer and httpServerGetLocalLink for directories TEST_F(SdkHttpServerTest, HttpServerDirectoryListing) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; megaApi[0]->httpServerEnableFolderServer(false); EXPECT_FALSE(megaApi[0]->httpServerIsFolderServerEnabled()); auto server = scopedHttpServer(megaApi[0].get()); ASSERT_TRUE(server); unique_ptr rootNode(megaApi[0]->getRootNode()); ASSERT_NE(nullptr, rootNode.get()); unique_ptr localLink(megaApi[0]->httpServerGetLocalLink(rootNode.get())); ASSERT_TRUE(localLink); CASE_info << "Performing HTTP request to folder link" << localLink.get(); auto response = HttpClient::get(localLink.get()); EXPECT_EQ(403, response.statusCode); megaApi[0]->httpServerEnableFolderServer(true); EXPECT_TRUE(megaApi[0]->httpServerIsFolderServerEnabled()); response = HttpClient::get(localLink.get()); EXPECT_EQ(200, response.statusCode); EXPECT_FALSE(response.body.empty()); const std::string folderName = "subfolder"; auto node = createFolder(0, folderName, rootNode.get()); ASSERT_TRUE(node); localLink.reset(megaApi[0]->httpServerGetLocalLink(node.get())); ASSERT_TRUE(localLink); CASE_info << "Performing HTTP request to subfolder link" << localLink.get(); response = HttpClient::get(localLink.get()); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(response.body.find(folderName) != string::npos) << "Response body: " << response.body << " does not contain folder name: " << folderName; CASE_info << "finished"; } // test httpServerEnableFileServer and httpServerGetLocalLink for files TEST_F(SdkHttpServerTest, HttpServerFileAccess) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // Prepare a test file const std::string testFileName = "http_test_file.txt"; const std::string testFileContents = "This is a test file for HTTP server access."; auto node = uploadFile(0, testFileName, testFileContents); ASSERT_TRUE(node); megaApi[0]->httpServerEnableFileServer(false); EXPECT_FALSE(megaApi[0]->httpServerIsFileServerEnabled()); auto server = scopedHttpServer(megaApi[0].get()); ASSERT_TRUE(server); unique_ptr localLink(megaApi[0]->httpServerGetLocalLink(node.get())); ASSERT_TRUE(localLink); CASE_info << "Performing HTTP request to file link " << localLink.get(); auto response = HttpClient::get(localLink.get()); EXPECT_EQ(403, response.statusCode); megaApi[0]->httpServerEnableFileServer(true); EXPECT_TRUE(megaApi[0]->httpServerIsFileServerEnabled()); response = HttpClient::get(localLink.get()); EXPECT_EQ(200, response.statusCode); EXPECT_EQ(testFileContents, response.body); CASE_info << "finished"; } // test httpServerSetRestrictedMode and httpServerGetRestrictedMode TEST_F(SdkHttpServerTest, GetSetRestrictedMode) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; // default mode is ALLOW_CREATED_LOCAL_LINKS EXPECT_EQ(MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS, megaApi[0]->httpServerGetRestrictedMode()); megaApi[0]->httpServerEnableFileServer(true); auto server = scopedHttpServer(megaApi[0].get()); ASSERT_TRUE(server); const std::string testFileNameStart = "http_test_file_start.txt"; const std::string testFileContentsStart = "This is a test file for HTTP server access before changing modes."; auto fileNodeStart = uploadFile(0, testFileNameStart, testFileContentsStart); ASSERT_TRUE(fileNodeStart); const std::string testFileNameAfter = "http_test_file.txt"; const std::string testFileContentsAfter = "This is a test file for HTTP server access."; auto fileNodeAfter = uploadFile(0, testFileNameAfter, testFileContentsAfter); ASSERT_TRUE(fileNodeAfter); unique_ptr fileLinkAfter(megaApi[0]->httpServerGetLocalLink(fileNodeAfter.get())); ASSERT_TRUE(fileLinkAfter); std::string fileLinkAfterStr(fileLinkAfter.get()); CASE_info << "Generated file link: " << fileLinkAfterStr; // generate file link for the first file std::string link = extractEndpointFromUrl(fileLinkAfterStr); ASSERT_FALSE(link.empty()); unique_ptr base64handlePtr(fileNodeStart->getBase64Handle()); std::string name = fileNodeStart->getName(); std::string escapedName; URLCodec::escape(&name, &escapedName); std::string fileLinkStartStr = link + "/" + base64handlePtr.get() + "/" + escapedName; CASE_info << "Generated file link for first file: " << fileLinkStartStr; // test restricted modes MegaApi::HTTP_SERVER_DENY_ALL { megaApi[0]->httpServerSetRestrictedMode(MegaApi::HTTP_SERVER_DENY_ALL); EXPECT_EQ(MegaApi::HTTP_SERVER_DENY_ALL, megaApi[0]->httpServerGetRestrictedMode()); auto fileStartResponse = HttpClient::get(fileLinkStartStr); EXPECT_EQ(403, fileStartResponse.statusCode); auto fileAfterResponse = HttpClient::get(fileLinkAfterStr); EXPECT_EQ(403, fileAfterResponse.statusCode); } // test restricted modes MegaApi::HTTP_SERVER_ALLOW_ALL { megaApi[0]->httpServerSetRestrictedMode(MegaApi::HTTP_SERVER_ALLOW_ALL); EXPECT_EQ(MegaApi::HTTP_SERVER_ALLOW_ALL, megaApi[0]->httpServerGetRestrictedMode()); auto fileStartResponse = HttpClient::get(fileLinkStartStr); EXPECT_EQ(200, fileStartResponse.statusCode); auto fileAfterResponse = HttpClient::get(fileLinkAfterStr); EXPECT_EQ(200, fileAfterResponse.statusCode); } // test restricted modes MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS { megaApi[0]->httpServerSetRestrictedMode(MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS); EXPECT_EQ(MegaApi::HTTP_SERVER_ALLOW_CREATED_LOCAL_LINKS, megaApi[0]->httpServerGetRestrictedMode()); auto fileStartResponse = HttpClient::get(fileLinkStartStr); EXPECT_EQ(403, fileStartResponse.statusCode); auto fileAfterResponse = HttpClient::get(fileLinkAfterStr); EXPECT_EQ(200, fileAfterResponse.statusCode); } // test restricted modes MegaApi::HTTP_SERVER_ALLOW_LAST_LOCAL_LINK { const std::string testFileNameLast = "http_test_file_last.txt"; const std::string testFileContentsLast = "This is a last test file for HTTP server access."; auto fileNodeLast = uploadFile(0, testFileNameLast, testFileContentsLast); ASSERT_TRUE(fileNodeLast); unique_ptr fileLinkLast(megaApi[0]->httpServerGetLocalLink(fileNodeLast.get())); ASSERT_TRUE(fileLinkLast); CASE_info << "Generated file link for last file: " << fileLinkLast.get(); megaApi[0]->httpServerSetRestrictedMode(MegaApi::HTTP_SERVER_ALLOW_LAST_LOCAL_LINK); EXPECT_EQ(MegaApi::HTTP_SERVER_ALLOW_LAST_LOCAL_LINK, megaApi[0]->httpServerGetRestrictedMode()); auto fileStartResponse = HttpClient::get(fileLinkStartStr); EXPECT_EQ(403, fileStartResponse.statusCode); auto fileAfterResponse = HttpClient::get(fileLinkAfterStr); EXPECT_EQ(403, fileAfterResponse.statusCode); auto fileLastResponse = HttpClient::get(fileLinkLast.get()); EXPECT_EQ(200, fileLastResponse.statusCode); } // test invalid restricted mode value (should not change) megaApi[0]->httpServerSetRestrictedMode(99); EXPECT_EQ(MegaApi::HTTP_SERVER_ALLOW_LAST_LOCAL_LINK, megaApi[0]->httpServerGetRestrictedMode()); CASE_info << "finished"; } // test httpServerAddListener, httpServerRemoveListener and check MegaTransferListener callbacks TEST_F(SdkHttpServerTest, HttpServerListenerCallbacks) { ASSERT_NO_FATAL_FAILURE(SdkTest::getAccountsForTest(1)); CASE_info << "started"; testing::NiceMock mockListener{megaApi[0].get()}; std::promise fileNodeHandlePromise; EXPECT_CALL(mockListener, onTransferFinish) .WillOnce( [&fileNodeHandlePromise](MegaApi*, MegaTransfer* transfer, MegaError*) { LOG_debug << "onTransferFinish called for node handle: " << transfer->getNodeHandle(); if (transfer) fileNodeHandlePromise.set_value(transfer->getNodeHandle()); else fileNodeHandlePromise.set_value(UNDEF); }); auto server = scopedHttpServer(megaApi[0].get()); ASSERT_TRUE(server); megaApi[0]->httpServerAddListener(&mockListener); // Upload test file to trigger transfer events const std::string testFileName = "http_server_listener_test.txt"; const std::string testFileContents = "HTTP server listener test file content"; auto fileNode = uploadFile(0, testFileName, testFileContents); ASSERT_TRUE(fileNode); // get local link to trigger the transfer unique_ptr fileLocalLink(megaApi[0]->httpServerGetLocalLink(fileNode.get())); ASSERT_TRUE(fileLocalLink); string fileLinkStr(fileLocalLink.get()); CASE_info << "Local file link: " << fileLinkStr; auto response = HttpClient::get(fileLinkStr); EXPECT_EQ(200, response.statusCode); // Wait for OnTransferFinish callback auto fileNodeHandle = fileNodeHandlePromise.get_future(); ASSERT_EQ(fileNodeHandle.wait_for(std::chrono::seconds(5U)), std::future_status::ready) << "Timeout waiting for onTransferFinish callback"; EXPECT_EQ(fileNode->getHandle(), fileNodeHandle.get()); megaApi[0]->httpServerRemoveListener(&mockListener); CASE_info << "finished"; } /** * Test basic HTTP server functionality with GET request. */ TEST_F(SdkHttpServerTest, BasicGet) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "HTTP server basic test content"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_basic.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_EQ(testFileContent, response.body); } /** * Test HTTP server with HEAD request. */ TEST_F(SdkHttpServerTest, HeadRequest) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "HTTP server HEAD test content"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_head.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto response = HttpClient::head(url); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(response.body.empty()); ASSERT_TRUE(response.headers.find("content-length") != response.headers.end()); EXPECT_EQ(response.headers["content-length"], std::to_string(testFileContent.size())); } /** * Test HTTP server with valid range requests. */ TEST_F(SdkHttpServerTest, ValidRangeRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_range.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // Standard range: first 10 bytes auto range1 = HttpClient::get(url, "0-9"); EXPECT_EQ(206, range1.statusCode); EXPECT_EQ("0123456789", range1.body); // Standard range: middle 10 bytes auto range2 = HttpClient::get(url, "10-19"); EXPECT_EQ(206, range2.statusCode); EXPECT_EQ("ABCDEFGHIJ", range2.body); // Overlapping range auto range3 = HttpClient::get(url, "5-14"); EXPECT_EQ(206, range3.statusCode); EXPECT_EQ("56789ABCDE", range3.body); // Suffix range: last 10 bytes auto suffixRange = HttpClient::get(url, "-10"); EXPECT_EQ(200, suffixRange.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_EQ(testFileContent, suffixRange.body); // BUG: Server returns full file instead of last 10 bytes // Suffix range: last 5 bytes auto suffixRange2 = HttpClient::get(url, "-5"); EXPECT_EQ(200, suffixRange2.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_EQ(testFileContent, suffixRange2.body); // BUG: Server returns full file instead of last 5 bytes // Single byte range: first byte auto singleByte1 = HttpClient::get(url, "0-0"); EXPECT_EQ(206, singleByte1.statusCode); EXPECT_EQ("0", singleByte1.body); // Single byte range: middle byte auto singleByte2 = HttpClient::get(url, "15-15"); EXPECT_EQ(206, singleByte2.statusCode); EXPECT_EQ("F", singleByte2.body); // Single byte range: last byte size_t fileSize = testFileContent.size(); auto singleByte3 = HttpClient::get(url, std::to_string(fileSize - 1) + "-" + std::to_string(fileSize - 1)); EXPECT_EQ(206, singleByte3.statusCode); EXPECT_EQ("Z", singleByte3.body); // Prefix range: first 15 bytes (0-14) auto prefixRange = HttpClient::get(url, "0-14"); EXPECT_EQ(206, prefixRange.statusCode); EXPECT_EQ("0123456789ABCDE", prefixRange.body); // Range from position to end (should return from N to end) auto rangeToEnd = HttpClient::get(url, "26-"); EXPECT_EQ(206, rangeToEnd.statusCode); EXPECT_EQ("QRSTUVWXYZ", rangeToEnd.body); // Full file range (0 to last byte) auto fullRange = HttpClient::get(url, "0-" + std::to_string(fileSize - 1)); EXPECT_EQ(200, fullRange.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_EQ(testFileContent, fullRange.body); // Range starting at 1 auto edgeCase1 = HttpClient::get(url, "1-5"); EXPECT_EQ(206, edgeCase1.statusCode); EXPECT_EQ("12345", edgeCase1.body); // Range ending at second-to-last byte auto edgeCase2 = HttpClient::get(url, std::to_string(fileSize - 3) + "-" + std::to_string(fileSize - 2)); EXPECT_EQ(206, edgeCase2.statusCode); EXPECT_EQ("XY", edgeCase2.body); } /** * Test HTTP server with very large range requests. * Tests various range formats on very large files including suffix ranges. */ TEST_F(SdkHttpServerTest, VeryLargeRangeRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(50 * 1024 * 1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_large_range.bin", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // Full file range auto fileSize = testFileContent.size(); auto largeRange = HttpClient::get(url, "0-" + std::to_string(fileSize - 1)); EXPECT_EQ(200, largeRange.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == largeRange.body); // Middle range: from 25% to 50%, end is inclusive auto begin = fileSize / 4; auto end = fileSize / 2; auto midRange = HttpClient::get(url, std::to_string(begin) + "-" + std::to_string(end)); EXPECT_EQ(206, midRange.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data() + begin, end - begin + 1) == midRange.body); // Suffix range: last 10MB (bytes=-10485760) auto suffixRange = HttpClient::get(url, "-10485760"); EXPECT_EQ(200, suffixRange.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == suffixRange.body); // BUG: Server returns full file instead of last 10MB // Suffix range: last 25% of file auto suffixRange2 = HttpClient::get(url, "-" + std::to_string(fileSize / 4)); EXPECT_EQ(200, suffixRange2.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == suffixRange2.body); // BUG: Server returns full file instead of last 25% // Range from 75% to end begin = fileSize * 3 / 4; end = testFileContent.size() - 1; auto rangeToEnd = HttpClient::get(url, std::to_string(begin) + "-"); EXPECT_EQ(206, rangeToEnd.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data() + begin, end - begin + 1) == rangeToEnd.body); } /** * Test HTTP server with invalid range requests (416 Requested Range Not Satisfiable). */ TEST_F(SdkHttpServerTest, InvalidRangeRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "Test content"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_invalid_range.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // Range starting beyond file end auto fileSize = testFileContent.size(); auto invalidRange1 = HttpClient::get(url, std::to_string(fileSize) + "-" + std::to_string(fileSize + 100)); EXPECT_EQ(416, invalidRange1.statusCode); // Range completely beyond file end auto invalidRange2 = HttpClient::get(url, "1000-2000"); EXPECT_EQ(416, invalidRange2.statusCode); // Range with start > end auto invalidRange3 = HttpClient::get(url, "10-5"); EXPECT_EQ(416, invalidRange3.statusCode); } /** * Test HTTP server with non-existent file (404 Not Found). */ TEST_F(SdkHttpServerTest, NonExistentFile) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); auto server = scopedHttpServer(api); ASSERT_TRUE(server); int port = api->httpServerIsRunning(); std::string invalidHandle = "12345678"; std::string invalidUrl = baseURL(port) + invalidHandle + "/nonexistent_file.txt"; auto response = HttpClient::get(invalidUrl); EXPECT_EQ(403, response.statusCode); // BUG: HTTP protocol expects 404 Not Found } /** * Test HTTP server with empty file. * Tests GET, HEAD, and range requests for empty files. */ TEST_F(SdkHttpServerTest, EmptyFile) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); // Upload empty file std::unique_ptr uploadedNode = uploadFile(0, "test_http_empty.txt", ""); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // GET request for empty file auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(response.body.empty()); EXPECT_TRUE(response.headers.find("content-length") != response.headers.end()); EXPECT_EQ("0", response.headers["content-length"]); // HEAD request for empty file auto headResponse = HttpClient::head(url); EXPECT_EQ(200, headResponse.statusCode); EXPECT_TRUE(headResponse.body.empty()); EXPECT_TRUE(headResponse.headers.find("content-length") != headResponse.headers.end()); // Range requests for empty file auto rangeResponse1 = HttpClient::get(url, "0-0"); EXPECT_EQ(200, rangeResponse1.statusCode); // BUG: HTTP protocol expects 416 Range Not Satisfiable auto rangeResponse2 = HttpClient::get(url, "0-10"); EXPECT_EQ(200, rangeResponse2.statusCode); // BUG: HTTP protocol expects 416 Range Not Satisfiable auto suffixRange = HttpClient::get(url, "-10"); EXPECT_EQ(200, suffixRange.statusCode); // BUG: HTTP protocol expects 416 Range Not Satisfiable } /** * Test HTTP server with large file. * Tests various range requests on large files including suffix ranges. */ TEST_F(SdkHttpServerTest, LargeFile) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(10 * 1024 * 1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_large.bin", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // Full file GET request auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(testFileContent == response.body); // Standard range: first 1MB auto rangeResponse = HttpClient::get(url, "0-1048575"); EXPECT_EQ(206, rangeResponse.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data(), 1048575u + 1) == rangeResponse.body); // Standard range: second 1MB auto rangeResponse2 = HttpClient::get(url, "1048576-2097151"); EXPECT_EQ(206, rangeResponse2.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data() + 1048576, 2097151u - 1048576u + 1) == rangeResponse2.body); // Suffix range: last 1MB (bytes=-1048576) auto suffixRange = HttpClient::get(url, "-1048576"); EXPECT_EQ(200, suffixRange.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == suffixRange.body); // BUG: Server returns full file instead of last 1MB // Suffix range: last 512KB auto suffixRange2 = HttpClient::get(url, "-524288"); EXPECT_EQ(200, suffixRange2.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == suffixRange2.body); // BUG: Server returns full file instead of last 512KB // Range from middle to near end auto midRange = HttpClient::get(url, "5242880-6291455"); EXPECT_EQ(206, midRange.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data() + 5242880, 6291455u - 5242880u + 1) == midRange.body); // Small range from beginning auto smallRange = HttpClient::get(url, "0-1023"); EXPECT_EQ(206, smallRange.statusCode); EXPECT_TRUE(std::string_view(testFileContent.data(), 1023u + 1) == smallRange.body); } /** * Test HTTP server with concurrent requests. */ TEST_F(SdkHttpServerTest, ConcurrentRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(100 * 1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_concurrent.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); const int numRequests = 10; std::vector> futures; for (int i = 0; i < numRequests; i++) { futures.push_back(std::async(std::launch::async, [url]() { return HttpClient::get(url); })); } for (size_t i = 0; i < futures.size(); i++) { auto response = futures[i].get(); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(testFileContent == response.body); } } /** * Test HTTP server with concurrent range requests. * Tests concurrent standard and suffix range requests. */ TEST_F(SdkHttpServerTest, ConcurrentRangeRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(2 * 1024 * 1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_concurrent_range.bin", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); const int numRequests = 5; std::vector> futures; // Concurrent standard range requests constexpr auto INTERVAL = 200000; constexpr auto LENGTH = 200000; for (size_t i = 0; i < numRequests; i++) { size_t start = i * INTERVAL; size_t end = start + LENGTH - 1; std::string range = std::to_string(start) + "-" + std::to_string(end); futures.push_back(std::async(std::launch::async, [url, range]() { return HttpClient::get(url, range); })); } for (size_t i = 0; i < futures.size(); i++) { auto response = futures[i].get(); EXPECT_EQ(206, response.statusCode); size_t start = i * INTERVAL; EXPECT_TRUE(std::string_view(testFileContent.data() + start, LENGTH) == response.body); } futures.clear(); // Concurrent suffix range requests std::vector suffixSizes = {100000, 200000, 300000, 400000, 500000}; for (size_t suffixSize: suffixSizes) { std::string range = "-" + std::to_string(suffixSize); futures.push_back(std::async( std::launch::async, [url, range, &testFileContent]() { auto resp = HttpClient::get(url, range); EXPECT_EQ(200, resp.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_TRUE(testFileContent == resp.body); // BUG: Server returns full file instead of last N bytes return resp; })); } for (auto& future: futures) { future.get(); } } /** * Test HTTP server restart and multiple start/stop cycles. */ TEST_F(SdkHttpServerTest, Restart) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "HTTP server restart test"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_restart.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); for (int cycle = 0; cycle < 10; cycle++) { auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); auto response = HttpClient::get(link.get()); EXPECT_EQ(200, response.statusCode); EXPECT_EQ(testFileContent, response.body); } } /** * Test HTTP server with malformed URLs. */ TEST_F(SdkHttpServerTest, MalformedUrls) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::string baseUrl = baseURL(api->httpServerIsRunning()); std::vector malformedUrls = { baseUrl + "invalid", baseUrl + "12345/invalid", baseUrl + "!@#$%^&*()", baseUrl + "", baseUrl + "a/b/c/d/e/f", }; for (const auto& url: malformedUrls) { auto response = HttpClient::get(url); EXPECT_TRUE(response.statusCode == 404 || response.statusCode == 403); // BUG: HTTP protocol expects 400 Bad Request or 404 Not Found } } /** * Test HTTP server with unsupported HTTP methods. */ TEST_F(SdkHttpServerTest, UnsupportedMethods) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "HTTP methods test"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_methods.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto postResponse = HttpClient::post(url); EXPECT_EQ(200, postResponse.statusCode); // BUG: HTTP protocol expects 405 Method Not Allowed auto putResponse = HttpClient::put(url); // BUG: HTTP protocol expects 405 Method Not Allowed. Due to a race condition (?), // the server may have time to return 500 EXPECT_TRUE(putResponse.statusCode == 0 || putResponse.statusCode == 500); auto deleteResponse = HttpClient::del(url); EXPECT_EQ(405, deleteResponse.statusCode); } /** * Test HTTP server stability under rapid requests. */ TEST_F(SdkHttpServerTest, RapidRequests) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_rapid.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); constexpr int numRequests = 50; int successCount = 0; int failureCount = 0; for (int i = 0; i < numRequests; i++) { auto response = HttpClient::get(url); if (response.statusCode == 200 && response.body == testFileContent) { ++successCount; } else { ++failureCount; } } EXPECT_GT(successCount, numRequests * 0.9); EXPECT_LT(failureCount, numRequests * 0.1); } /** * Test HTTP server with special characters in file names. * Tests files with spaces, URL-encoded characters, non-ASCII, and special symbols. */ TEST_F(SdkHttpServerTest, SpecialCharactersInFilename) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); // Files and content const std::vector testFiles = { "file with spaces.txt", "file%with&special#.txt", "file+=with+plus.txt", "\xD1\x83\xD0\xBA\xD1\x80\xD0\xB0\xD1\x97\xD0\xBD\xD1\x81\xD1\x8C\xD0\xBA\xD0\xB8\xD0\xB9." "txt", "test-file-normal.txt", }; std::vector> uploadedNodes; for (const auto& fileName: testFiles) { std::unique_ptr uploadedNode = uploadFile(0, fileName, fileName); ASSERT_NE(uploadedNode, nullptr); uploadedNodes.push_back(std::move(uploadedNode)); } auto server = scopedHttpServer(api); ASSERT_TRUE(server); for (size_t i = 0; i < testFiles.size(); i++) { const auto& fileName = testFiles[i]; std::unique_ptr link(api->httpServerGetLocalLink(uploadedNodes[i].get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_EQ(fileName, response.body); } } /** * Test HTTP server with very small file sizes. */ TEST_F(SdkHttpServerTest, DifferentFileSizes) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); // Test 1-byte file std::string testFileContent1 = "A"; std::unique_ptr uploadedNode1 = uploadFile(0, "test_1byte.tx", testFileContent1); ASSERT_NE(uploadedNode1, nullptr); // Test 2-byte file std::string testFileContent2 = "AB"; std::unique_ptr uploadedNode2 = uploadFile(0, "test_2byte.tx", testFileContent2); ASSERT_NE(uploadedNode2, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); // Test 1-byte file std::unique_ptr link1(api->httpServerGetLocalLink(uploadedNode1.get())); ASSERT_NE(link1, nullptr); std::string url1 = link1.get(); // Full file GET auto response = HttpClient::get(url1); EXPECT_EQ(200, response.statusCode); EXPECT_EQ("A", response.body); // Range request for single byte auto rangeResponse = HttpClient::get(url1, "0-0"); EXPECT_EQ(200, rangeResponse.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_EQ("A", rangeResponse.body); // Range request beyond file auto invalidRange = HttpClient::get(url1, "1-5"); EXPECT_EQ(416, invalidRange.statusCode); // Test 2-byte file std::unique_ptr link2(api->httpServerGetLocalLink(uploadedNode2.get())); ASSERT_NE(link2, nullptr); std::string url2 = link2.get(); // Range: first byte auto range1 = HttpClient::get(url2, "0-0"); EXPECT_EQ(206, range1.statusCode); EXPECT_EQ("A", range1.body); // Range: second byte auto range2 = HttpClient::get(url2, "1-1"); EXPECT_EQ(206, range2.statusCode); EXPECT_EQ("B", range2.body); // Range: both bytes auto range3 = HttpClient::get(url2, "0-1"); EXPECT_EQ(200, range3.statusCode); // BUG: HTTP protocol expects 206 Partial Content EXPECT_EQ("AB", range3.body); } /** * Test HTTP server with very long URLs * Tests server behavior with extremely long URL paths, including non-existent files (404). */ TEST_F(SdkHttpServerTest, VeryLongUrl) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = "Test content"; std::unique_ptr uploadedNode = uploadFile(0, "test_http_long.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); // Create a 1 KB long path by appending many characters constexpr size_t targetSize = 1 * 1024; std::string longPath; longPath.reserve(targetSize); while (longPath.size() < targetSize) { longPath += "/very/long/path/segment/for/testing/"; } longPath.resize(targetSize); // Test with very long path to non-existent file std::string longUrl = baseURL(api->httpServerIsRunning()) + longPath; auto response = HttpClient::get(longUrl); EXPECT_EQ(404, response.statusCode); // Test with valid URL but very long query parameters std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); std::string longUrlWithQuery = url + "?" + std::string(targetSize - 1, 'x'); auto queryResponse = HttpClient::get(longUrlWithQuery); EXPECT_EQ(404, queryResponse.statusCode); // BUG: Server treats query as part of filename // Verify normal URL still works auto normalResponse = HttpClient::get(url); EXPECT_EQ(200, normalResponse.statusCode); EXPECT_EQ(testFileContent, normalResponse.body); } /** * Test HTTP server various connections handling. */ TEST_F(SdkHttpServerTest, ConnectionHandling) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); std::string testFileContent = randomBytes(1024); std::unique_ptr uploadedNode = uploadFile(0, "test_http_connection.txt", testFileContent); ASSERT_NE(uploadedNode, nullptr); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(uploadedNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); // Test multiple sequential requests for (int i = 0; i < 5; i++) { auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_TRUE(testFileContent == response.body); } // Test HEAD followed by GET auto headResponse = HttpClient::head(url); EXPECT_EQ(200, headResponse.statusCode); auto getResponse = HttpClient::get(url); EXPECT_EQ(200, getResponse.statusCode); EXPECT_TRUE(testFileContent == getResponse.body); // Test range request followed by full request auto rangeResponse = HttpClient::get(url, "0-99"); EXPECT_EQ(206, rangeResponse.statusCode); EXPECT_TRUE(100 == rangeResponse.body.size()); auto fullResponse = HttpClient::get(url); EXPECT_EQ(200, fullResponse.statusCode); EXPECT_TRUE(testFileContent == fullResponse.body); } /** * Test HTTP server with empty folder. */ TEST_F(SdkHttpServerTest, FolderEmpty) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); // Create empty folder auto folderNode = createFolder(0, "test_http_folder_empty"); ASSERT_NE(folderNode, nullptr); // Enable folder server support api->httpServerEnableFolderServer(true); ASSERT_TRUE(api->httpServerIsFolderServerEnabled()); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr link(api->httpServerGetLocalLink(folderNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_NE(response.body.find(""), std::string::npos); // BUG: Server returns HTML page without <html></html> auto headResponse = HttpClient::head(url); EXPECT_EQ(200, headResponse.statusCode); } /** * Test HTTP server with folder with files. */ TEST_F(SdkHttpServerTest, FolderWithFiles) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); MegaApi* api = megaApi[0].get(); // Create folder auto folderNode = createFolder(0, "test_http_folder_files"); ASSERT_NE(folderNode, nullptr); // Upload files to folder const std::vector<std::string> testFiles = { "file 1.txt", "file#2.txt", #ifndef WIN32 // ? is not allowed on Windows "file?3.dat", #endif "file-3.dat", }; std::vector<std::unique_ptr<MegaNode>> uploadedNodes; for (const auto& fileName: testFiles) { std::unique_ptr<MegaNode> uploadedNode = uploadFile(0, fileName, fileName, folderNode.get()); ASSERT_NE(uploadedNode, nullptr); uploadedNodes.push_back(std::move(uploadedNode)); } // Enable folder server support api->httpServerEnableFolderServer(true); ASSERT_TRUE(api->httpServerIsFolderServerEnabled()); auto server = scopedHttpServer(api); ASSERT_TRUE(server); std::unique_ptr<char[]> link(api->httpServerGetLocalLink(folderNode.get())); ASSERT_NE(link, nullptr); std::string url = link.get(); auto response = HttpClient::get(url); EXPECT_EQ(200, response.statusCode); EXPECT_NE(response.body.find("<title>"), std::string::npos); // BUG: Server returns HTML page without <html></html> // Check that file names appear in the HTML for (const auto& fileName: testFiles) { EXPECT_TRUE(response.body.find(fileName) != std::string::npos); } // HEAD request auto headResponse = HttpClient::head(url); EXPECT_EQ(200, headResponse.statusCode); } }�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_lockless_cs_channel.cpp��������������������������������������0000664�0000000�0000000�00000023563�15162662266�0024545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sdk_test_lockless_cs_channel.cpp * @brief This file defines tests for the lockless CS channel */ #include "env_var_accounts.h" #include "integration_test_utils.h" #include "mega/scoped_helpers.h" #include "mega/testhooks.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTestNodesSetUp.h" using namespace sdk_test; using namespace testing; class SdkTestLocklessCSChannel: public SdkTestNodesSetUp { public: const std::string& getRootTestDir() const override { return rootTestDir; } const std::vector<sdk_test::NodeInfo>& getElements() const override { return testNode; } const fs::path& getLocalFolder() const { return localFolder.getPath(); } private: const std::string rootTestDir{"locklessCS"}; const std::string localFolderName{getFilePrefix() + "dir"}; const LocalTempDir localFolder{fs::current_path() / localFolderName}; const std::vector<NodeInfo> testNode{FileNodeInfo("remoteTestFile").setSize(100)}; }; // Returns a function that sets flag to true when a CS for a given command completes. auto commandChecker(const std::string& command, bool& flag) { std::ostringstream ostream; ostream << "\"a\":\"" << command << "\""; return [&flag, pattern = ostream.str()](auto& request) { flag |= (request->status == REQ_FAILURE || request->status == REQ_SUCCESS) && request->out->find(pattern) != std::string::npos; }; } #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED /** * @brief Ensures that the lockless channel is correctly used when retrieving the download URL ("g") * from the intermediate layer or internally when downloading a node. */ TEST_F(SdkTestLocklessCSChannel, DownloadFile) { const auto logPre{getLogPrefix()}; const std::unique_ptr<MegaNode> remoteNode{getNodeByPath("remoteTestFile")}; ASSERT_TRUE(remoteNode) << "Failed to get the node to be downloaded"; bool usedLocklessChannel{false}; globalMegaTestHooks.interceptLocklessCSRequest = commandChecker("g", usedLocklessChannel); LOG_info << logPre << "Get the download URL. The \"g\" command should use the lockless channel."; NiceMock<MockRequestListener> urlTracker(megaApi[0].get()); megaApi[0]->getDownloadUrl(remoteNode.get(), false, true, &urlTracker); ASSERT_TRUE(urlTracker.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Error getting the download URL for the remote node."; ASSERT_TRUE(usedLocklessChannel) << "Lockless channel has not been used to get the download URL."; LOG_info << logPre << "Download a node. The internal \"g\" command should use the lockless channel."; usedLocklessChannel = false; const auto errCode = downloadNode(megaApi[0].get(), remoteNode.get(), getLocalFolder(), true, MAX_TIMEOUT, ::mega::MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, ::mega::MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); ASSERT_EQ(errCode, API_OK) << "Failed to download the remote node."; ASSERT_TRUE(usedLocklessChannel) << "The lockless channel was not used when downloading a node."; } TEST_F(SdkTestLocklessCSChannel, ImportFileLink) { // Convenience. auto& client = *megaApi[0]; // Try and locate the node we want to share. auto source = getNodeByPath("remoteTestFile"); ASSERT_TRUE(source) << "Couldn't locate test file"; // Try and generate a public link for our node. NiceMock<MockRequestListener> exportTracker(&client); client.exportNode(source.get(), 0, false, false, &exportTracker); ASSERT_TRUE(exportTracker.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Couldn't generate public link for test file"; // Refresh the snapshot of our test file. source = getNodeByPath("remoteTestFile"); ASSERT_TRUE(source) << "Couldn't locate test file"; // Retrieve our node's public link. auto link = makeUniqueFrom(source->getPublicLink()); ASSERT_TRUE(link) << "Couldn't retrieve public link for test file"; // Log our client into a different account so we can import the link. ASSERT_NO_FATAL_FAILURE(locallogout(0)); auto [username, password] = getEnvVarAccounts().getVarValues(1); ASSERT_FALSE(username.empty()); ASSERT_FALSE(password.empty()); auto loginTracker = asyncRequestLogin(0, username.c_str(), password.c_str()); ASSERT_EQ(loginTracker->waitForResult(), API_OK) << "Couldn't log in client as " << username; ASSERT_NO_FATAL_FAILURE(fetchnodes(0)); // Get our hands on the target client's root node. auto target = makeUniqueFrom(client.getRootNode()); ASSERT_TRUE(target) << "Couldn't get target client's root node"; // So we know whether the import below used the lockless CS channel. auto usedLocklessChannel = false; globalMegaTestHooks.interceptLocklessCSRequest = commandChecker("g", usedLocklessChannel); // Try and import the node into our second client. NiceMock<MockRequestListener> importTracker(&client); client.importFileLink(link.get(), target.get(), &importTracker); ASSERT_TRUE(importTracker.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Couldn't import test file into target client"; // Make sure import used the lockless CS channel. ASSERT_TRUE(usedLocklessChannel) << "Test file import didn't use the lockless CS channel"; } TEST_F(SdkTestLocklessCSChannel, StreamFile) { // Address our client more easily. auto& client = *megaApi[0]; // Try and locate the node we want to stream. auto node = getNodeByPath("remoteTestFile"); ASSERT_TRUE(node) << "Couldn't locate test file"; // So we know whether streaming below used the lockless CS channel. auto usedLocklessChannel = false; globalMegaTestHooks.interceptLocklessCSRequest = commandChecker("g", usedLocklessChannel); // Try and stream some data from the node. NiceMock<MockMegaTransferListener> listener; client.startStreaming(node.get(), 0, 100, &listener); // Wait for all of the data to be streamed. ASSERT_TRUE(listener.waitForFinishOrTimeout(MAX_TIMEOUT)) << "Couldn't stream data from test file"; // Make sure streaming used the lockless CS channel. ASSERT_TRUE(usedLocklessChannel) << "Test file stream didn't use the lockless CS channel"; } #endif #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED /** * @brief Simulates and tests recovery from communication failures in the lockless CS channel. */ TEST_F(SdkTestLocklessCSChannel, CommunicationFailures) { const auto logPre{getLogPrefix()}; const std::unique_ptr<MegaNode> remoteNode{getNodeByPath("remoteTestFile")}; ASSERT_TRUE(remoteNode) << "Failed to get the node to be downloaded"; bool usedLocklessChannel; int errorCounter; // Each error (request timeout) takes HttpIO::REQUESTTIMEOUT (2 minutes) auto simulateNoResponse = [&usedLocklessChannel, &errorCounter](std::unique_ptr<HttpReq>& locklessCSReq) { if (errorCounter && locklessCSReq->status == REQ_SUCCESS && locklessCSReq->out->find("\"a\":\"g\"") != std::string::npos) { LOG_info << "Restore API request status to REQ_INFLIGHT to simulate a timeout."; locklessCSReq->status = REQ_INFLIGHT; usedLocklessChannel = true; --errorCounter; } return true; }; // Each error causes an exponential backoff for the lockless CS channel. auto simulateAPI_EAGAIN = [&usedLocklessChannel, &errorCounter](std::unique_ptr<HttpReq>& locklessCSReq) { if (errorCounter && locklessCSReq->status == REQ_SUCCESS && locklessCSReq->out->find("\"a\":\"g\"") != std::string::npos) { LOG_info << "Replacing API response in the lockless channel with -3."; locklessCSReq->in = "-3"; usedLocklessChannel = true; --errorCounter; } return true; }; LOG_info << logPre << "Download a node after a timeout due to API not responding."; usedLocklessChannel = false; errorCounter = 1; // Cause one request timeout globalMegaTestHooks.interceptLocklessCSRequest = simulateNoResponse; auto errCode = downloadNode(megaApi[0].get(), remoteNode.get(), getLocalFolder(), true, MAX_TIMEOUT, ::mega::MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, ::mega::MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); globalMegaTestHooks.interceptLocklessCSRequest = nullptr; ASSERT_EQ(errCode, API_OK) << "Failed to download the remote node."; ASSERT_EQ(errorCounter, 0) << "No error simulation has been caused."; ASSERT_TRUE(usedLocklessChannel) << "The lockless channel was not used when downloading a node."; LOG_info << logPre << "Download a node after a backoff due to API returning -3."; usedLocklessChannel = false; errorCounter = 6; // Receive -3 6 times. globalMegaTestHooks.interceptLocklessCSRequest = simulateAPI_EAGAIN; errCode = downloadNode(megaApi[0].get(), remoteNode.get(), getLocalFolder(), true, MAX_TIMEOUT, ::mega::MegaTransfer::COLLISION_CHECK_ASSUMEDIFFERENT, ::mega::MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N); globalMegaTestHooks.interceptLocklessCSRequest = nullptr; ASSERT_EQ(errCode, API_OK) << "Failed to download the remote node."; ASSERT_EQ(errorCounter, 0) << "No error simulation has ocurred."; ASSERT_TRUE(usedLocklessChannel) << "The lockless channel was not used when downloading a node."; } #endif ���������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_network.cpp��������������������������������������������������0000664�0000000�0000000�00000017100�15162662266�0022230�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @brief Mega SDK test file for network commands * * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "SdkTest_test.h" #include <gmock/gmock.h> namespace { /** * @brief SdkTest.NetworkConnectivityTest * * Test for MegaApi::runNetworkConnectivityTest(), which should consist of: * - get ServerInfo from remote API * - send and receive simple UDP messages * - send and receive UDP messages for DNS lookup * - send event 99495 */ TEST_F(SdkTest, NetworkConnectivityTest) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); RequestTracker tracker(megaApi[0].get()); megaApi[0]->runNetworkConnectivityTest(&tracker); ASSERT_EQ(API_OK, tracker.waitForResult(10)) << "Network connectivity test took way more than the expected 1 second"; auto* testResults = tracker.request->getMegaNetworkConnectivityTestResults(); ASSERT_THAT(testResults, ::testing::NotNull()); ASSERT_THAT(testResults->getIPv4UDP(), ::testing::AnyOf( MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_PASS, MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE)); ASSERT_THAT(testResults->getIPv4DNS(), ::testing::AnyOf( MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_PASS, MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE)); ASSERT_THAT(testResults->getIPv6UDP(), ::testing::AnyOf( MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_PASS, MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE)); ASSERT_THAT(testResults->getIPv6DNS(), ::testing::AnyOf( MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_PASS, MegaNetworkConnectivityTestResults::NETWORK_CONNECTIVITY_TEST_NET_UNREACHABLE)); } /** * @brief SdkTestStorageServerAccessProtocol.testProtocol * * Test for MegaApi::getUploadURL, MegaApi::getDownloadUrl, MegaApi::getThumbnailUploadURL, * MegaApi::getPreviewUploadURL * - Return HTTPS address if forceSSL is true, or HTTP address if false */ class SdkTestStorageServerAccessProtocol: public ::testing::WithParamInterface<bool>, public SdkTest {}; TEST_P(SdkTestStorageServerAccessProtocol, testProtocol) { // setup ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + AVATARSRC, AVATARSRC)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + THUMBNAIL, THUMBNAIL)); ASSERT_TRUE(getFileFromArtifactory("test-data/" + PREVIEW, PREVIEW)); const bool forceSSL = GetParam(); const char* protocolPrefix = forceSSL ? "https://" : "http://"; std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode()); // UploadURL { RequestTracker rt(megaApi[0].get()); int64_t fileSize = getFilesize(AVATARSRC.c_str()); // get URL ASSERT_GT(fileSize, 0); megaApi[0]->getUploadURL(fileSize, forceSSL, &rt); ASSERT_EQ(API_OK, rt.waitForResult()); const char* fileURL = rt.request->getName(); ASSERT_NE(fileURL, nullptr); EXPECT_TRUE(Utils::startswith(fileURL, protocolPrefix)) << "url " << fileURL << " for file upload is not " << protocolPrefix << " address"; // getLink() will return a pure ip address // getText() will return a pure ipv6 address // Create a "media upload" instance std::unique_ptr<MegaBackgroundMediaUpload> req( MegaBackgroundMediaUpload::createInstance(megaApi[0].get())); // encrypt file contents with the file key and get URL suffix std::unique_ptr<char[]> suffix( req->encryptFile(AVATARSRC.c_str(), 0, &fileSize, AVATARDST.c_str(), false)); ASSERT_NE(suffix, nullptr) << "Got NULL suffix after encryption"; std::unique_ptr<char[]> fingerprint(megaApi[0]->getFingerprint(AVATARDST.c_str())); string finalurl(fileURL); if (suffix) finalurl.append(suffix.get()); // upload file string binaryUploadToken; synchronousHttpPOSTFile(finalurl, AVATARDST.c_str(), binaryUploadToken); ASSERT_NE(binaryUploadToken.size(), 0u); ASSERT_GT(binaryUploadToken.size(), 3u) << "POST failed, fa server error: " << binaryUploadToken; std::unique_ptr<char[]> base64UploadToken( megaApi[0]->binaryToBase64(binaryUploadToken.data(), binaryUploadToken.length())); int err = synchronousMediaUploadComplete(0, req.get(), AVATARSRC.c_str(), rootNode.get(), fingerprint.get(), nullptr, base64UploadToken.get(), nullptr); ASSERT_EQ(API_OK, err) << "Cannot complete media upload (error: " << err << ")"; } std::unique_ptr<MegaNode> uploadNode{ megaApi[0]->getNodeByPath(AVATARSRC.c_str(), rootNode.get())}; ASSERT_TRUE(uploadNode) << "could not find uploaded file"; // file download URL { RequestTracker rt(megaApi[0].get()); megaApi[0]->getDownloadUrl(uploadNode.get(), true, forceSSL, &rt); ASSERT_EQ(API_OK, rt.waitForResult()); const char* url = rt.request->getName(); ASSERT_NE(url, nullptr) << "nullptr for file download"; EXPECT_TRUE(Utils::startswith(url, protocolPrefix)) << "url " << url << " for file download is not " << protocolPrefix << " address"; } // thumbnail upload URL { RequestTracker rt(megaApi[0].get()); int64_t fileSize = getFilesize(THUMBNAIL.c_str()); ASSERT_GT(fileSize, 0); // get URL megaApi[0]->getThumbnailUploadURL(uploadNode->getHandle(), fileSize, forceSSL, &rt); ASSERT_EQ(API_OK, rt.waitForResult()); const char* url = rt.request->getName(); ASSERT_NE(url, nullptr) << "nullptr for thumbnail upload"; EXPECT_TRUE(Utils::startswith(url, protocolPrefix)) << "url " << url << " for thumbnail is not " << protocolPrefix << " address"; } // preview upload URL { RequestTracker rt(megaApi[0].get()); int64_t fileSize = getFilesize(PREVIEW.c_str()); ASSERT_GT(fileSize, 0); megaApi[0]->getPreviewUploadURL(uploadNode->getHandle(), fileSize, forceSSL, &rt); ASSERT_EQ(API_OK, rt.waitForResult()); const char* url = rt.request->getName(); ASSERT_NE(url, nullptr) << "nullptr for preview upload"; EXPECT_TRUE(Utils::startswith(url, protocolPrefix)) << "url " << url << " for preview is not " << protocolPrefix << " address"; } } INSTANTIATE_TEST_SUITE_P(StorageAccessProtocol, SdkTestStorageServerAccessProtocol, ::testing::Values(false, true)); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_node_tags.cpp������������������������������������������������0000664�0000000�0000000�00000067727�15162662266�0022526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "sdk_test_node_tags.h" #include "mega/scoped_helpers.h" #include "sdk_test_utils.h" #include <gmock/gmock-matchers.h> namespace mega { static bool contains(const MegaStringList& list, const std::string& value); static std::vector<std::string> nodeNames(const std::vector<MegaNodePtr>& nodes); template<typename... Parameters, typename Predicate> static auto satisfies(Predicate&& predicate, Parameters&&... arguments) -> std::enable_if_t<std::conjunction_v<std::is_invocable_r<bool, Predicate, Parameters>...>, bool> { return (... && std::invoke(predicate, std::forward<Parameters>(arguments))); } static std::vector<MegaNodePtr> toVector(const MegaNodeList& list); static std::vector<std::string> toVector(const MegaStringList& list); template<typename... Parameters, typename Predicate> static auto waitUntilSatisfied(Predicate&& predicate, std::chrono::steady_clock::time_point until, Parameters&&... arguments) -> decltype(satisfies(predicate, arguments...)) { auto result = satisfies(predicate, arguments...); while (!result && std::chrono::steady_clock::now() < until) { std::this_thread::sleep_for(std::chrono::milliseconds(128)); result = satisfies(predicate, arguments...); } return result; } template<typename... Parameters, typename Period, typename Predicate, typename Rep> static auto waitUntilSatisfied(Predicate&& predicate, std::chrono::duration<Rep, Period> period, Parameters&&... arguments) -> decltype(satisfies(predicate, arguments...)) { return waitUntilSatisfied(std::forward<Predicate>(predicate), std::chrono::steady_clock::now() + period, std::forward<Parameters>(arguments)...); } static constexpr auto DefaultTimeoutMs = 30 * 1000; static constexpr auto ErrorTag = std::in_place_type<Error>; static constexpr auto NodeTag = std::in_place_type<std::unique_ptr<MegaNode>>; static constexpr auto StringVectorTag = std::in_place_type<std::vector<std::string>>; TEST_F(SdkTestNodeTagsBasic, AddTagFailsWhenTagContainsSeparator) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "f0,f1"), API_EARGS); } TEST_F(SdkTestNodeTagsBasic, AddTagFailsWhenTagExists) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "f0"), API_OK); EXPECT_EQ(addTag(*client0, *file, "F0"), API_EEXIST); } TEST_F(SdkTestNodeTagsBasic, AddTagSucceedsWhenTagContainsWildcard) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "f*0?"), API_OK); } TEST_F(SdkTestNodeTagsBasic, AddTagSucceeds) { auto directory = nodeByPath(*client0, "/d0"); ASSERT_NE(directory, nullptr); EXPECT_EQ(addTag(*client0, *directory, "cafe"), API_OK); EXPECT_EQ(addTag(*client0, *directory, "café"), API_OK); } TEST_F(SdkTestNodeTagsBasic, ExistingTagsCopiedToNewFileVersion) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "f0"), API_OK); EXPECT_EQ(addTag(*client0, *file, "f1"), API_OK); auto fileTags = getTags(*client0, "/d0/f0"); ASSERT_EQ(result(fileTags), API_OK); auto directory = nodeByPath(*client0, "/d0"); ASSERT_NE(directory, nullptr); // Ensure that new version content is different, otherwise SDK won't perform a full upload auto newFile = createFileWithContent(*client0, *directory, "f0", "abcd"); ASSERT_EQ(result(newFile), API_OK); auto newFileTags = getTags(*client0, "/d0/f0"); ASSERT_EQ(result(newFileTags), API_OK); EXPECT_EQ(value(fileTags), value(newFileTags)); } TEST_F(SdkTestNodeTagsBasic, ManipulateTagsOnInshare) { // Get our hands on the root node. auto root = rootNode(*client0); ASSERT_NE(root, nullptr); // Add a directory for us to share. auto directory = createDirectory(*client0, *root, "d"); ASSERT_EQ(result(directory), API_OK); // Make sure client0 is friend with client1 and client2. ASSERT_EQ(befriend(*client0, *client1), API_OK); ASSERT_EQ(befriend(*client0, *client2), API_OK); // Convenience. constexpr auto RO = MegaShare::ACCESS_READ; constexpr auto RW = MegaShare::ACCESS_FULL; // client1 should have full access to d. ASSERT_EQ(share(*client0, *value(directory), *client1, RW), API_OK); // client2 should have read-only access to d. ASSERT_EQ(share(*client0, *value(directory), *client2, RO), API_OK); // Convenience. auto hasTag = [this](const MegaNode& node, const std::string& tag) { return [handle = node.getHandle(), tag, this](const MegaApi& client) { return SdkTestNodeTagsCommon::hasTag(client, handle, tag); }; }; // hasTag // clients with read-only access should not be able to add a tag. EXPECT_EQ(addTag(*client2, *value(directory), "a"), API_EACCESS); // clients with full access to share should be able to add a tag. EXPECT_EQ(addTag(*client1, *value(directory), "b"), API_OK); // And that tag should be visible to all clients. EXPECT_TRUE(waitUntilSatisfied(hasTag(*value(directory), "b"), std::chrono::milliseconds(DefaultTimeoutMs), *client0, *client1, *client2)); // clients with read-only access shouldn't be able to update tags. EXPECT_EQ(renameTag(*client2, *value(directory), "b", "c"), API_EACCESS); // But clients with full access, should. EXPECT_EQ(renameTag(*client1, *value(directory), "b", "c"), API_OK); // And all clients should see the change. EXPECT_TRUE(waitUntilSatisfied(std::not_fn(hasTag(*value(directory), "b")), std::chrono::milliseconds(DefaultTimeoutMs), *client0, *client1, *client2)); EXPECT_TRUE(waitUntilSatisfied(hasTag(*value(directory), "c"), std::chrono::milliseconds(DefaultTimeoutMs), *client0, *client1, *client2)); // clients with read-only access shouldn't be able to remove a tag. EXPECT_EQ(removeTag(*client2, *value(directory), "c"), API_EACCESS); // But clients with full-access should. EXPECT_EQ(removeTag(*client1, *value(directory), "c"), API_OK); // And once again, all clients should see the change. EXPECT_TRUE(waitUntilSatisfied(std::not_fn(hasTag(*value(directory), "c")), std::chrono::milliseconds(DefaultTimeoutMs), *client0, *client1, *client2)); } TEST_F(SdkTestNodeTagsBasic, RemoveTagFailsWhenTagDoesntExist) { auto directory = nodeByPath(*client0, "/d0"); ASSERT_NE(directory, nullptr); EXPECT_EQ(removeTag(*client0, *directory, "d0"), API_ENOENT); } TEST_F(SdkTestNodeTagsBasic, RemoveTagSucceeds) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "f0"), API_OK); EXPECT_EQ(removeTag(*client0, *file, "F0"), API_OK); } TEST_F(SdkTestNodeTagsBasic, RenameTagFailsWhenNewTagExists) { auto file = nodeByPath(*client0, "/d0/f0"); ASSERT_NE(file, nullptr); EXPECT_EQ(addTag(*client0, *file, "café"), API_OK); EXPECT_EQ(addTag(*client0, *file, "tupée"), API_OK); EXPECT_EQ(renameTag(*client0, *file, "CAFÉ", "TUPÉE"), API_EEXIST); } TEST_F(SdkTestNodeTagsBasic, RenameTagFailsWhenTagDoesntExist) { auto directory = nodeByPath(*client0, "/d0"); ASSERT_NE(directory, nullptr); EXPECT_EQ(renameTag(*client0, *directory, "bogus", "insane"), API_ENOENT); } TEST_F(SdkTestNodeTagsBasic, RenameTagSucceeds) { auto directory = nodeByPath(*client0, "/d0"); ASSERT_NE(directory, nullptr); EXPECT_EQ(addTag(*client0, *directory, "d0"), API_OK); EXPECT_EQ(renameTag(*client0, *directory, "D0", "d1"), API_OK); } TEST_F(SdkTestNodeTagsSearch, AllTagsSucceeds) { using testing::UnorderedElementsAre; // Make sure client1 contains at least one tag. auto root = rootNode(*client1); ASSERT_NE(root, nullptr); auto q = createDirectory(*client1, *root, "q"); ASSERT_EQ(result(q), API_OK); EXPECT_EQ(addTag(*client1, *value(q), "q"), API_OK); // Make sure client2 contains at least one tag. root = rootNode(*client2); ASSERT_NE(root, nullptr); auto r = createDirectory(*client2, *root, "r"); ASSERT_EQ(result(r), API_OK); EXPECT_EQ(addTag(*client2, *value(r), "r"), API_OK); // Make sure client0, client1 and client2 are friends. EXPECT_EQ(befriend(*client0, *client1), API_OK); EXPECT_EQ(befriend(*client0, *client2), API_OK); // Share q with client0. EXPECT_EQ(share(*client1, *value(q), *client0, MegaShare::ACCESS_READWRITE), API_OK); // Share r with client0. EXPECT_EQ(share(*client2, *value(r), *client0, MegaShare::ACCESS_FULL), API_OK); // Move x/y/z into the rubbish bin. auto rubbish = makeUniqueFrom(client0->getRubbishNode()); ASSERT_NE(rubbish, nullptr); auto z = nodeByPath(*client0, "/x/y/z"); ASSERT_NE(z, nullptr); EXPECT_EQ(moveNode(*client0, *z, *rubbish), API_OK); // Get all tags visible in client0. auto tags = allTags(*client0); ASSERT_EQ(result(tags), API_OK); // Should contain all tags except those from client1. EXPECT_THAT( value(tags), UnorderedElementsAre("r", "xf0", "xf1", "xf2", "yf0", "yf1", "yf2", "zf0", "zf1", "zf2")); } TEST_F(SdkTestNodeTagsSearch, FindNodesByDirectorySucceeds) { using testing::UnorderedElementsAre; auto y = nodeByPath(*client0, "/x/y"); ASSERT_NE(y, nullptr); auto filter = makeUniqueFrom(MegaSearchFilter::createInstance()); ASSERT_NE(filter, nullptr); filter->byLocationHandle(y->getHandle()); auto nodes = search(*client0, *filter); ASSERT_EQ(result(nodes), API_OK); EXPECT_THAT(nodeNames(value(nodes)), UnorderedElementsAre("yf", "z", "zf")); } TEST_F(SdkTestNodeTagsSearch, FindNodesByWildcardSucceeds) { using testing::UnorderedElementsAre; auto filter = makeUniqueFrom(MegaSearchFilter::createInstance()); ASSERT_NE(filter, nullptr); filter->byTag("f0"); auto nodes = search(*client0, *filter); ASSERT_EQ(result(nodes), API_OK); EXPECT_THAT(nodeNames(value(nodes)), UnorderedElementsAre("xf", "yf", "zf")); } TEST_F(SdkTestNodeTagsSearch, FindNodeByTagSucceedsWhenNoMatches) { auto filter = makeUniqueFrom(MegaSearchFilter::createInstance()); ASSERT_NE(filter, nullptr); filter->byTag("bogus"); auto nodes = search(*client0, *filter); ASSERT_EQ(result(nodes), API_OK); ASSERT_TRUE(value(nodes).empty()); } TEST_F(SdkTestNodeTagsSearch, FindNodeByTagSucceedsWhenWildcard) { auto filter = makeUniqueFrom(MegaSearchFilter::createInstance()); ASSERT_NE(filter, nullptr); filter->byTag("zf*"); auto nodes = search(*client0, *filter); ASSERT_EQ(result(nodes), API_OK); ASSERT_TRUE(value(nodes).empty()); } TEST_F(SdkTestNodeTagsSearch, FindNodeByTagSucceeds) { auto filter = makeUniqueFrom(MegaSearchFilter::createInstance()); ASSERT_NE(filter, nullptr); // Find a node with a given name by some specified tag. auto find = [&](const char* tag, const char* name) { filter->byTag(tag); auto nodes = search(*client0, *filter); ASSERT_EQ(result(nodes), API_OK); ASSERT_EQ(value(nodes).size(), 1u); ASSERT_STREQ(value(nodes).front()->getName(), name); }; // find // Find xf based on its first tag, xf0. EXPECT_NO_FATAL_FAILURE(find("xf0", "xf")); // Find yf based on its second tag, yf1. EXPECT_NO_FATAL_FAILURE(find("YF1", "yf")); // Find zf based on its third and final tag, zf2. EXPECT_NO_FATAL_FAILURE(find("zf2", "zf")); } TEST_F(SdkTestNodeTagsSearch, TagsBelowNaturallySortedSucceeds) { using testing::ElementsAre; auto x = nodeByPath(*client0, "/x"); ASSERT_NE(x, nullptr); // Get our hands on the files under /x. auto xf = nodeByPath(*client0, "xf", x.get()); ASSERT_NE(xf, nullptr); auto yf = nodeByPath(*client0, "y/yf", x.get()); ASSERT_NE(yf, nullptr); auto zf = nodeByPath(*client0, "y/z/zf", x.get()); ASSERT_NE(zf, nullptr); // Add some recognizable tags. EXPECT_EQ(addTags(*client0, *zf, "nf000", "nf123", "nf0123", "nf00123"), API_OK); EXPECT_EQ(addTags(*client0, *yf, "nf00", "nf234", "nf0234", "nf00234"), API_OK); EXPECT_EQ(addTags(*client0, *xf, "nf0", "nf345", "nf0345", "nf00345"), API_OK); // Retrieve all tags under /x starting with nf. auto tags = tagsBelow(*client0, *x, "nf"); ASSERT_EQ(result(tags), API_OK); EXPECT_THAT(value(tags), ElementsAre("nf0", "nf00", "nf000", "nf00123", "nf0123", "nf123", "nf00234", "nf0234", "nf234", "nf00345", "nf0345", "nf345")); } TEST_F(SdkTestNodeTagsSearch, TagsBelowSucceeds) { using testing::ElementsAre; auto x = nodeByPath(*client0, "/x"); ASSERT_NE(x, nullptr); auto y = nodeByPath(*client0, "y", x.get()); ASSERT_NE(y, nullptr); auto z = nodeByPath(*client0, "z", y.get()); ASSERT_NE(z, nullptr); // All tags below z. auto tags = tagsBelow(*client0, *z); ASSERT_EQ(result(tags), API_OK); EXPECT_THAT(value(tags), ElementsAre("zf0", "zf1", "zf2")); // All tags below y. tags = tagsBelow(*client0, *y); ASSERT_EQ(result(tags), API_OK); EXPECT_THAT(value(tags), ElementsAre("yf0", "yf1", "yf2", "zf0", "zf1", "zf2")); // Add a new version of yf without the yf1 tag. // Ensure that new version content is different, otherwise SDK won't perform a full upload auto yf = createFileWithContent(*client0, *y, "yf", "abcd"); ASSERT_EQ(result(yf), API_OK); ASSERT_EQ(removeTag(*client0, *value(yf), "yf1"), API_OK); // All tags below x starting with y. tags = tagsBelow(*client0, *x, "y*"); ASSERT_EQ(result(tags), API_OK); EXPECT_THAT(value(tags), ElementsAre("yf0", "yf2")); } auto SdkTestNodeTagsCommon::SetUp() -> void { SdkTest::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(3)); client0 = megaApi[0].get(); client1 = megaApi[1].get(); client2 = megaApi[2].get(); ASSERT_EQ(fileVersioning(*client0, true), API_OK); ASSERT_EQ(fileVersioning(*client1, true), API_OK); ASSERT_EQ(fileVersioning(*client2, true), API_OK); // Makes sharing a lot more convenient. client1->setManualVerificationFlag(false); } auto SdkTestNodeTagsCommon::addTag(MegaApi& client, const MegaNode& node, const std::string& tag) -> Error { RequestTracker tracker(&client); client.addNodeTag(const_cast<MegaNode*>(&node), tag.c_str(), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return result; auto added = WaitFor( [&client, handle = node.getHandle(), &tag, this]() { auto node = nodeByHandle(client, handle); if (!node) return false; auto tags = makeUniqueFrom(node->getTags()); if (!tags) return false; return contains(*tags, tag); }, DefaultTimeoutMs); return added ? API_OK : LOCAL_ETIMEOUT; } auto SdkTestNodeTagsCommon::allTags(const MegaApi& client) -> AllTagsResult { auto self = const_cast<MegaApi*>(&client); auto tags = makeUniqueFrom(self->getAllNodeTags()); if (!tags) return AllTagsResult(ErrorTag, API_EINTERNAL); return AllTagsResult(StringVectorTag, toVector(*tags)); } auto SdkTestNodeTagsCommon::copyNode(MegaApi& client, const MegaNode& source, const MegaNode& target, const std::string& name) -> CopyNodeResult { RequestTracker tracker(&client); client.copyNode(const_cast<MegaNode*>(&source), const_cast<MegaNode*>(&target), name.c_str(), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return CopyNodeResult(ErrorTag, result); MegaNodePtr node; WaitFor( [&]() { return (node = nodeByPath(client, name, &target)) != nullptr; }, DefaultTimeoutMs); if (!node) return CopyNodeResult(ErrorTag, LOCAL_ETIMEOUT); return CopyNodeResult(NodeTag, std::move(node)); } auto SdkTestNodeTagsCommon::createFile(MegaApi& client, const MegaNode& parent, const std::string& name) -> UploadFileResult { using sdk_test::LocalTempFile; auto filePath = u8path_compat(name); LocalTempFile file(filePath, 0); return uploadFile(client, parent, filePath); } auto SdkTestNodeTagsCommon::createFileWithContent(MegaApi& client, const MegaNode& parent, const std::string& name, const std::string_view content) -> UploadFileResult { using sdk_test::LocalTempFile; auto filePath = u8path_compat(name); LocalTempFile file(filePath, content); return uploadFile(client, parent, filePath); } auto SdkTestNodeTagsCommon::fileVersioning(MegaApi& client, bool enabled) -> Error { RequestTracker tracker(&client); client.setFileVersionsOption(!enabled, &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return result; std::string text = tracker.request->getText(); unsigned int value; auto result = std::from_chars(text.data(), &text[text.size()], value); if (result.ec != std::errc{} || enabled != !value) return API_EINTERNAL; return API_OK; } auto SdkTestNodeTagsCommon::getTags(MegaApi& client, const std::string& path) -> AllTagsResult { auto node = nodeByPath(client, path); if (!node) return AllTagsResult(ErrorTag, API_ENOENT); auto tags = makeUniqueFrom(node->getTags()); if (!tags) return AllTagsResult(ErrorTag, API_EINTERNAL); return AllTagsResult(StringVectorTag, toVector(*tags)); } auto SdkTestNodeTagsCommon::hasTag(const MegaApi& client, MegaHandle handle, const std::string& tag) -> bool { auto node = nodeByHandle(client, handle); if (!node) return false; auto tags = makeUniqueFrom(node->getTags()); return contains(*tags, tag); } auto SdkTestNodeTagsCommon::moveNode(MegaApi& client, const MegaNode& source, const MegaNode& target) -> Error { RequestTracker tracker(&client); client.moveNode(const_cast<MegaNode*>(&source), const_cast<MegaNode*>(&target), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return result; auto sourceHandle = source.getHandle(); auto targetHandle = target.getHandle(); auto moved = WaitFor( [&]() { auto node = nodeByHandle(client, sourceHandle); return node != nullptr && node->getParentHandle() == targetHandle; }, DefaultTimeoutMs); return moved ? API_OK : LOCAL_ETIMEOUT; } auto SdkTestNodeTagsCommon::nodeByHandle(const MegaApi& client, MegaHandle handle) -> MegaNodePtr { return makeUniqueFrom(const_cast<MegaApi&>(client).getNodeByHandle(handle)); } auto SdkTestNodeTagsCommon::nodeByPath(const MegaApi& client, const std::string& path, const MegaNode* root) -> MegaNodePtr { auto self = const_cast<MegaApi*>(&client); auto node = self->getNodeByPath(path.c_str(), const_cast<MegaNode*>(root)); return makeUniqueFrom(node); } auto SdkTestNodeTagsCommon::openShareDialog(MegaApi& client, const MegaNode& node) -> Error { RequestTracker tracker(&client); client.openShareDialog(const_cast<MegaNode*>(&node), &tracker); return tracker.waitForResult(); } auto SdkTestNodeTagsCommon::removeTag(MegaApi& client, const MegaNode& node, const std::string& tag) -> Error { RequestTracker tracker(&client); client.removeNodeTag(const_cast<MegaNode*>(&node), tag.c_str(), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return result; auto removed = WaitFor( [&client, handle = node.getHandle(), &tag, this]() { auto node = nodeByHandle(client, handle); if (!node) return false; auto tags = makeUniqueFrom(node->getTags()); if (!tags) return false; return !contains(*tags, tag); }, DefaultTimeoutMs); return removed ? API_OK : LOCAL_ETIMEOUT; } auto SdkTestNodeTagsCommon::renameTag(MegaApi& client, const MegaNode& node, const std::string& oldTag, const std::string& newTag) -> Error { RequestTracker tracker(&client); client.updateNodeTag(const_cast<MegaNode*>(&node), newTag.c_str(), oldTag.c_str(), &tracker); if (auto result = tracker.waitForResult(); result != API_OK) return result; auto handle = node.getHandle(); auto renamed = WaitFor( [&client, handle, &newTag, &oldTag, this]() { auto node = nodeByHandle(client, handle); if (!node) return false; auto tags = makeUniqueFrom(node->getTags()); if (!tags) return false; return contains(*tags, newTag) && !contains(*tags, oldTag); }, DefaultTimeoutMs); return renamed ? API_OK : LOCAL_ETIMEOUT; } auto SdkTestNodeTagsCommon::rootNode(const MegaApi& client) const -> MegaNodePtr { return makeUniqueFrom(const_cast<MegaApi&>(client).getRootNode()); } auto SdkTestNodeTagsCommon::search(const MegaApi& client, const MegaSearchFilter& filter) -> SearchResult { auto nodes = makeUniqueFrom(const_cast<MegaApi&>(client).search(&filter)); if (!nodes) return SearchResult(std::in_place_index<0>, API_EINTERNAL); return SearchResult(std::in_place_index<1>, toVector(*nodes)); } auto SdkTestNodeTagsCommon::share(MegaApi& client0, const MegaNode& node, const MegaApi& client1, int permissions) -> Error { RequestTracker tracker(&client0); client0.share(const_cast<MegaNode*>(&node), const_cast<MegaApi&>(client1).getMyEmail(), permissions, &tracker); auto result = tracker.waitForResult(); if (result == API_EKEY) { if ((result = openShareDialog(client0, node)) != API_OK) return result; return share(client0, node, client1, permissions); } if (result != API_OK) return result; auto shared = WaitFor( [&client1, handle = node.getHandle(), this]() { return nodeByHandle(client1, handle) != nullptr; }, DefaultTimeoutMs); return shared ? API_OK : LOCAL_ETIMEOUT; } auto SdkTestNodeTagsCommon::tagsBelow(const MegaApi& client, const MegaNode& node, const std::string& pattern) -> AllTagsResult { auto pattern_ = pattern.empty() ? nullptr : pattern.c_str(); auto self = const_cast<MegaApi*>(&client); auto tags = makeUniqueFrom(self->getAllNodeTagsBelow(&node, pattern_)); if (!tags) return AllTagsResult(ErrorTag, API_EINTERNAL); return AllTagsResult(StringVectorTag, toVector(*tags)); } auto SdkTestNodeTagsCommon::uploadFile(MegaApi& client, const MegaNode& parent, const fs::path& path) -> UploadFileResult { TransferTracker tracker(&client); MegaUploadOptions uploadOptions; uploadOptions.fileName = path.filename().string(); uploadOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; const auto localPath = path.string(); client.startUpload(localPath, const_cast<MegaNode*>(&parent), nullptr, &uploadOptions, &tracker); if (auto result = tracker.waitForResult(); result != API_OK) { return UploadFileResult(ErrorTag, result); } MegaNodePtr file = nullptr; MegaHandle fileHandle = tracker.resultNodeHandle; WaitFor( [&]() { return (file = nodeByHandle(client, fileHandle)) != nullptr; }, DefaultTimeoutMs); if (!file) { return UploadFileResult(ErrorTag, LOCAL_ETIMEOUT); } return UploadFileResult(NodeTag, std::move(file)); } auto SdkTestNodeTagsBasic::SetUp() -> void { SdkTestNodeTagsCommon::SetUp(); auto prepare = [&](MegaApi& client) { auto root = rootNode(client); ASSERT_NE(root, nullptr); auto directory = createDirectory(client, *root, "d0"); ASSERT_EQ(result(directory), API_OK); auto file = createFile(client, *value(directory), "f0"); ASSERT_EQ(result(file), API_OK); }; // prepare ASSERT_NO_FATAL_FAILURE(prepare(*client0)); } auto SdkTestNodeTagsSearch::SetUp() -> void { SdkTestNodeTagsCommon::SetUp(); auto prepare = [&](MegaApi& client) { auto root = rootNode(client); ASSERT_NE(root, nullptr); auto x = createDirectory(client, *root, "x"); ASSERT_EQ(result(x), API_OK); auto xf = createFile(client, *value(x), "xf"); ASSERT_EQ(result(xf), API_OK); auto y = createDirectory(client, *value(x), "y"); ASSERT_EQ(result(y), API_OK); auto yf = copyNode(client, *value(xf), *value(y), "yf"); ASSERT_EQ(result(yf), API_OK); auto z = createDirectory(client, *value(y), "z"); ASSERT_EQ(result(z), API_OK); auto zf = copyNode(client, *value(xf), *value(z), "zf"); ASSERT_EQ(result(zf), API_OK); ASSERT_EQ(addTags(client, *value(xf), "xf0", "xf1", "xf2"), API_OK); ASSERT_EQ(addTags(client, *value(yf), "yf0", "yf1", "yf2"), API_OK); ASSERT_EQ(addTags(client, *value(zf), "zf0", "zf1", "zf2"), API_OK); }; // prepare // Set up test state. ASSERT_NO_FATAL_FAILURE(prepare(*client0)); } bool contains(const MegaStringList& list, const std::string& value) { for (auto i = 0, j = list.size(); i < j; ++i) { if (value == list.get(i)) return true; } return false; } std::vector<std::string> nodeNames(const std::vector<MegaNodePtr>& nodes) { auto name = [](const MegaNodePtr& node) -> std::string { return node->getName(); }; // name std::vector<std::string> names; std::transform(std::begin(nodes), std::end(nodes), std::back_inserter(names), name); return names; } std::vector<MegaNodePtr> toVector(const MegaNodeList& list) { std::vector<MegaNodePtr> result; result.reserve(static_cast<std::size_t>(list.size())); for (auto i = 0, j = list.size(); i < j; ++i) { result.emplace_back(makeUniqueFrom(list.get(i)->copy())); } return result; } std::vector<std::string> toVector(const MegaStringList& list) { std::vector<std::string> result; result.reserve(static_cast<std::size_t>(list.size())); for (auto i = 0, j = list.size(); i < j; ++i) { result.push_back(list.get(i)); } return result; } } // mega �����������������������������������������sdk-10.11.0/tests/integration/sdk_test_node_tags.h��������������������������������������������������0000664�0000000�0000000�00000007550�15162662266�0022157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include "SdkTest_test.h" namespace mega { // Convenience types. using MegaNodePtr = std::unique_ptr<MegaNode>; using MegaSearchFilterPtr = std::unique_ptr<MegaSearchFilter>; class SdkTestNodeTagsCommon: public SdkTest { protected: auto SetUp() -> void override; public: // Convenience traits. template<typename T> using IsConvertibleToString = std::is_convertible<T, std::string>; template<typename T, typename... Ts> using AreConvertibleToStrings = std::conjunction<IsConvertibleToString<T>, IsConvertibleToString<Ts>...>; // Convenience types. using AllTagsResult = std::variant<Error, std::vector<std::string>>; using CopyNodeResult = std::variant<Error, MegaNodePtr>; using SearchResult = std::variant<Error, std::vector<MegaNodePtr>>; using UploadFileResult = std::variant<Error, MegaNodePtr>; auto addTag(MegaApi& client, const MegaNode& node, const std::string& tag) -> Error; template<typename T> auto addTags(MegaApi& client, const MegaNode& node, T&& tag) -> std::enable_if_t<IsConvertibleToString<T>::value, Error> { return addTag(client, node, tag); } template<typename T, typename... Ts> auto addTags(MegaApi& client, const MegaNode& node, T&& tag, Ts&&... tags) -> std::enable_if_t<AreConvertibleToStrings<T, Ts...>::value, Error> { if (auto result = addTag(client, node, tag); result != API_OK) return result; return addTags(client, node, tags...); } auto allTags(const MegaApi& client) -> AllTagsResult; auto copyNode(MegaApi& client, const MegaNode& source, const MegaNode& target, const std::string& name) -> CopyNodeResult; auto createFile(MegaApi& client, const MegaNode& parent, const std::string& name) -> UploadFileResult; auto createFileWithContent(MegaApi& client, const MegaNode& parent, const std::string& name, const std::string_view content) -> UploadFileResult; auto fileVersioning(MegaApi& client, bool enabled) -> Error; auto getTags(MegaApi& client, const std::string& path) -> AllTagsResult; auto hasTag(const MegaApi& client, MegaHandle handle, const std::string& tag) -> bool; auto moveNode(MegaApi& client, const MegaNode& source, const MegaNode& target) -> Error; auto nodeByHandle(const MegaApi& client, MegaHandle handle) -> MegaNodePtr; auto nodeByPath(const MegaApi& client, const std::string& path, const MegaNode* root = nullptr) -> MegaNodePtr; auto openShareDialog(MegaApi& client, const MegaNode& node) -> Error; auto removeTag(MegaApi& client, const MegaNode& node, const std::string& tag) -> Error; auto renameTag(MegaApi& client, const MegaNode& node, const std::string& oldTag, const std::string& newTag) -> Error; auto rootNode(const MegaApi& client) const -> MegaNodePtr; auto search(const MegaApi& client, const MegaSearchFilter& filter) -> SearchResult; auto share(MegaApi& client0, const MegaNode& node, const MegaApi& client1, int permissions) -> Error; auto tagsBelow(const MegaApi& client, const MegaNode& node, const std::string& pattern = std::string()) -> AllTagsResult; auto uploadFile(MegaApi& client, const MegaNode& parent, const fs::path& path) -> UploadFileResult; MegaApi* client0 = nullptr; MegaApi* client1 = nullptr; MegaApi* client2 = nullptr; }; // SdkTestNodeTagsCommon class SdkTestNodeTagsBasic: public SdkTestNodeTagsCommon { auto SetUp() -> void override; }; // SdkTestNodeTagsBasic class SdkTestNodeTagsSearch: public SdkTestNodeTagsCommon { auto SetUp() -> void override; }; // SdkTestNodeTagsSearch } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_pitag.cpp����������������������������������������������������0000664�0000000�0000000�00000070316�15162662266�0021653�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "env_var_accounts.h" #include "integration_test_utils.h" #include "mega/testhooks.h" #include "passwordManager/SdkTestPasswordManager.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" #ifdef ENABLE_SYNC #include "backup_test_utils.h" #include "SdkTestSyncNodesOperations.h" #endif #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED #include <chrono> #include <condition_variable> #include <functional> #include <mutex> #include <string> #include <type_traits> namespace { void createLocalTree(const fs::path& parentPath, const std::vector<sdk_test::NodeInfo>& nodes); void createLocalEntry(const fs::path& parentPath, const sdk_test::NodeInfo& node) { std::visit( [&](const auto& nodeInfo) { using NodeInfoType = std::decay_t<decltype(nodeInfo)>; if constexpr (std::is_same_v<NodeInfoType, sdk_test::FileNodeInfo>) { const fs::path filePath = parentPath / nodeInfo.name; const auto fileSize = nodeInfo.size > 0 ? nodeInfo.size : 1u; ASSERT_NO_THROW(sdk_test::createFile(filePath, fileSize)); } else { const fs::path dirPath = parentPath / nodeInfo.name; ASSERT_TRUE(fs::create_directory(dirPath)) << "Unable to create directory " << dirPath.string(); createLocalTree(dirPath, nodeInfo.childs); } }, node); } void createLocalTree(const fs::path& parentPath, const std::vector<sdk_test::NodeInfo>& nodes) { for (const auto& node: nodes) { createLocalEntry(parentPath, node); } } class PitagCommandObserver { public: PitagCommandObserver(): mPreviousHook(globalMegaTestHooks.onHttpReqPost) { globalMegaTestHooks.onHttpReqPost = [this](HttpReq* req) { handleRequest(req); return false; }; } ~PitagCommandObserver() { globalMegaTestHooks.onHttpReqPost = mPreviousHook; } bool waitForValue(const std::string& expected, std::chrono::milliseconds timeout) { std::unique_lock<std::mutex> lock(mMutex); if (mLastValue == expected) { return true; } mExpected = expected; mCaptured = false; mExpecting = true; if (!mCv.wait_for(lock, timeout, [&] { return mCaptured; })) { mExpecting = false; return false; } mExpecting = false; return mLastValue == expected; } std::string capturedValue() const { std::lock_guard<std::mutex> lock(mMutex); return mLastValue; } private: void handleRequest(HttpReq* req) { if (!req || !req->out) { return; } const std::string& payload = *req->out; const std::string commandToken = "\"a\":\"p\""; const std::string pitagToken = "\"p\":\""; const auto commandPos = payload.find(commandToken); if (commandPos == std::string::npos) { return; } auto pitagPos = payload.find(pitagToken, commandPos); if (pitagPos == std::string::npos) { return; } pitagPos += pitagToken.size(); const auto endPos = payload.find('"', pitagPos); if (endPos == std::string::npos) { return; } { std::lock_guard<std::mutex> lock(mMutex); const std::string value = payload.substr(pitagPos, endPos - pitagPos); mLastValue = value; if (mExpecting && value == mExpected) { mCaptured = true; } } mCv.notify_all(); } std::function<bool(HttpReq*)> mPreviousHook; mutable std::mutex mMutex; std::condition_variable mCv; bool mCaptured{false}; bool mExpecting{false}; std::string mExpected; std::string mLastValue; }; class SdkTestPitag: public SdkTest {}; class SdkTestPitagPasswordManager: public SdkTestPasswordManager {}; #ifdef ENABLE_SYNC class SdkTestPitagSyncUpload: public sdk_test::SdkTestSyncNodesOperations {}; class SdkTestPitagBackupUpload: public SdkTestBackup { void TearDown() override { removeBackupSync(); cleanupPerApiBackupsMonitorMap(0); SdkTestBackup::TearDown(); } }; #endif } // anonymous namespace TEST_F(SdkTestPitag, PitagCapturedForRegularUpload) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_regular.bin"); const std::string remoteName = path_u8string(localFilePath.filename()); const std::string localPathUtf8 = path_u8string(localFilePath); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-regular-upload"); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; PitagCommandObserver observer; TransferTracker tracker(megaApi[0].get()); MegaUploadOptions options; options.fileName = remoteName; options.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; options.pitagTrigger = MegaApi::PITAG_TRIGGER_CAMERA; megaApi[0]->startUpload(localPathUtf8, rootNode.get(), nullptr, &options, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); const std::string expected = std::string{"U"} + options.pitagTrigger + "fD."; ASSERT_TRUE(observer.waitForValue(expected, waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForCreateFolder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; PitagCommandObserver observer; createFolder(0, "Folder", rootNode.get()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("F.FD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForUploadWithFolderController) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; PitagCommandObserver observer; createFolder(0, "Folder", rootNode.get()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("F.FD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForBatchFolderUpload) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; const std::string localFolderName = getFilePrefix() + "pitag_batch_folder"; const fs::path localFolderPath = fs::current_path() / localFolderName; sdk_test::LocalTempDir localFolder(localFolderPath); const std::vector<sdk_test::NodeInfo> localStructure{ sdk_test::DirNodeInfo("nested") .addChild(sdk_test::DirNodeInfo("inner").addChild( sdk_test::FileNodeInfo("inner_file.bin").setSize(8))) .addChild(sdk_test::FileNodeInfo("nested_file.bin").setSize(12)), sdk_test::FileNodeInfo("root_file_a.bin").setSize(10), sdk_test::FileNodeInfo("root_file_b.bin").setSize(14)}; ASSERT_NO_FATAL_FAILURE(createLocalTree(localFolderPath, localStructure)); PitagCommandObserver observer; TransferTracker tracker(megaApi[0].get()); MegaUploadOptions folderOptions; folderOptions.fileName = localFolderName; folderOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(localFolderPath.string(), rootNode.get(), nullptr, &folderOptions, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("U.FD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForIncomingShareUpload) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); std::unique_ptr<MegaNode> ownerRoot{megaApi[0]->getRootNode()}; ASSERT_TRUE(ownerRoot) << "Unable to get root node for owner account"; inviteTestAccount(0, 1, "Hi!!"); const std::string folderName = getFilePrefix() + "incomingShare"; RequestTracker folderTracker(megaApi[0].get()); megaApi[0]->createFolder(folderName.c_str(), ownerRoot.get(), &folderTracker); ASSERT_EQ(API_OK, folderTracker.waitForResult()) << "Failed to create folder for sharing"; std::unique_ptr<MegaNode> folderNode{ megaApi[0]->getNodeByHandle(folderTracker.request->getNodeHandle())}; ASSERT_TRUE(folderNode) << "Unable to obtain shared folder node"; ASSERT_NO_FATAL_FAILURE( shareFolder(folderNode.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); auto inShareAvailable = [this]() { std::unique_ptr<MegaShareList> shares{megaApi[1]->getInSharesList()}; return shares && shares->size() > 0; }; ASSERT_TRUE(WaitFor(inShareAvailable, defaultTimeoutMs)) << "Incoming share not received by sharee"; const MegaHandle sharedHandle = folderNode->getHandle(); ASSERT_TRUE(WaitFor( [this, sharedHandle, &folderName]() { std::unique_ptr<MegaNode> node{megaApi[1]->getNodeByHandle(sharedHandle)}; return node && node->isNodeKeyDecrypted() && node->getName() && folderName == node->getName(); }, defaultTimeoutMs)) << "Incoming share not decrypted by sharee"; std::unique_ptr<MegaNode> incomingNode{megaApi[1]->getNodeByHandle(sharedHandle)}; ASSERT_TRUE(incomingNode) << "Sharee cannot access incoming share node"; ASSERT_STREQ(folderName.c_str(), incomingNode->getName()); const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_inshare.bin"); const std::string localPathUtf8 = path_u8string(localFilePath); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-inshare-upload"); PitagCommandObserver observer; TransferTracker tracker(megaApi[1].get()); MegaUploadOptions shareOptions; shareOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; shareOptions.pitagTrigger = MegaApi::PITAG_TRIGGER_SCANNER; megaApi[1]->startUpload(localPathUtf8, incomingNode.get(), nullptr, &shareOptions, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); constexpr auto timeout = 3s; // short timeout, it has to be available const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(timeout); const std::string expected = std::string{"U"} + shareOptions.pitagTrigger + "fi."; ASSERT_TRUE(observer.waitForValue(expected, waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForBackgroundMediaUpload) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const auto sourcePath = fs::current_path() / (getFilePrefix() + "pitag_background_upload.bin"); const std::string encryptedPath = path_u8string(sourcePath) + ".enc"; // encryptFile output destination const std::string fileOutput = getFilePrefix() + "pitag_background_remote.bin"; // Create input file to upload through the background media upload pipeline size_t size = 1024; const sdk_test::LocalTempFile localFile(sourcePath, size); const int64_t fileSize = static_cast<int64_t>(fs::file_size(sourcePath)); PitagCommandObserver observer; synchronousMediaUpload(/*apiIndex*/ 0, fileSize, path_u8string(sourcePath).c_str(), encryptedPath.c_str(), fileOutput.c_str()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); const std::string expected = std::string{"U"} + MegaApi::PITAG_TRIGGER_CAMERA + "fD."; ASSERT_TRUE(observer.waitForValue(expected, waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForChatTargetUpload) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_chat_target.bin"); const std::string remoteName = localFilePath.filename().string(); const std::string localPathUtf8 = localFilePath.string(); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-chat-target-upload"); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; PitagCommandObserver observer; TransferTracker tracker(megaApi[0].get()); MegaUploadOptions options; options.fileName = remoteName; options.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; options.pitagTrigger = MegaApi::PITAG_TRIGGER_CAMERA; options.pitagTarget = MegaApi::PITAG_TARGET_CHAT_1TO1; megaApi[0]->startUpload(localPathUtf8, rootNode.get(), nullptr, &options, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); std::string expected; expected.push_back('U'); expected.push_back(options.pitagTrigger); expected.push_back('f'); expected.push_back(options.pitagTarget); expected.push_back('.'); ASSERT_TRUE(observer.waitForValue(expected, waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForCopyNodeFromCloudDrive) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; const std::string srcName = getFilePrefix() + "pitag_copy_src"; RequestTracker createTracker(megaApi[0].get()); megaApi[0]->createFolder(srcName.c_str(), rootNode.get(), &createTracker); ASSERT_EQ(API_OK, createTracker.waitForResult()); std::unique_ptr<MegaNode> srcNode{ megaApi[0]->getNodeByHandle(createTracker.request->getNodeHandle())}; ASSERT_TRUE(srcNode) << "Unable to get source folder"; PitagCommandObserver observer; RequestTracker copyTracker(megaApi[0].get()); const std::string dstName = getFilePrefix() + "pitag_copy_dst"; megaApi[0]->copyNode(srcNode.get(), rootNode.get(), dstName.c_str(), ©Tracker); ASSERT_EQ(API_OK, copyTracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("C.FDD", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForCopyNodeFromIncomingShare) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); std::unique_ptr<MegaNode> ownerRoot{megaApi[0]->getRootNode()}; ASSERT_TRUE(ownerRoot) << "Unable to get root node for owner account"; inviteTestAccount(0, 1, "Hi!!"); const std::string folderName = getFilePrefix() + "pitag_copy_inshare_src"; RequestTracker folderTracker(megaApi[0].get()); megaApi[0]->createFolder(folderName.c_str(), ownerRoot.get(), &folderTracker); ASSERT_EQ(API_OK, folderTracker.waitForResult()) << "Failed to create folder for sharing"; std::unique_ptr<MegaNode> folderNode{ megaApi[0]->getNodeByHandle(folderTracker.request->getNodeHandle())}; ASSERT_TRUE(folderNode) << "Unable to obtain shared folder node"; ASSERT_NO_FATAL_FAILURE( shareFolder(folderNode.get(), mApi[1].email.c_str(), MegaShare::ACCESS_FULL)); auto inShareAvailable = [this]() { std::unique_ptr<MegaShareList> shares{megaApi[1]->getInSharesList()}; return shares && shares->size() > 0; }; ASSERT_TRUE(WaitFor(inShareAvailable, defaultTimeoutMs)) << "Incoming share not received by sharee"; const MegaHandle sharedHandle = folderNode->getHandle(); ASSERT_TRUE(WaitFor( [this, sharedHandle, &folderName]() { std::unique_ptr<MegaNode> node{megaApi[1]->getNodeByHandle(sharedHandle)}; return node && node->isNodeKeyDecrypted() && node->getName() && folderName == node->getName(); }, 60 * 1000)) << "Incoming share node not ready"; std::unique_ptr<MegaNode> incomingNode{megaApi[1]->getNodeByHandle(sharedHandle)}; ASSERT_TRUE(incomingNode) << "Sharee cannot access incoming share node"; ASSERT_STREQ(folderName.c_str(), incomingNode->getName()); std::unique_ptr<MegaNode> shareeRoot{megaApi[1]->getRootNode()}; ASSERT_TRUE(shareeRoot) << "Unable to get sharee root node"; PitagCommandObserver observer; RequestTracker copyTracker(megaApi[1].get()); const std::string dstName = getFilePrefix() + "pitag_copy_from_inshare_dst"; megaApi[1]->copyNode(incomingNode.get(), shareeRoot.get(), dstName.c_str(), ©Tracker); ASSERT_EQ(API_OK, copyTracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("C.FDi", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForRemoteCopyUploadDedup) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode) << "Unable to get root node"; const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_remote_copy.bin"); const std::string localPathUtf8 = path_u8string(localFilePath); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-remote-copy"); // Initial upload to create the deduplication source in Cloud Drive { TransferTracker tracker(megaApi[0].get()); MegaUploadOptions options; options.fileName = localFilePath.filename().string(); options.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; megaApi[0]->startUpload(localPathUtf8, rootNode.get(), nullptr, &options, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); } PitagCommandObserver observer; TransferTracker tracker(megaApi[0].get()); MegaUploadOptions options; options.fileName = getFilePrefix() + "pitag_remote_copy_dest.bin"; options.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME; options.pitagTrigger = MegaApi::PITAG_TRIGGER_CAMERA; megaApi[0]->startUpload(localPathUtf8, rootNode.get(), nullptr, &options, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); const std::string expected = std::string{"c"} + options.pitagTrigger + "fD."; ASSERT_TRUE(observer.waitForValue(expected, waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitagPasswordManager, PitagCapturedForPasswordNodeCreation) { PitagCommandObserver observer; std::unique_ptr<MegaNode::PasswordNodeData> pwdData{ MegaNode::PasswordNodeData::createInstance("pwd", "notes", "url", "user", nullptr)}; const auto pwdHandle = sdk_test::createPasswordNode(mApi, getFilePrefix() + "pitag_pwd", pwdData.get(), getBaseHandle()); ASSERT_NE(pwdHandle, UNDEF) << "Password node was not created"; const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(SdkTestPasswordManager::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("P..D.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitagPasswordManager, PitagCapturedForPasswordFolderCreation) { PitagCommandObserver observer; const std::string folderName = getFilePrefix() + "pitag_pwm_folder"; MegaHandle folderHandle = createFolder(0, folderName.c_str(), getBaseNode().get()); ASSERT_NE(folderHandle, UNDEF) << "Password manager folder was not created"; const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(SdkTestPasswordManager::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("P.FD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForImportFileLink) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); std::unique_ptr<MegaNode> rootNode{megaApi[0]->getRootNode()}; ASSERT_TRUE(rootNode); RequestTracker folderTracker(megaApi[0].get()); const std::string targetFolderName = getFilePrefix() + "pitag_import_target"; megaApi[0]->createFolder(targetFolderName.c_str(), rootNode.get(), &folderTracker); ASSERT_EQ(API_OK, folderTracker.waitForResult()); std::unique_ptr<MegaNode> importParent{ megaApi[0]->getNodeByHandle(folderTracker.request->getNodeHandle())}; ASSERT_TRUE(importParent); const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_import.bin"); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-import"); const std::string localPathUtf8 = path_u8string(localFilePath); { TransferTracker tracker(megaApi[0].get()); megaApi[0]->startUpload(localPathUtf8, rootNode.get(), nullptr, nullptr, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); } std::unique_ptr<MegaNode> uploaded{ megaApi[0]->getNodeByPath((std::string("/") + localFilePath.filename().string()).c_str())}; ASSERT_TRUE(uploaded); RequestTracker linkTracker(megaApi[0].get()); megaApi[0]->exportNode(uploaded.get(), 0, false, false, &linkTracker); ASSERT_EQ(API_OK, linkTracker.waitForResult()); const char* link = linkTracker.request->getLink(); ASSERT_TRUE(link); PitagCommandObserver observer; RequestTracker importTracker(megaApi[0].get()); megaApi[0]->importFileLink(link, importParent.get(), &importTracker); ASSERT_EQ(API_OK, importTracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("I.fDf", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitag, PitagCapturedForImportFolderLink) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // Account 0 uploads a file inside a folder, exports the folder link std::unique_ptr<MegaNode> ownerRoot{megaApi[0]->getRootNode()}; ASSERT_TRUE(ownerRoot); const std::string folderName = getFilePrefix() + "pitag_import_folder"; const MegaHandle folderHandle = createFolder(0, folderName.c_str(), ownerRoot.get()); ASSERT_NE(folderHandle, UNDEF); std::unique_ptr<MegaNode> folder{megaApi[0]->getNodeByHandle(folderHandle)}; ASSERT_TRUE(folder); const auto localFilePath = fs::current_path() / (getFilePrefix() + "pitag_import_folder_file.bin"); const sdk_test::LocalTempFile localFile(localFilePath, "pitag-import-folder"); { TransferTracker tracker(megaApi[0].get()); megaApi[0]->startUpload(localFilePath.string(), folder.get(), nullptr, nullptr, &tracker); ASSERT_EQ(API_OK, tracker.waitForResult()); } RequestTracker linkTracker(megaApi[0].get()); megaApi[0]->exportNode(folder.get(), 0 /*expireTime*/, false /*writable*/, false /*megaHosted*/, &linkTracker); ASSERT_EQ(API_OK, linkTracker.waitForResult()); const char* folderLink = linkTracker.request->getLink(); ASSERT_TRUE(folderLink); // Account 1 logs into the folder link (guest), authorizes the folder, then logs back to import RequestTracker loginLinkTracker(megaApi[1].get()); megaApi[1]->loginToFolder(folderLink, &loginLinkTracker); ASSERT_EQ(API_OK, loginLinkTracker.waitForResult()); ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); std::unique_ptr<MegaNode> linkRoot{megaApi[1]->getRootNode()}; ASSERT_TRUE(linkRoot); std::unique_ptr<MegaNode> authorized{megaApi[1]->authorizeNode(linkRoot.get())}; ASSERT_TRUE(authorized); RequestTracker logoutLink(megaApi[1].get()); megaApi[1]->logout(false, &logoutLink); ASSERT_EQ(API_OK, logoutLink.waitForResult()); ASSERT_NO_FATAL_FAILURE(login(1)); ASSERT_NO_FATAL_FAILURE(fetchnodes(1)); std::unique_ptr<MegaNode> destRoot{megaApi[1]->getRootNode()}; ASSERT_TRUE(destRoot); PitagCommandObserver observer; RequestTracker copyTracker(megaApi[1].get()); megaApi[1]->copyNode(authorized.get(), destRoot.get(), nullptr, ©Tracker); ASSERT_EQ(API_OK, copyTracker.waitForResult()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("I.FDF", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } #ifdef ENABLE_SYNC TEST_F(SdkTestPitagSyncUpload, PitagCapturedForSyncUpload) { PitagCommandObserver observer; const fs::path filePath = getLocalTmpDir() / "dir1" / (getFilePrefix() + "pitag_sync_upload.bin"); ASSERT_TRUE(fs::exists(filePath.parent_path()) || fs::create_directories(filePath.parent_path())); ASSERT_NO_THROW(sdk_test::createFile(filePath, 1)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("SafD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitagSyncUpload, PitagCapturedForSyncRemoteCopy) { const fs::path firstFile = getLocalTmpDir() / "dir1" / (getFilePrefix() + "pitag_sync_clone_src.bin"); ASSERT_TRUE(fs::exists(firstFile.parent_path()) || fs::create_directories(firstFile.parent_path())); const auto commonMtime = fs::file_time_type::clock::now(); ASSERT_NO_THROW(sdk_test::createFile(firstFile, "c", commonMtime)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); PitagCommandObserver observer; const fs::path cloneFile = getLocalTmpDir() / "dir2" / (getFilePrefix() + "pitag_sync_clone_dst.bin"); ASSERT_TRUE(fs::exists(cloneFile.parent_path()) || fs::create_directories(cloneFile.parent_path())); ASSERT_NO_THROW(sdk_test::createFile(cloneFile, "c", commonMtime)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("cafD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } TEST_F(SdkTestPitagBackupUpload, PitagCapturedForBackupUpload) { createBackupSync(); ASSERT_NE(getBackupId(), INVALID_HANDLE); mApi[0].addsyncMonitor(getBackupId()); ASSERT_TRUE(mApi[0].waitForBackupSyncUpToDate(getBackupId())); PitagCommandObserver observer; const fs::path filePath = getLocalFolderPath() / (getFilePrefix() + "pitag_backup_upload.bin"); ASSERT_NO_THROW(sdk_test::createFile(filePath, 1)); ASSERT_TRUE(mApi[0].waitForBackupSyncUpToDate(getBackupId())); const auto waitTimeout = std::chrono::duration_cast<std::chrono::milliseconds>(sdk_test::MAX_TIMEOUT); ASSERT_TRUE(observer.waitForValue("BafD.", waitTimeout)) << "Unexpected pitag payload captured: " << observer.capturedValue(); } #endif // ENABLE_SYNC #endif // MEGASDK_DEBUG_TEST_HOOKS_ENABLED ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_share.cpp����������������������������������������������������0000664�0000000�0000000�00000133634�15162662266�0021654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "sdk_test_share.h" #include <gmock/gmock.h> #include <chrono> #include <thread> class SdkTestShareOrder: public SdkTestShare { protected: class HandleUserPair { public: HandleUserPair(MegaHandle handle, const std::string& user, int64_t timestamp = 0): mHandle(handle), mUser(user), mTimestamp(timestamp) {} private: MegaHandle mHandle; std::string mUser; int64_t mTimestamp; // only for trace purpose friend bool operator==(const HandleUserPair& lhs, const HandleUserPair& rhs) { return lhs.mHandle == rhs.mHandle && lhs.mUser == rhs.mUser; } friend void PrintTo(const HandleUserPair& handleUserPair, std::ostream* os) { *os << "{" << toNodeHandle(handleUserPair.mHandle) << ", " << handleUserPair.mUser << ", " << handleUserPair.mTimestamp << "}"; } }; std::vector<HandleUserPair> toHandleUserPair(MegaShareList* shareList); bool waitForOutShare(MegaApi& api, unsigned expectedNum); }; void SdkTestShare::createShareAtoB(MegaNode* node, const Party& partyA, const Party& partyB, int accessType) { assert(partyA.apiIndex < mApi.size()); assert(partyB.apiIndex < mApi.size()); // convinience auto& apiA = mApi[partyA.apiIndex]; auto& apiB = mApi[partyB.apiIndex]; apiA.nodeUpdated = apiB.nodeUpdated = false; apiA.mOnNodesUpdateCompletion = createOnNodesUpdateLambda(node->getHandle(), MegaNode::CHANGE_TYPE_OUTSHARE, apiA.nodeUpdated); apiB.mOnNodesUpdateCompletion = createOnNodesUpdateLambda(node->getHandle(), MegaNode::CHANGE_TYPE_INSHARE, apiB.nodeUpdated); ASSERT_NO_FATAL_FAILURE(shareFolder(node, apiB.email.c_str(), accessType, partyA.apiIndex)); if (partyA.wait) { ASSERT_TRUE(waitForResponse(&apiA.nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; } if (partyB.wait) { ASSERT_TRUE(waitForResponse(&apiB.nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; } resetOnNodeUpdateCompletionCBs(); apiA.nodeUpdated = apiB.nodeUpdated = false; } void SdkTestShare::createShareAtoB(MegaNode* node, bool waitForA, bool waitForB, int accessType) { createShareAtoB(node, {0, waitForA}, {1, waitForB}, accessType); } void SdkTestShare::removeShareAtoB(MegaNode* node, unsigned apiIndexA, unsigned apiIndexB) { assert(apiIndexA < mApi.size()); assert(apiIndexB < mApi.size()); mApi[apiIndexA].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(node->getHandle(), MegaNode::CHANGE_TYPE_OUTSHARE, mApi[apiIndexA].nodeUpdated); mApi[apiIndexB].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(node->getHandle(), MegaNode::CHANGE_TYPE_REMOVED, mApi[apiIndexB].nodeUpdated); ASSERT_NO_FATAL_FAILURE( shareFolder(node, mApi[apiIndexB].email.c_str(), MegaShare::ACCESS_UNKNOWN, apiIndexA)); ASSERT_TRUE(waitForResponse(&mApi[apiIndexA].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[apiIndexB].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[apiIndexA].nodeUpdated = mApi[apiIndexB].nodeUpdated = false; } void SdkTestShare::removeShareAtoB(MegaNode* node) { removeShareAtoB(node, 0, 1); } void SdkTestShare::resetCredentialsIfContactFound(const unsigned i, const unsigned j) { std::unique_ptr<MegaUser> c1(mApi[i].megaApi->getContact(mApi[j].email.c_str())); std::unique_ptr<MegaUser> c2(mApi[j].megaApi->getContact(mApi[i].email.c_str())); if (!c1 || !c2) { return; } ASSERT_NO_FATAL_FAILURE(resetCredential(i, j)); } void SdkTestShare::resetCredential(unsigned apiIndexA, unsigned apiIndexB) { if (areCredentialsVerified(apiIndexA, mApi[apiIndexB].email)) { ASSERT_NO_FATAL_FAILURE(resetCredentials(apiIndexA, mApi[apiIndexB].email)); ASSERT_FALSE(areCredentialsVerified(apiIndexA, mApi[apiIndexB].email)); } if (areCredentialsVerified(apiIndexB, mApi[apiIndexA].email)) { ASSERT_NO_FATAL_FAILURE(resetCredentials(apiIndexB, mApi[apiIndexA].email)); ASSERT_FALSE(areCredentialsVerified(apiIndexB, mApi[apiIndexA].email)); } } void SdkTestShare::verifyContactCredentials(unsigned apiIndexA, unsigned apiIndexB) { if (!areCredentialsVerified(apiIndexA, mApi[apiIndexB].email)) { ASSERT_NO_FATAL_FAILURE(verifyCredentials(apiIndexA, mApi[apiIndexB].email)); ASSERT_NO_FATAL_FAILURE(areCredentialsVerified(apiIndexA, mApi[apiIndexB].email)); } if (!areCredentialsVerified(apiIndexB, mApi[apiIndexA].email)) { ASSERT_NO_FATAL_FAILURE(verifyCredentials(apiIndexB, mApi[apiIndexA].email)); ASSERT_NO_FATAL_FAILURE(areCredentialsVerified(apiIndexB, mApi[apiIndexA].email)); } } void SdkTestShare::addContactsAndVerifyCredential(unsigned fromApiIndex, unsigned toApiIndex) { mApi[fromApiIndex].contactRequestUpdated = mApi[toApiIndex].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(fromApiIndex, mApi[toApiIndex].email, "TestSharesContactVerification contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[fromApiIndex].contactRequestUpdated)) << "Inviting contact timeout: " << maxTimeout << " seconds."; ASSERT_TRUE(waitForResponse(&mApi[toApiIndex].contactRequestUpdated)) << "Waiting for invitation timeout: " << maxTimeout << " seconds."; ASSERT_NO_FATAL_FAILURE(getContactRequest(toApiIndex, false)); mApi[fromApiIndex].contactRequestUpdated = mApi[toApiIndex].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(replyContact(mApi[toApiIndex].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT, toApiIndex)); ASSERT_TRUE(waitForResponse(&mApi[toApiIndex].contactRequestUpdated)) << "Accepting contact timeout: " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[fromApiIndex].contactRequestUpdated)) << "Waiting for invitation acceptance timeout: " << maxTimeout << " seconds"; // Verify credentials: LOG_verbose << "TestSharesContactVerification : Verify A and B credentials"; verifyContactCredentials(fromApiIndex, toApiIndex); } std::pair<MegaHandle, std::unique_ptr<MegaNode>> SdkTestShare::createFolder(unsigned int apiIndex, const char* name, MegaNode* parent) { MegaHandle nh = SdkTest::createFolder(apiIndex, name, parent); std::unique_ptr<MegaNode> node{megaApi[apiIndex]->getNodeByHandle(nh)}; return {nh, std::move(node)}; } std::vector<SdkTestShareOrder::HandleUserPair> SdkTestShareOrder::toHandleUserPair(MegaShareList* shareList) { std::vector<SdkTestShareOrder::HandleUserPair> ret; if (!shareList) { return ret; } for (int i = 0; i < shareList->size(); ++i) { auto share = shareList->get(i); ret.push_back({share->getNodeHandle(), share->getUser(), share->getTimestamp()}); } return ret; } bool SdkTestShareOrder::waitForOutShare(MegaApi& api, unsigned expectedNum) { return WaitFor( [&api, expectedNum]() { return unique_ptr<MegaShareList>(api.getOutShares())->size() == static_cast<int>(expectedNum); }, 60 * 1000); } /** * @brief TEST_F TestSharesContactVerification * * Test contact verification for shares * */ TEST_F(SdkTestShare, TestSharesContactVerification) { // What we are going to test here: // 1: Create a share between A and B, being A and B already contacts in the following scenarios: // 1-1: A and B credentials already verified by both. // 1-2: A has verified B, but B has not verified A. B verifies A after creating the share. // 1-3: None are verified. Then A verifies B and later B verifies A. // 2: Create a share between A and B, being A and B no contacts. LOG_info << "___TEST TestSharesContactVerification___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); // The idea of the test is to ensure manual verification works as expected, so force it to true megaApi[0]->setManualVerificationFlag(true); megaApi[1]->setManualVerificationFlag(true); // Define all folers needed for the tests. string folder11 = "EnhancedSecurityShares-1"; string folder12 = "EnhancedSecurityShares-21"; string folder13 = "EnhancedSecurityShares-22"; string folder2 = "EnhancedSecurityShares-23"; std::unique_ptr<MegaNode> remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); // // 1: Create a share between A and B, being A and B already contacts. // // Make accounts contacts LOG_verbose << "TestSharesContactVerification : Make account contacts"; mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(inviteContact(0, mApi[1].email, "TestSharesContactVerification contact request A to B", MegaContactRequest::INVITE_ACTION_ADD)); ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) << "Inviting contact timeout: " << maxTimeout << " seconds."; ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) << "Waiting for invitation timeout: " << maxTimeout << " seconds."; ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) << "Accepting contact timeout: " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) << "Waiting for invitation acceptance timeout: " << maxTimeout << " seconds"; mApi[0].cr.reset(); mApi[1].cr.reset(); // Ensure no account has the other verified from previous unfinished tests. if (areCredentialsVerified(0, mApi[1].email)) { ASSERT_NO_FATAL_FAILURE(resetCredentials(0, mApi[1].email)); } if (areCredentialsVerified(1, mApi[0].email)) { ASSERT_NO_FATAL_FAILURE(resetCredentials(1, mApi[0].email)); } // // 1-1: A and B credentials already verified by both. // fs::path basePath = u8path_compat(folder11.c_str()); auto [nh, remoteBaseNode] = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; ASSERT_NE(remoteBaseNode.get(), nullptr); // Verify credentials: LOG_verbose << "TestSharesContactVerification : Verify A and B credentials"; ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email)); ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email)); ASSERT_TRUE(areCredentialsVerified(0, mApi[1].email)); ASSERT_TRUE(areCredentialsVerified(1, mApi[0].email)); // Create share. // B should end with a new inshare and able to decrypt the new node. LOG_verbose << "TestSharesContactVerification : Share a folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); std::unique_ptr<MegaNode> inshareNode(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(WaitFor( [this, nh = nh]() { return unique_ptr<MegaNode>(megaApi[1]->getNodeByHandle(nh))->isNodeKeyDecrypted(); }, 60 * 1000)) << "Cannot decrypt inshare in B account."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Share the same node again LOG_verbose << "TestSharesContactVerification : Share again the same folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(WaitFor( [this, nh = nh]() { return unique_ptr<MegaNode>(megaApi[1]->getNodeByHandle(nh))->isNodeKeyDecrypted(); }, 60 * 1000)) << "Cannot decrypt inshare in B account."; // Auxiliar function to reset both accounts credentials verification auto resetAllCredentials = [this]() { ASSERT_NO_FATAL_FAILURE(resetCredentials(0, mApi[1].email)); ASSERT_NO_FATAL_FAILURE(resetCredentials(1, mApi[0].email)); ASSERT_FALSE(areCredentialsVerified(0, mApi[1].email)); ASSERT_FALSE(areCredentialsVerified(1, mApi[0].email)); }; // Reset credentials LOG_verbose << "TestSharesContactVerification : Reset credentials"; ASSERT_NO_FATAL_FAILURE(resetAllCredentials()); // Established share remains in the same status. ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(inshareNode->isNodeKeyDecrypted()) << "Cannot decrypt inshare in B account."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); ASSERT_NO_FATAL_FAILURE(resetCredential(0, 1)); // // 1-2: A has verified B, but B has not verified A. B verifies A after creating the share. // basePath = u8path_compat(folder12.c_str()); // Use a different node std::tie(nh, remoteBaseNode) = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; ASSERT_NE(remoteBaseNode.get(), nullptr); // Verify credentials LOG_verbose << "TestSharesContactVerification : Verify B credentials:"; ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email)); ASSERT_TRUE(areCredentialsVerified(0, mApi[1].email)); ASSERT_FALSE(areCredentialsVerified(1, mApi[0].email)); // Create share // B should end with an unverified inshare and be unable to decrypt the node. LOG_verbose << "TestSharesContactVerification : Share a folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 1; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Share the same node again LOG_verbose << "TestSharesContactVerification : Share again the same folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 1; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Verify A credentials in B account // B unverified inshare should end as a functional inshare. It should be able to decrypt the new // node. LOG_verbose << "TestSharesContactVerification : Verify A credentials"; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nh, MegaNode::CHANGE_TYPE_NAME, mApi[1].nodeUpdated); // file name is set when decrypted. ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email)); ASSERT_TRUE(areCredentialsVerified(1, mApi[0].email)); ASSERT_TRUE(waitForResponse(&mApi[1].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(WaitFor( [this, nh = nh]() { return unique_ptr<MegaNode>(megaApi[1]->getNodeByHandle(nh))->isNodeKeyDecrypted(); }, 60 * 1000)) << "Cannot decrypt inshare in B account."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Reset credentials: LOG_verbose << "TestSharesContactVerification : Reset credentials"; ASSERT_NO_FATAL_FAILURE(resetCredential(0, 1)); // // 1-3: None are verified. Then A verifies B and later B verifies A. // basePath = u8path_compat(folder13.c_str()); // Use a different node std::tie(nh, remoteBaseNode) = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; ASSERT_NE(remoteBaseNode.get(), nullptr); // Create share. // A should end with an unverified outshare and B should have an unverified inshare and unable // to decrypt the new node. LOG_verbose << "TestSharesContactVerification : Share a folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Share the same node again LOG_verbose << "TestSharesContactVerification : Share again the same folder from A to B"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Verify B credentials in A account // Unverified outshare in A should disappear and be a regular outshare. No changes expected in // B, the share should still be unverified. LOG_verbose << "TestSharesContactVerification : Verify B credentials"; ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email)); ASSERT_TRUE(areCredentialsVerified(0, mApi[1].email)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 1; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Verify A credentials in B account // B unverified inshare should end as a functional inshare. It should be able to decrypt the new // node. LOG_verbose << "TestSharesContactVerification : Verify A credentials"; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nh, MegaNode::CHANGE_TYPE_NAME, mApi[1].nodeUpdated); ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email)); ASSERT_TRUE(areCredentialsVerified(1, mApi[0].email)); ASSERT_TRUE(waitForResponse(&mApi[1].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(WaitFor( [this, nh = nh]() { return unique_ptr<MegaNode>(megaApi[1]->getNodeByHandle(nh))->isNodeKeyDecrypted(); }, 60 * 1000)) << "Cannot decrypt inshare in B account."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Reset credentials LOG_verbose << "TestSharesContactVerification : Reset credentials"; ASSERT_NO_FATAL_FAILURE(resetCredential(0, 1)); // Delete contacts LOG_verbose << "TestSharesContactVerification : Remove Contact"; ASSERT_EQ(API_OK, removeContact(0, mApi[1].email)); unique_ptr<MegaUser> user(megaApi[0]->getContact(mApi[1].email.c_str())); ASSERT_FALSE(user == nullptr) << "Not user for contact email: " << mApi[1].email; ASSERT_EQ(MegaUser::VISIBILITY_HIDDEN, user->getVisibility()) << "Contact is still visible after removing it." << mApi[1].email; // // 2: Create a share between A and B, being A and B no contacts. // basePath = u8path_compat(folder2.c_str()); // Use a different node std::tie(nh, remoteBaseNode) = createFolder(0, path_u8string(basePath).c_str(), remoteRootNode.get()); ASSERT_NE(nh, UNDEF) << "Error creating remote basePath"; remoteBaseNode.reset(megaApi[0]->getNodeByHandle(nh)); ASSERT_NE(remoteBaseNode.get(), nullptr); // Create share. // Since are no contacts, B should receive a contact request. LOG_verbose << "TestSharesContactVerification : Share a folder from A to B"; mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE(createShareAtoB(remoteBaseNode.get(), false, false)); ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) << "Inviting contact timeout: " << maxTimeout << " seconds."; ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) << "Waiting for invitation timeout: " << maxTimeout << " seconds."; ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // B accepts the contact request. // Now B should end with an inshare without 'pk' yet, so "verified". mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false; ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT)); ASSERT_TRUE(waitForResponse(&mApi[1].contactRequestUpdated)) << "Accepting contact timeout: " << maxTimeout << " seconds"; ASSERT_TRUE(waitForResponse(&mApi[0].contactRequestUpdated)) << "Waiting for invitation acceptance timeout: " << maxTimeout << " seconds"; ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); // Verify B credentials in A account // Unverified outshare in A should disappear and be a regular outshare. No changes expected in // B, the share should still be unverified. LOG_verbose << "TestSharesContactVerification : Verify B credentials"; ASSERT_NO_FATAL_FAILURE(verifyCredentials(0, mApi[1].email)); ASSERT_TRUE(areCredentialsVerified(0, mApi[1].email)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 1; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_FALSE(inshareNode->isNodeKeyDecrypted()) << "Inshare is decrypted in B account, and it should be not."; // Verify A credentials in B account // B unverified inshare should end as a functional inshare. It should be able to decrypt the new // node. LOG_verbose << "TestSharesContactVerification : Verify A credentials"; mApi[1].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(nh, MegaNode::CHANGE_TYPE_NAME, mApi[1].nodeUpdated); ASSERT_NO_FATAL_FAILURE(verifyCredentials(1, mApi[0].email)); ASSERT_TRUE(areCredentialsVerified(1, mApi[0].email)); ASSERT_TRUE(waitForResponse(&mApi[1].nodeUpdated)) << "Node update not received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); mApi[0].nodeUpdated = mApi[1].nodeUpdated = false; ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_NE(inshareNode.get(), nullptr); ASSERT_TRUE(WaitFor( [this, nh = nh]() { return unique_ptr<MegaNode>(megaApi[1]->getNodeByHandle(nh))->isNodeKeyDecrypted(); }, 60 * 1000)) << "Cannot decrypt inshare in B account."; // Remove share LOG_verbose << "TestSharesContactVerification : Remove shared folder from A to B"; ASSERT_NO_FATAL_FAILURE(removeShareAtoB(remoteBaseNode.get())); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[0]->getUnverifiedOutShares())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 0; }, 60 * 1000)); ASSERT_TRUE(WaitFor( [this]() { return unique_ptr<MegaShareList>(megaApi[1]->getUnverifiedInShares())->size() == 0; }, 60 * 1000)); inshareNode.reset(megaApi[1]->getNodeByHandle(nh)); ASSERT_EQ(inshareNode.get(), nullptr); // Reset credentials LOG_verbose << "TestSharesContactVerification : Reset credentials"; ASSERT_NO_FATAL_FAILURE(resetCredential(0, 1)); // Delete contacts LOG_verbose << "TestSharesContactVerification : Remove Contact"; ASSERT_EQ(API_OK, removeContact(0, mApi[1].email)); user.reset(megaApi[0]->getContact(mApi[1].email.c_str())); ASSERT_FALSE(user == nullptr) << "Not user for contact email: " << mApi[1].email; ASSERT_EQ(MegaUser::VISIBILITY_HIDDEN, user->getVisibility()) << "Contact is still visible after removing it." << mApi[1].email; } TEST_F(SdkTestShareOrder, GetOutSharesOrUnverifiedOutSharesOrderedByCreationTime) { LOG_info << "___TEST TestSharesContactVerification___"; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(3)); megaApi[0]->setManualVerificationFlag(true); megaApi[1]->setManualVerificationFlag(true); megaApi[2]->setManualVerificationFlag(true); // Ensure no account has the other verified from previous unfinished tests. ASSERT_NO_FATAL_FAILURE(resetCredentialsIfContactFound(0, 1)); ASSERT_NO_FATAL_FAILURE(resetCredentialsIfContactFound(0, 2)); LOG_info << "Invite from account 0 to 1 and verify credential"; ASSERT_NO_FATAL_FAILURE(addContactsAndVerifyCredential(0, 1)); // Root node std::unique_ptr<MegaNode> remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); LOG_info << "Create folders"; auto [handle1, shareNode1] = createFolder(0, "share1", remoteRootNode.get()); auto [handle2, shareNode2] = createFolder(0, "share2", remoteRootNode.get()); ASSERT_THAT(shareNode1, testing::NotNull()); ASSERT_THAT(shareNode2, testing::NotNull()); LOG_info << "Share folder from account 0 to account 1 share node 2"; unsigned expectedNum = 1; auto& api = *megaApi[0].get(); ASSERT_NO_FATAL_FAILURE(createShareAtoB(shareNode2.get())); ASSERT_TRUE(waitForOutShare(api, expectedNum++)); LOG_info << "Share folder from account 0 to account 2 share node 2"; // Share creation time might be same if they are created too close, delay 3 seconds std::this_thread::sleep_for(std::chrono::seconds{3}); ASSERT_NO_FATAL_FAILURE(createShareAtoB(shareNode2.get(), {0, false}, {2, false})); ASSERT_TRUE(waitForOutShare(api, expectedNum++)); LOG_info << "Share folder from account 0 to account 2 share node 1"; std::this_thread::sleep_for(std::chrono::seconds{3}); ASSERT_NO_FATAL_FAILURE(createShareAtoB(shareNode1.get(), {0, false}, {2, false})); ASSERT_TRUE(waitForOutShare(api, expectedNum++)); auto user1 = mApi[1].email; auto user2 = mApi[2].email; auto shares = toHandleUserPair(megaApi[0]->getOutShares(MegaApi::ORDER_SHARE_CREATION_ASC)); auto expectedShares = std::vector<HandleUserPair>{ {handle2, user1}, {handle2, user2}, {handle1, user2}, }; ASSERT_THAT(shares, testing::ElementsAreArray(expectedShares)); shares = toHandleUserPair(megaApi[0]->getUnverifiedOutShares(MegaApi::ORDER_SHARE_CREATION_ASC)); expectedShares = std::vector<HandleUserPair>{ {handle2, user2}, {handle1, user2}, }; ASSERT_THAT(shares, testing::ElementsAreArray(expectedShares)); shares = toHandleUserPair(megaApi[0]->getOutShares(MegaApi::ORDER_SHARE_CREATION_DESC)); expectedShares = std::vector<HandleUserPair>{ {handle1, user2}, {handle2, user2}, {handle2, user1}, }; ASSERT_THAT(shares, testing::ElementsAreArray(expectedShares)); shares = toHandleUserPair(megaApi[0]->getUnverifiedOutShares(MegaApi::ORDER_SHARE_CREATION_DESC)); expectedShares = std::vector<HandleUserPair>{ {handle1, user2}, {handle2, user2}, }; ASSERT_THAT(shares, testing::ElementsAreArray(expectedShares)); } TEST_F(SdkTestShare, TestSharesPermission) { static const auto logPre = getLogPrefix(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(3)); // Ensure no account has the other verified from previous unfinished tests. ASSERT_NO_FATAL_FAILURE(resetCredentialsIfContactFound(0, 1)); ASSERT_NO_FATAL_FAILURE(resetCredentialsIfContactFound(0, 2)); LOG_info << logPre << "#### Test preconditions. Invite from account 0 to 1 and verify " "credential ####"; ASSERT_NO_FATAL_FAILURE(addContactsAndVerifyCredential(0, 1)); LOG_info << logPre << "#### Test preconditions. Invite from account 0 to 2 and verify " "credential ####"; ASSERT_NO_FATAL_FAILURE(addContactsAndVerifyCredential(0, 2)); // Root node std::unique_ptr<MegaNode> remoteRootNode(megaApi[0]->getRootNode()); ASSERT_NE(remoteRootNode.get(), nullptr); LOG_info << logPre << "#### Test preconditions. Create folders ####"; auto [handle1, shareNode1] = createFolder(0, "share1", remoteRootNode.get()); auto [handle2, shareNode2] = createFolder(0, "share2", remoteRootNode.get()); ASSERT_THAT(shareNode1, testing::NotNull()); ASSERT_THAT(shareNode2, testing::NotNull()); auto waitForNode = [this](const unsigned idx, const std::string& sharedPath, std::shared_ptr<MegaNode>& sharedNode) -> std::function<bool()> { // Wait until node attr's is decrypted, otherwise getNodeByPath could not retrieve // it, even if it has already been received return [=, this, &sharedNode]() { sharedNode.reset(megaApi[idx]->getNodeByPath(sharedPath.c_str())); return !!sharedNode; }; }; LOG_info << logPre << "#### Test1 Share (full access) folder from account 0 to account " "1 share node 1 ####"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(shareNode1.get(), true, true, MegaShare::ACCESS_FULL)); { std::shared_ptr<MegaNode> sharedNode; string sharedPath = megaApi[0]->getMyEmail(); sharedPath.append(":share1"); RequestTracker listener{megaApi[1].get()}; ASSERT_TRUE(WaitFor(waitForNode(1, sharedPath, sharedNode), 60 * 1000)) << "Cannot get outshare in A account."; megaApi[1]->setNodeLabel(sharedNode.get(), 1, &listener); ASSERT_TRUE(API_OK == listener.waitForResult()); ASSERT_TRUE(WaitFor( [this]() -> bool { std::unique_ptr<MegaNode> sharedNode(megaApi[0]->getNodeByPath("/share1")); return (1 == sharedNode->getLabel()); }, 20 * 1000)); } LOG_info << logPre << "#### Test2. Share (Read and Write) folder from account 0 to " "account 2 share node 2 ####"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(shareNode2.get(), {0, true}, {2, true})); { std::shared_ptr<MegaNode> sharedNode; string sharedPath = megaApi[0]->getMyEmail(); RequestTracker listener{megaApi[2].get()}; sharedPath.append(":share2"); ASSERT_TRUE(WaitFor(waitForNode(2, sharedPath, sharedNode), 60 * 1000)) << "Cannot get outshare in A account."; megaApi[2]->setNodeLabel(sharedNode.get(), 1, &listener); ASSERT_TRUE(API_EACCESS == listener.waitForResult()); } } ����������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_share.h������������������������������������������������������0000664�0000000�0000000�00000003027�15162662266�0021311�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef SDK_TEST_SHARE_H #define SDK_TEST_SHARE_H #include "SdkTest_test.h" class SdkTestShare: public virtual SdkTest { protected: struct Party { unsigned apiIndex; bool wait; // wait for response }; void createShareAtoB(MegaNode* node, const Party& partyA, const Party& partyB, int accessType = MegaShare::ACCESS_READWRITE); // Use mApi[0] as party A and mApi[1] as party B void createShareAtoB(MegaNode* node, bool waitForA = true, bool waitForB = true, int accessType = MegaShare::ACCESS_READWRITE); // Remove a share ensuring node changes are notified. void removeShareAtoB(MegaNode* node, unsigned apiIndexA, unsigned apiIndexB); // Use mApi[0] and mApi[1] void removeShareAtoB(MegaNode* node); // Reset credential between two accounts if contact found void resetCredentialsIfContactFound(const unsigned i, const unsigned j); // Reset credential between two accounts void resetCredential(unsigned apiIndexA, unsigned apiIndexB); // Verify contact credentials between two accounts. void verifyContactCredentials(unsigned apiIndexA, unsigned apiIndexB); void addContactsAndVerifyCredential(unsigned apiIndexA, unsigned apiIndexB); [[nodiscard]] std::pair<MegaHandle, std::unique_ptr<MegaNode>> createFolder(unsigned int apiIndex, const char* name, MegaNode* parent); }; #endif // SDK_TEST_SHARE_H ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_share_nested.cpp���������������������������������������������0000664�0000000�0000000�00000052257�15162662266�0023217�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sdk_test_share_nested.cpp * @brief This file defines tests related with nested shares */ #include "integration_test_utils.h" #include "sdk_test_share.h" #include "SdkTestNodesSetUp.h" using namespace sdk_test; class SdkTestShareNested: public virtual SdkTestShare, public virtual SdkTestNodesSetUp { public: void SetUp() override { SdkTestShare::SetUp(); ASSERT_NO_FATAL_FAILURE(getAccountsForTest(3)); ASSERT_NO_FATAL_FAILURE(createRootTestDir()); createNodes(getElements(), getRootTestDirectory()); } const std::string& getRootTestDir() const override { return rootTestDir; } const std::vector<sdk_test::NodeInfo>& getElements() const override { return treeElements; } // Override, we don't need to have different creation time. bool keepDifferentCreationTimes() override { return false; } // Create a file node in the remote account. // Optionally, it will be validate in apiIndexB acount. void createRemoteFileNode(unsigned apiIndexA, const sdk_test::FileNodeInfo& fileInfo, MegaNode* rootnode, std::optional<unsigned> apiIndexB) { bool checkA{false}; bool checkB{false}; mApi[apiIndexA].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, checkA); if (apiIndexB) { mApi[*apiIndexB].mOnNodesUpdateCompletion = createOnNodesUpdateLambda(INVALID_HANDLE, MegaNode::CHANGE_TYPE_NEW, checkB); } sdk_test::LocalTempFile localFile{fileInfo.name, fileInfo.size}; MegaHandle file1Handle = INVALID_HANDLE; ASSERT_EQ(MegaError::API_OK, doStartUpload(apiIndexA, &file1Handle, fileInfo.name.c_str(), rootnode, nullptr /*fileName*/, fileInfo.mtime, nullptr /*appData*/, false /*isSourceTemporary*/, false /*startFirst*/, nullptr /*cancelToken*/)) << "Failure uploading a file"; ASSERT_TRUE(waitForResponse(&checkA)) << "New node not received on client " << apiIndexA << " after " << maxTimeout << " seconds"; if (apiIndexB) { ASSERT_TRUE(waitForResponse(&checkB)) << "New node not received on client " << *apiIndexB << " after " << maxTimeout << " seconds"; } resetOnNodeUpdateCompletionCBs(); std::unique_ptr<MegaNode> nodeFile{megaApi[apiIndexA]->getNodeByHandle(file1Handle)}; ASSERT_NE(nodeFile, nullptr) << "Cannot get the node for the updated file (error: " << mApi[apiIndexA].lastError << ")"; setNodeAdditionalAttributes(fileInfo, nodeFile); } void matchTree(const MegaHandle rootHandle, unsigned apiIndexA, unsigned apiIndexB) const { const std::unique_ptr<MegaNode> rootNodeA{megaApi[apiIndexA]->getNodeByHandle(rootHandle)}; const std::unique_ptr<MegaNode> rootNodeB{megaApi[apiIndexB]->getNodeByHandle(rootHandle)}; ASSERT_TRUE(rootNodeA) << "Node not present the accout #" << apiIndexA << ". Handle: " << toNodeHandle(rootHandle); ASSERT_TRUE(rootNodeB) << "Node not present the second accout#" << apiIndexB << ". Handle: " << toNodeHandle(rootHandle); ASSERT_NO_FATAL_FAILURE( matchTreeRecurse(rootNodeA.get(), rootNodeB.get(), apiIndexA, apiIndexB)); } bool waitForNodeToBeDecrypted(unsigned apiIndex, MegaHandle nodeHandle) { return WaitFor( [this, apiIndex, nodeHandle]() { unique_ptr<MegaNode> node(megaApi[apiIndex]->getNodeByHandle(nodeHandle)); return node && node->isNodeKeyDecrypted(); }, defaultTimeoutMs); } protected: // Name of the initial elements in the remote tree static constexpr auto FOLDER_A = "folderA"; static constexpr auto FOLDER_B = "folderB"; static constexpr auto FOLDER_C = "folderC"; static constexpr auto FILE_A = "fileA"; static constexpr auto FILE_B = "fileB"; static constexpr auto FILE_C = "fileC"; static constexpr unsigned sharerIndex{0}; static constexpr unsigned shareeAliceIndex{1}; static constexpr unsigned shareeBobIndex{2}; private: // root in the cloud where the tree is created const std::string rootTestDir{"locklessCS"}; // It represents the following tree: // RemoteRoot // └── "folderA" //    ├── "fileA" //    └── "folderB" //    ├── "fileB" //    └── "folderC" //    └── "fileC" const std::vector<NodeInfo> treeElements{ DirNodeInfo(FOLDER_A) .addChild(FileNodeInfo(FILE_A).setSize(100)) .addChild( DirNodeInfo(FOLDER_B) .addChild(FileNodeInfo(FILE_B).setSize(100)) .addChild(DirNodeInfo(FOLDER_C).addChild(FileNodeInfo(FILE_C).setSize(100))))}; // Check if the passed nodes have the same handle, if they are are decrypted and if they have // the same name. Print meaninful messages depending on the assert. void verifySameNodes(MegaNode* nodeA, unsigned apiIndexA, MegaNode* nodeB, unsigned apiIndexB) const { ASSERT_TRUE(nodeA && nodeB) << "Invalid nodes in the comparision."; ASSERT_EQ(nodeA->getHandle(), nodeB->getHandle()) << "Handles don't match. " << toNodeHandle(nodeA->getHandle()) << " vs " << toNodeHandle(nodeB->getHandle()); ASSERT_TRUE(nodeA->isNodeKeyDecrypted() || nodeB->isNodeKeyDecrypted()) << "Node " << toNodeHandle(nodeA->getHandle()) << " is not decryptable in both accounts " << apiIndexA << " and " << apiIndexB; ASSERT_FALSE(nodeA->isNodeKeyDecrypted() && !nodeB->isNodeKeyDecrypted()) << "Account " << apiIndexB << " can't decrypt " << nodeA->getName(); ASSERT_FALSE(!nodeA->isNodeKeyDecrypted() && nodeB->isNodeKeyDecrypted()) << "Account " << apiIndexA << " can't decrypt " << nodeB->getName(); ASSERT_STREQ(nodeA->getName(), nodeB->getName()) << "Node names don't match in in both accounts."; }; // It validates the passed nodes and their descents. // The function is called recursively for folders. void matchTreeRecurse(MegaNode* rootNodeA, MegaNode* rootNodeB, unsigned apiIndexA, unsigned apiIndexB) const { ASSERT_NO_FATAL_FAILURE(verifySameNodes(rootNodeA, apiIndexA, rootNodeB, apiIndexB)); std::unique_ptr<MegaNodeList> childrenListA{megaApi[apiIndexA]->getChildren(rootNodeA)}; std::unique_ptr<MegaNodeList> childrenListB{megaApi[apiIndexB]->getChildren(rootNodeB)}; std::unordered_map<MegaHandle, MegaNode*> indexB; // Index for childrenListB using the handles. for (auto j = 0; j < childrenListB->size(); ++j) { auto childNodeB{childrenListB->get(j)}; ASSERT_TRUE(childNodeB) << "null node in the list of childs of " << rootNodeB->getName() << "in the " << apiIndexB << " account."; indexB.emplace(childNodeB->getHandle(), childNodeB); } for (auto i = 0; i < childrenListA->size(); ++i) { auto childNodeA = childrenListA->get(i); ASSERT_TRUE(childNodeA) << "null node in the list of childs of " << rootNodeA->getName() << "in the " << apiIndexA << " account."; auto itChildNodeB = indexB.find(childNodeA->getHandle()); ASSERT_NE(itChildNodeB, indexB.end()) << "Can't find " << (childNodeA->isNodeKeyDecrypted() ? childNodeA->getName() : toNodeHandle(childNodeA->getHandle())) << " in the " << apiIndexB << " account"; auto childNodeB = itChildNodeB->second; if (childNodeA->isFolder() && childNodeB->isFolder()) { ASSERT_NO_FATAL_FAILURE( matchTreeRecurse(childNodeA, childNodeB, apiIndexA, apiIndexB)); } else { ASSERT_NO_FATAL_FAILURE( verifySameNodes(childNodeA, apiIndexA, childNodeB, apiIndexB)); } indexB.erase(itChildNodeB); } std::string extraNodes; for (auto [_, unmatchedChildNodeB]: indexB) { extraNodes += " " + toNodeHandle(unmatchedChildNodeB->getHandle()); if (unmatchedChildNodeB->isNodeKeyDecrypted()) extraNodes += ":" + string(unmatchedChildNodeB->getName()); } ASSERT_EQ(indexB.size(), 0) << "Unexpected " << indexB.size() << "node(s) found in the " << apiIndexB << " account: " << extraNodes; } }; /** * @brief Basic test for nested shares * * It tests the basic functionality, creating a nested share and ensuring * that all peers can see their respective files. * */ TEST_F(SdkTestShareNested, BasicNestedShares) { const auto logPre = getLogPrefix(); LOG_info << "Starting body of " << logPre; // Make sharer and sharees contacts. ASSERT_NO_FATAL_FAILURE( inviteTestAccount(sharerIndex, shareeAliceIndex, "Sharer inviting Alice")) << "Failure inviting Alice"; ASSERT_NO_FATAL_FAILURE(inviteTestAccount(sharerIndex, shareeBobIndex, "Sharer inviting Bob")) << "Failure inviting Bob"; if (gManualVerification) { ASSERT_NO_FATAL_FAILURE(verifyContactCredentials(sharerIndex, shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(verifyContactCredentials(sharerIndex, shareeBobIndex)); } LOG_info << logPre << "Share folder \"folderA\" to Alice and subfolder \"folderB\" to Bob"; auto sharerFolderANode = getNodeByPath(FOLDER_A); auto sharerFolderBNode = getNodeByPath(string(FOLDER_A) + "/" + FOLDER_B); ASSERT_TRUE(sharerFolderANode) << "folder \"folderA\" not found."; ASSERT_TRUE(sharerFolderBNode) << "folder \"folderB\" not found."; ASSERT_NO_FATAL_FAILURE(createShareAtoB(sharerFolderANode.get(), {sharerIndex, true}, {shareeAliceIndex, true}, MegaShare::ACCESS_FULL)); ASSERT_NO_FATAL_FAILURE(createShareAtoB(sharerFolderBNode.get(), {sharerIndex, true}, {shareeBobIndex, true}, MegaShare::ACCESS_FULL)); LOG_info << logPre << "Ensure that the sharer, Alice and Bob can see the same nodes and that the tree is " "decrypted."; waitForNodeToBeDecrypted(shareeAliceIndex, sharerFolderANode->getHandle()); ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderANode->getHandle(), sharerIndex, shareeAliceIndex)); waitForNodeToBeDecrypted(shareeBobIndex, sharerFolderBNode->getHandle()); ASSERT_NO_FATAL_FAILURE(matchTree(sharerFolderBNode->getHandle(), sharerIndex, shareeBobIndex)); ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderBNode->getHandle(), shareeAliceIndex, shareeBobIndex)); LOG_info << logPre << "Logout and login to ensure that all is correct after fetching nodes."; ASSERT_NO_FATAL_FAILURE(logout(sharerIndex, false, maxTimeout)); ASSERT_NO_FATAL_FAILURE(login(sharerIndex)); ASSERT_NO_FATAL_FAILURE(fetchnodes(sharerIndex)); ASSERT_NO_FATAL_FAILURE(logout(shareeAliceIndex, false, maxTimeout)); ASSERT_NO_FATAL_FAILURE(login(shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(fetchnodes(shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(logout(shareeBobIndex, false, maxTimeout)); ASSERT_NO_FATAL_FAILURE(login(shareeBobIndex)); ASSERT_NO_FATAL_FAILURE(fetchnodes(shareeBobIndex)); LOG_info << logPre << "Check again that the sharer, Alice and Bob can see the same nodes and that " "the tree is decrypted."; ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderANode->getHandle(), sharerIndex, shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(matchTree(sharerFolderBNode->getHandle(), sharerIndex, shareeBobIndex)); ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderBNode->getHandle(), shareeAliceIndex, shareeBobIndex)); } /** * @brief Test upload a file in the nested share * * It test if a file uploaded by a sharee is decryptable, creating a nested share and uploading a * file in the inshare of the nested sharee, ensuring that all peers can see their respective files. */ TEST_F(SdkTestShareNested, DISABLED_UploadFileInNestedShare) { const auto logPre = getLogPrefix(); LOG_info << "Starting body of " << logPre; // Make sharer and sharees contacts. ASSERT_NO_FATAL_FAILURE( inviteTestAccount(sharerIndex, shareeAliceIndex, "Sharer inviting Alice")) << "Failure inviting Alice"; ASSERT_NO_FATAL_FAILURE(inviteTestAccount(sharerIndex, shareeBobIndex, "Sharer inviting Bob")) << "Failure inviting Bob"; if (gManualVerification) { ASSERT_NO_FATAL_FAILURE(verifyContactCredentials(sharerIndex, shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(verifyContactCredentials(sharerIndex, shareeBobIndex)); } LOG_info << logPre << "Share folder \"folderA\" to Alice and subfolder \"folderB\" to Bob."; auto sharerFolderANode = getNodeByPath(FOLDER_A); auto sharerFolderBNode = getNodeByPath(string(FOLDER_A) + "/" + FOLDER_B); ASSERT_TRUE(sharerFolderANode) << "folder \"folderA\" not found."; ASSERT_TRUE(sharerFolderBNode) << "folder \"folderB\" not found."; ASSERT_NO_FATAL_FAILURE(createShareAtoB(sharerFolderANode.get(), {sharerIndex, true}, {shareeAliceIndex, true}, MegaShare::ACCESS_FULL)); ASSERT_NO_FATAL_FAILURE(createShareAtoB(sharerFolderBNode.get(), {sharerIndex, true}, {shareeBobIndex, true}, MegaShare::ACCESS_FULL)); LOG_info << logPre << "Ensure that the sharer, Alice and Bob can see the same nodes and that the tree is " "decrypted."; ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderANode->getHandle(), sharerIndex, shareeAliceIndex)); ASSERT_NO_FATAL_FAILURE(matchTree(sharerFolderBNode->getHandle(), sharerIndex, shareeBobIndex)); ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderBNode->getHandle(), shareeAliceIndex, shareeBobIndex)); LOG_info << logPre << "Bob puts a file in the inshare folder. Check if Alice and the sharer can see the node."; auto shareeBobFolderBNode = std::unique_ptr<MegaNode>{ megaApi[shareeBobIndex]->getNodeByHandle(sharerFolderBNode->getHandle())}; ASSERT_NO_FATAL_FAILURE(createRemoteFileNode(shareeBobIndex, FileNodeInfo("fromBobInFolderB").setSize(100), shareeBobFolderBNode.get(), sharerIndex)); ASSERT_NO_FATAL_FAILURE(matchTree(sharerFolderBNode->getHandle(), sharerIndex, shareeBobIndex)); ASSERT_NO_FATAL_FAILURE( matchTree(sharerFolderBNode->getHandle(), shareeAliceIndex, shareeBobIndex)); } /** * @brief Verify sync state transitions for nested shares. * * Steps: * 1. sharer uses existing folders "folderA/folderB" * 2. sharer shares "folderA" with shareeAlice as FULL_ACCESS * 3. shareeAlice creates a sync on "folderA" * 4. sharer shares "folderB" with shareeAlice as FULL_ACCESS * 5. sharer unshares "folderB" from shareeAlice * 6. sharer shares "folderB" with shareeAlice as READ_ACCESS * * Result: * - sync stays RUNNING after steps 4 and 5 * - sync becomes SUSPENDED with SHARE_NON_FULL_ACCESS after step 6 */ TEST_F(SdkTestShareNested, SyncStateWithNestedShareFolders) { const auto logPre = getLogPrefix(); LOG_info << "Starting body of " << logPre; // Enable manual verification mode megaApi[sharerIndex]->setManualVerificationFlag(true); megaApi[shareeAliceIndex]->setManualVerificationFlag(true); ASSERT_NO_FATAL_FAILURE( inviteTestAccount(sharerIndex, shareeAliceIndex, "Sharer inviting Alice")) << "Failure inviting Alice"; ASSERT_NO_FATAL_FAILURE(verifyContactCredentials(sharerIndex, shareeAliceIndex)); LOG_info << logPre << "1) Use existing fixture folders folderA/folderB"; auto parentFolder = getNodeByPath(FOLDER_A); ASSERT_TRUE(parentFolder) << "folder \"folderA\" not found."; auto childFolder = getNodeByPath(std::string(FOLDER_A) + "/" + FOLDER_B); ASSERT_TRUE(childFolder) << "folder \"folderB\" not found."; LOG_info << logPre << "2) Share folderA to shareeAlice with FULL access"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(parentFolder.get(), {sharerIndex, true}, {shareeAliceIndex, true}, MegaShare::ACCESS_FULL)); LOG_info << logPre << "3) Create sync on shareeAlice for folderA"; fs::path localBasePath = makeNewTestRoot(); fs::path localSyncPath = localBasePath / "folderA_sync"; fs::create_directories(localSyncPath); MegaHandle newSyncRootNodeHandle = UNDEF; int err = synchronousSyncFolder(shareeAliceIndex, &newSyncRootNodeHandle, MegaSync::TYPE_TWOWAY, path_u8string(localSyncPath).c_str(), nullptr, parentFolder->getHandle(), nullptr); ASSERT_EQ(err, API_OK); std::unique_ptr<MegaNode> shareeParentFolder{ megaApi[shareeAliceIndex]->getNodeByHandle(parentFolder->getHandle())}; ASSERT_TRUE(shareeParentFolder); std::unique_ptr<MegaSync> sync = waitForSyncState(megaApi[shareeAliceIndex].get(), shareeParentFolder.get(), MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); MegaHandle backupId = sync->getBackupId(); LOG_info << logPre << "4) Share folderB to shareeAlice with FULL access"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(childFolder.get(), {sharerIndex, true}, {shareeAliceIndex, true}, MegaShare::ACCESS_FULL)); // Sync should remain RUNNING. sync = waitForSyncState(megaApi[shareeAliceIndex].get(), backupId, MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); LOG_info << logPre << "5) Unshare folderB from shareeAlice"; ASSERT_NO_FATAL_FAILURE(shareFolder(childFolder.get(), mApi[shareeAliceIndex].email.c_str(), MegaShare::ACCESS_UNKNOWN, sharerIndex)); // Sync should remain RUNNING. sync = waitForSyncState(megaApi[shareeAliceIndex].get(), backupId, MegaSync::RUNSTATE_RUNNING, MegaSync::NO_SYNC_ERROR); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_RUNNING); ASSERT_EQ(sync->getError(), MegaSync::NO_SYNC_ERROR); LOG_info << logPre << "6) Share folderB to shareeAlice with READ access"; ASSERT_NO_FATAL_FAILURE(createShareAtoB(childFolder.get(), {sharerIndex, true}, {shareeAliceIndex, true}, MegaShare::ACCESS_READ)); // Sync should become SUSPENDED due to non-full access in nested share. sync = waitForSyncState(megaApi[shareeAliceIndex].get(), backupId, MegaSync::RUNSTATE_SUSPENDED, MegaSync::SHARE_NON_FULL_ACCESS); ASSERT_TRUE(sync); ASSERT_EQ(sync->getRunState(), MegaSync::RUNSTATE_SUSPENDED); ASSERT_EQ(sync->getError(), MegaSync::SHARE_NON_FULL_ACCESS); LOG_info << logPre << "Cleanup: remove sync"; ASSERT_EQ(API_OK, synchronousRemoveSync(shareeAliceIndex, backupId)); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_sync_upload_operations_test.cpp������������������������������0000664�0000000�0000000�00000323137�15162662266�0026373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file * @brief This file is expected to contain tests involving sync upload operations * e.g., what happens when a file is duplicated inside a sync. */ #ifdef ENABLE_SYNC #include "integration_test_utils.h" #include "mega/testhooks.h" #include "mega/utils.h" #include "megautils.h" #include "mock_listeners.h" #include "sdk_test_utils.h" #include "SdkTestSyncNodesOperations.h" #include <gmock/gmock.h> using namespace sdk_test; using namespace testing; namespace { struct ScopedLegacyBuggySparseCrcHook { #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED std::function<void(bool&)> mPrev; bool mEnabled = false; explicit ScopedLegacyBuggySparseCrcHook(const bool enabled): mPrev{::mega::globalMegaTestHooks.onHookFileFingerprintUseLegacyBuggySparseCrc}, mEnabled{enabled} { setEnabled(mEnabled); } ~ScopedLegacyBuggySparseCrcHook() { ::mega::globalMegaTestHooks.onHookFileFingerprintUseLegacyBuggySparseCrc = std::move(mPrev); } void setEnabled(const bool enabled) { mEnabled = enabled; ::mega::globalMegaTestHooks.onHookFileFingerprintUseLegacyBuggySparseCrc = [enabled](bool& flag) { flag = enabled; }; } #else explicit ScopedLegacyBuggySparseCrcHook(const bool) {} void setEnabled(const bool) {} #endif }; } // namespace /** * @class SdkTestSyncUploadOperations * @brief Test fixture designed to test operations involving sync uploads. */ class SdkTestSyncUploadsOperations: public SdkTestSyncNodesOperations { protected: struct CloneCandidateSetup { fs::path tempDir; fs::path localPathInSync; std::shared_ptr<sdk_test::LocalTempFile> tempFile; std::unique_ptr<MegaNode> candidatesFolder; std::unique_ptr<MegaNode> candidateNode; }; // [TODO] SDK-5629. Check Lifetime of listeners in this test suite once this ticket has been // resolved std::unique_ptr<NiceMock<MockTransferListener>> mMtl; std::unique_ptr<NiceMock<MockSyncListener>> mMsl; bool mCleanupFunctionSet{false}; const std::string SYNC_REMOTE_PATH{"localSyncedDir"}; std::vector<shared_ptr<sdk_test::LocalTempFile>> mLocalFiles; std::unique_ptr<FSACCESS_CLASS> mFsAccess; SyncItemTrackerManager<SyncUploadOperationsTracker> mSyncListenerTrackers; SyncItemTrackerManager<SyncUploadOperationsTransferTracker> mTransferListenerTrackers; mutable std::recursive_timed_mutex trackerMutex; using TestMutexGuard = std::unique_lock<std::recursive_timed_mutex>; std::shared_ptr<SyncUploadOperationsTracker> addSyncListenerTracker(const std::string& s) { TestMutexGuard g(trackerMutex); return mSyncListenerTrackers.add(s); } std::shared_ptr<SyncUploadOperationsTracker> getSyncListenerTrackerByPath(const std::string& s) { TestMutexGuard g(trackerMutex); return mSyncListenerTrackers.getByPath(s); } std::shared_ptr<SyncUploadOperationsTransferTracker> addTransferListenerTracker(const std::string& s) { TestMutexGuard g(trackerMutex); return mTransferListenerTrackers.add(s); } std::shared_ptr<SyncUploadOperationsTransferTracker> getTransferListenerTrackerByPath(const std::string& s) { TestMutexGuard g(trackerMutex); return mTransferListenerTrackers.getByPath(s); } /** * @brief Waits for sync completion and verifies transfer behavior for a file operation. * * This is the common logic for file creation and move operations. * Call this AFTER setting up trackers and performing the file operation. * * @param localFilePathAbs Absolute path to the file being synced (for error messages). * @param st The sync listener tracker for this file. * @param tt The transfer listener tracker for this file. * @param isFullUploadExpected If true, validates transfer completes with API_OK. * If false, validates that NO transfer occurred (clone/setattr should be used instead). * @param matchSyncByFingerprint Whether local and cloud nodes in the sync should be matched by * fingerprint in addition to name. */ void waitForSyncAndVerifyTransfer(const fs::path& localFilePathAbs, std::shared_ptr<SyncUploadOperationsTracker> st, std::shared_ptr<SyncUploadOperationsTransferTracker> tt, const bool isFullUploadExpected, const bool matchSyncByFingerprint = true) { auto [syncStatus, syncErrCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_TRUE(syncStatus == std::future_status::ready) << "Sync state change not received for: " << localFilePathAbs; ASSERT_EQ(syncErrCode, API_OK) << "Sync completed with error for: " << localFilePathAbs; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive(matchSyncByFingerprint)); const auto transferTimeout = isFullUploadExpected ? COMMON_TIMEOUT : std::chrono::seconds(30); auto [transferStatus, transferErrCode] = tt->waitForCompletion(transferTimeout); const auto expectedTransferStatus = isFullUploadExpected ? std::future_status::ready : std::future_status::timeout; ASSERT_EQ(transferStatus, expectedTransferStatus) << "Unexpected transfer status for: " << localFilePathAbs << " [isFullUploadExpected: " << isFullUploadExpected << "]"; const auto expectedTransferStartCount = isFullUploadExpected ? 1 : 0; ASSERT_EQ(tt->transferStartCount.load(), expectedTransferStartCount) << "Transfer started count mismatch for: " << localFilePathAbs << " [isFullUploadExpected: " << isFullUploadExpected << "]"; if (isFullUploadExpected) { ASSERT_EQ(transferErrCode, API_OK) << "Transfer failed (" << localFilePathAbs << ")"; } } /** * @brief Creates a local test file and verifies sync completion and transfer behavior. * * @param localFilePathAbs Absolute filesystem path where the test file will be created. Must be * an absolute path to match correctly with sync state change events. * @param fileContent The content to write to the test file. * @param customMtime Custom modification time to apply to the created file * @param isFullUploadExpected If true, validates that transfer completes with API_OK. * If false, validates that NO transfer occurred (clone/setattr should be used instead). */ void createTestFileInternal(const fs::path& localFilePathAbs, const std::string& fileContent, std::chrono::time_point<fs::file_time_type::clock> customMtime, const bool isFullUploadExpected) { static const std::string logPre{"createTestFileInternal: "}; ASSERT_TRUE(mMtl) << logPre << "Invalid transfer listener"; auto tt = addTransferListenerTracker(localFilePathAbs.string()); ASSERT_TRUE(tt) << logPre << "Cannot add TransferListenerTracker for: " << localFilePathAbs.string(); auto st = addSyncListenerTracker(localFilePathAbs.string()); ASSERT_TRUE(st) << logPre << "Cannot add SyncListenerTracker for: " << localFilePathAbs.string(); LOG_debug << logPre << "Creating local file: " << localFilePathAbs.string(); auto localFile = std::make_shared<sdk_test::LocalTempFile>(localFilePathAbs, fileContent, customMtime); mLocalFiles.emplace_back(localFile); ASSERT_NO_FATAL_FAILURE( waitForSyncAndVerifyTransfer(localFilePathAbs, st, tt, isFullUploadExpected)); } /** * @brief Moves a file into the sync and verifies sync completion and transfer behavior. * * @param sourcePath Absolute path to the source file (outside sync). * @param targetPathInSync Absolute path where file will be moved (inside sync). * @param isFullUploadExpected If true, validates transfer completes with API_OK. * If false, validates that NO transfer occurred (clone/setattr should be used instead). * @param expectedMtimeAfterMove Optional: if provided, verifies the moved file has this mtime. */ void moveFileIntoSyncAndVerify(const fs::path& sourcePath, const fs::path& targetPathInSync, const bool isFullUploadExpected, std::optional<m_time_t> expectedMtimeAfterMove = std::nullopt) { moveIntoSyncAndVerifyImpl(targetPathInSync, isFullUploadExpected, expectedMtimeAfterMove, [&]() { std::error_code ec; fs::rename(sourcePath, targetPathInSync, ec); if (ec) { LOG_err << "Failed to move file from " << path_u8string(sourcePath) << " to " << path_u8string(targetPathInSync) << ". Error: " << ec.message(); } return ec; }); } void moveLocalTempFileIntoSyncAndVerify( const std::shared_ptr<sdk_test::LocalTempFile>& file, const fs::path& targetPathInSync, const bool isFullUploadExpected, std::optional<m_time_t> expectedMtimeAfterMove = std::nullopt, const bool matchSyncByFingerprint = true) { ASSERT_TRUE(file); moveIntoSyncAndVerifyImpl( targetPathInSync, isFullUploadExpected, expectedMtimeAfterMove, [&]() { return file->move(targetPathInSync); }, matchSyncByFingerprint); } void createCloneCandidateSetup(CloneCandidateSetup& setup, const std::string& logPre, const std::string& candidatesFolderPrefix, const std::string& tempDirPrefix, const std::string& fileName, const size_t fileSize, const bool fetchCandidateNode = false) { LOG_debug << logPre << "1. Creating cloud candidate file (outside sync)"; std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const std::string folderName = candidatesFolderPrefix + "_" + getThisThreadIdStr() + "_" + std::to_string(m_time(nullptr)); { RequestTracker createFolderTracker(megaApi[0].get()); megaApi[0]->createFolder(folderName.c_str(), rootNode.get(), &createFolderTracker); ASSERT_EQ(API_OK, createFolderTracker.waitForResult()); } std::unique_ptr<MegaNode> candidatesFolder( megaApi[0]->getChildNode(rootNode.get(), folderName.c_str())); ASSERT_TRUE(candidatesFolder); const fs::path tempDir = makeProcessTempDir(tempDirPrefix); auto tempFile = std::make_shared<sdk_test::LocalTempFile>(tempDir / fileName, fileSize); { TransferTracker uploadTracker(megaApi[0].get()); MegaUploadOptions uploadOptions; uploadOptions.mtime = m_time(nullptr) - 86400; megaApi[0]->startUpload(path_u8string(tempFile->getPath()), candidatesFolder.get(), nullptr, &uploadOptions, &uploadTracker); ASSERT_EQ(API_OK, uploadTracker.waitForResult(600)); } std::unique_ptr<MegaNode> candidateNode; if (fetchCandidateNode) { candidateNode.reset(megaApi[0]->getChildNode(candidatesFolder.get(), fileName.c_str())); ASSERT_TRUE(candidateNode); } ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); setup = {tempDir, fs::absolute(getLocalTmpDir() / fileName), std::move(tempFile), std::move(candidatesFolder), std::move(candidateNode)}; } private: template<typename MoveFn> void moveIntoSyncAndVerifyImpl(const fs::path& targetPathInSync, const bool isFullUploadExpected, const std::optional<m_time_t>& expectedMtimeAfterMove, MoveFn&& moveFn, const bool matchSyncByFingerprint = true) { static const std::string logPre{"moveIntoSyncAndVerifyImpl: "}; ASSERT_TRUE(mMtl) << logPre << "Invalid transfer listener"; auto tt = addTransferListenerTracker(targetPathInSync.string()); ASSERT_TRUE(tt) << logPre << "Cannot add TransferListenerTracker for: " << targetPathInSync.string(); auto st = addSyncListenerTracker(targetPathInSync.string()); ASSERT_TRUE(st) << logPre << "Cannot add SyncListenerTracker for: " << targetPathInSync.string(); const auto ec = moveFn(); ASSERT_FALSE(ec) << logPre << "Move into sync failed for: " << targetPathInSync; if (expectedMtimeAfterMove.has_value()) { auto [getMtimeOk, movedFileMtime] = mFsAccess->getmtimelocal( LocalPath::fromAbsolutePath(path_u8string(targetPathInSync))); ASSERT_TRUE(getMtimeOk) << logPre << "Failed to get mtime of moved file: " << targetPathInSync; ASSERT_EQ(movedFileMtime, expectedMtimeAfterMove.value()) << logPre << "Move should preserve mtime for: " << targetPathInSync; } ASSERT_NO_FATAL_FAILURE(waitForSyncAndVerifyTransfer(targetPathInSync, st, tt, isFullUploadExpected, matchSyncByFingerprint)); } protected: enum class CxfMtimeDirection { Increase, Decrease }; void runAsyncMacComputationForCxfCase(const std::string& logPre, const CxfMtimeDirection direction) { auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; static constexpr size_t FILE_SIZE = (5 * 1024 * 1024) + (2 * 1024) + 3; // 5MB + 2KB + 3 bytes const fs::path testFilePath = fs::absolute(getLocalTmpDir() / "test_file_cxf.dat"); LOG_debug << logPre << "1. Creating test file"; { auto st = addSyncListenerTracker(testFilePath.string()); ASSERT_TRUE(st); auto localFile = std::make_shared<sdk_test::LocalTempFile>(testFilePath, FILE_SIZE); mLocalFiles.emplace_back(localFile); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << "File sync timed out"; ASSERT_EQ(errCode, API_OK) << "File sync failed"; } ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); auto [getMtimeOk, originalMtime] = mFsAccess->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(testFilePath))); ASSERT_TRUE(getMtimeOk) << "Failed to get original mtime"; LOG_debug << logPre << "2. Removing sync (simulating logout without file deletion)"; removeTestSync(); LOG_debug << logPre << "3. Updating file mtime"; const m_time_t newMtime = direction == CxfMtimeDirection::Increase ? originalMtime + MIN_ALLOW_MTIME_DIFFERENCE : originalMtime - MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(testFilePath)), newMtime)); LOG_debug << logPre << "4. Re-adding sync (SRT_CXF case - no LocalNodes exist)"; ASSERT_NO_FATAL_FAILURE( initiateSync(getLocalTmpDirU8string(), SYNC_REMOTE_PATH, mBackupId)); LOG_debug << logPre << "5. Waiting for sync to complete with async MAC recomputation"; { auto st = addSyncListenerTracker(testFilePath.string()); ASSERT_TRUE(st); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << "File sync timed out"; ASSERT_EQ(errCode, API_OK) << "File sync failed"; } auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Backup node not found"; std::unique_ptr<MegaNode> cloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "test_file_cxf.dat", FILENODE)); ASSERT_TRUE(cloudNode) << "Cloud node not found after re-sync"; auto cloudNodeMtime = cloudNode->getModificationTime(); if (direction == CxfMtimeDirection::Increase) { ASSERT_EQ(cloudNodeMtime, newMtime) << "Cloud node mtime should match the updated local mtime after CXF sync"; } else { auto [getMtimeLocalOk, localMtime] = mFsAccess->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(testFilePath))); ASSERT_TRUE(getMtimeLocalOk) << "Failed to get local mtime after CXF resync"; ASSERT_GT(cloudNodeMtime, newMtime) << "Cloud node mtime should be newer than the last " "local changed mtime after CXF sync"; ASSERT_EQ(localMtime, cloudNodeMtime) << "Local file mtime should match cloud after CXF resync when cloud is newer"; } LOG_debug << logPre << "Test completed successfully"; } void runAsyncMacNonBlockingScenario(const std::string& logPre, const std::string& namePrefix, const bool startFromCxf) { // File sizes shared by CSF and CXF variants. static constexpr size_t SMALL_FILE_SIZE = (5 * 1024 * 1024) + (126 * 1024) + 17; // ~5MB static constexpr size_t LARGE_FILE_SIZE = (100 * 1024 * 1024) + (212 * 1024) + 2; // ~100MB auto absPath = [&](const std::string& filename) { return fs::absolute(getLocalTmpDir() / filename); }; const fs::path smallFile1Path = absPath(namePrefix + "small_file1.dat"); const fs::path smallFile2Path = absPath(namePrefix + "small_file2.dat"); const fs::path largeFilePath = absPath(namePrefix + "large_file.dat"); LOG_debug << logPre << "1. Creating test files (2 small, 1 large)"; auto createFileAndSync = [&](const fs::path& path, const size_t size, const std::string& label) { auto st = mSyncListenerTrackers.add(path.string()); ASSERT_TRUE(st) << logPre << "Cannot add SyncListenerTracker for " << label; auto localFile = std::make_shared<sdk_test::LocalTempFile>(path, size); mLocalFiles.emplace_back(localFile); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << logPre << label << " sync timed out"; ASSERT_EQ(errCode, API_OK) << logPre << label << " sync failed"; }; ASSERT_NO_FATAL_FAILURE(createFileAndSync(smallFile1Path, SMALL_FILE_SIZE, "Small file 1")); ASSERT_NO_FATAL_FAILURE(createFileAndSync(smallFile2Path, SMALL_FILE_SIZE, "Small file 2")); ASSERT_NO_FATAL_FAILURE(createFileAndSync(largeFilePath, LARGE_FILE_SIZE, "Large file")); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); LOG_debug << logPre << "2. All files synced, now updating mtimes"; const m_time_t newMtime = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; std::shared_ptr<SyncUploadOperationsTracker> stSmall1; std::shared_ptr<SyncUploadOperationsTracker> stSmall2; std::shared_ptr<SyncUploadOperationsTracker> stLarge; auto addSyncListeners = [&]() { stSmall1 = mSyncListenerTrackers.add(smallFile1Path.string()); stSmall2 = mSyncListenerTrackers.add(smallFile2Path.string()); stLarge = mSyncListenerTrackers.add(largeFilePath.string()); ASSERT_TRUE(stSmall1 && stSmall2 && stLarge); }; if (!startFromCxf) { ASSERT_NO_FATAL_FAILURE(addSyncListeners()); } else { LOG_debug << logPre << "2b. Removing sync (CXF) before updating mtimes"; removeTestSync(); } LOG_debug << logPre << "3. Updating mtimes for all 3 files"; ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(smallFile1Path)), newMtime)); ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(smallFile2Path)), newMtime)); ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(largeFilePath)), newMtime)); if (startFromCxf) { LOG_debug << logPre << "3b. Re-adding sync (CXF path upon sync re-addition)"; ASSERT_NO_FATAL_FAILURE( initiateSync(getLocalTmpDirU8string(), SYNC_REMOTE_PATH, mBackupId)); ASSERT_NO_FATAL_FAILURE(addSyncListeners()); } LOG_debug << logPre << "4. Waiting for all files to sync and tracking completion order"; using clock = std::chrono::steady_clock; std::atomic<clock::time_point> small1CompleteTime{clock::time_point::max()}; std::atomic<clock::time_point> small2CompleteTime{clock::time_point::max()}; std::atomic<clock::time_point> largeCompleteTime{clock::time_point::max()}; std::atomic<bool> small1Done{false}; std::atomic<bool> small2Done{false}; std::atomic<bool> largeDone{false}; auto waitAndStamp = [&](std::shared_ptr<SyncUploadOperationsTracker> st, std::atomic<clock::time_point>& ts, std::atomic<bool>& done, std::chrono::seconds timeout) { return std::thread( [&, st = std::move(st), timeout]() { auto [status, err] = st->waitForCompletion(timeout); if (status == std::future_status::ready && err == API_OK) { ts = clock::now(); done = true; } }); }; auto waitLarge = waitAndStamp(stLarge, largeCompleteTime, largeDone, std::chrono::seconds(180)); auto waitSmall1 = waitAndStamp(stSmall1, small1CompleteTime, small1Done, std::chrono::seconds(60)); auto waitSmall2 = waitAndStamp(stSmall2, small2CompleteTime, small2Done, std::chrono::seconds(60)); waitSmall1.join(); waitSmall2.join(); // By the time small files are done, the large one should not have finished yet in the // non-blocking scenario. If it has, we'll catch it in the ordering assertions below. EXPECT_FALSE(largeDone.load()) << logPre << "Large file completed before small files"; waitLarge.join(); ASSERT_TRUE(small1Done) << logPre << "Small file 1 mtime sync failed or timed out"; ASSERT_TRUE(small2Done) << logPre << "Small file 2 mtime sync failed or timed out"; ASSERT_TRUE(largeDone) << logPre << "Large file mtime sync failed or timed out"; LOG_debug << logPre << "6. Verifying completion order"; auto small1Time = small1CompleteTime.load(); auto small2Time = small2CompleteTime.load(); auto largeTime = largeCompleteTime.load(); EXPECT_LT(small1Time, largeTime) << logPre << "Small file 1 should complete before large file"; EXPECT_LT(small2Time, largeTime) << logPre << "Small file 2 should complete before large file"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); LOG_debug << logPre << "Test completed successfully"; } public: void SetUp() override { SdkTestNodesSetUp::SetUp(); mFsAccess = std::make_unique<FSACCESS_CLASS>(); if (createSyncOnSetup()) { ASSERT_NO_FATAL_FAILURE( initiateSync(getLocalTmpDirU8string(), SYNC_REMOTE_PATH, mBackupId)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocal()); } mMtl.reset(new NiceMock<MockTransferListener>(megaApi[0].get())); EXPECT_CALL(*mMtl, onTransferStart) .WillRepeatedly( [this](::mega::MegaApi*, ::mega::MegaTransfer* t) { if (!t || !t->getPath()) { return; } auto element = getTransferListenerTrackerByPath(t->getPath()); if (!element) return; ASSERT_EQ(++element->transferStartCount, 1) << "Unexpected times onTransferStart has been called: " << t->getPath(); }); EXPECT_CALL(*mMtl, onTransferFinish) .WillRepeatedly( [this](::mega::MegaApi*, ::mega::MegaTransfer* t, ::mega::MegaError* e) { if (!t || !t->getPath()) { return; } auto element = getTransferListenerTrackerByPath(t->getPath()); if (!element || !e) return; ASSERT_TRUE(!element->getActionCompleted()) << "onTransferFinish has been previously received: " << t->getPath(); element->setActionCompleted(); element->setActionCompletedPms(e->getErrorCode()); }); megaApi[0]->addListener(mMtl.get()); mMsl.reset(new NiceMock<MockSyncListener>(megaApi[0].get())); EXPECT_CALL(*mMsl.get(), onSyncFileStateChanged(_, _, _, _)) .WillRepeatedly( [this](MegaApi*, MegaSync* sync, std::string* localPath, int newState) { if (sync && sync->getBackupId() == getBackupId() && newState == MegaApi::STATE_SYNCED && localPath) { auto element = getSyncListenerTrackerByPath(*localPath); if (!element || element->getActionCompleted()) return; element->setActionCompleted(); element->setActionCompletedPms(API_OK); } }); megaApi[0]->addListener(mMsl.get()); } void TearDown() override { ASSERT_TRUE(mCleanupFunctionSet) << getLogPrefix() << "(TearDown). cleanupfunction has not been properly set by " "calling `setCleanupFunction()`."; ASSERT_TRUE(!mMtl) << getLogPrefix() << "(TearDown). Transfer listener has not been unregistered yet"; ASSERT_TRUE(!mMsl) << getLogPrefix() << "(TearDown). Sync listener has not been unregistered yet"; SdkTestSyncNodesOperations::TearDown(); } /** * @brief Sets the cleanup function to be executed during TearDown. * * If a custom cleanup function is provided, it will be used. * Otherwise, a default one will be set. * * @example: * - example1 (default cleanupFunction): * auto cleanup = setCleanupFunction(); * - example2 (custom cleanupFunction): * auto cleanup = setCleanupFunction([this](){ * // custom cleanup function code * }); * * @note: is mandatory calling this method at the beginning of each test of this file, otherwise * test will fail at teardown. The reason behind is to enforce setting an appropriate cleanup * function for each test. * * @param customCleanupFunction Optional custom cleanup function. * @return The cleanup function that was set. */ std::unique_ptr<MrProper> setCleanupFunction(std::function<void()> customCleanupFunction = nullptr) { mCleanupFunctionSet = true; if (customCleanupFunction) { return std::make_unique<MrProper>(customCleanupFunction); } else { return std::make_unique<MrProper>( [this]() { cleanDefaultListeners(); }); } } void removeTestSync() { if (mBackupId != UNDEF) { auto succeeded = removeSync(megaApi[0].get(), mBackupId); if (succeeded) { mBackupId = UNDEF; } ASSERT_TRUE(succeeded); } } void cleanDefaultListeners() { removeTestSync(); if (mMtl) { megaApi[0]->removeListener(mMtl.get()); mMtl.reset(); } if (mMsl) { megaApi[0]->removeListener(mMsl.get()); mMsl.reset(); } } /** * @brief Build a file tree with two empty sync folders */ const std::vector<NodeInfo>& getElements() const override { static const std::vector<NodeInfo> ELEMENTS{DirNodeInfo(SYNC_REMOTE_PATH) .addChild(DirNodeInfo("dir1")) .addChild(DirNodeInfo("dir2"))}; return ELEMENTS; } /** * @brief Updates local node mtime. See MIN_ALLOW_MTIME_DIFFERENCE */ void updateLocalNodeMtime(MegaHandle nodeHandle, const LocalPath& path, int64_t oldMtime, int64_t newMtime, const string& msg) { LOG_debug << "#### updateNodeMtime (" << msg << ")####"; bool mTimeChangeRecv{false}; mApi[0].mOnNodesUpdateCompletion = [&mTimeChangeRecv, oldMtime, nodeHandle](size_t, MegaNodeList* nodes) { ASSERT_TRUE(nodes) << "Invalid meganode list received"; for (int i = 0; i < nodes->size(); ++i) { MegaNode* n = nodes->get(i); if (n && n->getHandle() == nodeHandle && n->hasChanged(static_cast<uint64_t>(MegaNode::CHANGE_TYPE_ATTRIBUTES)) && oldMtime != n->getModificationTime()) { mTimeChangeRecv = true; } } }; mFsAccess->setmtimelocal(path, newMtime); ASSERT_TRUE(waitForResponse(&mTimeChangeRecv)) << "No mtime change received after " << maxTimeout << " seconds"; resetOnNodeUpdateCompletionCBs(); // important to reset } /** * @brief Creates a new local file for test. See createTestFileInternal */ void createTestFile(const string folderName, const std::string commonFileName, const std::string content, std::chrono::time_point<fs::file_time_type::clock> customMtime, const std::string& msg, bool isFullUploadExpected) { LOG_debug << "#### createTestFile ( " << msg << ") `" << commonFileName << "` into `" << folderName << "` with content(" << content << ") and customMtime (full upload expected) ####"; ASSERT_NO_FATAL_FAILURE( createTestFileInternal(fs::absolute(getLocalTmpDir() / folderName / commonFileName), content, customMtime, isFullUploadExpected)); }; /** * @brief Search nodes by fingerprint and validates the result. * * @param n The source node whose fingerprint will be used for the search. Must be a * valid node with a non-null fingerprint. * @param excludeMtime If true, uses getNodesByFingerprintIgnoringMtime() which compares * only CRC + size + isValid, ignoring the modification time. If false, * uses getNodesByFingerprint() which compares the entire fingerprint * including modification time * * @param expNodeCount The expected number of nodes that should be found with the given * fingerprint. * @param msg A descriptive message used for logging purposes * @see FS_MTIME_TOLERANCE_SECS tolerance */ void getNodesByFingerprint(MegaNode* n, bool excludeMtime, size_t expNodeCount, const string& msg) { LOG_debug << "#### getNodesByFingerprint (" << msg << ") ####"; ASSERT_TRUE(n) << "getNodesByFingerprint: Invalid node"; ASSERT_TRUE(n->getFingerprint()) << "Invalid fingerprint for node(" << toNodeHandle(n->getHandle()) << ")"; auto auxfp = n->getFingerprint(); std::unique_ptr<MegaNodeList> nl; if (excludeMtime) nl.reset(megaApi[0]->getNodesByFingerprintIgnoringMtime(auxfp)); else nl.reset(megaApi[0]->getNodesByFingerprint(auxfp)); ASSERT_EQ(nl->size(), expNodeCount) << "getNodesByFingerprint. " << msg << " Unexpected node count"; } /** * @brief Returns the backup MegaNode */ std::shared_ptr<MegaNode> getBackupNode() { std::unique_ptr<MegaSync> backupSync(megaApi[0]->getSyncByBackupId(getBackupId())); if (!backupSync) { LOG_err << "Cannot get backup sync"; return nullptr; } std::unique_ptr<MegaNode> backupNode( megaApi[0]->getNodeByHandle(backupSync->getMegaHandle())); if (!backupNode) { LOG_err << "Cannot get backup sync node"; return nullptr; } return backupNode; } /** * @brief Retrieves test folder nodes and their first-level child file nodes. * * This function locates a set of folders under a given backup node (`backupNode`), * as specified by the list of folder names in `folderNames`. For each folder found, * it retrieves a file node with the common name `commonFileName` contained directly * within that folder. The resulting folder and file nodes are stored in the * `folderNodes` and `fileNodes` vectors, respectively. * * This function assumes a single-level hierarchy: folders exist directly under * the backup node, and files exist directly inside those folders. * * @param backupNode The backup node under which the folders are located. * @param folderNames A list of the folder names. * @param folderNodes Vector to store the retrieved folder nodes. * @param fileNodes Vector to store the retrieved file nodes. * @param commonFileName The name of the common file expected inside each folder. * @param msg A descriptive message used for logging purposes. */ void getTestFolderNodesAndFirstLevelChildren(std::shared_ptr<MegaNode> backupNode, const std::vector<string>& folderNames, std::vector<std::unique_ptr<MegaNode>>& folderNodes, std::vector<std::unique_ptr<MegaNode>>& fileNodes, const std::string& commonFileName, const std::string& msg) { LOG_debug << "#### getTestFolderNodesAndFirstLevelChildren (" << msg << ") ####"; folderNodes.clear(); fileNodes.clear(); for (size_t i = 0; i < folderNames.size(); ++i) { std::unique_ptr<MegaNode> folderNode( megaApi[0]->getChildNodeOfType(backupNode.get(), folderNames.at(i).c_str(), FOLDERNODE)); ASSERT_TRUE(folderNode) << msg << "Cannot get folderNode(" << folderNames.at(i) << ")"; std::unique_ptr<MegaNode> fileNode( megaApi[0]->getChildNodeOfType(folderNode.get(), commonFileName.c_str(), FILENODE)); ASSERT_TRUE(fileNode) << msg << "Can not get fileNode(" << commonFileName << ") which is inside " << folderNames.at(i); folderNodes.emplace_back(std::move(folderNode)); fileNodes.emplace_back(std::move(fileNode)); } } /** * @brief Gets the absolute LocalPath of a localtest file. * * @param folderName The name of the parent folder of file. * @param fileName The name of the file for which to construct the absolute path. * @return A LocalPath object representing the absolute path to the specified test file. */ LocalPath getTestFileAbsolutePath(const std::string& folderName, const std::string& fileName) { return LocalPath::fromAbsolutePath( path_u8string(fs::absolute(getLocalTmpDir() / folderName / fileName))); } }; /** * @test * SdkTestSyncUploadsOperations.CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CSF * * 1. Enable legacy (buggy) sparse CRC sampling via debug hook. * 2. Create a couple of large random files outside the sync and move them in -> full upload * expected. * 3. Disable legacy hook, trigger a sync rescan to recompute fingerprint. * 4. Verify cloud nodes get their fingerprint updated (CRC corrected) without transfers. */ TEST_F(SdkTestSyncUploadsOperations, CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CSF) { #ifndef MEGASDK_DEBUG_TEST_HOOKS_ENABLED GTEST_SKIP() << "Requires MEGASDK_DEBUG_TEST_HOOKS_ENABLED"; #else const auto cleanup = setCleanupFunction(); static const std::string logPre{ "CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CSF: "}; const auto threadSuffix = "_" + getThisThreadIdStr(); ScopedLegacyBuggySparseCrcHook legacyHook{true}; // Sizes chosen to exceed the historical 32-bit sparse CRC offset overflow threshold (~33MB), // while keeping uploads fast enough for integration environments. static constexpr size_t FILE1_SIZE = 40 * 1024 * 1024; // 40MB static constexpr size_t FILE2_SIZE = 90 * 1024 * 1024; // 90MB const fs::path file1Path = fs::absolute(getLocalTmpDir() / ("crc_bug_csf_1" + threadSuffix + ".dat")); const fs::path file2Path = fs::absolute(getLocalTmpDir() / ("crc_bug_csf_2" + threadSuffix + ".dat")); const fs::path outsideLocalDir = getLocalTmpDir().parent_path() / ("crc_bug_csf_tmp_dir" + threadSuffix); LocalTempDir outsideDirCleanup(outsideLocalDir); const fs::path outsideFile1 = outsideLocalDir / file1Path.filename(); const fs::path outsideFile2 = outsideLocalDir / file2Path.filename(); LOG_debug << logPre << "1. Creating two large files outside sync (avoid partial-file uploads)"; auto localFile1 = std::make_shared<sdk_test::LocalTempFile>(outsideFile1, FILE1_SIZE); auto localFile2 = std::make_shared<sdk_test::LocalTempFile>(outsideFile2, FILE2_SIZE); mLocalFiles.emplace_back(localFile1); mLocalFiles.emplace_back(localFile2); auto moveFileIntoSyncWithLocalTempFileAndVerify = [&](const fs::path& targetPathInSync, const std::shared_ptr<sdk_test::LocalTempFile>& f) { ASSERT_NO_FATAL_FAILURE( moveLocalTempFileIntoSyncAndVerify(f, targetPathInSync, true, std::nullopt, false)); }; LOG_debug << logPre << "2. Moving files into sync with legacy buggy sparse CRC enabled"; ASSERT_NO_FATAL_FAILURE(moveFileIntoSyncWithLocalTempFileAndVerify(file1Path, localFile1)); ASSERT_NO_FATAL_FAILURE(moveFileIntoSyncWithLocalTempFileAndVerify(file2Path, localFile2)); LOG_debug << logPre << "3. Disabling legacy hook and rescanning sync to recompute fingerprints"; legacyHook.setEnabled(false); megaApi[0]->rescanSync(getBackupId(), true); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << logPre << "Cannot get backup node"; auto waitForRemoteFingerprint = [&](const fs::path& localPath, const std::string& filename, std::shared_ptr<SyncUploadOperationsTransferTracker> tt) { const std::unique_ptr<char[]> expectedFp{ megaApi[0]->getFingerprint(path_u8string(localPath).c_str())}; ASSERT_TRUE(expectedFp) << logPre << "Cannot compute local fingerprint for: " << localPath; const auto deadline = std::chrono::steady_clock::now() + COMMON_TIMEOUT; for (;;) { std::unique_ptr<MegaNode> cloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), filename.c_str(), FILENODE)); if (cloudNode && cloudNode->getFingerprint() && std::string_view{cloudNode->getFingerprint()} == expectedFp.get()) { break; } if (std::chrono::steady_clock::now() >= deadline) { FAIL() << logPre << "Timed out waiting for remote fingerprint update for: " << localPath; } std::this_thread::sleep_for(std::chrono::seconds(1)); } ASSERT_EQ(tt->transferStartCount.load(), 0) << logPre << "Transfer must not start for: " << localPath; }; auto tt1 = addTransferListenerTracker(file1Path.string()); auto tt2 = addTransferListenerTracker(file2Path.string()); ASSERT_TRUE(tt1 && tt2); LOG_debug << logPre << "4. Verifying remote fingerprints updated without transfers"; ASSERT_NO_FATAL_FAILURE( waitForRemoteFingerprint(file1Path, path_u8string(file1Path.filename()), tt1)); ASSERT_NO_FATAL_FAILURE( waitForRemoteFingerprint(file2Path, path_u8string(file2Path.filename()), tt2)); #endif } /** * @test * SdkTestSyncUploadsOperations.CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CXF * * 1. Remove sync to start from CXF (no LocalNodes). * 2. Manually upload a large file with legacy (buggy) sparse CRC enabled. * 3. Disable legacy hook, place the same file into the sync local root, and re-add the sync. * 4. Verify cloud node fingerprint gets corrected without transfers. */ TEST_F(SdkTestSyncUploadsOperations, CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CXF) { #ifndef MEGASDK_DEBUG_TEST_HOOKS_ENABLED GTEST_SKIP() << "Requires MEGASDK_DEBUG_TEST_HOOKS_ENABLED"; #else const auto cleanup = setCleanupFunction(); static const std::string logPre{ "CrcOnlyMismatchBugFixUpdatesRemoteFingerprintWithoutTransfer_CXF: "}; const auto threadSuffix = "_" + getThisThreadIdStr(); static constexpr size_t FILE_SIZE = 40 * 1024 * 1024; // 40MB const std::string fileName = "crc_bug_cxf" + threadSuffix + ".dat"; auto backupNodeBefore = getBackupNode(); ASSERT_TRUE(backupNodeBefore) << logPre << "Cannot get backup node"; const MegaHandle backupHandle = backupNodeBefore->getHandle(); LOG_debug << logPre << "1. Removing sync (CXF path upon re-addition)"; removeTestSync(); std::unique_ptr<MegaNode> backupNode(megaApi[0]->getNodeByHandle(backupHandle)); ASSERT_TRUE(backupNode) << logPre << "Cannot re-acquire backup node by handle"; const fs::path outsideLocalDir = getLocalTmpDir().parent_path() / ("crc_bug_cxf_tmp_dir" + threadSuffix); LocalTempDir outsideDirCleanup(outsideLocalDir); const fs::path outsideLocalPath = outsideLocalDir / fileName; LOG_debug << logPre << "2. Creating random file outside sync"; createRandomFile(outsideLocalPath, FILE_SIZE); const m_time_t fixedMtime = m_time(nullptr) - 60; ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(outsideLocalPath)), fixedMtime)) << logPre << "Failed to set mtime on temp file"; LOG_debug << logPre << "3. Manual upload with legacy buggy sparse CRC enabled"; ScopedLegacyBuggySparseCrcHook legacyHook{true}; auto uploadedNode = uploadFile(megaApi[0].get(), outsideLocalPath, backupNode.get()); ASSERT_TRUE(uploadedNode) << logPre << "Manual upload failed"; LOG_debug << logPre << "4. Disabling legacy hook and moving file into sync local root"; legacyHook.setEnabled(false); const fs::path insideSyncPath = fs::absolute(getLocalTmpDir() / fileName); std::error_code renameError; fs::rename(outsideLocalPath, insideSyncPath, renameError); ASSERT_FALSE(renameError) << logPre << "Failed to move file into sync: " << renameError.message(); auto [getMtimeOk, movedMtime] = mFsAccess->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(insideSyncPath))); ASSERT_TRUE(getMtimeOk) << logPre << "Failed to get mtime of moved file"; ASSERT_EQ(movedMtime, fixedMtime) << logPre << "fs::rename should preserve mtime"; auto tt = addTransferListenerTracker(insideSyncPath.string()); ASSERT_TRUE(tt); LOG_debug << logPre << "5. Re-adding sync and waiting for remote fingerprint correction"; ASSERT_NO_FATAL_FAILURE(initiateSync(getLocalTmpDirU8string(), SYNC_REMOTE_PATH, mBackupId)); const std::unique_ptr<char[]> expectedFp{ megaApi[0]->getFingerprint(path_u8string(insideSyncPath).c_str())}; ASSERT_TRUE(expectedFp) << logPre << "Cannot compute local fingerprint"; const auto deadline = std::chrono::steady_clock::now() + COMMON_TIMEOUT; for (;;) { std::unique_ptr<MegaNode> cloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), fileName.c_str(), FILENODE)); if (cloudNode && cloudNode->getFingerprint() && std::string_view{cloudNode->getFingerprint()} == expectedFp.get()) { break; } if (std::chrono::steady_clock::now() >= deadline) { FAIL() << logPre << "Timed out waiting for remote fingerprint update"; } std::this_thread::sleep_for(std::chrono::seconds(1)); } ASSERT_EQ(tt->transferStartCount.load(), 0) << logPre << "Transfer must not start"; #endif } /** * @test SdkTestSyncUploadsOperations.BasicFileUpload * * 1. Create a new local file inside sync directory `dir2`. * 2. Wait for sync (sync engine must upload file to the cloud). * 3. Verify that local and remote models match */ TEST_F(SdkTestSyncUploadsOperations, BasicFileUpload) { const auto cleanup = setCleanupFunction(); auto mtime = fs::file_time_type::clock::now(); LOG_err << "BasicFileUpload (TC1) create `file1`"; ASSERT_NO_FATAL_FAILURE(createTestFile("dir1", "file1", "abcde", mtime, "CF1", true)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); } /** * @test SdkTestSyncUploadsOperations.DuplicatedFilesUpload * * 1. Create a new local file `file1` in `dir1` with given content and mtime. * - Expect a full upload (transfer started and finished). * 2. Create a new local file `file1` in `dir2` with the same content and mtime. * - Expect a remote copy (no transfer started). * 3. Verify that local and remote models match */ TEST_F(SdkTestSyncUploadsOperations, DuplicatedFilesUpload) { const auto cleanup = setCleanupFunction(); auto mtime = fs::file_time_type::clock::now(); ASSERT_NO_FATAL_FAILURE(createTestFile("dir1", "file1", "abcde", mtime, "CF1", true)); ASSERT_NO_FATAL_FAILURE(createTestFile("dir2", "file1", "abcde", mtime, "CF2", false)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); } /** * @test SdkTestSyncUploadsOperations.DuplicatedFilesUploadDifferentMtime * * 1. Create a new local file `file1` in `dir1` with given content and mtime `mt1`. * - Expect a full upload. * 2. Create the same file `file1` in `dir2` with same content but different mtime `mt2`. * - Expect a remote copy since fingerprints differs in mtime only, and MAC matches. * 3. Verify that local and remote models match */ TEST_F(SdkTestSyncUploadsOperations, DuplicatedFilesUploadDifferentMtime) { const auto cleanup = setCleanupFunction(); auto mtime1 = fs::file_time_type::clock::now(); auto mtime2 = mtime1 + std::chrono::seconds( MIN_ALLOW_MTIME_DIFFERENCE); // See MIN_ALLOW_MTIME_DIFFERENCE definition ASSERT_NO_FATAL_FAILURE(createTestFile("dir1", "file1", "abcde", mtime1, "CF1", true)); ASSERT_NO_FATAL_FAILURE(createTestFile("dir2", "file1", "abcde", mtime2, "CF2", false)); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); } /** * @brief SdkTestSyncUploadsOperations.MultimediaFileUpload * * Test the metadata and thumbnails from a synced video. * */ #if !defined(USE_FREEIMAGE) && !defined(USE_MEDIAINFO) TEST_F(SdkTestSyncUploadsOperations, DISABLED_MultimediaFileUpload) #else TEST_F(SdkTestSyncUploadsOperations, MultimediaFileUpload) #endif { const auto cleanup = setCleanupFunction(); static const string VIDEO_FILE = "sample_video.mp4"; static const std::string logPre = getLogPrefix(); LOG_verbose << logPre << "Upload a multimedia file in a sync"; // Get the file first and move it later to ensure that it is fully uploaded at once. ASSERT_TRUE(getFileFromArtifactory("test-data/" + VIDEO_FILE, VIDEO_FILE)); fs::rename(VIDEO_FILE, getLocalTmpDir() / VIDEO_FILE); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); auto uploadedNode = getNodeByPath(SYNC_REMOTE_PATH + "/" + VIDEO_FILE); ASSERT_TRUE(uploadedNode); #ifdef USE_MEDIAINFO static constexpr int VIDEO_FILE_DURATION_SECS{5}; static constexpr int VIDEO_FILE_HEIGHT_PX{360}; static constexpr int VIDEO_FILE_WIDTH_PX{640}; static constexpr int AVC1_FORMAT{887}; // ID from MediaInfo ASSERT_EQ(uploadedNode->getDuration(), VIDEO_FILE_DURATION_SECS) << "Duration is not correct or unavailable."; ASSERT_EQ(uploadedNode->getHeight(), VIDEO_FILE_HEIGHT_PX) << "Height is not correct or unavailable."; ASSERT_EQ(uploadedNode->getWidth(), VIDEO_FILE_WIDTH_PX) << "Width ID is not correct or unavailable."; ASSERT_EQ(uploadedNode->getVideocodecid(), AVC1_FORMAT) << "Codec ID is not correct or unavailable."; #endif #ifdef USE_FREEIMAGE ASSERT_TRUE(uploadedNode->hasThumbnail()) << "Thumbnail is not available for the uploaded node."; #endif } /** * @test SdkTestSyncUploadsOperations.getnodesByFingerprintNoMtime * * 1. Create two files with identical content but different mtimes in separate directories. * - File `file1` in `dir1` with mtime `mt1` * - File `file1` in `dir2` with mtime `mt2` (differs by MIN_ALLOW_MTIME_DIFFERENCE) * 2. Get nodes by fingerprint with and without mtime * 3. Update the mtime of `file1` to match mtime of `file2`. * 4. Get nodes by fingerprint with and without mtime * 5. Verify that local and remote models match */ TEST_F(SdkTestSyncUploadsOperations, getnodesByFingerprintNoMtime) { const auto cleanup = setCleanupFunction(); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; const std::vector<string> folderNames{"dir1", "dir2"}; const std::string commonContent{"abcde"}; const std::string commonFileName{"file1"}; std::vector<std::unique_ptr<MegaNode>> folderNodes; std::vector<std::unique_ptr<MegaNode>> fileNodes; const auto mtime1 = fs::file_time_type::clock::now(); const auto mtime2 = mtime1 + std::chrono::seconds( MIN_ALLOW_MTIME_DIFFERENCE); // See MIN_ALLOW_MTIME_DIFFERENCE definition const std::vector<std::chrono::time_point<fs::file_time_type::clock>> mtimes{mtime1, mtime2}; ASSERT_NO_FATAL_FAILURE(createTestFile(folderNames.at(0), commonFileName, commonContent, mtimes.at(0), "CF1", true)); ASSERT_NO_FATAL_FAILURE(createTestFile(folderNames.at(1), commonFileName, commonContent, mtimes.at(1), "CF2", false)); ASSERT_NO_FATAL_FAILURE(getTestFolderNodesAndFirstLevelChildren(backupNode, folderNames, folderNodes, fileNodes, commonFileName, "(GN1)")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(0).get(), false /*excludeMtime*/, 1 /*expNumNodes*/, "FP1")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(0).get(), true /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP2")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(1).get(), false /*excludeMtime*/, 1 /*expNumNodes*/, "FP3")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(1).get(), true /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP4")); updateLocalNodeMtime(fileNodes.at(0)->getHandle(), getTestFileAbsolutePath(folderNames.at(0), commonFileName), fileNodes.at(0)->getModificationTime(), /*oldMtime*/ fileNodes.at(1)->getModificationTime(), /*newMtime*/ "MT1"); ASSERT_NO_FATAL_FAILURE(getTestFolderNodesAndFirstLevelChildren(backupNode, folderNames, folderNodes, fileNodes, commonFileName, "(GN2)")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(0).get(), false /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP5")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(0).get(), true /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP6")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(1).get(), false /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP7")); ASSERT_NO_FATAL_FAILURE(getNodesByFingerprint(fileNodes.at(1).get(), true /*excludeMtime*/, fileNodes.size() /*expNumNodes*/, "FP8")); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); } /** * @test SdkTestSyncUploadsOperations.updateLocalNodeMtime * * 1. Create a new local file `file1` in `dir1` with given content and mtime (now). * - Expect a full upload. * 2. Update the mtime of `file1`. * 3. Verify that local and remote models match */ TEST_F(SdkTestSyncUploadsOperations, updateLocalNodeMtime) { const auto cleanup = setCleanupFunction(); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; const std::vector<string> folderNames{"dir1"}; const std::string commonFileName{"file1"}; std::vector<std::unique_ptr<MegaNode>> folderNodes; std::vector<std::unique_ptr<MegaNode>> fileNodes; ASSERT_NO_FATAL_FAILURE(createTestFile(folderNames.at(0), commonFileName, "abcde", fs::file_time_type::clock::now(), "CF1", true)); ASSERT_NO_FATAL_FAILURE(getTestFolderNodesAndFirstLevelChildren(backupNode, folderNames, folderNodes, fileNodes, commonFileName, "(GN1)")); updateLocalNodeMtime(fileNodes.at(0)->getHandle(), getTestFileAbsolutePath(folderNames.at(0), commonFileName), fileNodes.at(0)->getModificationTime(), /*oldMtime*/ fileNodes.at(0)->getModificationTime() + 100, /*newMtime*/ "MT1"); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); } /** * @test SdkTestSyncUploadsOperations.updateLocalEmptyNodeMtime * * 1. Create a new empty local file `empty_file` in `dir1`. * - Expect a full upload. * 2. Update the mtime of `empty_file`. * 3. Verify that sync completes without a full transfer. * 4. Verify that local and remote mtimes match the updated mtime. */ TEST_F(SdkTestSyncUploadsOperations, updateLocalEmptyNodeMtime) { const auto cleanup = setCleanupFunction(); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; const std::vector<string> folderNames{"dir1"}; const std::string commonFileName{"empty_file"}; std::vector<std::unique_ptr<MegaNode>> folderNodes; std::vector<std::unique_ptr<MegaNode>> fileNodes; ASSERT_NO_FATAL_FAILURE(createTestFile(folderNames.at(0), commonFileName, "", fs::file_time_type::clock::now(), "CF_empty", true)); ASSERT_NO_FATAL_FAILURE(getTestFolderNodesAndFirstLevelChildren(backupNode, folderNames, folderNodes, fileNodes, commonFileName, "(GN_empty)")); const LocalPath localPath = getTestFileAbsolutePath(folderNames.at(0), commonFileName); const fs::path localPathFs = u8path_compat(localPath.toPath(false)); const std::string localPathString = localPathFs.string(); auto st = addSyncListenerTracker(localPathString); auto tt = addTransferListenerTracker(localPathString); ASSERT_TRUE(st) << "Cannot add SyncListenerTracker for: " << localPathString; ASSERT_TRUE(tt) << "Cannot add TransferListenerTracker for: " << localPathString; const auto oldMtime = fileNodes.at(0)->getModificationTime(); const auto newMtime = oldMtime + MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_NO_FATAL_FAILURE(updateLocalNodeMtime(fileNodes.at(0)->getHandle(), localPath, oldMtime, newMtime, "MT_empty")); ASSERT_NO_FATAL_FAILURE(waitForSyncAndVerifyTransfer(localPathFs, st, tt, false)); std::unique_ptr<MegaNode> refreshedNode( megaApi[0]->getNodeByHandle(fileNodes.at(0)->getHandle())); ASSERT_TRUE(refreshedNode) << "Cannot get refreshed node for: " << localPathString; ASSERT_EQ(refreshedNode->getModificationTime(), newMtime) << "Cloud node mtime should match the updated local mtime for: " << localPathString; auto [getLocalMtimeOk, localMtime] = mFsAccess->getmtimelocal(localPath); ASSERT_TRUE(getLocalMtimeOk) << "Cannot get local mtime for: " << localPathString; ASSERT_EQ(localMtime, newMtime) << "Local file mtime should remain updated for: " << localPathString; } /** * @test SdkTestSyncUploadsOperations.CloneNodeWithDifferentMtime * * This test validates the clone node mechanism when a file with different mtime * is moved into the sync. The clone should be found via NODE_COMP_DIFFERS_MTIME. * * 1. Create a random file (50MB) outside the sync local root (thread-safe path). * 2. Upload it manually (not through sync) to a unique remote folder outside sync. * 3. Change the mtime of the local file. * 4. Set up expectations that no full transfer will occur. * 5. Move/rename the file into the sync local root with a different name (preserving mtime). * 6. Wait for sync - a clone node operation should occur (NODE_COMP_DIFFERS_MTIME). * 7. Verify both local and remote files have the same mtime (the updated one from step 3). */ TEST_F(SdkTestSyncUploadsOperations, CloneNodeWithDifferentMtime) { static const auto logPre = getLogPrefix(); const auto cleanup = setCleanupFunction(); const std::string originalFileName = "original_file_outside_sync.dat"; const std::string clonedFileName = "cloned_file_inside_sync.dat"; const size_t fileSize = 50 * 1024 * 1024; // 50MB LOG_debug << logPre << "1. Prepare unique remote folder outside sync"; const std::string threadSuffix = "_" + getThisThreadIdStr(); const std::string uniqueRemoteFolderName = "clone_mtime_test_folder" + threadSuffix; const fs::path outsideLocalDir = getLocalTmpDir().parent_path() / ("clone_mtime_test_dir" + threadSuffix); LocalTempDir outsideDirCleanup(outsideLocalDir); const fs::path outsideLocalPath = outsideLocalDir / originalFileName; LOG_debug << logPre << "1b. Creating random file outside sync at: " << path_u8string(outsideLocalPath); createRandomFile(outsideLocalPath, fileSize); LOG_debug << logPre << "2. Creating unique remote folder to manually upload the file: " << uniqueRemoteFolderName; auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; std::unique_ptr<MegaNode> rootTestNode( megaApi[0]->getNodeByHandle(backupNode->getParentHandle())); ASSERT_TRUE(rootTestNode) << "Cannot get parent node of sync remote root"; auto uploadTargetHandle = createFolder(0, uniqueRemoteFolderName.c_str(), rootTestNode.get()); ASSERT_NE(uploadTargetHandle, UNDEF) << "Failed to create unique remote folder"; std::unique_ptr<MegaNode> uploadTargetNode(megaApi[0]->getNodeByHandle(uploadTargetHandle)); ASSERT_TRUE(uploadTargetNode) << "Cannot get created remote folder node"; LOG_debug << logPre << "2b. Uploading file manually to cloud at the unique remote folder: " << uniqueRemoteFolderName; auto uploadedNode = uploadFile(megaApi[0].get(), outsideLocalPath, uploadTargetNode.get()); ASSERT_TRUE(uploadedNode) << "Manual upload failed"; const int64_t originalMtime = uploadedNode->getModificationTime(); LOG_debug << logPre << "2c. Original uploaded file mtime: " << originalMtime; const m_time_t newMtimeTimeT = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; LOG_debug << logPre << "3. Changing local file mtime to: " << newMtimeTimeT << " seconds"; ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(outsideLocalPath)), newMtimeTimeT)) << "Failed to set mtime on file outside sync"; const fs::path insideSyncPath = fs::absolute(getLocalTmpDir() / clonedFileName); LOG_debug << logPre << "4. Moving file into sync and waiting for sync (no transfer expected): " << path_u8string(insideSyncPath); ASSERT_NO_FATAL_FAILURE(moveFileIntoSyncAndVerify(outsideLocalPath, insideSyncPath, false /*isFullUploadExpected*/, newMtimeTimeT /*expectedMtimeAfterMove*/)); LOG_debug << logPre << "5. Verifying mtime of cloned node"; std::unique_ptr<MegaNode> clonedNode( megaApi[0]->getChildNodeOfType(backupNode.get(), clonedFileName.c_str(), FILENODE)); ASSERT_TRUE(clonedNode) << "Cloned node not found in cloud"; ASSERT_EQ(clonedNode->getModificationTime(), newMtimeTimeT) << "Cloned remote node mtime should match the updated local mtime"; LOG_debug << logPre << "6. Verifying mtime of local file"; { auto [getMtimeSucceeded, currentLocalMtimeTimeT] = mFsAccess->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(insideSyncPath))); ASSERT_TRUE(getMtimeSucceeded) << "Failed to get local file mtime"; ASSERT_EQ(currentLocalMtimeTimeT, newMtimeTimeT) << "Local file mtime should still be the updated value"; } LOG_debug << logPre << "7. Verifying mtime of original uploaded node"; { std::unique_ptr<MegaNode> refreshedUploadedNode( megaApi[0]->getNodeByHandle(uploadedNode->getHandle())); ASSERT_TRUE(refreshedUploadedNode) << "Cannot get refreshed original uploaded node"; ASSERT_EQ(refreshedUploadedNode->getModificationTime(), originalMtime) << "Original uploaded node mtime should remain unchanged"; } LOG_debug << logPre << "8. Cleanup: removing remote test folder"; ASSERT_EQ(API_OK, doDeleteNode(0, uploadTargetNode.get())) << "Failed to cleanup remote test folder"; LOG_debug << logPre << "Test completed successfully"; } /** * @test SdkTestSyncUploadsOperations.CloneEmptyNodeWithDifferentMtime * * This test validates the clone node path for an empty file whose mtime differs. * * 1. Create an empty file outside the sync. * 2. Upload it manually to a unique remote folder outside the sync root. * 3. Change the local file mtime. * 4. Move it into the sync root with a different name. * 5. Verify the sync completes without a full transfer and preserves the updated mtime. */ TEST_F(SdkTestSyncUploadsOperations, CloneEmptyNodeWithDifferentMtime) { static const std::string logPre{"CloneEmptyNodeWithDifferentMtime: "}; const auto cleanup = setCleanupFunction(); const std::string originalFileName = "original_empty_file_outside_sync.dat"; const std::string clonedFileName = "cloned_empty_file_inside_sync.dat"; LOG_debug << logPre << "1. Prepare unique remote folder outside sync"; const std::string threadSuffix = "_" + getThisThreadIdStr(); const std::string uniqueRemoteFolderName = "clone_empty_mtime_test_folder" + threadSuffix; const fs::path outsideLocalDir = getLocalTmpDir().parent_path() / ("clone_empty_mtime_test_dir" + threadSuffix); LocalTempDir outsideDirCleanup(outsideLocalDir); const fs::path outsideLocalPath = outsideLocalDir / originalFileName; LOG_debug << logPre << "1b. Creating empty file outside sync at: " << path_u8string(outsideLocalPath); sdk_test::createFile(outsideLocalPath, std::string_view{}); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Cannot get backup sync node"; std::unique_ptr<MegaNode> rootTestNode( megaApi[0]->getNodeByHandle(backupNode->getParentHandle())); ASSERT_TRUE(rootTestNode) << "Cannot get parent node of sync remote root"; LOG_debug << logPre << "2. Creating unique remote folder to manually upload the file: " << uniqueRemoteFolderName; auto uploadTargetHandle = createFolder(0, uniqueRemoteFolderName.c_str(), rootTestNode.get()); ASSERT_NE(uploadTargetHandle, UNDEF) << "Failed to create unique remote folder"; std::unique_ptr<MegaNode> uploadTargetNode(megaApi[0]->getNodeByHandle(uploadTargetHandle)); ASSERT_TRUE(uploadTargetNode) << "Cannot get created remote folder node"; LOG_debug << logPre << "2b. Uploading empty file manually to cloud at: " << uniqueRemoteFolderName; auto uploadedNode = uploadFile(megaApi[0].get(), outsideLocalPath, uploadTargetNode.get()); ASSERT_TRUE(uploadedNode) << "Manual upload failed"; ASSERT_EQ(uploadedNode->getSize(), 0) << "Uploaded node should remain empty"; const int64_t originalMtime = uploadedNode->getModificationTime(); const m_time_t newMtimeTimeT = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; LOG_debug << logPre << "3. Changing local empty file mtime to: " << newMtimeTimeT; ASSERT_TRUE( mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(outsideLocalPath)), newMtimeTimeT)) << "Failed to set mtime on empty file outside sync"; const fs::path insideSyncPath = fs::absolute(getLocalTmpDir() / clonedFileName); LOG_debug << logPre << "4. Moving empty file into sync and waiting for sync (no transfer " << "expected): " << path_u8string(insideSyncPath); ASSERT_NO_FATAL_FAILURE(moveFileIntoSyncAndVerify(outsideLocalPath, insideSyncPath, false /*isFullUploadExpected*/, newMtimeTimeT /*expectedMtimeAfterMove*/)); LOG_debug << logPre << "5. Verifying cloned empty node"; std::unique_ptr<MegaNode> clonedNode( megaApi[0]->getChildNodeOfType(backupNode.get(), clonedFileName.c_str(), FILENODE)); ASSERT_TRUE(clonedNode) << "Cloned empty node not found in cloud"; ASSERT_EQ(clonedNode->getModificationTime(), newMtimeTimeT) << "Cloned remote node mtime should match the updated local mtime"; ASSERT_EQ(clonedNode->getSize(), 0) << "Cloned remote node should remain empty"; auto [getMtimeSucceeded, currentLocalMtimeTimeT] = mFsAccess->getmtimelocal(LocalPath::fromAbsolutePath(path_u8string(insideSyncPath))); ASSERT_TRUE(getMtimeSucceeded) << "Failed to get local mtime of cloned empty file"; ASSERT_EQ(currentLocalMtimeTimeT, newMtimeTimeT) << "Local empty file mtime should still be the updated value"; ASSERT_EQ(fs::file_size(insideSyncPath), 0u) << "Local cloned file should remain empty after sync"; LOG_debug << logPre << "6. Verifying original uploaded empty node"; std::unique_ptr<MegaNode> refreshedUploadedNode( megaApi[0]->getNodeByHandle(uploadedNode->getHandle())); ASSERT_TRUE(refreshedUploadedNode) << "Cannot get refreshed original uploaded node"; ASSERT_EQ(refreshedUploadedNode->getModificationTime(), originalMtime) << "Original uploaded node mtime should remain unchanged"; ASSERT_EQ(refreshedUploadedNode->getSize(), 0) << "Original uploaded node should remain empty"; LOG_debug << logPre << "7. Cleanup: removing remote test folder"; ASSERT_EQ(API_OK, doDeleteNode(0, uploadTargetNode.get())) << "Failed to cleanup remote test folder"; LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests that async MAC computation doesn't block small files. * * Creates 3 files (2 small, 1 large ~10MB), syncs them, then updates mtimes for all 3. * Verifies that the small files complete their mtime-only sync quickly, even while * the large file's MAC computation may be in progress. * * This validates the non-blocking MAC computation implementation for SRT_CSF cases. */ TEST_F(SdkTestSyncUploadsOperations, AsyncMacComputationDoesNotBlockSmallFiles) { static const std::string logPre{"AsyncMacComputationDoesNotBlockSmallFiles: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; ASSERT_NO_FATAL_FAILURE(runAsyncMacNonBlockingScenario(logPre, "csf_", false)); } /** * @brief Tests async MAC computation does not block small files when resyncing from CXF state. * * Creates two small files and one large file, updates mtimes, removes and re-adds the sync (CXF), * and verifies small files complete before the large file while using the shared async MAC path. */ TEST_F(SdkTestSyncUploadsOperations, AsyncMacComputationDoesNotBlockSmallFilesFromCxf) { static const std::string logPre{"AsyncMacComputationDoesNotBlockSmallFilesFromCxf: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; ASSERT_NO_FATAL_FAILURE(runAsyncMacNonBlockingScenario(logPre, "cxf_", true)); } /** * @brief Tests async MAC computation for SRT_CXF case (local logout/relog). * * This exercises the scenario where LocalNodes are lost (logout/relog) but files * remain in sync folder. Upon re-sync, files with mtime-only differences use * async MAC computation as unsynced nodes are re-processed. * * The test verifies that the sync eventually completes correctly. */ TEST_F(SdkTestSyncUploadsOperations, AsyncMacComputationForCxfCase_LocalNewer) { ASSERT_NO_FATAL_FAILURE( runAsyncMacComputationForCxfCase("AsyncMacComputationForCxfCase_LocalNewer: ", CxfMtimeDirection::Increase)); } TEST_F(SdkTestSyncUploadsOperations, AsyncMacComputationForCxfCase_CloudNewer) { ASSERT_NO_FATAL_FAILURE( runAsyncMacComputationForCxfCase("AsyncMacComputationForCxfCase_CloudNewer: ", CxfMtimeDirection::Decrease)); } /** * @brief Tests MAC computation obsolescence when file is deleted during computation. * * Creates a large file, triggers mtime update to start MAC computation, then * deletes the file while computation may be pending. The sync should handle * this gracefully without errors. */ TEST_F(SdkTestSyncUploadsOperations, MacComputationObsolescenceOnDelete) { static const std::string logPre{"MacComputationObsolescenceOnDelete: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; // Use a large file to increase chance of pending MAC computation static constexpr size_t LARGE_FILE_SIZE = 100 * 1024 * 1024; // 100MB const fs::path largeFilePath = fs::absolute(getLocalTmpDir() / "large_file_delete_test.dat"); LOG_debug << logPre << "1. Creating large test file"; { auto st = addSyncListenerTracker(largeFilePath.string()); ASSERT_TRUE(st); auto localFile = std::make_shared<sdk_test::LocalTempFile>(largeFilePath, LARGE_FILE_SIZE); mLocalFiles.emplace_back(localFile); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << "Large file sync timed out"; ASSERT_EQ(errCode, API_OK) << "Large file sync failed"; } ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); LOG_debug << logPre << "2. Updating mtime to trigger MAC computation"; const m_time_t newMtime = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_TRUE(mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(largeFilePath)), newMtime)); // Brief delay to let sync start processing std::this_thread::sleep_for(std::chrono::seconds(3)); LOG_debug << logPre << "3. Deleting the file while MAC computation may be pending"; mLocalFiles.clear(); LOG_debug << logPre << "4. Waiting for sync to stabilize (file should be removed from cloud)"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); // Verify the cloud node is gone auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Backup node not found"; std::unique_ptr<MegaNode> cloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_file_delete_test.dat", FILENODE)); ASSERT_FALSE(cloudNode) << "Cloud node should have been deleted"; LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests MAC computation obsolescence when file is moved during computation. * * Creates a large file, triggers mtime update to start MAC computation, then * moves the file to another directory while computation may be pending. * The sync should handle this gracefully, completing the move correctly. */ TEST_F(SdkTestSyncUploadsOperations, MacComputationObsolescenceOnMove) { static const std::string logPre{"MacComputationObsolescenceOnMove: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; // Sync is already created during SetUp() // Use a large file to increase chance of pending MAC computation static constexpr size_t LARGE_FILE_SIZE = 100 * 1024 * 1024; // 100MB const fs::path largeFilePath = fs::absolute(getLocalTmpDir() / "large_file_move_test.dat"); const fs::path destDir = fs::absolute(getLocalTmpDir() / "dir1"); const fs::path destFilePath = destDir / "large_file_move_test.dat"; LOG_debug << logPre << "1. Creating large test file"; { auto st = addSyncListenerTracker(largeFilePath.string()); ASSERT_TRUE(st); auto localFile = std::make_shared<sdk_test::LocalTempFile>(largeFilePath, LARGE_FILE_SIZE); mLocalFiles.emplace_back(localFile); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << "Large file sync timed out"; ASSERT_EQ(errCode, API_OK) << "Large file sync failed"; } ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); LOG_debug << logPre << "2. Updating mtime to trigger MAC computation"; const m_time_t newMtime = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_TRUE(mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(largeFilePath)), newMtime)); // Brief delay to let sync start processing std::this_thread::sleep_for(std::chrono::seconds(3)); LOG_debug << logPre << "3. Moving file to dir1 while MAC computation may be pending"; { auto st = addSyncListenerTracker(destFilePath.string()); ASSERT_TRUE(st); // On Windows, if MAC computation has the file open, the move will fail. // Retry a few times with delays to handle this platform limitation. std::error_code ec; static constexpr int MAX_MOVE_RETRIES = 10; static constexpr auto RETRY_DELAY = std::chrono::seconds(3); for (int attempt = 0; attempt < MAX_MOVE_RETRIES; ++attempt) { ec = mLocalFiles.back()->move(destFilePath); if (!ec) { LOG_debug << logPre << "File moved successfully on attempt " << (attempt + 1); break; } LOG_debug << logPre << "Move attempt " << (attempt + 1) << " failed: " << ec.message() << ". Retrying..."; std::this_thread::sleep_for(RETRY_DELAY); } ASSERT_FALSE(ec) << "Failed to move large file after " << MAX_MOVE_RETRIES << " attempts: " << ec.message(); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << "Large file sync timed out"; ASSERT_EQ(errCode, API_OK) << "Large file sync failed"; } LOG_debug << logPre << "4. Waiting for sync to complete the move"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); // Verify the cloud node is in the new location auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Backup node not found"; std::unique_ptr<MegaNode> dir1Node( megaApi[0]->getChildNodeOfType(backupNode.get(), "dir1", FOLDERNODE)); ASSERT_TRUE(dir1Node) << "dir1 not found in cloud"; std::unique_ptr<MegaNode> movedNode( megaApi[0]->getChildNodeOfType(dir1Node.get(), "large_file_move_test.dat", FILENODE)); ASSERT_TRUE(movedNode) << "Moved file not found in dir1"; // Original location should be empty std::unique_ptr<MegaNode> oldLocationNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_file_move_test.dat", FILENODE)); ASSERT_FALSE(oldLocationNode) << "File should not exist at original location"; // Verify the mtime of the moved file ASSERT_EQ(movedNode->getModificationTime(), newMtime) << "Moved file should have the updated mtime"; LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests MAC computation obsolescence when a file is truncated to empty during computation. * * Creates one small and one large file, triggers mtime updates for both, waits until the small * file completes while the large one is still pending, truncates the large file to empty while its * async MAC computation should still be in flight, and finally updates the empty file mtime again * to exercise the empty-file MAC path after the state was invalidated. */ TEST_F(SdkTestSyncUploadsOperations, MacComputationObsolescenceOnTruncateToEmptyThenMtimeUpdate) { static const std::string logPre{"MacComputationObsolescenceOnTruncateToEmptyThenMtimeUpdate: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; static constexpr size_t LARGE_FILE_SIZE = 300 * 1024 * 1024; // 300MB for reliable MAC delay static constexpr size_t SMALL_FILE_SIZE = (5 * 1024 * 1024) + (2 * 1024) + 3; // ~5MB const fs::path largeFilePath = fs::absolute(getLocalTmpDir() / "large_file_truncate_to_empty_test.dat"); const fs::path smallFilePath = fs::absolute(getLocalTmpDir() / "small_file_truncate_to_empty_test.dat"); const LocalPath largeLocalPath = LocalPath::fromAbsolutePath(path_u8string(largeFilePath)); auto createFileAndWaitForSync = [&](const fs::path& path, const size_t size, const char* label) { auto st = addSyncListenerTracker(path.string()); ASSERT_TRUE(st) << "Cannot add SyncListenerTracker for " << label; auto localFile = std::make_shared<sdk_test::LocalTempFile>(path, size); mLocalFiles.emplace_back(localFile); auto [status, errCode] = st->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(status, std::future_status::ready) << label << " sync timed out"; ASSERT_EQ(errCode, API_OK) << label << " sync failed"; }; LOG_debug << logPre << "1. Creating small and large test files"; ASSERT_NO_FATAL_FAILURE(createFileAndWaitForSync(smallFilePath, SMALL_FILE_SIZE, "Small file")); ASSERT_NO_FATAL_FAILURE(createFileAndWaitForSync(largeFilePath, LARGE_FILE_SIZE, "Large file")); ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); auto stSmall = addSyncListenerTracker(smallFilePath.string()); auto stLarge = addSyncListenerTracker(largeFilePath.string()); ASSERT_TRUE(stSmall) << "Cannot add SyncListenerTracker for small file mtime update"; ASSERT_TRUE(stLarge) << "Cannot add SyncListenerTracker for large file mtime update"; LOG_debug << logPre << "2. Updating mtimes to trigger MAC computation"; const m_time_t intermediateMtime = m_time(nullptr) + MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_TRUE(mFsAccess->setmtimelocal(LocalPath::fromAbsolutePath(path_u8string(smallFilePath)), intermediateMtime)) << "Failed to set mtime on small file"; ASSERT_TRUE(mFsAccess->setmtimelocal(largeLocalPath, intermediateMtime)) << "Failed to set mtime on large file"; LOG_debug << logPre << "3. Waiting for small file to finish while large file remains pending"; auto [smallStatus, smallErrCode] = stSmall->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(smallStatus, std::future_status::ready) << "Small file mtime sync timed out"; ASSERT_EQ(smallErrCode, API_OK) << "Small file mtime sync failed"; auto [largeStatusBeforeTruncate, _] = stLarge->waitForCompletion(std::chrono::seconds(1)); ASSERT_EQ(largeStatusBeforeTruncate, std::future_status::timeout) << "Large file completed before truncation; test did not exercise in-flight MAC " "obsolescence"; LOG_debug << logPre << "4. Truncating large file to empty while MAC computation is pending"; std::error_code ec; fs::resize_file(largeFilePath, 0, ec); ASSERT_FALSE(ec) << "Failed to truncate file to empty: " << ec.message(); ASSERT_EQ(fs::file_size(largeFilePath), 0u) << "Local file should be empty after truncation"; LOG_debug << logPre << "5. Waiting for the truncated large file to resync"; auto [largeStatusAfterTruncate, largeErrCode] = stLarge->waitForCompletion(COMMON_TIMEOUT); ASSERT_EQ(largeStatusAfterTruncate, std::future_status::ready) << "Large truncated file sync timed out"; ASSERT_EQ(largeErrCode, API_OK) << "Large truncated file sync failed"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Backup node not found"; std::unique_ptr<MegaNode> emptyCloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_file_truncate_to_empty_test.dat", FILENODE)); ASSERT_TRUE(emptyCloudNode) << "Cloud node should still exist after truncation to empty"; ASSERT_EQ(emptyCloudNode->getSize(), 0) << "Cloud node should be empty after stabilization"; ASSERT_EQ(fs::file_size(largeFilePath), 0u) << "Local file should remain empty after sync"; auto stAfterEmpty = addSyncListenerTracker(largeFilePath.string()); auto ttAfterEmpty = addTransferListenerTracker(largeFilePath.string()); ASSERT_TRUE(stAfterEmpty) << "Cannot add SyncListenerTracker for second mtime update"; ASSERT_TRUE(ttAfterEmpty) << "Cannot add TransferListenerTracker for second mtime update"; LOG_debug << logPre << "6. Updating mtime again on the now-empty file"; const auto emptyFileMtime = emptyCloudNode->getModificationTime(); const auto finalMtime = emptyFileMtime + MIN_ALLOW_MTIME_DIFFERENCE; ASSERT_NO_FATAL_FAILURE(updateLocalNodeMtime(emptyCloudNode->getHandle(), largeLocalPath, emptyFileMtime, finalMtime, "MT_truncate_empty_followup")); ASSERT_NO_FATAL_FAILURE( waitForSyncAndVerifyTransfer(largeFilePath, stAfterEmpty, ttAfterEmpty, false)); backupNode = getBackupNode(); ASSERT_TRUE(backupNode) << "Backup node not found after second mtime update"; std::unique_ptr<MegaNode> refreshedEmptyCloudNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_file_truncate_to_empty_test.dat", FILENODE)); ASSERT_TRUE(refreshedEmptyCloudNode) << "Cannot get refreshed cloud node after mtime update"; ASSERT_EQ(refreshedEmptyCloudNode->getModificationTime(), finalMtime) << "Cloud node mtime should match the second local mtime update"; ASSERT_EQ(refreshedEmptyCloudNode->getSize(), 0) << "Cloud node should remain empty after the second mtime update"; auto [getLocalMtimeOk, localMtime] = mFsAccess->getmtimelocal(largeLocalPath); ASSERT_TRUE(getLocalMtimeOk) << "Failed to get local mtime after second update"; ASSERT_EQ(localMtime, finalMtime) << "Local empty file mtime should match the second updated value"; ASSERT_EQ(fs::file_size(largeFilePath), 0u) << "Local file should remain empty after the second mtime update"; LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests pre-computed MAC for clone candidates (SRT_XSF case). * * This test validates the non-blocking MAC pre-computation for files that * have potential clone candidates in the cloud (outside the sync root). * * Scenario: * 1. Upload several files to cloud (outside sync root) - various sizes * 2. Create sync with local files having same content but different mtime * 3. Verify that small files sync quickly (cloned) while large file MAC computes */ TEST_F(SdkTestSyncUploadsOperations, PreComputedMacForCloneCandidatesNonBlocking) { static const std::string logPre{"PreComputedMacForCloneCandidatesNonBlocking: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; // File sizes - names chosen so large file is processed first alphabetically // ("a_large" < "b_small1" < "c_small2") static constexpr size_t SMALL_FILE_SIZE = (5 * 1024 * 1024) + (100 * 1024) + 7; // ~5MB static constexpr size_t LARGE_FILE_SIZE = (400 * 1024 * 1024) + (500 * 1024) + 3; // ~400MB LOG_debug << logPre << "1. Creating cloud folder for clone candidates (outside sync root)"; std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode()); ASSERT_TRUE(rootNode); const std::string candidatesFolderName = "clone_candidates_" + std::to_string(m_time(nullptr)); { RequestTracker createFolderTracker(megaApi[0].get()); megaApi[0]->createFolder(candidatesFolderName.c_str(), rootNode.get(), &createFolderTracker); ASSERT_EQ(API_OK, createFolderTracker.waitForResult()); } std::unique_ptr<MegaNode> candidatesFolder( megaApi[0]->getChildNode(rootNode.get(), candidatesFolderName.c_str())); ASSERT_TRUE(candidatesFolder); LOG_debug << logPre << "2. Creating temp files for upload to cloud"; // Create temp files outside sync folder using LocalTempFile const fs::path tempDir = makeProcessTempDir("clone_test"); auto largeTempFile = std::make_shared<sdk_test::LocalTempFile>(tempDir / "a_large_file.dat", LARGE_FILE_SIZE); auto small1TempFile = std::make_shared<sdk_test::LocalTempFile>(tempDir / "b_small_file1.dat", SMALL_FILE_SIZE); auto small2TempFile = std::make_shared<sdk_test::LocalTempFile>(tempDir / "c_small_file2.dat", SMALL_FILE_SIZE); LOG_debug << logPre << "3. Uploading clone candidate files to cloud (outside sync)"; // Helper to upload a file to cloud with older mtime auto uploadToCloud = [&](const fs::path& filePath, const int timeoutSeconds = 60) { TransferTracker uploadTracker(megaApi[0].get()); MegaUploadOptions uploadOptions; uploadOptions.mtime = m_time(nullptr) - 86400; const auto localPath = path_u8string(filePath); megaApi[0]->startUpload(localPath, candidatesFolder.get(), nullptr, &uploadOptions, &uploadTracker); return uploadTracker.waitForResult(timeoutSeconds) == API_OK; }; ASSERT_TRUE(uploadToCloud(largeTempFile->getPath(), 600)) << "Failed to upload large file"; ASSERT_TRUE(uploadToCloud(small1TempFile->getPath())) << "Failed to upload small file 1"; ASSERT_TRUE(uploadToCloud(small2TempFile->getPath())) << "Failed to upload small file 2"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); LOG_debug << logPre << "4. Moving files into sync folder (triggers clone with MAC computation)"; const fs::path largeSyncPath = fs::absolute(getLocalTmpDir() / "a_large_file.dat"); const fs::path small1SyncPath = fs::absolute(getLocalTmpDir() / "b_small_file1.dat"); const fs::path small2SyncPath = fs::absolute(getLocalTmpDir() / "c_small_file2.dat"); // Track completion times using clock = std::chrono::steady_clock; std::atomic<clock::time_point> largeCompleteTime{clock::time_point::max()}; std::atomic<clock::time_point> small1CompleteTime{clock::time_point::max()}; std::atomic<clock::time_point> small2CompleteTime{clock::time_point::max()}; std::atomic<bool> largeDone{false}, small1Done{false}, small2Done{false}; auto stLarge = mSyncListenerTrackers.add(largeSyncPath.string()); auto stSmall1 = mSyncListenerTrackers.add(small1SyncPath.string()); auto stSmall2 = mSyncListenerTrackers.add(small2SyncPath.string()); ASSERT_TRUE(stLarge && stSmall1 && stSmall2); // Move files into sync folder ASSERT_FALSE(largeTempFile->move(largeSyncPath)) << "Failed to move large file"; ASSERT_FALSE(small1TempFile->move(small1SyncPath)) << "Failed to move small file 1"; ASSERT_FALSE(small2TempFile->move(small2SyncPath)) << "Failed to move small file 2"; // Track these for cleanup mLocalFiles.emplace_back(largeTempFile); mLocalFiles.emplace_back(small1TempFile); mLocalFiles.emplace_back(small2TempFile); LOG_debug << logPre << "5. Waiting for sync completions and tracking order"; // Wait in parallel threads to capture accurate timestamps std::thread waitLarge( [&]() { auto [status, err] = stLarge->waitForCompletion(std::chrono::seconds(600)); if (status == std::future_status::ready && err == API_OK) { largeCompleteTime = clock::now(); largeDone = true; LOG_debug << logPre << "Large file sync completed"; } }); std::thread waitSmall1( [&]() { auto [status, err] = stSmall1->waitForCompletion(std::chrono::seconds(300)); if (status == std::future_status::ready && err == API_OK) { small1CompleteTime = clock::now(); small1Done = true; LOG_debug << logPre << "Small file 1 sync completed"; } }); std::thread waitSmall2( [&]() { auto [status, err] = stSmall2->waitForCompletion(std::chrono::seconds(300)); if (status == std::future_status::ready && err == API_OK) { small2CompleteTime = clock::now(); small2Done = true; LOG_debug << logPre << "Small file 2 sync completed"; } }); waitLarge.join(); waitSmall1.join(); waitSmall2.join(); // Verify all completed ASSERT_TRUE(largeDone) << "Large file sync failed or timed out"; ASSERT_TRUE(small1Done) << "Small file 1 sync failed or timed out"; ASSERT_TRUE(small2Done) << "Small file 2 sync failed or timed out"; LOG_debug << logPre << "6. Verifying completion order (small files should complete before large)"; auto largeTime = largeCompleteTime.load(); auto small1Time = small1CompleteTime.load(); auto small2Time = small2CompleteTime.load(); LOG_debug << logPre << "Completion times relative to large file:" << " small1=" << std::chrono::duration_cast<std::chrono::milliseconds>(small1Time - largeTime).count() << "ms" << " small2=" << std::chrono::duration_cast<std::chrono::milliseconds>(small2Time - largeTime).count() << "ms"; // The key assertion: small files should complete BEFORE large file EXPECT_LT(small1Time, largeTime) << "Small file 1 should complete before large file - async MAC may be blocking"; EXPECT_LT(small2Time, largeTime) << "Small file 2 should complete before large file - async MAC may be blocking"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); // Verify files exist in sync auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode); std::unique_ptr<MegaNode> largeNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "a_large_file.dat", FILENODE)); std::unique_ptr<MegaNode> small1Node( megaApi[0]->getChildNodeOfType(backupNode.get(), "b_small_file1.dat", FILENODE)); std::unique_ptr<MegaNode> small2Node( megaApi[0]->getChildNodeOfType(backupNode.get(), "c_small_file2.dat", FILENODE)); ASSERT_TRUE(largeNode) << "Large file not found in sync"; ASSERT_TRUE(small1Node) << "Small file 1 not found in sync"; ASSERT_TRUE(small2Node) << "Small file 2 not found in sync"; // Cleanup fs::remove_all(tempDir); { RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->remove(candidatesFolder.get(), &removeTracker); removeTracker.waitForResult(); } LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests clone candidate MAC computation when local file is deleted mid-computation. */ TEST_F(SdkTestSyncUploadsOperations, CloneCandidateMacObsolescenceOnLocalDelete) { static const std::string logPre{"CloneCandidateMacObsolescenceOnLocalDelete: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; static constexpr size_t LARGE_FILE_SIZE = 300 * 1024 * 1024; // 300MB for reliable MAC delay CloneCandidateSetup setup; ASSERT_NO_FATAL_FAILURE(createCloneCandidateSetup(setup, logPre, "clone_del_test", "clone_del", "large_clone_del.dat", LARGE_FILE_SIZE)); LOG_debug << logPre << "2. Moving file into sync folder (triggers clone MAC computation)"; ASSERT_FALSE(setup.tempFile->move(setup.localPathInSync)) << "Failed to move file into sync"; mLocalFiles.emplace_back(std::move(setup.tempFile)); // Brief delay to let MAC computation start std::this_thread::sleep_for(std::chrono::seconds(10)); LOG_debug << logPre << "3. Deleting local file while MAC computation may be pending"; mLocalFiles.clear(); LOG_debug << logPre << "4. Waiting for sync to stabilize"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); // File should not exist in sync since we deleted it LOG_debug << logPre << "5. Verifying file does not exist in sync"; auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode); std::unique_ptr<MegaNode> syncNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_clone_del.dat", FILENODE)); ASSERT_FALSE(syncNode) << "File should not exist in sync after deletion"; // Cleanup fs::remove_all(setup.tempDir); { RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->remove(setup.candidatesFolder.get(), &removeTracker); removeTracker.waitForResult(); } LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests clone candidate MAC computation when cloud candidate is deleted mid-computation. */ TEST_F(SdkTestSyncUploadsOperations, CloneCandidateMacObsolescenceOnCloudDelete) { static const std::string logPre{"CloneCandidateMacObsolescenceOnCloudDelete: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; static constexpr size_t LARGE_FILE_SIZE = 300 * 1024 * 1024; // 300MB for reliable MAC delay CloneCandidateSetup setup; ASSERT_NO_FATAL_FAILURE(createCloneCandidateSetup(setup, logPre, "clone_cloud_del", "clone_cloud_del", "large_cloud_del.dat", LARGE_FILE_SIZE, true)); LOG_debug << logPre << "2. Moving file into sync folder (triggers clone MAC computation)"; // Set up tracker BEFORE moving file auto st = mSyncListenerTrackers.add(setup.localPathInSync.string()); ASSERT_TRUE(st); ASSERT_FALSE(setup.tempFile->move(setup.localPathInSync)) << "Failed to move file into sync"; mLocalFiles.emplace_back(std::move(setup.tempFile)); // Brief delay to let MAC computation start std::this_thread::sleep_for(std::chrono::seconds(10)); LOG_debug << logPre << "3. Deleting cloud candidate while MAC computation may be pending"; { RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->remove(setup.candidateNode.get(), &removeTracker); ASSERT_EQ(API_OK, removeTracker.waitForResult()); } LOG_debug << logPre << "4. Waiting for sync to complete (should fall back to full upload)"; auto [status, err] = st->waitForCompletion(std::chrono::seconds(600)); ASSERT_EQ(status, std::future_status::ready) << "Sync timed out after cloud candidate deletion"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); // File should exist in sync (uploaded, not cloned since candidate was deleted) LOG_debug << logPre << "5. Verifying file exists in sync"; auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode); std::unique_ptr<MegaNode> syncNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_cloud_del.dat", FILENODE)); ASSERT_TRUE(syncNode) << "File should exist in sync after full upload"; // Cleanup fs::remove_all(setup.tempDir); { RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->remove(setup.candidatesFolder.get(), &removeTracker); removeTracker.waitForResult(); } LOG_debug << logPre << "Test completed successfully"; } /** * @brief Tests clone candidate MAC computation when the local file is truncated to empty * mid-computation. * * The clone precomputation should be discarded, the original cloud candidate should remain * untouched, and the sync should fall back to uploading the now-empty local file. */ TEST_F(SdkTestSyncUploadsOperations, CloneCandidateMacObsolescenceOnTruncateToEmpty) { static const std::string logPre{"CloneCandidateMacObsolescenceOnTruncateToEmpty: "}; auto cleanup = setCleanupFunction(); LOG_debug << logPre << "Test started"; static constexpr size_t LARGE_FILE_SIZE = 300 * 1024 * 1024; // 300MB for reliable MAC delay CloneCandidateSetup setup; ASSERT_NO_FATAL_FAILURE(createCloneCandidateSetup(setup, logPre, "clone_truncate_empty", "clone_truncate_empty", "large_clone_truncate_empty.dat", LARGE_FILE_SIZE, true)); LOG_debug << logPre << "2. Moving file into sync folder (triggers clone MAC computation)"; auto st = addSyncListenerTracker(setup.localPathInSync.string()); ASSERT_TRUE(st); ASSERT_FALSE(setup.tempFile->move(setup.localPathInSync)) << "Failed to move file into sync"; mLocalFiles.emplace_back(std::move(setup.tempFile)); std::this_thread::sleep_for(std::chrono::seconds(10)); auto [statusBeforeTruncate, _] = st->waitForCompletion(std::chrono::seconds(1)); ASSERT_EQ(statusBeforeTruncate, std::future_status::timeout) << "Clone candidate MAC completed before truncation; test did not exercise in-flight " "obsolescence"; LOG_debug << logPre << "3. Truncating local file to empty while clone MAC may be pending"; std::error_code ec; fs::resize_file(setup.localPathInSync, 0, ec); ASSERT_FALSE(ec) << "Failed to truncate file to empty: " << ec.message(); ASSERT_EQ(fs::file_size(setup.localPathInSync), 0u) << "Local file should be empty"; LOG_debug << logPre << "4. Waiting for sync to complete via fallback upload"; auto [syncStatus, syncErr] = st->waitForCompletion(std::chrono::seconds(600)); ASSERT_EQ(syncStatus, std::future_status::ready) << "Sync timed out after truncation to empty"; ASSERT_EQ(syncErr, API_OK) << "Sync failed after truncation to empty"; ASSERT_NO_FATAL_FAILURE(waitForSyncToMatchCloudAndLocalExhaustive()); auto backupNode = getBackupNode(); ASSERT_TRUE(backupNode); std::unique_ptr<MegaNode> syncNode( megaApi[0]->getChildNodeOfType(backupNode.get(), "large_clone_truncate_empty.dat", FILENODE)); ASSERT_TRUE(syncNode) << "File should exist in sync after fallback upload"; ASSERT_EQ(syncNode->getSize(), 0) << "Synced file should be empty after truncation"; ASSERT_EQ(fs::file_size(setup.localPathInSync), 0u) << "Local synced file should remain empty"; std::unique_ptr<MegaNode> refreshedCandidate( megaApi[0]->getNodeByHandle(setup.candidateNode->getHandle())); ASSERT_TRUE(refreshedCandidate) << "Original cloud candidate should still exist"; ASSERT_EQ(refreshedCandidate->getSize(), static_cast<int64_t>(LARGE_FILE_SIZE)) << "Original cloud candidate should remain unchanged"; fs::remove_all(setup.tempDir); { RequestTracker removeTracker(megaApi[0].get()); megaApi[0]->remove(setup.candidatesFolder.get(), &removeTracker); removeTracker.waitForResult(); } LOG_debug << logPre << "Test completed successfully"; } #endif // ENABLE_SYNC ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_user_alerts.cpp����������������������������������������������0000664�0000000�0000000�00000031321�15162662266�0023070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sdk_test_user_alerts.cpp * @brief Tests that involve interactions with User alerts. */ #include "integration_test_utils.h" #include "sdk_test_utils.h" #include "SdkTest_test.h" namespace { static bool ComapreResults(const std::vector<int>& alertTypes, unique_ptr<MegaUserAlertList>&& list) { if (alertTypes.size() != static_cast<size_t>(list->size())) { return false; } for (size_t idx = 0; idx < alertTypes.size(); ++idx) { if (alertTypes[idx] != list->get(static_cast<int>(idx))->getType()) { return false; } } return true; } /** * @brief SdkTest.UserAlertPaymentVsReminder */ TEST_F(SdkTest, UserAlertPaymentVsReminder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(WaitFor( [this]() { return megaApi[0]->isLoggedIn() && megaApi[0]->getClient()->useralerts.catchupdone; }, 3000)); // there should be no existing alerts of test account UserAlerts& userAlerts = megaApi[0]->getClient()->useralerts; // add a Payment Reminder auto referenceTime = time(nullptr); userAlerts.add( new UserAlert::PaymentReminder(referenceTime, referenceTime + 100, userAlerts.nextId())); unique_ptr<MegaUserAlertList> megaAlerts{megaApi[0]->getUserAlerts()}; ASSERT_TRUE(ComapreResults(std::vector<int>{MegaUserAlert::TYPE_PAYMENTREMINDER}, std::move(megaAlerts))); // add a failed Payment done after the reminder - reminder should be kept userAlerts.add( new UserAlert::Payment(false, 1, referenceTime, userAlerts.nextId(), name_id::psts_v2)); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults( std::vector<int>{MegaUserAlert::TYPE_PAYMENTREMINDER, MegaUserAlert::TYPE_PAYMENT_FAILED}, std::move(megaAlerts))); // add a successful Payment done after the reminder - reminder should be removed userAlerts.add( new UserAlert::Payment(true, 1, referenceTime, userAlerts.nextId(), name_id::psts_v2)); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults( std::vector<int>{MegaUserAlert::TYPE_PAYMENT_FAILED, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED}, std::move(megaAlerts))); // prepare an empty file to upload // this will give alerts a chance to be persisted in database PerApi& target = mApi[0]; target.resetlastEvent(); std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode()); ASSERT_NE(rootNode.get(), nullptr); const auto newNode = sdk_test::uploadFile(megaApi[0].get(), sdk_test::LocalTempFile{"alerts", 1}, rootNode.get()); ASSERT_TRUE(newNode) << "Cannot create node in Cloud Drive"; ASSERT_TRUE(WaitFor( [&target]() { return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, 10000)); // store current session, for reading the same database later std::unique_ptr<char[]> session(dumpSession(0)); // logout and use session to login, imitate app's restart ASSERT_NO_FATAL_FAILURE(locallogout(0)); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get(), 0)); // this will make sure that alerts are loaded from database ASSERT_NO_FATAL_FAILURE(fetchnodes(0, maxTimeout)); // verify the alerts after restart // the removed reminder won't appear anymore megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults( std::vector<int>{MegaUserAlert::TYPE_PAYMENT_FAILED, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED}, std::move(megaAlerts))); // add an old payment reminder - it should be removed userAlerts.add(new UserAlert::PaymentReminder(referenceTime - 100, referenceTime + 100, userAlerts.nextId())); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults( std::vector<int>{MegaUserAlert::TYPE_PAYMENT_FAILED, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED}, std::move(megaAlerts))); // add new reminder later than the successful payment - reminder should be kept userAlerts.add(new UserAlert::PaymentReminder(referenceTime + 3, referenceTime + 100, userAlerts.nextId())); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults(std::vector<int>{MegaUserAlert::TYPE_PAYMENT_FAILED, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED, MegaUserAlert::TYPE_PAYMENTREMINDER}, std::move(megaAlerts))); // add an old successful Payment done before the reminder - reminder should be kept userAlerts.add( new UserAlert::Payment(true, 1, referenceTime + 1, userAlerts.nextId(), name_id::psts_v2)); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(ComapreResults(std::vector<int>{MegaUserAlert::TYPE_PAYMENT_FAILED, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED, MegaUserAlert::TYPE_PAYMENTREMINDER, MegaUserAlert::TYPE_PAYMENT_SUCCEEDED}, std::move(megaAlerts))); } static bool containsAlertType(MegaUserAlertList* userList, int type) { if (!userList) { return false; } for (int i = 0; i < userList->size(); ++i) { if (userList->get(i)->getType() == type) { return true; } } return false; } static bool compareAlertLists(MegaUserAlertList* baseline, MegaUserAlertList* current) { if (!baseline || !current) { return false; } if (baseline->size() != current->size()) { return false; } for (int i = 0; i < baseline->size(); ++i) { if (baseline->get(i)->getType() != current->get(i)->getType()) { return false; } } return true; } /** * @brief SdkTest.UserAlertReminderDroppedForActivePro * * Test setup: * - Ensure login and initial user alerts catchup are complete * - Promote account to PRO and refresh account details * * Test steps: * - Capture baseline user alerts list * - Add an expired PaymentReminder * - Verify alerts list remains unchanged (reminder dropped) * - Demote to FREE and refresh account details * - Local logout and resume session * - Fetch nodes from cache/server * - Verify alerts list remains unchanged after restart */ TEST_F(SdkTest, UserAlertReminderDroppedForActivePro) { constexpr int catchupWaitMs = 3000; constexpr int proAccountLevel = 1; constexpr int freeAccountLevel = 0; constexpr auto reminderStartOffsetSec = 200; constexpr auto reminderEndOffsetSec = 100; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(WaitFor( [this]() { return megaApi[0]->getClient()->useralerts.catchupdone; }, catchupWaitMs)); MegaClient* client = megaApi[0]->getClient(); UserAlerts& userAlerts = client->useralerts; ASSERT_EQ(setAccountLevel(*megaApi[0], MegaAccountDetails::ACCOUNT_TYPE_PROI, proAccountLevel, nullptr), API_OK); ASSERT_EQ(synchronousGetSpecificAccountDetails(0, false, false, true), API_OK); unique_ptr<MegaUserAlertList> baselineAlerts{megaApi[0]->getUserAlerts()}; const auto referenceTime = time(nullptr); userAlerts.add(new UserAlert::PaymentReminder(referenceTime - reminderStartOffsetSec, referenceTime - reminderEndOffsetSec, userAlerts.nextId())); unique_ptr<MegaUserAlertList> megaAlerts{megaApi[0]->getUserAlerts()}; ASSERT_TRUE(compareAlertLists(baselineAlerts.get(), megaAlerts.get())); // switch back to Free, restart session, and verify the reminder is still absent ASSERT_EQ(setAccountLevel(*megaApi[0], MegaAccountDetails::ACCOUNT_TYPE_FREE, freeAccountLevel, nullptr), API_OK); ASSERT_EQ(synchronousGetSpecificAccountDetails(0, false, false, true), API_OK); std::unique_ptr<char[]> session(dumpSession(0)); ASSERT_NO_FATAL_FAILURE(locallogout(0)); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get(), 0)); ASSERT_NO_FATAL_FAILURE(fetchnodes(0, maxTimeout)); ASSERT_TRUE(WaitFor( [this]() { return megaApi[0]->getClient()->useralerts.catchupdone; }, catchupWaitMs)); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_TRUE(compareAlertLists(baselineAlerts.get(), megaAlerts.get())); } /** * @brief SdkTest.UserAlertReminderPurgedAfterAccountUpgrade * * Test setup: * - Ensure login and initial user alerts catchup are complete * * Test steps: * - Add an expired PaymentReminder and verify it appears * - Promote account to PRO and refresh account details * - Wait until the reminder is purged * - Force a DB commit to persist removals * - Demote to FREE and refresh account details * - Local logout and resume session * - Fetch nodes from cache/server * - Verify the reminder is still absent after restart */ TEST_F(SdkTest, UserAlertReminderPurgedAfterAccountUpgrade) { constexpr int catchupWaitMs = 3000; constexpr int purgeWaitMs = 5000; constexpr int commitWaitMs = 10000; constexpr int proAccountLevel = 1; constexpr int freeAccountLevel = 0; constexpr auto reminderStartOffsetSec = 200; constexpr auto reminderEndOffsetSec = 100; constexpr size_t uploadBytes = 1; ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); ASSERT_TRUE(WaitFor( [this]() { return megaApi[0]->getClient()->useralerts.catchupdone; }, catchupWaitMs)); MegaClient* client = megaApi[0]->getClient(); UserAlerts& userAlerts = client->useralerts; const auto referenceTime = time(nullptr); userAlerts.add(new UserAlert::PaymentReminder(referenceTime - reminderStartOffsetSec, referenceTime - reminderEndOffsetSec, userAlerts.nextId())); unique_ptr<MegaUserAlertList> megaAlerts{megaApi[0]->getUserAlerts()}; ASSERT_TRUE(containsAlertType(megaAlerts.get(), MegaUserAlert::TYPE_PAYMENTREMINDER)); ASSERT_EQ(setAccountLevel(*megaApi[0], MegaAccountDetails::ACCOUNT_TYPE_PROI, proAccountLevel, nullptr), API_OK); ASSERT_EQ(synchronousGetSpecificAccountDetails(0, false, false, true), API_OK); ASSERT_TRUE(WaitFor( [this]() { unique_ptr<MegaUserAlertList> megaAlerts{megaApi[0]->getUserAlerts()}; return !containsAlertType(megaAlerts.get(), MegaUserAlert::TYPE_PAYMENTREMINDER); }, purgeWaitMs)); // Force a DB commit so the removal is persisted PerApi& target = mApi[0]; target.resetlastEvent(); std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode()); ASSERT_NE(rootNode.get(), nullptr); const auto newNode = sdk_test::uploadFile(megaApi[0].get(), sdk_test::LocalTempFile{"alerts_purge", uploadBytes}, rootNode.get()); ASSERT_TRUE(newNode) << "Cannot create node in Cloud Drive"; ASSERT_TRUE(WaitFor( [&target]() { return target.lastEventsContain(MegaEvent::EVENT_COMMIT_DB); }, commitWaitMs)); // switch back to Free, restart session, and verify the reminder is still absent ASSERT_EQ(setAccountLevel(*megaApi[0], MegaAccountDetails::ACCOUNT_TYPE_FREE, freeAccountLevel, nullptr), API_OK); ASSERT_EQ(synchronousGetSpecificAccountDetails(0, false, false, true), API_OK); std::unique_ptr<char[]> session(dumpSession(0)); ASSERT_NO_FATAL_FAILURE(locallogout(0)); ASSERT_NO_FATAL_FAILURE(resumeSession(session.get(), 0)); ASSERT_NO_FATAL_FAILURE(fetchnodes(0, maxTimeout)); ASSERT_TRUE(WaitFor( [this]() { return megaApi[0]->getClient()->useralerts.catchupdone; }, catchupWaitMs)); megaAlerts.reset(megaApi[0]->getUserAlerts()); ASSERT_FALSE(containsAlertType(megaAlerts.get(), MegaUserAlert::TYPE_PAYMENTREMINDER)); } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/sdk_test_user_attributes.cpp������������������������������������������0000664�0000000�0000000�00000073063�15162662266�0023775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file sdk_test_user_attributes.cpp * @brief This file defines tests that involve interactions with User attributes. They include * operations like: * - Set and get attribute using generic function * - Set and get attribute using dedicated function * - Delete attribute */ #include "SdkTest_test.h" #include <gmock/gmock.h> #include <charconv> namespace { /** * @class SdkTestUserAttribute * @brief Setup an account with a certain user. * */ class SdkTestUserAttribute: public SdkTest { protected: void testStaticInformation(int at, const std::string& name, const std::string& longName) { ASSERT_GE(megaApi.size(), 1u); ASSERT_EQ(megaApi[0]->userAttributeFromString(name.c_str()), at); ASSERT_STREQ(megaApi[0]->userAttributeToString(at), name.c_str()); ASSERT_STREQ(megaApi[0]->userAttributeToLongName(at), longName.c_str()); } void testGenericSet(int at, ErrorCodes err) { ASSERT_GE(megaApi.size(), 1u); RequestTracker setAttrTracker(megaApi[0].get()); megaApi[0]->setUserAttribute(at, "", &setAttrTracker); ASSERT_EQ(setAttrTracker.waitForResult(), err) << "Unexpected result of setUserAttribute() for " << megaApi[0]->userAttributeToLongName(at); } void testGenericGet(int at, const std::vector<ErrorCodes>& results, MegaUser* user = nullptr) { RequestTracker getAttrTracker(megaApi[0].get()); megaApi[0]->getUserAttribute(user, at, &getAttrTracker); EXPECT_THAT(results, testing::Contains(getAttrTracker.waitForResult())) << "Unexpected result of getUserAttribute() for " << megaApi[0]->userAttributeToLongName(at); } template<typename T> void testValue(int at, std::function<void(RequestTracker&)> get, std::function<void(T, RequestTracker&)> set = nullptr, const std::vector<T>& alternatives = {}) { ASSERT_GE(megaApi.size(), 1u); string attributeName = megaApi[0]->userAttributeToLongName(at); T originalValue{}; bool removeAttribute = false; if (get) { // get original attribute value auto [ec, v] = testValueGetOnly<T>(at, get); ASSERT_FALSE(HasFatalFailure()); ASSERT_THAT(ec, testing::AnyOf(API_OK, API_ENOENT)); originalValue = std::move(v); // Check if private encrypted attribute was set to blank. Caller should pass the value // which represents blank value for such attributes As an example - for Camera folder // and Chat folder one can pass UNDEF as third alternative parameter. removeAttribute = (ec == API_ENOENT) || (alternatives.size() == 3 && originalValue == alternatives[2]); } ASSERT_GE(alternatives.size(), 2u); T newValue = originalValue == alternatives[0] ? alternatives[1] : alternatives[0]; // set new value to attribute { RequestTracker setAttrTracker(megaApi[0].get()); set(newValue, setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to set " << attributeName << " to new value"; if (!get) return; // nothing more to test here // confirm that it worked auto [ec, v] = testValueGetOnly<T>(at, get); ASSERT_FALSE(HasFatalFailure()); ASSERT_EQ(ec, API_OK); ASSERT_EQ(v, newValue); } // set attribute back to original value { RequestTracker setAttrTracker(megaApi[0].get()); if (removeAttribute) { megaApi[0]->deleteUserAttribute(at, &setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to deleteUserAttribute() " << attributeName; } else { set(originalValue, setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to set " << attributeName << " to original value"; // confirm that it worked auto [ec, v] = testValueGetOnly<T>(at, get); ASSERT_FALSE(HasFatalFailure()); ASSERT_EQ(ec, API_OK); ASSERT_EQ(v, originalValue); } } } template<typename T> void testRawPointer(int at, std::function<void(RequestTracker&)> get, std::function<void(T*, RequestTracker&)> set = nullptr, const std::vector<std::shared_ptr<T>>& alternatives = {}) { ASSERT_GE(megaApi.size(), 1u); string attributeName = megaApi[0]->userAttributeToLongName(at); std::unique_ptr<T> originalValue; bool removeAttribute = false; if (get) { // get original attribute value RequestTracker getAttrTracker(megaApi[0].get()); get(getAttrTracker); ErrorCodes ec = getAttrTracker.waitForResult(); if (ec == API_ENOENT) { removeAttribute = true; } else { ASSERT_EQ(API_OK, ec) << "Failed to get " << attributeName; ASSERT_NO_FATAL_FAILURE(originalValue.reset( std::get<T*>(getRelevantPointer<T*>(at, *getAttrTracker.request))->copy())); } } if (!set) return; // nothing left to do here ASSERT_GE(alternatives.size(), 2u); std::shared_ptr<T> newValue{ (originalValue && equalValues(*originalValue, *alternatives[0])) ? alternatives[1] : alternatives[0]}; // set new value to attribute { RequestTracker setAttrTracker(megaApi[0].get()); set(newValue.get(), setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to set " << attributeName << " to new value"; if (!get) return; // nothing more to test here // confirm that it worked RequestTracker getAttrTracker(megaApi[0].get()); get(getAttrTracker); ASSERT_EQ(API_OK, getAttrTracker.waitForResult()); T* v{}; ASSERT_NO_FATAL_FAILURE( v = std::get<T*>(getRelevantPointer<T*>(at, *getAttrTracker.request))); ASSERT_TRUE(equalValues(*v, *newValue)); } // set attribute back to original value { RequestTracker setAttrTracker(megaApi[0].get()); if (removeAttribute) { megaApi[0]->deleteUserAttribute(at, &setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to deleteUserAttribute() " << attributeName; } else { set(originalValue.get(), setAttrTracker); ASSERT_EQ(API_OK, setAttrTracker.waitForResult()) << "Failed to set " << attributeName << " to original value"; // confirm that it worked RequestTracker getAttrTracker(megaApi[0].get()); get(getAttrTracker); ASSERT_EQ(API_OK, getAttrTracker.waitForResult()); T* v{}; ASSERT_NO_FATAL_FAILURE( v = std::get<T*>(getRelevantPointer<T*>(at, *getAttrTracker.request))); ASSERT_TRUE(equalValues(*v, *originalValue)); } } } private: template<typename T> std::tuple<ErrorCodes, T> testValueGetOnly(int at, std::function<void(RequestTracker&)> get) { RequestTracker getAttrTracker(megaApi[0].get()); get(getAttrTracker); ErrorCodes ec = getAttrTracker.waitForResult(); T originalValue = ec == API_OK ? getRelevantValue<T>(at, *getAttrTracker.request) : T{}; return {ec, originalValue}; } template<typename T> T getNumericValueFromText(const std::string& text) const { if constexpr (std::is_same_v<T, bool>) { EXPECT_EQ(text.size(), 1); return text.size() == 1 && text[0] == '1'; } else { T v{}; auto [ptr, error] = std::from_chars(text.data(), text.data() + text.size(), v); EXPECT_EQ(error, std::errc{}); return v; } } template<typename T> T getRelevantValue(int at, const MegaRequest& request) const { bool testFlag = false; switch (at) { case MegaApi::USER_ATTR_RUBBISH_TIME: case MegaApi::USER_ATTR_STORAGE_STATE: return static_cast<T>(request.getNumber()); case MegaApi::USER_ATTR_COOKIE_SETTINGS: return static_cast<T>(request.getNumDetails()); case MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER: case MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER: return static_cast<T>(request.getNodeHandle()); case MegaApi::USER_ATTR_LAST_PSA: { EXPECT_THAT(request.getText(), testing::NotNull()); T v{}; if (request.getText()) { std::string textB64{request.getText()}; std::string text{Base64::atob(textB64)}; v = getNumericValueFromText<T>(text); } return v; } case MegaApi::USER_ATTR_DISABLE_VERSIONS: case MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION: case MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG: case MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE: testFlag = true; [[fallthrough]]; default: { EXPECT_THAT(request.getText(), testing::NotNull()); T v = request.getText() == nullptr ? T{} : getNumericValueFromText<T>(request.getText()); if (testFlag) { EXPECT_EQ(v, request.getFlag()); } return v; } } } template<typename T> std::variant<MegaStringMap*, MegaPushNotificationSettings*> getRelevantPointer(int at, const MegaRequest& request) const { switch (at) { case MegaApi::USER_ATTR_PUSH_SETTINGS: return const_cast<MegaPushNotificationSettings*>( request.getMegaPushNotificationSettings()); default: return request.getMegaStringMap(); } } static bool equalValues(const MegaPushNotificationSettings& first, const MegaPushNotificationSettings& second) { return static_cast<const MegaPushNotificationSettingsPrivate&>(first) == static_cast<const MegaPushNotificationSettingsPrivate&>(second); } static bool equalValues(const MegaStringMap& first, const MegaStringMap& second) { return *static_cast<const MegaStringMapPrivate&>(first).getMap() == *static_cast<const MegaStringMapPrivate&>(second).getMap(); } }; template<> std::string SdkTestUserAttribute::getRelevantValue<std::string>([[maybe_unused]] int at, const MegaRequest& request) const { EXPECT_THAT(request.getText(), testing::NotNull()); return request.getText() ? request.getText() : ""; } /** * @brief SdkTestUserAttribute.NoAccess */ TEST_F(SdkTestUserAttribute, NoAccess) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_AUTHRING; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*!authring", "AUTHRING")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_ED25519_PUBLIC_KEY; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "+puEd255", "ED25519_PUBK")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_CU25519_PUBLIC_KEY; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "+puCu255", "CU25519_PUBK")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_KEYRING; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*keyring", "KEYRING")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_SIG_RSA_PUBLIC_KEY; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "+sigPubk", "SIG_RSA_PUBK")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_SIG_CU255_PUBLIC_KEY; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "+sigCu255", "SIG_CU255_PUBK")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = 29; // ATTR_AUTHCU255 (deprecated) ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*!authCu255", "AUTHCU255")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_EARGS, API_ENOENT})); at = MegaApi::USER_ATTR_MY_BACKUPS_FOLDER; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!bak", "MY_BACKUPS_FOLDER")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = MegaApi::USER_ATTR_JSON_SYNC_CONFIG_DATA; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*~jscd", "JSON_SYNC_CONFIG_DATA")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EARGS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); at = 37; // ATTR_KEYS ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!keys", "KEYS")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EACCESS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); } /** * @brief SdkTestUserAttribute.Lastname */ TEST_F(SdkTestUserAttribute, Lastname) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(2)); int at = MegaApi::USER_ATTR_LASTNAME; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "lastname", "LASTNAME")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<std::string>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](std::string newValue, RequestTracker& tracker) { api->setUserAttribute(at, newValue.c_str(), &tracker); }, {"LastName 1", "LastName 2"})); // test generic getUserAttribute for other user ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT}, megaApi[1]->getMyUser())); } /** * @brief SdkTestUserAttribute.PasswordReminder */ TEST_F(SdkTestUserAttribute, PasswordReminder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_PWD_REMINDER; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!prd", "PWD_REMINDER")); ASSERT_NO_FATAL_FAILURE(testGenericSet(at, API_EARGS)); ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); } /** * @brief SdkTestUserAttribute.DisableVersions */ TEST_F(SdkTestUserAttribute, DisableVersions) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_DISABLE_VERSIONS; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!dv", "DISABLE_VERSIONS")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](bool newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {true, false})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getFileVersionsOption(&tracker); }, [api = megaApi[0].get()](bool newValue, RequestTracker& tracker) { api->setFileVersionsOption(newValue, &tracker); }, {true, false})); } /** * @brief SdkTestUserAttribute.ContactLinkVerification */ TEST_F(SdkTestUserAttribute, ContactLinkVerification) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_CONTACT_LINK_VERIFICATION; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^clv", "CONTACT_LINK_VERIFICATION")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](bool newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {true, false})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getContactLinksOption(&tracker); }, [api = megaApi[0].get()](bool newValue, RequestTracker& tracker) { api->setContactLinksOption(newValue, &tracker); }, {true, false})); } /** * @brief SdkTestUserAttribute.VisibleWelcomeDialog */ TEST_F(SdkTestUserAttribute, VisibleWelcomeDialog) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_VISIBLE_WELCOME_DIALOG; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!weldlg", "VISIBLE_WELCOME_DIALOG")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](bool newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {true, false})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getVisibleWelcomeDialog(&tracker); }, [api = megaApi[0].get()](bool newValue, RequestTracker& tracker) { api->setVisibleWelcomeDialog(newValue, &tracker); }, {true, false})); } /** * @brief SdkTestUserAttribute.VisibleTermsOfService */ TEST_F(SdkTestUserAttribute, VisibleTermsOfService) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_VISIBLE_TERMS_OF_SERVICE; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!tos", "VISIBLE_TERMS_OF_SERVICE")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](int newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {true, false})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<bool>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getVisibleTermsOfService(&tracker); }, [api = megaApi[0].get()](bool newValue, RequestTracker& tracker) { api->setVisibleTermsOfService(newValue, &tracker); }, {true, false})); } /** * @brief SdkTestUserAttribute.CookieSettings */ TEST_F(SdkTestUserAttribute, CookieSettings) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_COOKIE_SETTINGS; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!csp", "COOKIE_SETTINGS")); // test generic getUserAttribute (cannot be set using setUserAttribute) ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getCookieSettings(&tracker); }, [api = megaApi[0].get()](int newValue, RequestTracker& tracker) { api->setCookieSettings(newValue, &tracker); }, {1, 0})); } /** * @brief SdkTestUserAttribute.NoCallKit */ TEST_F(SdkTestUserAttribute, NoCallKit) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_NO_CALLKIT; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!nokit", "NO_CALLKIT")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](int newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {1, 0})); } /** * @brief SdkTestUserAttribute.RubbishTime */ TEST_F(SdkTestUserAttribute, RubbishTime) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_RUBBISH_TIME; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!rubbishtime", "RUBBISH_TIME")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](int newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {1, 2})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getRubbishBinAutopurgePeriod(&tracker); }, [api = megaApi[0].get()](int newValue, RequestTracker& tracker) { api->setRubbishBinAutopurgePeriod(newValue, &tracker); }, {1, 2})); } /** * @brief SdkTestUserAttribute.LastPSA */ TEST_F(SdkTestUserAttribute, LastPSA) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_LAST_PSA; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!lastPsa", "LAST_PSA")); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](int newValue, RequestTracker& tracker) { std::string v{std::to_string(newValue)}; api->setUserAttribute(at, v.c_str(), &tracker); }, {1, 2})); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<int>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { // has no dedicated interface for getting it api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get()](int newValue, RequestTracker& tracker) { api->setPSA(newValue, &tracker); }, {1, 0})); } /** * @brief SdkTestUserAttribute.StorageState */ TEST_F(SdkTestUserAttribute, StorageState) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_STORAGE_STATE; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!usl", "STORAGE_STATE")); // test generic getUserAttribute (cannot be set using setUserAttribute) ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); } /** * @brief SdkTestUserAttribute.CameraUploadsFolder */ TEST_F(SdkTestUserAttribute, CameraUploadsFolder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_CAMERA_UPLOADS_FOLDER; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*!cam", "CAMERA_UPLOADS_FOLDER")); // test generic getUserAttribute (cannot be set using setUserAttribute) ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); // create 2 folders std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()}; RequestTracker folderTracker1(megaApi[0].get()); megaApi[0]->createFolder("TestCameraFolder1", rootnode.get(), &folderTracker1); RequestTracker folderTracker2(megaApi[0].get()); megaApi[0]->createFolder("TestCameraFolder2", rootnode.get(), &folderTracker2); ASSERT_EQ(folderTracker1.waitForResult(), API_OK); ASSERT_EQ(folderTracker2.waitForResult(), API_OK); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<MegaHandle>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getCameraUploadsFolder(&tracker); }, [api = megaApi[0].get()](MegaHandle newValue, RequestTracker& tracker) { api->setCameraUploadsFolder(newValue, &tracker); }, {folderTracker1.getNodeHandle(), folderTracker2.getNodeHandle(), UNDEF})); } /** * @brief SdkTestUserAttribute.MyChatFilesFolder */ TEST_F(SdkTestUserAttribute, MyChatFilesFolder) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_MY_CHAT_FILES_FOLDER; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*!cf", "MY_CHAT_FILES_FOLDER")); // test generic getUserAttribute (cannot be set using setUserAttribute) ASSERT_NO_FATAL_FAILURE(testGenericGet(at, {API_OK, API_ENOENT})); // create 2 folders std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()}; RequestTracker folderTracker1(megaApi[0].get()); megaApi[0]->createFolder("TestChatFilesFolder1", rootnode.get(), &folderTracker1); RequestTracker folderTracker2(megaApi[0].get()); megaApi[0]->createFolder("TestChatFilesFolder2", rootnode.get(), &folderTracker2); ASSERT_EQ(folderTracker1.waitForResult(), API_OK); ASSERT_EQ(folderTracker2.waitForResult(), API_OK); // test dedicated interfaces ASSERT_NO_FATAL_FAILURE(testValue<MegaHandle>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getMyChatFilesFolder(&tracker); }, [api = megaApi[0].get()](MegaHandle newValue, RequestTracker& tracker) { api->setMyChatFilesFolder(newValue, &tracker); }, {folderTracker1.getNodeHandle(), folderTracker2.getNodeHandle(), UNDEF})); } /** * @brief SdkTestUserAttribute.LastInteraction */ TEST_F(SdkTestUserAttribute, LastInteraction) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_LAST_INTERACTION; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "*!lstint", "LAST_INT")); std::string v1{"0:1710410495"}; std::unique_ptr<char[]> v1b64(megaApi[0]->binaryToBase64(v1.c_str(), v1.size())); std::shared_ptr<MegaStringMap> alternative1{MegaStringMap::createInstance()}; alternative1->set("BODjmzqzD3g", v1b64.get()); std::string v2{"0:1710410496"}; std::unique_ptr<char[]> v2b64(megaApi[0]->binaryToBase64(v2.c_str(), v2.size())); std::shared_ptr<MegaStringMap> alternative2{MegaStringMap::createInstance()}; alternative2->set("BODjmzqzD3g", v2b64.get()); // test generic interfaces ASSERT_NO_FATAL_FAILURE(testRawPointer<MegaStringMap>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); }, [api = megaApi[0].get(), at](MegaStringMap* newValue, RequestTracker& tracker) { api->setUserAttribute(at, newValue, &tracker); }, {alternative1, alternative2})); } /** * @brief SdkTestUserAttribute.PushSettings */ TEST_F(SdkTestUserAttribute, PushSettings) { ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1)); int at = MegaApi::USER_ATTR_PUSH_SETTINGS; ASSERT_NO_FATAL_FAILURE(testStaticInformation(at, "^!ps", "PUSH_SETTINGS")); // test generic getUserAttribute (cannot be set using setUserAttribute) ASSERT_NO_FATAL_FAILURE(testRawPointer<MegaPushNotificationSettings>( at, [api = megaApi[0].get(), at](RequestTracker& tracker) { api->getUserAttribute(at, &tracker); })); // test dedicated interfaces std::shared_ptr<MegaPushNotificationSettings> alternative1{ MegaPushNotificationSettings::createInstance()}; std::shared_ptr<MegaPushNotificationSettings> alternative2{ MegaPushNotificationSettings::createInstance()}; alternative2->enableContacts(!alternative1->isContactsEnabled()); ASSERT_NO_FATAL_FAILURE(testRawPointer<MegaPushNotificationSettings>( at, [api = megaApi[0].get()](RequestTracker& tracker) { api->getPushNotificationSettings(&tracker); }, [api = megaApi[0].get()](MegaPushNotificationSettings* newValue, RequestTracker& tracker) { api->setPushNotificationSettings(newValue, &tracker); }, {alternative1, alternative2})); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/test-data/������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020022�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/test-data/email_processor.py������������������������������������������0000664�0000000�0000000�00000011012�15162662266�0023555�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ # Extract a link from an email. # Used by sdk/test/integration test SdkTest.SdkTestCreateAccount import time SCRIPT_EXECUTION_TIME = time.time() import email import imaplib import os import sys class EmailProcessor: debug = False def __init__(self, user, password, host='mail.mega.co.nz', port=993): self.mail = imaplib.IMAP4_SSL(host, port) self.mail.login(user, password) self.mail.select('Inbox') def get_validation_link_from_email(self, to, intent, ref_time=None, delta=360.): """Get validation link from email.""" link = None if ref_time is None: ref_time = time.time() messages = self.get_message_from_email(to, ref_time, delta) if not messages: return None for message in messages: sub = list(message)[0] text = message[sub] if not text[1]: continue for line in text[1].splitlines(): if self.debug: if line.startswith('https://'): print("line:" + line) if line.startswith('https://') and ('#' + intent) in line: link = line.strip() break self.mail.close() self.mail.logout() return link def get_message_from_email(self, to, ref_time, delta=360.): """Get message from email.""" # sort all emails sent to the right address, newest first result, emails = self.mail.sort('REVERSE DATE', 'UTF-8', '(To "{}")'.format(to)) if len(emails[0]) == 0: return None # get all potential emails messages = [] for emailID in emails[0].split(): message = self.process_email(ref_time, emailID, delta) if message: messages.append(message) return messages def process_email(self, ref_time, emailID, delta=360.): """Process each piece of an email to find the body and subject""" # Get email data from the ID. _, data = self.mail.fetch(emailID, "(RFC822)") for part in data: # Look for a tuple. It will contain the email body. We can ignore the rest. # It is usually in the first "part", but not always. if isinstance(part, tuple): # The body is the second in the tuple msg = email.message_from_bytes(part[1]) # Process the subject subject, encoding = email.header.decode_header(msg['subject'])[0] if isinstance(subject, bytes): subject = subject.decode(encoding) if self.debug: print("Subject: " + subject) # Discard if it is outdated dt = 0 for item in msg['DKIM-Signature'].split(';'): if 't=' in item: dt = item.strip()[2:] break else: assert dt, 'timestamp not found from email header' elapsed = ref_time - float(dt) if elapsed > delta: # Emails are sorted by time so no need to continue processing break # Get the plain text from the body text = None for m in msg.walk(): content_type = m.get_content_type() if content_type == 'text/plain': text = m.get_payload(decode=True).decode('raw-unicode-escape') break if subject and text: return {subject: [emailID, text]} return None if len(sys.argv) == 1 or "--help" in sys.argv[1:] or (os.name == "nt" and "/?" in sys.argv[1:]): # no args, --help or /? on windows print("usage: " + sys.argv[0] + " email-user email-password to-email-address {confirm|cancel|recover|<other>} max-age-in-seconds") print("") print("e.g. python email_processor.py sdk-jenkins a-password sdk-jenkins+test-e-1@mega.co.nz recover 36000") print("$TEST_PASS can override email-password, but password placholder still requried on command line") exit(0); user = os.getenv('TEST_USER') or sys.argv[1] password = os.getenv('TEST_PASS') or sys.argv[2] to = sys.argv[3] intent = sys.argv[4] if intent == "delete": # backwards compatible intent = "cancel" delta = float(sys.argv[5]) ep = EmailProcessor(user, password) link = ep.get_validation_link_from_email(to, intent, SCRIPT_EXECUTION_TIME, delta) if link: print(link, end = '') ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/integration/test.h����������������������������������������������������������������0000664�0000000�0000000�00000150706�15162662266�0017275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ #ifndef TEST_H #define TEST_H 1 #include <chrono> #include <sstream> #include <string> #include <thread> #include <vector> #include <functional> #include <stdio.h> #include <map> #include <future> #include <fstream> #include <atomic> #include <random> #include "gtest/gtest.h" #include <mega.h> #include <megaapi_impl.h> #include "../stdfs.h" using namespace ::mega; using namespace ::std; std::string logTime(); void WaitMillisec(unsigned n); enum class PROG_OUTPUT_TYPE { TEXT, // skip \n and concatenate lines; uses fgets() BINARY // read everything just as it was received; uses fread() }; string runProgram(const string& command, PROG_OUTPUT_TYPE ot); // platform specific Http POST void synchronousHttpPOSTFile(const string& url, const string& filepath, string& responsedata); void synchronousHttpPOSTData(const string& url, const string& senddata, string& responsedata); class LogStream { public: LogStream() : mBuffer() { } LogStream(LogStream&& other) noexcept : mBuffer(std::move(other.mBuffer)) { } ~LogStream(); template<typename T> LogStream& operator<<(const T* value) { mBuffer << value; return *this; } template<typename T, typename = typename std::enable_if<std::is_scalar<T>::value>::type> LogStream& operator<<(const T value) { mBuffer << value; return *this; } template<typename T, typename = typename std::enable_if<!std::is_scalar<T>::value>::type> LogStream& operator<<(const T& value) { mBuffer << value; return *this; } private: std::ostringstream mBuffer; }; // LogStream extern std::string USER_AGENT; extern bool gResumeSessions; extern bool gScanOnly; extern bool gManualVerification; extern bool gFreeAccounts; LogStream out(); enum { THREADS_PER_MEGACLIENT = 3 }; class TestFS { public: // these getters should return std::filesystem::path type, when C++17 will become mandatory // $WORKSPACE or hard coded path // ie. /home/<user>/mega_tests static fs::path GetBaseFolder(); // PID specific directory static fs::path GetProcessFolder(); // directory for "test" within the process folder, often created and deleted per test static fs::path GetTestFolder(); static fs::path GetTrashFolder(); void DeleteTestFolder() { DeleteFolder(GetTestFolder()); } void DeleteTrashFolder() { DeleteFolder(GetTrashFolder()); } static void ChangeToProcessFolder(); static void ClearProcessFolder(); ~TestFS(); private: void DeleteFolder(fs::path folder); std::vector<std::thread> m_cleaners; }; void moveToTrash(const fs::path& p); fs::path makeNewTestRoot(); std::unique_ptr<::mega::FileSystemAccess> makeFsAccess(); fs::path makeReusableClientFolder(const string& subfolder); class RequestRetryRecorder { // Convenience. using Milliseconds = std::chrono::milliseconds; // Describes a particular class of retry. struct RetryEntry { // How many times did this class of retry occur? std::size_t mCount = 0; // What was the longest time we spent performing this retry? Milliseconds mLongest = Milliseconds::min(); // And the shortest time? Milliseconds mShortest = Milliseconds::max(); }; // Entry // Maps retry class to retry entry. using RetryEntryMap = std::map<retryreason_t, RetryEntry>; // Translates a retry entry into a human-readable string. std::string report(const RetryEntryMap::value_type& entry) const { std::ostringstream ostream; ostream << "Requests retried due to " << toString(entry.first) << " " << entry.second.mCount << " time(s) [duration " << entry.second.mShortest.count() << "ms-" << entry.second.mLongest.count() << "ms]"; return ostream.str(); } // Tracks statistics about a specific retry class. RetryEntryMap mEntries; // Serializes access to mEnties. mutable std::mutex mEntriesLock; // Who's the current recorder? static RequestRetryRecorder* mInstance; public: RequestRetryRecorder() : mEntries() , mEntriesLock() { // Only one instance should ever exist at a time. assert(!mInstance); mInstance = this; } RequestRetryRecorder(const RequestRetryRecorder&) = delete; ~RequestRetryRecorder() { assert(mInstance == this); mInstance = nullptr; } RequestRetryRecorder& operator=(const RequestRetryRecorder&) = delete; // Obtain a reference to the current recorder. static RequestRetryRecorder& instance() { assert(mInstance); return *mInstance; } // Record a retry period. void record(retryreason_t reason, Milliseconds duration) { // Acquire lock. std::lock_guard<std::mutex> guard(mEntriesLock); // Get our hands on the specified entry. auto& entry = mEntries[reason]; // Populate entry. entry.mCount = entry.mCount + 1; entry.mLongest = std::max(entry.mLongest, duration); entry.mShortest = std::min(entry.mShortest, duration); } // Transform recorded retry entries to a human-readable string. template<typename Printer> void report(Printer&& printer) const { // Acquire lock. std::lock_guard<std::mutex> guard(mEntriesLock); // Print entries. for (auto& i : mEntries) printer(report(i)); } void reset() { // Acquire lock. std::lock_guard<std::mutex> guard(mEntriesLock); // Clear recorded request retries. mEntries.clear(); } }; // RequestRetryRecorder class RequestRetryTracker { // Convenience. using HRClock = std::chrono::high_resolution_clock; using HRTimePoint = HRClock::time_point; // Why did our request need to be retried? retryreason_t mReason = RETRY_NONE; // When were we notified that the request was retried? HRTimePoint mWhen = HRTimePoint::max(); public: // Signal that a request is being retried. void track(const std::string& clientName, retryreason_t reason) { // Coalesce contiguous retries of the same class. if (mReason == reason) return; // Convenience. auto now = HRClock::now(); // We were already tracking an existing retry. if (mReason != RETRY_NONE) { // Convenience. using std::chrono::duration_cast; using std::chrono::milliseconds; // How long did it take until our request succeeded? auto elapsed = duration_cast<milliseconds>(now - mWhen); // Log how long the request took. out() << clientName << ": request retry completed: reason: " << toString(mReason) << ", duration: " << elapsed.count() << "ms"; // Record statistics about the retry. RequestRetryRecorder::instance().record(mReason, elapsed); } // Latch new reason and timestamp. mReason = reason; mWhen = now; // No request is being retried. if (mReason == RETRY_NONE) return; out() << clientName << ": request retry begun: reason: " << toString(mReason); } }; // RequestRetryTracker #ifdef ENABLE_SYNC template<typename T> using shared_promise = std::shared_ptr<promise<T>>; using PromiseBoolSP = shared_promise<bool>; using PromiseErrorSP = shared_promise<Error>; using PromiseHandleSP = shared_promise<handle>; using PromiseStringSP = shared_promise<string>; using PromiseUnsignedSP = shared_promise<unsigned>; using PromiseVoidSP = shared_promise<void>; struct Model { // records what we think the tree should look like after sync so we can confirm it struct ModelNode { enum nodetype { file, folder }; nodetype type = folder; string mCloudName; string mFsName; string name; string content; vector<unique_ptr<ModelNode>> kids; ModelNode* parent = nullptr; bool changed = false; bool fsOnly = false; ModelNode() = default; ModelNode(const ModelNode& other); ModelNode& fsName(const string& name); const string& fsName() const; ModelNode& cloudName(const string& name); const string& cloudName() const; void generate(const fs::path& path, bool force); string path() const; string fsPath() const; ModelNode* addkid(); ModelNode* addkid(unique_ptr<ModelNode>&& p); bool typematchesnodetype(nodetype_t nodetype) const; void print(string prefix=""); std::unique_ptr<ModelNode> clone(); }; Model(); Model(const Model& other); Model& operator=(const Model& rhs); ModelNode* addfile(const string& path, const string& content); ModelNode* addfile(const string& path); ModelNode* addfolder(const string& path); ModelNode* addnode(const string& path, ModelNode::nodetype type); ModelNode* copynode(const string& src, const string& dst); /** * @brief Create a folder-type ModelNode with the specified name. * * @param utf8Name The name of the folder to be created, in UTF-8 encoding * @return The created folder ModelNode */ unique_ptr<ModelNode> makeModelSubfolder(const string& utf8Name); /** * @brief Create a file type ModelNode with the specified name. * If content parameter is empty, the content is the specified name, * otherwise it is content itself. * * @param utf8Name The name of the folder to be created, in UTF-8 encoding. * @param content The content of the file. If it is empty, the content is file name utf8Name. * Default value is empty. * @return The created file ModelNode */ unique_ptr<ModelNode> makeModelSubfile(const string& utf8Name, string content = {}); /** * @brief Create a folder tree whose depth is with a specified depth and its root folder is named prefix. * * The structure is as follows: * - Each folder, except for the leaf folders, has `n` child folders. Child folders are named after * their parent name with the format <parentName>_<index>, starting with index 0. * - Each folder has `filesperdir` number of child files. Child files are named after their parent name * with the format file<index>_<parentName>. * * Examples: * buildModelSubdirs(f, 2, 2, 1) * root -- level 1 -- level 2 * f -- file0_f * -- f_0 -- file0_f_0 * -- f_0_0 -- file0_f_0_0 * -- f_0_1 -- file0_f_0_1 * -- f_1 -- file0_f_1 * -- f_1_0 -- file0_f_1_0 * -- f_1_1 -- file0_f_1_1 * * @param prefix The name of the root folder * @param n The number of child folders under each folder except the leaf folder. * @param recurselevel The depth of folder tree. For example, 0 means only root folder. * 1 means one level folder under the root. * @param filesperdir The number of files under each folder * @return the root folder ModelNode */ unique_ptr<ModelNode> buildModelSubdirs(const string& prefix, int n, int recurselevel, int filesperdir); ModelNode* childnodebyname(ModelNode* n, const std::string& s); ModelNode* findnode(string path, ModelNode* startnode = nullptr); unique_ptr<ModelNode> removenode(const string& path); bool movenode(const string& sourcepath, const string& destpath); bool movetosynctrash(unique_ptr<ModelNode>&& node, const string& syncrootpath); bool movetosynctrash(const string& path, const string& syncrootpath); void ensureLocalDebrisTmpLock(const string& syncrootpath); bool removesynctrash(const string& syncrootpath, const string& subpath = ""); void emulate_rename(std::string nodepath, std::string newname); void emulate_move(std::string nodepath, std::string newparentpath); void emulate_copy(std::string nodepath, std::string newparentpath); void emulate_rename_copy(std::string nodepath, std::string newparentpath, std::string newname); void emulate_delete(std::string nodepath); void generate(const fs::path& path, bool force = false); void swap(Model& other); unique_ptr<ModelNode> root; }; struct StandardClient; class CloudItem { public: CloudItem(const Node* node); CloudItem(const Node& node); CloudItem(const string& path, bool fromRoot = false); CloudItem(const char* path, bool fromRoot = false); CloudItem(const NodeHandle& nodeHandle); CloudItem(handle nodeHandle); std::shared_ptr<Node> resolve(StandardClient& client) const; private: NodeHandle mNodeHandle; string mPath; bool mFromRoot = false; }; // CloudItem struct SyncOptions { string drivePath = string(1, '\0'); string excludePath; string logName; bool legacyExclusionsEligible = false; bool isBackup = false; bool uploadIgnoreFile = false; }; // SyncOptions struct SyncStallInfoTests { SyncStallInfo::CloudStallInfoMap cloud; SyncStallInfo::LocalStallInfoMap local; void extractFrom(SyncStallInfo& stallInfo) { for (auto& syncStallInfoMapPair : stallInfo.syncStallInfoMaps) { auto& syncStallInfoMap = syncStallInfoMapPair.second; for (auto& stallEntry: syncStallInfoMap.cloud) cloud.insert(stallEntry); for (auto& stallEntry: syncStallInfoMap.local) local.insert(stallEntry); } } void clear() { cloud.clear(); local.clear(); } bool empty() const { return cloud.empty() && local.empty(); } }; // SyncStallInfoTests class StandardSyncController : public SyncController { using Callback = std::function<bool(const fs::path&)>; bool call(const Callback& callback, const LocalPath& path) const; void set(Callback& callback, Callback value); Callback mDeferPutnode; Callback mDeferPutnodeCompletion; Callback mDeferUpload; mutable std::mutex mLock; public: StandardSyncController() = default; bool deferPutnode(const LocalPath& path) const override; bool deferPutnodeCompletion(const LocalPath& path) const override; bool deferUpload(const LocalPath& path) const override; void setDeferPutnodeCallback(Callback callback); void setDeferPutnodeCompletionCallback(Callback callback); void setDeferUploadCallback(Callback callback); }; // StandardSyncController // Convenience. template<typename T> class SynchronizedFunction; template<typename R, typename... P> class SynchronizedFunction<R(P...)> { std::function<R(P...)> mFunction; mutable std::mutex mLock; public: SynchronizedFunction(std::function<R(P...)> function = nullptr) : mFunction(std::move(function)) { } operator bool() const { return operator!=(nullptr); } R operator()(P... arguments) { std::function<R(P...)> function; { std::lock_guard guard(mLock); function = mFunction; } function(arguments...); } SynchronizedFunction& operator=(const SynchronizedFunction& rhs) { if (this == &rhs) return *this; std::unique_lock l(mLock, std::defer_lock); std::unique_lock r(mLock, std::defer_lock); std::lock(l, r); mFunction = rhs.mFunction; return *this; } SynchronizedFunction& operator=(std::function<R(P...)> rhs) { std::lock_guard guard(mLock); mFunction = std::move(rhs); return *this; } SynchronizedFunction& operator=(std::nullptr_t) { std::lock_guard guard(mLock); mFunction = nullptr; return *this; } bool operator==(std::nullptr_t) const { std::lock_guard guard(mLock); return mFunction == nullptr; } bool operator!=(std::nullptr_t) const { return !(*this == nullptr); } }; // SynchronizedFunction<(R, P...)> struct StandardClient : public MegaApp { shared_ptr<WAIT_CLASS> waiter; #ifdef GFX_CLASS GfxProc gfx; #endif string client_dbaccess_path; std::unique_ptr<HttpIO> httpio; mutable std::recursive_mutex clientMutex; MegaClient client; std::atomic<bool> clientthreadexit{false}; bool fatalerror = false; string clientname; std::function<void()> nextfunctionMC; std::function<void()> nextfunctionSC; string nextfunctionMC_sourcefile, nextfunctionSC_sourcefile; int nextfunctionMC_sourceline = -1, nextfunctionSC_sourceline = -1; std::condition_variable functionDone; std::mutex functionDoneMutex; std::string salt; std::set<fs::path> localFSFilesThatMayDiffer; fs::path fsBasePath; handle basefolderhandle = UNDEF; enum resultprocenum { PRELOGIN, LOGIN, FETCHNODES, PUTNODES, UNLINK, CATCHUP, COMPLETION }; // use COMPLETION when we use a completion function, rather than trying to match tags on callbacks struct ResultProc { StandardClient& client; ResultProc(StandardClient& c) : client(c) {} struct id_callback { int request_tag = 0; handle h = UNDEF; std::function<bool(error)> f; id_callback(std::function<bool(error)> cf, int tag, handle ch) : request_tag(tag), h(ch), f(cf) {} id_callback(id_callback&&) = default; }; recursive_mutex mtx; // recursive because sometimes we need to set up new operations during a completion callback map<resultprocenum, map<int, id_callback>> m; // f is to return true if no more callbacks are expected, and the expected-entry will be removed void prepresult(resultprocenum rpe, int tag, std::function<void()>&& requestfunc, std::function<bool(error)>&& f, handle h = UNDEF); void processresult(resultprocenum rpe, error e, handle h, int tag); } resultproc; // thread as last member so everything else is initialised before we start it std::thread clientthread; string ensureDir(const fs::path& p); /** * @brief Sets the minimum permissible configurable values for upload throttling. * * Default upload throttling values can be unsuitable for tests. * This method sets them as follows: * throttleUpdateRate -> to the lowest value allowed (current: 1min) * maxUploadsBeforeThrottle -> to the maximum value allowed (current: 5) * * If any of the operations fails, the result is logged with a warn, but there are no asserts. * The reasons are: 1) This method is meant to be called within the StandardClient constructor, * and we avoid propagating ASSERT_NO_FATAL_FAILURE all along the code. 2) The specific code for * setting these values for the UploadThrottlingManager is already exercised in its own test * suite. 3) Just a few tests will fall into the upload throttling logic, so for most test it * doesn't really matter if this method fails for some very unexpected reason. */ void setMinimumUploadThrottleSettings(); StandardClient(const fs::path& basepath, const string& name, const fs::path& workingFolder = fs::path()); ~StandardClient(); void localLogout(); bool logout(bool keepSyncsConfigFile); static mutex om; bool logcb = false; chrono::steady_clock::time_point lastcb = std::chrono::steady_clock::now(); string lp(LocalNode* ln); void onCallback(); SynchronizedFunction<void(const SyncConfig&)> onAutoResumeResult; SynchronizedFunction<void(const SyncConfig&)> onRemovedSync; void sync_added(const SyncConfig& config) override; void sync_removed(const SyncConfig& config) override; std::atomic<bool> received_syncs_restored{false}; void syncs_restored(SyncError syncError) override; std::atomic<bool> received_node_actionpackets{false}; std::condition_variable nodes_updated_cv; void nodes_updated(sharedNode_vector* nodes, int numNodes) override; bool waitForNodesUpdated(unsigned numSeconds); void syncupdate_stateconfig(const SyncConfig& config) override; void syncupdate_treestate(const SyncConfig& config, const LocalPath& localPath, treestate_t treeState, nodetype_t type) override; std::atomic<bool> received_user_alerts{false}; std::condition_variable user_alerts_updated_cv; void useralerts_updated(UserAlert::Base**, int) override; bool waitForUserAlertsUpdated(unsigned numSeconds); std::atomic<bool> received_user_actionpackets{false}; std::mutex user_actionpackets_mutex; std::condition_variable user_updated_cv; void users_updated(User**users, int size) override; // If none lambda is register with createsOnUserUpdateLamda, any user action package generates an event for stop waiting period. // If a lambda is register, waiting period only finished if lambda returns true when it is called // Once waiting period is finised, removeOnUserUpdateLamda should be called bool waitForUserUpdated(unsigned numSeconds); std::mutex mUserActionPackageMutex; std::function<bool(User*)> mCheckUserChange; void createsOnUserUpdateLamda(std::function<bool(User*)> onUserUpdateLambda); // Should be called to remove registered lamda void removeOnUserUpdateLamda(); SynchronizedFunction<void(const SyncConfig&)> mOnSyncStateConfig; SynchronizedFunction<void(const SyncConfig&, const LocalPath&, treestate_t, nodetype_t)> mOnSyncTreeState; void syncupdate_scanning(bool b) override; std::atomic<bool> mStallDetected{false}; std::atomic<bool> mConflictsDetected{false}; std::atomic<bool> mTotalStallsUpdated{false}; std::atomic<bool> mTotalConflictsUpdated{false}; void syncupdate_conflicts(bool state) override; void syncupdate_stalled(bool state) override; void syncupdate_totalconflicts(bool state) override; void syncupdate_totalstalls(bool state) override; void file_added(File* file) override; void file_complete(File* file) override; std::atomic<unsigned> transfersAdded{0}, transfersRemoved{0}, transfersPrepared{0}, transfersFailed{0}, transfersUpdated{0}, transfersComplete{0}; void transfer_added(Transfer* transfer) override { onCallback(); ++transfersAdded; if (mOnTransferAdded) mOnTransferAdded(*transfer); } SynchronizedFunction<void(Transfer&)> mOnTransferAdded; void transfer_removed(Transfer*) override { onCallback(); ++transfersRemoved; } void transfer_prepare(Transfer*) override { onCallback(); ++transfersPrepared; } void transfer_failed(Transfer*, const Error&, dstime = 0) override { onCallback(); ++transfersFailed; } void transfer_update(Transfer*) override { onCallback(); ++transfersUpdated; } SynchronizedFunction<void(Transfer*)> onTransferCompleted; bool waitForAttrMyBackupIsSet(unsigned numSeconds, bool& newBackupIsSet); bool isUserAttributeSet(attr_t attr, unsigned numSeconds, error& err); void transfer_complete(Transfer* transfer) override { onCallback(); if (onTransferCompleted) onTransferCompleted(transfer); ++transfersComplete; } RequestRetryTracker mRetryTracker; void notify_retry(dstime t, retryreason_t r) override; void request_error(error e) override; void request_response_progress(m_off_t a, m_off_t b) override; void threadloop(); void updateClientDowaitDs(const dstime lastClientDoWait); dstime consumeClientDowaitDs(const dstime timeGranularity = 1); void resetClientDowaitDs(); private: dstime mClientDowaitDs{}; public: static bool debugging; // turn this on to prevent the main thread timing out when stepping in the MegaClient template <class PROMISE_VALUE> future<PROMISE_VALUE> thread_do(std::function<void(MegaClient&, shared_promise<PROMISE_VALUE>)> f, string sf, int sl) { unique_lock<mutex> guard(functionDoneMutex); std::shared_ptr<promise<PROMISE_VALUE>> promiseSP(new promise<PROMISE_VALUE>()); nextfunctionMC = [this, promiseSP, f](){ f(this->client, promiseSP); }; nextfunctionMC_sourcefile = sf; nextfunctionMC_sourceline = sl; waiter->notify(); while (!functionDone.wait_until(guard, chrono::steady_clock::now() + chrono::seconds(600), [this]() { return !nextfunctionMC; })) { if (!debugging) { promiseSP->set_value(PROMISE_VALUE()); break; } } return promiseSP->get_future(); } template <class PROMISE_VALUE> future<PROMISE_VALUE> thread_do(std::function<void(StandardClient&, shared_promise<PROMISE_VALUE>)> f, string sf, int sl) { unique_lock<mutex> guard(functionDoneMutex); std::shared_ptr<promise<PROMISE_VALUE>> promiseSP(new promise<PROMISE_VALUE>()); nextfunctionSC_sourcefile = sf; nextfunctionSC_sourceline = sl; nextfunctionSC = [this, promiseSP, f]() { f(*this, promiseSP); }; waiter->notify(); while (!functionDone.wait_until(guard, chrono::steady_clock::now() + chrono::seconds(600), [this]() { return !nextfunctionSC; })) { if (!debugging) { promiseSP->set_value(PROMISE_VALUE()); break; } } return promiseSP->get_future(); } void preloginFromEnv(const string& userenv, PromiseBoolSP pb); void loginFromEnv(const string& userenv, const string& pwdenv, PromiseBoolSP pb); void loginFromSession(const string& session, PromiseBoolSP pb); #if defined(MEGA_MEASURE_CODE) || defined(DEBUG) void sendDeferredAndReset(); #endif class BasicPutNodesCompletion { public: BasicPutNodesCompletion(std::function<void(const Error&)>&& callable) : mCallable(std::move(callable)) {} void operator()(const Error& e, targettype_t, vector<NewNode>&, bool, int /*tag*/, const map<string, string>& /*fileHandles*/) { mCallable(e); } private: std::function<void(const Error&)> mCallable; }; // BasicPutNodesCompletion bool copy(const CloudItem& source, const CloudItem& target, const string& name, VersioningOption versioningPolicy = NoVersioning); bool copy(const CloudItem& source, const CloudItem& target, VersioningOption versioningPolicy = NoVersioning); void copy(const CloudItem& source, const CloudItem& target, string name, PromiseBoolSP result, VersioningOption versioningPolicy); bool putnodes(const CloudItem& parent, VersioningOption versioningPolicy, std::vector<NewNode>&& nodes); void putnodes(const CloudItem& parent, VersioningOption versioningPolicy, std::vector<NewNode>&& nodes, PromiseBoolSP result); void uploadFolderTree_recurse(handle parent, handle& h, const fs::path& p, vector<NewNode>& newnodes); void uploadFolderTree(fs::path p, CloudItem n2, PromiseBoolSP pb); // Necessary to make sure we release the file once we're done with it. struct FileGet : public File { void completed(Transfer* t, putsource_t source) override { File::completed(t, source); result->set_value(true); delete this; } void terminated(error) override { result->set_value(false); delete this; } PromiseBoolSP result; }; // FileGet void downloadFile(const CloudItem& item, const fs::path& destination, PromiseBoolSP result); bool downloadFile(const CloudItem& item, const fs::path& destination); struct FilePut : public File { std::function<void(bool)> completion; FilePut(std::function<void(bool)>&& c) : completion(c) {} void completed(Transfer* t, putsource_t source) override { // do the same thing as File::completed(t, source), but only execute our functor completion() after putnodes completes assert(!transfer || t == transfer); assert(source == PUTNODES_APP); // derived class for sync doesn't use this code path assert(t->type == PUT); auto finalCompletion = std::move(completion); sendPutnodesOfUpload( t->client, t->uploadhandle, "", *t->ultoken, t->filekey, source, NodeHandle(), [finalCompletion](const Error&, targettype_t, vector<NewNode>&, bool /*targetOverride*/, int /*tag*/, const std::map<std::string, std::string>& /*fileHandles*/) { if (finalCompletion) finalCompletion(true); }, nullptr, false); delete this; } void terminated(error) override { if (completion) completion(false); delete this; } }; // FilePut bool uploadFolderTree(fs::path p, const CloudItem& item); void uploadFile(const fs::path& path, const string& name, const Node* parent, TransferDbCommitter& committer, std::function<void(bool)>&& completion, VersioningOption vo = NoVersioning); void uploadFile(const fs::path& path, const string& name, const Node* parent, std::function<void(bool)>&& completion, VersioningOption vo = NoVersioning); bool uploadFile(const fs::path& path, const string& name, const CloudItem& parent, int timeoutSeconds = 30, VersioningOption vo = NoVersioning); bool uploadFile(const fs::path& path, const CloudItem& parent, int timeoutSeconds = 30, VersioningOption vo = NoVersioning); void uploadFilesInTree_recurse(const Node* target, const fs::path& p, std::atomic<int>& inprogress, TransferDbCommitter& committer, VersioningOption vo); bool uploadFilesInTree(fs::path p, const CloudItem& n2, VersioningOption vo = NoVersioning); void uploadFile(const fs::path& sourcePath, const string& targetName, const CloudItem& parent, std::function<void(error)> completion, const VersioningOption versioningPolicy = NoVersioning); void uploadFile(const fs::path& sourcePath, const CloudItem& parent, std::function<void(error)> completion, const VersioningOption versioningPolicy = NoVersioning); class TreeProcPrintTree : public TreeProc { public: void proc(MegaClient*, std::shared_ptr<Node> /*n*/) override { // out() << "fetchnodes tree: " << n->displaypath(); } }; // mark node as removed and notify SynchronizedFunction<void(StandardClient& mc, PromiseBoolSP pb)> onFetchNodes; void fetchnodes(bool noCache, bool loadSyncs, bool reloadingMidSession, PromiseBoolSP pb); bool fetchnodes(bool noCache, bool loadSyncs, bool reloadingMidSession); NewNode makeSubfolder(const string& utf8Name); void catchup(std::function<void(error)> completion); void catchup(PromiseBoolSP pb); unsigned deleteTestBaseFolder(bool mayNeedDeleting); void deleteTestBaseFolder(bool mayNeedDeleting, bool deleted, PromiseUnsignedSP result); void ensureTestBaseFolder(bool mayneedmaking, PromiseBoolSP pb); NewNode* buildSubdirs(list<NewNode>& nodes, const string& prefix, int n, int recurselevel); bool makeCloudSubdirs(const string& prefix, int depth, int fanout); void makeCloudSubdirs(const string& prefix, int depth, int fanout, PromiseBoolSP pb, const string& atpath = ""); struct SyncInfo { NodeHandle h; fs::path localpath; string remotepath; }; SyncConfig syncConfigByBackupID(handle backupID) const; bool syncSet(handle backupId, SyncInfo& info) const; SyncInfo syncSet(handle backupId); SyncInfo syncSet(handle backupId) const; std::shared_ptr<Node> getcloudrootnode(); std::shared_ptr<Node> gettestbasenode(); std::shared_ptr<Node> getcloudrubbishnode(); std::shared_ptr<Node> getsyncdebrisnode(); std::shared_ptr<Node> drillchildnodebyname(std::shared_ptr<Node> n, const string& path); vector<std::shared_ptr<Node>> drillchildnodesbyname(Node* n, const string& path); // setupBackup is implicitly in Vault handle setupBackup_mainthread(const string& rootPath); handle setupBackup_mainthread(const string& rootPath, const SyncOptions& syncOptions); void setupBackup_inThread(const string& rootPath, const SyncOptions& syncOptions, PromiseHandleSP result); // isBackup here allows configuring backups that are not in vault handle setupSync_mainthread(const string& rootPath, const CloudItem& remoteItem, const bool isBackup, const bool uploadIgnoreFile, const string& drivePath = string(1, '\0')); handle setupSync_mainthread(const string& rootPath, const CloudItem& remoteItem, const SyncOptions& syncOptions); void setupSync_inThread(const string& rootPath, const CloudItem& remoteItem, const SyncOptions& syncOptions, PromiseHandleSP result); void importSyncConfigs(string configs, PromiseBoolSP result); bool importSyncConfigs(string configs); string exportSyncConfigs(); void delSync_inthread(handle backupId, PromiseBoolSP result); struct CloudNameLess { bool operator()(const string& lhs, const string& rhs) const { return compare(lhs, rhs) < 0; } static int compare(const string& lhs, const string& rhs) { return compareUtf(lhs, false, rhs, false, false); } static bool equal(const string& lhs, const string& rhs) { return compare(lhs, rhs) == 0; } }; // CloudNameLess bool recursiveConfirm(Model::ModelNode* mn, Node* n, int& descendants, const string& identifier, int depth, bool& firstreported, bool expectFail, bool skipIgnoreFile); bool localNodesMustHaveNodes = true; auto equal_range_utf8EscapingCompare(multimap<string, LocalNode*, CloudNameLess>& ns, const string& cmpValue, bool unescapeValue, bool unescapeMap, bool caseInsensitive) -> std::pair<multimap<string, LocalNode*>::iterator, multimap<string, LocalNode*>::iterator>; bool recursiveConfirm(Model::ModelNode* mn, LocalNode* n, int& descendants, const string& identifier, int depth, bool& firstreported, bool expectFail, bool skipIgnoreFile); bool recursiveConfirm(Model::ModelNode* mn, fs::path p, int& descendants, const string& identifier, int depth, bool ignoreDebris, bool& firstreported, bool expectFail, bool skipIgnoreFile); Sync* syncByBackupId(handle backupId); bool setSyncPausedByBackupId(handle id, bool pause); void enableSyncByBackupId(handle id, PromiseBoolSP result, const string& logname); bool enableSyncByBackupId(handle id, const string& logname); void backupIdForSyncPath(const fs::path& path, PromiseHandleSP result); handle backupIdForSyncPath(fs::path path); enum Confirm { CONFIRM_LOCALFS = 0x01, CONFIRM_LOCALNODE = 0x02, CONFIRM_LOCAL = CONFIRM_LOCALFS | CONFIRM_LOCALNODE, CONFIRM_REMOTE = 0x04, CONFIRM_ALL = CONFIRM_LOCAL | CONFIRM_REMOTE, }; bool confirmModel_mainthread(handle id, Model::ModelNode* mRoot, Node* rRoot, bool expectFail, bool skipIgnoreFile); bool confirmModel_mainthread(handle id, Model::ModelNode* mRoot, LocalNode* lRoot, bool expectFail, bool skipIgnoreFile); bool confirmModel_mainthread(handle id, Model::ModelNode* mRoot, fs::path lRoot, bool ignoreDebris, bool expectFail, bool skipIgnoreFile); bool confirmModel(handle id, Model::ModelNode* mRoot, Node* rRoot, bool expectFail, bool skipIgnoreFile); bool confirmModel(handle id, Model::ModelNode* mRoot, LocalNode* lRoot, bool expectFail, bool skipIgnoreFile); bool confirmModel(handle id, Model::ModelNode* mRoot, fs::path lRoot, bool ignoreDebris, bool expectFail, bool skipIgnoreFile); bool confirmModel(handle backupId, Model::ModelNode* mnode, const int confirm, const bool ignoreDebris, bool expectFail, bool skipIgnoreFile); void prelogin_result(int, string*, string* salt, error e) override; void login_result(error e) override; void fetchnodes_result(const Error& e) override; bool setattr(const CloudItem& item, attr_map&& updates); void setattr(const CloudItem& item, attr_map&& updates, PromiseBoolSP result); bool rename(const CloudItem& item, const string& newName); void unlink_result(handle h, error e) override; handle lastPutnodesResultFirstHandle = UNDEF; void putnodes_result(const Error& e, targettype_t tt, vector<NewNode>& nn, bool targetOverride, int tag, const std::map<std::string, std::string>& fileHandles) override; void catchup_result() override; void disableSync(handle id, SyncError error, bool enabled, bool keepSyncDB, PromiseBoolSP result); bool disableSync(handle id, SyncError error, bool enabled, bool keepSyncDB); template<typename ResultType, typename Callable> ResultType withWait(Callable&& callable, ResultType&& defaultValue = ResultType()) { using std::future_status; using std::shared_ptr; using PromiseType = promise<ResultType>; using PointerType = shared_ptr<PromiseType>; auto promise = PointerType(new PromiseType()); auto future = promise->get_future(); callable(std::move(promise)); auto status = future.wait_for(std::chrono::seconds(20)); if (status == future_status::ready) { return future.get(); } LOG_warn << "Timed out in withWait"; return std::move(defaultValue); } void deleteremote(const CloudItem& item, PromiseBoolSP result); bool deleteremote(const CloudItem& item); bool deleteremotedebris(); void deleteremotedebris(PromiseBoolSP result); void deleteremotenodes(vector<std::shared_ptr<Node> > ns, PromiseBoolSP pb); bool movenode(const CloudItem& source, const CloudItem& target, const string& newName = ""); void movenode(const CloudItem& source, const CloudItem& target, const string& newName, PromiseBoolSP result); void movenodetotrash(string path, PromiseBoolSP pb); void exportnode(std::shared_ptr<Node> n, int del, m_time_t expiry, bool writable, bool megaHosted, promise<Error>& pb); void getpubliclink(Node* n, int del, m_time_t expiry, bool writable, bool megaHosted, promise<Error>& pb); void waitonsyncs(chrono::seconds d = chrono::seconds(2)); /** * @brief Collect syncs problems (stall issues and name conflicts) * @param problems SyncProblems struct where sync problems will be stored */ void syncproblemsDetected(SyncProblems& problems); bool conflictsDetected(list<NameConflict>& conflicts); bool stallsDetected(SyncStallInfoTests& stalls); bool syncStallDetected(SyncStallInfoTests& si) const; bool login_reset(bool noCache = false); bool login_reset(const string& user, const string& pw, bool noCache = false, bool resetBaseCloudFolder = true); bool resetBaseFolderMulticlient(StandardClient* c2 = nullptr, StandardClient* c3 = nullptr, StandardClient* c4 = nullptr); void cleanupForTestReuse(int loginIndex); bool login_reset_makeremotenodes(const string& prefix, int depth = 0, int fanout = 0, bool noCache = false); bool login_reset_makeremotenodes(const string& user, const string& pw, const string& prefix, int depth, int fanout, bool noCache = false); void ensureSyncUserAttributes(PromiseBoolSP result); bool ensureSyncUserAttributes(); bool login(const string& user, const string& pw); bool login_fetchnodes(const string& user, const string& pw, bool makeBaseFolder = false, bool noCache = false); bool login_fetchnodesFromSession(const string& session); bool delSync_mainthread(handle backupId); bool confirmModel_mainthread(Model::ModelNode* mnode, handle backupId, bool ignoreDebris = false, int confirm = CONFIRM_ALL, bool expectFail = false, bool skipIgnoreFile = true); bool match(handle id, const Model::ModelNode* source); void match(handle id, const Model::ModelNode* source, PromiseBoolSP result); bool match(NodeHandle handle, const Model::ModelNode* source); void match(NodeHandle handle, const Model::ModelNode* source, PromiseBoolSP result); bool waitFor(std::function<bool(StandardClient&)> predicate, std::chrono::seconds timeout, std::chrono::milliseconds sleepIncrement = std::chrono::milliseconds(500)); bool waitForSyncTotalStallsStateUpdateTrue(const std::chrono::seconds timeout); bool match(const Node& destination, const Model::ModelNode& source) const; bool makeremotenodes(const string& prefix, int depth, int fanout); bool backupOpenDrive(const fs::path& drivePath); void triggerPeriodicScanEarly(handle backupID); /** * @brief Checks synchronization problems for a given backupId. * * This function validates synchronization issues (conflicts) related to the specified backup * by: * - Ensuring that the backup ID exists in the conflicts map. * - Verifying that the number of conflicts matches the expected count. * - Checking that the last conflict entry has the expected local path. * - Comparing the clashing local names with the provided file names. * * @param backupId The handle representing the ID of the backup to check. * @param backupIdsCount The total number of backup IDs. * @param expectedConflicts The total expected number of conflicts for the given backup. * @param localPath The expected local path associated with the most recent conflict. * @param f1 The first file name expected to be clashing in the conflict. * @param f2 The second file name expected to be clashing in the conflict. */ void checkSyncProblems(const handle backupId, const int backupIdsCount, const unsigned int totalExpectedConflicts, const LocalPath& localPath, const std::string& f1, const std::string& f2); /** * @brief Creates a hard link from a File node. * * @param src The source file path for the hard link * @param dst The destination file path for the hard link * @param sourcePath A reference to a `LocalPath` object where the source path will be stored. * @param targetPath A reference to a `LocalPath` object where the destination path will be * stored. */ void createHardLink(const fs::path& src, const fs::path& dst, LocalPath& sourcePath, LocalPath& targetPath); /** * @brief Deletes a file using the client fileAccess. * * It uses DEFAULTWAIT for retries if the operation ends with a transient error. */ void unlinklocal(const LocalPath& localPath); /** * @brief Checks for synchronization stall issues related to a specific BackupId. * * This function validates synchronization stall issues by checking the number of detected * stalls. It verifies the following: * - The expected number of stalls matches the actual size of the stall info maps. * - At least one stall issue is detected. * - A specific stall issue involving the given source and target paths is present, * along with the correct reasons and path problems. * * @param backupId The handle representing the ID of the backup to check. * @param expectedStalls The expected number of stall issues detected for the given backup. * @param sourcePath A reference to a `LocalPath` object representing the source file path * @param targetPath A reference to a `LocalPath` object representing the target file path */ void checkStallIssues(const handle backupId, const unsigned int expectedStalls, LocalPath& sourcePath, LocalPath& targetPath); handle getNodeHandle(const CloudItem& item); void getNodeHandle(const CloudItem& item, PromiseHandleSP result); FileFingerprint fingerprint(const fs::path& fsPath); vector<FileFingerprint> fingerprints(const string& path); #ifndef NDEBUG virtual void move_begin(const LocalPath& source, const LocalPath& target) override { if (mOnMoveBegin) mOnMoveBegin(source, target); } SynchronizedFunction<void(const LocalPath&, const LocalPath&)> mOnMoveBegin; #endif // ! NDEBUG void backupOpenDrive(const fs::path& drivePath, PromiseBoolSP result); void ipcr(handle id, ipcactions_t action, PromiseBoolSP result); bool ipcr(handle id, ipcactions_t action); bool ipcr(handle id); void opcr(const string& email, opcactions_t action, PromiseHandleSP result); handle opcr(const string& email, opcactions_t action); bool opcr(const string& email); bool iscontact(const string& email); bool isverified(const string& email); bool verifyCredentials(const string& email); bool resetCredentials(const string& email); void rmcontact(const string& email, PromiseBoolSP result); bool rmcontact(const string& email); void opensharedialog(const CloudItem& item, PromiseErrorSP result); Error opensharedialog(const CloudItem& item); void share(const CloudItem& item, const string& email, accesslevel_t permissions, PromiseErrorSP result); Error share(const CloudItem& item, const string& email, accesslevel_t permissions); void upgradeSecurity(PromiseBoolSP result); SynchronizedFunction<void(File&)> mOnFileAdded; SynchronizedFunction<void(File&)> mOnFileComplete; SynchronizedFunction<void(bool)> mOnStall; SynchronizedFunction<void(bool)> mOnConflictsDetected; SynchronizedFunction<void(bool)> mOnTotalStallsUpdate; SynchronizedFunction<void(bool)> mOnTotalConflictsUpdate; void setHasImmediateStall(HasImmediateStallPredicate predicate); void setIsImmediateStall(IsImmediateStallPredicate predicate); void setSyncController(SyncControllerPtr controller); void setDownloadSpeed(m_off_t downloadSpeed); void setUploadSpeed(m_off_t uploadSpeed); void prepareOneFolder(NewNode* node, const std::string& name, bool canChangeVault); void prepareOneFolder(NewNode* node, const char* name, bool canChangeVault); bool requestsCompleted() const; bool transfersCompleted(direction_t type) const; }; struct StandardClientInUseEntry { bool inUse = false; shared_ptr<StandardClient> ptr; string name; int loginIndex; StandardClientInUseEntry(bool iu, shared_ptr<StandardClient> sp, string n, int index) : inUse(iu) , ptr(sp) , name(n) , loginIndex(index) {} }; class StandardClientInUse { list<StandardClientInUseEntry>::iterator entry; public: StandardClientInUse(list<StandardClientInUseEntry>::iterator i) : entry(i) { assert(!entry->inUse); entry->inUse = true; } ~StandardClientInUse() { entry->ptr->cleanupForTestReuse(entry->loginIndex); entry->inUse = false; } StandardClient* operator->() { return entry->ptr.get(); } operator StandardClient*() { return entry->ptr.get(); } operator StandardClient&() { return *entry->ptr; } }; class ClientManager { // reuse the same client for subsequent tests, to save all the time of logging in, fetchnodes, etc. map<int, list<StandardClientInUseEntry>> clients; public: StandardClientInUse getCleanStandardClient(int loginIndex, fs::path workingFolder); void clear(); ~ClientManager(); }; template<class T> bool debugTolerantWaitOnFuture(std::future<T> f, size_t numSeconds) { // don't just use get() as we will stall an entire jenkins run if the promise is not fulfilled // rather, wait with a timeout // if we stop in the debugger, continue the wait after the debugger continues // otherwise, things fail on timeout immediately for (size_t i = 0; i < numSeconds*10; ++i) { if (future_status::ready == f.wait_for(std::chrono::milliseconds(100))) { return true; } } return false; } extern ClientManager* g_clientManager; #endif // ENABLE_SYNC // common base class for test suites so we // always change into the process directory // for each test class SdkTestBase : public ::testing::Test { public: static bool clearProcessFolderEachTest; // set to check that the tests are independednt // by clearing the process's folder // slow as remove the database // run before each test void SetUp() override; }; fs::path getLinkExtractSrciptPath(); // Convenience. bool isFileHidden(const LocalPath& path); bool isFileHidden(const fs::path& path); // Useful utilities. bool createFile(const fs::path& path, const void* data, const size_t data_length); bool createFile(const fs::path &path, const std::string &data); bool createFile(const fs::path& path, const std::string& data, std::chrono::seconds delta); std::string randomData(const std::size_t length); #ifndef _WIN32 // Helper class to handle directory permissions class PermissionHandler { fs::path mDPath; std::optional<fs::perms> mOriginalPermissions{std::nullopt}; bool mPermissionsRemoved{false}; public: explicit PermissionHandler(const std::string& dPath) : mDPath(dPath) { try { // Retrieve and store the current permissions mOriginalPermissions = fs::status(mDPath).permissions(); } catch (const fs::filesystem_error& e) { LOG_debug << "Failed to retrieve original permissions for directory: '" << mDPath << "': " << e.what(); } } ~PermissionHandler() { restorePermissions(); } bool removePermissions(fs::perms permissionsToRemove) { if (!mOriginalPermissions) { LOG_debug << "Original permissions needed"; return false; } if (mPermissionsRemoved) { LOG_debug << "Permissions were already removed before, they should be restored first"; return false; } try { fs::permissions(mDPath, *mOriginalPermissions & ~permissionsToRemove, fs::perm_options::replace); mPermissionsRemoved = true; LOG_verbose << "Successfuly removed permissions for directory: " << mDPath; } catch (const fs::filesystem_error& e) { LOG_debug << "Failed to remove permissions for directory: " << mDPath << " with error: " << e.what(); mPermissionsRemoved = false; } return mPermissionsRemoved; } bool restorePermissions() { if (!mOriginalPermissions) { LOG_debug << "Original permissions needed"; return false; } if (!mPermissionsRemoved) return true; try { fs::permissions(mDPath, *mOriginalPermissions, fs::perm_options::replace); mPermissionsRemoved = false; // This restores the flag, as the permissions have been restored too. LOG_verbose << "Successfuly restored permissions for directory: " << mDPath; } catch (const fs::filesystem_error& e) { LOG_debug << "Failed to restore permissions for directory: " << mDPath << " with error: " << e.what(); } return !mPermissionsRemoved; } bool originalPermissionsAvailable() const { return mOriginalPermissions.has_value(); } }; #endif #endif // TEST_H ����������������������������������������������������������sdk-10.11.0/tests/python/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015132�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/python/sync_stress.py�������������������������������������������������������������0000664�0000000�0000000�00000015677�15162662266�0020103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Application for stress testing syncing algorithm (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ import sys import os from threading import Thread import random import time import string import shutil import logging import collections class Action: dir_type = "dir" file_type = "file" def __init__ (self, base_sync_dir, tmp_dir, otype): self.base_sync_dir = base_sync_dir self.otype = otype self.tmp_dir = tmp_dir self.seed = "MegaSDK" self.words = open("lorem.txt", "r").read().replace("\n", '').split() self.max_files = 100 self.full_name = "" self.base_name = "" def is_dir (self): return self.otype == self.dir_type def is_file (self): return self.otype == self.file_type def get_random_str (self, size=10, chars = string.ascii_uppercase + string.digits): return ''.join (random.choice (chars) for x in range (size)) def fdata (self): """ Produce random data """ a = collections.deque (self.words) b = collections.deque (self.seed) while True: yield ' '.join (list (a)[0:1024]) a.rotate (int (b[0])) b.rotate (1) def create_file (self, fname, flen): """ Create a file with the given length """ g = self.fdata () fout = open (fname, 'w') while os.path.getsize (fname) < flen: fout.write (g.next()) fout.close () def create (self): """ Create a file / dir with a random name Must be the first action to be called """ # generate a random file / dir name self.base_name = self.get_random_str () # set a full name self.full_name = self.base_sync_dir + "/" + self.base_name if self.is_dir (): try: os.mkdir (self.full_name); except Exception, e: print "Failed to create dir !", e sys.exit (1) else: try: f = open (self.full_name, 'w') except Exception, e: print "Failed to create file !", e sys.exit (1) f.close () logging.info ('[create] [%s] %s', self.otype, self.full_name) def rename (self): """ Rename a file / dir """ self.base_name = self.get_random_str () new_name = self.base_sync_dir + "/" + self.base_name try: shutil.move (self.full_name, new_name) except Exception, e: print "Failed to move an object !", e sys.exit (1) logging.info ('[rename] [%s] %s => %s', self.otype, self.full_name, new_name) self.full_name = new_name def moveout (self): """ Move out of sync dir """ new_name = self.tmp_dir + "/" + self.base_name try: shutil.move (self.full_name, new_name) except Exception, e: print "Failed to move an object !", e sys.exit (1) logging.info ('[moveout] [%s] %s => %s', self.otype, self.full_name, new_name) self.full_name = new_name def movein (self): """ Move into sync dir """ new_name = self.base_sync_dir + "/" + self.base_name try: shutil.move (self.full_name, new_name) except Exception, e: print "Failed to move an object !", e sys.exit (1) logging.info ('[movein] [%s] %s => %s', self.otype, self.full_name, new_name) self.full_name = new_name def delete (self): """ Delete file / dir """ if self.is_dir (): try: shutil.rmtree (self.full_name) except Exception, e: print "Failed to delete directory !", e sys.exit (1) else: try: os.unlink (self.full_name) except Exception, e: print "Failed to delete file !", e sys.exit (1) logging.info ('[delete] [%s] %s', self.otype, self.full_name) self.full_name = "" self.base_name = "" def filldir (self): """ fill dir with random files """ if self.is_file (): return max_files = random.randint (1, self.max_files) for i in range (0, max_files): flen = random.randint (1, 1024 * 4) fname = self.get_random_str () self.create_file (self.full_name + "/" + fname, flen) logging.info ('[filldir] [%s] created %d files', self.otype, max_files) class Worker (Thread): """ Worker class (run in a thread) """ def __init__ (self, base_sync_dir, tmp_dir): Thread.__init__(self) self.base_sync_dir = base_sync_dir self.tmp_dir = tmp_dir def cmds (self): f = open ("cmds.txt", "r") l_cmds = [] for line in f: if (line[0] != '#') and (len (line) > 1): l_cmds.append (line.strip()) f.close() i = 0 while i < len (l_cmds): yield l_cmds[i] i = i + 1 def run (self): otype = [Action.dir_type, Action.file_type] a = Action (self.base_sync_dir, self.tmp_dir, random.choice (otype)) cmd = self.cmds () for c in cmd: if c == "create": a.create () elif c == "moveout": a.moveout () elif c == "movein": a.movein () elif c == "rename": a.rename () elif c == "filldir": a.filldir () elif c == "delete": a.delete () elif c == "sleep": logging.info ('[sleep]') time.sleep (2) else: logging.error ('Unknown command: %s', c) def main (num_workers, base_sync_dir, tmp_dir): random.seed (time.time()) w_list = [] for i in range (0, num_workers): w = Worker (base_sync_dir, tmp_dir) w_list.append (w) for w in w_list: w.run () if __name__ == "__main__": if len (sys.argv) < 4: print "Please run as: " + sys.argv[0] + " [number of workers] [sync directory] [out of sync directory]" sys.exit (1) # logging stuff, output to stdout logging.StreamHandler (sys.stdout) logging.basicConfig (level=logging.INFO) main (int (sys.argv[1]), sys.argv[2], sys.argv[3]) �����������������������������������������������������������������sdk-10.11.0/tests/python/sync_test.py���������������������������������������������������������������0000664�0000000�0000000�00000046052�15162662266�0017526�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Base class for testing syncing algorithm (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ # TODO tests: # * "pause" sync # * lock directory # * large (> 4Gb) files # * > 10000 folders to synchronize from sync_test_base import SyncTestBase from sync_test_base import get_random_str from sync_test_base import generate_unicode_name import random import os import logging import time import math class SyncTest(SyncTestBase): """ Class with MEGA SDK test methods """ # tests def test_create_delete_files(self): """ create files with different size, compare files on both folders, remove files, check that files removed from the second folder """ logging.info("Launching test_create_delete_files test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create files l_files = self.files_create() self.assertIsNotNone(l_files, "Creating files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.files_remove(l_files), "Removing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_create_rename_delete_files(self): """ create files with different size, compare files on both folders, rename files """ logging.info("Launching test_create_rename_delete_files test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create files l_files = self.files_create() self.assertIsNotNone(l_files, "Creating files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # renaming self.assertTrue(self.files_rename(l_files), "Renaming files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.files_remove(l_files), "Removing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_create_move_delete_files(self): """ create files with different size, move and delete files """ logging.info("Launching test_create_move_delete_files test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create files l_files = self.files_create() self.assertIsNotNone(l_files, "Creating files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # move & delete self.assertTrue(self.files_moveanddelete(l_files,"subfolder"), "Move&Delete files") #~ self.assertTrue(self.files_moveanddelete(l_files,"."), "Move&Delete files") #this is expected to fail as of 20171019 self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_mimic_update_with_backup_files(self): """ create files with different size, mimic_update_with_backup """ logging.info("Launching test_mimic_update_with_backup_files test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create files l_files = self.files_create() self.assertIsNotNone(l_files, "Creating files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # mimic update with backup self.assertTrue(self.files_mimic_update_with_backup(l_files), "Mimic update with backup files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # remove files self.assertTrue(self.files_remove(l_files), "Removing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_create_delete_dirs(self): """ create directories with different amount of files, compare directories on both sync folders, remove directories, check that directories removed from the second folder """ logging.info("Launching test_create_delete_dirs test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create dirs l_dirs = self.dirs_create() self.assertIsNotNone(l_dirs, "Creating directories") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.dirs_check(l_dirs), "Comparing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.dirs_remove(l_dirs), "Removing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_create_rename_delete_dirs(self): """ create directories with different amount of files, compare directories on both sync folders, rename directories compare directories on both sync folders, remove directories, check that directories removed from the second folder """ logging.info("Launching test_create_rename_delete_dirs test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create dirs l_dirs = self.dirs_create() self.assertIsNotNone(l_dirs, "Creating directories") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.dirs_check(l_dirs), "Comparing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # rename dirs self.assertTrue(self.dirs_rename(l_dirs), "Rename directories") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.dirs_check(l_dirs), "Comparing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.dirs_remove(l_dirs), "Removing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True def test_sync_files_write(self): """ write data to a file located in both sync folders check for the result, expected result: both files contains the same content """ logging.info("Launching test_sync_files_write test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") for _ in range(0, self.nr_files): self.assertTrue(self.app.is_alive(), "Test application is not running") strlen = random.randint(10, 20) fname = get_random_str(size=strlen) fname_in = os.path.join(self.app.local_folder_in, fname) fname_out = os.path.join(self.app.local_folder_out, fname) logging.debug("Writing to both files: %s and %s" % (fname_in, fname_out)) with open(fname_in, 'a'): os.utime(fname_in, None) with open(fname_out, 'a'): os.utime(fname_out, None) #self.app.sync() for _ in range(self.nr_changes): with open(fname_in, 'a') as f_in: f_in.write(get_random_str(100)) with open(fname_out, 'a') as f_out: f_out.write(get_random_str(100)) for r in range(self.app.nr_retries): self.app.attempt=r md5_in = "INPUT FILE NOT READABLE"; md5_out = "OUTPUT FILE NOT READABLE"; try: md5_in = self.md5_for_file(fname_in) md5_out = self.md5_for_file(fname_out) except IOError: pass; if md5_in == md5_out: break self.app.sync() logging.debug("File %s md5: %s" % (fname_in, md5_in)) logging.debug("File %s md5: %s" % (fname_out, md5_out)) self.assertEqual(md5_in, md5_out, "Files do not match") def test_local_operations(self): """ write data to a file located in both sync folders check for the result, expected result: both files contains the same content """ logging.info("Launching test_local_operations test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); l_tree = self.local_tree_create("", self.nr_dirs) self.assertIsNotNone(l_tree, "Failed to create directory tree!") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() self.assertTrue(self.local_tree_compare(l_tree), "Failed to compare directory trees!") self.assertTrue(self.app.is_alive(), "Test application is not running") self.assertTrue(self.local_tree_create_and_move(l_tree), "Failed to create a new sub folder and move an existing directory into it!") self.assertTrue(self.app.is_alive(), "Test application is not running") self.assertTrue(self.local_tree_multiple_renames(l_tree), "Failed to rename folder multiple times and then rename back to the original name!") self.assertTrue(self.app.is_alive(), "Test application is not running") def test_update_mtime(self): """ update mtime of a file in both local folders """ logging.info("Launching test_update_mtime test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); in_file = os.path.join(self.app.local_folder_in, "mtime_test") out_file = os.path.join(self.app.local_folder_out, "mtime_test") for _ in range(self.nr_time_changes): logging.debug("Touching: %s" % in_file) now = math.floor(time.time()) #floor to get seconds with open(in_file, 'a'): os.utime(in_file, (now, now)) # with open(out_file, 'a'): # os.utime(in_file, (now, now)) atime=0 mtime=0 for r in range(self.app.nr_retries): self.app.attempt=r try: mtime = os.path.getmtime(out_file) except OSError: pass try: atime = os.path.getatime(out_file) except OSError: pass logging.debug("Comparing time: %s. atime: %d = %d, mtime: %d = %d" % (out_file, now, atime, now, mtime)) if (mtime==now): #all good break; self.app.sync() logging.debug("Comparing time for %s failed! Retrying [%d/%d] .." % (out_file, r + 1, self.nr_retries)) #self.assertEqual(atime, now, "atime values are different") self.assertEqual(mtime, now, "mtime values are different") self.assertTrue(self.app.is_alive(), "Test application is not running") def test_create_rename_delete_unicode_files_dirs(self): """ create directories with different amount of files, using Unicode encoding for files / directories names, compare directories on both sync folders, rename directories compare directories on both sync folders, remove directories, check that directories removed from the second folder """ logging.info("Launching test_create_rename_delete_unicode_files_dirs test") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.change_folders(); # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create files l_files = self.files_create(generate_unicode_name) self.assertIsNotNone(l_files, "Creating files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # renaming self.assertTrue(self.files_rename(l_files, generate_unicode_name), "Renaming files") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.files_check(l_files), "Comparing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.files_remove(l_files), "Removing files") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty #TODO: why is this twice? self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") # create dirs l_dirs = self.dirs_create(generate_unicode_name) self.assertIsNotNone(l_dirs, "Creating directories") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.dirs_check(l_dirs), "Comparing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # rename dirs self.assertTrue(self.dirs_rename(l_dirs, generate_unicode_name), "Rename directories") self.assertTrue(self.app.is_alive(), "Test application is not running") self.app.sync() # comparing self.assertTrue(self.dirs_check(l_dirs), "Comparing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # remove files self.assertTrue(self.dirs_remove(l_dirs), "Removing directories") self.assertTrue(self.app.is_alive(), "Test application is not running") # make sure remote folders are empty self.assertTrue(self.dirs_check_empty(), "Checking if remote folders are empty") self.assertTrue(self.app.is_alive(), "Test application is not running") return True ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/python/sync_test_app.py�����������������������������������������������������������0000664�0000000�0000000�00000015024�15162662266�0020361�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Application for testing syncing algorithm (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ import os import time import random from sync_test_base import get_random_str import shutil import logging import datetime class SyncTestApp(object): """ test application base class """ def __init__(self, local_mount_in, local_mount_out, work_folder, delete_tmp_files=True, use_large_files=True): """ work_dir: a temporary folder to place generated files remote_folder: a remote folder to sync """ self.start_time = time.time() random.seed(time.time()) self.local_mount_in = local_mount_in self.local_mount_out = local_mount_out self.rnd_folder = get_random_str() self.local_folder_in = os.path.join(self.local_mount_in, self.rnd_folder) self.local_folder_out = os.path.join(self.local_mount_out, self.rnd_folder) self.work_folder = os.path.join(work_folder, self.rnd_folder) self.nr_retries = 200 self.delete_tmp_files = delete_tmp_files self.use_large_files = use_large_files def change_folders(self): """ cleans directories and call finish """ time.sleep(0.2) # to prevent from sync algorithm interpreting we are renaming if self.delete_tmp_files: try: shutil.rmtree(self.local_folder_in) except OSError: pass time.sleep(1.2) # to prevent from sync algorithm interpreting we are renaming self.rnd_folder = get_random_str() self.local_folder_in = os.path.join(self.local_mount_in, self.rnd_folder) self.local_folder_out = os.path.join(self.local_mount_out, self.rnd_folder) self.work_folder = os.path.join(self.work_folder, self.rnd_folder) self.prepare_folders(); def __enter__(self): # call subclass function res = self.start() if not res: self.stop() raise Exception('Failed to start app!') res = self.prepare_folders() if not res: self.stop() raise Exception('Failed to start app!') return self def __exit__(self, exc_type, exc_value, traceback): # remove tmp folders if self.delete_tmp_files: try: logging.debug("Deleting %s" % self.local_folder_in) shutil.rmtree(self.local_folder_in) except OSError: pass try: logging.debug("Deleting %s" % self.local_folder_out) shutil.rmtree(self.local_folder_out) except OSError: pass try: logging.debug("Deleting %s" % self.work_folder) shutil.rmtree(self.work_folder) except OSError: pass # terminate apps self.stop() logging.info("Execution time: %s" % str(datetime.timedelta(seconds=time.time()-self.start_time))) @staticmethod def touch(path): """ create an empty file update utime """ with open(path, 'a'): os.utime(path, None) def prepare_folders(self): """ prepare upsync, downsync and work directories """ # create "in" folder logging.info("IN folder: %s" % self.local_folder_in) try: os.makedirs(self.local_folder_in) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (self.local_folder_in, e)) return False logging.info("OUT folder: %s" % self.local_folder_out) self.sync() # temporary workaround #tmp_fix_file = os.path.join(self.local_mount_out, "tmp_fix") success = False # try to access the dir for r in range(0, self.nr_retries): self.attempt=r try: if os.path.isdir(self.local_folder_out): success = True break else: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d] .." % (self.local_folder_out, r + 1, self.nr_retries)) #self.touch(tmp_fix_file) self.sync() except OSError: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d] .." % (self.local_folder_out, r + 1, self.nr_retries)) #self.touch(tmp_fix_file) self.sync() if success is False: logging.error("Failed to access directory: %s" % self.local_folder_out) return False # create work folder logging.debug("Work folder: %s" % self.work_folder) try: os.makedirs(self.work_folder) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (self.work_folder, e)) return False return True def stop(self): """ cleans directories and call finish """ if self.delete_tmp_files: try: shutil.rmtree(self.local_folder_in) except OSError: pass self.sync() self.finish() # virtual methods def start(self): """ start application """ raise NotImplementedError("Not Implemented !") def finish(self): """ stop application """ raise NotImplementedError("Not Implemented !") def sync(self): """ wait for full synchronization """ raise NotImplementedError("Not Implemented !") def pause(self): """ pause application """ raise NotImplementedError("Not Implemented !") def unpause(self): """ unpause application """ raise NotImplementedError("Not Implemented !") def is_alive(self): """ return True if application instance is running """ raise NotImplementedError("Not Implemented !") ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/python/sync_test_base.py����������������������������������������������������������0000664�0000000�0000000�00000107661�15162662266�0020524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Base class for testing syncing algorithm (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ import os import random import string import shutil import hashlib import unittest import logging import platform import unicodedata import time def get_unicode_str(size=10, max_char=0xFFFF, onlyNormalized=False, includeUnexisting=False): ''' generates valid (for current OS) Unicode file name Notice: if includeUnexisting==True, it is possible that files don't get synchronized ''' if platform.system() == "Windows": # Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t). exclude = string.punctuation + u"\t" + u''.join([unichr(x) for x in range(0, 32)]) else: # I guess it mainly depends on fs type #exclude = u"/" + u"." + u''.join([unichr(x) for x in range(0, 1)]) exclude = u"/" + u"." + u''.join([unichr(x) for x in range(0, 32)]) name = u"" while len(name) < size: c = unichr(random.randint(0, max_char)) if c not in exclude: try: if not includeUnexisting: unicodedata.name(c) #this will cause invalid unicode character to throw exception if onlyNormalized: name = name + unicodedata.normalize('NFC',c) #only normalized chars else: name = name + c # except UnicodeDecodeError: # print "UnicodeDecodeError con",c,repr(c),c.encode('utf-8') # c.decode('utf-8') # try: # unicodedata.name(c) # except: # pass # pass except ValueError: # try: # unicodedata.name(c) # print "that one was valid!",c,repr(c) # pass # except: # pass pass return name def get_exotic_str(size=10): """ generate string containing random combinations of % and ASCII symbols """ name = u"" while len(name) < size: num = random.randint(1, 3) name = name + u"%" * num + get_random_str(2) return name def get_random_str(size=10, chars=string.ascii_lowercase + string.ascii_uppercase + string.digits): """ return a random string size: size of an output string chars: characters to use """ return ''.join(random.choice(chars) for x in range(size)) def generate_ascii_name(first_symbol, i): """ generate random ASCII string """ strlen = random.randint(0, 20) return first_symbol + get_random_str(size=strlen) + str(i) def generate_unicode_name_old(first_symbol, i): """ generate random UTF string """ strlen = random.randint(10, 30) c = random.choice(['short-utf', 'utf', 'exotic']) if c == 'short-utf': s = get_unicode_str(strlen, 0xFF) elif c == 'utf': s = get_unicode_str(strlen) else: s = get_exotic_str(strlen) #logging.debug("Creating Unicode file: %s" % (s.encode("unicode-escape"))) return s cogen=0 def generate_unicode_name(first_symbol, i): """ generate random UTF string """ strlen = random.randint(10, 30) c = random.choice(['short-utf', 'utf', 'exotic']) if c == 'short-utf': s = get_unicode_str(strlen, 0xFF) elif c == 'utf': s = get_unicode_str(strlen) else: s = get_exotic_str(strlen) #logging.debug("Creating Unicode file: %s" % (s.encode("unicode-escape"))) global cogen cogen=cogen+1 return str(cogen)+"_"+s def normalizeandescape(name): name=escapefsincompatible(name) name=unicodedata.normalize('NFC',unicode(name)) #name=unicodedata.normalize('NFC',name) #name=escapefsincompatible(name) return name def escapefsincompatible(name): """ Escape file system incompatible characters """ import urllib for i in "\\/:?\"<>|*": name=name.replace(i,urllib.quote(i).lower()) return name class SyncTestBase(unittest.TestCase): """ Base class with MEGA SDK test helper methods """ def __init__(self, methodName, app): """ local_mount_in: local upsync folder local_mount_out: local downsync folder work_folder: a temporary out of sync folder """ super(SyncTestBase, self).__init__(methodName) self.app = app self.nr_retries = 200 self.nr_files = 10 self.nr_dirs = 10 self.nr_time_changes = 10 self.nr_changes = 10 self.local_obj_nr = 5 self.force_syncing = False def check_empty(self, folder_name): """ return True if folder is empty """ logging.debug("Checking if folder %s is empty" % folder_name) # leave old files if not self.app.delete_tmp_files: return True for r in range(0, self.nr_retries): self.app.attempt=r try: res = not os.listdir(folder_name) except OSError, e: logging.error("Failed to list dir: %s (%s)" % (folder_name, e)) return False if res: return True logging.debug("Directory %s is not empty! Retrying [%d/%d] .." % (folder_name, r + 1, self.nr_retries)) self.app.sync() #~ try: #~ shutil.rmtree(folder_name) #~ except OSError, e: #~ logging.error("Failed to delete folder: %s (%s)" % (folder_name, e)) #~ return False @staticmethod def md5_for_file(fname, block_size=2**20): """ calculates md5 of a file """ fout = open(fname, 'r') md5 = hashlib.md5() while True: data = fout.read(block_size) if not data: break md5.update(data) fout.close() return md5.hexdigest() @staticmethod def touch(path): """ create an empty file update utime """ with open(path, 'a'): os.utime(path, None) @staticmethod def file_create(fname, fsize): """ create a file of a size fsize and fill with a random data """ fout = open(fname, 'wb') fout.write(get_random_str(fsize)) fout.close() def files_create_size(self, first_symbol, maxsize, nr_files, dname, file_generate_name_func, l_files): """ create a list of files of a specific size """ for i in range(nr_files): fname = file_generate_name_func(first_symbol, i) ffname = os.path.join(dname, fname) if maxsize == 0: fsize = 0 else: fsize = random.randint(1, maxsize) try: self.file_create(ffname, fsize) except IOError, e: logging.error("Failed to create file: %s (%s)" % (ffname, e)) return False except UnicodeEncodeError, e: logging.debug("Discarded filename due to UnicodeEncodeError: %s" % (ffname)) i=i-1 continue md5_str = self.md5_for_file(ffname) l_files.append({"name":fname, "size":fsize, "md5":md5_str, "name_orig":fname}) logging.debug("File created: %s [%s, %db]" % (ffname, md5_str, fsize)) return True def files_create(self, file_generate_name_func=generate_ascii_name): """ create files in "in" instance and check files presence in "out" instance Return list of files """ logging.debug("Creating files.. (nrfiles="+str(self.nr_files)+")") l_files = [] # empty files res = self.files_create_size("e", 0, self.nr_files, self.app.local_folder_in, file_generate_name_func, l_files) if not res: return None # small files < 1k if not hasattr(self.app, 'only_empty_files') or not self.app.only_empty_files: res = self.files_create_size("s", 1024, self.nr_files, self.app.local_folder_in, file_generate_name_func, l_files) if not res: return None if self.app.use_large_files: # medium files < 1mb res = self.files_create_size("m", 1024*1024, self.nr_files, self.app.local_folder_in, file_generate_name_func, l_files) if not res: return None # large files < 10mb res = self.files_create_size("l", 10*1024*1024, self.nr_files, self.app.local_folder_in, file_generate_name_func, l_files) if not res: return None # randomize list random.shuffle(l_files) return l_files def files_check(self, l_files, dir_name=""): """ check files on both folders compare names, size, md5 sums """ logging.debug("Checking files..") # check files for f in l_files: dd_out = os.path.join(self.app.local_folder_out, dir_name) ffname = os.path.join(dd_out, f["name"]) #when saving mega alters some characters (we will look for #destiny file having that in mind) ffname=normalizeandescape(ffname) dd_in = os.path.join(self.app.local_folder_in, dir_name) ffname_in = os.path.join(dd_in, f["name"]) success = False logging.debug("Comparing %s and %s" % (ffname_in, ffname)) # try to access the file for r in range(0, self.nr_retries): self.app.attempt=r try: with open(ffname): pass success = True break except IOError as ex: # wait for a file logging.debug("File %s not found! Retrying [%d/%d] .." % (ffname, r + 1, self.nr_retries)) logging.debug("%s" % (ffname.encode("unicode-escape"))) self.app.sync() if success is False: logging.error("Failed to compare files: %s and %s" % (ffname_in, ffname)) return False # get md5 of synced file md5_str = self.md5_for_file(ffname) if md5_str != f["md5"]: logging.error("MD5 sums don't match for file: %s" % ffname) return False return True def dir_create(self, dname, files_num, files_maxsize, file_generate_name_func=generate_ascii_name): """ create and fill directory with files return files list """ try: os.makedirs(dname) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (dname, e)) return None l_files = [] res = self.files_create_size("s", files_maxsize, files_num, dname, file_generate_name_func, l_files) if not res: return None return l_files def dir_create_size(self, symbol, dirs_num, files_num, files_maxsize, parent_dir, dir_generate_name_func, l_dirs): """ create dirs_num directories with directories """ for i in range(dirs_num): dname = dir_generate_name_func(symbol, i) ddname = os.path.join(parent_dir, dname) l_files = self.dir_create(ddname, files_num, files_maxsize, dir_generate_name_func) if l_files is None: logging.error("Failed to create directory: %s" % ddname) return False l_dirs.append({"name":dname, "files_nr":files_num, "name_orig":dname, "l_files":l_files}) logging.debug("Directory created: %s [%d files]" % (ddname, files_num)) return True def dirs_create(self, dir_generate_name_func=generate_ascii_name): """ create dirs """ logging.debug("Creating "+str(self.nr_dirs)+" directories..") l_dirs = [] # create empty dirs res = self.dir_create_size("z", self.nr_dirs, 0, 0, self.app.local_folder_in, dir_generate_name_func, l_dirs) if not res: return None # create dirs with #nr_files files if not hasattr(self.app, 'only_empty_folders') or not self.app.only_empty_folders: res = self.dir_create_size("d", self.nr_dirs, self.nr_files, 1024, self.app.local_folder_in, dir_generate_name_func, l_dirs) if not res: return None # randomize list random.shuffle(l_dirs) return l_dirs def dirs_check(self, l_dirs): """ check directories for both folders """ logging.debug("Checking directories..") for d in l_dirs: dname = os.path.join(self.app.local_folder_out, d["name"]) ##when saving mega alters some characters (we will look for #destiny file having that in mind) dname=normalizeandescape(dname) dname_in = os.path.join(self.app.local_folder_in, d["name"]) success = False logging.debug("Comparing dirs: %s and %s" % (dname_in, dname)) # try to access the dir for r in range(0, self.nr_retries): self.app.attempt=r try: if os.path.isdir(dname): success = True break else: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d].." % (dname, r + 1, self.nr_retries)) self.app.sync() except OSError: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d].." % (dname, r + 1, self.nr_retries)) self.app.sync() if success is False: logging.error("Failed to access directories: %s and " % dname) return False # check files res = self.files_check(d["l_files"], d["name"]) if not res: logging.error("Directories do not match !") return False return True def file_rename(self, ffname_src, ffname_dst): """ renaming file return True if renamed """ for r in range(0, self.nr_retries): self.app.attempt=r if os.path.exists(ffname_src): try: shutil.move(ffname_src, ffname_dst) except OSError, e: logging.error("Failed to rename file: %s (%s)" % (ffname_src, e)) return False if self.force_syncing: self.app.sync() # try to access both files (old and new) if not os.path.exists(ffname_dst): logging.debug("Failed to access a newly renamed file: %s, retrying [%d/%d].." % (ffname_dst, r + 1, self.nr_retries)) continue if os.path.exists(ffname_src): logging.debug("Still can access an old renamed file: %s, retrying [%d/%d]" % (ffname_src, r + 1, self.nr_retries)) continue break # try to access both files (old and new) if not os.path.exists(ffname_dst): logging.error("Failed to access a newly renamed file: %s. Aborting.." % ffname_dst) return False if os.path.exists(ffname_src): logging.error("Still can access an old renamed file: %s. Aborting.." % ffname_src) return False return True def files_rename(self, l_files, file_generate_name_func=generate_ascii_name): """ rename objects in "in" instance and check new files in "out" instance """ logging.debug("Renaming files..") i = 0 for f in l_files: ffname_src = os.path.join(self.app.local_folder_in, f["name"]) f["name"] = file_generate_name_func("renamed_", i) i = i + 1 ffname_dst = os.path.join(self.app.local_folder_in, f["name"]) logging.debug("Renaming file: %s => %s" % (ffname_src, ffname_dst)) if not self.file_rename(ffname_src, ffname_dst): return False return True def files_moveanddelete(self, l_files, where=".", timeout=0, file_generate_name_func=generate_ascii_name): """ moves and deletes objects in "in" instance and check new files in "out" instance """ logging.debug("Move&Rename files..") try: os.makedirs(os.path.join(self.app.local_folder_in,where)) except Exception, e: logging.debug("Unable to create subfolder: %s (%s)" % (where, e)) i = 0 for f in l_files: ffname_src = os.path.join(self.app.local_folder_in, f["name"]) f["name"] = file_generate_name_func("renamed_", i) i = i + 1 ffname_dst = os.path.join(self.app.local_folder_in, where, f["name"]) logging.debug("move&delete file: %s => %s" % (ffname_src, ffname_dst)) if os.path.exists(ffname_src): try: shutil.move(ffname_src, ffname_dst) except OSError, e: logging.error("Failed to rename file: %s (%s)" % (ffname_src, e)) return False try: time.sleep(timeout) os.remove(ffname_dst) except OSError, e: logging.error("Failed to delete file: %s (%s)" % (ffname_dst, e)) return False if (where != "."): shutil.rmtree(os.path.join(self.app.local_folder_in,where)) return True def files_mimic_update_with_backup(self, l_files, timeout=0, file_generate_name_func=generate_ascii_name): """ moves and deletes objects in "in" instance and check new files in "out" instance """ logging.debug("Mimic update with backup files..") i = 0 for f in l_files: ffname_src = os.path.join(self.app.local_folder_in, f["name"]) #f["name"] = file_generate_name_func("renamed_", i) i = i + 1 ffname_dst = os.path.join(self.app.local_folder_in, "renamed_"+f["name"]) ffname_dst_out = os.path.join(self.app.local_folder_out, "renamed_"+f["name"]) logging.debug("Mimic update with backup file: %s => %s" % (ffname_src, ffname_dst)) if os.path.exists(ffname_src): try: shutil.move(ffname_src, ffname_dst) except OSError, e: logging.error("Failed to rename file: %s (%s)" % (ffname_src, e)) return False try: time.sleep(timeout) with open(ffname_dst, 'r') as f: with open(ffname_src, 'w') as f2: for r in range(100): f2.write("whatever") time.sleep(0.03) if os.path.exists(ffname_dst_out): #existing temporary file logging.error("ERROR in sync: Temporary file being created in syncout: : %s!" % (ffname_dst)) os.remove(ffname_dst) return False; os.remove(ffname_dst) except OSError, e: logging.error("Failed to delete file: %s (%s)" % (ffname_dst, e)) return False return True def files_remove(self, l_files): """ remove files in "in" instance and check files absence in "out" instance """ logging.debug("Removing files..") for f in l_files: ffname = os.path.join(self.app.local_folder_in, f["name"]) logging.debug("Deleting: %s" % ffname) for r in range(0, self.nr_retries): self.app.attempt=r try: os.remove(ffname) except OSError, e: logging.error("Failed to delete file: %s (%s)" % (ffname, e)) return False if self.force_syncing: self.app.sync() # check if local file does not exist if not os.path.exists(ffname): break logging.debug("Deleted file %s still exists, retrying [%d/%d].." % (ffname, r + 1, self.nr_retries)) if os.path.exists(ffname): logging.debug("Deleted file %s still exists, aborting.." % ffname) success = False for f in l_files: ffname = os.path.join(self.app.local_folder_out, f["name"]) for r in range(0, self.nr_retries): self.app.attempt=r try: # file must be deleted with open(ffname): pass logging.debug("File %s is not deleted. Retrying [%d/%d] .." % (ffname, r + 1, self.nr_retries)) self.app.sync() except IOError: success = True break if success is False: logging.error("Failed to delete file: %s" % ffname) return False return True def dirs_rename(self, l_dirs, dir_generate_name_func=generate_ascii_name): """ rename directories in "in" instance and check directories new names in "out" instance """ logging.debug("Renaming directories..") i = 0 for d in l_dirs: dname_src = os.path.join(self.app.local_folder_in, d["name"]) d["name"] = dir_generate_name_func("renamed_", i) i = i + 1 dname_dst = os.path.join(self.app.local_folder_in, d["name"]) try: shutil.move(dname_src, dname_dst) except OSError, e: logging.error("Failed to rename directory: %s (%s)" % (dname_src, e)) return False if self.force_syncing: self.app.sync() # try to both dirs if not os.path.exists(dname_dst): logging.error("Failed to access a newly renamed directory: %s" % dname_dst) return False if os.path.exists(dname_src): logging.error("Still can access an old directory: %s" % dname_src) return False logging.debug("Directory renamed: %s => %s" % (dname_src, dname_dst)) return True def dirs_remove(self, l_dirs): """ remove directories in "in" instance and check directories absence in "out" instance """ logging.debug("Removing directories..") for d in l_dirs: dname = os.path.join(self.app.local_folder_in, d["name"]) try: shutil.rmtree(dname) except OSError, e: logging.error("Failed to delete dir: %s (%s)" % (dname, e)) return False logging.debug("Directory removed: %s" % dname) if self.force_syncing: self.app.sync() if os.path.exists(dname): logging.error("Still can access a renamed directory: %s" % dname) return False success = False for d in l_dirs: dname = os.path.join(self.app.local_folder_out, d["name"]) for r in range(0, self.nr_retries): self.app.attempt=r try: # dir must be deleted if not os.path.isdir(dname): success = True break logging.debug("Directory %s is not deleted! Retrying [%d/%d] .." % (dname, r + 1, self.nr_retries)) self.app.sync() except OSError: success = True break if success is False: logging.error("Failed to delete dir: %s" % dname) return False return True def dirs_check_empty(self): """ return True if both folders are empty """ return self.check_empty(self.app.local_folder_in) and self.check_empty(self.app.local_folder_out) def local_tree_create_dir(self, parent_dir): """ generate directory """ strlen = random.randint(10, 20) dname = get_random_str(size=strlen) ddname = os.path.join(parent_dir, dname) real_dname = os.path.join(self.app.local_folder_in, ddname) try: os.makedirs(real_dname) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (real_dname, e)) return None, None, None, None # populate with random amount of files obj_nr = random.randint(1, self.local_obj_nr) l_files = [] for _ in range(0, obj_nr): strlen = random.randint(10, 20) fname = get_random_str(size=strlen) + ".txt" ffname = os.path.join(ddname, fname) fname_real = os.path.join(self.app.local_folder_in, ffname) try: self.file_create(fname_real, random.randint(10, 100)) except IOError, e: logging.error("Failed to create file: %s (%s)" % (fname_real, e)) return None, None, None, None l_files.append({"name":fname, "fname":ffname}) # populate with random amount of dirs obj_nr = random.randint(1, self.local_obj_nr) l_dirs = [] for _ in range(0, obj_nr): strlen = random.randint(10, 20) cname = get_random_str(size=strlen) ccname = os.path.join(ddname, cname) cname_real = os.path.join(self.app.local_folder_in, ccname) try: os.makedirs(cname_real) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (cname_real, e)) return None, None, None, None l_dirs.append({"name":cname, "fname":ccname}) # recursively create subtree return dname, ddname, l_files, l_dirs def local_tree_create(self, parent_dir, dirs_nr): """ generate local directory tree, recursively populate with random number of directories / files return list of dictionaries """ if dirs_nr == 0: return None l_tree = [] for _ in range(0, dirs_nr): dname, ddname, l_files, l_dirs = self.local_tree_create_dir(parent_dir) if dname is None: return None l_child = self.local_tree_create(ddname, dirs_nr - 1) l_tree.append({"name":dname, "fname":ddname, "files":l_files, "dirs":l_dirs, "child":l_child}) return l_tree def local_tree_get_dir(self, l_tree): """ directory walk generator Returns relative directory path """ for i in l_tree: yield i["fname"] for dd in i["dirs"]: yield dd["fname"] if i["child"] is not None: for x in self.local_tree_get_dir(i["child"]): yield x def local_tree_get_dirs(self, l_tree): """ directory walk generator Returns dir dict """ for i in l_tree: yield i if i["child"] is not None: for x in self.local_tree_get_dirs(i["child"]): yield x def local_tree_get_file(self, l_tree): """ directory walk generator Returns relative file path """ for i in l_tree: for ff in i["files"]: yield ff["fname"] if i["child"] is not None: for x in self.local_tree_get_file(i["child"]): yield x def local_tree_compare(self, l_tree): """ compare two local trees return True if they are the same """ total_dirs = total_files = 0 # try to access directories in "out" folder for d in self.local_tree_get_dir(l_tree): dname = os.path.join(self.app.local_folder_out, d) success = False total_dirs = total_dirs + 1 # logging.debug("Trying to access dir: %s" % dname) for r in range(0, self.nr_retries): self.app.attempt=r try: if os.path.isdir(dname): success = True break else: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d].." % (dname, r + 1, self.nr_retries)) self.app.sync() except OSError: # wait for a dir logging.debug("Directory %s not found! Retrying [%d/%d].." % (dname, r + 1, self.nr_retries)) self.app.sync() if success is False: logging.error("Failed to access directories: %s and " % dname) return False # try to access files in "out" folder for f in self.local_tree_get_file(l_tree): fname = os.path.join(self.app.local_folder_out, f) success = False total_files = total_files + 1 # logging.debug("Trying to access file: %s" % fname) for r in range(0, self.nr_retries): self.app.attempt=r try: with open(fname): pass success = True break except IOError: # wait for a file logging.debug("File %s not found! Retrying [%d/%d] .." % (fname, r + 1, self.nr_retries)) self.app.sync() if success is False: logging.error("Failed to access file: %s" % fname) return False logging.debug("Total dirs: %d, files: %d" % (total_dirs, total_files)) return True def local_tree_create_and_move(self, l_tree): """ create a folder and fill with content randomly select an existing folder and create a subfolder of it move the first folder to the newly created one compare results, repeat 10 times return True if success """ for _ in range(0, self.nr_dirs): # Create a dir l_dir = [] dname, ddname, l_files, l_dirs = self.local_tree_create_dir("") if dname is None: return False logging.debug("Directory created: %s" % ddname) l_dir.append({"name":dname, "fname":ddname, "files":l_files, "dirs":l_dirs, "child":None}) # wait for a sync and compare if not self.local_tree_compare(l_dir): return False # select random existing folder dir_dicts_l = [d for d in self.local_tree_get_dirs(l_tree)] dd = random.choice(dir_dicts_l) if dd["dirs"] is None: dd["dirs"] = [] # create a new subfolder strlen = random.randint(10, 20) dname = get_random_str(size=strlen) ddname = os.path.join(dd["fname"], dname) dname_real = os.path.join(self.app.local_folder_in, ddname) logging.debug("Creating new dir: %s, parent: %s" % (ddname, dd["fname"])) try: os.makedirs(dname_real) except OSError, e: logging.error("Failed to create directory: %s (%s)" % (dname_real, e)) return False dd["dirs"].append({"name":dname, "fname":ddname, "files":None, "dirs":None, "child":None}) # move existing folder into newly created folder old_name = os.path.join(self.app.local_folder_in, l_dir[0]["fname"]) new_name = os.path.join(dname_real, l_dir[0]["name"]) logging.debug("Moving %s to %s" % (old_name, new_name)) try: shutil.move(old_name, new_name) except OSError, e: logging.error("Failed to move dir: %s to new: %s (%s)" % (old_name, new_name, e)) return False # fix existing dir dict new_ffname = os.path.join(ddname, l_dir[0]["name"]) l_dir[0]["fname"] = new_ffname for f in l_dir[0]["files"]: f["ffname"] = os.path.join(new_ffname, f["name"]) for d in l_dir[0]["dirs"]: d["ffname"] = os.path.join(new_ffname, d["name"]) # wait for a sync and compare if not self.local_tree_compare(l_tree): return False # all good ! return True def local_tree_multiple_renames(self, l_tree): """ perform several object renames then rename back to the original name """ # rename dirs for _ in range(0, self.nr_changes): # select random existing folder dir_dicts_l = [d for d in self.local_tree_get_dirs(l_tree)] dd = random.choice(dir_dicts_l) if dd["dirs"] is None: continue oname = dd["dirs"][0]["name"] odname = os.path.join(dd["fname"], oname) orig_name = os.path.join(self.app.local_folder_in, odname) prev_name = orig_name # rename 10 times for _ in range(0, self.nr_changes): strlen = random.randint(10, 20) dname = get_random_str(size=strlen) ddname = os.path.join(dd["fname"], dname) dname_real = os.path.join(self.app.local_folder_in, ddname) logging.debug("Renaming %s to %s" % (prev_name, dname_real)) if not self.file_rename(prev_name, dname_real): return False prev_name = dname_real # rename back to origin logging.debug("Moving %s to %s" % (prev_name, orig_name)) if not self.file_rename(prev_name, orig_name): return False self.app.sync() # wait for a sync and compare if not self.local_tree_compare(l_tree): return False # rename files for _ in range(0, self.nr_changes): # select random existing folder dir_dicts_l = [d for d in self.local_tree_get_dirs(l_tree)] dd = random.choice(dir_dicts_l) if dd["files"] is None: continue oname = dd["files"][0]["name"] odname = os.path.join(dd["fname"], oname) orig_name = os.path.join(self.app.local_folder_in, odname) prev_name = orig_name # rename 10 times for _ in range(0, self.nr_changes): strlen = random.randint(10, 20) dname = get_random_str(size=strlen) ddname = os.path.join(dd["fname"], dname) dname_real = os.path.join(self.app.local_folder_in, ddname) logging.debug("Renaming %s to %s" % (prev_name, dname_real)) if not self.file_rename(prev_name, dname_real): return False prev_name = dname_real # rename back to origin logging.debug("Moving %s to %s" % (prev_name, orig_name)) if not self.file_rename(prev_name, orig_name): return False self.app.sync() # wait for a sync and compare if not self.local_tree_compare(l_tree): return False return True �������������������������������������������������������������������������������sdk-10.11.0/tests/python/sync_test_megacli.py�������������������������������������������������������0000664�0000000�0000000�00000014357�15162662266�0021212�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Application for testing syncing algorithm (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ import sys import os import time import shutil import unittest import xmlrunner import subprocess import re from sync_test_app import SyncTestApp from sync_test import SyncTest import logging import argparse class SyncTestMegaCliApp(SyncTestApp): """ operates with megacli application """ def __init__(self, local_mount_in, local_mount_out, delete_tmp_files=True, use_large_files=True, check_if_alive=True): """ local_mount_in: local upsync folder local_mount_out: local downsync folder """ self.work_dir = os.path.join(".", "work_dir") SyncTestApp.__init__(self, local_mount_in, local_mount_out, self.work_dir, delete_tmp_files, use_large_files) self.check_if_alive = check_if_alive def sync(self): time.sleep(5) def start(self): # try to create work dir return True def finish(self): try: shutil.rmtree(self.work_dir) except OSError, e: logging.error("Failed to remove dir: %s (%s)" % (self.work_dir, e)) def is_alive(self): """ return True if application instance is running """ if not self.check_if_alive: return True s = subprocess.Popen(["ps", "axw"], stdout=subprocess.PIPE) for x in s.stdout: if re.search("megacli", x): return True return False def pause(self): """ pause application """ # TODO: implement this ! raise NotImplementedError("Not Implemented !") def unpause(self): """ unpause application """ # TODO: implement this ! raise NotImplementedError("Not Implemented !") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--test1", help="test_create_delete_files", action="store_true") parser.add_argument("--test2", help="test_create_rename_delete_files", action="store_true") parser.add_argument("--test3", help="test_create_delete_dirs", action="store_true") parser.add_argument("--test4", help="test_create_rename_delete_dirs", action="store_true") parser.add_argument("--test5", help="test_sync_files_write", action="store_true") parser.add_argument("--test6", help="test_local_operations", action="store_true") parser.add_argument("--test7", help="test_update_mtime", action="store_true") parser.add_argument("--test8", help="test_create_rename_delete_unicode_files_dirs", action="store_true") parser.add_argument("--test9", help="test_create_move_delete_files", action="store_true") parser.add_argument("--test10", help="test_mimic_update_with_backup_files", action="store_true") parser.add_argument("-a", "--all", help="run all tests", action="store_true") parser.add_argument("-b", "--basic", help="run basic, stable tests", action="store_true") parser.add_argument("-d", "--debug", help="use debug output", action="store_true") parser.add_argument("-l", "--large", help="use large files for testing", action="store_true") parser.add_argument("-n", "--nodelete", help="Do not delete work files", action="store_false") parser.add_argument("-c", "--check", help="Do not check if megacli is running (useful, if other application is used for testing)", action="store_false") parser.add_argument("upsync_dir", help="local upsync directory") parser.add_argument("downsync_dir", help="local downsync directory") args = parser.parse_args() if args.debug: lvl = logging.DEBUG else: lvl = logging.INFO if args.all: args.test1 = args.test2 = args.test3 = args.test4 = args.test5 = args.test6 = args.test7 = args.test8 = args.test9 = args.test10 = True if args.basic: args.test1 = args.test2 = args.test3 = args.test4 = True logging.StreamHandler(sys.stdout) logging.basicConfig(format='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl) logging.info("") logging.info("1) Start the first [megacli] and run the following command: sync " + args.upsync_dir + " [remote folder]") logging.info("2) Start the second [megacli] and run the following command: sync " + args.downsync_dir + " [remote folder]") logging.info("3) Wait for both folders get fully synced") logging.info("4) Run: python %s", sys.argv[0]) logging.info("") logging.info(" Make sure you have unittest module installed: pip install unittest-xml-reporting") logging.info("") time.sleep(5) with SyncTestMegaCliApp(args.upsync_dir, args.downsync_dir, args.nodelete, args.large, args.check) as app: suite = unittest.TestSuite() if args.test1: suite.addTest(SyncTest("test_create_delete_files", app)) if args.test2: suite.addTest(SyncTest("test_create_rename_delete_files", app)) if args.test3: suite.addTest(SyncTest("test_create_delete_dirs", app, )) if args.test4: suite.addTest(SyncTest("test_create_rename_delete_dirs", app)) if args.test5: suite.addTest(SyncTest("test_sync_files_write", app)) if args.test6: suite.addTest(SyncTest("test_local_operations", app)) if args.test7: suite.addTest(SyncTest("test_update_mtime", app)) if args.test8: suite.addTest(SyncTest("test_create_rename_delete_unicode_files_dirs", app)) if args.test9: suite.addTest(SyncTest("test_create_move_delete_files", app)) if args.test10: suite.addTest(SyncTest("test_mimic_update_with_backup_files", app)) testRunner = xmlrunner.XMLTestRunner(output='test-reports') testRunner.run(suite) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/python/unicode_filenames.py�������������������������������������������������������0000664�0000000�0000000�00000005673�15162662266�0021170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������""" Application for generating random Unicode filenames (c) 2013-2014 by Mega Limited, Wellsford, New Zealand This file is part of the MEGA SDK - Client Access Engine. Applications using the MEGA API must present a valid application key and comply with the the rules set forth in the Terms of Service. The MEGA SDK is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. @copyright Simplified (2-clause) BSD License. You should have received a copy of the license along with this program. """ # python unicode_filenames.py -o out -p -c 100 -m 0xFF -l 1 import random import os import platform import string import sys import argparse def touch(path): """ create an empty file update utime """ with open(path, 'a'): os.utime(path, None) def gen_name(l, exclude, max_val): """ Generate random string of size l """ name = u"" while len(name) < l: c = unichr(random.randint(0, max_val)) if c not in exclude: name = name + c return name class hexact(argparse.Action): 'An argparse.Action that handles hex string input' def __call__(self, parser, namespace, values, option_string=None): base = 10 if '0x' in values: base = 16 setattr(namespace, self.dest, int(values, base)) return if __name__ == "__main__": parser = argparse.ArgumentParser(description="Create files with random Unicode filenames.") parser.add_argument("-c", "--file-count", help="How many files to create, default is 10000 (not checking for unique names)", default=1000, type=int) parser.add_argument("-l", "--filename-length", help="Generated filenames length, default is 20", default=20, type=int) parser.add_argument("-m", "--max-unicode-char", help="Maximal Unicode character, default is 0xFFFF", action=hexact, default=0xFFFF) parser.add_argument("-o", "--output-dir", help="Output dir (required)") parser.add_argument("-p", "--toprint", help="print generated filename to stdout", action="store_true") args = parser.parse_args() if args.output_dir is None: parser.print_help() sys.exit(1) try: os.makedirs(args.output_dir) except OSError, e: pass if platform.system() == "Windows": # Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t). exclude = string.punctuation + u"\t" + u''.join([unichr(x) for x in range(0, 32)]) else: # find . -exec rm {} \; exclude = u"/" + u"." + u''.join([unichr(x) for x in range(0, 1)]) for _ in range(0, args.file_count): s = gen_name(args.filename_length, exclude, args.max_unicode_char) fname = os.path.join(args.output_dir, s) if args.toprint: print fname.encode('utf-8') touch(fname) ���������������������������������������������������������������������sdk-10.11.0/tests/sdk_test_data_provider.cpp��������������������������������������������������������0000664�0000000�0000000�00000006404�15162662266�0021044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file tests/integration/sdk_test_data_provider.cpp * @brief Mega SDK test file * * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * You should have received a copy of the license along with this * program. */ #include "sdk_test_data_provider.h" #include "mega/logging.h" #include <curl/curl.h> #include <fstream> #include <memory> namespace { // cURL Callback function to write downloaded data to a stream // See https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html // See https://github.com/curl/curl/pull/9874 returning CURL_WRITEFUNC_ERROR // is better than 0 on errors. size_t writeData(void* ptr, size_t size, size_t nmemb, std::ofstream* stream) { if (stream->write((char*)ptr, static_cast<std::streamsize>(size * nmemb))) { return size * nmemb; } else { #ifdef CURL_WRITEFUNC_ERROR return CURL_WRITEFUNC_ERROR; #else return 0; #endif } } /** * @brief Download a file from a URL using cURL * * @param url The URL of the File * @param dstPath The destination file path to write * @return True if the file was downloaded successfully, otherwise false */ bool getFileFromURL(const std::string& url, const fs::path& dstPath) { auto curlCleaner = [](CURL* curl) { curl_easy_cleanup(curl); }; // Initialize libcurl std::unique_ptr<CURL, decltype(curlCleaner)> curl{curl_easy_init(), curlCleaner}; if (!curl) { LOG_err << "Failed to initialize libcurl"; return false; } // Open file to save downloaded data std::ofstream ofs(dstPath, std::ios::binary | std::ios::out); if (!ofs) { LOG_err << "Error opening file for writing:" << path_u8string(dstPath); return false; } // Download curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeData); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &ofs); CURLcode res = curl_easy_perform(curl.get()); if (res != CURLE_OK) { LOG_err << "curl_easy_perform() failed: " << curl_easy_strerror(res); return false; } // Close file ofs.close(); if (!ofs) { LOG_verbose << "Error closing file:" << path_u8string(dstPath); return false; } LOG_verbose << "File " << path_u8string(dstPath) << " downloaded successfully"; return true; } } bool getFileFromArtifactory(const std::string& relativeUrl, const fs::path& dstPath) { static const std::string baseUrl{ "https://artifactory.developers.mega.co.nz:443/artifactory/sdk"}; // Join base URL and relatvie URL bool startedWithBackSlash = !relativeUrl.empty() && relativeUrl[0] == '/'; std::string seperator = startedWithBackSlash ? "" : "/"; const auto absoluateUrl = baseUrl + seperator + relativeUrl; return getFileFromURL(absoluateUrl, dstPath); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/sdk_test_data_provider.h����������������������������������������������������������0000664�0000000�0000000�00000002135�15162662266�0020506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file tests/integration/sdk_test_data_provider.h * @brief Mega SDK test file * * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "stdfs.h" #include <string> /** * @brief Download a file from the Artifactory * * @param relativeUrl The relative URL to the base URL "https://artifactory.developers.mega.co.nz:443/artifactory/sdk/" * @param dstPath The destination file path to write * @return True if the file was downloaded successfully, otherwise false */ bool getFileFromArtifactory(const std::string& relativeUrl, const fs::path& dstPath); �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/sdk_test_utils.cpp����������������������������������������������������������������0000664�0000000�0000000�00000024460�15162662266�0017363�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "sdk_test_utils.h" #include "mega/logging.h" #include "mega/types.h" #include <fstream> #include <random> #include <string_view> #include <vector> namespace sdk_test { static fs::path executableDir; fs::path getTestDataDir() { return executableDir; } void setTestDataDir(const fs::path& dataDir) { executableDir = dataDir; } void copyFileFromTestData(fs::path filename, fs::path destination) { fs::path source = getTestDataDir() / filename; if (fs::is_directory(destination)) { destination = destination / filename; } if (fs::exists(destination)) { if (fs::equivalent(source, destination)) { return; } fs::remove(destination); } fs::copy_file(source, destination); } std::string hashFile(const fs::path& filePath) { std::ifstream in{filePath, std::ios::binary}; if (!in) throw std::runtime_error("Cannot open file for hashing: " + filePath.string()); ::mega::HashSHA256 hasher; constexpr std::size_t CHUNK = 64 * 1024; std::vector<::mega::byte> buffer(CHUNK); while (in.good()) { in.read(reinterpret_cast<char*>(buffer.data()), static_cast<std::streamsize>(buffer.size())); const auto n = static_cast<unsigned>(in.gcount()); if (n > 0) hasher.add(buffer.data(), n); } std::string digest; hasher.get(&digest); return digest; } std::string hashFileHex(const fs::path& filePath) { const auto bin = hashFile(filePath); std::ostringstream out; out << std::hex << std::setfill('0'); for (const unsigned char ch: bin) out << std::setw(2) << static_cast<int>(ch); return out.str(); } void createRandomFile(const fs::path& filePath, const std::size_t fileSizeBytes) { std::ofstream out(filePath, std::ios::binary | std::ios::trunc); if (!out) throw std::runtime_error("Cannot open file: " + filePath.string()); constexpr auto CHUNK = std::size_t{64 * 1024}; std::vector<char> buffer; buffer.reserve(CHUNK); std::mt19937_64 rng{std::random_device{}()}; constexpr int minReadableChar = 32; constexpr int maxReadableChar = 126; std::uniform_int_distribution<int> dist(minReadableChar, maxReadableChar); for (auto remaining = fileSizeBytes; remaining > 0;) { std::size_t toWrite = std::min(remaining, CHUNK); buffer.clear(); for (std::size_t i = 0; i < toWrite; ++i) buffer.push_back(static_cast<char>(dist(rng))); out.write(buffer.data(), static_cast<std::streamsize>(toWrite)); remaining -= toWrite; } } void createFile(const fs::path& filePath, const size_t fileSizeBytes) { writeFileContent<std::size_t>(filePath, std::ios::binary, fileSizeBytes); } void createFile(const fs::path& filePath, const std::string_view contents, std::optional<fs::file_time_type> customMtime) { writeFileContent<std::string_view>(filePath, std::ios::binary, contents); if (customMtime) { std::error_code ec; fs::last_write_time(filePath, *customMtime, ec); if (ec) { throw std::runtime_error("Failed to set mtime: " + ec.message()); } } } void appendToFile(const fs::path& filePath, const size_t bytesToAppend) { writeFileContent<std::size_t>(filePath, std::ios::binary | std::ios::app, bytesToAppend); } void appendToFile(const fs::path& filePath, const std::string_view contents) { writeFileContent<std::string_view>(filePath, std::ios::binary | std::ios::app, contents); } LocalTempFile::LocalTempFile(const fs::path& _filePath, const size_t fileSizeBytes): mFilePath(_filePath) { createRandomFile(mFilePath, fileSizeBytes); } LocalTempFile::LocalTempFile(const fs::path& _filePath, const std::string_view contents, std::optional<fs::file_time_type> customMtime): mFilePath(_filePath) { createFile(mFilePath, contents, customMtime); } void LocalTempFile::appendData(const size_t bytesToAppend) const { appendToFile(mFilePath, bytesToAppend); } void LocalTempFile::appendData(const std::string_view contentsToAppend) const { appendToFile(mFilePath, contentsToAppend); } std::error_code LocalTempFile::move(const fs::path& newPath) { std::error_code ec; fs::rename(mFilePath, newPath, ec); if (!ec) { mFilePath = newPath; } else { LOG_err << "Failed to move file from " << path_u8string(mFilePath) << " to " << path_u8string(newPath) << ". Error: " << ec.message(); } return ec; } LocalTempFile::~LocalTempFile() { std::error_code ec; fs::remove(mFilePath, ec); if (ec) { LOG_err << "Error removing file: ec: " << ec.value() << " msg: " << ec.message(); } } FileNodeInfo::FileNodeInfo(const std::string& _name, const std::optional<unsigned int>& _label, const bool _fav, const unsigned int _size, const std::chrono::seconds _secondsSinceMod, const bool _sensitive, const std::string& _description, const std::set<std::string>& _tags): NodeCommonInfo{_name, _label, _fav, _sensitive, _description, _tags}, size(_size) { using std::chrono::system_clock; static const int64_t refTime = system_clock::to_time_t(system_clock::now()); if (auto nSecs = _secondsSinceMod.count(); nSecs != 0) { mtime = refTime - nSecs; } } LocalTempDir::LocalTempDir(const fs::path& _dirPath): mDirPath(_dirPath) { if (fs::exists(mDirPath)) { // We avoid creation in this case to avoid unintentional removals const auto msg = "Directory already exists: " + _dirPath.string(); LOG_err << msg; throw std::runtime_error(msg); } fs::create_directories(mDirPath); } LocalTempDir::~LocalTempDir() { std::error_code ec; fs::remove_all(mDirPath, ec); if (ec) { LOG_err << "Error removing directory: ec: " << ec.value() << " msg: " << ec.message(); } } bool LocalTempDir::move(const fs::path& newLocation) { if (std::filesystem::exists(newLocation)) { LOG_err << "Moving " << mDirPath.string() << " to " << newLocation.string() << " will overwrite the target path. Romove it before proceeding with the operation."; return false; } try { std::filesystem::rename(mDirPath, newLocation); } catch (const std::exception& e) { LOG_err << "Error moving directory from " << mDirPath.string() << " to " << newLocation.string() << ". Error: " << e.what(); return false; } mDirPath = newLocation; return true; } namespace { // Needed forward declaration for recursive call void processDirChildName(const DirNodeInfo&, std::vector<std::string>&); /** * @brief Put the name of the node and their children (if any) in the given vector */ void processNodeName(const NodeInfo& node, std::vector<std::string>& names) { std::visit( [&names](const auto& arg) { names.push_back(arg.name); if constexpr (std::is_same_v<decltype(arg), const DirNodeInfo&>) { processDirChildName(arg, names); } }, node); } /** * @brief Aux function to call inside the visitor. Puts the names of the children in names. */ void processDirChildName(const DirNodeInfo& dir, std::vector<std::string>& names) { std::for_each(dir.childs.begin(), dir.childs.end(), [&names](const auto& child) { processNodeName(child, names); }); } } std::vector<std::string> DirNodeInfo::getChildrenNames() const { std::vector<std::string> result; result.reserve(childs.size()); std::transform(std::begin(childs), std::end(childs), std::back_inserter(result), [](const auto& child) -> std::string { return getNodeName(child); }); return result; } std::vector<std::string> getNodeNames(const NodeInfo& node) { std::vector<std::string> result; processNodeName(node, result); return result; } std::string getNodeName(const NodeInfo& node) { return std::visit( [](const auto& n) -> std::string { return n.name; }, node); } std::vector<std::string> getLocalFirstChildrenNames_if(const std::filesystem::path& localPath, std::function<bool(const std::string&)> filter) { if (!std::filesystem::is_directory(localPath)) return {}; std::vector<std::string> result; const auto pushName = [&result, &filter](const std::filesystem::path& path) { const auto name = path.filename().string(); if (!filter || filter(name)) result.emplace_back(std::move(name)); }; std::filesystem::directory_iterator children{localPath}; std::for_each(begin(children), end(children), pushName); return result; } std::vector<ChildNameAndFingerprint> getLocalFirstChildrenNamesAndFingerprints_if(::mega::MegaApi* megaApi, const std::filesystem::path& localPath, std::function<bool(const std::string&)> filter) { if (!std::filesystem::is_directory(localPath)) return {}; std::vector<ChildNameAndFingerprint> result; const auto pushNameAndFp = [&result, &filter, megaApi](const std::filesystem::path& path) { const auto name = path.filename().string(); if (!filter || filter(name)) { std::optional<std::string> fingerprint; std::error_code ec; if (std::filesystem::is_regular_file(path, ec) && !ec) { std::unique_ptr<char[]> fp{megaApi->getFingerprint(path_u8string(path).c_str())}; fingerprint = std::string(fp.get()); } result.emplace_back(std::move(name), std::move(fingerprint)); } }; std::filesystem::directory_iterator children{localPath}; std::for_each(begin(children), end(children), pushNameAndFp); return result; } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/sdk_test_utils.h������������������������������������������������������������������0000664�0000000�0000000�00000037756�15162662266�0017044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#ifndef INCLUDE_TESTS_SDK_TEST_UTILS_H_ #define INCLUDE_TESTS_SDK_TEST_UTILS_H_ #include "megaapi.h" #include "stdfs.h" #include <algorithm> #include <atomic> #include <fstream> #include <functional> #include <future> #include <optional> #include <set> #include <thread> #include <type_traits> #include <variant> #include <vector> namespace sdk_test { /** * @brief Opens a file with the given mode, and writes either "size" zeros or the given "contents". * * @tparam T A type that indicates the data: either std::size_t or std::string_view. * @param filePath Path to open * @param openMode e.g. std::ios::binary or std::ios::binary | std::ios::app * @param data The data to write. If T = std::size_t, we write that many zero bytes. * If T = std::string_view or std::string, we write those bytes. */ template<typename T> void writeFileContent(const fs::path& filePath, const std::ios::openmode openMode, const T& data) { std::ofstream outFile(filePath, openMode); if (!outFile.is_open()) { const auto msg = "Cannot open file: " + filePath.string(); throw std::runtime_error(msg); } if constexpr (std::is_convertible_v<T, std::size_t>) { // data is a byte count -> write that many zeros const std::vector<char> buffer(data, 0); outFile.write(buffer.data(), static_cast<std::streamsize>(buffer.size())); } else if constexpr (std::is_same_v<T, std::string_view> || std::is_same_v<T, std::string>) { // data is a string -> write these contents outFile.write(data.data(), static_cast<std::streamsize>(data.size())); } else { static_assert(!sizeof(T*), "writeFileContent used with invalid type"); } } /** * @brief Returns the path to the folder containing resources for the tests. * * IMPORTANT: `setTestDataDir` must be called before. */ fs::path getTestDataDir(); /** * @brief Sets the path to the folder where the test resources are located. Usually called from the main function. * It should be set before calling getTestDataDir or copyFileFromTestData * * Example: * setTestDataDir(fs::absolute(fs::path(argv[0]).parent_path())); * */ void setTestDataDir(const fs::path& dataDir); /** * @brief Copies file from the resources data directory to the given destination (current working directory by default). * IMPORTANT: `setTestDataDir` must be called before. */ void copyFileFromTestData(fs::path filename, fs::path destination = "."); /** * @brief Returns a SHA-256 hash of the file. */ std::string hashFile(const fs::path& filePath); /** * @brief Returns a SHA-256 hash of the file with a human-readable format. */ std::string hashFileHex(const fs::path& filePath); /** * @brief Creates a file of a given size with random, printable data. It throws if the file cannot * be opened. */ void createRandomFile(const fs::path& filePath, const size_t fileSizeBytes); /** * @brief Creates a file of a given size. It throws if the file cannot be opened */ void createFile(const fs::path& filePath, const size_t fileSizeBytes); /** * @brief Creates a file with the given contents. It throws if the file cannot be opened */ void createFile(const fs::path& filePath, const std::string_view contents, std::optional<fs::file_time_type> customMtime = std::nullopt); /** * @brief Appends data to a file with the given contents. It throws if the file cannot be opened */ void appendToFile(const fs::path& filePath, const size_t bytesToAppend); /** * @brief Appends data to a file with the given contents. It throws if the file cannot be opened */ void appendToFile(const fs::path& filePath, const std::string_view contentsToAppend); /** * @class LocalTempFile * @brief Helper class to apply RAII when creating a file locally */ class LocalTempFile { public: LocalTempFile(const fs::path& _filePath, const size_t fileSizeBytes); LocalTempFile(const fs::path& _filePath, const std::string_view contents, std::optional<fs::file_time_type> customMtime = std::nullopt); ~LocalTempFile(); // Delete copy constructors -> Don't allow many objects to remove the same file LocalTempFile(const LocalTempFile&) = delete; LocalTempFile& operator=(const LocalTempFile&) = delete; // Allow move operations LocalTempFile(LocalTempFile&&) noexcept = default; LocalTempFile& operator=(LocalTempFile&&) noexcept = default; const fs::path& getPath() const { return mFilePath; } /* * @brief Appends bytesToAppend of data to the file. * @see appendToFile() */ void appendData(const size_t bytesToAppend) const; /* * @brief Appends contents to the file. * @see appendToFile() */ void appendData(const std::string_view contentsToAppend) const; /** * @brief Move/rename the file to the given path. * File path is updated on success. * @return std::error_code The error code if the operation failed. */ std::error_code move(const fs::path& newPath); private: fs::path mFilePath; }; /** * @class LocalTempDir * @brief Helper class to apply RAII when creating a directory locally */ class LocalTempDir { public: LocalTempDir(const fs::path& _dirPath); ~LocalTempDir(); // Delete copy constructors -> Don't allow many objects to remove the same dir LocalTempDir(const LocalTempDir&) = delete; LocalTempDir& operator=(const LocalTempDir&) = delete; // Allow move operations LocalTempDir(LocalTempDir&&) noexcept = default; LocalTempDir& operator=(LocalTempDir&&) noexcept = default; const fs::path& getPath() const { return mDirPath; } /** * @brief Move the current temp dir to the given location * * @return true if the operation succeeded, false there was an error or if there is already a * file/directory in the given newLocation. */ bool move(const fs::path& newLocation); private: fs::path mDirPath; }; /** * @class NodeCommonInfo * @brief Common information shared both by files and directories * * This struct and the inherited ones implement the builder pattern so it is easy to create custom * nodes with the required fields. */ template<typename Derived> struct NodeCommonInfo { std::string name; std::optional<unsigned int> label = std::nullopt; // e.g. MegaNode::NODE_LBL_PURPLE bool fav = false; bool sensitive = false; std::string description; std::set<std::string> tags; Derived& setName(const std::string& _name) { name = _name; return static_cast<Derived&>(*this); } Derived& setLabel(const std::optional<unsigned int>& _label) { label = _label; return static_cast<Derived&>(*this); } Derived& setFav(const bool _fav) { fav = _fav; return static_cast<Derived&>(*this); } Derived& setSensitive(const bool _sensitive) { sensitive = _sensitive; return static_cast<Derived&>(*this); } Derived& setDescription(const std::string& _description) { description = _description; return static_cast<Derived&>(*this); } Derived& setTags(const std::set<std::string>& _tags) { tags = _tags; return static_cast<Derived&>(*this); } Derived& addTag(const std::string& _tag) { tags.insert(_tag); return static_cast<Derived&>(*this); } }; /** * @class FileNodeInfo * @brief Struct holding all the relevant information a file node can have */ struct FileNodeInfo: public NodeCommonInfo<FileNodeInfo> { // Same value as in megaapi.h static constexpr int64_t INVALID_CUSTOM_MOD_TIME = -1; unsigned int size = 0; int64_t mtime = INVALID_CUSTOM_MOD_TIME; FileNodeInfo(const std::string& _name, const std::optional<unsigned int>& _label = std::nullopt, const bool _fav = false, const unsigned int _size = 0, const std::chrono::seconds _secondsSinceMod = {}, const bool _sensitive = false, const std::string& _description = {}, const std::set<std::string>& _tags = {}); FileNodeInfo& setSize(const unsigned int _size) { size = _size; return *this; } FileNodeInfo& setMtime(const std::chrono::seconds _secondsSinceMod) { mtime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now() - _secondsSinceMod); return *this; } FileNodeInfo& setMtime(const std::chrono::system_clock::time_point _timePoint) { mtime = std::chrono::system_clock::to_time_t(_timePoint); return *this; } }; struct DirNodeInfo; using NodeInfo = std::variant<FileNodeInfo, DirNodeInfo>; /** * @class DirNodeInfo * @brief Struct holding all the relevant information a directory node can have */ struct DirNodeInfo: public NodeCommonInfo<DirNodeInfo> { std::vector<NodeInfo> childs{}; DirNodeInfo(const std::string& _name, const std::vector<NodeInfo>& _childs = {}, const std::optional<unsigned int>& _label = std::nullopt, const bool _fav = false, const bool _sensitive = false, const std::string& _description = {}, const std::set<std::string>& _tags = {}): NodeCommonInfo{_name, _label, _fav, _sensitive, _description, _tags}, childs(_childs) {} DirNodeInfo& addChild(const NodeInfo& child) { childs.emplace_back(child); return *this; } /** * @brief Returns a vector with the names of the firs successors of the directory */ std::vector<std::string> getChildrenNames() const; }; /** * @brief Returns the names in the tree specified by node * * @note The tree is iterated using a depth-first approach */ std::vector<std::string> getNodeNames(const NodeInfo& node); /** * @brief Get the name of the give node */ std::string getNodeName(const NodeInfo& node); /** * @brief Waits for a condition to become true or until a timeout occurs. * * This function repeatedly checks a predicate at intervals (controlled by sleepDuration) * and stops when the predicate returns true or when the specified timeout has been reached. * * @tparam Duration The type of the timeout duration (e.g., std::chrono::milliseconds, * std::chrono::seconds). * @tparam SleepDuration The type of the sleep interval between predicate checks (default is * std::chrono::milliseconds). * @param predicate The condition to be evaluated. It should be a callable returning a bool. * @param timeout The maximum duration to wait for the predicate to return true. * @param sleepDuration The interval between each check of the predicate. Defaults to 100 * milliseconds. * * @return true if the predicate returns true within the timeout period, otherwise false. * * @note The predicate will be evaluated at least once, and then at intervals of `sleepDuration`. * * @example * using namespace std::chrono_literals * bool conditionMet = waitFor( * [] { return some_condition(); }, * 5s, * 200ms * ); */ template<typename Duration, typename SleepDuration = std::chrono::milliseconds> bool waitFor(const std::function<bool()>& predicate, Duration timeout, SleepDuration sleepDuration = std::chrono::milliseconds(100)) { auto startTime = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - startTime < timeout) { if (predicate()) return true; std::this_thread::sleep_for(sleepDuration); } return false; } /** * @brief Get the names of the files/directories that are contained within the given path. * * Note: if the path does not point to a directory, an empty vector is returned * * @param localPath The path to evaluate their children * @param filter Required named-based condition to be included in the results. * @return A vector with the names of the children */ std::vector<std::string> getLocalFirstChildrenNames_if(const std::filesystem::path& localPath, std::function<bool(const std::string&)> filter = nullptr); using ChildNameAndFingerprint = std::pair<std::string, std::optional<std::string>>; /** * @brief Get the names and fingerprints of the files/directories that are contained within the * given path. * * Note: if the path does not point to a directory, an empty vector is returned * * @param megaApi Api to be used to calculate the local file fingerprint. * @param localPath The path to evaluate their children * @param filter Required named-based condition to be included in the results. * @return A vector with the names and fingerprints of the children */ std::vector<ChildNameAndFingerprint> getLocalFirstChildrenNamesAndFingerprints_if( ::mega::MegaApi* megaApi, const std::filesystem::path& localPath, std::function<bool(const std::string&)> filter = nullptr); } /** * @struct SyncUploadOperationsTracker * @brief Tracks SyncListener callbacks for a specific local file. */ struct SyncUploadOperationsTracker { private: std::string absolutePathStr; std::atomic<bool> actionCompleted; std::unique_ptr<std::promise<int>> actionCompletedPms; std::unique_ptr<std::future<int>> actionCompletedFut; public: SyncUploadOperationsTracker(const std::string& p): absolutePathStr(p), actionCompleted(false), actionCompletedPms(new std::promise<int>()), actionCompletedFut(new std::future<int>(actionCompletedPms->get_future())) {} const std::string& getPath() const { return absolutePathStr; } void setActionCompleted() { actionCompleted.store(true); } bool getActionCompleted() { return actionCompleted.load(); } void setActionCompletedPms(const int v) { actionCompletedPms->set_value(v); } std::pair<std::future_status, int> waitForCompletion(std::chrono::milliseconds timeout) { auto status = actionCompletedFut->wait_for(timeout); auto errCode = status == std::future_status::ready ? actionCompletedFut->get() : -1 /*API_EINTERNAL*/; return {status, errCode}; } }; /** * @struct SyncUploadOperationsTransferTracker * @brief Tracks Transfer listener callbacks for a specific local file. */ struct SyncUploadOperationsTransferTracker: public SyncUploadOperationsTracker { std::atomic<int> transferStartCount; SyncUploadOperationsTransferTracker(const std::string& p): SyncUploadOperationsTracker(p), transferStartCount(0) {} }; /** * @class SyncItemTrackerManager * @brief Generic manager for any tracker type that provides absolutePathStr. * * @tparam T Tracker type (must contain std::string absolutePathStr). */ template<typename T> class SyncItemTrackerManager { public: /** * @brief Creates and stores a tracker of type T for the given absolute path. * * @param path Absolute filesystem path whose events should be tracked. * @return A shared pointer to the created tracker. */ std::shared_ptr<T> add(const std::string& path) { auto tracker = std::make_shared<T>(path); mTrackers.emplace_back(tracker); return tracker; } /** * @brief Retrieves a tracker by its associated absolute path. * * @param path Absolute filesystem path used to locate the tracker. * @return A shared pointer to the tracker, or nullptr if not found. */ std::shared_ptr<T> getByPath(const std::string& path) const { std::shared_ptr<T> result; std::for_each(mTrackers.begin(), mTrackers.end(), [&](const std::shared_ptr<T>& tracker) { if (tracker && tracker->getPath() == path) { result = tracker; } }); return result; } private: std::vector<std::shared_ptr<T>> mTrackers; }; #endif // INCLUDE_TESTS_SDK_TEST_UTILS_H_ ������������������sdk-10.11.0/tests/stdfs.h���������������������������������������������������������������������������0000664�0000000�0000000�00000003030�15162662266�0015101�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// tests/stdfs.h #pragma once #include <filesystem> #include <string> #include <type_traits> namespace fs = std::filesystem; // --- UTF-8 helpers ----------------------------------------------------------- // Always return UTF-8 bytes as std::string, regardless of standard/library. inline std::string path_u8string(const fs::path& p) { #if defined(__cpp_char8_t) // C++20+: u8string() = std::u8string auto u8 = p.u8string(); // std::u8string return std::string(reinterpret_cast<const char*>(u8.data()), u8.size()); #else // C++17: u8string() = std::string return p.u8string(); #endif } // C++17/20 compatible replacement for fs::u8path(...) to silence deprecation inline fs::path u8path_compat(const char* s) { #if defined(__cpp_char8_t) return fs::path(reinterpret_cast<const char8_t*>(s)); #else return fs::u8path(s); #endif } inline fs::path u8path_compat(const std::string& s) { #if defined(__cpp_char8_t) return fs::path(reinterpret_cast<const char8_t*>(s.c_str())); #else return fs::u8path(s); #endif } #if defined(__cpp_char8_t) // Convert a u8"" literal to std::string (byte-preserving). inline std::string u8_to_std_string(const char8_t* s) { return std::string(reinterpret_cast<const char*>(s)); } // U8("…") -> std::string with the same bytes, on C++20+ (token-paste to avoid spaces) #define U8(x) u8_to_std_string(u8##x) #else // On C++17, narrow literals are already UTF-8 in our builds; just copy. inline std::string u8_to_std_string(const char* s) { return std::string(s); } #define U8(x) x #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/tool/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014566�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/tool/cmds.txt���������������������������������������������������������������������0000664�0000000�0000000�00000001130�15162662266�0016250�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# scenario describes action with an object (file or directory) # valid commands: # create moveout movein rename filldir delete sleep # # [create] randomly creates file or directory with a random name # [moveout] moves object out of sync tree # [movein] moves object into sync tree # [rename] gives a new random name # [filldir] if a directory: creates a random number of files (up to 100) # [delete] deletes object # [sleep] sleeps for 2 secs # # scenario must start with [create] create moveout movein rename rename rename rename #filldir sleep moveout sleep sleep sleep movein rename sleep rename ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/tool/compare_ops_in_two_folders.sh������������������������������������������������0000775�0000000�0000000�00000005507�15162662266�0022540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # This is a script that performs the same operations in two folders # and compares them. # It can be used to test a mounted ftp/webdav folder vs a local one f1=$1 shift f2=$1 shift if [[ "x$f1" == "x" || "x$f2" == "x" ]]; then echo "USAGE: $0 folder1 folder2" exit 1 fi function clear_folders() { echo -n "cleaning ..." if [[ "x$f1" != "x" ]]; then rm -r "$f1/"* fi if [[ "x$f2" != "x" ]]; then rm -r "$f2/"* fi echo " DONE" } if [[ "x$1" == "x--clear" ]]; then clear_folders exit fi if [ "$(ls -A $f1)$(ls -A $f2)" ]; then echo "initialization folders not empty!" exit 1 fi testnumber=1 function printtitle() { echo -n " Checking test $testnumber ... " } function test_equal() { totalattempts=6 attempts=$totalattempts diff -r $f1 $f2 >/dev/null 2>/dev/null result=$? while [[ $attempts -ge 0 && $result -ne 0 ]]; do echo -n "$attempts" sleep $((1*($totalattempts-$attempts))) attempts=$(($attempts - 1)) diff -r $f1 $f2 >/dev/null 2>/dev/null result=$? done if [[ $result -ne 0 ]]; then echo " ... FAILED" find $f1 $f2 clear_folders #TODO: only if !debug ? exit fi echo " ... OK" testnumber=$(($testnumber + 1)) #find "$f1" #TODO: only if debug echo "---------" } #Test 01 printtitle for i in $f1 $f2; do #touch $i/f01.txt || exit 1 #touch is not supported in gvfsd mounted folder echo "" > $i/f01.txt || exit 1 done test_equal #Test 02 printtitle for i in $f1 $f2; do echo test > $i/f02.txt || exit 1 done test_equal testnumber=3 #Test 03 printtitle for i in $f1 $f2; do #mkdir -p $i/f0{1,2}/f0{1,2}/f0{1,2,3}; #This seem to fail sometimes for ftp! why? It seems some issue with gvfs & caches (some mkdir fail before being attempted) for x in $i/f0{1,2}{,/sf0{1,2}{,/ssf0{1,2,3}}}; do mkdir $x || exit 1 done done test_equal #Test 04 printtitle for i in $f1 $f2; do cp $i/f01{,_copy}.txt || exit 1 done test_equal #Test 05 printtitle for i in $f1 $f2; do mv $i/f0{2,_moved}.txt || exit 1 done test_equal #Test 06 printtitle for i in $f1 $f2; do rm $i/f01_copy.txt || exit 1 done test_equal #Test 07 printtitle for i in $f1 $f2; do cp $i/f01.txt $i/f01/ || exit 1 done test_equal #Test 08 printtitle for i in $f1 $f2; do #echo change >> $i/f01.txt #this op is not supported in GVFS echo change > $i/f01.txt done test_equal #Test 09 printtitle for i in $f1 $f2; do mv $i/f01.txt $i/f01/ || exit 1 done test_equal #Test 10 printtitle for i in $f1 $f2; do rm -r $i/f02 || exit 1 done test_equal #Test 11 printtitle for i in $f1 $f2; do cp -r $i/f01 $i/f01_copied || exit 1 done test_equal #Test 12 printtitle for i in $f1 $f2; do mv $i/f01 $i/f01_moved || exit 1 done test_equal #Test 13 printtitle for i in $f1 $f2; do mv $i/f01_moved $i/f01_copied/ || exit 1 done test_equal clear_folders �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/�����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0014570�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Arguments_test.cpp�����������������������������������������������������������0000664�0000000�0000000�00000005657�15162662266�0020315�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "gtest/gtest.h" #include <array> #include <iterator> #include <vector> #include <algorithm> #include <initializer_list> #include "mega/arguments.h" using mega::ArgumentsParser; // // Provides argc and argv like parameters in int main(int argc, char** argv) // class Argv { public: Argv(std::initializer_list<std::string> init); char** argv(); int argc(); private: std::vector<std::string> mInit; std::vector<char*> mArgv; }; Argv::Argv(std::initializer_list<std::string> init) : mInit(init) , mArgv(init.size() + 1 , nullptr) // extra 1 for nullptr ending { std::transform(std::begin(mInit), std::end(mInit), std::begin(mArgv), [](const std::string& elem) { return const_cast<char*>(elem.data()); }); } char** Argv::argv() { return mArgv.data(); } int Argv::argc() { return static_cast<int>(mInit.size()); } TEST(Argumenmts, ParseNoArgumentsSuccessfully) { Argv argv = { "executable.exe" }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_TRUE(arguments.empty()); } TEST(Argumenmts, ParseOneNoValueArgumentSuccessfully) { Argv argv = { "executable.exe", "-h", }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_TRUE(!arguments.empty()); ASSERT_TRUE(arguments.contains("-h")); ASSERT_EQ("", arguments.getValue("-h")); } TEST(Argumenmts, ParseOneHasValueArgumentSuccessfully) { Argv argv = { "executable.exe", "-t=10", }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_FALSE(arguments.empty()); ASSERT_TRUE(arguments.contains("-t")); ASSERT_EQ("10", arguments.getValue("-t")); } TEST(Argumenmts, ParseOneListOfArgumentsSuccessfully) { Argv argv = { "executable.exe", "-h", "-t=10", "-n=the name", }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_FALSE(arguments.empty()); ASSERT_EQ(3, static_cast<int>(arguments.size())); ASSERT_EQ("", arguments.getValue("-h")); ASSERT_EQ("10", arguments.getValue("-t")); ASSERT_EQ("the name", arguments.getValue("-n")); ASSERT_FALSE(arguments.contains("-xxx")); ASSERT_EQ("", arguments.getValue("-xxx")); } TEST(Argumenmts, getValueDefaultIsNotReturnDefaultIfValueIsEmpty) { Argv argv = { "executable.exe", "-h", }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_EQ("", arguments.getValue("-h", "default")); } TEST(Argumenmts, getValueDefaultReturnedDefaultIfNameNotExist) { Argv argv = { "executable.exe", "-h", }; auto arguments = ArgumentsParser::parse(argv.argc(), argv.argv()); ASSERT_EQ("default", arguments.getValue("-x", "default")); } ���������������������������������������������������������������������������������sdk-10.11.0/tests/unit/AttrMap_test.cpp�������������������������������������������������������������0000664�0000000�0000000�00000006567�15162662266�0017721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/attrmap.h> #include <map> #include <string> #include <string_view> using namespace mega; AttrMap toAttrMap(const std::map<std::string_view, std::string>& m) { AttrMap result; for (const auto& [k, v]: m) result.map[AttrMap::string2nameid(k)] = v; return result; } std::string toJson(const std::map<std::string_view, std::string>& m) { return toAttrMap(m).getjson(); } std::string toJsonObject(const std::map<std::string_view, std::string>& m) { return toAttrMap(m).getJsonObject(); } TEST(AttrMap, serialize_unserialize) { mega::AttrMap map; map.map = std::map<mega::nameid, std::string>{ {13, "foo"}, {42, "blah"}, }; std::string d; map.serialize(&d); mega::AttrMap newMap; newMap.unserialize(d.c_str(), d.c_str() + d.size()); ASSERT_EQ(map.map, newMap.map); } #ifndef WIN32 // data was recorded with "mock" utf-8 not the actual utf-16 TEST(AttrMap, unserialize_32bit) { // This is the result of serialization on 32bit Windows const std::array<char, 16> rawData = { 0x01, 0x0d, 0x03, 0x00, 0x66, 0x6f, 0x6f, 0x01, 0x2a, 0x04, 0x00, 0x62, 0x6c, 0x61, 0x68, 0x00 }; const std::string d(rawData.data(), rawData.size()); mega::AttrMap expMap; expMap.map = std::map<mega::nameid, std::string>{ {13, "foo"}, {42, "blah"}, }; mega::AttrMap newMap; newMap.unserialize(d.c_str(), d.c_str() + d.size()); ASSERT_EQ(expMap.map, newMap.map); } #endif TEST(AttrMap, applyUpdates) { auto baseMap = toAttrMap({{"a", "hello"}, {"b", "world"}, {"n", toJson({{"a", "hi"}, {"b", "foo"}})}}); const auto updateMap = toAttrMap( {{"a", ""}, {"b", "hello"}, {"c", "world"}, {"n", toJson({{"a", ""}, {"c", "hi"}})}}); baseMap.applyUpdates(updateMap.map); const auto expected = toAttrMap({{"b", "hello"}, {"c", "world"}, {"n", toJson({{"a", ""}, {"c", "hi"}})}}); EXPECT_EQ(baseMap.map, expected.map); } TEST(AttrMap, applyUpdatesWithNestedFields) { using namespace std::literals; auto baseMap = toAttrMap( {{"a", "hello"}, {"b", "world"}, {"n", toJsonObject({{"a", "hi"}, {"b", "foo"}})}}); auto updateMap = toAttrMap( {{"a", ""}, {"b", "hello"}, {"c", "world"}, {"n", toJsonObject({{"a", ""}, {"c", "hi"}})}}); baseMap.applyUpdatesWithNestedFields(updateMap, std::array{"n"sv}); auto expected = toAttrMap( {{"b", "hello"}, {"c", "world"}, {"n", toJsonObject({{"b", "foo"}, {"c", "hi"}})}}); EXPECT_EQ(baseMap.map, expected.map); // Now remove the nested field updateMap = toAttrMap({{"n", ""}}); baseMap.applyUpdatesWithNestedFields(updateMap, std::array{"n"sv}); expected = toAttrMap({{"b", "hello"}, {"c", "world"}}); EXPECT_EQ(baseMap.map, expected.map); } �����������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/CMakeLists.txt���������������������������������������������������������������0000664�0000000�0000000�00000004567�15162662266�0017344�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_executable(test_unit) target_sources(test_unit PRIVATE constants.h DefaultedDbTable.h DefaultedDirAccess.h DefaultedFileAccess.h DefaultedFileSystemAccess.h FsNode.h NotImplemented.h utils.h main.cpp Arguments_test.cpp AttrMap_test.cpp CacheLRU_test.cpp canceller_test.cpp ChunkMacMap_test.cpp Commands_test.cpp Crypto_test.cpp cxx20_features_test.cpp FileFingerprint_test.cpp FileFingerprint_CRC_test.cpp File_test.cpp FsNode.cpp getDefaultLogName.cpp hashcash_test.cpp Logging_test.cpp MediaProperties_test.cpp MegaApi_test.cpp NodesMatchedByFsid_test.cpp JSONNumericParsers_test.cpp name_collision_test.cpp PayCrypter_test.cpp PendingContactRequest_test.cpp proxy_test.cpp Scoped_timer_test.cpp Serialization_test.cpp Share_test.cpp Sync_conflict_test.cpp Sync_test.cpp SyncUploadThrottling_test.cpp TextChat_test.cpp Transfer_test.cpp Transferstats_test.cpp User_test.cpp user_attributes_test.cpp impl/share_test.cpp utils.cpp utils_test.cpp utils_optional_test.cpp pwm_file_parser_test.cpp pwm_import_validation.cpp LRUCache_test.cpp MacComparison_test.cpp totp_test.cpp localpath_test.cpp shared_mutex_tests.cpp uripath_test.cpp Sqlite_test.cpp file_access_tests.cpp jsonsplitter_test.cpp megaclient_test.cpp SharedMemoryZone_test.cpp ) target_sources_conditional(test_unit FLAG ENABLE_ISOLATED_GFX PRIVATE Isolatedprocess_test.cpp GfxCommands_test.cpp ) # Load File Service test sources. include(file_service/file_service.cmake) # Link with SDKlib target_link_libraries(test_unit PRIVATE MEGA::CommonHeaderPaths MEGA::SDKlib ) # Link with the common interface library for the tests. target_link_libraries(test_unit PRIVATE MEGA::test_tools MEGA::test_common) # Adjust compilation flags for warnings and errors target_platform_compile_options( TARGET test_unit WINDOWS /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$<CONFIG:Debug>:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET test_unit WINDOWS /WX UNIX $<$<CONFIG:Debug>: -Werror> ) endif() �����������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/CacheLRU_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000104663�15162662266�0017733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file CacheLRU_test.cpp * @brief Unitary test for NodeManger cache LRU * * (c) 2013-2023 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "utils.h" #include <gtest/gtest.h> #include <mega/megaapp.h> #include <mega/megaclient.h> #include <mega/user.h> #include <mega/utils.h> class CacheLRU: public testing::Test { protected: uint32_t mLruSize = 0; mega::MegaApp mApp; mega::NodeManager::MissingParentNodes mMissingParentNodes; uint64_t mIndex = 1; static constexpr uint64_t mNumRootNodes{3}; // Root node + rubbish + vault std::shared_ptr<mega::MegaClient> mClient; std::shared_ptr<mega::Node> addRootNodes() { auto rootNode = addNode(mega::nodetype_t::ROOTNODE, nullptr, false, true); addNode(mega::nodetype_t::VAULTNODE, nullptr, false, true); addNode(mega::nodetype_t::RUBBISHNODE, nullptr, false, true); return rootNode; } public: std::shared_ptr<mega::Node> init(uint32_t lruSize) { mLruSize = lruSize; auto dbAccess = new mega::SqliteDbAccess(mega::LocalPath::fromAbsolutePath(".")); mClient = mt::makeClient(mApp, dbAccess); mClient->sid = "AWA5YAbtb4JO-y2zWxmKZpSe5-6XM7CTEkA-3Nv7J4byQUpOazdfSC1ZUFlS-kah76gPKUEkTF9g7MeE"; mClient->opensctable(); mClient->mNodeManager.setCacheLRUMaxSize(mLruSize); return addRootNodes(); } ~CacheLRU() { mClient.reset(); } uint64_t numNodesInRam() const { return mClient->mNodeManager.getNumberNodesInRam(); } uint64_t numNodesInCacheLru() const { return mClient->mNodeManager.getNumNodesAtCacheLRU(); } uint64_t numNodesTotal() const { return mClient->mNodeManager.getNodeCount(); } void setLruMaxSize(uint32_t size) { mClient->mNodeManager.setCacheLRUMaxSize(size); mLruSize = size; } std::shared_ptr<mega::Node> addNode(mega::nodetype_t nodeType, const std::shared_ptr<mega::Node>& parent, bool notify, bool isFetching, std::function<void(mega::Node&)> nodeSetupCb = nullptr) { auto& nodeRef = mt::makeNode(*mClient, nodeType, mega::NodeHandle().set6byte(mIndex++), parent.get()); std::shared_ptr<mega::Node> node(&nodeRef); if (nodeSetupCb) { nodeSetupCb(nodeRef); } node->serializefingerprint(&node->attrs.map['c']); node->setfingerprint(); mClient->mNodeManager.addNode(node, notify, isFetching, mMissingParentNodes); mClient->mNodeManager.saveNodeInDb(node.get()); return node; } void checkNodesInCache(std::optional<uint64_t> expInRam, std::optional<uint64_t> expTotal, std::optional<uint64_t> expInLRU) { if (expInRam.has_value()) { ASSERT_EQ(numNodesInRam(), expInRam) << "Unexpected num nodes in RAM"; } if (expTotal.has_value()) { ASSERT_EQ(numNodesTotal(), expTotal) << "Unexpected total num nodes"; } if (expInLRU.has_value()) { ASSERT_EQ(numNodesInCacheLru(), expInLRU) << "Unexpected num nodes in LRU cache"; } } void addNodeInCache(std::vector<std::string>& fingerprints, std::shared_ptr<mega::Node> folderNode, const m_off_t size, const int64_t mtime, const int32_t crcPart, const unsigned int numNodesWithSameData = 1, const mega::handle owner = 1, std::optional<mega::attr_map> attrs = std::nullopt) { for (unsigned int i = 0; i < numNodesWithSameData; ++i) { addNode(mega::nodetype_t::FILENODE, folderNode, true, false, [&fingerprints, attrs, owner, size, mtime, crcPart](mega::Node& file) { file.size = static_cast<m_off_t>(size); file.owner = owner; file.ctime = mtime; file.mtime = mtime; file.crc[0] = crcPart; file.crc[1] = crcPart; file.crc[2] = crcPart; file.crc[3] = crcPart; file.isvalid = true; file.attrs.map = attrs.value_or( [] { mega::attr_map m; m[101] = "foo"; m[102] = "bar"; return m; }()); std::string fp; file.mega::FileFingerprint::serialize(&fp); fingerprints.push_back(fp); }); } } void expectedNodeCountByFp(std::vector<std::string>& fingerprints, size_t expectedNodeCount, const size_t fpIdx, const bool excludeMtime = true) { ASSERT_LT(fpIdx, fingerprints.size()) << "expectedNodeCountByFp Invalid Fp Index"; const char* fingerpritnString = fingerprints.at(fpIdx).data(); std::unique_ptr<mega::FileFingerprint> fp( mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size())); ASSERT_TRUE(!!fp) << "expectedNodeCountByFp: fingerprint could not be unserialized"; mega::sharedNode_vector nodes( mClient->mNodeManager.getNodesByFingerprint(*fp, excludeMtime)); ASSERT_EQ(nodes.size(), expectedNodeCount) << "expectedNodeCountByFp: Unexpected node count"; } }; TEST_F(CacheLRU, checkNumNodes_higherLRUSize) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); for (uint32_t i = 0; i < mLruSize - 4; i++) { addNode(mega::nodetype_t::FILENODE, rootNode, false, true); } ASSERT_EQ(numNodesInRam(), numNodesInCacheLru()); ASSERT_EQ(numNodesTotal(), numNodesInRam()); for (uint32_t i = 0; i < mLruSize; i++) { addNode(mega::nodetype_t::FILENODE, rootNode, true, false); } // 2 (rubbis + vault) -> root node is load at LRU when getParent is called ASSERT_EQ(numNodesInRam(), mLruSize + 2); ASSERT_EQ(numNodesTotal(), mIndex - 1); } TEST_F(CacheLRU, checkNumNodes_LRUSize) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); for (uint32_t i = 0; i < mLruSize - 4; i++) { addNode(mega::nodetype_t::FILENODE, folder, true, false); } ASSERT_EQ(numNodesInRam(), mLruSize); ASSERT_EQ(numNodesTotal(), mLruSize); for (uint32_t i = 0; i < 4; i++) { addNode(mega::nodetype_t::FILENODE, folder, true, false); } // 3 root nodes -> folder is at LRU cache, it accesed to set parent from new children ASSERT_EQ(numNodesInRam(), mLruSize + 3); ASSERT_EQ(numNodesTotal(), mIndex - 1); } TEST_F(CacheLRU, removeNode) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint64_t indexFromNodeAtLRU = mIndex; uint32_t numNodes = 15; for (uint32_t i = 0; i < numNodes; i++) { addNode( mega::nodetype_t::FILENODE, folder, true, false, [this](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}}; }); } // Root node + rubbish + vault ASSERT_EQ(numNodesInRam(), mLruSize + 3); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); // 3 root nodes -> folder is at LRU cache, it's accesed to set parent from new children ASSERT_EQ(numNodesInRam(), mLruSize + 3); ASSERT_EQ(numNodesTotal(), mIndex - 1); auto& nodeMgr = mClient->mNodeManager; auto nodeToRemove = nodeMgr.getNodeByHandle(mega::NodeHandle().set6byte(indexFromNodeAtLRU)); nodeToRemove->changed.removed = true; nodeMgr.notifyNode(nodeToRemove); nodeToRemove.reset(); nodeMgr.notifyPurge(); ASSERT_EQ(numNodesInRam(), mLruSize + 2); ASSERT_EQ(numNodesInCacheLru(), mLruSize - 1); ASSERT_EQ(numNodesTotal(), mIndex - 2); } TEST_F(CacheLRU, getNodebyFingerprint_RAM_NoLRU) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = 15; std::vector<std::string> fingerprints; std::shared_ptr<mega::Node> nodeRemovedFromLRU; for (uint32_t i = 0; i < numNodes; i++) { auto fileNode = addNode( mega::nodetype_t::FILENODE, folder, true, false, [this, &fingerprints](mega::Node& file) { auto index = static_cast<int32_t>(mIndex); file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; // Modify fingerprint look nodes by fingerprint file.crc[0] = index; file.crc[1] = index; file.crc[2] = index; file.crc[3] = index; file.isvalid = true; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}}; std::string fp; file.mega::FileFingerprint::serialize(&fp); fingerprints.push_back(fp); }); if (i == 1) { nodeRemovedFromLRU = fileNode; } } folder.reset(); // Root node + rubbish + vault + node with reference ASSERT_EQ(numNodesInRam(), mLruSize + 4); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); ASSERT_GT(fingerprints.size(), 1); // No found at LRU, fingerprint at DB const char* fingerpritnString = fingerprints.front().data(); std::unique_ptr<mega::FileFingerprint> fp(mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size())); std::shared_ptr<mega::Node> node(mClient->mNodeManager.getNodeByFingerprint(*fp)); ASSERT_NE(node.get(), nullptr); // No found at LRU, fingerprint at DB but node is in RAM fingerpritnString = fingerprints.at(1).data(); fp = mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size()); node = mClient->mNodeManager.getNodeByFingerprint(*fp); ASSERT_NE(node.get(), nullptr); ASSERT_EQ(node.get(), nodeRemovedFromLRU.get()); // Found at LRU, fingerprint at mFingerPrints fingerpritnString = fingerprints.back().data(); fp = mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size()); node = mClient->mNodeManager.getNodeByFingerprint(*fp); ASSERT_NE(node.get(), nullptr); } TEST_F(CacheLRU, getNodeByFingerprint_NoRAM_NoLRU) { auto rootNode = init(8); ASSERT_EQ(mClient->mNodeManager.getNumberNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = 15; std::vector<std::string> fingerprints; for (uint32_t i = 0; i < numNodes; i++) { addNode( mega::nodetype_t::FILENODE, folder, true, false, [this, &fingerprints](mega::Node& file) { auto index = static_cast<int32_t>(mIndex); file.size = static_cast<m_off_t>(index); file.owner = 88; file.ctime = 44; file.mtime = 44; // Modify fingerprint look nodes by fingerprint file.crc[0] = index; file.crc[1] = index; file.crc[2] = index; file.crc[3] = index; file.isvalid = true; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}}; std::string fp; file.mega::FileFingerprint::serialize(&fp); fingerprints.push_back(fp); }); } folder.reset(); // Root node + rubbish + vault ASSERT_EQ(numNodesInRam(), mLruSize + 3); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); // 3 root nodes -> folder is at LRU cache, it's accesed to set parent from new children ASSERT_EQ(numNodesInRam(), mLruSize + 3); ASSERT_EQ(numNodesTotal(), mIndex - 1); ASSERT_GT(fingerprints.size(), 1); // No found at LRU, fingerprint at DB const char* fingerpritnString = fingerprints.front().data(); std::unique_ptr<mega::FileFingerprint> fp(mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size())); mega::sharedNode_vector nodes(mClient->mNodeManager.getNodesByFingerprint(*fp)); ASSERT_EQ(nodes.size(), 1); // Found at LRU, fingerprint at mFingerPrints fingerpritnString = fingerprints.back().data(); fp = mega::FileFingerprint::unserialize(fingerpritnString, fingerpritnString + fingerprints.front().size()); nodes = mClient->mNodeManager.getNodesByFingerprint(*fp); ASSERT_EQ(nodes.size(), 1); } /** * @test CacheLRU.getNodesByFingerprintIgnoringMtime * Test preconditions: Initialize the LRU cache with size (8) and create the root node. * * 1. Add multiple groups of nodes to the cache with different FPs (size, mtime, and CRC) between * groups. All elements in the same group will have the same FP (size, mtime, and CRC). * 2. Validate that the total number of nodes in the cache matches the expected count. * 3. Test `getNodesByFingerprint` ignoring `mtime` to confirm that node search by FP behaves * correctly. * - This test case forces the SDK to search nodes in the DB and allocate them in the LRU cache and * in the FingerprintContainer. * - The LRU cache size (8) is small enough that this test case also exercises node unloading from * the LRU cache when new ones need to be allocated. * 4. Add more groups of nodes to the cache with different FPs (size, mtime, and CRC values) between * groups. All elements in the same group will have the same FP (size, mtime, and CRC). * 5. Validate that the total number of nodes in the cache matches the expected count. * 6. Test `getNodesByFingerprint` both ignoring and including `mtime` to confirm that node search * by FP behaves correctly in both cases. * - This test case forces the SDK to search nodes in the DB and allocate them in the LRU cache and * in the FingerprintContainer. * - The LRU cache size (8) is small enough that this test case also exercises node unloading from * the LRU cache when new ones need to be allocated. */ TEST_F(CacheLRU, getNodesByFingerprintIgnoringMtime) { constexpr uint32_t lruSize{8}; auto rootNode = init(lruSize); ASSERT_EQ(mClient->mNodeManager.getNumberNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); std::vector<std::string> fingerprints; uint64_t expectedNumNodes{0}; const std::vector<uint32_t> numNodes{5, 10, 15}; expectedNumNodes = numNodes[0] * 3; addNodeInCache(fingerprints, folder, 10 /*size=*/, 20 /*mtime=*/, 30 /*crc*/, numNodes[0]); addNodeInCache(fingerprints, folder, 20 /*size=*/, 20 /*mtime=*/, 40 /*crc*/, numNodes[0]); addNodeInCache(fingerprints, folder, 30 /*size=*/, 40 /*mtime=*/, 50 /*crc*/, numNodes[0]); ASSERT_EQ(fingerprints.size(), expectedNumNodes) << "Unexpected num nodes in fingerprints vector"; ASSERT_NO_FATAL_FAILURE(checkNodesInCache(mNumRootNodes + mLruSize, mNumRootNodes + expectedNumNodes + +1 /*testFolder*/, lruSize)) << "TC1: Unexpected num nodes in cache"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 0 /*FpIndex*/, true /*excludeMtime*/)) << "TC2: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 5 /*FpIndex*/, true /*excludeMtime*/)) << "TC3: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 10 /*FpIndex*/, true /*excludeMtime*/)) << "TC4: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 0 /*FpIndex*/, true /*excludeMtime*/)) << "TC5: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 5 /*FpIndex*/, true /*excludeMtime*/)) << "TC6: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 10 /*FpIndex*/, true /*excludeMtime*/)) << "TC7: getNodesByFingerprint(ignoring mtime)"; addNodeInCache(fingerprints, folder, 40, 50, 60, numNodes[1]); addNodeInCache(fingerprints, folder, 40, 80, 60, numNodes[2]); folder.reset(); expectedNumNodes += numNodes[1] + numNodes[2]; ASSERT_NO_FATAL_FAILURE(checkNodesInCache(mNumRootNodes + mLruSize, mNumRootNodes + expectedNumNodes + +1 /*testFolder*/, lruSize)) << "TC8: Unexpected num nodes in cache"; ASSERT_NO_FATAL_FAILURE(expectedNodeCountByFp(fingerprints, numNodes[1] + numNodes[2], 15 /*FpIndex*/, true /*excludeMtime*/)) << "TC9: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE(expectedNodeCountByFp(fingerprints, numNodes[1] + numNodes[2], 15 /*FpIndex*/, true /*excludeMtime*/)) << "TC10: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[1], 15 /*FpIndex*/, false /*excludeMtime*/)) << "TC11: getNodesByFingerprint(including mtime)"; } /** * @test CacheLRU.getNodesByFingerprintIgnoringMtimeSmallLRUCache * Test preconditions: Initialize the LRU cache with a very small size (2) and create the root node. * * 1. Add multiple groups of nodes to the cache with different FPs (size, mtime, and CRC) between * groups. All elements in the same group will have the same FP (size, mtime, and CRC). * 2. Validate that the total number of nodes in the cache matches the expected count. * 3. Test `getNodesByFingerprint` both ignoring and including `mtime` to confirm that node search * by FP behaves correctly in both cases. * - This test case forces the SDK to search nodes in the DB and allocate them in the LRU cache and * in the FingerprintContainer. * - As the LRU cache size (2) is very small, we search nodes from different groups (and multiple * times), which causes the LRU to unload nodes to allocate new ones. */ TEST_F(CacheLRU, getNodesByFingerprintIgnoringMtimeSmallLRUCache) { constexpr uint32_t lruSize{2}; auto rootNode = init(lruSize); uint64_t expectedNumNodes{0}; ASSERT_EQ(mClient->mNodeManager.getNumberNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); std::vector<std::string> fingerprints; const std::vector<uint32_t> numNodes{2, 3}; addNodeInCache(fingerprints, folder, 40, 50, 60, numNodes[0]); addNodeInCache(fingerprints, folder, 40, 80, 60, numNodes[1]); expectedNumNodes = numNodes[0] + numNodes[1]; folder.reset(); ASSERT_NO_FATAL_FAILURE(checkNodesInCache(mNumRootNodes + mLruSize, mNumRootNodes + expectedNumNodes + +1 /*testFolder*/, lruSize)) << "TC1: Unexpected num nodes in cache"; // Get nodes by Fingerprint (repeating use cases to force LRU cache to discard nodes) // Also test both cases including/ignoring mtime in fingerprint comparison ASSERT_NO_FATAL_FAILURE(expectedNodeCountByFp(fingerprints, numNodes[0] + numNodes[1], 0 /*FpIndex*/, true /*excludeMtime*/)) << "TC2: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE(expectedNodeCountByFp(fingerprints, numNodes[0] + numNodes[1], 2 /*FpIndex*/, true /*excludeMtime*/)) << "TC3: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE(expectedNodeCountByFp(fingerprints, numNodes[0] + numNodes[1], 0 /*FpIndex*/, true /*excludeMtime*/)) << "TC4: getNodesByFingerprint(ignoring mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 0 /*FpIndex*/, false /*excludeMtime*/)) << "TC5: getNodesByFingerprint(including mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[1], 2 /*FpIndex*/, false /*excludeMtime*/)) << "TC6: getNodesByFingerprint(including mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[0], 0 /*FpIndex*/, false /*excludeMtime*/)) << "TC7: getNodesByFingerprint(including mtime)"; ASSERT_NO_FATAL_FAILURE( expectedNodeCountByFp(fingerprints, numNodes[1], 2 /*FpIndex*/, false /*excludeMtime*/)) << "TC8: getNodesByFingerprint(including mtime)"; } TEST_F(CacheLRU, searchNode) // processUnserializedNodes { auto rootNode = init(8); ASSERT_EQ(mClient->mNodeManager.getNumberNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = 15; std::vector<std::string> names; std::shared_ptr<mega::Node> nodeInRAM; std::string nameNodeInRam; for (uint32_t i = 0; i < numNodes; i++) { std::string name = "name" + std::to_string(mIndex); names.push_back(name); auto fileNode = addNode( mega::nodetype_t::FILENODE, folder, true, false, [this, &name](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}, {110, name}}; }); if (i == 1) { nodeInRAM = fileNode; nameNodeInRam = name; } } // Root node + rubbish + vault + node in RAM ASSERT_EQ(numNodesInRam(), mLruSize + 4); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); ASSERT_EQ(numNodesTotal(), mIndex - 1); ASSERT_GT(names.size(), 1); // No found at LRU mega::NodeSearchFilter searchFilter; searchFilter.byAncestors({rootNode->nodehandle, mega::UNDEF, mega::UNDEF}); searchFilter.byName(names.front()); auto& nodeMgr = mClient->mNodeManager; mega::sharedNode_vector nodes(nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0})); ASSERT_EQ(nodes.size(), 1); // No found at LRU but in RAM searchFilter.byName(nameNodeInRam); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 1); // Found at LRU searchFilter.byName(names.back()); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 1); // Search a not out shared folder by out share searchFilter.byAncestors({mega::UNDEF, mega::UNDEF, mega::UNDEF}); searchFilter.byName(""); searchFilter.setIncludedShares(mega::OUT_SHARES); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 0); // Set the folder as public link folder->plink.reset(new mega::PublicLink{0x1, 0x1, 0x1, false}); nodeMgr.saveNodeInDb(folder.get()); // Search searchFilter.setIncludedShares(mega::LINK); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 16); // Set the folder as out shared as well mega::User user{"name@name.com"}; folder->outshares.reset(new mega::share_map{}); folder->outshares->emplace(0x1ull, std::make_unique<mega::Share>(&user, mega::FULL, 0x1)); nodeMgr.saveNodeInDb(folder.get()); // Search by public link searchFilter.setIncludedShares(mega::LINK); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 16); // Search out shares with name searchFilter.byName(names.back()); searchFilter.setIncludedShares(mega::OUT_SHARES); nodes = nodeMgr.searchNodes(searchFilter, 0 /*order None*/, mega::CancelToken(), mega::NodeSearchPage{0, 0}); ASSERT_EQ(nodes.size(), 1); } TEST_F(CacheLRU, getChildren) { auto rootNode = init(8); auto& nodeMgr = mClient->mNodeManager; ASSERT_EQ(numNodesInRam(), 3); std::array<uint32_t, 3> numNodesForFolder = {2, 10, 10}; std::array<std::shared_ptr<mega::Node>, 3> folders; std::shared_ptr<mega::Node> nodeInRAM; for (uint32_t f = 0; f < 3; f++) { auto& folder = folders[f] = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true, [f](mega::Node& folderNode) { folderNode.attrs.map = std::map<mega::nameid, std::string>{ {110, "Folder" + std::to_string(f + 1)}}; }); auto numNodes = numNodesForFolder[f]; for (uint32_t i = 0; i < numNodes; i++) { auto fileNode = addNode(mega::nodetype_t::FILENODE, folder, true, false, [this](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}}; }); if ((f == 2) && (i == numNodes / 2)) { nodeInRAM = fileNode; } } mega::sharedNode_list children = nodeMgr.getChildren(folder.get()); // All children in RAM ASSERT_EQ(children.size(), numNodes); } auto children = nodeMgr.getChildren(folders[0].get()); // None node in RAM ASSERT_EQ(children.size(), numNodesForFolder[0]); } TEST_F(CacheLRU, getNodeByHandle) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = 15; std::vector<mega::NodeHandle> handles; std::shared_ptr<mega::Node> nodeInRAM; for (uint32_t i = 0; i < numNodes; i++) { auto fileNode = addNode( mega::nodetype_t::FILENODE, folder, true, false, [this, &handles](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; std::string name = "name" + std::to_string(mIndex); file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}, {110, name}}; handles.push_back(file.nodeHandle()); }); if (i == numNodes / 2) { nodeInRAM = fileNode; } } ASSERT_GT(handles.size(), 1); mega::NodeHandle firstNodeHandle = handles.front(); mega::NodeHandle lasttNodeHandle = handles.front(); mega::NodeHandle nodeInRAMHandle = nodeInRAM->nodeHandle(); auto& nodeMgr = mClient->mNodeManager; // No Node at RAM => no at LRU // ASSERT_EQ(client->mNodeManager.getNodeInRAM(firstNodeHandle).get(), nullptr); std::shared_ptr<mega::Node> node = nodeMgr.getNodeByHandle(firstNodeHandle); // Node at RAM and LRU auto auxiliarNode = nodeMgr.getNodeByHandle(lasttNodeHandle); ASSERT_NE(auxiliarNode, nullptr); ASSERT_NE(auxiliarNode->mNodePosition->second.mLRUPosition, nodeMgr.invalidCacheLRUPos()); node = nodeMgr.getNodeByHandle(lasttNodeHandle); ASSERT_EQ(auxiliarNode.get(), node.get()); // Node at RAM, no at LRU // ASSERT_NE(client->mNodeManager.getNodeInRAM(nodeInRAMHandle).get(), nullptr); ASSERT_NE(nodeInRAM, nullptr); ASSERT_EQ(nodeInRAM->mNodePosition->second.mLRUPosition, nodeMgr.invalidCacheLRUPos()); node = nodeMgr.getNodeByHandle(nodeInRAMHandle); ASSERT_EQ(nodeInRAM.get(), node.get()); } TEST_F(CacheLRU, childNodeByNameType) { auto rootNode = init(8); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = 15; std::vector<std::string> names; std::shared_ptr<mega::Node> nodeInRAM; std::string nameNodeInRam; for (uint32_t i = 0; i < numNodes; i++) { std::string name = "name" + std::to_string(mIndex); names.push_back(name); auto fileNode = addNode( mega::nodetype_t::FILENODE, folder, true, false, [this, &name](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}, {110, name}}; }); if (i == 1) { nodeInRAM = fileNode; nameNodeInRam = name; } } // Root node + rubbish + vault + node in RAM ASSERT_EQ(numNodesInRam(), mLruSize + 4); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); ASSERT_GT(names.size(), 1); auto& nodeMgr = mClient->mNodeManager; // No found at LRU std::shared_ptr<mega::Node> node = nodeMgr.childNodeByNameType(folder.get(), names.front().c_str(), mega::nodetype_t::FILENODE); ASSERT_NE(node, nullptr); // No found at LRU but in RAM node = nodeMgr.childNodeByNameType(folder.get(), nameNodeInRam.c_str(), mega::nodetype_t::FILENODE); ASSERT_NE(node, nullptr); // Found at LRU node = nodeMgr.childNodeByNameType(folder.get(), names.back().c_str(), mega::nodetype_t::FILENODE); ASSERT_NE(node, nullptr); } TEST_F(CacheLRU, reduceCacheLRUSize) { auto rootNode = init(20); ASSERT_EQ(numNodesInRam(), 3); auto folder = addNode(mega::nodetype_t::FOLDERNODE, rootNode, false, true); uint32_t numNodes = mLruSize; for (uint32_t i = 0; i < numNodes; i++) { addNode(mega::nodetype_t::FILENODE, folder, true, false, [this](mega::Node& file) { file.size = static_cast<m_off_t>(mIndex); file.owner = 88; file.ctime = 44; std::string name = "name" + std::to_string(mIndex); file.attrs.map = std::map<mega::nameid, std::string>{{101, "foo"}, {102, "bar"}, {110, name}}; }); } // Root node + rubbish + vault ASSERT_EQ(numNodesInRam(), mLruSize + 3); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); setLruMaxSize(8); // Root node + rubbish + vault ASSERT_EQ(numNodesInRam(), mLruSize + 3); // Root node + rubbish + vault + folder ASSERT_EQ(numNodesTotal(), numNodes + 4); } �����������������������������������������������������������������������������sdk-10.11.0/tests/unit/ChunkMacMap_test.cpp���������������������������������������������������������0000664�0000000�0000000�00000001233�15162662266�0020461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/utils.h> namespace mega { } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Commands_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000020303�15162662266�0020072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <memory> #include <gtest/gtest.h> #include <mega/command.h> #include <mega/json.h> #include <mega/megaapp.h> #include <mega/megaclient.h> #include <mega/types.h> using namespace std; using namespace mega; namespace { class MockApp_CommandGetCountryCallingCodes : public MegaApp { public: using DataType = map<string, vector<string>>; int mCallCount = 0; ErrorCodes mLastError = ErrorCodes::API_EINTERNAL; std::unique_ptr<DataType> mCountryCallingCodes; void getcountrycallingcodes_result(const ErrorCodes e, DataType* const data) override { ++mCallCount; mLastError = e; if (data) { mCountryCallingCodes = std::unique_ptr<DataType>{new DataType{*data}}; } else { assert(e != ErrorCodes::API_OK); } } }; } // anonymous /*TEST(Commands, CommandGetCountryCallingCodes_processResult_happyPath) { MockApp_CommandGetCountryCallingCodes app; JSON json; json.pos = R"({"cc":"AD","l":[376]},{"cc":"AE","l":[971,13]},{"cc":"AF","l":[93,13,42]})"; const auto jsonBegin = json.pos; const auto jsonLength = strlen(json.pos); CommandGetCountryCallingCodes::processResult(app, json); const map<string, vector<string>> expected{ {"AD", {"376"}}, {"AE", {"971", "13"}}, {"AF", {"93", "13", "42"}}, }; ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_onlyOneCountry) { MockApp_CommandGetCountryCallingCodes app; JSON json; json.pos = R"({"cc":"AD","l":[12,376]})"; const auto jsonBegin = json.pos; const auto jsonLength = strlen(json.pos); CommandGetCountryCallingCodes::processResult(app, json); const map<string, vector<string>> expected{ {"AD", {"12", "376"}}, }; ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_extraFieldShouldBeIgnored) { MockApp_CommandGetCountryCallingCodes app; JSON json; json.pos = R"({"cc":"AD","l":[12,376],"blah":"42"})"; const auto jsonBegin = json.pos; const auto jsonLength = strlen(json.pos); CommandGetCountryCallingCodes::processResult(app, json); const map<string, vector<string>> expected{ {"AD", {"12", "376"}}, }; ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_OK, app.mLastError); ASSERT_NE(nullptr, app.mCountryCallingCodes); ASSERT_EQ(expected, *app.mCountryCallingCodes); ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } TEST(Commands, CommandGetCountryCallingCodes_processResult_invalidResponse) { MockApp_CommandGetCountryCallingCodes app; JSON json; json.pos = R"({"cc":"AD","blah":[12,376]})"; const auto jsonBegin = json.pos; const auto jsonLength = strlen(json.pos); CommandGetCountryCallingCodes::processResult(app, json); ASSERT_EQ(1, app.mCallCount); ASSERT_EQ(API_EINTERNAL, app.mLastError); ASSERT_EQ(nullptr, app.mCountryCallingCodes); ASSERT_EQ(ptrdiff_t(jsonLength), std::distance(jsonBegin, json.pos)); // assert json has been parsed all the way } class FileSystemAccessMockup : public ::mega::FileSystemAccess { public: FileSystemAccessMockup() {} std::unique_ptr<FileAccess> newfileaccess(bool = true) override{ return std::unique_ptr<FileAccess>(); } DirAccess* newdiraccess() override {return nullptr;} bool getlocalfstype(const ::mega::LocalPath&, ::mega::FileSystemType&) const override { return false; } void path2local(const string*, string*) const override {} void local2path(const string*, string*) const override {} #if defined(_WIN32) void path2local(const string*, std::wstring*) const override {} void local2path(const std::wstring*, string*) const override {} #endif void tmpnamelocal(LocalPath&) const override {} bool getsname(const LocalPath& , LocalPath& ) const override { return false; } bool renamelocal(LocalPath&, LocalPath&, bool = true) override { return false; } bool copylocal(LocalPath&, LocalPath&, m_time_t) override { return false; } bool unlinklocal(LocalPath&) override { return false; } bool rmdirlocal(LocalPath&) override { return false; } bool mkdirlocal(LocalPath&, bool = false) override { return false; } bool setmtimelocal(LocalPath&, m_time_t) override { return false; } bool chdirlocal(LocalPath&) const override { return false; } bool getextension(const LocalPath&, string&) const override { return false; } bool expanselocalpath(LocalPath& , LocalPath& ) override { return false; } bool cwd(LocalPath&) const { return false; } void addevents(Waiter*, int) override {} virtual bool issyncsupported(const LocalPath&, bool& b, SyncError& se, SyncWarning& sw) { b = false; se = NO_SYNC_ERROR; sw = NO_SYNC_WARNING; return true;} }; class HttpIOMockup : public ::mega::HttpIO { public: HttpIOMockup(){} void post(struct HttpReq*, const char* = NULL, unsigned = 0) override{}; void cancel(HttpReq*) override{} m_off_t postpos(void*) override{ return 0; } bool doio(void) override{ return false; } void setuseragent(string*) override{} void addevents(Waiter*, int) override {} }; class MegaAppMockup : public ::mega::MegaApp { public: MegaAppMockup(){} }; class ClientMockup : public ::mega::MegaClient { public: ClientMockup(MegaAppMockup& megaApp, HttpIOMockup& httpIO, FileSystemAccessMockup& fileSystem) : MegaClient(&megaApp, nullptr, &httpIO, &fileSystem, nullptr, nullptr, nullptr, "UserAgent", 1) { } }; TEST(Commands, CommandFetchAds) { FileSystemAccessMockup fileSystem; HttpIOMockup httpIO; MegaAppMockup megaApp; ClientMockup client(megaApp, httpIO, fileSystem); client.json.pos = R"({"id": "wphl","iu": "/22060108601/wph/wph_l"},{"id":"wphr","iu": "/22060108601/wph/wph_r"},{"id":"wpht","iu": "/22060108601/wph/wph_t"})"; std::vector<std::string> v; handle h = UNDEF; int adFlags = 512; ::mega::CommandFetchAds command(&client, adFlags, v, h, [](::mega::Error e, ::mega::string_map value) { ASSERT_EQ(e, API_OK); ASSERT_EQ(value.size(), 3); ASSERT_NE(value.find("wphl"), value.end()); ASSERT_EQ(value["wphl"], "/22060108601/wph/wph_l"); ASSERT_NE(value.find("wphr"), value.end()); ASSERT_EQ(value["wphr"], "/22060108601/wph/wph_r"); ASSERT_NE(value.find("wpht"), value.end()); ASSERT_EQ(value["wpht"], "/22060108601/wph/wph_t"); }); command.client = &client; ::mega::Command::Result r(::mega::Command::Outcome::CmdArray); command.procresult(r); } TEST(Commands, CommandQueryAds) { FileSystemAccessMockup fileSystem; HttpIOMockup httpIO; MegaAppMockup megaApp; ClientMockup client(megaApp, httpIO, fileSystem); client.json.pos = R"(1)"; handle h = UNDEF; int adFlags = 512; ::mega::CommandQueryAds command(&client, adFlags, h, [](::mega::Error e, int value) { ASSERT_EQ(e, API_OK); ASSERT_EQ(value, 1); }); command.client = &client; ::mega::Command::Result r(::mega::Command::Outcome::CmdArray); command.procresult(r); } */ �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Crypto_test.cpp��������������������������������������������������������������0000664�0000000�0000000�00000101114�15162662266�0017611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "../src/crypto/sodium.cpp" #include "gtest/gtest.h" #include "mega.h" #include <cryptopp/hex.h> #include <math.h> using namespace mega; // Test encryption/decryption using AES in mode GCM // (test vectors from 'tlvstore_test.js', in Webclient) TEST(Crypto, AES_GCM) { // string keyStr = "dGQhii+B7+eLLHRiOA690w=="; //gitleaks:allow string keyStr = "dGQhii-B7-eLLHRiOA690w"; //gitleaks:allow Base64 URL encoding unsigned keyLen = SymmCipher::KEYLENGTH; byte* keyBytes = new byte[keyLen]; keyLen = static_cast<unsigned>(Base64::atob(keyStr.data(), keyBytes, static_cast<int>(keyLen))); string ivStr = "R8q1njARXS7urWv3"; unsigned ivLen = 12; byte* ivBytes = new byte[ivLen]; ivLen = static_cast<unsigned>(Base64::atob(ivStr.data(), ivBytes, static_cast<int>(ivLen))); unsigned tagLen = 16; string plainStr = "dGQhwoovwoHDr8OnwossdGI4DsK9w5M"; auto plainLen = plainStr.length(); byte* plainBytes = new byte[plainLen]; plainLen = static_cast<size_t>(Base64::atob(plainStr.data(), plainBytes, static_cast<int>(plainLen))); string plainText((const char*)plainBytes, plainLen); string cipherStr = "L3zqVYAOsRk7zMg2KsNTVShcad8TjIQ7umfsvia21QO0XTj8vaeR"; auto cipherLen = cipherStr.length(); byte* cipherBytes = new byte[cipherLen]; cipherLen = static_cast<size_t>( Base64::atob(cipherStr.data(), cipherBytes, static_cast<int>(cipherLen))); string cipherText((const char*)cipherBytes, cipherLen); SymmCipher key; key.setkey(keyBytes, SymmCipher::KEYLENGTH); string result; // Test AES_GCM_12_16 encryption result.clear(); ASSERT_TRUE(key.gcm_encrypt(&plainText, ivBytes, ivLen, tagLen, &result)) << "GCM encryption failed"; ASSERT_STREQ(result.data(), cipherText.data()) << "GCM encryption: cipher text doesn't match the expected value"; // Test AES_GCM_12_16 decryption result.clear(); ASSERT_TRUE(key.gcm_decrypt(&cipherText, ivBytes, ivLen, tagLen, &result)) << "GCM decryption failed"; ASSERT_STREQ(result.data(), plainText.data()) << "GCM decryption: plain text doesn't match the expected value"; delete[] keyBytes; delete[] ivBytes; delete[] plainBytes; delete[] cipherBytes; } // Test encryption/decryption of the xxTEA algorithm that we use for media file attributes TEST(Crypto, xxTea) { // two cases with data generated in the javascript version { uint32_t key1[4] = { 0x00000000, 0x01000000, 0x02000000, 0x03000000 }; uint32_t data1[16]; for (unsigned i = sizeof(data1) / sizeof(data1[0]); i--; ) data1[i] = i; uint32_t encCmpData[16] = { 140302874, 3625593116, 1921165214, 2581869937, 2444819365, 2195760850, 718076837, 454900461, 2002331402, 793381415, 760353645, 2589596551, 709756921, 4142288381, 633884585, 418697353 }; xxteaEncrypt(data1, 16, key1); ASSERT_TRUE(0 == memcmp(data1, encCmpData, sizeof(data1))); xxteaDecrypt(data1, 16, key1); for (unsigned i = sizeof(data1) / sizeof(data1[0]); i--; ) { ASSERT_TRUE(data1[i] == i); } } { uint32_t key2[4] = { 0, 0xFFFFFFFF, 0xFEFFFFFF, 0xFDFFFFFF }; uint32_t data2[16]; for (unsigned i = sizeof(data2) / sizeof(data2[0]); i--;) data2[i] = -i; uint32_t encCmpData2[16] = { 1331968695, 2520133218, 2881973170, 783802011, 1812010991, 1359505125, 15067484, 3344073997, 4210258643, 824383226, 3584459687, 2866083302, 881254637, 502181030, 680349945, 1722488731 }; xxteaEncrypt(data2, 16, key2); ASSERT_TRUE(0 == memcmp(data2, encCmpData2, sizeof(data2))); xxteaDecrypt(data2, 16, key2); for (unsigned i = sizeof(data2) / sizeof(data2[0]); i--; ) { ASSERT_TRUE(data2[i] == uint32_t(-(int)i)); } } } // Test encryption/decryption using AES in mode CCM // (test vectors from 'tlvstore_test.js', in Webclient) TEST(Crypto, AES_CCM) { byte keyBytes[] = { 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 }; byte ivBytes[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b }; unsigned tagLen = 16; byte plainBytes[] = { 0x34, 0x32 }; // "42" in hexadecimal string plainText((const char*)plainBytes, sizeof plainBytes); byte cipherBytes[] = { 0x28, 0xbe, 0x1a, 0xc7, 0xb4, 0x3d, 0x88, 0x68, 0x86, 0x9b, 0x9a, 0x45, 0xd3, 0xde, 0x43, 0x6c, 0xd0, 0xcc }; string cipherText((const char*)cipherBytes, sizeof cipherBytes); SymmCipher key; key.setkey(keyBytes, sizeof keyBytes); string result; // Test AES_CCM_12_16 encryption result.clear(); ASSERT_TRUE(key.ccm_encrypt(&plainText, ivBytes, sizeof ivBytes, tagLen, &result)) << "CCM encryption failed"; ASSERT_STREQ(result.data(), cipherText.data()) << "CCM encryption: cipher text doesn't match the expected value"; // Test AES_CCM_12_16 decryption result.clear(); ASSERT_TRUE(key.ccm_decrypt(&cipherText, ivBytes, sizeof ivBytes, tagLen, &result)) << "CCM decryption failed"; ASSERT_STREQ(result.data(), plainText.data()) << "CCM decryption: plain text doesn't match the expected value"; } #ifdef ENABLE_CHAT // Test functions of Ed25519: // - Binary & Hex fingerprints of public key // - Creation of signature for RSA public key // - Verification of signature for RSA public key // - Creation and verification of signatures for random messages // // (test vectors from 'authrigh_test.js', in Webclient) TEST(Crypto, Ed25519_Signing) { // string prEd255str = "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A="; // Base64 string prEd255str = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A="; // Base64 URL encoded // string puEd255str = "11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo"; // Base64 string puEd255str = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"; // Base64 URL encoded // string fpEd255str = "If4x36FUomFia/hUBG/SJxt77Us"; // Base64 string fpEd255str = "If4x36FUomFia_hUBG_SJxt77Us"; // Base64 url encoded string fpEd255hex = "21FE31DFA154A261626BF854046FD2271B7BED4B"; string pqstr = "1XJHwX9WYEVk7KOack5nhOgzgnYWrVdt0UY2yn5Lw38mPzkVn" "kHCmguqWIfL5bzVpbHHhlG9yHumvyyu9r1gKUMz4Y/1cf69" "1WIQmRGfg8dB2TeRUSvwb2A7EFGeFqQZHclgvpM2aq4PXrP" "PmQAciTxjguxcL1lem/fXGd1X6KKxPJ+UfQ5TZbV4O2aOwY" "uxys1YHh3mNHEp/xE1/fx292hdejPTJIX8IC5zjsss76e9P" "SVOgSrz+jQQYKbKpT5Yamml98bEZuLY9ncMGUmw5q4WHi/O" "dcvskHUydAL0qNOqbCwvt1Y7xIQfclR0SQE/AbwuJui0mt3" "PuGjM42T/DQ=="; string estr = "AQE="; string fpRSAstr = "GN2sWsukWnEarqVPS7mE5sPro38"; // Base64 url encoded string fpRSAhex = "18ddac5acba45a711aaea54f4bb984e6c3eba37f"; string sigRSAstr = "AAAAAFPqtrj3Qr4d83Oz/Ya6svzJfeoSBtWPC7KBU4" // Base64 "KqWMI8OX3eXT45+IyWCTTA5yeip/GThvkS8O2HBF" "aNLvSAFq5/5lQG"; uint64_t sigRSAts = 1407891128; // authring_test.js specify 1407891127650 ms, which is later rounded to seconds // Initialize variables const int keySeedLen = EdDSA::SEED_KEY_LENGTH; unsigned char keySeed[keySeedLen]; ASSERT_EQ(keySeedLen, Base64::atob(prEd255str.data(), keySeed, keySeedLen)) << "Failed to convert Ed25519 private key to binary"; PrnGen rng; EdDSA signkey(rng, keySeed); string puEd255bin; puEd255bin.resize(puEd255str.size() * 3 / 4 + 3); puEd255bin.resize(static_cast<size_t>(Base64::atob(puEd255str.data(), (byte*)puEd255bin.data(), static_cast<int>(puEd255bin.size())))); ASSERT_TRUE(!memcmp(puEd255bin.data(), signkey.pubKey, EdDSA::PUBLIC_KEY_LENGTH)) << "Public Ed25519 key doesn't match the derived public key"; // convert from Base64 to Base64 URL encoding std::replace(pqstr.begin(), pqstr.end(), '+', '-'); std::replace(pqstr.begin(), pqstr.end(), '/', '_'); string pqbin; pqbin.resize(pqstr.size() * 3 / 4 + 3); pqbin.resize(static_cast<size_t>( Base64::atob(pqstr.data(), (byte*)pqbin.data(), static_cast<int>(pqbin.size())))); string ebin; ebin.resize(estr.size() * 3 / 4 + 3); ebin.resize(static_cast<size_t>( Base64::atob(estr.data(), (byte*)ebin.data(), static_cast<int>(ebin.size())))); string pubRSAbin; pubRSAbin.append(pqbin.data(), pqbin.size()); pubRSAbin.append(ebin.data(), ebin.size()); // convert from Base64 to Base64 URL encoding std::replace(sigRSAstr.begin(), sigRSAstr.end(), '+', '-'); std::replace(sigRSAstr.begin(), sigRSAstr.end(), '/', '_'); string sigRSAbin; sigRSAbin.resize(sigRSAstr.size() * 4 / 3 + 4); sigRSAbin.resize(static_cast<size_t>(Base64::atob(sigRSAstr.data(), (byte*)sigRSAbin.data(), static_cast<int>(sigRSAbin.size())))); // ____ Check signature of RSA public key ____ string sigPubk; signkey.signKey((unsigned char *) pubRSAbin.data(), pubRSAbin.size(), &sigPubk, sigRSAts); ASSERT_EQ(sigRSAbin.size(), sigPubk.size()) << "Wrong size of signature"; ASSERT_TRUE(!memcmp(sigRSAbin.data(), sigPubk.data(), sigRSAbin.size())) << "RSA signatures don't match"; // ____ Verify signature of RSA public key ____ // good signature bool sigOK = EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigRSAbin, (unsigned char*) puEd255bin.data()); ASSERT_TRUE(sigOK) << "Verification of RSA signature failed."; // bad signature string sigBuf = sigRSAbin; sigBuf.at(70) = 42; sigOK = EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigBuf, (unsigned char*) puEd255bin.data()); ASSERT_FALSE(sigOK) << "Verification of bad RSA signature succeed when it should fail."; // empty signature sigBuf.clear(); sigOK = EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigBuf, (unsigned char*) puEd255bin.data()); ASSERT_FALSE(sigOK) << "Verification of empty RSA signature succeed when it should fail."; // bad timestamp sigBuf = sigRSAbin; sigBuf.at(0) = 42; sigOK = EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigBuf, (unsigned char*) puEd255bin.data()); ASSERT_FALSE(sigOK) << "Verification of RSA signature with wrong timestamp succeed when it should fail."; // signature with bad point sigBuf = sigRSAbin; sigBuf.at(8) = 42; sigOK = EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigBuf, (unsigned char*) puEd255bin.data()); ASSERT_FALSE(sigOK) << "Verification of RSA signature with bad point succeed when it should fail."; // ____ Create and verify signatures of random messages ____ const unsigned keylen = SymmCipher::KEYLENGTH; byte key[keylen]; string sig; for (int i = 0; i < 100; i++) { rng.genblock(key, keylen); signkey.signKey((unsigned char *) key, keylen, &sig); ASSERT_TRUE(EdDSA::verifyKey((unsigned char*) pubRSAbin.data(), pubRSAbin.size(), &sigRSAbin, (unsigned char*) puEd255bin.data())) << "Verification of signature failed for a random key."; } } #endif TEST(Crypto, SymmCipher_xorblock_bytes) { byte src[10] = { (byte)0, (byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6, (byte)7, (byte)8, (byte)9 }; byte dest[10] = { (byte)20, (byte)30, (byte)40, (byte)50, (byte)60, (byte)70, (byte)80, (byte)90, (byte)100, (byte)110 }; SymmCipher::xorblock(src, dest, sizeof(dest)); byte result[10] = { (byte)(0 ^ (byte)20), (byte)(1 ^ (byte)30), (byte)(2 ^ (byte)40), (byte)(3 ^ (byte)50), (byte)(4 ^ (byte)60), (byte)(5 ^ (byte)70), (byte)(6 ^ (byte)80), (byte)(7 ^ (byte)90), (byte)(8 ^ (byte)100), (byte)(9 ^ (byte)110) }; ASSERT_EQ(memcmp(dest, result, sizeof(dest)), 0); } TEST(Crypto, SymmCipher_xorblock_block_aligned) { byte src[SymmCipher::BLOCKSIZE]; byte n = 0; std::generate(src, src + sizeof(src), [&n]() {return n++; }); ASSERT_EQ(reinterpret_cast<ptrdiff_t>(src) % static_cast<ptrdiff_t>(sizeof(ptrdiff_t)), 0); byte dest[SymmCipher::BLOCKSIZE]; n = 100; std::generate(dest, dest + sizeof(src), [&n]() { return n = static_cast<byte>(n + 3); }); ASSERT_EQ(reinterpret_cast<ptrdiff_t>(dest) % static_cast<ptrdiff_t>(sizeof(ptrdiff_t)), 0); byte result[SymmCipher::BLOCKSIZE]; byte* output = result; std::transform(src, src + sizeof(src), dest, output, [](byte a, byte b) { return (byte)(a ^ b); }); SymmCipher::xorblock(src, dest); // aligned case ASSERT_EQ(memcmp(dest, result, sizeof(dest)), 0); } TEST(Crypto, SymmCipher_xorblock_block_unaligned) { byte src[SymmCipher::BLOCKSIZE + 1]; byte n = 0; std::generate(src, src + sizeof(src), [&n]() {return n++; }); byte dest[SymmCipher::BLOCKSIZE]; n = 100; std::generate(dest, dest + sizeof(dest), [&n]() { return n = static_cast<byte>(n + 3); }); byte result[SymmCipher::BLOCKSIZE]; byte* output = result; std::transform(src + 1, src + sizeof(src), dest, output, [](byte a, byte b) { return (byte)(a ^ b); }); SymmCipher::xorblock(src + 1, dest); // un-aligned case ASSERT_EQ(memcmp(dest, result, sizeof(dest)), 0); } // Test SymmCipher::isZeroKey // // Test whether a key is a zerokey or generated with a zerokey // Use different data structures (byte[], byte*, std::vector<byte>, std::string) // // 1) Test a 16-byte key all zeros - isZeroKey should be true // 2) Test a 16-byte key all ones - isZeroKey should be false // 3) Test a 32-byte key all zeros - isZeroKey should be true // 4) Test a 32-byte key all ones - isZeroKey should be true // 5) Test a 32-byte key half zeros, half ones - isZeroKey should be false // 6) Test a 32-byte key: "0123456789ABCDEF0123456789ABCDEF" - isZeroKey should be true //gitleaks:allow TEST(Crypto, SymmCipher_isZeroKey) { // 1) Test a 16-byte key all zeros - isZeroKey should be true byte key_test1[SymmCipher::BLOCKSIZE] = {}; ASSERT_EQ(SymmCipher::isZeroKey(key_test1, SymmCipher::BLOCKSIZE), true); // 2) Test a 16-byte key all ones - isZeroKey should be false auto key_test2 = std::make_unique<byte[]>(SymmCipher::BLOCKSIZE); std::memset(key_test2.get(), 1, SymmCipher::BLOCKSIZE); ASSERT_EQ(SymmCipher::isZeroKey(key_test2.get(), SymmCipher::BLOCKSIZE), false); // 3) Test a 32-byte key all zeros - isZeroKey should be true std::vector<byte> key_test3(FILENODEKEYLENGTH, 0); ASSERT_EQ(SymmCipher::isZeroKey(key_test3.data(), FILENODEKEYLENGTH), true); // 4) Test a 32-byte key all ones - isZeroKey should be true std::string key_test4(FILENODEKEYLENGTH, 1); ASSERT_EQ(SymmCipher::isZeroKey(reinterpret_cast<byte*>(key_test4.data()), FILENODEKEYLENGTH), true); // 5) Test a 32-byte key half zeros, half ones - isZeroKey should be false byte key_test5[FILENODEKEYLENGTH]; std::memset(key_test5, 0, SymmCipher::BLOCKSIZE); std::memset(key_test5 + SymmCipher::BLOCKSIZE, 1, SymmCipher::BLOCKSIZE); ASSERT_EQ(SymmCipher::isZeroKey(key_test5, FILENODEKEYLENGTH), false); // 6) Test a 32-byte key: "0123456789ABCDEF0123456789ABCDEF" - isZeroKey should be true //gitleaks:allow std::string key_test6; key_test6.resize(FILENODEKEYLENGTH); key_test6.replace(0, SymmCipher::BLOCKSIZE, "0123456789ABCDEF"); key_test6.replace(SymmCipher::BLOCKSIZE, SymmCipher::BLOCKSIZE, "0123456789ABCDEF"); ASSERT_EQ(SymmCipher::isZeroKey(reinterpret_cast<byte*>(key_test6.data()), FILENODEKEYLENGTH), true); } using CryptoPP::AutoSeededRandomPool; namespace { AutoSeededRandomPool rng; std::vector<byte> randomBytes(const size_t n) { std::vector<byte> buf(n); rng.GenerateBlock(buf.data(), buf.size()); return buf; } ::testing::AssertionResult equalBuf(const void* const a, const void* const b, const size_t len, const char* msg) { if (std::memcmp(a, b, len) == 0) return ::testing::AssertionSuccess(); return ::testing::AssertionFailure() << msg; } } // namespace TEST(Crypto, SymmCipher_CbcEncryptDecryptWithKey) { SymmCipher cipher; const std::string plain = "Somewhere in la Mancha, in a place whose name I do not care to remember"; std::string cipherText, recovered; const auto key = randomBytes(SymmCipher::KEYLENGTH); const auto iv = randomBytes(SymmCipher::BLOCKSIZE); ASSERT_TRUE(cipher.cbc_encrypt_with_key(plain, cipherText, key.data(), key.size(), iv.data())); ASSERT_TRUE( cipher.cbc_decrypt_with_key(cipherText, recovered, key.data(), key.size(), iv.data())); EXPECT_EQ(plain, recovered); } TEST(Crypto, SymmCipher_CbcEncryptWithZeroKeyLenFails) { SymmCipher cipher; const std::string plain = "irrelevant"; std::string cipherText; const auto key = randomBytes(SymmCipher::KEYLENGTH); const auto iv = randomBytes(SymmCipher::BLOCKSIZE); const auto ok = cipher.cbc_encrypt_with_key(plain, cipherText, key.data(), /*keylen*/ 0, iv.data()); EXPECT_FALSE(ok); } TEST(Crypto, SymmCipher_GcmEncryptDecryptReuseKey) { SymmCipher cipher; // set the internal key once const auto internalKey = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(internalKey.data()); const std::string plain = "Chat message with Paco"; auto testFunc = [&cipher, &plain](unsigned int tagLen) { std::string cipherText, recovered; const auto iv = randomBytes(12); // 96-bit nonce ASSERT_TRUE(cipher.gcm_encrypt(&plain, iv.data(), static_cast<unsigned>(iv.size()), tagLen, &cipherText)); ASSERT_TRUE(cipher.gcm_decrypt(&cipherText, iv.data(), static_cast<unsigned>(iv.size()), tagLen, &recovered)); EXPECT_EQ(plain, recovered); }; testFunc(16); testFunc(12); } TEST(Crypto, SymmCipher_GcmEncryptDecryptReuseKeyWithAAD) { SymmCipher cipher; const auto key = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(key.data()); const std::string plain = "Chat message with Paco"; constexpr byte aad[] = {1, 2, 3, 4}; const auto iv = randomBytes(12); // 96-bit nonce constexpr auto tagLen = 16u; std::string cipherText; ASSERT_TRUE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); const auto ctLen = cipherText.size() - tagLen; const byte* ctPtr = reinterpret_cast<const byte*>(cipherText.data()); const byte* tagPtr = ctPtr + ctLen; std::vector<byte> recoveredBuf(plain.size()); ASSERT_TRUE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); std::string recovered(recoveredBuf.begin(), recoveredBuf.end()); EXPECT_EQ(plain, recovered); } TEST(Crypto, SymmCipher_GcmEncryptReuseKeyWithAAD_ReturnFalse) { SymmCipher cipher; const auto key = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(key.data()); const std::string plain = "Test Encryption Return false"; constexpr byte aad[] = {1, 2, 3, 4}; const auto iv = randomBytes(12); // 96-bit nonce constexpr auto tagLen = 16u; std::string cipherText; // data is null EXPECT_FALSE(cipher.gcm_encrypt_add(nullptr, plain.size(), aad, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); // data size is 0 EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), 0, aad, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); // AAD is null EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), nullptr, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); // AAD size is 0 EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, 0, iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); // iv is null EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), nullptr, iv.size(), tagLen, cipherText, /*expectedSize =*/0)); // iv size is 0 EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), iv.data(), 0, tagLen, cipherText, /*expectedSize =*/0)); // tag length is 0 EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), iv.data(), iv.size(), 0, cipherText, /*expectedSize =*/0)); // expected size is not equal to cipher text size EXPECT_FALSE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/1)); } TEST(Crypto, SymmCipher_GcmDecryptReuseKeyWithAAD_ReturnFalse) { SymmCipher cipher; const auto key = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(key.data()); const std::string plain = "Test Decryption Return false"; constexpr byte aad[] = {1, 2, 3, 4}; const auto iv = randomBytes(12); // 96-bit nonce constexpr auto tagLen = 16u; std::string cipherText; std::vector<byte> recoveredBuf(plain.size()); ASSERT_TRUE(cipher.gcm_encrypt_add(reinterpret_cast<const byte*>(plain.data()), plain.size(), aad, sizeof(aad), iv.data(), iv.size(), tagLen, cipherText, /*expectedSize =*/0)); const auto ctLen = cipherText.size() - tagLen; const byte* ctPtr = reinterpret_cast<const byte*>(cipherText.data()); const byte* tagPtr = ctPtr + ctLen; // data is null EXPECT_FALSE(cipher.gcm_decrypt_add(nullptr, ctLen, aad, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // data size is 0 EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, 0, aad, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // AAD is null EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, nullptr, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // AAD size is 0 EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, 0, tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // tag is null EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), nullptr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // tag size is 0 EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, 0, iv.data(), iv.size(), recoveredBuf.data(), recoveredBuf.size())); // iv is null EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, tagLen, nullptr, iv.size(), recoveredBuf.data(), recoveredBuf.size())); // iv size is 0 EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, tagLen, iv.data(), 0, recoveredBuf.data(), recoveredBuf.size())); // result buf is null EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), nullptr, recoveredBuf.size())); // result buf size is 0 EXPECT_FALSE(cipher.gcm_decrypt_add(ctPtr, ctLen, aad, sizeof(aad), tagPtr, tagLen, iv.data(), iv.size(), recoveredBuf.data(), 0)); } TEST(Crypto, SymmCipher_KeyRotationBreaksOldCipher) { SymmCipher cipher; const auto iv = randomBytes(SymmCipher::BLOCKSIZE); // first key const auto k1 = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(k1.data()); constexpr byte kPlain[SymmCipher::BLOCKSIZE] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; byte data1[SymmCipher::BLOCKSIZE]; std::memcpy(data1, kPlain, SymmCipher::BLOCKSIZE); cipher.cbc_encrypt(data1, sizeof(data1), iv.data()); // second key const auto k2 = randomBytes(SymmCipher::KEYLENGTH); cipher.setkey(k2.data()); byte tmp[SymmCipher::BLOCKSIZE]; std::memcpy(tmp, data1, sizeof(tmp)); ASSERT_TRUE(cipher.cbc_decrypt(tmp, sizeof(tmp), iv.data())); EXPECT_FALSE(equalBuf(tmp, kPlain, SymmCipher::BLOCKSIZE, "Decrypt unexpectedly produced the right data")); cipher.setkey(k1.data()); ASSERT_TRUE(cipher.cbc_decrypt(data1, sizeof(data1), iv.data())); EXPECT_TRUE(equalBuf(data1, kPlain, SymmCipher::BLOCKSIZE, "Round-trip failed")); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/DefaultedDbTable.h�����������������������������������������������������������0000664�0000000�0000000�00000013622�15162662266�0020060�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2020 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <mega/db.h> #include "NotImplemented.h" namespace mt { class DefaultedDbTable: public mega::DbTable, public mega::DBTableNodes { public: using mega::DbTable::DbTable; DefaultedDbTable(mega::PrnGen& gen) : DbTable(gen, false, nullptr) { } void rewind() override { //throw NotImplemented{__func__}; } bool next(uint32_t*, std::string*) override { return false; //throw NotImplemented{__func__}; } bool get(uint32_t, std::string*) override { return false; //throw NotImplemented{__func__}; } bool getNode(mega::NodeHandle, mega::NodeSerialized&) override { return false; //throw NotImplemented{__func__}; } bool getNodeByFingerprint(const std::string&, mega::NodeSerialized&, mega::NodeHandle&) override { return false; } bool getNodesByOrigFingerprint(const std::string&, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&) override { return false; } bool getRootNodes(std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&) override { return false; //throw NotImplemented(__func__); } bool getNodesWithSharesOrLink(std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&, mega::ShareType_t) override { return false; } bool getNodesByFingerprintNoMtime( const std::string&, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&) override { return false; } uint64_t getNumberOfChildren(mega::NodeHandle /*parentHandle*/) override { return 0; } bool getChildren(const mega::NodeSearchFilter&, int, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&, mega::CancelToken, const mega::NodeSearchPage&) override { return false; //throw NotImplemented(__func__); } bool listChildNodesLexicographically( const mega::handle, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&, mega::CancelToken, const size_t, const std::optional<mega::NodeSearchLexicographicalOffset>&) override { return false; // throw NotImplemented(__func__); } bool searchNodes(const mega::NodeSearchFilter&, int, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&, mega::CancelToken, const mega::NodeSearchPage&) override { return false; //throw NotImplemented(__func__); } auto getNodeTagsBelow(mega::CancelToken, mega::NodeHandle, const std::string&) -> std::optional<std::set<std::string>> override { return std::nullopt; } bool getRecentNodes(const mega::NodeSearchPage&, mega::m_time_t /*since*/, std::vector<std::pair<mega::NodeHandle, mega::NodeSerialized>>&) override { return false; } bool getFavouritesHandles(mega::NodeHandle, uint32_t, std::vector<mega::NodeHandle>&) override { return false; } bool childNodeByNameType(mega::NodeHandle, const std::string& /*name*/, mega::nodetype_t, std::pair<mega::NodeHandle, mega::NodeSerialized>&) override { return false; } bool getNodeSizeTypeAndFlags(mega::NodeHandle, m_off_t& /*size*/, mega::nodetype_t&, uint64_t& /*oldFlags*/) override { return false; } bool isAncestor(mega::NodeHandle, mega::NodeHandle, mega::CancelToken) override { return false; } uint64_t getNumberOfNodes() override { return false; } uint64_t getNumberOfChildrenByType(mega::NodeHandle /*parentHandle*/, mega::nodetype_t) override { return 0; } void updateCounter(mega::NodeHandle, const std::string&) override {} void updateCounterAndFlags(mega::NodeHandle, uint64_t /*flags*/, const std::string& /*nodeCounterBlob*/) override {} void createIndexes(bool /*enableIndexesForSearching*/, bool /*enableIndexesForLexicographicalList*/) override {} void dropSearchDBIndexes() override {} void dropLexicographicDBIndexes() override {} bool put(uint32_t, char*, unsigned) override { return false; //throw NotImplemented{__func__}; } bool put(mega::Node *) override { return false; //throw NotImplemented{__func__}; } bool del(uint32_t) override { return false; //throw NotImplemented{__func__}; } bool remove(mega::NodeHandle) override { return false; //throw NotImplemented{__func__}; } bool removeNodes() override { return false; //throw NotImplemented{__func__}; } void truncate() override { //throw NotImplemented{__func__}; } void begin() override { //throw NotImplemented{__func__}; } void commit() override { //throw NotImplemented{__func__}; } void abort() override { // throw NotImplemented{__func__}; } void remove() override { //throw NotImplemented{__func__}; } }; } // mt ��������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/DefaultedDirAccess.h���������������������������������������������������������0000664�0000000�0000000�00000002001�15162662266�0020410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <mega/filesystem.h> #include "NotImplemented.h" namespace mt { class DefaultedDirAccess : public mega::DirAccess { public: bool dopen(mega::LocalPath*, mega::FileAccess*, bool) override { throw NotImplemented{__func__}; } bool dnext(mega::LocalPath&, mega::LocalPath&, bool = true, mega::nodetype_t* = NULL) override { throw NotImplemented{__func__}; } }; } // mt �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/DefaultedFileAccess.h��������������������������������������������������������0000664�0000000�0000000�00000005766�15162662266�0020576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <mega/filesystem.h> #include "NotImplemented.h" namespace mt { using ::mega::FSLogging; class DefaultedFileAccess: public mega::FileAccess { public: DefaultedFileAccess(): mega::FileAccess{nullptr} {} virtual bool fopen(const ::mega::LocalPath&, ::mega::OpenFlag, FSLogging, ::mega::DirAccess* = nullptr, bool = false, bool = false, ::mega::LocalPath* = nullptr) override { throw NotImplemented{__func__}; return false; } void updatelocalname(const ::mega::LocalPath&, bool) override { throw NotImplemented{__func__}; } virtual bool frawread(void*, unsigned long, m_off_t, bool, FSLogging, bool* = nullptr) override { throw NotImplemented{__func__}; return false; } virtual bool openf(FSLogging) override { throw NotImplemented{__func__}; return false; } virtual void fclose() override { throw NotImplemented{__func__}; } virtual bool fwrite(const void*, unsigned long, m_off_t, unsigned long* = nullptr, bool* = nullptr) override { throw NotImplemented{__func__}; return false; } virtual bool fstat(::mega::m_time_t&, m_off_t&) override { throw NotImplemented{__func__}; return false; } virtual bool ftruncate(m_off_t = 0) override { throw NotImplemented{__func__}; return false; } virtual bool setSparse() override { throw NotImplemented{__func__}; return false; } virtual std::optional<std::pair<std::uint64_t, std::uint64_t>> getFileSize() const override { throw NotImplemented{__func__}; return std::nullopt; } virtual bool sysread(void*, unsigned long, m_off_t, bool*) override { throw NotImplemented{__func__}; return false; } virtual bool sysstat(::mega::m_time_t*, m_off_t*, FSLogging) override { throw NotImplemented{__func__}; } virtual bool sysopen(bool, FSLogging) override { throw NotImplemented{__func__}; return false; } virtual void sysclose() override { throw NotImplemented{__func__}; } }; } // mt ����������sdk-10.11.0/tests/unit/DefaultedFileSystemAccess.h��������������������������������������������������0000664�0000000�0000000�00000005572�15162662266�0021776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <mega/filesystem.h> #include <megaapi.h> #include "NotImplemented.h" namespace mt { class DefaultedFileSystemAccess: public mega::FileSystemAccess { public: using FileSystemAccess::getlocalfstype; bool issyncsupported(const mega::LocalPath&, bool& b, mega::SyncError& se, mega::SyncWarning& sw) override { b = false; se = mega::NO_SYNC_ERROR; sw = mega::NO_SYNC_WARNING; return true; } DefaultedFileSystemAccess() {} std::unique_ptr<mega::FileAccess> newfileaccess(bool /*followSymLinks*/ = true) override { throw NotImplemented{__func__}; } std::unique_ptr<mega::DirAccess> newdiraccess() override { throw NotImplemented{__func__}; } bool getlocalfstype(const mega::LocalPath&, mega::FileSystemType& type) const override { return type = mega::FS_UNKNOWN, false; } bool getsname(const mega::LocalPath&, mega::LocalPath&) const override { throw NotImplemented{__func__}; } bool renamelocal(const mega::LocalPath&, const mega::LocalPath&, bool = true) override { throw NotImplemented{__func__}; } bool copylocal(const mega::LocalPath&, const mega::LocalPath&, mega::m_time_t) override { throw NotImplemented{__func__}; } bool unlinklocal(const mega::LocalPath&) override { throw NotImplemented{__func__}; } bool rmdirlocal(const mega::LocalPath&) override { throw NotImplemented{__func__}; } bool mkdirlocal(const mega::LocalPath&, bool, bool) override { throw NotImplemented{__func__}; } bool setmtimelocal(const mega::LocalPath&, mega::m_time_t) override { throw NotImplemented{__func__}; } bool chdirlocal(mega::LocalPath&) const override { throw NotImplemented{__func__}; } bool expanselocalpath(const mega::LocalPath& /*path*/, mega::LocalPath& /*absolutepath*/) override { throw NotImplemented{__func__}; } void addevents(mega::Waiter*, int) override { throw NotImplemented{__func__}; } bool cwd(mega::LocalPath&) const override { throw NotImplemented{__func__}; } }; } // mt ��������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/FileFingerprint_CRC_test.cpp�������������������������������������������������0000664�0000000�0000000�00000026002�15162662266�0022111�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// === Unified CRC tests: production (IA/FA) + 32-bit-overflow emulation ======= #include "DefaultedFileAccess.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/base64.h> #include <mega/crypto/cryptopp.h> #include <bitset> #if !defined(_WIN32) #include <arpa/inet.h> // htonl #endif namespace { using ::mega::byte; using ::testing::ContainerEq; using CRCLanes = std::array<std::uint32_t, 4>; constexpr std::uint64_t operator"" _MiB(const unsigned long long n) noexcept { return n * 1024ull * 1024ull; } struct Layout { static constexpr unsigned kLanes{4}; static constexpr unsigned kBlocks{32}; static constexpr unsigned kWindowBytes{64}; // bytes per sampled window static constexpr unsigned kDenominator{kLanes * kBlocks - 1}; // 127 static constexpr std::uint64_t kWindowU{kWindowBytes}; }; constexpr std::uint32_t kDeterministicSeed{0xA5A5A5A5u}; // stable non-trivial PRNG seed constexpr std::int64_t kTestMtimeSecs{1'700'000'000}; constexpr std::size_t kCrcBytes{Layout::kLanes * 4}; constexpr std::uint32_t kEqMask40MiB{0b0111u}; constexpr std::uint32_t kEqMask52MiB{0b0011u}; constexpr std::uint32_t kEqMask88MiB{0b0001u}; // ---------- Minimal in-memory IA and FA (exercise production code) ----------- class MemIA final: public ::mega::InputStreamAccess { public: explicit MemIA(const std::vector<byte>& data): mData(data) {} m_off_t size() override { return static_cast<m_off_t>(mData.size()); } bool read(byte* buffer, const unsigned n) override { if (!buffer) { // skip/seek forward by n if (mPos + n > mData.size()) return false; mPos += n; return true; } if (mPos + n > mData.size()) return false; std::memcpy(buffer, &mData[mPos], n); mPos += n; return true; } private: const std::vector<byte>& mData; std::size_t mPos{0}; }; class MemFA final: public ::mt::DefaultedFileAccess { public: MemFA(const std::vector<byte>& data, const ::mega::m_time_t mt): mData(data) { mtime = mt; size = static_cast<m_off_t>(data.size()); } bool openf(::mega::FSLogging) override { mIsOpen = true; return true; } void closef() override { mIsOpen = false; } bool frawread(void* buf, unsigned long n, m_off_t off, bool /*nolock*/, ::mega::FSLogging, bool* /*retry*/ = nullptr) override { if (!mIsOpen || off < 0) return false; const auto nbytes = static_cast<std::size_t>(n); const auto offsz = static_cast<std::size_t>(off); if (offsz > mData.size() || nbytes > (mData.size() - offsz)) return false; if (buf) std::memcpy(buf, mData.data() + offsz, nbytes); return true; } private: bool mIsOpen{false}; const std::vector<byte>& mData; }; // --------- Utilities --------------------------------------------------------- template<typename T, std::size_t N, typename Mask = std::uint32_t> [[nodiscard]] inline Mask laneEqMaskBitset(const std::array<T, N>& a, const std::array<T, N>& b) { static_assert(N <= std::numeric_limits<Mask>::digits, "Mask too narrow for number of lanes; use a wider Mask."); std::bitset<N> bits; for (std::size_t i = 0; i < N; ++i) bits.set(i, a[i] == b[i]); return static_cast<Mask>(bits.to_ulong()); } [[nodiscard]] inline std::uint32_t htonl_u32(const std::uint32_t x) noexcept { #if defined(_WIN32) return _byteswap_ulong(x); #else return htonl(x); #endif } // Extract the 22-char CRC b64 from size:mtime:CRC:valid [[nodiscard]] std::string crcB64FromDbg(const std::string& dbg) { const auto p1 = dbg.find(':'); if (p1 == std::string::npos) return {}; const auto p2 = dbg.find(':', p1 + 1); if (p2 == std::string::npos) return {}; const auto p3 = dbg.find(':', p2 + 1); if (p3 == std::string::npos) return {}; return dbg.substr(p2 + 1, p3 - (p2 + 1)); } [[nodiscard]] CRCLanes b64ToLanesHost(const std::string& b64) { CRCLanes out{}; byte buf[kCrcBytes]{}; const auto n = ::mega::Base64::atob(b64.c_str(), buf, static_cast<int>(kCrcBytes)); if (n == static_cast<int>(kCrcBytes)) { std::memcpy(out.data(), buf, kCrcBytes); // stored as host-endian words } return out; } [[nodiscard]] std::string lanesToB64(const CRCLanes& lanesHost) { byte raw[kCrcBytes]; std::memcpy(raw, lanesHost.data(), kCrcBytes); // base64 output capacity = 4 * ceil(N / 3) const auto cap = static_cast<std::size_t>(4 * ((kCrcBytes + 2) / 3)); std::string out(cap, '\0'); const auto outSize = ::mega::Base64::btoa(raw, kCrcBytes, out.data()); out.resize(static_cast<std::size_t>(outSize)); return out; } // Deterministic PRNG (xorshift32) for fully stable bytes across platforms void fillDeterministic(std::vector<byte>& buf, const std::uint32_t seed = kDeterministicSeed) { std::uint32_t x = seed; for (auto& b: buf) { x ^= x << 13; x ^= x >> 17; x ^= x << 5; b = static_cast<byte>(x & 0xFF); } } // ---------- Buggy 32-bit overflow emulation (for comparison) ----------------- [[nodiscard]] inline std::uint64_t sparseOffset64(const std::uint64_t size, const unsigned lane, const unsigned j) noexcept { const std::uint64_t idx = std::uint64_t(lane) * Layout::kBlocks + j; const std::uint64_t numer = (size - Layout::kWindowU) * idx; // 64-bit multiply const std::uint64_t off = Layout::kDenominator ? (numer / Layout::kDenominator) : 0; const std::uint64_t max = size - Layout::kWindowU; return off > max ? max : off; } // Emulates the 32-bit multiply (overflow) & 32-bit divide bug [[nodiscard]] inline std::uint64_t sparseOffset32_bug(const std::uint64_t size, const unsigned lane, const unsigned j) noexcept { const std::uint32_t sz32 = static_cast<std::uint32_t>(size); const std::uint32_t idx32 = static_cast<std::uint32_t>(lane * Layout::kBlocks + j); const std::uint32_t numer = static_cast<std::uint32_t>((sz32 - Layout::kWindowBytes) * idx32); // wraps const std::uint32_t off32 = Layout::kDenominator ? (numer / Layout::kDenominator) : 0; const std::uint64_t max = size - Layout::kWindowU; return off32 > max ? max : off32; } void computeCrcFromBytes(const std::vector<byte>& data, const bool use64Fix, CRCLanes& lanesHost_out) { for (unsigned li = 0; li < Layout::kLanes; ++li) { ::mega::HashCRC32 crc; for (unsigned j = 0; j < Layout::kBlocks; ++j) { const auto off = use64Fix ? sparseOffset64(static_cast<std::uint64_t>(data.size()), li, j) : sparseOffset32_bug(static_cast<std::uint64_t>(data.size()), li, j); crc.add(&data[static_cast<std::size_t>(off)], Layout::kWindowBytes); } std::int32_t v{0}; crc.get(reinterpret_cast<byte*>(&v)); lanesHost_out[li] = htonl_u32(static_cast<std::uint32_t>(v)); // match cloud packing } } [[nodiscard]] inline CRCLanes computeCrcFromBytes(const std::vector<byte>& data, const bool use64Fix) { CRCLanes lanes{}; computeCrcFromBytes(data, use64Fix, lanes); return lanes; } // ---------- Shared helper to compute + check one synthetic case -------------- struct SynthResult { std::string goodB64; std::string bugB64; }; void runOneSyntheticCase(const std::uint64_t sizeBytes, const std::uint32_t seed, const std::string_view label, const std::uint32_t expectedEqMask, SynthResult& out) { // Create deterministic data std::vector<byte> data(static_cast<std::size_t>(sizeBytes)); fillDeterministic(data, seed); // Production (IA) std::string goodB64_IA; { MemIA ia(data); ::mega::FileFingerprint fp; ASSERT_TRUE(fp.genfingerprint(&ia, /*cmtime*/ kTestMtimeSecs, /*ignoremtime*/ false)); goodB64_IA = crcB64FromDbg(fp.fingerprintDebugString()); } // Production (FA) std::string goodB64_FA; { MemFA fa(data, /*mtime*/ kTestMtimeSecs); ::mega::FileFingerprint fp; ASSERT_TRUE(fp.genfingerprint(&fa, /*ignoremtime*/ false)); goodB64_FA = crcB64FromDbg(fp.fingerprintDebugString()); } // Reference "good" emulation via helper (64-bit math) const auto goodCRClanes = computeCrcFromBytes(data, /*use64Fix=*/true); const auto goodB64_ref = lanesToB64(goodCRClanes); { const auto goodHostLanesFromB64 = b64ToLanesHost(goodB64_ref); ASSERT_THAT(goodCRClanes, ContainerEq(goodHostLanesFromB64)); } EXPECT_EQ(goodB64_IA, goodB64_ref) << "IA/ref mismatch for " << label; EXPECT_EQ(goodB64_FA, goodB64_ref) << "FA/ref mismatch for " << label; EXPECT_EQ(goodB64_IA, goodB64_FA) << "IA/FA mismatch for " << label; // Buggy emulation const auto bugCRClanes = computeCrcFromBytes(data, /*use64Fix=*/false); const auto bugB64 = lanesToB64(bugCRClanes); { const auto badHostLanesFromB64 = b64ToLanesHost(bugB64); ASSERT_THAT(bugCRClanes, ContainerEq(badHostLanesFromB64)); } EXPECT_NE(goodB64_ref, bugB64) << "Buggy CRC should differ for " << label; const auto eqMask = laneEqMaskBitset(goodCRClanes, bugCRClanes); EXPECT_EQ(eqMask, expectedEqMask) << "Unexpected lane pattern for " << label; out.goodB64 = goodB64_ref; out.bugB64 = bugB64; } } // namespace TEST(FileFingerprint, CRC64Fix_Synth_40MiB_GoodVsBuggy) { SynthResult r; ASSERT_NO_FATAL_FAILURE( runOneSyntheticCase(40_MiB, kDeterministicSeed, "40MiB", kEqMask40MiB, r)); static constexpr const char* kGood{"6iqpUy7DdAKx5NIRg31i_g"}; static constexpr const char* kBug{"6iqpUy7DdAKx5NIRGX1AAA"}; EXPECT_EQ(r.goodB64, kGood); EXPECT_EQ(r.bugB64, kBug); } TEST(FileFingerprint, CRC64Fix_Synth_52MiB_GoodVsBuggy) { SynthResult r; ASSERT_NO_FATAL_FAILURE( runOneSyntheticCase(52_MiB, kDeterministicSeed, "52MiB", kEqMask52MiB, r)); static constexpr const char* kGood{"7SMVr_-v9_H7MDsN9yuVGA"}; static constexpr const char* kBug{"7SMVr_-v9_Gk00B4SWd30g"}; EXPECT_EQ(r.goodB64, kGood); EXPECT_EQ(r.bugB64, kBug); } TEST(FileFingerprint, CRC64Fix_Synth_88MiB_GoodVsBuggy) { SynthResult r; ASSERT_NO_FATAL_FAILURE( runOneSyntheticCase(88_MiB, kDeterministicSeed, "88MiB", kEqMask88MiB, r)); static constexpr const char* kGood{"3hhTVPVhwzudmjN1odbO6w"}; static constexpr const char* kBug{"3hhTVIMatxXS_18ZkPyITg"}; EXPECT_EQ(r.goodB64, kGood); EXPECT_EQ(r.bugB64, kBug); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/FileFingerprint_test.cpp�����������������������������������������������������0000664�0000000�0000000�00000036125�15162662266�0021431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <array> #include <memory> #include <numeric> #include <string> #include <gtest/gtest.h> #include <mega/filefingerprint.h> #include "DefaultedFileAccess.h" namespace { //class MockFileAccess : public mt::DefaultedFileAccess //{ //public: // MockFileAccess(const mega::m_time_t mtime, std::vector<mega::byte> content, const bool readFails = false) // : mContent{std::move(content)} // , mReadFails{readFails} // { // this->size = mContent.size(); // this->mtime = mtime; // } // // MEGA_DISABLE_COPY_MOVE(MockFileAccess) // // bool sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) override // { // *curr_mtime = mtime; // *curr_size = size; // return true; // } // // bool sysopen(bool async = false) override // { // return true; // } // // bool sysread(mega::byte* buffer, const unsigned size, const m_off_t offset) override // { // if (mReadFails) // { // return false; // } // assert(static_cast<unsigned>(offset) + size <= mContent.size()); // std::copy(mContent.begin() + static_cast<unsigned>(offset), mContent.begin() + static_cast<unsigned>(offset) + size, buffer); // return true; // } // // void sysclose() override // {} // // bool getReadFails() const // { // return mReadFails; // } // //private: // const std::vector<mega::byte> mContent; // const bool mReadFails = false; //}; // //class MockInputStreamAccess : public mega::InputStreamAccess //{ //public: // MockInputStreamAccess(const mega::m_time_t mtime, std::vector<mega::byte> content, const bool readFails = false) // : mFa{mtime, std::move(content), readFails} // {} // // mega::m_time_t getMTime() const // { // return mFa.mtime; // } // // void setSize(const m_off_t size) // { // mFa.size = size; // } // // m_off_t size() override // { // return mFa.size; // } // // bool read(mega::byte* buffer, const unsigned size) override // { // if (mFa.getReadFails()) // { // return false; // } // if (!buffer) // { // return true; // } // return mFa.frawread(buffer, size, 0); // } // //private: // MockFileAccess mFa; //}; } // anonymous TEST(FileFingerprint, FileFingerprintCmp_compareNotSmaller) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; mega::FileFingerprint copiedFfp; copiedFfp = ffp; ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &copiedFfp)); } TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfSize) { mega::FileFingerprint ffp; ffp.size = 1; mega::FileFingerprint ffp2; ffp2.size = 2; ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); } TEST(FileFingerprint, FileFingerprintCmp_compareNotSmallerBecauseOfSize) { mega::FileFingerprint ffp; ffp.size = 2; mega::FileFingerprint ffp2; ffp2.size = 1; ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); } TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfMTime) { mega::FileFingerprint ffp; ffp.mtime = 1; mega::FileFingerprint ffp2; ffp2.mtime = 2; ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); } TEST(FileFingerprint, FileFingerprintCmp_compareNotSmallerBecauseOfMTime) { mega::FileFingerprint ffp; ffp.mtime = 2; mega::FileFingerprint ffp2; ffp2.mtime = 1; ASSERT_FALSE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); } TEST(FileFingerprint, FileFingerprintCmp_compareSmallerBecauseOfCrc) { mega::FileFingerprint ffp; ffp.crc[0] = 1; mega::FileFingerprint ffp2; ffp2.crc[0] = 2; ASSERT_TRUE(mega::FileFingerprintCmp{}(&ffp, &ffp2)); } TEST(FileFingerprint, defaultConstructor) { const mega::FileFingerprint ffp; ASSERT_EQ(-1, ffp.size); ASSERT_EQ(0, ffp.mtime); const mega::FingerprintCrc expected = {0, 0, 0, 0}; ASSERT_EQ(expected, ffp.crc); ASSERT_EQ(false, ffp.isvalid); } TEST(FileFingerprint, copyAssignment) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; mega::FileFingerprint copiedFfp; copiedFfp = ffp; ASSERT_EQ(copiedFfp.size, ffp.size); ASSERT_EQ(copiedFfp.mtime, ffp.mtime); ASSERT_EQ(copiedFfp.crc, ffp.crc); ASSERT_EQ(copiedFfp.isvalid, ffp.isvalid); } TEST(FileFingerprint, copyConstructor) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; const mega::FileFingerprint copiedFfp{ffp}; ASSERT_EQ(copiedFfp.size, ffp.size); ASSERT_EQ(copiedFfp.mtime, ffp.mtime); ASSERT_EQ(copiedFfp.crc, ffp.crc); ASSERT_EQ(copiedFfp.isvalid, ffp.isvalid); } TEST(FileFingerprint, comparisonOperator_compareEqual) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; mega::FileFingerprint copiedFfp; copiedFfp = ffp; ASSERT_TRUE(ffp == copiedFfp); } TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfSize) { mega::FileFingerprint ffp; ffp.isvalid = true; ffp.size = 1; mega::FileFingerprint ffp2; ffp2.isvalid = true; ASSERT_FALSE(ffp == ffp2); } #ifndef __ANDROID__ TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfMTime) { mega::FileFingerprint ffp; ffp.isvalid = true; ffp.mtime = mega::FS_MTIME_TOLERANCE_SECS + 1; // difference must be at least 3 mega::FileFingerprint ffp2; ffp2.isvalid = true; ASSERT_FALSE(ffp == ffp2); } #endif TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfValid) { mega::FileFingerprint ffp; ffp.isvalid = false; mega::FileFingerprint ffp2; ffp2.isvalid = true; ASSERT_TRUE(ffp != ffp2); } TEST(FileFingerprint, comparisonOperator_compareNotEqualBecauseOfCrc) { mega::FileFingerprint ffp; ffp.isvalid = true; ffp.crc[0] = 1; mega::FileFingerprint ffp2; ffp2.isvalid = true; ASSERT_FALSE(ffp == ffp2); } TEST(FileFingerprint, serialize_unserialize) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; std::string data; ASSERT_TRUE(ffp.serialize(&data)); const char* start = data.data(); auto ffp2 = mega::FileFingerprint::unserialize(start, start + data.size()); ASSERT_EQ(ffp2->size, ffp.size); ASSERT_EQ(ffp2->mtime, ffp.mtime); ASSERT_EQ(ffp2->crc, ffp.crc); ASSERT_EQ(ffp2->isvalid, ffp.isvalid); } TEST(FileFingerprint, unserialize_32bit) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; // This is the result of serialization on 32bit Windows const std::array<char, 33> rawData = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01 }; const char* start = rawData.data(); auto ffp2 = mega::FileFingerprint::unserialize(start, start + rawData.size()); ASSERT_EQ(ffp2->size, ffp.size); ASSERT_EQ(ffp2->mtime, ffp.mtime); ASSERT_EQ(ffp2->crc, ffp.crc); ASSERT_EQ(ffp2->isvalid, ffp.isvalid); } TEST(FileFingerprint, unserialize_butStringTooShort) { std::string data = "blah"; const char* start = data.data(); ASSERT_EQ(nullptr, mega::FileFingerprint::unserialize(start, start + data.size())); } TEST(FileFingerprint, serializefingerprint_unserializefingerprint) { mega::FileFingerprint ffp; ffp.size = 1; ffp.mtime = 2; std::iota(ffp.crc.begin(), ffp.crc.end(), 3); ffp.isvalid = true; std::string data; ffp.serializefingerprint(&data); mega::FileFingerprint ffp2; ASSERT_TRUE(ffp2.unserializefingerprint(&data)); ASSERT_EQ(ffp2.size, -1); // it is not clear why `size` is dealed with ASSERT_EQ(ffp2.mtime, ffp.mtime); ASSERT_EQ(ffp2.crc, ffp.crc); ASSERT_EQ(ffp2.isvalid, ffp.isvalid); } //TEST(FileFingerprint, genfingerprint_FileAccess_forTinyFile) //{ // mega::FileFingerprint ffp; // MockFileAccess fa{1, {3, 4, 5, 6}}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(4, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {100992003, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_FileAccess_forTinyFile_butReadFails) //{ // mega::FileFingerprint ffp; // MockFileAccess fa{1, {3, 4, 5, 6}, true}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_FileAccess_forSmallFile) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(100); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockFileAccess fa{1, std::move(content)}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(100, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {215253208, 661795201, 937191950, 562141813}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_FileAccess_forSmallFile_butReadFails) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(100); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockFileAccess fa{1, std::move(content), true}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_FileAccess_forLargeFile) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(20000); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockFileAccess fa{1, std::move(content)}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(20000, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {-1424885571, 1204627086, 1194313128, -177560448}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_FileAccess_forLargeFile_butReadFails) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(20000); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockFileAccess fa{1, std::move(content), true}; // ASSERT_TRUE(ffp.genfingerprint(&fa)); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile) //{ // mega::FileFingerprint ffp; // MockInputStreamAccess is{1, {3, 4, 5, 6}}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(4, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {100992003, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile_butReadFails) //{ // mega::FileFingerprint ffp; // MockInputStreamAccess is{1, {3, 4, 5, 6}, true}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forTinyFile_butSizeNegative) //{ // mega::FileFingerprint ffp; // MockInputStreamAccess is{1, {3, 4, 5, 6}}; // is.setSize(-1); // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forSmallFile) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(100); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockInputStreamAccess is{1, std::move(content)}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(100, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {215253208, 661795201, 937191950, 562141813}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forSmallFile_butReadFails) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(100); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockInputStreamAccess is{1, std::move(content), true}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forLargeFile) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(20000); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockInputStreamAccess is{1, std::move(content)}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(20000, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {-1236811658, -1236811658, -1236811658, -1236811658}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(true, ffp.isvalid); //} // //TEST(FileFingerprint, genfingerprint_InputStreamAccess_forLargeFile_butReadFails) //{ // mega::FileFingerprint ffp; // std::vector<mega::byte> content(20000); // std::iota(content.begin(), content.end(), mega::byte{0}); // MockInputStreamAccess is{1, std::move(content), true}; // ASSERT_TRUE(ffp.genfingerprint(&is, is.getMTime())); // ASSERT_EQ(-1, ffp.size); // ASSERT_EQ(1, ffp.mtime); // const std::array<int32_t, 4> expected = {0, 0, 0, 0}; // ASSERT_EQ(expected, ffp.crc); // ASSERT_EQ(false, ffp.isvalid); //} �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/File_test.cpp����������������������������������������������������������������0000664�0000000�0000000�00000006272�15162662266�0017221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/megaclient.h> #include <mega/megaapp.h> #include <mega/file.h> #include "DefaultedFileSystemAccess.h" #include "utils.h" #include "mega.h" namespace { //class MockFileSystemAccess : public mt::DefaultedFileSystemAccess //{ //public: // bool unlinklocal(std::string*) override // { // return true; // } //}; void checkFiles(const mega::File& exp, const mega::File& act) { ASSERT_EQ(exp.name, act.name); ASSERT_EQ(exp.getLocalname(), act.getLocalname()); ASSERT_EQ(exp.h, act.h); ASSERT_EQ(exp.hprivate, act.hprivate); ASSERT_EQ(exp.hforeign, act.hforeign); ASSERT_EQ(exp.syncxfer, act.syncxfer); ASSERT_EQ(exp.temporaryfile, act.temporaryfile); ASSERT_EQ(exp.privauth, act.privauth); ASSERT_EQ(exp.pubauth, act.pubauth); ASSERT_EQ(std::string{exp.chatauth}, std::string{act.chatauth}); ASSERT_TRUE(std::equal(exp.filekey, exp.filekey + mega::FILENODEKEYLENGTH, act.filekey)); ASSERT_EQ(exp.targetuser, act.targetuser); const auto expPitagString = mega::pitagToString(*exp.getPitag()); const auto actPitagString = mega::pitagToString(*act.getPitag()); ASSERT_EQ(expPitagString, actPitagString); ASSERT_EQ(nullptr, act.transfer); if (static_cast<const mega::FileFingerprint&>(exp).isvalid || static_cast<const mega::FileFingerprint&>(act).isvalid) { ASSERT_EQ(static_cast<const mega::FileFingerprint&>(exp), static_cast<const mega::FileFingerprint&>(act)); } } } TEST(File, serialize_unserialize) { mega::MegaApp app; auto client = mt::makeClient(app); mega::File file; file.name = "foo"; file.setLocalname(::mega::LocalPath::fromAbsolutePath(file.name)); file.h.set6byte(42); file.hprivate = true; file.hforeign = true; file.syncxfer = true; file.temporaryfile = true; file.privauth = "privauth"; file.pubauth = "pubauth"; file.chatauth = new char[4]{'b', 'a', 'r', '\0'}; // owned by file std::fill(file.filekey, file.filekey + mega::FILENODEKEYLENGTH, 'X'); file.targetuser = "targetuser"; file.transfer = new mega::Transfer{client.get(), mega::GET}; // owned by client file.transfer->files.push_back(&file); file.file_it = file.transfer->files.begin(); Pitag pitag{PitagPurpose::Upload, PitagTrigger::Camera, PitagNodeType::File, PitagTarget::CloudDrive, PitagImportSource::FolderLink}; file.setPitag(pitag); std::string d; file.serialize(&d); auto newFile = std::unique_ptr<mega::File>{mega::File::unserialize(&d)}; checkFiles(file, *newFile); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/FsNode.cpp�������������������������������������������������������������������0000664�0000000�0000000�00000005460�15162662266�0016457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "FsNode.h" #include "utils.h" namespace mt { using mega::FSACCESS_CLASS; using mega::LocalPath; //FsNode::FsNode(FsNode* parent, const mega::nodetype_t type, std::string name) //: mFsId{mt::nextFsId()} //, mMTime{nextRandomInt()} //, mParent{parent} //, mType{type} //, mName{LocalPath::fromPlatformEncodedRelative(std::move(name))} //{ // assert(mType == mega::FILENODE || mType == mega::FOLDERNODE); // // if (parent) // { // assert(parent->getType() == mega::FOLDERNODE); // parent->mChildren.push_back(this); // } // // auto path = getPath(); // // if (mType == mega::FILENODE) // { // mSize = nextRandomInt(); // mContent.reserve(static_cast<size_t>(mSize)); // for (m_off_t i = 0; i < mSize; ++i) // { // mContent.push_back(nextRandomByte()); // } // mFileAccess->fopen(path, true, false); // mFingerprint.genfingerprint(mFileAccess.get()); // } // else // { // mFileAccess->fopen(path, true, false); // mFingerprint.mtime = mMTime; // } //} // //FsNode::FileAccess::FileAccess(const FsNode& fsNode) //: mFsNode{fsNode} //{} // //bool FsNode::FileAccess::fopen(const LocalPath& path, bool, bool, mega::DirAccess* iteratingDir, bool) //{ // mPath = path; // return sysopen(); //} // //bool FsNode::FileAccess::sysstat(mega::m_time_t* curr_mtime, m_off_t* curr_size) //{ // *curr_mtime = mtime; // *curr_size = size; // return true; //} // //bool FsNode::FileAccess::sysopen(bool async) //{ // if (mPath == mFsNode.getPath()) // { // fsidvalid = true; // fsid = mFsNode.getFsId(); // size = mFsNode.getSize(); // mtime = mFsNode.getMTime(); // type = mFsNode.getType(); // return true; // } // else // { // return false; // } //} // //bool FsNode::FileAccess::sysread(mega::byte* buffer, unsigned size, m_off_t offset) //{ // const auto& content = mFsNode.getContent(); // assert(static_cast<unsigned>(offset) + size <= content.size()); // std::copy(content.begin() + static_cast<unsigned>(offset), content.begin() + static_cast<unsigned>(offset) + size, buffer); // return true; //} // //void FsNode::FileAccess::sysclose() //{} } // mt ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/FsNode.h���������������������������������������������������������������������0000664�0000000�0000000�00000001411�15162662266�0016114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <map> #include <memory> #include <mega.h> #include <mega/types.h> #include <mega/filefingerprint.h> #include "DefaultedFileAccess.h" namespace mt { } // mt �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/GfxCommands_test.cpp���������������������������������������������������������0000664�0000000�0000000�00000015320�15162662266�0020542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "gtest/gtest.h" #include "mega/gfx/worker/command_serializer.h" #include "mega/gfx/worker/commands.h" #include "mega/gfx/worker/comms.h" #include <chrono> using mega::GfxDimension; using mega::gfx::CommandHello; using mega::gfx::CommandHelloResponse; using mega::gfx::CommandNewGfx; using mega::gfx::CommandNewGfxResponse; using mega::gfx::CommandSerializer; using mega::gfx::CommandShutDown; using mega::gfx::CommandShutDownResponse; using mega::gfx::CommandSupportFormats; using mega::gfx::CommandSupportFormatsResponse; using mega::gfx::IReader; using std::chrono::milliseconds; using namespace std::chrono_literals; namespace mega { namespace gfx { bool operator==(const CommandNewGfx& lhs, const CommandNewGfx& rhs) { return lhs.Task.Path == rhs.Task.Path && lhs.Task.Dimensions == rhs.Task.Dimensions; } bool operator==(const CommandNewGfxResponse& lhs, const CommandNewGfxResponse& rhs) { return lhs.ErrorCode == rhs.ErrorCode && lhs.ErrorText == rhs.ErrorText && lhs.Images == rhs.Images; } bool operator==(const CommandShutDown& /*lhs*/, const CommandShutDown& /*rhs*/) { return true; } bool operator==(const CommandShutDownResponse& /*lhs*/, const CommandShutDownResponse& /*rhs*/) { return true; } bool operator==(const CommandHello& lhs, const CommandHello& rhs) { return lhs.Text == rhs.Text; } bool operator==(const CommandHelloResponse& lhs, const CommandHelloResponse& rhs) { return lhs.Text == rhs.Text; } bool operator==(const CommandSupportFormats& /*lhs*/, const CommandSupportFormats& /*rhs*/) { return true; } bool operator==(const CommandSupportFormatsResponse& lhs, const CommandSupportFormatsResponse& rhs) { return lhs.formats == rhs.formats && lhs.videoformats == rhs.videoformats; } } } class StringReader : public IReader { public: StringReader(std::string&& value) : mValue(std::move(value)), mIndex{0} {} private: bool doRead(void* out, size_t n, milliseconds timeout) override; std::string mValue; size_t mIndex; }; bool StringReader::doRead(void* out, size_t n, milliseconds /*timeout*/) { if (mIndex > mValue.size()) return false; if (mIndex + n > mValue.size()) return false; std::memcpy(out, mValue.data() + mIndex, n); mIndex += n; return true; } TEST(GfxCommandSerializer, CommandNewGfxSerializeAndUnserializeSuccessfully) { CommandNewGfx sourceCommand; sourceCommand.Task.Path = "c:\\path\\image.png"; sourceCommand.Task.Dimensions = std::vector<GfxDimension>{ {250, 0} }; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandNewGfx*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandNewGfxResponseSerializeAndUnserializeSuccessfully) { CommandNewGfxResponse sourceCommand; sourceCommand.ErrorCode = 0; sourceCommand.ErrorText = "OK"; sourceCommand.Images.push_back("imagedata"); auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandNewGfxResponse*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandShutdownSerializeAndUnserializeSuccessfully) { CommandShutDown sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandShutDown*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandShutdownResponseSerializeAndUnserializeSuccessfully) { CommandShutDownResponse sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandShutDownResponse*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandHelloSerializeAndUnserializeSuccessfully) { CommandHello sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandHello*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandHelloResponseSerializeAndUnserializeSuccessfully) { CommandHelloResponse sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandHelloResponse*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandSupportFormatsSerializeAndUnserializeSuccessfully) { CommandSupportFormats sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandSupportFormats*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } TEST(GfxCommandSerializer, CommandSupportFormatsResponseSerializeAndUnserializeSuccessfully) { CommandSupportFormatsResponse sourceCommand; auto data = CommandSerializer::serialize(&sourceCommand); ASSERT_NE(data, nullptr); StringReader reader(std::move(*data)); auto command = CommandSerializer::unserialize(reader, 5000ms); ASSERT_NE(command, nullptr); auto targetCommand = dynamic_cast<CommandSupportFormatsResponse*>(command.get()); ASSERT_NE(targetCommand, nullptr); ASSERT_EQ(sourceCommand, *targetCommand); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Isolatedprocess_test.cpp�����������������������������������������������������0000664�0000000�0000000�00000005134�15162662266�0021501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// none relevant small groups tests not worth of putting to its own test files // are put here #include "gmock/gmock.h" #include "gtest/gtest.h" #include "mega/gfx/isolatedprocess.h" #include "mega/scoped_timer.h" #include <chrono> #include <vector> using mega::LocalPath; using mega::ScopedSteadyTimer; using std::chrono::seconds; using Params = mega::GfxIsolatedProcess::Params; TEST(Isolatedprocess, CancelableSleeperCanBecancelledInNoTime) { ScopedSteadyTimer timer; mega::CancellableSleeper sleeper; std::thread t ([&sleeper](){ sleeper.sleep(seconds(60)); // long enough }); sleeper.cancel(); if (t.joinable()) t.join(); // cancel should be done immediately, but we don't want a // too short number that the result could be affected by disturbance. ASSERT_TRUE(timer.passedTime() < seconds(10)); } // // Test hellobeater can be shutdown quickly // TEST(Isolatedprocess, GfxWorkerHelloBeaterCanGracefullyShutdownInNoTime) { ScopedSteadyTimer timer; { mega::HelloBeater beater(seconds(60), "__"); //long enough } // cancel should be done immediately, but we don't want a // too short number that the result could be affected by disturbance. ASSERT_TRUE(timer.passedTime() < seconds(10)); } TEST(Isolatedprocess, ParamsConstructedWithDefaultAsExpected) { const std::string exec{"the/path is/exe"}; const std::string expectedExec{LocalPath::fromAbsolutePath(exec).toPath(false)}; Params params{"endpoint", exec}; ASSERT_THAT(params.toArgs(), testing::ElementsAre(expectedExec, "-n=endpoint", "-l=60")); } TEST(Isolatedprocess, ParamsConstructedWithExtraParametersAsExpected) { const std::string exec{"the/path is/exe"}; const std::string expectedExec{LocalPath::fromAbsolutePath(exec).toPath(false)}; const auto rawArgs = std::vector<std::string>{"-t=10", "-d=the/path is/log"}; Params params{"endpoint", exec, seconds{20}, rawArgs}; ASSERT_THAT( params.toArgs(), testing::ElementsAre(expectedExec, "-n=endpoint", "-l=20", "-t=10", "-d=the/path is/log")); } // Duplicate parameters are also retained. TEST(Isolatedprocess, ParamsConstructedWithDuplidatedExtraParametersAreKeptAsExpected) { const std::string exec{"the/path is/exe"}; const std::string expectedExec{LocalPath::fromAbsolutePath(exec).toPath(false)}; const auto rawArgs = std::vector<std::string>{"-n=anotherEndplint", "-l=20"}; Params params{"endpoint", exec, seconds{20}, rawArgs}; ASSERT_THAT( params.toArgs(), testing::ElementsAre(expectedExec, "-n=endpoint", "-l=20", "-n=anotherEndplint", "-l=20")); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/JSONNumericParsers_test.cpp��������������������������������������������������0000664�0000000�0000000�00000003514�15162662266�0021772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2026 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega.h> TEST(JSONNumericParsers, GetFloatParsesDecimalNumbers) { mega::JSON integerJson("25"); mega::JSON decimalJson("25.99"); mega::JSON negativeJson("-12.5"); mega::JSON leadingDotJson(".5"); EXPECT_DOUBLE_EQ(integerJson.getfloat(), 25.0); EXPECT_DOUBLE_EQ(decimalJson.getfloat(), 25.99); EXPECT_DOUBLE_EQ(negativeJson.getfloat(), -12.5); EXPECT_DOUBLE_EQ(leadingDotJson.getfloat(), 0.5); } TEST(JSONNumericParsers, GetFloatParsesScientificNotation) { mega::JSON negativeExpJson("-3.5e1"); mega::JSON positiveExpJson("2E3"); mega::JSON fractionalExpJson("4.2e-2"); EXPECT_DOUBLE_EQ(negativeExpJson.getfloat(), -35.0); EXPECT_DOUBLE_EQ(positiveExpJson.getfloat(), 2000.0); EXPECT_DOUBLE_EQ(fractionalExpJson.getfloat(), 0.042); } TEST(JSONNumericParsers, GetFloatAdvancesAcrossSequentialTokens) { mega::JSON json("25.99,-3.5e1,2E3"); EXPECT_DOUBLE_EQ(json.getfloat(), 25.99); EXPECT_DOUBLE_EQ(json.getfloat(), -35.0); EXPECT_DOUBLE_EQ(json.getfloat(), 2000.0); } TEST(JSONNumericParsers, GetFloatParsesValuesAfterNameValueSeparator) { mega::JSON json(":25.99,-4.2e-1"); EXPECT_DOUBLE_EQ(json.getfloat(), 25.99); EXPECT_DOUBLE_EQ(json.getfloat(), -0.42); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/LRUCache_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000003400�15162662266�0017716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @brief Unitary test for generic cache LRU */ #include <gtest/gtest.h> #include <mega/types.h> void addElementToLRU(LRUCache<int, std::string>& lru, int element) { lru.put(element, std::to_string(element)); auto optionalElement = lru.get(element); ASSERT_TRUE(optionalElement.has_value()); ASSERT_EQ(optionalElement.value(), std::to_string(element)); } /** * @brief Test container LRUCache adding elements */ TEST(LRUCache, addElements) { std::vector<int> elements{1, 2, 3, 4}; LRUCache<int, std::string> lru(elements.size()); for (const auto& element: elements) { addElementToLRU(lru, element); } ASSERT_FALSE(lru.get(5).has_value()); } /** * @brief Test container LRUCache adding elements and exceeding the size * * First element added should be removed */ TEST(LRUCache, addElementsExceedingSize) { std::vector<int> elements{1, 2, 3, 4}; LRUCache<int, std::string> lru(elements.size() - 1); for (const auto& element: elements) { addElementToLRU(lru, element); } // First element has been removed ASSERT_FALSE(lru.get(elements[0]).has_value()); } /** * @brief Test container LRUCache adding elements and exceeding the size * * Unlike the previous test, here the first element is accessed before any purge occurs. * As a result, the first element remains in the cache while the second is removed. * * First element added should be removed */ TEST(LRUCache, addElementsExceedingSizeV2) { LRUCache<int, std::string> lru(3); addElementToLRU(lru, 1); addElementToLRU(lru, 2); addElementToLRU(lru, 3); addElementToLRU(lru, 1); // Exceed size addElementToLRU(lru, 4); ASSERT_TRUE(lru.get(1).has_value()); ASSERT_FALSE(lru.get(2).has_value()); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Logging_test.cpp�������������������������������������������������������������0000664�0000000�0000000�00000046160�15162662266�0017730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/logging.h> #ifdef ENABLE_LOG_PERFORMANCE namespace { class MockLogger : public mega::Logger { public: MockLogger() { mega::SimpleLogger::setOutputClass(this); } ~MockLogger() { mega::SimpleLogger::setOutputClass(nullptr); } void log(const char* time, int loglevel, const char* source, const char* message, const char** directMessages, size_t* directMessagesSizes, unsigned numberMessages) override { EXPECT_EQ(nullptr, time); EXPECT_EQ(nullptr, source); if (directMessages) { for (unsigned i = 0; i < numberMessages; i++) { mLogLevel.insert(loglevel); mMessage.push_back(std::string{directMessages[i], directMessagesSizes[i]}); } } else if (message) { mLogLevel.insert(loglevel); mMessage.push_back(message); } } void checkLogLevel(const int expLogLevel) const { EXPECT_EQ(1, mLogLevel.size()); EXPECT_EQ(expLogLevel, *mLogLevel.begin()); } std::vector<std::string> mMessage; private: std::set<int> mLogLevel; }; std::string expMsg(const std::string& file, const int line, const std::string& message) { return message + " ["+file + ":" + std::to_string(line) + "]"; } } TEST(Logging, performanceMode_OneDirectMessage) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message = "some message"; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << mega::DirectMessage{message.data()}; logger.checkLogLevel(level); ASSERT_EQ(2, logger.mMessage.size()); ASSERT_EQ(message, logger.mMessage[0]); ASSERT_EQ(" [file.cpp:13]", logger.mMessage[1]); } } TEST(Logging, performanceMode_MultipleDirectMessages) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message1 = "some message 1"; const std::string message2 = "some message 2"; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << mega::DirectMessage{message1.data()} << mega::DirectMessage{message2.data()}; logger.checkLogLevel(level); ASSERT_EQ(3, logger.mMessage.size()); ASSERT_EQ(message1, logger.mMessage[0]); ASSERT_EQ(message2, logger.mMessage[1]); ASSERT_EQ(" [file.cpp:13]", logger.mMessage[2]); } } TEST(Logging, performanceMode_StringsChained) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message1 = "some message 1"; const std::string message2 = "some message 2"; auto chainedF = [&]() { mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), 14, true} << message2; return message1; }; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message1 << chainedF(); ASSERT_EQ(2, logger.mMessage.size()); ASSERT_EQ(message1 + message2 + " [file.cpp:14]", logger.mMessage[0]); ASSERT_EQ(message1 + " [file.cpp:13]", logger.mMessage[1]); } } TEST(Logging, performanceMode_DirectMessagesChained) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message1 = "some message 1"; const std::string message2 = "some message 2"; auto chainedF = [&]() { mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), 14, true} << mega::DirectMessage{message2.data()}; return message1; }; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << mega::DirectMessage{message1.data()} << chainedF().data(); ASSERT_EQ(4, logger.mMessage.size()); ASSERT_EQ(message1, logger.mMessage[0]); ASSERT_EQ(message2, logger.mMessage[1]); ASSERT_EQ(" [file.cpp:14]", logger.mMessage[2]); ASSERT_EQ(message1 + " [file.cpp:13]", logger.mMessage[3]); } } TEST(Logging, performanceMode_forStdString) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message = "some message"; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message; logger.checkLogLevel(level); ASSERT_EQ(1, logger.mMessage.size()); ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]); } } TEST(Logging, performanceMode_forCString) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message = "some message"; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message.c_str(); logger.checkLogLevel(level); ASSERT_EQ(1, logger.mMessage.size()); ASSERT_EQ(expMsg(file, line, message), logger.mMessage[0]); } } TEST(Logging, performanceMode_forEnum) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const auto obj = mega::LogLevel::logDebug; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << obj; logger.checkLogLevel(level); ASSERT_EQ(1, logger.mMessage.size()); ASSERT_EQ(expMsg(file, line, "4"), logger.mMessage[0]); } } TEST(Logging, performanceMode_forPointer) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const double obj = 42; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << &obj; logger.checkLogLevel(level); ASSERT_EQ(1, logger.mMessage.size()); ASSERT_GE(logger.mMessage[0].size(), file.size() + 5); // 5 = ':13 ' plus null terminator } } TEST(Logging, performanceMode_forNullPointer) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const double* obj = nullptr; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << obj; logger.checkLogLevel(level); ASSERT_EQ(1, logger.mMessage.size()); ASSERT_EQ(expMsg(file, line, "(NULL)"), logger.mMessage[0]); } } namespace { template<typename Type> void test_forIntegerNumber(const Type number) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << number; logger.checkLogLevel(level); EXPECT_EQ(1, logger.mMessage.size()); std::ostringstream expected; expected << number; EXPECT_EQ(expMsg(file, line, expected.str()), logger.mMessage[0]); } } template<typename Type> void test_forFloatingNumber(const Type number) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << number; logger.checkLogLevel(level); EXPECT_EQ(1, logger.mMessage.size()); std::ostringstream expected; expected << number; const auto msg = expMsg(file, line, expected.str()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } } } TEST(Logging, performanceMode_forInt) { test_forIntegerNumber<int>(0); test_forIntegerNumber<int>(42); test_forIntegerNumber<int>(-42); test_forIntegerNumber<int>(std::numeric_limits<int>::lowest()); test_forIntegerNumber<int>(std::numeric_limits<int>::max()); } TEST(Logging, performanceMode_forLong) { test_forIntegerNumber<long>(0); test_forIntegerNumber<long>(42); test_forIntegerNumber<long>(-42); test_forIntegerNumber<long>(std::numeric_limits<long>::lowest()); test_forIntegerNumber<long>(std::numeric_limits<long>::max()); } TEST(Logging, performanceMode_forLongLong) { test_forIntegerNumber<long long>(0); test_forIntegerNumber<long long>(42); test_forIntegerNumber<long long>(-42); test_forIntegerNumber<long long>(std::numeric_limits<long long>::lowest()); test_forIntegerNumber<long long>(std::numeric_limits<long long>::max()); } TEST(Logging, performanceMode_forUnsignedInt) { test_forIntegerNumber<unsigned int>(0); test_forIntegerNumber<unsigned int>(42); test_forIntegerNumber<unsigned int>(std::numeric_limits<unsigned int>::lowest()); test_forIntegerNumber<unsigned int>(std::numeric_limits<unsigned int>::max()); } TEST(Logging, performanceMode_forUnsignedLong) { test_forIntegerNumber<unsigned long>(0); test_forIntegerNumber<unsigned long>(42); test_forIntegerNumber<unsigned long>(std::numeric_limits<unsigned long>::lowest()); test_forIntegerNumber<unsigned long>(std::numeric_limits<unsigned long>::max()); } TEST(Logging, performanceMode_forUnsignedLongLong) { test_forIntegerNumber<unsigned long long>(0); test_forIntegerNumber<unsigned long long>(42); test_forIntegerNumber<unsigned long long>(std::numeric_limits<unsigned long long>::lowest()); test_forIntegerNumber<unsigned long long>(std::numeric_limits<unsigned long long>::max()); } TEST(Logging, performanceMode_forFloat) { test_forFloatingNumber<float>(0.f); test_forFloatingNumber<float>(42.123f); test_forFloatingNumber<float>(-42.123f); test_forFloatingNumber<float>(std::numeric_limits<float>::lowest()); test_forFloatingNumber<float>(std::numeric_limits<float>::min()); test_forFloatingNumber<float>(std::numeric_limits<float>::max()); } TEST(Logging, performanceMode_forDouble) { test_forFloatingNumber<double>(0.); test_forFloatingNumber<double>(42.123); test_forFloatingNumber<double>(-42.123); test_forFloatingNumber<double>(std::numeric_limits<double>::lowest()); test_forFloatingNumber<double>(std::numeric_limits<double>::min()); test_forFloatingNumber<double>(std::numeric_limits<double>::max()); } TEST(Logging, performanceMode_withMessageLargeThanLogBuffer) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string firstMessage(mega::LOGGER_CHUNKS_SIZE - file.size() - 5, 'X'); // 5 = ':13 ' plus null terminator const std::string secondMessage = "yay"; const std::string message = firstMessage + secondMessage; mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message; logger.checkLogLevel(level); ASSERT_EQ(2, logger.mMessage.size()); ASSERT_EQ(expMsg(file, line, message).substr(0, mega::LOGGER_CHUNKS_SIZE - 1), logger.mMessage[0]); ASSERT_EQ(expMsg(file, line, message).substr(mega::LOGGER_CHUNKS_SIZE - 1), logger.mMessage[1]); } } TEST(Logging, performanceMode_withHugeMessage) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; const std::string file = "file.cpp"; const int line = 13; const std::string message(5000, 'X'); mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message; logger.checkLogLevel(level); const int totallength = 5000 + strlen(" [file.cpp:13]") + 1; const size_t fullMsgCount = totallength / (mega::LOGGER_CHUNKS_SIZE - 1); ASSERT_EQ(fullMsgCount + 1, logger.mMessage.size()); ASSERT_EQ(totallength % (mega::LOGGER_CHUNKS_SIZE - 1) - 1, logger.mMessage.back().size()); } } TEST(Logging, performanceMode_withHugeMessage_butNoLogger) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { mega::SimpleLogger::setOutputClass(nullptr); const std::string file = "file.cpp"; const int line = 13; const std::string message(5000, 'X'); mega::SimpleLogger{static_cast<mega::LogLevel>(level), file.c_str(), line} << message; // ensure no crash or other funny business } } #else namespace { class MockLogger : public mega::Logger { public: MockLogger() { mega::SimpleLogger::setOutputClass(this); } ~MockLogger() { mega::SimpleLogger::setOutputClass(nullptr); } void log(const char *time, int loglevel, const char *source, const char *message) override { EXPECT_NE(nullptr, time); EXPECT_NE(nullptr, source); EXPECT_NE(nullptr, message); mLogLevel.insert(loglevel); mMessage.push_back(message); } void checkLogLevel(const int expLogLevel) const { EXPECT_EQ(1u, mLogLevel.size()); EXPECT_EQ(expLogLevel, *mLogLevel.begin()); } std::vector<std::string> mMessage; private: std::set<int> mLogLevel; }; } #endif TEST(Logging, toStr) { ASSERT_EQ(0, std::strcmp("verbose", mega::SimpleLogger::toStr(mega::LogLevel::logMax))); ASSERT_EQ(0, std::strcmp("debug", mega::SimpleLogger::toStr(mega::LogLevel::logDebug))); ASSERT_EQ(0, std::strcmp("info", mega::SimpleLogger::toStr(mega::LogLevel::logInfo))); ASSERT_EQ(0, std::strcmp("warn", mega::SimpleLogger::toStr(mega::LogLevel::logWarning))); ASSERT_EQ(0, std::strcmp("err", mega::SimpleLogger::toStr(mega::LogLevel::logError))); ASSERT_EQ(0, std::strcmp("FATAL", mega::SimpleLogger::toStr(mega::LogLevel::logFatal))); } #ifdef NDEBUG TEST(Logging, toStr_withBadLogLevel) { ASSERT_EQ(0, std::strcmp("", mega::SimpleLogger::toStr(static_cast<mega::LogLevel>(42)))); } #endif TEST(Logging, macroVerbose) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_verbose << msg; const auto currentLevel = mega::LogLevel::logMax; if (level >= currentLevel) { logger.checkLogLevel(currentLevel); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } else { ASSERT_EQ(0u, logger.mMessage.size()); } } } TEST(Logging, macroDebug) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_debug << msg; const auto currentLevel = mega::LogLevel::logDebug; if (level >= currentLevel) { logger.checkLogLevel(currentLevel); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } else { ASSERT_EQ(0u, logger.mMessage.size()); } } } TEST(Logging, macroInfo) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_info << msg; const auto currentLevel = mega::LogLevel::logInfo; if (level >= currentLevel) { logger.checkLogLevel(currentLevel); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } else { ASSERT_EQ(0u, logger.mMessage.size()); } } } TEST(Logging, macroWarn) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_warn << msg; const auto currentLevel = mega::LogLevel::logWarning; if (level >= currentLevel) { logger.checkLogLevel(currentLevel); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } else { ASSERT_EQ(0u, logger.mMessage.size()); } } } TEST(Logging, macroErr) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_err << msg; const auto currentLevel = mega::LogLevel::logError; if (level >= currentLevel) { logger.checkLogLevel(currentLevel); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } else { ASSERT_EQ(0u, logger.mMessage.size()); } } } TEST(Logging, macroFatal) { for (int level = 0; level <= mega::LogLevel::logMax; ++level) { MockLogger logger; mega::SimpleLogger::setLogLevel(static_cast<mega::LogLevel>(level)); const std::string msg = "foobar"; LOG_fatal << msg; logger.checkLogLevel(mega::LogLevel::logFatal); ASSERT_EQ(1u, logger.mMessage.size()); EXPECT_NE(logger.mMessage[0].find(msg), std::string::npos); } } TEST(Logging, Extract_file_name_from_full_path) { ASSERT_EQ(0, strcmp(::mega::log_file_leafname(__FILE__), "Logging_test.cpp" )); ASSERT_EQ(0, strcmp(::mega::log_file_leafname("logging.h"), "logging.h") ); ASSERT_EQ(0, strcmp(::mega::log_file_leafname("include/mega/logging.h"), "logging.h")); ASSERT_EQ(0, strcmp(::mega::log_file_leafname("include\\mega\\logging.h"), "logging.h" )); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/MacComparison_test.cpp�������������������������������������������������������0000664�0000000�0000000�00000023024�15162662266�0021067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file tests/unit/MacComparison_test.cpp * @brief Unit tests for MAC comparison functions used in upload deduplication. */ #include "DefaultedFileAccess.h" #include <gtest/gtest.h> #include <mega/crypto/cryptopp.h> #include <mega/filesystem.h> #include <mega/utils.h> #include <cstring> #include <vector> namespace { using namespace mega; /** * @brief Mock FileAccess that simulates successful reads with configurable content. * * Can be configured to fail reads at a specific byte position to simulate I/O errors * or file truncation during MAC computation. */ class MockFileAccessForMac: public mt::DefaultedFileAccess { public: /** * @param content The file content to read from. * @param failAtBytePos If > 0, reads that would access data at or beyond this position will * fail. * @param failErrorCode The error code to set when read fails. */ MockFileAccessForMac(const std::vector<byte>& content, size_t failAtBytePos = 0, int failErrorCode = 5): mContent(content), mFailAtBytePos(failAtBytePos), mFailErrorCode(failErrorCode) { size = static_cast<m_off_t>(content.size()); type = FILENODE; } bool openf(FSLogging) override { mIsOpen = true; return true; } void closef() override { mIsOpen = false; } void fclose() override { mIsOpen = false; } bool sysread(void* buffer, unsigned long length, m_off_t offset, bool* retry) override { return doRead(buffer, length, offset, retry); } bool frawread(void* buffer, unsigned long length, m_off_t offset, bool /*alreadyOpened*/, FSLogging, bool* retry = nullptr) override { return doRead(buffer, length, offset, retry); } // Allow test to modify failure position after creation void setFailAtBytePos(size_t pos, int errCode = 5) { mFailAtBytePos = pos; mFailErrorCode = errCode; } private: bool doRead(void* buffer, unsigned long length, m_off_t offset, bool* retry) { if (retry) *retry = false; if (!mIsOpen || offset < 0) return false; const auto nbytes = static_cast<std::size_t>(length); const auto off = static_cast<std::size_t>(offset); // Simulate read failure at specific position if (mFailAtBytePos > 0 && (off + nbytes > mFailAtBytePos)) { errorcode = mFailErrorCode; return false; } // Normal out-of-bounds check if (off > mContent.size() || nbytes > (mContent.size() - off)) { errorcode = 38; // EOF-style error return false; } if (buffer) std::memcpy(buffer, mContent.data() + off, nbytes); return true; } std::vector<byte> mContent; bool mIsOpen{false}; size_t mFailAtBytePos{0}; int mFailErrorCode{5}; }; /** * @brief Helper to create a valid node key for testing. * * Creates a key with the specified IV and MAC values. */ std::string createTestNodeKey(int64_t iv, int64_t mac) { std::string key(SymmCipher::KEYLENGTH + 2 * sizeof(int64_t), '\0'); // Fill key portion with test data for (size_t i = 0; i < SymmCipher::KEYLENGTH; ++i) { key[i] = static_cast<char>(i ^ 0xAB); } // Set IV and MAC std::memcpy(&key[SymmCipher::KEYLENGTH], &iv, sizeof(iv)); std::memcpy(&key[SymmCipher::KEYLENGTH + sizeof(iv)], &mac, sizeof(mac)); return key; } } // anonymous namespace // ============================================================================ // Test Cases // ============================================================================ TEST(MacComparison, SuccessfulMacMatch) { // Create test content std::vector<byte> content(1024, 0x42); // 1KB of 0x42 const int64_t iv = 0x1234567890ABCDEF; // Compute MAC and verify the result is valid MockFileAccessForMac fa(content); ASSERT_TRUE(fa.openf(FSLogging::logOnError)); std::string dummyKey = createTestNodeKey(iv, 0); // MAC=0 to get computed MAC MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa, dummyKey, FILENODE, "test.txt"); // Verify MAC was computed successfully EXPECT_EQ(result.errorCode, 0); EXPECT_NE(result.localMac, INVALID_META_MAC); EXPECT_EQ(result.remoteMac, 0); // We passed 0 as remote MAC // Since remoteMac (0) != localMac (computed), areEqualMacs should be false EXPECT_FALSE(result.areEqualMacs); // Verify the MAC is deterministic by computing it again on a fresh mock // with the correct remote MAC MockFileAccessForMac fa2(content); ASSERT_TRUE(fa2.openf(FSLogging::logOnError)); std::string correctKey = createTestNodeKey(iv, result.localMac); MacComparisonResult result2 = CompareLocalFileMetaMacWithNodeKey(&fa2, correctKey, FILENODE, "test.txt"); // Now verify the comparison works when MACs match EXPECT_EQ(result2.errorCode, 0); EXPECT_EQ(result2.remoteMac, result.localMac); // Note: Due to the way MAC computation works with the mock, // we verify that the computation succeeds without errors. // The actual MAC matching test requires the underlying crypto // to be deterministic across mock instances. } TEST(MacComparison, MacMismatch_DifferentContent) { // Create test content std::vector<byte> content(1024, 0x42); MockFileAccessForMac fa(content); ASSERT_TRUE(fa.openf(FSLogging::logOnError)); // Create a key with a different (wrong) MAC value // Use static_cast to avoid implicit unsigned-to-signed narrowing on platforms where // the hex literal is unsigned long (> INT64_MAX) const int64_t wrongMac = static_cast<int64_t>(0xDEADBEEFCAFEBABEULL); std::string wrongKey = createTestNodeKey(0x1234567890ABCDEF, wrongMac); MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa, wrongKey, FILENODE, "test.txt"); // Should have computed MAC successfully but values don't match EXPECT_FALSE(result.areEqualMacs); EXPECT_EQ(result.errorCode, 0); // No error, just different content EXPECT_NE(result.localMac, INVALID_META_MAC); EXPECT_EQ(result.remoteMac, wrongMac); EXPECT_NE(result.localMac, result.remoteMac); } TEST(MacComparison, ReadError_MidFileFailure) { // Create content large enough to require multiple reads (>128KB chunks) std::vector<byte> content(256 * 1024, 0x42); // 256KB // Fail after reading 100KB (mid-way through computation) MockFileAccessForMac fa(content, 100 * 1024 /*failAtBytePos*/, 5 /*EIO-style error*/); ASSERT_TRUE(fa.openf(FSLogging::logOnError)); std::string nodeKey = createTestNodeKey(0x1234567890ABCDEF, 0x1111111111111111); MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa, nodeKey, FILENODE, "test.txt"); // Should report read error EXPECT_FALSE(result.areEqualMacs); EXPECT_NE(result.errorCode, 0); // Should have an error code EXPECT_EQ(result.localMac, INVALID_META_MAC); EXPECT_EQ(result.remoteMac, static_cast<int64_t>(0x1111111111111111)); } TEST(MacComparison, ReadError_FileTruncated) { // Create small content but claim larger size (simulates file truncation after fopen) std::vector<byte> content(100, 0x42); MockFileAccessForMac fa(content); fa.size = 1024; // Claim file is larger than actual content ASSERT_TRUE(fa.openf(FSLogging::logOnError)); std::string nodeKey = createTestNodeKey(0x1234567890ABCDEF, 0x2222222222222222); MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa, nodeKey, FILENODE, "test.txt"); // Should report read error (trying to read beyond available data) EXPECT_FALSE(result.areEqualMacs); EXPECT_NE(result.errorCode, 0); // Should have an error code EXPECT_EQ(result.localMac, INVALID_META_MAC); } TEST(MacComparison, ErrorCodeIsCaptured) { // Test that the specific error code from the FileAccess is captured std::vector<byte> content(256 * 1024, 0x42); const int expectedErrorCode = 42; // Custom error code MockFileAccessForMac fa(content, 50 * 1024, expectedErrorCode); ASSERT_TRUE(fa.openf(FSLogging::logOnError)); std::string nodeKey = createTestNodeKey(0x1234567890ABCDEF, 0x3333333333333333); MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa, nodeKey, FILENODE, "test.txt"); EXPECT_FALSE(result.areEqualMacs); EXPECT_EQ(result.errorCode, expectedErrorCode); } TEST(MacComparison, EmptyFile) { // Empty file should still compute a valid MAC std::vector<byte> content; // Empty const int64_t iv = 0x1234567890ABCDEF; // First, compute the reference MAC MockFileAccessForMac fa1(content); fa1.size = 0; ASSERT_TRUE(fa1.openf(FSLogging::logOnError)); std::string dummyKey = createTestNodeKey(iv, 0); MacComparisonResult refResult = CompareLocalFileMetaMacWithNodeKey(&fa1, dummyKey, FILENODE, "empty.txt"); ASSERT_EQ(refResult.errorCode, 0) << "Failed to compute reference MAC"; int64_t expectedMac = refResult.localMac; // Now test with the correct MAC MockFileAccessForMac fa2(content); fa2.size = 0; ASSERT_TRUE(fa2.openf(FSLogging::logOnError)); std::string correctKey = createTestNodeKey(iv, expectedMac); MacComparisonResult result = CompareLocalFileMetaMacWithNodeKey(&fa2, correctKey, FILENODE, "empty.txt"); EXPECT_TRUE(result.areEqualMacs); EXPECT_EQ(result.errorCode, 0); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/MediaProperties_test.cpp�����������������������������������������������������0000664�0000000�0000000�00000004634�15162662266�0021436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <array> #include <gtest/gtest.h> #include <mega/mediafileattribute.h> namespace { void checkMediaProperties(const mega::MediaProperties& exp, const mega::MediaProperties& act) { ASSERT_EQ(exp.shortformat, act.shortformat); ASSERT_EQ(exp.width, act.width); ASSERT_EQ(exp.height, act.height); ASSERT_EQ(exp.fps, act.fps); ASSERT_EQ(exp.playtime, act.playtime); ASSERT_EQ(exp.containerid, act.containerid); ASSERT_EQ(exp.videocodecid, act.videocodecid); ASSERT_EQ(exp.audiocodecid, act.audiocodecid); ASSERT_EQ(exp.is_VFR, act.is_VFR); ASSERT_EQ(exp.no_audio, act.no_audio); } } TEST(MediaProperties, serialize_unserialize) { mega::MediaProperties mp; mp.shortformat = 10; mp.width = 11; mp.height = 12; mp.fps = 13; mp.playtime = 14; mp.containerid = 15; mp.videocodecid = 16; mp.audiocodecid = 17; mp.is_VFR = true; mp.no_audio = true; const auto d = mp.serialize(); const mega::MediaProperties newMp{d}; checkMediaProperties(mp, newMp); } TEST(MediaProperties, unserialize_32bit) { mega::MediaProperties mp; mp.shortformat = 10; mp.width = 11; mp.height = 12; mp.fps = 13; mp.playtime = 14; mp.containerid = 15; mp.videocodecid = 16; mp.audiocodecid = 17; mp.is_VFR = true; mp.no_audio = true; // This is the result of serialization on 32bit Windows const std::array<char, 39> rawData = { 0x0a, 0x0b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const std::string d(rawData.data(), rawData.size()); const mega::MediaProperties newMp{d}; checkMediaProperties(mp, newMp); } ����������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/MegaApi_test.cpp�������������������������������������������������������������0000664�0000000�0000000�00000063740�15162662266�0017650�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <atomic> #include <memory> #include <thread> #include <gtest/gtest.h> #include <mega/types.h> #include <megaapi.h> #include <megaapi_impl.h> using namespace std; using namespace mega; namespace { unique_ptr<MegaStringList> createMegaStringList(const vector<const char*>& data) { string_vector list; for (const auto& value : data) { list.emplace_back(value); } return unique_ptr<MegaStringList>(new MegaStringListPrivate(std::move(list))); } } // anonymous TEST(MegaApi, MegaStringList_get_and_size_happyPath) { const vector<const char*> data{ "foo", "bar", }; auto stringList = createMegaStringList(data); ASSERT_EQ(2, stringList->size()); ASSERT_EQ(string{"foo"}, string{stringList->get(0)}); ASSERT_EQ(string{"bar"}, string{stringList->get(1)}); ASSERT_EQ(nullptr, stringList->get(2)); } TEST(MegaApi, MegaStringList_get_and_size_emptyStringList) { const vector<const char*> data{ }; auto stringList = createMegaStringList(data); ASSERT_EQ(0, stringList->size()); ASSERT_EQ(nullptr, stringList->get(0)); } TEST(MegaApi, MegaStringList_copy_happyPath) { const vector<const char*> data{ "foo", "bar", }; auto stringList = createMegaStringList(data); auto copiedStringList = unique_ptr<MegaStringList>{stringList->copy()}; ASSERT_EQ(2, copiedStringList->size()); ASSERT_EQ(string{"foo"}, string{copiedStringList->get(0)}); ASSERT_EQ(string{"bar"}, string{copiedStringList->get(1)}); ASSERT_EQ(nullptr, copiedStringList->get(2)); } TEST(MegaApi, MegaStringList_copy_emptyStringList) { const vector<const char*> data{ }; auto stringList = createMegaStringList(data); auto copiedStringList = unique_ptr<MegaStringList>{stringList->copy()}; ASSERT_EQ(0, copiedStringList->size()); ASSERT_EQ(nullptr, copiedStringList->get(0)); } TEST(MegaApi, MegaStringList_default_constructor) { auto stringList = unique_ptr<MegaStringList>{new MegaStringListPrivate}; ASSERT_EQ(0, stringList->size()); ASSERT_EQ(nullptr, stringList->get(0)); } TEST(MegaApi, MegaStringListMap_set_and_get_happyPath) { auto stringListMap = unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()}; auto stringList1 = createMegaStringList({"13", "42"}).release(); auto stringList2 = createMegaStringList({"awesome", "sweet", "cool"}).release(); stringListMap->set("foo", stringList1); stringListMap->set("bar", stringList2); ASSERT_EQ(2, stringListMap->size()); ASSERT_EQ(*stringList1, *stringListMap->get("foo")); ASSERT_EQ(*stringList2, *stringListMap->get("bar")); ASSERT_EQ(nullptr, stringListMap->get("blah")); auto expectedKeys = createMegaStringList({"bar", "foo"}); auto keys = std::unique_ptr<MegaStringList>{stringListMap->getKeys()}; ASSERT_EQ(*expectedKeys, *keys); } TEST(MegaApi, MegaStringListMap_get_emptyStringListMap) { auto stringListMap = unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()}; ASSERT_EQ(0, stringListMap->size()); ASSERT_EQ(nullptr, stringListMap->get("blah")); auto keys = std::unique_ptr<MegaStringList>{stringListMap->getKeys()}; ASSERT_EQ(0, keys->size()); } TEST(MegaApi, MegaStringListMap_copy_happyPath) { auto stringListMap = unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()}; auto stringList1 = createMegaStringList({"13", "42"}).release(); auto stringList2 = createMegaStringList({"awesome", "sweet", "cool"}).release(); stringListMap->set("foo", stringList1); stringListMap->set("bar", stringList2); auto copiedStringListMap = unique_ptr<MegaStringListMap>{stringListMap->copy()}; ASSERT_EQ(2, copiedStringListMap->size()); ASSERT_EQ(*stringList1, *copiedStringListMap->get("foo")); ASSERT_EQ(*stringList2, *copiedStringListMap->get("bar")); ASSERT_EQ(nullptr, copiedStringListMap->get("blah")); auto expectedKeys = createMegaStringList({"bar", "foo"}); auto keys = std::unique_ptr<MegaStringList>{stringListMap->getKeys()}; ASSERT_EQ(*expectedKeys, *keys); } TEST(MegaApi, MegaStringListMap_copy_emptyStringListMap) { auto stringListMap = unique_ptr<MegaStringListMap>{MegaStringListMap::createInstance()}; auto copiedStringListMap = unique_ptr<MegaStringListMap>{stringListMap->copy()}; ASSERT_EQ(0, copiedStringListMap->size()); ASSERT_EQ(nullptr, copiedStringListMap->get("blah")); auto keys = std::unique_ptr<MegaStringList>{stringListMap->getKeys()}; ASSERT_EQ(0, keys->size()); } TEST(MegaApi, MegaStringTable_append_and_get_happyPath) { auto stringListTable = unique_ptr<MegaStringTable>{MegaStringTable::createInstance()}; auto stringList1 = createMegaStringList({"13", "42"}).release(); auto stringList2 = createMegaStringList({"awesome", "sweet", "cool"}).release(); stringListTable->append(stringList1); stringListTable->append(stringList2); ASSERT_EQ(2, stringListTable->size()); ASSERT_EQ(*stringList1, *stringListTable->get(0)); ASSERT_EQ(*stringList2, *stringListTable->get(1)); ASSERT_EQ(nullptr, stringListTable->get(2)); } TEST(MegaApi, MegaStringTable_get_emptyStringTable) { auto stringListTable = unique_ptr<MegaStringTable>{MegaStringTable::createInstance()}; ASSERT_EQ(0, stringListTable->size()); ASSERT_EQ(nullptr, stringListTable->get(0)); } TEST(MegaApi, MegaStringTable_copy_happyPath) { auto stringListTable = unique_ptr<MegaStringTable>{MegaStringTable::createInstance()}; auto stringList1 = createMegaStringList({"13", "42"}).release(); auto stringList2 = createMegaStringList({"awesome", "sweet", "cool"}).release(); stringListTable->append(stringList1); stringListTable->append(stringList2); auto copiedStringTable = unique_ptr<MegaStringTable>{stringListTable->copy()}; ASSERT_EQ(2, copiedStringTable->size()); ASSERT_EQ(*stringList1, *copiedStringTable->get(0)); ASSERT_EQ(*stringList2, *copiedStringTable->get(1)); ASSERT_EQ(nullptr, copiedStringTable->get(2)); } TEST(MegaApi, MegaStringTable_copy_emptyStringTable) { auto stringListTable = unique_ptr<MegaStringTable>{MegaStringTable::createInstance()}; auto copiedStringTable = unique_ptr<MegaStringTable>{stringListTable->copy()}; ASSERT_EQ(0, copiedStringTable->size()); ASSERT_EQ(nullptr, copiedStringTable->get(0)); } TEST(MegaApi, getMimeType) { vector<thread> threads; atomic<int> successCount{0}; // 100 threads was enough to reliably crash the old non-thread-safe version for (int i = 0; i < 100; ++i) { threads.emplace_back([&successCount] { if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType("nosuch")} == nullptr) ++successCount; if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType(nullptr)} == nullptr) ++successCount; if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType("3ds")}.get() == string("image/x-3ds")) ++successCount; if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType(".3ds")}.get() == string("image/x-3ds")) ++successCount; if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType("zip")}.get() == string("application/zip")) ++successCount; if (std::unique_ptr<char[]>{::mega::MegaApi::getMimeType(".zip")}.get() == string("application/zip")) ++successCount; }); } for (auto& t : threads) { t.join(); } ASSERT_EQ(600, successCount); } TEST(MegaApi, MegaApiImpl_calcRecommendedProLevel) { MegaPricingPrivate pricing; std::function<void(int, int, int)> addTestProducts = [&](int proLevel, int gb, double pricecents) { pricing.addProduct({1000, 1000000, static_cast<unsigned int>(proLevel), gb, gb == -1 ? -1 : gb * 10, 1, pricecents, 10, 100, 0.0, 0.0, 0.0, "monthly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); pricing.addProduct({1000, 1000000, static_cast<unsigned int>(proLevel), gb, gb == -1 ? -1 : gb * 10, 12, pricecents * 12, 10, 100, 0.0, 0.0, 0.0, "yearly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); }; addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_LITE, 400, 499); addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_PROI, 2048, 999); addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_PROII, 8192, 1999); addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_PROIII, 16384, 2999); addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, -1, 0); addTestProducts(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, -1, 0); pricing.addProduct({1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_STARTER, 50, 50, 1, 1, 10, 100, 0.0, 0.0, 0.0, "monthly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); // only monthly pricing.addProduct({1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_BASIC, 100, 100, 1, 2, 10, 100, 0.0, 0.0, 0.0, "monthly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); pricing.addProduct({1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_BASIC, 100, 100 * 12, 12, 2 * 12, 10, 100, 0.0, 0.0, 0.0, "yearly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); pricing.addProduct({1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, 200, 200, 1, 3, 10, 100, 0.0, 0.0, 0.0, "monthly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(), 0, std::nullopt, std::nullopt}); Product testProduct = { 1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, 200, 200 * 12, 12, 3 * 12, 10, 100, 0.0, 0.0, 0.0, "yearly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(BusinessPlan{20, 40, 3, 50, 60, 70, 80, 90, 100, 15, 10}), 0, std::nullopt, std::nullopt}; pricing.addProduct(testProduct); const int testProductIndex = pricing.getNumProducts() - 1; ASSERT_EQ(pricing.getGBStorage(testProductIndex), testProduct.gbStorage); ASSERT_EQ(pricing.getAmount(testProductIndex), testProduct.amount); ASSERT_EQ(pricing.getAmountMonth(testProductIndex), testProduct.amountMonth); ASSERT_EQ(pricing.getAndroidID(testProductIndex), testProduct.androidid); ASSERT_EQ(pricing.getDescription(testProductIndex), testProduct.description); ASSERT_EQ(pricing.getGBPerStorage(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->gbPerStorage : 0); ASSERT_EQ(pricing.getGBPerTransfer(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->gbPerTransfer : 0); ASSERT_EQ(pricing.getGBStoragePerUser(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->gbStoragePerUser : 0); ASSERT_EQ(pricing.getGBTransferPerUser(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->gbTransferPerUser : 0); ASSERT_EQ(pricing.getHandle(testProductIndex), testProduct.productHandle); ASSERT_EQ(pricing.getIosID(testProductIndex), testProduct.iosid); ASSERT_EQ(pricing.getLocalPrice(testProductIndex), testProduct.localPrice); ASSERT_EQ(pricing.getMonths(testProductIndex), testProduct.months); ASSERT_EQ(pricing.getProLevel(testProductIndex), testProduct.proLevel); ASSERT_EQ(pricing.getTrialDurationInDays(testProductIndex), testProduct.trialDays); ASSERT_EQ(pricing.getPricePerStorage(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->pricePerStorage : 0); ASSERT_EQ(pricing.getPricePerTransfer(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->pricePerTransfer : 0); ASSERT_EQ(pricing.getPricePerUser(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->pricePerUser : 0); ASSERT_EQ(pricing.getLocalPricePerTransfer(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->localPricePerTransfer : 0); ASSERT_EQ(pricing.getLocalPricePerStorage(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->localPricePerStorage : 0); ASSERT_EQ(pricing.getLocalPricePerUser(testProductIndex), (testProduct.businessPlan) ? testProduct.businessPlan->localPricePerUser : 0); std::function<int(int, int)> test = [&](int level, int gb) { AccountDetails accDetails; AccountPlan accPlan; accPlan.level = level; accDetails.plans.push_back(std::move(accPlan)); accDetails.storage_used = gb * (m_off_t)(1024 * 1024 * 1024); unique_ptr<MegaAccountDetails> details(MegaAccountDetailsPrivate::fromAccountDetails(&accDetails)); return MegaApiImpl::calcRecommendedProLevel(pricing, *details.get()); }; int gb = 30; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_STARTER); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_BASIC); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 80; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_BASIC); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_BASIC); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 120; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 300; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_LITE); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 500; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 5000; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); gb = 10000; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PROIII); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); // too large - nothing found gb = 20000; ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_FREE, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_STARTER, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BASIC, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_LITE, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PROIII, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_BUSINESS, gb), MegaAccountDetails::ACCOUNT_TYPE_BUSINESS); ASSERT_EQ(test(MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI, gb), MegaAccountDetails::ACCOUNT_TYPE_PRO_FLEXI); } TEST(MegaApi, MegaApiImpl_mobileOffer) { std::string title{"Black-friday2025"}; bool uat{true}; Product testProduct = { 1000, 1000000, MegaAccountDetails::ACCOUNT_TYPE_ESSENTIAL, 200, 200 * 12, 12, 3 * 12, 10, 100, 0.0, 0.0, 0.0, "yearly", {}, "ios id", "android id", 1, std::make_unique<BusinessPlan>(BusinessPlan{20, 40, 3, 50, 60, 70, 80, 90, 100, 15, 10}), 0, MobileOffer{title, uat}, std::nullopt}; MegaPricingPrivate pricing; pricing.addProduct(testProduct); int index{0}; ASSERT_TRUE(pricing.hasMobileOffers(index)); ASSERT_EQ(title, pricing.getMobileOfferId(index)); ASSERT_EQ(uat, pricing.hasMobileOfferUat(index)); } TEST(MegaApi, UseCurrentPathIfNoBasePathIsGiven) { constexpr const char* appKey{nullptr}; constexpr const char* basePath{nullptr}; auto megaApi{MegaApi(appKey, basePath)}; ASSERT_STREQ(std::filesystem::current_path().string().c_str(), megaApi.getBasePath()); } ��������������������������������sdk-10.11.0/tests/unit/NodesMatchedByFsid_test.cpp��������������������������������������������������0000664�0000000�0000000�00000017562�15162662266�0022005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file NodesMatchedByFsid_test.cpp * @brief Tests for the methods used to search for nodes matched by fsid. */ #ifdef ENABLE_SYNC #include <gtest/gtest.h> #include <mega/syncinternals/syncinternals.h> using namespace mega; namespace { /** * @brief A default value for the user owner handle. */ constexpr handle COMMON_USER_OWNER = 1; /** * @brief A default value for the isFsidReused flag. */ constexpr bool FSID_REUSED = false; /** * @brief A default SourceNodeMatchByFSIDContext struct. */ constexpr SourceNodeMatchByFSIDContext BASIC_SOURCE_CONTEXT{FSID_REUSED, ExclusionState::ES_INCLUDED}; /** * @brief A default mtime. */ constexpr m_time_t SIMPLE_MTIME = 1; /** * @brief A default size. */ constexpr m_off_t SIMPLE_SIZE = 10; /** * @brief Generates a light FileFingerprint (mtime and size). * * This light FileFingerprint is enough for comparison purposes, * the CRC needs real data to be calculated, and we are not * testing FileFingerprint fields here. * * @param mtime The modification time of the file. * @param size The size of the file. * @return A valid FileFingerprint with the mtime and size. CRC would be empty. */ FileFingerprint genLightFingerprint(const m_time_t mtime = SIMPLE_MTIME, const m_off_t size = SIMPLE_SIZE) { FileFingerprint lightFp{}; lightFp.mtime = mtime; lightFp.size = size; lightFp.isvalid = true; return lightFp; } /** * @brief Generates a NodeMatchByFSIDAttributes structure. * * @param nodeType The type of the node (FILENODE or FOLDERNODE). * @param filesystemFingerprint The fingerprint of the filesystem. It needs an int for the * fingerprint and a string for the UUID. * @param userHandle The handle of the user owner. * @param fileFingerprint The fingerprint of the file. * @return A NodeMatchByFSIDAttributes struct with the assigned fields. */ NodeMatchByFSIDAttributes genMatchAttributes(const nodetype_t nodeType = FILENODE, const fsfp_t& filesystemFingerprint = {1, "UUID"}, const handle userHandle = COMMON_USER_OWNER, const FileFingerprint& fileFingerprint = genLightFingerprint(), const FileFingerprint& realFingerprint = genLightFingerprint()) { return NodeMatchByFSIDAttributes{nodeType, filesystemFingerprint, userHandle, fileFingerprint, realFingerprint}; } } // namespace /** * @brief Tests a match: both nodes are equivalent. */ TEST(NodesMatchedByFSIDTest, NodesAreEquivalent) { const auto sourceNodeAttributes{genMatchAttributes()}; const auto targetNodeAttributes{sourceNodeAttributes}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::Matched); } /** * @brief Tests mismatch due to FSID reused by the source node. */ TEST(NodesMatchedByFSIDTest, SourceNodeFsidReused) { const auto sourceNodeAttributes{genMatchAttributes()}; const auto targetNodeAttributes{sourceNodeAttributes}; constexpr bool fsidIsReused = true; constexpr SourceNodeMatchByFSIDContext context{fsidIsReused, ExclusionState::ES_INCLUDED}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, context), NodeMatchByFSIDResult::SourceFsidReused); } /** * @brief Test mismatch due to different filesystem fingerprints. */ TEST(NodesMatchedByFSIDTest, DifferentFilesystemsFingerprints) { const fsfp_t fsfp1{1, "UUID"}; const fsfp_t fsfp2{2, "UUID2"}; const auto sourceNodeAttributes{genMatchAttributes(FILENODE, fsfp1)}; const auto targetNodeAttributes{genMatchAttributes(FILENODE, fsfp2)}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::DifferentFilesystems); } /** * @brief Tests mismatch due to different node types. */ TEST(NodesMatchedByFSIDTest, DifferentNodeTypes) { const auto sourceNodeAttributes{genMatchAttributes(FILENODE)}; const auto targetNodeAttributes{genMatchAttributes(FOLDERNODE)}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::DifferentTypes); } /** * @brief Tests mismatch due to different owners. */ TEST(NodesMatchedByFSIDTest, DifferentOwners) { constexpr handle sourceOwner = 1; constexpr handle targetOwner = 2; const fsfp_t fsfp1{1, "UUID"}; const auto sourceNodeAttributes{genMatchAttributes(FILENODE, fsfp1, sourceOwner)}; const auto targetNodeAttributes{genMatchAttributes(FILENODE, fsfp1, targetOwner)}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::DifferentOwners); } /** * @brief Tests mismatch due to exclusion unknown. */ TEST(NodesMatchedByFSIDTest, SourceNodeExclusionStateIsUnknown) { const auto sourceNodeAttributes{genMatchAttributes()}; const auto targetNodeAttributes{sourceNodeAttributes}; constexpr SourceNodeMatchByFSIDContext context{false, ExclusionState::ES_UNKNOWN}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, context), NodeMatchByFSIDResult::SourceExclusionUnknown); } /** * @brief Tests mismatch due to node exclusion. */ TEST(NodesMatchedByFSIDTest, SourceNodeIsExcluded) { const auto sourceNodeAttributes{genMatchAttributes()}; const auto targetNodeAttributes{sourceNodeAttributes}; constexpr SourceNodeMatchByFSIDContext context{false, ExclusionState::ES_EXCLUDED}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, context), NodeMatchByFSIDResult::SourceIsExcluded); } /** * @brief Test mismatch due to different fingerprint due to mtime. */ TEST(NodesMatchedByFSIDTest, DifferentFingerprintDueToMtime) { const auto sourceFp = genLightFingerprint(); const auto targetFp = genLightFingerprint(SIMPLE_MTIME + 30, SIMPLE_SIZE); const fsfp_t fsfp1{1, "UUID"}; const auto sourceNodeAttributes{ genMatchAttributes(FILENODE, fsfp1, COMMON_USER_OWNER, sourceFp)}; const auto targetNodeAttributes{ genMatchAttributes(FILENODE, fsfp1, COMMON_USER_OWNER, targetFp)}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::DifferentFingerprintOnlyMtime); } /** * @brief Test mismatch due to different fingerprint due to size. */ TEST(NodesMatchedByFSIDTest, DifferentFingerprintDueToSize) { const auto sourceFp = genLightFingerprint(); const auto targetFp = genLightFingerprint(SIMPLE_MTIME, SIMPLE_SIZE + 1); const fsfp_t fsfp1{1, "UUID"}; const auto sourceNodeAttributes{ genMatchAttributes(FILENODE, fsfp1, COMMON_USER_OWNER, sourceFp)}; const auto targetNodeAttributes{ genMatchAttributes(FILENODE, fsfp1, COMMON_USER_OWNER, targetFp)}; ASSERT_EQ(areNodesMatchedByFsidEquivalent(sourceNodeAttributes, targetNodeAttributes, BASIC_SOURCE_CONTEXT), NodeMatchByFSIDResult::DifferentFingerprint); } #endif // ENABLE_SYNC����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/NotImplemented.h�������������������������������������������������������������0000664�0000000�0000000�00000001505�15162662266�0017666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <stdexcept> namespace mt { class NotImplemented : public std::runtime_error { public: NotImplemented(const std::string& function) : std::runtime_error{"Function not implemented: " + function} {} }; } // mt �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/PayCrypter_test.cpp����������������������������������������������������������0000664�0000000�0000000�00000017435�15162662266�0020447�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega.h" #include "gtest/gtest.h" using namespace mega; TEST(PayCrypter, allFeatures) { char BASE64_IV[] = "7XS3jX8CrWh6gpZIavQamA"; char BASE64_ENCKEY[] = "IcfMNKnMLJNJAH-XPMDShw"; char BASE64_HMACKEY[] = "rChwtATCap-CXO-KGxbEKZLL88lVfdZPZfZcdnMtj8o"; char KEYS[] = "IcfMNKnMLJNJAH-XPMDSh6wocLQEwmqfglzvihsWxCmSy_PJVX3WT2X2XHZzLY_K"; char CONTENT[] = "{\"first_name\": \"First\", \"last_name\": \"Last\"," " \"card_number\": \"4532646653175959\"," " \"expiry_date_month\": \"10\", \"expiry_date_year\": \"2020\"," " \"cv2\": \"123\", \"address1\": \"120 Albert St\"," " \"address2\": \"Auckland Central\", \"city\": \"Auckland\"," " \"province\": \"Auckland\", \"postal_code\": \"1010\"," " \"country_code\": \"NZ\"}"; char CIPHER_BYTES[] = "Btu6B6YxQV1oeMRij4Fn0Que9FfIE1LJyYdacVbNBM1bS-GZAtwQh5" "ZTtsakK6v_mMZGiQ3egRFSTNHzQU0jVa0GYZJ087NhlKlGtVO6PvBKmTkxpcnZpy1im" "S6uzKLccQU-IxKm1XnBF7gB7McbXDxb-j_s3-sjMJo_npDBOR3hUePGSyN-jmed7mvO" "K_fNY8DHqodpdVk7vy2PL8_iAY2SefttWGCD8DwiyxXx42KAjUaRHiYJqgdkZheF_Rp" "9l-KxgW8krDdkHsQu-nqeciezk5iA5OlylUmCfc57AKztBElyd4KIfz4B7kprmTeiiH" "8lhTCq7xZ64GdABzwfQghkf-fM9NJUD9bHfbTYfnnDRSvDrdJtD1gRVrkxnHNNVKhd6" "rtKToreM2bFhfUpcw"; char HMAC[] = "C7WRAdge50wzsAMqdM2_BVhntsP_OUYxaDMkPtRvewg"; //The same as the Javascript example char SDK_PUBKEY[] = "CACmWnYy7M5dqH7shqrj4jERfhhCfzoU5uDycAof1o8JyHu_F47b0aAB9KhKsIVKv90" "nbuea7wGuWsc0pxlrR5kKOnqMEcIQrLysFupSleqwilIgp5MUBvkPTdsn22Qc9Qldwm" "p_cbBNVfTrUVFSifv0QjDnbl7t9sLF5GgFMfYhWqMxAr3D3072cQF9eTbDLCbPD7RrC" "vUiTdqI1bT79e_187YSzCdjeVq_tZb5YnhLPHlgNQffmFJj41itSwpqrEYN8e5kIvsE" "INpHiLtXIIBBnld6NZu55U37sHeYkn5PB6cMi3ZEm90uIB7MT5CyHYLaEbJ9RkzJNRc" "xJAC2w4CnABEBAAE"; //From the Javascript example, disabling random padding (padding with 0) char CRYPT_GORT_SAYS[] = "AQB4PLTVCTdrPFXPWWCWZA3LdkjsIQgr7Ug8WBqFQlGqDR0YX0heatGVudAEb3TBOwvuoYsbOwVLOya22pqDJP6E-RUYDxbYC0dA02K7TSs97A9ZqnxnL6jvjW95X3BuR8YjStQJyy-a3FyAhrjyT9TnLOfKuUwIMLHf1eZB8H4JlAJ8VEQq9-SlusubiQZGZpYMeu2SBFJN-HI-93PEw2U3k-K6h7YYdhM-kIJ4-d2LuPWfyvuyjhs5fncgDgqPGZhq_4XOmV5Xh76aoqx8SBrPsotFvxE_CxOydivXhBMRaN6b6iL7MhuQXXDbOjvVis9uV2HnWraCxHbFwmUxoD6K"; char CRYPT_KEYS_BYTES[] = "AQB1FZOZJiEviXTXeBEOjyM6F9odENY6q4wzt73X0vVCbGBZyubKzHrNzHLaNkwGubd1RQ6wTuH3ypbK5wdM3QsyTcLq6DMv7O3JsH2R3MynRLuPGzHiNmZq2VkAMvELOo-XBeUknxrAstHZhWNQJImH4DBtnY57Mid1o-BTz7xKvRIUQvsj217CqE4CnVV6lxaloq6jvlenWATzCdEa1Q6Y8XN7hftn4Hl5ZrnAltIblBI0_fq2bkhqzZolpURbhypAg0oTFpnmj82QEBy4vwwdCOaQ8_lQjqQhsd3ah4O9gSkpYa6YoAtV9eBu338skJbhjprUVq04qi62Er_iichx"; //Initialize values byte enckey[sizeof(BASE64_ENCKEY)]; Base64::atob(BASE64_ENCKEY, enckey, sizeof(enckey)); byte iv[sizeof(BASE64_IV)]; int ivLen = Base64::atob(BASE64_IV, iv, sizeof(iv)); byte hmacKey[sizeof(BASE64_HMACKEY)]; int hmacKeyLen = Base64::atob(BASE64_HMACKEY, hmacKey, sizeof(hmacKey)); char keys[sizeof(KEYS)]; int keysLen = Base64::atob(KEYS, (byte *)keys, sizeof(keys)); byte pubkdata[sizeof(SDK_PUBKEY)]; int pubkdatalen = Base64::atob(SDK_PUBKEY, (byte *)pubkdata, sizeof(pubkdata)); ////////////////////// //Test AES-CBC encryption string result; string input = CONTENT; SymmCipher sym(enckey); ASSERT_TRUE(sym.cbc_encrypt_pkcs_padding(&input, iv, &result)); //Check result char* base64Result = new char[result.size()*4/3+4]; Base64::btoa((const byte*)result.data(), result.size(), base64Result); ASSERT_STREQ(base64Result, CIPHER_BYTES); ////////////////////// //Test AES-CBC decryption string plain; ASSERT_TRUE(sym.cbc_decrypt_pkcs_padding(&result, iv, &plain)); //Check result ASSERT_STREQ(input.c_str(), plain.c_str()); ////////////////////// //Test HMAC-SHA256 string toAuth; toAuth.assign((char*)iv, static_cast<size_t>(ivLen)); toAuth.append(result); string mac; mac.resize(32); HMACSHA256 hmacProcessor(hmacKey, static_cast<size_t>(hmacKeyLen)); hmacProcessor.add((byte *)toAuth.data(), toAuth.size()); hmacProcessor.get((byte *)mac.data()); //Check result char* macResult = new char[mac.size()*4/3+4]; Base64::btoa((const byte*)mac.data(), mac.size(), macResult); ASSERT_STREQ(macResult, HMAC); ////////////////////// //Test PayCrypter:encryptPayload() PrnGen rng; string payCrypterResult; PayCrypter payCrypter(rng); payCrypter.setKeys(enckey, hmacKey, iv); ASSERT_TRUE(payCrypter.encryptPayload(&input, &payCrypterResult)); //Prepare the expected result string CRYPT_PAYLOAD = mac; CRYPT_PAYLOAD.append((char*)iv, static_cast<size_t>(ivLen)); CRYPT_PAYLOAD.append(result); char* expectedPayload = new char[CRYPT_PAYLOAD.size()*4/3+4]; Base64::btoa((const byte*)CRYPT_PAYLOAD.data(), CRYPT_PAYLOAD.size(), expectedPayload); //Check result char* encryptPayloadResult = new char[payCrypterResult.size()*4/3+4]; Base64::btoa((const byte*)payCrypterResult.data(), payCrypterResult.size(), encryptPayloadResult); ASSERT_STREQ(expectedPayload, encryptPayloadResult); ////////////////////// //Test PayCrypter:rsaEncryptKeys(), disabling random padding to get known results string message = "Klaatu barada nikto."; string rsaRes; ASSERT_TRUE(payCrypter.rsaEncryptKeys(&message, pubkdata, pubkdatalen, &rsaRes, false)); //Check result char* rsaResult = new char[rsaRes.size()*4/3+4]; Base64::btoa((const byte*)rsaRes.data(), rsaRes.size(), rsaResult); ASSERT_STREQ(rsaResult, CRYPT_GORT_SAYS); ////////////////////// //Test PayCrypter:rsaEncryptKeys() with a binary input, disabling random padding to get known results string cryptKeysBytesBin; message.assign(keys, static_cast<size_t>(keysLen)); ASSERT_TRUE(payCrypter.rsaEncryptKeys(&message, pubkdata, pubkdatalen, &cryptKeysBytesBin, false)); //Check result char* cryptKeysBytes = new char[cryptKeysBytesBin.size()*4/3+4]; Base64::btoa((const byte*)cryptKeysBytesBin.data(), cryptKeysBytesBin.size(), cryptKeysBytes); ASSERT_STREQ(cryptKeysBytes, CRYPT_KEYS_BYTES); ////////////////////// //Test PayCrypter:hybridEncrypt() string finalResult; string contentString = CONTENT; ASSERT_TRUE(payCrypter.hybridEncrypt(&contentString, pubkdata, pubkdatalen, &finalResult, false)); //Prepare the expected result string expectedResult = cryptKeysBytesBin; expectedResult.append(CRYPT_PAYLOAD); char* expectedBase64Result = new char[expectedResult.size()*4/3+4]; Base64::btoa((const byte*)expectedResult.data(), expectedResult.size(), expectedBase64Result); //Check result char* finalCheck = new char[finalResult.size()*4/3+4]; Base64::btoa((const byte*)finalResult.data(), finalResult.size(), finalCheck); ASSERT_STREQ(finalCheck, expectedBase64Result); ////////////////////// delete[] finalCheck; delete[] expectedBase64Result; delete[] cryptKeysBytes; delete[] rsaResult; delete[] base64Result; delete[] macResult; delete[] expectedPayload; delete[] encryptPayloadResult; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/PendingContactRequest_test.cpp�����������������������������������������������0000664�0000000�0000000�00000004045�15162662266�0022607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/pendingcontactrequest.h> namespace { void checkPcrs(const mega::PendingContactRequest& exp, const mega::PendingContactRequest& act) { ASSERT_EQ(exp.id, act.id); ASSERT_EQ(exp.originatoremail, act.originatoremail); ASSERT_EQ(exp.targetemail, act.targetemail); ASSERT_EQ(exp.ts, act.ts); ASSERT_EQ(exp.uts, act.uts); ASSERT_EQ(exp.msg, act.msg); ASSERT_EQ(exp.isoutgoing, act.isoutgoing); } } TEST(PendingContactRequest, serialize_unserialize) { mega::PendingContactRequest pcr{1, "blah", "foo", 2, 3, "hello", true}; std::string d; ASSERT_TRUE(pcr.serialize(&d)); auto newPcr = std::unique_ptr<mega::PendingContactRequest>{mega::PendingContactRequest::unserialize(&d)}; checkPcrs(pcr, *newPcr); } TEST(PendingContactRequest, unserialize_32bit) { mega::PendingContactRequest pcr{1, "blah", "foo", 2, 3, "hello", true}; // This is the result of serialization on 32bit Windows const std::array<char, 40> rawData = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x62, 0x6c, 0x61, 0x68, 0x03, 0x66, 0x6f, 0x6f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x01 }; std::string d(rawData.data(), rawData.size()); auto newPcr = std::unique_ptr<mega::PendingContactRequest>{mega::PendingContactRequest::unserialize(&d)}; checkPcrs(pcr, *newPcr); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Scoped_timer_test.cpp��������������������������������������������������������0000664�0000000�0000000�00000001146�15162662266�0020752�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "gtest/gtest.h" #include "mega/scoped_timer.h" #include <thread> #include <chrono> using mega::ScopedSteadyTimer; using namespace std::chrono_literals; TEST(ScopedTimer, ScopedSteadyTimerMeasurePassedTimeCorrectly) { ScopedSteadyTimer timer; // sleep_for blocks the execution of the current thread for at least the specified // sleep_duration. It may block for longer than sleep_duration due to scheduling or resource // contention delays. std::this_thread::sleep_for(1000ms); auto duration = timer.passedTime(); ASSERT_GE(duration, 1000ms); // Never could be less that 1s } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Serialization_test.cpp�������������������������������������������������������0000664�0000000�0000000�00000046010�15162662266�0021151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <atomic> #include <memory> #include <numeric> #include <thread> #include <gtest/gtest.h> #include <mega.h> #include <megaapi.h> #include <mega/heartbeats.h> #include "DefaultedFileSystemAccess.h" #include "DefaultedDbTable.h" #include "utils.h" TEST(Serialization, JSON_storeobject) { std::string in_str("Test"); mega::JSON j; j.begin(in_str.data()); j.storeobject(&in_str); } // Test 64-bit int serialization/unserialization TEST(Serialization, Serialize64_serialize) { uint64_t in = 0xDEADBEEF; uint64_t out; mega::byte buf[sizeof in]; mega::Serialize64::serialize(buf, in); ASSERT_GT(mega::Serialize64::unserialize(buf, sizeof buf, &out), 0); ASSERT_EQ(in, out); } TEST(Serialization, CacheableReaderWriter) { auto checksize = [](size_t& n, size_t added) { n += added; return n; }; std::string writestring; mega::CacheableWriter w(writestring); mega::byte binary[] = { 1, 2, 3, 4, 5 }; std::string cstr1("test1"); std::string cstr2("test2diffdata"); std::string stringtest("diffstringagaindefinitelybigger"); int64_t i64 = static_cast<int64_t>(0x8765432112345678); uint32_t u32 = 0x87678765; mega::handle handle1 = 0x998; bool b = true; mega::byte by = 5; mega::chunkmac_map cm; size_t sizeadded = 0; w.serializebinary(binary, sizeof(binary)); ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(binary))); w.serializecstr(cstr1.c_str(), true); ASSERT_EQ(writestring.size(), checksize(sizeadded, 2 + cstr1.size() + 1)); w.serializecstr(cstr2.c_str(), false); ASSERT_EQ(writestring.size(), checksize(sizeadded, 2 + cstr2.size())); w.serializestring(stringtest); ASSERT_EQ(writestring.size(), checksize(sizeadded, 2 + stringtest.size())); w.serializei64(i64); ASSERT_EQ(writestring.size(), checksize(sizeadded, 8)); w.serializeu32(u32); ASSERT_EQ(writestring.size(), checksize(sizeadded, 4)); w.serializehandle(handle1); ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(mega::handle))); w.serializebool(b); ASSERT_EQ(writestring.size(), checksize(sizeadded, sizeof(bool))); w.serializebyte(by); ASSERT_EQ(writestring.size(), checksize(sizeadded, 1)); w.serializeexpansionflags(1, 0, 1, 0, 0, 0, 1, 1); ASSERT_EQ(writestring.size(), checksize(sizeadded, 8)); writestring += "abc"; // now read the serialized data back std::string readstring = writestring; mega::CacheableReader r(readstring); mega::byte check_binary[5]; std::string check_cstr1; std::string check_cstr2; std::string check_stringtest; int64_t check_i64; uint32_t check_u32; mega::handle check_handle1; bool check_b; mega::byte check_by; mega::chunkmac_map check_cm; ASSERT_TRUE(r.unserializebinary(check_binary, sizeof(check_binary))); ASSERT_EQ(0, memcmp(check_binary, binary, sizeof(binary))); ASSERT_TRUE(r.unserializecstr(check_cstr1, true)); ASSERT_EQ(check_cstr1, cstr1); ASSERT_TRUE(r.unserializecstr(check_cstr2, false)); ASSERT_EQ(check_cstr2, cstr2); ASSERT_TRUE(r.unserializestring(check_stringtest)); ASSERT_EQ(check_stringtest, stringtest); ASSERT_TRUE(r.unserializei64(check_i64)); ASSERT_EQ(check_i64, i64); ASSERT_TRUE(r.unserializeu32(check_u32)); ASSERT_EQ(check_u32, u32); ASSERT_TRUE(r.unserializehandle(check_handle1)); ASSERT_EQ(check_handle1, handle1); ASSERT_TRUE(r.unserializebool(check_b)); ASSERT_EQ(check_b, b); ASSERT_TRUE(r.unserializebyte(check_by)); ASSERT_EQ(check_by, by); unsigned char expansions[8]; ASSERT_FALSE(r.unserializeexpansionflags(expansions, 7)); ASSERT_TRUE(r.unserializeexpansionflags(expansions, 8)); ASSERT_EQ(expansions[0], 1); ASSERT_EQ(expansions[1], 0); ASSERT_EQ(expansions[2], 1); ASSERT_EQ(expansions[3], 0); ASSERT_EQ(expansions[4], 0); ASSERT_EQ(expansions[5], 0); ASSERT_EQ(expansions[6], 1); ASSERT_EQ(expansions[7], 1); r.eraseused(readstring); ASSERT_EQ(readstring, "abc"); mega::MediaProperties mp; mp.shortformat = 1; mp.width = 2; mp.height = 3; mp.fps = 4; mp.playtime = 5; mp.containerid = 6; mp.videocodecid = 7; mp.audiocodecid = 8; mp.is_VFR = true; mp.no_audio = false; std::string mps = mp.serialize(); mega::MediaProperties mp2(mps); ASSERT_EQ(mps, mp2.serialize()); ASSERT_EQ(mp2.shortformat, 1); ASSERT_EQ(mp2.width, 2u); ASSERT_EQ(mp2.height, 3u); ASSERT_EQ(mp2.fps, 4u); ASSERT_EQ(mp2.playtime, 5u); ASSERT_EQ(mp2.containerid, 6u); ASSERT_EQ(mp2.videocodecid, 7u); ASSERT_EQ(mp2.audiocodecid, 8u); ASSERT_EQ(mp2.is_VFR, true); ASSERT_EQ(mp2.no_audio, false); } TEST(Serialization, SerializeWString) { std::wstring wstringtest(L"file-файл.txt"); std::string writestring; mega::CacheableWriter w(writestring); w.serializestring(wstringtest); EXPECT_EQ(writestring.size(), wstringtest.size() * sizeof(wchar_t) + 2); // now read the serialized data back mega::CacheableReader r(writestring); std::wstring readedWstring; EXPECT_TRUE(r.unserializestring(readedWstring)); ASSERT_EQ(readedWstring, wstringtest); } namespace { //struct MockFileSystemAccess : mt::DefaultedFileSystemAccess //{ // void local2path(std::string* local, std::string* path) const override // { // *path = *local; // } // // void path2local(std::string* local, std::string* path) const override // { // *path = *local; // } // // bool getsname(std::string*, std::string*) const override // { // return false; // } //}; struct MockClient { mega::MegaApp app; ::mega::FSACCESS_CLASS fs; std::shared_ptr<mega::MegaClient> cli = mt::makeClient(app); MockClient() { mega::PrnGen gen; mt::DefaultedDbTable *defaultTable = new mt::DefaultedDbTable(gen); cli->sctable.reset(defaultTable); cli->mNodeManager.setTable(defaultTable); } }; } namespace { void checkDeserializedNode(const mega::Node& dl, const mega::Node& ref, bool ignore_fileattrstring = false) { ASSERT_EQ(ref.type, dl.type); ASSERT_EQ(ref.size, dl.size); ASSERT_EQ(ref.nodehandle, dl.nodehandle); ASSERT_EQ(ref.parenthandle, dl.parenthandle); ASSERT_EQ(ref.owner, dl.owner); ASSERT_EQ(ref.ctime, dl.ctime); ASSERT_EQ(!!dl.attrstring, !!ref.attrstring); ASSERT_TRUE(!dl.attrstring || *dl.attrstring == *ref.attrstring); ASSERT_EQ(ref.nodekeyUnchecked(), dl.nodekeyUnchecked()); ASSERT_EQ(ignore_fileattrstring ? "" : ref.fileattrstring, dl.fileattrstring); ASSERT_EQ(ref.attrs.map, dl.attrs.map); if (ref.plink) { ASSERT_NE(nullptr, dl.plink); ASSERT_EQ(ref.plink->ph, dl.plink->ph); ASSERT_EQ(ref.plink->cts, dl.plink->cts); ASSERT_EQ(ref.plink->ets, dl.plink->ets); ASSERT_EQ(ref.plink->takendown, dl.plink->takendown); } // TODO: deal with shares } } TEST(Serialization, Node_whenFolderIsEncrypted) { MockClient client; auto& n = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42)); n.attrstring.reset(new std::string("attrstring")); n.setKey("nodekeydata"); std::string data; ASSERT_TRUE(n.serialize(&data)); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); ASSERT_TRUE(dn); checkDeserializedNode(*dn, n); } TEST(Serialization, Node_whenFileIsEncrypted) { MockClient client; auto& n = mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42)); n.attrstring.reset(new std::string("attrstring")); n.setKey("nodekeydata"); n.size = 16; std::string data; ASSERT_TRUE(n.serialize(&data)); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); ASSERT_TRUE(dn); checkDeserializedNode(*dn, n); } TEST(Serialization, Node_whenTypeIsUnsupported) { MockClient client; auto& n = mt::makeNode(*client.cli, mega::TYPE_UNKNOWN, ::mega::NodeHandle().set6byte(42)); std::string data; ASSERT_FALSE(n.serialize(&data)); } TEST(Serialization, Node_forFile_withoutParent_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) { MockClient client; std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42))}; n->size = 12; n->owner = 43; n->ctime = 44; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(90u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFolder_withoutParent_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) { MockClient client; std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42))}; n->size = -1; n->owner = 43; n->ctime = 44; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(71u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(90u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares_withoutFileAttrString_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(104u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(108u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; n->plink.reset(new mega::PublicLink{n->nodehandle, 1, 2, false}); std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(131u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares_withAuthKey) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; using namespace mega; n->attrs.map = map<nameid, string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; n->plink.reset(new mega::PublicLink{n->nodehandle, 1, 2, false, "someAuthKey"}); std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(142u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFile_withoutShares_32bit) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FILENODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = 12; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; n->plink.reset(new mega::PublicLink{n->nodehandle, 1, 2, false}); // This is the result of serialization on 32bit Windows const std::array<char, 131> rawData = { 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x05, 0x00, 0x62, 0x6c, 0x61, 0x68, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x65, 0x03, 0x00, 0x66, 0x6f, 0x6f, 0x01, 0x66, 0x03, 0x00, 0x62, 0x61, 0x72, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const std::string data(rawData.data(), rawData.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFolder_withoutShares_withoutAttrs_withoutFileAttrString_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = -1; n->owner = 88; n->ctime = 44; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(71u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFolder_withoutShares_withoutFileAttrString_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = -1; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(85u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n); } TEST(Serialization, Node_forFolder_withoutShares_withoutPlink) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = -1; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(85u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n, true); } TEST(Serialization, Node_forFolder_withoutShares) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = -1; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; n->plink.reset(new mega::PublicLink{n->nodehandle, 1, 2, false}); std::string data; ASSERT_TRUE(n->serialize(&data)); ASSERT_EQ(108u, data.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n, true); } TEST(Serialization, Node_forFolder_withoutShares_32bit) { MockClient client; auto& parent = mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(43)); std::unique_ptr<mega::Node> n{&mt::makeNode(*client.cli, mega::FOLDERNODE, ::mega::NodeHandle().set6byte(42), &parent)}; n->size = -1; n->owner = 88; n->ctime = 44; n->attrs.map = std::map<mega::nameid, std::string>{ {101, "foo"}, {102, "bar"}, }; n->fileattrstring = "blah"; n->plink.reset(new mega::PublicLink{n->nodehandle, 1, 2, false}); // This is the result of serialization on 32bit Windows const std::array<unsigned char, 108> rawData = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x65, 0x03, 0x00, 0x66, 0x6f, 0x6f, 0x01, 0x66, 0x03, 0x00, 0x62, 0x61, 0x72, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const std::string data(reinterpret_cast<const char*>(rawData.data()), rawData.size()); auto dn = client.cli->mNodeManager.getNodeFromBlob(&data); checkDeserializedNode(*dn, *n, true); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Share_test.cpp���������������������������������������������������������������0000664�0000000�0000000�00000004744�15162662266�0017406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/share.h> void checkNewShares(const mega::NewShare& exp, const mega::NewShare& act) { ASSERT_EQ(exp.h, act.h); ASSERT_EQ(exp.outgoing, act.outgoing); ASSERT_EQ(exp.peer, act.peer); ASSERT_EQ(exp.access, act.access); ASSERT_EQ(exp.ts, act.ts); ASSERT_TRUE(std::equal(exp.key, exp.key + mega::SymmCipher::BLOCKSIZE, act.key)); ASSERT_EQ(exp.have_key, act.have_key); ASSERT_EQ(exp.have_auth, act.have_auth); ASSERT_EQ(exp.pending, act.pending); } TEST(Share, serialize_unserialize) { mega::User user; user.userhandle = 42; mega::PendingContactRequest pcr{123}; mega::Share share{&user, mega::RDONLY, 13, &pcr}; std::string d; share.serialize(&d); mega::byte key[mega::SymmCipher::BLOCKSIZE]; std::fill(key, key + mega::SymmCipher::BLOCKSIZE, 'X'); auto data = d.c_str(); auto newShare = std::unique_ptr<mega::NewShare>{mega::Share::unserialize(-1, 100, key, &data, d.data() + d.size())}; const mega::NewShare expectedNewShare{100, -1, user.userhandle, mega::RDONLY, 13, key, NULL, 123}; checkNewShares(expectedNewShare, *newShare); } TEST(Share, unserialize_32bit) { // This is the result of serialization on 32bit Windows const std::array<char, 26> rawData = { 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const std::string d(rawData.data(), rawData.size()); mega::byte key[mega::SymmCipher::BLOCKSIZE]; std::fill(key, key + mega::SymmCipher::BLOCKSIZE, 'X'); auto data = d.c_str(); auto newShare = std::unique_ptr<mega::NewShare>{mega::Share::unserialize(-1, 100, key, &data, d.data() + d.size())}; const mega::NewShare expectedNewShare{100, -1, 42, mega::RDONLY, 13, key, NULL, 123}; checkNewShares(expectedNewShare, *newShare); } ����������������������������sdk-10.11.0/tests/unit/SharedMemoryZone_test.cpp����������������������������������������������������0000664�0000000�0000000�00000005233�15162662266�0021571�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/types.h" #include <gtest/gtest.h> #include <atomic> #include <chrono> #include <thread> #include <type_traits> #include <vector> using ::SharedResource; TEST(SharedMemoryZone, OwnerThreadReadIsConst) { SharedResource<std::vector<int>> zone; zone.initOwnerThread(); const auto guard = zone.getReadGuardOwnerThread(); using DataRef = decltype(guard.getData()); static_assert(std::is_const<std::remove_reference_t<DataRef>>::value, "Read guard should expose const container"); } TEST(SharedMemoryZone, OwnerThreadWriteAllowsMutation) { SharedResource<std::vector<int>> zone; zone.initOwnerThread(); { auto guard = zone.getWriteGuardOwnerThread(); guard.getData().push_back(42); } const auto guard = zone.getReadGuardOwnerThread(); ASSERT_EQ(guard.getData().size(), 1u); EXPECT_EQ(guard.getData().front(), 42); } TEST(SharedMemoryZone, OtherThreadReadUsesMutexAndAllowsConstAccess) { SharedResource<std::vector<int>> zone; zone.initOwnerThread(); { auto guard = zone.getWriteGuardOwnerThread(); guard.getData().push_back(7); } std::atomic<size_t> observedSize{0}; std::thread reader( [&]() { const auto guard = zone.getReadGuardOtherThread(); observedSize = guard.getData().size(); }); reader.join(); EXPECT_EQ(observedSize.load(), 1u); } TEST(SharedMemoryZone, OtherThreadReadIsConst) { SharedResource<std::vector<int>> zone; zone.initOwnerThread(); std::thread reader( [&]() { const auto guard = zone.getReadGuardOtherThread(); using DataRef = decltype(guard.getData()); static_assert(std::is_const<std::remove_reference_t<DataRef>>::value, "Read guard should expose const container"); }); reader.join(); } TEST(SharedMemoryZone, OtherThreadWaitsForTimedMutex) { SharedResource<std::vector<int>> zone; zone.initOwnerThread(); std::thread reader; std::atomic<long long> elapsedMs{-1}; { auto guard = zone.getWriteGuardOwnerThread(); reader = std::thread{( [&]() { const auto start = std::chrono::steady_clock::now(); const auto guard = zone.getReadGuardOtherThread(); const auto end = std::chrono::steady_clock::now(); elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); })}; std::this_thread::sleep_for(std::chrono::milliseconds(1200)); } reader.join(); ASSERT_GE(elapsedMs.load(), 1000); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Sqlite_test.cpp��������������������������������������������������������������0000664�0000000�0000000�00000006303�15162662266�0017576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* @brief Unit tests for the Sqlite functionalities * * This test suite validates sqlite functionalites */ #include <gtest/gtest.h> #include <mega/db/sqlite.h> #include <mega/localpath.h> #include <filesystem> #include <mega.h> #include <stdfs.h> #include <string> using namespace mega; /** * @brief Validate renameDBFiles method * * Steps: * - Create new data base * - Call to renameDBFiles * - Check if all files has been renamed */ #ifdef WIN32 // As DB is opened when files are renamed, windows doesn't allow rename DB files TEST(Sqlite, DISABLED_renameDB) { #else TEST(Sqlite, renameDB) { #endif auto pathString{std::filesystem::current_path() / "folder"}; const MrProper cleanUp( [pathString]() { std::filesystem::remove_all(pathString); }); std::filesystem::create_directory(pathString); LocalPath folderPath = LocalPath::fromAbsolutePath(path_u8string(pathString)); SqliteDbAccess dbAccess{folderPath}; // Create and open DB std::unique_ptr<FileSystemAccess> fsaccess{new FSACCESS_CLASS}; const std::string dbName{"dbName"}; LocalPath currentDataBasePath{dbAccess.databasePath(*fsaccess, dbName, DbAccess::DB_VERSION)}; PrnGen rng; constexpr int flags = 0; std::unique_ptr<SqliteDbTable> db{dbAccess.open(rng, *fsaccess, dbName, flags, nullptr)}; if (!db) { ASSERT_TRUE(false) << "Failure opening DB"; } // Insert elements for (int i = 1; i < 10; ++i) { std::string content = "content " + std::to_string(i); db->put(static_cast<uint32_t>((i += DbTable::IDSPACING) | MegaClient::CACHEDUSER), static_cast<char*>(content.data()), static_cast<unsigned>(content.length())); } // check if auxiliar files exist LocalPath shmPath = currentDataBasePath; shmPath.append(LocalPath::fromRelativePath("-shm")); bool shmExists = std::filesystem::exists(shmPath.toPath(false)); EXPECT_TRUE(shmExists) << "Unexpected behavior, -shm file doesn't exist"; LocalPath walPath = currentDataBasePath; walPath.append(LocalPath::fromRelativePath("-wal")); bool walExists = std::filesystem::exists(walPath.toPath(false)); EXPECT_TRUE(walExists) << "Unexpected behavior, -wal file doesn't exist"; // Determine new path const std::string dbNewName{"dbNewName"}; LocalPath newDataBasePath{dbAccess.databasePath(*fsaccess, dbNewName, DbAccess::DB_VERSION)}; // Rename DB EXPECT_TRUE(dbAccess.renameDBFiles(*fsaccess, currentDataBasePath, newDataBasePath)) << "Failure to rename files (maybe they are in use)"; // Verify if auxiliar files exist if (shmExists) { shmPath = newDataBasePath; shmPath.append(LocalPath::fromRelativePath("-shm")); std::string aux = shmPath.toPath(false); EXPECT_TRUE(std::filesystem::exists(aux)) << "File " << aux << "doesn't exit when it should"; } if (walExists) { walPath = newDataBasePath; walPath.append(LocalPath::fromRelativePath("-wal")); std::string aux = walPath.toPath(false); EXPECT_TRUE(std::filesystem::exists(aux)) << "File " << aux << "doesn't exit when it should"; } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/SyncUploadThrottling_test.cpp������������������������������������������������0000664�0000000�0000000�00000035601�15162662266�0022500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file SyncUploadThrottling_test.cpp * @brief This file is expected to contain unit tests involving SyncUploadThrottlingFile logic. */ #ifdef ENABLE_SYNC #include "mega/megaclient.h" #include "mega/syncinternals/syncuploadthrottlingfile.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <chrono> using namespace mega; using namespace testing; static constexpr std::chrono::seconds DEFAULT_UPLOAD_COUNTER_INACTIVITY_EXPIRATION_TIME{10}; static constexpr unsigned DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE{2}; namespace { /** * @brief Generates a file fingerprint with the specified size and modification time. */ FileFingerprint generateFingerprint(const m_off_t size, const m_time_t mtime) { FileFingerprint fingerprint; fingerprint.size = size; fingerprint.mtime = mtime; return fingerprint; } /** * @brief Fixture to test SyncUpload_inClient with the mocked MockSyncThreadsafeState. * * This fixture is useful to test changes in the SyncUpload_inClient for in-flight uploads or * delayed uploads, testing that the abortion or adjustement logic works as expected. * * The MockSyncThreadsafeState is an attribute of SyncUpload_inClient which is mocked for this * fixture, so we don't need to mock the whole SyncUpload_inClient. Also, it uses a StrictMock to * ensure the order of the expectations, such as the one for the transferBegin method which is * called during the SyncUpload_inClient instantiation. That initial expectation is necessary in * order to correctly check other later expectations which are specific of the new changes, like the * fingerprint update. * * Apart from using a StrictMock, the expectations are forced to be checked in order with * InSequence. */ class UploadThrottlingFileChangesTest: public Test { protected: /** * @brief Mock class for SyncThreadsafeState * * The purpose is to be able to have a real SyncUpload_inClient, which is feasible for our unit * tests, and only mock the SyncThreadsafeState attribute. */ class MockSyncThreadsafeState: public SyncThreadsafeState { public: MockSyncThreadsafeState(const handle backupId, MegaClient* const client, const bool canChangeVault): SyncThreadsafeState(backupId, client, canChangeVault) {} // Mock methods from SyncThreadsafeState MOCK_METHOD(void, transferBegin, (direction_t direction, m_off_t numBytes), (override)); MOCK_METHOD(void, transferComplete, (direction_t direction, m_off_t numBytes), (override)); MOCK_METHOD(void, transferFailed, (direction_t direction, m_off_t numBytes), (override)); MOCK_METHOD(void, removeExpectedUpload, (NodeHandle h, const std::string& name), (override)); }; void SetUp() override { const handle dummyBackupId{1}; MegaClient* const dummyClient{nullptr}; const bool canChangeVault{false}; mMockSyncThreadsafeState = std::make_shared<StrictMock<MockSyncThreadsafeState>>(dummyBackupId, dummyClient, canChangeVault); // Add the initial expectation that should be triggered during SyncUpload_inClient // instantiation. EXPECT_CALL(*mMockSyncThreadsafeState, transferBegin(PUT, mInitialFingerprint.size)) .Times(1); } /** * @brief Create and initialize the SyncUpload_inClient with the MockSyncThreadSafeState. * This method should be called right after adding all the necessary expectations. * * @warning No additional expectations should be added after calling this method. */ void initializeSyncUpload_inClient() { const LocalPath dummyFullPath; const handle fsid{123}; const LocalPath dummyLocalName; const bool fromInshare{false}; // Create the SyncUpload_inClient mSyncUpload = std::make_shared<SyncUpload_inClient>(mDummyHandle, dummyFullPath, mNodeName, mInitialFingerprint, mMockSyncThreadsafeState, fsid, dummyLocalName, fromInshare, INVALID_META_MAC, SyncTransfer_inClient::AttributeOnlyUpdate::None); mSyncUpload->wasRequesterAbandoned = true; // We do not finish uploads. } static constexpr bool DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE{false}; static constexpr size_t DEFAULT_SIZE{50}; static constexpr m_time_t DEFAULT_MTIME{50}; const NodeHandle mDummyHandle{}; const std::string mNodeName{"testNode"}; const LocalPath mDummyLocalName{}; const FileFingerprint mInitialFingerprint{generateFingerprint(DEFAULT_SIZE, DEFAULT_MTIME)}; const FileFingerprint mDummyFingerprint; const LocalPath mDummyFullPath; UploadThrottlingFile mThrottlingFile; InSequence mSeq; // We want the expectations to be called in order. std::shared_ptr<MockSyncThreadsafeState> mMockSyncThreadsafeState; shared_ptr<SyncUpload_inClient> mSyncUpload; }; /** * @brief Increases the upload counter nTimes. * * @param UploadThrottlingFile The throttling file object whose upload counter must be increased. * @param nTimes The number of times to increase the counter. */ void increaseUploadCounter(UploadThrottlingFile& throttlingFile, const unsigned nTimes) { for (auto i = nTimes; i--;) { throttlingFile.increaseUploadCounter(); } } } // namespace // UploadThrottlingFileTest test cases /** * @test Verifies that the upload counter method increases the counter correctly. */ TEST(UploadThrottlingFileTest, IncreaseUploadCounterIncrementsCounter) { // Initial state UploadThrottlingFile throttlingFile; ASSERT_EQ(throttlingFile.uploadCounter(), 0); // Increase the counter and check expectations. constexpr unsigned numIncreases = 2; increaseUploadCounter(throttlingFile, numIncreases); ASSERT_EQ(throttlingFile.uploadCounter(), numIncreases); } /** * @test Verifies that the upload counter resets after inactivity. */ TEST(UploadThrottlingFileTest, CheckUploadThrottlingResetsCounterAfterInactivity) { UploadThrottlingFile throttlingFile; increaseUploadCounter(throttlingFile, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE); const auto uploadCounterInactivityExpirationTime = std::chrono::seconds(2); std::this_thread::sleep_for( uploadCounterInactivityExpirationTime + std::chrono::seconds(1)); // Wait enough time to exceed the inactivity expiration time. ASSERT_FALSE(throttlingFile.checkUploadThrottling(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, uploadCounterInactivityExpirationTime)); } /** * @test Verifies that throttling is applied when max uploads are exceeded. */ TEST(UploadThrottlingFileTest, CheckUploadThrottlingExceedsMaxUploads) { UploadThrottlingFile throttlingFile; increaseUploadCounter(throttlingFile, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE); ASSERT_TRUE( throttlingFile.checkUploadThrottling(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, DEFAULT_UPLOAD_COUNTER_INACTIVITY_EXPIRATION_TIME)); } /** * @test Verifies that the bypass flag is respected during throttling checks. * 1. First call should not bypass throttling. * 2. After setting the flag, next call should bypass throttling. * 3. Next call after that should not bypass throttling (flag is reset). */ TEST(UploadThrottlingFileTest, CheckUploadThrottlingBypassFlag) { UploadThrottlingFile throttlingFile; increaseUploadCounter(throttlingFile, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE); // First call should not bypass throttling. ASSERT_TRUE( throttlingFile.checkUploadThrottling(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, DEFAULT_UPLOAD_COUNTER_INACTIVITY_EXPIRATION_TIME)); // Set bypass flag to true. throttlingFile.bypassThrottlingNextTime(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE); // Next call should bypass throttling. ASSERT_FALSE( throttlingFile.checkUploadThrottling(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, DEFAULT_UPLOAD_COUNTER_INACTIVITY_EXPIRATION_TIME)); // Subsequent calls should not bypass. ASSERT_TRUE( throttlingFile.checkUploadThrottling(DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, DEFAULT_UPLOAD_COUNTER_INACTIVITY_EXPIRATION_TIME)); } // UploadThrottlingFileChangesTest test cases /** * @test Verifies that no abort occurs when putnodes have started. */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadNoAbortWhenPutnodesStarted) { EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, mInitialFingerprint.size)).Times(1); EXPECT_CALL(*mMockSyncThreadsafeState, removeExpectedUpload(mDummyHandle, mNodeName)).Times(1); initializeSyncUpload_inClient(); mSyncUpload->upsyncStarted = true; ASSERT_FALSE(mThrottlingFile.handleAbortUpload(*mSyncUpload, DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE, mDummyFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); } /** * @test Verifies that no abort occurs when upload is completed but it wasn't processed yet when * calling handleAbortUpload. */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadNoAbortWhenUploadIsCompleted) { EXPECT_CALL(*mMockSyncThreadsafeState, transferComplete(PUT, mInitialFingerprint.size)) .Times(1); EXPECT_CALL(*mMockSyncThreadsafeState, removeExpectedUpload(mDummyHandle, mNodeName)).Times(1); initializeSyncUpload_inClient(); mSyncUpload->upsyncStarted = true; mSyncUpload->wasFileTransferCompleted = true; mSyncUpload->wasUpsyncCompleted = true; mSyncUpload->wasRequesterAbandoned = false; ASSERT_FALSE(mThrottlingFile.handleAbortUpload(*mSyncUpload, DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE, mDummyFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); } /** * @test Verifies that no abort occurs when the upload hasn't started and the fingerprint is * updated. The fingerprint is checked before and after handleAbortUpload to ensure it is correctly * updated. */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadNoAbortWhenNotStartedAndUpdateFingerprint) { const FileFingerprint newFingerprint = generateFingerprint(100, 20); EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, mInitialFingerprint.size)).Times(1); EXPECT_CALL(*mMockSyncThreadsafeState, transferBegin(PUT, newFingerprint.size)).Times(1); EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, newFingerprint.size)).Times(1); initializeSyncUpload_inClient(); ASSERT_NE(newFingerprint.size, mSyncUpload->fingerprint().size); ASSERT_NE(newFingerprint.mtime, mSyncUpload->fingerprint().mtime); ASSERT_FALSE(mThrottlingFile.handleAbortUpload(*mSyncUpload, DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE, newFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); ASSERT_EQ(newFingerprint.size, mSyncUpload->fingerprint().size); ASSERT_EQ(newFingerprint.mtime, mSyncUpload->fingerprint().mtime); } /** * @test Verifies that the upload must be aborted if it started but putnodes does not. * Case 1: The upload counter did not reach DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE and the upload must * bypass throttling logic next time. */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadDoNotSetBypassFlag) { EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, mInitialFingerprint.size)).Times(1); initializeSyncUpload_inClient(); mSyncUpload->wasStarted = true; increaseUploadCounter(mThrottlingFile, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE - 1); ASSERT_TRUE(mThrottlingFile.handleAbortUpload(*mSyncUpload, DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE, mDummyFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); ASSERT_FALSE(mThrottlingFile.willBypassThrottlingNextTime()); } /** * @test Verifies that the upload must be aborted if it started but putnodes does not. * Case 2: The upload counter reached DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE and the upload must * bypass throttling logic next time. */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadAndSetBypassFlag) { EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, mInitialFingerprint.size)).Times(1); initializeSyncUpload_inClient(); mSyncUpload->wasStarted = true; increaseUploadCounter(mThrottlingFile, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE); ASSERT_TRUE(mThrottlingFile.handleAbortUpload(*mSyncUpload, DEFAULT_TRANSFER_DIRECTION_NEEDS_TO_CHANGE, mDummyFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); ASSERT_TRUE(mThrottlingFile.willBypassThrottlingNextTime()); } /** * @test Verifies that the upload must be aborted when the transfer direction needs to change (and * put nodes has not started). */ TEST_F(UploadThrottlingFileChangesTest, HandleAbortUploadAbortDueToTransferDirectionNeedsToChange) { EXPECT_CALL(*mMockSyncThreadsafeState, transferFailed(PUT, mInitialFingerprint.size)).Times(1); initializeSyncUpload_inClient(); constexpr bool transferDirectionNeedsToChange{true}; ASSERT_TRUE(mThrottlingFile.handleAbortUpload(*mSyncUpload, transferDirectionNeedsToChange, mDummyFingerprint, DEFAULT_MAX_UPLOADS_BEFORE_THROTTLE, mDummyFullPath)); } #endif // ENABLE_SYNC �������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Sync_conflict_test.cpp�������������������������������������������������������0000664�0000000�0000000�00000021410�15162662266�0021126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2021 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <memory> #include <gmock/gmock.h> #include <gtest/gtest.h> #include "megaapi_impl.h" #include "mega/sync.h" #ifdef ENABLE_SYNC namespace SyncConflictTests { using namespace mega; TEST(SyncStallHashTest, MegaSyncStallPrivateGetHash) { // Create some SyncStallEntry objects to initialize the MegaSyncStallPrivate after SyncStallEntry e1{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink } }; SyncStallEntry e1_same{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink } }; SyncStallEntry e2{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::NoProblem } }; std::hash<SyncStallEntry> hashEntryGetter; ASSERT_EQ(hashEntryGetter(e1), hashEntryGetter(e1)); ASSERT_EQ(hashEntryGetter(e1), hashEntryGetter(e1_same)); ASSERT_NE(hashEntryGetter(e1), hashEntryGetter(e2)); MegaSyncStallPrivate s1(e1); MegaSyncStallPrivate s1_same(e1_same); MegaSyncStallPrivate s2(e2); ASSERT_EQ(hashEntryGetter(e1), s1.getHash()); ASSERT_EQ(hashEntryGetter(e1_same), s1_same.getHash()); ASSERT_EQ(hashEntryGetter(e2), s2.getHash()); ASSERT_EQ(s1.getHash(), s1.getHash()); ASSERT_EQ(s1.getHash(), s1_same.getHash()); ASSERT_NE(s1.getHash(), s2.getHash()); } TEST(SyncStallHashTest, MegaSyncNameConflictStallPrivateGetHash) { // Create some NameConflict objects to initialize the MegaSyncNameConflictStallPrivate after std::vector<NameConflict::NameHandle> nhVec{}; std::vector<LocalPath> clashingLNames{}; NameConflict nc1{"cloudPath", nhVec, LocalPath(), clashingLNames}; NameConflict nc1_same{"cloudPath", nhVec, LocalPath(), clashingLNames}; nhVec.emplace_back("nameHandle", NodeHandle()); clashingLNames.emplace_back(LocalPath::fromAbsolutePath("./test/local")); NameConflict nc2{"cloudPath", nhVec, LocalPath(), clashingLNames}; std::hash<NameConflict> hashNCGetter; ASSERT_EQ(hashNCGetter(nc1), hashNCGetter(nc1)); ASSERT_EQ(hashNCGetter(nc1), hashNCGetter(nc1_same)); ASSERT_NE(hashNCGetter(nc1), hashNCGetter(nc2)); MegaSyncNameConflictStallPrivate s1(nc1); MegaSyncNameConflictStallPrivate s1_same(nc1_same); MegaSyncNameConflictStallPrivate s2(nc2); ASSERT_EQ(hashNCGetter(nc1), s1.getHash()); ASSERT_EQ(hashNCGetter(nc1_same), s1_same.getHash()); ASSERT_EQ(hashNCGetter(nc2), s2.getHash()); ASSERT_EQ(s1.getHash(), s1.getHash()); ASSERT_EQ(s1.getHash(), s1_same.getHash()); ASSERT_NE(s1.getHash(), s2.getHash()); } /** * @class MegaSyncStallListTest * @brief Dummy implementation of the MegaSyncStallList for testing purpose * */ class MegaSyncStallListTest: public MegaSyncStallList { public: std::vector<std::shared_ptr<MegaSyncStall>> mStalls; MegaSyncStallListTest(std::vector<std::shared_ptr<MegaSyncStall>>&& stalls): mStalls{stalls} {} size_t size() const override { return mStalls.size(); } const MegaSyncStall* get(size_t i) const override { return mStalls[i].get(); } }; class MegaSyncStallMapTest: public MegaSyncStallMapPrivate { public: MegaSyncStallMapTest(): MegaSyncStallMapPrivate() {} void add(const MegaHandle backupId, const MegaSyncStallListPrivate& testList) { mStallsMap.emplace(backupId, testList); } }; TEST(SyncStallHashTest, MegaSyncStallIssuesMapGetHash) { std::vector<NameConflict::NameHandle> nhVec{}; std::vector<LocalPath> clashingLNames{}; NameConflict nc1{"cloudPath1", nhVec, LocalPath(), clashingLNames}; std::shared_ptr<MegaSyncNameConflictStallPrivate> s1{new MegaSyncNameConflictStallPrivate(nc1)}; nhVec.emplace_back("nameHandle", NodeHandle()); clashingLNames.emplace_back(LocalPath::fromAbsolutePath("./test/local")); NameConflict nc2{"cloudPath2", nhVec, LocalPath(), clashingLNames}; std::shared_ptr<MegaSyncNameConflictStallPrivate> s2{new MegaSyncNameConflictStallPrivate(nc2)}; SyncStallEntry e1{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath1", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath1", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink } }; std::shared_ptr<MegaSyncStallPrivate> s3{new MegaSyncStallPrivate(e1)}; SyncStallEntry e2{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath2", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath2", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::NoProblem } }; std::shared_ptr<MegaSyncStallPrivate> s4{new MegaSyncStallPrivate(e1)}; MegaSyncStallListPrivate sl1; sl1.addStall(s1); sl1.addStall(s3); MegaSyncStallListPrivate sl2; sl1.addStall(s2); sl1.addStall(s4); MegaSyncStallListPrivate sl3; sl1.addStall(s2); sl1.addStall(s3); MegaSyncStallMapTest m1; m1.add(111111111111111, sl1); m1.add(222222222222222, sl2); MegaSyncStallMapTest m2; m2.add(111111111111111, sl1); m2.add(222222222222222, sl2); MegaSyncStallMapTest m3; m2.add(222222222222222, sl2); m2.add(111111111111111, sl1); MegaSyncStallMapTest m4; m4.add(222222222222222, sl1); m4.add(111111111111111, sl3); ASSERT_EQ(m1.getHash(), m2.getHash()); ASSERT_NE(m1.getHash(), m3.getHash()); ASSERT_NE(m2.getHash(), m4.getHash()); } TEST(SyncStallHashTest, MegaSyncStallListGetHash) { std::vector<NameConflict::NameHandle> nhVec{}; std::vector<LocalPath> clashingLNames{}; NameConflict nc1{"cloudPath", nhVec, LocalPath(), clashingLNames}; std::shared_ptr<MegaSyncNameConflictStallPrivate> s1{new MegaSyncNameConflictStallPrivate(nc1)}; SyncStallEntry e1{ SyncWaitReason::FileIssue, true, false, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {NodeHandle{}, "currentPath", PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink}, {LocalPath{}, PathProblem::DetectedSymlink } }; std::shared_ptr<MegaSyncStallPrivate> s2{new MegaSyncStallPrivate(e1)}; MegaSyncStallListTest testList1{ std::vector<std::shared_ptr<MegaSyncStall>>{s1, s2} }; MegaSyncStallListTest testList1_same{ std::vector<std::shared_ptr<MegaSyncStall>>{s1, s2} }; MegaSyncStallListTest testList2{ std::vector<std::shared_ptr<MegaSyncStall>>{s2, s1} }; MegaSyncStallListTest testList3{std::vector<std::shared_ptr<MegaSyncStall>>{s2}}; ASSERT_EQ(testList1.getHash(), testList1.getHash()); ASSERT_EQ(testList1.getHash(), testList1_same.getHash()); ASSERT_NE(testList1.getHash(), testList2.getHash()); ASSERT_NE(testList1.getHash(), testList3.getHash()); ASSERT_NE(testList2.getHash(), testList3.getHash()); } } // namespace #endif // ENABLE_SYNC ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Sync_test.cpp����������������������������������������������������������������0000664�0000000�0000000�00000075264�15162662266�0017265�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "constants.h" #include "DefaultedDbTable.h" #include "DefaultedDirAccess.h" #include "DefaultedFileAccess.h" #include "DefaultedFileSystemAccess.h" #include "FsNode.h" #include "utils.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/filesystem.h> #include <mega/heartbeats.h> #include <mega/megaapp.h> #include <mega/megaclient.h> #include <mega/scoped_helpers.h> #include <mega/sync.h> #include <mega/types.h> #include <memory> #include <numeric> #ifdef ENABLE_SYNC namespace mega { enum { TYPE_TWOWAY = SyncConfig::TYPE_TWOWAY }; enum { TYPE_UP = SyncConfig::TYPE_UP }; enum { TYPE_DOWN = SyncConfig::TYPE_DOWN }; }; namespace SyncConfigTests { using namespace mega; using namespace testing; class Directory { public: Directory(FSACCESS_CLASS& fsAccess, const LocalPath& path) : mFSAccess(fsAccess) , mPath(path) { mFSAccess.mkdirlocal(mPath, false, true); } ~Directory() { mFSAccess.emptydirlocal(mPath); mFSAccess.rmdirlocal(mPath); } MEGA_DISABLE_COPY_MOVE(Directory) operator const LocalPath&() const { return mPath; } const LocalPath& path() const { return mPath; } private: FSACCESS_CLASS& mFSAccess; LocalPath mPath; }; // Directory // Temporary shims so that we can easily switch to using // NiceMock / FakeStrictMock when GMock/GTest is upgraded on Jenkins. #if 0 template<typename MockClass> class FakeNiceMock : public MockClass { public: using MockClass::MockClass; }; // FakeNiceMock<T> template<typename MockClass> class FakeStrictMock : public MockClass { public: using MockClass::MockClass; }; // FakeStrictMock<T> #else template<typename T> using FakeNiceMock = NiceMock<T>; template<typename T> using FakeStrictMock = StrictMock<T>; #endif class Utilities { public: static string randomBase64(const size_t n = 16) { return Base64::btoa(randomBytes(n)); } static string randomBytes(const size_t n) { return mRNG.genstring(n); } static bool randomFile(LocalPath path, const size_t n = 64) { auto fileAccess = mFSAccess.newfileaccess(false); if (!fileAccess->fopen(path, OPEN_WRONLY, FSLogging::logOnError)) { return false; } if (fileAccess->size > 0) { if (!fileAccess->ftruncate()) { return false; } } const string data = randomBytes(n); const byte* bytes = reinterpret_cast<const byte*>(&data[0]); return fileAccess->fwrite(bytes, static_cast<unsigned>(n), 0x0); } static LocalPath randomPath(const LocalPath& prefix, const size_t n = 16) { LocalPath result = prefix; result.appendWithSeparator(randomPathRelative(n), false); return result; } static LocalPath randomPathAbsolute(const size_t n = 16) { return LocalPath::fromAbsolutePath(randomBase64(n)); } static LocalPath randomPathRelative(const size_t n = 16) { return LocalPath::fromRelativePath(randomBase64(n)); } static LocalPath separator() { #ifdef _WIN32 return LocalPath::fromRelativePath("\\"); #else // _WIN32 return LocalPath::fromRelativePath("/"); #endif // ! _WIN32 } private: static FSACCESS_CLASS mFSAccess; static PrnGen mRNG; }; // Utilities FSACCESS_CLASS Utilities::mFSAccess; PrnGen Utilities::mRNG; class SyncConfigTest : public Test { public: class IOContext : public SyncConfigIOContext { public: IOContext(FileSystemAccess& fsAccess, const string& authKey, const string& cipherKey, const string& name, PrnGen& rng) : SyncConfigIOContext(fsAccess, authKey, cipherKey, name, rng) { // Perform real behavior by default. ON_CALL(*this, driveID(_)) .WillByDefault(Invoke(this, &IOContext::driveIDConcrete)); ON_CALL(*this, getSlotsInOrder(_, _)) .WillByDefault(Invoke(this, &IOContext::getSlotsInOrderConcrete)); ON_CALL(*this, read(_, _, _)) .WillByDefault(Invoke(this, &IOContext::readConcrete)); ON_CALL(*this, remove(_, _)) .WillByDefault(Invoke(this, &IOContext::removeSlotConcrete)); ON_CALL(*this, remove(_)) .WillByDefault(Invoke(this, &IOContext::removeAllSlotsConcrete)); ON_CALL(*this, write(_, _, _)) .WillByDefault(Invoke(this, &IOContext::writeConcrete)); } MOCK_CONST_METHOD1(driveID, handle(const LocalPath&)); MOCK_METHOD2(getSlotsInOrder, error(const LocalPath&, vector<unsigned int>&)); MOCK_METHOD3(read, error(const LocalPath&, string&, unsigned int)); MOCK_METHOD2(remove, error(const LocalPath&, unsigned int)); MOCK_METHOD1(remove, error(const LocalPath&)); MOCK_METHOD3(write, error(const LocalPath&, const string&, unsigned int)); private: // Delegate to real behavior. handle driveIDConcrete(const LocalPath& drivePath) { return SyncConfigIOContext::driveID(drivePath); } error getSlotsInOrderConcrete(const LocalPath& dbPath, vector<unsigned int>& slotsVec) { return SyncConfigIOContext::getSlotsInOrder(dbPath, slotsVec); } error readConcrete(const LocalPath& dbPath, string& data, unsigned int slot) { return SyncConfigIOContext::read(dbPath, data, slot); } error removeSlotConcrete(const LocalPath& dbPath, unsigned int slot) { return SyncConfigIOContext::remove(dbPath, slot); } error removeAllSlotsConcrete(const LocalPath& dbPath) { return SyncConfigIOContext::remove(dbPath); } error writeConcrete(const LocalPath& dbPath, const string& data, unsigned int slot) { return SyncConfigIOContext::write(dbPath, data, slot); } }; // IOContext SyncConfigTest() : Test() , mFSAccess() , mRNG() , mConfigAuthKey(Utilities::randomBytes(16)) , mConfigCipherKey(Utilities::randomBytes(16)) , mConfigName(Utilities::randomBase64(16)) , mIOContext(mFSAccess, mConfigAuthKey, mConfigCipherKey, mConfigName, mRNG) { } string emptyDB() const { return "{\"sy\":[]}"; } FSACCESS_CLASS& fsAccess() { return mFSAccess; } IOContext& ioContext() { return mIOContext; } protected: FSACCESS_CLASS mFSAccess; PrnGen mRNG; const string mConfigAuthKey; const string mConfigCipherKey; const string mConfigName; FakeNiceMock<IOContext> mIOContext; }; // SyncConfigTest class SyncConfigIOContextTest : public SyncConfigTest { public: SyncConfigIOContextTest() : SyncConfigTest() { } string configName() const { return configPrefix() + mConfigName; } const string& configPrefix() const { return SyncConfigIOContext::NAME_PREFIX; } }; // SyncConfigIOContextTest TEST_F(SyncConfigIOContextTest, GetBadPath) { vector<unsigned int> slotsVec; // Generate a bogus path. const auto drivePath = Utilities::randomPathAbsolute(); // Try to read slots from an invalid path. EXPECT_NE(ioContext().getSlotsInOrder(drivePath, slotsVec), API_OK); // Slots should be empty. EXPECT_TRUE(slotsVec.empty()); } TEST_F(SyncConfigIOContextTest, GetNoSlots) { // Make sure the drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Generate some malformed slots for this user. { LocalPath configPath = drive; // This file will be ignored as it has no slot suffix. configPath.appendWithSeparator( LocalPath::fromRelativePath(configName()), false); EXPECT_TRUE(Utilities::randomFile(configPath)); // This file will be ignored as it has a malformed slot suffix. configPath.append(LocalPath::fromRelativePath(".")); EXPECT_TRUE(Utilities::randomFile(configPath)); // This file will be ignored as it has an invalid slot suffix. configPath.append(LocalPath::fromRelativePath("Q")); EXPECT_TRUE(Utilities::randomFile(configPath)); } // Generate a slot for a different user. { LocalPath configPath = drive; configPath.appendWithSeparator( LocalPath::fromRelativePath(configPrefix()), false); configPath.append(Utilities::randomPathRelative()); configPath.append(LocalPath::fromRelativePath(".0")); EXPECT_TRUE(Utilities::randomFile(configPath)); } vector<unsigned int> slotsVec; // Try and get a list of slots. EXPECT_EQ(ioContext().getSlotsInOrder(drive.path(), slotsVec), API_OK); // Slots should be empty. EXPECT_TRUE(slotsVec.empty()); } TEST_F(SyncConfigIOContextTest, GetSlotsOrderedByModificationTime) { const size_t NUM_SLOTS = 3; // Make sure drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Generate some slots for this user. { LocalPath configPath = drive; // Generate suitable config path prefix. configPath.appendWithSeparator( LocalPath::fromRelativePath(configName()), false); for (size_t i = 0; i < NUM_SLOTS; ++i) { using std::to_string; LocalPath newPath = configPath; // Generate suffix. LocalPath suffixPath = LocalPath::fromRelativePath("." + to_string(i)); // Complete config path. newPath.append(suffixPath); // Populate the file. EXPECT_TRUE(Utilities::randomFile(newPath)); // Set the modification time. EXPECT_TRUE(fsAccess().setmtimelocal(newPath, static_cast<m_time_t>(i * 1000))); } } vector<unsigned int> slotsVec; // Get the slots. EXPECT_EQ(ioContext().getSlotsInOrder(drive.path(), slotsVec), API_OK); // Did we retrieve the correct number of slots? ASSERT_EQ(slotsVec.size(), NUM_SLOTS); // Are the slots ordered by descending modification time? { vector<unsigned int> expected(NUM_SLOTS, 0); iota(begin(expected), end(expected), 0); EXPECT_TRUE(equal(begin(expected), end(expected), slotsVec.rbegin())); } } TEST_F(SyncConfigIOContextTest, GetSlotsOrderedBySlotSuffix) { const size_t NUM_SLOTS = 3; // Make sure drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Generate some slots for this user. { LocalPath configPath = drive; // Generate suitable config path prefix. configPath.appendWithSeparator( LocalPath::fromRelativePath(configName()), false); for (size_t i = 0; i < NUM_SLOTS; ++i) { using std::to_string; LocalPath newPath = configPath; // Generate suffix. LocalPath suffixPath = LocalPath::fromRelativePath("." + to_string(i)); // Complete config path. newPath.append(suffixPath); // Populate the file. EXPECT_TRUE(Utilities::randomFile(newPath)); // Set the modification time. EXPECT_TRUE(fsAccess().setmtimelocal(newPath, 0)); } } vector<unsigned int> slotsVec; // Get the slots. EXPECT_EQ(ioContext().getSlotsInOrder(drive.path(), slotsVec), API_OK); // Did we retrieve the correct number of slots? EXPECT_EQ(slotsVec.size(), NUM_SLOTS); // Are the slots ordered by descending slot number when their // modification time is the same? { vector<unsigned int> expected(NUM_SLOTS, 0); iota(begin(expected), end(expected), 0); EXPECT_TRUE(equal(begin(expected), end(expected), slotsVec.rbegin())); } } TEST_F(SyncConfigIOContextTest, Read) { // Make sure the drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Try writing some data out and reading it back again. { string read; string written = Utilities::randomBytes(64); EXPECT_EQ(ioContext().write(drive.path(), written, 0), API_OK); EXPECT_EQ(ioContext().read(drive.path(), read, 0), API_OK); EXPECT_EQ(read, written); } // Try a different slot to make sure it has an effect. { string read; string written = Utilities::randomBytes(64); EXPECT_EQ(ioContext().read(drive.path(), read, 1), API_EREAD); EXPECT_TRUE(read.empty()); EXPECT_EQ(ioContext().write(drive.path(), written, 1), API_OK); EXPECT_EQ(ioContext().read(drive.path(), read, 1), API_OK); EXPECT_EQ(read, written); } } TEST_F(SyncConfigIOContextTest, ReadBadData) { string data; // Make sure the drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Generate slot path. LocalPath slotPath = drive; slotPath.appendWithSeparator( LocalPath::fromRelativePath(configName()), false); slotPath.append(LocalPath::fromRelativePath(".0")); // Try loading a file that's too short to be valid. EXPECT_TRUE(Utilities::randomFile(slotPath, 1)); EXPECT_EQ(ioContext().read(drive.path(), data, 0), API_EREAD); EXPECT_TRUE(data.empty()); // Try loading a file composed entirely of junk. EXPECT_TRUE(Utilities::randomFile(slotPath, 128)); EXPECT_EQ(ioContext().read(drive.path(), data, 0), API_EREAD); EXPECT_TRUE(data.empty()); } TEST_F(SyncConfigIOContextTest, ReadBadPath) { const LocalPath drivePath = Utilities::randomPathAbsolute(); string data; // Try and read data from an insane path. EXPECT_EQ(ioContext().read(drivePath, data, 0), API_EREAD); EXPECT_TRUE(data.empty()); } TEST_F(SyncConfigIOContextTest, RemoveSlot) { // Make sure drive path exists. Directory drive(fsAccess(), Utilities::randomPathAbsolute()); // Generate a slot for this user. { LocalPath configPath = drive; // Generate path prefix. configPath.appendWithSeparator( LocalPath::fromRelativePath(configName()), false); // Generate suffix. configPath.append(LocalPath::fromRelativePath(".0")); // Populate slot. EXPECT_TRUE(Utilities::randomFile(configPath)); } // Remove the slot. EXPECT_EQ(ioContext().remove(drive.path(), 0), API_OK); // Remove again won't fail since we don't want errors in the log that are not actually problems EXPECT_EQ(ioContext().remove(drive.path(), 0), API_OK); } TEST_F(SyncConfigIOContextTest, RemoveSlots) { const auto drivePath = Utilities::randomPathAbsolute(); // No slots to remove. EXPECT_CALL(ioContext(), getSlotsInOrder(Eq(drivePath), _)) .WillOnce(Return(API_ENOENT)); EXPECT_EQ(ioContext().remove(drivePath), API_ENOENT); // Two slots to remove. static const vector<unsigned int> slotsVec = {0, 1}; EXPECT_CALL(ioContext(), getSlotsInOrder(Eq(drivePath), _)) .WillRepeatedly(DoAll(SetArgReferee<1>(slotsVec), Return(API_OK))); // All slots should be removed successfully. EXPECT_CALL(ioContext(), remove(Eq(drivePath), _)) .WillRepeatedly(Return(API_OK)); EXPECT_EQ(ioContext().remove(drivePath), API_OK); // Should only succeed if all slots can be removed. EXPECT_CALL(ioContext(), remove(Eq(drivePath), Eq(0u))) .WillRepeatedly(Return(API_EWRITE)); EXPECT_EQ(ioContext().remove(drivePath), API_EWRITE); } TEST_F(SyncConfigIOContextTest, Serialize) { SyncConfigVector read; SyncConfigVector written; JSONWriter writer; // Populate the database with two configs. { SyncConfig config; config.mBackupId = 1; config.mEnabled = false; config.mError = NO_SYNC_ERROR; config.mFilesystemFingerprint = fsfp_t(2, "1"); config.mLocalPath = Utilities::randomPathAbsolute(); config.mName = Utilities::randomBase64(); config.mOriginalPathOfRemoteRootNode = Utilities::randomBase64(); config.mRemoteNode = NodeHandle(); config.mWarning = NO_SYNC_WARNING; config.mSyncType = SyncConfig::TYPE_TWOWAY; written.emplace_back(config); config.mBackupId = 2; config.mEnabled = true; config.mError = UNKNOWN_ERROR; config.mFilesystemFingerprint = fsfp_t(2, "2"); config.mLocalPath = Utilities::randomPathAbsolute(); config.mName = Utilities::randomBase64(); config.mOriginalPathOfRemoteRootNode = Utilities::randomBase64(); config.mRemoteNode.set6byte(3); config.mWarning = LOCAL_IS_FAT; config.mSyncType = SyncConfig::TYPE_BACKUP; written.emplace_back(config); } // Serialize the database. { ioContext().serialize(written, writer); EXPECT_FALSE(writer.getstring().empty()); } // Deserialize the database. { JSON reader(writer.getstring()); EXPECT_TRUE(ioContext().deserialize(read, reader, false)); } // Are the databases identical? ASSERT_EQ(read.size(), written.size()); for (auto i = read.size(); i--; ) { auto& a = read[i]; auto& b = written[i]; EXPECT_EQ(a.mBackupId, b.mBackupId); EXPECT_EQ(a.mEnabled, b.mEnabled); EXPECT_EQ(a.mError, b.mError); EXPECT_EQ(a.mFilesystemFingerprint, b.mFilesystemFingerprint); EXPECT_EQ(a.mLocalPath, b.mLocalPath); EXPECT_EQ(a.mName, b.mName); EXPECT_EQ(a.mOriginalPathOfRemoteRootNode, b.mOriginalPathOfRemoteRootNode); EXPECT_EQ(a.mRemoteNode, b.mRemoteNode); EXPECT_EQ(a.mWarning, b.mWarning); EXPECT_EQ(a.mSyncType, b.mSyncType); } } TEST_F(SyncConfigIOContextTest, SerializeEmpty) { JSONWriter writer; // Serialize an empty database. { static const SyncConfigVector empty; // Does serializing an empty database yield an empty array? ioContext().serialize(empty, writer); EXPECT_EQ(writer.getstring(), emptyDB()); } // Deserialize the empty database. { SyncConfigVector configs; JSON reader(writer.getstring()); // Can we deserialize an empty database? EXPECT_TRUE(ioContext().deserialize(configs, reader, true)); EXPECT_TRUE(configs.empty()); } } TEST_F(SyncConfigIOContextTest, WriteBadPath) { const LocalPath drivePath = Utilities::randomPathAbsolute(); const string data = Utilities::randomBytes(64); auto dbPath = drivePath; dbPath.appendWithSeparator(Utilities::randomPathRelative(), false); // Try and write data to an insane path. EXPECT_NE(ioContext().write(dbPath, data, 0), API_OK); } class SyncConfigStoreTest : public SyncConfigTest { public: SyncConfigStoreTest() : SyncConfigTest() { } }; // SyncConfigStoreTest TEST_F(SyncConfigStoreTest, Read) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigVector written; // Create a database for later reading. { SyncConfigStore store(db, ioContext()); // Read empty so that the drive is known. EXPECT_EQ(store.read(LocalPath(), written, false), API_ENOENT); // Drive should be known. ASSERT_TRUE(store.driveKnown(LocalPath())); // Create a config for writing. SyncConfig config; config.mBackupId = 1; config.mLocalPath = Utilities::randomPathAbsolute(); config.mRemoteNode.set6byte(2); written.emplace_back(config); // Write database to disk. EXPECT_EQ(store.write(LocalPath(), written), API_OK); } SyncConfigStore store(db, ioContext()); SyncConfigVector read; // Read database back. EXPECT_EQ(store.read(LocalPath(), read, false), API_OK); // Drive should now be known. EXPECT_TRUE(store.driveKnown(LocalPath())); // Configs should be precisely what we wrote. ASSERT_EQ(read.size(), written.size()); for (auto i = read.size(); i--; ) { auto& a = read[i]; auto& b = written[i]; EXPECT_EQ(a.mBackupId, b.mBackupId); EXPECT_EQ(a.mEnabled, b.mEnabled); EXPECT_EQ(a.mError, b.mError); EXPECT_EQ(a.mFilesystemFingerprint, b.mFilesystemFingerprint); EXPECT_EQ(a.mLocalPath, b.mLocalPath); EXPECT_EQ(a.mName, b.mName); EXPECT_EQ(a.mOriginalPathOfRemoteRootNode, b.mOriginalPathOfRemoteRootNode); EXPECT_EQ(a.mRemoteNode, b.mRemoteNode); EXPECT_EQ(a.mWarning, b.mWarning); EXPECT_EQ(a.mSyncType, b.mSyncType); } } TEST_F(SyncConfigStoreTest, ReadEmpty) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); // No slots available for reading. EXPECT_CALL(ioContext(), getSlotsInOrder(Eq(db.path()), _)) .WillOnce(Return(API_ENOENT)); // No attempts should be made to read from disk. EXPECT_CALL(ioContext(), read(Eq(db.path()), _, _)) .Times(0); SyncConfigVector configs; // Read should inform the caller that no database is present. EXPECT_EQ(store.read(LocalPath(), configs, false), API_ENOENT); // Configs should remain empty. EXPECT_TRUE(configs.empty()); // Drive should be known as read didn't signal a fatal error. EXPECT_TRUE(store.driveKnown(LocalPath())); // Store should remain clean. EXPECT_FALSE(store.dirty()); } TEST_F(SyncConfigStoreTest, ReadFailNoDriveID) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); Directory drive(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); // Shouldn't try to read slots if we can't read the drive ID. EXPECT_CALL(ioContext(), getSlotsInOrder(_, _)) .Times(0); // Read of drive ID should fail. EXPECT_CALL(ioContext(), driveID(Eq(drive.path()))) .WillOnce(Return(UNDEF)); SyncConfigVector configs; // Read should report a fatal read error. EXPECT_EQ(store.read(drive, configs, true), API_EREAD); // Drive shouldn't be known. EXPECT_FALSE(store.driveKnown(drive)); // No slots available for reading. EXPECT_CALL(ioContext(), getSlotsInOrder(_, _)) .WillOnce(Return(API_ENOENT)); // Drive ID read should succeed. EXPECT_CALL(ioContext(), driveID(Eq(drive.path()))) .WillOnce(Return(1u)); // Read should report no entries. EXPECT_EQ(store.read(drive, configs, true), API_ENOENT); // Drive should now be known. EXPECT_TRUE(store.driveKnown(drive)); // Drive ID should be cached. EXPECT_EQ(store.driveID(drive), 1u); } TEST_F(SyncConfigStoreTest, ReadFail) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); // Return a single slot for reading. Expectation get = EXPECT_CALL(ioContext(), getSlotsInOrder(Eq(db.path()), _)) .WillOnce(DoAll(SetArgReferee<1>(vector<unsigned>{0}), Return(API_OK))); // Attempts to read the slot should fail. Expectation read = EXPECT_CALL(ioContext(), read(Eq(db.path()), _, Eq(0u))) .After(get) .WillOnce(Return(API_EREAD)); SyncConfigVector configs; // Read should report a fatal read error. EXPECT_EQ(store.read(LocalPath(), configs, false), API_EREAD); // Configs should remain empty. EXPECT_TRUE(configs.empty()); // Drive should remain unknown as read failed fatally. EXPECT_FALSE(store.driveKnown(LocalPath())); // Store should remain clean. EXPECT_FALSE(store.dirty()); } TEST_F(SyncConfigStoreTest, ReadFailFallback) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); // Return three slots for reading. Expectation get = EXPECT_CALL(ioContext(), getSlotsInOrder(Eq(db.path()), _)) .WillOnce(DoAll(SetArgReferee<1>(vector<unsigned>{2, 1, 0}), Return(API_OK))); // Attempts to read slots 2 and 1 should fail. Expectation read21 = EXPECT_CALL(ioContext(), read(Eq(db.path()), _, _)) .Times(2) .After(get) .WillRepeatedly(Return(API_EREAD)); // Attempts to read slot 0 should succeed. Expectation read0 = EXPECT_CALL(ioContext(), read(Eq(db.path()), _, Eq(0u))) .After(read21) .WillOnce(DoAll(SetArgReferee<1>(emptyDB()), Return(API_OK))); SyncConfigVector configs; // Read should succeed. EXPECT_EQ(store.read(LocalPath(), configs, false), API_OK); // Configs should remain empty. EXPECT_TRUE(configs.empty()); // Drive should be known as the read eventually succeeded. EXPECT_TRUE(store.driveKnown(LocalPath())); // Should should remain clean. EXPECT_FALSE(store.dirty()); } TEST_F(SyncConfigStoreTest, WriteDirty) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); SyncConfigVector configs; // Perform a read such that the drive is known. EXPECT_EQ(store.read(LocalPath(), configs, false), API_ENOENT); SyncConfigVector internal; // Populate configs. { SyncConfig config; // External config.mBackupId = 1; config.mExternalDrivePath = Utilities::randomPathAbsolute(); config.mLocalPath = Utilities::randomPath(config.mExternalDrivePath); config.mRemoteNode.set6byte(2); configs.emplace_back(config); // Internal config.mBackupId = 2; config.mExternalDrivePath.clear(); config.mLocalPath = Utilities::randomPathAbsolute(); config.mRemoteNode.set6byte(3); configs.emplace_back(config); internal.emplace_back(config); } // Mark internal database for writing. store.markDriveDirty(LocalPath()); // Serialize configs for later comparison. JSONWriter writer; ioContext().serialize(internal, writer); // First attempt to write the database should fail. // Subsequent attempts should succeed. EXPECT_CALL(ioContext(), write(Eq(db.path()), Eq(writer.getstring()), Eq(0u))) .Times(2) .WillOnce(Return(API_EWRITE)) .WillRepeatedly(Return(API_OK)); // First attempt to write dirty databases should fail. store.writeDirtyDrives(configs); // Store should be clean. EXPECT_FALSE(store.dirty()); // Mark drive as dirty. store.markDriveDirty(LocalPath()); // Second attempt to write dirty databases should succeed. store.writeDirtyDrives(configs); // Store should now be clean. EXPECT_FALSE(store.dirty()); } TEST_F(SyncConfigStoreTest, WriteEmpty) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); SyncConfigVector configs; // Read empty so that the drive is known. EXPECT_EQ(store.read(LocalPath(), configs, false), API_ENOENT); // Drive should now be known. ASSERT_TRUE(store.driveKnown(LocalPath())); // Attempt to remove the database should fail. EXPECT_CALL(ioContext(), remove(Eq(db.path()))) .WillOnce(Return(API_EWRITE)); // Mark drive as dirty. store.markDriveDirty(LocalPath()); // Write should fail. EXPECT_EQ(store.write(LocalPath(), configs), API_EWRITE); // Store should be clean. EXPECT_FALSE(store.dirty()); // Mark drive as dirty. store.markDriveDirty(LocalPath()); // Next attempt to remove should succeed. EXPECT_CALL(ioContext(), remove(Eq(db.path()))) .WillOnce(Return(API_OK)); // Write should succeed. EXPECT_EQ(store.write(LocalPath(), configs), API_OK); // Store should now be clean. EXPECT_FALSE(store.dirty()); } TEST_F(SyncConfigStoreTest, Write) { Directory db(fsAccess(), Utilities::randomPathAbsolute()); SyncConfigStore store(db, ioContext()); SyncConfigVector configs; // Read empty so that the drive is known. EXPECT_EQ(store.read(LocalPath(), configs, false), API_ENOENT); // Drive should be known. ASSERT_TRUE(store.driveKnown(LocalPath())); // Populate config vector. { SyncConfig config; config.mBackupId = 2; config.mLocalPath = Utilities::randomPathAbsolute(); config.mRemoteNode.set6byte(3); configs.emplace_back(config); } // Serialize configs for later comparison. JSONWriter writer; ioContext().serialize(configs, writer); // Attempts to write the first slot should fail. EXPECT_CALL(ioContext(), write(Eq(db.path()), Eq(writer.getstring()), Eq(0u))) .WillOnce(Return(API_EWRITE)); // No databases should be removed in anticipation as the write failed. EXPECT_CALL(ioContext(), remove(Eq(db.path()), Eq(1u))) .Times(0); // Mark drive as being dirty. store.markDriveDirty(LocalPath()); // Write should fail. EXPECT_EQ(store.write(LocalPath(), configs), API_EWRITE); // Store should be clean. EXPECT_FALSE(store.dirty()); // Mark drive as dirty. store.markDriveDirty(LocalPath()); // Attempts to write the first slot should succeed. EXPECT_CALL(ioContext(), write(Eq(db.path()), Eq(writer.getstring()), Eq(0u))) .WillOnce(Return(API_OK)); // Next slot should be removed in anticipation of future writes. EXPECT_CALL(ioContext(), remove(Eq(db.path()), Eq(1u))) .WillOnce(Return(API_OK)); // Write should succeed. EXPECT_EQ(store.write(LocalPath(), configs), API_OK); // Store should be clean. EXPECT_FALSE(store.dirty()); } } // SyncConfigTests #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/TextChat_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000007560�15162662266�0020067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/megaclient.h> #include <mega/megaapp.h> #include <mega/types.h> #include "DefaultedFileSystemAccess.h" #include "utils.h" #ifdef ENABLE_CHAT namespace { class MockFileSystemAccess : public mt::DefaultedFileSystemAccess { }; void checkTextChats(const mega::TextChat& exp, const mega::TextChat& act) { ASSERT_EQ(exp.getChatId(), act.getChatId()); ASSERT_EQ(exp.getOwnPrivileges(), act.getOwnPrivileges()); ASSERT_EQ(exp.getShard(), act.getShard()); ASSERT_EQ(*exp.getUserPrivileges(), *act.getUserPrivileges()); ASSERT_EQ(exp.getGroup(), act.getGroup()); ASSERT_EQ(exp.getTitle(), act.getTitle()); ASSERT_EQ(exp.getOwnUser(), act.getOwnUser()); ASSERT_EQ(exp.getTs(), act.getTs()); ASSERT_EQ(exp.getAttachments(), act.getAttachments()); ASSERT_EQ(exp.isFlagSet(0), act.isFlagSet(0)); ASSERT_EQ(exp.publicChat(), act.publicChat()); ASSERT_EQ(exp.getUnifiedKey(), act.getUnifiedKey()); } } TEST(TextChat, serialize_unserialize) { mega::MegaApp app; auto client = mt::makeClient(app); mega::TextChat tc(true); tc.setChatId(1); tc.setOwnPrivileges(mega::PRIV_STANDARD); tc.setShard(2); tc.addUserPrivileges(3, mega::PRIV_MODERATOR); tc.addUserPrivileges(4, mega::PRIV_RO); tc.setGroup(true); tc.setTitle("foo"); tc.setOwnUser(5); tc.setTs(6); tc.addUserForAttachment(7, 8); tc.addUserForAttachment(7, 9); tc.addUserForAttachment(8, 10); tc.setFlag(true, 0); tc.setUnifiedKey("bar"); std::string d; ASSERT_TRUE(tc.serialize(&d)); auto newTc = mega::TextChat::unserialize(client.get(), &d); checkTextChats(tc, *newTc); } TEST(TextChat, unserialize_32bit) { mega::MegaApp app; auto client = mt::makeClient(app); mega::TextChat tc(true); tc.setChatId(1); tc.setOwnPrivileges(mega::PRIV_STANDARD); tc.setShard(2); tc.addUserPrivileges(3, mega::PRIV_MODERATOR); tc.addUserPrivileges(4, mega::PRIV_RO); tc.setGroup(true); tc.setTitle("foo"); tc.setOwnUser(5); tc.setTs(6); tc.addUserForAttachment(7, 8); tc.addUserForAttachment(7, 9); tc.addUserForAttachment(8, 10); tc.setFlag(true, 0); tc.setUnifiedKey("bar"); // This is the result of serialization on 32bit Windows const std::array<char, 125> rawData = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x66, 0x6f, 0x6f, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x62, 0x61, 0x72 }; std::string d(rawData.data(), rawData.size()); auto newTc = mega::TextChat::unserialize(client.get(), &d); checkTextChats(tc, *newTc); } #endif ������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/Transfer_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000021735�15162662266�0020127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/megaapp.h" #include "mega/raid.h" #include "mega/transfer.h" #include "utils.h" #include <gtest/gtest.h> namespace { void checkTransfers(const mega::Transfer& exp, const mega::Transfer& act) { ASSERT_EQ(exp.type, act.type); ASSERT_EQ(exp.localfilename, act.localfilename); ASSERT_EQ(exp.filekey.bytes, act.filekey.bytes); ASSERT_EQ(exp.ctriv, act.ctriv); ASSERT_EQ(exp.metamac, act.metamac); ASSERT_TRUE(std::equal(exp.transferkey.data(), exp.transferkey.data() + mega::SymmCipher::KEYLENGTH, act.transferkey.data())); ASSERT_EQ(exp.lastaccesstime, act.lastaccesstime); ASSERT_EQ(exp.ultoken != nullptr, act.ultoken != nullptr); // Both NULLs OR both valid if (exp.ultoken && act.ultoken) { ASSERT_EQ(*exp.ultoken, *act.ultoken); } ASSERT_EQ(exp.tempurls, act.tempurls); ASSERT_EQ(exp.state, act.state); ASSERT_EQ(exp.priority, act.priority); } void setupTransfer(mega::Transfer& tf, const std::string& localfilename, char filekeyChar, int64_t ctriv, int64_t metamac, char transferkeyChar, int64_t lastaccesstime) { tf.localfilename = ::mega::LocalPath::fromAbsolutePath(localfilename); std::fill(&tf.filekey.bytes[0], &tf.filekey.bytes[0] + sizeof(tf.filekey), filekeyChar); tf.ctriv = ctriv; tf.metamac = metamac; std::fill(tf.transferkey.data(), tf.transferkey.data() + mega::SymmCipher::KEYLENGTH, transferkeyChar); tf.lastaccesstime = lastaccesstime; } } TEST(Transfer, serialize_unserialize_raid_urls_same_length) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "foo", 'X', 1, 2, 'Y', 3); tf.ultoken.reset(new mega::UploadToken); std::fill((::mega::byte*)tf.ultoken.get(), (::mega::byte*)tf.ultoken.get() + mega::UPLOADTOKENLEN, 'Z'); tf.tempurls = { "http://bar1.com", "http://bar2.com", "http://bar3.com", "http://bar4.com", "http://bar5.com", "http://bar6.com", }; tf.state = mega::TRANSFERSTATE_PAUSED; tf.priority = 4; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; checkTransfers(tf, *newTf); } // Test that URLs with different lengths are correctly parsed (e.g., sandbox3 RAID) TEST(Transfer, serialize_unserialize_raid_urls_different_lengths) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "test_file", 'A', 10, 20, 'B', 30); // Test with URLs of different lengths (simulating sandbox3 or different storage servers) tf.tempurls = { "http://gfs270n406.userstorage.mega.co.nz/dl/short", "http://gfs262n309.userstorage.mega.co.nz/dl/verylongtoken12345678901234567890", "http://gfs214n115.userstorage.mega.co.nz/dl/mediumtoken12345", "http://gfs204n127.userstorage.mega.co.nz/dl/" "extremelylongtokenabcdefghijklmnopqrstuvwxyz1234567890", "http://gfs208n116.userstorage.mega.co.nz/dl/normaltoken", "http://gfs206n167.userstorage.mega.co.nz/dl/anothermediumtoken67890", }; ASSERT_EQ(tf.tempurls.size(), mega::RAIDPARTS); tf.state = mega::TRANSFERSTATE_NONE; tf.priority = 100; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } // Test single URL (non-RAID download) TEST(Transfer, serialize_unserialize_single_url) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "single_file", 'C', 5, 10, 'D', 15); tf.tempurls = { "http://gfs123n456.userstorage.mega.co.nz/dl/" "verylongsingletokenabcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz"}; ASSERT_EQ(tf.tempurls.size(), 1u); tf.state = mega::TRANSFERSTATE_NONE; tf.priority = 50; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } // Test empty URLs (transfer before URLs are fetched) TEST(Transfer, serialize_unserialize_empty_urls) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "pending_file", 'E', 7, 14, 'F', 21); tf.tempurls = {}; // Empty URLs tf.state = mega::TRANSFERSTATE_NONE; tf.priority = 25; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } // Test with very long URLs (edge case for buffer handling) TEST(Transfer, serialize_unserialize_very_long_urls) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "large_file", 'G', 8, 16, 'H', 24); std::string longToken(200, 'x'); std::string mediumToken(150, 'y'); std::string shortToken(100, 'z'); tf.tempurls = { "http://gfs270n406.userstorage.mega.co.nz/dl/" + longToken, "http://gfs262n309.userstorage.mega.co.nz/dl/" + mediumToken, "http://gfs214n115.userstorage.mega.co.nz/dl/" + shortToken, "http://gfs204n127.userstorage.mega.co.nz/dl/" + longToken + "extra", "http://gfs208n116.userstorage.mega.co.nz/dl/" + mediumToken + "more", "http://gfs206n167.userstorage.mega.co.nz/dl/" + shortToken + "data", }; ASSERT_EQ(tf.tempurls.size(), mega::RAIDPARTS); tf.state = mega::TRANSFERSTATE_PAUSED; tf.priority = 200; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } // Test PUT transfer (upload) with single URL TEST(Transfer, serialize_unserialize_put_single_url) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::PUT}; setupTransfer(tf, "upload_file", 'I', 9, 18, 'J', 27); tf.tempurls = {"http://gfs999n999.userstorage.mega.co.nz/ul/" "uploadtoken1234567890abcdefghijklmnopqrstuvwxyz"}; ASSERT_EQ(tf.tempurls.size(), 1u); tf.state = mega::TRANSFERSTATE_NONE; tf.priority = 75; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } // Test edge case: first URL is shortest, last URL is longest TEST(Transfer, serialize_unserialize_extreme_length_variation) { mega::MegaApp app; auto client = mt::makeClient(app); mega::Transfer tf{client.get(), mega::GET}; setupTransfer(tf, "extreme_file", 'K', 11, 22, 'L', 33); tf.tempurls = { "http://a.co/x", "http://gfs262n309.userstorage.mega.co.nz/dl/medium12345", "http://gfs214n115.userstorage.mega.co.nz/dl/anothermedium67890", "http://gfs204n127.userstorage.mega.co.nz/dl/longertokenabcdefghijklmnopqrstuvwxyz", "http://gfs208n116.userstorage.mega.co.nz/dl/verylongtoken123456789012345678901234567890", "http://gfs206n167.userstorage.mega.co.nz/dl/" "extremelylongtokenabcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", }; ASSERT_EQ(tf.tempurls.size(), mega::RAIDPARTS); tf.state = mega::TRANSFERSTATE_NONE; tf.priority = 300; std::string d; ASSERT_TRUE(tf.serialize(&d)); mega::transfer_multimap tfMap[2]; auto newTf = std::unique_ptr<mega::Transfer>{mega::Transfer::unserialize(client.get(), &d, tfMap)}; ASSERT_NE(newTf, nullptr); checkTransfers(tf, *newTf); } �����������������������������������sdk-10.11.0/tests/unit/Transferstats_test.cpp�������������������������������������������������������0000664�0000000�0000000�00000016333�15162662266�0021204�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2024 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/transferstats.h" #include <gtest/gtest.h> using namespace mega::stats; /**************\ * TEST MEDIAN * \**************/ /** * @brief Tests calculateMedian function with an empty vector. */ TEST(TransferStatsTest, TestCalculateMedianEmptyVector) { const std::vector<m_off_t> values = {}; ASSERT_EQ(calculateMedian(values), 0); } /** * @brief Tests calculateMedian function with one element. */ TEST(TransferStatsTest, TestCalculateMedianOneElement) { const std::vector<m_off_t> values = {42}; ASSERT_EQ(calculateMedian(values), 42); } /** * @brief Tests calculateMedian function with two elements (even size). * * Ex: (10 + 20) / 2 = 15 */ TEST(TransferStatsTest, TestCalculateMedianEvenNumberOfElements) { const std::vector<m_off_t> values = {10, 20}; ASSERT_EQ(calculateMedian(values), 15); } /** * @brief Tests calculateMedian function with two elements requiring rounding. * * Ex: (10 + 21) / 2 = 15.5 -> rounds to 16 */ TEST(TransferStatsTest, TestCalculateMedianEvenNumberWithRounding) { const std::vector<m_off_t> values = {10, 21}; ASSERT_EQ(calculateMedian(values), 16); } /** * @brief Tests calculateMedian function with an odd number of elements. */ TEST(TransferStatsTest, TestCalculateMedianOddNumberOfElements) { const std::vector<m_off_t> values = {5, 10, 15}; ASSERT_EQ(calculateMedian(values), 10); } /** * @brief Tests calculateMedian function with negative numbers. */ TEST(TransferStatsTest, TestCalculateMedianNegativeNumbers) { const std::vector<m_off_t> values = {-10, -5, 0, 5, 10}; ASSERT_EQ(calculateMedian(values), 0); } /** * @brief Tests calculateMedian function with a larger vector. * * Ex: (5 + 7) / 2 = 6 */ TEST(TransferStatsTest, TestCalculateMedianLargerVector) { const std::vector<m_off_t> values = {1, 3, 5, 7, 9, 11}; ASSERT_EQ(calculateMedian(values), 6); } /** * @brief Tests calculateMedian function with large numbers. */ TEST(TransferStatsTest, TestCalculateMedianLargeNumbers) { const std::vector<m_off_t> values = {1000000000, 2000000000, 3000000000}; ASSERT_EQ(calculateMedian(values), 2000000000); } /** * @brief Tests calculateMedian function with large numbers and an even-sized vector, checking for * rounding. * * Ex: (1000000000 + 2000000001) / 2 = 1500000000.5 -> * rounds to 1500000001. */ TEST(TransferStatsTest, TestCalculateMedianLargeNumbersEvenSizeWithRounding) { const std::vector<m_off_t> values = {1000000000, 2000000001}; ASSERT_EQ(calculateMedian(values), 1500000001); } /************************\ * TEST WEIGHTED AVERAGE * \************************/ /** * @brief Tests calculateWeightedAverage function with empty vectors. */ TEST(TransferStatsTest, TestCalculateWeightedAverageEmptyVectors) { const std::vector<m_off_t> values = {}; const std::vector<m_off_t> weights = {}; ASSERT_EQ(calculateWeightedAverage(values, weights), 0); } /** * @brief Tests calculateWeightedAverage function with one element. */ TEST(TransferStatsTest, TestCalculateWeightedAverageOneElement) { const std::vector<m_off_t> values = {50}; const std::vector<m_off_t> weights = {2}; ASSERT_EQ(calculateWeightedAverage(values, weights), 50); } /** * @brief Tests calculateWeightedAverage function with zero weights. */ TEST(TransferStatsTest, TestCalculateWeightedAverageZeroWeights) { const std::vector<m_off_t> values = {10, 20, 30}; const std::vector<m_off_t> weights = {0, 0, 0}; ASSERT_EQ(calculateWeightedAverage(values, weights), 0); } /** * @brief Tests calculateWeightedAverage function with normal (equal) weights. */ TEST(TransferStatsTest, TestCalculateWeightedAverageNormalWeights) { const std::vector<m_off_t> values = {10, 20, 30}; const std::vector<m_off_t> weights = {1, 1, 1}; ASSERT_EQ(calculateWeightedAverage(values, weights), 20); } /** * @brief Tests calculateWeightedAverage function with varied weights. * * Ex: Weighted average = (10*1 + 20*2 + 30*3) / (1 + 2 + 3) * = (10 + 40 + 90) / 6 = 140 / 6 = 23.3333 -> rounds to 23. */ TEST(TransferStatsTest, TestCalculateWeightedAverageVariedWeights) { const std::vector<m_off_t> values = {10, 20, 30}; const std::vector<m_off_t> weights = {1, 2, 3}; ASSERT_EQ(calculateWeightedAverage(values, weights), 23); } /** * @brief Tests calculateWeightedAverage function with negative values. * * Ex: Weighted average = (-10*1 + -20*2 + -30*3) / (1+2+3) = (-10 -40 -90)/6 = * -140/6 ≈ -23.3333 -> rounds to -23. */ TEST(TransferStatsTest, TestCalculateWeightedAverageNegativeValues) { const std::vector<m_off_t> values = {-10, -20, -30}; const std::vector<m_off_t> weights = {1, 2, 3}; ASSERT_EQ(calculateWeightedAverage(values, weights), -23); } /** * @brief Tests calculateWeightedAverage function when weights sum to zero. * * Ex: Total weight = 1 -1 + 0 = 0, should return 0. */ TEST(TransferStatsTest, TestCalculateWeightedAverageWeightsSummingToZero) { const std::vector<m_off_t> values = {10, 20, 30}; const std::vector<m_off_t> weights = {1, -1, 0}; ASSERT_EQ(calculateWeightedAverage(values, weights), 0); } /** * @brief Tests calculateWeightedAverage function when the weighted sum is zero. * * Ex: Weighted sum = 10*1 + (-10)*1 = 0, total weight = 1 + 1 = 2 */ TEST(TransferStatsTest, TestCalculateWeightedAverageWeightedSumIsZero) { const std::vector<m_off_t> values = {10, -10}; const std::vector<m_off_t> weights = {1, 1}; ASSERT_EQ(calculateWeightedAverage(values, weights), 0); } /** * @brief Tests calculateWeightedAverage function with large numbers. * * Ex: Weighted average = (1000000000*1 + 2000000000*3) / 4 = * (1000000000 + 6000000000)/4 = 7000000000/4 = 1750000000. */ TEST(TransferStatsTest, TestCalculateWeightedAverageLargeNumbers) { const std::vector<m_off_t> values = {1000000000, 2000000000}; const std::vector<m_off_t> weights = {1, 3}; ASSERT_EQ(calculateWeightedAverage(values, weights), 1750000000); } /** * @brief Tests calculateWeightedAverage function when the result rounds up. * * Ex: Weighted average = (1 + 2) / 2 = 1.5 -> rounds to 2. */ TEST(TransferStatsTest, TestCalculateWeightedAverageRoundingUp) { const std::vector<m_off_t> values = {1, 2}; const std::vector<m_off_t> weights = {1, 1}; ASSERT_EQ(calculateWeightedAverage(values, weights), 2); } /** * @brief Tests calculateWeightedAverage function when the result rounds down. * * Ex: Weighted average = (1*2 + 2*1)/3 = (2 + 2)/3 = * 1.3333 -> rounds to 1. */ TEST(TransferStatsTest, TestCalculateWeightedAverageRoundingDown) { const std::vector<m_off_t> values = {1, 2}; const std::vector<m_off_t> weights = {2, 1}; ASSERT_EQ(calculateWeightedAverage(values, weights), 1); }�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/User_test.cpp����������������������������������������������������������������0000664�0000000�0000000�00000011272�15162662266�0017254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "DefaultedFileSystemAccess.h" #include "mega.h" #include "utils.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/megaapp.h> #include <mega/megaclient.h> #include <mega/user.h> #include <mega/user_attribute.h> namespace { //class MockFileSystemAccess : public mt::DefaultedFileSystemAccess //{ //}; void checkUsers(const mega::User& exp, const mega::User& act) { ASSERT_EQ(exp.userhandle, act.userhandle); ASSERT_EQ(exp.email, act.email); ASSERT_EQ(exp.show, act.show); ASSERT_EQ(exp.ctime, act.ctime); std::string expKey; exp.pubk.serializekey(&expKey, mega::AsymmCipher::PUBKEY); ASSERT_FALSE(expKey.empty()); std::string actKey; act.pubk.serializekey(&actKey, mega::AsymmCipher::PUBKEY); ASSERT_FALSE(actKey.empty()); ASSERT_EQ(expKey, actKey); const mega::UserAttribute* attribute1 = exp.getAttribute(mega::ATTR_FIRSTNAME); const mega::UserAttribute* attribute2 = act.getAttribute(mega::ATTR_FIRSTNAME); ASSERT_THAT(attribute1, testing::NotNull()); ASSERT_THAT(attribute2, testing::NotNull()); ASSERT_EQ(attribute1->value(), attribute2->value()); ASSERT_EQ(attribute1->version(), attribute2->version()); attribute1 = exp.getAttribute(mega::ATTR_LASTNAME); attribute2 = act.getAttribute(mega::ATTR_LASTNAME); ASSERT_THAT(attribute1, testing::NotNull()); ASSERT_THAT(attribute2, testing::NotNull()); ASSERT_EQ(attribute1->value(), attribute2->value()); } } TEST(User, serialize_unserialize) { mega::MegaApp app; auto client = mt::makeClient(app); const std::string email = "foo@bar.com"; mega::User user{email.c_str()}; user.userhandle = 13; user.ctime = 14; user.show = mega::VISIBLE; std::string firstname1 = "f"; std::string firstname2 = "f2"; std::string lastname = "oo"; user.setAttribute(mega::ATTR_FIRSTNAME, firstname1, firstname2); user.setAttribute(mega::ATTR_LASTNAME, lastname, {}); std::string key(128, 1); user.pubk.setkey(mega::AsymmCipher::PUBKEY, reinterpret_cast<const mega::byte*>(key.c_str()), static_cast<int>(key.size())); ASSERT_TRUE(user.pubk.isvalid(mega::AsymmCipher::PUBKEY)); std::string d; ASSERT_TRUE(user.serialize(&d)); auto newUser = mega::User::unserialize(client.get(), &d); checkUsers(user, *newUser); } TEST(User, unserialize_32bit) { mega::MegaApp app; auto client = mt::makeClient(app); const std::string email = "foo@bar.com"; mega::User user{email.c_str()}; user.userhandle = 13; user.ctime = 14; user.show = mega::VISIBLE; std::string firstname1 = "f"; std::string firstname2 = "f2"; std::string lastname = "oo"; user.setAttribute(mega::ATTR_FIRSTNAME, firstname1, firstname2); user.setAttribute(mega::ATTR_LASTNAME, lastname, {}); std::string key(128, 1); user.pubk.setkey(mega::AsymmCipher::PUBKEY, reinterpret_cast<const mega::byte*>(key.c_str()), static_cast<int>(key.size())); ASSERT_TRUE(user.pubk.isvalid(mega::AsymmCipher::PUBKEY)); // This is the result of serialization on 32bit Windows const std::array<char, 133> rawData = { 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x66, 0x6f, 0x6f, 0x40, 0x62, 0x61, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x66, 0x02, 0x00, 0x66, 0x32, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6f, 0x6f, 0x01, 0x00, 0x4e, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; std::string d(rawData.data(), rawData.size()); auto newUser = mega::User::unserialize(client.get(), &d); checkUsers(user, *newUser); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/canceller_test.cpp�����������������������������������������������������������0000664�0000000�0000000�00000001677�15162662266�0020276�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file canceller_test.cpp * @brief This file is expected to contain unit tests involving ScopedCanceller logic. */ #include <gtest/gtest.h> #include <mega/canceller.h> using ::mega::cancel_epoch_bump; using ::mega::ScopedCanceller; TEST(Canceller, SnapshotNotTriggeredUntilBumped) { ScopedCanceller s1; EXPECT_FALSE(s1.triggered()); // No bump -> still false EXPECT_FALSE(s1.triggered()) << "The same ScopedCanceller should remain untriggered without a previous cancel"; } TEST(Canceller, TriggeredAfterBump) { ScopedCanceller s1; cancel_epoch_bump(); EXPECT_TRUE(s1.triggered()); ScopedCanceller s2; EXPECT_FALSE(s2.triggered()) << "A new snapshot should see the new epoch and not be triggered yet"; } TEST(Canceller, MultipleBumpsStillTriggerOldSnapshots) { ScopedCanceller s1; cancel_epoch_bump(); cancel_epoch_bump(); cancel_epoch_bump(); EXPECT_TRUE(s1.triggered()); }�����������������������������������������������������������������sdk-10.11.0/tests/unit/constants.h������������������������������������������������������������������0000664�0000000�0000000�00000001271�15162662266�0016756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <string> namespace mt { const std::string gLocalDebris = ".debris"; } // mt ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/cxx20_features_test.cpp������������������������������������������������������0000664�0000000�0000000�00000001365�15162662266�0021202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gtest/gtest.h> #include <span> #include <string> #include <vector> template<typename T> concept Addable = requires(T a, T b) { a + b; }; static_assert(Addable<int>); static_assert(Addable<std::string>); // Test with a type that doesn't have operator+ struct NonAddable { int value; }; static_assert(!Addable<NonAddable>); TEST(Cxx20Features, ConceptsWorks) { // Test that Addable concept works with numeric types auto add = [](Addable auto a, Addable auto b) { return a + b; }; EXPECT_EQ(add(5, 3), 8); EXPECT_EQ(add(2.5, 1.5), 4.0); EXPECT_EQ(add(10u, 20u), 30u); } TEST(Cxx20Features, SpanWorks) { std::vector<int> v{1, 2, 3}; std::span<int> s{v}; EXPECT_EQ(s.size(), v.size()); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_access_tests.cpp��������������������������������������������������������0000664�0000000�0000000�00000006564�15162662266�0020771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gtest/gtest.h> #include <megafs.h> namespace mega { namespace testing { struct FileAccessTests: ::testing::Test { FileAccessTests(): Test(), mFilesystem(), mFilePath(LocalPath::fromAbsolutePath("file")), mFileAccess(mFilesystem.newfileaccess(false)) {} // Called before each test in our fixture executes. void SetUp() override { // Make sure we have no state from a prior test run. auto unlinked = mFilesystem.unlinklocal(mFilePath); // Unlinking was successful or the file didn't exist. ASSERT_TRUE(unlinked || !mFilesystem.target_exists); // Convenience. auto logOnError = FSLogging::logOnError; // Make sure our test file is open for IO. ASSERT_TRUE(mFileAccess->fopen(mFilePath, OPEN_RDWR, logOnError)); } // How we interface with the local filesystem. FSACCESS_CLASS mFilesystem; // The path to our test file. LocalPath mFilePath; // How we read and write our test file. FileAccessPtr mFileAccess; }; // FileAccessTests TEST_F(FileAccessTests, frawread_fwrite) { // Data for us to write to disk. static const std::string expected = "AAAABBBBCCCCDDDD"; // Write data in reverse order, in groups of four characters. for (std::size_t i = 0, j = expected.size(); i != j; i += 4) { // Compute offset. auto offset = static_cast<m_off_t>(j - i - 4); // Tracks how much data we wrote to disk. auto count = 0ul; // Try and write four characters to disk. ASSERT_TRUE(mFileAccess->fwrite(expected.data() + offset, 4, offset, &count)); // Make sure we actually wrote four characters to disk. ASSERT_EQ(count, 4ul); } // Where we'll store the data we've read. std::string computed(4, '\0'); // Read data in order, in groups of four characters. for (std::size_t i = 0ul, j = expected.size() - 4; i != j; i += 4) { // Convenience. auto logOnError = FSLogging::logOnError; // Convenience. auto offset = static_cast<m_off_t>(i); // Try and read four characters from disk. ASSERT_TRUE(mFileAccess->frawread(computed.data(), 4, offset, true, logOnError)); // Make sure we've read what we expected. ASSERT_FALSE(expected.compare(i, 4, computed)); } // Convenience. auto noLogging = FSLogging::noLogging; // Make sure frawread(...) fails if it can't read everything. ASSERT_FALSE(mFileAccess->frawread(computed.data(), 4, 14, true, noLogging)); } TEST_F(FileAccessTests, fread) { // Data we want to write to disk. static const std::string expected = "ABCD"; // Convenience. auto logOnError = FSLogging::logOnError; // Try and write the data to disk. ASSERT_TRUE(mFileAccess->fwrite(expected.data(), 4, 0)); // Where we'll store the data we've read from disk. std::string computed; // Read without padding. ASSERT_TRUE(mFileAccess->fread(&computed, 4, 0, 0, logOnError)); // Make sure we've read what we expected. EXPECT_EQ(computed, expected); // Clear the string. computed = std::string(6, '!'); // Read with padding. ASSERT_TRUE(mFileAccess->fread(&computed, 2, 6, 2, logOnError)); // Make sure the string was correctly padded. EXPECT_EQ(computed, std::string("CD\0\0\0\0\0\0", 8)); } } // testing } // mega ��������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/����������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017227�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/avl_tree_tests.cpp����������������������������������������������0000664�0000000�0000000�00000033434�15162662266�0022765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/file_service/avl_tree.h> #include <mega/file_service/testing/unit/avl_node.h> #include <deque> #include <vector> namespace mega { namespace file_service { template<typename Traits> using DetectValidate = typename Traits::Validate; template<typename Traits> constexpr auto HasValidateV = std::is_invocable_r_v<bool, DetectedT<DetectValidate, Traits>, const Node&>; template<typename Traits, typename KeyType = typename AVLTree<Traits>::KeyType> static std::vector<KeyType> breadth(const AVLTree<Traits>& tree); template<typename Traits, typename NodeType = typename AVLTree<Traits>::NodeType> static AVLTree<Traits> treeFrom(std::vector<NodeType>& nodes); template<typename Traits> static bool validate(const AVLTree<Traits>& tree); template<typename Traits> static bool validate(typename AVLTree<Traits>::ConstIterator node, typename AVLTree<Traits>::ConstIterator parent); TEST(AVLTree, add) { using ::testing::ElementsAre; // Basic addition tests. { Node n00{0}; Node n01{0}; AVLTree<Traits> tree; // Trees are always initially empty. ASSERT_TRUE(tree.empty()); // We can add a node to the tree. auto [iterator, added] = tree.add(n00); // The iterator references the node we added. ASSERT_TRUE(added); ASSERT_NE(iterator, tree.end()); EXPECT_EQ(&*iterator, &n00); // Make sure the tree's valid. ASSERT_TRUE(validate(tree)); // When we add a node with a duplicate key, we get an iterator // referencing the node in the tree with that key. std::tie(iterator, added) = tree.add(n01); EXPECT_FALSE(added); EXPECT_EQ(&*iterator, &n00); // Make sure the tree remains valid. ASSERT_TRUE(validate(tree)); } // Add the specified nodes to a tree, validating after each addition. auto addAndValidate = [](AVLTree<Traits>& tree, std::vector<Node>& nodes) { // Add each node to the tree. for (auto& node: nodes) { // Add the node to the tree. auto [iterator, added] = tree.add(node); // Make sure the node was added. ASSERT_TRUE(added); // Make sure the iterator's valid. ASSERT_NE(iterator, tree.end()); // And references the node we just added. ASSERT_EQ(&*iterator, &node); // And that adding the node didn't invalidate the tree. ASSERT_TRUE(validate(tree)); // How many nodes have we added? auto count = &node - &nodes[0] + 1; // Make sure the tree contains the right number of nodes. ASSERT_EQ(tree.size(), count); } }; // addAndValidate // Add with left-left rebalance. { std::vector<Node> nodes = {2, 1, 0}; AVLTree<Traits> tree; ASSERT_NO_FATAL_FAILURE(addAndValidate(tree, nodes)); ASSERT_THAT(breadth(tree), ElementsAre(1, 0, 2)); } // Add with left-right rebalance. { std::vector<Node> nodes = {2, 0, 1}; AVLTree<Traits> tree; ASSERT_NO_FATAL_FAILURE(addAndValidate(tree, nodes)); ASSERT_THAT(breadth(tree), ElementsAre(1, 0, 2)); } // Add with right-left rebalance. { std::vector<Node> nodes = {0, 2, 1}; AVLTree<Traits> tree; ASSERT_NO_FATAL_FAILURE(addAndValidate(tree, nodes)); ASSERT_THAT(breadth(tree), ElementsAre(1, 0, 2)); } // Add with right-right rebalance. { std::vector<Node> nodes = {0, 1, 2}; AVLTree<Traits> tree; ASSERT_NO_FATAL_FAILURE(addAndValidate(tree, nodes)); ASSERT_THAT(breadth(tree), ElementsAre(1, 0, 2)); } } TEST(AVLTree, equality) { // Can't compare trees that whose nodes contain incomparable values. static_assert(!IsEqualityComparableV<AVLTree<UncomparableTraits>>); AVLTree<Traits> tree0; AVLTree<Traits> tree1; // Empty trees are always equal. EXPECT_EQ(tree0, tree1); // Create some nodes for us to play with. std::vector<Node> nodes0 = {0, 1}; std::vector<Node> nodes1 = nodes0; tree0.add(nodes0[0]); // tree0 contains more nodes than tree1. EXPECT_NE(tree0, tree1); // tree0 contains different values than tree1. tree1.add(nodes1[1]); EXPECT_NE(tree0, tree1); // Both trees are identical. tree0.add(nodes0[1]); tree1.add(nodes1[0]); EXPECT_EQ(tree0, tree1); } TEST(AVLTree, find) { std::vector<Node> nodes = {0, 1, 2, 3, 4, 5, 6, 7}; AVLTree<Traits> tree; // Add a bunch of nodes to the tree. for (auto& node: nodes) tree.add(node); // Make sure we can find them all. for (auto& node: nodes) { // Try and find this node in the tree. auto iterator = tree.find(node.mKey); // Make sure the iterator references the node we expect. ASSERT_NE(iterator, tree.end()); EXPECT_EQ(&*iterator, &node); } } TEST(AVLTree, iteration) { std::vector<Node> nodes = {0, 1, 2, 3, 4, 5, 6, 7}; auto tree = treeFrom<Traits>(nodes); // Make sure we can traverse the tree in order. auto i = tree.begin(); auto m = nodes.begin(); for (; m != nodes.end(); ++i, ++m) { ASSERT_NE(i, tree.end()); ASSERT_EQ(&*i, &*m); } ASSERT_EQ(i, tree.end()); // Make sure we can traverse the tree in reverse order. auto j = tree.rbegin(); auto n = nodes.rbegin(); for (; n != nodes.rend(); ++j, ++n) { ASSERT_TRUE(j); ASSERT_EQ(&*j, &*n); } ASSERT_EQ(j, tree.rend()); } TEST(AVLTree, lower_bound) { std::vector<Node> nodes = {-1, 2, 4}; auto tree = treeFrom<Traits>(nodes); auto iterator = tree.lower_bound(0); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 2); iterator = tree.lower_bound(3); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 4); iterator = tree.lower_bound(1); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 2); iterator = tree.lower_bound(4); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 4); iterator = tree.lower_bound(5); ASSERT_EQ(iterator, tree.end()); } TEST(AVLTree, metadata) { std::vector<Node> nodes = {0, 1, 2, 3, 4, 5, 6}; AVLTree<TraitsWithMetadata> tree; // Add each node, checking that tree metadata is correct. for (auto& node: nodes) { tree.add(node); ASSERT_TRUE(validate(tree)); } } TEST(AVLTree, remove) { using testing::ElementsAre; // Remove leaf nodes. { std::vector<Node> nodes = {1, 0, 2}; auto tree = treeFrom<Traits>(nodes); // Remove by key. auto* node = tree.remove(0); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 0); // Validate the tree. ASSERT_EQ(tree.size(), 2u); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(1, 2)); // Remove by iterator. auto iterator = tree.find(2); ASSERT_NE(iterator, tree.end()); node = tree.remove(iterator); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 2); ASSERT_EQ(tree.size(), 1u); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(1)); // Remove root. node = tree.remove(1); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 1); // Validate tree. EXPECT_TRUE(tree.empty()); EXPECT_EQ(tree.size(), 0u); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre()); } // Remove branch nodes. { std::vector<Node> nodes = {3, 1, 5, 2, 4}; auto tree = treeFrom<Traits>(nodes); // Remove right-leaning branch. auto* node = tree.remove(1); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 1); ASSERT_TRUE(validate(tree)); EXPECT_THAT(breadth(tree), ElementsAre(3, 2, 5, 4)); // Remove left-leaning branch. node = tree.remove(5); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 5); ASSERT_TRUE(validate(tree)); EXPECT_THAT(breadth(tree), ElementsAre(3, 2, 4)); } // Remove subtree nodes. { std::vector<Node> nodes = {5, 2, 8, 1, 4, 6, 9, 3, 7}; auto tree = treeFrom<Traits>(nodes); // Remove root (replacement has child left.) auto* node = tree.remove(5); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 5); auto iterator = tree.root(); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 4); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(4, 2, 8, 1, 3, 6, 9, 7)); // Remove 8 (replacement has no child.) node = tree.remove(8); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 8); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(4, 2, 7, 1, 3, 6, 9)); // Remove root (replacement has no child.); node = tree.remove(4); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 4); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(3, 2, 7, 1, 6, 9)); } // Left-left rebalance. { std::vector<Node> nodes = {1, 2, 3, 4}; auto tree = treeFrom<Traits>(nodes); auto* node = tree.remove(4); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 4); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(2, 1, 3)); } // Left-right rebalance. { std::vector<Node> nodes = {3, 1, 4, 2}; auto tree = treeFrom<Traits>(nodes); auto* node = tree.remove(4); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 4); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(2, 1, 3)); } // Right-left rebalance. { std::vector<Node> nodes = {2, 1, 4, 3}; auto tree = treeFrom<Traits>(nodes); auto* node = tree.remove(1); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 1); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(3, 2, 4)); } // Right-right rebalance. { std::vector<Node> nodes = {2, 1, 3, 4}; auto tree = treeFrom<Traits>(nodes); auto* node = tree.remove(1); ASSERT_NE(node, nullptr); EXPECT_EQ(node->mKey, 1); ASSERT_TRUE(validate(tree)); ASSERT_THAT(breadth(tree), ElementsAre(3, 2, 4)); } } TEST(AVLTree, upper_bound) { std::vector<Node> nodes = {-1, 2, 4}; auto tree = treeFrom<Traits>(nodes); auto iterator = tree.upper_bound(-2); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, -1); iterator = tree.upper_bound(-1); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 2); iterator = tree.upper_bound(2); ASSERT_NE(iterator, tree.end()); ASSERT_EQ(iterator->mKey, 4); iterator = tree.upper_bound(4); ASSERT_EQ(iterator, tree.end()); iterator = tree.lower_bound(5); ASSERT_EQ(iterator, tree.end()); } template<typename Traits, typename KeyType> static std::vector<KeyType> breadth(const AVLTree<Traits>& tree) { // No nodes to order if the tree's empty. if (tree.empty()) return {}; // Convenience. using Iterator = decltype(tree.end()); // The keys we've seen in breadth-first order. std::vector<KeyType> keys; // Nodes remaining to be processed. std::deque<Iterator> pending(1, tree.root()); // Reserve space for our node references. keys.reserve(tree.size()); // Traverse the tree. while (!pending.empty()) { // Pop the first iterator from the queue. auto iterator = pending.front(); pending.pop_front(); // Keep track of which key this iterator references. keys.emplace_back(iterator->mKey); // Push this node's children onto the queue. if (auto left = iterator.left()) pending.emplace_back(left); if (auto right = iterator.right()) pending.emplace_back(right); } // Return ordered keys to caller. return keys; } template<typename Traits, typename NodeType> AVLTree<Traits> treeFrom(std::vector<NodeType>& nodes) { AVLTree<Traits> tree; for (auto& node: nodes) tree.add(node); return tree; } template<typename Traits> bool validate(const AVLTree<Traits>& tree) { return validate<Traits>(tree.root(), tree.end()); } template<typename Traits> bool validate(typename AVLTree<Traits>::ConstIterator node, typename AVLTree<Traits>::ConstIterator parent) { using LinkTraits = detail::LinkTraits<Traits>; // No node? Can't be invalid. if (!node) return true; // A node's parent must be who linked to us. if (node.parent() != parent) return false; // A node's balance must be between [-1, +1]. if (std::abs(LinkTraits::balance(*node)) > 1) return false; // Validate left subtree. if (auto left = node.left()) { // Our left subtree must have a key less than ours. if (left->mKey >= node->mKey) return false; // Bail if our left subtree isn't valid. if (!validate<Traits>(left, node)) return false; } // Validate our metadata. if constexpr (HasValidateV<Traits>) { if (!typename Traits::Validate()(node)) return false; } auto right = node.right(); // No right subtree so we're valid. if (!right) return true; // Right subtree must have a key greater than ours. if (right->mKey <= node->mKey) return false; // Validate our right subtree. return validate<Traits>(right, node); } } // file_service } // mega ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/avl_tree_trait_tests.cpp����������������������������������������0000664�0000000�0000000�00000006510�15162662266�0024163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gtest/gtest.h> #include <mega/file_service/avl_tree_iterator.h> #include <mega/file_service/avl_tree_traits.h> #include <mega/file_service/testing/unit/avl_node.h> #include <functional> namespace mega { namespace file_service { namespace detail { static_assert(ValueIsEqualityComparableV<Traits>); static_assert(!ValueIsEqualityComparableV<UncomparableTraits>); } // detail TEST(AVLTreeKeyTraits, compare) { using KT = detail::KeyTraits<Traits>; Node n0{0}; Node n1{1}; EXPECT_EQ(KT::compare(n0.mKey, n0.mKey), 0); EXPECT_GT(KT::compare(n1.mKey, n0.mKey), 0); EXPECT_LT(KT::compare(n0.mKey, n1.mKey), 0); } TEST(AVLTreeKeyTraits, custom_compare) { struct TraitsWithCustomCompare: Traits { using Compare = std::greater<int>; }; // TraitsWithCustomCompare using KT = detail::KeyTraits<TraitsWithCustomCompare>; Node n0{0}; Node n1{1}; EXPECT_EQ(KT::compare(n0.mKey, n0.mKey), 0); EXPECT_GT(KT::compare(n0.mKey, n1.mKey), 0); EXPECT_LT(KT::compare(n1.mKey, n0.mKey), 0); } TEST(AVLTreeKeyTraits, key) { using KT = detail::KeyTraits<Traits>; Node n0{0}; Node n1{1}; EXPECT_EQ(KT::key(n0), 0); EXPECT_EQ(KT::key(n1), 1); } TEST(AVLTreeLinkTraits, child) { using LT = detail::LinkTraits<Traits>; Node n0{0}; Node n1{1}; Node n2{2}; n1.mLink.mChildren[0] = &n0; n1.mLink.mChildren[1] = &n2; EXPECT_EQ(LT::child(n1, 0), &n0); EXPECT_EQ(LT::child(n1, 1), &n2); LT::child(n1, 0) = nullptr; EXPECT_EQ(n1.mLink.mChildren[0], nullptr); } TEST(AVLTreeLinkTraits, height) { using LT = detail::LinkTraits<Traits>; Node n0{0}; n0.mLink.mHeight = 1; EXPECT_EQ(LT::height(n0), 1); LT::height(n0) = 0; EXPECT_EQ(n0.mLink.mHeight, 0); } TEST(AVLTreeLinkTraits, left) { using LT = detail::LinkTraits<Traits>; Node n0{0}; Node n1{1}; n1.mLink.mChildren[0] = &n0; EXPECT_EQ(LT::left(n1), &n0); LT::left(n1) = nullptr; EXPECT_EQ(n1.mLink.mChildren[0], nullptr); } TEST(AVLTreeLinkTraits, link) { using LT = detail::LinkTraits<Traits>; Node n{0}; EXPECT_EQ(<::link(n), &n.mLink); } TEST(AVLTreeLinkTraits, parent) { using LT = detail::LinkTraits<Traits>; Node n0{0}; Node n1{1}; n1.mLink.mParent = &n0; EXPECT_EQ(LT::parent(n1), &n0); LT::parent(n1) = nullptr; EXPECT_EQ(n1.mLink.mParent, nullptr); } TEST(AVLTreeLinkTraits, right) { using LT = detail::LinkTraits<Traits>; Node n0{0}; Node n1{1}; n0.mLink.mChildren[1] = &n1; EXPECT_EQ(LT::right(n0), &n1); LT::right(n0) = nullptr; EXPECT_EQ(n0.mLink.mChildren[1], nullptr); } TEST(AVLTreeMetadataTraits, update) { using LT = detail::LinkTraits<Traits>; using IteratorType = AVLTreeIterator<Node, LT, true, false>; // No metadata. { using MT = detail::MetadataTraits<Traits>; Node n0{0}; MT::update<IteratorType>(n0); } // With metadata. using MT = detail::MetadataTraits<TraitsWithMetadata>; Node n0{0}; Node n1{1}; Node n2{2}; LT::left(n1) = &n0; LT::right(n1) = &n2; MT::update<IteratorType>(n0); EXPECT_EQ(n0.mSize, 1); MT::update<IteratorType>(n2); EXPECT_EQ(n2.mSize, 1); MT::update<IteratorType>(n1); EXPECT_EQ(n1.mSize, 3); } } // file_service } // mega ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/file_range_trait_tests.cpp��������������������������������������0000664�0000000�0000000�00000001073�15162662266�0024454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_range.h> #include <mega/file_service/file_range_traits.h> namespace mega { namespace file_service { static_assert(IsFileRangeV<const FileRange>); static_assert(IsFileRangeV<const volatile FileRange>); static_assert(IsFileRangeV<volatile FileRange>); static_assert(IsFileRangeV<FileRange>); static_assert(!IsFileRangeV<const FileRange*>); static_assert(!IsFileRangeV<const FileRange&>); static_assert(!IsFileRangeV<FileRange*>); static_assert(!IsFileRangeV<FileRange&>); static_assert(!IsFileRangeV<int>); } // file_service } // mega ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/file_range_tree_tests.cpp���������������������������������������0000664�0000000�0000000�00000023721�15162662266�0024274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gtest/gtest.h> #include <mega/file_service/file_range_map.h> #include <mega/file_service/file_range_set.h> #include <mega/file_service/testing/unit/avl_utilities.h> #include <cstdlib> #include <functional> #include <type_traits> namespace mega { namespace file_service { struct NoCopyMove { NoCopyMove(int value): mValue(value) {} NoCopyMove(const NoCopyMove& other) = delete; NoCopyMove& operator=(const NoCopyMove& rhs) = delete; const int mValue; }; // NoCopyMove // Commmon cases for add(...) and tryAdd(...). template<typename AddFunction> static void testAdd(AddFunction&& add); // Maps should only be comparable if their value type is. static_assert(IsEqualityComparableV<FileRangeMap<int>>); static_assert(!IsEqualityComparableV<std::pair<const FileRange, NoCopyMove>>); // Maps should only be copyable if their value type is. static_assert(std::is_copy_assignable_v<FileRangeMap<int>>); static_assert(std::is_copy_constructible_v<FileRangeMap<int>>); static_assert(!std::is_copy_assignable_v<FileRangeMap<NoCopyMove>>); static_assert(!std::is_copy_constructible_v<FileRangeMap<NoCopyMove>>); TEST(FileRangeMap, add) { FileRangeMap<NoCopyMove> map; auto [iterator, added] = map.add(std::piecewise_construct, std::forward_as_tuple(0, 1), std::forward_as_tuple(0)); EXPECT_TRUE(added); ASSERT_NE(iterator, map.end()); EXPECT_EQ(iterator->first, FileRange(0, 1)); EXPECT_EQ(iterator->second.mValue, 0); std::tie(iterator, added) = map.tryAdd(FileRange(0, 1), 0); EXPECT_FALSE(added); EXPECT_EQ(iterator, map.begin()); std::tie(iterator, added) = map.tryAdd(FileRange(1, 2), 1); EXPECT_TRUE(added); EXPECT_EQ(iterator->first, FileRange(1, 2)); EXPECT_EQ(iterator->second.mValue, 1); } TEST(FileRangeSet, add) { auto add = [](FileRangeSet& set, const FileRange& range) { return set.add(range); }; // add EXPECT_NO_FATAL_FAILURE(testAdd(std::move(add))); } TEST(FileRangeSet, constructor) { FileRangeSet set; // A newly constructed set should be empty. EXPECT_EQ(set.begin(), set.end()); EXPECT_TRUE(set.empty()); EXPECT_EQ(set.size(), 0u); } TEST(FileRangeSet, copy_assignment) { FileRangeSet set0; // Add some ranges to our set. set0.add(0u, 1u); set0.add(1u, 2u); set0.add(2u, 3u); // Sanity. EXPECT_EQ(set0.size(), 3u); FileRangeSet set1; set1 = set0; // Make sure set1 has been populated. EXPECT_EQ(set0.size(), set1.size()); // set0 should be equivalent to set1. EXPECT_EQ(set0, set1); FileRangeSet set2; // Assigning set2 to set1 should clear set1. set1 = set2; // set1 should be empty. EXPECT_TRUE(set1.empty()); } TEST(FileRangeSet, copy_constructor) { FileRangeSet set0; // Add some ranges to our set. set0.add(0u, 1u); set0.add(1u, 2u); set0.add(2u, 3u); // Sanity. EXPECT_EQ(set0.size(), 3u); FileRangeSet set1(set0); // Make sure set1 has been populated. EXPECT_EQ(set0.size(), set1.size()); // set0 should be equivalent to set1. EXPECT_EQ(set0, set1); } TEST(FileRangeSet, find) { FileRangeSet set; // Add some ranges to the tree. set.add(2u, 4u); set.add(6u, 8u); // Sanity. EXPECT_EQ(set.size(), 2u); // Check nonoverlapping cases. auto [m, n] = set.find(FileRange(0, 2)); EXPECT_EQ(m, set.end()); EXPECT_EQ(m, n); std::tie(m, n) = set.find(FileRange(8, 10)); EXPECT_EQ(m, set.end()); EXPECT_EQ(m, n); // Check single overlap cases. std::tie(m, n) = set.find(FileRange(1, 3)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_EQ(++m, n); std::tie(m, n) = set.find(FileRange(2, 4)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_EQ(++m, n); std::tie(m, n) = set.find(FileRange(3, 5)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_EQ(++m, n); // Check multiple overlap cases. std::tie(m, n) = set.find(FileRange(1, 7)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_NE(++m, n); EXPECT_EQ(*m, FileRange(6, 8)); EXPECT_EQ(++m, n); std::tie(m, n) = set.find(FileRange(2, 8)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_NE(++m, n); EXPECT_EQ(*m, FileRange(6, 8)); EXPECT_EQ(++m, n); std::tie(m, n) = set.find(FileRange(3, 9)); EXPECT_NE(m, set.end()); EXPECT_EQ(*m, FileRange(2, 4)); EXPECT_NE(++m, n); EXPECT_EQ(*m, FileRange(6, 8)); EXPECT_EQ(++m, n); } TEST(FileRangeSet, iteration) { // The ranges that we'll be adding to our set. std::vector<FileRange> ranges = {{0, 1}, {1, 2}, {2, 3}}; FileRangeSet set; // Add the ranges to our set. for (auto& range: ranges) set.add(range); // Sanity. EXPECT_EQ(set.size(), 3u); auto i = ranges.begin(); auto j = ranges.end(); auto m = set.begin(); auto n = set.end(); // Iterate over the tree, checking each range in turn. for (; i != j && m != n && *i == *m; ++i, ++m) ; // Make sure we compared every range. EXPECT_EQ(i, j); EXPECT_EQ(m, n); } TEST(FileRangeSet, move_assignment) { FileRangeSet set0; FileRangeSet set1; // Generate two identical sets. for (std::size_t i = 0; i < 3; ++i) { FileRange range(i, i + 1); set0.add(range); set1.add(range); } // Sanity. EXPECT_EQ(set0.size(), 3u); EXPECT_EQ(set0.size(), set1.size()); FileRangeSet set2; // Move set0's contents into set2. set2 = std::move(set0); // set0 should now be empty. EXPECT_EQ(set0.begin(), set0.end()); EXPECT_TRUE(set0.empty()); EXPECT_EQ(set0.size(), 0u); // set1 should be equivalent to set2. EXPECT_EQ(set1, set2); // Moving set0 into set2 should clear set2. set2 = std::move(set0); // set0 should now be equivalent to set2. EXPECT_EQ(set0, set2); } TEST(FileRangeSet, move_constructor) { FileRangeSet set0; FileRangeSet set1; // Generate two identical sets. for (std::size_t i = 0; i < 3; ++i) { FileRange range(i, i + 1); set0.add(range); set1.add(range); } // Sanity. EXPECT_EQ(set0.size(), 3u); EXPECT_EQ(set0.size(), set1.size()); // Move set0's contents to set2. FileRangeSet set2(std::move(set0)); // set0 should now be empty. EXPECT_EQ(set0.begin(), set0.end()); EXPECT_TRUE(set0.empty()); EXPECT_EQ(set0.size(), 0u); // set1 should now be equivalent to set2. EXPECT_EQ(set1, set2); } TEST(FileRangeSet, remove_contained) { FileRangeSet set; set.add(1u, 3u); auto i = set.add(4u, 6u).first; set.add(7u, 9u); // Sanity. EXPECT_EQ(set.size(), 3u); auto m = set.remove(FileRange(0, 2)); EXPECT_EQ(m, set.end()); EXPECT_EQ(set.size(), 3u); m = set.remove(FileRange(0, 4)); EXPECT_EQ(i, m); EXPECT_EQ(set.size(), 2u); m = set.remove(FileRange(4, 9)); EXPECT_EQ(m, set.end()); EXPECT_EQ(set.size(), 0u); } TEST(FileRangeSet, remove_multiple) { FileRangeSet set; // Add some ranges to the set. auto i = set.add(0u, 1u).first; auto k = set.add(2u, 3u).first; set.add(1u, 2u); // Sanity. EXPECT_EQ(set.size(), 3u); // Remove ranges up to but not including k. auto m = set.remove(i, k); EXPECT_EQ(m, k); EXPECT_EQ(set.size(), 1u); } TEST(FileRangeSet, remove_single) { FileRangeSet set; // Add some ranges to the set. auto i = set.add(0u, 1u).first; auto j = set.add(1u, 2u).first; auto k = set.add(2u, 3u).first; // Sanity. EXPECT_EQ(set.size(), 3u); // Remove each range in sequence. auto m = set.remove(i); EXPECT_EQ(m, j); EXPECT_EQ(set.size(), 2u); m = set.remove(m); EXPECT_EQ(m, k); EXPECT_EQ(set.size(), 1u); m = set.remove(m); EXPECT_EQ(m, set.end()); EXPECT_EQ(set.size(), 0u); } TEST(FileRangeSet, tryAdd) { auto tryAdd = [](FileRangeSet& set, const FileRange& range) { return set.tryAdd(range); }; // tryAdd EXPECT_NO_FATAL_FAILURE(testAdd(std::move(tryAdd))); } template<typename AddFunction> void testAdd(AddFunction&& add) { FileRangeSet set; // You should be able to add a range to an empty set. // // Before: ________ // After: __AA____ auto [iterator, added] = std::invoke(add, set, FileRange(2, 4)); // Make sure the range was added. EXPECT_TRUE(added); // Make sure the returned iterator references our range. ASSERT_NE(iterator, set.end()); EXPECT_EQ(iterator->mBegin, 2); EXPECT_EQ(iterator->mEnd, 4); // Make sure the set recognizes it is no longer empty. EXPECT_FALSE(set.empty()); EXPECT_EQ(set.size(), 1u); // Before: __AA____ // Adding: _BB_____ // After: _BAA____ std::tie(iterator, added) = std::invoke(add, set, FileRange(1, 3)); EXPECT_TRUE(added); ASSERT_NE(iterator, set.end()); EXPECT_EQ(iterator->mBegin, 1); EXPECT_EQ(iterator->mEnd, 2); EXPECT_EQ(set.size(), 2u); // Before: _BAA____ // Adding: ___CC___ // After: _BAA____ std::tie(iterator, added) = std::invoke(add, set, FileRange(3, 5)); EXPECT_FALSE(added); ASSERT_NE(iterator, set.end()); EXPECT_EQ(iterator->mBegin, 2); EXPECT_EQ(iterator->mEnd, 4); EXPECT_EQ(set.size(), 2u); // Before: _BAA____ // Adding: DDDDD___ // After: DBAA____ std::tie(iterator, added) = std::invoke(add, set, FileRange(0, 5)); EXPECT_TRUE(added); ASSERT_NE(iterator, set.end()); EXPECT_EQ(iterator->mBegin, 0); EXPECT_EQ(iterator->mEnd, 1); EXPECT_EQ(set.size(), 3u); // Before: DBAA____ // Adding: ____CC__ // After: DBAACC__ std::tie(iterator, added) = std::invoke(add, set, FileRange(4, 6)); EXPECT_TRUE(added); ASSERT_NE(iterator, set.end()); EXPECT_EQ(iterator->mBegin, 4); EXPECT_EQ(iterator->mEnd, 6); EXPECT_EQ(set.size(), 4u); } } // file_service } // mega �����������������������������������������������sdk-10.11.0/tests/unit/file_service/file_range_tree_trait_tests.cpp���������������������������������0000664�0000000�0000000�00000002022�15162662266�0025466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/file_range.h> #include <mega/file_service/file_range_tree_traits.h> #include <mega/file_service/type_traits.h> namespace mega { namespace file_service { namespace detail { struct KeyFunctionA { void operator()(const FileRange&) const; }; // KeyFunctionC struct KeyFunctionB { const FileRange& operator()(int) const; }; // KeyFunctionD static_assert(IsValidKeyFunctionV<Identity, const FileRange>); static_assert(IsValidKeyFunctionV<SelectFirst, std::pair<const FileRange, int>>); static_assert(!IsValidKeyFunctionV<KeyFunctionA, const FileRange>); static_assert(!IsValidKeyFunctionV<KeyFunctionB, const FileRange>); static_assert(IsValidValueTypeV<const FileRange>); static_assert(IsValidValueTypeV<FileRange>); static_assert(!IsValidValueTypeV<int>); static_assert(IsValidValueTypeV<std::pair<const FileRange, int>>); static_assert(IsValidValueTypeV<std::pair<FileRange, int>>); static_assert(!IsValidValueTypeV<std::pair<std::pair<FileRange, int>, int>>); } // detail } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/file_service.cmake����������������������������������������������0000664�0000000�0000000�00000001040�15162662266�0022663�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������target_include_directories(test_unit PRIVATE file_service) target_link_libraries(test_unit PRIVATE MEGA::FileServiceHeaderPaths) target_sources(test_unit PRIVATE file_service/avl_tree_tests.cpp file_service/avl_tree_trait_tests.cpp file_service/file_range_trait_tests.cpp file_service/file_range_tree_tests.cpp file_service/file_range_tree_trait_tests.cpp file_service/type_trait_tests.cpp ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/�����������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0020140�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/file_service/����������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0022577�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/file_service/testing/��������������������������������������0000775�0000000�0000000�00000000000�15162662266�0024254�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/file_service/testing/unit/���������������������������������0000775�0000000�0000000�00000000000�15162662266�0025233�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/file_service/testing/unit/avl_node.h�����������������������0000664�0000000�0000000�00000004363�15162662266�0027201�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree_node.h> #include <mega/file_service/avl_tree_traits.h> #include <utility> namespace mega { namespace file_service { struct Node { Node(int key): mLink{}, mKey(key), mSize{} {} AVLTreeNode<Node> mLink; int mKey; int mSize; }; // Node struct Traits { static constexpr auto mLinkPointer = &Node::mLink; static constexpr auto mValuePointer = &Node::mKey; }; // Traits class TraitsWithMetadata: public Traits { using LT = detail::LinkTraits<TraitsWithMetadata>; using MT = detail::MetadataTraits<TraitsWithMetadata>; public: static constexpr auto mMetadataPointer = &Node::mSize; struct Update { template<typename IteratorType> int operator()(IteratorType node) const { auto size = 1; if (auto left = node.left()) size += left->mSize; if (auto right = node.right()) size += right->mSize; return size; } }; // Update class Validate { template<typename IteratorType> auto validate(IteratorType node) const { if (!node) return std::make_pair(0, true); auto [left, leftOk] = validate(node.left()); if (!leftOk) return std::make_pair(0, false); auto [right, rightOk] = validate(node.right()); if (!rightOk) return std::make_pair(0, false); auto actualSize = node->mSize; auto computedSize = left + right + 1; return std::make_pair(computedSize, actualSize == computedSize); } public: template<typename IteratorType> bool operator()(IteratorType node) const { return validate(node).second; } }; // Validate }; // TraitsWithMetadata struct Uncomparable {}; // Uncomparable struct UncomparableNode { AVLTreeNode<UncomparableNode> mLink; Uncomparable mValue; }; // UncomparableNode struct UncomparableTraits { static constexpr auto mLinkPointer = &UncomparableNode::mLink; static constexpr auto mValuePointer = &UncomparableNode::mValue; }; // UncomparableTraits } // file_service } // mega �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/mega/file_service/testing/unit/avl_utilities.h������������������0000664�0000000�0000000�00000004256�15162662266�0030270�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <mega/file_service/avl_tree.h> #include <cinttypes> #include <fstream> #include <string> namespace mega { namespace file_service { template<typename Traits> std::ostream& render(std::ostream& ostream, const AVLTree<Traits>& tree) { ostream << "digraph {\n"; render(tree.root(), ostream, tree); ostream << "}\n"; return ostream; } template<typename Traits> void render(const std::string& path, const AVLTree<Traits>& tree) { std::ofstream ostream(path, std::ios::trunc); render(ostream, tree); } template<typename Traits> std::ostream& render(typename AVLTree<Traits>::ConstIterator iterator, std::ostream& ostream, const AVLTree<Traits>& tree) { // No node? Nothing to render. if (!iterator) return ostream; // Generate a unique ID for the node referenced by iterator. auto id = [](auto iterator) { return reinterpret_cast<std::uintmax_t>(&*iterator); }; // id // Render a child. auto renderChild = [&id, &ostream, &tree](auto iterator) { // No child? Nothing to render. if (!iterator) return; // Render the child. render(iterator, ostream, tree); // Get our hands on the child's parent. auto parent = iterator.parent(); // Assume child is parent's left child. auto port = "sw"; // Child is actually parent's right child. if (iterator == parent.right()) port = "se"; // Link the parent to its child. ostream << id(parent) << ":" << port << " -> " << id(iterator) << ";\n"; // Link the child to its parent. ostream << id(iterator) << ":n" << " -> " << id(parent) << ":" << port << ";\n"; }; // renderChild // Convenience. using KeyTraits = typename AVLTree<Traits>::KeyTraits; // Render this node. ostream << id(iterator) << " [ label = \"" << KeyTraits::key(*iterator) << "\" ];\n"; // Render this node's left and right children. renderChild(iterator.left()); renderChild(iterator.right()); // Return iterator to caller. return ostream; } } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/file_service/type_trait_tests.cpp��������������������������������������������0000664�0000000�0000000�00000006322�15162662266�0023344�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <mega/file_service/type_traits.h> #include <utility> namespace mega { namespace file_service { static_assert(IsNoneSuchV<NoneSuch>); static_assert(!IsNoneSuchV<int>); static_assert(IsNotNoneSuchV<int>); static_assert(!IsNotNoneSuchV<NoneSuch>); struct DetectedTest0 { using type = void; }; // DetectedTest0 struct DetectedTest1 {}; // DetectedTest1 template<typename Type> using DetectType = typename Type::type; static_assert(DetectedV<DetectType, DetectedTest0>); static_assert(std::is_same_v<DetectedT<DetectType, DetectedTest0>, void>); static_assert(!DetectedV<DetectType, DetectedTest1>); static_assert(std::is_same_v<DetectedT<DetectType, DetectedTest1>, NoneSuch>); static_assert(DetectedOrV<int, DetectType, DetectedTest0>); static_assert(std::is_same_v<DetectedOrT<int, DetectType, DetectedTest0>, void>); static_assert(!DetectedOrV<int, DetectType, DetectedTest1>); static_assert(std::is_same_v<DetectedOrT<int, DetectType, DetectedTest1>, int>); struct Base {}; // Base struct DerivedA: Base {}; // DerivedA struct DerivedB: DerivedA {}; // DerivedB struct Unrelated {}; // Unrelated static_assert(std::is_same_v<MostSpecificClassT<Base, DerivedA>, DerivedA>); static_assert(std::is_same_v<MostSpecificClassT<Base, DerivedA, DerivedB>, DerivedB>); static_assert(std::is_same_v<MostSpecificClassT<Base, Unrelated>, NoneSuch>); static_assert(std::is_same_v<MostSpecificClassT<Base, DerivedA, Unrelated>, NoneSuch>); struct Object { const int mConstMember{}; int mMember{}; static constexpr int sConstMember{}; }; // Object using ConstMember = MemberPointerTraits<decltype(&Object::mConstMember)>; static_assert(ConstMember::value); static_assert(std::is_same_v<ConstMember::ClassType, Object>); static_assert(std::is_same_v<ConstMember::MemberType, const int>); using Member = MemberPointerTraits<decltype(&Object::mMember)>; static_assert(Member::value); static_assert(std::is_same_v<Member::ClassType, Object>); static_assert(std::is_same_v<Member::MemberType, int>); template<typename T> using DetectClassType = typename T::ClassType; template<typename T> using DetectMemberType = typename T::MemberType; using StaticConstMember = MemberPointerTraits<decltype(&Object::sConstMember)>; static_assert(!StaticConstMember::value); static_assert(!DetectedV<DetectClassType, StaticConstMember>); static_assert(!DetectedV<DetectMemberType, StaticConstMember>); static_assert(std::is_same_v<RemoveCVRefT<const int&>, int>); static_assert(std::is_same_v<RemoveCVRefT<int&>, int>); static_assert(std::is_same_v<RemoveCVRefT<int>, int>); static_assert(IsEqualityComparableV<int>); static_assert(IsEqualityComparableV<std::pair<int, int>>); static_assert(!IsEqualityComparableV<Unrelated>); static_assert(!IsEqualityComparableV<std::pair<int, Unrelated>>); template<typename Provided, typename Expected> constexpr auto SelectFirstResultIsV = std::is_same_v<std::invoke_result_t<SelectFirst, Provided>, Expected>; // Convenience. using IntPair = std::pair<int, int>; static_assert(SelectFirstResultIsV<const IntPair&, const int&>); static_assert(SelectFirstResultIsV<IntPair&, int&>); static_assert(SelectFirstResultIsV<IntPair&&, int&&>); static_assert(SelectFirstResultIsV<IntPair, int&&>); } // file_service } // mega ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/getDefaultLogName.cpp��������������������������������������������������������0000664�0000000�0000000�00000000203�15162662266�0020616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <string> const std::string& getDefaultLogName() { static const std::string k = "sdk_unit_tests.log"; return k; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/hashcash_test.cpp������������������������������������������������������������0000664�0000000�0000000�00000016231�15162662266�0020120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2024 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> #include <mega/canceller.h> #include <mega/hashcash.h> #include <future> #include <thread> using namespace mega; namespace { using Clock = std::chrono::steady_clock; using namespace std::chrono_literals; const std::string kTokenHard{"K4QHo4I6XmnLNNsFqutTwObWZMClxf7ov--5OHLdGXSMHRwN8bLvrUTlpnhXVdtO"}; constexpr uint8_t kHighEasiness{200}; constexpr uint8_t kLowEasiness{5}; constexpr auto kCappedWorkers{1u}; constexpr auto kLowTtl{30ms}; constexpr auto kLargeTtl{15min}; struct RunResult { std::string gencashRes; std::chrono::milliseconds elapsed; }; auto run_gencash(const std::string& token, const uint8_t easiness, const std::chrono::milliseconds ttl, const cancel_epoch_t epoch, const unsigned workers) -> RunResult { const auto start = Clock::now(); auto gencashRes = gencash(token, easiness, ttl, epoch, workers); return {std::move(gencashRes), std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start)}; } auto run_gencash_async = [](auto&&... args) { auto ready = std::make_shared<std::promise<void>>(); std::future<void> go = ready->get_future(); auto fut = std::async(std::launch::async, [ready, tup = std::make_tuple(std::forward<decltype(args)>(args)...)]() mutable { ready->set_value(); return std::apply(run_gencash, std::move(tup)); }); go.wait(); return fut; }; auto expect_cancelled = [](const RunResult& r, const cancel_epoch_t epoch, const std::chrono::milliseconds upper, const bool cancelTriggered = false) { EXPECT_TRUE(r.gencashRes.empty()); EXPECT_LT(r.elapsed, upper); EXPECT_EQ(ScopedCanceller(epoch).triggered(), cancelTriggered); }; auto expect_completed = [](const RunResult& r, const cancel_epoch_t epoch) { EXPECT_FALSE(r.gencashRes.empty()); EXPECT_FALSE(ScopedCanceller(epoch).triggered()); }; } // namespace TEST(Hashcash, Gencash) { const std::vector<std::uint8_t> easinessV{180, 192}; const std::vector<unsigned> numWorkersV{8u, 2u}; const std::vector<std::string> hashcash{ "wFqIT_wY3tYKcrm5zqwaUoWym3ZCz32cCsrJOgYBgihtpaWUhGyWJ--EY-zfwI-i", "3NIjq_fgu6bTyepwHuKiaB8a1YRjISBhktWK1fjhRx86RhOqKZNAcOZht0wJvmhQ", "HGztcvhT0sngIveS6C4CY1nx64YFtXnbcqX_Dvj7NxmX0SCNRlCZ51_pMWQgpHdv", }; for (const auto& easiness: easinessV) { for (const auto& numWorkers: numWorkersV) { for (const auto& hc: hashcash) { const auto reqEpochSnapshot{cancel_epoch_snapshot()}; const auto res = run_gencash(hc, easiness, kLargeTtl, reqEpochSnapshot, numWorkers); ASSERT_TRUE(validateHashcash(hc, easiness, res.gencashRes)) << "Failed hash: " << hc << ": genCashResult [easiness = " << +easiness << ", numWorkers = " << numWorkers << "]"; ASSERT_EQ(retryGencashData(), std::nullopt); } } } } TEST(Hashcash, CancelsDuringComputeReturnsQuickly) { const auto reqEpochSnapshot{cancel_epoch_snapshot()}; auto fut = run_gencash_async(kTokenHard, kLowEasiness, kLargeTtl, reqEpochSnapshot, kCappedWorkers); std::this_thread::sleep_for(200ms); // A bit more wait once we know that we are running cancel_epoch_bump(); const auto res = fut.get(); expect_cancelled(res, reqEpochSnapshot, 1s, true); } TEST(Hashcash, CancelBeforeStartSkipsDoesNotAffect) { cancel_epoch_bump(); const auto reqEpochSnapshot{cancel_epoch_snapshot()}; const auto res = gencash(kTokenHard, kHighEasiness, reqEpochSnapshot); EXPECT_FALSE(res.empty()); } // With very hard difficulty and test-shortened budget, we should early-exit. TEST(Hashcash, BudgetEarlyExitWithoutCancel) { const auto reqEpochSnapshot{cancel_epoch_snapshot()}; const auto res = run_gencash(kTokenHard, kLowEasiness, kLowTtl, // intentionally too small reqEpochSnapshot, kCappedWorkers); expect_cancelled(res, reqEpochSnapshot, 2s); } TEST(Hashcash, BudgetEarlyExitWithoutCancelWRetries) { const auto reqEpochSnapshot{cancel_epoch_snapshot()}; // Warm up: gencash must succeed and reset previous retry data (if any) { const auto res = run_gencash(kTokenHard, kHighEasiness, kLargeTtl, reqEpochSnapshot, MAX_WORKERS_FOR_GENCASH); expect_completed(res, reqEpochSnapshot); ASSERT_EQ(retryGencashData(), std::nullopt); } // Force retry up to RetryGencash::kMaxRetries times for (unsigned i = 0; i < RetryGencash::kMaxRetries; ++i) { const auto res = run_gencash(kTokenHard, kLowEasiness, kLowTtl, reqEpochSnapshot, kCappedWorkers); expect_cancelled(res, reqEpochSnapshot, 2s); const auto retryData = retryGencashData(); ASSERT_NE(retryData, std::nullopt); EXPECT_EQ(retryData->mForceRetryCount, i + 1); EXPECT_GE(res.elapsed, retryData->mBudget); EXPECT_GE(retryData->mGencashTime, retryData->mBudget); } // Attempt n RetryGencash::kMaxRetries won't trigger any more retries { const auto retryDataPre = retryGencashData(); ASSERT_NE(retryDataPre, std::nullopt); EXPECT_EQ(retryDataPre->mForceRetryCount, RetryGencash::kMaxRetries); const auto res = run_gencash(kTokenHard, kHighEasiness, kLowTtl, reqEpochSnapshot, MAX_WORKERS_FOR_GENCASH); expect_completed(res, reqEpochSnapshot); const auto retryData = retryGencashData(); ASSERT_NE(retryData, std::nullopt); EXPECT_EQ(retryData->mForceRetryCount, 0u); } // Attempt n RetryGencash::kMaxRetries+1 should reset any previous retryGencashData { const auto res = run_gencash(kTokenHard, kHighEasiness, kLargeTtl, reqEpochSnapshot, MAX_WORKERS_FOR_GENCASH); expect_completed(res, reqEpochSnapshot); EXPECT_EQ(retryGencashData(), std::nullopt); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/impl/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015531�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/impl/share_test.cpp����������������������������������������������������������0000664�0000000�0000000�00000003366�15162662266�0020406�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "impl/share.h" #include "mega/types.h" #include <gtest/gtest.h> #include <vector> using namespace ::mega::impl; using ::mega::Share; class ShareSorterTest: public ::testing::Test { protected: void SetUp() override; std::vector<Share> mShares; std::vector<ShareData> mShareDatas; }; void ShareSorterTest::SetUp() { Test::SetUp(); // Share with a smaller timestamp is created earlier mShares = { Share{nullptr, mega::RDONLY, 20}, Share{nullptr, mega::RDONLY, 10}, Share{nullptr, mega::RDONLY, 30}, }; mShareDatas = { {1, &mShares[0], true}, {2, &mShares[1], true}, {3, &mShares[2], true}, }; } TEST_F(ShareSorterTest, SortByShareCreationTimeAscendingly) { ShareSorter::sort(mShareDatas, ::mega::MegaApi::ORDER_SHARE_CREATION_ASC); ASSERT_EQ(mShareDatas[0].getNodeHandle(), 2); ASSERT_EQ(mShareDatas[1].getNodeHandle(), 1); ASSERT_EQ(mShareDatas[2].getNodeHandle(), 3); } TEST_F(ShareSorterTest, SortByShareCreationTimeDescendingly) { ShareSorter::sort(mShareDatas, ::mega::MegaApi::ORDER_SHARE_CREATION_DESC); ASSERT_EQ(mShareDatas[0].getNodeHandle(), 3); ASSERT_EQ(mShareDatas[1].getNodeHandle(), 1); ASSERT_EQ(mShareDatas[2].getNodeHandle(), 2); } TEST_F(ShareSorterTest, SortByOthersDoesNotChangeOrder) { ShareSorter::sort(mShareDatas, ::mega::MegaApi::ORDER_NONE); ASSERT_EQ(mShareDatas[0].getNodeHandle(), 1); ASSERT_EQ(mShareDatas[1].getNodeHandle(), 2); ASSERT_EQ(mShareDatas[2].getNodeHandle(), 3); ShareSorter::sort(mShareDatas, ::mega::MegaApi::ORDER_CREATION_DESC); ASSERT_EQ(mShareDatas[0].getNodeHandle(), 1); ASSERT_EQ(mShareDatas[1].getNodeHandle(), 2); ASSERT_EQ(mShareDatas[2].getNodeHandle(), 3); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/jsonsplitter_test.cpp��������������������������������������������������������0000664�0000000�0000000�00000115362�15162662266�0021103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2025 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/command.h" #include "mega/json.h" #include "mega/types.h" #include <gmock/gmock.h> #include <gtest/gtest.h> using namespace mega; class JSONSplitterTest: public ::testing::Test { protected: void SetUp() override { splitter.clear(); filters.clear(); } void TearDown() override { // Cleanup } JSONSplitter splitter; std::map<std::string, JSONSplitter::FilterCallback> filters; std::string callbackData; bool callbackResult = true; // Helper function to create a simple callback that records data JSONSplitter::FilterCallback createCallbackWithString(std::string& output) { return [&output](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { return JSONSplitter::ResultFromBool(json->storeobject(&output)); } return JSONSplitter::CallbackResult::FAILED; }; } // Helper function to create a simple callback that records data in vector JSONSplitter::FilterCallback createCallbackWithVector(std::vector<std::string>& output) { return [&output](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { std::string temp; if (json->storeobject(&temp)) { output.emplace_back(temp); return JSONSplitter::CallbackResult::SUCCESS; } } return JSONSplitter::CallbackResult::FAILED; }; } }; class TestCommand: public Command { public: bool procresult(Result, JSON&) override { return true; } }; TEST_F(JSONSplitterTest, ConstructorAndInitialState) { JSONSplitter newSplitter; EXPECT_TRUE(newSplitter.isStarting()); EXPECT_FALSE(newSplitter.hasFinished()); EXPECT_FALSE(newSplitter.hasFailed()); } TEST_F(JSONSplitterTest, ClearResetsState) { // Simple test data std::string testJson = R"({"test": "value"})"; splitter.processChunk(nullptr, testJson.c_str()); // Clear and verify state is reset splitter.clear(); EXPECT_TRUE(splitter.isStarting()); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessErrorResponse) { std::string testJson = R"({"err":-1})"; Error err; filters["#"] = [&err](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { TestCommand cmd; return JSONSplitter::ResultFromBool(cmd.checkError(err, *json)); } return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(-1, err); } TEST_F(JSONSplitterTest, ProcessSimpleObjectNumber) { std::string testJson = "-1,"; // Can not be a number without any suffix? m_off_t capturedData; filters["#"] = [&capturedData](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { capturedData = json->getint(); } return JSONSplitter::CallbackResult::SUCCESS; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length() - 1)); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(-1, capturedData); } TEST_F(JSONSplitterTest, ProcessSimpleObjectString) { std::string testJson = R"({"key": "value"})"; std::string capturedData; filters["{\"key"] = createCallbackWithString(capturedData); auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ("value", capturedData); } TEST_F(JSONSplitterTest, ProcessNestedObject) { std::string testJson = R"({"outer": {"inner": "value"}})"; std::string capturedData; // Why {{outer\"inner" : // first { is from the wrapper of the JSON object // second { is part of the "outer", it means the value for field "outer" is a JSON, so push // {outer together into the stack the \" before "inner" is also a separator of the path, not // part of the name "inner", it means the value for filed "inner" is a string filters["{{outer\"inner"] = createCallbackWithString(capturedData); auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(capturedData, "value"); } TEST_F(JSONSplitterTest, ProcessStringWithEscapes) { std::string testJson = R"({"escaped": "quote:\" new line:\n tab:\t"})"; std::string capturedData; filters["{\"escaped"] = createCallbackWithString(capturedData); auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(capturedData, R"(quote:\" new line:\n tab:\t)"); } TEST_F(JSONSplitterTest, ProcessChunkedData) { // Split JSON across mutiple chunks std::string chunk1 = R"({"key1": "value1",)"; std::string chunk2 = R"("key2": "value2"})"; std::vector<std::string> capturedData; filters["{\"key1"] = createCallbackWithVector(capturedData); filters["{\"key2"] = createCallbackWithVector(capturedData); auto consumed1 = splitter.processChunk(&filters, chunk1.c_str()); EXPECT_EQ(consumed1, static_cast<m_off_t>(chunk1.length())); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); // Must purge consumed bytes // if passing chunk1 + chunk2 here, splitter won't work as expected auto consumed2 = splitter.processChunk(&filters, chunk2.c_str()); EXPECT_EQ(consumed2, static_cast<m_off_t>(chunk2.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_THAT(capturedData, testing::ElementsAre("value1", "value2")); } TEST_F(JSONSplitterTest, ProcessArrayWithStarters) { std::string testJson = R"({"a":[{"a": "d", "i": "abc"}, {"a": "x", "sn": "xyz"}]})"; std::vector<std::string> capturedData; filters["{[a{\"a"] = createCallbackWithVector(capturedData); auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_THAT(capturedData, testing::ElementsAre("d", "x")); } TEST_F(JSONSplitterTest, ProcessChunkWithPauseFromStart) { std::string testJson = R"({"a":[{"a": "d", "i": "abc"}, {"a": "x", "sn": "xyz"}]})"; bool first = true; std::vector<std::string> capturedData; filters["{[a{\"a"] = [&first, &capturedData](JSON* json) -> JSONSplitter::CallbackResult { if (first) { first = false; return JSONSplitter::CallbackResult::PAUSED; } else { std::string output; json->storeobject(&output); capturedData.emplace_back(output); } return JSONSplitter::CallbackResult::SUCCESS; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(0, capturedData.size()); // No need to purge because the consumed length is 0 consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_THAT(capturedData, testing::ElementsAre("d", "x")); } TEST_F(JSONSplitterTest, ProcessChunkWithPauseFromMiddle) { std::string testJson = R"({"a":[{"a": "d", "i": "abc"}, {"b": "x", "sn": "xyz"}]})"; bool first = true; std::vector<std::string> capturedData; filters["{[a{\"a"] = [&capturedData](JSON* json) -> JSONSplitter::CallbackResult { std::string output; json->storeobject(&output); capturedData.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; filters["{[a{\"b"] = [&first, &capturedData](JSON* json) -> JSONSplitter::CallbackResult { if (first) { first = false; return JSONSplitter::CallbackResult::PAUSED; } else { std::string output; json->storeobject(&output); capturedData.emplace_back(output); } return JSONSplitter::CallbackResult::SUCCESS; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 16); // {"a":[{"a": "d", EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_THAT(capturedData, testing::ElementsAre("d")); // Must purge consumed bytes before next call testJson.erase(0, static_cast<size_t>(consumed)); consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_THAT(capturedData, testing::ElementsAre("d", "x")); } TEST_F(JSONSplitterTest, ProcessChunkWithMultiplePauseAtStringValue) { std::string testJson = R"({"key1":"value1", "key2":"value2", "key3":"value3"})"; int callCount = 0; std::vector<std::string> capturedValues; // Filter for string values - pause at first value filters["{\"key1"] = [&callCount, &capturedValues](JSON* json) -> JSONSplitter::CallbackResult { callCount++; if (callCount <= 2) { return JSONSplitter::CallbackResult::PAUSED; } std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; filters["{\"key2"] = [&capturedValues](JSON* json) -> JSONSplitter::CallbackResult { std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; filters["{\"key3"] = [&capturedValues](JSON* json) -> JSONSplitter::CallbackResult { std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // First call should pause at key1's string value auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(0, capturedValues.size()); // Second call should pause at key1's string value consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(0, capturedValues.size()); // Second call should process all remaining values consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_THAT(capturedValues, testing::ElementsAre("value1", "value2", "value3")); } TEST_F(JSONSplitterTest, ProcessChunkWithMultiplePauseCheckStartAndEndFilters) { std::string testJson = R"({"key1":"value1", "key2":"value2", "key3":"value3"})"; int callCount = 0; int flag = -1; std::vector<std::string> capturedValues; // start filter filters["<"] = [&flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); flag = 1; return JSONSplitter::CallbackResult::SUCCESS; }; // end filter filters[">"] = [&flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); flag = 0; return JSONSplitter::CallbackResult::SUCCESS; }; // Filter for string values - pause at first value filters["{\"key1"] = [&callCount, &capturedValues, &flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_EQ(flag, 1); callCount++; if (callCount <= 2) { return JSONSplitter::CallbackResult::PAUSED; } std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; filters["{\"key2"] = [&capturedValues, &flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_EQ(flag, 1); std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; filters["{\"key3"] = [&capturedValues, &flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_EQ(flag, 1); std::string output; json->storeobject(&output); capturedValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // First call should pause at key1's string value auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(0, capturedValues.size()); EXPECT_EQ(0, flag); // Second call should pause at key1's string value consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(0, capturedValues.size()); EXPECT_EQ(0, flag); // Second call should process all remaining values consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_THAT(capturedValues, testing::ElementsAre("value1", "value2", "value3")); EXPECT_EQ(0, flag); } TEST_F(JSONSplitterTest, ProcessNestedFilterWithPauseAtInnerString) { // Test case: Inner string filter pauses, but there's an outer object filter // On resume, inner filter should work, and outer filter receives the remaining data std::string testJson = R"({"a":{"b":"d","i":"abc","x":"y"},"c":"d"})"; bool firstInnerCall = true; int innerCallCount = 0; int outerCallCount = 0; std::vector<std::string> capturedInnerValues; // Inner filter for "i" - pause on first call filters["{{a\"i"] = [&firstInnerCall, &innerCallCount, &capturedInnerValues]( JSON* json) -> JSONSplitter::CallbackResult { innerCallCount++; if (firstInnerCall) { firstInnerCall = false; return JSONSplitter::CallbackResult::PAUSED; } std::string output; json->storeobject(&output); capturedInnerValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // Outer filter for "a" object - receives only the remaining part (closing brace) filters["{{a"] = [&outerCallCount](JSON* json) -> JSONSplitter::CallbackResult { outerCallCount++; // "x":"y"},"c":"d"} // We should process "x":"y", and leave JSON at the first } std::string key, value; EXPECT_TRUE(json->storeKeyValueFromObject(key, value)); EXPECT_EQ(key, "x"); EXPECT_EQ(value, "y"); return JSONSplitter::ResultFromBool(json->leaveobject()); }; // First call: inner "i" pauses auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 5); // {"a": - up to outer filter start EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_TRUE(splitter.hasPaused()); EXPECT_EQ(1, innerCallCount); EXPECT_EQ(0, outerCallCount); // Outer not reached yet EXPECT_EQ(0, capturedInnerValues.size()); // Purge consumed bytes testJson.erase(0, static_cast<size_t>(consumed)); // Second call: resume, inner succeeds, outer handles closure consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_EQ(2, innerCallCount); // Inner called again EXPECT_EQ(1, outerCallCount); // Outer called EXPECT_THAT(capturedInnerValues, testing::ElementsAre("abc")); } TEST_F(JSONSplitterTest, EmptyFiltersChain) { JSONSplitter::FiltersChain chain; std::string testJson = R"({"test": "value"})"; splitter.processChunk(chain, testJson.c_str()); EXPECT_FALSE(splitter.isStarting()); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessChunkWithFiltersChain) { std::string testJson = R"({"key1":{"key11":"value11"}, "key2":"value2", "key3":"value3"})"; bool startFlag = false; bool firstFlag = false; bool key1Flag = false; bool key2Flag = false; bool key3Flag = false; bool endFlag = false; JSONSplitter::FiltersChain chain; std::map<std::string, JSONSplitter::FilterCallback> filters1; std::map<std::string, JSONSplitter::FilterCallback> filters2; filters2["<"] = [&startFlag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); startFlag = true; return JSONSplitter::CallbackResult::SUCCESS; }; // Filter for JSONSpliter start filters2[""] = [&firstFlag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); firstFlag = true; return JSONSplitter::CallbackResult::SUCCESS; }; filters2["{{key1"] = [&key1Flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); json->enterobject(); json->leaveobject(); key1Flag = true; return JSONSplitter::CallbackResult::SUCCESS; }; filters2["{\"key2"] = [&key2Flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); key2Flag = true; return JSONSplitter::CallbackResult::SUCCESS; }; filters2[">"] = [&endFlag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); endFlag = true; return JSONSplitter::CallbackResult::SUCCESS; }; filters1["{\"key3"] = [&key3Flag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); json->enterobject(); json->leaveobject(); key3Flag = true; return JSONSplitter::CallbackResult::SUCCESS; }; chain.emplace_back(&filters1); chain.emplace_back(&filters2); auto consumed = splitter.processChunk(chain, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_TRUE(startFlag); EXPECT_TRUE(firstFlag); EXPECT_TRUE(key1Flag); EXPECT_TRUE(key2Flag); EXPECT_TRUE(key3Flag); EXPECT_TRUE(endFlag); } TEST_F(JSONSplitterTest, ProcessChunkWithFiltersChainContainingDuplicatedFilter) { std::string testJson = R"({"key":"value"})"; bool keyFlag1 = false; bool keyFlag2 = false; JSONSplitter::FiltersChain chain; std::map<std::string, JSONSplitter::FilterCallback> filters1; std::map<std::string, JSONSplitter::FilterCallback> filters2; filters1["{\"key"] = [&keyFlag1](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); keyFlag1 = true; return JSONSplitter::CallbackResult::SUCCESS; }; filters2["{\"key"] = [&keyFlag2](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); keyFlag2 = true; return JSONSplitter::CallbackResult::SUCCESS; }; chain.emplace_back(&filters1); chain.emplace_back(&filters2); auto consumed = splitter.processChunk(chain, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_TRUE(keyFlag1); EXPECT_FALSE(keyFlag2); } TEST_F(JSONSplitterTest, ProcessChunkWithFiltersChainContainingNullFilters) { std::string testJson = R"({"key":"value"})"; bool keyFlag = false; JSONSplitter::FiltersChain chain; filters["{\"key"] = [&keyFlag](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); keyFlag = true; return JSONSplitter::CallbackResult::SUCCESS; }; chain.emplace_back(nullptr); chain.emplace_back(&filters); auto consumed = splitter.processChunk(chain, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_FALSE(splitter.hasPaused()); EXPECT_TRUE(keyFlag); } TEST_F(JSONSplitterTest, ProcessErrorResponseWithFiltersChain) { std::string testJson = R"({"err":-1})"; Error err; JSONSplitter::FiltersChain chain; std::map<std::string, JSONSplitter::FilterCallback> filters1; std::map<std::string, JSONSplitter::FilterCallback> filters2; filters2["#"] = [&err](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { TestCommand cmd; return JSONSplitter::ResultFromBool(cmd.checkError(err, *json)); } return JSONSplitter::CallbackResult::FAILED; }; chain.emplace_back(&filters1); chain.emplace_back(&filters2); auto consumed = splitter.processChunk(chain, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(-1, err); } // Keep in mind, this unit test is added after the JSONSplitter has served well for a long time. // So this test is for the supported scenarios, and new features, not for the edge cases. // Because JSONSplitter is intended for specific scenarios like streaming parsing the well formed // JSONs from API Server. But it's harmless to record those unexpected cases. // 1. Numbers: {"int": 123, "float": 3.14, "negative": -123} // 2. Booleans and Null values: {"bool": true, "null": null} // 3. Arrays: ["a", "b", "c"] // 4. Spaces: // before the end of first chunk: R"({"key1": "value1", )" and R"("key2": "value2"})" // before the number:{"err": -1} // before the string: {"key": "value"} // The assert(false) in prodution code can prevent unexpected scenarios, // and they are playing a critical role in the stability of the SDK. // Here's some cases will trigger asserts, and might replace some of them when streaming parsing is // applied. // Note: All following cases will trigger asserts, but not all of them need to be applied. /* TEST_F(JSONSplitterTest, ProcessNull) { // This may return a nameid of 0? std::string testJson = R"({"key":null})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessDeepEnbeddedObjectWithoutConsuming) { std::string testJson = R"({"outer":{"inner":{“deep":"value"}}})"; bool result = false; filters["{{outer{"] = [&result](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { result = true; return JSONSplitter::CallbackResult::SUCCESS; } return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_TRUE(result); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedObject) { std::string testJson = R"({"key":"value"{}})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessEndEarly) { std::string testJson = R"({"key":})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedBracket1) { std::string testJson = R"({"key":[})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedBracket2) { std::string testJson = R"({"key":{])"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedBracket3) { // segmentfault std::string testJson = "}"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessNormalFailure) { std::string testJson = R"({"key1": "value1", "key2": "value2"})"; filters["{\"key2"] = [](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 18); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedComma) { std::string testJson = R"({"arr":[,1]})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessNoColon) { std::string testJson = R"({"key""value"})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedNumber) { std::string testJson = R"({"key":"value"123})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedCharacter) { std::string testJson = R"({"key":@})"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedFailure) { std::string testJson = R"({"err":-1})"; filters["#"] = [](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedFailure2) { std::string testJson = R"({"key":"value"})"; filters[""] = [](JSON* json) -> JSONSplitter::CallbackResult { EXPECT_NE(json, nullptr); return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedCloseBracketWithEmptyStack) { std::string testJson = R"(}{}})"; // extra } auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } TEST_F(JSONSplitterTest, ProcessUnexpectedContentWithEmptyStack) { std::string testJson = R"("abc","def")"; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_TRUE(splitter.hasFailed()); } // error filter can not be paused TEST_F(JSONSplitterTest, ProcessErrorResponseWithPause) { std::string testJson = R"({"err":-1})"; Error err(ErrorCodes::API_OK); bool first = true; filters["#"] = [&first, &err](JSON* json) -> JSONSplitter::CallbackResult { if (json && json->pos) { if (first) { first = false; return JSONSplitter::CallbackResult::PAUSED; } TestCommand cmd; return JSONSplitter::ResultFromBool(cmd.checkError(err, *json)); } return JSONSplitter::CallbackResult::FAILED; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 0); EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(ErrorCodes::API_OK, err); consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(ErrorCodes::API_EINTERNAL, err); } // object can not be paused TEST_F(JSONSplitterTest, ProcessChunkWithPauseAtObjectClosure) { // Note: Do not add space in the JSON string, e.g. after the ":" std::string testJson = R"({"a":[{"a":"d","i":"abc"},{"a":"x","sn":"xyz"}]})"; bool firstObject = true; std::vector<std::string> capturedObjects; // Filter for the array of objects - pause at first object closure filters["{[a{"] = [&firstObject, &capturedObjects](JSON* json) -> JSONSplitter::CallbackResult { if (firstObject) { firstObject = false; return JSONSplitter::CallbackResult::PAUSED; } else { std::string output; json->storeobject(&output); capturedObjects.emplace_back(output); } return JSONSplitter::CallbackResult::SUCCESS; }; auto consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, 6); // Consumed up to before the first object: {"a":[ EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(0, capturedObjects.size()); // Must purge consumed bytes before next call testJson.erase(0, static_cast<size_t>(consumed)); // Second call should process the first object and pause at second object closure consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_THAT(capturedObjects, testing::ElementsAre(R"({"a":"d","i":"abc"})", R"({"a":"x","sn":"xyz"})")); } // object can not be paused TEST_F(JSONSplitterTest, ProcessNestedFilterWithPauseAtOuterObject) { // Test case: {"a":{"b":"d","i":"abc"},"c":"d"} // Filters: {{a (outer object), {{a"i (inner string), {"c (outer string) // Scenario: inner "i" filter triggers first and succeeds, // then outer "a" object filter triggers and returns PAUSED, // on resume, inner "i" should be SKIPPED, // outer "a" receives only remaining data (closing brace) std::string testJson = R"({"a":{"b":"d","i":"abc"},"c":"d"})"; bool firstOuterCall = true; int innerCallCount = 0; int outerCallCount = 0; std::vector<std::string> capturedInnerValues; std::vector<std::string> capturedCValues; // Inner filter for "i" string value filters["{{a\"i"] = [&innerCallCount, &capturedInnerValues](JSON* json) -> JSONSplitter::CallbackResult { innerCallCount++; std::string output; json->storeobject(&output); capturedInnerValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // Outer filter for "a" object - pause on first call // On resume, receives only the closing brace (inner already skipped) filters["{{a"] = [&firstOuterCall, &outerCallCount](JSON* json) -> JSONSplitter::CallbackResult { outerCallCount++; if (firstOuterCall) { firstOuterCall = false; return JSONSplitter::CallbackResult::PAUSED; } // On resume, just consume the closing brace return JSONSplitter::ResultFromBool(json->leaveobject()); }; // Filter for "c" string value filters["{\"c"] = [&capturedCValues](JSON* json) -> JSONSplitter::CallbackResult { std::string output; json->storeobject(&output); capturedCValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // First call: should process until outer "a" object pauses // Inner "i" should be processed before outer "a" pauses auto consumed = splitter.processChunk(&filters, testJson.c_str()); // Consumed should be at the position where outer filter starts at the "a" object EXPECT_EQ(consumed, 5); // {"a": EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(1, innerCallCount); // Inner "i" was processed once EXPECT_EQ(1, outerCallCount); // Outer "a" was called and paused EXPECT_THAT(capturedInnerValues, testing::ElementsAre("abc")); EXPECT_EQ(0, capturedCValues.size()); // "c" not processed yet // Purge consumed bytes testJson.erase(0, static_cast<size_t>(consumed)); // Second call: should resume from "a" object // Inner "i" should be SKIPPED (already processed) // Outer "a" handles the closure consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(1, innerCallCount); // Inner "i" should NOT be called again (skipped) EXPECT_EQ(2, outerCallCount); // Outer "a" called second time EXPECT_THAT(capturedCValues, testing::ElementsAre("d")); } TEST_F(JSONSplitterTest, ProcessNestedFilterWithPauseAtOuterObjectAndLeftMoreData) { // Test case: {"a":{"b":"d","b2":"abc","x":"y"},"c":"d"} // Filters: {{a (outer object), {{a"b2 (inner string), {"c (outer string) // Scenario: inner "b2" filter triggers first and succeeds, // then outer "a" object filter triggers and returns PAUSED, // on resume, inner "b2" should be SKIPPED, // outer "a" receives only remaining data (,"x":"y"}) std::string testJson = R"({"a":{"b":"d","b2":"abc","x":"y"},"c":"d"})"; bool firstOuterCall = true; int innerCallCount = 0; int outerCallCount = 0; std::vector<std::string> capturedInnerValues; std::vector<std::string> capturedCValues; // Inner filter for "i" string value filters["{{a\"b2"] = [&innerCallCount, &capturedInnerValues](JSON* json) -> JSONSplitter::CallbackResult { innerCallCount++; std::string output; json->storeobject(&output); capturedInnerValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // Outer filter for "a" object - pause on first call // On resume, receives the remaining data filters["{{a"] = [&firstOuterCall, &outerCallCount](JSON* json) -> JSONSplitter::CallbackResult { outerCallCount++; if (firstOuterCall) { firstOuterCall = false; return JSONSplitter::CallbackResult::PAUSED; } // "x":"y"},"c":"d"} // We should process "x":"y", and leave JSON at the first } std::string key, value; EXPECT_TRUE(json->storeKeyValueFromObject(key, value)); EXPECT_EQ(key, "x"); EXPECT_EQ(value, "y"); return JSONSplitter::ResultFromBool(json->leaveobject()); }; // Filter for "c" string value filters["{\"c"] = [&capturedCValues](JSON* json) -> JSONSplitter::CallbackResult { std::string output; json->storeobject(&output); capturedCValues.emplace_back(output); return JSONSplitter::CallbackResult::SUCCESS; }; // First call: should process until outer "a" object pauses // Inner "i" should be processed before outer "a" pauses auto consumed = splitter.processChunk(&filters, testJson.c_str()); // Consumed should be at the position where outer filter starts at the "a" object EXPECT_EQ(consumed, 5); // {"a": EXPECT_FALSE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(1, innerCallCount); // Inner "i" was processed once EXPECT_EQ(1, outerCallCount); // Outer "a" was called and paused EXPECT_THAT(capturedInnerValues, testing::ElementsAre("abc")); EXPECT_EQ(0, capturedCValues.size()); // "c" not processed yet // Purge consumed bytes testJson.erase(0, static_cast<size_t>(consumed)); // Second call: should resume from "a" object // Inner "i" should be SKIPPED (already processed) // Outer "a" handles remaining data and the closure consumed = splitter.processChunk(&filters, testJson.c_str()); EXPECT_EQ(consumed, static_cast<m_off_t>(testJson.length())); EXPECT_TRUE(splitter.hasFinished()); EXPECT_FALSE(splitter.hasFailed()); EXPECT_EQ(1, innerCallCount); // Inner "i" should NOT be called again (skipped) EXPECT_EQ(2, outerCallCount); // Outer "a" called second time EXPECT_THAT(capturedCValues, testing::ElementsAre("d")); } */������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/localpath_test.cpp�����������������������������������������������������������0000664�0000000�0000000�00000040135�15162662266�0020305�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file localpath_test.cpp * @brief Unit tests for the LocalPath class. * * This test suite validates various conversions and operations involving the LocalPath class, * including transformations between local file system paths and MEGA path representations. */ #include "../stdfs.h" #include "mega/logging.h" #include "megaapi.h" #include <gtest/gtest.h> #include <mega/file.h> #include <string> using namespace mega; #ifdef WIN32 static const std::string rootName = "D"; static const std::string rootDrive = rootName + ':'; static const std::string winPathPrefix = "\\\\?\\"; #else static const std::string rootName; static const std::string rootDrive; #endif static const std::string pathSep{LocalPath::localPathSeparator_utf8}; TEST(LocalPathTest, ClearLocalPathAndThenAppend) { #ifdef WIN32 std::string input{"D:\\home\\myFolder"}; #else std::string input{"/home/user/myFolder"}; #endif std::string expected{input + pathSep}; LocalPath auxLocalPath = LocalPath::fromAbsolutePath(input); LocalPath auxLocalPath2 = LocalPath::fromAbsolutePath(input); auxLocalPath2.clear(); ASSERT_FALSE(auxLocalPath.endsInSeparator()); auxLocalPath.appendWithSeparator(auxLocalPath2, true); EXPECT_EQ(auxLocalPath.toPath(false), expected); } TEST(LocalPathTest, AppendEmptyLocalPathWithSeparator) { #ifdef WIN32 std::string input{"D:\\home\\myFolder"}; #else std::string input{"/home/user/myFolder"}; #endif std::string expected{input + pathSep}; LocalPath auxLocalPath = LocalPath::fromAbsolutePath(input); ASSERT_FALSE(auxLocalPath.endsInSeparator()); auxLocalPath.appendWithSeparator(LocalPath(), true); EXPECT_EQ(auxLocalPath.toPath(false), expected); } TEST(LocalPathTest, LocalPathStrToMegaPathStr) { #ifdef WIN32 string_type input = string_type{L"D:\\home\\user\\Jos\x65\xCC\x81.txt"}; std::string expected = std::string{"D:\\home\\user\\Jose\xC3\x8C\xC2\x81.txt"}; #else std::string input = string_type{"/home/user/Jos\x65\xCC\x81.txt"}; std::string expected = std::string{"/home/user/Jos\x65\xCC\x81.txt"}; #endif std::string outputPathStr; LocalPath::local2path(&input, &outputPathStr, false); EXPECT_EQ(outputPathStr, expected); } TEST(LocalPathTest, LocalPathStrToMegaPathStrNormalized) { #ifdef WIN32 string_type input{L"D:\\home\\user\\Jos\x65\xCC\x81.txt"}; std::string expected{"D:\\home\\user\\Jose\xC3\x8C\xC2\x81.txt"}; #else std::string input{"/home/user/Jos\x65\xCC\x81.txt"}; std::string expected{"/home/user/Jos\xC3\xA9.txt"}; #endif std::string outputPathStr; LocalPath::local2path(&input, &outputPathStr, true); EXPECT_EQ(outputPathStr, expected); } TEST(LocalPathTest, MegaPathStrToLocalPathStr) { #ifdef WIN32 std::string input{"D:\\home\\user\\Jos\x65\xCC\x81.txt"}; string_type expected{L"D:\\home\\user\\Jose\x301.txt"}; #else std::string input = string_type{"/home/user/Jos\x65\xCC\x81.txt"}; std::string expected = std::string{"/home/user/Jos\x65\xCC\x81.txt"}; #endif string_type outputPathStr; LocalPath::path2local(&input, &outputPathStr); EXPECT_EQ(outputPathStr, expected); } TEST(LocalPathTest, AbsoluteLocalPathFromFileNameStr) { #ifdef WIN32 std::string input{"Jose\xC3\x8C\xC2\x81.txt"}; std::string expected{path_u8string((fs::current_path() / "Jos\x65\xCC\x81.txt"))}; #else std::string input{"Jos\x65\xCC\x81.txt"}; std::string expected{path_u8string((fs::current_path() / "Jos\x65\xCC\x81.txt"))}; #endif auto auxLocalPath = LocalPath::fromAbsolutePath(expected); auto outputLocalPath = LocalPath::fromAbsolutePath(input); EXPECT_TRUE(outputLocalPath.isAbsolute()); EXPECT_EQ(outputLocalPath, auxLocalPath); } TEST(LocalPathTest, AbsoluteLocalPathFromPreformattedLocalPathStr) { #if defined(WIN32) std::string input{"D:\\home\\user\\Jose\x65\xCC\x81.txt"}; std::string expected{"D:\\home\\user\\Jose\x65\xCC\x81.txt"}; #elif defined(__MACH__) std::string input{"/home/user/Jos\xC3\xA9.txt"}; std::string expected{"/home/user/Jose\xCC\x81.txt"}; #else std::string input{"/home/user/Jos\xC3\xA9.txt"}; std::string expected{"/home/user/Jos\xC3\xA9.txt"}; #endif auto outputLocalPath = LocalPath::fromAbsolutePath(input); EXPECT_TRUE(outputLocalPath.isAbsolute()); EXPECT_EQ(outputLocalPath.toPath(false), expected); } TEST(LocalPathTest, RelativeLocalPathFromPreformattedLocalPathStr) { #if defined(WIN32) std::string input{"Jose\x65\xCC\x81.txt"}; std::string expected{"Jose\x65\xCC\x81.txt"}; #elif defined(__MACH__) std::string input{"Jos\xC3\xA9.txt"}; std::string expected{"Jose\xCC\x81.txt"}; #else std::string input{"Jos\xC3\xA9.txt"}; std::string expected{"Jos\xC3\xA9.txt"}; #endif auto outputLocalPath = LocalPath::fromRelativePath(input); EXPECT_FALSE(outputLocalPath.isAbsolute()); EXPECT_EQ(outputLocalPath.toPath(false), expected); } TEST(LocalPathTest, AbsolutePathFromPlatformEncodedStr) { #if defined(WIN32) std::wstring input{L"D:\\home\\user\\leaf.txt"}; std::string expected{"D:\\home\\user\\leaf.txt"}; #else std::string input{"/home/userleaf.txt"}; std::string expected = input; #endif auto outputLocalPath = LocalPath::fromPlatformEncodedAbsolute(std::move(input)); EXPECT_EQ(outputLocalPath.toPath(false), expected); } TEST(LocalPathTest, Clear) { auto localPath = LocalPath::fromAbsolutePath("/home/user/Jos\x65\xCC\x81.txt"); auto checkLocalPath = LocalPath::fromRelativePath("Jos\x65\xCC\x81.txt"); EXPECT_FALSE(localPath.empty()); EXPECT_EQ(localPath.leafName(), checkLocalPath); localPath.clear(); EXPECT_FALSE(localPath.isAbsolute()); EXPECT_TRUE(localPath.empty()); } TEST(LocalPathTest, Append) { LocalPath localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep); auto leafLocalPath = LocalPath::fromRelativePath("folder2"); localPath.append(leafLocalPath); auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "folder2"); EXPECT_EQ(localPath, checkLocalPath); leafLocalPath = LocalPath::fromRelativePath("bar.txt"); localPath.appendWithSeparator(leafLocalPath, true); checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "folder2" + pathSep + "bar.txt"); EXPECT_EQ(localPath, checkLocalPath); } TEST(LocalPathTest, Prepend) { auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "bar.txt"); auto localPath = LocalPath::fromRelativePath("bar.txt"); auto prependPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1"); localPath.prependWithSeparator(prependPath); EXPECT_EQ(localPath, checkLocalPath); } TEST(LocalPathTest, Trim) { auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1"); auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep); localPath.trimNonDriveTrailingSeparator(); EXPECT_EQ(localPath, checkLocalPath); } TEST(LocalPathTest, ChangeLeaf) { auto newLeaf = LocalPath::fromRelativePath("newLeaf.txt"); auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "home" + pathSep + "leaf.txt"); auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "home" + pathSep + "newLeaf.txt"); localPath.changeLeaf(newLeaf); EXPECT_EQ(localPath, checkLocalPath); } TEST(LocalPathTest, ChangeSuffix) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "bar.txt"); auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "bar(1).txt"); auto auxlocalPath = localPath.insertFilenameSuffix("(1)"); EXPECT_EQ(auxlocalPath, checkLocalPath); } TEST(LocalPathTest, GetUTF8Representation) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "bar.txt"); EXPECT_EQ(localPath.toPath(false), rootDrive + pathSep + "bar.txt"); } TEST(LocalPathTest, GetLeafName) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "bar.txt"); auto checkLocalPath = LocalPath::fromRelativePath("bar.txt"); EXPECT_EQ(localPath.leafName(), checkLocalPath); } TEST(LocalPathTest, CheckIfEmpty) { LocalPath localPath; EXPECT_TRUE(localPath.empty()); } TEST(LocalPathTest, IsRootPath) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep); EXPECT_TRUE(localPath.isRootPath()); localPath = LocalPath::fromRelativePath("bar.txt"); EXPECT_FALSE(localPath.isRootPath()); } TEST(LocalPathTest, GetPatentPath) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "home" + pathSep + "bar.txt"); auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "home" + pathSep); auto auxLocalPath = localPath.parentPath(); EXPECT_EQ(auxLocalPath, checkLocalPath); } TEST(LocalPathTest, GetLeafNameByteIndex) { const std::string leaf = "bar.txt"; auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + leaf); #ifdef WIN32 EXPECT_EQ(localPath.getLeafnameByteIndex(), leaf.size()); #else EXPECT_EQ(localPath.getLeafnameByteIndex(), rootDrive.size()); #endif } TEST(LocalPathTest, GetSubPath) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "bar.txt"); auto checkLocalPath = LocalPath::fromRelativePath("bar.txt"); #ifdef WIN32 auto sublocalPath = localPath.subpathFrom(winPathPrefix.size() + rootDrive.size() + pathSep.size()); #else auto sublocalPath = localPath.subpathFrom(rootDrive.size() + pathSep.size()); #endif EXPECT_EQ(sublocalPath, checkLocalPath); } TEST(LocalPathTest, ContainsAnotherPath) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "bar.txt"); auto sublocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep); EXPECT_TRUE(sublocalPath.isContainingPathOf(localPath)); } TEST(LocalPathTest, NextPathComponent) { LocalPath nextComponent; auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "bar.txt"); #ifdef WIN32 size_t idx = winPathPrefix.size() + rootDrive.size() + pathSep.size(); #else size_t idx = rootDrive.size() + pathSep.size(); #endif localPath.nextPathComponent(idx, nextComponent); EXPECT_TRUE(localPath.hasNextPathComponent(idx)); EXPECT_EQ(nextComponent.toPath(false), "folder1"); EXPECT_TRUE(localPath.hasNextPathComponent(idx)); localPath.nextPathComponent(idx, nextComponent); EXPECT_EQ(nextComponent.toPath(false), "bar.txt"); EXPECT_FALSE(localPath.hasNextPathComponent(idx)); } TEST(LocalPathTest, GetExtention) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "bar.txt"); std::string ext = ".txt"; EXPECT_EQ(localPath.extension(), ext); EXPECT_TRUE(localPath.extension(ext)); } TEST(LocalPathTest, IsLocalPathRelated) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "bar.txt"); auto checkLocalPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep); EXPECT_TRUE(localPath.related(checkLocalPath)); EXPECT_TRUE(checkLocalPath.related(localPath)); } TEST(LocalPathTest, LocalPathToLocalPathStr) { auto localPath = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder1" + pathSep + "Jos\x65\xCC\x81.txt"); EXPECT_EQ(localPath.toPath(false), rootDrive + pathSep + "folder1" + pathSep + "Jos\x65\xCC\x81.txt"); EXPECT_EQ(localPath.toPath(true), rootDrive + pathSep + "folder1" + pathSep + "Jos\xC3\xA9.txt"); } /** * @brief Tests that the copy assignment operator works correctly. * * Regression test for a bug where operator= returned LocalPath (by value) * instead of LocalPath& (by reference), which could leave the assigned-to * object with a null mImplementation after the temporary return value was destroyed. */ TEST(LocalPathTest, CopyAssignment) { // Create source path LocalPath source = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder" + pathSep + "file.txt"); ASSERT_FALSE(source.empty()); // Test simple copy assignment LocalPath dest; dest = source; // Both source and dest should be valid after assignment EXPECT_FALSE(source.empty()) << "Source should remain valid after copy assignment"; EXPECT_FALSE(dest.empty()) << "Destination should be valid after copy assignment"; EXPECT_EQ(source, dest) << "Source and destination should be equal"; // Verify dest can still be used (this would crash with null mImplementation) EXPECT_EQ(dest.toPath(false), source.toPath(false)); EXPECT_EQ(dest.leafName(), source.leafName()); } /** * @brief Tests that chained copy assignment works correctly. * * This specifically tests the return type of operator= - it must return * LocalPath& for chained assignments to work. */ TEST(LocalPathTest, ChainedCopyAssignment) { LocalPath a = LocalPath::fromAbsolutePath(rootDrive + pathSep + "original.txt"); LocalPath b; LocalPath c; // Chained assignment: c = b = a // This requires operator= to return LocalPath& c = b = a; // All three should be valid and equal EXPECT_FALSE(a.empty()); EXPECT_FALSE(b.empty()); EXPECT_FALSE(c.empty()); EXPECT_EQ(a, b); EXPECT_EQ(b, c); // Verify all can still be used std::string expectedPath = a.toPath(false); EXPECT_EQ(b.toPath(false), expectedPath); EXPECT_EQ(c.toPath(false), expectedPath); } /** * @brief Tests that move assignment works correctly. */ TEST(LocalPathTest, MoveAssignment) { std::string originalPath = rootDrive + pathSep + "folder" + pathSep + "file.txt"; LocalPath source = LocalPath::fromAbsolutePath(originalPath); LocalPath dest; dest = std::move(source); // dest should have the original value EXPECT_FALSE(dest.empty()); EXPECT_EQ(dest.toPath(false), originalPath); } /** * @brief Tests that copy constructor works correctly. */ TEST(LocalPathTest, CopyConstructor) { LocalPath source = LocalPath::fromAbsolutePath(rootDrive + pathSep + "folder" + pathSep + "file.txt"); // Copy construct LocalPath dest(source); // Both should be valid and equal EXPECT_FALSE(source.empty()); EXPECT_FALSE(dest.empty()); EXPECT_EQ(source, dest); EXPECT_EQ(source.toPath(false), dest.toPath(false)); } /** * @brief Tests that move constructor works correctly. */ TEST(LocalPathTest, MoveConstructor) { std::string originalPath = rootDrive + pathSep + "folder" + pathSep + "file.txt"; LocalPath source = LocalPath::fromAbsolutePath(originalPath); // Move construct LocalPath dest(std::move(source)); // dest should have the original value EXPECT_FALSE(dest.empty()); EXPECT_EQ(dest.toPath(false), originalPath); } TEST(LocalPathTest, leafOrParentName) { LOG_debug << "#### Test1: get leafOrParentName for different example LocalPaths ####"; // "D:\\foo\\bar.txt" or "/foo/bar.txt" LocalPath lp = LocalPath::fromAbsolutePath(rootDrive + pathSep + "foo" + pathSep + "bar.txt"); ASSERT_EQ(lp.leafOrParentName(), "bar.txt"); // "D:\\foo\\" or "/foo/" lp = LocalPath::fromAbsolutePath(rootDrive + pathSep + "foo" + pathSep); ASSERT_EQ(lp.leafOrParentName(), "foo"); // "D:\\foo" or "/foo" lp = LocalPath::fromAbsolutePath(rootDrive + pathSep + "foo"); ASSERT_EQ(lp.leafOrParentName(), "foo"); // "D:\\" or "/" lp = LocalPath::fromAbsolutePath(rootDrive + pathSep); ASSERT_EQ(lp.leafOrParentName(), rootName); #ifdef WIN32 // "D:" lp = LocalPath::fromAbsolutePath(rootDrive); ASSERT_EQ(lp.leafOrParentName(), rootName); // "D" lp = LocalPath::fromAbsolutePath(rootName); ASSERT_EQ(lp.leafOrParentName(), rootName); #endif // ".\\foo\\" or "./foo/" lp = LocalPath::fromRelativePath(std::string(".") + pathSep + "foo" + pathSep); ASSERT_EQ(lp.leafOrParentName(), "foo"); // ".\\foo" or "./foo" lp = LocalPath::fromRelativePath(std::string(".") + pathSep + "foo"); ASSERT_EQ(lp.leafOrParentName(), "foo"); } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/main.cpp���������������������������������������������������������������������0000664�0000000�0000000�00000001350�15162662266�0016217�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include <gtest/gtest.h> int main (int argc, char *argv[]) { testing::InitGoogleTest(&argc, argv); int rc = RUN_ALL_TESTS(); return rc; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/megaclient_test.cpp����������������������������������������������������������0000664�0000000�0000000�00000004537�15162662266�0020454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/megaapp.h" #include "mega/megaclient.h" #include "sdk_test_utils.h" #include "utils.h" #include <gtest/gtest.h> #include <filesystem> using namespace mega; using namespace sdk_test; namespace { class MegaClientTest: public ::testing::Test { protected: void SetUp() override { app = std::make_shared<MegaApp>(); client = mt::makeClient(*app); } void TearDown() override { client.reset(); app.reset(); } std::shared_ptr<MegaApp> app; std::shared_ptr<MegaClient> client; handle testHandle = 0x1234; }; TEST_F(MegaClientTest, isValidLocalSyncRoot_OK) { const fs::path dirPath = fs::current_path() / "megaclient_test_valid_local_sync_root"; LocalTempDir tempDir(dirPath); LocalPath localPath = LocalPath::fromAbsolutePath(path_u8string(dirPath)); const auto [err, sErr, sWarn] = client->isValidLocalSyncRoot(localPath, testHandle); EXPECT_EQ(err, API_OK); EXPECT_EQ(sErr, NO_SYNC_ERROR); EXPECT_EQ(sWarn, NO_SYNC_WARNING); } TEST_F(MegaClientTest, isValidLocalSyncRoot_NotAbsolutePath) { const fs::path relPath = fs::path("relative") / "path" / "to" / "dir"; LocalPath localPath = LocalPath::fromRelativePath(path_u8string(relPath)); const auto [err, sErr, sWarn] = client->isValidLocalSyncRoot(localPath, testHandle); EXPECT_EQ(err, API_EARGS); EXPECT_EQ(sErr, NO_SYNC_ERROR); EXPECT_EQ(sWarn, NO_SYNC_WARNING); } TEST_F(MegaClientTest, isValidLocalSyncRoot_NonExistentPath) { const fs::path dirPath = fs::current_path() / "megaclient_test_non_existent_path"; LocalPath localPath = LocalPath::fromAbsolutePath(path_u8string(dirPath)); const auto [err, sErr, sWarn] = client->isValidLocalSyncRoot(localPath, testHandle); EXPECT_EQ(err, API_ENOENT); EXPECT_EQ(sErr, LOCAL_PATH_UNAVAILABLE); EXPECT_EQ(sWarn, NO_SYNC_WARNING); } TEST_F(MegaClientTest, isValidLocalSyncRoot_NotAFolder) { const fs::path filePath = fs::current_path() / "megaclient_test_not_a_folder.txt"; LocalTempFile tempFile(filePath, "Temporary file content"); LocalPath localPath = LocalPath::fromAbsolutePath(path_u8string(filePath)); const auto [err, sErr, sWarn] = client->isValidLocalSyncRoot(localPath, testHandle); EXPECT_EQ(err, API_EACCESS); EXPECT_EQ(sErr, INVALID_LOCAL_TYPE); EXPECT_EQ(sWarn, NO_SYNC_WARNING); } } // namespace�����������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/name_collision_test.cpp������������������������������������������������������0000664�0000000�0000000�00000016437�15162662266�0021341�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file * @brief Tests for the utilities in mega/name_collision.hpp */ #include "mega/name_collision.h" #include "mega/utils.h" #include <gtest/gtest.h> using namespace mega::ncoll; using mega::fileExtensionDotPosition; TEST(NameCollision, SplitBaseNameKindId) { using namespace std::literals; EXPECT_EQ(getBaseNameKindId("test"), (std::tuple{"test"s, ENameType::baseNameOnly, 0u})); EXPECT_EQ(getBaseNameKindId("test (1)"), (std::tuple{"test"s, ENameType::withIdSpace, 1u})); EXPECT_EQ(getBaseNameKindId("test (0)"), (std::tuple{"test"s, ENameType::withIdSpace, 0u})); EXPECT_EQ(getBaseNameKindId("test(2)"), (std::tuple{"test"s, ENameType::withIdNoSpace, 2u})); EXPECT_EQ(getBaseNameKindId("test (3)"), (std::tuple{"test "s, ENameType::withIdSpace, 3u})); EXPECT_EQ(getBaseNameKindId("t((3))"), (std::tuple{"t((3))"s, ENameType::baseNameOnly, 0u})); } TEST(NameCollision, GetDotPos) { std::string name = "test.txt"; size_t dotPos = fileExtensionDotPosition(name); ASSERT_EQ(dotPos, 4); ASSERT_EQ(name.substr(0, dotPos), "test"); ASSERT_EQ(name.substr(dotPos), ".txt"); name = "test .txt"; dotPos = fileExtensionDotPosition(name); ASSERT_EQ(dotPos, 5); ASSERT_EQ(name.substr(0, dotPos), "test "); ASSERT_EQ(name.substr(dotPos), ".txt"); name = "test."; dotPos = fileExtensionDotPosition(name); ASSERT_EQ(dotPos, 4); ASSERT_EQ(name.substr(0, dotPos), "test"); ASSERT_EQ(name.substr(dotPos), "."); name = "test"; dotPos = fileExtensionDotPosition(name); ASSERT_EQ(dotPos, 4); ASSERT_EQ(name.substr(0, dotPos), "test"); ASSERT_EQ(name.substr(dotPos), ""); } TEST(NameCollision, NextFreeIndexFromZero) { NewFreeIndexProvider p; // Initially, all free ASSERT_TRUE(p.isFree(ENameType::baseNameOnly, 0)); ASSERT_TRUE(p.isFree(ENameType::withIdSpace, 1)); ASSERT_TRUE(p.isFree(ENameType::withIdNoSpace, 1)); // Occupy the base name ASSERT_EQ(p.getNextFreeIndex(ENameType::baseNameOnly, 0), 0); ASSERT_FALSE(p.isFree(ENameType::baseNameOnly, 0)); // Occupy sequentially with space ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdSpace, 1), 1); ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdSpace, 1), 2); ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdSpace, 1), 3); // Occupy sequentially with no space ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdNoSpace, 1), 1); ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdNoSpace, 1), 2); ASSERT_EQ(p.getNextFreeIndex(ENameType::withIdNoSpace, 1), 3); } TEST(NameCollision, NextFreeIndexWithHoles) { const auto validateKind = [](const ENameType& kind) { NewFreeIndexProvider p; // Fill leaving some empty ids p.addOccupiedIndex(kind, 2); p.addOccupiedIndex(kind, 3); p.addOccupiedIndex(kind, 5); p.addOccupiedIndex(kind, 7); // Check some occupied ASSERT_FALSE(p.isFree(kind, 2)); ASSERT_FALSE(p.isFree(kind, 5)); // The wholes are free ASSERT_TRUE(p.isFree(kind, 1)); ASSERT_TRUE(p.isFree(kind, 4)); ASSERT_TRUE(p.isFree(kind, 6)); // Start getting from 1 ASSERT_EQ(p.getNextFreeIndex(kind, 1), 1); ASSERT_EQ(p.getNextFreeIndex(kind, 1), 4); ASSERT_FALSE(p.isFree(kind, 4)); // Get one form out range ASSERT_EQ(p.getNextFreeIndex(kind, 8), 8); // Continue getting from 1 ASSERT_EQ(p.getNextFreeIndex(kind, 1), 6); ASSERT_EQ(p.getNextFreeIndex(kind, 1), 9); // We can also add the 0 ASSERT_TRUE(p.isFree(kind, 0)); p.addOccupiedIndex(kind, 0); ASSERT_FALSE(p.isFree(kind, 0)); }; ASSERT_NO_FATAL_FAILURE(validateKind(ENameType::withIdSpace)); ASSERT_NO_FATAL_FAILURE(validateKind(ENameType::withIdNoSpace)); } TEST(NameCollision, SolverFromZero) { NameCollisionSolver s; // Trivial case for test ASSERT_EQ(s("test"), "test"); ASSERT_EQ(s("test"), "test (1)"); ASSERT_EQ(s("test"), "test (2)"); // Empty base name ASSERT_EQ(s(""), ""); ASSERT_EQ(s(""), " (1)"); ASSERT_EQ(s(""), " (2)"); // Empty base name no space ASSERT_EQ(s("(2)"), "(2)"); ASSERT_EQ(s("(2)"), "(3)"); ASSERT_EQ(s("(0)"), "(0)"); ASSERT_EQ(s("(0)"), "(1)"); ASSERT_EQ(s("(0)"), "(4)"); // Space at the end of the name ASSERT_EQ(s("test "), "test "); ASSERT_EQ(s("test "), "test (1)"); // Number stick to the name ASSERT_EQ(s("test(1)"), "test(1)"); ASSERT_EQ(s("test(1)"), "test(2)"); ASSERT_EQ(s("test(1)"), "test(3)"); // We can add files with the 0 ASSERT_EQ(s("test(0)"), "test(0)"); ASSERT_EQ(s("test(0)"), "test(4)"); } TEST(NameCollision, SolverFromExistingNoNumbers) { NameCollisionSolver s({"test", "foo", "test "}); ASSERT_EQ(s("test"), "test (1)"); ASSERT_EQ(s("test"), "test (2)"); ASSERT_EQ(s("foo"), "foo (1)"); ASSERT_EQ(s("test "), "test (1)"); } TEST(NameCollision, SolverFromExistingWithNumbers) { NameCollisionSolver s({"test", "test (1)", "test (3)", "foo"}); // If it exists we get the next number available ASSERT_EQ(s("test (3)"), "test (4)"); ASSERT_EQ(s("test (3)"), "test (5)"); ASSERT_EQ(s("test"), "test (2)"); ASSERT_EQ(s("test"), "test (6)"); ASSERT_EQ(s("foo"), "foo (1)"); } TEST(NameCollision, FileNameSolverFromZero) { FileNameCollisionSolver s; // Trivial case for test and foo ASSERT_EQ(s("test.txt"), "test.txt"); ASSERT_EQ(s("test.txt"), "test (1).txt"); ASSERT_EQ(s("test.txt"), "test (2).txt"); // Same name different extension ASSERT_EQ(s("test.md"), "test.md"); ASSERT_EQ(s("test.md"), "test (1).md"); ASSERT_EQ(s("test.md"), "test (2).md"); ASSERT_EQ(s(".txt"), ".txt"); ASSERT_EQ(s(".txt"), " (1).txt"); ASSERT_EQ(s(".txt"), " (2).txt"); // No extension (should be supported) ASSERT_EQ(s("foo"), "foo"); ASSERT_EQ(s("foo"), "foo (1)"); ASSERT_EQ(s("foo"), "foo (2)"); // Space at the end of the base name ASSERT_EQ(s("test .txt"), "test .txt"); ASSERT_EQ(s("test .txt"), "test (1).txt"); // Space at the end of the extension ASSERT_EQ(s("test.txt "), "test.txt "); ASSERT_EQ(s("test.txt "), "test (1).txt "); // Number stick to the name ASSERT_EQ(s("test(1).txt"), "test(1).txt"); ASSERT_EQ(s("test(1).txt"), "test(2).txt"); ASSERT_EQ(s("test(1).txt"), "test(3).txt"); // Zero ASSERT_EQ(s("test(0).txt"), "test(0).txt"); ASSERT_EQ(s("test(0).txt"), "test(4).txt"); } TEST(NameCollision, FileNameSolverFromExistingNoNumbers) { FileNameCollisionSolver s({"test.txt", "foo", "test.md"}); ASSERT_EQ(s("test.txt"), "test (1).txt"); ASSERT_EQ(s("test.txt"), "test (2).txt"); ASSERT_EQ(s("foo"), "foo (1)"); ASSERT_EQ(s("test.md"), "test (1).md"); } TEST(NameCollision, FileNameSolverFromExistingWithNumbers) { FileNameCollisionSolver s({"test.txt", "test (1).txt", "test (3).txt", "foo", "test (1).md"}); // If it exists we get the next number available ASSERT_EQ(s("test (3).txt"), "test (4).txt"); ASSERT_EQ(s("test (3).txt"), "test (5).txt"); ASSERT_EQ(s("test.txt"), "test (2).txt"); ASSERT_EQ(s("test.txt"), "test (6).txt"); ASSERT_EQ(s("foo"), "foo (1)"); ASSERT_EQ(s("test.md"), "test.md"); ASSERT_EQ(s("test.md"), "test (2).md"); } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/proxy_test.cpp���������������������������������������������������������������0000664�0000000�0000000�00000007704�15162662266�0017524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/proxy.h" #include "mega/utils.h" #include <gtest/gtest.h> using ::mega::Proxy; namespace mega { std::ostream& operator<<(std::ostream& os, const ::mega::Proxy& proxy) { os << "[" << "type=" << proxy.getProxyType() << ", url=" << proxy.getProxyURL() << ", user=" << proxy.getUsername() << ", password=" << proxy.getPassword() << "]"; return os; } } TEST(Proxy, NoHostURLReturnsDefaultProxy) { ASSERT_EQ(Proxy{}, Proxy::parseFromURL("http://:122")); } TEST(Proxy, EmptyStringReturnsDefaultProxy) { ASSERT_EQ(Proxy{}, Proxy::parseFromURL("")); } TEST(Proxy, ParseURLWithoutCredential) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("https://example.com"); } ASSERT_EQ(expected, Proxy::parseFromURL("https://example.com")); } TEST(Proxy, ParseURLWithPort) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("https://example.com:1010"); } ASSERT_EQ(expected, Proxy::parseFromURL("https://example.com:1010")); } TEST(Proxy, ParseURLWithCrendential) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setCredentials("user", "pass"); expected.setProxyURL("https://example.com:1010"); } ASSERT_EQ(expected, Proxy::parseFromURL("https://user:pass@example.com:1010")); } TEST(Proxy, SocksSchemeIsSupported) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("socks5h://example.com"); } ASSERT_EQ(expected, Proxy::parseFromURL("socks5h://example.com")); } TEST(Proxy, SchemeIsGuessed) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("http://example.com"); } ASSERT_EQ(expected, Proxy::parseFromURL("example.com")); } TEST(Proxy, IncompleteCrendentialIsNeglected) { Proxy expected; { expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("https://example.com:1010"); } ASSERT_EQ(expected, Proxy::parseFromURL("https://user@example.com:1010")); } #if !defined(WIN32) && !defined(__APPLE__) && !(__ANDROID__) using mega::Utils; class EnvRestorer { public: EnvRestorer(const std::string& name); ~EnvRestorer(); private: std::string mName; std::string mValue; bool mHasValue{false}; }; EnvRestorer::EnvRestorer(const std::string& name): mName{name} { std::tie(mValue, mHasValue) = Utils::getenv(mName); } EnvRestorer::~EnvRestorer() { if (!mHasValue) Utils::unsetenv(mName); else Utils::setenv(mName, mValue); } TEST(Proxy, GetProxyFromEnv) { EnvRestorer http_proxy_restorer{"http_proxy"}; EnvRestorer HTTP_PROXY_restorer{"HTTP_PROXY"}; EnvRestorer https_proxy_restorer{"https_proxy"}; EnvRestorer HTTPS_PROXY_restorer{"HTTPS_PROXY"}; // unset Utils::unsetenv("http_proxy"); Utils::unsetenv("HTTP_PROXY"); Utils::unsetenv("https_proxy"); Utils::unsetenv("HTTPS_PROXY"); // No environment is set, default is returned Proxy expected{}; Proxy proxy{}; mega::getEnvProxy(&proxy); ASSERT_EQ(expected, proxy); // https_proxy is set, get from https_proxy Utils::setenv("https_proxy", "https://example3.com"); expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("https://example3.com"); mega::getEnvProxy(&proxy); ASSERT_EQ(expected, proxy); // https_proxy, HTTP_PROXY is set. HTTP_PROXY takes priority Utils::setenv("HTTP_PROXY", "http://example2.com"); expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("http://example2.com"); mega::getEnvProxy(&proxy); ASSERT_EQ(expected, proxy); // https_proxy, HTTP_PROXY and http_proxy is set. http_proxy takes prority Utils::setenv("http_proxy", "http://example1.com"); expected.setProxyType(Proxy::CUSTOM); expected.setProxyURL("http://example1.com"); mega::getEnvProxy(&proxy); ASSERT_EQ(expected, proxy); } #endif ������������������������������������������������������������sdk-10.11.0/tests/unit/pwm_file_parser_test.cpp�����������������������������������������������������0000664�0000000�0000000�00000020466�15162662266�0021521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/pwm_file_parser.h" #include "sdk_test_utils.h" #include <gmock/gmock.h> #include <gtest/gtest.h> using namespace mega::pwm::import; using testing::HasSubstr; using testing::Not; TEST(PWMImportGooglePasswordCSVFile, WellFormatedFile) { constexpr std::string_view fileContents{R"(name,url,username,password,note foo.com,https://foo.com/,tx,"hola""""\""\"".,,", hello.co,https://hello.co/,hello,hello.1234,Description with ñ test.com,https://test.com/,test3,"hello.12,34", test.com,https://test.com/,txema,hel\nlo.1234,"" test2.com,https://test2.com/,test,hello.1234, ,https://nopassname.com/,test,hello.1234, HeLLO😍🤣🥰😉🥰😌🥰😋😘😌,https://m.facebook.com/,😌,123,😍HeLLO😌 )"}; const std::vector<std::vector<std::string_view>> expected{ {"foo.com", "https://foo.com/", "tx", R"(hola""\"\".,,)", ""}, {"hello.co", "https://hello.co/", "hello", "hello.1234", "Description with ñ"}, {"test.com", "https://test.com/", "test3", "hello.12,34", ""}, {"test.com", "https://test.com/", "txema", "hel\\nlo.1234", ""}, {"test2.com", "https://test2.com/", "test", "hello.1234", ""}, {"", "https://nopassname.com/", "test", "hello.1234", ""}, {"HeLLO😍🤣🥰😉🥰😌🥰😋😘😌", "https://m.facebook.com/", "😌", "123", "😍HeLLO😌"}}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_TRUE(results.mErrMsg.empty()); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::OK); size_t i = 0; ASSERT_EQ(results.mResults.size(), expected.size()); for (const auto& result: results.mResults) { EXPECT_EQ(result.mErrCode, PassEntryParseResult::ErrCode::OK); const auto& e = expected[i++]; EXPECT_EQ(result.mName, e[0]); EXPECT_EQ(result.mUrl, e[1]); EXPECT_EQ(result.mUserName, e[2]); EXPECT_EQ(result.mPassword, e[3]); EXPECT_EQ(result.mNote, e[4]); } } TEST(PWMImportGooglePasswordCSVFile, MissingHeader) { constexpr std::string_view fileContents{ R"(hello.co,https://hello.co/,hello,hello.1234,Description with ñ test2.com,https://test2.com/,test,hello.1234, )"}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: name")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: url")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: username")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: password")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: note")); ASSERT_THAT(results.mErrMsg, HasSubstr("expected to be a header with the column")); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::INVALID_HEADER); ASSERT_TRUE(results.mResults.empty()); } TEST(PWMImportGooglePasswordCSVFile, MissingColumnInHeader) { constexpr std::string_view fileContents{ R"(name,url,username,password,noteWrong hello.co,https://hello.co/,hello,hello.1234,Description with ñ test2.com,https://test2.com/,test,hello.1234, )"}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: note")); ASSERT_THAT(results.mErrMsg, Not(HasSubstr("expected to be a header with the column"))); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::MISSING_COLUMN); ASSERT_TRUE(results.mResults.empty()); } TEST(PWMImportGooglePasswordCSVFile, MissingColumnInEntry) { constexpr std::string_view fileContents{ R"(name,url,username,password,note https://hello.co/,hel"lo,hello.1234,Description with ñ test.com,https://test.com/,test3,hello.1234, )"}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_TRUE(results.mErrMsg.empty()); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::OK); ASSERT_EQ(results.mResults.size(), 2); // The first is wrong const PassEntryParseResult& first = results.mResults[0]; EXPECT_EQ(first.mErrCode, PassEntryParseResult::ErrCode::INVALID_NUM_OF_COLUMN); ASSERT_EQ(first.mOriginalContent, "https://hello.co/,hel\"lo,hello.1234,Description with ñ"); const PassEntryParseResult& second = results.mResults[1]; EXPECT_EQ(second.mErrCode, PassEntryParseResult::ErrCode::OK); EXPECT_EQ(second.mName, "test.com"); EXPECT_EQ(second.mUrl, "https://test.com/"); EXPECT_EQ(second.mUserName, "test3"); EXPECT_EQ(second.mPassword, "hello.1234"); EXPECT_EQ(second.mNote, ""); } TEST(PWMImportGooglePasswordCSVFile, AllEntriesWrong) { constexpr std::string_view fileContents{ R"(name,url,username,password,note https://hello.co/,hello,hello.1234,Description with ñ test.com,https://test.com/,hello.1234, )"}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::NO_VALID_ENTRIES); EXPECT_EQ(results.mErrMsg, "All the entries in the file were wrongly formatted"); ASSERT_EQ(results.mResults.size(), 2); // The first is wrong const PassEntryParseResult& first = results.mResults[0]; EXPECT_EQ(first.mErrCode, PassEntryParseResult::ErrCode::INVALID_NUM_OF_COLUMN); ASSERT_EQ(first.mOriginalContent, "https://hello.co/,hello,hello.1234,Description with ñ"); const PassEntryParseResult& second = results.mResults[1]; EXPECT_EQ(second.mErrCode, PassEntryParseResult::ErrCode::INVALID_NUM_OF_COLUMN); ASSERT_EQ(second.mOriginalContent, "test.com,https://test.com/,hello.1234,"); } TEST(PWMImportGooglePasswordCSVFile, CompletelyWrongFile) { constexpr std::string_view fileContents{ R"(This is the conent of a text file not a csv so this should trigger some errors. )"}; const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, fileContents}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::INVALID_HEADER); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: name")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: url")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: username")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: password")); ASSERT_THAT(results.mErrMsg, HasSubstr("column with name: note")); ASSERT_THAT(results.mErrMsg, HasSubstr("expected to be a header with the column")); } TEST(PWMImportGooglePasswordCSVFile, EmptyFile) { const std::string fname = "test.csv"; sdk_test::LocalTempFile f{fname, 0}; auto results = parseGooglePasswordCSVFile(fname); ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::INVALID_HEADER); ASSERT_THAT(results.mErrMsg, HasSubstr("File should have at least a header row")); } TEST(PWMReadImportFile, FileDoesNotExist) { const std::string fname = "test.csv"; auto results = readPasswordImportFile(fname, FileSource::GOOGLE_PASSWORD); // The file existence is checked at higher levels but a cantOpenFile should be triggered ASSERT_EQ(results.mErrCode, PassFileParseResult::ErrCode::CANT_OPEN_FILE); ASSERT_THAT(results.mErrMsg, HasSubstr("could not be opened")); } TEST(PWMReadImportFile, GooglePassword) { const std::string fname = "test.csv"; constexpr std::string_view fileContents{R"(name,url,username,password,note foo.com,https://foo.com/,tx,"hola""""\""\"".,,", hello.co,https://hello.co/,hello,hello.1234,Description with ñ test.com,https://test.com/,test3,"hello.12,34", test.com,https://test.com/,txema,hel\nlo.1234,"" test2.com,https://test2.com/,test,hello.1234, )"}; sdk_test::LocalTempFile f{fname, fileContents}; auto resultsRead = readPasswordImportFile(fname, FileSource::GOOGLE_PASSWORD); auto resultsDirect = parseGooglePasswordCSVFile(fname); ASSERT_EQ(resultsDirect.mErrMsg, resultsRead.mErrMsg); ASSERT_EQ(resultsDirect.mErrCode, resultsRead.mErrCode); ASSERT_EQ(resultsDirect.mResults.size(), resultsRead.mResults.size()); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/pwm_import_validation.cpp����������������������������������������������������0000664�0000000�0000000�00000005643�15162662266�0021713�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "mega/megaclient.h" #include "mega/name_collision.h" #include "mega/pwm_file_parser.h" #include <gmock/gmock.h> #include <gtest/gtest.h> using namespace mega::ncoll; using namespace mega::pwm::import; using namespace testing; using namespace mega; TEST(PWMImportValidator, NormalExecutionNoConflicts) { NameCollisionSolver solver{}; std::vector<PassEntryParseResult> entries{ {{}, "pas,test", "passName", "test.com", "uName", "pass", {}}, {{}, "pas,foo", "passName2", "foo.com", "uName2", "pass2", "Notes 1"}, {PassEntryParseResult::ErrCode::INVALID_NUM_OF_COLUMN, "i,num,of", {}, {}, {}, {}, {}}, {{}, "noPassword,foo.com", "noPassword", "foo.com", "name", "", "Notes 1"}, {{}, "pas,bar", "", "foo.com", "NoPasName", "pass2", "Notes 1"}, }; auto [bad, good] = MegaClient::validatePasswordEntries(std::move(entries), solver); // Check bad ASSERT_EQ(bad.size(), 3); ASSERT_THAT(bad, Contains(Pair("i,num,of", PasswordEntryError::PARSE_ERROR))); ASSERT_THAT(bad, Contains(Pair("noPassword,foo.com", PasswordEntryError::MISSING_PASSWORD))); ASSERT_THAT(bad, Contains(Pair("pas,bar", PasswordEntryError::MISSING_NAME))); // Check good ASSERT_EQ(good.size(), 2); // good 1 ASSERT_NE(good["passName"], nullptr); ASSERT_EQ(good["passName"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_URL)], "test.com"); ASSERT_EQ(good["passName"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_PWD)], "pass"); ASSERT_EQ(good["passName"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_NOTES)], ""); // good 2 ASSERT_NE(good["passName2"], nullptr); ASSERT_EQ(good["passName2"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_URL)], "foo.com"); ASSERT_EQ(good["passName2"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_NOTES)], "Notes 1"); } TEST(PWMImportValidator, WithNameCollisions) { NameCollisionSolver solver({"passName", "passName (1)", "passName (3)"}); std::vector<PassEntryParseResult> entries{ {{}, "pas,test", "passName", "test.com", "uName", "pass", {} }, {{}, "pas,foo", "passName2", "foo.com", "uName2", "pass2", "Notes 1"}, {{}, "pas,foo", "passName (2)", "foo.com", "uName2", "pass2", "Notes 1"}, }; auto [bad, good] = MegaClient::validatePasswordEntries(std::move(entries), solver); ASSERT_TRUE(bad.empty()); ASSERT_EQ(good.size(), 3); ASSERT_THAT(good, Contains(Key("passName (2)"))); ASSERT_EQ(good["passName (2)"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_URL)], "test.com"); ASSERT_THAT(good, Contains(Key("passName2"))); ASSERT_THAT(good, Contains(Key("passName (4)"))); ASSERT_EQ(good["passName (4)"]->map[AttrMap::string2nameid(MegaClient::PWM_ATTR_PASSWORD_URL)], "foo.com"); } ���������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/shared_mutex_tests.cpp�������������������������������������������������������0000664�0000000�0000000�00000012736�15162662266�0021217�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <chrono> #include <condition_variable> #include <cstdint> #include <future> #include <mutex> #include <thread> #include <gtest/gtest.h> #include <mega/common/lock.h> #include <mega/common/shared_mutex.h> namespace mega { namespace testing { // Convenience. using Clock = std::chrono::steady_clock; using Milliseconds = std::chrono::milliseconds; using TimePoint = Clock::time_point; class SharedMutexTests: public ::testing::Test { // Signaled when a function has completed execution. std::condition_variable mCV; // Serializes access to instance members. std::mutex mLock; // How many functions are currently being executed? std::uint64_t mNumFunctions; public: SharedMutexTests(): Test(), mCV(), mLock(), mNumFunctions(0) { } ~SharedMutexTests() { std::unique_lock<std::mutex> lock(mLock); // Wait for queued functions to complete. mCV.wait(lock, [&]() { return !mNumFunctions; }); } // Queue a function for execution on another thread. template<typename Ret> std::future<Ret> execute(std::function<Ret()> function) { std::lock_guard<std::mutex> guard(mLock); // Wrap the caller's function. auto wrapper = [this](std::function<Ret()>& function) { // Execute the caller's function. auto result = function(); std::lock_guard<std::mutex> guard(mLock); // Notify the fixture that the function's completed. --mNumFunctions; // Wake the fixture if necessary. mCV.notify_one(); // Return result to caller. return result; }; // wrapper function = std::bind(std::move(wrapper), std::move(function)); // Package the task for execution. auto task = std::packaged_task<Ret()>(std::move(function)); // Retrieve future so the caller can wait for the function's result. auto future = task.get_future(); // Remember that we've queued a function for execution. ++mNumFunctions; // Spawn a thread to execute the task. std::thread thread(std::move(task)); // Detach the thread so we can return immediately. thread.detach(); // Return the future to the caller. return future; } }; // SharedMutexTests using namespace common; TEST_F(SharedMutexTests, lock_fails) { SharedMutex mutex; { UniqueLock<SharedMutex> lock0(mutex, std::try_to_lock); ASSERT_TRUE(lock0); auto result = execute(std::function<bool()>([&]() { return !UniqueLock<SharedMutex>(mutex, std::try_to_lock); })); ASSERT_TRUE(result.get()); } SharedLock<SharedMutex> lock0(mutex, std::try_to_lock); ASSERT_TRUE(lock0); auto result = execute(std::function<bool()>([&]() { return !UniqueLock<SharedMutex>(mutex, std::try_to_lock); })); ASSERT_TRUE(result.get()); } TEST_F(SharedMutexTests, lock_succeeds) { SharedMutex mutex; UniqueLock<SharedMutex> lock0(mutex, std::try_to_lock); ASSERT_TRUE(lock0); UniqueLock<SharedMutex> lock1(mutex, std::try_to_lock); ASSERT_TRUE(lock1); auto result = execute(std::function<TimePoint()>([&]() { UniqueLock<SharedMutex> lock(mutex, std::defer_lock); if (lock.try_lock_for(Milliseconds(256))) return Clock::now(); return TimePoint::min(); })); std::this_thread::sleep_for(Milliseconds(32)); auto released = Clock::now(); lock0.unlock(); lock1.unlock(); auto acquired = result.get(); ASSERT_GT(acquired, released); } TEST_F(SharedMutexTests, shared_lock_fails) { SharedMutex mutex; UniqueLock<SharedMutex> lock(mutex, std::try_to_lock); ASSERT_TRUE(lock); auto result = execute(std::function<bool()>([&]() { return !SharedLock<SharedMutex>(mutex, std::try_to_lock); })); ASSERT_TRUE(result.get()); ASSERT_FALSE(SharedLock<SharedMutex>(mutex, std::try_to_lock)); } TEST_F(SharedMutexTests, shared_lock_recursive_succeeds) { SharedMutex mutex; SharedLock<SharedMutex> lock0(mutex, std::try_to_lock); ASSERT_TRUE(lock0); SharedLock<SharedMutex> lock1(mutex, std::try_to_lock); ASSERT_TRUE(lock1); } TEST_F(SharedMutexTests, shared_lock_succeeds) { SharedMutex mutex; SharedLock<SharedMutex> lock(mutex, std::try_to_lock); ASSERT_TRUE(lock); auto result = execute(std::function<TimePoint()>([&]() { SharedLock<SharedMutex> lock(mutex, std::try_to_lock); if (lock) return Clock::now(); return TimePoint::max(); })); std::this_thread::sleep_for(Milliseconds(32)); auto acquired = result.get(); ASSERT_LE(acquired, Clock::now()); } TEST_F(SharedMutexTests, to_shared_lock_succeeds) { SharedMutex mutex; UniqueLock<SharedMutex> lock0(mutex, std::try_to_lock); ASSERT_TRUE(lock0); auto result = execute(std::function<TimePoint()>( [&]() { SharedLock<SharedMutex> lock(mutex, std::defer_lock); if (lock.try_lock_for(Milliseconds(256))) return Clock::now(); return TimePoint::min(); })); std::this_thread::sleep_for(Milliseconds(32)); auto released = Clock::now(); auto lock1 = lock0.to_shared_lock(); ASSERT_TRUE(lock1); auto acquired = result.get(); ASSERT_GE(acquired, released); lock1.unlock(); ASSERT_TRUE(lock0.try_lock()); } } // testing } // mega ����������������������������������sdk-10.11.0/tests/unit/totp_test.cpp����������������������������������������������������������������0000664�0000000�0000000�00000012637�15162662266�0017332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file * @brief Tests for the contents of totp.h file */ #include "mega/logging.h" #include "mega/totp.h" #include "megaapi.h" #include <gtest/gtest.h> #include <chrono> #include <string> using namespace mega; using namespace mega::totp; using namespace std::chrono; struct TotpTestRow { seconds timeSinceEpoch; std::string expectedResult; HashAlgorithm algorithm; /** * @brief Return the secret used in Appendix B in https://www.rfc-editor.org/rfc/rfc6238 * There the secrets are in hex. Here they are translated to base32 */ std::string getSecretInRFC() const { switch (algorithm) { case HashAlgorithm::SHA1: return "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; case HashAlgorithm::SHA256: return "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA===="; case HashAlgorithm::SHA512: return "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3" "TQOJQGEZDGNBVGY3TQOJQGEZDGNA="; } return ""; } }; /** * @brief Test the cases presented in Appendix B in https://www.rfc-editor.org/rfc/rfc6238 */ TEST(GenerateTOTP, RFC6238TestVector) { static const std::vector<TotpTestRow> testsVectors{ {59s, "94287082", HashAlgorithm::SHA1}, {59s, "46119246", HashAlgorithm::SHA256}, {59s, "90693936", HashAlgorithm::SHA512}, {1111111109s, "07081804", HashAlgorithm::SHA1}, {1111111109s, "68084774", HashAlgorithm::SHA256}, {1111111109s, "25091201", HashAlgorithm::SHA512}, {1111111111s, "14050471", HashAlgorithm::SHA1}, {1111111111s, "67062674", HashAlgorithm::SHA256}, {1111111111s, "99943326", HashAlgorithm::SHA512}, {1234567890s, "89005924", HashAlgorithm::SHA1}, {1234567890s, "91819424", HashAlgorithm::SHA256}, {1234567890s, "93441116", HashAlgorithm::SHA512}, {2000000000s, "69279037", HashAlgorithm::SHA1}, {2000000000s, "90698825", HashAlgorithm::SHA256}, {2000000000s, "38618901", HashAlgorithm::SHA512}, {20000000000s, "65353130", HashAlgorithm::SHA1}, {20000000000s, "77737706", HashAlgorithm::SHA256}, {20000000000s, "47863826", HashAlgorithm::SHA512}}; for (const auto& tv: testsVectors) { const auto [totp, expirationTime] = generateTOTP(tv.getSecretInRFC(), tv.timeSinceEpoch, 8, 30s, tv.algorithm); EXPECT_EQ(totp, tv.expectedResult); EXPECT_EQ(expirationTime.count(), 30 - tv.timeSinceEpoch.count() % 30); } } TEST(GenerateTOTP, PreconditionsFailure) { EXPECT_EQ(generateTOTP("").first, "") << "Empty shared secret"; EXPECT_EQ(generateTOTP("GEZDGN==BVGY3TQOJQGEZDGNBVGY3TQOJQ").first, "") << "Padding in between the secret"; EXPECT_EQ(generateTOTP("AAAAA0").first, "") << "Invalid character (0)"; EXPECT_EQ(generateTOTP("GEZDGN", 5).first, "") << "Less digits than allowed"; EXPECT_EQ(generateTOTP("GEZDGN", 11).first, "") << "More digits than allowed"; EXPECT_EQ(generateTOTP("GEZDGN", 6, -5s).first, "") << "Negative time step"; EXPECT_EQ(generateTOTP("GEZDGN", 6, 0s).first, "") << "Zero time step"; EXPECT_EQ(generateTOTP("GEZDGN", 6, 30s, HashAlgorithm::SHA1, system_clock::now(), system_clock::now() - 5s) .first, "") << "tEval lower than t0"; EXPECT_EQ(generateTOTP("GEZDGN", -5s).first, "") << "Negative time delta"; } using TotpData = MegaNode::PasswordNodeData::TotpData; using Validation = MegaNode::PasswordNodeData::TotpData::Validation; static std::pair<std::unique_ptr<TotpData>, std::unique_ptr<Validation>> generateData(const char* shse, const int expT, const int alg, const int nd) { std::unique_ptr<TotpData> data{TotpData::createInstance(shse, expT, alg, nd)}; std::unique_ptr<Validation> valid{data->getValidation()}; return {std::move(data), std::move(valid)}; }; TEST(GenerateTOTPFromTotpData, PreconditionsFailure) { static constexpr std::string_view logPre{"GenerateTOTPFromTotpData.PreconditionsFailure: "}; { LOG_debug << logPre << "Empty shared secret"; const auto [data, valid] = generateData("", -1, -1, -1); EXPECT_TRUE(valid->sharedSecretExist()); EXPECT_FALSE(valid->sharedSecretValid()); // Validate defaults in first case EXPECT_TRUE(valid->nDigitsValid()); EXPECT_TRUE(valid->expirationTimeValid()); EXPECT_TRUE(valid->algorithmValid()); } { LOG_debug << logPre << "Invalid shared secret"; const auto [data, valid] = generateData("GEZDGN==BVGY3TQOJQGEZDGNBVGY3TQOJQ", -1, -1, -1); EXPECT_TRUE(valid->sharedSecretExist()); EXPECT_FALSE(valid->sharedSecretValid()); } { LOG_debug << logPre << "Invalid expiration time"; const auto [data, valid] = generateData("GEZDGN", 0, -1, -1); EXPECT_FALSE(valid->expirationTimeValid()); } { LOG_debug << logPre << "Invalid hash algorithm"; const auto [data, valid] = generateData("GEZDGN", -1, 50, -1); EXPECT_FALSE(valid->algorithmValid()); } { LOG_debug << logPre << "Invalid digits"; const auto [data, valid] = generateData("GEZDGN", -1, -1, 5); EXPECT_FALSE(valid->nDigitsValid()); } } �������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/uripath_test.cpp�������������������������������������������������������������0000664�0000000�0000000�00000013672�15162662266�0020020�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file uripath_test.cpp * @brief Unit tests for the PathURI class. */ #include "../stdfs.h" #include "mega/logging.h" #include <gtest/gtest.h> #include <mega/file.h> #include <regex> #include <string> using namespace std; using namespace mega; #ifdef WIN32 static const string rootName = "D"; static const string rootDrive = rootName + ':'; static const string winPathPrefix = "\\\\?\\"; static const string_type uriBase{L"content://com.android.externalstorage.documents"}; static const string_type uriLeaf1{L"folder1"}; static const string_type uriLeaf2{L"file.txt"}; #else static const string_type uriBase{"content://com.android.externalstorage.documents"}; static const string_type uriLeaf1{"folder1"}; #endif static const std::string auxUriBase{"content://com.android.externalstorage.documents"}; static const std::string auxUriLeaf1{"folder1"}; static const std::string auxUriLeaf2{"file.txt"}; static const std::string pathSep{LocalPath::localPathSeparator_utf8}; static const std::string uriPathSep{LocalPath::uriPathSeparator_utf8}; /** * @brief UnitTests implementation to handle URIs */ class MEGA_API TestPlatformURIHelper: public PlatformURIHelper { public: bool isURI(const string_type& path) override { std::string aux; LocalPath::local2path(&path, &aux, false); static const std::regex uriRegex(R"(^[a-zA-Z][a-zA-Z\d+\-.]*://.+$)"); return std::regex_match(aux, uriRegex); } std::optional<string_type> getName(const string_type&) override { assert(false); return std::nullopt; } std::optional<string_type> getParentURI(const string_type&) override { assert(false); return std::nullopt; } std::optional<string_type> getPath(const string_type&) override { assert(false); return std::nullopt; } std::optional<string_type> getURI(const string_type&, const std::vector<string_type>) override { assert(false); return std::nullopt; } private: TestPlatformURIHelper() { URIHandler::setPlatformHelper(this); } ~TestPlatformURIHelper() override {} static TestPlatformURIHelper mPlatformHelper; }; TestPlatformURIHelper TestPlatformURIHelper::mPlatformHelper; TEST(UriPathTest, isURI) { const std::map<std::string, bool> testURIs = { // URIs {"content://com.android.externalstorage.documents/document/primary%3ADownload%2Ffile.pdf", true}, {"content://media/external/images/media/12345", true}, {"content://com.android.providers.downloads.documents/document/5678", true}, {"content://com.android.contacts/contacts/1", true}, {"content://com.whatsapp.provider.media/item/12345", true}, {"file:///storage/emulated/0/Download/example.txt", true}, {"file:///sdcard/Pictures/photo.jpg", true}, {"http://www.example.com/file.mp3", true}, {"https://drive.google.com/uc?id=abc123", true}, {"ftp://ftp.example.com/public/file.zip", true}, // Non-URIs {"/storage/emulated/0/Download/example.txt", false}, {"/sdcard/DCIM/Camera/photo.jpg", false}, {"/mnt/sdcard/Music/song.mp3", false}, {"/data/data/com.example.app/files/config.json", false}, {"./relative/path/to/file.txt", false}, {"storage/emulated/0/Music/audio.mp3", false}, {"Downloads/file.txt", false}, {"DCIM/Camera/video.mp4", false}, {"data/user/0/com.example.app/cache/temp.tmp", false}}; for (const auto& [s, r]: testURIs) { const auto isUri = LocalPath::isURIPath(s); EXPECT_EQ(isUri, r) << s << " - isURI(" << isUri << "). Expected(" << r << ")"; } } TEST(UriPathTest, append) { auto uriPath = LocalPath::fromURIPath(uriBase); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf1), true); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf2), true); const auto expected{auxUriBase + uriPathSep + auxUriLeaf1 + uriPathSep + auxUriLeaf2}; EXPECT_EQ(uriPath.toPath(false), expected); } TEST(UriPathTest, appendRelativePathMultipleLevels) { auto uriPath = LocalPath::fromURIPath(uriBase); auto aux = LocalPath::fromRelativePath(auxUriLeaf1 + pathSep + auxUriLeaf2); uriPath.appendWithSeparator(aux, true); const auto expected{auxUriBase + uriPathSep + auxUriLeaf1 + uriPathSep + auxUriLeaf2}; EXPECT_EQ(uriPath.toPath(false), expected); } TEST(UriPathTest, getParentPath) { auto uriPath = LocalPath::fromURIPath(uriBase + uriLeaf1); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf1), true); const auto expected{auxUriBase + auxUriLeaf1}; EXPECT_EQ(uriPath.parentPath().toPath(false), expected); } TEST(UriPathTest, getLeafName) { auto uriPath = LocalPath::fromURIPath(uriBase + uriLeaf1); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf2), true); EXPECT_EQ(uriPath.leafName().toPath(false), auxUriLeaf2); EXPECT_EQ(uriPath.leafOrParentName(), auxUriLeaf2); } TEST(UriPathTest, clear) { auto uriPath = LocalPath::fromURIPath(uriBase); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf1), true); EXPECT_FALSE(uriPath.empty()); uriPath.clear(); EXPECT_TRUE(uriPath.empty()); } TEST(UriPathTest, getExtension) { auto uriPath = LocalPath::fromURIPath(uriBase); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf2), true); EXPECT_EQ(uriPath.extension(), ".txt"); } TEST(UriPathTest, insertFilenameSuffix) { auto uriPath = LocalPath::fromURIPath(uriBase); uriPath.appendWithSeparator(LocalPath::fromRelativePath(auxUriLeaf2), true); uriPath = uriPath.insertFilenameSuffix("(1)"); const auto expected{auxUriBase + uriPathSep + "file(1).txt"}; EXPECT_EQ(uriPath.toPath(false), expected); } TEST(UriPathTest, endsInSeparator) { auto uriStr = uriBase; uriStr.pop_back(); auto uriPath = LocalPath::fromURIPath(uriStr); EXPECT_TRUE(uriPath.endsInSeparator()); } ����������������������������������������������������������������������sdk-10.11.0/tests/unit/user_attributes_test.cpp�����������������������������������������������������0000664�0000000�0000000�00000027142�15162662266�0021565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2024 by Mega Limited, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "mega/user.h" #include "mega/user_attribute.h" #include <gmock/gmock-matchers.h> #include <gtest/gtest.h> #include <tuple> // attr2string class attr2stringWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, std::string>> {}; TEST_P(attr2stringWithParam, attr2string) { const auto& [at, name] = GetParam(); ASSERT_EQ(mega::User::attr2string(at), name); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, attr2stringWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, "+a"), std::make_tuple(mega::ATTR_FIRSTNAME, "firstname"), std::make_tuple(mega::ATTR_AUTHRING, "*!authring"), std::make_tuple(mega::ATTR_ED25519_PUBK, "+puEd255"))); // attr2longname class attr2longnameWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, std::string>> {}; TEST_P(attr2longnameWithParam, attr2longname) { const auto& [at, longName] = GetParam(); ASSERT_EQ(mega::User::attr2longname(at), longName); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, attr2longnameWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, "AVATAR"), std::make_tuple(mega::ATTR_FIRSTNAME, "FIRSTNAME"), std::make_tuple(mega::ATTR_AUTHRING, "AUTHRING"), std::make_tuple(mega::ATTR_ED25519_PUBK, "ED25519_PUBK"))); // string2attr class string2attrWithParam: public testing::TestWithParam<std::tuple<std::string, mega::attr_t>> {}; TEST_P(string2attrWithParam, string2attr) { const auto& [name, at] = GetParam(); ASSERT_EQ(mega::User::string2attr(name.c_str()), at); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, string2attrWithParam, testing::Values(std::make_tuple("+a", mega::ATTR_AVATAR), std::make_tuple("firstname", mega::ATTR_FIRSTNAME), std::make_tuple("*!authring", mega::ATTR_AUTHRING), std::make_tuple("+puEd255", mega::ATTR_ED25519_PUBK))); // needversioning class needversioningWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, int>> {}; TEST_P(needversioningWithParam, needversioning) { const auto& [at, ver] = GetParam(); ASSERT_EQ(mega::User::needversioning(at), ver); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, needversioningWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, 0), std::make_tuple(mega::ATTR_FIRSTNAME, 0), std::make_tuple(mega::ATTR_AUTHRING, 1), std::make_tuple(mega::ATTR_ED25519_PUBK, 1), std::make_tuple(mega::ATTR_UNKNOWN, -1))); // scope class scopeWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, char>> {}; TEST_P(scopeWithParam, scope) { const auto& [at, scope] = GetParam(); ASSERT_EQ(mega::User::scope(at), scope); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, scopeWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, '+'), std::make_tuple(mega::ATTR_FIRSTNAME, '#'), std::make_tuple(mega::ATTR_AUTHRING, '*'), std::make_tuple(mega::ATTR_ED25519_PUBK, '+'))); // isAuthring class isAuthringWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, bool>> {}; TEST_P(isAuthringWithParam, isAuthring) { const auto& [at, authring] = GetParam(); if (authring) ASSERT_TRUE(mega::User::isAuthring(at)); else ASSERT_FALSE(mega::User::isAuthring(at)); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, isAuthringWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, false), std::make_tuple(mega::ATTR_FIRSTNAME, false), std::make_tuple(mega::ATTR_AUTHRING, true), std::make_tuple(mega::ATTR_ED25519_PUBK, false))); // getMaxAttributeSize class getMaxAttributeSizeWithParam: public testing::TestWithParam<std::tuple<mega::attr_t, size_t>> {}; TEST_P(getMaxAttributeSizeWithParam, getMaxAttributeSize) { const auto& [at, attributeMaxSize] = GetParam(); ASSERT_EQ(mega::User::getMaxAttributeSize(at), attributeMaxSize); } static constexpr size_t MAX_USER_VAR_SIZE = 16 * 1024 * 1024; // 16MB - User attributes whose second character is ! or ~ (per example // *!dn, ^!keys", ...) static constexpr size_t MAX_USER_ATTRIBUTE_SIZE = 64 * 1024; // 64kB - Other user attributes INSTANTIATE_TEST_SUITE_P( UserAttributeTests, getMaxAttributeSizeWithParam, testing::Values(std::make_tuple(mega::ATTR_AVATAR, MAX_USER_ATTRIBUTE_SIZE), std::make_tuple(mega::ATTR_FIRSTNAME, MAX_USER_ATTRIBUTE_SIZE), std::make_tuple(mega::ATTR_AUTHRING, MAX_USER_VAR_SIZE), std::make_tuple(mega::ATTR_ED25519_PUBK, MAX_USER_ATTRIBUTE_SIZE))); // interfaces class InterfacesWithParam: public testing::TestWithParam<mega::attr_t> { protected: void validateValue(mega::attr_t at, const mega::UserAttribute* attribute, const std::optional<const std::string>& value) { if (!value) { ASSERT_TRUE(!attribute || attribute->isNotExisting() || (at == mega::ATTR_AVATAR && attribute->value().empty())); } else if (at == mega::ATTR_AVATAR) { ASSERT_TRUE(attribute->value().empty()) << "Found value " << attribute->value(); } else { ASSERT_EQ(attribute->value(), *value); } } std::string mEmail{"foo@bar.com"}; mega::User mUser{mEmail.c_str()}; std::string mValue1{"Foo"}; std::string mVersion1{"FHqlO7Gbl_w"}; }; TEST_P(InterfacesWithParam, SetValueAndVersion) { auto unchanged = mUser.changed; mUser.setAttribute(GetParam(), mValue1, mVersion1); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_TRUE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, mValue1)); ASSERT_EQ(attribute->version(), mVersion1); } TEST_P(InterfacesWithParam, SetValueSameVersion) { mUser.setAttribute(GetParam(), mValue1, mVersion1); std::string value2{"Bar"}; mUser.changed = {}; auto unchanged = mUser.changed; ASSERT_FALSE(mUser.updateAttributeIfDifferentVersion(GetParam(), value2, mVersion1)); ASSERT_EQ(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_TRUE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, mValue1)); ASSERT_EQ(attribute->version(), mVersion1); } TEST_P(InterfacesWithParam, SetValueDifferentVersion) { mUser.setAttribute(GetParam(), mValue1, mVersion1); std::string value2{"Bar"}; std::string version2{"FHqlO7Gbl_x"}; mUser.changed = {}; auto unchanged = mUser.changed; ASSERT_TRUE(mUser.updateAttributeIfDifferentVersion(GetParam(), value2, version2)); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_TRUE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, value2)); ASSERT_EQ(attribute->version(), version2); } TEST_P(InterfacesWithParam, SetValueEmptyVersion) { auto unchanged = mUser.changed; mUser.setAttribute(GetParam(), mValue1, {}); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_TRUE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, mValue1)); ASSERT_TRUE(attribute->version().empty()); } TEST_P(InterfacesWithParam, Invalidate) { mUser.setAttribute(GetParam(), mValue1, mVersion1); mUser.changed = {}; auto unchanged = mUser.changed; mUser.setAttributeExpired(GetParam()); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_FALSE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, mValue1)); ASSERT_EQ(attribute->version(), mVersion1); } TEST_P(InterfacesWithParam, RemoveValueUpdateVersion) { mUser.setAttribute(GetParam(), mValue1, mVersion1); mUser.changed = {}; auto unchanged = mUser.changed; std::string version2{"FHqlO7Gbl_x"}; mUser.removeAttributeUpdateVersion( GetParam(), version2); // remove value, but keep updated version and attribute as invalid ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_THAT(attribute, testing::NotNull()); ASSERT_FALSE(attribute->isValid()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, "")); ASSERT_EQ(attribute->version(), version2); } TEST_P(InterfacesWithParam, RemoveValueOwnUser) { mUser.cacheNonExistingAttributes(); mUser.setAttribute(GetParam(), mValue1, mVersion1); mUser.changed = {}; auto unchanged = mUser.changed; mUser.removeAttribute(GetParam()); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, std::nullopt)); } TEST_P(InterfacesWithParam, RemoveValueOtherUser) { mUser.setAttribute(GetParam(), mValue1, mVersion1); mUser.changed = {}; auto unchanged = mUser.changed; mUser.removeAttribute(GetParam()); ASSERT_NE(memcmp(&mUser.changed, &unchanged, sizeof(mUser.changed)), 0); const mega::UserAttribute* attribute = mUser.getAttribute(GetParam()); ASSERT_NO_FATAL_FAILURE(validateValue(GetParam(), attribute, std::nullopt)); } INSTANTIATE_TEST_SUITE_P(UserAttributeTests, InterfacesWithParam, testing::Values(mega::ATTR_AVATAR, mega::ATTR_FIRSTNAME, mega::ATTR_AUTHRING, mega::ATTR_ED25519_PUBK)); ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/utils.cpp��������������������������������������������������������������������0000664�0000000�0000000�00000005476�15162662266�0016450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "utils.h" #include <random> #include <mega/megaapp.h> #include <mega/heartbeats.h> #include <mega.h> #include "constants.h" #include "DefaultedFileSystemAccess.h" #include "FsNode.h" namespace mt { namespace { std::mt19937 gRandomGenerator{1}; ::mega::FSACCESS_CLASS g_fsa; } // anonymous mega::handle nextFsId() { static mega::handle fsId{0}; return fsId++; } std::shared_ptr<mega::MegaClient> makeClient(mega::MegaApp& app, mega::DbAccess* dbAccess) { struct HttpIo : mega::HttpIO { void addevents(mega::Waiter*, int) override {} void post(struct mega::HttpReq*, const char* = NULL, unsigned = 0) override {} void cancel(mega::HttpReq*) override {} m_off_t postpos(void*) override { return {}; } bool doio(void) override { return {}; } void setuseragent(std::string*) override {} }; auto httpio = new HttpIo; auto deleter = [httpio](mega::MegaClient* client) { delete client; delete httpio; }; using namespace mega; auto waiter = std::make_shared<WAIT_CLASS>(); std::shared_ptr<mega::MegaClient> client{ new mega::MegaClient{&app, waiter, httpio, dbAccess, nullptr, "unit_test", 0}, deleter}; return client; } mega::Node& makeNode(mega::MegaClient& client, const mega::nodetype_t type, mega::NodeHandle handle, mega::Node* const parent) { assert(client.nodeByHandle(handle) == nullptr); const auto ph = parent ? parent->nodeHandle() : ::mega::NodeHandle(); auto n = new mega::Node{client, handle, ph, type, -1, mega::UNDEF, nullptr, 0}; // owned by the client if (type == mega::FILENODE || type == mega::FOLDERNODE || type == mega::TYPE_UNKNOWN) { n->setkey(reinterpret_cast<const mega::byte*>(std::string((type == mega::FILENODE) ? mega::FILENODEKEYLENGTH : mega::FOLDERNODEKEYLENGTH, 'X').c_str())); } return *n; } std::uint16_t nextRandomInt() { std::uniform_int_distribution<std::uint16_t> dist{0, std::numeric_limits<std::uint16_t>::max()}; return dist(gRandomGenerator); } mega::byte nextRandomByte() { std::uniform_int_distribution<unsigned short> dist{0, std::numeric_limits<mega::byte>::max()}; return static_cast<mega::byte>(dist(gRandomGenerator)); } } // mt ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/utils.h����������������������������������������������������������������������0000664�0000000�0000000�00000002254�15162662266�0016104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <memory> #include <mega/megaclient.h> #include <mega/node.h> #include <mega/sync.h> #include <mega/filefingerprint.h> namespace mt { class FsNode; mega::handle nextFsId(); std::shared_ptr<mega::MegaClient> makeClient(mega::MegaApp& app, mega::DbAccess* dbAccess = nullptr); mega::Node& makeNode(mega::MegaClient& client, mega::nodetype_t type, mega::NodeHandle handle, mega::Node* parent = nullptr); void collectAllFsNodes(std::map<mega::LocalPath, const mt::FsNode*>& nodes, const mt::FsNode& node); std::uint16_t nextRandomInt(); mega::byte nextRandomByte(); } // mt ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tests/unit/utils_optional_test.cpp������������������������������������������������������0000664�0000000�0000000�00000022740�15162662266�0021405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/utils.h> #include <mega/utils_optional.h> using namespace mega; class HelperClass { public: HelperClass(const std::string& s): str{s} {}; std::string getStr() const { return str; } std::optional<int> toInt() const { return stringToNumber<int>(str); } private: std::string str; }; TEST(OptMonadicOp, Transform) { const std::optional<std::string> a = "hello"; const auto getSize = [](const std::string& s) -> int { return static_cast<int>(s.size()); }; // With empty auto result = std::optional<std::string>{} | transform(getSize); static_assert(std::is_same_v<decltype(result), std::optional<int>>); EXPECT_FALSE(result.has_value()); // With lvalues result = a | transform(getSize); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 5); // With rvalues result = std::move(a) | transform(std::move(getSize)); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 5); // With class method auto fromClass = std::optional<HelperClass>(std::string{"6"}) | transform(&HelperClass::getStr); EXPECT_EQ(fromClass, std::optional<std::string>("6")); fromClass = std::optional<HelperClass>() | transform(&HelperClass::getStr); EXPECT_EQ(fromClass, std::optional<std::string>()); } TEST(OptMonadicOp, OrElse) { const std::optional<std::string> a = "hello"; const auto getEmpty = [] { return std::optional{std::string{"EMPTY"}}; }; // With empty auto result = std::optional<std::string>{} | or_else(getEmpty); static_assert(std::is_same_v<decltype(result), std::remove_const_t<decltype(a)>>); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), "EMPTY"); // With lvalues result = a | or_else(getEmpty); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), "hello"); // With rvalues result = std::move(a) | or_else(std::move(getEmpty)); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), "hello"); } TEST(OptMonadicOp, AndThen) { const std::optional<std::string> a = "5"; const auto toInt = [](const std::string& s) -> std::optional<int> { return stringToNumber<int>(s); }; // With empty auto result = std::optional<std::string>{} | and_then(toInt); static_assert(std::is_same_v<decltype(result), std::optional<int>>); EXPECT_FALSE(result.has_value()); // With lvalues result = a | and_then(toInt); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 5); // With rvalues result = std::move(a) | and_then(std::move(toInt)); EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 5); // With class method auto fromClass = std::optional<HelperClass>(std::string{"6"}) | and_then(&HelperClass::toInt); EXPECT_EQ(fromClass, std::optional<int>(6)); fromClass = std::optional<HelperClass>(std::string{"not"}) | and_then(&HelperClass::toInt); EXPECT_EQ(fromClass, std::optional<int>()); } /** * @brief Reproduction of the example at https://en.cppreference.com/w/cpp/utility/optional/and_then */ TEST(OptMonadicOp, Combined) { using namespace std::literals; const std::vector<std::optional<std::string>> v{"1234", "15 foo", "bar", "42", "5000000000", " 5", std::nullopt, "-43"}; const auto manipulate = [](auto&& o) { // Disable format to match the one in the example at cppreference // clang-format off return (o // if optional is nullopt convert it to optional with "" string | or_else([]{ return std::optional{""s}; }) // flatmap from strings to ints (making empty optionals where it fails) | and_then(stringToNumber<int>) // map int to int + 1 | transform([](int n) { return n + 1; }) // convert back to strings | transform([](int n) { return std::to_string(n); })) // replace all empty optionals that were left by // and_then and ignored by transforms with "NaN" .value_or("NaN"s); // clang-format on }; const std::vector<std::string> resultExpected{"1235", "16", "NaN", "43", "NaN", "NaN", "NaN", "-42"}; std::vector<std::string> result; result.reserve(resultExpected.size()); std::transform(begin(v), end(v), std::back_inserter(result), manipulate); EXPECT_THAT(result, testing::ElementsAreArray(resultExpected)); } TEST(OptMonadicOp, ChainingMixRvaluesAndLvalues) { std::optional<std::string> persistent = "250"; auto fallback = [] { return std::optional<std::string>{"fallback"}; }; auto result = persistent | transform( [](const std::string& s) { return s + "0"; }) // lvalue operation: "2500" | and_then( [](std::string s) -> std::optional<int> { return stringToNumber<int>(s); // converts "2500" to 2500 }) | transform( [](int n) -> int { return n / 10; }); // should yield 250 EXPECT_TRUE(result.has_value()); EXPECT_EQ(result.value(), 250); // Also test with a temporary (rvalue) empty optional to trigger the fallback. auto resultEmpty = std::optional<std::string>{} | transform( [](const std::string& s) { return s + "0"; }) | and_then( [](std::string s) -> std::optional<int> { return stringToNumber<int>(s); }) | transform( [](const int i) { return numberToString(i); }) | or_else(fallback) | transform( [](const std::string& s) -> std::string { return s + "!"; }); EXPECT_TRUE(resultEmpty.has_value()); EXPECT_EQ(resultEmpty.value(), "fallback!"); } /** * @class MoveTracker * @brief Helper struct to validate move semantics */ struct MoveTracker { int value; static int copy_count; static int move_count; MoveTracker(int v): value(v) {} MoveTracker(const MoveTracker& other): value(other.value) { ++copy_count; } MoveTracker(MoveTracker&& other) noexcept: value(other.value) { ++move_count; } MoveTracker& operator=(const MoveTracker& other) { value = other.value; ++copy_count; return *this; } MoveTracker& operator=(MoveTracker&& other) noexcept { value = other.value; ++move_count; return *this; } }; int MoveTracker::copy_count = 0; int MoveTracker::move_count = 0; TEST(OptMonadicOp, MoveSemantics) { // Reset counters to ensure a clean test. MoveTracker::copy_count = 0; MoveTracker::move_count = 0; std::optional<MoveTracker> opt{MoveTracker(100)}; auto result = std::move(opt) | and_then( [](MoveTracker&& mt) -> std::optional<MoveTracker> { mt.value += 50; return std::optional<MoveTracker>{std::move(mt)}; }) | transform( [](const MoveTracker& mt) -> int { return mt.value; }); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 150); // Validate that no copies occurred. EXPECT_EQ(MoveTracker::copy_count, 0); EXPECT_EQ(MoveTracker::move_count, 2); } TEST(OptMonadicOp, FallbackAfterConversionFailure) { const std::optional<std::string> invalid = "invalid_number"; auto fallback = [] { return std::optional<int>{-999}; }; auto result = invalid | and_then( [](const std::string& s) -> std::optional<int> { return stringToNumber<int>(s); }) | or_else(fallback) | transform( [](int n) -> std::string { return std::to_string(n); }); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result.value(), "-999"); } TEST(OptMonadicOp, NonCopyableMoveOnly) { std::optional<std::unique_ptr<int>> opt = std::make_unique<int>(42); auto result = std::move(opt) | and_then( [](std::unique_ptr<int>&& ptr) -> std::optional<std::unique_ptr<int>> { if (ptr) *ptr += 8; return std::optional<std::unique_ptr<int>>{std::move(ptr)}; }) | transform( [](const std::unique_ptr<int>& ptr) -> int { return *ptr; }); ASSERT_TRUE(result.has_value()); EXPECT_EQ(*result, 50); } ��������������������������������sdk-10.11.0/tests/unit/utils_test.cpp���������������������������������������������������������������0000664�0000000�0000000�00000154504�15162662266�0017504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * (c) 2019 by Mega Limited, Wellsford, New Zealand * * This file is part of the MEGA SDK - Client Access Engine. * * Applications using the MEGA API must present a valid application key * and comply with the the rules set forth in the Terms of Service. * * The MEGA SDK is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "megafs.h" #include <gmock/gmock.h> #include <gtest/gtest.h> #include <mega/base64.h> #include <mega/db.h> #include <mega/db/sqlite.h> #include <mega/filesystem.h> #include <mega/json.h> #include <mega/process.h> #include <mega/scoped_helpers.h> #include <mega/utils.h> #include <algorithm> #include <vector> TEST(utils, readLines) { static const std::string input = "\r" "\n" " \r" " a\r\n" "b\n" "c\r" " d \r" " \n" "efg\n"; static const std::vector<std::string> expected = { " a", "b", "c", " d ", "efg" }; std::vector<std::string> output; ASSERT_TRUE(::mega::readLines(input, output)); ASSERT_EQ(output.size(), expected.size()); ASSERT_TRUE(std::equal(expected.begin(), expected.end(), output.begin())); } TEST(Filesystem, EscapesControlCharactersIfNecessary) { using namespace mega; FSACCESS_CLASS fsAccess; // Cloud should never receive unescaped control characters. // If it does, make sure we escape accordingly. const string input("\0\r\n", 3); // Most restrictive escaping policy. { string name = input; fsAccess.escapefsincompatible(&name, FS_UNKNOWN); ASSERT_EQ(name, "%00%0d%0a"); } // Least restrictive escaping policy. { string name = input; fsAccess.escapefsincompatible(&name, FS_EXT); ASSERT_EQ(name, "%00\r\n"); } } TEST(Filesystem, EscapesReservedCharacters) { using namespace mega; // All of these characters will be escaped. string name = "\\/:?\"<>|*"; // not % anymore (for now) // Generate expected result. ostringstream osstream; for (auto character : name) { osstream << "%" << std::hex << std::setfill('0') << std::setw(2) << +character; } // Use most restrictive escaping policy. FSACCESS_CLASS fsAccess; fsAccess.escapefsincompatible(&name, FS_UNKNOWN); // Was the string correctly escaped? ASSERT_EQ(name, osstream.str()); } TEST(Filesystem, UnescapesEscapedCharacters) { using namespace mega; FSACCESS_CLASS fsAccess; // All of these characters will be escaped. string name = "%\\/:?\"<>|*"; fsAccess.escapefsincompatible(&name, FS_UNKNOWN); // Everything will be unescaped except for control characters. fsAccess.unescapefsincompatible(&name); // Was the string correctly unescaped? ASSERT_STREQ(name.c_str(), "%\\/:?\"<>|*"); } TEST(CharacterSet, IterateUtf8) { using ::mega::unicodeCodepointIterator; // Single code-unit. { auto it = unicodeCodepointIterator("abc"); EXPECT_FALSE(it.end()); EXPECT_EQ(it.get(), 'a'); EXPECT_EQ(it.get(), 'b'); EXPECT_EQ(it.get(), 'c'); EXPECT_TRUE(it.end()); EXPECT_EQ(it.get(), '\0'); } // Multiple code-unit. { auto it = unicodeCodepointIterator("q\xf0\x90\x80\x80r"); EXPECT_FALSE(it.end()); EXPECT_EQ(it.get(), 'q'); EXPECT_EQ(it.get(), 0x10000); EXPECT_EQ(it.get(), 'r'); EXPECT_TRUE(it.end()); EXPECT_EQ(it.get(), '\0'); } } TEST(CharacterSet, IterateUtf16) { using mega::unicodeCodepointIterator; // Single code-unit. { auto it = unicodeCodepointIterator(L"abc"); EXPECT_FALSE(it.end()); EXPECT_EQ(it.get(), L'a'); EXPECT_EQ(it.get(), L'b'); EXPECT_EQ(it.get(), L'c'); EXPECT_TRUE(it.end()); EXPECT_EQ(it.get(), L'\0'); } // Multiple code-unit. { auto it = unicodeCodepointIterator(L"q\xd800\xdc00r"); EXPECT_FALSE(it.end()); EXPECT_EQ(it.get(), L'q'); EXPECT_EQ(it.get(), 0x10000); EXPECT_EQ(it.get(), L'r'); EXPECT_TRUE(it.end()); EXPECT_EQ(it.get(), L'\0'); } } using namespace mega; using namespace std; // Disambiguate between Microsoft's FileSystemType. using ::mega::FileSystemType; class ComparatorTest : public ::testing::Test { public: template<typename T, typename U> int compare(const T& lhs, const U& rhs) const { return compareUtf(lhs, true, rhs, true, false); } template<typename T, typename U> int ciCompare(const T& lhs, const U& rhs) const { return compareUtf(lhs, true, rhs, true, true); } LocalPath fromAbsPath(const string& s) { return LocalPath::fromAbsolutePath(s); } LocalPath fromRelPath(const string& s) { return LocalPath::fromRelativePath(s); } }; // ComparatorTest TEST_F(ComparatorTest, CompareLocalPaths) { LocalPath lhs; LocalPath rhs; // Case insensitive { // Make sure basic characters are uppercased. lhs = fromRelPath("abc"); rhs = fromRelPath("ABC"); EXPECT_EQ(ciCompare(lhs, rhs), 0); EXPECT_EQ(ciCompare(rhs, lhs), 0); // Make sure comparison invariants are not violated. lhs = fromRelPath("abc"); rhs = fromRelPath("ABCD"); EXPECT_LT(ciCompare(lhs, rhs), 0); EXPECT_GT(ciCompare(rhs, lhs), 0); // Make sure escapes are decoded. lhs = fromRelPath("a%30b"); rhs = fromRelPath("A0B"); EXPECT_EQ(ciCompare(lhs, rhs), 0); EXPECT_EQ(ciCompare(rhs, lhs), 0); // Make sure decoded characters are uppercased. lhs = fromRelPath("%61%62%63"); rhs = fromRelPath("ABC"); EXPECT_EQ(ciCompare(lhs, rhs), 0); EXPECT_EQ(ciCompare(rhs, lhs), 0); // Invalid escapes are left as-is. lhs = fromRelPath("a%qb%"); rhs = fromRelPath("A%qB%"); EXPECT_EQ(ciCompare(lhs, rhs), 0); EXPECT_EQ(ciCompare(rhs, lhs), 0); } // Case sensitive { // Basic comparison. lhs = fromRelPath("abc"); EXPECT_EQ(compare(lhs, lhs), 0); // Make sure characters are not uppercased. rhs = fromRelPath("ABC"); EXPECT_NE(compare(lhs, rhs), 0); EXPECT_NE(compare(rhs, lhs), 0); // Make sure comparison invariants are not violated. lhs = fromRelPath("abc"); rhs = fromRelPath("abcd"); EXPECT_LT(compare(lhs, rhs), 0); EXPECT_GT(compare(rhs, lhs), 0); // Make sure escapes are decoded. lhs = fromRelPath("a%30b"); rhs = fromRelPath("a0b"); EXPECT_EQ(compare(lhs, rhs), 0); EXPECT_EQ(compare(rhs, lhs), 0); // Invalid escapes are left as-is. lhs = fromRelPath("a%qb%"); EXPECT_EQ(compare(lhs, lhs), 0); #ifdef _WIN32 // Non-UNC prefixes should be skipped. lhs = fromAbsPath("\\\\?\\C:\\"); rhs = fromAbsPath("C:\\"); EXPECT_EQ(compare(lhs, rhs), 0); EXPECT_EQ(compare(rhs, lhs), 0); lhs = fromAbsPath("\\\\.\\C:\\"); rhs = fromAbsPath("C:\\"); EXPECT_EQ(compare(lhs, rhs), 0); EXPECT_EQ(compare(rhs, lhs), 0); //// Prefixes should only be removed from absolute paths. //lhs = fromAbsPath("\\\\?\\X"); //rhs = fromAbsPath("X"); //EXPECT_NE(compare(lhs, rhs), 0); //EXPECT_NE(compare(rhs, lhs), 0); #endif // _WIN32 } // Filesystem-specific { lhs = fromRelPath("a\7%30b%31c"); rhs = fromRelPath("A%070B1C"); } } TEST_F(ComparatorTest, CompareLocalPathAgainstString) { LocalPath lhs; string rhs; // Case insensitive { // Simple comparison. lhs = fromRelPath("abc"); rhs = "ABC"; EXPECT_EQ(ciCompare(lhs, rhs), 0); // Invariants. lhs = fromRelPath("abc"); rhs = "abcd"; EXPECT_LT(ciCompare(lhs, rhs), 0); lhs = fromRelPath("abcd"); rhs = "abc"; EXPECT_GT(ciCompare(lhs, rhs), 0); // All local escapes are decoded. lhs = fromRelPath("a%30b%31c"); rhs = "A0b1C"; EXPECT_EQ(ciCompare(lhs, rhs), 0); // Escapes are uppercased. lhs = fromRelPath("%61%62%63"); rhs = "ABC"; EXPECT_EQ(ciCompare(lhs, rhs), 0); // Invalid escapes are left as-is. lhs = fromRelPath("a%qb%"); rhs = "A%QB%"; EXPECT_EQ(ciCompare(lhs, rhs), 0); } // Case sensitive { // Simple comparison. lhs = fromRelPath("abc"); rhs = "abc"; EXPECT_EQ(compare(lhs, rhs), 0); // Invariants. rhs = "abcd"; EXPECT_LT(compare(lhs, rhs), 0); lhs = fromRelPath("abcd"); rhs = "abc"; EXPECT_GT(compare(lhs, rhs), 0); // All local escapes are decoded. lhs = fromRelPath("a%30b%31c"); rhs = "a0b1c"; EXPECT_EQ(compare(lhs, rhs), 0); // Invalid escapes left as-is. lhs = fromRelPath("a%qb%r"); rhs = "a%qb%r"; EXPECT_EQ(compare(lhs, rhs), 0); #ifdef _WIN32 // Non-UNC prefixes should be skipped. lhs = fromAbsPath("\\\\?\\C:\\"); rhs = "C:\\"; EXPECT_EQ(compare(lhs, rhs), 0); EXPECT_EQ(compare(rhs, lhs), 0); lhs = fromAbsPath("\\\\.\\C:\\"); rhs = "C:\\"; EXPECT_EQ(compare(lhs, rhs), 0); EXPECT_EQ(compare(rhs, lhs), 0); //// Prefixes should only be removed from absolute paths. //lhs = fromAbsPath("\\\\?\\X"); //rhs = "X"; //EXPECT_NE(compare(lhs, rhs), 0); //EXPECT_NE(compare(rhs, lhs), 0); #endif // _WIN32 } // Filesystem-specific { lhs = fromRelPath("a\7%30b%31c"); rhs = "A%070B1C"; } } TEST(Conversion, HexVal) { // Decimal [0-9] for (int i = 0x30; i < 0x3a; ++i) { EXPECT_EQ(hexval(i), i - 0x30); } // Lowercase hexadecimal [a-f] for (int i = 0x41; i < 0x47; ++i) { EXPECT_EQ(hexval(i), i - 0x37); } // Uppercase hexadeimcal [A-F] for (int i = 0x61; i < 0x67; ++i) { EXPECT_EQ(hexval(i), i - 0x57); } } TEST(URLCodec, Escape) { string input = "abc123!@#$%^&*()"; string output; URLCodec::escape(&input, &output); EXPECT_EQ(output, "abc123%21%40%23%24%25%5e%26%2a%28%29"); string input2 = "EF字幕组 编织记忆 stitchers S02E10.mp4"; string output2; URLCodec::escape(&input2, &output2); EXPECT_EQ(output2, "EF%e5%ad%97%e5%b9%95%e7%bb%84%20%e7%bc%96%e7%bb%87%e8%ae%b0%e5%bf%86%20stitchers%20S02E10.mp4"); } TEST(URLCodec, Unescape) { string input = "a%4a%4Bc"; string output; URLCodec::unescape(&input, &output); EXPECT_EQ(output, "aJKc"); } TEST(URLCodec, UnescapeInvalidEscape) { string input; string output; // First character is invalid. input = "a%qbc"; URLCodec::unescape(&input, &output); EXPECT_EQ(output, "a%qbc"); // Second character is invalid. input = "a%bqc"; URLCodec::unescape(&input, &output); EXPECT_EQ(output, "a%bqc"); } TEST(URLCodec, UnescapeShortEscape) { string input; string output; // No hex digits. input = "a%"; URLCodec::unescape(&input, &output); EXPECT_EQ(output, "a%"); // Single hex digit. input = "a%a"; URLCodec::unescape(&input, &output); EXPECT_EQ(output, "a%a"); } TEST(Filesystem, isContainingPathOf) { using namespace mega; #ifdef _WIN32 #define SEP "\\" #else // _WIN32 #define SEP "/" #endif // ! _WIN32 LocalPath lhs; LocalPath rhs; size_t pos; // lhs does not contain rhs. constexpr const size_t sentinel = std::numeric_limits<size_t>::max(); pos = sentinel; lhs = LocalPath::fromRelativePath("a" SEP "b"); rhs = LocalPath::fromRelativePath("a" SEP "c"); EXPECT_FALSE(lhs.isContainingPathOf(rhs, &pos)); EXPECT_EQ(pos, sentinel); // lhs does not contain rhs. // they do, however, share a common prefix. pos = sentinel; lhs = LocalPath::fromRelativePath("a"); rhs = LocalPath::fromRelativePath("ab"); EXPECT_FALSE(lhs.isContainingPathOf(rhs, &pos)); EXPECT_EQ(pos, sentinel); // lhs contains rhs. // no trailing separator. pos = sentinel; lhs = LocalPath::fromRelativePath("a"); rhs = LocalPath::fromRelativePath("a" SEP "b"); EXPECT_TRUE(lhs.isContainingPathOf(rhs, &pos)); EXPECT_EQ(pos, 2u); // trailing separator. pos = sentinel; lhs = LocalPath::fromRelativePath("a" SEP); rhs = LocalPath::fromRelativePath("a" SEP "b"); EXPECT_TRUE(lhs.isContainingPathOf(rhs, &pos)); EXPECT_EQ(pos, 2u); // lhs contains itself. pos = sentinel; lhs = LocalPath::fromRelativePath("a" SEP "b"); EXPECT_TRUE(lhs.isContainingPathOf(lhs, &pos)); EXPECT_EQ(pos, 3u); #ifdef _WIN32 // case insensitive. pos = sentinel; lhs = LocalPath::fromRelativePath("a" SEP "B"); rhs = LocalPath::fromRelativePath("A" SEP "b"); EXPECT_TRUE(lhs.isContainingPathOf(rhs, &pos)); EXPECT_EQ(pos, 3u); #endif // _WIN32 #undef SEP } class SqliteDBTest : public ::testing::Test { public: SqliteDBTest() : Test() , fsAccess() , name("test") , rng() , rootPath(LocalPath::fromAbsolutePath(".")) { // Get the current path. bool result = fsAccess.cwd(rootPath); if (!result) assert(result); // Create temporary DB root path. rootPath.appendWithSeparator( LocalPath::fromRelativePath("db"), false); // Make sure our root path is clear. fsAccess.emptydirlocal(rootPath); fsAccess.rmdirlocal(rootPath); // Create root path. result = fsAccess.mkdirlocal(rootPath, false, true); if (!result) assert(result); } ~SqliteDBTest() { // Remove temporary root path. fsAccess.emptydirlocal(rootPath); bool result = fsAccess.rmdirlocal(rootPath); if (!result) assert(result); } FSACCESS_CLASS fsAccess; string name; PrnGen rng; LocalPath rootPath; }; // SqliteDBTest TEST_F(SqliteDBTest, CreateCurrent) { SqliteDbAccess dbAccess(rootPath); // Assume databases are in legacy format until proven otherwise. EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::LEGACY_DB_VERSION); // Create a new database. DbTablePtr dbTable(dbAccess.openTableWithNodes(rng, fsAccess, name, 0, nullptr)); // Was the database created successfully? ASSERT_TRUE(!!dbTable); // New databases should not be in the legacy format. EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::DB_VERSION); } TEST_F(SqliteDBTest, OpenCurrent) { // Create a dummy database. { SqliteDbAccess dbAccess(rootPath); EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::LEGACY_DB_VERSION); DbTablePtr dbTable(dbAccess.openTableWithNodes(rng, fsAccess, name, 0, nullptr)); ASSERT_TRUE(!!dbTable); EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::DB_VERSION); } // Open the database. SqliteDbAccess dbAccess(rootPath); EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::LEGACY_DB_VERSION); DbTablePtr dbTable(dbAccess.openTableWithNodes(rng, fsAccess, name, 0, nullptr)); EXPECT_TRUE(!!dbTable); EXPECT_EQ(dbAccess.currentDbVersion, DbAccess::DB_VERSION); } TEST_F(SqliteDBTest, ProbeCurrent) { SqliteDbAccess dbAccess(rootPath); // Create dummy database. { auto dbFile = dbAccess.databasePath(fsAccess, name, DbAccess::DB_VERSION); auto fileAccess = fsAccess.newfileaccess(false); EXPECT_TRUE(fileAccess->fopen(dbFile, OPEN_WRONLY, FSLogging::logOnError)); } EXPECT_TRUE(dbAccess.probe(fsAccess, name)); } TEST_F(SqliteDBTest, ProbeLegacy) { SqliteDbAccess dbAccess(rootPath); // Create dummy database. { auto dbFile = dbAccess.databasePath(fsAccess, name, DbAccess::LEGACY_DB_VERSION); auto fileAccess = fsAccess.newfileaccess(false); EXPECT_TRUE(fileAccess->fopen(dbFile, OPEN_WRONLY, FSLogging::logOnError)); } EXPECT_TRUE(dbAccess.probe(fsAccess, name)); } TEST_F(SqliteDBTest, ProbeNone) { SqliteDbAccess dbAccess(rootPath); EXPECT_FALSE(dbAccess.probe(fsAccess, name)); } TEST_F(SqliteDBTest, RootPath) { SqliteDbAccess dbAccess(rootPath); EXPECT_EQ(dbAccess.rootPath(), rootPath); } #ifdef WIN32 #define SEP "\\" #else // WIN32 #define SEP "/" #endif // ! WIN32 TEST(LocalPath, AppendWithSeparator) { LocalPath source; LocalPath target = LocalPath::fromRelativePath(""); // Doesn't add a separator if the target is empty. source = LocalPath::fromRelativePath("a"); target.appendWithSeparator(source, false); EXPECT_EQ(target.toPath(false), "a"); // Doesn't add a separator if the source begins with one. source = LocalPath::fromRelativePath(SEP "b"); target = LocalPath::fromRelativePath("a"); target.appendWithSeparator(source, true); EXPECT_EQ(target.toPath(false), "a" SEP "b"); // Doesn't add a separator if the target ends with one. source = LocalPath::fromRelativePath("b"); target = LocalPath::fromRelativePath("a" SEP); target.appendWithSeparator(source, true); EXPECT_EQ(target.toPath(false), "a" SEP "b"); // Adds a separator when: // - source doesn't begin with one. // - target doesn't end with one. target = LocalPath::fromRelativePath("a"); target.appendWithSeparator(source, true); EXPECT_EQ(target.toPath(false), "a" SEP "b"); } TEST(LocalPath, PrependWithSeparator) { LocalPath source; LocalPath target; // No separator if target is empty. source = LocalPath::fromRelativePath("b"); target.prependWithSeparator(source); EXPECT_EQ(target.toPath(false), "b"); // No separator if target begins with separator. target = LocalPath::fromRelativePath(SEP "a"); target.prependWithSeparator(source); EXPECT_EQ(target.toPath(false), "b" SEP "a"); // No separator if source ends with separator. source = LocalPath::fromRelativePath("b" SEP); target = LocalPath::fromRelativePath("a"); target.prependWithSeparator(source); EXPECT_EQ(target.toPath(false), "b" SEP "a"); } #undef SEP TEST(JSONWriter, arg_stringWithEscapes) { JSONWriter writer; writer.arg_stringWithEscapes("ke", "\"\\"); EXPECT_EQ(writer.getstring(), "\"ke\":\"\\\"\\\\\""); } TEST(JSONWriter, escape) { class Writer : public JSONWriter { public: using JSONWriter::escape; }; Writer writer; string input = "\"\\"; string expected = "\\\"\\\\"; EXPECT_EQ(writer.escape(input.c_str(), input.size()), expected); } TEST(JSON, stripWhitespace) { auto input = string(" a\rb\n c\r{\"a\":\"q\\r \\\" s\"\n} x y\n z\n"); auto expected = string("abc{\"a\":\"q\\r \\\" s\"}xyz"); auto computed = JSON::stripWhitespace(input); ASSERT_EQ(computed, expected); input = "{\"a\":\"bcde"; expected = "{\"a\":\""; computed = JSON::stripWhitespace(input); ASSERT_EQ(computed, expected); } TEST(Utils, replace_char) { ASSERT_EQ(Utils::replace(string(""), '*', '@'), ""); ASSERT_EQ(Utils::replace(string("*"), '*', '@'), "@"); ASSERT_EQ(Utils::replace(string("**"), '*', '@'), "@@"); ASSERT_EQ(Utils::replace(string("*aa"), '*', '@'), "@aa"); ASSERT_EQ(Utils::replace(string("*aa*bb*"), '*', '@'), "@aa@bb@"); ASSERT_EQ(Utils::replace(string("sd*"), '*', '@'), "sd@"); ASSERT_EQ(Utils::replace(string("*aa**bb*"), '*', '@'), "@aa@@bb@"); } TEST(Utils, replace_string) { ASSERT_EQ(Utils::replace(string(""), "*", "@"), ""); ASSERT_EQ(Utils::replace(string("*"), "*", "@"), "@"); ASSERT_EQ(Utils::replace(string("**"), "*", "@"), "@@"); ASSERT_EQ(Utils::replace(string("*aa"), "*", "@"), "@aa"); ASSERT_EQ(Utils::replace(string("*aa*bb*"), "*", "@"), "@aa@bb@"); ASSERT_EQ(Utils::replace(string("sd*"), "*", "@"), "sd@"); ASSERT_EQ(Utils::replace(string("*aa**bb*"), "*", "@"), "@aa@@bb@"); ASSERT_EQ(Utils::replace(string("*aa**bb*"), "*", "@"), "@aa@@bb@"); ASSERT_EQ(Utils::replace(string(""), "", "@"), ""); ASSERT_EQ(Utils::replace(string("abc"), "", "@"), "abc"); } TEST(Utils, NaturalSortingAscii) { // Comparison between symbols ASSERT_EQ(naturalsorting_compare("!", "!"), 0); ASSERT_GT(naturalsorting_compare("@", "!"), 0); ASSERT_LT(naturalsorting_compare("#", "$"), 0); // Comparison between symbols and numbers ASSERT_LT(naturalsorting_compare("#", "0"), 0); ASSERT_LT(naturalsorting_compare("!", "9"), 0); ASSERT_GT(naturalsorting_compare("9", "#"), 0); // Comparison between symbols and letters ASSERT_LT(naturalsorting_compare("&", "a"), 0); ASSERT_LT(naturalsorting_compare("!", "Z"), 0); ASSERT_GT(naturalsorting_compare("a", "#"), 0); // Comparison between numbers and letters ASSERT_LT(naturalsorting_compare("9", "a"), 0); ASSERT_GT(naturalsorting_compare("a", "1"), 0); ASSERT_LT(naturalsorting_compare("1", "A"), 0); // Comparison between symbols and letters (case sensitive) ASSERT_GT(naturalsorting_compare("A", "a"), 0); ASSERT_GT(naturalsorting_compare("B", "a"), 0); ASSERT_LT(naturalsorting_compare("a", "C"), 0); // Comparison between strings containing letters and numbers ASSERT_GT(naturalsorting_compare("a1", "a0"), 0); ASSERT_LT(naturalsorting_compare("a1", "a2"), 0); // Comparison between strings containing letters and symbols ASSERT_LT(naturalsorting_compare("a!", "a#"), 0); ASSERT_GT(naturalsorting_compare("a#", "a@"), 0); // Comparison between strings containing letters, numbers and symbols ASSERT_LT(naturalsorting_compare("1a!", "1a#"), 0); ASSERT_LT(naturalsorting_compare("!a1", "a1#"), 0); ASSERT_LT(naturalsorting_compare("!a1", "1#a"), 0); ASSERT_GT(naturalsorting_compare("a1!", "1a#"), 0); ASSERT_GT(naturalsorting_compare("a!1", "1a#"), 0); ASSERT_GT(naturalsorting_compare("2a!", "1a#"), 0); ASSERT_EQ(naturalsorting_compare("1a&", "1a&"), 0); // Comparison between strings with different lengths ASSERT_GT(naturalsorting_compare("abc", "ab"), 0); ASSERT_LT(naturalsorting_compare("ab", "abc"), 0); // Comparison between strings containing white spaces ASSERT_LT(naturalsorting_compare("a ", "a!"), 0); ASSERT_GT(naturalsorting_compare("a#", "a "), 0); // Comparison between numbers of different lengths ASSERT_GT(naturalsorting_compare("10", "2"), 0); ASSERT_GT(naturalsorting_compare("100", "20"), 0); // Comparison between numbers containing zeros at the beginning ASSERT_LT(naturalsorting_compare("0", "00"), 0); ASSERT_LT(naturalsorting_compare("00", "000"), 0); ASSERT_LT(naturalsorting_compare("00123", "123"), 0); ASSERT_LT(naturalsorting_compare("a0", "a00"), 0); ASSERT_LT(naturalsorting_compare("00123", "124"), 0); ASSERT_GT(naturalsorting_compare("0124", "00123"), 0); } TEST(Utils, NaturalSortingUnicode) { std::vector<std::string> names{ "11.txt", "#.txt", "中文.txt", "😼.txt", "१.txt", "一.txt", "2.txt", "cafe.txt", "file2.txt", "1.txt", "Cafe.txt", "अमन.txt.txt", "file1.txt", "cáfe.txt", "file11.txt", "test.txt", "中文1.txt", "☺️.txt", "{}.txt", }; std::vector<std::string> sortedNames{ "{}.txt", "#.txt", "☺️.txt", "😼.txt", "१.txt", "1.txt", "2.txt", "11.txt", "cafe.txt", "Cafe.txt", "cáfe.txt", "file1.txt", "file2.txt", "file11.txt", "test.txt", "अमन.txt.txt", "一.txt", "中文.txt", "中文1.txt", }; auto comp = [](const std::string& a, const std::string& b) { return naturalsorting_compare(a.c_str(), b.c_str()) < 0; }; std::stable_sort(names.begin(), names.end(), comp); ASSERT_THAT(names, testing::ElementsAreArray(sortedNames)); } TEST(RemotePath, nextPathComponent) { // Absolute path. { RemotePath path("/a/b/"); RemotePath component; size_t index = 0; ASSERT_TRUE(path.nextPathComponent(index, component)); ASSERT_EQ(component, "a"); ASSERT_TRUE(path.nextPathComponent(index, component)); ASSERT_EQ(component, "b"); ASSERT_FALSE(path.nextPathComponent(index, component)); ASSERT_TRUE(component.empty()); // Sanity. path = RemotePath("/"); index = 0; ASSERT_FALSE(path.nextPathComponent(index, component)); ASSERT_TRUE(component.empty()); } // Relative path. { RemotePath path("a/b/"); RemotePath component; size_t index = 0; ASSERT_TRUE(path.nextPathComponent(index, component)); ASSERT_EQ(component, "a"); ASSERT_TRUE(path.nextPathComponent(index, component)); ASSERT_EQ(component, "b"); ASSERT_FALSE(path.nextPathComponent(index, component)); ASSERT_TRUE(component.empty()); // Sanity. path = RemotePath(""); index = 0; ASSERT_FALSE(path.nextPathComponent(index, component)); ASSERT_TRUE(component.empty()); } } class TooLongNameTest : public ::testing::Test { public: TooLongNameTest() : Test() , mPrefixName(LocalPath::fromRelativePath("d")) , mPrefixPath() { } LocalPath Append(const LocalPath& prefix, const string& name) const { LocalPath path = prefix; path.appendWithSeparator( LocalPath::fromRelativeName(name, mFsAccess, FS_UNKNOWN), false); return path; } LocalPath AppendLongName(const LocalPath& prefix, char character) const { // Representative limit. // // True limit depends on specific filesystem. constexpr size_t MAX_COMPONENT_LENGTH = 255; string name(MAX_COMPONENT_LENGTH + 1, character); return Append(prefix, name); } bool CreateDummyFile(const LocalPath& path) { ::mega::byte data = 0x21; auto fileAccess = mFsAccess.newfileaccess(false); return fileAccess->fopen(path, OPEN_WRONLY, FSLogging::logOnError) && fileAccess->fwrite(&data, 1, 0); } void SetUp() override { // Flag should initially be clear. ASSERT_FALSE(mFsAccess.target_name_too_long); // Retrieve the current working directory. ASSERT_TRUE(mFsAccess.cwd(mPrefixPath)); // Compute absolute path to "container" directory. mPrefixPath.appendWithSeparator(mPrefixName, false); // Remove container directory. mFsAccess.emptydirlocal(mPrefixPath); mFsAccess.rmdirlocal(mPrefixPath); // Create container directory. ASSERT_TRUE(mFsAccess.mkdirlocal(mPrefixPath, false, true)); } void TearDown() override { // Destroy container directory. mFsAccess.emptydirlocal(mPrefixPath); mFsAccess.rmdirlocal(mPrefixPath); } FSACCESS_CLASS mFsAccess; LocalPath mPrefixName; LocalPath mPrefixPath; }; // TooLongNameTest TEST_F(TooLongNameTest, Copy) { // Absolute { auto source = Append(mPrefixPath, "s"); auto target = AppendLongName(mPrefixPath, 'u'); ASSERT_TRUE(CreateDummyFile(source)); ASSERT_FALSE(mFsAccess.copylocal(source, target, 0)); ASSERT_TRUE(mFsAccess.target_name_too_long); // Legitimate "bad path" error should clear the flag. target = Append(mPrefixPath, "u"); target = Append(target, "v"); ASSERT_FALSE(mFsAccess.copylocal(source, target, 0)); ASSERT_FALSE(mFsAccess.target_name_too_long); } } TEST_F(TooLongNameTest, CreateDirectory) { // Absolute { auto path = AppendLongName(mPrefixPath, 'x'); ASSERT_FALSE(mFsAccess.mkdirlocal(path, false, true)); ASSERT_TRUE(mFsAccess.target_name_too_long); // A legitimate "bad path" error should clear the flag. path = Append(mPrefixPath, "x"); path = Append(path, "y"); ASSERT_FALSE(mFsAccess.mkdirlocal(path, false, true)); ASSERT_FALSE(mFsAccess.target_name_too_long); } } TEST_F(TooLongNameTest, Rename) { // Absolute { auto source = Append(mPrefixPath, "q"); auto target = AppendLongName(mPrefixPath, 'r'); ASSERT_TRUE(mFsAccess.mkdirlocal(source, false, true)); ASSERT_FALSE(mFsAccess.renamelocal(source, target, false)); ASSERT_TRUE(mFsAccess.target_name_too_long); // Legitimate "bad path" error should clear the flag. target = Append(mPrefixPath, "u"); target = Append(target, "v"); ASSERT_FALSE(mFsAccess.renamelocal(source, target, false)); ASSERT_FALSE(mFsAccess.target_name_too_long); } } class ProcessTest : public ::testing::Test { public: ProcessTest() : Test() { } }; #ifdef WIN32 string dirCommand = "dir"; string shellCommand = "cmd"; #else string dirCommand = "ls"; string shellCommand = "sh"; #endif TEST_F(ProcessTest, Poll) { Process p; string out; string error; bool ok = p.run(vector<string>{dirCommand}, unordered_map<string, string>(), [&](const unsigned char* data, size_t len) {out.append((const char*)(data), len); }, [&](const unsigned char* data, size_t len) {error.append((const char*)(data), len); }); ASSERT_TRUE(ok) << "run failed" << endl; while (p.isAlive()) { if (!p.poll()) usleep(100000); } p.flush(); ASSERT_FALSE(out.empty()) << "no output received"; ASSERT_TRUE(error.empty()) << "error received"; } TEST_F(ProcessTest, Wait) { Process p; string out; string error; bool ok = p.run(vector<string>{dirCommand}, unordered_map<string, string>(), [&](const unsigned char* data, size_t len) {out.append((const char*)(data), len); }, [&](const unsigned char* data, size_t len) {error.append((const char*)(data), len); }); ASSERT_TRUE(ok) << "run failed" << endl; p.wait(); ASSERT_FALSE(out.empty()) << "no output received"; ASSERT_TRUE(error.empty()) << "error received"; } TEST_F(ProcessTest, RunError) { Process p; string out; string error; bool ok = p.run(vector<string>{"this-command-does-not-exist", "tmp"}, unordered_map<string, string>(), [&](const unsigned char* data, size_t len) {out.append((const char*)(data), len); }, [&](const unsigned char* data, size_t len) {error.append((const char*)(data), len); }); // ok posix // fails windows ok = p.wait(); ASSERT_FALSE(ok) << "run ok!" << endl; } TEST_F(ProcessTest, WaitNonRedirect) { Process p; bool ok = p.run(vector<string>{dirCommand}); ASSERT_TRUE(ok) << "run failed" << endl; ok = p.wait(); ASSERT_TRUE(ok) << "program failed" << endl; } TEST_F(ProcessTest, ErrorNonRedirect) { Process p; bool ok = p.run(vector<string>{dirCommand, "/file-does-not-exist"}); ASSERT_TRUE(ok) << "run failed" << endl; ok = p.wait(); ASSERT_FALSE(ok) << "program ok" << endl; } class SprintfTest : public ::testing::Test { }; TEST_F(SprintfTest, nulTerminateWhenBufferFull) { std::string countToSix("123456"); // g++ detects if we don't use a variable std::string buf(countToSix.size(), 'x'); // with macro commented out snprintf(buf.data(), 3, "%s", countToSix.data()); ASSERT_EQ(buf[0], '1'); ASSERT_EQ(buf[1], '2'); ASSERT_EQ(buf[2], '\0'); } TEST_F(SprintfTest, Multiple) { std::string buffer(7, '\x0'); std::string aToH("ABCDEFGH"); std::string countToFour("1234"); snprintf(buffer.data(), buffer.size(), "%s", countToFour.data()); snprintf(&buffer[countToFour.size()], buffer.size() - countToFour.size(), "%s", aToH.data()); ASSERT_EQ(buffer[0], '1'); ASSERT_EQ(buffer[1], '2'); ASSERT_EQ(buffer[2], '3'); ASSERT_EQ(buffer[3], '4'); ASSERT_EQ(buffer[4], 'A'); ASSERT_EQ(buffer[5], 'B'); ASSERT_EQ(buffer[6], '\0'); } TEST_F(SprintfTest, ResizeAndPrint) { unsigned int price = 120; string sprice; sprice.resize(128); snprintf(const_cast<char*>(sprice.data()), sprice.length(), "%.2f", price / 100.0); replace(sprice.begin(), sprice.end(), ',', '.'); // sprince = "1.20\0\0\0\..." ASSERT_EQ((string)sprice.c_str(), "1.20"); } TEST(extensionOf, fails_when_extension_contains_invalid_characters) { using ::mega::extensionOf; std::string computed; // Characters below '.' ASSERT_FALSE(extensionOf(std::string("a.-"), computed)); ASSERT_TRUE(computed.empty()); // Characters above 'z'. ASSERT_FALSE(extensionOf(std::string("a.{"), computed)); ASSERT_TRUE(computed.empty()); } TEST(extensionOf, fails_when_extension_isnt_present) { using ::mega::extensionOf; std::string computed; // No extension. ASSERT_FALSE(extensionOf(std::string("a"), computed)); ASSERT_TRUE(computed.empty()); // Empty string. ASSERT_FALSE(extensionOf(std::string(), computed)); ASSERT_TRUE(computed.empty()); } TEST(extensionOf, succeeds) { using ::mega::extensionOf; std::string computed; // Multicharacter extension. ASSERT_TRUE(extensionOf(std::string("a.BcD"), computed)); ASSERT_EQ(computed, ".bcd"); // Single character extension. ASSERT_TRUE(extensionOf(std::wstring(L".a"), computed)); ASSERT_EQ(computed, ".a"); // Empty extension. ASSERT_TRUE(extensionOf(std::string("."), computed)); ASSERT_EQ(computed, "."); } TEST(fromHex, fails_when_empty_string) { EXPECT_FALSE(fromHex<short>(nullptr, nullptr).second); EXPECT_FALSE(fromHex<short>("").second); } TEST(fromHex, fails_when_invalid_character) { EXPECT_FALSE(fromHex<short>('q').second); EXPECT_FALSE(fromHex<short>('_').second); } TEST(fromHex, fails_when_out_of_range) { EXPECT_FALSE(fromHex<signed char>("80").second); EXPECT_FALSE(fromHex<short>("8000").second); EXPECT_FALSE(fromHex<unsigned char>("100").second); EXPECT_FALSE(fromHex<unsigned short>("10000").second); } TEST(fromHex, succeeds) { auto s8 = fromHex<signed char>("7f"); EXPECT_TRUE(s8.second); EXPECT_EQ(s8.first, 0x7f); auto s16 = fromHex<short>("7fff"); EXPECT_TRUE(s16.second); EXPECT_EQ(s16.first, 0x7fff); auto u8 = fromHex<unsigned char>("ff"); EXPECT_TRUE(u8.second); EXPECT_EQ(u8.first, 0xff); auto u16 = fromHex<unsigned short>("ffff"); EXPECT_TRUE(u16.second); EXPECT_EQ(u16.first, 0xffff); } TEST(Split, no_delimiter) { auto input = std::string(); auto result = split(input, '.'); // Empty string. EXPECT_EQ(result.first.first, input.data()); EXPECT_FALSE(result.first.second); EXPECT_FALSE(result.second.first); EXPECT_FALSE(result.second.second); // No delimiter. input = "abc"; result = split(input, '.'); EXPECT_EQ(result.first.first, input.data()); EXPECT_EQ(result.first.second, input.size()); EXPECT_FALSE(result.second.first); EXPECT_FALSE(result.second.second); } TEST(Split, with_delimiter) { auto input = std::string("a."); auto result = split(input, '.'); // Delimiter only. EXPECT_EQ(result.first.first, input.data()); EXPECT_EQ(result.first.second, 1u); EXPECT_EQ(result.second.first, &input[1]); EXPECT_EQ(result.second.second, 1u); // Delimiter and tail. input = "abc.qrs"; result = split(input, '.'); EXPECT_EQ(result.first.first, input.data()); EXPECT_EQ(result.first.second, 3u); EXPECT_EQ(result.second.first, &input[3]); EXPECT_EQ(result.second.second, 4u); } TEST(EscapeWildCars, UseCases) { EXPECT_EQ(escapeWildCards("hello"), "hello"); EXPECT_EQ(escapeWildCards("hel*lo"), "hel\\*lo"); EXPECT_EQ(escapeWildCards("*hello*"), "\\*hello\\*"); EXPECT_EQ(escapeWildCards("\\*hello*"), "\\*hello\\*"); EXPECT_EQ(escapeWildCards("\\*hello\\*"), "\\*hello\\*"); EXPECT_EQ(escapeWildCards("hel\\\\*lo"), "hel\\\\\\*lo"); } TEST(ScopedHelpers, ScopedDestructor) { // So we can test various binding styles. struct Functor { void memberNoArguments() {} void memberWithArguments(std::string) {} static void rawNoArguments() {} static void rawWithArguments(std::string) {} }; // Functor // Make sure we can bind raw function pointers. { auto x = makeScopedDestructor(&Functor::rawNoArguments); // This also tests that convertible arguments are allowed. auto y = makeScopedDestructor(&Functor::rawWithArguments, "Test"); } // Make sure we can bind member function pointers. { auto f = Functor(); auto x = makeScopedDestructor(&Functor::memberNoArguments, &f); auto y = makeScopedDestructor(&Functor::memberWithArguments, &f, "Test"); } // Make sure we can bind lambda functions. auto x = 0; // Lambda without parameters. { auto y = makeScopedDestructor( [&x]() { ++x; }); } // Make sure the destructor was executed. EXPECT_EQ(x, 1); // Lambda with parameters. { auto y = makeScopedDestructor( [&x](int v) { x += v; }, 3); // Make sure convertible arguments are accepted. auto z = makeScopedDestructor([](std::string) {}, "Test"); } // Make sure destructor was executed. EXPECT_EQ(x, 4); } TEST(ScopedHelpers, ScopedValue) { const std::string originalValue = "before"; std::string value = originalValue; { const std::string expectedValue = "After"; // Also tests that conertible arguments are accepted. auto guard = makeScopedValue(value, expectedValue.c_str()); // Make sure value's value was changed. ASSERT_EQ(value, expectedValue); } // Make sure value's value was restored. ASSERT_EQ(value, originalValue); } TEST(ScopedHelpers, MakePtrFrom) { struct Dummy { static void destructor(Dummy* dummy) { delete dummy; } }; // Dummy // Shared pointer, default deleter. { auto x = makeSharedFrom(new Dummy); // Verify type signature. static_assert(std::is_same_v<decltype(x), std::shared_ptr<Dummy>>); } // Shared pointer, custom deleter. { auto x = makeSharedFrom(new Dummy, &Dummy::destructor); // Verify type signature. static_assert(std::is_same_v<decltype(x), std::shared_ptr<Dummy>>); // Verify deleter. auto d = std::get_deleter<void (*)(Dummy*)>(x); ASSERT_TRUE(d); EXPECT_EQ(*d, &Dummy::destructor); } // Unique pointer, default deleter. { auto x = makeUniqueFrom(new Dummy); // Verify type signature. using ComputedType = decltype(x); using ExpectedType = std::unique_ptr<Dummy, std::default_delete<Dummy>>; static_assert(std::is_same_v<ComputedType, ExpectedType>); } // Unique pointer, custom deleter. { auto x = makeUniqueFrom(new Dummy, &Dummy::destructor); using ComputedType = decltype(x); using ExpectedType = std::unique_ptr<Dummy, void (*)(Dummy*)>; static_assert(std::is_same_v<ComputedType, ExpectedType>); } } TEST(LikeCompare, ExactMatch) { ASSERT_TRUE(likeCompare("hello", "hello")); ASSERT_TRUE(likeCompare("he1lo", "he1lo")); ASSERT_TRUE(likeCompare("hélloé", "hélloé")); ASSERT_TRUE(likeCompare("你好", "你好")); ASSERT_FALSE(likeCompare("hello1", "hello")); ASSERT_FALSE(likeCompare("helo", "he1lo")); ASSERT_FALSE(likeCompare("héllo", "hélloé")); ASSERT_FALSE(likeCompare("你好", "你好!")); } TEST(LikeCompare, MatchOne) { ASSERT_TRUE(likeCompare("hell?", "hello")); ASSERT_TRUE(likeCompare("héll?é", "hélloé")); ASSERT_TRUE(likeCompare("你?", "你好")); ASSERT_FALSE(likeCompare("hello?", "hello")); ASSERT_FALSE(likeCompare("hel?o", "he1lo")); ASSERT_FALSE(likeCompare("héll?", "hélloé")); ASSERT_FALSE(likeCompare("你?", "你好!")); } TEST(LikeCompare, MatchAll) { ASSERT_TRUE(likeCompare("h*o", "hello")); ASSERT_TRUE(likeCompare("*é", "hélloé")); ASSERT_TRUE(likeCompare("*", "你好")); ASSERT_FALSE(likeCompare("he1*lo", "hello")); ASSERT_FALSE(likeCompare("*你", "你好!")); } TEST(LikeCompare, CaseInsensitiveMatch) { ASSERT_TRUE(likeCompare("HELLO", "hello")); ASSERT_TRUE(likeCompare("HÉllOé", "hélloé")); } TEST(LikeCompare, AccentInsensitiveMatch) { ASSERT_TRUE(likeCompare("HÉllOé", "HElloe")); ASSERT_TRUE(likeCompare("façade", "facade")); ASSERT_TRUE(likeCompare("nghiên", "nghiAªn")); } // \\* is \* in c++ string. It is the escaping of the character * in the pattern, which makes it // match only the single character *. TEST(LikeCompare, EscapeMatch) { ASSERT_TRUE(likeCompare("H\\*Elloe", "H*Elloe")); ASSERT_TRUE(likeCompare("\\*你*", "*你好!")); ASSERT_FALSE(likeCompare("H\\*", "H*Elloe")); ASSERT_FALSE(likeCompare("\\*你", "**你")); } TEST(LikeCompare, CombinedMatch) { ASSERT_TRUE(likeCompare("HÉ?l*e", "heLloé")); ASSERT_TRUE(likeCompare("你ç?*", "你c好!")); ASSERT_FALSE(likeCompare("HÉ?l*e\\*", "heLloé")); } TEST(NaturalSorting, Numbers) { static const std::vector<std::string> input = {"123", "0123", "00123", "234", "0234", "00234", "00", "0", "000"}; // input static const std::vector<std::string> expected = {"0", "00", "000", "00123", "0123", "123", "00234", "0234", "234"}; // expected std::vector<std::string> computed = input; std::sort(computed.begin(), computed.end(), NaturalSortingComparator()); EXPECT_EQ(computed, expected); } class CreateIdFromName: public testing::TestWithParam<uint64_t> { public: static constexpr uint64_t compileTimeSeed() { uint64_t s = 0; for (const auto c: __TIME__) { s <<= 8; s |= static_cast<uint64_t>(c); } return s; } }; TEST_P(CreateIdFromName, ValidateNewImplementation) { static constexpr uint64_t seed = CreateIdFromName::compileTimeSeed(); static constexpr string_view validChars{"!#$%&*+0123456789?^_abcdefghijklmnopqrstuvwxyz~"}; static constexpr char n[8]{validChars[seed % validChars.size()], validChars[seed * 2 % validChars.size()], validChars[seed * 3 % validChars.size()], validChars[seed * 4 % validChars.size()], validChars[seed * 5 % validChars.size()], validChars[seed * 6 % validChars.size()], validChars[seed * 7 % validChars.size()], validChars[seed * 8 % validChars.size()]}; const uint64_t nameSize = GetParam(); switch (nameSize) { case 1: { static constexpr char name[]{n[0], 0}; static_assert(makeNameid(name) == MAKENAMEID1(n[0])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID1(n[0])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID1(n[0])) << "Failed for \"" << name << '"'; break; } case 2: { static constexpr char name[]{n[0], n[1], 0}; static_assert(makeNameid(name) == MAKENAMEID2(n[0], n[1])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID2(n[0], n[1])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID2(n[0], n[1])) << "Failed for \"" << name << '"'; break; } case 3: { static constexpr char name[]{n[0], n[1], n[2], 0}; static_assert(makeNameid(name) == MAKENAMEID3(n[0], n[1], n[2])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID3(n[0], n[1], n[2])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID3(n[0], n[1], n[2])) << "Failed for \"" << name << '"'; break; } case 4: { static constexpr char name[]{n[0], n[1], n[2], n[3], 0}; static_assert(makeNameid(name) == MAKENAMEID4(n[0], n[1], n[2], n[3])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID4(n[0], n[1], n[2], n[3])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID4(n[0], n[1], n[2], n[3])) << "Failed for \"" << name << '"'; break; } case 5: { static constexpr char name[]{n[0], n[1], n[2], n[3], n[4], 0}; static_assert(makeNameid(name) == MAKENAMEID5(n[0], n[1], n[2], n[3], n[4])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID5(n[0], n[1], n[2], n[3], n[4])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID5(n[0], n[1], n[2], n[3], n[4])) << "Failed for \"" << name << '"'; break; } case 6: { static constexpr char name[]{n[0], n[1], n[2], n[3], n[4], n[5], 0}; static_assert(makeNameid(name) == MAKENAMEID6(n[0], n[1], n[2], n[3], n[4], n[5])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID6(n[0], n[1], n[2], n[3], n[4], n[5])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID6(n[0], n[1], n[2], n[3], n[4], n[5])) << "Failed for \"" << name << '"'; break; } case 7: { static constexpr char name[]{n[0], n[1], n[2], n[3], n[4], n[5], n[6], 0}; static_assert(makeNameid(name) == MAKENAMEID7(n[0], n[1], n[2], n[3], n[4], n[5], n[6])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID7(n[0], n[1], n[2], n[3], n[4], n[5], n[6])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID7(n[0], n[1], n[2], n[3], n[4], n[5], n[6])) << "Failed for \"" << name << '"'; break; } case 8: { static constexpr char name[]{n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], 0}; static_assert(makeNameid(name) == MAKENAMEID8(n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7])); ASSERT_EQ(makeNameid(string{name}), MAKENAMEID8(n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7])) << "Failed for \"" << name << '"'; static const char* constCharPtr = name; ASSERT_EQ(makeNameid(constCharPtr), MAKENAMEID8(n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7])) << "Failed for \"" << name << '"'; break; } } } INSTANTIATE_TEST_SUITE_P(NameidTests, CreateIdFromName, testing::Values(1, 2, 3, 4, 5, 6, 7, 8)); // Test class Range TEST(RangeTest, ValidRange) { // Range from 2 to 5 -> expect iteration over 2, 3, 4 Range r(2, 5); std::vector<unsigned> values(std::begin(r), std::end(r)); EXPECT_THAT(values, testing::ElementsAre(2, 3, 4)); } TEST(RangeTest, EmptyRangeWhenStartEqualsToEnd) { // Range from 5 to 5 -> empty range Range r(5, 5); EXPECT_TRUE(r.empty()); } TEST(RangeTest, EmptyRangeWhenStartGreaterThanEnd) { // Range from 6 to 5 -> empty range Range r(6, 5); EXPECT_TRUE(r.empty()); unsigned count = 0; for ([[maybe_unused]] const auto val: r) { ++count; } EXPECT_EQ(count, 0); } TEST(RangeTest, OverloadRangeToZeroStart) { // range(5) -> Range(0, 5) auto r = range(5); std::vector<unsigned> values; for (const auto val: r) { values.push_back(val); } EXPECT_THAT(values, testing::ElementsAre(0, 1, 2, 3, 4)); } TEST(RangeTest, VerifySingleElementRange) { // Range(7, 8) should iterate exactly once auto r = range(7, 8); unsigned count = 0; unsigned valueCollected = 0; for (const auto val: r) { ++count; valueCollected = val; } EXPECT_EQ(count, 1); EXPECT_EQ(valueCollected, 7u); } struct FileAccessTest: ::testing::Test { FileAccessTest(): Test(), mFSAccess(), mName(LocalPath::fromAbsolutePath("file")) {} // Called before any test in the fixture is executed. void SetUp() override { // Remove the file if it's present after a previous test run. ASSERT_TRUE(mFSAccess.unlinklocal(mName) || !mFSAccess.target_exists); } // Convenience. ::mega::FSLogging NO_LOGGING = ::mega::FSLogging::noLogging; // So we can get our hands on a FileAccess instance. FSACCESS_CLASS mFSAccess; // The name of our test file. ::mega::LocalPath mName; }; // FileAccessTest TEST_F(FileAccessTest, OpenForReadWriteSucceeds) { // So we can open a file. auto fileAccess = mFSAccess.newfileaccess(false); // Sanity. ASSERT_TRUE(fileAccess); // Opening for reading and writing should create a new file if necessary. EXPECT_TRUE(fileAccess->fopen(mName, OPEN_RDWR, NO_LOGGING)); } TEST_F(FileAccessTest, OpenEquivalence) { auto fileAccess0 = mFSAccess.newfileaccess(false); auto fileAccess1 = mFSAccess.newfileaccess(false); // Sanity. ASSERT_TRUE(fileAccess0); ASSERT_TRUE(fileAccess1); // Create a new file. ASSERT_TRUE(fileAccess0->fopen(mName, OPEN_RDWR, NO_LOGGING)); // Open an existing file. EXPECT_TRUE(fileAccess1->fopen(mName, OPEN_RDWR, NO_LOGGING)); // Convenience. auto& lhs = *fileAccess0; auto& rhs = *fileAccess1; // Make sure selected state is equivalent. EXPECT_EQ(lhs.fopenSucceeded, rhs.fopenSucceeded); EXPECT_EQ(lhs.size, rhs.size); EXPECT_EQ(lhs.mtime, rhs.mtime); EXPECT_EQ(lhs.fsid, rhs.fsid); EXPECT_EQ(lhs.type, rhs.type); EXPECT_EQ(lhs.mIsSymLink, rhs.mIsSymLink); } TEST(IP, is_valid_ipv4_address_fails) { ASSERT_FALSE(isValidIPv4Address("")); ASSERT_FALSE(isValidIPv4Address("1")); ASSERT_FALSE(isValidIPv4Address("1.2")); ASSERT_FALSE(isValidIPv4Address("1.2.3")); ASSERT_FALSE(isValidIPv4Address("::1")); } TEST(IP, is_valid_ipv4_address_succeeds) { ASSERT_TRUE(isValidIPv4Address("192.168.0.1")); } TEST(IP, is_valid_ipv6_address_fails) { ASSERT_FALSE(isValidIPv6Address("")); ASSERT_FALSE(isValidIPv6Address("192.168.0.1")); ASSERT_FALSE(isValidIPv6Address("0")); ASSERT_FALSE(isValidIPv6Address("::q")); } TEST(IP, is_valid_ipv6_address_succeeds) { ASSERT_TRUE(isValidIPv6Address("2001:db8:3333:4444:5555:6666:7777:8888")); ASSERT_TRUE(isValidIPv6Address("2001:db8::")); ASSERT_TRUE(isValidIPv6Address("::1234:5678")); ASSERT_TRUE(isValidIPv6Address("2001:db8:3333:4444:5555:6666:1.2.3.4")); ASSERT_TRUE(isValidIPv6Address("2001:db8::1234:5678:5.6.7.8")); ASSERT_TRUE(isValidIPv6Address("::11.22.33.44")); } // Using string_vector to avoid parsing problems with GTest. TEST(DNS, cache_resolved_urls_fails) { CurlHttpIO io; // Not enough IPs for each URI. EXPECT_LT(io.cacheresolvedurls(string_vector(1), string_vector(1)), 0); // Not enough URIs for each IP. EXPECT_LT(io.cacheresolvedurls(string_vector(1), string_vector(4)), 0); // Multiple URIs and no IPs. EXPECT_LT(io.cacheresolvedurls(string_vector(2), string_vector()), 0); // Make sure the cache remains empty. EXPECT_TRUE(io.getCachedDNSEntries().empty()); } TEST(DNS, cache_resolved_urls_succeeds) { CurlHttpIO io; // Expected DNS cache entries. std::map<std::string, DNSEntry> expected = {{"a.com", DNSEntry{"1.2.3.4", "::1"}}, {"b.com", DNSEntry{"2.3.4.5", "::2"}}}; // expected std::vector<std::string> uris = {"https://a.com", "https://b.com"}; std::vector<std::string> ips = {"1.2.3.4", "::1", "2.3.4.5", "::2"}; // Each URI is associated with a valid IPv4 and IPv6 address. EXPECT_EQ(io.cacheresolvedurls(uris, ips), 0); // Make sure the DNS cache was updated as expected. EXPECT_EQ(expected, io.getCachedDNSEntries()); // Each URI is associated with an invalid IP. // // a.com has an invalid IPv4 address. // b.com has an invalid IPv6 address. ips[0] = "badV4"; ips[3] = "badV6"; EXPECT_EQ(io.cacheresolvedurls(uris, ips), 2); // Make sure previously valid IPs were cleared. expected["a.com"].ipv4.clear(); expected["b.com"].ipv6.clear(); EXPECT_EQ(expected, io.getCachedDNSEntries()); // Each URI isn't associated with any valid IP address. ips[1] = ips[3]; ips[2] = ips[0]; EXPECT_EQ(io.cacheresolvedurls(uris, ips), 4); // Make sure URIs remain and that any valid IPs were cleared. expected["a.com"].ipv6.clear(); expected["b.com"].ipv4.clear(); EXPECT_EQ(expected, io.getCachedDNSEntries()); // A new URI isn't associated with any valid IP addresses. uris = {"c.com"}; ips = {"badV4", "badV6"}; EXPECT_EQ(io.cacheresolvedurls(uris, ips), 2); // Make sure no entry was added to the DNS cache. EXPECT_EQ(expected, io.getCachedDNSEntries()); // Two new URIs only have a single valid IP each. uris = {"https://d.com", "https://e.com"}; ips = {"4.5.6.7", "badV6", "badV4", "::3"}; EXPECT_EQ(io.cacheresolvedurls(uris, ips), 2); // Make sure each URI was added to the cache. expected["d.com"].ipv4 = ips[0]; expected["e.com"].ipv6 = ips[3]; EXPECT_EQ(expected, io.getCachedDNSEntries()); // Invalid URIs are skipped. uris = {"z"}; ips = {"4.3.2.1", "::4"}; EXPECT_EQ(io.cacheresolvedurls(uris, ips), 0); // Make sure the bad URI wasn't added to the cache. EXPECT_EQ(expected, io.getCachedDNSEntries()); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015000�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/CMakeLists.txt��������������������������������������������������������������0000664�0000000�0000000�00000000414�15162662266�0017537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_subdirectory(ccronexpr) add_subdirectory(csv) if(USE_LIBUV AND USE_OPENSSL) add_subdirectory(evt-tls) endif() if(NOT WIN32 AND NOT HAVE_GLOB_H) add_subdirectory(glob) endif() add_subdirectory(http_parser) add_subdirectory(utf8proc) add_subdirectory(zxcvbn-c) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/ccronexpr/������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017003�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/ccronexpr/CMakeLists.txt����������������������������������������������������0000664�0000000�0000000�00000001355�15162662266�0021547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(ccronexpr OBJECT ccronexpr.h ccronexpr.cpp ) set(CCRONEXPR_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/ccronexpr") file(MAKE_DIRECTORY "${CCRONEXPR_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/ccronexpr.h" "${CCRONEXPR_PUBLIC_INCLUDE_DIR}/ccronexpr.h" COPYONLY ) target_include_directories(ccronexpr SYSTEM PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(WIN32) set_source_files_properties( ccronexpr.cpp PROPERTIES COMPILE_FLAGS "/wd4456" # declaration hides previous local declaration ) endif() if(ENABLE_JAVA_BINDINGS) set_target_properties(ccronexpr PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/ccronexpr/LICENSE�����������������������������������������������������������0000664�0000000�0000000�00000001112�15162662266�0020003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Copyright 2015, staticlibs.net 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. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/ccronexpr/ccronexpr.cpp�����������������������������������������������������0000664�0000000�0000000�00000063252�15162662266�0021522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright 2015, alex at staticlibs.net * * 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. */ /* * File: CronExprParser.cpp * Author: alex * * Created on February 24, 2015, 9:35 AM */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <errno.h> #include <limits.h> #include <string.h> #include <math.h> #include <inttypes.h> #include <string> #include "ccronexpr.h" #define CRON_MAX_SECONDS 60 #define CRON_MAX_MINUTES 60 #define CRON_MAX_HOURS 24 #define CRON_MAX_DAYS_OF_WEEK 8 #define CRON_MAX_DAYS_OF_MONTH 32 #define CRON_MAX_MONTHS 12 #define CRON_CF_SECOND 0 #define CRON_CF_MINUTE 1 #define CRON_CF_HOUR_OF_DAY 2 #define CRON_CF_DAY_OF_WEEK 3 #define CRON_CF_DAY_OF_MONTH 4 #define CRON_CF_MONTH 5 #define CRON_CF_YEAR 6 #define CRON_CF_ARR_LEN 7 #define CRON_INVALID_INSTANT ((time_t) -1) static const char* DAYS_ARR[] = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; #define CRON_DAYS_ARR_LEN 7 static const char* MONTHS_ARR[] = { "FOO", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; #define CRON_MONTHS_ARR_LEN 13 #define CRON_MAX_STR_LEN_TO_SPLIT 256 #define CRON_MAX_NUM_TO_SRING 1000000000 /* computes number of digits in decimal number */ #define CRON_NUM_OF_DIGITS(num) ((num < 0 ? 1 : 0) + \ (abs(num) < 10 ? 1 : \ (abs(num) < 100 ? 2 : \ (abs(num) < 1000 ? 3 : \ (abs(num) < 10000 ? 4 : \ (abs(num) < 100000 ? 5 : \ (abs(num) < 1000000 ? 6 : \ (abs(num) < 10000000 ? 7 : \ (abs(num) < 100000000 ? 8 : \ (abs(num) < 1000000000 ? 9 : 10)))))))))) #ifndef _WIN32 struct tm *gmtime_r(const time_t *timep, struct tm *result); struct tm *localtime_r(const time_t *timep, struct tm *result); #endif /* Defining 'cron_mktime' to use use UTC (default) or local time */ #ifndef CRON_USE_LOCAL_TIME /* http://stackoverflow.com/a/22557778 */ #ifdef _WIN32 time_t cron_mktime(struct tm* tm) { return _mkgmtime(tm); } #else /* _WIN32 */ #ifndef ANDROID /* can be hidden in time.h */ time_t timegm(struct tm* __tp); #endif /* ANDROID */ time_t cron_mktime(struct tm* tm) { #if !defined(ANDROID) || defined(__LP64__) return timegm(tm); #else /* ANDROID */ /* https://github.com/adobe/chromium/blob/cfe5bf0b51b1f6b9fe239c2a3c2f2364da9967d7/base/os_compat_android.cc#L20 */ static const time_t kTimeMax = ~(1L << (sizeof (time_t) * CHAR_BIT - 1)); static const time_t kTimeMin = (1L << (sizeof (time_t) * CHAR_BIT - 1)); time64_t result = timegm64(tm); if (result < kTimeMin || result > kTimeMax) return -1; return result; #endif /* ANDROID */ } #endif /* _WIN32 */ #ifndef CRON_TEST_MALLOC #define cronFree(x) free(x); #define cronMalloc(x) malloc(x); #else void* cronMalloc(size_t n); void cronFree(void* p); #endif struct tm* cron_time(time_t* date, [[maybe_unused]] struct tm* out) { #ifdef __MINGW32__ return gmtime(date); #else /* __MINGW32__ */ #ifdef _WIN32 errno_t err = gmtime_s(out, date); return 0 == err ? out : NULL; #else /* _WIN32 */ return gmtime_r(date, out); #endif /* _WIN32 */ #endif /* __MINGW32__ */ } #else /* CRON_USE_LOCAL_TIME */ time_t cron_mktime(struct tm* tm) { return mktime(tm); } struct tm* cron_time(time_t* date, struct tm* out) { #ifdef _WIN32 errno_t err = localtime_s(out, date); return 0 == err ? out : NULL; #else /* _WIN32 */ return localtime_r(date, out); #endif /* _WIN32 */ } #endif /* CRON_USE_LOCAL_TIME */ void cron_set_bit(uint8_t* rbyte, int idx) { uint8_t j = (uint8_t) (idx / 8); uint8_t k = (uint8_t) (idx % 8); rbyte[j] = static_cast<uint8_t>(rbyte[j] | (1 << k)); } void cron_del_bit(uint8_t* rbyte, int idx) { uint8_t j = (uint8_t) (idx / 8); uint8_t k = (uint8_t) (idx % 8); rbyte[j] = static_cast<uint8_t>(rbyte[j] & ~(1 << k)); } uint8_t cron_get_bit(const uint8_t* rbyte, int idx) { uint8_t j = (uint8_t) (idx / 8); uint8_t k = (uint8_t) (idx % 8); if (rbyte[j] & (1 << k)) { return 1; } else { return 0; } } static void free_splitted(char** splitted, size_t len) { size_t i; if (!splitted) return; for (i = 0; i < len; i++) { if (splitted[i]) { cronFree(splitted[i]); } } cronFree(splitted); } static char* strdupl(const char* str, size_t len) { if (!str) return NULL; char* res = (char*) cronMalloc(len + 1); if (!res) return NULL; memset(res, 0, len + 1); memcpy(res, str, len); return res; } static unsigned int next_set_bit(const uint8_t* bits, unsigned int max, unsigned int from_index, int* notfound) { unsigned int i; if (!bits) { *notfound = 1; return 0; } for (i = from_index; i < max; i++) { if (cron_get_bit(bits, i)) return i; } *notfound = 1; return 0; } static void push_to_fields_arr(int* arr, int fi) { int i; if (!arr || -1 == fi) { return; } for (i = 0; i < CRON_CF_ARR_LEN; i++) { if (arr[i] == fi) return; } for (i = 0; i < CRON_CF_ARR_LEN; i++) { if (-1 == arr[i]) { arr[i] = fi; return; } } } static int add_to_field(struct tm* calendar, int field, int val) { if (!calendar || -1 == field) { return 1; } switch (field) { case CRON_CF_SECOND: calendar->tm_sec = calendar->tm_sec + val; break; case CRON_CF_MINUTE: calendar->tm_min = calendar->tm_min + val; break; case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = calendar->tm_hour + val; break; case CRON_CF_DAY_OF_WEEK: /* mkgmtime ignores this field */ case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = calendar->tm_mday + val; break; case CRON_CF_MONTH: calendar->tm_mon = calendar->tm_mon + val; break; case CRON_CF_YEAR: calendar->tm_year = calendar->tm_year + val; break; default: return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { return 1; } return 0; } /** * Reset the calendar setting all the fields provided to zero. */ static int reset(struct tm* calendar, int field) { if (!calendar || -1 == field) { return 1; } switch (field) { case CRON_CF_SECOND: calendar->tm_sec = 0; break; case CRON_CF_MINUTE: calendar->tm_min = 0; break; case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = 0; break; case CRON_CF_DAY_OF_WEEK: calendar->tm_wday = 0; break; case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = 1; break; case CRON_CF_MONTH: calendar->tm_mon = 0; break; case CRON_CF_YEAR: calendar->tm_year = 0; break; default: return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { return 1; } return 0; } static int reset_all(struct tm* calendar, int* fields) { int i; int res = 0; if (!calendar || !fields) { return 1; } for (i = 0; i < CRON_CF_ARR_LEN; i++) { if (-1 != fields[i]) { res = reset(calendar, fields[i]); if (0 != res) return res; } } return 0; } static int set_field(struct tm* calendar, int field, int val) { if (!calendar || -1 == field) { return 1; } switch (field) { case CRON_CF_SECOND: calendar->tm_sec = val; break; case CRON_CF_MINUTE: calendar->tm_min = val; break; case CRON_CF_HOUR_OF_DAY: calendar->tm_hour = val; break; case CRON_CF_DAY_OF_WEEK: calendar->tm_wday = val; break; case CRON_CF_DAY_OF_MONTH: calendar->tm_mday = val; break; case CRON_CF_MONTH: calendar->tm_mon = val; break; case CRON_CF_YEAR: calendar->tm_year = val; break; default: return 1; /* unknown field */ } time_t res = cron_mktime(calendar); if (CRON_INVALID_INSTANT == res) { return 1; } return 0; } /** * Search the bits provided for the next set bit after the value provided, * and reset the calendar. */ static unsigned int find_next(const uint8_t* bits, unsigned int max, unsigned int value, struct tm* calendar, unsigned int field, unsigned int nextField, int* lower_orders, int* res_out) { int notfound = 0; int err = 0; unsigned int next_value = next_set_bit(bits, max, value, ¬found); /* roll over if needed */ if (notfound) { err = add_to_field(calendar, nextField, 1); if (err) goto return_error; err = reset(calendar, field); if (err) goto return_error; notfound = 0; next_value = next_set_bit(bits, max, 0, ¬found); } if (notfound || next_value != value) { err = set_field(calendar, field, next_value); if (err) goto return_error; err = reset_all(calendar, lower_orders); if (err) goto return_error; } return next_value; return_error: *res_out = 1; return 0; } static unsigned int find_next_day(struct tm* calendar, const uint8_t* days_of_month, unsigned int day_of_month, const uint8_t* days_of_week, unsigned int day_of_week, int* resets, int* res_out) { int err; unsigned int count = 0; unsigned int max = 366; while ((!cron_get_bit(days_of_month, day_of_month) || !cron_get_bit(days_of_week, day_of_week)) && count++ < max) { err = add_to_field(calendar, CRON_CF_DAY_OF_MONTH, 1); if (err) goto return_error; day_of_month = calendar->tm_mday; day_of_week = calendar->tm_wday; reset_all(calendar, resets); } return day_of_month; return_error: *res_out = 1; return 0; } static int do_next(const cron_expr* expr, struct tm* calendar, unsigned int dot) { int i; int res = 0; int* resets = NULL; int* empty_list = NULL; unsigned int second = 0; unsigned int update_second = 0; unsigned int minute = 0; unsigned int update_minute = 0; unsigned int hour = 0; unsigned int update_hour = 0; unsigned int day_of_week = 0; unsigned int day_of_month = 0; unsigned int update_day_of_month = 0; unsigned int month = 0; unsigned int update_month = 0; resets = (int*) cronMalloc(CRON_CF_ARR_LEN * sizeof(int)); if (!resets) goto return_result; empty_list = (int*) cronMalloc(CRON_CF_ARR_LEN * sizeof(int)); if (!empty_list) goto return_result; for (i = 0; i < CRON_CF_ARR_LEN; i++) { resets[i] = -1; empty_list[i] = -1; } second = calendar->tm_sec; update_second = find_next(expr->seconds, CRON_MAX_SECONDS, second, calendar, CRON_CF_SECOND, CRON_CF_MINUTE, empty_list, &res); if (0 != res) goto return_result; if (second == update_second) { push_to_fields_arr(resets, CRON_CF_SECOND); } minute = calendar->tm_min; update_minute = find_next(expr->minutes, CRON_MAX_MINUTES, minute, calendar, CRON_CF_MINUTE, CRON_CF_HOUR_OF_DAY, resets, &res); if (0 != res) goto return_result; if (minute == update_minute) { push_to_fields_arr(resets, CRON_CF_MINUTE); } else { res = do_next(expr, calendar, dot); if (0 != res) goto return_result; } hour = calendar->tm_hour; update_hour = find_next(expr->hours, CRON_MAX_HOURS, hour, calendar, CRON_CF_HOUR_OF_DAY, CRON_CF_DAY_OF_WEEK, resets, &res); if (0 != res) goto return_result; if (hour == update_hour) { push_to_fields_arr(resets, CRON_CF_HOUR_OF_DAY); } else { res = do_next(expr, calendar, dot); if (0 != res) goto return_result; } day_of_week = calendar->tm_wday; day_of_month = calendar->tm_mday; update_day_of_month = find_next_day(calendar, expr->days_of_month, day_of_month, expr->days_of_week, day_of_week, resets, &res); if (0 != res) goto return_result; if (day_of_month == update_day_of_month) { push_to_fields_arr(resets, CRON_CF_DAY_OF_MONTH); } else { res = do_next(expr, calendar, dot); if (0 != res) goto return_result; } month = calendar->tm_mon; /*day already adds one if no day in same month is found*/ update_month = find_next(expr->months, CRON_MAX_MONTHS, month, calendar, CRON_CF_MONTH, CRON_CF_YEAR, resets, &res); if (0 != res) goto return_result; if (month != update_month) { if (calendar->tm_year - dot > 4) { res = -1; goto return_result; } res = do_next(expr, calendar, dot); if (0 != res) goto return_result; } goto return_result; return_result: if (!resets || !empty_list) { res = -1; } if (resets) { cronFree(resets); } if (empty_list) { cronFree(empty_list); } return res; } static int to_upper(char* str) { if (!str) return 1; int i; for (i = 0; '\0' != str[i]; i++) { str[i] = (char) toupper(str[i]); } return 0; } static char* to_string(int num) { if (abs(num) >= CRON_MAX_NUM_TO_SRING) return NULL; size_t size = CRON_NUM_OF_DIGITS(num) + 1; char* str = (char*) cronMalloc(size); if (!str) return NULL; int res = snprintf(str, size, "%d", num); if (res < 0) { cronFree(str); return NULL; } return str; } static char* string_to_char_ptr(const std::string& str) { char* result = (char*) cronMalloc(str.length() + 1); result[str.length()] = '\0'; size_t i = 0; for (auto c: str) { result[i++] = c; } return result; } static char* str_replace(char *orig, const char *rep, const char *with) { if (!orig) return nullptr; // Nothing to do size_t orig_len = strlen(orig); if (!rep) return strdupl(orig, orig_len); // We do not want to replace anything size_t rep_len = strlen(rep); if (rep_len == 0) return strdupl(orig, orig_len); const char* aux_with = with ? with : ""; size_t with_len = strlen(aux_with); std::string aux_result {orig}; size_t start_pos = 0; size_t count = 0; while ((start_pos = aux_result.find(rep, start_pos)) != std::string::npos) { ++count; start_pos += rep_len; } if (!count) return strdupl(orig, orig_len); if (rep_len < with_len) { aux_result.reserve(orig_len + (with_len - rep_len) * count + 1); } start_pos = 0; while ((start_pos = aux_result.find(rep, start_pos)) != std::string::npos) { aux_result.replace(start_pos, rep_len, with); start_pos += with_len; } return string_to_char_ptr(aux_result); } static unsigned int parse_uint(const char* str, int* errcode) { char* endptr; errno = 0; long int l = strtol(str, &endptr, 0); if (errno == ERANGE || *endptr != '\0' || l < 0 || l > INT_MAX) { *errcode = 1; return 0; } else { *errcode = 0; return (unsigned int) l; } } static char** split_str(const char* str, char del, size_t* len_out) { size_t i; size_t stlen = 0; size_t len = 0; int accum = 0; char* buf = NULL; char** res = NULL; size_t bi = 0; size_t ri = 0; char* tmp; if (!str) goto return_error; for (i = 0; '\0' != str[i]; i++) { stlen += 1; if (stlen >= CRON_MAX_STR_LEN_TO_SPLIT) goto return_error; } for (i = 0; i < stlen; i++) { if (del == str[i]) { if (accum > 0) { len += 1; accum = 0; } } else if (!isspace(str[i])) { accum += 1; } } /* tail */ if (accum > 0) { len += 1; } if (0 == len) return NULL; buf = (char*) cronMalloc(stlen + 1); if (!buf) goto return_error; memset(buf, 0, stlen + 1); res = (char**) cronMalloc(len * sizeof(char*)); if (!res) goto return_error; for (i = 0; i < stlen; i++) { if (del == str[i]) { if (bi > 0) { tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; memset(buf, 0, stlen + 1); bi = 0; } } else if (!isspace(str[i])) { buf[bi++] = str[i]; } } /* tail */ if (bi > 0) { tmp = strdupl(buf, bi); if (!tmp) goto return_error; res[ri++] = tmp; } cronFree(buf); *len_out = len; return res; return_error: if (buf) { cronFree(buf); } free_splitted(res, len); *len_out = 0; return NULL; } static char* replace_ordinals(char* value, const char** arr, size_t arr_len) { size_t i; char* cur = value; char* res = NULL; int first = 1; for (i = 0; i < arr_len; i++) { char* strnum = to_string((int) i); if (!strnum) { if (!first) { cronFree(cur); } return NULL; } res = str_replace(cur, arr[i], strnum); cronFree(strnum); if (!first) { cronFree(cur); } if (!res) { return NULL; } cur = res; if (first) { first = 0; } } return res; } static int has_char(char* str, char ch) { size_t i; size_t len = 0; if (!str) return 0; len = strlen(str); for (i = 0; i < len; i++) { if (str[i] == ch) return 1; } return 0; } static unsigned int* get_range(char* field, unsigned int min, unsigned int max, const char** error) { char** parts = NULL; size_t len = 0; unsigned int* res = (unsigned int*) cronMalloc(2 * sizeof(unsigned int)); if (!res) goto return_error; res[0] = 0; res[1] = 0; if (1 == strlen(field) && '*' == field[0]) { res[0] = min; res[1] = max - 1; } else if (!has_char(field, '-')) { int err = 0; unsigned int val = parse_uint(field, &err); if (err) { *error = "Unsigned integer parse error 1"; goto return_error; } res[0] = val; res[1] = val; } else { parts = split_str(field, '-', &len); if (0 == len || len > 2) { *error = "Specified range has more than two fields"; goto return_error; } int err = 0; res[0] = parse_uint(parts[0], &err); if (err) { *error = "Unsigned integer parse error 2"; goto return_error; } res[1] = parse_uint(parts[1], &err); if (err) { *error = "Unsigned integer parse error 3"; goto return_error; } } if (res[0] >= max || res[1] >= max) { *error = "Specified range exceeds maximum"; goto return_error; } if (res[0] < min || res[1] < min) { *error = "Specified range is less than minimum"; goto return_error; } free_splitted(parts, len); *error = NULL; return res; return_error: free_splitted(parts, len); if (res) { cronFree(res); } return NULL; } void set_number_hits(const char* value, uint8_t* target, unsigned int min, unsigned int max, const char** error) { size_t i; unsigned int i1; size_t len = 0; char** fields = split_str(value, ',', &len); if (!fields) { *error = "Comma split error"; goto return_result; } for (i = 0; i < len; i++) { if (!has_char(fields[i], '/')) { /* Not an incrementer so it must be a range (possibly empty) */ unsigned int* range = get_range(fields[i], min, max, error); if (*error) { if (range) { cronFree(range); } goto return_result; } for (i1 = range[0]; i1 <= range[1]; i1++) { cron_set_bit(target, i1); } cronFree(range); } else { size_t len2 = 0; char** split = split_str(fields[i], '/', &len2); if (0 == len2 || len2 > 2) { *error = "Incrementer has more than two fields"; free_splitted(split, len2); goto return_result; } unsigned int* range = get_range(split[0], min, max, error); if (*error) { if (range) { cronFree(range); } free_splitted(split, len2); goto return_result; } if (!has_char(split[0], '-')) { range[1] = max - 1; } int err = 0; unsigned int delta = parse_uint(split[1], &err); if (err) { *error = "Unsigned integer parse error 4"; cronFree(range); free_splitted(split, len2); goto return_result; } for (i1 = range[0]; i1 <= range[1]; i1 += delta) { cron_set_bit(target, i1); } free_splitted(split, len2); cronFree(range); } } goto return_result; return_result: free_splitted(fields, len); } static void set_months(char* value, uint8_t* targ, const char** error) { int err; unsigned int i; unsigned int max = 12; char* replaced = NULL; err = to_upper(value); if (err) return; replaced = replace_ordinals(value, MONTHS_ARR, CRON_MONTHS_ARR_LEN); if (!replaced) return; set_number_hits(replaced, targ, 1, max + 1, error); cronFree(replaced); /* ... and then rotate it to the front of the months */ for (i = 1; i <= max; i++) { if (cron_get_bit(targ, i)) { cron_set_bit(targ, i - 1); cron_del_bit(targ, i); } } } static void set_days(char* field, uint8_t* targ, int max, const char** error) { if (1 == strlen(field) && '?' == field[0]) { field[0] = '*'; } set_number_hits(field, targ, 0, max, error); } static void set_days_of_month(char* field, uint8_t* targ, const char** error) { /* Days of month start with 1 (in Cron and Calendar) so add one */ set_days(field, targ, CRON_MAX_DAYS_OF_MONTH, error); /* ... and remove it from the front */ if (targ) { cron_del_bit(targ, 0); } } void cron_parse_expr(const char* expression, cron_expr* target, const char** error) { const char* err_local; size_t len = 0; char** fields = NULL; char* days_replaced = NULL; if (!error) { error = &err_local; } *error = NULL; if (!expression) { *error = "Invalid NULL expression"; goto return_res; } fields = split_str(expression, ' ', &len); if (len != 6) { *error = "Invalid number of fields, expression must consist of 6 fields"; goto return_res; } set_number_hits(fields[0], target->seconds, 0, 60, error); if (*error) goto return_res; set_number_hits(fields[1], target->minutes, 0, 60, error); if (*error) goto return_res; set_number_hits(fields[2], target->hours, 0, 24, error); if (*error) goto return_res; to_upper(fields[5]); days_replaced = replace_ordinals(fields[5], DAYS_ARR, CRON_DAYS_ARR_LEN); set_days(days_replaced, target->days_of_week, 8, error); cronFree(days_replaced); if (*error) goto return_res; if (cron_get_bit(target->days_of_week, 7)) { /* Sunday can be represented as 0 or 7*/ cron_set_bit(target->days_of_week, 0); cron_del_bit(target->days_of_week, 7); } set_days_of_month(fields[3], target->days_of_month, error); if (*error) goto return_res; set_months(fields[4], target->months, error); if (*error) goto return_res; goto return_res; return_res: free_splitted(fields, len); } time_t cron_next(const cron_expr* expr, time_t date) { /* The plan: 1 Round up to the next whole second 2 If seconds match move on, otherwise find the next match: 2.1 If next match is in the next minute then roll forwards 3 If minute matches move on, otherwise find the next match 3.1 If next match is in the next hour then roll forwards 3.2 Reset the seconds and go to 2 4 If hour matches move on, otherwise find the next match 4.1 If next match is in the next day then roll forwards, 4.2 Reset the minutes and seconds and go to 2 ... */ if (!expr) return CRON_INVALID_INSTANT; struct tm calval; memset(&calval, 0, sizeof(struct tm)); struct tm* calendar = cron_time(&date, &calval); if (!calendar) return CRON_INVALID_INSTANT; time_t original = cron_mktime(calendar); if (CRON_INVALID_INSTANT == original) return CRON_INVALID_INSTANT; int res = do_next(expr, calendar, calendar->tm_year); if (0 != res) return CRON_INVALID_INSTANT; time_t calculated = cron_mktime(calendar); if (CRON_INVALID_INSTANT == calculated) return CRON_INVALID_INSTANT; if (calculated == original) { /* We arrived at the original timestamp - round up to the next whole second and try again... */ res = add_to_field(calendar, CRON_CF_SECOND, 1); if (0 != res) return CRON_INVALID_INSTANT; int res = do_next(expr, calendar, calendar->tm_year); if (0 != res) return CRON_INVALID_INSTANT; } return cron_mktime(calendar); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/ccronexpr/ccronexpr.h�������������������������������������������������������0000664�0000000�0000000�00000005710�15162662266�0021162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* * Copyright 2015, alex at staticlibs.net * * 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. */ /* * File: ccronexpr.h * Author: alex * * Created on February 24, 2015, 9:35 AM */ /* * MODIFICATIONS BY MEGA (prominent notice stating changes per Apache 2.0 licence): * Additionally modifications can be seen in detail our public github repository. * * 2018/06/18: - marked cron_next's first parameter as a const pointer, as the function does not change the object passed * - also marked some internal function parameters in a similar fashion. * */ #ifndef CCRONEXPR_H #define CCRONEXPR_H #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) extern "C" { #endif #if !defined(ANDROID) || defined(__LP64__) #include <time.h> #else /* ANDROID */ #include <time64.h> #endif /* ANDROID */ #include <stdint.h> /*added for use if uint*_t data types*/ #ifndef ARRAY_LEN #define ARRAY_LEN(x) sizeof(x)/sizeof(x[0]) #endif #ifdef __MINGW32__ /* To avoid warning when building with mingw */ time_t _mkgmtime(struct tm* tm); #endif /* __MINGW32__ */ /** * Parsed cron expression */ typedef struct { uint8_t seconds[8]; uint8_t minutes[8]; uint8_t hours[3]; uint8_t days_of_week[1]; uint8_t days_of_month[4]; uint8_t months[2]; } cron_expr; /** * Parses specified cron expression. * * @param expression cron expression as nul-terminated string, * should be no longer that 256 bytes * @param target to cron expression structure, it's client code responsibility * to free/destroy it afterwards * @param error output error message, will be set to string literal * error message in case of error. Will be set to NULL on success. * The error message should NOT be freed by client. */ void cron_parse_expr(const char* expression, cron_expr* target, const char** error); /** * Uses the specified expression to calculate the next 'fire' date after * the specified date. All dates are processed as UTC (GMT) dates * without timezones information. To use local dates (current system timezone) * instead of GMT compile with '-DCRON_USE_LOCAL_TIME' * * @param expr parsed cron expression to use in next date calculation * @param date start date to start calculation from * @return next 'fire' date in case of success, '((time_t) -1)' in case of error. */ time_t cron_next(const cron_expr* expr, time_t date); #if defined(__cplusplus) && !defined(CRON_COMPILE_AS_CXX) } /* extern "C"*/ #endif #endif /* CCRONEXPR_H */ ��������������������������������������������������������sdk-10.11.0/third_party/csv/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015573�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/csv/CMakeLists.txt����������������������������������������������������������0000664�0000000�0000000�00000000756�15162662266�0020343�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(csv INTERFACE) set(CSV_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/csv") file(MAKE_DIRECTORY "${CSV_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/csv.h" "${CSV_PUBLIC_INCLUDE_DIR}/csv.h" COPYONLY ) target_include_directories(csv SYSTEM INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(ENABLE_JAVA_BINDINGS) set_property(TARGET csv PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON ) endif() ������������������sdk-10.11.0/third_party/csv/LICENSE�����������������������������������������������������������������0000664�0000000�0000000�00000002060�15162662266�0016576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MIT License Copyright (c) 2017-2019 Vincent La Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/csv/csv.h�������������������������������������������������������������������0000664�0000000�0000000�00001065153�15162662266�0016552�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off #pragma once /* CSV for C++, version 2.3.0 https://github.com/vincentlaucsb/csv-parser MIT License Copyright (c) 2017-2024 Vincent La Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CSV_HPP #define CSV_HPP /** @file * @brief Defines functionality needed for basic CSV parsing */ #include <algorithm> #include <deque> #include <fstream> #include <iterator> #include <memory> #include <mutex> #include <thread> #include <sstream> #include <string> #include <vector> #include <bitset> /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_MMAP_HEADER #define MIO_MMAP_HEADER // #include "mio/page.hpp" /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_PAGE_HEADER #define MIO_PAGE_HEADER #ifdef _WIN32 # include <windows.h> #else # include <unistd.h> #endif namespace mio { /** * This is used by `basic_mmap` to determine whether to create a read-only or * a read-write memory mapping. */ enum class access_mode { read, write }; /** * Determines the operating system's page allocation granularity. * * On the first call to this function, it invokes the operating system specific syscall * to determine the page size, caches the value, and returns it. Any subsequent call to * this function serves the cached value, so no further syscalls are made. */ inline size_t page_size() { static const size_t page_size = [] { #ifdef _WIN32 SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); return SystemInfo.dwAllocationGranularity; #else return sysconf(_SC_PAGE_SIZE); #endif }(); return page_size; } /** * Alligns `offset` to the operating's system page size such that it subtracts the * difference until the nearest page boundary before `offset`, or does nothing if * `offset` is already page aligned. */ inline size_t make_offset_page_aligned(size_t offset) noexcept { const size_t page_size_ = page_size(); // Use integer division to round down to the nearest page alignment. return offset / page_size_ * page_size_; } } // namespace mio #endif // MIO_PAGE_HEADER #include <iterator> #include <string> #include <system_error> #include <cstdint> #ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif // WIN32_LEAN_AND_MEAN # include <windows.h> #else // ifdef _WIN32 # define INVALID_HANDLE_VALUE -1 #endif // ifdef _WIN32 namespace mio { // This value may be provided as the `length` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. enum { map_entire_file = 0 }; #ifdef _WIN32 using file_handle_type = HANDLE; #else using file_handle_type = int; #endif // This value represents an invalid file handle type. This can be used to // determine whether `basic_mmap::file_handle` is valid, for example. const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; template<access_mode AccessMode, typename ByteT> struct basic_mmap { using value_type = ByteT; using size_type = size_t; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; using difference_type = std::ptrdiff_t; using iterator = pointer; using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator<iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>; using iterator_category = std::random_access_iterator_tag; using handle_type = file_handle_type; static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); private: // Points to the first requested byte, and not to the actual start of the mapping. pointer data_ = nullptr; // Length--in bytes--requested by user (which may not be the length of the // full mapping) and the length of the full mapping. size_type length_ = 0; size_type mapped_length_ = 0; // Letting user map a file using both an existing file handle and a path // introcudes some complexity (see `is_handle_internal_`). // On POSIX, we only need a file handle to create a mapping, while on // Windows systems the file handle is necessary to retrieve a file mapping // handle, but any subsequent operations on the mapped region must be done // through the latter. handle_type file_handle_ = INVALID_HANDLE_VALUE; #ifdef _WIN32 handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; #endif // Letting user map a file using both an existing file handle and a path // introcudes some complexity in that we must not close the file handle if // user provided it, but we must close it if we obtained it using the // provided path. For this reason, this flag is used to determine when to // close `file_handle_`. bool is_handle_internal_; public: /** * The default constructed mmap object is in a non-mapped state, that is, * any operation that attempts to access nonexistent underlying data will * result in undefined behaviour/segmentation faults. */ basic_mmap() = default; #ifdef __cpp_exceptions /** * The same as invoking the `map` function, except any error that may occur * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ template<typename String> basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); if(error) { throw std::system_error(error); } } /** * The same as invoking the `map` function, except any error that may occur * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); if(error) { throw std::system_error(error); } } #endif // __cpp_exceptions /** * `basic_mmap` has single-ownership semantics, so transferring ownership * may only be accomplished by moving the object. */ basic_mmap(const basic_mmap&) = delete; basic_mmap(basic_mmap&&); basic_mmap& operator=(const basic_mmap&) = delete; basic_mmap& operator=(basic_mmap&&); /** * If this is a read-write mapping, the destructor invokes sync. Regardless * of the access mode, unmap is invoked as a final step. */ ~basic_mmap(); /** * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ handle_type file_handle() const noexcept { return file_handle_; } handle_type mapping_handle() const noexcept; /** Returns whether a valid memory mapping has been created. */ bool is_open() const noexcept { return file_handle_ != invalid_handle; } /** * Returns true if no mapping was established, that is, conceptually the * same as though the length that was mapped was 0. This function is * provided so that this class has Container semantics. */ bool empty() const noexcept { return length() == 0; } /** Returns true if a mapping was established. */ bool is_mapped() const noexcept; /** * `size` and `length` both return the logical length, i.e. the number of bytes * user requested to be mapped, while `mapped_length` returns the actual number of * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ size_type size() const noexcept { return length(); } size_type length() const noexcept { return length_; } size_type mapped_length() const noexcept { return mapped_length_; } /** Returns the offset relative to the start of the mapping. */ size_type mapping_offset() const noexcept { return mapped_length_ - length_; } /** * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping * exists. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > pointer data() noexcept { return data_; } const_pointer data() const noexcept { return data_; } /** * Returns an iterator to the first requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > iterator begin() noexcept { return data(); } const_iterator begin() const noexcept { return data(); } const_iterator cbegin() const noexcept { return data(); } /** * Returns an iterator one past the last requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > iterator end() noexcept { return data() + length(); } const_iterator end() const noexcept { return data() + length(); } const_iterator cend() const noexcept { return data() + length(); } /** * Returns a reverse iterator to the last memory mapped byte, if a valid * memory mapping exists, otherwise this function call is undefined * behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } /** * Returns a reverse iterator past the first mapped byte, if a valid memory * mapping exists, otherwise this function call is undefined behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } /** * Returns a reference to the `i`th byte from the first requested byte (as returned * by `data`). If this is invoked when no valid memory mapping has been created * prior to this call, undefined behaviour ensues. */ reference operator[](const size_type i) noexcept { return data_[i]; } const_reference operator[](const size_type i) const noexcept { return data_[i]; } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `path`, which must be a path to an existing file, is used to retrieve a file * handle (which is closed when the object destructs or `unmap` is called), which is * then used to memory map the requested region. Upon failure, `error` is set to * indicate the reason and the object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about * providing a value that is aligned with the operating system's page allocation * granularity. This is adjusted by the implementation such that the first requested * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ template<typename String> void map(const String& path, const size_type offset, const size_type length, std::error_code& error); /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `path`, which must be a path to an existing file, is used to retrieve a file * handle (which is closed when the object destructs or `unmap` is called), which is * then used to memory map the requested region. Upon failure, `error` is set to * indicate the reason and the object remains in an unmapped state. * * The entire file is mapped. */ template<typename String> void map(const String& path, std::error_code& error) { map(path, 0, map_entire_file, error); } /** * Establishes a memory mapping with AccessMode. If the mapping is * unsuccesful, the reason is reported via `error` and the object remains in * a state as if this function hadn't been called. * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the * object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about * providing a value that is aligned with the operating system's page allocation * granularity. This is adjusted by the implementation such that the first requested * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error); /** * Establishes a memory mapping with AccessMode. If the mapping is * unsuccesful, the reason is reported via `error` and the object remains in * a state as if this function hadn't been called. * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the * object remains in an unmapped state. * * The entire file is mapped. */ void map(const handle_type handle, std::error_code& error) { map(handle, 0, map_entire_file, error); } /** * If a valid memory mapping has been created prior to this call, this call * instructs the kernel to unmap the memory region and disassociate this object * from the file. * * The file handle associated with the file that is mapped is only closed if the * mapping was created using a file path. If, on the other hand, an existing * file handle was used to create the mapping, the file handle is not closed. */ void unmap(); void swap(basic_mmap& other); /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ template<access_mode A = AccessMode> typename std::enable_if<A == access_mode::write, void>::type sync(std::error_code& error); /** * All operators compare the address of the first byte and size of the two mapped * regions. */ private: template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); } const_pointer get_mapping_start() const noexcept { return !data() ? nullptr : data() - mapping_offset(); } /** * The destructor syncs changes to disk if `AccessMode` is `write`, but not * if it's `read`, but since the destructor cannot be templated, we need to * do SFINAE in a dedicated function, where one syncs and the other is a noop. */ template<access_mode A = AccessMode> typename std::enable_if<A == access_mode::write, void>::type conditional_sync(); template<access_mode A = AccessMode> typename std::enable_if<A == access_mode::read, void>::type conditional_sync(); }; template<access_mode AccessMode, typename ByteT> bool operator==(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); template<access_mode AccessMode, typename ByteT> bool operator!=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); template<access_mode AccessMode, typename ByteT> bool operator<(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); template<access_mode AccessMode, typename ByteT> bool operator<=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); template<access_mode AccessMode, typename ByteT> bool operator>(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); template<access_mode AccessMode, typename ByteT> bool operator>=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b); /** * This is the basis for all read-only mmap objects and should be preferred over * directly using `basic_mmap`. */ template<typename ByteT> using basic_mmap_source = basic_mmap<access_mode::read, ByteT>; /** * This is the basis for all read-write mmap objects and should be preferred over * directly using `basic_mmap`. */ template<typename ByteT> using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>; /** * These aliases cover the most common use cases, both representing a raw byte stream * (either with a char or an unsigned char/uint8_t). */ using mmap_source = basic_mmap_source<char>; using ummap_source = basic_mmap_source<unsigned char>; using mmap_sink = basic_mmap_sink<char>; using ummap_sink = basic_mmap_sink<unsigned char>; /** * Convenience factory method that constructs a mapping for any `basic_mmap` or * `basic_mmap` type. */ template< typename MMap, typename MappingToken > MMap make_mmap(const MappingToken& token, int64_t offset, int64_t length, std::error_code& error) { MMap mmap; mmap.map(token, offset, length, error); return mmap; } /** * Convenience factory method. * * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, * `std::filesystem::path`, `std::vector<char>`, or similar), or a * `mmap_source::handle_type`. */ template<typename MappingToken> mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, mmap_source::size_type length, std::error_code& error) { return make_mmap<mmap_source>(token, offset, length, error); } template<typename MappingToken> mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) { return make_mmap_source(token, 0, map_entire_file, error); } /** * Convenience factory method. * * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, * `std::filesystem::path`, `std::vector<char>`, or similar), or a * `mmap_sink::handle_type`. */ template<typename MappingToken> mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, mmap_sink::size_type length, std::error_code& error) { return make_mmap<mmap_sink>(token, offset, length, error); } template<typename MappingToken> mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) { return make_mmap_sink(token, 0, map_entire_file, error); } } // namespace mio // #include "detail/mmap.ipp" /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_BASIC_MMAP_IMPL #define MIO_BASIC_MMAP_IMPL // #include "mio/mmap.hpp" // #include "mio/page.hpp" // #include "mio/detail/string_util.hpp" /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_STRING_UTIL_HEADER #define MIO_STRING_UTIL_HEADER #include <type_traits> namespace mio { namespace detail { template< typename S, typename C = typename std::decay<S>::type, typename = decltype(std::declval<C>().data()), typename = typename std::enable_if< std::is_same<typename C::value_type, char>::value #ifdef _WIN32 || std::is_same<typename C::value_type, wchar_t>::value #endif >::type > struct char_type_helper { using type = typename C::value_type; }; template<class T> struct char_type { using type = typename char_type_helper<T>::type; }; // TODO: can we avoid this brute force approach? template<> struct char_type<char*> { using type = char; }; template<> struct char_type<const char*> { using type = char; }; template<size_t N> struct char_type<char[N]> { using type = char; }; template<size_t N> struct char_type<const char[N]> { using type = char; }; #ifdef _WIN32 template<> struct char_type<wchar_t*> { using type = wchar_t; }; template<> struct char_type<const wchar_t*> { using type = wchar_t; }; template<size_t N> struct char_type<wchar_t[N]> { using type = wchar_t; }; template<size_t N> struct char_type<const wchar_t[N]> { using type = wchar_t; }; #endif // _WIN32 template<typename CharT, typename S> struct is_c_str_helper { static constexpr bool value = std::is_same< CharT*, // TODO: I'm so sorry for this... Can this be made cleaner? typename std::add_pointer< typename std::remove_cv< typename std::remove_pointer< typename std::decay< S >::type >::type >::type >::type >::value; }; template<typename S> struct is_c_str { static constexpr bool value = is_c_str_helper<char, S>::value; }; #ifdef _WIN32 template<typename S> struct is_c_wstr { static constexpr bool value = is_c_str_helper<wchar_t, S>::value; }; #endif // _WIN32 template<typename S> struct is_c_str_or_c_wstr { static constexpr bool value = is_c_str<S>::value #ifdef _WIN32 || is_c_wstr<S>::value #endif ; }; template< typename String, typename = decltype(std::declval<String>().data()), typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type > const typename char_type<String>::type* c_str(const String& path) { return path.data(); } template< typename String, typename = decltype(std::declval<String>().empty()), typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type > bool empty(const String& path) { return path.empty(); } template< typename String, typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type > const typename char_type<String>::type* c_str(String path) { return path; } template< typename String, typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type > bool empty(String path) { return !path || (*path == 0); } } // namespace detail } // namespace mio #endif // MIO_STRING_UTIL_HEADER #include <algorithm> #ifndef _WIN32 # include <unistd.h> # include <fcntl.h> # include <sys/mman.h> # include <sys/stat.h> #endif namespace mio { namespace detail { #ifdef _WIN32 namespace win { /** Returns the 4 upper bytes of an 8-byte integer. */ inline DWORD int64_high(int64_t n) noexcept { return n >> 32; } /** Returns the 4 lower bytes of an 8-byte integer. */ inline DWORD int64_low(int64_t n) noexcept { return n & 0xffffffff; } template< typename String, typename = typename std::enable_if< std::is_same<typename char_type<String>::type, char>::value >::type > file_handle_type open_file_helper(const String& path, const access_mode mode) { return ::CreateFileA(c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); } template<typename String> typename std::enable_if< std::is_same<typename char_type<String>::type, wchar_t>::value, file_handle_type >::type open_file_helper(const String& path, const access_mode mode) { return ::CreateFileW(c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); } } // win #endif // _WIN32 /** * Returns the last platform specific system error (errno on POSIX and * GetLastError on Win) as a `std::error_code`. */ inline std::error_code last_error() noexcept { std::error_code error; #ifdef _WIN32 error.assign(GetLastError(), std::system_category()); #else error.assign(errno, std::system_category()); #endif return error; } template<typename String> file_handle_type open_file(const String& path, const access_mode mode, std::error_code& error) { error.clear(); if(detail::empty(path)) { error = std::make_error_code(std::errc::invalid_argument); return invalid_handle; } #ifdef _WIN32 const auto handle = win::open_file_helper(path, mode); #else // POSIX const auto handle = ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR); #endif if(handle == invalid_handle) { error = detail::last_error(); } return handle; } inline size_t query_file_size(file_handle_type handle, std::error_code& error) { error.clear(); #ifdef _WIN32 LARGE_INTEGER file_size; if(::GetFileSizeEx(handle, &file_size) == 0) { error = detail::last_error(); return 0; } return static_cast<int64_t>(file_size.QuadPart); #else // POSIX struct stat sbuf; if(::fstat(handle, &sbuf) == -1) { error = detail::last_error(); return 0; } return sbuf.st_size; #endif } struct mmap_context { char* data; int64_t length; int64_t mapped_length; #ifdef _WIN32 file_handle_type file_mapping_handle; #endif }; inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, const int64_t length, const access_mode mode, std::error_code& error) { const int64_t aligned_offset = make_offset_page_aligned(offset); const int64_t length_to_map = offset - aligned_offset + length; #ifdef _WIN32 const int64_t max_file_size = offset + length; const auto file_mapping_handle = ::CreateFileMapping( file_handle, 0, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, win::int64_high(max_file_size), win::int64_low(max_file_size), 0); if(file_mapping_handle == invalid_handle) { error = detail::last_error(); return {}; } char* mapping_start = static_cast<char*>(::MapViewOfFile( file_mapping_handle, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map)); if(mapping_start == nullptr) { // Close file handle if mapping it failed. ::CloseHandle(file_mapping_handle); error = detail::last_error(); return {}; } #else // POSIX char* mapping_start = static_cast<char*>(::mmap( 0, // Don't give hint as to where to map. length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, MAP_SHARED, file_handle, aligned_offset)); if(mapping_start == MAP_FAILED) { error = detail::last_error(); return {}; } #endif mmap_context ctx; ctx.data = mapping_start + offset - aligned_offset; ctx.length = length; ctx.mapped_length = length_to_map; #ifdef _WIN32 ctx.file_mapping_handle = file_mapping_handle; #endif return ctx; } } // namespace detail // -- basic_mmap -- template<access_mode AccessMode, typename ByteT> basic_mmap<AccessMode, ByteT>::~basic_mmap() { conditional_sync(); unmap(); } template<access_mode AccessMode, typename ByteT> basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other) : data_(std::move(other.data_)) , length_(std::move(other.length_)) , mapped_length_(std::move(other.mapped_length_)) , file_handle_(std::move(other.file_handle_)) #ifdef _WIN32 , file_mapping_handle_(std::move(other.file_mapping_handle_)) #endif , is_handle_internal_(std::move(other.is_handle_internal_)) { other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; other.file_handle_ = invalid_handle; #ifdef _WIN32 other.file_mapping_handle_ = invalid_handle; #endif } template<access_mode AccessMode, typename ByteT> basic_mmap<AccessMode, ByteT>& basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other) { if(this != &other) { // First the existing mapping needs to be removed. unmap(); data_ = std::move(other.data_); length_ = std::move(other.length_); mapped_length_ = std::move(other.mapped_length_); file_handle_ = std::move(other.file_handle_); #ifdef _WIN32 file_mapping_handle_ = std::move(other.file_mapping_handle_); #endif is_handle_internal_ = std::move(other.is_handle_internal_); // The moved from basic_mmap's fields need to be reset, because // otherwise other's destructor will unmap the same mapping that was // just moved into this. other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; other.file_handle_ = invalid_handle; #ifdef _WIN32 other.file_mapping_handle_ = invalid_handle; #endif other.is_handle_internal_ = false; } return *this; } template<access_mode AccessMode, typename ByteT> typename basic_mmap<AccessMode, ByteT>::handle_type basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept { #ifdef _WIN32 return file_mapping_handle_; #else return file_handle_; #endif } template<access_mode AccessMode, typename ByteT> template<typename String> void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset, const size_type length, std::error_code& error) { error.clear(); if(detail::empty(path)) { error = std::make_error_code(std::errc::invalid_argument); return; } const auto handle = detail::open_file(path, AccessMode, error); if(error) { return; } map(handle, offset, length, error); // This MUST be after the call to map, as that sets this to true. if(!error) { is_handle_internal_ = true; } } template<access_mode AccessMode, typename ByteT> void basic_mmap<AccessMode, ByteT>::map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error) { error.clear(); if(handle == invalid_handle) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } const auto file_size = detail::query_file_size(handle, error); if(error) { return; } if(offset + length > file_size) { error = std::make_error_code(std::errc::invalid_argument); return; } const auto ctx = detail::memory_map(handle, offset, length == map_entire_file ? (file_size - offset) : length, AccessMode, error); if(!error) { // We must unmap the previous mapping that may have existed prior to this call. // Note that this must only be invoked after a new mapping has been created in // order to provide the strong guarantee that, should the new mapping fail, the // `map` function leaves this instance in a state as though the function had // never been invoked. unmap(); file_handle_ = handle; is_handle_internal_ = false; data_ = reinterpret_cast<pointer>(ctx.data); length_ = ctx.length; mapped_length_ = ctx.mapped_length; #ifdef _WIN32 file_mapping_handle_ = ctx.file_mapping_handle; #endif } } template<access_mode AccessMode, typename ByteT> template<access_mode A> typename std::enable_if<A == access_mode::write, void>::type basic_mmap<AccessMode, ByteT>::sync(std::error_code& error) { error.clear(); if(!is_open()) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } if(data()) { #ifdef _WIN32 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0) #else // POSIX if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) #endif { error = detail::last_error(); return; } } #ifdef _WIN32 if(::FlushFileBuffers(file_handle_) == 0) { error = detail::last_error(); } #endif } template<access_mode AccessMode, typename ByteT> void basic_mmap<AccessMode, ByteT>::unmap() { if(!is_open()) { return; } // TODO do we care about errors here? #ifdef _WIN32 if(is_mapped()) { ::UnmapViewOfFile(get_mapping_start()); ::CloseHandle(file_mapping_handle_); } #else // POSIX if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); } #endif // If `file_handle_` was obtained by our opening it (when map is called with // a path, rather than an existing file handle), we need to close it, // otherwise it must not be closed as it may still be used outside this // instance. if(is_handle_internal_) { #ifdef _WIN32 ::CloseHandle(file_handle_); #else // POSIX ::close(file_handle_); #endif } // Reset fields to their default values. data_ = nullptr; length_ = mapped_length_ = 0; file_handle_ = invalid_handle; #ifdef _WIN32 file_mapping_handle_ = invalid_handle; #endif } template<access_mode AccessMode, typename ByteT> bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept { #ifdef _WIN32 return file_mapping_handle_ != invalid_handle; #else // POSIX return is_open(); #endif } template<access_mode AccessMode, typename ByteT> void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other) { if(this != &other) { using std::swap; swap(data_, other.data_); swap(file_handle_, other.file_handle_); #ifdef _WIN32 swap(file_mapping_handle_, other.file_mapping_handle_); #endif swap(length_, other.length_); swap(mapped_length_, other.mapped_length_); swap(is_handle_internal_, other.is_handle_internal_); } } template<access_mode AccessMode, typename ByteT> template<access_mode A> typename std::enable_if<A == access_mode::write, void>::type basic_mmap<AccessMode, ByteT>::conditional_sync() { // This is invoked from the destructor, so not much we can do about // failures here. std::error_code ec; sync(ec); } template<access_mode AccessMode, typename ByteT> template<access_mode A> typename std::enable_if<A == access_mode::read, void>::type basic_mmap<AccessMode, ByteT>::conditional_sync() { // noop } template<access_mode AccessMode, typename ByteT> bool operator==(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { return a.data() == b.data() && a.size() == b.size(); } template<access_mode AccessMode, typename ByteT> bool operator!=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { return !(a == b); } template<access_mode AccessMode, typename ByteT> bool operator<(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { if(a.data() == b.data()) { return a.size() < b.size(); } return a.data() < b.data(); } template<access_mode AccessMode, typename ByteT> bool operator<=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { return !(a > b); } template<access_mode AccessMode, typename ByteT> bool operator>(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { if(a.data() == b.data()) { return a.size() > b.size(); } return a.data() > b.data(); } template<access_mode AccessMode, typename ByteT> bool operator>=(const basic_mmap<AccessMode, ByteT>& a, const basic_mmap<AccessMode, ByteT>& b) { return !(a < b); } } // namespace mio #endif // MIO_BASIC_MMAP_IMPL #endif // MIO_MMAP_HEADER /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_PAGE_HEADER #define MIO_PAGE_HEADER #ifdef _WIN32 # include <windows.h> #else # include <unistd.h> #endif namespace mio { /** * This is used by `basic_mmap` to determine whether to create a read-only or * a read-write memory mapping. */ enum class access_mode { read, write }; /** * Determines the operating system's page allocation granularity. * * On the first call to this function, it invokes the operating system specific syscall * to determine the page size, caches the value, and returns it. Any subsequent call to * this function serves the cached value, so no further syscalls are made. */ inline size_t page_size() { static const size_t page_size = [] { #ifdef _WIN32 SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); return SystemInfo.dwAllocationGranularity; #else return sysconf(_SC_PAGE_SIZE); #endif }(); return page_size; } /** * Alligns `offset` to the operating's system page size such that it subtracts the * difference until the nearest page boundary before `offset`, or does nothing if * `offset` is already page aligned. */ inline size_t make_offset_page_aligned(size_t offset) noexcept { const size_t page_size_ = page_size(); // Use integer division to round down to the nearest page alignment. return offset / page_size_ * page_size_; } } // namespace mio #endif // MIO_PAGE_HEADER /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies * or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef MIO_SHARED_MMAP_HEADER #define MIO_SHARED_MMAP_HEADER // #include "mio/mmap.hpp" #include <system_error> // std::error_code #include <memory> // std::shared_ptr namespace mio { /** * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with * `std::shared_ptr` semantics. * * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if * shared semantics are not required. */ template< access_mode AccessMode, typename ByteT > class basic_shared_mmap { using impl_type = basic_mmap<AccessMode, ByteT>; std::shared_ptr<impl_type> pimpl_; public: using value_type = typename impl_type::value_type; using size_type = typename impl_type::size_type; using reference = typename impl_type::reference; using const_reference = typename impl_type::const_reference; using pointer = typename impl_type::pointer; using const_pointer = typename impl_type::const_pointer; using difference_type = typename impl_type::difference_type; using iterator = typename impl_type::iterator; using const_iterator = typename impl_type::const_iterator; using reverse_iterator = typename impl_type::reverse_iterator; using const_reverse_iterator = typename impl_type::const_reverse_iterator; using iterator_category = typename impl_type::iterator_category; using handle_type = typename impl_type::handle_type; using mmap_type = impl_type; basic_shared_mmap() = default; basic_shared_mmap(const basic_shared_mmap&) = default; basic_shared_mmap& operator=(const basic_shared_mmap&) = default; basic_shared_mmap(basic_shared_mmap&&) = default; basic_shared_mmap& operator=(basic_shared_mmap&&) = default; /** Takes ownership of an existing mmap object. */ basic_shared_mmap(mmap_type&& mmap) : pimpl_(std::make_shared<mmap_type>(std::move(mmap))) {} /** Takes ownership of an existing mmap object. */ basic_shared_mmap& operator=(mmap_type&& mmap) { pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); return *this; } /** Initializes this object with an already established shared mmap. */ basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {} /** Initializes this object with an already established shared mmap. */ basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap) { pimpl_ = std::move(mmap); return *this; } #ifdef __cpp_exceptions /** * The same as invoking the `map` function, except any error that may occur * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ template<typename String> basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); if(error) { throw std::system_error(error); } } /** * The same as invoking the `map` function, except any error that may occur * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); if(error) { throw std::system_error(error); } } #endif // __cpp_exceptions /** * If this is a read-write mapping and the last reference to the mapping, * the destructor invokes sync. Regardless of the access mode, unmap is * invoked as a final step. */ ~basic_shared_mmap() = default; /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; } /** * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ handle_type file_handle() const noexcept { return pimpl_ ? pimpl_->file_handle() : invalid_handle; } handle_type mapping_handle() const noexcept { return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; } /** Returns whether a valid memory mapping has been created. */ bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } /** * Returns true if no mapping was established, that is, conceptually the * same as though the length that was mapped was 0. This function is * provided so that this class has Container semantics. */ bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } /** * `size` and `length` both return the logical length, i.e. the number of bytes * user requested to be mapped, while `mapped_length` returns the actual number of * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } size_type mapped_length() const noexcept { return pimpl_ ? pimpl_->mapped_length() : 0; } /** * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping * exists. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > pointer data() noexcept { return pimpl_->data(); } const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } /** * Returns an iterator to the first requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ iterator begin() noexcept { return pimpl_->begin(); } const_iterator begin() const noexcept { return pimpl_->begin(); } const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } /** * Returns an iterator one past the last requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > iterator end() noexcept { return pimpl_->end(); } const_iterator end() const noexcept { return pimpl_->end(); } const_iterator cend() const noexcept { return pimpl_->cend(); } /** * Returns a reverse iterator to the last memory mapped byte, if a valid * memory mapping exists, otherwise this function call is undefined * behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } /** * Returns a reverse iterator past the first mapped byte, if a valid memory * mapping exists, otherwise this function call is undefined behaviour. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > reverse_iterator rend() noexcept { return pimpl_->rend(); } const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } /** * Returns a reference to the `i`th byte from the first requested byte (as returned * by `data`). If this is invoked when no valid memory mapping has been created * prior to this call, undefined behaviour ensues. */ reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `path`, which must be a path to an existing file, is used to retrieve a file * handle (which is closed when the object destructs or `unmap` is called), which is * then used to memory map the requested region. Upon failure, `error` is set to * indicate the reason and the object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about * providing a value that is aligned with the operating system's page allocation * granularity. This is adjusted by the implementation such that the first requested * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ template<typename String> void map(const String& path, const size_type offset, const size_type length, std::error_code& error) { map_impl(path, offset, length, error); } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `path`, which must be a path to an existing file, is used to retrieve a file * handle (which is closed when the object destructs or `unmap` is called), which is * then used to memory map the requested region. Upon failure, `error` is set to * indicate the reason and the object remains in an unmapped state. * * The entire file is mapped. */ template<typename String> void map(const String& path, std::error_code& error) { map_impl(path, 0, map_entire_file, error); } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the * object remains in an unmapped state. * * `offset` is the number of bytes, relative to the start of the file, where the * mapping should begin. When specifying it, there is no need to worry about * providing a value that is aligned with the operating system's page allocation * granularity. This is adjusted by the implementation such that the first requested * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at * `offset` from the start of the file. * * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error) { map_impl(handle, offset, length, error); } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the * object remains in an unmapped state. * * The entire file is mapped. */ void map(const handle_type handle, std::error_code& error) { map_impl(handle, 0, map_entire_file, error); } /** * If a valid memory mapping has been created prior to this call, this call * instructs the kernel to unmap the memory region and disassociate this object * from the file. * * The file handle associated with the file that is mapped is only closed if the * mapping was created using a file path. If, on the other hand, an existing * file handle was used to create the mapping, the file handle is not closed. */ void unmap() { if(pimpl_) pimpl_->unmap(); } void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ template< access_mode A = AccessMode, typename = typename std::enable_if<A == access_mode::write>::type > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } /** All operators compare the underlying `basic_mmap`'s addresses. */ friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ == b.pimpl_; } friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return !(a == b); } friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ < b.pimpl_; } friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ <= b.pimpl_; } friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ > b.pimpl_; } friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ >= b.pimpl_; } private: template<typename MappingToken> void map_impl(const MappingToken& token, const size_type offset, const size_type length, std::error_code& error) { if(!pimpl_) { mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error); if(error) { return; } pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); } else { pimpl_->map(token, offset, length, error); } } }; /** * This is the basis for all read-only mmap objects and should be preferred over * directly using basic_shared_mmap. */ template<typename ByteT> using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>; /** * This is the basis for all read-write mmap objects and should be preferred over * directly using basic_shared_mmap. */ template<typename ByteT> using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>; /** * These aliases cover the most common use cases, both representing a raw byte stream * (either with a char or an unsigned char/uint8_t). */ using shared_mmap_source = basic_shared_mmap_source<char>; using shared_ummap_source = basic_shared_mmap_source<unsigned char>; using shared_mmap_sink = basic_shared_mmap_sink<char>; using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>; } // namespace mio #endif // MIO_SHARED_MMAP_HEADER /** @file * @brief Contains the main CSV parsing algorithm and various utility functions */ #include <algorithm> #include <array> #include <condition_variable> #include <deque> #include <fstream> #include <memory> #include <mutex> #include <unordered_map> #include <unordered_set> #include <thread> #include <vector> #include <memory> #include <unordered_map> #include <string> #include <vector> /** @file * A standalone header file containing shared code */ #include <algorithm> #include <array> #include <cmath> #include <cstdlib> #include <deque> #if defined(_WIN32) # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # include <windows.h> # undef max # undef min #elif defined(__linux__) # include <unistd.h> #endif /** Helper macro which should be #defined as "inline" * in the single header version */ #define CSV_INLINE inline #include <type_traits> // Copyright 2017-2019 by Martin Moene // // string-view lite, a C++17-like string_view for C++98 and later. // For more information see https://github.com/martinmoene/string-view-lite // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef NONSTD_SV_LITE_H_INCLUDED #define NONSTD_SV_LITE_H_INCLUDED #define string_view_lite_MAJOR 1 #define string_view_lite_MINOR 1 #define string_view_lite_PATCH 0 #define string_view_lite_VERSION nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY(string_view_lite_PATCH) #define nssv_STRINGIFY( x ) nssv_STRINGIFY_( x ) #define nssv_STRINGIFY_( x ) #x // string-view lite configuration: #define nssv_STRING_VIEW_DEFAULT 0 #define nssv_STRING_VIEW_NONSTD 1 #define nssv_STRING_VIEW_STD 2 #if !defined( nssv_CONFIG_SELECT_STRING_VIEW ) # define nssv_CONFIG_SELECT_STRING_VIEW ( nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD ) #endif #if defined( nssv_CONFIG_SELECT_STD_STRING_VIEW ) || defined( nssv_CONFIG_SELECT_NONSTD_STRING_VIEW ) # error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_... #endif #ifndef nssv_CONFIG_STD_SV_OPERATOR # define nssv_CONFIG_STD_SV_OPERATOR 0 #endif #ifndef nssv_CONFIG_USR_SV_OPERATOR # define nssv_CONFIG_USR_SV_OPERATOR 1 #endif #ifdef nssv_CONFIG_CONVERSION_STD_STRING # define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING # define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING #endif #ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS # define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 #endif #ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS # define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 #endif // Control presence of exception handling (try and auto discover): #ifndef nssv_CONFIG_NO_EXCEPTIONS # if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) # define nssv_CONFIG_NO_EXCEPTIONS 0 # else # define nssv_CONFIG_NO_EXCEPTIONS 1 # endif #endif // C++ language version detection (C++20 is speculative): // Note: VC14.0/1900 (VS2015) lacks too much from C++14. #ifndef nssv_CPLUSPLUS # if defined(_MSVC_LANG ) && !defined(__clang__) # define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) # else # define nssv_CPLUSPLUS __cplusplus # endif #endif #define nssv_CPP98_OR_GREATER ( nssv_CPLUSPLUS >= 199711L ) #define nssv_CPP11_OR_GREATER ( nssv_CPLUSPLUS >= 201103L ) #define nssv_CPP11_OR_GREATER_ ( nssv_CPLUSPLUS >= 201103L ) #define nssv_CPP14_OR_GREATER ( nssv_CPLUSPLUS >= 201402L ) #define nssv_CPP17_OR_GREATER ( nssv_CPLUSPLUS >= 201703L ) #define nssv_CPP20_OR_GREATER ( nssv_CPLUSPLUS >= 202000L ) // use C++17 std::string_view if available and requested: #if nssv_CPP17_OR_GREATER && defined(__has_include ) # if __has_include( <string_view> ) # define nssv_HAVE_STD_STRING_VIEW 1 # else # define nssv_HAVE_STD_STRING_VIEW 0 # endif #else # define nssv_HAVE_STD_STRING_VIEW 0 #endif #define nssv_USES_STD_STRING_VIEW ( (nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW) ) #define nssv_HAVE_STARTS_WITH ( nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW ) #define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH // // Use C++17 std::string_view: // #if nssv_USES_STD_STRING_VIEW #include <string_view> // Extensions for std::string: #if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS namespace nonstd { template< class CharT, class Traits, class Allocator = std::allocator<CharT> > std::basic_string<CharT, Traits, Allocator> to_string( std::basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() ) { return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a ); } template< class CharT, class Traits, class Allocator > std::basic_string_view<CharT, Traits> to_string_view( std::basic_string<CharT, Traits, Allocator> const & s ) { return std::basic_string_view<CharT, Traits>( s.data(), s.size() ); } // Literal operators sv and _sv: #if nssv_CONFIG_STD_SV_OPERATOR using namespace std::literals::string_view_literals; #endif #if nssv_CONFIG_USR_SV_OPERATOR inline namespace literals { inline namespace string_view_literals { constexpr std::string_view operator "" _sv( const char* str, size_t len ) noexcept // (1) { return std::string_view{ str, len }; } constexpr std::u16string_view operator "" _sv( const char16_t* str, size_t len ) noexcept // (2) { return std::u16string_view{ str, len }; } constexpr std::u32string_view operator "" _sv( const char32_t* str, size_t len ) noexcept // (3) { return std::u32string_view{ str, len }; } constexpr std::wstring_view operator "" _sv( const wchar_t* str, size_t len ) noexcept // (4) { return std::wstring_view{ str, len }; } }} // namespace literals::string_view_literals #endif // nssv_CONFIG_USR_SV_OPERATOR } // namespace nonstd #endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS namespace nonstd { using std::string_view; using std::wstring_view; using std::u16string_view; using std::u32string_view; using std::basic_string_view; // literal "sv" and "_sv", see above using std::operator==; using std::operator!=; using std::operator<; using std::operator<=; using std::operator>; using std::operator>=; using std::operator<<; } // namespace nonstd #else // nssv_HAVE_STD_STRING_VIEW // // Before C++17: use string_view lite: // // Compiler versions: // // MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) // MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) // MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) // MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) // MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) // MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) // MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) // MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) // MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) // MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) #if defined(_MSC_VER ) && !defined(__clang__) # define nssv_COMPILER_MSVC_VER (_MSC_VER ) # define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) #else # define nssv_COMPILER_MSVC_VER 0 # define nssv_COMPILER_MSVC_VERSION 0 #endif #define nssv_COMPILER_VERSION( major, minor, patch ) (10 * ( 10 * major + minor) + patch) #if defined(__clang__) # define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) #else # define nssv_COMPILER_CLANG_VERSION 0 #endif #if defined(__GNUC__) && !defined(__clang__) # define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #else # define nssv_COMPILER_GNUC_VERSION 0 #endif // half-open range [lo..hi): #define nssv_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) // Presence of language and library features: #ifdef _HAS_CPP0X # define nssv_HAS_CPP0X _HAS_CPP0X #else # define nssv_HAS_CPP0X 0 #endif // Unless defined otherwise below, consider VC14 as C++11 for variant-lite: #if nssv_COMPILER_MSVC_VER >= 1900 # undef nssv_CPP11_OR_GREATER # define nssv_CPP11_OR_GREATER 1 #endif #define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) #define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) #define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) #define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) #define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) #define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) #define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) #define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) // Presence of C++11 language features: #define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 #define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 #define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 #define nssv_HAVE_NOEXCEPT nssv_CPP11_140 #define nssv_HAVE_NULLPTR nssv_CPP11_100 #define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 #define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 #define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 #define nssv_HAVE_WCHAR16_T nssv_CPP11_100 #define nssv_HAVE_WCHAR32_T nssv_CPP11_100 #if ! ( ( nssv_CPP11 && nssv_COMPILER_CLANG_VERSION ) || nssv_BETWEEN( nssv_COMPILER_CLANG_VERSION, 300, 400 ) ) # define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 #endif // Presence of C++14 language features: #define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 // Presence of C++17 language features: #define nssv_HAVE_NODISCARD nssv_CPP17_000 // Presence of C++ library features: #define nssv_HAVE_STD_HASH nssv_CPP11_120 // C++ feature usage: #if nssv_HAVE_CONSTEXPR_11 # define nssv_constexpr constexpr #else # define nssv_constexpr /*constexpr*/ #endif #if nssv_HAVE_CONSTEXPR_14 # define nssv_constexpr14 constexpr #else # define nssv_constexpr14 /*constexpr*/ #endif #if nssv_HAVE_EXPLICIT_CONVERSION # define nssv_explicit explicit #else # define nssv_explicit /*explicit*/ #endif #if nssv_HAVE_INLINE_NAMESPACE # define nssv_inline_ns inline #else # define nssv_inline_ns /*inline*/ #endif #if nssv_HAVE_NOEXCEPT # define nssv_noexcept noexcept #else # define nssv_noexcept /*noexcept*/ #endif //#if nssv_HAVE_REF_QUALIFIER //# define nssv_ref_qual & //# define nssv_refref_qual && //#else //# define nssv_ref_qual /*&*/ //# define nssv_refref_qual /*&&*/ //#endif #if nssv_HAVE_NULLPTR # define nssv_nullptr nullptr #else # define nssv_nullptr NULL #endif #if nssv_HAVE_NODISCARD # define nssv_nodiscard [[nodiscard]] #else # define nssv_nodiscard /*[[nodiscard]]*/ #endif // Additional includes: #include <algorithm> #include <cassert> #include <iterator> #include <limits> #include <ostream> #include <string> // std::char_traits<> #if ! nssv_CONFIG_NO_EXCEPTIONS # include <stdexcept> #endif #if nssv_CPP11_OR_GREATER # include <type_traits> #endif // Clang, GNUC, MSVC warning suppression macros: #if defined(__clang__) # pragma clang diagnostic ignored "-Wreserved-user-defined-literal" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wuser-defined-literals" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wliteral-suffix" #endif // __clang__ #if nssv_COMPILER_MSVC_VERSION >= 140 # define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] # define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress: code) ) # define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable: codes)) #else # define nssv_SUPPRESS_MSGSL_WARNING(expr) # define nssv_SUPPRESS_MSVC_WARNING(code, descr) # define nssv_DISABLE_MSVC_WARNINGS(codes) #endif #if defined(__clang__) # define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") #elif defined(__GNUC__) # define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") #elif nssv_COMPILER_MSVC_VERSION >= 140 # define nssv_RESTORE_WARNINGS() __pragma(warning(pop )) #else # define nssv_RESTORE_WARNINGS() #endif // Suppress the following MSVC (GSL) warnings: // - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not // start with an underscore are reserved // - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; // use brace initialization, gsl::narrow_cast or gsl::narow // - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead nssv_DISABLE_MSVC_WARNINGS( 4455 26481 26472 ) //nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) //nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) namespace nonstd { namespace sv_lite { template < class CharT, class Traits = std::char_traits<CharT> > class basic_string_view; // // basic_string_view: // template < class CharT, class Traits /* = std::char_traits<CharT> */ > class basic_string_view { public: // Member types: typedef Traits traits_type; typedef CharT value_type; typedef CharT * pointer; typedef CharT const * const_pointer; typedef CharT & reference; typedef CharT const & const_reference; typedef const_pointer iterator; typedef const_pointer const_iterator; typedef std::reverse_iterator< const_iterator > reverse_iterator; typedef std::reverse_iterator< const_iterator > const_reverse_iterator; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; // 24.4.2.1 Construction and assignment: nssv_constexpr basic_string_view() nssv_noexcept : data_( nssv_nullptr ) , size_( 0 ) {} #if nssv_CPP11_OR_GREATER nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept = default; #else nssv_constexpr basic_string_view( basic_string_view const & other ) nssv_noexcept : data_( other.data_) , size_( other.size_) {} #endif nssv_constexpr basic_string_view( CharT const * s, size_type count ) : data_( s ) , size_( count ) {} nssv_constexpr basic_string_view( CharT const * s) : data_( s ) , size_( Traits::length(s) ) {} // Assignment: #if nssv_CPP11_OR_GREATER nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept = default; #else nssv_constexpr14 basic_string_view & operator=( basic_string_view const & other ) nssv_noexcept { data_ = other.data_; size_ = other.size_; return *this; } #endif // 24.4.2.2 Iterator support: nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator( end() ); } nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator( begin() ); } nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } // 24.4.2.3 Capacity: nssv_constexpr size_type size() const nssv_noexcept { return size_; } nssv_constexpr size_type length() const nssv_noexcept { return size_; } nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits< size_type >::max)(); } // since C++20 nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept { return 0 == size_; } // 24.4.2.4 Element access: nssv_constexpr const_reference operator[]( size_type pos ) const { return data_at( pos ); } nssv_constexpr14 const_reference at( size_type pos ) const { #if nssv_CONFIG_NO_EXCEPTIONS assert( pos < size() ); #else if ( pos >= size() ) { throw std::out_of_range("nonst::string_view::at()"); } #endif return data_at( pos ); } nssv_constexpr const_reference front() const { return data_at( 0 ); } nssv_constexpr const_reference back() const { return data_at( size() - 1 ); } nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } // 24.4.2.5 Modifiers: nssv_constexpr14 void remove_prefix( size_type n ) { assert( n <= size() ); data_ += n; size_ -= n; } nssv_constexpr14 void remove_suffix( size_type n ) { assert( n <= size() ); size_ -= n; } nssv_constexpr14 void swap( basic_string_view & other ) nssv_noexcept { using std::swap; swap( data_, other.data_ ); swap( size_, other.size_ ); } // 24.4.2.6 String operations: size_type copy( CharT * dest, size_type n, size_type pos = 0 ) const { #if nssv_CONFIG_NO_EXCEPTIONS assert( pos <= size() ); #else if ( pos > size() ) { throw std::out_of_range("nonst::string_view::copy()"); } #endif const size_type rlen = (std::min)( n, size() - pos ); (void) Traits::copy( dest, data() + pos, rlen ); return rlen; } nssv_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const { #if nssv_CONFIG_NO_EXCEPTIONS assert( pos <= size() ); #else if ( pos > size() ) { throw std::out_of_range("nonst::string_view::substr()"); } #endif return basic_string_view( data() + pos, (std::min)( n, size() - pos ) ); } // compare(), 6x: nssv_constexpr14 int compare( basic_string_view other ) const nssv_noexcept // (1) { if ( const int result = Traits::compare( data(), other.data(), (std::min)( size(), other.size() ) ) ) return result; return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; } nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other ) const // (2) { return substr( pos1, n1 ).compare( other ); } nssv_constexpr int compare( size_type pos1, size_type n1, basic_string_view other, size_type pos2, size_type n2 ) const // (3) { return substr( pos1, n1 ).compare( other.substr( pos2, n2 ) ); } nssv_constexpr int compare( CharT const * s ) const // (4) { return compare( basic_string_view( s ) ); } nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s ) const // (5) { return substr( pos1, n1 ).compare( basic_string_view( s ) ); } nssv_constexpr int compare( size_type pos1, size_type n1, CharT const * s, size_type n2 ) const // (6) { return substr( pos1, n1 ).compare( basic_string_view( s, n2 ) ); } // 24.4.2.7 Searching: // starts_with(), 3x, since C++20: nssv_constexpr bool starts_with( basic_string_view v ) const nssv_noexcept // (1) { return size() >= v.size() && compare( 0, v.size(), v ) == 0; } nssv_constexpr bool starts_with( CharT c ) const nssv_noexcept // (2) { return starts_with( basic_string_view( &c, 1 ) ); } nssv_constexpr bool starts_with( CharT const * s ) const // (3) { return starts_with( basic_string_view( s ) ); } // ends_with(), 3x, since C++20: nssv_constexpr bool ends_with( basic_string_view v ) const nssv_noexcept // (1) { return size() >= v.size() && compare( size() - v.size(), npos, v ) == 0; } nssv_constexpr bool ends_with( CharT c ) const nssv_noexcept // (2) { return ends_with( basic_string_view( &c, 1 ) ); } nssv_constexpr bool ends_with( CharT const * s ) const // (3) { return ends_with( basic_string_view( s ) ); } // find(), 4x: nssv_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) { return assert( v.size() == 0 || v.data() != nssv_nullptr ) , pos >= size() ? npos : to_pos( std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); } nssv_constexpr14 size_type find( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) { return find( basic_string_view( &c, 1 ), pos ); } nssv_constexpr14 size_type find( CharT const * s, size_type pos, size_type n ) const // (3) { return find( basic_string_view( s, n ), pos ); } nssv_constexpr14 size_type find( CharT const * s, size_type pos = 0 ) const // (4) { return find( basic_string_view( s ), pos ); } // rfind(), 4x: nssv_constexpr14 size_type rfind( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) { if ( size() < v.size() ) return npos; if ( v.empty() ) return (std::min)( size(), pos ); const_iterator last = cbegin() + (std::min)( size() - v.size(), pos ) + v.size(); const_iterator result = std::find_end( cbegin(), last, v.cbegin(), v.cend(), Traits::eq ); return result != last ? size_type( result - cbegin() ) : npos; } nssv_constexpr14 size_type rfind( CharT c, size_type pos = npos ) const nssv_noexcept // (2) { return rfind( basic_string_view( &c, 1 ), pos ); } nssv_constexpr14 size_type rfind( CharT const * s, size_type pos, size_type n ) const // (3) { return rfind( basic_string_view( s, n ), pos ); } nssv_constexpr14 size_type rfind( CharT const * s, size_type pos = npos ) const // (4) { return rfind( basic_string_view( s ), pos ); } // find_first_of(), 4x: nssv_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) { return pos >= size() ? npos : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); } nssv_constexpr size_type find_first_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) { return find_first_of( basic_string_view( &c, 1 ), pos ); } nssv_constexpr size_type find_first_of( CharT const * s, size_type pos, size_type n ) const // (3) { return find_first_of( basic_string_view( s, n ), pos ); } nssv_constexpr size_type find_first_of( CharT const * s, size_type pos = 0 ) const // (4) { return find_first_of( basic_string_view( s ), pos ); } // find_last_of(), 4x: nssv_constexpr size_type find_last_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) { return empty() ? npos : pos >= size() ? find_last_of( v, size() - 1 ) : to_pos( std::find_first_of( const_reverse_iterator( cbegin() + pos + 1 ), crend(), v.cbegin(), v.cend(), Traits::eq ) ); } nssv_constexpr size_type find_last_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) { return find_last_of( basic_string_view( &c, 1 ), pos ); } nssv_constexpr size_type find_last_of( CharT const * s, size_type pos, size_type count ) const // (3) { return find_last_of( basic_string_view( s, count ), pos ); } nssv_constexpr size_type find_last_of( CharT const * s, size_type pos = npos ) const // (4) { return find_last_of( basic_string_view( s ), pos ); } // find_first_not_of(), 4x: nssv_constexpr size_type find_first_not_of( basic_string_view v, size_type pos = 0 ) const nssv_noexcept // (1) { return pos >= size() ? npos : to_pos( std::find_if( cbegin() + pos, cend(), not_in_view( v ) ) ); } nssv_constexpr size_type find_first_not_of( CharT c, size_type pos = 0 ) const nssv_noexcept // (2) { return find_first_not_of( basic_string_view( &c, 1 ), pos ); } nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos, size_type count ) const // (3) { return find_first_not_of( basic_string_view( s, count ), pos ); } nssv_constexpr size_type find_first_not_of( CharT const * s, size_type pos = 0 ) const // (4) { return find_first_not_of( basic_string_view( s ), pos ); } // find_last_not_of(), 4x: nssv_constexpr size_type find_last_not_of( basic_string_view v, size_type pos = npos ) const nssv_noexcept // (1) { return empty() ? npos : pos >= size() ? find_last_not_of( v, size() - 1 ) : to_pos( std::find_if( const_reverse_iterator( cbegin() + pos + 1 ), crend(), not_in_view( v ) ) ); } nssv_constexpr size_type find_last_not_of( CharT c, size_type pos = npos ) const nssv_noexcept // (2) { return find_last_not_of( basic_string_view( &c, 1 ), pos ); } nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos, size_type count ) const // (3) { return find_last_not_of( basic_string_view( s, count ), pos ); } nssv_constexpr size_type find_last_not_of( CharT const * s, size_type pos = npos ) const // (4) { return find_last_not_of( basic_string_view( s ), pos ); } // Constants: #if nssv_CPP17_OR_GREATER static nssv_constexpr size_type npos = size_type(-1); #elif nssv_CPP11_OR_GREATER enum : size_type { npos = size_type(-1) }; #else enum { npos = size_type(-1) }; #endif private: struct not_in_view { const basic_string_view v; nssv_constexpr not_in_view( basic_string_view v ) : v( v ) {} nssv_constexpr bool operator()( CharT c ) const { return npos == v.find_first_of( c ); } }; nssv_constexpr size_type to_pos( const_iterator it ) const { return it == cend() ? npos : size_type( it - cbegin() ); } nssv_constexpr size_type to_pos( const_reverse_iterator it ) const { return it == crend() ? npos : size_type( crend() - it - 1 ); } nssv_constexpr const_reference data_at( size_type pos ) const { #if nssv_BETWEEN( nssv_COMPILER_GNUC_VERSION, 1, 500 ) return data_[pos]; #else return assert( pos < size() ), data_[pos]; #endif } private: const_pointer data_; size_type size_; public: #if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS template< class Allocator > basic_string_view( std::basic_string<CharT, Traits, Allocator> const & s ) nssv_noexcept : data_( s.data() ) , size_( s.size() ) {} #if nssv_HAVE_EXPLICIT_CONVERSION template< class Allocator > explicit operator std::basic_string<CharT, Traits, Allocator>() const { return to_string( Allocator() ); } #endif // nssv_HAVE_EXPLICIT_CONVERSION #if nssv_CPP11_OR_GREATER template< class Allocator = std::allocator<CharT> > std::basic_string<CharT, Traits, Allocator> to_string( Allocator const & a = Allocator() ) const { return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a ); } #else std::basic_string<CharT, Traits> to_string() const { return std::basic_string<CharT, Traits>( begin(), end() ); } template< class Allocator > std::basic_string<CharT, Traits, Allocator> to_string( Allocator const & a ) const { return std::basic_string<CharT, Traits, Allocator>( begin(), end(), a ); } #endif // nssv_CPP11_OR_GREATER #endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS }; // // Non-member functions: // // 24.4.3 Non-member comparison functions: // lexicographically compare two string views (function template): template< class CharT, class Traits > nssv_constexpr bool operator== ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) == 0 ; } template< class CharT, class Traits > nssv_constexpr bool operator!= ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) != 0 ; } template< class CharT, class Traits > nssv_constexpr bool operator< ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) < 0 ; } template< class CharT, class Traits > nssv_constexpr bool operator<= ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) <= 0 ; } template< class CharT, class Traits > nssv_constexpr bool operator> ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) > 0 ; } template< class CharT, class Traits > nssv_constexpr bool operator>= ( basic_string_view <CharT, Traits> lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.compare( rhs ) >= 0 ; } // Let S be basic_string_view<CharT, Traits>, and sv be an instance of S. // Implementations shall provide sufficient additional overloads marked // constexpr and noexcept so that an object t with an implicit conversion // to S can be compared according to Table 67. #if nssv_CPP11_OR_GREATER && ! nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 100, 141 ) #define nssv_BASIC_STRING_VIEW_I(T,U) typename std::decay< basic_string_view<T,U> >::type #if nssv_BETWEEN( nssv_COMPILER_MSVC_VERSION, 140, 150 ) # define nssv_MSVC_ORDER(x) , int=x #else # define nssv_MSVC_ORDER(x) /*, int=x*/ #endif // == template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator==( basic_string_view <CharT, Traits> lhs, nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs ) nssv_noexcept { return lhs.compare( rhs ) == 0; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator==( nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, basic_string_view <CharT, Traits> rhs ) nssv_noexcept { return lhs.size() == rhs.size() && lhs.compare( rhs ) == 0; } // != template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator!= ( basic_string_view < CharT, Traits > lhs, nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept { return lhs.size() != rhs.size() || lhs.compare( rhs ) != 0 ; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator!= ( nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, basic_string_view < CharT, Traits > rhs ) nssv_noexcept { return lhs.compare( rhs ) != 0 ; } // < template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator< ( basic_string_view < CharT, Traits > lhs, nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept { return lhs.compare( rhs ) < 0 ; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator< ( nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, basic_string_view < CharT, Traits > rhs ) nssv_noexcept { return lhs.compare( rhs ) < 0 ; } // <= template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator<= ( basic_string_view < CharT, Traits > lhs, nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept { return lhs.compare( rhs ) <= 0 ; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator<= ( nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, basic_string_view < CharT, Traits > rhs ) nssv_noexcept { return lhs.compare( rhs ) <= 0 ; } // > template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator> ( basic_string_view < CharT, Traits > lhs, nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept { return lhs.compare( rhs ) > 0 ; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator> ( nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, basic_string_view < CharT, Traits > rhs ) nssv_noexcept { return lhs.compare( rhs ) > 0 ; } // >= template< class CharT, class Traits nssv_MSVC_ORDER(1) > nssv_constexpr bool operator>= ( basic_string_view < CharT, Traits > lhs, nssv_BASIC_STRING_VIEW_I( CharT, Traits ) rhs ) nssv_noexcept { return lhs.compare( rhs ) >= 0 ; } template< class CharT, class Traits nssv_MSVC_ORDER(2) > nssv_constexpr bool operator>= ( nssv_BASIC_STRING_VIEW_I( CharT, Traits ) lhs, basic_string_view < CharT, Traits > rhs ) nssv_noexcept { return lhs.compare( rhs ) >= 0 ; } #undef nssv_MSVC_ORDER #undef nssv_BASIC_STRING_VIEW_I #endif // nssv_CPP11_OR_GREATER // 24.4.4 Inserters and extractors: namespace detail { template< class Stream > void write_padding( Stream & os, std::streamsize n ) { for ( std::streamsize i = 0; i < n; ++i ) os.rdbuf()->sputc( os.fill() ); } template< class Stream, class View > Stream & write_to_stream( Stream & os, View const & sv ) { typename Stream::sentry sentry( os ); if ( !os ) return os; const std::streamsize length = static_cast<std::streamsize>( sv.length() ); // Whether, and how, to pad: const bool pad = ( length < os.width() ); const bool left_pad = pad && ( os.flags() & std::ios_base::adjustfield ) == std::ios_base::right; if ( left_pad ) write_padding( os, os.width() - length ); // Write span characters: os.rdbuf()->sputn( sv.begin(), length ); if ( pad && !left_pad ) write_padding( os, os.width() - length ); // Reset output stream width: os.width( 0 ); return os; } } // namespace detail template< class CharT, class Traits > std::basic_ostream<CharT, Traits> & operator<<( std::basic_ostream<CharT, Traits>& os, basic_string_view <CharT, Traits> sv ) { return detail::write_to_stream( os, sv ); } // Several typedefs for common character types are provided: typedef basic_string_view<char> string_view; typedef basic_string_view<wchar_t> wstring_view; #if nssv_HAVE_WCHAR16_T typedef basic_string_view<char16_t> u16string_view; typedef basic_string_view<char32_t> u32string_view; #endif }} // namespace nonstd::sv_lite // // 24.4.6 Suffix for basic_string_view literals: // #if nssv_HAVE_USER_DEFINED_LITERALS namespace nonstd { nssv_inline_ns namespace literals { nssv_inline_ns namespace string_view_literals { #if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS nssv_constexpr nonstd::sv_lite::string_view operator "" sv( const char* str, size_t len ) nssv_noexcept // (1) { return nonstd::sv_lite::string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::u16string_view operator "" sv( const char16_t* str, size_t len ) nssv_noexcept // (2) { return nonstd::sv_lite::u16string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::u32string_view operator "" sv( const char32_t* str, size_t len ) nssv_noexcept // (3) { return nonstd::sv_lite::u32string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::wstring_view operator "" sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) { return nonstd::sv_lite::wstring_view{ str, len }; } #endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS #if nssv_CONFIG_USR_SV_OPERATOR nssv_constexpr nonstd::sv_lite::string_view operator "" _sv( const char* str, size_t len ) nssv_noexcept // (1) { return nonstd::sv_lite::string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::u16string_view operator "" _sv( const char16_t* str, size_t len ) nssv_noexcept // (2) { return nonstd::sv_lite::u16string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::u32string_view operator "" _sv( const char32_t* str, size_t len ) nssv_noexcept // (3) { return nonstd::sv_lite::u32string_view{ str, len }; } nssv_constexpr nonstd::sv_lite::wstring_view operator "" _sv( const wchar_t* str, size_t len ) nssv_noexcept // (4) { return nonstd::sv_lite::wstring_view{ str, len }; } #endif // nssv_CONFIG_USR_SV_OPERATOR }}} // namespace nonstd::literals::string_view_literals #endif // // Extensions for std::string: // #if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS namespace nonstd { namespace sv_lite { // Exclude MSVC 14 (19.00): it yields ambiguous to_string(): #if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 template< class CharT, class Traits, class Allocator = std::allocator<CharT> > std::basic_string<CharT, Traits, Allocator> to_string( basic_string_view<CharT, Traits> v, Allocator const & a = Allocator() ) { return std::basic_string<CharT,Traits, Allocator>( v.begin(), v.end(), a ); } #else template< class CharT, class Traits > std::basic_string<CharT, Traits> to_string( basic_string_view<CharT, Traits> v ) { return std::basic_string<CharT, Traits>( v.begin(), v.end() ); } template< class CharT, class Traits, class Allocator > std::basic_string<CharT, Traits, Allocator> to_string( basic_string_view<CharT, Traits> v, Allocator const & a ) { return std::basic_string<CharT, Traits, Allocator>( v.begin(), v.end(), a ); } #endif // nssv_CPP11_OR_GREATER template< class CharT, class Traits, class Allocator > basic_string_view<CharT, Traits> to_string_view( std::basic_string<CharT, Traits, Allocator> const & s ) { return basic_string_view<CharT, Traits>( s.data(), s.size() ); } }} // namespace nonstd::sv_lite #endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS // // make types and algorithms available in namespace nonstd: // namespace nonstd { using sv_lite::basic_string_view; using sv_lite::string_view; using sv_lite::wstring_view; #if nssv_HAVE_WCHAR16_T using sv_lite::u16string_view; #endif #if nssv_HAVE_WCHAR32_T using sv_lite::u32string_view; #endif // literal "sv" using sv_lite::operator==; using sv_lite::operator!=; using sv_lite::operator<; using sv_lite::operator<=; using sv_lite::operator>; using sv_lite::operator>=; using sv_lite::operator<<; #if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS using sv_lite::to_string; using sv_lite::to_string_view; #endif } // namespace nonstd // 24.4.5 Hash support (C++11): // Note: The hash value of a string view object is equal to the hash value of // the corresponding string object. #if nssv_HAVE_STD_HASH #include <functional> namespace std { template<> struct hash< nonstd::string_view > { public: std::size_t operator()( nonstd::string_view v ) const nssv_noexcept { return std::hash<std::string>()( std::string( v.data(), v.size() ) ); } }; template<> struct hash< nonstd::wstring_view > { public: std::size_t operator()( nonstd::wstring_view v ) const nssv_noexcept { return std::hash<std::wstring>()( std::wstring( v.data(), v.size() ) ); } }; template<> struct hash< nonstd::u16string_view > { public: std::size_t operator()( nonstd::u16string_view v ) const nssv_noexcept { return std::hash<std::u16string>()( std::u16string( v.data(), v.size() ) ); } }; template<> struct hash< nonstd::u32string_view > { public: std::size_t operator()( nonstd::u32string_view v ) const nssv_noexcept { return std::hash<std::u32string>()( std::u32string( v.data(), v.size() ) ); } }; } // namespace std #endif // nssv_HAVE_STD_HASH nssv_RESTORE_WARNINGS() #endif // nssv_HAVE_STD_STRING_VIEW #endif // NONSTD_SV_LITE_H_INCLUDED // If there is another version of Hedley, then the newer one // takes precedence. // See: https://github.com/nemequ/hedley /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson <evan@nemerson.com> * * To the extent possible under law, the author(s) have dedicated all * copyright and related and neighboring rights to this software to * the public domain worldwide. This software is distributed without * any warranty. * * For details, see <http://creativecommons.org/publicdomain/zero/1.0/>. * SPDX-License-Identifier: CC0-1.0 */ #if !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < 9) #if defined(HEDLEY_VERSION) # undef HEDLEY_VERSION #endif #define HEDLEY_VERSION 9 #if defined(HEDLEY_STRINGIFY_EX) # undef HEDLEY_STRINGIFY_EX #endif #define HEDLEY_STRINGIFY_EX(x) #x #if defined(HEDLEY_STRINGIFY) # undef HEDLEY_STRINGIFY #endif #define HEDLEY_STRINGIFY(x) HEDLEY_STRINGIFY_EX(x) #if defined(HEDLEY_CONCAT_EX) # undef HEDLEY_CONCAT_EX #endif #define HEDLEY_CONCAT_EX(a,b) a##b #if defined(HEDLEY_CONCAT) # undef HEDLEY_CONCAT #endif #define HEDLEY_CONCAT(a,b) HEDLEY_CONCAT_EX(a,b) #if defined(HEDLEY_VERSION_ENCODE) # undef HEDLEY_VERSION_ENCODE #endif #define HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) #if defined(HEDLEY_VERSION_DECODE_MAJOR) # undef HEDLEY_VERSION_DECODE_MAJOR #endif #define HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) #if defined(HEDLEY_VERSION_DECODE_MINOR) # undef HEDLEY_VERSION_DECODE_MINOR #endif #define HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) #if defined(HEDLEY_VERSION_DECODE_REVISION) # undef HEDLEY_VERSION_DECODE_REVISION #endif #define HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) #if defined(HEDLEY_GNUC_VERSION) # undef HEDLEY_GNUC_VERSION #endif #if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) # define HEDLEY_GNUC_VERSION HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #elif defined(__GNUC__) # define HEDLEY_GNUC_VERSION HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) #endif #if defined(HEDLEY_GNUC_VERSION_CHECK) # undef HEDLEY_GNUC_VERSION_CHECK #endif #if defined(HEDLEY_GNUC_VERSION) # define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (HEDLEY_GNUC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_MSVC_VERSION) # undef HEDLEY_MSVC_VERSION #endif #if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) # define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) #elif defined(_MSC_FULL_VER) # define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) #elif defined(_MSC_VER) # define HEDLEY_MSVC_VERSION HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) #endif #if defined(HEDLEY_MSVC_VERSION_CHECK) # undef HEDLEY_MSVC_VERSION_CHECK #endif #if !defined(_MSC_VER) # define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) #elif defined(_MSC_VER) && (_MSC_VER >= 1400) # define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) #elif defined(_MSC_VER) && (_MSC_VER >= 1200) # define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) #else # define HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) #endif #if defined(HEDLEY_INTEL_VERSION) # undef HEDLEY_INTEL_VERSION #endif #if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) # define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) #elif defined(__INTEL_COMPILER) # define HEDLEY_INTEL_VERSION HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif #if defined(HEDLEY_INTEL_VERSION_CHECK) # undef HEDLEY_INTEL_VERSION_CHECK #endif #if defined(HEDLEY_INTEL_VERSION) # define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (HEDLEY_INTEL_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_PGI_VERSION) # undef HEDLEY_PGI_VERSION #endif #if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) # define HEDLEY_PGI_VERSION HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) #endif #if defined(HEDLEY_PGI_VERSION_CHECK) # undef HEDLEY_PGI_VERSION_CHECK #endif #if defined(HEDLEY_PGI_VERSION) # define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (HEDLEY_PGI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_SUNPRO_VERSION) # undef HEDLEY_SUNPRO_VERSION #endif #if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) # define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) #elif defined(__SUNPRO_C) # define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) #elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) # define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) #elif defined(__SUNPRO_CC) # define HEDLEY_SUNPRO_VERSION HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) #endif #if defined(HEDLEY_SUNPRO_VERSION_CHECK) # undef HEDLEY_SUNPRO_VERSION_CHECK #endif #if defined(HEDLEY_SUNPRO_VERSION) # define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (HEDLEY_SUNPRO_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_EMSCRIPTEN_VERSION) # undef HEDLEY_EMSCRIPTEN_VERSION #endif #if defined(__EMSCRIPTEN__) # define HEDLEY_EMSCRIPTEN_VERSION HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) #endif #if defined(HEDLEY_EMSCRIPTEN_VERSION_CHECK) # undef HEDLEY_EMSCRIPTEN_VERSION_CHECK #endif #if defined(HEDLEY_EMSCRIPTEN_VERSION) # define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (HEDLEY_EMSCRIPTEN_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_ARM_VERSION) # undef HEDLEY_ARM_VERSION #endif #if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) # define HEDLEY_ARM_VERSION HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) #elif defined(__CC_ARM) && defined(__ARMCC_VERSION) # define HEDLEY_ARM_VERSION HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) #endif #if defined(HEDLEY_ARM_VERSION_CHECK) # undef HEDLEY_ARM_VERSION_CHECK #endif #if defined(HEDLEY_ARM_VERSION) # define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (HEDLEY_ARM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_IBM_VERSION) # undef HEDLEY_IBM_VERSION #endif #if defined(__ibmxl__) # define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) #elif defined(__xlC__) && defined(__xlC_ver__) # define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) #elif defined(__xlC__) # define HEDLEY_IBM_VERSION HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) #endif #if defined(HEDLEY_IBM_VERSION_CHECK) # undef HEDLEY_IBM_VERSION_CHECK #endif #if defined(HEDLEY_IBM_VERSION) # define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (HEDLEY_IBM_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_TI_VERSION) # undef HEDLEY_TI_VERSION #endif #if defined(__TI_COMPILER_VERSION__) # define HEDLEY_TI_VERSION HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) #endif #if defined(HEDLEY_TI_VERSION_CHECK) # undef HEDLEY_TI_VERSION_CHECK #endif #if defined(HEDLEY_TI_VERSION) # define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (HEDLEY_TI_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_CRAY_VERSION) # undef HEDLEY_CRAY_VERSION #endif #if defined(_CRAYC) # if defined(_RELEASE_PATCHLEVEL) # define HEDLEY_CRAY_VERSION HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) # else # define HEDLEY_CRAY_VERSION HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) # endif #endif #if defined(HEDLEY_CRAY_VERSION_CHECK) # undef HEDLEY_CRAY_VERSION_CHECK #endif #if defined(HEDLEY_CRAY_VERSION) # define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (HEDLEY_CRAY_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_IAR_VERSION) # undef HEDLEY_IAR_VERSION #endif #if defined(__IAR_SYSTEMS_ICC__) # if __VER__ > 1000 # define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) # else # define HEDLEY_IAR_VERSION HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) # endif #endif #if defined(HEDLEY_IAR_VERSION_CHECK) # undef HEDLEY_IAR_VERSION_CHECK #endif #if defined(HEDLEY_IAR_VERSION) # define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (HEDLEY_IAR_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_TINYC_VERSION) # undef HEDLEY_TINYC_VERSION #endif #if defined(__TINYC__) # define HEDLEY_TINYC_VERSION HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) #endif #if defined(HEDLEY_TINYC_VERSION_CHECK) # undef HEDLEY_TINYC_VERSION_CHECK #endif #if defined(HEDLEY_TINYC_VERSION) # define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (HEDLEY_TINYC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_DMC_VERSION) # undef HEDLEY_DMC_VERSION #endif #if defined(__DMC__) # define HEDLEY_DMC_VERSION HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) #endif #if defined(HEDLEY_DMC_VERSION_CHECK) # undef HEDLEY_DMC_VERSION_CHECK #endif #if defined(HEDLEY_DMC_VERSION) # define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (HEDLEY_DMC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_COMPCERT_VERSION) # undef HEDLEY_COMPCERT_VERSION #endif #if defined(__COMPCERT_VERSION__) # define HEDLEY_COMPCERT_VERSION HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) #endif #if defined(HEDLEY_COMPCERT_VERSION_CHECK) # undef HEDLEY_COMPCERT_VERSION_CHECK #endif #if defined(HEDLEY_COMPCERT_VERSION) # define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (HEDLEY_COMPCERT_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_PELLES_VERSION) # undef HEDLEY_PELLES_VERSION #endif #if defined(__POCC__) # define HEDLEY_PELLES_VERSION HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) #endif #if defined(HEDLEY_PELLES_VERSION_CHECK) # undef HEDLEY_PELLES_VERSION_CHECK #endif #if defined(HEDLEY_PELLES_VERSION) # define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (HEDLEY_PELLES_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_GCC_VERSION) # undef HEDLEY_GCC_VERSION #endif #if \ defined(HEDLEY_GNUC_VERSION) && \ !defined(__clang__) && \ !defined(HEDLEY_INTEL_VERSION) && \ !defined(HEDLEY_PGI_VERSION) && \ !defined(HEDLEY_ARM_VERSION) && \ !defined(HEDLEY_TI_VERSION) && \ !defined(__COMPCERT__) # define HEDLEY_GCC_VERSION HEDLEY_GNUC_VERSION #endif #if defined(HEDLEY_GCC_VERSION_CHECK) # undef HEDLEY_GCC_VERSION_CHECK #endif #if defined(HEDLEY_GCC_VERSION) # define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (HEDLEY_GCC_VERSION >= HEDLEY_VERSION_ENCODE(major, minor, patch)) #else # define HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) #endif #if defined(HEDLEY_HAS_ATTRIBUTE) # undef HEDLEY_HAS_ATTRIBUTE #endif #if defined(__has_attribute) # define HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) #else # define HEDLEY_HAS_ATTRIBUTE(attribute) (0) #endif #if defined(HEDLEY_GNUC_HAS_ATTRIBUTE) # undef HEDLEY_GNUC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) # define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else # define HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_ATTRIBUTE) # undef HEDLEY_GCC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) # define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) #else # define HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_CPP_ATTRIBUTE) # undef HEDLEY_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) # define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) #else # define HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) #endif #if defined(HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) # undef HEDLEY_GNUC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) # define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else # define HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_CPP_ATTRIBUTE) # undef HEDLEY_GCC_HAS_CPP_ATTRIBUTE #endif #if defined(__has_cpp_attribute) && defined(__cplusplus) # define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) #else # define HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_BUILTIN) # undef HEDLEY_HAS_BUILTIN #endif #if defined(__has_builtin) # define HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) #else # define HEDLEY_HAS_BUILTIN(builtin) (0) #endif #if defined(HEDLEY_GNUC_HAS_BUILTIN) # undef HEDLEY_GNUC_HAS_BUILTIN #endif #if defined(__has_builtin) # define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else # define HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_BUILTIN) # undef HEDLEY_GCC_HAS_BUILTIN #endif #if defined(__has_builtin) # define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) #else # define HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_FEATURE) # undef HEDLEY_HAS_FEATURE #endif #if defined(__has_feature) # define HEDLEY_HAS_FEATURE(feature) __has_feature(feature) #else # define HEDLEY_HAS_FEATURE(feature) (0) #endif #if defined(HEDLEY_GNUC_HAS_FEATURE) # undef HEDLEY_GNUC_HAS_FEATURE #endif #if defined(__has_feature) # define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else # define HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_FEATURE) # undef HEDLEY_GCC_HAS_FEATURE #endif #if defined(__has_feature) # define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) #else # define HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_EXTENSION) # undef HEDLEY_HAS_EXTENSION #endif #if defined(__has_extension) # define HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) #else # define HEDLEY_HAS_EXTENSION(extension) (0) #endif #if defined(HEDLEY_GNUC_HAS_EXTENSION) # undef HEDLEY_GNUC_HAS_EXTENSION #endif #if defined(__has_extension) # define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else # define HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_EXTENSION) # undef HEDLEY_GCC_HAS_EXTENSION #endif #if defined(__has_extension) # define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) #else # define HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_DECLSPEC_ATTRIBUTE) # undef HEDLEY_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) # define HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) #else # define HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) #endif #if defined(HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) # undef HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) # define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else # define HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) # undef HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE #endif #if defined(__has_declspec_attribute) # define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) #else # define HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_HAS_WARNING) # undef HEDLEY_HAS_WARNING #endif #if defined(__has_warning) # define HEDLEY_HAS_WARNING(warning) __has_warning(warning) #else # define HEDLEY_HAS_WARNING(warning) (0) #endif #if defined(HEDLEY_GNUC_HAS_WARNING) # undef HEDLEY_GNUC_HAS_WARNING #endif #if defined(__has_warning) # define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else # define HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_GCC_HAS_WARNING) # undef HEDLEY_GCC_HAS_WARNING #endif #if defined(__has_warning) # define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) #else # define HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ defined(__clang__) || \ HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_TI_VERSION_CHECK(6,0,0) || \ HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ (HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) # define HEDLEY_PRAGMA(value) _Pragma(#value) #elif HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define HEDLEY_PRAGMA(value) __pragma(value) #else # define HEDLEY_PRAGMA(value) #endif #if defined(HEDLEY_DIAGNOSTIC_PUSH) # undef HEDLEY_DIAGNOSTIC_PUSH #endif #if defined(HEDLEY_DIAGNOSTIC_POP) # undef HEDLEY_DIAGNOSTIC_POP #endif #if defined(__clang__) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") # define HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") #elif HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") # define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #elif HEDLEY_GCC_VERSION_CHECK(4,6,0) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") # define HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") #elif HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) # define HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) #elif HEDLEY_ARM_VERSION_CHECK(5,6,0) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") # define HEDLEY_DIAGNOSTIC_POP _Pragma("pop") #elif HEDLEY_TI_VERSION_CHECK(8,1,0) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") # define HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") #elif HEDLEY_PELLES_VERSION_CHECK(2,90,0) # define HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") # define HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") #else # define HEDLEY_DIAGNOSTIC_PUSH # define HEDLEY_DIAGNOSTIC_POP #endif #if defined(HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) # undef HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if HEDLEY_HAS_WARNING("-Wdeprecated-declarations") # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") #elif HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") #elif HEDLEY_PGI_VERSION_CHECK(17,10,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif HEDLEY_GCC_VERSION_CHECK(4,3,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) #elif HEDLEY_TI_VERSION_CHECK(8,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") #elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") #elif HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") #elif HEDLEY_PELLES_VERSION_CHECK(2,90,0) # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") #else # define HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif #if defined(HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) # undef HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") #elif HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") #elif HEDLEY_PGI_VERSION_CHECK(17,10,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") #elif HEDLEY_GCC_VERSION_CHECK(4,3,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") #elif HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) #elif HEDLEY_TI_VERSION_CHECK(8,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") #else # define HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif #if defined(HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) # undef HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if HEDLEY_HAS_WARNING("-Wcast-qual") # define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") #elif HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") #elif HEDLEY_GCC_VERSION_CHECK(3,0,0) # define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") #else # define HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif #if defined(HEDLEY_DEPRECATED) # undef HEDLEY_DEPRECATED #endif #if defined(HEDLEY_DEPRECATED_FOR) # undef HEDLEY_DEPRECATED_FOR #endif #if defined(__cplusplus) && (__cplusplus >= 201402L) # define HEDLEY_DEPRECATED(since) [[deprecated("Since " #since)]] # define HEDLEY_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]] #elif \ HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ HEDLEY_TI_VERSION_CHECK(8,3,0) # define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) # define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) #elif \ HEDLEY_HAS_ATTRIBUTE(deprecated) || \ HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) # define HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) #elif HEDLEY_MSVC_VERSION_CHECK(14,0,0) # define HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) # define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) #elif \ HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ HEDLEY_PELLES_VERSION_CHECK(6,50,0) # define HEDLEY_DEPRECATED(since) _declspec(deprecated) # define HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_DEPRECATED(since) _Pragma("deprecated") # define HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") #else # define HEDLEY_DEPRECATED(since) # define HEDLEY_DEPRECATED_FOR(since, replacement) #endif #if defined(HEDLEY_UNAVAILABLE) # undef HEDLEY_UNAVAILABLE #endif #if \ HEDLEY_HAS_ATTRIBUTE(warning) || \ HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) #else # define HEDLEY_UNAVAILABLE(available_since) #endif #if defined(HEDLEY_WARN_UNUSED_RESULT) # undef HEDLEY_WARN_UNUSED_RESULT #endif #if defined(__cplusplus) && (__cplusplus >= 201703L) # define HEDLEY_WARN_UNUSED_RESULT [[nodiscard]] #elif \ HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ (HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ HEDLEY_PGI_VERSION_CHECK(17,10,0) # define HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #elif defined(_Check_return_) /* SAL */ # define HEDLEY_WARN_UNUSED_RESULT _Check_return_ #else # define HEDLEY_WARN_UNUSED_RESULT #endif #if defined(HEDLEY_SENTINEL) # undef HEDLEY_SENTINEL #endif #if \ HEDLEY_HAS_ATTRIBUTE(sentinel) || \ HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(5,4,0) # define HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) #else # define HEDLEY_SENTINEL(position) #endif #if defined(HEDLEY_NO_RETURN) # undef HEDLEY_NO_RETURN #endif #if HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_NO_RETURN __noreturn #elif HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L # define HEDLEY_NO_RETURN _Noreturn #elif defined(__cplusplus) && (__cplusplus >= 201103L) # define HEDLEY_NO_RETURN [[noreturn]] #elif \ HEDLEY_HAS_ATTRIBUTE(noreturn) || \ HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(18,0,0) || \ (HEDLEY_TI_VERSION_CHECK(17,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif HEDLEY_MSVC_VERSION_CHECK(13,10,0) # define HEDLEY_NO_RETURN __declspec(noreturn) #elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) # define HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") #elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) # define HEDLEY_NO_RETURN __attribute((noreturn)) #elif HEDLEY_PELLES_VERSION_CHECK(9,0,0) # define HEDLEY_NO_RETURN __declspec(noreturn) #else # define HEDLEY_NO_RETURN #endif #if defined(HEDLEY_UNREACHABLE) # undef HEDLEY_UNREACHABLE #endif #if defined(HEDLEY_UNREACHABLE_RETURN) # undef HEDLEY_UNREACHABLE_RETURN #endif #if \ (HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(HEDLEY_ARM_VERSION))) || \ HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_IBM_VERSION_CHECK(13,1,5) # define HEDLEY_UNREACHABLE() __builtin_unreachable() #elif HEDLEY_MSVC_VERSION_CHECK(13,10,0) # define HEDLEY_UNREACHABLE() __assume(0) #elif HEDLEY_TI_VERSION_CHECK(6,0,0) # if defined(__cplusplus) # define HEDLEY_UNREACHABLE() std::_nassert(0) # else # define HEDLEY_UNREACHABLE() _nassert(0) # endif # define HEDLEY_UNREACHABLE_RETURN(value) return value #elif defined(EXIT_FAILURE) # define HEDLEY_UNREACHABLE() abort() #else # define HEDLEY_UNREACHABLE() # define HEDLEY_UNREACHABLE_RETURN(value) return value #endif #if !defined(HEDLEY_UNREACHABLE_RETURN) # define HEDLEY_UNREACHABLE_RETURN(value) HEDLEY_UNREACHABLE() #endif #if defined(HEDLEY_ASSUME) # undef HEDLEY_ASSUME #endif #if \ HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_ASSUME(expr) __assume(expr) #elif HEDLEY_HAS_BUILTIN(__builtin_assume) # define HEDLEY_ASSUME(expr) __builtin_assume(expr) #elif HEDLEY_TI_VERSION_CHECK(6,0,0) # if defined(__cplusplus) # define HEDLEY_ASSUME(expr) std::_nassert(expr) # else # define HEDLEY_ASSUME(expr) _nassert(expr) # endif #elif \ (HEDLEY_HAS_BUILTIN(__builtin_unreachable) && !defined(HEDLEY_ARM_VERSION)) || \ HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_IBM_VERSION_CHECK(13,1,5) # define HEDLEY_ASSUME(expr) ((void) ((expr) ? 1 : (__builtin_unreachable(), 1))) #else # define HEDLEY_ASSUME(expr) ((void) (expr)) #endif HEDLEY_DIAGNOSTIC_PUSH #if \ HEDLEY_HAS_WARNING("-Wvariadic-macros") || \ HEDLEY_GCC_VERSION_CHECK(4,0,0) # if defined(__clang__) # pragma clang diagnostic ignored "-Wvariadic-macros" # elif defined(HEDLEY_GCC_VERSION) # pragma GCC diagnostic ignored "-Wvariadic-macros" # endif #endif #if defined(HEDLEY_NON_NULL) # undef HEDLEY_NON_NULL #endif #if \ HEDLEY_HAS_ATTRIBUTE(nonnull) || \ HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) # define HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) #else # define HEDLEY_NON_NULL(...) #endif HEDLEY_DIAGNOSTIC_POP #if defined(HEDLEY_PRINTF_FORMAT) # undef HEDLEY_PRINTF_FORMAT #endif #if defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) # define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) #elif defined(__MINGW32__) && HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) # define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) #elif \ HEDLEY_HAS_ATTRIBUTE(format) || \ HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) #elif HEDLEY_PELLES_VERSION_CHECK(6,0,0) # define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) #else # define HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) #endif #if defined(HEDLEY_CONSTEXPR) # undef HEDLEY_CONSTEXPR #endif #if defined(__cplusplus) # if __cplusplus >= 201103L # define HEDLEY_CONSTEXPR constexpr # endif #endif #if !defined(HEDLEY_CONSTEXPR) # define HEDLEY_CONSTEXPR #endif #if defined(HEDLEY_PREDICT) # undef HEDLEY_PREDICT #endif #if defined(HEDLEY_LIKELY) # undef HEDLEY_LIKELY #endif #if defined(HEDLEY_UNLIKELY) # undef HEDLEY_UNLIKELY #endif #if defined(HEDLEY_UNPREDICTABLE) # undef HEDLEY_UNPREDICTABLE #endif #if HEDLEY_HAS_BUILTIN(__builtin_unpredictable) # define HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable(!!(expr)) #endif #if \ HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ HEDLEY_GCC_VERSION_CHECK(9,0,0) # define HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(expr, value, probability) # define HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1, probability) # define HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0, probability) # define HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) # define HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) # if !defined(HEDLEY_BUILTIN_UNPREDICTABLE) # define HEDLEY_BUILTIN_UNPREDICTABLE(expr) __builtin_expect_with_probability(!!(expr), 1, 0.5) # endif #elif \ HEDLEY_HAS_BUILTIN(__builtin_expect) || \ HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ (HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(6,1,0) || \ HEDLEY_TINYC_VERSION_CHECK(0,9,27) # define HEDLEY_PREDICT(expr, expected, probability) \ (((probability) >= 0.9) ? __builtin_expect(!!(expr), (expected)) : (((void) (expected)), !!(expr))) # define HEDLEY_PREDICT_TRUE(expr, probability) \ (__extension__ ({ \ HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ })) # define HEDLEY_PREDICT_FALSE(expr, probability) \ (__extension__ ({ \ HEDLEY_CONSTEXPR double hedley_probability_ = (probability); \ ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ })) # define HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) # define HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #else # define HEDLEY_PREDICT(expr, expected, probability) (((void) (expected)), !!(expr)) # define HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) # define HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) # define HEDLEY_LIKELY(expr) (!!(expr)) # define HEDLEY_UNLIKELY(expr) (!!(expr)) #endif #if !defined(HEDLEY_UNPREDICTABLE) # define HEDLEY_UNPREDICTABLE(expr) HEDLEY_PREDICT(expr, 1, 0.5) #endif #if defined(HEDLEY_MALLOC) # undef HEDLEY_MALLOC #endif #if \ HEDLEY_HAS_ATTRIBUTE(malloc) || \ HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_MALLOC __attribute__((__malloc__)) #elif HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) # define HEDLEY_MALLOC __declspec(restrict) #else # define HEDLEY_MALLOC #endif #if defined(HEDLEY_PURE) # undef HEDLEY_PURE #endif #if \ HEDLEY_HAS_ATTRIBUTE(pure) || \ HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ HEDLEY_PGI_VERSION_CHECK(17,10,0) # define HEDLEY_PURE __attribute__((__pure__)) #elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) # define HEDLEY_PURE _Pragma("FUNC_IS_PURE;") #else # define HEDLEY_PURE #endif #if defined(HEDLEY_CONST) # undef HEDLEY_CONST #endif #if \ HEDLEY_HAS_ATTRIBUTE(const) || \ HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ HEDLEY_PGI_VERSION_CHECK(17,10,0) # define HEDLEY_CONST __attribute__((__const__)) #else # define HEDLEY_CONST HEDLEY_PURE #endif #if defined(HEDLEY_RESTRICT) # undef HEDLEY_RESTRICT #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) # define HEDLEY_RESTRICT restrict #elif \ HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ defined(__clang__) # define HEDLEY_RESTRICT __restrict #elif HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) # define HEDLEY_RESTRICT _Restrict #else # define HEDLEY_RESTRICT #endif #if defined(HEDLEY_INLINE) # undef HEDLEY_INLINE #endif #if \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ (defined(__cplusplus) && (__cplusplus >= 199711L)) # define HEDLEY_INLINE inline #elif \ defined(HEDLEY_GCC_VERSION) || \ HEDLEY_ARM_VERSION_CHECK(6,2,0) # define HEDLEY_INLINE __inline__ #elif \ HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) # define HEDLEY_INLINE __inline #else # define HEDLEY_INLINE #endif #if defined(HEDLEY_ALWAYS_INLINE) # undef HEDLEY_ALWAYS_INLINE #endif #if \ HEDLEY_HAS_ATTRIBUTE(always_inline) || \ HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) HEDLEY_INLINE #elif HEDLEY_MSVC_VERSION_CHECK(12,0,0) # define HEDLEY_ALWAYS_INLINE __forceinline #elif HEDLEY_TI_VERSION_CHECK(7,0,0) && defined(__cplusplus) # define HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") #else # define HEDLEY_ALWAYS_INLINE HEDLEY_INLINE #endif #if defined(HEDLEY_NEVER_INLINE) # undef HEDLEY_NEVER_INLINE #endif #if \ HEDLEY_HAS_ATTRIBUTE(noinline) || \ HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_NEVER_INLINE __attribute__((__noinline__)) #elif HEDLEY_MSVC_VERSION_CHECK(13,10,0) # define HEDLEY_NEVER_INLINE __declspec(noinline) #elif HEDLEY_PGI_VERSION_CHECK(10,2,0) # define HEDLEY_NEVER_INLINE _Pragma("noinline") #elif HEDLEY_TI_VERSION_CHECK(6,0,0) && defined(__cplusplus) # define HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_NEVER_INLINE _Pragma("inline=never") #elif HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) # define HEDLEY_NEVER_INLINE __attribute((noinline)) #elif HEDLEY_PELLES_VERSION_CHECK(9,0,0) # define HEDLEY_NEVER_INLINE __declspec(noinline) #else # define HEDLEY_NEVER_INLINE #endif #if defined(HEDLEY_PRIVATE) # undef HEDLEY_PRIVATE #endif #if defined(HEDLEY_PUBLIC) # undef HEDLEY_PUBLIC #endif #if defined(HEDLEY_IMPORT) # undef HEDLEY_IMPORT #endif #if defined(_WIN32) || defined(__CYGWIN__) # define HEDLEY_PRIVATE # define HEDLEY_PUBLIC __declspec(dllexport) # define HEDLEY_IMPORT __declspec(dllimport) #else # if \ HEDLEY_HAS_ATTRIBUTE(visibility) || \ HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ HEDLEY_TI_VERSION_CHECK(8,0,0) || \ (HEDLEY_TI_VERSION_CHECK(7,3,0) && defined(__TI_EABI__) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) # define HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) # define HEDLEY_PUBLIC __attribute__((__visibility__("default"))) # else # define HEDLEY_PRIVATE # define HEDLEY_PUBLIC # endif # define HEDLEY_IMPORT extern #endif #if defined(HEDLEY_NO_THROW) # undef HEDLEY_NO_THROW #endif #if \ HEDLEY_HAS_ATTRIBUTE(nothrow) || \ HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_NO_THROW __attribute__((__nothrow__)) #elif \ HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) # define HEDLEY_NO_THROW __declspec(nothrow) #else # define HEDLEY_NO_THROW #endif #if defined(HEDLEY_FALL_THROUGH) # undef HEDLEY_FALL_THROUGH #endif #if \ defined(__cplusplus) && \ (!defined(HEDLEY_SUNPRO_VERSION) || HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ !defined(HEDLEY_PGI_VERSION) # if \ (__cplusplus >= 201703L) || \ ((__cplusplus >= 201103L) && HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)) # define HEDLEY_FALL_THROUGH [[fallthrough]] # elif (__cplusplus >= 201103L) && HEDLEY_HAS_CPP_ATTRIBUTE(clang::fallthrough) # define HEDLEY_FALL_THROUGH [[clang::fallthrough]] # elif (__cplusplus >= 201103L) && HEDLEY_GCC_VERSION_CHECK(7,0,0) # define HEDLEY_FALL_THROUGH [[gnu::fallthrough]] # endif #endif #if !defined(HEDLEY_FALL_THROUGH) # if HEDLEY_GNUC_HAS_ATTRIBUTE(fallthrough,7,0,0) && !defined(HEDLEY_PGI_VERSION) # define HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) # elif defined(__fallthrough) /* SAL */ # define HEDLEY_FALL_THROUGH __fallthrough # else # define HEDLEY_FALL_THROUGH # endif #endif #if defined(HEDLEY_RETURNS_NON_NULL) # undef HEDLEY_RETURNS_NON_NULL #endif #if \ HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ HEDLEY_GCC_VERSION_CHECK(4,9,0) # define HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) #elif defined(_Ret_notnull_) /* SAL */ # define HEDLEY_RETURNS_NON_NULL _Ret_notnull_ #else # define HEDLEY_RETURNS_NON_NULL #endif #if defined(HEDLEY_ARRAY_PARAM) # undef HEDLEY_ARRAY_PARAM #endif #if \ defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ !defined(__STDC_NO_VLA__) && \ !defined(__cplusplus) && \ !defined(HEDLEY_PGI_VERSION) && \ !defined(HEDLEY_TINYC_VERSION) # define HEDLEY_ARRAY_PARAM(name) (name) #else # define HEDLEY_ARRAY_PARAM(name) #endif #if defined(HEDLEY_IS_CONSTANT) # undef HEDLEY_IS_CONSTANT #endif #if defined(HEDLEY_REQUIRE_CONSTEXPR) # undef HEDLEY_REQUIRE_CONSTEXPR #endif /* Note the double-underscore. For internal use only; no API * guarantees! */ #if defined(HEDLEY__IS_CONSTEXPR) # undef HEDLEY__IS_CONSTEXPR #endif #if \ HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ HEDLEY_TI_VERSION_CHECK(6,1,0) || \ HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) || \ HEDLEY_CRAY_VERSION_CHECK(8,1,0) # define HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) #endif #if !defined(__cplusplus) # if \ HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ HEDLEY_TINYC_VERSION_CHECK(0,9,24) # if defined(__INTPTR_TYPE__) # define HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) # else # include <stdint.h> # define HEDLEY__IS_CONSTEXPR(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) # endif # elif \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(HEDLEY_SUNPRO_VERSION) && !defined(HEDLEY_PGI_VERSION)) || \ HEDLEY_HAS_EXTENSION(c_generic_selections) || \ HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ HEDLEY_ARM_VERSION_CHECK(5,3,0) # if defined(__INTPTR_TYPE__) # define HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) # else # include <stdint.h> # define HEDLEY__IS_CONSTEXPR(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) # endif # elif \ defined(HEDLEY_GCC_VERSION) || \ defined(HEDLEY_INTEL_VERSION) || \ defined(HEDLEY_TINYC_VERSION) || \ defined(HEDLEY_TI_VERSION) || \ defined(__clang__) # define HEDLEY__IS_CONSTEXPR(expr) ( \ sizeof(void) != \ sizeof(*( \ 1 ? \ ((void*) ((expr) * 0L) ) : \ ((struct { char v[sizeof(void) * 2]; } *) 1) \ ) \ ) \ ) # endif #endif #if defined(HEDLEY__IS_CONSTEXPR) # if !defined(HEDLEY_IS_CONSTANT) # define HEDLEY_IS_CONSTANT(expr) HEDLEY__IS_CONSTEXPR(expr) # endif # define HEDLEY_REQUIRE_CONSTEXPR(expr) (HEDLEY__IS_CONSTEXPR(expr) ? (expr) : (-1)) #else # if !defined(HEDLEY_IS_CONSTANT) # define HEDLEY_IS_CONSTANT(expr) (0) # endif # define HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) #endif #if defined(HEDLEY_BEGIN_C_DECLS) # undef HEDLEY_BEGIN_C_DECLS #endif #if defined(HEDLEY_END_C_DECLS) # undef HEDLEY_END_C_DECLS #endif #if defined(HEDLEY_C_DECL) # undef HEDLEY_C_DECL #endif #if defined(__cplusplus) # define HEDLEY_BEGIN_C_DECLS extern "C" { # define HEDLEY_END_C_DECLS } # define HEDLEY_C_DECL extern "C" #else # define HEDLEY_BEGIN_C_DECLS # define HEDLEY_END_C_DECLS # define HEDLEY_C_DECL #endif #if defined(HEDLEY_STATIC_ASSERT) # undef HEDLEY_STATIC_ASSERT #endif #if \ !defined(__cplusplus) && ( \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ HEDLEY_HAS_FEATURE(c_static_assert) || \ HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ defined(_Static_assert) \ ) # define HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) #elif \ (defined(__cplusplus) && (__cplusplus >= 201703L)) || \ HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ (defined(__cplusplus) && HEDLEY_TI_VERSION_CHECK(8,3,0)) # define HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr, message) #elif defined(__cplusplus) && (__cplusplus >= 201103L) # define HEDLEY_STATIC_ASSERT(expr, message) static_assert(expr) #else # define HEDLEY_STATIC_ASSERT(expr, message) #endif #if defined(HEDLEY_CONST_CAST) # undef HEDLEY_CONST_CAST #endif #if defined(__cplusplus) # define HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr)) #elif \ HEDLEY_HAS_WARNING("-Wcast-qual") || \ HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ HEDLEY_DIAGNOSTIC_PUSH \ HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ ((T) (expr)); \ HEDLEY_DIAGNOSTIC_POP \ })) #else # define HEDLEY_CONST_CAST(T, expr) ((T) (expr)) #endif #if defined(HEDLEY_REINTERPRET_CAST) # undef HEDLEY_REINTERPRET_CAST #endif #if defined(__cplusplus) # define HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr)) #else # define HEDLEY_REINTERPRET_CAST(T, expr) (*((T*) &(expr))) #endif #if defined(HEDLEY_STATIC_CAST) # undef HEDLEY_STATIC_CAST #endif #if defined(__cplusplus) # define HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr)) #else # define HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) #endif #if defined(HEDLEY_CPP_CAST) # undef HEDLEY_CPP_CAST #endif #if defined(__cplusplus) # define HEDLEY_CPP_CAST(T, expr) static_cast<T>(expr) #else # define HEDLEY_CPP_CAST(T, expr) (expr) #endif #if defined(HEDLEY_MESSAGE) # undef HEDLEY_MESSAGE #endif #if HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define HEDLEY_MESSAGE(msg) \ HEDLEY_DIAGNOSTIC_PUSH \ HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ HEDLEY_PRAGMA(message msg) \ HEDLEY_DIAGNOSTIC_POP #elif \ HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message msg) #elif HEDLEY_CRAY_VERSION_CHECK(5,0,0) # define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(_CRI message msg) #elif HEDLEY_IAR_VERSION_CHECK(8,0,0) # define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg)) #elif HEDLEY_PELLES_VERSION_CHECK(2,0,0) # define HEDLEY_MESSAGE(msg) HEDLEY_PRAGMA(message(msg)) #else # define HEDLEY_MESSAGE(msg) #endif #if defined(HEDLEY_WARNING) # undef HEDLEY_WARNING #endif #if HEDLEY_HAS_WARNING("-Wunknown-pragmas") # define HEDLEY_WARNING(msg) \ HEDLEY_DIAGNOSTIC_PUSH \ HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ HEDLEY_PRAGMA(clang warning msg) \ HEDLEY_DIAGNOSTIC_POP #elif \ HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ HEDLEY_PGI_VERSION_CHECK(18,4,0) # define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(GCC warning msg) #elif HEDLEY_MSVC_VERSION_CHECK(15,0,0) # define HEDLEY_WARNING(msg) HEDLEY_PRAGMA(message(msg)) #else # define HEDLEY_WARNING(msg) HEDLEY_MESSAGE(msg) #endif #if defined(HEDLEY_REQUIRE_MSG) # undef HEDLEY_REQUIRE_MSG #endif #if HEDLEY_HAS_ATTRIBUTE(diagnose_if) # if HEDLEY_HAS_WARNING("-Wgcc-compat") # define HEDLEY_REQUIRE_MSG(expr, msg) \ HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ __attribute__((__diagnose_if__(!(expr), msg, "error"))) \ HEDLEY_DIAGNOSTIC_POP # else # define HEDLEY_REQUIRE_MSG(expr, msg) __attribute__((__diagnose_if__(!(expr), msg, "error"))) # endif #else # define HEDLEY_REQUIRE_MSG(expr, msg) #endif #if defined(HEDLEY_REQUIRE) # undef HEDLEY_REQUIRE #endif #define HEDLEY_REQUIRE(expr) HEDLEY_REQUIRE_MSG(expr, #expr) #if defined(HEDLEY_FLAGS) # undef HEDLEY_FLAGS #endif #if HEDLEY_HAS_ATTRIBUTE(flag_enum) # define HEDLEY_FLAGS __attribute__((__flag_enum__)) #endif #if defined(HEDLEY_FLAGS_CAST) # undef HEDLEY_FLAGS_CAST #endif #if HEDLEY_INTEL_VERSION_CHECK(19,0,0) # define HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("warning(disable:188)") \ ((T) (expr)); \ HEDLEY_DIAGNOSTIC_POP \ })) #else # define HEDLEY_FLAGS_CAST(T, expr) HEDLEY_STATIC_CAST(T, expr) #endif /* Remaining macros are deprecated. */ #if defined(HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) # undef HEDLEY_GCC_NOT_CLANG_VERSION_CHECK #endif #if defined(__clang__) # define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) #else # define HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif #if defined(HEDLEY_CLANG_HAS_ATTRIBUTE) # undef HEDLEY_CLANG_HAS_ATTRIBUTE #endif #define HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) HEDLEY_HAS_ATTRIBUTE(attribute) #if defined(HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) # undef HEDLEY_CLANG_HAS_CPP_ATTRIBUTE #endif #define HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) HEDLEY_HAS_CPP_ATTRIBUTE(attribute) #if defined(HEDLEY_CLANG_HAS_BUILTIN) # undef HEDLEY_CLANG_HAS_BUILTIN #endif #define HEDLEY_CLANG_HAS_BUILTIN(builtin) HEDLEY_HAS_BUILTIN(builtin) #if defined(HEDLEY_CLANG_HAS_FEATURE) # undef HEDLEY_CLANG_HAS_FEATURE #endif #define HEDLEY_CLANG_HAS_FEATURE(feature) HEDLEY_HAS_FEATURE(feature) #if defined(HEDLEY_CLANG_HAS_EXTENSION) # undef HEDLEY_CLANG_HAS_EXTENSION #endif #define HEDLEY_CLANG_HAS_EXTENSION(extension) HEDLEY_HAS_EXTENSION(extension) #if defined(HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) # undef HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE #endif #define HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) #if defined(HEDLEY_CLANG_HAS_WARNING) # undef HEDLEY_CLANG_HAS_WARNING #endif #define HEDLEY_CLANG_HAS_WARNING(warning) HEDLEY_HAS_WARNING(warning) #endif /* !defined(HEDLEY_VERSION) || (HEDLEY_VERSION < X) */ namespace csv { #ifdef _MSC_VER #pragma region Compatibility Macros #endif /** * @def IF_CONSTEXPR * Expands to `if constexpr` in C++17 and `if` otherwise * * @def CONSTEXPR_VALUE * Expands to `constexpr` in C++17 and `const` otherwise. * Mainly used for global variables. * * @def CONSTEXPR * Expands to `constexpr` in decent compilers and `inline` otherwise. * Intended for functions and methods. */ #define STATIC_ASSERT(x) static_assert(x, "Assertion failed") #if CMAKE_CXX_STANDARD == 17 || __cplusplus >= 201703L #define CSV_HAS_CXX17 #endif #if CMAKE_CXX_STANDARD >= 14 || __cplusplus >= 201402L #define CSV_HAS_CXX14 #endif #ifdef CSV_HAS_CXX17 #include <string_view> /** @typedef string_view * The string_view class used by this library. */ using string_view = std::string_view; #else /** @typedef string_view * The string_view class used by this library. */ using string_view = nonstd::string_view; #endif #ifdef CSV_HAS_CXX17 #define IF_CONSTEXPR if constexpr #define CONSTEXPR_VALUE constexpr #define CONSTEXPR_17 constexpr #else #define IF_CONSTEXPR if #define CONSTEXPR_VALUE const #define CONSTEXPR_17 inline #endif #ifdef CSV_HAS_CXX14 template<bool B, class T = void> using enable_if_t = std::enable_if_t<B, T>; #define CONSTEXPR_14 constexpr #define CONSTEXPR_VALUE_14 constexpr #else template<bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; #define CONSTEXPR_14 inline #define CONSTEXPR_VALUE_14 const #endif // Resolves g++ bug with regard to constexpr methods // See: https://stackoverflow.com/questions/36489369/constexpr-non-static-member-function-with-non-constexpr-constructor-gcc-clang-d #if defined __GNUC__ && !defined __clang__ #if (__GNUC__ >= 7 &&__GNUC_MINOR__ >= 2) || (__GNUC__ >= 8) #define CONSTEXPR constexpr #endif #else #ifdef CSV_HAS_CXX17 #define CONSTEXPR constexpr #endif #endif #ifndef CONSTEXPR #define CONSTEXPR inline #endif #ifdef _MSC_VER #pragma endregion #endif namespace internals { // Get operating system specific details #if defined(_WIN32) inline int getpagesize() { _SYSTEM_INFO sys_info = {}; GetSystemInfo(&sys_info); return std::max(sys_info.dwPageSize, sys_info.dwAllocationGranularity); } const int CSV_PAGE_SIZE = getpagesize(); #else const int CSV_PAGE_SIZE = getpagesize(); /** Size of a memory page in bytes. Used by * csv::internals::CSVFieldArray when allocating blocks. */ #endif /** For functions that lazy load a large CSV, this determines how * many bytes are read at a time */ constexpr size_t ITERATION_CHUNK_SIZE = 10000000; // 10MB template<typename T> inline bool is_equal(T a, T b, T epsilon = 0.001) { /** Returns true if two floating point values are about the same */ static_assert(std::is_floating_point<T>::value, "T must be a floating point type."); return std::abs(a - b) < epsilon; } /** @typedef ParseFlags * An enum used for describing the significance of each character * with respect to CSV parsing * * @see quote_escape_flag */ enum class ParseFlags { QUOTE_ESCAPE_QUOTE = 0, /**< A quote inside or terminating a quote_escaped field */ QUOTE = 2 | 1, /**< Characters which may signify a quote escape */ NOT_SPECIAL = 4, /**< Characters with no special meaning or escaped delimiters and newlines */ DELIMITER = 4 | 2, /**< Characters which signify a new field */ NEWLINE = 4 | 2 | 1 /**< Characters which signify a new row */ }; /** Transform the ParseFlags given the context of whether or not the current * field is quote escaped */ constexpr ParseFlags quote_escape_flag(ParseFlags flag, bool quote_escape) noexcept { return (ParseFlags)((int)flag & ~((int)ParseFlags::QUOTE * quote_escape)); } // Assumed to be true by parsing functions: allows for testing // if an item is DELIMITER or NEWLINE with a >= statement STATIC_ASSERT(ParseFlags::DELIMITER < ParseFlags::NEWLINE); /** Optimizations for reducing branching in parsing loop * * Idea: The meaning of all non-quote characters changes depending * on whether or not the parser is in a quote-escaped mode (0 or 1) */ STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, false) == ParseFlags::NOT_SPECIAL); STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, false) == ParseFlags::QUOTE); STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, false) == ParseFlags::DELIMITER); STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, false) == ParseFlags::NEWLINE); STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, true) == ParseFlags::NOT_SPECIAL); STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, true) == ParseFlags::QUOTE_ESCAPE_QUOTE); STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, true) == ParseFlags::NOT_SPECIAL); STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, true) == ParseFlags::NOT_SPECIAL); /** An array which maps UTF-8 chars to a parsing flag */ using ParseFlagMap = std::array<ParseFlags, std::numeric_limits<unsigned char>::max() + 1>; /** An array which maps UTF-8 chars to a flag indicating if it is whitespace */ using WhitespaceMap = std::array<bool, std::numeric_limits<unsigned char>::max() + 1>; } /** Integer indicating a requested column wasn't found. */ constexpr int CSV_NOT_FOUND = -1; } namespace csv { namespace internals { struct ColNames; using ColNamesPtr = std::shared_ptr<ColNames>; /** @struct ColNames * A data structure for handling column name information. * * These are created by CSVReader and passed (via smart pointer) * to CSVRow objects it creates, thus * allowing for indexing by column name. */ struct ColNames { public: ColNames() = default; ColNames(const std::vector<std::string>& names) { set_col_names(names); } std::vector<std::string> get_col_names() const; void set_col_names(const std::vector<std::string>&); int index_of(csv::string_view) const; bool empty() const noexcept { return this->col_names.empty(); } size_t size() const noexcept; private: std::vector<std::string> col_names; std::unordered_map<std::string, size_t> col_pos; }; } } /** @file * Defines an object used to store CSV format settings */ #include <iterator> #include <stdexcept> #include <string> #include <vector> namespace csv { namespace internals { class IBasicCSVParser; } class CSVReader; /** Determines how to handle rows that are shorter or longer than the majority */ enum class VariableColumnPolicy { THROW = -1, IGNORE_ROW = 0, KEEP = 1 }; /** Stores the inferred format of a CSV file. */ struct CSVGuessResult { char delim; int header_row; }; /** Stores information about how to parse a CSV file. * Can be used to construct a csv::CSVReader. */ class CSVFormat { public: /** Settings for parsing a RFC 4180 CSV file */ CSVFormat() = default; /** Sets the delimiter of the CSV file * * @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap */ CSVFormat& delimiter(char delim); /** Sets a list of potential delimiters * * @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap * @param[in] delim An array of possible delimiters to try parsing the CSV with */ CSVFormat& delimiter(const std::vector<char> & delim); /** Sets the whitespace characters to be trimmed * * @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap * @param[in] ws An array of whitespace characters that should be trimmed */ CSVFormat& trim(const std::vector<char> & ws); /** Sets the quote character * * @throws `std::runtime_error` thrown if trim, quote, or possible delimiting characters overlap */ CSVFormat& quote(char quote); /** Sets the column names. * * @note Unsets any values set by header_row() */ CSVFormat& column_names(const std::vector<std::string>& names); /** Sets the header row * * @note Unsets any values set by column_names() */ CSVFormat& header_row(int row); /** Tells the parser that this CSV has no header row * * @note Equivalent to `header_row(-1)` * */ CSVFormat& no_header() { this->header_row(-1); return *this; } /** Turn quoting on or off */ CSVFormat& quote(bool use_quote) { this->no_quote = !use_quote; return *this; } /** Tells the parser how to handle columns of a different length than the others */ CONSTEXPR_14 CSVFormat& variable_columns(VariableColumnPolicy policy = VariableColumnPolicy::IGNORE_ROW) { this->variable_column_policy = policy; return *this; } /** Tells the parser how to handle columns of a different length than the others */ CONSTEXPR_14 CSVFormat& variable_columns(bool policy) { this->variable_column_policy = (VariableColumnPolicy)policy; return *this; } #ifndef DOXYGEN_SHOULD_SKIP_THIS char get_delim() const { // This error should never be received by end users. if (this->possible_delimiters.size() > 1) { throw std::runtime_error("There is more than one possible delimiter."); } return this->possible_delimiters.at(0); } CONSTEXPR bool is_quoting_enabled() const { return !this->no_quote; } CONSTEXPR char get_quote_char() const { return this->quote_char; } CONSTEXPR int get_header() const { return this->header; } std::vector<char> get_possible_delims() const { return this->possible_delimiters; } std::vector<char> get_trim_chars() const { return this->trim_chars; } CONSTEXPR VariableColumnPolicy get_variable_column_policy() const { return this->variable_column_policy; } #endif /** CSVFormat for guessing the delimiter */ CSV_INLINE static CSVFormat guess_csv() { CSVFormat format; format.delimiter({ ',', '|', '\t', ';', '^' }) .quote('"') .header_row(0); return format; } bool guess_delim() { return this->possible_delimiters.size() > 1; } friend CSVReader; friend internals::IBasicCSVParser; private: /**< Throws an error if delimiters and trim characters overlap */ void assert_no_char_overlap(); /**< Set of possible delimiters */ std::vector<char> possible_delimiters = { ',' }; /**< Set of whitespace characters to trim */ std::vector<char> trim_chars = {}; /**< Row number with columns (ignored if col_names is non-empty) */ int header = 0; /**< Whether or not to use quoting */ bool no_quote = false; /**< Quote character */ char quote_char = '"'; /**< Should be left empty unless file doesn't include header */ std::vector<std::string> col_names = {}; /**< Allow variable length columns? */ VariableColumnPolicy variable_column_policy = VariableColumnPolicy::IGNORE_ROW; }; } // clang-format off /** @file * Defines the data type used for storing information about a CSV row */ #include <cmath> #include <deque> #include <iterator> #include <memory> // For CSVField #include <limits> // For CSVField #include <unordered_map> #include <unordered_set> #include <string> #include <sstream> #include <vector> /** @file * @brief Implements data type parsing functionality */ #include <cmath> #include <cctype> #include <string> #include <cassert> namespace csv { /** Enumerates the different CSV field types that are * recognized by this library * * @note Overflowing integers will be stored and classified as doubles. * @note Unlike previous releases, integer enums here are platform agnostic. */ enum class DataType { UNKNOWN = -1, CSV_NULL, /**< Empty string */ CSV_STRING, /**< Non-numeric string */ CSV_INT8, /**< 8-bit integer */ CSV_INT16, /**< 16-bit integer (short on MSVC/GCC) */ CSV_INT32, /**< 32-bit integer (int on MSVC/GCC) */ CSV_INT64, /**< 64-bit integer (long long on MSVC/GCC) */ CSV_BIGINT, /**< Value too big to fit in a 64-bit in */ CSV_DOUBLE /**< Floating point value */ }; static_assert(DataType::CSV_STRING < DataType::CSV_INT8, "String type should come before numeric types."); static_assert(DataType::CSV_INT8 < DataType::CSV_INT64, "Smaller integer types should come before larger integer types."); static_assert(DataType::CSV_INT64 < DataType::CSV_DOUBLE, "Integer types should come before floating point value types."); namespace internals { /** Compute 10 to the power of n */ template<typename T> HEDLEY_CONST CONSTEXPR_14 long double pow10(const T& n) noexcept { long double multiplicand = n > 0 ? 10 : 0.1, ret = 1; // Make all numbers positive T iterations = n > 0 ? n : -n; for (T i = 0; i < iterations; i++) { ret *= multiplicand; } return ret; } /** Compute 10 to the power of n */ template<> HEDLEY_CONST CONSTEXPR_14 long double pow10(const unsigned& n) noexcept { long double multiplicand = n > 0 ? 10 : 0.1, ret = 1; for (unsigned i = 0; i < n; i++) { ret *= multiplicand; } return ret; } #ifndef DOXYGEN_SHOULD_SKIP_THIS /** Private site-indexed array mapping byte sizes to an integer size enum */ constexpr DataType int_type_arr[8] = { DataType::CSV_INT8, // 1 DataType::CSV_INT16, // 2 DataType::UNKNOWN, DataType::CSV_INT32, // 4 DataType::UNKNOWN, DataType::UNKNOWN, DataType::UNKNOWN, DataType::CSV_INT64 // 8 }; template<typename T> inline DataType type_num() { static_assert(std::is_integral<T>::value, "T should be an integral type."); static_assert(sizeof(T) <= 8, "Byte size must be no greater than 8."); return int_type_arr[sizeof(T) - 1]; } template<> inline DataType type_num<float>() { return DataType::CSV_DOUBLE; } template<> inline DataType type_num<double>() { return DataType::CSV_DOUBLE; } template<> inline DataType type_num<long double>() { return DataType::CSV_DOUBLE; } template<> inline DataType type_num<std::nullptr_t>() { return DataType::CSV_NULL; } template<> inline DataType type_num<std::string>() { return DataType::CSV_STRING; } CONSTEXPR_14 DataType data_type(csv::string_view in, long double* const out = nullptr, const char decimalsymbol = '.'); #endif /** Given a byte size, return the largest number than can be stored in * an integer of that size * * Note: Provides a platform-agnostic way of mapping names like "long int" to * byte sizes */ template<size_t Bytes> CONSTEXPR_14 long double get_int_max() { static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8, "Bytes must be a power of 2 below 8."); IF_CONSTEXPR (sizeof(signed char) == Bytes) { return (long double)std::numeric_limits<signed char>::max(); } IF_CONSTEXPR (sizeof(short) == Bytes) { return (long double)std::numeric_limits<short>::max(); } IF_CONSTEXPR (sizeof(int) == Bytes) { return (long double)std::numeric_limits<int>::max(); } IF_CONSTEXPR (sizeof(long int) == Bytes) { return (long double)std::numeric_limits<long int>::max(); } IF_CONSTEXPR (sizeof(long long int) == Bytes) { return (long double)std::numeric_limits<long long int>::max(); } HEDLEY_UNREACHABLE(); } /** Given a byte size, return the largest number than can be stored in * an unsigned integer of that size */ template<size_t Bytes> CONSTEXPR_14 long double get_uint_max() { static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8, "Bytes must be a power of 2 below 8."); IF_CONSTEXPR(sizeof(unsigned char) == Bytes) { return (long double)std::numeric_limits<unsigned char>::max(); } IF_CONSTEXPR(sizeof(unsigned short) == Bytes) { return (long double)std::numeric_limits<unsigned short>::max(); } IF_CONSTEXPR(sizeof(unsigned int) == Bytes) { return (long double)std::numeric_limits<unsigned int>::max(); } IF_CONSTEXPR(sizeof(unsigned long int) == Bytes) { return (long double)std::numeric_limits<unsigned long int>::max(); } IF_CONSTEXPR(sizeof(unsigned long long int) == Bytes) { return (long double)std::numeric_limits<unsigned long long int>::max(); } HEDLEY_UNREACHABLE(); } /** Largest number that can be stored in a 8-bit integer */ CONSTEXPR_VALUE_14 long double CSV_INT8_MAX = get_int_max<1>(); /** Largest number that can be stored in a 16-bit integer */ CONSTEXPR_VALUE_14 long double CSV_INT16_MAX = get_int_max<2>(); /** Largest number that can be stored in a 32-bit integer */ CONSTEXPR_VALUE_14 long double CSV_INT32_MAX = get_int_max<4>(); /** Largest number that can be stored in a 64-bit integer */ CONSTEXPR_VALUE_14 long double CSV_INT64_MAX = get_int_max<8>(); /** Largest number that can be stored in a 8-bit ungisned integer */ CONSTEXPR_VALUE_14 long double CSV_UINT8_MAX = get_uint_max<1>(); /** Largest number that can be stored in a 16-bit unsigned integer */ CONSTEXPR_VALUE_14 long double CSV_UINT16_MAX = get_uint_max<2>(); /** Largest number that can be stored in a 32-bit unsigned integer */ CONSTEXPR_VALUE_14 long double CSV_UINT32_MAX = get_uint_max<4>(); /** Largest number that can be stored in a 64-bit unsigned integer */ CONSTEXPR_VALUE_14 long double CSV_UINT64_MAX = get_uint_max<8>(); /** Given a pointer to the start of what is start of * the exponential part of a number written (possibly) in scientific notation * parse the exponent */ HEDLEY_PRIVATE CONSTEXPR_14 DataType _process_potential_exponential( csv::string_view exponential_part, const long double& coeff, long double * const out) { long double exponent = 0; auto result = data_type(exponential_part, &exponent); // Exponents in scientific notation should not be decimal numbers if (result >= DataType::CSV_INT8 && result < DataType::CSV_DOUBLE) { if (out) *out = coeff * pow10(exponent); return DataType::CSV_DOUBLE; } return DataType::CSV_STRING; } /** Given the absolute value of an integer, determine what numeric type * it fits in */ HEDLEY_PRIVATE HEDLEY_PURE CONSTEXPR_14 DataType _determine_integral_type(const long double& number) noexcept { // We can assume number is always non-negative assert(number >= 0); if (number <= internals::CSV_INT8_MAX) return DataType::CSV_INT8; else if (number <= internals::CSV_INT16_MAX) return DataType::CSV_INT16; else if (number <= internals::CSV_INT32_MAX) return DataType::CSV_INT32; else if (number <= internals::CSV_INT64_MAX) return DataType::CSV_INT64; else // Conversion to long long will cause an overflow return DataType::CSV_BIGINT; } /** Distinguishes numeric from other text values. Used by various * type casting functions, like csv_parser::CSVReader::read_row() * * #### Rules * - Leading and trailing whitespace ("padding") ignored * - A string of just whitespace is NULL * * @param[in] in String value to be examined * @param[out] out Pointer to long double where results of numeric parsing * get stored * @param[in] decimalSymbol the character separating integral and decimal part, * defaults to '.' if omitted */ CONSTEXPR_14 DataType data_type(csv::string_view in, long double* const out, const char decimalSymbol) { // Empty string --> NULL if (in.size() == 0) return DataType::CSV_NULL; bool ws_allowed = true, dot_allowed = true, digit_allowed = true, is_negative = false, has_digit = false, prob_float = false; unsigned places_after_decimal = 0; long double integral_part = 0, decimal_part = 0; for (size_t i = 0, ilen = in.size(); i < ilen; i++) { const char& current = in[i]; switch (current) { case ' ': if (!ws_allowed) { if (isdigit(in[i - 1])) { digit_allowed = false; ws_allowed = true; } else { // Ex: '510 123 4567' return DataType::CSV_STRING; } } break; case '+': if (!ws_allowed) { return DataType::CSV_STRING; } break; case '-': if (!ws_allowed) { // Ex: '510-123-4567' return DataType::CSV_STRING; } is_negative = true; break; // case decimalSymbol: not allowed because decimalSymbol is not a literal, // it is handled in the default block case 'e': case 'E': // Process scientific notation if (prob_float || (i && i + 1 < ilen && isdigit(in[i - 1]))) { size_t exponent_start_idx = i + 1; prob_float = true; // Strip out plus sign if (in[i + 1] == '+') { exponent_start_idx++; } return _process_potential_exponential( in.substr(exponent_start_idx), is_negative ? -(integral_part + decimal_part) : integral_part + decimal_part, out ); } return DataType::CSV_STRING; break; default: short digit = static_cast<short>(current - '0'); if (digit >= 0 && digit <= 9) { // Process digit has_digit = true; if (!digit_allowed) return DataType::CSV_STRING; else if (ws_allowed) // Ex: '510 456' ws_allowed = false; // Build current number if (prob_float) decimal_part += digit / pow10(++places_after_decimal); else integral_part = (integral_part * 10) + digit; } // case decimalSymbol: not allowed because decimalSymbol is not a literal. else if (dot_allowed && current == decimalSymbol) { dot_allowed = false; prob_float = true; } else { return DataType::CSV_STRING; } } } // No non-numeric/non-whitespace characters found if (has_digit) { long double number = integral_part + decimal_part; if (out) { *out = is_negative ? -number : number; } return prob_float ? DataType::CSV_DOUBLE : _determine_integral_type(number); } // Just whitespace return DataType::CSV_NULL; } } } namespace csv { namespace internals { class IBasicCSVParser; static const std::string ERROR_NAN = "Not a number."; static const std::string ERROR_OVERFLOW = "Overflow error."; static const std::string ERROR_FLOAT_TO_INT = "Attempted to convert a floating point value to an integral type."; static const std::string ERROR_NEG_TO_UNSIGNED = "Negative numbers cannot be converted to unsigned types."; std::string json_escape_string(csv::string_view s) noexcept; /** A barebones class used for describing CSV fields */ struct RawCSVField { RawCSVField() = default; RawCSVField(size_t _start, size_t _length, bool _double_quote = false) { start = _start; length = _length; has_double_quote = _double_quote; } /** The start of the field, relative to the beginning of the row */ size_t start; /** The length of the row, ignoring quote escape characters */ size_t length; /** Whether or not the field contains an escaped quote */ bool has_double_quote; }; /** A class used for efficiently storing RawCSVField objects and expanding as necessary * * @par Implementation * This data structure stores RawCSVField in continguous blocks. When more capacity * is needed, a new block is allocated, but previous data stays put. * * @par Thread Safety * This class may be safely read from multiple threads and written to from one, * as long as the writing thread does not actively touch fields which are being * read. */ class CSVFieldList { public: /** Construct a CSVFieldList which allocates blocks of a certain size */ CSVFieldList(size_t single_buffer_capacity = (size_t)(internals::CSV_PAGE_SIZE / sizeof(RawCSVField))) : _single_buffer_capacity(single_buffer_capacity) { this->allocate(); } // No copy constructor CSVFieldList(const CSVFieldList& other) = delete; // CSVFieldArrays may be moved CSVFieldList(CSVFieldList&& other) : _single_buffer_capacity(other._single_buffer_capacity) { for (auto&& buffer : other.buffers) { this->buffers.emplace_back(std::move(buffer)); } _current_buffer_size = other._current_buffer_size; _back = other._back; } template <class... Args> void emplace_back(Args&&... args) { if (this->_current_buffer_size == this->_single_buffer_capacity) { this->allocate(); } *(_back++) = RawCSVField(std::forward<Args>(args)...); _current_buffer_size++; } size_t size() const noexcept { return this->_current_buffer_size + ((this->buffers.size() - 1) * this->_single_buffer_capacity); } RawCSVField& operator[](size_t n) const; private: const size_t _single_buffer_capacity; /** * Prefer std::deque over std::vector because it does not * reallocate upon expansion, allowing pointers to its members * to remain valid & avoiding potential race conditions when * CSVFieldList is accesssed simulatenously by a reading thread and * a writing thread */ std::deque<std::unique_ptr<RawCSVField[]>> buffers = {}; /** Number of items in the current buffer */ size_t _current_buffer_size = 0; /** Pointer to the current empty field */ RawCSVField* _back = nullptr; /** Allocate a new page of memory */ void allocate(); }; /** A class for storing raw CSV data and associated metadata */ struct RawCSVData { std::shared_ptr<void> _data = nullptr; csv::string_view data = ""; internals::CSVFieldList fields; std::unordered_set<size_t> has_double_quotes = {}; // TODO: Consider replacing with a more thread-safe structure std::unordered_map<size_t, std::string> double_quote_fields = {}; internals::ColNamesPtr col_names = nullptr; internals::ParseFlagMap parse_flags; internals::WhitespaceMap ws_flags; }; using RawCSVDataPtr = std::shared_ptr<RawCSVData>; } /** * @class CSVField * @brief Data type representing individual CSV values. * CSVFields can be obtained by using CSVRow::operator[] */ class CSVField { public: /** Constructs a CSVField from a string_view */ constexpr explicit CSVField(csv::string_view _sv) noexcept : sv(_sv) { }; operator std::string() const { return std::string("<CSVField> ") + std::string(this->sv); } /** Returns the value casted to the requested type, performing type checking before. * * \par Valid options for T * - std::string or csv::string_view * - signed integral types (signed char, short, int, long int, long long int) * - floating point types (float, double, long double) * - unsigned integers are not supported at this time, but may be in a later release * * \par Invalid conversions * - Converting non-numeric values to any numeric type * - Converting floating point values to integers * - Converting a large integer to a smaller type that will not hold it * * @note This method is capable of parsing scientific E-notation. * See [this page](md_docs_source_scientific_notation.html) * for more details. * * @throws std::runtime_error Thrown if an invalid conversion is performed. * * @warning Currently, conversions to floating point types are not * checked for loss of precision * * @warning Any string_views returned are only guaranteed to be valid * if the parent CSVRow is still alive. If you are concerned * about object lifetimes, then grab a std::string or a * numeric value. * */ template<typename T = std::string> T get() { IF_CONSTEXPR(std::is_arithmetic<T>::value) { // Note: this->type() also converts the CSV value to float if (this->type() <= DataType::CSV_STRING) { throw std::runtime_error(internals::ERROR_NAN); } } IF_CONSTEXPR(std::is_integral<T>::value) { // Note: this->is_float() also converts the CSV value to float if (this->is_float()) { throw std::runtime_error(internals::ERROR_FLOAT_TO_INT); } IF_CONSTEXPR(std::is_unsigned<T>::value) { if (this->value < 0) { throw std::runtime_error(internals::ERROR_NEG_TO_UNSIGNED); } } } // Allow fallthrough from previous if branch IF_CONSTEXPR(!std::is_floating_point<T>::value) { IF_CONSTEXPR(std::is_unsigned<T>::value) { // Quick hack to perform correct unsigned integer boundary checks if (this->value > internals::get_uint_max<sizeof(T)>()) { throw std::runtime_error(internals::ERROR_OVERFLOW); } } else if (internals::type_num<T>() < this->_type) { throw std::runtime_error(internals::ERROR_OVERFLOW); } } return static_cast<T>(this->value); } /** Parse a hexadecimal value, returning false if the value is not hex. */ bool try_parse_hex(int& parsedValue); /** Attempts to parse a decimal (or integer) value using the given symbol, * returning `true` if the value is numeric. * * @note This method also updates this field's type * */ bool try_parse_decimal(long double& dVal, const char decimalSymbol = '.'); /** Compares the contents of this field to a numeric value. If this * field does not contain a numeric value, then all comparisons return * false. * * @note Floating point values are considered equal if they are within * `0.000001` of each other. * * @warning Multiple numeric comparisons involving the same field can * be done more efficiently by calling the CSVField::get<>() method. * * @sa csv::CSVField::operator==(const char * other) * @sa csv::CSVField::operator==(csv::string_view other) */ template<typename T> CONSTEXPR_14 bool operator==(T other) const noexcept { static_assert(std::is_arithmetic<T>::value, "T should be a numeric value."); if (this->_type != DataType::UNKNOWN) { if (this->_type == DataType::CSV_STRING) { return false; } return internals::is_equal(value, static_cast<long double>(other), 0.000001L); } long double out = 0; if (internals::data_type(this->sv, &out) == DataType::CSV_STRING) { return false; } return internals::is_equal(out, static_cast<long double>(other), 0.000001L); } /** Return a string view over the field's contents */ CONSTEXPR csv::string_view get_sv() const noexcept { return this->sv; } /** Returns true if field is an empty string or string of whitespace characters */ CONSTEXPR_14 bool is_null() noexcept { return type() == DataType::CSV_NULL; } /** Returns true if field is a non-numeric, non-empty string */ CONSTEXPR_14 bool is_str() noexcept { return type() == DataType::CSV_STRING; } /** Returns true if field is an integer or float */ CONSTEXPR_14 bool is_num() noexcept { return type() >= DataType::CSV_INT8; } /** Returns true if field is an integer */ CONSTEXPR_14 bool is_int() noexcept { return (type() >= DataType::CSV_INT8) && (type() <= DataType::CSV_INT64); } /** Returns true if field is a floating point value */ CONSTEXPR_14 bool is_float() noexcept { return type() == DataType::CSV_DOUBLE; }; /** Return the type of the underlying CSV data */ CONSTEXPR_14 DataType type() noexcept { this->get_value(); return _type; } private: long double value = 0; /**< Cached numeric value */ csv::string_view sv = ""; /**< A pointer to this field's text */ DataType _type = DataType::UNKNOWN; /**< Cached data type value */ CONSTEXPR_14 void get_value() noexcept { /* Check to see if value has been cached previously, if not * evaluate it */ if ((int)_type < 0) { this->_type = internals::data_type(this->sv, &this->value); } } }; /** Data structure for representing CSV rows */ class CSVRow { public: friend internals::IBasicCSVParser; CSVRow() = default; /** Construct a CSVRow from a RawCSVDataPtr */ CSVRow(internals::RawCSVDataPtr _data) : data(_data) {} CSVRow(internals::RawCSVDataPtr _data, size_t _data_start, size_t _field_bounds) : data(_data), data_start(_data_start), fields_start(_field_bounds) {} /** Indicates whether row is empty or not */ CONSTEXPR bool empty() const noexcept { return this->size() == 0; } /** Return the number of fields in this row */ CONSTEXPR size_t size() const noexcept { return row_length; } /** @name Value Retrieval */ ///@{ CSVField operator[](size_t n) const; CSVField operator[](const std::string&) const; std::string to_json(const std::vector<std::string>& subset = {}) const; std::string to_json_array(const std::vector<std::string>& subset = {}) const; /** Retrieve this row's associated column names */ std::vector<std::string> get_col_names() const { return this->data->col_names->get_col_names(); } /** Convert this CSVRow into a vector of strings. * **Note**: This is a less efficient method of * accessing data than using the [] operator. */ operator std::vector<std::string>() const; ///@} /** A random access iterator over the contents of a CSV row. * Each iterator points to a CSVField. */ class iterator { public: #ifndef DOXYGEN_SHOULD_SKIP_THIS using value_type = CSVField; using difference_type = int; using pointer = std::shared_ptr<CSVField>; using reference = CSVField & ; using iterator_category = std::random_access_iterator_tag; #endif iterator(const CSVRow*, int i); reference operator*() const; pointer operator->() const; iterator operator++(int); iterator& operator++(); iterator operator--(int); iterator& operator--(); iterator operator+(difference_type n) const; iterator operator-(difference_type n) const; /** Two iterators are equal if they point to the same field */ CONSTEXPR bool operator==(const iterator& other) const noexcept { return this->i == other.i; }; CONSTEXPR bool operator!=(const iterator& other) const noexcept { return !operator==(other); } #ifndef NDEBUG friend CSVRow; #endif private: const CSVRow * daddy = nullptr; // Pointer to parent std::shared_ptr<CSVField> field = nullptr; // Current field pointed at int i = 0; // Index of current field }; /** A reverse iterator over the contents of a CSVRow. */ using reverse_iterator = std::reverse_iterator<iterator>; /** @name Iterators * @brief Each iterator points to a CSVField object. */ ///@{ iterator begin() const; iterator end() const noexcept; reverse_iterator rbegin() const noexcept; reverse_iterator rend() const; ///@} private: /** Retrieve a string view corresponding to the specified index */ csv::string_view get_field(size_t index) const; internals::RawCSVDataPtr data; /** Where in RawCSVData.data we start */ size_t data_start = 0; /** Where in the RawCSVDataPtr.fields array we start */ size_t fields_start = 0; /** How many columns this row spans */ size_t row_length = 0; }; #ifdef _MSC_VER #pragma region CSVField::get Specializations #endif /** Retrieve this field's original string */ template<> inline std::string CSVField::get<std::string>() { return std::string(this->sv); } /** Retrieve a view over this field's string * * @warning This string_view is only guaranteed to be valid as long as this * CSVRow is still alive. */ template<> CONSTEXPR_14 csv::string_view CSVField::get<csv::string_view>() { return this->sv; } /** Retrieve this field's value as a long double */ template<> CONSTEXPR_14 long double CSVField::get<long double>() { if (!is_num()) throw std::runtime_error(internals::ERROR_NAN); return this->value; } #ifdef _MSC_VER #pragma endregion CSVField::get Specializations #endif /** Compares the contents of this field to a string */ template<> CONSTEXPR bool CSVField::operator==(const char * other) const noexcept { return this->sv == other; } /** Compares the contents of this field to a string */ template<> CONSTEXPR bool CSVField::operator==(csv::string_view other) const noexcept { return this->sv == other; } } inline std::ostream& operator << (std::ostream& os, csv::CSVField const& value) { os << std::string(value); return os; } namespace csv { namespace internals { /** Helper constexpr function to initialize arrays with default values */ template<typename T, typename Out> HEDLEY_CONST CONSTEXPR_17 Out container_to_default(T&& value) { Out a{}; for (auto& e: a) e = value; return a; } /** Create a vector v where each index i corresponds to the * ASCII number for a character and, v[i + 128] labels it according to * the CSVReader::ParseFlags enum */ HEDLEY_CONST CONSTEXPR_17 ParseFlagMap make_parse_flags(char delimiter) { ParseFlagMap ret = container_to_default<ParseFlagMap::value_type, ParseFlagMap>( ParseFlags::NOT_SPECIAL); ret[static_cast<unsigned char>(delimiter)] = ParseFlags::DELIMITER; ret[static_cast<unsigned char>('\r')] = ParseFlags::NEWLINE; ret[static_cast<unsigned char>('\n')] = ParseFlags::NEWLINE; return ret; } /** Create a vector v where each index i corresponds to the * ASCII number for a character and, v[i + 128] labels it according to * the CSVReader::ParseFlags enum */ HEDLEY_CONST CONSTEXPR_17 ParseFlagMap make_parse_flags(char delimiter, char quote_char) { ParseFlagMap ret = make_parse_flags(delimiter); ret[static_cast<unsigned char>(quote_char)] = ParseFlags::QUOTE; return ret; } /** Create a vector v where each index i corresponds to the * ASCII number for a character c and, v[i + 128] is true if * c is a whitespace character */ HEDLEY_CONST CONSTEXPR_17 WhitespaceMap make_ws_flags(const char* ws_chars, size_t n_chars) { WhitespaceMap ret = container_to_default<bool, WhitespaceMap>(false); for (size_t i = 0; i < n_chars; ++i) { ret[static_cast<unsigned char>(ws_chars[i])] = true; } return ret; } inline WhitespaceMap make_ws_flags(const std::vector<char>& flags) { return make_ws_flags(flags.data(), flags.size()); } CSV_INLINE size_t get_file_size(csv::string_view filename); CSV_INLINE std::string get_csv_head(csv::string_view filename); /** Read the first 500KB of a CSV file */ CSV_INLINE std::string get_csv_head(csv::string_view filename, size_t file_size); /** A std::deque wrapper which allows multiple read and write threads to concurrently * access it along with providing read threads the ability to wait for the deque * to become populated */ template<typename T> class ThreadSafeDeque { public: ThreadSafeDeque(size_t notify_size = 100) : _notify_size(notify_size) {}; ThreadSafeDeque(const ThreadSafeDeque& other) { this->data = other.data; this->_notify_size = other._notify_size; } ThreadSafeDeque(const std::deque<T>& source) : ThreadSafeDeque() { this->data = source; } void clear() noexcept { this->data.clear(); } bool empty() const noexcept { return this->data.empty(); } T& front() noexcept { return this->data.front(); } T& operator[](size_t n) { return this->data[n]; } void push_back(T&& item) { std::lock_guard<std::mutex> lock{ this->_lock }; this->data.push_back(std::move(item)); if (this->size() >= _notify_size) { this->_cond.notify_all(); } } T pop_front() noexcept { std::lock_guard<std::mutex> lock{ this->_lock }; T item = std::move(data.front()); data.pop_front(); return item; } size_t size() const noexcept { return this->data.size(); } /** Returns true if a thread is actively pushing items to this deque */ constexpr bool is_waitable() const noexcept { return this->_is_waitable; } /** Wait for an item to become available */ void wait() { if (!is_waitable()) { return; } std::unique_lock<std::mutex> lock{ this->_lock }; this->_cond.wait(lock, [this] { return this->size() >= _notify_size || !this->is_waitable(); }); lock.unlock(); } typename std::deque<T>::iterator begin() noexcept { return this->data.begin(); } typename std::deque<T>::iterator end() noexcept { return this->data.end(); } /** Tell listeners that this deque is actively being pushed to */ void notify_all() { std::unique_lock<std::mutex> lock{ this->_lock }; this->_is_waitable = true; this->_cond.notify_all(); } /** Tell all listeners to stop */ void kill_all() { std::unique_lock<std::mutex> lock{ this->_lock }; this->_is_waitable = false; this->_cond.notify_all(); } private: bool _is_waitable = false; size_t _notify_size; std::mutex _lock; std::condition_variable _cond; std::deque<T> data; }; constexpr const int UNINITIALIZED_FIELD = -1; } /** Standard type for storing collection of rows */ using RowCollection = internals::ThreadSafeDeque<CSVRow>; namespace internals { /** Abstract base class which provides CSV parsing logic. * * Concrete implementations may customize this logic across * different input sources, such as memory mapped files, stringstreams, * etc... */ class IBasicCSVParser { public: IBasicCSVParser() = default; IBasicCSVParser(const CSVFormat&, const ColNamesPtr&); IBasicCSVParser(const ParseFlagMap& parse_flags, const WhitespaceMap& ws_flags ) : _parse_flags(parse_flags), _ws_flags(ws_flags) {}; virtual ~IBasicCSVParser() {} /** Whether or not we have reached the end of source */ bool eof() { return this->_eof; } /** Parse the next block of data */ virtual void next(size_t bytes) = 0; /** Indicate the last block of data has been parsed */ void end_feed(); CONSTEXPR_17 ParseFlags parse_flag(const char ch) const noexcept { return _parse_flags.data()[static_cast<unsigned char>(ch)]; } CONSTEXPR_17 ParseFlags compound_parse_flag(const char ch) const noexcept { return quote_escape_flag(parse_flag(ch), this->quote_escape); } /** Whether or not this CSV has a UTF-8 byte order mark */ CONSTEXPR bool utf8_bom() const { return this->_utf8_bom; } void set_output(RowCollection& rows) { this->_records = &rows; } protected: /** @name Current Parser State */ ///@{ CSVRow current_row; RawCSVDataPtr data_ptr = nullptr; ColNamesPtr _col_names = nullptr; CSVFieldList* fields = nullptr; int field_start = UNINITIALIZED_FIELD; size_t field_length = 0; /** An array where the (i + 128)th slot gives the ParseFlags for ASCII character i */ ParseFlagMap _parse_flags; ///@} /** @name Current Stream/File State */ ///@{ bool _eof = false; /** The size of the incoming CSV */ size_t source_size = 0; ///@} /** Whether or not source needs to be read in chunks */ CONSTEXPR bool no_chunk() const { return this->source_size < ITERATION_CHUNK_SIZE; } /** Parse the current chunk of data * * * @returns How many character were read that are part of complete rows */ size_t parse(); /** Create a new RawCSVDataPtr for a new chunk of data */ void reset_data_ptr(); private: /** An array where the (i + 128)th slot determines whether ASCII character i should * be trimmed */ WhitespaceMap _ws_flags; bool quote_escape = false; bool field_has_double_quote = false; /** Where we are in the current data block */ size_t data_pos = 0; /** Whether or not an attempt to find Unicode BOM has been made */ bool unicode_bom_scan = false; bool _utf8_bom = false; /** Where complete rows should be pushed to */ RowCollection* _records = nullptr; CONSTEXPR_17 bool ws_flag(const char ch) const noexcept { return _ws_flags[static_cast<unsigned char>(ch)]; } size_t& current_row_start() { return this->current_row.data_start; } void parse_field() noexcept; /** Finish parsing the current field */ void push_field(); /** Finish parsing the current row */ void push_row(); /** Handle possible Unicode byte order mark */ void trim_utf8_bom(); }; /** A class for parsing CSV data from a `std::stringstream` * or an `std::ifstream` */ template<typename TStream> class StreamParser: public IBasicCSVParser { using RowCollection = ThreadSafeDeque<CSVRow>; public: StreamParser(TStream& source, const CSVFormat& format, const ColNamesPtr& col_names = nullptr ) : IBasicCSVParser(format, col_names), _source(std::move(source)) {}; StreamParser( TStream& source, internals::ParseFlagMap parse_flags, internals::WhitespaceMap ws_flags) : IBasicCSVParser(parse_flags, ws_flags), _source(std::move(source)) {}; ~StreamParser() {} void next(size_t bytes = ITERATION_CHUNK_SIZE) override { if (this->eof()) return; this->reset_data_ptr(); this->data_ptr->_data = std::make_shared<std::string>(); if (source_size == 0) { const auto start = _source.tellg(); _source.seekg(0, std::ios::end); const auto end = _source.tellg(); _source.seekg(0, std::ios::beg); source_size = end - start; } // Read data into buffer size_t length = std::min(source_size - stream_pos, bytes); std::unique_ptr<char[]> buff(new char[length]); _source.seekg(stream_pos, std::ios::beg); _source.read(buff.get(), length); stream_pos = _source.tellg(); ((std::string*)(this->data_ptr->_data.get()))->assign(buff.get(), length); // Create string_view this->data_ptr->data = *((std::string*)this->data_ptr->_data.get()); // Parse this->current_row = CSVRow(this->data_ptr); size_t remainder = this->parse(); if (stream_pos == source_size || no_chunk()) { this->_eof = true; this->end_feed(); } else { this->stream_pos -= (length - remainder); } } private: TStream _source; size_t stream_pos = 0; }; /** Parser for memory-mapped files * * @par Implementation * This class constructs moving windows over a file to avoid * creating massive memory maps which may require more RAM * than the user has available. It contains logic to automatically * re-align each memory map to the beginning of a CSV row. * */ class MmapParser : public IBasicCSVParser { public: MmapParser(csv::string_view filename, const CSVFormat& format, const ColNamesPtr& col_names = nullptr ) : IBasicCSVParser(format, col_names) { this->_filename = filename.data(); this->source_size = get_file_size(filename); }; ~MmapParser() {} void next(size_t bytes) override; private: std::string _filename; size_t mmap_pos = 0; }; } } /** The all encompassing namespace */ namespace csv { /** Stuff that is generally not of interest to end-users */ namespace internals { std::string format_row(const std::vector<std::string>& row, csv::string_view delim = ", "); std::vector<std::string> _get_col_names( csv::string_view head, const CSVFormat format = CSVFormat::guess_csv()); struct GuessScore { double score; size_t header; }; CSV_INLINE GuessScore calculate_score(csv::string_view head, const CSVFormat& format); CSVGuessResult _guess_format(csv::string_view head, const std::vector<char>& delims = { ',', '|', '\t', ';', '^', '~' }); } std::vector<std::string> get_col_names( csv::string_view filename, const CSVFormat format = CSVFormat::guess_csv()); /** Guess the delimiter used by a delimiter-separated values file */ CSVGuessResult guess_format(csv::string_view filename, const std::vector<char>& delims = { ',', '|', '\t', ';', '^', '~' }); /** @class CSVReader * @brief Main class for parsing CSVs from files and in-memory sources * * All rows are compared to the column names for length consistency * - By default, rows that are too short or too long are dropped * - Custom behavior can be defined by overriding bad_row_handler in a subclass */ class CSVReader { public: /** * An input iterator capable of handling large files. * @note Created by CSVReader::begin() and CSVReader::end(). * * @par Iterating over a file * @snippet tests/test_csv_iterator.cpp CSVReader Iterator 1 * * @par Using with `<algorithm>` library * @snippet tests/test_csv_iterator.cpp CSVReader Iterator 2 */ class iterator { public: #ifndef DOXYGEN_SHOULD_SKIP_THIS using value_type = CSVRow; using difference_type = std::ptrdiff_t; using pointer = CSVRow * ; using reference = CSVRow & ; using iterator_category = std::input_iterator_tag; #endif iterator() = default; iterator(CSVReader* reader) : daddy(reader) {}; iterator(CSVReader*, CSVRow&&); /** Access the CSVRow held by the iterator */ CONSTEXPR_14 reference operator*() { return this->row; } /** Return a pointer to the CSVRow the iterator has stopped at */ CONSTEXPR_14 pointer operator->() { return &(this->row); } iterator& operator++(); /**< Pre-increment iterator */ iterator operator++(int); /**< Post-increment iterator */ /** Returns true if iterators were constructed from the same CSVReader * and point to the same row */ CONSTEXPR bool operator==(const iterator& other) const noexcept { return (this->daddy == other.daddy) && (this->i == other.i); } CONSTEXPR bool operator!=(const iterator& other) const noexcept { return !operator==(other); } private: CSVReader * daddy = nullptr; // Pointer to parent CSVRow row; // Current row size_t i = 0; // Index of current row }; /** @name Constructors * Constructors for iterating over large files and parsing in-memory sources. */ ///@{ CSVReader(csv::string_view filename, CSVFormat format = CSVFormat::guess_csv()); /** Allows parsing stream sources such as `std::stringstream` or `std::ifstream` * * @tparam TStream An input stream deriving from `std::istream` * @note Currently this constructor requires special CSV dialects to be manually * specified. */ template<typename TStream, csv::enable_if_t<std::is_base_of<std::istream, TStream>::value, int> = 0> CSVReader(TStream& source, CSVFormat format = CSVFormat()) : _format(format) { using Parser = internals::StreamParser<TStream>; if (!format.col_names.empty()) this->set_col_names(format.col_names); this->parser = std::unique_ptr<Parser>( new Parser(source, format, col_names)); // For C++11 this->initial_read(); } ///@} CSVReader(const CSVReader&) = delete; // No copy constructor CSVReader(CSVReader&&) = default; // Move constructor CSVReader& operator=(const CSVReader&) = delete; // No copy assignment CSVReader& operator=(CSVReader&& other) = default; ~CSVReader() { if (this->read_csv_worker.joinable()) { this->read_csv_worker.join(); } } /** @name Retrieving CSV Rows */ ///@{ bool read_row(CSVRow &row); iterator begin(); HEDLEY_CONST iterator end() const noexcept; /** Returns true if we have reached end of file */ bool eof() const noexcept { return this->parser->eof(); }; ///@} /** @name CSV Metadata */ ///@{ CSVFormat get_format() const; std::vector<std::string> get_col_names() const; int index_of(csv::string_view col_name) const; ///@} /** @name CSV Metadata: Attributes */ ///@{ /** Whether or not the file or stream contains valid CSV rows, * not including the header. * * @note Gives an accurate answer regardless of when it is called. * */ CONSTEXPR bool empty() const noexcept { return this->n_rows() == 0; } /** Retrieves the number of rows that have been read so far */ CONSTEXPR size_t n_rows() const noexcept { return this->_n_rows; } /** Whether or not CSV was prefixed with a UTF-8 bom */ bool utf8_bom() const noexcept { return this->parser->utf8_bom(); } ///@} protected: /** * \defgroup csv_internal CSV Parser Internals * @brief Internals of CSVReader. Only maintainers and those looking to * extend the parser should read this. * @{ */ /** Sets this reader's column names and associated data */ void set_col_names(const std::vector<std::string>&); /** @name CSV Settings **/ ///@{ CSVFormat _format; ///@} /** @name Parser State */ ///@{ /** Pointer to a object containing column information */ internals::ColNamesPtr col_names = std::make_shared<internals::ColNames>(); /** Helper class which actually does the parsing */ std::unique_ptr<internals::IBasicCSVParser> parser = nullptr; /** Queue of parsed CSV rows */ std::unique_ptr<RowCollection> records{new RowCollection(100)}; size_t n_cols = 0; /**< The number of columns in this CSV */ size_t _n_rows = 0; /**< How many rows (minus header) have been read so far */ /** @name Multi-Threaded File Reading Functions */ ///@{ bool read_csv(size_t bytes = internals::ITERATION_CHUNK_SIZE); ///@} /**@}*/ private: /** Whether or not rows before header were trimmed */ bool header_trimmed = false; /** @name Multi-Threaded File Reading: Flags and State */ ///@{ std::thread read_csv_worker; /**< Worker thread for read_csv() */ ///@} /** Read initial chunk to get metadata */ void initial_read() { this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE); this->read_csv_worker.join(); } void trim_header(); }; } /** @file * Calculates statistics from CSV files */ #include <unordered_map> #include <sstream> #include <vector> namespace csv { /** Class for calculating statistics from CSV files and in-memory sources * * **Example** * \include programs/csv_stats.cpp * */ class CSVStat { public: using FreqCount = std::unordered_map<std::string, size_t>; using TypeCount = std::unordered_map<DataType, size_t>; std::vector<long double> get_mean() const; std::vector<long double> get_variance() const; std::vector<long double> get_mins() const; std::vector<long double> get_maxes() const; std::vector<FreqCount> get_counts() const; std::vector<TypeCount> get_dtypes() const; std::vector<std::string> get_col_names() const { return this->reader.get_col_names(); } CSVStat(csv::string_view filename, CSVFormat format = CSVFormat::guess_csv()); CSVStat(std::stringstream& source, CSVFormat format = CSVFormat()); private: // An array of rolling averages // Each index corresponds to the rolling mean for the column at said index std::vector<long double> rolling_means; std::vector<long double> rolling_vars; std::vector<long double> mins; std::vector<long double> maxes; std::vector<FreqCount> counts; std::vector<TypeCount> dtypes; std::vector<long double> n; // Statistic calculators void variance(const long double&, const size_t&); void count(CSVField&, const size_t&); void min_max(const long double&, const size_t&); void dtype(CSVField&, const size_t&); void calc(); void calc_chunk(); void calc_worker(const size_t&); CSVReader reader; std::deque<CSVRow> records = {}; }; } #include <string> #include <type_traits> #include <unordered_map> namespace csv { /** Returned by get_file_info() */ struct CSVFileInfo { std::string filename; /**< Filename */ std::vector<std::string> col_names; /**< CSV column names */ char delim; /**< Delimiting character */ size_t n_rows; /**< Number of rows in a file */ size_t n_cols; /**< Number of columns in a CSV */ }; /** @name Shorthand Parsing Functions * @brief Convienience functions for parsing small strings */ ///@{ CSVReader operator ""_csv(const char*, size_t); CSVReader operator ""_csv_no_header(const char*, size_t); CSVReader parse(csv::string_view in, CSVFormat format = CSVFormat()); CSVReader parse_no_header(csv::string_view in); ///@} /** @name Utility Functions */ ///@{ std::unordered_map<std::string, DataType> csv_data_types(const std::string&); CSVFileInfo get_file_info(const std::string& filename); int get_col_pos(csv::string_view filename, csv::string_view col_name, const CSVFormat& format = CSVFormat::guess_csv()); ///@} } /** @file * A standalone header file for writing delimiter-separated files */ #include <fstream> #include <iostream> #include <string> #include <tuple> #include <type_traits> #include <vector> namespace csv { namespace internals { static int DECIMAL_PLACES = 5; /** * Calculate the absolute value of a number */ template<typename T = int> inline T csv_abs(T x) { return abs(x); } template<> inline int csv_abs(int x) { return abs(x); } template<> inline long int csv_abs(long int x) { return labs(x); } template<> inline long long int csv_abs(long long int x) { return llabs(x); } template<> inline float csv_abs(float x) { return fabsf(x); } template<> inline double csv_abs(double x) { return fabs(x); } template<> inline long double csv_abs(long double x) { return fabsl(x); } /** * Calculate the number of digits in a number */ template< typename T, csv::enable_if_t<std::is_arithmetic<T>::value, int> = 0 > int num_digits(T x) { x = csv_abs(x); int digits = 0; while (x >= 1) { x /= 10; digits++; } return digits; } /** to_string() for unsigned integers */ template<typename T, csv::enable_if_t<std::is_unsigned<T>::value, int> = 0> inline std::string to_string(T value) { std::string digits_reverse = ""; if (value == 0) return "0"; while (value > 0) { digits_reverse += (char)('0' + (value % 10)); value /= 10; } return std::string(digits_reverse.rbegin(), digits_reverse.rend()); } /** to_string() for signed integers */ template< typename T, csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0 > inline std::string to_string(T value) { if (value >= 0) return to_string((size_t)value); return "-" + to_string((size_t)(value * -1)); } /** to_string() for floating point numbers */ template< typename T, csv::enable_if_t<std::is_floating_point<T>::value, int> = 0 > inline std::string to_string(T value) { #ifdef __clang__ return std::to_string(value); #else // TODO: Figure out why the below code doesn't work on clang std::string result = ""; T integral_part; T fractional_part = std::abs(std::modf(value, &integral_part)); integral_part = std::abs(integral_part); // Integral part if (value < 0) result = "-"; if (integral_part == 0) { result += "0"; } else { for (int n_digits = num_digits(integral_part); n_digits > 0; n_digits --) { int digit = (int)(std::fmod(integral_part, pow10(n_digits)) / pow10(n_digits - 1)); result += (char)('0' + digit); } } // Decimal part result += "."; if (fractional_part > 0) { fractional_part *= (T)(pow10(DECIMAL_PLACES)); for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) { int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1)); result += (char)('0' + digit); } } else { result += "0"; } return result; #endif } } /** Sets how many places after the decimal will be written for floating point numbers * * @param precision Number of decimal places */ #ifndef __clang___ inline static void set_decimal_places(int precision) { internals::DECIMAL_PLACES = precision; } #endif /** @name CSV Writing */ ///@{ /** * Class for writing delimiter separated values files * * To write formatted strings, one should * -# Initialize a DelimWriter with respect to some output stream * -# Call write_row() on std::vector<std::string>s of unformatted text * * @tparam OutputStream The output stream, e.g. `std::ofstream`, `std::stringstream` * @tparam Delim The delimiter character * @tparam Quote The quote character * @tparam Flush True: flush after every writing function, * false: you need to flush explicitly if needed. * In both cases the destructor will flush. * * @par Hint * Use the aliases csv::CSVWriter<OutputStream> to write CSV * formatted strings and csv::TSVWriter<OutputStream> * to write tab separated strings * * @par Example w/ std::vector, std::deque, std::list * @snippet test_write_csv.cpp CSV Writer Example * * @par Example w/ std::tuple * @snippet test_write_csv.cpp CSV Writer Tuple Example */ template<class OutputStream, char Delim, char Quote, bool Flush> class DelimWriter { public: /** Construct a DelimWriter over the specified output stream * * @param _out Stream to write to * @param _quote_minimal Limit field quoting to only when necessary */ DelimWriter(OutputStream& _out, bool _quote_minimal = true) : out(_out), quote_minimal(_quote_minimal) {}; /** Construct a DelimWriter over the file * * @param[out] filename File to write to */ DelimWriter(const std::string& filename) : DelimWriter(std::ifstream(filename)) {}; /** Destructor will flush remaining data * */ ~DelimWriter() { out.flush(); } /** Format a sequence of strings and write to CSV according to RFC 4180 * * @warning This does not check to make sure row lengths are consistent * * @param[in] record Sequence of strings to be formatted * * @return The current DelimWriter instance (allowing for operator chaining) */ template<typename T, size_t Size> DelimWriter& operator<<(const std::array<T, Size>& record) { for (size_t i = 0; i < Size; i++) { out << csv_escape(record[i]); if (i + 1 != Size) out << Delim; } end_out(); return *this; } /** @copydoc operator<< */ template<typename... T> DelimWriter& operator<<(const std::tuple<T...>& record) { this->write_tuple<0, T...>(record); return *this; } /** * @tparam T A container such as std::vector, std::deque, or std::list * * @copydoc operator<< */ template< typename T, typename Alloc, template <typename, typename> class Container, // Avoid conflicting with tuples with two elements csv::enable_if_t<std::is_class<Alloc>::value, int> = 0 > DelimWriter& operator<<(const Container<T, Alloc>& record) { const size_t ilen = record.size(); size_t i = 0; for (const auto& field : record) { out << csv_escape(field); if (i + 1 != ilen) out << Delim; i++; } end_out(); return *this; } /** Flushes the written data * */ void flush() { out.flush(); } private: template< typename T, csv::enable_if_t< !std::is_convertible<T, std::string>::value && !std::is_convertible<T, csv::string_view>::value , int> = 0 > std::string csv_escape(T in) { return internals::to_string(in); } template< typename T, csv::enable_if_t< std::is_convertible<T, std::string>::value || std::is_convertible<T, csv::string_view>::value , int> = 0 > std::string csv_escape(T in) { IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) { return _csv_escape(in); } return _csv_escape(std::string(in)); } std::string _csv_escape(csv::string_view in) { /** Format a string to be RFC 4180-compliant * @param[in] in String to be CSV-formatted * @param[out] quote_minimal Only quote fields if necessary. * If False, everything is quoted. */ // Do we need a quote escape bool quote_escape = false; for (auto ch : in) { if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n') { quote_escape = true; break; } } if (!quote_escape) { if (quote_minimal) return std::string(in); else { std::string ret(1, Quote); ret += in.data(); ret += Quote; return ret; } } // Start initial quote escape sequence std::string ret(1, Quote); for (auto ch: in) { if (ch == Quote) ret += std::string(2, Quote); else ret += ch; } // Finish off quote escape ret += Quote; return ret; } /** Recurisve template for writing std::tuples */ template<size_t Index = 0, typename... T> typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) { out << csv_escape(std::get<Index>(record)); IF_CONSTEXPR (Index + 1 < sizeof...(T)) out << Delim; this->write_tuple<Index + 1>(record); } /** Base case for writing std::tuples */ template<size_t Index = 0, typename... T> typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) { (void)record; end_out(); } /** Ends a line in 'out' and flushes, if Flush is true.*/ void end_out() { out << '\n'; IF_CONSTEXPR(Flush) out.flush(); } OutputStream & out; bool quote_minimal; }; /** An alias for csv::DelimWriter for writing standard CSV files * * @sa csv::DelimWriter::operator<<() * * @note Use `csv::make_csv_writer()` to in instatiate this class over * an actual output stream. */ template<class OutputStream, bool Flush = true> using CSVWriter = DelimWriter<OutputStream, ',', '"', Flush>; /** Class for writing tab-separated values files * * @sa csv::DelimWriter::write_row() * @sa csv::DelimWriter::operator<<() * * @note Use `csv::make_tsv_writer()` to in instatiate this class over * an actual output stream. */ template<class OutputStream, bool Flush = true> using TSVWriter = DelimWriter<OutputStream, '\t', '"', Flush>; /** Return a csv::CSVWriter over the output stream */ template<class OutputStream> inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) { return CSVWriter<OutputStream>(out, quote_minimal); } /** Return a buffered csv::CSVWriter over the output stream (does not auto flush) */ template<class OutputStream> inline CSVWriter<OutputStream, false> make_csv_writer_buffered(OutputStream& out, bool quote_minimal=true) { return CSVWriter<OutputStream, false>(out, quote_minimal); } /** Return a csv::TSVWriter over the output stream */ template<class OutputStream> inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) { return TSVWriter<OutputStream>(out, quote_minimal); } /** Return a buffered csv::TSVWriter over the output stream (does not auto flush) */ template<class OutputStream> inline TSVWriter<OutputStream, false> make_tsv_writer_buffered(OutputStream& out, bool quote_minimal=true) { return TSVWriter<OutputStream, false>(out, quote_minimal); } ///@} } namespace csv { namespace internals { CSV_INLINE size_t get_file_size(csv::string_view filename) { std::ifstream infile(std::string(filename), std::ios::binary); const auto start = infile.tellg(); infile.seekg(0, std::ios::end); const auto end = infile.tellg(); return end - start; } CSV_INLINE std::string get_csv_head(csv::string_view filename) { return get_csv_head(filename, get_file_size(filename)); } CSV_INLINE std::string get_csv_head(csv::string_view filename, size_t file_size) { const size_t bytes = 500000; std::error_code error; size_t length = std::min((size_t)file_size, bytes); auto mmap = mio::make_mmap_source(std::string(filename), 0, length, error); if (error) { throw std::runtime_error("Cannot open file " + std::string(filename)); } return std::string(mmap.begin(), mmap.end()); } #ifdef _MSC_VER #pragma region IBasicCVParser #endif CSV_INLINE IBasicCSVParser::IBasicCSVParser( const CSVFormat& format, const ColNamesPtr& col_names ) : _col_names(col_names) { if (format.no_quote) { _parse_flags = internals::make_parse_flags(format.get_delim()); } else { _parse_flags = internals::make_parse_flags(format.get_delim(), format.quote_char); } _ws_flags = internals::make_ws_flags( format.trim_chars.data(), format.trim_chars.size() ); } CSV_INLINE void IBasicCSVParser::end_feed() { using internals::ParseFlags; bool empty_last_field = this->data_ptr && this->data_ptr->_data && !this->data_ptr->data.empty() && (parse_flag(this->data_ptr->data.back()) == ParseFlags::DELIMITER || parse_flag(this->data_ptr->data.back()) == ParseFlags::QUOTE); // Push field if (this->field_length > 0 || empty_last_field) { this->push_field(); } // Push row if (this->current_row.size() > 0) this->push_row(); } CSV_INLINE void IBasicCSVParser::parse_field() noexcept { using internals::ParseFlags; auto& in = this->data_ptr->data; // Trim off leading whitespace while (data_pos < in.size() && ws_flag(in[data_pos])) data_pos++; if (field_start == UNINITIALIZED_FIELD) field_start = (int)(data_pos - current_row_start()); // Optimization: Since NOT_SPECIAL characters tend to occur in contiguous // sequences, use the loop below to avoid having to go through the outer // switch statement as much as possible while (data_pos < in.size() && compound_parse_flag(in[data_pos]) == ParseFlags::NOT_SPECIAL) data_pos++; field_length = data_pos - (field_start + current_row_start()); // Trim off trailing whitespace, this->field_length constraint matters // when field is entirely whitespace for (size_t j = data_pos - 1; ws_flag(in[j]) && this->field_length > 0; j--) this->field_length--; } CSV_INLINE void IBasicCSVParser::push_field() { // Update if (field_has_double_quote) { fields->emplace_back( field_start == UNINITIALIZED_FIELD ? 0 : (unsigned int)field_start, field_length, true ); field_has_double_quote = false; } else { fields->emplace_back( field_start == UNINITIALIZED_FIELD ? 0 : (unsigned int)field_start, field_length ); } current_row.row_length++; // Reset field state field_start = UNINITIALIZED_FIELD; field_length = 0; } /** @return The number of characters parsed that belong to complete rows */ CSV_INLINE size_t IBasicCSVParser::parse() { using internals::ParseFlags; this->quote_escape = false; this->data_pos = 0; this->current_row_start() = 0; this->trim_utf8_bom(); auto& in = this->data_ptr->data; while (this->data_pos < in.size()) { switch (compound_parse_flag(in[this->data_pos])) { case ParseFlags::DELIMITER: this->push_field(); this->data_pos++; break; case ParseFlags::NEWLINE: this->data_pos++; // Catches CRLF (or LFLF, CRCRLF, or any other non-sensical combination of newlines) while (this->data_pos < in.size() && parse_flag(in[this->data_pos]) == ParseFlags::NEWLINE) this->data_pos++; // End of record -> Write record this->push_field(); this->push_row(); // Reset this->current_row = CSVRow(data_ptr, this->data_pos, fields->size()); break; case ParseFlags::NOT_SPECIAL: this->parse_field(); break; case ParseFlags::QUOTE_ESCAPE_QUOTE: if (data_pos + 1 == in.size()) return this->current_row_start(); else if (data_pos + 1 < in.size()) { auto next_ch = parse_flag(in[data_pos + 1]); if (next_ch >= ParseFlags::DELIMITER) { quote_escape = false; data_pos++; break; } else if (next_ch == ParseFlags::QUOTE) { // Case: Escaped quote data_pos += 2; this->field_length += 2; this->field_has_double_quote = true; break; } } // Case: Unescaped single quote => not strictly valid but we'll keep it this->field_length++; data_pos++; break; default: // Quote (currently not quote escaped) if (this->field_length == 0) { quote_escape = true; data_pos++; if (field_start == UNINITIALIZED_FIELD && data_pos < in.size() && !ws_flag(in[data_pos])) field_start = (int)(data_pos - current_row_start()); break; } // Case: Unescaped quote this->field_length++; data_pos++; break; } } return this->current_row_start(); } CSV_INLINE void IBasicCSVParser::push_row() { current_row.row_length = fields->size() - current_row.fields_start; this->_records->push_back(std::move(current_row)); } CSV_INLINE void IBasicCSVParser::reset_data_ptr() { this->data_ptr = std::make_shared<RawCSVData>(); this->data_ptr->parse_flags = this->_parse_flags; this->data_ptr->col_names = this->_col_names; this->fields = &(this->data_ptr->fields); } CSV_INLINE void IBasicCSVParser::trim_utf8_bom() { auto& data = this->data_ptr->data; if (!this->unicode_bom_scan && data.size() >= 3) { if (data[0] == '\xEF' && data[1] == '\xBB' && data[2] == '\xBF') { this->data_pos += 3; // Remove BOM from input string this->_utf8_bom = true; } this->unicode_bom_scan = true; } } #ifdef _MSC_VER #pragma endregion #endif #ifdef _MSC_VER #pragma region Specializations #endif CSV_INLINE void MmapParser::next(size_t bytes = ITERATION_CHUNK_SIZE) { // Reset parser state this->field_start = UNINITIALIZED_FIELD; this->field_length = 0; this->reset_data_ptr(); // Create memory map size_t length = std::min(this->source_size - this->mmap_pos, bytes); std::error_code error; this->data_ptr->_data = std::make_shared<mio::basic_mmap_source<char>>(mio::make_mmap_source(this->_filename, this->mmap_pos, length, error)); this->mmap_pos += length; if (error) throw error; auto mmap_ptr = (mio::basic_mmap_source<char>*)(this->data_ptr->_data.get()); // Create string view this->data_ptr->data = csv::string_view(mmap_ptr->data(), mmap_ptr->length()); // Parse this->current_row = CSVRow(this->data_ptr); size_t remainder = this->parse(); if (this->mmap_pos == this->source_size || no_chunk()) { this->_eof = true; this->end_feed(); } this->mmap_pos -= (length - remainder); } #ifdef _MSC_VER #pragma endregion #endif } } namespace csv { namespace internals { CSV_INLINE std::vector<std::string> ColNames::get_col_names() const { return this->col_names; } CSV_INLINE void ColNames::set_col_names(const std::vector<std::string>& cnames) { this->col_names = cnames; for (size_t i = 0; i < cnames.size(); i++) { this->col_pos[cnames[i]] = i; } } CSV_INLINE int ColNames::index_of(csv::string_view col_name) const { auto pos = this->col_pos.find(col_name.data()); if (pos != this->col_pos.end()) return (int)pos->second; return CSV_NOT_FOUND; } CSV_INLINE size_t ColNames::size() const noexcept { return this->col_names.size(); } } } /** @file * Defines an object used to store CSV format settings */ #include <algorithm> #include <set> namespace csv { CSV_INLINE CSVFormat& CSVFormat::delimiter(char delim) { this->possible_delimiters = { delim }; this->assert_no_char_overlap(); return *this; } CSV_INLINE CSVFormat& CSVFormat::delimiter(const std::vector<char> & delim) { this->possible_delimiters = delim; this->assert_no_char_overlap(); return *this; } CSV_INLINE CSVFormat& CSVFormat::quote(char quote) { this->no_quote = false; this->quote_char = quote; this->assert_no_char_overlap(); return *this; } CSV_INLINE CSVFormat& CSVFormat::trim(const std::vector<char> & chars) { this->trim_chars = chars; this->assert_no_char_overlap(); return *this; } CSV_INLINE CSVFormat& CSVFormat::column_names(const std::vector<std::string>& names) { this->col_names = names; this->header = -1; return *this; } CSV_INLINE CSVFormat& CSVFormat::header_row(int row) { if (row < 0) this->variable_column_policy = VariableColumnPolicy::KEEP; this->header = row; this->col_names = {}; return *this; } CSV_INLINE void CSVFormat::assert_no_char_overlap() { auto delims = std::set<char>( this->possible_delimiters.begin(), this->possible_delimiters.end()), trims = std::set<char>( this->trim_chars.begin(), this->trim_chars.end()); // Stores intersection of possible delimiters and trim characters std::vector<char> intersection = {}; // Find which characters overlap, if any std::set_intersection( delims.begin(), delims.end(), trims.begin(), trims.end(), std::back_inserter(intersection)); // Make sure quote character is not contained in possible delimiters // or whitespace characters if (delims.find(this->quote_char) != delims.end() || trims.find(this->quote_char) != trims.end()) { intersection.push_back(this->quote_char); } if (!intersection.empty()) { std::string err_msg = "There should be no overlap between the quote character, " "the set of possible delimiters " "and the set of whitespace characters. Offending characters: "; // Create a pretty error message with the list of overlapping // characters for (size_t i = 0; i < intersection.size(); i++) { err_msg += "'"; err_msg += intersection[i]; err_msg += "'"; if (i + 1 < intersection.size()) err_msg += ", "; } throw std::runtime_error(err_msg + '.'); } } } /** @file * @brief Defines functionality needed for basic CSV parsing */ namespace csv { namespace internals { CSV_INLINE std::string format_row(const std::vector<std::string>& row, csv::string_view delim) { /** Print a CSV row */ std::stringstream ret; for (size_t i = 0; i < row.size(); i++) { ret << row[i]; if (i + 1 < row.size()) ret << delim; else ret << '\n'; } ret.flush(); return ret.str(); } /** Return a CSV's column names * * @param[in] filename Path to CSV file * @param[in] format Format of the CSV file * */ CSV_INLINE std::vector<std::string> _get_col_names(csv::string_view head, CSVFormat format) { // Parse the CSV auto trim_chars = format.get_trim_chars(); std::stringstream source(head.data()); RowCollection rows; StreamParser<std::stringstream> parser(source, format); parser.set_output(rows); parser.next(); return CSVRow(std::move(rows[format.get_header()])); } CSV_INLINE GuessScore calculate_score(csv::string_view head, const CSVFormat& format) { // Frequency counter of row length std::unordered_map<size_t, size_t> row_tally = { { 0, 0 } }; // Map row lengths to row num where they first occurred std::unordered_map<size_t, size_t> row_when = { { 0, 0 } }; // Parse the CSV std::stringstream source(head.data()); RowCollection rows; StreamParser<std::stringstream> parser(source, format); parser.set_output(rows); parser.next(); for (size_t i = 0; i < rows.size(); i++) { auto& row = rows[i]; // Ignore zero-length rows if (row.size() > 0) { if (row_tally.find(row.size()) != row_tally.end()) { row_tally[row.size()]++; } else { row_tally[row.size()] = 1; row_when[row.size()] = i; } } } double final_score = 0; size_t header_row = 0; // Final score is equal to the largest // row size times rows of that size for (auto& pair : row_tally) { auto row_size = pair.first; auto row_count = pair.second; double score = (double)(row_size * row_count); if (score > final_score) { final_score = score; header_row = row_when[row_size]; } } return { final_score, header_row }; } /** Guess the delimiter used by a delimiter-separated values file */ CSV_INLINE CSVGuessResult _guess_format(csv::string_view head, const std::vector<char>& delims) { /** For each delimiter, find out which row length was most common. * The delimiter with the longest mode row length wins. * Then, the line number of the header row is the first row with * the mode row length. */ CSVFormat format; size_t max_score = 0, header = 0; char current_delim = delims[0]; for (char cand_delim : delims) { auto result = calculate_score(head, format.delimiter(cand_delim)); if ((size_t)result.score > max_score) { max_score = (size_t)result.score; current_delim = cand_delim; header = result.header; } } return { current_delim, (int)header }; } } /** Return a CSV's column names * * @param[in] filename Path to CSV file * @param[in] format Format of the CSV file * */ CSV_INLINE std::vector<std::string> get_col_names(csv::string_view filename, CSVFormat format) { auto head = internals::get_csv_head(filename); /** Guess delimiter and header row */ if (format.guess_delim()) { auto guess_result = guess_format(filename, format.get_possible_delims()); format.delimiter(guess_result.delim).header_row(guess_result.header_row); } return internals::_get_col_names(head, format); } /** Guess the delimiter used by a delimiter-separated values file */ CSV_INLINE CSVGuessResult guess_format(csv::string_view filename, const std::vector<char>& delims) { auto head = internals::get_csv_head(filename); return internals::_guess_format(head, delims); } /** Reads an arbitrarily large CSV file using memory-mapped IO. * * **Details:** Reads the first block of a CSV file synchronously to get information * such as column names and delimiting character. * * @param[in] filename Path to CSV file * @param[in] format Format of the CSV file * * \snippet tests/test_read_csv.cpp CSVField Example * */ CSV_INLINE CSVReader::CSVReader(csv::string_view filename, CSVFormat format) : _format(format) { auto head = internals::get_csv_head(filename); using Parser = internals::MmapParser; /** Guess delimiter and header row */ if (format.guess_delim()) { auto guess_result = internals::_guess_format(head, format.possible_delimiters); format.delimiter(guess_result.delim); format.header = guess_result.header_row; this->_format = format; } if (!format.col_names.empty()) this->set_col_names(format.col_names); this->parser = std::unique_ptr<Parser>(new Parser(filename, format, this->col_names)); // For C++11 this->initial_read(); } /** Return the format of the original raw CSV */ CSV_INLINE CSVFormat CSVReader::get_format() const { CSVFormat new_format = this->_format; // Since users are normally not allowed to set // column names and header row simulatenously, // we will set the backing variables directly here new_format.col_names = this->col_names->get_col_names(); new_format.header = this->_format.header; return new_format; } /** Return the CSV's column names as a vector of strings. */ CSV_INLINE std::vector<std::string> CSVReader::get_col_names() const { if (this->col_names) { return this->col_names->get_col_names(); } return std::vector<std::string>(); } /** Return the index of the column name if found or * csv::CSV_NOT_FOUND otherwise. */ CSV_INLINE int CSVReader::index_of(csv::string_view col_name) const { auto _col_names = this->get_col_names(); for (size_t i = 0; i < _col_names.size(); i++) if (_col_names[i] == col_name) return (int)i; return CSV_NOT_FOUND; } CSV_INLINE void CSVReader::trim_header() { if (!this->header_trimmed) { for (int i = 0; i <= this->_format.header && !this->records->empty(); i++) { if (i == this->_format.header && this->col_names->empty()) { this->set_col_names(this->records->pop_front()); } else { this->records->pop_front(); } } this->header_trimmed = true; } } /** * @param[in] names Column names */ CSV_INLINE void CSVReader::set_col_names(const std::vector<std::string>& names) { this->col_names->set_col_names(names); this->n_cols = names.size(); } /** * Read a chunk of CSV data. * * @note This method is meant to be run on its own thread. Only one `read_csv()` thread * should be active at a time. * * @param[in] bytes Number of bytes to read. * * @see CSVReader::read_csv_worker * @see CSVReader::read_row() */ CSV_INLINE bool CSVReader::read_csv(size_t bytes) { // Tell read_row() to listen for CSV rows this->records->notify_all(); this->parser->set_output(*this->records); this->parser->next(bytes); if (!this->header_trimmed) { this->trim_header(); } // Tell read_row() to stop waiting this->records->kill_all(); return true; } /** * Retrieve rows as CSVRow objects, returning true if more rows are available. * * @par Performance Notes * - Reads chunks of data that are csv::internals::ITERATION_CHUNK_SIZE bytes large at a time * - For performance details, read the documentation for CSVRow and CSVField. * * @param[out] row The variable where the parsed row will be stored * @see CSVRow, CSVField * * **Example:** * \snippet tests/test_read_csv.cpp CSVField Example * */ CSV_INLINE bool CSVReader::read_row(CSVRow &row) { while (true) { if (this->records->empty()) { if (this->records->is_waitable()) // Reading thread is currently active => wait for it to populate records this->records->wait(); else if (this->parser->eof()) // End of file and no more records return false; else { // Reading thread is not active => start another one if (this->read_csv_worker.joinable()) this->read_csv_worker.join(); this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE); } } else if (this->records->front().size() != this->n_cols && this->_format.variable_column_policy != VariableColumnPolicy::KEEP) { auto errored_row = this->records->pop_front(); if (this->_format.variable_column_policy == VariableColumnPolicy::THROW) { if (errored_row.size() < this->n_cols) throw std::runtime_error("Line too short " + internals::format_row(errored_row)); throw std::runtime_error("Line too long " + internals::format_row(errored_row)); } } else { row = this->records->pop_front(); this->_n_rows++; return true; } } return false; } } /** @file * Defines an input iterator for csv::CSVReader */ namespace csv { /** Return an iterator to the first row in the reader */ CSV_INLINE CSVReader::iterator CSVReader::begin() { if (this->records->empty()) { this->read_csv_worker = std::thread(&CSVReader::read_csv, this, internals::ITERATION_CHUNK_SIZE); this->read_csv_worker.join(); // Still empty => return end iterator if (this->records->empty()) return this->end(); } this->_n_rows++; CSVReader::iterator ret(this, this->records->pop_front()); return ret; } /** A placeholder for the imaginary past the end row in a CSV. * Attempting to deference this will lead to bad things. */ CSV_INLINE HEDLEY_CONST CSVReader::iterator CSVReader::end() const noexcept { return CSVReader::iterator(); } ///////////////////////// // CSVReader::iterator // ///////////////////////// CSV_INLINE CSVReader::iterator::iterator(CSVReader* _daddy, CSVRow&& _row) : daddy(_daddy) { row = std::move(_row); } /** Advance the iterator by one row. If this CSVReader has an * associated file, then the iterator will lazily pull more data from * that file until the end of file is reached. * * @note This iterator does **not** block the thread responsible for parsing CSV. * */ CSV_INLINE CSVReader::iterator& CSVReader::iterator::operator++() { if (!daddy->read_row(this->row)) { this->daddy = nullptr; // this == end() } return *this; } /** Post-increment iterator */ CSV_INLINE CSVReader::iterator CSVReader::iterator::operator++(int) { auto temp = *this; if (!daddy->read_row(this->row)) { this->daddy = nullptr; // this == end() } return temp; } } /** @file * Defines the data type used for storing information about a CSV row */ #include <cassert> #include <functional> namespace csv { namespace internals { CSV_INLINE RawCSVField& CSVFieldList::operator[](size_t n) const { const size_t page_no = n / _single_buffer_capacity; const size_t buffer_idx = (page_no < 1) ? n : n % _single_buffer_capacity; return this->buffers[page_no][buffer_idx]; } CSV_INLINE void CSVFieldList::allocate() { buffers.push_back(std::unique_ptr<RawCSVField[]>(new RawCSVField[_single_buffer_capacity])); _current_buffer_size = 0; _back = buffers.back().get(); } } /** Return a CSVField object corrsponding to the nth value in the row. * * @note This method performs bounds checking, and will throw an * `std::runtime_error` if n is invalid. * * @complexity * Constant, by calling csv::CSVRow::get_csv::string_view() * */ CSV_INLINE CSVField CSVRow::operator[](size_t n) const { return CSVField(this->get_field(n)); } /** Retrieve a value by its associated column name. If the column * specified can't be round, a runtime error is thrown. * * @complexity * Constant. This calls the other CSVRow::operator[]() after * converting column names into indices using a hash table. * * @param[in] col_name The column to look for */ CSV_INLINE CSVField CSVRow::operator[](const std::string& col_name) const { auto & col_names = this->data->col_names; auto col_pos = col_names->index_of(col_name); if (col_pos > -1) { return this->operator[](col_pos); } throw std::runtime_error("Can't find a column named " + col_name); } CSV_INLINE CSVRow::operator std::vector<std::string>() const { std::vector<std::string> ret; for (size_t i = 0; i < size(); i++) ret.push_back(std::string(this->get_field(i))); return ret; } CSV_INLINE csv::string_view CSVRow::get_field(size_t index) const { using internals::ParseFlags; if (index >= this->size()) throw std::runtime_error("Index out of bounds."); const size_t field_index = this->fields_start + index; auto& field = this->data->fields[field_index]; auto field_str = csv::string_view(this->data->data).substr(this->data_start + field.start); if (field.has_double_quote) { auto& value = this->data->double_quote_fields[field_index]; if (value.empty()) { bool prev_ch_quote = false; for (size_t i = 0; i < field.length; i++) { if (this->data->parse_flags[static_cast<unsigned char>(field_str[i])] == ParseFlags::QUOTE) { if (prev_ch_quote) { prev_ch_quote = false; continue; } else { prev_ch_quote = true; } } value += field_str[i]; } } return csv::string_view(value); } return field_str.substr(0, field.length); } CSV_INLINE bool CSVField::try_parse_hex(int& parsedValue) { size_t start = 0, end = 0; // Trim out whitespace chars for (; start < this->sv.size() && this->sv[start] == ' '; start++); for (end = start; end < this->sv.size() && this->sv[end] != ' '; end++); int value_ = 0; size_t digits = (end - start); size_t base16_exponent = digits - 1; if (digits == 0) return false; for (const auto& ch : this->sv.substr(start, digits)) { int digit = 0; switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': digit = static_cast<int>(ch - '0'); break; case 'a': case 'A': digit = 10; break; case 'b': case 'B': digit = 11; break; case 'c': case 'C': digit = 12; break; case 'd': case 'D': digit = 13; break; case 'e': case 'E': digit = 14; break; case 'f': case 'F': digit = 15; break; default: return false; } value_ += digit * (int)pow(16, (double)base16_exponent); base16_exponent--; } parsedValue = value_; return true; } CSV_INLINE bool CSVField::try_parse_decimal(long double& dVal, const char decimalSymbol) { // If field has already been parsed to empty, no need to do it aagin: if (this->_type == DataType::CSV_NULL) return false; // Not yet parsed or possibly parsed with other decimalSymbol if (this->_type == DataType::UNKNOWN || this->_type == DataType::CSV_STRING || this->_type == DataType::CSV_DOUBLE) this->_type = internals::data_type(this->sv, &this->value, decimalSymbol); // parse again // Integral types are not affected by decimalSymbol and need not be parsed again // Either we already had an integral type before, or we we just got any numeric type now. if (this->_type >= DataType::CSV_INT8 && this->_type <= DataType::CSV_DOUBLE) { dVal = this->value; return true; } // CSV_NULL or CSV_STRING, not numeric return false; } #ifdef _MSC_VER #pragma region CSVRow Iterator #endif /** Return an iterator pointing to the first field. */ CSV_INLINE CSVRow::iterator CSVRow::begin() const { return CSVRow::iterator(this, 0); } /** Return an iterator pointing to just after the end of the CSVRow. * * @warning Attempting to dereference the end iterator results * in dereferencing a null pointer. */ CSV_INLINE CSVRow::iterator CSVRow::end() const noexcept { return CSVRow::iterator(this, (int)this->size()); } CSV_INLINE CSVRow::reverse_iterator CSVRow::rbegin() const noexcept { return std::reverse_iterator<CSVRow::iterator>(this->end()); } CSV_INLINE CSVRow::reverse_iterator CSVRow::rend() const { return std::reverse_iterator<CSVRow::iterator>(this->begin()); } CSV_INLINE HEDLEY_NON_NULL(2) CSVRow::iterator::iterator(const CSVRow* _reader, int _i) : daddy(_reader), i(_i) { if (_i < (int)this->daddy->size()) this->field = std::make_shared<CSVField>( this->daddy->operator[](_i)); else this->field = nullptr; } CSV_INLINE CSVRow::iterator::reference CSVRow::iterator::operator*() const { return *(this->field.get()); } CSV_INLINE CSVRow::iterator::pointer CSVRow::iterator::operator->() const { return this->field; } CSV_INLINE CSVRow::iterator& CSVRow::iterator::operator++() { // Pre-increment operator this->i++; if (this->i < (int)this->daddy->size()) this->field = std::make_shared<CSVField>( this->daddy->operator[](i)); else // Reached the end of row this->field = nullptr; return *this; } CSV_INLINE CSVRow::iterator CSVRow::iterator::operator++(int) { // Post-increment operator auto temp = *this; this->operator++(); return temp; } CSV_INLINE CSVRow::iterator& CSVRow::iterator::operator--() { // Pre-decrement operator this->i--; this->field = std::make_shared<CSVField>( this->daddy->operator[](this->i)); return *this; } CSV_INLINE CSVRow::iterator CSVRow::iterator::operator--(int) { // Post-decrement operator auto temp = *this; this->operator--(); return temp; } CSV_INLINE CSVRow::iterator CSVRow::iterator::operator+(difference_type n) const { // Allows for iterator arithmetic return CSVRow::iterator(this->daddy, i + (int)n); } CSV_INLINE CSVRow::iterator CSVRow::iterator::operator-(difference_type n) const { // Allows for iterator arithmetic return CSVRow::iterator::operator+(-n); } #ifdef _MSC_VER #pragma endregion CSVRow Iterator #endif } /** @file * Implements JSON serialization abilities */ namespace csv { /* The implementations for json_extra_space() and json_escape_string() were modified from source code for JSON for Modern C++. The respective license is below: The code is licensed under the [MIT License](http://opensource.org/licenses/MIT): Copyright © 2013-2015 Niels Lohmann. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ namespace internals { /*! @brief calculates the extra space to escape a JSON string @param[in] s the string to escape @return the number of characters required to escape string @a s @complexity Linear in the length of string @a s. */ static std::size_t json_extra_space(csv::string_view& s) noexcept { std::size_t result = 0; for (const auto& c : s) { switch (c) { case '"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t': { // from c (1 byte) to \x (2 bytes) result += 1; break; } default: { if (c >= 0x00 && c <= 0x1f) { // from c (1 byte) to \uxxxx (6 bytes) result += 5; } break; } } } return result; } CSV_INLINE std::string json_escape_string(csv::string_view s) noexcept { const auto space = json_extra_space(s); if (space == 0) { return std::string(s); } // create a result string of necessary size size_t result_size = s.size() + space; std::string result(result_size, '\\'); std::size_t pos = 0; for (const auto& c : s) { switch (c) { // quotation mark (0x22) case '"': { result[pos + 1] = '"'; pos += 2; break; } // reverse solidus (0x5c) case '\\': { // nothing to change pos += 2; break; } // backspace (0x08) case '\b': { result[pos + 1] = 'b'; pos += 2; break; } // formfeed (0x0c) case '\f': { result[pos + 1] = 'f'; pos += 2; break; } // newline (0x0a) case '\n': { result[pos + 1] = 'n'; pos += 2; break; } // carriage return (0x0d) case '\r': { result[pos + 1] = 'r'; pos += 2; break; } // horizontal tab (0x09) case '\t': { result[pos + 1] = 't'; pos += 2; break; } default: { if (c >= 0x00 && c <= 0x1f) { // print character c as \uxxxx snprintf(&result[pos + 1], result_size - pos - 1, "u%04x", int(c)); pos += 6; // overwrite trailing null character result[pos] = '\\'; } else { // all other characters are added as-is result[pos++] = c; } break; } } } return result; } } /** Convert a CSV row to a JSON object, i.e. * `{"col1":"value1","col2":"value2"}` * * @note All strings are properly escaped. Numeric values are not quoted. * @param[in] subset A subset of columns to contain in the JSON. * Leave empty for original columns. */ CSV_INLINE std::string CSVRow::to_json(const std::vector<std::string>& subset) const { std::vector<std::string> col_names = subset; if (subset.empty()) { col_names = this->data ? this->get_col_names() : std::vector<std::string>({}); } const size_t _n_cols = col_names.size(); std::string ret = "{"; for (size_t i = 0; i < _n_cols; i++) { auto& col = col_names[i]; auto field = this->operator[](col); // TODO: Possible performance enhancements by caching escaped column names ret += '"' + internals::json_escape_string(col) + "\":"; // Add quotes around strings but not numbers if (field.is_num()) ret += internals::json_escape_string(field.get<csv::string_view>()); else ret += '"' + internals::json_escape_string(field.get<csv::string_view>()) + '"'; // Do not add comma after last string if (i + 1 < _n_cols) ret += ','; } ret += '}'; return ret; } /** Convert a CSV row to a JSON array, i.e. * `["value1","value2",...]` * * @note All strings are properly escaped. Numeric values are not quoted. * @param[in] subset A subset of columns to contain in the JSON. * Leave empty for all columns. */ CSV_INLINE std::string CSVRow::to_json_array(const std::vector<std::string>& subset) const { std::vector<std::string> col_names = subset; if (subset.empty()) col_names = this->data ? this->get_col_names() : std::vector<std::string>({}); const size_t _n_cols = col_names.size(); std::string ret = "["; for (size_t i = 0; i < _n_cols; i++) { auto field = this->operator[](col_names[i]); // Add quotes around strings but not numbers if (field.is_num()) ret += internals::json_escape_string(field.get<csv::string_view>()); else ret += '"' + internals::json_escape_string(field.get<csv::string_view>()) + '"'; // Do not add comma after last string if (i + 1 < _n_cols) ret += ','; } ret += ']'; return ret; } } /** @file * Calculates statistics from CSV files */ #include <string> namespace csv { /** Calculate statistics for an arbitrarily large file. When this constructor * is called, CSVStat will process the entire file iteratively. Once finished, * methods like get_mean(), get_counts(), etc... can be used to retrieve statistics. */ CSV_INLINE CSVStat::CSVStat(csv::string_view filename, CSVFormat format) : reader(filename, format) { this->calc(); } /** Calculate statistics for a CSV stored in a std::stringstream */ CSV_INLINE CSVStat::CSVStat(std::stringstream& stream, CSVFormat format) : reader(stream, format) { this->calc(); } /** Return current means */ CSV_INLINE std::vector<long double> CSVStat::get_mean() const { std::vector<long double> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->rolling_means[i]); } return ret; } /** Return current variances */ CSV_INLINE std::vector<long double> CSVStat::get_variance() const { std::vector<long double> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->rolling_vars[i]/(this->n[i] - 1)); } return ret; } /** Return current mins */ CSV_INLINE std::vector<long double> CSVStat::get_mins() const { std::vector<long double> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->mins[i]); } return ret; } /** Return current maxes */ CSV_INLINE std::vector<long double> CSVStat::get_maxes() const { std::vector<long double> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->maxes[i]); } return ret; } /** Get counts for each column */ CSV_INLINE std::vector<CSVStat::FreqCount> CSVStat::get_counts() const { std::vector<FreqCount> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->counts[i]); } return ret; } /** Get data type counts for each column */ CSV_INLINE std::vector<CSVStat::TypeCount> CSVStat::get_dtypes() const { std::vector<TypeCount> ret; for (size_t i = 0; i < this->get_col_names().size(); i++) { ret.push_back(this->dtypes[i]); } return ret; } CSV_INLINE void CSVStat::calc_chunk() { /** Only create stats counters the first time **/ if (dtypes.empty()) { /** Go through all records and calculate specified statistics */ for (size_t i = 0; i < this->get_col_names().size(); i++) { dtypes.push_back({}); counts.push_back({}); rolling_means.push_back(0); rolling_vars.push_back(0); mins.push_back(NAN); maxes.push_back(NAN); n.push_back(0); } } // Start threads std::vector<std::thread> pool; for (size_t i = 0; i < this->get_col_names().size(); i++) pool.push_back(std::thread(&CSVStat::calc_worker, this, i)); // Block until done for (auto& th : pool) th.join(); this->records.clear(); } CSV_INLINE void CSVStat::calc() { constexpr size_t CALC_CHUNK_SIZE = 5000; for (auto& row : reader) { this->records.push_back(std::move(row)); /** Chunk rows */ if (this->records.size() == CALC_CHUNK_SIZE) { calc_chunk(); } } if (!this->records.empty()) { calc_chunk(); } } CSV_INLINE void CSVStat::calc_worker(const size_t &i) { /** Worker thread for CSVStat::calc() which calculates statistics for one column. * * @param[in] i Column index */ auto current_record = this->records.begin(); for (size_t processed = 0; current_record != this->records.end(); processed++) { if (current_record->size() == this->get_col_names().size()) { auto current_field = (*current_record)[i]; // Optimization: Don't count() if there's too many distinct values in the first 1000 rows if (processed < 1000 || this->counts[i].size() <= 500) this->count(current_field, i); this->dtype(current_field, i); // Numeric Stuff if (current_field.is_num()) { long double x_n = current_field.get<long double>(); // This actually calculates mean AND variance this->variance(x_n, i); this->min_max(x_n, i); } } else if (this->reader.get_format().get_variable_column_policy() == VariableColumnPolicy::THROW) { throw std::runtime_error("Line has different length than the others " + internals::format_row(*current_record)); } ++current_record; } } CSV_INLINE void CSVStat::dtype(CSVField& data, const size_t &i) { /** Given a record update the type counter * @param[in] record Data observation * @param[out] i The column index that should be updated */ auto type = data.type(); if (this->dtypes[i].find(type) != this->dtypes[i].end()) { // Increment count this->dtypes[i][type]++; } else { // Initialize count this->dtypes[i].insert(std::make_pair(type, 1)); } } CSV_INLINE void CSVStat::count(CSVField& data, const size_t &i) { /** Given a record update the frequency counter * @param[in] record Data observation * @param[out] i The column index that should be updated */ auto item = data.get<std::string>(); if (this->counts[i].find(item) != this->counts[i].end()) { // Increment count this->counts[i][item]++; } else { // Initialize count this->counts[i].insert(std::make_pair(item, 1)); } } CSV_INLINE void CSVStat::min_max(const long double &x_n, const size_t &i) { /** Update current minimum and maximum * @param[in] x_n Data observation * @param[out] i The column index that should be updated */ if (std::isnan(this->mins[i])) this->mins[i] = x_n; if (std::isnan(this->maxes[i])) this->maxes[i] = x_n; if (x_n < this->mins[i]) this->mins[i] = x_n; else if (x_n > this->maxes[i]) this->maxes[i] = x_n; } CSV_INLINE void CSVStat::variance(const long double &x_n, const size_t &i) { /** Given a record update rolling mean and variance for all columns * using Welford's Algorithm * @param[in] x_n Data observation * @param[out] i The column index that should be updated */ long double& current_rolling_mean = this->rolling_means[i]; long double& current_rolling_var = this->rolling_vars[i]; long double& current_n = this->n[i]; long double delta; long double delta2; current_n++; if (current_n == 1) { current_rolling_mean = x_n; } else { delta = x_n - current_rolling_mean; current_rolling_mean += delta/current_n; delta2 = x_n - current_rolling_mean; current_rolling_var += delta*delta2; } } /** Useful for uploading CSV files to SQL databases. * * Return a data type for each column such that every value in a column can be * converted to the corresponding data type without data loss. * @param[in] filename The CSV file * * \return A mapping of column names to csv::DataType enums */ CSV_INLINE std::unordered_map<std::string, DataType> csv_data_types(const std::string& filename) { CSVStat stat(filename); std::unordered_map<std::string, DataType> csv_dtypes; auto col_names = stat.get_col_names(); auto temp = stat.get_dtypes(); for (size_t i = 0; i < stat.get_col_names().size(); i++) { auto& col = temp[i]; auto& col_name = col_names[i]; if (col[DataType::CSV_STRING]) csv_dtypes[col_name] = DataType::CSV_STRING; else if (col[DataType::CSV_INT64]) csv_dtypes[col_name] = DataType::CSV_INT64; else if (col[DataType::CSV_INT32]) csv_dtypes[col_name] = DataType::CSV_INT32; else if (col[DataType::CSV_INT16]) csv_dtypes[col_name] = DataType::CSV_INT16; else if (col[DataType::CSV_INT8]) csv_dtypes[col_name] = DataType::CSV_INT8; else csv_dtypes[col_name] = DataType::CSV_DOUBLE; } return csv_dtypes; } } #include <sstream> #include <vector> namespace csv { /** Shorthand function for parsing an in-memory CSV string * * @return A collection of CSVRow objects * * @par Example * @snippet tests/test_read_csv.cpp Parse Example */ CSV_INLINE CSVReader parse(csv::string_view in, CSVFormat format) { std::stringstream stream(in.data()); return CSVReader(stream, format); } /** Parses a CSV string with no headers * * @return A collection of CSVRow objects */ CSV_INLINE CSVReader parse_no_header(csv::string_view in) { CSVFormat format; format.header_row(-1); return parse(in, format); } /** Parse a RFC 4180 CSV string, returning a collection * of CSVRow objects * * @par Example * @snippet tests/test_read_csv.cpp Escaped Comma * */ CSV_INLINE CSVReader operator ""_csv(const char* in, size_t n) { return parse(csv::string_view(in, n)); } /** A shorthand for csv::parse_no_header() */ CSV_INLINE CSVReader operator ""_csv_no_header(const char* in, size_t n) { return parse_no_header(csv::string_view(in, n)); } /** * Find the position of a column in a CSV file or CSV_NOT_FOUND otherwise * * @param[in] filename Path to CSV file * @param[in] col_name Column whose position we should resolve * @param[in] format Format of the CSV file */ CSV_INLINE int get_col_pos( csv::string_view filename, csv::string_view col_name, const CSVFormat& format) { CSVReader reader(filename, format); return reader.index_of(col_name); } /** Get basic information about a CSV file * @include programs/csv_info.cpp */ CSV_INLINE CSVFileInfo get_file_info(const std::string& filename) { CSVReader reader(filename); CSVFormat format = reader.get_format(); for (auto it = reader.begin(); it != reader.end(); ++it); CSVFileInfo info = { filename, reader.get_col_names(), format.get_delim(), reader.n_rows(), reader.get_col_names().size() }; return info; } } #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/evt-tls/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016376�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/evt-tls/CMakeLists.txt������������������������������������������������������0000664�0000000�0000000�00000001712�15162662266�0021137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(evt-tls OBJECT queue.h evt_tls.h evt_tls.cpp ) if(TARGET PkgConfig::openssl) target_link_libraries(evt-tls PRIVATE PkgConfig::openssl) elseif(TARGET OpenSSL::SSL) target_link_libraries(evt-tls PRIVATE OpenSSL::SSL) else() message(FATAL_ERROR "evt-tls requires OpenSSL. (No PkgConfig::openssl or OpenSSL::SSL found).") endif() set(EVT_TLS_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/evt-tls") file(MAKE_DIRECTORY "${EVT_TLS_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/evt_tls.h" "${EVT_TLS_PUBLIC_INCLUDE_DIR}/evt_tls.h" COPYONLY ) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/queue.h" "${EVT_TLS_PUBLIC_INCLUDE_DIR}/queue.h" COPYONLY ) target_include_directories(evt-tls SYSTEM PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(ENABLE_JAVA_BINDINGS) set_target_properties(evt-tls PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() ������������������������������������������������������sdk-10.11.0/third_party/evt-tls/LICENSE�������������������������������������������������������������0000664�0000000�0000000�00000002106�15162662266�0017402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The MIT License (MIT) Copyright (c) 2015 Devchandra M. Leishangthem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/evt-tls/evt_tls.cpp���������������������������������������������������������0000664�0000000�0000000�00000026636�15162662266�0020577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//%LICENSE//////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Devchandra M. Leishangthem (dlmeetei at gmail dot com) // // Distributed under the MIT License (See accompanying file LICENSE) // ////////////////////////////////////////////////////////////////////////// // //%/////////////////////////////////////////////////////////////////////////// // clang-format off #include <assert.h> #include <string.h> #include <evt-tls/evt_tls.h> /* *All the asserts used in the code are possible targets for error * handling/error reporting */ evt_endpt_t evt_tls_get_role(const evt_tls_t *t) { assert(t != NULL); #if OPENSSL_VERSION_NUMBER < 0x10002000L return t->ssl->server ? ENDPT_IS_SERVER : ENDPT_IS_CLIENT; #else return SSL_is_server(t->ssl) ? ENDPT_IS_SERVER : ENDPT_IS_CLIENT; #endif } void evt_tls_set_role(evt_tls_t *t, evt_endpt_t role) { assert(t != NULL && (role == ENDPT_IS_CLIENT || role == ENDPT_IS_SERVER)); if ( ENDPT_IS_SERVER == role ) { SSL_set_accept_state(t->ssl); } else { SSL_set_connect_state(t->ssl); } } SSL_CTX *evt_get_SSL_CTX(const evt_ctx_t *ctx) { return ctx->ctx; } SSL *evt_get_ssl(const evt_tls_t *tls) { return tls->ssl; } static void tls_begin(void) { SSL_library_init(); #if OPENSSL_VERSION_NUMBER < 0x10100000L // the following functions have been deprecated and are no longer needed starting with OpenSSL v1.1.0 SSL_load_error_strings(); ERR_load_BIO_strings(); #endif OpenSSL_add_all_algorithms(); } evt_tls_t *evt_ctx_get_tls(evt_ctx_t *d_eng) { int r = 0; evt_tls_t *con = (evt_tls_t *)malloc(sizeof(evt_tls_t)); if ( !con ) { return NULL; } memset( con, 0, sizeof *con); SSL *ssl = SSL_new(d_eng->ctx); if ( !ssl ) { free(con); return NULL; } con->ssl = ssl; //use default buf size for now. r = BIO_new_bio_pair(&(con->ssl_bio), 0, &(con->app_bio), 0); if (r != 1) { //order is important SSL_free(ssl); ssl = NULL; free(con); con = NULL; return NULL; } SSL_set_bio(con->ssl, con->ssl_bio, con->ssl_bio); QUEUE_INIT(&(con->q)); QUEUE_INSERT_TAIL(&(d_eng->live_con), &(con->q)); con->writer = d_eng->writer; con->reader = d_eng->reader; con->evt_ctx = d_eng; return con; } void evt_ctx_set_writer(evt_ctx_t *ctx, net_wrtr my_writer) { ctx->writer = my_writer; assert( ctx->writer != NULL); } void evt_tls_set_writer(evt_tls_t *tls, net_wrtr my_writer) { tls->writer = my_writer; assert( tls->writer != NULL); } void evt_ctx_set_reader(evt_ctx_t *ctx, net_rdr my_reader) { ctx->reader = my_reader; //assert( ctx->reader != NULL); } void evt_tls_set_reader(evt_tls_t *tls, net_rdr my_reader) { tls->reader = my_reader; //assert( ctx->reader != NULL); } void evt_ctx_set_nio(evt_ctx_t *ctx, net_rdr my_reader, net_wrtr my_writer) { ctx->reader = my_reader; //assert( ctx->reader != NULL); ctx->writer = my_writer; assert( ctx->writer != NULL); } int evt_ctx_set_crt_key(evt_ctx_t *tls, const char *crtf, const char *key) { SSL_CTX_set_verify(tls->ctx, SSL_VERIFY_NONE, NULL); int r = SSL_CTX_use_certificate_file(tls->ctx, crtf, SSL_FILETYPE_PEM); if(r != 1) { return r; } tls->cert_set = 1; r = SSL_CTX_use_PrivateKey_file(tls->ctx, key, SSL_FILETYPE_PEM); if(r != 1) { return r; } r = SSL_CTX_check_private_key(tls->ctx); if(r != 1) { return r; } tls->key_set = 1; return 1; } int evt_ctx_init(evt_ctx_t *tls) { tls_begin(); //Currently we support only TLS, No DTLS //XXX SSLv23_method is deprecated change this, //Allow evt_ctx_init to take the method as input param, //allow others like dtls tls->ctx = SSL_CTX_new(SSLv23_method()); if ( !tls->ctx ) { return -1; } uint32_t options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; SSL_CTX_set_options(tls->ctx, options); #if defined(SSL_MODE_RELEASE_BUFFERS) SSL_CTX_set_mode(tls->ctx, SSL_MODE_AUTO_RETRY | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_RELEASE_BUFFERS ); #else SSL_CTX_set_mode(tls->ctx, SSL_MODE_AUTO_RETRY | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE ); #endif tls->cert_set = 0; tls->key_set = 0; tls->ssl_err_ = 0; tls->writer = NULL; tls->reader = NULL; QUEUE_INIT(&(tls->live_con)); return 0; } int evt_ctx_init_ex(evt_ctx_t *tls, const char *crtf, const char *key) { #ifndef NDEBUG int r = 0; r = #endif evt_ctx_init( tls); assert( 0 == r); return evt_ctx_set_crt_key(tls, crtf, key); } int evt_ctx_is_crtf_set(evt_ctx_t *t) { return t->cert_set; } int evt_ctx_is_key_set(evt_ctx_t *t) { return t->key_set; } static int evt__send_pending(evt_tls_t *conn) { assert( conn != NULL); int pending = (int)BIO_pending(conn->app_bio); if ( !(pending > 0) ) return 0; void *buf = new char[pending]; assert(buf != NULL && "Memory alloc failed"); if (!buf) return 0; int p = BIO_read(conn->app_bio, buf, pending); assert(p == pending); assert( conn->writer != NULL && "You need to set network writer first"); p = conn->writer(conn, buf, p); return p; } static int evt__tls__op(evt_tls_t *conn, enum tls_op_type op, void *buf, size_t sz) { int r = 0; int bytes = 0; char tbuf[16*1024] = {0}; switch ( op ) { case EVT_TLS_OP_HANDSHAKE: { r = SSL_do_handshake(conn->ssl); do { bytes = evt__send_pending(conn); } while ( bytes > 0 ); if (1 == r || 0 == r) { assert(conn->hshake_cb != NULL ); conn->hshake_cb(conn, r); } if (r != 1) { break; } // fall through to process possible data queued after the handshake } // fall-through case EVT_TLS_OP_READ: { r = SSL_read(conn->ssl, tbuf, sizeof(tbuf)); do { if ( r == 0 ) goto handle_shutdown; do { bytes = evt__send_pending(conn); } while ( bytes > 0 ); if (r > 0) { assert(conn->read_cb != NULL); conn->read_cb(conn, tbuf, r); } r = SSL_read(conn->ssl, tbuf, sizeof(tbuf)); } while (r > 0); //do it again if required break; } case EVT_TLS_OP_WRITE: { assert( sz > 0 && "number of bytes to write should be positive"); r = SSL_write(conn->ssl, buf, int(sz)); if ( 0 == r) goto handle_shutdown; do { bytes = evt__send_pending(conn); } while ( bytes > 0 ); if ( r > 0 && conn->write_cb) { conn->write_cb(conn, r); } break; } /* we initiate shutdown process, send the close_notify but we are not * sure if peer will sent their close_notify hence fire the callback * if the peer replied, it will be processed in SSL_read returning 0 * and jump to handle_shutdown * * No check for SSL_shutdown done as it may be possible that user * called close upon receive of EOF. * TODO: Find a elegant way later * */ case EVT_TLS_OP_SHUTDOWN: { r = SSL_shutdown(conn->ssl); do { bytes = evt__send_pending(conn); } while ( bytes > 0 ); if ( conn->close_cb ) { conn->close_cb(conn, r); } break; } default: assert( 0 && "Unsupported operation"); break; } return r; handle_shutdown: r = SSL_shutdown(conn->ssl); //it might be possible that peer send close_notify and close the network //hence, no check if sending is complete evt__send_pending(conn); if ( (1 == r) && conn->close_cb ) { conn->close_cb(conn, r); } return r; } int evt_tls_is_handshake_over( const evt_tls_t *evt) { return SSL_is_init_finished(evt->ssl); } int evt_tls_feed_data(evt_tls_t *c, void *data, int sz) { int offset = 0; int rv = 0; int i = 0; assert( data != NULL && "invalid argument passed"); assert( sz > 0 && "Size of data should be positive"); for( offset = 0; offset < sz; offset += i ) { //handle error condition i = BIO_write(c->app_bio, (char *)data + offset, sz - offset); //if handshake is not complete, do it again if ( evt_tls_is_handshake_over(c) ) { rv = evt__tls__op(c, EVT_TLS_OP_READ, NULL, 0); } else { rv = evt__tls__op(c, EVT_TLS_OP_HANDSHAKE, NULL, 0); } } return rv; } int evt_tls_connect(evt_tls_t *con, evt_handshake_cb cb) { con->hshake_cb = cb; SSL_set_connect_state(con->ssl); return evt__tls__op(con, EVT_TLS_OP_HANDSHAKE, NULL, 0); } int evt_tls_accept(evt_tls_t *tls, evt_handshake_cb cb) { assert(tls != NULL); SSL_set_accept_state(tls->ssl); tls->hshake_cb = cb; //assert( tls->reader != NULL && "You need to set network reader first"); //char edata[16*1024] = {0}; //tls->reader(tls, edata, sizeof(edata)); return 0; } int evt_tls_write(evt_tls_t *c, void *msg, size_t str_len, evt_write_cb on_write) { c->write_cb = on_write; return evt__tls__op(c, EVT_TLS_OP_WRITE, msg, str_len); } // read only register the callback to be made int evt_tls_read(evt_tls_t *c, evt_read_cb on_read) { assert(c != NULL); c->read_cb = on_read; return 0; } int evt_tls_close(evt_tls_t *tls, evt_close_cb cb) { assert(tls != NULL); tls->close_cb = cb; return evt__tls__op(tls, EVT_TLS_OP_SHUTDOWN, NULL, 0); } //need impl int evt_tls_force_close(evt_tls_t *tls, evt_close_cb cb); int evt_tls_free(evt_tls_t *tls) { BIO_free(tls->app_bio); tls->app_bio = NULL; SSL_free(tls->ssl); tls->ssl = NULL; QUEUE_REMOVE( &(tls->q)); QUEUE_INIT( &(tls->q) ); free(tls); tls = NULL; return 0; } void evt_ctx_free(evt_ctx_t *ctx) { QUEUE* qh; evt_tls_t *tls = NULL; assert( ctx != NULL); //clean all pending connections QUEUE_FOREACH(qh, &ctx->live_con) { tls = QUEUE_DATA(qh, evt_tls_t, q); evt__tls__op(tls, EVT_TLS_OP_SHUTDOWN, NULL, 0); } SSL_CTX_free(ctx->ctx); ctx->ctx = NULL; #if OPENSSL_VERSION_NUMBER < 0x10000000 ERR_remove_state(0); #elif OPENSSL_VERSION_NUMBER < 0x10100000 ERR_remove_thread_state(NULL); #endif ERR_free_strings(); EVP_cleanup(); sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); //SSL_COMP_free_compression_methods(); CRYPTO_cleanup_all_ex_data(); } // adapted from Openssl's s23_srvr.c code int evt_is_tls_stream(const char *bfr, const ssize_t #ifndef NDEBUG nrd #endif ) { int is_tls = 0; assert( nrd >= 11); if ((bfr[0] & 0x80) && (bfr[2] == 1)) // SSL2_MT_CLIENT_HELLO { // SSLv2 is_tls = 1; } if ( (bfr[0] == 0x16 ) && (bfr[1] == 0x03) && (bfr[5] == 1) && ((bfr[3] == 0 && bfr[4] < 5) || (bfr[9] == bfr[1])) ) { //SSLv3 and above is_tls = 1; } return is_tls; } ��������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/evt-tls/evt_tls.h�����������������������������������������������������������0000664�0000000�0000000�00000014125�15162662266�0020232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//%LICENSE//////////////////////////////////////////////////////////////////// // // Copyright (c) 2015 Devchandra M. Leishangthem (dlmeetei at gmail dot com) // // Distributed under the MIT License (See accompanying file LICENSE) // ////////////////////////////////////////////////////////////////////////// // //%/////////////////////////////////////////////////////////////////////////// #ifndef EVT_TLS_H #define EVT_TLS_H // clang-format off #ifdef __cplusplus extern "C" { #endif #include <openssl/err.h> #include <openssl/ssl.h> #include <openssl/conf.h> #include "queue.h" #ifdef _WIN32 #if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) typedef intptr_t ssize_t; # define _SSIZE_T_ # define _SSIZE_T_DEFINED #endif #endif typedef struct evt_tls_s evt_tls_t; //callback used for handshake completion notificat6ion //common for both client and server role typedef void (*evt_handshake_cb)(evt_tls_t *con, int status); typedef void (*evt_read_cb)(evt_tls_t *con, char *buf, int size); typedef void (*evt_write_cb)(evt_tls_t *con, int status); typedef void (*evt_close_cb)(evt_tls_t *con, int status); typedef int (*net_wrtr)(evt_tls_t *tls, void *edata, int len); typedef int (*net_rdr)(evt_tls_t *tls, void *edata, int len); /* * The TLS context, similar to openSSL's SSl_CTX */ typedef struct evt_ctx_s { //find better place for it , should be one time init SSL_CTX *ctx; //is cert set int cert_set; //is key set int key_set; //flag to signify if ssl error has occured int ssl_err_; //list of live connections created from this ctx void *live_con[2]; //function used to updating peer with SSL data net_wrtr writer; //function for reading network data and feeding to evt net_rdr reader; } evt_ctx_t; struct evt_tls_s { void *data; //Our BIO, all IO should be through this BIO *app_bio; SSL *ssl; //this can be changed per connections net_wrtr writer; net_rdr reader; //callbacks evt_handshake_cb hshake_cb; evt_read_cb read_cb; evt_write_cb write_cb; evt_close_cb close_cb; //back handle to parent evt_ctx_t *evt_ctx; QUEUE q; BIO *ssl_bio; //the ssl BIO used only by openSSL }; //supported TLS operation enum tls_op_type { EVT_TLS_OP_HANDSHAKE ,EVT_TLS_OP_READ ,EVT_TLS_OP_WRITE ,EVT_TLS_OP_SHUTDOWN }; /*configure the tls state machine */ int evt_ctx_init(evt_ctx_t *tls); /*configure the tls state machine This apart from configuring state machine also set up cert and key */ int evt_ctx_init_ex(evt_ctx_t *tls, const char *crtf, const char *key); /* set the certifcate and key in orderi. This need more breakup */ int evt_ctx_set_crt_key(evt_ctx_t *tls, const char *crtf, const char *key); /* test if the certificate is set*/ int evt_ctx_is_crtf_set(evt_ctx_t *t); /* test if the key is set */ int evt_ctx_is_key_set(evt_ctx_t *t); /*get a new async tls endpoint from tls engine */ evt_tls_t *evt_ctx_get_tls(evt_ctx_t *d_eng); /*evt-tls is based on BIO pair wherein user takes control of network io writer(tested) and reader(currently untested) is responsible for networt io. This set up the writer and reader which is inherited by all endpoints */ void evt_ctx_set_writer(evt_ctx_t *ctx, net_wrtr my_writer); void evt_ctx_set_reader(evt_ctx_t *ctx, net_rdr my_reader); void evt_ctx_set_nio(evt_ctx_t *ctx, net_rdr my_reader, net_wrtr my_writer); /*clean up the resources held by async tls engine, This also closes endpoints if any left */ void evt_ctx_free(evt_ctx_t *ctx); /*entry point to the tls world, Call this function whenever network read happen Experimental state with network reader concept, but this is tested*/ int evt_tls_feed_data(evt_tls_t *c, void *data, int sz); /*set up the writer and reader for this particular endpoint*/ void evt_tls_set_writer(evt_tls_t *tls, net_wrtr my_writer); void evt_tls_set_reader(evt_tls_t *tls, net_rdr my_reader); /*Check if handshake is over, return 1 if handshake is done otherwise 0 */ int evt_tls_is_handshake_over(const evt_tls_t *evt); /*Perform a handshake for client role endpoint, equivalent of `SSL_connect` Upon completion, `evt_handshake_cb is called, status == 0 for failure and 1 otherwise */ int evt_tls_connect(evt_tls_t *con, evt_handshake_cb cb); /*Perform a handshake for server role endpoint, equivalent of `SSL_accept` Upon completion, `evt_handshake_cb is called, status == 0 for failure and 1 otherwise */ int evt_tls_accept( evt_tls_t *tls, evt_handshake_cb cb); /*Perform wrapping of text and do network write, `evt_write_cb` is called on completion and status is used for status */ int evt_tls_write(evt_tls_t *c, void *msg, size_t str_len, evt_write_cb on_write); /*Perform a unwrapping of network received data, equivalent of `SSL_read` and `evt_read_cb is called on completion */ int evt_tls_read(evt_tls_t *c, evt_read_cb on_read ); /* equivalent of SSL_shutwdown, This performs Two-way SSL_dhutdown */ int evt_tls_close(evt_tls_t *c, evt_close_cb cls); /*XXX: should not be API, should be performed by evt_tls_close */ int evt_tls_free(evt_tls_t *tls); /****************************************************************************** SSL helper API ******************************************************************************/ //openssl>=1.0.2 has SSL_is_server API to check if the ssl connection is server. //Older versions does not have this function. Hence this function is introduced. enum evt_endpt_t { ENDPT_IS_CLIENT ,ENDPT_IS_SERVER }; typedef enum evt_endpt_t evt_endpt_t; /*Tells if the tls endpoint is client or server */ evt_endpt_t evt_tls_get_role(const evt_tls_t *t); /* set role to endpoint either server role or client role */ void evt_tls_set_role(evt_tls_t *t, enum evt_endpt_t role); /*Gives the ptr to SSL_CTX usable raw openSSL programming */ SSL_CTX *evt_get_SSL_CTX(const evt_ctx_t *ctx); /*Gives the ssl usable for doing raw OpenSSL programming */ SSL *evt_get_ssl(const evt_tls_t *tls); /*check if incoming data is TLS clientHello. return 1 if the stream is TLS and 0 otherwise */ int evt_is_tls_stream(const char *bfr, const ssize_t nrd); #ifdef __cplusplus } #endif #endif //define EVT_TLS_H �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/evt-tls/queue.h�������������������������������������������������������������0000664�0000000�0000000�00000011545�15162662266�0017701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef QUEUE_H_ #define QUEUE_H_ // clang-format off typedef void *QUEUE[2]; /* Private macros. */ #define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0])) #define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1])) #define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q))) #define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q))) /* Public macros. */ #define QUEUE_DATA(ptr, type, field) \ ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field))) #define QUEUE_FOREACH(q, h) \ for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q)) #define QUEUE_EMPTY(q) \ ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q)) #define QUEUE_HEAD(q) \ (QUEUE_NEXT(q)) #define QUEUE_INIT(q) \ do { \ QUEUE_NEXT(q) = (q); \ QUEUE_PREV(q) = (q); \ } \ while (0) #define QUEUE_ADD(h, n) \ do { \ QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \ QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \ QUEUE_PREV(h) = QUEUE_PREV(n); \ QUEUE_PREV_NEXT(h) = (h); \ } \ while (0) #define QUEUE_SPLIT(h, q, n) \ do { \ QUEUE_PREV(n) = QUEUE_PREV(h); \ QUEUE_PREV_NEXT(n) = (n); \ QUEUE_NEXT(n) = (q); \ QUEUE_PREV(h) = QUEUE_PREV(q); \ QUEUE_PREV_NEXT(h) = (h); \ QUEUE_PREV(q) = (n); \ } \ while (0) #define QUEUE_INSERT_HEAD(h, q) \ do { \ QUEUE_NEXT(q) = QUEUE_NEXT(h); \ QUEUE_PREV(q) = (h); \ QUEUE_NEXT_PREV(q) = (q); \ QUEUE_NEXT(h) = (q); \ } \ while (0) #define QUEUE_INSERT_TAIL(h, q) \ do { \ QUEUE_NEXT(q) = (h); \ QUEUE_PREV(q) = QUEUE_PREV(h); \ QUEUE_PREV_NEXT(q) = (q); \ QUEUE_PREV(h) = (q); \ } \ while (0) #define QUEUE_REMOVE(q) \ do { \ QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \ QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \ } \ while (0) #endif /* QUEUE_H_ */ �����������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/glob/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015723�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/glob/CMakeLists.txt���������������������������������������������������������0000664�0000000�0000000�00000000374�15162662266�0020467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(glob OBJECT glob.h glob.c ) target_include_directories(glob SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) if(ENABLE_JAVA_BINDINGS) set_target_properties(glob PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/glob/glob.c�����������������������������������������������������������������0000664�0000000�0000000�00000052270�15162662266�0017020�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* * Natanael Arndt, 2011: removed collate.h dependencies * (my changes are trivial) * * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Guido van Rossum. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; #endif /* LIBC_SCCS and not lint */ #include <sys/cdefs.h> #ifdef __FBSDID __FBSDID("$FreeBSD$"); #endif /* * glob(3) -- a superset of the one defined in POSIX 1003.2. * * The [!...] convention to negate a range is supported (SysV, Posix, ksh). * * Optional extra services, controlled by flags not defined by POSIX: * * GLOB_QUOTE: * Escaping convention: \ inhibits any special meaning the following * character might have (except \ at end of string is retained). * GLOB_MAGCHAR: * Set in gl_flags if pattern contained a globbing character. * GLOB_NOMAGIC: * Same as GLOB_NOCHECK, but it will only append pattern if it did * not contain any magic characters. [Used in csh style globbing] * GLOB_ALTDIRFUNC: * Use alternately specified directory access functions. * GLOB_TILDE: * expand ~user/foo to the /home/dir/of/user/foo * GLOB_BRACE: * expand {1,2}{a,b} to 1a 1b 2a 2b * gl_matchc: * Number of matches in the current invocation of glob. */ /* * Some notes on multibyte character support: * 1. Patterns with illegal byte sequences match nothing - even if * GLOB_NOCHECK is specified. * 2. Illegal byte sequences in filenames are handled by treating them as * single-byte characters with a value of the first byte of the sequence * cast to wchar_t. * 3. State-dependent encodings are not currently supported. */ #include <sys/param.h> #include <sys/stat.h> #include <ctype.h> #include <dirent.h> #include <errno.h> #include <limits.h> #include <pwd.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <wchar.h> #include "glob.h" #define DOLLAR '$' #define DOT '.' #define EOS '\0' #define LBRACKET '[' #define NOT '!' #define QUESTION '?' #define QUOTE '\\' #define RANGE '-' #define RBRACKET ']' #define SEP '/' #define STAR '*' #define TILDE '~' #define UNDERSCORE '_' #define LBRACE '{' #define RBRACE '}' #define SLASH '/' #define COMMA ',' #ifndef DEBUG #define M_QUOTE 0x8000000000ULL #define M_PROTECT 0x4000000000ULL #define M_MASK 0xffffffffffULL #define M_CHAR 0x00ffffffffULL typedef uint_fast64_t Char; #else #define M_QUOTE 0x80 #define M_PROTECT 0x40 #define M_MASK 0xff #define M_CHAR 0x7f typedef char Char; #endif #define CHAR(c) ((Char)((c)&M_CHAR)) #define META(c) ((Char)((c)|M_QUOTE)) #define M_ALL META('*') #define M_END META(']') #define M_NOT META('!') #define M_ONE META('?') #define M_RNG META('-') #define M_SET META('[') #define ismeta(c) (((c)&M_QUOTE) != 0) static int compare(const void *, const void *); static int g_Ctoc(const Char *, char *, size_t); static int g_lstat(Char *, struct stat *, glob_t *); static DIR *g_opendir(Char *, glob_t *); static const Char *g_strchr(const Char *, wchar_t); #ifdef notdef static Char *g_strcat(Char *, const Char *); #endif static int g_stat(Char *, struct stat *, glob_t *); static int glob0(const Char *, glob_t *, size_t *); static int glob1(Char *, glob_t *, size_t *); static int glob2(Char *, Char *, Char *, Char *, glob_t *, size_t *); static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, size_t *); static int globextend(const Char *, glob_t *, size_t *); static const Char * globtilde(const Char *, Char *, size_t, glob_t *); static int globexp1(const Char *, glob_t *, size_t *); static int globexp2(const Char *, const Char *, glob_t *, int *, size_t *); static int match(Char *, Char *, Char *); #ifdef DEBUG static void qprintf(const char *, Char *); #endif int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob) { const char *patnext; size_t limit; Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; mbstate_t mbs; wchar_t wc; size_t clen; patnext = pattern; if (!(flags & GLOB_APPEND)) { pglob->gl_pathc = 0; pglob->gl_pathv = NULL; if (!(flags & GLOB_DOOFFS)) pglob->gl_offs = 0; } limit = 0; pglob->gl_flags = flags & ~GLOB_MAGCHAR; pglob->gl_errfunc = errfunc; pglob->gl_matchc = 0; bufnext = patbuf; bufend = bufnext + MAXPATHLEN - 1; if (flags & GLOB_NOESCAPE) { memset(&mbs, 0, sizeof(mbs)); while (bufend - bufnext >= MB_CUR_MAX) { clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) return (GLOB_NOMATCH); else if (clen == 0) break; *bufnext++ = wc; patnext += clen; } } else { /* Protect the quoted characters. */ memset(&mbs, 0, sizeof(mbs)); while (bufend - bufnext >= MB_CUR_MAX) { if (*patnext == QUOTE) { if (*++patnext == EOS) { *bufnext++ = QUOTE | M_PROTECT; continue; } prot = M_PROTECT; } else prot = 0; clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) return (GLOB_NOMATCH); else if (clen == 0) break; *bufnext++ = wc | prot; patnext += clen; } } *bufnext = EOS; if (flags & GLOB_BRACE) return globexp1(patbuf, pglob, &limit); else return glob0(patbuf, pglob, &limit); } /* * Expand recursively a glob {} pattern. When there is no more expansion * invoke the standard globbing routine to glob the rest of the magic * characters */ static int globexp1(const Char *pattern, glob_t *pglob, size_t *limit) { const Char* ptr = pattern; int rv; /* Protect a single {}, for find(1), like csh */ if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) return glob0(pattern, pglob, limit); while ((ptr = g_strchr(ptr, LBRACE)) != NULL) if (!globexp2(ptr, pattern, pglob, &rv, limit)) return rv; return glob0(pattern, pglob, limit); } /* * Recursive brace globbing helper. Tries to expand a single brace. * If it succeeds then it invokes globexp1 with the new pattern. * If it fails then it tries to glob the rest of the pattern and returns. */ static int globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv, size_t *limit) { int i; Char *lm, *ls; const Char *pe, *pm, *pm1, *pl; Char patbuf[MAXPATHLEN]; /* copy part up to the brace */ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) continue; *lm = EOS; ls = lm; /* Find the balanced brace */ for (i = 0, pe = ++ptr; *pe; pe++) if (*pe == LBRACKET) { /* Ignore everything between [] */ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) continue; if (*pe == EOS) { /* * We could not find a matching RBRACKET. * Ignore and just look for RBRACE */ pe = pm; } } else if (*pe == LBRACE) i++; else if (*pe == RBRACE) { if (i == 0) break; i--; } /* Non matching braces; just glob the pattern */ if (i != 0 || *pe == EOS) { *rv = glob0(patbuf, pglob, limit); return 0; } for (i = 0, pl = pm = ptr; pm <= pe; pm++) switch (*pm) { case LBRACKET: /* Ignore everything between [] */ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) continue; if (*pm == EOS) { /* * We could not find a matching RBRACKET. * Ignore and just look for RBRACE */ pm = pm1; } break; case LBRACE: i++; break; case RBRACE: if (i) { i--; break; } /* FALLTHROUGH */ case COMMA: if (i && *pm == COMMA) break; else { /* Append the current string */ for (lm = ls; (pl < pm); *lm++ = *pl++) continue; /* * Append the rest of the pattern after the * closing brace */ for (pl = pe + 1; (*lm++ = *pl++) != EOS;) continue; /* Expand the current pattern */ #ifdef DEBUG qprintf("globexp2:", patbuf); #endif *rv = globexp1(patbuf, pglob, limit); /* move after the comma, to the next string */ pl = pm + 1; } break; default: break; } *rv = 0; return 0; } /* * expand tilde from the passwd file. */ static const Char * globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) { struct passwd *pwd; char *h; const Char *p; Char *b, *eb; if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE)) return pattern; /* * Copy up to the end of the string or / */ eb = &patbuf[patbuf_len - 1]; for (p = pattern + 1, h = (char *) patbuf; h < (char *)eb && *p && *p != SLASH; *h++ = *p++) continue; *h = EOS; if (((char *) patbuf)[0] == EOS) { /* * handle a plain ~ or ~/ by expanding $HOME first (iff * we're not running setuid or setgid) and then trying * the password file */ if ((h = getenv("HOME")) == NULL) { if (((h = getlogin()) != NULL && (pwd = getpwnam(h)) != NULL) || (pwd = getpwuid(getuid())) != NULL) h = pwd->pw_dir; else return pattern; } } else { /* * Expand a ~user */ if ((pwd = getpwnam((char*) patbuf)) == NULL) return pattern; else h = pwd->pw_dir; } /* Copy the home directory */ for (b = patbuf; b < eb && *h; *b++ = *h++) continue; /* Append the rest of the pattern */ while (b < eb && (*b++ = *p++) != EOS) continue; *b = EOS; return patbuf; } /* * The main glob() routine: compiles the pattern (optionally processing * quotes), calls glob1() to do the real pattern matching, and finally * sorts the list (unless unsorted operation is requested). Returns 0 * if things went well, nonzero if errors occurred. */ static int glob0(const Char *pattern, glob_t *pglob, size_t *limit) { const Char *qpatnext; int err; size_t oldpathc; Char *bufnext, c, patbuf[MAXPATHLEN]; qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); oldpathc = pglob->gl_pathc; bufnext = patbuf; /* We don't need to check for buffer overflow any more. */ while ((c = *qpatnext++) != EOS) { switch (c) { case LBRACKET: c = *qpatnext; if (c == NOT) ++qpatnext; if (*qpatnext == EOS || g_strchr(qpatnext+1, RBRACKET) == NULL) { *bufnext++ = LBRACKET; if (c == NOT) --qpatnext; break; } *bufnext++ = M_SET; if (c == NOT) *bufnext++ = M_NOT; c = *qpatnext++; do { *bufnext++ = CHAR(c); if (*qpatnext == RANGE && (c = qpatnext[1]) != RBRACKET) { *bufnext++ = M_RNG; *bufnext++ = CHAR(c); qpatnext += 2; } } while ((c = *qpatnext++) != RBRACKET); pglob->gl_flags |= GLOB_MAGCHAR; *bufnext++ = M_END; break; case QUESTION: pglob->gl_flags |= GLOB_MAGCHAR; *bufnext++ = M_ONE; break; case STAR: pglob->gl_flags |= GLOB_MAGCHAR; /* collapse adjacent stars to one, * to avoid exponential behavior */ if (bufnext == patbuf || bufnext[-1] != M_ALL) *bufnext++ = M_ALL; break; default: *bufnext++ = CHAR(c); break; } } *bufnext = EOS; #ifdef DEBUG qprintf("glob0:", patbuf); #endif if ((err = glob1(patbuf, pglob, limit)) != 0) return(err); /* * If there was no match we are going to append the pattern * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified * and the pattern did not contain any magic characters * GLOB_NOMAGIC is there just for compatibility with csh. */ if (pglob->gl_pathc == oldpathc) { if (((pglob->gl_flags & GLOB_NOCHECK) || ((pglob->gl_flags & GLOB_NOMAGIC) && !(pglob->gl_flags & GLOB_MAGCHAR)))) return(globextend(pattern, pglob, limit)); else return(GLOB_NOMATCH); } if (!(pglob->gl_flags & GLOB_NOSORT)) qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc, pglob->gl_pathc - oldpathc, sizeof(char *), compare); return(0); } static int compare(const void *p, const void *q) { return(strcmp(*(char **)p, *(char **)q)); } static int glob1(Char *pattern, glob_t *pglob, size_t *limit) { Char pathbuf[MAXPATHLEN]; /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ if (*pattern == EOS) return(0); return(glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, pattern, pglob, limit)); } /* * The functions glob2 and glob3 are mutually recursive; there is one level * of recursion for each segment in the pattern that contains one or more * meta characters. */ static int glob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, glob_t *pglob, size_t *limit) { struct stat sb; Char *p, *q; int anymeta; /* * Loop over pattern segments until end of pattern or until * segment with meta character found. */ for (anymeta = 0;;) { if (*pattern == EOS) { /* End of pattern? */ *pathend = EOS; if (g_lstat(pathbuf, &sb, pglob)) return(0); if (((pglob->gl_flags & GLOB_MARK) && pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) || (S_ISLNK(sb.st_mode) && (g_stat(pathbuf, &sb, pglob) == 0) && S_ISDIR(sb.st_mode)))) { if (pathend + 1 > pathend_last) return (GLOB_ABORTED); *pathend++ = SEP; *pathend = EOS; } ++pglob->gl_matchc; return(globextend(pathbuf, pglob, limit)); } /* Find end of next segment, copy tentatively to pathend. */ q = pathend; p = pattern; while (*p != EOS && *p != SEP) { if (ismeta(*p)) anymeta = 1; if (q + 1 > pathend_last) return (GLOB_ABORTED); *q++ = *p++; } if (!anymeta) { /* No expansion, do next segment. */ pathend = q; pattern = p; while (*pattern == SEP) { if (pathend + 1 > pathend_last) return (GLOB_ABORTED); *pathend++ = *pattern++; } } else /* Need expansion, recurse. */ return(glob3(pathbuf, pathend, pathend_last, pattern, p, pglob, limit)); } /* NOTREACHED */ } static int glob3(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, Char *restpattern, glob_t *pglob, size_t *limit) { struct dirent *dp; DIR *dirp; int err; char buf[MAXPATHLEN]; /* * The readdirfunc declaration can't be prototyped, because it is * assigned, below, to two functions which are prototyped in glob.h * and dirent.h as taking pointers to differently typed opaque * structures. */ struct dirent *(*readdirfunc)(); if (pathend > pathend_last) return (GLOB_ABORTED); *pathend = EOS; errno = 0; if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { /* TODO: don't call for ENOENT or ENOTDIR? */ if (pglob->gl_errfunc) { if (g_Ctoc(pathbuf, buf, sizeof(buf))) return (GLOB_ABORTED); if (pglob->gl_errfunc(buf, errno) || pglob->gl_flags & GLOB_ERR) return (GLOB_ABORTED); } return(0); } err = 0; /* Search directory for matching names. */ if (pglob->gl_flags & GLOB_ALTDIRFUNC) readdirfunc = pglob->gl_readdir; else readdirfunc = readdir; while ((dp = (*readdirfunc)(dirp))) { char *sc; Char *dc; wchar_t wc; size_t clen; mbstate_t mbs; /* Initial DOT must be matched literally. */ if (dp->d_name[0] == DOT && *pattern != DOT) continue; memset(&mbs, 0, sizeof(mbs)); dc = pathend; sc = dp->d_name; while (dc < pathend_last) { clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) { wc = *sc; clen = 1; memset(&mbs, 0, sizeof(mbs)); } if ((*dc++ = wc) == EOS) break; sc += clen; } if (!match(pathend, pattern, restpattern)) { *pathend = EOS; continue; } err = glob2(pathbuf, --dc, pathend_last, restpattern, pglob, limit); if (err) break; } if (pglob->gl_flags & GLOB_ALTDIRFUNC) (*pglob->gl_closedir)(dirp); else closedir(dirp); return(err); } /* * Extend the gl_pathv member of a glob_t structure to accomodate a new item, * add the new item, and update gl_pathc. * * This assumes the BSD realloc, which only copies the block when its size * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic * behavior. * * Return 0 if new item added, error code if memory couldn't be allocated. * * Invariant of the glob_t structure: * Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and * gl_pathv points to (gl_offs + gl_pathc + 1) items. */ static int globextend(const Char *path, glob_t *pglob, size_t *limit) { char **pathv; size_t i, newsize, len; char *copy; const Char *p; if (*limit && pglob->gl_pathc > *limit) { errno = 0; return (GLOB_NOSPACE); } newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); pathv = pglob->gl_pathv ? realloc((char *)pglob->gl_pathv, newsize) : malloc(newsize); if (pathv == NULL) { if (pglob->gl_pathv) { free(pglob->gl_pathv); pglob->gl_pathv = NULL; } return(GLOB_NOSPACE); } if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) { /* first time around -- clear initial gl_offs items */ pathv += pglob->gl_offs; for (i = pglob->gl_offs + 1; --i > 0; ) *--pathv = NULL; } pglob->gl_pathv = pathv; for (p = path; *p++;) continue; len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ if ((copy = malloc(len)) != NULL) { if (g_Ctoc(path, copy, len)) { free(copy); return (GLOB_NOSPACE); } pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; } pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; return(copy == NULL ? GLOB_NOSPACE : 0); } /* * pattern matching function for filenames. Each occurrence of the * * pattern causes a recursion level. */ static int match(Char *name, Char *pat, Char *patend) { int ok, negate_range; Char c, k; while (pat < patend) { c = *pat++; switch (c & M_MASK) { case M_ALL: if (pat == patend) return(1); do if (match(name, pat, patend)) return(1); while (*name++ != EOS); return(0); case M_ONE: if (*name++ == EOS) return(0); break; case M_SET: ok = 0; if ((k = *name++) == EOS) return(0); if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) ++pat; while (((c = *pat++) & M_MASK) != M_END) if ((*pat & M_MASK) == M_RNG) { if (CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1])) ok = 1; pat += 2; } else if (c == k) ok = 1; if (ok == negate_range) return(0); break; default: if (*name++ != c) return(0); break; } } return(*name == EOS); } /* Free allocated data belonging to a glob_t structure. */ void globfree(glob_t *pglob) { size_t i; char **pp; if (pglob->gl_pathv != NULL) { pp = pglob->gl_pathv + pglob->gl_offs; for (i = pglob->gl_pathc; i--; ++pp) if (*pp) free(*pp); free(pglob->gl_pathv); pglob->gl_pathv = NULL; } } static DIR * g_opendir(Char *str, glob_t *pglob) { char buf[MAXPATHLEN]; if (!*str) strcpy(buf, "."); else { if (g_Ctoc(str, buf, sizeof(buf))) return (NULL); } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return((*pglob->gl_opendir)(buf)); return(opendir(buf)); } static int g_lstat(Char *fn, struct stat *sb, glob_t *pglob) { char buf[MAXPATHLEN]; if (g_Ctoc(fn, buf, sizeof(buf))) { errno = ENAMETOOLONG; return (-1); } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return((*pglob->gl_lstat)(buf, sb)); return(lstat(buf, sb)); } static int g_stat(Char *fn, struct stat *sb, glob_t *pglob) { char buf[MAXPATHLEN]; if (g_Ctoc(fn, buf, sizeof(buf))) { errno = ENAMETOOLONG; return (-1); } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return((*pglob->gl_stat)(buf, sb)); return(stat(buf, sb)); } static const Char * g_strchr(const Char *str, wchar_t ch) { do { if (*str == ch) return (str); } while (*str++); return (NULL); } static int g_Ctoc(const Char *str, char *buf, size_t len) { mbstate_t mbs; size_t clen; memset(&mbs, 0, sizeof(mbs)); while (len >= MB_CUR_MAX) { clen = wcrtomb(buf, *str, &mbs); if (clen == (size_t)-1) return (1); if (*str == L'\0') return (0); str++; buf += clen; len -= clen; } return (1); } #ifdef DEBUG static void qprintf(const char *str, Char *s) { Char *p; (void)printf("%s:\n", str); for (p = s; *p; p++) (void)printf("%c", CHAR(*p)); (void)printf("\n"); for (p = s; *p; p++) (void)printf("%c", *p & M_PROTECT ? '"' : ' '); (void)printf("\n"); for (p = s; *p; p++) (void)printf("%c", ismeta(*p) ? '_' : ' '); (void)printf("\n"); } #endif ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/glob/glob.h�����������������������������������������������������������������0000664�0000000�0000000�00000007561�15162662266�0017030�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Guido van Rossum. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)glob.h 8.1 (Berkeley) 6/2/93 * $FreeBSD$ */ #ifndef _GLOB_H_ #define _GLOB_H_ #include <sys/cdefs.h> #include <sys/types.h> struct stat; typedef struct { size_t gl_pathc; /* Count of total paths so far. */ size_t gl_matchc; /* Count of paths matching pattern. */ size_t gl_offs; /* Reserved at beginning of gl_pathv. */ int gl_flags; /* Copy of flags parameter to glob. */ char **gl_pathv; /* List of paths matching pattern. */ /* Copy of errfunc parameter to glob. */ int (*gl_errfunc)(const char *, int); /* * Alternate filesystem access methods for glob; replacement * versions of closedir(3), readdir(3), opendir(3), stat(2) * and lstat(2). */ void (*gl_closedir)(void *); struct dirent *(*gl_readdir)(void *); void *(*gl_opendir)(const char *); int (*gl_lstat)(const char *, struct stat *); int (*gl_stat)(const char *, struct stat *); } glob_t; #define GLOB_APPEND 0x0001 /* Append to output from previous call. */ #define GLOB_DOOFFS 0x0002 /* Use gl_offs. */ #define GLOB_ERR 0x0004 /* Return on error. */ #define GLOB_MARK 0x0008 /* Append / to matching directories. */ #define GLOB_NOCHECK 0x0010 /* Return pattern itself if nothing matches. */ #define GLOB_NOSORT 0x0020 /* Don't sort. */ #define GLOB_NOESCAPE 0x2000 /* Disable backslash escaping. */ /* Error values returned by glob(3) */ #define GLOB_NOSPACE (-1) /* Malloc call failed. */ #define GLOB_ABORTED (-2) /* Unignored error. */ #define GLOB_NOMATCH (-3) /* No match and GLOB_NOCHECK was not set. */ #define GLOB_NOSYS (-4) /* Obsolete: source comptability only. */ #define GLOB_ALTDIRFUNC 0x0040 /* Use alternately specified directory funcs. */ #define GLOB_BRACE 0x0080 /* Expand braces ala csh. */ #define GLOB_MAGCHAR 0x0100 /* Pattern had globbing characters. */ #define GLOB_NOMAGIC 0x0200 /* GLOB_NOCHECK without magic chars (csh). */ #define GLOB_QUOTE 0x0400 /* Quote special chars with \. */ #define GLOB_TILDE 0x0800 /* Expand tilde names from the passwd file. */ /* source compatibility, these are the old names */ #define GLOB_MAXPATH GLOB_LIMIT #define GLOB_ABEND GLOB_ABORTED __BEGIN_DECLS int glob(const char *, int, int (*)(const char *, int), glob_t *); void globfree(glob_t *); __END_DECLS #endif /* !_GLOB_H_ */ �����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/http_parser/����������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017333�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/http_parser/AUTHORS���������������������������������������������������������0000664�0000000�0000000�00000004706�15162662266�0020412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Authors ordered by first contribution. Ryan Dahl <ry@tinyclouds.org> Jeremy Hinegardner <jeremy@hinegardner.org> Sergey Shepelev <temotor@gmail.com> Joe Damato <ice799@gmail.com> tomika <tomika_nospam@freemail.hu> Phoenix Sol <phoenix@burninglabs.com> Cliff Frey <cliff@meraki.com> Ewen Cheslack-Postava <ewencp@cs.stanford.edu> Santiago Gala <sgala@apache.org> Tim Becker <tim.becker@syngenio.de> Jeff Terrace <jterrace@gmail.com> Ben Noordhuis <info@bnoordhuis.nl> Nathan Rajlich <nathan@tootallnate.net> Mark Nottingham <mnot@mnot.net> Aman Gupta <aman@tmm1.net> Tim Becker <tim.becker@kuriositaet.de> Sean Cunningham <sean.cunningham@mandiant.com> Peter Griess <pg@std.in> Salman Haq <salman.haq@asti-usa.com> Cliff Frey <clifffrey@gmail.com> Jon Kolb <jon@b0g.us> Fouad Mardini <f.mardini@gmail.com> Paul Querna <pquerna@apache.org> Felix Geisendörfer <felix@debuggable.com> koichik <koichik@improvement.jp> Andre Caron <andre.l.caron@gmail.com> Ivo Raisr <ivosh@ivosh.net> James McLaughlin <jamie@lacewing-project.org> David Gwynne <loki@animata.net> Thomas LE ROUX <thomas@november-eleven.fr> Randy Rizun <rrizun@ortivawireless.com> Andre Louis Caron <andre.louis.caron@usherbrooke.ca> Simon Zimmermann <simonz05@gmail.com> Erik Dubbelboer <erik@dubbelboer.com> Martell Malone <martellmalone@gmail.com> Bertrand Paquet <bpaquet@octo.com> BogDan Vatra <bogdan@kde.org> Peter Faiman <peter@thepicard.org> Corey Richardson <corey@octayn.net> Tóth Tamás <tomika_nospam@freemail.hu> Cam Swords <cam.swords@gmail.com> Chris Dickinson <christopher.s.dickinson@gmail.com> Uli Köhler <ukoehler@btronik.de> Charlie Somerville <charlie@charliesomerville.com> Patrik Stutz <patrik.stutz@gmail.com> Fedor Indutny <fedor.indutny@gmail.com> runner <runner.mei@gmail.com> Alexis Campailla <alexis@janeasystems.com> David Wragg <david@wragg.org> Vinnie Falco <vinnie.falco@gmail.com> Alex Butum <alexbutum@linux.com> Rex Feng <rexfeng@gmail.com> Alex Kocharin <alex@kocharin.ru> Mark Koopman <markmontymark@yahoo.com> Helge Heß <me@helgehess.eu> Alexis La Goutte <alexis.lagoutte@gmail.com> George Miroshnykov <george.miroshnykov@gmail.com> Maciej Małecki <me@mmalecki.com> Marc O'Morain <github.com@marcomorain.com> Jeff Pinner <jpinner@twitter.com> Timothy J Fontaine <tjfontaine@gmail.com> Akagi201 <akagi201@gmail.com> Romain Giraud <giraud.romain@gmail.com> Jay Satiro <raysatiro@yahoo.com> Arne Steen <Arne.Steen@gmx.de> Kjell Schubert <kjell.schubert@gmail.com> Olivier Mengué <dolmen@cpan.org> ����������������������������������������������������������sdk-10.11.0/third_party/http_parser/CMakeLists.txt��������������������������������������������������0000664�0000000�0000000�00000001374�15162662266�0022100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(http_parser OBJECT http_parser.h http_parser.cpp ) set(HTTP_PARSER_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/http_parser") file(MAKE_DIRECTORY "${HTTP_PARSER_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/http_parser.h" "${HTTP_PARSER_PUBLIC_INCLUDE_DIR}/http_parser.h" COPYONLY ) target_include_directories(http_parser SYSTEM PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(ENABLE_JAVA_BINDINGS) set_target_properties(http_parser PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() if(APPLE AND ENABLE_SDKLIB_WERROR) set_source_files_properties( http_parser.cpp PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion" ) endif() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/http_parser/LICENSE-MIT�����������������������������������������������������0000664�0000000�0000000�00000002343�15162662266�0020771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev. Additional changes are licensed under the same terms as NGINX and copyright Joyent, Inc. and other Node contributors. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/http_parser/http_parser.cpp�������������������������������������������������0000664�0000000�0000000�00000217421�15162662266�0022401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev * * Additional changes are licensed under the same terms as NGINX and * copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include <assert.h> #include <stddef.h> #include <ctype.h> #include <stdlib.h> #include <string.h> #include <limits.h> #include "http_parser.h" #ifdef __cplusplus extern "C" { #endif #ifndef ULLONG_MAX # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ #endif #ifndef MIN # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef ARRAY_SIZE # define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #endif #ifndef BIT_AT # define BIT_AT(a, i) \ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ (1 << ((unsigned int) (i) & 7)))) #endif #ifndef ELEM_AT # define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) #endif #define SET_ERRNO(e) \ do { \ parser->http_errno = (e); \ } while(0) #define CURRENT_STATE() p_state #define UPDATE_STATE(V) p_state = (enum state) (V); #define RETURN(V) \ do { \ parser->state = CURRENT_STATE(); \ return (V); \ } while (0); #define REEXECUTE() \ goto reexecute; \ #ifdef __GNUC__ # define LIKELY(X) __builtin_expect(!!(X), 1) # define UNLIKELY(X) __builtin_expect(!!(X), 0) #else # define LIKELY(X) (X) # define UNLIKELY(X) (X) #endif /* Run the notify callback FOR, returning ER if it fails */ #define CALLBACK_NOTIFY_(FOR, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ } while (0) /* Run the notify callback FOR and consume the current byte */ #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) /* Run the notify callback FOR and don't consume the current byte */ #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) /* Run data callback FOR with LEN bytes, returning ER if it fails */ #define CALLBACK_DATA_(FOR, LEN, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (FOR##_mark) { \ if (LIKELY(settings->on_##FOR)) { \ parser->state = CURRENT_STATE(); \ if (UNLIKELY(0 != \ settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ FOR##_mark = NULL; \ } \ } while (0) /* Run the data callback FOR and consume the current byte */ #define CALLBACK_DATA(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) /* Run the data callback FOR and don't consume the current byte */ #define CALLBACK_DATA_NOADVANCE(FOR) \ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) /* Set the mark FOR; non-destructive if mark is already set */ #define MARK(FOR) \ do { \ if (!FOR##_mark) { \ FOR##_mark = p; \ } \ } while (0) /* Don't allow the total size of the HTTP headers (including the status * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect * embedders against denial-of-service attacks where the attacker feeds * us a never-ending header that the embedder keeps buffering. * * This check is arguably the responsibility of embedders but we're doing * it on the embedder's behalf because most won't bother and this way we * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger * than any reasonable request or response so this should never affect * day-to-day operation. */ #define COUNT_HEADER_SIZE(V) \ do { \ parser->nread += (V); \ if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ SET_ERRNO(HPE_HEADER_OVERFLOW); \ goto error; \ } \ } while (0) #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" #define CONTENT_LENGTH "content-length" #define TRANSFER_ENCODING "transfer-encoding" #define UPGRADE "upgrade" #define CHUNKED "chunked" #define KEEP_ALIVE "keep-alive" #define CLOSE "close" static const char *method_strings[] = { #define XX(num, name, string) #string, HTTP_METHOD_MAP(XX) #undef XX }; /* Tokens as defined by rfc 2616. Also lowercases them. * token = 1*<any CHAR except CTLs or separators> * separators = "(" | ")" | "<" | ">" | "@" * | "," | ";" | ":" | "\" | <"> * | "/" | "[" | "]" | "?" | "=" * | "{" | "}" | SP | HT */ static const char tokens[256] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0, 0, 0, 0, 0, 0, 0, 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0, 0, 0, 0, 0, 0, 0, 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0, 0, 0, 0, 0, 0, 0, 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 0, '!', 0, '#', '$', '%', '&', '\'', /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 0, 0, '*', '+', 0, '-', '.', 0, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ '0', '1', '2', '3', '4', '5', '6', '7', /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ '8', '9', 0, 0, 0, 0, 0, 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 'x', 'y', 'z', 0, 0, 0, '^', '_', /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 'x', 'y', 'z', 0, '|', 0, '~', 0 }; static const int8_t unhex[256] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 }; #if HTTP_PARSER_STRICT # define T(v) 0 #else # define T(v) v #endif static const uint8_t normal_url_char[32] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; #undef T enum state : uint8_t { s_dead = 1 /* important that this is > 0 */ , s_start_req_or_res , s_res_or_resp_H , s_start_res , s_res_H , s_res_HT , s_res_HTT , s_res_HTTP , s_res_first_http_major , s_res_http_major , s_res_first_http_minor , s_res_http_minor , s_res_first_status_code , s_res_status_code , s_res_status_start , s_res_status , s_res_line_almost_done , s_start_req , s_req_method , s_req_spaces_before_url , s_req_schema , s_req_schema_slash , s_req_schema_slash_slash , s_req_server_start , s_req_server , s_req_server_with_at , s_req_path , s_req_query_string_start , s_req_query_string , s_req_fragment_start , s_req_fragment , s_req_http_start , s_req_http_H , s_req_http_HT , s_req_http_HTT , s_req_http_HTTP , s_req_first_http_major , s_req_http_major , s_req_first_http_minor , s_req_http_minor , s_req_line_almost_done , s_header_field_start , s_header_field , s_header_value_discard_ws , s_header_value_discard_ws_almost_done , s_header_value_discard_lws , s_header_value_start , s_header_value , s_header_value_lws , s_header_almost_done , s_chunk_size_start , s_chunk_size , s_chunk_parameters , s_chunk_size_almost_done , s_headers_almost_done , s_headers_done /* Important: 's_headers_done' must be the last 'header' state. All * states beyond this must be 'body' states. It is used for overflow * checking. See the PARSING_HEADER() macro. */ , s_chunk_data , s_chunk_data_almost_done , s_chunk_data_done , s_body_identity , s_body_identity_eof , s_message_done }; #define PARSING_HEADER(state) (state <= s_headers_done) enum header_states : uint8_t { h_general = 0 , h_C , h_CO , h_CON , h_matching_connection , h_matching_proxy_connection , h_matching_content_length , h_matching_transfer_encoding , h_matching_upgrade , h_connection , h_content_length , h_transfer_encoding , h_upgrade , h_matching_transfer_encoding_chunked , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close , h_matching_connection_upgrade , h_matching_connection_token , h_transfer_encoding_chunked , h_connection_keep_alive , h_connection_close , h_connection_upgrade }; enum http_host_state : uint8_t { s_http_host_dead = 1 , s_http_userinfo_start , s_http_userinfo , s_http_host_start , s_http_host_v6_start , s_http_host , s_http_host_v6 , s_http_host_v6_end , s_http_host_v6_zone_start , s_http_host_v6_zone , s_http_host_port_start , s_http_host_port }; /* Macros for character classes; depends on strict-mode */ #define CR '\r' #define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ (c) == ')') #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') #define STRICT_TOKEN(c) (tokens[(unsigned char)c]) #if HTTP_PARSER_STRICT #define TOKEN(c) (tokens[(unsigned char)c]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) #define IS_URL_CHAR(c) \ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) #if HTTP_PARSER_STRICT # define STRICT_CHECK(cond) \ do { \ if (cond) { \ SET_ERRNO(HPE_STRICT); \ goto error; \ } \ } while (0) # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) #else # define STRICT_CHECK(cond) # define NEW_MESSAGE() start_state #endif /* Map errno values to strings for human-readable output */ #define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, static struct { const char *name; const char *description; } http_strerror_tab[] = { HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) }; #undef HTTP_STRERROR_GEN int http_message_needs_eof(const http_parser *parser); /* Our URL parser. * * This is designed to be shared by http_parser_execute() for URL validation, * hence it has a state transition + byte-for-byte interface. In addition, it * is meant to be embedded in http_parser_parse_url(), which does the dirty * work of turning state transitions URL components for its API. * * This function should only be invoked with non-space characters. It is * assumed that the caller cares about (and can detect) the transition between * URL and non-URL states by looking for these. */ static enum state parse_url_char(enum state s, const char ch) { if (ch == ' ' || ch == '\r' || ch == '\n') { return s_dead; } #if HTTP_PARSER_STRICT if (ch == '\t' || ch == '\f') { return s_dead; } #endif switch (s) { case s_req_spaces_before_url: /* Proxied requests are followed by scheme of an absolute URI (alpha). * All methods except CONNECT are followed by '/' or '*'. */ if (ch == '/' || ch == '*') { return s_req_path; } if (IS_ALPHA(ch)) { return s_req_schema; } break; case s_req_schema: if (IS_ALPHA(ch)) { return s; } if (ch == ':') { return s_req_schema_slash; } break; case s_req_schema_slash: if (ch == '/') { return s_req_schema_slash_slash; } break; case s_req_schema_slash_slash: if (ch == '/') { return s_req_server_start; } break; case s_req_server_with_at: if (ch == '@') { return s_dead; } /* FALLTHROUGH */ case s_req_server_start: case s_req_server: if (ch == '/') { return s_req_path; } if (ch == '?') { return s_req_query_string_start; } if (ch == '@') { return s_req_server_with_at; } if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { return s_req_server; } break; case s_req_path: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': return s_req_query_string_start; case '#': return s_req_fragment_start; } break; case s_req_query_string_start: case s_req_query_string: if (IS_URL_CHAR(ch)) { return s_req_query_string; } switch (ch) { case '?': /* allow extra '?' in query string */ return s_req_query_string; case '#': return s_req_fragment_start; } break; case s_req_fragment_start: if (IS_URL_CHAR(ch)) { return s_req_fragment; } switch (ch) { case '?': return s_req_fragment; case '#': return s; } break; case s_req_fragment: if (IS_URL_CHAR(ch)) { return s; } switch (ch) { case '?': case '#': return s; } break; default: break; } /* We should never fall out of the switch above unless there's an error */ return s_dead; } size_t http_parser_execute (http_parser *parser, const http_parser_settings *settings, const char *data, size_t len) { char c, ch; int8_t unhex_val; const char *p = data; const char *header_field_mark = 0; const char *header_value_mark = 0; const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; enum state p_state = (enum state) parser->state; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { return 0; } if (len == 0) { switch (CURRENT_STATE()) { case s_body_identity_eof: /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if * we got paused. */ CALLBACK_NOTIFY_NOADVANCE(message_complete); return 0; case s_dead: case s_start_req_or_res: case s_start_res: case s_start_req: return 0; default: SET_ERRNO(HPE_INVALID_EOF_STATE); return 1; } } if (CURRENT_STATE() == s_header_field) header_field_mark = data; if (CURRENT_STATE() == s_header_value) header_value_mark = data; switch (CURRENT_STATE()) { case s_req_path: case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_server: case s_req_server_with_at: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: url_mark = data; break; case s_res_status: status_mark = data; break; default: break; } for (p=data; p != data + len; p++) { ch = *p; if (PARSING_HEADER(CURRENT_STATE())) COUNT_HEADER_SIZE(1); reexecute: switch (CURRENT_STATE()) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ if (LIKELY(ch == CR || ch == LF)) break; SET_ERRNO(HPE_CLOSED_CONNECTION); goto error; case s_start_req_or_res: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->content_length = ULLONG_MAX; if (ch == 'H') { UPDATE_STATE(s_res_or_resp_H); CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; UPDATE_STATE(s_start_req); REEXECUTE(); } break; } case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; UPDATE_STATE(s_res_HT); } else { if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; UPDATE_STATE(s_req_method); } break; case s_start_res: { parser->flags = 0; parser->content_length = ULLONG_MAX; switch (ch) { case 'H': UPDATE_STATE(s_res_H); break; case CR: case LF: break; default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } CALLBACK_NOTIFY(message_begin); break; } case s_res_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HT); break; case s_res_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_res_HTT); break; case s_res_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_res_HTTP); break; case s_res_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_res_first_http_major); break; case s_res_first_http_major: if (UNLIKELY(ch < '0' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = static_cast<short unsigned>(ch - '0'); UPDATE_STATE(s_res_http_major); break; /* major HTTP version or dot */ case s_res_http_major: { if (ch == '.') { UPDATE_STATE(s_res_first_http_minor); break; } if (!IS_NUM(ch)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = static_cast<short unsigned>(10 * parser->http_major); parser->http_major = static_cast<short unsigned>(parser->http_major + (ch - '0')); if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* first digit of minor HTTP version */ case s_res_first_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = static_cast<short unsigned>(ch - '0'); UPDATE_STATE(s_res_http_minor); break; /* minor HTTP version or end of request line */ case s_res_http_minor: { if (ch == ' ') { UPDATE_STATE(s_res_first_status_code); break; } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = static_cast<short unsigned>(10 * parser->http_minor); parser->http_minor = static_cast<short unsigned>(parser->http_minor + (ch - '0')); if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } case s_res_first_status_code: { if (!IS_NUM(ch)) { if (ch == ' ') { break; } SET_ERRNO(HPE_INVALID_STATUS); goto error; } parser->status_code = static_cast<short unsigned>(ch - '0'); UPDATE_STATE(s_res_status_code); break; } case s_res_status_code: { if (!IS_NUM(ch)) { switch (ch) { case ' ': UPDATE_STATE(s_res_status_start); break; case CR: UPDATE_STATE(s_res_line_almost_done); break; case LF: UPDATE_STATE(s_header_field_start); break; default: SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } parser->status_code = static_cast<short unsigned>(10 * parser->status_code); parser->status_code = static_cast<short unsigned>(parser->status_code + (ch - '0')); if (UNLIKELY(parser->status_code > 999)) { SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; } case s_res_status_start: { if (ch == CR) { UPDATE_STATE(s_res_line_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); break; } MARK(status); UPDATE_STATE(s_res_status); parser->index = 0; break; } case s_res_status: if (ch == CR) { UPDATE_STATE(s_res_line_almost_done); CALLBACK_DATA(status); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); CALLBACK_DATA(status); break; } break; case s_res_line_almost_done: STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_field_start); break; case s_start_req: { if (ch == CR || ch == LF) break; parser->flags = 0; parser->content_length = ULLONG_MAX; if (UNLIKELY(!IS_ALPHA(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } parser->method = (enum http_method) 0; parser->index = 1; switch (ch) { case 'A': parser->method = HTTP_ACL; break; case 'B': parser->method = HTTP_BIND; break; case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; case 'G': parser->method = HTTP_GET; break; case 'H': parser->method = HTTP_HEAD; break; case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } UPDATE_STATE(s_req_method); CALLBACK_NOTIFY(message_begin); break; } case s_req_method: { const char *matcher; if (UNLIKELY(ch == '\0')) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; if (ch == ' ' && matcher[parser->index] == '\0') { UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (parser->method == HTTP_CONNECT) { if (parser->index == 1 && ch == 'H') { parser->method = HTTP_CHECKOUT; } else if (parser->index == 2 && ch == 'P') { parser->method = HTTP_COPY; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_MKCOL) { if (parser->index == 1 && ch == 'O') { parser->method = HTTP_MOVE; } else if (parser->index == 1 && ch == 'E') { parser->method = HTTP_MERGE; } else if (parser->index == 1 && ch == '-') { parser->method = HTTP_MSEARCH; } else if (parser->index == 2 && ch == 'A') { parser->method = HTTP_MKACTIVITY; } else if (parser->index == 3 && ch == 'A') { parser->method = HTTP_MKCALENDAR; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_SUBSCRIBE) { if (parser->index == 1 && ch == 'E') { parser->method = HTTP_SEARCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_REPORT) { if (parser->index == 2 && ch == 'B') { parser->method = HTTP_REBIND; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->index == 1) { if (parser->method == HTTP_POST) { if (ch == 'R') { parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ } else if (ch == 'U') { parser->method = HTTP_PUT; /* or HTTP_PURGE */ } else if (ch == 'A') { parser->method = HTTP_PATCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_LOCK) { if (ch == 'I') { parser->method = HTTP_LINK; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } } else if (parser->index == 2) { if (parser->method == HTTP_PUT) { if (ch == 'R') { parser->method = HTTP_PURGE; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->method == HTTP_UNLOCK) { if (ch == 'S') { parser->method = HTTP_UNSUBSCRIBE; } else if(ch == 'B') { parser->method = HTTP_UNBIND; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { parser->method = HTTP_PROPPATCH; } else if (parser->index == 3 && parser->method == HTTP_UNLOCK && ch == 'I') { parser->method = HTTP_UNLINK; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } ++parser->index; break; } case s_req_spaces_before_url: { if (ch == ' ') break; MARK(url); if (parser->method == HTTP_CONNECT) { UPDATE_STATE(s_req_server_start); } UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } break; } case s_req_schema: case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: { switch (ch) { /* No whitespace allowed here */ case ' ': case CR: case LF: SET_ERRNO(HPE_INVALID_URL); goto error; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_server: case s_req_server_with_at: case s_req_path: case s_req_query_string_start: case s_req_query_string: case s_req_fragment_start: case s_req_fragment: { switch (ch) { case ' ': UPDATE_STATE(s_req_http_start); CALLBACK_DATA(url); break; case CR: case LF: parser->http_major = 0; parser->http_minor = 9; UPDATE_STATE((ch == CR) ? s_req_line_almost_done : s_header_field_start); CALLBACK_DATA(url); break; default: UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } } break; } case s_req_http_start: switch (ch) { case 'H': UPDATE_STATE(s_req_http_H); break; case ' ': break; default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } break; case s_req_http_H: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HT); break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); UPDATE_STATE(s_req_http_HTT); break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); UPDATE_STATE(s_req_http_HTTP); break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); UPDATE_STATE(s_req_first_http_major); break; /* first digit of major HTTP version */ case s_req_first_http_major: if (UNLIKELY(ch < '1' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = static_cast<short unsigned>(ch - '0'); UPDATE_STATE(s_req_http_major); break; /* major HTTP version or dot */ case s_req_http_major: { if (ch == '.') { UPDATE_STATE(s_req_first_http_minor); break; } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = static_cast<short unsigned>(10 * parser->http_major); parser->http_major = static_cast<short unsigned>(parser->http_major + (ch - '0')); if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* first digit of minor HTTP version */ case s_req_first_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = static_cast<short unsigned>(ch - '0'); UPDATE_STATE(s_req_http_minor); break; /* minor HTTP version or end of request line */ case s_req_http_minor: { if (ch == CR) { UPDATE_STATE(s_req_line_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_field_start); break; } /* XXX allow spaces after digit? */ if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = static_cast<short unsigned>(10 * parser->http_minor); parser->http_minor = static_cast<short unsigned>(parser->http_minor + (ch - '0')); if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } break; } /* end of request line */ case s_req_line_almost_done: { if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } UPDATE_STATE(s_header_field_start); break; } case s_header_field_start: { if (ch == CR) { UPDATE_STATE(s_headers_almost_done); break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ UPDATE_STATE(s_headers_almost_done); REEXECUTE(); } c = TOKEN(ch); if (UNLIKELY(!c)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } MARK(header_field); parser->index = 0; UPDATE_STATE(s_header_field); switch (c) { case 'c': parser->header_state = h_C; break; case 'p': parser->header_state = h_matching_proxy_connection; break; case 't': parser->header_state = h_matching_transfer_encoding; break; case 'u': parser->header_state = h_matching_upgrade; break; default: parser->header_state = h_general; break; } break; } case s_header_field: { const char* start = p; for (; p != data + len; p++) { ch = *p; c = TOKEN(ch); if (!c) break; switch (parser->header_state) { case h_general: break; case h_C: parser->index++; parser->header_state = (c == 'o' ? h_CO : h_general); break; case h_CO: parser->index++; parser->header_state = (c == 'n' ? h_CON : h_general); break; case h_CON: parser->index++; switch (c) { case 'n': parser->header_state = h_matching_connection; break; case 't': parser->header_state = h_matching_content_length; break; default: parser->header_state = h_general; break; } break; /* connection */ case h_matching_connection: parser->index++; if (parser->index > sizeof(CONNECTION)-1 || c != CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONNECTION)-2) { parser->header_state = h_connection; } break; /* proxy-connection */ case h_matching_proxy_connection: parser->index++; if (parser->index > sizeof(PROXY_CONNECTION)-1 || c != PROXY_CONNECTION[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { parser->header_state = h_connection; } break; /* content-length */ case h_matching_content_length: parser->index++; if (parser->index > sizeof(CONTENT_LENGTH)-1 || c != CONTENT_LENGTH[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { parser->header_state = h_content_length; } break; /* transfer-encoding */ case h_matching_transfer_encoding: parser->index++; if (parser->index > sizeof(TRANSFER_ENCODING)-1 || c != TRANSFER_ENCODING[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { parser->header_state = h_transfer_encoding; /* Multiple `Transfer-Encoding` headers should be treated as * one, but with values separate by a comma. * * See: https://tools.ietf.org/html/rfc7230#section-3.2.2 */ parser->flags &= static_cast<unsigned char>(~F_CHUNKED & 0x7F); // flags is 7 bits, apply mask to avoid data type conversion warning } break; /* upgrade */ case h_matching_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE)-1 || c != UPGRADE[parser->index]) { parser->header_state = h_general; } else if (parser->index == sizeof(UPGRADE)-2) { parser->header_state = h_upgrade; } break; case h_connection: case h_content_length: case h_transfer_encoding: case h_upgrade: if (ch != ' ') parser->header_state = h_general; break; default: assert(0 && "Unknown header_state"); break; } } COUNT_HEADER_SIZE(static_cast<unsigned>(p - start)); if (p == data + len) { --p; break; } if (ch == ':') { UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); break; } SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } case s_header_value_discard_ws: if (ch == ' ' || ch == '\t') break; if (ch == CR) { UPDATE_STATE(s_header_value_discard_ws_almost_done); break; } if (ch == LF) { UPDATE_STATE(s_header_value_discard_lws); break; } /* FALLTHROUGH */ case s_header_value_start: { MARK(header_value); UPDATE_STATE(s_header_value); parser->index = 0; c = LOWER(ch); switch (parser->header_state) { case h_upgrade: parser->flags |= F_UPGRADE; parser->header_state = h_general; break; case h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { parser->header_state = h_matching_transfer_encoding_chunked; } else { parser->header_state = h_general; } break; case h_content_length: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } parser->content_length = ch - '0'; break; case h_connection: /* looking for 'Connection: keep-alive' */ if (c == 'k') { parser->header_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { parser->header_state = h_matching_connection_close; } else if (c == 'u') { parser->header_state = h_matching_connection_upgrade; } else { parser->header_state = h_matching_connection_token; } break; /* Multi-value `Connection` header */ case h_matching_connection_token_start: break; default: parser->header_state = h_general; break; } break; } case s_header_value: { const char* start = p; enum header_states h_state = (enum header_states) parser->header_state; for (; p != data + len; p++) { ch = *p; if (ch == CR) { UPDATE_STATE(s_header_almost_done); parser->header_state = h_state; CALLBACK_DATA(header_value); break; } if (ch == LF) { UPDATE_STATE(s_header_almost_done); COUNT_HEADER_SIZE(static_cast<unsigned>(p - start)); parser->header_state = h_state; CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } c = LOWER(ch); switch (h_state) { case h_general: { const char* p_cr; const char* p_lf; size_t limit = data + len - p; limit = MIN(limit, HTTP_MAX_HEADER_SIZE); p_cr = (const char*) memchr(p, CR, limit); p_lf = (const char*) memchr(p, LF, limit); if (p_cr != NULL) { if (p_lf != NULL && p_cr >= p_lf) p = p_lf; else p = p_cr; } else if (UNLIKELY(p_lf != NULL)) { p = p_lf; } else { p = data + len; } --p; break; } case h_connection: case h_transfer_encoding: assert(0 && "Shouldn't get here."); break; case h_content_length: { uint64_t t; if (ch == ' ') break; if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } t = parser->content_length; t *= 10; t += ch - '0'; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); parser->header_state = h_state; goto error; } parser->content_length = t; break; } /* Transfer-Encoding: chunked */ case h_matching_transfer_encoding_chunked: parser->index++; if (parser->index > sizeof(CHUNKED)-1 || c != CHUNKED[parser->index]) { h_state = h_general; } else if (parser->index == sizeof(CHUNKED)-2) { h_state = h_transfer_encoding_chunked; } break; case h_matching_connection_token_start: /* looking for 'Connection: keep-alive' */ if (c == 'k') { h_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { h_state = h_matching_connection_close; } else if (c == 'u') { h_state = h_matching_connection_upgrade; } else if (STRICT_TOKEN(c)) { h_state = h_matching_connection_token; } else if (c == ' ' || c == '\t') { /* Skip lws */ } else { h_state = h_general; } break; /* looking for 'Connection: keep-alive' */ case h_matching_connection_keep_alive: parser->index++; if (parser->index > sizeof(KEEP_ALIVE)-1 || c != KEEP_ALIVE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(KEEP_ALIVE)-2) { h_state = h_connection_keep_alive; } break; /* looking for 'Connection: close' */ case h_matching_connection_close: parser->index++; if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(CLOSE)-2) { h_state = h_connection_close; } break; /* looking for 'Connection: upgrade' */ case h_matching_connection_upgrade: parser->index++; if (parser->index > sizeof(UPGRADE) - 1 || c != UPGRADE[parser->index]) { h_state = h_matching_connection_token; } else if (parser->index == sizeof(UPGRADE)-2) { h_state = h_connection_upgrade; } break; case h_matching_connection_token: if (ch == ',') { h_state = h_matching_connection_token_start; parser->index = 0; } break; case h_transfer_encoding_chunked: if (ch != ' ') h_state = h_general; break; case h_connection_keep_alive: case h_connection_close: case h_connection_upgrade: if (ch == ',') { if (h_state == h_connection_keep_alive) { parser->flags |= F_CONNECTION_KEEP_ALIVE; } else if (h_state == h_connection_close) { parser->flags |= F_CONNECTION_CLOSE; } else if (h_state == h_connection_upgrade) { parser->flags |= F_CONNECTION_UPGRADE; } h_state = h_matching_connection_token_start; parser->index = 0; } else if (ch != ' ') { h_state = h_matching_connection_token; } break; default: UPDATE_STATE(s_header_value); h_state = h_general; break; } } parser->header_state = h_state; COUNT_HEADER_SIZE(static_cast<unsigned>(p - start)); if (p == data + len) --p; break; } case s_header_almost_done: { STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_value_lws); break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') { UPDATE_STATE(s_header_value_start); REEXECUTE(); } /* finished the header */ switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; default: break; } UPDATE_STATE(s_header_field_start); REEXECUTE(); } case s_header_value_discard_ws_almost_done: { STRICT_CHECK(ch != LF); UPDATE_STATE(s_header_value_discard_lws); break; } case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { UPDATE_STATE(s_header_value_discard_ws); break; } else { switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; case h_connection_close: parser->flags |= F_CONNECTION_CLOSE; break; case h_connection_upgrade: parser->flags |= F_CONNECTION_UPGRADE; break; case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; default: break; } /* header value was empty */ MARK(header_value); UPDATE_STATE(s_header_field_start); CALLBACK_DATA_NOADVANCE(header_value); REEXECUTE(); } } case s_headers_almost_done: { STRICT_CHECK(ch != LF); if (parser->flags & F_TRAILING) { /* End of a chunked request */ UPDATE_STATE(s_message_done); CALLBACK_NOTIFY_NOADVANCE(chunk_complete); REEXECUTE(); } UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ parser->upgrade = ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == (F_UPGRADE | F_CONNECTION_UPGRADE) || parser->method == HTTP_CONNECT); /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we * will interpret that as saying that this message has no body. This * is needed for the annoying case of recieving a response to a HEAD * request. * * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so * we have to simulate it by handling a change in errno below. */ if (settings->on_headers_complete) { switch (settings->on_headers_complete(parser)) { case 0: break; case 1: parser->flags |= F_SKIPBODY; break; default: SET_ERRNO(HPE_CB_headers_complete); RETURN(p - data); /* Error */ } } if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { RETURN(p - data); } REEXECUTE(); } case s_headers_done: { int hasBody; STRICT_CHECK(ch != LF); parser->nread = 0; hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX); if (parser->upgrade && (parser->method == HTTP_CONNECT || (parser->flags & F_SKIPBODY) || !hasBody)) { /* Exit, the rest of the message is in a different protocol. */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); RETURN((p - data) + 1); } if (parser->flags & F_SKIPBODY) { UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ UPDATE_STATE(s_chunk_size_start); } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ UPDATE_STATE(s_body_identity); } else { if (!http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ UPDATE_STATE(s_body_identity_eof); } } } break; } case s_body_identity: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* The difference between advancing content_length and p is because * the latter will automaticaly advance on the next loop iteration. * Further, if content_length ends up at 0, we want to see the last * byte again for our message complete callback. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_message_done); /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * * The alternative to doing this is to wait for the next byte to * trigger the data callback, just as in every other case. The * problem with this is that this makes it difficult for the test * harness to distinguish between complete-on-EOF and * complete-on-length. It's not clear that this distinction is * important for applications, but let's keep it for now. */ CALLBACK_DATA_(body, p - body_mark + 1, p - data); REEXECUTE(); } break; } /* read until EOF */ case s_body_identity_eof: MARK(body); p = data + len - 1; break; case s_message_done: UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); if (parser->upgrade) { /* Exit, the rest of the message is in a different protocol. */ RETURN((p - data) + 1); } break; case s_chunk_size_start: { assert(parser->nread == 1); assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; if (UNLIKELY(unhex_val == -1)) { SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length = unhex_val; UPDATE_STATE(s_chunk_size); break; } case s_chunk_size: { uint64_t t; assert(parser->flags & F_CHUNKED); if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } unhex_val = unhex[(unsigned char)ch]; if (unhex_val == -1) { if (ch == ';' || ch == ' ') { UPDATE_STATE(s_chunk_parameters); break; } SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } t = parser->content_length; t *= 16; t += unhex_val; /* Overflow? Test against a conservative limit for simplicity. */ if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } parser->content_length = t; break; } case s_chunk_parameters: { assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; } break; } case s_chunk_size_almost_done: { assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; UPDATE_STATE(s_header_field_start); } else { UPDATE_STATE(s_chunk_data); } CALLBACK_NOTIFY(chunk_header); break; } case s_chunk_data: { uint64_t to_read = MIN(parser->content_length, (uint64_t) ((data + len) - p)); assert(parser->flags & F_CHUNKED); assert(parser->content_length != 0 && parser->content_length != ULLONG_MAX); /* See the explanation in s_body_identity for why the content * length and data pointers are managed this way. */ MARK(body); parser->content_length -= to_read; p += to_read - 1; if (parser->content_length == 0) { UPDATE_STATE(s_chunk_data_almost_done); } break; } case s_chunk_data_almost_done: assert(parser->flags & F_CHUNKED); assert(parser->content_length == 0); STRICT_CHECK(ch != CR); UPDATE_STATE(s_chunk_data_done); CALLBACK_DATA(body); break; case s_chunk_data_done: assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; UPDATE_STATE(s_chunk_size_start); CALLBACK_NOTIFY(chunk_complete); break; default: assert(0 && "unhandled state"); SET_ERRNO(HPE_INVALID_INTERNAL_STATE); goto error; } } /* Run callbacks for any marks that we have leftover after we ran our of * bytes. There should be at most one of these set, so it's OK to invoke * them in series (unset marks will not result in callbacks). * * We use the NOADVANCE() variety of callbacks here because 'p' has already * overflowed 'data' and this allows us to correct for the off-by-one that * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' * value that's in-bounds). */ assert(((header_field_mark ? 1 : 0) + (header_value_mark ? 1 : 0) + (url_mark ? 1 : 0) + (body_mark ? 1 : 0) + (status_mark ? 1 : 0)) <= 1); CALLBACK_DATA_NOADVANCE(header_field); CALLBACK_DATA_NOADVANCE(header_value); CALLBACK_DATA_NOADVANCE(url); CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); RETURN(len); error: if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { SET_ERRNO(HPE_UNKNOWN); } RETURN(p - data); } /* Does the parser need to see an EOF to find the end of the message? */ int http_message_needs_eof (const http_parser *parser) { if (parser->type == HTTP_REQUEST) { return 0; } /* See RFC 2616 section 4.4 */ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ parser->status_code == 204 || /* No Content */ parser->status_code == 304 || /* Not Modified */ parser->flags & F_SKIPBODY) { /* response to a HEAD request */ return 0; } if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { return 0; } return 1; } int http_should_keep_alive (const http_parser *parser) { if (parser->http_major > 0 && parser->http_minor > 0) { /* HTTP/1.1 */ if (parser->flags & F_CONNECTION_CLOSE) { return 0; } } else { /* HTTP/1.0 or earlier */ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { return 0; } } return !http_message_needs_eof(parser); } const char * http_method_str (enum http_method m) { return ELEM_AT(method_strings, m, "<unknown>"); } void http_parser_init (http_parser *parser, enum http_parser_type t) { void *data = parser->data; /* preserve application data */ memset(parser, 0, sizeof(*parser)); parser->data = data; parser->type = t; parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); parser->http_errno = HPE_OK; } void http_parser_settings_init(http_parser_settings *settings) { memset(settings, 0, sizeof(*settings)); } const char * http_errno_name(enum http_errno err) { assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].name; } const char * http_errno_description(enum http_errno err) { assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); return http_strerror_tab[err].description; } static enum http_host_state http_parse_host_char(enum http_host_state s, const char ch) { switch(s) { case s_http_userinfo: case s_http_userinfo_start: if (ch == '@') { return s_http_host_start; } if (IS_USERINFO_CHAR(ch)) { return s_http_userinfo; } break; case s_http_host_start: if (ch == '[') { return s_http_host_v6_start; } if (IS_HOST_CHAR(ch)) { return s_http_host; } break; case s_http_host: if (IS_HOST_CHAR(ch)) { return s_http_host; } /* FALLTHROUGH */ case s_http_host_v6_end: if (ch == ':') { return s_http_host_port_start; } break; case s_http_host_v6: if (ch == ']') { return s_http_host_v6_end; } /* FALLTHROUGH */ case s_http_host_v6_start: if (IS_HEX(ch) || ch == ':' || ch == '.') { return s_http_host_v6; } if (s == s_http_host_v6 && ch == '%') { return s_http_host_v6_zone_start; } break; case s_http_host_v6_zone: if (ch == ']') { return s_http_host_v6_end; } /* FALLTHROUGH */ case s_http_host_v6_zone_start: /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || ch == '~') { return s_http_host_v6_zone; } break; case s_http_host_port: case s_http_host_port_start: if (IS_NUM(ch)) { return s_http_host_port; } break; default: break; } return s_http_host_dead; } static int http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { assert(u->field_set & (1 << UF_HOST)); enum http_host_state s; const char *p; size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; u->field_data[UF_HOST].len = 0; s = found_at ? s_http_userinfo_start : s_http_host_start; for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { enum http_host_state new_s = http_parse_host_char(s, *p); if (new_s == s_http_host_dead) { return 1; } switch(new_s) { case s_http_host: if (s != s_http_host) { u->field_data[UF_HOST].off = static_cast<uint16_t>(p - buf); } u->field_data[UF_HOST].len++; break; case s_http_host_v6: if (s != s_http_host_v6) { u->field_data[UF_HOST].off = static_cast<uint16_t>(p - buf); } u->field_data[UF_HOST].len++; break; case s_http_host_v6_zone_start: case s_http_host_v6_zone: u->field_data[UF_HOST].len++; break; case s_http_host_port: if (s != s_http_host_port) { u->field_data[UF_PORT].off = static_cast<uint16_t>(p - buf); u->field_data[UF_PORT].len = 0; u->field_set |= (1 << UF_PORT); } u->field_data[UF_PORT].len++; break; case s_http_userinfo: if (s != s_http_userinfo) { u->field_data[UF_USERINFO].off = static_cast<uint16_t>(p - buf); u->field_data[UF_USERINFO].len = 0; u->field_set |= (1 << UF_USERINFO); } u->field_data[UF_USERINFO].len++; break; default: break; } s = new_s; } /* Make sure we don't end somewhere unexpected */ switch (s) { case s_http_host_start: case s_http_host_v6_start: case s_http_host_v6: case s_http_host_v6_zone_start: case s_http_host_v6_zone: case s_http_host_port_start: case s_http_userinfo: case s_http_userinfo_start: return 1; default: break; } return 0; } void http_parser_url_init(struct http_parser_url *u) { memset(u, 0, sizeof(*u)); } int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u) { enum state s; const char *p; enum http_parser_url_fields uf, old_uf; int found_at = 0; u->port = u->field_set = 0; s = is_connect ? s_req_server_start : s_req_spaces_before_url; old_uf = UF_MAX; for (p = buf; p < buf + buflen; p++) { s = parse_url_char(s, *p); /* Figure out the next field that we're operating on */ switch (s) { case s_dead: return 1; /* Skip delimeters */ case s_req_schema_slash: case s_req_schema_slash_slash: case s_req_server_start: case s_req_query_string_start: case s_req_fragment_start: continue; case s_req_schema: uf = UF_SCHEMA; break; case s_req_server_with_at: found_at = 1; //falls through case s_req_server: uf = UF_HOST; break; case s_req_path: uf = UF_PATH; break; case s_req_query_string: uf = UF_QUERY; break; case s_req_fragment: uf = UF_FRAGMENT; break; default: assert(false && "Unexpected state"); return 1; } /* Nothing's changed; soldier on */ if (uf == old_uf) { u->field_data[uf].len++; continue; } u->field_data[uf].off = static_cast<uint16_t>(p - buf); u->field_data[uf].len = 1; u->field_set = static_cast<uint16_t>(u->field_set | (1 << uf)); old_uf = uf; } /* host must be present if there is a schema */ /* parsing http:///toto will fail */ if ((u->field_set & (1 << UF_SCHEMA)) && (u->field_set & (1 << UF_HOST)) == 0) { return 1; } if (u->field_set & (1 << UF_HOST)) { if (http_parse_host(buf, u, found_at) != 0) { return 1; } } /* CONNECT requests can only contain "hostname:port" */ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { return 1; } if (u->field_set & (1 << UF_PORT)) { /* Don't bother with endp; we've already validated the string */ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); /* Ports have a max value of 2^16 */ if (v > 0xffff) { return 1; } u->port = (uint16_t) v; } return 0; } void http_parser_pause(http_parser *parser, int paused) { /* Users should only be pausing/unpausing a parser that is not in an error * state. In non-debug builds, there's not much that we can do about this * other than ignore it. */ if (HTTP_PARSER_ERRNO(parser) == HPE_OK || HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); } else { assert(0 && "Attempting to pause parser in error state"); } } int http_body_is_final(const struct http_parser *parser) { return parser->state == s_message_done; } unsigned long http_parser_version(void) { return HTTP_PARSER_VERSION_MAJOR * 0x10000 | HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } #ifdef __cplusplus } #endif �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/http_parser/http_parser.h���������������������������������������������������0000664�0000000�0000000�00000032370�15162662266�0022044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef http_parser_h #define http_parser_h #ifdef __cplusplus extern "C" { #endif /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 6 #define HTTP_PARSER_VERSION_PATCH 0 #include <sys/types.h> #if defined(_WIN32) && !defined(__MINGW32__) && \ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) #include <BaseTsd.h> #include <stddef.h> typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; typedef unsigned __int16 uint16_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else #include <stdint.h> #endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster */ #ifndef HTTP_PARSER_STRICT # define HTTP_PARSER_STRICT 1 #endif /* Maximium header size allowed. If the macro is not defined * before including this header then the default is used. To * change the maximum header size, define the macro in the build * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove * the effective limit on the size of the header, define the macro * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) */ #ifndef HTTP_MAX_HEADER_SIZE # define HTTP_MAX_HEADER_SIZE (80*1024) #endif typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings; /* Callbacks should return non-zero to indicate an error. The parser will * then halt execution. * * The one exception is on_headers_complete. In a HTTP_RESPONSE parser * returning '1' from on_headers_complete will tell the parser that it * should not expect a body. This is used when receiving a response to a * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: * chunked' headers that indicate the presence of a body. * * http_data_cb does not return data chunks. It will be called arbitrarily * many times for each string. E.G. you might get 10 callbacks for "on_url" * each providing just a few characters more data. */ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); typedef int (*http_cb) (http_parser*); /* Request Methods */ #define HTTP_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ XX(1, GET, GET) \ XX(2, HEAD, HEAD) \ XX(3, POST, POST) \ XX(4, PUT, PUT) \ /* pathological */ \ XX(5, CONNECT, CONNECT) \ XX(6, OPTIONS, OPTIONS) \ XX(7, TRACE, TRACE) \ /* WebDAV */ \ XX(8, COPY, COPY) \ XX(9, LOCK, LOCK) \ XX(10, MKCOL, MKCOL) \ XX(11, MOVE, MOVE) \ XX(12, PROPFIND, PROPFIND) \ XX(13, PROPPATCH, PROPPATCH) \ XX(14, SEARCH, SEARCH) \ XX(15, UNLOCK, UNLOCK) \ XX(16, BIND, BIND) \ XX(17, REBIND, REBIND) \ XX(18, UNBIND, UNBIND) \ XX(19, ACL, ACL) \ /* subversion */ \ XX(20, REPORT, REPORT) \ XX(21, MKACTIVITY, MKACTIVITY) \ XX(22, CHECKOUT, CHECKOUT) \ XX(23, MERGE, MERGE) \ /* upnp */ \ XX(24, MSEARCH, M-SEARCH) \ XX(25, NOTIFY, NOTIFY) \ XX(26, SUBSCRIBE, SUBSCRIBE) \ XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ /* RFC-5789 */ \ XX(28, PATCH, PATCH) \ XX(29, PURGE, PURGE) \ /* CalDAV */ \ XX(30, MKCALENDAR, MKCALENDAR) \ /* RFC-2068, section 19.6.1.2 */ \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ enum http_method { #define XX(num, name, string) HTTP_##name = num, HTTP_METHOD_MAP(XX) #undef XX }; enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; /* Flag values for http_parser.flags field */ enum flags { F_CHUNKED = 1 << 0 , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 , F_CONNECTION_UPGRADE = 1 << 3 , F_TRAILING = 1 << 4 , F_UPGRADE = 1 << 5 , F_SKIPBODY = 1 << 6 }; /* Map for errno-related constants * * The provided argument should be a macro that takes 2 arguments. */ #define HTTP_ERRNO_MAP(XX) \ /* No error */ \ XX(OK, "success") \ \ /* Callback-related errors */ \ XX(CB_message_begin, "the on_message_begin callback failed") \ XX(CB_url, "the on_url callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \ XX(CB_body, "the on_body callback failed") \ XX(CB_message_complete, "the on_message_complete callback failed") \ XX(CB_status, "the on_status callback failed") \ XX(CB_chunk_header, "the on_chunk_header callback failed") \ XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ \ /* Parsing-related errors */ \ XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ XX(HEADER_OVERFLOW, \ "too many header bytes seen; overflow detected") \ XX(CLOSED_CONNECTION, \ "data received after completed connection: close message") \ XX(INVALID_VERSION, "invalid HTTP version") \ XX(INVALID_STATUS, "invalid HTTP status code") \ XX(INVALID_METHOD, "invalid HTTP method") \ XX(INVALID_URL, "invalid URL") \ XX(INVALID_HOST, "invalid host") \ XX(INVALID_PORT, "invalid port") \ XX(INVALID_PATH, "invalid path") \ XX(INVALID_QUERY_STRING, "invalid query string") \ XX(INVALID_FRAGMENT, "invalid fragment") \ XX(LF_EXPECTED, "LF character expected") \ XX(INVALID_HEADER_TOKEN, "invalid character in header") \ XX(INVALID_CONTENT_LENGTH, \ "invalid character in content-length header") \ XX(INVALID_CHUNK_SIZE, \ "invalid character in chunk size header") \ XX(INVALID_CONSTANT, "invalid constant string") \ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \ XX(PAUSED, "parser is paused") \ XX(UNKNOWN, "an unknown error occurred") /* Define HPE_* values for each errno value above */ #define HTTP_ERRNO_GEN(n, s) HPE_##n, enum http_errno { HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) }; #undef HTTP_ERRNO_GEN /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) /* Forward declaration for enums defined in the implementation file */ enum state : uint8_t; enum header_states : uint8_t; struct http_parser { /** PRIVATE **/ enum http_parser_type type : 2; /* enum http_parser_type */ unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ enum state state; /* enum state from http_parser.c */ enum header_states header_state : 8; /* enum header_state from http_parser.c */ unsigned int index : 8; /* index into current matcher */ uint32_t nread; /* # bytes read in various scenarios */ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ /** READ-ONLY **/ unsigned short http_major; unsigned short http_minor; unsigned int status_code : 16; /* responses only */ unsigned int method : 8; /* requests only */ unsigned int http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that. * 0 = No upgrade header present. * Should be checked when http_parser_execute() returns in addition to * error checking. */ unsigned int upgrade : 1; /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ }; struct http_parser_settings { http_cb on_message_begin; http_data_cb on_url; http_data_cb on_status; http_data_cb on_header_field; http_data_cb on_header_value; http_cb on_headers_complete; http_data_cb on_body; http_cb on_message_complete; /* When on_chunk_header is called, the current chunk length is stored * in parser->content_length. */ http_cb on_chunk_header; http_cb on_chunk_complete; }; enum http_parser_url_fields { UF_SCHEMA = 0 , UF_HOST = 1 , UF_PORT = 2 , UF_PATH = 3 , UF_QUERY = 4 , UF_FRAGMENT = 5 , UF_USERINFO = 6 , UF_MAX = 7 }; /* Result structure for http_parser_parse_url(). * * Callers should index into field_data[] with UF_* values iff field_set * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and * because we probably have padding left over), we convert any port to * a uint16_t. */ struct http_parser_url { uint16_t field_set; /* Bitmask of (1 << UF_*) values */ uint16_t port; /* Converted UF_PORT string */ struct { uint16_t off; /* Offset into buffer in which field starts */ uint16_t len; /* Length of run in buffer */ } field_data[UF_MAX]; }; /* Returns the library version. Bits 16-23 contain the major version number, * bits 8-15 the minor version number and bits 0-7 the patch level. * Usage example: * * unsigned long version = http_parser_version(); * unsigned major = (version >> 16) & 255; * unsigned minor = (version >> 8) & 255; * unsigned patch = version & 255; * printf("http_parser v%u.%u.%u\n", major, minor, patch); */ unsigned long http_parser_version(void); void http_parser_init(http_parser *parser, enum http_parser_type type); /* Initialize http_parser_settings members to 0 */ void http_parser_settings_init(http_parser_settings *settings); /* Executes the parser. Returns number of parsed bytes. Sets * `parser->http_errno` on error. */ size_t http_parser_execute(http_parser *parser, const http_parser_settings *settings, const char *data, size_t len); /* If http_should_keep_alive() in the on_headers_complete or * on_message_complete callback returns 0, then this should be * the last message on the connection. * If you are the server, respond with the "Connection: close" header. * If you are the client, close the connection. */ int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m); /* Return a string name of the given error */ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); /* Initialize all http_parser_url members to 0 */ void http_parser_url_init(struct http_parser_url *u); /* Parse a URL; return nonzero on failure */ int http_parser_parse_url(const char *buf, size_t buflen, int is_connect, struct http_parser_url *u); /* Pause or un-pause the parser; a nonzero value pauses */ void http_parser_pause(http_parser *parser, int paused); /* Checks if this is the final chunk of the body. */ int http_body_is_final(const http_parser *parser); #ifdef __cplusplus } #endif #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016552�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/CMakeLists.txt�����������������������������������������������������0000664�0000000�0000000�00000001330�15162662266�0021307�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(utf8proc OBJECT utf8proc.h utf8proc.cpp ) set(UTF8PROC_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/utf8proc") file(MAKE_DIRECTORY "${UTF8PROC_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/utf8proc.h" "${UTF8PROC_PUBLIC_INCLUDE_DIR}/utf8proc.h" COPYONLY ) target_include_directories(utf8proc SYSTEM PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(ENABLE_JAVA_BINDINGS) set_target_properties(utf8proc PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() if(APPLE AND ENABLE_SDKLIB_WERROR) set_source_files_properties( utf8proc.cpp PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion" ) endif() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/LICENSE������������������������������������������������������������0000664�0000000�0000000�00000006556�15162662266�0017573�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Copyright (c) 2009, 2013 Public Software Group e. V., Berlin, Germany Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This software distribution contains derived data from a modified version of the Unicode data files. The following license applies to that data: COPYRIGHT AND PERMISSION NOTICE Copyright (c) 1991-2007 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that (a) the above copyright notice(s) and this permission notice appear with all copies of the Data Files or Software, (b) both the above copyright notice(s) and this permission notice appear in associated documentation, and (c) there is clear notice in each modified Data File or in the Software as well as in the documentation associated with the Data File(s) or Software that the data or software has been modified. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. Unicode and the Unicode logo are trademarks of Unicode, Inc., and may be registered in some jurisdictions. All other trademarks and registered trademarks mentioned herein are the property of their respective owners. ��������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/utf8proc.cpp�������������������������������������������������������0000664�0000000�0000000�00000072673�15162662266�0021047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* -*- mode: c; c-basic-offset: 2; tab-width: 2; indent-tabs-mode: nil -*- */ /* * Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other contributors. * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * This library contains derived data from a modified version of the * Unicode data files. * * The original data files are available at * http://www.unicode.org/Public/UNIDATA/ * * Please notice the copyright statement in the file "utf8proc_data.c". */ /* * File name: utf8proc.c * * Description: * Implementation of libutf8proc. */ #include "utf8proc.h" #include "utf8proc_data.c" UTF8PROC_DLLEXPORT const utf8proc_int8_t utf8proc_utf8class[256] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0 }; #define UTF8PROC_HANGUL_SBASE 0xAC00 #define UTF8PROC_HANGUL_LBASE 0x1100 #define UTF8PROC_HANGUL_VBASE 0x1161 #define UTF8PROC_HANGUL_TBASE 0x11A7 #define UTF8PROC_HANGUL_LCOUNT 19 #define UTF8PROC_HANGUL_VCOUNT 21 #define UTF8PROC_HANGUL_TCOUNT 28 #define UTF8PROC_HANGUL_NCOUNT 588 #define UTF8PROC_HANGUL_SCOUNT 11172 /* END is exclusive */ #define UTF8PROC_HANGUL_L_START 0x1100 #define UTF8PROC_HANGUL_L_END 0x115A #define UTF8PROC_HANGUL_L_FILLER 0x115F #define UTF8PROC_HANGUL_V_START 0x1160 #define UTF8PROC_HANGUL_V_END 0x11A3 #define UTF8PROC_HANGUL_T_START 0x11A8 #define UTF8PROC_HANGUL_T_END 0x11FA #define UTF8PROC_HANGUL_S_START 0xAC00 #define UTF8PROC_HANGUL_S_END 0xD7A4 /* Should follow semantic-versioning rules (semver.org) based on API compatibility. (Note that the shared-library version number will be different, being based on ABI compatibility.): */ #define STRINGIZEx(x) #x #define STRINGIZE(x) STRINGIZEx(x) UTF8PROC_DLLEXPORT const char *utf8proc_version(void) { return STRINGIZE(UTF8PROC_VERSION_MAJOR) "." STRINGIZE(UTF8PROC_VERSION_MINOR) "." STRINGIZE(UTF8PROC_VERSION_PATCH) ""; } UTF8PROC_DLLEXPORT const char *utf8proc_errmsg(utf8proc_ssize_t errcode) { switch (errcode) { case UTF8PROC_ERROR_NOMEM: return "Memory for processing UTF-8 data could not be allocated."; case UTF8PROC_ERROR_OVERFLOW: return "UTF-8 string is too long to be processed."; case UTF8PROC_ERROR_INVALIDUTF8: return "Invalid UTF-8 string"; case UTF8PROC_ERROR_NOTASSIGNED: return "Unassigned Unicode code point found in UTF-8 string."; case UTF8PROC_ERROR_INVALIDOPTS: return "Invalid options for UTF-8 processing chosen."; default: return "An unknown error occurred while processing UTF-8 data."; } } #define utf_cont(ch) (((ch) & 0xc0) == 0x80) UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *dst ) { utf8proc_uint32_t uc; const utf8proc_uint8_t *end; *dst = -1; if (!strlen) return 0; end = str + ((strlen < 0) ? 4 : strlen); uc = *str++; if (uc < 0x80) { *dst = uc; return 1; } // Must be between 0xc2 and 0xf4 inclusive to be valid if ((uc - 0xc2) > (0xf4-0xc2)) return UTF8PROC_ERROR_INVALIDUTF8; if (uc < 0xe0) { // 2-byte sequence // Must have valid continuation character if (str >= end || !utf_cont(*str)) return UTF8PROC_ERROR_INVALIDUTF8; *dst = ((uc & 0x1f)<<6) | (*str & 0x3f); return 2; } if (uc < 0xf0) { // 3-byte sequence if ((str + 1 >= end) || !utf_cont(*str) || !utf_cont(str[1])) return UTF8PROC_ERROR_INVALIDUTF8; // Check for surrogate chars if (uc == 0xed && *str > 0x9f) return UTF8PROC_ERROR_INVALIDUTF8; uc = ((uc & 0xf)<<12) | ((*str & 0x3f)<<6) | (str[1] & 0x3f); if (uc < 0x800) return UTF8PROC_ERROR_INVALIDUTF8; *dst = uc; return 3; } // 4-byte sequence // Must have 3 valid continuation characters if ((str + 2 >= end) || !utf_cont(*str) || !utf_cont(str[1]) || !utf_cont(str[2])) return UTF8PROC_ERROR_INVALIDUTF8; // Make sure in correct range (0x10000 - 0x10ffff) if (uc == 0xf0) { if (*str < 0x90) return UTF8PROC_ERROR_INVALIDUTF8; } else if (uc == 0xf4) { if (*str > 0x8f) return UTF8PROC_ERROR_INVALIDUTF8; } *dst = ((uc & 7)<<18) | ((*str & 0x3f)<<12) | ((str[1] & 0x3f)<<6) | (str[2] & 0x3f); return 4; } UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t uc) { return (((utf8proc_uint32_t)uc)-0xd800 > 0x07ff) && ((utf8proc_uint32_t)uc < 0x110000); } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) { if (uc < 0x00) { return 0; } else if (uc < 0x80) { dst[0] = (utf8proc_uint8_t) uc; return 1; } else if (uc < 0x800) { dst[0] = (utf8proc_uint8_t)(0xC0 + (uc >> 6)); dst[1] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 2; // Note: we allow encoding 0xd800-0xdfff here, so as not to change // the API, however, these are actually invalid in UTF-8 } else if (uc < 0x10000) { dst[0] = (utf8proc_uint8_t)(0xE0 + (uc >> 12)); dst[1] = (utf8proc_uint8_t)(0x80 + ((uc >> 6) & 0x3F)); dst[2] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 3; } else if (uc < 0x110000) { dst[0] = (utf8proc_uint8_t)(0xF0 + (uc >> 18)); dst[1] = (utf8proc_uint8_t)(0x80 + ((uc >> 12) & 0x3F)); dst[2] = (utf8proc_uint8_t)(0x80 + ((uc >> 6) & 0x3F)); dst[3] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 4; } else return 0; } /* internal "unsafe" version that does not check whether uc is in range */ static utf8proc_ssize_t unsafe_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) { if (uc < 0x00) { return 0; } else if (uc < 0x80) { dst[0] = (utf8proc_uint8_t)uc; return 1; } else if (uc < 0x800) { dst[0] = (utf8proc_uint8_t)(0xC0 + (uc >> 6)); dst[1] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 2; } else if (uc == 0xFFFF) { dst[0] = (utf8proc_uint8_t)0xFF; return 1; } else if (uc == 0xFFFE) { dst[0] = (utf8proc_uint8_t)0xFE; return 1; } else if (uc < 0x10000) { dst[0] = (utf8proc_uint8_t)(0xE0 + (uc >> 12)); dst[1] = (utf8proc_uint8_t)(0x80 + ((uc >> 6) & 0x3F)); dst[2] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 3; } else if (uc < 0x110000) { dst[0] = (utf8proc_uint8_t)(0xF0 + (uc >> 18)); dst[1] = (utf8proc_uint8_t)(0x80 + ((uc >> 12) & 0x3F)); dst[2] = (utf8proc_uint8_t)(0x80 + ((uc >> 6) & 0x3F)); dst[3] = (utf8proc_uint8_t)(0x80 + (uc & 0x3F)); return 4; } else return 0; } /* internal "unsafe" version that does not check whether uc is in range */ static const utf8proc_property_t *unsafe_get_property(utf8proc_int32_t uc) { /* ASSERT: uc >= 0 && uc < 0x110000 */ return utf8proc_properties + ( utf8proc_stage2table[ utf8proc_stage1table[uc >> 8] + (uc & 0xFF) ] ); } UTF8PROC_DLLEXPORT const utf8proc_property_t *utf8proc_get_property(utf8proc_int32_t uc) { return uc < 0 || uc >= 0x110000 ? utf8proc_properties : unsafe_get_property(uc); } /* return whether there is a grapheme break between boundclasses lbc and tbc (according to the definition of extended grapheme clusters) Rule numbering refers to TR29 Version 29 (Unicode 9.0.0): http://www.unicode.org/reports/tr29/tr29-29.html CAVEATS: Please note that evaluation of GB10 (grapheme breaks between emoji zwj sequences) and GB 12/13 (regional indicator code points) require knowledge of previous characters and are thus not handled by this function. This may result in an incorrect break before an E_Modifier class codepoint and an incorrectly missing break between two REGIONAL_INDICATOR class code points if such support does not exist in the caller. See the special support in grapheme_break_extended, for required bookkeeping by the caller. */ static utf8proc_bool grapheme_break_simple(int lbc, int tbc) { return (lbc == UTF8PROC_BOUNDCLASS_START) ? true : // GB1 (lbc == UTF8PROC_BOUNDCLASS_CR && // GB3 tbc == UTF8PROC_BOUNDCLASS_LF) ? false : // --- (lbc >= UTF8PROC_BOUNDCLASS_CR && lbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true : // GB4 (tbc >= UTF8PROC_BOUNDCLASS_CR && tbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true : // GB5 (lbc == UTF8PROC_BOUNDCLASS_L && // GB6 (tbc == UTF8PROC_BOUNDCLASS_L || // --- tbc == UTF8PROC_BOUNDCLASS_V || // --- tbc == UTF8PROC_BOUNDCLASS_LV || // --- tbc == UTF8PROC_BOUNDCLASS_LVT)) ? false : // --- ((lbc == UTF8PROC_BOUNDCLASS_LV || // GB7 lbc == UTF8PROC_BOUNDCLASS_V) && // --- (tbc == UTF8PROC_BOUNDCLASS_V || // --- tbc == UTF8PROC_BOUNDCLASS_T)) ? false : // --- ((lbc == UTF8PROC_BOUNDCLASS_LVT || // GB8 lbc == UTF8PROC_BOUNDCLASS_T) && // --- tbc == UTF8PROC_BOUNDCLASS_T) ? false : // --- (tbc == UTF8PROC_BOUNDCLASS_EXTEND || // GB9 tbc == UTF8PROC_BOUNDCLASS_ZWJ || // --- tbc == UTF8PROC_BOUNDCLASS_SPACINGMARK || // GB9a lbc == UTF8PROC_BOUNDCLASS_PREPEND) ? false : // GB9b ((lbc == UTF8PROC_BOUNDCLASS_E_BASE || // GB10 (requires additional handling below) lbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ) && // ---- tbc == UTF8PROC_BOUNDCLASS_E_MODIFIER) ? false : // ---- (lbc == UTF8PROC_BOUNDCLASS_ZWJ && // GB11 (tbc == UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ || // ---- tbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ)) ? false : // ---- (lbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR && // GB12/13 (requires additional handling below) tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR) ? false : // ---- true; // GB999 } static utf8proc_bool grapheme_break_extended(int lbc, int tbc, utf8proc_int32_t *state) { int lbc_override = lbc; if (state && *state != UTF8PROC_BOUNDCLASS_START) lbc_override = *state; utf8proc_bool break_permitted = grapheme_break_simple(lbc_override, tbc); if (state) { // Special support for GB 12/13 made possible by GB999. After two RI // class codepoints we want to force a break. Do this by resetting the // second RI's bound class to UTF8PROC_BOUNDCLASS_OTHER, to force a break // after that character according to GB999 (unless of course such a break is // forbidden by a different rule such as GB9). if (*state == tbc && tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR) *state = UTF8PROC_BOUNDCLASS_OTHER; // Special support for GB10. Fold any EXTEND codepoints into the previous // boundclass if we're dealing with an emoji base boundclass. else if ((*state == UTF8PROC_BOUNDCLASS_E_BASE || *state == UTF8PROC_BOUNDCLASS_E_BASE_GAZ) && tbc == UTF8PROC_BOUNDCLASS_EXTEND) *state = UTF8PROC_BOUNDCLASS_E_BASE; else *state = tbc; } return break_permitted; } UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break_stateful( utf8proc_int32_t c1, utf8proc_int32_t c2, utf8proc_int32_t *state) { return grapheme_break_extended(utf8proc_get_property(c1)->boundclass, utf8proc_get_property(c2)->boundclass, state); } UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break( utf8proc_int32_t c1, utf8proc_int32_t c2) { return utf8proc_grapheme_break_stateful(c1, c2, NULL); } static utf8proc_int32_t seqindex_decode_entry(const utf8proc_uint16_t **entry) { utf8proc_int32_t entry_cp = **entry; if ((entry_cp & 0xF800) == 0xD800) { *entry = *entry + 1; entry_cp = ((entry_cp & 0x03FF) << 10) | (**entry & 0x03FF); entry_cp += 0x10000; } return entry_cp; } static utf8proc_int32_t seqindex_decode_index(const utf8proc_uint32_t seqindex) { const utf8proc_uint16_t *entry = &utf8proc_sequences[seqindex]; return seqindex_decode_entry(&entry); } static utf8proc_ssize_t seqindex_write_char_decomposed(utf8proc_uint16_t seqindex, utf8proc_int32_t *dst, utf8proc_ssize_t bufsize, utf8proc_option_t options, int *last_boundclass) { utf8proc_ssize_t written = 0; const utf8proc_uint16_t *entry = &utf8proc_sequences[seqindex & 0x1FFF]; int len = seqindex >> 13; if (len >= 7) { len = *entry; entry++; } for (; len >= 0; entry++, len--) { utf8proc_int32_t entry_cp = seqindex_decode_entry(&entry); written += utf8proc_decompose_char(entry_cp, (dst) ? dst+written : dst, (bufsize > written) ? (bufsize - written) : 0, options, last_boundclass); if (written < 0) return UTF8PROC_ERROR_OVERFLOW; } return written; } UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_tolower(utf8proc_int32_t c) { utf8proc_int32_t cl = utf8proc_get_property(c)->lowercase_seqindex; return cl != UINT16_MAX ? seqindex_decode_index(cl) : c; } UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_toupper(utf8proc_int32_t c) { utf8proc_int32_t cu = utf8proc_get_property(c)->uppercase_seqindex; return cu != UINT16_MAX ? seqindex_decode_index(cu) : c; } UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_totitle(utf8proc_int32_t c) { utf8proc_int32_t cu = utf8proc_get_property(c)->titlecase_seqindex; return cu != UINT16_MAX ? seqindex_decode_index(cu) : c; } /* return a character width analogous to wcwidth (except portable and hopefully less buggy than most system wcwidth functions). */ UTF8PROC_DLLEXPORT int utf8proc_charwidth(utf8proc_int32_t c) { return utf8proc_get_property(c)->charwidth; } UTF8PROC_DLLEXPORT utf8proc_category_t utf8proc_category(utf8proc_int32_t c) { return (utf8proc_category_t)utf8proc_get_property(c)->category; } UTF8PROC_DLLEXPORT const char *utf8proc_category_string(utf8proc_int32_t c) { static const char s[][3] = {"Cn","Lu","Ll","Lt","Lm","Lo","Mn","Mc","Me","Nd","Nl","No","Pc","Pd","Ps","Pe","Pi","Pf","Po","Sm","Sc","Sk","So","Zs","Zl","Zp","Cc","Cf","Cs","Co"}; return s[utf8proc_category(c)]; } #define utf8proc_decompose_lump(replacement_uc) \ return utf8proc_decompose_char((replacement_uc), dst, bufsize, \ (utf8proc_option_t)(options & ~UTF8PROC_LUMP), last_boundclass) UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_char(utf8proc_int32_t uc, utf8proc_int32_t *dst, utf8proc_ssize_t bufsize, utf8proc_option_t options, int *last_boundclass) { const utf8proc_property_t *property; utf8proc_propval_t category; utf8proc_int32_t hangul_sindex; if (uc < 0 || uc >= 0x110000) return UTF8PROC_ERROR_NOTASSIGNED; property = unsafe_get_property(uc); category = property->category; hangul_sindex = uc - UTF8PROC_HANGUL_SBASE; if (options & (UTF8PROC_COMPOSE|UTF8PROC_DECOMPOSE)) { if (hangul_sindex >= 0 && hangul_sindex < UTF8PROC_HANGUL_SCOUNT) { utf8proc_int32_t hangul_tindex; if (bufsize >= 1) { dst[0] = UTF8PROC_HANGUL_LBASE + hangul_sindex / UTF8PROC_HANGUL_NCOUNT; if (bufsize >= 2) dst[1] = UTF8PROC_HANGUL_VBASE + (hangul_sindex % UTF8PROC_HANGUL_NCOUNT) / UTF8PROC_HANGUL_TCOUNT; } hangul_tindex = hangul_sindex % UTF8PROC_HANGUL_TCOUNT; if (!hangul_tindex) return 2; if (bufsize >= 3) dst[2] = UTF8PROC_HANGUL_TBASE + hangul_tindex; return 3; } } if (options & UTF8PROC_REJECTNA) { if (!category) return UTF8PROC_ERROR_NOTASSIGNED; } if (options & UTF8PROC_IGNORE) { if (property->ignorable) return 0; } if (options & UTF8PROC_LUMP) { if (category == UTF8PROC_CATEGORY_ZS) utf8proc_decompose_lump(0x0020); if (uc == 0x2018 || uc == 0x2019 || uc == 0x02BC || uc == 0x02C8) utf8proc_decompose_lump(0x0027); if (category == UTF8PROC_CATEGORY_PD || uc == 0x2212) utf8proc_decompose_lump(0x002D); if (uc == 0x2044 || uc == 0x2215) utf8proc_decompose_lump(0x002F); if (uc == 0x2236) utf8proc_decompose_lump(0x003A); if (uc == 0x2039 || uc == 0x2329 || uc == 0x3008) utf8proc_decompose_lump(0x003C); if (uc == 0x203A || uc == 0x232A || uc == 0x3009) utf8proc_decompose_lump(0x003E); if (uc == 0x2216) utf8proc_decompose_lump(0x005C); if (uc == 0x02C4 || uc == 0x02C6 || uc == 0x2038 || uc == 0x2303) utf8proc_decompose_lump(0x005E); if (category == UTF8PROC_CATEGORY_PC || uc == 0x02CD) utf8proc_decompose_lump(0x005F); if (uc == 0x02CB) utf8proc_decompose_lump(0x0060); if (uc == 0x2223) utf8proc_decompose_lump(0x007C); if (uc == 0x223C) utf8proc_decompose_lump(0x007E); if ((options & UTF8PROC_NLF2LS) && (options & UTF8PROC_NLF2PS)) { if (category == UTF8PROC_CATEGORY_ZL || category == UTF8PROC_CATEGORY_ZP) utf8proc_decompose_lump(0x000A); } } if (options & UTF8PROC_STRIPMARK) { if (category == UTF8PROC_CATEGORY_MN || category == UTF8PROC_CATEGORY_MC || category == UTF8PROC_CATEGORY_ME) return 0; } if (options & UTF8PROC_CASEFOLD) { if (property->casefold_seqindex != UINT16_MAX) { return seqindex_write_char_decomposed(property->casefold_seqindex, dst, bufsize, options, last_boundclass); } } if (options & (UTF8PROC_COMPOSE|UTF8PROC_DECOMPOSE)) { if (property->decomp_seqindex != UINT16_MAX && (!property->decomp_type || (options & UTF8PROC_COMPAT))) { return seqindex_write_char_decomposed(property->decomp_seqindex, dst, bufsize, options, last_boundclass); } } if (options & UTF8PROC_CHARBOUND) { utf8proc_bool boundary; int tbc = property->boundclass; boundary = grapheme_break_extended(*last_boundclass, tbc, last_boundclass); if (boundary) { if (bufsize >= 1) dst[0] = 0xFFFF; if (bufsize >= 2) dst[1] = uc; return 2; } } if (bufsize >= 1) *dst = uc; return 1; } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options ) { return utf8proc_decompose_custom(str, strlen, buffer, bufsize, options, NULL, NULL); } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_custom( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options, utf8proc_custom_func custom_func, void *custom_data ) { /* strlen will be ignored, if UTF8PROC_NULLTERM is set in options */ utf8proc_ssize_t wpos = 0; if ((options & UTF8PROC_COMPOSE) && (options & UTF8PROC_DECOMPOSE)) return UTF8PROC_ERROR_INVALIDOPTS; if ((options & UTF8PROC_STRIPMARK) && !(options & UTF8PROC_COMPOSE) && !(options & UTF8PROC_DECOMPOSE)) return UTF8PROC_ERROR_INVALIDOPTS; { utf8proc_int32_t uc; utf8proc_ssize_t rpos = 0; utf8proc_ssize_t decomp_result; int boundclass = UTF8PROC_BOUNDCLASS_START; while (1) { if (options & UTF8PROC_NULLTERM) { rpos += utf8proc_iterate(str + rpos, -1, &uc); /* checking of return value is not necessary, as 'uc' is < 0 in case of error */ if (uc < 0) return UTF8PROC_ERROR_INVALIDUTF8; if (rpos < 0) return UTF8PROC_ERROR_OVERFLOW; if (uc == 0) break; } else { if (rpos >= strlen) break; rpos += utf8proc_iterate(str + rpos, strlen - rpos, &uc); if (uc < 0) return UTF8PROC_ERROR_INVALIDUTF8; } if (custom_func != NULL) { uc = custom_func(uc, custom_data); /* user-specified custom mapping */ } decomp_result = utf8proc_decompose_char( uc, (buffer) ? buffer + wpos : buffer, (bufsize > wpos) ? (bufsize - wpos) : 0, options, &boundclass ); if (decomp_result < 0) return decomp_result; wpos += decomp_result; /* prohibiting integer overflows due to too long strings: */ if (wpos < 0 || wpos > (utf8proc_ssize_t)(SSIZE_MAX/sizeof(utf8proc_int32_t)/2)) return UTF8PROC_ERROR_OVERFLOW; } } if ((options & (UTF8PROC_COMPOSE|UTF8PROC_DECOMPOSE)) && bufsize >= wpos) { utf8proc_ssize_t pos = 0; while (pos < wpos-1) { utf8proc_int32_t uc1, uc2; const utf8proc_property_t *property1, *property2; uc1 = buffer[pos]; uc2 = buffer[pos+1]; property1 = unsafe_get_property(uc1); property2 = unsafe_get_property(uc2); if (property1->combining_class > property2->combining_class && property2->combining_class > 0) { buffer[pos] = uc2; buffer[pos+1] = uc1; if (pos > 0) pos--; else pos++; } else { pos++; } } } return wpos; } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_normalize_utf32(utf8proc_int32_t *buffer, utf8proc_ssize_t length, utf8proc_option_t options) { /* UTF8PROC_NULLTERM option will be ignored, 'length' is never ignored */ if (options & (UTF8PROC_NLF2LS | UTF8PROC_NLF2PS | UTF8PROC_STRIPCC)) { utf8proc_ssize_t rpos; utf8proc_ssize_t wpos = 0; utf8proc_int32_t uc; for (rpos = 0; rpos < length; rpos++) { uc = buffer[rpos]; if (uc == 0x000D && rpos < length-1 && buffer[rpos+1] == 0x000A) rpos++; if (uc == 0x000A || uc == 0x000D || uc == 0x0085 || ((options & UTF8PROC_STRIPCC) && (uc == 0x000B || uc == 0x000C))) { if (options & UTF8PROC_NLF2LS) { if (options & UTF8PROC_NLF2PS) { buffer[wpos++] = 0x000A; } else { buffer[wpos++] = 0x2028; } } else { if (options & UTF8PROC_NLF2PS) { buffer[wpos++] = 0x2029; } else { buffer[wpos++] = 0x0020; } } } else if ((options & UTF8PROC_STRIPCC) && (uc < 0x0020 || (uc >= 0x007F && uc < 0x00A0))) { if (uc == 0x0009) buffer[wpos++] = 0x0020; } else { buffer[wpos++] = uc; } } length = wpos; } if (options & UTF8PROC_COMPOSE) { utf8proc_int32_t *starter = NULL; utf8proc_int32_t current_char; const utf8proc_property_t *starter_property = NULL, *current_property; utf8proc_propval_t max_combining_class = -1; utf8proc_ssize_t rpos; utf8proc_ssize_t wpos = 0; utf8proc_int32_t composition; for (rpos = 0; rpos < length; rpos++) { current_char = buffer[rpos]; current_property = unsafe_get_property(current_char); if (starter && current_property->combining_class > max_combining_class) { /* combination perhaps possible */ utf8proc_int32_t hangul_lindex; utf8proc_int32_t hangul_sindex; hangul_lindex = *starter - UTF8PROC_HANGUL_LBASE; if (hangul_lindex >= 0 && hangul_lindex < UTF8PROC_HANGUL_LCOUNT) { utf8proc_int32_t hangul_vindex; hangul_vindex = current_char - UTF8PROC_HANGUL_VBASE; if (hangul_vindex >= 0 && hangul_vindex < UTF8PROC_HANGUL_VCOUNT) { *starter = UTF8PROC_HANGUL_SBASE + (hangul_lindex * UTF8PROC_HANGUL_VCOUNT + hangul_vindex) * UTF8PROC_HANGUL_TCOUNT; starter_property = NULL; continue; } } hangul_sindex = *starter - UTF8PROC_HANGUL_SBASE; if (hangul_sindex >= 0 && hangul_sindex < UTF8PROC_HANGUL_SCOUNT && (hangul_sindex % UTF8PROC_HANGUL_TCOUNT) == 0) { utf8proc_int32_t hangul_tindex; hangul_tindex = current_char - UTF8PROC_HANGUL_TBASE; if (hangul_tindex >= 0 && hangul_tindex < UTF8PROC_HANGUL_TCOUNT) { *starter += hangul_tindex; starter_property = NULL; continue; } } if (!starter_property) { starter_property = unsafe_get_property(*starter); } if (starter_property->comb_index < 0x8000 && current_property->comb_index != UINT16_MAX && current_property->comb_index >= 0x8000) { int sidx = starter_property->comb_index; int idx = (current_property->comb_index & 0x3FFF) - utf8proc_combinations[sidx]; if (idx >= 0 && idx <= utf8proc_combinations[sidx + 1] ) { idx += sidx + 2; if (current_property->comb_index & 0x4000) { composition = (utf8proc_combinations[idx] << 16) | utf8proc_combinations[idx+1]; } else composition = utf8proc_combinations[idx]; if (composition > 0 && (!(options & UTF8PROC_STABLE) || !(unsafe_get_property(composition)->comp_exclusion))) { *starter = composition; starter_property = NULL; continue; } } } } buffer[wpos] = current_char; if (current_property->combining_class) { if (current_property->combining_class > max_combining_class) { max_combining_class = current_property->combining_class; } } else { starter = buffer + wpos; starter_property = NULL; max_combining_class = -1; } wpos++; } length = wpos; } return length; } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_reencode(utf8proc_int32_t *buffer, utf8proc_ssize_t length, utf8proc_option_t options) { /* UTF8PROC_NULLTERM option will be ignored, 'length' is never ignored ASSERT: 'buffer' has one spare byte of free space at the end! */ length = utf8proc_normalize_utf32(buffer, length, options); if (length < 0) return length; { utf8proc_ssize_t rpos, wpos = 0; utf8proc_int32_t uc; if (options & UTF8PROC_CHARBOUND) { for (rpos = 0; rpos < length; rpos++) { uc = buffer[rpos]; wpos += unsafe_encode_char(uc, ((utf8proc_uint8_t *)buffer) + wpos); } } else { for (rpos = 0; rpos < length; rpos++) { uc = buffer[rpos]; wpos += utf8proc_encode_char(uc, ((utf8proc_uint8_t *)buffer) + wpos); } } ((utf8proc_uint8_t *)buffer)[wpos] = 0; return wpos; } } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options ) { return utf8proc_map_custom(str, strlen, dstptr, options, NULL, NULL); } UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map_custom( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options, utf8proc_custom_func custom_func, void *custom_data ) { utf8proc_int32_t *buffer; utf8proc_ssize_t result; *dstptr = NULL; result = utf8proc_decompose_custom(str, strlen, NULL, 0, options, custom_func, custom_data); if (result < 0) return result; buffer = (utf8proc_int32_t *) malloc(result * sizeof(utf8proc_int32_t) + 1); if (!buffer) return UTF8PROC_ERROR_NOMEM; result = utf8proc_decompose_custom(str, strlen, buffer, result, options, custom_func, custom_data); if (result < 0) { free(buffer); return result; } result = utf8proc_reencode(buffer, result, options); if (result < 0) { free(buffer); return result; } { utf8proc_int32_t *newptr; newptr = (utf8proc_int32_t *) realloc(buffer, (size_t)result+1); if (newptr) buffer = newptr; } *dstptr = (utf8proc_uint8_t *)buffer; return result; } UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFD(const utf8proc_uint8_t *str) { utf8proc_uint8_t *retval; utf8proc_map(str, 0, &retval, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE)); return retval; } UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFC(const utf8proc_uint8_t *str) { utf8proc_uint8_t *retval; utf8proc_map(str, 0, &retval, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_COMPOSE)); return retval; } UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKD(const utf8proc_uint8_t *str) { utf8proc_uint8_t *retval; utf8proc_map(str, 0, &retval, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT)); return retval; } UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKC(const utf8proc_uint8_t *str) { utf8proc_uint8_t *retval; utf8proc_map(str, 0, &retval, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_COMPOSE | UTF8PROC_COMPAT)); return retval; } ���������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/utf8proc.h���������������������������������������������������������0000664�0000000�0000000�00000071450�15162662266�0020504�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /* * Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other contributors. * Copyright (c) 2009 Public Software Group e. V., Berlin, Germany * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /** * @mainpage * * utf8proc is a free/open-source (MIT/expat licensed) C library * providing Unicode normalization, case-folding, and other operations * for strings in the UTF-8 encoding, supporting Unicode version * 8.0.0. See the utf8proc home page (http://julialang.org/utf8proc/) * for downloads and other information, or the source code on github * (https://github.com/JuliaLang/utf8proc). * * For the utf8proc API documentation, see: @ref utf8proc.h * * The features of utf8proc include: * * - Transformation of strings (@ref utf8proc_map) to: * - decompose (@ref UTF8PROC_DECOMPOSE) or compose (@ref UTF8PROC_COMPOSE) Unicode combining characters (http://en.wikipedia.org/wiki/Combining_character) * - canonicalize Unicode compatibility characters (@ref UTF8PROC_COMPAT) * - strip "ignorable" (@ref UTF8PROC_IGNORE) characters, control characters (@ref UTF8PROC_STRIPCC), or combining characters such as accents (@ref UTF8PROC_STRIPMARK) * - case-folding (@ref UTF8PROC_CASEFOLD) * - Unicode normalization: @ref utf8proc_NFD, @ref utf8proc_NFC, @ref utf8proc_NFKD, @ref utf8proc_NFKC * - Detecting grapheme boundaries (@ref utf8proc_grapheme_break and @ref UTF8PROC_CHARBOUND) * - Character-width computation: @ref utf8proc_charwidth * - Classification of characters by Unicode category: @ref utf8proc_category and @ref utf8proc_category_string * - Encode (@ref utf8proc_encode_char) and decode (@ref utf8proc_iterate) Unicode codepoints to/from UTF-8. */ /** @file */ #ifndef UTF8PROC_H #define UTF8PROC_H /** @name API version * * The utf8proc API version MAJOR.MINOR.PATCH, following * semantic-versioning rules (http://semver.org) based on API * compatibility. * * This is also returned at runtime by @ref utf8proc_version; however, the * runtime version may append a string like "-dev" to the version number * for prerelease versions. * * @note The shared-library version number in the Makefile * (and CMakeLists.txt, and MANIFEST) may be different, * being based on ABI compatibility rather than API compatibility. */ /** @{ */ /** The MAJOR version number (increased when backwards API compatibility is broken). */ #define UTF8PROC_VERSION_MAJOR 2 /** The MINOR version number (increased when new functionality is added in a backwards-compatible manner). */ #define UTF8PROC_VERSION_MINOR 1 /** The PATCH version (increased for fixes that do not change the API). */ #define UTF8PROC_VERSION_PATCH 0 /** @} */ #include <stdlib.h> #include <sys/types.h> #if defined(_MSC_VER) && _MSC_VER < 1800 // MSVC prior to 2013 lacked stdbool.h and inttypes.h typedef signed char utf8proc_int8_t; typedef unsigned char utf8proc_uint8_t; typedef short utf8proc_int16_t; typedef unsigned short utf8proc_uint16_t; typedef int utf8proc_int32_t; typedef unsigned int utf8proc_uint32_t; # ifdef _WIN64 typedef __int64 utf8proc_ssize_t; typedef unsigned __int64 utf8proc_size_t; # else typedef int utf8proc_ssize_t; typedef unsigned int utf8proc_size_t; # endif # ifndef __cplusplus // emulate C99 bool typedef unsigned char utf8proc_bool; # ifndef __bool_true_false_are_defined # define false 0 # define true 1 # define __bool_true_false_are_defined 1 # endif # else typedef bool utf8proc_bool; # endif #else # include <stddef.h> # include <stdbool.h> # include <inttypes.h> typedef int8_t utf8proc_int8_t; typedef uint8_t utf8proc_uint8_t; typedef int16_t utf8proc_int16_t; typedef uint16_t utf8proc_uint16_t; typedef int32_t utf8proc_int32_t; typedef uint32_t utf8proc_uint32_t; typedef size_t utf8proc_size_t; typedef ptrdiff_t utf8proc_ssize_t; typedef bool utf8proc_bool; #endif #include <limits.h> #ifdef _WIN32 # ifdef UTF8PROC_EXPORTS # define UTF8PROC_DLLEXPORT __declspec(dllexport) # else # define UTF8PROC_DLLEXPORT # endif #elif __GNUC__ >= 4 # define UTF8PROC_DLLEXPORT #else # define UTF8PROC_DLLEXPORT #endif #ifdef __cplusplus extern "C" { #endif #ifndef SSIZE_MAX #define SSIZE_MAX ((size_t)SIZE_MAX/2) #endif #ifndef UINT16_MAX # define UINT16_MAX 65535U #endif /** * Option flags used by several functions in the library. */ typedef enum { /** The given UTF-8 input is NULL terminated. */ UTF8PROC_NULLTERM = (1<<0), /** Unicode Versioning Stability has to be respected. */ UTF8PROC_STABLE = (1<<1), /** Compatibility decomposition (i.e. formatting information is lost). */ UTF8PROC_COMPAT = (1<<2), /** Return a result with decomposed characters. */ UTF8PROC_COMPOSE = (1<<3), /** Return a result with decomposed characters. */ UTF8PROC_DECOMPOSE = (1<<4), /** Strip "default ignorable characters" such as SOFT-HYPHEN or ZERO-WIDTH-SPACE. */ UTF8PROC_IGNORE = (1<<5), /** Return an error, if the input contains unassigned codepoints. */ UTF8PROC_REJECTNA = (1<<6), /** * Indicating that NLF-sequences (LF, CRLF, CR, NEL) are representing a * line break, and should be converted to the codepoint for line * separation (LS). */ UTF8PROC_NLF2LS = (1<<7), /** * Indicating that NLF-sequences are representing a paragraph break, and * should be converted to the codepoint for paragraph separation * (PS). */ UTF8PROC_NLF2PS = (1<<8), /** Indicating that the meaning of NLF-sequences is unknown. */ UTF8PROC_NLF2LF = (UTF8PROC_NLF2LS | UTF8PROC_NLF2PS), /** Strips and/or convers control characters. * * NLF-sequences are transformed into space, except if one of the * NLF2LS/PS/LF options is given. HorizontalTab (HT) and FormFeed (FF) * are treated as a NLF-sequence in this case. All other control * characters are simply removed. */ UTF8PROC_STRIPCC = (1<<9), /** * Performs unicode case folding, to be able to do a case-insensitive * string comparison. */ UTF8PROC_CASEFOLD = (1<<10), /** * Inserts 0xFF bytes at the beginning of each sequence which is * representing a single grapheme cluster (see UAX#29). */ UTF8PROC_CHARBOUND = (1<<11), /** Lumps certain characters together. * * E.g. HYPHEN U+2010 and MINUS U+2212 to ASCII "-". See lump.md for details. * * If NLF2LF is set, this includes a transformation of paragraph and * line separators to ASCII line-feed (LF). */ UTF8PROC_LUMP = (1<<12), /** Strips all character markings. * * This includes non-spacing, spacing and enclosing (i.e. accents). * @note This option works only with @ref UTF8PROC_COMPOSE or * @ref UTF8PROC_DECOMPOSE */ UTF8PROC_STRIPMARK = (1<<13), } utf8proc_option_t; /** @name Error codes * Error codes being returned by almost all functions. */ /** @{ */ /** Memory could not be allocated. */ #define UTF8PROC_ERROR_NOMEM -1 /** The given string is too long to be processed. */ #define UTF8PROC_ERROR_OVERFLOW -2 /** The given string is not a legal UTF-8 string. */ #define UTF8PROC_ERROR_INVALIDUTF8 -3 /** The @ref UTF8PROC_REJECTNA flag was set and an unassigned codepoint was found. */ #define UTF8PROC_ERROR_NOTASSIGNED -4 /** Invalid options have been used. */ #define UTF8PROC_ERROR_INVALIDOPTS -5 /** @} */ /* @name Types */ /** Holds the value of a property. */ typedef utf8proc_int16_t utf8proc_propval_t; /** Struct containing information about a codepoint. */ typedef struct utf8proc_property_struct { /** * Unicode category. * @see utf8proc_category_t. */ utf8proc_propval_t category; utf8proc_propval_t combining_class; /** * Bidirectional class. * @see utf8proc_bidi_class_t. */ utf8proc_propval_t bidi_class; /** * @anchor Decomposition type. * @see utf8proc_decomp_type_t. */ utf8proc_propval_t decomp_type; utf8proc_uint16_t decomp_seqindex; utf8proc_uint16_t casefold_seqindex; utf8proc_uint16_t uppercase_seqindex; utf8proc_uint16_t lowercase_seqindex; utf8proc_uint16_t titlecase_seqindex; utf8proc_uint16_t comb_index; unsigned bidi_mirrored:1; unsigned comp_exclusion:1; /** * Can this codepoint be ignored? * * Used by @ref utf8proc_decompose_char when @ref UTF8PROC_IGNORE is * passed as an option. */ unsigned ignorable:1; unsigned control_boundary:1; /** The width of the codepoint. */ unsigned charwidth:2; unsigned pad:2; /** * Boundclass. * @see utf8proc_boundclass_t. */ unsigned boundclass:8; } utf8proc_property_t; /** Unicode categories. */ typedef enum { UTF8PROC_CATEGORY_CN = 0, /**< Other, not assigned */ UTF8PROC_CATEGORY_LU = 1, /**< Letter, uppercase */ UTF8PROC_CATEGORY_LL = 2, /**< Letter, lowercase */ UTF8PROC_CATEGORY_LT = 3, /**< Letter, titlecase */ UTF8PROC_CATEGORY_LM = 4, /**< Letter, modifier */ UTF8PROC_CATEGORY_LO = 5, /**< Letter, other */ UTF8PROC_CATEGORY_MN = 6, /**< Mark, nonspacing */ UTF8PROC_CATEGORY_MC = 7, /**< Mark, spacing combining */ UTF8PROC_CATEGORY_ME = 8, /**< Mark, enclosing */ UTF8PROC_CATEGORY_ND = 9, /**< Number, decimal digit */ UTF8PROC_CATEGORY_NL = 10, /**< Number, letter */ UTF8PROC_CATEGORY_NO = 11, /**< Number, other */ UTF8PROC_CATEGORY_PC = 12, /**< Punctuation, connector */ UTF8PROC_CATEGORY_PD = 13, /**< Punctuation, dash */ UTF8PROC_CATEGORY_PS = 14, /**< Punctuation, open */ UTF8PROC_CATEGORY_PE = 15, /**< Punctuation, close */ UTF8PROC_CATEGORY_PI = 16, /**< Punctuation, initial quote */ UTF8PROC_CATEGORY_PF = 17, /**< Punctuation, final quote */ UTF8PROC_CATEGORY_PO = 18, /**< Punctuation, other */ UTF8PROC_CATEGORY_SM = 19, /**< Symbol, math */ UTF8PROC_CATEGORY_SC = 20, /**< Symbol, currency */ UTF8PROC_CATEGORY_SK = 21, /**< Symbol, modifier */ UTF8PROC_CATEGORY_SO = 22, /**< Symbol, other */ UTF8PROC_CATEGORY_ZS = 23, /**< Separator, space */ UTF8PROC_CATEGORY_ZL = 24, /**< Separator, line */ UTF8PROC_CATEGORY_ZP = 25, /**< Separator, paragraph */ UTF8PROC_CATEGORY_CC = 26, /**< Other, control */ UTF8PROC_CATEGORY_CF = 27, /**< Other, format */ UTF8PROC_CATEGORY_CS = 28, /**< Other, surrogate */ UTF8PROC_CATEGORY_CO = 29, /**< Other, private use */ } utf8proc_category_t; /** Bidirectional character classes. */ typedef enum { UTF8PROC_BIDI_CLASS_L = 1, /**< Left-to-Right */ UTF8PROC_BIDI_CLASS_LRE = 2, /**< Left-to-Right Embedding */ UTF8PROC_BIDI_CLASS_LRO = 3, /**< Left-to-Right Override */ UTF8PROC_BIDI_CLASS_R = 4, /**< Right-to-Left */ UTF8PROC_BIDI_CLASS_AL = 5, /**< Right-to-Left Arabic */ UTF8PROC_BIDI_CLASS_RLE = 6, /**< Right-to-Left Embedding */ UTF8PROC_BIDI_CLASS_RLO = 7, /**< Right-to-Left Override */ UTF8PROC_BIDI_CLASS_PDF = 8, /**< Pop Directional Format */ UTF8PROC_BIDI_CLASS_EN = 9, /**< European Number */ UTF8PROC_BIDI_CLASS_ES = 10, /**< European Separator */ UTF8PROC_BIDI_CLASS_ET = 11, /**< European Number Terminator */ UTF8PROC_BIDI_CLASS_AN = 12, /**< Arabic Number */ UTF8PROC_BIDI_CLASS_CS = 13, /**< Common Number Separator */ UTF8PROC_BIDI_CLASS_NSM = 14, /**< Nonspacing Mark */ UTF8PROC_BIDI_CLASS_BN = 15, /**< Boundary Neutral */ UTF8PROC_BIDI_CLASS_B = 16, /**< Paragraph Separator */ UTF8PROC_BIDI_CLASS_S = 17, /**< Segment Separator */ UTF8PROC_BIDI_CLASS_WS = 18, /**< Whitespace */ UTF8PROC_BIDI_CLASS_ON = 19, /**< Other Neutrals */ UTF8PROC_BIDI_CLASS_LRI = 20, /**< Left-to-Right Isolate */ UTF8PROC_BIDI_CLASS_RLI = 21, /**< Right-to-Left Isolate */ UTF8PROC_BIDI_CLASS_FSI = 22, /**< First Strong Isolate */ UTF8PROC_BIDI_CLASS_PDI = 23, /**< Pop Directional Isolate */ } utf8proc_bidi_class_t; /** Decomposition type. */ typedef enum { UTF8PROC_DECOMP_TYPE_FONT = 1, /**< Font */ UTF8PROC_DECOMP_TYPE_NOBREAK = 2, /**< Nobreak */ UTF8PROC_DECOMP_TYPE_INITIAL = 3, /**< Initial */ UTF8PROC_DECOMP_TYPE_MEDIAL = 4, /**< Medial */ UTF8PROC_DECOMP_TYPE_FINAL = 5, /**< Final */ UTF8PROC_DECOMP_TYPE_ISOLATED = 6, /**< Isolated */ UTF8PROC_DECOMP_TYPE_CIRCLE = 7, /**< Circle */ UTF8PROC_DECOMP_TYPE_SUPER = 8, /**< Super */ UTF8PROC_DECOMP_TYPE_SUB = 9, /**< Sub */ UTF8PROC_DECOMP_TYPE_VERTICAL = 10, /**< Vertical */ UTF8PROC_DECOMP_TYPE_WIDE = 11, /**< Wide */ UTF8PROC_DECOMP_TYPE_NARROW = 12, /**< Narrow */ UTF8PROC_DECOMP_TYPE_SMALL = 13, /**< Small */ UTF8PROC_DECOMP_TYPE_SQUARE = 14, /**< Square */ UTF8PROC_DECOMP_TYPE_FRACTION = 15, /**< Fraction */ UTF8PROC_DECOMP_TYPE_COMPAT = 16, /**< Compat */ } utf8proc_decomp_type_t; /** Boundclass property. (TR29) */ typedef enum { UTF8PROC_BOUNDCLASS_START = 0, /**< Start */ UTF8PROC_BOUNDCLASS_OTHER = 1, /**< Other */ UTF8PROC_BOUNDCLASS_CR = 2, /**< Cr */ UTF8PROC_BOUNDCLASS_LF = 3, /**< Lf */ UTF8PROC_BOUNDCLASS_CONTROL = 4, /**< Control */ UTF8PROC_BOUNDCLASS_EXTEND = 5, /**< Extend */ UTF8PROC_BOUNDCLASS_L = 6, /**< L */ UTF8PROC_BOUNDCLASS_V = 7, /**< V */ UTF8PROC_BOUNDCLASS_T = 8, /**< T */ UTF8PROC_BOUNDCLASS_LV = 9, /**< Lv */ UTF8PROC_BOUNDCLASS_LVT = 10, /**< Lvt */ UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR = 11, /**< Regional indicator */ UTF8PROC_BOUNDCLASS_SPACINGMARK = 12, /**< Spacingmark */ UTF8PROC_BOUNDCLASS_PREPEND = 13, /**< Prepend */ UTF8PROC_BOUNDCLASS_ZWJ = 14, /**< Zero Width Joiner */ UTF8PROC_BOUNDCLASS_E_BASE = 15, /**< Emoji Base */ UTF8PROC_BOUNDCLASS_E_MODIFIER = 16, /**< Emoji Modifier */ UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ = 17, /**< Glue_After_ZWJ */ UTF8PROC_BOUNDCLASS_E_BASE_GAZ = 18, /**< E_BASE + GLUE_AFTER_ZJW */ } utf8proc_boundclass_t; /** * Function pointer type passed to @ref utf8proc_map_custom and * @ref utf8proc_decompose_custom, which is used to specify a user-defined * mapping of codepoints to be applied in conjunction with other mappings. */ typedef utf8proc_int32_t (*utf8proc_custom_func)(utf8proc_int32_t codepoint, void *data); /** * Array containing the byte lengths of a UTF-8 encoded codepoint based * on the first byte. */ UTF8PROC_DLLEXPORT extern const utf8proc_int8_t utf8proc_utf8class[256]; /** * Returns the utf8proc API version as a string MAJOR.MINOR.PATCH * (http://semver.org format), possibly with a "-dev" suffix for * development versions. */ UTF8PROC_DLLEXPORT const char *utf8proc_version(void); /** * Returns an informative error string for the given utf8proc error code * (e.g. the error codes returned by @ref utf8proc_map). */ UTF8PROC_DLLEXPORT const char *utf8proc_errmsg(utf8proc_ssize_t errcode); /** * Reads a single codepoint from the UTF-8 sequence being pointed to by `str`. * The maximum number of bytes read is `strlen`, unless `strlen` is * negative (in which case up to 4 bytes are read). * * If a valid codepoint could be read, it is stored in the variable * pointed to by `codepoint_ref`, otherwise that variable will be set to -1. * In case of success, the number of bytes read is returned; otherwise, a * negative error code is returned. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate(const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *codepoint_ref); /** * Check if a codepoint is valid (regardless of whether it has been * assigned a value by the current Unicode standard). * * @return 1 if the given `codepoint` is valid and otherwise return 0. */ UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t codepoint); /** * Encodes the codepoint as an UTF-8 string in the byte array pointed * to by `dst`. This array must be at least 4 bytes long. * * In case of success the number of bytes written is returned, and * otherwise 0 is returned. * * This function does not check whether `codepoint` is valid Unicode. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t codepoint, utf8proc_uint8_t *dst); /** * Look up the properties for a given codepoint. * * @param codepoint The Unicode codepoint. * * @returns * A pointer to a (constant) struct containing information about * the codepoint. * @par * If the codepoint is unassigned or invalid, a pointer to a special struct is * returned in which `category` is 0 (@ref UTF8PROC_CATEGORY_CN). */ UTF8PROC_DLLEXPORT const utf8proc_property_t *utf8proc_get_property(utf8proc_int32_t codepoint); /** Decompose a codepoint into an array of codepoints. * * @param codepoint the codepoint. * @param dst the destination buffer. * @param bufsize the size of the destination buffer. * @param options one or more of the following flags: * - @ref UTF8PROC_REJECTNA - return an error `codepoint` is unassigned * - @ref UTF8PROC_IGNORE - strip "default ignorable" codepoints * - @ref UTF8PROC_CASEFOLD - apply Unicode casefolding * - @ref UTF8PROC_COMPAT - replace certain codepoints with their * compatibility decomposition * - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster * - @ref UTF8PROC_LUMP - lump certain different codepoints together * - @ref UTF8PROC_STRIPMARK - remove all character marks * @param last_boundclass * Pointer to an integer variable containing * the previous codepoint's boundary class if the @ref UTF8PROC_CHARBOUND * option is used. Otherwise, this parameter is ignored. * * @return * In case of success, the number of codepoints written is returned; in case * of an error, a negative error code is returned (@ref utf8proc_errmsg). * @par * If the number of written codepoints would be bigger than `bufsize`, the * required buffer size is returned, while the buffer will be overwritten with * undefined data. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_char( utf8proc_int32_t codepoint, utf8proc_int32_t *dst, utf8proc_ssize_t bufsize, utf8proc_option_t options, int *last_boundclass ); /** * The same as @ref utf8proc_decompose_char, but acts on a whole UTF-8 * string and orders the decomposed sequences correctly. * * If the @ref UTF8PROC_NULLTERM flag in `options` is set, processing * will be stopped, when a NULL byte is encounted, otherwise `strlen` * bytes are processed. The result (in the form of 32-bit unicode * codepoints) is written into the buffer being pointed to by * `buffer` (which must contain at least `bufsize` entries). In case of * success, the number of codepoints written is returned; in case of an * error, a negative error code is returned (@ref utf8proc_errmsg). * See @ref utf8proc_decompose_custom to supply additional transformations. * * If the number of written codepoints would be bigger than `bufsize`, the * required buffer size is returned, while the buffer will be overwritten with * undefined data. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options ); /** * The same as @ref utf8proc_decompose, but also takes a `custom_func` mapping function * that is called on each codepoint in `str` before any other transformations * (along with a `custom_data` pointer that is passed through to `custom_func`). * The `custom_func` argument is ignored if it is `NULL`. See also @ref utf8proc_map_custom. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_custom( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options, utf8proc_custom_func custom_func, void *custom_data ); /** * Normalizes the sequence of `length` codepoints pointed to by `buffer` * in-place (i.e., the result is also stored in `buffer`). * * @param buffer the (native-endian UTF-32) unicode codepoints to re-encode. * @param length the length (in codepoints) of the buffer. * @param options a bitwise or (`|`) of one or more of the following flags: * - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS * - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS * - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF * - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters * - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite * codepoints * - @ref UTF8PROC_STABLE - prohibit combining characters that would violate * the unicode versioning stability * * @return * In case of success, the length (in codepoints) of the normalized UTF-32 string is * returned; otherwise, a negative error code is returned (@ref utf8proc_errmsg). * * @warning The entries of the array pointed to by `str` have to be in the * range `0x0000` to `0x10FFFF`. Otherwise, the program might crash! */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_normalize_utf32(utf8proc_int32_t *buffer, utf8proc_ssize_t length, utf8proc_option_t options); /** * Reencodes the sequence of `length` codepoints pointed to by `buffer` * UTF-8 data in-place (i.e., the result is also stored in `buffer`). * Can optionally normalize the UTF-32 sequence prior to UTF-8 conversion. * * @param buffer the (native-endian UTF-32) unicode codepoints to re-encode. * @param length the length (in codepoints) of the buffer. * @param options a bitwise or (`|`) of one or more of the following flags: * - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS * - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS * - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF * - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters * - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite * codepoints * - @ref UTF8PROC_STABLE - prohibit combining characters that would violate * the unicode versioning stability * - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster * * @return * In case of success, the length (in bytes) of the resulting nul-terminated * UTF-8 string is returned; otherwise, a negative error code is returned * (@ref utf8proc_errmsg). * * @warning The amount of free space pointed to by `buffer` must * exceed the amount of the input data by one byte, and the * entries of the array pointed to by `str` have to be in the * range `0x0000` to `0x10FFFF`. Otherwise, the program might crash! */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_reencode(utf8proc_int32_t *buffer, utf8proc_ssize_t length, utf8proc_option_t options); /** * Given a pair of consecutive codepoints, return whether a grapheme break is * permitted between them (as defined by the extended grapheme clusters in UAX#29). * * @param state Beginning with Version 29 (Unicode 9.0.0), this algorithm requires * state to break graphemes. This state can be passed in as a pointer * in the `state` argument and should initially be set to 0. If the * state is not passed in (i.e. a null pointer is passed), UAX#29 rules * GB10/12/13 which require this state will not be applied, essentially * matching the rules in Unicode 8.0.0. * * @warning If the state parameter is used, `utf8proc_grapheme_break_stateful` must * be called IN ORDER on ALL potential breaks in a string. */ UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break_stateful( utf8proc_int32_t codepoint1, utf8proc_int32_t codepoint2, utf8proc_int32_t *state); /** * Same as @ref utf8proc_grapheme_break_stateful, except without support for the * Unicode 9 additions to the algorithm. Supported for legacy reasons. */ UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break( utf8proc_int32_t codepoint1, utf8proc_int32_t codepoint2); /** * Given a codepoint `c`, return the codepoint of the corresponding * lower-case character, if any; otherwise (if there is no lower-case * variant, or if `c` is not a valid codepoint) return `c`. */ UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_tolower(utf8proc_int32_t c); /** * Given a codepoint `c`, return the codepoint of the corresponding * upper-case character, if any; otherwise (if there is no upper-case * variant, or if `c` is not a valid codepoint) return `c`. */ UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_toupper(utf8proc_int32_t c); /** * Given a codepoint `c`, return the codepoint of the corresponding * title-case character, if any; otherwise (if there is no title-case * variant, or if `c` is not a valid codepoint) return `c`. */ UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_totitle(utf8proc_int32_t c); /** * Given a codepoint, return a character width analogous to `wcwidth(codepoint)`, * except that a width of 0 is returned for non-printable codepoints * instead of -1 as in `wcwidth`. * * @note * If you want to check for particular types of non-printable characters, * (analogous to `isprint` or `iscntrl`), use @ref utf8proc_category. */ UTF8PROC_DLLEXPORT int utf8proc_charwidth(utf8proc_int32_t codepoint); /** * Return the Unicode category for the codepoint (one of the * @ref utf8proc_category_t constants.) */ UTF8PROC_DLLEXPORT utf8proc_category_t utf8proc_category(utf8proc_int32_t codepoint); /** * Return the two-letter (nul-terminated) Unicode category string for * the codepoint (e.g. `"Lu"` or `"Co"`). */ UTF8PROC_DLLEXPORT const char *utf8proc_category_string(utf8proc_int32_t codepoint); /** * Maps the given UTF-8 string pointed to by `str` to a new UTF-8 * string, allocated dynamically by `malloc` and returned via `dstptr`. * * If the @ref UTF8PROC_NULLTERM flag in the `options` field is set, * the length is determined by a NULL terminator, otherwise the * parameter `strlen` is evaluated to determine the string length, but * in any case the result will be NULL terminated (though it might * contain NULL characters with the string if `str` contained NULL * characters). Other flags in the `options` field are passed to the * functions defined above, and regarded as described. See also * @ref utfproc_map_custom to supply a custom codepoint transformation. * * In case of success the length of the new string is returned, * otherwise a negative error code is returned. * * @note The memory of the new UTF-8 string will have been allocated * with `malloc`, and should therefore be deallocated with `free`. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options ); /** * Like @ref utf8proc_map, but also takes a `custom_func` mapping function * that is called on each codepoint in `str` before any other transformations * (along with a `custom_data` pointer that is passed through to `custom_func`). * The `custom_func` argument is ignored if it is `NULL`. */ UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map_custom( const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options, utf8proc_custom_func custom_func, void *custom_data ); /** @name Unicode normalization * * Returns a pointer to newly allocated memory of a NFD, NFC, NFKD or NFKC * normalized version of the null-terminated string `str`. These * are shortcuts to calling @ref utf8proc_map with @ref UTF8PROC_NULLTERM * combined with @ref UTF8PROC_STABLE and flags indicating the normalization. */ /** @{ */ /** NFD normalization (@ref UTF8PROC_DECOMPOSE). */ UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFD(const utf8proc_uint8_t *str); /** NFC normalization (@ref UTF8PROC_COMPOSE). */ UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFC(const utf8proc_uint8_t *str); /** NFKD normalization (@ref UTF8PROC_DECOMPOSE and @ref UTF8PROC_COMPAT). */ UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKD(const utf8proc_uint8_t *str); /** NFKC normalization (@ref UTF8PROC_COMPOSE and @ref UTF8PROC_COMPAT). */ UTF8PROC_DLLEXPORT utf8proc_uint8_t *utf8proc_NFKC(const utf8proc_uint8_t *str); /** @} */ #ifdef __cplusplus } #endif #endif ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/utf8proc/utf8proc_data.c����������������������������������������������������0000664�0000000�0000000�00006137653�15162662266�0021506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off const utf8proc_uint16_t utf8proc_sequences[] = { 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 32, 32, 776, 32, 772, 50, 51, 32, 769, 956, 32, 807, 49, 49, 8260, 52, 49, 8260, 50, 51, 8260, 52, 65, 768, 224, 65, 769, 225, 65, 770, 226, 65, 771, 227, 65, 776, 228, 65, 778, 229, 230, 67, 807, 231, 69, 768, 232, 69, 769, 233, 69, 770, 234, 69, 776, 235, 73, 768, 236, 73, 769, 237, 73, 770, 238, 73, 776, 239, 240, 78, 771, 241, 79, 768, 242, 79, 769, 243, 79, 770, 244, 79, 771, 245, 79, 776, 246, 248, 85, 768, 249, 85, 769, 250, 85, 770, 251, 85, 776, 252, 89, 769, 253, 254, 115, 115, 97, 768, 97, 769, 97, 770, 97, 771, 97, 776, 97, 778, 99, 807, 101, 768, 101, 769, 101, 770, 101, 776, 105, 768, 105, 769, 105, 770, 105, 776, 110, 771, 111, 768, 111, 769, 111, 770, 111, 771, 111, 776, 117, 768, 117, 769, 117, 770, 117, 776, 121, 769, 121, 776, 65, 772, 257, 97, 772, 65, 774, 259, 97, 774, 65, 808, 261, 97, 808, 67, 769, 263, 99, 769, 67, 770, 265, 99, 770, 67, 775, 267, 99, 775, 67, 780, 269, 99, 780, 68, 780, 271, 100, 780, 273, 69, 772, 275, 101, 772, 69, 774, 277, 101, 774, 69, 775, 279, 101, 775, 69, 808, 281, 101, 808, 69, 780, 283, 101, 780, 71, 770, 285, 103, 770, 71, 774, 287, 103, 774, 71, 775, 289, 103, 775, 71, 807, 291, 103, 807, 72, 770, 293, 104, 770, 295, 73, 771, 297, 105, 771, 73, 772, 299, 105, 772, 73, 774, 301, 105, 774, 73, 808, 303, 105, 808, 73, 775, 105, 775, 73, 74, 307, 105, 106, 74, 770, 309, 106, 770, 75, 807, 311, 107, 807, 76, 769, 314, 108, 769, 76, 807, 316, 108, 807, 76, 780, 318, 108, 780, 76, 183, 320, 108, 183, 322, 78, 769, 324, 110, 769, 78, 807, 326, 110, 807, 78, 780, 328, 110, 780, 700, 110, 331, 79, 772, 333, 111, 772, 79, 774, 335, 111, 774, 79, 779, 337, 111, 779, 339, 82, 769, 341, 114, 769, 82, 807, 343, 114, 807, 82, 780, 345, 114, 780, 83, 769, 347, 115, 769, 83, 770, 349, 115, 770, 83, 807, 351, 115, 807, 83, 780, 353, 115, 780, 84, 807, 355, 116, 807, 84, 780, 357, 116, 780, 359, 85, 771, 361, 117, 771, 85, 772, 363, 117, 772, 85, 774, 365, 117, 774, 85, 778, 367, 117, 778, 85, 779, 369, 117, 779, 85, 808, 371, 117, 808, 87, 770, 373, 119, 770, 89, 770, 375, 121, 770, 89, 776, 255, 90, 769, 378, 122, 769, 90, 775, 380, 122, 775, 90, 780, 382, 122, 780, 595, 387, 389, 596, 392, 598, 599, 396, 477, 601, 603, 402, 608, 611, 617, 616, 409, 623, 626, 629, 79, 795, 417, 111, 795, 419, 421, 640, 424, 643, 429, 648, 85, 795, 432, 117, 795, 650, 651, 436, 438, 658, 441, 445, 68, 381, 454, 68, 382, 100, 382, 76, 74, 457, 76, 106, 108, 106, 78, 74, 460, 78, 106, 110, 106, 65, 780, 462, 97, 780, 73, 780, 464, 105, 780, 79, 780, 466, 111, 780, 85, 780, 468, 117, 780, 220, 772, 470, 252, 772, 220, 769, 472, 252, 769, 220, 780, 474, 252, 780, 220, 768, 476, 252, 768, 196, 772, 479, 228, 772, 550, 772, 481, 551, 772, 198, 772, 483, 230, 772, 485, 71, 780, 487, 103, 780, 75, 780, 489, 107, 780, 79, 808, 491, 111, 808, 490, 772, 493, 491, 772, 439, 780, 495, 658, 780, 106, 780, 68, 90, 499, 68, 122, 100, 122, 71, 769, 501, 103, 769, 405, 447, 78, 768, 505, 110, 768, 197, 769, 507, 229, 769, 198, 769, 509, 230, 769, 216, 769, 511, 248, 769, 65, 783, 513, 97, 783, 65, 785, 515, 97, 785, 69, 783, 517, 101, 783, 69, 785, 519, 101, 785, 73, 783, 521, 105, 783, 73, 785, 523, 105, 785, 79, 783, 525, 111, 783, 79, 785, 527, 111, 785, 82, 783, 529, 114, 783, 82, 785, 531, 114, 785, 85, 783, 533, 117, 783, 85, 785, 535, 117, 785, 83, 806, 537, 115, 806, 84, 806, 539, 116, 806, 541, 72, 780, 543, 104, 780, 414, 547, 549, 65, 775, 551, 97, 775, 69, 807, 553, 101, 807, 214, 772, 555, 246, 772, 213, 772, 557, 245, 772, 79, 775, 559, 111, 775, 558, 772, 561, 559, 772, 89, 772, 563, 121, 772, 11365, 572, 410, 11366, 578, 384, 649, 652, 583, 585, 587, 589, 591, 614, 633, 635, 641, 32, 774, 32, 775, 32, 778, 32, 808, 32, 771, 32, 779, 661, 768, 769, 787, 776, 769, 953, 881, 883, 697, 887, 32, 837, 59, 1011, 168, 769, 913, 769, 940, 183, 917, 769, 941, 919, 769, 942, 921, 769, 943, 927, 769, 972, 933, 769, 973, 937, 769, 974, 970, 769, 953, 776, 769, 945, 946, 947, 948, 949, 950, 951, 952, 954, 955, 957, 958, 959, 960, 961, 963, 964, 965, 966, 967, 968, 969, 921, 776, 970, 933, 776, 971, 945, 769, 949, 769, 951, 769, 953, 769, 971, 769, 965, 776, 769, 953, 776, 965, 776, 959, 769, 965, 769, 969, 769, 983, 933, 978, 769, 978, 776, 985, 987, 989, 991, 993, 995, 997, 999, 1001, 1003, 1005, 1007, 962, 920, 1016, 931, 1010, 1019, 891, 892, 893, 1045, 768, 1104, 1045, 776, 1105, 1106, 1043, 769, 1107, 1108, 1109, 1110, 1030, 776, 1111, 1112, 1113, 1114, 1115, 1050, 769, 1116, 1048, 768, 1117, 1059, 774, 1118, 1119, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1048, 774, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1080, 774, 1077, 768, 1077, 776, 1075, 769, 1110, 776, 1082, 769, 1080, 768, 1091, 774, 1121, 1123, 1125, 1127, 1129, 1131, 1133, 1135, 1137, 1139, 1141, 1140, 783, 1143, 1141, 783, 1145, 1147, 1149, 1151, 1153, 1163, 1165, 1167, 1169, 1171, 1173, 1175, 1177, 1179, 1181, 1183, 1185, 1187, 1189, 1191, 1193, 1195, 1197, 1199, 1201, 1203, 1205, 1207, 1209, 1211, 1213, 1215, 1231, 1046, 774, 1218, 1078, 774, 1220, 1222, 1224, 1226, 1228, 1230, 1040, 774, 1233, 1072, 774, 1040, 776, 1235, 1072, 776, 1237, 1045, 774, 1239, 1077, 774, 1241, 1240, 776, 1243, 1241, 776, 1046, 776, 1245, 1078, 776, 1047, 776, 1247, 1079, 776, 1249, 1048, 772, 1251, 1080, 772, 1048, 776, 1253, 1080, 776, 1054, 776, 1255, 1086, 776, 1257, 1256, 776, 1259, 1257, 776, 1069, 776, 1261, 1101, 776, 1059, 772, 1263, 1091, 772, 1059, 776, 1265, 1091, 776, 1059, 779, 1267, 1091, 779, 1063, 776, 1269, 1095, 776, 1271, 1067, 776, 1273, 1099, 776, 1275, 1277, 1279, 1281, 1283, 1285, 1287, 1289, 1291, 1293, 1295, 1297, 1299, 1301, 1303, 1305, 1307, 1309, 1311, 1313, 1315, 1317, 1319, 1321, 1323, 1325, 1327, 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1381, 1410, 1575, 1619, 1575, 1620, 1608, 1620, 1575, 1621, 1610, 1620, 1575, 1652, 1608, 1652, 1735, 1652, 1610, 1652, 1749, 1620, 1729, 1620, 1746, 1620, 2344, 2364, 2352, 2364, 2355, 2364, 2325, 2364, 2326, 2364, 2327, 2364, 2332, 2364, 2337, 2364, 2338, 2364, 2347, 2364, 2351, 2364, 2503, 2494, 2503, 2519, 2465, 2492, 2466, 2492, 2479, 2492, 2610, 2620, 2616, 2620, 2582, 2620, 2583, 2620, 2588, 2620, 2603, 2620, 2887, 2902, 2887, 2878, 2887, 2903, 2849, 2876, 2850, 2876, 2962, 3031, 3014, 3006, 3015, 3006, 3014, 3031, 3142, 3158, 3263, 3285, 3270, 3285, 3270, 3286, 3270, 3266, 3274, 3285, 3398, 3390, 3399, 3390, 3398, 3415, 3545, 3530, 3545, 3535, 3548, 3530, 3545, 3551, 3661, 3634, 3789, 3762, 3755, 3737, 3755, 3745, 3851, 3906, 4023, 3916, 4023, 3921, 4023, 3926, 4023, 3931, 4023, 3904, 4021, 3953, 3954, 3953, 3956, 4018, 3968, 4018, 3969, 4019, 3968, 4019, 3969, 3953, 3968, 3986, 4023, 3996, 4023, 4001, 4023, 4006, 4023, 4011, 4023, 3984, 4021, 4133, 4142, 11520, 11521, 11522, 11523, 11524, 11525, 11526, 11527, 11528, 11529, 11530, 11531, 11532, 11533, 11534, 11535, 11536, 11537, 11538, 11539, 11540, 11541, 11542, 11543, 11544, 11545, 11546, 11547, 11548, 11549, 11550, 11551, 11552, 11553, 11554, 11555, 11556, 11557, 11559, 11565, 4316, 5104, 5105, 5106, 5107, 5108, 5109, 6917, 6965, 6919, 6965, 6921, 6965, 6923, 6965, 6925, 6965, 6929, 6965, 6970, 6965, 6972, 6965, 6974, 6965, 6975, 6965, 6978, 6965, 42571, 65, 198, 66, 68, 69, 398, 71, 72, 73, 74, 75, 76, 77, 78, 79, 546, 80, 82, 84, 85, 87, 592, 593, 7426, 604, 7446, 7447, 7453, 7461, 594, 597, 607, 609, 613, 618, 7547, 669, 621, 7557, 671, 625, 624, 627, 628, 632, 642, 427, 7452, 656, 657, 65, 805, 7681, 97, 805, 66, 775, 7683, 98, 775, 66, 803, 7685, 98, 803, 66, 817, 7687, 98, 817, 199, 769, 7689, 231, 769, 68, 775, 7691, 100, 775, 68, 803, 7693, 100, 803, 68, 817, 7695, 100, 817, 68, 807, 7697, 100, 807, 68, 813, 7699, 100, 813, 274, 768, 7701, 275, 768, 274, 769, 7703, 275, 769, 69, 813, 7705, 101, 813, 69, 816, 7707, 101, 816, 552, 774, 7709, 553, 774, 70, 775, 7711, 102, 775, 71, 772, 7713, 103, 772, 72, 775, 7715, 104, 775, 72, 803, 7717, 104, 803, 72, 776, 7719, 104, 776, 72, 807, 7721, 104, 807, 72, 814, 7723, 104, 814, 73, 816, 7725, 105, 816, 207, 769, 7727, 239, 769, 75, 769, 7729, 107, 769, 75, 803, 7731, 107, 803, 75, 817, 7733, 107, 817, 76, 803, 7735, 108, 803, 7734, 772, 7737, 7735, 772, 76, 817, 7739, 108, 817, 76, 813, 7741, 108, 813, 77, 769, 7743, 109, 769, 77, 775, 7745, 109, 775, 77, 803, 7747, 109, 803, 78, 775, 7749, 110, 775, 78, 803, 7751, 110, 803, 78, 817, 7753, 110, 817, 78, 813, 7755, 110, 813, 213, 769, 7757, 245, 769, 213, 776, 7759, 245, 776, 332, 768, 7761, 333, 768, 332, 769, 7763, 333, 769, 80, 769, 7765, 112, 769, 80, 775, 7767, 112, 775, 82, 775, 7769, 114, 775, 82, 803, 7771, 114, 803, 7770, 772, 7773, 7771, 772, 82, 817, 7775, 114, 817, 83, 775, 7777, 115, 775, 83, 803, 7779, 115, 803, 346, 775, 7781, 347, 775, 352, 775, 7783, 353, 775, 7778, 775, 7785, 7779, 775, 84, 775, 7787, 116, 775, 84, 803, 7789, 116, 803, 84, 817, 7791, 116, 817, 84, 813, 7793, 116, 813, 85, 804, 7795, 117, 804, 85, 816, 7797, 117, 816, 85, 813, 7799, 117, 813, 360, 769, 7801, 361, 769, 362, 776, 7803, 363, 776, 86, 771, 7805, 118, 771, 86, 803, 7807, 118, 803, 87, 768, 7809, 119, 768, 87, 769, 7811, 119, 769, 87, 776, 7813, 119, 776, 87, 775, 7815, 119, 775, 87, 803, 7817, 119, 803, 88, 775, 7819, 120, 775, 88, 776, 7821, 120, 776, 89, 775, 7823, 121, 775, 90, 770, 7825, 122, 770, 90, 803, 7827, 122, 803, 90, 817, 7829, 122, 817, 104, 817, 116, 776, 119, 778, 121, 778, 97, 702, 383, 775, 223, 65, 803, 7841, 97, 803, 65, 777, 7843, 97, 777, 194, 769, 7845, 226, 769, 194, 768, 7847, 226, 768, 194, 777, 7849, 226, 777, 194, 771, 7851, 226, 771, 7840, 770, 7853, 7841, 770, 258, 769, 7855, 259, 769, 258, 768, 7857, 259, 768, 258, 777, 7859, 259, 777, 258, 771, 7861, 259, 771, 7840, 774, 7863, 7841, 774, 69, 803, 7865, 101, 803, 69, 777, 7867, 101, 777, 69, 771, 7869, 101, 771, 202, 769, 7871, 234, 769, 202, 768, 7873, 234, 768, 202, 777, 7875, 234, 777, 202, 771, 7877, 234, 771, 7864, 770, 7879, 7865, 770, 73, 777, 7881, 105, 777, 73, 803, 7883, 105, 803, 79, 803, 7885, 111, 803, 79, 777, 7887, 111, 777, 212, 769, 7889, 244, 769, 212, 768, 7891, 244, 768, 212, 777, 7893, 244, 777, 212, 771, 7895, 244, 771, 7884, 770, 7897, 7885, 770, 416, 769, 7899, 417, 769, 416, 768, 7901, 417, 768, 416, 777, 7903, 417, 777, 416, 771, 7905, 417, 771, 416, 803, 7907, 417, 803, 85, 803, 7909, 117, 803, 85, 777, 7911, 117, 777, 431, 769, 7913, 432, 769, 431, 768, 7915, 432, 768, 431, 777, 7917, 432, 777, 431, 771, 7919, 432, 771, 431, 803, 7921, 432, 803, 89, 768, 7923, 121, 768, 89, 803, 7925, 121, 803, 89, 777, 7927, 121, 777, 89, 771, 7929, 121, 771, 7931, 7933, 7935, 945, 787, 945, 788, 7936, 768, 7937, 768, 7936, 769, 7937, 769, 7936, 834, 7937, 834, 913, 787, 7936, 913, 788, 7937, 7944, 768, 7938, 7945, 768, 7939, 7944, 769, 7940, 7945, 769, 7941, 7944, 834, 7942, 7945, 834, 7943, 949, 787, 949, 788, 7952, 768, 7953, 768, 7952, 769, 7953, 769, 917, 787, 7952, 917, 788, 7953, 7960, 768, 7954, 7961, 768, 7955, 7960, 769, 7956, 7961, 769, 7957, 951, 787, 951, 788, 7968, 768, 7969, 768, 7968, 769, 7969, 769, 7968, 834, 7969, 834, 919, 787, 7968, 919, 788, 7969, 7976, 768, 7970, 7977, 768, 7971, 7976, 769, 7972, 7977, 769, 7973, 7976, 834, 7974, 7977, 834, 7975, 953, 787, 953, 788, 7984, 768, 7985, 768, 7984, 769, 7985, 769, 7984, 834, 7985, 834, 921, 787, 7984, 921, 788, 7985, 7992, 768, 7986, 7993, 768, 7987, 7992, 769, 7988, 7993, 769, 7989, 7992, 834, 7990, 7993, 834, 7991, 959, 787, 959, 788, 8000, 768, 8001, 768, 8000, 769, 8001, 769, 927, 787, 8000, 927, 788, 8001, 8008, 768, 8002, 8009, 768, 8003, 8008, 769, 8004, 8009, 769, 8005, 965, 787, 965, 788, 8016, 768, 965, 787, 768, 8017, 768, 8016, 769, 965, 787, 769, 8017, 769, 8016, 834, 965, 787, 834, 8017, 834, 933, 788, 8017, 8025, 768, 8019, 8025, 769, 8021, 8025, 834, 8023, 969, 787, 969, 788, 8032, 768, 8033, 768, 8032, 769, 8033, 769, 8032, 834, 8033, 834, 937, 787, 8032, 937, 788, 8033, 8040, 768, 8034, 8041, 768, 8035, 8040, 769, 8036, 8041, 769, 8037, 8040, 834, 8038, 8041, 834, 8039, 945, 768, 949, 768, 951, 768, 953, 768, 959, 768, 965, 768, 969, 768, 7936, 837, 7936, 953, 7937, 837, 7937, 953, 7938, 837, 7938, 953, 7939, 837, 7939, 953, 7940, 837, 7940, 953, 7941, 837, 7941, 953, 7942, 837, 7942, 953, 7943, 837, 7943, 953, 7944, 837, 8064, 7945, 837, 8065, 7946, 837, 8066, 7947, 837, 8067, 7948, 837, 8068, 7949, 837, 8069, 7950, 837, 8070, 7951, 837, 8071, 7968, 837, 7968, 953, 7969, 837, 7969, 953, 7970, 837, 7970, 953, 7971, 837, 7971, 953, 7972, 837, 7972, 953, 7973, 837, 7973, 953, 7974, 837, 7974, 953, 7975, 837, 7975, 953, 7976, 837, 8080, 7977, 837, 8081, 7978, 837, 8082, 7979, 837, 8083, 7980, 837, 8084, 7981, 837, 8085, 7982, 837, 8086, 7983, 837, 8087, 8032, 837, 8032, 953, 8033, 837, 8033, 953, 8034, 837, 8034, 953, 8035, 837, 8035, 953, 8036, 837, 8036, 953, 8037, 837, 8037, 953, 8038, 837, 8038, 953, 8039, 837, 8039, 953, 8040, 837, 8096, 8041, 837, 8097, 8042, 837, 8098, 8043, 837, 8099, 8044, 837, 8100, 8045, 837, 8101, 8046, 837, 8102, 8047, 837, 8103, 945, 774, 945, 772, 8048, 837, 8048, 953, 945, 837, 945, 953, 940, 837, 940, 953, 945, 834, 8118, 837, 945, 834, 953, 913, 774, 8112, 913, 772, 8113, 913, 768, 8048, 902, 8049, 913, 837, 8115, 32, 787, 32, 834, 168, 834, 8052, 837, 8052, 953, 951, 837, 951, 953, 942, 837, 942, 953, 951, 834, 8134, 837, 951, 834, 953, 917, 768, 8050, 904, 8051, 919, 768, 8052, 905, 8053, 919, 837, 8131, 8127, 768, 8127, 769, 8127, 834, 953, 774, 953, 772, 970, 768, 953, 776, 768, 912, 953, 834, 970, 834, 953, 776, 834, 921, 774, 8144, 921, 772, 8145, 921, 768, 8054, 906, 8055, 8190, 768, 8190, 769, 8190, 834, 965, 774, 965, 772, 971, 768, 965, 776, 768, 944, 961, 787, 961, 788, 965, 834, 971, 834, 965, 776, 834, 933, 774, 8160, 933, 772, 8161, 933, 768, 8058, 910, 8059, 929, 788, 8165, 168, 768, 901, 96, 8060, 837, 8060, 953, 969, 837, 969, 953, 974, 837, 974, 953, 969, 834, 8182, 837, 969, 834, 953, 927, 768, 8056, 908, 8057, 937, 768, 8060, 911, 8061, 937, 837, 8179, 180, 32, 788, 8194, 8195, 8208, 32, 819, 46, 46, 46, 46, 46, 46, 8242, 8242, 8242, 8242, 8242, 8245, 8245, 8245, 8245, 8245, 33, 33, 32, 773, 63, 63, 63, 33, 33, 63, 8242, 8242, 8242, 8242, 48, 52, 53, 54, 55, 56, 57, 43, 8722, 61, 40, 41, 82, 115, 97, 47, 99, 97, 47, 115, 67, 176, 67, 99, 47, 111, 99, 47, 117, 400, 176, 70, 78, 111, 81, 83, 77, 84, 69, 76, 84, 77, 90, 937, 197, 70, 8526, 1488, 1489, 1490, 1491, 70, 65, 88, 915, 928, 8721, 49, 8260, 55, 49, 8260, 57, 49, 8260, 49, 48, 49, 8260, 51, 50, 8260, 51, 49, 8260, 53, 50, 8260, 53, 51, 8260, 53, 52, 8260, 53, 49, 8260, 54, 53, 8260, 54, 49, 8260, 56, 51, 8260, 56, 53, 8260, 56, 55, 8260, 56, 49, 8260, 8560, 73, 73, 8561, 73, 73, 73, 8562, 73, 86, 8563, 86, 8564, 86, 73, 8565, 86, 73, 73, 8566, 86, 73, 73, 73, 8567, 73, 88, 8568, 88, 8569, 88, 73, 8570, 88, 73, 73, 8571, 8572, 8573, 8574, 8575, 105, 105, 105, 105, 105, 105, 118, 118, 105, 118, 105, 105, 118, 105, 105, 105, 105, 120, 120, 105, 120, 105, 105, 8580, 48, 8260, 51, 8592, 824, 8594, 824, 8596, 824, 8656, 824, 8660, 824, 8658, 824, 8707, 824, 8712, 824, 8715, 824, 8739, 824, 8741, 824, 8747, 8747, 8747, 8747, 8747, 8750, 8750, 8750, 8750, 8750, 8764, 824, 8771, 824, 8773, 824, 8776, 824, 61, 824, 8801, 824, 8781, 824, 60, 824, 62, 824, 8804, 824, 8805, 824, 8818, 824, 8819, 824, 8822, 824, 8823, 824, 8826, 824, 8827, 824, 8834, 824, 8835, 824, 8838, 824, 8839, 824, 8866, 824, 8872, 824, 8873, 824, 8875, 824, 8828, 824, 8829, 824, 8849, 824, 8850, 824, 8882, 824, 8883, 824, 8884, 824, 8885, 824, 12296, 12297, 49, 48, 49, 49, 49, 50, 49, 51, 49, 52, 49, 53, 49, 54, 49, 55, 49, 56, 49, 57, 50, 48, 40, 49, 41, 40, 50, 41, 40, 51, 41, 40, 52, 41, 40, 53, 41, 40, 54, 41, 40, 55, 41, 40, 56, 41, 40, 57, 41, 40, 49, 48, 41, 40, 49, 49, 41, 40, 49, 50, 41, 40, 49, 51, 41, 40, 49, 52, 41, 40, 49, 53, 41, 40, 49, 54, 41, 40, 49, 55, 41, 40, 49, 56, 41, 40, 49, 57, 41, 40, 50, 48, 41, 49, 46, 50, 46, 51, 46, 52, 46, 53, 46, 54, 46, 55, 46, 56, 46, 57, 46, 49, 48, 46, 49, 49, 46, 49, 50, 46, 49, 51, 46, 49, 52, 46, 49, 53, 46, 49, 54, 46, 49, 55, 46, 49, 56, 46, 49, 57, 46, 50, 48, 46, 40, 97, 41, 40, 98, 41, 40, 99, 41, 40, 100, 41, 40, 101, 41, 40, 102, 41, 40, 103, 41, 40, 104, 41, 40, 105, 41, 40, 106, 41, 40, 107, 41, 40, 108, 41, 40, 109, 41, 40, 110, 41, 40, 111, 41, 40, 112, 41, 40, 113, 41, 40, 114, 41, 40, 115, 41, 40, 116, 41, 40, 117, 41, 40, 118, 41, 40, 119, 41, 40, 120, 41, 40, 121, 41, 40, 122, 41, 9424, 9425, 9426, 9427, 9428, 9429, 9430, 9431, 9432, 9433, 9434, 9435, 9436, 9437, 9438, 9439, 9440, 9441, 83, 9442, 9443, 9444, 9445, 9446, 9447, 89, 9448, 9449, 8747, 8747, 8747, 8747, 58, 58, 61, 61, 61, 61, 61, 61, 10973, 824, 11312, 11313, 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, 11322, 11323, 11324, 11325, 11326, 11327, 11328, 11329, 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, 11338, 11339, 11340, 11341, 11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, 11354, 11355, 11356, 11357, 11358, 11361, 619, 7549, 637, 11368, 11370, 11372, 11379, 11382, 575, 576, 11393, 11395, 11397, 11399, 11401, 11403, 11405, 11407, 11409, 11411, 11413, 11415, 11417, 11419, 11421, 11423, 11425, 11427, 11429, 11431, 11433, 11435, 11437, 11439, 11441, 11443, 11445, 11447, 11449, 11451, 11453, 11455, 11457, 11459, 11461, 11463, 11465, 11467, 11469, 11471, 11473, 11475, 11477, 11479, 11481, 11483, 11485, 11487, 11489, 11491, 11500, 11502, 11507, 11617, 27597, 40863, 19968, 20008, 20022, 20031, 20057, 20101, 20108, 20128, 20154, 20799, 20837, 20843, 20866, 20886, 20907, 20960, 20981, 20992, 21147, 21241, 21269, 21274, 21304, 21313, 21340, 21353, 21378, 21430, 21448, 21475, 22231, 22303, 22763, 22786, 22794, 22805, 22823, 22899, 23376, 23424, 23544, 23567, 23586, 23608, 23662, 23665, 24027, 24037, 24049, 24062, 24178, 24186, 24191, 24308, 24318, 24331, 24339, 24400, 24417, 24435, 24515, 25096, 25142, 25163, 25903, 25908, 25991, 26007, 26020, 26041, 26080, 26085, 26352, 26376, 26408, 27424, 27490, 27513, 27571, 27595, 27604, 27611, 27663, 27668, 27700, 28779, 29226, 29238, 29243, 29247, 29255, 29273, 29275, 29356, 29572, 29577, 29916, 29926, 29976, 29983, 29992, 30000, 30091, 30098, 30326, 30333, 30382, 30399, 30446, 30683, 30690, 30707, 31034, 31160, 31166, 31348, 31435, 31481, 31859, 31992, 32566, 32593, 32650, 32701, 32769, 32780, 32786, 32819, 32895, 32905, 33251, 33258, 33267, 33276, 33292, 33307, 33311, 33390, 33394, 33400, 34381, 34411, 34880, 34892, 34915, 35198, 35211, 35282, 35328, 35895, 35910, 35925, 35960, 35997, 36196, 36208, 36275, 36523, 36554, 36763, 36784, 36789, 37009, 37193, 37318, 37324, 37329, 38263, 38272, 38428, 38582, 38585, 38632, 38737, 38750, 38754, 38761, 38859, 38893, 38899, 38913, 39080, 39131, 39135, 39318, 39321, 39340, 39592, 39640, 39647, 39717, 39727, 39730, 39740, 39770, 40165, 40565, 40575, 40613, 40635, 40643, 40653, 40657, 40697, 40701, 40718, 40723, 40736, 40763, 40778, 40786, 40845, 40860, 40864, 12306, 21316, 21317, 12363, 12441, 12365, 12441, 12367, 12441, 12369, 12441, 12371, 12441, 12373, 12441, 12375, 12441, 12377, 12441, 12379, 12441, 12381, 12441, 12383, 12441, 12385, 12441, 12388, 12441, 12390, 12441, 12392, 12441, 12399, 12441, 12399, 12442, 12402, 12441, 12402, 12442, 12405, 12441, 12405, 12442, 12408, 12441, 12408, 12442, 12411, 12441, 12411, 12442, 12358, 12441, 32, 12441, 32, 12442, 12445, 12441, 12424, 12426, 12459, 12441, 12461, 12441, 12463, 12441, 12465, 12441, 12467, 12441, 12469, 12441, 12471, 12441, 12473, 12441, 12475, 12441, 12477, 12441, 12479, 12441, 12481, 12441, 12484, 12441, 12486, 12441, 12488, 12441, 12495, 12441, 12495, 12442, 12498, 12441, 12498, 12442, 12501, 12441, 12501, 12442, 12504, 12441, 12504, 12442, 12507, 12441, 12507, 12442, 12454, 12441, 12527, 12441, 12528, 12441, 12529, 12441, 12530, 12441, 12541, 12441, 12467, 12488, 4352, 4353, 4522, 4354, 4524, 4525, 4355, 4356, 4357, 4528, 4529, 4530, 4531, 4532, 4533, 4378, 4358, 4359, 4360, 4385, 4361, 4362, 4363, 4364, 4365, 4366, 4367, 4368, 4369, 4370, 4449, 4450, 4451, 4452, 4453, 4454, 4455, 4456, 4457, 4458, 4459, 4460, 4461, 4462, 4463, 4464, 4465, 4466, 4467, 4468, 4469, 4448, 4372, 4373, 4551, 4552, 4556, 4558, 4563, 4567, 4569, 4380, 4573, 4575, 4381, 4382, 4384, 4386, 4387, 4391, 4393, 4395, 4396, 4397, 4398, 4399, 4402, 4406, 4416, 4423, 4428, 4593, 4594, 4439, 4440, 4441, 4484, 4485, 4488, 4497, 4498, 4500, 4510, 4513, 19977, 22235, 19978, 20013, 19979, 30002, 19993, 19969, 22825, 22320, 40, 4352, 41, 40, 4354, 41, 40, 4355, 41, 40, 4357, 41, 40, 4358, 41, 40, 4359, 41, 40, 4361, 41, 40, 4363, 41, 40, 4364, 41, 40, 4366, 41, 40, 4367, 41, 40, 4368, 41, 40, 4369, 41, 40, 4370, 41, 40, 4352, 4449, 41, 40, 4354, 4449, 41, 40, 4355, 4449, 41, 40, 4357, 4449, 41, 40, 4358, 4449, 41, 40, 4359, 4449, 41, 40, 4361, 4449, 41, 40, 4363, 4449, 41, 40, 4364, 4449, 41, 40, 4366, 4449, 41, 40, 4367, 4449, 41, 40, 4368, 4449, 41, 40, 4369, 4449, 41, 40, 4370, 4449, 41, 40, 4364, 4462, 41, 40, 4363, 4457, 4364, 4453, 4523, 41, 40, 4363, 4457, 4370, 4462, 41, 40, 19968, 41, 40, 20108, 41, 40, 19977, 41, 40, 22235, 41, 40, 20116, 41, 40, 20845, 41, 40, 19971, 41, 40, 20843, 41, 40, 20061, 41, 40, 21313, 41, 40, 26376, 41, 40, 28779, 41, 40, 27700, 41, 40, 26408, 41, 40, 37329, 41, 40, 22303, 41, 40, 26085, 41, 40, 26666, 41, 40, 26377, 41, 40, 31038, 41, 40, 21517, 41, 40, 29305, 41, 40, 36001, 41, 40, 31069, 41, 40, 21172, 41, 40, 20195, 41, 40, 21628, 41, 40, 23398, 41, 40, 30435, 41, 40, 20225, 41, 40, 36039, 41, 40, 21332, 41, 40, 31085, 41, 40, 20241, 41, 40, 33258, 41, 40, 33267, 41, 21839, 24188, 31631, 80, 84, 69, 50, 49, 50, 50, 50, 51, 50, 52, 50, 53, 50, 54, 50, 55, 50, 56, 50, 57, 51, 48, 51, 49, 51, 50, 51, 51, 51, 52, 51, 53, 4352, 4449, 4354, 4449, 4355, 4449, 4357, 4449, 4358, 4449, 4359, 4449, 4361, 4449, 4363, 4449, 4364, 4449, 4366, 4449, 4367, 4449, 4368, 4449, 4369, 4449, 4370, 4449, 4366, 4449, 4535, 4352, 4457, 4364, 4462, 4363, 4468, 4363, 4462, 20116, 20845, 19971, 20061, 26666, 26377, 31038, 21517, 29305, 36001, 31069, 21172, 31192, 30007, 36969, 20778, 21360, 27880, 38917, 20241, 20889, 27491, 24038, 21491, 21307, 23447, 23398, 30435, 20225, 36039, 21332, 22812, 51, 54, 51, 55, 51, 56, 51, 57, 52, 48, 52, 49, 52, 50, 52, 51, 52, 52, 52, 53, 52, 54, 52, 55, 52, 56, 52, 57, 53, 48, 49, 26376, 50, 26376, 51, 26376, 52, 26376, 53, 26376, 54, 26376, 55, 26376, 56, 26376, 57, 26376, 49, 48, 26376, 49, 49, 26376, 49, 50, 26376, 72, 103, 101, 114, 103, 101, 86, 76, 84, 68, 12450, 12452, 12454, 12456, 12458, 12459, 12461, 12463, 12465, 12467, 12469, 12471, 12473, 12475, 12477, 12479, 12481, 12484, 12486, 12488, 12490, 12491, 12492, 12493, 12494, 12495, 12498, 12501, 12504, 12507, 12510, 12511, 12512, 12513, 12514, 12516, 12518, 12520, 12521, 12522, 12523, 12524, 12525, 12527, 12528, 12529, 12530, 12450, 12497, 12540, 12488, 12450, 12523, 12501, 12449, 12450, 12531, 12506, 12450, 12450, 12540, 12523, 12452, 12491, 12531, 12464, 12452, 12531, 12481, 12454, 12457, 12531, 12456, 12473, 12463, 12540, 12489, 12456, 12540, 12459, 12540, 12458, 12531, 12473, 12458, 12540, 12512, 12459, 12452, 12522, 12459, 12521, 12483, 12488, 12459, 12525, 12522, 12540, 12460, 12525, 12531, 12460, 12531, 12510, 12462, 12460, 12462, 12491, 12540, 12461, 12517, 12522, 12540, 12462, 12523, 12480, 12540, 12461, 12525, 12461, 12525, 12464, 12521, 12512, 12461, 12525, 12513, 12540, 12488, 12523, 12461, 12525, 12527, 12483, 12488, 12464, 12521, 12512, 12464, 12521, 12512, 12488, 12531, 12463, 12523, 12476, 12452, 12525, 12463, 12525, 12540, 12493, 12465, 12540, 12473, 12467, 12523, 12490, 12467, 12540, 12509, 12469, 12452, 12463, 12523, 12469, 12531, 12481, 12540, 12512, 12471, 12522, 12531, 12464, 12475, 12531, 12481, 12475, 12531, 12488, 12480, 12540, 12473, 12487, 12471, 12489, 12523, 12488, 12531, 12490, 12494, 12494, 12483, 12488, 12495, 12452, 12484, 12497, 12540, 12475, 12531, 12488, 12497, 12540, 12484, 12496, 12540, 12524, 12523, 12500, 12450, 12473, 12488, 12523, 12500, 12463, 12523, 12500, 12467, 12499, 12523, 12501, 12449, 12521, 12483, 12489, 12501, 12451, 12540, 12488, 12502, 12483, 12471, 12455, 12523, 12501, 12521, 12531, 12504, 12463, 12479, 12540, 12523, 12506, 12477, 12506, 12491, 12498, 12504, 12523, 12484, 12506, 12531, 12473, 12506, 12540, 12472, 12505, 12540, 12479, 12509, 12452, 12531, 12488, 12508, 12523, 12488, 12507, 12531, 12509, 12531, 12489, 12507, 12540, 12523, 12507, 12540, 12531, 12510, 12452, 12463, 12525, 12510, 12452, 12523, 12510, 12483, 12495, 12510, 12523, 12463, 12510, 12531, 12471, 12519, 12531, 12511, 12463, 12525, 12531, 12511, 12522, 12511, 12522, 12496, 12540, 12523, 12513, 12460, 12513, 12460, 12488, 12531, 12513, 12540, 12488, 12523, 12516, 12540, 12489, 12516, 12540, 12523, 12518, 12450, 12531, 12522, 12483, 12488, 12523, 12522, 12521, 12523, 12500, 12540, 12523, 12540, 12502, 12523, 12524, 12512, 12524, 12531, 12488, 12466, 12531, 12527, 12483, 12488, 48, 28857, 49, 28857, 50, 28857, 51, 28857, 52, 28857, 53, 28857, 54, 28857, 55, 28857, 56, 28857, 57, 28857, 49, 48, 28857, 49, 49, 28857, 49, 50, 28857, 49, 51, 28857, 49, 52, 28857, 49, 53, 28857, 49, 54, 28857, 49, 55, 28857, 49, 56, 28857, 49, 57, 28857, 50, 48, 28857, 50, 49, 28857, 50, 50, 28857, 50, 51, 28857, 50, 52, 28857, 104, 80, 97, 100, 97, 65, 85, 98, 97, 114, 111, 86, 112, 99, 100, 109, 100, 109, 178, 100, 109, 179, 73, 85, 24179, 25104, 26157, 21644, 22823, 27491, 26126, 27835, 26666, 24335, 20250, 31038, 112, 65, 110, 65, 956, 65, 109, 65, 107, 65, 75, 66, 77, 66, 71, 66, 99, 97, 108, 107, 99, 97, 108, 112, 70, 110, 70, 956, 70, 956, 103, 109, 103, 107, 103, 72, 122, 107, 72, 122, 77, 72, 122, 71, 72, 122, 84, 72, 122, 956, 8467, 109, 8467, 100, 8467, 107, 8467, 102, 109, 110, 109, 956, 109, 109, 109, 99, 109, 107, 109, 109, 109, 178, 99, 109, 178, 109, 178, 107, 109, 178, 109, 109, 179, 99, 109, 179, 109, 179, 107, 109, 179, 109, 8725, 115, 109, 8725, 115, 178, 80, 97, 107, 80, 97, 77, 80, 97, 71, 80, 97, 114, 97, 100, 114, 97, 100, 8725, 115, 114, 97, 100, 8725, 115, 178, 112, 115, 110, 115, 956, 115, 109, 115, 112, 86, 110, 86, 956, 86, 109, 86, 107, 86, 77, 86, 112, 87, 110, 87, 956, 87, 109, 87, 107, 87, 77, 87, 107, 937, 77, 937, 97, 46, 109, 46, 66, 113, 99, 99, 99, 100, 67, 8725, 107, 103, 67, 111, 46, 100, 66, 71, 121, 104, 97, 72, 80, 105, 110, 75, 75, 75, 77, 107, 116, 108, 109, 108, 110, 108, 111, 103, 108, 120, 109, 98, 109, 105, 108, 109, 111, 108, 80, 72, 112, 46, 109, 46, 80, 80, 77, 80, 82, 115, 114, 83, 118, 87, 98, 86, 8725, 109, 65, 8725, 109, 49, 26085, 50, 26085, 51, 26085, 52, 26085, 53, 26085, 54, 26085, 55, 26085, 56, 26085, 57, 26085, 49, 48, 26085, 49, 49, 26085, 49, 50, 26085, 49, 51, 26085, 49, 52, 26085, 49, 53, 26085, 49, 54, 26085, 49, 55, 26085, 49, 56, 26085, 49, 57, 26085, 50, 48, 26085, 50, 49, 26085, 50, 50, 26085, 50, 51, 26085, 50, 52, 26085, 50, 53, 26085, 50, 54, 26085, 50, 55, 26085, 50, 56, 26085, 50, 57, 26085, 51, 48, 26085, 51, 49, 26085, 103, 97, 108, 42561, 42563, 42565, 42567, 42569, 42573, 42575, 42577, 42579, 42581, 42583, 42585, 42587, 42589, 42591, 42593, 42595, 42597, 42599, 42601, 42603, 42605, 42625, 42627, 42629, 42631, 42633, 42635, 42637, 42639, 42641, 42643, 42645, 42647, 42649, 42651, 42787, 42789, 42791, 42793, 42795, 42797, 42799, 42803, 42805, 42807, 42809, 42811, 42813, 42815, 42817, 42819, 42821, 42823, 42825, 42827, 42829, 42831, 42833, 42835, 42837, 42839, 42841, 42843, 42845, 42847, 42849, 42851, 42853, 42855, 42857, 42859, 42861, 42863, 42874, 42876, 7545, 42879, 42881, 42883, 42885, 42887, 42892, 42897, 42899, 42903, 42905, 42907, 42909, 42911, 42913, 42915, 42917, 42919, 42921, 620, 670, 647, 43859, 42933, 42935, 294, 43831, 43858, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5057, 5058, 5059, 5060, 5061, 5062, 5063, 5064, 5065, 5066, 5067, 5068, 5069, 5070, 5071, 5072, 5073, 5074, 5075, 5076, 5077, 5078, 5079, 5080, 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 5089, 5090, 5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, 5099, 5100, 5101, 5102, 5103, 35912, 26356, 36040, 28369, 20018, 21477, 22865, 21895, 22856, 25078, 30313, 32645, 34367, 34746, 35064, 37007, 27138, 27931, 28889, 29662, 33853, 37226, 39409, 20098, 21365, 27396, 29211, 34349, 40478, 23888, 28651, 34253, 35172, 25289, 33240, 34847, 24266, 26391, 28010, 29436, 37070, 20358, 20919, 21214, 25796, 27347, 29200, 30439, 34310, 34396, 36335, 38706, 39791, 40442, 30860, 31103, 32160, 33737, 37636, 35542, 22751, 24324, 31840, 32894, 29282, 30922, 36034, 38647, 22744, 23650, 27155, 28122, 28431, 32047, 32311, 38475, 21202, 32907, 20956, 20940, 31260, 32190, 33777, 38517, 35712, 25295, 35582, 20025, 23527, 24594, 29575, 30064, 21271, 30971, 20415, 24489, 19981, 27852, 25976, 32034, 21443, 22622, 30465, 33865, 35498, 27578, 27784, 25342, 33509, 25504, 30053, 20142, 20841, 20937, 26753, 31975, 33391, 35538, 37327, 21237, 21570, 24300, 26053, 28670, 31018, 38317, 39530, 40599, 40654, 26310, 27511, 36706, 24180, 24976, 25088, 25754, 28451, 29001, 29833, 31178, 32244, 32879, 36646, 34030, 36899, 37706, 21015, 21155, 21693, 28872, 35010, 24265, 24565, 25467, 27566, 31806, 29557, 20196, 22265, 23994, 24604, 29618, 29801, 32666, 32838, 37428, 38646, 38728, 38936, 20363, 31150, 37300, 38584, 24801, 20102, 20698, 23534, 23615, 26009, 29134, 30274, 34044, 36988, 26248, 38446, 21129, 26491, 26611, 27969, 28316, 29705, 30041, 30827, 32016, 39006, 25134, 38520, 20523, 23833, 28138, 36650, 24459, 24900, 26647, 38534, 21033, 21519, 23653, 26131, 26446, 26792, 27877, 29702, 30178, 32633, 35023, 35041, 38626, 21311, 28346, 21533, 29136, 29848, 34298, 38563, 40023, 40607, 26519, 28107, 33256, 31520, 31890, 29376, 28825, 35672, 20160, 33590, 21050, 20999, 24230, 25299, 31958, 23429, 27934, 26292, 36667, 38477, 24275, 20800, 21952, 22618, 26228, 20958, 29482, 30410, 31036, 31070, 31077, 31119, 38742, 31934, 34322, 35576, 36920, 37117, 39151, 39164, 39208, 40372, 37086, 38583, 20398, 20711, 20813, 21193, 21220, 21329, 21917, 22022, 22120, 22592, 22696, 23652, 24724, 24936, 24974, 25074, 25935, 26082, 26257, 26757, 28023, 28186, 28450, 29038, 29227, 29730, 30865, 31049, 31048, 31056, 31062, 31117, 31118, 31296, 31361, 31680, 32265, 32321, 32626, 32773, 33261, 33401, 33879, 35088, 35222, 35585, 35641, 36051, 36104, 36790, 38627, 38911, 38971, 24693, 55376, 57070, 33304, 20006, 20917, 20840, 20352, 20805, 20864, 21191, 21242, 21845, 21913, 21986, 22707, 22852, 22868, 23138, 23336, 24274, 24281, 24425, 24493, 24792, 24910, 24840, 24928, 25140, 25540, 25628, 25682, 25942, 26395, 26454, 28379, 28363, 28702, 30631, 29237, 29359, 29809, 29958, 30011, 30237, 30239, 30427, 30452, 30538, 30528, 30924, 31409, 31867, 32091, 32574, 33618, 33775, 34681, 35137, 35206, 35519, 35531, 35565, 35722, 36664, 36978, 37273, 37494, 38524, 38875, 38923, 39698, 55370, 56394, 55370, 56388, 55372, 57301, 15261, 16408, 16441, 55380, 56905, 55383, 56528, 55391, 57043, 40771, 40846, 102, 102, 102, 105, 102, 108, 102, 102, 105, 102, 102, 108, 383, 116, 115, 116, 1396, 1398, 1396, 1381, 1396, 1387, 1406, 1398, 1396, 1389, 1497, 1460, 1522, 1463, 1506, 1492, 1499, 1500, 1501, 1512, 1514, 1513, 1473, 1513, 1474, 64329, 1473, 64329, 1474, 1488, 1463, 1488, 1464, 1488, 1468, 1489, 1468, 1490, 1468, 1491, 1468, 1492, 1468, 1493, 1468, 1494, 1468, 1496, 1468, 1497, 1468, 1498, 1468, 1499, 1468, 1500, 1468, 1502, 1468, 1504, 1468, 1505, 1468, 1507, 1468, 1508, 1468, 1510, 1468, 1511, 1468, 1512, 1468, 1513, 1468, 1514, 1468, 1493, 1465, 1489, 1471, 1499, 1471, 1508, 1471, 1488, 1500, 1649, 1659, 1662, 1664, 1658, 1663, 1657, 1700, 1702, 1668, 1667, 1670, 1671, 1677, 1676, 1678, 1672, 1688, 1681, 1705, 1711, 1715, 1713, 1722, 1723, 1728, 1729, 1726, 1746, 1747, 1709, 1735, 1734, 1736, 1655, 1739, 1733, 1737, 1744, 1609, 1574, 1575, 1574, 1749, 1574, 1608, 1574, 1735, 1574, 1734, 1574, 1736, 1574, 1744, 1574, 1609, 1740, 1574, 1580, 1574, 1581, 1574, 1605, 1574, 1610, 1576, 1580, 1576, 1581, 1576, 1582, 1576, 1605, 1576, 1609, 1576, 1610, 1578, 1580, 1578, 1581, 1578, 1582, 1578, 1605, 1578, 1609, 1578, 1610, 1579, 1580, 1579, 1605, 1579, 1609, 1579, 1610, 1580, 1581, 1580, 1605, 1581, 1580, 1581, 1605, 1582, 1580, 1582, 1581, 1582, 1605, 1587, 1580, 1587, 1581, 1587, 1582, 1587, 1605, 1589, 1581, 1589, 1605, 1590, 1580, 1590, 1581, 1590, 1582, 1590, 1605, 1591, 1581, 1591, 1605, 1592, 1605, 1593, 1580, 1593, 1605, 1594, 1580, 1594, 1605, 1601, 1580, 1601, 1581, 1601, 1582, 1601, 1605, 1601, 1609, 1601, 1610, 1602, 1581, 1602, 1605, 1602, 1609, 1602, 1610, 1603, 1575, 1603, 1580, 1603, 1581, 1603, 1582, 1603, 1604, 1603, 1605, 1603, 1609, 1603, 1610, 1604, 1580, 1604, 1581, 1604, 1582, 1604, 1605, 1604, 1609, 1604, 1610, 1605, 1580, 1605, 1581, 1605, 1582, 1605, 1605, 1605, 1609, 1605, 1610, 1606, 1580, 1606, 1581, 1606, 1582, 1606, 1605, 1606, 1609, 1606, 1610, 1607, 1580, 1607, 1605, 1607, 1609, 1607, 1610, 1610, 1580, 1610, 1581, 1610, 1582, 1610, 1605, 1610, 1609, 1610, 1610, 1584, 1648, 1585, 1648, 1609, 1648, 32, 1612, 1617, 32, 1613, 1617, 32, 1614, 1617, 32, 1615, 1617, 32, 1616, 1617, 32, 1617, 1648, 1574, 1585, 1574, 1586, 1574, 1606, 1576, 1585, 1576, 1586, 1576, 1606, 1578, 1585, 1578, 1586, 1578, 1606, 1579, 1585, 1579, 1586, 1579, 1606, 1605, 1575, 1606, 1585, 1606, 1586, 1606, 1606, 1610, 1585, 1610, 1586, 1610, 1606, 1574, 1582, 1574, 1607, 1576, 1607, 1578, 1607, 1589, 1582, 1604, 1607, 1606, 1607, 1607, 1648, 1610, 1607, 1579, 1607, 1587, 1607, 1588, 1605, 1588, 1607, 1600, 1614, 1617, 1600, 1615, 1617, 1600, 1616, 1617, 1591, 1609, 1591, 1610, 1593, 1609, 1593, 1610, 1594, 1609, 1594, 1610, 1587, 1609, 1587, 1610, 1588, 1609, 1588, 1610, 1581, 1609, 1581, 1610, 1580, 1609, 1580, 1610, 1582, 1609, 1582, 1610, 1589, 1609, 1589, 1610, 1590, 1609, 1590, 1610, 1588, 1580, 1588, 1581, 1588, 1582, 1588, 1585, 1587, 1585, 1589, 1585, 1590, 1585, 1575, 1611, 1578, 1580, 1605, 1578, 1581, 1580, 1578, 1581, 1605, 1578, 1582, 1605, 1578, 1605, 1580, 1578, 1605, 1581, 1578, 1605, 1582, 1580, 1605, 1581, 1581, 1605, 1610, 1581, 1605, 1609, 1587, 1581, 1580, 1587, 1580, 1581, 1587, 1580, 1609, 1587, 1605, 1581, 1587, 1605, 1580, 1587, 1605, 1605, 1589, 1581, 1581, 1589, 1605, 1605, 1588, 1581, 1605, 1588, 1580, 1610, 1588, 1605, 1582, 1588, 1605, 1605, 1590, 1581, 1609, 1590, 1582, 1605, 1591, 1605, 1581, 1591, 1605, 1605, 1591, 1605, 1610, 1593, 1580, 1605, 1593, 1605, 1605, 1593, 1605, 1609, 1594, 1605, 1605, 1594, 1605, 1610, 1594, 1605, 1609, 1601, 1582, 1605, 1602, 1605, 1581, 1602, 1605, 1605, 1604, 1581, 1605, 1604, 1581, 1610, 1604, 1581, 1609, 1604, 1580, 1580, 1604, 1582, 1605, 1604, 1605, 1581, 1605, 1581, 1580, 1605, 1581, 1605, 1605, 1581, 1610, 1605, 1580, 1581, 1605, 1580, 1605, 1605, 1582, 1580, 1605, 1582, 1605, 1605, 1580, 1582, 1607, 1605, 1580, 1607, 1605, 1605, 1606, 1581, 1605, 1606, 1581, 1609, 1606, 1580, 1605, 1606, 1580, 1609, 1606, 1605, 1610, 1606, 1605, 1609, 1610, 1605, 1605, 1576, 1582, 1610, 1578, 1580, 1610, 1578, 1580, 1609, 1578, 1582, 1610, 1578, 1582, 1609, 1578, 1605, 1610, 1578, 1605, 1609, 1580, 1605, 1610, 1580, 1581, 1609, 1580, 1605, 1609, 1587, 1582, 1609, 1589, 1581, 1610, 1588, 1581, 1610, 1590, 1581, 1610, 1604, 1580, 1610, 1604, 1605, 1610, 1610, 1581, 1610, 1610, 1580, 1610, 1610, 1605, 1610, 1605, 1605, 1610, 1602, 1605, 1610, 1606, 1581, 1610, 1593, 1605, 1610, 1603, 1605, 1610, 1606, 1580, 1581, 1605, 1582, 1610, 1604, 1580, 1605, 1603, 1605, 1605, 1580, 1581, 1610, 1581, 1580, 1610, 1605, 1580, 1610, 1601, 1605, 1610, 1576, 1581, 1610, 1587, 1582, 1610, 1606, 1580, 1610, 1589, 1604, 1746, 1602, 1604, 1746, 1575, 1604, 1604, 1607, 1575, 1603, 1576, 1585, 1605, 1581, 1605, 1583, 1589, 1604, 1593, 1605, 1585, 1587, 1608, 1604, 1593, 1604, 1610, 1607, 1608, 1587, 1604, 1605, 1589, 1604, 1609, 17, 1589, 1604, 1609, 32, 1575, 1604, 1604, 1607, 32, 1593, 1604, 1610, 1607, 32, 1608, 1587, 1604, 1605, 7, 1580, 1604, 32, 1580, 1604, 1575, 1604, 1607, 1585, 1740, 1575, 1604, 44, 12289, 12290, 58, 33, 63, 12310, 12311, 8230, 8229, 8212, 8211, 95, 123, 125, 12308, 12309, 12304, 12305, 12298, 12299, 12300, 12301, 12302, 12303, 91, 93, 8254, 35, 38, 42, 45, 60, 62, 92, 36, 37, 64, 32, 1611, 1600, 1611, 32, 1612, 32, 1613, 32, 1614, 1600, 1614, 32, 1615, 1600, 1615, 32, 1616, 1600, 1616, 32, 1617, 1600, 1617, 32, 1618, 1600, 1618, 1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1594, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1610, 1604, 1570, 1604, 1571, 1604, 1573, 1604, 1575, 34, 39, 47, 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, 65369, 65370, 94, 124, 126, 10629, 10630, 12539, 12449, 12451, 12453, 12455, 12457, 12515, 12517, 12519, 12483, 12540, 12531, 12441, 12442, 12644, 12593, 12594, 12595, 12596, 12597, 12598, 12599, 12600, 12601, 12602, 12603, 12604, 12605, 12606, 12607, 12608, 12609, 12610, 12611, 12612, 12613, 12614, 12615, 12616, 12617, 12618, 12619, 12620, 12621, 12622, 12623, 12624, 12625, 12626, 12627, 12628, 12629, 12630, 12631, 12632, 12633, 12634, 12635, 12636, 12637, 12638, 12639, 12640, 12641, 12642, 12643, 162, 163, 172, 175, 166, 165, 8361, 9474, 8592, 8593, 8594, 8595, 9632, 9675, 55297, 56360, 55297, 56361, 55297, 56362, 55297, 56363, 55297, 56364, 55297, 56365, 55297, 56366, 55297, 56367, 55297, 56368, 55297, 56369, 55297, 56370, 55297, 56371, 55297, 56372, 55297, 56373, 55297, 56374, 55297, 56375, 55297, 56376, 55297, 56377, 55297, 56378, 55297, 56379, 55297, 56380, 55297, 56381, 55297, 56382, 55297, 56383, 55297, 56384, 55297, 56385, 55297, 56386, 55297, 56387, 55297, 56388, 55297, 56389, 55297, 56390, 55297, 56391, 55297, 56392, 55297, 56393, 55297, 56394, 55297, 56395, 55297, 56396, 55297, 56397, 55297, 56398, 55297, 56399, 55297, 56536, 55297, 56537, 55297, 56538, 55297, 56539, 55297, 56540, 55297, 56541, 55297, 56542, 55297, 56543, 55297, 56544, 55297, 56545, 55297, 56546, 55297, 56547, 55297, 56548, 55297, 56549, 55297, 56550, 55297, 56551, 55297, 56552, 55297, 56553, 55297, 56554, 55297, 56555, 55297, 56556, 55297, 56557, 55297, 56558, 55297, 56559, 55297, 56560, 55297, 56561, 55297, 56562, 55297, 56563, 55297, 56564, 55297, 56565, 55297, 56566, 55297, 56567, 55297, 56568, 55297, 56569, 55297, 56570, 55297, 56571, 55299, 56512, 55299, 56513, 55299, 56514, 55299, 56515, 55299, 56516, 55299, 56517, 55299, 56518, 55299, 56519, 55299, 56520, 55299, 56521, 55299, 56522, 55299, 56523, 55299, 56524, 55299, 56525, 55299, 56526, 55299, 56527, 55299, 56528, 55299, 56529, 55299, 56530, 55299, 56531, 55299, 56532, 55299, 56533, 55299, 56534, 55299, 56535, 55299, 56536, 55299, 56537, 55299, 56538, 55299, 56539, 55299, 56540, 55299, 56541, 55299, 56542, 55299, 56543, 55299, 56544, 55299, 56545, 55299, 56546, 55299, 56547, 55299, 56548, 55299, 56549, 55299, 56550, 55299, 56551, 55299, 56552, 55299, 56553, 55299, 56554, 55299, 56555, 55299, 56556, 55299, 56557, 55299, 56558, 55299, 56559, 55299, 56560, 55299, 56561, 55299, 56562, 55300, 56473, 55300, 56506, 55300, 56475, 55300, 56506, 55300, 56485, 55300, 56506, 55300, 56625, 55300, 56615, 55300, 56626, 55300, 56615, 55300, 57159, 55300, 57150, 55300, 57159, 55300, 57175, 55301, 56505, 55301, 56506, 55301, 56505, 55301, 56496, 55301, 56505, 55301, 56509, 55301, 56760, 55301, 56751, 55301, 56761, 55301, 56751, 55302, 56512, 55302, 56513, 55302, 56514, 55302, 56515, 55302, 56516, 55302, 56517, 55302, 56518, 55302, 56519, 55302, 56520, 55302, 56521, 55302, 56522, 55302, 56523, 55302, 56524, 55302, 56525, 55302, 56526, 55302, 56527, 55302, 56528, 55302, 56529, 55302, 56530, 55302, 56531, 55302, 56532, 55302, 56533, 55302, 56534, 55302, 56535, 55302, 56536, 55302, 56537, 55302, 56538, 55302, 56539, 55302, 56540, 55302, 56541, 55302, 56542, 55302, 56543, 55348, 56663, 55348, 56677, 55348, 56664, 55348, 56677, 55348, 56671, 55348, 56686, 55348, 56671, 55348, 56687, 55348, 56671, 55348, 56688, 55348, 56671, 55348, 56689, 55348, 56671, 55348, 56690, 55348, 56761, 55348, 56677, 55348, 56762, 55348, 56677, 55348, 56763, 55348, 56686, 55348, 56764, 55348, 56686, 55348, 56763, 55348, 56687, 55348, 56764, 55348, 56687, 305, 567, 913, 914, 916, 917, 918, 919, 921, 922, 923, 924, 925, 926, 927, 929, 1012, 932, 934, 935, 936, 8711, 8706, 1013, 977, 1008, 981, 1009, 982, 988, 55354, 56610, 55354, 56611, 55354, 56612, 55354, 56613, 55354, 56614, 55354, 56615, 55354, 56616, 55354, 56617, 55354, 56618, 55354, 56619, 55354, 56620, 55354, 56621, 55354, 56622, 55354, 56623, 55354, 56624, 55354, 56625, 55354, 56626, 55354, 56627, 55354, 56628, 55354, 56629, 55354, 56630, 55354, 56631, 55354, 56632, 55354, 56633, 55354, 56634, 55354, 56635, 55354, 56636, 55354, 56637, 55354, 56638, 55354, 56639, 55354, 56640, 55354, 56641, 55354, 56642, 55354, 56643, 1646, 1697, 1647, 48, 46, 48, 44, 49, 44, 50, 44, 51, 44, 52, 44, 53, 44, 54, 44, 55, 44, 56, 44, 57, 44, 40, 65, 41, 40, 66, 41, 40, 67, 41, 40, 68, 41, 40, 69, 41, 40, 70, 41, 40, 71, 41, 40, 72, 41, 40, 73, 41, 40, 74, 41, 40, 75, 41, 40, 76, 41, 40, 77, 41, 40, 78, 41, 40, 79, 41, 40, 80, 41, 40, 81, 41, 40, 82, 41, 40, 83, 41, 40, 84, 41, 40, 85, 41, 40, 86, 41, 40, 87, 41, 40, 88, 41, 40, 89, 41, 40, 90, 41, 12308, 83, 12309, 67, 68, 87, 90, 72, 86, 83, 68, 83, 83, 80, 80, 86, 87, 67, 77, 67, 77, 68, 68, 74, 12411, 12363, 12467, 12467, 23383, 21452, 12487, 22810, 35299, 20132, 26144, 28961, 21069, 24460, 20877, 26032, 21021, 32066, 36009, 22768, 21561, 28436, 25237, 25429, 36938, 25351, 25171, 31105, 31354, 21512, 28288, 30003, 21106, 21942, 37197, 12308, 26412, 12309, 12308, 19977, 12309, 12308, 20108, 12309, 12308, 23433, 12309, 12308, 28857, 12309, 12308, 25171, 12309, 12308, 30423, 12309, 12308, 21213, 12309, 12308, 25943, 12309, 24471, 21487, 20029, 20024, 20033, 55360, 56610, 20320, 20411, 20482, 20602, 20633, 20687, 13470, 55361, 56890, 20820, 20836, 20855, 55361, 56604, 13497, 20839, 55361, 56651, 20887, 20900, 20172, 20908, 55396, 56799, 20995, 13535, 21051, 21062, 21111, 13589, 21253, 21254, 21321, 21338, 21363, 21373, 21375, 55362, 56876, 28784, 21450, 21471, 55362, 57187, 21483, 21489, 21510, 21662, 21560, 21576, 21608, 21666, 21750, 21776, 21843, 21859, 21892, 21931, 21939, 21954, 22294, 22295, 22097, 22132, 22766, 22478, 22516, 22541, 22411, 22578, 22577, 22700, 55365, 56548, 22770, 22775, 22790, 22818, 22882, 55365, 57000, 55365, 57066, 23020, 23067, 23079, 23000, 23142, 14062, 14076, 23304, 23358, 55366, 56776, 23491, 23512, 23539, 55366, 57112, 23551, 23558, 24403, 14209, 23648, 23744, 23693, 55367, 56804, 23875, 55367, 56806, 23918, 23915, 23932, 24033, 24034, 14383, 24061, 24104, 24125, 24169, 14434, 55368, 56707, 14460, 24240, 24243, 24246, 55400, 57234, 55368, 57137, 33281, 24354, 14535, 55372, 57016, 55384, 56794, 24418, 24427, 14563, 24474, 24525, 24535, 24569, 24705, 14650, 14620, 55369, 57044, 24775, 24904, 24908, 24954, 25010, 24996, 25007, 25054, 25104, 25115, 25181, 25265, 25300, 25424, 55370, 57100, 25405, 25340, 25448, 25475, 25572, 55370, 57329, 25634, 25541, 25513, 14894, 25705, 25726, 25757, 25719, 14956, 25964, 55372, 56330, 26083, 26360, 26185, 15129, 15112, 15076, 20882, 20885, 26368, 26268, 32941, 17369, 26401, 26462, 26451, 55372, 57283, 15177, 26618, 26501, 26706, 55373, 56429, 26766, 26655, 26900, 26946, 27043, 27114, 27304, 55373, 56995, 27355, 15384, 27425, 55374, 56487, 27476, 15438, 27506, 27551, 27579, 55374, 56973, 55367, 56587, 55374, 57082, 27726, 55375, 56508, 27839, 27853, 27751, 27926, 27966, 28009, 28024, 28037, 55375, 56606, 27956, 28207, 28270, 15667, 28359, 55375, 57041, 28153, 28526, 55375, 57182, 55375, 57230, 28614, 28729, 28699, 15766, 28746, 28797, 28791, 28845, 55361, 56613, 28997, 55376, 56931, 29084, 55376, 57259, 29224, 29264, 55377, 56840, 29312, 29333, 55377, 57141, 55378, 56340, 29562, 29579, 16044, 29605, 16056, 29767, 29788, 29829, 29898, 16155, 29988, 55379, 56374, 30014, 55379, 56466, 55368, 56735, 30224, 55379, 57249, 55379, 57272, 55380, 56388, 16380, 16392, 55380, 56563, 55380, 56562, 55380, 56601, 55380, 56627, 30494, 30495, 30603, 16454, 16534, 55381, 56349, 30798, 16611, 55381, 56870, 55381, 56986, 55381, 57029, 31211, 16687, 31306, 31311, 55382, 56700, 55382, 56999, 31470, 16898, 55382, 57259, 31686, 31689, 16935, 55383, 56448, 31954, 17056, 31976, 31971, 32000, 55383, 57222, 32099, 17153, 32199, 32258, 32325, 17204, 55384, 56872, 55384, 56903, 17241, 55384, 57049, 32634, 55384, 57150, 32661, 32762, 55385, 56538, 55385, 56611, 32864, 55385, 56744, 32880, 55372, 57183, 17365, 32946, 33027, 17419, 33086, 23221, 55385, 57255, 55385, 57269, 55372, 57235, 55372, 57244, 33284, 36766, 17515, 33425, 33419, 33437, 21171, 33457, 33459, 33469, 33510, 55386, 57148, 33565, 33635, 33709, 33571, 33725, 33767, 33619, 33738, 33740, 33756, 55387, 56374, 55387, 56683, 55387, 56533, 17707, 34033, 34035, 34070, 55388, 57290, 34148, 55387, 57132, 17757, 17761, 55387, 57265, 55388, 56530, 17771, 34384, 34407, 34409, 34473, 34440, 34574, 34530, 34600, 34667, 34694, 17879, 34785, 34817, 17913, 34912, 55389, 56935, 35031, 35038, 17973, 35066, 13499, 55390, 56494, 55390, 56678, 18110, 18119, 35488, 55391, 56488, 36011, 36033, 36123, 36215, 55391, 57135, 55362, 56324, 36299, 36284, 36336, 55362, 56542, 36564, 55393, 56786, 55393, 56813, 37012, 37105, 37137, 55393, 57134, 37147, 37432, 37591, 37592, 37500, 37881, 37909, 55394, 57338, 38283, 18837, 38327, 55395, 56695, 18918, 38595, 23986, 38691, 55396, 56645, 55396, 56858, 19054, 19062, 38880, 55397, 56330, 19122, 55397, 56470, 38953, 55397, 56758, 39138, 19251, 39209, 39335, 39362, 39422, 19406, 55398, 57136, 40000, 40189, 19662, 19693, 40295, 55400, 56526, 19704, 55400, 56581, 55400, 56846, 55400, 56977, 19798, 40702, 40709, 40719, 40726, 55401, 56832, 192, 193, 194, 195, 196, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 216, 217, 218, 219, 220, 221, 222, 376, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286, 288, 290, 292, 296, 298, 300, 302, 306, 308, 310, 313, 315, 317, 319, 321, 323, 325, 327, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 377, 379, 381, 579, 386, 388, 391, 395, 401, 502, 408, 573, 544, 416, 418, 420, 423, 428, 431, 435, 437, 440, 444, 503, 453, 452, 456, 455, 459, 458, 461, 463, 465, 467, 469, 471, 473, 475, 478, 480, 482, 484, 486, 488, 490, 492, 494, 498, 497, 500, 504, 506, 508, 510, 512, 514, 516, 518, 520, 522, 524, 526, 528, 530, 532, 534, 536, 538, 540, 542, 548, 550, 552, 554, 556, 558, 560, 562, 571, 11390, 11391, 577, 582, 584, 586, 588, 590, 11375, 11373, 11376, 385, 390, 393, 394, 399, 42923, 403, 42924, 404, 42893, 42922, 407, 406, 42926, 11362, 42925, 412, 11374, 413, 415, 11364, 422, 425, 42929, 430, 580, 433, 434, 581, 439, 42930, 42928, 880, 882, 886, 1021, 1022, 1023, 938, 939, 975, 984, 986, 990, 992, 994, 996, 998, 1000, 1002, 1004, 1006, 1017, 895, 1015, 1018, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1120, 1122, 1124, 1126, 1128, 1130, 1132, 1134, 1136, 1138, 1140, 1142, 1144, 1146, 1148, 1150, 1152, 1162, 1164, 1166, 1168, 1170, 1172, 1174, 1176, 1178, 1180, 1182, 1184, 1186, 1188, 1190, 1192, 1194, 1196, 1198, 1200, 1202, 1204, 1206, 1208, 1210, 1212, 1214, 1217, 1219, 1221, 1223, 1225, 1227, 1229, 1216, 1232, 1234, 1236, 1238, 1240, 1242, 1244, 1246, 1248, 1250, 1252, 1254, 1256, 1258, 1260, 1262, 1264, 1266, 1268, 1270, 1272, 1274, 1276, 1278, 1280, 1282, 1284, 1286, 1288, 1290, 1292, 1294, 1296, 1298, 1300, 1302, 1304, 1306, 1308, 1310, 1312, 1314, 1316, 1318, 1320, 1322, 1324, 1326, 1329, 1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 43888, 43889, 43890, 43891, 43892, 43893, 43894, 43895, 43896, 43897, 43898, 43899, 43900, 43901, 43902, 43903, 43904, 43905, 43906, 43907, 43908, 43909, 43910, 43911, 43912, 43913, 43914, 43915, 43916, 43917, 43918, 43919, 43920, 43921, 43922, 43923, 43924, 43925, 43926, 43927, 43928, 43929, 43930, 43931, 43932, 43933, 43934, 43935, 43936, 43937, 43938, 43939, 43940, 43941, 43942, 43943, 43944, 43945, 43946, 43947, 43948, 43949, 43950, 43951, 43952, 43953, 43954, 43955, 43956, 43957, 43958, 43959, 43960, 43961, 43962, 43963, 43964, 43965, 43966, 43967, 5112, 5113, 5114, 5115, 5116, 5117, 42570, 42877, 11363, 7680, 7682, 7684, 7686, 7688, 7690, 7692, 7694, 7696, 7698, 7700, 7702, 7704, 7706, 7708, 7710, 7712, 7714, 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7748, 7750, 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7776, 7778, 7780, 7782, 7784, 7786, 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7808, 7810, 7812, 7814, 7816, 7818, 7820, 7822, 7824, 7826, 7828, 7840, 7842, 7844, 7846, 7848, 7850, 7852, 7854, 7856, 7858, 7860, 7862, 7864, 7866, 7868, 7870, 7872, 7874, 7876, 7878, 7880, 7882, 7884, 7886, 7888, 7890, 7892, 7894, 7896, 7898, 7900, 7902, 7904, 7906, 7908, 7910, 7912, 7914, 7916, 7918, 7920, 7922, 7924, 7926, 7928, 7930, 7932, 7934, 7944, 7945, 7946, 7947, 7948, 7949, 7950, 7951, 7960, 7961, 7962, 7963, 7964, 7965, 7976, 7977, 7978, 7979, 7980, 7981, 7982, 7983, 7992, 7993, 7994, 7995, 7996, 7997, 7998, 7999, 8008, 8009, 8010, 8011, 8012, 8013, 8025, 8027, 8029, 8031, 8040, 8041, 8042, 8043, 8044, 8045, 8046, 8047, 8122, 8123, 8136, 8137, 8138, 8139, 8154, 8155, 8184, 8185, 8170, 8171, 8186, 8187, 8072, 8073, 8074, 8075, 8076, 8077, 8078, 8079, 8088, 8089, 8090, 8091, 8092, 8093, 8094, 8095, 8104, 8105, 8106, 8107, 8108, 8109, 8110, 8111, 8120, 8121, 8124, 8140, 8152, 8153, 8168, 8169, 8172, 8188, 8498, 8544, 8545, 8546, 8547, 8548, 8549, 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, 8559, 8579, 9398, 9399, 9400, 9401, 9402, 9403, 9404, 9405, 9406, 9407, 9408, 9409, 9410, 9411, 9412, 9413, 9414, 9415, 9416, 9417, 9418, 9419, 9420, 9421, 9422, 9423, 11264, 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, 11273, 11274, 11275, 11276, 11277, 11278, 11279, 11280, 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, 11289, 11290, 11291, 11292, 11293, 11294, 11295, 11296, 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, 11305, 11306, 11307, 11308, 11309, 11310, 11360, 570, 574, 11367, 11369, 11371, 11378, 11381, 11392, 11394, 11396, 11398, 11400, 11402, 11404, 11406, 11408, 11410, 11412, 11414, 11416, 11418, 11420, 11422, 11424, 11426, 11428, 11430, 11432, 11434, 11436, 11438, 11440, 11442, 11444, 11446, 11448, 11450, 11452, 11454, 11456, 11458, 11460, 11462, 11464, 11466, 11468, 11470, 11472, 11474, 11476, 11478, 11480, 11482, 11484, 11486, 11488, 11490, 11499, 11501, 11506, 4256, 4257, 4258, 4259, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, 4292, 4293, 4295, 4301, 42560, 42562, 42564, 42566, 42568, 42572, 42574, 42576, 42578, 42580, 42582, 42584, 42586, 42588, 42590, 42592, 42594, 42596, 42598, 42600, 42602, 42604, 42624, 42626, 42628, 42630, 42632, 42634, 42636, 42638, 42640, 42642, 42644, 42646, 42648, 42650, 42786, 42788, 42790, 42792, 42794, 42796, 42798, 42802, 42804, 42806, 42808, 42810, 42812, 42814, 42816, 42818, 42820, 42822, 42824, 42826, 42828, 42830, 42832, 42834, 42836, 42838, 42840, 42842, 42844, 42846, 42848, 42850, 42852, 42854, 42856, 42858, 42860, 42862, 42873, 42875, 42878, 42880, 42882, 42884, 42886, 42891, 42896, 42898, 42902, 42904, 42906, 42908, 42910, 42912, 42914, 42916, 42918, 42920, 42932, 42934, 42931, 65313, 65314, 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, 55297, 56320, 55297, 56321, 55297, 56322, 55297, 56323, 55297, 56324, 55297, 56325, 55297, 56326, 55297, 56327, 55297, 56328, 55297, 56329, 55297, 56330, 55297, 56331, 55297, 56332, 55297, 56333, 55297, 56334, 55297, 56335, 55297, 56336, 55297, 56337, 55297, 56338, 55297, 56339, 55297, 56340, 55297, 56341, 55297, 56342, 55297, 56343, 55297, 56344, 55297, 56345, 55297, 56346, 55297, 56347, 55297, 56348, 55297, 56349, 55297, 56350, 55297, 56351, 55297, 56352, 55297, 56353, 55297, 56354, 55297, 56355, 55297, 56356, 55297, 56357, 55297, 56358, 55297, 56359, 55297, 56496, 55297, 56497, 55297, 56498, 55297, 56499, 55297, 56500, 55297, 56501, 55297, 56502, 55297, 56503, 55297, 56504, 55297, 56505, 55297, 56506, 55297, 56507, 55297, 56508, 55297, 56509, 55297, 56510, 55297, 56511, 55297, 56512, 55297, 56513, 55297, 56514, 55297, 56515, 55297, 56516, 55297, 56517, 55297, 56518, 55297, 56519, 55297, 56520, 55297, 56521, 55297, 56522, 55297, 56523, 55297, 56524, 55297, 56525, 55297, 56526, 55297, 56527, 55297, 56528, 55297, 56529, 55297, 56530, 55297, 56531, 55299, 56448, 55299, 56449, 55299, 56450, 55299, 56451, 55299, 56452, 55299, 56453, 55299, 56454, 55299, 56455, 55299, 56456, 55299, 56457, 55299, 56458, 55299, 56459, 55299, 56460, 55299, 56461, 55299, 56462, 55299, 56463, 55299, 56464, 55299, 56465, 55299, 56466, 55299, 56467, 55299, 56468, 55299, 56469, 55299, 56470, 55299, 56471, 55299, 56472, 55299, 56473, 55299, 56474, 55299, 56475, 55299, 56476, 55299, 56477, 55299, 56478, 55299, 56479, 55299, 56480, 55299, 56481, 55299, 56482, 55299, 56483, 55299, 56484, 55299, 56485, 55299, 56486, 55299, 56487, 55299, 56488, 55299, 56489, 55299, 56490, 55299, 56491, 55299, 56492, 55299, 56493, 55299, 56494, 55299, 56495, 55299, 56496, 55299, 56497, 55299, 56498, 55302, 56480, 55302, 56481, 55302, 56482, 55302, 56483, 55302, 56484, 55302, 56485, 55302, 56486, 55302, 56487, 55302, 56488, 55302, 56489, 55302, 56490, 55302, 56491, 55302, 56492, 55302, 56493, 55302, 56494, 55302, 56495, 55302, 56496, 55302, 56497, 55302, 56498, 55302, 56499, 55302, 56500, 55302, 56501, 55302, 56502, 55302, 56503, 55302, 56504, 55302, 56505, 55302, 56506, 55302, 56507, 55302, 56508, 55302, 56509, 55302, 56510, 55302, 56511, 55354, 56576, 55354, 56577, 55354, 56578, 55354, 56579, 55354, 56580, 55354, 56581, 55354, 56582, 55354, 56583, 55354, 56584, 55354, 56585, 55354, 56586, 55354, 56587, 55354, 56588, 55354, 56589, 55354, 56590, 55354, 56591, 55354, 56592, 55354, 56593, 55354, 56594, 55354, 56595, 55354, 56596, 55354, 56597, 55354, 56598, 55354, 56599, 55354, 56600, 55354, 56601, 55354, 56602, 55354, 56603, 55354, 56604, 55354, 56605, 55354, 56606, 55354, 56607, 55354, 56608, 55354, 56609, }; const utf8proc_uint16_t utf8proc_stage1table[] = { 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, 6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 8960, 9216, 9472, 9728, 9984, 10240, 10496, 10752, 11008, 11264, 11520, 11776, 12032, 12288, 12544, 12800, 13056, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13568, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13824, 14080, 13312, 13312, 13312, 14336, 13312, 14592, 14848, 15104, 15360, 15616, 15872, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 16384, 16640, 16896, 17152, 17408, 17664, 16128, 17920, 18176, 18176, 18176, 18176, 18176, 18176, 18176, 18176, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18688, 18944, 19200, 19456, 19712, 19968, 20224, 20480, 20736, 20992, 21248, 21504, 21760, 22016, 22272, 22528, 22784, 23040, 23296, 23552, 23808, 24064, 23808, 24320, 24576, 24832, 25088, 25344, 25600, 25856, 26112, 26368, 23808, 26624, 23808, 26880, 23808, 23808, 23808, 27136, 27136, 27136, 27392, 27648, 27904, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 27136, 27136, 27136, 27136, 28160, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 27136, 27136, 28416, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 27136, 27136, 28672, 28928, 23808, 23808, 23808, 29184, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 29440, 13312, 13312, 29696, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 29952, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 30208, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 30464, 30720, 30976, 31232, 31488, 31744, 32000, 32256, 10240, 10240, 32512, 23808, 23808, 23808, 23808, 23808, 32768, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 33024, 33280, 23808, 23808, 23808, 23808, 33536, 23808, 33792, 34048, 34304, 34560, 34816, 35072, 35328, 35584, 35840, 36096, 23808, 23808, 23808, 23808, 23808, 23808, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 36352, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 36608, 36864, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 13312, 37120, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 37376, 37632, 37888, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 38144, 38400, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 23808, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 38656, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 18432, 38656, }; const utf8proc_uint16_t utf8proc_stage2table[] = { 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 3, 5, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 7, 7, 3, 8, 9, 9, 10, 11, 10, 9, 9, 12, 13, 9, 14, 15, 16, 15, 15, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 15, 9, 18, 19, 20, 9, 9, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 12, 9, 13, 47, 48, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 12, 75, 13, 75, 2, 2, 2, 2, 2, 2, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 76, 9, 11, 11, 11, 11, 77, 9, 78, 77, 79, 80, 75, 81, 77, 82, 83, 84, 85, 86, 87, 88, 9, 9, 89, 90, 91, 92, 93, 94, 95, 9, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 75, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 75, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 214, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 214, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 214, 214, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 214, 340, 341, 342, 214, 343, 340, 340, 340, 340, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 214, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 214, 214, 214, 214, 214, 214, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 214, 482, 483, 214, 484, 214, 485, 486, 214, 214, 214, 487, 488, 214, 489, 214, 490, 491, 214, 492, 493, 494, 495, 496, 214, 214, 497, 214, 498, 499, 214, 214, 500, 214, 214, 214, 214, 214, 214, 214, 501, 214, 214, 502, 214, 214, 503, 214, 214, 214, 504, 505, 506, 507, 508, 509, 214, 214, 214, 214, 214, 510, 214, 340, 214, 214, 214, 214, 214, 214, 214, 214, 511, 512, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 522, 523, 523, 523, 523, 523, 523, 523, 47, 47, 47, 47, 522, 522, 522, 522, 522, 522, 522, 522, 522, 522, 523, 523, 47, 47, 47, 47, 47, 47, 524, 525, 526, 527, 528, 529, 47, 47, 530, 531, 532, 533, 534, 47, 47, 47, 47, 47, 47, 47, 522, 47, 523, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 540, 540, 548, 540, 549, 540, 550, 551, 552, 553, 553, 553, 553, 552, 554, 553, 553, 553, 553, 553, 555, 555, 556, 557, 558, 559, 560, 561, 553, 553, 553, 553, 562, 563, 553, 564, 565, 553, 553, 566, 566, 566, 566, 567, 553, 553, 553, 553, 540, 540, 540, 568, 569, 570, 571, 572, 573, 540, 553, 553, 553, 540, 540, 540, 553, 553, 574, 540, 540, 540, 553, 553, 553, 553, 540, 552, 553, 553, 540, 575, 576, 576, 575, 576, 576, 575, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 577, 578, 579, 580, 581, 47, 582, 583, 0, 0, 584, 585, 586, 587, 588, 589, 0, 0, 0, 0, 87, 590, 591, 592, 593, 594, 595, 0, 596, 0, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 0, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 75, 700, 701, 702, 703, 704, 214, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 540, 540, 540, 540, 540, 839, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 0, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 0, 0, 523, 1044, 1044, 1044, 1044, 1044, 1044, 0, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 0, 1044, 1084, 0, 0, 1085, 1085, 11, 0, 553, 540, 540, 540, 540, 553, 540, 540, 540, 1086, 553, 540, 540, 540, 540, 540, 540, 553, 553, 553, 553, 553, 553, 540, 540, 553, 540, 540, 1086, 1087, 540, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1103, 540, 553, 1103, 1096, 0, 0, 0, 0, 0, 0, 0, 0, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 0, 0, 0, 0, 0, 1106, 1106, 1106, 1103, 1103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1107, 1108, 1108, 1108, 1107, 1107, 1109, 1109, 1110, 10, 10, 1111, 15, 1112, 1085, 1085, 540, 540, 540, 540, 540, 540, 540, 540, 1113, 1114, 1115, 1112, 1116, 0, 1117, 1112, 1118, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1125, 1118, 1118, 1118, 1118, 1118, 1118, 1126, 1127, 1118, 1128, 1129, 1130, 1131, 1113, 1114, 1115, 1132, 1133, 1134, 1135, 1136, 553, 540, 540, 540, 540, 540, 553, 540, 540, 553, 1137, 1137, 1137, 1137, 1137, 1137, 1137, 1137, 1137, 1137, 10, 1138, 1138, 1112, 1118, 1118, 1139, 1118, 1118, 1118, 1118, 1140, 1141, 1142, 1143, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1126, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1144, 1145, 1146, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1147, 1148, 1112, 1149, 540, 540, 540, 540, 540, 540, 540, 1108, 1085, 540, 540, 540, 540, 553, 540, 1125, 1125, 540, 540, 1085, 553, 540, 540, 553, 1118, 1118, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 1118, 1118, 1118, 1150, 1150, 1126, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 1117, 0, 1151, 1126, 1152, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 1126, 540, 553, 540, 540, 553, 540, 540, 553, 553, 553, 540, 553, 553, 540, 553, 540, 540, 540, 553, 540, 553, 540, 553, 540, 553, 540, 540, 0, 0, 1126, 1126, 1126, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1126, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1126, 1126, 1126, 1118, 1118, 1118, 1118, 1118, 1118, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 540, 540, 540, 540, 540, 540, 540, 553, 540, 1155, 1155, 77, 9, 9, 9, 1155, 0, 0, 0, 0, 0, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 540, 540, 540, 540, 1157, 540, 540, 540, 540, 540, 540, 540, 540, 540, 1157, 540, 540, 540, 1157, 540, 540, 540, 540, 540, 0, 0, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 0, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 553, 553, 553, 0, 0, 1158, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 0, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 1118, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 1107, 553, 540, 540, 553, 540, 540, 553, 540, 540, 540, 553, 553, 553, 1129, 1130, 1131, 540, 540, 540, 553, 540, 540, 553, 553, 540, 540, 540, 540, 540, 1153, 1153, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1161, 1162, 1160, 1160, 1160, 1160, 1160, 1160, 1163, 1164, 1160, 1165, 1166, 1160, 1160, 1160, 1160, 1160, 1153, 1159, 1167, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1159, 1159, 1168, 1159, 1159, 1160, 540, 553, 540, 540, 1153, 1153, 1153, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1160, 1160, 1153, 1153, 1177, 1177, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1177, 1179, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 0, 0, 0, 1160, 1160, 1160, 1160, 0, 0, 1180, 1160, 1181, 1159, 1159, 1153, 1153, 1153, 1153, 0, 0, 1182, 1159, 0, 0, 1183, 1184, 1168, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 1185, 0, 0, 0, 0, 1186, 1187, 0, 1188, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1160, 1160, 1189, 1189, 1190, 1190, 1190, 1190, 1190, 1190, 1191, 1189, 0, 0, 0, 0, 0, 1153, 1153, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1192, 0, 1160, 1193, 0, 1160, 1160, 0, 0, 1180, 0, 1159, 1159, 1159, 1153, 1153, 0, 0, 0, 0, 1153, 1153, 0, 0, 1153, 1153, 1168, 0, 0, 0, 1153, 0, 0, 0, 0, 0, 0, 0, 1194, 1195, 1196, 1160, 0, 1197, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1153, 1153, 1160, 1160, 1160, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1153, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 0, 0, 1180, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 0, 1153, 1153, 1159, 0, 1159, 1159, 1168, 0, 0, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1177, 1189, 0, 0, 0, 0, 0, 0, 0, 1160, 0, 0, 0, 0, 0, 0, 0, 1153, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 0, 0, 1180, 1160, 1198, 1153, 1159, 1153, 1153, 1153, 1153, 0, 0, 1199, 1200, 0, 0, 1201, 1202, 1168, 0, 0, 0, 0, 0, 0, 0, 0, 1203, 1204, 0, 0, 0, 0, 1205, 1206, 0, 1160, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1191, 1160, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1160, 1160, 1160, 0, 1207, 1160, 1208, 1160, 0, 0, 0, 1160, 1160, 0, 1160, 0, 1160, 1160, 0, 0, 0, 1160, 1160, 0, 0, 0, 1160, 1160, 1160, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 1209, 1159, 1153, 1159, 1159, 0, 0, 0, 1210, 1211, 1159, 0, 1212, 1213, 1214, 1168, 0, 0, 1160, 0, 0, 0, 0, 0, 0, 1215, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 1190, 1190, 1085, 1085, 1085, 1085, 1085, 1085, 1189, 1085, 0, 0, 0, 0, 0, 1153, 1159, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1160, 1153, 1153, 1153, 1159, 1159, 1159, 1159, 0, 1216, 1153, 1217, 0, 1153, 1153, 1153, 1168, 0, 0, 0, 0, 0, 0, 0, 1218, 1219, 0, 1160, 1160, 1160, 0, 0, 0, 0, 0, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 0, 0, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1191, 1160, 1153, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 0, 0, 1180, 1160, 1159, 1221, 1222, 1159, 1223, 1159, 1159, 0, 1224, 1225, 1226, 0, 1227, 1228, 1153, 1168, 0, 0, 0, 0, 0, 0, 0, 1229, 1230, 0, 0, 0, 0, 0, 0, 0, 1160, 0, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1231, 1159, 1159, 1153, 1153, 1153, 1153, 0, 1232, 1233, 1159, 0, 1234, 1235, 1236, 1168, 1237, 1191, 0, 0, 0, 0, 1160, 1160, 1160, 1238, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1160, 1160, 1160, 1153, 1153, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1191, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1239, 0, 0, 0, 0, 1240, 1159, 1159, 1153, 1153, 1153, 0, 1153, 0, 1159, 1241, 1242, 1159, 1243, 1244, 1245, 1246, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 1159, 1159, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1153, 340, 1247, 1153, 1153, 1153, 1153, 1248, 1248, 1168, 0, 0, 0, 0, 11, 340, 340, 340, 340, 340, 340, 523, 1153, 1249, 1249, 1249, 1249, 1153, 1153, 1153, 1044, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1044, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 0, 340, 0, 0, 340, 340, 0, 340, 0, 0, 340, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 0, 340, 340, 340, 340, 340, 340, 340, 0, 340, 340, 340, 0, 340, 0, 340, 0, 0, 340, 340, 0, 340, 340, 340, 340, 1153, 340, 1251, 1153, 1153, 1153, 1153, 1252, 1252, 0, 1153, 1153, 340, 0, 0, 340, 340, 340, 340, 340, 0, 523, 0, 1253, 1253, 1253, 1253, 1153, 1153, 0, 0, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 0, 0, 1254, 1255, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1191, 1191, 1191, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1256, 1177, 1177, 1177, 1177, 1177, 1177, 1191, 1177, 1191, 1191, 1191, 553, 553, 1191, 1191, 1191, 1191, 1191, 1191, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1191, 553, 1191, 553, 1191, 1257, 1258, 1259, 1258, 1259, 1159, 1159, 1160, 1160, 1160, 1260, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1261, 1160, 1160, 1160, 1160, 1262, 1160, 1160, 1160, 1160, 1263, 1160, 1160, 1160, 1160, 1264, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1265, 1160, 1160, 1160, 0, 0, 0, 0, 1266, 1267, 1268, 1269, 1270, 1271, 1272, 1273, 1274, 1267, 1267, 1267, 1267, 1153, 1159, 1267, 1275, 540, 540, 1168, 1177, 540, 540, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1153, 1153, 1276, 1153, 1153, 1153, 1153, 0, 1153, 1153, 1153, 1153, 1277, 1153, 1153, 1153, 1153, 1278, 1153, 1153, 1153, 1153, 1279, 1153, 1153, 1153, 1153, 1280, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1281, 1153, 1153, 1153, 0, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 553, 1191, 1191, 1191, 1191, 1191, 1191, 0, 1191, 1191, 1177, 1177, 1177, 1177, 1177, 1191, 1191, 1191, 1191, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1282, 1283, 1160, 1160, 1160, 1160, 1284, 1284, 1153, 1285, 1153, 1153, 1159, 1153, 1153, 1153, 1153, 1153, 1180, 1284, 1168, 1168, 1159, 1159, 1153, 1153, 1160, 1178, 1178, 1250, 1250, 1178, 1178, 1178, 1178, 1178, 1178, 1044, 1044, 1044, 1177, 1177, 1177, 1160, 1160, 1160, 1160, 340, 1160, 1159, 1159, 1153, 1153, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1160, 1284, 1284, 1284, 1160, 1160, 1284, 1284, 1284, 1284, 1284, 1284, 1284, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1284, 1159, 1153, 1153, 1284, 1284, 1284, 1284, 1284, 1284, 553, 1160, 1284, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1284, 1284, 1284, 1153, 1191, 1191, 1286, 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, 1314, 1315, 1316, 1317, 1318, 1319, 1320, 1321, 1322, 1323, 0, 1324, 0, 0, 0, 0, 0, 1325, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1044, 1326, 340, 340, 340, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1328, 1329, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 540, 540, 540, 1177, 1044, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 77, 77, 77, 77, 77, 1085, 77, 1085, 1085, 77, 0, 0, 0, 0, 0, 0, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1367, 1368, 1369, 1370, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413, 1414, 1415, 1416, 1417, 0, 0, 1418, 1419, 1420, 1421, 1422, 1423, 0, 0, 1424, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1177, 1044, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1425, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1258, 1259, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 1044, 1044, 1044, 1426, 1426, 1426, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1153, 1153, 1168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1168, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 0, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 574, 574, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1153, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1168, 1153, 1177, 1177, 1044, 1179, 1177, 1177, 1177, 1189, 340, 540, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1427, 1427, 1427, 1427, 1427, 1427, 1427, 1427, 1427, 1427, 0, 0, 0, 0, 0, 0, 1428, 1428, 1428, 1428, 1428, 1428, 1424, 1428, 1428, 1428, 1428, 574, 574, 574, 81, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1179, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1087, 1160, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 340, 1160, 340, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1153, 1153, 1153, 1159, 1159, 1159, 1159, 1153, 1153, 1159, 1159, 1159, 0, 0, 0, 0, 1159, 1159, 1153, 1159, 1159, 1159, 1159, 1159, 1159, 1086, 540, 553, 0, 0, 0, 0, 1085, 0, 0, 0, 1428, 1428, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 540, 553, 1159, 1159, 1153, 0, 0, 1177, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1153, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 0, 1168, 1284, 1153, 1284, 1284, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1159, 1159, 1159, 1159, 1153, 1153, 540, 540, 540, 540, 540, 540, 540, 540, 0, 0, 553, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1179, 1177, 1177, 1177, 1177, 1177, 1177, 0, 0, 540, 540, 540, 540, 540, 553, 553, 553, 553, 553, 553, 540, 540, 553, 839, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1153, 1153, 1153, 1159, 1429, 1430, 1431, 1432, 1433, 1434, 1435, 1436, 1437, 1438, 1160, 1160, 1439, 1440, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1180, 1441, 1153, 1153, 1153, 1153, 1442, 1443, 1444, 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 540, 553, 540, 540, 540, 540, 540, 540, 540, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 0, 0, 0, 1153, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1153, 1153, 1153, 1153, 1159, 1159, 1153, 1153, 1452, 1168, 1153, 1153, 1160, 1160, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1180, 1159, 1153, 1153, 1159, 1159, 1159, 1153, 1159, 1153, 1153, 1153, 1452, 1452, 0, 0, 0, 0, 0, 0, 0, 0, 1177, 1177, 1177, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1153, 1180, 0, 0, 0, 1177, 1177, 1177, 1177, 1177, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 1160, 1160, 1160, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 523, 523, 523, 523, 523, 523, 1044, 1044, 1453, 1454, 1455, 1456, 1457, 1457, 1458, 1459, 1460, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 540, 540, 540, 1177, 566, 553, 553, 553, 553, 553, 540, 540, 553, 553, 553, 553, 540, 1159, 566, 566, 566, 566, 566, 566, 566, 1160, 1160, 1160, 1160, 553, 1160, 1160, 1160, 1160, 1159, 1159, 540, 1160, 1160, 0, 540, 540, 0, 0, 0, 0, 0, 0, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 1461, 1462, 1463, 523, 1464, 1465, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 1474, 523, 1475, 1476, 1477, 1478, 1479, 1480, 1481, 1482, 1483, 1484, 1485, 1486, 1487, 1488, 1489, 1490, 1491, 1492, 523, 1493, 1494, 1495, 1496, 1497, 1498, 1499, 1500, 1501, 1502, 1503, 1504, 1505, 1506, 1507, 1508, 1509, 1510, 1511, 1512, 1513, 1514, 1515, 1516, 1517, 1518, 1519, 1520, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 1521, 1522, 1523, 214, 214, 1524, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 1523, 214, 214, 214, 214, 214, 1525, 1526, 1527, 1528, 1491, 1529, 1530, 1531, 1532, 1533, 1534, 1535, 1536, 1537, 1538, 1539, 1540, 1541, 1542, 1543, 1544, 1545, 1546, 1547, 1548, 1549, 1550, 1551, 1552, 1553, 1554, 1555, 1556, 1557, 1558, 1559, 1560, 540, 540, 553, 540, 540, 540, 540, 540, 540, 540, 553, 540, 540, 576, 1561, 553, 555, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 0, 0, 0, 0, 0, 540, 575, 553, 540, 553, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 1569, 1570, 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1594, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 1624, 1625, 1626, 1627, 1628, 1629, 1630, 1631, 1632, 1633, 1634, 1635, 1636, 1637, 1638, 1639, 1640, 1641, 1642, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 1651, 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, 1660, 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, 1670, 1671, 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681, 1682, 1683, 1684, 1685, 1686, 1687, 1688, 1689, 1690, 1691, 1692, 1693, 1694, 1695, 1696, 1697, 1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 1714, 1715, 1716, 1717, 214, 214, 1718, 214, 1719, 1720, 1721, 1722, 1723, 1724, 1725, 1726, 1727, 1728, 1729, 1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739, 1740, 1741, 1742, 1743, 1744, 1745, 1746, 1747, 1748, 1749, 1750, 1751, 1752, 1753, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1761, 1762, 1763, 1764, 1765, 1766, 1767, 1768, 1769, 1770, 1771, 1772, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781, 1782, 1783, 1784, 1785, 1786, 1787, 1788, 1789, 1790, 1791, 1792, 1793, 1794, 1795, 1796, 1797, 1798, 1799, 1800, 1801, 1802, 1803, 1804, 1805, 1806, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, 1821, 1822, 1823, 1824, 1825, 1826, 1827, 1828, 1829, 1830, 1831, 1832, 1833, 1834, 1835, 1836, 0, 0, 1837, 1838, 1839, 1840, 1841, 1842, 0, 0, 1843, 1844, 1845, 1846, 1847, 1848, 1849, 1850, 1851, 1852, 1853, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, 1862, 1863, 1864, 1865, 1866, 1867, 1868, 1869, 1870, 1871, 1872, 1873, 1874, 1875, 1876, 1877, 1878, 1879, 1880, 0, 0, 1881, 1882, 1883, 1884, 1885, 1886, 0, 0, 1887, 1888, 1889, 1890, 1891, 1892, 1893, 1894, 0, 1895, 0, 1896, 0, 1897, 0, 1898, 1899, 1900, 1901, 1902, 1903, 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, 1913, 1914, 1915, 1916, 1917, 1918, 1919, 1920, 1921, 1922, 1923, 1924, 1925, 1926, 1927, 1928, 0, 0, 1929, 1930, 1931, 1932, 1933, 1934, 1935, 1936, 1937, 1938, 1939, 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 0, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 0, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 0, 0, 2011, 2012, 2013, 2014, 2015, 2016, 0, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 0, 0, 2036, 2037, 2038, 0, 2039, 2040, 2041, 2042, 2043, 2044, 2045, 2046, 2047, 0, 2048, 2049, 2050, 2051, 2050, 2050, 2050, 2052, 2050, 2050, 2050, 81, 2053, 2054, 2055, 2056, 1084, 2057, 1084, 1084, 1084, 1084, 9, 2058, 2059, 2060, 2061, 2059, 2059, 2060, 2061, 2059, 9, 9, 9, 9, 2062, 2063, 2064, 9, 2065, 2066, 2067, 2068, 2069, 2070, 2071, 76, 10, 10, 10, 2072, 2073, 9, 2074, 2075, 9, 80, 92, 9, 2076, 9, 2077, 48, 48, 9, 9, 9, 2078, 12, 13, 2079, 2080, 2081, 9, 9, 9, 9, 9, 9, 9, 9, 75, 9, 48, 9, 9, 2082, 9, 9, 9, 9, 9, 9, 9, 2050, 81, 81, 81, 81, 81, 0, 2083, 2084, 2085, 2086, 81, 81, 81, 81, 81, 81, 2087, 2088, 0, 0, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101, 2102, 2103, 2104, 2105, 2106, 2107, 2108, 2109, 2110, 2111, 2112, 2113, 2114, 2115, 0, 2116, 2117, 2118, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2127, 2128, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 2129, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 1189, 11, 11, 11, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 540, 540, 566, 566, 540, 540, 540, 540, 566, 566, 566, 540, 540, 839, 839, 839, 839, 540, 839, 839, 839, 566, 566, 540, 553, 540, 566, 566, 553, 553, 553, 553, 540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2130, 2131, 2132, 2133, 77, 2134, 2135, 2136, 77, 2137, 2138, 2139, 2139, 2139, 2140, 2141, 2142, 2142, 2143, 2144, 77, 2145, 2146, 77, 75, 2147, 2148, 2149, 2149, 2149, 77, 77, 2150, 2151, 2152, 77, 2153, 77, 2154, 77, 2153, 77, 2155, 2156, 2157, 2132, 83, 2158, 2159, 2160, 2161, 2162, 2163, 2164, 2165, 2166, 2167, 2168, 1085, 2169, 2170, 2171, 2172, 2173, 2174, 75, 75, 75, 75, 2175, 2176, 2158, 2168, 2177, 77, 75, 1085, 77, 2178, 1191, 2179, 2180, 2181, 2182, 2183, 2184, 2185, 2186, 2187, 2188, 2189, 2190, 2191, 2192, 2193, 2194, 2195, 2196, 2197, 2198, 2199, 2200, 2201, 2202, 2203, 2204, 2205, 2206, 2207, 2208, 2209, 2210, 2211, 2212, 2213, 2214, 2215, 2216, 2217, 2218, 2219, 2220, 2221, 2222, 2223, 2224, 2225, 2226, 1426, 1426, 2227, 2228, 2229, 1426, 1426, 1426, 2227, 2230, 77, 77, 0, 0, 0, 0, 2231, 75, 2232, 75, 2233, 77, 77, 77, 77, 77, 2234, 2235, 77, 77, 77, 77, 75, 77, 77, 75, 77, 77, 75, 77, 77, 77, 77, 77, 77, 77, 2236, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 2237, 2238, 2239, 2240, 77, 2241, 77, 2242, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1109, 75, 75, 75, 75, 1109, 1109, 1109, 1109, 75, 75, 1109, 75, 2243, 2243, 2244, 2245, 75, 75, 75, 2246, 2247, 2243, 2248, 2249, 2243, 75, 75, 75, 2243, 14, 84, 75, 2243, 2243, 75, 75, 75, 2243, 2243, 2243, 2243, 75, 2243, 2243, 2243, 2243, 2250, 2251, 2252, 2253, 75, 75, 75, 75, 2243, 2254, 2255, 2243, 2256, 2257, 2243, 2243, 2243, 75, 75, 75, 75, 75, 2243, 75, 2243, 2258, 2243, 2243, 2243, 2243, 2259, 2243, 2260, 2261, 2262, 2243, 2263, 2264, 2265, 2243, 2243, 2243, 2266, 75, 75, 75, 75, 2243, 2243, 2243, 2243, 75, 75, 75, 75, 75, 75, 75, 75, 75, 2243, 2267, 2268, 2269, 75, 2270, 2271, 2243, 2243, 2243, 2243, 2243, 2243, 75, 2272, 2273, 2274, 2275, 2276, 2277, 2278, 2279, 2280, 2281, 2282, 2283, 2284, 2285, 2286, 2287, 2288, 2243, 2243, 2289, 2290, 2291, 2292, 2293, 2294, 2295, 2296, 2297, 2298, 2243, 2243, 2243, 75, 75, 2243, 2243, 2299, 2300, 75, 75, 75, 75, 75, 2243, 75, 75, 75, 75, 75, 75, 75, 75, 75, 2301, 2243, 75, 75, 2243, 2243, 2302, 2303, 2243, 2304, 2305, 2306, 2307, 2308, 2243, 2243, 2309, 2310, 2311, 2312, 2243, 2243, 2243, 75, 75, 75, 75, 75, 2243, 2243, 75, 75, 75, 75, 75, 75, 75, 75, 75, 2243, 2243, 2243, 2243, 2243, 75, 75, 2243, 2243, 75, 75, 75, 75, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2313, 2314, 2315, 2316, 2243, 2243, 2243, 2243, 2243, 2243, 2317, 2318, 2319, 2320, 75, 75, 2243, 2243, 2321, 2321, 2243, 2321, 2321, 2243, 2243, 2321, 2321, 2321, 2243, 2321, 2243, 2321, 77, 77, 77, 77, 77, 77, 77, 77, 12, 13, 12, 13, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 77, 77, 77, 77, 2243, 2243, 77, 77, 77, 77, 77, 77, 77, 2322, 2323, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1085, 1109, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 838, 77, 1085, 1085, 1085, 1085, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 1109, 1109, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 1085, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 77, 1085, 1109, 1109, 1109, 1109, 1109, 1109, 1085, 1085, 1085, 1085, 1085, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2324, 2325, 2326, 2327, 2328, 2329, 2330, 2331, 2332, 2333, 2334, 2335, 2336, 2337, 2338, 2339, 2340, 2341, 2342, 2343, 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2351, 2352, 2353, 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2361, 2362, 2363, 2364, 2365, 2366, 2367, 2368, 2369, 2370, 2371, 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, 2380, 2381, 2382, 2383, 2384, 2385, 2386, 2387, 2388, 2389, 2390, 2391, 2392, 2393, 2394, 2395, 2396, 2397, 2398, 2399, 2400, 2401, 2402, 2403, 2404, 2405, 2406, 2407, 2408, 2409, 2410, 2411, 2412, 2413, 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2421, 2422, 2423, 2424, 2425, 2426, 2427, 2428, 2429, 2430, 2431, 2432, 2433, 2434, 2435, 2436, 2437, 2438, 2439, 2440, 2441, 2442, 2443, 2444, 2445, 2446, 2447, 2448, 2449, 2450, 2451, 2452, 2453, 2454, 2455, 2456, 2457, 2458, 2459, 2460, 2461, 2462, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 75, 77, 77, 77, 77, 77, 77, 77, 77, 77, 75, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 75, 75, 75, 75, 75, 1109, 1109, 75, 77, 77, 77, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 2463, 77, 77, 77, 77, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 75, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 1085, 1085, 1085, 838, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 1085, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2465, 1085, 1085, 1085, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2243, 1109, 75, 2321, 2321, 12, 13, 75, 2321, 2321, 75, 2321, 2321, 2321, 1109, 1109, 1109, 75, 1109, 2243, 2243, 2321, 2321, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 2321, 1109, 75, 1109, 2321, 2321, 2321, 2321, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 1109, 1109, 75, 75, 1109, 75, 1109, 75, 1109, 1109, 75, 75, 1109, 1109, 75, 75, 1109, 1109, 75, 75, 1109, 1109, 75, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 75, 1109, 1109, 75, 1109, 75, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 1258, 1259, 1258, 1259, 12, 13, 75, 1109, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2243, 2243, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 2321, 2321, 2321, 2321, 1109, 1109, 1109, 2321, 1109, 1109, 1109, 1109, 2321, 2321, 2321, 2243, 2243, 75, 2243, 2243, 75, 75, 12, 13, 1258, 1259, 2321, 1109, 1109, 1109, 1109, 2321, 1109, 2321, 2321, 2321, 1109, 1109, 2321, 2321, 1109, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 2243, 2243, 2243, 2243, 2243, 75, 75, 12, 13, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 2466, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 1109, 2243, 2243, 2321, 2243, 75, 75, 2243, 75, 2243, 1109, 75, 2243, 75, 2243, 2243, 2321, 2321, 75, 75, 75, 75, 1109, 2321, 2321, 1109, 1109, 1109, 1109, 1109, 1109, 2243, 2243, 2243, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 75, 75, 75, 75, 75, 75, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 75, 75, 1109, 1109, 2243, 2243, 2243, 2243, 1109, 2243, 2243, 75, 75, 2243, 2467, 2468, 2469, 75, 1109, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2243, 2243, 2321, 2321, 2243, 2243, 2243, 2243, 2243, 2243, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 1109, 1109, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 1109, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2321, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2243, 2321, 2321, 2243, 2243, 2243, 2243, 2321, 2321, 2321, 2321, 2321, 2321, 2243, 2243, 2243, 2243, 1109, 1109, 1109, 1109, 1109, 2470, 2471, 2243, 1109, 1109, 1109, 2321, 2321, 2321, 2321, 2321, 1109, 1109, 1109, 1109, 1109, 2321, 2321, 2243, 75, 75, 75, 75, 2321, 1109, 1109, 75, 2321, 2321, 2321, 2321, 2321, 1109, 2321, 75, 75, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 77, 77, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1109, 1085, 1085, 1109, 1109, 1109, 1109, 1109, 1109, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2472, 2473, 2474, 2475, 2476, 2477, 2478, 2479, 2480, 2481, 2482, 2483, 2484, 2485, 2486, 2487, 2488, 2489, 2490, 2491, 2492, 2493, 2494, 2495, 2496, 2497, 2498, 2499, 2500, 2501, 2502, 2503, 2504, 2505, 2506, 2507, 2508, 2509, 2510, 2511, 2512, 2513, 2514, 2515, 2516, 2517, 2518, 0, 2519, 2520, 2521, 2522, 2523, 2524, 2525, 2526, 2527, 2528, 2529, 2530, 2531, 2532, 2533, 2534, 2535, 2536, 2537, 2538, 2539, 2540, 2541, 2542, 2543, 2544, 2545, 2546, 2547, 2548, 2549, 2550, 2551, 2552, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561, 2562, 2563, 2564, 2565, 0, 2566, 2567, 2568, 2569, 2570, 2571, 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 214, 2583, 2584, 214, 2585, 2586, 214, 214, 214, 214, 214, 2587, 2588, 2589, 2590, 2591, 2592, 2593, 2594, 2595, 2596, 2597, 2598, 2599, 2600, 2601, 2602, 2603, 2604, 2605, 2606, 2607, 2608, 2609, 2610, 2611, 2612, 2613, 2614, 2615, 2616, 2617, 2618, 2619, 2620, 2621, 2622, 2623, 2624, 2625, 2626, 2627, 2628, 2629, 2630, 2631, 2632, 2633, 2634, 2635, 2636, 2637, 2638, 2639, 2640, 2641, 2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 2651, 2652, 2653, 2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 2670, 2671, 2672, 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2683, 2684, 2685, 2686, 2687, 2688, 2689, 2690, 214, 77, 77, 1085, 77, 77, 1085, 2691, 2692, 2693, 2694, 540, 540, 540, 2695, 2696, 0, 0, 0, 0, 0, 9, 9, 9, 9, 1427, 9, 9, 2697, 2698, 2699, 2700, 2701, 2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2710, 2711, 2712, 2713, 2714, 2715, 2716, 2717, 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725, 2726, 2727, 2728, 2729, 2730, 2731, 2732, 2733, 2734, 0, 2735, 0, 0, 0, 0, 0, 2736, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 2737, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1168, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 9, 9, 80, 92, 80, 92, 9, 9, 9, 80, 92, 9, 80, 92, 1428, 1428, 1428, 1428, 9, 1428, 1428, 1428, 9, 1084, 9, 9, 1084, 9, 80, 92, 9, 9, 80, 92, 12, 13, 12, 13, 12, 13, 12, 13, 9, 9, 9, 9, 9, 522, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1084, 1084, 9, 9, 9, 9, 1084, 9, 2061, 1428, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 1085, 1085, 1085, 1085, 2738, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2739, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2740, 2741, 2742, 2743, 2744, 2745, 2746, 2747, 2748, 2749, 2750, 2751, 2752, 2753, 2754, 2755, 2756, 2757, 2758, 2759, 2760, 2761, 2762, 2763, 2764, 2765, 2766, 2767, 2768, 2769, 2770, 2771, 2772, 2773, 2774, 2775, 2776, 2777, 2778, 2779, 2780, 2781, 2782, 2783, 2784, 2785, 2786, 2787, 2788, 2789, 2790, 2791, 2792, 2793, 2794, 2795, 2796, 2797, 2798, 2799, 2800, 2801, 2802, 2803, 2804, 2805, 2806, 2807, 2808, 2809, 2810, 2811, 2812, 2813, 2814, 2815, 2816, 2817, 2818, 2819, 2820, 2821, 2822, 2823, 2824, 2825, 2826, 2827, 2828, 2829, 2830, 2831, 2832, 2833, 2834, 2835, 2836, 2837, 2838, 2839, 2840, 2841, 2842, 2843, 2844, 2845, 2846, 2847, 2848, 2849, 2850, 2851, 2852, 2853, 2854, 2855, 2856, 2857, 2858, 2859, 2860, 2861, 2862, 2863, 2864, 2865, 2866, 2867, 2868, 2869, 2870, 2871, 2872, 2873, 2874, 2875, 2876, 2877, 2878, 2879, 2880, 2881, 2882, 2883, 2884, 2885, 2886, 2887, 2888, 2889, 2890, 2891, 2892, 2893, 2894, 2895, 2896, 2897, 2898, 2899, 2900, 2901, 2902, 2903, 2904, 2905, 2906, 2907, 2908, 2909, 2910, 2911, 2912, 2913, 2914, 2915, 2916, 2917, 2918, 2919, 2920, 2921, 2922, 2923, 2924, 2925, 2926, 2927, 2928, 2929, 2930, 2931, 2932, 2933, 2934, 2935, 2936, 2937, 2938, 2939, 2940, 2941, 2942, 2943, 2944, 2945, 2946, 2947, 2948, 2949, 2950, 2951, 2952, 2953, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 2954, 1428, 1428, 1428, 1085, 1179, 1160, 2227, 1258, 1259, 1258, 1259, 1258, 1259, 1258, 1259, 1258, 1259, 1085, 1085, 1258, 1259, 1258, 1259, 1258, 1259, 1258, 1259, 1424, 2955, 2956, 2956, 1085, 2227, 2227, 2227, 2227, 2227, 2227, 2227, 2227, 2227, 2957, 1087, 552, 1086, 2958, 2958, 1424, 1179, 1179, 1179, 1179, 1179, 2959, 1085, 2960, 2961, 2962, 1179, 1160, 1428, 1085, 77, 0, 1160, 1160, 1160, 1160, 1160, 2963, 1160, 1160, 1160, 1160, 2964, 2965, 2966, 2967, 2968, 2969, 2970, 2971, 2972, 2973, 2974, 2975, 2976, 2977, 2978, 2979, 2980, 2981, 2982, 2983, 2984, 2985, 2986, 2987, 1160, 2988, 2989, 2990, 2991, 2992, 2993, 1160, 1160, 1160, 1160, 1160, 2994, 2995, 2996, 2997, 2998, 2999, 3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 3009, 1160, 1160, 0, 0, 3010, 3011, 3012, 3013, 3014, 3015, 3016, 1424, 1160, 1160, 1160, 1160, 1160, 3017, 1160, 1160, 1160, 1160, 3018, 3019, 3020, 3021, 3022, 3023, 3024, 3025, 3026, 3027, 3028, 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036, 3037, 3038, 3039, 3040, 3041, 1160, 3042, 3043, 3044, 3045, 3046, 3047, 1160, 1160, 1160, 1160, 1160, 3048, 3049, 3050, 3051, 3052, 3053, 3054, 3055, 3056, 3057, 3058, 3059, 3060, 3061, 3062, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 3063, 3064, 3065, 3066, 1160, 3067, 1160, 1160, 3068, 3069, 3070, 3071, 1428, 1179, 3072, 3073, 3074, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 3075, 3076, 3077, 3078, 3079, 3080, 3081, 3082, 3083, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3091, 3092, 3093, 3094, 3095, 3096, 3097, 3098, 3099, 3100, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109, 3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3122, 3123, 3124, 3125, 3126, 3127, 3128, 3129, 3130, 3131, 3132, 3133, 3134, 3135, 3136, 3137, 3138, 3139, 3140, 3141, 3142, 3143, 3144, 3145, 3146, 3147, 3148, 3149, 3150, 3151, 3152, 3153, 3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166, 3167, 3168, 0, 1191, 1191, 3169, 3170, 3171, 3172, 3173, 3174, 3175, 3176, 3177, 3178, 3179, 3180, 3181, 3182, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, 3195, 3196, 3197, 3198, 3199, 3200, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, 3211, 3212, 3213, 0, 3214, 3215, 3216, 3217, 3218, 3219, 3220, 3221, 3222, 3223, 3224, 3225, 3226, 3227, 3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3239, 3240, 3241, 3242, 3243, 3244, 3245, 3246, 3247, 3248, 3249, 3250, 3251, 3252, 3253, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 3254, 3255, 3256, 3257, 3258, 3259, 3260, 3261, 3262, 3263, 3264, 3265, 3266, 3267, 3268, 3269, 3270, 3271, 3272, 3273, 3274, 3275, 3276, 3277, 3278, 3279, 3280, 3281, 3282, 3283, 3284, 3285, 3286, 3287, 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, 3296, 3297, 3298, 3299, 3300, 1191, 3301, 3302, 3303, 3304, 3305, 3306, 3307, 3308, 3309, 3310, 3311, 3312, 3313, 3314, 3315, 3316, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, 3325, 3326, 3327, 3328, 3329, 3330, 3331, 3332, 3333, 3334, 3335, 3336, 3337, 3338, 3339, 3340, 3341, 3342, 3343, 3344, 3345, 3346, 3347, 3348, 3349, 3350, 3351, 3352, 3353, 3354, 3355, 3356, 3357, 3358, 3359, 3360, 3361, 3362, 3363, 3364, 3365, 3366, 3367, 3368, 3369, 3370, 3371, 3372, 3373, 3374, 3375, 3376, 3377, 3378, 3379, 3380, 3381, 3382, 3383, 3384, 3385, 3386, 3387, 3388, 3389, 3390, 3391, 3392, 3393, 3394, 3395, 3396, 3397, 3398, 3399, 3400, 3401, 3402, 3403, 3404, 3405, 3406, 3407, 3408, 3409, 3410, 3411, 3412, 3413, 3414, 3415, 3416, 3417, 3418, 3419, 3420, 3421, 3422, 3423, 3424, 3425, 3426, 3427, 0, 3428, 3429, 3430, 3431, 3432, 3433, 3434, 3435, 3436, 3437, 3438, 3439, 3440, 3441, 3442, 3443, 3444, 3445, 3446, 3447, 3448, 3449, 3450, 3451, 3452, 3453, 3454, 3455, 3456, 3457, 3458, 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, 3467, 3468, 3469, 3470, 3471, 3472, 3473, 3474, 3475, 3476, 3477, 3478, 3479, 3480, 3481, 3482, 3483, 3484, 3485, 3486, 3487, 3488, 3489, 3490, 3491, 3492, 3493, 3494, 3495, 3496, 3497, 3498, 3499, 3500, 3501, 3502, 3503, 3504, 3505, 3506, 3507, 3508, 3509, 3510, 3511, 3512, 3513, 3514, 3515, 3516, 3517, 3518, 3519, 3520, 3521, 3522, 3523, 3524, 3525, 3526, 3527, 3528, 3529, 3530, 3531, 3532, 3533, 3534, 3535, 3536, 3537, 3538, 3539, 3540, 3541, 3542, 3543, 3544, 3545, 3546, 3547, 3548, 3549, 3550, 3551, 3552, 3553, 3554, 3555, 3556, 3557, 3558, 3559, 3560, 3561, 3562, 3563, 3564, 3565, 3566, 3567, 3568, 3569, 3570, 3571, 3572, 3573, 3574, 3575, 3576, 3577, 3578, 3579, 3580, 3581, 3582, 3583, 3584, 3585, 3586, 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3594, 3595, 3596, 3597, 3598, 3599, 3600, 3601, 3602, 3603, 3604, 3605, 3606, 3607, 3608, 3609, 3610, 3611, 3612, 3613, 3614, 3615, 3616, 3617, 3618, 3619, 3620, 3621, 3622, 3623, 3624, 3625, 3626, 3627, 3628, 3629, 3630, 3631, 3632, 3633, 3634, 3635, 3636, 3637, 3638, 3639, 3640, 3641, 3642, 3643, 3644, 3645, 3646, 3647, 3648, 3649, 3650, 3651, 3652, 3653, 3654, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662, 3663, 3664, 3665, 3666, 3667, 3668, 3669, 3670, 3671, 3672, 3673, 3674, 3675, 3676, 3677, 3678, 3679, 3680, 3681, 3682, 3683, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1179, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 523, 523, 523, 523, 523, 523, 1044, 1044, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1179, 1428, 1428, 1428, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3684, 3685, 3686, 3687, 3688, 3689, 3690, 3691, 3692, 3693, 3694, 3695, 3696, 3697, 3698, 3699, 3700, 3701, 3702, 3703, 3704, 3705, 3706, 3707, 3708, 3709, 3710, 3711, 3712, 3713, 3714, 3715, 3716, 3717, 3718, 3719, 3720, 3721, 3722, 3723, 3724, 3725, 3726, 3727, 3728, 3729, 1160, 540, 839, 839, 839, 9, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 9, 522, 3730, 3731, 3732, 3733, 3734, 3735, 3736, 3737, 3738, 3739, 3740, 3741, 3742, 3743, 3744, 3745, 3746, 3747, 3748, 3749, 3750, 3751, 3752, 3753, 3754, 3755, 3756, 3757, 3758, 3759, 540, 540, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 540, 540, 1044, 1044, 1044, 1044, 1044, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 522, 522, 522, 522, 522, 522, 522, 522, 522, 47, 47, 3760, 3761, 3762, 3763, 3764, 3765, 3766, 3767, 3768, 3769, 3770, 3771, 3772, 3773, 214, 214, 3774, 3775, 3776, 3777, 3778, 3779, 3780, 3781, 3782, 3783, 3784, 3785, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 3794, 3795, 3796, 3797, 3798, 3799, 3800, 3801, 3802, 3803, 3804, 3805, 3806, 3807, 3808, 3809, 3810, 3811, 3812, 3813, 3814, 3815, 3816, 3817, 3818, 3819, 3820, 3821, 3822, 3823, 3824, 3825, 3826, 3827, 3828, 3829, 3830, 3831, 3832, 3833, 3834, 3835, 3836, 1523, 1523, 1523, 1523, 1523, 1523, 1523, 214, 3837, 3838, 3839, 3840, 3841, 3842, 3843, 3844, 3845, 3846, 3847, 3848, 3849, 3850, 3851, 522, 3852, 3852, 3853, 3854, 3855, 214, 340, 3856, 3857, 3858, 3859, 214, 214, 3860, 3861, 3862, 3863, 3864, 3865, 3866, 3867, 3868, 3869, 3870, 3871, 3872, 3873, 3874, 3875, 3876, 3877, 3878, 3879, 3880, 3881, 3882, 3883, 3884, 0, 3885, 3886, 3887, 3888, 3889, 3890, 3891, 3892, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 3893, 3894, 214, 340, 340, 340, 340, 1160, 1160, 1160, 1153, 1160, 1160, 1160, 1168, 1160, 1160, 1160, 1160, 1153, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1153, 1153, 1159, 77, 77, 1085, 1085, 0, 0, 0, 0, 1190, 1190, 1190, 1190, 1190, 1190, 1191, 1191, 1189, 3895, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1428, 1428, 1428, 1428, 0, 0, 0, 0, 0, 0, 0, 0, 1159, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1168, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 1177, 1177, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 1160, 1160, 1160, 1160, 1160, 1160, 1177, 1177, 1177, 1160, 1177, 1160, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1153, 553, 553, 553, 1177, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1452, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1177, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 1327, 0, 0, 0, 1153, 1153, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1180, 1159, 1159, 1153, 1153, 1153, 1153, 1159, 1159, 1153, 1159, 1159, 1159, 1452, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 0, 1179, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 1177, 1177, 340, 1160, 1160, 1160, 1160, 1153, 523, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1178, 1160, 1160, 1160, 340, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1153, 1153, 1159, 1159, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1153, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1159, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 1177, 1177, 1177, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1179, 1160, 1160, 1160, 1160, 1160, 1160, 1191, 1191, 1191, 1160, 1284, 1153, 1284, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 540, 1160, 540, 540, 553, 1160, 1160, 540, 540, 1160, 1160, 1160, 1160, 1160, 540, 540, 1160, 540, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1179, 1177, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1153, 1153, 1159, 1159, 1177, 1177, 1160, 1179, 1179, 1159, 1168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 214, 3896, 214, 214, 214, 214, 214, 214, 214, 3852, 3897, 3898, 3899, 3900, 214, 214, 214, 214, 214, 214, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3901, 3902, 3903, 3904, 3905, 3906, 3907, 3908, 3909, 3910, 3911, 3912, 3913, 3914, 3915, 3916, 3917, 3918, 3919, 3920, 3921, 3922, 3923, 3924, 3925, 3926, 3927, 3928, 3929, 3930, 3931, 3932, 3933, 3934, 3935, 3936, 3937, 3938, 3939, 3940, 3941, 3942, 3943, 3944, 3945, 3946, 3947, 3948, 3949, 3950, 3951, 3952, 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 3970, 3971, 3972, 3973, 3974, 3975, 3976, 3977, 3978, 3979, 3980, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1153, 1159, 1159, 1153, 1159, 1159, 1177, 1159, 1168, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3981, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 3982, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 1330, 0, 0, 0, 0, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 0, 0, 0, 0, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3983, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3985, 3986, 3987, 3988, 3989, 3990, 3991, 3992, 3992, 3993, 3994, 3995, 3996, 3997, 3998, 3999, 4000, 4001, 4002, 4003, 4004, 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 4034, 4035, 4036, 4037, 4038, 4039, 4040, 4041, 4042, 4043, 4044, 4045, 4046, 4047, 4048, 4049, 4050, 4051, 4052, 4053, 4054, 4055, 4056, 4057, 4058, 4059, 4060, 4061, 4062, 4063, 4064, 4065, 4066, 4067, 4068, 4069, 4070, 4071, 4072, 4073, 4074, 4075, 4004, 4076, 4077, 4078, 4079, 4080, 4081, 4082, 4083, 4084, 4085, 4086, 4087, 4088, 4089, 4090, 4091, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4110, 4111, 4112, 4113, 4114, 4115, 4116, 4117, 4118, 4119, 4120, 4121, 4122, 4123, 4124, 4125, 4126, 4127, 4128, 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, 4138, 4139, 4140, 4141, 4142, 4143, 4094, 4144, 4145, 4146, 4147, 4148, 4149, 4150, 4151, 4078, 4152, 4153, 4154, 4155, 4156, 4157, 4158, 4159, 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, 4169, 4170, 4171, 4004, 4172, 4173, 4174, 4175, 4176, 4177, 4178, 4179, 4180, 4181, 4182, 4183, 4184, 4185, 4186, 4187, 4188, 4189, 4190, 4191, 4192, 4193, 4194, 4195, 4196, 4197, 4198, 4080, 4199, 4200, 4201, 4202, 4203, 4204, 4205, 4206, 4207, 4208, 4209, 4210, 4211, 4212, 4213, 4214, 4215, 4216, 4217, 4218, 4219, 4220, 4221, 4222, 4223, 4224, 4225, 4226, 4227, 4228, 4229, 4230, 4231, 4232, 4233, 4234, 4235, 4236, 4237, 4238, 4239, 4240, 4241, 4242, 4243, 4244, 4245, 4246, 4247, 4248, 1160, 1160, 4249, 1160, 4250, 1160, 1160, 4251, 4252, 4253, 4254, 4255, 4256, 4257, 4258, 4259, 4260, 1160, 4261, 1160, 4262, 1160, 1160, 4263, 4264, 1160, 1160, 1160, 4265, 4266, 4267, 4268, 4269, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, 4287, 4288, 4289, 4290, 4291, 4292, 4293, 4294, 4295, 4296, 4297, 4298, 4299, 4300, 4301, 4302, 4303, 4304, 4305, 4306, 4307, 4308, 4309, 4133, 4310, 4311, 4312, 4313, 4314, 4315, 4315, 4316, 4317, 4318, 4319, 4320, 4321, 4322, 4323, 4263, 4324, 4325, 4326, 4327, 4328, 4329, 0, 0, 4330, 4331, 4332, 4333, 4334, 4335, 4336, 4337, 4277, 4338, 4339, 4340, 4249, 4341, 4342, 4343, 4344, 4345, 4346, 4347, 4348, 4349, 4350, 4351, 4352, 4286, 4353, 4287, 4354, 4355, 4356, 4357, 4358, 4250, 4025, 4359, 4360, 4361, 4095, 4182, 4362, 4363, 4294, 4364, 4295, 4365, 4366, 4367, 4252, 4368, 4369, 4370, 4371, 4372, 4253, 4373, 4374, 4375, 4376, 4377, 4378, 4309, 4379, 4380, 4133, 4381, 4313, 4382, 4383, 4384, 4385, 4386, 4318, 4387, 4262, 4388, 4319, 4076, 4389, 4320, 4390, 4322, 4391, 4392, 4393, 4394, 4395, 4324, 4258, 4396, 4325, 4397, 4326, 4398, 3992, 4399, 4400, 4401, 4402, 4403, 4404, 4405, 4406, 4407, 4408, 4409, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4410, 4411, 4412, 4413, 4414, 4415, 4416, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4417, 4418, 4419, 4420, 4421, 0, 0, 0, 0, 0, 4422, 4423, 4424, 4425, 4426, 4427, 4428, 4429, 4430, 4431, 4432, 4433, 4434, 4435, 4436, 4437, 4438, 4439, 4440, 4441, 4442, 4443, 4444, 4445, 4446, 4447, 0, 4448, 4449, 4450, 4451, 4452, 0, 4453, 0, 4454, 4455, 0, 4456, 4457, 0, 4458, 4459, 4460, 4461, 4462, 4463, 4464, 4465, 4466, 4467, 4468, 4469, 4470, 4471, 4472, 4473, 4474, 4475, 4476, 4477, 4478, 4479, 4480, 4481, 4482, 4483, 4484, 4485, 4486, 4487, 4488, 4489, 4490, 4491, 4492, 4493, 4494, 4495, 4496, 4497, 4498, 4499, 4500, 4501, 4502, 4503, 4504, 4505, 4506, 4507, 4508, 4509, 4510, 4511, 4512, 4513, 4514, 4515, 4516, 4517, 4518, 4519, 4520, 4521, 4522, 4523, 4524, 4525, 4526, 4527, 4528, 4529, 4530, 4531, 4532, 4533, 4534, 4535, 4536, 4537, 4538, 4539, 4540, 4541, 4542, 4543, 4544, 4545, 4546, 4547, 4548, 4549, 4550, 4551, 4552, 4553, 4554, 4555, 4556, 4557, 4558, 4559, 4560, 4561, 4562, 4563, 4564, 4565, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 4566, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4567, 4568, 4569, 4570, 4571, 4572, 4573, 4574, 4575, 4576, 4577, 4578, 4579, 4580, 4581, 4582, 4583, 4584, 4585, 4586, 4587, 4588, 4589, 4590, 4591, 4592, 4593, 4594, 4595, 4596, 4597, 4598, 4599, 4600, 4601, 4602, 4603, 4604, 4605, 4606, 4607, 4608, 4609, 4610, 4611, 4612, 4613, 4614, 4605, 4615, 4616, 4617, 4618, 4619, 4620, 4621, 4622, 4623, 4624, 4625, 4626, 4627, 4628, 4629, 4630, 4631, 4632, 4633, 4634, 4635, 4636, 4637, 4638, 4639, 4640, 4641, 4642, 4643, 4644, 4645, 4646, 4647, 4648, 4649, 4650, 4651, 4652, 4653, 4654, 4655, 4656, 4657, 4658, 4659, 4660, 4661, 4662, 4663, 4664, 4665, 4666, 4667, 4668, 4669, 4670, 4671, 4672, 4673, 4674, 4675, 4676, 4677, 4678, 4679, 4680, 4681, 4682, 4683, 4684, 4685, 4686, 4687, 4688, 4689, 4690, 4691, 4692, 4693, 4694, 4695, 4696, 4697, 4698, 4699, 4700, 4701, 4702, 4703, 4704, 4705, 4706, 4707, 4708, 4709, 4710, 4711, 4712, 4713, 4714, 4606, 4715, 4716, 4717, 4718, 4719, 4720, 4721, 4722, 4723, 4724, 4725, 4726, 4727, 4728, 4729, 4730, 4731, 4732, 4733, 4734, 4735, 4736, 4737, 4738, 4739, 4740, 4741, 4742, 4743, 4744, 4745, 4746, 4747, 4748, 4749, 4750, 4751, 4752, 4753, 4754, 4755, 4756, 4757, 4758, 4759, 4760, 4761, 4762, 4763, 4764, 4765, 4766, 4767, 4768, 4769, 4770, 4771, 4772, 4773, 4774, 4775, 4776, 4777, 4778, 4779, 4780, 4781, 4782, 4783, 4784, 4785, 4786, 4787, 4788, 4789, 4790, 4791, 4792, 4793, 4794, 4795, 4796, 4797, 4798, 4799, 4800, 4801, 4802, 4803, 4804, 4805, 4806, 4807, 4808, 4809, 4810, 4811, 4812, 4813, 4814, 4815, 4816, 4817, 4818, 4819, 4820, 4821, 4822, 4823, 4824, 4825, 4826, 4827, 4828, 4829, 4830, 4831, 4832, 4833, 4834, 4835, 4836, 4837, 4838, 4839, 4840, 4841, 4842, 4843, 4844, 4845, 4846, 4847, 4848, 4849, 4850, 4851, 4852, 4853, 4854, 4855, 4856, 4857, 4858, 4859, 4860, 4861, 4862, 4863, 4864, 4865, 4866, 4867, 4868, 4869, 4870, 4871, 4872, 4873, 4874, 4875, 4876, 4877, 4878, 4879, 4880, 4881, 4882, 4883, 4884, 4885, 4886, 4887, 4888, 4889, 4890, 4891, 4892, 4893, 4894, 4895, 4896, 4897, 4898, 4899, 4900, 4901, 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, 4920, 4921, 4922, 4923, 4924, 4925, 4926, 4927, 2956, 2955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4928, 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4962, 4963, 4964, 4965, 4966, 4967, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 4977, 4978, 4979, 4980, 4981, 4982, 4983, 4984, 4985, 4986, 4987, 4988, 4989, 4990, 4991, 0, 0, 4992, 4993, 4994, 4995, 4996, 4997, 4998, 4999, 5000, 5001, 5002, 5003, 5004, 5005, 5006, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 5016, 5017, 5018, 5019, 5020, 5021, 5022, 5023, 5024, 5025, 5026, 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, 5045, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5057, 5058, 1085, 0, 0, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 5059, 5060, 5061, 5062, 5063, 5064, 5065, 5066, 5067, 5068, 0, 0, 0, 0, 0, 0, 540, 540, 540, 540, 540, 540, 540, 553, 553, 553, 553, 553, 553, 553, 540, 540, 5069, 5070, 5071, 5072, 5072, 5073, 5074, 5075, 5076, 5077, 5078, 5079, 5080, 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 1428, 1428, 5089, 5090, 5091, 5091, 5091, 5091, 5092, 5092, 5092, 5093, 5094, 5095, 0, 5096, 5097, 5098, 5099, 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108, 5109, 5110, 5111, 5112, 5113, 5114, 0, 5115, 5116, 5117, 5118, 0, 0, 0, 0, 5119, 5120, 5121, 1118, 5122, 0, 5123, 5124, 5125, 5126, 5127, 5128, 5129, 5130, 5131, 5132, 5133, 5134, 5135, 5136, 5137, 5138, 5139, 5140, 5141, 5142, 5143, 5144, 5145, 5146, 5147, 5148, 5149, 5150, 5151, 5152, 5153, 5154, 5155, 5156, 5157, 5158, 5159, 5160, 5161, 5162, 5163, 5164, 5165, 5166, 5167, 5168, 5169, 5170, 5171, 5172, 5173, 5174, 5175, 5176, 5177, 5178, 5179, 5180, 5181, 5182, 5183, 5184, 5185, 5186, 5187, 5188, 5189, 5190, 5191, 5192, 5193, 5194, 5195, 5196, 5197, 5198, 5199, 5200, 5201, 5202, 5203, 5204, 5205, 5206, 5207, 5208, 5209, 5210, 5211, 5212, 5213, 5214, 5215, 5216, 5217, 5218, 5219, 5220, 5221, 5222, 5223, 5224, 5225, 5226, 5227, 5228, 5229, 5230, 5231, 5232, 5233, 5234, 5235, 5236, 5237, 5238, 5239, 5240, 5241, 5242, 5243, 5244, 5245, 5246, 5247, 5248, 5249, 5250, 5251, 5252, 5253, 5254, 5255, 5256, 5257, 0, 0, 81, 0, 5258, 5259, 5260, 5261, 5262, 5263, 5264, 5265, 5266, 5267, 5268, 5269, 5270, 5271, 5272, 5273, 5274, 5275, 5276, 5277, 5278, 5279, 5280, 5281, 5282, 5283, 5284, 5285, 5286, 5287, 5288, 5289, 5290, 5291, 5292, 5293, 5294, 5295, 5296, 5297, 5298, 5299, 5300, 5301, 5302, 5303, 5304, 5305, 5306, 5307, 5308, 5309, 5310, 5311, 5312, 5313, 5314, 5315, 5316, 5317, 5318, 5319, 5320, 5321, 5322, 5323, 5324, 5325, 5326, 5327, 5328, 5329, 5330, 5331, 5332, 5333, 5334, 5335, 5336, 5337, 5338, 5339, 5340, 5341, 5342, 5343, 5344, 5345, 5346, 5347, 5348, 5349, 5350, 5351, 5352, 5353, 5354, 5355, 5356, 5357, 5358, 5359, 5360, 5361, 5362, 5363, 5364, 5365, 5366, 5367, 5368, 5369, 5370, 5371, 5372, 5373, 5374, 5375, 5376, 5377, 5378, 5379, 5380, 5381, 5382, 5383, 5384, 5385, 5386, 5387, 5388, 5389, 5390, 5391, 5392, 5393, 5394, 5395, 5396, 5397, 5398, 5399, 5400, 5401, 5402, 5403, 5404, 5405, 5406, 5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5421, 5422, 5423, 5424, 5425, 5426, 5427, 5428, 5429, 5430, 5431, 5432, 5433, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5442, 5443, 5444, 5445, 5446, 5447, 0, 0, 0, 5448, 5449, 5450, 5451, 5452, 5453, 0, 0, 5454, 5455, 5456, 5457, 5458, 5459, 0, 0, 5460, 5461, 5462, 5463, 5464, 5465, 0, 0, 5466, 5467, 5468, 0, 0, 0, 5469, 5470, 5471, 5472, 5473, 5474, 5475, 0, 5476, 5477, 5478, 5479, 5480, 5481, 5482, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5483, 5483, 5483, 1085, 77, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 340, 1160, 1160, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 1160, 1160, 1160, 340, 340, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 0, 340, 340, 0, 340, 340, 1160, 340, 340, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 340, 1160, 0, 0, 340, 340, 1160, 340, 340, 1160, 340, 1160, 1160, 1160, 340, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 1044, 9, 1044, 0, 0, 0, 0, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 1190, 5484, 1190, 5484, 1190, 1190, 1190, 1190, 1190, 1190, 5484, 1190, 5484, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 1191, 838, 1191, 838, 838, 838, 838, 838, 1191, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5485, 5486, 5486, 5486, 5486, 5486, 5486, 5485, 5486, 5485, 5485, 5485, 5485, 1427, 1427, 1220, 1427, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 77, 1427, 1427, 77, 838, 838, 0, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 77, 77, 0, 0, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 1160, 340, 340, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 553, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 5487, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 5484, 5484, 5484, 5484, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1426, 340, 340, 340, 340, 340, 340, 340, 340, 1426, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 540, 540, 540, 540, 540, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1177, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1177, 2227, 2227, 2227, 2227, 2227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5488, 5489, 5490, 5491, 5492, 5493, 5494, 5495, 5496, 5497, 5498, 5499, 5500, 5501, 5502, 5503, 5504, 5505, 5506, 5507, 5508, 5509, 5510, 5511, 5512, 5513, 5514, 5515, 5516, 5517, 5518, 5519, 5520, 5521, 5522, 5523, 5524, 5525, 5526, 5527, 5528, 5529, 5530, 5531, 5532, 5533, 5534, 5535, 5536, 5537, 5538, 5539, 5540, 5541, 5542, 5543, 5544, 5545, 5546, 5547, 5548, 5549, 5550, 5551, 5552, 5553, 5554, 5555, 5556, 5557, 5558, 5559, 5560, 5561, 5562, 5563, 5564, 5565, 5566, 5567, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 1160, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 0, 0, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 0, 0, 0, 0, 0, 0, 5568, 5569, 5570, 5571, 5572, 5573, 5574, 5575, 5576, 5577, 5578, 5579, 5580, 5581, 5582, 5583, 5584, 5585, 5586, 5587, 5588, 5589, 5590, 5591, 5592, 5593, 5594, 5595, 5596, 5597, 5598, 5599, 5600, 5601, 5602, 5603, 0, 0, 0, 0, 5604, 5605, 5606, 5607, 5608, 5609, 5610, 5611, 5612, 5613, 5614, 5615, 5616, 5617, 5618, 5619, 5620, 5621, 5622, 5623, 5624, 5625, 5626, 5627, 5628, 5629, 5630, 5631, 5632, 5633, 5634, 5635, 5636, 5637, 5638, 5639, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 1160, 340, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 340, 340, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 340, 1160, 340, 340, 340, 340, 340, 1160, 1160, 1160, 340, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 340, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 340, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 340, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 340, 1160, 1160, 340, 1160, 1160, 340, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 340, 1160, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1156, 1156, 1156, 1106, 1156, 1106, 0, 0, 1156, 0, 1106, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1106, 1156, 1156, 1106, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 0, 1156, 1106, 0, 0, 0, 1156, 0, 0, 1156, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 0, 1103, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 1156, 1156, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1156, 1106, 1156, 1106, 1106, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1156, 5641, 5641, 5640, 5640, 5642, 5642, 5642, 5642, 5640, 1156, 1106, 1156, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 0, 0, 0, 0, 0, 0, 0, 0, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 0, 1106, 1106, 0, 0, 0, 0, 0, 5640, 5640, 5640, 5640, 5640, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 5640, 5640, 5640, 5640, 5640, 5640, 0, 0, 0, 9, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 0, 0, 0, 0, 0, 1103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1156, 1156, 1106, 0, 0, 0, 0, 5642, 5640, 1106, 1156, 5640, 5640, 5640, 5640, 5642, 5642, 5642, 5640, 5640, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 0, 0, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5640, 5640, 5640, 5640, 5642, 5640, 5642, 5642, 5642, 5642, 1156, 1153, 1153, 1153, 0, 1153, 1153, 0, 0, 0, 0, 0, 1153, 553, 1153, 540, 1156, 1156, 1156, 1156, 0, 1156, 1156, 1156, 0, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 0, 0, 0, 0, 540, 566, 553, 0, 0, 0, 0, 1168, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 0, 0, 0, 0, 0, 0, 0, 0, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 1158, 0, 0, 0, 0, 0, 0, 0, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 5640, 5640, 1103, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 5640, 5640, 5640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1106, 1156, 1156, 1106, 1106, 1106, 1156, 1106, 5643, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 540, 553, 0, 0, 0, 0, 5640, 5642, 5642, 5642, 5642, 1158, 1158, 1158, 1158, 1103, 1103, 1103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1106, 1156, 1156, 1156, 1156, 1106, 1106, 1106, 1156, 1156, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1156, 1156, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 1156, 1156, 1106, 1106, 1156, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1156, 1156, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 0, 0, 5640, 5640, 5640, 5640, 5640, 5640, 5642, 5640, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1106, 1156, 1106, 0, 0, 0, 0, 0, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 5640, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1156, 1106, 1106, 1156, 1106, 0, 0, 0, 0, 0, 0, 0, 1158, 1158, 1158, 1158, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5640, 5640, 5640, 5642, 5640, 5640, 5642, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5644, 5645, 5646, 5647, 5648, 5649, 5650, 5651, 5652, 5653, 5654, 5655, 5656, 5657, 5658, 5659, 5660, 5661, 5662, 5663, 5664, 5665, 5666, 5667, 5668, 5669, 5670, 5671, 5672, 5673, 5674, 5675, 5676, 5677, 5678, 5679, 5680, 5681, 5682, 5683, 5684, 5685, 5686, 5687, 5688, 5689, 5690, 5691, 5692, 5693, 5694, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5695, 5696, 5697, 5698, 5699, 5700, 5701, 5702, 5703, 5704, 5705, 5706, 5707, 5708, 5709, 5710, 5711, 5712, 5713, 5714, 5715, 5716, 5717, 5718, 5719, 5720, 5721, 5722, 5723, 5724, 5725, 5726, 5727, 5728, 5729, 5730, 5731, 5732, 5733, 5734, 5735, 5736, 5737, 5738, 5739, 5740, 5741, 5742, 5743, 5744, 5745, 0, 0, 0, 0, 0, 0, 0, 5640, 5640, 5640, 5640, 5640, 5640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5746, 5746, 5747, 5746, 5746, 5746, 5746, 5746, 5747, 5746, 5746, 5747, 5747, 5747, 5746, 5746, 5747, 5746, 5746, 5746, 5747, 5747, 5746, 5747, 5747, 5746, 5746, 5747, 5747, 5747, 5747, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1159, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1168, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 0, 0, 0, 0, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1220, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1168, 1153, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5748, 5749, 5750, 5751, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5752, 1160, 1160, 1160, 1160, 1160, 5753, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1159, 1159, 1168, 5754, 1177, 1177, 5755, 1177, 1177, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 0, 0, 0, 0, 0, 0, 540, 540, 540, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5756, 1153, 1153, 1153, 1153, 1159, 1153, 5757, 5758, 1153, 5759, 5760, 1168, 1168, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1177, 1177, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 340, 340, 340, 1160, 340, 340, 340, 340, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 1160, 340, 340, 340, 340, 1180, 1044, 1044, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1153, 1159, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1452, 1160, 1237, 1237, 1160, 1177, 1177, 1177, 1177, 1177, 1180, 1153, 1153, 1177, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1160, 1177, 1160, 1177, 1177, 1177, 0, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1159, 1159, 1153, 1452, 1180, 1153, 1177, 1177, 1177, 1177, 1177, 1177, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 0, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1177, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1180, 1168, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1153, 1153, 1159, 1159, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 0, 0, 1180, 1160, 5761, 1159, 1153, 1159, 1159, 1159, 1159, 0, 0, 5762, 1159, 0, 0, 5763, 5764, 1452, 0, 0, 1160, 0, 0, 0, 0, 0, 0, 5765, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 0, 0, 540, 540, 540, 540, 540, 540, 540, 0, 0, 0, 540, 540, 540, 540, 540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1168, 1153, 1153, 1159, 1180, 1160, 1160, 1160, 1160, 1177, 1177, 1177, 1177, 1177, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 1177, 0, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5766, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 5767, 5768, 5769, 5770, 5771, 5772, 1153, 1153, 1159, 1168, 1180, 1160, 1160, 1177, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5773, 1159, 1159, 1153, 1153, 1153, 1153, 0, 0, 5774, 5775, 5776, 5777, 1153, 1153, 1159, 1168, 1180, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1177, 1160, 1160, 1160, 1160, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1159, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1159, 1153, 1159, 1168, 1153, 1177, 1177, 1177, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1153, 1159, 1153, 1159, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1452, 1180, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 1153, 1153, 1153, 1159, 1159, 1153, 1153, 1153, 1153, 1159, 1153, 1153, 1153, 1153, 1168, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 1190, 1177, 1177, 1177, 1191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5778, 5779, 5780, 5781, 5782, 5783, 5784, 5785, 5786, 5787, 5788, 5789, 5790, 5791, 5792, 5793, 5794, 5795, 5796, 5797, 5798, 5799, 5800, 5801, 5802, 5803, 5804, 5805, 5806, 5807, 5808, 5809, 5810, 5811, 5812, 5813, 5814, 5815, 5816, 5817, 5818, 5819, 5820, 5821, 5822, 5823, 5824, 5825, 5826, 5827, 5828, 5829, 5830, 5831, 5832, 5833, 5834, 5835, 5836, 5837, 5838, 5839, 5840, 5841, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 5842, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 0, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 5843, 1160, 1177, 1177, 1177, 1177, 1177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1178, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 1044, 1044, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 0, 1159, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1159, 1153, 1153, 1159, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 1426, 0, 1044, 1044, 1044, 1044, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 0, 0, 0, 0, 1044, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 566, 566, 566, 566, 566, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 540, 540, 540, 540, 540, 540, 540, 1044, 1044, 1044, 1044, 1044, 838, 838, 838, 838, 523, 523, 523, 523, 1044, 838, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 1250, 0, 5484, 5484, 5484, 5484, 5484, 5484, 5484, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1153, 1153, 1153, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 1179, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1179, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 340, 340, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 340, 340, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 1160, 340, 340, 340, 1160, 340, 340, 1160, 1160, 1160, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 340, 1160, 340, 340, 340, 340, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 0, 0, 0, 0, 0, 340, 340, 340, 340, 340, 340, 340, 340, 340, 340, 0, 0, 1191, 1153, 566, 1177, 81, 81, 81, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 838, 838, 838, 838, 838, 838, 838, 838, 1191, 1191, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1191, 838, 1191, 838, 838, 838, 1191, 838, 838, 838, 1191, 1191, 1191, 838, 1191, 838, 1191, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 838, 1191, 1191, 1191, 1191, 1191, 838, 838, 838, 838, 1191, 838, 1191, 1191, 1191, 838, 838, 838, 1191, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 838, 838, 838, 1191, 1191, 838, 838, 838, 838, 1191, 1191, 838, 838, 838, 1191, 1191, 838, 838, 838, 838, 1191, 1191, 1191, 1191, 1191, 838, 838, 838, 838, 838, 838, 1191, 838, 838, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 838, 838, 1191, 838, 1191, 838, 1191, 1191, 1191, 1191, 1191, 1191, 838, 838, 838, 1191, 1191, 838, 838, 838, 1191, 838, 1191, 838, 1191, 838, 838, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 838, 1191, 1191, 1191, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1191, 838, 1191, 838, 838, 838, 838, 838, 838, 838, 1191, 838, 1191, 838, 838, 838, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 1191, 838, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 838, 838, 1191, 1191, 1191, 1191, 838, 838, 0, 0, 1191, 838, 1191, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1191, 1191, 1191, 1191, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 5844, 5845, 1191, 838, 838, 1191, 838, 5846, 5847, 5848, 5849, 5850, 5851, 5852, 5853, 5854, 566, 566, 566, 1191, 1191, 1191, 5855, 5856, 5857, 5858, 5859, 5860, 81, 81, 81, 81, 81, 81, 81, 81, 553, 553, 553, 553, 553, 553, 553, 553, 838, 838, 540, 540, 540, 540, 540, 553, 553, 838, 838, 838, 838, 838, 838, 1191, 1191, 838, 838, 1191, 1191, 1191, 1191, 838, 838, 1191, 1191, 838, 838, 838, 838, 838, 1191, 1191, 838, 1191, 1191, 838, 838, 540, 540, 540, 540, 1191, 1191, 1191, 1191, 1191, 1191, 838, 1191, 1191, 1191, 1191, 5861, 5862, 5863, 5864, 5865, 5866, 5867, 5868, 838, 838, 838, 838, 838, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 838, 838, 838, 838, 838, 838, 838, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 540, 540, 540, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 5484, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 1190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5869, 2157, 2132, 5870, 2159, 2160, 5871, 2139, 2142, 5872, 5873, 2143, 2162, 2145, 5874, 2147, 2148, 2149, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 2153, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 0, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5901, 0, 5903, 2175, 0, 0, 5906, 0, 0, 5909, 5910, 0, 0, 5913, 5914, 5915, 5916, 0, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 0, 5930, 0, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 0, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5869, 2157, 0, 5870, 2159, 2160, 5871, 0, 0, 5872, 5873, 2143, 2162, 2145, 5874, 2147, 2148, 0, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 0, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5869, 2157, 0, 5870, 2159, 2160, 5871, 0, 2142, 5872, 5873, 2143, 2162, 0, 5874, 0, 0, 0, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 0, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5869, 2157, 2132, 5870, 2159, 2160, 5871, 2139, 2142, 5872, 5873, 2143, 2162, 2145, 5874, 2147, 2148, 2149, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 2153, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5869, 2157, 2132, 5870, 2159, 2160, 5871, 2139, 2142, 5872, 5873, 2143, 2162, 2145, 5874, 2147, 2148, 2149, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 2153, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5869, 2157, 2132, 5870, 2159, 2160, 5871, 2139, 2142, 5872, 5873, 2143, 2162, 2145, 5874, 2147, 2148, 2149, 5875, 5876, 5877, 5878, 5879, 5880, 5881, 2153, 5882, 5883, 5884, 5885, 2158, 5886, 2138, 2140, 2168, 2177, 5887, 2144, 5888, 5889, 2163, 5890, 5891, 5892, 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5901, 5902, 5903, 2175, 5904, 5905, 5906, 5907, 5908, 5909, 5910, 5911, 5912, 5913, 5914, 5915, 5916, 5917, 5918, 5919, 5920, 5921, 5922, 5923, 5924, 5925, 5926, 5927, 5928, 2176, 5929, 5930, 5931, 5950, 5932, 5933, 5934, 5935, 5936, 5937, 5938, 5939, 5940, 5941, 5942, 5943, 5944, 5945, 5946, 5947, 5948, 5949, 5951, 5952, 0, 0, 5953, 5954, 2172, 5955, 5956, 5957, 5958, 5959, 5960, 5961, 5962, 5963, 5964, 5965, 5966, 5967, 5968, 5969, 5970, 5971, 5972, 5973, 5974, 5975, 5976, 5977, 5978, 5979, 5980, 5981, 5982, 5983, 5984, 5985, 5986, 5987, 5988, 5989, 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999, 6000, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 2173, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, 2171, 6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, 2170, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6005, 6060, 6061, 6062, 6063, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 2173, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, 2171, 6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, 2170, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6064, 6060, 6061, 6062, 6063, 5953, 5954, 2172, 5955, 5956, 5957, 5958, 5959, 5960, 5961, 5962, 5963, 5964, 5965, 5966, 5967, 5968, 5969, 5970, 5971, 5972, 5973, 5974, 5975, 5976, 5977, 5978, 5979, 5980, 5981, 5982, 5983, 5984, 5985, 5986, 5987, 5988, 5989, 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999, 6000, 6001, 6002, 6003, 6004, 6005, 6006, 6007, 6008, 6009, 6010, 6011, 6012, 6013, 6014, 6015, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, 6024, 2173, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, 6033, 6034, 6035, 6036, 2171, 6037, 6038, 6039, 6040, 6041, 6042, 6043, 6044, 6045, 6046, 6047, 6048, 2170, 6049, 6050, 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, 6064, 6060, 6061, 6062, 6063, 6065, 6066, 0, 0, 6067, 6068, 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6067, 6068, 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6067, 6068, 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6067, 6068, 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6077, 6078, 6079, 6080, 6081, 6082, 6083, 6084, 6085, 6086, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 838, 838, 838, 838, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 838, 838, 838, 838, 838, 838, 838, 838, 1153, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 838, 1153, 838, 838, 1044, 1044, 1044, 1044, 1044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1153, 1153, 1153, 1153, 1153, 0, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 1153, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 540, 540, 540, 540, 540, 540, 540, 0, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 540, 0, 0, 540, 540, 540, 540, 540, 540, 540, 0, 540, 540, 0, 540, 540, 540, 540, 540, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1106, 1156, 1106, 1106, 1106, 1156, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1156, 1106, 1106, 1106, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1106, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1106, 1156, 1106, 1156, 1156, 1106, 1106, 1106, 1106, 1106, 1156, 1156, 1156, 1106, 1156, 1106, 1156, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1106, 1156, 1156, 1156, 1106, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1156, 1106, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1106, 1106, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 1156, 1106, 1156, 1156, 1156, 1156, 1156, 1156, 0, 0, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 5642, 553, 553, 553, 553, 553, 553, 553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6087, 6088, 6089, 6090, 6091, 6092, 6093, 6094, 6095, 6096, 6097, 6098, 6099, 6100, 6101, 6102, 6103, 6104, 6105, 6106, 6107, 6108, 6109, 6110, 6111, 6112, 6113, 6114, 6115, 6116, 6117, 6118, 6119, 6120, 6121, 6122, 6123, 6124, 6125, 6126, 6127, 6128, 6129, 6130, 6131, 6132, 6133, 6134, 6135, 6136, 6137, 6138, 6139, 6140, 6141, 6142, 6143, 6144, 6145, 6146, 6147, 6148, 6149, 6150, 6151, 6152, 6153, 6154, 540, 540, 540, 540, 540, 540, 1180, 0, 0, 0, 0, 0, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 1154, 0, 0, 0, 0, 1103, 1103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6155, 6156, 6157, 6158, 0, 6159, 6160, 6161, 6162, 6163, 6164, 6165, 6166, 6167, 6168, 6169, 6170, 6171, 6172, 6173, 6174, 6175, 6176, 6177, 6178, 6179, 6180, 6181, 6182, 6183, 6184, 6185, 0, 6156, 6157, 0, 6186, 0, 0, 6161, 0, 6163, 6164, 6165, 6166, 6167, 6168, 6169, 6170, 6171, 6172, 0, 6174, 6175, 6176, 6177, 0, 6179, 0, 6181, 0, 0, 0, 0, 0, 0, 6157, 0, 0, 0, 0, 6161, 0, 6163, 0, 6165, 0, 6167, 6168, 6169, 0, 6171, 6172, 0, 6174, 0, 0, 6177, 0, 6179, 0, 6181, 0, 6183, 0, 6185, 0, 6156, 6157, 0, 6187, 0, 0, 6161, 6162, 6163, 6164, 0, 6166, 6167, 6168, 6169, 6170, 6171, 6172, 0, 6174, 6175, 6176, 6177, 0, 6179, 6180, 6181, 6182, 0, 6184, 0, 6155, 6156, 6157, 6158, 6187, 6159, 6160, 6161, 6162, 6163, 0, 6165, 6166, 6167, 6168, 6169, 6170, 6171, 6172, 6173, 6174, 6175, 6176, 6177, 6178, 6179, 6180, 6181, 0, 0, 0, 0, 0, 6188, 6189, 6190, 0, 6191, 6192, 6193, 6194, 6195, 0, 6196, 6197, 6198, 6199, 6200, 6201, 6202, 6203, 6204, 6205, 6206, 6207, 6208, 6209, 6210, 6211, 6212, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1109, 1109, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6213, 6214, 6215, 6216, 6217, 6218, 6219, 6220, 6221, 6222, 6223, 1220, 1220, 0, 0, 0, 6224, 6225, 6226, 6227, 6228, 6229, 6230, 6231, 6232, 6233, 6234, 6235, 6236, 6237, 6238, 6239, 6240, 6241, 6242, 6243, 6244, 6245, 6246, 6247, 6248, 6249, 6250, 6251, 6252, 6253, 6254, 0, 6255, 6256, 6257, 6258, 6259, 6260, 6261, 6262, 6263, 6264, 6265, 6266, 6267, 6268, 6269, 6270, 6271, 6272, 6273, 6274, 6275, 6276, 6277, 6278, 6279, 6280, 6281, 3613, 6282, 6283, 6284, 6285, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 6286, 6287, 0, 0, 0, 0, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 6288, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 1191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6289, 6290, 6291, 6292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6293, 6294, 6295, 6296, 6297, 6298, 6299, 6300, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 6308, 6309, 6310, 6311, 6312, 6313, 6314, 6315, 6316, 6317, 6318, 6319, 6320, 6321, 6322, 6323, 6324, 6325, 6326, 6327, 6328, 6329, 6330, 6331, 6332, 6333, 6334, 6335, 6336, 0, 0, 0, 0, 6337, 6338, 6339, 6340, 6341, 6342, 6343, 6344, 6345, 0, 0, 0, 0, 0, 0, 0, 6346, 6347, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 6348, 6348, 6348, 6348, 6348, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 1085, 1085, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 6349, 6349, 6349, 6349, 1085, 1085, 1085, 1085, 2464, 1085, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 2464, 2464, 2464, 1085, 2464, 2464, 2464, 1085, 1085, 1085, 2465, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 2465, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 2464, 1085, 1085, 1085, 2464, 2464, 2464, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 77, 77, 77, 1085, 1085, 1085, 1085, 77, 77, 77, 1085, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 1085, 1085, 1085, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 77, 77, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 77, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 0, 1085, 1085, 1085, 1085, 1085, 1085, 2464, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 2464, 0, 0, 2464, 2464, 2464, 2464, 2464, 2464, 2464, 1085, 1085, 2464, 2464, 2464, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 1160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6350, 6351, 6352, 6353, 6354, 4271, 6355, 6356, 6357, 6358, 4272, 6359, 6360, 6361, 4273, 6362, 6363, 6364, 6365, 6366, 6367, 6368, 6369, 6370, 6371, 6372, 6373, 4331, 6374, 6375, 6376, 6377, 6378, 6379, 6380, 6381, 6382, 4336, 4274, 4275, 4337, 6383, 6384, 4082, 6385, 4276, 6386, 6387, 6388, 6389, 6389, 6389, 6390, 6391, 6392, 6393, 6394, 6395, 6396, 6397, 6398, 6399, 6400, 6401, 6402, 6403, 6404, 6405, 6406, 6407, 6407, 4339, 6408, 6409, 6410, 6411, 4278, 6412, 6413, 6414, 4235, 6415, 6416, 6417, 6418, 6419, 6420, 6421, 6422, 6423, 6424, 6425, 6426, 6427, 6428, 6429, 6430, 6431, 6432, 6433, 6434, 6435, 6436, 6437, 6438, 6439, 6440, 6440, 6441, 6442, 6443, 4078, 6444, 6445, 6446, 6447, 6448, 6449, 6450, 6451, 4283, 6452, 6453, 6454, 6455, 6456, 6457, 6458, 6459, 6460, 6461, 6462, 6463, 6464, 6465, 6466, 6467, 6468, 6469, 6470, 6471, 6472, 4024, 6473, 6474, 6475, 6475, 6476, 6477, 6477, 6478, 6479, 6480, 6481, 6482, 6483, 6484, 6485, 6486, 6487, 6488, 6489, 6490, 4284, 6491, 6492, 6493, 6494, 4351, 6494, 6495, 4286, 6496, 6497, 6498, 6499, 4287, 3997, 6500, 6501, 6502, 6503, 6504, 6505, 6506, 6507, 6508, 6509, 6510, 6511, 6512, 6513, 6514, 6515, 6516, 6517, 6518, 6519, 6520, 6521, 4288, 6522, 6523, 6524, 6525, 6526, 6527, 4290, 6528, 6529, 6530, 6531, 6532, 6533, 6534, 6535, 4025, 4359, 6536, 6537, 6538, 6539, 6540, 6541, 6542, 6543, 4291, 6544, 6545, 6546, 6547, 4402, 6548, 6549, 6550, 6551, 6552, 6553, 6554, 6555, 6556, 6557, 6558, 6559, 6560, 4095, 6561, 6562, 6563, 6564, 6565, 6566, 6567, 6568, 6569, 6570, 6571, 4292, 4182, 6572, 6573, 6574, 6575, 6576, 6577, 6578, 6579, 4363, 6580, 6581, 6582, 6583, 6584, 6585, 6586, 6587, 4364, 6588, 6589, 6590, 6591, 6592, 6593, 6594, 6595, 6596, 6597, 6598, 6599, 4366, 6600, 6601, 6602, 6603, 6604, 6605, 6606, 6607, 6608, 6609, 6610, 6610, 6611, 6612, 4368, 6613, 6614, 6615, 6616, 6617, 6618, 6619, 4081, 6620, 6621, 6622, 6623, 6624, 6625, 6626, 4374, 6627, 6628, 6629, 6630, 6631, 6632, 6632, 4375, 4404, 6633, 6634, 6635, 6636, 6637, 4043, 4377, 6638, 6639, 4303, 6640, 6641, 4257, 6642, 6643, 4307, 6644, 6645, 6646, 6647, 6647, 6648, 6649, 6650, 6651, 6652, 6653, 6654, 6655, 6656, 6657, 6658, 6659, 6660, 6661, 6662, 6663, 6664, 6665, 6666, 6667, 6668, 6669, 6670, 6671, 6672, 6673, 6674, 4313, 6675, 6676, 6677, 6678, 6679, 6680, 6681, 6682, 6683, 6684, 6685, 6686, 6687, 6688, 6689, 6690, 6476, 6691, 6692, 6693, 6694, 6695, 6696, 6697, 6698, 6699, 6700, 6701, 6702, 4099, 6703, 6704, 6705, 6706, 6707, 6708, 4316, 6709, 6710, 6711, 6712, 6713, 6714, 6715, 6716, 6717, 6718, 6719, 6720, 6721, 6722, 6723, 6724, 6725, 6726, 6727, 6728, 4038, 6729, 6730, 6731, 6732, 6733, 6734, 4384, 6735, 6736, 6737, 6738, 6739, 6740, 6741, 6742, 6743, 6744, 6745, 6746, 6747, 6748, 6749, 6750, 6751, 6752, 6753, 6754, 4389, 4390, 6755, 6756, 6757, 6758, 6759, 6760, 6761, 6762, 6763, 6764, 6765, 6766, 6767, 4391, 6768, 6769, 6770, 6771, 6772, 6773, 6774, 6775, 6776, 6777, 6778, 6779, 6780, 6781, 6782, 6783, 6784, 6785, 6786, 6787, 6788, 6789, 6790, 6791, 6792, 6793, 6794, 6795, 6796, 6797, 4397, 4397, 6798, 6799, 6800, 6801, 6802, 6803, 6804, 6805, 6806, 6807, 4398, 6808, 6809, 6810, 6811, 6812, 6813, 6814, 6815, 6816, 6817, 6818, 6819, 6820, 6821, 6822, 6823, 6824, 6825, 6826, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 2053, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 574, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 3984, 0, 0, }; const utf8proc_property_t utf8proc_properties[] = { {0, 0, 0, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false,false,false,false, 0, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_BN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_BN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_S, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_B, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_LF}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_WS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_B, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CR}, {UTF8PROC_CATEGORY_CC, 0, UTF8PROC_BIDI_CLASS_B, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ES, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5093, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5084, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5096, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1, UINT16_MAX, 1, UINT16_MAX, 2784, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2, UINT16_MAX, 2, UINT16_MAX, 49, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3, UINT16_MAX, 3, UINT16_MAX, 704, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 4, UINT16_MAX, 4, UINT16_MAX, 62, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5, UINT16_MAX, 5, UINT16_MAX, 2872, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6, UINT16_MAX, 6, UINT16_MAX, 782, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7, UINT16_MAX, 7, UINT16_MAX, 808, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 8, UINT16_MAX, 8, UINT16_MAX, 111, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 9, UINT16_MAX, 9, UINT16_MAX, 898, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 10, UINT16_MAX, 10, UINT16_MAX, 913, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 11, UINT16_MAX, 11, UINT16_MAX, 999, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 12, UINT16_MAX, 12, UINT16_MAX, 2890, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 13, UINT16_MAX, 13, UINT16_MAX, 160, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 14, UINT16_MAX, 14, UINT16_MAX, 205, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 15, UINT16_MAX, 15, UINT16_MAX, 2982, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 16, UINT16_MAX, 16, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 17, UINT16_MAX, 17, UINT16_MAX, 1087, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 18, UINT16_MAX, 18, UINT16_MAX, 1173, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 19, UINT16_MAX, 19, UINT16_MAX, 1257, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 20, UINT16_MAX, 20, UINT16_MAX, 254, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 21, UINT16_MAX, 21, UINT16_MAX, 3042, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 22, UINT16_MAX, 22, UINT16_MAX, 1337, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 23, UINT16_MAX, 23, UINT16_MAX, 3122, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 24, UINT16_MAX, 24, UINT16_MAX, 303, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 25, UINT16_MAX, 25, UINT16_MAX, 1423, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PC, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1446, UINT16_MAX, 1446, 352, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1448, UINT16_MAX, 1448, 2818, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2795, UINT16_MAX, 2795, 401, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1449, UINT16_MAX, 1449, 743, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1450, UINT16_MAX, 1450, 414, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2820, UINT16_MAX, 2820, 2875, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1452, UINT16_MAX, 1452, 795, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1453, UINT16_MAX, 1453, 853, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1454, UINT16_MAX, 1454, 463, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1455, UINT16_MAX, 1455, 901, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1456, UINT16_MAX, 1456, 956, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1457, UINT16_MAX, 1457, 1043, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1458, UINT16_MAX, 1458, 2932, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1459, UINT16_MAX, 1459, 512, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1460, UINT16_MAX, 1460, 557, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1462, UINT16_MAX, 1462, 2994, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2809, UINT16_MAX, 2809, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1463, UINT16_MAX, 1463, 1130, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 3288, UINT16_MAX, 3288, 1215, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1464, UINT16_MAX, 1464, 1296, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1465, UINT16_MAX, 1465, 606, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2891, UINT16_MAX, 2891, 3082, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1466, UINT16_MAX, 1466, 1380, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2908, UINT16_MAX, 2908, 3131, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 3295, UINT16_MAX, 3295, 655, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2817, UINT16_MAX, 2817, 1466, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_NOBREAK, 26, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8219, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 1621, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PI, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_BN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8221, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8225, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 35, 35, 7130, UINT16_MAX, 7130, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8228, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 14, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PF, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 16423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 16426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 16429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8240, 50, UINT16_MAX, 50, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8243, 53, UINT16_MAX, 53, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8246, 56, UINT16_MAX, 56, UINT16_MAX, 3143, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8249, 59, UINT16_MAX, 59, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8252, 62, UINT16_MAX, 62, UINT16_MAX, 1537, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8255, 65, UINT16_MAX, 65, UINT16_MAX, 1579, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 66, UINT16_MAX, 66, UINT16_MAX, 1549, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8259, 69, UINT16_MAX, 69, UINT16_MAX, 2852, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8262, 72, UINT16_MAX, 72, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8265, 75, UINT16_MAX, 75, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8268, 78, UINT16_MAX, 78, UINT16_MAX, 3357, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8271, 81, UINT16_MAX, 81, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8274, 84, UINT16_MAX, 84, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8277, 87, UINT16_MAX, 87, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8280, 90, UINT16_MAX, 90, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8283, 93, UINT16_MAX, 93, UINT16_MAX, 2878, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 94, UINT16_MAX, 94, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8287, 97, UINT16_MAX, 97, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8290, 100, UINT16_MAX, 100, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8293, 103, UINT16_MAX, 103, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8296, 106, UINT16_MAX, 106, UINT16_MAX, 3461, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8299, 109, UINT16_MAX, 109, UINT16_MAX, 1597, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8302, 112, UINT16_MAX, 112, UINT16_MAX, 1591, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 113, UINT16_MAX, 113, UINT16_MAX, 1585, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8306, 116, UINT16_MAX, 116, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8309, 119, UINT16_MAX, 119, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8312, 122, UINT16_MAX, 122, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8315, 125, UINT16_MAX, 125, UINT16_MAX, 1509, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8318, 128, UINT16_MAX, 128, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 129, UINT16_MAX, 129, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 8322, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8324, UINT16_MAX, 7977, UINT16_MAX, 7977, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8326, UINT16_MAX, 7978, UINT16_MAX, 7978, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8328, UINT16_MAX, 7979, UINT16_MAX, 7979, 3192, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8330, UINT16_MAX, 7980, UINT16_MAX, 7980, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8332, UINT16_MAX, 7981, UINT16_MAX, 7981, 1540, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8334, UINT16_MAX, 2819, UINT16_MAX, 2819, 1582, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1447, UINT16_MAX, 1447, 1558, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8336, UINT16_MAX, 7982, UINT16_MAX, 7982, 2855, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8338, UINT16_MAX, 7983, UINT16_MAX, 7983, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8340, UINT16_MAX, 7984, UINT16_MAX, 7984, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8342, UINT16_MAX, 7985, UINT16_MAX, 7985, 3406, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8344, UINT16_MAX, 7986, UINT16_MAX, 7986, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8346, UINT16_MAX, 7987, UINT16_MAX, 7987, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8348, UINT16_MAX, 7988, UINT16_MAX, 7988, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8350, UINT16_MAX, 7989, UINT16_MAX, 7989, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8352, UINT16_MAX, 7990, UINT16_MAX, 7990, 2881, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7991, UINT16_MAX, 7991, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8354, UINT16_MAX, 7992, UINT16_MAX, 7992, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8356, UINT16_MAX, 7993, UINT16_MAX, 7993, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8358, UINT16_MAX, 7994, UINT16_MAX, 7994, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8360, UINT16_MAX, 7995, UINT16_MAX, 7995, 3510, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8362, UINT16_MAX, 7996, UINT16_MAX, 7996, 1606, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8364, UINT16_MAX, 7997, UINT16_MAX, 7997, 1594, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7998, UINT16_MAX, 7998, 1588, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8366, UINT16_MAX, 7999, UINT16_MAX, 7999, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8368, UINT16_MAX, 8000, UINT16_MAX, 8000, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8370, UINT16_MAX, 8001, UINT16_MAX, 8001, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8372, UINT16_MAX, 8002, UINT16_MAX, 8002, 1523, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8374, UINT16_MAX, 8003, UINT16_MAX, 8003, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8004, UINT16_MAX, 8004, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8376, UINT16_MAX, 8005, UINT16_MAX, 8005, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8378, 188, UINT16_MAX, 188, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8381, UINT16_MAX, 8006, UINT16_MAX, 8006, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8383, 193, UINT16_MAX, 193, UINT16_MAX, 3259, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8386, UINT16_MAX, 8007, UINT16_MAX, 8007, 3308, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8388, 198, UINT16_MAX, 198, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8391, UINT16_MAX, 8008, UINT16_MAX, 8008, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8393, 203, UINT16_MAX, 203, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8396, UINT16_MAX, 8009, UINT16_MAX, 8009, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8398, 208, UINT16_MAX, 208, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8401, UINT16_MAX, 8010, UINT16_MAX, 8010, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8403, 213, UINT16_MAX, 213, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8406, UINT16_MAX, 8011, UINT16_MAX, 8011, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8408, 218, UINT16_MAX, 218, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8411, UINT16_MAX, 8012, UINT16_MAX, 8012, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8413, 223, UINT16_MAX, 223, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8416, UINT16_MAX, 8013, UINT16_MAX, 8013, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 226, UINT16_MAX, 226, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8014, UINT16_MAX, 8014, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8419, 229, UINT16_MAX, 229, UINT16_MAX, 2858, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8422, UINT16_MAX, 8015, UINT16_MAX, 8015, 2862, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8424, 234, UINT16_MAX, 234, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8427, UINT16_MAX, 8016, UINT16_MAX, 8016, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8429, 239, UINT16_MAX, 239, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8432, UINT16_MAX, 8017, UINT16_MAX, 8017, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8434, 244, UINT16_MAX, 244, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8437, UINT16_MAX, 8018, UINT16_MAX, 8018, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8439, 249, UINT16_MAX, 249, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8442, UINT16_MAX, 8019, UINT16_MAX, 8019, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8444, 254, UINT16_MAX, 254, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8447, UINT16_MAX, 8020, UINT16_MAX, 8020, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8449, 259, UINT16_MAX, 259, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8452, UINT16_MAX, 8021, UINT16_MAX, 8021, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8454, 264, UINT16_MAX, 264, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8457, UINT16_MAX, 8022, UINT16_MAX, 8022, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8459, 269, UINT16_MAX, 269, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8462, UINT16_MAX, 8023, UINT16_MAX, 8023, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8464, 274, UINT16_MAX, 274, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8467, UINT16_MAX, 8024, UINT16_MAX, 8024, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 277, UINT16_MAX, 277, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 5138, UINT16_MAX, 5138, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8470, 280, UINT16_MAX, 280, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8473, UINT16_MAX, 8025, UINT16_MAX, 8025, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8475, 285, UINT16_MAX, 285, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8478, UINT16_MAX, 8026, UINT16_MAX, 8026, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8480, 290, UINT16_MAX, 290, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8483, UINT16_MAX, 8027, UINT16_MAX, 8027, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8485, 295, UINT16_MAX, 295, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8488, UINT16_MAX, 8028, UINT16_MAX, 8028, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8490, 8492, UINT16_MAX, 8, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1454, UINT16_MAX, 1454, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8494, 304, UINT16_MAX, 304, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8497, UINT16_MAX, 8029, UINT16_MAX, 8029, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8499, 309, UINT16_MAX, 309, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8502, UINT16_MAX, 8030, UINT16_MAX, 8030, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8504, 314, UINT16_MAX, 314, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8507, UINT16_MAX, 8031, UINT16_MAX, 8031, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8509, 319, UINT16_MAX, 319, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8512, UINT16_MAX, 8032, UINT16_MAX, 8032, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8514, 324, UINT16_MAX, 324, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8517, UINT16_MAX, 8033, UINT16_MAX, 8033, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8519, 329, UINT16_MAX, 329, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8522, UINT16_MAX, 8034, UINT16_MAX, 8034, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8524, 334, UINT16_MAX, 334, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8527, UINT16_MAX, 8035, UINT16_MAX, 8035, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 337, UINT16_MAX, 337, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8036, UINT16_MAX, 8036, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8530, 340, UINT16_MAX, 340, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8533, UINT16_MAX, 8037, UINT16_MAX, 8037, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8535, 345, UINT16_MAX, 345, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8538, UINT16_MAX, 8038, UINT16_MAX, 8038, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8540, 350, UINT16_MAX, 350, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8543, UINT16_MAX, 8039, UINT16_MAX, 8039, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8545, 8545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 355, UINT16_MAX, 355, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8040, UINT16_MAX, 8040, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8548, 358, UINT16_MAX, 358, UINT16_MAX, 2974, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8551, UINT16_MAX, 8041, UINT16_MAX, 8041, 2978, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8553, 363, UINT16_MAX, 363, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8556, UINT16_MAX, 8042, UINT16_MAX, 8042, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8558, 368, UINT16_MAX, 368, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8561, UINT16_MAX, 8043, UINT16_MAX, 8043, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 371, UINT16_MAX, 371, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8044, UINT16_MAX, 8044, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8564, 374, UINT16_MAX, 374, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8567, UINT16_MAX, 8045, UINT16_MAX, 8045, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8569, 379, UINT16_MAX, 379, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8572, UINT16_MAX, 8046, UINT16_MAX, 8046, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8574, 384, UINT16_MAX, 384, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8577, UINT16_MAX, 8047, UINT16_MAX, 8047, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8579, 389, UINT16_MAX, 389, UINT16_MAX, 3012, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8582, UINT16_MAX, 8048, UINT16_MAX, 8048, 3015, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8584, 394, UINT16_MAX, 394, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8587, UINT16_MAX, 8049, UINT16_MAX, 8049, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8589, 399, UINT16_MAX, 399, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8592, UINT16_MAX, 8050, UINT16_MAX, 8050, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8594, 404, UINT16_MAX, 404, UINT16_MAX, 3018, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8597, UINT16_MAX, 8051, UINT16_MAX, 8051, 3021, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8599, 409, UINT16_MAX, 409, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8602, UINT16_MAX, 8052, UINT16_MAX, 8052, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8604, 414, UINT16_MAX, 414, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8607, UINT16_MAX, 8053, UINT16_MAX, 8053, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 417, UINT16_MAX, 417, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8054, UINT16_MAX, 8054, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8610, 420, UINT16_MAX, 420, UINT16_MAX, 3030, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8613, UINT16_MAX, 8055, UINT16_MAX, 8055, 3033, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8615, 425, UINT16_MAX, 425, UINT16_MAX, 3036, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8618, UINT16_MAX, 8056, UINT16_MAX, 8056, 3039, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8620, 430, UINT16_MAX, 430, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8623, UINT16_MAX, 8057, UINT16_MAX, 8057, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8625, 435, UINT16_MAX, 435, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8628, UINT16_MAX, 8058, UINT16_MAX, 8058, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8630, 440, UINT16_MAX, 440, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8633, UINT16_MAX, 8059, UINT16_MAX, 8059, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8635, 445, UINT16_MAX, 445, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8638, UINT16_MAX, 8060, UINT16_MAX, 8060, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8640, 450, UINT16_MAX, 450, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8643, UINT16_MAX, 8061, UINT16_MAX, 8061, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8645, 455, UINT16_MAX, 455, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8648, UINT16_MAX, 8062, UINT16_MAX, 8062, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8650, 460, UINT16_MAX, 460, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8653, 463, UINT16_MAX, 463, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8656, UINT16_MAX, 8063, UINT16_MAX, 8063, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8658, 468, UINT16_MAX, 468, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8661, UINT16_MAX, 8064, UINT16_MAX, 8064, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8663, 473, UINT16_MAX, 473, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8666, UINT16_MAX, 8065, UINT16_MAX, 8065, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 18, 18, 3288, UINT16_MAX, 3288, 3140, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8066, UINT16_MAX, 8066, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 476, UINT16_MAX, 476, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 477, UINT16_MAX, 477, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8067, UINT16_MAX, 8067, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 478, UINT16_MAX, 478, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8068, UINT16_MAX, 8068, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 479, UINT16_MAX, 479, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 480, UINT16_MAX, 480, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8069, UINT16_MAX, 8069, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 481, UINT16_MAX, 481, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 482, UINT16_MAX, 482, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 483, UINT16_MAX, 483, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8070, UINT16_MAX, 8070, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 484, UINT16_MAX, 484, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 485, UINT16_MAX, 485, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 486, UINT16_MAX, 486, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 487, UINT16_MAX, 487, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8071, UINT16_MAX, 8071, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 488, UINT16_MAX, 488, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 489, UINT16_MAX, 489, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8072, UINT16_MAX, 8072, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 490, UINT16_MAX, 490, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 491, UINT16_MAX, 491, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 492, UINT16_MAX, 492, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8073, UINT16_MAX, 8073, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8074, UINT16_MAX, 8074, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 493, UINT16_MAX, 493, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 494, UINT16_MAX, 494, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8075, UINT16_MAX, 8075, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 495, UINT16_MAX, 495, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8688, 498, UINT16_MAX, 498, UINT16_MAX, 3565, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8691, UINT16_MAX, 8076, UINT16_MAX, 8076, 3614, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 501, UINT16_MAX, 501, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8077, UINT16_MAX, 8077, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 502, UINT16_MAX, 502, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8078, UINT16_MAX, 8078, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 503, UINT16_MAX, 503, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 504, UINT16_MAX, 504, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8079, UINT16_MAX, 8079, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 505, UINT16_MAX, 505, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 506, UINT16_MAX, 506, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8080, UINT16_MAX, 8080, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 507, UINT16_MAX, 507, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8700, 510, UINT16_MAX, 510, UINT16_MAX, 3663, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8703, UINT16_MAX, 8081, UINT16_MAX, 8081, 3712, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 513, UINT16_MAX, 513, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 514, UINT16_MAX, 514, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 515, UINT16_MAX, 515, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8082, UINT16_MAX, 8082, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 516, UINT16_MAX, 516, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8083, UINT16_MAX, 8083, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 517, UINT16_MAX, 517, UINT16_MAX, 1573, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 518, UINT16_MAX, 518, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8084, UINT16_MAX, 8084, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 519, UINT16_MAX, 519, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8085, UINT16_MAX, 8085, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8086, UINT16_MAX, 8086, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8712, 522, UINT16_MAX, 522, 8087, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8715, 522, 8088, 522, 8087, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8717, UINT16_MAX, 8088, UINT16_MAX, 8087, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8719, 529, UINT16_MAX, 529, 8089, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8722, 529, 8090, 529, 8089, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8724, UINT16_MAX, 8090, UINT16_MAX, 8089, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8726, 536, UINT16_MAX, 536, 8091, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8729, 536, 8092, 536, 8091, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8731, UINT16_MAX, 8092, UINT16_MAX, 8091, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8733, 543, UINT16_MAX, 543, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8736, UINT16_MAX, 8093, UINT16_MAX, 8093, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8738, 548, UINT16_MAX, 548, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8741, UINT16_MAX, 8094, UINT16_MAX, 8094, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8743, 553, UINT16_MAX, 553, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8746, UINT16_MAX, 8095, UINT16_MAX, 8095, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8748, 558, UINT16_MAX, 558, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8751, UINT16_MAX, 8096, UINT16_MAX, 8096, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8753, 563, UINT16_MAX, 563, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8756, UINT16_MAX, 8097, UINT16_MAX, 8097, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8758, 568, UINT16_MAX, 568, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8761, UINT16_MAX, 8098, UINT16_MAX, 8098, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8763, 573, UINT16_MAX, 573, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8766, UINT16_MAX, 8099, UINT16_MAX, 8099, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8768, 578, UINT16_MAX, 578, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8771, UINT16_MAX, 8100, UINT16_MAX, 8100, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1451, UINT16_MAX, 1451, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8773, 583, UINT16_MAX, 583, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8776, UINT16_MAX, 8101, UINT16_MAX, 8101, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8778, 588, UINT16_MAX, 588, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8781, UINT16_MAX, 8102, UINT16_MAX, 8102, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8783, 593, UINT16_MAX, 593, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8786, UINT16_MAX, 8103, UINT16_MAX, 8103, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 596, UINT16_MAX, 596, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8104, UINT16_MAX, 8104, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8789, 599, UINT16_MAX, 599, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8792, UINT16_MAX, 8105, UINT16_MAX, 8105, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8794, 604, UINT16_MAX, 604, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8797, UINT16_MAX, 8106, UINT16_MAX, 8106, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8799, 609, UINT16_MAX, 609, UINT16_MAX, 1567, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8802, UINT16_MAX, 8107, UINT16_MAX, 8107, 1570, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8804, 614, UINT16_MAX, 614, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8807, UINT16_MAX, 8108, UINT16_MAX, 8108, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8809, 619, UINT16_MAX, 619, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8812, UINT16_MAX, 8109, UINT16_MAX, 8109, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8814, 8814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8816, 626, UINT16_MAX, 626, 8110, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8819, 626, 8111, 626, 8110, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8821, UINT16_MAX, 8111, UINT16_MAX, 8110, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8823, 633, UINT16_MAX, 633, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8826, UINT16_MAX, 8112, UINT16_MAX, 8112, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 636, UINT16_MAX, 636, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 637, UINT16_MAX, 637, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8830, 640, UINT16_MAX, 640, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8833, UINT16_MAX, 8113, UINT16_MAX, 8113, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8835, 645, UINT16_MAX, 645, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8838, UINT16_MAX, 8114, UINT16_MAX, 8114, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8840, 650, UINT16_MAX, 650, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8843, UINT16_MAX, 8115, UINT16_MAX, 8115, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8845, 655, UINT16_MAX, 655, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8848, UINT16_MAX, 8116, UINT16_MAX, 8116, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8850, 660, UINT16_MAX, 660, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8853, UINT16_MAX, 8117, UINT16_MAX, 8117, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8855, 665, UINT16_MAX, 665, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8858, UINT16_MAX, 8118, UINT16_MAX, 8118, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8860, 670, UINT16_MAX, 670, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8863, UINT16_MAX, 8119, UINT16_MAX, 8119, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8865, 675, UINT16_MAX, 675, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8868, UINT16_MAX, 8120, UINT16_MAX, 8120, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8870, 680, UINT16_MAX, 680, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8873, UINT16_MAX, 8121, UINT16_MAX, 8121, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8875, 685, UINT16_MAX, 685, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8878, UINT16_MAX, 8122, UINT16_MAX, 8122, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8880, 690, UINT16_MAX, 690, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8883, UINT16_MAX, 8123, UINT16_MAX, 8123, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8885, 695, UINT16_MAX, 695, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8888, UINT16_MAX, 8124, UINT16_MAX, 8124, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8890, 700, UINT16_MAX, 700, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8893, UINT16_MAX, 8125, UINT16_MAX, 8125, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8895, 705, UINT16_MAX, 705, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8898, UINT16_MAX, 8126, UINT16_MAX, 8126, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8900, 710, UINT16_MAX, 710, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8903, UINT16_MAX, 8127, UINT16_MAX, 8127, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8905, 715, UINT16_MAX, 715, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8908, UINT16_MAX, 8128, UINT16_MAX, 8128, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8910, 720, UINT16_MAX, 720, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8913, UINT16_MAX, 8129, UINT16_MAX, 8129, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8915, 725, UINT16_MAX, 725, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8918, UINT16_MAX, 8130, UINT16_MAX, 8130, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 728, UINT16_MAX, 728, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8131, UINT16_MAX, 8131, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8921, 731, UINT16_MAX, 731, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8924, UINT16_MAX, 8132, UINT16_MAX, 8132, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 734, UINT16_MAX, 734, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 735, UINT16_MAX, 735, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 1461, UINT16_MAX, 1461, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 736, UINT16_MAX, 736, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8133, UINT16_MAX, 8133, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8929, 739, UINT16_MAX, 739, UINT16_MAX, 1543, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8932, UINT16_MAX, 8134, UINT16_MAX, 8134, 1546, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8934, 744, UINT16_MAX, 744, UINT16_MAX, 2866, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8937, UINT16_MAX, 8135, UINT16_MAX, 8135, 2869, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8939, 749, UINT16_MAX, 749, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8942, UINT16_MAX, 8136, UINT16_MAX, 8136, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8944, 754, UINT16_MAX, 754, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8947, UINT16_MAX, 8137, UINT16_MAX, 8137, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8949, 759, UINT16_MAX, 759, UINT16_MAX, 1615, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8952, UINT16_MAX, 8138, UINT16_MAX, 8138, 1618, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8954, 764, UINT16_MAX, 764, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8957, UINT16_MAX, 8139, UINT16_MAX, 8139, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 8959, 769, UINT16_MAX, 769, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 8962, UINT16_MAX, 8140, UINT16_MAX, 8140, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 772, UINT16_MAX, 772, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 773, UINT16_MAX, 773, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8141, UINT16_MAX, 8141, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 774, UINT16_MAX, 774, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 775, UINT16_MAX, 775, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8142, UINT16_MAX, 8142, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8143, UINT16_MAX, 8143, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 776, UINT16_MAX, 776, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8144, UINT16_MAX, 8144, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 777, UINT16_MAX, 777, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 778, UINT16_MAX, 778, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 779, UINT16_MAX, 779, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 780, UINT16_MAX, 780, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8145, UINT16_MAX, 8145, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 781, UINT16_MAX, 781, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8146, UINT16_MAX, 8146, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 782, UINT16_MAX, 782, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8147, UINT16_MAX, 8147, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 783, UINT16_MAX, 783, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8148, UINT16_MAX, 8148, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 784, UINT16_MAX, 784, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8149, UINT16_MAX, 8149, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8150, UINT16_MAX, 8150, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8151, UINT16_MAX, 8151, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8152, UINT16_MAX, 8152, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8153, UINT16_MAX, 8153, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8154, UINT16_MAX, 8154, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8155, UINT16_MAX, 8155, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8156, UINT16_MAX, 8156, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8157, UINT16_MAX, 8157, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2804, UINT16_MAX, 2804, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8158, UINT16_MAX, 8158, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8159, UINT16_MAX, 8159, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8160, UINT16_MAX, 8160, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8161, UINT16_MAX, 8161, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8162, UINT16_MAX, 8162, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8163, UINT16_MAX, 8163, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8164, UINT16_MAX, 8164, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8165, UINT16_MAX, 8165, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8166, UINT16_MAX, 8166, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8167, UINT16_MAX, 8167, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8168, UINT16_MAX, 8168, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8169, UINT16_MAX, 8169, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8170, UINT16_MAX, 8170, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8171, UINT16_MAX, 8171, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8172, UINT16_MAX, 8172, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8173, UINT16_MAX, 8173, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8174, UINT16_MAX, 8174, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8175, UINT16_MAX, 8175, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8176, UINT16_MAX, 8176, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8177, UINT16_MAX, 8177, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8178, UINT16_MAX, 8178, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8179, UINT16_MAX, 8179, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8180, UINT16_MAX, 8180, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8181, UINT16_MAX, 8181, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8182, UINT16_MAX, 8182, 1576, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8183, UINT16_MAX, 8183, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8184, UINT16_MAX, 8184, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 7, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 9, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 17, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 788, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 22, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 24, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8981, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8983, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8985, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8987, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8989, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 8991, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 11, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 18, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 23, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 801, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32768, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32769, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32770, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32771, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32775, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32776, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32778, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32772, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32814, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32773, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32780, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32779, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32782, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32783, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32815, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32816, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 232, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 216, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32781, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 202, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32808, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32813, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32807, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32784, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 202, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32774, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 202, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32777, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32810, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32812, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32811, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32809, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 1, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 1, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32819, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, 802, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, 803, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32817, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, 804, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, 8997, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 240, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, 807, 7127, UINT16_MAX, 7127, 32818, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 233, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 234, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 808, UINT16_MAX, 808, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8185, UINT16_MAX, 8185, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 809, UINT16_MAX, 809, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8186, UINT16_MAX, 8186, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 810, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 811, UINT16_MAX, 811, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8187, UINT16_MAX, 8187, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9004, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8188, UINT16_MAX, 8188, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8189, UINT16_MAX, 8189, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8190, UINT16_MAX, 8190, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, 0, 814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 815, UINT16_MAX, 815, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 9008, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9010, 820, UINT16_MAX, 820, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, 0, 821, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9014, 824, UINT16_MAX, 824, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9017, 827, UINT16_MAX, 827, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9020, 830, UINT16_MAX, 830, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9023, 833, UINT16_MAX, 833, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9026, 836, UINT16_MAX, 836, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9029, 839, UINT16_MAX, 839, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9032, 17226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 845, UINT16_MAX, 845, UINT16_MAX, 1673, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 846, UINT16_MAX, 846, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 847, UINT16_MAX, 847, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 848, UINT16_MAX, 848, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 849, UINT16_MAX, 849, UINT16_MAX, 1726, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 850, UINT16_MAX, 850, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 851, UINT16_MAX, 851, UINT16_MAX, 1777, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 852, UINT16_MAX, 852, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 807, UINT16_MAX, 807, UINT16_MAX, 1830, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 853, UINT16_MAX, 853, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 854, UINT16_MAX, 854, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 35, UINT16_MAX, 35, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 855, UINT16_MAX, 855, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 856, UINT16_MAX, 856, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 857, UINT16_MAX, 857, UINT16_MAX, 1881, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 858, UINT16_MAX, 858, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 859, UINT16_MAX, 859, UINT16_MAX, 5027, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 860, UINT16_MAX, 860, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 861, UINT16_MAX, 861, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 862, UINT16_MAX, 862, UINT16_MAX, 1932, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 863, UINT16_MAX, 863, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 864, UINT16_MAX, 864, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 865, UINT16_MAX, 865, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 866, UINT16_MAX, 866, UINT16_MAX, 1983, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9059, 869, UINT16_MAX, 869, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9062, 872, UINT16_MAX, 872, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9065, UINT16_MAX, 2583, UINT16_MAX, 2583, 4904, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9067, UINT16_MAX, 2616, UINT16_MAX, 2616, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9069, UINT16_MAX, 2621, UINT16_MAX, 2621, 4913, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9071, UINT16_MAX, 2658, UINT16_MAX, 2658, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9073, 17267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7121, UINT16_MAX, 7121, 2088, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7122, UINT16_MAX, 7122, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2829, UINT16_MAX, 2829, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7123, UINT16_MAX, 7123, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7124, UINT16_MAX, 7124, 2141, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7125, UINT16_MAX, 7125, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7126, UINT16_MAX, 7126, 2192, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 915, UINT16_MAX, 915, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7127, UINT16_MAX, 7127, 2245, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7128, UINT16_MAX, 7128, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7129, UINT16_MAX, 7129, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7130, UINT16_MAX, 7130, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7131, UINT16_MAX, 7131, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7132, UINT16_MAX, 7132, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7133, UINT16_MAX, 7133, 2401, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2830, UINT16_MAX, 2830, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7134, UINT16_MAX, 7134, 5023, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 860, 917, UINT16_MAX, 917, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 917, UINT16_MAX, 917, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7136, UINT16_MAX, 7136, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 897, UINT16_MAX, 897, 2349, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7137, UINT16_MAX, 7137, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7138, UINT16_MAX, 7138, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7139, UINT16_MAX, 7139, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 2818, UINT16_MAX, 2818, 2452, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9078, UINT16_MAX, 8191, UINT16_MAX, 8191, 2036, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9080, UINT16_MAX, 8192, UINT16_MAX, 8192, 2297, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9082, UINT16_MAX, 2727, UINT16_MAX, 2727, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9084, UINT16_MAX, 2696, UINT16_MAX, 2696, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9086, UINT16_MAX, 2732, UINT16_MAX, 2732, 5033, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 896, UINT16_MAX, 896, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 846, 846, 7122, UINT16_MAX, 7122, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 852, 852, 915, UINT16_MAX, 915, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2505, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9090, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9092, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 863, 863, 7137, UINT16_MAX, 7137, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 858, 858, 2830, UINT16_MAX, 2830, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8193, UINT16_MAX, 8193, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 902, UINT16_MAX, 902, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8194, UINT16_MAX, 8194, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 903, UINT16_MAX, 903, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8195, UINT16_MAX, 8195, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 904, UINT16_MAX, 904, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 7148, UINT16_MAX, 7148, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 905, UINT16_MAX, 905, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8196, UINT16_MAX, 8196, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 906, UINT16_MAX, 906, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8197, UINT16_MAX, 8197, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 907, UINT16_MAX, 907, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8198, UINT16_MAX, 8198, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 908, UINT16_MAX, 908, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8199, UINT16_MAX, 8199, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 909, UINT16_MAX, 909, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8200, UINT16_MAX, 8200, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 910, UINT16_MAX, 910, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8201, UINT16_MAX, 8201, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 911, UINT16_MAX, 911, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8202, UINT16_MAX, 8202, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 912, UINT16_MAX, 912, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8203, UINT16_MAX, 8203, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 913, UINT16_MAX, 913, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8204, UINT16_MAX, 8204, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 853, 853, 7128, UINT16_MAX, 7128, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 859, 859, 7134, UINT16_MAX, 7134, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 914, UINT16_MAX, 8205, UINT16_MAX, 8205, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8206, UINT16_MAX, 8206, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 915, 852, UINT16_MAX, 852, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 849, 849, 7124, UINT16_MAX, 7124, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 916, UINT16_MAX, 916, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8207, UINT16_MAX, 8207, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 917, 918, UINT16_MAX, 918, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 919, UINT16_MAX, 919, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8208, UINT16_MAX, 8208, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 920, UINT16_MAX, 920, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 921, UINT16_MAX, 921, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 922, UINT16_MAX, 922, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9115, 925, UINT16_MAX, 925, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9118, 928, UINT16_MAX, 928, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 929, UINT16_MAX, 929, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9122, 932, UINT16_MAX, 932, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 933, UINT16_MAX, 933, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 934, UINT16_MAX, 934, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 935, UINT16_MAX, 935, UINT16_MAX, 2525, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9128, 938, UINT16_MAX, 938, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 939, UINT16_MAX, 939, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 940, UINT16_MAX, 940, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 941, UINT16_MAX, 941, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 942, UINT16_MAX, 942, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9135, 945, UINT16_MAX, 945, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9138, 948, UINT16_MAX, 948, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9141, 951, UINT16_MAX, 951, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 952, UINT16_MAX, 952, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 953, UINT16_MAX, 953, UINT16_MAX, 2615, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 954, UINT16_MAX, 954, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 955, UINT16_MAX, 955, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 956, UINT16_MAX, 956, UINT16_MAX, 2522, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 957, UINT16_MAX, 957, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 958, UINT16_MAX, 958, UINT16_MAX, 2511, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 959, UINT16_MAX, 959, UINT16_MAX, 2601, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 960, UINT16_MAX, 960, UINT16_MAX, 2635, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 961, UINT16_MAX, 961, UINT16_MAX, 2531, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9154, 964, UINT16_MAX, 964, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 965, UINT16_MAX, 965, UINT16_MAX, 2528, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 966, UINT16_MAX, 966, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 967, UINT16_MAX, 967, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 968, UINT16_MAX, 968, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 969, UINT16_MAX, 969, UINT16_MAX, 2641, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 970, UINT16_MAX, 970, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 971, UINT16_MAX, 971, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 972, UINT16_MAX, 972, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 973, UINT16_MAX, 973, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 974, UINT16_MAX, 974, UINT16_MAX, 2542, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 975, UINT16_MAX, 975, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 976, UINT16_MAX, 976, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 977, UINT16_MAX, 977, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 978, UINT16_MAX, 978, UINT16_MAX, 2659, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 979, UINT16_MAX, 979, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 980, UINT16_MAX, 980, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 981, UINT16_MAX, 981, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 982, UINT16_MAX, 982, UINT16_MAX, 2665, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 983, UINT16_MAX, 983, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 984, UINT16_MAX, 984, UINT16_MAX, 2653, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 985, UINT16_MAX, 985, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 986, UINT16_MAX, 986, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8209, UINT16_MAX, 8209, 2622, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8210, UINT16_MAX, 8210, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8211, UINT16_MAX, 8211, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8212, UINT16_MAX, 8212, 2575, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8213, UINT16_MAX, 8213, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8214, UINT16_MAX, 8214, 2564, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8215, UINT16_MAX, 8215, 2608, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8216, UINT16_MAX, 8216, 2638, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8217, UINT16_MAX, 8217, 2553, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9179, UINT16_MAX, 8218, UINT16_MAX, 8218, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8219, UINT16_MAX, 8219, 2581, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8220, UINT16_MAX, 8220, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8221, UINT16_MAX, 8221, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8222, UINT16_MAX, 8222, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8223, UINT16_MAX, 8223, 2644, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8224, UINT16_MAX, 8224, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8225, UINT16_MAX, 8225, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8226, UINT16_MAX, 8226, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8227, UINT16_MAX, 8227, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8228, UINT16_MAX, 8228, 2584, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8229, UINT16_MAX, 8229, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8230, UINT16_MAX, 8230, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8231, UINT16_MAX, 8231, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8232, UINT16_MAX, 8232, 2662, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8233, UINT16_MAX, 8233, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8234, UINT16_MAX, 8234, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8235, UINT16_MAX, 8235, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8236, UINT16_MAX, 8236, 2668, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8237, UINT16_MAX, 8237, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8238, UINT16_MAX, 8238, 2656, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8239, UINT16_MAX, 8239, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8240, UINT16_MAX, 8240, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9181, UINT16_MAX, 8241, UINT16_MAX, 8241, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9183, UINT16_MAX, 8242, UINT16_MAX, 8242, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8243, UINT16_MAX, 8243, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9185, UINT16_MAX, 8244, UINT16_MAX, 8244, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8245, UINT16_MAX, 8245, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8246, UINT16_MAX, 8246, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8247, UINT16_MAX, 8247, 2578, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9187, UINT16_MAX, 8248, UINT16_MAX, 8248, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8249, UINT16_MAX, 8249, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8250, UINT16_MAX, 8250, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8251, UINT16_MAX, 8251, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8252, UINT16_MAX, 8252, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9189, UINT16_MAX, 8253, UINT16_MAX, 8253, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9191, UINT16_MAX, 8254, UINT16_MAX, 8254, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9193, UINT16_MAX, 8255, UINT16_MAX, 8255, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8256, UINT16_MAX, 8256, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1003, UINT16_MAX, 1003, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8257, UINT16_MAX, 8257, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1004, UINT16_MAX, 1004, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8258, UINT16_MAX, 8258, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1005, UINT16_MAX, 1005, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8259, UINT16_MAX, 8259, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1006, UINT16_MAX, 1006, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8260, UINT16_MAX, 8260, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1007, UINT16_MAX, 1007, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8261, UINT16_MAX, 8261, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1008, UINT16_MAX, 1008, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8262, UINT16_MAX, 8262, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1009, UINT16_MAX, 1009, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8263, UINT16_MAX, 8263, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1010, UINT16_MAX, 1010, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8264, UINT16_MAX, 8264, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1011, UINT16_MAX, 1011, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8265, UINT16_MAX, 8265, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1012, UINT16_MAX, 1012, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8266, UINT16_MAX, 8266, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1013, UINT16_MAX, 1013, UINT16_MAX, 2595, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8267, UINT16_MAX, 8267, 2598, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9206, 1016, UINT16_MAX, 1016, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9209, UINT16_MAX, 8268, UINT16_MAX, 8268, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1019, UINT16_MAX, 1019, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8269, UINT16_MAX, 8269, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1020, UINT16_MAX, 1020, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8270, UINT16_MAX, 8270, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1021, UINT16_MAX, 1021, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8271, UINT16_MAX, 8271, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1022, UINT16_MAX, 1022, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8272, UINT16_MAX, 8272, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1023, UINT16_MAX, 1023, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8273, UINT16_MAX, 8273, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ME, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1024, UINT16_MAX, 1024, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8274, UINT16_MAX, 8274, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1025, UINT16_MAX, 1025, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8275, UINT16_MAX, 8275, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1026, UINT16_MAX, 1026, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8276, UINT16_MAX, 8276, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1027, UINT16_MAX, 1027, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8277, UINT16_MAX, 8277, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1028, UINT16_MAX, 1028, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8278, UINT16_MAX, 8278, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1029, UINT16_MAX, 1029, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8279, UINT16_MAX, 8279, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1030, UINT16_MAX, 1030, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8280, UINT16_MAX, 8280, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1031, UINT16_MAX, 1031, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8281, UINT16_MAX, 8281, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1032, UINT16_MAX, 1032, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8282, UINT16_MAX, 8282, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1033, UINT16_MAX, 1033, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8283, UINT16_MAX, 8283, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1034, UINT16_MAX, 1034, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8284, UINT16_MAX, 8284, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1035, UINT16_MAX, 1035, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8285, UINT16_MAX, 8285, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1036, UINT16_MAX, 1036, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8286, UINT16_MAX, 8286, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1037, UINT16_MAX, 1037, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8287, UINT16_MAX, 8287, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1038, UINT16_MAX, 1038, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8288, UINT16_MAX, 8288, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1039, UINT16_MAX, 1039, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8289, UINT16_MAX, 8289, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1040, UINT16_MAX, 1040, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8290, UINT16_MAX, 8290, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1041, UINT16_MAX, 1041, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8291, UINT16_MAX, 8291, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1042, UINT16_MAX, 1042, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8292, UINT16_MAX, 8292, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1043, UINT16_MAX, 1043, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8293, UINT16_MAX, 8293, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1044, UINT16_MAX, 1044, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8294, UINT16_MAX, 8294, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1045, UINT16_MAX, 1045, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8295, UINT16_MAX, 8295, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1046, UINT16_MAX, 1046, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8296, UINT16_MAX, 8296, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1047, UINT16_MAX, 1047, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8297, UINT16_MAX, 8297, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1048, UINT16_MAX, 1048, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8298, UINT16_MAX, 8298, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1049, UINT16_MAX, 1049, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8299, UINT16_MAX, 8299, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1050, UINT16_MAX, 1050, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8300, UINT16_MAX, 8300, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1051, UINT16_MAX, 1051, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9244, 1054, UINT16_MAX, 1054, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9247, UINT16_MAX, 8301, UINT16_MAX, 8301, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1057, UINT16_MAX, 1057, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8302, UINT16_MAX, 8302, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1058, UINT16_MAX, 1058, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8303, UINT16_MAX, 8303, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1059, UINT16_MAX, 1059, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8304, UINT16_MAX, 8304, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1060, UINT16_MAX, 1060, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8305, UINT16_MAX, 8305, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1061, UINT16_MAX, 1061, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8306, UINT16_MAX, 8306, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1062, UINT16_MAX, 1062, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8307, UINT16_MAX, 8307, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8308, UINT16_MAX, 8308, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9255, 1065, UINT16_MAX, 1065, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9258, UINT16_MAX, 8309, UINT16_MAX, 8309, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9260, 1070, UINT16_MAX, 1070, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9263, UINT16_MAX, 8310, UINT16_MAX, 8310, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1073, UINT16_MAX, 1073, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8311, UINT16_MAX, 8311, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9266, 1076, UINT16_MAX, 1076, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9269, UINT16_MAX, 8312, UINT16_MAX, 8312, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1079, UINT16_MAX, 1079, UINT16_MAX, 2629, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8313, UINT16_MAX, 8313, 2632, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9272, 1082, UINT16_MAX, 1082, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9275, UINT16_MAX, 8314, UINT16_MAX, 8314, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9277, 1087, UINT16_MAX, 1087, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9280, UINT16_MAX, 8315, UINT16_MAX, 8315, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9282, 1092, UINT16_MAX, 1092, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9285, UINT16_MAX, 8316, UINT16_MAX, 8316, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1095, UINT16_MAX, 1095, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8317, UINT16_MAX, 8317, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9288, 1098, UINT16_MAX, 1098, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9291, UINT16_MAX, 8318, UINT16_MAX, 8318, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9293, 1103, UINT16_MAX, 1103, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9296, UINT16_MAX, 8319, UINT16_MAX, 8319, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9298, 1108, UINT16_MAX, 1108, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9301, UINT16_MAX, 8320, UINT16_MAX, 8320, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1111, UINT16_MAX, 1111, UINT16_MAX, 2647, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8321, UINT16_MAX, 8321, 2650, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9304, 1114, UINT16_MAX, 1114, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9307, UINT16_MAX, 8322, UINT16_MAX, 8322, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9309, 1119, UINT16_MAX, 1119, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9312, UINT16_MAX, 8323, UINT16_MAX, 8323, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9314, 1124, UINT16_MAX, 1124, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9317, UINT16_MAX, 8324, UINT16_MAX, 8324, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9319, 1129, UINT16_MAX, 1129, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9322, UINT16_MAX, 8325, UINT16_MAX, 8325, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9324, 1134, UINT16_MAX, 1134, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9327, UINT16_MAX, 8326, UINT16_MAX, 8326, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9329, 1139, UINT16_MAX, 1139, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9332, UINT16_MAX, 8327, UINT16_MAX, 8327, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1142, UINT16_MAX, 1142, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8328, UINT16_MAX, 8328, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9335, 1145, UINT16_MAX, 1145, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9338, UINT16_MAX, 8329, UINT16_MAX, 8329, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1148, UINT16_MAX, 1148, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8330, UINT16_MAX, 8330, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1149, UINT16_MAX, 1149, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8331, UINT16_MAX, 8331, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1150, UINT16_MAX, 1150, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8332, UINT16_MAX, 8332, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1151, UINT16_MAX, 1151, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8333, UINT16_MAX, 8333, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1152, UINT16_MAX, 1152, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8334, UINT16_MAX, 8334, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1153, UINT16_MAX, 1153, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8335, UINT16_MAX, 8335, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1154, UINT16_MAX, 1154, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8336, UINT16_MAX, 8336, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1155, UINT16_MAX, 1155, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8337, UINT16_MAX, 8337, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1156, UINT16_MAX, 1156, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8338, UINT16_MAX, 8338, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1157, UINT16_MAX, 1157, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8339, UINT16_MAX, 8339, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1158, UINT16_MAX, 1158, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8340, UINT16_MAX, 8340, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1159, UINT16_MAX, 1159, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8341, UINT16_MAX, 8341, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1160, UINT16_MAX, 1160, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8342, UINT16_MAX, 8342, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1161, UINT16_MAX, 1161, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8343, UINT16_MAX, 8343, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1162, UINT16_MAX, 1162, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8344, UINT16_MAX, 8344, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1163, UINT16_MAX, 1163, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8345, UINT16_MAX, 8345, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1164, UINT16_MAX, 1164, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8346, UINT16_MAX, 8346, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1165, UINT16_MAX, 1165, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8347, UINT16_MAX, 8347, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1166, UINT16_MAX, 1166, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8348, UINT16_MAX, 8348, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1167, UINT16_MAX, 1167, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8349, UINT16_MAX, 8349, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1168, UINT16_MAX, 1168, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8350, UINT16_MAX, 8350, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1169, UINT16_MAX, 1169, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8351, UINT16_MAX, 8351, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1170, UINT16_MAX, 1170, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8352, UINT16_MAX, 8352, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1171, UINT16_MAX, 1171, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8353, UINT16_MAX, 8353, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1172, UINT16_MAX, 1172, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8354, UINT16_MAX, 8354, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1173, UINT16_MAX, 1173, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8355, UINT16_MAX, 8355, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1174, UINT16_MAX, 1174, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8356, UINT16_MAX, 8356, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1175, UINT16_MAX, 1175, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1176, UINT16_MAX, 1176, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1177, UINT16_MAX, 1177, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1178, UINT16_MAX, 1178, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1179, UINT16_MAX, 1179, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1180, UINT16_MAX, 1180, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1181, UINT16_MAX, 1181, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1182, UINT16_MAX, 1182, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1183, UINT16_MAX, 1183, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1184, UINT16_MAX, 1184, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1185, UINT16_MAX, 1185, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1186, UINT16_MAX, 1186, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1187, UINT16_MAX, 1187, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1188, UINT16_MAX, 1188, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1189, UINT16_MAX, 1189, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1190, UINT16_MAX, 1190, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1191, UINT16_MAX, 1191, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1192, UINT16_MAX, 1192, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1193, UINT16_MAX, 1193, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1194, UINT16_MAX, 1194, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1195, UINT16_MAX, 1195, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1196, UINT16_MAX, 1196, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1197, UINT16_MAX, 1197, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1198, UINT16_MAX, 1198, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1199, UINT16_MAX, 1199, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1200, UINT16_MAX, 1200, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1201, UINT16_MAX, 1201, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1202, UINT16_MAX, 1202, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1203, UINT16_MAX, 1203, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1204, UINT16_MAX, 1204, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1205, UINT16_MAX, 1205, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1206, UINT16_MAX, 1206, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1207, UINT16_MAX, 1207, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1208, UINT16_MAX, 1208, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1209, UINT16_MAX, 1209, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1210, UINT16_MAX, 1210, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1211, UINT16_MAX, 1211, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1212, UINT16_MAX, 1212, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8357, UINT16_MAX, 8357, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8358, UINT16_MAX, 8358, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8359, UINT16_MAX, 8359, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8360, UINT16_MAX, 8360, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8361, UINT16_MAX, 8361, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8362, UINT16_MAX, 8362, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8363, UINT16_MAX, 8363, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8364, UINT16_MAX, 8364, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8365, UINT16_MAX, 8365, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8366, UINT16_MAX, 8366, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8367, UINT16_MAX, 8367, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8368, UINT16_MAX, 8368, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8369, UINT16_MAX, 8369, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8370, UINT16_MAX, 8370, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8371, UINT16_MAX, 8371, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8372, UINT16_MAX, 8372, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8373, UINT16_MAX, 8373, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8374, UINT16_MAX, 8374, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8375, UINT16_MAX, 8375, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8376, UINT16_MAX, 8376, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8377, UINT16_MAX, 8377, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8378, UINT16_MAX, 8378, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8379, UINT16_MAX, 8379, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8380, UINT16_MAX, 8380, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8381, UINT16_MAX, 8381, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8382, UINT16_MAX, 8382, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8383, UINT16_MAX, 8383, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8384, UINT16_MAX, 8384, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8385, UINT16_MAX, 8385, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8386, UINT16_MAX, 8386, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8387, UINT16_MAX, 8387, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8388, UINT16_MAX, 8388, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8389, UINT16_MAX, 8389, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8390, UINT16_MAX, 8390, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8391, UINT16_MAX, 8391, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8392, UINT16_MAX, 8392, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8393, UINT16_MAX, 8393, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8394, UINT16_MAX, 8394, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9405, 9405, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 222, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 228, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 10, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 11, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 12, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 13, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 14, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 15, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 16, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 17, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 18, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 19, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 20, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 21, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 22, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 23, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 24, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 25, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_PREPEND}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 2, 0, UTF8PROC_BOUNDCLASS_PREPEND}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 30, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 31, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 32, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9407, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9411, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9413, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2671, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2676, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2679, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 27, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 28, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 29, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 33, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 34, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32785, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 230, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32786, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 220, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32787, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 35, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_COMPAT, 9417, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_COMPAT, 9419, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_COMPAT, 9421, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_COMPAT, 9423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9425, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2685, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2688, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, 9429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2682, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_PREPEND}, {UTF8PROC_CATEGORY_MN, 36, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2691, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9431, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2694, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2697, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9435, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 7, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32788, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 9, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9441, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9443, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9445, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9447, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9451, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 7, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32789, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2700, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32790, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9461, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9467, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9471, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9473, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32792, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2704, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9477, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32791, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32793, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2709, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32795, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2712, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2716, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32794, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2719, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 84, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 91, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32796, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2722, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32799, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2725, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2730, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32797, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32798, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32800, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2733, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2737, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_PREPEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32801, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 9, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32802, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32803, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2740, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2745, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32804, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9519, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 103, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 107, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 118, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 122, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9523, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 9525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NOBREAK, 1335, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 216, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9528, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9530, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9534, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9536, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 129, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 130, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9540, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 132, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9542, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9544, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, UTF8PROC_DECOMP_TYPE_COMPAT, 9546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, UTF8PROC_DECOMP_TYPE_COMPAT, 9550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 9564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2748, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32805, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1376, UINT16_MAX, 1376, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1377, UINT16_MAX, 1377, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1378, UINT16_MAX, 1378, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1379, UINT16_MAX, 1379, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1380, UINT16_MAX, 1380, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1381, UINT16_MAX, 1381, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1382, UINT16_MAX, 1382, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1383, UINT16_MAX, 1383, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1384, UINT16_MAX, 1384, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1385, UINT16_MAX, 1385, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1386, UINT16_MAX, 1386, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1387, UINT16_MAX, 1387, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1388, UINT16_MAX, 1388, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1389, UINT16_MAX, 1389, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1390, UINT16_MAX, 1390, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1391, UINT16_MAX, 1391, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1392, UINT16_MAX, 1392, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1393, UINT16_MAX, 1393, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1394, UINT16_MAX, 1394, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1395, UINT16_MAX, 1395, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1396, UINT16_MAX, 1396, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1397, UINT16_MAX, 1397, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1398, UINT16_MAX, 1398, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1399, UINT16_MAX, 1399, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1400, UINT16_MAX, 1400, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1401, UINT16_MAX, 1401, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1402, UINT16_MAX, 1402, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1403, UINT16_MAX, 1403, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1404, UINT16_MAX, 1404, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1405, UINT16_MAX, 1405, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1406, UINT16_MAX, 1406, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1407, UINT16_MAX, 1407, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1408, UINT16_MAX, 1408, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1409, UINT16_MAX, 1409, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1410, UINT16_MAX, 1410, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1411, UINT16_MAX, 1411, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1412, UINT16_MAX, 1412, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1413, UINT16_MAX, 1413, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1414, UINT16_MAX, 1414, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1415, UINT16_MAX, 1415, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1416, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_L}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, false, 2, 0, UTF8PROC_BOUNDCLASS_L}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, false, 2, 0, UTF8PROC_BOUNDCLASS_V}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_V}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_T}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8395, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8396, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8397, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8398, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8399, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8400, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8401, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8402, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8403, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8404, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8405, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8406, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8407, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8408, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8409, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8410, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8411, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8412, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8413, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8414, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8415, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8416, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8417, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8418, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8419, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8420, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8421, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8422, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8423, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8424, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8425, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8426, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8427, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8428, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8429, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8430, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8431, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8432, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8433, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8434, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8435, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8436, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8437, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8438, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8439, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8440, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8441, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8442, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8443, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8444, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8445, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8446, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8447, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8448, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8449, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8450, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8451, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8452, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8453, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8454, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8455, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8456, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8457, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8458, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8459, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8460, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8461, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8462, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8463, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8464, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8465, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8466, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8467, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8468, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8469, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8470, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8471, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8472, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8473, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8474, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8475, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8476, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8477, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8478, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8479, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, 8480, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1417, 1417, UINT16_MAX, 1417, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1418, 1418, UINT16_MAX, 1418, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1419, 1419, UINT16_MAX, 1419, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1420, 1420, UINT16_MAX, 1420, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1421, 1421, UINT16_MAX, 1421, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1422, 1422, UINT16_MAX, 1422, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2751, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9615, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2754, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9617, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2757, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2760, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9621, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2763, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2766, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 9625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32806, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2769, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9627, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2772, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2775, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2778, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 2781, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 9635, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 9, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 955, 8211, UINT16_MAX, 8211, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 957, 8213, UINT16_MAX, 8213, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 969, 8223, UINT16_MAX, 8223, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 972, 8226, UINT16_MAX, 8226, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 973, 8227, UINT16_MAX, 8227, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 981, 8235, UINT16_MAX, 8235, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1004, 8258, UINT16_MAX, 8258, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1445, 8481, UINT16_MAX, 8481, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1447, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1451, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1461, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1467, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1468, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 4, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 486, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1470, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 6, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 10, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 12, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 355, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 14, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1471, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 15, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 19, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 20, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1473, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 21, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1474, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 8, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 17, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 20, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 21, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 968, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8482, UINT16_MAX, 8482, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8483, UINT16_MAX, 8483, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 2, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1476, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 94, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1477, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1484, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1486, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 25, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 1495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 214, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9688, 1498, UINT16_MAX, 1498, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9691, UINT16_MAX, 8484, UINT16_MAX, 8484, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9693, 1503, UINT16_MAX, 1503, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9696, UINT16_MAX, 8485, UINT16_MAX, 8485, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9698, 1508, UINT16_MAX, 1508, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9701, UINT16_MAX, 8486, UINT16_MAX, 8486, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9703, 1513, UINT16_MAX, 1513, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9706, UINT16_MAX, 8487, UINT16_MAX, 8487, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9708, 1518, UINT16_MAX, 1518, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9711, UINT16_MAX, 8488, UINT16_MAX, 8488, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9713, 1523, UINT16_MAX, 1523, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9716, UINT16_MAX, 8489, UINT16_MAX, 8489, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9718, 1528, UINT16_MAX, 1528, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9721, UINT16_MAX, 8490, UINT16_MAX, 8490, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9723, 1533, UINT16_MAX, 1533, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9726, UINT16_MAX, 8491, UINT16_MAX, 8491, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9728, 1538, UINT16_MAX, 1538, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9731, UINT16_MAX, 8492, UINT16_MAX, 8492, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9733, 1543, UINT16_MAX, 1543, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9736, UINT16_MAX, 8493, UINT16_MAX, 8493, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9738, 1548, UINT16_MAX, 1548, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9741, UINT16_MAX, 8494, UINT16_MAX, 8494, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9743, 1553, UINT16_MAX, 1553, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9746, UINT16_MAX, 8495, UINT16_MAX, 8495, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9748, 1558, UINT16_MAX, 1558, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9751, UINT16_MAX, 8496, UINT16_MAX, 8496, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9753, 1563, UINT16_MAX, 1563, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9756, UINT16_MAX, 8497, UINT16_MAX, 8497, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9758, 1568, UINT16_MAX, 1568, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9761, UINT16_MAX, 8498, UINT16_MAX, 8498, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9763, 1573, UINT16_MAX, 1573, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9766, UINT16_MAX, 8499, UINT16_MAX, 8499, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9768, 1578, UINT16_MAX, 1578, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9771, UINT16_MAX, 8500, UINT16_MAX, 8500, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9773, 1583, UINT16_MAX, 1583, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9776, UINT16_MAX, 8501, UINT16_MAX, 8501, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9778, 1588, UINT16_MAX, 1588, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9781, UINT16_MAX, 8502, UINT16_MAX, 8502, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9783, 1593, UINT16_MAX, 1593, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9786, UINT16_MAX, 8503, UINT16_MAX, 8503, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9788, 1598, UINT16_MAX, 1598, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9791, UINT16_MAX, 8504, UINT16_MAX, 8504, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9793, 1603, UINT16_MAX, 1603, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9796, UINT16_MAX, 8505, UINT16_MAX, 8505, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9798, 1608, UINT16_MAX, 1608, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9801, UINT16_MAX, 8506, UINT16_MAX, 8506, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9803, 1613, UINT16_MAX, 1613, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9806, UINT16_MAX, 8507, UINT16_MAX, 8507, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9808, 1618, UINT16_MAX, 1618, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9811, UINT16_MAX, 8508, UINT16_MAX, 8508, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9813, 1623, UINT16_MAX, 1623, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9816, UINT16_MAX, 8509, UINT16_MAX, 8509, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9818, 1628, UINT16_MAX, 1628, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9821, UINT16_MAX, 8510, UINT16_MAX, 8510, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9823, 1633, UINT16_MAX, 1633, UINT16_MAX, 2884, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9826, UINT16_MAX, 8511, UINT16_MAX, 8511, 2887, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9828, 1638, UINT16_MAX, 1638, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9831, UINT16_MAX, 8512, UINT16_MAX, 8512, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9833, 1643, UINT16_MAX, 1643, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9836, UINT16_MAX, 8513, UINT16_MAX, 8513, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9838, 1648, UINT16_MAX, 1648, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9841, UINT16_MAX, 8514, UINT16_MAX, 8514, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9843, 1653, UINT16_MAX, 1653, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9846, UINT16_MAX, 8515, UINT16_MAX, 8515, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9848, 1658, UINT16_MAX, 1658, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9851, UINT16_MAX, 8516, UINT16_MAX, 8516, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9853, 1663, UINT16_MAX, 1663, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9856, UINT16_MAX, 8517, UINT16_MAX, 8517, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9858, 1668, UINT16_MAX, 1668, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9861, UINT16_MAX, 8518, UINT16_MAX, 8518, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9863, 1673, UINT16_MAX, 1673, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9866, UINT16_MAX, 8519, UINT16_MAX, 8519, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9868, 1678, UINT16_MAX, 1678, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9871, UINT16_MAX, 8520, UINT16_MAX, 8520, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9873, 1683, UINT16_MAX, 1683, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9876, UINT16_MAX, 8521, UINT16_MAX, 8521, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9878, 1688, UINT16_MAX, 1688, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9881, UINT16_MAX, 8522, UINT16_MAX, 8522, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9883, 1693, UINT16_MAX, 1693, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9886, UINT16_MAX, 8523, UINT16_MAX, 8523, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9888, 1698, UINT16_MAX, 1698, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9891, UINT16_MAX, 8524, UINT16_MAX, 8524, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9893, 1703, UINT16_MAX, 1703, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9896, UINT16_MAX, 8525, UINT16_MAX, 8525, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9898, 1708, UINT16_MAX, 1708, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9901, UINT16_MAX, 8526, UINT16_MAX, 8526, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9903, 1713, UINT16_MAX, 1713, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9906, UINT16_MAX, 8527, UINT16_MAX, 8527, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9908, 1718, UINT16_MAX, 1718, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9911, UINT16_MAX, 8528, UINT16_MAX, 8528, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9913, 1723, UINT16_MAX, 1723, UINT16_MAX, 3006, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9916, UINT16_MAX, 8529, UINT16_MAX, 8529, 3009, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9918, 1728, UINT16_MAX, 1728, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9921, UINT16_MAX, 8530, UINT16_MAX, 8530, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9923, 1733, UINT16_MAX, 1733, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9926, UINT16_MAX, 8531, UINT16_MAX, 8531, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9928, 1738, UINT16_MAX, 1738, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9931, UINT16_MAX, 8532, UINT16_MAX, 8532, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9933, 1743, UINT16_MAX, 1743, UINT16_MAX, 3024, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9936, UINT16_MAX, 8533, UINT16_MAX, 8533, 3027, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9938, 1748, UINT16_MAX, 1748, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9941, UINT16_MAX, 8534, UINT16_MAX, 8534, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9943, 1753, UINT16_MAX, 1753, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9946, UINT16_MAX, 8535, UINT16_MAX, 8535, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9948, 1758, UINT16_MAX, 1758, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9951, UINT16_MAX, 8536, UINT16_MAX, 8536, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9953, 1763, UINT16_MAX, 1763, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9956, UINT16_MAX, 8537, UINT16_MAX, 8537, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9958, 1768, UINT16_MAX, 1768, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9961, UINT16_MAX, 8538, UINT16_MAX, 8538, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9963, 1773, UINT16_MAX, 1773, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9966, UINT16_MAX, 8539, UINT16_MAX, 8539, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9968, 1778, UINT16_MAX, 1778, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9971, UINT16_MAX, 8540, UINT16_MAX, 8540, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9973, 1783, UINT16_MAX, 1783, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9976, UINT16_MAX, 8541, UINT16_MAX, 8541, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9978, 1788, UINT16_MAX, 1788, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9981, UINT16_MAX, 8542, UINT16_MAX, 8542, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9983, 1793, UINT16_MAX, 1793, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9986, UINT16_MAX, 8543, UINT16_MAX, 8543, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9988, 1798, UINT16_MAX, 1798, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9991, UINT16_MAX, 8544, UINT16_MAX, 8544, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9993, 1803, UINT16_MAX, 1803, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 9996, UINT16_MAX, 8545, UINT16_MAX, 8545, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 9998, 1808, UINT16_MAX, 1808, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10001, UINT16_MAX, 8546, UINT16_MAX, 8546, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10003, 1813, UINT16_MAX, 1813, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10006, UINT16_MAX, 8547, UINT16_MAX, 8547, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10008, 1818, UINT16_MAX, 1818, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10011, UINT16_MAX, 8548, UINT16_MAX, 8548, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10013, 1823, UINT16_MAX, 1823, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10016, UINT16_MAX, 8549, UINT16_MAX, 8549, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10018, 1828, UINT16_MAX, 1828, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10021, UINT16_MAX, 8550, UINT16_MAX, 8550, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10023, 1833, UINT16_MAX, 1833, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10026, UINT16_MAX, 8551, UINT16_MAX, 8551, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10028, 1838, UINT16_MAX, 1838, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10031, UINT16_MAX, 8552, UINT16_MAX, 8552, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10033, 1843, UINT16_MAX, 1843, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10036, UINT16_MAX, 8553, UINT16_MAX, 8553, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10038, 1848, UINT16_MAX, 1848, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10041, UINT16_MAX, 8554, UINT16_MAX, 8554, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10043, 1853, UINT16_MAX, 1853, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10046, UINT16_MAX, 8555, UINT16_MAX, 8555, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10048, 1858, UINT16_MAX, 1858, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10051, UINT16_MAX, 8556, UINT16_MAX, 8556, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10053, 1863, UINT16_MAX, 1863, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10056, UINT16_MAX, 8557, UINT16_MAX, 8557, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10058, 1868, UINT16_MAX, 1868, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10061, UINT16_MAX, 8558, UINT16_MAX, 8558, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10063, 10063, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10065, 10065, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10067, 10067, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10069, 10069, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 10071, 10071, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10073, 1738, 8532, UINT16_MAX, 8532, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1883, UINT16_MAX, 1883, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10076, 1886, UINT16_MAX, 1886, UINT16_MAX, 3241, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10079, UINT16_MAX, 8559, UINT16_MAX, 8559, 3250, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10081, 1891, UINT16_MAX, 1891, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10084, UINT16_MAX, 8560, UINT16_MAX, 8560, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10086, 1896, UINT16_MAX, 1896, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10089, UINT16_MAX, 8561, UINT16_MAX, 8561, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10091, 1901, UINT16_MAX, 1901, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10094, UINT16_MAX, 8562, UINT16_MAX, 8562, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10096, 1906, UINT16_MAX, 1906, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10099, UINT16_MAX, 8563, UINT16_MAX, 8563, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10101, 1911, UINT16_MAX, 1911, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10104, UINT16_MAX, 8564, UINT16_MAX, 8564, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10106, 1916, UINT16_MAX, 1916, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10109, UINT16_MAX, 8565, UINT16_MAX, 8565, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10111, 1921, UINT16_MAX, 1921, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10114, UINT16_MAX, 8566, UINT16_MAX, 8566, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10116, 1926, UINT16_MAX, 1926, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10119, UINT16_MAX, 8567, UINT16_MAX, 8567, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10121, 1931, UINT16_MAX, 1931, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10124, UINT16_MAX, 8568, UINT16_MAX, 8568, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10126, 1936, UINT16_MAX, 1936, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10129, UINT16_MAX, 8569, UINT16_MAX, 8569, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10131, 1941, UINT16_MAX, 1941, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10134, UINT16_MAX, 8570, UINT16_MAX, 8570, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10136, 1946, UINT16_MAX, 1946, UINT16_MAX, 3455, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10139, UINT16_MAX, 8571, UINT16_MAX, 8571, 3458, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10141, 1951, UINT16_MAX, 1951, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10144, UINT16_MAX, 8572, UINT16_MAX, 8572, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10146, 1956, UINT16_MAX, 1956, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10149, UINT16_MAX, 8573, UINT16_MAX, 8573, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10151, 1961, UINT16_MAX, 1961, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10154, UINT16_MAX, 8574, UINT16_MAX, 8574, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10156, 1966, UINT16_MAX, 1966, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10159, UINT16_MAX, 8575, UINT16_MAX, 8575, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10161, 1971, UINT16_MAX, 1971, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10164, UINT16_MAX, 8576, UINT16_MAX, 8576, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10166, 1976, UINT16_MAX, 1976, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10169, UINT16_MAX, 8577, UINT16_MAX, 8577, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10171, 1981, UINT16_MAX, 1981, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10174, UINT16_MAX, 8578, UINT16_MAX, 8578, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10176, 1986, UINT16_MAX, 1986, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10179, UINT16_MAX, 8579, UINT16_MAX, 8579, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10181, 1991, UINT16_MAX, 1991, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10184, UINT16_MAX, 8580, UINT16_MAX, 8580, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10186, 1996, UINT16_MAX, 1996, UINT16_MAX, 3559, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10189, UINT16_MAX, 8581, UINT16_MAX, 8581, 3562, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10191, 2001, UINT16_MAX, 2001, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10194, UINT16_MAX, 8582, UINT16_MAX, 8582, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10196, 2006, UINT16_MAX, 2006, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10199, UINT16_MAX, 8583, UINT16_MAX, 8583, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10201, 2011, UINT16_MAX, 2011, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10204, UINT16_MAX, 8584, UINT16_MAX, 8584, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10206, 2016, UINT16_MAX, 2016, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10209, UINT16_MAX, 8585, UINT16_MAX, 8585, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10211, 2021, UINT16_MAX, 2021, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10214, UINT16_MAX, 8586, UINT16_MAX, 8586, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10216, 2026, UINT16_MAX, 2026, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10219, UINT16_MAX, 8587, UINT16_MAX, 8587, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10221, 2031, UINT16_MAX, 2031, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10224, UINT16_MAX, 8588, UINT16_MAX, 8588, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10226, 2036, UINT16_MAX, 2036, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10229, UINT16_MAX, 8589, UINT16_MAX, 8589, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10231, 2041, UINT16_MAX, 2041, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10234, UINT16_MAX, 8590, UINT16_MAX, 8590, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10236, 2046, UINT16_MAX, 2046, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10239, UINT16_MAX, 8591, UINT16_MAX, 8591, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10241, 2051, UINT16_MAX, 2051, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10244, UINT16_MAX, 8592, UINT16_MAX, 8592, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10246, 2056, UINT16_MAX, 2056, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10249, UINT16_MAX, 8593, UINT16_MAX, 8593, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10251, 2061, UINT16_MAX, 2061, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10254, UINT16_MAX, 8594, UINT16_MAX, 8594, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10256, 2066, UINT16_MAX, 2066, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10259, UINT16_MAX, 8595, UINT16_MAX, 8595, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10261, 2071, UINT16_MAX, 2071, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10264, UINT16_MAX, 8596, UINT16_MAX, 8596, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10266, 2076, UINT16_MAX, 2076, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10269, UINT16_MAX, 8597, UINT16_MAX, 8597, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10271, 2081, UINT16_MAX, 2081, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10274, UINT16_MAX, 8598, UINT16_MAX, 8598, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10276, 2086, UINT16_MAX, 2086, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10279, UINT16_MAX, 8599, UINT16_MAX, 8599, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10281, 2091, UINT16_MAX, 2091, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10284, UINT16_MAX, 8600, UINT16_MAX, 8600, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10286, 2096, UINT16_MAX, 2096, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10289, UINT16_MAX, 8601, UINT16_MAX, 8601, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10291, 2101, UINT16_MAX, 2101, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10294, UINT16_MAX, 8602, UINT16_MAX, 8602, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10296, 2106, UINT16_MAX, 2106, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10299, UINT16_MAX, 8603, UINT16_MAX, 8603, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2109, UINT16_MAX, 2109, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8604, UINT16_MAX, 8604, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2110, UINT16_MAX, 2110, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8605, UINT16_MAX, 8605, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2111, UINT16_MAX, 2111, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8606, UINT16_MAX, 8606, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10304, UINT16_MAX, 8607, UINT16_MAX, 8607, 3761, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10306, UINT16_MAX, 8608, UINT16_MAX, 8608, 3814, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10308, UINT16_MAX, 8609, UINT16_MAX, 8609, 4793, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10310, UINT16_MAX, 8610, UINT16_MAX, 8610, 4796, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10312, UINT16_MAX, 8611, UINT16_MAX, 8611, 4799, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10314, UINT16_MAX, 8612, UINT16_MAX, 8612, 4802, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10316, UINT16_MAX, 8613, UINT16_MAX, 8613, 4805, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10318, UINT16_MAX, 8614, UINT16_MAX, 8614, 4808, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10320, 2130, UINT16_MAX, 2130, UINT16_MAX, 3867, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10323, 2133, UINT16_MAX, 2133, UINT16_MAX, 3920, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10326, 2136, UINT16_MAX, 2136, UINT16_MAX, 4811, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10329, 2139, UINT16_MAX, 2139, UINT16_MAX, 4814, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10332, 2142, UINT16_MAX, 2142, UINT16_MAX, 4817, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10335, 2145, UINT16_MAX, 2145, UINT16_MAX, 4820, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10338, 2148, UINT16_MAX, 2148, UINT16_MAX, 4823, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10341, 2151, UINT16_MAX, 2151, UINT16_MAX, 4826, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10344, UINT16_MAX, 8615, UINT16_MAX, 8615, 3973, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10346, UINT16_MAX, 8616, UINT16_MAX, 8616, 3977, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10348, UINT16_MAX, 8617, UINT16_MAX, 8617, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10350, UINT16_MAX, 8618, UINT16_MAX, 8618, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10352, UINT16_MAX, 8619, UINT16_MAX, 8619, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10354, UINT16_MAX, 8620, UINT16_MAX, 8620, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10356, 2166, UINT16_MAX, 2166, UINT16_MAX, 3981, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10359, 2169, UINT16_MAX, 2169, UINT16_MAX, 3985, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10362, 2172, UINT16_MAX, 2172, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10365, 2175, UINT16_MAX, 2175, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10368, 2178, UINT16_MAX, 2178, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10371, 2181, UINT16_MAX, 2181, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10374, UINT16_MAX, 8621, UINT16_MAX, 8621, 3989, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10376, UINT16_MAX, 8622, UINT16_MAX, 8622, 4042, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10378, UINT16_MAX, 8623, UINT16_MAX, 8623, 4829, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10380, UINT16_MAX, 8624, UINT16_MAX, 8624, 4832, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10382, UINT16_MAX, 8625, UINT16_MAX, 8625, 4835, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10384, UINT16_MAX, 8626, UINT16_MAX, 8626, 4838, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10386, UINT16_MAX, 8627, UINT16_MAX, 8627, 4841, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10388, UINT16_MAX, 8628, UINT16_MAX, 8628, 4844, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10390, 2200, UINT16_MAX, 2200, UINT16_MAX, 4095, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10393, 2203, UINT16_MAX, 2203, UINT16_MAX, 4148, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10396, 2206, UINT16_MAX, 2206, UINT16_MAX, 4847, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10399, 2209, UINT16_MAX, 2209, UINT16_MAX, 4850, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10402, 2212, UINT16_MAX, 2212, UINT16_MAX, 4853, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10405, 2215, UINT16_MAX, 2215, UINT16_MAX, 4856, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10408, 2218, UINT16_MAX, 2218, UINT16_MAX, 4859, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10411, 2221, UINT16_MAX, 2221, UINT16_MAX, 4862, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10414, UINT16_MAX, 8629, UINT16_MAX, 8629, 4201, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10416, UINT16_MAX, 8630, UINT16_MAX, 8630, 4253, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10418, UINT16_MAX, 8631, UINT16_MAX, 8631, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10420, UINT16_MAX, 8632, UINT16_MAX, 8632, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10422, UINT16_MAX, 8633, UINT16_MAX, 8633, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10424, UINT16_MAX, 8634, UINT16_MAX, 8634, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10426, UINT16_MAX, 8635, UINT16_MAX, 8635, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10428, UINT16_MAX, 8636, UINT16_MAX, 8636, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10430, 2240, UINT16_MAX, 2240, UINT16_MAX, 4305, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10433, 2243, UINT16_MAX, 2243, UINT16_MAX, 4357, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10436, 2246, UINT16_MAX, 2246, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10439, 2249, UINT16_MAX, 2249, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10442, 2252, UINT16_MAX, 2252, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10445, 2255, UINT16_MAX, 2255, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10448, 2258, UINT16_MAX, 2258, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10451, 2261, UINT16_MAX, 2261, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10454, UINT16_MAX, 8637, UINT16_MAX, 8637, 4409, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10456, UINT16_MAX, 8638, UINT16_MAX, 8638, 4413, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10458, UINT16_MAX, 8639, UINT16_MAX, 8639, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10460, UINT16_MAX, 8640, UINT16_MAX, 8640, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10462, UINT16_MAX, 8641, UINT16_MAX, 8641, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10464, UINT16_MAX, 8642, UINT16_MAX, 8642, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10466, 2276, UINT16_MAX, 2276, UINT16_MAX, 4417, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10469, 2279, UINT16_MAX, 2279, UINT16_MAX, 4421, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10472, 2282, UINT16_MAX, 2282, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10475, 2285, UINT16_MAX, 2285, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10478, 2288, UINT16_MAX, 2288, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10481, 2291, UINT16_MAX, 2291, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10484, 10484, UINT16_MAX, UINT16_MAX, UINT16_MAX, 4425, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10486, UINT16_MAX, 8643, UINT16_MAX, 8643, 4477, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10488, 18682, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10493, UINT16_MAX, 8644, UINT16_MAX, 8644, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10495, 18689, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10500, UINT16_MAX, 8645, UINT16_MAX, 8645, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10502, 18696, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10507, UINT16_MAX, 8646, UINT16_MAX, 8646, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10509, 2319, UINT16_MAX, 2319, UINT16_MAX, 4529, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10512, 2322, UINT16_MAX, 2322, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10515, 2325, UINT16_MAX, 2325, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10518, 2328, UINT16_MAX, 2328, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10521, UINT16_MAX, 8647, UINT16_MAX, 8647, 4581, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10523, UINT16_MAX, 8648, UINT16_MAX, 8648, 4634, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10525, UINT16_MAX, 8649, UINT16_MAX, 8649, 4865, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10527, UINT16_MAX, 8650, UINT16_MAX, 8650, 4868, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10529, UINT16_MAX, 8651, UINT16_MAX, 8651, 4871, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10531, UINT16_MAX, 8652, UINT16_MAX, 8652, 4874, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10533, UINT16_MAX, 8653, UINT16_MAX, 8653, 4877, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10535, UINT16_MAX, 8654, UINT16_MAX, 8654, 4880, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10537, 2347, UINT16_MAX, 2347, UINT16_MAX, 4687, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10540, 2350, UINT16_MAX, 2350, UINT16_MAX, 4740, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10543, 2353, UINT16_MAX, 2353, UINT16_MAX, 4883, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10546, 2356, UINT16_MAX, 2356, UINT16_MAX, 4886, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10549, 2359, UINT16_MAX, 2359, UINT16_MAX, 4889, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10552, 2362, UINT16_MAX, 2362, UINT16_MAX, 4892, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10555, 2365, UINT16_MAX, 2365, UINT16_MAX, 4895, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10558, 2368, UINT16_MAX, 2368, UINT16_MAX, 4898, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10561, UINT16_MAX, 8655, UINT16_MAX, 8655, 4901, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 820, UINT16_MAX, 8656, UINT16_MAX, 8656, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10563, UINT16_MAX, 8657, UINT16_MAX, 8657, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 824, UINT16_MAX, 8658, UINT16_MAX, 8658, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10565, UINT16_MAX, 8659, UINT16_MAX, 8659, 4910, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 827, UINT16_MAX, 8660, UINT16_MAX, 8660, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10567, UINT16_MAX, 8661, UINT16_MAX, 8661, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 830, UINT16_MAX, 8662, UINT16_MAX, 8662, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10569, UINT16_MAX, 8663, UINT16_MAX, 8663, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 833, UINT16_MAX, 8664, UINT16_MAX, 8664, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10571, UINT16_MAX, 8665, UINT16_MAX, 8665, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 836, UINT16_MAX, 8666, UINT16_MAX, 8666, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10573, UINT16_MAX, 8667, UINT16_MAX, 8667, 5030, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 839, UINT16_MAX, 8668, UINT16_MAX, 8668, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10575, 10577, 8669, UINT16_MAX, 8669, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10579, 10581, 8670, UINT16_MAX, 8670, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10583, 10585, 8671, UINT16_MAX, 8671, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10587, 10589, 8672, UINT16_MAX, 8672, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10591, 10593, 8673, UINT16_MAX, 8673, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10595, 10597, 8674, UINT16_MAX, 8674, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10599, 10601, 8675, UINT16_MAX, 8675, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10603, 10605, 8676, UINT16_MAX, 8676, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10607, 2417, UINT16_MAX, 2417, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10610, 2420, UINT16_MAX, 2420, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10613, 2423, UINT16_MAX, 2423, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10616, 2426, UINT16_MAX, 2426, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10619, 2429, UINT16_MAX, 2429, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10622, 2432, UINT16_MAX, 2432, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10625, 2435, UINT16_MAX, 2435, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10628, 2438, UINT16_MAX, 2438, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10631, 10633, 8677, UINT16_MAX, 8677, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10635, 10637, 8678, UINT16_MAX, 8678, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10639, 10641, 8679, UINT16_MAX, 8679, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10643, 10645, 8680, UINT16_MAX, 8680, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10647, 10649, 8681, UINT16_MAX, 8681, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10651, 10653, 8682, UINT16_MAX, 8682, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10655, 10657, 8683, UINT16_MAX, 8683, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10659, 10661, 8684, UINT16_MAX, 8684, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10663, 2473, UINT16_MAX, 2473, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10666, 2476, UINT16_MAX, 2476, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10669, 2479, UINT16_MAX, 2479, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10672, 2482, UINT16_MAX, 2482, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10675, 2485, UINT16_MAX, 2485, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10678, 2488, UINT16_MAX, 2488, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10681, 2491, UINT16_MAX, 2491, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10684, 2494, UINT16_MAX, 2494, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10687, 10689, 8685, UINT16_MAX, 8685, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10691, 10693, 8686, UINT16_MAX, 8686, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10695, 10697, 8687, UINT16_MAX, 8687, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10699, 10701, 8688, UINT16_MAX, 8688, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10703, 10705, 8689, UINT16_MAX, 8689, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10707, 10709, 8690, UINT16_MAX, 8690, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10711, 10713, 8691, UINT16_MAX, 8691, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10715, 10717, 8692, UINT16_MAX, 8692, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10719, 2529, UINT16_MAX, 2529, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10722, 2532, UINT16_MAX, 2532, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10725, 2535, UINT16_MAX, 2535, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10728, 2538, UINT16_MAX, 2538, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10731, 2541, UINT16_MAX, 2541, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10734, 2544, UINT16_MAX, 2544, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10737, 2547, UINT16_MAX, 2547, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10740, 2550, UINT16_MAX, 2550, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10743, UINT16_MAX, 8693, UINT16_MAX, 8693, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10745, UINT16_MAX, 8694, UINT16_MAX, 8694, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10747, 10749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10751, 10753, 8695, UINT16_MAX, 8695, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10755, 10757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10759, 10759, UINT16_MAX, UINT16_MAX, UINT16_MAX, 4907, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10761, 18955, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10766, 2576, UINT16_MAX, 2576, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10769, 2579, UINT16_MAX, 2579, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10772, 2582, UINT16_MAX, 2582, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2583, 2584, UINT16_MAX, 2584, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10777, 2587, UINT16_MAX, 2587, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 807, 807, 7127, UINT16_MAX, 7127, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 4919, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10786, 10788, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10790, 10792, 8696, UINT16_MAX, 8696, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10794, 10796, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10798, 10798, UINT16_MAX, UINT16_MAX, UINT16_MAX, 4916, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10800, 18994, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10805, 2615, UINT16_MAX, 2615, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2616, 2617, UINT16_MAX, 2617, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10810, 2620, UINT16_MAX, 2620, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2621, 2622, UINT16_MAX, 2622, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10815, 2625, UINT16_MAX, 2625, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10818, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10822, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10824, UINT16_MAX, 8697, UINT16_MAX, 8697, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10826, UINT16_MAX, 8698, UINT16_MAX, 8698, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10828, 19022, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 2641, 17226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10834, 10834, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10836, 19030, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10841, 2651, UINT16_MAX, 2651, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10844, 2654, UINT16_MAX, 2654, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10847, 2657, UINT16_MAX, 2657, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2658, 2659, UINT16_MAX, 2659, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10856, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10858, UINT16_MAX, 8699, UINT16_MAX, 8699, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10860, UINT16_MAX, 8700, UINT16_MAX, 8700, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10862, 19056, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 2675, 17267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10868, 10868, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10870, UINT16_MAX, 8701, UINT16_MAX, 8701, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10872, 10872, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10874, 19068, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10879, 2689, UINT16_MAX, 2689, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10882, 2692, UINT16_MAX, 2692, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10885, 2695, UINT16_MAX, 2695, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2696, 2697, UINT16_MAX, 2697, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10890, 2700, UINT16_MAX, 2700, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 10893, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 2703, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 2704, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10897, 10899, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10901, 10903, 8702, UINT16_MAX, 8702, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10905, 10907, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10909, 10909, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5036, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, 10911, 19105, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10916, 2726, UINT16_MAX, 2726, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2727, 2728, UINT16_MAX, 2728, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 10921, 2731, UINT16_MAX, 2731, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2732, 2733, UINT16_MAX, 2733, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LT, 0, UTF8PROC_BIDI_CLASS_L, 0, 10926, 2736, UINT16_MAX, 2736, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, 2737, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10930, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 4971, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, 0, 2740, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, 0, 2741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, UTF8PROC_DECOMP_TYPE_COMPAT, 26, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, UTF8PROC_DECOMP_TYPE_COMPAT, 26, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, UTF8PROC_DECOMP_TYPE_NOBREAK, 26, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_BN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_BN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_ZWJ}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NOBREAK, 2742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10935, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PI, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PF, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 2745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10938, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19132, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZL, 0, UTF8PROC_BIDI_CLASS_WS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_ZP, 0, UTF8PROC_BIDI_CLASS_B, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_LRE, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_RLE, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_PDF, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_LRO, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_RLO, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_COMPAT, 10943, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_COMPAT, 19137, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10948, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10953, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10955, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_CS, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10957, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10959, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10961, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27347, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_LRI, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_RLI, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_FSI, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_PDI, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 8, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUPER, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SUPER, 2782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SUPER, 2783, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 2784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 2785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 2786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 13, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_SUB, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SUB, 2782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SUB, 2783, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUB, 2784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUB, 2785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUB, 2786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 4, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 14, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 23, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 7, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 10, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 11, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 12, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 13, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 15, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 18, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 19, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_COMPAT, 10979, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19173, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19176, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10988, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19182, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19185, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2804, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10997, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 6, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 11, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 10999, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 11002, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19196, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 11007, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2818, 866, UINT16_MAX, 866, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 1456, 10, UINT16_MAX, 10, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, 2819, 65, UINT16_MAX, 65, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 4, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2821, UINT16_MAX, 2821, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 14, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2822, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2823, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2824, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2825, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 8, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19210, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 858, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2829, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2830, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FONT, 2831, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 9, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8703, UINT16_MAX, 8703, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19216, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19219, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 27414, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19229, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19232, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19235, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19238, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19241, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19244, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19247, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19253, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19256, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19259, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 11070, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 1454, 2880, UINT16_MAX, 2880, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11073, 2883, UINT16_MAX, 2883, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19268, 2887, UINT16_MAX, 2887, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11080, 2890, UINT16_MAX, 2890, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2891, 2892, UINT16_MAX, 2892, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11085, 2895, UINT16_MAX, 2895, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19280, 2899, UINT16_MAX, 2899, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 27476, 2904, UINT16_MAX, 2904, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11097, 2907, UINT16_MAX, 2907, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2908, 2909, UINT16_MAX, 2909, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11102, 2912, UINT16_MAX, 2912, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19297, 2916, UINT16_MAX, 2916, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 1457, 2917, UINT16_MAX, 2917, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2795, 2918, UINT16_MAX, 2918, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 1449, 2919, UINT16_MAX, 2919, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 1458, 2920, UINT16_MAX, 2920, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 8, UINT16_MAX, 8704, UINT16_MAX, 8704, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11113, UINT16_MAX, 8705, UINT16_MAX, 8705, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19307, UINT16_MAX, 8706, UINT16_MAX, 8706, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11118, UINT16_MAX, 8707, UINT16_MAX, 8707, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21, UINT16_MAX, 8708, UINT16_MAX, 8708, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11120, UINT16_MAX, 8709, UINT16_MAX, 8709, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19314, UINT16_MAX, 8710, UINT16_MAX, 8710, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 27509, UINT16_MAX, 8711, UINT16_MAX, 8711, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11129, UINT16_MAX, 8712, UINT16_MAX, 8712, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23, UINT16_MAX, 8713, UINT16_MAX, 8713, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11131, UINT16_MAX, 8714, UINT16_MAX, 8714, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19325, UINT16_MAX, 8715, UINT16_MAX, 8715, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 11, UINT16_MAX, 8716, UINT16_MAX, 8716, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 2, UINT16_MAX, 8717, UINT16_MAX, 8717, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3, UINT16_MAX, 8718, UINT16_MAX, 8718, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12, UINT16_MAX, 8719, UINT16_MAX, 8719, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 2944, UINT16_MAX, 2944, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8720, UINT16_MAX, 8720, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FRACTION, 19329, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5039, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5042, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5045, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11150, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5048, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5054, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5051, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5057, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11152, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5060, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11154, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5063, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11156, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5066, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11158, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5069, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11160, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 11162, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19356, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 11167, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19361, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5072, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11172, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5075, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11174, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5078, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11176, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5081, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11178, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5090, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11180, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5087, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11182, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5099, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5102, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11184, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11186, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11188, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11190, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11192, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5105, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5108, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11194, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11196, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5111, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5114, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11198, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11200, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5117, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5120, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5147, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5150, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11202, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11204, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5123, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5126, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11206, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11208, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5129, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5132, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11210, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11212, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5153, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5156, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5135, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5138, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5141, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5144, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11214, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11216, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11218, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11220, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5159, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5162, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5165, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5168, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11222, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11224, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11228, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11230, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11232, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11234, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11236, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, 0, 3046, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, 0, 3047, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11240, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11242, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11244, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11246, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11248, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11252, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11254, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11256, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11258, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 11260, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27673, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27677, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27681, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27685, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27689, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27693, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27697, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27701, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27705, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27709, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27713, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11333, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11335, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11337, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11339, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11341, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11343, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11345, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11347, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 11349, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19543, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 19573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19579, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19582, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19585, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19588, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19591, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19594, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19597, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19600, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19603, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19606, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19609, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19612, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19615, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19618, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19621, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19624, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19627, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19630, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19636, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19639, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19642, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19645, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19648, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 19651, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1446, 3270, UINT16_MAX, 3270, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1448, 3271, UINT16_MAX, 3271, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2795, 3272, UINT16_MAX, 3272, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1449, 3273, UINT16_MAX, 3273, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1450, 3274, UINT16_MAX, 3274, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2820, 3275, UINT16_MAX, 3275, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1452, 3276, UINT16_MAX, 3276, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1453, 3277, UINT16_MAX, 3277, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1454, 3278, UINT16_MAX, 3278, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1455, 3279, UINT16_MAX, 3279, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1456, 3280, UINT16_MAX, 3280, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1457, 3281, UINT16_MAX, 3281, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1458, 3282, UINT16_MAX, 3282, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1459, 3283, UINT16_MAX, 3283, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1460, 3284, UINT16_MAX, 3284, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1462, 3285, UINT16_MAX, 3285, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2809, 3286, UINT16_MAX, 3286, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1463, 3287, UINT16_MAX, 3287, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3288, 3289, UINT16_MAX, 3289, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1464, 3290, UINT16_MAX, 3290, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1465, 3291, UINT16_MAX, 3291, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2891, 3292, UINT16_MAX, 3292, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1466, 3293, UINT16_MAX, 3293, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2908, 3294, UINT16_MAX, 3294, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3295, 3296, UINT16_MAX, 3296, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2817, 3297, UINT16_MAX, 3297, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 0, UINT16_MAX, 8721, UINT16_MAX, 8721, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1, UINT16_MAX, 8722, UINT16_MAX, 8722, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2, UINT16_MAX, 8723, UINT16_MAX, 8723, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3, UINT16_MAX, 8724, UINT16_MAX, 8724, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4, UINT16_MAX, 8725, UINT16_MAX, 8725, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 5, UINT16_MAX, 8726, UINT16_MAX, 8726, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 6, UINT16_MAX, 8727, UINT16_MAX, 8727, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 7, UINT16_MAX, 8728, UINT16_MAX, 8728, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 8, UINT16_MAX, 8729, UINT16_MAX, 8729, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 9, UINT16_MAX, 8730, UINT16_MAX, 8730, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 10, UINT16_MAX, 8731, UINT16_MAX, 8731, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 11, UINT16_MAX, 8732, UINT16_MAX, 8732, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12, UINT16_MAX, 8733, UINT16_MAX, 8733, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 13, UINT16_MAX, 8734, UINT16_MAX, 8734, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 14, UINT16_MAX, 8735, UINT16_MAX, 8735, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 15, UINT16_MAX, 8736, UINT16_MAX, 8736, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 16, UINT16_MAX, 8737, UINT16_MAX, 8737, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 17, UINT16_MAX, 8738, UINT16_MAX, 8738, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 18, UINT16_MAX, 8739, UINT16_MAX, 8739, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 19, UINT16_MAX, 8740, UINT16_MAX, 8740, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 20, UINT16_MAX, 8741, UINT16_MAX, 8741, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 21, UINT16_MAX, 8742, UINT16_MAX, 8742, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 22, UINT16_MAX, 8743, UINT16_MAX, 8743, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 23, UINT16_MAX, 8744, UINT16_MAX, 8744, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 24, UINT16_MAX, 8745, UINT16_MAX, 8745, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 25, UINT16_MAX, 8746, UINT16_MAX, 8746, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_E_BASE}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_E_BASE}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 27874, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19686, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 11497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 19691, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, 11502, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, true, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5171, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3312, UINT16_MAX, 3312, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3313, UINT16_MAX, 3313, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3314, UINT16_MAX, 3314, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3315, UINT16_MAX, 3315, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3316, UINT16_MAX, 3316, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3317, UINT16_MAX, 3317, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3318, UINT16_MAX, 3318, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3319, UINT16_MAX, 3319, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3320, UINT16_MAX, 3320, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3321, UINT16_MAX, 3321, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3322, UINT16_MAX, 3322, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3323, UINT16_MAX, 3323, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3324, UINT16_MAX, 3324, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3325, UINT16_MAX, 3325, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3326, UINT16_MAX, 3326, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3327, UINT16_MAX, 3327, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3328, UINT16_MAX, 3328, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3329, UINT16_MAX, 3329, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3330, UINT16_MAX, 3330, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3331, UINT16_MAX, 3331, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3332, UINT16_MAX, 3332, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3333, UINT16_MAX, 3333, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3334, UINT16_MAX, 3334, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3335, UINT16_MAX, 3335, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3336, UINT16_MAX, 3336, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3337, UINT16_MAX, 3337, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3338, UINT16_MAX, 3338, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3339, UINT16_MAX, 3339, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3340, UINT16_MAX, 3340, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3341, UINT16_MAX, 3341, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3342, UINT16_MAX, 3342, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3343, UINT16_MAX, 3343, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3344, UINT16_MAX, 3344, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3345, UINT16_MAX, 3345, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3346, UINT16_MAX, 3346, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3347, UINT16_MAX, 3347, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3348, UINT16_MAX, 3348, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3349, UINT16_MAX, 3349, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3350, UINT16_MAX, 3350, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3351, UINT16_MAX, 3351, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3352, UINT16_MAX, 3352, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3353, UINT16_MAX, 3353, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3354, UINT16_MAX, 3354, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3355, UINT16_MAX, 3355, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3356, UINT16_MAX, 3356, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3357, UINT16_MAX, 3357, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3358, UINT16_MAX, 3358, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8747, UINT16_MAX, 8747, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8748, UINT16_MAX, 8748, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8749, UINT16_MAX, 8749, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8750, UINT16_MAX, 8750, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8751, UINT16_MAX, 8751, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8752, UINT16_MAX, 8752, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8753, UINT16_MAX, 8753, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8754, UINT16_MAX, 8754, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8755, UINT16_MAX, 8755, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8756, UINT16_MAX, 8756, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8757, UINT16_MAX, 8757, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8758, UINT16_MAX, 8758, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8759, UINT16_MAX, 8759, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8760, UINT16_MAX, 8760, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8761, UINT16_MAX, 8761, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8762, UINT16_MAX, 8762, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8763, UINT16_MAX, 8763, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8764, UINT16_MAX, 8764, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8765, UINT16_MAX, 8765, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8766, UINT16_MAX, 8766, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8767, UINT16_MAX, 8767, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8768, UINT16_MAX, 8768, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8769, UINT16_MAX, 8769, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8770, UINT16_MAX, 8770, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8771, UINT16_MAX, 8771, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8772, UINT16_MAX, 8772, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8773, UINT16_MAX, 8773, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8774, UINT16_MAX, 8774, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8775, UINT16_MAX, 8775, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8776, UINT16_MAX, 8776, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8777, UINT16_MAX, 8777, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8778, UINT16_MAX, 8778, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8779, UINT16_MAX, 8779, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8780, UINT16_MAX, 8780, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8781, UINT16_MAX, 8781, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8782, UINT16_MAX, 8782, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8783, UINT16_MAX, 8783, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8784, UINT16_MAX, 8784, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8785, UINT16_MAX, 8785, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8786, UINT16_MAX, 8786, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8787, UINT16_MAX, 8787, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8788, UINT16_MAX, 8788, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8789, UINT16_MAX, 8789, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8790, UINT16_MAX, 8790, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8791, UINT16_MAX, 8791, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8792, UINT16_MAX, 8792, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8793, UINT16_MAX, 8793, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3359, UINT16_MAX, 3359, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8794, UINT16_MAX, 8794, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3360, UINT16_MAX, 3360, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3361, UINT16_MAX, 3361, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3362, UINT16_MAX, 3362, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8795, UINT16_MAX, 8795, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8796, UINT16_MAX, 8796, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3363, UINT16_MAX, 3363, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8797, UINT16_MAX, 8797, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3364, UINT16_MAX, 3364, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8798, UINT16_MAX, 8798, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3365, UINT16_MAX, 3365, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8799, UINT16_MAX, 8799, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1468, UINT16_MAX, 1468, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1486, UINT16_MAX, 1486, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1467, UINT16_MAX, 1467, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1475, UINT16_MAX, 1475, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3366, UINT16_MAX, 3366, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8800, UINT16_MAX, 8800, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3367, UINT16_MAX, 3367, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8801, UINT16_MAX, 8801, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUB, 9, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 2891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3368, UINT16_MAX, 3368, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3369, UINT16_MAX, 3369, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3370, UINT16_MAX, 3370, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8802, UINT16_MAX, 8802, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3371, UINT16_MAX, 3371, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8803, UINT16_MAX, 8803, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3372, UINT16_MAX, 3372, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8804, UINT16_MAX, 8804, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3373, UINT16_MAX, 3373, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8805, UINT16_MAX, 8805, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3374, UINT16_MAX, 3374, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8806, UINT16_MAX, 8806, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3375, UINT16_MAX, 3375, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8807, UINT16_MAX, 8807, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3376, UINT16_MAX, 3376, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8808, UINT16_MAX, 8808, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3377, UINT16_MAX, 3377, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8809, UINT16_MAX, 8809, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3378, UINT16_MAX, 3378, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8810, UINT16_MAX, 8810, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3379, UINT16_MAX, 3379, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8811, UINT16_MAX, 8811, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3380, UINT16_MAX, 3380, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8812, UINT16_MAX, 8812, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3381, UINT16_MAX, 3381, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8813, UINT16_MAX, 8813, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3382, UINT16_MAX, 3382, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8814, UINT16_MAX, 8814, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3383, UINT16_MAX, 3383, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8815, UINT16_MAX, 8815, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3384, UINT16_MAX, 3384, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8816, UINT16_MAX, 8816, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3385, UINT16_MAX, 3385, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8817, UINT16_MAX, 8817, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3386, UINT16_MAX, 3386, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8818, UINT16_MAX, 8818, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3387, UINT16_MAX, 3387, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8819, UINT16_MAX, 8819, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3388, UINT16_MAX, 3388, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8820, UINT16_MAX, 8820, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3389, UINT16_MAX, 3389, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8821, UINT16_MAX, 8821, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3390, UINT16_MAX, 3390, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8822, UINT16_MAX, 8822, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3391, UINT16_MAX, 3391, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8823, UINT16_MAX, 8823, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3392, UINT16_MAX, 3392, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8824, UINT16_MAX, 8824, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3393, UINT16_MAX, 3393, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8825, UINT16_MAX, 8825, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3394, UINT16_MAX, 3394, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8826, UINT16_MAX, 8826, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3395, UINT16_MAX, 3395, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8827, UINT16_MAX, 8827, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3396, UINT16_MAX, 3396, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8828, UINT16_MAX, 8828, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3397, UINT16_MAX, 3397, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8829, UINT16_MAX, 8829, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3398, UINT16_MAX, 3398, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8830, UINT16_MAX, 8830, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3399, UINT16_MAX, 3399, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8831, UINT16_MAX, 8831, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3400, UINT16_MAX, 3400, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8832, UINT16_MAX, 8832, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3401, UINT16_MAX, 3401, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8833, UINT16_MAX, 8833, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3402, UINT16_MAX, 3402, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8834, UINT16_MAX, 8834, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3403, UINT16_MAX, 3403, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8835, UINT16_MAX, 8835, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3404, UINT16_MAX, 3404, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8836, UINT16_MAX, 8836, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3405, UINT16_MAX, 3405, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8837, UINT16_MAX, 8837, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3406, UINT16_MAX, 3406, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8838, UINT16_MAX, 8838, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3407, UINT16_MAX, 3407, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8839, UINT16_MAX, 8839, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3408, UINT16_MAX, 3408, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8840, UINT16_MAX, 8840, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3409, UINT16_MAX, 3409, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8841, UINT16_MAX, 8841, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3410, UINT16_MAX, 3410, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8842, UINT16_MAX, 8842, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3411, UINT16_MAX, 3411, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8843, UINT16_MAX, 8843, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3412, UINT16_MAX, 3412, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8844, UINT16_MAX, 8844, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3413, UINT16_MAX, 3413, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8845, UINT16_MAX, 8845, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3414, UINT16_MAX, 3414, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8846, UINT16_MAX, 8846, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3415, UINT16_MAX, 3415, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8847, UINT16_MAX, 8847, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3416, UINT16_MAX, 3416, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8848, UINT16_MAX, 8848, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3417, UINT16_MAX, 3417, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8849, UINT16_MAX, 8849, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3418, UINT16_MAX, 3418, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8850, UINT16_MAX, 8850, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3419, UINT16_MAX, 3419, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8851, UINT16_MAX, 8851, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3420, UINT16_MAX, 3420, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8852, UINT16_MAX, 8852, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3421, UINT16_MAX, 3421, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8853, UINT16_MAX, 8853, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 3422, UINT16_MAX, 3422, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8854, UINT16_MAX, 8854, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8855, UINT16_MAX, 8855, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8856, UINT16_MAX, 8856, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8857, UINT16_MAX, 8857, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8858, UINT16_MAX, 8858, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8859, UINT16_MAX, 8859, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8860, UINT16_MAX, 8860, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8861, UINT16_MAX, 8861, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8862, UINT16_MAX, 8862, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8863, UINT16_MAX, 8863, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8864, UINT16_MAX, 8864, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8865, UINT16_MAX, 8865, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8866, UINT16_MAX, 8866, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8867, UINT16_MAX, 8867, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8868, UINT16_MAX, 8868, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8869, UINT16_MAX, 8869, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8870, UINT16_MAX, 8870, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8871, UINT16_MAX, 8871, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8872, UINT16_MAX, 8872, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8873, UINT16_MAX, 8873, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8874, UINT16_MAX, 8874, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8875, UINT16_MAX, 8875, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8876, UINT16_MAX, 8876, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8877, UINT16_MAX, 8877, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8878, UINT16_MAX, 8878, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8879, UINT16_MAX, 8879, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8880, UINT16_MAX, 8880, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8881, UINT16_MAX, 8881, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8882, UINT16_MAX, 8882, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8883, UINT16_MAX, 8883, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8884, UINT16_MAX, 8884, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8885, UINT16_MAX, 8885, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8886, UINT16_MAX, 8886, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8887, UINT16_MAX, 8887, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8888, UINT16_MAX, 8888, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8889, UINT16_MAX, 8889, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8890, UINT16_MAX, 8890, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8891, UINT16_MAX, 8891, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8892, UINT16_MAX, 8892, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8893, UINT16_MAX, 8893, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8894, UINT16_MAX, 8894, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3424, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3425, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3428, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3430, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3431, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3434, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3435, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3436, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3438, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3440, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3441, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3442, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3443, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3444, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3445, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3447, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3451, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3461, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3467, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3468, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3470, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3471, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3473, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3474, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3476, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3477, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3484, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3486, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3496, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3498, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3502, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3504, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3506, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3508, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3512, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3516, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3518, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3519, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3520, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3522, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3523, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3524, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3526, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3527, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3528, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3529, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3530, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3531, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3533, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3534, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3535, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3536, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3537, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3539, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3540, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3541, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3542, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3543, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3544, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3547, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3579, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3580, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3581, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3582, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3583, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3584, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3585, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3586, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3587, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3588, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3589, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3590, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3591, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3593, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3594, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3595, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3596, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3597, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3598, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3599, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3600, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3601, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3602, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3603, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3604, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3605, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3606, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3607, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3608, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3609, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3610, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3611, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3612, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3613, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3614, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3615, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3616, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3617, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3618, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3620, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3621, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3622, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3624, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3626, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3627, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3630, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3632, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3635, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3636, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3637, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3638, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3639, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ZS, 0, UTF8PROC_BIDI_CLASS_WS, UTF8PROC_DECOMP_TYPE_WIDE, 26, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 218, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 224, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 3640, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3641, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3642, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5239, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5174, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11835, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5177, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11837, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5180, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11839, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5183, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11841, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5186, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11843, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5189, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11845, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5192, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5195, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11849, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5198, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11851, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5201, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11853, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5204, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11855, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5207, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11857, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5210, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5213, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5216, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5219, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11867, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5223, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11869, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11871, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5227, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11873, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11875, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5231, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11877, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11879, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5235, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11881, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11883, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11885, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 8, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32820, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 8, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 32821, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 11887, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 11889, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5242, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, 11891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_VERTICAL, 11893, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5310, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5245, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11895, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5248, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5251, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11899, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5254, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11901, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5257, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11903, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5260, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11905, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5263, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11907, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5266, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11909, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5269, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11911, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5272, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11913, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5275, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11915, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5278, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11917, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5281, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11919, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5284, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11921, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5287, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11923, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5290, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11925, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11927, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5294, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11929, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11931, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5298, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11933, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11935, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5302, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11937, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11939, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5306, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11941, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11943, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5313, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5316, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5319, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5322, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11945, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11947, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11949, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11951, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 11953, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5325, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, 0, 11955, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_VERTICAL, 11957, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3768, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3770, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3771, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3772, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3774, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3783, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3788, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3789, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3791, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3792, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3794, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3796, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3797, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3798, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3799, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3800, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3801, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3802, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3803, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3804, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3805, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3806, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3807, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3808, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3810, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3811, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3812, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3813, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3815, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3816, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3818, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3819, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3821, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3822, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3823, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3824, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3825, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3826, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3827, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3828, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3829, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3830, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3831, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3832, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3833, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3834, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3835, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3836, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3837, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3838, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3839, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3840, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3841, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3842, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3843, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3844, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3845, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3849, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3850, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3851, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3853, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3855, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3856, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3857, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3858, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 3860, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3862, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3866, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3430, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3867, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3868, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3869, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3870, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3434, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20255, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20258, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20261, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20264, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20270, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20276, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20282, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20294, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28529, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28533, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28537, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28541, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 28545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 53125, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 44940, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20370, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20373, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20379, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20382, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20385, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20388, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20391, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20394, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20397, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20400, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20403, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20406, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20412, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20418, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20421, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20424, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20430, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20436, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20442, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20445, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20451, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4094, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4095, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4096, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 20481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12292, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12294, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12296, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12298, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12300, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12302, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12304, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12306, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12308, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12310, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12312, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12314, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12316, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12318, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12320, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3770, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3783, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3789, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3792, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3794, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3796, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12322, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12324, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12326, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12328, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12330, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12332, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12334, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12336, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12338, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12340, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12342, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12344, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12346, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 12348, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 36926, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 28739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12359, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3862, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4169, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4170, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4171, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4172, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4173, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4174, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4175, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4176, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4177, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4178, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4180, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4181, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4182, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4183, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4184, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4185, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4186, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4187, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4188, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4189, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4190, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 3865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4191, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4192, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4193, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4194, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4195, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4196, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4197, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4198, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4199, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4200, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12393, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12395, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12397, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12399, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12401, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12403, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12405, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12407, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12411, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12413, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12417, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12419, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_CIRCLE, 12421, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12425, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12431, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12435, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20636, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 20639, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 12450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 20644, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 12455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 20649, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4268, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4269, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4270, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4272, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4274, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4276, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4278, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4280, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4281, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4282, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4284, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4286, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4289, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4290, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4292, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4293, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4294, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4296, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4297, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4298, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4300, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4301, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4302, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4304, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4306, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4308, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4309, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4310, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4311, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4312, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4313, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 4314, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28895, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28899, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20711, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28906, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20718, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20721, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37108, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28921, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20733, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20736, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28934, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28938, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20750, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20753, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28953, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28957, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37155, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 45352, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37166, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37174, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 28992, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20804, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20807, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20810, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29005, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37201, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29014, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20826, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20829, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20832, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12643, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12645, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12647, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12649, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20843, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37233, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29049, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37245, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20866, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12677, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12679, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37257, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29070, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37266, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20887, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37274, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12703, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20900, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20903, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20906, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20909, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20916, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12727, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20921, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20924, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20927, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29122, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20934, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20937, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20940, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37327, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37338, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29153, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29157, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20969, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20972, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20975, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29170, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20984, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12799, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37377, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 20998, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12811, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12813, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12815, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12819, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12821, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12823, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12825, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 12827, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21021, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21024, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21027, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21030, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21033, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21036, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21039, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21042, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21045, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21048, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21051, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21054, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21057, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21060, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21063, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21066, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12877, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12879, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21073, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12884, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12886, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 12888, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 21082, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 21085, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 12896, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12898, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12900, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12902, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12904, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29290, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12910, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12912, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12914, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12916, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12918, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12920, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12922, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12924, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21118, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29313, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12933, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12935, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12937, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12939, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12941, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12943, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12945, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21139, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21145, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12959, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12961, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12963, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12965, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12967, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12969, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12971, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12973, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12975, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12977, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21171, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21174, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12985, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21182, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21185, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 12996, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21190, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21193, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29388, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13008, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21202, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21205, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21208, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21211, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 37598, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 45795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13033, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13035, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13037, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13039, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13041, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13043, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13045, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13047, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13049, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13051, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13053, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13055, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13057, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13059, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13061, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13063, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13065, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13067, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13073, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13075, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13077, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13086, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13088, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13090, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13092, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13094, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13096, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13098, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13100, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13102, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21298, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13109, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13111, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21308, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13119, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 29505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 21317, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13130, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13132, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 13134, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 21328, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 21331, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13150, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13152, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13154, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13156, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13158, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21352, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21355, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21358, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21361, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21364, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21367, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21370, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21373, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21379, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21382, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21385, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21388, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21391, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21394, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21397, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21400, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21403, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21406, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21412, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 21415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SQUARE, 21418, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5037, UINT16_MAX, 5037, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8895, UINT16_MAX, 8895, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5038, UINT16_MAX, 5038, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8896, UINT16_MAX, 8896, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5039, UINT16_MAX, 5039, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8897, UINT16_MAX, 8897, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5040, UINT16_MAX, 5040, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8898, UINT16_MAX, 8898, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5041, UINT16_MAX, 5041, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8899, UINT16_MAX, 8899, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1445, UINT16_MAX, 1445, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8481, UINT16_MAX, 8481, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5042, UINT16_MAX, 5042, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8900, UINT16_MAX, 8900, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5043, UINT16_MAX, 5043, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8901, UINT16_MAX, 8901, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5044, UINT16_MAX, 5044, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8902, UINT16_MAX, 8902, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5045, UINT16_MAX, 5045, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8903, UINT16_MAX, 8903, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5046, UINT16_MAX, 5046, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8904, UINT16_MAX, 8904, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5047, UINT16_MAX, 5047, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8905, UINT16_MAX, 8905, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5048, UINT16_MAX, 5048, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8906, UINT16_MAX, 8906, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5049, UINT16_MAX, 5049, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8907, UINT16_MAX, 8907, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5050, UINT16_MAX, 5050, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8908, UINT16_MAX, 8908, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5051, UINT16_MAX, 5051, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8909, UINT16_MAX, 8909, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5052, UINT16_MAX, 5052, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8910, UINT16_MAX, 8910, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5053, UINT16_MAX, 5053, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8911, UINT16_MAX, 8911, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5054, UINT16_MAX, 5054, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8912, UINT16_MAX, 8912, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5055, UINT16_MAX, 5055, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8913, UINT16_MAX, 8913, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5056, UINT16_MAX, 5056, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8914, UINT16_MAX, 8914, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5057, UINT16_MAX, 5057, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8915, UINT16_MAX, 8915, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5058, UINT16_MAX, 5058, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8916, UINT16_MAX, 8916, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5059, UINT16_MAX, 5059, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8917, UINT16_MAX, 8917, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5060, UINT16_MAX, 5060, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8918, UINT16_MAX, 8918, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5061, UINT16_MAX, 5061, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8919, UINT16_MAX, 8919, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5062, UINT16_MAX, 5062, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8920, UINT16_MAX, 8920, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5063, UINT16_MAX, 5063, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8921, UINT16_MAX, 8921, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5064, UINT16_MAX, 5064, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8922, UINT16_MAX, 8922, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5065, UINT16_MAX, 5065, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8923, UINT16_MAX, 8923, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5066, UINT16_MAX, 5066, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8924, UINT16_MAX, 8924, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5067, UINT16_MAX, 5067, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8925, UINT16_MAX, 8925, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5068, UINT16_MAX, 5068, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8926, UINT16_MAX, 8926, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5069, UINT16_MAX, 5069, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8927, UINT16_MAX, 8927, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5070, UINT16_MAX, 5070, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8928, UINT16_MAX, 8928, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5071, UINT16_MAX, 5071, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8929, UINT16_MAX, 8929, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5072, UINT16_MAX, 5072, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8930, UINT16_MAX, 8930, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 981, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 983, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5073, UINT16_MAX, 5073, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8931, UINT16_MAX, 8931, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5074, UINT16_MAX, 5074, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8932, UINT16_MAX, 8932, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5075, UINT16_MAX, 5075, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8933, UINT16_MAX, 8933, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5076, UINT16_MAX, 5076, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8934, UINT16_MAX, 8934, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5077, UINT16_MAX, 5077, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8935, UINT16_MAX, 8935, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5078, UINT16_MAX, 5078, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8936, UINT16_MAX, 8936, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5079, UINT16_MAX, 5079, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8937, UINT16_MAX, 8937, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5080, UINT16_MAX, 5080, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8938, UINT16_MAX, 8938, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5081, UINT16_MAX, 5081, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8939, UINT16_MAX, 8939, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5082, UINT16_MAX, 5082, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8940, UINT16_MAX, 8940, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5083, UINT16_MAX, 5083, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8941, UINT16_MAX, 8941, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5084, UINT16_MAX, 5084, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8942, UINT16_MAX, 8942, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5085, UINT16_MAX, 5085, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8943, UINT16_MAX, 8943, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5086, UINT16_MAX, 5086, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8944, UINT16_MAX, 8944, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5087, UINT16_MAX, 5087, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8945, UINT16_MAX, 8945, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5088, UINT16_MAX, 5088, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8946, UINT16_MAX, 8946, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5089, UINT16_MAX, 5089, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8947, UINT16_MAX, 8947, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5090, UINT16_MAX, 5090, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8948, UINT16_MAX, 8948, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5091, UINT16_MAX, 5091, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8949, UINT16_MAX, 8949, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5092, UINT16_MAX, 5092, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8950, UINT16_MAX, 8950, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5093, UINT16_MAX, 5093, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8951, UINT16_MAX, 8951, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5094, UINT16_MAX, 5094, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8952, UINT16_MAX, 8952, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5095, UINT16_MAX, 5095, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8953, UINT16_MAX, 8953, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5096, UINT16_MAX, 5096, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8954, UINT16_MAX, 8954, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5097, UINT16_MAX, 5097, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8955, UINT16_MAX, 8955, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5098, UINT16_MAX, 5098, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8956, UINT16_MAX, 8956, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5099, UINT16_MAX, 5099, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8957, UINT16_MAX, 8957, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5100, UINT16_MAX, 5100, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8958, UINT16_MAX, 8958, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5101, UINT16_MAX, 5101, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8959, UINT16_MAX, 8959, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5102, UINT16_MAX, 5102, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8960, UINT16_MAX, 8960, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5103, UINT16_MAX, 5103, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8961, UINT16_MAX, 8961, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5104, UINT16_MAX, 5104, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8962, UINT16_MAX, 8962, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5105, UINT16_MAX, 5105, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8963, UINT16_MAX, 8963, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5106, UINT16_MAX, 5106, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8964, UINT16_MAX, 8964, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5107, UINT16_MAX, 5107, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8965, UINT16_MAX, 8965, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5108, UINT16_MAX, 5108, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8966, UINT16_MAX, 8966, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5109, UINT16_MAX, 5109, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8967, UINT16_MAX, 8967, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5110, UINT16_MAX, 5110, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8968, UINT16_MAX, 8968, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5110, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5111, UINT16_MAX, 5111, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8969, UINT16_MAX, 8969, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5112, UINT16_MAX, 5112, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8970, UINT16_MAX, 8970, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5113, UINT16_MAX, 5113, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5114, UINT16_MAX, 5114, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8971, UINT16_MAX, 8971, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5115, UINT16_MAX, 5115, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8972, UINT16_MAX, 8972, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5116, UINT16_MAX, 5116, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8973, UINT16_MAX, 8973, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5117, UINT16_MAX, 5117, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8974, UINT16_MAX, 8974, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5118, UINT16_MAX, 5118, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8975, UINT16_MAX, 8975, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5119, UINT16_MAX, 5119, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8976, UINT16_MAX, 8976, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1479, UINT16_MAX, 1479, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5120, UINT16_MAX, 5120, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8977, UINT16_MAX, 8977, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5121, UINT16_MAX, 5121, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8978, UINT16_MAX, 8978, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5122, UINT16_MAX, 5122, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8979, UINT16_MAX, 8979, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5123, UINT16_MAX, 5123, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8980, UINT16_MAX, 8980, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5124, UINT16_MAX, 5124, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8981, UINT16_MAX, 8981, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5125, UINT16_MAX, 5125, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8982, UINT16_MAX, 8982, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5126, UINT16_MAX, 5126, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8983, UINT16_MAX, 8983, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5127, UINT16_MAX, 5127, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8984, UINT16_MAX, 8984, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5128, UINT16_MAX, 5128, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8985, UINT16_MAX, 8985, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5129, UINT16_MAX, 5129, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8986, UINT16_MAX, 8986, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5130, UINT16_MAX, 5130, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8987, UINT16_MAX, 8987, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5131, UINT16_MAX, 5131, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8988, UINT16_MAX, 8988, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 785, UINT16_MAX, 785, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1470, UINT16_MAX, 1470, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1478, UINT16_MAX, 1478, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5132, UINT16_MAX, 5132, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1480, UINT16_MAX, 1480, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5133, UINT16_MAX, 5133, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5134, UINT16_MAX, 5134, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 1482, UINT16_MAX, 1482, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5135, UINT16_MAX, 5135, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5136, UINT16_MAX, 5136, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8989, UINT16_MAX, 8989, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5137, UINT16_MAX, 5137, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8990, UINT16_MAX, 8990, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5138, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 371, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ET, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 8991, UINT16_MAX, 8991, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5075, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5139, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 3360, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SUPER, 5140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5141, 5141, UINT16_MAX, 5141, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5142, 5142, UINT16_MAX, 5142, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5143, 5143, UINT16_MAX, 5143, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5144, 5144, UINT16_MAX, 5144, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5145, 5145, UINT16_MAX, 5145, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5146, 5146, UINT16_MAX, 5146, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5147, 5147, UINT16_MAX, 5147, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5148, 5148, UINT16_MAX, 5148, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5149, 5149, UINT16_MAX, 5149, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5150, 5150, UINT16_MAX, 5150, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5151, 5151, UINT16_MAX, 5151, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5152, 5152, UINT16_MAX, 5152, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5153, 5153, UINT16_MAX, 5153, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5154, 5154, UINT16_MAX, 5154, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5155, 5155, UINT16_MAX, 5155, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5156, 5156, UINT16_MAX, 5156, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5157, 5157, UINT16_MAX, 5157, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5158, 5158, UINT16_MAX, 5158, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5159, 5159, UINT16_MAX, 5159, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5160, 5160, UINT16_MAX, 5160, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5161, 5161, UINT16_MAX, 5161, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5162, 5162, UINT16_MAX, 5162, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5163, 5163, UINT16_MAX, 5163, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5164, 5164, UINT16_MAX, 5164, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5165, 5165, UINT16_MAX, 5165, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5166, 5166, UINT16_MAX, 5166, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5167, 5167, UINT16_MAX, 5167, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5168, 5168, UINT16_MAX, 5168, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5169, 5169, UINT16_MAX, 5169, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5170, 5170, UINT16_MAX, 5170, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5171, 5171, UINT16_MAX, 5171, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5172, 5172, UINT16_MAX, 5172, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5173, 5173, UINT16_MAX, 5173, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5174, 5174, UINT16_MAX, 5174, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5175, 5175, UINT16_MAX, 5175, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5176, 5176, UINT16_MAX, 5176, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5177, 5177, UINT16_MAX, 5177, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5178, 5178, UINT16_MAX, 5178, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5179, 5179, UINT16_MAX, 5179, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5180, 5180, UINT16_MAX, 5180, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5181, 5181, UINT16_MAX, 5181, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5182, 5182, UINT16_MAX, 5182, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5183, 5183, UINT16_MAX, 5183, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5184, 5184, UINT16_MAX, 5184, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5185, 5185, UINT16_MAX, 5185, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5186, 5186, UINT16_MAX, 5186, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5187, 5187, UINT16_MAX, 5187, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5188, 5188, UINT16_MAX, 5188, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5189, 5189, UINT16_MAX, 5189, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5190, 5190, UINT16_MAX, 5190, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5191, 5191, UINT16_MAX, 5191, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5192, 5192, UINT16_MAX, 5192, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5193, 5193, UINT16_MAX, 5193, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5194, 5194, UINT16_MAX, 5194, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5195, 5195, UINT16_MAX, 5195, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5196, 5196, UINT16_MAX, 5196, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5197, 5197, UINT16_MAX, 5197, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5198, 5198, UINT16_MAX, 5198, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5199, 5199, UINT16_MAX, 5199, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5200, 5200, UINT16_MAX, 5200, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5201, 5201, UINT16_MAX, 5201, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5202, 5202, UINT16_MAX, 5202, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5203, 5203, UINT16_MAX, 5203, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5204, 5204, UINT16_MAX, 5204, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5205, 5205, UINT16_MAX, 5205, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5206, 5206, UINT16_MAX, 5206, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5207, 5207, UINT16_MAX, 5207, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5208, 5208, UINT16_MAX, 5208, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5209, 5209, UINT16_MAX, 5209, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5210, 5210, UINT16_MAX, 5210, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5211, 5211, UINT16_MAX, 5211, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5212, 5212, UINT16_MAX, 5212, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5213, 5213, UINT16_MAX, 5213, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5214, 5214, UINT16_MAX, 5214, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5215, 5215, UINT16_MAX, 5215, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5216, 5216, UINT16_MAX, 5216, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5217, 5217, UINT16_MAX, 5217, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5218, 5218, UINT16_MAX, 5218, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5219, 5219, UINT16_MAX, 5219, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 5220, 5220, UINT16_MAX, 5220, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_LV}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_LVT}, {UTF8PROC_CATEGORY_CS, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_CO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5221, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5222, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3584, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5223, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5224, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5225, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3638, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5227, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5228, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5229, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5230, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5231, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5232, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5233, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5234, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5235, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5236, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5237, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5238, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5239, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5240, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5241, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5242, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5243, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5244, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5245, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5246, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5247, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5248, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5249, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5251, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5252, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5253, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5254, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5255, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5256, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5257, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5258, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5259, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5260, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5261, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5262, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5263, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5264, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5265, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5266, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5268, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5269, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5270, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5272, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5274, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5276, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5278, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5280, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5281, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5282, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5284, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5286, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5289, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5290, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5292, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5293, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5294, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5296, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5297, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5298, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5300, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5301, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5302, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5304, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5306, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5308, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5309, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5310, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5311, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5312, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5313, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5314, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5315, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5316, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5317, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5318, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5319, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5320, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5321, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5322, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5323, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5324, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5325, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5326, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3586, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5327, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5328, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5329, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5330, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5331, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5332, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5333, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5334, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5335, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5336, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5337, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5338, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5339, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5340, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5341, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5342, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5343, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5344, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5345, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5346, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5347, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5348, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5349, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3444, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5350, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5351, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5352, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5353, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5354, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5355, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5356, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5357, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5358, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5359, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5360, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5361, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5362, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5363, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5364, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5365, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5366, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5367, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5368, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5369, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5370, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5371, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5372, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5373, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5374, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5375, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5377, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5378, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5379, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5380, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5381, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5382, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5383, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5384, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5385, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5386, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5387, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5388, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5389, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5390, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5391, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5392, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5393, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5394, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5395, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5396, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5397, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5398, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5399, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5400, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5401, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5402, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5403, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3637, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5404, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5405, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5406, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5407, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5408, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5410, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5411, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5412, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5413, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5414, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 4170, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5416, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5417, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5418, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5419, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5420, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5421, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5422, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5424, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5425, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5428, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5430, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5431, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5434, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5435, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5436, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3591, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5438, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5440, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5441, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5442, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5443, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5444, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5445, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5447, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3542, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5451, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5461, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5467, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5468, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5470, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5471, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5473, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5474, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5476, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5477, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5484, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5486, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5496, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5498, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5502, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3470, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5504, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5506, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5508, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5512, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5516, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5518, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 4175, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5519, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5520, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5522, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 4179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5523, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5524, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5526, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5527, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5528, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5529, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5530, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5531, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5533, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5534, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5535, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5536, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5537, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5539, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5540, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5541, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5542, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5543, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5544, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5579, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5580, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5581, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5582, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5583, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5584, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5585, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5586, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5587, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5588, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5589, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5590, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5591, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5593, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5594, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5595, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5596, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5597, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5598, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5599, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5600, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5601, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5602, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5603, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5604, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5605, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5606, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5607, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5608, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5609, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5610, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5611, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5612, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5613, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5614, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5615, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5616, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5617, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5621, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5624, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5626, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5630, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5632, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 5633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13826, 13826, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13828, 13828, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13830, 13830, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 22024, 22024, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 22027, 22027, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13838, 13840, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13840, 13840, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13842, 13842, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13844, 13844, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13846, 13846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13848, 13848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 13850, 13850, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 26, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5664, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 2822, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 2825, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5665, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5666, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5667, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5668, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5669, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_FONT, 5670, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_FONT, 2782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13867, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13869, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13871, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13873, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13875, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13877, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13879, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13881, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13883, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13885, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13887, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13889, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13893, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13895, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13899, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13901, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13903, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13905, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13907, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13909, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13911, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13913, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13915, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13917, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13919, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13921, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13923, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, 0, 13925, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_R, UTF8PROC_DECOMP_TYPE_COMPAT, 13927, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5737, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5737, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5738, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5738, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5738, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5738, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5740, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5740, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5740, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5740, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5743, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5743, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5743, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5743, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5744, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5744, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5744, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5744, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5746, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5746, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5746, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5746, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5747, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5747, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5747, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5747, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5748, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5748, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5748, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5748, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5750, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5750, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5751, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5751, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5752, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5752, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5753, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5753, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5754, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5754, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5755, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5755, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5756, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5756, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5756, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5756, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5759, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5759, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5759, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5759, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5761, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5761, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5761, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5761, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5762, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5762, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5764, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5764, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5764, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5764, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5765, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5765, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_AL, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5768, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5768, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5770, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5770, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5771, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5772, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5772, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5774, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5774, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13969, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13969, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13971, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13971, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13973, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13973, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13975, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13975, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13977, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13977, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13979, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13979, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13981, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13981, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13981, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13983, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13983, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13983, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 5793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 5793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13986, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13988, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13990, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13992, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13994, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13996, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 13998, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14000, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14002, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14004, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14006, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14008, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14010, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14012, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14014, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14016, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14018, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14020, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14022, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14024, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14026, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14028, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14030, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14032, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14034, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14036, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14038, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14040, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14042, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14044, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14046, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14048, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14050, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14052, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14054, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14056, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14058, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14060, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14062, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14064, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14066, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14068, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14070, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14072, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14074, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14076, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14078, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14080, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14082, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14084, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14086, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14088, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14090, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14092, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14094, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14096, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14098, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14100, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14102, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14106, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14108, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14110, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14112, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14114, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14116, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14118, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14120, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14122, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14124, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14126, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14130, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14132, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14134, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14136, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14138, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14150, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14152, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14154, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14156, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14158, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14160, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14162, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14164, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14166, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14168, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14170, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22364, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22367, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22370, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22373, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22379, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14190, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14192, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13990, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14194, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 13992, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14196, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14198, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14000, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14200, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14002, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14004, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14202, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14204, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14012, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14206, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14014, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14016, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14208, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14210, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14020, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14212, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14022, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14024, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14082, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14084, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14090, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14092, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14094, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14102, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14106, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14108, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14116, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14118, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14120, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14214, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14216, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14218, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14220, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14170, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14222, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14224, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14160, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14226, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14162, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14164, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13986, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13988, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14228, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13990, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14230, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13994, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13996, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 13998, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14000, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14232, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14006, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14008, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14010, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14012, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14234, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14020, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14026, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14028, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14030, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14032, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14034, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14038, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14040, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14042, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14044, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14046, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14048, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14236, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14050, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14052, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14054, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14056, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14058, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14060, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14064, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14066, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14068, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14070, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14072, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14074, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14076, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14078, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14080, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14086, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14088, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14096, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14098, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14100, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14102, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14110, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14112, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14114, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14116, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14238, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14122, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14124, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14126, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14134, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14136, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14138, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14240, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14242, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14154, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14156, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14158, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14160, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14244, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 13990, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14230, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14000, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14232, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14012, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14234, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14020, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14246, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14046, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14248, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14252, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14102, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14104, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14116, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14240, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14160, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14244, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 22446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 22449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 22452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14263, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14265, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14269, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14281, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14289, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14293, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14297, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14301, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14309, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14311, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14313, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14315, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14263, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14265, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14269, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14281, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14289, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14293, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14297, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14301, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14309, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14311, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14313, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14315, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14250, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14248, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14252, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 14062, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14040, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14042, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14044, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14062, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14064, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14317, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14317, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22520, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22523, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22526, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22529, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22535, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22541, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22544, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22547, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22580, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22580, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22583, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22583, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22586, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22589, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22595, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22595, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22598, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22601, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22604, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22607, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22610, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22610, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22613, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22616, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22622, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22637, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22640, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22643, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22646, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22649, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22652, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22655, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22658, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22661, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22664, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22667, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22670, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22673, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22673, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22676, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22679, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22682, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22685, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22685, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22688, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22691, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22694, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22697, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22700, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22703, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22706, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22709, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22712, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22715, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22718, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22721, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22724, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22727, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22730, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22733, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22736, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22748, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22751, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22613, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22754, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22772, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 22562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 22790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22796, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 30991, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 30995, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 30999, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 31003, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 31007, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 31011, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 31015, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 22827, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 63790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 63809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 31050, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6484, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6486, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PC, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 2785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 2786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6495, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6496, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6498, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 3046, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 3047, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6502, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_VERTICAL, 6504, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 6505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PC, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_COMPAT, 6490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_SMALL, 6478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_SMALL, 2745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_SMALL, 6481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 2785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 2786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6493, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_SMALL, 6506, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6508, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SMALL, 2782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_SMALL, 6509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 2784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6512, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_SMALL, 6513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_SMALL, 6514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SMALL, 6515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14708, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14710, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14712, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14714, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14716, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14718, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14720, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14722, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14724, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14726, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14728, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14730, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14732, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 14734, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6544, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6547, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6547, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6549, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 5776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 5776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_INITIAL, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_MEDIAL, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14771, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14771, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_ISOLATED, 14777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FINAL, 14777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6587, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6506, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6588, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 2785, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 2786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6508, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_WIDE, 2782, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_WIDE, 6478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PD, 0, UTF8PROC_BIDI_CLASS_ES, UTF8PROC_DECOMP_TYPE_WIDE, 6509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_WIDE, 2745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_WIDE, 6589, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_WIDE, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_CS, UTF8PROC_DECOMP_TYPE_WIDE, 6481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 814, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 2784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6511, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1446, 6590, UINT16_MAX, 6590, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1448, 6591, UINT16_MAX, 6591, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2795, 6592, UINT16_MAX, 6592, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1449, 6593, UINT16_MAX, 6593, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1450, 6594, UINT16_MAX, 6594, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2820, 6595, UINT16_MAX, 6595, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1452, 6596, UINT16_MAX, 6596, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1453, 6597, UINT16_MAX, 6597, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1454, 6598, UINT16_MAX, 6598, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1455, 6599, UINT16_MAX, 6599, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1456, 6600, UINT16_MAX, 6600, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1457, 6601, UINT16_MAX, 6601, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1458, 6602, UINT16_MAX, 6602, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1459, 6603, UINT16_MAX, 6603, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1460, 6604, UINT16_MAX, 6604, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1462, 6605, UINT16_MAX, 6605, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2809, 6606, UINT16_MAX, 6606, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1463, 6607, UINT16_MAX, 6607, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 3288, 6608, UINT16_MAX, 6608, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1464, 6609, UINT16_MAX, 6609, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1465, 6610, UINT16_MAX, 6610, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2891, 6611, UINT16_MAX, 6611, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1466, 6612, UINT16_MAX, 6612, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2908, 6613, UINT16_MAX, 6613, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 3295, 6614, UINT16_MAX, 6614, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2817, 6615, UINT16_MAX, 6615, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6512, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6504, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6616, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PC, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 2704, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 0, UINT16_MAX, 8992, UINT16_MAX, 8992, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 1, UINT16_MAX, 8993, UINT16_MAX, 8993, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 2, UINT16_MAX, 8994, UINT16_MAX, 8994, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 3, UINT16_MAX, 8995, UINT16_MAX, 8995, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 4, UINT16_MAX, 8996, UINT16_MAX, 8996, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 5, UINT16_MAX, 8997, UINT16_MAX, 8997, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 6, UINT16_MAX, 8998, UINT16_MAX, 8998, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 7, UINT16_MAX, 8999, UINT16_MAX, 8999, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 8, UINT16_MAX, 9000, UINT16_MAX, 9000, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 9, UINT16_MAX, 9001, UINT16_MAX, 9001, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 10, UINT16_MAX, 9002, UINT16_MAX, 9002, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 11, UINT16_MAX, 9003, UINT16_MAX, 9003, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 12, UINT16_MAX, 9004, UINT16_MAX, 9004, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 13, UINT16_MAX, 9005, UINT16_MAX, 9005, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 14, UINT16_MAX, 9006, UINT16_MAX, 9006, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 15, UINT16_MAX, 9007, UINT16_MAX, 9007, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 16, UINT16_MAX, 9008, UINT16_MAX, 9008, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 17, UINT16_MAX, 9009, UINT16_MAX, 9009, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 18, UINT16_MAX, 9010, UINT16_MAX, 9010, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 19, UINT16_MAX, 9011, UINT16_MAX, 9011, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 20, UINT16_MAX, 9012, UINT16_MAX, 9012, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 21, UINT16_MAX, 9013, UINT16_MAX, 9013, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 22, UINT16_MAX, 9014, UINT16_MAX, 9014, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 23, UINT16_MAX, 9015, UINT16_MAX, 9015, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 24, UINT16_MAX, 9016, UINT16_MAX, 9016, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_WIDE, 25, UINT16_MAX, 9017, UINT16_MAX, 9017, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6617, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6618, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6620, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PS, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PE, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_PO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6621, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4314, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6622, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6624, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6626, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6627, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6630, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4268, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4269, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4270, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4272, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4273, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4274, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4276, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4277, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4278, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4280, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4281, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4282, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4284, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4285, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4286, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4289, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4290, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4292, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4293, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4294, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4296, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4297, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4298, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4300, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4301, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4302, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4304, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4305, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4306, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4308, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4309, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4310, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 4311, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6632, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6635, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, true, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6636, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6637, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6638, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6639, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6640, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6641, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6642, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6643, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6644, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6645, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6646, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6647, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6648, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6649, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6650, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6651, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6652, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6653, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6654, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6655, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6656, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6657, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6658, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6659, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6660, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6661, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6662, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6663, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6664, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6665, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6666, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6667, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6668, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6669, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6670, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6671, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6672, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6673, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6674, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6675, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6676, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6677, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6678, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6679, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6680, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6681, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6682, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6683, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6684, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6685, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_NARROW, 6686, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6687, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6688, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6689, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6690, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_WIDE, 6691, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6692, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SC, 0, UTF8PROC_BIDI_CLASS_ET, UTF8PROC_DECOMP_TYPE_WIDE, 6693, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6694, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6695, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6696, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6697, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6698, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6699, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_NARROW, 6700, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_CONTROL}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NL, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6701, UINT16_MAX, 6701, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6703, UINT16_MAX, 6703, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6705, UINT16_MAX, 6705, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6707, UINT16_MAX, 6707, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6709, UINT16_MAX, 6709, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6711, UINT16_MAX, 6711, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6713, UINT16_MAX, 6713, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6715, UINT16_MAX, 6715, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6717, UINT16_MAX, 6717, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6719, UINT16_MAX, 6719, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6721, UINT16_MAX, 6721, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6723, UINT16_MAX, 6723, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6725, UINT16_MAX, 6725, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6727, UINT16_MAX, 6727, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6729, UINT16_MAX, 6729, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6731, UINT16_MAX, 6731, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6733, UINT16_MAX, 6733, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6735, UINT16_MAX, 6735, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6737, UINT16_MAX, 6737, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6739, UINT16_MAX, 6739, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6741, UINT16_MAX, 6741, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6743, UINT16_MAX, 6743, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6745, UINT16_MAX, 6745, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6747, UINT16_MAX, 6747, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6749, UINT16_MAX, 6749, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6751, UINT16_MAX, 6751, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6753, UINT16_MAX, 6753, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6755, UINT16_MAX, 6755, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6757, UINT16_MAX, 6757, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6759, UINT16_MAX, 6759, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6761, UINT16_MAX, 6761, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6763, UINT16_MAX, 6763, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6765, UINT16_MAX, 6765, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6767, UINT16_MAX, 6767, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6769, UINT16_MAX, 6769, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6771, UINT16_MAX, 6771, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6773, UINT16_MAX, 6773, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6775, UINT16_MAX, 6775, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6777, UINT16_MAX, 6777, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6779, UINT16_MAX, 6779, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9018, UINT16_MAX, 9018, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9020, UINT16_MAX, 9020, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9022, UINT16_MAX, 9022, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9024, UINT16_MAX, 9024, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9026, UINT16_MAX, 9026, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9028, UINT16_MAX, 9028, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9030, UINT16_MAX, 9030, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9032, UINT16_MAX, 9032, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9034, UINT16_MAX, 9034, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9036, UINT16_MAX, 9036, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9038, UINT16_MAX, 9038, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9040, UINT16_MAX, 9040, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9042, UINT16_MAX, 9042, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9044, UINT16_MAX, 9044, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9046, UINT16_MAX, 9046, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9048, UINT16_MAX, 9048, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9050, UINT16_MAX, 9050, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9052, UINT16_MAX, 9052, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9054, UINT16_MAX, 9054, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9056, UINT16_MAX, 9056, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9058, UINT16_MAX, 9058, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9060, UINT16_MAX, 9060, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9062, UINT16_MAX, 9062, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9064, UINT16_MAX, 9064, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9066, UINT16_MAX, 9066, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9068, UINT16_MAX, 9068, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9070, UINT16_MAX, 9070, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9072, UINT16_MAX, 9072, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9074, UINT16_MAX, 9074, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9076, UINT16_MAX, 9076, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9078, UINT16_MAX, 9078, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9080, UINT16_MAX, 9080, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9082, UINT16_MAX, 9082, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9084, UINT16_MAX, 9084, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9086, UINT16_MAX, 9086, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9088, UINT16_MAX, 9088, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9090, UINT16_MAX, 9090, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9092, UINT16_MAX, 9092, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9094, UINT16_MAX, 9094, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9096, UINT16_MAX, 9096, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6781, UINT16_MAX, 6781, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6783, UINT16_MAX, 6783, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6785, UINT16_MAX, 6785, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6787, UINT16_MAX, 6787, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6789, UINT16_MAX, 6789, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6791, UINT16_MAX, 6791, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6793, UINT16_MAX, 6793, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6795, UINT16_MAX, 6795, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6797, UINT16_MAX, 6797, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6799, UINT16_MAX, 6799, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6801, UINT16_MAX, 6801, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6803, UINT16_MAX, 6803, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6805, UINT16_MAX, 6805, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6807, UINT16_MAX, 6807, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6809, UINT16_MAX, 6809, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6811, UINT16_MAX, 6811, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6813, UINT16_MAX, 6813, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6815, UINT16_MAX, 6815, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6817, UINT16_MAX, 6817, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6819, UINT16_MAX, 6819, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6821, UINT16_MAX, 6821, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6823, UINT16_MAX, 6823, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6825, UINT16_MAX, 6825, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6827, UINT16_MAX, 6827, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6829, UINT16_MAX, 6829, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6831, UINT16_MAX, 6831, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6833, UINT16_MAX, 6833, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6835, UINT16_MAX, 6835, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6837, UINT16_MAX, 6837, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6839, UINT16_MAX, 6839, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6841, UINT16_MAX, 6841, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6843, UINT16_MAX, 6843, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6845, UINT16_MAX, 6845, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6847, UINT16_MAX, 6847, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6849, UINT16_MAX, 6849, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 6851, UINT16_MAX, 6851, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9098, UINT16_MAX, 9098, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9100, UINT16_MAX, 9100, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9102, UINT16_MAX, 9102, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9104, UINT16_MAX, 9104, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9106, UINT16_MAX, 9106, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9108, UINT16_MAX, 9108, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9110, UINT16_MAX, 9110, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9112, UINT16_MAX, 9112, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9114, UINT16_MAX, 9114, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9116, UINT16_MAX, 9116, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9118, UINT16_MAX, 9118, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9120, UINT16_MAX, 9120, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9122, UINT16_MAX, 9122, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9124, UINT16_MAX, 9124, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9126, UINT16_MAX, 9126, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9128, UINT16_MAX, 9128, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9130, UINT16_MAX, 9130, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9132, UINT16_MAX, 9132, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9134, UINT16_MAX, 9134, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9136, UINT16_MAX, 9136, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9138, UINT16_MAX, 9138, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9140, UINT16_MAX, 9140, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9142, UINT16_MAX, 9142, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9144, UINT16_MAX, 9144, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9146, UINT16_MAX, 9146, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9148, UINT16_MAX, 9148, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9150, UINT16_MAX, 9150, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9152, UINT16_MAX, 9152, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9154, UINT16_MAX, 9154, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9156, UINT16_MAX, 9156, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9158, UINT16_MAX, 9158, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9160, UINT16_MAX, 9160, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9162, UINT16_MAX, 9162, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9164, UINT16_MAX, 9164, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9166, UINT16_MAX, 9166, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9168, UINT16_MAX, 9168, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6853, UINT16_MAX, 6853, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6855, UINT16_MAX, 6855, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6857, UINT16_MAX, 6857, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6859, UINT16_MAX, 6859, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6861, UINT16_MAX, 6861, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6863, UINT16_MAX, 6863, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6865, UINT16_MAX, 6865, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6867, UINT16_MAX, 6867, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6869, UINT16_MAX, 6869, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6871, UINT16_MAX, 6871, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6873, UINT16_MAX, 6873, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6875, UINT16_MAX, 6875, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6877, UINT16_MAX, 6877, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6879, UINT16_MAX, 6879, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6881, UINT16_MAX, 6881, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6883, UINT16_MAX, 6883, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6885, UINT16_MAX, 6885, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6887, UINT16_MAX, 6887, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6889, UINT16_MAX, 6889, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6891, UINT16_MAX, 6891, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6893, UINT16_MAX, 6893, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6895, UINT16_MAX, 6895, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6897, UINT16_MAX, 6897, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6899, UINT16_MAX, 6899, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6901, UINT16_MAX, 6901, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6903, UINT16_MAX, 6903, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6905, UINT16_MAX, 6905, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6907, UINT16_MAX, 6907, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6909, UINT16_MAX, 6909, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6911, UINT16_MAX, 6911, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6913, UINT16_MAX, 6913, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6915, UINT16_MAX, 6915, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6917, UINT16_MAX, 6917, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6919, UINT16_MAX, 6919, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6921, UINT16_MAX, 6921, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6923, UINT16_MAX, 6923, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6925, UINT16_MAX, 6925, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6927, UINT16_MAX, 6927, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6929, UINT16_MAX, 6929, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6931, UINT16_MAX, 6931, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6933, UINT16_MAX, 6933, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6935, UINT16_MAX, 6935, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6937, UINT16_MAX, 6937, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6939, UINT16_MAX, 6939, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6941, UINT16_MAX, 6941, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6943, UINT16_MAX, 6943, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6945, UINT16_MAX, 6945, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6947, UINT16_MAX, 6947, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6949, UINT16_MAX, 6949, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6951, UINT16_MAX, 6951, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 6953, UINT16_MAX, 6953, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9170, UINT16_MAX, 9170, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9172, UINT16_MAX, 9172, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9174, UINT16_MAX, 9174, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9176, UINT16_MAX, 9176, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9178, UINT16_MAX, 9178, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9180, UINT16_MAX, 9180, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9182, UINT16_MAX, 9182, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9184, UINT16_MAX, 9184, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9186, UINT16_MAX, 9186, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9188, UINT16_MAX, 9188, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9190, UINT16_MAX, 9190, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9192, UINT16_MAX, 9192, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9194, UINT16_MAX, 9194, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9196, UINT16_MAX, 9196, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9198, UINT16_MAX, 9198, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9200, UINT16_MAX, 9200, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9202, UINT16_MAX, 9202, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9204, UINT16_MAX, 9204, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9206, UINT16_MAX, 9206, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9208, UINT16_MAX, 9208, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9210, UINT16_MAX, 9210, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9212, UINT16_MAX, 9212, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9214, UINT16_MAX, 9214, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9216, UINT16_MAX, 9216, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9218, UINT16_MAX, 9218, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9220, UINT16_MAX, 9220, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9222, UINT16_MAX, 9222, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9224, UINT16_MAX, 9224, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9226, UINT16_MAX, 9226, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9228, UINT16_MAX, 9228, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9230, UINT16_MAX, 9230, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9232, UINT16_MAX, 9232, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9234, UINT16_MAX, 9234, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9236, UINT16_MAX, 9236, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9238, UINT16_MAX, 9238, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9240, UINT16_MAX, 9240, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9242, UINT16_MAX, 9242, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9244, UINT16_MAX, 9244, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9246, UINT16_MAX, 9246, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9248, UINT16_MAX, 9248, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9250, UINT16_MAX, 9250, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9252, UINT16_MAX, 9252, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9254, UINT16_MAX, 9254, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9256, UINT16_MAX, 9256, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9258, UINT16_MAX, 9258, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9260, UINT16_MAX, 9260, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9262, UINT16_MAX, 9262, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9264, UINT16_MAX, 9264, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9266, UINT16_MAX, 9266, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9268, UINT16_MAX, 9268, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9270, UINT16_MAX, 9270, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_AN, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5328, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15147, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5332, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15151, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5336, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15155, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MN, 7, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49206, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_CF, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, true, 0, 0, UTF8PROC_BOUNDCLASS_PREPEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49208, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 15159, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, 15163, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5340, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5344, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49210, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5348, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15167, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15171, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49212, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49216, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5354, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 0, UTF8PROC_BIDI_CLASS_NSM, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49214, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15175, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15179, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49218, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15183, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49220, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5362, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5366, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15187, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, 15191, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7003, UINT16_MAX, 7003, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7005, UINT16_MAX, 7005, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7007, UINT16_MAX, 7007, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7009, UINT16_MAX, 7009, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7011, UINT16_MAX, 7011, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7013, UINT16_MAX, 7013, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7015, UINT16_MAX, 7015, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7017, UINT16_MAX, 7017, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7019, UINT16_MAX, 7019, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7021, UINT16_MAX, 7021, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7023, UINT16_MAX, 7023, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7025, UINT16_MAX, 7025, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7027, UINT16_MAX, 7027, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7029, UINT16_MAX, 7029, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7031, UINT16_MAX, 7031, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7033, UINT16_MAX, 7033, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7035, UINT16_MAX, 7035, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7037, UINT16_MAX, 7037, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7039, UINT16_MAX, 7039, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7041, UINT16_MAX, 7041, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7043, UINT16_MAX, 7043, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7045, UINT16_MAX, 7045, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7047, UINT16_MAX, 7047, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7049, UINT16_MAX, 7049, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7051, UINT16_MAX, 7051, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7053, UINT16_MAX, 7053, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7055, UINT16_MAX, 7055, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7057, UINT16_MAX, 7057, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7059, UINT16_MAX, 7059, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7061, UINT16_MAX, 7061, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7063, UINT16_MAX, 7063, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, 7065, UINT16_MAX, 7065, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9272, UINT16_MAX, 9272, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9274, UINT16_MAX, 9274, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9276, UINT16_MAX, 9276, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9278, UINT16_MAX, 9278, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9280, UINT16_MAX, 9280, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9282, UINT16_MAX, 9282, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9284, UINT16_MAX, 9284, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9286, UINT16_MAX, 9286, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9288, UINT16_MAX, 9288, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9290, UINT16_MAX, 9290, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9292, UINT16_MAX, 9292, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9294, UINT16_MAX, 9294, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9296, UINT16_MAX, 9296, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9298, UINT16_MAX, 9298, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9300, UINT16_MAX, 9300, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9302, UINT16_MAX, 9302, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9304, UINT16_MAX, 9304, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9306, UINT16_MAX, 9306, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9308, UINT16_MAX, 9308, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9310, UINT16_MAX, 9310, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9312, UINT16_MAX, 9312, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9314, UINT16_MAX, 9314, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9316, UINT16_MAX, 9316, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9318, UINT16_MAX, 9318, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9320, UINT16_MAX, 9320, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9322, UINT16_MAX, 9322, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9324, UINT16_MAX, 9324, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9326, UINT16_MAX, 9326, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9328, UINT16_MAX, 9328, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9330, UINT16_MAX, 9330, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9332, UINT16_MAX, 9332, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, 9334, UINT16_MAX, 9334, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MN, 9, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5370, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5374, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15259, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15263, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5378, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15267, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15271, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15275, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15279, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15283, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49222, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 226, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_SPACINGMARK}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49224, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49226, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49228, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49230, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_MC, 216, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 49232, false, false, false, false, 0, 0, UTF8PROC_BOUNDCLASS_EXTEND}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5390, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5394, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15287, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5398, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15291, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, 5404, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15299, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15303, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, 15307, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, true, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2908, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 5, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 10, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 12, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 13, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 15, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 16, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 17, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 18, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 19, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 20, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 21, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 22, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 23, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 24, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 25, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2908, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 3295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 1, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 4, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 5, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 6, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 8, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 9, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 10, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 11, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 12, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 13, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 14, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 15, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 16, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 17, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 18, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 19, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 20, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 21, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 22, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 23, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 24, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 25, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7119, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7120, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7121, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7122, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7123, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7124, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7125, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7126, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 915, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7127, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7129, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7130, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7131, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7132, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7133, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2830, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7134, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7135, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 917, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7136, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7137, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7138, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7139, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2818, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 845, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 849, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 850, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 851, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 807, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 853, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 35, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 855, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 856, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 857, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 858, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 914, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 860, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 862, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 866, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FONT, 7141, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7143, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7145, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7147, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7121, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7122, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2829, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7123, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7124, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7125, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7126, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 915, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7127, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7128, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7129, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7130, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7131, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7132, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7133, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7134, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7135, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 917, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7136, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7137, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7138, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7139, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 2818, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7140, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 845, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 849, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 850, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 851, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 852, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 807, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 853, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 35, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 855, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 856, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 857, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 914, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 860, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 862, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 866, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SM, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_FONT, 7141, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, true, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7142, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7144, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7145, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7146, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7147, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7143, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 7148, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_FONT, 904, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 38, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 31, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 32, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2780, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_ND, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_FONT, 2781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7149, UINT16_MAX, 7149, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7151, UINT16_MAX, 7151, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7153, UINT16_MAX, 7153, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7155, UINT16_MAX, 7155, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7157, UINT16_MAX, 7157, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7159, UINT16_MAX, 7159, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7161, UINT16_MAX, 7161, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7163, UINT16_MAX, 7163, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7165, UINT16_MAX, 7165, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7167, UINT16_MAX, 7167, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7169, UINT16_MAX, 7169, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7171, UINT16_MAX, 7171, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7173, UINT16_MAX, 7173, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7175, UINT16_MAX, 7175, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7177, UINT16_MAX, 7177, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7179, UINT16_MAX, 7179, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7181, UINT16_MAX, 7181, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7183, UINT16_MAX, 7183, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7185, UINT16_MAX, 7185, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7187, UINT16_MAX, 7187, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7189, UINT16_MAX, 7189, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7191, UINT16_MAX, 7191, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7193, UINT16_MAX, 7193, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7195, UINT16_MAX, 7195, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7197, UINT16_MAX, 7197, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7199, UINT16_MAX, 7199, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7201, UINT16_MAX, 7201, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7203, UINT16_MAX, 7203, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7205, UINT16_MAX, 7205, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7207, UINT16_MAX, 7207, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7209, UINT16_MAX, 7209, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7211, UINT16_MAX, 7211, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7213, UINT16_MAX, 7213, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LU, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, 7215, UINT16_MAX, 7215, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9336, UINT16_MAX, 9336, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9338, UINT16_MAX, 9338, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9340, UINT16_MAX, 9340, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9342, UINT16_MAX, 9342, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9344, UINT16_MAX, 9344, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9346, UINT16_MAX, 9346, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9348, UINT16_MAX, 9348, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9350, UINT16_MAX, 9350, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9352, UINT16_MAX, 9352, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9354, UINT16_MAX, 9354, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9356, UINT16_MAX, 9356, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9358, UINT16_MAX, 9358, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9360, UINT16_MAX, 9360, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9362, UINT16_MAX, 9362, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9364, UINT16_MAX, 9364, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9366, UINT16_MAX, 9366, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9368, UINT16_MAX, 9368, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9370, UINT16_MAX, 9370, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9372, UINT16_MAX, 9372, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9374, UINT16_MAX, 9374, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9376, UINT16_MAX, 9376, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9378, UINT16_MAX, 9378, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9380, UINT16_MAX, 9380, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9382, UINT16_MAX, 9382, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9384, UINT16_MAX, 9384, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9386, UINT16_MAX, 9386, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9388, UINT16_MAX, 9388, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9390, UINT16_MAX, 9390, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9392, UINT16_MAX, 9392, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9394, UINT16_MAX, 9394, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9396, UINT16_MAX, 9396, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9398, UINT16_MAX, 9398, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9400, UINT16_MAX, 9400, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LL, 0, UTF8PROC_BIDI_CLASS_R, 0, UINT16_MAX, UINT16_MAX, 9402, UINT16_MAX, 9402, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 7217, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 5760, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 7218, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 7219, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 1, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6551, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6563, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_AL, UTF8PROC_DECOMP_TYPE_FONT, 6569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15412, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15414, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15416, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15418, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15420, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15422, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15424, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15428, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15430, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_NO, 0, UTF8PROC_BIDI_CLASS_EN, UTF8PROC_DECOMP_TYPE_COMPAT, 15432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23626, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23632, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23635, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23638, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23641, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23644, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23647, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23650, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23653, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23656, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23659, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23662, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23665, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23668, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23671, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23674, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23677, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23680, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23683, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23686, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23689, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23692, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23695, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23698, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23701, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23704, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 2795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 1463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 15515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 15517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1456, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3288, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 1466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2908, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3295, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 2817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15519, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15523, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 23717, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15528, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 15530, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, UTF8PROC_DECOMP_TYPE_SUPER, 15532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15534, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15536, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 15538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 4278, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7348, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7349, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7350, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7351, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7352, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3869, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7353, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7354, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7355, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 5399, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7356, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7357, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7358, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7359, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7360, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7361, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7362, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7363, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7364, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7365, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7366, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7367, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3426, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7368, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 4191, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 4192, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7369, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3581, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7370, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7371, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7372, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7373, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7374, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 4174, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 3499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7375, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7377, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_SQUARE, 7378, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23772, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_COMPAT, 23787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 7406, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_L, UTF8PROC_DECOMP_TYPE_CIRCLE, 7407, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_SK, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_E_MODIFIER}, {UTF8PROC_CATEGORY_SO, 0, UTF8PROC_BIDI_CLASS_ON, 0, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_E_BASE_GAZ}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7408, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7409, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7410, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7411, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7413, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7414, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7415, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7416, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7417, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7418, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7419, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7420, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7422, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7423, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7424, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7425, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7427, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7428, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7358, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7429, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7431, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7432, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7433, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7434, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7435, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3442, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7437, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7438, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7439, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7440, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7376, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7441, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7442, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7443, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7444, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7445, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7446, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7447, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7448, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7449, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7450, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7452, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7453, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7454, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7455, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7457, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7458, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7459, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7460, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7461, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7462, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7463, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7464, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7465, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7466, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7467, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7468, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7469, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7470, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7471, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7472, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7473, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7474, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7475, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7476, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7477, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7478, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7479, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7481, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7482, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7483, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7484, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7485, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7487, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7488, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7489, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7351, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7490, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7491, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7492, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7494, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7496, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7497, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7498, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7499, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7500, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7501, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7502, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7503, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7504, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7505, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7507, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7508, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7509, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7510, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7512, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7513, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7514, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3468, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7515, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7516, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7517, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7518, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7519, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7521, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7522, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7524, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7525, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7526, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7527, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7528, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7529, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7530, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7531, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7532, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7533, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7534, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7535, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7537, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7538, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7539, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7540, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7541, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3480, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7543, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7545, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7546, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7547, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7548, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7550, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7552, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7553, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7554, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7555, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7556, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7557, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7558, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7559, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7560, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7561, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7562, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7564, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7565, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7566, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7567, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7568, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7569, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7571, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7572, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7573, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7574, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7575, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7576, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7578, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7580, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7581, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7582, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7583, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7584, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7585, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7587, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7588, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7589, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7590, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7591, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7592, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7593, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7594, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7595, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7596, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7597, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7599, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7600, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7601, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7602, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7603, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7604, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7605, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7606, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7607, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7608, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7609, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7610, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7611, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7612, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7613, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7614, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7616, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7617, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7618, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7619, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7620, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7622, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7623, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7624, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7626, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7627, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7628, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7631, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7632, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7633, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7636, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7637, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7638, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7639, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7640, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7641, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7643, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7645, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7647, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7648, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7650, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7651, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7652, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7653, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7654, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7655, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7656, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7657, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7658, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7660, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7661, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7662, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7663, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7664, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7665, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7667, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7668, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7669, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7671, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7673, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7674, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7675, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7676, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7677, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7678, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7679, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7680, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7681, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7683, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7684, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7686, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7687, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7689, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7690, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7691, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7693, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7694, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7695, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7697, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7699, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7700, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7701, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7702, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7703, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7704, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7705, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7706, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7707, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7708, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7709, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7710, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7712, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7713, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7715, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7717, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7718, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7720, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7722, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7724, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7725, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7726, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7728, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7730, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7732, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7734, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7735, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7736, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7737, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7738, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7739, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7741, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7742, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7743, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7745, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7747, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7749, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7750, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7751, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7752, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7753, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7755, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7757, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7758, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7759, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7761, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7762, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7763, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7764, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7766, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7767, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7768, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7769, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7770, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7771, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7773, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7774, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7775, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7776, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7777, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7778, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7779, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7781, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7783, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7784, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7786, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7787, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7789, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7790, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7791, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7793, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7795, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7796, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7798, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7799, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7801, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7802, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7803, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7804, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7805, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7806, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7807, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7809, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7811, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7813, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7815, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7816, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7817, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7818, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7819, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7820, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7821, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7822, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7823, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7824, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7825, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7826, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7828, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7829, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7830, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7831, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7832, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7833, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7834, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7835, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7836, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7837, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7838, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7840, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7842, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7844, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7845, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7846, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7847, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7848, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7850, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7851, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7853, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7854, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7855, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7857, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7859, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7860, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7861, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7862, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7863, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7864, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7865, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7866, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7867, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7868, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7869, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7870, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7871, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7872, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7873, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7874, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3570, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7875, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7877, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7878, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7879, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7880, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7881, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7882, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7884, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7886, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7887, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7888, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3577, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7889, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7891, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7892, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7893, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7894, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7895, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7897, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7899, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7900, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7901, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7902, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7904, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7905, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7907, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7909, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7910, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7911, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7912, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7914, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7915, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7916, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7917, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7918, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7919, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7920, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7921, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7923, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7924, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7925, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7926, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7928, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7929, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7930, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7931, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7932, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7934, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7936, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7937, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7938, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7939, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7941, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7942, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7944, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7945, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7947, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7948, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7949, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7950, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7951, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7952, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7953, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7954, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7956, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7957, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7958, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7959, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7960, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7961, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7963, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7964, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7966, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7968, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3625, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7970, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3629, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7971, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7972, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7973, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7974, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 3634, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, {UTF8PROC_CATEGORY_LO, 0, UTF8PROC_BIDI_CLASS_L, 0, 7975, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, false, false, false, false, 2, 0, UTF8PROC_BOUNDCLASS_OTHER}, }; const utf8proc_uint16_t utf8proc_combinations[] = { 0, 46, 192, 193, 194, 195, 196, 197, 0, 256, 258, 260, 550, 461, 0, 0, 512, 514, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7680, 7840, 0, 0, 0, 0, 0, 7842, 1, 11, 262, 264, 0, 0, 0, 199, 0, 0, 0, 266, 268, 0, 46, 200, 201, 202, 7868, 203, 0, 552, 274, 276, 280, 278, 282, 0, 0, 516, 518, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7864, 0, 7704, 7706, 0, 0, 7866, 0, 46, 204, 205, 206, 296, 207, 0, 0, 298, 300, 302, 304, 463, 0, 0, 520, 522, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7882, 0, 0, 7724, 0, 0, 7880, 0, 42, 504, 323, 0, 209, 0, 0, 325, 0, 0, 0, 7748, 327, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7750, 7752, 7754, 0, 46, 210, 211, 212, 213, 214, 0, 0, 332, 334, 490, 558, 465, 336, 416, 524, 526, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7884, 0, 0, 0, 0, 0, 7886, 0, 46, 217, 218, 219, 360, 220, 366, 0, 362, 364, 370, 0, 467, 368, 431, 532, 534, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7908, 0, 7798, 7796, 0, 7794, 7910, 0, 46, 7922, 221, 374, 7928, 376, 0, 0, 562, 0, 0, 7822, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7924, 0, 0, 0, 0, 0, 7926, 0, 46, 224, 225, 226, 227, 228, 229, 0, 257, 259, 261, 551, 462, 0, 0, 513, 515, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7681, 7841, 0, 0, 0, 0, 0, 7843, 1, 11, 263, 265, 0, 0, 0, 231, 0, 0, 0, 267, 269, 0, 46, 232, 233, 234, 7869, 235, 0, 553, 275, 277, 281, 279, 283, 0, 0, 517, 519, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7865, 0, 7705, 7707, 0, 0, 7867, 0, 46, 236, 237, 238, 297, 239, 0, 0, 299, 301, 303, 0, 464, 0, 0, 521, 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7883, 0, 0, 7725, 0, 0, 7881, 0, 42, 505, 324, 0, 241, 0, 0, 326, 0, 0, 0, 7749, 328, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7751, 7753, 7755, 0, 46, 242, 243, 244, 245, 246, 0, 0, 333, 335, 491, 559, 466, 337, 417, 525, 527, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7885, 0, 0, 0, 0, 0, 7887, 0, 46, 249, 250, 251, 361, 252, 367, 0, 363, 365, 371, 0, 468, 369, 432, 533, 535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7909, 0, 7799, 7797, 0, 7795, 7911, 0, 46, 7923, 253, 375, 7929, 255, 7833, 0, 563, 0, 0, 7823, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7925, 0, 0, 0, 0, 0, 7927, 6, 42, 7696, 0, 0, 0, 7690, 270, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7692, 7694, 7698, 6, 42, 7697, 0, 0, 0, 7691, 271, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7693, 7695, 7699, 1, 11, 500, 284, 0, 0, 0, 290, 7712, 286, 0, 288, 486, 1, 11, 501, 285, 0, 0, 0, 291, 7713, 287, 0, 289, 487, 2, 44, 292, 0, 7718, 0, 7720, 0, 0, 0, 7714, 542, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7716, 0, 0, 0, 7722, 2, 44, 293, 0, 7719, 0, 7721, 0, 0, 0, 7715, 543, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7717, 7830, 0, 0, 7723, 2, 2, 308, 2, 11, 309, 0, 0, 0, 0, 0, 0, 0, 0, 496, 1, 41, 7728, 0, 0, 0, 0, 310, 0, 0, 0, 0, 488, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7730, 7732, 1, 41, 7729, 0, 0, 0, 0, 311, 0, 0, 0, 0, 489, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7731, 7733, 1, 42, 313, 0, 0, 0, 0, 315, 0, 0, 0, 0, 317, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7734, 7738, 7740, 1, 42, 314, 0, 0, 0, 0, 316, 0, 0, 0, 0, 318, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7735, 7739, 7741, 1, 41, 340, 0, 0, 0, 0, 342, 0, 0, 0, 7768, 344, 0, 0, 528, 530, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7770, 7774, 1, 41, 341, 0, 0, 0, 0, 343, 0, 0, 0, 7769, 345, 0, 0, 529, 531, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7771, 7775, 1, 40, 346, 348, 0, 0, 0, 350, 0, 0, 0, 7776, 352, 0, 0, 0, 0, 536, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7778, 1, 40, 347, 349, 0, 0, 0, 351, 0, 0, 0, 7777, 353, 0, 0, 0, 0, 537, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7779, 6, 42, 354, 0, 0, 0, 7786, 356, 0, 0, 0, 0, 538, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7788, 7790, 7792, 4, 42, 7831, 0, 355, 0, 0, 0, 7787, 357, 0, 0, 0, 0, 539, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7789, 7791, 7793, 0, 40, 7808, 7810, 372, 0, 7812, 0, 0, 0, 0, 0, 7814, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7816, 0, 40, 7809, 7811, 373, 0, 7813, 7832, 0, 0, 0, 0, 7815, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7817, 1, 41, 377, 7824, 0, 0, 0, 0, 0, 0, 0, 379, 381, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7826, 7828, 1, 41, 378, 7825, 0, 0, 0, 0, 0, 0, 0, 380, 382, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7827, 7829, 0, 11, 475, 471, 0, 0, 0, 0, 0, 469, 0, 0, 0, 473, 0, 11, 476, 472, 0, 0, 0, 0, 0, 470, 0, 0, 0, 474, 7, 7, 478, 7, 7, 479, 7, 7, 480, 7, 7, 481, 1, 7, 508, 0, 0, 0, 0, 0, 482, 1, 7, 509, 0, 0, 0, 0, 0, 483, 7, 7, 492, 7, 7, 493, 11, 11, 494, 11, 11, 495, 1, 1, 506, 1, 1, 507, 1, 1, 510, 1, 1, 511, 7, 7, 554, 7, 7, 555, 1, 7, 7756, 0, 0, 7758, 0, 0, 556, 1, 7, 7757, 0, 0, 7759, 0, 0, 557, 7, 7, 560, 7, 7, 561, 0, 49, 8173, 901, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8129, 0, 50, 8122, 902, 0, 0, 0, 0, 0, 8121, 8120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7944, 7945, 0, 8124, 0, 48, 8136, 904, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7960, 7961, 0, 50, 8138, 905, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7976, 7977, 0, 8140, 0, 48, 8154, 906, 0, 0, 938, 0, 0, 8153, 8152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7992, 7993, 0, 48, 8184, 908, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8008, 8009, 0, 48, 8170, 910, 0, 0, 939, 0, 0, 8169, 8168, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8025, 0, 50, 8186, 911, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8040, 8041, 0, 8188, 0, 49, 8146, 912, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8151, 0, 50, 8048, 940, 0, 0, 0, 0, 0, 8113, 8112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7936, 7937, 8118, 8115, 0, 48, 8050, 941, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7952, 7953, 0, 50, 8052, 942, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7968, 7969, 8134, 8131, 0, 49, 8054, 943, 0, 0, 970, 0, 0, 8145, 8144, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7984, 7985, 8150, 0, 49, 8162, 944, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8167, 0, 49, 8058, 973, 0, 0, 971, 0, 0, 8161, 8160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8016, 8017, 8166, 0, 48, 8056, 972, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8000, 8001, 0, 50, 8060, 974, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8032, 8033, 8182, 8179, 1, 4, 979, 0, 0, 980, 0, 8, 1024, 0, 0, 0, 1025, 0, 0, 0, 1238, 1, 1, 1027, 4, 4, 1031, 1, 1, 1036, 0, 8, 1037, 0, 0, 0, 1252, 0, 0, 1250, 1049, 4, 12, 1264, 0, 0, 1262, 1038, 0, 0, 0, 1266, 0, 8, 1117, 0, 0, 0, 1253, 0, 0, 1251, 1081, 0, 8, 1104, 0, 0, 0, 1105, 0, 0, 0, 1239, 1, 1, 1107, 4, 4, 1111, 1, 1, 1116, 4, 12, 1265, 0, 0, 1263, 1118, 0, 0, 0, 1267, 14, 14, 1142, 14, 14, 1143, 4, 8, 1244, 0, 0, 0, 1217, 4, 8, 1245, 0, 0, 0, 1218, 4, 8, 1234, 0, 0, 0, 1232, 4, 8, 1235, 0, 0, 0, 1233, 4, 4, 1242, 4, 4, 1243, 4, 4, 1246, 4, 4, 1247, 4, 4, 1254, 4, 4, 1255, 4, 4, 1258, 4, 4, 1259, 4, 4, 1260, 4, 4, 1261, 4, 4, 1268, 4, 4, 1269, 4, 4, 1272, 4, 4, 1273, 17, 19, 1570, 1571, 1573, 18, 18, 1572, 18, 18, 1574, 18, 18, 1728, 18, 18, 1730, 18, 18, 1747, 20, 20, 2345, 20, 20, 2353, 20, 20, 2356, 21, 22, 2507, 2508, 23, 25, 2888, 2891, 2892, 26, 26, 2964, 26, 27, 3020, 3018, 27, 27, 3019, 28, 28, 3144, 29, 29, 3264, 29, 31, 3271, 3272, 3274, 29, 29, 3275, 32, 33, 3402, 3404, 32, 32, 3403, 34, 36, 3546, 3548, 3550, 34, 34, 3549, 37, 37, 4134, 38, 38, 6918, 38, 38, 6920, 38, 38, 6922, 38, 38, 6924, 38, 38, 6926, 38, 38, 6930, 38, 38, 6971, 38, 38, 6973, 38, 38, 6976, 38, 38, 6977, 38, 38, 6979, 10, 41, 7682, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7684, 7686, 10, 41, 7683, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7685, 7687, 1, 1, 7688, 1, 1, 7689, 0, 1, 7700, 7702, 0, 1, 7701, 7703, 8, 8, 7708, 8, 8, 7709, 10, 10, 7710, 10, 10, 7711, 1, 1, 7726, 1, 1, 7727, 7, 7, 7736, 7, 7, 7737, 1, 40, 7742, 0, 0, 0, 0, 0, 0, 0, 0, 7744, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7746, 1, 40, 7743, 0, 0, 0, 0, 0, 0, 0, 0, 7745, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7747, 0, 1, 7760, 7762, 0, 1, 7761, 7763, 1, 10, 7764, 0, 0, 0, 0, 0, 0, 0, 0, 7766, 1, 10, 7765, 0, 0, 0, 0, 0, 0, 0, 0, 7767, 7, 7, 7772, 7, 7, 7773, 10, 10, 7780, 10, 10, 7781, 10, 10, 7782, 10, 10, 7783, 10, 10, 7784, 10, 10, 7785, 1, 1, 7800, 1, 1, 7801, 4, 4, 7802, 4, 4, 7803, 3, 40, 7804, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7806, 3, 40, 7805, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7807, 4, 10, 7820, 0, 0, 0, 0, 0, 7818, 4, 10, 7821, 0, 0, 0, 0, 0, 7819, 10, 10, 7835, 0, 46, 7846, 7844, 0, 7850, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7848, 0, 46, 7847, 7845, 0, 7851, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7849, 2, 8, 7852, 0, 0, 0, 0, 0, 7862, 2, 8, 7853, 0, 0, 0, 0, 0, 7863, 0, 46, 7856, 7854, 0, 7860, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7858, 0, 46, 7857, 7855, 0, 7861, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7859, 0, 46, 7872, 7870, 0, 7876, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7874, 0, 46, 7873, 7871, 0, 7877, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7875, 2, 2, 7878, 2, 2, 7879, 0, 46, 7890, 7888, 0, 7894, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7892, 0, 46, 7891, 7889, 0, 7895, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7893, 2, 2, 7896, 2, 2, 7897, 0, 46, 7900, 7898, 0, 7904, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7906, 0, 0, 0, 0, 0, 7902, 0, 46, 7901, 7899, 0, 7905, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7907, 0, 0, 0, 0, 0, 7903, 0, 46, 7914, 7912, 0, 7918, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7920, 0, 0, 0, 0, 0, 7916, 0, 46, 7915, 7913, 0, 7919, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7921, 0, 0, 0, 0, 0, 7917, 0, 50, 7938, 7940, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7942, 8064, 0, 50, 7939, 7941, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7943, 8065, 0, 50, 7946, 7948, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7950, 8072, 0, 50, 7947, 7949, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7951, 8073, 0, 1, 7954, 7956, 0, 1, 7955, 7957, 0, 1, 7962, 7964, 0, 1, 7963, 7965, 0, 50, 7970, 7972, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7974, 8080, 0, 50, 7971, 7973, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7975, 8081, 0, 50, 7978, 7980, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7982, 8088, 0, 50, 7979, 7981, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7983, 8089, 0, 49, 7986, 7988, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7990, 0, 49, 7987, 7989, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7991, 0, 49, 7994, 7996, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7998, 0, 49, 7995, 7997, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7999, 0, 1, 8002, 8004, 0, 1, 8003, 8005, 0, 1, 8010, 8012, 0, 1, 8011, 8013, 0, 49, 8018, 8020, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8022, 0, 49, 8019, 8021, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8023, 0, 49, 8027, 8029, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8031, 0, 50, 8034, 8036, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8038, 8096, 0, 50, 8035, 8037, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8039, 8097, 0, 50, 8042, 8044, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8046, 8104, 0, 50, 8043, 8045, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8047, 8105, 50, 50, 8066, 50, 50, 8067, 50, 50, 8068, 50, 50, 8069, 50, 50, 8070, 50, 50, 8071, 50, 50, 8074, 50, 50, 8075, 50, 50, 8076, 50, 50, 8077, 50, 50, 8078, 50, 50, 8079, 50, 50, 8082, 50, 50, 8083, 50, 50, 8084, 50, 50, 8085, 50, 50, 8086, 50, 50, 8087, 50, 50, 8090, 50, 50, 8091, 50, 50, 8092, 50, 50, 8093, 50, 50, 8094, 50, 50, 8095, 50, 50, 8098, 50, 50, 8099, 50, 50, 8100, 50, 50, 8101, 50, 50, 8102, 50, 50, 8103, 50, 50, 8106, 50, 50, 8107, 50, 50, 8108, 50, 50, 8109, 50, 50, 8110, 50, 50, 8111, 50, 50, 8114, 50, 50, 8116, 50, 50, 8119, 50, 50, 8130, 50, 50, 8132, 50, 50, 8135, 0, 49, 8141, 8142, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8143, 0, 49, 8157, 8158, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8159, 47, 48, 8164, 8165, 48, 48, 8172, 50, 50, 8178, 50, 50, 8180, 50, 50, 8183, 51, 51, 8602, 51, 51, 8603, 51, 51, 8622, 51, 51, 8653, 51, 51, 8654, 51, 51, 8655, 51, 51, 8708, 51, 51, 8713, 51, 51, 8716, 51, 51, 8740, 51, 51, 8742, 51, 51, 8769, 51, 51, 8772, 51, 51, 8775, 51, 51, 8777, 51, 51, 8800, 51, 51, 8802, 51, 51, 8813, 51, 51, 8814, 51, 51, 8815, 51, 51, 8816, 51, 51, 8817, 51, 51, 8820, 51, 51, 8821, 51, 51, 8824, 51, 51, 8825, 51, 51, 8832, 51, 51, 8833, 51, 51, 8836, 51, 51, 8837, 51, 51, 8840, 51, 51, 8841, 51, 51, 8876, 51, 51, 8877, 51, 51, 8878, 51, 51, 8879, 51, 51, 8928, 51, 51, 8929, 51, 51, 8930, 51, 51, 8931, 51, 51, 8938, 51, 51, 8939, 51, 51, 8940, 51, 51, 8941, 51, 51, 10972, 52, 52, 12364, 52, 52, 12366, 52, 52, 12368, 52, 52, 12370, 52, 52, 12372, 52, 52, 12374, 52, 52, 12376, 52, 52, 12378, 52, 52, 12380, 52, 52, 12382, 52, 52, 12384, 52, 52, 12386, 52, 52, 12389, 52, 52, 12391, 52, 52, 12393, 52, 53, 12400, 12401, 52, 53, 12403, 12404, 52, 53, 12406, 12407, 52, 53, 12409, 12410, 52, 53, 12412, 12413, 52, 52, 12436, 52, 52, 12446, 52, 52, 12460, 52, 52, 12462, 52, 52, 12464, 52, 52, 12466, 52, 52, 12468, 52, 52, 12470, 52, 52, 12472, 52, 52, 12474, 52, 52, 12476, 52, 52, 12478, 52, 52, 12480, 52, 52, 12482, 52, 52, 12485, 52, 52, 12487, 52, 52, 12489, 52, 53, 12496, 12497, 52, 53, 12499, 12500, 52, 53, 12502, 12503, 52, 53, 12505, 12506, 52, 53, 12508, 12509, 52, 52, 12532, 52, 52, 12535, 52, 52, 12536, 52, 52, 12537, 52, 52, 12538, 52, 52, 12542, 54, 55, 1, 4250, 54, 55, 1, 4252, 54, 55, 1, 4267, 56, 57, 1, 4398, 56, 57, 1, 4399, 58, 61, 1, 4939, 1, 4940, 62, 67, 1, 5307, 1, 5308, 1, 5310, 68, 69, 1, 5562, 68, 69, 1, 5563, 70, 71, 1, 53598, 70, 71, 1, 53599, 72, 81, 1, 53600, 1, 53601, 1, 53602, 1, 53603, 1, 53604, 70, 71, 1, 53691, 70, 71, 1, 53692, 72, 75, 1, 53693, 1, 53695, 72, 75, 1, 53694, 1, 53696, }; �������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016532�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/CMakeLists.txt�����������������������������������������������������0000664�0000000�0000000�00000001566�15162662266�0021302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(zxcvbn-c OBJECT zxcvbn.h dict-src.h zxcvbn.cpp ) set(ZXCVBN_C_PUBLIC_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include/zxcvbn-c") file(MAKE_DIRECTORY "${ZXCVBN_C_PUBLIC_INCLUDE_DIR}") configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/zxcvbn.h" "${ZXCVBN_C_PUBLIC_INCLUDE_DIR}/zxcvbn.h" COPYONLY ) target_include_directories(zxcvbn-c SYSTEM PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include> ) if(ENABLE_JAVA_BINDINGS) set_target_properties(zxcvbn-c PROPERTIES POSITION_INDEPENDENT_CODE ON ) endif() if(WIN32 AND ENABLE_SDKLIB_WERROR) set_source_files_properties( zxcvbn.cpp PROPERTIES COMPILE_FLAGS "/wd4456" ) endif() if(APPLE AND ENABLE_SDKLIB_WERROR) set_source_files_properties( zxcvbn.cpp PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion" ) endif() ������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/LICENSE.txt��������������������������������������������������������0000664�0000000�0000000�00000002065�15162662266�0020360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The MIT License Copyright (c) 2015-2017 Tony Evans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/README.md����������������������������������������������������������0000664�0000000�0000000�00000010004�15162662266�0020004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# zxcvbn-c This is a C/C++ implementation of the zxcvbn password strength estimation. The code is intended to be included as part of the source of a C/C++ program. Like the original this code is for character sets which use single byte characters primarily in the code range 0x20 to 0x7E. The original coffee script version is available at https://github.com/lowe/zxcvbn An article on the reasons for zxcvbn is at https://tech.dropox.com/2012/04/zxcvbn-realistic-password-strength-estimation ##Building The makefile will build several test programs to test the code. It shows the steps needed to use the code in C and C++ programs, using the dictionary data read from file or included within the program executable. The makefile has only been tried on Linux using GCC version 4.8.4, but should be faily portable to other systems. When dictionary data is included in your program's executable, the files `zxcvbn.c` , `zxcvbn.h` , `dict-src.h` are used in your program. When dictionary data is read from file, the files `zxcvbn.c` , `zxcvbn.h` , `dict-crc.h` and `zxcvbn.dict` are used in your program, compiled with `#define USE_DICT_FILE`. The CRC of the dictionary data file is written to `dict-crc.h` so your executable can detect corruption of the data. Rename `zxcvbn.c` to `zxcvbn.cpp` (or whatever your compiler uses) to compile as C++. The `dict*.h` and `zxcvbn.dict` files are generated by the dictgen program compiled from dict-generate.cpp (see makefile for details). ##Using Initially call `ZxcvbnInit()` with the pathname of the `zxcvbn.dict` file. This can be omitted when dictionary data is included in the executable. Call `ZxcvbnMatch()` with the password and optional user dictionary to get the entropy estimation and optional information on the password parts (which will need freeing with `ZxcvbnFreeInfo()` after use). Do this for each password to be tested, or as each character of it is entered into your program. The optional user dictionary can change between each call. Finally call `ZxcvbnUninit()` to free the dictionary data from read from file. This can be omitted when dictionary data is included in the executable. Review the test program in `test.c` for an example. ## Differences from the original version. The entropy calculated will sometimes differ from the original because of * The UK keyboard layout is also included, so there are additional spacial sequences, e.g. **;'#** is a spacial sequence. * The different character classes in a password are taken into account when calculating the strength of brute-force matches. * Dijktra's path searching algorithm is used to combine parts of the entered password. This can result in the found parts of the password being combined differently than the original coffee script. E.g. the password **passwordassword** is combined by the original coffee script as **p** (3.5 bits) + **asswordassword** (12.6 bits) + multiple part allowance (1.0bit) to give total entropy of 17.1 bits. This implementation combines it as **password** (1.0 bit) + **assword** (11.6 bits) + multiple part allowance (1.0bit) to give 13.6 bits. * For multi part passwords the original coffee script version multiplies the number of guesses needed by the factorial of the number of parts. This is not possible in this version as Dijktra's algorithm is used. Instead one bit entropy is added for the part at the end of the password, 1.7 bits for each part in the middle of a password and nothing for the part at the beginning. This gives similar results compared to the coffee script version when there are 4 or less parts, but will differ significantly when there are many parts (which is likely to be a rare occurrence). ##References The original coffee-script version is available at https://github.com/lowe/zxcvbn The dictionary words are taken from the original coffee script version. Dictionary trie encoding (used for by the word lookup code) based on idea from the Caroline Word Graph from http://www.pathcom.com/~vadco/cwg.html ## License MIT License * http://www.opensource.org/licenses/mit-license.php ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/dict-src.h���������������������������������������������������������0000664�0000000�0000000�00006603274�15162662266�0020435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off #define ROOT_NODE_LOC 0 #define BITS_CHILD_PATT_INDEX 14 #define BITS_CHILD_MAP_INDEX 18 #define SHIFT_CHILD_MAP_INDEX BITS_CHILD_PATT_INDEX #define SHIFT_WORD_ENDING_BIT (SHIFT_CHILD_MAP_INDEX + BITS_CHILD_MAP_INDEX) static const unsigned int DictNodes[36480] = { 0, 1916966, 17170589, 41140594, 56426987, 68911683, 77709955, 91554518, 91947735, 101942045, 112001899, 131040250, 140395081, 148522082, 164119758, 164316367, 181749059, 195102096, 195544465, 207979993, 226018882, 226215491, 245434046, 253216497, 260507420, 265209644, 276907868, 286327369, 296339401, 303859687, 304859115, 314968098, 324126805, 340363429, 349259986, 356141292, 366119205, 370755902, 379029866, 389810585, 397576634, 413043194, 418761225, 419121674, 427887154, 434178644, 441647689, 447122077, 454328905, 462555882, 467536639, 467995393, 479152964, 486198121, 495798681, 505613264, 505744337, 516803602, 523717688, 524094521, 533990500, 542641298, 557223130, 564317330, 580226377, 589041013, 597069203, 616943051, 617123276, 623267311, 628772362, 636341807, 636669488, 644566615, 652168832, 664735424, 670073338, 677678849, 678069346, 695488345, 706989973, 717443008, 727502830, 733859857, 734480113, 746392979, 750719066, 766120115, 780636407, 792727849, 805815394, 821006776, 828985820, 829313501, 835424759, 843010584, 848088393, 857261154, 882823853, 883198314, 898435481, 904794898, 915215168, 922862153, 927011707, 936891310, 938021810, 944821193, 951849945, 956961771, 962008069, 962350937, 972391522, 995447946, 1002737497,1015485668,1020826879,1021334785,1034917183,1036866886, 1049482639,1061098937,1061279162,1075467784,1086800457,1095355795,1102993027,1113020080,1113331377,1119459024,1129791994,1136662307,1146716745,1153079130,1153467095, 1162057604,1169774496,1177475013,1182537693,1190429495,1190549509,1201313847,1207539790,1209517457,1225611389,1225889918,1228789895,1236097182,1241323697,1251121370, 1251481819,1260689666,1268685091,1274091836,1284856169,1294637457,1301813679,638977, 655362, 671747, 688132, 5, 704518, 720902, 737286, 753670, 688134, 770055, 950280, 966665, 983048, 999434, 1032200, 1015816, 688136, 1048587, 1097740, 1146888, 1163276, 688141, 688142, 1212431, 1245192, 688141, 1261584, 1277969, 688146, 1294355, 1327124, 1359880, 688149, 1376264, 688150, 1392662, 1409047, 1425432, 1163289, 1441800, 1458202, 1474568, 688154, 1490971, 1343510, 1015834, 1523720, 1540120, 1556488, 688152, 1572892, 1605650, 1622045, 1638430, 1654815, 688157, 1671176, 1687575, 1474585, 1703968, 1736737, 1753122, 1769506, 1785891, 688164, 1802277, 1851406, 1064968, 1867789, 1884185, 1900568, 1376279, 2261031, 2342952, 1015816, 2490409, 2654229, 2670600, 1015829, 2687017, 1015830, 2850838, 688170, 1114139, 688153, 2867243, 2932780, 3031061, 3047432, 1359880, 3063853, 3162132, 3194901, 3211285, 688149, 3227674, 1474586, 1474586, 3244073, 3407918, 3440662, 3457047, 688151, 3473432, 3489834, 1376277, 2375727, 3506224, 3637297, 2326541, 3784745, 3948565, 3964954, 1474581, 3457045, 3981353, 4145202, 2850830, 4309018, 4325398, 4341781, 4358170, 688150, 1359898, 4374579, 4472858, 3457046, 1359898, 4390934, 4489268, 4538421, 4571190, 4603918, 688136, 4620343, 1278008, 4636729, 4653114, 688187, 3457048, 1065002, 1245226, 4669500, 688129, 688184, 4718649, 688186, 4735037, 2850838, 2850829, 4767760, 4784184, 4800574, 1278010, 4816953, 4833281, 4849727, 4866082, 688192, 4882497, 4964374, 4980757, 3489814, 4997145, 1064973, 5013517, 5029954, 4997143, 5062723, 5161028, 5193749, 3833879, 5210181, 2326550, 1245208, 5242900, 1376279, 5275718, 1245207, 5324824, 5341226, 2326550, 5357639, 5390361, 5406742, 688154, 5423176, 5472329, 1556501, 5505088, 5521438, 5537794, 688161, 5554207, 5570634, 5587002, 5603364, 688159, 5619787, 1064984, 1376280, 1359896, 5685324, 5734425, 2326549, 5750796, 5799957, 5816341, 3047437, 5832717, 5849109, 5865485, 1245205, 5881933, 5931086, 2850837, 5963798, 5980185, 5996568, 1474573, 4259861, 6013007, 6144072, 1359873, 1064981, 2506832, 6193193, 6357073, 6389774, 1064985, 6406185, 6570005, 6586382, 3457037, 6602773, 6619150, 2850829, 6635561, 6799381, 6815758, 3457066, 6832169, 1064974, 6995994, 7012407, 7028758, 7045176, 1277975, 7061540, 7077921, 7094328, 688210, 7110739, 7143508, 7159892, 7176193, 688191, 7192577, 7209034, 7225360, 688145, 7241759, 7258168, 688190, 7274581, 7307297, 7323650, 7340090, 7225380, 4718678, 7356503, 7405631, 4636705, 7422040, 7454802, 7471105, 7487546, 688212, 7503956, 7208996, 7520273, 7536702, 1654842, 7553113, 7585855, 7602239, 7618617, 4653072, 7635007, 1654847, 7651418, 7700537, 7716886, 7733306, 7749655, 7766075, 5537816, 7782434, 7798875, 7831609, 4866111, 7847999, 1474617, 7864378, 7880763, 1654817, 7897089, 7913489, 4636746, 7929914, 5537855, 7946266, 7962658, 7979070, 7995466, 8011818, 688185, 8028252, 8224861, 8306782, 8405011, 3031066, 8437855, 8470586, 8486930, 8503380, 688162, 8519688, 8536090, 8552456, 1015834, 8568835, 8585217, 8601617, 7176254, 8618004, 8650778, 8667162, 8683541, 1474581, 8699925, 8716314, 4177941, 8732717, 8831072, 8880154, 8896538, 5406746, 1376278, 1245197, 8912993, 8978454, 8994842, 9011222, 1376282, 9027608, 1245209, 9044030, 9060388, 9076754, 9093136, 688144, 9109602, 9142295, 9158682, 9175063, 3457050, 9191478, 9224291, 9273367, 2850841, 9289828, 9338904, 9355290, 9371672, 1556506, 9388040, 9404442, 9420824, 1015850, 9437282, 9469977, 2326554, 9486362, 9502733, 1245210, 9519130, 9535530, 2850842, 9551898, 9568270, 1064986, 9584741, 9617471, 9633825, 5603351, 9650258, 9666615, 688202, 9617424, 9683046, 9977959, 10010728, 5537866, 2686984, 10043497, 10092566, 1359893, 10108949, 1359894, 10125333, 10141720, 9469966, 10158126, 10190954, 1376282, 3457048, 10240107, 1474582, 8683543, 10272876, 10092570, 10321948, 10354710, 10371094, 4358166, 2326553, 10387481, 1064974, 10403862, 10420247, 3457046, 10436630, 10453016, 1556502, 10469485, 5210135, 5341209, 10534970, 10551326, 10567762, 7094308, 6389782, 10584174, 10616845, 1245206, 10633327, 10682402, 10698836, 10715172, 1785918, 10731632, 10764306, 10780702, 7487574, 10797078, 10813454, 1064982, 10829827, 10846221, 10862617, 1474622, 10879012, 10895383, 4866074, 10911747, 10928162, 4653079, 10944598, 10960931, 1277968, 10977296, 4718594, 10993666, 11010065, 11026490, 688158, 1654785, 11042829, 11059203, 4653120, 11075697, 11386903, 11403272, 1015831, 11419755, 1114226, 11452437, 1359895, 11468915, 11550836, 11599898, 1474583, 11616338, 11632698, 1785858, 11649141, 11681899, 1900566, 11714678, 11763735, 11780119, 11796503, 11812887, 688151, 10338328, 11829367, 11862040, 1556503, 11878520, 2326552, 1065002, 11960343, 11976729, 2326551, 11993156, 12025977, 1245207, 12058642, 12075094, 12091514, 688250, 12107799, 12124202, 2850839, 12140667, 12173326, 1064983, 7176251, 4636758, 12189713, 12206166, 7094330, 12222538, 12238873, 5537818, 12255263, 12271633, 4636689, 12288017, 12304418, 9666574, 12320852, 4800598, 12337276, 12369955, 7225374, 12386390, 4718625, 5603344, 12402723, 12419073, 12091423, 12435581, 12632190, 12697608, 1015832, 12714042, 5537809, 12730392, 12746779, 12779581, 12697621, 1359896, 12812415, 12861466, 1474584, 12877952, 12910614, 1376280, 12927105, 12992538, 4603925, 4227095, 13009026, 13074495, 13090872, 13107201, 1785919, 13123615, 13140002, 13156355, 1277953, 13172867, 8683546, 13221912, 13238296, 13254680, 688152, 13271114, 13287425, 13303841, 7176250, 13320324, 13385753, 2326552, 13402154, 1015822, 13418578, 7176256, 13435013, 13484045, 1245208, 13500498, 13516858, 1654870, 10813443, 13533208, 13549610, 2850840, 13565981, 13582394, 7225362, 13598784, 13615133, 7225347, 13631547, 13647898, 13664292, 13680653, 2850898, 13697158, 13910041, 13926408, 1015833, 13942809, 13959285, 1359897, 1376277, 13991961, 1687578, 14008455, 11452442, 14041110, 1376281, 14057497, 14073879, 3457049, 14090376, 14139416, 1556505, 14155801, 14172297, 14221337, 14237721, 14254105, 688153, 14270602, 14303245, 1245209, 14319641, 9273386, 14336139, 14385166, 14401561, 14417934, 1064985, 14434305, 14450690, 14467073, 9093179, 14483486, 14499926, 4718595, 14516306, 14532644, 2850834, 14549076, 14565394, 2850902, 14581900, 14827533, 14843925, 1359885, 14860429, 5996570, 14893080, 7225377, 14909453, 14925846, 1376269, 14942350, 14975018, 1474584, 14991383, 3457037, 15007887, 15040629, 15073304, 1556493, 15089808, 4407319, 15138875, 15155274, 15171668, 1654819, 15187993, 2326541, 15204497, 15253650, 15286285, 15302669, 1163277, 10387498, 15319186, 4751402, 15351955, 15450136, 3964953, 15466509, 4997134, 15482954, 15499300, 7258147, 15515707, 15532048, 5537823, 15548471, 15564858, 1277966, 15581306, 15597601, 4866077, 15613954, 15630360, 15646779, 688163, 15663252, 15859861, 15892501, 1359914, 15908895, 15925310, 4685886, 15941661, 15958041, 15974403, 688214, 15990891, 16023702, 14942230, 16056465, 16105473, 16121911, 16138314, 9666623, 16154648, 1851414, 16171033, 2326570, 16187530, 15089688, 4259853, 16220202, 16236586, 16252970, 16269354, 16285738, 688170, 16302122, 11943950, 16318500, 16334930, 2326584, 16351249, 16367672, 13303890, 16384023, 16400413, 16416826, 16433174, 8503327, 16449572, 16465921, 16482338, 688131, 16498718, 16515106, 7176223, 16531607, 16646296, 16678933, 4603912, 16695304, 1015822, 16711701, 16728217, 16760863, 16777275, 9093175, 16793621, 5652493, 16810138, 16842777, 2326542, 16859282, 16892059, 16941080, 16957463, 16973846, 3211290, 4145194, 16990222, 17006606, 17022990, 17039374, 17055758, 688142, 17072190, 17088569, 9666584, 17105052, 17121436, 17137820, 17154204, 688284, 17629342, 17662026, 5537874, 17629220, 17678495, 17711126, 1409047, 17727514, 17743928, 17760278, 3457042, 17776800, 17940641, 1015831, 18071714, 18104483, 18170020, 18219172, 18268161, 18284706, 18317313, 4669441, 688210, 18333861, 18366523, 18382907, 18399291, 18415675, 18432074, 18448458, 18464842, 18481234, 7094354, 18497618, 18137170, 18513975, 688183, 7094346, 18530362, 9666619, 18137170, 18546724, 18563101, 7094273, 18579472, 5537810, 18596006, 18661462, 18677761, 7258171, 18694161, 18710695, 7225360, 18759738, 688145, 18776121, 8011833, 18792616, 19071145, 4669497, 19218602, 19267620, 688186, 7176208, 19283986, 19300368, 19316753, 19333291, 9666577, 19398828, 4669473, 19431597, 19562670, 19611664, 12386321, 19628061, 18710703, 19644592, 19677361, 19710130, 18710707, 19759135, 19775522, 19791906, 19808314, 4718655, 7176372, 19824821, 19873818, 19890198, 13254679, 4685879, 19906742, 19939354, 11812886, 19955834, 19972170, 1114295, 19988664, 20070484, 20086868, 18137089, 20103252, 20119634, 20135937, 20152322, 1277988, 20168889, 20201658, 20234257, 20250660, 4636706, 20267025, 7487489, 20283579, 20332577, 20349116, 20398269, 20430865, 688191, 20086868, 20447422, 20594879, 688129, 20660242, 20676667, 20693008, 7094305, 20709568, 20791355, 20807696, 20824066, 20840503, 4718628, 20856890, 7225402, 20873409, 20906017, 1654866, 20922369, 20938946, 20971553, 19333306, 20987963, 21004321, 5603357, 21020867, 21086394, 21118977, 21135396, 19546196, 21151940, 4718609, 21217313, 21233861, 19349520, 21266490, 5537848, 21283009, 4718626, 1114310, 21315669, 2375879, 21348411, 21364738, 21381121, 21397588, 21414088, 21446857, 1278011, 21577930, 7094335, 21610532, 21626897, 21643265, 21659851, 4718676, 1114316, 21692618, 21725258, 20217892, 21741754, 20414480, 21774530, 21807162, 21823543, 21839888, 21446657, 21856340, 21872641, 21889041, 21905409, 16482388, 21921976, 22003917, 22085633, 22102034, 22118401, 22134839, 4669499, 22151169, 22167554, 22184115, 22216789, 22249678, 22298681, 22315044, 8503329, 22331428, 22347983, 7176209, 22380752, 22462673, 22511826, 22560801, 1654868, 9093183, 9093154, 22577363, 22675472, 22691857, 4800530, 22708241, 22724820, 20430906, 4718650, 22757562, 22790357, 22823126, 22855895, 22888481, 22905018, 22937816, 22970453, 688146, 23003353, 23036122, 19644635, 7503905, 23085276, 23150609, 23167034, 23183377, 4718648, 21266433, 23199803, 23216146, 23232712, 23265501, 4669524, 23363602, 23380190, 23412753, 23429332, 9093153, 23462074, 2375903, 23494880, 23527649, 23560418, 23593187, 23838948, 23887930, 23904258, 23920837, 23953509, 23986405, 24019028, 1654868, 24035359, 24051796, 9093176, 23281746, 24068326, 24166459, 24182802, 1114343, 24199400, 24281146, 24297531, 24313857, 24330434, 24363196, 24412349, 24444961, 24461545, 12386305, 24494113, 24510698, 24576235, 24608831, 24625388, 24707307, 24739899, 24756421, 24789229, 19349540, 24821778, 24838202, 24854545, 24870945, 24887534, 22560852, 24936670, 24969252, 24985602, 1278010, 25001986, 25018369, 25034785, 22970426, 25051375, 25133057, 13303826, 25149680, 25182224, 25198610, 25214977, 25231393, 25247930, 25280753, 25313297, 25329861, 19349540, 25362468, 25379058, 25411666, 25428211, 1114337, 25477178, 25493521, 20430881, 25509906, 25526516, 25575613, 22560842, 4800545, 25608386, 25640993, 25657578, 25723115, 25755848, 25788500, 7880740, 25804859, 25821242, 25837570, 25854010, 25870353, 7176225, 25886965, 25935903, 25952340, 25968641, 25985057, 26001594, 26034405, 26067156, 22560826, 26099958, 26165281, 21757988, 26181855, 26230800, 4800514, 26247415, 26411093, 9093138, 688144, 7225345, 26444024, 26411066, 26476737, 22397153, 26509561, 26607866, 26673207, 7077889, 26689787, 26738774, 26755258, 26788092, 22724692, 26820861, 1114331, 19349535, 26853630, 26902783, 26951711, 7225403, 26968127, 7176228, 26984507, 9093137, 27001088, 27082811, 27099393, 27148289, 18759697, 27164735, 27181314, 27214068, 1114371, 27262992, 27279417, 27295828, 27312186, 27328530, 27344970, 27361466, 27394308, 27443433, 7094298, 19546170, 27476229, 1114331, 27557951, 27574305, 27590692, 20430904, 27607071, 27623646, 27656454, 27688977, 27705377, 27721921, 27754513, 22724664, 27770916, 4669526, 27787527, 27836602, 26099748, 27869217, 27885761, 27918600, 2375945, 27967754, 5537793, 1114379, 28000340, 4685860, 28016908, 28115213, 688202, 28180497, 28197093, 21659703, 28229876, 28278817, 28295356, 1114337, 20430907, 28344587, 19726606, 28377146, 28393535, 28409919, 7176250, 28426241, 28442838, 28475592, 28508431, 23019521, 20267044, 28541200, 28655647, 28672017, 28688440, 28704801, 28721183, 28737595, 28754106, 28786961, 29081874, 1785938, 29245715, 29278266, 23314491, 29294868, 29442325, 26968097, 29474849, 29491236, 29507615, 688130, 29524002, 29540630, 1114391, 29573400, 29605921, 29622516, 29671440, 29688089, 29720858, 29835547, 9093156, 29884700, 29491218, 29950036, 22937885, 29966622, 30048289, 30064858, 30113999, 30146634, 7208993, 30162960, 19546129, 30179364, 30195919, 30228497, 30245093, 30277690, 30294079, 30310463, 25428154, 30327071, 30425124, 30441535, 19546148, 30458144, 30556196, 30572562, 1114305, 30589217, 1114305, 30621986, 30654712, 30687268, 7176210, 30703647, 30720001, 26083361, 30736466, 30752784, 21512276, 30769210, 30785734, 30818362, 23183377, 7290916, 30834979, 30916856, 30949460, 1785978, 30965776, 30982161, 30998546, 31014913, 8601656, 31031588, 12664888, 31096888, 31113249, 31129636, 22724694, 31146277, 24805393, 31178811, 24985617, 31195403, 31227937, 31244582, 1114407, 12713985, 31310010, 31342651, 7209023, 31359272, 18743359, 31392002, 31424705, 31457577, 31490135, 31539498, 31605035, 31637567, 31653921, 31670485, 31703099, 19333306, 31719425, 31735841, 31752423, 31785167, 31817972, 31867180, 31916116, 1114397, 31932479, 31948836, 31965485, 31998219, 32030964, 22560786, 32079889, 32096292, 32112907, 24985682, 32145710, 32227631, 32276543, 32292920, 32309306, 32325649, 32342228, 32374866, 32391231, 22216789, 32407585, 32424122, 32457008, 32506161, 32571636, 32620618, 4669457, 32636945, 32653313, 32669780, 32686386, 19267601, 32719155, 32768017, 32784692, 32833553, 19579189, 32850122, 32882747, 32899130, 29507585, 32915540, 32931873, 32948534, 1654843, 10960912, 32997687, 33062929, 33079508, 33112250, 26788100, 33144833, 33161290, 33177658, 33194242, 33227022, 33259553, 33276197, 33308731, 33325141, 33358091, 33390836, 21217339, 33439780, 33456191, 33472698, 33505592, 33571129, 33620279, 33685816, 33751099, 1114371, 33767480, 33783809, 33800404, 33833018, 7487572, 33849658, 33898682, 33931347, 33964347, 34013500, 34078749, 34095121, 34111489, 34127874, 34144292, 7176248, 34160724, 21184513, 34177341, 34226212, 18137121, 23183361, 34242772, 34275410, 34291796, 34308132, 27115522, 34324562, 27557946, 34341182, 34472206, 34504705, 7618578, 34521407, 28327972, 34619448, 34635809, 34652378, 34701501, 34734097, 34750693, 34783552, 34848769, 34865238, 25493540, 34881572, 34897921, 34914337, 34930874, 34963458, 34980062, 35012641, 35029028, 35045583, 35078330, 35110968, 35127329, 35143866, 35176662, 35209234, 29622465, 35225616, 35242001, 19267617, 35258450, 35274753, 35291153, 35307556, 35323967, 26296353, 27967504, 35340541, 35373114, 35389761, 35422266, 35438609, 35455009, 35471396, 35487760, 4800529, 35504210, 35520528, 17629215, 35536983, 35586104, 35602433, 20430865, 35618901, 35651643, 35668290, 35782740, 8011792, 35799076, 4718714, 20692993, 35815440, 35831824, 19742737, 35848274, 7290881, 35864738, 12386367, 35897345, 35913787, 20430866, 35930435, 36127044, 36208657, 5537855, 36225082, 36241410, 7208977, 36258117, 36290642, 19546113, 36306945, 36323357, 36339713, 1785873, 36356422, 36470842, 7176255, 36487495, 36552901, 20430849, 36585630, 36618568, 36667478, 36683777, 36700193, 24363194, 36716607, 36732991, 36749543, 36782287, 36815177, 36880570, 36913153, 36929569, 36946108, 36995402, 37028171, 9093204, 37077324, 37126341, 20971554, 37158913, 37175299, 37191762, 37208080, 37224503, 4669476, 37240865, 37257303, 37306385, 37322940, 37371988, 1785940, 37388599, 37454157, 37503310, 26984449, 37552463, 37634384, 37666847, 37683284, 37699617, 37715999, 37732411, 30245057, 32620580, 27197777, 37748737, 37765179, 16482305, 37781842, 37896531, 1114360, 37027898, 37945639, 37978452, 38043989, 688187, 38093048, 38125790, 38158678, 4669514, 4718651, 4653057, 38191137, 38207524, 20627473, 38224215, 26296322, 38256656, 4685883, 38273368, 38469977, 38519130, 38535463, 38568018, 38584402, 38600786, 18022482, 38617435, 26198214, 20856891, 38715423, 7929892, 38731809, 38748219, 29507600, 38764737, 38797531, 688164, 4653114, 22462812, 38830389, 38862865, 38879457, 38912033, 38928400, 12075067, 38944990, 38977885, 39010305, 4669458, 39027038, 39239762, 21659706, 39256298, 19546129, 37027924, 39321951, 26198368, 39387355, 39420257, 39502178, 39551331, 1114468, 28114961, 39616570, 39632959, 22560831, 39649637, 39715174, 39763984, 18055199, 39780538, 39813136, 20693051, 39829863, 40075624, 20430922, 21184571, 40124475, 40140801, 5537914, 40157271, 40206394, 1278036, 23183419, 40222913, 5537851, 29999121, 40255719, 40288617, 24985633, 21299258, 22216897, 40321386, 40353793, 22724625, 40370235, 19742753, 40386923, 40419387, 16482340, 40435960, 40468844, 12091476, 40501613, 14467103, 40567150, 40632402, 31342628, 22216890, 40648974, 19546128, 40681473, 1114479, 40698224, 40829260, 28000338, 40878377, 40911105, 37847073, 40960369, 41058305, 4669442, 41074876, 41123858, 4718666, 41549825, 1278026, 41566579, 41730049, 41746658, 41779572, 41812341, 41861308, 38994292, 41910646, 1114487, 19235038, 1785857, 42008952, 42058105, 42123560, 42156410, 18759681, 42189048, 38027323, 19382561, 42221947, 42303868, 38027280, 42401793, 42418194, 42434618, 9093150, 42451325, 42516862, 42565691, 42582032, 20430911, 42598783, 42663937, 21659707, 42680704, 42762625, 42876945, 2375924, 42893698, 1114360, 7208993, 42959036, 17662008, 43008387, 43041156, 43106335, 43122746, 43139131, 43155491, 4718623, 43172229, 43254150, 43335713, 43352257, 20627515, 8503297, 43286587, 43384833, 19546171, 14467131, 43401274, 43417602, 43434181, 43466976, 43499911, 20595080, 43614224, 43630676, 43647012, 43663602, 19382466, 43696521, 43778442, 43876622, 43909291, 7258129, 7176223, 43974842, 44007628, 4718666, 44040587, 44073356, 44351885, 1114461, 37224507, 44401038, 29491231, 4669522, 44450040, 44482577, 44498962, 44515387, 44532023, 44597311, 44613633, 44630033, 4653074, 7340063, 44646667, 24527066, 44679567, 44728542, 19578963, 44761310, 44793873, 44810258, 4669499, 44826786, 44859636, 21184548, 44908944, 44974481, 4866106, 45006866, 45023445, 19546171, 45056311, 1114341, 45121539, 45137936, 45154335, 19136596, 45170908, 45236626, 4669562, 45285392, 4718594, 45301818, 1785915, 45318414, 45351287, 45383738, 45400467, 45433232, 45498757, 23068885, 45580692, 35586084, 45613057, 45629442, 1785874, 12156986, 45646229, 44236835, 45695012, 45711393, 45727826, 7176194, 45744534, 46039447, 46072052, 4669495, 46121019, 1785889, 46137588, 41910680, 46186513, 46203109, 46236057, 19333363, 46268432, 1654801, 46284833, 19382562, 46301594, 19382494, 46415873, 43384868, 38027300, 46432667, 21184528, 46711196, 688159, 19579150, 7225345, 46825530, 12091450, 46842069, 42975268, 46874707, 41091130, 46907805, 47071625, 47153377, 47186209, 47219102, 46120993, 37224479, 47267841, 22970385, 47284639, 47415381, 12091451, 39747641, 47448471, 47481029, 47514016, 29868095, 24477732, 47563169, 47628702, 4636708, 47677637, 47710626, 47808760, 47841313, 28327952, 47858083, 47906832, 19628049, 47923258, 28524603, 47939671, 47989156, 48070890, 48136613, 7340114, 48185530, 48218534, 48332859, 48349200, 7258128, 48365650, 20152378, 48381968, 19808287, 23183396, 48398584, 19579303, 48431416, 48497064, 48546217, 48644522, 4636703, 48742827, 38043649, 48775361, 48808364, 48889874, 48906241, 48922683, 8503354, 29114561, 48939082, 7880786, 48955821, 49004560, 37847126, 49021132, 49053727, 5537825, 49070510, 49217967, 49316146, 49348644, 4718610, 4669473, 49365348, 12386337, 49398192, 49480113, 49512718, 1114379, 49545303, 49594802, 49627194, 49643553, 49659940, 49676723, 24887297, 49709073, 49725503, 1114197, 30572561, 49741855, 49758244, 49774648, 22134820, 49791162, 49824180, 1654802, 49856949, 49889362, 49905697, 688157, 49922486, 50020569, 50053121, 50069937, 50102711, 50233784, 50298939, 50315265, 37224504, 50331684, 37503035, 50348068, 23281748, 50364857, 50446778, 18530335, 50479120, 20856954, 50495931, 9666577, 50610177, 50626923, 50659329, 50675796, 50692112, 50708496, 4669474, 50725052, 50774240, 50807198, 50855967, 38371391, 50872764, 50921879, 50954685, 38797371, 49348639, 51036606, 51118138, 51134548, 51150932, 1114343, 51167290, 51183649, 4636754, 51200001, 1785915, 51216831, 51249188, 51265599, 51282241, 51314744, 51331154, 51347487, 4636756, 51364288, 51413441, 39763969, 29491217, 19235109, 51462594, 51544515, 51577284, 9093138, 51642821, 51691974, 51790279, 688162, 51888584, 51970270, 21774633, 52003058, 19382458, 52036041, 52134050, 52166714, 52183076, 52199426, 52215866, 52232251, 7176372, 52249034, 52494795, 21659684, 47677900, 52707742, 19235263, 1114382, 52756941, 52789284, 37503009, 52806094, 19628033, 52871479, 26427793, 23314465, 52936788, 52953172, 18710776, 52969730, 53002703, 28327967, 53051605, 53084196, 53100607, 4669496, 53117116, 25575441, 53166544, 53280831, 53297215, 53313537, 53329938, 53346320, 19742779, 53362747, 53379537, 53461458, 53542929, 29098196, 53559327, 53575911, 53608484, 53624848, 20431185, 53641217, 53657684, 53477434, 53674215, 53706815, 53723322, 53756371, 53821908, 27721729, 53871061, 53903830, 4718650, 54067671, 54116824, 54182361, 54247457, 13303844, 54264282, 54329819, 54362113, 54378972, 54444509, 54510046, 4669471, 54542367, 19185700, 54559199, 4653087, 7929885, 54510046, 54607930, 7929915, 54624736, 54755809, 54804738, 54837496, 1114408, 54870103, 688214, 1114334, 54919650, 54968504, 7225361, 38223931, 55050453, 46120961, 55083025, 55099698, 55132249, 55164945, 23019556, 55181498, 55214143, 55230666, 55263233, 688184, 55279632, 55296212, 55328826, 55345169, 55361569, 26918970, 55378403, 45351098, 55476389, 55509303, 55574761, 55607566, 55640097, 22462948, 55656933, 37929383, 55771322, 55803937, 55820289, 55836674, 55853092, 13303825, 55869495, 26296356, 55885880, 55902244, 55918676, 7340116, 55935462, 55968231, 56017090, 37847103, 56050152, 9945147, 56131600, 4636734, 56148298, 56180950, 56213522, 56229889, 688130, 56246331, 7258113, 56263145, 25493562, 56328210, 32391231, 56344769, 56377834, 56754668, 57000429, 57033198, 57065531, 57081872, 57098257, 57114660, 29507647, 57131202, 57163839, 57180193, 57196626, 57212986, 28000289, 57229717, 57278522, 29507620, 57295343, 4669503, 1654858, 57360880, 57410033, 57458724, 20267064, 44122113, 57475154, 57491485, 57507924, 57524225, 57212984, 57540664, 57557074, 57573573, 25493567, 57606177, 57622776, 57655538, 57688100, 31457528, 57704464, 57720834, 57737413, 57770040, 23986177, 57786866, 47808575, 57884757, 57917939, 57967092, 58049013, 58163702, 58212410, 49348667, 58228934, 58262007, 9666634, 58360014, 58409314, 1114616, 58458617, 36339748, 58491386, 58605902, 2376094, 58655227, 42828284, 58802447, 1785890, 12714000, 27181540, 41910439, 58835453, 58900990, 58949690, 20627474, 58966072, 58982416, 58998858, 7340106, 59015226, 59031582, 59047994, 59064377, 10715199, 59080734, 20594936, 59097599, 49823761, 3227666, 59146752, 59260946, 59277328, 22724641, 59293732, 1114213, 59310113, 59326546, 59342906, 59359499, 59392036, 59408632, 59441236, 59457552, 43417674, 59473956, 59490546, 59523131, 57114653, 59539492, 59556149, 59589121, 59736123, 23281680, 59752962, 59802115, 19398952, 59965956, 2375991, 60080645, 60145906, 1785912, 60178950, 60244487, 60342792, 32899131, 60391508, 60407809, 7880767, 60424503, 4669458, 60490249, 60555542, 60588554, 60653765, 60686829, 60719137, 18579457, 60736011, 60784869, 60817409, 5537876, 60834316, 60932510, 60981734, 25788479, 52036109, 19382355, 61014209, 61046801, 49381434, 61063252, 53100580, 61080078, 22937870, 19742778, 61161919, 61194767, 61325840, 61423674, 61440084, 61456468, 19546170, 61472986, 61521921, 61538517, 30572580, 61571601, 61685960, 61718529, 20840507, 30507041, 61735442, 25935908, 61849658, 61866043, 61882431, 33390593, 61899283, 61931537, 61947960, 61964474, 61997368, 62062826, 62128660, 62194197, 62226466, 62242852, 19185665, 62259524, 40288273, 62341121, 21217364, 62357533, 62373890, 62390503, 39632927, 62423098, 59179072, 62439506, 43384890, 62456342, 62570580, 53756437, 62587066, 62620183, 62668858, 62685243, 62701945, 62767640, 62980633, 63045648, 63062033, 63078436, 63095159, 63127724, 61063201, 56246306, 63160858, 14499856, 63226395, 63275009, 1114596, 60145720, 7618596, 63291932, 1114653, 63357329, 57311248, 63389725, 41910814, 63406328, 63438851, 63455248, 63471700, 63488016, 57557066, 63504927, 63684839, 27557904, 21217298, 63717920, 38715451, 36667395, 63782948, 57507842, 63799381, 63832067, 63848507, 63864890, 63881279, 63897663, 25477121, 63914020, 63930431, 63946785, 63963137, 63979522, 40353828, 63995988, 38535416, 14467088, 64012305, 64028730, 12386337, 64045087, 64061684, 64110786, 64143445, 57212931, 28459064, 64176186, 7340049, 64192528, 64208953, 56246308, 64225338, 26198313, 64242209, 64307746, 64389606, 64421972, 26198247, 64438305, 2376227, 64455204, 19185697, 64520228, 64537125, 64569361, 41943233, 64585972, 16482363, 4718609, 64635146, 7487524, 64667684, 64684117, 64717350, 64848423, 4669502, 64880656, 64897042, 64913437, 25428148, 64929808, 1114664, 23724049, 34209876, 64946729, 64979221, 65011729, 19546186, 65028258, 20430877, 38027322, 65060953, 65093716, 65110570, 19382719, 65142867, 39010388, 65176107, 65225212, 31014943, 65258028, 65454637, 4718674, 65503434, 29868088, 65536558, 65568826, 40370193, 65585234, 65601821, 2376239, 65634363, 65650704, 65667090, 65683492, 65700107, 65732644, 4685825, 65749552, 50348090, 26411009, 65798366, 57212991, 65831473, 65945601, 65962068, 65978397, 65994755, 66011403, 53100545, 66044466, 37847057, 66093072, 66109615, 66142677, 66175539, 66224692, 32522426, 26968146, 66273313, 66289878, 66322448, 65454083, 66338898, 66355258, 66371640, 32948225, 66388533, 66486486, 20971604, 66519284, 16515105, 22495633, 66568758, 66650580, 66699498, 66765367, 29491201, 66798136, 66830580, 22724682, 66879544, 66895905, 66912272, 1654786, 66928921, 29442084, 66961466, 51347540, 66978021, 67010577, 24018977, 67026980, 67043599, 20430906, 67076282, 67109411, 67174431, 67191009, 67224121, 67404346, 67453141, 67485697, 67502503, 7241729, 67534849, 23183391, 67551314, 12386340, 67568187, 67633153, 67650108, 67698724, 67715089, 67731489, 67748083, 67797303, 67862564, 67878975, 67895489, 1114407, 67928637, 67977252, 67993601, 68010017, 68026554, 68059440, 68108346, 68124689, 68141281, 48283681, 4800575, 68174398, 68239416, 30310482, 68255803, 68272164, 68288513, 68304929, 68321466, 47317567, 22347792, 68354292, 68403236, 68419586, 68435969, 68452385, 68468922, 68501885, 68567128, 25493520, 19382571, 68600384, 68632776, 68665921, 22544418, 68698143, 68714718, 68747842, 68796452, 68812831, 1114338, 68829586, 68878558, 69288516, 69484850, 69517413, 69550661, 69583249, 26198213, 69615810, 35913764, 30670864, 69648400, 38535438, 4669474, 69664990, 69698118, 69730887, 23281695, 69780040, 69861961, 69960266, 7405604, 69992719, 70025275, 70041601, 70058407, 70090949, 70123751, 70156875, 7929857, 39010362, 70221882, 29491233, 70238264, 21659710, 70255180, 70369499, 60260538, 70402637, 70467642, 70483969, 4866079, 70500942, 70615631, 59424784, 70680631, 70697153, 60817439, 70729729, 70746143, 5537876, 70762727, 70795327, 70811730, 16482320, 70828068, 70844432, 19791928, 70861392, 66912314, 70959188, 70975504, 70991946, 71008453, 71041617, 71123137, 71155770, 71172152, 71188513, 71205097, 71237716, 71254074, 71270433, 19382866, 71287073, 71319588, 38142010, 71336531, 71434641, 71467208, 688161, 71500043, 1114606, 71533140, 26984484, 71581927, 71615061, 71795286, 37027841, 71843922, 38141988, 40222721, 71860258, 38141953, 71876692, 71892993, 71909393, 18710613, 71926359, 72024664, 9093176, 72073217, 72089684, 20430864, 72106585, 72204689, 72237259, 17645627, 72270055, 72302901, 72335860, 24018999, 72417882, 71909460, 72483419, 72532026, 72548571, 72581153, 72598108, 30801953, 72647261, 72827176, 32784692, 72859864, 4718676, 72892578, 72925217, 14467130, 72942174, 72990968, 73023700, 73056315, 73072641, 73089057, 4685860, 73105570, 57196546, 73138783, 73187386, 45629457, 73203729, 3211265, 32620560, 73220407, 73285649, 7159871, 73302563, 73368160, 73450081, 73482549, 73515427, 73564770, 73761379, 7929872, 73859294, 73891925, 73924626, 7208978, 73941364, 44548183, 73973819, 73990756, 74039359, 38371361, 74055762, 74072065, 4653092, 74089061, 18759716, 74170982, 74219521, 74235963, 48775354, 20414465, 74252670, 19136571, 40173604, 74302055, 19399272, 74416161, 13156410, 41123841, 74433129, 22462749, 74498081, 74515050, 64978960, 74564203, 27886154, 74711660, 51183672, 40206352, 60260528, 74809965, 74874898, 18711150, 74891348, 9093242, 74908271, 37896274, 75005953, 75022369, 75038802, 75055162, 75071547, 75087873, 1654787, 75104514, 75137648, 75169968, 75202618, 75219018, 1785887, 75235624, 75268340, 75317279, 75333818, 75366823, 19579474, 75399252, 75415636, 75431972, 22347777, 75448945, 75563634, 75645002, 75661329, 75678323, 49332279, 75710851, 75743234, 20693028, 75760244, 44892325, 75923472, 26411025, 37503060, 75776018, 44662817, 75939869, 75956257, 75972690, 37224450, 75989621, 43795062, 42942538, 76120308, 20430932, 76169441, 76202042, 60145723, 76218999, 53477377, 76317304, 76382293, 76415609, 21659664, 76464372, 76513866, 5603329, 76546065, 76562468, 76578879, 19546145, 76595834, 76824660, 76841018, 9093150, 76857979, 76923173, 76956284, 77005130, 32948283, 77037627, 1114464, 77054145, 7487519, 77087357, 77120126, 77234815, 77283344, 22724670, 25002068, 77299934, 25493560, 77332536, 49676324, 77349357, 32948282, 77381665, 77398656, 18677776, 77480577, 5603362, 77529274, 77562498, 1114559, 77660520, 28623235, 77972100, 22134815, 32899073, 78135941, 78217684, 19136545, 41091108, 78267014, 78315790, 78348935, 78398088, 78430241, 7340033, 78446650, 78463246, 78495799, 78512187, 12255290, 78528998, 78561929, 1278015, 78627466, 78692378, 11812886, 78708799, 12091410, 78725202, 78742155, 78774334, 688212, 7487550, 4685888, 78790891, 78824076, 79020685, 79085604, 79101969, 79118369, 79134751, 79151159, 1785911, 79168142, 79233457, 79265851, 79282424, 79315412, 21446672, 79364546, 79446563, 79511966, 79560705, 1114767, 79577341, 65028097, 79609941, 79642877, 79676048, 15974416, 79741585, 2376338, 79839249, 1114519, 79855860, 79905117, 26296356, 79938195, 79986705, 4636746, 80003161, 80035903, 20856895, 80052884, 80134203, 80150529, 16515146, 80167573, 80216342, 80248896, 28524580, 80265486, 80298041, 33832961, 80314612, 80363813, 80396950, 80560791, 63897684, 80625727, 80642084, 80658449, 80675420, 80724001, 80740538, 75874388, 7241744, 80773784, 40501450, 80871479, 80887892, 80904755, 80953653, 80986145, 81002532, 81019184, 81068090, 36405265, 46252095, 81084432, 81100884, 58769466, 81117849, 67731473, 77266980, 81166369, 81182954, 81248273, 81264852, 81298074, 81330212, 81346561, 81362977, 81379514, 75661345, 81412763, 81461916, 81510644, 47153377, 81559783, 81592989, 81724062, 81772603, 81789159, 81821698, 81838081, 28000289, 81855135, 81903652, 81920063, 46252114, 81936821, 37912865, 81969169, 31703058, 81985623, 1114460, 82035360, 82133240, 25493535, 82165762, 82182707, 82231378, 82247709, 34127935, 47431711, 82264268, 39059492, 82296849, 82313611, 82346241, 82395138, 82411578, 82427937, 57376827, 76415009, 82444961, 1114786, 82674217, 82707107, 41992390, 82788648, 7880737, 82821452, 29868088, 82870948, 82936485, 82985638, 1114341, 83067366, 30162960, 83099733, 83132449, 83148986, 29098210, 83182247, 50708497, 83230906, 82788550, 83263784, 18743359, 83296449, 83329320, 83361825, 83378398, 1114354, 83410960, 83427391, 83443715, 83460178, 83476538, 83492923, 77414586, 83509928, 83558706, 83591226, 12664834, 83607740, 83656997, 83689808, 83722276, 83738914, 83771945, 83804357, 83837609, 83952298, 1654800, 84050380, 84082931, 84132058, 26181927, 84181028, 7487547, 84197377, 29868095, 84214443, 84295738, 84312123, 22937897, 75874307, 84328652, 84361450, 30081060, 84426807, 84443466, 84476588, 47497901, 84541471, 61112338, 84557883, 84574225, 84590650, 19742749, 84607198, 84639800, 84656161, 84672730, 84721719, 84738079, 84754465, 28295226, 84771127, 84837038, 85148293, 85229627, 22938023, 85246509, 7209042, 85295791, 85377108, 1261569, 85393444, 85409808, 85426193, 85442778, 29114724, 85492400, 85557490, 85590049, 4718625, 85607089, 27754513, 85688353, 14499841, 85705394, 85770429, 62685201, 85803067, 85819589, 85852707, 19530006, 85917755, 26673207, 85934098, 85950522, 85966907, 85983314, 49332282, 86000307, 86114706, 86163519, 7323681, 86179873, 1654843, 86196282, 86212625, 28524577, 86229047, 86245407, 7913531, 86261823, 86278177, 86295220, 86409233, 86426014, 86474836, 19235009, 86491329, 16482306, 86524597, 26296322, 86573278, 86605912, 86639286, 86704187, 4669498, 86721207, 86786359, 22462760, 86851825, 86884388, 47497576, 30572603, 86901432, 87048829, 87080976, 8601616, 22265938, 54984762, 39059514, 87097537, 87130129, 4636690, 87146532, 87162936, 87179300, 16515088, 87196188, 87261586, 87310637, 77004858, 87343505, 87376569, 87425246, 87457876, 87474212, 18759681, 87491258, 87588949, 87621981, 87654433, 87670820, 87687224, 87703774, 87736379, 87753010, 87785743, 87818939, 87868001, 87900404, 87949608, 87982780, 88064036, 88080400, 88096842, 88113211, 88129537, 88145923, 70991954, 88162363, 88178689, 88195075, 55640146, 88211704, 88244257, 88260796, 23969848, 88309963, 26705956, 88342544, 88358914, 88375997, 88424754, 88457300, 5537808, 7225346, 88473636, 88490477, 66371620, 88522837, 88555578, 21184529, 88572297, 88654526, 88719444, 88735745, 25722935, 88752831, 88801297, 88817720, 19267666, 1114408, 88834752, 26984484, 53346305, 88900289, 88998594, 37896209, 89047041, 70172689, 89064131, 89145530, 26181927, 89178820, 22724682, 35242017, 89293286, 89325812, 9093137, 89375429, 89489831, 89522517, 52544198, 89571550, 52494391, 89604807, 89669650, 89686250, 89752264, 89948873, 90046522, 90063144, 90095972, 7536699, 90128609, 24887327, 90161209, 90177537, 90193979, 90210322, 90227089, 90259653, 90292938, 90341573, 90374201, 90391110, 688185, 90424011, 90521857, 90570950, 1114370, 49905722, 73236565, 90604236, 90800845, 19382513, 90882064, 90898516, 18563156, 90914874, 90931284, 49053712, 90947782, 90981070, 47382591, 91046607, 43155512, 37913296, 91095761, 91275986, 33832996, 91341523, 91373601, 91389981, 1785887, 1114836, 91406337, 26296375, 91422784, 4685842, 91439829, 91488314, 91504656, 32899089, 91521690, 32620602, 22790330, 42303610, 91881498, 91897870, 91914298, 91930658, 7094306, 92357336, 92439257, 19383002, 92586507, 92635867, 92700905, 92734172, 8503355, 92848144, 37224465, 92865245, 92913665, 92930079, 688192, 92947166, 93110975, 1654839, 93159425, 4718654, 33832976, 93175824, 93192416, 93225695, 93257950, 93290529, 67059745, 93307616, 93438011, 69697831, 93455073, 93504226, 39649502, 93536995, 93683926, 93717034, 93749307, 93766372, 44433464, 93814977, 93848293, 94142717, 94175480, 26181855, 94208036, 66256955, 94224467, 94257154, 7208962, 94273552, 94289951, 35913745, 94306305, 19693905, 94323135, 22216952, 94355457, 94371896, 94388286, 23052594, 38748176, 94405350, 94470227, 94503655, 94535735, 94552250, 94584917, 5537850, 29098533, 67272763, 94618344, 94666753, 94683559, 94716098, 94748858, 94781499, 94797860, 78774273, 94814953, 95027946, 95076353, 95092738, 95109178, 46120977, 60096754, 95125520, 3211293, 95142302, 19644614, 95191787, 95240249, 95256634, 51347492, 95273708, 95322375, 95371605, 37896276, 95420503, 42942481, 95469599, 95485985, 25346130, 95502673, 95535853, 95600982, 95633530, 95650378, 25428154, 95683310, 95895953, 95929071, 96027376, 96076529, 1114519, 96125414, 96158052, 59064337, 96190878, 30245078, 96239909, 22724611, 96272466, 8011777, 96289522, 96387810, 59490393, 4669457, 7340033, 96420595, 96600820, 96699125, 96764662, 20430970, 96813278, 96845883, 96862282, 7209018, 96879263, 96928503, 78168123, 96994040, 97043007, 24527411, 97075961, 37847124, 97207034, 97288955, 73646202, 52740154, 97370305, 4669524, 97403644, 97649405, 66256952, 97698558, 97730615, 97747003, 97763329, 36323330, 97780479, 69681441, 97829632, 37847074, 97911133, 688183, 97944321, 98091222, 98124546, 19398882, 31392034, 98172945, 2376451, 98189787, 26411009, 98222266, 30162980, 98255613, 44302369, 98304033, 98320443, 98336804, 51200058, 54558736, 98353924, 98418864, 98451489, 98467899, 98484410, 98517765, 98583302, 98779911, 98877652, 98910405, 98943752, 99058051, 99090490, 1114325, 99106852, 54722577, 99123233, 99139642, 99156027, 7176208, 10977281, 99172706, 1114889, 38371402, 99222282, 99304127, 99352577, 99368989, 28327994, 12714020, 1114891, 9666618, 99386124, 70172728, 1114310, 20267067, 99451661, 38535934, 99500090, 1114894, 11026433, 99517092, 26279938, 19546114, 99582735, 99713808, 99745875, 99778594, 95518968, 99795729, 22134843, 99844112, 19267643, 37502994, 99860973, 99893441, 99926802, 100008531, 100107027, 100155662, 7258170, 100188711, 100220944, 100237346, 4800596, 7241764, 100254484, 100565481, 7880740, 100630721, 1114620, 56246306, 100663747, 40140816, 100696853, 100811221, 7159867, 100844310, 53903390, 100941860, 100958701, 100991767, 19349505, 101105959, 101138625, 101171218, 21577786, 101188376, 101237345, 101270297, 101434138, 41910450, 101482943, 101515348, 31703124, 101531651, 101548065, 101564588, 31392034, 101597495, 101663015, 39354594, 101695784, 52544097, 94584863, 53903442, 101728670, 45154362, 101777594, 21659732, 101810353, 101842980, 101859703, 101892891, 2376476, 102302494, 102465537, 19382604, 102482009, 5537793, 102515487, 102564640, 37896225, 102597409, 102728482, 102809793, 62242900, 102842455, 102892323, 102941476, 66256927, 103056165, 70811730, 103137473, 103170854, 103203623, 103268545, 1654802, 103301928, 103432593, 103465322, 20627473, 103497950, 103530618, 1114921, 103547132, 103580458, 13303892, 7536724, 103678763, 103792828, 103579732, 19383084, 103842605, 103907637, 103940407, 104005938, 44662801, 2375964, 104038648, 104071982, 76480570, 38223934, 104186349, 38371402, 104219439, 84787647, 104383280, 54607903, 104448085, 104480786, 21659649, 104497566, 61653666, 19267641, 104546552, 22462741, 104579889, 104743730, 1114390, 104809267, 19267617, 104874071, 104923195, 39649484, 104939576, 19579700, 10715199, 104956725, 7405626, 105038646, 18710795, 105104183, 105218872, 105317177, 27181249, 105366330, 105496931, 105562499, 26198264, 105595170, 54444859, 105628476, 105759549, 84754450, 105791758, 105824289, 105840725, 70434900, 105873482, 105889968, 4636705, 105923390, 105988927, 106053648, 7225401, 106070848, 1114309, 106152184, 30245416, 106185537, 106431298, 2376515, 106512402, 106528954, 106562212, 106627156, 66912314, 106643540, 106660676, 106693445, 104448193, 106725392, 106741817, 106758450, 106791100, 106840902, 106906439, 106938561, 7258148, 106971976, 107053244, 107103049, 51150906, 107152202, 7094328, 107184971, 107233466, 107266892, 107561044, 54509586, 107577838, 107610114, 19267639, 107627341, 107741185, 107757652, 107774798, 107806906, 107839902, 107889487, 1114949, 47202574, 107971049, 108036282, 108069278, 108118423, 108150970, 108183975, 108216406, 101793828, 108233552, 108281857, 76120148, 108298298, 1114409, 108314810, 108347423, 108363962, 1114487, 108397393, 108511265, 108527649, 1114298, 108544017, 5537850, 108560695, 108625979, 108642740, 65454136, 108675922, 108773672, 23314516, 108806995, 23266100, 109035602, 109051962, 109068372, 4637226, 47431696, 109085524, 109134677, 109215760, 109232429, 19742722, 18513951, 109265460, 30572577, 109314296, 1114382, 109347146, 109380438, 109494614, 37224532, 109527895, 109593432, 109675353, 109724002, 109772833, 109789598, 109838522, 109871962, 109953083, 109969424, 1261599, 109986401, 110019419, 110216028, 110264405, 110297302, 110329857, 110346276, 22724667, 110362917, 110395451, 110412637, 110461407, 110510942, 36471268, 16515073, 110575633, 42958906, 110592863, 110690462, 110723073, 110739515, 110756704, 60555573, 4636688, 110805857, 110838626, 19185727, 110887105, 110919739, 39354599, 110936931, 111002468, 111067153, 104480786, 111084389, 111263942, 1114513, 111296893, 26198275, 111362918, 111395009, 111428303, 19644609, 111477607, 7061505, 111510376, 111591764, 111657698, 111690129, 111722989, 64864272, 111755457, 111788905, 111838058, 111870007, 111886770, 111919538, 111951994, 111968292, 111984698, 30687316, 112411500, 112672853, 112706030, 112739181, 2375903, 58720315, 112853870, 112918714, 112951537, 28327994, 112984943, 37847098, 113050480, 113131816, 113165169, 113230065, 113262608, 113279039, 38748223, 113295393, 113311745, 7258196, 113329010, 80167105, 113492851, 113573972, 31916090, 20430907, 40075281, 1114294, 113591156, 113640309, 113722230, 113885201, 20430922, 113901626, 3211327, 113917988, 43828087, 113935224, 113984377, 114180254, 7225375, 114213754, 114262209, 114295655, 114327778, 16482335, 114360860, 114426045, 114458909, 114491408, 21512251, 114507792, 114163796, 26968065, 114524404, 5603386, 114573370, 21839946, 114589712, 114606164, 56672314, 114623027, 114672507, 114704415, 22560852, 114720954, 114753806, 114787024, 114819073, 114835487, 20856895, 3211327, 99533293, 114851925, 114885500, 114934653, 115098494, 4718655, 115130638, 115163219, 78201054, 115196799, 1114326, 115261525, 22970452, 115295104, 115508097, 115557250, 19546145, 1114739, 115605562, 2376474, 96206906, 115621905, 23052594, 115638305, 115654869, 115687672, 115720423, 112869378, 19808257, 115753859, 40075295, 116015554, 116097041, 97337418, 116114308, 116162869, 116195402, 73646202, 116211905, 116244539, 116261751, 116310050, 7258141, 116327301, 116392184, 116424935, 116457490, 20856850, 116474758, 116523911, 116588602, 25870394, 74416187, 10977316, 116605121, 116638600, 19267642, 116916675, 116949897, 33555338, 117031819, 117096648, 117130124, 117194810, 109199415, 110510273, 33554650, 117212045, 4636730, 117309633, 117343118, 4866049, 117392271, 117440528, 117456982, 39256122, 117474192, 117555233, 24805409, 117571643, 117588144, 85377106, 117620772, 117637178, 117653521, 24985618, 117670801, 117851026, 117899585, 117932266, 117997585, 118014867, 118063290, 118095873, 1277983, 41091103, 118113172, 118260629, 118325434, 118358205, 118391421, 118424470, 118456529, 20971538, 118505864, 118538602, 118571026, 7225402, 118588311, 118685713, 118702305, 104120337, 118735768, 12271698, 118915156, 118931514, 39355106, 72859707, 118948421, 5537851, 118980624, 5603390, 118997913, 119046486, 7077924, 119079249, 119112602, 119178139, 7487546, 119226369, 119242811, 26984449, 119260060, 119309213, 119604126, 52544104, 119734354, 94552250, 38944954, 119751583, 119799882, 119816223, 119832634, 4800571, 119849888, 119914581, 4456464, 119948193, 119996475, 19267602, 120012889, 120045569, 50938786, 120062883, 120193956, 120226036, 120275169, 1654846, 120307745, 21020756, 120324154, 5603329, 120340673, 120374181, 120423334, 120619943, 42303524, 120816552, 36339728, 23166977, 65028112, 120882089, 120996333, 108216351, 54558778, 121028624, 121045930, 121077776, 20430851, 121095083, 121192506, 12664891, 121209603, 121258924, 1115053, 38797345, 121340846, 121389112, 24805458, 121405471, 42942497, 121422072, 121454862, 121488303, 121569354, 121585882, 40059169, 121635760, 121717681, 121847809, 121864226, 43384848, 121880990, 121930077, 121963442, 1785938, 122011892, 65044498, 122061201, 122094515, 47317205, 122176436, 122504117, 25133092, 122585274, 122617874, 41959482, 122634858, 122684342, 122749741, 122814550, 122830906, 38141969, 9093122, 122848183, 122978705, 2376632, 123012025, 123076670, 7209023, 123093946, 123273402, 123306017, 24182817, 123322649, 26279969, 123355358, 47808568, 123388859, 123437092, 123453441, 47317206, 98796476, 123470012, 23314449, 123519164, 123568300, 123601853, 123682872, 37224530, 123699201, 104170343, 123716542, 26279953, 123764792, 57294849, 123782079, 123846878, 123879426, 123895994, 63275025, 18711488, 123928661, 123962305, 124027842, 124092449, 41911012, 124109763, 124207105, 52232209, 124223562, 124240836, 124289989, 39649547, 124339142, 124469742, 124502032, 12091408, 124519367, 124633310, 1114587, 124665942, 32571450, 124682525, 1114409, 124715025, 108216406, 124731450, 124747793, 23314463, 124764161, 29098281, 51511544, 124780730, 124814280, 51183690, 40452127, 124846111, 124862522, 124879817, 32112673, 124928970, 125273035, 7258170, 38797330, 125321274, 125337659, 7077924, 125354170, 125386826, 9945146, 125403835, 125453260, 125550678, 125567034, 27394300, 125584333, 78889082, 125682638, 69337105, 125960208, 125976790, 52232314, 126010319, 54968321, 31392002, 86491137, 126140417, 126156817, 18711079, 126173505, 126206165, 126238805, 126271551, 4636735, 126288168, 99976144, 126321617, 126436306, 126484538, 126500900, 4636674, 126518227, 126550510, 126583764, 126631953, 19382888, 126648337, 126665685, 126714838, 126780375, 126844945, 126861386, 32899154, 64569362, 126878680, 127009753, 38797370, 29081656, 127123642, 127156242, 127173594, 60030993, 127255515, 20627489, 127336641, 127369275, 74842316, 127386588, 110412765, 127451514, 127483920, 127500346, 7405601, 127517122, 127599582, 1114721, 127648735, 2376012, 78889082, 127730222, 127763424, 127811643, 38371390, 127828961, 127959382, 127991825, 37322768, 128008276, 118571071, 128024887, 128090113, 10977311, 128106580, 1115106, 128123189, 128156049, 128188452, 24182801, 128204801, 22446429, 128222179, 128254036, 128270420, 29491236, 128286942, 128320484, 128484325, 128549057, 19399654, 128581715, 128614756, 4800586, 128647204, 7077947, 25100321, 128664227, 128746471, 128812008, 96878815, 24985657, 128877545, 129057197, 129105976, 129122767, 40222751, 129171514, 7209019, 48857170, 129188842, 129319915, 43828204, 129369069, 129433658, 129450066, 29507642, 129466616, 129499165, 129515754, 129581089, 4800594, 129598446, 129647599, 129795056, 129860593, 129138704, 129908914, 129957889, 126271519, 129974626, 130024434, 130073587, 108937274, 130139124, 19808287, 52543981, 130203892, 130253461, 54509603, 130302354, 19267668, 130352117, 130417203, 130466806, 130531569, 130252856, 92881303, 130564802, 130614263, 119816210, 129581112, 130663416, 130744379, 130760920, 108855314, 130793694, 31342609, 130826781, 130859256, 130892793, 130990098, 51200001, 131006666, 131400699, 131514404, 131530941, 131564090, 131612735, 59031586, 131629119, 80412705, 131645496, 131662714, 131711012, 131727377, 69681429, 22462650, 131744764, 131826625, 114901185, 131891386, 131924989, 131956810, 131973179, 131989505, 20905987, 132006910, 132104768, 132136993, 132153402, 132169745, 28459025, 2228283, 132187135, 132285313, 132333874, 132366369, 12075009, 124682298, 132382783, 30670851, 26705976, 132400128, 132514817, 132580354, 59047952, 132628538, 21135444, 132645891, 38043706, 132792813, 19267668, 132826116, 132924169, 10715192, 102564315, 132956422, 132988986, 5603345, 133006341, 72859770, 133153798, 133497065, 133529857, 688190, 133579783, 133694015, 7176276, 72024372, 22446245, 133726568, 133776392, 133841099, 133873876, 133906680, 133939287, 133988677, 134021176, 100630590, 3211338, 134037735, 134070525, 134104073, 134201542, 132350015, 19235125, 134234397, 134266943, 39747641, 134283678, 134332475, 19579176, 134349031, 7225426, 134381653, 134414423, 134463825, 134496586, 134529218, 134562005, 134594748, 26968081, 134643715, 4669443, 134660154, 3211282, 134677514, 1654848, 134725790, 32342337, 134758648, 134791354, 94618635, 134823953, 26182069, 134841356, 135037965, 49823760, 99418170, 135087118, 135168085, 1115151, 135200826, 135217211, 67272759, 135233553, 135249994, 20267039, 135267344, 135447569, 129712490, 135479327, 135495738, 12386363, 135512295, 135545584, 135595026, 18711571, 135725130, 135742040, 135790676, 135807261, 135839938, 135872741, 135906324, 7176195, 135955477, 136019984, 1115158, 136037399, 54722593, 136086552, 136135651, 115212346, 136168473, 136380634, 136430537, 136478736, 136495163, 19546130, 136512538, 37371962, 136642561, 30146562, 136659995, 116457473, 136724698, 136774107, 136806456, 136822815, 136839252, 12156929, 136856361, 136888351, 21069883, 136905756, 137085981, 137117754, 137134082, 137150500, 137166911, 86179873, 137183290, 137199690, 137216059, 137232385, 137248786, 28196865, 137265185, 34127954, 137281749, 137314321, 137330746, 34127905, 137347073, 137363459, 137379922, 137396421, 137429164, 137461761, 21839906, 137478160, 137494602, 137511109, 137543922, 137576506, 137592865, 28000315, 137609588, 137642215, 82264148, 137674755, 65044562, 7077946, 137692190, 137740347, 137756705, 137773092, 137789503, 137805825, 137822225, 137838655, 40419410, 137856031, 138019276, 138052640, 27885782, 19644839, 74416210, 138118177, 78430267, 9666576, 138330421, 138363294, 138412525, 56672257, 19644602, 138445858, 138526805, 126746660, 138560547, 138608722, 138626084, 138723329, 138739741, 54445093, 138757158, 138969124, 138985556, 7209044, 139002919, 139084073, 109494346, 139116560, 139133050, 688250, 58785968, 139150376, 121077776, 139198538, 37502993, 139214849, 139231291, 29688873, 139247701, 139280870, 139313169, 139329845, 139362518, 139396138, 139526226, 139543595, 139641077, 3211322, 139691052, 139788690, 139837495, 87097402, 139854893, 139919417, 94584890, 132579803, 139935932, 139985185, 140018231, 140051502, 140132411, 49725499, 5537828, 140148929, 140182150, 140230870, 19267646, 30949450, 140263483, 140280879, 140313648, 140361994, 79511842, 140493873, 140755690, 140804282, 140837159, 140870503, 140902416, 20693023, 140918846, 140936242, 101564820, 141230264, 100237396, 141312083, 22970427, 141345016, 141378611, 1654839, 20594864, 141459704, 141492313, 141525049, 20430910, 90293300, 141541377, 133529656, 141558174, 141607315, 141640094, 141689109, 141721684, 31260858, 141738254, 141770945, 19235014, 141804597, 141869242, 20594771, 141901840, 141918683, 141951390, 142000212, 47858239, 142017590, 2376759, 142065953, 142099512, 142164026, 142181191, 142213136, 142229588, 142245904, 142262346, 142278692, 25870399, 103661924, 142295291, 142345273, 142475512, 142508516, 133726456, 142540986, 142574650, 142640187, 142771260, 142836797, 142901555, 104841249, 142951486, 143000639, 143082336, 143130890, 143164480, 143212853, 137838655, 49053754, 122765589, 143246401, 143393262, 58786215, 143425873, 143458741, 143491154, 143507514, 143523842, 33194925, 143540260, 143556625, 98631754, 143574082, 22511633, 143638586, 25346107, 126665013, 143656003, 143687856, 60817466, 143721540, 143769786, 143803461, 143900981, 1114822, 143934534, 144163911, 144228434, 144244922, 19235111, 7536724, 144277964, 144310335, 4718608, 144327752, 144392410, 117129388, 21577764, 144442441, 129532122, 144490980, 144523450, 144556049, 144572490, 20595172, 20856914, 144588986, 144621754, 144655434, 144818238, 144834618, 144850961, 144867391, 132366369, 54805061, 22938699, 144884812, 144983117, 145080336, 117653588, 22691876, 145097806, 1474602, 13303842, 145179727, 145228667, 145261648, 145309754, 145326294, 145359953, 145621452, 145654866, 50938472, 145768479, 145784895, 99368993, 96879344, 142737482, 145802323, 145883154, 145900481, 145965917, 31457589, 146015316, 146080853, 107806906, 146261078, 62619733, 146341971, 146374692, 146391057, 22462948, 146407480, 67059774, 146423890, 146440228, 60407867, 146456592, 146473019, 144359802, 146490455, 146587665, 146604106, 146620620, 146653185, 23724116, 146670052, 146702421, 44662786, 146735316, 20594773, 146768984, 146834521, 147030032, 20430903, 128647184, 147046894, 147080282, 147276891, 147308618, 35471418, 147326044, 147619860, 1114222, 147653283, 147734742, 147767355, 21233861, 13303837, 147783765, 12386320, 147816701, 147849252, 76578833, 25477156, 147866717, 147931329, 147964740, 147997790, 148062478, 148095785, 20267090, 148128863, 148176954, 148193494, 148226305, 148276320, 22462749, 148324540, 148374113, 148406872, 46448673, 148455506, 38043706, 148471865, 148489313, 1556506, 8011778, 148964451, 9945145, 149111908, 149161061, 149274641, 149291082, 48250962, 149308518, 47202574, 13156368, 149472359, 149701736, 149750889, 149832810, 41911403, 149881773, 26296404, 26427815, 9093175, 149913616, 104661049, 149931116, 150011921, 150028346, 22134858, 150045159, 150093882, 100155409, 150111341, 38223956, 150159446, 108937274, 29541373, 150176878, 39502387, 150373487, 150454992, 126500865, 150487252, 150519890, 22315009, 150537328, 20414500, 95518913, 150602865, 35766303, 45891620, 150635634, 150863934, 20595827, 150880442, 150914089, 150946932, 113181064, 150995137, 7323732, 151027898, 21659651, 151061621, 151127158, 151191674, 151208020, 9666591, 151224532, 151258231, 151291000, 151487609, 60097658, 151552204, 151585771, 151635067, 19235041, 151715856, 151732287, 55869503, 151749024, 151798908, 151929981, 151995518, 55869442, 152043814, 152109073, 38371346, 152125924, 152159179, 125812798, 152207762, 152256547, 48922655, 152273082, 152306815, 152453354, 152519808, 152568746, 24477780, 152601729, 152634498, 152682760, 152731665, 152748049, 152764474, 70696961, 40222778, 90931217, 152781080, 152240187, 152814723, 153042961, 119587370, 153060484, 1114284, 153175173, 153289862, 67272734, 38027265, 153322631, 1114572, 4653278, 153420936, 153518350, 2376012, 153339017, 153551129, 153584778, 153632826, 153649238, 153665572, 20430910, 153682113, 153715851, 2376844, 153797773, 153879694, 153977129, 154009618, 154026170, 20382005, 154059047, 47858071, 29065298, 154091521, 154107963, 12271634, 154124289, 154140731, 23314468, 154158223, 154453136, 154615824, 32620561, 24051748, 154633361, 135495681, 154682514, 31211579, 154813587, 92995643, 154976749, 31211642, 155009057, 7471162, 69681746, 21446657, 155025444, 155042964, 29868116, 155091333, 155172880, 155189971, 155222017, 36339729, 155238648, 10977282, 155272341, 37028054, 155353089, 29869206, 2376855, 155369488, 155386071, 155418709, 155451955, 155501720, 155616409, 155649178, 4866064, 155681079, 78201032, 155746378, 42303505, 20594965, 155762901, 155796321, 155828240, 19546143, 9093123, 7913473, 155845787, 155893796, 155910228, 78430292, 60096760, 155927708, 156189801, 20595124, 3211280, 156270836, 156319943, 19399012, 156370077, 156533900, 156582242, 156631098, 156647482, 94535683, 156663809, 58835214, 156680801, 156713018, 156729428, 97305508, 110346256, 156745744, 29507643, 156763294, 156827707, 156845215, 44302418, 156893432, 156926170, 22134817, 156976182, 157025418, 157074592, 157189281, 157253648, 1277981, 40075322, 29098181, 157270202, 119734275, 157303970, 157631651, 22134800, 157696082, 157712577, 157745211, 71450642, 27983931, 157762724, 157861029, 157942122, 157975051, 158024032, 158057638, 26968148, 29507600, 158203988, 2375767, 86163519, 158221479, 158318924, 98418874, 158367937, 4636701, 158401704, 19383038, 122077432, 158466542, 158498832, 87130196, 158515201, 1277969, 158531894, 60196009, 54607888, 7323681, 158581930, 1785918, 158662730, 20430930, 158679332, 158745771, 105579000, 38797396, 158794924, 158844077, 100073529, 158925998, 37224511, 159024303, 159253680, 159302833, 71548987, 159367226, 19644629, 60195483, 159383745, 39649365, 42156068, 33751042, 159416351, 108363834, 159433906, 1278011, 159547615, 39354689, 159596792, 20463925, 159630515, 159711333, 159744475, 159776853, 159810740, 70991889, 160006344, 160038996, 9093202, 160056501, 160105625, 160137275, 117948634, 101793850, 160154806, 116195345, 160269495, 160317498, 160333908, 30441556, 98320468, 1114549, 160350394, 41911480, 160383643, 160433337, 160497722, 18710787, 57098324, 160515258, 160793036, 160825588, 160874989, 160907517, 160940090, 92913665, 21839994, 160956969, 160990395, 36765781, 161038829, 17645585, 161072316, 161186144, 161218746, 161252541, 36765781, 161318078, 161366579, 161415359, 161480901, 76644474, 56279071, 161514687, 57294911, 161693781, 161726878, 64635170, 23986192, 161775647, 38535354, 161792036, 161808419, 88457247, 161825984, 161907905, 108724710, 162037991, 162070612, 61046868, 162088130, 22724639, 162136258, 162168849, 65044516, 22347834, 162185460, 162234427, 1114515, 78201325, 162251971, 162464284, 147243275, 162529893, 1114328, 162612052, 29671440, 162660661, 162694340, 2376437, 162807825, 1115333, 162824385, 18710884, 126665285, 162857513, 162889744, 14499899, 162907334, 163005498, 163071175, 163332571, 163364949, 163397665, 25346081, 163415240, 163479585, 22217151, 163497161, 163561531, 163577892, 163594298, 163610641, 19742738, 163627304, 163659964, 163709121, 163741872, 163775690, 163824568, 163873169, 87310391, 163905621, 163939531, 92701402, 164037836, 164086989, 1458184, 164298770, 4653078, 1064983, 65732641, 164692235, 27181293, 164725968, 164824273, 164937730, 164954128, 59277329, 164971730, 165085440, 165168339, 165233876, 32948255, 19529995, 165283029, 165479341, 165511224, 19546174, 85263061, 165527783, 165560605, 19530013, 165593697, 165626049, 139919419, 165658817, 165692630, 165740618, 165756986, 165773396, 126025730, 165789818, 21446672, 165807319, 165904618, 165971160, 166020313, 166102234, 166151387, 22954279, 166281409, 166314017, 166330384, 166346811, 25346050, 166363137, 166380611, 31703122, 166412370, 166428729, 67551233, 166445242, 166479068, 166544266, 140017850, 166593757, 166740168, 166772794, 5537825, 166790366, 167019743, 167068538, 32522426, 167118048, 167281889, 167411767, 167428426, 167461218, 1654842, 167511266, 127483937, 44105729, 167560419, 167608504, 167690709, 29688283, 167724260, 167788991, 167821314, 1115365, 167837909, 167871718, 167920871, 168067316, 168116307, 168149243, 168199400, 168280305, 168313325, 168346857, 168427970, 168510698, 42942546, 168641771, 168706084, 32899146, 168722846, 168772844, 71663674, 39355629, 1115374, 168870289, 168902842, 168936687, 169017588, 169067760, 169149681, 1114698, 41992678, 169214445, 167559916, 169247213, 169280754, 169608435, 169755892, 100073505, 12255268, 169805045, 169985270, 170016784, 20414521, 163022071, 170033374, 170066002, 170082363, 170098704, 158629919, 7405569, 170115361, 170147922, 170165496, 170263095, 20250653, 170295297, 24805438, 39650553, 104480826, 170312140, 20594864, 170344634, 170377617, 96927761, 170410177, 170444026, 1114693, 170575099, 19235086, 7290896, 170656307, 110527091, 170706029, 170755324, 170852410, 26624401, 170870013, 170934273, 170951322, 170983441, 171000074, 171032635, 171049018, 22315039, 171065573, 171099390, 171148543, 103645266, 171229860, 41911552, 171295122, 60195061, 171344065, 171377921, 171459842, 171524282, 42942522, 171558147, 163528880, 136839226, 171688124, 171737089, 39354777, 171754756, 171820293, 20856891, 171884607, 171900984, 171917394, 85983249, 171934005, 171966500, 171983317, 172015838, 1115265, 172049670, 33734951, 172113937, 30146634, 172130682, 172163108, 18711012, 172180347, 172212240, 21659722, 172228794, 132923701, 172261393, 158908645, 172279047, 172507807, 172557576, 172654778, 53412063, 172687669, 172720520, 172754185, 172852490, 172934411, 172999948, 94044219, 173080842, 173113346, 173129729, 163610660, 173147405, 173196558, 173309968, 173326395, 144359628, 40222736, 19252039, 173344015, 173425936, 173506561, 22724671, 173524241, 173589778, 173703387, 52543674, 173736148, 1114298, 173769188, 173802771, 173851924, 96878924, 173932748, 80363768, 173965805, 173999381, 23379969, 174048534, 174145780, 174195991, 174358920, 174392002, 174441752, 174489955, 174556015, 174620731, 174637138, 12075024, 174654276, 174686683, 174720281, 174851354, 18710805, 19267600, 174915601, 105579050, 174932993, 174997589, 26444450, 175031579, 19531036, 175080733, 175227157, 175259730, 99401786, 22937786, 7176209, 146768893, 175277342, 175390997, 175424051, 175472657, 175490077, 175522081, 126861386, 79315507, 175554890, 175587345, 44302354, 23281722, 175605023, 175948171, 175981856, 176177153, 176193599, 94388257, 176210190, 88457232, 176243018, 165609505, 176275649, 47858338, 176308442, 176358689, 37371920, 176455769, 22724667, 176489762, 176636327, 76054545, 176668730, 176685776, 176718017, 119062614, 176751907, 7241786, 176947871, 176996658, 1114421, 177029178, 177045563, 7471166, 106102846, 177063204, 177127650, 177160276, 177176592, 59064406, 177194277, 177242196, 31260730, 177259814, 177307918, 177340498, 177356858, 177373185, 25100347, 141607141, 175227080, 177390887, 177472808, 177636649, 177684516, 177701458, 7225373, 177734954, 23052485, 177800145, 177914864, 177979409, 26198423, 107823301, 177996329, 178028730, 178062635, 178110497, 178126884, 153665592, 178144556, 93471167, 178259245, 38748219, 178308398, 178389185, 178421974, 178455855, 178537776, 178651218, 178668819, 178716673, 40829379, 178733140, 178749524, 178766017, 178799076, 178831553, 178865457, 178962490, 163397633, 178980146, 30245078, 156582197, 179126357, 58294308, 179159330, 114327637, 179192075, 179225907, 179257575, 179290171, 4653115, 179307828, 179536065, 179569973, 179650591, 98336852, 53887822, 179667535, 179732538, 38371387, 179750198, 12271678, 179830858, 34308154, 179847354, 179881271, 179962483, 179994920, 180027578, 180060245, 180094264, 180225337, 180290874, 71450657, 180338773, 29098348, 180371644, 135545729, 180421947, 180667074, 180716136, 180749628, 108527700, 180846625, 99254460, 180862977, 18579515, 180879761, 180912774, 47628346, 138068026, 180962621, 181028158, 181125202, 64471098, 181142847, 76120122, 181192000, 23216212, 181290305, 181436686, 181470530, 181551837, 181601386, 31342650, 181649493, 181682210, 95780900, 181698935, 181731345, 7536670, 182093124, 182191429, 182256966, 182321715, 9666596, 182371655, 182501562, 182534228, 182550612, 23314433, 182566970, 24182843, 19399330, 182584648, 182747537, 182781054, 87097380, 182829141, 182861908, 182878479, 40206337, 182912329, 183124182, 8011834, 40665104, 183158090, 108855314, 183238904, 183272564, 7209149, 183320593, 183337034, 32899154, 183353588, 2377035, 183402497, 124469544, 183419073, 183452142, 183484417, 7405652, 183502156, 183551309, 183764302, 183813455, 28754142, 183895376, 183976122, 55623884, 20463925, 184008721, 73532613, 184025319, 184058042, 184090796, 184123450, 184139812, 132644927, 184156357, 184190289, 184320082, 184336635, 184385938, 184435942, 184485202, 184583235, 184614970, 184631313, 78004257, 184647696, 184664127, 14467105, 184681601, 184713864, 184746186, 184780115, 184829268, 184959324, 113459216, 184993109, 185057297, 42942538, 185073917, 185107798, 185156951, 185287659, 185336769, 55623975, 185401558, 185435480, 1114504, 41058362, 185532620, 185566553, 185630737, 19742794, 185647163, 185663736, 9093194, 132464657, 185697024, 130990113, 185779546, 1114521, 185975472, 159154235, 186040669, 186073089, 174637073, 186090843, 186139996, 186384632, 9666617, 186417782, 113426490, 186450891, 186500445, 124371003, 138182715, 186581050, 22691841, 186597762, 186663319, 186695856, 153321528, 186729822, 4718621, 186794020, 186810401, 24805409, 186826788, 186843153, 72859710, 110510273, 29687932, 186860147, 19399394, 186893663, 187008352, 39714900, 187072513, 187088955, 110756059, 84132026, 187106657, 22347812, 74416161, 187221346, 187368005, 19235009, 187401571, 7225530, 187516260, 187598071, 187645968, 20430905, 35782738, 1114325, 187663717, 187744819, 187793466, 187810193, 187842562, 158384186, 187859132, 187908097, 22446350, 187924832, 187957496, 42303544, 187991398, 188335463, 188417207, 188466536, 188532073, 22446818, 188596710, 21839954, 188630378, 188712299, 96272466, 188761052, 29688283, 188826988, 188891167, 188908909, 188940347, 188956673, 188973268, 189007214, 189203099, 189252975, 27181314, 189349947, 12091393, 189366465, 1114301, 189399041, 189415483, 87836016, 189433201, 189497402, 7356452, 189514037, 189546497, 19235165, 189563188, 189613426, 189727539, 189792670, 189842803, 47530178, 189890753, 1115053, 189923778, 190006644, 190072181, 190103848, 190136394, 190152866, 190185665, 124469544, 190218546, 52543664, 190252406, 190399863, 190498168, 183337034, 5537823, 190546106, 47858246, 190580089, 190644809, 190742996, 190793082, 26198306, 190891387, 158663063, 163021284, 190940540, 30245212, 191006073, 191071613, 191217681, 188727359, 95780893, 191235454, 191447227, 191496193, 127025208, 191512957, 191578693, 191611507, 191645055, 18661431, 191710592, 22463462, 191858049, 191955443, 192004132, 120291391, 192021556, 1114392, 192069690, 192086079, 192102463, 192120194, 192184321, 4718647, 192200720, 39976962, 192217274, 192249941, 158056449, 192284035, 192365956, 192430389, 4718651, 192463702, 192577538, 192594113, 59818279, 192628101, 192693494, 192726406, 192906631, 192970834, 192988552, 193036347, 193053412, 193101907, 153273435, 193135039, 193168339, 193200321, 193234313, 193429799, 193463690, 193609729, 193626698, 193658911, 688131, 193675464, 193708341, 45547594, 193741031, 193773624, 7061520, 193790037, 193823195, 193856058, 193904940, 193955211, 194037132, 29098269, 194086285, 194217018, 32620545, 194282894, 194593013, 53903423, 194643156, 194691288, 55853072, 194724074, 7176249, 194789839, 194838611, 194871359, 24985663, 194887696, 194904082, 21266463, 194921871, 135561565, 26198500, 195018785, 195035137, 195051537, 99368978, 10977338, 195067905, 195084305, 128270369, 195445363, 195477589, 195510356, 195526715, 5603385, 37502978, 195954066, 196117907, 72925268, 196198622, 196231384, 196265285, 196329546, 196345914, 196362303, 113295393, 196378683, 11239460, 196396436, 26280020, 196444449, 196477329, 196510436, 196559115, 196591674, 41911307, 196608290, 196641686, 196673847, 196739286, 196773269, 26181880, 196806038, 4718648, 196952286, 196986010, 98418919, 197017848, 42303521, 13303867, 197050831, 22397351, 197099536, 155811843, 197115907, 45891666, 197133719, 197296214, 117932218, 197313075, 30245265, 197361858, 197394594, 197428632, 197492820, 27590657, 197510100, 197559705, 197607610, 197640385, 197673044, 197689428, 87130113, 197706024, 197739930, 1359886, 197804065, 44122170, 197821851, 197871004, 197935121, 197951681, 197985693, 2376437, 37503012, 198051230, 198148497, 27181297, 198180880, 19235010, 198198687, 198312020, 198328551, 71926720, 18710717, 198361346, 19579066, 198394218, 198426821, 198459685, 7159882, 198492218, 198509782, 198559136, 198903201, 199016507, 199033020, 199082228, 199132347, 22134802, 199181730, 199246782, 24182801, 99811386, 199295462, 199327833, 199360545, 199377084, 20627489, 199427491, 65749051, 199492254, 199542180, 9093179, 19267601, 199607717, 199688210, 199704769, 199737828, 7340094, 163479568, 199771558, 199934165, 199966913, 4800545, 199999505, 200016076, 200050087, 200097851, 200114193, 4669476, 200130853, 51200031, 200163504, 200196258, 200228922, 34127889, 20414500, 200246696, 200377769, 26296402, 200492458, 150290657, 41911723, 200607148, 200704447, 133890642, 200736851, 200769826, 181338197, 200803757, 1654785, 97304802, 200851811, 200917157, 22724644, 151715876, 200951021, 200982839, 201048532, 201098206, 201146769, 22528003, 201179384, 37896210, 201213358, 201426351, 201475504, 201572556, 61718586, 37945614, 19235324, 201605584, 98336827, 201720022, 201753128, 159154194, 201785655, 201851316, 19267658, 201883664, 87130171, 201901489, 201999794, 152666167, 202113272, 171394483, 59819444, 202147253, 202212031, 202260894, 60080934, 202309820, 202360090, 202424986, 202457336, 202491318, 202539130, 74399828, 202556855, 202703960, 104005706, 202768577, 202801236, 54722644, 202817594, 202834004, 19546196, 202851768, 202932260, 202948821, 202982841, 203048378, 12386340, 203096439, 40124498, 203130299, 203425212, 203539131, 41911741, 203587777, 203621822, 203670975, 203751638, 203784547, 203849972, 203899189, 54215018, 203933120, 204047809, 204096509, 204128749, 204161265, 204193851, 37896228, 204211650, 204308721, 204341460, 204374209, 204408259, 204488740, 204505475, 204539332, 204637637, 204767418, 204800533, 13107216, 204832769, 204800084, 204850630, 26198101, 103579649, 204947906, 205030855, 22937878, 205096392, 20627540, 205178313, 205225985, 205242452, 205259058, 205291554, 205308152, 205342154, 20430884, 205390090, 205424075, 205505005, 205538764, 205685250, 205733904, 14499871, 205751757, 205799725, 205832524, 205881588, 205930689, 205963480, 205997518, 206045185, 206061631, 25346111, 61046815, 206079439, 206276048, 206373064, 206405716, 21200932, 206423174, 206456273, 22446165, 206586218, 79429719, 41992459, 206618660, 23724050, 206635239, 206669266, 206749780, 206766081, 54247483, 206782712, 206815633, 206848306, 206881143, 206913857, 206946548, 26428883, 206996948, 207093796, 207110145, 24051729, 207126562, 37355751, 207143735, 207258552, 207306836, 207323223, 207372597, 207405457, 207437880, 207454856, 207487012, 72089617, 207503447, 29868091, 207554005, 207601975, 18759739, 46120993, 207668694, 25034786, 207734231, 207814692, 58769425, 207831247, 25346065, 207864052, 207914456, 207962143, 7159844, 208373210, 208519361, 208553435, 208584805, 208617508, 37027856, 208635109, 208668124, 40665144, 208732353, 208765397, 29868106, 208798058, 208831287, 208946653, 209011097, 209043485, 209059858, 13271099, 209076529, 37896251, 7487572, 209142030, 21659704, 209174586, 7618593, 209192213, 209240290, 209272868, 1785874, 209289505, 209322000, 209339870, 209372639, 209535643, 37322810, 209584448, 42909780, 20430849, 209651168, 209780929, 63275092, 209815009, 209862657, 209879103, 209895480, 97075231, 209913314, 209978851, 10813482, 210174222, 210207357, 210239981, 210272319, 90210367, 56131585, 210289202, 210337876, 1114605, 210355684, 19399063, 210518317, 26296378, 210551032, 210583585, 38027295, 4718714, 210600372, 37027924, 26296337, 2377189, 19382501, 210632866, 210665473, 210681912, 25346110, 210698322, 12075039, 210716134, 210896359, 210945512, 40501538, 211027433, 1114197, 19579373, 211109354, 211206738, 211240427, 211321085, 211355116, 211419811, 211501531, 211535341, 211616672, 44860603, 211681755, 211715566, 211763393, 211797487, 1114390, 211845402, 211961328, 68337700, 212026865, 212157938, 212222200, 212254779, 50364501, 93159460, 212272627, 212436468, 18759697, 19235029, 212566033, 212583925, 23052519, 212631569, 41911798, 212648489, 7471167, 7225428, 212681379, 212764151, 212828193, 212845979, 19742779, 50970682, 212893885, 212926754, 212959290, 14467101, 7487504, 212976308, 213091832, 213206521, 213254622, 82264095, 213288442, 99254686, 213435899, 1114392, 213614815, 22397290, 92881400, 213663745, 48922708, 45547602, 213680129, 28409940, 213698044, 146047696, 213795019, 213827666, 12156986, 39567396, 213845501, 1114464, 213909697, 213942331, 144080899, 213958673, 118571071, 213976574, 91521098, 214156799, 60096728, 214253819, 214302964, 214352116, 214401041, 214418944, 214468097, 214532155, 214548481, 213565443, 214564881, 43434290, 214581434, 214614632, 214647235, 214679610, 76120122, 214697474, 214761873, 214794484, 214844891, 214877640, 214958481, 214991092, 109101457, 215041539, 215320068, 215498836, 215515137, 53100561, 26183173, 215531579, 215547905, 49348625, 215565830, 4669526, 4669503, 215613527, 215662608, 215679035, 7094275, 215696903, 215793852, 215843178, 44351664, 215877128, 39650057, 215991817, 216056561, 216105374, 50938126, 216155658, 22397224, 216268836, 42909755, 216285688, 216318010, 26198690, 36765882, 216334580, 216383547, 18727420, 216401419, 216516108, 216597179, 61112337, 216646186, 216678459, 19546113, 216694871, 1114706, 198967313, 216745485, 216843790, 216908138, 216940577, 26181825, 216958479, 19546141, 37322810, 217055235, 37224506, 217071633, 94994788, 217088058, 160759894, 217105936, 217237009, 217284625, 23265528, 217301406, 37896487, 217351605, 217415764, 217433506, 217497617, 26181863, 1115666, 217514040, 217530424, 26279972, 217546945, 44302420, 217581073, 217628756, 217645092, 21168376, 217661933, 217694622, 4669470, 1115333, 217743418, 217761299, 217825550, 217858122, 9945119, 217875392, 217907384, 217990619, 218021976, 218054673, 218071056, 9093206, 218087653, 218120276, 218137062, 218170900, 218449429, 218564118, 218611741, 218628159, 28000258, 218644482, 7176247, 218662423, 122732545, 36290577, 218759242, 218777112, 218825576, 218906625, 218923067, 26444007, 218939481, 218972382, 25870392, 219004961, 219021371, 219037712, 219054083, 83492946, 219070480, 28000273, 219087010, 219121177, 214909126, 219185466, 219234320, 219250747, 219267269, 219301402, 219350555, 219430929, 2377205, 219447332, 219463764, 219480148, 28327937, 219496449, 219512863, 24805460, 219529447, 219562391, 219596316, 219725883, 219743773, 219792725, 219857900, 219906065, 19529921, 219922516, 219938852, 205242424, 219955217, 219971998, 220020922, 220053708, 220086361, 108070430, 220119271, 116359643, 220153375, 220184612, 133709908, 220202528, 220299281, 9093178, 220315896, 220349985, 220479577, 220513281, 196395092, 220578253, 220610642, 220628514, 220694051, 220775596, 220823674, 119881812, 220841508, 2376019, 220905959, 220954683, 220971044, 4800546, 220987408, 221004113, 32948225, 221038117, 221120038, 14450705, 221201959, 49348609, 221298746, 221315131, 2377081, 221331870, 58818594, 221380609, 50071080, 221398569, 221528082, 221544634, 221577617, 221611562, 221676466, 87836203, 117555259, 221725556, 221773906, 221790224, 221806862, 221840940, 221972013, 29114616, 61046815, 222037550, 122617915, 35848209, 222101563, 222117954, 222150664, 222167048, 4653115, 222185007, 222249016, 222265400, 222283312, 222316080, 56672274, 222348849, 222658758, 222691521, 222724185, 45154388, 222758450, 222822562, 209567803, 222855570, 222904404, 222922291, 223003017, 223084814, 20267041, 223117543, 91293236, 223150081, 5603412, 223166500, 223182906, 59064404, 223199316, 223215652, 223232017, 19742794, 19383101, 223249656, 37929312, 223348277, 223428667, 223445214, 223477793, 223494332, 89064018, 27181997, 223543329, 223559924, 223609121, 2376094, 223643190, 223739905, 39780564, 181503543, 223757880, 80412756, 96125224, 59277313, 223903747, 34160722, 46448722, 223920212, 223936734, 50364638, 223969338, 223985858, 224018625, 224052793, 224149562, 7159838, 224165944, 74268730, 224182719, 224215099, 224231908, 101875910, 224264460, 224362766, 224395453, 224429626, 224493604, 27754554, 224509954, 145784890, 75268289, 36503611, 19398872, 224527498, 76922969, 224577083, 224788482, 55263268, 224804949, 16515131, 98927533, 224839228, 224919765, 224952404, 29491284, 224969064, 25133114, 225017872, 225034257, 225050821, 22397117, 225083585, 225117466, 225181880, 45351106, 37929154, 225264102, 29098280, 225297981, 225345572, 225361937, 225379800, 225427578, 225444037, 225478206, 31866935, 225525761, 225542177, 163741882, 225560024, 13287441, 225608173, 225640759, 225706001, 122617889, 225723967, 23560473, 225773120, 225853499, 37912762, 225871425, 212746256, 225919390, 22134842, 222724097, 225968321, 226000955, 4390933, 226197521, 7258114, 226557968, 54215142, 226575940, 108069108, 226641477, 226722239, 40091679, 226754645, 42942523, 226787572, 226838086, 226885818, 226918805, 226967900, 35487780, 227001927, 227099108, 187645953, 227133000, 30162945, 41992676, 104185939, 227198537, 227328222, 227362378, 227393552, 227409951, 1114479, 11026448, 227427557, 227459258, 227491857, 227508840, 180027578, 227540995, 227557605, 227591755, 227705325, 227737659, 227754241, 227803358, 227836181, 20152321, 227868858, 41894153, 19235112, 96862292, 227901723, 227952204, 64782410, 228081853, 228114449, 228130879, 228147282, 121077796, 41779422, 228165197, 228327709, 228361806, 121864276, 228427343, 228491348, 99368996, 53346362, 205963612, 228509264, 33554692, 228557412, 228606300, 18563134, 228640337, 1114460, 228721226, 228753675, 228787794, 14467131, 228966458, 228982868, 44302337, 228999382, 19252243, 229031952, 167084049, 1114513, 229049939, 228704831, 229115476, 229343627, 229376033, 229392442, 229408827, 229426773, 229474679, 229507088, 229523473, 229540149, 229572666, 114901586, 20267024, 229589008, 229605393, 9945105, 124895499, 229623382, 229687297, 189349972, 229705303, 229754456, 229802683, 21659703, 229852761, 60178626, 229967450, 26968080, 230065755, 230129973, 230164060, 230228001, 61456417, 230244586, 32686116, 230310289, 230342842, 230375441, 47628321, 230391996, 230442589, 230506689, 230540894, 230639199, 230720352, 230768826, 22937825, 230801652, 230850837, 27181365, 230883522, 230917728, 231030858, 47628305, 231047409, 231080499, 231130252, 231179873, 231245410, 32948306, 231374970, 231391290, 43384863, 7536696, 231407652, 231424017, 231440420, 73072703, 231457079, 231522363, 231538846, 1114401, 231572635, 231621378, 231671395, 231999076, 173850682, 232064613, 232195369, 232243217, 4669469, 232261222, 232424176, 35241985, 147832848, 37847058, 232474215, 232605288, 232654441, 232751293, 232783930, 232800257, 232816996, 232851050, 232916587, 233013279, 233029691, 99369034, 233046906, 233095361, 37847095, 233129580, 233193658, 136610070, 233226471, 233259250, 233291777, 22904916, 233308193, 216956987, 233325383, 233358957, 233457262, 233506415, 233554413, 233586745, 233603258, 233637488, 20840450, 24560109, 233801329, 141378117, 18759709, 233882014, 233932402, 7487517, 53903422, 233980101, 234012981, 1114958, 234045524, 1114605, 234063475, 234209283, 92995642, 14467127, 234226078, 234275146, 234309236, 234391157, 234455335, 234489462, 234536991, 49528865, 234553568, 234587767, 234652116, 4636708, 234702456, 234766524, 234815889, 71450682, 234849913, 234946561, 234963000, 20627512, 234979637, 114196496, 235013754, 235340108, 235390587, 235438324, 19562498, 95404116, 235488316, 235553237, 19628048, 235585722, 235619964, 53903378, 235733262, 235767421, 140083282, 21184548, 7356474, 235865726, 235947647, 7209082, 235996800, 7340091, 236044319, 236060673, 54509650, 236078721, 236192191, 236225106, 1114288, 236258946, 39813156, 236388368, 35913744, 236404922, 236437562, 236454242, 27181285, 236504707, 69681404, 236585256, 236619396, 236683596, 236732645, 236765201, 53903394, 236783237, 236930694, 50282578, 236978177, 236994616, 237011026, 48332874, 237027514, 7258128, 237060112, 237076542, 237092880, 7208962, 22398599, 237110920, 237191257, 237224897, 52037257, 237289503, 102416420, 237307530, 237371867, 237404163, 237420603, 237436929, 209174584, 237454987, 237551632, 29507668, 104170005, 237569676, 27967570, 237635213, 237699368, 237732033, 237764824, 143523899, 237797435, 41992459, 237814387, 237846776, 237879548, 237912093, 37224466, 237928661, 237961447, 50938075, 237994106, 238010384, 24969275, 238027045, 238059578, 238075988, 238092373, 238125113, 238141476, 4685882, 238159502, 238454057, 238501972, 238518273, 238534689, 33751098, 238551701, 183943434, 238600209, 238616577, 10715192, 238633178, 238682128, 238698513, 106315794, 238715066, 238749327, 238796833, 238813242, 212746299, 238829585, 238846010, 238862399, 238878753, 238895162, 115081275, 238913168, 238944442, 26280010, 238977266, 239009874, 9093136, 239026739, 18710726, 239077009, 239157811, 19726631, 239206433, 31014948, 64012319, 239224466, 239272194, 239306387, 165232722, 21839889, 239370241, 238141457, 239386843, 239419973, 239453844, 239550731, 20856868, 239583499, 239617685, 239747350, 239781526, 27181256, 239944694, 240010903, 240092824, 240173073, 240190606, 1654817, 240287988, 240337082, 18513978, 240369665, 240386344, 240420505, 240486042, 240533626, 240549946, 49709115, 240566853, 240599098, 240615483, 240631987, 240664577, 13303870, 240682651, 240730300, 55132593, 240779327, 240795664, 22724610, 240812263, 240845042, 240877809, 99368993, 70959188, 132464673, 240910399, 240926776, 7471140, 240943167, 240959974, 181878785, 240993948, 162660629, 241205303, 189973011, 241221694, 241239709, 241352778, 21086430, 241370782, 241468173, 241518239, 7094273, 241550115, 241598546, 241616544, 78774356, 241698465, 18563075, 241745937, 241762320, 241778745, 20693050, 241796770, 242075299, 242171935, 51200059, 54722563, 242189988, 242221092, 242237456, 9093204, 46448724, 242255525, 39059487, 242303229, 132464699, 242337446, 242417748, 242434241, 242468017, 242532411, 242548794, 242565204, 24182868, 47235130, 242581752, 242614486, 242647981, 132908711, 242681512, 242746235, 242778143, 242794554, 19136586, 242812585, 242861012, 242910272, 45629524, 225558586, 242958337, 242974779, 242991588, 59277345, 243024578, 184664081, 29245495, 243073212, 243123882, 243187898, 243222187, 243336876, 243384337, 55066641, 19185697, 243402413, 243482683, 50348090, 243499541, 243531777, 243549155, 4718623, 243582638, 243631791, 243697328, 243761236, 217645140, 243777552, 98336785, 243793956, 199245825, 243812017, 243941823, 243975858, 244025011, 244088865, 244106932, 244170753, 244187559, 244220092, 244270773, 244334628, 35127359, 244351034, 23150651, 244367393, 244383745, 244400145, 244416724, 244449496, 244483766, 198000724, 244565687, 26918928, 244613137, 244629560, 46448676, 244647608, 244744250, 72089659, 198082845, 244760635, 43925535, 244778681, 244876016, 244926113, 244973626, 244989969, 245006515, 40419410, 245040546, 27181365, 245104641, 71548946, 245122746, 245155515, 245219590, 245252184, 38797375, 245286588, 245335741, 245399745, 203849921, 245548735, 245858502, 245891320, 245924477, 7176221, 245958336, 245991105, 49053754, 246056642, 40091664, 246105795, 246169616, 31211549, 246187716, 246268189, 24805435, 246300890, 42123458, 246351557, 98550045, 246431802, 98254911, 246449862, 246499015, 246630088, 43795041, 246695625, 19579590, 246808773, 50512044, 246841700, 246874341, 31457473, 246908618, 246956091, 246972432, 246988802, 247005213, 7340088, 247021945, 247088843, 247185594, 247219916, 247334605, 247382213, 247414956, 247447568, 247463952, 20430850, 247480353, 247496762, 247513147, 2129975, 247529901, 247578810, 247611940, 247678670, 155518671, 247758907, 247775248, 247791619, 247808082, 247824400, 59179063, 247840954, 108724699, 247873754, 247924432, 248055505, 248119933, 32686139, 248152080, 153665552, 248168464, 18513922, 248185080, 248217616, 132694032, 248234000, 31457550, 248252114, 248514259, 248579796, 248660191, 248709156, 36405265, 248726358, 248840385, 248873498, 248938558, 248955281, 19579610, 248989397, 26951696, 249021122, 249070451, 249151505, 249169622, 19382835, 249233626, 30572561, 249282748, 249331729, 18710753, 249349847, 249397441, 240926737, 249430033, 249446588, 113672277, 249497304, 249544705, 43352097, 249561124, 49348692, 4636675, 249579225, 86556701, 19234905, 249643194, 249676178, 116621498, 249726682, 249921996, 249956059, 250020034, 250052641, 250069732, 250118953, 250152668, 49348644, 250234589, 29099742, 250283743, 250396754, 112771130, 103661834, 250414816, 250511630, 155009027, 250544145, 250560586, 250576959, 75743234, 250593336, 250610268, 250659300, 250691601, 250708023, 21839930, 250724408, 26705921, 250741358, 250775265, 250822900, 250871890, 29507585, 250888208, 26968123, 250906338, 24985662, 251199543, 251215930, 24985659, 251232318, 251250403, 251330791, 251364198, 183484474, 251396503, 251428927, 119816225, 251447012, 251510801, 175227073, 251527531, 251560180, 39010321, 251610853, 251692774, 251823847, 251887834, 251936993, 251971304, 252036841, 252133458, 7864378, 252150313, 252182714, 252215628, 252264532, 189349946, 252282602, 252364523, 252444708, 28000312, 252462828, 252643053, 252707645, 252740186, 42942527, 252806058, 252838085, 90227089, 252872430, 168116509, 252920763, 252969444, 253003503, 253083706, 253100211, 253132922, 253149201, 5603331, 253167344, 253198362, 11026467, 253642482, 253788242, 253806323, 253870298, 19579105, 253919265, 253937396, 21839903, 254019317, 254099457, 133709860, 254116133, 254150390, 254314231, 254361659, 254378015, 207962167, 254394723, 254459962, 254478072, 254525737, 254558497, 254591034, 56164622, 26935529, 100630529, 147030074, 254609145, 254707450, 254754832, 124813343, 19382359, 254771413, 254804057, 123863057, 254838523, 255034605, 233635841, 255067477, 255132201, 255164452, 255182021, 33915066, 255213752, 255297276, 255328312, 28409938, 255344671, 255361080, 255377470, 67076282, 255393793, 76120100, 255411965, 255477502, 255590600, 255624959, 255705146, 255721489, 12664850, 141738197, 255738331, 255772416, 45352243, 25985080, 255870534, 21135574, 255919364, 255985409, 256067330, 256114997, 1278036, 256149251, 256311589, 84787386, 256344308, 256393279, 80298066, 256409800, 256442554, 256475358, 256508175, 29065273, 256542468, 256639265, 20856906, 256673541, 256754286, 55493175, 125404934, 256788231, 210583674, 22970426, 256868768, 256917540, 256933906, 256950330, 256966673, 256983252, 257016301, 2376474, 257050376, 257378057, 257507715, 257541898, 29868049, 26984464, 257589279, 22528063, 257605663, 26411067, 257622075, 257638431, 155058250, 257656587, 257737370, 1114499, 257771276, 257818660, 44302392, 257835256, 210583615, 257869581, 258015955, 9945172, 258049806, 170606651, 198426872, 41091108, 258212202, 42876961, 258245101, 258278305, 19579074, 258328335, 258393872, 29491258, 258459409, 258574098, 258686992, 258703419, 258719745, 24346657, 66256959, 258737939, 258803476, 258965578, 61112351, 258981921, 258999673, 259064112, 19399018, 259112993, 2375924, 259131157, 259244439, 259278068, 259326009, 79626256, 259342888, 259376918, 47317232, 259424501, 259474035, 259506369, 259538961, 259555521, 185663489, 259589911, 259687515, 42467559, 259720984, 19742804, 259802905, 259866625, 4718610, 259883071, 259899448, 75087888, 259917594, 260014386, 9093122, 260047936, 260096059, 260113707, 260161572, 25870369, 260177951, 49053732, 260196123, 2376683, 64602113, 260308993, 35045653, 260326163, 260374763, 260408063, 126156801, 57573682, 260456692, 108724625, 260736783, 260800568, 22724690, 19808272, 260818717, 260915233, 7290916, 135545166, 260931601, 260949790, 261013567, 261029921, 261046458, 261079341, 261113631, 178126849, 261275845, 261310014, 99976038, 1114832, 261357571, 120799235, 261374893, 261406779, 261423137, 261439722, 261506831, 26198423, 29950014, 261570744, 261652497, 261668882, 261686408, 261785376, 261833371, 261881872, 261898299, 261914625, 21020705, 261932833, 261996730, 262029313, 262045899, 262078695, 262111263, 24477755, 262127850, 262193575, 262225976, 262242490, 262275073, 262291540, 262308363, 262358818, 262422838, 262471902, 262504464, 262520849, 262537956, 262586886, 262652147, 19726692, 262701920, 262751830, 262817571, 262897722, 262914049, 20693076, 262930626, 262963444, 263014180, 263110657, 263127073, 263143612, 26181953, 67911738, 263194405, 263341862, 263454753, 4653072, 263471301, 263503957, 263536657, 245022783, 263553270, 263618763, 263651364, 26083414, 263668008, 25460738, 263700543, 263716900, 263733264, 263749649, 12386305, 88735780, 263767847, 263831768, 36667455, 263864506, 53542929, 263897282, 263930087, 263962831, 263995885, 21184586, 264028404, 264077859, 264143496, 264175674, 264192017, 22937812, 264208401, 264226054, 264292136, 264339642, 53413673, 264372260, 264388898, 264421824, 264470750, 264503354, 264519697, 264536097, 264552682, 264619818, 264668971, 264732866, 41992378, 264765457, 55640082, 264782012, 264831609, 264880186, 264896529, 264913121, 49725458, 264945727, 264962236, 25034815, 265011202, 258621476, 265028070, 265060415, 265076967, 265109562, 265125947, 265143893, 265191425, 22724626, 80412705, 265586477, 47857923, 265650845, 36241425, 265783086, 265912404, 265928947, 265978433, 266010868, 266059809, 266076219, 266092766, 266125524, 126075186, 266158274, 41912111, 26165311, 266191070, 266224100, 41123899, 266256473, 266289377, 266321956, 259080399, 266340144, 266387489, 266404026, 19808315, 12386362, 266437844, 266485819, 66977850, 266503985, 266585906, 266666001, 266682612, 245350463, 266731576, 266747962, 266764305, 25346066, 266782515, 266846410, 266878992, 266895377, 266913588, 266995506, 27721786, 267075890, 128270353, 27836602, 267109132, 267175733, 267223242, 267257209, 267322824, 121930076, 22937948, 267403728, 267519798, 267583546, 267599931, 267616257, 267632852, 267665621, 267698239, 267714748, 267763901, 267798327, 267895028, 267944365, 267993300, 268025892, 181190657, 47923231, 268042242, 268058858, 268124216, 268140545, 27787297, 268157221, 75874360, 268190333, 268222785, 268255248, 268271633, 268288033, 268304618, 268369953, 268386340, 30195728, 268402930, 16515108, 28295354, 268437304, 268534092, 268582948, 268599890, 100581460, 268632149, 268664834, 262111290, 268681630, 268730401, 268746985, 268779604, 268795905, 268812321, 268828858, 268861629, 268894615, 268927039, 268945209, 20217872, 198426817, 38223888, 268992981, 269025364, 205291556, 269042127, 210550840, 269092666, 269139969, 19579366, 23019576, 269158203, 2377532, 269205690, 269238289, 18711665, 269254967, 58835240, 269320503, 269387581, 168558666, 269436734, 269533217, 269549786, 269600575, 269648220, 22937885, 269682131, 269731648, 269829953, 269893695, 121012287, 47317485, 269910521, 269942815, 269959226, 269975750, 270009861, 270059330, 270205142, 270237732, 270254165, 270286865, 270303444, 210272287, 270337859, 270402173, 25100289, 270434571, 270467103, 30245093, 270483487, 270499898, 270516241, 270532820, 270565462, 270581953, 270614561, 270631143, 270665540, 270713018, 270745659, 270762218, 270827723, 270860474, 270893140, 270909476, 108085333, 270925857, 270942692, 270975195, 271007956, 271040726, 271073591, 271139035, 1114549, 271171617, 271188004, 145424721, 271204388, 271221402, 87179295, 271253748, 271302868, 271336112, 271401195, 271435589, 271483628, 271532049, 271548501, 21659651, 271581268, 271597652, 26099713, 271614315, 35110913, 271646751, 271663280, 271696058, 271728656, 271745041, 24887297, 271762388, 25853988, 271810732, 271843361, 271859898, 271894342, 271941974, 271974994, 272007402, 67666114, 272074567, 272138835, 272238408, 62685217, 272302097, 272318635, 272384036, 36667409, 272402249, 272498691, 272515956, 66371643, 28196900, 272564428, 272597345, 272679179, 272711914, 272777233, 272795466, 272859153, 30228513, 272875578, 272891921, 272908500, 272941240, 273024458, 273072322, 273106763, 273170465, 273186852, 273203216, 38715409, 273219895, 273286988, 273399984, 273432579, 273448993, 273465380, 13303894, 273483597, 273563684, 273580088, 273596449, 273612988, 273663822, 273712975, 273760287, 273776673, 273793212, 273842681, 273875160, 31260858, 273907770, 273924155, 273940543, 23560389, 273956881, 273973266, 29491259, 273991254, 274056171, 274104817, 23380026, 268386340, 274155344, 274219194, 274252020, 274300961, 274317348, 274333712, 274350097, 274367068, 274415700, 7929892, 274432218, 274481387, 153682005, 274515793, 274563105, 274579674, 274628664, 274645178, 274678108, 25100319, 274710612, 274726975, 268845092, 274743540, 274792506, 268402690, 274810706, 20840530, 275007315, 275087669, 113672277, 275122004, 275316984, 275350261, 275416917, 275465445, 26198500, 275498838, 275611706, 275628327, 275662679, 275759137, 275775674, 275808481, 275841330, 275873809, 275890177, 275906593, 275923132, 21282852, 275972314, 276023128, 276201714, 276236082, 276316382, 276349481, 276381697, 276399961, 276430865, 276447268, 16515128, 276465029, 276529183, 24019007, 276545847, 276611747, 276694156, 276744026, 99188753, 276807768, 33833140, 276842331, 277235549, 50938042, 277317470, 277415775, 135561410, 277463534, 26296379, 277497696, 22397225, 277677921, 277741768, 277774529, 277807162, 7176255, 277825378, 28491969, 83951675, 277907299, 278005604, 87130129, 278087525, 19529921, 19136594, 278153062, 213844027, 7356417, 278265912, 183124027, 278282426, 278316903, 278397150, 233882075, 278431592, 278610003, 278644585, 278757576, 278792042, 278855898, 46448673, 278905394, 278954000, 278970452, 37912762, 278988651, 279052500, 279085072, 37224451, 279101633, 279134292, 279150676, 7176228, 279167377, 279199828, 86179841, 279216321, 279249648, 279298945, 279349100, 279429179, 279445697, 279479754, 279528051, 279560777, 279660397, 279707879, 279742318, 279871521, 279888620, 279937328, 253886705, 279986179, 280002576, 280018961, 280035346, 280051930, 280101083, 280135535, 173752379, 52822324, 280232202, 280264720, 280281147, 280297473, 264749089, 266338307, 280314167, 280381296, 207405531, 280446833, 280545138, 36519953, 280608769, 280625169, 115539986, 280641722, 280674337, 280692595, 26313000, 33833018, 98926823, 280725364, 280953184, 280985871, 281018427, 281034839, 23724118, 27181560, 281084092, 27574305, 281133112, 23986177, 281151349, 281198608, 281215060, 35323940, 281231604, 40222756, 281281502, 281331574, 281462647, 281526356, 59736100, 281542927, 31457528, 281575638, 281609745, 281657706, 281690376, 281741176, 281919870, 168444318, 7438420, 281968725, 282001465, 111984656, 282019705, 86738398, 282132750, 256410227, 282165557, 282198365, 282232698, 282460654, 171393651, 282492987, 94584868, 282509474, 282542435, 22216926, 282607700, 21839956, 282625915, 282707836, 19580797, 282755252, 282771489, 7291060, 192184322, 91013701, 282789758, 282853562, 282886228, 282904447, 282968122, 282984532, 283002752, 90996967, 283101057, 283230492, 283297666, 283410644, 67272737, 283443258, 163397668, 283459601, 283476001, 283492410, 283508820, 283525204, 283541540, 23150666, 283558132, 283607041, 283623487, 196837407, 283639871, 7503935, 283656314, 4636794, 283672659, 283705360, 283721746, 283738143, 72613944, 283754805, 283787461, 283821491, 283870491, 283918580, 231572664, 283969411, 284098634, 284115002, 16515088, 284131909, 284164154, 21184575, 284181556, 158384144, 284229820, 284280708, 284444549, 284524545, 108642321, 216956986, 284541159, 284574249, 284606547, 284639478, 27181214, 18710798, 284704785, 284723078, 284788615, 38371410, 173703227, 284885178, 230375483, 284917844, 284934228, 161153060, 284950772, 284999894, 285033802, 51150932, 285114962, 285149064, 285278550, 285311227, 285362057, 285557006, 285589708, 285622795, 285671440, 285687837, 285704359, 285753557, 47317262, 285788042, 285868218, 277414102, 141262906, 285901876, 285950138, 285982779, 99369022, 40222778, 163415947, 286001036, 286113850, 286130193, 80298002, 286148493, 19644711, 22790453, 286245175, 71549012, 286310426, 215679038, 286427022, 286721935, 286818363, 36470785, 53903419, 286836624, 208601170, 286902161, 286998823, 153175954, 287031354, 35913729, 287049619, 133726655, 287097641, 287130729, 287211522, 287228097, 287262612, 287344533, 287410070, 287491991, 287540486, 287606680, 287801402, 33390651, 287818069, 54445977, 287867111, 287899737, 287933377, 287998561, 288032666, 129532467, 288081819, 288147356, 288211634, 288276666, 288310655, 288376733, 288424897, 41895838, 288491423, 288571955, 288620853, 135561414, 288655264, 18513921, 288704417, 288966562, 20595269, 289097635, 289195940, 289261142, 289325360, 33407162, 61112379, 289374266, 268402772, 289391405, 289456465, 289490757, 289538084, 133709825, 289554635, 289589126, 289654693, 125403342, 289783810, 68862399, 289802150, 289898805, 289933223, 289997111, 290062759, 290095292, 128270395, 290144638, 242417681, 290194008, 114901221, 290244520, 290324666, 290357265, 290375593, 69517543, 290424746, 22462549, 84803669, 290488564, 290537528, 87179322, 290554100, 19808319, 290603492, 290637739, 290865418, 290899517, 290947532, 290980326, 291013235, 100630561, 291045732, 291080108, 291129261, 291209820, 291258385, 291276293, 291323988, 291340811, 137330751, 63357012, 291391214, 291438676, 281706689, 291455720, 291505370, 291555246, 291733626, 291750181, 291783423, 21184528, 158974168, 291831994, 291864639, 291880993, 122732560, 291898305, 42909713, 291964847, 292012482, 209272916, 292094260, 292143321, 292176065, 292210608, 292290642, 292308672, 292340180, 63995940, 292389342, 292423229, 292471159, 1114739, 292504859, 292552974, 32391186, 292587441, 292913214, 246268388, 292931506, 293011514, 135561639, 88309778, 293029811, 293078964, 22938093, 293111733, 293208730, 20840481, 22937895, 293242806, 18710837, 293356073, 293388661, 293437924, 8568914, 293471146, 31260730, 293504951, 29098181, 293650676, 293701560, 176767058, 293930035, 294010881, 12156964, 52543712, 294027280, 30687233, 294045625, 294111162, 284737801, 294193083, 141721682, 294273518, 294305793, 7471106, 294324156, 294486393, 2376844, 78201407, 294552953, 294619069, 294813882, 294848446, 294895813, 294929523, 7208996, 294963135, 295026772, 295045056, 295094209, 295108670, 295141439, 295157842, 43925505, 161366202, 38371412, 295176130, 295290819, 295338547, 41894645, 295387699, 295438272, 295486505, 295518290, 295535155, 295585732, 40304658, 200851642, 295667653, 295714898, 295733190, 21446692, 295780545, 22397147, 295815111, 93110273, 295960634, 86556731, 295977034, 295993402, 296009745, 281100324, 296026145, 296043295, 1114507, 296092483, 296141041, 100630612, 52543746, 296175560, 97828927, 296239159, 296255547, 296271896, 1654813, 56246327, 296288338, 296304671, 296321108, 5603410, 296665307, 296697915, 296714488, 296748695, 26198312, 296829627, 296878564, 126779615, 296911803, 7340089, 296961994, 297059042, 122830864, 93569025, 297092273, 297156644, 22134785, 297173009, 297189461, 297222868, 99369015, 297256907, 297369948, 39273011, 297404364, 59080788, 4669496, 297517178, 297534587, 1114468, 191922484, 297615396, 153665553, 297632119, 297664548, 297680913, 123928769, 297699277, 297894101, 297926715, 297943307, 41992912, 297975866, 297992786, 66371601, 298026958, 298107883, 298158031, 32686309, 1114352, 298221604, 298237969, 298254337, 298270753, 24379428, 298287137, 86474785, 298303870, 25722935, 248414211, 298354640, 268124246, 298418235, 298434753, 298467386, 149291067, 298485713, 298598781, 298664149, 298697271, 214302913, 298729768, 18710935, 298762426, 298795067, 109969463, 298811688, 298844567, 175489744, 298877195, 298911027, 298942548, 72613889, 298960850, 299106636, 299155653, 299188455, 156516474, 299221028, 35799162, 299237562, 299272147, 19267706, 299319370, 299336289, 299369172, 299403220, 35766288, 77283329, 31457484, 299516095, 299581863, 29065233, 299616213, 299860180, 9093121, 299892821, 22397142, 299925768, 25034836, 299976662, 300040819, 19742777, 300074967, 300138729, 300171327, 300188396, 300237761, 300304344, 300384287, 96845842, 300401103, 300450242, 300532064, 300566489, 300614708, 300662815, 300679252, 300695641, 300728508, 300779482, 35831844, 300860235, 300908741, 300941556, 300992475, 301041628, 301154779, 37322837, 301188714, 301252625, 87310391, 301269181, 19546186, 301302625, 301334716, 96092177, 301383991, 301449468, 301482181, 301515000, 301547754, 70058043, 301615069, 215171154, 301826380, 301875255, 301891642, 301908027, 123076641, 301924912, 20840451, 301973748, 49905723, 50874334, 302024671, 302055612, 302106592, 302153729, 30246172, 302172129, 34406459, 302235665, 1114616, 23724090, 302252116, 15974418, 302268647, 302303202, 49332241, 302498084, 302563387, 302580141, 21184541, 302629963, 302661716, 302678101, 302712803, 302760225, 302792721, 302809124, 302825530, 302841913, 302858276, 8503352, 302874655, 302891071, 1359936, 302907623, 302940215, 76120065, 302957158, 303005752, 303023677, 303071314, 36290596, 303089636, 303136826, 49528891, 303153764, 303204325, 303267924, 28000340, 303284440, 303317486, 26296384, 303349822, 8011858, 303366714, 303417318, 303465277, 303497464, 303530346, 303562996, 303612975, 238616634, 303645872, 303693906, 303710466, 303743039, 303759444, 303775760, 303792214, 303808513, 303824958, 35586084, 95780865, 181682233, 303841314, 59047994, 304220136, 304318441, 304431293, 304463873, 46121023, 304481477, 304513098, 304529466, 40665172, 304547818, 304594945, 210436170, 304611361, 43581523, 304627728, 304644218, 304660602, 177586235, 304677445, 304709663, 110346326, 304726074, 296321087, 136413432, 304742952, 304775210, 29507587, 14467089, 304791882, 304824375, 1245246, 304840721, 7487562, 305252332, 20840466, 57393368, 12156929, 305367021, 305545565, 7471135, 305578280, 37945953, 305612782, 30245080, 305758224, 132988985, 37945596, 305775898, 29868091, 305842159, 38223935, 305907696, 305954817, 16515156, 305973171, 306022385, 254099474, 306087922, 306217361, 306249890, 306282980, 59490727, 306315834, 306366451, 71548961, 306414100, 306481140, 1114461, 31457569, 306627356, 306677749, 306807186, 306856129, 306889267, 205408246, 306937887, 306954276, 205340731, 306971649, 307038199, 307085623, 73203745, 307152888, 1114312, 307429946, 1115286, 307480569, 145802469, 307577111, 307609822, 307642385, 14467090, 307658836, 144031802, 19384314, 307677179, 307759100, 101793793, 19399020, 307806437, 307839253, 43286564, 307871941, 150781954, 307904674, 307937466, 307972093, 308135934, 90456100, 308215870, 308232228, 19136529, 308248635, 308265208, 308297791, 42909732, 308315587, 308396805, 51150865, 308461812, 4636701, 308510916, 308576274, 7929914, 47202498, 308593311, 308641978, 308675285, 308725759, 1114328, 309053440, 20594973, 309101091, 309167560, 309250049, 26165332, 309363670, 85377083, 309430274, 309510637, 309543126, 309577731, 309643268, 20430932, 309755988, 309774341, 309903361, 309919800, 31342654, 309938182, 29098346, 38535662, 309985486, 71450683, 310034978, 310118183, 17793079, 310181946, 21659732, 310198289, 29868066, 310216022, 132924192, 310265863, 78184979, 310329403, 76480570, 310347784, 310427665, 9093156, 310444090, 130302036, 310460979, 310509599, 19644641, 310528009, 310624533, 154140756, 310657055, 310673431, 1556504, 310689823, 310706212, 310722961, 310755329, 12386321, 310773770, 311050302, 1654814, 311067565, 311099455, 41304146, 311116322, 311197713, 37797962, 311214391, 311281675, 311345210, 311361622, 78774308, 311377983, 311394359, 50446352, 35045598, 311412748, 311527437, 41123857, 311656780, 311705831, 311738442, 145784914, 311754954, 311787764, 42058279, 311838734, 311885841, 311903429, 48496818, 311937039, 172852152, 312017739, 312066498, 312150032, 171295121, 312199185, 312264109, 312313874, 312379411, 1114896, 13156438, 260489252, 312475684, 312492328, 312524836, 120013029, 78774356, 312543252, 312655903, 19185667, 312674325, 312754485, 312787028, 36290646, 312803329, 312819771, 24018962, 312838166, 173424695, 312985623, 201031902, 313067544, 313262138, 313278734, 313311295, 176766979, 39354689, 313327886, 313360466, 48660481, 313376850, 313395225, 91111483, 313443275, 51413047, 313492074, 313540667, 41912346, 313557555, 313606226, 313622564, 313638929, 64635073, 313655877, 313688701, 313722907, 313819537, 313853980, 173228035, 313968669, 314099742, 314197461, 28557526, 314247199, 48464220, 314377014, 314441813, 314476576, 314671163, 56246330, 314687547, 314703930, 314720257, 94388226, 314736826, 314769482, 25493586, 314786023, 60817409, 314820377, 1114515, 27787348, 314884114, 314900498, 314916882, 314935329, 22956065, 78708738, 315410467, 315605282, 315637796, 25002070, 315656228, 315818069, 25493505, 315850811, 7241764, 315867837, 315916597, 7340035, 51347485, 31146042, 315949087, 37847076, 315967525, 316096809, 316129469, 316162106, 39010340, 316180518, 316293203, 20611265, 316325946, 316342356, 133808895, 316360743, 316506575, 316555575, 54919432, 316622888, 316688425, 74399803, 316817594, 316852266, 317032491, 19579121, 317098028, 317145332, 317194296, 77365330, 317212717, 19628090, 317260116, 317325343, 58720258, 317341937, 317374522, 317390866, 52887610, 317409326, 317472769, 45629499, 317490648, 173752383, 317538340, 317554721, 117555201, 317571320, 317604136, 317638703, 317767902, 4653092, 317800884, 317834658, 317899352, 31703162, 317948005, 317982768, 318030927, 164954113, 318081073, 318228530, 318373946, 318390568, 94994649, 318423076, 25952289, 318439786, 26968148, 318472786, 29114815, 318507059, 318636095, 136495162, 318652993, 76644474, 4636727, 53413940, 266305569, 318685406, 213450808, 318720053, 81526970, 318816257, 318833126, 318865490, 31670330, 318881828, 318898383, 318933046, 318997432, 66764855, 18711367, 319046612, 319094843, 319111412, 27787281, 319162423, 319324346, 319356931, 319373565, 27787327, 319406347, 319438881, 319455291, 269107231, 319471617, 319488002, 319504420, 28524561, 319520857, 319553569, 319570108, 319619105, 319635649, 319670328, 7536703, 319719481, 319768634, 35110915, 319817787, 319897683, 319931389, 187416798, 319963192, 319979696, 320012474, 320047164, 320096317, 38207505, 320342078, 320389604, 52035807, 29114561, 320423999, 302186578, 320489536, 12714015, 320555073, 188760257, 320618513, 320634911, 41959457, 320653378, 320833603, 320915524, 321011745, 321029497, 321095749, 321159226, 36405332, 321175786, 321241274, 321274717, 321323221, 321355834, 313442321, 321374278, 321470547, 321503268, 321519700, 321536084, 209174529, 321552420, 321568944, 321601722, 321634307, 321650691, 18710759, 321667383, 321732802, 321765562, 321798558, 321847352, 321863713, 24379623, 321882183, 321947720, 322027706, 13303871, 322060599, 322126161, 322159657, 322191548, 24051775, 24051775, 322240698, 7159841, 322273642, 322306111, 322322604, 322357321, 322472010, 322553931, 322603084, 322650171, 322666497, 322682913, 322699450, 104873985, 322733574, 291881016, 208764987, 322781476, 322848845, 322994298, 323010618, 323026961, 323043387, 323059898, 323093701, 313196840, 305774608, 323126695, 38223906, 323176526, 323291215, 1114402, 323371067, 222609467, 309985338, 323389520, 323420178, 271614011, 323438673, 323568109, 19644630, 323600793, 323633345, 323666106, 39649478, 38027282, 172819535, 323700818, 186286116, 323813668, 323881043, 9912382, 323944465, 89194554, 323961061, 323994009, 324026442, 1114693, 324044884, 126271517, 324487254, 324765783, 39010363, 324829631, 4866084, 324862399, 324894990, 228982817, 19611706, 324929624, 50708539, 311525377, 324976641, 237912123, 324993384, 7880721, 325042707, 325075007, 325091391, 186761217, 29098254, 325107960, 52543911, 325142617, 78774330, 325257274, 325306458, 43794674, 97484855, 325386530, 325420858, 7405650, 325468218, 29442065, 325486683, 325534314, 325583107, 325615909, 325648471, 325697781, 325746905, 82264097, 53100575, 325781596, 137248801, 8011807, 325877777, 325894145, 325910587, 26968082, 325927134, 72925270, 325959989, 325993377, 326042352, 41877735, 326090783, 48283666, 326107550, 326156519, 9093149, 326191197, 326385722, 326402107, 326418433, 326434849, 326451386, 326484190, 326516948, 22904890, 326549752, 326582354, 326598689, 326616363, 326664433, 326699102, 326746143, 326762554, 326778897, 221577428, 326795486, 272990655, 326830175, 326893626, 326910011, 326928480, 327010401, 327108706, 327139329, 327155728, 222609492, 327172565, 47235073, 327206667, 327287116, 14467105, 327336206, 327368906, 327403619, 147243220, 19267647, 98550981, 327551076, 327664381, 327714917, 78250001, 327778490, 327712769, 19808292, 327813222, 327893214, 19579169, 327926890, 327975098, 328007868, 328056895, 328073445, 328106215, 328139973, 328173318, 328222823, 328337512, 69681314, 328384756, 72859732, 328433720, 328450104, 29868114, 328466833, 328501353, 328714346, 328827496, 328861803, 18710725, 19644900, 2377478, 199245860, 50364618, 328990896, 329023573, 56246354, 329058412, 329203729, 90996967, 22397290, 329220129, 329236666, 329271405, 31211602, 27983905, 329367765, 329400407, 329451630, 329517167, 329599088, 329646266, 329680436, 329711698, 26296349, 329728244, 329779313, 329941051, 329957377, 329973763, 27574354, 329990180, 330006795, 330041458, 330170427, 114196538, 330186785, 330203195, 330219521, 330235937, 330252476, 330301626, 46268490, 330334778, 22462640, 330383423, 140017840, 330400204, 330432775, 330483827, 330778740, 51150866, 281707437, 330842174, 76480596, 330859292, 4636756, 330907841, 330942581, 331088439, 25755684, 23265365, 43827847, 331122806, 131825850, 331202808, 331235778, 331319415, 331399522, 331449312, 331499640, 331612196, 331628561, 331644929, 331661345, 331677927, 331710480, 270467089, 129859640, 331726865, 27787320, 331745401, 331827322, 331972904, 317325370, 332005594, 115212781, 332054848, 154779664, 332120100, 332136534, 49774650, 332155003, 332302460, 19398841, 158203988, 332415009, 332433533, 332496954, 332513297, 332529697, 145965660, 332548222, 104480799, 1115420, 332580991, 332660754, 332677354, 332742891, 332775643, 55722196, 332808667, 332840993, 332857562, 37913170, 332906581, 332939561, 28197057, 75678323, 332974208, 333054138, 333086914, 333120889, 333186590, 333234487, 321994838, 333299954, 333332543, 333349175, 333414743, 333448038, 72925187, 20430930, 333480595, 333529285, 219316461, 333561887, 333578241, 184664097, 333596801, 333875330, 333939909, 311083037, 333973635, 21266468, 40665150, 334069761, 126500866, 334088324, 334184641, 334217630, 334266401, 334282768, 334299220, 334315536, 38715466, 334334085, 334430266, 334446651, 334463009, 18710834, 200016828, 334479361, 334495777, 32948240, 334512362, 334577882, 334626879, 310181946, 334643901, 334694534, 334774552, 334807114, 186695856, 334824380, 7897124, 334856451, 334891143, 335202440, 335266004, 335299261, 335347941, 335380513, 133709882, 160759844, 335399049, 335462417, 335478988, 335511860, 335561575, 160923681, 335593508, 335610073, 84344865, 335642682, 69025794, 335659669, 25952315, 335710346, 335757530, 335806480, 321929279, 335825035, 335890572, 114163729, 335970498, 336005261, 305774650, 336101393, 336117994, 232456535, 336183489, 108725477, 2376014, 336216065, 336232507, 336249407, 336283790, 336347329, 336380081, 336412731, 45547556, 336429367, 114163771, 336494608, 45547549, 336513167, 70434818, 43794807, 336576905, 336658590, 336691259, 336707585, 336724172, 336758543, 336822288, 336840848, 336887866, 25870395, 336904274, 336920634, 18710726, 336937204, 44302420, 336986778, 337019568, 337084682, 337117220, 275857492, 37503098, 337133762, 337166883, 12386335, 337232319, 84066333, 337264696, 337281057, 337297714, 337330234, 337346619, 55853130, 337365137, 337510776, 332955730, 337559953, 337592761, 337674273, 337690812, 337740080, 19742778, 337791122, 337870864, 36667451, 337887454, 87801873, 28180513, 337920085, 7159810, 337954963, 28229633, 338002007, 338052215, 338084705, 338116666, 338133009, 338149409, 7487489, 338167956, 338247866, 338282645, 338362431, 338378808, 338395218, 110527091, 338411839, 338512022, 108249908, 338591802, 18712727, 100845720, 338608161, 338624513, 338640929, 338657466, 338692249, 338739540, 338804752, 338821123, 338837675, 272793658, 338905242, 338985756, 4866079, 339034201, 339069083, 339214530, 26198597, 339249308, 36470820, 339394623, 24019026, 105529588, 339413149, 339492922, 339509265, 279150625, 2377886, 339525834, 339560607, 1114353, 339706652, 183812289, 339755194, 24412349, 90063271, 339789984, 339921057, 2376373, 340001053, 340035746, 39649896, 108773647, 340115818, 340148833, 340181008, 21086538, 340199587, 340246741, 8011807, 47235103, 340279297, 23281678, 340295902, 41058363, 340330660, 4669465, 340723878, 340903195, 340951073, 340967610, 341000208, 1116327, 57376852, 341016981, 341065744, 341082175, 341098993, 47202600, 341147684, 341164088, 39354674, 247463937, 341181172, 80167307, 43155530, 341279134, 341327889, 58769426, 341346472, 341410373, 341442619, 341458945, 247464010, 341477421, 86704210, 341524931, 341557284, 22724663, 341573988, 227622970, 32899075, 341608617, 341737531, 341754058, 341786640, 341803092, 341819408, 220299266, 341837018, 341885330, 341934274, 257605633, 341966849, 217530431, 341985450, 342116523, 342165676, 342198445, 342294750, 342327771, 342362286, 342541189, 342605887, 173883623, 220643502, 342622401, 342655330, 41992599, 342704321, 342737112, 342769697, 342786049, 22544401, 342802654, 342835233, 342851772, 228114491, 297746448, 342902959, 342999388, 343032371, 39650119, 343081309, 343113786, 343130129, 24051748, 343146785, 343181488, 34308158, 343441621, 119062587, 343474420, 343523386, 343539729, 343557254, 343588949, 283656251, 343622079, 51512536, 343656625, 343704133, 343736378, 343752721, 343769143, 80412731, 343787698, 343883994, 343935155, 93159440, 344049011, 344096968, 344131398, 344178706, 88555536, 344197300, 344310327, 48283732, 344344757, 344410294, 344473618, 343588922, 344490468, 344522769, 344539348, 211779784, 344571960, 344588319, 344604756, 147767329, 264749119, 344621418, 197099556, 344653825, 344670225, 344686648, 322207829, 344705207, 344867125, 1785930, 344899916, 344948752, 218693716, 344966395, 345047234, 345082040, 345178835, 345211109, 345243732, 345262265, 345309406, 345342761, 345374998, 345407546, 345424279, 101679162, 345458874, 345669809, 345702458, 345719450, 3211297, 345752858, 345819323, 345899041, 19611649, 274120778, 345917628, 346032317, 346112031, 49889297, 346130622, 346193975, 346210634, 346245311, 346292298, 346309189, 1115666, 346341435, 346357818, 346374338, 346409152, 36290616, 346474689, 346638530, 346750977, 19185681, 346769603, 346816545, 37929146, 346835140, 268369983, 214909206, 346900677, 346948351, 346996978, 347030067, 347080901, 347128834, 347176996, 56672273, 84754516, 347193779, 347226148, 199966753, 347244742, 347408583, 347490504, 347572425, 347752650, 347818187, 347883616, 347963578, 347996373, 348029106, 348078278, 53002241, 42303490, 348110884, 305250322, 348129484, 348210718, 348258526, 65781778, 348293325, 348424398, 332546083, 32620603, 348471329, 197804114, 348488062, 348539087, 348570023, 348602384, 348618754, 348635137, 348653529, 348701053, 348766817, 348801232, 348930523, 348962849, 348980013, 349045040, 349093947, 349112529, 24051768, 349176444, 349225182, 78201174, 95158330, 307054625, 349571283, 349716680, 349749334, 349765633, 349782033, 74399777, 349798459, 349814842, 37519418, 349831673, 349863952, 4800569, 349882580, 349929475, 349946474, 349995009, 66666555, 350011428, 350027793, 350044193, 350060715, 350126096, 68206678, 350143202, 350175499, 350208058, 350224386, 100237367, 350240981, 350273918, 119832635, 350322772, 46825508, 90406928, 350341333, 117653563, 31146043, 350486772, 350535739, 27787338, 350552067, 350568532, 350585018, 350617617, 350634228, 350683152, 350699583, 350716234, 350750934, 350863376, 245350459, 173228154, 350881989, 221102138, 350928954, 40288312, 350945466, 350978065, 25034783, 350994514, 43139130, 351011086, 351044553, 351092794, 351109179, 326943681, 351125690, 351160535, 351223809, 351240209, 21217336, 351256762, 351289360, 221003793, 272285698, 351305791, 351322175, 351338682, 351371553, 84754495, 351404106, 351420417, 25034826, 351436836, 351453258, 351469599, 20971579, 351486589, 351518809, 351551544, 351567930, 351585250, 351617041, 100237345, 351633714, 351666209, 62079194, 351684824, 351799513, 351862843, 351879201, 31146042, 100237370, 351895569, 351912021, 49889343, 35209275, 351944705, 351961304, 94535762, 351994996, 352043092, 352059423, 352075839, 352092196, 75644944, 352110810, 352174165, 352206904, 352223265, 12713985, 352239675, 28524546, 352256058, 271220753, 352272442, 352288827, 40288292, 352305210, 352321701, 268124219, 352354362, 25100351, 27181362, 352370775, 66256958, 352422107, 352468993, 240238676, 352485560, 352569564, 314851715, 352616450, 352632887, 352649274, 83591227, 352665663, 352682047, 352698612, 352747578, 352763933, 352782557, 352829456, 40206395, 352848094, 352911418, 352928147, 23150628, 352962783, 353009722, 22904891, 353026049, 220954641, 353042490, 353058833, 353075233, 266682612, 353093856, 353190311, 353222878, 21020746, 250298424, 353255694, 353288255, 353304820, 353353921, 353387670, 353419322, 27394300, 353435649, 353452095, 353468746, 353501247, 353517812, 108724791, 353566914, 353599504, 353618145, 353648656, 353665083, 353681441, 353697828, 353714192, 24477713, 112869409, 353730591, 353747137, 353779796, 353796132, 83771514, 353813568, 353862039, 350339073, 353894403, 353910842, 353927224, 353943585, 259950150, 84639747, 353960120, 354041889, 354058276, 354074687, 354091130, 4800548, 354109666, 354173505, 354207971, 97075236, 354271320, 298942500, 66256982, 354304464, 354418984, 112869588, 354451658, 318652474, 354486500, 354533777, 77365332, 354567287, 354598945, 354615496, 354648095, 354664484, 166379583, 354680891, 21708833, 354698198, 354763185, 354795553, 354812122, 354861119, 31703103, 354879717, 354910239, 354926625, 354943164, 354992191, 318652449, 355008571, 66666551, 355027174, 98796694, 355141863, 355237946, 12271675, 111657698, 355254465, 155058260, 47858012, 34324539, 355289320, 355402266, 355469545, 355516602, 355551155, 355598353, 78184677, 355615795, 355696826, 156533165, 355731690, 342786106, 355811402, 155058212, 355827926, 1114353, 355860536, 355877050, 351420500, 355910431, 79495253, 355961067, 356008133, 356040897, 185663505, 356073488, 20906015, 356089892, 51200033, 356106303, 356122625, 29507602, 356567277, 356616430, 356761818, 86130747, 356813039, 69255170, 356860700, 356911344, 356991170, 357023801, 223182864, 8503313, 357042417, 357172965, 357253872, 197689377, 357302525, 357335099, 357353268, 357433346, 357449786, 357466129, 357482529, 357499114, 357564839, 357599474, 357712435, 357763315, 357843173, 357878004, 357992693, 358072795, 7077951, 358107382, 358318165, 29098336, 358350849, 358367265, 51183672, 358383864, 119079578, 358418679, 358498497, 358533368, 358613262, 358648057, 35782673, 358729978, 358795515, 359006589, 20594773, 359074044, 359170270, 359203010, 359236294, 21659681, 359270653, 359416259, 359450878, 359498082, 47858071, 359546939, 76923066, 359563346, 359579684, 100630545, 359598335, 359713024, 23281665, 359760279, 359794945, 359841808, 359858976, 359893250, 359956666, 359989304, 2376161, 360005665, 360022052, 360038456, 360055006, 360089797, 360139011, 360267970, 360301979, 246218810, 360349988, 360417540, 360481597, 19267615, 360513567, 51789882, 23281700, 360529937, 43139108, 360548613, 360857867, 312017138, 360892678, 360989135, 361038049, 361071542, 37356095, 20398269, 199540821, 361138439, 175227042, 361234670, 30572577, 361285896, 26296351, 65028154, 361349431, 361415203, 361482505, 361546625, 361595096, 291211530, 361629963, 43155538, 361726399, 216088824, 361761036, 35782673, 361810189, 362023182, 362088505, 362135588, 261537848, 362152180, 362201282, 362234340, 9945106, 362268943, 146915345, 362381313, 31457474, 362397729, 16482362, 362416400, 139363028, 362512877, 362545236, 2211914, 362561622, 1376291, 362580241, 362858770, 13303841, 54446355, 362940692, 44433441, 363037281, 19382465, 93225615, 363071765, 19399012, 18711469, 363153686, 363200705, 363233514, 363299093, 363331778, 363366679, 1115183, 357974047, 363432216, 363495506, 181502522, 363512119, 363577640, 363612441, 363724882, 4636689, 74842786, 363743514, 363807290, 277414110, 363858203, 38371364, 26968122, 363987188, 39649954, 364036407, 364101661, 364118074, 192004182, 364136732, 364250175, 364333341, 364496145, 1114490, 364560947, 364609796, 27967546, 27770881, 259950878, 364659269, 364691514, 364707895, 9666561, 114901250, 364726559, 364824864, 202375869, 42582017, 364888673, 180142116, 364921076, 37650516, 364971396, 365035583, 211910659, 365054241, 365215909, 365249537, 155517330, 365314234, 365349154, 365594915, 186581051, 365674718, 365707280, 81969153, 365724010, 365756452, 365774681, 365805770, 365838904, 365871187, 365906212, 116424720, 45023290, 27181253, 365953200, 365986808, 366067956, 26182796, 366233894, 366543041, 366576476, 36290644, 366625577, 105497537, 366659879, 366788796, 366837949, 366872872, 209092609, 49905748, 366985301, 367018001, 367036713, 14499876, 367116346, 367134335, 367181825, 367198382, 88277028, 2377454, 367248459, 367282474, 296009761, 367345738, 367362130, 38535357, 88784954, 367380779, 367493203, 367526321, 246218785, 367558842, 367593772, 367839533, 367887942, 367937838, 368083239, 368116172, 51347519, 368148543, 93225587, 20595172, 368167215, 368263226, 368279636, 26968119, 51347490, 368298181, 368345502, 368396592, 368493569, 2376762, 368560433, 368640731, 368706029, 368740658, 368787457, 4685855, 42582047, 368806195, 368967735, 112869460, 368984097, 369002804, 369066058, 369083164, 144441428, 369132591, 369166645, 369263530, 24527411, 369297520, 369344700, 369393850, 369428790, 5537826, 369510711, 363200904, 369705453, 7897124, 33751098, 369740088, 369788826, 369836218, 369870423, 369917955, 360120402, 369934773, 369967505, 122077370, 370000168, 370032858, 370082051, 298319930, 370116921, 370280762, 366608468, 370313531, 370360506, 42107234, 42303521, 370395452, 76414992, 370475196, 370524222, 370541340, 90112270, 370589898, 370624829, 370704878, 50708511, 370737210, 340246551, 371114386, 371165503, 371329344, 371392996, 24002855, 78184745, 371427485, 120291391, 371509569, 371589534, 371640377, 371689794, 31342655, 371738947, 371885419, 371933598, 371982619, 372031784, 372065947, 372113704, 372148548, 159777016, 372277515, 315768890, 372312389, 372441151, 58835497, 23052485, 372459846, 372572242, 372588560, 283361338, 372604964, 372622115, 372672839, 23052488, 227131585, 372787528, 140902416, 372836681, 372967754, 373066059, 373145909, 373179005, 371982394, 373213516, 2378061, 373293300, 22937793, 373343224, 373424411, 373473687, 373506106, 373524814, 93159455, 373639503, 373786960, 373850298, 373883189, 373915953, 373983569, 20464091, 374049106, 41911187, 374212947, 374292538, 374309068, 140083282, 37961921, 374344020, 374407227, 140902402, 374423568, 12091395, 374440273, 374475093, 113442852, 374557014, 374868311, 374915159, 374964241, 374980787, 375013790, 318914645, 375063098, 102875400, 26296394, 375112035, 375177536, 375242807, 86245434, 375259322, 1114397, 375292318, 47319265, 375342973, 375373911, 375423561, 375523672, 67273447, 375587285, 206536778, 375621977, 375671130, 375767056, 22790397, 27183451, 375783655, 375816445, 375851356, 2377504, 375963892, 376015197, 376062008, 376078558, 87179297, 376111167, 376127521, 18579457, 376144116, 376195422, 376490335, 376572256, 376620751, 376668913, 129712314, 376717556, 376766650, 172853052, 20464017, 376801633, 376864961, 360251801, 89293286, 174441332, 376897599, 376913976, 173752402, 26296323, 30572602, 376932706, 377046063, 377077815, 377094474, 377127105, 377160017, 377194090, 28114961, 377260387, 377372754, 141787194, 377391046, 377438430, 377471390, 28196866, 377522532, 377569473, 377602241, 377636358, 377684418, 377768293, 377832356, 377864752, 377913428, 377929946, 377980730, 378029781, 378061156, 378093627, 378110781, 378145126, 2376218, 378308967, 378374504, 378438157, 42467554, 378503615, 378535972, 20856866, 378552661, 378601557, 378634433, 378667234, 378700007, 49021140, 378734953, 378814481, 77365281, 378830879, 378847249, 378863672, 378880033, 378896420, 378912784, 338149393, 378929445, 378963008, 379011090, 28000290, 379486211, 379502714, 5603361, 379521387, 379716035, 379750764, 379830948, 96026894, 379895994, 379930989, 38537582, 380011244, 380060554, 18711012, 380111215, 380338390, 380371137, 380405670, 380504432, 380584245, 19644614, 44302393, 380616793, 380649531, 146391102, 380665978, 7176314, 380684257, 380748001, 235536417, 380780737, 380815729, 23183418, 380862495, 7176266, 88457298, 380878930, 380897650, 380993733, 381026321, 18711681, 299417631, 381042919, 381077875, 381175593, 381223163, 381274484, 381436351, 381471093, 381520246, 87048251, 381600443, 381649118, 381681738, 24805392, 381700471, 26181902, 381812794, 24019003, 381829534, 381880696, 7569439, 381927482, 381944104, 381976594, 381993019, 382009360, 382025731, 20971523, 382044537, 382190571, 382238985, 25493563, 382289143, 382339450, 127336449, 382403123, 27181253, 187859130, 382452113, 108773573, 382484711, 382519675, 382682293, 382730257, 382746657, 382763067, 382779428, 382795832, 54149156, 382812218, 30146623, 382828855, 382894803, 382926849, 44122171, 382945660, 382992685, 383025345, 383057979, 383074363, 144277701, 383090705, 383107073, 86704184, 383124204, 383175037, 383304218, 383369275, 49348624, 383385774, 62488938, 383435000, 383468132, 383516705, 383533269, 383566010, 383598595, 139640895, 383616719, 383664144, 383680514, 383697093, 383729906, 250626277, 383762461, 383778879, 129450017, 383795233, 383811585, 383829977, 383877136, 383895934, 383942856, 57114625, 383975965, 384008250, 57376785, 384027007, 384321920, 384401717, 384434583, 384467544, 384516154, 93995010, 384534913, 384648792, 384696542, 384730751, 384780674, 51576891, 384895363, 385041219, 385089767, 385124740, 385237735, 385269791, 18055227, 385288581, 385368123, 385386069, 385434212, 385484194, 385548584, 32391201, 385581073, 385599878, 385646609, 385662994, 18712367, 109658305, 385679582, 385714334, 38748242, 385761381, 385794107, 26279954, 385810433, 385826849, 385843388, 385892541, 385925342, 385957947, 385974290, 385992687, 88817697, 386058631, 386188441, 386220090, 386236598, 108249270, 174637138, 18710744, 269844516, 108086189, 386271624, 386368163, 24805432, 386450273, 386482209, 386498778, 386547936, 90210362, 353714212, 386582921, 386646052, 386662458, 270991377, 386678977, 386711611, 386727969, 142590168, 168427934, 98746427, 386746762, 386908374, 18743329, 386943371, 387107212, 110575675, 387170653, 2378125, 387203130, 298860807, 31211604, 387219538, 387235898, 76120148, 387252410, 92881155, 387285077, 1115110, 387320206, 387416122, 137576465, 387432449, 387448849, 387465252, 387481601, 338149432, 387498020, 387514635, 57376801, 387547194, 87162954, 387564168, 387596291, 387612705, 69255204, 387631503, 387727752, 101105934, 387761446, 387811728, 387908971, 387956772, 387973176, 86556754, 387991953, 388186572, 388221330, 388350354, 388400357, 388432439, 109199361, 388464656, 388481238, 19644712, 388497467, 388514350, 388547160, 388596733, 114196562, 388629371, 388663699, 388710433, 388729236, 388808762, 388825105, 7094305, 388841530, 301252625, 388858049, 388891349, 388939950, 388989648, 389022059, 389054465, 4653138, 389073301, 389185618, 20414522, 389204374, 389269911, 389333374, 389382302, 51789887, 389414913, 18579473, 389431544, 389464701, 389497025, 126026049, 389529848, 132792901, 389562561, 389595192, 389611551, 280281172, 389627960, 294944801, 389646744, 389775361, 7487570, 20463965, 389791777, 115671099, 390187418, 262111248, 278118417, 7225380, 71975364, 390365598, 390415226, 71548930, 390463881, 390545740, 162498971, 390594561, 37027895, 390611214, 51576833, 390643898, 390676684, 390711708, 390941085, 391004176, 30507067, 391020577, 391037468, 391102768, 269090875, 391151703, 391202468, 391233758, 391266898, 391301534, 25018399, 59277341, 391397377, 391413834, 57196561, 391430450, 80412754, 391462970, 391479313, 391495713, 391512293, 391545143, 390955025, 391610450, 65568769, 391626945, 391659553, 391676092, 391727175, 391790778, 391823669, 391856421, 391888954, 391905314, 391921665, 20267063, 391940511, 392151336, 392184066, 216563748, 28196900, 392216612, 392232977, 392249345, 268419105, 392268192, 71450657, 392317345, 392399266, 34226234, 51576849, 392432035, 392495671, 392527930, 392544289, 392560722, 33915798, 241778743, 392577258, 392642577, 299976516, 392661412, 392823451, 4718593, 392874405, 392986683, 42958849, 393005478, 393118026, 393151130, 393185703, 108773661, 393251240, 393382313, 393415082, 393480619, 319029281, 393527586, 162857042, 393561334, 393593246, 393642794, 183173390, 393740536, 393775532, 30507071, 393871546, 274120735, 22528001, 393904359, 393939373, 394100995, 394133717, 394168750, 394264885, 251658744, 394297758, 394346727, 394381743, 20676667, 394428641, 394461428, 231571654, 394512133, 1116592, 394592670, 394641760, 394674723, 394742193, 395035539, 55640148, 395086258, 395132964, 271613955, 132694074, 395149368, 395165727, 317554772, 395182544, 395297306, 273874946, 395362366, 12664834, 395381171, 395444417, 395477055, 395493439, 395510003, 67829823, 21217343, 395559634, 395624532, 395640868, 129581119, 395658707, 395706402, 395722836, 395739322, 27396397, 251428897, 395771962, 41992513, 395790121, 395837637, 395870835, 395903756, 395968548, 395985471, 20430864, 21577729, 396017878, 396051902, 396102068, 35913812, 396198074, 396231085, 276807892, 396282293, 396395059, 396443706, 396460381, 396493058, 396525790, 396558786, 396642742, 396690305, 396740125, 396788667, 396839351, 21184570, 396902436, 396918800, 383729681, 396937656, 397082657, 397099090, 397115955, 19579315, 397164841, 397197590, 397230347, 397262884, 318833015, 185466943, 397279291, 57442490, 397295632, 397312029, 397328627, 397379360, 397427686, 397459514, 397475899, 19333363, 397492421, 397527481, 95256633, 261898324, 397920699, 66256980, 397967774, 398019004, 398164166, 398196753, 398213121, 7503928, 398229507, 398245970, 398262273, 66928724, 398278878, 398311441, 398327841, 398344575, 147849274, 38977620, 398410024, 398442583, 264798224, 398494141, 398541070, 398573601, 398590010, 55640123, 27328513, 398606867, 398639160, 398655572, 398671888, 398688259, 398704698, 398721042, 36519972, 398739902, 398884881, 398901322, 398917690, 46252091, 398935168, 398983891, 19350375, 399015952, 221594237, 399032536, 399067583, 399115238, 399147946, 187154514, 399179835, 399196404, 399247808, 399327931, 399376791, 399409715, 399458391, 399507765, 399540283, 395722808, 399559105, 33079370, 399753709, 342671419, 399788482, 399884347, 399900730, 56328225, 399919555, 399999189, 400031761, 400048186, 400065252, 44662843, 400114646, 400179419, 400211986, 168641643, 400230852, 400310568, 400343288, 251380890, 400375871, 20971583, 400392276, 4800634, 400408577, 400424977, 400441400, 61964346, 400460229, 400506896, 400523293, 400540148, 400621655, 128630843, 400671127, 400703570, 400719905, 400736314, 46252049, 31703073, 1115289, 400752824, 19644610, 400837062, 23314448, 400932938, 19546194, 400949279, 400965668, 400982075, 400998586, 401031388, 401099207, 285082094, 401146622, 401178655, 401195066, 19382768, 401211476, 46252116, 152240164, 401230280, 401375248, 401391633, 4800568, 401410505, 7225418, 401473950, 25034838, 401524123, 401574346, 401719573, 401752138, 401768532, 401784890, 401801468, 401834254, 401869259, 402014267, 402030628, 112869439, 402046977, 402063396, 402079761, 272318707, 402096765, 141230081, 402128959, 402145466, 402178049, 402194495, 402211137, 281100324, 402246092, 402309178, 28492262, 402326392, 402375550, 402408009, 402508237, 402557390, 402620475, 402636858, 402653201, 402669626, 402685983, 402702395, 20267044, 97026440, 40288319, 333824002, 402718756, 402735400, 402768358, 402801021, 402866260, 96239632, 402884679, 402948112, 402964497, 402980866, 402997306, 403013649, 403030049, 403046629, 403079252, 403095752, 403128356, 31178768, 403145645, 403177833, 403212751, 403357909, 135069698, 403390553, 278233959, 403425744, 403571018, 92848129, 403603514, 403619843, 383844619, 403636282, 403652611, 403669074, 44302395, 403685458, 46268417, 403702781, 403734544, 403750915, 403767297, 55869524, 403784570, 403832890, 289505364, 403849232, 137330704, 385433786, 403866747, 403947718, 403980504, 404014870, 404062238, 2326583, 404078623, 404095220, 404144614, 404177419, 140017935, 404226236, 404275263, 404291617, 55869499, 404310481, 404406308, 290127927, 265961712, 28197142, 404422657, 264142932, 404441554, 404799984, 404850813, 404914461, 404946945, 404963345, 94076983, 404980583, 405014995, 119062587, 405113300, 405211605, 405307621, 405340389, 211255502, 405373172, 405422097, 405439080, 405471707, 405504419, 405553396, 405602388, 251592740, 405621206, 405700671, 405717025, 49447143, 405735895, 405784296, 405848122, 87179281, 234962980, 405864932, 405897300, 24805394, 405913993, 405995714, 26198220, 406029040, 76038619, 406077842, 406128287, 406161880, 406208515, 406224929, 35143866, 249446586, 406241364, 406257722, 20856887, 406276569, 406473178, 406555099, 406667678, 406717280, 406765756, 406814964, 406866396, 406930035, 406964701, 407027905, 407060692, 407093479, 37503031, 407128089, 199278623, 407177694, 38223930, 407224860, 407292383, 72089602, 407339044, 407355867, 407388471, 407453755, 407470273, 407503211, 407535698, 407552001, 407568458, 137429322, 137592890, 407587296, 407718369, 407781460, 27967572, 78201080, 407800290, 57770020, 407879743, 407897020, 354091090, 407929054, 407961656, 407978015, 19185722, 407996899, 408061446, 277858788, 74399761, 379502628, 408109352, 408141882, 408158692, 72089663, 408191007, 408207546, 19398951, 408242661, 408305722, 1116646, 408322967, 221577513, 408422887, 408486100, 233259044, 408521097, 408586728, 408716527, 408797398, 408829988, 25395263, 408848873, 311656797, 170115313, 409010392, 98926789, 409043201, 51642665, 409092407, 409157851, 125550666, 409190933, 141607131, 409225706, 409387495, 222609441, 409436346, 409468987, 409485329, 409501918, 409537003, 409632785, 118424174, 409649366, 107823389, 409682417, 409731291, 409766380, 159154179, 409829412, 100581432, 409845991, 409881069, 409927973, 9093182, 409960692, 171983157, 410009657, 3457081, 410028526, 410222836, 410271985, 410304596, 410320925, 66519043, 75972666, 410337861, 410370065, 410386676, 410435998, 303301939, 410485716, 410533970, 410550357, 410583042, 410599460, 247463999, 59179067, 410616258, 60604695, 410700271, 410747073, 20414465, 394805307, 410779824, 204193847, 410812503, 29098226, 410862288, 410894337, 410910737, 159006721, 140018157, 410927415, 410995184, 36667423, 411140627, 411172865, 411189281, 199377082, 411208177, 411287610, 411304020, 411320356, 411337547, 411385872, 25002043, 411404786, 411517627, 170115361, 411568469, 411648992, 411699699, 411748852, 411975883, 412008715, 63275041, 25051220, 412041494, 163397695, 412074346, 29098385, 412107234, 412155920, 136495120, 412174837, 412271145, 412303396, 412319761, 412336202, 305610932, 412355062, 412483642, 412499985, 412516564, 412549320, 412583727, 412631096, 411385914, 412649975, 412715512, 412762145, 412778532, 412794896, 221003793, 412813817, 412926354, 412975506, 413024272, 69681448, 90406969, 413288955, 413368496, 413401147, 24477751, 145014984, 413417546, 337870906, 413436412, 413483025, 413499636, 19530023, 413548547, 413564986, 413581371, 20561921, 413598365, 413729273, 413761572, 413777975, 38043679, 413796861, 351666194, 413876440, 413909050, 332791825, 413925409, 413941946, 413974584, 173506596, 413990975, 414007482, 414040065, 414056465, 66256970, 27344929, 414072921, 414105825, 36749348, 414138370, 414154754, 414171167, 414187537, 414203940, 270401592, 414220618, 414253112, 414269476, 414285825, 414302241, 53133348, 414319060, 334364675, 414367930, 2378238, 414403071, 414499049, 414531617, 414548156, 414597424, 144572433, 414646275, 414663142, 21757988, 414695636, 24379428, 414728208, 414744790, 414778324, 414827082, 414859327, 414875681, 414892250, 414941751, 332808228, 414973953, 414990395, 415006721, 415023137, 266404026, 415039490, 264028353, 415058432, 415137825, 415154394, 415203547, 415236308, 415268900, 274382849, 415285434, 415319543, 415385829, 415416356, 31211553, 415432740, 415449151, 2376475, 415465936, 415580177, 415596769, 415629504, 415711265, 415727850, 415793346, 415825956, 415842480, 20971604, 415875297, 415908062, 415940641, 110346271, 415957178, 415989962, 416023155, 97648657, 416055354, 416071697, 416088097, 416104682, 111722982, 98418994, 416170043, 87900353, 416186679, 416252181, 416284673, 416301089, 333136103, 416317496, 22511652, 416336232, 268042273, 416399604, 416448545, 416465127, 416497700, 349061327, 416514380, 416563258, 416579601, 77332513, 416596001, 343883994, 416612964, 416661562, 416677951, 416694335, 416713217, 416809361, 416841759, 416858148, 416874559, 30720036, 416891420, 416956686, 416991746, 417054942, 417090051, 417136676, 417153041, 354910209, 417169425, 417186935, 417218591, 417234980, 417251391, 293470290, 417267771, 417284129, 417300666, 417333437, 274415633, 417366260, 417417732, 417530033, 417562683, 417579067, 417595428, 417611832, 219463711, 25968676, 417628161, 417644603, 223051812, 417660987, 417677330, 417693732, 417710097, 417726465, 417743006, 417775674, 417792059, 417808443, 417824826, 417841215, 417857569, 417873979, 302940196, 417890305, 417906762, 417923073, 417939473, 19382543, 417955842, 417972410, 418005259, 24477752, 418040325, 418187782, 418365523, 418398292, 349405268, 418414676, 335806465, 418431198, 255328319, 418463963, 418496743, 418529316, 418545697, 28065809, 418562234, 418594872, 418611282, 418627770, 418662919, 418712072, 240926751, 198082645, 7258118, 419086344, 419102753, 80298039, 107135282, 419482123, 419594454, 419627256, 419660157, 419725747, 326500436, 419760652, 419858957, 106053648, 142475486, 419971378, 420006414, 420069412, 420085793, 420102843, 420151510, 420186639, 420266142, 21200980, 420301328, 121700538, 420366865, 420495630, 38699071, 420528829, 420577281, 420593681, 1114314, 420610357, 71909394, 420645394, 197902633, 420757562, 131629119, 420774197, 420806673, 420823122, 140083258, 420842003, 420956692, 35799041, 421052449, 421068986, 421104149, 421363795, 421396757, 51167248, 421430579, 40206372, 421462032, 50069681, 421479194, 421527774, 421560379, 421576720, 421593119, 30687249, 421610042, 421658682, 421675067, 48283734, 421691636, 51167233, 421741383, 421773394, 421792278, 421855263, 90210388, 421871860, 421921255, 421969936, 421986335, 106102843, 422005271, 422234648, 104071186, 422281496, 422314001, 422332953, 422380339, 422445072, 124305439, 422461826, 422527246, 422562330, 256638977, 422611483, 22937724, 422709235, 422758940, 422822076, 22937685, 422871288, 422904755, 422985942, 423018512, 18055199, 423036623, 423084229, 423116993, 423149782, 423182642, 39780623, 423217693, 423412135, 302972964, 423447070, 423494523, 423526593, 37371988, 47317225, 129679442, 423559869, 113869109, 423608542, 423643679, 423690271, 87179348, 423708203, 423757987, 85377108, 38797329, 423807520, 424135201, 424214721, 424249890, 424296641, 424329702, 424361985, 39649703, 424380963, 424444318, 424493470, 41894682, 424544804, 424659493, 424756612, 424820767, 424837282, 424869889, 123076667, 424886857, 424987174, 425083781, 49905748, 425148802, 425215405, 7258194, 425263178, 425279925, 425312904, 425347623, 425427130, 425462312, 425591337, 425624952, 425672926, 425705488, 425721873, 425739036, 425787643, 425837603, 425888297, 426066136, 426099436, 426148002, 426183210, 426246229, 426278971, 426297899, 36471069, 426395102, 84754463, 426426399, 426442838, 6389819, 426459699, 426508620, 426560044, 426721466, 135561430, 426754535, 426803233, 108249355, 426819666, 46743746, 426838573, 426917904, 38371345, 318570497, 426936027, 427000078, 427035182, 427098196, 277643322, 26427477, 427115626, 427163649, 427180063, 427196490, 71450706, 427213031, 158203921, 228049477, 427245961, 427327702, 427360959, 427410045, 427442410, 427507745, 427524282, 2378287, 427556923, 286081330, 427575856, 21577759, 427672566, 427739697, 427787311, 210124855, 427819239, 52232228, 427852002, 4800525, 428231219, 163659998, 428313140, 10977296, 428441802, 428476981, 428523578, 428539963, 428556506, 428605473, 32686096, 241647674, 428624438, 428687690, 428722743, 428785858, 126665013, 36405332, 428821048, 428949562, 80035899, 428966952, 429015284, 429064678, 429097016, 2378297, 429113403, 429129829, 429162578, 7258143, 429181498, 133742833, 429327217, 429394491, 18710825, 429476412, 429541949, 429607486, 429654643, 26640401, 429689407, 429834299, 364707873, 429851033, 29114724, 429886016, 429965345, 429982749, 430017089, 430227514, 430243899, 66764855, 430262850, 430377539, 430473460, 430524996, 26198294, 19382434, 131825850, 430572680, 430672453, 271155217, 244990009, 430735601, 430768145, 430785904, 430833749, 430869062, 431000135, 431079910, 431112481, 431144996, 431161640, 431196744, 431391468, 105578712, 431440614, 431505496, 431538192, 23724091, 431555123, 431604180, 29950015, 431653242, 431685690, 431702075, 431718402, 431734785, 151258153, 431753801, 233930769, 234242270, 431819338, 432079079, 85377041, 432114251, 432160824, 1114349, 432178134, 432242886, 432275519, 385728802, 432292038, 432324801, 432357620, 432407053, 432473156, 432521234, 432538014, 432587022, 48922642, 8486928, 87162936, 432620105, 432719674, 432766977, 40665090, 432783564, 432818764, 432884301, 2375767, 69681419, 432998990, 20627515, 186663349, 433096356, 433127460, 433144150, 433176762, 433209615, 433242305, 433274965, 433310287, 433373677, 237683089, 433406934, 433471547, 58000231, 433487958, 433505238, 433572432, 433685043, 433733830, 433769041, 144359831, 433850962, 158138427, 65781793, 433913914, 433930299, 433948245, 106315810, 433996708, 434028666, 434045050, 434063955, 434096723, 434126866, 434143262, 6586391, 434159700, 252526608, 434536937, 102711537, 434602040, 434618426, 106906560, 434634808, 434651630, 434686549, 434733115, 434749847, 286818388, 341246037, 434784854, 434880688, 434913773, 293947991, 434946084, 434962722, 434997848, 435077613, 93159508, 435110102, 435142878, 47302814, 117555258, 435178073, 435306956, 38748178, 435339282, 435358298, 435437858, 435470354, 21578034, 435489371, 435585082, 273367256, 435601905, 40206394, 435653212, 435716154, 144359691, 435735133, 435847227, 4669456, 435863770, 435912737, 435929465, 435994837, 436027550, 436060219, 26394817, 436076996, 436142391, 263634977, 436209918, 436256785, 18711749, 436273170, 15974522, 30245086, 436292190, 230640223, 436456032, 436552010, 207585338, 436587105, 312197409, 436633788, 1785873, 131842279, 436685410, 298091861, 436781245, 144572490, 8011777, 436816483, 146407425, 436914788, 436928515, 436944932, 436961336, 436980324, 436994122, 437010433, 437026900, 206766138, 437043426, 389513447, 437077988, 196837431, 437127781, 437373542, 437439079, 437518832, 437567544, 437584058, 437616948, 437666252, 437701224, 437780835, 437846260, 437897833, 60096860, 101875979, 438026433, 438059067, 438078058, 20464085, 438206547, 438239268, 163397649, 438255652, 75874321, 135413796, 438273750, 438338145, 438372971, 438435898, 12271652, 438452625, 429932770, 182599735, 438485050, 2378348, 438501569, 438534496, 438567098, 438602349, 175227474, 438684270, 153616417, 34160696, 438731022, 438763764, 438812758, 95109121, 438830538, 438879690, 438927391, 435748922, 438946415, 439061104, 247463995, 439290481, 439353930, 439386171, 439402497, 285982776, 9666576, 439419442, 439468197, 439500882, 112508961, 439517432, 7258169, 439552626, 27754497, 439599137, 439615575, 7405627, 194806030, 439664888, 132792806, 153534717, 439700083, 439795924, 439831156, 439896693, 440009441, 440057940, 36339748, 440075493, 440107905, 440158838, 440353060, 440418737, 344114706, 440452154, 440516610, 440533185, 440566069, 3211338, 239206433, 440601207, 440680741, 440715896, 122552337, 246005842, 440860730, 182730811, 440878185, 440961657, 441106491, 40206337, 441123002, 441155643, 441171969, 163610708, 441188407, 21758007, 441205066, 32948241, 441237752, 441270303, 48283707, 250888251, 441286657, 441303073, 100073531, 441319460, 441335810, 441352377, 441384961, 441401830, 297156610, 441434170, 441453178, 441516278, 441581771, 441614573, 441748091, 442043004, 442106049, 20856918, 442141309, 260964637, 442206846, 442286412, 22397140, 442335249, 26313208, 28196865, 442354303, 442482853, 442515700, 50446391, 442567296, 442614220, 442649217, 442777601, 442794459, 314753060, 442829442, 13156410, 442974394, 25034769, 443007034, 443023630, 145114049, 23150676, 443058819, 25346081, 443105468, 443155069, 443188442, 443239044, 443468421, 443515340, 443548132, 86130705, 443581637, 443613243, 110100482, 443630061, 443665030, 22790637, 95518805, 181160583, 443793749, 7864321, 443845256, 443892668, 443924562, 443943561, 56131640, 443940865, 444009098, 444055848, 444088508, 444140171, 444235812, 20856894, 444252733, 444304012, 444367845, 444432443, 444449338, 444497978, 444514388, 119341140, 444530724, 444547307, 444582541, 444727688, 444761819, 110969127, 444825682, 313540641, 444844686, 444924364, 444959375, 445022271, 445038625, 40353808, 445057680, 70811732, 445120700, 112886924, 445169697, 445186282, 445251617, 445268004, 445284368, 19235153, 94535713, 445303441, 445530145, 445546714, 20971594, 445598354, 445663891, 445729428, 35782738, 445794965, 445843117, 285606289, 445926038, 446022587, 446070970, 446103588, 58605585, 446122647, 19628280, 20430881, 446201919, 183992356, 253887058, 446218510, 143589394, 446251285, 32112703, 28491861, 446285531, 446349371, 446368408, 446450329, 446677393, 20856916, 446709882, 446726707, 59490508, 446776449, 326943283, 446808123, 446824506, 31031359, 272908585, 446843546, 446892699, 446958236, 447086651, 447103039, 2850858, 1114306, 447448435, 76924133, 447498910, 447562476, 447610943, 447627482, 447679135, 447742420, 447793824, 36880598, 447889622, 447922739, 447971514, 448004341, 448055915, 448119458, 448154273, 448348378, 448397601, 448430959, 448495814, 19235083, 448531106, 448626705, 448643297, 53903415, 448676256, 448725049, 448742745, 163005368, 55525461, 113672467, 448809635, 53100602, 448989860, 370033203, 449085498, 449102022, 176767031, 185466962, 53100602, 449134785, 449167637, 449200372, 107135247, 449249576, 449282133, 4718628, 449317541, 449432230, 449527809, 49823803, 449544488, 90210386, 205391077, 449579687, 449789953, 449806420, 19628116, 18513936, 260784144, 147832891, 449822922, 449855573, 449888341, 198000643, 449923752, 450251433, 450315222, 450380001, 450412769, 18137151, 450446236, 450497194, 450562731, 450611884, 450707477, 1015832, 450726573, 450969814, 451002609, 451035137, 213565473, 64618512, 451051713, 451084362, 112508945, 451100920, 27574335, 451136174, 451231802, 28197153, 451250238, 451297774, 451330296, 35487803, 451363146, 55607332, 451395768, 451478869, 451545775, 451608820, 451658032, 451707066, 451739944, 36470842, 451772449, 20856865, 451789814, 2377630, 40140836, 451857072, 451970506, 452020913, 452067386, 452083794, 49348637, 372458432, 452101048, 452151986, 452214868, 452231169, 452249833, 452296948, 14450721, 167283379, 298238008, 452346141, 452378822, 452411425, 53346362, 452430516, 452575294, 333185210, 452591946, 452627125, 427000104, 452774582, 452821008, 24805378, 452840119, 452903175, 452952308, 453001493, 453034177, 453066785, 47153237, 453085880, 453148674, 16482361, 453165057, 453181499, 44662802, 453200569, 50938042, 364003386, 453312698, 453345892, 19644610, 453394516, 265158962, 453410901, 453444071, 453492794, 453509179, 453525941, 453561018, 453692091, 453755084, 453787892, 453836801, 453853220, 453869585, 436207926, 453887339, 453937852, 91276426, 278070135, 453987005, 454036158, 454085311, 26705936, 88457275, 454213717, 454246459, 454262817, 49905746, 454279252, 49823745, 20840482, 454295614, 454311993, 29507608, 454429376, 454721914, 454754597, 84754491, 93782301, 454789825, 107839681, 9093183, 454885377, 454901777, 86737108, 20595315, 454920898, 28491952, 455016634, 455049884, 455101123, 455166660, 455346885, 455459112, 455491756, 455525242, 436650020, 455573690, 22724671, 455608867, 86884408, 455671994, 455707334, 455885201, 455920327, 439615519, 129957904, 19628341, 455966917, 119062614, 455999676, 456051400, 456114364, 19579176, 173752337, 47628291, 456166089, 2376570, 456248010, 456310982, 456343569, 456360158, 456392975, 456425694, 36339943, 456461003, 456540393, 12664916, 127025215, 456573412, 456608305, 172853822, 456657612, 456968520, 457017440, 457097250, 20152336, 457113896, 22560850, 457146828, 49381434, 457181901, 457311514, 457377954, 457457700, 457475113, 457507645, 457540188, 363200741, 136413377, 37224465, 457588772, 457605176, 457622333, 42828005, 457657038, 457902030, 457982011, 1114581, 457998943, 100630530, 458047571, 458083023, 458145809, 458162571, 458196861, 94535698, 458227960, 458260656, 458293470, 21774684, 458326202, 26198214, 458359288, 14450724, 37913060, 458394320, 78201025, 74039378, 96092161, 458539095, 458590929, 458686546, 458705618, 7471138, 30245149, 458752031, 458768442, 458784785, 458801364, 458834106, 458866744, 76923340, 458885843, 99255162, 458967764, 459063628, 459115221, 459161819, 459195984, 459243537, 459260107, 459293002, 104120379, 459326235, 459374681, 459410134, 459653498, 459686106, 459737815, 19742749, 459819736, 459918041, 460030651, 460079140, 460095544, 213958738, 460111890, 460129117, 460177971, 460226642, 460242980, 19742723, 460259553, 460292482, 460357649, 460374098, 134365185, 460393178, 39567396, 460472353, 460489595, 351666193, 460522779, 460573403, 460622556, 113934899, 460719788, 201082894, 460770013, 168116708, 460849900, 423690256, 460898541, 460931635, 460983006, 56246305, 461258938, 51183679, 461292024, 461327071, 119062612, 461408992, 461488691, 461537813, 461570084, 461586907, 461619236, 461635640, 24805438, 461654753, 461701405, 461733946, 52232251, 461753058, 461818595, 461848592, 461864977, 461881407, 20725793, 461900516, 1474583, 337723701, 461963476, 461998821, 462061650, 462078149, 462110842, 11354171, 462129894, 462241825, 462260967, 49905667, 462357089, 462389307, 462405633, 462422049, 351338682, 106892008, 462441193, 462848035, 3457058, 462864385, 3211323, 462883563, 200114239, 462965484, 29098182, 463224868, 463241233, 49332287, 463259424, 463306841, 463339578, 22462547, 463356098, 49316247, 463388730, 1114132, 463405178, 3211386, 1116909, 463421456, 463388703, 6389789, 463440622, 463489775, 4636731, 159416353, 463570387, 463618081, 463634490, 310132753, 463651798, 463716623, 119079335, 463749441, 52887568, 463782090, 463814854, 463847667, 463896612, 463915760, 19070977, 464028260, 464077109, 19791889, 464112369, 464191570, 41058340, 464210674, 82330051, 464437436, 464486484, 19316820, 464502866, 61161561, 464519652, 464554739, 22446818, 464666707, 464699724, 464749126, 87130148, 464781713, 464814494, 35045629, 178028629, 464866036, 464912615, 464946211, 464995457, 465029877, 465226486, 42582074, 465272868, 465289232, 465305617, 465323581, 465372753, 465453058, 465469498, 465485841, 465502241, 465518593, 465535060, 465551883, 465600807, 465633335, 465649695, 465666107, 108250241, 465682433, 465698852, 67059775, 465716379, 465764353, 19742804, 376619988, 465783484, 106037281, 465830451, 288620602, 465879073, 465895871, 240795684, 465928223, 49905665, 465947383, 466176760, 466240576, 466289393, 466338040, 466372013, 466420023, 466488057, 466535369, 311657168, 466583816, 1114288, 466634005, 22397253, 466681914, 466698438, 466731879, 317967098, 466764558, 466799355, 317391342, 466913065, 466960746, 466993352, 467025937, 467043347, 467077884, 467159805, 36831316, 467288257, 467320891, 467337402, 467370018, 467387414, 467419199, 13582337, 467435542, 467451937, 9093151, 467471102, 19579771, 134627329, 467897088, 467943455, 186384414, 467959842, 467976209, 3457087, 468387662, 468437762, 468550452, 468582947, 71450683, 468650755, 468713757, 468746296, 468762658, 7241757, 468779645, 152764452, 7471163, 468811812, 468828218, 468844628, 468863748, 86425844, 468962053, 469043974, 53903363, 469221839, 469271227, 1114596, 469320574, 469355271, 469404424, 233521508, 12271649, 89325730, 469483642, 469502729, 469568266, 469664492, 469712932, 469729528, 469762318, 469795004, 469846795, 39354837, 469912332, 47317415, 470043405, 470092558, 470172095, 19235453, 177586233, 470204744, 470253924, 470289167, 470551312, 470712353, 470728740, 216678456, 19237649, 242794497, 213207826, 24182847, 8503332, 470747923, 470794433, 470827095, 122617858, 470878996, 470976719, 44302369, 471007425, 471042837, 49725498, 471173910, 471223063, 182878293, 471272216, 471419673, 126665285, 428622196, 471531604, 471547905, 87130130, 471567130, 36552901, 471698203, 60555426, 76792518, 471859201, 186810385, 471878428, 22937829, 471943965, 472006854, 472039487, 43794892, 472056052, 472104977, 220971044, 472121429, 27181345, 472156958, 472288031, 472334655, 472434122, 472481978, 472517408, 472566561, 472711615, 58425361, 472744386, 472828706, 473025315, 473104598, 473137374, 473170506, 473202724, 1785886, 473219156, 38797434, 98795779, 473238308, 473399385, 473432081, 24985674, 473448511, 45547553, 473467685, 13156385, 180404502, 473546785, 185548993, 287736001, 473563550, 473615142, 473664295, 473713448, 473811452, 473858065, 158908823, 473877289, 474188586, 474300474, 55869458, 474319659, 474401567, 19644839, 474448253, 114901221, 19742721, 474516268, 474597370, 474644912, 156582177, 474726417, 126664892, 474745645, 474808613, 474841397, 56770622, 177045521, 474876718, 474955793, 474972450, 201031913, 26705949, 475007791, 475119617, 475136348, 475169261, 475201623, 475252933, 475302704, 49905681, 475365594, 475417393, 475496864, 1114941, 475548466, 475595000, 45351313, 475627970, 475712307, 475792842, 475840779, 475876148, 475988057, 126075139, 476023605, 476072758, 19579316, 4669502, 476171063, 166428729, 476299450, 476334904, 476561656, 476594453, 476627029, 476659956, 16482306, 30245350, 163021201, 476708900, 476725322, 163397714, 476741876, 476791079, 476826425, 476907318, 476971548, 85819606, 104071185, 83558631, 477039418, 428294226, 477219643, 477331514, 248414266, 477348088, 463388701, 477380991, 477446203, 477462746, 477511950, 477545631, 113672493, 477594399, 80298066, 19579709, 477645628, 477824412, 477888701, 477921767, 477973309, 249446586, 7536657, 478052437, 478087220, 235684326, 478134872, 478186302, 478249118, 478281914, 478316327, 478382911, 55853057, 478448448, 478528429, 104661009, 478563137, 311214494, 478708126, 43827532, 478756949, 478790100, 478841666, 478939783, 478972739, 479035793, 479068244, 479084580, 388808720, 479100929, 160038968, 479117394, 479133752, 1474682, 479480645, 479562566, 451805266, 479693639, 479772926, 479822010, 479854946, 479906632, 87834963, 479986157, 113426490, 480018554, 157728826, 480034874, 480051217, 126337106, 480070473, 480133306, 480168778, 480264661, 480299851, 480412141, 480447308, 480559318, 480594765, 480643918, 480804897, 27181287, 7159825, 480821441, 24002983, 101433542, 390414395, 480856911, 480952378, 219512891, 480968721, 38535399, 480985332, 481037136, 37912829, 481248847, 10977339, 481315665, 481394999, 481460710, 481495890, 481558716, 269615293, 481607913, 114737354, 481640484, 481656888, 481674813, 481722590, 481755479, 19267670, 481790803, 481870072, 481902676, 19398962, 481919523, 332972455, 479854901, 481987412, 482050241, 482083907, 343490746, 128713066, 482118485, 482263124, 111657132, 482279709, 482312930, 482345153, 482377745, 95683603, 482394418, 26083345, 482429782, 482541690, 482558011, 95699130, 482574787, 374685754, 482607105, 482623491, 482639954, 482656453, 271745080, 482689040, 482705482, 482721989, 482754802, 482787386, 482803745, 482820155, 83558597, 482836496, 482852948, 387514404, 482869322, 482886179, 482954071, 483049762, 483082788, 483148327, 483180573, 483196945, 483213368, 483229778, 483246139, 405848080, 483265368, 483314521, 483642202, 31457466, 483721619, 345768735, 483754020, 45727775, 483770554, 483804618, 483852534, 483918611, 483967034, 22938001, 483983608, 484019035, 484081697, 27574331, 484100956, 371327451, 484148998, 484196629, 2378589, 484229313, 484264798, 122732580, 484360739, 153518294, 484427115, 484477672, 484526943, 484573435, 27181333, 484625248, 484753439, 484769810, 19235002, 484786398, 484819250, 103071930, 484851748, 127189379, 484871009, 93159506, 484982842, 40222779, 132792629, 484999257, 485032299, 485064993, 420806674, 485100386, 104169956, 485294162, 113475777, 485310712, 7536657, 267192287, 485343786, 29098333, 485378915, 485474600, 485507135, 50446391, 485523515, 121700554, 126665188, 485542756, 485589027, 16089152, 485605450, 485621834, 485638218, 485657445, 162663269, 485687607, 485753370, 485821286, 485900481, 485933115, 455540754, 72089633, 485949698, 485985127, 486047973, 486081053, 486113313, 486132584, 473415711, 435470420, 486476650, 486589909, 486639276, 486689643, 486738796, 486785406, 486834577, 486867192, 486899934, 184664122, 486935405, 487063863, 487129466, 487161872, 487178243, 487194640, 37027871, 84017185, 487211494, 487243970, 487276575, 123469860, 487293223, 427212836, 257589279, 487328622, 487441140, 487538958, 487571539, 126075147, 487604410, 19235239, 47906832, 487639919, 248168479, 487735859, 487784465, 18710935, 16482335, 487803760, 47677841, 487902065, 488000370, 29099055, 488096553, 488128700, 488180595, 488341521, 488357921, 488374460, 488423917, 488456755, 488506282, 36470815, 488538298, 207585296, 488573812, 488852341, 488917612, 488964129, 269664462, 488980666, 489013699, 489046075, 489062586, 115081274, 489095716, 489163638, 489226303, 489242708, 489259066, 7290938, 489275623, 149897300, 489308408, 24018960, 489341114, 407290650, 489376631, 489524088, 20840451, 489589625, 169967673, 489701379, 19267620, 489720698, 489898651, 489947543, 175489744, 489982843, 216105208, 490015612, 490078622, 490127630, 7340065, 490160765, 490193118, 490225697, 490242107, 141541412, 490258517, 490293460, 490340565, 490376061, 277414081, 48922641, 220070074, 490455226, 205996204, 490490750, 490670975, 490733626, 490750015, 375242755, 26427659, 490768356, 490815547, 490834816, 356876510, 490883969, 490930506, 490965890, 491015043, 491061284, 491077880, 491110622, 491143185, 491159861, 491193064, 491244420, 491391877, 22724730, 491454482, 18759716, 491471249, 491503675, 491520036, 1261626, 491536570, 491572102, 98779620, 491652656, 491686791, 51514248, 491752329, 491817866, 282656784, 491946068, 52822357, 73187344, 491965323, 492044474, 41910604, 380862481, 492080012, 492159098, 492175419, 75530426, 43188225, 492191920, 492224515, 492240899, 44548282, 492260237, 492325774, 114245665, 492437723, 492470996, 492503056, 492522383, 492571536, 492617786, 97583188, 492637073, 492768146, 492980538, 493013907, 493176184, 493223969, 113426448, 435437858, 73236666, 493240336, 493257341, 493289720, 493325204, 493404366, 493456277, 493551632, 29687980, 493568563, 493620118, 493764624, 7258168, 2377092, 121209103, 493782743, 41058320, 493830388, 493882263, 493980568, 91111506, 41992488, 494043378, 494078873, 494158238, 494206993, 494223637, 77004801, 494257572, 1785914, 12271679, 494324634, 86556734, 494453814, 494502886, 494534896, 494570395, 494633227, 494667589, 494717852, 494928065, 494960907, 494993467, 495009808, 473448481, 98550498, 495026739, 495076775, 495124516, 495141036, 495173865, 495206667, 493404424, 495240059, 495271994, 7880788, 495290464, 495370296, 495386683, 495403044, 25870339, 495422365, 242434328, 285901447, 4685825, 495534256, 475299856, 495567635, 236027962, 495618974, 495665400, 495700895, 495763538, 495779898, 10190907, 496176032, 441171999, 29098290, 496288953, 496356257, 496468052, 496484410, 496500961, 496533789, 496569250, 72859732, 496648804, 496698081, 496746554, 97484817, 496765859, 497011620, 497058141, 497090824, 497142693, 497189057, 497224614, 497273347, 62619834, 325419082, 497322919, 497371923, 497418273, 132644946, 497434683, 497451071, 444235777, 497467616, 497503144, 497566077, 497634217, 497681455, 497713449, 497745921, 25837627, 497765290, 22462816, 4800570, 108822564, 497847211, 498025701, 184402190, 47860198, 498059230, 498092972, 118571042, 498174893, 498238682, 111460538, 498287439, 498369179, 498417723, 119095498, 498437038, 498516162, 12156944, 498549442, 9093194, 498600879, 498682800, 498746290, 1114370, 22937930, 498797489, 2378674, 498994099, 499171580, 499204437, 49053697, 499254251, 499305396, 499368158, 18513979, 499400955, 31014928, 499449945, 499485621, 499581192, 423002171, 499630162, 499646466, 18579492, 499663262, 499712016, 20873275, 499728637, 499761851, 23494926, 499813302, 132464676, 499860571, 59031588, 499895223, 100630587, 500023586, 500059064, 500154862, 177586239, 500190137, 9945106, 500304826, 500337595, 500386748, 167283645, 500468670, 500646393, 500678849, 273465428, 500711509, 1114842, 500744406, 500777208, 500809921, 500842710, 500875323, 341491771, 500894655, 500957268, 500973911, 55918623, 501006917, 501039518, 1114195, 501088670, 501137750, 501170590, 2378688, 122617940, 97305422, 501222337, 501432813, 38797394, 501468098, 501793709, 421429330, 501827378, 501907472, 312280008, 501924066, 501956821, 501989460, 502005844, 8011792, 502022180, 331809021, 502039616, 502087989, 502122446, 502202556, 502251521, 502267967, 502284321, 178028874, 502302294, 502369219, 30246137, 502432443, 37847098, 502483685, 502547739, 502595809, 502628586, 22446634, 502696555, 502762436, 502824991, 135413762, 502841506, 502877125, 503155191, 503218210, 53100573, 503236635, 503333115, 503385030, 503414801, 503432397, 503466951, 503562325, 503596919, 56246289, 36520020, 503661247, 503710259, 22298710, 503759673, 503811016, 504021240, 504053818, 30245212, 504073161, 504155082, 504202113, 94388227, 504250384, 59064322, 504269771, 504447058, 72008250, 504463377, 7176254, 504479966, 504513250, 113311780, 192184356, 504548300, 178356520, 354091067, 240353316, 504660024, 504676434, 504692737, 504709204, 1114932, 286818320, 504728525, 504874396, 504938983, 504989239, 505036998, 505069786, 505121742, 505168323, 505200698, 15515650, 505217335, 505282643, 505315345, 505332063, 505400271, 505495954, 505545012, 26427815, 505593857, 48922708, 506072018, 117948979, 506200334, 506233632, 506265682, 52215809, 506284593, 506334163, 506446372, 22397224, 506514388, 365592860, 506642833, 59064377, 506675201, 506692070, 506727111, 506774313, 506806494, 506839246, 506888401, 31457629, 506938897, 172821461, 506986689, 507022294, 1654840, 507150906, 507202519, 507445452, 112886174, 507478301, 507510843, 47317157, 507530200, 1114364, 507756737, 507789394, 507805732, 507822164, 171819026, 507838465, 507856519, 95685593, 507887690, 507904082, 228458529, 507923418, 507969758, 508002337, 228458578, 508021723, 508068088, 44354524, 508100609, 508117798, 508150050, 508182529, 508199273, 508233664, 508281032, 508314299, 508365789, 508464094, 508513247, 508559419, 140083231, 378700082, 508578784, 508660705, 508772368, 508788739, 99368963, 508808162, 508906467, 508969157, 509001764, 143523873, 509021156, 509068832, 509168613, 509280292, 7913546, 509297528, 26443978, 509345986, 509379095, 509427745, 268943396, 509447142, 509575354, 347373569, 40452112, 509608483, 509674020, 54214870, 509742055, 510004200, 510100019, 300993513, 510151658, 22399979, 510266348, 510430189, 510707218, 510738433, 39649628, 510757870, 510823407, 510869690, 510902359, 312280348, 510951643, 510984762, 22464018, 511033914, 34209850, 511082859, 511115477, 511148343, 511213916, 152175285, 511249392, 32374843, 511344798, 511378800, 511428053, 511477715, 511527921, 511577074, 216940562, 511672629, 511705808, 511738349, 511773683, 511869010, 30163002, 511887674, 511918273, 511953908, 512019445, 512262199, 512278883, 512344122, 39010388, 512363510, 234242133, 512429047, 512573498, 512589841, 186220577, 512609272, 512655361, 512672177, 512704823, 512770857, 512805881, 452905978, 512886562, 144359691, 41912635, 20431457, 512951384, 513015809, 27770964, 513032408, 513066361, 513133563, 513245272, 513277970, 31457511, 513294421, 513330172, 513379325, 513507683, 513572897, 513589496, 179847354, 513625086, 513687740, 513739775, 60424276, 513884161, 47858659, 513900732, 31457629, 513949944, 513982960, 125403400, 514031650, 514048341, 514100224, 514132993, 176210114, 514441797, 147767355, 514474202, 514526210, 514572346, 98746425, 1114921, 255460038, 126025954, 284738029, 54804702, 514589028, 514624515, 514703737, 20856834, 151191553, 514769181, 514802332, 514853892, 514998328, 260784212, 19349520, 515017733, 69926948, 515145786, 7618577, 515165190, 43139135, 160399445, 4718654, 515230727, 189907026, 515325970, 515345416, 515473467, 170246202, 477265982, 515491413, 515539002, 515555796, 494223560, 515607561, 121077791, 515702843, 222167099, 515722250, 515916894, 105514352, 515981942, 516015823, 516066315, 173556532, 516131852, 516178626, 247185594, 516230157, 516344846, 516393892, 516440065, 30162961, 516459535, 516522047, 516538426, 183173137, 516557840, 516587805, 56246356, 132464695, 516623377, 516685906, 516702265, 1261604, 516718675, 516751419, 516767886, 7258145, 517098515, 517308417, 76414978, 517325044, 517375147, 517423193, 297156638, 41041979, 205684820, 38207489, 98320417, 517456259, 517488641, 218693649, 517508116, 517570577, 517586977, 517603386, 517619770, 343034901, 517639190, 517734695, 517768318, 263536657, 517816616, 517849146, 517865474, 517881857, 42156116, 47678007, 517901335, 291782692, 517950488, 55869499, 518046005, 518078495, 518094865, 31342610, 518114329, 518226113, 518258864, 518294554, 518425036, 518488465, 518521282, 5537809, 518604844, 518652397, 51150907, 518684939, 252674768, 518717662, 60915771, 167166074, 518753307, 518799418, 518816093, 56836178, 518848698, 518881743, 518930516, 24182786, 50708514, 518949916, 519160201, 519244829, 182879149, 519343134, 519471297, 519503955, 169721887, 519536656, 519553321, 519586103, 519654431, 519716866, 44564711, 45727745, 519733671, 509296842, 519766209, 519801888, 519867425, 519932962, 111460538, 519998499, 520077371, 520096804, 520142907, 520159445, 520192058, 48906299, 520208570, 48906298, 520244261, 373589818, 520388667, 520404993, 520421393, 82788577, 312197595, 520438511, 35242017, 520539174, 520588327, 520653864, 520735785, 520815155, 520863803, 480051263, 520880304, 144310354, 520916010, 521129003, 521207867, 521224418, 163659861, 521257946, 123076667, 521341996, 26198232, 109101315, 521404432, 521423495, 521454518, 521519178, 25870418, 521535572, 142328394, 521554057, 521617441, 521633879, 521686061, 521830641, 16515089, 521863652, 331336750, 23281680, 521899055, 159924281, 522027571, 522079280, 521948145, 522125623, 522193887, 522243121, 522421295, 522453023, 7929873, 522470114, 66961444, 522502180, 141607268, 522521650, 522567739, 522584277, 522617021, 205390877, 522649843, 522699047, 522732372, 367083537, 522780981, 522816563, 172853033, 163529127, 163774644, 522931252, 523010861, 523075642, 523092027, 72925243, 523111477, 523206740, 485326881, 523223057, 523242550, 523288824, 177586260, 523321375, 9666582, 523337762, 8503314, 523354129, 523370805, 523406391, 523501864, 523534337, 523550783, 523567306, 27590692, 523600286, 523616314, 334266424, 523649962, 523681808, 523698239, 63160376, 524043501, 524075071, 53100628, 524517498, 16482333, 524536890, 20627457, 130793530, 98795692, 524730369, 4669470, 524747777, 524815419, 524913724, 524992513, 317194303, 525009089, 525044797, 525091501, 525143102, 525192255, 525369377, 249004114, 39813121, 525387255, 525451322, 60197952, 525467810, 525503553, 47595845, 525680951, 285901826, 250888193, 525746798, 525782082, 7471137, 368115775, 525862037, 43827602, 525943537, 525995075, 526224452, 526289989, 526385418, 7077919, 256426305, 526419269, 526483726, 1114316, 526516647, 18710824, 90947778, 526549322, 526584699, 38731809, 526615060, 526680146, 526696449, 526712914, 526729217, 4669522, 526748742, 526896199, 526942426, 526991393, 527007760, 317964763, 527025370, 527076424, 527269971, 527302909, 527335482, 527351871, 99369023, 527368555, 527401901, 527435712, 527483066, 527516201, 80412728, 527548772, 372457561, 527584329, 527679889, 38797314, 527715402, 528025990, 528073086, 528121915, 528140936, 32899105, 528187806, 528237282, 2378827, 528272460, 38027320, 528419917, 528482335, 92881319, 64536661, 528501838, 528714831, 346210497, 528780368, 528891935, 18563102, 528911441, 226754591, 27181766, 529039418, 465764436, 529058486, 529105692, 529155987, 529204065, 529236004, 529252369, 529268984, 223903777, 529302939, 529350715, 27181333, 529368339, 529417182, 529465557, 22462900, 529498197, 64864319, 529530965, 77562054, 56328251, 529566802, 529779795, 529891329, 529907748, 20856849, 10715137, 529927252, 530022614, 530055510, 530088499, 18727106, 530137124, 530154615, 530186299, 112509002, 530205781, 31391946, 106315793, 530285146, 530350566, 530383034, 530415815, 530465079, 530533462, 38649930, 530598016, 530680919, 530762840, 530825691, 530860422, 530907736, 1114326, 530956782, 530989300, 531038817, 531071189, 531103761, 531120129, 156958723, 531139673, 52543825, 135102749, 531481222, 531532890, 531808504, 531844187, 94584894, 531890207, 531906682, 55640186, 531923896, 20414495, 531973799, 532021249, 532037635, 4800515, 532054074, 532070459, 532086840, 350322770, 532103731, 532155484, 532236255, 99336251, 532269800, 532316408, 532348930, 532365370, 368656852, 532381755, 117555236, 532401245, 532499550, 532611287, 532645001, 532696159, 532792197, 145719602, 532857141, 532889981, 532955320, 533040224, 533122145, 533203587, 21135361, 193055158, 52543750, 200376321, 533253218, 533397717, 533430331, 287719589, 330891758, 533447058, 93225493, 533496001, 22398068, 533529535, 533594391, 533627373, 533661557, 533709363, 533758353, 533793296, 533856507, 533908579, 56344824, 534318181, 41992417, 46252095, 534413496, 211501722, 361038120, 534497085, 534547558, 534642689, 106315835, 383320122, 165888637, 534662247, 534940776, 534987000, 359219263, 317964954, 535022697, 535153636, 535199957, 535235690, 535298122, 535315392, 535347490, 133726439, 228147282, 535380055, 535429786, 535462141, 535497835, 535593019, 226951169, 535610779, 535659467, 197689403, 535708062, 273465345, 535759980, 535809133, 535874670, 405507183, 535953466, 535969990, 536002871, 536068948, 536120432, 536248616, 536281105, 536297669, 536331261, 536364185, 536398961, 536497266, 536576084, 536592402, 97026227, 536609897, 19529942, 162791733, 536691050, 26182033, 38371361, 536723906, 536806096, 395362367, 536838682, 41992408, 536906867, 537035015, 537087092, 537152629, 537234550, 537283226, 537332855, 537395393, 101581048, 537431160, 2376082, 537740425, 537788660, 18710787, 217612305, 537837569, 54853649, 537854010, 537873529, 537936571, 40140817, 537988218, 538066980, 538083386, 136495188, 248135696, 538102252, 538165434, 29688951, 538201211, 538263796, 538314691, 305610932, 538362870, 538427703, 538496124, 538673211, 2129936, 198082722, 538692198, 538758269, 107823335, 538806206, 538853562, 538886146, 23314448, 538902714, 538938494, 538984692, 331336831, 5537972, 539036800, 539133386, 539181178, 22298683, 18579514, 539197890, 24985617, 539279611, 539331713, 539525202, 539542563, 539593858, 539675779, 539738885, 126156859, 539804041, 539888772, 69681422, 539937925, 540131946, 86130958, 52545216, 540181685, 540230762, 27181997, 44662817, 540278960, 540314758, 540409938, 540426276, 157745236, 540442817, 540475720, 540527744, 540623146, 540691591, 540822664, 540888201, 180404502, 534855713, 540968655, 541016065, 136495121, 541032484, 541049897, 29688200, 541084810, 541261860, 541278244, 541297803, 541330572, 541427071, 541491217, 18727319, 40895310, 516587549, 538017874, 541510797, 541723790, 57294879, 541770309, 60424228, 541802749, 541835349, 2375931, 2378056, 541868117, 541902291, 541951652, 541983134, 47858735, 542032058, 134250694, 542066624, 542113978, 542146647, 542198927, 542310670, 33177618, 542343569, 542379152, 542523578, 542559377, 19644779, 542621780, 240353338, 539593875, 543050900, 543244346, 55918651, 543263893, 260800568, 543539448, 36405307, 54607908, 543571999, 7405585, 543588540, 543637973, 543670458, 347079131, 543703262, 543736104, 62439457, 543768639, 543784993, 36405307, 380813865, 543801375, 543817729, 473448507, 543835274, 543886099, 543935638, 26183582, 544030802, 544049200, 544096749, 20250660, 544132247, 69517796, 79316243, 544279704, 544459826, 40370177, 544505941, 544538625, 119734334, 544555201, 544587978, 544620603, 492060708, 544637010, 449675323, 544653313, 452821051, 544670182, 544705689, 544836762, 544899434, 12075044, 317964500, 544932110, 544967835, 73646162, 545033372, 545112283, 545146041, 545210619, 545260320, 17645626, 545295517, 545390806, 42680806, 545423445, 545459358, 47857985, 545557663, 545653314, 545702290, 80298017, 488390887, 36290615, 545754272, 54919738, 545918113, 545983650, 233603265, 546078756, 546098339, 546147492, 546325276, 546375223, 80298068, 546423957, 546507886, 546586838, 333824122, 546620542, 113672378, 1114309, 546671781, 546802854, 546865386, 546930910, 22462734, 546963707, 547013661, 547045434, 547061842, 547078202, 547094610, 7094330, 547114151, 547340346, 171983062, 7471186, 547359912, 547507369, 17793079, 212451329, 547586107, 119062586, 547602490, 444235835, 171984308, 547618871, 547635259, 43925540, 547652915, 547684385, 547701220, 547733896, 547766308, 28557713, 547782899, 547835050, 20693008, 547916971, 432586769, 548129964, 548242363, 390234170, 358760785, 548290596, 158662712, 134365200, 548306978, 150110835, 548326390, 548392109, 548474030, 548519937, 7847961, 548539567, 216137764, 548815596, 257753147, 548864795, 548913169, 309329975, 548929553, 548946812, 548995285, 549028026, 549060990, 549110023, 549162160, 549240848, 507904003, 549260465, 33554655, 549503308, 549552186, 549568782, 549601296, 549618250, 549651275, 549699944, 549751986, 26279952, 549830692, 549847098, 7471178, 44352666, 549863482, 98795603, 549883059, 550011143, 550061239, 550109377, 550142515, 550194356, 210239546, 550305985, 550341813, 10715153, 550423284, 550472886, 550633666, 550667725, 550715662, 550748456, 550781002, 550798489, 137674755, 550830268, 550879418, 550912017, 550928458, 550948023, 550997176, 551059540, 551075856, 7340087, 57212957, 551092283, 551108690, 551125240, 551161017, 551224792, 551275706, 551536129, 551600709, 39747706, 39976962, 551636155, 551698646, 551731506, 551764462, 180142096, 551797117, 551865532, 83263550, 551931069, 108937217, 89293284, 551979199, 552026186, 473662693, 552042989, 552075665, 552108095, 552124452, 552140858, 444235898, 552157747, 552206717, 552275134, 552354051, 55722269, 552389823, 552452170, 552468964, 552501445, 552537280, 552616396, 22200383, 361201697, 122617887, 41913537, 552651970, 552730706, 552747066, 22462664, 552763477, 2377165, 552799427, 547127352, 552914116, 553042343, 553074939, 553127109, 553486464, 553567088, 2378950, 103579679, 553651399, 553766088, 7405690, 553926878, 553960018, 553993900, 185663546, 554041429, 554074684, 554123495, 554156441, 554189680, 554271296, 554306761, 554385490, 554401851, 554418192, 67551250, 554437834, 554500154, 554516554, 554532900, 26411092, 554552523, 52035828, 554631464, 554667212, 554746075, 554778642, 554798285, 554844176, 160399630, 554860545, 398475531, 554880206, 66519056, 554992459, 555043785, 555123852, 555172180, 555240655, 22447172, 555322576, 119210066, 50872668, 555404497, 459292730, 555485404, 22265872, 555535570, 555614610, 284639290, 555666643, 54558900, 555712570, 39649577, 555732180, 555814101, 555909153, 555925505, 555942082, 555974714, 127615009, 555991228, 2377464, 556041005, 556105942, 556138559, 556154936, 556171346, 21839929, 556188062, 556240086, 556384315, 556400656, 556417087, 556433466, 135249984, 556449850, 556466235, 176537636, 556482718, 556516404, 556567767, 556647966, 70091049, 3457026, 556698840, 172870777, 200720570, 556795155, 556843094, 456556545, 556862681, 557040915, 7913508, 146243669, 557089016, 114901933, 557121658, 57294866, 557138002, 557154386, 557170770, 557187255, 127090871, 557563937, 19234993, 557583579, 557696123, 557777867, 557826107, 557842626, 276447295, 38223954, 557877516, 121077791, 557927644, 558039941, 558104631, 558120976, 558137377, 38748193, 558153745, 558170186, 558104631, 451625146, 53415133, 558189790, 185663572, 558270214, 558317599, 47153419, 558337247, 468598842, 156958783, 558484704, 558563387, 20906040, 558579728, 20250708, 558596170, 558612564, 558628893, 558645251, 558661714, 558678322, 386416673, 54215250, 558714081, 29081663, 558858458, 558907409, 558924042, 558956560, 126861345, 558972945, 558989578, 559022096, 559038497, 559055457, 559090914, 559284313, 559317539, 559382545, 144572434, 559400522, 559431762, 559448097, 559464932, 559497464, 559530020, 24985684, 559546554, 559579308, 559615203, 559644730, 14041130, 559661143, 559710264, 31703102, 559726778, 127418562, 559762660, 559972510, 560005317, 560040094, 560090341, 7536722, 560153264, 560221414, 560283707, 560300033, 127369233, 119947265, 309329975, 560317411, 560349370, 171412711, 560382010, 560400920, 560450792, 162234427, 560677050, 560709716, 147767380, 560729321, 60195668, 560827626, 560909547, 260456641, 389513447, 560958700, 561057005, 561152084, 561168385, 561184797, 88784897, 561201368, 561234042, 499367937, 40452154, 561253614, 561463756, 561499375, 561561684, 561578020, 299270266, 53870808, 561597680, 561725772, 561775471, 17629220, 561843441, 561938916, 561972078, 562036929, 152158225, 562072818, 562118672, 20627519, 562135073, 50692155, 562151460, 562167864, 128008274, 562185996, 562234507, 562315559, 189431810, 18759738, 562348659, 562384115, 562447092, 562544972, 562593855, 155009057, 157171888, 562613492, 562659387, 57212929, 562675728, 562692129, 562708481, 16515074, 4636754, 562728181, 562859254, 562957559, 563036196, 563052577, 5603391, 563069157, 563104410, 563150930, 563170552, 563216473, 563252473, 22397510, 2376951, 563380412, 563429377, 27574289, 563446323, 563497765, 563579667, 563626333, 563658811, 563675333, 563709502, 563757092, 200228880, 563776762, 563937362, 563956987, 19235035, 564035620, 564052030, 370639034, 99844154, 564068707, 564134094, 192250042, 564185094, 564232276, 29098517, 564248816, 564281584, 56229890, 220070177, 564727036, 564824245, 564888532, 564937038, 564989181, 565120254, 565183551, 197378224, 565264400, 565280823, 24805431, 97026440, 565300479, 565348759, 565411841, 565428810, 565461025, 565477569, 565510758, 565559584, 565658599, 565723402, 19644683, 565759232, 104384159, 171295188, 565906689, 18710824, 566001700, 83263505, 566018084, 31342609, 7209514, 566037762, 566216640, 566264024, 566296882, 12271619, 566330091, 47317485, 566381827, 566447364, 566558934, 566591681, 566624453, 566657025, 74072095, 566673584, 566706369, 566738977, 566755521, 566791429, 566935822, 566971654, 567017873, 567051589, 567116747, 567166016, 35799098, 567214256, 567246849, 567263700, 567315719, 567410965, 567443722, 567477188, 9093202, 567526763, 567574730, 567610632, 527679837, 407290621, 567672833, 26411044, 567692553, 568036618, 568082645, 568115470, 18727299, 568148248, 568180738, 19267639, 568200459, 568328859, 568380684, 568443066, 568478989, 1359889, 568575040, 568626446, 135545164, 36472005, 568724751, 568839440, 568918034, 568935196, 568986735, 569050246, 569082049, 569115057, 569148989, 569199889, 569311639, 569347255, 569396498, 529465402, 569429266, 569462035, 569557884, 78201407, 569609492, 569671696, 569688080, 19546130, 569707797, 569835553, 569852117, 569888022, 569950209, 7929887, 569966651, 569982977, 569999600, 570035479, 150781955, 570164531, 570195970, 67272759, 570212892, 570281240, 570346777, 570490939, 4292666, 570508285, 96272468, 570543386, 87097345, 31309882, 570625307, 570671357, 36470800, 570707228, 570785985, 570818808, 570854685, 570966485, 570999421, 571034910, 571228408, 221577619, 571261086, 571294266, 571342849, 139726111, 571362592, 571392001, 40861729, 45547537, 571408385, 571424842, 571441210, 691489, 571460898, 571539658, 571572477, 571605245, 443940922, 571641123, 571703332, 571719698, 163020993, 571736122, 405881046, 571753386, 571785274, 571801664, 571818015, 571834369, 571850836, 197902642, 571870500, 572067109, 572113256, 572165414, 572244001, 22937948, 572263719, 572407871, 572424225, 19071034, 572440824, 572473360, 572489745, 572506489, 69926943, 21069886, 191925446, 231097712, 40206352, 572573501, 223150111, 572622644, 572703124, 572735547, 572752106, 1116507, 572817439, 572833794, 572407811, 572853544, 448970834, 573115689, 247463967, 573177892, 4784145, 573195907, 112888109, 573276346, 69255242, 573309283, 573374650, 573407397, 573443370, 573538574, 573571073, 46268433, 573590827, 573738284, 573833402, 573867071, 573948188, 574013503, 539115553, 174654276, 574033197, 574278958, 574341306, 51347538, 49022149, 574377263, 574439440, 574455825, 36258059, 574472440, 571719697, 574508336, 574637120, 574686249, 47858124, 407356058, 365579378, 574721187, 574767307, 574803249, 574851618, 59064349, 402866232, 574898626, 574980497, 87179282, 575016242, 575078597, 22446252, 575111834, 575147315, 575262004, 575406081, 144031762, 575422546, 43581671, 575439096, 404980583, 575474633, 575553553, 575570162, 575602744, 575619103, 210550868, 34226177, 575638837, 575835299, 575884598, 575996980, 576045092, 576061749, 576094244, 576110804, 576143361, 576160311, 576194133, 333938959, 576242720, 391840392, 576310583, 576441048, 576537062, 126665197, 576569745, 576602298, 576638264, 576897492, 576946261, 1117497, 576979106, 339279934, 40075280, 577011796, 41913658, 577028132, 76120123, 577044565, 117653505, 242434225, 577077434, 152322624, 577110017, 577126436, 577142868, 1114139, 577159582, 577211707, 577274303, 577307372, 96879060, 577356085, 577391932, 577618380, 577650874, 577683642, 577717429, 577765970, 577798847, 577847540, 577898308, 577948176, 29065298, 578014525, 578096446, 578243903, 578486331, 444235792, 578502740, 578519098, 31145985, 578535455, 57573434, 578551824, 559382612, 578568501, 578601018, 578617347, 116965562, 578633948, 578700196, 158498832, 578732048, 3211281, 578751808, 160759890, 578817345, 578912450, 578945925, 4669514, 579013954, 46448699, 392184098, 579092538, 55869443, 579112259, 579325252, 579420219, 579436748, 579469597, 577929279, 579505477, 126075554, 168165685, 579617012, 39649954, 579666005, 579702086, 579797185, 579829822, 579846170, 4685859, 579865927, 579993629, 29507617, 580010039, 364118032, 19399065, 8486946, 409944120, 580026603, 580059402, 580092197, 580128072, 580190529, 580619594, 580668747, 580799820, 89293014, 135413761, 580894779, 49905697, 580911564, 580945019, 581025847, 581042746, 301252666, 581091361, 581107770, 1116258, 581127501, 581288020, 581304506, 581337313, 581369944, 84787393, 581405563, 581438798, 581566527, 570409509, 36519969, 581586255, 47858273, 581651792, 581733713, 581861393, 26199237, 581877793, 581894474, 51347489, 8011811, 549847041, 581926945, 581894202, 581946706, 582092396, 25870372, 582157647, 275350256, 582238399, 331337043, 582303826, 185663519, 582320621, 582356308, 582418433, 582435032, 130990081, 582469697, 582536533, 126075303, 582582465, 582618454, 582795594, 104825026, 582829654, 582893762, 582926745, 582962519, 583044440, 583126361, 583319569, 583339354, 583450657, 404291862, 583470062, 583532603, 583549195, 41992450, 20463781, 583583529, 583631079, 583664425, 583696420, 583713362, 583747709, 191873976, 583811089, 583827530, 295698487, 583844511, 419971479, 583893805, 583961947, 584073511, 584106002, 584122404, 7241745, 584138965, 65028155, 584172083, 584224092, 584417362, 584434917, 584467329, 584515642, 512884754, 584532750, 41353272, 584567669, 584630695, 584663237, 125501499, 584696769, 29098198, 584761530, 584796639, 584843739, 584879453, 584974392, 62439486, 584991195, 585026910, 368083239, 585141599, 533397562, 56131620, 585190752, 585239905, 89113069, 585384883, 585466186, 585499047, 585534818, 22921550, 585598515, 1115165, 585679020, 585711617, 337772561, 585729162, 585780579, 585878884, 85265765, 586023190, 586059110, 586138699, 586170399, 586186755, 54247427, 586204097, 586268969, 57426229, 586302010, 586350750, 85264429, 586384667, 586435943, 586547282, 586567016, 586613068, 586662184, 586695063, 73236565, 586728825, 132792813, 586796393, 586845546, 18563106, 586924225, 586960235, 587188890, 587235364, 20693009, 587251795, 587284693, 41913708, 587317335, 587367119, 27574356, 587418989, 587464786, 587481172, 587497488, 587513915, 587530276, 19579275, 587548205, 587612196, 587628602, 587644945, 587661498, 19644658, 587697518, 587759921, 2376148, 587828591, 167562608, 587894129, 587972831, 588025202, 30801979, 34209795, 588136506, 283639825, 588152890, 588169233, 588185633, 395018276, 588202000, 588218635, 588251172, 318833075, 588270963, 588349891, 588382224, 588398609, 588414994, 65699899, 588431396, 588448011, 588480570, 218628155, 588496980, 588513309, 588529720, 588546106, 59179010, 588563013, 588595217, 588611817, 588644637, 588677383, 588726308, 49676344, 588742715, 588759056, 588775460, 57098258, 588791824, 67551316, 588811636, 588890170, 588906555, 588923066, 383713381, 588955665, 41648146, 588972066, 588988474, 589004814, 589021210, 18513934, 589499766, 497599382, 455855479, 19382813, 589594650, 589611082, 15646775, 589630840, 589742096, 589758688, 487079994, 59588609, 589791975, 589824551, 589856952, 46448696, 589939123, 63258640, 10862666, 589974905, 590086175, 1065015, 590103189, 590151764, 13287508, 590168065, 57278546, 590184451, 50462779, 57114641, 590204282, 590318971, 590447210, 40075323, 210124858, 590496381, 590528513, 590544913, 73089042, 578732033, 590561774, 590594270, 10960951, 590627546, 274481627, 18710813, 8683562, 590663036, 2376109, 590826877, 590876030, 590955357, 591004171, 591053175, 591085889, 591118369, 591134756, 591154559, 591200453, 591234622, 591282466, 591318400, 591380509, 591396881, 591413304, 591429691, 591446017, 54247487, 591464021, 591511553, 10960955, 591528499, 421429305, 5537972, 591577489, 591610162, 591642658, 25428026, 591659041, 591675454, 350306321, 26984465, 27951188, 591692151, 591725630, 591773755, 591790154, 133890133, 591809921, 591921237, 591954640, 591986999, 204193848, 592052283, 592068852, 592118109, 2378802, 280461313, 592153986, 592216097, 592232506, 11354141, 592248890, 592265233, 40173690, 592281822, 592314404, 592330788, 592347172, 476791083, 592363521, 13303810, 592380212, 592432515, 592514436, 592642241, 592674938, 592691216, 22528017, 592709381, 592792965, 19382491, 592842118, 592970135, 593002718, 593035467, 593068068, 24477698, 593086661, 593134339, 57376842, 593182946, 593215524, 593231889, 593248257, 593264673, 274268346, 593281226, 593313851, 593330398, 593362977, 391676090, 593379552, 593412154, 593431943, 593510416, 593526815, 2379144, 593543170, 593559588, 38977553, 593579401, 593776010, 593854650, 22937798, 593887249, 593905438, 593970272, 594021771, 594100312, 594136460, 594182180, 594198612, 594215098, 594247696, 26984522, 594264277, 37224522, 594296868, 93159482, 594313432, 594346000, 594363177, 594395499, 594427962, 129450001, 594444472, 594526420, 152567839, 594559306, 28459067, 36831291, 594592064, 30703652, 26083391, 25985087, 594657749, 594690107, 594706433, 594723010, 594758633, 594804772, 594821204, 594837690, 594870866, 594903041, 594919508, 576208932, 594936002, 594969534, 595020000, 595119501, 22954177, 402702369, 595214399, 595230906, 53657636, 595266958, 337510456, 137248824, 595361828, 595378525, 595411300, 595443745, 137248827, 595463567, 595558433, 497598695, 595574842, 595591224, 414679073, 595607800, 595640336, 595656721, 595673089, 595689958, 595722276, 12664849, 595739074, 595820881, 392003647, 595853314, 62259258, 595869868, 595902686, 595935455, 595987856, 596066337, 596082922, 596148241, 596164820, 596198662, 596262948, 596279351, 596295880, 596328533, 596361400, 387514424, 596446609, 596492305, 596508673, 596525259, 596557887, 596574394, 596607280, 596656145, 596674109, 596721960, 596754448, 332955665, 596774290, 379502595, 596836565, 22560827, 596869156, 21200954, 596885690, 596918568, 596951360, 597016639, 597033146, 597495188, 597655774, 597688721, 597721300, 597754186, 595836991, 597786683, 597803009, 597819604, 597855637, 597901347, 597917727, 597934081, 597950676, 597753892, 597983290, 597999675, 266485793, 598016036, 598032442, 137248785, 598048954, 598081539, 598097979, 598114320, 598130691, 594591803, 598147105, 598163492, 594526264, 526385154, 598179914, 598196255, 598212667, 416104449, 598229245, 598261818, 405881188, 598278813, 598412694, 271597601, 598491137, 598509797, 598540324, 25002017, 598557147, 335659066, 598592514, 598641030, 598688011, 143572993, 87179295, 598723991, 598802490, 598818833, 598835412, 598868197, 598900794, 598917179, 598933505, 598949921, 598966357, 598999271, 599032790, 245350417, 66256914, 599100824, 154140689, 599179322, 599195707, 143573050, 599213895, 599277599, 599293969, 599310372, 65781816, 599328335, 599392679, 53657633, 59277370, 599425082, 599441490, 599457850, 595836945, 599476488, 599540205, 599572516, 137248854, 599588895, 30801983, 599608729, 599670819, 599687199, 599703610, 599719953, 28459041, 599736545, 599769121, 599785530, 599801873, 470925524, 137248852, 599818276, 271220792, 599834683, 599851024, 599867478, 599883834, 599900219, 392003617, 599916602, 599932945, 599949348, 599965754, 599982097, 57393364, 600000610, 335659024, 600031418, 22511672, 392003658, 600064186, 600100250, 274628671, 225476644, 593182722, 600147650, 600195161, 600227873, 36962535, 12664887, 600244994, 600293961, 600395163, 600555706, 35012671, 49905682, 600588474, 600621073, 600637662, 600670244, 600686648, 600702977, 49725473, 600719393, 600735833, 600768570, 600784913, 600801298, 600817722, 600834065, 600850644, 600883412, 600916027, 600932388, 600948791, 225476609, 600965179, 600981535, 599900216, 600997946, 601014331, 386465794, 601034140, 601178663, 601211484, 601260035, 158056464, 601277010, 601309217, 601325756, 54182515, 601374842, 601391162, 53657659, 601408241, 601456936, 601489630, 601522369, 601558429, 601604155, 601620538, 601637040, 601670095, 601718801, 137248831, 601735204, 601751569, 601768010, 601784351, 601800740, 601817151, 137248850, 601833508, 601849874, 601866271, 601882625, 601899092, 23233035, 601918878, 37650514, 601980986, 159154239, 602000799, 602062884, 602079265, 72335361, 602096113, 602144842, 602161338, 360251611, 602193953, 602210363, 602226885, 602259870, 37847096, 602312096, 602472507, 270401572, 602488954, 602505402, 602538040, 602554401, 602571045, 602603576, 12664868, 602620104, 602652703, 54149156, 602669092, 602685473, 596262913, 602702160, 601718815, 602735085, 602767630, 602800164, 602816570, 602832913, 25034808, 602849316, 600948792, 602866128, 602983841, 392003668, 603078689, 603098530, 92373393, 271613983, 603179769, 603226145, 603242714, 603291837, 603327907, 603389968, 100581435, 30245112, 245350433, 603407871, 206372922, 117555282, 335659041, 603505077, 603537441, 603555469, 66371586, 603620310, 603685185, 603718259, 416399361, 603750644, 603799833, 266338360, 603833459, 603865105, 603883333, 603931362, 603963450, 64159803, 603979778, 603996394, 604063178, 604110884, 604127439, 118390801, 604160222, 604195577, 604241953, 604258363, 269041695, 604274705, 261931082, 604291270, 604323876, 353173505, 604343716, 604504148, 604520664, 604556709, 18530320, 604651813, 604684578, 604717119, 197312528, 604733523, 604766394, 165708576, 604799160, 604884390, 604946618, 51003425, 604979218, 25460772, 529547322, 604996566, 605061687, 605093924, 605110305, 605127452, 605176052, 605225019, 605241530, 89669665, 605274167, 605290532, 605306913, 605323452, 605372418, 32686081, 605392295, 432456127, 605454372, 605470721, 605487590, 605520058, 605552944, 605602122, 20283448, 605634892, 253886706, 605684275, 605732897, 27754532, 42680536, 605749496, 549503980, 605782264, 89112770, 605818280, 605946279, 605978822, 73760770, 606012779, 606063354, 606093642, 606126097, 606142676, 53412363, 34979841, 606175859, 606208017, 606224402, 28409872, 595443770, 606244265, 606306362, 591986705, 606322691, 606339088, 606355515, 606371873, 21184543, 19579165, 606388255, 606404794, 606437574, 606470707, 606519332, 606535713, 606552295, 606588330, 606748866, 137248804, 606781693, 606814501, 606847193, 606883243, 606961841, 606994690, 607027259, 262750394, 607043620, 271613969, 607060031, 607076388, 607092769, 607109156, 36831318, 595443713, 607125537, 20349114, 607145388, 607223994, 607256985, 607289377, 260129638, 607305764, 607323900, 607354916, 31211576, 607371697, 607404065, 607420634, 360251611, 362397754, 607473069, 607518906, 607551544, 607567905, 607584444, 607633467, 607650103, 607717037, 607799883, 607846484, 607863078, 607928532, 607961329, 607993873, 608010298, 62406672, 22464144, 608030126, 608092216, 608108577, 608125234, 608157727, 608174113, 608190650, 608223248, 608239633, 608256229, 472105044, 608288799, 608306017, 608337953, 608354492, 608403514, 608419857, 608436257, 608452837, 608488879, 608550928, 608567355, 608583698, 608600100, 280281105, 608616570, 608633018, 608668508, 608718256, 608796688, 608813087, 325779473, 269107258, 608832945, 608947363, 608996786, 609075413, 609108190, 609141222, 609173588, 609190074, 609222712, 609239073, 233259039, 609257862, 609304762, 39780929, 609337867, 609386512, 609402897, 609420060, 609471923, 609714232, 609730762, 68042788, 609765513, 609831952, 609861633, 609878033, 609894474, 442269754, 609914292, 132169729, 609959999, 609976353, 609992922, 610041873, 22560842, 610058433, 21020728, 610091067, 610107580, 610156973, 57491457, 29114553, 610206143, 610238650, 610271250, 610287652, 610304001, 39780775, 610321771, 12664865, 610370036, 610451942, 610484260, 610500624, 49676305, 610519666, 610566145, 610582545, 610598970, 66371617, 610615354, 610631713, 610648308, 610697508, 411140170, 610762755, 610779169, 610796889, 610861232, 26099768, 610894017, 97075284, 226951225, 610926836, 610975761, 610992625, 611041338, 611057721, 611074425, 611139617, 574488612, 611155970, 611172574, 611205332, 264339514, 36667410, 4669462, 611239161, 611270714, 611287099, 332677121, 611303460, 414990392, 611319992, 611401814, 611418170, 608010257, 20152323, 611435703, 611483711, 57737252, 611500257, 611532831, 611549240, 611566065, 611614723, 283738170, 611631329, 611664147, 611700149, 270401554, 611745976, 611828794, 611893434, 19235111, 611926309, 263749650, 602079268, 611959116, 612007969, 612024508, 612073739, 612106273, 612122975, 612188629, 612220929, 612237345, 612253884, 612304847, 413564944, 392003640, 88735780, 612369072, 612437430, 612483287, 610598929, 612515924, 606191633, 612532554, 612565051, 39059492, 612584887, 612729628, 612778047, 5603384, 612797880, 65732664, 310558736, 119062585, 612859938, 612876322, 9404449, 612896185, 613092794, 12075024, 20430884, 613173468, 613223867, 613318738, 93228476, 180404417, 613335750, 613367825, 613387709, 13156436, 613499956, 613548276, 610467873, 613600702, 613711930, 613728314, 137248770, 325779487, 613744671, 613761108, 613777409, 613793851, 21200932, 613810234, 31211606, 613828430, 613875713, 613892152, 137248826, 597753914, 613910700, 613941311, 613957665, 613974238, 614006800, 614023185, 596230207, 614039637, 614072379, 614089188, 614121859, 614154241, 614170827, 198459450, 162758692, 614206911, 614436288, 614581289, 614616513, 614662202, 614678612, 614698434, 31457473, 614744784, 310673429, 614777061, 1114832, 614809786, 614844199, 614908346, 614940920, 126665672, 614973734, 1114661, 615039189, 32964665, 615075267, 615153665, 29081663, 615170049, 30245173, 615189271, 615235777, 615270043, 615317522, 615334628, 615383041, 277414074, 615400650, 615452100, 615530497, 36700193, 615547073, 615579706, 615596116, 615612722, 97304843, 615645273, 20430904, 615681477, 615727121, 76644407, 615747014, 615891028, 615907525, 615940097, 498761745, 615956562, 615973427, 616022033, 272531514, 616040356, 192184379, 616104469, 616137154, 616218920, 616253190, 616301476, 616336839, 290553892, 616431875, 616464469, 4358161, 60555554, 47500486, 616497422, 616529921, 616549832, 616629789, 616680905, 616779210, 616824863, 42205220, 616841247, 616857636, 257605690, 616875290, 22462887, 617483725, 617578807, 617644091, 617663715, 617693200, 617709585, 617726210, 617762254, 617906390, 22528031, 233833268, 617938961, 209944780, 617958863, 618201304, 618233919, 19234998, 618253356, 107823301, 149831989, 22216901, 618316400, 618348575, 336937018, 618364929, 618381367, 582139959, 618397712, 618414097, 54447568, 618434001, 618499538, 618595437, 618643793, 118095888, 618677573, 618745299, 618823877, 61456401, 618856465, 1114421, 618873325, 618909140, 238108688, 99369034, 618972924, 108249281, 619007445, 84131898, 619119586, 619151787, 499924993, 619184398, 29098263, 619218036, 619269590, 619365281, 619413690, 619446564, 266191252, 619515351, 619580888, 619610113, 348094527, 619627443, 619708419, 26984506, 57294906, 279249136, 619727695, 619826649, 42156088, 286801936, 619892186, 619974107, 47267856, 620088796, 48496807, 620167354, 620200245, 620236253, 2379230, 241060187, 620494931, 620527802, 620563935, 276808072, 30507092, 620760544, 620809697, 620937608, 110413165, 620973538, 621035768, 621068347, 621086293, 621134637, 106741817, 621199594, 621265095, 621317603, 621398330, 100155455, 621432292, 621510885, 621543653, 621576209, 621592634, 19251684, 621608991, 621625588, 621678053, 387629323, 621743590, 82264066, 73351399, 622005735, 622068126, 622117086, 51773665, 622150082, 62620071, 622235112, 63275044, 622297104, 622313474, 100155447, 622333417, 132924463, 622460964, 622477329, 3211322, 58769425, 622497258, 622559846, 622608442, 622628331, 622674225, 622739857, 622775788, 622854203, 622870786, 622906861, 623132673, 153665593, 623149300, 623198209, 170295380, 623218158, 60181518, 623575441, 623608039, 244564001, 623640607, 49905727, 623660528, 623886337, 623904866, 623935521, 35864612, 623954519, 624017467, 624033793, 624050193, 229965880, 103710756, 579372084, 624066561, 624083000, 624099363, 13303839, 624115728, 26968095, 624132407, 624197941, 624230436, 42205242, 88555536, 624247532, 624295954, 18563108, 624312382, 182157329, 624328740, 64864312, 624348657, 21233970, 624446962, 624494285, 624545267, 624705595, 624724332, 39256080, 12075066, 30687291, 624804403, 196559107, 49348666, 624853354, 624885846, 624902145, 52232274, 33832989, 624922100, 624984445, 77562153, 625053173, 42156090, 625099623, 625135094, 52953147, 625364471, 625430008, 126665595, 625493369, 625558051, 688158, 625626617, 625724922, 625804155, 26182853, 198426709, 357974050, 625836608, 625868801, 625885243, 625901586, 625918149, 27787298, 625951658, 625983972, 626016454, 626052603, 626281323, 235536402, 618856506, 626331132, 626376901, 626413053, 37912738, 626476137, 626556929, 98926869, 374685732, 626574452, 626622845, 277889196, 22446655, 626688063, 626704385, 129450068, 162709690, 626724350, 626886606, 626970111, 627052032, 19628861, 627114202, 627163442, 627199489, 627248642, 5406779, 627327287, 627392866, 627445251, 627523620, 53903444, 627540454, 627576324, 32948282, 627640881, 627691013, 627736634, 59064379, 566018064, 627756550, 236585149, 49905786, 627953159, 21069908, 628016441, 628082624, 1114391, 628130169, 192004218, 628198920, 628310101, 1114546, 628343116, 123797741, 628392232, 182878493, 628425267, 628477449, 227000616, 112869460, 131842354, 98795798, 628621524, 628654162, 118980609, 628670522, 27230267, 237682874, 628686878, 628703262, 628719646, 628736651, 625541771, 629194814, 629211162, 7847966, 629231115, 629309522, 44105764, 629329420, 629407988, 38191135, 629460493, 27181346, 629541950, 61161765, 629588174, 57426217, 37748769, 629637501, 629706254, 629916010, 629948696, 629981268, 69501121, 7094275, 629997690, 7503931, 448119191, 630015340, 630079505, 630097272, 630145258, 630210623, 392184292, 630230543, 2326549, 630456353, 93995090, 630473322, 361201697, 93226181, 134365200, 630522555, 41913872, 7225407, 630574609, 19628049, 630653176, 630687595, 630752518, 630816784, 630834651, 76840976, 630866106, 630902290, 630984211, 631096818, 121012240, 20594881, 631161379, 631227259, 631259329, 4669442, 631295508, 631475733, 258113618, 525025558, 631537880, 631574038, 33555131, 451772479, 460406868, 631637828, 631685570, 631767449, 631803415, 631849018, 56328209, 631868952, 632000025, 632078558, 632111186, 632131098, 30507025, 632196635, 98795713, 632242757, 523305018, 4800596, 632278506, 632340699, 484573185, 632373441, 632406100, 632422706, 206880831, 632455516, 632488180, 632537105, 632553488, 632569942, 632586276, 18137144, 632602682, 537871233, 632622620, 632782906, 632800275, 632832176, 100073505, 632864825, 632881188, 29540453, 632901149, 632933918, 633225486, 633258206, 633292005, 633324579, 142737470, 633376287, 633520527, 633569523, 633622048, 633684923, 633733306, 633766102, 7176314, 160759882, 633802273, 633880917, 633929787, 633946129, 15532033, 633962737, 8503359, 633995352, 634028317, 634062528, 21839908, 634097186, 508510330, 634142778, 634159370, 634192959, 634277411, 634342948, 634424684, 634474021, 634555942, 634637863, 634687016, 634733247, 108183569, 634782657, 140083231, 634850857, 635011450, 262078757, 635043900, 635093485, 76923094, 635128771, 635191933, 635227690, 1114535, 197378357, 18711334, 529907743, 635306020, 635322562, 635355194, 463388731, 635375147, 635503021, 635552228, 635588140, 635633722, 635650065, 635666495, 635683949, 635732384, 1114274, 635781138, 378847268, 635797735, 635831837, 635883053, 636011151, 140083216, 636046840, 636112430, 636191143, 636223674, 636256273, 636272842, 636305471, 636321817, 8503353, 636649505, 324829243, 361644068, 637076132, 55132461, 637144625, 637259314, 637304833, 637321275, 637338016, 637386811, 59047952, 637403629, 637439539, 637534384, 637570612, 637632726, 637665297, 637681700, 79708222, 637699573, 19383826, 637749924, 637847058, 637878602, 637914677, 1114334, 1114539, 638058580, 168444603, 638075860, 100155448, 78250047, 638127670, 638340663, 7077889, 638468178, 638484513, 638500946, 51347457, 638520888, 638616168, 37503058, 638650063, 303497464, 143819594, 78480475, 638701113, 1114338, 638779616, 638812215, 638828737, 368378971, 638861313, 174637118, 638878619, 638930490, 639045179, 400789052, 639140242, 639189485, 1114835, 639222474, 21282817, 639271396, 639303738, 76792022, 639323709, 639402232, 639438398, 639602239, 639715166, 29098226, 639815232, 639877202, 105382110, 639893505, 130105542, 639913537, 639959098, 639975826, 640025570, 640060994, 19398870, 640142915, 640288391, 640320930, 640385576, 640421444, 640483591, 181682232, 640536133, 640814662, 640975429, 641007617, 641024017, 26918913, 641040632, 641076807, 19398872, 641158728, 641224265, 641303324, 19382501, 641355338, 35045685, 12091514, 211255502, 641466612, 641515726, 641565604, 641597841, 641630771, 641680446, 641728529, 176898111, 641745443, 47857943, 641810465, 20414522, 641827822, 59064336, 641879627, 642072985, 642105885, 171983157, 642138494, 642187323, 522944698, 104661051, 642205221, 642285803, 642321259, 2376958, 642368753, 642433294, 642465810, 176767012, 642482411, 642515475, 642551372, 642761707, 18711446, 642810078, 642842641, 78004298, 642862669, 642924741, 642960974, 643072402, 643121349, 643155191, 219857900, 643203635, 35045685, 643252421, 163417679, 643285300, 643334145, 643350545, 5603346, 643367134, 643400155, 643433075, 643465486, 643498979, 499204289, 643534416, 643665489, 121933394, 86556706, 643711060, 405078017, 643727774, 643776529, 221380609, 64864315, 643792897, 643812947, 127025183, 11632641, 643924415, 643956898, 643993172, 25346110, 644104606, 644154910, 644206165, 644317914, 35307602, 644349988, 644366394, 644382779, 644399346, 293224464, 22397208, 644435542, 644481106, 644497444, 380665914, 644513806, 644530233, 644546624, 7225368, 644927064, 645120780, 645185866, 645221977, 22938084, 40894957, 645285824, 645336666, 485326906, 645447873, 645484123, 645533276, 132644948, 645759169, 11239455, 645793888, 2377630, 645876472, 645940601, 77283329, 646005941, 39469702, 646055676, 646089819, 646139485, 646237790, 38535361, 9666596, 646382968, 646434399, 646497418, 646549088, 646611173, 646643794, 32620575, 8683521, 646663777, 646873646, 646909538, 26198754, 647056995, 647171684, 647220837, 647284264, 647315538, 283656208, 647335526, 191155815, 647433832, 647548521, 647594043, 42680668, 647610554, 192118847, 647646826, 647810667, 136413454, 648089196, 202834004, 648200414, 205996313, 648232996, 648249429, 648285805, 16482307, 648348418, 648399592, 648449646, 648547396, 648613487, 648741077, 269647956, 95699160, 648775458, 648841203, 648888807, 648937489, 186662986, 648957552, 120291384, 649035792, 649052413, 649085342, 649137777, 649199647, 649216001, 649232401, 649248842, 41648161, 649268850, 649347258, 649380333, 649416307, 20856849, 649678452, 649740370, 649756981, 36258515, 649793141, 649838823, 76923431, 649872080, 100155428, 113672378, 649904350, 649937692, 2376951, 649986281, 650019013, 650054254, 650104438, 650166561, 650199226, 650235511, 650281191, 650317432, 650461428, 650510565, 55869522, 650543579, 650576685, 650641809, 650675571, 650724789, 334758158, 650792569, 650887250, 650904534, 650969755, 49332287, 114835540, 651018491, 651067606, 651101444, 18563131, 651169402, 651247886, 59818293, 651284091, 651333244, 651509936, 651542613, 651575689, 651660925, 528891940, 651755754, 651821631, 98795742, 651854031, 651886608, 651902993, 57294922, 42765950, 651919396, 651935761, 155828225, 651955839, 652001511, 652034079, 652050495, 652067772, 652099666, 652116026, 19546198, 652132408, 652148818, 15974458, 44863105, 652528851, 652575626, 652624097, 652656702, 652676738, 652804344, 652836952, 24805440, 497254436, 652873347, 653001085, 653066433, 653099011, 653116330, 653148346, 26182306, 653184644, 126665595, 1114645, 653364150, 653412824, 653460648, 653525025, 653541434, 22462753, 653561439, 178357066, 653627013, 653672481, 333824084, 653689018, 653722149, 653758086, 654003847, 654082685, 654118536, 446677076, 654180712, 654229752, 133022333, 654262330, 654278926, 654315145, 2379402, 654495371, 654577292, 654655895, 654690730, 654754206, 654803581, 654836126, 490127630, 654885090, 654921332, 654986893, 655197775, 655261910, 655295370, 655344179, 162660661, 655392826, 41913998, 655409463, 312280008, 53412308, 9093182, 655475069, 655541495, 655589570, 655624128, 655675023, 655952147, 656018334, 123863058, 656064570, 656081297, 656117392, 656261588, 656310346, 656326657, 656343099, 656359457, 656376117, 656409123, 656474356, 656526993, 656704051, 656756370, 162693313, 656821907, 656916852, 22462705, 656949662, 656998584, 657084052, 657149589, 657228018, 657260607, 54542418, 657280662, 97191159, 657391838, 657426081, 657474284, 657523488, 657559191, 657638745, 657706648, 22938001, 19530669, 657867212, 657903257, 657981633, 658014209, 26968126, 658034330, 49332283, 658198171, 658259986, 84787448, 658276353, 19645154, 658295450, 658341970, 658362012, 658423825, 658441187, 658473523, 658524366, 658575005, 658735326, 400867330, 658768181, 658804382, 658935455, 659000992, 659079735, 659112411, 473728230, 659148449, 659293662, 659325010, 659341498, 214892945, 659377826, 659426979, 659521938, 659570746, 18727110, 659587258, 659620415, 659656356, 659734530, 563216606, 659751156, 659800286, 409944080, 659833069, 659865797, 20594881, 659898453, 659931220, 310132820, 659947582, 659963966, 659980350, 659996734, 133529662, 660013240, 660098725, 660291851, 660328102, 660357364, 660406756, 143605823, 660439076, 660455457, 126664880, 660475559, 660652094, 660668667, 660719123, 660783226, 149389661, 660801043, 660868776, 660996132, 522453050, 499499064, 661016233, 661078210, 661114143, 661143553, 296321057, 132644867, 26673169, 661163690, 26182024, 661261995, 661340242, 661356602, 592363579, 661374526, 144359703, 661425836, 661504994, 522453028, 661540525, 661619090, 661668106, 661701254, 661751774, 25346082, 661786286, 661897274, 661913659, 661930002, 356958209, 19267646, 211910674, 661948382, 539967546, 661980962, 662048431, 662192424, 167544077, 193658883, 39354594, 662227974, 662290676, 662339642, 662356024, 7258148, 662376112, 662474417, 422743730, 662618174, 197902820, 7471159, 662638259, 2130002, 662700090, 662716686, 375242807, 662752948, 662818485, 662965942, 121077761, 381599966, 663031479, 663077357, 121209318, 38371331, 176586810, 663113400, 663241096, 663277241, 663339431, 663372522, 443940880, 30998545, 663420986, 29507666, 663438035, 663470116, 104170099, 663487355, 663519314, 663535649, 663555770, 663635019, 663667181, 525386130, 663699949, 663732257, 663748794, 1114687, 663785147, 664013419, 664076373, 664109516, 664145596, 664224039, 18711021, 1116126, 55623947, 663831398, 664260285, 132464702, 664371697, 664420577, 664456894, 664567870, 664584276, 664600634, 664617680, 626000139, 664649731, 7471107, 664669887, 665112257, 665240346, 665288786, 665305124, 463945802, 665325250, 665422733, 665485826, 665534522, 126271524, 27181885, 100155451, 665551070, 19382821, 665587395, 665715089, 665749988, 665796794, 39354717, 156582357, 665833156, 228835386, 47677675, 428802081, 665912743, 108462613, 665961536, 666013381, 666143271, 648314938, 666222785, 666255752, 666288162, 11632671, 666304734, 666337296, 45351461, 666357446, 666521287, 666584341, 666632250, 29688474, 666652277, 666698203, 176898111, 666731821, 666779873, 666812450, 11010084, 666829854, 666878682, 244646183, 666914504, 59064379, 654279131, 667126982, 667172920, 46268450, 310132799, 667190555, 667239338, 29114557, 667274953, 20152321, 667437017, 667487946, 667615320, 667648034, 622346269, 667665449, 1654846, 667697493, 667746842, 667812071, 667848395, 668090603, 666730689, 188317730, 668123194, 668139604, 1359956, 668159692, 529907728, 668208845, 668270676, 668287007, 144359745, 29212837, 54214842, 637681722, 221694028, 668307150, 668500698, 666599977, 668535180, 93159458, 668598835, 668647440, 176898848, 668664787, 138231993, 668700367, 668762514, 106315793, 668815056, 114901271, 668995281, 669040833, 669074138, 19185680, 669106261, 669142738, 669191891, 669302842, 64864340, 127189259, 669322964, 669384740, 669401150, 27770896, 669417716, 669467308, 669534177, 28000342, 669601128, 106315858, 98795762, 669649093, 669698206, 669745236, 669765333, 669847254, 1114842, 669958228, 59064356, 669977044, 670191319, 670548551, 98861516, 670600920, 39650089, 670646581, 670679057, 670695479, 389775391, 670712222, 435847201, 666714169, 670761160, 20464687, 670797529, 670859323, 670879450, 670910529, 670977653, 671023189, 671059675, 671106724, 671137874, 56672342, 22790536, 671154443, 671190748, 37847041, 671252666, 671285896, 671321821, 671648740, 240926775, 671698654, 671780575, 493472216, 147931629, 671878880, 18711466, 671943053, 671990135, 672022584, 126500946, 672039396, 672071736, 672088146, 37847042, 672104719, 672137300, 672153616, 672170043, 672186385, 672202810, 672219199, 35700793, 672239329, 672452322, 115212346, 672531685, 404291600, 672567011, 163841242, 672632548, 42156049, 672714469, 672829158, 672891188, 86429415, 672941722, 672991971, 19136574, 150093825, 22233281, 673025768, 673287763, 179668237, 673402601, 673451754, 673497755, 673550059, 673595410, 132464724, 108773643, 673615596, 673779437, 20856865, 673824826, 673841387, 136151114, 179668237, 673874757, 78774330, 623345700, 673910510, 673972699, 674005947, 674055082, 674086945, 674103312, 202653713, 674119713, 674137632, 64209018, 674234653, 674267138, 674283556, 674300213, 674333396, 674365497, 643350583, 674385647, 674729712, 674808127, 674907127, 674955732, 675008241, 29557128, 56246308, 675106546, 675185535, 675251483, 563069142, 675303155, 21659730, 675430801, 113181165, 675463170, 42663994, 7471226, 675481414, 18513933, 675529242, 27115522, 675596990, 675645604, 675676161, 675693126, 675726619, 666599646, 675778292, 675907981, 675954751, 19382699, 675974901, 676056822, 232243201, 27967519, 676155127, 676315466, 216563771, 5603450, 676351736, 171771718, 676433657, 676515578, 23281693, 60555974, 676679419, 85917713, 676741957, 676777724, 676872469, 95518906, 676908797, 284786744, 140083201, 677021343, 677055186, 343490782, 677101569, 45285378, 677118039, 677170942, 677232657, 677249060, 136675720, 677266707, 664272929, 674267192, 162661423, 677318399, 677512517, 54445748, 381714487, 252526593, 677580544, 677626566, 677658653, 35307554, 677920990, 97484802, 677954022, 677986305, 239566932, 678002762, 678019085, 1376318, 678035542, 678051864, 4866070, 678514434, 678547203, 678743812, 678825149, 52543674, 678872910, 678921434, 678969633, 53100561, 679002113, 4685841, 679022341, 21839927, 50938050, 679120646, 679198753, 33390608, 197902606, 679216086, 679280859, 679313580, 679350023, 27476962, 587186235, 679399176, 679595785, 679661322, 679756370, 679788577, 21741754, 679808779, 679903808, 679937099, 679968982, 180568066, 680001565, 260784130, 19382581, 680021772, 26182035, 680116313, 680148993, 99844132, 680167191, 62242833, 361726219, 680263950, 680296911, 44433471, 680345906, 680378599, 110854202, 680414989, 27182095, 680624314, 680660750, 126664890, 680722769, 121012280, 680757516, 680808207, 680854271, 680902738, 680919076, 19546115, 680935910, 680968228, 37224481, 680988432, 681100477, 29114453, 681165523, 681197781, 681230524, 26427586, 681280576, 681328642, 19579507, 681348350, 681414417, 681509068, 681541633, 681558293, 681594642, 681673034, 681705489, 107135169, 78331921, 681722270, 681771202, 681803832, 205013077, 162168849, 681820162, 681836599, 327499792, 681856787, 681987860, 152322404, 28623090, 682066658, 140332821, 682100613, 682180609, 682197051, 71548991, 682213687, 180715744, 29099538, 682279220, 682328701, 682364694, 682479383, 96272468, 682659608, 1117977, 682721540, 529907748, 682770724, 682835999, 682852369, 290553914, 682869125, 682954522, 329908627, 683115049, 683147351, 183124004, 36339934, 683198741, 683282203, 683361076, 97189889, 683393596, 683442723, 170493165, 683508117, 29098340, 683560732, 683724573, 20152379, 43794702, 202031298, 683872030, 19579042, 683933729, 29835594, 37650465, 683953951, 38535390, 684085024, 684146955, 684183329, 684261673, 684294613, 58458194, 684327100, 684377471, 684444571, 684507797, 684556289, 535429332, 30097594, 684572730, 684589353, 684625698, 684691235, 476594369, 684753072, 70483969, 430391905, 684785934, 684818490, 339034201, 684838692, 685015226, 22790227, 685047893, 685084453, 438715088, 685395072, 685490390, 76578832, 29114705, 685526822, 685739815, 685785281, 685820195, 185241384, 685899996, 685965371, 685981889, 686014760, 298844234, 686047480, 163610683, 686080222, 205979918, 686116649, 686195234, 686277995, 110821763, 686329642, 686424065, 112869379, 389054522, 686444331, 686526252, 686769045, 60571848, 40173690, 686833850, 686866620, 18579540, 686915832, 686948663, 42058005, 104546305, 687015442, 687047955, 657965304, 175227133, 687096030, 121389119, 687128661, 687165229, 687247150, 687292866, 687377606, 687425134, 687472722, 687489210, 188776531, 687525679, 687603905, 301269009, 687640368, 687734843, 526238045, 687751637, 19382996, 687784023, 7340091, 687833172, 64471056, 687853361, 688031546, 66879504, 688080202, 688161130, 48250939, 688193790, 192430139, 158908900, 688242777, 688276929, 688324766, 451625146, 688357598, 688390637, 688422943, 688439518, 688475954, 688754483, 324878367, 19579169, 688849084, 688898376, 688947489, 688983860, 689078357, 689111578, 251592720, 689180469, 618579718, 689291450, 689324117, 689360694, 689442615, 689504706, 689586625, 689639224, 105008953, 341229604, 689701348, 234045441, 689733649, 66932538, 689753915, 689982894, 690048828, 37945745, 87162966, 690129356, 690196285, 690245438, 690376511, 60915730, 60424209, 690438161, 19382708, 690454611, 539591738, 690488043, 690536898, 690618425, 690635058, 690668491, 690720576, 690818881, 19579093, 49348625, 690884418, 690962433, 13303811, 690982723, 691208524, 46334034, 691261252, 36470960, 19399265, 691339646, 2376347, 691392325, 550010967, 691519889, 691556166, 44860091, 28623006, 691634842, 691666980, 691684171, 691733801, 90406913, 691782178, 310198289, 54447943, 691863901, 691896404, 691912740, 14499843, 691933000, 692175644, 692224193, 41812162, 692257038, 692289599, 155779156, 692309833, 440352769, 692355154, 692375370, 593969556, 118489383, 692518929, 692535370, 692552400, 692584641, 692621131, 692699717, 288178362, 692732117, 692765613, 27967490, 692798191, 692899660, 692965197, 200016066, 693047118, 693145423, 693224005, 23281748, 693258996, 693306267, 693354766, 693387327, 693403704, 202539090, 693420220, 693469634, 693551433, 693616881, 693649594, 312017108, 693686096, 693781094, 693833553, 488308922, 1114643, 693964626, 52035828, 694027244, 694079238, 694161235, 694387089, 44860709, 694419654, 694452794, 694501823, 694534229, 694567100, 694619988, 694682147, 694747488, 29098419, 694780351, 648970886, 694814710, 694882133, 55640123, 695078742, 695127895, 695241912, 695308120, 695419150, 695451841, 158203963, 695914330, 696156654, 696192859, 696271271, 190513169, 696305603, 696353729, 696418391, 696471388, 696582357, 2377221, 19398854, 696618845, 696700766, 696747270, 19530023, 696815455, 696911534, 696959169, 696991803, 697012064, 697057843, 697106970, 697175144, 149848258, 697221685, 697322882, 697385374, 697438049, 690635058, 697483727, 697536354, 697700195, 697761985, 153157691, 697798500, 697860459, 697893194, 697929573, 698141629, 698205453, 114835472, 698253370, 604766267, 175538193, 698270033, 698302650, 698339174, 632160273, 698601076, 698662943, 1277985, 92373077, 446742725, 54772126, 278071237, 698683239, 698830296, 698863464, 501432533, 698909420, 698960351, 699006993, 256294986, 699027305, 31211537, 99369046, 699109226, 699322219, 699416776, 699449615, 1785859, 699483083, 699531282, 699548418, 699597181, 163528908, 476008300, 699666285, 699727956, 699744257, 699760673, 699777636, 699826212, 699842562, 699859166, 699891770, 162971806, 100843736, 699908510, 119996433, 699961198, 700072809, 700121669, 135561486, 179290170, 700154087, 700190575, 700450724, 700515612, 700547532, 126665834, 700582797, 700646042, 60817424, 700678226, 700695122, 7225361, 178667713, 436649985, 700731248, 318570554, 700878705, 701006094, 93225501, 26083387, 701042546, 57000328, 701137089, 701172026, 701202743, 701268377, 701304691, 701484916, 158613567, 701566837, 701615990, 35045576, 701678293, 701727031, 85265738, 701793810, 8585252, 28295185, 701825939, 701878135, 38223901, 30572606, 155519038, 701960056, 702173049, 702218426, 702251667, 33751122, 702300353, 702336890, 702432538, 21512251, 1118075, 702500732, 641843592, 702566269, 702628241, 702660796, 702713726, 702844799, 702955579, 702971922, 702988688, 703054310, 1785940, 173752353, 703090560, 703152376, 385794132, 703184897, 35913787, 703205249, 567103362, 101924923, 703332648, 158908900, 703365313, 703397973, 703430712, 703447122, 531611649, 41304095, 703467395, 703565700, 703614853, 703842947, 703889719, 121700641, 703954945, 205242385, 703971490, 704004366, 104071223, 21168326, 704037412, 704102416, 238141526, 704119347, 704167994, 704184478, 321929252, 704217274, 704251454, 704299201, 120799291, 704335750, 2376405, 26198286, 704430273, 704462907, 704480621, 64602128, 704512213, 29115005, 704548743, 12386363, 704741458, 704758937, 704790929, 47628372, 78774273, 704825046, 704888890, 704908324, 704954385, 704974728, 705019935, 476987451, 705040265, 705103491, 705183803, 705204106, 705282357, 20627530, 705318795, 705397317, 180862977, 705431390, 705530189, 705609746, 705626197, 705662860, 705809593, 705875853, 705941390, 706023311, 706053922, 706121613, 706187152, 706265282, 706301841, 706396561, 132464658, 706431196, 706478164, 696418867, 706495307, 706545547, 706596754, 706658613, 706691146, 95699322, 706711443, 706822161, 571588626, 706842516, 706904081, 15564831, 706920641, 37929186, 706953247, 706969659, 70484026, 707350422, 21758015, 210995368, 707497879, 707609139, 18711202, 707657942, 707694488, 707740663, 707788988, 707838333, 707903710, 707936551, 707969221, 121700545, 708005242, 69697959, 708116769, 708149490, 708184000, 708234254, 708280590, 708313354, 708349849, 708444912, 236585154, 675381434, 182157370, 708493582, 708529265, 708625409, 708689976, 347554072, 228982840, 708710298, 708890523, 709017814, 347652417, 709050450, 709066785, 443547933, 93159457, 709087132, 709148886, 49381392, 515211298, 709181863, 709214266, 391610385, 709231584, 709283741, 709411178, 709443772, 50938927, 709493330, 94535766, 709525540, 61112322, 709543289, 709611422, 709722286, 709771452, 709820449, 709840799, 710050239, 710084231, 18514004, 710116492, 134053904, 710164791, 710230017, 710246695, 115114015, 710279354, 710312273, 51200016, 710345015, 710414240, 710492357, 32964690, 710528929, 94621602, 710656293, 710690067, 710738216, 148111393, 710770746, 710787089, 38682659, 710803906, 710889379, 710967645, 711000081, 538001530, 711020452, 29098391, 711135141, 28115003, 711233446, 711327938, 711361659, 711446439, 63979583, 711541535, 711593896, 711901454, 4636690, 711935273, 711983690, 712019881, 712249258, 712344859, 712393370, 18137162, 712425720, 55132345, 712462251, 111346026, 712540249, 712573923, 712605889, 712638794, 712671248, 712687830, 712720469, 712757164, 712835285, 712871853, 29098149, 713048257, 713082672, 388399370, 268976185, 713133998, 713263640, 251363450, 713314223, 27541540, 713359361, 713375803, 713394201, 334741521, 713442160, 193151191, 713523229, 713539601, 713556026, 57311291, 713572596, 713625520, 258359380, 713707441, 713785360, 713802049, 713838514, 713916452, 29491256, 1116084, 713933519, 713985971, 58786278, 714084276, 714277050, 319455295, 714311868, 49054848, 714424337, 90931256, 714444725, 714525459, 79937784, 714571807, 714588348, 287737823, 714637330, 714653925, 714686523, 714703034, 714735860, 22544401, 714788790, 714985399, 57294884, 715047176, 715096312, 715129026, 194052151, 118489281, 715163154, 715194561, 373588939, 715228016, 715310220, 715358426, 715411384, 715637450, 715685970, 165347450, 715702331, 715718885, 715752108, 108069194, 715817572, 85311547, 715866186, 715882718, 70074401, 151257300, 715919289, 716013970, 716065047, 716128314, 716148666, 58818594, 716296123, 716391013, 32964682, 716476348, 716554256, 78774288, 716571923, 716621093, 716669002, 716685579, 23724068, 716722109, 716820414, 716898599, 2376479, 716931105, 19579489, 716948003, 717013022, 717029458, 717045834, 717062266, 7176210, 717079204, 717144731, 717197247, 717291556, 717307962, 19546169, 717325286, 44548289, 717357114, 523305044, 717373524, 717389908, 311033940, 717406505, 717836225, 717948897, 718012498, 718029018, 718081986, 718225585, 718258373, 42090532, 236569044, 718291211, 718323745, 718340154, 692977822, 718359930, 718472003, 718520675, 718586068, 718618812, 718668501, 72925240, 718720963, 718931650, 718979824, 149225670, 719028468, 20594966, 719077562, 719111009, 72613904, 719143179, 719179716, 719257786, 41894111, 719294405, 719437946, 719454701, 719487060, 432439378, 79790922, 719503418, 158138385, 719523782, 26361915, 719622087, 41910626, 719720392, 719864209, 639353365, 719896762, 719933385, 719994913, 720011300, 270467128, 179192012, 720027834, 720064458, 720158964, 720208499, 720244683, 257572920, 720355570, 20676692, 720388154, 720404721, 720441292, 720502786, 720519169, 79708193, 720535610, 100155425, 720552864, 720617835, 720650690, 720733098, 70811651, 720766201, 720801741, 720900046, 9093153, 720961729, 330809403, 60099046, 720994321, 24051713, 4685840, 65503295, 721014735, 721174776, 721207313, 41914320, 721227729, 721305797, 721338677, 721371218, 721387578, 336887809, 60198866, 721404194, 721440723, 721715675, 721748221, 721781513, 721813586, 502824961, 38944853, 721833940, 32882743, 721911893, 35668052, 721944990, 721993922, 375390439, 722026900, 722063317, 722124816, 29098407, 722141267, 722174010, 119095472, 485359652, 722190750, 227164234, 722240035, 227164234, 722309078, 722488906, 51413047, 2377504, 236569706, 722603991, 722666433, 722735064, 722797292, 722846098, 722898905, 722993211, 22397482, 723013594, 32620602, 723075547, 208617528, 723107898, 123306047, 22691876, 723128283, 723370580, 139723895, 723423196, 723505117, 723599553, 723636190, 723730743, 37896209, 58785986, 723798174, 723849183, 723996640, 724058659, 724123956, 85265140, 724176865, 724320475, 724353914, 22544468, 724406242, 4669469, 724484282, 96878855, 724516923, 7094288, 724537315, 724648865, 547571344, 724697968, 724783076, 32686097, 725059914, 725156172, 725205987, 725238246, 725271109, 725304050, 725401844, 725451236, 725487589, 99336193, 725565847, 725601484, 725680145, 22397170, 725697171, 46743634, 725747099, 725798886, 725864423, 725946344, 4358170, 376488503, 726204709, 726238752, 726337239, 726385398, 726437865, 726483137, 726519786, 726646970, 39652232, 136413432, 726680540, 726745495, 726780110, 726831083, 726990933, 727024154, 727089419, 727122114, 727154885, 727187812, 727220771, 471764972, 727289837, 727351380, 21217313, 727367991, 727433435, 727466273, 13303882, 727895679, 727945199, 289620452, 728056030, 728088632, 728104961, 728121538, 728158192, 728318215, 728368405, 728416442, 728450027, 728499462, 58458171, 728567793, 432947217, 412696846, 32784725, 211206580, 728715250, 728846323, 728924215, 728940766, 728973805, 729010164, 729104577, 704856276, 729137430, 729174005, 729318213, 729351666, 87179323, 729416040, 215679060, 729464868, 114835530, 729481252, 269730935, 729501686, 729632514, 729661498, 27983931, 729678284, 729710625, 592265274, 729727034, 386088977, 729747447, 729792596, 38535361, 26198245, 729812984, 187809850, 207486977, 729878521, 729924970, 730005726, 730038289, 91111479, 1785919, 730055323, 730107898, 730169345, 730186203, 730219376, 730300658, 730333220, 730349960, 730386427, 156745729, 730482354, 730529985, 21217335, 114885651, 730566652, 730677630, 730726459, 730746877, 572129473, 730808337, 50364664, 730828798, 730907019, 730943487, 731090944, 731169349, 731205633, 731287554, 30245917, 731447789, 731481501, 21839873, 731545659, 245071930, 731562793, 211321045, 731595346, 634028516, 731627869, 26280010, 731664387, 82821904, 731759722, 63258625, 391839992, 731811844, 659784174, 54919502, 191909893, 98910394, 731959302, 732086542, 732119271, 192184322, 732155911, 732237832, 732397853, 392560699, 732432446, 84787530, 732483593, 732512314, 16515075, 732528910, 732565514, 311377922, 732610619, 430784709, 75923457, 732631051, 47857959, 732696588, 732958733, 40501331, 733004087, 733071185, 182452325, 733118650, 733155342, 733216850, 41058320, 733237263, 733429842, 733446388, 733496176, 28491969, 733578051, 733627260, 733675556, 30228536, 733692703, 733741285, 733773857, 62242875, 733794320, 734220306, 734331120, 159924227, 734366346, 734413048, 734445625, 734462014, 12091427, 455856147, 734908436, 734990357, 49053712, 735086337, 110412819, 53412410, 735170582, 735531031, 735625464, 735658333, 735690826, 100990994, 735707722, 735744024, 735805629, 735838617, 277594196, 735871866, 735924249, 735969570, 736002084, 720896001, 736018702, 67272706, 736051492, 736116955, 736149563, 90931202, 736167341, 736215329, 736247882, 241647617, 22401050, 736264193, 736280635, 736297044, 736313345, 736329745, 19530084, 736350235, 736428116, 736444500, 736460836, 56049750, 26296377, 736477242, 7618634, 736497692, 736627662, 736675274, 736725329, 736788566, 736804922, 736821332, 736837716, 46743588, 736854209, 212008993, 731087883, 43795575, 736890909, 736986587, 737018028, 737052517, 485326907, 737117101, 65732609, 737153054, 737297285, 737362004, 4653057, 716374050, 30245040, 737378613, 302841913, 269681801, 737411258, 64208953, 737444084, 737497119, 149897298, 737558602, 55918674, 737576062, 737625044, 737674059, 737722561, 737756030, 241647652, 737788253, 737820754, 737837249, 20725791, 737870135, 737935553, 318570498, 737968164, 737984596, 738000980, 519618596, 738021408, 738299937, 130990202, 738394113, 738410559, 37847096, 738427443, 19399909, 738476254, 738509048, 1114475, 738542069, 91521108, 738660386, 738709539, 738758692, 738852923, 19235064, 738869795, 509296917, 738934800, 738951204, 159924287, 738971685, 739082504, 739131585, 2379814, 544571578, 739168295, 43794709, 739230281, 45353167, 739332136, 89014330, 739397673, 38699071, 739524641, 131842285, 739541168, 739573843, 19529813, 739610666, 739656292, 96880406, 739708971, 739954732, 740049784, 740098296, 28409873, 740130874, 740147686, 216612900, 740179969, 740196436, 740212794, 223625233, 740233261, 654786801, 740360279, 19628117, 740413486, 740495407, 35831809, 262111265, 740622580, 740671504, 740688347, 740720824, 740802577, 740819175, 87179266, 740851975, 85819698, 740901663, 55132402, 740950825, 740982981, 741019696, 741065105, 126042338, 741097919, 741130437, 741163301, 299860031, 741197218, 741261313, 20856834, 741277918, 741314609, 741478450, 29950074, 205455548, 741589246, 156958721, 741638227, 241532958, 741671549, 741707827, 741753279, 153518527, 741787839, 741835037, 19546141, 741871668, 742065352, 266191271, 742133813, 742277136, 28409920, 742293735, 742326583, 742395958, 540475860, 98631739, 742556347, 742605014, 742638162, 742670529, 104005668, 120799249, 246218753, 37503035, 742706790, 742801425, 742817985, 742850774, 742887479, 742998098, 743014613, 743051320, 622739889, 743129466, 743162053, 19234988, 743194984, 743243835, 743263757, 1117471, 574735466, 743342675, 743444537, 743489729, 743526458, 743653393, 743673915, 743739452, 744050749, 210436097, 744243362, 303497447, 744276199, 744308958, 38797313, 744342120, 744374331, 744390687, 723075128, 472910138, 37650435, 744407293, 744440117, 744473070, 744506484, 744554910, 65781777, 98205713, 744607806, 207405516, 375539452, 456556560, 744718666, 744751105, 120799295, 744767505, 744783928, 744800289, 744816676, 104005648, 156958736, 744837183, 744965020, 352223249, 745014239, 745098176, 745144407, 745193558, 745209892, 400932920, 745230400, 745341658, 2377150, 745374695, 91292000, 745439803, 745508929, 181519510, 25870337, 745607234, 745652671, 745685023, 395624507, 745701409, 55164987, 745721923, 745833377, 745881601, 67960888, 745900190, 745951300, 29098359, 746045626, 746078291, 746114333, 746229829, 746291511, 57426963, 746357123, 746819654, 746946748, 746999879, 22724666, 747081800, 122978518, 747159745, 747195199, 36520020, 747258357, 747372967, 747405397, 747442249, 66752586, 747503832, 747537405, 747573323, 747653327, 747684033, 37503006, 747720780, 747782230, 400080954, 747798587, 747814970, 747831314, 725729511, 747851853, 747995330, 748028318, 288309786, 748077245, 748110838, 155521043, 748175720, 748225483, 314867730, 42156065, 656916842, 748277838, 30228536, 748437937, 748470331, 748486842, 748519509, 748552255, 748568609, 748585840, 748667128, 146243832, 748699679, 748716116, 748732417, 748748833, 748765372, 748814392, 24035329, 46071824, 748830721, 222036027, 748847415, 748912904, 748965967, 167493902, 749097040, 150257695, 749178961, 279300512, 749260882, 55869514, 72925215, 749359187, 749441108, 749473877, 749586254, 31211606, 749634154, 749682689, 497254431, 29114705, 749699802, 36323410, 749736022, 749896951, 546488378, 749945321, 92700861, 79151106, 750014551, 750108888, 750141684, 750190593, 750207032, 750223393, 750239775, 750256187, 26116282, 750272568, 750288955, 750305316, 25493507, 750321665, 750338107, 750354590, 750391384, 745291792, 750436536, 750518289, 750534743, 750584022, 30572628, 750620761, 604766266, 751112283, 751304935, 751338219, 94584891, 751390812, 751452808, 751489117, 98795995, 751567271, 47859864, 751603806, 751747089, 751763746, 751796309, 751831034, 751878819, 215990564, 751962750, 752042698, 752091738, 752157822, 752210015, 752288145, 19579170, 752321436, 752369895, 1474623, 752406624, 471945313, 752500922, 155517330, 752533702, 151732240, 752570466, 22937819, 69816419, 19399612, 752685156, 752816229, 752861681, 752910369, 634474598, 213385249, 752930919, 753012840, 753074234, 54542395, 753091690, 753143913, 719634468, 180142138, 753207870, 753255184, 753287204, 753303585, 26198209, 379617316, 753320112, 64864378, 753356906, 753602667, 753697142, 753795130, 144359910, 753815660, 753913965, 30589169, 753991769, 91848761, 754027749, 754094190, 754208879, 754274416, 754356337, 57294864, 754438258, 754552947, 754614330, 754630740, 7159892, 754647056, 293503035, 754667636, 754897013, 755011702, 755123306, 755171521, 21774606, 755208311, 755269938, 755306616, 755351553, 143228945, 755368350, 755421305, 755516363, 1114548, 244744208, 755568762, 755646760, 2376626, 755683451, 755744855, 755795753, 755843473, 42434618, 755876084, 155518199, 755929212, 756060285, 645185722, 30572815, 100843739, 756105247, 756121844, 756174974, 756252939, 756286263, 37929230, 37912846, 756401816, 756486271, 45355136, 756551809, 41975892, 515211346, 756712172, 26706004, 248463444, 756764802, 756842584, 548946781, 756875265, 756892049, 187596884, 756925559, 107200913, 169050197, 756957185, 100155476, 756977795, 33918682, 757137946, 757203028, 757219415, 757269220, 757317633, 74318867, 757338244, 757432721, 84590594, 757469317, 757596353, 757629012, 125812792, 757649542, 757747847, 757826207, 757874881, 472517996, 757907698, 60097533, 757940468, 757989460, 758006322, 758054948, 758071359, 1114433, 758091912, 758219892, 758267962, 758284774, 758317542, 98926840, 758351899, 758448159, 19808292, 758464696, 758550665, 758796426, 758841969, 758956197, 758989214, 759038849, 759087188, 759103524, 36831249, 759120427, 46120961, 759173259, 8863752, 759267636, 212124152, 759316698, 172852152, 759366107, 759398482, 231440420, 759419020, 759480351, 759496762, 759513147, 759529473, 21020690, 759548873, 759632013, 759710147, 759742495, 759758911, 260161594, 2377494, 759779470, 759890663, 687603934, 19579078, 759926927, 759906378, 760006449, 760090486, 760156304, 760332289, 760348874, 760381662, 224493604, 760414456, 760451217, 760512548, 760528912, 126025814, 486916132, 760545339, 36667447, 760565906, 760631443, 760877204, 74268688, 65437697, 760987730, 761008277, 761069584, 29692054, 96881030, 761085953, 94535742, 761106583, 761234341, 761282561, 761299028, 761315634, 761348471, 761381086, 761417880, 761516185, 761626625, 761643041, 761659428, 761675832, 761693757, 394692303, 36765944, 761741503, 22200383, 761811098, 390234171, 761860251, 761939141, 31981799, 761974940, 762019927, 762073245, 762118160, 273940497, 762138782, 762187935, 51642616, 762319008, 762482849, 762581154, 762659060, 762712227, 762774300, 762826916, 762954135, 762990757, 763072678, 763150522, 763185891, 763218879, 763285671, 763546686, 763593452, 56049786, 763642437, 763678888, 93110335, 49905722, 763740610, 112722133, 763823068, 763888608, 258359380, 20594925, 763936954, 47317259, 231227651, 763973801, 764232958, 764285098, 764383403, 764428488, 764461114, 764477524, 3211348, 4866112, 764498092, 764793005, 764855894, 764924078, 113672385, 765005999, 765117122, 765165584, 99369017, 765182244, 144360645, 765247902, 2376859, 765297042, 765346101, 327499777, 765382151, 518520833, 765444181, 108544017, 765477224, 214171918, 93110327, 421707834, 765530288, 765657358, 765689892, 765706298, 72089674, 765722625, 765743281, 1245248, 765776050, 765837371, 765855750, 302809124, 765903032, 765984827, 766001338, 77578802, 766035069, 766099512, 37912677, 114182791, 766513332, 766673144, 47317245, 766706555, 36880597, 766742709, 766841014, 766886074, 766922935, 767017044, 767033428, 27181341, 767049787, 187646010, 7077905, 767066976, 767115346, 20595219, 767132132, 767164500, 767181044, 767234232, 767299284, 767362323, 452904394, 767410626, 129712484, 22790580, 767492155, 767508497, 767525050, 767561913, 767660218, 767901818, 767918293, 767950907, 22938274, 767967980, 768016969, 768114894, 768166844, 29065271, 768247162, 768344148, 768360634, 38535416, 768397499, 768557244, 768610492, 768704797, 57114660, 768737466, 768774333, 165658810, 768819282, 26951738, 768838747, 251510866, 338919917, 768884949, 485326911, 768917540, 768933951, 768950305, 173883623, 768967241, 769069246, 577847489, 491753663, 559792184, 231260234, 769216704, 361988595, 39780550, 46973012, 769315009, 769394331, 769441968, 769474791, 769511618, 769589580, 769640174, 769687554, 421593104, 769708227, 769920429, 769966264, 770048296, 79463294, 770085060, 770150597, 770228417, 770261023, 770277450, 29950034, 770298054, 770408670, 770441676, 84312122, 125403444, 770474569, 770576583, 40599614, 770703604, 28492071, 770754014, 7209145, 770789576, 770851095, 770883836, 223051839, 770920649, 150257719, 771080488, 771112993, 7209191, 771129428, 771147531, 26182083, 26182635, 771231946, 771281099, 771359419, 771407928, 771424338, 771440833, 771473592, 771559628, 771622430, 771670209, 25428004, 771703507, 771735554, 144621799, 771756237, 20135995, 771805390, 771883556, 771948886, 771981386, 771997937, 772030548, 1114687, 84787393, 772047209, 772079738, 772096563, 39714833, 772149455, 772243512, 772259922, 772277659, 772329680, 772456533, 485326905, 101171259, 772489527, 772554769, 772573198, 485326865, 772620305, 338919733, 772636708, 90931274, 772653470, 129598017, 772702401, 372064449, 772735222, 698860083, 772800823, 772870353, 772949438, 772997334, 773030110, 773062933, 277364949, 773099730, 45547553, 773144635, 168444092, 103645268, 773161629, 773296339, 773406913, 773443796, 773522107, 773570634, 269647954, 773591253, 149393622, 626180129, 477987060, 773689559, 7209015, 773771480, 773816356, 773837017, 773881887, 49823762, 773898453, 773932490, 773980346, 774013237, 774047002, 774115546, 769163281, 774196021, 774242386, 774259436, 774312155, 441303058, 774422750, 774455370, 57000177, 774471754, 27181218, 168444209, 774488248, 774574300, 774685086, 774735115, 774799443, 774833122, 774869213, 774946938, 774963386, 774997818, 775045429, 775078180, 775145223, 20840570, 328253710, 255328330, 775229662, 775327967, 775373048, 230244353, 775405651, 75743233, 775439466, 775491808, 7340118, 775585823, 775602193, 150257726, 775622881, 775737570, 775835875, 776013626, 228245781, 776061010, 20840465, 776081636, 776143235, 39977042, 776176121, 776208628, 394691123, 776261861, 776323131, 776340019, 776389388, 38536722, 46743569, 776458470, 40599607, 210436155, 776601684, 563396839, 776622311, 776700285, 776765616, 776798271, 133726430, 776815015, 20725846, 776848399, 776880312, 776966376, 7684218, 777127018, 777177887, 777225523, 110362824, 777261289, 47301772, 777404635, 777437215, 777454184, 201638554, 777486520, 777572586, 777703659, 777765701, 777797940, 777846785, 7340034, 777865719, 777929164, 777961556, 777978167, 307086462, 778043662, 778076382, 778108987, 778125399, 778174866, 778223700, 168444148, 778244332, 113672891, 776192059, 778306518, 778375405, 778502337, 778534986, 778551378, 18743329, 778572014, 778715604, 778768623, 778829908, 778847251, 778879176, 243073587, 778916080, 779010508, 779042832, 779059284, 18710757, 779079921, 116588618, 779141448, 779190567, 779223526, 779257107, 779305204, 779358450, 779403646, 779452909, 438190164, 779485215, 779501879, 779571443, 2378627, 234668329, 779633022, 167165969, 779686132, 46743626, 779780180, 167608513, 779796791, 779864246, 779931839, 1228802, 282001425, 779994134, 780030197, 780091585, 67453011, 103645243, 780124382, 780157223, 140083259, 780189889, 779649082, 780222708, 780271690, 487917096, 780288074, 50938622, 780304628, 312263355, 780357878, 780437035, 780484784, 780517564, 780566951, 780599380, 780615764, 341049380, 20267066, 58294288, 781046008, 781238354, 781255744, 781304014, 197379046, 781353288, 781403073, 781471993, 95339290, 781582645, 781615178, 781633437, 781680698, 781697087, 81068095, 10977316, 52494418, 135938402, 781717097, 781762619, 781779130, 781811972, 781865210, 387235841, 189349904, 509297755, 781947131, 782025045, 782074205, 782106660, 782123250, 108724757, 782156269, 7602205, 782192892, 782385368, 89506008, 40452097, 782418413, 782450874, 90128609, 782487805, 782565435, 76923128, 7340116, 782581991, 782618878, 782746439, 782782719, 782848256, 782909457, 782926017, 20267094, 594575416, 782960803, 783007803, 783024187, 783040513, 210518033, 783057084, 28545281, 4636734, 226967768, 783110402, 783288292, 131825882, 52543664, 185581874, 783335546, 783351893, 783388931, 783503620, 783613968, 783630338, 783646721, 65699857, 783663266, 31391997, 783697469, 768819286, 783749381, 783810921, 783843571, 783896838, 783962375, 171397384, 784109833, 784221360, 784271093, 784351634, 784400615, 65781819, 93651274, 784437514, 784764234, 784814347, 784892398, 59818177, 784926213, 784973906, 784990396, 688209936, 785040048, 785105557, 785154066, 785171917, 785219832, 755843131, 785256716, 174440908, 19383814, 785383653, 785416414, 785450240, 785498423, 785564077, 785613799, 46612513, 785680235, 52039949, 785745709, 785809605, 785846542, 785925532, 81625089, 785990593, 786059535, 786153879, 22937880, 786186462, 786219409, 786256144, 786333909, 263536699, 786366657, 786399581, 786432414, 786485521, 786530851, 786595895, 786612424, 786647203, 786698514, 786944275, 787021882, 20840507, 787038789, 53100545, 787072105, 787155650, 787251416, 787283984, 27181465, 787304724, 787435797, 787501334, 787595457, 787629237, 108773592, 787681559, 66928714, 49004580, 787726580, 787775890, 167690649, 787829016, 787906561, 787922975, 37732415, 787943705, 135249979, 788025626, 104005890, 788119822, 788153579, 788218042, 788251932, 788283394, 35782714, 788299858, 788316346, 23314495, 788353307, 788664604, 788730141, 788840743, 788873464, 788907148, 41959482, 788955329, 788987921, 789004492, 789037092, 21020735, 789057822, 789119399, 789152307, 789202955, 789266618, 789299470, 789336351, 789446900, 167903826, 105005560, 789495866, 51167826, 789514176, 789561794, 789643281, 1116526, 789659649, 789676309, 789710050, 789758333, 789827872, 43828106, 648757857, 790020933, 790056144, 790134867, 179142904, 790171937, 26198312, 88784932, 790253858, 790495394, 235536468, 790531958, 547618871, 90472534, 188399717, 790593722, 790626362, 790642747, 37126180, 790659106, 28000272, 790675487, 361201700, 55787536, 790696227, 788234709, 790806812, 398573642, 790872280, 499499873, 790905403, 790971893, 791019902, 52543747, 74268673, 791073060, 791286053, 791363600, 791380052, 740900900, 791400742, 791445735, 791478288, 791494715, 791511043, 244564050, 791527506, 791544749, 791576635, 791593266, 719994943, 791625729, 299270228, 791644786, 93471306, 791691323, 26444082, 791712039, 791806171, 29949969, 791841158, 791888671, 791937087, 90898490, 791956218, 791986207, 608010324, 52544429, 792002748, 792051768, 792068152, 792084496, 792100881, 792117334, 439615546, 792133635, 792150032, 792166484, 792182820, 93225227, 792200410, 792252712, 792330510, 792364153, 792428730, 792464356, 792510500, 792526904, 31981816, 792543290, 792559675, 792576544, 792642495, 792707073, 44302353, 29507586, 793071914, 3211300, 793153835, 793265333, 69681972, 793317676, 793415981, 793493588, 793514286, 793628975, 1115177, 1118512, 793724922, 26198602, 793776433, 304381969, 357040967, 158498874, 30507025, 793891122, 241058014, 793973043, 794052828, 405307608, 794099728, 794120500, 237551652, 159006778, 445038674, 794231805, 794263568, 83492867, 71450746, 31129601, 794284341, 794510661, 312279571, 130777105, 794574906, 794595638, 248086587, 106938590, 794657261, 426803233, 794689780, 794738724, 794755459, 794788017, 32882747, 794820693, 794853620, 794902591, 794918945, 26951738, 794936228, 794972471, 664272930, 795087160, 238616612, 795151296, 795201849, 795263846, 504185325, 795300154, 795426817, 200114260, 795443233, 795463995, 795541540, 795558136, 65503288, 795590715, 795607226, 795639893, 795672577, 795689162, 795721786, 795740644, 795791066, 37945538, 795824444, 795885652, 29688005, 795902036, 795918984, 795951352, 795984142, 796018737, 749961843, 796168509, 18727128, 639762824, 796295356, 56328276, 796344536, 796377089, 796393502, 29360184, 796410072, 796447038, 796725567, 4636727, 796823872, 38797375, 796889409, 248135711, 796950721, 796983515, 797017679, 797083154, 797114810, 797151554, 797233475, 797278397, 797310992, 797331780, 797397317, 797475127, 797540622, 797577542, 299417682, 797659463, 797856072, 287719780, 797918259, 798003529, 55099428, 798069066, 798130234, 1261627, 798146576, 798163530, 798200139, 389529793, 798359635, 798392513, 798426309, 798457873, 798475209, 798523477, 1228856, 798560588, 798605726, 798658893, 18710721, 669532218, 798720031, 798736442, 798752801, 70156304, 798769746, 798801978, 101581022, 798818389, 798851381, 798883841, 233734175, 798904654, 799001862, 36519999, 799052111, 799330504, 799392116, 764428594, 799428944, 799494481, 8011834, 47857880, 799543634, 799688586, 544571477, 799768577, 799785019, 4800515, 799805779, 1114749, 799986004, 800066068, 800166229, 800342476, 86949924, 800374968, 21020706, 800461142, 21659679, 800522453, 222035969, 259392011, 800555888, 800637698, 601260035, 199311376, 800690519, 124241660, 800772440, 601506098, 800850514, 800887129, 800948417, 800985434, 801095947, 801128620, 801161274, 323323315, 159924260, 801177867, 801214811, 801378652, 801489140, 92373478, 801538634, 303301101, 801571064, 281559058, 801603666, 801619969, 801636369, 36323346, 801657181, 89539053, 801718465, 398589968, 20611234, 801755486, 35733562, 802066783, 802144470, 802177080, 802193439, 271614036, 802209851, 802226373, 802259137, 802291796, 802308180, 802324481, 290537489, 802345312, 802406458, 207618980, 802423133, 802455554, 802471967, 802488337, 802504740, 28524600, 802521735, 802570298, 26099715, 802586657, 802603067, 4800543, 31211578, 802619578, 802652787, 802684986, 802701313, 47841297, 802717755, 35094586, 802734350, 802767429, 802804065, 802848823, 802865236, 26198301, 802881724, 802930721, 182292834, 802947131, 802963489, 40353823, 802980133, 544620630, 803017059, 803115364, 100237330, 803160372, 803210025, 803242000, 803258427, 245350402, 803274810, 803291195, 803307523, 803323963, 803340346, 266666017, 803356703, 351420434, 803373244, 803422266, 803439028, 803471361, 130646049, 803487829, 803520586, 803536925, 803553297, 65798180, 803569878, 602832897, 803602625, 803638907, 803684411, 803700794, 803717183, 803733562, 603209745, 803754341, 803866041, 803930468, 803962897, 803979868, 804028479, 804044836, 699383811, 804061269, 804094353, 804131174, 804258252, 804290746, 47153448, 804323386, 144294311, 804340297, 804438985, 804487464, 804519954, 804536378, 804552763, 804569151, 804585505, 804602104, 804634641, 804651026, 113442852, 804671847, 69681553, 804847698, 804868456, 804947060, 804995558, 388956386, 29065272, 317554762, 805028273, 37912968, 805065065, 19383493, 805163370, 158662657, 805228907, 805323000, 805359980, 805437602, 203030906, 14467108, 805470487, 805507437, 478789846, 29081601, 805568597, 132694100, 805601843, 805650488, 409944098, 805671278, 805732354, 805748738, 564281346, 805765175, 805781590, 805797944, 51789888, 806261103, 806341490, 806436923, 47317334, 806453332, 806469818, 806502671, 806535412, 228458529, 100073490, 806588784, 806699067, 54247479, 806715614, 806748194, 10715167, 806769009, 47317287, 1115050, 806862906, 806879266, 41058335, 806895645, 806912074, 806928442, 46071810, 806949234, 807075962, 98336890, 807094066, 807174499, 807239969, 807272481, 807289020, 807338017, 807358835, 807439864, 55132249, 807502021, 807534764, 29098200, 807567418, 807583828, 102531130, 807604596, 807731259, 150700232, 807748232, 29098250, 494043378, 807781958, 807829748, 807883125, 100647136, 372769170, 807965046, 808112503, 808207661, 153518273, 75431992, 562348573, 808257664, 529907713, 808338707, 71450641, 808386591, 808403003, 808419514, 808452471, 22528016, 808484951, 808534739, 287719638, 808571256, 808764537, 121209386, 808833401, 808928396, 313360466, 808976608, 809013626, 809123871, 809140308, 355270849, 809156986, 809189620, 141377831, 809238722, 809271710, 809320646, 809355214, 809439611, 809517516, 620118432, 7258175, 809550299, 809582677, 809615377, 684818490, 809636220, 809746449, 809762849, 809783677, 809861819, 809910621, 21839954, 7241759, 809947518, 810226047, 654786722, 810338898, 810406272, 37945747, 114163798, 108773574, 810501134, 58769437, 810584270, 38043684, 810635649, 115982369, 442384978, 810717570, 380862465, 541982721, 573571130, 810811408, 810827835, 108249292, 810844553, 810930563, 243925485, 810996100, 811061637, 654163984, 811110790, 23724063, 400080912, 811224200, 811286557, 811307338, 811368634, 811401525, 811438471, 811565139, 811597825, 811614210, 4800514, 341229584, 811630608, 811647060, 811663557, 811696213, 376668723, 811733384, 811811217, 811843854, 811880841, 722584091, 811925717, 58769467, 811958288, 811974889, 812011914, 812274059, 812370726, 812421516, 200048888, 27181304, 812487053, 37917070, 812564539, 85377060, 812585359, 812777556, 812793914, 38027338, 19644505, 317931943, 812811547, 812859481, 812892457, 812924986, 14467146, 812941705, 813023248, 204669303, 813044112, 813088826, 53100560, 41058335, 813105168, 813121538, 813137978, 216956987, 731087709, 813158801, 317916033, 813224338, 813301776, 77365250, 22397688, 813318229, 813351001, 469287171, 813384516, 813416450, 813432890, 813449233, 271745057, 18759696, 813470099, 813662702, 343884339, 813695471, 813760760, 813797780, 341049402, 813924381, 166871122, 813940772, 813957507, 443088978, 230244434, 813994389, 814092694, 814186581, 814219891, 814256535, 814303529, 117948602, 814387608, 814715289, 814891363, 814956811, 814993818, 815055096, 815087674, 1115177, 815106933, 132923662, 815170011, 815206811, 19382542, 815448740, 2375868, 29688050, 815518108, 22397576, 815583645, 301367527, 815628580, 815694043, 815731102, 815808782, 815843126, 815906832, 815923259, 815939706, 99369082, 768819233, 144359618, 163742180, 815960479, 816087491, 23232714, 816120066, 1114539, 816152887, 816218199, 816267450, 816300049, 816316490, 816332979, 37847073, 816370080, 812466232, 816432390, 816496722, 816513268, 41898401, 816562472, 816594975, 479100986, 816611639, 816678734, 816729450, 816812450, 174997562, 29099538, 816889941, 816927139, 817070294, 817107364, 817170838, 817234004, 14467073, 817250305, 84754506, 817271205, 817332421, 817365185, 39649632, 817402278, 817596730, 817628026, 817680807, 22315044, 817729960, 394691466, 469795002, 817823992, 817857312, 817889311, 817905850, 43794727, 817938465, 172163088, 817956141, 121930025, 818006590, 488063190, 818057641, 4669495, 99256015, 818218078, 818282618, 624328735, 220184577, 818298939, 818319786, 818429983, 818446392, 818462782, 193970362, 818479186, 818495674, 818532779, 818593985, 818626593, 818643132, 818696620, 818778541, 818856214, 818889079, 818921791, 18759739, 19399057, 117129409, 819024302, 819167846, 819216602, 819267246, 70811707, 819319215, 819401136, 819511595, 819544081, 819561048, 534986753, 819609908, 819661086, 819725144, 819810737, 819921089, 108642305, 819955374, 820007346, 380829732, 820134074, 820171187, 820251430, 820302260, 820429120, 820494621, 820527304, 19398828, 820564405, 99254535, 820642014, 820676142, 76578875, 820741557, 30507092, 199426049, 820810166, 820838416, 1277977, 820859319, 820953523, 729137338, 820985873, 79151168, 52232278, 821318073, 821399994, 821477618, 72089682, 821514683, 821576032, 488505466, 821608506, 83951675, 821629372, 33555244, 821706810, 135561639, 821723220, 821739556, 821755921, 24805450, 302579713, 821776829, 821887060, 625803573, 821903416, 502005842, 182157375, 821921532, 821957054, 451788858, 317390932, 822051130, 822100531, 822149358, 822198274, 808239106, 313360439, 822215124, 822263866, 442466321, 74416210, 822280436, 822329506, 19399028, 822362170, 100876347, 822383039, 770277538, 822624874, 822673960, 533495893, 22397722, 822706590, 822755411, 85311572, 324813871, 822788583, 822837559, 822903186, 822953463, 823022016, 823119605, 823202241, 18715074, 823263248, 53870944, 823279652, 823300547, 823361722, 509378561, 823394388, 823411251, 1116084, 823459929, 170295359, 823492852, 823546308, 823590971, 216956929, 823611845, 291520874, 823722531, 823788411, 823820494, 823873990, 824134656, 824168903, 824248634, 824283592, 163844553, 19579189, 824410168, 824426578, 43925562, 824444502, 824512970, 824590889, 20267011, 824624505, 824688854, 824725963, 56279043, 824791500, 825020877, 21233880, 64864257, 825180408, 825213481, 825245893, 825278818, 825327927, 825393422, 825427789, 825508063, 825557048, 19530188, 38535629, 202015700, 825573627, 825622610, 825640397, 825688514, 19530781, 825770998, 756253158, 825836410, 825889230, 826098507, 826151375, 826245543, 219971777, 41877589, 71549012, 826278121, 826310715, 826327288, 826364368, 826409407, 826446289, 826511826, 826623387, 826671137, 826687972, 247463972, 826721389, 826769622, 826806112, 826851729, 826888659, 827003348, 827080778, 2380245, 827097090, 60145722, 827118038, 827293890, 827326652, 330563666, 827375957, 827424827, 827441274, 443940895, 827457937, 574046264, 827490389, 827523074, 5603359, 827539883, 827573249, 827642327, 827670988, 827703332, 827719713, 52494393, 827736089, 724353059, 827757016, 828031170, 828063802, 392822801, 828080326, 828113643, 7192634, 828162610, 828211258, 569950263, 828227772, 624198523, 828278981, 828325889, 828342331, 28459026, 828363225, 21659704, 828474942, 1114364, 828523202, 43827616, 828576218, 828637370, 37224512, 828669953, 828686392, 1359930, 828702906, 828735547, 828752069, 828784725, 828821979, 303022302, 828883497, 828916511, 352813113, 828964922, 288669699, 829245045, 679002141, 18563130, 2378266, 829688227, 19235069, 829755870, 829816833, 829833275, 347848926, 526237881, 829854057, 829948244, 830013729, 830046292, 830062888, 830099935, 830128162, 7913508, 830149088, 830263777, 830343794, 830394850, 830521533, 85590074, 830554305, 199540795, 830591459, 830689764, 830833000, 830882023, 830915054, 191561976, 830947553, 830981065, 831029265, 831045690, 831062072, 46268498, 831082981, 831144184, 474300433, 4669443, 831177665, 831246822, 831341931, 831394279, 93159425, 831471912, 831508968, 831668787, 183894191, 26444047, 22724641, 831717939, 831771113, 13287453, 798523450, 831848506, 831864888, 831881298, 831897617, 291291152, 831918570, 531873848, 54722562, 831951339, 29507668, 832077857, 832094290, 832110593, 396836948, 832127060, 832143546, 112722178, 37503009, 832176879, 832274463, 832291038, 20086802, 832328172, 832508397, 775946241, 832569575, 832602175, 832620294, 832667834, 832700902, 832733242, 7634947, 832751190, 832815453, 563036161, 832848120, 832880961, 321044711, 832914962, 832946531, 37912897, 74235905, 833016302, 833192158, 833224763, 833241108, 833273858, 123469882, 15646721, 40665106, 833290455, 1117184, 833323252, 833376751, 173752383, 833442288, 833585465, 833634867, 833683538, 833700062, 48922706, 833735456, 833781761, 833798545, 833835505, 833913025, 833950194, 833994769, 834015731, 834076858, 834109644, 834142424, 20594973, 834175035, 834191553, 834224212, 834240712, 28524578, 18713561, 834277876, 333824034, 834389299, 834420912, 499450104, 834453562, 834470136, 251380033, 834502692, 834519128, 834553694, 834654709, 834732650, 834781598, 834830369, 19579056, 834849667, 834895873, 834912289, 834928828, 834978429, 835010561, 56672289, 835027123, 835059771, 238616592, 7618644, 835080694, 835141790, 835174586, 317931714, 27181304, 835208259, 835239972, 5537847, 835256968, 835289230, 835321869, 835338248, 835354645, 2377271, 835371031, 11911193, 835387450, 835403805, 1556564, 6389783, 835867128, 836030969, 836108306, 836124730, 836141115, 836157473, 302940242, 836176754, 836272164, 836288586, 25100370, 38535253, 836309498, 836375035, 407208020, 836452907, 80298058, 527974401, 836506108, 836599871, 836616255, 836632798, 836669949, 836747486, 836780120, 836812816, 836830875, 836878337, 836894737, 4636728, 836911120, 836927490, 836943908, 7209016, 836964862, 312329733, 837046783, 837177856, 837321013, 36472005, 837353530, 837374465, 99336208, 22560831, 53416450, 38535253, 837437180, 87162939, 837472771, 837550113, 837566650, 837599318, 837615802, 837648570, 837682326, 837714630, 837746874, 360251701, 837779489, 837795876, 837812225, 837828811, 837861564, 18710837, 837913150, 23052581, 837964292, 838174021, 838238874, 838271867, 838303826, 838320183, 838336528, 838353533, 838390277, 838467600, 163610640, 838484027, 838502146, 18677796, 838550365, 259392730, 657441610, 838598738, 838615056, 176586811, 838636038, 19382708, 838746804, 838861746, 18710877, 216088661, 838914567, 838996488, 628146192, 1114954, 839057409, 839073848, 2850840, 839094793, 839156061, 223051777, 839193098, 300040250, 57098273, 839258635, 839451565, 839485190, 151732282, 839532629, 839565545, 839602700, 839696581, 839730501, 839795089, 17858619, 839828731, 839913997, 840040506, 840056891, 840077838, 94289921, 85688376, 840139565, 124715067, 237797434, 840204304, 840220731, 840237118, 615974485, 840258063, 840434752, 840482875, 47858051, 840499217, 840515809, 840548408, 840564818, 215531577, 840581178, 840597739, 840630273, 96060029, 840646900, 491864416, 840696005, 840729060, 840761592, 840798736, 840876048, 840892490, 840908997, 804372722, 20873217, 840942180, 840990779, 841007178, 686719034, 841023701, 841056504, 841089108, 207683620, 841105652, 841154855, 841191953, 841290258, 841367783, 841400402, 428752954, 841421331, 213631192, 841503252, 841781472, 841875515, 841893690, 78201325, 841941206, 841974008, 842006591, 132694049, 842022914, 842040549, 172868052, 842072295, 842104863, 842121233, 423739410, 842137848, 842171763, 842219921, 842253594, 29098345, 842317881, 35913786, 842334401, 842366993, 41910696, 842383553, 842416376, 842448959, 14254143, 842469909, 74022913, 394805332, 108773725, 842661971, 842694659, 842711098, 202113083, 840073249, 842727509, 842760208, 106923538, 842776661, 842809403, 3211326, 842825905, 842863126, 842912279, 129450020, 842989597, 447053882, 843335871, 843385451, 843448404, 843464890, 843497886, 843551257, 843677894, 843710465, 54509642, 843731482, 843857956, 76923056, 843875708, 843939924, 173555928, 843956308, 843972799, 844042779, 844173852, 844267831, 19235049, 87310602, 844333057, 84754434, 844349524, 18711324, 844370461, 562708538, 844452201, 844547009, 844616222, 844677342, 844711676, 844747295, 844942953, 836468950, 844993056, 845053953, 172261460, 845070395, 845087278, 845119681, 845152292, 845168657, 845185098, 110100562, 845201465, 303628324, 845218152, 845269783, 845320737, 4685882, 845386274, 845430873, 845464877, 409944097, 845513285, 845550115, 59064323, 108642367, 76923073, 845594660, 845611009, 503988242, 845632036, 845693142, 845725889, 845758497, 845779493, 846004465, 147030047, 439517185, 846037301, 104480842, 846069957, 50938217, 846102529, 846118971, 846135482, 7487545, 846168673, 846201054, 38715476, 846235898, 42765863, 846299324, 846348689, 846381300, 1114418, 846434854, 19546175, 846531376, 846598695, 846710450, 846757889, 7258138, 175161360, 846778920, 846909993, 846987827, 32620575, 847036701, 847069217, 847085624, 64864338, 327254597, 847102890, 847135745, 847200463, 73170945, 847237674, 847347934, 847385131, 529907713, 847462741, 847511800, 847548972, 847675479, 19579201, 847729197, 847774189, 847806493, 495566904, 847822904, 847839314, 19579272, 847856315, 1654801, 704413754, 847904974, 847954197, 756957200, 847988085, 410009656, 848019473, 848036029, 848068625, 212451345, 848480368, 848532014, 848642234, 848679471, 848740788, 848773317, 522813620, 848806074, 848843312, 848920607, 848937019, 37732369, 848958001, 849056306, 849104247, 849151275, 333824063, 849203763, 849281238, 849313850, 329908382, 849330261, 849363276, 98796663, 44351674, 849416756, 849527080, 52740154, 849560939, 849609982, 108298298, 849658044, 849707238, 849808671, 849838097, 849854657, 849887718, 849924661, 850083922, 387055617, 850104886, 850149412, 610353215, 850165982, 850198591, 43155519, 850215475, 850265566, 850296848, 606765089, 74711041, 850313233, 89325584, 850330206, 850378769, 850395199, 791871674, 850411578, 850427960, 850444324, 28000378, 744685604, 850465335, 850576678, 850624528, 850640912, 850657296, 850673680, 239009808, 36339713, 850692154, 850743864, 851022393, 851085369, 851132475, 103072131, 851151140, 851202618, 30245273, 143196445, 851379637, 851444005, 851476734, 851525994, 851558401, 116015187, 851579192, 851640542, 851673281, 851708342, 851759675, 851807193, 851837491, 851886266, 851919691, 851972668, 852021821, 852070974, 144622015, 852216428, 563397055, 794853569, 852263098, 294846957, 852295991, 852361290, 69255204, 852378308, 852496959, 852590676, 842317882, 852607059, 852639828, 59179082, 852656215, 852705499, 852742720, 852903008, 852951741, 853000508, 853065744, 853082128, 26198565, 853098714, 853147665, 853165169, 321274280, 853196833, 114327738, 853217857, 853278906, 853314327, 853360699, 853377060, 62390357, 853393466, 32899090, 853410036, 853458980, 853475361, 853491794, 853508159, 853524513, 853540865, 405897233, 853561922, 39649442, 853639184, 12156959, 853655858, 853688770, 853771305, 176586768, 853803541, 853840451, 853903053, 476791176, 853955140, 854020677, 854151750, 38797374, 245612545, 854212852, 854262520, 21659682, 854315591, 854360082, 1245210, 92995618, 854381128, 854687761, 302710956, 854704403, 854740689, 854786861, 386105428, 854852565, 854901336, 854951109, 854987337, 855031844, 855048314, 855064612, 855081132, 855118410, 855197403, 855261246, 33832991, 855277627, 43745296, 855298635, 210124802, 210129484, 855474369, 855507346, 125812798, 182550564, 417873953, 855560781, 855638881, 127025153, 855670970, 855708083, 855785503, 27787323, 855802122, 855835877, 855872078, 856031315, 856064231, 856096804, 856113328, 856145956, 856162320, 856179025, 856211678, 856248911, 856310307, 856375605, 251592705, 856412650, 856440848, 856457239, 35684436, 856478288, 856740433, 856784925, 856801281, 856817681, 856834077, 856850433, 18546705, 856867038, 856899778, 624115794, 856937042, 25853953, 856998248, 857047332, 152109073, 857114682, 857161810, 857178370, 783630338, 857210911, 857227300, 19644632, 857243649, 233734161, 857707091, 857899091, 857931793, 771112977, 857948196, 30163002, 857964906, 857997329, 858013713, 856309845, 858032484, 299581457, 858079476, 73449742, 858128458, 187154506, 566853690, 858144826, 813137921, 858161153, 858177620, 284344404, 858193978, 858210526, 858243260, 858292260, 858308692, 858325340, 858357791, 93159483, 858378836, 858444373, 858570945, 109101315, 2376014, 858608214, 146964738, 858702017, 858739287, 858947986, 858999710, 109494303, 859050584, 859177202, 78430290, 859214425, 859341048, 859373652, 20250657, 859390196, 859439348, 859488444, 859542076, 859586793, 859619386, 859635768, 859652129, 859668698, 859717936, 859766820, 859787866, 859848897, 859881505, 333119676, 859897872, 87162944, 24559901, 859914787, 859979794, 176586768, 860000859, 860045726, 860045560, 860099164, 860176714, 860209169, 860225722, 860258363, 860274874, 860307654, 860340310, 860356852, 860406332, 860454975, 258998458, 265748564, 860471984, 860536865, 860553275, 860569636, 860586040, 860603965, 603652127, 860651609, 31178788, 608010276, 860688989, 860836446, 860913748, 860930049, 200376394, 860947169, 396886079, 860995620, 861014907, 861044810, 861061179, 257589264, 95109156, 861082207, 861127355, 861176002, 861210686, 861257914, 861290715, 861323974, 861356049, 861374255, 861421642, 861438011, 861454337, 40861699, 861470751, 861487162, 43794727, 861503694, 861552724, 286801953, 861569267, 7536642, 861618193, 861634577, 861651029, 861683960, 861716704, 861749465, 861786720, 862012069, 862065249, 862163554, 862322747, 223182849, 19398856, 838058046, 862340537, 862409315, 862503460, 862570329, 862601252, 862617663, 93012024, 862634047, 862650431, 862666810, 18711059, 862687844, 862814290, 21659711, 862830628, 862846979, 77054041, 862868069, 862912568, 862928978, 862945364, 862961665, 7913530, 20856862, 862978066, 9666644, 7471137, 862994644, 863029425, 120307771, 863076410, 676970532, 863093007, 863130214, 863224022, 29065275, 863261287, 863437964, 863488069, 863556200, 863650267, 863682746, 214909126, 863715329, 863732198, 345751738, 863767518, 863814213, 863846619, 863879201, 592363556, 863900265, 864031338, 864109037, 20840534, 864141498, 864175571, 194052151, 864223425, 864256186, 864293483, 864473708, 864583764, 277479440, 864600557, 52035966, 256327763, 864632914, 864649298, 864665682, 864682066, 17825874, 864703085, 864781026, 864813603, 10960970, 864882026, 590299167, 864960777, 865010042, 865042625, 865079918, 865210991, 865325680, 467484794, 109969492, 865386514, 865402896, 90472510, 865420542, 865468664, 865501185, 493781074, 865522289, 865583352, 238141526, 865615927, 865632314, 865648699, 2228346, 865669746, 643924415, 865751667, 866030196, 866157098, 866189370, 405897275, 866210421, 866369868, 866422530, 866451678, 866484455, 866516994, 89571344, 866538102, 866713617, 866730020, 62799928, 187416762, 866746369, 334479416, 23068761, 866767479, 736641640, 866877751, 866943174, 866975957, 867010818, 867078776, 867156161, 867189117, 867259001, 867336228, 688209978, 867352782, 569393185, 867403743, 867439226, 867532885, 64864313, 867566306, 867598337, 175161361, 867614778, 130990082, 867631106, 867647491, 867664165, 867696741, 867730981, 867816059, 170688760, 183812374, 867881596, 867958842, 867975201, 89112843, 867996285, 868073508, 868090794, 610353168, 868127358, 307609631, 868176511, 19399125, 868434334, 868483605, 868520576, 868647487, 117080426, 616038586, 868682375, 868716397, 54804964, 868778547, 868831873, 868879360, 278970427, 123305985, 868913794, 35520513, 868974840, 19579496, 869007447, 869056598, 869072954, 869089339, 41959440, 869110403, 18497618, 869257860, 869302275, 18563129, 869322997, 869384686, 869417454, 869450222, 869482990, 144359918, 869520005, 869712358, 93159453, 869748598, 869814918, 7618576, 869925058, 869957717, 869990401, 870008783, 870072508, 870124958, 332529681, 870187184, 870219976, 870252626, 870268929, 870285396, 334266370, 870306439, 870383652, 870400016, 870416401, 197951717, 870432990, 870467671, 870531474, 870580306, 870596666, 870613028, 38371330, 870629708, 870678561, 870694943, 870711313, 870727866, 870760715, 870793460, 870842562, 870875194, 870891583, 870907967, 870924474, 400932865, 870957244, 871010952, 871072724, 591052863, 871120898, 871137338, 63717409, 871153695, 871170234, 871207561, 871546898, 871563322, 273465403, 871584394, 91521055, 871695084, 871743875, 871776586, 871809258, 871875947, 871924449, 871973108, 872022079, 872038433, 872054843, 872071169, 27787297, 872092299, 872205027, 872235755, 872287757, 872367634, 872398849, 872416203, 872469132, 872518285, 872694174, 175570975, 872742996, 89014273, 872759995, 872808744, 872841522, 872874871, 872927886, 872988916, 873037857, 873054244, 870154256, 83509250, 118424045, 873075343, 873201681, 873218081, 873234625, 700989796, 873267422, 873299985, 873316581, 873349606, 89489901, 873382290, 26296338, 873431351, 24166417, 873497200, 873530224, 873616016, 873677121, 873709584, 873729024, 873758934, 398442737, 873796241, 336560186, 22397363, 873906258, 873922753, 873955345, 873972508, 874021052, 874070859, 874123922, 257687999, 874201170, 212402234, 513277969, 874222227, 874349174, 69501176, 874381328, 874400784, 874430480, 874450811, 38715425, 874479674, 874496018, 587677732, 874512785, 874545153, 30441489, 874561780, 108724440, 874610746, 874627128, 637386834, 874648212, 874926741, 874975894, 875020959, 875069662, 875102292, 28524628, 875123351, 875168726, 875233311, 875249738, 875266130, 875282449, 7569466, 172853070, 875300386, 875369112, 116277914, 875430500, 875479332, 39649550, 875544593, 31342610, 875561958, 875594005, 875626654, 875659507, 350765073, 875713177, 875791061, 875841926, 72335396, 875888641, 87162913, 875909786, 876003344, 104923167, 876020159, 478052682, 876052791, 876118491, 876155547, 876216356, 70238225, 876233105, 876265556, 876281892, 26296406, 554532865, 876298241, 876314708, 876331212, 876368540, 463717423, 876496119, 876548412, 876609750, 876643542, 876691687, 876724283, 312819796, 313196760, 64864287, 876743900, 465928249, 876860061, 876937482, 876970210, 877007518, 877215765, 877232154, 1114222, 877248570, 877264958, 18677818, 877281364, 877298157, 85508154, 877330514, 877346849, 877364411, 877417119, 230294079, 877477951, 877494765, 877529024, 877580960, 19579971, 12091456, 877937552, 57573435, 878018808, 878051544, 878084183, 878133437, 878166075, 49905727, 878182631, 878215344, 878248114, 4800576, 878297341, 878329941, 18759737, 878363074, 878449063, 878493714, 221544634, 26181831, 878510304, 878542849, 878559249, 645759034, 878575647, 250920962, 878592031, 15515681, 878608744, 602603553, 878657769, 878691284, 878740703, 878788666, 326550058, 878809761, 186433554, 336887844, 878903297, 40288315, 878919873, 13303870, 25100304, 878952692, 230392231, 158629924, 19235106, 879006370, 311050241, 879085351, 879149542, 879182021, 879215130, 879284899, 879642449, 879690337, 879722532, 11239482, 879740448, 879841633, 879886584, 879923876, 879984866, 880017409, 351420475, 880038565, 880099996, 880148538, 880164881, 261898303, 880181370, 255721508, 880197648, 22691897, 880218790, 880280467, 880328948, 880379828, 880410657, 24952868, 880427064, 25100324, 880444140, 880492560, 25346105, 106037307, 880509342, 880558081, 880575216, 21086580, 880623700, 47857931, 880640016, 4800541, 880657925, 880706112, 24805379, 880743079, 880804410, 880855827, 880903536, 318865465, 880951527, 611614753, 880984095, 881000449, 881016916, 354074660, 881034198, 603652097, 112869394, 101876113, 881102005, 881185448, 881246271, 72613924, 881263222, 881295418, 79938635, 881312126, 881361570, 881393722, 96059876, 881413948, 17907774, 881492126, 881525183, 121520866, 881557563, 881574646, 881625225, 440664501, 881688634, 881705044, 351666178, 4784187, 881721402, 423690241, 881739621, 298238036, 881808041, 881967138, 881983489, 62799931, 882000301, 882052879, 882099057, 882164469, 882234026, 882343952, 882360379, 35782715, 882376721, 44302354, 882394590, 129171472, 882428243, 882508856, 122552406, 882578091, 69648415, 205111380, 882721395, 882758316, 882786329, 882802723, 29507641, 883179577, 96272419, 883654680, 883671097, 883692206, 883723138, 883774127, 883839664, 884001293, 884116709, 69817009, 884178962, 156745786, 884200114, 884261061, 884294048, 884343216, 884424869, 884458224, 884511411, 884675065, 208798097, 37503062, 884752469, 884789940, 884839093, 571981858, 884899962, 884916477, 884949667, 885031131, 37322940, 885068470, 885162170, 885195041, 885228186, 736362497, 885260821, 885297847, 885457382, 885494456, 885620772, 885637204, 99369044, 885654385, 885719123, 885751892, 25346132, 885769491, 784384549, 885817530, 885850175, 885866529, 885882881, 613138449, 885904057, 886100666, 886243514, 789594148, 886276327, 886308898, 242237456, 886330043, 37224504, 886456721, 571392036, 70615073, 886489121, 886505508, 55869457, 886522152, 886557069, 886608572, 886669596, 107118786, 886739645, 886882337, 47677626, 886899440, 886952638, 887001791, 887067328, 887132865, 28409915, 887193822, 887226401, 736854017, 330498107, 887246945, 887296706, 887390422, 60261058, 887423377, 360677409, 887456949, 887505096, 887541624, 887755459, 60227777, 887832592, 224788497, 887853732, 887915045, 887947678, 887996474, 888012801, 51282187, 888030987, 888114240, 267075600, 888160470, 888197828, 888291851, 888345285, 888455185, 91406372, 888471636, 210436132, 310198273, 888488129, 888520721, 21659666, 888537189, 888574662, 888653911, 1114889, 888717500, 888771271, 888816795, 218628097, 888869576, 889012625, 889045077, 889077819, 3211265, 889094202, 889110781, 816075465, 260784210, 889143697, 889176248, 889258042, 57294911, 889279178, 889377483, 889536760, 889569338, 266223632, 86638610, 889586078, 889634874, 344883259, 889651602, 488505466, 889705164, 226771192, 40419412, 889798741, 165511227, 889836237, 889897238, 889931471, 889979111, 890011679, 890028218, 890065614, 890376911, 890585450, 890617938, 353173535, 890634690, 890717321, 890767057, 890830849, 158662659, 890848138, 20267082, 890896759, 890930774, 890994892, 615645220, 891027540, 610598948, 891044477, 891076624, 891093150, 20267010, 891126965, 891175125, 891207739, 891224081, 109494273, 891240449, 24805439, 891256890, 28000330, 891273438, 219512868, 594198561, 891306043, 891322384, 508788752, 891339398, 309641300, 891388531, 174391652, 891425488, 44662801, 891523793, 891600929, 891622098, 891720403, 891802324, 891863076, 891879440, 99368977, 22216897, 891900629, 892044021, 892109093, 892141864, 314163607, 892174830, 892207415, 331338454, 892272948, 892321794, 892338234, 892354577, 221003809, 892375767, 892420437, 892471424, 680525857, 892551226, 77054168, 892572376, 28459090, 892617154, 892703449, 892798410, 892850906, 41877589, 892928777, 892965595, 893031132, 35047738, 2380509, 92848186, 893227742, 893338071, 893388432, 893419521, 893436098, 893468756, 353009665, 893485114, 893501499, 893518564, 893567373, 893616325, 893653727, 91521042, 893866720, 893927892, 202784826, 893977894, 894030516, 29115935, 894076187, 894124117, 894157790, 894205983, 894222558, 894255318, 894288058, 894325473, 19939354, 894489314, 894566458, 369476384, 130531329, 894585123, 894665008, 2376451, 894714270, 894763256, 894800611, 894844987, 894866148, 2380517, 894910637, 895046374, 56246289, 110412403, 895107131, 50364602, 895124155, 69697885, 765870142, 895177447, 895406824, 895483986, 895500476, 895550681, 37371935, 895636201, 895730258, 244122842, 895762728, 895795287, 895849194, 895945819, 895993221, 896057361, 896073940, 896106744, 896139512, 476009195, 896172733, 896226028, 7077947, 896306440, 896368673, 740901170, 896385425, 896418049, 896468541, 896516477, 896586477, 896663848, 896697334, 619513197, 896766702, 896865007, 897076563, 897143536, 897206183, 76644353, 897270257, 47860198, 897320480, 897417469, 47235088, 897454833, 897534266, 897569233, 897630295, 897680425, 745444082, 897716441, 897766131, 541048916, 897843511, 637371433, 897909536, 897941810, 897974490, 898026880, 898093812, 898334955, 165478401, 898367570, 178126906, 568545058, 463339536, 418463920, 685457424, 898384920, 1277955, 773881892, 898814709, 899023494, 899076854, 899137821, 432816922, 899170569, 291012639, 899224311, 899339000, 899449246, 899498325, 570687547, 645284746, 899551993, 899601146, 899727377, 47317259, 899743992, 107201178, 899776529, 899793144, 899827681, 899892364, 51412995, 899940648, 899973179, 757219521, 899989520, 289538107, 900006381, 900043515, 94388308, 900153544, 99975340, 900186444, 900235297, 900251731, 900289276, 900333601, 341131323, 53887229, 900350528, 900383031, 900448631, 900485885, 900661482, 9076791, 900727002, 900777317, 26181845, 900862301, 22970653, 900923514, 900940269, 900977406, 16515135, 901087795, 74072080, 901136442, 22397126, 901155442, 28000313, 901206783, 901333401, 26411230, 173555954, 901365982, 125272123, 901403392, 51806493, 901627986, 507936961, 98795762, 901646273, 901698305, 901812994, 901890107, 901906618, 901939236, 37912770, 172819997, 901955851, 901993219, 19579056, 89768018, 902055051, 83394596, 902136803, 902171198, 30146619, 30146618, 143393709, 902217764, 26296376, 902238980, 902533893, 902648582, 499712058, 902709735, 902763271, 902840590, 26968150, 902874152, 902922647, 902955091, 902987777, 903005030, 903036986, 126075554, 903057993, 903102826, 903135655, 903168231, 903201061, 359579684, 903233784, 903271176, 903350586, 69746751, 903381050, 903397393, 88506401, 903418633, 903528923, 903561275, 343949328, 903582474, 42909780, 903664395, 903774712, 903807005, 903823444, 403456086, 903844620, 253722640, 903942925, 59736120, 904003888, 904052753, 904069604, 7176251, 904106766, 904221455, 904282365, 136544330, 73760824, 904314896, 327434271, 904331697, 904364226, 904397376, 904429652, 904446010, 305840307, 904467216, 904578611, 904659158, 904696593, 389529822, 905171731, 905248944, 37913003, 905286420, 905368341, 905429025, 905445598, 905478337, 905511844, 905543973, 905581272, 905627222, 905696022, 60096783, 905871823, 905920724, 905953313, 905974551, 906051643, 906067970, 906084586, 906150237, 906185491, 906231809, 19235056, 906253080, 21234026, 906397902, 906444818, 906461983, 874446865, 906510418, 906526778, 906543163, 30245142, 906560444, 906592287, 906608699, 906625309, 906658921, 906739729, 906756580, 906789747, 906870873, 906904534, 906969489, 143573050, 907002123, 907034687, 261062692, 251379979, 27967521, 907051252, 907105049, 54722593, 64864289, 907170586, 907247649, 907264637, 907296769, 907313185, 907329724, 907378689, 907395558, 878198842, 907428425, 80199913, 907531035, 637960225, 907627086, 907726907, 907789152, 637960225, 907842332, 22447751, 908021737, 908067582, 65044566, 704315450, 179847390, 908100776, 908165153, 908181988, 908214308, 908230712, 908247102, 7880762, 908263441, 908284701, 908362295, 908395019, 908443919, 908478431, 908509370, 908542011, 253886657, 908558337, 908574945, 908612382, 115425316, 908673041, 444448961, 908689566, 4800567, 908727071, 19579364, 908787930, 908836923, 908853515, 908890912, 909050126, 909082952, 99827745, 909132262, 909164545, 41648190, 185663524, 332136479, 909181159, 909214045, 909247344, 909328443, 909345229, 909377624, 909415201, 909611810, 909693731, 909820097, 909857572, 909923109, 909967857, 910016528, 144441361, 910033481, 910136102, 910217132, 910299943, 910393428, 115114000, 910414632, 29491258, 796311738, 910526778, 910557220, 92750036, 910574455, 910622892, 37322938, 910622754, 65454136, 910660393, 910775082, 91111498, 910857003, 911001077, 790593722, 630374430, 911049105, 911081546, 254476869, 911097914, 911114439, 425787594, 911163568, 911196194, 911212575, 438878694, 911233836, 911364909, 911458338, 189055050, 911474705, 911491130, 911507473, 911523903, 240926753, 911540685, 911573114, 911589434, 911605819, 911622161, 682852383, 911640256, 911672187, 74842921, 911704127, 179339297, 911722070, 248894254, 89293520, 911786000, 12271690, 911807279, 911954736, 224804882, 912134961, 912196078, 912228353, 912244753, 912261136, 912277535, 7209046, 912298802, 912392208, 19742802, 912408577, 912425016, 912441907, 912490527, 912506913, 912523300, 302940216, 912539834, 912577331, 19235146, 912724788, 912769255, 912801794, 912818234, 912834577, 912851192, 324829201, 912883943, 445038594, 912921397, 913031252, 44433411, 913052470, 913244379, 913276986, 913293396, 123682819, 913309900, 913342522, 94535739, 913358930, 913375291, 913391632, 38715395, 913409436, 541950618, 913473767, 913506552, 913539131, 218546178, 913557217, 913609527, 913653978, 22724625, 63979553, 913707832, 913769192, 913822521, 913883801, 913932304, 913948675, 913965138, 913982003, 839696642, 914030869, 53903423, 3211346, 914063418, 29507658, 914084666, 914194468, 26198117, 914215739, 214909412, 914260004, 3211328, 914281276, 54448957, 914374730, 914391099, 215678977, 914407427, 914423890, 914440440, 178176059, 914472976, 914494270, 914620432, 914636803, 914653381, 914686165, 914718721, 914735107, 914751570, 914768833, 914833424, 914849823, 914866235, 914882577, 846217217, 914899137, 473628861, 914931928, 914964680, 845709554, 914997284, 19546175, 915013668, 915034943, 915080765, 915128549, 915161172, 915177488, 915193930, 791593157, 915526465, 915620071, 915652780, 915689463, 915739458, 52237123, 915883163, 915931750, 915981489, 329236903, 1114405, 916048659, 916095377, 916127831, 58458114, 916177572, 385794107, 916247364, 916424083, 126468312, 916505862, 279003537, 180043978, 916570170, 916586719, 126500900, 916635664, 916652088, 56279102, 916668501, 916706117, 916768342, 916832469, 916865242, 916915384, 43155491, 916963944, 917001030, 917193191, 917241939, 917274625, 54542418, 627736577, 917291589, 917324046, 917361479, 917471795, 41915208, 917520385, 917537191, 220299297, 917569730, 2375868, 917602552, 917640009, 917719275, 233554811, 917771082, 917983737, 918061249, 918094072, 918126625, 918143169, 162496890, 918176832, 296321080, 918229835, 918410060, 494588749, 918475598, 918536405, 918568996, 177324034, 918585530, 918618382, 918650897, 918669960, 918717296, 918803095, 918847911, 918881033, 918913266, 165232695, 107774146, 918946891, 918978577, 918994977, 919011742, 919063143, 919142401, 600424466, 919163727, 919442256, 919502849, 278986840, 919524177, 919603207, 919666977, 919699519, 7503954, 919720786, 919883346, 919912451, 67043412, 919929085, 919962023, 919999315, 920076346, 920092894, 920130388, 920195925, 920371623, 920404052, 920420368, 22970397, 920437047, 920507222, 920601602, 920651973, 920699836, 920731664, 920748068, 55607313, 920769367, 920813627, 920829988, 13303811, 920849200, 920913518, 7143482, 920965976, 921043120, 183501192, 921080665, 921174484, 921223356, 154075153, 921272351, 921288762, 26427596, 921309692, 921408346, 36520018, 160350266, 740147416, 37912983, 921473883, 921567502, 19529959, 921600950, 921665826, 921698754, 921780472, 921813048, 921829458, 72089636, 843989393, 921850716, 922092410, 74399830, 922142278, 279298271, 922189840, 743276603, 232653691, 922211165, 922304782, 922338444, 922386999, 922419358, 247189210, 338133008, 922452716, 922501613, 922536099, 922583310, 922615992, 922702686, 922796226, 922830558, 11059202, 1687610, 922964831, 923177824, 923259745, 923369665, 222855380, 923402459, 923439155, 923485235, 923571042, 923631700, 923648016, 16367649, 923664420, 923680890, 19742842, 321323602, 923697453, 73924625, 923734883, 923877554, 923926757, 923959900, 924008507, 85819589, 532512954, 924029796, 924123338, 639976588, 924160869, 924172346, 50315281, 924254209, 924270595, 924287058, 924303418, 55361595, 924319930, 924352990, 924390246, 924554087, 24985631, 439091216, 924664002, 55133736, 265191455, 924701544, 924746082, 924799849, 924865386, 925106424, 925139885, 2380651, 107774247, 287883711, 925171771, 925188112, 925204511, 789676608, 96780410, 925220897, 389627905, 925237801, 925274988, 925455213, 925500374, 925564945, 925581345, 925602670, 2380655, 925663291, 925679860, 55869498, 180011082, 115081453, 925729089, 925762669, 925815664, 925864817, 925930354, 209240229, 42942467, 926007575, 926045043, 11026468, 1654787, 926220663, 926258036, 926339957, 926389110, 926454647, 96567358, 263831959, 926634872, 42303506, 106528954, 926744634, 158810535, 926765945, 18711109, 926810360, 128270353, 260784164, 926845937, 926892446, 926946170, 926973982, 8011862, 926990367, 92782594, 927404924, 927465554, 84132026, 927486845, 927547450, 927563809, 927585150, 35684369, 927662555, 927699839, 927826136, 627736607, 927858777, 927891489, 43417659, 867549200, 927907924, 927924453, 50708481, 927961984, 928071764, 22298641, 928088067, 150110266, 928109441, 928171772, 928202810, 662716433, 928224130, 191594580, 928273283, 928385414, 928432186, 7405632, 928453508, 1785872, 928548251, 928596174, 928645137, 928662905, 928727288, 928759966, 928792764, 928846725, 928923707, 142360865, 928945030, 929174407, 107200972, 929251975, 929300711, 929333640, 929371016, 929448180, 929502089, 929629519, 268959746, 80937268, 929710096, 917438523, 36339770, 929730514, 929780618, 929857778, 761693757, 929890526, 929923089, 525369418, 929944459, 930073686, 331338636, 930138309, 285905805, 930186974, 154615809, 930221344, 930255758, 930316346, 930332731, 930349130, 930365498, 57573412, 930386831, 930463802, 930480187, 930496548, 245350434, 930517904, 930612152, 930660517, 930693179, 930709505, 223821841, 930726591, 930775057, 930791626, 74318010, 930824606, 121929925, 930878353, 930922581, 93159441, 930960274, 931103301, 341573650, 931135972, 931168449, 640778273, 931203079, 931271571, 931348885, 72925220, 138280993, 931402644, 113016887, 931615637, 10190869, 100991009, 931713942, 70811664, 931828631, 931894168, 932107161, 932167852, 932200670, 932234895, 932287386, 932349472, 163007532, 932446517, 932479034, 166707977, 932496944, 132087844, 932528212, 932544513, 932561319, 932598683, 730284236, 932807719, 932888660, 932905201, 637878458, 44302339, 932937954, 305774628, 932971670, 933008284, 933073821, 933118011, 933134337, 933150723, 933167186, 933184051, 933232862, 933266364, 933298258, 933314618, 1114229, 933335966, 933577271, 933610268, 933663647, 933778336, 933838864, 921829460, 807026704, 933860257, 933953950, 934002773, 934040482, 45154305, 934100993, 934117573, 934150378, 140886018, 934215754, 934232153, 934269859, 36519954, 934417316, 934528106, 934576164, 934592719, 934625316, 56672319, 934642276, 934690879, 934707263, 416727226, 934723639, 934740168, 934777765, 29098326, 934972582, 935035186, 60686626, 935067706, 935085071, 935117048, 935149900, 383778875, 935203750, 999445, 935248120, 70221883, 393805841, 935280699, 935297025, 935313439, 562659330, 21020731, 935334823, 935526667, 935559888, 935596968, 935659333, 49676324, 935706812, 373309525, 935756641, 935788775, 935821865, 25870352, 935855264, 935969932, 936019994, 936067381, 139116603, 936104873, 47628347, 936230994, 936247358, 936263738, 936280122, 936296451, 20840511, 936317866, 19644605, 439730794, 936378763, 314835229, 220235821, 936411322, 936448939, 936542266, 895746107, 936558806, 936596396, 936760237, 291062752, 543785042, 53758299, 936820739, 936837123, 936853507, 936869891, 193658883, 937022383, 937263713, 937295908, 937314265, 937361652, 937410596, 12271632, 937428145, 937492513, 937508880, 75415611, 937529395, 937574401, 874496033, 937591116, 686096584, 937644976, 579682548, 105726015, 937693938, 937759665, 72482904, 937869667, 937937237, 938397793, 938442834, 84132403, 938464179, 938574461, 938606625, 938623012, 355565584, 938639378, 938655930, 938688941, 7487521, 938742708, 938819620, 938836180, 25133087, 938873781, 938934328, 938950714, 938969260, 939004854, 939098145, 33669136, 939114708, 939147322, 939163711, 47841343, 939180090, 939196475, 226951170, 939213042, 939246162, 939278570, 939343875, 939360272, 939376703, 939393210, 939425828, 46448703, 939447223, 939491821, 939524097, 939540482, 159006723, 939559084, 939589876, 939639151, 939676600, 939737277, 272875521, 939770133, 939802687, 939819041, 137592848, 939835455, 52543755, 939852106, 939884562, 939900964, 939917368, 939933882, 939966935, 940015618, 940032036, 61882385, 22298681, 139640850, 940048695, 940114343, 700973087, 940146706, 940163108, 271613985, 940184505, 27803665, 940229315, 940310561, 30245458, 940326970, 940343355, 70156362, 940359716, 152322290, 940376067, 28459041, 940392504, 940409265, 940441631, 940457987, 604192990, 940475422, 940523579, 940540148, 940589119, 940605642, 729350202, 940638771, 940687445, 940720163, 940736543, 940752932, 67551295, 298942538, 940774330, 29868230, 940965917, 940982305, 404291642, 941000366, 19644620, 941047844, 941064409, 941097931, 941146324, 941180027, 941261003, 941293911, 941326580, 941375521, 941391908, 181551311, 535724091, 941408274, 941424826, 941457627, 941490388, 941523321, 941588673, 941626299, 532169461, 160219195, 941773756, 941853422, 207618234, 511181031, 941900097, 941936599, 941998113, 942014757, 942052285, 942149499, 942180742, 942227653, 95158330, 942260708, 942293303, 942358730, 44662868, 942391490, 942424097, 942441328, 942526317, 942588090, 942620876, 808321060, 942658494, 376488982, 942870476, 373949896, 942932161, 942965978, 943018943, 943084480, 943145018, 54792044, 943166401, 943259822, 943313858, 943390804, 38223873, 943412163, 943543236, 943636677, 95683053, 943674309, 943734820, 943751170, 4177978, 943768146, 828342331, 943805382, 943931578, 943969223, 944046143, 76923840, 944062467, 944079189, 944128486, 169246961, 22724692, 169476180, 944165832, 944259286, 284737621, 944292146, 944324641, 944341189, 48447725, 944378345, 944455911, 90816596, 944488465, 944505572, 944555845, 944603218, 944619713, 944652738, 944734420, 944767034, 53416230, 944783846, 817250362, 945193435, 945228403, 945324880, 95338719, 945373242, 945389570, 945406429, 945476554, 945619000, 58458168, 945635662, 945684511, 571719739, 477446162, 945700927, 945717436, 945766689, 945799543, 945832158, 945865575, 206405718, 29868035, 945897665, 945930303, 593182753, 945946709, 945979606, 25034811, 946017227, 946061386, 946078395, 946126904, 946143729, 946192690, 946225359, 946262988, 25788433, 946356287, 946372671, 946389180, 946438160, 946454545, 946471141, 946503866, 946537463, 264142884, 54756297, 946585631, 32047121, 946602047, 24019002, 946619093, 946667713, 946700290, 946718131, 946765887, 946782271, 946798778, 946831664, 946880734, 946913335, 946929700, 946946081, 946962650, 947011642, 947028024, 947044385, 947062605, 947143910, 947192039, 947224698, 947241146, 947273745, 31703114, 947295181, 947388610, 947421266, 16515130, 947437864, 947473768, 947519755, 947552442, 947585057, 248742636, 947606478, 947683359, 947699729, 57393394, 947716113, 80248888, 44662906, 947737551, 947864021, 947896858, 619020372, 947962141, 947996633, 948043974, 948076577, 948092964, 948109328, 948125713, 948142876, 948191476, 948240578, 948273188, 948289552, 31703057, 948305956, 948322362, 948338721, 948356613, 283639892, 948409296, 948535332, 52887583, 948551736, 948568095, 948584481, 948601020, 948650173, 948682945, 948716186, 948748321, 948766672, 948830396, 47317168, 948880661, 948928545, 225525791, 948949969, 949064658, 949141537, 949159289, 949223728, 949272609, 949289840, 949371074, 327925973, 949405893, 76120080, 96239645, 949452889, 25100344, 949486170, 949553094, 949600567, 949666088, 949698844, 949769171, 949816205, 89325569, 402702391, 949879213, 949928122, 949960988, 52002872, 950028910, 950075578, 950113236, 335626241, 950190271, 950255813, 895762434, 950293461, 950405820, 950452520, 950485204, 155254800, 950518599, 950550704, 950583542, 405504223, 950649628, 950703062, 950780189, 70811710, 950812727, 845053981, 950831676, 770032102, 950894623, 950910994, 950927376, 950943761, 29507613, 950960184, 950976594, 950993084, 951042066, 27951162, 951058533, 951091233, 951107643, 951124165, 951156978, 951194583, 951320593, 951336961, 951353428, 951369757, 686997879, 951386113, 28196919, 951402512, 951418954, 951435461, 951468274, 951500801, 951517217, 83509330, 951533799, 951566889, 951599162, 951615547, 951631873, 43417603, 951653336, 107201577, 303349778, 951713851, 951730192, 951746578, 951762945, 951779384, 233930785, 951795770, 951812118, 951828544, 16482368, 952124140, 952172742, 12664888, 952205348, 952221938, 952259546, 952320486, 952353264, 952402002, 239157434, 952419637, 952500260, 952517758, 952566341, 952598951, 952631912, 952664952, 952714643, 952795258, 952812481, 952877073, 388399847, 952894742, 1114598, 952991780, 953008924, 13303867, 953057466, 953095131, 953254184, 19382525, 953291740, 953373661, 2376347, 2376535, 953484664, 5537828, 953532660, 95011239, 953586654, 953647290, 25346132, 953684959, 953766880, 953942625, 953975804, 845922320, 954056735, 954073171, 954106342, 12714042, 954138891, 954171474, 954188070, 877576228, 954258401, 954351679, 954368031, 954384417, 772472833, 954405858, 954450301, 954515489, 47858148, 954536931, 516702292, 954810949, 258621441, 954848228, 954912260, 954974452, 955023627, 955061221, 955105511, 955138220, 298877315, 955171094, 955203601, 71008562, 955221526, 955269318, 955301946, 955320221, 23986207, 39944228, 955372518, 171295188, 855261306, 955449588, 232751298, 955498781, 955531338, 744046666, 955547834, 955580770, 955634663, 955744450, 955777474, 99401729, 955859905, 955929576, 956060649, 313442363, 193855681, 956236323, 956301329, 53084218, 956317698, 326172673, 132792506, 956336475, 956366878, 956383290, 5603402, 956399680, 956416018, 4866072, 956432448, 956448790, 956465210, 956481658, 16482337, 956503018, 18563114, 956710914, 956727995, 337936385, 956776862, 956826014, 69615618, 956875593, 956923962, 956940344, 200228946, 147784261, 957169721, 957186074, 957202490, 957218838, 3457083, 957235231, 957251586, 8503312, 957273068, 957305837, 957366335, 957382720, 957399098, 55263250, 8011839, 957415498, 957431834, 1785881, 957448847, 22315010, 957481082, 957497362, 10223639, 957513792, 957530114, 61718558, 957546527, 957562966, 5537821, 957579328, 926072887, 957596798, 957644989, 1114822, 957677593, 957693974, 9093146, 957715438, 957743118, 4653082, 957759523, 957775907, 957792291, 957808675, 688163, 957830127, 957944816, 958153164, 958185956, 958218257, 427573303, 958234837, 958267481, 41304066, 958305265, 958365729, 727220465, 958382266, 958218510, 958416132, 958481193, 958513349, 958546092, 958583794, 948356371, 958649331, 958693398, 958709768, 7176205, 958726356, 958764020, 958857233, 958873633, 958890206, 958923037, 958960629, 959005095, 959042550, 959135745, 216613141, 959157239, 959217666, 26411024, 959234224, 959267030, 319012922, 959299815, 959332827, 959370232, 47317208, 959545402, 652607883, 959564312, 959615993, 959709268, 959725626, 373593082, 959743813, 959796219, 19136514, 959905850, 959922235, 87179266, 959943676, 960023588, 960069648, 960086033, 960107517, 41304065, 85688376, 960205822, 960479692, 960512355, 960578507, 60686829, 960627030, 960659457, 960676269, 960725734, 22216890, 960795647, 960923216, 960971061, 961004343, 961118609, 961151198, 961184007, 599687231, 961233834, 116588628, 961270541, 961331389, 405507473, 961364574, 961413720, 369180858, 961462330, 94388283, 961479380, 50970625, 961515329, 961579917, 111722763, 961642805, 961675265, 615153700, 961696768, 961745921, 961811458, 961876995, 961942532, 961986596, 16482320, 581664825, 962270214, 962297938, 15646755, 962314266, 962330680, 155811862, 962778119, 962843656, 962972856, 963035336, 963068743, 54446776, 963100942, 963133934, 963166462, 963215376, 963232723, 307088714, 963269641, 209567761, 270860536, 963433482, 266371088, 2380811, 963543463, 963576001, 30245357, 963608817, 963646476, 963728397, 699383811, 963806565, 963887856, 446923236, 767197370, 963936287, 963952698, 963969510, 80298039, 303480894, 964006926, 964252687, 964313173, 964350992, 964416529, 964543946, 24379623, 964591944, 47153553, 964645906, 607076408, 964739130, 22970369, 964756176, 964788309, 964822083, 964854235, 964890050, 21184570, 964940819, 965018093, 965051717, 21659706, 23232935, 965116090, 965153812, 965313427, 2376148, 965361882, 47202493, 851460340, 965410817, 236879931, 122978788, 965432341, 104169672, 965525988, 965563414, 965722330, 182305063, 965776407, 965869584, 453181441, 873431056, 965891096, 966131728, 966149404, 2380825, 966181358, 966213691, 135413776, 966230032, 966246459, 966264857, 966311969, 966328336, 99565627, 966349850, 966459409, 966476722, 13287457, 1114383, 966525121, 966558105, 11616292, 966590652, 966639839, 966688801, 96272443, 966710299, 312279237, 966772080, 189579748, 966819922, 33541658, 966841372, 967103517, 967196902, 967295060, 30441490, 967311546, 967349278, 967376913, 875888696, 967393362, 277479425, 967414815, 967491772, 967545888, 967639076, 887226424, 123797589, 967655460, 967671871, 967688255, 7208976, 140902401, 967704908, 967753972, 967803775, 119079381, 967868713, 13156415, 967906337, 967999681, 968037410, 968083997, 754270701, 968278960, 968360021, 968392763, 18710786, 968414243, 968542694, 129368557, 968572986, 189694126, 968589845, 144361438, 968627236, 968687802, 968720852, 285721084, 968769754, 261521425, 22937685, 968823845, 968905766, 968971303, 969130234, 79317198, 969200680, 969261731, 969342979, 40337424, 969359418, 969375773, 20627514, 62619862, 969392575, 969425185, 969457695, 969474106, 969490918, 969524278, 969574597, 969626665, 969834689, 969872426, 969937963, 970015054, 54771956, 970063903, 32374847, 970080478, 970118188, 970162803, 970194975, 114835519, 970211576, 970244194, 970282029, 970511406, 62799928, 120995856, 970540339, 970572667, 54449199, 970609712, 970719679, 970757169, 970817832, 970850533, 970883156, 970900204, 806518818, 970948794, 970986546, 178356519, 167690709, 971079866, 971113205, 299417617, 269762921, 132366420, 971183155, 971325633, 13303892, 12075039, 971358948, 284786744, 37913129, 971407604, 402342230, 971459288, 1114474, 971559988, 971653176, 100679739, 971672539, 971719612, 120848420, 971756597, 971833562, 971882513, 586695063, 971904054, 98926888, 972112319, 972145311, 391840410, 972194084, 972259345, 19267602, 972276126, 37912774, 20595629, 972326039, 972832830, 37371905, 972854327, 973067320, 973111482, 973144095, 973160449, 62259236, 973178233, 973242672, 973295985, 973390225, 973423813, 973460537, 973526074, 973636080, 973689486, 973750756, 168117165, 973783305, 973832507, 973882443, 973914170, 398589953, 973935675, 974028801, 350535697, 974045570, 974110990, 974144368, 974226020, 974274786, 25346111, 400736330, 974307937, 29103164, 974345277, 974454988, 974487610, 974503992, 19382373, 974525502, 974607423, 974656576, 974733543, 974766269, 974804033, 974864468, 974881182, 974932163, 974979156, 176210124, 975000642, 975213635, 975274559, 975306785, 945602591, 975328324, 975407329, 975438028, 975470625, 975487063, 975536335, 975568913, 975589082, 406224959, 975619347, 975667866, 975700002, 549847076, 975721541, 975799707, 975847440, 318767160, 975864052, 975913446, 975946744, 976028628, 166346788, 94535697, 976076863, 117080250, 976098374, 976224273, 976240676, 976257520, 976306420, 976355412, 389578810, 976372707, 699023396, 976404666, 976437265, 976453650, 748585194, 976475207, 976535588, 976552020, 976568506, 976601377, 976634049, 976666660, 744751167, 400687160, 976683210, 318865426, 976715934, 274677793, 976748575, 976764984, 870907937, 976781668, 976814457, 976882046, 976928826, 366641211, 976945764, 976994305, 977010721, 385843386, 977027074, 977043492, 271597585, 977059876, 977076257, 977092779, 977159586, 977224081, 26705994, 1654906, 977258869, 977305658, 977322043, 977338616, 397918264, 977376328, 152666196, 977700258, 330858710, 977764600, 977802313, 977879957, 977949448, 19645992, 47431681, 978031690, 352321552, 29605890, 23019523, 978190611, 978223162, 978239572, 978255888, 395362307, 978272444, 978321892, 22462831, 978354590, 264765441, 978408523, 978519081, 11239440, 978550970, 1114277, 978584021, 52544709, 978616568, 978649173, 978684665, 978731285, 22397284, 978765398, 978833753, 978895070, 978928459, 978977265, 939737106, 979026007, 625770513, 979080268, 144196165, 979293261, 237240626, 979370202, 979419531, 979454110, 979501112, 395362337, 979517525, 171398222, 979550401, 979583060, 24477727, 979599361, 44302338, 979620943, 979730491, 979748926, 979796277, 979829131, 979861748, 979911057, 979943610, 979978541, 980025402, 980042009, 980074529, 980092281, 980157082, 55640066, 980189428, 980238399, 980254802, 980271347, 980320330, 980336698, 980353083, 980369409, 980385825, 980402364, 980456528, 980533434, 28180498, 980571217, 980702290, 980779026, 711377251, 980795724, 980848921, 980926686, 980964435, 981041210, 981057832, 30228513, 981090548, 981139473, 123601125, 981155873, 981172437, 981205270, 31883687, 981237845, 981270795, 981303563, 981336308, 981385430, 981417985, 981434791, 981467324, 981516362, 981532708, 24477729, 981554260, 981765262, 981811236, 318292052, 981827642, 230850718, 981843969, 981860775, 981893183, 981909748, 981958849, 47857822, 20381882, 262111250, 981996629, 982204900, 101417230, 982237644, 982275158, 982335890, 982387292, 982451499, 982501573, 982548562, 982564881, 464764986, 982581445, 982619223, 982681139, 982763229, 982810655, 264798267, 982832216, 259932374, 983056385, 607043643, 983073261, 29835477, 983106736, 983154704, 983171131, 983187512, 26755130, 983204088, 983236794, 943259997, 983274585, 983318610, 499646466, 983335105, 393805857, 983367738, 400687121, 983389274, 983564558, 983602267, 263749691, 983695823, 354762808, 983744548, 48464347, 983760982, 983777338, 268124177, 983793886, 748224529, 983831644, 983962717, 984023226, 984056112, 984104977, 460161087, 984121588, 984175710, 291504326, 984252449, 39583828, 984274015, 984400107, 984437856, 984498212, 984514576, 984531281, 984563747, 984580127, 984596516, 984612927, 247119954, 984629349, 984662072, 174030881, 984678586, 984716385, 984912994, 985022481, 985041424, 50364909, 985105114, 985137770, 985191523, 985252037, 985285022, 985333815, 985350357, 985388132, 985499871, 810205217, 985546815, 985563192, 985579706, 985612663, 985645114, 150585558, 985666661, 985759802, 985776212, 807174892, 58442164, 985793308, 985841908, 985892069, 22462760, 985928806, 986190951, 19644991, 986317475, 986400148, 986449273, 87179350, 259440834, 986518632, 986595639, 29098279, 986660897, 261898276, 934150388, 986677406, 986710101, 1115289, 74891300, 986742996, 986775863, 18710742, 986842164, 986895465, 986977386, 987026539, 626999591, 574046264, 987154006, 987220066, 987250746, 987267473, 987299914, 987316225, 987332625, 970915876, 987354220, 987550829, 987612300, 57983147, 987660882, 19350014, 987693622, 987775462, 987807962, 908673108, 987856929, 53477377, 987873492, 987906079, 987922495, 212009023, 41304080, 987938852, 987955233, 987971620, 987988175, 988021186, 988103830, 988135610, 988171674, 144031761, 988217516, 988250113, 988266690, 988299452, 988348614, 988381217, 988397945, 988463399, 988497469, 988545487, 988594364, 988644048, 988676580, 988708881, 988725281, 988741866, 416383009, 988808792, 117637121, 988861550, 988921887, 988938296, 988955121, 989003835, 989020161, 989036546, 20971522, 989052964, 989069396, 279527610, 989090927, 989151316, 989167652, 410763320, 989184063, 989200570, 989233345, 989265951, 989282548, 989331752, 989366334, 989413412, 989429793, 989446330, 989478928, 55640081, 989495759, 989549680, 989742775, 989790600, 989822994, 989839391, 25034836, 989860977, 989954107, 927399992, 989970487, 989986900, 990003444, 990052556, 990085306, 990123122, 990199894, 990216503, 68567056, 990286963, 990364260, 990413096, 990445780, 23560498, 990478369, 990494954, 139231268, 990565492, 990626007, 270843937, 990658830, 990691384, 607289402, 990708105, 990789874, 990822433, 990844021, 990920928, 990953895, 990986241, 991002657, 981483751, 991019025, 991035617, 991068346, 991104472, 991134444, 991188086, 991314557, 991346747, 991363073, 991379650, 991417463, 23019551, 991462037, 991510772, 991559864, 991641636, 991658001, 991674865, 606945336, 991723576, 991739937, 246218783, 991761528, 991824896, 991854650, 406208568, 598523960, 991876217, 992040058, 992117137, 992149540, 992165889, 244203553, 992182568, 992218057, 127713339, 210681873, 992297621, 224952351, 992346822, 992379191, 992444472, 992460976, 992493655, 75874305, 992544477, 992591958, 992608500, 992662651, 221200443, 992722947, 992739386, 260816952, 992755956, 104005691, 992805357, 992842876, 992919588, 992936226, 992971782, 993039485, 993154174, 993232037, 993313915, 636028456, 993395206, 993460443, 993494206, 993542388, 993596543, 144425958, 993657018, 993693548, 993742262, 993793152, 993837089, 993853476, 993869840, 993886225, 993904189, 993956993, 994017578, 994083047, 994115617, 994132465, 994186370, 994263056, 994279860, 994317443, 994410526, 994426881, 106315862, 994443512, 994476619, 994541784, 83263506, 605946389, 994574644, 994623505, 523403468, 994640457, 994743428, 994792581, 994934815, 43581973, 994951185, 74612810, 994967842, 995000379, 387481636, 995016993, 995049557, 995084826, 445038625, 995136646, 995185799, 379617298, 995267720, 19383863, 995349641, 995394377, 872824914, 995804879, 37912843, 995852577, 995890315, 995988620, 996033325, 996098206, 996136077, 996245774, 996278290, 269666054, 996294743, 24166459, 996344168, 996398222, 996540475, 996559238, 996607364, 126075147, 125404950, 296862050, 996671902, 996725903, 996852116, 996885834, 996966455, 996982785, 996999601, 52543948, 997033427, 997081146, 18711398, 997098029, 94584914, 997151888, 997262661, 997326906, 997346396, 86245377, 997430417, 997523950, 997558075, 997610642, 997671387, 997703711, 997720148, 997736533, 997769404, 997822298, 998064213, 998097356, 998134931, 998195738, 998260799, 982499384, 639336932, 162693361, 998277313, 998310158, 998342719, 998359096, 998375506, 48365585, 686276812, 41992421, 998395789, 998457614, 998490832, 737558584, 998528148, 998609339, 998675605, 998835323, 26624219, 998916849, 998965450, 999003286, 999129341, 781697055, 999165977, 19398952, 999212326, 999260908, 96715091, 999314583, 26411025, 999391425, 999429272, 999606077, 999655430, 999707801, 999768278, 999800894, 110575633, 999817300, 999833658, 357974052, 999854280, 999915742, 999953562, 1000113985,1000177749,1000210490,1000229484,1000276170,1000308984,1000341988,1000374542,108642320, 1000407122,1000423425,1000439867,66912257, 1000461467,1000592540, 1000706553,1000789149,819265569, 1000915130,1000952990,1001144604,1001209938,978518032, 530907194, 104120337, 1001227369,72876226, 1001313439,1001374372,1001444512, 1001493665,1001619544,1001652306,1001668796,1001718092,1001766928,182550614, 809713720, 1001784538,1001832718,1001865658,1001897985,1001914452,1001931058,1001966606, 1002016008,1002079290,1002143802,332136465, 1002165410,1002242103,1002258521,712409147, 1002296483,119996452, 1002422769,1002471482,1002488592,1002525860,1002603938, 1002668518,1002700857,1002717197,3457088, 1003164837,67043410, 1003279526,1003438164,178126864, 916520996, 54968351, 1003459751,24527772, 1003508904,1003705513, 33538467, 1003765816,897433636, 1003783105,1003848844,1003897141,1003934890,70467666, 1004028288,1004115115,1004213420,1004339282,1004355777,793477151, 1004388437, 1004425356,35831824, 108216321, 1004486842,22402221, 1004519636,1004554209,541950194, 1004623022,157630466, 1004733306,1004786863,22462887, 1004885168,1004945901, 1004983473,1005196466,968624361, 1005273175,1005322549,1005355041,31260673, 1005376691,1005589684,246694209, 1005666874,163889169, 1005720757,1005846586,11239425, 1005863115,1005895738,132579994, 1005912122,83263547, 1005933750,1005994072,1006026938,1006063503,520159562, 1006094791,1006141903,1006195895,1006256159,432554221, 1006277816,35063327, 1006486252,1006536051,538181668, 18712256, 547865203, 1006589113,1006683215,1006736570,1006862737,1006895318,618316400, 1006927908,152256586, 100630586, 1006949563,1007097020,1007190897,1007255553,1007272369,121209292, 87130170, 1007304707,212746257, 1007322856,1007387038,37913306, 1007436239,1007490237, 1007632401,105611282, 27181334, 288313294, 1007649145,106102846, 1007717713,1007850686,1007913104,1007960148,1007976449,516210706, 278183952, 1007993186,1008042215, 1008080063,537313313, 1008129216,1008293057,1008391362,1008520704,1008583635,1008615460,1008631864,1008649789,504136055, 346750977, 1008702659,44892453, 1008784177, 418464965, 29065252, 1008850116,1009009041,789643282, 1009041822,155500785, 473530431, 1009091259,1009145029,1009271640,1009352938,225902625, 1009423558,1009532929, 61112353, 216956986, 1009550611,1009598550,1009614906,76578900, 1009632113,953204754, 1009697867,37027856, 1009729567,1009745978,111657186, 1009767623,29687980, 1009849544,1010095305,1010242762,2376244, 1010321253,703088183, 1010385144,1010418814,1010467013,19529976, 1010500540,26951681, 1010532437,39714875, 282198510, 1010570443,1010767052,1010827280,1010848973,98779361, 75431995, 1010894387,2375991, 1010974968,1011007758,361447621, 1011045582,1011272686,61162058, 1011335834, 168886437, 1011368296,1011417147,1011433473,669253649, 1011450104,7487570, 1011482625,673775675, 1011499067,1011515393,41959441, 708427833, 42860720, 1011534736, 1011586255,41915600, 100188653, 1011700945,1011744853,1011777537,23068942, 1011799250,1011848403,1011979476,1114449, 1012060977,1012122276,2381013, 1114336, 1012192470,1012401099,39354565, 127713362, 1012449682,109969410, 1012503767,1012662674,555417655, 1012711426,1012727994,1012760769,1012793380,662716479, 1012809940, 1012842554,1012859080,1012891711,201572415, 1012908433,1012946136,1013007500,1013055505,1013071946,110100562, 1013088273,727433435, 1013109977,1013317825,1013350981, 1013383416,1013421274,31014970, 1013530660,45547538, 1013547066,1013563986,1013596218,1013612628,1013628964,29442064, 1013647750,1013699803,15515703, 108183553, 1013829469,1013907642,1013940465,1013978332,1014093021,1014153233,1014170821,1014202554,498761787, 127418638, 1014240478,1014382608,1014399570,1014433575,1014497473, 1014531737,690552917, 1014600927,1014661416,1014694196,1014748384,1014857812,1014875756,1114195, 1014941721,1014994145,1015120142,1015152936,1015190754,198082746, 1015253090,1015283713,1015300159,1359935, 1015316539,1015332923,1015349307,38043707, 1015370979,1015414868,480673828, 1015431171,1278132, 1015447577,1015463965, 65044513, 1015841429,1015895269,1016054161,1016086584,1016102913,43797976, 1016120109,1016186991,158564415, 1016267300,1016332546,63275066, 168642443, 1016370406, 1785889, 1016435943,1016534248,1016709386,1016746489,1016829161,198082762, 904069178, 1016911082,612483130, 971096080, 1016987684,854147128, 1017004116,633702366, 1017025771,1017168202,152666174, 1017200881,1017238764,1017315345,1017331905,1017369837,1017463073,2381038, 1017500911,1017594604,1017643303,26279972, 1017675969, 1017713357,855523345, 1017774621,1017812208,1017872385,37650518, 1017890032,1017973852,1018057969,31342714, 287244532, 1018107122,1018249892,1018314938,1018348090, 1018397309,22970852, 1018434803,314441813, 1018500340,1018725350,1018762485,57000429, 1018839603,1018889089,216072381, 1018942710,1018991863,19644630, 1019085013, 684327098, 1019122936,1019265518,1019303161,1019431984,27181287, 1019478200,1114694, 1019560926,58769492, 1019609377,57426471, 1114949, 1019643424,162660827, 1019745530,1019794683,1019909372,32686097, 125501499, 244514890, 1020021340,1020084472,1020117076,1020133432,46071809, 1020150060,1020201699,1020231736,19382732, 1020248066,1020264481,16482366, 1020286205,1020447158,54448461, 1020497975,1020609712,116260920, 178356616, 1020663038,1020707826,1020756161,1020789045,31309825, 1021236480,9945150, 1021711618,18137146, 1021837434,295157819, 198967354, 1021859075,1021968713,102711537, 1022034407,740147414, 1022083073,1022099843,1022132777, 33964507, 1022165206,1022198480,1022235908,1022298255,350535681, 1022361844,1022410769,19628254, 1022432517,1022509550,1022542017,1022574826,1022645510,95341606, 1022705908,1022754818,1022771258,123322385, 1022789312,1022820388,1022836792,1022853306,279003425, 1022885946,941883466, 1022907655,1023082837,1023131980,23461946, 1023186184,126665267, 1023235337,299860050, 47858294, 1023377466,22217341, 1023399178,1023497483,443973691, 1023639610,1023655994,24805379, 1023672938,54804702, 1023726860,1023869466,1023934640,26182529, 1023967418,1024000033,1024016417,1024032769,268402747, 1024049188,396836919, 1024065729,29868507, 1024103693,1024196773, 1024234766,18714330, 1024414991,315818000, 1024496912,1024576697,624116653, 56246290, 1024640797,1024742673,668533061, 1024804211,38797313, 1024857362,1025152275, 1025295957,1025344794,1025409041,736641419, 32620545, 60227806, 95518913, 1025425594,1025458398,1025496340,1025594645,1025639488,1025692950,1025818894,37912853, 1025853351,1025917370,186810369, 1025950447,376668514, 104170440, 1026048311,620118418, 1026113553,1026130404,1026168087,221806677, 1026245773,376553767, 1026331928, 21233960, 1026441420,1026474228,1026523586,1026606824,1026671573,1026719975,1026752597,1026785547,1026818356,1026867393,1026905369,22397198, 1026981946,7471122, 1027003674,1027233051,1027358922,1027391710,4669456, 636144403, 77582525, 1027429660,1027474159,1027571771,1027588308,245039326, 1027626269,1027703139,60096809, 1027768506,1027801380,133971985, 1027872030,216383572, 1027937567,1028265248,1028325407,490143807, 1028341793,257540112, 290127888, 1028358753,1028391205,1028429089, 1028472848,87162897, 78184989, 1028494626,1028805923,1028882724,301269051, 1028953380,95519050, 1029084453,1029161423,33194073, 1029215526,1029161045,275480762, 1029313831,248414209, 1029406807,180715739, 1029455905,1029472509,255295524, 135561414, 1029510440,344621140, 1029575977,1029636129,1029652758,1029685671,1474573, 1029718463,1029750867,265682961, 1029786416,1029849310,490143803, 305479754, 1029882042,1029914680,7143440, 20611342, 307642426, 1029936426,1029996626,1030012929, 236027978, 1030034321,293503032, 1030079049,1030182187,1030340667,694747167, 50315295, 1030362412,196395066, 1030440011,1030473141,1030537300,69517534, 905658451, 1030559021,40665162, 490389505, 1030668372,1030684759,1030733857,876281914, 1030752589,64618512, 1030837550,1030914106,1030930449,19382647, 1030949468,1031017775, 1031345456,38141968, 1031504120,1031539149,244563986, 80134226, 1031585978,1031618577,1031635272,1031684154,1031700769,1031735055,96305153, 1031802428,1031847999, 764428594, 833273892, 1031864551,684425317, 1031897399,1031962836,1031996836,1032060987,43745316, 29835450, 1032082737,69697815, 1032208606,111247391, 1032246578, 76693853, 27181269, 1032328499,1032519697,1032541492,1032585298,1032602227,171294934, 1032634582,1032667220,59277348, 240926842, 1032683852,1032732794,1032749755, 416399416, 1032798549,460374050, 1032847419,1032869173,1032931385,1032978491,1032996133,84590593, 32620580, 1033049398,1033241139,1033289951,1033339066,1033371707, 1033393463,633946171, 1033438571,84426807, 168116708, 1033490832,1033535701,1033568341,1033601217,37896210, 291079274, 1033633985,120160330, 1033671992,1033751986, 1033830458,1033847789,1033912514,1033949698,1033999673,1034076182,4866081, 1034095819,1034175235,1034229050,1034305637,1034343739,1034486235,158908823, 1034523964, 1034601024,428605522, 1034638653,1034818878,1034879061,297156625, 1035064640,22937866, 1035273083,1035305191,1035339281,1035392321,108249313, 1035535736,1035584794, 55640067, 1035649242,1035703618,1035747546,1035801923,1035895006,33177684, 1035927569,1035943937,159154262, 1035960506,1035993172,1036009658,165232695, 1036043339, 34406474, 1036075009,1036091425,63258660, 1036113220,1036354154,86179899, 89014308, 1036403335,1036451899,717307920, 1036473669,1036534069,1036566772,1036616842, 1036665295,1036714001,94535754, 1036732036,1036796150,921829435, 1037030727,1037276488,1037353153,1037386188,1037418672,52543981, 1037456713,1037501307,1037535308, 1037587786,1037632308,172820193, 1037669707,1037762746,1037800780,64536752, 1037894523,1037931853,1038041146,1038057556,1038073911,266223674, 1038091355,1038128462, 1038387958,169345057, 1038434890,160219220, 1038467969,1038516346,115212781, 1038538063,1038600128,161185957, 1038647863,1038680551,310133006, 1038733220,1038844098, 1038882128,1038942264,454279201, 1001964147,1038958778,1038996817,39813121, 1039139009,1039171643,249004114, 1039190590,675528763, 1039237137,1039253505,87130141, 1039275346,1039483104,1039515879,173752337, 1039548602,1039581217,36290593, 119210046, 1039603027,1039680379,1039712470,7323703, 1039745146,1001310022,1039766868, 1039829723,1039876340,1039925265,1039941706,43384890, 1039961769,465322170, 1040026911,1040056813,1040094497,488046808, 1040138256,1040156844,1040187474,1040203835, 1040220218,59179025, 1040236728,1040323925,1040400442,105530241, 291079306, 1040422230,1040602455,20595579, 1040679907,473732440, 1040711683,429654074, 1040728370, 1040763077,1040810444,1040842928,1040880985,663420944, 1040974090,1041006625,1041023603,1041055803,176767031, 1041077594,1041154232,1041241435,1041301704,1041339740, 126664941, 1041387282,1041465731,110417245, 1041503582,1041569119,1041645930,1041681050,144572478, 330563618, 1041727672,1041814880,463618110, 1039106319,1041945953, 295845906, 148455508, 1042022671,1042060642,1042121850,95524195, 1042153685,1042191716,1042404709,51347578, 1042535782,1042645077,126026466, 1042677818,1042694228, 170311698, 100925901, 1042716007,1042792646,1042825432,187826803, 1042858221,1042896232,1042961769,1043027306,1043092843,850280506, 1043268717,19267587, 343474508, 67305743, 1043317222,183484417, 1043350089,1043453292,1043546739,360355181, 1043584366,1043695622,1043742737,73760842, 1043693626,1043764591,1043830128,1043928433, 1043989418,1044026738,1044173036,1044234920,126665047, 1044288883,165889253, 1044545996,157712385, 1044579035,234329460, 432439378, 1044644111,1044682101,1044791566, 490881079, 1044824987,1044873274,42582052, 1044895094,1044958371,173769096, 1045004505,508712311, 1042022667,1045037327,1045069882,646791185, 1045087190,1045157240, 463487034, 108774371, 1045255545,1045337453,1045383907,1045419386,1045479441,54558775, 1045495866,1045512462,509280314, 1045550459,1045665148,468598842, 310132799, 1045747069,1045792299,1045845374,1045905490,1045921825,1045938234,318570513, 1045957253,152177539, 7929940, 1046009215,1046169711,46448658, 329908443, 1046249528, 52756562, 1046265873,1046282452,1046315044,1046332262,1046364193,1046380775,395165780, 1046413582,348880912, 1046446306,1046478864,1046495291,1046512336,421593089, 1046544577,36323450, 244564027, 1046582656,148079053, 1046774197,1046808591,1046888533,1046921779,1046975873,1047035933,1047054369,26279952, 462864442, 1047085242, 1047123330,141705378, 200048871, 1047238019,1047314468,1047330832,26183092, 1047352708,1047434629,144359908, 1047528395,1047576594,214679611, 1047593941,252040582, 1047642298,1047675465,1047778695,1047872741,1047904311,319160404, 1047926152,295485496, 1048008073,445530114, 1048052187,1048086043,69877819, 1048171914,5603344, 1048286603,1048365739,1048412400,1048444961,1048466361,1048510520,116195410, 1048527157,1048565132,1048642757,1048674610,26181941, 55132490, 353222673, 1048712589, 263831818, 875888643, 1048821816,1048838226,4702394, 1048854759,1048887313,265158853, 1048909198,62439780, 1049069732,1049100858,1049149473,1049166591,1049215971, 40075265, 1049248258,25346077, 1049296929,1049313364,1049329850,1049363590,1049395234,1049411932,1049444410,41353271, 1049460737,15974462, 1049875856,1050152331, 1050231166,1050281806,1050329173,21282874, 205979837, 1050361915,1116312, 58425360, 1050383761,1050460574,1050509560,1050543059,1050576473,1050695058,543784993, 1050793363,1050842516,1050985501,1051017685,1051055509,1051131989,317964683, 1051164689,1051182280,1051252118,1051361338,25493588, 1051377722,26198313, 1051396981, 1051459946,1051497879,1051574514,1051607073,1051623460,1051640001,74874897, 1051674952,26951711, 1051721916,143917073, 1051776408,276447263, 1051902166,1051934751, 600260833, 1051951374,1051983890,1052005394,1052098746,1052133337,1052186009,225034299, 1052279015,1052311766,1052344506,1052377282,1052415386,1052574537,1052623034, 341835983, 100073529, 1052661147,1052721566,1052775836,1052819726,1052852280,1052869105,1052917918,1052956061,1053229249,1053263275,1053311189,1053345069,1053392985, 1053425882,1053477605,1053540574,1053573354,281706689, 1053638832,1053676958,1053788639,20627540, 19398854, 37224534, 1053835457,1053868299,1053906335,1053968186, 1054021024,1054212181,1054250401,1054327186,1054375999,1054392353,443940900, 1054408890,114835515, 1054441685,1054476091,1054523973,1054557051,441172026, 163845538, 1054589851,1054638692,223182879, 1054692771,47202501, 1054949564,1054998877,78168123, 1055036836,1055080481,1055097003,1055164151,449560639, 1055211989,43942114, 52544198, 99287226, 1055247454,1055364517,1055424987,880804033, 1055457528,1115183, 1055490595,405504794, 141607528, 113672470, 1055555893,1055593894,1055737208, 1055785227,1055817942,1055850792,1055888807,37322788, 1055934741,1056014367,1056030753,935313424, 1056047352,33734870, 1056079930,41895631, 67043345, 1056098878, 1056145467,1785978, 1056167336,1056343595,1056391253,124092475, 1056425337,22398357, 1056495017,1056555170,1056592825,1056637290,571850811, 1056670110,1056718907, 1056735476,1056784568,1056866507,1056904618,1056981564,1057030392,1057068459,1057161618,291521533, 1057211646,1057259578,1057275921,1057292321,1057308901,203621051, 1057346988,1057456187,1057472513,264306721, 1057488985,1057523286,1057587938,132644919, 1057620052,1057636567,1057669313,1057701919,1057719707,1057767969,1057833172, 1057871277,1058111849,1058144292,1010941969,23265369, 1058160657,1058180300,110414809, 1058258980,25788419, 1058275514,1058309029,1058362798,210272287, 1058472314, 64176144, 1058504948,1058554100,1058606956,1058652574,29098182, 1058701556,1058750741,1058783265,123797988, 1058800698,1058870703,1058947258,54758167, 1058985392, 1059081006,1059160122,1059176507,13303864, 1059198385,1059373143,1059422538,1059459577,1059537082,1059569976,1059640561,1059684915,1059734507,1059788210,1059834460, 1059902899,1060044994,1060083124,1060159793,1060226219,1060279733,1060536804,1060569173,1060601920,44974143, 1060618488,1060654393,1117497, 1060689334,1060815096, 134692922, 1060853175,985137237, 1060962387,1060995157,133022333, 1061033400,45547534, 1061077051,38797328, 1061683470,1061721531,127191247, 1061765819,1061814334, 1061836220,1061896885,1061946153,1061978170,99964349, 1062000062,1062127190,29688144, 20463835, 1062196617,1062245823,625442938, 1062311360,369262666, 1062371572, 27508794, 1062421551,1062454925,1062521123,1062600707,1062617146,1062633473,1062649915,392560698, 1062667821,1062737345,1062813992,1062846549,57426697, 1062879581, 1062917570,1063065027,27230394, 1063130564,123028527, 192004113, 1063190759,1063223332,1063239698,20725778, 1063261637,1063474630,1063519591,1063605703,143671418, 1063698883,1063731201,24985636, 1063747600,1063764052,1063780385,38371362, 1063802312,1063884233,1063944606,1063994926,130302036, 1064064458,1064144002,1064222802, 553779236, 14499869, 677593358, 1064239135,1064255710,1064288742,1064326603,68878421, 1064555980,1064665104,195051604, 1064687053,1064801742,1064894878,1064943617, 98926869, 1064960401,44354521, 1064994281,1065075020,1065124125,1065156690,1065173008,473448464, 1065189687,12075066, 285902503, 362692641, 1065260495,1065326032, 1065391569,1065473490,1065533751,41648146, 1065598993,28623067, 1065620947,694403089, 1065768404,1065861521,1065899477,1066210774,1066341847,1066434561,20465076, 1066451006,1066467676,1066501178,38944954, 185925648, 1066571224,1066747064,22216926, 716604571, 1066795130,1066812140,1066860792,1066894026,1066942900,18716121, 1066980826,1067242971,925636060, 1067287146,1067341277,1067467607,537346103, 1067533418,1067581626,1067619806,687014289, 1067685343,1067794449,1067810852,75432010, 43794810, 164053619, 1067832800,1067909179,123076626, 1067925697,1067958355,1067991836,534331451, 1068045793,35144103, 1068253647,1068302606,1068335290,1068373474, 38535516, 1068499325,85263263, 1068564930,1068646960,2381283, 770442547, 1068695554,1068711937,132644926, 18022482, 1068733924,1069043786,1042022893,1069094373, 926288663, 1069143526,1069220333,293011514, 1069255775,1069356481,1069433018,1069465617,1069482058,659079434, 1069503975,1069615067,1069662239,40550462, 1069679155, 110969046, 659292709, 1069733352,1069908554,1069941298,1069989972,1070006330,269664519, 1070028265,1070170305,97304770, 1070203841,2381290, 85688402, 1070268475, 1070284858,250904632, 1070306795,1070419547,1070468193,1070547229,546883598, 1070581043,26411044, 1070618092,1070743637,328254026, 1070776405,1070809697,144392755, 1070842197,1070891476,1070943253,1070993898,136544315, 1071022440,507035665, 1071076845,662783680, 1071382590,1071399267,175227316, 1071470062,1071579497,1071612200, 1071650287,928202810, 700645624, 1071732208,338236913, 1071846898,1071891833,1071961587,1072070714,508788794, 1072087076,1072103546,255377441, 1072125428,1072187331, 1072240117,1072447848,544620546, 539591269, 1072496842,1072530017,1072562177,853786686, 1072584182,1072666103,1072758988,1072797176,1072857268,1072873505,497599382, 2378688, 1072890604,1072940090,1073010169,1073250599,1073283265,1073315873,298319888, 1073332809,1073433616,7471121, 1073468922,273875169, 1073578209,1073616379, 1073709448,1073747452,1073807418,311656662, 1073829373,311099455, 749142220, 1073944062,1074004234,33390650, 118571070, 877936933, 1074042367,1074335050,1074382235, 1074435584,1074484737,1074528779,1074577594,1074613225,1074664962,1074714115,41915908, 1074812421,1074908080,174637140, 1074974464,1075020020,1115211, 1075074566, 1075136998,1075167418,427639393, 1075205639,4718621, 1075282275,1075347514,1075364640,134594561, 1075397153,187416824, 140018273, 1075824283,19808314, 1075877385, 1075970107,42467530, 1075986618,1076024842,1076117588,1076134085,1076166657,498761745, 1076188683,1076281882,1076352524,1076559931,1076576289,54558802, 1076593315, 1076674595,69681445, 122078738, 1076696589,1076887912,1076937298,22347835, 1076969508,1008812049,18710834, 1076985915,4767745, 1077002462,1077035356,1077073422, 1077248085,1077281038,488063205, 1114499, 1077314682,303480894, 1077346696,1077384719,1077499182,57426190, 1077581328,377454823, 1077629880,1077690426,49528891, 1077712401,1077761554,1077919930,1077954077,1078002019,1078067258,24805434, 1078083944,753631893, 1078133616,1078215142,1078251021,289308703, 1078329705,1078362309, 1078395052,1078427707,1078444276,116359569, 310509604, 1078493360,1078528006,1078575290,172261435, 1078613523,1078854475,1078902842,1078919227,1078941204,1079017488, 39651038, 1079034126,1079066836,1079099634,1079136419,1079203349,1079297278,1079345396,44351912, 1079399533,1079465494,1079640385,1079673326,1079705633,1079722197, 215992170, 1079760407,22959640, 1079967937,1080006169,1080082699,1080116465,1080183644,21397563, 1080231289,1080295483,57999870, 1080312157,1080344885,1080383002, 1080546843,27181345, 1080612380,343130145, 115621947, 1080689203,1080743453,1080806683,1080852564,1080869458,1080901665,1080918204,607961089, 1080972830,1081264172, 1081312539,1081364505,1081409722,1081442589,1081475156,20594929, 1081497119,1081557477,99827715, 1081672632,1081721018,1081756786,1081835934,1081884756,596836353, 1081906032,1081954609,1082065084,1082118988,1082179615,116195386, 1082201632,1082250785,635585312, 1082344172,1082392607,1082409018,74268708, 1082431010,1082590327, 1082622202,1082687577,64536834, 203587832, 1082725923,1082851709,1082917257,1082998843,178460196, 1083020837,122552379, 22466833, 78889044, 1083211964,374964283, 162660821, 1083260929,1083277345,1083293882,1083327152,1083393695,1083424769,1083441185,1083457618,1083473921,1083490367,1083506719,1083523089,1083539474,60342273, 1083556054,1083589210,19234904, 1083654330,1083692582,41894111, 1083769025,1083802959,23074343, 1083885102,1083954728,1084047376,1084069417,1084167722,1084315179, 1084457421,7159826, 1084490197,1084528172,1084588424,204690989, 1084620986,1084659246,1084719120,1084735489,1084751931,94388242, 1084773935,1084915713,888520722, 1084937776,1085014074,100630587, 1085032384,1085079585,1085096184,1085128705,926629905, 1085145121,1085161508,828817464, 142737467, 1085177912,1085194322,1085213413, 809205818, 1085281841,1085494834,1085560371,1085621628,1085690994,54919920, 1085767920,1085806132,1085882653,1085920821,1085964851,1086019126,418431012, 1086062625, 627474632, 1086079319,1086117431,1086243309,1086276655,52543875, 845545561, 22134820, 1086308411,1086324752,1086341137,637354468, 1086359899,1086390301,100155395, 1086412344,1086489396,1086527033,1086668836,156172374, 1086685428,133529659, 794755541, 831914000, 1086734392,1086750779,508788737, 1086767326,1068712020,1086903866, 1087127830,1087161887,1087196767,1087263680,445546682, 1087329851,1087406138,284115003, 1087426083,25870367, 1087493692,1087553820,1087619157,1087652583,1087684690, 1087701025,1087717434,1087733819,1087750631,1087799482,1087837757,1087917219,1087968830,1088099903,1088307234,1088323869,1088356407,349224961, 1088374596,1088421946, 333676626, 1088443968,1088525889,1088634881,544538708, 20595269, 386727994, 1088651504,1088685151,1088738882,1088914332,381173983, 29114550, 543670458, 1088962888, 1089011745,1089028589,1089066563,1089143942,13156434, 1089176705,1089208538,1089258019,1089328708,88457233, 1089372346,1089405509,1089443397,1089652075,446923018, 1089700287,97910786, 1089732830,1089766172,157172811, 1089815248,300695882, 2380045, 387630199, 1089847540,1089896923,1089934918,331334389, 473842735, 1090033223, 22791365, 1090158999,1090191548,1090242206,700646086, 1090340962,190578874, 1090375949,142360843, 1090426440,1090475593,1090639434,1090764862,1090783760,878346256, 1090847213,1090885195,331335039, 1090978370,1091032652,1091130957,1091208120,1091256520,1091289646,1091326818,1091388353,4636706, 1091453090,1091488812,1091553632, 1091601733,2381390, 1091666991,1091704399,1091819088,1091962317,1092011608,1092059152,79921169, 59064378, 39502730, 1092075533,10338326, 1092097617,1092239923, 1092294226,1092370488,20725819, 1092392531,931135664, 1092469088,1092501567,546488382, 1092518100,1092550738,80920773, 1092567350,1092616940,1092665418,1092681729, 1092698175,78889015, 1092714578,1092730938,987643963, 1092747322,37847102, 1092769364,1092894885,1092927957,89096276, 1092965973,1093015126,61456466, 1093091913, 1093195351,1093386426,1093423434,7340094, 1093488056,1093551105,68567122, 1093621336,1093681208,692715602, 1093697599,727482369, 1654818, 1093717888,1093785177, 1093829569,1093894500,1093928065,1093965402,1094011995,1094058965,1094112859,960430142, 100155426, 1094222633,1094254947,1094320796,58769606, 68239416, 1094369338, 1094385748,494224248, 1094407772,1094550120,1094582557,1094620765,1094685625,1094731198,1094784606,953647162, 1094850143,1095008567,1095074193,62685268, 1095106561, 1095123003,25346066, 1095139576,1095172337,1095204865,67043390, 1095221662,88309815, 156516385, 1095276128,4800567, 74465402, 1095336010,291520728, 228458514, 1095784033,1095833186,295534810, 1095898723,1096041719,104661301, 1096091082,1096138938,1096171556,1096188296,1096226404,1096319034,1096335419,1096351765,2375779, 1096368542,1096417847,1096450248,1096483312,36503585, 1096534180,1096564737,1096581153,73089106, 1096600759,26181836, 1096652389,1096761614,1096799846,1096909293, 1096941861,1048887298,1096980071,12271700, 1097045608,1097121793,409944081, 1097143913,1097285964,31916106, 2381418, 1097334998,1097367745,114901428, 1097400801, 355237919, 1097449488,1097465940,1097482498,1097520747,1097569900,1097763926,33560173, 1097826490,55132393, 1097861259,1097930350,22560785, 53903415, 1098045039, 1098186788,1098203169,1098219831,1098285278,85557545, 1098317840,1638435, 1098334297,120717470, 1098367426,1098449899,1098503792,1098566371,1098596899,1098662466, 1098711226,95338925, 1098749553,1098809557,1098847858,1099005968,1099022354,848789533, 1099038737,412336186, 1099055304,1099093619,1099137577,1099170069,1099202577, 1099220081,1099251874,1099284673,1099317441,1099350214,1099385823,1099432122,1099464778,1785885, 1099486836,19382695, 1099732597,1099798134,1099857979,145784868, 1099875902,1099926171,1099991984,1100060279,1100136449,2097207, 1100054614,1100158584,1100207737,1100284062,1100317632,1100349638,1100382789,1100415194,158384129, 1100469882,1100546359,1100612380,1100660769,1100677156,271614008, 1100699259,19382624, 1100906581,37028737, 21184529, 1100939322,1100955707,1100972317,20431046, 1101008074,1101070437,1101103572,1101152637,1101218193,94618461, 607289401, 1101251440,1101338236,1101449234,213499988, 1101512762,1101529143,52232221, 1101546005, 356040720, 1101578241,7618589, 1101600381,1101826590,1101873274,1101889750,1101928062,392282305, 1102053394,1102075519,74399777, 1102190208,22954177, 1102250565, 1102288513,1102464371,1102514796,170493647, 1102561528,1102595046,1102627127,89293016, 1102692845,34521105, 1102725431,1102790715,1102807098,1102823460,1102839882, 240238609, 446185530, 225902648, 1102861954,163021014, 1102970935,69255226, 1103156868,1103446940,1103500933,1103610045,115556885, 1103643404,1103711232,1103741159, 1103774105,1103806911,1103839451,1103872082,614957072, 1103888959,1103921239,1103976070,1104101869,1104134940,1104184938,1104254599,1104413147,224657444, 1104445663, 218644516, 785023439, 1104497507,239026739, 1104593127,1104631432,1104675291,1104707905,1104746121,1104855254,223199250, 36323364, 1104893578,1105122955,1105182891, 1105248339,223855530, 1105286796,827441234, 1105352333,1105483406,1105543252,1105565327,1105674736,26181808, 1105723589,1105756332,1105789157,1105822182,1105857521, 1105906549,1105969364,1106002506,1106035776,1106083924,1106100225,1106116641,1106133178,1106171536,1106264645,321274349, 1106302609,56344977, 1106411746,1106450066, 1106543490,1106596701,1106695827,1106788410,58769467, 1106805091,1106876052,1106968593,29868050, 1106985018,29688474, 60101031, 126664917, 1107001380,108724472, 1107017914,1107050513,162971929, 1107072661,1107269270,2380655, 1107345901,1107379447,1107433111,1107498648,1107623994,86556756, 1107646105,1107691596,1107738708, 158384130, 1107755571,1107809946,1107908251,1107984884,1108072092,98795608, 1108214253,1108246612,1108263405,1108295866,1108332208,94388308, 1108426785,1108448925, 181190885, 1108514462,1108607724,1108656321,94584954, 1108689878,1108755700,1108803666,1108821610,251199521, 1108885813,1108918290,1108934887,1108967442,22937962, 1108989599,1109049377,398590010, 1109065995,1109104288,558399572, 1109327956,1109344474,173228035, 1109393601,1109426472,1109464737,172212255, 1109524765,1109557514, 97910800, 1109591323,1109639227,1109655770,1109704705,1109721105,62373946, 1109743266,1109824629,1109902442,1109950959,1110016837,1110048990,1110087331,1110147258, 1110180801,1110245643,1110283940,1110398629,1110605886,40599636, 1110622292,1110638778,1110677158,131842354, 1110741881,1110786534,1110819697,1110890151,1110982674, 1110999258,1111048274,56279098, 1111065729,1111103144,1111164629,1111198427,1111250601,1111343137,1111359573,101580993, 750256212, 76792045, 1111398058,1111475011, 1111529131,121209985, 1111590391,1111655553,1111692972,7209146, 1111785703,230391994, 155992436, 1111818271,1111835171,1111905965,120307767, 1112032267,1112080615, 1112113714,1112162490,1112195339,1112228222,1112278220,1112327105,1112392310,1112430254,31866954, 1112555604,1112571905,121471451, 1112589858,96567378, 1112654623, 1112703200,1112735780,1112752214,1112768513,46743615, 1112784897,1112801343,364527649, 1112823471,1112869525,1112916001,1112932416,14974999, 1112949456,7094354, 1112981567,1112997951,20430911, 47497516, 1113659058,1113718868,1113735354,1113768207,1113800762,85266705, 1113822899,59818269, 1113899455,1113931833,1113948466, 1113981029,1114019508,1114101429,1114178069,1114216118,1114275962,1114294155,1114345768,1114423780,351666235, 1114456308,1114505486,1114538198,1114576567,1114658488, 1114705126,1114773177,1114849363,1114887866,1114947777,1114980538,1115015045,1115100859,1115276525,239157466, 1115313852,1115439293,1115472709,1115510461,21512209, 143638586, 29114601, 1115639309,108773671, 1115723454,1116031455,1116078273,1116113457,1116165823,1116343296,453394450, 1116375576,4636794, 1116427505,1116471418, 1116487802,21577764, 41074874, 91111457, 1116504295,1116542656,1116603238,1116640961,59047937, 1116686286,1116766292,1116782650,1116799032,1116815361,1116831803, 1116848161,1116864514,1116880897,1116897297,1116913726,1116930106,1116946449,1116962833,1116979229,4653058, 1116995869,19628061, 114491393, 1117034178,538460391, 1117077726,1117115810,1117159482,1117178994,1117263555,1117586175,1117640388,794247169, 712688063, 1117683748,44024006, 1117700424,1117754998,1117815028,1117869765, 632832484, 1117932576,1117997814,1118044196,29098560, 1118066374,1118175476,1118230215,1118328520,1118394057,1118541514,1118685529,61178078, 1118738123,1118797841, 1118815432,1118879777,1118896164,383729720, 1118918348,1119010901,1119049421,1119109868,300908746, 1119159163,1119192475,1119240209,44433424, 1119256592,1114345, 1119278798,1119377103,1119436801,103579707, 324829200, 659800548, 1119803089,1119917778,1120011151,1120060678,1120126364,1120190480,294682708, 526239387, 581189867, 1120207308,1120241478,1120294611,1120409300,294027322, 1120474837,1120518474,1120550971,118571082, 1120567789,1120600298,1120667388,1120698385,1120716710,1118324006, 1120814778,1120847411,448970769, 1120929397,1120994000,1121027181,1121076062,347652834, 1121140950,58835130, 1121179350,119177709, 1121321018,1121337403,1121353785, 30752769, 1121375959,1121520501,1121583162,1121599963,74842310, 540426298, 1121632504,1121665661,1121698174,1121747137,1039106722,1121779775,20267070, 1121801944, 1121867481,37912999, 1121976321,376799707, 167657473, 1121992762,1122009358,1122041859,389775376, 1122064090,480412141, 1122162395,1122320442,1122337434,1122369722, 1122402378,1122418747,1122435073,51183619, 1122457308,1122522845,1122697410,1122730170,421380155, 1122762753,1122780807,1122812152,1122844977,1122910485,1122944105, 1123024913,1123044890,1123107009,1123139600,1123156289,1654786, 1123188821,1123221722,22790229, 1123276510,1123418305,1123450896,1123467297,1123483677,1123500035, 1123516602,1123549659,1123582036,902627514, 1123598558,1123636959,621297960, 49053981, 1123718880,1123800801,1123860666,1123899106,1123975412,1124024379,1124040908, 1124073956,738656565, 1124106602,1124139066,1124158066,775340034, 1124204580,1124221012,120340721, 1124243171,1124453438,1124505316,26181846, 1124630721,1124669157, 22298708, 1124746524,1124783846,1124828014,1124892858,1124931303,49184769, 1125013224,1125089547,1125127913,119881783, 1125236925,1125269697,231227779, 47857960, 1125308138,33555639, 1125384276,1125401556,1125455595,1125564498,1125582500,1005585620,1125619436,1125732316,1125810506,1125843028,593330177, 1125865197,314654777, 1125941860,1125992378,1126072536,1126105281,1126138148,1126205883,1126252760,39976996, 1126285560,1126319048,1126351070,1126383675,1126401579,1126455022,1126794973, 1126842430,1126864623,656916775, 1127120955,1127137511,135545784, 1127170296,245859198, 1127202900,826687740, 120307748, 1127224941,194151453, 1127274224,136151102, 1127351163,1127383955,91111442, 121209046, 1127438065,1127487218,862094792, 1127552755,1127661812,1127710957,1044791313,16515194, 1127749364,1127792696,1127809106, 1127826012,1127880437,1127962358,1128173752,1128235201,1128268477,1128317342,7241744, 1128366282,1128398849,1128415316,31703124, 1128437495,1128530404,1128568568, 1128677562,1128710439,1128748793,1128978170,38748176, 1129070901,1129103376,126025810, 1129125627,95339660, 429162530, 1129240316,1129316410,1129332820,94535764, 1129351360,1129414872,1107738682,1129453309,1129530643,30146642, 1129578804,1129627841,1129660609,79593529, 1129694136,1129742580,1129912062,1130171454,30639333, 1130223359,168902842, 108758232, 1130283966,1130332242,1130348577,1130370816,1130430796,1130479632,556171323, 1130497938,831913985, 1130534657,1130630116,1130679913, 1130725914,1130791098,1130829570,1130874741,1130922250,1130960537,1131009795,1131118666,1131135173,1131173636,173883577, 1131239173,1131315282,786105531, 1131334628, 1131380737,1131397137,1131413505,2097238, 1131435782,1131512021,1131544635,21217339, 1131562977,1131632391,1131910920,1131954531,1132019770,29687896, 1132036324, 1132091145,1132216321,56131668, 556974164, 1132238602,1132331873,1132369675,1132412961,1132429394,1132446180,94388241, 1132478527,1132496454,1132544056,295616594, 1132566284,1132724791,1132757370,1132790253,1132822772,1132872173,91309642, 1132904634,1132938437,1132975885,1133068474,1133106958,1133199536,1133232130,127500289, 1133254415,1133560301,1133592698,286294032, 1133609217,880804033, 1133658168,7323730, 144425453, 1133680400,1133789894,1133827857,1133887797,1133920338,1133937267, 1133975314,1134052323,1134090003,579373534, 1134281082,1134313503,1134329915,68354241, 397918282, 1134346302,41058367, 1134362864,39650171, 1134398381,112935143, 1134461993,1134494156,1134526522,303628372, 1134542930,337936400, 1134565140,1134788667,19529932, 1134808036,1134854900,97337402, 1134952507,81068049, 1134969292, 106315860, 1135003242,848697109, 1135073046,116015297, 1135204119,214909602, 1135253272,133814041, 1135302426,1135362106,1135378491,1135394846,1038073887,1135411322, 1135430751,721862714, 1135526374,1135564571,1135608511,1135662876,1135777565,230260922, 60180056, 1135843102,574996497, 1135935506,1135957791,1136023328,1136132659, 1136181434,1136216270,1136148717,1136269089,1136362563,931135972, 1136400162,1136525924,1136574760,1136607248,1136623977,32899088, 1137006372,1137071909,1137394087, 218628112, 1137426654,1137459263,395362362, 1137475600,1137492317,1137524738,1137541156,1137557537,53903393, 1137573951,858914872, 1137590303,1137606869,38223903, 1137639754,1137672248,1137688609,1137705337,28524618, 1137770682,1137803841,1137836065,21086394, 1137852418,1137869044,1137918196,63979579, 1137967163,1137983895, 1138016523,20971553, 1138049083,386465810, 1138065424,1138081850,1138098193,55640065, 1138114561,1138130946,886505473, 1138149092,1138212923,1138229284,1138245849, 1138284326,1138311199,1138327610,1138343953,1138360353,1138376788,64618525, 602603606, 1138393106,1138410035,1138460835,1138507998,1138540738,1138573524,1138606116, 1138624381,1138655718,1138688033,268140575, 1138705132,1138753903,1138789376,41123871, 346570785, 1138819357,1138851899,1138868279,1138886205,1138933818,1138950203, 394445067, 1138970966,1139033686,1139098084,1139130431,1139152679,21266449, 394805281, 1139231595,31703095, 1139278102,1139316520,630784295, 1139480361,1139622218, 1139655638,1139721163,1139769402,8601658, 1139791658,693273069, 1139922209,1139966074,1139982876,1140047928,1140064442,1140097112,1140135723,1140246263,1140299564, 1140441306,84639807, 43155515, 1140496173,1140578094,1140687798,1140752470,7880705, 1140774703,1140953226,29114457, 167548720, 1140998707,1141049785,1141096449, 319029265, 1141115765,180568066, 1141184305,1141380914,1141441296,1141473342,118980639, 1141489822,1141522491,1141539522,1141588418,1141670311,1141704048,960924120, 1141753758,1141806899,544899156, 1141997790,220069973, 1142030567,1142063943,1142096202,1142134580,1142343346,121389089, 1142396725,1142540302,1142588298,1142642486, 1142718997,1142751432,1142784016,186433593, 1142800625,1142833338,1142866117,1142900727,1142947841,361972060, 1142964455,1143002935,210616743, 343949371, 124649473, 1143078967,39010320, 1143101240,1143177275,1143199545,845922320, 1143439591,87162964, 1143474340,464765012, 1143505096,419971603, 1143538276,606224443, 1143586890, 148963410, 1143603389,255639585, 1143636382,1143685390,214679570, 1143718136,1143751433,99418143, 20627542, 1143783457,1143799895,53510177, 1143848996,1143865560, 1143898196,187154433, 1143920442,810663995, 1143996979,25460738, 1144045740,1144078534,1144111137,1144127706,1144176826,22560786, 1144209602,1144242407,1144275002, 1144291391,266387519, 1144308259,1144373597,1144406209,1144438840,596689093, 1144461115,1144684737,1144723174,1144772412,84754488, 1144848570,1144886808,1144930779, 1144964371,41910537, 1145012446,1145046862,1145094330,1145127326,1145182013,1145258379,1145292328,945651729, 1145323706,1145356485,1145389140,120799316, 1145405526, 1145422135,1145487444,142278692, 1145509694,1145667601,1145689919,183812289, 1145749562,599900163, 1145765946,18716480, 1145782458,1145815515,1145848033,984334392, 1145881203,1145913537,1145946148,1145962690,1146001217,1146144051,1146175546,1146191931,276234324, 1146208442,40599634, 1146241837,97337403, 1146306818,12386388, 1146339764,1146372642,1146454033,219906065, 1146470485,1146509122,1146607427,1146667044,729464849, 1146684212,1146820420,1147060286,333676626, 1147076964,1147110597, 240238651, 37371940, 1147143284,42942527, 1147191789,1147224080,44662841, 1147246405,22462729, 1147295558,26296394, 385728802, 391839829, 285082538, 1147459399, 256638993, 1147557704,146965421, 1147633680,62620095, 1147652038,1147699601,1147732001,1147748411,192184351, 1147764912,64864292, 1147799365,1147852617,1147912385, 1147950922,1148142539,1148192475,1148256725,1148289041,129040458, 1148311371,1148420178,1148436711,1148475212,1148557133,741835037, 1148649690,239910945, 1148704590, 1120419899,1148895290,382238721, 1148914312,582795457, 1148961783,1149010138,1149059278,1149109154,1149146959,921059361, 1149403528,32964682, 260456641, 642154554, 1149436570,1094139960,1149470197,168869972, 1149518050,1149550778,34865183, 1149589328,1149715108,1149780182,1149818705,1149862243,177750100, 1149928527,7258113, 1149981651,1150025810,1150046357,1150108010,1150140639,1150189626,423739451, 1150211922,1150356482,1150402814,1150452582,450363428, 1150485701,1150517330,14139427, 1150539603,1150763756,1150812408,329269251, 1150845117,1150877781,19529987, 215564373, 1150911143,1150959617,1150976055,302711370, 1150992628,1151041734,1151074337, 113180885, 2376476, 1151095056,1151172839,1151205392,83492897, 1151221844,1151238450,1151271799,1151320122,1151336481,70991931, 1151354386,18710753, 1151390218, 1151451169,1151467602,1151484467,110412013, 128270399, 1151533072,1151549442,65601765, 1151571796,1151686485,1151713547,1151746130,1151762491,1151778832,1151795217, 1151811620,1151828403,1151862171,25952312, 57311291, 1151910464,1151942685,1151959042,33194242, 1151975506,1151991994,1152027014,1152073764,19579255, 1152090194, 1152106684,1152156630,1152222140,1152254128,1152287009,67043402, 1152320082,1152352341,1152385026,1152406001,1152483386,1152499771,1152516363,57311261, 1152554838, 1152685911,303661115, 1152761890,1152778298,1152794627,1152811009,106741777, 1152833368,736362578, 1152893012,1098792961,1152915289,150110244, 1152991484,1153029183, 1153417217,676069438, 1153433625,1153450058,3457041, 1153877696,1153914715,1154062172,1154138397,1154171777,1154220049,209141761, 1154240364,1154285650,60260574, 1154302037,1154340701,1154433108,1154449424,158662689, 967688195, 1154466050,1154503940,1114401, 1154570078,1154695731,91848759, 324960312, 1154745407,1154832223, 1154875853,1154908248,1154946912,24182842, 1155059496,1155106739,108019997, 1155159905,676069409, 1155252468,252772647, 55050426, 753631404, 1155301687,736837665, 1155372898,1155536739,7340035, 1155635044,91848735, 1155678452,1155733349,1155825746,631259172, 1155848038,1155989752,1156026198,1156071720,1156110183,1156323176, 572407837, 1156382968,463519761, 1156415752,1156465087,1156497740,1156546593,1114784986,1156568937,1156628602,43188282, 161858094, 1156647008,1156727093,1156762274, 1156861569,1156907009,914030655, 1156924799,22970852, 409124865, 1156989017,1157024643,213631246, 1157076842,1157257067,805453906, 1157338316,403669023, 1157398587, 1157415530,1157470060,1157584749,1157661219,1157726225,21069842, 1157743268,9945143, 1157814126,116363010, 1158053947,45629498, 1158071757,1158125423,1158190960, 1158285718,205455901, 1158354801,1158447481,1158512935,144179263, 62488938, 1158545652,91111506, 1158600562,186761247, 1158697312,1158758620,1158823938,320503824, 1158841300,1158889531,37650434, 1158908176,1159010163,1159092084,1159152163,1159218316,90931256, 1159272309,1159632758,1159727752,1159774975,1159823823,1159876130, 1159927671,272236602, 184860708, 298877104, 1159987431,1160025976,1160085535,257540132, 1160102668,1160167893,285606107, 41975890, 1160206201,1160288122,1160347732, 1160364068,1160381566,1160435579,1160566652,1160610010,1160659132,108527633, 1160708659,1160763261,30687263, 1160845182,1114850, 2376873, 1161020068,1161085002, 398721108, 1161101371,1161118180,55623869, 1161156479,1161331315,1161364130,786072953, 1161396466,1161429236,1161478874,1161511314,1161560353,48857124, 1161595924, 1161658938,1161713536,1161795457,1161871418,43401275, 1161893762,1161969749,19628091, 1162008451,200097793, 977715431, 1162467205,1162642174,1162674364,214909722, 160809796, 1162725042,116457531, 1162775309,19546168, 1162827654,1162925959,1163007880,21757968, 95518805, 18710829, 1163101263,1163149608,1163182311,1163215326, 1163250252,1163313236,1163329552,1163345994,1163362501,1163395244,1163433865,1163526707,1163576662,1163624511,1163640888,60097006, 516276455, 1163663242,1162723329, 1163827083,1163886666,89145402, 703578397, 1073315895,1163906780,1163970240,1164002579,1164050918,1164083283,1164121996,1164197970,1164214899,1164247524,319012948, 1164280001,1164312871,1164345514,7290964, 1164400525,1164498830,123715600, 1164640315,1164656641,98779376, 1164673060,1164689425,1164705850,219758859, 1164722369, 1164755003,1164771390,444710970, 32948257, 1164792484,1164853281,1164869714,1164886213,1164918800,1164935362,28295226, 1164967995,1164984353,23019521, 1165000720, 1165017090,7405571, 1165033573,1165066256,83492867, 1165082660,1083277375,1165099422,1165154191,1165296795,44353109, 1165345122,1165399952,34472284, 1165514641, 1165672916,1165722396,1165771593,1165820148,1165869230,1165918267,1165934593,1165951190,711361200, 1165989778,1166230711,1166278692,787595554, 1166296842,1166345593, 1166410892,1166458897,1166475570,1166514067,1166606883,1166672666,1166721282,1166755034,1166803003,239665662, 1166820076,1166872845,1166917844,1166950653,1166984162, 209076240, 1167021972,360120351, 1167120277,19398875, 556466260, 1167349654,1167442154,390316121, 1167513495,221282340, 1167573298,1167605954,1167638888,1167688166, 24363196, 759267658, 1167720932,1167753666,1167841176,544653328, 1167934461,1167969881,1168032338,1168070553,1168179284,31457565, 1168195781,1168228524,1168261136, 1168277563,76972229, 1168294158,1168326962,1168359799,1168392250,1168408593,175587329, 1168424996,1168441428,830128212, 1168463770,1168591760,1168638944,1168693147, 78692378, 1168870331,1168916775,1168955292,1169048138,1032765637,1169080336,1168277521,1169097000,1169129505,579027002, 1169151901,1169293370,1169309755,502482149, 1169326324,226754618, 132366420, 1169376102,1169408186,1169446814,1036009535,1169506550,1169574578,1169637732,1169676191,1169971105,1170244044,1170276836,200835131, 1170309476,1170342074,1117586118,857751944, 1170374950,1170440819,1170472961,1170489430,1170505744,1170522129,215433290, 1170544546,1170638441,1170735178,1170751524, 223199318, 1170767873,19349562, 1170786402,1170823075,579327908, 188776757, 1170937765,1171265446,1171308757,1171341498,1171374165,1171406931,1171441752,1171494823, 687278271, 22446481, 1171570978,1171603539,1171641464,1062027300,1171707816,1171752136,72286402, 500678714, 1171822505,86835216, 1171898616,1171931210,1171947717, 858701925, 1171980851,1172029761,1172065001,1172176899,1172193843,57212959, 1172242916,1172281258,1172395947,1172472042,1172537758,1172586744,1172620955,1172668447, 1172684856,113016916, 27967521, 1172702657,1172750522,1172537537,1172789164,224804895, 198082739, 1172883099,1172930596,27181285, 65732692, 1172953005,1173012812, 970194980, 635224122, 1172275380,1173067694,1173242270,1173291039,1173307967,1173340178,1173356737,1173389374,1173405754,1173423051,1173477295,1173586547,117309633, 1173622438,21234006, 1173657520,1173750455,1173815673,1173881018,59490502, 1173919665,1173979699,1174034354,140083263, 1038073892,1174099891,1174230964,1174339880, 1174378421,1174487319,1174519824,19530077, 26182853, 1174542262,1174749619,1174782183,1174816108,1174880257,1174897049,1174935479,1175065804,1175126075,183814296, 1175142622,1175175199,1175191626,1175208018,1175224887,1175258080,1175306647,1175339090,1175355425,1175373725,1175420929,1175437329,512884754, 1175453880,1175536161, 1175601236,91111486, 1175623608,1175699660,809028565, 1175732824,1175787449,1175846928,19267586, 40173630, 1175863458,1175896149,1175934906,1176000443,193036321, 24805431, 1176059966,1176190978,226754576, 16515127, 1176210331,1176273162,1176305994,1176338506,1176354898,373244456, 1176377276,1176457056,1176502881,1176536441, 1176606653,1176751195,1176800108,1176852414,1176912058,1176950719,1176993850,1177010235,1177032640,1177092327,924418081, 1177130945,978616568, 1177245634,1177288897, 1177327555,1177370645,4734997, 1177393092,1177786310,19383422, 1177851847,1177928827,448495649, 1178010023,1178042427,430719997, 1178064840,1178140957,1178178291, 1178256112,1178304698,1178343369,1178468574,1178507210,1178632368,1178665018,313360401, 28560671, 1178687435,439746974, 1091573708,1178752973,1178796278,1178861585, 1178878515,1178933198,1179009083,1179031503,1179124196,1179156656,1179195344,95322444, 1179287637,1179320650,37650491, 1179359185,1179522581,1179618431,1179681122, 1179729994,1179746386,1179762936,31342625, 1179801554,1179981779,1180041275,232947928, 379502654, 1180063700,1180189235,88457300, 1180237825,26705975, 1180260309, 87162962, 1180320567,1180440534,1180483778,1180516933,1180551984,1180620759,1180866520,1180893860,2381336, 56672272, 1180958911,1181024819,1181073892,1181107381, 1181155387,1181172771,209092666, 1181220922,310133006, 1181237436,1181292038,101580986, 1181352405,1181384705,1181401105,24051786, 1181420111,1181483969,1181550071, 1181615210,99976207, 1181663248,176210229, 1181685721,1181794556,563052580, 1181833178,1181898715,1182007458,619708475, 126665197, 1182046172,1182220305,1182238293, 1182285882,520096789, 121700441, 1182303295,1182384398,1182417089,1182449748,1182466050,85311546, 1182482462,1182498880,1182515217,5668885, 1182926633,1009401919, 1182973985,1182991297,1183061027,105251482, 1183187252,80035903, 20463886, 1066748889,1183241279,1183285335,1183340510,1183416617,1183455199,1183520736,1183635425, 324081634, 1183668195,7520315, 1183744796,1183793185,1183809618,1183825978,81068091, 1183842997,1183891642,1183930340,1184006203,116277623, 1184028645,22970638, 1184203036,71548961, 1184268289,1184285244,1184339942,20676624, 1184438247,1184520168,424542498, 1184569321,1184798698,1184913387,754271028, 1185006810,1185054756, 123076625, 1185071531,1185109996,1185185808,354533392, 1185208301,1185251559,1185284405,1185317245,1185382459,1185399037,1185432597,1185497818,1185529915,300040208, 1185546519,1185581625,1185634286,1185824851,1185858935,1185961967,1186027504,76578898, 54920765, 141607105, 76120100, 1186158577,273367125, 514769380, 1186322418, 1186387955,1186449398,66912315, 1186519028,1186600949,1186694185,1186725947,418431007, 1186742494,1186781174,1186992299,1187043319,1187102913,247316581, 666714196, 1187135843,1187207160,1187272697,54925270, 1187528789,133890471, 1187561554,1187578042,376669310, 1187616762,1187676373,1187709126,1187741732,31342594, 1187764219, 57229661, 37028056, 1187840058,31342676, 1187856442,1187873120,1187911676,1187971276,72877103, 1188003898,1188020627,1188053008,25428026, 1188069923,1188135508, 1188190205,7110692, 1188282427,1188298768,641286207, 43829209, 184565991, 1188315903,1188364650,1188398839,1188446452,1188495419,512721084, 1188517886,1188593665, 456261649, 1188610270,69780329, 921714874, 1188643230,1188698111,1188823254,1188856163,45351335, 1188921629,1188954129,56213586, 1188976640,1189118034,1189140481, 1189251462,1189298338,1189334435,1189396728,202375386, 1189435394,787300927, 1189512329,33734977, 1189560542,100450339, 1189593322,699023434, 1189658817,1189697539, 1189806350,1189839800,43827723, 1189890603,1189992452,22446481, 113311760, 1190068282,150781968, 1190085101,1190117564,1190167003,1190199527,1190232067,1190248532, 1190264833,1190281279,244563970, 1190298480,1190382129,4800692, 1190854986,1190893574,1191008263,1191100592,730890488, 463634490, 20448758, 36962535, 87376414, 1191133370,1191167877,1191248066,1191286733,1191329876,37847114, 1191346407,1191379000,1191400797,1191428282,1191461087,1191516168,1191575929,1191642254,298092320, 168116482, 866746426, 1191743760,1191827465,1191909386,1192067103,1192083492,1192099924,1192116260,1192132879,1192171531,1192399961,1192443988,1192460289,57212945, 1192476885,1192509457,1192525842,444465636, 1192542244,1192558809,1192591823,1192640571,1192659006,267845691, 270843967, 1192706261,1192738874,265879615, 1192755800, 1192804583,273760257, 1192837156,1192853771,186957857, 1192887200,920748033, 1192952078,1192984634,1193001023,1032667169,346309189, 1193023500,250839226, 1193083340, 1193121805,1193181217,44122128, 1193203726,1193279547,1193295889,42958884, 318570578, 1193312257,1193328907,1193361444,177602995, 1193383951,1193443639,1193509332, 1193558371,211058751, 1193624348,19185748, 1193673452,1193728016,624607314, 1193820654,1193859089,1194218927,1194296349,237764666, 1194334226,1194416147,633995323, 1194493664,1194590770,1194639444,80412728, 47596072, 1194661908,1194786832,306315901, 1194809365,144080929, 1194852605,24805393, 40091649, 1194887397,20840510, 1194918591,1194973206,1195065585,1195098659,1195163665,608010258, 1195183434,1195235351,1195311186,1195327660,1195366424,1195412391,1195464729,252985838, 1195524097, 1195540543,692715583, 991134402, 1195557358,1195589690,277594169, 1195606103,1195655591,1195688389,199035619, 1195743258,1376257, 1196054555,1196149152,1196196426, 1196228692,1196244993,111722919, 1196267548,1196377089,19398898, 1196443082,1196496925,343130171, 7176195, 1196590143,1196672266,2381854, 1196753389,19185720, 1196786168,400081380, 1196818449,1196834832,1196851202,1164738618,1196867946,186695868, 1196905034,127025211, 1196982975,1197037599,1197129969,1197168672,60752169, 1197211839,390234170, 37896276, 1197283361,1197392058,1197430818,108249355, 103187007, 1197491763,1197573623,1197637663,156958778, 1197660195,175374558, 1197725732, 27181141, 1197801474,1197817859,1197834322,1197851420,1197899962,1197932748,1197971493,1198030932,21282852, 590790672, 1198053414,1198358591,556613844, 1198375487, 1198407918,1198456833,1198473691,136675586, 1198506068,1198523769,1198587959,1198605595,1198653612,1198686211,1198703076,1198741543,1198800952,1198817509,19382673, 1198856232,74055764, 1198920836,902037748, 1198970921,1199063281,97484818, 1199095994,1199134762,285982721, 1199227214,937433131, 1199276276,1199326437,1199358293, 439698300, 1199407146,1245306, 1199429676,39653243, 1199669513,1199718482,717307905, 1199736838,1199790125,1199898687,1172783105,1199915571,749584625, 1199970350, 1200095316,1200112063,1200144588,1200177274,1200193777,1200232495,1200330800,1200379331,1200439502,368083148, 1200494641,1200555993,1200603194,129368639, 1200625714, 26181988, 1359888, 1200734290,1200752255,1200805939,437502289, 1200904244,1201002549,1201061970,1201078516,1201127426,650526783, 234668265, 1201150006,1201242194, 232243236, 658751489, 11632641, 666288191, 1201258529,1201274913,1201291297,392069153, 1201624661,1201670991,1201766598,1201803481,1201848351,1201864788,1201881104, 746864866, 1201903672,1201963194,1201996071,171396060, 1202028629,1202063299,38371412, 1033011218,1202112435,1202165817,1202209516,1202258114,269681137, 1202291835, 1202372878,1202405757,1202477114,153321714, 1202542651,1202684145,1202716858,41992390, 86949905, 1202749637,1202782845,1202815060,271220769, 1202837564,144031866, 1202979659,1203028199,1203060794,1203077468,41911133, 300974370, 1203116093,1203159098,1203175483,153255993, 1203198014,1203471250,1203520073,1203618515,1203650577, 438665402, 91111546, 1203673151,520339642, 172853659, 842875461, 1203738688,1203847516,1203880534,1203929276,1203978654,1204033601,1204243645,1204289896,1204339445, 1204404800,1204437055,4800570, 1204453562,470908945, 681836575, 1204486202,1204508738,1204569622,1204617274,1204634679,1204688963,1204814065,1204846862,1204880108, 1204934724,716210577, 1205108795,1205125338,1205174520,1205207853,1205272634,76284129, 1205291996,1205321786,1205338186,940752897, 1205354946,1205442629,1205524550, 1205618713,1205672007,113935224, 1205753928,1205796897,435683525, 227558314, 1205819465,314245138, 70910359, 1205977794,1206032458,1206124744,1206159750,1206208473, 1206255698,1206272563,1206327371,1206420709,1206457554,1206507596,93159480, 1206779993,41992277, 621297862, 1206812865,1206845736,1206878293,1206911246,1206944055, 1207012556,1207091675,1207123969,1207140566,1207174070,1207241250,1207287824,101581031, 1207305035,1207356277,1207418897,221577410, 539594244, 1207441485,1207500831, 15646794, 1207517187,96272470, 1207894260,1207943203,1207959583,376094756, 1207982159,1208026679,1208080464,1208156282,8011779, 1208173380,18710707, 1208208964, 1208271062,1208303646,16482390, 144360190, 1208320197,1208358993,1208451154,183812654, 1208467699,849362980, 1208516667,1208532993,1208549412,1208565777,93225146, 1208588370,1208781029,242171905, 1208817747,22446489, 1114653, 1208893626,50922537, 1208927837,63193147, 1208991746,1209008129,1209024545,1209041226,1209073665, 109953087, 173506623, 1209096276,1209237905,427671979, 1209270960,1209337648,1209385018,1209401428,209616980, 139919444, 56229890, 1209422839,160464955, 388726842, 1209483476,1209931861,1210155370,1210187832,1210204245,325779459, 1210236959,25100305, 1210253527,1210286164,1210302497,1210318906,270401595, 1210335268,1210351679, 312819770, 1210371072,949452858, 1210400826,902152251, 1210418067,608010242, 1210466320,401375263, 119816249, 1210482691,1210499285,219512833, 1210531925,602472504, 1210564641,1210581374,1210630792,283738113, 1210662945,1210679378,220905488, 1210695928,599638052, 137232420, 1210728912,1210843220,401375233, 1210865750,1210925479, 1210957841,1210974224,522453049, 1210990628,1211007034,600948822, 1211023649,1211056186,395624450, 1211072700,1211121665,137248830, 1211138471,948273185, 1211177047, 1211286042,1211351310,1211383864,119832592, 1211400225,1211416577,1102839844,1211433153,1211466130,1211514964,1211531322,128630801, 1211548242,1211580472,1211596833, 1211613242,1211629627,1211645988,366428223, 1211663059,1211695160,1211711519,1211727874,1211744312,67829796, 1211760676,220954708, 1211777438,26230820, 1211832408, 1211897945,1211996250,1212039184,325779515, 1212055568,22560785, 1212072150,1212104720,1212121090,891240484, 1212143707,451330106, 613548068, 1212235812,26230843, 1212252372,1212284964,1212301328,1212318033,1212350500,1212366881,509378591, 1212383445,1212416016,1212432468,1212448852,425738433, 1212465210,509378577, 1212483748, 1212514362,298942523, 50873129, 1212531189,1212645378,128630785, 1212667996,270401594, 600948772, 1212782685,1213060949,1213087747,1213104187,1210531898,23986234, 1213120959,1213154212,1213186471,1213219230,1213268054,1213284410,1213300820,799784976, 38537641, 1213317183,1213333537,1213349924,1213366274,1213382657,66256929, 1213401875,263602235, 150700309, 1213448440,1213480978,24477727, 1213497566,1042448400,1213530309,1213562897,1213580245,1213628452,66256913, 35700794, 1213644883, 1213678483,1213726756,1213743190,1213759524,1213775956,128630842, 1213797543,1213841768,1213895653,1213939730,1213957693,1214005496,1214038078,1214054586,1214090101, 580649213, 261914660, 1214152890,1214185531,24805462, 1214201914,1214218296,1214234657,29622465, 1214252162,1214303645,1214349874,1214398467,226951252, 67829776, 119816278, 1214414879,908705851, 1214436075,1214480439,1210351649,354713658, 1214503006,1214628038,414203935, 1214660611,1214677076,1214693392,1214709789,1214727469, 1214775691,1214808095,1214824450,119816247, 1214840863,1214857252,1214873686,588726288, 57376850, 1214890042,1214906385,161480737, 1214922755,1214941758,1214988344, 1215004705,32047162, 1215021304,1215053860,1214234641,395460666, 1215070672,1215191135,1215250855,161480786, 1215283236,315949140, 1215299666,1215316004,598032400, 1215332434,613728272, 1215348912,1215381698,1215414273,1215430689,360153121, 1215453280,67829791, 1215514235,20725823, 1215561914,325779489, 1215594582,1215611102, 1215643679,30703633, 1215660218,800407610, 1215692858,312819729, 1215709585,1215742074,141230116, 1215764577,26279995, 1215873025,1215889441,161480762, 1215905793, 22691899, 1215922487,1215990142,252821507, 1216036865,1216053462,1216086378,1216125026,1216305251,20414523, 1216364602,1216380945,881016865, 1216397543,1216430139, 1216446466,1216463066,1216512001,1216528417,1216544826,49676347, 1216562832,1216593994,1216610340,1216626705,1216643073,1216659659,1216692842,1216741377,601817144, 415416379, 1216764004,1212235792,1216839902,880427063, 1216876114,119832612, 1216905274,1216922002,1216970836,600047652, 1216987349,117555263, 1217019935,25100291, 1217036519,26083359, 1217075301,1217134650,1217151035,1217167423,1217183988,1217232927,22511679, 1217250284,1217298448,35602489, 1217315000,161480761, 1217396965, 1217429729,1217468518,1217527866,67829777, 1217544385,1217583207,44662846, 1217626356,1217675548,1217741530,1217773585,1217789985,1217806372,1217822736,1180155921, 1217839371,1211072545,1217871908,270401610, 1217888670,1217937582,1217987226,1218019386,1218035775,1218052159,301547521, 1218069571,85377057, 1218101281,401391652, 1218121056,1218166906,77316132, 1218184267,1218215969,1218232402,1218248720,155664402, 1218269699,1218347678,1218396161,1218412628,351846484, 1218430016,35487761, 1218478081,1218494527,1218510849,1218527249,81068065, 1218543690,4800586, 209895427, 1218560188,1218610248,1218674746,1218691090,1210236986,1218707492,1218723896, 1218740257,88735745, 119832577, 1218756666,875888657, 1218773009,1218789432,1218805796,100237315, 1218828392,1218871354,1218887743,509427775, 4784144, 1218904299, 1218936863,1218953630,1216741393,1219002660,1219073152,101515322, 1219123305,1219264825,65798180, 1219313680,1219330122,1219346449,1219362852,137248802, 251002916, 1219379422,1219411969,1211121723,880427039, 1219434602,1219477520,599588881, 451330079, 1192443906,47301813, 1219500139,1219680364,1219756068,1219772479,1219788922, 1219805883,1219857861,1219903546,1219919890,100237343, 1219936345,423641091, 1219975277,1220068163,1220116496,602079249, 119832607, 70091101, 1220134592,66486305, 1220166775,1220198484,1220215046,1220247637,1220280321,600948798, 1220299066,1220329832,1220378627,137248771, 1220401262,1220509963,1220542497,1220558849,1220575249, 602472481, 351617060, 1220591672,1220608058,1220624447,161480767, 1220640826,1220657211,1211744342,1220673539,1220689978,117555256, 1220706306,1220722691,57163777, 1220739697,1220854440,989839434, 1220903244,438370363, 1220952100,1211121681,1220970310,71450642, 1221019003,1221066808,1221083218,1221099713,1221133376,1221182088, 1221214692,1221247009,499564601, 1221263423,1221280074,1221312705,1221345339,395624465, 953647140, 1221362057,1221449839,1221509179,1210236929,1221526079,9912376, 1221559785,49823828, 1221640276,1221656612,1221672977,81068106, 1221689566,880721923, 673775649, 1221722148,405864506, 1221738789,700645712, 1221777520,1222050005, 1222083054,1222115359,1222132378,1222165027,1222230090,1222246459,1222263032,1222295611,599080961, 1222312247,1222377713,1222411968,1222443233,1222475809,1222492218, 148291643, 1222508775,1222541673,47858817, 266764289, 1222574202,4685826, 1222590650,1222623463,1222662257,1222705400,1222744178,1222836257,95011083, 1222852968, 1222901761,1222924403,1223001265,1223065658,258080842, 853639184, 1223088244,1223328093,1223366773,1223409908,1223460602,1223509369,29688637, 1223576915,22134866, 1223639527,1223688621,1223741716,26443974, 7159882, 1223868609,1223902081,1223950632,1223983137,1223999546,75219018, 1224015903,1224032809,1224071286,1224098122, 1224132182,1224196290,1224228922,70484031, 1224251511,1224448120,1224513657,1224589343,81068116, 1224606491,1224655613,1224704089,1224736961,1224769637,13516801, 1224805052,22446273, 1224851539,1224890490,1225048725,254099519, 999736063, 1225097300,1225113636,597950735, 1225130042,1225147034,1225181662,1225234555,1225294590, 1225327358,1225359391,46710815, 1225375803,1225392212,4685837, 1225409651,1225447548,563036161, 157253649, 1225495806,1225539642,1225556030,35307556, 1225572845, 1226184831,1226260674,1226293434,1226326032,840237073, 1226342622,1226375227,1226391637,1226424337,1226440897,1226473764,146718751, 1226539313,1226604814,26968065, 875413697, 1226640174,1226719498,1226752017,162545665, 341934116, 39780845, 1226768785,1226802394,1226850305,331546641, 1226872960,1227118721,1227178177,1227214896, 1227260152,1227292731,7471160, 1227309367,1227380866,1227440211,71548931, 1227472982,1227489316,471974081, 308215894, 1227511939,1227604004,1227620408,672317473, 1227643012,1227702516,1227751975,1204338999,1227784281,226968045, 1227818266,1227882788,26279953, 152256547, 50348033, 1227954309,1228062942,1228096429,1228129136, 1228210460,196837377, 1228276107,1228314758,44353210, 1228472628,1228521742,108773570, 1228556546,199278628, 1228619969,1228652629,49348806, 1228686366,98418946, 1228734770,1228767291,107774145, 1229166728,251658520, 1229309813,1229357089,98910405, 1229374555,1229406241,1229422649,64602170, 111067199, 1229439255,20267026, 1229478025,1229556309,318570551, 1229602817,1229619259,38748216, 1229636031,1229668408,55918654, 1229684934,838058046, 1229717734,19579096, 1229815866,1229834109, 1229871242,132644950, 1229914613,1230029333,1230061923,1085538679,1230133387,1230307541,451805243, 1230340337,1230373306,1230406095,106364991, 1230454842,1230471199, 7471190, 1230493836,1230701156,347079322, 1230749755,103514148, 1230766344,1230815429,1230854285,928301072, 1230913572,1230930496,17907730, 1230963102,1231012170, 1231044970,98746384, 1231078683,1231127014,1231159527,1231192122,54804973, 1231208533,1231241709,1231274237,1231307166,1231357081,364707841, 1231388878,1231439285, 47677644, 58212368, 1231503553,150913510, 56131643, 1231542248,1231585365,1231618107,85268622, 1231634618,1231673487,1231719587,1231771792,1231913189,19628033, 729792545, 1231946090,1231979263,1232028141,1232060475,1232079888,1232110501,1232159305,1232257557,51347459, 1232289964,1232328849,1232443538,118571041, 1232492691, 1232585379,1232666626,20856835, 38649887, 176767034, 1232683300,1232748561,1232765300,1232798026,1232831101,20856889, 363200855, 1232863425,1232896594,22773793, 1232929079,1233000596,48857121, 1233059903,703087561, 1233077043,309329953, 1233148053,673775698, 1233223905,1233256641,1233289413,1233328278,1233426583,1233486271, 11239453, 162972589, 1233518594,38649914, 1233535369,1233617303,428294207, 1233649723,225902595, 1233671994,1233748052,1233764414,1233780794,1233797137,1233813559, 711147551, 1233829904,65896507, 1233846273,444235793, 1233863106,1233944741,1233978097,1234027981,116457556, 1234075957,1234109569,1162723386,126500923, 29098444, 1234141428,1234190353,1234206924,1234239719,1234272315,1234288946,222609439, 1234327704,1234600129,1234632920,1234670304,1234731781,1234797116,29098196, 1234848134, 1234895041,1234929437,1235025938,690847760, 192365956, 1235042378,19791954, 1235064985,1235239359,1235272155,1235304620,222724154, 1235337300,1235353822,1235392666, 1235451937,1235468304,21217282, 1235486878,1235533908,1235550589,1235615937,1235654811,1235718406,1235785884,108183610, 1235877970,1235896165,1235966109,1236012943, 29507601, 1236041787,1236058113,15646738, 1236074580,368787520, 1236387282,338919917, 1236467924,1236500562,1236516900,1236533496,110412327, 4653309, 309068212, 1236567180,1236621471,1236697121,1236713473,1236730976,102416417, 1236779166,1168965688,1236815278,1236878554,746078293, 1236932768,1237096609,917438482, 1237162146, 458932702, 1237205154,378519583, 1117977, 1237244067,1237303332,1237320103,1237352449,209174531, 1237371057,1237418177,1237450785,20856890, 1237473444,1237686039, 1237729715,1237762236,1237811686,1237844001,26198238, 1237860546,1237899429,1238040769,438714570, 1238073637,517881914, 1238106170,1238124284,1238161574,113180858, 284114946, 1238222854,1238270220,1238369396,1238417409,1238433825,1238450364,139018276, 1238499514,1238532409,1238581834,1238614032,1238630435,1238646815,758054948, 1238663227,1238679796,1238729304,1238778337,1238833319,1239122763,1239171105,1239187514,75218979, 1239209967,1239269862,1239302328,1239390376,1239434292,1239485261, 1138884962,1239532502,54722619, 83181626, 1239597141,442105914, 1239636137,484294719, 1239711745,108724490, 1239728372,1239777591,1239844651,1239908753,1239941468, 336445777, 1239974199,1240040567,1240073089,1240122341,1240186897,1240203486,1240242346,1240285495,190808557, 1240357035,1240438956,30097493, 1240498389,712409146, 265748481, 1240537261,1240596746,392822843, 1240631918,13303866, 1240678456,753614949, 322519226, 1240701102,1240793319,1785942, 1240826204,19399438, 1240858817, 101679163, 1240891450,1240908124,144277720, 1240940778,1241006823,268402774, 1241045167,1241153959,1241186548,1241235473,1241251841,1012416571,1241268282,2382000, 1241284626,1241301074,12091428, 450641944, 1241634994,1241745269,19235252, 1241795157,1241841665,1241858242,1241891004,1241946291,1242087483,1242104114,1242136592, 39632927, 1242153292,1242202324,1242234909,1242251327,681836607, 1242267837,240238628, 1242306740,1242399017,60195668, 162498462, 1242431719,1242464258,1242485156, 1242546235,1242568885,1242661412,1242732726,1242795647,1242842711,1242896567,1242988545,66256899, 1243005159,29098173, 1243044024,19382899, 242417960, 1243156672, 1243251097,1243283457,1075822594,1243302910,36339728, 1243371705,33194237, 1243480148,1243496449,1243512849,980517076, 1243529630,1243578427,62406672, 1243601082, 1243660839,113901642, 1243697594,1243778378,1243824682,1243857140,1243912379,1244119271,19185723, 1244156228,1244217962,48922626, 1244272828,1244354749,1244397569, 9093154, 1244414174,1244453054,1244530311,27770938, 1244563232,1244616895,515145786, 105382136, 1244659898,1244692536,1244708922,65503249, 1244730483,1244807361, 7290911, 7159864, 1244841012,1244895424,1245085882,1245124801,1245206101,1245255874,1245298910,9076820, 1245331512,1245348434,1245380644,81051734, 1245403331, 35897345, 1245480123,237240517, 227328248, 73384184, 51511537, 1245528148,1245544692,1245593794,1245626562,755679589, 1245662648,1245724731,1245746077,1245796548, 1245911237,225558544, 1245970743,1246036219,100191962, 1246086717,47301907, 1246134847,91848740, 1246167071,1246184197,209240297, 1246250202,1246304454,1197001819, 1246462366,93438011, 1246511441,39649576, 1246543889,774767039, 1246566599,1246609425,1246626396,1246675824,1246757076,1246789633,1246806100,1246822923,1246877896, 1246988902,1247053051,1247134251,53756145, 200016273, 1247183661,144310305, 35782689, 1247248777,1247331449,22397140, 1247401129,519782485, 1247467721,432947217, 412696846, 1247549642,1247592449,1247608990,1247641659,75907320, 1247657985,34144320, 1247674469,1247713483,1247772708,74072066, 1247795404,1248067585,1248084268, 1248133151,66371668, 1248151866,1248182614,1248221389,1248270542,1248346328,1248379545,1248428090,100991039, 437879899, 45023290, 1248450767,1248559290,1248592132, 1248641633,1248673828,1248690193,71450682, 1248712912,1248952514,1248985336,1249017873,1249034471,1249067066,173228034, 1249083969,1249117784,1249165369,1120550913, 1249188049,1249263913,366919735, 1249299392,1249346216,1249394721,1249411265,400687140, 1249450194,1249591297,256639035, 1249608062,116457503, 1249656890,1249673290, 768819274, 1249695955,156221456, 1249755218,1249771706,1249810644,523403450, 43925540, 182730809, 1249984760,1250023637,1002274847,60555757, 1250105558,1084937263, 1250203863,127189185, 1250263395,162529855, 1250328917,1250377802,1250394170,3211323, 1250414713,1250509156,1250541626,1250557969,385237047, 1250574521,46743554, 1250607166,1250623504,825098324, 1250639930,39010378, 1250662616,859979838, 1250803745,775667770, 1250820180,1250836564,1250852922,88457218, 1250875609,1250983994, 1218740283,1251000376,265650177, 1251017105,1251049558,1251065942,1251082326,1251098710,54902870, 247185630, 1251426312,1251442721,2850858, 1251459146,27967530, 1251891420,278070986, 1251951707,1251989725,1252065570,550077686, 1252098312,842924063, 1252153566,1252332025,1252409745,135413791, 433554451, 1252442300,312279237, 325419064, 1252492228,1252540778,1252579551,1252743392,1252819336,1252852023,43565250, 1252917281,1252933825,1252966458,1252982847,67272707, 1253005537,1253103842, 101105934, 1253212218,1253228606,108642362, 1253251299,1253408826,119062612, 1253431524,1253490721,85377079, 1253513445,1253654530,1253670948,337149969, 1253688044, 1253742822,1253884474,1253939431,139837497, 1254233292,1254299880,1254381801,285901461, 1254529258,1254606422,1114688, 141610000, 1253824747,549847056, 1254670805, 1254709484,1254818076,158548050, 1254883637,163348887, 1254922477,1255085386,31457861, 1255133171,1255211851,1255260234,1061946153,110972192, 1255276773,1255309401, 492421697, 1255344146,1255410451,1255463150,1255620667,1255638451,1255687100,1255719326,30245397, 1255768286,1255807215,1255867071,1255917078,1255971056,210125121, 1256133560,1256194262,1256227034,1256277275,1256325732,1256374465,1256408035,1256446193,1256571583,1256620035,134643713, 205340689, 1256636473,16482326, 1256659186, 1256982126,121405476, 1257030298,1257068787,1257161106,1257210270,1257259239,1257298164,1257390167,173506591, 1257439543,161775675, 1257505182,1257553978,490143761, 1257570535,1257603738,19742782, 1257637112,1257736389,1257789685,1257904374,1257996763,1258029249,1258062068,1258110993,1147256865,1257161105,1258133751,798426309, 1258193058,1258225886,1258264824,230850645, 93569082, 1258330361,1258537454,1258576122,539886563, 141279679, 1258700817,1258717571,30015521, 1258756347,59031636, 1258832103,302825530, 1258871036,86540502, 58835345, 1258963132,1259018109,1259094017,531873855, 1259116797,192118801, 1259323474,1259346174,1259438139,1259460863, 1259700687,1259749392,498532436, 1259765980,1259831489,21758036, 1259864080,8601659, 1259880692,512737511, 1259934383,1259995406,997343287, 27230266, 1260028102, 1260061141,563069104, 1260099840,1260176442,1260240954,1260257297,1154449482,134562285, 1260273877,1260306466,1260322845,64208913, 1260340842,1260411137,1260568824, 1260601588,1260650682,41913935, 1261017347,1261159414,1261224268,1261273121,1261289554,183779525, 1261305872,736870417, 1261323201,1261389760,1261437010,1261453370, 1261469755,1261492484,1261650005,1261682772,51789857, 1261699289,1261732026,1261771013,116965562, 1261879297,1261895743,132644897, 1261912266,1261945292,596836410, 1261984006,1262076652,1262126008,26902583, 1262174225,311918666, 1262191820,1262239780,1262256145,1114728, 1262272698,1262307874,1262354490,1262370879,1262387218, 459735041, 1262409991,1262622984,1262721289,1262797096,379912988, 1262830960,52543797, 1262882731,1262960910,1262993601,469450788, 177586210, 1263032586,47644730, 1263130891,98549946, 1263212812,1263452620,1263490713,1263540493,1263600283,1263655182,62619918, 71450708, 1263748358,1263819023,1263899109,1263966480,1264140289, 1264156731,1264173260,1264205909,1264238778,1264271393,1264287829,388448315, 1264320884,1264354693,135938788, 1264425233,1264599253,1264632210,1264681015,1264697807, 1264746689,318570555, 1264779457,1264812121,5537795, 1264851218,1264926990,249004087, 1264746554,1264959522,1264977150,1265031443,1265222126,1265254402,1265270970, 2378639, 1265303933,1265369336,1265408276,1265566028,1265615072,1265647690,18727179, 141787194, 1265664363,98796476, 1265703189,1265784564,1265827873,1265844225, 186761233, 1265867030,1115443, 1266080023,1266204886,79790900, 37913797, 1266237684,157745169, 1266286806,1266319853,526499899, 1266352314,1266385072,1266424088, 381599966, 241647674, 30245357, 488063339, 1266506009,4653365, 1266632210,156712976, 1266663506,1266679893,1266712762,1266745530,1266778778,1266811745,1266850074, 870596666, 1266931995,1267057134,1267089941,1267122234,19530025, 1267138644,1267155175,1267194140,1267287289,26198232, 1267325213,1267587358,1267663079,1267696110, 1267728654,1267767583,538198238, 1267876088,1267909154,656917551, 1267990942,1182466107,1268039904,1268078880,1268121601,118571092, 1268140272,1268226337,1268351162, 1268383803,1268401187,190824849, 1268449498,62439508, 1268504866,1268580574,1268613162,1268629562,1268645910,1268662329,12091422, 1268848932,1269006398,1269024121, 1269094693,1269154070,1269187308,2376445, 121257986, 1269242150,114245695, 1269307687,1269537064,1269662190,1269694742,1269727234,1269743617,1269760056,1269776385, 1269792852,140083284, 1269811351,62439510, 1269842280,1133232130,1269892810,1269940306,1269956609,1269973025,1269989433,1270005842,1270022145,27967489, 114245666, 956481591, 1270044969,1270186736,1270235698,1270284589,1270319590,1270356266,882343937, 1270497362,34521089, 365578531, 1172881762,1270520107,1270601107,1270647358, 1270700332,579361069, 1270825226,1087438864,7208963, 1270860717,1270923440,98910439, 1270962478,1271153100,1271186300,1271235718,1179632100,1271268266,71008453, 1271306543,98550057, 1271372080,1093550435,1271447636,1271470385,1271519538,1271660561,1271677090,543949363, 433275073, 1271709754,1271726294,180011092, 1271765299, 1272005673,1272038861,1272088238,1272136010,1272174900,1272465700,121405456, 1272512743,1272545464,190808264, 1272627650,1272709313,1272748341,1272824001,1272856635, 1272873023,967688195, 1272889655,1272955191,156991758, 1273020604,894157286, 1273076022,1273190711,1273266379,1273299253,1273331898,1273364703,25346082, 1273413666, 4685853, 1273436472,144277666, 1273626655,140083217, 1273643501,1273676496,1273708602,1273724929,1214480443,1273741560,11239482, 1273780537,1273839834,67059715, 1273895226,1273974438,1274009915,133890670, 1274398917,1274446037,1274481118,1274532318,1274593281,25346065, 1274609907,447053883, 1274665277,1274790501,1274872528, 540786955, 1274905556,1274954089,375390430, 1274992958,1275133968,31342649, 1275152178,116572163, 1275232449,1003684110,1275265081,1275282148,1275337023,1275511247, 9666619, 1275563757,665387024, 1275612174,1275658907,1275707474,40140858, 1275724496,1275756720,1275789313,47857941, 1275807021,1275855284,387760321, 1275890366, 32342746, 1275943232,1276019315,795115575, 1276052586,1276105339,1276166419,1276198997,1276238145,1276363785,1276461073,269666952, 1276478003,134250721, 1276527186, 955072529, 1276560764,1276631362,1276920100,654066335, 1276990013,641433664, 1277040963,1277280257,21774759, 1277298085,2228282, 1277385028,1277427912,1277460496, 45547577, 1153876165,1277477095,1277514397,1277591805,1277624590,385794106, 1277657399,1277723157,1277755394,1277772082,50938060, 35242020, 1277805945,1277871487, 247890226, 1277935970,1277991237,1278220614,1278296511,1278335303,1278460232,101924897, 1278509287,1278548296,1278722715,1278771367,1278824170,1278875977,1278918730, 1278941514,1278985990,92700942, 1279034315,1279088971,1279164490,93110354, 1279181208,1279230366,20693051, 1279282978,1279345286,1279400268,1279525351,1279574312, 1279613261,1279690933,29917243, 1279754298,126075283, 43827465, 49053697, 1279777102,1280049615,167903430, 1280098885,1280131130,1280149199,1280203087,1280458835, 1280498000,519962850, 1280573688,1280606504,387235876, 1280645457,1280688306,113475883, 1280737338,5537856, 1280760146,1280819259,25346170, 246054994, 1280842067, 1280967038,1281015809,1281032251,1281048594,60555573, 1281067300,1281114170,2380290, 1281136980,4702631, 1281345015,76792012, 1281415509,1281497430,1281606584, 347652322, 1281654842,60195061, 1281671354,1281705056,1281759575,1281822325,1281874264,1281966165,1281999262,103645201, 1282054489,1282146363,1282169178,1282261008, 1278009, 20725793, 1282277589,1282316635,1282382172,1282496861,1282611550,1282660703,1282867694,1282900305,7340095, 1282939232,1283098712,11239483, 65028127, 79315850, 1283152225,1283293370,1283326146,7520290, 1283360004,172032017, 1283430754,1283637699,1283676515,1283774820,1283915835,1283932353,1283964985,1283987813, 1284053350,1284113785,1284178114,1284210773,266764372, 1284243489,266747905, 1284259856,132926752, 1284282727,1284424844,1284473827,1284508041,1284571194,40894662, 1284587713,52543785, 1284621911,1284669867,1284702267,100630546, 1284718840,100155410, 1284751378,1284767745,1020641339,1284790632,1285161235,1285193812,1285210675, 1285265770,1285325067,1285357652,1285380459,1285439758,1285472504,1285505082,140902402, 1285527916,1285603346,1285619743,1285636151,664272897, 1285652516,1285672448, 1285734456,132464722, 1285757293,142066735, 1285822830,1286068591,1286111538,1286144494,1286176826,227557655, 27181232, 47317722, 1286199664,1286393196,1286440070, 1286471763,1286504757,1286537959,781255016, 1286576497,547520529, 1286652116,692289618, 1286684897,1286717441,192004155, 1286734216,1286773106,1286832866,1286865082, 1286897862,1286930634,1286968680,1287029523,1287082501,94535714, 1287160511,376799444, 214909198, 1287215475,1287324030,1287372859,1287395700,1287487577,714424321, 1287526773,1287788918,1288051063,1288110574,172852292, 1288143079,1288175830,1288208798,1288257890,1288307138,1288388609,194625553, 1288406614,1288473439,50872975, 1288526200,14467088, 197312513, 141541392, 1288634616,1288667195,1288683898,160815481, 1288717465,1288749253,116654116, 1288788346,1289001339,1289060605,347128022, 1289093690,1289143264,1289191655,480578556, 1289225184,1289279868,1289470010,1289486399,1289502753,1289519162,311083067, 1289542013,1289656702,1289830842,27951120, 1289863445,1289896545,1289928900,1289999506,1290066303,1290207779,1290273252,1290308231,1290341983,1290404241,1290436830,1290469379,1290488830,144244922, 1290557824, 1290639745,1290731537,1290749125,246399399, 1290781073,1290814015,1290852440,1072857268,1290912921,1290944529,1290960932,637354085, 1290977306,1290993672,1291010056, 1291026454,16482361, 1291047854,1291180418,20971582, 1291288650,262946900, 1133151146,1291305916,1291344259,1291436410,1291468862,1291485418,433586651, 1291550753, 1291567188,664158394, 32112696, 1291585487,1291655556,1291698209,1291714623,164053619, 1291731725,1291780180,1291796765,2382213, 1291829734,1291868550,1291915526, 1291983239,1292042733,1292080337,1292130696,1292206138,181682239, 4653059, 1292222520,1292238849,15646776, 1292261769,698662970, 232243218, 1292422634,1292484833, 1292517434,1253408827,1292533934,1114954, 1292582975,1292599997,1292654942,1292697630,1292714008,16482321, 1292736906,1292779536,8011833, 1292795961,1292812345, 1292828729,90406969, 1292845112,1292861526,7225399, 1292884363,1293140162,1293174057,1293223195,1293272627,1293353374,1293408652,1293533765,519963362, 99565569, 1293566849,187416762, 1293616505,1293681115,1293719949,1293975575,678019108, 1293991960,1294008406,7487530, 1294024928,1294060270,114905392, 1294106894,1294143638, 24985664, 103579684, 1294178702,490389562, 1294254429,846757921, 1294286882,1294303249,1556515, 1294319809,1294352458,227377209, 1294368824,1294385234,938491940, 126025802, 1294401537,83804834, 194527291, 1294417984,1294440847,1294467136,1294489999,1294522767,1120655, 1294549263,1294581882,1245215, 1294604688,1294948754, 57000150, 1295073297,1295089680,1295106105,1295122489,1295138898,1295155201,1295171617,1295188004,1295204532,1295220738,1295237183,1295253505,7340061, 1295269905, 1295286290,1295302714,1295319099,1218068543,1295341971,98795682, 1295679572,95780880, 1295698032,159301633, 1295746875,1295797452,1295882644,1295931797,1295995073, 789643280, 49774593, 1296079254,1296122231,669401170, 19382448, 1296161175,1296243096,1296400385,3395982, 563069121, 1296416930,55656484, 1296456089,2381967, 1296599368,1296646362,1296699906,463372796, 21168359, 585648292, 1296751002,1296843370,1296892541,1296925914,1296980379,1297056863,1297105380,556614775, 444858944, 1297137665,1297154318,655589780, 1297193372,1297471901,1297619358,1297668511,1540104, 1297859501,1297891550,27983903, 1297930656,1298008176,503595044, 614776895, 1298061729,1298154352,1298235516,1298274722,1298317498,1298350467,19791930, 584663877, 1298389411,233799781, 1298514005,1298553252,1298645210,1298694202,1262370875, 1298713835,47202880, 1298760901,46268474, 1298792532,1298808890,207962175, 1298825273,579846161, 1298848165,453181524, 1298942104,1298972888,1299006539,19136567, 1299038439,1299077542,1299382551,1299420029,781255268, 429391930, 136364225, 1299485470,2376433, 1299551696,41026210, 1299612129,1299660817,58605586, 1299683751, 130105753, 1299782056,1299874169,1299940490,67551234, 1299988516,1300011433,1300126122,1300334303,1300453803,1300578797,1300612680,156713018, 1300677977,1300742864, 1300774928,1300797868,1300856916,1300873290,1300889659,14303257, 1300906621,1300938810,15646777, 1300961709,49905726, 1301184706,219512906, 1301217296,1301233727, 1301250132,1301266433,37224534, 1301283047,1301315777,49332257, 1301349613,1301386105,200933379, 1301435277,312198185, 111345841, 49905698, 1301480416,824311825, 1301528609,1301545018,1301561368,1556503, 1301577814,1301594176,8503299, 1301617070,1301643323,1301659703,1301676154,4685887, 1301692445,1301708829,1301725963, 118489867, 1301763072,616775754, 1302075824,1302430015,1302529250,884129877, 1302577211,1302593784,1302626535,1302659135,20840510, 1302682033,1302741415,1302780338, 1302855739,1302872261,614776833, 81985595, 1302906056,1302970588,667172880, 71549050, 1303036318,1303091635,1303169274,1303232513,668762130, 1303249325,1303298270, 1303331198,1303379970,460980225, 1303396779,621346846, 1303431059,1303482791,1303534004,1303642382,1303674939,1303691495,1303726372,1303773243,89325569, 1303789626, 51200018, 1303812533,1304002642,182878394, 1304023250,120848439, 233111588, 1304074678,1304151498,1304199352,87130143, 1304287671,1304330331,1304369592,1304402361, 1304533434,336560144, 642499109, 50348117, 238616605, 882360353, 1304576089,27590715, 61063185, 1304615355,1304674552,1304707156,1304725667,46268434, 1304772830, 516587583, 83394561, 1304805407,19185722, 99418128, 1304828348,1305149524,1261569, 1305165883,1305182456,1305217136,1117954, 1305264398,4637028, 1305299659, 1305385405,38715450, 1305434558,1305542995,1305592507,1305642711,666714129, 1305696703,27230267, 176980154, 1305778624,1305844161,39010340, 1306001594,100844949, 144360018, 1306040087,1306085025,1306132564,19382472, 1306151902,53346333, 55918650, 1306204610,341229585, 1306368451,108642335, 1306427986,1306466756,1306526037, 1306574906,1306591970,1306624184,1306706235,30246085, 162660629, 257212502, 47202641, 1306757259,1306827205,1307066430,1307082810,449691770, 1307105217,1307214238, 1307263469,821575697, 1307298106,1307330269,1307384253,1307433414,55869470, 1307492538,1307529412,1307590849,4292663, 1307623482,1307639867,1307658054,1307711943, 1307852801,1026981905,1307871143,1307934934,662929594, 37847122, 883982737, 1307974088,1308017021,1308082471,1308121545,400163016, 1308164375,37896226, 1308199259, 245612577, 1308229754,7094331, 1308246463,1308285386,1308508357,1308547531,428802132, 1308590422,1308629452,1308711359,1308787163,283361296, 1308819511,61112356, 1308842445,20463905, 1308924366,477331514, 1309002289,134643728, 1309055439,54215057, 11354146, 1309163522,1309179934,15974426, 1309196290,1292238970,1309219280, 1309492124,1309542512,1309590394,295960634, 1309644261,1309687892,1309704471,38912018, 1309737153,185466898, 1309769744,497632954, 1309792721,12091423, 38141953, 1309874642,1309933940,37945813, 1309972947,795197652, 649019428, 1310054868,1310130239,11026461, 1310153173,1310195748,458622538, 1310212656,1310262289,1310294074, 646791252, 181682260, 1310310835,1310349782,1310392375,1310408721,3211266, 4849728, 1310431703,1310523426,1310546392,4866112, 1310572578,1310595472,51779984 }; static unsigned char WordEndBits[4560] = { 0, 0, 32, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 4, 0, 99, 0, 14, 32, 20, 0, 0, 0, 40, 213,0, 158,170,160,89, 135,15, 2, 8, 128,16, 1, 152,4, 0, 36, 37, 96, 34, 17, 33, 0, 0, 0, 0, 0, 80, 1, 0, 192,64, 129,136,67, 5, 84, 168,144,36, 0, 130,0, 225,32, 1, 8, 0, 0, 0, 0, 4, 4, 120,36, 8, 32, 0, 0, 0, 132,146,4, 128,7, 10, 129,0, 0, 0, 8, 30, 80, 0, 0, 0, 8, 128,28, 2, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 56, 60, 32, 0, 241,21, 128,1, 0, 86, 24, 74, 102,16, 229,0, 18, 228,79, 0, 0, 0, 64, 32, 0, 168,0, 0, 7, 32, 128,100,5, 36, 64, 0, 1, 0, 72, 2, 8, 1, 0, 42, 8, 2, 0, 0, 65, 32, 72, 2, 4, 0, 1, 128,0, 4, 20, 2, 9, 0, 97, 194,2, 32, 67, 4, 81, 16, 4, 0, 0, 2, 64, 8, 128,0, 64, 2, 100,129,64, 36, 72, 10, 0, 0, 0, 0, 18, 64, 0, 129,0, 0, 36, 0, 0, 17, 0, 0, 0, 0, 0, 64, 0, 16, 4, 0, 224,0, 12, 1, 8, 0, 0, 0, 0, 128,0, 64, 8, 0, 20, 10, 130, 128,19, 0, 32, 0, 162,146,0, 2, 131,24, 7, 41, 68, 0, 6, 21, 0, 11, 0, 136,20, 49, 140,0, 168,0, 32, 36, 40, 1, 0, 138,0, 48, 0, 129,136,0, 132,34, 0, 0, 166,5, 0, 64, 0, 0, 56, 57, 128,186,96, 64, 129,17, 0, 0, 160,128,1, 0, 2, 0, 64, 0, 5, 75, 99, 8, 64, 0, 0, 0, 94, 10, 6, 137,16, 48, 67, 0, 96, 1, 1, 132,64, 34, 48, 16, 0, 0, 144,8, 208,0, 17, 132,128,0, 0, 2, 0, 0, 96, 0, 0, 24, 0, 64, 8, 2, 8, 1, 0, 16, 128,64, 52, 128,0, 0, 66, 17, 16, 0, 192,128,4, 2, 113,0, 4, 105,0, 1, 0, 0, 3, 0, 16, 6, 40, 8, 2, 0, 0, 1, 0, 140,130,36, 1, 0, 24, 32, 0, 16, 148,199,0, 4, 0, 36, 9, 0, 160,0, 200,128,8, 72, 40, 97, 0, 0, 0, 10, 0, 2, 128,2, 100,0, 134,0, 2, 2, 8, 47, 34, 128,1, 10, 130,2, 0, 80, 34, 80, 16, 0, 64, 0, 80, 64, 144,51, 64, 4, 8, 19, 0, 0, 32, 0, 0, 60, 108,64, 74, 147,0, 0, 20, 0, 192,0, 35, 128,32, 0, 113,1, 42, 167,2, 24, 154,66, 57, 83, 42, 80, 24, 0, 16, 41, 52, 139,128,130,139,0, 99, 166,158,136,137,84, 42, 129,9, 246,220,8, 79, 148,130,206,173,28, 85, 163,224,63, 64, 12, 34, 147,8, 160,20, 10, 128,64, 4, 14, 73, 86, 72, 235,21, 1, 0, 128,36, 144,160,15, 2, 0, 4, 8, 82, 57, 5, 8, 34, 128,100,0, 50, 36, 9, 0, 80, 232,32, 34, 19, 40, 129,32, 32, 140,1, 16, 66, 128,30, 22, 193,129,0, 35, 128,161,8, 24, 64, 96, 120,24, 80, 76, 33, 33, 0, 28, 145,0, 41, 121,197,4, 90, 16, 44, 21, 32, 104,0, 64, 37, 4, 32, 64, 4, 32, 0, 4, 64, 12, 108,18, 32, 68, 64, 0, 0, 0, 0, 48, 128,136,136,152,128,26, 36, 102,1, 0, 76, 32, 32, 4, 136,0, 27, 56, 254,7, 26, 4, 166,168,18, 37, 9, 101,132,129,166,0, 32, 81, 2, 12, 1, 27, 172,4, 102,62, 35, 8, 12, 9, 177,6, 8, 160,28, 149,0, 216,97, 2, 91, 6, 36, 69, 35, 16, 185,2, 0, 4, 1, 129,210,35, 2, 168,29, 136,43, 66, 160,201,25, 192,21, 58, 0, 12, 34, 18, 32, 32, 138,151,6, 32, 1, 100,3, 128,53, 2, 130,39, 1, 8, 238,61, 65, 55, 23, 109,41, 19, 128,66, 102,65, 2, 72, 8, 224,0, 217,128,37, 137,112,6, 66, 231,34, 152,225,43, 32, 16, 24, 96, 16, 96, 20, 56, 154,86, 2, 50, 50, 96, 34, 8, 74, 183,51, 12, 96, 64, 10, 2, 80, 196,149,0, 56, 0, 163,36, 10, 8, 134,1, 34, 0, 80, 18, 30, 49, 100,44, 26, 24, 28, 142,146,27, 113,8, 11, 68, 192,134,104,56, 66, 128,80, 0, 8, 0, 56, 68, 228,32, 32, 136,1, 64, 33, 160,1, 1, 0, 240,36, 65, 96, 14, 105,32, 144,8, 204,9, 24, 136,171,16, 160,203,67, 1, 225,225,32, 160,7, 136,65, 1, 102,1, 130,66, 81, 64, 132,10, 35, 16, 168,129,91, 85, 48, 193,91, 76, 96, 80, 194,60, 124,8, 2, 72, 17, 134,8, 0, 24, 140,104,19, 156,24, 200,69, 8, 0, 1, 0, 133,0, 136,2, 36, 1, 8, 129,130,148,2, 8, 200,1, 20, 0, 128,0, 35, 2, 0, 74, 0, 0, 0, 8, 4, 0, 24, 0, 72, 196,19, 16, 73, 128,153,10, 163,135,96, 4, 4, 84, 106,161,65, 193,128,0, 71, 56, 8, 32, 28, 52, 225,129,5, 33, 24, 102,39, 16, 129,53, 139,81, 0, 64, 0, 0, 32, 202,1, 1, 112,95, 12, 193,251,241,53, 0, 7, 172,47, 1, 128,1, 0, 0, 128,0, 88, 4, 96, 2, 0, 0, 0, 134,24, 128,36, 69, 129,128,7, 64, 3, 12, 0, 70, 129,152,64, 0, 145,134,67, 6, 55, 160,130,124,77, 163,34, 4, 152,18, 20, 35, 144,1, 73, 227,64, 1, 0, 97, 164,81, 0, 72, 134,5, 66, 126,3, 62, 81, 66, 128,76, 65, 49, 168,29, 8, 65, 48, 4, 64, 34, 32, 73, 0, 200,192,1, 16, 48, 129,64, 5, 136,20, 200,138,1, 138,34, 0, 129,0, 0, 2, 64, 36, 100,0, 2, 0, 4, 2, 1, 1, 36, 0, 33, 19, 137,26, 0, 16, 68, 32, 0, 4, 180,4, 128,6, 75, 160,5, 80, 158,12, 113,164,9, 3, 96, 22, 71, 32, 3, 12, 32, 128,70, 6, 0, 173,68, 168,38, 21, 0, 0, 128,69, 32, 168,18, 240,32, 1, 56, 0, 130,130,241,72, 201,69, 10, 8, 94, 32, 193,0, 19, 47, 40, 130,190,32, 224,32, 234,17, 0, 178,193,2, 118,146, 130,16, 1, 0, 210,0, 136,6, 64, 4, 208,2, 100,9, 2, 8, 34, 14, 14, 130,20, 65, 16, 65, 16, 4, 8, 8, 4, 129,2, 32, 2, 16, 0, 0, 0, 0, 2, 88, 0, 147,197,24, 56, 16, 2, 5, 118,169,132,93, 244,204,89, 34, 64, 9, 38, 6, 229,4, 69, 17, 112,17, 128,16, 58, 6, 0, 113,16, 33, 35, 72, 73, 8, 64, 160,128,8, 129,0, 32, 33, 0, 4, 32, 212,22, 128,108,4, 0, 8, 16, 65, 0, 132,0, 1, 16, 129,6, 0, 0, 8, 0, 128,3, 128,0, 8, 128,35, 19, 128,3, 194,64, 8, 22, 8, 4, 18, 66, 4, 129,109,0, 36, 0, 34, 8, 194,12, 17, 0, 64, 0, 0, 34, 32, 16, 128,72, 42, 2, 0, 1, 36, 8, 2, 104,0, 66, 32, 6, 144,35, 4, 167,0, 2, 16, 2, 20, 4, 16, 0, 128,124,0, 128,128,5, 136,114,136,4, 0, 16, 5, 0, 136,16, 16, 4, 0, 220,12, 10, 0, 85, 138,0, 0, 16, 0, 36, 0, 1, 0, 2, 48, 0, 8, 3, 1, 0, 1, 32, 0, 144,0, 0, 0, 4, 0, 0, 128,2, 0, 64, 1, 2, 0, 144,147,0, 2, 0, 0, 40, 33, 62, 144,16, 83, 65, 136,204,66, 168,185, 130,9, 17, 154,8, 22, 215,130,40, 40, 146,164,3, 107,1, 2, 40, 54, 164,8, 18, 67, 0, 37, 32, 12, 33, 1, 130,172,0, 82, 98, 8, 27, 180,0, 144,204,80, 4, 73, 9, 5, 32, 0, 6, 20, 0, 38, 4, 8, 80, 0, 0, 0, 128,129,13, 34, 74, 148,8, 68, 4, 9, 128,0, 128,17, 64, 4, 0, 0, 37, 80, 128,0, 64, 28, 64, 0, 66, 129,8, 0, 64, 2, 12, 8, 164,4, 8, 0, 0, 72, 32, 0, 66, 65, 9, 0, 56, 176,204,168,164,82, 16, 20, 128,16, 0, 64, 32, 2, 72, 36, 104,128,0, 8, 32, 0, 16, 0, 0, 130,52, 0, 34, 34, 12, 0, 4, 0, 0, 16, 0, 0, 137,128,132,64, 0, 128,2, 0, 0, 0, 0, 8, 0, 10, 56, 0, 5, 0, 32, 0, 12, 0, 4, 128, 16, 4, 8, 69, 48, 1, 192,6, 0, 0, 24, 176,0, 24, 34, 121,160,132,48, 160,0, 73, 0, 76, 4, 0, 0, 10, 20, 8, 128,4, 0, 34, 0, 10, 0, 16, 66, 0, 0, 16, 1, 0, 0, 0, 0, 16, 0, 0, 176,96, 48, 1, 240,1, 129,112,0, 140,9, 32, 6, 172,32, 4, 8, 164,16, 80, 91, 0, 144,40, 64, 36, 165,128,101,0, 10, 105, 8, 23, 3, 67, 5, 35, 72, 0, 65, 164,240,3, 84, 20, 137,3, 12, 97, 48, 176,20, 40, 48, 16, 43, 23, 64, 44, 86, 2, 202,36, 130,16, 136,16, 56, 22, 82, 64, 1, 0, 128,16, 199,40, 166,164,138,4, 8, 100,34, 152,16, 180,63, 84, 228,146,0, 3, 65, 85, 128,32, 88, 21, 6, 2, 82, 2, 192,193,248,20, 35, 114,156,80, 1, 75, 20, 211,64, 0, 8, 242,211,199,77, 129,41, 193,48, 57, 18, 68, 12, 10, 160,66, 220,80, 30, 2, 9, 64, 96, 23, 17, 24, 8, 4, 80, 2, 8, 16, 0, 228,4, 0, 12, 4, 192,3, 0, 12, 33, 6, 2, 0, 36, 72, 67, 128,0, 12, 68, 130,11, 129,195,64, 196,4, 34, 96, 30, 114,156,32, 0, 68, 18, 57, 16, 84, 16, 58, 195,57, 65, 66, 0, 6, 7, 198,0, 0, 89, 44, 17, 49, 18, 2, 0, 128,6, 64, 16, 161,34, 37, 128,1, 168,1, 14, 0, 1, 128,0, 36, 0, 114,128,152,128,0, 98, 20, 1, 0, 0, 129,0, 65, 1, 0, 128,16, 8, 106,21, 160,16, 4, 66, 16, 129,1, 96, 4, 0, 6, 16, 8, 4, 5, 50, 71, 16, 0, 16, 12, 128,0, 64, 145,69, 0, 100,18, 192,11, 72, 65, 35, 0, 80, 28, 44, 116,113,212,97, 64, 68, 5, 202,1, 202,16, 196,4, 0, 161,19, 105,24, 132,12, 134,231,89, 64, 186,158,13, 71, 246,154,32, 2, 0, 0, 16, 48, 1, 4, 64, 12, 0, 4, 4, 13, 1, 193,208,209,36, 0, 188,67, 64, 134,0, 2, 130,0, 224,16, 10, 4, 70, 183,161,251,12, 66, 16, 48, 76, 72, 32, 35, 4, 0, 44, 189,19, 38, 64, 130,68, 102,44, 4, 137,42, 1, 130,12, 4, 24, 166,119,141,84, 54, 32, 96, 128,40, 60, 163,5, 94, 200,1, 112,19, 16, 24, 12, 32, 1, 0, 148,60, 16, 48, 108,17, 1, 226,128,198,11, 23, 99, 188,162,5, 98, 160,192,64, 8, 129,36, 64, 24, 128,24, 28, 165,1, 54, 3, 48, 0, 4, 46, 164,18, 1, 14, 72, 0, 192,133,3, 160,0, 68, 1, 64, 32, 2, 6, 0, 96, 72, 194,18, 12, 241,15, 128,236,4, 2, 136,8, 64, 48, 208,2, 11, 66, 2, 166,0, 56, 130,33, 2, 21, 252, 124,149,3, 192,41, 9, 2, 128,3, 28, 162,129,155,4, 98, 94, 154,50, 9, 141,192,100,129,128,10, 0, 18, 80, 1, 81, 1, 0, 137,3, 184,7, 0, 0, 20, 65, 4, 2, 80, 5, 10, 153,48, 17, 25, 194,16, 39, 14, 35, 16, 35, 73, 30, 128,10, 4, 192,0, 16, 0, 0, 0, 64, 0, 0, 2, 0, 0, 0, 160,1, 0, 145,0, 0, 0, 136, 35, 128,0, 0, 128,80, 0, 0, 0, 4, 0, 2, 0, 0, 0, 0, 4, 0, 18, 64, 8, 0, 0, 0, 0, 12, 68, 8, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 67, 0, 0, 0, 18, 32, 4, 0, 0, 0, 66, 1, 0, 128,8, 2, 40, 34, 0, 10, 0, 64, 0, 5, 1, 0, 0, 128,0, 8, 129,64, 80, 64, 16, 34, 0, 64, 70, 100,2, 80, 8, 64, 0, 132,0, 4, 8, 4, 34, 64, 144,128,0, 9, 224,140,0, 32, 0, 0, 160,12, 0, 0, 4, 177,128,2, 67, 1, 0, 4, 146,22, 244,136,9, 81, 40, 32, 155,209,205,18, 4, 2, 0, 140,32, 16, 132,0, 0, 19, 148,0, 40, 204,35, 0, 232,32, 112,37, 24, 0, 84, 82, 0, 14, 76, 42, 5, 94, 160,45, 17, 103,160,86, 20, 33, 168,64, 104,2, 0, 250,49, 78, 2, 64, 34, 2, 0, 16, 0, 30, 24, 114,75, 175,150,38, 15, 98, 248,94, 54, 113,154,130,40, 97, 16, 4, 8, 3, 0, 192,9, 245, 131,19, 232,0, 184,0, 178,8, 0, 5, 82, 22, 128,80, 5, 3, 33, 0, 16, 130,68, 69, 20, 144,27, 78, 245,128,10, 84, 20, 193,18, 1, 146,164,107,18, 0, 28, 2, 68, 4, 1, 180,128,72, 32, 69, 40, 32, 32, 148,72, 22, 16, 132,64, 68, 36, 33, 78, 80, 32, 131,33, 16, 40, 96, 16, 1, 40, 168,0, 160,10, 10, 8, 16, 0, 196,90, 226,0, 28, 1, 0, 72, 64, 96, 160,194,66, 5, 0, 128,1, 0, 176,130,0, 152,200,0, 192,33, 0, 0, 6, 0, 224,128,126,80, 144,152,165,66, 144,165,88, 54, 134, 136,15, 8, 108,164,12, 66, 20, 97, 25, 128,128,33, 180,139,168,0, 203,44, 177,226,22, 32, 205,2, 68, 76, 148,197,85, 193,4, 164,146,67, 132,116,32, 132,17, 48, 133,40, 152,234,161,123,23, 9, 192,0, 4, 72, 161,132,173,32, 55, 133,0, 128,2, 0, 77, 0, 29, 35, 2, 66, 0, 16, 5, 239,218,51, 72, 208,156,162,120,66, 152, 64, 21, 85, 101,132,218,105,11, 14, 128,41, 15, 83, 35, 132,34, 130,72, 67, 180,1, 157,28, 24, 12, 136,249,16, 120,39, 193,229,149,4, 98, 192,154,201,5, 32, 57, 35, 0, 72, 12, 0, 193,210,69, 154,64, 12, 193,2, 68, 16, 246,1, 0, 184,0, 64, 0, 2, 0, 7, 90, 240,93, 0, 4, 64, 16, 16, 0, 132,8, 68, 4, 133,78, 8, 68, 75, 188,48, 195,2, 32, 56, 224,0, 56, 0, 40, 174,4, 35, 84, 73, 105,132,79, 224,32, 32, 116,133,39, 66, 129,64, 129,8, 226,185,68, 29, 133,0, 28, 227,69, 138,66, 216,33, 48, 37, 135,248,79, 204,154,129,1, 88, 35, 24, 70, 69, 1, 34, 152,14, 54, 129,28, 96, 82, 4, 7, 87, 224,14, 132,72, 2, 0, 0, 0, 0, 136,164, 128,0, 0, 0, 8, 1, 32, 0, 8, 0, 0, 152,0, 0, 0, 128,128,8, 130,0, 0, 0, 0, 0, 0, 68, 1, 1, 0, 0, 0, 0, 0, 64, 129,0, 0, 0, 0, 0, 0, 0, 50, 34, 25, 100,70, 108,0, 168,51, 0, 87, 136,32, 148,79, 25, 160,160,30, 24, 137,186,96, 4, 105,176,212,32, 0, 64, 164,68, 128,192,10, 16, 130,0, 66, 214, 113,169,2, 32, 66, 8, 2, 2, 16, 208,19, 28, 100,224,47, 64, 235,128,208,89, 208,5, 130,171,34, 0, 9, 0, 8, 0, 0, 16, 4, 3, 0, 0, 0, 136,37, 1, 64, 20, 20, 65, 129,25, 96, 33, 0, 12, 5, 82, 4, 74, 128,42, 198,96, 168,2, 246,192,131,22, 129,0, 197,4, 33, 9, 67, 0, 172,113,99, 129,118,30, 144,232,21, 118, 37, 128,43, 41, 135,25, 204,19, 73, 145,192,0, 8, 139,10, 0, 1, 19, 128,32, 133,16, 32, 24, 68, 81, 17, 176,136,147,213,11, 20, 1, 0, 0, 32, 0, 160,0, 0, 16, 4, 0, 224,86, 5, 64, 192,64, 164,24, 134,78, 44, 50, 10, 136,4, 8, 8, 16, 16, 132,0, 2, 34, 213,1, 0, 68, 36, 26, 24, 4, 65, 144,106,16, 0, 128,0, 208,1, 0, 1, 136,16, 64, 0, 3, 68, 86, 232,0, 2, 20, 2, 48, 128,162,8, 168,16, 11, 1, 5, 70, 2, 35, 224,22, 8, 64, 0, 252,9, 33, 248,16, 128,4, 68, 195,24, 64, 44, 1, 64, 128,16, 16, 32, 2, 28, 0, 16, 100,0, 0, 0, 0, 0, 1, 2, 32, 0, 0, 0, 5, 0, 0, 16, 2, 128,32, 8, 0, 0, 0, 0, 76, 36, 48, 0, 15, 0, 69, 0, 26, 19, 132,30, 0, 0, 20, 9, 65, 8, 0, 192,135,16, 0, 4, 0, 32, 0, 208,0, 34, 0, 3, 32, 2, 0, 228,32, 2, 0, 0, 0, 128,0, 8, 0, 0, 6, 31, 1, 35, 128,18, 16, 2, 48, 8, 144,0, 0, 32, 0, 8, 4, 0, 4, 32, 0, 0, 8, 8, 1, 0, 2, 2, 40, 19, 35, 9, 128,147,49, 53, 4, 16, 128, 7, 80, 200,140,130,14, 135,0, 0, 1, 32, 0, 107,78, 0, 161,65, 0, 152,40, 104,42, 124,34, 105,1, 164,187,6, 6, 64, 2, 195,254,48, 98, 0, 128,90, 16, 4, 116,212,2, 24, 8, 32, 192,28, 138,7, 32, 11, 134,145,80, 1, 180,34, 11, 160,7, 32, 56, 0, 40, 0, 40, 43, 206,6, 211,0, 0, 128,0, 64, 128,0, 0, 64, 0, 0, 142,33, 64, 8, 16, 16, 0, 10, 134,128,183,134,24, 69, 1, 8, 97, 192,13, 34, 50, 204,128,16, 145,0, 20, 1, 66, 10, 12, 130,0, 22, 133,64, 176,4, 196,132, 0, 168,44, 10, 64, 5, 128,80, 64, 200,104,21, 62, 213,64, 18, 32, 48, 215,86, 28, 3, 212,1, 252,68, 12, 193,147,20, 0, 104,68, 16, 64, 24, 56, 0, 2, 49, 1, 0, 8, 64, 32, 32, 32, 160,32, 1, 4, 1, 96, 1, 1, 92, 6, 36, 16, 81, 177,19, 114,104,1, 129,40, 1, 2, 64, 40, 18, 1, 9, 34, 16, 36, 34, 0, 1, 72, 192, 131,36, 200,32, 10, 75, 0, 196,65, 0, 32, 0, 0, 36, 50, 40, 4, 49, 36, 164,128,32, 65, 84, 0, 26, 33, 0, 0, 4, 8, 8, 0, 0, 1, 143,196,66, 1, 20, 65, 240,192,132,194,5, 32, 34, 248,32, 60, 65, 53, 133,21, 4, 205,18, 160,34, 129,16, 26, 41, 4, 1, 28, 81, 231,94, 105,4, 64, 17, 19, 179,216,246,108,144,97, 163, 168,86, 9, 1, 65, 0, 4, 168,128,0, 0, 6, 0, 12, 0, 1, 136,16, 0, 4, 145,0, 167,1, 65, 4, 1, 40, 152,65, 4, 3, 2, 30, 144,1, 176,16, 0, 192,0, 0, 212,32, 0, 9, 228,66, 182,26, 128,178,1, 16, 145,32, 0, 1, 0, 128,0, 88, 42, 0, 1, 12, 132,0, 4, 0, 66, 32, 40, 33, 40, 72, 8, 32, 193,1, 3, 117, 78, 3, 161,5, 152,1, 70, 153,40, 1, 138,13, 16, 251,107,148,5, 14, 65, 56, 180,193,156,254,180,18, 2, 136,5, 48, 0, 244,3, 120,26, 70, 158,147,196,128,16, 140,0, 2, 44, 110,164,5, 48, 130,1, 8, 5, 72, 52, 1, 14, 1, 120,237,4, 22, 85, 132,0, 67, 48, 218,175,11, 0, 10, 192,184,88, 128,0, 0, 0, 1, 5, 128, 141,120,202,32, 96, 132,64, 76, 71, 132,0, 152,146,76, 34, 2, 5, 3, 16, 24, 11, 96, 197,68, 165,16, 16, 150,78, 129,68, 8, 209,226,129,128,72, 2, 32, 1, 1, 64, 11, 52, 3, 2, 104,2, 2, 1, 1, 16, 176,1, 0, 40, 48, 85, 18, 4, 64, 64, 34, 65, 54, 20, 0, 0, 128,0, 80, 200,99, 40, 52, 4, 32, 66, 13, 1, 0, 128, 79, 12, 128,2, 72, 33, 0, 196,230,65, 82, 8, 200,124,4, 164,0, 148,72, 73, 32, 0, 128,32, 154,197,132,36, 10, 208,1, 0, 226,216,30, 152,100,94, 96, 26, 41, 211,69, 18, 219,178,199,43, 49, 6, 70, 76, 64, 148,131,49, 133,51, 58, 160,226,131,35, 150,76, 48, 129,88, 69, 18, 23, 4, 80, 64, 16, 56, 2, 121,22, 4, 32, 152, 6, 69, 250,8, 40, 172,48, 16, 12, 36, 4, 0, 2, 5, 66, 128,1, 16, 181,136,152,0, 0, 52, 36, 82, 81, 39, 184,231,36, 52, 66, 45, 36, 193,212,6, 12, 113,0, 20, 124,128,20, 30, 48, 65, 160,98, 8, 54, 144,1, 66, 208,127,12, 40, 9, 84, 8, 165,241,6, 72, 73, 5, 2, 242,18, 0, 0, 34, 228,38, 117,210,18, 26, 192,146, 69, 192,68, 232,162,2, 67, 198,50, 66, 11, 64, 50, 26, 192,132,145,0, 0, 128,23, 26, 227,208,210,173,34, 162,2, 0, 0, 97, 64, 78, 28, 129,128,192,211,138,57, 6, 40, 0, 24, 112,196,64, 69, 37, 32, 152,0, 132,194,0, 112,4, 0, 16, 0, 0, 61, 10, 147,25, 9, 161,34, 16, 185,25, 8, 153,138,148,160,40, 96, 20, 8, 40, 0, 129,66, 32, 45, 2, 0, 0, 0, 32, 6, 4, 16, 32, 0, 0, 8, 8, 3, 2, 28, 35, 16, 128,72, 37, 33, 17, 48, 0, 0, 0, 136,2, 192,20, 0, 160,51, 4, 4, 144,0, 136,47, 17, 24, 49, 28, 8, 68, 199,36, 32, 0, 128,2, 1, 128,24, 8, 0, 128,0, 0, 0, 80, 16, 171,87, 9, 14, 36, 160,82, 0, 135,227,144,61, 3, 93, 4, 153,96, 37, 64, 65, 4, 32, 116,137,80, 96, 3, 2, 2, 128,32, 65, 20, 101,138,4, 15, 8, 69, 182,36, 1, 58, 0, 4, 32, 132,224,48, 2, 194,4, 132,2, 65, 128,129,207,33, 142,67, 64, 77, 224,1, 64, 220,143,13, 12, 16, 100,133,4, 129,84, 24, 42, 165,0, 130,104,1, 160,44, 135,96, 122,229,34, 65, 200,153,53, 16, 139, 66, 163,73, 96, 64, 49, 89, 2, 240,128,0, 204,135,98, 61, 5, 132,64, 4, 44, 64, 50, 3, 9, 65, 2, 88, 8, 1, 40, 134,131,16, 203,32, 98, 128,82, 226,72, 4, 8, 1, 162,8, 2, 98, 2, 37, 96, 16, 40, 33, 128,74, 24, 16, 80, 0, 4, 90, 1, 0, 8, 5, 0, 0, 8, 0, 129,4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 18, 0, 128,9, 0, 128,0, 0, 64, 0, 0, 0, 0, 0, 4, 0, 128,0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 4, 144,145,32, 0, 16, 32, 32, 7, 0, 33, 32, 192,2, 8, 241,56, 203,134,4, 2, 0, 0, 24, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 12, 64, 32, 0, 0, 0, 16, 144,0, 16, 30, 2, 128,129,144,0, 64, 1, 128,5, 0, 48, 27, 0, 192,13, 33, 128,208,0, 130,129,7, 130,82, 240,140,88, 26, 32, 20, 4, 10, 12, 40, 209,0, 1, 70, 179,14, 0, 2, 48, 18, 65, 210,16, 0, 132,2, 2, 0, 0, 192,1, 40, 40, 20, 16, 222, 144,20, 184,99, 160,131,250,1, 136,36, 180,65, 35, 196,8, 10, 64, 64, 0, 80, 36, 68, 34, 32, 8, 6, 65, 21, 15, 242,2, 66, 216,84, 200,17, 141,128,3, 13, 1, 0, 0, 16, 23, 1, 67, 64, 3, 133,144,77, 196,16, 24, 64, 25, 17, 14, 64, 22, 8, 9, 4, 154,168,128,41, 8, 228,86, 127,160,4, 12, 37, 76, 68, 104,117,163,196, 16, 2, 70, 1, 39, 0, 64, 1, 0, 64, 151,0, 64, 65, 4, 0, 129,41, 0, 140,166,52, 28, 0, 8, 3, 66, 35, 0, 64, 128,3, 128,0, 0, 0, 0, 120,0, 1, 0, 0, 0, 138,60, 4, 96, 64, 151,8, 0, 4, 128,88, 164,19, 128,160,18, 0, 192,1, 64, 9, 2, 16, 8, 0, 128,0, 64, 4, 0, 70, 13, 9, 16, 136,66, 20, 69, 2, 136,128,1, 64, 66, 3, 0, 224,224 }; static const unsigned short ChildLocs[79994] = { 157, 162, 167, 1, 475, 558, 639, 711, 779, 828, 885, 937, 969, 2, 7, 17, 29, 38, 42, 50, 58, 65, 67, 71, 77, 83, 92, 97, 98, 109, 110, 116, 133, 142, 144, 149, 36019,155, 156, 158, 159, 160, 161, 163, 164, 165, 166, 168, 175, 187, 197, 201, 203, 204, 208, 214, 217, 223, 169, 170, 171, 172, 174, 173, 176, 180, 181, 177, 161, 161, 178, 179, 161, 161, 182, 184, 183, 185, 186, 188, 193, 189, 191, 190, 192, 194, 195, 196, 198, 199, 200, 202, 190, 205, 206, 207, 209, 174, 210, 211, 212, 213, 215, 216, 207, 218, 219, 220, 221, 222, 224, 190, 226, 225, 227, 228, 229, 230, 243, 265, 317, 326, 344, 360, 363, 371, 379, 401, 406, 410, 419, 422, 428, 441, 448, 464, 467, 469, 231, 233, 237, 241, 242, 232, 232, 161, 161, 161, 161, 161, 161, 161, 234, 161, 161, 161, 161, 161, 161, 161, 161, 161, 235, 236, 238, 161, 161, 161, 161, 161, 161, 161, 161, 239, 240, 244, 248, 256, 264, 245, 161, 161, 161, 161, 161, 246, 247, 249, 255, 161, 161, 161, 161, 250, 253, 251, 252, 254, 161, 161, 257, 261, 161, 161, 161, 161, 161, 161, 190, 258, 259, 260, 262, 263, 266, 268, 273, 274, 305, 180, 308, 312, 161, 161, 161, 161, 161, 267, 161, 161, 161, 161, 161, 269, 272, 161, 161, 161, 161, 161, 161, 270, 271, 161, 161, 161, 161, 161, 161, 161, 161, 161, 161, 275, 276, 281, 282, 161, 297, 298, 299, 300, 303, 277, 278, 279, 280, 283, 285, 286, 287, 292, 294, 284, 288, 161, 161, 289, 161, 290, 161, 291, 293, 295, 296, 301, 302, 186, 304, 306, 307, 309, 310, 311, 313, 314, 315, 316, 318, 190, 321, 323, 180, 319, 320, 322, 324, 325, 291, 327, 330, 333, 180, 335, 200, 161, 328, 329, 331, 332, 334, 161, 336, 337, 340, 338, 339, 200, 341, 342, 343, 345, 351, 356, 346, 347, 348, 349, 350, 352, 353, 354, 355, 357, 332, 358, 359, 361, 260, 260, 362, 364, 367, 242, 365, 366, 368, 369, 370, 372, 374, 378, 161, 373, 375, 376, 377, 174, 380, 383, 273, 384, 388, 395, 399, 381, 161, 382, 161, 161, 161, 161, 161, 161, 161, 161, 161, 385, 386, 180, 387, 161, 161, 161, 161, 389, 161, 161, 161, 392, 161, 390, 391, 393, 394, 161, 161, 161, 161, 396, 161, 161, 161, 161, 161, 397, 398, 161, 161, 161, 161, 161, 161, 161, 161, 161, 400, 402, 403, 404, 405, 407, 408, 409, 411, 415, 412, 413, 414, 416, 417, 418, 420, 421, 423, 427, 424, 425, 426, 429, 431, 438, 430, 432, 436, 433, 434, 435, 437, 439, 440, 442, 446, 443, 444, 445, 447, 449, 455, 461, 450, 451, 452, 453, 454, 456, 457, 459, 458, 460, 462, 463, 465, 466, 468, 470, 471, 472, 473, 474, 476, 493, 501, 508, 520, 529, 538, 541, 544, 547, 550, 557, 477, 480, 485, 207, 489, 478, 479, 161, 161, 161, 161, 174, 172, 161, 481, 482, 483, 484, 486, 487, 488, 490, 491, 492, 494, 498, 495, 496, 497, 499, 500, 190, 502, 258, 260, 207, 183, 503, 506, 507, 504, 505, 509, 513, 180, 515, 510, 511, 512, 514, 516, 517, 518, 519, 521, 525, 522, 523, 524, 526, 161, 161, 174, 527, 528, 241, 530, 534, 531, 532, 533, 535, 536, 537, 539, 192, 540, 542, 543, 545, 546, 548, 549, 551, 554, 552, 553, 555, 556, 559, 563, 571, 578, 587, 590, 593, 601, 604, 613, 616, 620, 623, 626, 629, 631, 635, 636, 560, 562, 561, 161, 564, 566, 568, 565, 567, 569, 570, 572, 575, 573, 285, 574, 576, 577, 579, 580, 585, 581, 584, 582, 583, 586, 588, 589, 591, 592, 594, 595, 596, 600, 597, 598, 599, 240, 602, 603, 605, 207, 609, 606, 607, 608, 306, 610, 611, 612, 614, 615, 617, 618, 619, 621, 622, 624, 625, 627, 628, 630, 632, 633, 634, 637, 638, 640, 643, 647, 655, 657, 664, 670, 673, 680, 683, 687, 688, 691, 694, 697, 700, 702, 707, 708, 641, 642, 644, 645, 646, 648, 242, 649, 651, 654, 232, 336, 203, 650, 652, 653, 161, 173, 190, 656, 183, 658, 663, 659, 660, 661, 662, 665, 667, 666, 573, 285, 668, 574, 669, 671, 672, 190, 674, 675, 676, 677, 678, 679, 681, 682, 684, 686, 685, 689, 690, 692, 693, 695, 696, 698, 699, 701, 703, 705, 704, 706, 709, 710, 712, 717, 722, 725, 728, 732, 751, 758, 765, 768, 771, 774, 174, 713, 715, 489, 714, 716, 718, 719, 721, 174, 720, 200, 723, 190, 724, 180, 726, 727, 190, 260, 729, 731, 730, 733, 737, 741, 747, 734, 735, 736, 738, 739, 740, 742, 743, 584, 744, 745, 746, 748, 749, 750, 752, 754, 192, 756, 753, 755, 757, 759, 761, 764, 760, 762, 763, 766, 767, 769, 770, 772, 773, 775, 776, 777, 778, 780, 783, 787, 789, 793, 796, 799, 805, 808, 810, 819, 822, 825, 781, 782, 784, 785, 786, 788, 790, 791, 792, 794, 795, 286, 286, 797, 798, 800, 801, 507, 400, 802, 803, 804, 806, 180, 807, 809, 811, 242, 815, 812, 813, 814, 816, 817, 818, 820, 821, 823, 824, 826, 827, 182, 829, 832, 836, 839, 844, 848, 856, 862, 867, 869, 872, 875, 878, 881, 830, 831, 833, 834, 835, 837, 838, 840, 842, 841, 843, 845, 846, 357, 359, 847, 849, 850, 854, 851, 852, 853, 855, 260, 857, 861, 858, 564, 859, 860, 863, 864, 161, 573, 865, 574, 298, 299, 866, 868, 870, 871, 873, 874, 876, 877, 879, 880, 882, 883, 884, 886, 892, 896, 899, 908, 911, 917, 919, 922, 925, 930, 934, 887, 889, 888, 890, 891, 893, 894, 895, 897, 898, 161, 668, 900, 904, 906, 901, 902, 903, 905, 907, 909, 910, 912, 913, 914, 915, 916, 918, 920, 921, 923, 924, 926, 927, 928, 929, 931, 932, 933, 935, 936, 938, 943, 948, 950, 953, 960, 966, 939, 941, 940, 942, 944, 565, 945, 946, 947, 949, 840, 951, 952, 954, 959, 668, 955, 299, 956, 957, 958, 961, 962, 963, 964, 965, 967, 968, 970, 971, 972, 973, 974, 978, 985, 1031, 1234, 1447, 1641, 1665, 1741, 1832, 1863, 1925, 1927, 3, 2307, 4, 2777, 2780, 2916, 5, 6, 3385, 3471, 3611, 3647, 3666, 3672, 3687, 975, 977, 976, 979, 981, 980, 982, 983, 984, 986, 1012, 409, 1014, 1015, 1018, 186, 1020, 1027, 1029, 565, 987, 988, 1009, 409, 1007, 186, 1011, 989, 1007, 161, 990, 996, 997, 161, 991, 161, 161, 992, 161, 993, 994, 161, 995, 409, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1008, 1010, 1013, 1016, 1017, 1019, 1021, 409, 1024, 161, 1022, 1023, 1025, 414, 161, 1026, 1028, 1030, 1032, 1045, 1061, 1072, 1095, 302, 1117, 1120, 1130, 1132, 1135, 1142, 1162, 1167, 1192, 1222, 1232, 1033, 1034, 1035, 296, 1037, 1038, 1043, 1044, 304, 1010, 161, 414, 1036, 1039, 1040, 1041, 186, 1042, 715, 161, 301, 161, 1046, 1049, 1051, 1052, 1055, 161, 161, 1060, 1047, 161, 304, 1048, 1050, 161, 350, 1053, 186, 414, 161, 1054, 1056, 1057, 1058, 1059, 1062, 1065, 1066, 1063, 1064, 1067, 1069, 1068, 1070, 1071, 1073, 1076, 161, 1081, 1088, 1074, 1075, 1077, 1078, 1079, 1080, 1082, 435, 1083, 1086, 1084, 1085, 1087, 1089, 1094, 161, 1090, 186, 1091, 296, 556, 1092, 1093, 414, 161, 556, 1044, 1096, 1098, 1102, 1115, 1044, 296, 161, 1097, 1026, 1099, 1100, 1101, 1103, 1108, 304, 1110, 1113, 1104, 1105, 1106, 1107, 1109, 1111, 1111, 1112, 1114, 350, 437, 1116, 1118, 1119, 1121, 1122, 1125, 1129, 161, 1042, 1123, 1124, 161, 161, 1126, 1127, 1128, 750, 161, 1131, 1133, 1134, 1136, 1137, 1138, 1139, 1140, 1141, 1127, 213, 1143, 304, 1144, 1146, 1151, 1152, 1157, 1158, 1145, 161, 1147, 1148, 1149, 1150, 437, 1153, 1155, 1154, 186, 1156, 186, 1093, 1159, 1160, 1161, 1163, 1164, 1165, 1166, 1168, 1185, 435, 1186, 1187, 1169, 1174, 1178, 1182, 413, 1170, 1171, 1172, 1173, 1175, 1176, 414, 1177, 418, 418, 421, 1179, 1026, 1180, 1181, 1183, 1184, 304, 468, 519, 161, 161, 161, 186, 161, 161, 1188, 1190, 1191, 1189, 1193, 1196, 1200, 1202, 1213, 1219, 1194, 1195, 1197, 1198, 1199, 296, 1201, 296, 304, 1203, 1209, 1204, 1207, 1205, 1206, 417, 213, 161, 1208, 161, 1210, 1212, 1211, 1042, 161, 426, 1214, 418, 1218, 1215, 1216, 1217, 1220, 1221, 463, 213, 161, 1223, 1224, 1228, 1229, 161, 1225, 1226, 414, 1227, 304, 1189, 1230, 1231, 556, 304, 1037, 1233, 1087, 161, 1235, 1247, 302, 1339, 1348, 1360, 1368, 1385, 1387, 304, 1388, 1394, 1409, 1416, 1439, 1236, 1243, 1246, 1237, 1238, 1239, 161, 161, 1240, 1241, 161, 1242, 1244, 1245, 1248, 1251, 1271, 1277, 1285, 1324, 1249, 1250, 1252, 1258, 1261, 1264, 1044, 1253, 1254, 1255, 350, 1256, 1208, 1257, 296, 556, 418, 1259, 161, 1260, 1262, 1263, 186, 1042, 161, 1150, 1217, 1265, 662, 180, 186, 1266, 1267, 1150, 556, 1268, 1269, 161, 414, 1270, 1272, 1273, 1274, 1275, 1276, 213, 161, 1278, 1281, 1279, 1280, 1282, 1283, 1284, 1286, 1288, 1306, 1310, 1311, 1287, 1289, 1294, 1290, 1291, 1292, 1293, 161, 1091, 1295, 1298, 1296, 311, 1297, 1299, 1198, 1300, 1301, 1302, 1042, 1303, 1304, 1305, 1307, 1216, 186, 1308, 1309, 418, 1312, 1318, 1313, 1314, 186, 1042, 161, 1315, 1305, 1316, 304, 1317, 1319, 1320, 1321, 1322, 1323, 1325, 1330, 1333, 1326, 1327, 1328, 1329, 1208, 417, 1331, 311, 213, 1332, 1334, 1336, 1042, 1337, 1335, 161, 1093, 161, 1338, 1217, 1340, 1342, 1343, 1342, 161, 1344, 1345, 1346, 161, 1341, 161, 706, 161, 1341, 1347, 1349, 1352, 311, 1357, 1359, 161, 161, 161, 1350, 1026, 1351, 1353, 1356, 1309, 1354, 1355, 1042, 161, 1303, 161, 1358, 1361, 1366, 556, 1362, 1364, 161, 1363, 1365, 1367, 1369, 1373, 1375, 1343, 1377, 1370, 440, 1371, 417, 1372, 1374, 418, 161, 185, 1376, 418, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1042, 161, 715, 161, 1386, 161, 301, 679, 1389, 1044, 1390, 1391, 1392, 1393, 1395, 1396, 1404, 1397, 1402, 1398, 1399, 1400, 186, 1401, 1403, 1405, 1406, 1407, 1208, 1042, 1408, 435, 186, 1410, 1411, 1414, 1412, 1413, 1415, 186, 1417, 1428, 1429, 161, 1433, 1418, 1419, 161, 1422, 1420, 1421, 161, 1423, 1189, 1427, 1424, 1425, 1257, 1426, 1305, 1128, 1430, 1431, 1432, 1434, 1435, 1438, 1436, 213, 213, 1437, 679, 301, 1109, 301, 1440, 301, 304, 1441, 1442, 1443, 1444, 1445, 1446, 161, 468, 1448, 1466, 1468, 1491, 295, 1511, 1516, 1525, 1540, 1546, 1548, 440, 1574, 1588, 1601, 1605, 1610, 1639, 1449, 304, 161, 296, 1450, 1453, 161, 1464, 635, 161, 1451, 1126, 1452, 414, 200, 207, 1454, 1460, 1462, 1462, 161, 1463, 1455, 1456, 1457, 1458, 1459, 1461, 421, 421, 222, 418, 1465, 1421, 186, 1042, 1467, 421, 161, 1469, 1471, 1475, 311, 1487, 161, 161, 296, 414, 1470, 161, 1472, 1473, 1190, 1474, 1476, 161, 1479, 1481, 1483, 1477, 186, 1478, 161, 1092, 304, 1480, 1482, 1484, 1485, 750, 1486, 435, 161, 1488, 1489, 1490, 1492, 1495, 1502, 350, 1505, 1508, 1493, 1494, 1496, 161, 1499, 1500, 1371, 1501, 1497, 1498, 161, 1498, 418, 425, 161, 1503, 1504, 1506, 1507, 1509, 1510, 1512, 1513, 1515, 1514, 413, 355, 1517, 414, 1520, 161, 1518, 1519, 1521, 1522, 1523, 1524, 1526, 1527, 1531, 1535, 1528, 1529, 1530, 1532, 1533, 1534, 1536, 1539, 1537, 1421, 1538, 1042, 1303, 1541, 1543, 1542, 1544, 1545, 418, 417, 222, 1547, 1060, 161, 414, 1549, 1570, 1550, 1560, 1564, 1567, 161, 1551, 1552, 1553, 1554, 1556, 1555, 1557, 1558, 1559, 1426, 1482, 304, 1561, 1336, 1042, 1562, 1060, 437, 1563, 1565, 1566, 1150, 1092, 161, 1568, 1216, 1569, 1042, 1571, 1572, 1573, 437, 304, 1575, 1365, 1584, 1587, 1576, 1342, 1581, 1577, 1578, 1579, 1580, 304, 1093, 1582, 1583, 1585, 186, 1586, 556, 1092, 304, 1149, 1425, 1042, 519, 1589, 1591, 1596, 1590, 1592, 1593, 1594, 1595, 161, 1597, 1599, 350, 1598, 161, 161, 1498, 1600, 350, 1602, 1603, 1604, 1606, 1607, 1608, 1609, 161, 1611, 1619, 1631, 1636, 1612, 1613, 1615, 1614, 1042, 1616, 1617, 1618, 936, 161, 1620, 1624, 1621, 468, 1622, 1623, 1134, 936, 1625, 1627, 1626, 1189, 463, 1628, 1629, 1630, 1042, 161, 715, 414, 161, 304, 161, 1632, 1421, 1633, 1042, 1634, 161, 715, 161, 161, 1635, 1637, 1638, 213, 1121, 1640, 1642, 1644, 1658, 1643, 414, 418, 430, 1087, 222, 1645, 1392, 1646, 1652, 1654, 1647, 1648, 1649, 1650, 1651, 1653, 1655, 435, 1657, 1656, 1659, 1663, 1660, 1661, 1662, 1664, 1666, 1669, 1700, 414, 1706, 1707, 1719, 1723, 1667, 161, 1668, 1670, 1671, 1677, 1690, 1694, 1697, 1672, 1673, 186, 1674, 161, 556, 1675, 1676, 1507, 161, 1678, 1681, 1317, 1685, 1679, 1680, 1682, 1683, 1684, 1425, 1156, 1686, 1687, 1042, 1688, 1689, 418, 304, 420, 1691, 1692, 1693, 186, 417, 430, 1695, 1696, 1698, 1699, 1701, 1702, 1703, 1704, 1705, 350, 1708, 1709, 1710, 1716, 1711, 1712, 1713, 1714, 1715, 1717, 1718, 1080, 1720, 161, 1721, 1722, 1724, 418, 1725, 1726, 1728, 1730, 1731, 1734, 1736, 1738, 1727, 1729, 1732, 1733, 1735, 1737, 1717, 1739, 1740, 1742, 1751, 1755, 1761, 1771, 295, 1776, 1779, 1794, 1808, 1811, 1828, 1743, 1745, 1093, 1748, 1749, 1744, 1746, 1747, 161, 1087, 1750, 1752, 1753, 1754, 161, 161, 1042, 1756, 1758, 161, 161, 1757, 161, 1759, 1760, 1054, 750, 1060, 1198, 1762, 1763, 1767, 409, 1764, 1765, 1766, 1768, 1769, 1770, 296, 418, 1332, 750, 1772, 1418, 1773, 161, 463, 1774, 1775, 1208, 417, 1426, 1777, 414, 1778, 161, 161, 1780, 1782, 1788, 1781, 161, 1783, 1784, 1785, 1786, 1787, 1789, 161, 1790, 161, 1791, 1792, 161, 161, 1793, 1795, 1796, 1799, 1807, 1017, 1047, 1087, 1797, 1798, 1044, 1421, 161, 1042, 1303, 161, 1800, 1806, 1801, 1802, 1803, 1804, 1805, 1809, 1810, 1812, 1814, 1815, 1816, 1824, 418, 1825, 1813, 161, 519, 301, 519, 296, 1817, 1820, 1823, 1818, 1173, 1819, 1143, 418, 1821, 1822, 301, 1341, 1826, 1827, 1829, 1830, 1831, 1459, 1833, 1834, 1836, 1840, 1848, 1851, 1852, 1853, 1859, 301, 1860, 1060, 1097, 350, 1835, 161, 418, 1837, 1838, 1839, 1012, 1841, 1842, 161, 1843, 1845, 1844, 1846, 1847, 1849, 186, 1850, 418, 1854, 414, 1855, 1413, 1856, 1857, 1858, 418, 1042, 1861, 301, 1862, 1864, 1866, 1868, 696, 975, 1869, 1872, 1874, 1880, 1590, 1887, 1917, 1919, 1865, 1867, 1425, 1042, 161, 1870, 1871, 1343, 161, 519, 1093, 1873, 1042, 1303, 296, 161, 418, 304, 161, 1875, 1876, 1877, 161, 1878, 1879, 350, 161, 1881, 161, 1882, 1884, 1885, 1883, 213, 304, 1886, 414, 1888, 1891, 161, 1302, 1894, 1898, 1900, 1901, 1905, 1906, 1911, 977, 1914, 161, 1889, 1890, 414, 1892, 1893, 1823, 1895, 1897, 1896, 1899, 740, 1902, 420, 1903, 1904, 1907, 1909, 1908, 1910, 1657, 1912, 1913, 1915, 1916, 750, 1054, 418, 301, 1198, 1918, 1920, 414, 1922, 1923, 1921, 161, 1924, 1926, 1928, 1824, 1930, 1932, 1933, 1087, 1343, 1936, 301, 186, 1929, 1931, 161, 440, 1343, 1026, 519, 1017, 947, 1093, 301, 1934, 1935, 161, 1937, 418, 1938, 414, 1939, 1941, 1960, 1991, 2012, 2022, 2063, 2075, 2089, 2109, 2110, 2172, 2190, 2192, 2200, 2215, 2219, 2226, 2230, 2281, 2286, 2295, 2297, 2300, 1940, 1942, 1944, 1945, 1946, 1948, 1951, 1952, 1955, 1957, 1959, 1943, 1097, 430, 426, 418, 1458, 519, 1500, 1947, 1868, 161, 414, 161, 161, 1949, 1950, 161, 417, 304, 1953, 414, 186, 1954, 161, 556, 350, 1956, 445, 1459, 1958, 1961, 1972, 1979, 1982, 1985, 1962, 1963, 1967, 1968, 1969, 1011, 1964, 1965, 1966, 1059, 1956, 161, 161, 1341, 484, 1343, 1970, 1971, 161, 350, 1973, 1975, 1974, 1270, 161, 1245, 1026, 1976, 414, 161, 301, 1977, 161, 1177, 161, 1978, 161, 1980, 418, 1981, 1983, 1984, 161, 976, 1981, 1093, 1986, 213, 161, 1987, 1988, 1989, 1990, 1992, 2000, 2001, 2004, 2005, 301, 1993, 1996, 1997, 1998, 1994, 1995, 161, 1999, 2002, 2003, 468, 161, 301, 1824, 2006, 2007, 161, 2011, 295, 350, 304, 2008, 2009, 2010, 1093, 1459, 2013, 2014, 465, 2017, 2018, 519, 301, 301, 304, 161, 161, 161, 2015, 706, 2016, 1343, 417, 2019, 2021, 2020, 304, 409, 1938, 1059, 2023, 2024, 2025, 2026, 2029, 2034, 1343, 1500, 161, 2038, 2039, 2041, 2048, 2050, 2051, 161, 414, 301, 161, 301, 2027, 2028, 2030, 1093, 2031, 2032, 2033, 161, 161, 1590, 161, 2035, 2036, 2037, 161, 2040, 2042, 301, 2044, 161, 2043, 2045, 1342, 2046, 2047, 161, 2049, 161, 301, 301, 414, 2052, 2058, 2059, 2053, 2054, 2055, 2056, 2057, 1060, 161, 301, 414, 2060, 2061, 2062, 2064, 2068, 2070, 2072, 2065, 519, 2066, 2067, 2069, 2071, 186, 519, 519, 2073, 2074, 161, 414, 161, 301, 161, 2076, 2077, 2079, 2082, 2083, 1173, 2078, 2080, 2081, 2084, 186, 2085, 2086, 2087, 2088, 414, 2090, 2091, 2095, 301, 2097, 679, 2099, 304, 2101, 1498, 2103, 304, 2105, 301, 304, 161, 301, 301, 1432, 2092, 2093, 1093, 2094, 161, 2096, 301, 2098, 1765, 161, 350, 2100, 2102, 2104, 161, 161, 2106, 301, 1482, 301, 2107, 2108, 414, 2111, 2112, 2118, 2120, 2122, 2141, 2144, 2153, 977, 2154, 2157, 2158, 2159, 417, 2170, 2171, 2113, 2114, 2115, 161, 186, 2116, 635, 2117, 304, 2119, 213, 2121, 1351, 1028, 2123, 2128, 2129, 2131, 2135, 2138, 2139, 2140, 1334, 2124, 2125, 628, 2126, 1189, 161, 1400, 556, 161, 2127, 161, 2130, 414, 161, 161, 213, 2132, 161, 2134, 1609, 1818, 2133, 2136, 161, 2137, 161, 161, 414, 350, 1412, 304, 161, 635, 2142, 2143, 628, 295, 295, 1400, 1425, 2145, 2148, 2149, 2151, 2146, 628, 2147, 295, 161, 1480, 2150, 2152, 418, 1126, 2130, 1437, 301, 1310, 2155, 161, 2156, 186, 1042, 161, 1150, 1401, 161, 186, 975, 2160, 2163, 2165, 933, 2167, 2168, 933, 2161, 2162, 2164, 2166, 2169, 418, 1116, 414, 1121, 161, 414, 161, 417, 2173, 2178, 2184, 2186, 1843, 161, 301, 2174, 2175, 2177, 222, 1343, 302, 2176, 484, 484, 301, 1862, 2179, 2183, 1862, 2180, 2181, 2182, 2185, 2187, 2189, 350, 2188, 1093, 304, 2191, 161, 350, 301, 161, 2193, 679, 301, 2197, 2198, 161, 161, 2194, 301, 519, 1924, 161, 2195, 2196, 161, 2199, 2201, 2202, 2204, 2214, 161, 301, 1011, 301, 2203, 2205, 418, 2211, 512, 2206, 2207, 2208, 2209, 2210, 1284, 2212, 2213, 2216, 2217, 2218, 2220, 2222, 2221, 161, 2223, 977, 2224, 2225, 2160, 933, 2227, 2228, 417, 933, 2160, 161, 2229, 409, 1011, 2231, 2239, 2255, 2260, 1371, 2263, 2273, 2277, 2232, 2235, 161, 2237, 2233, 2234, 2236, 2238, 161, 936, 2240, 2244, 161, 2241, 2242, 2243, 1334, 1437, 186, 2245, 1343, 2246, 161, 2247, 2248, 2253, 2249, 2250, 2251, 2252, 1093, 2254, 296, 556, 1198, 2256, 301, 2258, 2257, 679, 161, 2259, 409, 304, 304, 2261, 301, 2262, 295, 2264, 2268, 1740, 2271, 161, 2265, 2266, 2267, 2269, 2270, 2272, 161, 2274, 2275, 2276, 161, 1415, 2278, 2279, 2280, 2181, 1161, 2282, 2283, 2284, 2285, 2287, 2291, 2294, 1143, 161, 161, 2288, 2289, 519, 2290, 161, 1822, 161, 161, 296, 519, 2292, 161, 161, 161, 2293, 484, 519, 1996, 417, 301, 296, 2181, 1980, 1342, 519, 2296, 1126, 2298, 2299, 161, 161, 301, 301, 418, 1750, 706, 2301, 2302, 2303, 2304, 2305, 2306, 2308, 2334, 2361, 2381, 2383, 2392, 2402, 2413, 2421, 2436, 2439, 2448, 2450, 2454, 2455, 2309, 2310, 2311, 161, 295, 2314, 2317, 2319, 2325, 2326, 2329, 301, 2332, 161, 2312, 2313, 2315, 1813, 2316, 2318, 161, 161, 304, 2320, 2321, 2322, 161, 2323, 2324, 418, 311, 2327, 161, 2328, 686, 304, 2330, 2331, 1384, 2333, 418, 2335, 2341, 2343, 295, 1363, 2354, 2358, 2336, 2337, 2338, 2339, 2340, 2342, 414, 161, 161, 213, 161, 1216, 2344, 2346, 2348, 2351, 2345, 2347, 463, 936, 2349, 2350, 2352, 2353, 484, 2355, 2356, 161, 2357, 161, 1250, 161, 414, 2359, 2360, 1573, 440, 161, 2362, 1028, 2363, 2364, 2368, 2373, 2377, 161, 161, 2380, 1813, 161, 1042, 1421, 2365, 2366, 161, 186, 1303, 1223, 161, 2367, 2369, 1343, 161, 417, 2370, 304, 2371, 414, 161, 2372, 161, 414, 161, 161, 161, 2374, 2375, 2376, 2378, 2379, 295, 2382, 1421, 2384, 2385, 1036, 2386, 2388, 2390, 2391, 1421, 161, 161, 418, 350, 161, 2387, 161, 161, 2389, 161, 418, 161, 2393, 2394, 2395, 2397, 2400, 161, 2396, 2398, 2399, 161, 161, 1459, 2401, 2403, 2407, 2404, 2405, 213, 2406, 2408, 2409, 1415, 2410, 2411, 2412, 2414, 161, 2415, 2416, 161, 2418, 1744, 301, 435, 161, 2024, 2417, 2213, 414, 2419, 161, 2420, 2422, 186, 2423, 2431, 1563, 161, 2435, 2424, 2429, 2425, 2426, 2427, 2428, 2430, 2432, 2433, 2434, 304, 1327, 426, 425, 2437, 304, 2438, 2440, 1109, 295, 2441, 2443, 2442, 2444, 1181, 161, 2445, 2446, 2447, 2449, 421, 2451, 2452, 1383, 2453, 213, 556, 414, 350, 161, 2456, 2500, 2505, 2545, 2564, 2566, 2595, 2597, 2603, 2608, 2612, 2644, 2656, 2664, 2749, 2750, 1998, 2751, 2753, 2771, 414, 2457, 2458, 2465, 2470, 2471, 426, 2473, 2480, 1208, 2481, 2486, 2490, 2495, 301, 435, 304, 2459, 2464, 2460, 2461, 2462, 2463, 1862, 350, 2466, 2467, 2468, 2469, 1037, 161, 1604, 2472, 2474, 2475, 2053, 2476, 2477, 2478, 1405, 161, 414, 1093, 2479, 2482, 2483, 2484, 2485, 2487, 2488, 2489, 161, 2491, 2492, 414, 1787, 2493, 2494, 2496, 2497, 2498, 161, 2499, 2501, 2502, 1303, 161, 1590, 1458, 1044, 2503, 1341, 2504, 2367, 1938, 186, 1042, 1343, 161, 2506, 2509, 2513, 2515, 2517, 2540, 2542, 304, 2507, 301, 2508, 2510, 161, 161, 1012, 2511, 1343, 2512, 417, 418, 1733, 417, 1060, 2514, 161, 2516, 295, 2518, 2520, 2527, 2528, 414, 161, 2535, 2519, 161, 161, 414, 2521, 161, 2522, 2524, 2525, 2094, 2526, 161, 2094, 2523, 161, 421, 414, 161, 186, 2529, 2531, 2530, 2532, 2533, 2534, 2536, 2537, 2538, 2539, 2541, 414, 2543, 2544, 2546, 1778, 2549, 2551, 706, 2562, 161, 2547, 2548, 2550, 2552, 2553, 2554, 2555, 2560, 2556, 161, 2557, 2558, 2559, 2561, 1093, 350, 2563, 2565, 1896, 1542, 2567, 2569, 2578, 2584, 2588, 2589, 2591, 2593, 426, 2568, 2570, 2577, 2050, 414, 161, 1050, 2571, 2572, 1500, 2574, 161, 2575, 418, 161, 1097, 301, 1793, 2573, 1990, 301, 414, 222, 161, 161, 2576, 161, 186, 1343, 161, 161, 1367, 1060, 301, 301, 2579, 161, 2580, 304, 2581, 2582, 2583, 2585, 2586, 1876, 1093, 161, 2587, 161, 161, 418, 295, 301, 301, 2094, 304, 2590, 161, 296, 213, 2592, 2594, 519, 252, 1367, 1573, 2596, 1087, 301, 2598, 222, 2601, 2602, 2599, 161, 2600, 1054, 1693, 2604, 2606, 2605, 2607, 161, 2609, 2610, 440, 2611, 1459, 1459, 414, 2613, 2622, 2627, 2624, 2633, 2639, 1129, 414, 2614, 2618, 2619, 1208, 161, 2615, 2616, 2617, 304, 2474, 161, 2620, 161, 2621, 414, 2623, 2624, 161, 304, 2626, 161, 1058, 304, 2625, 304, 1050, 2628, 301, 161, 301, 2629, 2630, 2631, 2632, 2634, 2638, 2635, 2636, 2637, 1042, 161, 1303, 161, 161, 1400, 186, 2333, 161, 1276, 2640, 161, 2641, 311, 715, 2642, 2643, 2645, 2646, 2648, 2652, 2654, 2647, 2649, 2650, 463, 2651, 2653, 2655, 2272, 2657, 440, 418, 2228, 2659, 2661, 2658, 2660, 1126, 186, 350, 161, 2662, 2663, 414, 186, 1042, 161, 414, 2665, 2672, 2673, 2680, 2692, 2728, 2731, 2733, 2742, 2225, 2744, 2746, 1080, 2666, 161, 2670, 2667, 2668, 2669, 2276, 1121, 2671, 414, 2674, 2675, 2679, 161, 2676, 296, 2678, 2677, 2681, 414, 2682, 2686, 2683, 2684, 2685, 316, 2687, 2688, 2689, 2690, 2691, 2693, 2696, 2701, 2374, 2708, 2714, 2716, 2717, 2720, 2723, 2726, 2694, 2695, 2697, 2698, 2699, 161, 2700, 2702, 706, 2703, 2704, 2705, 2706, 2707, 2709, 2710, 2711, 2712, 2713, 2715, 2718, 2719, 2721, 1310, 1093, 2722, 556, 1128, 2724, 2725, 2727, 2729, 2730, 2732, 2717, 426, 2734, 161, 2678, 2735, 2740, 2678, 161, 2736, 2738, 2737, 2739, 161, 2741, 2390, 1060, 2743, 426, 161, 2745, 418, 2747, 2748, 1365, 161, 2752, 1127, 2651, 2754, 2756, 2760, 1962, 2762, 2763, 2764, 2768, 414, 2755, 2757, 2758, 2759, 2761, 2765, 630, 2766, 556, 2767, 2769, 2770, 2772, 2775, 2773, 2774, 1126, 2776, 750, 222, 2778, 1044, 573, 2779, 2781, 2790, 434, 1824, 2791, 2798, 2801, 2803, 2827, 2908, 2913, 2915, 2782, 2783, 2788, 2784, 2785, 2164, 2786, 2787, 2789, 2469, 161, 2792, 2793, 2794, 2795, 2796, 304, 2797, 2799, 1217, 2800, 2802, 418, 2804, 2809, 2810, 706, 2818, 2819, 2823, 2805, 2806, 2807, 2808, 304, 1415, 2811, 2812, 2814, 2813, 414, 280, 2815, 1787, 2469, 2816, 161, 414, 1121, 2817, 2820, 1198, 2821, 2822, 2824, 2825, 2826, 2828, 2833, 2856, 2872, 2876, 161, 2829, 2830, 2831, 2832, 1212, 2834, 2836, 2838, 2849, 2852, 2040, 2835, 1284, 1400, 186, 1042, 161, 2837, 161, 2839, 2847, 2840, 2841, 2845, 2842, 2843, 2844, 2846, 161, 2848, 2850, 417, 2851, 2853, 2854, 2855, 1426, 1042, 2857, 2859, 2869, 1297, 2858, 1555, 304, 512, 2860, 2862, 2864, 1343, 161, 1107, 2865, 417, 2867, 1161, 1458, 2861, 161, 161, 1608, 2863, 1101, 304, 2866, 2868, 1400, 2870, 1302, 1418, 2871, 1150, 1305, 1335, 2873, 2874, 2875, 186, 1042, 1303, 2877, 2881, 2891, 2892, 2878, 2879, 435, 2880, 2882, 2887, 2890, 2883, 2884, 2885, 1425, 2886, 556, 418, 304, 2888, 2889, 1280, 1688, 2893, 2895, 2902, 2903, 2894, 2896, 2897, 2898, 2899, 2900, 2901, 435, 1425, 1042, 2904, 2905, 2906, 2907, 1189, 417, 301, 2909, 2912, 435, 2910, 2911, 1093, 1037, 2914, 213, 2262, 301, 2917, 2918, 2922, 1343, 2919, 1415, 2920, 2921, 2923, 519, 1823, 296, 1367, 2924, 2941, 2957, 2999, 3008, 3026, 3042, 3064, 3065, 3068, 3073, 3109, 3120, 426, 3137, 3145, 3176, 3186, 3214, 3216, 1609, 3095, 3219, 2925, 2927, 2933, 2935, 2936, 2938, 2940, 1412, 301, 1966, 1966, 301, 2926, 161, 1028, 161, 2928, 2930, 2929, 301, 2931, 2932, 2934, 2937, 161, 222, 301, 1415, 2939, 414, 161, 301, 519, 519, 161, 2942, 2943, 2944, 2950, 2955, 2306, 1778, 1011, 2945, 519, 2946, 2947, 2948, 2949, 1242, 161, 417, 296, 2951, 2952, 2953, 296, 2954, 2956, 2958, 2960, 2964, 2996, 414, 1415, 2272, 2959, 1875, 1787, 715, 2961, 161, 2962, 2963, 2965, 2973, 2977, 2980, 2986, 2992, 635, 2966, 302, 2967, 1938, 2968, 2969, 2970, 2971, 2972, 2974, 1896, 2975, 2976, 2978, 2979, 161, 2981, 2982, 1635, 161, 161, 2983, 2984, 2985, 2479, 161, 2987, 161, 301, 2988, 1198, 1896, 1989, 2989, 2990, 2991, 161, 1804, 2993, 2994, 2995, 161, 2997, 2998, 1101, 3000, 3003, 3005, 440, 3007, 3001, 3002, 301, 1500, 161, 3004, 418, 3006, 161, 1754, 936, 3009, 3010, 3011, 3013, 3014, 3016, 3020, 3022, 2028, 3023, 161, 414, 190, 161, 3012, 3015, 3017, 3018, 3019, 350, 1093, 3021, 161, 161, 161, 296, 161, 161, 3024, 3025, 3027, 161, 1867, 1735, 3032, 1150, 1787, 3028, 3029, 3030, 3031, 161, 2272, 1500, 3033, 3034, 3036, 3037, 350, 161, 3035, 161, 301, 519, 1778, 556, 3038, 519, 3039, 3040, 414, 3041, 161, 3043, 1813, 161, 3045, 161, 3047, 3048, 1223, 3050, 3057, 3059, 3044, 161, 3046, 161, 1228, 350, 3049, 2387, 304, 3051, 161, 3052, 3053, 1150, 3054, 3055, 3056, 161, 3058, 161, 3060, 3062, 3061, 3063, 3066, 1640, 418, 161, 3067, 161, 3069, 3071, 1595, 418, 1938, 418, 1500, 3070, 301, 1498, 161, 3072, 301, 161, 1981, 414, 3074, 3082, 3086, 3089, 3098, 3099, 3104, 1680, 3106, 1609, 1060, 3075, 3076, 3077, 1321, 3080, 161, 161, 1868, 3078, 468, 3079, 3081, 161, 3083, 429, 429, 3084, 3085, 3087, 3088, 161, 3090, 3091, 161, 161, 3092, 3095, 3096, 3093, 3094, 161, 3097, 873, 301, 414, 295, 519, 3100, 3101, 3103, 3102, 1823, 1208, 3105, 296, 3107, 740, 1042, 3108, 3110, 350, 3111, 3113, 3114, 3115, 161, 3118, 2050, 3112, 435, 161, 417, 2626, 161, 3116, 1036, 3002, 161, 3117, 3119, 3121, 3122, 3129, 301, 3131, 3135, 3123, 3124, 3125, 3126, 3127, 3128, 3130, 417, 679, 1180, 161, 3132, 3133, 3134, 1208, 3136, 435, 1208, 1042, 3138, 3139, 3141, 519, 3140, 3142, 3143, 3144, 3146, 3154, 3161, 3165, 3173, 161, 1862, 3147, 3150, 3151, 3152, 161, 3148, 3149, 186, 715, 1354, 1093, 3153, 3155, 3156, 2038, 3158, 3159, 1223, 2038, 3160, 161, 161, 3157, 3162, 3163, 3164, 3166, 421, 1044, 519, 3167, 1223, 417, 3168, 3169, 3170, 1042, 3171, 161, 3172, 3174, 3012, 161, 3175, 3025, 161, 213, 3102, 628, 3152, 3177, 3182, 3178, 3179, 161, 3180, 3181, 302, 252, 2596, 3183, 3184, 3185, 414, 3187, 3190, 3198, 3199, 3200, 1343, 519, 1913, 2225, 818, 3213, 161, 1851, 3188, 3189, 1822, 301, 3191, 3192, 414, 414, 3193, 3197, 161, 3194, 3195, 3196, 435, 414, 2846, 2340, 1822, 3201, 161, 3205, 301, 3209, 3211, 3202, 2610, 3204, 3203, 3206, 3207, 3208, 414, 3210, 3212, 414, 161, 302, 213, 161, 301, 2939, 3215, 1778, 3217, 1542, 161, 1223, 161, 519, 3218, 3220, 1223, 161, 3221, 3224, 3228, 3240, 3253, 1161, 3254, 3277, 3279, 3283, 3289, 3292, 3305, 3349, 3377, 3379, 414, 161, 3222, 3223, 519, 161, 519, 413, 519, 161, 3225, 1640, 3227, 1608, 161, 435, 1608, 3226, 3229, 3234, 3239, 3230, 3232, 1470, 1297, 3231, 437, 417, 3233, 3235, 3236, 161, 3237, 3238, 1058, 161, 573, 3241, 3242, 3252, 3243, 3245, 3247, 3251, 3244, 3246, 3248, 161, 3249, 3250, 1345, 2784, 3255, 3262, 3264, 3266, 3267, 3268, 3270, 1823, 3271, 3273, 2382, 3275, 414, 3256, 311, 2094, 3257, 3258, 3259, 3260, 3261, 3263, 2161, 1363, 161, 2270, 1011, 3265, 1893, 1893, 213, 161, 161, 185, 3269, 1600, 420, 1028, 161, 161, 1011, 1050, 418, 740, 1823, 3272, 418, 3274, 1640, 3276, 186, 1101, 1722, 304, 3278, 2050, 2272, 3280, 3281, 1109, 161, 3153, 3282, 3284, 3286, 3287, 161, 3285, 933, 350, 161, 3288, 296, 3290, 3291, 3293, 3296, 3297, 3300, 3304, 3294, 3295, 1305, 161, 161, 2596, 3298, 3299, 3301, 3303, 3302, 1334, 161, 3281, 418, 185, 3306, 3315, 3316, 419, 3328, 3331, 1343, 3338, 3344, 3348, 161, 3307, 3308, 3314, 3309, 3310, 3311, 414, 3312, 161, 3313, 1208, 1156, 161, 3317, 3318, 3322, 3327, 1093, 3319, 3320, 2880, 3321, 161, 414, 556, 3323, 3324, 556, 418, 3325, 3326, 3329, 3330, 3332, 3333, 3334, 3335, 3336, 186, 1042, 161, 3337, 304, 1054, 3339, 3343, 3340, 3341, 3342, 1425, 1091, 3345, 1044, 3346, 1425, 1042, 1334, 1400, 3347, 1042, 821, 1143, 3350, 3351, 3353, 418, 304, 3356, 3364, 3375, 1470, 1823, 2376, 3352, 316, 2931, 3354, 3355, 3357, 3360, 3362, 3358, 3359, 301, 3361, 3363, 3365, 186, 3366, 161, 2683, 3367, 3371, 161, 3374, 3368, 3369, 3370, 1426, 2479, 161, 3372, 3373, 3376, 304, 3378, 437, 295, 1458, 3380, 3384, 3381, 3382, 3383, 414, 3386, 3387, 3392, 3394, 3404, 3406, 3410, 3415, 1913, 3422, 3426, 3433, 3470, 3388, 3391, 161, 3389, 3390, 440, 519, 1542, 417, 3393, 161, 161, 3395, 3396, 3400, 3403, 304, 1093, 2196, 1363, 2489, 3397, 3398, 161, 440, 3399, 3401, 418, 3402, 1054, 1393, 3405, 1797, 1343, 3407, 3408, 3409, 417, 3411, 213, 3412, 161, 3413, 161, 3414, 3416, 3417, 3418, 3419, 3420, 3421, 3423, 3424, 161, 302, 161, 3425, 3427, 1458, 3428, 1287, 414, 3429, 3431, 3430, 936, 1128, 3432, 1126, 311, 161, 3434, 3444, 3454, 3458, 3462, 1715, 1609, 3435, 3436, 3440, 1534, 3442, 635, 3437, 3438, 1425, 1042, 1303, 3439, 1042, 161, 3441, 3443, 3445, 3448, 3449, 3452, 350, 3446, 3447, 3450, 3451, 1579, 186, 1042, 161, 3453, 3194, 1608, 3455, 301, 1284, 3456, 3457, 3459, 3460, 3461, 3463, 3466, 3464, 3465, 186, 1586, 161, 3467, 3468, 3469, 430, 1640, 1640, 628, 3472, 3477, 3485, 3500, 3502, 3507, 3524, 1223, 3526, 3528, 3530, 3532, 3536, 3542, 3566, 2750, 3605, 3608, 3609, 3473, 418, 1474, 3475, 418, 3474, 3476, 418, 161, 161, 3478, 3087, 426, 3480, 3479, 3481, 3482, 3483, 3484, 1042, 161, 3486, 3487, 3489, 3498, 1317, 161, 3488, 662, 1150, 3490, 3491, 3493, 3492, 186, 3494, 3496, 161, 556, 3495, 3497, 1458, 161, 161, 3499, 1498, 161, 3501, 3503, 3504, 3505, 3506, 3508, 1107, 3511, 304, 3513, 3516, 3519, 1778, 3509, 161, 3510, 3512, 3514, 3515, 3517, 3518, 3520, 3521, 414, 161, 161, 3522, 161, 414, 418, 3523, 161, 1060, 161, 3525, 3527, 296, 161, 161, 3529, 1372, 3531, 421, 2280, 3533, 161, 468, 3535, 3534, 414, 1198, 161, 161, 3537, 3538, 465, 3541, 161, 3539, 3540, 3543, 3546, 3547, 3548, 3549, 936, 3552, 1107, 3556, 3544, 1363, 3545, 3550, 213, 3551, 3553, 3554, 3555, 3557, 3560, 418, 3562, 435, 161, 3558, 3559, 161, 435, 3561, 414, 161, 190, 3563, 1956, 3564, 3565, 418, 3567, 3579, 3580, 1474, 3604, 3568, 3573, 715, 3569, 3570, 3571, 3572, 1283, 463, 3574, 3575, 161, 3576, 3578, 3577, 750, 161, 161, 1515, 1212, 1425, 1042, 161, 2276, 3581, 3588, 3592, 3598, 3601, 3582, 3583, 3584, 3585, 3586, 3587, 3589, 3590, 3591, 3593, 3596, 3594, 186, 3595, 418, 3597, 1515, 3599, 3600, 302, 936, 161, 3602, 1128, 3603, 3606, 296, 3607, 1359, 418, 3610, 3612, 3624, 3638, 3644, 2750, 3613, 3616, 3621, 3623, 3614, 3615, 3617, 2111, 3620, 3618, 3619, 304, 3622, 1093, 301, 161, 3625, 3629, 3632, 161, 161, 296, 3626, 3627, 3628, 161, 1797, 3630, 161, 1198, 3631, 1042, 3633, 3634, 3013, 3635, 3636, 1280, 1060, 435, 350, 3637, 161, 417, 3639, 161, 3640, 3641, 161, 3642, 1097, 213, 437, 161, 1949, 301, 3643, 301, 2800, 3645, 1470, 161, 3646, 1216, 186, 1042, 161, 3648, 3652, 3653, 3654, 409, 2309, 2776, 3655, 3660, 3661, 3662, 3663, 161, 3160, 3649, 350, 3651, 1093, 3650, 2040, 161, 2420, 3326, 161, 629, 3656, 3657, 3658, 3659, 213, 1756, 1042, 1217, 304, 161, 556, 975, 161, 3664, 161, 3665, 3667, 3267, 3669, 3670, 417, 3671, 3668, 1343, 161, 628, 417, 3673, 1824, 1466, 3678, 3681, 3682, 184, 750, 3684, 750, 3685, 3686, 519, 3674, 1959, 414, 3675, 3676, 3677, 3679, 3680, 1093, 2028, 3683, 1042, 296, 1161, 161, 706, 1010, 3688, 3689, 3064, 3690, 3694, 3695, 433, 3697, 3699, 3703, 3705, 161, 2362, 161, 434, 3691, 1345, 3692, 3693, 3696, 3698, 1393, 1459, 3700, 3701, 3702, 3704, 304, 3706, 3707, 8, 4271, 4276, 2463, 10, 4831, 4832, 11, 5124, 12, 5363, 13, 5802, 14, 6258, 16, 6577, 6578, 3708, 3709, 3710, 3711, 3712, 3713, 3738, 3789, 3814, 3816, 3819, 3834, 3839, 301, 3854, 3862, 3950, 3960, 161, 4004, 9, 4142, 4187, 4220, 4246, 4249, 4250, 4253, 4264, 186, 304, 519, 1093, 161, 3714, 3715, 3315, 3716, 3718, 1156, 3002, 161, 3726, 2191, 1121, 161, 161, 3717, 161, 706, 161, 1101, 3719, 996, 414, 3720, 3722, 1126, 409, 3721, 3723, 1981, 161, 3724, 3725, 1302, 421, 3727, 3729, 3731, 1657, 3440, 1042, 3732, 3735, 3728, 1990, 213, 3730, 3733, 3734, 304, 161, 1343, 350, 3736, 3737, 3739, 3741, 3744, 408, 3751, 3784, 3785, 414, 3740, 3742, 936, 414, 161, 3743, 185, 3745, 3749, 3750, 295, 161, 1351, 434, 414, 3746, 161, 295, 3747, 3748, 414, 2199, 161, 1372, 2142, 414, 2111, 3752, 3754, 3755, 3757, 3763, 1876, 3765, 3766, 936, 3767, 3771, 3772, 3780, 3781, 3782, 1161, 445, 3753, 1893, 933, 3756, 3758, 3760, 3759, 3761, 3762, 3764, 556, 185, 3768, 3769, 3770, 1412, 2196, 2195, 3773, 3774, 3777, 3775, 304, 3776, 296, 1042, 3778, 3779, 414, 1093, 161, 3783, 1740, 295, 1060, 1036, 3786, 1126, 3787, 3788, 414, 3790, 3796, 3798, 3800, 3804, 3806, 1474, 3808, 1756, 3813, 1173, 161, 1270, 3791, 3795, 3792, 3793, 3794, 3797, 161, 3799, 556, 2963, 3801, 161, 3802, 3803, 3805, 3441, 440, 2040, 161, 350, 161, 161, 3807, 418, 3809, 3810, 3811, 3812, 1850, 296, 296, 161, 3815, 1097, 301, 213, 3817, 3818, 1228, 414, 213, 417, 3820, 3821, 3826, 3827, 440, 1343, 3828, 3830, 3832, 1639, 1093, 3002, 1938, 3822, 3823, 3825, 161, 161, 161, 1189, 350, 414, 3824, 161, 222, 350, 740, 2272, 1640, 3829, 1793, 3831, 1757, 3833, 3835, 3837, 161, 161, 1851, 3838, 3836, 161, 296, 161, 161, 3840, 161, 3849, 161, 3851, 3852, 3853, 1500, 3841, 3842, 3844, 3848, 161, 161, 161, 161, 161, 1050, 161, 3843, 556, 3845, 304, 3846, 3847, 161, 161, 350, 3850, 1093, 161, 295, 161, 414, 414, 1109, 440, 3855, 3856, 1042, 3859, 3860, 414, 1449, 3861, 161, 161, 3857, 161, 1639, 414, 161, 750, 3858, 161, 1403, 213, 3863, 3870, 3872, 3876, 3887, 3893, 3894, 3899, 3900, 3926, 3930, 3934, 3935, 3938, 3949, 3864, 3865, 161, 161, 3866, 3867, 3868, 3869, 161, 3871, 3704, 3873, 296, 161, 161, 3874, 3875, 1128, 710, 3877, 3881, 1756, 3883, 3885, 2575, 3886, 161, 1793, 3878, 3879, 1934, 3880, 3882, 421, 3884, 304, 1937, 3888, 3889, 161, 3890, 3891, 3892, 608, 3895, 3896, 414, 1059, 161, 3897, 3898, 2272, 3892, 1184, 1847, 2225, 161, 414, 3901, 3904, 3914, 3915, 1343, 3918, 3919, 3771, 3924, 3925, 1060, 1093, 409, 161, 3902, 186, 3002, 304, 3903, 1126, 1126, 161, 3905, 3908, 3910, 161, 161, 301, 3906, 2050, 3907, 3909, 3911, 3912, 3913, 3916, 3917, 1391, 1060, 1092, 1093, 161, 161, 1023, 3920, 3923, 3921, 3922, 3927, 3928, 3929, 161, 3931, 161, 3932, 3933, 3936, 296, 440, 3937, 3939, 414, 3940, 3943, 3947, 1913, 414, 3948, 161, 1824, 3941, 2976, 3942, 161, 3944, 3945, 3946, 1640, 440, 1367, 296, 161, 3951, 161, 3267, 161, 3952, 3953, 3955, 3956, 3959, 1013, 409, 161, 556, 3954, 3917, 414, 519, 3957, 3958, 3961, 3966, 3968, 3971, 3973, 3975, 161, 3981, 519, 3983, 3993, 414, 3998, 3999, 4000, 4001, 4002, 301, 1951, 409, 3962, 3963, 3965, 3964, 161, 3049, 3967, 414, 3969, 3970, 1305, 161, 415, 161, 3469, 1590, 413, 161, 3972, 161, 161, 161, 3974, 1896, 435, 3976, 3977, 3013, 1876, 2162, 3978, 161, 161, 2203, 3979, 3928, 3980, 161, 161, 3982, 1280, 295, 414, 1143, 3544, 3984, 3102, 1042, 161, 3986, 3992, 161, 3985, 161, 161, 3987, 3989, 3988, 3990, 3991, 213, 186, 418, 3994, 3996, 418, 161, 3995, 556, 3997, 161, 296, 418, 2062, 4003, 4005, 161, 4006, 4007, 1284, 161, 161, 4008, 414, 4009, 4017, 4032, 4040, 4043, 4047, 4049, 4053, 4054, 4059, 4066, 4069, 4071, 4082, 4086, 4110, 4114, 408, 3959, 161, 4141, 4010, 4012, 3064, 413, 4013, 4014, 409, 4016, 161, 161, 4011, 1754, 414, 420, 4015, 608, 608, 2539, 414, 4018, 4023, 4027, 4029, 4030, 161, 161, 1037, 4019, 4021, 161, 2285, 1868, 4020, 1060, 4022, 161, 1092, 161, 519, 355, 4024, 161, 161, 1223, 1990, 4025, 414, 161, 1342, 161, 4026, 418, 4028, 414, 1850, 222, 301, 296, 301, 4031, 161, 161, 4033, 161, 301, 4036, 4039, 414, 2449, 4034, 413, 1037, 4035, 213, 4037, 4038, 1198, 1010, 4041, 418, 418, 1028, 4042, 1639, 161, 4044, 4045, 4046, 3888, 161, 2626, 3778, 1895, 3603, 186, 4048, 213, 3603, 1896, 556, 4050, 4052, 1042, 161, 4051, 161, 1459, 2382, 4055, 4056, 4057, 4058, 409, 2111, 1459, 161, 413, 418, 4060, 4061, 4062, 1876, 4063, 1343, 161, 4064, 414, 161, 933, 1060, 3002, 213, 474, 4065, 1938, 4067, 4068, 4070, 1823, 4072, 350, 4074, 4076, 4079, 4081, 161, 1459, 1639, 1161, 4073, 1150, 186, 161, 161, 161, 4075, 2526, 4077, 1640, 2166, 4078, 4080, 1470, 4083, 4084, 1990, 161, 414, 161, 4085, 161, 4087, 4090, 4096, 4106, 161, 414, 4107, 4088, 4089, 1245, 161, 350, 301, 414, 1882, 1862, 301, 161, 818, 4091, 4092, 4093, 222, 4094, 186, 1042, 161, 161, 295, 2050, 161, 4095, 4097, 4099, 301, 4102, 4103, 1093, 4105, 350, 4098, 161, 4100, 2677, 4101, 1949, 414, 4104, 296, 1732, 161, 1459, 1342, 1093, 414, 161, 1962, 4108, 4109, 409, 161, 4111, 1756, 4112, 186, 4113, 414, 161, 4115, 1365, 4119, 4126, 4129, 4132, 4133, 4134, 1604, 4139, 4140, 161, 161, 161, 161, 4116, 4117, 161, 4118, 2340, 1042, 161, 4120, 1727, 4122, 4121, 4123, 161, 4124, 4125, 161, 4127, 4128, 2449, 608, 4130, 1042, 474, 2283, 1026, 4131, 161, 414, 1060, 418, 1757, 161, 4135, 1036, 4138, 161, 2127, 1754, 4136, 4137, 304, 1044, 161, 3954, 2331, 3954, 161, 213, 1754, 4143, 4145, 1109, 4148, 3267, 3302, 4154, 4157, 4161, 4168, 4170, 1459, 4171, 4174, 4180, 4186, 1639, 304, 4144, 161, 4146, 4147, 4149, 161, 4152, 4153, 161, 161, 161, 4150, 4151, 161, 2526, 1757, 1126, 418, 1304, 1823, 4155, 1538, 3198, 4156, 3946, 1240, 4158, 4160, 2197, 161, 4159, 161, 1823, 161, 1851, 608, 4162, 3281, 4163, 4165, 4164, 4166, 161, 1036, 4167, 4169, 2191, 975, 4172, 4173, 1412, 4175, 3267, 1604, 4178, 295, 1343, 1026, 4176, 4177, 4179, 4181, 1818, 4184, 4185, 4182, 4183, 1054, 418, 1760, 418, 1150, 161, 418, 1036, 161, 414, 4188, 4189, 4193, 4194, 4195, 4200, 4202, 4203, 4204, 1026, 4205, 161, 1851, 4190, 4191, 4192, 1343, 1026, 161, 1425, 1042, 4196, 161, 4199, 4197, 1198, 4198, 161, 4201, 161, 2150, 3643, 1949, 1459, 4206, 4208, 4211, 4214, 418, 1026, 161, 4207, 437, 161, 3858, 4209, 161, 186, 3321, 4210, 417, 1060, 213, 1639, 418, 1744, 1418, 4212, 4213, 161, 161, 1754, 4215, 1042, 414, 161, 3098, 4216, 4219, 4217, 4218, 1913, 1824, 1356, 4221, 4222, 4228, 4230, 4233, 4234, 4236, 1097, 4243, 4244, 301, 161, 1459, 1459, 4223, 4227, 4224, 161, 4225, 4226, 418, 426, 4229, 414, 304, 161, 1757, 4231, 4232, 418, 296, 1343, 161, 4235, 1372, 1351, 4237, 4238, 4242, 295, 429, 3002, 4239, 4240, 696, 295, 4241, 409, 1343, 409, 4245, 161, 4247, 296, 4248, 213, 421, 161, 1297, 4251, 440, 295, 1885, 4252, 414, 1143, 3559, 4254, 4256, 417, 4259, 4260, 4262, 421, 4263, 4255, 161, 4257, 4258, 4203, 161, 679, 1093, 1143, 1093, 4261, 161, 161, 465, 1962, 4265, 4266, 4267, 4269, 4270, 296, 4144, 161, 161, 1962, 161, 4268, 4179, 161, 437, 1367, 1640, 1640, 4272, 1270, 336, 4273, 4274, 161, 4275, 161, 3875, 4277, 4278, 4279, 414, 4280, 4355, 4357, 4383, 4409, 4430, 4440, 4450, 4466, 4483, 4486, 4487, 4563, 4565, 4648, 4651, 4655, 4760, 4775, 4807, 4810, 4821, 4825, 4829, 4281, 4282, 4286, 4288, 1459, 1143, 4289, 4290, 4292, 4294, 4302, 4310, 4312, 4320, 4347, 4354, 296, 874, 4283, 1482, 161, 1459, 4284, 4285, 295, 1459, 161, 186, 4287, 161, 161, 1093, 1042, 418, 3670, 1818, 1036, 4291, 161, 418, 4284, 4293, 418, 161, 556, 409, 4295, 1818, 304, 4299, 556, 4296, 4297, 4298, 4300, 4301, 414, 161, 1421, 4291, 4303, 4304, 4307, 4308, 4309, 161, 1640, 1389, 1962, 556, 4305, 4306, 440, 418, 4311, 161, 304, 213, 418, 161, 161, 4203, 4313, 4315, 4133, 1028, 4317, 161, 4319, 161, 4314, 4316, 4318, 304, 304, 161, 4321, 4323, 4328, 4331, 4333, 4336, 2524, 1823, 4338, 4340, 4343, 4335, 1109, 4322, 1938, 4324, 4327, 4325, 4326, 1126, 933, 426, 1126, 3487, 4329, 426, 4330, 1036, 418, 2970, 161, 4332, 4334, 409, 161, 4337, 4339, 4341, 4342, 161, 4344, 1060, 414, 4345, 4346, 4348, 4353, 161, 4349, 4350, 161, 4351, 4352, 4356, 874, 414, 355, 161, 4358, 301, 4360, 4363, 4367, 434, 4381, 1892, 417, 4382, 304, 4359, 2094, 4361, 161, 4362, 1143, 4364, 295, 4365, 435, 4366, 161, 4368, 3267, 1604, 4371, 4372, 1371, 4374, 4375, 4377, 1459, 4380, 1050, 161, 4369, 4370, 304, 1343, 161, 4373, 4376, 4378, 740, 4379, 185, 1459, 1101, 1886, 414, 1143, 4384, 4385, 4388, 3267, 1609, 4389, 1604, 4392, 4397, 4398, 4399, 4403, 4407, 4408, 1143, 186, 4386, 161, 161, 4387, 295, 435, 2770, 716, 4390, 4391, 4393, 296, 4394, 4395, 608, 161, 4396, 1223, 1107, 304, 301, 1092, 468, 4400, 4402, 4401, 421, 4198, 2430, 4404, 2196, 1150, 4406, 4405, 1143, 1640, 414, 4410, 4411, 4413, 4414, 1150, 4415, 3995, 4416, 4419, 4420, 3438, 4421, 4424, 4426, 2082, 295, 4412, 3967, 1459, 1999, 1042, 295, 1609, 3729, 186, 161, 161, 427, 295, 409, 296, 1126, 4417, 4418, 414, 4422, 213, 4423, 161, 161, 1372, 161, 440, 418, 4425, 4427, 1198, 161, 4428, 4429, 1640, 4431, 4433, 4436, 414, 4432, 4434, 4435, 161, 4437, 4438, 4439, 4441, 1107, 4442, 4443, 4444, 440, 4447, 1126, 4449, 161, 2024, 161, 161, 1036, 1343, 414, 1426, 186, 1876, 161, 4445, 1042, 4446, 161, 2340, 4313, 4448, 4451, 4456, 3762, 3776, 1036, 4458, 4459, 4462, 2727, 679, 161, 161, 4452, 1425, 4453, 556, 4454, 4455, 296, 414, 435, 4457, 1100, 161, 2225, 1027, 4460, 1530, 4461, 1754, 4463, 4465, 304, 1371, 414, 4464, 2203, 161, 4467, 4468, 4471, 4472, 1270, 4474, 4476, 4477, 4479, 4480, 4481, 4469, 4470, 4473, 4042, 4475, 296, 304, 1343, 1054, 4478, 304, 350, 409, 435, 161, 295, 4364, 161, 4482, 4484, 4485, 1150, 1184, 295, 4488, 4490, 4492, 4493, 4498, 4501, 4507, 1343, 4517, 4518, 4545, 1810, 4547, 4551, 4552, 4558, 4560, 1851, 414, 445, 296, 161, 4489, 1341, 1245, 311, 4491, 4494, 161, 161, 4495, 4496, 4497, 468, 4499, 4500, 1143, 1896, 1823, 4502, 4504, 4505, 4503, 418, 1459, 4506, 161, 4508, 304, 4510, 1150, 4512, 4514, 4516, 1093, 4509, 1421, 1336, 1042, 1750, 4511, 4513, 4515, 2109, 161, 161, 3620, 1810, 414, 4519, 4524, 4525, 4531, 4533, 1343, 4537, 161, 2575, 4164, 4540, 414, 161, 519, 4520, 1470, 161, 1823, 213, 4521, 186, 161, 4523, 161, 295, 4522, 161, 414, 355, 4526, 4529, 161, 1990, 161, 4527, 4528, 4530, 3476, 304, 4532, 519, 1302, 4534, 4536, 161, 4055, 4535, 1851, 161, 161, 4538, 1036, 161, 4539, 3770, 414, 4541, 4543, 4542, 4544, 418, 4546, 1699, 304, 706, 4548, 304, 311, 161, 4549, 186, 4550, 161, 296, 3833, 1228, 213, 418, 414, 186, 4553, 418, 4556, 161, 635, 161, 4554, 4555, 4557, 161, 418, 1850, 161, 161, 301, 4559, 161, 4561, 4562, 1150, 1093, 418, 4564, 716, 1971, 161, 414, 4566, 4571, 4572, 4578, 4584, 4599, 4601, 4604, 4606, 4611, 4616, 4617, 4626, 4628, 4632, 4641, 4644, 4646, 4647, 1242, 186, 1824, 4567, 4568, 1048, 4569, 4570, 418, 474, 2161, 161, 4573, 4574, 4577, 1302, 1042, 440, 678, 4575, 4576, 4579, 4580, 4581, 304, 687, 161, 1823, 161, 435, 301, 4582, 301, 556, 4461, 4583, 1101, 4585, 4590, 413, 1343, 304, 2396, 4596, 3002, 4597, 304, 4586, 4588, 4587, 4589, 161, 4591, 4592, 4593, 4366, 2420, 4594, 4595, 4598, 1287, 2352, 296, 4600, 1143, 301, 1896, 3169, 161, 4602, 1981, 1542, 4603, 4605, 4607, 4608, 4610, 4609, 161, 1949, 161, 161, 4612, 4615, 4613, 418, 4614, 1899, 161, 4618, 4621, 161, 1050, 3098, 161, 4619, 161, 4620, 421, 161, 4622, 4624, 418, 161, 4623, 4625, 296, 3098, 417, 679, 4627, 161, 4629, 418, 4630, 4465, 426, 440, 1343, 1482, 161, 430, 4631, 161, 4633, 4635, 4637, 4639, 1583, 161, 1459, 4634, 4636, 4638, 161, 4640, 4642, 4643, 4645, 4364, 1042, 4649, 4650, 4652, 4653, 4654, 350, 706, 4656, 4659, 4662, 4665, 4666, 4672, 1727, 4689, 4691, 4698, 4703, 4709, 161, 1843, 4728, 4735, 4738, 4755, 3959, 4756, 4759, 4657, 4658, 1121, 4660, 4661, 4663, 295, 4664, 418, 418, 161, 4667, 350, 4670, 4671, 2197, 2102, 4668, 4669, 2837, 414, 2174, 1217, 1026, 4583, 414, 4673, 4674, 4677, 1895, 1842, 161, 418, 4680, 4218, 4681, 295, 161, 1843, 1109, 4684, 1459, 4675, 435, 4676, 2963, 4678, 161, 4679, 417, 161, 426, 161, 4682, 4683, 301, 4685, 418, 4686, 4687, 1459, 4688, 466, 4690, 4692, 4693, 408, 440, 1343, 4696, 4697, 2309, 440, 161, 4694, 4695, 1180, 1180, 4225, 418, 4699, 4701, 2094, 4700, 4702, 296, 295, 161, 418, 4704, 4705, 4708, 161, 519, 161, 161, 4706, 4707, 4710, 3002, 4717, 4719, 4722, 350, 4724, 2453, 4711, 4712, 4713, 161, 4714, 161, 996, 2199, 1126, 4715, 414, 161, 484, 4716, 161, 304, 1217, 161, 4718, 161, 4720, 2472, 4721, 304, 4723, 556, 301, 4725, 425, 4726, 4727, 161, 4729, 4731, 301, 4734, 435, 4730, 161, 418, 1938, 4732, 414, 4733, 3689, 1343, 4736, 418, 425, 4737, 4739, 4742, 4744, 4749, 4750, 4753, 3618, 2678, 4740, 1343, 4741, 4743, 161, 3602, 4745, 4747, 4746, 4748, 161, 2267, 4751, 4177, 4752, 4754, 304, 1109, 4757, 4758, 414, 1184, 3665, 409, 4761, 4762, 4765, 4168, 4767, 4769, 4770, 4772, 355, 2442, 408, 161, 4763, 4764, 686, 519, 1198, 4766, 4768, 1143, 4771, 1387, 295, 418, 350, 161, 706, 161, 212, 4773, 4774, 2847, 414, 4776, 4782, 1145, 4783, 295, 4790, 4795, 4796, 1108, 4806, 2050, 4777, 4780, 4778, 414, 4779, 296, 4781, 414, 4784, 4786, 304, 304, 4787, 1126, 161, 4785, 186, 161, 161, 3250, 414, 1862, 4788, 4789, 4791, 4793, 4792, 3169, 1538, 1042, 161, 4794, 409, 213, 161, 2270, 4797, 4801, 4803, 304, 418, 3227, 161, 4805, 414, 4798, 4800, 161, 4799, 4193, 4350, 418, 161, 417, 4802, 161, 4804, 161, 161, 1851, 161, 414, 2393, 518, 161, 1075, 295, 4808, 4809, 4811, 4813, 4817, 4812, 1093, 186, 4814, 414, 4815, 2840, 1937, 4816, 161, 1563, 418, 161, 296, 4818, 4820, 161, 4819, 1036, 1823, 4822, 440, 421, 4823, 4824, 409, 4826, 161, 417, 185, 4827, 425, 4828, 4830, 519, 468, 1080, 4833, 1223, 1412, 1044, 1223, 1173, 4834, 4836, 4835, 301, 1850, 4837, 4851, 4860, 4876, 4882, 4894, 4895, 1459, 350, 4944, 4945, 4949, 4986, 4988, 5009, 5048, 5057, 5080, 5103, 5116, 5118, 5119, 1962, 4838, 4840, 4845, 161, 4850, 408, 4839, 222, 1754, 4841, 421, 4843, 4842, 4844, 4846, 1060, 4847, 161, 4848, 4849, 4852, 4853, 161, 4854, 161, 296, 1184, 161, 161, 1134, 4855, 1087, 4856, 4857, 4858, 4859, 1458, 4861, 4866, 4867, 4873, 4875, 4862, 1093, 4863, 4864, 4865, 4868, 3267, 1604, 4872, 1962, 1639, 161, 4869, 350, 1042, 4870, 4871, 4874, 4877, 3002, 1042, 2864, 4879, 161, 4880, 296, 4878, 304, 161, 421, 4058, 161, 4881, 4883, 3547, 4884, 4885, 295, 4886, 4890, 4893, 161, 435, 295, 4887, 213, 4888, 4889, 519, 608, 161, 2539, 3954, 4891, 4892, 2837, 304, 296, 161, 213, 1371, 295, 696, 161, 4896, 161, 4897, 4899, 4908, 4909, 1726, 4914, 4916, 4923, 4926, 4928, 4929, 4931, 4932, 4934, 4938, 4940, 4942, 1184, 1609, 161, 1184, 4898, 2003, 414, 4900, 4902, 4903, 4904, 4905, 4907, 161, 161, 4901, 420, 420, 304, 161, 4906, 414, 161, 414, 1563, 492, 350, 350, 420, 4910, 4911, 4913, 4031, 556, 4912, 1145, 4915, 350, 186, 4917, 4919, 4920, 161, 1870, 161, 4918, 350, 4921, 161, 4922, 1459, 740, 4924, 4925, 1459, 4927, 4169, 474, 4634, 3730, 4930, 440, 1101, 1757, 1323, 1198, 4933, 186, 213, 4935, 3527, 4936, 4937, 4939, 213, 4941, 1727, 4943, 419, 418, 355, 4946, 4947, 4948, 4950, 4953, 4956, 1093, 4883, 4959, 1280, 4962, 4983, 608, 4985, 4337, 4951, 161, 4952, 519, 3194, 161, 4954, 161, 4955, 161, 4957, 4958, 4960, 4961, 414, 161, 4963, 4968, 4969, 4970, 1343, 4978, 161, 4979, 4980, 4964, 4965, 4966, 4967, 1640, 1738, 161, 161, 3002, 1739, 421, 4971, 161, 4972, 4976, 414, 295, 4973, 296, 4974, 417, 4975, 4073, 161, 4977, 3002, 4225, 161, 421, 706, 414, 414, 161, 4981, 161, 4982, 4055, 4984, 222, 1962, 418, 161, 3025, 4987, 3512, 4989, 4990, 4991, 4994, 417, 4998, 4999, 5000, 5002, 5006, 5007, 161, 4364, 1042, 161, 355, 161, 706, 4992, 4993, 1829, 1818, 4995, 4996, 304, 1343, 252, 161, 4997, 440, 2538, 161, 161, 213, 5001, 161, 2442, 556, 5003, 5004, 5005, 1895, 3315, 5008, 5010, 5016, 5022, 5024, 5033, 5035, 5039, 5042, 447, 5045, 5047, 5011, 706, 5012, 5013, 5014, 5015, 5017, 5018, 5019, 5020, 5021, 5023, 5025, 5028, 5026, 5027, 5029, 5030, 5031, 5032, 161, 1087, 414, 5034, 5036, 5037, 5038, 161, 2210, 350, 5040, 5041, 5043, 302, 161, 5044, 5046, 5049, 3855, 213, 5050, 5051, 5052, 5053, 5054, 5055, 5056, 5058, 5063, 414, 5070, 5071, 5073, 5074, 2387, 161, 5075, 5059, 5062, 5060, 5061, 3098, 295, 414, 192, 5064, 3103, 5065, 418, 5066, 1343, 474, 5067, 5069, 1639, 161, 1093, 1036, 1640, 5068, 1876, 186, 435, 2191, 5072, 3479, 295, 161, 740, 483, 2837, 213, 304, 5076, 5077, 1042, 1022, 2763, 977, 161, 5078, 5079, 5081, 2362, 5084, 5091, 750, 5092, 5093, 5095, 4607, 1270, 1990, 5098, 5102, 5082, 5083, 5085, 5086, 5087, 5090, 1680, 2081, 1126, 5088, 5089, 161, 161, 4961, 5094, 5096, 5097, 5099, 5100, 435, 4075, 5101, 2199, 2605, 2050, 519, 5104, 5106, 1042, 295, 295, 2225, 5108, 1851, 5105, 414, 161, 1757, 414, 1876, 161, 5107, 161, 161, 5109, 5115, 304, 295, 1028, 161, 161, 5110, 5112, 5111, 213, 1343, 1756, 5113, 5114, 1184, 5117, 1590, 1367, 414, 213, 295, 5120, 295, 5122, 1010, 5123, 5121, 1823, 1640, 5125, 5127, 5128, 3478, 5126, 5129, 5130, 5131, 4218, 1343, 1843, 1012, 5132, 5133, 5234, 5257, 5278, 5326, 5359, 5134, 5140, 5175, 556, 5177, 5178, 5188, 5192, 5193, 5195, 5208, 5209, 5221, 5226, 5229, 5230, 5135, 301, 161, 5136, 1042, 161, 5137, 1042, 5138, 5139, 5141, 414, 161, 5142, 5143, 5147, 5065, 5148, 5151, 5152, 5155, 5156, 5158, 5160, 5165, 3368, 5167, 5172, 161, 5144, 5145, 5146, 1161, 1363, 1143, 1608, 350, 302, 161, 1280, 5149, 350, 5150, 1217, 1143, 5153, 2382, 5154, 5157, 1010, 5159, 420, 1938, 161, 5161, 296, 5164, 5162, 161, 5163, 1208, 304, 1640, 5166, 5168, 3153, 418, 5170, 5169, 1823, 5171, 421, 1036, 430, 5173, 628, 5174, 3922, 5176, 1042, 161, 4133, 5179, 1060, 5187, 304, 5180, 414, 5181, 5182, 5183, 5184, 5185, 5186, 5189, 440, 440, 414, 161, 1474, 5190, 5191, 161, 4305, 161, 418, 1823, 213, 5056, 5194, 1042, 161, 1756, 161, 161, 5196, 5200, 2225, 3267, 5201, 161, 5207, 417, 5197, 5198, 161, 161, 414, 706, 161, 519, 1143, 5199, 3098, 3267, 1042, 1028, 5202, 5205, 213, 161, 161, 161, 5203, 1093, 5204, 5056, 2442, 2955, 1013, 161, 5206, 5210, 5211, 161, 5212, 2213, 5214, 5218, 3438, 2449, 3730, 161, 5213, 414, 3914, 161, 5215, 5216, 5217, 5219, 5220, 5222, 5223, 5225, 161, 5224, 5227, 5228, 4169, 425, 750, 3315, 3670, 5231, 5232, 414, 161, 2537, 161, 1093, 161, 296, 5233, 5235, 5240, 5241, 5243, 161, 5245, 5246, 5247, 5249, 5254, 161, 5255, 5256, 161, 5236, 5239, 161, 1161, 5237, 5238, 1042, 1097, 161, 5242, 2442, 5244, 161, 409, 296, 1876, 161, 5248, 295, 1343, 296, 5250, 186, 5251, 5252, 5253, 1184, 1184, 2191, 2191, 5258, 5263, 5264, 5265, 1093, 5273, 5275, 1109, 350, 5034, 5259, 5260, 5261, 5262, 5242, 5266, 161, 5270, 161, 5272, 5238, 5267, 1042, 213, 1756, 5269, 5268, 5271, 1538, 1042, 161, 161, 161, 5274, 3922, 2191, 3654, 409, 5276, 4461, 5277, 213, 1270, 1903, 5149, 5279, 5286, 556, 1459, 1198, 5287, 5288, 5292, 5315, 5318, 5320, 5321, 5325, 186, 1851, 5280, 5281, 5282, 5285, 3102, 1042, 417, 161, 5283, 5284, 1012, 161, 1842, 295, 1843, 5289, 5290, 5291, 161, 161, 414, 435, 161, 5293, 5309, 2082, 161, 414, 1730, 186, 1609, 3760, 311, 5294, 5295, 5303, 5306, 161, 474, 5296, 5297, 5301, 1885, 5298, 1603, 5299, 5300, 5302, 5304, 5305, 5307, 5308, 1012, 1426, 3098, 5310, 1843, 161, 5311, 5312, 5313, 417, 5314, 161, 5316, 296, 5317, 1112, 5319, 409, 418, 350, 1198, 2864, 3631, 5246, 1161, 1876, 5322, 304, 161, 874, 161, 5324, 933, 5323, 1604, 1459, 5327, 5328, 5330, 5346, 2447, 2191, 5347, 5351, 5355, 5358, 409, 408, 1883, 5223, 5329, 414, 5331, 5332, 200, 200, 180, 5333, 5065, 5337, 5246, 3274, 5338, 5339, 5340, 161, 5344, 350, 161, 161, 3244, 1640, 5334, 1143, 1990, 213, 3169, 5335, 5336, 435, 417, 5341, 5343, 5342, 161, 213, 1343, 5345, 296, 426, 3770, 1143, 161, 1012, 5348, 3510, 5349, 161, 1895, 5350, 5352, 161, 5354, 5353, 435, 3770, 213, 1343, 161, 161, 5356, 5357, 186, 1042, 418, 1297, 1851, 161, 5360, 5361, 5362, 5364, 5365, 5366, 414, 5367, 5377, 5388, 5400, 5414, 2065, 5432, 5443, 5457, 5471, 1851, 5473, 5498, 5513, 5569, 5607, 3998, 5608, 5657, 5680, 5705, 5752, 5756, 5774, 5776, 5799, 5368, 418, 161, 1093, 161, 5369, 3002, 5374, 161, 414, 5370, 1343, 414, 1538, 5371, 1343, 3771, 161, 4300, 5372, 5373, 414, 5375, 2165, 1042, 5376, 1343, 295, 977, 161, 3029, 414, 573, 5378, 5379, 1412, 5382, 5384, 302, 301, 5207, 5385, 5386, 350, 2016, 421, 2237, 5207, 2191, 5380, 3875, 161, 5381, 1060, 1418, 350, 1150, 5383, 5387, 5389, 5392, 5393, 5395, 5399, 5390, 5391, 2238, 3006, 1097, 5394, 5396, 1343, 5397, 5398, 409, 5401, 5402, 222, 5408, 5409, 3670, 5410, 161, 3959, 5411, 5413, 301, 435, 5403, 161, 161, 5404, 5405, 5406, 425, 5407, 1425, 3267, 213, 1036, 414, 5412, 5415, 5417, 4987, 5419, 1042, 304, 430, 295, 5423, 5424, 5425, 5426, 5430, 304, 5416, 5418, 1042, 2442, 295, 5420, 5421, 5422, 418, 1818, 3730, 1042, 3771, 295, 295, 1935, 409, 2313, 418, 5427, 5428, 5429, 2270, 5431, 2270, 295, 5433, 3766, 5436, 5437, 5439, 304, 295, 1044, 1109, 161, 5442, 1829, 5434, 161, 5435, 161, 2017, 1060, 161, 3002, 3529, 418, 5438, 519, 4287, 161, 5440, 5441, 608, 161, 161, 5444, 5446, 2331, 5447, 4419, 5450, 5453, 5456, 161, 435, 5445, 213, 418, 3766, 418, 5448, 4203, 1042, 4678, 5449, 5451, 2864, 5452, 3002, 161, 161, 161, 5454, 5455, 296, 465, 1754, 304, 414, 304, 5458, 5462, 5463, 5470, 426, 1143, 5459, 1042, 161, 355, 161, 5460, 5461, 161, 1342, 1280, 1818, 5464, 5469, 4515, 5465, 161, 5466, 5467, 5468, 519, 5472, 5474, 5476, 5478, 161, 295, 5482, 5485, 1343, 295, 1589, 5490, 5496, 1161, 161, 5475, 4203, 1876, 213, 1343, 161, 161, 5477, 2864, 161, 5479, 5480, 161, 5481, 161, 2283, 295, 5483, 161, 3917, 161, 413, 5484, 5486, 5487, 5488, 1343, 5489, 161, 186, 186, 492, 161, 296, 5491, 5495, 5492, 5493, 5494, 186, 296, 5497, 1042, 418, 161, 161, 4203, 5499, 296, 5507, 5510, 414, 5500, 5504, 4308, 2600, 5506, 5501, 161, 5502, 186, 5503, 161, 5505, 5508, 5509, 5511, 5512, 5514, 1156, 5520, 5522, 5530, 5533, 5538, 5540, 5545, 5547, 5548, 5551, 5553, 5558, 5564, 5565, 161, 5568, 2576, 5515, 5517, 5518, 5519, 161, 161, 519, 2376, 1180, 5516, 1250, 161, 301, 414, 182, 1938, 186, 2245, 3529, 5521, 161, 414, 5523, 161, 3102, 5524, 5525, 1093, 5526, 5528, 5529, 1757, 1250, 5527, 5531, 1896, 5532, 304, 161, 686, 5122, 5534, 1093, 5535, 1367, 5536, 5537, 1459, 5539, 5541, 5542, 3807, 5544, 301, 5543, 5546, 296, 222, 686, 2538, 5549, 5550, 161, 355, 435, 161, 161, 4075, 4164, 161, 1060, 1639, 519, 5552, 5554, 296, 5555, 5557, 5556, 5559, 5563, 5560, 5562, 5561, 301, 1432, 161, 5566, 5567, 5570, 492, 161, 5573, 295, 1042, 5576, 5591, 5596, 1342, 1851, 5598, 5599, 5600, 1017, 5606, 5571, 1198, 5572, 161, 161, 161, 5574, 5575, 3964, 213, 3529, 414, 4382, 5577, 5578, 5579, 2451, 5582, 874, 5584, 5589, 161, 1504, 161, 1093, 556, 409, 5580, 5581, 5583, 5585, 5588, 5586, 5587, 5590, 5552, 5592, 5595, 1042, 5593, 161, 1042, 5594, 2174, 294, 161, 750, 5597, 161, 161, 1145, 161, 161, 3438, 1389, 5601, 1198, 5604, 418, 5605, 1093, 213, 161, 5602, 161, 5603, 1818, 1042, 5609, 301, 5611, 5616, 5621, 5627, 5632, 5634, 5635, 5638, 1371, 5640, 5644, 5455, 5648, 3002, 5653, 5655, 414, 3534, 5610, 5610, 161, 1093, 5612, 5613, 5615, 5614, 161, 5617, 440, 295, 5620, 1639, 3724, 5618, 4179, 5619, 161, 519, 418, 1042, 2050, 161, 817, 161, 5622, 5623, 161, 5624, 1343, 5626, 161, 161, 161, 5625, 5628, 5629, 301, 5630, 2050, 1845, 161, 161, 1026, 1372, 5631, 161, 5633, 556, 161, 5636, 4015, 5637, 5639, 5641, 5642, 5643, 679, 161, 5645, 5646, 2539, 5647, 161, 5649, 5652, 1190, 5650, 5651, 519, 435, 161, 1555, 5654, 304, 295, 418, 161, 608, 679, 161, 1862, 161, 5656, 5658, 5659, 5663, 5664, 519, 5665, 4168, 301, 5666, 5667, 5668, 5670, 5676, 5679, 421, 161, 1938, 5660, 5661, 301, 252, 5662, 1899, 5005, 5669, 414, 5671, 5672, 5674, 5675, 1343, 161, 161, 5673, 161, 1899, 556, 296, 5677, 5678, 1847, 3959, 418, 4898, 421, 1050, 1640, 1886, 5681, 1573, 5682, 5686, 5409, 295, 316, 5689, 4733, 5690, 5704, 161, 5683, 5685, 5684, 296, 519, 519, 5687, 1639, 5688, 161, 2270, 5691, 5692, 5698, 5700, 161, 5693, 5696, 5697, 5694, 5695, 5699, 1042, 5701, 161, 678, 414, 186, 5702, 161, 5703, 5706, 5709, 296, 5712, 5713, 5716, 5717, 5718, 1343, 5723, 5728, 5729, 5740, 5741, 2142, 5751, 295, 5707, 161, 1143, 4638, 5708, 161, 5083, 3088, 5710, 5711, 5714, 304, 418, 295, 5715, 161, 436, 5719, 5720, 5721, 437, 414, 5722, 5481, 466, 161, 4203, 418, 2167, 3488, 1161, 161, 5724, 5725, 5726, 1336, 1876, 161, 1626, 296, 1756, 161, 5727, 161, 5730, 5731, 5731, 350, 5732, 5734, 304, 3479, 2854, 5735, 5737, 3181, 418, 2969, 5733, 426, 5736, 5738, 5739, 161, 1107, 301, 1343, 3998, 5742, 5743, 5746, 5748, 1037, 161, 304, 1639, 5744, 350, 5745, 2142, 161, 5747, 5749, 161, 5750, 5753, 5755, 1343, 5754, 418, 5757, 5759, 5763, 295, 5764, 5768, 5769, 1583, 5770, 5771, 1727, 295, 418, 5758, 426, 213, 161, 5760, 1093, 5761, 161, 1343, 5762, 161, 161, 1418, 185, 213, 414, 5765, 5766, 161, 414, 5767, 1482, 296, 5772, 304, 5773, 1824, 5775, 1042, 440, 430, 161, 161, 435, 1093, 161, 414, 5777, 3435, 5778, 5781, 5784, 5785, 1609, 5790, 5791, 5792, 5793, 5794, 5798, 1459, 161, 421, 1480, 161, 5779, 5780, 414, 161, 418, 5782, 5783, 161, 1093, 161, 1699, 5786, 5787, 5788, 5789, 3167, 409, 418, 1092, 161, 2130, 2387, 1093, 417, 5795, 5796, 295, 161, 435, 304, 161, 5797, 1818, 3196, 5800, 5801, 408, 1343, 1054, 1850, 161, 435, 1343, 414, 5803, 5804, 5806, 15, 5919, 6014, 6099, 6198, 6200, 417, 6248, 6254, 5805, 5807, 5808, 5810, 5811, 5821, 5834, 5838, 5840, 5842, 5843, 5858, 5860, 5862, 5866, 161, 5889, 5896, 5898, 5903, 5908, 1981, 5910, 5912, 408, 5809, 716, 1150, 302, 1604, 213, 417, 5812, 519, 5815, 5150, 1042, 5816, 161, 5813, 5814, 3850, 161, 2451, 519, 161, 1639, 161, 5817, 1092, 440, 1343, 440, 418, 5818, 161, 5820, 5819, 3227, 161, 414, 3193, 5822, 5825, 5826, 1604, 4293, 5827, 5829, 161, 5832, 5833, 5823, 161, 5824, 1895, 1143, 5828, 161, 5830, 5831, 474, 296, 213, 1640, 414, 161, 1609, 5835, 5836, 5837, 5839, 1028, 417, 296, 5841, 161, 304, 1899, 186, 1876, 161, 1459, 295, 1093, 2040, 5844, 5845, 311, 5855, 213, 304, 4733, 414, 1143, 5846, 5847, 1756, 5848, 5852, 161, 302, 468, 5849, 5850, 5851, 5853, 5854, 750, 5856, 5857, 5859, 1042, 3689, 3098, 161, 161, 213, 5861, 161, 418, 5863, 1818, 3302, 5207, 4374, 418, 417, 1639, 1223, 5864, 5865, 5867, 5868, 5870, 296, 3267, 5878, 5879, 1851, 5880, 418, 5883, 5888, 1459, 1343, 1343, 2115, 161, 1228, 5869, 161, 2113, 5871, 5874, 5875, 295, 5876, 5877, 161, 425, 1050, 161, 161, 414, 161, 5872, 161, 161, 1107, 5873, 3518, 1013, 1093, 556, 161, 3006, 1050, 161, 161, 1343, 161, 418, 4203, 5881, 5882, 1459, 1343, 556, 5884, 3267, 2024, 5887, 5885, 1459, 5886, 4727, 1847, 440, 295, 418, 409, 3250, 3098, 5890, 5892, 1727, 5893, 1639, 1639, 474, 5891, 295, 1426, 161, 296, 161, 3167, 414, 1143, 5894, 3098, 5895, 2963, 435, 4226, 161, 2270, 5856, 161, 5897, 4062, 426, 418, 418, 418, 161, 409, 161, 296, 5899, 350, 161, 5900, 5901, 414, 161, 296, 3510, 5902, 2289, 5904, 252, 161, 5905, 213, 3778, 5906, 5907, 5909, 1851, 213, 1042, 3435, 440, 5911, 161, 417, 418, 296, 2848, 418, 5913, 5916, 304, 5918, 5914, 161, 5915, 161, 4364, 5917, 1640, 304, 5920, 5944, 5949, 5950, 5956, 5957, 5958, 3730, 5969, 5970, 5971, 418, 5981, 5982, 5989, 5995, 5998, 6000, 6007, 6010, 1573, 5921, 5924, 5935, 186, 2419, 5936, 5942, 5943, 5922, 409, 5923, 414, 5925, 5926, 5928, 5929, 1876, 874, 161, 5930, 4532, 5934, 1150, 635, 5927, 2489, 1895, 5931, 5932, 5933, 161, 1500, 5937, 5938, 1230, 2766, 1756, 161, 5940, 1150, 5939, 5941, 5945, 5946, 5947, 5948, 161, 3850, 161, 1582, 5951, 5952, 295, 3479, 161, 161, 1036, 5955, 5953, 1042, 5954, 418, 161, 1093, 1042, 161, 2102, 1851, 418, 5959, 296, 4467, 3776, 430, 5961, 5960, 5962, 5964, 5966, 1270, 5963, 409, 350, 5965, 1351, 3510, 5967, 5968, 185, 4733, 4203, 295, 295, 5972, 5973, 5976, 5977, 5979, 5974, 5975, 304, 418, 3250, 161, 161, 1028, 5978, 3730, 414, 1343, 161, 161, 161, 5980, 418, 417, 213, 465, 1787, 5983, 5984, 304, 5985, 5986, 5988, 161, 296, 418, 474, 5987, 2719, 161, 295, 1343, 5990, 418, 5991, 161, 5992, 5993, 5994, 296, 5996, 5997, 161, 466, 1143, 5999, 6001, 6002, 6004, 417, 6003, 6005, 295, 6006, 296, 6008, 6009, 6011, 6012, 6013, 6015, 6017, 6020, 6023, 6037, 6042, 6053, 6056, 6058, 6068, 6069, 6080, 5875, 6095, 6016, 161, 2596, 161, 161, 161, 1498, 161, 161, 6018, 6019, 2330, 556, 3637, 6021, 6022, 2165, 440, 1343, 295, 161, 161, 435, 161, 350, 161, 435, 6024, 6030, 1639, 414, 355, 6025, 6026, 6027, 2283, 3888, 6028, 6029, 6031, 1604, 3766, 6032, 1343, 6035, 1093, 161, 6036, 5934, 6033, 6034, 304, 6038, 1342, 6041, 1036, 6039, 186, 4308, 213, 161, 6040, 161, 1474, 304, 417, 6043, 2199, 6047, 6048, 6051, 6052, 2797, 6044, 6045, 304, 6046, 1829, 161, 1459, 3794, 6049, 6050, 213, 1343, 418, 1639, 1097, 706, 1376, 6054, 161, 3013, 6055, 1938, 296, 3302, 440, 6057, 5360, 1938, 296, 1042, 6059, 6060, 6061, 6062, 1042, 6063, 2362, 161, 6067, 417, 4391, 161, 161, 736, 3670, 1824, 161, 161, 296, 1876, 304, 161, 6064, 1474, 5630, 161, 6065, 6066, 3098, 418, 2782, 1432, 6070, 6072, 6073, 6075, 426, 418, 6076, 6078, 1126, 418, 6071, 161, 6074, 1136, 161, 213, 161, 6077, 418, 296, 304, 6079, 6081, 6082, 161, 1145, 6084, 6085, 6086, 6087, 161, 418, 213, 6083, 414, 6088, 6090, 6092, 304, 6094, 1028, 418, 6089, 213, 1036, 1060, 6091, 1604, 161, 418, 6093, 6096, 6098, 6097, 6100, 6111, 6112, 6123, 6132, 6134, 6137, 6138, 6140, 6147, 6148, 6149, 6154, 1112, 6167, 6171, 6175, 6185, 6195, 6196, 6197, 1573, 6101, 715, 6102, 936, 6105, 6106, 6107, 304, 4425, 6108, 414, 6109, 161, 6103, 6104, 1297, 161, 740, 2382, 6110, 1640, 1013, 408, 350, 1228, 6113, 161, 6114, 3555, 6116, 1143, 6115, 414, 6117, 2165, 6118, 6119, 6121, 6122, 161, 5832, 3002, 350, 6120, 4062, 3728, 1372, 2143, 161, 6124, 213, 6125, 3102, 6127, 6128, 4425, 3959, 6131, 161, 1060, 161, 6126, 296, 420, 1583, 6129, 6130, 6130, 304, 295, 6133, 1343, 1935, 6135, 6136, 418, 1583, 301, 6139, 474, 6141, 304, 6142, 6146, 6143, 6144, 6145, 418, 1012, 1109, 3098, 1474, 6150, 295, 296, 161, 161, 295, 1501, 6153, 161, 1405, 6151, 1060, 4316, 6152, 435, 1365, 409, 6155, 6156, 6163, 6157, 3013, 6158, 6160, 1343, 687, 6161, 414, 414, 161, 161, 6159, 6162, 2196, 296, 1823, 1093, 3098, 6164, 6165, 6166, 2225, 161, 6168, 213, 5986, 6169, 6170, 4332, 3181, 1343, 418, 6172, 1343, 161, 1829, 161, 6173, 161, 6174, 414, 1609, 213, 161, 417, 6176, 6178, 6180, 161, 161, 6183, 295, 6177, 1459, 296, 1026, 6179, 6181, 6182, 1143, 3487, 161, 6184, 1143, 3181, 6186, 295, 296, 1042, 6188, 5606, 6187, 414, 161, 6189, 3098, 6190, 6191, 874, 6192, 6193, 304, 474, 418, 6194, 417, 417, 3689, 750, 1590, 6122, 6199, 6201, 6202, 6207, 6209, 679, 6214, 418, 6216, 6218, 6219, 6227, 6241, 6246, 2770, 2442, 420, 1228, 6203, 6205, 161, 6206, 6204, 296, 161, 1343, 295, 6208, 6210, 6211, 4695, 6213, 161, 6212, 1640, 1042, 6215, 161, 295, 1343, 6113, 4470, 1343, 4820, 6217, 1230, 1042, 1036, 2199, 6220, 5061, 3098, 6222, 6223, 6225, 440, 1639, 6221, 1726, 304, 161, 421, 1727, 1011, 6224, 474, 6226, 2191, 185, 161, 409, 6228, 6230, 3098, 6234, 6236, 750, 6237, 6238, 6239, 1050, 6240, 1026, 161, 1938, 6229, 304, 1938, 304, 355, 161, 6231, 161, 6232, 161, 6233, 6235, 1862, 556, 1818, 5642, 474, 418, 417, 3113, 3959, 6242, 6244, 161, 1990, 6245, 1851, 6243, 1302, 1042, 1609, 6247, 161, 295, 1145, 418, 414, 6249, 304, 6251, 1818, 161, 295, 6252, 418, 417, 6250, 414, 414, 161, 161, 301, 1060, 418, 750, 418, 1818, 161, 6253, 6255, 6257, 6256, 2755, 4879, 6259, 6260, 6265, 6302, 6319, 6335, 6344, 6348, 6349, 6359, 6360, 6362, 6397, 6407, 6427, 933, 6430, 6506, 6544, 6571, 6572, 6573, 418, 6261, 295, 1180, 6264, 350, 6262, 161, 750, 6263, 512, 161, 180, 161, 4316, 1042, 161, 6266, 6272, 6273, 519, 6281, 161, 4898, 6301, 6267, 6270, 6268, 6269, 6271, 6274, 6276, 296, 6277, 295, 1371, 295, 6278, 301, 6280, 430, 6275, 1733, 3766, 418, 5122, 6278, 6279, 484, 161, 414, 6282, 2362, 6285, 6287, 6290, 6293, 6295, 6297, 161, 6298, 6299, 161, 6283, 6284, 6283, 6286, 161, 1093, 1198, 6288, 483, 430, 161, 6289, 6291, 6292, 3762, 6294, 1876, 6296, 1459, 3829, 3603, 418, 6300, 421, 4879, 3827, 414, 6303, 6304, 6307, 2580, 6310, 6312, 1343, 6313, 4055, 161, 6315, 6318, 161, 467, 161, 996, 161, 6305, 6190, 6306, 414, 512, 161, 3435, 161, 6308, 6309, 6311, 975, 1042, 213, 6314, 421, 2538, 6316, 6317, 3827, 3827, 6320, 304, 6322, 6323, 6324, 6332, 6333, 6334, 6321, 3250, 295, 295, 1851, 295, 1270, 1851, 301, 474, 6325, 1787, 1042, 1093, 6329, 3548, 161, 6326, 6327, 6328, 6330, 6331, 295, 1150, 2270, 1042, 161, 696, 6336, 426, 1143, 6337, 6339, 6340, 426, 6342, 161, 1459, 6343, 6338, 161, 161, 161, 2203, 161, 6341, 1092, 186, 414, 161, 3603, 414, 6345, 2362, 6346, 4883, 1343, 414, 161, 2539, 426, 161, 6347, 5763, 161, 161, 1851, 3529, 6350, 161, 6351, 6352, 3002, 6356, 6353, 161, 1426, 6354, 161, 933, 6355, 6357, 6358, 418, 6361, 296, 4835, 3729, 2538, 6363, 1618, 295, 6364, 6366, 6367, 6369, 6370, 6393, 6394, 417, 6395, 6365, 1818, 1042, 426, 2549, 4081, 6368, 3102, 440, 161, 414, 5671, 1809, 6371, 6375, 6378, 6379, 6381, 1343, 6384, 1109, 6385, 420, 6390, 1297, 6372, 6373, 2526, 6374, 161, 1093, 6376, 414, 1092, 6377, 161, 161, 6380, 1302, 6382, 418, 161, 6383, 161, 296, 417, 161, 192, 6386, 6387, 6388, 6389, 6391, 6392, 6396, 1343, 414, 6398, 3267, 6400, 6403, 6404, 6406, 6399, 1121, 440, 161, 6401, 6402, 1757, 3439, 1042, 6405, 161, 414, 161, 6408, 6410, 6412, 1156, 6418, 6419, 6420, 6424, 6425, 161, 6409, 1938, 161, 420, 3469, 6411, 161, 1054, 6413, 6416, 6417, 3178, 1284, 6414, 186, 161, 6415, 1818, 2165, 1042, 440, 161, 3829, 3321, 1050, 6421, 6422, 161, 6423, 1028, 1876, 418, 6426, 6428, 161, 161, 6429, 1462, 6431, 6433, 6438, 6443, 5826, 6449, 6464, 6466, 6467, 6474, 6480, 6484, 6490, 6491, 6492, 6499, 6502, 1639, 1297, 6505, 6432, 1937, 3227, 161, 161, 5483, 304, 421, 6434, 3015, 6437, 4396, 6435, 6436, 3098, 1640, 435, 1699, 161, 6439, 304, 6441, 6442, 2420, 6440, 1699, 421, 296, 1036, 350, 6444, 3250, 161, 996, 6445, 414, 6446, 6447, 6448, 213, 3423, 6450, 4193, 6451, 6453, 6455, 6456, 6457, 1343, 6460, 4871, 6463, 161, 213, 161, 186, 161, 295, 1532, 2203, 6452, 4075, 6454, 4077, 296, 6458, 6459, 6461, 418, 161, 1126, 6462, 1367, 2221, 6465, 435, 1302, 161, 4332, 6468, 6469, 6471, 6472, 1343, 161, 350, 161, 4075, 161, 6470, 740, 5637, 430, 4695, 4721, 185, 6473, 933, 6475, 6477, 420, 161, 1011, 6476, 161, 161, 418, 1990, 6478, 417, 6479, 630, 1604, 6481, 6482, 429, 6483, 1459, 6485, 1604, 6486, 440, 6488, 6489, 161, 1885, 161, 435, 1093, 161, 1699, 161, 414, 6487, 417, 186, 2196, 304, 1042, 161, 161, 1938, 6493, 6494, 6496, 6498, 161, 435, 161, 1093, 1938, 296, 435, 3167, 1093, 6495, 301, 6497, 6006, 6500, 440, 418, 6501, 426, 1042, 418, 161, 409, 6503, 414, 304, 1343, 295, 418, 161, 161, 6504, 6507, 6510, 6511, 6513, 6515, 6521, 6531, 6532, 6533, 6534, 6538, 1639, 6543, 421, 6508, 186, 6509, 1563, 418, 2769, 161, 653, 6512, 296, 1371, 6514, 161, 161, 161, 6516, 213, 6517, 1341, 1343, 4374, 6518, 635, 161, 161, 161, 3250, 161, 161, 161, 556, 418, 6519, 6520, 6522, 3799, 6523, 6524, 6525, 6526, 6527, 414, 1905, 6529, 6528, 6530, 4638, 1023, 296, 740, 1143, 6535, 6536, 295, 1371, 435, 161, 161, 161, 6537, 556, 6539, 6541, 6542, 1121, 1093, 161, 161, 6540, 1048, 1048, 296, 3024, 1418, 414, 4951, 3213, 6545, 6546, 6550, 161, 6552, 6554, 295, 3959, 161, 6555, 1851, 222, 161, 301, 161, 6547, 161, 6548, 519, 6549, 414, 186, 161, 355, 6551, 6553, 414, 4478, 2539, 185, 2340, 414, 686, 6556, 419, 6568, 1042, 4883, 1343, 295, 6569, 6570, 161, 161, 6557, 6558, 6560, 186, 6562, 1042, 6565, 6566, 1885, 161, 6559, 435, 1011, 6561, 6563, 5336, 1896, 6564, 4301, 1823, 6567, 740, 1356, 492, 2040, 1459, 213, 161, 2837, 440, 417, 421, 2340, 1876, 874, 161, 1143, 6574, 6575, 414, 1739, 6576, 1042, 3689, 161, 3833, 161, 161, 5651, 1093, 161, 6579, 2362, 6581, 5826, 6583, 1604, 6118, 4879, 6584, 1343, 6586, 418, 6587, 6593, 6596, 6600, 418, 5934, 6601, 161, 1302, 6580, 6386, 6582, 161, 1474, 161, 6585, 3928, 1126, 1935, 414, 304, 1459, 6588, 6590, 6589, 6591, 6592, 6594, 161, 304, 6595, 2024, 1459, 6597, 6598, 6599, 6602, 6603, 6604, 6606, 18, 7359, 7364, 2306, 7370, 20, 7968, 22, 8299, 8302, 23, 9286, 27, 9530, 9537, 28, 9718, 9719, 9759, 468, 6605, 1823, 418, 6607, 6608, 6609, 6610, 6636, 6650, 6667, 6668, 6676, 6679, 6684, 6693, 6695, 6697, 6769, 6820, 6886, 6887, 6943, 19, 7135, 7236, 7308, 7328, 7348, 7352, 7356, 414, 6611, 6615, 6620, 6622, 6625, 6628, 6630, 6632, 161, 301, 6612, 6613, 3778, 355, 1093, 6614, 1223, 414, 6616, 1640, 304, 304, 6617, 6618, 6619, 6621, 6623, 6624, 1760, 161, 3778, 750, 6626, 414, 6627, 161, 414, 1054, 6629, 1059, 6631, 414, 1938, 6633, 6634, 519, 418, 1432, 301, 301, 6635, 6637, 6638, 6643, 6644, 6646, 4513, 6648, 936, 161, 6639, 6641, 6640, 4065, 6642, 2127, 6645, 3861, 161, 6647, 6649, 6651, 6653, 6655, 6657, 304, 6660, 6661, 6662, 6663, 161, 6652, 6654, 1198, 161, 6656, 1093, 1093, 3102, 2963, 1343, 6658, 161, 6659, 418, 1458, 414, 1343, 2605, 706, 2719, 434, 6664, 1640, 6665, 6666, 161, 1998, 818, 6669, 6670, 6672, 6671, 6673, 304, 440, 161, 6674, 463, 161, 6675, 1310, 161, 6677, 6678, 3670, 440, 6680, 304, 1640, 161, 6682, 304, 6681, 161, 6683, 2915, 1345, 6685, 6687, 6688, 6689, 6691, 6686, 295, 3998, 418, 6690, 6692, 161, 1482, 418, 6694, 418, 6696, 414, 161, 4300, 414, 6698, 5469, 6704, 6713, 6719, 6722, 2826, 6724, 6725, 6741, 6742, 6757, 1343, 6758, 6760, 417, 6761, 6762, 6766, 6767, 6699, 1343, 414, 6701, 6703, 1245, 635, 6700, 1059, 301, 161, 2272, 161, 6702, 161, 304, 5217, 6705, 1458, 706, 6708, 6706, 519, 6707, 6709, 6712, 6710, 414, 6711, 1208, 1091, 1426, 6714, 6715, 1639, 161, 3680, 1173, 6716, 6717, 418, 6718, 1609, 1060, 6720, 519, 161, 161, 6721, 2340, 296, 304, 6723, 6726, 1813, 6729, 6731, 6735, 3049, 161, 6737, 2272, 3488, 296, 6727, 6728, 161, 6730, 519, 304, 1367, 161, 6732, 6733, 6734, 6736, 1223, 161, 6738, 161, 6739, 6740, 414, 6743, 2864, 6745, 6748, 6754, 161, 6756, 6744, 5878, 2115, 161, 161, 304, 635, 161, 4835, 4093, 6746, 6747, 161, 161, 6749, 1134, 6751, 1343, 3282, 6752, 6750, 2191, 418, 4858, 418, 6753, 161, 1879, 6755, 4284, 1042, 213, 161, 3049, 6759, 3778, 301, 1367, 6763, 6765, 161, 1778, 6764, 161, 440, 161, 161, 1778, 252, 6768, 1908, 6770, 6776, 6784, 1109, 6786, 3098, 6792, 6797, 6800, 6803, 213, 6819, 1190, 5684, 5684, 6771, 6772, 301, 6774, 519, 1868, 6775, 6773, 301, 6777, 6778, 413, 304, 6780, 6781, 1363, 435, 6779, 414, 1639, 161, 161, 6782, 418, 161, 6783, 6785, 190, 519, 6787, 6788, 161, 6789, 414, 161, 301, 1787, 3002, 161, 5242, 6790, 6791, 161, 6793, 6796, 304, 161, 6794, 161, 161, 6795, 161, 818, 2094, 161, 6798, 304, 418, 161, 421, 6799, 1044, 1044, 1793, 414, 6801, 6802, 6804, 6809, 6812, 6813, 6815, 6816, 6817, 417, 6818, 1590, 6805, 6806, 161, 6717, 6807, 6808, 6810, 6811, 355, 161, 435, 1093, 304, 6814, 1896, 304, 1778, 1418, 1595, 222, 519, 4485, 2507, 1432, 1797, 350, 6821, 213, 6826, 6832, 6847, 3098, 6848, 1604, 6853, 6856, 6870, 1037, 6871, 6872, 6882, 6883, 1156, 418, 6822, 6595, 304, 6825, 2197, 1343, 6823, 6824, 161, 1092, 1184, 161, 750, 161, 6827, 2568, 6831, 519, 418, 6828, 1054, 186, 1042, 6829, 161, 1334, 186, 6830, 1823, 556, 6833, 6835, 6839, 6842, 296, 301, 6846, 304, 6834, 4041, 304, 2252, 161, 6836, 6837, 301, 6838, 161, 304, 6840, 1425, 435, 161, 6841, 213, 213, 2610, 185, 6843, 414, 6844, 161, 6164, 1639, 6845, 976, 161, 414, 161, 1757, 304, 1343, 161, 4538, 296, 1813, 301, 161, 304, 4835, 161, 6849, 6851, 6850, 222, 6852, 301, 6854, 418, 304, 5284, 6855, 6857, 6859, 6861, 6867, 161, 1365, 6858, 161, 301, 2225, 6860, 213, 161, 161, 6862, 556, 6865, 6863, 6864, 2463, 161, 6866, 818, 161, 6868, 6869, 161, 3302, 161, 1093, 161, 213, 301, 161, 1245, 295, 6873, 6876, 418, 4063, 6878, 6880, 1097, 1639, 161, 6874, 301, 304, 6875, 418, 6877, 161, 3453, 161, 6879, 161, 161, 161, 4359, 161, 1640, 6881, 4814, 161, 5494, 161, 6561, 6884, 6885, 418, 6888, 1847, 6893, 6901, 6910, 6912, 6918, 6928, 6934, 6936, 6942, 6889, 1317, 750, 6892, 519, 1190, 6890, 304, 6891, 519, 222, 6894, 161, 3013, 6895, 161, 6897, 6898, 6900, 6896, 161, 6899, 1868, 4109, 2782, 6902, 1217, 6905, 2523, 6903, 6904, 6906, 3250, 6907, 1342, 6908, 161, 6909, 304, 161, 3423, 418, 6911, 3287, 6913, 2234, 304, 4065, 304, 6916, 6914, 6915, 6917, 414, 6919, 6922, 6923, 161, 6924, 161, 161, 6920, 161, 6921, 1367, 161, 161, 1868, 556, 301, 222, 6925, 6926, 1367, 6927, 6929, 6930, 418, 161, 6931, 6933, 161, 161, 3555, 6932, 3833, 6935, 3315, 1356, 418, 6937, 6939, 686, 3756, 6938, 6940, 6941, 1115, 1093, 463, 3637, 2451, 1250, 6944, 414, 6945, 6958, 6971, 6978, 6993, 5610, 7001, 3013, 7004, 7018, 7021, 7023, 7035, 7045, 7054, 7066, 7077, 7100, 7107, 7127, 7129, 7133, 7134, 6946, 6948, 2050, 6951, 6952, 6953, 1093, 6954, 635, 6947, 1868, 1087, 1778, 414, 6949, 6950, 301, 3169, 3976, 1814, 1060, 301, 6955, 6957, 6956, 161, 3807, 161, 6959, 3194, 418, 6960, 161, 6967, 161, 1087, 1778, 213, 1011, 6961, 6964, 6962, 6963, 6965, 6966, 161, 4055, 6968, 6969, 6970, 6972, 6973, 6975, 519, 414, 6974, 161, 6976, 6977, 5751, 301, 414, 6979, 1160, 6981, 6984, 6992, 161, 2678, 1639, 161, 2111, 222, 6980, 161, 6982, 6983, 161, 302, 435, 679, 1343, 1778, 3006, 6985, 6987, 6986, 161, 2580, 2558, 6988, 6989, 6990, 6991, 1500, 1250, 301, 1813, 6994, 4291, 161, 6995, 6996, 6621, 6997, 161, 6885, 7000, 161, 161, 1270, 1054, 2362, 1276, 6998, 519, 6999, 7002, 304, 161, 7003, 7005, 7006, 7008, 7012, 7013, 7014, 1778, 7015, 7016, 7017, 7007, 355, 7009, 7011, 7010, 186, 222, 1093, 2082, 7019, 7020, 7022, 414, 7024, 1012, 7025, 7027, 161, 7030, 7032, 3399, 2678, 7034, 414, 2576, 161, 1028, 1500, 7026, 161, 161, 161, 304, 7028, 7029, 6635, 421, 1850, 252, 7031, 161, 2523, 161, 1750, 740, 418, 418, 417, 7033, 418, 1847, 414, 304, 161, 418, 7036, 7037, 7040, 440, 7043, 421, 3002, 418, 7038, 1060, 161, 200, 161, 7039, 301, 161, 7041, 1498, 7042, 1150, 304, 434, 1087, 213, 7044, 4183, 7046, 7048, 7049, 440, 7053, 161, 161, 304, 1343, 3250, 7047, 435, 1107, 435, 1342, 161, 350, 4065, 161, 6980, 161, 304, 7050, 435, 7051, 7052, 161, 2050, 7055, 1036, 2799, 7063, 7064, 161, 7065, 2252, 414, 7056, 7057, 7058, 7060, 161, 414, 7061, 7059, 1093, 1060, 161, 7062, 2082, 186, 435, 7067, 7072, 750, 7076, 1107, 7068, 161, 2040, 7069, 7070, 7071, 213, 7073, 161, 161, 7074, 7075, 414, 7078, 7082, 7084, 7094, 7096, 7099, 2050, 2270, 750, 7079, 301, 7080, 635, 519, 161, 301, 519, 7081, 296, 6965, 519, 3250, 519, 418, 7083, 3049, 161, 2840, 7085, 7086, 4419, 7087, 7088, 418, 7089, 7091, 4055, 7092, 161, 2677, 161, 7090, 7093, 3250, 161, 161, 7095, 6755, 2252, 301, 7097, 7098, 7101, 418, 7102, 1639, 7103, 7105, 7104, 7106, 7108, 7109, 1583, 7111, 7112, 7113, 7116, 7122, 161, 7124, 161, 161, 161, 3704, 7110, 161, 1060, 296, 3103, 556, 185, 1937, 1937, 7114, 1640, 1640, 7115, 414, 161, 3479, 7117, 1093, 7120, 4183, 7118, 7119, 7121, 2199, 7123, 2840, 976, 7125, 977, 7126, 7128, 1778, 519, 7097, 5684, 301, 7130, 7131, 7132, 1087, 5684, 161, 3250, 1054, 161, 161, 1150, 1889, 1145, 1640, 1150, 7136, 1017, 7147, 7148, 7151, 7155, 7161, 7166, 7168, 295, 1028, 7169, 7172, 7196, 7233, 1639, 161, 7137, 4602, 7140, 7141, 6046, 7144, 4570, 7146, 414, 1539, 2252, 7138, 7139, 7142, 7143, 414, 1173, 7145, 6899, 519, 161, 7149, 7150, 7152, 161, 7153, 7154, 161, 1050, 295, 295, 2596, 301, 740, 7156, 7157, 1726, 7158, 7159, 418, 1639, 161, 161, 161, 414, 1999, 1418, 418, 1482, 1823, 7160, 7162, 161, 412, 7163, 7165, 161, 413, 7164, 1093, 1054, 7167, 2261, 7170, 2115, 7171, 7173, 7182, 7187, 6423, 7192, 7194, 161, 7174, 7176, 7179, 7180, 7181, 161, 7175, 161, 7177, 7145, 7178, 161, 1367, 161, 7183, 7184, 304, 7185, 414, 161, 3559, 1097, 1343, 161, 213, 1150, 7186, 7188, 1060, 7189, 7190, 213, 414, 7191, 7193, 7195, 7197, 7202, 7213, 7221, 295, 7223, 7227, 161, 7198, 1249, 7200, 7199, 1862, 7201, 1028, 435, 7203, 7211, 7212, 7204, 7205, 161, 7207, 7208, 7206, 7209, 7210, 1862, 1862, 7214, 7217, 7220, 7215, 7216, 161, 7218, 161, 7219, 1028, 7222, 3559, 1343, 161, 161, 7224, 7226, 7225, 414, 3626, 1310, 7228, 7229, 7230, 2376, 417, 5083, 2111, 1778, 7231, 7232, 7234, 304, 7235, 213, 1128, 414, 573, 7237, 7263, 5065, 7266, 7281, 7283, 7293, 7295, 7297, 1913, 7298, 7299, 7301, 7304, 7307, 7238, 7244, 7249, 7253, 7254, 7256, 7261, 7239, 7242, 1705, 7240, 7241, 7243, 4607, 519, 7245, 7246, 7247, 468, 7248, 1042, 161, 4287, 408, 1059, 301, 161, 7250, 7251, 7252, 1367, 2596, 7255, 1590, 7257, 7258, 7259, 7260, 7262, 350, 7264, 200, 7265, 4313, 161, 7267, 7271, 161, 7268, 7269, 7270, 161, 2499, 414, 311, 3439, 7272, 7274, 161, 7278, 7273, 7275, 7276, 7277, 7279, 7280, 7282, 628, 976, 409, 7284, 3013, 7286, 1036, 1108, 7290, 6160, 1060, 7285, 1126, 1365, 1415, 3929, 7287, 295, 161, 7288, 161, 418, 7289, 7291, 7292, 7294, 161, 2238, 161, 7296, 418, 1938, 350, 418, 1640, 7300, 418, 7302, 933, 7303, 7305, 3829, 1042, 304, 161, 161, 7306, 4301, 1829, 1150, 7309, 7310, 7312, 7313, 7315, 7319, 7321, 5441, 414, 1640, 7311, 304, 161, 1829, 1895, 7314, 213, 1343, 417, 161, 7316, 213, 3098, 7317, 6405, 296, 417, 1639, 7318, 213, 7320, 1042, 1415, 7322, 1583, 7325, 7323, 7324, 7326, 7327, 1879, 7329, 7340, 7344, 1756, 7347, 7330, 7331, 7338, 6243, 1037, 7332, 818, 7333, 7336, 7188, 7337, 7334, 7335, 818, 818, 161, 7339, 350, 161, 1905, 7341, 7343, 161, 350, 161, 7342, 296, 1145, 213, 1093, 296, 4712, 7345, 7346, 1757, 161, 161, 440, 628, 7349, 7350, 7351, 161, 1595, 161, 304, 7353, 7354, 3095, 7355, 417, 1609, 161, 1126, 161, 1797, 7357, 7358, 295, 519, 7360, 173, 7361, 7362, 7363, 414, 3689, 7365, 7369, 7366, 7367, 161, 7368, 161, 2406, 7371, 7373, 7375, 7381, 7385, 7386, 7388, 7391, 7395, 7396, 7423, 7426, 7445, 7446, 7448, 7490, 7496, 7497, 7498, 4042, 7372, 296, 1230, 7374, 1223, 7376, 7378, 161, 7379, 7377, 2516, 1787, 301, 304, 7380, 161, 161, 301, 304, 161, 7382, 1367, 1868, 7383, 7384, 519, 1054, 5157, 1126, 7387, 7389, 7390, 7392, 7393, 7394, 7397, 7398, 7409, 7413, 7422, 1392, 7399, 417, 7403, 7404, 7405, 7400, 7401, 1127, 7402, 1425, 1091, 1608, 7406, 161, 161, 7407, 435, 7408, 161, 7410, 301, 7412, 161, 414, 7411, 4307, 7414, 186, 7415, 7416, 7417, 7419, 161, 7420, 7418, 7421, 296, 706, 7424, 1903, 7425, 1608, 1626, 161, 7427, 161, 7429, 7431, 7435, 7428, 7430, 7432, 7434, 7433, 186, 5056, 7436, 7441, 1223, 7443, 161, 7444, 7437, 7438, 1438, 161, 186, 3113, 7439, 161, 7440, 7442, 1815, 7447, 161, 7449, 7451, 7453, 301, 7454, 7463, 7464, 7465, 418, 7466, 7471, 7482, 7485, 7450, 7452, 1126, 3169, 7455, 7459, 7460, 519, 7456, 7458, 7457, 435, 161, 1459, 7461, 7462, 3197, 1060, 421, 1367, 161, 5086, 408, 161, 7467, 7468, 7469, 7470, 1228, 1778, 1037, 7472, 7475, 161, 7473, 7474, 1242, 213, 1128, 7476, 7477, 161, 7478, 7479, 186, 7480, 7481, 7483, 7484, 2094, 1343, 1250, 7486, 7488, 7489, 1126, 7487, 2755, 301, 301, 7491, 1590, 7459, 7493, 7495, 7492, 1212, 301, 7494, 2272, 818, 1126, 296, 7499, 7500, 7501, 21, 7663, 7746, 7747, 7832, 7842, 7849, 7898, 7946, 7964, 7502, 7503, 7505, 7507, 161, 7513, 7515, 1087, 7518, 7524, 7525, 7539, 7559, 7576, 7577, 7589, 7631, 7640, 7647, 7657, 1097, 161, 7504, 161, 7506, 519, 418, 414, 7508, 7510, 421, 7512, 7509, 1363, 7511, 420, 213, 1640, 420, 1181, 7514, 3281, 3907, 1092, 417, 7516, 7517, 1223, 301, 2182, 161, 7519, 7521, 7523, 161, 186, 7520, 414, 1949, 7522, 161, 350, 7526, 7527, 7532, 7533, 2082, 2963, 7537, 716, 418, 7528, 7531, 7529, 7530, 1160, 1538, 7534, 414, 7535, 295, 7536, 7538, 161, 4511, 7540, 519, 7549, 2362, 1756, 7550, 7551, 7541, 7546, 7542, 7543, 7545, 161, 7544, 418, 1482, 186, 7547, 7548, 414, 2309, 818, 161, 7552, 7553, 7558, 1343, 440, 2963, 161, 304, 7554, 7555, 7556, 7557, 161, 7560, 7563, 7567, 7568, 418, 440, 7570, 7573, 7572, 7561, 161, 161, 7562, 161, 161, 161, 7564, 7566, 301, 7565, 414, 7569, 1876, 7571, 1042, 7572, 4091, 161, 7574, 7575, 1042, 213, 161, 4814, 1059, 252, 1415, 7578, 7579, 7582, 7586, 7558, 3766, 3959, 7587, 161, 3922, 350, 7580, 7581, 7583, 7584, 7585, 7588, 304, 1365, 161, 7590, 7596, 7601, 7602, 7605, 7606, 7609, 7613, 7622, 5861, 7624, 7625, 7628, 7629, 7591, 1198, 7592, 7593, 7594, 414, 7595, 161, 1391, 1405, 7597, 7598, 3181, 7599, 7600, 7603, 7604, 1336, 1876, 1365, 7607, 7608, 1305, 7610, 7611, 161, 3354, 1498, 7612, 750, 1060, 7614, 7615, 7618, 7620, 1107, 417, 1028, 7616, 418, 1498, 7617, 706, 161, 161, 2741, 417, 1885, 7619, 304, 414, 512, 161, 161, 7621, 7623, 1538, 2333, 161, 2605, 161, 7626, 301, 7627, 186, 295, 418, 6347, 5503, 7630, 161, 185, 2166, 7632, 7633, 161, 418, 7634, 7636, 414, 161, 161, 161, 161, 213, 1418, 213, 161, 7635, 7637, 7638, 7639, 418, 556, 1036, 213, 7641, 7643, 3098, 1604, 3766, 161, 7644, 7642, 355, 3233, 7645, 1876, 161, 161, 161, 7646, 5552, 1042, 2362, 417, 295, 7648, 484, 7649, 1343, 7652, 1059, 7653, 7650, 7651, 7654, 7655, 7656, 7658, 7661, 7662, 936, 7659, 7660, 1787, 3397, 161, 161, 1367, 301, 161, 7664, 7667, 7674, 7675, 1054, 7688, 686, 7690, 7693, 7700, 1042, 5747, 7706, 7721, 1060, 7733, 7734, 7741, 7745, 161, 1150, 686, 7665, 7666, 1459, 3439, 1604, 1876, 1459, 161, 1459, 1609, 7668, 7670, 161, 7669, 414, 2161, 7671, 1876, 1844, 7672, 874, 7673, 161, 4532, 161, 2420, 1323, 301, 3004, 7676, 161, 7677, 7682, 7687, 6083, 6583, 161, 161, 186, 3198, 7678, 7679, 161, 161, 556, 1093, 7680, 7681, 2855, 686, 7683, 304, 1343, 161, 161, 7684, 3729, 161, 1343, 161, 7685, 7686, 409, 1604, 414, 1010, 7689, 2199, 304, 7691, 417, 7692, 304, 7694, 7699, 7695, 7697, 7696, 7698, 414, 3124, 7701, 7702, 161, 7703, 5468, 7704, 435, 186, 2596, 706, 5469, 161, 296, 1150, 7705, 1101, 1101, 7707, 7708, 7709, 7711, 2449, 7712, 7715, 7716, 1010, 7719, 418, 519, 161, 6153, 7710, 421, 2225, 7713, 7714, 679, 161, 7717, 7718, 1093, 3167, 7720, 7722, 818, 7724, 6162, 7725, 7726, 7727, 7728, 3959, 7723, 4898, 213, 420, 213, 6222, 1160, 296, 304, 295, 418, 7729, 7731, 161, 161, 161, 7730, 161, 1343, 417, 7732, 556, 4285, 7735, 7737, 7738, 3964, 7736, 1107, 161, 1150, 304, 161, 161, 2199, 295, 7739, 7740, 161, 7742, 5754, 5763, 1270, 161, 161, 7743, 7744, 3610, 304, 7748, 7753, 7763, 7764, 7766, 7767, 7769, 414, 7772, 7787, 7792, 7805, 7806, 7810, 7812, 7818, 7822, 161, 7826, 7831, 7749, 7750, 1542, 1059, 161, 222, 7751, 7752, 1754, 7754, 7756, 7757, 7762, 7755, 519, 429, 161, 414, 7758, 7759, 304, 3778, 161, 161, 7760, 1270, 414, 161, 186, 1042, 5552, 7761, 7765, 519, 161, 161, 301, 414, 519, 161, 295, 2730, 7768, 7770, 7771, 7773, 7776, 7783, 1060, 7784, 417, 3399, 7774, 2191, 350, 7775, 414, 7777, 686, 7778, 1145, 7780, 7781, 161, 7779, 1757, 3730, 7782, 161, 1060, 1093, 7785, 7786, 161, 418, 161, 1418, 161, 161, 7788, 440, 7789, 4587, 301, 161, 7790, 161, 7791, 7793, 7794, 4925, 7795, 1093, 7799, 7800, 7801, 161, 414, 1343, 4108, 7796, 7797, 7798, 1608, 2576, 7802, 7803, 7804, 414, 440, 7807, 7808, 161, 418, 2016, 7809, 6309, 161, 161, 7811, 519, 7813, 7814, 6155, 519, 3025, 7815, 7816, 7817, 3988, 7819, 4168, 161, 7821, 1459, 7820, 7823, 4109, 7824, 1609, 7825, 420, 1459, 161, 4401, 430, 7827, 7829, 7828, 161, 7830, 421, 1824, 7833, 7836, 3954, 7834, 7835, 1050, 7837, 7838, 7839, 7840, 7841, 7843, 1173, 7844, 7845, 2538, 7846, 7847, 7848, 1699, 7850, 161, 7855, 7857, 7860, 7864, 7875, 7877, 7878, 7883, 7888, 7889, 1027, 7891, 7894, 7896, 161, 301, 161, 7851, 7852, 7853, 7854, 1198, 5750, 7856, 7858, 7859, 1230, 1042, 7861, 7862, 7863, 7865, 7869, 301, 7866, 301, 7867, 7868, 7870, 7871, 7872, 7873, 7874, 161, 7876, 686, 1042, 7879, 7881, 7880, 7882, 1042, 161, 302, 418, 7884, 301, 7886, 7885, 1042, 161, 7887, 1093, 7890, 414, 4856, 161, 7892, 7893, 556, 4055, 7895, 7897, 1270, 296, 5819, 7899, 7901, 7928, 7940, 7943, 7945, 350, 7900, 7902, 1981, 414, 512, 7065, 519, 1343, 161, 7903, 7904, 3203, 161, 414, 7905, 7906, 7908, 7913, 7915, 295, 7918, 7926, 161, 418, 161, 7907, 1583, 161, 3559, 1208, 1042, 1583, 7909, 161, 7912, 418, 417, 7910, 414, 161, 161, 7911, 161, 1177, 1060, 1060, 301, 213, 7914, 7916, 1028, 7917, 414, 213, 4407, 161, 7919, 161, 7922, 7920, 7921, 296, 7923, 7924, 7925, 414, 2213, 7927, 7929, 7934, 7939, 161, 7930, 7931, 7932, 7933, 7935, 7938, 7936, 7937, 1241, 1198, 7941, 7942, 7944, 295, 1087, 7947, 7948, 7950, 7952, 161, 2449, 7954, 7955, 161, 7956, 7958, 7962, 161, 7949, 7951, 414, 161, 1538, 6309, 1198, 161, 161, 7953, 1145, 4042, 440, 213, 1093, 161, 1060, 1635, 4482, 301, 7957, 161, 7959, 1297, 7960, 414, 414, 7961, 1343, 1639, 1093, 440, 7963, 7965, 7966, 7967, 7969, 7980, 7982, 295, 7995, 8002, 8005, 8009, 8013, 8015, 8028, 8029, 8036, 8062, 8068, 8080, 8081, 414, 7970, 7972, 7973, 161, 7978, 7971, 7974, 7977, 7975, 7976, 161, 7979, 519, 7981, 2430, 7983, 7985, 7992, 7993, 161, 7984, 161, 1059, 7986, 7990, 7991, 1126, 7987, 7988, 7989, 7377, 161, 213, 519, 7994, 161, 2755, 7996, 2602, 8000, 1822, 7997, 7998, 7999, 519, 7389, 8001, 4898, 421, 421, 8003, 1792, 8004, 8006, 8007, 8008, 161, 8010, 8011, 8012, 213, 414, 161, 5086, 6706, 8014, 161, 2025, 1367, 161, 8016, 8020, 8023, 8024, 8026, 8027, 161, 8017, 8019, 8018, 161, 8021, 1036, 4478, 1050, 8022, 1793, 3354, 8025, 1824, 3399, 1787, 1173, 417, 2331, 1519, 3954, 2094, 295, 8030, 8032, 8034, 8031, 8033, 8035, 8037, 8038, 8054, 8055, 1126, 1990, 8057, 1868, 1778, 1778, 161, 2817, 8039, 8040, 8042, 8045, 161, 8041, 414, 186, 213, 161, 8043, 161, 8044, 1208, 1257, 1608, 8046, 8048, 8050, 715, 8047, 8049, 8051, 8052, 8053, 1198, 1438, 1793, 222, 1245, 1868, 8056, 7087, 519, 8058, 8059, 414, 8060, 8061, 519, 4879, 8063, 2538, 8065, 8066, 8064, 8067, 8069, 1208, 8070, 8075, 8077, 8079, 434, 2799, 1335, 414, 2514, 8071, 8072, 8073, 414, 8074, 1367, 1367, 8076, 414, 8078, 414, 2195, 1087, 740, 519, 1223, 8082, 2367, 8083, 8084, 8085, 161, 1824, 1092, 8086, 213, 8087, 1212, 186, 8088, 8168, 8210, 8247, 8282, 8284, 8296, 8089, 8090, 421, 1851, 8092, 8093, 8094, 8096, 8101, 8106, 8110, 8111, 8128, 8137, 8141, 8152, 8154, 1981, 8155, 161, 4352, 1883, 8091, 418, 1011, 440, 426, 2191, 8095, 418, 304, 8097, 2040, 8098, 635, 1060, 2524, 8099, 8100, 3729, 8102, 8103, 8104, 161, 1538, 161, 186, 8105, 161, 8107, 8108, 1297, 1270, 417, 8109, 1604, 5163, 417, 161, 213, 8112, 8114, 8127, 519, 161, 161, 414, 1217, 8113, 161, 434, 304, 8115, 8116, 8118, 8119, 8121, 8117, 1297, 301, 3002, 161, 8120, 161, 8122, 161, 8123, 8124, 8125, 8126, 414, 161, 6127, 417, 213, 418, 8129, 418, 161, 8130, 414, 1042, 3620, 8131, 1756, 7416, 418, 8134, 161, 1486, 1818, 8132, 8133, 161, 1437, 1740, 8135, 8136, 8138, 8139, 8140, 8142, 408, 8144, 2719, 8143, 1050, 161, 304, 161, 8145, 2245, 418, 4461, 8146, 8147, 8148, 8149, 8150, 8151, 2199, 8153, 186, 1042, 1026, 414, 8156, 8161, 8163, 8164, 417, 8166, 1639, 1883, 8157, 8159, 1363, 1595, 8158, 8160, 8162, 418, 4226, 8165, 8167, 8169, 8177, 8179, 3002, 8180, 418, 8182, 8183, 8192, 8198, 8203, 8204, 430, 8206, 8207, 8209, 8170, 8174, 414, 8175, 8171, 1876, 8172, 7536, 933, 161, 1054, 350, 8173, 1400, 1538, 1042, 213, 161, 161, 161, 1938, 8176, 3479, 466, 161, 161, 8178, 4168, 295, 161, 8181, 185, 301, 3479, 1184, 8184, 8189, 8190, 417, 8185, 8186, 1026, 8187, 301, 161, 8188, 161, 296, 304, 1184, 8191, 1573, 8193, 8197, 8194, 8195, 8196, 418, 1876, 418, 8199, 301, 8201, 8200, 8202, 8205, 2479, 1054, 2524, 8208, 465, 2129, 466, 3659, 1362, 8211, 8213, 8216, 8219, 8221, 8241, 1990, 8243, 8245, 304, 6528, 1198, 8212, 1538, 1042, 295, 161, 8214, 350, 8215, 414, 1150, 161, 8217, 1026, 414, 161, 464, 1143, 8218, 8220, 3438, 3928, 1143, 8222, 8224, 8225, 8232, 8234, 8240, 8223, 418, 8226, 8228, 1343, 161, 161, 8227, 3529, 8229, 8230, 8231, 8233, 414, 1276, 161, 8235, 8238, 8236, 8237, 8239, 414, 1482, 1160, 8242, 161, 1143, 3439, 4308, 8244, 8246, 8248, 8249, 8252, 8255, 1818, 8257, 8258, 304, 8260, 8261, 8263, 8264, 8266, 8268, 8271, 8276, 8280, 186, 8250, 8251, 8253, 414, 186, 1042, 161, 8254, 1059, 1023, 8256, 161, 1116, 1480, 8259, 409, 7320, 4465, 1949, 8262, 295, 417, 161, 161, 8265, 8267, 1042, 1026, 295, 1823, 4327, 161, 213, 1756, 161, 3002, 6549, 933, 8269, 8202, 161, 1116, 8270, 5503, 161, 161, 7212, 8272, 8273, 4883, 8275, 414, 161, 1389, 1042, 161, 161, 8274, 2142, 295, 8277, 414, 161, 8278, 161, 8279, 1143, 936, 1426, 8281, 295, 414, 213, 5246, 1042, 161, 8283, 8285, 8286, 8287, 679, 8288, 440, 8289, 8292, 304, 5284, 8294, 414, 1555, 2165, 161, 414, 5909, 161, 1756, 161, 3185, 1093, 8290, 8291, 161, 161, 161, 8293, 2894, 161, 8295, 1554, 161, 2191, 1362, 8297, 295, 1198, 8298, 414, 4252, 8300, 8301, 8303, 302, 8304, 414, 8305, 8317, 8332, 8359, 8367, 8370, 8377, 8388, 8395, 6583, 8404, 8406, 24, 25, 8973, 8998, 26, 9119, 9147, 9160, 9250, 9258, 9276, 9277, 9281, 8306, 2225, 4042, 8308, 161, 8309, 8310, 8313, 8316, 8307, 161, 1425, 1042, 1343, 161, 1212, 161, 8311, 435, 1426, 8312, 1042, 2050, 1425, 1042, 440, 161, 8314, 8315, 1042, 1459, 8318, 8322, 8325, 8326, 8327, 8329, 8330, 1363, 8331, 161, 418, 350, 8319, 8320, 8321, 2191, 1092, 8323, 161, 8324, 3603, 161, 161, 1473, 161, 1028, 304, 8328, 8333, 8336, 8337, 8340, 8358, 2915, 2568, 1126, 8334, 8335, 1640, 8338, 8339, 1459, 8341, 1362, 8343, 8345, 8347, 1343, 440, 5122, 8348, 8352, 8356, 161, 8342, 1126, 161, 8344, 350, 311, 1604, 1640, 8346, 1896, 976, 1459, 7961, 1640, 8349, 1459, 8350, 350, 8351, 8353, 8354, 8355, 8357, 161, 519, 3368, 418, 2225, 8360, 8361, 8366, 1270, 295, 161, 1900, 1405, 161, 8362, 8365, 161, 161, 8363, 8364, 2750, 556, 1093, 8368, 4461, 8369, 296, 1844, 8371, 8372, 1895, 8373, 8375, 3479, 1343, 8374, 350, 161, 1093, 1060, 414, 2165, 161, 8376, 1093, 4203, 8378, 8379, 296, 8380, 8383, 1640, 8384, 8385, 8387, 1640, 1363, 418, 8381, 1092, 8382, 1640, 1343, 213, 1797, 302, 8386, 1515, 1539, 8389, 8391, 8393, 161, 417, 8394, 8390, 161, 161, 161, 8392, 715, 1688, 414, 2378, 8396, 8397, 8398, 519, 2017, 8399, 1538, 161, 8400, 8401, 8402, 8403, 1198, 2210, 8405, 440, 8407, 8419, 8422, 8427, 8430, 8436, 8437, 8439, 8442, 8469, 8472, 8473, 8490, 8492, 3880, 8494, 8499, 8504, 1639, 5410, 1760, 7337, 8408, 8412, 8414, 8416, 5512, 161, 1814, 8409, 8410, 519, 8411, 8413, 8415, 8417, 8418, 1101, 8420, 1363, 1363, 1060, 421, 8421, 8423, 8425, 1143, 8424, 8426, 1883, 4422, 3799, 8428, 213, 1756, 8429, 1109, 161, 1639, 417, 1824, 414, 418, 8431, 3807, 8432, 1036, 8433, 8434, 8435, 161, 3833, 418, 296, 3980, 8438, 418, 5954, 161, 8440, 8441, 1365, 414, 161, 6643, 161, 8443, 8450, 8459, 1343, 8466, 8467, 8468, 8444, 519, 1027, 8448, 8449, 4952, 519, 8445, 8446, 8447, 2111, 161, 186, 161, 8451, 8453, 1938, 1482, 1198, 8456, 1036, 2115, 8457, 161, 8452, 8454, 186, 8455, 2340, 161, 556, 1092, 1332, 8458, 421, 1284, 1818, 1343, 8460, 8465, 295, 8461, 8464, 8462, 417, 1609, 8463, 418, 1885, 417, 418, 295, 161, 301, 437, 296, 1962, 418, 8470, 8471, 6643, 818, 1126, 8474, 8476, 8484, 8485, 8488, 161, 8475, 8477, 8478, 8479, 8481, 161, 3169, 414, 1323, 8086, 8480, 8482, 8483, 414, 1341, 186, 3198, 1042, 161, 8486, 8487, 435, 1458, 414, 8489, 186, 3198, 161, 8491, 8493, 207, 8495, 8496, 1343, 418, 8497, 161, 8498, 2576, 8500, 1822, 8501, 8503, 8502, 161, 414, 1143, 296, 8505, 417, 8506, 8510, 2951, 8520, 8528, 8532, 1474, 8535, 161, 8582, 8654, 8656, 7616, 5684, 8507, 161, 8509, 8508, 8511, 8513, 8514, 161, 161, 8516, 8512, 8515, 1334, 1425, 161, 1042, 8517, 8518, 186, 8519, 1150, 418, 3724, 8521, 8522, 8092, 418, 213, 8524, 161, 8525, 8526, 8523, 161, 1092, 161, 414, 8527, 8529, 161, 8530, 8531, 7478, 1538, 1042, 161, 1486, 8533, 8534, 519, 8536, 8540, 8559, 8568, 8574, 8537, 8538, 716, 8539, 1042, 1303, 1093, 161, 161, 4497, 1054, 8541, 8545, 8554, 8542, 8543, 8544, 8546, 8547, 8550, 8552, 3425, 1042, 8548, 1150, 8549, 417, 1823, 8551, 8553, 186, 1042, 161, 1128, 445, 8555, 161, 8556, 8557, 8558, 2463, 213, 161, 1093, 6159, 8560, 8565, 8551, 161, 8561, 1608, 8562, 8563, 8564, 414, 7885, 161, 186, 1303, 161, 8566, 8567, 1042, 715, 8569, 8570, 437, 161, 1127, 1823, 414, 1426, 213, 2763, 161, 8571, 8572, 8573, 8575, 8581, 435, 161, 8576, 8577, 556, 418, 1990, 8580, 1128, 8578, 8579, 1208, 1091, 296, 8583, 8602, 295, 8610, 8613, 8631, 8637, 417, 8648, 8584, 1823, 8586, 252, 8590, 8596, 8601, 8585, 8587, 1060, 414, 8588, 8589, 8591, 1425, 8594, 8595, 1150, 8592, 8593, 556, 1156, 8597, 8598, 8599, 8600, 8603, 8604, 304, 8605, 1116, 161, 8606, 8607, 556, 8608, 8609, 1426, 1092, 3325, 8611, 8612, 1212, 1208, 1042, 8614, 8620, 8625, 161, 8615, 8618, 8616, 8617, 8619, 186, 1876, 161, 1093, 715, 8621, 8623, 8622, 1257, 8624, 418, 1128, 2344, 8626, 8627, 3341, 350, 8628, 8629, 8630, 1608, 186, 1042, 161, 1303, 8632, 8635, 3631, 8633, 161, 1823, 556, 8634, 8636, 8638, 8645, 8646, 8639, 8642, 8643, 8640, 8641, 1297, 1688, 8644, 186, 1156, 296, 8647, 8649, 8651, 8650, 1770, 1608, 8652, 8653, 414, 7324, 161, 8655, 414, 8657, 3435, 8659, 8689, 8712, 8715, 8751, 8773, 8776, 8782, 8787, 8789, 8804, 8805, 8808, 8809, 8884, 8939, 8942, 8970, 3922, 8971, 3002, 5754, 8658, 635, 8660, 8661, 8674, 8675, 8677, 8681, 8685, 8686, 8662, 1405, 8664, 8666, 8669, 8671, 8673, 1044, 8663, 1538, 1042, 715, 311, 8665, 7478, 1208, 1042, 8667, 8668, 1765, 302, 437, 8670, 2040, 8672, 161, 1044, 8676, 304, 427, 8678, 2817, 8679, 8680, 8682, 4503, 8684, 8683, 186, 1257, 2050, 8687, 8673, 8688, 8690, 8698, 8703, 8708, 8709, 1778, 8691, 8693, 161, 8694, 8692, 1212, 186, 1042, 8695, 8696, 8697, 350, 161, 1303, 8699, 8700, 161, 8701, 8702, 414, 435, 3439, 1042, 161, 8704, 8705, 8707, 161, 161, 8706, 161, 2225, 3196, 418, 161, 8710, 679, 350, 427, 8711, 186, 1042, 296, 161, 213, 8713, 8714, 161, 161, 1010, 8716, 8725, 8737, 8739, 8741, 8748, 8717, 8721, 8722, 2094, 8718, 8719, 8720, 213, 7481, 1400, 1116, 8723, 1302, 8724, 296, 556, 1485, 8726, 8731, 1383, 8732, 8734, 161, 1698, 8727, 1042, 161, 8728, 161, 1198, 8729, 8730, 8733, 1212, 186, 1042, 161, 8735, 8736, 8738, 1215, 8740, 4438, 3962, 222, 8742, 8743, 8744, 8745, 186, 1042, 161, 8746, 8747, 8749, 8750, 161, 417, 8752, 8756, 161, 8759, 8753, 161, 1212, 8754, 8755, 1087, 1981, 8757, 8758, 8760, 8767, 427, 8761, 161, 8762, 8763, 8764, 8765, 1208, 8766, 556, 1184, 1437, 8768, 8769, 8770, 8771, 8772, 8774, 304, 8775, 8777, 7517, 8778, 8779, 8781, 2817, 8780, 8783, 8784, 3029, 161, 8785, 8786, 418, 1563, 8788, 418, 8790, 8792, 8799, 8800, 1242, 186, 8791, 1242, 8793, 161, 8796, 8798, 350, 8794, 186, 8795, 161, 874, 556, 1733, 8797, 161, 161, 417, 161, 421, 1060, 679, 556, 1212, 1270, 8801, 8803, 161, 1054, 8802, 1242, 295, 8806, 8807, 8711, 1305, 1389, 3280, 161, 8810, 8811, 8816, 8830, 301, 8843, 8850, 8855, 8878, 7232, 418, 8812, 8813, 8815, 8814, 304, 3554, 8817, 8819, 8822, 8826, 8818, 3041, 8820, 1555, 8821, 8823, 8824, 8825, 1198, 1242, 8827, 8828, 161, 8829, 1689, 1608, 8831, 8836, 8839, 8832, 1126, 8833, 8834, 186, 1042, 161, 1562, 8835, 161, 1156, 8837, 8838, 8840, 8841, 161, 161, 8842, 213, 1189, 8844, 8847, 1212, 1208, 8845, 161, 1282, 8846, 8848, 8849, 8851, 8852, 8853, 8854, 1208, 1042, 1128, 2147, 8856, 8862, 8865, 8873, 1150, 8857, 304, 8858, 301, 161, 8859, 213, 8860, 161, 8861, 8863, 8864, 8866, 8867, 8868, 1322, 8869, 1093, 8870, 8871, 8872, 8874, 7817, 8875, 8876, 186, 8877, 186, 2886, 8879, 8880, 8883, 1173, 1813, 706, 8881, 8882, 186, 1042, 161, 1336, 1042, 1282, 1212, 8885, 8891, 8900, 8910, 8915, 8673, 8886, 3554, 8887, 8889, 8888, 3439, 1042, 715, 161, 8890, 161, 8892, 8896, 413, 8898, 350, 8893, 8735, 8894, 8895, 4289, 8897, 186, 417, 715, 161, 161, 301, 8899, 1304, 186, 1042, 161, 8901, 8902, 8904, 161, 8907, 8903, 8905, 8906, 1128, 161, 8908, 1425, 8909, 2651, 1459, 1242, 1217, 437, 556, 213, 414, 8911, 161, 1426, 8912, 8913, 8914, 8916, 8927, 8928, 8932, 3479, 8917, 8923, 1823, 1212, 213, 350, 8918, 8922, 8919, 8920, 8921, 414, 186, 1091, 1426, 161, 8924, 8925, 8926, 186, 1091, 1608, 414, 435, 1760, 8929, 8634, 311, 8930, 8931, 1425, 1091, 1426, 8933, 8935, 8934, 161, 8936, 8937, 8938, 8940, 8941, 8943, 8960, 8965, 8967, 8944, 8947, 8959, 1425, 8945, 8946, 8948, 8949, 8957, 161, 1514, 1042, 8950, 161, 1257, 8951, 8952, 8953, 8954, 414, 8955, 161, 8956, 186, 8958, 161, 1356, 556, 8961, 1150, 8963, 8962, 186, 1156, 161, 8964, 1425, 2333, 8966, 161, 8968, 8969, 7188, 1640, 1640, 8972, 8974, 213, 1343, 1042, 8975, 8978, 8982, 8984, 8985, 8990, 8997, 295, 414, 8159, 2880, 8976, 8977, 3964, 6487, 716, 2283, 8979, 2262, 8980, 212, 3479, 8981, 213, 1343, 1756, 161, 350, 7076, 161, 1093, 350, 1060, 1938, 1418, 161, 8983, 3928, 418, 304, 2225, 3098, 628, 161, 484, 8986, 161, 8987, 414, 161, 8988, 1343, 3152, 1609, 8989, 1208, 2886, 8991, 161, 8992, 8996, 8993, 8994, 8995, 1425, 1257, 296, 296, 750, 161, 414, 8999, 9000, 295, 9008, 9011, 9012, 9017, 295, 414, 9018, 2468, 1093, 9001, 1343, 9002, 9005, 161, 185, 418, 9003, 1042, 9004, 4461, 295, 9006, 9007, 4284, 3603, 556, 9009, 9010, 2130, 3196, 418, 418, 2020, 9013, 9015, 304, 9016, 2020, 466, 9014, 3098, 3102, 3603, 3152, 421, 301, 414, 1412, 1042, 977, 9019, 9021, 9024, 9026, 9036, 9037, 9042, 9047, 9049, 9051, 9070, 9076, 9081, 9104, 9107, 1459, 9113, 5409, 9117, 9118, 519, 9020, 161, 7091, 417, 418, 9022, 9023, 161, 3227, 161, 435, 435, 4370, 2800, 9025, 9027, 9028, 9030, 9031, 295, 9033, 9034, 161, 414, 9035, 1060, 818, 9029, 1342, 996, 1750, 161, 4346, 1818, 9032, 301, 1280, 301, 301, 301, 1093, 418, 1223, 1036, 1044, 1060, 9038, 6966, 8056, 9039, 409, 161, 161, 9040, 9041, 9043, 1640, 9044, 1343, 9045, 1459, 1060, 9046, 9048, 1757, 1126, 414, 350, 161, 161, 9050, 295, 9052, 5065, 9053, 9058, 9059, 9062, 1343, 161, 9063, 9066, 161, 1640, 3102, 161, 161, 519, 9054, 9056, 4075, 161, 9055, 161, 161, 161, 417, 414, 186, 1042, 9057, 1895, 7926, 1886, 9060, 9061, 421, 435, 556, 409, 9064, 350, 9065, 9067, 1640, 9068, 9069, 9071, 9073, 9072, 9074, 9075, 519, 213, 437, 161, 1054, 161, 1532, 9077, 6283, 9080, 1949, 9078, 9079, 1087, 161, 161, 8835, 1093, 1343, 9082, 9084, 9095, 9098, 9103, 161, 9083, 1432, 161, 6706, 161, 161, 9085, 445, 2602, 9088, 301, 9089, 161, 9086, 186, 9087, 213, 556, 1485, 304, 9090, 9091, 9092, 9093, 9094, 1042, 161, 161, 1580, 421, 2147, 9096, 1343, 9097, 4055, 9099, 1515, 9100, 9101, 9102, 9105, 3002, 9106, 1028, 304, 296, 519, 350, 9108, 9109, 9110, 4168, 418, 977, 161, 1036, 161, 161, 9111, 9112, 9114, 9116, 9115, 183, 304, 161, 213, 1787, 9120, 9122, 9125, 3833, 9126, 295, 295, 9134, 9137, 1885, 161, 9121, 161, 3006, 301, 9123, 427, 678, 9124, 9127, 9128, 9130, 9129, 2683, 9131, 161, 9132, 9133, 9135, 9136, 519, 1829, 706, 161, 9138, 9143, 9144, 9146, 295, 418, 161, 7933, 9139, 750, 9141, 161, 9140, 3006, 9142, 6982, 161, 1343, 9145, 1418, 1813, 474, 213, 474, 161, 161, 161, 9148, 9150, 1343, 9151, 161, 1639, 9152, 9154, 161, 1459, 1363, 9149, 296, 3088, 9153, 9155, 9156, 9158, 304, 1343, 9159, 8379, 161, 9157, 296, 4706, 161, 4615, 9161, 9163, 213, 1087, 9165, 9168, 9170, 9178, 9216, 9219, 9237, 9242, 9245, 5751, 9162, 355, 9164, 1426, 9166, 186, 1042, 9167, 161, 9169, 9171, 5529, 9176, 3880, 9177, 9172, 161, 9173, 9174, 9175, 9179, 6744, 9182, 9186, 161, 9180, 9181, 414, 8585, 1343, 1426, 9183, 9184, 414, 186, 1042, 9185, 1343, 2340, 414, 4108, 9187, 9213, 1756, 9214, 161, 1060, 161, 4523, 9188, 414, 9189, 9191, 9193, 9200, 9206, 9208, 161, 350, 9190, 9192, 9194, 9195, 9196, 9197, 9198, 9199, 418, 9201, 9204, 9202, 9203, 9205, 9207, 1899, 9209, 9210, 9211, 9212, 414, 1418, 750, 9215, 414, 1905, 2195, 161, 9217, 1092, 5639, 9218, 1042, 9220, 9223, 9226, 9227, 9228, 9229, 4164, 161, 9221, 9222, 4003, 9224, 9225, 161, 1426, 3167, 8405, 1042, 418, 414, 9230, 2165, 1042, 9233, 1365, 9234, 9235, 9236, 1161, 161, 9231, 936, 161, 213, 9232, 975, 8135, 1913, 2196, 213, 977, 9238, 9239, 9240, 4055, 9241, 414, 3181, 161, 161, 9243, 304, 161, 161, 414, 9244, 9246, 9247, 9248, 9249, 9251, 9252, 9257, 3002, 8319, 9253, 9254, 9255, 1555, 161, 716, 1608, 414, 9256, 4065, 186, 1876, 161, 1189, 933, 304, 4901, 1868, 1150, 6341, 414, 9259, 9262, 9265, 9267, 9268, 9270, 9271, 9272, 1343, 9273, 9275, 1093, 9260, 9261, 161, 1217, 213, 161, 9263, 9264, 414, 161, 252, 9266, 440, 161, 161, 435, 1093, 1297, 296, 9269, 421, 161, 161, 1376, 1042, 9274, 418, 296, 9278, 304, 4883, 2548, 161, 9279, 9280, 9282, 213, 9283, 1899, 1297, 9284, 161, 1184, 161, 9285, 9287, 9288, 9336, 9383, 9435, 9481, 9515, 9289, 9293, 9300, 9302, 9306, 9307, 435, 9309, 9311, 9320, 213, 9322, 9324, 418, 9325, 9327, 9330, 9331, 1410, 9290, 1640, 304, 161, 9292, 9291, 9294, 161, 9295, 1892, 9296, 9298, 1876, 1121, 3603, 161, 161, 9297, 9299, 9301, 296, 304, 3267, 9303, 186, 1042, 418, 9304, 161, 9305, 5054, 418, 296, 161, 3102, 304, 161, 161, 9308, 161, 1036, 414, 418, 3102, 161, 304, 296, 1116, 9310, 186, 1042, 161, 417, 9312, 304, 9314, 9315, 9316, 9317, 9318, 295, 213, 296, 9319, 1639, 9313, 1640, 1640, 304, 3974, 1143, 435, 1459, 186, 1042, 3832, 161, 3833, 417, 3861, 9321, 161, 519, 161, 9323, 161, 161, 4108, 3754, 1042, 1230, 302, 418, 213, 9326, 4308, 1093, 161, 161, 161, 4991, 9328, 6440, 9329, 1876, 161, 161, 1891, 304, 1092, 161, 417, 1208, 9332, 9334, 9333, 1756, 161, 9335, 1343, 9337, 9345, 9347, 9354, 9360, 1343, 8791, 9361, 6504, 1223, 9367, 1990, 9369, 9377, 9380, 6790, 409, 9338, 2225, 9339, 161, 2225, 9340, 9341, 414, 1538, 1042, 1107, 161, 161, 6677, 1343, 418, 161, 1425, 9342, 296, 9344, 556, 1092, 9343, 9346, 9348, 9352, 304, 161, 9349, 296, 304, 9350, 301, 9351, 1315, 9353, 414, 186, 686, 161, 409, 9355, 1343, 9356, 161, 9358, 161, 304, 9357, 414, 3439, 9359, 161, 161, 161, 468, 1418, 418, 296, 161, 161, 9362, 9365, 418, 9366, 161, 9363, 1208, 417, 9364, 1184, 1184, 161, 9368, 161, 161, 9370, 9373, 9374, 9375, 9376, 9371, 161, 9372, 519, 9069, 519, 213, 1343, 1639, 161, 9378, 161, 1092, 9379, 9381, 7280, 9382, 296, 1981, 1341, 9384, 9386, 9390, 4284, 9391, 440, 1242, 9392, 9400, 9403, 9405, 9423, 3548, 9385, 161, 1938, 418, 161, 9387, 9388, 1727, 417, 9389, 1962, 1150, 296, 296, 295, 295, 9393, 9394, 6405, 161, 417, 414, 418, 161, 9395, 9396, 9399, 161, 9397, 9398, 213, 161, 296, 1036, 9401, 9402, 1093, 9404, 161, 418, 2817, 161, 9406, 9408, 9410, 414, 9411, 1343, 9412, 9415, 9417, 9418, 1639, 9407, 161, 1792, 9409, 161, 6950, 296, 933, 9413, 9414, 3829, 9416, 161, 161, 3730, 1343, 161, 9419, 418, 9420, 9421, 161, 1482, 161, 1500, 9422, 2270, 435, 1124, 9424, 9426, 9428, 9432, 1851, 9425, 3098, 4063, 1793, 9427, 161, 9429, 1990, 1276, 9430, 161, 9431, 1405, 9433, 304, 418, 9434, 1093, 9436, 9438, 414, 9442, 6744, 9443, 4883, 9444, 9445, 9449, 9452, 9455, 3998, 9457, 9469, 9471, 9476, 1981, 3046, 2142, 9437, 519, 161, 3778, 9439, 9441, 9440, 5839, 3603, 6452, 2706, 161, 9446, 9448, 304, 440, 1981, 1639, 421, 9447, 418, 304, 2225, 9450, 9451, 414, 418, 2868, 706, 9453, 1093, 161, 2880, 9454, 9456, 5875, 9458, 161, 3833, 295, 440, 9459, 9460, 417, 1639, 185, 213, 418, 418, 1727, 9461, 9462, 6813, 8431, 9463, 1876, 9459, 1343, 519, 9465, 9466, 9468, 161, 161, 161, 161, 350, 9464, 9467, 4301, 1640, 2868, 1143, 409, 3181, 2082, 9470, 295, 9472, 1059, 2142, 4883, 9473, 9474, 1184, 9475, 414, 1824, 9477, 9479, 3267, 1042, 5875, 9480, 3992, 2270, 414, 9478, 1042, 161, 1538, 1042, 687, 161, 9482, 9487, 9488, 9490, 9496, 9497, 9503, 222, 9506, 9509, 161, 9513, 1093, 9483, 435, 1150, 9484, 9485, 161, 186, 9486, 213, 1189, 161, 933, 9489, 468, 4073, 213, 9491, 9492, 9495, 9493, 9494, 9498, 4031, 440, 9501, 9502, 2050, 1639, 9499, 161, 161, 9500, 1042, 161, 213, 2191, 161, 1323, 6139, 417, 9504, 161, 9505, 1302, 1042, 161, 9507, 161, 9508, 6127, 1635, 9510, 161, 9512, 9511, 3928, 3098, 440, 9514, 161, 418, 9516, 9517, 296, 1876, 9519, 9523, 9526, 9529, 9518, 9520, 9521, 9522, 9524, 9525, 161, 161, 295, 161, 9527, 9528, 304, 9531, 9532, 2306, 9536, 9533, 9534, 9535, 9538, 9539, 9540, 9541, 9544, 9551, 9562, 9565, 9568, 2516, 9574, 519, 9578, 9607, 9624, 9636, 9637, 9644, 9677, 9694, 9715, 9716, 2280, 9542, 161, 9543, 2289, 414, 9545, 9546, 9548, 9549, 9550, 1938, 9547, 161, 414, 417, 3046, 161, 161, 9552, 9555, 161, 9558, 6284, 9560, 9553, 9554, 818, 9556, 301, 161, 9557, 9559, 2238, 2741, 9561, 2252, 161, 9563, 161, 5242, 1962, 440, 1885, 9564, 161, 161, 9566, 9567, 7232, 2197, 519, 1760, 9569, 2272, 9570, 9571, 9572, 161, 9573, 9575, 417, 9576, 9577, 9579, 735, 1653, 9585, 426, 9586, 9591, 9596, 9598, 9601, 9605, 1639, 9580, 9583, 9581, 417, 9582, 9584, 1101, 409, 9587, 9588, 9590, 1459, 161, 1093, 1093, 161, 1343, 9589, 417, 417, 9592, 9593, 9594, 9595, 9597, 9599, 9600, 5122, 9602, 418, 161, 9603, 9604, 1276, 1425, 9606, 9608, 1458, 9612, 9614, 9620, 9621, 9622, 9609, 304, 161, 161, 161, 9610, 4263, 9611, 9613, 439, 9615, 9617, 9616, 9618, 439, 9619, 161, 1981, 417, 3603, 874, 9623, 9625, 9626, 9627, 161, 301, 9629, 9631, 1093, 161, 1343, 186, 161, 678, 161, 9628, 161, 9630, 1109, 9632, 9633, 4707, 9634, 9635, 1604, 417, 519, 1191, 9638, 9639, 7452, 9640, 222, 9641, 161, 161, 9642, 750, 161, 9643, 350, 9645, 9649, 7210, 9650, 9651, 9652, 9654, 9658, 9660, 9662, 9663, 9670, 9671, 9675, 161, 9646, 9647, 161, 9648, 1778, 2225, 161, 161, 1981, 161, 1026, 9653, 1343, 3250, 1479, 9655, 161, 1317, 9656, 9657, 296, 463, 213, 9659, 1876, 161, 161, 161, 161, 414, 161, 161, 9661, 213, 474, 3634, 418, 9664, 9666, 161, 161, 9665, 161, 1563, 161, 1206, 9667, 1818, 6341, 9668, 9669, 296, 1459, 1425, 1231, 1608, 9672, 9673, 295, 161, 9674, 304, 161, 1093, 465, 161, 9378, 9676, 418, 3302, 161, 161, 9678, 9680, 9685, 161, 9686, 9688, 6609, 421, 9679, 9681, 9683, 1343, 161, 9682, 9684, 1092, 4898, 1797, 418, 9687, 418, 1143, 296, 414, 9689, 9690, 9692, 9691, 161, 7586, 161, 9693, 2340, 7324, 161, 414, 5368, 1523, 9695, 9696, 9697, 9701, 9703, 9706, 9707, 9708, 9711, 3029, 1756, 161, 3386, 9698, 9699, 9700, 1356, 9702, 1778, 1757, 9704, 9705, 3843, 350, 3829, 975, 1590, 1885, 9709, 465, 9710, 1428, 9712, 9713, 161, 9714, 9717, 421, 3134, 9720, 9721, 9726, 9729, 9739, 9740, 9742, 9747, 9751, 3055, 9755, 9757, 1899, 9722, 1013, 7390, 9723, 9724, 2468, 9725, 316, 1657, 9727, 1093, 9728, 1198, 414, 4781, 9730, 9736, 9731, 9732, 9733, 9734, 9735, 9737, 9738, 6598, 1092, 9741, 4111, 9743, 9745, 9744, 435, 2463, 161, 9746, 1899, 9748, 9750, 9749, 1757, 1343, 414, 1367, 9752, 7383, 9754, 9753, 4133, 161, 1787, 9756, 9758, 9760, 9767, 9779, 9761, 9763, 9765, 161, 9762, 161, 2538, 9764, 296, 161, 2538, 3827, 4879, 9766, 9768, 9777, 9778, 9769, 9770, 161, 9771, 9772, 9773, 9774, 9775, 9776, 9780, 9781, 9805, 9809, 30, 10082,31, 10830,10832,33, 11365,11367,11369,11371,35, 36, 11723,11725,37, 11885,11886,11897,11922,9782, 9793, 9795, 9799, 9802, 9804, 9783, 161, 9786, 3893, 9787, 9790, 7627, 9784, 4245, 9785, 1126, 873, 9788, 9789, 213, 715, 9791, 9792, 9794, 9796, 9797, 9798, 9800, 161, 9801, 9803, 9806, 9808, 9807, 9810, 9812, 9811, 9813, 414, 9814, 9818, 9823, 9832, 9835, 9838, 9846, 9851, 9861, 9863, 9887, 9909, 9954, 9956, 9962, 9967, 10017,10027,10031,10055,10063,10067, 10079,9815, 9816, 9817, 440, 8162, 4879, 7303, 9819, 2225, 3181, 301, 161, 4245, 1797, 9820, 929, 9821, 161, 9557, 635, 9822, 161, 9824, 9825, 9827, 9828, 2225, 9831, 1753, 161, 161, 9826, 9829, 3019, 1590, 9830, 9824, 161, 161, 161, 9833, 417, 518, 9834, 9836, 304, 161, 418, 9837, 417, 161, 418, 9839, 9841, 440, 1824, 9843, 9844, 304, 161, 9840, 1365, 3013, 9842, 161, 418, 1059, 161, 1017, 161, 9845, 418, 9847, 9850, 161, 1012, 9848, 1842, 4693, 9849, 304, 1343, 1843, 1845, 418, 4203, 161, 9852, 9854, 2280, 9855, 9856, 213, 9859, 161, 304, 9853, 304, 2969, 5468, 213, 3321, 161, 9857, 9858, 2225, 750, 440, 9860, 414, 161, 5065, 418, 161, 418, 9862, 9864, 9865, 3893, 9867, 9873, 9875, 304, 9876, 1343, 9881, 9882, 9883, 9885, 417, 484, 161, 9886, 161, 9866, 161, 296, 9868, 161, 304, 9869, 161, 304, 9870, 9871, 519, 9872, 9874, 9877, 9879, 9880, 1371, 1297, 1823, 252, 9878, 1216, 161, 301, 1191, 9884, 9884, 4598, 304, 1640, 434, 9888, 9893, 9897, 9898, 9900, 9902, 9906, 9907, 417, 7126, 2817, 161, 9889, 9890, 519, 9891, 9892, 9894, 161, 9895, 9896, 414, 1470, 417, 161, 1609, 9899, 519, 418, 418, 2393, 9901, 350, 1149, 467, 9903, 9905, 9904, 4615, 304, 296, 9908, 295, 296, 9456, 161, 9910, 9911, 9913, 9915, 9919, 6032, 9920, 161, 9923, 9933, 440, 1343, 9935, 9943, 9944, 9948, 1044, 2082, 9951, 9953, 414, 161, 9912, 2166, 9914, 2766, 161, 161, 161, 2526, 161, 161, 418, 9916, 301, 161, 9917, 9918, 161, 2196, 301, 1937, 678, 213, 161, 1150, 161, 706, 9921, 6217, 519, 9922, 414, 3098, 2651, 161, 9924, 301, 9925, 301, 9931, 9932, 301, 9926, 414, 161, 161, 161, 9927, 7408, 6122, 9930, 9928, 9929, 418, 222, 418, 9934, 7145, 161, 161, 9936, 9938, 9940, 9942, 414, 9937, 161, 706, 9939, 9941, 414, 161, 3435, 9945, 9946, 304, 418, 9947, 1050, 418, 9949, 8411, 2280, 9950, 9952, 296, 4058, 161, 9955, 9957, 1217, 9958, 9912, 2309, 9959, 1126, 1387, 9960, 9961, 9963, 9964, 9965, 519, 9966, 9968, 9970, 9973, 9976, 9977, 9984, 9985, 9986, 9993, 9999, 10001,10002,10007, 10008,10011,10012,10016,9969, 3610, 9971, 9972, 9974, 7574, 1036, 414, 1060, 9975, 296, 418, 414, 418, 9978, 435, 9981, 6755, 9979, 9980, 9982, 9983, 4683, 418, 1418, 161, 161, 213, 414, 9987, 9988, 9989, 1343, 1756, 1126, 3771, 9992, 1280, 161, 350, 9990, 1143, 9991, 2196, 1824, 9994, 9995, 9996, 418, 1498, 161, 9997, 161, 9998, 1640, 467, 10000,350, 440, 2028, 161, 301, 10003,10004,10005,10006,1124, 3680, 409, 161, 421, 519, 6003, 3833, 161, 1011, 161, 213, 161, 474, 161, 10009,10010,5138, 161, 161, 161, 10013,10014,409, 10015,1754, 161, 10018,10020,10022,161, 10024,10019,1160, 1538, 10021,5830, 1609, 1640, 556, 10023,10025,10026,414, 10028,10029,1876, 161, 222, 5783, 10030,161, 414, 161, 5294, 161, 161, 10032,350, 10037,10040,1480, 161, 5780, 10049, 1787, 161, 10053,1412, 10033,10034,10036,10035,10038,161, 10039,10041,10042,10043,10045,10044,463, 213, 10046,418, 10048,10047,213, 1937, 161, 10050,10051, 10052,10054,4527, 1515, 2750, 10056,10057,10059,440, 10062,161, 1037, 1191, 414, 161, 3178, 1735, 10058,161, 161, 355, 161, 10060,1093, 1747, 3674, 10061, 4679, 414, 161, 161, 161, 161, 1481, 161, 222, 161, 10064,1093, 1093, 6405, 440, 10065,628, 10066,414, 161, 1208, 6309, 213, 1482, 414, 418, 10068, 5481, 10070,1818, 6066, 10074,301, 1850, 10077,10078,10069,10071,10072,10073,2225, 10075,10076,630, 1827, 161, 10080,161, 10081,10083,10084,10085,161, 10086, 10087,10088,10123,10160,10220,10228,10243,10291,10315,10324,10335,10344,32, 10475,10529,10567,10572,10616,10619,10657,10741,10769,10774,10815,10823,10824,10826, 10089,10092,10101,10102,6741, 10104,10109,1026, 10115,10118,10119,10122,1482, 10090,10091,10093,10095,3102, 10097,1343, 7494, 301, 10099,10100,161, 10094,2596, 10096,10098,420, 161, 3799, 2610, 10103,414, 301, 10105,10106,10107,161, 161, 161, 8073, 161, 4314, 161, 10108,414, 161, 10110,1818, 10112,1500, 161, 161, 10111,10113,10114,8181, 10116,1744, 6309, 213, 10117,161, 409, 161, 418, 1858, 1858, 3620, 716, 2191, 5164, 10120,440, 418, 161, 414, 161, 584, 311, 10121,213, 161, 2162, 1426, 4003, 414, 10124,10130,10132,10142,10147,1604, 10149,10153,1093, 10158,10125,10126,10127,10128,161, 3913, 6950, 1421, 1425, 1042, 10129,10131,301, 161, 10133,10135,10136,10139,10134,421, 9069, 10137,10138,161, 161, 10140,213, 10141,10143,10144,161, 10145,10146,10148,1365, 302, 1470, 1143, 1818, 414, 1151, 10150,10152,304, 161, 10151,414, 996, 1036, 996, 10154,10156,10157,10155,161, 519, 409, 421, 304, 418, 418, 296, 4898, 10159,10161,10171,10179,10181,10189,10193,10197,10217,3088, 10162,161, 10164,3085, 10165,10167,10169,10170,1297, 10163,2345, 161, 10166,519, 10168,414, 818, 10172,1245, 10174,4041, 10176,10177,10178,5481, 10173,10175,1405, 716, 10180,7126, 1245, 10182,10183,10185,10187,10184,161, 1284, 10186,10188,1733, 304, 1143, 10190,1343, 161, 161, 10191,10192,10194,10196,304, 10195,1334, 1425, 1042, 10198,301, 10199,10202,421, 10210,10213,4055, 10215,1093, 2880, 1042, 10200,10201, 1756, 10203,10206,10204,10205,10207,10208,10209,10211,1459, 10212,1208, 1586, 1426, 10214,161, 1818, 3103, 10216,161, 10218,484, 10219,8645, 1208, 5122, 10221, 10222,3766, 10223,10224,2963, 3959, 1764, 414, 10225,1208, 10226,186, 10227,1150, 1092, 304, 10229,161, 10230,2115, 635, 414, 161, 10231,10233,10234,10236, 1036, 10238,10239,10240,1875, 10223,161, 10232,161, 1962, 3799, 213, 10235,161, 10237,1042, 1343, 296, 161, 301, 161, 10241,10242,10244,417, 10245,10255, 10257,10272,10276,10280,10288,10289,1297, 1116, 1245, 1437, 2596, 1470, 3160, 10246,2167, 10248,161, 10254,10247,186, 427, 296, 161, 2283, 10249,10251,10252, 10250,2787, 3439, 1042, 161, 10253,427, 414, 1756, 161, 1216, 311, 10256,1226, 10258,10263,1302, 10265,10269,1883, 10259,10260,10261,10262,10264,8905, 161, 1208, 10266,10267,10268,2851, 1425, 10270,161, 556, 10271,1189, 8680, 1283, 10273,294, 10274,10275,161, 296, 10277,10279,161, 1744, 304, 10278,467, 186, 463, 10281,10286,750, 468, 10282,186, 10283,161, 10284,10285,161, 1059, 10287,213, 2428, 10290,519, 1208, 10292,10293,10300,1343, 6393, 10303,10314,2316, 161, 4577, 161, 4003, 10294,8676, 430, 10295,10299,817, 10296,10297,10298,1093, 1689, 10301,1470, 10302,10304,10310,10313,213, 679, 1787, 8612, 10305,1493, 304, 304, 161, 1787, 10306,10307,10308,10309,1080, 311, 10311,10312,350, 679, 350, 10316,10318,295, 10320,10321,6386, 10322,418, 10317,414, 1109, 161, 10319,679, 1037, 10323,10325,3541, 10327,435, 10329,10331,10332,10333,10326,295, 1343, 10328,418, 186, 10330,465, 409, 296, 750, 10334,408, 161, 161, 10336,10340,10341,5532, 10337,466, 10338,355, 10339,418, 936, 418, 10342,10343,213, 10345,295, 304, 421, 414, 10346,10381,10384,10388,10389,10396,10399, 10405,10407,1851, 10432,10439,3049, 10444,10455,10460,10463,10465,10466,10469,4374, 10347,10351,10355,10360,10366,929, 10367,10369,161, 10371,10372,10374,10375, 10376,2998, 10380,2040, 10348,2378, 10349,161, 10350,484, 10352,10354,10353,186, 10356,10357,10358,10359,10361,10362,716, 10363,301, 10364,10365,10368,1173, 161, 1474, 161, 213, 10370,2225, 161, 484, 1590, 161, 161, 1563, 1403, 304, 10373,1958, 304, 10377,304, 10378,10379,1823, 295, 295, 1899, 10382,10383, 1989, 161, 1937, 420, 10385,1875, 2378, 10386,1778, 10387,1823, 1342, 10390,10391,7479, 10223,10392,1284, 10393,10394,10395,10397,10398,2000, 1980, 10400,10402, 10404,10401,1341, 1228, 10403,7143, 1245, 8413, 161, 10406,1060, 10408,10413,10417,10420,301, 10421,161, 10426,10428,10429,161, 10409,10410,10411,10412,1189, 1156, 10414,10416,1128, 10415,161, 2651, 10418,10419,186, 3654, 161, 996, 519, 301, 161, 10422,10423,10424,10425,161, 10427,161, 161, 1342, 304, 10430, 10431,1216, 186, 3321, 161, 161, 414, 10433,1639, 4638, 10437,161, 10434,706, 10435,10436,10438,466, 10440,296, 10442,3157, 161, 161, 161, 10441,10443, 1805, 1245, 1036, 10445,2047, 10446,10447,10448,10450,10454,1036, 2142, 1093, 304, 10449,304, 414, 304, 304, 1191, 161, 161, 10451,161, 10452,10453,5337, 1037, 4937, 10456,10458,1191, 10459,161, 10457,818, 519, 161, 706, 1962, 1087, 519, 10461,10462,9829, 519, 10464,435, 161, 5556, 3964, 818, 161, 10467, 1405, 304, 1059, 5233, 8746, 304, 1093, 10468,301, 10470,10471,10474,10472,10473,3855, 556, 10476,213, 10486,10487,10499,519, 295, 10505,10506,10525,10527, 10528,1343, 6956, 519, 10477,10478,10483,10484,1342, 2040, 304, 10479,2312, 10480,10481,161, 5517, 10482,161, 519, 10485,10268,414, 10488,10490,10491,161, 10493,301, 10496,295, 301, 10489,10492,186, 301, 10494,161, 10495,435, 161, 161, 296, 10497,161, 10498,161, 161, 304, 355, 414, 10500,1150, 8421, 10502,304, 519, 10501,161, 1150, 434, 10503,2430, 10504,161, 296, 1042, 6446, 10507,10511,10513,2060, 10523,1093, 10524,1173, 301, 10508,10509,10510,10512, 1573, 1335, 414, 10514,161, 302, 9399, 10516,10521,161, 10515,10517,10518,10519,10520,1425, 1091, 686, 161, 10522,161, 1744, 161, 10526,1824, 222, 1036, 161, 409, 10530,10532,10533,213, 10535,10536,10537,10538,10544,5980, 10545,10546,10550,10561,10562,2655, 10565,10566,161, 222, 10531,2596, 10534,435, 418, 1036, 1459, 6785, 10539,10541,10542,304, 161, 519, 10543,301, 161, 10540,1093, 161, 301, 418, 304, 417, 5583, 418, 5671, 10547,10548,161, 1060, 213, 161, 161, 350, 161, 161, 1889, 10549,161, 418, 417, 1150, 7007, 10551,10558,418, 10559,295, 10552,10553,10554,10555,10556,1426, 10557,10560,161, 463, 4063, 1962, 418, 435, 1208, 10563,295, 418, 10564,161, 6643, 161, 519, 1910, 3928, 1042, 304, 10568,10569,3837, 1470, 10570,10571,10573,10585,10591,10595, 10599,10603,10604,10612,10613,2374, 10574,10575,10576,10582,10584,161, 10577,186, 1042, 10578,1823, 10579,10580,10581,10583,161, 2782, 818, 10586,818, 161, 10587,10588,10589,1042, 161, 1150, 350, 161, 10590,10592,10594,10593,186, 1257, 161, 1283, 10596,10597,10598,519, 706, 1903, 10600,161, 1208, 10601,556, 10602,186, 1156, 1608, 161, 1028, 1042, 295, 10605,10606,10609,10610,10146,10607,10608,1322, 186, 2886, 1426, 468, 8612, 1470, 10611,161, 10614,161, 10615, 161, 10617,10618,10620,10622,1109, 10623,161, 10626,10628,7106, 10630,161, 10636,161, 10649,10654,10655,10656,161, 3988, 936, 10621,161, 213, 311, 519, 1060, 10624,295, 1191, 10625,10627,4582, 3551, 556, 8634, 1813, 10629,3041, 1425, 10631,296, 2221, 10632,10633,10634,10635,10637,10641,10642,161, 10645,10646, 10638,10639,10640,10643,10644,161, 161, 2183, 8095, 418, 418, 10647,10648,10650,474, 5797, 10651,1823, 519, 10652,161, 4583, 161, 10653,409, 4789, 2028, 430, 10658,10664,10665,10674,10676,10680,10682,10686,10694,10697,10698,10702,10704,10706,10709,10715,10722,10725,10738,161, 7232, 10659,10662,1455, 10660,10661, 161, 161, 6358, 161, 10663,10666,10668,10670,10671,10667,8899, 161, 10669,2428, 295, 1126, 4532, 10672,2817, 10673,10675,10677,301, 10678,519, 10679,2817, 1538, 1257, 161, 10681,10683,4278, 295, 10685,750, 10684,418, 161, 5319, 1418, 434, 10687,10688,10690,2111, 10691,350, 10689,1328, 7885, 1042, 161, 10692, 10693,10695,10696,5409, 4335, 1092, 3778, 161, 2976, 10699,296, 706, 10700,10701,10703,350, 1101, 185, 10705,9101, 10707,519, 10708,10710,10711,10714,8363, 750, 4203, 186, 10712,10713,519, 2907, 5021, 1813, 2817, 304, 10716,10717,10719,7565, 10718,10720,10721,10723,10724,304, 2442, 1305, 10726,10729,10730,10733, 10727,1470, 10728,10731,10732,1334, 186, 750, 1050, 10734,10735,10736,10737,2886, 161, 10739,10740,10742,10745,10756,3609, 295, 10758,10760,10767,10768,10743, 10744,1962, 2040, 1555, 10746,5424, 9102, 10750,10755,10747,186, 10748,1426, 556, 418, 10749,715, 10751,10753,715, 161, 10752,418, 10754,10121,10757,10759, 1426, 1297, 10761,10762,5122, 350, 4207, 421, 10763,10764,10765,10766,4203, 465, 6066, 6393, 161, 3803, 2280, 1198, 435, 9292, 10770,161, 10771,10772,10773, 10775,10785,10794,10807,10808,2367, 10776,10777,10779,10780,10784,10778,10781,10782,10783,1208, 1308, 4927, 3002, 161, 3724, 10786,10790,10791,10787,10788,1538, 1042, 10789,161, 4193, 213, 440, 6035, 161, 10792,10793,161, 2963, 316, 10795,10796,296, 10797,10802,936, 1405, 10806,519, 1305, 1329, 414, 584, 10798, 5065, 186, 10799,10801,1343, 161, 10800,10803,2225, 440, 161, 161, 10804,10805,1482, 260, 161, 186, 10809,10810,10811,1093, 10812,10814,161, 161, 161, 9972, 1208, 10813,10816,3559, 10818,4062, 10819,10822,161, 161, 1143, 10817,161, 1093, 1126, 10446,161, 435, 2196, 10820,304, 10821,9633, 2221, 4693, 295, 10825,1363, 10827,10829,10828,418, 10831,10833,10834,5083, 10835,10836,10871,10884,10908,10917,10944,10966,10312,417, 10984,10985,11011,11033,11048,11052,11067, 34, 11332,11341,11342,11361,11363,10837,10843,10852,10855,10858,161, 10859,10863,161, 10865,10868,10838,10840,10842,10839,414, 1393, 10841,10844,1086, 10851, 10845,10846,1425, 10847,10848,10849,10850,10853,1042, 519, 10854,161, 1364, 1698, 430, 10856,10857,1060, 1060, 1814, 1500, 1426, 10860,10861,10862,750, 10864, 161, 2412, 10866,10867,10869,10870,10872,10878,10879,4382, 10882,1590, 5512, 161, 10873,161, 10874,10875,10876,10877,418, 1198, 161, 3807, 10136,10880,10881, 161, 10883,10885,10887,10888,10889,10890,10899,10903,414, 10886,9829, 1778, 1868, 1793, 1245, 161, 1778, 947, 5481, 161, 3194, 9557, 1245, 556, 414, 161, 3959, 10891,10895,10896,1756, 1343, 1481, 10898,161, 161, 10892,10894,161, 10893,936, 740, 1060, 10897,417, 186, 311, 301, 10900,10901,10902,10904,10907, 10905,1425, 1257, 10906,10909,5715, 301, 10911,10912,301, 10910,161, 161, 1042, 161, 10913,10916,10914,10915,1228, 301, 3113, 10918,10919,10922,10927,10928, 10929,7976, 1851, 10930,10931,10935,4482, 10937,10942,161, 10920,10921,161, 161, 408, 10923,10924,7000, 10925,10926,2133, 1060, 2182, 418, 4678, 5563, 296, 3013, 10932,10933,10934,492, 1823, 465, 10936,1916, 161, 296, 1042, 161, 1608, 10938,10939,1042, 10334,4139, 10941,161, 1343, 10940,408, 304, 435, 295, 1343, 10943,10945,10946,10947,10960,10312,10962,10285,1470, 1245, 10948,10955,440, 10959,161, 10949,10951,161, 10950,10952,161, 10953,1198, 10954,10956,10957, 10958,10961,1823, 10963,10964,10965,10967,213, 10969,10972,1981, 10974,1343, 10979,10982,161, 2316, 10968,7330, 10970,8876, 10971,1841, 10973,295, 1608, 161, 3282, 213, 10301,417, 10975,10970,10976,5532, 6956, 5536, 10977,161, 10978,161, 414, 213, 1028, 10980,3432, 10981,6904, 161, 10983,10312,414, 10986,10987, 10990,10991,295, 10994,414, 10995,1962, 11006,1949, 11009,1885, 1329, 1173, 10988,421, 10989,213, 1126, 161, 161, 1778, 1935, 161, 10992,161, 10993,10996, 10999,11002,1343, 11005,161, 161, 1882, 10997,1343, 186, 707, 10998,635, 11000,161, 161, 11001,420, 1013, 6454, 11003,418, 11004,296, 1604, 11007,11008, 1191, 519, 11010,1778, 1284, 1470, 11012,11018,11021,213, 11024,11026,11029,4252, 11030,11031,6956, 519, 11013,11015,161, 11016,818, 11014,519, 3361, 3006, 11017,11019,161, 11020,161, 421, 11022,2272, 11023,161, 3041, 409, 5854, 1538, 11025,161, 421, 556, 350, 421, 186, 11027,11028,2784, 1470, 11032,11034, 11035,11036,8244, 11038,11039,11040,11043,11045,11046,161, 161, 6113, 1249, 4065, 161, 418, 3013, 8672, 161, 1412, 1129, 11037,213, 4883, 1371, 161, 1026, 414, 3302, 161, 161, 161, 161, 1093, 2568, 556, 1228, 161, 161, 435, 1184, 161, 161, 11041,1042, 418, 11042,414, 161, 4407, 1017, 11044,11047,11049, 161, 11050,11051,2195, 161, 3443, 1498, 1935, 519, 11053,11055,11056,11058,11061,8410, 11065,11066,1935, 3807, 11054,11057,11059,11060,818, 519, 2252, 519, 818, 11062,11063,11064,213, 161, 3423, 4580, 1042, 9958, 161, 5122, 3959, 1109, 11068,9928, 7106, 1604, 11074,11075,8413, 11069,1191, 11070,186, 11071,213, 11072,556, 1092, 1198, 11073,414, 706, 161, 161, 11076,11078,11079,11077,3730, 11080,11109,11114,3233, 11158,11163,11167,11177,11190,11213,11214,11219,11229, 11234,11247,11275,11280,11289,11302,11081,11082,11086,11089,11092,11093,11103,11104,11090,6890, 1405, 11083,11084,11085,11087,11088,11090,519, 11091,818, 161, 1250, 11094,11095,11097,11101,11096,11098,11099,11100,186, 1042, 1303, 161, 11102,1555, 635, 11105,11108,11106,11107,1426, 3555, 11110,11111,11113,11112,11115, 11116,11117,11120,11125,11128,11146,161, 11155,11118,296, 465, 11119,11121,11122,1093, 11123,11124,1608, 1425, 1042, 11126,11127,1208, 1823, 11129,11131,11132, 186, 11139,11141,11143,11130,11133,11134,11136,11135,716, 11137,11138,11140,6104, 11142,11119,304, 9379, 11144,11145,11147,11154,5122, 1188, 11148,11151,11149, 11150,161, 11152,11153,11156,11157,1302, 1091, 11159,11160,11161,11162,11164,11165,11166,11168,11174,11169,11172,11170,11171,161, 3198, 11173,11159,11175,11176, 474, 11178,1042, 3766, 295, 11183,11187,11179,161, 161, 11182,11180,11181,11184,11185,11186,11188,1223, 11189,11191,2111, 11196,11192,11193,11194,11195,11197, 11203,11205,11208,11198,11201,11199,11200,11202,11204,11206,11207,11209,11210,11211,11212,161, 11215,11216,8818, 11162,11217,11218,11220,11222,11226,10237,3203, 161, 11221,161, 11223,11224,11225,11227,11228,435, 186, 1231, 11230,11231,161, 3479, 11232,11233,11235,161, 11238,1534, 11236,11237,1555, 11239,11241,11243, 11240,11242,11244,11245,11246,11248,11252,11255,11262,11266,11274,11249,11251,11250,463, 11253,1059, 11254,3639, 3631, 1042, 11256,11259,11257,2040, 11258,11260, 11261,186, 1823, 11263,11264,1208, 11265,556, 437, 11267,11268,304, 11269,11270,11271,11272,11273,11276,11277,161, 11278,11279,4995, 11281,11288,11282,11283, 11284,11285,11286,11287,11290,11295,11297,11299,7332, 11291,11292,11293,11294,8682, 161, 350, 11296,1437, 2167, 715, 11298,4058, 1282, 11300,11301,11303,11307, 11308,295, 11316,11318,11330,11304,11305,519, 11306,1797, 161, 1195, 11309,11310,11311,11313,11312,1478, 213, 11314,11315,11317,11319,11322,11324,11328,11320, 975, 11321,186, 1091, 161, 11323,11325,1305, 11326,11327,1208, 1257, 1426, 11329,11331,2894, 5190, 1823, 11333,11334,11339,11340,11335,11337,11338,161, 4478, 161, 11336,4203, 296, 1093, 11343,11349,11356,11360,213, 190, 1474, 414, 11344,3002, 161, 11345,1555, 161, 11346,213, 11347,213, 11348,11350,11352,11354, 8946, 519, 11351,1042, 161, 11353,2225, 161, 8909, 11355,11357,11358,11359,10693,1042, 11362,1482, 417, 161, 418, 11364,8173, 1297, 11366,484, 11368,11370, 11372,11373,414, 11374,11375,11386,11399,11403,11407,11417,11419,519, 11421,11422,11440,11469,11498,11513,11518,11520,11552,11561,11563,11582,11587,11608,11609, 11610,1150, 1432, 1036, 11376,11378,11380,5409, 1904, 414, 11381,417, 6085, 11377,1092, 161, 161, 11379,161, 414, 414, 408, 11382,3827, 11383,11385,161, 6520, 484, 11384,414, 2094, 461, 1150, 11387,161, 11390,11393,11388,9687, 11389,295, 414, 161, 1376, 161, 11391,2050, 11392,414, 8780, 186, 1042, 161, 11394,11395,11396,11397,11398,186, 1042, 161, 1128, 437, 1093, 11400,304, 2289, 3689, 417, 1639, 11401,1042, 161, 161, 3302, 161, 161, 11402,1459, 252, 414, 2280, 304, 3730, 11404,11405,11406,2280, 1042, 295, 161, 161, 2270, 10911,414, 573, 418, 11408,11410,5065, 11413,11415,2165, 11416,4217, 7761, 1609, 1899, 11409,11411,11412,11414,628, 5285, 556, 6190, 1126, 252, 418, 1412, 11418,161, 161, 1662, 304, 213, 463, 161, 1474, 4314, 417, 11420,2225, 304, 1109, 414, 418, 11423,304, 11424,11425,11427,11430,1343, 440, 11435,11437,1026, 1026, 304, 418, 474, 161, 608, 11426,161, 161, 1036, 1087, 418, 706, 11428,11429,296, 1851, 414, 11431,1208, 1657, 2165, 11433,9705, 161, 161, 2868, 11432,161, 2195, 161, 161, 11434,417, 11436,11438,11439,11441,11444,11447, 11455,304, 11467,304, 11442,418, 11443,1990, 608, 420, 4879, 11445,11446,6131, 2539, 295, 11448,161, 11451,408, 11449,11450,11452,11453,11454,1310, 463, 2309, 11456,11466,11457,11459,11460,11461,1432, 11464,608, 161, 2345, 11458,1425, 1257, 10350,161, 484, 161, 5669, 11462,161, 418, 11463,1722, 161, 11465, 11468,11470,11472,2028, 11478,11479,6006, 1604, 11483,11484,11485,11486,9227, 11487,11492,417, 11496,1305, 301, 304, 161, 11471,1470, 1112, 11473,11474,11475, 1343, 213, 7028, 304, 4583, 417, 11476,1343, 11477,161, 11480,11481,161, 161, 11482,161, 418, 4073, 417, 301, 161, 301, 1343, 418, 213, 418, 418, 11488,11489,304, 1060, 414, 161, 161, 2799, 11490,161, 11482,11491,417, 9829, 11493,11494,1093, 11495,1180, 1036, 304, 2028, 161, 11497,1107, 11499,11500, 9984, 11501,1107, 11503,11505,1343, 11507,1387, 161, 213, 304, 4287, 519, 161, 296, 414, 161, 11502,418, 3196, 11504,1418, 11429,186, 11506,414, 1639, 11508,11509,5069, 11511,3274, 11510,11512,11514,7320, 1042, 304, 11515,417, 11516,295, 11517,11519,11521,11523,11524,11528,1343, 11529,11532,11533,11534,11537, 11540,11543,11545,11549,11551,1161, 161, 519, 414, 11522,1112, 1470, 11525,1028, 936, 1036, 11526,161, 11527,301, 1867, 161, 11530,161, 11531,1657, 161, 161, 161, 1109, 11535,296, 11536,161, 295, 3002, 1474, 10644,11538,11539,4169, 295, 608, 11541,1011, 161, 11542,1216, 1640, 11544,1883, 3167, 161, 435, 11546,11548,5642, 301, 418, 161, 11547,161, 409, 11550,418, 161, 4073, 161, 2840, 9243, 1230, 11553,295, 11554,11555,11557,10451,11556,295, 435, 296, 304, 11558,11559,11560,1847, 2387, 161, 1042, 1026, 11562,161, 1538, 304, 304, 161, 161, 11564,11571,11575,11576,413, 556, 161, 1284, 11580,4164, 11565, 11566,11569,11570,11567,1042, 161, 6755, 11568,161, 3487, 11572,11573,11574,414, 7586, 1107, 11577,1387, 11579,3435, 462, 3368, 11578,11581,161, 11583,11584, 304, 161, 11585,161, 161, 11586,11588,11589,11590,4500, 414, 11591,11592,213, 11606,11607,295, 186, 6654, 1042, 304, 161, 5166, 1042, 414, 1143, 11593, 3302, 11594,11595,5763, 11596,11598,11599,11601,11604,1459, 1161, 161, 161, 161, 1093, 161, 1060, 1459, 1640, 11597,635, 2378, 11600,976, 295, 11602,11603, 304, 1121, 9464, 1603, 630, 4109, 11605,213, 10262,435, 7586, 1387, 304, 418, 435, 11611,5503, 11612,11646,11673,11690,11707,11708,11722,11613,11614,466, 11616,11618,11624,11625,11628,1150, 11630,11633,11634,11636,161, 11637,11640,11641,10653,11645,716, 421, 161, 161, 11615,1109, 11617,418, 418, 1818, 11619, 2050, 3778, 11620,4105, 186, 1876, 519, 11621,1722, 414, 343, 180, 11622,161, 11623,161, 2472, 414, 11626,1938, 1208, 1042, 11627,161, 11629,414, 3267, 161, 11631,2050, 161, 11632,11635,519, 355, 161, 161, 161, 11638,11639,11642,213, 1426, 8181, 4308, 161, 11644,11643,3227, 5494, 1011, 1733, 161, 11647, 11651,11119,11653,11655,295, 11656,435, 301, 11661,11663,2750, 11666,11668,11669,11648,11649,213, 186, 3654, 1042, 161, 1706, 413, 11650,1876, 1756, 161, 161, 161, 161, 3964, 11652,3529, 11654,11657,434, 296, 1270, 1350, 11660,11658,11659,4794, 11662,409, 418, 4203, 418, 1351, 9243, 1109, 435, 295, 11664, 11665,4308, 295, 161, 414, 11667,2730, 213, 161, 161, 213, 161, 350, 435, 1851, 11670,296, 11672,11671,11674,4284, 11676,11678,11680,11681,11683,11685, 11687,11689,11675,11677,1538, 1042, 161, 1609, 11679,686, 686, 161, 11682,1583, 1143, 1426, 1876, 161, 5934, 161, 11684,161, 3731, 161, 11686,1640, 1640, 161, 11688,1876, 161, 161, 1054, 161, 3274, 1938, 1740, 11691,11692,11694,11695,11696,11698,11701,11703,11704,1208, 350, 11693,161, 2040, 2225, 874, 11697, 161, 1538, 4313, 11699,3913, 1036, 11700,976, 11702,9169, 161, 11705,11706,1208, 1042, 161, 1810, 161, 414, 11709,1938, 161, 11711,11715,11718,213, 11721, 7091, 11710,10773,414, 11712,11714,186, 11713,11716,2847, 426, 11717,6164, 11650,1042, 3762, 11719,1143, 11720,161, 11710,161, 1109, 4307, 1042, 4252, 3302, 7332, 11724,11726,11727,11728,11740,11751,11761,11766,11772,11776,11778,11779,11782,11789,11804,1042, 11836,7888, 11847,11867,11875,11883,11884,1343, 3962, 1036, 706, 11729,11730,11732,11733,5736, 11735,11736,161, 11738,161, 409, 186, 161, 161, 11731,161, 608, 3250, 11734,936, 161, 10446,161, 186, 304, 161, 213, 11737,474, 161, 2750, 161, 161, 11739,1990, 11741,222, 2225, 11742,11747,1037, 11749,11750,1093, 161, 1059, 222, 11743,11745,418, 11744,933, 630, 304, 1126, 426, 304, 11746,4514, 161, 414, 5285, 6309, 1270, 1343, 4922, 11748,161, 161, 3670, 11752,11754,11755,3847, 3460, 440, 161, 11759,11753,414, 421, 2262, 161, 1343, 11756,161, 11757,11758,11760,421, 161, 2539, 421, 11762,11763,11764,11765,3002, 161, 4352, 161, 417, 2969, 421, 11767,5683, 295, 11770,11771,414, 11768,11769,304, 1343, 1143, 161, 161, 161, 161, 350, 161, 8376, 161, 1151, 296, 11773,11774,874, 635, 11775,4203, 296, 1092, 11777, 161, 161, 161, 418, 630, 434, 350, 7014, 9937, 11780,414, 11781,1727, 1343, 161, 161, 706, 11783,11784,3933, 1876, 11786,3103, 11788,2196, 161, 161, 161, 440, 161, 3002, 11785,161, 3534, 1143, 11787,161, 1756, 161, 161, 161, 3002, 11790,11791,1458, 161, 1823, 304, 11795,11796,11801,11802,1430, 3799, 11792,161, 11794,11793,1962, 417, 11797,750, 11799,161, 11798,11800,1151, 2434, 1538, 1042, 11803,7937, 213, 11805,1998, 11806,11809,414, 11810,11811,11595, 11813,11814,11820,11823,11824,11829,1112, 11830,11833,4164, 11834,9167, 1539, 635, 11807,435, 9176, 11808,414, 1026, 414, 304, 418, 418, 11812,161, 11815, 1876, 11818,161, 161, 11816,161, 4583, 11817,11819,418, 11821,11822,933, 763, 213, 418, 1823, 1823, 11825,11826,11827,161, 1459, 161, 1343, 161, 1539, 635, 1343, 11828,414, 750, 1539, 3153, 2362, 11831,418, 3766, 1885, 11832,3391, 11835,2221, 1101, 414, 11837,11838,716, 11843,11846,10479,10379,11839,11840, 161, 1364, 161, 11841,1283, 11842,11844,11845,414, 1410, 467, 11848,426, 1905, 11850,4306, 9167, 5878, 11851,11852,11853,11854,11857,161, 11858,11861,11862, 11865,11866,1150, 435, 11849,8634, 519, 1342, 519, 1036, 435, 161, 414, 161, 556, 161, 185, 213, 1042, 11855,11856,2270, 161, 1343, 414, 11859,11860, 11863,11864,409, 11868,2245, 2225, 11871,11873,301, 161, 11869,11870,3446, 1608, 11872,5468, 3551, 2596, 1538, 11874,1343, 252, 11876,3181, 11878,11879,11880, 11882,161, 11877,414, 3198, 1036, 161, 9732, 161, 11881,474, 161, 418, 3802, 7961, 350, 9724, 161, 3551, 414, 2448, 11887,11889,11892,11895,295, 418, 301, 11888,1126, 1093, 750, 421, 1010, 11890,11891,1426, 1042, 161, 11893,11894,304, 6405, 3185, 7280, 1640, 11896,421, 426, 11898,11899,11900,5469, 1876, 11901,11906,11907,11908,11912,161, 1198, 3529, 414, 161, 1042, 161, 414, 414, 11902,11903,5597, 2166, 11904,11905,2150, 161, 301, 716, 185, 11909,414, 11910,463, 11911,161, 1899, 11913,11915,2652, 418, 11914,11916,11917,11918,11919,11920,11921,11923,9536, 421, 11924,421, 11925,295, 11926,11929,11995,12008, 12064,12112,12121,12140,12175,12184,12231,12237,39, 12371,40, 12653,12654,12688,12704,12754,12838,12864,12898,12950,41, 13162,13180,11927,11928,11930,11931, 11932,11937,11938,750, 11939,1109, 11940,11969,11981,11988,2017, 3778, 213, 1134, 304, 161, 161, 4203, 11933,11934,418, 11935,418, 414, 161, 161, 11936, 417, 3928, 1092, 304, 213, 213, 161, 11941,186, 3198, 1150, 11942,11943,11948,11953,11957,11803,11961,11962,426, 11968,440, 1458, 1899, 3013, 414, 11944, 11945,10867,161, 11947,418, 418, 304, 161, 161, 11946,304, 11949,11951,11950,11952,11954,11956,4308, 3832, 161, 11955,4720, 3013, 6583, 11958,11959,11960, 11963,414, 161, 11964,1343, 11967,161, 11965,161, 11966,556, 628, 11970,11971,4168, 418, 11972,11979,161, 2225, 161, 3799, 213, 556, 11973,11974,1604, 418, 11978,6052, 418, 3227, 2195, 1609, 4218, 1363, 161, 11975,161, 11976,635, 11977,161, 1609, 185, 1042, 161, 185, 420, 11980,11982,11983,1876, 11985, 418, 11987,7761, 11984,418, 11986,418, 161, 180, 1727, 11989,1542, 11990,11991,11992,11993,11994,11996,11997,11999,465, 12004,295, 12005,12007,1109, 492, 1010, 161, 11998,161, 161, 1297, 12000,161, 12001,12002,3178, 1093, 12003,1042, 161, 301, 12006,975, 418, 12009,12018,1831, 12032,12042,12045,12054,12057, 12061,6351, 12010,161, 12016,12011,12012,12013,12014,12015,12017,12019,12022,12028,12031,186, 12020,12021,12023,11468,12024,11817,418, 12021,12025,12026,12027, 12029,12030,414, 1042, 11939,4332, 1012, 12033,3267, 12038,12039,1343, 12040,1093, 12034,12036,161, 12035,1343, 12037,161, 4720, 678, 4373, 1387, 185, 12041, 426, 1847, 2485, 12043,12044,2983, 12046,12051,12047,12048,12049,161, 12050,350, 12052,12053,12055,12056,213, 213, 1415, 12058,161, 12059,161, 302, 12060, 1668, 12062,12063,414, 161, 1012, 12065,12071,12075,12080,12087,12088,12096,8312, 12097,12098,12099,12100,1161, 12107,12111,161, 12066,12069,161, 161, 1050, 12067,12068,161, 296, 161, 417, 12070,161, 12072,301, 12074,161, 161, 161, 418, 12073,1501, 418, 1173, 414, 3098, 161, 12076,12077,3689, 6002, 440, 417, 161, 8161, 161, 3689, 435, 417, 12078,1480, 350, 12079,213, 417, 417, 1059, 1886, 1150, 11565,161, 295, 12081,417, 12084,12082,6383, 12083,186, 161, 1091, 12085,161, 12086,414, 9741, 161, 4169, 1876, 185, 418, 12089,12091,12093,429, 12090,12092,3409, 3880, 3399, 12094,12095,161, 11544,1640, 418, 1846, 12101,12103,12102,12104,12105,1208, 12106,1426, 556, 11921,12108,1827, 12109,12110,414, 161, 161, 1026, 1847, 12113,12117,12118,213, 12119,474, 12120, 12114,161, 12115,161, 12116,474, 12122,1143, 12138,1143, 12139,12123,12124,12130,12134,12135,12125,12129,12126,186, 12127,161, 556, 12128,12131,161, 213, 466, 12132,12133,185, 296, 12136,12137,418, 5469, 12141,12143,12154,12156,12158,12169,12171,12172,3479, 12142,12144,3102, 12146,12148,1343, 5065, 161, 12150, 3731, 12151,12145,161, 2203, 350, 4678, 12147,12149,12152,12153,12155,161, 12157,296, 414, 12159,679, 12161,161, 12166,12160,12162,12163,12164,12165,12167, 12168,1365, 12170,1449, 301, 12173,12174,409, 409, 12176,12178,12179,434, 12177,304, 2393, 296, 304, 12180,3680, 12001,408, 12181,1371, 9937, 1829, 161, 12182,12183,1109, 12185,12196,12199,12200,12206,12208,295, 12211,1107, 12215,12230,12186,12195,11817,12187,12191,465, 295, 1371, 295, 12194,12188,12189,3002, 11817,161, 11817,12190,12192,484, 12193,6066, 1343, 9848, 12197,161, 12198,418, 1899, 434, 12201,295, 409, 12202,12203,1093, 750, 161, 161, 12204,12205, 12207,185, 12209,418, 304, 12210,1343, 161, 1542, 8181, 12212,12213,425, 12214,12216,12218,465, 295, 12229,295, 418, 12217,1036, 1343, 12219,12228,12220, 12225,12227,3510, 1886, 12221,12223,12222,556, 12224,12226,294, 350, 295, 1372, 465, 418, 716, 12232,12235,12233,12234,12236,1012, 12238,6066, 12240,295, 4218, 1343, 12241,12239,12242,12243,12248,12249,12251,12260,12291,12294,1161, 12299,12321,12324,12344,12346,12347,12351,12352,12353,12361,12363,12364,12368,4359, 12370,12244,6838, 12245,12246,12247,2891, 2469, 311, 1824, 1126, 3132, 2293, 1899, 6415, 12250,5469, 420, 161, 12252,12254,12256,12258,161, 12253,350, 12255, 161, 12257,12259,6229, 2020, 409, 12261,213, 12264,1938, 1539, 3174, 5597, 12280,12284,12285,12286,1542, 12288,161, 161, 12262,304, 295, 12263,12265,186, 12266,12267,12268,556, 1733, 304, 161, 12269,12272,12270,11294,435, 12271,1092, 213, 12273,750, 12274,12275,12278,12279,12276,12277,3595, 161, 12281,12282, 12283,4595, 161, 161, 161, 161, 12263,12287,12289,12204,12290,1425, 417, 2340, 5615, 1143, 12292,1845, 1862, 12293,1497, 421, 418, 12295,12296,12297,12298, 12300,12302,2106, 161, 12303,1017, 12304,12308,12309,12310,1446, 3088, 12314,301, 12301,418, 1109, 12305,12306,12307,161, 161, 3946, 12311,1342, 301, 12313, 301, 414, 12312,12315,7967, 12320,3157, 12316,2196, 12317,12318,12319,418, 161, 12322,161, 12323,12325,12327,12335,1343, 295, 161, 12342,12343,1028, 440, 12326,161, 186, 12328,1938, 1542, 1609, 12330,12333,350, 12329,414, 161, 12331,295, 1999, 161, 1609, 12332,1013, 2442, 4058, 12334,1343, 1026, 161, 3680, 1060, 679, 12336,12339,12168,12340,12341,12337,1609, 296, 12338,417, 1609, 418, 418, 1885, 8167, 417, 1012, 1885, 465, 426, 628, 1028, 12345,1173, 6537, 161, 1609, 161, 1173, 2621, 12348,12349,12350,10454,161, 11258,1042, 1937, 5754, 12354,12355,12356,12357,295, 418, 7967, 12359,12360,161, 2257, 311, 3194, 2362, 414, 161, 161, 161, 5046, 161, 161, 12358,186, 296, 418, 420, 1886, 1042, 1482, 12362,3469, 1515, 161, 12365,12366,161, 161, 1097, 301, 12367, 12369,1640, 628, 1862, 179, 161, 213, 304, 161, 414, 12372,12381,409, 12412,12413,10655,304, 6066, 12420,3776, 12426,12431,12436,10334,12459,414, 12460, 12373,12374,12378,749, 12375,12377,12376,12379,12380,12382,12393,12402,12403,12405,12408,1608, 12383,12384,12386,12392,12385,11322,519, 1555, 12387,12388,12391, 12389,12390,1302, 2333, 715, 1195, 12394,12397,12398,12395,12396,1026, 417, 161, 12399,12400,12401,1042, 12404,12406,2462, 2891, 12407,161, 1302, 715, 3864, 1563, 12409,12411,12410,4496, 311, 12414,12415,301, 1499, 161, 12416,12417,12419,417, 161, 161, 12418,1042, 161, 11150,161, 421, 996, 519, 12421,12422, 12424,161, 1036, 2519, 1050, 12423,161, 1227, 418, 12425,1608, 1156, 12427,12428,12430,1184, 1093, 414, 2050, 9820, 161, 12429,1699, 408, 417, 161, 421, 161, 350, 12432,186, 213, 12433,12434,12435,12437,12441,12442,12447,12449,12453,1756, 417, 12457,12438,12439,2469, 12440,161, 2339, 161, 12443,12446,12444, 9522, 12445,12448,12450,12451,12452,1042, 715, 161, 1054, 1054, 4758, 12454,12455,12456,12458,1297, 1302, 1756, 304, 12461,4951, 12464,12496,12513,12517,12521, 12547,12551,12552,12555,417, 12564,12566,12570,12574,12584,12593,12637,11942,12652,12462,1903, 12463,161, 435, 4497, 12465,12469,12473,12479,161, 12481,12482, 12488,12489,12466,311, 12467,12468,12470,12471,12472,12474,12478,12475,12476,1208, 1042, 12477,1756, 12480,519, 414, 413, 161, 311, 304, 12483,12484,12487, 12485,12486,11212,10219,12490,12491,12492,12493,12494,12495,414, 12497,12501,12507,1877, 12508,750, 5239, 12511,12498,12499,12500,12502,161, 12506,12503,12504, 12505,304, 1026, 3680, 1889, 304, 12509,3149, 12510,1354, 12512,1216, 1208, 1042, 1806, 1862, 12514,161, 12515,161, 750, 1060, 12516,417, 2469, 10615,161, 1322, 12518,12519,1896, 466, 12520,12522,12523,12238,12525,161, 12531,12535,1343, 1843, 12541,1845, 12545,12524,1847, 12526,12529,1371, 161, 3002, 4688, 12527, 12528,12530,12001,1121, 1371, 161, 1899, 4235, 12532,12533,414, 12534,161, 12536,12539,12540,185, 304, 12537,12538,5469, 10299,1343, 3002, 161, 161, 12183, 12542,12543,12544,12546,12548,12549,12550,161, 1935, 161, 12553,12554,1421, 186, 1042, 715, 161, 12556,12557,12563,12558,12562,12559,12560,12561,4901, 12565, 414, 161, 222, 5647, 301, 12567,2649, 161, 12568,12569,12571,12572,12573,1818, 750, 11162,12575,12581,12583,12576,12577,12578,350, 2182, 12579,12580,12582, 12585,12588,12589,12590,10655,296, 1846, 12592,1885, 12586,12587,435, 1421, 1861, 161, 12591,213, 474, 1208, 1042, 1121, 12594,12599,12608,12616,12620,12621, 12633,12634,161, 3169, 12595,12596,12597,12598,161, 1321, 10331,12600,161, 186, 1042, 2050, 304, 1727, 12601,161, 12604,12602,12603,12605,12606,12607,12609, 12610,12611,186, 12612,12613,12614,161, 12615,1405, 466, 12617,12619,12618,750, 3469, 161, 2966, 10121,12622,12623,12631,3128, 12632,161, 4901, 1401, 3425, 1093, 426, 4824, 12624,12625,12626,12627,12628,12629,12630,12635,311, 12636,304, 1150, 12638,12640,213, 161, 12639,186, 936, 12641,12650,12642,12643,12644, 12645,12646,12647,12648,161, 12649,2489, 213, 12651,1060, 7933, 679, 414, 3603, 12655,12659,12681,4941, 12682,12685,1270, 12686,12656,12657,12658,1459, 161, 12660,12661,12662,12665,12669,12671,12676,12679,7241, 3929, 12663,12664,12666,12667,12668,12670,12672,8655, 12673,12674,12675,7150, 12677,630, 12678,12680,1143, 12683,4465, 4872, 161, 12684,161, 1474, 417, 5083, 2024, 425, 12687,12689,12690,12694,12697,12691,12693,12692,213, 161, 161, 1156, 296, 12695,12696,161, 12698,5552, 12700,12701,12702,12699,12703,12705,12707,12708,12711,12712,519, 12001,12714,295, 12719,12724,12727,12732,12736,12739,12745,12746,12747,12750,426, 12751,418, 12706,409, 519, 1336, 1042, 519, 1823, 4732, 161, 12709,12710,1042, 1371, 2963, 1787, 12236,1542, 12713,161, 12715,161, 12717,1028, 418, 1829, 414, 161, 1060, 161, 12716,1545, 161, 1583, 161, 12718,12720,10366,12722,12721,1542, 295, 409, 12723,301, 161, 161, 161, 12725,3721, 12726,161, 12728, 1387, 12731,1109, 12729,12730,11817,1432, 3803, 1121, 12733,9486, 12735,12734,12737,161, 12738,12740,12742,12743,12744,1818, 12741,9522, 161, 301, 350, 161, 9379, 1093, 7623, 5404, 435, 1036, 161, 12748,12749,12752,12753,474, 12755,12769,12770,12772,12775,12777,12780,12786,12809,12815,12821,12756,12762,12763,12766, 12768,2952, 12757,2237, 3079, 12760,12761,434, 12758,1173, 1590, 304, 706, 12759,1208, 1042, 296, 8655, 10693,1042, 2998, 1367, 3603, 2998, 1818, 4678, 12764, 12765,10317,1345, 296, 1341, 1787, 2040, 12767,474, 3049, 161, 1787, 2283, 5396, 12771,12773,12774,1223, 3227, 1542, 161, 161, 12776,421, 466, 2750, 12778, 7076, 185, 12779,2776, 2776, 12781,12785,12782,12783,12784,12787,12791,12800,7558, 161, 12805,12806,161, 301, 12788,12790,2252, 12789,12792,519, 12793,12796, 161, 12794,12795,7332, 3405, 12797,12798,12799,12801,12803,12804,1787, 435, 2568, 12802,1862, 5512, 12807,161, 12808,12810,12811,12812,434, 426, 304, 12813, 12814,12816,12817,12820,10655,1343, 295, 12818,161, 161, 161, 304, 12819,421, 161, 161, 466, 12822,12826,2655, 12831,295, 12832,12833,3959, 12823,301, 1037, 161, 1198, 12824,8159, 12825,1343, 12827,6577, 12828,161, 6561, 12829,161, 5669, 161, 12830,161, 435, 12306,161, 12834,12836,12837,8390, 1813, 12835, 3807, 7496, 12839,12842,12845,12857,1270, 418, 12858,12860,12862,4884, 12840,12841,3391, 186, 1542, 12843,12844,1276, 463, 12846,12848,12852,12854,12856,435, 12847,12849,12850,1937, 12851,417, 1938, 3167, 12050,296, 3167, 12853,12855,1937, 414, 3610, 414, 6886, 3391, 12859,161, 296, 12861,12863,12865,12866,213, 1640, 12870,12871,12874,12876,12877,12883,12889,12893,9573, 296, 304, 12867,1080, 12868,12869,12872,161, 12873,161, 1060, 2519, 12875,296, 2475, 161, 1217, 408, 12878,12879,12882,12880,12881,161, 12884,408, 12885,161, 12886,161, 12888,161, 12887,12890,12891,1217, 12892,4207, 12894,12897,12895,12896,12899,12912, 12936,12944,12900,1121, 161, 12901,12905,12910,1323, 3451, 350, 12902,418, 12903,12904,295, 12906,519, 678, 3992, 12907,12908,12909,1087, 1498, 6712, 12911, 414, 12913,12915,12918,161, 12921,161, 185, 12914,7061, 296, 6354, 213, 12916,12917,418, 418, 740, 3198, 161, 8730, 12919,1938, 4951, 1270, 12920,12922, 3013, 2191, 12923,1343, 12925,12927,12928,12930,4951, 4542, 350, 12921,12924,161, 440, 12926,1270, 418, 161, 12929,2756, 635, 1343, 2762, 12931,5046, 12932, 630, 12933,12934,12935,1028, 12748,12937,161, 12941,414, 12942,7180, 12938,12939,1284, 12940,2826, 213, 414, 1126, 161, 12943,9623, 311, 12945,2748, 161, 12946,1405, 12947,12948,12949,12951,9494, 12952,4313, 12953,12955,11724,1151, 161, 350, 435, 1093, 2203, 12954,414, 12956,12971,13010,13021,13023,13037,13044, 295, 13045,13052,13104,13108,1459, 417, 12957,12961,1310, 12963,12967,12958,12960,12959,12962,12964,1356, 161, 12965,1334, 12966,1042, 12968,12969,12970,12972, 12976,12987,12990,12991,12994,13000,13006,12973,1437, 12974,12975,12977,12978,12981,12983,12985,12979,161, 12980,12982,186, 12434,12984,12986,414, 8593, 12988, 12989,11202,12992,2817, 12993,12995,12996,12997,12998,12999,12385,13001,13002,13003,13004,13005,13007,8673, 13008,13009,1042, 13011,161, 13016,13019,161, 295, 161, 13012,13013,1208, 13014,296, 556, 13015,1198, 13017,2826, 13018,8647, 13020,13022,2139, 186, 13024,13028,13035,304, 13025,13026,13027,186, 2886, 161, 13029,13030,13031,13032,13033,13034,13036,161, 715, 3469, 13038,2040, 13039,13040,1042, 161, 161, 13041,304, 13042,13043,4169, 1126, 936, 12943,13046,1415, 13047,13049,13048,13050,13051,13053,13055,13076,13078,13092,13099,13103,13054,2040, 437, 13056,13059,13061,13062,13067,13057,13058,186, 1876, 161, 8617, 1335, 13060,716, 1329, 13063,13066,1421, 13064,13065,1093, 427, 13068,13075,13069,13070,13071,13072,13073,13074,186, 1042, 161, 13077,13079,3041, 13083,13085,4051, 13080,13081,13082,13084,2817, 13086,13087,13090,13088,3631, 1042, 13089,417, 1608, 13091,13093,13096,13098,13094,13095,13097,1230, 11265,1823, 13100,13101,13102, 161, 1302, 1586, 213, 161, 635, 9205, 4766, 13105,13106,13107,13109,13116,13122,13127,13110,13112,2040, 13111,161, 10146,161, 8680, 445, 13113,1087, 13114, 13115,13117,12236,13118,13119,13120,13121,13123,13124,186, 13125,556, 13126,13128,13158,13161,13129,13135,13136,13139,161, 13145,13153,10593,13130,13131,13132, 13133,13134,13137,13138,13140,13141,13142,13143,13144,1823, 2948, 13146,13147,13148,13149,13150,13151,13152,13154,13155,13156,13157,13159,1206, 13160,8580, 2367, 13163,3476, 13176,1343, 417, 4794, 304, 430, 13177,414, 13164,161, 3198, 13167,1042, 13169,4263, 161, 13172,13173,13165,6414, 13166,13168,13170,13171,1093, 295, 975, 1823, 13174,13175,3928, 2784, 13178,13179,1542, 1542, 13181,301, 13183,7942, 4885, 13182,13184,13185,43, 13410,13411,44, 13565,13573,45, 1895, 46, 4065, 47, 14129,48, 49, 14490,14492,14495,14497,13186,13187,13188,13189,13195,13208,12205,13210,13219,13227,13257,13258,13262,13282,13292,13311,13342, 13351,13367,13393,13403,3818, 13405,13406,3112, 2272, 13190,13191,3469, 13192,13194,1097, 2020, 9899, 3861, 161, 13193,161, 161, 2098, 1470, 2196, 13196,13200, 13204,13205,13207,13197,1756, 13198,161, 161, 13199,161, 3555, 161, 3169, 13201,556, 161, 13202,13203,1283, 750, 161, 295, 1639, 3181, 1156, 13206,161, 1087, 186, 11145,13209,4052, 1042, 161, 418, 13211,13213,418, 440, 13214,161, 13217,161, 13212,1184, 1305, 161, 13215,13216,161, 13218,13220,304, 161, 13221,13223,161, 161, 13222,13224,295, 13225,13226,426, 13228,13230,13232,417, 13253,1823, 186, 4308, 1498, 296, 13229,4226, 13231,4773, 1042, 414, 1640, 13233,13236,13240,13241,13242,4062, 750, 13246,1343, 1756, 1727, 13247,13250,13252,13234,13235,1363, 5494, 13237,13238,13239,161, 3002, 161, 316, 1895, 13243, 13244,13245,1126, 5703, 161, 13248,13249,213, 13251,13245,414, 4065, 13254,414, 161, 13255,1756, 13256,8415, 418, 1044, 1230, 13259,1042, 13260,13261,13263, 519, 13265,13268,13269,418, 13271,13275,296, 13279,8250, 13280,1639, 13281,434, 1822, 13264,161, 13266,13267,161, 1818, 161, 161, 161, 2094, 161, 13270, 13272,295, 2538, 161, 13273,161, 1012, 13274,3510, 13276,2864, 4203, 13277,13278,161, 1150, 1418, 161, 161, 2430, 350, 161, 1189, 11279,161, 2236, 1126, 161, 13283,1208, 13286,2651, 213, 13284,13285,13287,304, 4794, 13288,1060, 13289,414, 161, 13290,13291,161, 13293,13295,13297,13298,5481, 13299,434, 13300, 13301,10536,13303,2678, 301, 13294,295, 13296,161, 4284, 3198, 11366,435, 13302,1028, 1060, 13304,13310,13305,13307,7337, 13306,13308,9522, 13309,161, 414, 1405, 414, 13312,5956, 304, 13314,13315,519, 13317,13319,7447, 13321,13323,13324,1595, 13326,13330,13339,13340,4883, 1639, 13313,222, 1342, 161, 635, 1883, 304, 161, 161, 12152,418, 13316,161, 4824, 13318,1093, 414, 13320,8525, 1093, 2238, 161, 185, 13322,474, 418, 3435, 2340, 2165, 1876, 3479, 161, 2024, 13325,1604, 2134, 1459, 1885, 435, 161, 1093, 161, 13327,740, 13328,13329,13331,13332,13334,13335,13337,161, 874, 161, 161, 435, 1208, 1036, 13333,1093, 161, 161, 296, 3167, 296, 996, 161, 13336,161, 13338,1470, 301, 3492, 2196, 186, 13341,1042, 1343, 161, 3799, 1042, 1367, 13343,435, 13345,420, 6718, 13349,13350,1270, 13344,1367, 12306,8580, 474, 13346,13347,13348,7478, 186, 161, 2191, 295, 12329,3302, 9988, 1042, 13352,13354,13355,13356,13360,13362,1343, 519, 13365,1223, 10655,13353,414, 161, 1127, 213, 350, 5207, 414, 161, 3198, 161, 13357,8136, 13358,414, 186, 1609, 1042, 13359,161, 13361,1075, 13363, 13364,13366,556, 161, 1297, 161, 350, 13368,13370,13373,13375,13381,13384,13385,13392,295, 161, 13369,417, 13371,295, 161, 13372,13374,13376,414, 13378, 13379,1635, 13377,13380,4705, 295, 161, 9682, 161, 161, 304, 301, 13382,13383,6222, 13386,13390,13387,13388,13389,13391,161, 161, 13394,13395,13396,4514, 11357,1342, 161, 301, 1223, 13397,13401,7478, 1208, 13398,161, 13399,13400,2463, 13402,13404,5207, 750, 440, 1297, 295, 161, 445, 304, 417, 13407,519, 13409,413, 13408,7463, 1126, 13412,301, 9892, 13413,13425,13427,13431,13443,13449,13450,13453,8685, 13461,13481,13486,1223, 13505,13545,13553,13558,13561,4374, 13564,13414,13415,13417,13418,13424,186, 3198, 1042, 1756, 161, 418, 13416,161, 1421, 4491, 13419,161, 3756, 13420,13421,186, 1042, 13422,13423,519, 750, 13426,414, 414, 2826, 414, 13428,13430,9947, 295, 13429,421, 13432,13434,11813,295, 13439,3959, 161, 13433,304, 13435,161, 13436,296, 13437,1432, 1693, 13438,13440,161, 13441,13442,13444,13445,1343, 13446,429, 13448,1851, 161, 1639, 2864, 1426, 4313, 161, 13447,1609, 6354, 161, 161, 418, 213, 5597, 1060, 418, 2191, 6405, 440, 296, 1297, 13451,13452,1343, 421, 13454,13456,518, 13457,429, 13458,13459,996, 13455,350, 1297, 1012, 296, 3113, 1343, 3510, 13460, 9474, 161, 418, 13462,409, 13465,2234, 13466,13471,13472,13476,13477,414, 13480,316, 13463,13464,1012, 296, 4233, 4680, 3750, 3510, 161, 13467,304, 1498, 13470,252, 161, 161, 161, 13468,3955, 740, 13469,13473,1426, 13474,1343, 13475,161, 414, 1093, 161, 1470, 418, 2362, 13478,13479,161, 161, 414, 936, 5487, 1371, 295, 418, 161, 414, 161, 161, 13482,13483,1904, 3929, 296, 2610, 161, 161, 13484,13485,304, 8580, 13487,13489,13490,161, 11045,8197, 13491, 13493,1365, 13496,13502,13504,13488,11856,161, 7170, 1042, 440, 161, 409, 417, 13492,13494,11045,1829, 13333,1028, 13495,304, 13497,13498,13499,13500,13501, 13503,418, 1756, 161, 161, 295, 409, 13506,13508,13511,301, 519, 13516,13517,13518,13525,13526,696, 13539,13544,13507,418, 2362, 435, 13509,13510,1542, 13512,304, 13515,13513,417, 13514,1542, 4679, 185, 8265, 418, 716, 1827, 13519,13522,13524,161, 2182, 186, 13520,13521,161, 4173, 161, 13523,161, 13527, 13532,13535,1028, 13538,3529, 13528,13530,13531,13529,519, 6046, 161, 252, 161, 161, 161, 1173, 3250, 161, 13533,13534,1173, 13536,1367, 435, 13537,161, 295, 2576, 3006, 13540,161, 13541,161, 13542,213, 13543,1212, 1818, 716, 296, 295, 3547, 13546,13548,13547,295, 161, 13549,13551,414, 13550,13552,3169, 161, 2366, 414, 3250, 5854, 161, 1145, 13554,1431, 295, 13555,13557,161, 13556,161, 6066, 213, 1343, 1916, 161, 976, 1486, 13559,13560,13562,13563,414, 1145, 161, 13566,13570,13567,13568,161, 13569,161, 5089, 13571,13572,13574,13575,13582,13587,13593,13600,13611,13616,13632,13634,1198, 13635,13664,13665,13707, 13713,13715,13742,13760,13789,13790,13792,13794,1245, 13576,13577,13580,13578,13579,13581,7990, 161, 3776, 13583,13586,13584,13585,161, 3944, 421, 13588,13589, 13591,414, 1995, 13429,13590,12004,13592,11921,3554, 13594,13596,13599,295, 161, 13595,5606, 13597,295, 161, 13598,13601,2280, 13602,434, 13603,13606,13608, 1044, 430, 1036, 13604,414, 4203, 1042, 1343, 13605,418, 2111, 13607,13609,1343, 161, 6635, 350, 161, 13610,1818, 13612,13613,12203,1093, 13614,161, 13615, 818, 13617,13619,13620,715, 161, 13622,13618,8251, 1184, 161, 13621,414, 2340, 1876, 161, 13623,13627,13624,13626,13625,414, 301, 1341, 301, 13628,13629, 13630,301, 161, 3102, 161, 13631,13633,213, 13636,13637,13644,13645,13650,13661,519, 13662,13663,2969, 13638,13639,556, 418, 13640,1452, 429, 13641,895, 13642,1792, 13643,1126, 13646,3013, 13647,5190, 161, 13649,13648,418, 414, 186, 1042, 13651,1823, 13652,13653,13654,13655,13656,13657,13658,13659,13660,3704, 301, 12486,2225, 1437, 13666,13672,13674,13676,10934,13679,13689,13691,13699,13701,161, 13705,417, 13706,434, 1150, 13667,1962, 13669,161, 161, 13668,213, 161, 1305, 213, 1405, 13670,1425, 13671,7696, 296, 556, 13673,161, 1459, 296, 440, 1459, 161, 2340, 4313, 13675,161, 213, 213, 1042, 1012, 161, 10934, 1343, 13677,2115, 13678,2094, 296, 13680,13681,414, 3976, 186, 13682,8356, 13683,161, 13688,13684,13686,13685,13687,1087, 13690,519, 1470, 1490, 295, 13692, 13694,13697,161, 13693,13695,13696,13698,13700,213, 186, 4583, 13702,13704,1343, 435, 161, 13703,161, 1458, 161, 1343, 301, 13708,13709,13711,13712,13710, 7989, 9121, 2094, 1778, 222, 1813, 13714,13716,1042, 6405, 13739,519, 13740,409, 414, 161, 13717,13718,13720,13723,13724,13728,977, 8771, 1133, 13730,13732, 13736,13719,1143, 740, 556, 3169, 1011, 13721,13722,13725,1128, 316, 13726,304, 13727,13729,2166, 13731,294, 4630, 13733,13734,1126, 13735,13737,13738,414, 418, 296, 418, 213, 161, 4542, 13741,8181, 3479, 213, 161, 13743,296, 13748,1036, 295, 437, 13758,13744,13746,414, 13745,1351, 13747,414, 161, 2094, 13749,3729, 13752,5246, 3102, 13755,1343, 3778, 418, 13757,9494, 161, 13750,426, 13751,8158, 421, 1011, 161, 161, 13753,161, 13754,1867, 418, 414, 13756, 1538, 13759,1042, 295, 161, 975, 435, 13761,13762,161, 13763,2430, 1756, 161, 13765,13766,13764,4773, 4308, 418, 161, 161, 13767,13769,13775,6066, 13777, 13781,13784,13787,161, 13768,13770,13773,13771,13772,13774,13776,1011, 13778,13780,13779,13782,13783,13785,13786,2901, 1184, 13788,2763, 295, 13791,414, 1706, 161, 161, 13793,4284, 4313, 13065,1150, 13034,296, 9650, 13795,13830,13867,13890,13920,13934,3728, 5483, 161, 13796,13797,13799,13800,13801,13807,13811,2817, 13812,13817,13825,13827,13829,3529, 1280, 1555, 295, 3178, 8074, 13798,462, 161, 1297, 161, 161, 185, 8099, 13802,13804,13806,13803,161, 161, 13805,13808, 3922, 1608, 304, 11813,13097,13809,13810,9167, 213, 11813,4595, 11813,1523, 1116, 161, 13813,161, 161, 13814,4523, 4284, 1042, 13816,1343, 161, 13815,5494, 556, 311, 4203, 6106, 13818,13819,161, 13821,13824,5481, 213, 13820,13822,1280, 13823,5469, 2270, 13826,1640, 7660, 13828,2420, 1426, 186, 1877, 161, 13831, 13832,13835,13839,13843,13844,13845,13851,13854,13857,13859,13860,13862,13865,11574,1150, 161, 2183, 13833,13834,161, 13836,13838,13837,186, 1270, 304, 13840, 1042, 1093, 1343, 13841,161, 13842,435, 435, 304, 161, 13846,413, 13847,5956, 13848,13849,296, 13850,4681, 13852,13853,1184, 1889, 430, 296, 4308, 1184, 2082, 296, 13855,1608, 161, 1938, 13856,409, 13858,295, 296, 1343, 295, 161, 161, 9243, 414, 161, 13861,716, 716, 161, 13863,13864,13866,1315, 556, 13868,1841, 13871,447, 13873,13879,13883,1093, 13889,421, 13869,13870,4465, 295, 161, 13872,13874,3770, 161, 1851, 13876,13875,161, 2283, 186, 1042, 1459, 13877,13878,13880,12035,13881,161, 13882,1876, 161, 13884,13885,186, 1042, 161, 13886,13887,13888,13891,421, 161, 13893,296, 161, 13894,13898,13899,13909, 13911,13912,13917,1208, 13892,1538, 1876, 161, 13895,161, 13896,213, 186, 748, 1042, 161, 414, 9638, 186, 13897,2165, 11684,161, 13900,13901,13906,1107, 161, 161, 13902,9567, 1044, 161, 161, 13903,161, 13904,161, 1813, 13905,1028, 13907,11530,161, 13908,13910,13913,13914,13915,13916,13918,1042, 161, 161, 13919,161, 161, 1042, 161, 161, 10176,1208, 13921,13923,161, 13844,13925,4883, 13926,13928,13931,13932,13933,161, 13922,350, 13924,186, 1042, 1060, 161, 13927,13929,13930,2782, 1490, 4496, 1093, 304, 5223, 161, 414, 3435, 13935,5246, 5481, 1876, 13938,2782, 13936,13937,13939,13940,13941,13945,13946,13947,13953, 13955,13970,13971,13987,14004,14005,14092,14101,14105,14117,14119,5362, 14128,161, 6155, 186, 435, 350, 13942,13943,296, 1042, 295, 13944,1302, 1042, 1116, 295, 2047, 304, 296, 2280, 429, 161, 13948,13949,13950,8181, 13951,161, 4583, 463, 186, 467, 161, 13952,301, 1343, 161, 1129, 4041, 161, 1280, 13954, 3776, 161, 2130, 13956,7188, 295, 13957,13958,13961,5410, 13969,1949, 1639, 161, 13882,1042, 161, 1938, 519, 13959,1962, 1343, 2225, 13960,161, 13962,3634, 13965,6393, 13966,161, 13963,161, 13964,1356, 414, 161, 13967,13968,1876, 161, 933, 13972,13973,414, 161, 1651, 13975,13978,13979,13980,4164, 13986,161, 3799, 13974,1756, 1109, 304, 13976,161, 13977,13981,13984,1126, 13982,161, 13983,2238, 13985,161, 2199, 350, 1824, 10047,414, 13988,161, 13993,13994,414, 186, 13989,13991,6377, 161, 13990,1418, 13992,414, 1938, 13995,2880, 3113, 1042, 13998,13999,1343, 2547, 14000,14001,14003,13996,13997,1198, 14002,4979, 7076, 1824, 1023, 14006,14007,14011,14014,14017,14035,14037,465, 14047,1728, 14050,14062,8265, 2174, 14066,14070,14074,14087,14089,414, 1121, 295, 1208, 161, 2196, 1971, 14008,14010,14009,161, 4492, 161, 14012,14013,161, 161, 3198, 414, 161, 1242, 296, 556, 414, 1028, 14015,1604, 417, 1217, 14016,14018,14019,161, 14020,14022,14023,14024,14026,14027,8429, 519, 14028,14032,295, 14033,161, 8136, 468, 11125,14021,185, 740, 14025,1482, 468, 474, 14029,975, 426, 1343, 14031,14030,296, 213, 161, 1717, 1896, 14034,1143, 14036,418, 427, 14038,14041,14045,304, 161, 5336, 14039,161, 3198, 350, 161, 14040,1421, 161, 1876, 556, 1126, 14042,14043,1042, 14044,161, 14046,14048,14049,295, 161, 14051,213, 14057,14058,2474, 161, 14059,14052,14056,13018,14053,1127, 213, 14054,14055, 161, 1223, 1189, 301, 5021, 556, 1797, 14060,14061,161, 161, 1121, 14063,14064,14065,14067,161, 161, 14068,14069,14071,1012, 213, 1842, 4306, 14073,1343, 1851, 12111,14072,213, 185, 14075,14076,14079,1371, 14081,1756, 417, 14082,161, 14077,977, 3153, 14078,14080,11294,2267, 2262, 161, 161, 409, 161, 4169, 975, 11842,14083,14084,14086,14085,414, 200, 161, 2847, 14088,14090,14091,1012, 3959, 14093,14094,14095,14096,14100,296, 1109, 14097,14098,1459, 14099,12486, 418, 161, 14102,161, 14103,14104,3689, 4705, 5361, 976, 14106,14107,3998, 14113,14115,14116,414, 186, 295, 6595, 14108,14109,1334, 3439, 1042, 14110,14111, 14112,2142, 13416,14114,12203,161, 161, 1150, 750, 14118,1134, 750, 1981, 3544, 1093, 2109, 14120,14121,14122,161, 14126,1640, 14123,14124,14125,14127,14130, 14131,14132,14190,14264,14303,14337,14347,14133,14136,14137,14143,14144,6083, 14146,14147,14149,14173,213, 14174,14176,14183,434, 4042, 14187,14188,161, 14134, 1156, 14135,3046, 1150, 14138,6113, 14139,14140,14141,14142,2225, 14145,161, 304, 4883, 9338, 213, 1467, 14148,1042, 1981, 161, 1093, 161, 3213, 414, 14150, 4352, 213, 408, 14160,9458, 3134, 14168,14169,14170,9069, 14151,14155,14157,1036, 14159,161, 304, 14152,14153,161, 1060, 14154,1028, 2330, 14156,161, 161, 9040, 14158,2378, 10446,414, 161, 14161,14163,14164,14165,14166,14167,161, 1459, 161, 161, 14162,3559, 3098, 3510, 1482, 1028, 418, 2330, 418, 161, 9128, 1459, 161, 14171,161, 14172,14175,2056, 295, 2655, 1823, 14177,14182,414, 3730, 14178,161, 14179,435, 14180,1128, 14181,1212, 1042, 14184,976, 425, 1228, 14186,161, 14185,1538, 295, 14189,3922, 3958, 14191,1851, 14197,14199,14213,14231,14234,1727, 14238,14239,418, 14244,14248,14251,14255,14256,5469, 161, 14262, 14192,161, 161, 14193,186, 14195,13897,161, 14194,1418, 14196,14198,11675,414, 161, 14200,14201,4497, 14207,14208,14209,161, 1387, 161, 1050, 418, 435, 13810,14202,706, 14203,14204,14206,161, 14205,414, 161, 1177, 14210,14211,161, 14212,14214,14217,161, 14219,1343, 1042, 14220,3766, 440, 14225,161, 14226, 4688, 14227,14229,1013, 1198, 1363, 14215,161, 14216,161, 1343, 14218,1640, 2362, 14221,14223,161, 161, 14222,5606, 161, 14224,304, 1757, 1363, 3725, 2111, 14228,1640, 14230,1876, 14232,14233,1012, 14235,296, 14236,295, 5139, 161, 304, 14237,418, 418, 716, 14240,2199, 434, 161, 14242,14243,14241,1198, 1905, 161, 14245,14246,14247,1128, 1189, 161, 14249,14250,14252,161, 14253,519, 5455, 14254,213, 1905, 1756, 2378, 5934, 4491, 161, 350, 1270, 1639, 161, 14257, 186, 414, 14258,1343, 14259,14260,5350, 14261,1013, 11797,14263,11574,418, 1101, 1841, 1012, 14265,14267,14269,14286,414, 14291,14292,933, 14293,14297,14300, 161, 14301,14266,437, 1818, 1876, 161, 14268,5918, 304, 440, 1343, 14270,1851, 14275,14281,14282,413, 161, 1012, 4255, 14271,1371, 14272,161, 5448, 161, 161, 14273,14274,14276,414, 14277,14278,1757, 11078,161, 14279,14280,4478, 417, 14283,295, 418, 161, 14284,14285,706, 14287,14288,186, 1028, 14289,14290, 3654, 213, 1376, 350, 1904, 161, 161, 14294,14295,1829, 1590, 14296,418, 1036, 301, 161, 12183,161, 3618, 14298,14299,161, 465, 295, 1640, 14302,1640, 750, 161, 14304,1323, 14305,421, 14307,14308,14310,14312,14315,14318,14322,350, 14331,14333,14335,4051, 1027, 1013, 14306,304, 252, 1938, 14309,4478, 14078, 304, 414, 6378, 14311,1343, 161, 1818, 304, 161, 14313,1343, 14314,409, 556, 14316,14317,418, 14319,14320,8676, 1060, 14321,14323,1042, 14324,14328,1868, 2449, 14325,14326,14327,14329,435, 14330,161, 409, 14332,2868, 186, 1042, 161, 14334,4705, 161, 14336,10377,409, 1805, 14338,14340,14341,14346,14339,3729, 3198, 417, 1756, 161, 161, 161, 14342,14343,14344,14345,296, 1818, 1042, 13198,355, 4291, 417, 14348,14350,14352,14373,14374,14381,14387,14389,14392,14398, 14418,14420,1245, 14454,14456,14475,14483,14488,14349,14351,222, 14353,1042, 14354,14372,14355,512, 180, 14356,161, 14359,14360,14361,14363,14365,14367,161, 2818, 14369,740, 14370,14357,14358,161, 418, 2526, 1217, 420, 14362,14364,161, 14366,14368,14371,3861, 2225, 1405, 14375,4091, 14376,14377,350, 14378,1198, 161, 14379,14380,14382,14383,14384,1500, 304, 222, 14385,14386,1530, 161, 14388,161, 296, 1829, 1371, 14390,9557, 161, 14391,12567,2755, 301, 9557, 414, 14393,14395,14394,556, 301, 161, 12567,3015, 14396,14397,3029, 14399,14400,14403,1604, 14405,14406,14415,933, 161, 417, 14416,1470, 14417,296, 14401,1143, 14402,14404,1541, 161, 14407,14408,14409,14412,14413,1609, 161, 420, 977, 14410,1480, 350, 4680, 14411,5954, 3167, 418, 296, 14414,1482, 161, 161, 3551, 628, 4513, 414, 14419,13032,519, 414, 6271, 14421,14427,14443,4322, 14446,2165, 14447,14449,14451,14453,14422,161, 14423,14424,14425,14426,435, 1042, 161, 14428,14435,1042, 14440,161, 14429,14430,14431,14432,14433,14434,468, 213, 161, 161, 14436,14437,14438,14439,14441,14442,14444,161, 161, 14445,161, 14164, 14448,7961, 14450,1050, 14452,418, 678, 14455,14457,4781, 213, 817, 14458,14459,14460,1343, 14462,14466,14467,14468,14474,161, 1640, 304, 1145, 161, 1542, 1541, 1542, 414, 519, 2651, 418, 14461,556, 1011, 161, 14463,5703, 14464,161, 304, 222, 161, 161, 14465,7010, 296, 4774, 161, 14469,14470,14473,14471, 14472,350, 1042, 1962, 14391,301, 818, 14476,14477,14478,418, 14482,5722, 161, 2142, 161, 14479,14480,295, 222, 14481,414, 13688,3302, 1640, 1042, 295, 161, 7076, 409, 14484,418, 14485,161, 3547, 14486,435, 1640, 14487,1054, 2802, 14489,3302, 252, 14491,14493,14494,9269, 14496,304, 420, 14498,14499,14501, 51, 14765,52, 14948,14951,14956,53, 15142,15144,14936,15222,54, 55, 15729,15736,57, 15906,15909,15922,635, 3102, 977, 14500,14502,14503,14504,14505, 14522,14523,14530,14534,14536,14542,14544,14552,14553,14601,14611,1590, 14637,14640,14695,14714,14730,14743,14747,9140, 14750,14756,409, 161, 3551, 14506,14507, 14509,14510,14514,14517,161, 161, 3079, 1143, 14508,1876, 1060, 414, 3013, 161, 3002, 1818, 14511,14512,14513,14515,14516,14518,14519,14520,414, 161, 161, 161, 14521,1583, 8061, 421, 2811, 161, 161, 14524,14525,14526,161, 2451, 1542, 426, 14528,14529,635, 6256, 3194, 1365, 1608, 161, 14527,161, 2539, 1608, 213, 1109, 418, 2280, 14531,1415, 14532,14533,14535,2225, 11813,440, 1143, 161, 2313, 14537,14538,14541,161, 186, 1042, 304, 14539,14540,161, 519, 1249, 1818, 295, 418, 14543,161, 161, 1297, 14545,304, 14546,14547,14550,295, 14551,414, 414, 1376, 1161, 5765, 14548,1042, 14549,161, 1886, 1276, 418, 296, 295, 301, 2538, 14554,14563,14565,14567,3267, 14570,14571,14576,14593,426, 13705,161, 14594,14596,635, 14599,14555,740, 14558,14559,14560,14561,222, 14562, 1128, 161, 14556,14557,418, 161, 14564,3153, 1730, 14566,161, 161, 414, 14568,14569,2367, 161, 161, 1813, 2523, 1367, 13834,1367, 1787, 161, 14572,14573, 14574,4055, 1787, 301, 161, 1823, 161, 14575,608, 14577,14580,14583,14585,3766, 14590,161, 14592,161, 2270, 14578,6393, 1151, 14579,3090, 635, 14581,14582, 3025, 295, 1962, 161, 14584,161, 1044, 161, 418, 10473,4203, 14586,295, 161, 1962, 14587,418, 14588,14589,1242, 1093, 1297, 14591,2576, 161, 161, 1191, 304, 924, 14595,14597,484, 3620, 14598,14600,418, 14602,14603,14607,14609,14610,418, 161, 350, 2782, 304, 1173, 3046, 4364, 14604,14605,301, 13424,435, 519, 161, 14606,1876, 414, 161, 1093, 414, 3435, 14608,161, 687, 12411,5045, 161, 161, 420, 6528, 2020, 7961, 1640, 1092, 161, 5368, 14612,14620,3098, 14621,301, 440, 14633,14634,14635,14636,7178, 14613,3907, 14616,14618,161, 14368,14614,161, 14615,1223, 1459, 14617,14619,414, 161, 14622,14627,2245, 14629, 12432,14630,14632,161, 14623,14625,14624,14626,161, 12117,222, 161, 14628,14631,5368, 2191, 1482, 161, 556, 1042, 2241, 296, 304, 296, 295, 295, 161, 161, 161, 14638,301, 161, 14639,14641,14644,14647,14650,14656,14657,14661,14666,14670,14672,14674,14678,14681,14689,14692,14694,1609, 1060, 2715, 14642,2568, 1198, 186, 161, 304, 161, 14643,14645,14646,311, 161, 2191, 484, 304, 3006, 14648,14649,418, 161, 14651,14654,2655, 7859, 1367, 161, 1223, 14652,414, 2340, 2109, 14653,161, 14655,355, 3959, 4730, 161, 14658,14660,1896, 14659,14662,14664,1121, 14665,414, 14663,161, 11484,14667,301, 3128, 14669,161, 418, 996, 14668,2081, 161, 418, 6678, 185, 14671,6394, 4898, 1479, 484, 14673,1304, 6127, 161, 14675,14676,440, 355, 161, 161, 3002, 161, 14677,296, 409, 14679,161, 2199, 14680,1778, 14682,14683,14685,14688,161, 5521, 1343, 186, 350, 635, 414, 161, 14684,417, 2677, 421, 519, 14686,466, 2191, 1481, 14687, 1028, 414, 161, 350, 6755, 304, 3833, 14690,304, 418, 14691,11982,14693,418, 3479, 1343, 295, 418, 1639, 4169, 6127, 5368, 11574,14696,1093, 161, 14698, 14699,14703,1905, 10723,14706,1990, 14708,14710,161, 14697,8611, 161, 2538, 7981, 14700,14701,14702,14704,14705,5146, 14707,1042, 161, 1850, 5368, 14709,1343, 295, 161, 161, 3002, 161, 350, 14711,14712,418, 14713,14715,14716,14721,2331, 14287,14724,1639, 14726,14727,1609, 14729,414, 161, 2165, 14717,1474, 161, 14719,14718,14720,628, 14722,4308, 1343, 977, 14723,186, 4308, 161, 161, 14725,161, 2196, 252, 3409, 1028, 14728,14731,296, 14733,434, 14734,14737,14738, 14740,14741,426, 304, 3487, 14732,4055, 161, 1342, 14139,1818, 1343, 1042, 14735,418, 304, 304, 14736,296, 185, 161, 304, 14739,161, 161, 740, 14742, 295, 440, 5710, 295, 2961, 14744,14745,161, 1862, 161, 706, 161, 14746,1097, 350, 161, 435, 4491, 4133, 14748,14749,414, 3435, 14751,11946,11942,14752, 9149, 14754,161, 3766, 161, 14753,5146, 14755,296, 4885, 161, 414, 14757,301, 14758,14759,14763,519, 4003, 635, 519, 161, 1356, 706, 14760,556, 14761, 14762,14764,14766,14767,14768,14771,12859,14772,14775,14779,14781,14784,14788,10625,14793,14801,14807,14847,14881,14882,14923,14932,14945,14947,409, 1343, 161, 14769,295, 414, 186, 11942,1042, 6097, 418, 14770,161, 2174, 1787, 10326,12001,161, 14773,417, 440, 414, 14774,161, 11960,161, 10047,14776,14777,3776, 14778,2225, 426, 14780,14782,14783,2472, 14785,1343, 14786,9934, 1042, 350, 14787,304, 425, 4678, 418, 161, 4058, 4482, 296, 14789,161, 295, 14790,14792, 4364, 304, 14791,1760, 465, 295, 10536,161, 2280, 3529, 161, 440, 295, 161, 14794,10773,14796,14797,1343, 14800,4448, 11693,484, 426, 14795,14798,14799, 161, 14802,414, 14804,14806,161, 14803,14805,161, 6189, 1640, 14808,7991, 14809,14811,10536,14825,14829,14832,14835,14836,14837,14845,161, 14810,296, 295, 5731, 9399, 14812,14818,14819,14822,14813,2963, 302, 14817,14814,14815,414, 161, 213, 161, 1425, 14816,1426, 1317, 1879, 14820,14821,1241, 468, 161, 161, 14823,14824,304, 161, 1818, 161, 14826,1431, 14827,161, 14828,6717, 14830,14831,161, 161, 2195, 304, 14833,161, 14834,6046, 301, 161, 161, 13429,14838, 14840,295, 213, 2225, 161, 14839,14841,161, 14842,161, 14843,1482, 14844,14846,161, 14848,14851,14858,14862,14866,14869,14875,14849,14850,14852,14853,14854, 14855,14856,14857,161, 14859,14860,14861,161, 1087, 1305, 14863,14864,14865,14867,14868,14870,2393, 14871,14873,161, 414, 161, 10229,304, 161, 14872,4109, 1498, 14874,1060, 1498, 2113, 414, 12914,14876,14877,14878,14879,14880,10299,295, 5469, 14883,14889,2449, 14891,14892,426, 14893,14895,14896,1027, 14899,14901, 14904,14905,14907,14910,14914,14918,14921,1729, 14884,2185, 14885,14887,10531,14886,418, 14888,4660, 14890,1093, 519, 484, 161, 414, 414, 414, 3730, 1787, 1097, 14894,4720, 1896, 14897,161, 4058, 7076, 14898,14900,14314,161, 14902,2130, 161, 1595, 14903,2191, 296, 186, 304, 14906,301, 1896, 161, 14908,818, 14909,161, 466, 409, 213, 14911,418, 14912,1343, 418, 426, 14913,304, 295, 5396, 1097, 304, 295, 14915,408, 2196, 161, 14916,14917,14919,418, 414, 14920,14922,14924,1640, 304, 14925,14927,14928,14930,14926,14929,4226, 519, 9595, 8003, 14931,14933,5368, 14934,296, 3922, 350, 14936,14938,14939,14941,933, 161, 14935,418, 14937,4108, 14940,4364, 14942,304, 1343, 161, 14943,13648,161, 14944,14946,2848, 1897, 14949,14950,14952,14953,14954,161, 14955,14957,14958, 14963,14964,222, 2081, 1270, 2851, 161, 1028, 350, 14959,14962,14960,161, 14961,161, 7580, 14965,14966,14967,14970,296, 3880, 161, 4042, 14968,161, 14969, 418, 161, 14971,14996,15004,15010,15015,15017,15027,15074,15080,15092,15099,15100,15116,15121,15125,15134,15138,414, 14972,4577, 14975,14981,14991,14993,7977, 14973,14974,3006, 519, 1126, 6528, 2111, 14182,14976,14978,14979,14977,2127, 2111, 7232, 1245, 5660, 1590, 6950, 14980,2568, 1792, 161, 1367, 14982,2127, 4316, 9940, 14983,14989,13643,14990,14984,14988,14985,14986,14987,1793, 1228, 304, 2094, 14992,14994,14995,12696,14997,14999,15001,440, 14937,15003,14998,1184, 304, 1092, 1026, 161, 414, 5246, 2971, 15000,15002,418, 15005,15006,417, 3778, 440, 440, 1092, 15007,15009,15008,15011,295, 295, 161, 15012,15013,161, 3013, 304, 1351, 1343, 350, 12145,15014,1042, 295, 2280, 161, 15016,2420, 418, 418, 1143, 414, 15018,296, 15021,161, 15025,2797, 1778, 161, 2770, 15019,15020, 161, 302, 296, 213, 15022,15023,1042, 161, 15024,161, 296, 15026,414, 10141,15028,15033,15037,1093, 15043,295, 15045,15047,15049,15063,15067,15071,15072, 696, 1161, 296, 15029,161, 15032,355, 15030,161, 15031,9584, 2196, 15034,15036,15035,468, 468, 161, 15038,295, 418, 161, 161, 161, 15039,15040,15041, 15042,15044,1143, 1459, 15046,15048,1542, 15050,15052,1604, 15056,15061,15062,161, 1459, 1161, 161, 1208, 186, 15051,3479, 161, 15053,161, 15054,4075, 161, 15055,15057,421, 1093, 7522, 1343, 426, 2129, 161, 15058,15060,1085, 161, 5699, 186, 15059,161, 418, 1107, 421, 1242, 161, 1242, 15064,296, 15066,161, 15065,2610, 296, 15068,418, 15069,15070,1730, 213, 4193, 418, 15073,296, 1810, 15075,15076,15077,15078,4482, 1036, 15079,1060, 15081,15085,15086,434, 1635, 2935, 15090,14578,1161, 301, 296, 3013, 161, 15082,15083,15084,15087,1604, 15089,15088,161, 4406, 408, 15091,6106, 1042, 1013, 1013, 301, 15093,15096,1797, 15094,519, 15095,5537, 161, 15097,6915, 15098,1792, 2390, 161, 1150, 417, 15101,15105,15106,15109,15110,15113,435, 15115,1343, 15102,1341, 15103,186, 15104, 4872, 295, 15107,15108,3794, 414, 414, 5785, 15111,15112,161, 161, 873, 161, 296, 11530,15114,418, 161, 418, 161, 15117,15118,10766,1028, 295, 15120, 3061, 161, 15119,161, 5396, 5751, 426, 15122,15123,15124,15126,8034, 15129,1044, 15130,15127,15128,15131,1250, 15132,15133,418, 15135,1343, 15137,5368, 15136, 304, 1093, 1093, 161, 409, 1109, 1418, 15139,15141,15140,161, 5572, 161, 1143, 161, 519, 15143,15145,10877,15170,15183,15191,15209,15213,15219,15146,15147, 4688, 15153,15156,4287, 15158,15166,15167,426, 304, 1504, 15169,3922, 426, 4492, 1093, 8256, 15148,213, 440, 15151,304, 15152,1093, 15149,161, 161, 15150, 15154,15155,296, 1935, 936, 9564, 15157,11833,4164, 1851, 296, 418, 161, 15159,296, 1727, 295, 15161,15162,15160,296, 304, 161, 1362, 15163,15165,3267, 1343, 295, 418, 5481, 161, 15164,15168,304, 295, 161, 2880, 295, 15171,15172,15173,15174,15175,15182,1297, 417, 417, 750, 409, 696, 414, 15176,15179, 15180,4164, 3046, 15177,1173, 15178,1042, 1270, 161, 14527,418, 15181,161, 15184,15185,1459, 15187,15188,15189,15190,1109, 15186,1042, 1093, 1639, 295, 11159, 301, 414, 608, 161, 417, 11181,8351, 5223, 161, 5780, 15192,421, 4879, 1010, 15197,161, 15198,15203,15205,1904, 15208,186, 1124, 15193,161, 15194,15195, 15196,161, 15199,1060, 1060, 15200,15202,15201,936, 186, 519, 1879, 15204,295, 468, 15206,15207,7859, 186, 1042, 161, 161, 15210,15211,15212,161, 15214, 15215,1042, 15216,484, 15217,414, 15218,15220,7150, 15221,301, 414, 161, 15223,7933, 1242, 15224,1093, 1297, 15225,15226,15227,15231,15239,15242,15271,15281, 15285,15290,15291,15294,15295,15334,15336,15361,15399,15400,15418,15428,15447,15457,15461,15463,15465,15228,1555, 15229,1093, 161, 15230,635, 414, 14527,440, 161, 414, 15232,15236,418, 15238,161, 1524, 15233,15234,15235,161, 7798, 7798, 686, 15237,161, 161, 3002, 3002, 1092, 304, 1323, 15240,7243, 15241,414, 15243,15245,15247,15252,15253,15259,426, 15261,13235,15264,15265,15267,426, 15270,15244,186, 5727, 5122, 161, 1563, 15246,161, 1151, 350, 161, 15248,1430, 15249,186, 15251,350, 5122, 15250,2963, 161, 706, 15254,15256,440, 15255,15257,15258,15260,301, 161, 1949, 161, 519, 15262,15263,427, 161, 414, 2539, 161, 304, 1756, 6520, 161, 15266,3203, 15268,2864, 418, 15269,15272,3730, 15273,304, 15274,1093, 15275,15276,161, 15278,440, 10663,435, 5469, 10655,1109, 1042, 1180, 161, 15277,161, 408, 15279,15280,435, 1036, 408, 468, 296, 15282,1145, 15283,1343, 440, 1345, 15284,15286,15288,161, 15289,1109, 161, 15287, 1184, 1092, 1198, 418, 1109, 161, 630, 1297, 304, 15292,5246, 295, 414, 15293,161, 15296,15297,15320,15324,15327,15330,15333,2157, 3880, 484, 1010, 161, 15298,15299,15301,15303,15305,15307,295, 3959, 15308,15317,15318,304, 4695, 408, 14261,15300,15302,161, 161, 161, 1012, 213, 628, 15304,4293, 161, 15306, 1418, 1372, 678, 15309,15311,3153, 418, 15314,15315,3194, 15310,161, 15312,15313,296, 426, 1595, 15316,1100, 1810, 430, 15319,418, 15321,15322,161, 418, 15323,161, 3302, 15325,15326,1042, 1343, 874, 818, 1916, 222, 1101, 421, 635, 15328,409, 15329,2851, 2539, 15331,296, 15332,1343, 3959, 1459, 161, 635, 1990, 161, 1010, 2755, 1037, 15335,9150, 161, 5469, 15337,15338,15340,4307, 15343,15344,15346,15348,15352,15353,15355,2182, 213, 15339,161, 15341,15342,301, 678, 15339,1173, 161, 15345,1223, 1829, 15347,15349,296, 301, 15351,15350,1949, 750, 2394, 2142, 161, 15354,161, 15356,15359,15360,301, 15357,1949, 15358, 1093, 15362,15363,15364,213, 15390,15391,11213,15392,15393,15394,2225, 15395,2362, 1426, 304, 414, 7002, 15365,15366,635, 15367,15371,15375,15377,15378,5717, 15379,7522, 15380,15381,15385,15386,4407, 15388,15389,213, 1198, 435, 5339, 15368,15370,161, 15369,5339, 161, 15372,15107,15373,15374,15376,1899, 3013, 304, 1134, 5767, 2191, 1876, 12563,420, 161, 5166, 975, 15369,15382,15383,15384,3181, 2021, 12563,1459, 15387,418, 15269,425, 430, 6654, 418, 3302, 186, 1042, 252, 161, 1563, 519, 161, 3728, 161, 4169, 1198, 161, 213, 15396,426, 12217,161, 15397,161, 1343, 15398,420, 295, 15401,213, 15402,15404,15409,15410, 1604, 15411,4073, 440, 15414,15415,15416,9117, 15417,1026, 161, 3730, 161, 2539, 1542, 15403,418, 4203, 15405,295, 15407,161, 15406,1778, 4422, 414, 6520, 15408,414, 260, 608, 161, 3959, 161, 161, 161, 9222, 1126, 15412,15413,1418, 161, 418, 440, 716, 213, 420, 161, 4111, 2050, 408, 161, 409, 1109, 161, 15419,295, 14287,1343, 15420,434, 15422,15426,3959, 2225, 1363, 15421,15423,15424,15425,1343, 1459, 304, 295, 186, 426, 161, 350, 15427,414, 213, 15429,15431,15433,15435,5506, 15436,161, 15437,15446,15430,15432,15434,302, 1060, 186, 161, 15438,15439,15440,161, 15441,6280, 15442,15443,15444,15445,2270, 15448,15449,418, 15450,2750, 15453,15454,7191, 3181, 304, 4055, 161, 1208, 161, 1042, 15451,15452,3487, 350, 996, 418, 1042, 9682, 7762, 9034, 3778, 2142, 15455,15456,418, 15458,418, 4901, 161, 15459,15460,5438, 1042, 10578,2340, 1092, 213, 15462,3281, 295, 1093, 435, 1093, 1093, 161, 15464,1198, 15466,56, 15563,15607,15659,15704,15706,15725,15726,15467,15473,15477,15483,15484,556, 15487,15489,15491,435, 15492,15495,15530,15535,15540,15545,15546,1036, 15556,15560, 355, 15468,15469,1640, 304, 15470,5645, 15471,5626, 3002, 608, 4081, 15472,15474,15475,4396, 414, 161, 161, 3654, 3479, 161, 161, 15476,3861, 556, 2651, 213, 15478,15479,440, 15480,608, 15481,161, 1223, 556, 15482,1189, 8750, 295, 5089, 295, 15485,15486,1459, 2094, 186, 418, 161, 15488,161, 161, 161, 161, 15490,4041, 1862, 414, 15493,213, 3928, 14313,15494,3397, 161, 1818, 296, 161, 414, 15496,15498,15501,4169, 3098, 11606,7819, 15526,301, 15528,3861, 15529,4164, 1727, 15497,14175,15499,161, 15500,15502,15503,15504,15508,15514,15515,15518,7007, 2931, 15519,15521,15523,6520, 161, 3728, 3194, 15505,15506,15507, 15509,15510,15511,15512,15513,4583, 350, 296, 15516,1895, 15517,7990, 1059, 417, 15520,15516,15522,414, 1303, 1482, 15524,15525,679, 8697, 296, 15527,304, 1101, 519, 418, 414, 1060, 414, 186, 1604, 1042, 161, 161, 15531,15534,7280, 15532,161, 2050, 15533,296, 1343, 1555, 15536,213, 1818, 15537,1500, 161, 1343, 161, 161, 15538,15539,161, 11171,15541,3766, 15543,161, 15542,556, 161, 2262, 8117, 1270, 15544,11817,296, 161, 2191, 15547,15552,440, 1365, 161, 15548,15550,161, 161, 15551,1699, 1738, 15549,1126, 304, 161, 301, 15553,15554,161, 161, 15555,414, 15557,304, 417, 296, 15558,1026, 15559,1087, 1640, 161, 186, 15561,1590, 6706, 15562,556, 736, 15564,1036, 15568,15569,304, 15589,15594,6860, 15595,15596,15599,15601,14309,304, 15605,15606,5246, 440, 161, 15565,15567,6083, 15566,1042, 161, 161, 3799, 2165, 213, 1756, 1126, 161, 296, 304, 2225, 1093, 1474, 15570,161, 417, 14723,1474, 414, 512, 15571,15574, 635, 1818, 15576,15577,15579,15580,1343, 13285,15583,15584,933, 4164, 15586,161, 15572,15573,15575,14261,15300,1013, 492, 1459, 161, 1896, 1895, 15578,1640, 2166, 1938, 556, 15581,15582,161, 679, 1060, 161, 1343, 15585,426, 5114, 15587,1640, 408, 628, 15588,161, 414, 161, 15590,1093, 15591,3880, 1823, 15592, 15593,608, 1050, 1028, 409, 161, 161, 1342, 1851, 9150, 426, 10655,15597,434, 15598,1343, 418, 161, 296, 466, 1604, 15600,2191, 4168, 1060, 15602,435, 295, 301, 15604,15603,465, 213, 435, 6966, 414, 4217, 5360, 15559,15608,15610,15612,15614,15622,15629,15630,15632,15634,15640,15645,15647,15654,413, 15656, 15609,418, 350, 304, 161, 15611,1150, 296, 15613,161, 4482, 519, 161, 5319, 15615,161, 15618,161, 15621,15616,15617,15619,1818, 161, 15620,409, 1459, 1400, 1425, 1042, 936, 15623,4289, 414, 15624,15625,418, 161, 15626,15627,161, 15628,1093, 519, 417, 3728, 15631,15633,161, 5754, 2245, 3002, 161, 15635, 1093, 15638,6239, 3088, 15639,1609, 304, 15636,15637,213, 1524, 3833, 440, 3102, 1938, 409, 15641,296, 519, 15644,6406, 295, 1818, 1042, 304, 15642,15643, 678, 1126, 1093, 1042, 15646,161, 161, 2245, 161, 3794, 213, 15648,15649,213, 295, 15651,3046, 15653,15650,2191, 465, 15652,1896, 1896, 161, 15655,161, 15657,15658,7961, 1563, 1519, 186, 15660,5362, 15661,15663,15664,15667,15668,15669,418, 15670,15671,15672,15673,15678,15679,15687,15691,15699,15702,15662,5386, 1028, 1028, 608, 15665,8203, 13977,15666,426, 296, 213, 15674,15676,304, 15677,414, 1230, 1042, 15675,1134, 161, 1208, 1042, 1850, 2142, 409, 15680,15682, 3479, 1343, 15683,15685,161, 15681,1012, 4052, 5675, 11799,213, 1371, 15684,161, 15686,15688,15689,519, 15690,15692,316, 15693,15698,304, 161, 15694,414, 15695,186, 5065, 1042, 1756, 15697,3213, 15696,414, 6190, 161, 15700,15701,1060, 161, 5238, 1876, 1297, 15703,161, 409, 15705,15707,15709,15710,679, 414, 15712,15713,15716,304, 15722,295, 1639, 1883, 15708,1818, 161, 4287, 6256, 1297, 15711,629, 1012, 414, 13274,6280, 15714,350, 2225, 408, 1126, 15715,15717, 15718,1563, 420, 474, 15721,6280, 4203, 1343, 161, 15719,161, 6280, 15720,295, 1042, 9044, 161, 15723,15724,295, 3476, 15727,15728,15730,3603, 15733,15734, 15731,15732,740, 1270, 15735,172, 14016,15737,15766,15767,15769,15775,15790,15791,435, 15800,15831,15842,15849,15867,15869,15875,15882,15900,15904,15738,15740, 8056, 161, 10881,15747,161, 15748,519, 15751,15762,301, 161, 15739,15741,15745,15746,1367, 15742,15744,15743,821, 161, 15749,15750,15752,15757,15761,3898, 15753,15754,15755,15756,414, 15758,186, 15759,5069, 161, 519, 15744,15760,2514, 1223, 519, 1245, 15763,15764,15765,15768,1343, 301, 2846, 15770,4987, 15771, 15772,4322, 435, 2324, 15773,15774,15776,1608, 161, 15777,15778,15786,15788,1173, 1899, 2199, 2270, 1143, 1470, 15779,15780,10216,15781,161, 426, 301, 301, 519, 706, 15413,1342, 15782,15783,15784,161, 161, 818, 1813, 706, 15785,15413,1490, 15787,15789,418, 15792,1367, 15795,15793,1037, 15794,296, 15796,15797, 2127, 15799,15798,304, 15801,296, 7893, 295, 15804,184, 15819,418, 3479, 15821,15822,1824, 1514, 15802,2245, 6933, 15803,2161, 161, 2427, 161, 161, 161, 15805,1851, 414, 15808,8035, 15810,15817,15818,2969, 15806,15807,350, 15809,186, 1150, 4746, 15811,15814,15815,2605, 161, 15812,3488, 15813,213, 15816,426, 2199, 414, 186, 295, 161, 1595, 15820,14044,1161, 1093, 414, 2770, 161, 15823,15825,15827,15829,161, 15824,161, 468, 161, 15826,484, 2182, 15828,2181, 15830,15832,15833,409, 161, 15837,15838,2741, 161, 413, 15834,15835,15836,15839,15840,15841,161, 161, 161, 1938, 1699, 161, 1150, 4582, 418, 295, 15843, 1590, 15844,15847,15848,1640, 435, 161, 161, 161, 15845,161, 161, 15846,1899, 437, 414, 213, 15850,2109, 161, 1097, 4482, 3315, 1905, 15854,15859,15861, 15866,1459, 15851,1351, 1458, 161, 15852,1343, 15853,414, 15855,15856,1042, 304, 161, 15857,15858,350, 15860,716, 3551, 5396, 2910, 15862,15864,15863,15865, 9518, 295, 161, 1458, 15868,161, 1640, 418, 161, 15870,301, 5875, 15872,15874,6131, 3046, 1180, 15871,1270, 15873,440, 414, 15876,15877,304, 295, 1343, 15879,15880,3907, 15878,161, 161, 1109, 556, 15881,296, 1595, 14167,1542, 417, 15031,414, 15883,15886,15890,15893,1756, 1371, 2538, 15894,15895,1829, 15884, 161, 15885,15887,15888,15889,15891,2181, 15828,15892,484, 4570, 4235, 2538, 15896,15899,1343, 161, 15897,1343, 15898,161, 15825,556, 15901,15902,417, 9450, 15903,417, 295, 421, 421, 1343, 161, 15905,2741, 161, 1270, 161, 15907,15908,15910,15912,1372, 15916,15918,15920,15911,15913,414, 15914,13986,161, 15915, 15917,15919,15921,1101, 161, 750, 750, 15923,15928,15931,15932,15924,161, 15925,15926,1458, 15927,15929,15930,161, 311, 1128, 15933,15934,15935,15936,59, 16375,61, 16747,16749,62, 16951,16954,16957,63, 17340,17343,17353,17355,64, 17520,17521,17599,1459, 1017, 15937,15938,15940,15946,15954,15963,15969,15972, 15975,15990,15996,16015,16017,16020,16066,16105,16166,1990, 60, 16275,16299,16309,16324,16337,1981, 16348,16367,15939,15941,5126, 161, 4461, 161, 15942,3479, 161, 11442,161, 15943,15944,15945,15947,15952,301, 161, 414, 421, 1010, 466, 161, 15948,15949,8181, 304, 15951,4707, 15950,418, 12239,161, 976, 15953, 350, 161, 1087, 10366,440, 15955,15956,15958,15959,3267, 1042, 15960,1371, 440, 15962,5679, 161, 15957,161, 9937, 1093, 3185, 15961,474, 3833, 15964,15965, 11898,3098, 15967,161, 15613,15968,161, 1639, 426, 1208, 418, 1467, 15966,350, 6222, 4737, 696, 418, 15970,15971,15973,15974,440, 295, 301, 213, 295, 1143, 15976,1012, 15977,15981,15986,15987,1343, 295, 15988,1080, 15989,304, 1609, 161, 8181, 1343, 1093, 161, 8181, 161, 6121, 15978,15980,161, 15979,161, 15982,15984,15985,1121, 414, 15983,7911, 350, 1818, 185, 186, 2430, 15991,161, 161, 5362, 15992,15993,15994,15995,1851, 4042, 15997,2113, 15998,16002,16003, 16011,16013,3002, 295, 1609, 10080,1042, 15999,16000,16001,4373, 2050, 4922, 414, 16004,3368, 16005,1208, 8431, 5294, 3778, 426, 161, 16009,161, 1640, 13404, 161, 16006,16007,16008,2440, 16010,418, 1356, 161, 16012,4461, 213, 933, 16014,2115, 16016,421, 16018,1093, 16019,1824, 161, 1590, 301, 1542, 414, 172, 16021,16022,8161, 16026,16028,16029,16031,16034,16035,2115, 16053,16054,16057,16061,16062,16063,1161, 608, 2293, 161, 408, 16023,2161, 16024,161, 16025,161, 1126, 16027,435, 1343, 1829, 16030,2864, 462, 3689, 1728, 1143, 3954, 4407, 635, 16032,4425, 301, 8058, 16033,414, 16036,16037,16038,3267, 16041,10545,16046, 1843, 15989,16049,3274, 161, 635, 1343, 161, 161, 421, 161, 16039,13977,161, 350, 161, 16040,16042,635, 161, 1343, 1343, 16043,440, 16044,16045,421, 2115, 16047,213, 16048,161, 161, 1867, 435, 16050,1093, 16051,16052,1292, 8390, 16055,418, 426, 16056,1640, 16058,16059,435, 409, 161, 16060,1847, 9518, 2024, 16064,16065,16067,16072,1343, 16081,16084,16086,16088,295, 1092, 16096,16099,16100,16102,1097, 295, 16068,12567,1028, 161, 16071,16069,16070,16073,16075, 1126, 16077,16079,161, 1981, 16074,1851, 426, 16076,418, 161, 16078,421, 976, 16080,161, 429, 16082,16083,161, 161, 16085,409, 5722, 161, 3399, 16087, 418, 161, 10192,161, 16089,16091,3635, 16094,421, 295, 1028, 16090,3250, 161, 16092,161, 350, 161, 186, 1042, 304, 16093,15311,440, 3153, 2111, 421, 16095,1093, 161, 9292, 16097,16098,1156, 2109, 418, 161, 161, 5157, 295, 16101,1426, 1270, 16103,16104,414, 16106,16107,16108,16109,16137,5839, 16138,6234, 16140,16141,16145,16146,16152,16153,16154,16163,16164,440, 635, 16165,161, 5207, 1467, 414, 16110,16113,16116,3198, 16117,1895, 16118,16122,16126,7332, 16128, 16129,5069, 16130,16135,3529, 16111,2162, 1093, 435, 16112,16114,16115,1189, 3529, 161, 16119,556, 3213, 16120,16121,16123,16124,16125,16127,1042, 161, 5005, 161, 1093, 161, 161, 679, 1305, 16131,16132,3479, 3730, 304, 16133,16134,16136,4680, 7762, 161, 161, 161, 161, 296, 3439, 1876, 1343, 5069, 16139,161, 1305, 3922, 1343, 1479, 408, 414, 16142,16144,301, 161, 161, 161, 161, 16143,161, 161, 3409, 213, 1595, 418, 161, 16147,16148,16149,16151,1459, 3267, 1343, 2526, 1343, 161, 1962, 1343, 1093, 161, 161, 16150,2115, 556, 1143, 16155,16158,16159,3267, 16161,440, 4678, 16162,16156,1882, 16157,16160,161, 1208, 474, 474, 161, 418, 418, 161, 7572, 161, 11623,4133, 1756, 16167,161, 16168,16170,16171,161, 16169,161, 186, 4313, 161, 3799, 213, 14422,414, 512, 161, 16172,1343, 16173,16176,16180,16185,16202,3267, 16205,16211,3218, 16212,16216,16218,16224,16226,16229,16235,16245,16249,16270,16274,301, 16174,161, 222, 16175,635, 2229, 16177,16178,16179,6166, 350, 3770, 5224, 1364, 16181,16183,16184,1727, 2921, 16182,16186,3302, 16187,3959, 16190,16192,6379, 16193,213, 1343, 5207, 1595, 16197,16198,161, 16199,161, 304, 414, 635, 16188,16189,161, 9292, 161, 3766, 16191,1093, 2224, 161, 2160, 186, 161, 161, 5954, 161, 3766, 16194,16196,16195,414, 296, 418, 9912, 4645, 16200,16201,421, 311, 186, 5941, 16203,1542, 161, 161, 161, 1609, 16204,16206,414, 16208,16207,161, 350, 1530, 16209,16210,295, 304, 161, 161, 556, 222, 1026, 16213,1092, 16214,1756, 14411,16215,5376, 414, 2130, 16217,8785, 3916, 161, 1093, 1050, 414, 1372, 9478, 3198, 16219,1756, 16220,16223,556, 417, 16221,16222,1060, 301, 936, 418, 635, 1938, 1109, 16225,1145, 1365, 161, 161, 161, 6589, 350, 161, 16227, 16228,16230,16231,16232,16234,161, 1875, 16233,16236,16238,16240,16243,16244,161, 16237,16239,161, 161, 161, 1343, 16241,6393, 435, 1343, 1479, 2191, 16242, 1093, 161, 4075, 2241, 1482, 161, 1026, 161, 186, 161, 418, 4491, 414, 161, 1343, 409, 16246,16248,3665, 16247,213, 1343, 3518, 3518, 414, 16250,5826, 8431, 161, 16251,304, 16252,16255,16257,14216,1215, 16259,16261,161, 1042, 16267,161, 16269,2115, 1418, 161, 1938, 16253,16254,16256,16258,435, 414, 350, 161, 16260,420, 161, 6966, 16262,16265,16266,425, 16263,16264,421, 161, 161, 421, 1640, 16268,1640, 1895, 295, 556, 4992, 16271,16273,3250, 12562,16272, 161, 4885, 161, 417, 1143, 1640, 420, 628, 418, 16276,16278,16279,16283,16286,16288,16290,16291,16296,1639, 16277,14391,16280,1343, 16282,1109, 5642, 16281, 2174, 6124, 15024,1459, 16284,16285,16287,1092, 435, 350, 161, 1459, 16289,933, 1482, 16292,12861,6217, 418, 16295,3250, 16293,350, 16294,161, 16297,16298, 418, 161, 161, 213, 1889, 414, 1223, 16300,16303,3098, 16304,1042, 4168, 7000, 311, 8026, 16306,16301,2864, 16302,1042, 161, 435, 161, 161, 3002, 161, 3198, 161, 161, 5368, 3315, 5368, 16305,16307,4203, 1387, 16308,3959, 2283, 161, 635, 16310,5483, 16311,16312,16313,1036, 5688, 16316,16317,213, 16318,16323, 14508,1896, 4478, 2313, 16314,3479, 213, 440, 16315,1899, 3529, 2174, 16319,2313, 16322,1371, 295, 16320,16321,4401, 296, 1223, 161, 161, 16325,16326,16331, 16334,302, 301, 1750, 186, 161, 2111, 4322, 161, 1343, 16327,161, 16328,4707, 161, 350, 296, 3689, 161, 4680, 16329,4065, 16330,213, 16332,1418, 161, 16333,16335,16336,161, 16338,7000, 414, 16341,440, 161, 1886, 16346,161, 16347,295, 16339,16340,174, 418, 414, 16342,16343,3479, 16345,15732,161, 161, 161, 1198, 16344,16349,16353,16354,16356,3267, 1609, 4062, 16357,16360,16362,16364,5722, 16366,16350,14391,16351,161, 16352,5399, 1892, 16355,418, 16358,16359, 161, 350, 1060, 421, 350, 16361,3928, 5164, 16363,304, 1280, 16365,2864, 2191, 933, 4992, 1823, 3276, 16368,16370,1042, 16372,1412, 161, 1161, 161, 16369, 16371,161, 7000, 2191, 4062, 933, 161, 874, 6192, 16281,1609, 16373,16374,8431, 350, 1609, 161, 16376,16377,16378,16442,16446,16454,16461,16465,16472,16477, 16484,2102, 16523,16525,16569,16592,418, 16640,16643,16705,16720,16731,16732,16735,16738,16741,16379,1466, 16381,213, 161, 16404,440, 11705,16410,16427,16428, 16436,16380,414, 16382,16383,16384,9478, 16385,4291, 16386,4313, 16388,16392,16398,16399,3959, 16402,635, 161, 185, 1161, 16387,7547, 16389,16391,10076,16390, 1134, 1042, 16393,16395,161, 16394,16396,16397,16400,16401,1305, 3603, 16403,1899, 1126, 1129, 161, 16405,1042, 161, 16406,161, 16407,5481, 16408,161, 3799, 16409,161, 161, 6354, 161, 16411,418, 417, 16412,16413,414, 4183, 16414,186, 5228, 161, 16419,16420,16423,16425,161, 16415,16416,1363, 16417,13856,16418, 185, 1757, 420, 16421,16422,16424,16426,16012,417, 414, 1538, 16429,1042, 440, 418, 16435,3178, 16430,16433,1343, 1727, 16431,16432,1093, 16434,414, 161, 6118, 213, 16437,16439,295, 426, 16440,161, 16438,414, 296, 213, 161, 3799, 213, 1418, 3778, 16441,1143, 1297, 417, 16443,295, 16445,161, 16444,6415, 418, 706, 16447,16448,316, 16452,16453,414, 16449,16450,6217, 1371, 16451,1343, 1151, 16347,161, 6286, 10036,1012, 16455,16456,418, 14073,1343, 16459,1845, 6264, 161, 16457,1042, 426, 7967, 7967, 3315, 5065, 16458,161, 161, 16460,421, 414, 3167, 1107, 161, 213, 696, 161, 16462,1343, 16463,408, 295, 2225, 418, 186, 161, 16464,350, 418, 435, 16466,16470,295, 16471,16467,6118, 4168, 295, 1583, 16468,16469,418, 2115, 418, 462, 16473,16474,16475,301, 14628, 1609, 222, 936, 161, 1343, 161, 418, 1028, 304, 161, 16476,418, 16478,161, 161, 16479,161, 16480,16481,16482,16483,16485,16487,16488,296, 13429,16500, 16503,16506,16507,16509,16520,16521,16522,5956, 16486,16489,16494,16496,4680, 295, 1363, 3959, 16497,161, 2442, 16490,16491,16492,161, 16493,16495,16498,16499, 16501,161, 16502,1534, 161, 1093, 16504,16505,1829, 556, 1371, 1372, 1351, 296, 4478, 16508,16510,2228, 434, 16511,16512,304, 16513,16514,16515,16517,16518, 161, 11654,414, 5126, 1109, 1371, 161, 161, 161, 3730, 161, 484, 16516,16519,161, 1757, 8134, 161, 4638, 1829, 295, 1851, 1851, 4680, 5630, 3529, 16524, 16526,16527,16529,16532,16534,16536,16538,304, 16541,16558,161, 16563,16566,16568,1884, 6264, 295, 16528,13682,1121, 16530,1343, 161, 16531,16533,414, 1060, 161, 4203, 16535,213, 6384, 161, 16537,1109, 1542, 417, 161, 16539,1418, 740, 1459, 161, 16540,16542,16543,16544,2313, 2109, 16546,16548,16549,5319, 16550, 16552,15989,16555,6264, 16556,186, 186, 161, 16545,350, 556, 16547,304, 185, 9929, 4058, 409, 1372, 5139, 1101, 512, 161, 434, 16551,161, 16553,16554, 16557,418, 12145,16559,16560,16561,16562,5239, 1656, 1093, 161, 1093, 5483, 161, 2730, 3527, 161, 161, 465, 161, 414, 3439, 3198, 1876, 16564,16565,161, 2393, 161, 4364, 16567,440, 418, 1846, 418, 4042, 434, 16570,16571,16572,16573,1109, 16577,16578,16582,16590,16591,1028, 15168,3518, 2362, 161, 5368, 213, 16574,16576,16575,296, 635, 296, 1908, 420, 4203, 16579,16580,16581,296, 4461, 635, 16583,16586,186, 16587,16584,16585,16588,16589,11250,2931, 3250, 3689, 304, 3101, 1101, 440, 5831, 16593,16594,16597,16605,16607,2165, 16608,16611,16617,1343, 16618,16625,16632,16638,1609, 10370,1343, 13523,161, 2596, 16595,16596, 16598,4168, 418, 16602,161, 16599,1524, 16600,16601,1482, 418, 16603,418, 3725, 16604,161, 414, 4583, 1608, 16606,213, 2115, 16609,418, 16610,16612,304, 161, 16613,16614,16615,16616,2225, 1126, 161, 16619,16622,418, 161, 11817,304, 1371, 161, 16620,16621,161, 295, 12887,16623,161, 16624,296, 1177, 16626, 16631,16627,16629,5455, 1582, 16630,7106, 16628,16633,7586, 9709, 16636,418, 16634,16635,16637,303, 222, 750, 16265,161, 418, 16639,161, 16641,1362, 295, 295, 295, 295, 16642,1885, 426, 2846, 161, 12771,295, 414, 16644,16645,16647,16648,16649,3267, 16655,16656,434, 16657,16659,16674,16682,750, 16685,16690, 16697,16701,16702,6264, 16704,186, 13523,186, 161, 213, 8955, 14508,16646,1896, 3002, 161, 296, 1042, 1343, 418, 161, 161, 414, 16650,213, 16651,3267, 418, 1173, 213, 16653,16654,161, 16652,161, 2826, 161, 302, 3706, 13318,2191, 1451, 1479, 3103, 16658,1042, 213, 1862, 16660,16666,16671,16673,1109, 16661, 16662,304, 2182, 1093, 161, 1177, 16663,16664,16665,12726,16667,161, 161, 16668,16669,16670,301, 301, 16672,1126, 161, 161, 3376, 16675,16678,296, 16680, 418, 2182, 16676,16677,4173, 161, 161, 16679,16681,414, 16683,414, 16684,186, 161, 316, 1093, 1036, 1459, 16686,16687,16688,1371, 16689,301, 13523,301, 161, 435, 161, 4093, 421, 1938, 1343, 6003, 6222, 16691,16692,16693,1343, 1459, 161, 16694,16695,16696,1343, 161, 1542, 1060, 161, 16698,301, 16699,16700, 161, 3509, 161, 1012, 295, 556, 16703,1012, 296, 1895, 13682,556, 409, 16706,16709,16711,16713,16719,16707,16708,350, 8750, 16710,16712,418, 933, 16714, 3766, 3776, 418, 16715,161, 16716,16717,16718,185, 296, 1482, 16721,3098, 16726,3479, 3959, 417, 16729,161, 4884, 16722,16723,16724,16725,16727,16728,16730, 3959, 409, 161, 466, 11539,296, 161, 409, 295, 16733,301, 16734,1143, 16736,6471, 16737,1026, 519, 16739,414, 417, 16740,161, 16742,4732, 440, 1042, 16743,1343, 16744,1851, 16745,16746,213, 1028, 161, 1143, 628, 16748,16750,16751,16752,161, 16753,16754,16756,16763,16776,16781,16797,16823,16826,16829,16831, 16861,16870,2191, 16900,16914,16925,16940,10415,16949,301, 16950,16755,16757,417, 16759,16761,4168, 295, 16762,1143, 16758,8491, 295, 161, 161, 1151, 1323, 16760,9593, 161, 16764,161, 16765,1459, 16766,16771,16774,3766, 16775,1026, 161, 16767,1542, 161, 16768,16769,16770,16772,16773,16777,1109, 16778,1876, 161, 5368, 16779,161, 16780,2191, 16782,222, 2811, 16783,161, 16784,16795,16796,16785,16787,16789,519, 161, 16786,16788,16790,16791,16792,16793,16794,1651, 161, 16798,6574, 417, 16799,16809,440, 16821,417, 16822,16800,16804,161, 16801,16802,161, 16803,16805,16806,1026, 16807,16808,1604, 1847, 414, 1459, 16810,3799, 3973, 5527, 16811,1343, 1756, 3152, 16818,3274, 1640, 1608, 16812,213, 16814,161, 16813,304, 16815,16816,16817,5238, 161, 16819,294, 16820,213, 975, 1449, 1173, 1449, 16824,16825,16827,491, 161, 16828,1336, 16830,16832,16835,16836,16837,16846,16847,295, 16849,301, 16859,16860,16858,1851, 1823, 16833,16834,161, 161, 2017, 213, 1899, 1363, 2934, 161, 1060, 16838,12861,16845,16839,16842,16843,16844,16840,16841,9918, 1343, 6034, 1042, 16848,6035, 6286, 161, 414, 16850, 16851,4866, 16854,2165, 16856,6393, 15548,420, 16857,2976, 6264, 16858,3194, 16852,16853,9918, 413, 16843,16855,15794,161, 6426, 1818, 295, 1418, 161, 2196, 1343, 1846, 9918, 295, 418, 161, 161, 161, 414, 16862,295, 16866,4884, 16869,16863,16864,16865,16867,161, 16868,1829, 16871,16877,16881,16882,16883,1343, 16884,16886,16891,16894,16895,16899,16872,16876,16873,1563, 9629, 1343, 16874,16875,16878,161, 304, 1343, 16399,4523, 16880,1093, 13810,16879,161, 296, 2943, 1343, 161, 5507, 2050, 1343, 3529, 5239, 161, 1134, 1981, 435, 3670, 3992, 161, 716, 16885,16887,16889,304, 16888,16890,16892,16893,3833, 440, 418, 16896, 1042, 418, 161, 16898,161, 16897,161, 1371, 2976, 9958, 16901,16913,16902,16905,4883, 16906,161, 161, 16903,350, 350, 16904,16907,16909,161, 16908,16910, 16911,16912,16915,161, 1425, 1042, 519, 16916,16921,996, 161, 1341, 161, 519, 301, 1934, 5512, 519, 16917,16920,301, 16918,16919,2755, 161, 16922,161, 161, 16923,15576,16924,161, 418, 2848, 16926,5552, 16927,440, 16928,16929,16931,16933,161, 933, 933, 16930,16932,1042, 161, 161, 679, 161, 4478, 161, 16934,16935,16938,2050, 186, 16936,16937,1060, 418, 8872, 414, 16939,414, 4712, 16941,1134, 16947,2655, 1343, 161, 16948,161, 16942,3315, 16943,16944,4308, 1343, 16945,16946,1426, 1876, 304, 417, 10256,418, 417, 16952,435, 1899, 16953,16955,409, 16956,409, 414, 16958,16962,16970,16981,16991,16998,17009,17020, 17025,7857, 17027,17032,17102,17138,17167,17184,17192,17237,17257,17283,17317,17326,1107, 17338,4042, 16959,161, 556, 16961,1432, 161, 304, 16960,304, 185, 418, 185, 1297, 161, 304, 16963,16964,213, 16528,1609, 16966,16969,417, 421, 161, 350, 1011, 414, 16965,1121, 161, 161, 16967,16968,16971,16977,16980, 1012, 16972,14263,16974,16973,430, 4688, 16975,4727, 16976,2280, 418, 2280, 11506,16978,1042, 440, 1343, 161, 16979,161, 350, 252, 3453, 8231, 16982,4883, 16983,16984,16988,5207, 161, 16990,1459, 417, 16985,1092, 16986,1343, 417, 16987,16989,4108, 16992,1595, 16783,16993,16994,16995,16996,16997,466, 161, 14508, 5606, 161, 161, 3112, 556, 3888, 710, 295, 161, 1818, 16999,17006,17007,161, 17000,295, 17001,295, 17003,17005,6422, 3002, 4682, 17002,429, 295, 17004, 414, 4726, 1372, 6462, 17008,186, 4727, 17010,17012,17013,17015,17016,17017,11294,7740, 17018,17011,1101, 414, 4680, 1026, 3479, 1542, 17014,161, 1042, 304, 161, 186, 350, 185, 161, 185, 2191, 417, 17019,13880,1323, 409, 17021,161, 17023,1371, 5626, 17024,7064, 17022,11817,3510, 296, 1895, 161, 750, 17026, 213, 6118, 8683, 17028,17029,750, 17030,17031,17033,17034,17037,17040,17044,3267, 17046,17047,17050,17072,1756, 17075,2038, 5602, 17081,17088,1010, 17095,635, 17096,17099,635, 1343, 635, 17035,8159, 161, 17036,161, 17038,17039,5368, 17041,4313, 1343, 2133, 17043,4922, 933, 1343, 1060, 17042,3098, 1343, 1756, 161, 418, 1937, 3954, 161, 17045,1343, 161, 161, 706, 296, 426, 3274, 17048,3098, 1343, 17049,2469, 17051,17054,17059,1371, 17065,635, 17068,2283, 635, 17052, 1425, 635, 17053,10454,296, 4922, 1343, 17055,17058,161, 17056,295, 17057,420, 5483, 1011, 418, 1876, 161, 2283, 635, 1818, 3098, 3766, 17060,4105, 17061, 17064,296, 17062,1885, 17063,1885, 3102, 1885, 2283, 3766, 161, 4083, 1343, 17066,414, 213, 17067,414, 161, 3098, 17069,17070,17071,418, 1012, 4732, 1842, 4218, 418, 1843, 17073,17074,740, 1847, 17076,17078,1343, 635, 17077,17079,17080,161, 1929, 17082,17083,213, 14164,465, 17084,17086,1885, 295, 3833, 17085, 17087,1126, 418, 1847, 304, 418, 161, 161, 418, 2313, 17089,4233, 4680, 1343, 418, 17091,17093,17090,17092,222, 1012, 17092,418, 17094,1343, 17097,3098, 11794,474, 17098,296, 10039,465, 17100,17101,418, 6462, 1886, 1886, 17103,17104,17105,17125,17129,17131,4478, 304, 1028, 1198, 1013, 17106,14077,17108,17109, 17110,17112,17116,17120,17121,4108, 17122,1851, 3730, 17107,3441, 4108, 185, 1757, 161, 17111,17113,17115,17114,17117,17118,17119,161, 161, 1847, 161, 418, 420, 3102, 17123,17124,186, 421, 17126,1093, 213, 556, 17127,17128,17130,8149, 17132,17133,17134,17135,17136,17137,17139,5684, 17140,17141,17153,17154,17155, 17156,17157,17163,17164,14935,1754, 295, 161, 3964, 161, 3970, 161, 161, 161, 17142,17144,17143,414, 161, 2362, 2190, 6283, 17145,17148,17152,1639, 17146, 17147,418, 17149,17150,17151,414, 686, 1042, 161, 17158,17159,17161,414, 17160,1208, 1042, 161, 1562, 213, 17162,1421, 186, 1042, 161, 17165,17166,2280, 17168,17169,213, 161, 3478, 17173,17175,161, 1342, 17177,635, 17178,17180,6083, 1017, 186, 17170,161, 17171,17172,17174,1042, 161, 933, 161, 213, 17176, 3928, 222, 301, 161, 17179,3922, 1727, 161, 8974, 17181,304, 1343, 418, 161, 17182,4316, 17183,17185,161, 1609, 1876, 17186,17187,17191,417, 1609, 414, 161, 3654, 16564,161, 1639, 161, 16987,17188,1042, 304, 161, 161, 161, 17189,161, 161, 17190,17193,17195,17196,3620, 1905, 17197,1474, 17201,17203,17212, 17215,17222,17230,17236,1180, 17194,161, 161, 1470, 1060, 519, 304, 161, 4712, 17198,17199,17200,1372, 17202,421, 17204,5065, 17208,17209,17210,1042, 17211, 17205,17207,2955, 17206,295, 421, 161, 556, 213, 425, 161, 421, 1343, 17213,17214,1180, 12892,17216,17218,17221,161, 435, 17217,1562, 161, 17219,1343, 17220,1270, 492, 1093, 409, 17223,17228,3321, 440, 1343, 17229,414, 161, 2864, 3087, 1905, 4500, 17224,17226,161, 17225,17227,1640, 1143, 161, 17231,17233, 1343, 1482, 17232,1787, 2298, 17234,17235,17238,409, 17239,3267, 3833, 17240,17241,4168, 295, 17243,17249,17251,421, 301, 556, 161, 161, 418, 161, 1093, 161, 161, 421, 296, 556, 17242,161, 16344,17244,304, 17245,17246,1150, 17247,414, 17248,161, 17250,2516, 1895, 295, 414, 2840, 17252,17255,295, 418, 161, 161, 161, 161, 161, 161, 414, 17253,295, 17254,17256,556, 1093, 1127, 17258,17259,17261,17263,17264,17266,17268,17271,17273,1126, 11987,17277,17278, 17280,2109, 1036, 1270, 186, 414, 186, 17260,7927, 17262,17265,17267,17269,17270,11960,17272,17274,17275,17276,1323, 186, 186, 316, 3603, 17279,7022, 1305, 296, 17281,17282,304, 1093, 466, 17284,17286,679, 17287,707, 161, 17290,11131,17291,350, 17294,17295,17315,304, 17285,161, 1092, 1092, 4898, 1754, 1028, 17288,3479, 1459, 17289,7280, 1270, 418, 6744, 161, 6744, 17292,1343, 17293,5065, 186, 1042, 161, 414, 13585,11813,213, 161, 185, 1109, 17296,17312,1042, 14717,440, 1343, 17313,414, 161, 17297,17299,161, 17302,17304,17306,161, 1343, 161, 161, 161, 17310,161, 17298,17300,17301,17303,17305,17307,4235, 17308, 17309,16426,17311,5307, 977, 1198, 750, 17314,17316,1343, 161, 161, 17318,1027, 17319,414, 3479, 17324,7848, 430, 161, 17320,17321,161, 1891, 17322,1042, 4065, 17323,17325,17327,17328,873, 17330,17332,17334,161, 17335,213, 17337,417, 301, 304, 161, 435, 161, 304, 161, 304, 17329,1060, 409, 418, 17331, 3169, 161, 17333,161, 409, 295, 161, 185, 17336,1876, 1818, 418, 1818, 1042, 17339,750, 414, 1036, 185, 1093, 17341,17342,17344,17346,3954, 17348,17349, 17350,17351,17352,17345,17347,6520, 3954, 213, 2755, 17354,161, 1342, 409, 556, 161, 435, 933, 17356,17357,17363,17371,17378,17384,17395,17405,17409,3689, 17413,17421,17452,9905, 17478,17479,17493,17508,409, 295, 304, 17517,17518,17519,161, 1418, 186, 1896, 17358,17360,17361,2449, 4168, 295, 1365, 5671, 17359, 304, 161, 1010, 1060, 17362,161, 17364,17365,2092, 1184, 17367,17368,17366,213, 1198, 161, 17369,17370,421, 17372,17375,5255, 6405, 295, 1343, 17376,5882, 17377,17373,17374,1042, 161, 3880, 4657, 1542, 1482, 7967, 696, 434, 17379,17381,17382,17383,1037, 161, 17380,296, 1343, 161, 417, 17385,17391,17392,17393, 161, 2442, 1538, 17386,6295, 1143, 17388,161, 161, 17387,17389,17390,17390,17394,296, 17396,17398,17400,6966, 440, 17402,161, 17404,161, 2225, 3002, 17397, 1143, 17399,6405, 3281, 161, 1899, 17401,440, 3992, 17403,2727, 440, 161, 161, 17406,409, 17407,301, 17408,161, 17410,17411,17412,161, 4576, 1343, 296, 4576, 1028, 17414,301, 17415,4313, 1297, 17416,304, 418, 17419,17420,1899, 1899, 17417,17418,161, 1459, 3551, 350, 1109, 466, 2225, 1085, 17094,1343, 161, 1012, 1842, 1343, 1843, 161, 17422,17426,10655,1093, 1895, 17431,17436,17441,17444,161, 414, 2362, 17423,161, 17424,15732,213, 161, 1080, 161, 17425,3518, 17427,13974,17430,17428,417, 17429,17432,17434,1981, 17433,296, 2142, 213, 17435,3341, 213, 17437,17438,161, 414, 17439,17440,17442,17443,296, 186, 1042, 1756, 936, 435, 2864, 17445,17449,1042, 161, 213, 161, 17446,17447,17448,17450,17451,2769, 750, 161, 586, 17453,17455,17458,17460,996, 17464,440, 17466, 17469,17473,17477,17454,2864, 1302, 440, 17456,161, 17457,304, 17459,17461,17463,687, 213, 17462,304, 3095, 161, 17465,1092, 161, 161, 17467,17468,435, 350, 1639, 2190, 11960,2442, 11817,17470,17472,440, 13849,17471,2442, 466, 414, 17474,6002, 440, 11639,17475,10655,17476,465, 1343, 161, 10320,4478, 17480, 3267, 17481,17485,418, 17486,17491,17492,1180, 161, 17482,17484,1042, 420, 1899, 17483,350, 3002, 4233, 161, 1093, 161, 17487,1640, 17488,304, 1297, 17489, 186, 17490,213, 161, 1026, 1341, 3198, 1876, 1270, 161, 161, 17494,17495,17496,17497,17501,1371, 418, 17504,17507,5788, 161, 213, 1343, 222, 161, 409, 186, 17498,17499,17500,17502,17503,161, 161, 4316, 161, 17505,17506,161, 13310,161, 186, 10198,418, 17509,161, 440, 2449, 17514,17515,17516,17510,3264, 17511,17512,1544, 1542, 417, 17513,417, 1093, 1026, 4203, 161, 1028, 161, 3267, 3113, 5021, 421, 2191, 295, 409, 295, 2245, 304, 17522,17525,2782, 17526, 17538,17539,17544,17545,17547,1028, 17549,304, 17592,17597,17523,161, 350, 17524,3518, 2931, 17527,17528,17529,420, 17531,350, 1284, 17530,17532,1109, 4382, 17533,17534,17535,17536,17537,409, 1760, 1093, 350, 17540,17541,17542,161, 17543,2130, 12238,296, 417, 3620, 17546,304, 1093, 3995, 17548,417, 1459, 17550, 1109, 17560,17564,17590,161, 17551,161, 9212, 3178, 17552,417, 17553,17554,17556,17555,17557,17558,17559,17561,1365, 17562,17563,17565,17572,17574,2868, 17579, 17566,17570,17567,17568,17569,17571,213, 12448,17573,17575,17576,17577,17578,17580,17582,17581,1515, 1059, 17583,17586,3164, 1365, 17584,17585,17587,17588,17589, 17591,3829, 2976, 3153, 418, 17593,17594,17595,2841, 17596,17598,17600,17601,17602,17603,17604,17608,17611,17623,17634,17651,17687,17688,17692,17702,17709,17714, 17716,17718,17755,66, 18481,18485,18489,18492,18540,18587,18598,18599,18611,7604, 295, 18614,17605,17606,435, 17607,304, 304, 17609,17610,17612,17615,519, 17616,17617,17621,17622,17613,519, 17614,1415, 628, 414, 17618,17619,1792, 161, 14990,17620,2576, 2523, 17624,17626,17627,17629,1109, 17631,17633,17625,409, 2182, 1173, 17628,17630,17632,7452, 17635,17645,17648,17649,1092, 17650,17636,17639,161, 2165, 17640,17643,17644,3959, 17637,316, 17638,1603, 6528, 17641,17642, 3893, 6009, 17646,17647,14391,519, 17652,17653,16422,17654,17664,17675,17677,17678,17685,17686,414, 519, 1787, 17655,10350,6860, 17659,2982, 414, 17656,161, 17657,213, 161, 17658,311, 161, 3056, 17660,17661,1087, 17662,1128, 17663,1297, 1421, 1437, 1302, 17665,17673,213, 161, 17666,17672,17667,17668,17669,17670, 17671,414, 302, 161, 17674,17676,1042, 161, 17679,17682,17680,161, 17681,17683,17684,2028, 2523, 213, 1640, 17689,17690,17691,213, 213, 350, 17693,17695, 17700,17701,17694,519, 3013, 12495,17696,3555, 1328, 17697,17698,17699,1425, 1042, 936, 2345, 17703,304, 17706,6264, 17704,17705,17707,17708,2088, 17710,17711, 17712,17713,17715,1143, 17717,295, 414, 161, 301, 9557, 186, 3837, 17719,17723,17724,17726,17746,304, 17752,161, 17720,17721,17722,1590, 418, 304, 161, 161, 17725,679, 13226,161, 17727,17732,16908,1429, 17734,161, 17735,17728,161, 7696, 17729,1150, 17730,17731,5122, 17733,8758, 2134, 161, 1365, 17736,17741, 17737,17738,17739,17740,1156, 17742,17743,17744,3555, 17745,301, 17747,17748,628, 350, 2053, 161, 17749,17750,17751,17753,17754,17756,17762,17772,17774,17775, 17777,14632,17780,17803,17804,1107, 414, 17757,161, 161, 161, 437, 17758,17759,17760,17761,1425, 161, 1042, 1150, 213, 3451, 17763,17764,4401, 17768,17770, 421, 4523, 17765,1195, 3002, 17766,17767,1093, 302, 17769,186, 17771,17773,2585, 161, 17776,5089, 11512,17778,17738,17779,17781,17786,17790,17794,17802,17782, 17783,17784,17785,17787,161, 17788,17789,17791,715, 17792,17793,1305, 437, 17795,467, 17798,17796,17797,161, 1133, 435, 17799,17800,17801,1772, 5021, 12432, 2081, 519, 17805,17811,17832,17840,17850,17872,2419, 4497, 17806,17807,17808,17810,17809,161, 1714, 17812,17813,17814,17816,17819,8852, 17815,1303, 556, 1270, 17817,17818,17820,17822,17825,17826,17830,3554, 17821,17823,17824,8955, 936, 17827,17828,17829,161, 1256, 17831,17833,17835,17837,17839,1297, 2419, 17834,17836, 17838,1302, 3341, 1188, 1121, 304, 437, 2868, 17841,17845,2353, 4438, 17842,17843,1538, 1042, 161, 17844,304, 16997,1208, 11265,17846,17849,17847,17848,1316, 1563, 296, 1426, 17851,17852,17861,17864,8364, 17853,17856,301, 17854,17855,1150, 1284, 17857,1302, 17858,556, 17859,1332, 17860,1421, 5186, 161, 1903, 17862, 17863,5021, 17865,17866,17870,17867,17868,17869,1614, 17871,556, 8612, 2353, 17873,1317, 304, 17874,1093, 8593, 414, 17875,17910,17913,18007,18091,18117,18170, 18189,18204,18211,18218,18219,18221,18223,18239,874, 18248,9466, 18255,18340,18449,18451,1161, 3013, 18478,17876,17878,17884,16071,17893,17896,17897,17902,17905, 17906,161, 17877,1317, 3778, 17879,1515, 17880,17881,17882,17883,2367, 304, 17885,17889,17890,17886,17887,17888,1128, 304, 17891,17892,17894,17895,161, 17731, 17898,17899,17900,17901,17903,17904,17907,17908,17909,4218, 17911,17912,17914,17922,1432, 17927,17938,17941,17996,18001,2028, 17915,17916,17920,1036, 17917,1150, 17918,17919,17921,9101, 17923,1212, 17924,3607, 1284, 1529, 17925,17926,17928,17932,1042, 17937,1121, 17929,17930,304, 17931,7696, 161, 17933,17934,17935,17936, 1208, 296, 417, 686, 418, 12385,17939,17940,17942,17943,17946,17959,17991,17944,17945,161, 1042, 17947,17951,17948,17949,17950,17952,17953,17954,17955,5021, 12303,17956,17957,17958,17960,17965,17979,17985,17961,17963,17962,17964,17966,17971,17976,17977,17967,17968,17969,17970,8758, 17972,17973,17974,17975,17978,17980, 17981,17982,17983,17984,17986,17987,17988,17989,17990,17992,17993,17994,17995,17997,18000,17998,17999,1321, 18002,18005,18006,18003,414, 18004,1421, 311, 437, 426, 18008,18038,18074,18078,18090,1310, 18009,18011,18012,18015,18019,18021,18027,18035,161, 716, 18010,17958,18013,18014,1421, 1507, 18016,18017,18018,18020, 1212, 4497, 18022,18023,18024,18025,18026,304, 1206, 18028,18032,18029,18030,18031,18033,18034,18036,18037,18039,18043,1093, 18047,18050,161, 18053,18054,18068, 414, 18040,414, 18041,161, 18042,18044,18046,18045,1425, 1586, 1426, 18048,18049,18051,18052,161, 3555, 1212, 1114, 1127, 161, 17994,18055,18058,18061,18056, 18057,350, 1335, 18059,2891, 10390,18060,18062,18063,18064,18065,18066,18067,18069,18070,18071,18072,18073,18075,18076,1426, 1806, 18077,18079,18082,18083,18086, 18080,18081,1384, 1042, 437, 18084,18085,1042, 18087,18088,18089,1060, 8955, 414, 936, 18092,12303,18094,18101,350, 18102,161, 18104,18107,161, 18093,18095, 18096,18099,18097,18098,427, 1087, 18100,17995,161, 18103,18105,18106,18108,18109,18110,18115,17964,18111,18112,18113,18114,18116,18118,18124,18133,18141,18151, 18160,18167,350, 18119,5217, 18120,8781, 18123,18121,3512, 18122,9285, 213, 161, 18125,18128,18132,18126,186, 18127,161, 556, 2350, 1216, 18129,18131,2309, 18130,18134,1366, 18135,18136,18137,18140,295, 18138,18139,1189, 161, 1459, 161, 18142,18146,18147,18148,18143,18145,1208, 18144,1421, 1208, 1257, 1212, 12303, 18149,161, 18150,2817, 1438, 301, 18152,18154,18153,18155,18156,1538, 1042, 161, 161, 1305, 18157,18158,18159,304, 18161,18165,1212, 311, 18162,18163,18164, 18166,18168,13034,18169,18171,295, 18173,18179,18180,2109, 1896, 18182,161, 18188,18172,414, 6528, 18174,8033, 414, 18175,18177,8682, 18176,1772, 936, 519, 18178,1459, 1604, 18181,14010,18183,18186,186, 4109, 1459, 1604, 161, 5656, 18184,1093, 18185,18187,18190,18195,18198,18202,18191,18194,18192,18193,1322, 186, 161, 1212, 2880, 1042, 18196,5222, 18197,18199,18200,18201,186, 2901, 1426, 18203,18205,556, 5455, 18206,18207,18208,1206, 18209,1208, 18210,556, 418, 1198, 18212,18213,18214,18215,18216,1208, 750, 161, 18217,18220,474, 18222,418, 414, 18224,18225,18227,14717,18228,161, 18234,1504, 304, 18226,161, 1889, 1093, 161, 18229,18233,18230,3555, 18231,2298, 18232,18235,18238,18236,18237,161, 18240,18242,18247,18241,18243,18244,18245,18246,18249,468, 18250,18251,18252,161, 3321, 161, 18253,18254,1689, 296, 18256,18258,18261,18276,18289,440, 18290,18291,18297,18326,9343, 18257,1823, 18259,18260,10390,311, 1212, 18262,18266,18268, 18273,18274,18263,18264,9065, 161, 18265,18267,18269,18270,18271,18272,18275,18277,18278,18285,1080, 18286,1134, 3555, 18279,18281,18280,18282,161, 18283,18284, 2770, 8763, 18287,18288,1042, 161, 1590, 2353, 3721, 18292,18295,18293,18294,186, 1091, 2340, 18296,11918,1425, 1042, 418, 18298,18306,18307,18318,1150, 10540, 18299,18301,18300,1212, 186, 1042, 1303, 1198, 18302,18303,213, 18304,18305,17934,16820,18308,18311,18309,18310,8593, 161, 18312,18313,1208, 18314,18315,18316, 18317,161, 18319,18320,18322,18321,186, 1586, 1426, 18323,18324,18325,13074,161, 18327,18332,18334,18335,18328,18329,18330,18331,18333,17818,18099,1692, 418, 2040, 1216, 1208, 1042, 18336,18339,18337,18338,18341,18342,18421,18426,18427,18433,18448,350, 304, 17995,18343,18346,18351,18358,18419,296, 18344,18345,463, 161, 1329, 18347,18348,10994,18349,18350,2040, 18352,18354,1189, 18353,18355,213, 161, 18356,18357,1276, 186, 161, 18359,18362,18370,18375,18380,18381,1130, 18382,18383,18390,18395,18403,18408,18412,18413,18360,1087, 18361,18363,18364,18368,18369,304, 12562,18365,18366,18367,161, 7383, 8684, 18371,18372,186, 18373, 161, 18374,18376,18377,18378,18379,1042, 418, 161, 1217, 161, 8203, 2196, 18384,18388,18385,18386,18387,5021, 9205, 18389,18391,1151, 1954, 715, 8074, 1189, 18392,18393,18394,18396,435, 18400,18397,18398,18399,18401,18402,1212, 1538, 1231, 2725, 186, 18404,18405,18406,18407,186, 1091, 161, 414, 18409,18410,706, 18411,3169, 18414,18416,18415,1208, 1042, 437, 18417,18418,414, 1538, 1042, 161, 18420,18422,18423,18425,213, 18424,18428,18431,18429,18430,18432,18434,18437, 18438,18441,18447,18435,18436,17882,4024, 18439,18440,18442,18445,18443,18444,1425, 1042, 13082,18446,3630, 1688, 1282, 10673,350, 18450,4133, 18452,18458,18468, 18473,18476,18453,18455,18457,1688, 18454,1042, 18456,5021, 18459,18461,18462,18460,186, 1586, 9704, 519, 311, 18463,186, 18464,1303, 1426, 18465,556, 18466, 18467,1425, 12266,2340, 18469,18470,18471,18472,1334, 1425, 1042, 519, 1401, 1405, 18474,18475,1383, 18477,18479,3405, 18480,2050, 301, 2998, 18482,1470, 301, 414, 3006, 301, 161, 18483,161, 18484,18486,420, 18487,18488,18490,18491,18493,213, 18496,18497,311, 18500,18501,18502,18506,18538,18539,426, 12799,1862, 18494,18495,304, 2129, 9040, 161, 4967, 421, 301, 418, 18498,15179,18499,1060, 1161, 18503,414, 18504,186, 18505,1343, 161, 161, 9129, 414, 556, 18507, 18508,18535,18509,18512,18513,18518,18520,18525,18532,18510,18511,18514,18515,18516,18517,18519,17818,18521,18524,18522,18523,18526,18529,18527,18528,1421, 1421, 18530,18531,18533,18116,18534,716, 12303,9101, 18536,18537,1315, 8750, 18541,18552,7494, 18553,18557,18566,1173, 18567,18571,18573,18574,18577,18582,18584,18542, 18544,18548,5610, 18550,1542, 1343, 1173, 301, 18543,161, 414, 414, 1026, 18545,18546,414, 161, 18547,4352, 18549,18551,161, 1640, 429, 301, 18554,3529, 350, 16694,18555,1899, 18556,18558,18559,18561,18564,161, 18560,18562,301, 9557, 161, 14391,18563,18565,409, 6965, 161, 18568,8405, 3056, 18569,161, 18570, 414, 1426, 161, 18572,350, 296, 161, 434, 18575,161, 161, 12639,18576,18578,1087, 18579,18580,18581,417, 18583,5698, 18585,12771,304, 18586,15901,18588, 18592,8136, 2809, 18594,18596,213, 18597,18589,18590,161, 18591,18593,414, 1042, 161, 18595,1916, 304, 418, 161, 6678, 435, 18600,18603,18609,18610,1060, 18601,2755, 161, 18602,161, 679, 4478, 18604,414, 18605,18608,706, 161, 18606,161, 418, 18607,1126, 1128, 18612,12567,18613,2755, 16071,301, 7859, 608, 355, 18615,2741, 1223, 18618,653, 18621,18616,18616,350, 18617,18619,18620,18622,161, 161, 161, 1951, 68, 69, 18897,18898,18927,70, 19043,2306, 19044, 19126,18623,18630,18665,18667,18668,18670,18675,18677,18681,18687,18692,18703,18728,18732,18736,18750,18756,18760,18762,2306, 18765,18771,18624,10009,18627,3185, 4522, 161, 161, 18625,1042, 2378, 18626,1042, 3833, 18628,18629,2538, 608, 18631,18633,12246,18634,18636,6153, 18653,18656,7337, 6153, 18632,18635,414, 161, 18637,18640,10626,18642,2864, 18643,1343, 18644,3603, 18645,18647,18650,161, 1093, 18638,18639,161, 18641,414, 1054, 679, 2539, 18646,18648,18649,18651,18652, 18654,304, 161, 161, 161, 161, 161, 18655,414, 161, 418, 301, 418, 18657,18658,10723,18659,18664,6160, 18660,426, 161, 2197, 161, 18661,18662,18663, 1818, 2050, 161, 1208, 18666,3012, 296, 18669,296, 18671,7389, 301, 18672,161, 18674,18673,679, 161, 6256, 18676,161, 18678,18680,161, 414, 18679,186, 2165, 3778, 161, 5122, 1143, 10069,2677, 304, 18682,18683,18684,15728,414, 3729, 161, 161, 161, 18685,18686,18688,5469, 18690,2474, 18691,18689,18693,18695, 18696,18699,18701,161, 435, 18694,1060, 161, 161, 161, 1542, 18697,1060, 414, 182, 161, 18698,418, 161, 4108, 18700,301, 301, 161, 417, 1538, 18702, 1093, 1418, 414, 18704,18705,18706,18708,1342, 18712,18719,18721,18723,18726,15277,18727,2142, 1217, 4461, 161, 18707,3181, 414, 414, 1595, 18709,1028, 818, 161, 18710,304, 7146, 18711,635, 161, 414, 161, 1500, 421, 18713,18714,161, 9040, 519, 18715,18716,1060, 1850, 409, 161, 161, 2537, 161, 18717,18718, 414, 1087, 161, 161, 414, 304, 18720,161, 18722,304, 1109, 161, 3488, 161, 2028, 18724,18725,295, 4128, 421, 1250, 418, 213, 301, 418, 1583, 1608, 2293, 18729,161, 18730,18731,18733,161, 18734,18735,6160, 161, 161, 161, 18737,5242, 6416, 18738,18739,417, 3102, 1824, 18740,18741,18742,2038, 18743,161, 18746,18747,608, 161, 2784, 350, 418, 435, 418, 2534, 11813,18744,18745,1042, 186, 161, 355, 161, 3169, 4075, 18748,18749,161, 18751,3603, 18752,18755, 3922, 519, 295, 18753,18754,5469, 18757,18758,736, 2167, 1044, 2225, 18759,3134, 3134, 161, 161, 18761,295, 301, 426, 414, 186, 2111, 186, 18763,161, 18764,2538, 6131, 414, 1060, 9512, 5552, 1109, 1060, 18766,635, 18768,7186, 161, 5242, 417, 18769,18767,418, 161, 304, 18770,7623, 301, 18772,18773,409, 18801,18808,18811,18820,18821,18825,18826,18831,18832,18846,18852,18855,18875,18883,1126, 18887,18893,18774,18778,18775,18776,18777,414, 161, 18779,18783,18784, 18788,18789,18790,18794,2624, 18796,18798,18800,213, 18780,18781,350, 18782,1150, 304, 161, 2626, 18785,18786,18787,18791,18792,18793,18795,161, 16629,18797, 2199, 1060, 304, 18799,18802,18804,18806,18803,18805,18807,18809,18810,936, 1851, 750, 1036, 1426, 18289,4105, 1706, 161, 18812,414, 1706, 18813,304, 18816, 18817,18818,161, 414, 161, 18814,414, 213, 11939,18815,1093, 18819,750, 161, 304, 18822,18823,18824,5394, 414, 18827,1935, 18828,9547, 161, 18829,18830, 5246, 1343, 1542, 3680, 414, 1036, 492, 18833,18834,18835,1109, 18837,18838,678, 2605, 18844,18845,1109, 161, 4885, 706, 301, 304, 18836,304, 304, 1184, 16987,161, 161, 18839,18840,18841,1050, 295, 435, 161, 414, 1699, 161, 161, 18842,750, 18843,417, 1999, 295, 1418, 18847,18848,18849,18850,18851,161, 18853,1583, 18854,1583, 1542, 1109, 18856,18858,18859,5751, 18861,18863,12432,18864,18866,18867,18868,11485,18871,18874,3954, 18857,213, 161, 18860,161, 1017, 1050, 18862,519, 519, 6153, 186, 3281, 678, 161, 161, 18865,186, 1563, 14906,3635, 18869,18870,1050, 18872,18873,14924,3721, 2331, 1498, 6276, 18188,18876, 18880,18881,6280, 18877,18878,933, 933, 161, 414, 161, 161, 6734, 161, 161, 18879,1060, 301, 3928, 1126, 161, 1305, 18882,161, 161, 1044, 414, 18884, 818, 1463, 18885,161, 1818, 18886,418, 161, 556, 417, 18888,18890,1145, 750, 161, 18889,18891,350, 2340, 18892,213, 161, 18894,18895,1107, 434, 161, 2539, 18896,18899,18900,18901,740, 18904,18908,18916,1042, 417, 18921,18926,484, 414, 18902,18903,3833, 3529, 8491, 13974,161, 18905,18906,311, 414, 18907, 1343, 417, 414, 18909,18911,18913,2472, 18915,417, 1604, 18910,18912,1542, 417, 18914,417, 1541, 3469, 2526, 161, 1173, 18917,18918,18919,18920,977, 3029, 3105, 18922,18924,18923,18925,18928,18929,18930,161, 18931,414, 18932,18935,18941,18943,18948,18949,18955,18957,18958,18978,18980,18983,18985,18991,18993,19000, 19001,19002,19008,1310, 19019,19027,19033,19035,19040,18933,18934,18936,161, 18938,18940,161, 18937,161, 161, 304, 18939,161, 1060, 161, 304, 414, 1093, 418, 1756, 18942,15915,18944,18945,18946,161, 18947,304, 1036, 14913,161, 414, 573, 2393, 18950,18951,5242, 18952,1934, 414, 7280, 18954,11482,1060, 1727, 1010, 434, 7076, 18953,18956,18959,435, 18964,417, 18960,161, 18961,12718,161, 18962,1042, 4461, 18963,414, 512, 18965,18966,3435, 18968,18969,10078,18970, 18971,5246, 18972,18974,18975,18967,295, 304, 1727, 161, 1387, 18973,3196, 304, 18976,4448, 18977,186, 1595, 304, 161, 18979,1323, 4284, 1042, 161, 6549, 18981,18982,301, 18984,1876, 414, 161, 3964, 161, 161, 8265, 1873, 18986,18989,417, 161, 7056, 18987,3002, 18988,2605, 161, 213, 18990,161, 1093, 9633, 18992,414, 18994,3435, 413, 18996,18997,161, 4615, 417, 4883, 18999,1545, 161, 161, 1026, 18995,4885, 18998,161, 1042, 4108, 161, 19003,19006,14628,19004, 418, 7523, 418, 161, 435, 19005,414, 161, 192, 19007,19009,19014,19015,18643,19016,19017,19018,304, 19010,6153, 19011,706, 161, 161, 2214, 19012,414, 161, 19013,1026, 414, 161, 2318, 409, 2050, 5469, 556, 19020,350, 19021,19022,19023,19026,19024,19025,161, 435, 6549, 19028,435, 2197, 19029,19030,19031, 19032,19034,414, 3250, 19036,161, 3198, 3435, 19038,936, 2195, 19039,19037,414, 6153, 3928, 161, 19041,19042,1351, 350, 19045,19048,19049,19050,19056,19059, 19064,19066,19070,19072,19074,19081,19086,19099,19103,19116,19122,19123,19046,19047,161, 4016, 304, 1949, 161, 1899, 18889,19051,161, 3117, 19052,19054,6405, 1028, 417, 1060, 161, 2463, 161, 19053,1042, 10789,414, 161, 10763,161, 19055,161, 12569,161, 4594, 19057,19058,19060,3102, 161, 161, 3855, 1778, 19061, 1121, 19062,19063,19065,161, 19067,19068,1230, 161, 414, 19069,16918,161, 19071,19073,5552, 161, 19075,19076,19069,417, 2115, 161, 304, 161, 161, 19077, 19079,161, 2474, 414, 414, 19078,161, 414, 161, 161, 1500, 161, 414, 161, 2393, 2748, 19080,414, 161, 1498, 19082,19083,19084,161, 1284, 161, 3439, 1876, 19085,161, 19087,350, 19089,19090,19092,4322, 19094,19097,1449, 19098,295, 19088,355, 5429, 161, 19091,1198, 3250, 19093,295, 301, 2526, 414, 19095, 19096,5069, 161, 5529, 19100,161, 19101,19102,19104,4395, 19106,19107,19111,19115,1426, 1060, 519, 161, 19105,421, 1415, 161, 9734, 161, 19108,19109,19110, 418, 6423, 418, 19112,414, 19113,19114,9732, 2538, 1367, 213, 19117,19118,161, 19119,4352, 19120,304, 161, 414, 4322, 304, 1198, 17662,19121,1093, 414, 161, 161, 161, 1797, 413, 301, 19124,1036, 19125,1150, 936, 19127,19128,19129,72, 19325,73, 19475,74, 19661,19664,19669,19730,19734,75, 4941, 76, 20045,20047,20106,20108,20117,19130,414, 19131,19132,19134,19141,19142,19144,19149,19151,19154,2449, 19159,19162,19185,19198,418, 19213,19220,19251,19269,19296, 19302,19306,19312,19316,161, 4364, 295, 19133,213, 4987, 1036, 4042, 1060, 161, 19135,19136,19139,3827, 19137,19138,4395, 421, 608, 19140,4364, 7848, 1270, 2280, 426, 161, 3112, 19143,296, 696, 19145,2362, 301, 417, 19146,161, 19147,19148,19150,213, 414, 19152,1150, 19153,161, 14527,1590, 4657, 1951, 1818, 301, 161, 161, 2280, 19155,19156,295, 19157,222, 295, 19158,19160,19161,161, 19163,19164,19165,19169,19172,19175,19176,19181,19182,1727, 19184,301, 418, 874, 607, 301, 222, 161, 161, 301, 1793, 19166,19167,19168,19170,161, 19171,161, 301, 161, 15932,409, 161, 19173,19174,19177,19178,2213, 1343, 414, 19179,6264, 19180,408, 3518, 19183,13452,6066, 161, 19186,19188,19191,304, 19194,19196,19197,301, 161, 301, 19187,11734,301, 161, 161, 19189,19190,1270, 19192,1017, 19193,296, 6131, 19195,161, 2313, 161, 4678, 161, 1101, 2538, 19199,19200,19202,19205,19207,19208,19210,413, 19211,2280, 213, 213, 1865, 17397, 161, 3954, 161, 1217, 4364, 19201,1640, 301, 161, 414, 519, 1037, 19203,19204,19206,161, 304, 2028, 19209,161, 9937, 161, 304, 679, 296, 295, 19212, 16696,161, 19214,296, 9892, 19215,19216,19217,1087, 161, 19218,1787, 418, 418, 418, 1179, 418, 296, 2539, 161, 9910, 14285,295, 161, 19219,161, 19221, 19226,2270, 1037, 19227,5362, 19230,19233,19237,19238,19240,19242,19245,19247,19249,19250,4255, 1449, 1343, 161, 1365, 161, 161, 3730, 4657, 19222,161, 161, 19223,19224,19225,418, 2538, 2024, 19228,19229,3730, 161, 414, 161, 161, 1590, 161, 1037, 1097, 19231,19232,161, 161, 4511, 414, 161, 19234,418, 19235, 19236,1028, 1028, 14894,302, 4373, 19239,161, 2539, 6520, 19241,161, 161, 1126, 418, 161, 19243,9733, 19244,19246,418, 1036, 161, 19248,4467, 3928, 304, 19252,10486,1343, 16927,19253,19254,6066, 19255,19259,19263,19267,19268,9557, 6703, 161, 3088, 19256,19258,1460, 19257,19260,19261,19262,295, 418, 161, 6703, 2837, 161, 161, 2428, 19264,295, 295, 19265,161, 19266,19250,161, 2092, 3827, 19270,2270, 19275,19277,19286,6153, 19287,4035, 19290,408, 13986,1097, 19292, 19271,19272,1934, 19273,1590, 19274,414, 19276,2050, 161, 418, 1372, 19278,19280,1036, 19282,1343, 19284,19285,1108, 161, 19279,2214, 418, 1108, 19281,161, 19283,1126, 418, 1595, 1595, 161, 1050, 301, 19288,19289,161, 19291,304, 7178, 19293,1343, 19294,161, 19295,9227, 1150, 19297,161, 19298,161, 161, 19299, 19300,1662, 3113, 4681, 19301,19303,14937,19305,19304,19307,19308,9557, 19309,6520, 19310,519, 19311,1228, 1173, 608, 14397,414, 19313,4410, 1093, 19314,1851, 1609, 161, 19315,296, 1028, 1011, 304, 19317,301, 304, 161, 19319,19323,19324,19318,2430, 19320,19321,19322,2672, 608, 19326,1459, 19329,19327,19328,19330, 19331,19332,19333,19342,19343,2221, 19344,19355,19356,19357,19358,19365,19369,19381,19388,19416,19422,19425,19448,19458,19468,19471,19472,18803,409, 213, 1150, 440, 19334,414, 19335,19336,19339,19340,19341,304, 161, 213, 2028, 161, 10778,161, 440, 5875, 19337,19338,213, 295, 161, 4042, 1042, 440, 4203, 161, 414, 161, 1010, 1899, 696, 301, 161, 19345,409, 213, 19346,1343, 418, 19348,19350,19351,2115, 19353,19354,295, 295, 19347,1818, 418, 19349,1876, 161, 161, 1028, 4638, 1459, 161, 1426, 1876, 874, 19352,11819,301, 3776, 19347,295, 4289, 4168, 161, 474, 1851, 161, 161, 304, 1851, 18836,519, 19359,19360, 4883, 19361,19362,19363,295, 161, 7783, 3101, 296, 301, 3776, 295, 4364, 19364,161, 414, 161, 161, 1474, 19366,19368,19367,161, 11546,3547, 19370,1850, 295, 19371,161, 19377,161, 19379,19380,426, 295, 1109, 19372,19373,19375,1343, 1851, 19376,15989,1459, 5381, 161, 295, 414, 11816,19374,350, 161, 414, 2165, 1371, 161, 295, 556, 1026, 1418, 1011, 19378,213, 409, 213, 4693, 1036, 7062, 19382,19383,295, 19385,1180, 19386,19387,11984,304, 161, 19384,6208, 414, 161, 4364, 161, 161, 295, 5736, 295, 16068,417, 1026, 414, 19389,19390,19396,161, 19398,222, 1109, 440, 1962, 19400,19407,19408,19411,19413,19415, 1107, 19391,1640, 556, 304, 161, 19392,19394,161, 19393,19395,18896,296, 9046, 3959, 19397,409, 161, 213, 19399,301, 418, 417, 19401,213, 19402,19406, 418, 1050, 213, 14415,161, 186, 1241, 19403,3461, 19404,14415,19405,4730, 161, 420, 6283, 161, 1479, 3880, 409, 222, 161, 2028, 213, 426, 19409,19410, 414, 296, 295, 418, 19412,19414,186, 14124,8019, 2741, 1733, 409, 19417,1011, 418, 19418,19419,19420,19421,3013, 19423,295, 19424,161, 4169, 3287, 435, 4883, 19426,19427,19428,19431,19432,19433,19434,19436,19437,19438,19440,19441,19443,484, 426, 19447,296, 3665, 161, 161, 19429,19430,295, 750, 161, 749, 1109, 3603, 6066, 19435,418, 10989,418, 19439,161, 12432,2797, 19442,161, 421, 161, 1343, 11546,213, 19444,19445,474, 295, 19446,1876, 418, 161, 430, 418, 628, 296, 19449,19452,304, 3776, 295, 19453,19456,2280, 161, 296, 19450,161, 19451,409, 466, 19454,465, 295, 418, 19455,4478, 161, 1343, 1270, 19457,295, 295, 434, 19459,19463,295, 417, 19464,19467,19460,1459, 19461,304, 9590, 161, 161, 19462,19465,4883, 295, 3250, 19466,296, 1042, 1270, 1343, 161, 19469,19470,1824, 213, 161, 10766,435, 161, 1107, 414, 9638, 1161, 1302, 3178, 417, 9044, 5339, 740, 19473,19474,296, 2111, 468, 1886, 19476,296, 1459, 19489,19491,19477,6868, 19478,19481,19485,19486,679, 434, 19479,301, 161, 19480,161, 19482,19483,19484,19487,19488,19490,556, 13261,161, 19148,19492, 19493,19494,19497,19495,19496,19498,19499,19500,19501,19504,19510,19517,295, 19528,418, 4879, 19531,19533,19555,19571,19605,19606,19608,19630,19640,19655,19656, 19657,19658,19502,295, 19503,2225, 304, 161, 2961, 409, 19505,1757, 19506,1538, 1876, 19509,678, 161, 161, 19507,19508,5494, 435, 9824, 1126, 19511,1343, 19512,3315, 9017, 1639, 296, 18702,161, 161, 19513,19516,19514,19515,161, 7885, 4308, 2442, 7981, 19518,19519,19520,19521,5469, 19522,19526,19527,161, 4609, 1343, 161, 296, 304, 1150, 1028, 304, 1343, 19523,19524,19525,4364, 301, 1270, 1851, 296, 6405, 19529,19530,1818, 19532,222, 5610, 19534,19536,19538,2225, 19539,1343, 19541,19543,19551,161, 19553,13781,3435, 417, 19554,1126, 19535,2195, 3195, 161, 418, 8158, 295, 9724, 15036,19537,213, 474, 678, 5082, 19540, 19542,2102, 161, 19544,3689, 19545,1962, 19546,3435, 3689, 19549,19550,2864, 161, 9046, 161, 418, 161, 2526, 418, 421, 3534, 19547,418, 19548,161, 296, 4922, 417, 1885, 418, 9901, 418, 1343, 1011, 19552,296, 418, 7442, 161, 296, 418, 161, 161, 414, 19556,19565,19566,2472, 295, 19567,19569,19570,440, 440, 1173, 440, 1640, 19557,19561,19563,3250, 19558,1242, 19559,161, 1563, 19560,1060, 19562,161, 7961, 434, 19564,19568,4919, 1184, 161, 3250, 3046, 161, 519, 1813, 435, 304, 417, 5671, 19572,19577,1134, 19586,417, 19590,19593,1343, 19594,19598,17098,19600,19603,4164, 417, 19604,19573,4169, 19575,161, 304, 19574,1747, 19576,5242, 161, 9172, 6966, 19578,556, 19584,1756, 19585,161, 161, 3250, 19579,350, 19580,19581,19582,19583,418, 2082, 161, 4638, 1042, 161, 161, 186, 420, 414, 8135, 1608, 5246, 8181, 1129, 1343, 426, 19587,19588,417, 213, 408, 161, 3453, 3267, 19589,417, 19591,19592,1387, 2306, 161, 161, 304, 186, 474, 213, 408, 19595,19596,19597,418, 16070,1143, 3766, 161, 186, 296, 9046, 1343, 161, 161, 421, 417, 19599,161, 19601,19602,4928, 1343, 418, 2280, 2474, 933, 296, 295, 4482, 4364, 12861,304, 421, 413, 414, 296, 3013, 1270, 19607,1426, 304, 161, 296, 19609,19610,19611,19614,19616,161, 19623,19629,1109, 9167, 19612,4631, 296, 6066, 19613,295, 678, 418, 2142, 19615,414, 19617,19618,19621,9459, 1343, 295, 13781,5875, 1609, 213, 19619,161, 161, 19620,19622,19624,11547,19628,19625,2837, 19626,1343, 295, 19627,2837, 1371, 14913,6127, 161, 19589,418, 161, 11045,1818, 19631,19633,3776, 3778, 295, 296, 19634,19639,161, 19632,435, 466, 19635,19636,19638,10798,3776, 304, 295, 161, 1150, 421, 161, 304, 161, 161, 19637,161, 161, 2241, 161, 421, 3917, 414, 19641,19642,7856, 1412, 295, 19645,19646,19653,14391,3222, 19643,19644,418, 1112, 15887,418, 19647,17282,19648,19650,161, 19652,16239,1093, 1829, 19649,19651,1937, 1938, 435, 414, 161, 1412, 1412, 161, 19654,1818, 2191, 161, 4935, 161, 296, 19659,19660,161, 19662,414, 19663,19665,19666,19667,19668, 19670,19679,19697,19714,19724,19671,750, 4203, 10176,1459, 1028, 19672,19673,19674,1699, 19675,19676,3291, 355, 3729, 1851, 161, 1028, 408, 1028, 4461, 19677, 19678,295, 19680,19682,19683,19685,19690,19692,19695,350, 3859, 19696,19681,296, 440, 295, 161, 1343, 2053, 161, 19684,295, 1343, 19686,3002, 414, 1012, 19687,9934, 3144, 1371, 4083, 19689,19688,19691,304, 1036, 161, 350, 1542, 19693,1036, 19694,19698,19699,19703,19705,19712,19713,5469, 161, 161, 19700,295, 19701,19702,1093, 4395, 19704,421, 19706,19708,19711,2134, 19707,8256, 4342, 19709,295, 1343, 418, 161, 19710,161, 1012, 8231, 14380,10626,161, 434, 4482, 19715,19719,19720,19721,434, 19723,484, 19716,19717,19718,19722,161, 161, 295, 11547,9517, 421, 19725,19726,19727,3551, 19728,19729,295, 161, 3551, 696, 161, 161, 1949, 2225, 1899, 1181, 19731,19732,19733,19735,19746,19755,11497,19778,19792,420, 19736,19737,19739,679, 11960,19740,296, 19745,19738,19741,421, 19742,2864, 19743,19744,3002, 161, 296, 5089, 1093, 434, 976, 19747,19750,19752,19753,296, 161, 19754,19748,161, 19749,161, 435, 161, 19751,435, 440, 19756,19764,19767,19768,19771,19774,19775,750, 1242, 19757,19758,19761,19762,161, 19759,213, 19760,1756, 161, 19763,19765,434, 19766,1208, 1109, 19769,19770, 161, 418, 1609, 418, 161, 161, 19772,19773,304, 1242, 440, 19776,19777,1042, 19779,19781,19783,679, 304, 19784,933, 19785,3907, 19786,19787,19788,4640, 161, 213, 435, 19780,161, 161, 6384, 161, 19782,4108, 3631, 1876, 14938,161, 2280, 1085, 414, 19789,186, 18373,19790,161, 161, 19791,1042, 417, 18366, 161, 19793,19797,19798,1223, 933, 350, 19800,19794,19795,19796,9298, 161, 19799,1542, 161, 1583, 161, 1223, 19801,19806,19813,19815,19825,19827,19828,19833, 19835,19836,19838,19850,19853,19863,19866,19875,19892,19905,19913,19921,19929,19932,19802,304, 16097,295, 3250, 426, 161, 414, 301, 19803,18562,19804,19805, 19807,19811,161, 1365, 19812,2378, 14749,19808,19809,414, 161, 3002, 1824, 19810,19814,2449, 434, 2280, 19816,19817,19818,19821,19823,19824,1851, 1036, 3776, 408, 295, 19819,19820,161, 304, 3959, 19822,1639, 12239,19826,311, 1028, 4203, 350, 418, 418, 19829,161, 19832,874, 161, 350, 296, 19830,161, 19831, 161, 418, 2143, 4203, 304, 19834,421, 15520,19837,19839,19841,19843,19844,161, 19846,6393, 19847,14628,295, 4879, 3551, 161, 19840,161, 161, 3827, 19842, 161, 1862, 1824, 409, 19845,296, 4203, 1343, 19848,6131, 19849,19851,19852,161, 519, 1097, 19854,2672, 519, 19855,161, 19856,19860,19861,740, 1037, 6256, 19862,161, 19857,19859,556, 19858,435, 304, 435, 2755, 414, 161, 2280, 16783,1635, 19864,19865,3529, 161, 16786,161, 304, 161, 484, 19867,19868,19870, 161, 1813, 426, 19872,19874,484, 2539, 19869,19871,161, 161, 19873,5736, 3529, 161, 161, 161, 1403, 2538, 19876,19877,19878,19879,19882,679, 19883,161, 1343, 19884,19889,19890,19891,414, 161, 161, 2289, 19880,19881,1722, 161, 161, 19885,19888,1895, 16493,19886,19887,19893,19894,4364, 1851, 19895,19897,19898, 11442,19900,19901,19902,2534, 19140,161, 301, 2539, 161, 19896,1223, 19426,161, 19899,519, 2539, 19903,302, 301, 1173, 19904,161, 161, 19906,9243, 19907, 19908,19911,2538, 2092, 417, 19912,1223, 161, 608, 161, 750, 19909,161, 19910,222, 296, 2538, 296, 304, 1180, 301, 19914,4425, 409, 19916,19917,1260, 19915,161, 19918,14937,161, 19919,19920,19922,9518, 19928,19923,19925,19733,19927,19924,161, 408, 161, 19926,2539, 421, 409, 6520, 408, 19930,7390, 1727, 19931,4395, 161, 4015, 421, 6131, 19933,19934,19936,5386, 301, 1010, 1173, 19935,608, 435, 161, 2539, 435, 3954, 19937,19967,19992,20010,20020,20033,20040, 1181, 19938,19939,434, 19940,19941,1036, 19942,556, 2537, 19946,19949,19951,19954,933, 19955,19957,19962,19965,19966,15412,213, 19798,304, 1640, 434, 295, 295, 19943,161, 19944,19945,19947,296, 19948,19950,161, 161, 19952,295, 161, 19953,414, 2331, 1818, 161, 484, 10536,301, 19956,161, 296, 474, 213, 19958,19961,19959,2750, 19960,19963,8525, 1818, 19964,161, 161, 1181, 1181, 4395, 7848, 4695, 19968,409, 3288, 350, 295, 19969,19970,2576, 304, 435, 19973, 19974,19975,19976,19977,19984,19985,1542, 161, 414, 435, 3501, 17254,4987, 1851, 19971,19972,2270, 4364, 295, 295, 1343, 161, 2280, 421, 161, 1851, 304, 426, 1036, 19978,19980,19979,296, 19981,19982,19983,1085, 295, 19986,19987,19988,19989,19990,19991,414, 421, 17254,19993,19995,19996,435, 5424, 19997,19999, 20006,20008,20009,434, 19994,295, 161, 10370,2837, 296, 296, 19998,1150, 4511, 161, 20000,213, 2225, 20001,414, 4255, 20002,20003,304, 20005,1028, 418, 161, 161, 16228,418, 161, 20004,295, 161, 2270, 20007,1851, 161, 421, 20011,679, 20012,1028, 414, 3913, 3250, 20013,20014,418, 20016,414, 1085, 20019, 295, 696, 295, 435, 295, 3730, 304, 296, 301, 434, 1012, 20015,414, 6423, 9937, 161, 161, 20017,161, 20018,20021,2539, 20025,20026,19140,3250, 20027, 20030,20032,484, 20022,20023,6505, 20024,1012, 9937, 20028,20029,161, 20031,301, 1097, 2755, 2539, 14924,161, 213, 20034,20037,20035,20036,20038,3250, 418, 1827, 304, 20039,1590, 20041,20044,20042,20043,418, 2539, 6256, 7945, 20046,20048,20049,20055,20057,20059,20065,20066,20068,20071,20072,20074,20076,20081,20084, 20090,20091,20097,20100,20102,20103,20104,161, 301, 518, 1418, 1924, 20050,20051,20052,4073, 2197, 3959, 608, 1097, 161, 4133, 2539, 161, 421, 20053,161, 4081, 20054,484, 1173, 20056,3827, 161, 6505, 19258,1173, 295, 301, 20058,301, 1093, 2280, 20060,20061,295, 20063,213, 430, 14578,161, 20062,20064,435, 295, 20067,20069,20070,1036, 161, 4473, 304, 161, 1343, 3922, 20073,161, 6013, 1126, 20075,2538, 20077,20078,20008,20080,355, 3529, 1097, 295, 301, 4879, 161, 20079,421, 1097, 19140,2539, 409, 296, 20082,161, 1851, 161, 20083,161, 161, 20085,16703,20087,161, 2457, 20088,484, 20089,20086,418, 409, 5396, 2142, 1343, 161, 5684, 296, 295, 7848, 301, 20092,421, 1343, 20093,3479, 2538, 20094,1181, 1778, 1026, 20095,161, 414, 20096,1343, 4342, 16691,15445,20098, 304, 3529, 6207, 161, 20099,161, 9243, 435, 295, 20101,161, 11547,9278, 295, 1813, 20105,421, 20107,20109,20113,20116,1754, 161, 8786, 20110,20111,20112, 20114,20115,161, 296, 295, 295, 20118,20120,20121,20122,20123,5410, 304, 1042, 295, 161, 20119,304, 19883,519, 556, 519, 161, 20124,20125,20126,20131, 20134,78, 2864, 79, 20816,20818,80, 21055,21060,81, 2306, 82, 21432,20127,20128,161, 20129,20130,20132,20133,20135,20136,414, 20137,20138,20161,20189, 20202,20205,20233,20248,20249,20255,20259,20267,20275,20315,414, 20382,20397,20399,20425,20452,20478,20516,20530,20544,20545,20548,414, 1190, 414, 20139,304, 20142,20144,20146,20148,20155,161, 7848, 20159,20160,1107, 20140,20141,427, 161, 7496, 304, 1036, 355, 20143,2020, 186, 17806,161, 20145,304, 20147,161, 20149,20150,20151,20154,161, 20152,304, 3631, 1876, 161, 20153,20156,20157,10541,1459, 7354, 4183, 20158,161, 304, 161, 1990, 20162,20163,20167,20174,20175, 20178,20180,2174, 20184,20187,20188,161, 1059, 301, 519, 304, 161, 3098, 20164,161, 1639, 1060, 20165,304, 20166,20168,20170,4168, 9733, 20172,350, 1217, 20169,4003, 5689, 1150, 20171,20173,20176,1042, 20177,295, 161, 161, 185, 429, 20179,20181,17039,706, 706, 20182,20183,350, 4359, 20185,20186,304, 316, 10446,302, 1059, 414, 20190,20191,20192,750, 304, 1343, 20193,20194,1026, 20196,6264, 20197,11982,1198, 161, 3529, 1935, 6264, 1827, 20195,295, 161, 304, 414, 20198,20199,1143, 213, 556, 20200,20201,161, 20203,20204,20206,20208,20210,20212,20216,20225,10354,1938, 301, 20207,2605, 20209,3922, 2199, 5750, 13220, 20211,417, 20213,20215,295, 20214,2782, 161, 161, 296, 161, 5745, 20217,20220,20224,161, 161, 20218,20219,1126, 350, 304, 161, 468, 304, 20221,161, 20222,20223,20226,20229,20230,20227,1044, 20228,20231,20232,20234,20237,1116, 1343, 6454, 20239,20243,20246,304, 1097, 20235,20236,519, 1343, 20238,301, 20240, 418, 1470, 161, 20241,20242,20244,2331, 10757,20245,10359,20247,1760, 6537, 316, 9458, 213, 350, 2115, 1935, 161, 222, 3046, 2782, 20250,9931, 1097, 20251, 5424, 20253,7010, 20254,7010, 20252,161, 3013, 161, 161, 1962, 8197, 1173, 20256,1107, 6577, 20257,20258,3226, 20260,20265,20266,1044, 20185,4285, 20185,1343, 20261,4316, 20262,20264,1609, 161, 20263,2196, 161, 301, 1823, 161, 20185,1093, 2028, 301, 20268,20270,161, 20271,20273,2082, 20269,4966, 161, 301, 161, 20272,1851, 161, 20274,1145, 161, 20276,20285,20294,20296,20299,20300,20302,20311,417, 161, 4602, 20277,20278,20279,20280,20282,20284,161, 304, 20281,161, 1990, 1036, 20283,2975, 20286,20287,304, 426, 20290,20293,1026, 355, 20288,409, 161, 417, 20289,161, 161, 418, 417, 161, 20291,161, 20292,161, 1983, 975, 414, 20295,1410, 350, 161, 2568, 1757, 161, 161, 301, 20297,7142, 20298,20301,1413, 161, 20303,20305,20307,20308,161, 2331, 161, 20304,20306,304, 3232, 20309,20310,9934, 20312,161, 20299,440, 1343, 161, 3075, 20314,161, 417, 20313,295, 1983, 3620, 414, 20316,20317,20321,20343,3267, 20344,1604, 20362, 20363,1343, 20364,20366,20367,20370,20373,20377,1161, 20378,429, 20318,301, 1037, 20320,3603, 20319,161, 20322,20324,20326,20327,20329,20331,20333,20335,20336, 20339,161, 20342,161, 295, 161, 20323,1191, 2508, 161, 161, 20325,1093, 161, 1640, 1640, 1143, 11294,20328,679, 418, 20330,161, 2221, 20332,20334,2428, 161, 350, 161, 161, 2539, 1459, 20337,20338,2921, 161, 4055, 414, 409, 474, 1012, 20340,2195, 1343, 1846, 20341,414, 4885, 1036, 706, 161, 418, 417, 20345,20349,20351,4003, 20352,20354,295, 20355,3227, 20356,20358,20359,20361,20346,20347,161, 20348,1896, 418, 20350,1895, 4992, 1459, 8158, 20353,213, 10684, 1365, 304, 2539, 1459, 1101, 20357,2840, 20360,1640, 15316,1341, 1818, 1343, 556, 301, 5671, 3267, 161, 161, 686, 20365,9848, 161, 161, 1343, 556, 304, 301, 2718, 20368,20369,295, 3453, 20371,296, 3267, 20372,1150, 1640, 8611, 20374,2142, 20376,1913, 2225, 20375,2374, 556, 20379,20380,161, 20381,20383,13723, 20385,1604, 20388,20391,20393,20394,20395,20396,4531, 304, 161, 20384,1093, 1044, 20386,304, 20387,304, 2166, 936, 20389,20390,20392,630, 1498, 1699, 716, 1698, 11482,2196, 1059, 1425, 440, 20398,8265, 1044, 20400,20404,20405,20406,20407,20409,20410,20412,20413,295, 20414,20416,19236,20420,20421,20422,20423,2362, 20401,161, 20402,20403,635, 304, 304, 1823, 2196, 2102, 1851, 1757, 295, 355, 519, 161, 161, 1217, 161, 20408,161, 213, 3002, 161, 350, 421, 14415, 161, 16888,20411,301, 5750, 2225, 1092, 296, 2114, 20415,1862, 1990, 1498, 4183, 1813, 10648,161, 1990, 20417,301, 20418,20419,1050, 6574, 1126, 3015, 421, 1962, 417, 2362, 161, 1190, 20424,20426,20427,20430,20432,430, 20441,1884, 20444,1806, 20445,20449,18538,1639, 1778, 2214, 10125,6703, 295, 20428,161, 20429, 1223, 5064, 608, 3728, 20431,295, 161, 3778, 161, 20433,20438,20439,1042, 440, 20435,20440,635, 20434,20435,20436,161, 20437,1883, 2161, 161, 1862, 304, 161, 4638, 418, 20442,1060, 20443,1181, 608, 7669, 2474, 301, 4003, 20446,6483, 11799,440, 20448,8611, 161, 20447,295, 1538, 4465, 213, 20450,161, 20451, 20453,1573, 20455,20458,20460,301, 4168, 20463,20467,20469,20471,20475,2430, 161, 7669, 20185,20454,213, 20456,10581,20457,161, 2024, 20459,2976, 161, 1787, 519, 14415,20461,20462,2028, 2262, 4335, 414, 20464,20465,2028, 20466,2106, 2782, 14198,20468,2298, 7186, 2028, 20470,519, 418, 20472,20473,20474,414, 161, 14415,1418, 20476,20477,20479,421, 20480,1818, 18836,20487,355, 20494,20495,20501,430, 20510,295, 161, 20515,408, 1818, 3112, 20481,20482,20483,20485,20484, 20486,4065, 15435,20488,20489,20490,20491,9167, 1343, 295, 3227, 161, 5722, 20492,20493,2165, 4844, 2393, 301, 161, 20496,20498,296, 936, 3602, 20497,1554, 20499,20500,1060, 20502,20503,20506,161, 1109, 1028, 414, 161, 2362, 161, 20504,418, 1093, 20505,11482,414, 161, 161, 1387, 161, 161, 161, 20507,1060, 11530,20509,20508,161, 161, 1109, 1109, 20511,161, 161, 20512,3501, 20513,20514,161, 295, 418, 20517,20521,20526,20528,161, 301, 20518,20520,10644,3527, 20519,11819,304, 4602, 4885, 20522,20523,3140, 161, 295, 301, 7010, 161, 20524,161, 1126, 1500, 5750, 20525,1060, 2214, 20527,5745, 1573, 304, 20529,161, 161, 301, 161, 301, 1498, 414, 20531,20533,20535,20536,1042, 20537,1905, 20538,20539,20542,20543,295, 2661, 426, 20532,20534,161, 1216, 1216, 161, 1143, 1276, 740, 7351, 13322,414, 296, 5313, 7859, 4500, 161, 1216, 20540,304, 161, 20541,161, 1482, 1680, 1528, 418, 417, 417, 5368, 3315, 1109, 20546,3098, 1876, 5638, 8432, 6690, 20547,161, 417, 11952,1413, 295, 20549,1797, 20551,8173, 1134, 161, 20552,301, 2196, 519, 20550,161, 161, 19235,2213, 5645, 414, 161, 3728, 936, 20553,1640, 20554,20579,20585,20592,20601,20610,20621,20645,20651,20672,20673,20674,20676,20693,20708,20721,20731,20732,20734,20764,20784,20787, 20800,20808,20812,20815,161, 20555,20556,20559,5747, 8797, 20560,161, 20561,20562,20566,20567,20570,20571,20575,3529, 420, 4726, 20557,20558,440, 161, 8073, 409, 4405, 161, 161, 1425, 1042, 161, 161, 418, 1042, 418, 5671, 20563,186, 1876, 1500, 20565,161, 296, 20564,296, 3013, 1042, 161, 161, 161, 20568, 161, 20569,1876, 161, 161, 1230, 161, 1042, 161, 161, 1823, 20572,418, 2167, 2447, 20573,20574,1343, 161, 1609, 20576,20578,161, 435, 20577,161, 161, 1609, 161, 1885, 4781, 20580,20581,20582,20584,3315, 355, 301, 161, 3002, 20147,355, 185, 710, 2117, 20583,1983, 418, 418, 301, 20586,301, 161, 20587, 20589,3435, 20591,5631, 161, 296, 295, 295, 1822, 20179,20588,884, 20590,706, 716, 706, 304, 1999, 3756, 2225, 4726, 213, 20593,3267, 20596,20598,1727, 20599,5477, 20600,1161, 11512,20594,1935, 161, 1935, 296, 20595,1343, 20597,1126, 316, 1640, 1101, 414, 20602,8351, 20603,213, 1093, 20606,1859, 1097, 1851, 20607,20608,20609,295, 635, 2449, 296, 1459, 20604,161, 20605,427, 19926,20611,20614,16696,14415,1899, 18785,20618,20612,20613,304, 1823, 1823, 20615,20616, 295, 161, 1305, 161, 20617,20619,20620,161, 4478, 161, 414, 20622,20626,20629,20632,20640,1343, 5722, 20642,20643,161, 3213, 161, 213, 20623,161, 1875, 20625,2596, 20624,213, 1128, 11246,20627,20390,706, 20628,1608, 304, 161, 20630,20631,161, 161, 296, 1092, 20633,20637,20634,20635,20636,161, 1689, 1823, 20638,20639,7411, 1133, 20641,20644,304, 7470, 418, 474, 20646,20647,2963, 4476, 20650,1372, 1012, 19945,20648,20649,16070,19426,161, 20652,20654,20656,296, 1851, 20659,301, 20662,20663,1350, 20664,3113, 20666,20670,301, 7003, 1343, 20653,161, 186, 1180, 20655,161, 161, 20657,20658,556, 2442, 161, 20660,20661, 16315,186, 19180,418, 3976, 3013, 465, 16070,161, 20665,161, 20667,20668,4465, 161, 4806, 20669,161, 8103, 161, 519, 409, 1818, 161, 295, 20671,161, 18246,20185,295, 20675,2963, 301, 5394, 161, 4993, 20677,20679,3730, 20680,20682,5736, 4168, 20683,20688,20691,20692,1823, 20678,1818, 5284, 161, 304, 161, 3959, 161, 20681,161, 304, 2963, 556, 304, 161, 20684,20685,20687,1889, 20686,1126, 20689,161, 2196, 1093, 20690,161, 161, 20694,304, 20695,3181, 20696, 20698,20699,1851, 20700,20703,20704,20705,20707,414, 1343, 1151, 2394, 1042, 435, 20697,434, 4077, 6199, 8616, 161, 1343, 161, 301, 414, 20701,6309, 20702, 1060, 296, 161, 1498, 161, 20706,418, 4139, 161, 414, 6046, 1028, 13643,161, 414, 301, 301, 20709,20717,301, 161, 20719,20710,20713,161, 10299,20716, 12263,2050, 20711,20712,414, 161, 161, 1060, 20714,20715,161, 1097, 161, 301, 1739, 20718,20720,20722,20723,20724,440, 295, 1823, 20726,20727,20725,1228, 16070,14508,20728,447, 20729,20730,161, 409, 296, 1956, 5319, 20733,161, 316, 161, 20735,20736,20740,20743,10233,20744,20745,20747,20748,20751,20753,20755, 20758,20762,1823, 304, 304, 426, 20737,20738,1093, 20739,20741,161, 519, 20742,161, 2378, 2539, 1092, 20746,1563, 2617, 161, 20749,429, 20750,213, 20752, 421, 421, 2538, 3198, 20754,1143, 20756,7220, 1884, 1343, 20757,350, 20759,20760,414, 20761,20763,304, 14933,20765,4726, 20766,4108, 20768,20770,20771,20772, 20776,20778,20779,20767,296, 1885, 20769,10331,9746, 2028, 1787, 1787, 20773,418, 20774,2362, 20775,161, 343, 161, 20777,19119,519, 465, 418, 20780,20782, 161, 1217, 161, 20781,414, 3102, 1042, 1343, 161, 2069, 20783,696, 421, 20785,556, 13300,3164, 20786,20788,20791,20796,418, 417, 161, 4042, 20789,9950, 8802, 161, 20790,296, 4015, 161, 4003, 20792,20793,1990, 20794,20795,350, 161, 186, 6379, 161, 161, 161, 1609, 1026, 5350, 11162,3634, 14314,417, 1026, 1990, 1990, 1818, 20797,1026, 20798,161, 8288, 1026, 5350, 20799,4511, 161, 161, 161, 161, 8634, 20801,161, 20803,20806,295, 161, 13922,20802,20804,161, 161, 20805,608, 4081, 20807,414, 414, 418, 20809,1022, 295, 414, 1060, 20810,20811,301, 1109, 20813,295, 1730, 301, 1760, 20814,18992,1797, 1107, 161, 20817,20819,20820,20821,20822,435, 20823,20826,20838,20852,20857,20872,20882,2537, 20899,20903,20917,20927,20981,20984,20994,21001,21003,21017,21036,21037,21048, 6889, 20824,1037, 161, 20825,161, 1365, 1093, 161, 1212, 20827,20829,1341, 20834,1143, 1956, 20828,304, 1060, 161, 20830,20831,1343, 20833,304, 1093, 20832, 1208, 10813,6537, 161, 1432, 161, 1060, 20835,414, 301, 20836,20837,1060, 20839,20840,20842,3555, 20846,20851,6097, 161, 20841,304, 2817, 418, 2943, 20843, 20844,1343, 161, 20845,161, 16694,3510, 20847,20848,20849,304, 161, 20850,161, 3002, 213, 161, 2167, 161, 20853,161, 301, 20856,4105, 1640, 20854,304, 161, 20855,20858,20861,20864,295, 304, 2289, 3529, 20865,20868,20869,161, 5254, 20859,556, 15732,161, 1343, 20653,408, 161, 20860,20862,20863,20866,304, 161, 1371, 20867,161, 4523, 20870,20871,20873,1143, 20881,414, 20874,5412, 2864, 20875,1093, 20877,20879,1609, 10094,1757, 20876,1042, 20878,14665,20880,186, 1042, 678, 161, 20883,161, 20884,20885,301, 20897,20898,715, 161, 7010, 2191, 1184, 1184, 20886,414, 161, 20887,1810, 20890,20891,20892,1042, 1563, 20896, 5875, 16441,161, 20888,20889,213, 1890, 161, 1555, 1054, 1640, 20893,1640, 20895,20894,311, 1310, 5166, 1876, 161, 7768, 1421, 20900,3281, 414, 161, 20901, 4232, 161, 10454,20902,161, 20904,20905,1635, 20906,20909,20912,161, 1060, 350, 1363, 4035, 20907,12569,20908,161, 20910,20911,5671, 4169, 20913,161, 161, 20914,3850, 20915,161, 161, 161, 19271,20916,20918,20919,20921,20922,295, 20924,20926,2229, 20920,161, 3959, 161, 1013, 2165, 977, 6165, 9057, 2109, 161, 556, 20923,1334, 186, 1042, 1756, 161, 414, 750, 222, 1432, 161, 20925,1042, 1126, 161, 20928,20930,20932,20950,3267, 20956,20961,20964,20965,440, 20971, 20973,1843, 20974,20977,20980,4164, 1609, 8797, 20929,750, 161, 1093, 20931,2150, 1899, 20933,20934,20939,15287,1842, 20945,556, 20947,295, 7207, 1843, 1037, 20948,161, 414, 161, 435, 435, 1609, 295, 20935,20937,161, 421, 20936,186, 20938,418, 20940,20941,20942,3529, 1372, 1101, 20943,20944,1458, 1013, 5631, 1101, 20946,1459, 468, 4169, 678, 7188, 7188, 440, 20949,161, 1019, 1847, 414, 20951,20952,161, 1343, 1343, 20954,1093, 20955,706, 933, 20953,16156,4737, 466, 1011, 414, 1143, 20957,161, 161, 304, 161, 20960,5228, 20958,20959,5228, 6309, 161, 20962,20963,350, 414, 161, 1938, 2880, 1876, 20966,20967,20968, 20969,20970,1126, 20972,304, 161, 435, 1371, 161, 161, 20975,213, 213, 440, 418, 20976,1010, 350, 20978,295, 418, 161, 161, 20979,20982,1044, 161, 414, 11817,20983,4291, 1270, 161, 2272, 161, 3267, 1604, 3827, 20286,1343, 20985,20986,20987,20991,20992,296, 421, 5671, 20988,20989,1343, 1896, 161, 161, 20990,8161, 11556,4111, 418, 3959, 421, 418, 20993,20995,20996,20997,20999,20998,1328, 161, 21000,186, 6637, 21002,2199, 1797, 21004,21006,21007,21008,1097, 161, 21010,304, 21011,21012,21014,350, 414, 7967, 21005,6703, 1101, 418, 161, 1367, 8162, 21009,706, 13964,20725,161, 21013,161, 21015,4308, 418, 161, 161, 21016,161, 3439, 1876, 161, 21018,2864, 21019,21021,21025,21027,1498, 304, 1343, 21030,21031,2864, 426, 11693,295, 7900, 213, 21020,21022,21023,161, 213, 1189, 213, 21024,21026,1458, 12661,21028,21029,3555, 21032,21034,1371, 418, 13424,161, 435, 161, 21033,435, 5429, 186, 1042, 21035,161, 3098, 18971, 301, 3103, 161, 3002, 417, 1609, 161, 21038,21044,2094, 440, 21047,161, 20901,21039,21040,21043,414, 186, 5190, 21041,161, 21042,213, 213, 3315, 161, 161, 21045,21046,13422,417, 414, 21049,7967, 21052,2199, 21054,414, 7967, 301, 1223, 21050,21051,3015, 21053,1143, 2199, 1387, 161, 21056,21057,21058,21059, 21061,21066,21070,21073,1760, 21062,3704, 414, 414, 3087, 1093, 1797, 21063,21064,21065,21067,21068,21069,21071,21072,5751, 4993, 21074,21078,21087,21104,21107, 21115,21119,21129,21132,2864, 21138,21144,21150,21160,21198,21208,21218,21234,21244,21248,21267,21285,21294,21297,409, 21075,7098, 2252, 5248, 161, 21076,1538, 20658,1042, 161, 21077,1425, 1042, 629, 21079,21080,1639, 21082,6914, 295, 21083,21084,304, 519, 1011, 1042, 21081,161, 2843, 21085,21086,21088,213, 21092, 21093,21094,21101,21103,21089,21090,21091,161, 7324, 213, 161, 1470, 818, 11476,1091, 296, 296, 295, 3227, 21095,4108, 21096,3013, 6309, 21098,19654,295, 874, 21100,3152, 933, 1609, 213, 213, 1151, 161, 21097,3002, 296, 21099,418, 1459, 1937, 519, 21102,1305, 936, 2313, 21105,161, 20420,3959, 21106,4308, 1851, 21108,296, 301, 5245, 21112,1109, 21113,301, 21109,21110,295, 21111,21114,818, 21116,1842, 9001, 1843, 21117,2165, 21118,418, 161, 414, 161, 21120, 21121,21122,1608, 21123,161, 21127,21128,1609, 947, 1050, 1538, 9687, 8872, 21124,5047, 10403,21125,21126,161, 10403,418, 21130,750, 21131,304, 161, 1372, 5631, 2848, 11385,1371, 21133,301, 519, 21134,304, 21136,11459,21135,161, 21137,355, 3046, 8495, 21139,21142,1109, 21140,21141,21143,1060, 21145,21146,21149, 2976, 301, 296, 213, 21147,435, 21148,4531, 161, 519, 316, 21151,21153,21155,21156,21158,21159,21152,14736,161, 161, 21154,1173, 21157,1093, 1824, 21161, 21164,21172,21195,21196,418, 4252, 519, 161, 21162,21163,21165,21170,21171,15559,161, 21166,21167,3799, 1756, 21168,21169,630, 1824, 414, 21173,21174,21176, 21178,21179,21181,21183,21184,18971,440, 21185,17124,21186,21187,21193,21194,1823, 17124,21175,3729, 16770,161, 21177,161, 17124,21180,350, 1317, 21182,1143, 3088, 13776,1363, 2514, 2262, 10140,1787, 4193, 21188,21190,1885, 21189,678, 21191,21192,213, 1640, 1886, 161, 21197,304, 161, 213, 1017, 1107, 21199,21203, 21204,1231, 21205,301, 21206,3770, 414, 21200,21201,21202,3368, 161, 161, 418, 296, 10847,301, 213, 750, 161, 161, 1538, 14120,161, 161, 21207,5503, 6046, 213, 1555, 161, 21209,21211,21213,9456, 21215,21217,21210,1343, 21212,161, 161, 21214,1228, 21216,21219,295, 19112,21221,21222,304, 21229,3479, 1498, 1590, 21231,417, 21233,8413, 161, 1126, 2362, 21220,414, 418, 1042, 8074, 161, 418, 519, 1028, 21223,21224,21228,1494, 1494, 161, 1036, 161, 1583, 15277, 21225,21226,21227,13643,1060, 161, 161, 2519, 20661,421, 161, 2362, 295, 21230,161, 161, 301, 161, 301, 161, 161, 2050, 8019, 21232,161, 1126, 161, 304, 21235,21236,21238,21200,1876, 222, 21239,21240,21242,301, 16554,21237,161, 161, 161, 304, 2526, 161, 161, 21241,1042, 21243,414, 21245,1156, 21246, 21247,414, 161, 9829, 1343, 2976, 161, 3512, 1036, 1028, 161, 414, 21249,5494, 21250,21253,21254,21257,161, 707, 21260,1036, 1990, 21261,21264,21265,21266, 161, 21251,418, 213, 5138, 418, 161, 161, 21252,350, 2864, 15435,1223, 1044, 1036, 21255,1608, 311, 426, 19654,440, 21256,418, 213, 1937, 1060, 21258, 161, 1060, 6577, 21259,4164, 5606, 3452, 161, 750, 21262,304, 161, 21263,1208, 4055, 161, 3721, 2770, 304, 21268,21269,21282,8181, 1150, 5532, 1778, 161, 519, 414, 21270,180, 21271,21272,10472,6755, 350, 3435, 21273,21277,161, 21278,21279,2626, 21281,18817,556, 21274,1757, 21275,1097, 21276,161, 304, 213, 186, 9333, 304, 1756, 421, 418, 304, 5941, 161, 414, 161, 161, 3435, 15579,1343, 161, 161, 21280,420, 421, 1818, 21283,350, 21284,161, 213, 628, 161, 21286,21287,21289,21290,1343, 21291,21292,161, 2270, 161, 4203, 4109, 435, 21288,11617,3002, 3510, 5350, 556, 484, 15102,161, 750, 213, 1216, 213, 21293,161, 21295,304, 161, 1223, 21296,21298,21299,295, 7669, 1813, 519, 355, 21300,21301,21302,21308,21329,21339,21355,21356,21361,21362,21363,21365,21371, 21375,21384,21399,21401,1990, 21406,21411,21416,21421,21425,21429,21430,13922,5935, 161, 161, 21303,19842,19243,474, 21304,161, 1367, 21305,21306,21307,1305, 1693, 21309,21310,21312,21313,21315,21320,21326,414, 1060, 21328,6980, 1050, 3049, 161, 21311,161, 736, 1754, 3140, 519, 161, 21314,1754, 10655,161, 21316, 2267, 21317,295, 21318,21319,2017, 304, 414, 414, 1341, 21321,21324,1343, 474, 161, 21325,161, 21322,161, 350, 161, 21323,408, 1640, 11946,213, 414, 280, 161, 5065, 1126, 161, 3041, 21327,421, 3827, 4461, 21330,1260, 21331,21333,21334,21335,21337,161, 1371, 6264, 21332,161, 3167, 1806, 1459, 474, 1459, 21336,21338,21340,21343,21348,519, 21349,21351,21352,21354,21341,304, 21342,21344,21346,21345,21347,686, 304, 3730, 21350,222, 161, 21353,301, 3529, 426, 662, 296, 161, 21357,21358,21360,1938, 1042, 21359,1371, 161, 1093, 421, 1494, 8458, 161, 21364,421, 1342, 21366,21369,1184, 2538, 301, 409, 21367,21368, 9733, 161, 414, 3013, 1093, 161, 161, 21370,161, 21372,21374,21373,186, 2387, 21376,21380,440, 936, 21382,4401, 296, 21377,21379,21378,1042, 2864, 161, 1161, 21381,21383,5409, 161, 296, 161, 2842, 161, 21385,21386,21388,21397,21398,5909, 4733, 161, 161, 414, 213, 161, 161, 1392, 4733, 21387,5552, 1374, 1042, 3771, 4407, 21389,21390,21391,21393,7819, 418, 1022, 1843, 21395,161, 1013, 740, 418, 21392,435, 161, 21394,3267, 21396,355, 1044, 301, 21400,21402, 21403,161, 3006, 21405,414, 418, 21404,301, 161, 21407,21408,21409,3770, 21410,213, 440, 161, 21412,213, 21413,161, 1851, 161, 2142, 21415,304, 2081, 3555, 21414,296, 13682,3555, 161, 161, 1134, 21417,1109, 304, 1343, 418, 3959, 21419,426, 1036, 21418,161, 9518, 21420,161, 21422,21424,21423,1787, 350, 21426,417, 21427,21428,21431,426, 295, 222, 12153,21433,21434,21435,11984,3267, 2766, 21438,7186, 21439,21442,1092, 21449,21451,21455,2082, 3518, 5469, 3479, 2225, 21436,21437,418, 17471,3169, 161, 1093, 1184, 1482, 161, 418, 21440,21441,1459, 21443,21445,21447,161, 161, 440, 21448,2191, 440, 1609, 161, 21444, 1143, 186, 1042, 161, 161, 301, 418, 21446,4885, 161, 11482,414, 161, 2719, 21450,21452,435, 213, 421, 21453,21454,435, 409, 304, 4964, 21456,21461, 84, 22252,87, 22637,88, 22949,89, 635, 23314,23317,23321,90, 23710,23713,23716,23718,91, 23885,23892,23929,21457,3435, 21459,635, 21460,1459, 16869, 161, 21458,2081, 161, 14937,2221, 1143, 21462,21463,21464,21465,21466,21471,21565,21599,21606,21609,21653,21668,21691,21704,21710,21777,85, 161, 21893,86, 22073,22128,22183,22197,22199,22200,22209,22233,421, 161, 414, 20022,1093, 21467,21468,304, 304, 418, 21470,161, 21469,161, 3447, 161, 414, 21472,7967, 21482,21489,21497,21498,21504,21512,21525,21532,21543,21549,21551,21554,21555,21557,21558,21560,1653, 21561,1060, 21473,21477,3833, 21478,21481,414, 21474,161, 6734, 21475,21476,6462, 1190, 3925, 1590, 21479,21480,222, 1184, 7651, 440, 21483,21485,21487,304, 7457, 21484,1126, 1112, 21486,9643, 21488,21490,21491,21493, 678, 2221, 7942, 21492,21494,21495,1639, 1896, 1639, 21496,21499,21500,21501,21502,21503,21505,21509,21510,2109, 687, 21506,21507,21508,21511,21513,21514,21515, 426, 4133, 161, 161, 21524,519, 301, 295, 1150, 3512, 304, 21516,301, 21521,4577, 21517,21518,21519,21520,21522,21523,3282, 21526,21528,1217, 21531,2655, 21527,161, 4395, 2538, 161, 21529,11436,21530,14010,1823, 414, 15421,21533,21538,21541,977, 21542,8197, 161, 21534,21536,21537,161, 21535,21539,21540,4169, 418, 21544,21548,418, 21545,1126, 1109, 21546,21547,418, 7014, 186, 21550,15044,19537,161, 161, 430, 21552,21553,9557, 8791, 10176,161, 21556,2750, 1541, 304, 21559,161, 1757, 2221, 2221, 21562,21563,21564,21566,21574,21576,21580,304, 2165, 21588,6405, 21589,21590,21592,21593,21595,1583, 21598,8772, 4322, 21567, 21568,21571,21572,21573,161, 21569,21570,1126, 1126, 418, 161, 3015, 21575,474, 21577,6105, 21578,21579,440, 316, 161, 1060, 556, 161, 5494, 161, 161, 21581,21583,21585,161, 7408, 1639, 21582,2050, 21584,2762, 418, 21586,21587,161, 161, 1343, 435, 301, 15003,484, 1459, 420, 1028, 21591,418, 304, 21594, 304, 161, 21596,161, 21597,1827, 1822, 161, 161, 1087, 161, 161, 213, 2183, 1343, 21600,696, 21601,21603,161, 21602,161, 21604,414, 21605,2776, 21607, 301, 21608,7180, 2678, 21610,213, 21617,21621,21623,21625,21629,21632,21645,21647,21649,21650,21652,1609, 1824, 8078, 21611,1097, 21614,161, 21616,161, 21612, 161, 21613,418, 21615,1793, 161, 21618,21619,21620,19718,161, 21622,2197, 1093, 161, 350, 161, 4332, 21624,1305, 21626,161, 6860, 4935, 21628,414, 161, 200, 1276, 21627,1093, 1343, 161, 3970, 213, 21630,21631,2331, 304, 21633,21636,21640,21643,21644,21634,21635,161, 161, 21637,21638,21639,161, 161, 21641, 2262, 21642,1297, 8945, 186, 1990, 12495,161, 418, 21646,1023, 1023, 1198, 21648,1101, 161, 161, 3551, 21651,1060, 1823, 21654,222, 21661,161, 21662,21663, 15690,21665,161, 7768, 3088, 21655,21656,21657,21658,21659,1935, 1173, 213, 301, 21660,440, 161, 3725, 1818, 1343, 1459, 21664,186, 12670,21666,186, 21667, 161, 7145, 21669,21670,7014, 21671,1555, 21676,21686,4008, 21687,21690,304, 414, 161, 1092, 161, 414, 21672,2880, 21674,1042, 21675,1905, 3771, 161, 21673, 3603, 350, 1143, 350, 1161, 2963, 1143, 21677,21678,212, 21679,21680,21681,21682,4164, 21685,185, 1126, 161, 1519, 1208, 2750, 1602, 21683,21684,1367, 409, 3365, 1097, 304, 21688,21689,427, 350, 3479, 304, 1367, 21692,21694,186, 21699,21700,1823, 21693,296, 2672, 186, 21695,21696,706, 2539, 222, 21697,21698, 1060, 161, 2538, 350, 21701,414, 21702,21703,213, 161, 21705,21707,21708,19948,1365, 408, 21706,4995, 1223, 161, 9733, 301, 301, 20620,15583,1093, 14770, 409, 20201,21709,418, 1093, 161, 1026, 21711,21724,21726,21728,21731,21735,21743,21744,21749,21753,21762,21763,21768,21769,21772,21773,2678, 21775,161, 21712, 21713,21715,21716,1059, 21717,21718,21722,21723,20188,304, 18077,1173, 21714,736, 418, 418, 2196, 21719,301, 21720,21721,21725,1882, 710, 161, 1727, 21727, 3937, 3875, 18389,21729,21730,21729,21732,161, 21733,161, 11482,21734,161, 21736,21738,21737,21739,21740,21741,21742,161, 355, 21745,21746,19173,21747,21748, 1787, 161, 18305,161, 161, 301, 161, 2538, 4511, 161, 301, 2537, 161, 418, 21750,21751,21752,21754,21756,21758,21759,21761,161, 161, 161, 21755,161, 21757,4075, 161, 161, 421, 161, 161, 5005, 1199, 21760,161, 161, 2523, 1012, 1842, 1843, 21764,21766,21765,21767,409, 3670, 679, 1107, 350, 679, 161, 1757, 2331, 21770,21771,1459, 418, 1846, 161, 4073, 21774,418, 414, 161, 21776,1590, 2963, 418, 21778,519, 161, 4615, 21779,1028, 1037, 414, 2376, 161, 161, 21780,418, 304, 21781,2580, 409, 9824, 21782,6124, 21786,21791,21802,21805,21809,21816,21821,21840,21842,21843,1343, 21844,21850,4500, 21852,21853,21855, 21864,21870,4164, 21883,21886,21888,21783,1343, 519, 301, 1365, 186, 1245, 21785,21784,1042, 161, 1421, 161, 715, 18718,161, 21787,21788,21789,2306, 1190, 161, 3084, 1093, 21790,1250, 213, 21792,21794,21798,21799,21800,21801,1050, 426, 21793,1093, 3512, 21795,21796,4164, 161, 440, 21797,2163, 418, 161, 409, 161, 519, 6358, 2800, 301, 3730, 1640, 2225, 1093, 21803,161, 21804,21806,21807,21808,21810,21812,1604, 21813,1284, 21814,14404,21815,161, 21811,161, 818, 7988, 161, 2573, 1126, 414, 186, 304, 161, 21817,21820,716, 21818,4078, 21819,21822,21823,1818, 21826,21830,21831,418, 21832,21837,21839,1787, 21824,21825, 1425, 468, 21827,1896, 21828,21829,1334, 186, 1042, 161, 161, 2969, 161, 21833,21834,21835,21836,1425, 1586, 296, 21838,21841,161, 4638, 2130, 2538, 213, 427, 161, 21845,21846,2471, 21848,161, 1028, 161, 1060, 161, 1223, 21847,2420, 161, 161, 21849,418, 161, 161, 519, 222, 21851,161, 161, 1824, 213, 21854,1757, 12583,161, 556, 11464,21856,3098, 21858,4883, 21859,21861,21862,355, 3250, 21857,1823, 1092, 161, 21860,21863,161, 418, 21865,21866,21867,304, 21868,301, 21869,1223, 161, 20714,19696,161, 13491,1223, 222, 161, 7696, 21871,21873,304, 21879,186, 21872,161, 161, 161, 1228, 21874,21875,21876,21877, 21878,21880,21881,21882,21884,21885,1042, 1109, 414, 161, 21887,21889,21891,9409, 2748, 12799,21890,6643, 2238, 161, 21892,161, 414, 1365, 21894,2040, 161, 2782, 21895,21901,21905,21924,21928,21932,21952,21954,21975,21977,21989,21997,22001,22003,1150, 22008,22012,22018,22035,22055,22057,22060,22063,22072,1150, 1150, 21896,21897,1060, 301, 161, 21899,21900,3476, 3551, 2237, 3015, 1590, 21898,519, 3778, 1028, 7199, 161, 519, 519, 11813,21902,3194, 1198, 213, 21903,21904, 161, 21906,21909,21912,21916,21922,1458, 21923,1060, 2449, 21907,161, 21908,435, 8410, 355, 21910,304, 161, 1126, 13905,21911,161, 21913,490, 21914,21915, 1343, 161, 161, 15983,161, 3046, 1036, 4984, 1367, 1418, 2748, 21917,1060, 161, 21919,21921,161, 21918,421, 2449, 21920,9732, 15480,414, 161, 222, 161, 706, 316, 21925,21926,21927,161, 21929,161, 1778, 21930,21931,161, 161, 21933,21938,21940,21942,21943,21945,21946,161, 21934,304, 316, 21935,21937,161, 21936,414, 304, 21939,417, 161, 350, 161, 1459, 161, 21941,161, 12050,161, 414, 21944,1107, 2197, 21947,21951,2868, 21948,161, 21949,21950,21953,301, 21955,21957,21959,21960,3113, 21962,1813, 21964,21965,21968,21969,21970,21971,414, 21972,414, 161, 1449, 161, 161, 21956,414, 161, 1223, 161, 1500, 161, 21958,161, 1223, 161, 161, 304, 929, 414, 161, 21961,161, 11482,21963,15746,304, 355, 161, 1372, 21966,301, 21967,161, 161, 263, 2678, 414, 7209, 1813, 304, 161, 7989, 15293,252, 161, 414, 161, 242, 1060, 2094, 414, 161, 1087, 1223, 7395, 435, 1750, 3250, 630, 161, 301, 21973,21974,21976,414, 161, 12696,21978,6453, 1604, 21980,21981,1343, 21983,21985,2921, 21988,161, 161, 161, 3250, 2203, 161, 21979,161, 414, 1421, 186, 1042, 2763, 161, 301, 21539,17513,301, 21982,1635, 679, 161, 21984,20583,19032,608, 3559, 21986,1846, 21987,213, 1640, 628, 21990,21992,21993,21994,21996,3620, 21991,161, 350, 1028, 18547,1060, 1699, 161, 21995,1981, 414, 161, 161, 1036, 21998,417, 21999,11781,7332, 161, 22000,161, 161, 22002,1036, 1245, 414, 22004,22005,1534, 22006,22007,4478, 1097, 161, 161, 22009,4720, 22010,22011,161, 2868, 1093, 11482,161, 161, 2995, 22013,22014,22015,22016,161, 22017,1297, 2840, 1302, 4419, 2191, 1026, 1036, 2038, 706, 161, 22019,22020,1109, 22021,22024,22030,3479, 22031,22032,22033,161, 22022,22023,414, 22025,1362, 1640, 22027,421, 22026,22028, 22029,161, 519, 4207, 22034,22036,22037,22041,22043,2182, 22052,161, 2678, 22054,161, 22038,22040,161, 161, 22039,6717, 161, 161, 414, 484, 1177, 22042, 22044,161, 22045,22046,161, 161, 1280, 1093, 414, 161, 161, 22047,22050,22051,161, 1177, 1150, 22048,22049,355, 161, 1793, 161, 161, 161, 414, 161, 1990, 161, 161, 1036, 22053,679, 3626, 1640, 22056,2755, 161, 22058,3620, 22059,1042, 21381,936, 22061,22062,414, 22064,22065,22067,22068,22069,8509, 161, 2167, 161, 19013,22066,1150, 409, 13922,1044, 185, 304, 418, 161, 22070,1372, 22071,4607, 3634, 11865,1916, 1868, 22074,1730, 22075,1109, 22082,22084,22086, 22088,5386, 22090,22094,4111, 22099,22112,22127,161, 1250, 18246,22076,2127, 3006, 1813, 22078,3967, 22079,1754, 22077,161, 14796,161, 519, 818, 1093, 22080, 22081,161, 1793, 22083,22085,5097, 1362, 1538, 222, 1823, 414, 1868, 22087,22089,1042, 161, 22091,22093,22092,414, 161, 1818, 161, 22095,22096,22097,22098, 22100,22106,22110,1371, 1028, 22101,6217, 3778, 22105,161, 22102,3469, 22103,22104,426, 22107,161, 161, 350, 22109,161, 2241, 22108,409, 161, 1924, 22111, 1332, 22113,418, 418, 22119,22125,2842, 2280, 161, 22114,414, 343, 22115,1161, 186, 3198, 1042, 22117,22118,1026, 161, 22116,4432, 1121, 418, 4438, 9974, 22120,22121,5536, 10140,22124,22122,22123,22126,2576, 301, 1778, 161, 22129,22132,22139,22146,22159,22161,15613,440, 22162,22163,22167,22170,22179,413, 22182, 445, 2755, 22130,22131,161, 301, 301, 22133,22134,22136,1042, 22137,22135,22138,414, 161, 4203, 1054, 22140,161, 414, 1093, 161, 22141,22145,22142,22143, 22144,161, 2469, 1284, 435, 463, 1850, 22147,22155,304, 22158,414, 414, 22148,21470,22153,22154,3409, 161, 22149,22150,22151,22152,1276, 3095, 161, 22156, 22157,1497, 1177, 1177, 161, 161, 414, 4898, 1497, 22160,4359, 13510,161, 161, 350, 18725,4576, 22164,418, 4768, 22165,252, 22166,519, 418, 22168,301, 161, 19310,22169,161, 19599,1173, 16071,414, 1093, 22171,22173,22176,304, 22178,1429, 19236,161, 161, 161, 161, 22172,1026, 414, 414, 161, 186, 161, 161, 222, 22174,22175,414, 161, 1025, 161, 414, 4743, 161, 161, 304, 7013, 301, 22177,1793, 1026, 161, 1242, 417, 492, 161, 418, 161, 161, 301, 301, 22180,22181,304, 1208, 8909, 2449, 2537, 996, 11538,304, 409, 414, 22184,22185,296, 22186,22187,161, 161, 22188,22190,426, 22191,22194,22196,304, 1474, 2272, 161, 161, 161, 304, 426, 2261, 14600,12750,1208, 1851, 22189,9069, 22192,22193,161, 161, 9061, 1868, 1036, 301, 296, 22195,161, 10095,22198, 414, 573, 1459, 3559, 213, 5065, 11855,3098, 1604, 22201,4425, 418, 417, 417, 22205,22208,1060, 22202,304, 414, 161, 161, 22203,161, 2921, 22204,301, 304, 1343, 2430, 22206,22207,427, 22210,22211,3315, 22213,22214,22215,22218,18500,6046, 22220,22222,3178, 22226,22227,22229,4164, 22232,414, 17347,435, 161, 22212,418, 7509, 1608, 161, 4885, 161, 1608, 161, 2963, 301, 161, 1093, 161, 706, 316, 3088, 1895, 22216,22217,6654, 4155, 1363, 22219,1143, 22221,296, 1097, 22223,22225,161, 22224,414, 9910, 301, 161, 161, 22228,556, 22230,22231,22234,22238,16097,22241,418, 22242,15164,22245,7848, 22235,22236,22237,22239, 540, 22240,3719, 161, 161, 1990, 22243,421, 22244,2538, 22246,22248,304, 22249,22250,22247,2238, 3548, 1245, 301, 6046, 1044, 22251,22253,22274,22284,22379, 22401,22423,22435,22482,22487,22496,22499,22531,22544,22566,21560,22586,22595,22606,22612,22621,21560,22626,22628,22636,2362, 22254,22256,22258,1365, 22261,22268, 22271,22272,3435, 8136, 22255,22257,2362, 21529,22259,429, 22260,417, 7623, 22109,213, 17094,418, 429, 22262,22265,2102, 22267,2499, 15207,22263,22264,22266, 1150, 3113, 22269,22270,4883, 161, 22273,213, 213, 9630, 22275,22276,22278,22281,418, 304, 409, 161, 22277,161, 409, 5631, 13844,22279,1847, 22280,22282, 22283,11556,304, 22285,22312,22318,22337,22353,22364,161, 304, 4401, 22286,304, 14216,22290,22293,22295,22299,22305,22307,22309,5239, 161, 22287,22288,440, 22289,1010, 429, 22291,161, 3196, 22292,418, 1459, 161, 161, 418, 430, 3850, 213, 1145, 417, 22294,22296,161, 1756, 22298,304, 1093, 22297,1971, 1145, 5918, 213, 2541, 440, 440, 22300,417, 22301,22304,161, 10004,420, 6654, 418, 296, 22302,440, 161, 161, 213, 22303,296, 4003, 22306,9001, 295, 22308, 22310,22311,3478, 22313,22314,22315,22316,22317,22319,22324,22329,22332,22333,22335,2434, 1595, 2225, 22320,22321,439, 22322,22323,161, 22325,22326,22327,22328, 6723, 439, 6744, 417, 7404, 418, 417, 417, 22330,409, 161, 22331,5157, 5882, 161, 213, 8534, 1538, 161, 10447,22334,8534, 161, 161, 426, 22336,408, 418, 22338,22342,22343,15445,22348,22350,430, 1899, 22351,22352,161, 1343, 22339,161, 1459, 213, 22340,22341,1459, 1093, 430, 414, 5253, 414, 1156, 1111, 1245, 440, 22344,4164, 22345,7961, 22346,22347,161, 1150, 22349,213, 161, 440, 161, 420, 5157, 161, 4168, 22354,22356,22358,22360,22362,22363,22355,213, 161, 1109, 1884, 213, 161, 161, 22357,22357,975, 976, 22359,1343, 213, 22361,161, 440, 1922, 4401, 161, 6264, 22365,1028, 22366,22369,10176,304, 22374, 2442, 22376,161, 22367,22368,22370,22371,22372,22373,1459, 161, 409, 161, 1011, 213, 161, 440, 213, 22375,426, 414, 161, 22377,22378,418, 11639,22380, 22384,22387,22389,22397,304, 22381,1365, 22383,435, 22382,1639, 435, 3169, 22385,8493, 22386,22388,435, 8493, 304, 22390,1829, 22392,22396,22391,7961, 1639, 1883, 409, 5789, 22393,22394,304, 22395,22398,22400,22399,1563, 304, 161, 22402,22405,22421,1109, 3435, 1905, 22403,22404,1363, 418, 1343, 22406,22408,22409, 22415,22417,22419,22420,3534, 3194, 22407,22410,22412,22413,1126, 22411,213, 417, 161, 22414,22416,22418,1730, 213, 426, 1109, 14937,1608, 1608, 22422,22424, 22430,1895, 22434,22425,3169, 22426,22429,435, 1922, 418, 1109, 22427,1109, 22428,1876, 418, 9570, 22431,22432,3850, 22433,22436,22442,22445,22446,22457,22463, 22471,22477,22437,418, 22438,409, 22440,3315, 161, 1028, 1563, 213, 463, 22439,440, 161, 409, 463, 1060, 22441,1608, 22443,2362, 8676, 22444,1343, 22447, 22448,22454,19095,22456,22449,417, 22453,22450,22451,161, 2440, 22452,1608, 635, 440, 22455,7188, 1971, 414, 1363, 440, 22458,22459,22460,2393, 304, 3210, 1542, 10129,21546,304, 22461,418, 22462,418, 418, 1922, 679, 22464,22465,213, 22468,22308,22470,6009, 22466,22467,22469,22472,22474,678, 22475,22476,213, 435, 22473,1101, 409, 161, 161, 6744, 161, 296, 435, 763, 445, 161, 462, 213, 295, 679, 6723, 22478,22480,15445,295, 22479,213, 6127, 1343, 22481, 6350, 1757, 14044,22483,13776,22485,22486,22484,304, 440, 10854,9447, 161, 22488,22493,22495,21531,13841,22489,22490,22491,1101, 213, 426, 22492,21529,11436, 22494,304, 1823, 1145, 6386, 1858, 1823, 22497,22498,22500,22501,22518,22529,977, 22530,1126, 1990, 418, 304, 1884, 304, 161, 22502,22504,22506,22507,22509, 22512,22516,22517,1542, 6744, 161, 161, 1208, 22503,22505,1343, 161, 161, 295, 6744, 22508,22510,22511,296, 296, 4979, 22513,22515,3959, 1107, 22514,304, 420, 161, 1060, 213, 418, 161, 22519,2221, 22520,22521,161, 22523,22526,22528,295, 22522,213, 9034, 22524,22525,22511,213, 8983, 1733, 161, 213, 1608, 22527,3959, 13783,8076, 161, 22532,22538,418, 22541,22543,21545,22533,1595, 1010, 3670, 22534,22535,7768, 15957,161, 418, 440, 213, 21547,22536,7623, 22537, 426, 22539,414, 1145, 22327,1962, 22540,1389, 3435, 430, 417, 3766, 22542,161, 21547,22545,22548,22551,22556,22560,6723, 9149, 1184, 1922, 22546,15065,5284, 409, 22547,414, 161, 414, 213, 635, 1184, 418, 22549,22550,22552,1150, 22553,418, 433, 1150, 22554,418, 4203, 22555,418, 22557,22558,6009, 6009, 22559, 418, 414, 474, 22561,418, 22563,22562,1107, 1112, 22564,22565,22567,22572,22579,22584,3875, 9704, 1242, 22568,213, 350, 22569,161, 1173, 304, 22570,22571, 22573,22574,679, 22575,22577,1824, 14937,350, 161, 8797, 161, 304, 304, 8797, 161, 1036, 22576,414, 2225, 414, 22578,22580,7961, 679, 408, 409, 350, 22581,1150, 22582,22583,22585,463, 13922,350, 22587,22589,22590,4401, 22588,3479, 3478, 421, 421, 2082, 22591,22592,22593,435, 430, 435, 1541, 22431,1545, 161, 22594,22596,22597,22600,22602,4109, 304, 22598,22599,161, 1608, 22601,1608, 22603,22604,418, 6483, 1981, 463, 22605,22607,22608,975, 22611,161, 440, 213, 161, 12205,22609,161, 22610,10717,161, 22613,22615,22616,22619,1595, 22614,22617,22618,4401, 1842, 426, 22620,22622,4883, 22624,22623,22625,213, 22627, 13849,22629,19341,22631,22633,977, 418, 22630,1126, 686, 2082, 13734,22632,13734,3085, 304, 22634,22635,3620, 22638,22663,22664,22672,22701,22706,22708,22718, 22722,22728,22730,22731,22769,22780,22814,22817,22820,22862,22889,22937,22939,22940,22943,22946,417, 22639,22641,22643,435, 295, 22644,22645,22655,22656,22660, 316, 22640,1459, 1459, 1459, 4307, 22642,161, 418, 296, 6393, 4169, 1042, 161, 161, 22646,22647,1756, 161, 22653,22654,161, 161, 22648,22649,3198, 22650, 161, 22651,22652,161, 17649,22657,22658,22659,1042, 161, 1321, 161, 22661,13818,22662,161, 161, 301, 22665,22670,1459, 22671,22666,22669,1270, 161, 22667, 22668,1486, 8136, 414, 4168, 22673,22675,22676,22679,3267, 22684,22697,22698,1797, 22699,22700,311, 22674,161, 686, 22677,22678,1042, 161, 22680,22682,22683, 2797, 22681,1037, 1093, 22685,22686,22690,301, 22691,22692,1459, 2797, 414, 20207,161, 22687,22688,161, 186, 1189, 304, 12904,22689,435, 1054, 17739,22693, 161, 22694,22695,22696,8495, 418, 420, 22702,1036, 1343, 6681, 22703,295, 414, 213, 414, 22704,22705,161, 161, 13998,22707,1899, 1143, 414, 22709,1730, 22716,1371, 22717,1470, 7967, 22710,1343, 5381, 22714,301, 22715,22711,22712,22713,1757, 2111, 418, 16989,161, 22719,22720,22721,20663,21655,161, 296, 13682, 1343, 1184, 6066, 1042, 5751, 10655,1426, 22723,1150, 22724,161, 22726,11660,696, 2082, 22725,10299,465, 304, 414, 295, 3730, 304, 1413, 22727,465, 295, 696, 295, 161, 14783,161, 1760, 22729,414, 22732,22736,22737,22740,22741,222, 22744,8181, 22746,22752,22759,22760,1843, 8509, 417, 22764,22765,22767,8265, 1126, 22733,301, 22734,161, 22735,417, 161, 15046,418, 161, 7509, 161, 22738,296, 22739,296, 161, 296, 1042, 1458, 1470, 22742,161, 22743,1949, 4570, 161, 22745,2252, 161, 22747,301, 1778, 22748,22749,1813, 414, 161, 22750,22751,301, 414, 161, 2196, 22753,22754,22756,22758,519, 186, 161, 22755,161, 350, 161, 421, 161, 3917, 22757,161, 161, 350, 1555, 5097, 421, 2782, 22761,22763,3002, 161, 304, 1036, 22762,161, 161, 304, 1026, 161, 4108, 186, 1042, 418, 161, 295, 426, 22766,161, 22768,1150, 2058, 22770,22773,22774,22775,2818, 22771,22772,1908, 1367, 304, 14962,161, 296, 2191, 686, 22776,161, 22777,316, 22779,1060, 22778,4207, 304, 1124, 414, 1405, 9824, 22781,22784,22786,22793,22795,22797,22799,1036, 22800,22803,22807,22809,22812,22813,1121, 22782, 295, 1657, 186, 22783,296, 22785,22787,22791,22792,161, 1060, 161, 22788,14728,161, 161, 161, 161, 22789,22790,161, 4041, 3006, 1044, 556, 1223, 2750, 12297,2106, 10373,301, 301, 296, 1242, 2362, 22794,13523,750, 936, 213, 22796,2516, 304, 435, 2362, 22798,161, 22801,22802,1223, 1822, 161, 161, 22804, 7957, 2094, 18725,22805,22806,996, 409, 296, 1042, 22808,6247, 4364, 7076, 22810,22811,19994,161, 3770, 414, 519, 213, 161, 435, 1198, 301, 22815,22816, 22818,22819,22819,22821,22822,304, 22835,1895, 22836,22838,22844,22845,22848,301, 22850,22851,22856,22859,22861,426, 3250, 161, 6768, 161, 222, 161, 22823, 22825,22827,22832,161, 22833,161, 22824,1341, 1048, 161, 22826,2632, 161, 22828,418, 22829,22830,1093, 22831,296, 3198, 161, 1877, 22834,1470, 161, 1342, 1050, 3153, 213, 1862, 161, 22837,1042, 19166,1093, 1150, 22839,6153, 22841,2474, 2203, 22842,161, 1101, 22840,22843,435, 22190,161, 418, 2748, 22846,2203, 418, 22847,22849,1740, 161, 301, 440, 161, 1640, 22852,474, 22855,1459, 421, 1818, 3098, 1343, 6283, 22853,1343, 556, 2191, 22854,22842,1343, 22232,409, 22857,22858,1343, 418, 435, 4055, 22860,1107, 6127, 418, 161, 161, 22863,17714,22865,9117, 22868,22869,295, 22870,22873,22875,22877,22878,22886,22888,22864, 1851, 2270, 679, 22866,22867,296, 1640, 22871,22872,22874,22876,22879,22880,22884,295, 14415,295, 161, 161, 22881,22882,161, 4111, 22883,409, 421, 20763, 22885,161, 519, 1851, 22887,296, 304, 5803, 22890,22905,22907,304, 22911,22918,295, 22921,22922,161, 22934,22935,22891,22894,22896,22900,295, 22892,22893, 161, 22895,161, 7744, 161, 22897,22898,22899,22901,22902,22904,22903,17585,161, 22906,22908,22909,1054, 22910,22912,22913,491, 22914,22916,1922, 2111, 304, 22915,161, 161, 2479, 9399, 161, 22917,22919,2142, 22920,414, 22923,22929,22924,22925,22926,22927,22928,22930,22931,22932,22933,1899, 4883, 161, 22936,5410, 465, 295, 295, 10191,161, 2576, 2142, 22938,295, 296, 1217, 8181, 414, 161, 22941,22942,22944,22945,6066, 1042, 1270, 1026, 161, 213, 161, 22947,22948, 22950,22952,22984,23005,23011,23013,23030,23035,23037,23049,23107,23110,23155,9820, 23156,23173,23287,1173, 23302,23303,23309,414, 161, 22951,1342, 161, 22953, 519, 22954,22955,22971,2605, 22974,1223, 161, 6046, 22956,22962,22969,161, 22970,14749,22957,22959,186, 186, 22961,22958,414, 161, 161, 161, 161, 1093, 1177, 421, 13441,22960,421, 2538, 5645, 22963,22964,295, 22965,22966,22967,22968,2617, 2127, 1177, 161, 1343, 519, 2755, 11406,414, 22972,1036, 11694,161, 22973,414, 1050, 22975,1198, 16198,22976,22977,22978,22979,22983,22980,22982,22981,414, 22985,2951, 22992,16431,22994,22995,2272, 16431,22997,22999,1173, 23000, 8095, 22986,22987,750, 22988,22989,22990,22991,417, 16441,22993,440, 185, 296, 1729, 22996,976, 304, 22998,10627,13717,4109, 213, 23001,23004,23002,23003, 304, 750, 23006,6351, 1387, 23007,23009,23010,936, 1093, 304, 23008,408, 161, 161, 2729, 409, 301, 295, 11442,23012,4448, 161, 23014,18919,23016,23018, 23023,23024,23026,23015,23017,427, 161, 2590, 17677,161, 23019,23020,23021,23022,161, 21360,5468, 1595, 23025,1908, 17740,23027,23028,161, 161, 414, 23029, 23031,23033,161, 4898, 23032,23034,1415, 408, 23036,161, 7999, 750, 23038,23039,5069, 161, 23042,22681,23044,874, 23048,414, 512, 192, 180, 23040,7053, 23041,1050, 23043,161, 7445, 23045,161, 23046,23047,20105,161, 23050,23054,23058,23061,23063,23064,23066,23069,23071,1904, 23093,23096,3435, 23100,23103,1459, 23105,213, 8064, 23051,23052,1341, 1191, 161, 23053,161, 161, 23055,23056,3195, 161, 23057,2191, 350, 1019, 161, 23059,213, 311, 23060,161, 11817,3302, 1938, 1097, 23062,2539, 161, 161, 222, 2111, 6283, 4332, 23065,418, 3478, 418, 10446,418, 222, 4935, 2474, 23067,23068,1868, 301, 161, 12921,213, 186, 1042, 1343, 23070,161, 23072,23074,1260, 23080,23082,1343, 295, 3196, 23091,4108, 23092,161, 304, 23073,1208, 161, 1938, 23075,23077,1026, 4075, 295, 296, 1458, 23076,414, 161, 23078,161, 23079,23081,3478, 1059, 1143, 23083,4683, 23084,22562,23085,23086,23087,417, 23089,418, 10851,418, 5284, 296, 1480, 23088, 4225, 161, 409, 23090,418, 4532, 2283, 425, 5060, 304, 628, 414, 23094,23095,161, 23097,23098,23099,933, 23101,23102,23104,1482, 296, 19744,23106,414, 1093, 23108,414, 23109,161, 414, 14317,161, 23111,23114,23118,23124,23127,161, 23128,23136,23137,23138,23144,23147,23149,23150,23154,161, 1161, 1343, 23112, 23113,301, 1850, 421, 23115,23116,23117,161, 161, 11568,161, 414, 161, 23119,3198, 2245, 23120,4179, 23121,161, 161, 161, 435, 161, 3529, 23122,23123, 414, 23125,161, 3098, 3013, 6808, 2050, 161, 23126,6819, 161, 3169, 408, 161, 161, 301, 1134, 2111, 1121, 161, 161, 414, 7009, 1824, 23129,296, 3098, 23131,4058, 1092, 23133,1343, 161, 23130,161, 8955, 161, 23132,1754, 8136, 23134,161, 9135, 23135,2340, 213, 161, 23139,23142,474, 161, 23140,161, 23141, 5483, 23143,3098, 161, 161, 418, 1990, 23145,23146,2523, 2378, 161, 23148,161, 418, 14516,1640, 474, 161, 4169, 1028, 161, 1363, 161, 161, 23151,23152, 23134,414, 23153,4511, 3730, 23157,23164,23168,23170,301, 1028, 23171,4511, 23172,301, 23158,23160,23161,4041, 23162,23163,4712, 435, 2238, 23159,1198, 22920, 316, 7483, 23165,23166,23167,4003, 23169,161, 301, 2024, 1868, 23174,23175,23203,23210,23216,23222,23224,23227,23238,23240,23241,23243,23245,23246,23250,23252, 23259,23269,23277,23176,23183,23187,23190,23202,23177,23180,23178,23179,23181,23182,161, 2840, 186, 23184,23185,23186,435, 23188,304, 295, 23189,23191,23196, 23192,23193,23194,23195,23197,6592, 23199,23198,23200,23201,23204,23208,1504, 23205,23206,23207,23209,23211,23212,296, 4693, 23213,2568, 1060, 23214,23215,20350, 23217,23219,23218,23220,23221,23223,21217,23225,1161, 1498, 295, 304, 301, 23226,161, 161, 421, 9733, 23228,23229,23232,23230,23231,23233,23234,23235,23236, 23237,23239,6966, 418, 161, 23242,8697, 161, 23244,296, 4695, 23247,23248,23249,23251,3923, 161, 23253,8697, 435, 23254,23255,23256,23257,23258,1302, 23260, 6736, 23267,11512,414, 252, 23261,1418, 23262,23264,2050, 1093, 23263,414, 1626, 161, 23265,23266,23268,3730, 3958, 350, 23270,296, 1036, 23273,2162, 23275, 409, 252, 161, 23271,23272,1042, 23274,435, 23276,1087, 350, 468, 161, 1115, 1431, 1044, 23278,304, 23279,23280,23281,23282,23283,23284,628, 23285,23286, 23288,23289,1093, 23294,23295,3959, 1364, 1978, 23296,23298,23300,23290,414, 1459, 23291,696, 23293,23292,161, 1851, 4615, 9292, 14924,23297,418, 1092, 23299, 295, 418, 161, 435, 1036, 295, 23301,3754, 1042, 418, 417, 7010, 933, 23304,23308,2218, 304, 12567,23305,16071,23306,23307,519, 1449, 23310,23311,1367, 23312,23313,355, 23315,2811, 5394, 23316,22949,635, 23318,409, 23319,23320,23322,23323,23324,1757, 23325,23326,23335,23351,23369,23375,23377,23380,23386,23395, 23397,23398,23424,23432,23536,23554,2038, 23556,23628,23647,23674,23694,23697,23700,23702,23705,161, 1036, 301, 3770, 6006, 5300, 23327,23329,440, 23333,23334, 23328,23330,161, 23331,213, 23332,1212, 161, 1042, 23336,6838, 2272, 23338,23341,23344,23347,23337,161, 23339,23340,23342,17438,5461, 161, 23343,161, 23345, 23346,23348,23349,23350,23352,23354,301, 23361,8347, 23365,161, 23366,23353,23355,161, 301, 23356,23359,186, 1042, 1270, 2748, 23357,161, 23358,1189, 417, 296, 23360,161, 161, 213, 161, 161, 23362,23363,1036, 10339,23364,161, 8864, 186, 23367,23368,414, 6130, 295, 23370,2280, 23372,23373,23374,23371,409, 1042, 350, 23376,1895, 12309,6222, 6222, 23378,23379,1787, 435, 1951, 23381,161, 19592,23384,19961,3479, 5875, 23385,12246,3088, 296, 23382,161, 161, 421, 186, 186, 23383,23387,750, 1097, 23388,23394,23389,1093, 23390,23391,23392,161, 23393,295, 4883, 23396,2331, 161, 818, 304, 23399,23401,23405,1161, 23412, 23415,1824, 23420,417, 23421,23422,14285,3954, 23400,1093, 10279,23402,4308, 23404,4789, 161, 161, 161, 23403,161, 2800, 1343, 23406,3689, 161, 161, 23409, 706, 161, 23407,23408,222, 23410,1212, 23411,1042, 23413,445, 23414,161, 161, 2267, 161, 23416,23418,2225, 23419,9860, 161, 23417,161, 161, 4075, 10039, 11799,5483, 440, 4203, 1028, 23423,9824, 418, 23425,23428,23429,23430,23431,23426,23427,414, 9693, 2017, 161, 1459, 1060, 1198, 1050, 519, 5875, 23433,23436, 23440,23444,23451,23454,23459,23460,304, 23463,23466,23468,23477,23479,23488,23531,23535,519, 23434,1343, 21005,23435,11914,161, 20697,161, 1949, 2182, 23437, 2962, 519, 23438,11112,23439,23441,23442,1818, 415, 161, 1778, 161, 23443,818, 435, 23445,23446,23447,414, 512, 23448,23449,23450,161, 23452,23453,4203, 23455,23456,23457,434, 355, 1793, 418, 1093, 23458,3210, 1059, 3143, 161, 3095, 1080, 13907,1134, 3113, 2183, 23461,23462,161, 414, 23464,2165, 161, 23465, 414, 343, 519, 161, 2191, 23467,1818, 3288, 1150, 23469,1343, 23473,23474,5069, 14880,2195, 5216, 23470,23471,23472,23475,23476,23132,161, 1087, 23478,23480, 23481,23482,23483,23484,427, 1228, 161, 8551, 2378, 15686,4335, 23485,23486,23487,1317, 414, 23489,23496,23498,3267, 23516,23520,23521,440, 23523,413, 23527, 161, 23530,4164, 252, 161, 23490,23491,23494,1990, 161, 417, 161, 1498, 304, 23492,519, 519, 23493,23495,417, 2293, 161, 304, 23497,23499,1824, 161, 23502,23503,23492,23504,23505,23507,23509,23512,23515,23500,23501,2111, 23492,11366,23506,2000, 1797, 445, 23508,161, 213, 519, 161, 23510,23511,1367, 23513, 6927, 23514,23517,23518,23519,23522,519, 1223, 161, 23524,23526,519, 296, 301, 23525,23528,23529,435, 2750, 1824, 2272, 23532,23533,23534,23537,23539,686, 161, 13986,301, 23540,23541,414, 23549,23552,1093, 23538,474, 304, 519, 161, 161, 474, 519, 414, 23542,5065, 23543,1042, 23545,1343, 23548,23544,23546, 23547,161, 14643,23550,23551,1042, 1343, 161, 161, 3098, 23551,3479, 161, 161, 740, 2166, 23553,1343, 161, 6035, 161, 296, 7320, 1042, 23555,161, 23557, 1080, 421, 23564,23567,23575,23576,23588,161, 23593,23594,23595,23600,23603,23607,23609,23615,23616,23626,161, 5512, 304, 1342, 1813, 3620, 23558,23560,2331, 23561,23562,2539, 4173, 23559,213, 161, 2469, 213, 1850, 161, 161, 161, 7669, 9364, 23563,23565,23566,296, 311, 1951, 161, 418, 2113, 3315, 161, 3098, 23551,23568,417, 23569,1343, 23572,687, 23573,23574,161, 185, 414, 23570,23571,161, 213, 161, 426, 418, 1250, 161, 23577,23579,23586,23587,519, 23578, 414, 161, 161, 3098, 222, 23580,161, 23581,1100, 23585,19167,23582,23583,23584,23589,161, 14391,1778, 12567,5544, 23591,23592,161, 23590,418, 2605, 2130, 1376, 418, 6166, 1372, 18122,1092, 3181, 23596,23597,23598,414, 23599,23601,23602,1036, 161, 1376, 302, 161, 301, 23604,706, 23605,23606,7220, 161, 23608, 161, 23610,23611,23614,1150, 435, 519, 1087, 161, 435, 556, 23612,6077, 161, 1482, 5242, 23613,10016,418, 23617,23619,23622,23624,3479, 3233, 418, 2826, 161, 23618,1093, 414, 463, 213, 161, 23620,161, 1582, 161, 23621,23623,5666, 23625,14415,161, 311, 1270, 23627,1363, 23629,1474, 23630,23633,23635,295, 23636,440, 1343, 23638,23643,23645,302, 4879, 21152,2799, 23631,222, 519, 23632,213, 23634,1343, 161, 161, 161, 435, 23637,161, 1180, 23639,23640,23641, 23642,161, 23644,1343, 23646,213, 519, 23648,23649,23650,23662,5875, 23666,10655,23670,2755, 23673,23651,23653,23654,161, 23652,23655,414, 161, 23656,1609, 1042, 17109,23660,23657,23658,23659,23661,186, 933, 161, 3962, 1092, 23663,23664,1093, 23665,1425, 8724, 296, 3097, 301, 161, 23667,418, 7053, 23668,23669, 1223, 161, 2196, 4875, 1323, 311, 161, 23671,465, 440, 23672,1604, 161, 3447, 23675,23679,23685,23687,23691,161, 417, 23676,418, 23677,4203, 418, 161, 23678,418, 9458, 304, 1093, 295, 440, 23680,23681,23684,186, 3267, 6190, 3435, 161, 418, 23682,23683,295, 8218, 2976, 161, 19095,23686,5238, 3198, 1042, 161, 23688,304, 23689,23690,161, 161, 161, 161, 161, 1809, 4164, 161, 161, 4515, 23692,418, 635, 414, 186, 3198, 1042, 23693,161, 12412,161, 23695, 23696,414, 161, 1303, 1093, 161, 1054, 1418, 414, 2191, 2440, 213, 23698,1042, 750, 161, 23699,161, 435, 161, 1635, 4169, 161, 23701,440, 161, 161, 23703,4089, 23704,6744, 6744, 23706,23707,11366,23709,161, 23708,301, 161, 23711,11960,23712,484, 3689, 414, 23714,301, 421, 23006,23715,23717,23719,23728, 23737,23743,23747,23749,23753,23757,23758,23762,23795,23800,7210, 23817,23818,23842,23870,23881,23883,222, 23720,23723,936, 414, 23721,296, 295, 1962, 3665, 23722,161, 23724,1042, 23726,22137,23725,161, 23727,23729,23730,301, 23732,23736,23731,23733,23734,23735,3730, 1938, 23738,23740,18018,23742,14242,23739,23741, 2474, 23744,1515, 350, 11439,23745,23746,161, 23748,3103, 161, 3439, 4308, 161, 23750,23752,161, 23751,23754,23756,304, 23755,1792, 7914, 2331, 23759,23760, 23761,418, 3559, 23763,23764,23767,3267, 23768,23771,23772,23773,21557,23780,23782,23791,23793,1426, 23765,23766,1482, 14937,1640, 23769,23770,1363, 14913,213, 440, 1184, 23774,23775,23777,213, 1242, 22190,439, 161, 23776,161, 3002, 316, 1351, 1962, 316, 2053, 161, 1343, 1343, 426, 23778,161, 316, 1093, 23779, 161, 23781,23783,23784,23785,23786,23789,7835, 9150, 23787,23788,8117, 1297, 23790,2196, 14736,23792,15172,3689, 15172,161, 23794,414, 3665, 23796,3267, 23797, 23799,2225, 161, 4473, 23798,1060, 414, 11294,296, 294, 161, 23801,23802,23804,484, 3267, 23810,23811,161, 440, 23814,4173, 6072, 23816,22196,417, 161, 519, 1850, 2225, 23803,304, 11112,161, 3321, 5409, 23805,10004,23806,161, 23807,161, 15887,161, 23808,23809,296, 7660, 161, 1787, 23812,296, 7047, 2182, 161, 161, 23813,23815,414, 161, 161, 1242, 15915,409, 15421,1482, 9517, 23819,23823,23826,23832,23833,23834,213, 440, 2378, 1653, 161, 23835,23837,23841, 409, 161, 19309,23820,12567,23821,2755, 23822,2539, 161, 20508,1823, 23824,301, 23825,161, 161, 1542, 417, 1883, 23827,420, 23830,161, 23828,414, 23829, 1042, 936, 161, 161, 1054, 414, 23831,435, 1778, 161, 23836,213, 4306, 1050, 23838,15421,23839,474, 161, 23840,435, 161, 409, 7339, 4073, 418, 23843, 23844,23849,23850,23852,23854,23856,23859,17677,23860,23864,23868,6505, 4602, 706, 1449, 23845,1048, 222, 3469, 23848,3855, 23846,23847,161, 4198, 23851,427, 427, 23853,161, 161, 8135, 1145, 435, 23855,161, 556, 414, 161, 9924, 161, 3095, 161, 1343, 23857,304, 1412, 23858,3922, 161, 23861,1343, 23863,23862, 161, 1343, 161, 2867, 2516, 161, 427, 23865,296, 418, 17677,161, 161, 2782, 2390, 418, 23866,186, 23867,23869,23871,23873,1208, 23874,23878,440, 161, 23879,23880,2210, 161, 1305, 23872,1425, 1091, 296, 161, 23875,295, 23876,23877,1764, 213, 5224, 418, 23882,421, 23884,213, 1868, 304, 161, 23886,23890, 161, 23887,23888,23889,23891,23893,9516, 23894,3959, 23896,3440, 23897,23899,23902,15300,23904,14225,23908,23914,23926,1042, 23928,19983,350, 23895,3409, 2028, 185, 23898,304, 1504, 23900,9892, 23901,3928, 23903,427, 23905,556, 350, 23906,23907,23909,2387, 23910,1036, 301, 418, 23912,23911,5494, 161, 161, 23913, 1387, 1916, 23915,23917,23920,23916,7846, 23918,23919,23921,23924,23922,23923,161, 414, 2651, 23925,23625,161, 1990, 23927,630, 12165,2982, 161, 23930,23931, 93, 24100,24103,24111,94, 24333,95, 24469,635, 24471,96, 24640,24644,24696,24699,468, 17677,23932,23933,23934,23935,23937,23942,23947,23950,23953,23956, 23958,23970,23975,23980,23996,23997,24008,24011,24031,24044,24068,24078,24092,24094,24095,2000, 15250,296, 23936,23938,161, 23941,1280, 23939,23940,3250, 23943, 23944,295, 23945,161, 355, 4583, 304, 161, 161, 7220, 23946,213, 608, 23948,301, 23949,23951,23952,1555, 4883, 18896,161, 161, 161, 2050, 519, 161, 301, 161, 2199, 161, 301, 161, 8711, 1343, 23954,409, 519, 23955,161, 3261, 23957,1173, 23959,23964,1150, 23965,1109, 23966,14391,9557, 161, 23960,23961, 161, 23962,23963,1934, 161, 1228, 1173, 18563,161, 14397,1126, 3326, 2028, 161, 2028, 301, 23967,23968,23969,161, 23971,23973,23974,23972,1126, 213, 304, 161, 161, 23976,1223, 23977,23979,161, 418, 2102, 706, 409, 414, 161, 13359,23978,11574,556, 15412,1060, 23981,1813, 23983,3046, 23984,23985,23986,16904, 23994,11819,1036, 23982,6077, 2854, 161, 3603, 23987,421, 23988,23989,23990,23991,23992,23993,23995,161, 23998,23999,295, 5409, 750, 24003,24007,4105, 24000, 24001,24002,24004,24005,24006,161, 1538, 2245, 161, 161, 161, 24009,24010,418, 24012,24014,24021,24022,1787, 418, 24024,24029,24030,5350, 161, 1126, 1962, 1966, 24013,24015,24018,24016,161, 161, 24017,161, 19025,24019,1392, 24020,6807, 161, 1126, 2678, 24023,304, 24025,24027,1962, 24026,161, 24028,186, 1042, 213, 161, 4971, 24032,24037,24038,24039,24040,4218, 418, 24041,24042,15315,24033,24035,24034,161, 260, 24036,301, 4164, 23125,296, 679, 24043,11078,222, 252, 414, 24045,24049,24050,24051,24055,24060,24061,24062,2028, 24046,24047,161, 22735,301, 24048,252, 301, 414, 22637,1097, 24052,1107, 24053,414, 161, 434, 24054,417, 435, 6811, 24056,2028, 24058,24057,414, 7696, 161, 2195, 1093, 24059,740, 213, 222, 2028, 24063,24064,24067,24065,24066,213, 24069,24070, 1413, 24072,24073,24076,1109, 24071,304, 296, 16315,24074,24075,936, 24077,3788, 936, 24079,5760, 24084,24088,24089,24091,519, 2197, 161, 24080,19871,23983, 161, 24081,161, 24082,161, 24083,740, 24085,161, 24086,24087,161, 12106,296, 6717, 296, 24090,414, 22636,24093,24096,3198, 1093, 817, 1823, 24097,24098, 24099,933, 24101,24102,24104,302, 24105,24109,24106,24107,24108,24110,24112,24113,295, 24114,24115,24128,24133,24149,24151,24161,24163,24179,24181,1829, 24198, 24201,24207,24214,24215,24217,24220,24226,24232,24245,24277,24286,24331,24332,161, 304, 161, 1150, 24116,24117,24122,24126,24127,2596, 1198, 2225, 414, 213, 418, 161, 24118,24119,24120,24121,213, 3799, 1042, 213, 24123,161, 24124,24125,18892,213, 1756, 161, 24129,24130,5207, 24132,1223, 295, 161, 24131,10454, 24134,24137,24139,24140,1824, 24135,24136,9693, 1127, 414, 1042, 24138,161, 1107, 1401, 1126, 10864,304, 24141,24142,24145,1365, 24143,24144,24146,24147,24148, 414, 161, 161, 3181, 24150,304, 24152,161, 161, 2165, 24159,1297, 24160,414, 186, 1604, 24153,24155,17677,301, 161, 161, 468, 24154,24156,1343, 24157, 161, 24158,24162,161, 24164,24168,24173,24177,414, 24165,161, 24166,24167,1206, 463, 24169,24170,11286,161, 161, 24171,24172,1421, 24174,24175,24176,1150, 8579, 706, 1028, 24178,296, 414, 24180,161, 304, 24182,24184,24185,6066, 24194,24195,1343, 161, 301, 24196,24197,161, 24183,556, 10655,2442, 3002, 14508, 295, 24186,24187,24188,24189,24192,414, 24190,1042, 213, 161, 24191,24193,414, 1609, 161, 414, 2225, 1583, 24199,24200,301, 24202,304, 24203,414, 24205, 24206,414, 161, 1818, 24204,418, 1459, 161, 1060, 1343, 161, 23430,24208,1181, 1060, 24211,4657, 24209,9069, 24210,24212,24213,161, 161, 1044, 519, 2390, 24216,435, 24218,24219,18246,24221,1635, 10108,161, 24222,1087, 5875, 161, 1109, 24223,24224,161, 3610, 161, 161, 161, 2474, 1093, 24225,301, 6999, 24227, 2471, 295, 3153, 24228,24231,6222, 161, 161, 24229,304, 295, 24230,161, 296, 1042, 8581, 296, 161, 161, 24233,3479, 161, 24237,24238,24241,1851, 24234, 24235,24236,417, 11232,161, 24239,1036, 24240,161, 24242,24243,24244,24246,24249,1895, 24254,24256,24258,295, 24262,24273,7777, 2750, 2174, 24247,3518, 24248, 24250,4193, 24251,24252,24253,24255,10039,350, 24257,24259,24260,2848, 24261,295, 296, 430, 435, 24263,296, 24264,24268,24269,1415, 24265,24266,24267,161, 414, 414, 24270,24271,24272,8251, 24274,24275,418, 24276,24278,24279,24284,301, 4570, 1093, 414, 24280,161, 414, 3725, 414, 3479, 24281,1026, 24282,2130, 1823, 24283,24285,1093, 161, 350, 414, 1023, 24287,24292,24297,24298,24302,24304,24194,24305,24309,24312,1126, 24313,1843, 24316,24326,24328,24288,24290,24291, 1882, 3195, 161, 24289,1851, 161, 213, 186, 1092, 24293,24295,24294,24296,1036, 4307, 1895, 24299,24300,24301,24303,2166, 435, 161, 1151, 435, 4706, 185, 24306,418, 24307,24308,24310,1412, 24311,1459, 1757, 24314,1899, 24315,24317,24318,1604, 4726, 1343, 24319,24320,3771, 24324,1846, 1459, 24325,24321,3923, 24322, 24323,3954, 15316,24327,1824, 24329,24330,4482, 24315,3013, 417, 1343, 1028, 24334,2406, 24335,4322, 24336,24337,24338,24342,24368,24370,463, 24401,24418,24420, 24426,24429,24434,24443,24445,24447,24453,24465,301, 24466,24467,9557, 1418, 161, 24339,1899, 24341,161, 24340,2203, 1042, 420, 2191, 420, 24343,1458, 24346, 24349,24354,440, 24363,161, 24344,6331, 24345,1757, 212, 213, 161, 350, 24347,17094,161, 24348,24350,1037, 24352,1851, 24351,24353,414, 1093, 2677, 1177, 414, 24355,1036, 24356,24359,24361,1026, 1060, 161, 23040,414, 1542, 417, 161, 24357,24358,418, 474, 24360,24362,24364,24365,1787, 2050, 2434, 24366,24367, 1754, 2677, 21360,1026, 161, 1093, 1097, 3291, 414, 161, 1223, 20715,2225, 24369,161, 9207, 24371,2610, 24373,24385,24386,24387,24389,24391,24393,24394,24397, 24398,24372,301, 12214,24374,24376,24379,24383,24375,24377,4801, 24378,2142, 5631, 24380,24381,24382,24384,936, 678, 414, 185, 161, 1583, 1372, 4395, 2143, 24388,10176,161, 24390,936, 1365, 24392,14380,1371, 1028, 301, 161, 24395,24396,24399,24400,24402,24403,24404,519, 1060, 1787, 1093, 23485,24405,414, 24406, 1426, 3302, 4108, 24408,24409,24411,24413,24416,24417,4407, 426, 161, 933, 24407,161, 24410,24412,161, 24414,24415,1054, 1145, 14049,11978,1899, 24419,1093, 24421,24422,24423,435, 1050, 161, 24424,161, 24425,161, 302, 5639, 301, 24427,750, 17649,24428,161, 418, 418, 417, 24430,24431,1181, 24433,628, 161, 484, 1223, 304, 414, 24432,1060, 24435,301, 24437,8330, 24438,24439,24442,3302, 22267,1093, 161, 24436,12203,750, 161, 1662, 161, 24440,24441,24444,161, 1538, 1042, 1198, 161, 161, 24446,21945,24448,24450,465, 24451,2280, 24452,161, 24449,301, 9557, 19310,2755, 1934, 17314,418, 440, 3550, 465, 161, 161, 301, 24454,24460,24463,1680, 161, 24455,302, 24456,24457,414, 161, 418, 24458,24459,24461,24462,24464,161, 161, 186, 1482, 417, 24468,4133, 4475, 24470, 24472,24473,161, 24474,24475,24476,24483,24487,24488,24490,24492,24496,24497,24499,24500,24507,24512,24537,24545,24546,24582,24591,24620,24625,24633,3554, 24638, 24639,421, 1060, 750, 304, 435, 14484,24477,24479,24482,12859,24478,12565,1343, 161, 3002, 350, 161, 24480,750, 24481,1822, 1822, 1173, 750, 1228, 161, 24484,24485,24486,3250, 1116, 1093, 1126, 161, 4041, 2442, 24489,222, 414, 301, 1500, 1824, 24491,10655,24493,161, 20681,304, 24494,750, 186, 24495,653, 5684, 24498,161, 23215,24501,24502,24503,161, 24504,24505,24506,1208, 1245, 161, 252, 706, 161, 5122, 161, 161, 1042, 418, 24508,161, 24509,2367, 1823, 1173, 3423, 161, 24510,24511,2854, 161, 12904,2067, 24513,24515,24518,161, 24519,24521,24526,24528,24536,24514,2111, 24282,24516,24517,350, 8781, 24520,24522, 304, 161, 24523,24525,24524,24527,24529,24532,2976, 24530,24531,24533,24534,24535,409, 1356, 13986,24538,24539,24544,304, 418, 1818, 7763, 24540,24541,24542, 24543,1757, 161, 295, 24547,24548,24551,24552,24557,24558,24559,24561,2017, 24562,24563,1843, 24568,24570,24571,24577,24579,414, 161, 161, 414, 24549,3512, 161, 24550,301, 1459, 17098,24553,24554,24555,1842, 6066, 4634, 24556,1343, 1843, 1845, 3730, 418, 435, 161, 161, 6309, 185, 161, 418, 1500, 5114, 4301, 24560,1822, 519, 304, 161, 5448, 1876, 24564,716, 414, 24565,24566,414, 213, 213, 414, 24567,3181, 418, 161, 17067,24569,421, 3167, 161, 418, 1846, 15315,24572,418, 161, 1459, 24573,24574,6118, 3479, 24575,1932, 9590, 24576,23001,1028, 161, 1640, 24578,213, 24580,408, 628, 24581,435, 24583,161, 1042, 161, 24586,161, 24584,8512, 161, 161, 161, 161, 24585,24587,24589,24588,24590,3169, 4577, 16911,186, 24592,8351, 24594,24598,24604,1727, 24607,24610,24612, 24613,1562, 24593,437, 7324, 161, 161, 24595,161, 740, 14569,24596,24597,296, 24599,24602,24600,414, 24601,24603,24605,8116, 556, 1092, 24606,1042, 7478, 161, 161, 24608,24609,24611,1938, 1818, 6290, 24614,24615,24616,24617,24618,24619,24621,24622,24624,161, 4055, 24623,3358, 304, 24626,24628,24630,24632,161, 16336,24627,24629,10176,468, 13533,161, 213, 24631,1868, 21370,161, 22490,434, 2102, 414, 24634,1109, 1640, 2770, 4889, 24637,1727, 24635,24636,19140,161, 2130, 418, 1876, 161, 3928, 1223, 24641,350, 24643,24642,24645,24647,24648,24651,679, 24653,21737,3469, 24654,24659,24665,418, 24672,24673,24680,24682,24694, 414, 24695,24646,426, 10125,1470, 24649,4291, 4901, 1042, 750, 24650,1093, 304, 24652,161, 350, 213, 716, 2451, 24655,409, 213, 24656,24657,24658,1437, 414, 1028, 24660,24663,213, 1217, 6548, 1042, 1756, 24661,24662,24664,414, 24666,1470, 24667,2471, 440, 24668,2293, 161, 296, 24671,1241, 24669,4042, 24670, 213, 24674,24675,304, 24676,24678,1212, 9937, 24677,1042, 414, 161, 7545, 213, 161, 24679,2837, 24681,2837, 4169, 1640, 3302, 24683,161, 24684,13998,3925, 24685,24691,24692,1059, 13850,418, 2166, 24686,1322, 24687,24688,24689,24690,414, 435, 468, 420, 1639, 161, 161, 1640, 296, 24693,161, 296, 1418, 1459, 519, 24697,24698,24700,1012, 304, 24707,24708,24710,24712,4405, 24713,24715,1042, 186, 24719,1108, 1845, 24722,1060, 24701,2430, 24702,24703,24704,24705,24706, 1459, 301, 24709,161, 24560,24711,301, 1109, 418, 7820, 414, 24714,418, 1092, 185, 418, 1323, 24716,24717,24718,161, 24720,24721,24723,24724,24754,24762, 24820,24860,24879,24901,24919,24928,24944,24948,24949,24970,25017,25031,25056,25067,25118,25120,25217,25269,25285,25353,25446,25454,25462,25465,24725,24730,24734, 296, 1824, 24736,24740,24741,24743,24746,24748,11716,24726,24728,24727,24729,3315, 24731,24732,24733,8194, 24735,5631, 24737,13776,24738,24739,24742,24744,24745, 24747,435, 7961, 24749,4042, 24752,24750,24751,213, 1060, 24753,24755,24758,1365, 24759,24756,24757,304, 19589,1343, 161, 9292, 1609, 5314, 418, 414, 24760, 414, 440, 24761,161, 418, 1087, 24763,24765,24766,24776,24778,24783,24788,24790,24791,24797,24817,24819,1890, 1150, 24764,1818, 519, 417, 5722, 409, 17810, 1890, 10350,24767,24769,24775,2040, 24768,24768,4193, 161, 24770,24773,24774,418, 213, 161, 4351, 24771,24772,3550, 161, 418, 418, 2143, 2848, 161, 463, 161, 161, 24777,1343, 161, 2632, 24779,24780,24781,186, 24782,161, 556, 1419, 24167,24784,24785,1990, 12943,13886,24786,1208, 24787,24789,24792,24794,24795, 24796,24793,976, 417, 2539, 23389,418, 418, 24798,24803,14643,24809,24810,24799,24801,24800,1189, 1127, 24802,1208, 463, 24804,24807,24805,24806,3631, 1042, 1150, 350, 13082,24808,4875, 24811,11273,24816,24812,24813,24814,24815,435, 1343, 161, 24818,1059, 24821,24825,24839,24849,304, 24852,24854,24857,3855, 418, 24822,5562, 1760, 1470, 24823,24824,24826,24827,24833,24828,24829,24832,24830,24831,350, 24834,24836,24835,311, 1297, 10425,8746, 186, 24837,161, 24838,1042, 161, 1401, 24840,24846,24841,414, 24842,161, 24843,24844,24845,24847,24848,24850,301, 11547,24851,9518, 24853,184, 24855,24856,3829, 296, 24858,24859,304, 3539, 295, 936, 186, 24861,24862,24863,2082, 1604, 24868,304, 22636,24869,24872,8136, 5631, 24875,161, 7942, 161, 3302, 12193,161, 213, 161, 161, 161, 24864,24559,161, 24866,161, 161, 24867,2474, 706, 24865,1787, 161, 936, 161, 461, 1093, 24870,10250,296, 186, 1896, 1640, 13424,24871,11493,1343, 24873, 24874,24876,24877,24878,24880,24882,2280, 24885,24892,24894,24895,24900,24881,24883,24884,304, 24886,24891,24887,24888,24889,24890,24893,409, 24896,24897,4678, 24898,24899,1270, 434, 4789, 24902,4207, 24904,295, 24915,5083, 24903,161, 11568,24905,3479, 24908,24913,295, 1143, 5376, 24914,2191, 161, 24906,24907,11817, 304, 3438, 13066,414, 12238,186, 4308, 1371, 161, 24909,556, 24910,24911,24912,1206, 304, 350, 1962, 8103, 24916,304, 12238,24917,24918,24920,1362, 1109, 24921,24923,2394, 24926,24927,161, 301, 161, 301, 301, 296, 24922,24924,1042, 24925,9292, 161, 2221, 2387, 3157, 418, 24929,24934,24935,519, 24940,24942, 1173, 350, 11794,301, 23933,1343, 296, 24930,24932,24933,1449, 417, 24931,5441, 417, 24936,24937,24938,24939,24941,465, 4678, 4478, 1583, 24525,161, 418, 161, 24943,24943,24945,418, 24946,24947,1449, 3439, 161, 161, 161, 12385,222, 1862, 24950,24951,24957,24959,24961,301, 24962,24968,635, 301, 445, 23960, 2213, 1280, 16071,24952,1473, 24953,24954,24955,24956,1093, 12567,24958,1033, 161, 24960,2162, 4081, 519, 304, 24963,24964,24965,24966,24967,519, 301, 24969, 1097, 301, 24971,24973,3680, 24975,24982,24987,24989,24990,24998,25002,440, 25004,426, 25005,25008,24847,25013,161, 1990, 161, 2196, 24972,161, 21546,12297, 1851, 895, 24974,414, 2442, 24976,24978,1604, 6190, 1343, 1126, 24979,24980,24977,161, 350, 2241, 4680, 2269, 1895, 1937, 5603, 24981,3512, 161, 24983,10798, 24986,161, 1177, 301, 1787, 213, 161, 24984,24985,24988,418, 679, 295, 9065, 24991,10494,24992,24993,24995,24997,1093, 24994,161, 1949, 1787, 1173, 2234, 24996,161, 414, 161, 1093, 1980, 1093, 1060, 2261, 296, 24999,1459, 161, 161, 679, 161, 1026, 25000,25001,6768, 1093, 25003,1542, 21546,418, 1343, 418, 417, 740, 25006,2538, 25007,25009,25011,25010,25012,25014,25015,25016,414, 1028, 1093, 25018,25021,25025,25026,25029,25019,4042, 161, 25020,1173, 3063, 25022, 1093, 161, 25023,25024,4781, 628, 3555, 9205, 1310, 25027,25028,161, 161, 25030,23514,414, 25032,1160, 25033,519, 25035,11980,25040,25041,25042,1109, 25044, 25047,25051,1161, 316, 1150, 161, 25034,2558, 1270, 9824, 435, 25036,25037,1126, 25038,25039,161, 301, 3250, 630, 519, 10016,1092, 1449, 301, 213, 1126, 25043,414, 304, 1126, 25045,25046,350, 25048,25049,2181, 25050,2191, 9829, 25052,161, 25053,25054,25055,4041, 25057,213, 25058,25061,18777,1109, 474, 4287, 1109, 19112,25059,25060,1007, 25062,414, 161, 1008, 25063,161, 25064,161, 25065,161, 25066,25068,25069,25070,295, 25080,25086,25091,25092,25110,12238,25111, 414, 1060, 484, 161, 161, 25071,25075,25072,3439, 25073,213, 25074,3832, 933, 1889, 161, 1757, 474, 25076,3440, 2081, 161, 25077,1425, 25078,2340, 161, 556, 25079,1198, 25081,25082,1459, 1787, 25083,25084,25085,706, 161, 25087,484, 1459, 25088,25089,25090,185, 465, 25093,25098,25106,161, 350, 25094,3529, 25095,25096,25097,2786, 25099,25104,25100,25101,25102,161, 25103,3055, 1128, 1421, 1208, 25105,556, 7481, 25107,25108,25109,186, 1689, 186, 25112,25115,1486, 25113,556, 1485, 435, 25114,2921, 25116,25117,25119,3157, 1372, 25121,25124,25129,25131,25138,25146,25151,25162,426, 25174,25177,25178,25184,25187,25191,25193, 25198,1459, 25212,16267,25214,25122,25123,25125,1356, 1609, 25126,1097, 25127,445, 304, 301, 213, 25128,750, 3964, 1705, 418, 186, 25130,161, 556, 417, 14426,161, 25132,2191, 1143, 25133,1740, 25134,25135,25136,25137,25139,25140,25142,4523, 25145,3626, 635, 1715, 161, 435, 2961, 25141,414, 186, 1042, 1128, 161, 25143,635, 25144,1217, 2948, 1198, 2182, 295, 1126, 296, 2283, 2167, 25147,1241, 25148,25149,161, 5626, 1093, 2028, 4607, 435, 418, 25150,25152,25161, 161, 25153,25160,25154,161, 161, 25155,25156,1212, 1208, 1093, 301, 25157,2880, 1042, 25158,25159,421, 25163,2167, 25167,25171,161, 25172,484, 25164,25165, 13074,25166,653, 25168,25169,25170,161, 18073,1405, 1432, 1050, 25173,25175,3144, 19789,25176,679, 19244,2130, 1093, 8288, 20644,24925,25179,25180,25182,3510, 10789,304, 25181,679, 678, 25183,2225, 421, 301, 25185,3730, 25186,24753,3022, 25188,25189,24878,25190,1938, 186, 161, 1640, 25192,420, 7085, 161, 1026, 421, 25194,25195,25197,1028, 25196,161, 25199,25201,1949, 15440,1371, 295, 25209,25200,161, 25202,25203,25206,25204,25205,161, 25207,25208,25210,161, 25211, 435, 4003, 25213,25215,25216,25218,25219,25221,25224,1609, 25225,25233,1824, 25235,25236,25240,25242,25243,25245,25261,25265,25266,222, 301, 1470, 25220,1363, 161, 161, 1595, 8158, 15325,25222,25223,24846,161, 161, 25226,25231,25232,418, 25227,25228,25229,25230,4279, 301, 519, 301, 25234,301, 2539, 1365, 296, 418, 185, 418, 25237,185, 25238,25239,25241,2539, 1813, 1470, 1590, 440, 25244,304, 1343, 213, 25246,25255,25256,25259,418, 25247,25251,4193, 25248,25249, 25250,1012, 1895, 1842, 25252,25254,21130,25253,409, 185, 25257,1459, 408, 25258,7323, 3551, 679, 161, 6131, 4081, 25260,25262,301, 25263,25264,25267,25268, 3117, 161, 25270,25271,25272,25278,3178, 25279,25280,1639, 1787, 519, 161, 161, 25273,1343, 418, 1778, 25274,414, 161, 25275,1059, 25276,25277,25281,25282, 4465, 440, 1343, 25284,161, 161, 161, 25283,1583, 161, 425, 417, 1343, 4461, 25286,1823, 409, 25289,25292,25295,25298,25299,1401, 25300,25302,25303,25352, 25287,25288,25290,25291,25293,2199, 25294,25296,25297,161, 17677,301, 301, 301, 5745, 25294,25301,15339,295, 161, 440, 1280, 25304,25305,2020, 25306,25308, 25310,9478, 25314,25318,2165, 4308, 2951, 25321,25327,25330,25332,25334,25337,25341,25346,25348,25349,420, 186, 4218, 10069,25307,25309,630, 350, 1305, 1310, 186, 25311,304, 25312,25313,25315,25317,1896, 25316,414, 161, 311, 1270, 25319,474, 25320,25322,25325,25326,2162, 1270, 25323,350, 2420, 25324,2817, 1284, 25328,25329,25331,25333,25335,25336,350, 350, 8103, 25338,975, 418, 25339,161, 421, 25340,350, 25342,25343,25344,17298,24618,25345,25347,418, 25350,25351, 11032,25354,25355,25358,25444,25445,25356,1341, 25357,437, 1198, 2050, 20837,161, 5745, 1093, 25359,417, 25360,25364,25367,25380,25383,25388,25391,25392,25398, 3689, 25400,25404,13816,25405,25410,25414,25418,25429,20264,25436,161, 25442,25361,3169, 24789,25362,1515, 25363,22986,25365,706, 4108, 25366,161, 8103, 421, 161, 1143, 310, 25368,25369,25371,25378,304, 4730, 25370,350, 25372,25376,310, 1093, 1042, 25373,25374,25375,25377,25379,186, 25381,25382,304, 1042, 304, 1284, 4109, 12391,427, 25384,25386,25385,25387,2596, 1895, 25389,25390,3198, 25393,25396,25394,161, 25395,1116, 25397,25399,25401,25403,161, 186, 25402,4439, 421, 16828,186, 418, 5631, 295, 25406,25407,25408,186, 414, 1270, 12587,25409,25411,25412,25413,1310, 25415,25416,25417,25419,25422,25423,418, 25425,414, 1297, 25420,311, 25421,976, 311, 25424,25426,11993,25427,1121, 1280, 25428,25430,25431,25433,418, 25434,2016, 25432,25320,429, 25435,1093, 161, 1280, 25437, 25438,25441,1717, 25439,25440,25443,2191, 1341, 3167, 350, 2741, 25447,25450,4308, 414, 25452,474, 4042, 16919,1029, 25448,25449,161, 25451,161, 414, 213, 5149, 1474, 25453,1042, 161, 161, 8073, 161, 161, 25455,25458,3267, 2196, 440, 295, 25460,25456,25457,25459,1109, 25461,1935, 25463,295, 1223, 5284, 161, 25464,161, 161, 2937, 25466,25467,25468,25469,715, 1126, 1590, 9458, 1818, 3954, 213, 25470,25471,25472,25473,99, 25842,25843,100, 26170,26195,102, 2306, 26492,103, 26599,104, 26857,105, 27277,27315,108, 27439,27442,27445,27450,25474,414, 25475,25478,25479,25480,25492,25498,25500,25502,25510,25511,25521,25523, 25528,25562,25569,25612,25616,25636,25638,25734,25773,25806,25814,25824,25831,25832,25841,25476,25477,1143, 1143, 3923, 161, 2568, 213, 519, 418, 350, 8061, 25481,25482,25486,25489,7914, 1343, 161, 161, 19789,161, 6808, 7000, 1093, 161, 2094, 1060, 25483,161, 7859, 25484,25485,25487,3882, 19812,2094, 25488,161, 1342, 1538, 350, 25490,25491,1604, 1876, 161, 2817, 186, 161, 4316, 2203, 25493,7586, 21945,25494,15613,1962, 25495,25496,161, 25497,1389, 1900, 4287, 420, 1060, 1093, 1028, 161, 2576, 301, 161, 1223, 25499,161, 25501,1143, 1730, 25503,25505,1042, 25506,1028, 11484,25504,1304, 161, 1093, 1093, 161, 3002, 213, 25507,25508,25509,2576, 519, 1851, 6238, 161, 1387, 161, 25512,25513,2040, 5239, 161, 301, 161, 304, 12341,414, 1208, 3654, 25514,1756, 161, 161, 25517, 25515,25516,25518,3439, 7393, 161, 25519,13404,25520,25522,818, 413, 17465,25524,25525,25526,25527,25529,25535,25543,1813, 25545,25548,25554,25557,2225, 25560, 25561,414, 25530,25531,5552, 25532,3888, 2221, 10597,161, 25533,1093, 3025, 1367, 418, 25534,161, 25536,25537,8486, 25540,11482,1415, 161, 25538,25539,25541, 25542,25544,1286, 161, 413, 608, 25546,3302, 25547,161, 3006, 3488, 750, 161, 25549,1012, 4065, 25551,25553,6537, 1843, 161, 9292, 25550,161, 25552,295, 414, 161, 9292, 2272, 301, 1797, 295, 25555,304, 2252, 161, 25556,1813, 1367, 161, 1421, 25558,25559,409, 947, 519, 414, 3861, 25563,25565,25567,1223, 25564,161, 301, 25566,12534,25568,426, 1150, 2451, 25570,25574,25580,25584,25586,25592,25595,25597,25599,25601,2225, 25603,2183, 2782, 1037, 25571,3548, 25572, 25573,25575,25576,2951, 25577,1198, 519, 435, 2272, 161, 25578,25579,2526, 25581,25583,818, 1097, 25582,1297, 161, 3119, 1750, 6913, 161, 25585,14992,161, 2104, 1042, 301, 161, 25587,25194,25589,25589,304, 25591,161, 25588,25590,25593,740, 25594,24345,25596,1787, 161, 11684,161, 161, 213, 25598,5407, 161, 25600,1042, 1126, 7000, 161, 435, 25602,161, 1191, 25604,25606,25608,6190, 25610,213, 161, 1190, 25611,25605,519, 23535,2901, 25607,25609,418, 3964, 25613, 25614,161, 2127, 4752, 1060, 2678, 25615,25617,25625,25632,304, 25633,25635,414, 4291, 25618,25619,1787, 25621,25622,25623,1760, 2430, 19615,2059, 25620,7337, 1792, 161, 25624,2430, 25626,25627,414, 25628,25629,5238, 161, 25630,977, 25631,296, 5083, 9947, 519, 25634,161, 10881,161, 161, 2755, 7859, 25637,3487, 418, 25639,25675,25679,25681,20102,1813, 1604, 25684,25690,25696,25704,25707,25709,161, 25711,25717,25718,426, 25733,25640,25641,25643,25645,417, 5114, 25648, 25651,25657,25660,25669,25672,1367, 304, 1414, 25642,161, 2677, 25644,316, 161, 1459, 556, 7028, 414, 418, 25646,635, 25647,25649,7126, 25650,1365, 1405, 25652,25654,25656,414, 25653,2082, 25655,304, 25237,25658,21991,25659,350, 25661,25667,25662,25665,25663,25664,25666,25668,25670,25671,25673,25674,25676,25677, 161, 25678,23083,304, 13643,295, 25680,304, 161, 4570, 301, 409, 25682,161, 25683,414, 435, 4515, 1609, 2245, 161, 409, 409, 3807, 25685,519, 161, 4515, 25686,25689,25687,25688,414, 161, 25691,25692,25693,3479, 1343, 161, 635, 161, 11808,161, 161, 1640, 2382, 25694,25695,6066, 418, 213, 25697,25698, 25703,25699,296, 161, 25700,25701,25702,1093, 296, 161, 4203, 25705,440, 6574, 25706,161, 1343, 295, 25708,25710,4111, 17806,25712,25713,25714,25716,161, 421, 750, 1962, 161, 161, 161, 1223, 350, 350, 1343, 3807, 25715,2191, 996, 13372,161, 1640, 3302, 4063, 1092, 414, 25719,409, 25720,25721,25722,10532, 25729,418, 3227, 161, 25732,1276, 25723,301, 1302, 6003, 1343, 437, 25724,1198, 25727,161, 25725,25726,1305, 1766, 25728,25730,25731,414, 186, 7556, 414, 1042, 301, 4065, 408, 25735,25736,25741,1097, 25742,25743,440, 161, 25745,25749,25764,3267, 25772,25737,25739,1036, 25740,1036, 25738,2784, 7961, 3250, 304, 3250, 2576, 1793, 15304,412, 418, 25744,161, 3250, 350, 161, 25746,25747,1754, 304, 25748,7752, 3548, 25750,25751,25755,25756,440, 25758,9465, 25759,8974, 25760,1150, 25752,25754,6980, 161, 25753,161, 7686, 2267, 161, 161, 25757,1675, 304, 12193,161, 1823, 1757, 11998,1143, 161, 25761,25762,161, 25763,161, 161, 25765,25767,25768,25770,161, 4226, 161, 161, 414, 25766,296, 25769,25771,750, 161, 414, 25774,25775,25779,25784,25786,1343, 25789,25790,25791,25792, 2225, 25801,1851, 1787, 1990, 161, 1797, 161, 25776,25777,1876, 25778,161, 435, 161, 252, 350, 161, 3861, 25780,25782,161, 304, 2262, 25781,25783,417, 435, 463, 161, 421, 11636,15732,25785,161, 24636,1109, 5035, 161, 25787,7976, 1342, 161, 25788,304, 23520,418, 2114, 2262, 2196, 25793,25798,161, 25794, 25795,1938, 301, 25796,161, 161, 1813, 1050, 25797,25799,25800,1116, 1905, 161, 1938, 18851,161, 25802,25804,418, 25805,161, 161, 161, 25803,417, 2420, 417, 1093, 1778, 556, 417, 301, 409, 25807,295, 25812,25813,414, 161, 1270, 25808,936, 25809,161, 12246,1093, 25811,19236,414, 161, 304, 161, 11482, 161, 161, 161, 25810,301, 161, 1060, 161, 161, 1425, 1042, 2430, 25815,25817,25819,25820,1595, 161, 161, 25816,161, 161, 25818,715, 161, 408, 1813, 161, 5483, 437, 556, 421, 25821,25823,25822,161, 421, 608, 4661, 25825,1042, 25826,25830,161, 421, 213, 25827,25828,608, 25829,9735, 608, 186, 1042, 4026, 1727, 1343, 417, 417, 414, 25833,25834,25835,22213,25837,1876, 25838,1303, 25839,9706, 7419, 3731, 25840,417, 1150, 1342, 25836,3006, 414, 222, 25844, 25857,25858,25874,25888,25896,25898,25904,25906,25910,25912,25931,25938,25994,25998,101, 26112,26127,26161,26163,26165,26166,26167,4951, 25845,1109, 2280, 686, 25849,5362, 25850,25851,25854,25855,25856,161, 25846,25847,420, 161, 3654, 7000, 4407, 25848,161, 1208, 4640, 161, 8107, 161, 25852,1343, 25853,161, 161, 161, 2748, 23806,1604, 1343, 5643, 161, 1640, 161, 418, 1304, 161, 4306, 1356, 440, 1885, 25859,25861,25863,25865,25869,161, 25871,25873,25860,25862,418, 25864,161, 11560,25866,1604, 25868,161, 161, 25867,1093, 1418, 409, 25870,25872,161, 10103,25875,25876,25878,25881,440, 25885,25886,25877,5606, 161, 2453, 25879,25880,435, 24581,25882,13065,25884,25883,304, 22654,2252, 2127, 420, 25887,1980, 301, 4041, 4461, 1042, 25889,25891,25892,25894,161, 2677, 25895,2362, 25890,186, 1042, 161, 2880, 1042, 161, 25893,3178, 1042, 750, 161, 1042, 1756, 161, 161, 25897,9518, 440, 414, 25899,25900,25901,1604, 161, 25902,186, 304, 1060, 25903,161, 25905,1542, 18836,25907,161, 25908,295, 25909,484, 12567,350, 25901,25911,1042, 25913,25914,25916,3652, 1604, 25917,440, 25918,25926, 25928,25929,1190, 25930,19994,25915,1343, 1343, 161, 5639, 25919,1604, 25925,1343, 350, 1459, 4602, 25920,25922,25924,161, 25921,418, 9899, 25923,409, 2142, 2038, 1250, 25927,161, 296, 5503, 1028, 161, 1851, 25932,25936,25933,25934,25935,25937,25939,25941,25942,25943,25953,25956,25957,25959,25960,19589,5053, 25966, 161, 25978,25979,25985,1639, 4523, 519, 24094,1037, 25940,1217, 304, 161, 6935, 10708,1128, 295, 161, 1539, 3169, 25944,25946,1042, 25951,213, 350, 25945, 1981, 25947,430, 25948,161, 468, 25949,25950,350, 1413, 25952,25954,715, 25955,161, 821, 12306,222, 1895, 1896, 418, 25958,1640, 21182,25961,25962,18639, 25963,161, 2433, 161, 25964,25965,25967,25968,25970,25972,420, 25974,25977,10473,350, 25969,7000, 161, 25971,161, 161, 421, 1093, 1756, 1343, 25973,1981, 161, 10653,161, 25975,25976,414, 161, 7000, 7022, 13850,8209, 25980,25981,213, 418, 466, 25982,304, 25983,25984,161, 25986,25989,25990,25992,3479, 25993, 161, 1150, 25987,4083, 25988,1482, 1604, 25991,6066, 1459, 4640, 5350, 161, 25995,25996,25997,1818, 25923,6256, 25999,22951,519, 414, 1143, 26000,418, 295, 161, 161, 26001,161, 2050, 2864, 26002,1754, 161, 25899,26003,26006,26014,26015,26017,26025,26026,26028,26039,26042,26045,26047,26055,26056,26059,26068,26076, 26101,26107,26109,161, 161, 2050, 26004,1470, 2596, 26005,1949, 1813, 26007,26010,26011,26013,161, 12591,435, 26008,10673,26009,2840, 1150, 1216, 1302, 26012, 1087, 1895, 435, 3778, 1343, 10146,474, 1341, 4577, 304, 161, 1813, 519, 24848,1829, 4865, 301, 161, 26016,1173, 161, 26018,26021,26024,26019,2094, 26020, 161, 186, 13125,213, 161, 26022,26023,3490, 3439, 1042, 161, 3469, 24988,7143, 1223, 1604, 26027,26029,1173, 1747, 26030,7442, 23570,26031,26034,26038,1250, 936, 161, 26032,9128, 26033,161, 26035,26036,26037,2674, 1696, 26040,26041,2880, 161, 213, 26043,15480,161, 161, 26044,1757, 1026, 161, 213, 26046,474, 3510, 418, 4727, 26048,16387,26050,26054,12412,26049,26051,26053,26052,161, 1640, 1217, 8458, 26057,26058,2195, 414, 1640, 26060,26067,161, 26061,26062,26065, 26063,26064,1208, 1042, 1426, 26066,1189, 1121, 26069,26070,26072,26074,15799,3529, 2969, 1173, 1223, 301, 26071,161, 296, 519, 435, 26073,161, 350, 161, 1981, 161, 161, 426, 1036, 26075,26077,26078,26082,26083,213, 440, 26086,26090,26096,417, 26099,26079,26080,26081,3006, 1765, 1722, 13581,4058, 26084,26085, 2344, 161, 26087,26088,26089,26091,414, 26092,26095,434, 161, 1150, 26093,26094,213, 161, 1128, 1284, 3432, 414, 1527, 26097,26098,26100,1405, 1688, 26102, 161, 26103,26105,26104,350, 26106,2516, 26108,2430, 2523, 26110,414, 161, 26111,8634, 2420, 26113,26114,421, 26116,1590, 11607,19228,26120,26121,26123,706, 1604, 26115,161, 4482, 26117,26119,26118,26122,161, 22092,161, 1590, 26124,26126,2748, 161, 26125,3098, 1042, 4567, 1215, 3169, 26128,15970,26132,7146, 26136, 26152,26153,26160,635, 296, 17156,414, 350, 435, 26129,1060, 414, 161, 2750, 20286,26130,1885, 1343, 26131,4682, 2142, 2241, 418, 2471, 1482, 1778, 26133, 161, 161, 26134,1850, 26135,26137,26138,26139,26142,26148,161, 4207, 161, 4065, 2331, 996, 161, 161, 3807, 2094, 161, 5483, 161, 11294,161, 26140,1097, 26141,679, 26143,26144,2060, 26145,26146,2539, 1367, 4111, 26147,26149,1342, 26151,26150,1792, 5368, 26154,26156,213, 414, 635, 26159,26155,22156,161, 635, 161, 2111, 1706, 1093, 3267, 9046, 26157,161, 3002, 635, 1228, 1792, 26158,1539, 1787, 26162,26164,2516, 519, 161, 296, 3399, 26168,26169,26171,26179,26187, 26188,26190,26192,26193,185, 679, 435, 26172,26174,296, 161, 26173,26175,26176,26177,26178,26180,26185,26181,26182,26183,26184,26186,161, 296, 2655, 26189, 466, 24890,5603, 26191,26194,26196,26216,26227,26265,26267,1009, 26298,26299,26300,26197,1343, 296, 1109, 161, 26198,26205,26215,161, 933, 161, 710, 161, 161, 26199,26200,14218,26201,26202,26203,26204,26206,1093, 414, 26208,7957, 9069, 26207,26209,26210,26211,468, 161, 26212,26213,26214,21106,1042, 2706, 26217, 7243, 4042, 26218,26219,26224,26225,161, 418, 301, 414, 316, 26220,26221,26222,26223,4346, 418, 6358, 26226,26228,7243, 295, 26230,26262,4979, 26264,26229, 414, 26231,26242,418, 26245,26248,26253,417, 26256,26261,417, 3833, 26232,26235,26233,26234,26236,26238,26237,26239,26240,26241,26243,418, 26244,161, 420, 2162, 26246,294, 26247,5469, 26249,26252,414, 26250,1036, 161, 26251,161, 213, 26254,414, 1060, 414, 26255,161, 414, 161, 6046, 1026, 12432,3704, 26257, 26258,26259,26260,1426, 5032, 161, 26263,440, 26266,26268,26270,26274,440, 26279,26283,161, 26269,26271,26272,421, 26273,26275,161, 3321, 26276,304, 161, 26277,26278,26280,26281,706, 26282,26284,414, 161, 26285,414, 26288,26293,161, 161, 26286,26287,17681,161, 7262, 26289,26290,26291,26292,7885, 11788,161, 161, 26294,26295,26296,26297,8448, 2449, 26301,26305,26302,26303,26304,26306,26307,26309,1990, 7696, 26308,161, 1733, 1305, 26310,26311,26312,26313,26317,26349, 26350,26369,26370,26377,26379,26397,26404,26443,26446,26450,26453,26459,26470,26484,26485,26487,10354,26314,161, 26315,26316,2252, 468, 6790, 4133, 7859, 421, 4879, 161, 26318,26319,26321,26328,26330,1392, 26343,161, 26344,26320,1190, 519, 161, 3548, 161, 26322,26323,26324,26325,1367, 14172,3376, 26326,304, 161, 161, 26327,26329,5384, 295, 418, 1143, 26331,3267, 4313, 26336,15420,26339,26342,161, 4532, 161, 161, 26332,1093, 26333,26335,435, 26334,161, 161, 26337, 161, 161, 26338,26340,26341,7961, 418, 1036, 161, 161, 2523, 1823, 10907,418, 26345,686, 26346,26347,1042, 161, 414, 161, 26348,11675,3847, 414, 26351, 26352,2362, 9496, 26355,161, 1270, 26356,26358,161, 26367,1425, 24093,1042, 186, 26353,26354,26357,519, 13810,161, 26359,6933, 26361,26364,2524, 26365,26366, 26360,4308, 161, 26362,2516, 26363,222, 2094, 1640, 7586, 296, 1223, 26368,408, 161, 414, 2538, 2449, 414, 1156, 3267, 26371,6379, 3778, 26372,26373,2191, 1109, 1604, 26376,8314, 304, 750, 213, 2191, 4958, 418, 23389,26374,1126, 26375,818, 1792, 26378,1134, 435, 26380,9243, 26382,26383,2245, 26386,26390,26396, 417, 484, 161, 1151, 2750, 26381,3019, 161, 10968,161, 933, 296, 26384,26385,26387,26388,1981, 1981, 26389,1981, 161, 417, 26391,26392,1257, 26393,26395, 1284, 161, 1425, 1093, 706, 161, 26394,4382, 161, 26398,26401,26399,26400,3250, 435, 161, 26402,186, 1876, 23215,161, 26403,26405,26406,26408,295, 26413, 26419,26420,26422,26423,440, 26428,26431,26433,26436,26440,26442,13423,1962, 1432, 186, 301, 26407,296, 26409,817, 26410,26411,2864, 1302, 1042, 418, 1143, 440, 26412,26414,26417,1813, 1824, 818, 2266, 161, 1342, 161, 26418,26415,161, 26416,1126, 467, 2362, 6046, 435, 6642, 5981, 1129, 2362, 26421,1740, 818, 421, 1223, 556, 418, 1143, 26424,26425,1604, 4919, 25951,4168, 2538, 1480, 414, 1050, 22571,350, 26426,26427,6968, 26429,26430,6394, 161, 435, 161, 161, 706, 161, 421, 556, 161, 26432,161, 161, 10473,1150, 26434,26435,213, 418, 26437,26438,26439,26441,296, 1134, 161, 435, 161, 818, 2782, 222, 26444, 7981, 414, 3922, 26445,414, 26447,5319, 1042, 5409, 26448,2713, 1060, 161, 26449,1092, 161, 26451,26452,26454,26457,1150, 26458,14022,1150, 213, 1367, 26455, 26456,26460,26462,161, 3222, 26464,26465,26461,2539, 414, 3730, 26463,5562, 2741, 6980, 716, 4284, 1876, 678, 161, 26466,26469,26467,26468,161, 24662,26471, 23653,213, 26477,426, 1343, 295, 304, 161, 26478,26482,26483,5398, 26472,26473,420, 26474,26475,1042, 26476,414, 26479,26480,4465, 3766, 26481,1217, 186, 161, 466, 161, 12082,440, 18399,750, 4901, 26486,4168, 1093, 8818, 26488,296, 26489,1813, 818, 26490,2025, 26491,161, 4587, 414, 161, 1343, 519, 161, 484, 519, 2568, 26493,26494,26495,26557,26573,26577,26584,26597,26496,26501,26502,304, 26511,26513,5747, 26523,26533,26542,1245, 26543,301, 706, 26497,26500, 304, 3954, 9824, 3025, 161, 161, 26498,26499,161, 1323, 716, 7028, 556, 1184, 1371, 2817, 1093, 26503,161, 26510,161, 26504,213, 161, 26507,26505,26506, 26508,26509,6124, 26512,414, 414, 421, 26514,1042, 1480, 26519,161, 26520,26515,161, 26516,9364, 161, 414, 26517,161, 26518,26521,26522,1042, 161, 161, 161, 1184, 1335, 26524,26525,5207, 26526,304, 26531,26527,26528,26529,26530,1554, 26532,1093, 1990, 161, 26534,26536,161, 26537,295, 26540,26541,21423,1851, 414, 26535,161, 161, 2451, 1342, 161, 26538,26539,414, 9522, 418, 4307, 295, 161, 2782, 2270, 12303,161, 414, 161, 26544,26547,26548,26549,2165, 1876, 7416, 26553,1109, 3771, 26554,26556,3029, 26407,26545,421, 26546,26550,26551,26552,26555,11803,630, 26558,26566,26567,26568,26569,26570,463, 26571,26572,301, 301, 14723,26559,161, 26560,26563,1042, 26564,26561,26562,213, 2367, 161, 4164, 26565,1421, 1054, 1042, 1343, 2182, 435, 2901, 1184, 302, 1787, 11547,2270, 5610, 408, 161, 26574,976, 26575,26576,350, 301, 13856,5483, 466, 26578,2755, 679, 26579,26580,26581,26582,26583,161, 161, 3934, 426, 20985,161, 2040, 161, 1729, 409, 4503, 1109, 1208, 1042, 1343, 26585,304, 466, 26586,304, 26587,26592,1087, 26593,26594,26596,26588,26589,1818, 4306, 26590,161, 161, 26591, 295, 10198,8095, 161, 26595,1042, 295, 26598,1150, 435, 5139, 26600,26602,26601,26603,26604,24809,26607,26613,26623,26629,26632,26633,26634,26646,26650,26691, 26706,26717,26725,6886, 26741,26782,26816,26833,26847,26848,26853,26856,26605,1036, 408, 6734, 26606,1036, 26608,26612,414, 26609,26610,2161, 186, 3198, 26611, 161, 421, 1037, 26614,26615,26616,26619,26620,26622,161, 2016, 435, 1044, 26617,1459, 26618,26621,421, 4511, 2280, 26624,1093, 26626,161, 161, 26628,26625, 26627,302, 295, 213, 161, 12239,26630,26631,7210, 161, 304, 20619,26635,26639,26640,26641,26645,2225, 26636,2605, 26638,26637,26636,3631, 1042, 1756, 161, 161, 1818, 295, 2142, 1208, 26642,26644,26643,186, 1042, 936, 161, 716, 1028, 295, 1760, 26647,1876, 26648,161, 417, 3964, 161, 161, 26649,26651,6153, 26656,295, 9557, 26657,26658,26669,26672,26679,26680,26681,26686,426, 26652,161, 26653,26654,26655,519, 161, 6131, 22198,2931, 421, 4111, 1412, 2197, 161, 2094, 161, 16912,16912,1640, 26659,26662,26664,26665,161, 9472, 26666,2523, 6330, 26660,161, 26661,161, 26663,161, 161, 6131, 3659, 26667,161, 304, 161, 26668,161, 161, 26670,26671,26673,26674,26675,1343, 26676,161, 26677,26678,421, 161, 1208, 186, 421, 5233, 350, 1766, 161, 6950, 213, 8431, 4073, 418, 5722, 26682,26683,26684,26685,429, 26277,26687,24223,414, 26688,26689,26690,26692,26693,26700,26701,26704,161, 304, 750, 10915,26694,26697,26695,26696,26698, 4055, 213, 26699,1343, 484, 161, 26702,26703,414, 222, 414, 26705,213, 26707,26709,161, 161, 26710,161, 26712,26716,1250, 26708,519, 5224, 161, 26711, 414, 23496,1134, 26713,26714,302, 679, 1778, 936, 414, 161, 418, 26715,4291, 17168,1356, 1635, 26718,22511,26719,26721,26722,26724,295, 295, 440, 414, 26720,4638, 2165, 1042, 8218, 1538, 3102, 6309, 519, 26723,13986,161, 11946,2165, 213, 3766, 414, 161, 8181, 26726,1604, 26727,426, 26728,26730,26731,26737, 26738,1639, 3435, 161, 18484,304, 10486,435, 26729,418, 161, 608, 2539, 301, 679, 519, 19031,161, 26732,26733,26734,933, 26736,1054, 3282, 26735,161, 3315, 26739,26740,3962, 20166,26742,26743,26748,26749,1042, 26750,26751,2538, 1990, 26758,26759,26763,1470, 818, 26744,26746,161, 26747,26745,426, 222, 2092, 161, 2975, 161, 24698,1270, 26752,8181, 161, 26753,161, 26754,26755,26756,26757,686, 302, 161, 1949, 7296, 26760,301, 26761,26762,26764,26766,26768,26770, 26771,440, 26772,1343, 26773,26774,26776,26779,26780,1609, 161, 1150, 26765,15065,414, 161, 161, 11357,350, 936, 26767,161, 414, 414, 161, 3098, 2165, 26769,161, 519, 296, 1778, 1092, 161, 185, 213, 420, 4169, 213, 3006, 26775,1797, 161, 26777,1305, 26778,435, 186, 1042, 161, 26781,435, 1058, 26783, 409, 26784,161, 26785,2655, 26788,26790,26796,161, 3079, 161, 161, 161, 296, 161, 414, 556, 26786,26787,4051, 10415,26789,26791,26794,1459, 26792,26793, 26795,6890, 1563, 26797,1738, 26798,23346,5503, 26801,26803,26806,26808,161, 26811,26815,161, 3250, 301, 26799,26800,161, 26802,20102,161, 26804,26805,161, 421, 430, 161, 161, 26807,26809,26810,26812,26813,26814,414, 26817,26819,26822,26823,26824,26825,5407, 26826,26828,414, 426, 26818,18237,161, 4758, 350, 1699, 26820,161, 213, 26821,301, 740, 295, 1356, 608, 2797, 304, 7970, 26827,304, 26829,6066, 26831,304, 26832,161, 161, 161, 466, 26830,414, 4640, 161, 1093, 4058, 9243, 26834,26835,26841,26844,2040, 1542, 26846,26836,26837,26838,26839,26840,26842,414, 1583, 26843,26845,3438, 13220,408, 26849,26851,414, 5702, 17376,1727, 26850,16239,26852,414, 161, 1824, 186, 3654, 2165, 1042, 1756, 7022, 161, 3970, 296, 26854,26855,26858,26859,26860,26861,26862,5350, 106, 27041,107, 27262,27272,27273,26863,26869,26870,26873,435, 26876,161, 26877,161, 26878,26881,26882,2643, 435, 26885,350, 26864,26865,26866,1121, 26867,6247, 1425, 1042, 26868,161, 1343, 295, 161, 26871,26872,296, 26874,304, 26875,519, 1107, 2817, 26879,4883, 26880,186, 421, 740, 301, 161, 296, 295, 26883, 440, 161, 26884,161, 414, 1121, 161, 3439, 1876, 1727, 161, 445, 26886,1150, 26889,26906,26925,26931,26934,26936,26940,26942,26947,26953,26961,26971,26976, 26983,26989,27025,27030,27031,27038,2040, 27040,26887,1421, 3002, 26888,26890,26894,26897,26898,26901,26902,4407, 3554, 26891,26892,26893,26895,26896,1042, 161, 1580, 161, 1341, 1131, 936, 26899,2907, 26900,304, 1260, 3554, 1093, 26903,1899, 26904,26905,701, 21522,26907,213, 26909,26916,1962, 26923,26908,26910,26913, 26911,26912,26914,26915,26917,26919,12385,26918,1421, 186, 1091, 161, 26920,26921,26922,186, 1212, 26924,26926,26930,161, 161, 26927,26928,26929,304, 8511, 1217, 26932,316, 26933,161, 7478, 8052, 311, 161, 26935,161, 26937,161, 26938,26939,26941,1343, 26943,26944,26945,26946,1425, 1087, 26948,26951,26952,26949, 26950,26954,26956,26959,26960,13138,26955,26957,26958,3946, 1198, 8136, 26962,26964,295, 26967,26969,26963,26965,26966,26968,26970,26972,26973,821, 26974,26975, 26977,26979,26982,26978,13081,1425, 1042, 26980,1212, 26981,186, 6309, 161, 26984,26988,26985,26986,26987,26990,26991,26994,26998,27007,301, 27008,27013,17376, 27014,27017,27021,26992,26993,7494, 26995,26996,26997,2817, 1334, 26999,27002,1093, 304, 27000,27001,186, 1042, 213, 161, 1150, 1335, 418, 27003,27004,1230, 1042, 27005,27006,27009,27010,1042, 161, 27011,213, 27012,414, 161, 1087, 161, 296, 4169, 1640, 27015,1042, 23699,1343, 1639, 418, 27016,1609, 2020, 27018, 1028, 3227, 27020,161, 1938, 27019,27022,2094, 3033, 1208, 1042, 27023,27024,427, 3555, 27026,161, 1727, 1438, 27028,7126, 27027,350, 2040, 1198, 3554, 27029, 3529, 2191, 2257, 27032,27034,27037,1744, 27033,1699, 27035,706, 27036,3041, 186, 2886, 161, 161, 6415, 2651, 437, 304, 27039,2191, 12738,350, 27042,27043, 27047,27051,2020, 27054,27056,27066,27078,27084,27089,27092,3469, 27044,27045,15479,27046,1143, 2191, 26995,213, 161, 213, 27048,27049,1962, 2963, 161, 1962, 161, 27050,304, 161, 161, 27052,519, 296, 6423, 161, 27053,414, 5166, 1609, 1474, 161, 27055,27057,27060,27064,1851, 27065,1270, 8509, 414, 421, 161, 27058,161, 1198, 7496, 27059,161, 3724, 161, 161, 27061,27062,27063,1367, 1515, 27067,27074,161, 27075,27076,161, 27077,1036, 27068,27071,414, 161, 213, 27069,23535,27070,27072,27073,161, 3469, 23520,519, 3439, 1042, 3368, 161, 933, 161, 27079,27080,27081,161, 27082,27083,161, 3652, 27085,27087,2225, 2050, 27086,161, 27088,414, 2340, 161, 27090,1093, 161, 27091,1143, 6222, 27093,3634, 27096,2191, 161, 213, 27094,27095,27097,7496, 414, 27098,27099,27105,27126, 12239,27133,27149,27157,27162,2976, 27167,27172,27182,27188,27189,27211,27228,27242,27244,27259,27260,2797, 3041, 519, 1242, 27100,27101,6309, 27102,350, 26795, 1245, 11151,27103,27104,414, 3355, 161, 161, 27106,27108,27114,161, 27116,27119,27120,27123,27125,27107,27109,27111,27112,27110,27113,1302, 12106,1426, 27115, 1727, 2755, 2755, 27117,27118,1279, 8864, 27121,9101, 27122,296, 27124,10634,161, 1116, 27127,27129,27128,435, 3555, 161, 27130,1336, 1042, 27131,27132,161, 1092, 1772, 27134,27135,27141,27142,27145,27147,27136,27137,186, 27138,2340, 27139,27140,8557, 161, 296, 6222, 27143,4287, 27144,27146,27148,2102, 8058, 27150, 27151,27153,27152,414, 10198,161, 27154,27155,1302, 27156,556, 418, 1332, 16062,27158,27159,27160,27161,186, 417, 161, 27163,27164,27165,414, 186, 27166, 296, 161, 304, 556, 1092, 27168,27170,27169,2152, 302, 27171,414, 27173,27176,27178,27180,161, 7332, 27174,27175,715, 27177,8852, 1425, 1042, 161, 27179, 1230, 8724, 27181,161, 161, 27183,519, 27186,27184,27185,27187,27190,27191,27193,27195,5248, 27206,3002, 27210,24694,304, 27192,2600, 7235, 161, 27194,2483, 1128, 11294,2677, 27196,27201,27197,27198,27199,27200,435, 186, 161, 3169, 1425, 27202,556, 27203,27204,27205,27207,27208,27209,1608, 296, 161, 1415, 27212, 27214,1059, 213, 27219,295, 27225,27213,27215,27216,27217,1208, 27218,18717,556, 1482, 435, 27220,27221,27223,27222,427, 296, 161, 414, 186, 27224,2017, 706, 2468, 27226,27227,1367, 27229,27236,27237,27240,1829, 1829, 27230,304, 1092, 27233,414, 27231,1875, 27232,1426, 161, 1418, 1092, 304, 27234,27235,5238, 1042, 686, 161, 1305, 7047, 27238,1093, 27239,27241,12738,27243,316, 7775, 2225, 3799, 6106, 213, 27245,27246,27251,27257,1217, 4133, 161, 27247,27250,161, 1216, 27248,27249,435, 161, 295, 519, 27252,27255,8746, 27253,1042, 161, 27254,1093, 161, 27256,414, 161, 1093, 1087, 27258,1405, 2111, 350, 161, 716, 25708,4491, 1036, 27261,161, 27263,27267,27268,161, 27269,27271,1126, 27264,27266,161, 27265,161, 9061, 161, 414, 5150, 2191, 3002, 161, 27270,1042, 213, 421, 161, 3285, 304, 304, 296, 1042, 296, 27274,27275,27276,27278,27279,4627, 27284,27280,27281,27282,27283,27285,27286,27287,27289,27294,27288,27290,1240, 27291,27292,27293,161, 161, 1910, 414, 27295,27300,15744,27302,27306,27309,27310,27296,27297,27298,27299,27301,27303,27304,27305,161, 2210, 1305, 27307,27308, 414, 26814,27311,1393, 27312,27313,27314,27316,27317,27318,27324,27319,27320,27321,27322,27323,27325,27326,27327,27328,27338,27343,27345,27346,27350,556, 7076, 27355,27356,27367,27371,27388,27392,27423,27429,27438,27329,302, 27331,463, 27330,27332,27333,27336,414, 27334,27335,213, 1242, 1335, 350, 213, 311, 27337, 161, 27339,161, 24865,27341,27340,161, 26150,161, 27342,13378,1297, 350, 27344,1563, 4313, 1198, 161, 2213, 1778, 2548, 4016, 350, 27347,1538, 27348,161, 161, 27349,161, 27351,161, 27352,2109, 4042, 27353,27354,1059, 222, 1208, 1876, 27357,2270, 27358,27359,27360,10907,414, 27364,27365,295, 1245, 608, 304, 519, 161, 1085, 2523, 27361,27362,27363,1343, 874, 161, 1459, 1459, 3282, 161, 27366,1093, 9207, 161, 995, 27368,27370,27369,1640, 1538, 3652, 1876, 25958, 161, 1754, 27372,350, 27378,27379,27383,161, 7098, 27384,161, 519, 27373,27375,27374,1302, 1042, 2050, 161, 27376,27377,1284, 3962, 437, 27380,27382,27381, 1421, 4284, 1042, 1303, 1757, 296, 418, 3315, 161, 161, 27385,27386,27387,3169, 426, 27389,27390,750, 161, 9860, 27391,161, 27393,27394,27396,27397,27398, 27400,14094,161, 1639, 27407,27412,27413,27418,27419,27421,4291, 1640, 27395,304, 2024, 9431, 161, 1208, 213, 161, 350, 27399,1208, 1042, 417, 10644,27401, 27403,27404,27402,213, 1437, 1538, 27405,161, 27406,5107, 27408,1173, 301, 27409,27410,27411,213, 161, 27414,27415,4063, 27416,1639, 716, 1208, 27417,24290, 161, 27420,414, 27422,474, 750, 161, 27424,409, 213, 27425,27426,27427,213, 161, 3892, 6516, 3754, 1876, 687, 161, 4169, 1198, 213, 27428,512, 161, 630, 180, 1412, 1412, 1343, 161, 4789, 161, 27430,6066, 1343, 27431,1080, 161, 27432,27435,27437,27433,296, 1876, 161, 27434,27436,161, 12462,27440,27441, 27443,27444,27446,27447,27448,27449,2191, 1362, 27451,27452,27454,304, 27455,1198, 418, 295, 27456,27462,418, 27453,436, 750, 161, 716, 2058, 418, 27457, 161, 27458,22712,161, 27459,27460,27461,27463,27468,27471,27478,27481,27486,27489,27492,27494,27497,27500,27508,27609,27464,27465,27466,27467,27469,27470,2891, 27472,573, 1996, 27473,27477,27474,27475,27476,27479,27480,27482,27483,27484,27485,27487,27488,27490,27491,27493,27495,556, 1042, 27496,161, 27498,27499,27501, 27503,27502,27504,27505,27506,27507,27509,27551,27573,27602,27603,27607,1109, 27510,27514,27516,5065, 27517,27520,27522,161, 27526,27533,27543,27546,3046, 161, 27511,27512,161, 27513,161, 27515,1304, 5424, 161, 304, 1036, 27518,27519,27521,1042, 2225, 27523,414, 414, 27524,1128, 27525,1297, 8864, 1302, 304, 27527, 161, 27528,27529,27532,1459, 27530,27531,519, 1128, 27534,161, 750, 21600,27537,27538,27535,27536,161, 13820,27033,161, 27539,2678, 161, 27540,161, 414, 8521, 186, 213, 27541,161, 27542,296, 1107, 27544,161, 27545,414, 27547,27548,27549,1754, 27550,4712, 304, 27552,27553,27555,301, 27557,27560,27561,27566, 304, 1345, 1861, 27554,161, 27556,1093, 414, 161, 418, 296, 304, 161, 27558,27559,409, 426, 295, 1787, 27562,750, 161, 213, 27563,161, 27564,27565, 1862, 9681, 9681, 13424,27567,161, 27568,27572,27569,27570,414, 1421, 186, 1042, 27571,161, 1590, 1421, 27574,161, 27576,27578,27579,27581,3728, 27583,414, 27593,27596,27597,27599,5339, 27601,1198, 27575,17281,304, 1563, 3479, 161, 519, 27577,3046, 440, 27580,301, 519, 161, 414, 2576, 27582,3770, 213, 5828, 11984,1126, 8262, 27584,27585,27586,414, 213, 304, 414, 4570, 27587,27589,418, 1028, 161, 27591,1126, 161, 27588,1818, 2238, 12411,27590,161, 27592,161, 4967, 27594,1635, 27595,2514, 418, 9681, 304, 161, 161, 161, 27598,27600,9044, 15715,161, 161, 161, 27604,27605,1425, 161, 27606,716, 556, 27608,314, 27610,27616,573, 3241, 303, 27611,283, 3245, 294, 27612,512, 174, 27613,3693, 21270,161, 27614,27615,27617,111, 1365, 3527, 112, 28498,113, 28681,114, 28980,28988,28993,115, 24694,29100,29103,27618,27620,27619,27621,27622,27623,27624,27635,27657,27674,27676,27682,27691,27694,27719,27722,27726,27732,27763,3198, 27785,27798,27801,27803,27818,27842,27849,27859,430, 27863,27874,1036, 161, 408, 484, 27625,27626,27629,27631,27634,1109, 2934, 161, 519, 4335, 161, 11928, 27627,304, 414, 27628,27630,161, 5642, 1896, 9947, 414, 27632,27633,4478, 1180, 27636,27637,27638,27642,27646,27648,161, 27652,161, 27656,1824, 161, 27639, 27640,161, 27641,161, 2864, 304, 27643,27644,435, 27645,414, 161, 161, 1036, 161, 435, 3603, 5767, 27647,8580, 27649,1604, 1042, 27651,161, 161, 414, 27650,27653,27654,161, 27655,27658,16873,5407, 27660,3267, 27661,304, 27671,440, 27672,4511, 3730, 27673,161, 22244,2283, 27659,1050, 484, 296, 6122, 2283, 161, 24525,161, 27662,27664,1343, 161, 27666,27670,408, 414, 1093, 2345, 27663,27665,161, 161, 414, 27667,186, 27668,2558, 161, 27669,1432, 417, 4638, 678, 19754,4478, 2028, 484, 414, 2393, 27675,27677,19097,27678,2081, 27681,6860, 2539, 27679,27680,13612,304, 1634, 1042, 27683,27684,27685,3281, 27687,27688, 6718, 4064, 4407, 27690,1092, 1093, 301, 18011,27686,161, 27689,27692,27693,16019,2115, 161, 161, 1459, 409, 161, 1150, 27695,3267, 27697,27700,27701,161, 27714,27718,27696,1042, 161, 213, 1042, 213, 27698,4958, 635, 27699,414, 161, 27702,27705,27706,6677, 27708,301, 5503, 1343, 1126, 27711,4164, 5934, 15331, 27703,350, 27704,6124, 1412, 27707,27709,27710,161, 468, 409, 27712,27713,161, 6124, 409, 1336, 27715,4203, 2434, 27716,27717,161, 161, 27720,4015, 161, 27721,27723,1042, 5387, 161, 161, 27724,27725,27727,27728,27730,1981, 27729,161, 1297, 27731,27733,27734,27741,413, 27743,27745,3778, 27749,1555, 27753,27754, 27756,628, 27760,12198,414, 1956, 304, 295, 222, 161, 14562,27735,16528,418, 27737,27738,27739,27736,1818, 4308, 161, 18725,27740,27742,355, 21892,27744, 161, 161, 484, 484, 161, 2293, 27746,161, 2182, 27748,161, 27747,27750,27751,27752,27755,635, 161, 161, 1093, 1044, 27757,27758,295, 417, 27759,161, 213, 2241, 27761,27762,295, 418, 1150, 6654, 161, 296, 16195,27764,27765,27769,27776,350, 27777,3892, 27780,27782,27783,27784,1459, 161, 27766,186, 161, 4779, 27767,161, 27768,27770,27771,161, 27772,27774,4327, 161, 1050, 3250, 1191, 27773,27775,1189, 161, 161, 1432, 161, 27778,1042, 161, 27779,161, 186, 27781,161, 420, 16768,1639, 3267, 24319,19426,1042, 161, 161, 10058,1230, 433, 27786,440, 27788,27789,161, 27796,27797,27787,296, 556, 1305, 6046, 6035, 1813, 1813, 27790,4307, 1042, 27792,27795,27791,27793,27794,1426, 1823, 1367, 14242,27799,27800,161, 13187,426, 16134,27802,421, 556, 213, 161, 27804,27805, 4638, 27806,27808,27809,27811,1818, 27812,27813,27816,1778, 185, 3194, 3169, 15690,18663,2449, 186, 27807,2530, 213, 1459, 161, 2530, 161, 27810,3730, 5157, 2750, 3559, 15065,435, 27814,27815,27817,10926,203, 180, 414, 27819,3435, 27822,27824,27825,27827,4879, 27833,1343, 295, 1342, 27834,27841,27820,27821,27823, 16874,1151, 3267, 27826,296, 417, 1027, 304, 1371, 1150, 1126, 2934, 11294,1343, 1889, 27828,27829,161, 27830,161, 27831,161, 27832,213, 5159, 678, 418, 27835,27836,27837,635, 161, 27838,27840,161, 1093, 27839,161, 161, 409, 27843,17077,27845,27846,6561, 27847,27848,27844,27512,414, 1541, 1480, 27850,27851, 27856,161, 27852,27853,161, 161, 1228, 161, 1891, 23313,936, 27854,1609, 27855,18843,1793, 27857,27858,1297, 7404, 161, 2195, 27860,417, 1635, 27861,1480, 27862,414, 27864,27865,3264, 1425, 5826, 2251, 27675,27868,27871,1093, 635, 27873,27866,27867,161, 418, 1363, 418, 296, 27869,3157, 27870,161, 1134, 27872, 296, 3198, 2727, 7070, 27875,27877,27911,27927,28010,28054,28070,28098,28127,28141,28184,28191,28193,28219,28249,28277,28284,28347,28356,28357,28425,28454,28458, 28478,28483,28486,28495,27876,27878,27885,27888,27889,27894,27895,27896,27899,27902,27909,27910,11436,161, 27879,27880,27883,1302, 1876, 27881,27882,186, 27884, 1426, 161, 556, 11347,304, 7070, 27886,874, 161, 414, 161, 213, 27887,1757, 1093, 418, 161, 304, 296, 4643, 1818, 27890,213, 27892,27893,161, 27891, 1759, 3578, 1212, 1425, 161, 12615,7835, 161, 4284, 161, 161, 11202,301, 161, 1426, 27897,27898,1583, 186, 16391,27900,161, 20264,27901,1818, 295, 27903, 27905,27904,161, 7478, 1538, 1042, 687, 161, 27906,27908,12511,1421, 27907,301, 1351, 27912,27913,27916,1886, 1343, 27921,27924,1727, 304, 27914,27915,27917, 818, 1017, 27918,161, 161, 2318, 301, 161, 27919,161, 161, 161, 27920,161, 556, 13888,27922,350, 418, 8635, 27923,296, 1341, 1190, 421, 27925,7383, 27926,27928,4603, 27932,27941,27945,27951,27952,27957,27991,28001,28005,28009,3954, 27929,12562,27930,161, 304, 2040, 27931,1270, 27933,27934,27935,27940,1305, 21878,2182, 1189, 27936,6968, 27937,296, 27938,304, 27939,414, 468, 8676, 27942,27943,8248, 161, 27944,701, 1037, 161, 27946,27949,1093, 1303, 27947,27948, 27950,1208, 1042, 1093, 437, 4332, 296, 16564,295, 27205,27953,27954,27955,27956,27958,435, 27962,27964,27970,27986,27988,27989,27959,27960,1284, 437, 27961, 1263, 1425, 1042, 27963,27965,27966,27967,4769, 27968,27969,27971,27975,27979,12432,27972,27973,1208, 27974,1212, 556, 27976,27978,27977,27980,27981,27982,27983, 27984,27985,27987,161, 414, 3439, 4308, 161, 350, 161, 1059, 27990,27992,27995,27998,27993,27994,27996,27997,27999,28000,1538, 1042, 715, 161, 28002,28004, 9704, 1459, 161, 28003,28006,8103, 304, 28007,28008,8544, 161, 414, 573, 28011,28012,28014,28015,28020,28029,1260, 28032,28034,28037,28041,28042,28044,1730, 28046,28047,28049,28052,4781, 1143, 1126, 28013,296, 21761,28016,28017,28018,28019,161, 435, 161, 161, 2750, 4898, 6003, 409, 350, 161, 28021,28022,2050, 295, 161, 28023,28024,161, 28025,304, 2283, 936, 28026,28027,28028,24934,28030,28031,1896, 409, 350, 28033,350, 435, 421, 161, 295, 10652,11134,28035, 28036,304, 28038,28039,976, 28040,418, 2130, 28043,2378, 5494, 161, 414, 1042, 28045,5438, 420, 1059, 1459, 28048,5409, 26273,5589, 474, 1893, 28050,28051, 161, 1425, 1042, 437, 213, 28053,628, 7800, 28055,28056,5253, 161, 28057,28058,28060,161, 28063,484, 28065,28068,414, 296, 161, 414, 161, 28059,1042, 161, 1692, 161, 28061,161, 295, 28062,28064,296, 28066,1093, 28067,28069,28071,5256, 28076,28079,28083,28085,28095,28072,28073,28075,161, 161, 28074,2817, 17170,3169, 186, 1042, 25875,28077,28078,1042, 161, 715, 213, 28080,28081,1432, 28082,186, 1586, 296, 161, 936, 28084,426, 28086,28089,28087,28088,1538, 2333, 1303, 28090,28091,28092,28093,28094,186, 417, 1426, 3988, 28096,28097,2902, 304, 2854, 1813, 28099,28102,28104,28105,20193,161, 28116,28120,4051, 3884, 1054, 28100,1044, 28101,28103,161, 430, 161, 213, 10677,301, 161, 304, 1387, 296, 28106,28109,28110,28112,28107,28108,161, 8376, 161, 301, 28111,28113, 12534,28114,28115,161, 28117,4327, 9205, 28118,161, 28119,7478, 186, 1042, 28121,28124,28122,18073,28123,1208, 1156, 9704, 28125,28126,28128,28131,295, 28133, 1895, 2976, 161, 28137,161, 296, 28139,161, 28140,28129,161, 3812, 28130,28132,28134,28135,161, 28136,3169, 1208, 1042, 1372, 28138,28142,28144,28147,1541, 28148,28154,295, 519, 28155,28156,28159,28180,28182,28143,629, 28145,161, 4720, 28146,1121, 1371, 3002, 1350, 350, 4364, 3102, 465, 161, 296, 161, 28149, 28150,28153,6124, 28151,28152,3551, 3551, 435, 1161, 304, 3770, 185, 213, 1042, 1474, 161, 1372, 28157,1426, 28158,28160,3113, 28161,28163,28165,28166,28170, 4465, 28171,1824, 14139,28173,28178,6280, 28162,28164,161, 5126, 304, 2203, 28167,28168,28169,4720, 1510, 28172,1042, 28174,3153, 28176,28175,28177,426, 28179, 409, 4193, 28181,936, 14787,1343, 295, 161, 5495, 6264, 161, 10039,161, 1028, 28183,1851, 19831,1935, 4482, 28185,28186,28188,28187,28189,28190,1640, 28192, 28194,28200,5839, 28204,2183, 28215,28216,28218,22831,28195,28199,2420, 1425, 28196,556, 28197,24167,28198,17793,1302, 1042, 28201,3267, 8818, 28202,3174, 28203, 28205,1093, 28206,3267, 28207,28210,409, 1405, 1315, 2345, 161, 161, 161, 1405, 28208,28209,1093, 1879, 28211,28212,28213,28214,186, 1764, 28217,301, 1042, 28220,28225,28226,28231,440, 28238,24711,28240,10370,28247,28248,161, 304, 28221,28223,440, 1195, 28224,408, 28222,295, 186, 1042, 161, 13348,3432, 1899, 1896, 12223,295, 28227,28229,161, 28228,161, 435, 1302, 414, 28230,12534,4523, 19065,421, 1470, 9169, 28232,28237,4432, 3438, 28233,28234,28235,28236,15782, 1042, 9486, 2537, 28239,414, 28241,28244,28245,1332, 28246,28242,28243,213, 161, 304, 435, 1230, 1042, 1012, 418, 28250,9243, 28252,28257,28263,28266,28267, 4806, 28268,28271,1843, 28272,28273,3959, 161, 28276,161, 27977,2289, 1284, 186, 2519, 28251,161, 6966, 28253,28256,12771,418, 3250, 28254,28255,186, 1042, 414, 161, 2113, 1050, 28258,1044, 28262,28259,1208, 1042, 28260,28261,1895, 28264,474, 28265,4737, 301, 161, 28269,28270,161, 421, 161, 466, 296, 1504, 18113,1291, 1534, 409, 3833, 1042, 414, 15857,28274,818, 1042, 418, 28275,161, 5396, 161, 161, 1792, 28278,28280,28279,435, 1555, 28281,28282,28283,28285, 28290,7874, 1126, 1813, 28305,28314,28319,28320,161, 28337,28340,28286,28288,414, 28289,161, 5780, 28287,21522,2362, 28291,28293,28294,28296,28301,161, 28292, 1569, 1042, 161, 28295,28297,28300,28298,28299,28302,28303,28304,13888,304, 28306,28309,28310,6009, 161, 28307,1297, 28308,1042, 1421, 161, 1303, 28311,1302, 28312,350, 28313,161, 1042, 686, 28315,28318,28316,414, 28317,1042, 161, 1189, 1054, 161, 10643,1756, 1899, 185, 2678, 28321,28327,28329,17957,28322,28323, 8877, 28324,28325,28326,186, 1042, 161, 427, 28328,9741, 408, 1260, 28330,28333,28331,28332,161, 1042, 1688, 28334,28335,28336,28338,28339,28341,20619,28344, 28346,28342,28343,28345,1208, 427, 27001,1208, 28348,161, 28349,28350,301, 3160, 1459, 1613, 28351,28352,28353,161, 28354,28355,740, 12273,1092, 1150, 28358, 28363,28374,28375,295, 28382,28383,28389,28402,28403,28420,28359,4438, 21878,28360,304, 28361,28362,28364,301, 28366,28369,28372,6389, 28365,28367,28368,1216, 1425, 1042, 28370,28371,1949, 484, 186, 3198, 1042, 1303, 161, 28373,1334, 1425, 1042, 1670, 28376,8691, 17810,161, 28380,28377,1042, 1805, 28378,161, 23215, 28379,28381,2344, 186, 1042, 161, 28384,4523, 3160, 28386,28385,1405, 12738,28387,28388,1757, 28390,28394,28398,1756, 28391,414, 28392,3615, 186, 3654, 28393, 161, 556, 1332, 28395,304, 28396,28397,28399,2040, 28400,1093, 28401,26795,304, 161, 304, 295, 28404,1538, 3198, 28408,1756, 28410,28412,161, 350, 28405, 28406,28407,1910, 4528, 556, 28409,519, 421, 161, 28411,10673,1425, 1042, 28413,5562, 28416,8135, 28417,28414,28415,186, 1042, 1093, 28418,28419,3160, 2817, 28421,28424,23251,28422,28423,28426,28431,28432,28435,1899, 28438,28451,28452,161, 28453,28427,304, 28428,301, 8636, 7098, 5163, 28429,28430,3041, 350, 161, 28433,28434,1343, 1718, 28436,28437,1383, 5760, 161, 28439,28442,28443,28446,28440,161, 28441,1421, 417, 296, 435, 28409,28444,28445,435, 2880, 1042, 9212, 5122, 28447,28448,28449,28450,161, 296, 5997, 296, 4871, 1109, 3778, 28455,19927,28457,28456,1092, 1405, 296, 295, 295, 434, 28459,28460,28467,3399, 28471, 27210,311, 414, 161, 2362, 933, 186, 27033,28461,28462,28463,161, 1334, 414, 1042, 2677, 161, 28464,4678, 28466,2420, 161, 28465,304, 1060, 1242, 435, 1208, 1266, 28468,1223, 28469,3136, 1208, 28470,556, 1184, 1555, 296, 1405, 28472,1297, 28473,6217, 28474,28475,28476,28477,1626, 1133, 161, 14090,28479,161, 28480,186, 1284, 161, 1085, 28481,706, 28482,1093, 1042, 1109, 414, 408, 3267, 28484,161, 28485,1151, 304, 1362, 1841, 28487,28490,28494,161, 28488,28489, 28491,28492,301, 6768, 186, 186, 28493,1813, 2406, 28496,445, 28497,28499,28500,28508,28513,28517,28518,161, 1198, 18866,28501,301, 1093, 28503,161, 28505, 161, 301, 301, 28502,28504,28506,161, 28507,8660, 28509,28510,28512,20658,10299,28511,161, 7430, 161, 28514,28515,304, 28516,1583, 161, 161, 2387, 414, 161, 16868,213, 161, 6217, 1904, 28519,28520,28523,28527,28554,28568,28589,28591,301, 28600,1037, 28601,28602,28604,28607,28633,28634,28639,28650,414, 28665, 1107, 28678,28521,28522,161, 1060, 1750, 28524,28525,3103, 28526,304, 161, 421, 161, 1341, 186, 350, 304, 1092, 818, 301, 414, 28528,28531,161, 28534, 28542,28552,1093, 28529,28530,28532,28533,161, 10141,161, 2094, 28535,15091,28537,28540,213, 28541,295, 295, 2050, 28536,12110,417, 161, 1150, 28538,28539, 161, 418, 2142, 2130, 414, 28543,28544,1036, 28550,1343, 3832, 1050, 2283, 1740, 161, 28545,28538,28549,161, 28546,28547,28548,1011, 1026, 1093, 161, 28551, 28553,186, 1044, 28555,28559,28561,28563,28567,295, 874, 1216, 21925,16391,28556,28557,28558,161, 161, 161, 161, 414, 3724, 28560,874, 1093, 161, 3893, 2378, 28562,440, 635, 414, 435, 161, 635, 28564,13648,28565,28566,1208, 2651, 3196, 1042, 418, 28569,28570,28571,9207, 14789,4885, 28575,28576,28577,28583, 295, 28584,28585,28587,750, 3250, 474, 1829, 1036, 28572,161, 1343, 161, 161, 28573,1851, 434, 28574,1372, 28578,28579,161, 28580,28581,28582,28586,519, 161, 295, 16490,161, 161, 28588,161, 28590,426, 4287, 161, 3250, 1042, 304, 4871, 213, 417, 435, 28592,28595,186, 295, 440, 28599,24925,418, 28593, 28594,10984,24925,161, 3282, 161, 161, 28596,28597,3654, 213, 2109, 1727, 1028, 161, 161, 28598,161, 1451, 10138,9222, 1143, 2056, 222, 28603,1042, 440, 28605,28606,3928, 295, 161, 4448, 213, 28608,28609,28610,28617,28619,161, 28626,1036, 28628,1121, 28629,1249, 186, 1341, 1757, 161, 418, 28611,28614,1126, 161, 414, 28612,28613,28615,28616,28618,161, 161, 161, 1899, 4695, 414, 12238,28620,28621,1876, 28622,161, 28624,2605, 28625,4352, 161, 9937, 3770, 14380, 1896, 28623,3551, 161, 161, 2196, 4871, 1896, 628, 28627,161, 19744,161, 28630,28631,28632,1760, 519, 161, 434, 25173,161, 3770, 316, 161, 161, 21350, 9233, 28635,161, 2195, 28636,1876, 28638,418, 161, 161, 161, 161, 28637,418, 161, 5242, 161, 1093, 4465, 161, 409, 28640,28643,28644,28645,440, 295, 28646,6113, 28647,28648,1109, 28641,161, 161, 28642,1208, 1042, 161, 161, 161, 296, 295, 1343, 161, 355, 296, 304, 28649,161, 10993,161, 28651,28652, 28654,28655,28657,28658,28661,28664,1245, 426, 28653,4169, 1387, 2199, 3893, 161, 161, 28656,28659,304, 161, 28660,161, 3559, 2165, 28662,28663,2802, 161, 4364, 1343, 161, 28666,28669,28676,20286,28667,17758,186, 161, 28668,818, 28670,28673,3002, 28675,28671,1023, 28672,414, 161, 9494, 4285, 301, 1028, 1412, 28674,186, 1042, 1036, 28677,1498, 1191, 296, 161, 1818, 222, 28679,161, 1342, 28680,1228, 519, 1228, 28682,28683,28684,28693,28718,28732,28755,9633, 28764, 28771,28780,28781,28783,28784,28802,28817,28824,28838,28840,28843,28845,28905,28932,28957,28960,28967,28970,28975,28685,28686,1297, 1036, 28692,3160, 996, 414, 28687,1810, 2165, 1198, 28689,28690,28691,635, 161, 28688,1727, 2196, 295, 414, 28694,28698,28701,28705,28713,5207, 28715,1026, 161, 28717,1028, 421, 1590, 28695,28696,28697,28699,28700,161, 1028, 161, 161, 6459, 1060, 16344,28702,161, 28584,28703,1026, 161, 414, 304, 304, 1026, 28704,414, 161, 161, 161, 161, 1026, 28706,28709,161, 9169, 28710,1504, 417, 28712,28707,28708,5711, 3181, 2963, 414, 161, 3634, 28711,28714,418, 2976, 28716,408, 1604, 1393, 161, 28719,28720,28721,519, 28725,1990, 635, 161, 7210, 161, 28722,28723,3267, 13524,418, 11005,6035, 28724,4105, 161, 3181, 1036, 414, 28726,3267, 28728,28729, 3479, 5190, 7868, 418, 3315, 28730,9376, 14366,161, 2847, 1093, 28727,161, 1143, 1640, 1510, 1093, 6487, 28731,414, 28733,28735,28737,28743,28745,440, 1343, 9233, 28746,28747,161, 1639, 2283, 161, 161, 28734,161, 161, 28736,161, 295, 28738,301, 28740,161, 10096,161, 6280, 28739,28741,1093, 28742,28744,414, 421, 5455, 161, 2442, 3119, 28748,28750,28749,5455, 421, 28751,28753,1949, 161, 28752,4173, 484, 161, 161, 28754,28756,2442, 28757,28758,295, 28760,28761, 161, 3730, 1270, 28762,28763,304, 4364, 466, 161, 161, 161, 28759,409, 296, 304, 295, 295, 2280, 2280, 161, 466, 28765,28766,304, 3087, 9031, 28767, 28768,28770,6013, 161, 9950, 1025, 28769,418, 304, 2313, 28772,2115, 1851, 28773,28777,28778,185, 3551, 4376, 1042, 1343, 678, 28774,296, 24498,161, 28775, 28776,1011, 28779,295, 1851, 161, 1829, 414, 28782,161, 296, 19065,414, 28785,28787,28788,28789,3281, 28791,28800,409, 28801,28786,414, 1093, 304, 28790, 5639, 28792,28798,1343, 3665, 161, 28799,1542, 161, 1542, 28793,161, 28794,28797,1026, 28795,28796,161, 17513,417, 2196, 933, 2053, 679, 161, 28803,16694, 28812,28814,434, 440, 2571, 4491, 28816,9834, 414, 421, 1595, 28804,1250, 28811,414, 161, 4287, 1792, 28805,28806,5645, 28807,1028, 301, 1868, 161, 1093, 2539, 28808,28809,1241, 28810,161, 28813,1050, 1342, 13810,161, 161, 161, 28815,519, 414, 28818,1813, 28820,11819,28822,1028, 161, 28823,7081, 28819,161, 6966, 28821,1028, 161, 5675, 161, 1036, 28825,28826,28828,28831,1036, 28832,28836,296, 1198, 161, 4531, 161, 1143, 1818, 1198, 28827,414, 186, 3198, 6190, 28829,161, 161, 28830,161, 28833,4289, 28834,28835,4422, 28837,5763, 161, 1230, 28839,28841,28842,10044,296, 28844,161, 1060, 28846,28857,28859,28860,28889, 28890,28894,18643,28895,295, 28897,28898,28902,4880, 161, 414, 519, 28847,28851,28852,28853,28855,1250, 1173, 301, 28848,28849,301, 418, 161, 161, 28850, 301, 2025, 185, 161, 18547,161, 28854,161, 519, 28856,1013, 28858,414, 20602,28861,28865,28866,28868,28871,1342, 161, 28888,1639, 161, 3194, 15310,28862, 28864,28863,421, 421, 1011, 185, 304, 20402,28867,418, 28869,185, 161, 28870,2113, 28872,28877,28880,1818, 15576,28882,28884,474, 1843, 3510, 5350, 6280, 28887,28873,28874,28876,1013, 421, 28875,2191, 1847, 19887,28878,28879,28881,161, 28883,28885,28886,421, 301, 7522, 161, 301, 28891,28892,28893,161, 301, 161, 161, 608, 22434,161, 28896,679, 161, 4193, 414, 21226,1351, 28899,28901,6153, 1371, 7207, 28900,3250, 161, 161, 2094, 161, 28779,1850, 295, 186, 28903,304, 28904,28906,28908,28909,28911,28922,1343, 295, 28924,9474, 161, 28925,28930,161, 161, 28907,3342, 1223, 5797, 28910,1012, 15366,28912,15024,28916, 28917,1343, 3315, 28919,28920,435, 28913,28914,28915,435, 408, 10176,1010, 28918,15366,425, 28921,2280, 435, 28923,519, 2289, 28926,28927,1343, 3802, 161, 16490,24611,28928,28929,28931,28933,414, 28934,9168, 28940,28942,28946,304, 5126, 28950,28953,161, 295, 27513,27513,28935,28936,355, 350, 1938, 1538, 1042, 213, 28937,28938,1026, 28939,28941,355, 706, 28943,28944,28945,28947,28949,9292, 414, 28948,186, 1042, 161, 9292, 933, 161, 4638, 161, 1042, 28951,1555, 28952,418, 355, 4814, 28954,7736, 28955,2121, 418, 417, 161, 242, 161, 161, 161, 28956,1206, 161, 28958,28959,2130, 28961,22264,28963,1042, 28964,161, 28966,417, 28962,350, 16770,435, 1097, 161, 350, 3762, 28965,161, 161, 1699, 161, 28968,3453, 1387, 1060, 28969,161, 161, 161, 7289, 414, 28971,1087, 304, 28973,4523, 28974,28972,161, 2225, 161, 1128, 1109, 161, 5722, 414, 28976,28978,295, 1343, 4879, 7859, 28979,28977,161, 304, 608, 3771, 161, 23708, 301, 28981,28984,28982,28983,28985,28986,28987,28989,14494,28991,28990,28992,28994,28995,28996,28997,29009,29012,29025,29029,29035,29040,29043,29044,29046,29051, 29059,29066,29067,1087, 29071,29086,186, 29092,29096,29097,5684, 7028, 421, 28998,29002,29003,29005,29007,295, 16769,977, 161, 29008,28999,519, 29000,29001, 6001, 4293, 304, 161, 421, 161, 29004,4589, 161, 1367, 1093, 29006,161, 161, 1501, 222, 29010,3827, 29011,296, 161, 304, 1343, 161, 414, 29013,29014, 29015,29017,29018,29020,1343, 29021,29023,1060, 6256, 3015, 3689, 21925,420, 304, 420, 161, 3435, 2225, 1756, 3529, 29016,161, 1851, 29019,161, 8379, 29022, 29024,29026,29027,301, 161, 556, 435, 29028,19927,295, 418, 1145, 28248,304, 29030,29032,21226,161, 4353, 29031,1983, 296, 1778, 8181, 29033,3469, 295, 161, 1184, 29034,3797, 1818, 29036,161, 10094,161, 29037,29039,750, 161, 29038,1367, 6271, 29041,29042,3730, 295, 185, 1343, 13798,8733, 161, 161, 29045, 185, 29047,29048,29050,161, 2161, 161, 1093, 161, 161, 29049,556, 417, 29052,1608, 5228, 440, 29054,29055,29057,3689, 29058,13285,3518, 29053,161, 2225, 4308, 11250,7961, 161, 29056,1426, 161, 161, 1284, 414, 29060,7085, 29061,1093, 1036, 1156, 29062,29063,678, 1843, 161, 161, 635, 3766, 4217, 3274, 1640, 6966, 5864, 4109, 1843, 29064,29065,161, 414, 1093, 409, 1245, 679, 933, 5146, 29068,304, 29069,29070,27931,29068,4235, 29072,4898, 29073,29074,29075,2449, 29076,29083,19789,5362, 1754, 496, 1107, 4284, 3267, 1876, 1727, 1962, 417, 7859, 161, 161, 29077,29078,29080,29082,1459, 29079,350, 161, 29081,161, 186, 186, 29084,1121, 418, 29085,161, 161, 5065, 1028, 29087,29090,4879, 9611, 29091,161, 414, 29088,29089,304, 1877, 1845, 1109, 4203, 4693, 1042, 161, 29093, 7976, 29094,29095,1087, 304, 295, 29098,519, 29099,29101,29102,29104,29106,29109,29110,13329,29111,295, 29112,6393, 29113,302, 414, 29105,29107,4395, 1013, 7859, 301, 2596, 1363, 421, 161, 29108,1012, 21925,2537, 7390, 161, 434, 5448, 1410, 29114,161, 29115,117, 118, 120, 30096,30098,121, 123, 30611,30613, 124, 30775,30839,125, 126, 31284,31304,31314,127, 131, 32149,132, 32248,32250,32324,3029, 2166, 435, 5588, 29116,12481,29117,29122,29139,29156,29162,29167, 29175,29188,29190,29197,29198,29203,29250,29270,29349,29354,29380,29387,29410,29428,29441,29444,29447,161, 29118,29119,161, 1850, 161, 12117,29121,29120,29123, 29127,29131,29133,4089, 29134,29136,3025, 29124,161, 29125,29126,161, 6706, 29128,409, 29129,161, 29130,3861, 29132,13274,161, 161, 7408, 161, 161, 161, 222, 29135,7768, 161, 1093, 29137,29138,6609, 29140,29142,29143,29145,29141,2748, 12668,2127, 161, 10009,12859,1036, 29144,1343, 14411,161, 29146,1208, 29149, 29147,29148,29150,29154,29151,29152,1425, 29153,29155,4898, 3559, 29157,1818, 29160,29161,1756, 19948,222, 6520, 304, 1459, 29158,29159,5300, 161, 350, 252, 161, 17658,29163,186, 1129, 295, 2362, 29164,6744, 29165,161, 29166,2272, 29168,29173,161, 440, 29174,29169,5412, 2165, 29170,213, 161, 3002, 7188, 635, 29171,29172,3829, 14908,417, 29176,29178,29179,29180,29184,1343, 418, 29186,29187,161, 161, 161, 29177,296, 4746, 1059, 1042, 29181,29182,29183,29185,8921, 1341, 29189,1010, 6744, 161, 1044, 161, 4322, 301, 161, 29191,417, 222, 29192,29194,1604, 161, 3004, 1343, 161, 29193,2880, 1042, 2340, 161, 417, 161, 161, 29195,161, 186, 1609, 29196,161, 29199,1093, 161, 29200,161, 29201,161, 161, 12567,301, 1037, 301, 29202,414, 29204,1012, 29212,29213,29214,29216, 29217,13612,29223,29226,29230,29232,29234,29236,29238,29245,29246,1459, 29205,414, 29206,519, 1128, 29209,29210,29211,414, 4607, 161, 29207,161, 1595, 161, 29208,2289, 1341, 1590, 2188, 2024, 161, 1060, 301, 1367, 29215,1905, 17095,8772, 1341, 161, 3049, 301, 519, 29218,161, 29220,29221,29222,1824, 350, 29219, 1093, 161, 1851, 161, 5819, 161, 484, 16637,29224,29225,161, 1050, 1722, 29227,19426,29228,29229,2238, 1026, 414, 29231,2677, 418, 304, 161, 3453, 29233, 6621, 1343, 3439, 29235,1756, 3529, 5934, 161, 3529, 161, 29237,21522,1208, 1042, 29239,29243,29244,161, 161, 3945, 29240,161, 29241,1208, 1470, 1042, 29242, 6537, 3944, 418, 1109, 2094, 161, 1966, 519, 18817,1426, 29247,2241, 296, 1371, 29248,29249,414, 573, 29251,29255,5065, 29256,3267, 29257,29258,29262,29263, 301, 29266,29268,418, 15814,301, 29252,29253,161, 2028, 29254,16654,1749, 161, 1705, 414, 2447, 1097, 29259,350, 304, 1092, 161, 2526, 29260,519, 29261, 1028, 350, 414, 435, 301, 29264,2331, 29265,26342,6677, 1042, 1459, 435, 418, 29267,435, 1470, 29269,1951, 29271,8181, 29277,29283,29302,29303,29305,29306, 29310,29313,18795,29314,29317,29320,29321,8772, 29322,29328,4164, 29348,2025, 29272,29274,29276,29273,29275,27748,29278,29279,29282,29280,28355,213, 29281,29284, 29285,29287,3267, 1842, 29291,29292,29294,19613,29295,29296,1843, 29297,29299,161, 6520, 4133, 29301,1050, 304, 435, 18181,161, 7076, 28038,1013, 316, 29286, 161, 161, 29288,3250, 1343, 161, 29289,161, 3315, 29290,426, 1343, 1026, 296, 1143, 296, 414, 1938, 15289,29293,519, 5767, 1087, 161, 10766,9741, 161, 161, 252, 29298,15164,29300,5590, 1847, 29304,1143, 1343, 161, 296, 301, 1787, 430, 29307,29308,29309,26518,437, 1059, 29311,29312,1966, 1995, 213, 28767, 161, 29315,29316,296, 29318,29319,5819, 29323,22618,29326,29324,29325,161, 1036, 29327,29329,29336,29340,29347,2678, 161, 414, 1590, 29330,304, 161, 29332, 2480, 29333,29335,161, 29331,161, 295, 29334,3548, 161, 1792, 1787, 161, 29337,161, 29339,29338,29341,29342,519, 29343,5625, 29345,519, 29344,21613,161, 161, 29346,222, 6046, 2519, 27070,1980, 11377,29350,5511, 29353,161, 29351,29352,296, 4225, 6118, 161, 29355,295, 29357,29361,679, 29364,29366,23202,29368, 750, 29371,29372,1042, 213, 29374,161, 29376,29379,2826, 161, 414, 1787, 3006, 426, 21969,1050, 7145, 161, 29356,1787, 2275, 29358,29359,304, 29360,2238, 29362,29363,1093, 1093, 301, 29365,716, 350, 161, 29367,161, 301, 414, 296, 29369,29370,29373,161, 29375,2191, 414, 161, 3006, 350, 426, 1028, 29377, 1639, 29378,29381,2028, 29382,1787, 5207, 29383,29384,1823, 1050, 653, 29385,29386,1343, 6718, 252, 414, 29388,29390,29393,295, 29397,3665, 304, 161, 29401, 29406,296, 29389,414, 584, 302, 29391,29392,519, 29394,29396,29395,1036, 304, 29398,29399,29400,17681,1297, 29402,29403,295, 29404,3098, 29405,5855, 2867, 304, 185, 161, 29407,1692, 29408,29409,1938, 29411,29413,29414,936, 29417,161, 29420,29422,29423,29425,29426,29412,295, 161, 1813, 1093, 161, 1426, 1093, 29415,29416,161, 425, 2142, 29418,29419,161, 29421,295, 1426, 301, 10009,1028, 2840, 29424,29427,161, 29429,29434,29437,29439,213, 29430,301, 29432,29433, 2316, 29431,355, 213, 213, 161, 1075, 161, 161, 1059, 161, 29435,2047, 3365, 1635, 161, 29436,1938, 1150, 29438,20763,484, 1093, 161, 1028, 29440,3907, 3853, 296, 161, 161, 4603, 13441,17077,186, 29442,301, 440, 161, 5689, 3922, 608, 29443,9945, 161, 1343, 29445,417, 161, 29446,414, 10847,25968,29448, 4313, 29449,304, 29450,304, 161, 1161, 161, 409, 1093, 409, 1134, 1426, 29451,29493,119, 29725,29745,29748,29776,29813,29831,29452,2234, 7414, 29453,29455, 1729, 29456,29462,29464,29474,29479,29489,29490,161, 29454,2081, 12737,414, 8061, 7990, 161, 1297, 29457,161, 29458,29459,29461,161, 519, 161, 556, 6046, 418, 2901, 29460,161, 4580, 1042, 161, 1116, 29463,161, 8251, 161, 161, 29465,301, 3766, 29470,161, 29471,29466,29467,29468,29469,13968,1042, 29472,29473, 1876, 29475,29476,29477,29478,1010, 29480,1332, 29482,29483,29486,29487,21945,1223, 29488,1116, 4064, 409, 750, 161, 29481,15310,1882, 29484,2225, 161, 29485, 1217, 161, 1042, 2748, 3799, 1418, 161, 1792, 1367, 414, 1270, 10186,29491,1126, 2915, 29492,1897, 161, 29494,5284, 29495,21523,302, 2420, 29496,29519,29550, 29582,29606,29620,29632,29658,29675,29698,29497,29498,29499,29501,29502,29503,29505,434, 29506,29507,29508,29509,29510,484, 29513,161, 161, 18992,161, 161, 13849,4364, 29500,161, 6208, 1851, 2280, 18836,161, 296, 29504,14508,4801, 295, 1150, 409, 161, 1036, 1851, 421, 1818, 161, 519, 484, 161, 296, 818, 1351, 29511,296, 29512,1829, 484, 14380,161, 161, 296, 29514,161, 29518,161, 17124,29515,29516,29517,418, 15091,296, 4884, 29520,29522,29524,29525,29527, 29530,29533,29535,29539,29540,29545,304, 304, 29547,29521,161, 295, 295, 29523,5362, 1851, 484, 29526,20062,29528,1851, 1059, 29529,295, 161, 161, 29531, 29532,4382, 1042, 29534,295, 1230, 1042, 434, 3721, 933, 161, 1650, 421, 434, 29536,29538,29537,686, 414, 29541,296, 5089, 408, 29542,1270, 1851, 484, 295, 1372, 29543,29544,29546,1109, 295, 29548,29549,29551,29554,29557,29558,29562,5610, 29564,29570,29572,15539,29574,29575,29576,519, 29552,161, 29553,29555, 161, 29556,295, 29559,9570, 161, 414, 29560,295, 29561,161, 29563,29565,304, 29566,161, 1109, 28613,161, 2576, 296, 29567,29568,29569,421, 304, 29571, 679, 29573,1036, 1150, 2655, 295, 161, 24069,29577,186, 29578,29579,29580,29581,29583,29587,29595,29598,29605,1351, 10377,29584,466, 29586,29585,976, 161, 434, 29588,29590,29591,29592,7967, 29594,15182,161, 9243, 295, 29589,1270, 1829, 1351, 295, 29593,465, 5956, 29596,466, 27718,29597,161, 296, 4491, 29599, 4695, 556, 933, 29600,29601,1012, 1851, 29602,29603,4678, 29604,161, 430, 24890,161, 295, 29607,29608,29611,29616,29617,295, 6279, 15493,414, 16639,29609, 1042, 29610,295, 421, 29612,29614,161, 29615,3302, 29613,1851, 304, 295, 1270, 161, 304, 6423, 161, 161, 435, 23132,29618,435, 29619,29621,29624,29628, 29630,29631,434, 29622,1421, 304, 435, 4979, 296, 29623,421, 3529, 29625,29626,29627,2280, 295, 421, 295, 6599, 29629,295, 10566,14375,1818, 295, 5483, 29633,29641,29642,29647,29648,29649,29653,29654,29655,29656,29657,161, 421, 295, 29634,7000, 29635,933, 11429,29636,29637,29638,20649,1042, 3315, 4726, 14261, 1363, 29639,29640,29643,3928, 29645,29646,1036, 29644,2469, 213, 7556, 3620, 161, 2442, 2241, 296, 295, 16694,1895, 20786,161, 161, 161, 29650,29652,414, 3435, 1891, 16405,3098, 3440, 1042, 161, 29651,3213, 1161, 296, 7000, 687, 1181, 1109, 29659,29662,29668,29671,29673,29674,421, 295, 3551, 5362, 29660,29661, 15445,161, 295, 29663,295, 29634,29667,933, 421, 29664,29666,29664,29665,295, 296, 10663,295, 295, 421, 29669,29670,295, 295, 295, 295, 161, 295, 679, 2270, 3551, 421, 5150, 29672,435, 161, 1217, 409, 295, 5398, 9207, 161, 295, 161, 421, 933, 29676,29677,434, 29679,679, 161, 161, 29683,29689, 29691,29692,29693,29694,29695,296, 2280, 29678,1851, 29680,29681,11652,29682,1036, 1036, 3002, 29684,304, 1851, 1343, 29686,1036, 29685,161, 1093, 29687,1036, 29688,29690,296, 1851, 5469, 2310, 295, 1028, 1351, 161, 5362, 29696,29697,29699,29711,29717,29724,1036, 29700,295, 29701,29702,29703,350, 296, 304, 750, 161, 161, 29704,29708,1042, 29705,1012, 29706,11799,1343, 29707,29709,29710,161, 1351, 1426, 434, 29712,29715,821, 29716,29713,29714,414, 1085, 295, 421, 161, 1036, 418, 696, 161, 29718,29722,4695, 29723,29719,29720,29721,5469, 295, 417, 29726,29729,29731,13972,29735,29738,1470, 29739,29740,2499, 2331, 29727, 29728,1173, 2234, 29730,29732,1198, 29733,29734,9522, 1305, 29736,29737,161, 2961, 9845, 161, 29741,29742,29743,29744,161, 29746,29747,16621,29749,29750,29751, 29753,29754,29759,29760,29767,29770,29775,25520,13414,418, 6717, 1555, 414, 29752,1161, 2272, 1364, 1093, 2082, 2082, 29755,409, 2040, 29758,29756,161, 29757, 5760, 1042, 29761,29762,29763,1042, 1280, 29764,6980, 1160, 1161, 161, 161, 161, 29765,29766,8351, 301, 3479, 3529, 29768,414, 161, 29769,1028, 161, 1060, 29771,29773,29774,29772,186, 304, 1042, 7961, 9952, 29777,29783,29790,29798,29804,1297, 29778,29779,1981, 29781,29782,2102, 1150, 420, 29780,2161, 1425, 4308, 8102, 161, 29784,29785,29788,3812, 29786,186, 1042, 8429, 161, 29787,3302, 6620, 186, 1876, 161, 4532, 29789,29791,29792,29793,29795,29797,10081,296, 295, 3103, 14306,29794,186, 161, 4226, 29796,20757,295, 29799,3169, 1938, 29802,29803,296, 29800,29801,161, 29805,28004,11960,29806,29807,4041, 29810,29808,29809, 29811,418, 29812,3063, 29814,4894, 29815,29819,6956, 29821,1498, 29823,29827,29816,29817,1023, 1608, 161, 29818,1042, 296, 4226, 29820,213, 3315, 29822,213, 29824,29825,29826,1042, 2190, 161, 29828,29829,161, 29830,29832,29833,29866,29876,29892,29904,29915,29916,29924,29925,29942,29943,29968,29987,3198, 30015,30026, 30030,30059,30064,30070,30071,30078,30080,30091,29834,3267, 29842,1609, 29843,29845,29847,29848,29850,1652, 1843, 29851,29854,29859,29862,29864,161, 1883, 29835, 29836,29837,29840,1093, 161, 15500,1143, 350, 1482, 29838,29839,29841,296, 304, 161, 29844,12153,1530, 418, 8921, 29846,7820, 1109, 5494, 716, 7320, 1042, 420, 161, 1603, 161, 1092, 418, 1756, 1092, 29849,414, 414, 304, 484, 213, 213, 29852,186, 17098,29853,6595, 1026, 161, 1490, 161, 976, 556, 3492, 161, 29855,29856,29857,12153,1823, 29858,414, 435, 186, 161, 414, 29860,1538, 1042, 418, 161, 5314, 29861,29863,1143, 5068, 29865,977, 679, 186, 29867, 1109, 29872,29873,29874,1470, 29868,29869,29870,29871,418, 161, 3002, 1044, 6127, 29875,418, 29877,29878,5956, 29879,29881,29884,29888,29889,295, 15035,29880, 29882,1208, 29883,414, 1608, 186, 3479, 213, 161, 29885,468, 213, 350, 29886,414, 161, 29887,1183, 213, 161, 1156, 1426, 1824, 29890,2900, 29891,556, 1759, 29893,3559, 417, 296, 29896,29897,29898,29900,29901,3959, 161, 161, 29894,1208, 29895,1778, 25700,437, 213, 29899,213, 420, 3954, 161, 161, 301, 1036, 29902,1425, 1042, 29903,1689, 1756, 414, 29905,29906,29907,29908,29909,1876, 29910,29911,29913,5875, 3770, 10047,29914,4542, 3922, 301, 408, 296, 2133, 161, 161, 20350,11294,686, 1426, 15887,9207, 4352, 1426, 9687, 161, 29912,213, 4058, 1036, 408, 3550, 7056, 186, 2333, 161, 2596, 417, 29917,1426, 295, 1303, 29920,29921,29923,29918,29919,301, 8921, 29922,350, 2094, 161, 1028, 1341, 301, 1087, 5469, 3002, 29926,29927,29933,29934,29935,29936,161, 29937,29939, 7241, 29940,29941,13369,1896, 29928,29932,1343, 295, 161, 3250, 29929,1093, 29930,29931,296, 21289,14508,11294,435, 295, 1885, 296, 5404, 1818, 29938,296, 161, 11693,1425, 1042, 4226, 3550, 29944,29945,29948,29951,29953,29956,29957,29961,687, 409, 445, 1085, 29963,29966,29967,5953, 418, 29946,29947,161, 29949, 1498, 161, 29950,186, 8455, 1343, 161, 29952,16564,3227, 29954,29955,25984,29958,29959,29960,295, 874, 161, 421, 161, 1054, 161, 421, 1418, 29962,418, 2020, 29964,29965,161, 296, 3959, 29969,29971,29972,29975,295, 29981,29983,29984,2053, 418, 29970,161, 5645, 1650, 4523, 1412, 29973,15513,302, 29974,301, 29976,418, 29979,161, 3421, 29977,29978,29980,1150, 29982,29985,304, 2516, 29986,29988,29991,29993,4038, 29995,29996,519, 29998,29999,30001,30006,1862, 29989, 161, 161, 29990,29992,1876, 678, 161, 29994,1087, 304, 161, 3250, 1087, 296, 4844, 10019,1772, 29997,161, 5865, 2191, 30000,161, 414, 1044, 30002,30003, 30004,1634, 30005,2283, 8746, 161, 161, 1756, 6264, 161, 6889, 556, 18271,8755, 936, 1216, 30007,30009,11958,30014,161, 435, 30008,1093, 716, 30010,7126, 30011,30012,30013,8755, 161, 30016,12811,408, 30019,1365, 30020,30023,30017,30018,1223, 4475, 30021,302, 418, 1459, 30022,161, 30024,2952, 30025,1341, 1862, 30027,30028,5299, 301, 1173, 161, 30029,8259, 4287, 12792,30031,2430, 30034,30035,9291, 30040,30042,1156, 1760, 30044,30046,30047,1109, 1459, 30051,30032,519, 30033,30036,30037,30038,161, 463, 161, 30039,30041,1342, 716, 161, 350, 435, 304, 414, 976, 1813, 30043,301, 301, 30045,1093, 716, 1245, 30048,9824, 418, 519, 161, 30049,161, 186, 30050,30052,30055,30056,414, 30053,30054,161, 1778, 1093, 161, 30057,1470, 4314, 1093, 2262, 30058,1042, 630, 519, 519, 440, 30060,30061,161, 1156, 295, 8136, 8136, 30062,30063,414, 161, 8521, 29191,161, 30065,161, 30066,30067,933, 17254,421, 678, 420, 296, 12859,1426, 4313, 30068,418, 161, 30069,1042, 161, 1303, 414, 161, 5469, 556, 414, 30072,30077,161, 30073,30075,161, 161, 161, 30074,12203,161, 750, 161, 13900, 2900, 30076,1093, 161, 1026, 4583, 414, 161, 1342, 213, 296, 2102, 21892,161, 222, 30079,13968,1042, 161, 304, 435, 186, 512, 630, 180, 2174, 1093, 628, 30081,1756, 1343, 161, 3603, 30082,30085,8730, 30086,30088,3799, 22062,30083,418, 30084,716, 3196, 30087,161, 512, 180, 30089,3440, 14127,1343, 1126, 30090,6528, 213, 435, 30092,296, 30093,17254,30094,161, 1886, 11294,296, 30095,30097,3944, 10356,30099,122, 30205,30265,30266,30319,30320,30322,30368,30386, 30387,30414,30100,30101,30104,30110,30116,30117,30118,30119,30121,30122,30130,30135,30145,22682,30162,30168,30171,30192,30193,30195,30197,30201,161, 30204,1604, 11670,304, 30102,30103,350, 414, 1242, 161, 30105,30106,3267, 1042, 30108,161, 30107,350, 3267, 417, 30109,30111,1134, 30113,4872, 30114,3959, 3959, 161, 418, 316, 1467, 30112,418, 161, 30115,343, 1042, 161, 161, 296, 5225, 1343, 9437, 418, 30120,186, 409, 1097, 30123,30129,296, 161, 4108, 161, 1093, 30124,2306, 30125,30126,30127,30128,161, 1418, 1097, 301, 30131,161, 30132,30134,161, 1862, 409, 26631,30133,161, 2585, 418, 30136,30138,30139,30140,30141, 3315, 30142,1093, 1883, 30137,161, 3518, 161, 3198, 301, 1877, 1126, 3025, 30143,30144,350, 30146,30147,30148,30150,30153,30157,30158,440, 30159,4203, 9540, 30160,161, 5228, 1343, 161, 161, 301, 161, 161, 414, 161, 301, 4814, 161, 30149,30151,222, 30152,30154,30155,30156,1060, 304, 301, 2374, 301, 296, 11703,1343, 161, 1028, 418, 17314,11819,30161,161, 161, 1143, 30163,30167,440, 8065, 161, 213, 519, 30164,30166,30165,30169,1590, 30170,1150, 301, 1028, 30172,3397, 30173,30176,30177,30178,30179,30181,30183,30189,9918, 1459, 7586, 161, 5089, 30174,435, 161, 161, 161, 30175,304, 161, 1097, 301, 6153, 556, 301, 161, 20102,213, 1042, 161, 161, 161, 19283,474, 30180,6153, 30182,30184,30185,2864, 30186,417, 5248, 161, 3002, 4257, 161, 30187,30188,5146, 5061, 30190,30191,440, 295, 30194,1851, 12534,750, 161, 420, 161, 161, 25228,9069, 30196,161, 301, 301, 1498, 30198,15478,4308, 30199,30200,414, 30202,27855, 161, 519, 161, 30203,9493, 295, 414, 1060, 301, 304, 301, 301, 301, 9040, 15901,30206,30208,30210,30212,30215,30219,30221,30222,30225,30234,30236,30242, 30249,1480, 30261,161, 2434, 30262,161, 9207, 1343, 1474, 1343, 161, 30207,18551,161, 296, 418, 161, 418, 161, 30209,30211,6083, 161, 30213,30214,3959, 161, 301, 213, 28787,350, 414, 23699,7044, 30216,30217,409, 30218,414, 5065, 6723, 30220,4169, 1895, 295, 1595, 1363, 996, 30223,161, 30224,1093, 161, 30226,30228,4169, 161, 27639,301, 30229,1343, 2190, 296, 161, 1843, 14628,14100,30233,161, 1890, 30227,418, 1482, 3729, 161, 30230,5246, 10626,30232,1343, 161, 1060, 161, 30231,161, 1060, 161, 556, 519, 1635, 1876, 30235,2755, 2755, 1639, 30237,4391, 161, 30240,30241,301, 417, 30238,30239,30243,24560,30245, 4168, 30247,30244,1143, 30246,1143, 5789, 1143, 30248,161, 30250,30251,14189,30253,30254,161, 30256,5768, 30257,301, 30258,19447,10016,1899, 30252,8158, 2221, 1126, 213, 1895, 6350, 7522, 161, 26509,301, 30255,304, 301, 11409,418, 420, 1028, 1143, 3829, 30259,30260,4971, 421, 161, 3250, 30263,30264,16918,30267, 421, 30268,30269,30271,161, 10968,4041, 30275,30280,30287,30293,30302,30307,30309,30315,707, 30318,2197, 213, 440, 161, 30270,161, 30272,21945,426, 30274, 161, 296, 30273,414, 1538, 1042, 1756, 161, 161, 161, 213, 30276,409, 1093, 296, 30277,30278,30279,30281,15341,3215, 936, 30284,30286,161, 30282,301, 16071,30283,30285,418, 161, 30288,30289,1230, 30290,4465, 1150, 161, 3261, 161, 18923,161, 435, 2596, 556, 295, 161, 30291,30292,414, 1093, 14094,30294, 30296,30298,417, 30299,1161, 30295,1304, 30297,1042, 161, 161, 30300,30301,30303,30304,1042, 5909, 30305,18992,10253,161, 18563,222, 213, 1343, 161, 161, 161, 30306,161, 30308,295, 414, 11574,161, 23251,30310,30312,30313,30314,9299, 30311,1757, 740, 5122, 5589, 161, 30316,440, 213, 30317,161, 17124,1042, 161, 23297,5122, 30321,8483, 420, 30323,304, 30324,30326,30327,30328,4322, 707, 30330,30332,30333,30335,30337,30343,30350,30353,30354,30361,30363,30325,14048, 2333, 440, 161, 414, 5552, 8181, 13731,30263,1851, 161, 30329,295, 295, 16451,1403, 296, 414, 30331,10479,161, 2602, 161, 30334,301, 23592,679, 161, 3529, 30336,2340, 4313, 874, 161, 414, 161, 30338,30340,26519,161, 3833, 30339,30341,30342,161, 30344,161, 30345,2050, 161, 161, 1938, 4406, 30346,30348, 28328,1042, 1563, 1756, 3227, 30349,161, 1735, 161, 3730, 30347,1305, 24124,2976, 6577, 30351,30352,414, 16117,161, 1093, 1639, 409, 30355,161, 161, 304, 2040, 30356,30360,9172, 161, 1426, 30357,30358,30359,30362,1042, 426, 161, 2040, 161, 161, 414, 6300, 14562,4382, 4108, 30364,3440, 1876, 30365,161, 678, 3771, 30366,4407, 161, 161, 2663, 5054, 161, 30367,30369,30371,30374,30379,30382,3315, 295, 30370,421, 30372,1530, 29634,161, 1198, 30373,161, 3452, 295, 3769, 435, 933, 30375,30377,1818, 30376,414, 1938, 1042, 161, 30378,421, 304, 679, 8136, 30380,30381,295, 30383,26586,161, 30385,30384,161, 30388,30389, 5494, 3551, 30390,30391,30395,161, 3861, 30396,30398,30401,161, 30403,30404,30407,30408,30413,161, 1418, 350, 26243,418, 3544, 1891, 7000, 161, 2596, 30392, 1143, 1895, 30393,16451,30394,1042, 1160, 161, 161, 1899, 301, 1990, 296, 296, 161, 1343, 30397,161, 30399,30400,420, 5469, 4511, 635, 4883, 161, 186, 304, 161, 30402,161, 19467,186, 1042, 161, 440, 1343, 30405,30406,161, 678, 678, 4108, 30409,161, 30410,933, 1426, 1042, 30411,161, 30412,4922, 1885, 1042, 30415,1756, 30416,30417,30419,30423,30430,30449,30465,30466,30486,30489,30511,30538,30561,30562,30565,30568,30574,30579,161, 30589,30603,30604,30609,30418, 2182, 161, 5207, 30420,30422,2430, 1899, 435, 30421,213, 4308, 5671, 311, 30424,30428,1223, 6714, 30425,30426,161, 30427,3435, 30429,304, 15677,1756, 1093, 414, 2040, 161, 350, 414, 30431,30434,30438,355, 1042, 4883, 1343, 9233, 2094, 301, 161, 1639, 30432,30433,1640, 1184, 30435,304, 30437,30436,414, 13717, 30439,1824, 161, 6165, 30442,295, 30444,13897,30445,30447,296, 30440,30441,1143, 16770,30443,30446,30448,3550, 5494, 414, 30450,30454,30456,30457,295, 30458, 30460,30462,1044, 30463,5469, 30451,1896, 30452,30453,161, 30455,1899, 5398, 435, 16097,15439,4883, 4218, 1844, 15958,30459,3827, 418, 1823, 161, 161, 30461, 301, 7859, 418, 2318, 161, 30464,3267, 1297, 8004, 1223, 435, 15439,30467,30468,295, 30472,30474,30484,30485,686, 1184, 1042, 161, 30469,186, 4308, 1756, 30470,30471,30473,418, 185, 30475,1208, 30478,30481,161, 30476,30477,414, 186, 1042, 1270, 161, 30479,556, 30480,1297, 6055, 414, 30482,161, 13298,30483, 3405, 519, 414, 30487,30488,1935, 2016, 30490,30491,30494,30496,2367, 21852,301, 30498,30499,30500,3766, 1093, 3728, 417, 30502,2576, 519, 161, 30492,30493, 1012, 161, 8181, 1343, 3510, 30495,30497,161, 29159,1189, 13423,161, 418, 161, 1609, 161, 4470, 30501,1343, 5368, 161, 161, 468, 1343, 1756, 30503,30504, 30510,1173, 30505,30508,161, 161, 1208, 1012, 1813, 3729, 1343, 5069, 30506,30507,5481, 161, 3153, 6194, 8181, 1100, 30509,296, 26365,30512,30513,5552, 30514, 883, 30516,22161,440, 30519,30523,30527,5715, 30533,1050, 420, 161, 30515,1595, 1242, 161, 417, 1087, 418, 418, 30517,3167, 30518,1109, 417, 30520,2901, 30522,161, 30521,414, 8191, 414, 30524,414, 161, 161, 414, 30525,30526,304, 1177, 417, 161, 414, 3724, 1793, 2094, 30528,30529,6405, 30530,1732, 30531, 30532,161, 161, 161, 350, 1156, 1317, 213, 2469, 30534,30535,30537,30536,1425, 1156, 296, 30539,740, 30540,30543,30546,3198, 30547,301, 30555,426, 30556, 30557,30558,30559,161, 417, 1431, 1161, 161, 186, 1173, 30541,2107, 420, 2283, 435, 30542,13610,463, 30544,161, 30545,1101, 1087, 161, 213, 30548,30549, 161, 1876, 30550,161, 30553,161, 1981, 1054, 30551,1042, 161, 161, 161, 30552,161, 1608, 3194, 418, 30554,1143, 608, 430, 161, 296, 3098, 1042, 295, 161, 161, 17399,1042, 2191, 161, 6113, 161, 706, 30560,6744, 7835, 316, 1093, 27204,304, 30563,30564,161, 19777,1042, 304, 30566,30567,30569,213, 30570, 30571,1342, 30572,30573,1365, 2362, 161, 16350,16350,161, 161, 2058, 161, 5537, 414, 1813, 11045,1980, 295, 7430, 418, 30575,30576,435, 750, 2024, 161, 996, 30577,2748, 161, 8067, 30578,30580,8135, 30581,30582,1813, 295, 161, 301, 10536,30583,30586,9080, 30584,30585,418, 30587,30588,186, 1732, 30590,30600, 30602,3778, 161, 30591,30592,30593,30594,30595,30596,30597,30598,30599,414, 30601,161, 213, 161, 2453, 421, 7848, 30605,15887,30606,10350,30607,420, 1216, 161, 12203,161, 750, 30608,1421, 30610,1042, 4513, 1421, 161, 5954, 1962, 161, 161, 30612,1012, 426, 1842, 4218, 1843, 1845, 30614,30627,30646,15181,1824, 30662,30672,30675,30677,1995, 30615,30617,1343, 1184, 30618,30619,30620,30621,161, 30616,414, 7990, 4898, 19412,301, 6131, 1093, 1635, 30622,1042, 30623,161, 1093, 161, 30624,30625,30626,1184, 30628,30630,30633,30636,30638,30641,30642,30645,30629,414, 161, 1093, 161, 30631,30632,30634,1474, 5722, 30635,435, 1092, 30637,30639,30640,30643,30644,2161, 1302, 1042, 161, 30647,30648,4284, 679, 1042, 30649,30652,30653,30655,30657,161, 30659,30661,161, 304, 3827, 1459, 311, 1962, 519, 161, 414, 30650,1145, 417, 30651,3654, 4308, 1343, 161, 1116, 2225, 414, 3102, 1756, 30654,161, 20569,161, 30656,161, 1885, 6541, 1042, 161, 30658,1093, 426, 30660,296, 2367, 30663,9984, 30664,30665,30666,4352, 30667,30668,418, 4218, 519, 28382,30669,30670,30671,7859, 30673,30674,1365, 2331, 30676, 421, 19904,1297, 1093, 414, 30678,1060, 24408,30680,3778, 30683,30684,30687,30679,30681,3928, 30682,30685,30686,30688,1823, 30689,30713,30729,30747,30764,30774, 30690,30691,1387, 30692,418, 30693,30695,30698,9472, 30700,30703,30709,4583, 30712,1883, 161, 11891,1459, 296, 350, 304, 295, 161, 8067, 30694,161, 30696, 2225, 161, 16858,1280, 30697,161, 30699,9321, 11961,30701,426, 418, 30702,161, 161, 1060, 161, 9704, 418, 2283, 30704,30705,30706,30707,30708,186, 2165, 1042, 30710,30711,161, 161, 1093, 213, 161, 421, 161, 1418, 1938, 2283, 1583, 26547,1042, 7000, 161, 417, 30714,30718,30721,295, 13388,30727,30728,350, 20022,3811, 426, 161, 2449, 30715,30716,161, 30717,1297, 30719,161, 30720,161, 1343, 30722,30725,30726,1426, 1876, 1756, 20620,161, 30723,23756,30724,2901, 161, 295, 295, 484, 30730,30733,4883, 30734,295, 30737,30740,30743,30746,2183, 20075,30731,1042, 30732,296, 296, 30735,30736,9955, 213, 30738,2472, 3776, 30739,161, 3302, 26720,30741,30742,296, 1042, 11961,1143, 296, 1042, 161, 30744,30745,161, 6001, 1876, 296, 21804,30748,30749,30750,3095, 30752,30753,933, 30754,30755,30757,30758,30759,30761,13579,161, 161, 8162, 30751,1028, 3827, 2442, 161, 608, 311, 30756,27316,296, 30760,421, 6520, 30762,3479, 30763,213, 161, 161, 30765,30766,301, 30768,30769,30770,30772,30773,30767,161, 1538, 4293, 161, 418, 295, 3267, 1270, 1280, 161, 30771,1116, 296, 24061,1851, 161, 295, 1787, 30776,30785,30793,30806,30808,30830,3153, 599, 30832,30837,30777,750, 435, 30780,30783,2894, 7091, 161, 30778,30779,5238, 1042, 161, 161, 30781, 30782,4293, 1343, 5552, 161, 1609, 213, 161, 30784,161, 1757, 6050, 161, 161, 161, 30786,30787,30788,30789,30792,161, 161, 2040, 420, 440, 3102, 301, 295, 30790,295, 30791,14048,1042, 161, 161, 1590, 2082, 30794,30795,30797,30798,213, 1027, 161, 161, 30796,1876, 1107, 1297, 6066, 161, 30799,440, 30804, 161, 30805,161, 414, 161, 10039,30800,3013, 161, 30802,3959, 161, 161, 30801,161, 5751, 1343, 161, 30803,30807,421, 421, 161, 30809,30813,30818,30823, 30827,1059, 30810,5763, 161, 161, 161, 1093, 30811,252, 30812,161, 30814,30815,18751,301, 30816,161, 30817,30819,30821,30820,296, 30822,161, 4773, 6309, 213, 30824,30825,30826,30828,30829,30831,2442, 30833,30834,418, 13922,679, 409, 30836,1284, 3959, 30835,1756, 3959, 213, 161, 1426, 9458, 304, 295, 30838, 30840,30853,30861,30872,30894,30899,3769, 355, 26586,3169, 30841,30844,30847,30848,30850,27148,161, 30852,30842,1042, 414, 161, 2868, 30843,161, 418, 30845, 30846,1538, 1042, 304, 161, 161, 213, 1297, 161, 30849,30851,30854,30856,30858,30859,161, 30855,213, 3439, 1876, 161, 161, 1583, 30857,1824, 295, 161, 1297, 1405, 8431, 30860,417, 296, 8431, 4308, 30862,4883, 30863,30864,30866,30868,30869,30865,295, 9478, 1876, 15715,161, 30867,1042, 213, 30870,30871,29452, 30873,304, 30875,30878,7911, 350, 295, 30880,30874,9290, 11984,161, 30876,30877,1376, 304, 296, 161, 2088, 5065, 1042, 252, 1093, 1042, 30879,1555, 414, 30881,30887,1109, 186, 30888,1042, 30891,30893,161, 30882,3518, 1143, 30884,30883,421, 30885,30886,350, 1126, 1640, 30889,30890,418, 418, 30892,30895,421, 30896,30897,30898,414, 30900,30903,30910,30926,30932,30934,30939,30940,30941,30944,30946,30950,30977,30999,31013,31020,3134, 31026,31043,31044,31053,31075,31083, 31085,301, 1555, 30901,30902,5552, 1042, 161, 161, 1270, 1460, 30904,30905,304, 30906,30908,161, 414, 161, 24028,161, 30907,161, 7395, 2538, 421, 30909, 463, 222, 30911,7354, 30915,30924,2000, 748, 30912,30913,30914,30916,30919,30920,1150, 30917,30918,213, 22062,304, 1405, 2558, 30921,30922,30923,30925,161, 1639, 1093, 1270, 30927,222, 1458, 30931,161, 161, 30928,30929,1842, 7819, 30930,1343, 1843, 1845, 1013, 1847, 1042, 185, 295, 435, 30933,414, 1093, 295, 301, 30935,30937,30936,2750, 3302, 30938,304, 213, 1756, 5069, 5481, 161, 5163, 161, 350, 30942,1199, 161, 30943,161, 301, 30945,30947,30948,30949,4511, 679, 608, 2539, 30951,1012, 30955,30956,2471, 30959,30969,30971,2890, 30973,30974,30975,304, 161, 30952,30953,414, 30954,1367, 7566, 295, 161, 740, 435, 213, 30957,14194,161, 30958,30960,30963,161, 30965,1343, 161, 30967,1343, 161, 30961,30962,1212, 186, 1042, 296, 30964,28004,30966,30968,2196, 686, 30970, 414, 2915, 161, 19744,1093, 161, 3766, 161, 30972,635, 519, 1797, 1754, 818, 414, 414, 161, 222, 1335, 30976,1042, 30978,30979,30993,30997,296, 3049, 426, 30980,635, 1727, 30983,30985,30986,161, 30987,30991,30981,30982,30984,418, 1657, 161, 4179, 4164, 30988,7933, 30989,556, 30990,414, 25997,213, 30992, 350, 4226, 161, 30994,30995,161, 30996,161, 4164, 16321,1895, 30998,414, 31000,31001,414, 31002,31005,301, 426, 31006,31008,21005,161, 11574,31011,31012, 29841,686, 2471, 301, 414, 296, 161, 31003,31004,161, 1093, 296, 31007,295, 11574,1060, 161, 16490,161, 1093, 31009,2580, 301, 301, 31009,31010,161, 419, 2101, 161, 31014,31015,1342, 31017,1387, 1604, 414, 31016,4316, 350, 296, 31018,1093, 1042, 31019,296, 31021,519, 1036, 21354,161, 296, 31022,11045, 161, 1050, 31023,31024,31025,31027,31028,31031,1080, 31035,1036, 31036,426, 161, 31037,31038,31042,1459, 31029,31030,31032,31033,31034,161, 10778,2453, 161, 161, 3637, 304, 301, 6891, 161, 31039,295, 31041,1060, 31040,9845, 161, 1538, 14609,1818, 161, 161, 2362, 13964,222, 3827, 9762, 1823, 31045,31046,31049, 2309, 31047,31048,31050,31051,31052,31054,3922, 686, 31055,976, 4327, 31057,31059,10047,31062,31063,31064,31073,301, 3778, 304, 161, 31056,304, 1539, 414, 1143, 1093, 3198, 31058,1756, 7416, 161, 31060,1173, 1538, 1042, 213, 6377, 31061,3780, 2610, 186, 15024,161, 296, 31065,31066,4217, 3680, 31067,13922,304, 3479, 31069,2195, 4108, 31071,435, 26575,186, 468, 31068,161, 31070,1899, 4898, 161, 1143, 31072,420, 3276, 31074,16071,161, 31076,31081,31082,31077,31078, 31079,31080,20720,3922, 31084,1412, 14638,304, 161, 31086,31087,31122,31170,31173,31204,31205,31219,31247,31277,31283,31088,31092,1101, 4733, 31095,4203, 31097, 304, 31100,2115, 31102,31106,31117,31118,31119,1534, 295, 31121,31089,31090,31091,3435, 1891, 161, 1343, 4322, 8074, 161, 556, 936, 31093,1134, 1754, 161, 31094,519, 1367, 1173, 7990, 31096,10575,6950, 161, 31098,31099,1270, 31101,161, 1590, 1126, 10039,2053, 31103,31104,31105,1851, 1093, 161, 14380,8581, 1739, 4364, 409, 1538, 1042, 304, 252, 31107,31109,31111,24408,1042, 31112,14287,31114,304, 31115,31108,519, 161, 31110,161, 1302, 1042, 31113,1343, 161, 252, 1876, 6415, 31116,161, 161, 936, 3964, 1093, 1415, 818, 1093, 161, 1087, 295, 15413,161, 31120,161, 31123,31129,161, 31148,21535,31154,31155,31157,31161, 409, 2449, 3770, 31167,31168,161, 31124,31127,1036, 31125,1876, 1343, 161, 447, 31126,31128,1876, 26002,161, 10473,161, 350, 31130,31139,161, 31140,31144, 31131,414, 31135,12217,31132,161, 161, 31133,161, 213, 161, 1128, 31134,213, 2817, 31136,161, 31137,186, 31138,161, 1242, 7047, 1042, 311, 1343, 31141, 296, 296, 31143,31142,2147, 1198, 30553,4971, 161, 1459, 31145,31146,31147,161, 1586, 31149,31151,1150, 1829, 1093, 161, 161, 31150,161, 1706, 31152,31153, 161, 635, 161, 4083, 161, 2270, 434, 296, 10076,1093, 295, 31156,1343, 161, 414, 15493,1538, 1042, 7522, 161, 31158,31160,2280, 161, 5150, 161, 31159, 1426, 1876, 5954, 161, 31162,31164,1270, 1097, 31166,213, 31163,31165,31169,1823, 31171,31172,31174,31176,31179,28004,31183,31186,31188,31190,31196,2449, 31201, 31203,31175,295, 1161, 1793, 161, 31177,304, 3250, 31178,161, 414, 161, 1956, 161, 161, 31180,295, 31182,3770, 1093, 409, 2449, 31181,1012, 296, 1343, 31184,350, 295, 31185,31187,1042, 161, 414, 161, 161, 161, 161, 161, 304, 295, 31189,1343, 161, 1126, 3631, 1042, 1343, 161, 31191,31192,31193,1093, 31195,7146, 4105, 7337, 161, 435, 9912, 31194,1757, 2390, 1547, 1042, 31197,414, 31198,519, 161, 11583,31199,414, 186, 161, 31200,10175,2109, 295, 161, 31202,1851, 296, 1876, 4883, 4898, 3447, 31206,31210,31215,8676, 31207,31209,418, 31208,186, 1042, 1962, 161, 418, 31211,31212,31213,296, 31214,1405, 679, 31216,31218,31217,4640, 14287,24330,1109, 31220,4733, 418, 31221,31224,295, 31226,31237,31239,31242,31243,31246,31222,1538, 1042, 31223,1126, 31225,161, 29215, 31227,31228,31231,31234,31229,161, 31230,161, 2848, 295, 31232,31233,186, 1042, 8074, 31235,31236,304, 11617,161, 31238,161, 11045,296, 3198, 1042, 161, 1134, 161, 31240,1042, 161, 31241,161, 5053, 4291, 1228, 1228, 31244,161, 161, 31245,1757, 10075,3439, 1042, 161, 161, 17128,1297, 31248,31258,31264,31273, 31275,3689, 161, 3559, 31249,31251,31253,1534, 295, 933, 31255,1990, 31256,161, 16820,31257,31250,31252,213, 1876, 31254,1184, 426, 304, 434, 1851, 1150, 31252,161, 3559, 186, 1042, 161, 31259,2270, 1028, 304, 24772,31263,31260,296, 1042, 31261,31262,295, 2457, 31265,933, 31266,31272,1093, 2851, 31267,31271, 5253, 414, 296, 16262,1042, 31268,4407, 31269,31270,418, 16112,750, 350, 31274,3250, 7720, 304, 2040, 304, 1640, 1640, 31276,1093, 31278,31279,19904,3847, 2225, 6124, 31280,31281,31282,252, 161, 1876, 420, 31285,31286,31293,31297,6643, 31287,31288,31290,27980,31291,5854, 31292,19313,414, 31289,161, 1228, 418, 186, 161, 296, 8622, 1042, 1107, 2855, 31294,31296,4432, 31295,1555, 5246, 296, 1042, 161, 161, 2362, 2817, 1010, 161, 1426, 31298,31299,5780, 31302,31303, 31300,31301,1093, 1297, 7126, 2420, 31305,31313,1109, 31306,31310,31307,31308,31309,31311,31312,421, 301, 296, 25057,31315,31318,31316,31317,31319,31320,31321, 31322,128, 31456,31460,129, 31577,31578,31581,31584,31624,31628,31630,31633,31706,130, 350, 31819,31821,3013, 31866,31323,31324,31330,31337,31339,31340,31342, 31347,31350,31355,31357,31363,31369,31411,31414,31432,31434,31446,31450,31455,1116, 31325,31328,161, 31326,161, 31327,213, 8612, 31329,31331,31332,1500, 31336, 1060, 31333,1173, 31334,31335,608, 421, 484, 1538, 2165, 1042, 3178, 161, 31181,1458, 295, 31338,161, 295, 15887,2280, 5469, 1150, 5751, 31341,414, 3439, 1143, 1230, 31343,1042, 6678, 31345,31344,161, 31346,296, 2741, 350, 519, 31348,161, 31349,874, 1343, 17124,161, 2442, 161, 161, 161, 295, 31351,31353, 31352,186, 1756, 161, 161, 420, 2161, 4382, 9044, 161, 31354,213, 12153,31356,1042, 161, 161, 3368, 161, 161, 4327, 295, 31358,418, 31359,31360,7000, 295, 440, 3013, 295, 1260, 161, 1143, 4327, 14048,31361,1343, 1126, 31362,1885, 1889, 1092, 3153, 1885, 31364,2283, 213, 3267, 1590, 31365,31366,31367,414, 31368,1042, 440, 161, 414, 31370,31371,31372,31373,15341,31384,31385,2674, 31386,31389,31405,31407,161, 15172,31408,417, 31409,31410,3479, 186, 635, 420, 3446, 161, 7961, 4396, 414, 31374,213, 31376,31378,440, 31379,24157,31382,31383,933, 295, 31375,295, 161, 31377,29293,1418, 409, 31380,31381,3974, 6033, 414, 16097,1036, 161, 29293,31387,161, 31388,2537, 161, 9732, 14167,414, 31390,31391,31392,31393,31394,31395,31396,31397,31398,31399,31400,31401,31402,31403, 31404,31406,161, 11870,7961, 3098, 161, 9409, 435, 31412,161, 31413,161, 161, 161, 417, 161, 414, 343, 180, 31415,31416,31418,1425, 31419,31421,1876, 31422,31423,6393, 31425,31426,31427,31430,31431,5005, 4396, 1143, 1161, 6166, 1851, 161, 31417,31420,2548, 435, 295, 414, 1635, 1026, 22232,161, 1305, 31424, 13389,1418, 295, 304, 414, 26995,16391,161, 31428,213, 1824, 31429,161, 3439, 1876, 1405, 3954, 161, 933, 1212, 1208, 1876, 1555, 31433,519, 222, 2102, 2449, 4898, 161, 161, 421, 31435,31437,31438,295, 418, 161, 417, 31444,161, 414, 161, 1609, 213, 1303, 161, 3771, 31436,2195, 2196, 1905, 4164, 161, 556, 31439,31441,31440,414, 1608, 23342,161, 31442,31443,1608, 1054, 161, 304, 161, 31445,1093, 1608, 31447,31448,31449,408, 296, 418, 304, 26631,295, 1818, 31451,31453,31452,31454,1538, 1876, 295, 874, 161, 417, 31457,20178,31459,296, 31458,31461,31469,31470,1829, 31472,31481,31491,31493,31495,31512,31514, 31524,31526,31535,31554,31568,31569,31570,31572,9283, 2523, 31462,3302, 31463,31464,31465,414, 31468,2951, 1604, 1242, 1343, 1639, 161, 2165, 440, 161, 296, 1876, 161, 14125,1538, 1042, 31466,161, 31467,295, 31471,4364, 16699,1343, 5239, 304, 31473,31475,31479,31480,6083, 2732, 31474,1343, 161, 161, 31476,14513, 31477,1013, 31478,186, 1198, 1938, 1208, 1042, 161, 31482,31486,31483,414, 1792, 31484,31485,608, 161, 414, 9734, 31487,31488,161, 295, 161, 161, 31489, 161, 161, 13274,31490,418, 6238, 418, 1640, 31492,1343, 295, 31494,161, 161, 5254, 31496,296, 31497,1297, 13840,31501,484, 31498,31499,31500,2174, 31502, 31505,31506,31507,465, 31509,304, 31510,28775,28742,420, 31503,31504,2270, 696, 31508,678, 4078, 14947,3527, 161, 4233, 1372, 31511,31513,31515,31519,31520, 295, 31516,1818, 8181, 1042, 31517,2094, 161, 31518,31521,31522,161, 31523,3850, 418, 295, 1280, 31525,161, 31527,409, 295, 161, 4987, 31529,31530,1843, 31534,434, 31528,5824, 556, 5061, 414, 31531,31532,31533,3479, 418, 4448, 1846, 31536,31538,31539,31541,31542,31547,31548,31551,31552,1026, 31553,31537,5047, 421, 4089, 31540,1060, 15251,2269, 1143, 31543,31545,1126, 1107, 1126, 31544,161, 7289, 161, 161, 161, 31546,414, 161, 304, 1026, 14582,296, 31549,31550, 2270, 213, 2538, 1208, 1876, 161, 31555,31556,31562,161, 31564,6393, 31565,2931, 31567,161, 11385,31557,31558,31559,31560,1093, 31561,1087, 556, 31563,1217, 1876, 1012, 296, 31566,161, 1459, 15932,295, 3399, 15011,161, 1899, 295, 421, 296, 31571,28844,414, 161, 1025, 4583, 16315,31573,9282, 31576,31574,31575, 1050, 1430, 161, 4528, 1899, 31579,4335, 8676, 31580,31582,31583,31585,4707, 31589,31592,31595,301, 31597,31605,31609,31615,31618,31621,31623,161, 4105, 31586, 31587,1876, 31588,1343, 440, 161, 933, 161, 185, 8405, 295, 4482, 31590,161, 31591,304, 10078,31593,31594,295, 1538, 295, 161, 161, 2056, 1042, 1640, 1426, 2082, 295, 31596,31598,31599,417, 31604,1639, 161, 14962,31600,4638, 31601,1343, 1756, 1026, 31602,11960,1184, 31603,1640, 31606,434, 14687,417, 31607, 31608,16708,161, 414, 31610,31612,31613,31614,10339,417, 1026, 31611,8161, 3098, 414, 296, 1042, 440, 635, 161, 161, 414, 296, 1876, 161, 252, 161, 31616,31617,31619,936, 1270, 31620,161, 186, 1042, 4979, 2894, 31622,161, 161, 4987, 9912, 161, 16989,28744,31625,1343, 4927, 31626,418, 18792,750, 31627, 706, 31629,18795,4535, 8049, 31631,16639,31632,304, 426, 161, 31634,31635,31646,31648,31650,31651,31653,31654,31655,31657,31662,31666,31677,31679,31682,409, 31695,31698,31702,31703,3046, 1883, 304, 296, 31636,414, 31637,31638,22108,31640,28616,31641,5251, 440, 1343, 295, 31645,31383,417, 1639, 161, 31639,1937, 9274, 31642,31643,31644,31407,31647,1223, 31649,295, 296, 7064, 14242,296, 4987, 304, 295, 31652,161, 295, 1097, 418, 31656,304, 440, 1028, 222, 161, 31658,161, 31659,161, 296, 4638, 15124,31660,31661,31663,750, 5163, 31664,31665,414, 4183, 161, 31667,1036, 11655,1459, 161, 414, 161, 207, 31668,31670, 161, 31671,1270, 1343, 31672,31673,31674,161, 3518, 14261,31669,11336,2442, 7000, 31533,4366, 31533,1459, 9987, 2166, 9207, 5831, 31675,31676,5239, 1198, 31678, 15229,2040, 414, 213, 29473,295, 350, 977, 31680,161, 4263, 1173, 31681,1876, 5149, 161, 1938, 5483, 12238,31683,31685,31686,1093, 1107, 31688,6949, 31690, 5253, 1949, 31691,414, 161, 4285, 31684,161, 161, 1093, 2514, 31687,414, 161, 31689,1042, 161, 161, 161, 414, 2161, 2050, 161, 31692,31693,31694,409, 295, 31696,161, 31697,161, 31699,9207, 31700,414, 31701,27516,27516,161, 27516,161, 295, 7961, 1818, 31704,31705,161, 435, 414, 3198, 430, 18799,31707, 31749,31770,31785,31802,31815,31817,31708,31710,31712,31713,2183, 440, 4482, 31722,31728,31729,31731,31736,31740,31744,31748,2252, 696, 1343, 31709,4513, 296, 3267, 31711,31714,5248, 31720,31715,31716,5368, 31717,31718,161, 5248, 161, 350, 31719,31721,161, 421, 31723,213, 31724,161, 4511, 1012, 186, 161, 31725, 31726,25223,213, 1756, 1054, 350, 31727,1042, 161, 3113, 161, 1756, 1116, 161, 2241, 31730,2241, 4364, 3144, 31732,3267, 414, 1343, 161, 31735,414, 31733, 31734,161, 9129, 414, 350, 1121, 1036, 409, 161, 31737,31739,31738,2283, 1818, 1851, 31741,31742,31743,31745,295, 161, 1851, 2270, 31746,3227, 31747,296, 8181, 1042, 161, 31750,31752,4737, 304, 31754,304, 31758,31759,1727, 31760,31761,161, 31766,31768,434, 418, 161, 3770, 31751,295, 686, 1042, 7517, 161, 31753,161, 1851, 31755,414, 1824, 296, 13816,1343, 161, 161, 31756,31757,161, 9243, 679, 976, 3479, 161, 2028, 1270, 31762,304, 18305,31763,31764,31765, 161, 31767,1302, 3198, 1042, 31769,31771,31772,1904, 31776,304, 31777,31778,31781,31782,1121, 1150, 1270, 31773,31775,4203, 31774,185, 4203, 418, 468, 16329, 14230,2333, 161, 31779,296, 31780,1604, 1042, 161, 161, 21182,1895, 1230, 1042, 14287,6403, 161, 15036,161, 31783,31784,31786,421, 4883, 19739,31787,6367, 31789,2102, 6217, 31793,31794,31795,29653,2854, 31797,31798,31800,31801,2142, 3250, 1036, 31788,161, 31790,31791,31792,14913,1012, 1818, 304, 1843, 608, 295, 408, 31796,5552, 3799, 3113, 213, 161, 14411,31799,1426, 1371, 161, 409, 1036, 304, 996, 3850, 1343, 31803,31804,434, 31808,31809,31811,5999, 933, 414, 31813,304, 421, 12195,31805,31806,31807,31810,295, 31812,2082, 161, 31814,161, 31816,295, 22948,295, 31818,31820,31822,31824,31829,31833,31839,31844,296, 750, 31847,31848,31851,31854,31858,31863,31823,31825,296, 161, 1208, 1184, 31826,31827,161, 161, 31828,31830,31831,31832,161, 1343, 161, 31834,31835,31837, 1474, 1343, 31383,161, 1297, 7000, 31836,161, 1302, 31838,1539, 295, 31840,3730, 304, 31841,31842,31843,31845,414, 1538, 1042, 31846,161, 161, 1851, 1949, 31849,3731, 31850,1883, 2817, 161, 2880, 5089, 161, 161, 161, 161, 161, 161, 5529, 31852,31853,15341,31855,31856,301, 296, 161, 31857,161, 3799, 463, 213, 409, 31859,31860,1107, 7783, 3315, 31862,18519,18519,161, 31861,9824, 435, 418, 1093, 31864,13226,295, 1829, 31865,3529, 3529, 4164, 409, 1426, 31867, 11294,1184, 31869,161, 161, 1425, 31868,414, 1889, 4730, 31870,31871,161, 31872,31873,31951,31963,31969,31972,31977,1818, 31983,31985,31988,31998,32018,32040, 32042,32092,32118,32134,32143,32145,32146,2748, 4570, 304, 519, 31874,31876,31879,31887,296, 31889,21991,31890,31895,31901,31907,31909,31913,31916,31939,31945, 31948,3274, 161, 3049, 355, 31875,31877,1042, 31878,31880,31881,31885,31882,31883,31884,31886,31888,1208, 1087, 31891,31894,31892,31893,414, 186, 1231, 161, 31896,31899,31897,434, 31898,31900,4432, 161, 1086, 31902,31903,31905,31904,311, 12303,18010,31906,31908,31910,31911,31912,31914,31915,31917,31920,31928,31931, 31918,31919,304, 1334, 31921,31927,31922,31923,31924,31925,31926,31929,1425, 31930,161, 1626, 414, 304, 31932,31937,31933,437, 1198, 31934,31935,31936,304, 1189, 1284, 31938,31940,31943,31944,31941,350, 31942,22695,21296,161, 31946,31947,418, 301, 161, 31949,31950,31952,31960,31961,437, 31953,31956,31957,31958, 14091,31954,31955,161, 414, 3654, 417, 296, 936, 715, 31959,23565,161, 996, 3315, 3959, 31962,20848,304, 304, 474, 161, 161, 1028, 31964,31965,9518, 304, 295, 435, 161, 31967,420, 1608, 1886, 31966,1101, 1101, 1189, 1101, 409, 31968,414, 2393, 1036, 31970,161, 31971,1093, 161, 4678, 161, 304, 31973, 12534,31974,31976,3227, 31975,1208, 12132,1764, 421, 31978,1109, 2394, 31980,31982,19632,1990, 161, 2539, 31979,414, 161, 1706, 186, 1756, 1343, 7457, 161, 161, 31981,2191, 161, 17126,556, 304, 161, 31984,414, 1421, 6039, 27824,1426, 161, 161, 6528, 355, 161, 31986,31987,161, 31989,31990,31991,1297, 31992, 30433,295, 31996,161, 5398, 1829, 161, 301, 26449,31993,161, 186, 414, 31994,31995,418, 31997,418, 213, 31999,32003,32004,12859,7168, 32006,3922, 161, 32016,17376,161, 295, 1412, 161, 32000,301, 301, 32001,32002,407, 213, 32005,1640, 1343, 426, 32007,32010,6222, 4051, 161, 32008,437, 32009,161, 161, 161, 32011,161, 414, 161, 180, 180, 1640, 32012,5826, 32013,426, 32014,32015,4164, 8092, 1640, 630, 418, 418, 32017,296, 3555, 9824, 32019,32021,32026, 32029,1822, 4461, 32030,32031,32035,32036,32039,933, 32020,3315, 1533, 32022,32023,32024,418, 4109, 1843, 1756, 1845, 161, 1093, 1217, 1054, 1013, 740, 161, 418, 435, 32025,161, 2750, 9001, 29960,1823, 32027,32028,161, 13168,1757, 975, 2245, 32032,414, 161, 32033,32034,1059, 1847, 30812,32037,2050, 26827,32038, 32041,32043,3778, 12391,32072,32087,32044,414, 161, 32045,32046,740, 6358, 32050,32052,32056,32058,32061,22216,32065,32068,8772, 32047,32048,32049,161, 2725, 32051,32053,32054,1829, 32055,32057,29997,32059,418, 32060,1060, 26341,32062,32064,32063,161, 32066,4307, 32067,1823, 32069,32070,11327,32071,161, 32073,32074, 32077,32084,32075,32076,1297, 304, 3754, 32078,32080,32079,3439, 1231, 161, 32081,32082,556, 32083,417, 6904, 32085,32086,161, 32088,1059, 161, 32089,1539, 32090,1189, 32091,6637, 32093,1604, 32094,32095,32097,32100,32101,32102,3914, 32103,32105,32110,1343, 1883, 296, 2109, 213, 706, 414, 32096,9638, 11650,1042, 32098,32099,1733, 1759, 161, 2210, 556, 1367, 435, 301, 1028, 1059, 6588, 936, 32104,20619,17998,2191, 32106,32108,435, 435, 32107,1699, 161, 213, 10186, 1260, 32109,32111,32114,32112,8585, 32113,32115,32116,1425, 1042, 1426, 32117,32119,32122,1850, 32124,3278, 1343, 32126,32130,32131,161, 32120,414, 161, 161, 32121,32123,32125,32127,32129,8886, 32128,5237, 8634, 316, 1343, 32132,21684,32133,1555, 301, 32135,1995, 426, 32136,32139,32140,301, 32141,4226, 161, 9629, 32137,32138,213, 678, 32142,304, 1198, 418, 32144,161, 1935, 1604, 32147,2719, 2617, 608, 161, 32148,32150,32040,161, 426, 1223, 32151,32154,1459, 4352, 27876,5734, 32152,32153,6066, 6577, 32155,32190,32214,32242,1042, 32247,1635, 32156,32158,4733, 32159,32161,32162,32164,32167,32169,32173,32174,32185,32187,32188, 32157,7280, 1818, 32160,295, 750, 161, 32163,414, 32165,32166,161, 32168,161, 32170,1012, 295, 4169, 295, 11813,11819,16195,32171,32172,161, 213, 32175, 1297, 295, 32176,32177,874, 161, 32178,32180,32179,32181,5563, 17094,161, 32182,32183,32184,213, 32186,8351, 1270, 1639, 186, 1042, 304, 32189,5824, 1818, 32191,2442, 32199,32200,32208,32210,161, 32212,15000,32213,350, 440, 1474, 32192,32196,32193,32194,2534, 161, 12432,2534, 32195,3439, 1876, 1343, 24711,32197, 161, 161, 32198,11998,4532, 161, 1012, 2387, 1145, 4218, 1109, 32201,32202,414, 32204,213, 296, 1042, 32203,161, 32205,6050, 32206,6309, 16329,1343, 14422, 418, 3534, 1851, 161, 32207,32209,161, 1085, 519, 32211,11459,186, 20372,161, 213, 296, 12718,1726, 1405, 32215,32216,32219,32220,17142,32222,3013, 32223, 32224,32227,1405, 32232,32233,32235,434, 24639,32217,32218,409, 32221,1899, 14508,5207, 32225,32226,2340, 18373,32228,32230,3267, 32231,161, 440, 417, 1026, 1640, 32229,8581, 1640, 435, 1829, 3267, 3013, 161, 1426, 1876, 304, 161, 1851, 161, 32234,7390, 32236,32240,32237,32238,32239,1042, 1343, 7332, 1161, 161, 7880, 161, 32241,32041,4733, 466, 13922,466, 32243,1198, 32244,295, 1297, 11705,409, 32245,161, 161, 32246,161, 686, 5469, 32249,32251,32252,32254,186, 32257,32258,32267,32288,32309,32311,32316,31810,32320,32323,1899, 435, 304, 11045,32253,32255,474, 32256,296, 296, 32259,32262,32260,32261,12246,32263,32266, 161, 32264,32265,32268,414, 32274,32277,32279,32269,32271,32270,32272,32273,161, 9129, 1459, 1198, 32275,32276,32278,240, 32280,2101, 32284,32285,32281,32282, 2469, 32283,161, 32286,32287,32289,32291,32297,32299,3680, 32300,32304,23473,32290,750, 1415, 32292,32293,32294,32295,32296,1317, 1284, 296, 32298,629, 32301, 1364, 32302,32303,32305,32306,301, 9724, 32307,1415, 32308,32310,1426, 5622, 418, 32312,32313,32314,933, 2166, 161, 1044, 350, 1028, 32315,421, 32317,32318, 1414, 32319,414, 161, 11636,302, 161, 32321,32322,1583, 4352, 32325,32327,32332,32334,32336,32337,15944,32338,519, 213, 32326,301, 19789,32328,32329,32330, 32331,32333,27148,519, 29108,32335,161, 304, 6256, 14304,161, 20018,32339,18628,1460, 4879, 32340,32342,134, 32530,2306, 135, 136, 137, 33020,1351, 33024, 138, 33215,139, 33486,33499,141, 33593,33597,33620,33646,32341,32343,32344,32345,32346,32364,32372,13849,32375,32377,32383,32385,32389,32390,32398,32424,32438, 32461,32463,32469,32492,32500,32509,32514,32519,32521,32525,32528,1729, 161, 32347,32348,32349,32352,32355,32362,417, 161, 1223, 161, 161, 4570, 1245, 2028, 1899, 1060, 1270, 32350,2028, 32351,161, 414, 32353,32354,32356,2931, 414, 355, 32357,161, 32360,32361,32358,32359,4979, 161, 1862, 1036, 161, 32363,213, 32365,350, 32368,32369,161, 32370,161, 2102, 32366,32367,5285, 2191, 2817, 161, 161, 1639, 301, 161, 3198, 32371,1486, 304, 414, 161, 32373,32374,3315, 14120,408, 213, 161, 1059, 32376,7516, 161, 414, 32378,32379,32381,161, 32382,1899, 32380,1042, 161, 2272, 32384,304, 414, 435, 1449, 32386,32388,15181, 301, 15995,14467,7522, 186, 1260, 1042, 12007,12486,32387,161, 161, 9456, 32391,32394,32397,414, 222, 18562,1250, 32392,1934, 32393,32395,6577, 12567,161, 32396,1093, 161, 19112,4712, 161, 4109, 679, 350, 3922, 1418, 2028, 32399,32403,32404,445, 32405,3267, 32408,32412,32414,32421,32423,1935, 7404, 32400,32401, 10915,7496, 32402,161, 1899, 6222, 161, 2191, 1459, 32406,10915,161, 32407,32409,1343, 32411,2028, 32410,3041, 1538, 32413,161, 161, 1093, 6003, 32415,32418, 32419,32420,11005,161, 32416,350, 10915,32417,350, 161, 350, 161, 32422,186, 1938, 304, 161, 32425,32427,32431,32432,426, 32433,32434,32436,301, 161, 1173, 7186, 1097, 32426,2028, 519, 161, 32428,301, 32429,32430,1868, 161, 222, 161, 2755, 2106, 301, 1097, 15995,161, 301, 161, 1813, 222, 2028, 6965, 23568,1036, 1050, 32435,32437,1554, 426, 1092, 32439,1012, 32440,32442,32443,32444,32447,301, 32448,32452,32455,32456,32457,3063, 12661,32441,1459, 301, 161, 301, 10191,2028, 161, 32445,1421, 2817, 252, 32446,161, 301, 1343, 3002, 7623, 161, 19308,301, 12567,15995,409, 414, 32449,1042, 32451,161, 32450,32453, 32454,1042, 161, 3689, 304, 10191,2225, 161, 213, 435, 222, 440, 32458,1868, 418, 32460,32459,161, 302, 8136, 32462,29235,32464,32465,16470,32466,32468, 414, 161, 311, 161, 5589, 161, 301, 556, 2331, 418, 32467,1876, 161, 161, 6286, 161, 186, 32470,32473,32474,418, 32476,7125, 1028, 32477,32478,2797, 32479,350, 32480,32481,32483,32486,32487,7168, 5934, 418, 15746,414, 161, 301, 1778, 32471,417, 519, 4511, 32472,1367, 161, 15413,1640, 316, 4544, 32475, 17769,161, 161, 5089, 1756, 161, 13915,2538, 213, 440, 418, 161, 32482,32484,32485,161, 161, 417, 2345, 32488,1818, 4405, 161, 161, 24662,32489,2799, 161, 32490,32491,161, 519, 1126, 23873,296, 32493,301, 32494,295, 32495,32497,252, 1959, 2430, 1999, 1042, 161, 3169, 418, 32496,32498,11788,161, 32499, 161, 3654, 1756, 161, 296, 32501,1604, 6577, 1343, 32502,519, 408, 32503,1459, 6577, 32504,32506,304, 32508,32505,32507,186, 3302, 2272, 6643, 32510,2270, 296, 976, 30958,32511,7447, 32513,161, 9934, 1343, 32512,301, 2270, 6264, 32515,32516,11544,6113, 4570, 1223, 696, 32517,1093, 32518,30202,1608, 435, 32520, 161, 213, 1097, 1212, 1302, 32522,1343, 3920, 414, 9335, 32523,556, 161, 32524,556, 4781, 32526,301, 161, 296, 32527,32529,1996, 161, 32531,32545,32546, 32556,32563,32571,32572,32574,32575,32580,32582,32584,32622,32634,32654,32656,32657,32658,32696,32711,32718,32719,32720,32722,18689,414, 32532,32535,32536,32538, 32540,3603, 1990, 32541,32542,32544,32533,933, 32534,4308, 874, 32537,304, 304, 32539,296, 161, 414, 1538, 1042, 7416, 22431,3213, 161, 3198, 1876, 161, 161, 4065, 2880, 1042, 440, 32543,295, 32547,417, 445, 32548,161, 32549,32551,32550,5747, 1140, 3095, 24094,161, 32552,32553,32554,32555,161, 2210, 414, 414, 32557,32559,3267, 3555, 32562,296, 750, 32558,414, 161, 3534, 32560,32561,161, 32564,1036, 3770, 32565,1150, 161, 161, 32570,2362, 32566,304, 32568, 32569,32567,27600,1297, 161, 161, 350, 417, 418, 32573,6422, 295, 12117,418, 1753, 32576,4461, 2028, 32578,10571,32577,14508,4678, 32579,2134, 1862, 32581, 7354, 301, 5968, 1109, 32583,161, 1824, 32585,32617,32618,301, 32586,32587,32590,32594,32599,32612,3476, 32615,161, 32588,32589,32591,32592,32593,32595,32598, 32596,32597,32600,32603,32606,32609,32601,32602,9128, 161, 32604,32605,519, 32607,32608,32610,32611,427, 32613,32614,32616,3088, 296, 1143, 4789, 1341, 32619, 32621,1343, 161, 161, 4065, 161, 32620,161, 161, 296, 1418, 161, 32623,2755, 2755, 426, 304, 6643, 32624,414, 573, 32625,6309, 32628,32630,9291, 161, 32633,414, 161, 32626,6712, 32627,186, 161, 10763,1217, 30477,296, 32629,418, 1343, 161, 1026, 32631,32632,161, 2948, 32635,32638,15363,32639,32643,3113, 24099,5636, 20185,32644,32648,23450,32650,32651,32653,32636,4207, 1305, 32637,1847, 16499,11406,32640,1042, 32641,161, 161, 11150,3326, 32642,1455, 161, 715, 10191,213, 2028, 414, 420, 716, 32645,32647,4583, 161, 32646,1060, 418, 4410, 161, 1150, 11936,32649,161, 1156, 440, 32652,1093, 161, 1356, 1515, 936, 1284, 32655,11466,161, 295, 186, 2607, 295, 222, 32659,3049, 32660,32661,32663,32664,32665,32666,32671,32672,32673,32690,32693,519, 2474, 417, 435, 1217, 32662,301, 1060, 161, 2995, 301, 301, 301, 301, 16071,32667,161, 32668,1198, 32669,9399, 414, 1189, 32670,1532, 5597, 32674,32677,32679,32687,1050, 15133, 32675,418, 32676,426, 413, 304, 7145, 10943,3250, 1217, 32678,161, 17784,414, 32680,2313, 32682,30255,161, 32684,32681,32683,1297, 9129, 1302, 32685,32686, 750, 32688,32689,161, 8580, 1405, 32691,32692,32694,32695,296, 11546,1097, 1498, 818, 4055, 32697,32699,161, 32698,6393, 1818, 161, 1962, 32700,161, 32701, 32702,32703,304, 32708,9291, 161, 467, 161, 3517, 715, 222, 161, 10773,2750, 32704,32705,3278, 32706,4913, 1198, 3855, 32707,161, 32709,32710,15871,4307, 32712,161, 440, 32715,14494,32716,32717,32713,32714,2969, 5468, 420, 434, 3112, 27430,414, 1754, 1640, 32721,161, 32723,2218, 32724,32725,519, 301, 1093, 252, 445, 1173, 8159, 296, 1356, 296, 161, 1823, 414, 32726,32752,32808,421, 32832,32863,3619, 32895,32909,32911,32913,414, 32727,32730,32732,13309,32733, 32734,32735,32741,161, 32748,161, 32749,161, 1555, 1981, 32751,1851, 32728,32729,1981, 32731,418, 936, 936, 13849,296, 301, 1343, 414, 32736,32737,32738, 32739,32740,3226, 161, 161, 161, 32742,32746,186, 3654, 1042, 1756, 32743,707, 32744,32745,32747,1036, 16728,32750,5368, 9243, 161, 32716,161, 301, 32753, 32756,32758,32759,32761,32762,32763,161, 32764,32765,32766,32767,161, 32770,32778,32796,32803,32804,5684, 32807,435, 32754,316, 2340, 32755,1093, 21126,32757, 1757, 23125,1938, 350, 1727, 1823, 161, 161, 3267, 32760,2196, 1897, 1093, 630, 11568,4806, 295, 1093, 2525, 418, 2715, 3302, 414, 32768,1425, 32769,6280, 32771,32772,304, 32776,161, 32773,32774,32775,161, 32777,750, 161, 161, 17584,32779,32783,32786,32787,32793,32794,301, 32780,32781,32782,1060, 414, 1910, 32784,16650,213, 1962, 418, 679, 32785,161, 435, 304, 161, 304, 1060, 161, 301, 301, 2969, 418, 350, 435, 32788,4083, 32789,32792,32790,32791,24330, 161, 316, 1640, 32795,32797,32798,5122, 32799,32800,32802,414, 556, 161, 32801,1093, 3267, 1121, 5139, 32805,32806,161, 161, 32809,32814,32817,32820,32821, 1421, 32822,32827,32829,32831,32810,15806,32812,32811,4055, 350, 32813,2963, 5711, 32815,32816,4376, 161, 161, 304, 161, 32818,32819,3447, 3291, 15715,1028, 1343, 1093, 1109, 161, 32823,32824,213, 32825,161, 9824, 1198, 9017, 161, 414, 1426, 1876, 161, 32826,1042, 1093, 2224, 32828,161, 12203,13614,161, 15901, 32830,1109, 1150, 304, 418, 1027, 32833,32839,32840,304, 32856,32834,1134, 1542, 519, 32836,1177, 1823, 1028, 32835,414, 161, 1590, 2214, 418, 417, 417, 6405, 32837,32838,414, 32841,1362, 32843,6118, 434, 440, 32844,32846,32851,22190,32855,417, 32842,2050, 302, 2842, 355, 435, 161, 1583, 32845,13922,161, 32847,7053, 5290, 32850,440, 29418,417, 1609, 15499,25456,1882, 32848,32849,1909, 1640, 32852,32853,32854,4497, 213, 418, 418, 23383,32857,32860,32858,32859, 13255,1756, 161, 32861,32862,32864,32867,32877,32878,32892,32865,435, 161, 4490, 32866,32868,32871,32874,32875,161, 32869,32870,4051, 161, 161, 414, 1470, 32872,161, 32873,161, 32876,296, 1896, 304, 2224, 11680,4287, 32879,32880,32881,414, 32882,32884,32885,933, 304, 11429,32888,32891,32883,1542, 32886,417, 417, 32887,161, 3194, 1013, 32889,32890,2864, 3439, 1876, 161, 161, 1343, 32893,635, 161, 32894,1426, 1042, 418, 161, 161, 4352, 32896,426, 32897,32900, 32906,1206, 161, 2362, 1728, 161, 32898,161, 32899,1042, 8315, 16129,161, 14351,1042, 1012, 32901,161, 32902,32903,161, 32904,1042, 32905,1143, 2596, 295, 1609, 1727, 32907,1851, 1639, 32908,213, 29408,417, 32910,15300,749, 161, 1310, 32912,1012, 17095,304, 14194,16869,32914,32916,32922,32927,32929,32937,32941, 32949,222, 32951,32959,32976,32993,32997,33004,33008,33012,2213, 30852,1412, 32915,161, 301, 32917,32919,301, 2142, 32920,4901, 32918,32918,750, 161, 8749, 12887,32921,435, 1818, 32923,32924,2797, 15686,161, 32925,1042, 32926,13841,161, 161, 161, 6549, 1230, 4293, 435, 32928,161, 1054, 15124,1962, 1639, 1297, 1640, 1305, 32930,32931,1371, 32933,32934,15995,32936,161, 20069,161, 32932,161, 304, 32935,301, 2102, 32938,161, 32939,304, 14673,161, 32940,213, 1036, 1050, 32942,32944,32945,32948,304, 32943,512, 161, 161, 1412, 252, 161, 19102,161, 161, 32946,32947,213, 1756, 2674, 161, 161, 4169, 296, 32950,6577, 5819, 21350,1093, 3267, 16762,32952,1343, 440, 417, 5357, 32953,32954,3766, 32958,417, 161, 301, 2225, 350, 161, 161, 32955,1026, 32956,32957,468, 2166, 414, 32960,32964,32969,519, 426, 32971,32973,32975,161, 2472, 32961,304, 161, 32963,32962,414, 161, 32965,874, 1093, 32966,32967,161, 1757, 1126, 161, 32968,161, 301, 2767, 32970,213, 29546,32972,1184, 161, 409, 1060, 161, 1006, 414, 161, 32974,519, 7188, 1754, 161, 1126, 417, 32977,9243, 32978,32979, 32980,32981,32983,32984,440, 1343, 32990,3022, 32991,32992,161, 414, 3049, 7961, 29392,304, 435, 161, 3196, 32982,1845, 8405, 1042, 161, 468, 2028, 32985, 1604, 32988,32986,32987,186, 1042, 32989,15165,418, 418, 435, 440, 1343, 1829, 32994,1093, 32995,32996,295, 32998,33001,33002,32999,33000,161, 161, 161, 1093, 1093, 1305, 161, 15627,161, 33003,33005,15533,33007,1042, 2449, 1639, 161, 2213, 33006,161, 1756, 13416,161, 161, 213, 33009,20371,10233,4203, 5747, 33010,33011,33013,8161, 419, 161, 33015,33017,161, 4922, 33018,414, 304, 33014,414, 7930, 161, 33016,33019,750, 304, 161, 161, 161, 16728,4401, 33021, 33022,33023,33025,33026,33027,427, 33028,33032,33038,33040,33043,33046,33048,33053,33055,33058,33069,33092,33101,33114,33122,33163,33170,33177,33193,33194,33205, 33213,33214,33029,161, 33030,33031,3439, 3321, 161, 161, 33033,4638, 33034,295, 33036,13442,161, 33037,33035,161, 161, 161, 33039,161, 1173, 161, 161, 33041,33042,16386,1343, 161, 519, 1050, 414, 750, 16946,161, 161, 1604, 1150, 33044,33045,161, 414, 8357, 750, 1754, 2362, 435, 1036, 33047,1093, 33049, 161, 33050,33051,33052,213, 33054,213, 1305, 1042, 1173, 33056,1722, 4139, 519, 33057,33059,5469, 4461, 33060,9292, 9465, 7900, 33064,9984, 2474, 33068,519, 33061,33062,161, 33063,1150, 2345, 1284, 1092, 33065,33066,33067,161, 161, 1542, 4583, 1026, 350, 161, 4712, 417, 295, 414, 33070,33073,33076,33077,33078, 33080,33081,33074,33083,33087,33090,33091,1847, 24408,1367, 301, 161, 25457,33071,18237,161, 33072,4752, 1250, 1778, 2537, 16380,33074,9057, 33075,350, 2449, 161, 1097, 22682,161, 161, 33079,409, 1813, 301, 33082,161, 9733, 414, 161, 1036, 33084,33086,33085,474, 1727, 414, 161, 3435, 161, 519, 33088,33089, 6405, 417, 302, 161, 418, 2748, 33093,33094,33095,33096,301, 33098,33099,7337, 33100,23535,19271,161, 4514, 161, 1793, 161, 161, 706, 161, 9292, 161, 161, 1198, 161, 1093, 161, 33097,222, 161, 2028, 301, 7494, 1356, 33102,33103,33104,33105,33106,161, 161, 33108,5552, 1635, 1343, 1026, 418, 414, 4169, 408, 33107,161, 161, 4108, 33109,6127, 304, 13986,161, 33110,33111,186, 1756, 1343, 33113,2782, 295, 33112,1737, 6166, 414, 33115,1412, 5065, 33116,4322, 33117,33118,1756, 6567, 33119,33120,33121,2430, 1822, 161, 301, 161, 161, 304, 22062,296, 1538, 4313, 304, 161, 2976, 2196, 426, 161, 33123,33124,33126, 33129,33130,33134,33137,33139,33141,33143,33144,1990, 33148,33156,33157,1060, 33125,1013, 33127,1302, 33128,1668, 1793, 161, 161, 33131,33132,2453, 417, 33133, 33135,1470, 33136,1980, 1470, 33138,33140,12562,161, 33142,414, 474, 1843, 519, 1962, 33145,33146,161, 33147,33149,33150,9570, 161, 418, 2092, 1217, 33151, 33152,33155,161, 161, 161, 33153,33154,33158,33159,33161,15413,936, 1059, 301, 33160,161, 2238, 2576, 33162,33164,33166,161, 33168,33169,33165,161, 33167, 2289, 161, 161, 33171,2447, 161, 1042, 1343, 33173,161, 33174,33172,519, 186, 8909, 5356, 161, 33175,161, 33176,33178,33181,440, 33183,1938, 33184,33185, 33191,161, 33179,161, 5926, 33180,1876, 5360, 417, 161, 161, 161, 3488, 414, 33182,6050, 304, 1756, 161, 161, 2166, 33186,33187,33189,161, 4164, 2102, 556, 33188,8595, 33190,716, 213, 33192,21259,1818, 1376, 33195,33196,1042, 1904, 33198,213, 33204,1862, 1740, 161, 33197,15708,414, 1134, 2165, 1198, 440, 33199,33200,33201,440, 418, 33202,33203,185, 440, 213, 33206,33208,33207,1093, 463, 9399, 33209,33210,33211,33212,161, 1208, 1042, 1044, 161, 296, 295, 222, 140, 33308,33355,33416,33451,33453,33484,33216,33217,33225,466, 33228,33234,1343, 33236,33241,33242,33244,33282,33284,33286,33288,33298,33304,10536,33305, 14508,304, 33218,33219,33222,3438, 33223,33224,4396, 1421, 161, 161, 161, 161, 161, 33220,33221,33226,33227,161, 1022, 678, 1093, 161, 1418, 12433,33229, 33231,417, 33230,33232,1143, 33233,414, 3776, 33235,9128, 1128, 161, 4688, 33237,33238,10201,33240,414, 1426, 1042, 161, 414, 161, 33239,1876, 1426, 161, 1150, 7961, 13424,1048, 33243,1042, 1405, 10723,161, 161, 414, 161, 304, 3250, 161, 295, 9458, 33245,33250,33281,33246,33247,33248,33249,213, 3922, 33251, 33255,16725,33261,33266,33269,33271,33272,355, 33275,16724,304, 33278,25975,1334, 161, 33252,33253,33254,33256,33259,33257,33258,33260,1270, 1305, 33262,33264, 33265,33263,1216, 1116, 161, 16930,33267,33268,1304, 6970, 33270,11920,1608, 33273,8673, 33274,4535, 4497, 33276,33277,3551, 13086,33279,33280,1754, 1133, 33283, 161, 33285,161, 1343, 1302, 1042, 161, 33287,161, 33289,33292,33293,33297,33290,33291,33294,161, 33295,33296,161, 1371, 425, 161, 1793, 33299,33302,33303, 33300,33301,463, 7885, 1042, 6403, 6406, 1044, 1190, 252, 3954, 420, 295, 161, 33306,13841,33307,161, 3959, 1604, 296, 33309,33316,33319,33320,33323,33325, 33327,33328,3025, 33329,33330,33331,33337,418, 33341,33342,33348,33351,27507,2280, 1060, 33354,33310,33312,445, 33313,33315,33311,161, 5368, 1042, 3689, 9057, 5832, 161, 161, 418, 295, 33314,1421, 5238, 9213, 2786, 161, 161, 33317,304, 296, 33318,414, 304, 6378, 295, 33321,1097, 1036, 2976, 33322,33324,5469, 213, 33326,304, 295, 16487,161, 1365, 1824, 7623, 33332,33334,434, 33336,295, 1101, 33333,213, 10080,1042, 161, 33335,4003, 350, 1093, 161, 33338,1635, 7819, 33340,33339,1706, 414, 414, 1604, 418, 7642, 1037, 1727, 161, 33343,33344,33347,33345,33346,33349,161, 33350,33352,33353,1999, 33324,6744, 519, 1343, 519, 33356,33357,33363,33367,715, 33372,33373,33375,33381,33383,33386,33392,33393,33400,33402,33404,33406,33410,13986,1093, 304, 1054, 33358,736, 161, 161, 33359,161, 1198, 33360,33361,33362,435, 33364,33365,5536, 3670, 355, 33366,11556,1753, 304, 161, 25559,1805, 304, 33368,33369,304, 1750, 33370,6968, 33371, 5503, 304, 4105, 161, 161, 161, 213, 350, 161, 33374,2817, 33376,33377,33378,33379,33380,33382,2475, 33384,33385,429, 33283,304, 408, 304, 1793, 161, 161, 33387,161, 33388,33390,1793, 304, 740, 33389,1459, 161, 1305, 33391,161, 414, 161, 161, 33394,33397,33398,161, 33395,13161,161, 161, 33396,161, 2102, 33399,11874,5207, 161, 161, 26263,161, 350, 33401,4065, 1097, 295, 33403,161, 304, 418, 408, 33405,33407,33408,33409,716, 186, 161, 33411,33412, 222, 1699, 33413,519, 33415,350, 33414,2782, 33417,679, 33419,301, 33420,33421,33424,33426,33428,33429,33431,33435,33437,33439,33447,33448,33449,33450,33418, 417, 1109, 3637, 519, 4177, 33422,3954, 33423,33425,213, 4465, 933, 161, 33427,2741, 1474, 1126, 23394,161, 33430,15325,161, 23592,1128, 33432,161, 33433, 33434,161, 33436,295, 1350, 29062,161, 1343, 33438,33440,409, 556, 5362, 33445,33446,33441,33442,33443,1042, 414, 161, 161, 3098, 33444,13416,4065, 1426, 4055, 1228, 161, 9150, 3850, 29392,161, 7961, 295, 33452,33454,33455,33458,33462,33464,33468,2191, 3376, 33470,33471,33475,1042, 33476,33482,3376, 213, 33456, 161, 33457,161, 33459,1412, 414, 161, 33460,1426, 1876, 5376, 161, 33461,33463,1036, 161, 33465,11942,5954, 1343, 161, 33467,350, 161, 33466,628, 304, 20371,161, 33469,874, 1198, 161, 161, 14473,417, 420, 161, 1482, 33472,33473,2174, 304, 6537, 1640, 33474,294, 161, 213, 1093, 1639, 519, 3680, 33477, 33479,33478,33480,1042, 304, 33481,161, 15315,161, 33483,414, 3654, 161, 936, 186, 1876, 295, 33485,350, 33487,33488,1042, 33490,33492,33493,161, 1418, 161, 519, 33489,11385,4727, 1862, 33491,33494,161, 222, 33496,33497,33498,33495,33500,33501,33502,33503,33504,33511,33515,33516,33519,33520,33522,750, 33524, 33529,33538,1112, 33546,33550,33581,33585,33591,3778, 161, 2028, 417, 161, 1635, 33505,1042, 1343, 161, 33506,161, 33507,161, 4164, 33508,33509,33510,222, 161, 33512,417, 33513,1042, 161, 33514,3861, 296, 33517,33518,161, 2225, 161, 2677, 1706, 33521,1639, 296, 1042, 304, 16783,33523,33525,213, 4532, 33526, 301, 304, 12870,4638, 33527,33528,161, 296, 161, 161, 414, 161, 33530,213, 33533,33074,33534,936, 33535,33536,1822, 33531,33532,1876, 33533,161, 1054, 296, 33537,33539,1173, 6677, 8600, 161, 33540,414, 33542,33545,161, 556, 33541,1956, 418, 33543,33544,161, 2406, 420, 33547,33548,33549,33551,33552,33557, 1093, 33559,161, 33560,33561,33564,33565,33566,33573,33575,33576,222, 33578,440, 519, 222, 161, 418, 33553,33554,3964, 2352, 33556,4163, 4164, 33555,33558, 1037, 161, 15839,414, 5271, 33562,5790, 304, 2538, 161, 161, 33563,414, 174, 161, 213, 6118, 420, 418, 15237,2750, 33567,33568,1706, 4108, 33569,33570, 440, 2109, 32396,7053, 1843, 161, 2050, 873, 304, 4217, 1883, 15369,1640, 161, 1054, 161, 161, 1418, 33571,33572,33574,418, 33577,435, 3888, 161, 161, 33579,418, 2000, 33580,161, 6124, 161, 161, 33582,5605, 1042, 161, 33584,426, 4577, 33583,161, 2225, 5610, 213, 1042, 304, 414, 414, 33586,3689, 1367, 519, 33587,33589,33590,161, 3803, 33588,186, 29153,161, 653, 418, 25456,161, 304, 418, 33592,414, 33594,33595,33596,33598,33599,33607,33617,33619,25594, 418, 20532,161, 161, 16828,33600,33601,33603,33604,33606,1150, 1640, 161, 33602,1060, 161, 2225, 29017,1101, 1829, 304, 33605,304, 33608,3267, 33609,33610, 33611,32232,33613,33614,161, 161, 975, 440, 1459, 1818, 1198, 1042, 33612,161, 1198, 1121, 4065, 161, 13823,33615,161, 213, 33616,15901,1042, 161, 414, 1609, 6264, 33618,3954, 13416,1473, 213, 3267, 301, 1343, 33621,33622,33623,33624,33625,33626,1198, 33627,33629,33630,418, 33634,33639,33643,2280, 414, 33644, 2596, 18896,1899, 296, 556, 2028, 33628,296, 7076, 1050, 161, 1542, 3827, 418, 33631,33632,3928, 20185,418, 20761,161, 33633,435, 27175,33635,33636,33638, 161, 161, 161, 31003,33637,2210, 556, 350, 33640,33642,1595, 1639, 33641,465, 301, 17314,12859,33645,33647,1823, 33648,33652,33653,33654,33657,33659,33665, 33670,33675,33692,143, 1042, 34228,34266,34294,34308,34318,9878, 34323,34326,34330,4003, 296, 33649,33650,33651,222, 10108,301, 1640, 426, 1640, 33655,2442, 301, 9557, 3222, 33656,26627,33658,33660,33661,33663,33664,2196, 706, 33662,9332, 161, 33666,33668,1459, 33669,409, 1112, 33667,296, 28038,1343, 33671,33672, 33673,33674,161, 12713,3922, 1608, 33676,33678,6393, 33681,409, 33683,1896, 33691,33677,608, 33679,1608, 1371, 33680,4478, 213, 33682,33684,33685,33689,408, 33686,33687,33688,1189, 8136, 33690,33693,33695,33700,22949,418, 3479, 33701,33702,33705,222, 33694,33696,3518, 10842,33697,409, 466, 1011, 33698,33699,161, 33703,1823, 33704,2163, 3102, 33706,33745,33764,33828,33907,33936,33969,33976,33982,34027,34029,34032,34042,34054,34065,34073,34110,34113,34135,34185,34203,34205, 34209,34224,34226,33707,33708,33713,33721,222, 33723,33725,33730,1338, 161, 33735,33739,33742,5481, 304, 21560,33709,33710,33711,33712,33714,33718,33715,33716, 33717,33719,33720,33722,7545, 33724,33726,33727,33729,33728,33731,33732,33733,33734,701, 33736,33737,1194, 33738,4794, 17895,33740,33741,33743,33744,33746,33748, 33756,33757,1363, 33759,33762,33747,33749,14077,33750,33753,18031,5021, 33751,33752,33754,33755,2891, 186, 33758,33760,23226,13856,33761,4401, 33763,33765,33766, 33772,33784,33792,33795,33827,3958, 2102, 1270, 161, 33767,33769,33768,33770,33771,33773,309, 33774,4766, 33777,33775,33776,33778,311, 33779,33780,33781,33782, 33783,33785,33790,33786,33787,33788,33789,33791,33793,33794,556, 414, 4203, 408, 161, 33796,33805,33824,421, 1101, 10186,33797,33799,33801,33798,33800,12998, 33802,33803,33804,33806,33807,33808,33810,33817,33822,33809,33811,33814,33812,33813,5021, 6999, 33815,33816,11212,33818,33819,33820,33821,33823,161, 33825,33826, 33829,33831,33894,33903,33906,8803, 1270, 33830,186, 21217,33832,33833,33834,33887,33890,33835,33839,33840,33843,33849,6106, 33855,33861,33862,33863,33866,33868, 33869,8818, 33876,33881,33884,304, 33836,33837,33838,33841,33842,29634,295, 4535, 161, 12239,33844,33848,33845,33846,33847,33850,33851,33852,33853,33854,33856, 33857,33858,33859,4218, 33860,1194, 1640, 33864,1270, 33865,1208, 1093, 1042, 33867,33870,33871,186, 1323, 414, 33872,33873,33874,33875,9135, 2196, 33877,33878, 628, 2221, 12546,33879,4668, 33880,7478, 1042, 161, 33882,474, 33883,2313, 1042, 6110, 1824, 33885,33886,33888,17818,33889,33891,33892,33893,414, 33895,21217, 33896,8966, 33901,33897,33899,33900,33898,33902,1042, 304, 33904,33905,33908,33909,33910,33915,33919,33923,33924,33927,4654, 213, 33911,33912,33913,33914,1421, 186, 715, 33916,1270, 33917,33918,33920,33921,33922,33925,33926,33928,33929,33932,33935,33930,33931,33933,4497, 33934,33937,33947,33948,33949,33952,33962,33966, 33938,33940,33942,33945,3659, 33939,33941,33943,33944,33946,7280, 8259, 12395,161, 33950,33951,33953,14091,33954,1194, 33955,33956,33960,33957,33958,33959,1421, 1042, 33961,33963,33964,33965,33967,2102, 33968,33970,296, 161, 23201,10026,33971,33974,33972,33973,33975,33977,33979,33776,33981,186, 33978,1338, 33980,1111, 186, 213, 8248, 33983,33985,33988,33992,33996,34006,1733, 34021,2053, 34022,34023,679, 161, 33984,6968, 33986,33987,311, 33989,33990,33991,186, 463, 161, 33993,33994,33995,33997,34002,33998,33999,34000,34001,1150, 1515, 34003,34005,33935,34004,34007,34008,34010,34012,34020,34009,34009,34011,4497, 34013,34014,18507, 34016,34015,34017,34018,34019,1310, 700, 414, 1425, 161, 161, 34024,34025,34026,4346, 1054, 30919,34028,185, 34030,161, 34031,34033,34034,34036,34040,19412, 34035,414, 311, 5854, 34037,34039,25387,1310, 34038,1421, 1189, 3363, 34041,33893,34043,34045,34050,304, 1715, 34044,421, 311, 11294,34046,34047,34048,34049, 34051,34052,34053,34055,34056,34063,311, 408, 32063,34057,34061,34058,34059,34060,34062,1405, 34064,34066,34068,34069,34067,1715, 33899,34070,34071,34072,34074, 34078,34087,34088,34109,16828,186, 34075,34076,34077,34079,34080,34085,34081,34082,34083,34084,34086,34089,34100,34104,34090,34093,34097,34098,34091,34092,34094, 34095,34096,34099,34101,34102,34103,34105,33722,34108,34106,34107,12394,12395,34111,25412,34112,706, 34114,34116,34133,34134,409, 161, 34115,34117,34119,34124, 418, 34125,34126,34128,34129,34132,18067,34118,33944,34120,34121,34122,34123,1310, 5021, 34127,700, 34130,161, 34131,34136,34141,34147,33760,34155,34156,34158, 34162,34165,1885, 34182,304, 186, 25655,34137,10644,34138,34139,34140,24988,311, 1270, 4793, 34142,34145,34143,34144,11881,34146,34148,34151,34152,34153,161, 34154,1280, 34149,161, 34150,5246, 161, 2442, 1715, 34157,25386,311, 34159,34161,34160,3958, 1109, 1421, 34163,420, 34164,34166,34172,10390,556, 34176,304, 34179,34167,34171,34168,34169,34170,34173,34174,34175,34177,34178,34180,34181,34183,34184,34186,34188,34190,34192,34194,34198,14034,311, 34187,23123,5021, 34189, 1310, 34191,34193,161, 27148,186, 34195,1161, 34196,34197,32968,186, 34199,8818, 34200,18523,1715, 161, 34201,34202,34204,186, 2210, 34206,34208,34207,34210, 34213,34215,15316,34221,1310, 34211,4794, 11459,34212,161, 34214,34216,34217,186, 304, 13004,34218,34219,34220,34222,34223,34225,34227,12799,414, 34229,34230, 34233,4285, 34237,34239,18289,34246,34250,418, 34252,34253,34256,34262,1738, 34265,1412, 23733,34231,14078,34232,421, 408, 33854,34234,3730, 3730, 34235,34236, 678, 427, 34238,1459, 34240,1640, 34241,22690,186, 679, 34242,1297, 34243,34244,34245,34247,34249,4432, 34248,34251,3439, 8909, 34254,34255,976, 1270, 295, 296, 1903, 4065, 34257,34259,2196, 418, 34260,34258,474, 350, 296, 34261,1284, 686, 12738,350, 304, 25719,34263,975, 22516,34264,34267,34269,2157, 34273, 34275,34277,434, 1343, 414, 10633,34286,34289,34290,417, 296, 161, 34268,301, 1458, 414, 34270,418, 1590, 409, 418, 34271,421, 34272,161, 608, 22748, 2094, 34274,161, 34276,161, 1042, 161, 8842, 161, 34278,304, 5483, 34279,34280,34282,304, 1824, 34281,161, 1093, 213, 17740,161, 34283,34284,34285,34287, 3013, 34288,2182, 519, 301, 20204,1608, 34291,161, 3674, 222, 34292,34293,1640, 34295,301, 34296,34302,1876, 34303,34304,1474, 34305,34306,34307,573, 1150, 304, 2844, 161, 34297,34299,34300,161, 34298,1757, 1343, 417, 34301,161, 161, 630, 1459, 161, 27600,12668,161, 1608, 295, 161, 1276, 933, 409, 34309, 304, 34312,440, 3075, 9065, 440, 34315,10655,976, 34310,34311,34313,34314,34316,409, 34317,2864, 186, 213, 409, 409, 435, 34319,161, 34320,161, 34321, 34322,34324,34325,34327,34329,301, 9557, 34328,34331,34334,5918, 34332,34333,161, 2797, 145, 34620,34621,34624,147, 34770,3440, 34773,148, 34941,34943,34947, 35018,35028,35030,35044,35049,34335,34347,34351,34356,34357,34361,34408,146, 34578,34581,34582,34597,34613,34614,1173, 430, 34618,34619,34336,34340,421, 34345, 34346,8906, 34337,1208, 34338,34339,34341,34343,34342,15799,161, 34344,1692, 1093, 161, 296, 418, 1223, 161, 34348,16912,34350,34349,1050, 34352,34353,34355, 2191, 13609,184, 161, 34354,34358,1036, 1093, 34359,14944,34360,414, 34362,34365,34368,34376,34390,34391,34393,34394,2524, 34403,1107, 34404,34405,34406,34363, 1107, 34364,5454, 519, 2293, 519, 421, 5455, 34366,4779, 34367,161, 34369,34372,34375,1109, 34370,34371,161, 414, 1934, 34373,34374,34377,34380,34386,34389, 1093, 161, 34378,34379,1787, 161, 34381,34384,161, 161, 34382,414, 34383,161, 34385,161, 34387,161, 161, 34388,1060, 161, 414, 5222, 34392,1048, 304, 6886, 304, 1459, 17744,463, 161, 2625, 34395,34398,34400,34402,161, 34396,34397,1044, 161, 304, 5073, 3376, 161, 3025, 213, 414, 213, 161, 1044, 34399, 161, 418, 34401,161, 161, 2854, 414, 304, 13986,4511, 32968,1425, 34407,161, 9892, 34409,34410,34411,161, 34412,414, 34413,34425,34436,34442,34497,34501, 34502,34507,34514,34515,34520,34527,34530,34537,34540,11313,34541,34542,34549,34554,34565,34575,34414,1109, 34415,34418,34419,34422,1059, 1044, 34423,1109, 5254, 34416,34417,418, 2241, 34420,34421,1109, 34424,34426,34427,34428,34431,34434,10176,421, 8390, 34429,34430,34432,716, 34433,1109, 34435,3527, 161, 34437,34438, 34440,1842, 34439,427, 304, 8125, 350, 34441,34443,34446,34490,34492,34494,34495,34496,34444,161, 213, 34445,161, 161, 34447,34448,440, 34449,1022, 34450, 34456,4352, 34483,34487,12329,1013, 16499,213, 4461, 34451,34453,1013, 34452,1013, 34454,34455,34457,2161, 3267, 34459,34461,34464,34465,34468,34470,34473,34477, 34480,34481,34458,2596, 1013, 34460,34462,34463,2196, 2196, 4401, 679, 5089, 679, 468, 34466,34467,161, 1023, 34469,13922,296, 13922,34471,34472,435, 161, 435, 34474,34475,1217, 3603, 34476,34478,34479,4203, 4401, 1899, 350, 7961, 2195, 2262, 304, 34482,34484,34486,1109, 296, 2196, 34485,161, 34488,34489,304, 161, 34491,296, 1899, 34493,2438, 434, 5398, 1595, 295, 421, 1036, 304, 1981, 421, 413, 161, 1223, 4979, 34498,34500,3210, 161, 34499,5114, 27815,34503, 34504,34506,1542, 3551, 34505,5398, 161, 1143, 5637, 2221, 34508,34510,1059, 34511,1757, 1532, 1109, 34509,3730, 435, 4352, 679, 34512,1595, 34513,304, 161, 161, 4883, 295, 161, 3954, 296, 1223, 2894, 4073, 34516,34518,1023, 34519,21651,34517,34521,34523,23493,34526,34522,5073, 4705, 34524,13798,161, 34525,34528, 34529,16070,1823, 34531,34533,1217, 34535,2190, 161, 1343, 34532,34534,1829, 34536,304, 161, 34538,222, 34539,34536,5398, 5398, 4252, 34223,2596, 8203, 161, 716, 34543,2955, 25069,3315, 34547,34544,687, 34546,21158,14513,34545,34548,34550,34552,1126, 435, 15036,304, 34551,34553,34555,1109, 9164, 34561,34563,34556, 34557,34558,34559,34560,34562,34564,34566,34568,34570,34573,34574,34567,350, 16070,295, 696, 34569,350, 161, 161, 34571,34572,1150, 34576,304, 1150, 34577, 350, 4461, 186, 34579,34580,6935, 161, 15476,1150, 417, 34583,34584,34585,34586,34590,301, 34591,1827, 2111, 1317, 31915,34595,434, 161, 818, 19819,3674, 4712, 3409, 1058, 161, 414, 34587,8509, 34589,2017, 1356, 34588,1335, 161, 161, 1128, 28248,213, 34592,34594,1145, 161, 1109, 1459, 34593,34596,6276, 34598, 34604,5455, 34608,34609,2213, 19920,16630,34611,34612,34599,34602,355, 34600,34601,484, 34603,296, 1455, 34605,2050, 161, 161, 34606,34607,474, 418, 222, 34610,161, 608, 2059, 161, 5541, 29392,161, 1126, 213, 1756, 296, 161, 25937,295, 34615,34616,1305, 304, 3893, 8382, 34617,418, 418, 1054, 161, 161, 301, 1990, 5455, 16630,34622,34623,34625,34626,34628,34634,34635,34636,34641,34643,4483, 34646,34663,1223, 34699,34749,34762,304, 213, 1590, 1806, 409, 2677, 34627,408, 1884, 34629,34633,34630,34631,161, 34632,295, 16783,1555, 24311,34637,6759, 34640,295, 34638,34639,1356, 24581,1688, 34642,414, 301, 34644,1093, 4596, 1208, 34645,161, 34647,818, 34651,34653,30360,1097, 301, 34654,34659,34660,5455, 34648,34649,34650,519, 16630,3132, 161, 34652,34655,1044, 296, 161, 34656,34657,213, 34658,418, 1343, 222, 161, 34661,34662,350, 34664,3689, 34666,34671,34676,34678,34679,34680,34681,34682,34697,34698,1150, 34665,186, 1470, 222, 34667,34670,1426, 301, 34668,34669,556, 2094, 1787, 296, 413, 301, 34672,2474, 34673,161, 34674,1421, 1087, 27249,34675,34677,1216, 3198, 304, 296, 417, 301, 435, 304, 418, 5238, 34683,161, 34688,161, 34695,34684,34687,556, 34685,34686,1208, 417, 296, 2330, 34689,34690,34691,1093, 304, 3855, 34692, 34693,34694,34696,1060, 1425, 3881, 1093, 161, 222, 1060, 304, 418, 34700,34704,2270, 34706,34710,34711,34714,34716,34720,34721,34726,34730,34733,34735,34741, 34746,34748,21039,1317, 1862, 34701,34702,34703,34705,420, 161, 1013, 1189, 2472, 1208, 34707,34708,1595, 34709,519, 435, 10176,350, 1818, 161, 519, 161, 1245, 34712,1134, 6127, 34713,213, 34715,8116, 213, 34717,34718,417, 34719,161, 11409,23806,161, 34722,34723,34725,296, 435, 706, 13922,34724,161, 437, 11527,34727,34728,34729,1482, 6991, 4207, 435, 161, 1044, 161, 304, 1818, 301, 34731,161, 34732,301, 301, 1990, 161, 6222, 34734,414, 34736,1425, 34737, 34738,414, 304, 4045, 2309, 161, 34739,34740,34742,34744,34745,2678, 34743,161, 2210, 161, 519, 34747,34750,34752,34754,34756,34759,34761,519, 34751,34753, 161, 161, 34755,34757,34758,161, 3250, 1538, 34760,161, 23134,1438, 414, 161, 34763,1280, 1797, 161, 34768,34764,519, 1733, 161, 34765,34766,34767,34769, 34771,34772,34774,34777,34781,34803,34811,34820,222, 34829,34832,2524, 34863,34881,34886,34889,34903,34919,34928,12217,34936,1150, 1173, 34775,34776,1093, 445, 161, 161, 21429,161, 5469, 1093, 34778,34779,350, 34780,161, 1091, 296, 414, 34782,34786,34788,34791,34793,34796,1458, 34797,34783,34784,161, 34785,34787, 3435, 161, 1470, 1498, 34789,34790,34792,8815, 34794,4615, 3087, 13916,14944,1060, 34795,161, 34798,34800,34799,414, 7324, 1756, 161, 34801,414, 161, 818, 34802,161, 161, 4683, 414, 2519, 936, 34804,34805,1824, 34809,1432, 301, 7971, 34806,161, 1228, 414, 161, 414, 17714,161, 34807,34808,34810,161, 1173, 34812,34813,34814,34815,34818,1173, 161, 420, 1270, 750, 301, 161, 161, 34816,34817,3439, 1042, 24157,34819,34821,6678, 34822,28697,34826,34827,304, 34823, 34824,34825,161, 5468, 1223, 34828,34830,1494, 1604, 445, 414, 34831,34833,1093, 34835,301, 2025, 418, 34834,161, 1173, 1403, 34836,34858,34862,414, 34837, 34839,34840,1092, 34842,34846,34847,21429,34850,34852,34853,34854,414, 34838,2214, 445, 7138, 4966, 7882, 34841,15746,301, 34843,21429,301, 34844,1037, 1097, 1037, 34845,4203, 435, 34848,161, 7145, 34849,34849,301, 1403, 161, 1087, 34851,1087, 434, 434, 2508, 34855,34856,34857,301, 1760, 3674, 34859,34861,161, 34860,6577, 296, 750, 5760, 34864,34868,34874,34876,161, 34877,161, 34878,34879,34880,1539, 34865,34867,414, 2605, 34866,750, 161, 750, 5381, 1813, 414, 12769,161, 34869,34870,34871,1692, 34872,34873,1824, 161, 161, 161, 34875,1143, 556, 301, 34882,34883,34884,7120, 706, 23872,1227, 34885,34887,185, 3547, 161, 34888,161, 1323, 161, 34890,1109, 1093, 34892,34897,34899,34902,519, 161, 34891,161, 161, 418, 34893,161, 161, 34894,34895,3250, 34896,161, 34898, 304, 34900,1276, 417, 1093, 34901,350, 715, 1432, 34904,34905,1818, 34909,34911,295, 296, 5223, 301, 34916,34906,3929, 34907,2094, 34908,34910,1315, 34912, 34914,34913,414, 1608, 161, 34915,186, 1876, 2340, 161, 437, 437, 34917,34918,23132,213, 161, 34920,34922,3548, 34924,519, 34925,1868, 8056, 34921,5409, 2252, 161, 7911, 213, 161, 161, 161, 34923,222, 34926,34927,750, 9829, 34929,34931,34933,161, 304, 3554, 34930,161, 161, 301, 3097, 34932,161, 34934, 301, 1189, 34935,4781, 34937,3959, 2516, 34938,1367, 34939,34940,34942,34944,3954, 409, 34945,1037, 1449, 34946,34948,34952,34953,34955,414, 34962,750, 34969, 34987,34988,34996,35000,35005,35008,35010,35012,35014,34949,34950,7121, 11919,34951,18807,301, 1865, 34954,4987, 295, 161, 418, 34956,34961,161, 14527,34957, 161, 34958,1343, 34960,34959,34963,161, 34965,3525, 161, 34966,161, 34964,1042, 161, 5069, 161, 3185, 161, 34967,34968,34970,34971,34974,301, 34976,34977, 34980,295, 295, 304, 161, 34983,34984,519, 161, 350, 2309, 34972,1459, 34973,302, 1432, 34975,437, 301, 14508,1371, 34978,34979,12145,34981,11338,1730, 17190,161, 34982,1938, 161, 161, 1198, 34985,33537,34986,9693, 21804,161, 34989,2309, 34991,161, 161, 1107, 34992,34993,34990,161, 3504, 1956, 34994,34995, 34997,186, 161, 34998,34999,11436,11939,1145, 304, 161, 35001,11939,35002,417, 35003,35004,12238,2053, 302, 678, 35006,161, 1639, 936, 35007,474, 35009, 161, 1042, 304, 301, 26605,976, 35011,161, 35013,1042, 750, 161, 35015,2378, 750, 35016,35017,19739,301, 35019,29838,35023,35025,35020,3478, 35021,35022, 1343, 35024,35026,35027,35029,35031,161, 35032,35034,24793,35038,35041,35043,24793,35033,35035,35036,35037,1343, 1824, 408, 519, 35039,13065,301, 35040,35042, 519, 2514, 35045,35046,35047,35048,35050,35053,150, 151, 35437,152, 153, 35796,35801,154, 35937,35942,35976,35977,35980,35992,35995,36002,36005,35051,35052, 35054,35055,35057,35058,35063,35070,35071,35073,35083,35089,1862, 35099,35102,35140,35143,35153,35157,35159,35195,35214,35235,35237,35243,35247,35248,518, 35056, 1036, 161, 161, 1145, 5122, 429, 35059,35061,161, 35062,35060,4987, 1538, 1093, 161, 161, 35064,35067,3267, 15814,1042, 6405, 35068,1343, 17677,2134, 1640, 35065,304, 35066,1097, 435, 1479, 35069,19409,474, 296, 35072,1270, 1198, 1143, 4470, 35074,35076,1042, 4168, 1343, 5722, 13015,35078,35080,161, 1343, 35075, 1093, 161, 296, 28613,696, 35077,1042, 696, 35079,678, 296, 35081,35082,6118, 35084,519, 35086,161, 161, 1012, 35085,1842, 1343, 1843, 1845, 185, 35087, 35088,434, 35090,161, 976, 1297, 35092,161, 35093,35095,10198,35091,519, 977, 20990,3029, 35094,1640, 35096,1336, 304, 2766, 1343, 1487, 161, 20102,35097, 35098,35100,9687, 440, 3098, 1604, 35101,1343, 161, 161, 161, 933, 161, 185, 213, 161, 35103,35104,35105,35106,414, 3267, 35114,35115,35120,35128,3368, 3178, 1843, 35129,35131,35133,1885, 1851, 16528,1363, 3227, 1363, 161, 161, 2191, 2191, 1461, 435, 35107,6309, 1343, 295, 35108,35109,19689,35113,161, 409, 161, 679, 161, 35110,35111,35112,933, 5610, 2539, 35116,35118,1343, 4127, 161, 933, 635, 161, 35117,1054, 161, 161, 35119,414, 35121,464, 35123,35124, 295, 35127,29296,161, 1050, 213, 35122,3551, 161, 5626, 161, 1054, 1036, 421, 35125,161, 35126,1899, 4042, 35130,414, 296, 1060, 418, 35132,186, 418, 1847, 414, 35134,35137,1109, 3766, 295, 1092, 35139,161, 1490, 35135,414, 13198,35136,2163, 2164, 35138,296, 2378, 4448, 933, 35141,2864, 35142,4042, 408, 304, 20350,295, 1150, 1459, 35144,301, 35145,2994, 35148,16830,35149,35150,4042, 35152,161, 35146,15181,161, 435, 35147,1538, 1042, 1199, 22158,295, 161, 4307, 1042, 1459, 35151,296, 1198, 7000, 414, 161, 186, 1876, 3479, 1028, 161, 161, 35154,750, 35156,35155,35158,414, 35160,1891, 35163,35167,35170,519, 35172,1042, 35173,35175,35178,35180,35183,35184,35190,35192,3959, 161, 35194,6118, 35161,35162,492, 1981, 414, 35164,35165,35166,161, 1639, 435, 1060, 161, 474, 161, 474, 933, 6528, 6009, 35168,1042, 161, 161, 161, 1459, 630, 35169,35171,435, 16263,1143, 5539, 9299, 435, 35174,420, 35176,35177,186, 35179, 4773, 1042, 213, 161, 409, 421, 161, 409, 420, 3730, 35181,35182,304, 420, 161, 3730, 161, 3267, 304, 435, 161, 421, 1889, 1101, 186, 35185,35187, 35188,35186,421, 35189,15325,161, 474, 35191,474, 4979, 35193,630, 1482, 161, 3261, 35196,1109, 35197,35203,35205,25255,10911,418, 1093, 35207,35210,2449, 295, 2447, 35198,35199,21135,4284, 35200,874, 8134, 161, 35201,35202,417, 3954, 35204,161, 2537, 466, 35206,161, 2750, 35208,35209,1371, 418, 9590, 35211, 1876, 418, 35212,161, 3198, 3479, 161, 35213,35215,35216,35218,3267, 1109, 16986,35228,35229,3399, 35230,35234,1609, 2755, 35217,161, 5065, 26474,3198, 20848, 1343, 35219,414, 161, 35220,35221,186, 35223,1260, 2165, 1042, 518, 35224,6377, 1463, 35226,161, 186, 213, 1608, 35222,7961, 1895, 1143, 2524, 418, 35225, 35227,185, 4169, 6118, 35231,35233,418, 161, 35232,1544, 222, 222, 409, 35236,161, 35238,1876, 11689,161, 161, 35239,35242,161, 35240,35241,686, 35244, 35245,35246,1302, 1042, 1343, 161, 414, 3029, 9044, 1818, 35249,35250,35251,8218, 161, 1161, 5448, 418, 418, 716, 1823, 35252,420, 32784,35253,35272,35278, 35280,35296,35303,35308,35312,35351,35367,35371,35388,35389,35401,35428,295, 32987,35434,35435,161, 35254,35256,1297, 35259,35261,35262,35263,35271,35255,11853, 1429, 161, 1555, 161, 350, 35257,35258,35260,1818, 1876, 161, 161, 9924, 213, 161, 35264,35265,35266,414, 1640, 6574, 186, 3267, 35267,213, 1343, 35270, 4425, 740, 35268,35269,6574, 27752,7070, 1042, 35273,1604, 296, 35274,3103, 304, 35277,35275,35276,2548, 2655, 11652,35279,35281,35286,35290,3315, 35292,161, 35282,35284,304, 161, 3250, 35283,35285,35287,3250, 6423, 35288,35289,35291,1198, 161, 161, 1885, 35293,35294,35295,409, 35297,161, 304, 35298,35300,35301, 15708,414, 35302,2750, 2362, 295, 203, 418, 1042, 1343, 418, 161, 414, 24636,35299,1474, 161, 5789, 161, 1198, 213, 161, 35304,35305,1371, 295, 35307, 2655, 161, 3719, 35306,161, 1270, 35309,295, 35310,716, 14380,2471, 35311,1371, 6264, 185, 2457, 35313,35315,296, 35317,7003, 2457, 35322,11338,35323,5469, 35332,35335,35349,35314,161, 24230,435, 1343, 35316,295, 1371, 295, 1371, 1350, 161, 35318,35319,35320,1036, 295, 1896, 186, 1042, 161, 35321,414, 186, 1756, 1343, 161, 185, 24525,296, 161, 16694,14508,1895, 35324,35327,465, 709, 1371, 35328,35329,35331,35325,35326,161, 4241, 4694, 1896, 1010, 408, 35330, 426, 420, 35333,6384, 4773, 213, 1756, 35334,35336,35337,35340,35342,465, 11799,1343, 295, 35345,161, 161, 1013, 628, 161, 35338,161, 35339,3518, 1363, 3518, 35341,35341,35343,5122, 296, 35344,1012, 35346,35348,465, 1343, 35347,161, 35350,435, 1343, 295, 3550, 8181, 35352,35356,35357,5362, 35358,295, 161, 35365,35366,161, 16195,35353,35354,35355,1042, 9478, 1876, 418, 161, 1823, 1143, 1143, 8181, 35359,35360,435, 1343, 35363,1184, 35364,409, 161, 161, 5239, 35361,295, 35362,10069,2111, 1818, 418, 161, 161, 161, 35368,295, 7029, 35369,35370,35372,35373,161, 35380,556, 4883, 35382,1590, 10334,35383,35384,414, 35387,1640, 35374,35376,35377,35379,1100, 161, 1050, 35375,161, 35378,1818, 1042, 6153, 679, 35381,295, 296, 4465, 435, 1846, 161, 3113, 35385,1885, 35386, 35390,35391,35393,35395,35396,161, 213, 35399,17677,35392,35394,19810,13798,2225, 1042, 35397,35398,3730, 3002, 421, 2524, 26631,35400,1343, 161, 161, 213, 35402,35403,35404,295, 35406,35408,35411,1036, 2191, 213, 4678, 35405,35407,420, 2538, 35409,465, 3776, 1343, 295, 418, 35410,161, 414, 3728, 35412,35414, 35415,35417,1260, 35418,418, 35419,35421,35423,35424,35427,2195, 16745,15499,4218, 35413,1608, 161, 1937, 8160, 7763, 6750, 6286, 35416,15091,3098, 1161, 30930, 1343, 1093, 5228, 1459, 678, 35420,213, 1042, 185, 161, 5583, 5631, 35422,1060, 295, 12238,35425,1899, 35426,2225, 6384, 821, 933, 161, 296, 35429,24236, 1962, 11987,19085,35433,17516,35430,6118, 35431,6574, 1640, 35432,296, 1042, 3510, 16841,1426, 3479, 5138, 35436,15979,420, 35438,35439,35440,35441,35442,35475, 35476,35498,35549,35551,1107, 35566,7823, 35567,35443,35445,161, 35447,35449,556, 35450,35451,35452,35474,35444,186, 1876, 161, 161, 7669, 7669, 35446,301, 21239,301, 35448,1042, 440, 161, 417, 35233,161, 35453,7669, 35454,35462,35463,7669, 9233, 3603, 161, 35464,35466,933, 35467,301, 2028, 21239,435, 161, 304, 161, 304, 301, 35455,2191, 35456,301, 35457,35458,35459,35460,35461,7669, 301, 35465,25363,933, 21239,304, 301, 35468,35469,35470,35471,35472,35473, 35477,35479,35480,35487,35488,35489,35495,161, 161, 417, 213, 35478,750, 440, 418, 2362, 418, 35481,5606, 35482,35483,35485,35486,1343, 420, 161, 414, 3029, 35484,418, 16487,1757, 295, 4042, 16542,304, 687, 35490,440, 213, 35491,35492,213, 1962, 418, 679, 35494,295, 35493,161, 295, 35496,1109, 434, 7961, 35497,35499,35501,679, 4707, 35502,35503,35505,35507,35509,35511,35519,35548,35500,295, 1640, 1143, 687, 6574, 1583, 5223, 35504,35506,1042, 213, 161, 414, 440, 12412,35508,161, 35510,1042, 7494, 15732,213, 35512,35514,295, 1343, 35515,20619,35516,35518,35513,31364,10237,35517,435, 2040, 35520,35521,35522, 35524,9316, 35533,10847,295, 35537,35538,35404,3315, 35540,35542,5679, 1823, 2655, 420, 3102, 161, 35523,8162, 1892, 296, 4768, 414, 161, 2442, 35525,35526, 161, 35527,1706, 35528,414, 35530,1343, 35531,874, 1093, 35532,12412,1060, 1143, 213, 3102, 28030,1143, 35529,740, 1640, 33322,2382, 474, 213, 9990, 6394, 161, 420, 5166, 7961, 8655, 3729, 316, 161, 1459, 35534,35535,35536,407, 5159, 4465, 6394, 35539,296, 1823, 1823, 35541,2191, 418, 2442, 35543,35545,35546, 1962, 418, 161, 161, 1962, 35544,161, 161, 15091,417, 1818, 9634, 35547,1042, 161, 161, 34627,35550,32784,35552,3652, 34396,35553,35463,35555,35561,35562, 35564,35565,1036, 35554,35556,213, 35557,35560,35558,35559,19345,630, 933, 161, 35563,409, 304, 19560,161, 8374, 426, 1042, 440, 417, 161, 16121,16542, 35568,161, 304, 213, 35569,35571,35572,35584,35593,35617,35618,35626,35627,35685,35692,35748,35749,35752,35767,750, 3153, 35792,35793,3954, 350, 35570,296, 3954, 1150, 1013, 304, 35573,161, 35575,35577,35574,35576,1044, 1371, 35578,1604, 15124,19810,35581,1343, 32171,35583,3326, 435, 35579,35580,161, 4801, 161, 185, 35582,185, 9633, 304, 35585,35587,32806,420, 35589,4168, 35591,3959, 409, 35586,7574, 1343, 5163, 161, 35588,35590,296, 35592,414, 1538, 161, 35594, 35596,35598,35606,35607,35608,35609,35611,35614,35616,434, 35595,1896, 11406,1662, 161, 35597,35599,35604,421, 1371, 35600,35602,35601,3113, 35603,5254, 425, 35605,185, 1899, 4678, 185, 3770, 35610,304, 161, 161, 4576, 1343, 35612,35613,1935, 301, 35615,295, 161, 1343, 1851, 161, 161, 10934,1042, 10047,1143, 185, 35417,35619,35624,35625,440, 161, 417, 13882,35620,35622,161, 1459, 161, 35621,35623,1042, 161, 1459, 3529, 16097,304, 678, 445, 185, 35628,35630, 35632,35643,35644,936, 35647,15814,35653,35657,35678,295, 3227, 35681,35682,35684,161, 491, 14508,35629,6932, 295, 35631,6505, 2225, 3689, 35633,35635,35638, 13378,1042, 35641,1343, 35363,1126, 3959, 23599,161, 35642,35634,35636,161, 35637,3002, 161, 414, 14263,1756, 1823, 35639,35640,185, 628, 417, 6520, 161, 161, 161, 35645,35646,556, 186, 1459, 35648,35651,35652,35649,1806, 35650,3837, 4461, 35654,35656,12222,161, 35655,4583, 161, 161, 161, 3409, 417, 414, 35658,490, 35659,35660,20535,35663,35664,35671,295, 35674,4500, 8977, 35677,4352, 1899, 2088, 414, 186, 316, 2191, 161, 3267, 7106, 35661,2203, 161, 35662, 161, 161, 161, 5734, 706, 2868, 35665,9918, 421, 35668,3267, 161, 15814,413, 35669,3992, 414, 35666,1093, 1208, 414, 161, 35667,2241, 418, 418, 161, 12326,161, 35670,1604, 213, 1756, 35672,35673,161, 161, 304, 350, 213, 7007, 301, 35675,1054, 35676,414, 161, 5671, 35679,14080,35680,350, 304, 996, 1101, 213, 2109, 2150, 9494, 186, 1129, 1042, 418, 873, 35683,161, 2225, 2109, 304, 301, 484, 35686,5061, 295, 35690,35691,35687,1145, 35688,1145, 35689, 1184, 35693,35695,35698,35714,35720,35723,35727,35728,3479, 35733,35736,1727, 35737,35741,1161, 295, 15499,8158, 35694,161, 35696,35697,435, 1350, 430, 414, 35699,35700,3302, 35702,35703,10626,3670, 35704,35705,11627,35710,35713,414, 1161, 161, 556, 17638,161, 435, 35701,161, 161, 1459, 483, 556, 35365,161, 161, 35706,414, 35707,35708,35709,3098, 296, 5589, 35711,35712,414, 35715,35716,465, 3479, 19654,2225, 8095, 3518, 1013, 3238, 35717,35718,35719,1895, 1143, 35721,35722,35724,12198,1270, 35725,3098, 1604, 1042, 295, 1343, 874, 161, 5954, 161, 161, 430, 35726,3002, 18896,421, 7859, 4497, 4058, 35729,3098, 1042, 35731,2538, 161, 161, 161, 35730,161, 35732,35734,35735,8472, 2526, 350, 1060, 4497, 304, 20330,2378, 301, 161, 2191, 1913, 222, 35738,4203, 35739,11556, 21065,35740,740, 2150, 35742,35747,304, 418, 213, 161, 35743,414, 161, 180, 6299, 35744,35746,35745,296, 2976, 1336, 161, 1042, 5469, 409, 35750,16391, 16070,1270, 35751,161, 414, 161, 1756, 1343, 161, 161, 19748,19426,161, 161, 35753,1847, 35756,35761,3827, 295, 6393, 35762,35763,161, 35765,304, 1639, 31997,35754,35755,716, 1757, 35757,161, 35759,3013, 35760,1343, 295, 161, 350, 35758,1523, 933, 19622,2111, 1302, 3198, 1876, 1343, 418, 161, 161, 26729, 23368,161, 35764,1818, 35766,295, 1371, 35768,421, 3098, 35770,35782,1756, 6393, 1487, 213, 161, 35784,295, 20671,35769,1892, 414, 1143, 1891, 4373, 1042, 161, 1459, 35771,35775,35778,6340, 5069, 874, 1727, 35780,35772,35773,474, 35774,3169, 1042, 161, 161, 161, 161, 35776,161, 17067,35777,474, 32360,1479, 304, 1896, 35779,35781,35783,35785,35787,35788,35790,295, 3315, 8656, 295, 161, 161, 35786,1054, 1012, 5631, 409, 161, 161, 556, 35789,1372, 35791,2024, 417, 311, 35794,1161, 35795,35797,35798,35799,35800,350, 421, 474, 421, 5483, 35802,35805,4733, 1343, 35807,35813,1027, 35824,35852,35859,35865,414, 35900, 35928,35929,1109, 35933,35934,35803,556, 35804,1480, 1042, 161, 27411,20065,696, 35806,696, 304, 35808,35809,35810,35811,15108,24382,414, 1886, 1143, 35812, 35814,35818,420, 1824, 35822,35815,35816,35817,35819,421, 608, 35820,161, 35821,35823,2672, 4879, 9733, 35825,5469, 35827,35830,35831,35832,14749,4461, 35839, 4133, 35843,5469, 35846,35847,35849,161, 35826,295, 418, 608, 35828,2191, 35829,414, 357, 35833,161, 35835,35836,440, 1343, 1143, 5156, 35838,417, 414, 35834,3002, 28558,1109, 1042, 1604, 161, 35837,28558,1851, 6435, 35840,6393, 295, 35841,35842,1109, 35844,35307,35845,213, 6046, 35848,295, 1343, 161, 1730, 1093, 35850,304, 35851,161, 29983,417, 35853,35856,35858,2864, 4898, 161, 35854,414, 1609, 35855,213, 161, 35857,1198, 736, 350, 35860,2017, 4073, 35864, 295, 8585, 161, 35861,21381,35862,3435, 186, 35863,1876, 16329,161, 7303, 4346, 35866,186, 35888,35889,7880, 1042, 35890,35891,295, 161, 24542,1093, 35898, 35899,518, 213, 5060, 35867,35868,1639, 35869,35870,35872,35873,304, 35874,35875,17124,35877,35880,35884,35887,3194, 3227, 3195, 161, 419, 420, 418, 35871, 421, 21702,8136, 740, 2166, 161, 6003, 161, 185, 11819,1042, 420, 161, 35876,1640, 15579,35878,35879,304, 679, 161, 35881,311, 2196, 35882,418, 35883, 161, 1143, 35885,35886,1297, 409, 296, 4630, 304, 1829, 1143, 3029, 3315, 3850, 35892,35893,35895,1343, 3227, 35896,8136, 32716,35897,161, 213, 295, 161, 35894,440, 1143, 213, 161, 213, 418, 417, 1126, 1886, 5953, 418, 1583, 35901,35906,4898, 5839, 35907,35912,35914,161, 19739,35915,35918,35924,35902,35903, 35904,35905,414, 35869,1042, 3833, 9291, 4922, 933, 414, 35908,635, 7885, 35910,5376, 9305, 874, 2763, 35911,933, 1150, 35909,1270, 4314, 2976, 1437, 185, 35913,213, 304, 414, 213, 161, 2195, 5882, 186, 14120,1042, 440, 1851, 5069, 1609, 161, 1640, 1640, 35916,35917,4284, 629, 1876, 3013, 35919,35920,440, 1851, 435, 1280, 1459, 35921,35922,186, 1042, 35923,161, 35925,440, 1343, 418, 161, 1459, 3196, 35926,5702, 22654,161, 35927,1757, 1480, 35930,14091,35931, 1161, 9172, 161, 35932,350, 186, 1727, 35935,35936,35938,35939,35940,35941,35943,474, 35949,35958,35970,1042, 304, 35974,35944,556, 1101, 35945,35947,35948, 161, 35946,28908,161, 35950,35951,304, 35953,35954,35957,2040, 5647, 35952,1938, 1538, 1042, 161, 35955,35956,35959,35963,35967,35969,1150, 35960,440, 35961, 35962,4491, 35964,161, 35965,35966,161, 440, 35968,418, 161, 4263, 24910,1270, 6354, 1109, 35971,35972,1027, 975, 435, 23368,161, 35973,608, 11980,186, 3654, 213, 161, 35975,35978,35979,35981,35982,35983,5088, 35984,2272, 35987,35989,35991,30852,2442, 24396,1181, 350, 35985,408, 35986,161, 3085, 35988,35990, 750, 474, 161, 1042, 11459,3730, 35993,35994,1412, 35996,35998,35997,35999,36000,36001,36003,36004,36006,1150, 36007,1173, 1426, 1539, 36008,36009,36010,36011, 14078,36016,36017,408, 22308,1161, 350, 3185, 304, 18494,678, 161, 304, 678, 4754, 3928, 304, 1107, 161, 418, 296, 1823, 5342, 36012,36015,301, 26723, 1036, 1590, 295, 36013,36014,435, 161, 706, 161, 465, 36018,161, 608, 556, 36020,36022,36025,36028,36031,36032,36036,36039,36040,36042,36045,36046,36048, 1343, 36049,36055,36021,36023,36024,36026,2142, 36027,29569,161, 2106, 36029,36030,750, 18899,36033,556, 36034,36035,9557, 556, 36037,36038,3620, 418, 36041, 36043,36044,36047,36050,573, 36051,36052,161, 36053,161, 36054,36056,36058,36057,573, 213, 36059,36079,36120,36123,36154,36157,36159,3976, 2331, 36165,36166, 36197,412, 36201,36204,36223,36229,36232,36241,36060,687, 3603, 4291, 36061,7007, 2362, 36074,36062,36063,36064,36065,36066,36067,36068,36069,36070,36071,36072, 36073,36075,36076,36077,36078,1640, 36080,36081,36083,36086,36087,36088,36089,36091,36092,36095,36096,36103,36110,36111,36115,36117,36118,36119,1813, 14128,36082, 36084,1910, 36085,435, 440, 426, 1342, 301, 418, 1173, 418, 295, 519, 161, 296, 222, 161, 304, 2218, 1017, 161, 36090,7791, 1270, 161, 36093,36094, 301, 36097,519, 5223, 161, 2272, 301, 19308,36098,12567,36100,2755, 36101,1342, 4712, 16071,36099,608, 36102,1474, 14935,36104,161, 36105,36106,36109,414, 9069, 161, 301, 409, 36107,1876, 161, 161, 36108,161, 36112,36114,295, 1343, 22396,16728,29481,36113,13284,1882, 1011, 161, 440, 19039,6256, 418, 7623, 519, 36116,301, 519, 3729, 414, 12859,36121,2182, 36122,36124,36132,36133,36134,556, 435, 4688, 36135,1109, 36140,36142,4570, 36143,36146,36150,36151,36152, 417, 4354, 36125,4042, 1343, 440, 36126,350, 36131,9516, 996, 23933,414, 36127,36128,36129,420, 11754,14723,36130,5239, 301, 1609, 161, 8160, 161, 14913, 418, 3928, 1343, 418, 161, 414, 1014, 161, 161, 16042,4374, 1590, 36136,417, 36139,161, 5497, 1876, 36137,161, 421, 36138,161, 3087, 9057, 7511, 6118, 36141,301, 213, 417, 1590, 295, 36144,36145,161, 417, 1635, 6122, 1459, 3721, 1498, 36147,36148,750, 3721, 296, 161, 36149,161, 1343, 10233,2199, 36153, 36155,36156,36158,17225,36160,750, 36161,36163,36164,36162,3976, 1270, 350, 161, 14093,28769,350, 414, 36167,36168,36171,414, 36172,36173,36174,36176,1934, 36179,36180,933, 36181,36183,17383,36187,1639, 36195,1458, 350, 36169,36170,1459, 1459, 161, 4779, 26718,1899, 9924, 440, 36175,1459, 36177,161, 8265, 36178, 418, 304, 295, 161, 3922, 1949, 213, 161, 161, 36182,213, 161, 414, 4307, 418, 161, 36184,36185,4461, 36186,301, 161, 519, 19310,519, 2755, 14391, 32784,161, 1640, 11497,24694,3144, 36188,161, 36191,36193,36194,355, 304, 418, 304, 414, 36189,14527,7168, 1093, 161, 36190,32826,426, 1343, 1843, 1109, 7937, 1013, 1609, 414, 1036, 1847, 36192,5089, 679, 2117, 420, 18280,440, 36196,9724, 301, 161, 519, 36198,36199,36200,36202,5519, 36203,418, 36205,36206, 36208,413, 36213,36214,36216,36217,3610, 36218,36219,36221,36222,36207,706, 36209,36210,36211,36212,19566,1028, 8265, 36215,2225, 1458, 350, 161, 1851, 10486, 36220,519, 15085,36224,36225,36226,36227,36228,36230,36231,36233,36237,36234,36235,36236,36238,36239,161, 36240,36242,2394, 18618,36243,36319,5393, 36320,36362, 36369,36404,36408,36409,36429,36432,36434,36456,36467,36473,36474,36244,36252,36260,36262,36263,36264,36268,36269,36270,36275,36277,36286,36292,36296,36299,36305, 36306,36309,36310,36314,36316,36317,36245,435, 3827, 36246,36247,36249,301, 690, 301, 36248,302, 2538, 36250,7859, 36251,36253,2480, 36254,36259,6944, 304, 414, 36255,36258,1343, 213, 36256,36257,252, 414, 1608, 1608, 161, 161, 296, 1173, 36261,19095,296, 18896,8246, 1851, 161, 36265,36266,296, 4688, 304, 474, 36267,2390, 519, 295, 302, 19160,36271,3928, 36273,36272,36274,2537, 29213,36276,295, 2539, 608, 2539, 36278,36281,296, 36282,36284,31163,1727, 161, 36279,36280,4937, 418, 301, 13643,25150,36283,36285,161, 36287,36289,20508,2271, 36290,161, 2127, 9064, 420, 161, 36291,36288,2340, 301, 161, 36293,628, 1109, 36294,36295,2797, 1044, 301, 3861, 1538, 301, 1126, 2238, 36297,458, 27477,36298,459, 161, 458, 36300,14022,27470,36302,4575, 36303,1829, 36304,36301, 304, 2374, 36307,36308,1223, 2028, 556, 295, 36311,36313,36312,161, 1044, 301, 7859, 36315,36318,36321,36323,36325,17477,36326,679, 36327,36329,1198, 36332, 36340,36341,36348,36350,13182,36353,36357,36358,414, 7497, 36322,36324,3964, 2538, 301, 9162, 161, 36328,161, 2082, 17254,295, 14527,36330,36331,295, 161, 36333,36334,36335,36336,36337,1956, 36339,301, 12859,301, 1060, 418, 161, 35821,417, 2755, 36338,1426, 304, 35608,295, 418, 4500, 304, 18806,36342,36343, 161, 36345,1036, 295, 36346,161, 36347,4461, 36344,161, 1787, 161, 161, 1367, 295, 161, 36349,1862, 36351,36352,36354,1563, 295, 36354,18689,36355,161, 36356,22681,3222, 7494, 1302, 1042, 161, 2094, 2225, 161, 1590, 161, 36359,161, 435, 36360,36361,9227, 161, 36363,36365,36366,36367,36368,161, 36364,161, 7947, 161, 161, 161, 36370,1653, 36373,679, 36379,304, 36380,36383,36389,36396,36397,36400,36402,10481,36403,36371,36372,36374,36375,36377,36378,21143,161, 4695, 35679,1640, 435, 36376,295, 3827, 304, 1372, 301, 296, 3797, 5410, 11574,409, 36381,36382,23008,296, 1156, 161, 36384,3827, 36386,36385,5396, 36387, 36388,296, 304, 1371, 161, 4898, 301, 36390,36392,36394,11424,1851, 36395,36391,36393,296, 161, 1126, 304, 9679, 434, 21158,36398,36399,8909, 161, 252, 161, 5253, 434, 3720, 36401,301, 707, 36405,36406,36407,22034,1862, 434, 409, 36410,36411,1787, 36413,418, 1223, 36414,36418,36420,36422,36426,36428,2797, 161, 414, 36412,1060, 161, 5837, 161, 301, 36415,1893, 1343, 686, 296, 36416,1343, 295, 36417,466, 36419,161, 1425, 161, 36421,311, 36423,36424,36425, 161, 161, 296, 1042, 3771, 36342,1343, 161, 36427,1432, 5572, 1829, 36430,36431,36433,418, 36435,36436,36439,36443,13595,36444,36445,36447,36450,36451,1343, 36453,1639, 4478, 36455,5561, 15341,301, 304, 36437,19140,36438,818, 2516, 13643,36440,3730, 3112, 36441,304, 36442,1640, 2596, 36446,36448,1935, 519, 301, 36449,519, 1797, 3510, 10078,434, 36452,2538, 2270, 421, 36454,161, 36457,36459,36460,36462,36466,36458,3547, 13922,350, 36461,4737, 36463,7280, 434, 36464, 36465,36468,36472,573, 36469,3252, 36470,36471,409, 10134,16783,1862, 421, 7847, 36475,36476,36477,36478,161, 36479 }; static const unsigned short Ranks[84307] = { 0, 3239, 3240, 6134, 3651, 6622, 2187, 1326, 373, 1053, 166, 6241, 4380, 2469, 5805, 1692, 5353, 632, 6621, 5354, 1927, 2591, 2817, 3264, 3492, 5444, 6133, 4792, 7019, 2468, 6017, 6240, 5692, 5445, 4381, 5907, 4333, 5282, 4047, 1654, 7018, 2649, 3696, 2618, 5691, 4334, 4559, 5379, 2823, 3338, 5938, 6535, 1033, 474, 3589, 4296, 2794, 3737, 6271, 5549, 5303, 3221, 4749, 7055, 3962, 1470, 5378, 3665, 3819, 2767, 4868, 5547, 4560, 4685, 5078, 5228, 5079, 3924, 2198, 6761, 4869, 5637, 5550, 6650, 6758, 67, 342, 18, 1565, 169, 5376, 4137, 1425, 5720, 4448, 5473, 6270, 5380, 4011, 1803, 5639, 217, 1400, 3870, 4010, 3499, 4683, 3923, 6161, 6048, 6648, 4623, 6759, 6762, 5937, 5227, 5470, 6904, 5471, 6272, 4495, 6649, 5936, 5833, 4684, 3625, 268, 69, 883, 3368, 5377, 4807, 4136, 5469, 6533, 5546, 6393, 5080, 1943, 5472, 3588, 3707, 2127, 4686, 4751, 5302, 6532, 3069, 6394, 4393, 52, 4625, 4392, 6395, 344, 4, 838, 5834, 2078, 6, 2, 23, 3, 45, 865, 6392, 3922, 4752, 3395, 1841, 952, 4496, 5301, 677, 2197, 5548, 5375, 387, 4624, 468, 4748, 5141, 2658, 4750, 6906, 3963, 359, 97, 1576, 5722, 2605, 3037, 5638, 1377, 2976, 3186, 7053, 2001, 912, 6160, 3339, 6760, 3038, 6651, 6905, 517, 1461, 6391, 6534, 1751, 7054, 5721, 2846, 2838, 1068, 5518, 5796, 5601, 1671, 736, 6720, 4128, 2419, 6721, 6496, 1446, 6497, 2646, 1445, 6495, 4043, 5196, 2949, 1429, 2837, 3991, 7001, 6999, 6722, 6856, 2278, 1814, 3992, 4280, 5197, 5342, 3692, 4281, 3990, 3907, 3908, 3906, 3693, 3993, 3422, 2758, 2356, 2559, 2156, 3139, 2319, 1994, 1888, 2702, 1272, 1438, 1679, 1003, 1526, 1149, 690, 3579, 3285, 1437, 1637, 1444, 1156, 1515, 6857, 1627, 1412, 1256, 1767, 4848, 1430, 1459, 2184, 2243, 3613, 1478, 4076, 2420, 3647, 4719, 3084, 4718, 5600, 4541, 4786, 4847, 4540, 4482, 5053, 3722, 2206, 1049, 6355, 3358, 4042, 5517, 7000, 4203, 5797, 6127, 5602, 3945, 4846, 6126, 6858, 1356, 2560, 422, 424, 6859, 7005, 503, 1935, 3359, 7002, 5684, 1950, 19, 3525, 5343, 775, 5117, 1399, 3208, 3648, 5275, 6356, 7003, 1466, 921, 4720, 6010, 5898, 267, 831, 967, 539, 3695, 6011, 314, 1638, 232, 4434, 1387, 5198, 6498, 5276, 3909, 6723, 6861, 5897, 791, 297, 3113, 2786, 3209, 6609, 4787, 1439, 621, 4663, 1479, 3694, 1404, 4661, 3172, 6860, 7004, 1500, 708, 4849, 4662, 4166, 4077, 2279, 6499, 3171, 1639, 4542, 3387, 5685, 3555, 6734, 6874, 6247, 5811, 4286, 5283, 4171, 2840, 3860, 3425, 2542, 1122, 3995, 5917, 1816, 2967, 2759, 1084, 6627, 5695, 420, 3289, 384, 4215, 2075, 6024, 4988, 3328, 1564, 5696, 2760, 1996, 3728, 1501, 6372, 3530, 6370, 6368, 4440, 6025, 5612, 5124, 5918, 6873, 4610, 6628, 5810, 6513, 5812, 7023, 6514, 3028, 4336, 3390, 2298, 923, 4285, 3656, 4383, 2653, 493, 5915, 3455, 1589, 3389, 5916, 5450, 458, 6369, 4287, 2871, 316, 3655, 461, 6371, 1962, 6023, 2652, 1570, 2039, 6515, 2790, 5125, 5356, 1195, 5950, 4761, 6285, 3245, 6782, 5307, 5953, 5085, 4633, 3710, 6171, 6925, 2480, 5645, 6545, 6660, 5394, 4101, 5952, 5154, 2882, 2231, 6784, 2957, 231, 4876, 6546, 1004, 6284, 3192, 6929, 4811, 3709, 7071, 4303, 6283, 5644, 6927, 4184, 6282, 659, 5951, 4057, 2797, 6783, 6926, 2933, 443, 499, 308, 4016, 1125, 4140, 3435, 3009, 1944, 3673, 6928, 5308, 6170, 6781, 4302, 5084, 6169, 7070, 3928, 7069, 5155, 4242, 6659, 4875, 6168, 4947, 4243, 2529, 5235, 4690, 46, 4503, 4946, 453, 3151, 53, 5153, 1394, 6414, 5309, 2572, 5477, 5842, 4183, 42, 13, 335, 4240, 6413, 7068, 6061, 6542, 3400, 4182, 3466, 6658, 5232, 2142, 5948, 4399, 6411, 4100, 3927, 1620, 6409, 7067, 4810, 4632, 276, 1621, 241, 180, 956, 3191, 5734, 5643, 3150, 6778, 6779, 4567, 697, 5233, 6062, 4015, 5234, 5949, 5642, 4241, 7066, 6281, 5841, 5152, 2141, 6543, 6410, 4760, 6923, 5947, 227, 6924, 380, 5392, 602, 2714, 408, 6544, 244, 4566, 3041, 6412, 5393, 1541, 6780, 6777, 3636, 6449, 3232, 3161, 6586, 5033, 4525, 3601, 3576, 2134, 3635, 262, 953, 1104, 4263, 6964, 3251, 576, 2166, 351, 4647, 942, 6310, 7020, 6729, 45032,45652,1932, 3129, 5180, 1282, 1833, 4154, 6691, 428, 947, 4362, 89, 5580, 1957, 6097, 608, 45652,45032,45652,43174,41441,39974,43744,45032,42925,39007,44526,43429,42526,6313, 41441,38794,41891,41263, 44106,42031,42031,36318,42182,40237,41890,3473, 36965,44106,1010, 44526,7218, 38775,3199, 26306,32692,45032,34878,28220,43743,42925,44105,5579, 44105,6448, 20121, 34156,26010,33849,30082,38552,38270,39783,40696,43743,34878,20120,33578,33133,25416,6585, 34156,28219,18520,32930,9595, 44525,7111, 9120, 34490,40764,44525,44105, 12370,40032,40093,31984,36318,34489,45031,8801, 38887,14, 4648, 5035, 5991, 612, 6588, 564, 1911, 437, 565, 5865, 983, 32691,26662,34877,33849,24643,40126, 45651,39871,36965,40953,39040,44105,39803,44104,43429,7822, 32929,16887,36965,7840, 41096,33342,36318,29566,7561, 34156,31983,15528,33578,22235,33133,34877,36318, 36965,33342,16810,2414, 31982,13387,19499,45031,43743,44525,39921,16851,43174,928, 39783,39828,8637, 38909,31981,35303,43429,39056,38286,35786,41650,45651,37772, 30725,16850,15875,36317,14268,2236, 42925,42526,43743,38464,43173,43173,35786,43173,40642,36317,44104,45651,39338,41441,38845,42925,44525,43173,37688,42924,38023, 44524,33577,41649,7450, 45651,22554,35303,21396,8008, 35786,28218,13123,16849,43429,44104,5665, 44104,18425,35303,14402,32690,25698,39783,36317,21276,40126,40446, 44103,36317,44524,38736,45650,39402,42720,18519,36316,19036,43742,2832, 38357,37792,45650,40764,41440,44103,45650,39628,39402,45650,42924,45031,42345,43742,40594, 21662,2177, 40236,38518,44103,45649,36964,39827,41890,44103,40593,33342,30724,38461,39277,43742,43428,40195,39803,44102,42345,42720,41440,43742,38854,43428,42182, 38603,6314, 41263,31321,31980,40593,41177,40269,42924,44102,41649,38649,41096,43428,37868,39019,39141,38137,38592,39628,44, 4264, 853, 5990, 41353,40494,42181, 39871,43428,39122,32929,37838,39428,38429,43741,38655,44102,45031,45649,45649,43741,40158,41177,42924,39999,40126,39487,41759,44102,38883,42181,38505,38438,45030, 43741,39645,40354,627, 41263,45649,37999,41096,41649,41440,38003,42720,39921,38887,40541,39681,39048,38518,44524,42719,43741,45648,42031,38660,40953,44524,39039, 38638,38494,38167,44101,45648,40593,38342,39871,6587, 4526, 31979,36316,40593,33342,6311, 5099, 36316,40157,45648,30081,33341,35302,12736,19850,30723,39561,44523, 34489,28217,40236,23897,38924,39577,39518,41649,40881,33577,1865, 39850,45648,45647,43427,36316,33849,33341,5034, 43427,38663,4195, 43427,43172,34489,43740,14075, 32689,13150,22710,22895,36964,34877,36315,27794,16027,20329,26661,38803,40195,44101,45647,41648,6312, 42345,41019,45647,41263,33577,33132,18706,42526,12417,42719, 42719,43172,39237,41177,39084,31320,39681,39203,41353,40157,40642,41542,40157,40592,16397,39428,35302,42923,37889,44523,45030,38254,40592,37868,356, 45647,38290, 34489,39487,39352,45030,44523,41019,45030,45646,2435, 41759,41542,43172,38680,38373,17795,38247,43427,38719,38551,41095,38415,38229,42719,44523,37721,45646,20025, 45646,45029,14778,42526,45646,3839, 39502,8446, 34877,42181,11090,13523,8000, 10886,7210, 38676,3548, 5179, 42345,36964,39608,45029,35302,23684,14764,27033,18039, 33341,35302,35785,20915,11989,20453,13988,7766, 40764,44522,42718,44522,36964,31319,14025,25697,34155,33577,33132,38530,44101,34155,34155,36963,4523, 38981,38976, 45029,40494,40494,8631, 38935,17521,18915,13549,43172,38741,39019,44522,44522,44101,37977,39203,39486,40881,39266,23469,39189,45645,8828, 9490, 9100, 8641, 9888, 9213, 8745, 9450, 9730, 10072,21661,36963,24145,34488,25164,11420,45645,42181,40126,44521,31978,34876,25415,27032,42031,44521,41759,357, 41542,9972, 35785,39222, 45645,41095,42525,43171,41440,40696,39091,38727,42923,39338,45029,39352,39141,39802,44521,33849,12313,19664,20563,19766,42525,14069,44100,33576,34155,558, 4524, 40157,40236,42344,38871,45645,40881,40353,44521,39486,43171,3474, 44520,40093,38265,39140,39486,42525,41759,39680,41542,39031,39741,37828,39783,42344,38213,40763, 38686,44100,44100,45644,34876,42923,40881,39337,43171,40592,7625, 7685, 30722,33132,3602, 38672,38391,41262,38534,45028,40494,38362,42344,38672,39627,38995,40953, 16919,34154,43426,41758,39608,44520,9852, 7353, 8635, 9632, 9531, 9145, 11353,10885,8525, 41541,34488,43171,1111, 40236,41353,2533, 38437,38600,40952,44520,39699, 42180,38009,45644,39486,6963, 44520,38318,41890,39375,40493,42525,40592,42030,44100,41262,45644,40235,42180,41890,42923,45644,38898,37882,44519,41889,38570,39026, 41352,44519,44099,40235,39180,41352,41352,44099,39719,41758,34488,43426,34154,45028,41758,36315,44519,40032,45643,43740,7110, 43740,43740,45643,32929,34876,42180, 45643,32688,40493,38047,38415,38261,38512,39179,38667,40309,44519,40093,38772,40396,44099,43426,41758,42922,42180,45028,42524,42718,43426,39607,43739,41648,42030, 41541,34876,37988,41541,42344,42718,44518,43425,40195,45643,41095,41177,43170,45642,37762,1580, 38588,44518,42922,37712,44518,40396,42179,37888,43425,39921,41541, 45028,42718,39644,38318,10061,37707,37774,40541,20793,36963,28216,33848,27031,9197, 45027,33341,38903,13943,44518,42922,32687,39898,38198,41889,38272,40763,37906, 41648,38285,26660,38489,6315, 1899, 42179,39898,43739,41176,39802,38696,42179,41262,45027,45027,44517,43170,28215,39472,43739,39375,45642,43425,45642,33576,35785, 8357, 15102,11793,43739,40696,38995,45027,30721,34488,23468,34487,17603,44099,33848,30720,35301,37850,40952,37940,38987,38159,40541,38909,34875,42717,41648,11189, 32686,44517,33340,43738,36963,34487,30719,16026,30080,11792,19765,15506,19849,12281,13770,32685,30079,29107,26305,31318,12685,32929,27402,7622, 9822, 35301,6965, 45642,42030,45026,42179,45641,27401,37782,18914,37971,15392,38475,39291,40540,41095,43425,43424,43424,35785,36315,19353,35784,30718,36962,33132,26009,7867, 34875, 34487,7962, 16592,18299,38813,40831,36962,34154,15372,36315,34875,36314,34487,44098,20217,33848,27793,8674, 38119,40831,11046,42717,39663,39850,38438,15711,33340, 43170,14047,16495,36962,2534, 28647,31977,4704, 11419,8792, 11045,36314,42524,44517,43738,34875,42030,39945,8621, 44517,39827,43738,42029,43738,37795,34874,36962, 39590,32928,14506,41439,35301,42029,43737,43424,353, 2269, 5989, 34486,41439,40880,43424,42178,964, 34154,40763,42922,34874,44516,44098,38812,43423,39545,43737, 5864, 45641,668, 40269,37984,42717,40032,42524,40591,40695,40235,42029,41757,45641,42029,4153, 45026,2552, 9009, 38314,42178,30078,29106,36961,19352,35784,36961, 38409,41019,10012,43737,5032, 31976,36961,42921,24893,19266,31975,35301,12892,33576,29565,33848,10404,11352,45026,15033,13279,3076, 33131,36961,35300,35784,22709, 41094,38306,6909, 45026,771, 44098,41176,5724, 45025,26304,6049, 9391, 11870,25163,24642,42178,34153,21152,7230, 39203,8897, 11500,17286,13942,9594, 43423,36314, 34874,15391,36314,38123,44098,39763,40831,43170,39401,39502,482, 41757,45641,4297, 24383,34874,11712,9064, 44097,35784,26008,16559,41018,39974,19102,4627, 24641, 35783,24144,2930, 40952,19498,21395,41439,43737,24143,36313,35783,30717,14925,29105,7195, 42921,8468, 9751, 29104,10277,10071,31317,7389, 17141,33576,14731,26659, 43169,18638,9618, 6539, 2199, 36313,13836,13098,16180,21275,38646,39974,40446,27792,31316,15438,17072,17557,41889,16591,6050, 40642,37992,38521,45640,45640,5939, 30716,34873,27791,26303,38247,38789,40763,42524,31315,15101,33847,33847,26658,44097,9483, 26007,34486,19497,29564,21394,36960,7871, 20792,19496,36960,14365,17028, 31974,7835, 36960,33575,34873,33131,13378,13149,10921,7907, 45025,9212, 42921,33575,11089,21934,10580,41352,11566,4095, 8899, 26302,10131,7416, 21660,27790,40540, 14644,34486,9948, 9223, 33847,32684,28214,38373,40065,42717,45640,43169,9631, 10967,24892,9583, 6396, 11351,18518,35300,9530, 9305, 9996, 10348,30715,7292, 40591, 9285, 5941, 413, 12047,43169,1409, 8527, 40493,22894,8852, 45025,39849,9810, 264, 33575,34153,1917, 42523,18842,13598,6908, 44516,31973,7437, 7891, 12734,7262, 44516,9004, 11565,7388, 31314,42716,26657,39849,15613,41757,42716,45640,36313,42028,43169,35783,43736,7662, 42028,10219,35783,39471,40880,3340, 36313,38736,41757, 10218,8192, 41262,5384, 14380,8293, 9803, 38208,39849,40540,40880,42716,1894, 24382,44516,41439,41351,45639,41351,38363,39870,9322, 8941, 33340,36312,32928,8714, 10550,8620, 10845,10813,8481, 38614,8961, 38448,40952,11006,37793,10669,10884,20791,39444,5304, 10630,39444,44515,18841,21274,35300,36312,7373, 14240,36960,33847, 33846,28646,23073,5723, 23072,34153,24640,11044,42716,39471,39099,40591,45025,39827,45639,45639,34486,10494,35782,5386, 43736,18195,35300,11981,44515,5381, 11638, 41438,33131,24142,34873,9128, 29563,45639,33340,38483,39304,45024,39485,33846,29103,36959,41756,13774,5385, 16741,41018,41647,34485,33131,35782,17027,41176,2710, 45024,8869, 41438,39485,40235,42343,28213,32928,32683,10702,26301,30714,27030,8367, 39802,6162, 45638,15666,2709, 20328,31972,6764, 41756,45024,45638,42523,37958, 45638,39091,41647,21273,37959,38803,39098,41438,35299,13563,38803,30077,33575,36312,27400,2569, 43423,4626, 6653, 35782,28212,35782,45024,44515,24141,42178,42523, 45023,38518,45023,11955,8880, 8336, 32682,24639,36312,45023,15190,22708,17071,35781,36959,20452,9776, 14660,22094,10742,36311,36311,38924,21151,21659,15303,40446, 16464,37775,30076,30075,36959,42177,44515,3300, 34153,11954,37739,37786,27399,39237,42921,8141, 7529, 41540,41647,33574,40540,42715,40234,45638,45023,32681,45637, 33339,31313,19351,515, 737, 6399, 3536, 41351,42177,45022,45637,28645,7816, 7693, 6275, 1051, 41647,35299,37732,30713,37771,39945,45637,36959,36311,37780,33130, 24891,33574,32680,12725,18781,8730, 9529, 17411,38565,36311,6911, 35299,33574,33846,39062,44514,41176,41261,39304,34152,39921,36310,45022,39007,44514,44514,38450, 39644,44097,40951,35781,10377,31971,36310,8894, 21150,6397, 37924,29102,35781,33574,22234,4054, 39945,21272,13807,33846,32679,36310,36310,35299,38891,45637,5143, 22233,33845,7462, 35298,45022,42715,32928,34485,3591, 45022,33130,40234,40092,44514,7794, 45021,36309,12336,19942,23683,21933,12159,7666, 33130,33130,10579,33845, 18705,35298,36309,19941,11293,15048,18780,35781,34873,31970,7304, 9230, 32927,19940,35780,36309,23896,39607,33129,45021,37715,35298,8970, 9706, 11350,10523,10347, 9843, 9066, 8573, 44097,33339,40591,36309,36308,36308,9887, 34152,1760, 7934, 33845,14436,45636,28211,11711,35780,8996, 45636,19764,8181, 39782,2632, 21658,34485, 30074,28210,45021,15710,31969,44513,34152,33573,44096,17794,34872,44513,481, 2878, 14686,6765, 2424, 33129,13987,39545,38784,43423,40641,37822,39501,1739, 38831, 43422,43422,8280, 38914,2059, 42523,10920,5382, 42343,42920,44096,44096,42343,39000,40493,39078,45636,44513,22893,34485,16809,8044, 5387, 36958,27398,29101,36308, 38163,41756,20684,31968,39337,40065,23274,11637,35298,36958,8467, 34872,30073,34872,38315,43422,28644,45636,38945,42522,25696,39162,42920,25695,33573,341, 38981, 3341, 682, 44096,43422,45021,38227,45635,31312,30712,33339,16952,13052,36308,32927,2877, 5726, 2824, 44513,19582,22093,21516,43736,9536, 44512,42522,18358,25162, 43421,22707,43421,8893, 11636,6274, 6051, 35780,5835, 18038,10173,34484,44095,34872,11791,33845,28643,40641,33339,41756,29562,20562,10606,36307,39230,24890,40831, 45020,14883,39070,38591,45020,41351,40880,45020,42920,29561,35297,41438,24638,38134,39113,6537, 6273, 41175,23682,33129,36958,34484,23273,42343,7547, 38145,45635, 39741,43421,9006, 41437,42522,44095,45020,44095,40092,44095,41437,42177,42715,45635,43736,36958,31311,36957,43421,45635,30711,29100,36957,5383, 33844,2570, 4181, 34152,45634,45019,38976,43168,23681,16025,39920,39849,39471,42522,38564,45019,34151,8173, 39375,34871,33338,8321, 45634,9582, 36957,44512,3146, 42920,42342,36307, 42028,43735,45634,1161, 1014, 11499,40446,1597, 44512,38610,40234,42177,41350,39091,39680,39999,11234,45019,42919,45634,43735,44512,2265, 10027,6536, 8998, 42521, 30710,36957,44511,39999,13413,41094,39140,20119,23895,38919,40951,43420,45633,43420,42176,42176,38209,45633,42028,32927,36307,45633,45019,36956,36307,11418,35780, 34871,33844,14014,12839,11966,42521,43735,33129,17026,19763,40951,38253,40830,28209,18637,34871,34871,45018,45018,9471, 30072,12920,36956,35297,33338,7346, 42342, 8222, 4012, 8057, 21657,36956,10376,32678,34870,32927,7453, 18037,18583,43420,7176, 42521,1577, 12125,5725, 9337, 35779,34870,34870,44511,4232, 45018,34151,45018, 15335,33128,25694,20790,35779,7401, 39763,7057, 2682, 41175,42919,29099,39827,42027,39920,43168,44511,41261,45633,45017,10276,40396,42176,2606, 33573,35297,20561, 7337, 38784,847, 8170, 42176,4941, 10773,10883,10493,35297,8768, 10522,8523, 10375,35779,8543, 8761, 33573,35296,9051, 9031, 16396,9928, 33572,7433, 44511,36306, 30071,44510,187, 33128,4497, 11498,38089,43420,35779,31967,33128,32677,36306,8463, 43735,11953,41755,44094,42342,35778,44094,35778,11497,36306,10521,9902, 45017, 36306,34151,39561,14401,41261,40695,45017,45632,42919,34484,36305,40309,44510,37844,40156,30709,16617,40195,44094,15690,2847, 4394, 34870,9680, 36305,7930, 10049, 45017,313, 44094,38122,2546, 40951,1483, 40353,43734,11132,42342,11088,9441, 8277, 9802, 11564,10322,11563,45632,33572,28642,38591,45016,39456,23467,32676,36956, 8189, 39256,8174, 39561,9821, 10452,8909, 44510,10701,10812,44510,10578,11043,9449, 33844,36305,34869,8226, 42919,9995, 11952,9294, 35296,36955,10844,9261, 8316, 40950,10668,8238, 40194,3820, 42341,44093,42521,11635,10451,11188,41540,38159,9034, 38521,38970,38456,41018,41755,40830,39782,39518,42027,44509,39485,38285,42715, 42175,38689,45632,42520,39303,38316,44093,25693,43419,44093,40445,42918,40695,42520,40950,38529,41540,43734,44093,37726,41540,26300,42520,24140,35296,20216,18087, 17655,26656,45632,36955,27789,34151,32675,37775,39561,38194,38213,37958,45631,42520,45631,42918,41261,43734,33844,45016,44509,5229, 33572,39898,4561, 40641,44092, 43419,36305,36955,41646,31966,45631,40695,33843,45631,36955,7163, 40268,1730, 5142, 4498, 40590,45630,40590,38333,40395,39039,40353,45630,44092,39517,39351,40762, 39719,45016,40445,45630,42714,39091,43168,43168,44509,33338,36304,45630,36954,8093, 9098, 41889,38898,39589,42714,42175,45629,34869,41350,43734,44092,35778,27397, 45629,41646,29560,7599, 8495, 35778,7642, 33843,7954, 19663,34484,35777,11496,9366, 34869,36954,8554, 10549,10048,34150,7239, 38469,22385,7792, 32926,36954,31310, 39179,40879,36954,35296,33338,25414,7870, 45629,36304,35295,1731, 41350,42714,39998,45016,45015,38887,38220,42918,10145,42714,42027,37771,45015,38499,41888,38501, 37757,37750,38391,39121,41539,37702,38957,44092,38053,37753,38529,37808,36953,29098,20914,36304,36304,27788,6910, 5940, 41437,786, 17841,37864,44509,37835,41755, 39179,45015,42519,25161,36953,39782,44091,35777,27787,41018,42918,32674,39802,34869,40308,34150,39699,41755,14676,32926,278, 45629,6912, 2879, 36303,43167,38642, 39246,43419,37888,38669,41888,38726,38089,10919,13676,39107,45628,42519,42519,41350,39945,39741,42175,41754,39256,37894,42917,38437,43167,39189,40032,38185,38461, 39196,38696,40125,38241,41017,45628,15978,33128,26006,42519,45628,43167,42917,40445,34868,41094,23272,43167,38499,41754,40065,40445,43733,39740,45628,42713,39236, 286, 1415, 5008, 28208,32926,32926,16463,35777,14497,42175,34150,35777,3627, 254, 43733,16024,16314,34868,40879,40694,45015,43419,40762,38541,39456,38951,39627, 24889,18249,38408,39291,40590,38882,44508,38019,38923,42713,45627,41646,45014,37819,38745,38970,41646,38882,40395,41175,40194,42713,44091,40830,44091,38245,39196, 39974,38940,38257,45014,41260,40762,38485,38350,38757,41349,44508,42713,38923,44508,45627,35776,5836, 43418,8283, 4808, 6652, 39351,44091,44090,10843,40641,3626, 43733,33572,42174,44090,39944,2098, 45627,41260,18984,32925,14223,26005,11005,40830,40234,40492,43418,41175,8974, 10842,9688, 35295,44090,43733,44090,13634,1087, 29559,18636,25692,34150,29558,42917,23071,4754, 17889,14675,18913,34868,26299,45014,31309,16023,36953,30070,35776,31308,31965,45014,41888,40879,41260,36953,16395, 31307,40829,42917,26004,18840,42916,35776,39531,45627,4138, 27396,43418,14153,10011,33843,17209,24381,6398, 43418,45013,44508,18779,34483,31306,5474, 41437,33571, 45013,45626,45626,43732,43732,12836,45013,7056, 42027,25160,14924,33337,7463, 34149,5552, 39236,42174,41349,39973,41645,39920,14317,33843,43417,36952,19848,33337, 4096, 23466,17025,11562,11951,10811,10202,11634,8029, 44507,9630, 9629, 25413,16991,36952,29097,30708,34149,45013,37955,41754,2526, 38891,33127,26655,27395,36303, 29557,35295,13227,42712,37758,17744,18424,24637,35776,22092,24139,30069,27394,34149,41349,34868,9315, 14372,33571,33337,11004,34867,33571,6538, 11292,43166,29556, 39291,18248,39763,45012,36303,42916,38120,39363,20024,17793,44089,17840,36952,43417,38283,36952,38136,43166,42916,38909,33842,43166,41017,9421, 10882,10403,10966, 13009,11349,33571,44089,10881,7821, 9876, 4449, 44507,44507,45626,45012,30707,39680,43732,43166,21020,35775,43417,44089,42026,33842,42341,32673,28641,611, 6907, 33127,4753, 36303,34483,36951,10520,11561,23894,9149, 8075, 10605,8364, 11003,10429,10070,8119, 11233,8985, 41017,41349,11710,39998,36302,5551, 41645,42026,33337, 43165,35295,7665, 10880,11131,7379, 3007, 23271,36302,36951,42026,17654,43732,38257,35775,36302,33842,16343,41888,43417,15764,36951,18704,24380,19939,34483,9640, 40590,36951,15178,45012,10841,42341,20451,18517,41539,34483,32672,38962,6763, 42518,16616,41539,42341,37907,14400,32925,4094, 12130,43731,27393,38000,41348,34149, 34148,14659,36302,35775,28640,20215,14091,10251,11633,23465,33336,39607,45626,10519,26003,12114,41754,8111, 42712,20023,30706,24636,34148,35294,7830, 27392,26654, 22091,40879,41539,7849, 33570,30068,17285,32925,42518,35775,42340,37714,45012,42174,40353,41260,27391,35774,35774,33570,45625,40950,39303,33570,38316,38270,38389, 38271,39920,28639,36950,33336,34867,41259,33570,45625,13548,28638,26298,33569,15010,36950,37919,37964,38940,39763,28637,36950,41259,15287,36950,36301,28636,31964, 12931,16236,34482,38877,42518,38395,38084,43165,38794,38934,39589,38595,35294,7791, 3098, 40492,42518,17173,3590, 33336,12830,33842,44507,43731,17700,6913, 45625, 177, 3145, 14344,33841,41094,45011,38626,39627,44506,7734, 43165,36301,35774,14763,33569,3222, 45011,24138,34148,23270,35774,42026,40694,39213,42340,41887,7189, 41887,28635,28207,42517,45625,38187,45624,38857,40539,43731,9639, 40589,40352,39517,41436,35294,34148,39801,14132,1187, 41259,45624,39417,42712,38850,24635,39740, 20789,38588,42340,7415, 40268,40268,25159,34867,40444,44506,36301,33569,28634,30067,10577,39531,27390,30705,19495,19432,40156,42712,36949,29555,19662,31305,8074, 38891,35773,36949,42174,36301,38945,28206,3039, 36300,23464,29554,19431,40268,30704,36949,15505,35773,21149,43731,43730,42916,39973,41436,40950,32925,31963,34867, 44089,1559, 1416, 173, 4628, 3535, 860, 310, 651, 78, 5837, 1987, 446, 1566, 2633, 3537, 34482,23893,5306, 13180,9927, 45011,33127,38104,11087,36949,20450, 26002,17699,13513,14289,23892,15286,9820, 14297,38506,14658,33841,11790,8881, 9124, 8204, 39456,2452, 10201,9750, 30703,22706,9762, 10700,24137,35773,15785,40395, 36948,42340,9687, 39699,42711,28633,33841,36948,14730,9435, 872, 4298, 38044,4099, 42173,35773,37767,13596,31962,39230,37791,19265,33336,38284,35294,36948,41017, 35772,37950,33335,11789,36948,33127,33569,28205,30702,41174,39501,39801,4629, 29553,41887,42711,35293,43416,41753,4349, 38603,41538,42711,36300,35772,34482,41016, 45011,1583, 1860, 45624,41093,41645,39145,43416,40589,38297,39337,40878,41887,2712, 39848,30701,36947,40031,40267,44506,45624,41886,39140,41645,45010,45010,42025, 45623,38641,40065,44088,42339,39740,39255,38710,1616, 41, 44506,1752, 3070, 39589,38581,41174,38175,39179,45623,45623,38871,38666,38052,43416,39973,40762,40539, 43730,466, 43730,38545,39375,38775,42517,38779,42173,41753,42173,37950,38206,40267,38191,39132,39485,10810,39826,40949,38166,38613,43165,41174,42711,5145, 1666, 41753,36300,43164,34866,1870, 40829,42173,6164, 43730,31961,27029,17698,45010,41753,40395,44505,42172,21393,42339,35772,42710,45010,40878,3925, 6767, 41016,9507, 3343, 6917, 43416,39826,39870,44088,45009,42025,40444,42915,1797, 45623,25412,40492,10402,42710,39291,43729,45622,42172,41538,34866,44505,36947,35772,33335,15474, 16532,43729,34866,27028,18139,26297,30066,33335,42710,34482,34866,36947,35771,41436,43164,833, 2977, 34481,15267,45622,19938,39544,44088,40539,39401,43729,4233, 5728, 41093,32924,32924,42710,34481,34481,21271,12401,12514,26653,887, 1954, 1030, 4499, 43729,38800,41644,26001,40031,1657, 41436,41348,45622,41174,6276, 45009, 42517,42709,44088,43728,45622,43728,42025,40064,40394,39944,42517,38312,43728,43728,40394,41886,37914,38134,40308,44505,38581,38591,45621,41752,44505,36947,18423, 29096,25691,38244,38550,39189,39290,44504,38375,39627,39544,40394,39443,38831,43164,37860,42709,40352,21932,30700,41886,38248,42339,45009,5942, 4396, 41435,45621, 38009,44087,42172,37910,39531,38913,39078,38241,40829,38626,41435,38119,40829,42172,41886,41752,43164,28204,18778,14612,34147,13409,20118,34865,32671,18516,7567, 23891,20913,1417, 35771,45621,36946,7060, 24136,16462,38497,43727,44504,44087,43415,40761,35771,18839,18194,3871, 30699,3434, 44087,10548,38181,41259,4098, 44504, 40444,42171,44087,38366,45621,43415,39848,39374,44504,40761,44503,5943, 8811, 10450,8314, 40031,11950,11949,9714, 21515,22892,36946,34481,36300,16531,6165, 42171, 45620,36946,43415,44503,30065,322, 44086,8993, 21656,20327,10449,7804, 45009,43727,34147,7920, 33126,34147,35293,36299,36299,35771,19581,34480,15847,19847,23680, 30698,33335,27389,10772,30697,30696,37863,40064,39222,42516,38700,45008,9357, 24634,43163,9901, 8991, 7752, 42709,35293,8649, 11042,34480,34147,42915,33334,35293, 33334,35770,32924,44086,1673, 21931,30695,43415,40949,36946,45008,34865,66, 39644,2741, 5230, 33126,3147, 38680,39626,5553, 42516,18468,22232,39303,45620,45620, 42025,39106,44086,45620,38399,38264,39374,40031,39131,45008,42709,42024,31960,45619,44086,38595,44085,41348,38771,40394,40828,40949,39113,38919,45619,45619,39401, 42339,45619,29552,45008,45618,42338,41435,42516,38752,17450,15437,8770, 29095,26652,41752,26296,36299,27027,8847, 41348,44503,38042,3433, 40694,23890,670, 13705, 35770,27388,36299,16431,30694,39213,925, 41644,45618,41435,17556,10401,33841,34480,34146,35770,22705,40194,42516,1877, 45618,41538,18983,38580,43414,34865,24888, 38365,45618,19264,31304,34865,18036,30064,35770,34146,36298,44503,41752,41347,41644,7682, 45007,26000,44502,45007,5010, 42171,12493,13786,29094,25411,18703,34480, 8868, 35769,25158,44502,40694,43163,38068,42515,39121,38349,43163,36945,44085,11869,22231,40352,43414,39589,39337,38059,41016,40267,40156,45007,45617,33334,38300, 40828,38831,37962,39588,37710,45617,36298,44085,832, 37892,38186,43727,38149,9775, 38204,43163,41885,43414,3121, 42338,40092,33334,39084,42338,38951,45617,41173, 45007,33333,32670,27786,6916, 45006,25690,3668, 41016,42915,32669,34864,21930,33333,11348,12412,36945,10217,10275,33333,45006,22704,16430,36945,36298,18582,12550, 9994, 32668,33840,32667,28632,32666,35292,20788,18422,27387,14379,35292,43727,34479,36945,11560,20449,6540, 36944,30693,452, 6053, 16848,34146,22230,45006,6054, 4562, 42915,44085,3667, 44084,10771,19846,3099, 11417,45617,5944, 45616,28203,44084,36944,34479,34479,4055, 41885,10297,13562,16847,40352,38902,35769,13404,820, 33568,16208,35769,5305, 594, 10274,33333,35769,28202,38000,36944,14643,36944,28201,18193,33568,36298,35768,42024,6768, 44502,42024,29551,40125,37721,45616,38043, 34479,3538, 3243, 2425, 4871, 803, 3397, 527, 45006,937, 1015, 44502,4687, 44501,39973,38760,41751,39131,13132,4013, 42515,35292,34864,43414,36943,3666, 24135, 15070,24379,35768,27026,38505,44501,40064,41173,27025,33332,14554,21270,34478,33568,34864,36943,36943,27024,35292,37693,42024,5009, 1878, 43726,43162,38511,18581, 41538,21929,39351,39351,41885,42515,44084,38303,41644,42914,29093,45005,38593,45005,25157,18192,35291,36943,38986,41258,42708,44501,38622,39644,40194,45616,43413, 40589,38994,43162,44501,26295,43726,36297,1732, 42171,16371,40030,38412,43726,38854,12856,42708,35768,28631,39662,41015,44084,34864,41434,44083,6404, 36297,29092, 35768,462, 157, 6163, 44083,44083,2548, 2174, 18912,30063,23679,42914,15545,38255,483, 43162,39718,4235, 40351,20214,2742, 26294,35291,36297,27023,16071,35291, 38036,3502, 42708,41537,41434,42338,31959,36942,35291,32665,31303,27022,35290,13983,36942,45005,13313,16696,41885,44500,44083,36942,43413,16784,30062,39374,6654, 33568,35767,33126,38779,40267,45005,36297,13824,6655, 40949,1426, 4139, 42337,33567,28630,41751,43726,34863,29091,13785,44500,33126,31302,35767,34863,45004,41751, 7059, 38133,36296,14399,29550,44082,39388,6278, 36296,24378,17653,31958,36296,34478,35290,16461,44500,18421,34478,33567,38090,38779,33125,19430,12013,43725,3628, 33567,39848,45616,39188,18982,25689,33332,36296,43413,43162,17652,36295,13522,22703,28629,14982,27021,28200,44500,14591,12747,33567,34146,20326,27020,38461,42914, 40589,22891,34145,35290,23678,42337,36942,38730,38669,35290,11990,42170,6914, 45615,44082,41537,39290,42708,29090,39172,18981,20213,19429,41884,33332,25999,43161, 42707,36941,41015,38303,39898,42170,40878,25998,36295,34478,34145,35767,6402, 35767,26293,34145,36295,17449,36295,19762,35766,44082,38515,38551,34145,35289,34144, 15231,24377,34144,36941,34144,41884,36294,35289,41751,34477,34863,32924,30692,28628,16116,28199,33840,12261,39401,43725,22702,22553,14166,35289,26651,24887,41015, 32664,45615,34144,33332,33125,35766,36941,25997,44082,33566,24376,36941,35289,12245,40693,33331,41643,42515,34863,33840,19428,31957,27785,12341,32663,42914,34143, 42913,36940,32923,18357,19845,893, 40693,33331,34477,34862,16951,38808,16313,33566,40588,31301,29549,33840,4395, 28198,16115,39090,40492,35766,41884,45615,2931, 2904, 40393,42514,16139,29089,39998,40948,44499,44081,39484,616, 6769, 44081,27019,36294,45004,40444,35766,22890,35288,43161,41537,27784,23889,36294,36294,33839, 35288,39699,40761,34862,22229,25688,32662,38839,42913,44499,18420,27783,43161,33566,35765,42707,34143,4757, 39303,34143,33839,43413,23463,28197,36940,33125,19937, 4299, 45004,36293,683, 44499,35765,30061,35765,32661,9567, 45615,42707,3669, 41884,35765,33331,32660,33839,45614,24134,42707,44499,136, 6277, 2528, 3539, 18911, 32659,35764,17070,37957,40761,14316,39588,45004,39400,43725,41537,42170,42514,39026,42514,14628,16494,30691,44081,31300,25156,23070,34862,24633,35288,753, 3501, 39576,4097, 42337,25155,34862,43412,38945,1167, 45003,44498,44498,19936,24375,41093,36940,40351,39762,18247,40351,37932,40828,36940,42023,2547, 45614,40878,35764, 32923,36939,20560,39178,36293,41093,44081,45003,39315,40193,45614,12154,36939,15086,41750,33839,34477,39544,3500, 27782,42337,21655,44080,14855,18419,36293,39517, 45003,1454, 16886,40125,22889,15230,32923,33566,43161,4300, 11495,36939,39560,20448,23269,14789,45614,43160,39826,41883,41092,29088,36293,11868,34861,36292,42706, 19192,13363,33125,36292,16695,35764,36939,20325,35764,35288,25687,36938,36938,34861,9248, 42514,15784,4688, 28627,36938,10809,8145, 38784,42170,41258,40760,35763, 32923,41258,34477,45613,29548,22552,38636,42513,42513,43725,42023,14308,45003,43412,12258,45002,10741,36938,30060,32922,45613,40156,44080,34476,904, 43724,34143, 18515,43412,14470,13800,17379,8821, 39471,36937,45613,36292,35287,35763,29547,2683, 4756, 24133,43724,24632,14267,36292,33565,25996,40351,43724,25995,23462,35287, 16138,35287,15763,25154,33331,36291,25410,19035,38520,42706,6401, 23268,2956, 36937,15783,34476,37970,36937,36937,33330,44498,36936,39972,42169,14148,44080,3503, 30690,25686,38353,39919,39416,41643,23267,38830,39782,36291,13718,42706,39277,27781,15782,18418,11948,36936,36291,22384,27018,34142,44080,34476,33330,5082, 44498, 39006,33330,34476,35763,35287,44497,41015,12241,45002,34475,32658,35286,44497,39236,14165,36936,28626,33838,7058, 13927,35763,35762,23266,30689,36291,5144, 12809, 28625,42706,34861,26292,38266,24132,34142,26291,44497,13110,34142,36936,16740,12030,33565,12203,14748,154, 39801,4870, 45613,28196,13539,14716,27017,34475,35762, 35762,36935,17944,16656,16530,42169,27016,33565,42913,13861,42513,35286,15117,40491,45612,33565,45612,29087,12569,23888,39944,34475,40491,16655,6656, 32657,24631, 28624,14819,20447,36935,40443,25685,38676,43412,43160,34142,40393,40640,41173,22888,12737,20912,36935,35286,26650,15999,12208,43160,32922,21392,35286,42336,42913, 42513,19101,11041,24630,24374,16050,17990,29546,41536,24131,35285,40266,16207,38866,42912,45612,30688,13761,29086,42705,33838,35762,12584,44497,18086,21019,13488, 14715,29545,29085,7277, 41014,5475, 42912,45612,38129,34141,31956,36290,30687,36290,35285,42512,21803,7628, 40491,18980,23265,36935,18035,19263,15709,20683,38424, 42705,41643,42023,21514,36290,13833,35285,22228,33124,36290,34861,36289,14006,33330,36289,30686,32656,36289,30685,31955,33564,33124,38640,41883,12564,45611,13531, 24629,27386,3342, 30684,36289,39416,6766, 32922,35761,32922,33329,25994,36288,33329,34475,32655,12379,33838,24130,34474,34141,27015,26290,42023,23069,36934,35761, 36934,43411,16783,34860,21148,35761,36934,36934,42169,34141,25153,33124,31299,20212,35285,31954,31298,38074,6052, 9, 43724,5727, 45002,38051,26289,44079,43723, 42705,38107,44496,34860,45002,39222,33564,33838,36288,17989,14548,18702,41883,6400, 42336,36933,40693,27014,35761,31953,38287,38062,39530,7702, 16493,11709,36933, 31297,42169,41347,19761,36288,38385,36933,42512,18085,1378, 902, 1632, 44496,40308,23461,33329,15860,42168,32654,23677,34474,31296,21928,33564,33837,36933,25684, 42912,12363,34860,3370, 13276,34474,35760,34860,26649,31295,33329,42168,23264,961, 17988,6770, 25993,30683,25992,473, 40948,38375,42912,36932,16739,34474,33564, 25683,42512,31952,45001,33563,35760,35284,36932,35284,32653,45611,45001,32652,38923,16694,14882,31951,24129,13386,12221,33837,18298,38141,40350,40350,45001,2162, 40030,44496,37930,42168,39588,39826,39213,25152,35760,14563,15403,33328,33, 43723,2527, 43723,40443,25682,21927,35284,36288,39221,15492,20911,15324,41536,34473, 44079,15189,34859,39897,40539,38799,42911,30682,33837,34859,30059,38566,41347,29544,33837,36932,26288,39230,35760,20211,23887,36287,12656,44496,43723,40193,44495, 15323,42168,44495,39718,19494,14058,36932,19493,19760,27385,34473,44495,44495,27013,18514,35284,41643,43411,20910,25151,45611,12571,40125,35759,40538,36287,14110, 35759,18034,14096,25681,35759,33836,5146, 45001,36931,13895,13982,15047,23068,28623,23676,29543,15960,36931,28622,25150,15216,36931,34859,19844,3778, 33836,33124, 34859,33123,13351,34858,34858,42705,32651,22227,43160,33836,43722,42911,42022,31950,15805,12454,44079,43411,29084,41883,15009,28621,3369, 29083,19427,16693,31294, 36287,23460,25409,31293,15565,34858,33563,33123,15824,21802,33328,42911,36287,42512,15898,23263,37685,36286,42704,42336,29082,18910,37685,24628,19100,36931,22551, 45611,36930,35283,42704,35283,34141,3821, 6915, 1641, 2002, 6403, 580, 5147, 2711, 2362, 5081, 2426, 2903, 45000,9870, 35283,35283,456, 4234, 21654,43722,37684, 2634, 45000,34140,25680,30681,38564,45610,41434,44494,19843,27384,37684,39718,41642,34858,1152, 36286,21926,19099,40233,4755, 45610,13350,14981,27012,14590,36286, 36930,15527,13717,16235,42511,3396, 35282,42336,33563,16070,39972,12583,6613, 33836,15846,2185, 2894, 38401,41642,228, 43159,5439, 40828,2260, 3526, 4604, 27383, 12491,34857,41173,13661,36930,4044, 41258,34857,16137,35282,39740,42704,38853,40640,1995, 5603, 32921,35282,33835,12460,17172,1664, 2013, 2726, 45000,32650,40193, 37857,11187,38569,5056, 41257,38185,41882,3946, 403, 5345, 42335,35759,18246,8311, 11559,8756, 38293,14729,44079,12941,14013,7736, 28620,36930,37684,37684,14427, 23886,27780,41014,25679,19426,22550,45000,35282,14957,33123,36286,16258,25149,25408,44078,11186,36285,13926,19098,23459,15457,31292,41642,44999,40538,3614, 37761, 38939,863, 44494,23458,33563,292, 45610,13760,281, 4377, 33562,27779,11708,41750,37683,33562,29542,38032,45610,37696,5903, 30680,36929,13739,29081,27382,36929, 26648,34140,36285,16136,28619,17839,12225,35281,37683,29080,12308,35758,32649,29541,37683,14398,44494,18777,2071, 11002,10172,34140,31949,23262,17888,13749,22549, 34857,19580,44999,44494,42704,21269,35758,34140,34473,14477,41750,8423, 42335,5801, 15188,37840,38271,38125,24373,20787,42703,43159,15456,45609,42703,34857,43411, 41536,32648,37683,44078,36929,16312,44493,14994,24128,36285,34139,6725, 21147,32921,32647,22548,36929,22701,35758,34856,28618,41172,42167,43410,44493,44493,37951, 44493,39230,36928,38891,44999,39131,30679,43159,38789,36928,18417,11632,45609,13769,7008, 4981, 26647,17284,41882,36928,34473,45609,33123,25678,23261,33835,38328, 1725, 44492,45609,42167,28617,44999,27778,34856,25407,25406,35758,6500, 15436,44998,44492,20446,16492,3486, 37696,19425,45608,43159,38763,40827,39560,44492,41642, 42022,285, 42335,2787, 37682,3769, 36928,6236, 14871,17792,39162,20210,16022,42335,43410,5348, 39544,44492,1158, 42703,25677,36927,4722, 42334,44491,37709,40350, 34856,43410,43722,35757,34856,38272,42911,13569,22887,42511,33562,44491,42511,41092,38357,42334,38142,18033,37923,45608,43410,42511,43409,45608,37885,39781,38389, 14435,34855,44998,41750,39825,33328,38569,38429,24372,30678,38369,39246,41536,39121,38038,44491,41092,39416,43409,42510,44998,35757,37782,44998,27777,19191,20909, 36927,28195,26646,34472,21513,37682,38928,43722,43409,44078,18356,41535,32921,28616,23067,14476,34855,33328,45608,31291,36285,35281,36284,33835,35757,34855,21268, 36927,40393,35757,33122,41434,43158,22886,21267,33562,36284,38789,37718,45607,38685,36927,29540,43409,34472,33835,37682,31290,35281,35281,36926,32921,35280,33834, 18701,37682,37681,33834,39121,44078,44077,6234, 35756,26645,13417,34855,34854,43158,33834,12883,43721,18580,29079,45607,9409, 19579,44491,31948,29539,33834,28194, 36926,29078,41172,39680,42910,27011,10296,45607,38436,39221,39350,37710,37975,39501,43158,38178,38237,36284,27010,8441, 44997,35756,18297,42022,44997,43158,23457, 31289,7010, 36926,16692,35280,6726, 11416,19935,12011,42334,9260, 44490,3723, 4665, 37681,6724, 30058,37681,13529,33327,28193,27776,33833,22383,36926,34854,41014, 44997,9141, 38913,34139,32646,36925,18838,42167,33561,25405,43157,25991,41641,45607,23066,37681,41535,41749,42334,33122,37680,16738,32920,23456,24886,21266,22382, 14526,33561,26644,30057,33327,44490,36925,37680,33833,43721,18084,38580,37680,36925,34472,1701, 44490,45606,44077,36925,4544, 22547,35280,32920,43721,37680,37943, 38512,35280,40588,39470,38073,23260,38483,38322,24627,35756,35756,34854,33122,38239,44490,42333,38913,44077,15069,34472,33327,13012,33561,34471,39374,44997,19190, 28192,41535,44489,44996,9325, 41749,27381,26643,33122,22546,33561,33833,45606,22885,31947,16615,37679,36284,7343, 38700,17106,24626,44077,36924,31288,13538,14378, 6501, 19759,35755,26287,38418,44076,43157,45606,45606,35755,33833,23455,38473,21512,34471,35755,42910,27380,12688,36924,32645,44996,39626,36924,33327,35755,5519, 40640,36924,43157,42703,38489,28191,18635,8068, 34471,36283,40588,45605,40948,41347,23259,15665,38156,42333,38621,44996,37679,36923,37679,39607,34139,30677,41749, 36923,33560,22090,44076,40640,36923,28190,5119, 17697,16654,1257, 6359, 3261, 16179,35754,30676,21391,37679,16614,44996,39801,31287,35754,8879, 20559,13226,7615, 35279,35754,7207, 36923,44076,5057, 45605,45605,35754,34854,17337,33560,33121,14273,17486,12028,39626,23885,40308,35753,36922,7471, 42022,19661,22884,27775,19842, 34853,35753,36922,7479, 41014,5351, 29077,33832,35279,13433,35279,34471,34139,36922,27774,32644,36283,33121,35279,33326,33832,12255,44995,38485,34853,36922,45605, 37678,36921,36921,32920,12649,28615,29076,12610,45604,35278,35278,16691,33832,30056,30675,34853,34853,17987,32920,36283,29538,37678,3487, 43408,43721,44076,25148, 30674,27009,35278,17743,21511,43157,43156,33326,32643,36921,32919,34470,37678,36921,43720,45604,43156,33121,24625,36283,9993, 22381,44489,34138,36920,25676,27773, 41092,2895, 37678,12620,44489,23884,35753,32919,43720,24885,34470,34470,16558,12723,13001,27379,22226,37677,27378,29537,30055,22089,27772,18355,4435, 18837,13547, 45604,18245,33326,35753,19424,29536,34852,37677,33560,15526,36920,34852,25675,34852,35278,21146,27377,33560,4483, 19492,34138,37677,17986,26642,37677,17410,33832, 44075,37676,34138,24371,725, 34138,35277,41257,41091,13932,33326,31946,36282,32919,33831,38289,27376,13602,33559,11232,21390,37676,33831,8531, 8830, 13128,7278, 43408,11231,34137,21389,11707,11001,24624,23258,7698, 34852,32642,37676,34470,16885,35277,45604,10667,19097,34137,31945,10448,30054,22225,16049,36920,44995,8313, 7771, 41882,9293, 13860,23883,9528, 37676,25674,34137,13738,35752,34851,33831,31286,33121,36282,36920,32919,32641,25147,37675,36282,36282,23065,39484,14462,43156, 34851,12112,17555,15646,40307,44075,33559,36281,7531, 8979, 36919,35752,36919,37675,30053,44995,8297, 24623,21925,26641,37675,29075,9063, 29074,33559,33559,36281, 29073,33325,34851,32640,33558,7988, 14968,21388,35277,33325,14747,23064,24370,9036, 42510,36919,35752,36281,25146,34469,34851,31285,40760,44995,34469,37675,22224, 36281,38357,37820,45603,35752,36280,45603,44994,9180, 35277,31944,34469,24884,18032,25404,8360, 44994,27771,34850,37727,4205, 42510,23257,42333,42167,42510,42021, 42910,2998, 44489,27008,14222,34137,43408,45603,39202,34469,35751,8339, 39025,11347,10699,8529, 37674,37674,14360,10144,33831,33830,41749,40393,30052,42910,19758, 40266,33830,38389,38726,42166,38169,42021,39188,42509,43720,38882,36280,10840,8556, 30673,8718, 44488,16206,35276,34850,37709,41535,30672,42021,32639,37674,36280, 8672, 34850,9306, 38789,31943,34468,16157,33325,24369,36280,13160,8155, 41641,11788,6610, 8817, 37762,15708,36279,37674,11947,8494, 31942,25990,45603,37673,30671, 10808,33120,19757,30051,34136,32918,37673,35276,34136,22088,39530,41346,18416,10321,9079, 31284,8214, 23256,37673,31941,25145,26286,29535,30670,16069,19756,39025, 36279,43408,40639,19578,16205,37673,13804,35751,22545,35276,34468,34468,37967,37672,25144,32918,44075,44075,37672,24883,35751,42909,35276,36279,27770,27769,33120, 36279,37672,34468,33325,34467,31940,31283,42909,40693,1316, 8815, 39246,17140,34136,44488,17069,1318, 16491,40760,36278,4721, 18513,35275,27768,18031,26640,27375, 30050,11185,38428,28189,40760,40155,42909,33558,37672,17171,41257,43720,41433,32918,33558,16808,43156,40193,29534,19660,29533,15068,22700,31939,36278,21265,34850, 21145,45602,2072, 43155,39739,37671,38934,34136,34135,14456,30049,43155,44994,42166,33830,36278,41091,17485,44994,42166,18415,31938,36919,35275,25403,33830,24622, 23454,36278,34849,36918,34135,26639,37671,34467,32638,34135,21801,33120,34467,25143,30669,32637,35751,33829,36918,16884,26638,34467,36277,32918,35750,41257,29532, 41346,44993,4985, 4980, 37671,42166,40827,2674, 7007, 1600, 2589, 33829,4436, 3910, 2244, 5686, 3947, 3484, 4210, 943, 43407,4376, 5605, 315, 6863, 2951, 27374, 3580, 417, 5200, 6235, 160, 4282, 132, 5118, 5347, 76, 4599, 14525,3488, 984, 35275,37953,18634,18836,37976,1157, 28614,3086, 45602,44488,13235,4982, 4723, 1236, 42909,16653,4045, 45602,34849,5199, 5054, 17520,20022,36918,709, 589, 2997, 2647, 45602,35750,7006, 780, 4167, 42333,43719,42908,6358, 4283, 4206, 569, 5799, 34849,2519, 6862, 2157, 3065, 153, 5604, 5278, 37671,36277,3262, 43719,40639,38083,42509,4080, 5901, 39443,42509,2207, 40877,33829,30048,44074,33558,36918, 2963, 36277,19350,32917,32636,34466,28613,41346,18700,45601,25673,45601,43719,44993,7295, 39517,6727, 34466,43407,5440, 35275,4664, 40155,41882,41641,19755,37670, 36917,11494,43155,44993,33120,8414, 41433,35750,37670,39944,37670,13078,31282,14131,33324,17651,38448,40092,43155,39315,15762,36277,28188,34849,20558,37670,35274, 7009, 14553,14604,7214, 38700,2109, 759, 1852, 10115,15943,33829,36276,44488,24368,44993,35274,33828,18979,4788, 42165,44487,2788, 37669,40692,39255,36276,14580, 39428,43154,35750,34848,18835,19349,41881,1012, 19577,40491,32917,36917,12976,44992,35749,25672,1317, 3650, 44074,17791,41256,15589,33828,41534,5798, 2921, 29072, 36917,33828,18083,40064,42165,36276,34848,43407,35274,45601,45601,42509,44992,43719,45600,44487,44487,40307,45600,38279,40759,41641,4204, 43718,24367,25402,34135, 44487,39325,45600,43407,42332,42165,44992,42165,45600,14535,36917,30047,33557,34466,17650,33828,35749,12795,40948,4046, 44992,44486,4790, 30668,306, 42908,33557, 37669,1825, 34134,38219,20557,44486,24127,44074,16613,37669,18909,42702,37669,22883,10400,21924,33324,41748,21510,27007,35749,33557,16782,20556,27006,37668,29071, 21018,31937,37668,20324,38008,37850,40759,5344, 4918, 39516,40877,42332,42508,40091,42908,37668,25142,35274,44486,31281,34466,25989,16557,26285,40947,1206, 39679, 21387,34848,35273,41346,37668,18138,12156,44486,33119,44485,6612, 44074,37667,35749,41881,33324,17943,34134,23255,27767,15664,27373,16781,34134,40639,122, 42508, 1901, 3174, 307, 40124,40639,4725, 37985,56, 4603, 43718,38898,39039,36916,20323,38680,41256,45599,36276,39315,36916,38361,43718,32635,39698,41640,33557,27372, 22699,20908,15525,34848,33324,39718,35273,36916,14842,34847,15524,28187,18414,16652,38372,41013,29070,2245, 37667,4078, 24621,31936,37667,29531,6614, 20682,23675, 34134,37667,45599,36916,33119,33323,40947,41345,44991,44485,40692,1680, 44991,114, 41256,1405, 36915,34465,44991,45599,42508,3085, 42908,42907,12598,1691, 6864, 1915, 39897,44991,44990,23674,29069,44990,34465,35748,44990,17378,2520, 44990,45599,36915,6615, 1177, 44073,1827, 5902, 5349, 22223,33556,33556,38345,39098,40350, 38744,13561,27766,13212,35273,18579,35273,37666,42164,4207, 41881,1961, 17696,33827,22882,42021,34465,18244,24126,19754,31935,23254,12495,15371,1837, 39560,45598, 1246, 6013, 32634,1431, 44485,21923,38051,27371,33323,17377,7618, 44989,44485,12290,38994,15100,18191,25988,34465,43406,7446, 40192,4983, 23063,14234,25141,19423, 17519,35748,27765,24882,19934,33556,19422,19034,19262,35748,13779,3173, 37834,38633,41013,35272,38845,38230,33119,19189,8517, 43718,34464,37666,35748,9542, 12605, 8457, 33827,36915,15204,35747,30667,13211,12844,26637,17602,35747,26284,27370,19188,28612,13792,30666,4601, 31280,35747,34133,12498,38337,38981,42702,27369,45598, 16257,43406,31279,25401,12904,42164,43154,21386,31278,33556,35272,21144,37666,33555,32633,20786,34133,36275,37666,22544,44073,41256,24881,21653,38287,39698,882, 45598,40233,43717,35747,40266,42702,45598,44073,21264,33119,32632,33827,21017,23673,34464,34464,26636,23253,36275,15229,42702,37665,3485, 41345,44989,34464,23882, 304, 5350, 41013,33323,35272,40759,25987,39643,41172,41172,44484,39897,33118,27368,33827,43717,27005,35746,39919,13408,37665,38620,41881,41345,38094,40192,41880, 44989,45597,25140,36275,44484,38981,42508,6357, 41171,44484,35272,35746,34463,38195,1826, 38447,33826,39025,37665,41880,37665,12589,37991,24880,35746,32917,29068, 33826,34133,42020,45597,42164,41880,36275,38001,39516,40124,39943,40947,39717,40192,40759,40192,40692,40538,4917, 601, 40538,43717,39679,44073,5900, 33118,43406, 40758,44484,31934,13604,34133,42332,43154,41640,42907,36274,45597,44072,5606, 43717,33323,36915,857, 44989,40349,41091,5899, 42907,18978,24620,13451,42020,42507, 41534,45597,38733,14496,36914,45596,38980,36274,38424,14870,35746,25986,6012, 41091,22543,28611,36914,32631,37664,19753,1889, 498, 44988,11184,922, 991, 45596, 34132,2648, 6611, 37794,40638,41640,39025,36914,42332,43716,44072,42701,43154,41255,29530,40692,45596,44988,43716,40827,42020,44988,41171,36914,43406,44072,18512, 38020,13163,25671,17518,43716,16846,42020,35745,41090,42019,20681,44072,43716,37664,28186,24619,21385,11000,44071,43715,42019,43153,44071,44483,44071,21800,20209, 13202,39090,44988,37869,44987,31933,44071,40758,31277,4208, 42907,40947,38227,135, 45596,158, 38220,40758,38361,44483,42331,31932,33826,32917,22087,5800, 40827, 44483,164, 43153,5346, 4789, 4079, 1628, 6360, 1423, 5904, 4602, 4600, 4984, 4724, 5055, 45595,5277, 40826,39350,844, 2342, 2950, 3114, 1815, 3649, 5058, 4543, 36274,1357, 42701,40443,43405,41345,39943,44070,1000, 4666, 1064, 4331, 45595,40349,35745,37664,44987,38799,38788,44070,26283,43153,19576,32630,13778,37664,20785, 17105,33118,19491,27004,33118,29529,42507,44070,42507,30665,14714,43715,41013,4667, 39643,42331,41748,39972,40349,40877,42331,30664,39501,39276,31931,43153,40307, 44070,16845,33117,30046,34847,10698,35271,4209, 5520, 39006,37663,37663,36274,36913,37663,22698,42331,44483,37900,41090,40030,44482,38350,44069,16590,40758,39131, 40191,34463,34463,41880,37890,40155,33322,4531, 39428,3894, 42164,16114,21263,15998,38595,15761,22222,7385, 39400,3893, 44987,35271,22221,789, 5672, 34463,8786, 8424, 40691,45595,41748,15588,45595,1146, 37663,7223, 40443,2463, 10085,868, 4528, 3846, 34847,21922,11706,35745,41879,34462,1243, 2753, 34132,35745,36273,43715, 40691,18633,34847,35744,37662,34846,31930,14832,29067,25139,34132,37662,34462,34462,36273,22697,17554,24125,16370,41255,37662,18776,28185,34132,37662,43715,35744, 42507,36913,21384,41433,38154,27367,39870,24618,18578,23453,37661,33555,35271,19187,21262,35271,37661,37799,39221,38886,39698,35270,30045,34462,36913,36913,33117, 26635,37661,35270,37661,36273,36912,33555,15544,29528,24617,27366,36273,1269, 32916,25985,33826,34131,35744,34846,36912,27365,36272,25670,23452,33322,21261,32916, 24366,36912,37660,35744,37660,36912,16048,33322,33322,36911,36272,33825,14980,23672,19659,29066,2180, 45594,44069,21652,34131,26634,17649,28184,2809, 42330,27003, 43405,18467,36272,40638,44482,41748,12837,37660,16556,22696,5780, 35270,44069,35270,21260,38771,43405,35269,34846,23881,21259,20445,14442,23880,36911,42330,1835, 21651,31929,34461,29065,28183,31928,34131,24616,25984,35743,15473,34131,34846,35743,27764,24879,35743,28610,36911,21799,37660,24615,22086,34461,37659,24365,37659, 35743,39055,34845,35269,39972,4325, 44987,45594,39588,40063,41433,6464, 45594,45594,43714,33321,36272,33555,37659,22695,34845,15689,35742,22694,34130,18466,11183, 35269,25669,15587,25983,18030,38808,12934,44482,34461,17601,44069,36271,25982,19096,14881,33825,33825,36271,36911,15322,31927,32916,22542,33117,13713,33554,19841, 35269,23879,35268,17648,39998,14296,14455,17985,37659,2892, 23451,44986,26282,37658,33321,37658,37658,25138,23252,30663,28182,15845,14090,35268,20907,45593,435, 1635, 35742,33554,34845,4653, 35268,42019,44482,35268,26281,15435,22380,40490,29527,34130,44986,12776,39825,24124,19840,31926,20444,19933,38015,44068,32916,22693, 45593,27002,45593,4036, 39825,1147, 26633,3552, 39315,40757,44986,38939,40091,37658,31925,33554,40392,30044,36271,33321,35267,37657,20117,36910,6110, 22692,21016, 35267,36271,30662,37657,3414, 43714,12449,45593,273, 588, 39388,28609,38757,13544,44481,21258,26280,1507, 45592,30043,41171,1474, 45592,27763,35742,27364,1534, 5333, 1397, 34461,25981,37657,25668,18632,44986,33825,24364,40349,11415,36270,37657,6829, 28181,33117,4365, 45592,16460,29064,33554,31924,32915,29526,34460,13041, 36910,18137,34460,42906,35742,35267,8627, 1993, 4529, 23878,2295, 34460,29063,37656,21257,18413,12704,36910,41090,17742,22220,33321,17024,8229, 39679,36910,9555, 18354,28608,5186, 44985,2352, 34130,33824,34130,41879,35267,41012,45592,36909,20784,30042,38617,42906,37656,35741,40638,6596, 6978, 1698, 6703, 2994, 3109, 447, 121, 6977, 296, 39662,34845,28607,43405,198, 6329, 6704, 42019,44068,44068,44481,554, 16918,44985,37788,41344,41171,38306,45591,12512,4650, 43714,39643,44068, 43152,43404,45591,44067,20208,45591,20322,37917,44481,45591,39055,41640,4364, 38224,45590,104, 4472, 41255,33553,40946,33320,41639,21383,12383,44067,32629,44481, 44067,41090,7128, 30661,36270,37656,42701,22541,21382,43714,39013,42330,4584, 42506,43404,45590,38587,40155,37656,12475,24614,15008,36270,40757,1810, 41170,7125, 44985,35741,1745, 5500, 4530, 504, 1991, 7124, 38467,4198, 42506,20207,6212, 44985,34844,39739,6595, 39373,37655,35741,45590,24613,35266,20555,20554,25400,33116, 29525,5189, 44480,15523,13683,34844,38477,43152,40537,14552,35266,34844,27363,34129,34844,34460,38760,41879,38170,39400,33553,23251,36909,25667,33824,34843,14762, 35741,33553,41432,33320,17139,31276,22691,27001,14923,32915,35740,37655,33320,24123,29524,31275,44984,36909,35266,34843,3057, 38034,7845, 20021,29062,37655,21509, 20680,35740,18834,37655,22085,21508,28180,37888,22084,33553,41432,34129,36909,35740,24363,33824,36908,29523,36270,43713,32915,17790,27762,36269,36908,35740,15844, 35266,24612,20116,36269,27761,38085,38403,39576,35739,37654,18908,31274,33552,31923,32915,21015,25666,32914,33116,21921,27362,34129,3449, 37654,36908,37654,31273, 27000,34843,17409,33552,33824,34843,1662, 5107, 23671,33552,34842,26279,33320,31922,25399,33116,29522,37654,33823,16178,33552,42701,45590,33116,40063,44984,45589, 23877,36908,35265,37884,40757,44480,31272,21014,45589,25980,30041,39162,34129,6466, 31921,33823,41879,1794, 39662,16490,32914,33823,14233,15150,33551,146, 44480, 4709, 23250,17138,13100,16489,42906,44480,43713,40124,37653,36269,19261,16429,37738,30660,37880,38344,43404,38099,38441,22690,21256,34842,36907,42506,42163,25665, 17254,29521,35265,33823,34128,43404,44067,35739,37653,34128,34842,37966,45589,39387,38561,44479,36269,37929,44479,44479,41089,26999,15663,36907,26998,16529,42700, 34459,13554,33822,12835,36268,37653,37653,27361,41534,14164,13633,37652,18977,31271,36907,27760,37872,35739,18511,33822,19186,34128,26997,20443,28179,31270,4904, 17208,35265,37652,35265,34842,39825,24122,35739,35738,30040,22083,20321,34128,3202, 39698,5424, 43403,41534,44984,39153,40826,38223,39078,41747,44984,43713,15017, 44983,4651, 38976,42506,36268,18190,24362,5502, 22219,20906,37652,18243,25979,13643,24611,22881,37652,23062,21650,24878,14642,36268,14246,24877,37651,35264,34459, 32914,34459,18136,38016,34841,35264,37651,33551,35738,17408,38865,44066,18412,45589,37651,42330,42700,18189,16690,33822,33822,30659,19932,34841,43152,17023,22540, 33551,36907,35264,13057,33821,37651,30658,33821,16737,43152,39781,35264,40442,12257,14221,13135,22689,12377,34459,23670,34841,17376,13931,35738,41170,19095,38215, 30039,24121,34458,43151,1645, 13167,23669,14979,44066,768, 40154,37650,12179,44066,36268,26278,36267,27360,36906,35738,40537,13696,33319,35737,4781, 3796, 18082, 3642, 40826,34841,3059, 3763, 38329,2416, 34127,2639, 33115,38330,36906,34127,37650,4583, 12861,40877,43403,12081,45588,37650,22082,23249,29520,31269,33115,12545, 27759,20553,16651,16096,37850,38902,38779,42906,29061,43713,35263,13422,17068,31920,16650,23061,12810,37650,13712,34840,35263,15334,37807,45588,45588,45588,35737, 22539,44066,28606,36906,35263,26632,30038,3939, 33821,4363, 33821,34458,18699,32628,28178,33319,15688,35737,35263,3684, 44479,41012,44983,36267,40266,5501, 2259, 2754, 41344,37649,20679,27758,30657,35262,36906,25137,35262,34840,22218,34840,25978,16689,42905,43712,38394,40442,23668,39626,26631,18296,37649,34840,42700,20320, 33820,15977,37649,34127,30656,37649,42018,44983,14685,43712,21649,29519,42505,27359,33820,16990,7348, 39039,18411,21381,15522,18081,44065,18907,36267,31919,13748, 33551,18906,7544, 24610,37648,29060,45587,7265, 43403,7957, 12573,28177,30037,23248,25664,25398,12357,38986,41170,44478,36905,37648,24876,39255,24609,33319,20442, 33550,13230,45587,42329,44478,39336,20905,44478,36267,42329,29059,34839,35262,44065,40826,43151,44478,43403,36266,44065,32914,7651, 40825,19348,41012,39997,27358, 42163,45587,41432,38198,40490,41878,42163,39606,41170,45587,41639,43712,16649,16950,27757,30655,34458,38956,43712,18577,36266,35737,36905,26630,32913,19752,20319, 19931,31918,30036,17067,35736,19260,34127,35736,30035,40124,44983,37648,22538,34126,25663,37648,12588,33550,45586,40091,44477,25977,13614,10697,36266,11493,41639, 9801, 45586,29518,7765, 33319,15564,36905,20115,33550,8456, 9073, 45586,41344,31917,26629,34839,35262,2861, 37647,7209, 41533,2585, 26996,35736,32627,12887,36266, 133, 42018,5042, 36905,36904,14005,36904,44477,33550,34839,34458,36265,17066,16342,19930,32913,28176,31268,30654,13784,26628,36265,35261,34839,28175,19839,30653, 37647,14641,21013,36265,7894, 13097,23450,17942,18905,35736,32626,40091,33318,23876,28605,21380,40233,37647,17517,21143,16736,32625,43711,33115,1720, 36265,42700, 38659,45586,24608,34126,24875,22688,42163,44065,36264,34838,13660,38542,15133,21379,39943,27357,36904,27356,36904,15149,43711,44477,37647,36903,17448,18576,36903, 25397,37646,12947,42905,35261,33549,22217,33318,40442,42018,38048,45585,43151,42505,45585,41747,31267,40307,265, 5673, 40063,13460,6330, 40154,17887,43711,28174, 17253,13682,12673,33115,35261,31916,34838,37646,44064,20020,31915,32624,24607,31914,30034,33114,20441,21255,41169,20440,19929,497, 31266,41639,37646,36264,30033, 36903,44064,39006,40876,38213,45585,44477,44982,37767,41747,45585,43402,38550,42905,37646,5109, 41638,33549,37892,41344,33549,40233,37947,43151,38202,39090,42018, 39038,44476,45584,44982,11705,10999,9275, 9900, 37645,1699, 41878,1744, 37953,44064,44476,37919,40757,39625,40691,42699,38045,43150,41878,39266,39897,33318,18353, 14295,19490,36903,42905,22880,33318,36902,17137,45584,40306,33114,33549,33820,30652,10060,26277,42699,38975,14448,43402,16589,15058,34126,42017,45584,33820,14534, 33819,34457,23667,1793, 41533,45584,42017,34457,32913,37645,16488,24874,37645,34838,37645,36902,34838,37644,31913,37644,37644,27756,35261,37644,30032,29517,37643, 34126,23666,16234,35260,4420, 18188,33317,28173,41878,37643,8172, 38550,6109, 7558, 27355,43711,34837,36264,26276,7969, 29516,37643,30031,9333, 12607,33819,36264, 19185,26627,13986,7328, 7123, 36902,36902,7899, 36263,34125,34125,28604,32913,31912,35260,26626,37643,32623,21254,34837,19751,36263,35260,34837,34837,21378,35735, 2338, 7578, 34457,41877,17065,7336, 36263,19347,21253,16688,35260,33114,35259,36901,39576,41877,12182,44982,40265,44982,40063,16311,42699,44981,44476,44981,13136, 17695,24606,33548,35259,36901,36901,34836,31265,42017,7161, 2179, 8973, 36263,8843, 9176, 36262,30651,44064,41089,38576,39587,40490,40191,32622,40442,7939, 19838, 38679,45583,34457,16780,37642,20439,22081,27354,29515,25136,4158, 39130,38003,39762,33819,43150,37642,36901,40392,39236,42162,39587,38076,35259,24361,18465,44476, 27353,20114,12397,8502, 38969,9030, 8825, 20113,33819,29058,33548,20678,40946,37642,38388,38558,45583,40062,38799,44475,39427,40123,17407,37642,23665,33317,34836, 29057,36900,35259,35735,27755,42162,40825,44475,26995,37641,28172,9548, 35258,12348,16233,37641,12751,40537,39516,39543,41169,30030,2640, 31911,10200,10492,9065, 21012,33317,29056,29514,15942,15781,35258,33818,40232,38004,40756,15941,29513,23875,32621,34456,33818,36900,19837,43402,41432,41012,37641,36900,10839,34125,21920, 22879,43710,23874,27352,32620,37755,43710,40946,37929,35735,38031,34125,14397,19094,21648,14038,37641,15215,24360,34456,32912,34456,28171,32912,14163,19928,44063, 30650,15244,40232,6597, 30029,33548,32912,34456,19093,13930,35258,15804,41533,31910,43150,35735,31909,45583,37640,33818,34124,44063,25396,34455,20438,17694,36262, 35734,2239, 16687,42162,39265,40191,10233,34836,40392,29055,2037, 669, 10171,14573,43402,41533,34836,36900,12721,42699,10918,15455,33114,11492,9697, 11182,8327, 39336,22080,33548,11946,29512,36262,20437,40876,8662, 10399,11086,32619,12973,35258,36899,28170,23873,35257,22079,44981,38459,19836,38707,39576,42698,19346,41431, 45583,41747,33113,22379,26994,35257,16021,40691,36899,19184,18352,15416,23664,24120,33547,43710,27351,31908,39943,29054,34835,19658,39416,40123,31907,12360,43401, 36899,35734,23872,37640,17647,35734,35257,15099,33317,37640,33547,42017,34124,17789,30649,44063,23449,34835,21142,35734,26275,8140, 27754,32912,17600,35257,16686, 34835,33316,31264,37640,42162,19835,30028,15959,44981,31906,32911,35256,36262,34124,37639,40537,15645,26274,18351,42329,44063,45582,19345,37830,37882,42904,4037, 42904,41431,36261,44475,44980,43710,28603,28169,44980,35256,16113,44475,45582,44980,43150,43709,327, 347, 4121, 32911,33818,33547,26625,28168,14089,23247,10629, 39587,3895, 44062,45582,40638,40265,40348,44474,9482, 8528, 38600,37639,18242,23663,33547,25976,35733,15958,44062,369, 26624,28167,12025,36261,16095,42161,17646, 14410,34835,20436,42698,41011,44474,18029,43149,25662,37639,34455,34834,25975,33817,32911,26273,29511,33316,35733,36261,37639,33113,34124,33817,41746,43149,41255, 37760,38102,39484,40392,38012,16528,36899,26623,29510,20206,34123,35256,37788,14421,45582,34123,24119,34834,26622,33817,34455,30027,12970,20677,24873,39942,42904, 44474,15161,13868,36898,11960,40825,4652, 13321,15997,27350,18510,1653, 39530,12556,14307,22078,45581,35733,758, 31263,30026,42505,42016,44980,41746,34455,25974, 35733,35732,14572,33546,37638,36898,45581,14788,33817,35732,36898,7208, 3976, 2202, 25973,23662,23871,36898,34123,25661,21252,35256,37638,33546,32618,29509,32617, 29053,41011,25660,35732,33816,21377,26993,36897,33316,34454,32911,32910,21798,35255,813, 39188,39255,4324, 38956,40490,34454,41532,35255,35732,26272,35255,17553, 26992,37638,43149,31262,32910,14571,28166,35731,34123,17064,36261,31905,42698,29508,33816,17104,27753,31904,31261,36897,25135,11491,39235,18241,23246,30648,40536, 19092,29507,45581,23870,16844,36897,13586,17484,32616,19750,43149,26991,36897,33316,37638,42904,26990,27752,10199,23661,34122,23448,31903,37637,31260,36896,36896, 33113,14031,37637,7724, 41638,4270, 14684,13249,34834,37637,24118,26989,25972,36896,35731,44474,38391,39762,40489,39824,35255,30025,44979,22687,43148,39697,20676, 34834,38710,40030,40029,40123,35731,44979,15521,533, 37637,31902,7241, 44062,5423, 30024,11945,12046,39070,35731,7598, 25395,26621,36260,37636,35254,26620,37636, 32910,31259,28165,10770,36896,7664, 13362,25134,36260,28164,6332, 31258,25394,216, 604, 5585, 6702, 2440, 2294, 42505,45581,1079, 438, 5779, 41431,39560,36260, 26619,36895,45580,33816,19657,36895,36260,4367, 34833,23869,34454,21507,12353,39896,43401,34454,33113,23660,34833,16020,28602,26988,16527,31257,21797,32615,31256, 29052,26271,32614,31901,33546,12616,41877,32910,39229,44979,33546,28601,36895,6211, 2781, 40756,957, 981, 33816,37636,34122,35730,31255,41532,15644,22537,40441, 39140,42504,24117,17406,29506,12611,25659,44473,16917,22216,35254,34833,12812,41746,37906,2992, 34122,25393,4585, 35730,25392,44473,31254,33545,36895,7923, 40588, 26618,36894,4271, 34833,16288,45580,2993, 18904,448, 41011,3551, 38466,42161,258, 40637,869, 5875, 6463, 3605, 5876, 39455,43709,38388,45580,42698,6331, 42903, 35254,35730,36259,43401,583, 33815,1710, 34453,34122,33315,35254,5781, 2696, 42504,29505,37636,27751,35253,30023,33112,35253,37635,16883,35730,34832,33112,16779, 41638,35729,27750,37635,11867,44062,35253,22878,34453,14461,39717,20318,28163,27749,37635,32909,31253,34832,19927,38750,5108, 389, 5185, 31900,40637,44979,40154, 4968, 44978,1464, 33545,34453,14517,30647,42329,44061,20112,44473,22686,37874,38826,3317, 45580,25133,7769, 45579,37635,23659,36894,21647,17022,39153,1463, 43148, 41638,36259,42161,38626,17336,40536,43148,44978,6465, 33815,38356,22877,29051,22536,34121,15662,30646,5671, 16135,35253,37995,2695, 44978,43709,37853,40029,38865, 39800,42504,38037,33315,33545,34832,26617,36259,43709,23447,37634,5187, 5778, 11866,11181,37634,39336,45579,40441,33315,42903,43148,41877,20317,40946,36894,43708, 42903,35729,37634,41431,40090,44978,26616,43147,1535, 40154,34453,13117,42161,6706, 123, 41169,3162, 625, 45579,41637,42016,33815,20552,18631,36894,715, 45579, 39153,45578,40232,38401,35252,3234, 44473,40756,40825,36893,17375,18509,25658,19656,35729,21796,12489,42328,2375, 7127, 7126, 2918, 346, 41876,22215,6705, 42697, 38812,41532,31252,31251,36259,32613,33545,37634,18080,32612,43401,33112,5503, 43147,30645,37633,36258,18903,15631,39047,3381, 45578,42697,38439,36893,38767,4159, 24605,40191,45578,39350,21506,39606,39697,41746,20551,12871,42016,44472,37633,33112,29050,39195,43400,44472,30022,30644,41430,13903,42903,41637,43708,42504,44472, 30643,37633,44977,29049,40945,23658,2219, 4906, 45578,42902,44472,39314,33815,15504,44977,42697,30642,42016,14117,15434,30021,4366, 34121,630, 44977,3058, 39047, 34121,43708,32611,33814,37633,21646,15402,17599,45577,28600,42697,44471,22077,30641,44977,33544,44471,34452,29504,21795,33814,26987,16232,33315,29048,44471,36258, 12715,17335,22378,19926,14505,19421,14940,31250,22377,36258,37632,35252,19344,31249,13937,41011,42015,36893,16735,14640,26986,13918,37632,35729,18135,24116,31899, 37632,41876,35728,38534,42902,41343,44061,41430,42503,36258,33111,42015,36893,32610,32609,16807,17984,33111,22685,30640,31898,14657,42328,32909,44061,970, 38778, 35728,27748,33544,12269,32608,16916,37632,33814,30639,33314,23446,17645,32909,39484,44471,41343,39606,34121,33111,44976,23657,13273,26270,36257,36257,41169,34832, 34452,35728,34120,39055,38102,42328,37631,14209,35728,23245,12175,36257,19091,26985,13215,35252,18775,17252,36892,30638,16369,13695,21505,15733,36257,32909,36256, 30020,35252,28599,36892,12761,7959, 44061,35251,18833,14328,43708,29503,15016,25132,18976,34831,24359,34452,5190, 11865,22076,36256,17251,44060,35727,37977,27747, 38077,17552,5188, 43400,22876,12406,13421,14409,13766,34452,42696,38026,34831,36892,22376,37631,6213, 12089,34831,14728,38719,33814,34451,34120,40536,26615,42015, 27746,44976,13507,28162,13075,37631,33813,17644,37631,28598,22535,34831,12769,23244,3683, 40536,27349,42696,34451,24115,13403,37630,15612,26984,13799,32908,16989, 20675,32908,20674,35251,33544,35251,17207,38463,30019,39625,36892,41532,7863, 24872,32607,34451,33813,37630,31897,12164,29047,30018,16915,38308,39443,35251,35727, 34451,37630,37630,29046,33813,37629,40306,42503,13526,31896,13724,25657,30637,34120,25391,35250,22875,39870,25131,12230,29502,34120,34450,43707,28161,42015,30017, 36256,39047,16526,4473, 18240,17170,36891,22075,36256,37629,27348,43400,36255,24358,37629,42160,41745,37748,2584, 39455,38898,39896,40265,45577,28597,34450,30636, 23060,34119,44470,42014,15433,20019,17741,16734,36255,44976,15256,36891,23243,42902,37629,36891,35250,38122,14216,35727,38172,29, 42696,1969, 44060,33544,23059, 40945,38750,36255,6462, 45577,35250,15586,42160,2488, 27745,37823,42902,44976,42014,45577,36255,35727,1594, 6979, 13271,41876,42503,42503,34119,33543,24604,22214, 25390,32606,28596,37628,4905, 34830,12042,44975,42901,27347,36254,39090,873, 42901,28160,36254,42328,197, 44060,33111,4199, 40348,41430,14192,43707,1135, 39470, 3077, 42327,43707,35726,38452,42160,18975,31248,3477, 184, 2167, 44975,39971,41010,26614,1992, 32605,40824,38146,42327,44470,1315, 23058,26613,32908,12944,19489, 36254,34830,43147,16019,36891,39229,45576,39530,40824,38006,44470,38750,24357,25130,37628,38246,45576,2512, 45576,41745,17693,33543,4903, 44975,44470,39443,45576, 35250,43400,43707,44469,32604,25389,15352,25129,34450,17788,5013, 20111,31247,18350,12779,39762,27346,28159,39442,32908,18774,34119,40587,16733,34830,44060,30635, 37628,34450,27744,13420,36890,39427,43706,44469,21376,36890,33813,12191,21251,34119,13008,14674,26983,26982,7394, 17334,40489,42327,1433, 32603,34449,45575,40062, 39038,37896,7073, 36890,42160,26981,36890,34449,13815,15940,24114,34830,34449,29501,41010,34829,2659, 34829,34118,41531,37628,45575,19749,23057,22684,35249,4569, 45575,23056,36889,38108,44059,30016,38541,44975,40348,41089,14703,41637,36254,39575,43706,42696,38675,42327,37627,14103,20435,36253,36889,12658,36253,36253,6067, 4504, 39919,4877, 33812,35726,37627,36253,33110,33110,18698,41876,36252,45575,22534,36889,36889,34829,44469,42326,44469,1217, 36888,33314,41637,44468,36888,18575, 32907,43147,44974,5242, 14696,36888,16588,35249,35249,39800,41636,29500,34118,866, 45574,23242,25656,35726,41745,35726,26980,28595,33812,43399,36252,35725,22375, 19925,14777,42159,765, 43146,33812,3224, 38533,38512,45574,24113,44468,38909,32602,19183,36888,23445,38378,40945,37627,40062,43146,27743,13870,32601,29499,34449, 31895,34448,34448,13379,32907,17692,40391,35249,31894,31246,29045,35248,33543,12389,42695,19420,19655,44468,17250,40306,27742,34829,37627,8675, 41636,9654, 36887, 25128,5741, 37941,44974,38844,43146,43706,15823,31893,20018,20904,35248,41531,36252,34448,43706,45574,42901,11130,35248,27345,30634,29044,32907,37626,34118,40824, 27344,40756,39869,40190,43146,39662,40876,44468,40876,37626,42901,37626,45574,35725,17063,17249,35725,32907,34448,12712,24871,28158,12200,44059,22074,7521, 38944, 38710,44467,42695,32906,33314,23241,24870,39800,20673,44974,4452, 21794,12477,5649, 39516,5158, 43705,42900,35248,2347, 30633,1785, 31892,35725,1542, 38130,33812, 2253, 5236, 35724,33110,11944,43399,44059,35724,31245,36887,33543,14727,37708,43145,15897,28594,12503,18773,25655,44467,27741,44467,14024,43705,25127,37743,35247, 35247,29498,33811,17516,36252,15390,34447,38633,35247,40690,33110,23055,33109,26269,36887,35724,8785, 26979,35724,33542,44974,19488,39276,37812,35723,16428,18697, 37626,33542,4506, 35723,40391,44973,32600,13349,37860,4635, 43399,37833,39455,41168,33109,40535,39500,40587,33109,32599,36251,42326,39896,42900,19259,44973,36887, 37625,35723,36251,13442,33109,40945,30015,17598,7291, 4950, 18349,6068, 19182,33811,41254,44059,12582,37625,16094,44973,34828,36251,18974,37625,20550,15432,81, 6785, 5017, 30014,15732,3153, 33108,45573,39213,36886,41343,45573,25388,33108,33811,5555, 22874,22873,32906,42326,5015, 37725,3437, 34828,1502, 44058,6418, 10143, 11704,37625,2003, 1906, 1872, 42502,43399,37952,41430,40441,38345,5556, 3674, 189, 40944,5156, 37624,9886, 11085,11864,9119, 8607, 43705,33314,30632,34828,36886, 31891,37624,20434,20672,25126,34828,10398,33542,11631,42502,37624,41343,12877,21141,19343,38091,45573,41089,42502,23054,33811,24869,28593,27740,36886,11999,42900, 33108,40306,34827,3741, 40824,35723,28592,40535,37624,34447,44467,38666,19487,31244,26268,19654,37623,12501,35247,33108,39824,41342,35246,38278,42900,20205,44058, 43145,44466,33810,37693,42326,44973,242, 43705,5740, 20017,2660, 39848,45573,15389,39373,21011,13859,42502,45572,36251,44058,39997,21140,42695,25387,33542,38137, 38469,39470,41254,44466,38675,36250,41342,41745,930, 43398,42899,22073,34827,34447,33313,2328, 8477, 9800, 32906,45572,36886,31890,9560, 18696,35246,9377, 33541, 8496, 39625,9229, 42695,38679,44466,39400,44972,10170,43398,42694,36885,9799, 37623,2062, 44972,40029,44058,19924,38222,44057,42694,43145,42159,11943,8688, 43398, 4572, 5646, 10604,1843, 7074, 38994,33810,33313,36885,24356,36885,37623,41636,34118,34117,30631,14387,1341, 36250,23868,36250,37623,36250,36249,41636,6063, 45572, 30013,36249,23053,41429,44057,23656,18902,4878, 25654,17206,36885,33107,39212,35722,34827,28157,40489,44466,31889,43145,43704,42159,19258,36249,36884,42501,42501, 12294,43144,40944,37816,37622,13193,40029,44465,2503, 42325,31243,36249,36248,38820,35246,21250,40441,41342,19653,34447,42694,16394,36884,19342,26612,33810,21645, 29497,37622,25386,23444,1517, 36884,44057,34446,17941,34117,36248,36884,12709,31242,34117,12644,37622,15585,14611,4185, 5243, 37622,23655,36883,39661,44057,43704, 3630, 45572,25385,40823,40755,41635,38017,38820,42014,35246,34117,35722,28156,5844, 38803,34116,4816, 43704,23240,20671,30012,33107,33313,42501,30011,37621,33810, 40944,16778,24603,44972,38540,42014,26978,6069, 43144,44972,41342,1450, 42325,41088,44971,44056,41875,5739, 41635,34446,31888,39869,35722,24112,22533,21644,34827, 35245,33541,21249,22872,23867,30010,2128, 36883,30630,24355,33107,23866,33809,41341,32598,36883,33313,26267,35722,31887,45571,34116,36883,13210,38975,43398,43144, 43704,27739,37621,40944,37621,42501,10346,44971,4948, 39661,27343,37621,31886,39800,31885,43144,44465,5312, 40305,41875,44056,44465,194, 40062,4017, 39869,43703, 43143,44465,42899,39246,44464,42013,43703,38763,38788,41635,42694,36882,20016,44464,45571,35245,16310,35721,38620,8640, 29043,14610,33107,5737, 34446,34116,35721, 45571,4401, 43703,36248,37620,41875,44464,36248,34446,41341,44464,41168,42899,42899,3780, 33809,36247,29496,16047,42693,15687,21919,34826,17515,23865,29042,30009, 44056,3711, 36882,29495,14198,23052,33106,33106,42013,45571,40690,37620,30008,40489,35721,36247,33106,44463,44971,37876,42500,33106,36882,34445,39643,43703,35721, 23864,28155,14993,35245,12639,15630,39031,25971,5738, 42325,25384,36882,18630,652, 19834,7077, 36881,42013,45570,40153,42693,45570,36247,31884,13681,21643,36247, 19833,35720,15285,33541,37986,37620,13764,37620,37719,4103, 5957, 38073,44971,37919,42013,39387,40391,37841,38237,42693,37619,39047,44970,42012,34826,9190, 37913, 42325,44970,40090,29041,7647, 34826,20670,16914,32906,39083,35720,9155, 36881,33809,38857,40153,43397,25383,37619,9312, 37619,10101,34445,22072,17983,9784, 9610, 38222,26611,22071,36881,10740,9040, 11346,42693,32597,44056,42692,36246,7188, 40190,8342, 41635,36881,36880,25125,21918,11490,9603, 32596,11942,8913, 18464,10232, 35245,10603,11084,10547,18348,14589,10320,25382,35720,13148,11863,5955, 5012, 33541,7749, 20316,29494,36246,7221, 4636, 9875, 9499, 43143,30629,34116,24354,44970, 19181,12184,37619,37618,34826,7440, 42500,20783,37787,38198,18028,9992, 35244,9869, 16134,9527, 34445,23443,9277, 8751, 11703,10576,9292, 8905, 7729, 8252, 40028, 34115,3152, 13162,33540,35244,36880,11345,7803, 37618,17405,10769,13730,34115,24602,28591,4634, 14761,41634,36880,43397,45570,26266,26977,20782,18973,33312,13885, 35720,24868,12783,35719,45570,36246,4815, 44463,44055,31883,36880,30007,43702,15333,44970,36879,8159, 40190,10998,26976,9947, 8412, 9671, 45569,11180,429, 45569, 8387, 40305,42692,11941,45569,22374,41429,34825,8183, 44055,10447,9991, 13494,23051,36246,16555,42159,40587,35244,11344,35244,42158,43397,40823,4400, 4691, 12131, 44055,41634,21139,34115,42692,16256,1550, 44969,42158,41429,24353,37618,20110,43702,42012,12144,45569,30628,36245,34115,31882,32905,35243,14182,27738,13685,16554, 16806,45568,32905,20903,35719,30627,16287,19575,16648,2481, 20781,34825,17404,36245,43397,9926, 38830,45568,22070,15874,8270, 38242,13612,23654,40488,38799,35719, 16459,33809,12549,37618,13890,37617,15401,34445,35719,32905,36245,36245,20204,33808,8033, 12119,21010,35718,1605, 36244,5846, 25381,32595,27737,34825,18027,17374, 37946,42158,38001,38054,34114,37617,37617,12376,24601,6547, 22069,27342,15731,38707,31881,17691,23863,42158,23653,37617,40153,34825,45568,36879,11993,40535,34114, 34824,20669,42500,45568,37616,33105,32905,385, 39373,5014, 43702,33808,33808,45567,39587,40943,2329, 41744,5843, 44463,36879,42898,44969,35718,42012,44969,41429, 13022,16309,34824,33312,34824,14068,36879,16487,18295,15228,39387,39195,45567,33540,12672,41341,42012,44969,34444,37616,39971,43143,44463,29493,21375,7565, 9657, 11787,9132, 39919,40637,36878,37616,33540,29040,25380,22068,31880,23652,33105,36244,9029, 36878,16204,17447,16427,15067,37616,33105,20668,29039,40391,38540,44968, 20015,37615,33808,37755,31879,36244,30626,45567,23050,44055,34114,34444,38157,7387, 39781,2389, 12775,37615,34824,39942,31241,37615,44462,5845, 40153,42157,27341, 41168,21248,16587,44462,39500,5313, 36244,35718,27736,18026,19574,10295,14201,29492,11702,33807,40875,42500,781, 45567,40305,6064, 45566,39038,41254,15563,25379, 36878,25970,30006,14074,40232,37615,215, 5238, 32594,25378,36243,42499,15302,36243,38329,39761,39336,17333,5395, 8406, 44054,25969,37614,35718,29038,35243,35243, 36878,35717,11230,20433,34444,15976,25124,28154,35717,34114,39739,33105,36243,40190,42898,33312,11940,11489,13238,45566,407, 39031,33104,32593,44462,44968,19486, 41341,36243,35717,22373,11229,33104,37614,11488,38101,27735,37614,31878,24352,37614,12758,33312,35717,20109,19090,42157,14880,34444,42692,36242,15629,13992,20780, 20549,20779,35716,35716,36242,37613,31240,33311,5237, 32592,27340,23862,24600,31877,22683,23651,12146,24599,33807,38923,867, 14978,18695,33807,41168,33807,17021, 18772,25123,17020,44968,20548,37613,44462,21793,34113,37613,34113,12210,35716,31876,42157,38564,39997,39442,40690,37613,14900,44461,39483,3743, 45566,41531,36242, 19257,15543,21138,32591,8846, 40440,39290,44461,41634,39543,41428,43702,42011,36242,34113,26610,35243,33540,25122,34823,42898,41875,10739,40090,41088,40755,2608, 37825,732, 4762, 44054,45566,38853,42324,38353,20315,40152,1129, 16177,41874,42324,45565,38980,18771,41531,39455,40123,10602,44968,39106,42011,45565,44461,45565, 13183,5648, 808, 6930, 42011,9324, 42324,44054,17373,39971,30005,2061, 11939,8187, 41167,11228,11701,42499,10518,9960, 9506, 8767, 38598,43701,41340,9247, 9027, 8304, 44967,9481, 36877,35242,30625,16133,18901,2143, 34823,5310, 14695,24598,35716,16843,11938,9168, 15520,1036, 42011,43143,22213,32904,36241,5742, 44967,3372, 32590,44461,44054,4058, 30004,39997,40637,19652,13229,22212,15388,35242,34113,22871,29491,30624,34823,37834,33104,25968,40755,44967,19651,19180,42324,28590,25967, 37748,38025,38477,14175,38302,44967,44966,18410,35242,38986,42691,39062,42691,33104,34112,44460,33806,1005, 29490,32904,37612,44966,22682,36877,20778,39918,44460, 35242,37612,22372,14057,34823,13759,38221,43701,40231,2799, 44966,18347,35715,21247,28589,39971,44053,39661,35715,34822,39013,36241,20203,18770,41530,27734,42898, 27733,39661,33539,44966,18294,33539,19573,33806,32904,31239,34822,36241,43396,26975,32904,11862,19572,4141, 42499,45565,37612,24351,23239,16203,30623,35241,34443, 6065, 2978, 6066, 4142, 40636,1518, 2114, 2550, 7076, 1622, 5647, 3401, 3436, 44965,40755,26609,4568, 39000,40943,25653,41340,16612,31875,33806,33806,41530,8570, 29489,24597,35715,7344, 10345,8721, 8454, 11558,9819, 32903,39188,45564,39515,17205,17403,35715,7485, 8118, 37612,44965,42897,30003,36877,38617,44460,41530,16176, 34112,36241,21246,1929, 38004,39642,42897,38319,38322,43701,41634,39454,2504, 34112,10491,26974,5086, 40535,11291,8638, 43701,22532,10169,9189, 34822,35241,8504, 42157,42691,1786, 9083, 11700,10273,45564,21374,36240,37881,31238,38861,44965,41530,44460,34112,45564,41744,44053,40534,43700,44053,38464,39325,44459,11487,44459, 405, 1705, 5239, 43700,45564,42897,38685,33539,27339,1291, 22531,42010,43700,42897,42896,45563,40754,38654,45563,15387,38006,39098,44965,41744,44964,22371,42499, 40636,37611,2212, 39454,44964,38316,38689,38184,45563,10965,43142,16308,34111,23238,29488,40534,36877,40943,3302, 33805,33539,36240,14484,38794,43396,44964,39970, 45563,45562,36876,40534,40440,38082,41529,32903,34443,35714,30002,37611,20902,31874,34443,22530,25377,34111,36876,24596,45562,33805,36876,33538,28588,37611,35241, 37864,41744,37806,45562,40061,40122,44459,38688,40028,45562,39399,45561,45561,36876,44459,32903,33805,33103,12659,17787,33805,32903,44458,44053,43396,30001,33804, 7454, 31237,30622,33103,24350,44964,824, 41254,37611,37610,37610,39847,27732,36875,4814, 5954, 16525,33103,18769,25121,2715, 30000,5960, 27731,13578,7259, 39399, 24349,31873,32902,37610,34443,37610,44458,19089,7806, 11699,36875,45561,17982,37609,14776,21373,16732,42156,35241,22370,33103,28153,45561,36875,1188, 21792,44963, 38219,39265,4949, 2684, 42896,37959,43396,24111,25376,38668,44963,38525,43395,16685,31236,43700,44052,43699,44052,20432,37609,33804,35714,34822,38975,42498,44458, 23237,35240,4505, 38167,38994,40754,39130,39061,30621,15214,30620,45560,37609,34821,38537,12903,44963,21504,14787,43699,12216,17690,18187,36240,34111,15917,45560, 43142,28152,18900,43699,40875,26973,2455, 4763, 35714,37992,44052,28151,545, 39470,39012,43142,37609,17402,35240,29037,35714,1543, 12625,9740, 37750,6661, 16842, 41428,38033,17940,28587,32902,9517, 40690,45560,12247,38950,43699,8861, 9462, 11227,11040,12728,12559,43698,37757,37805,764, 38011,43142,38067,34821,35713,38277, 44052,41340,24595,38980,37608,40587,44963,40189,9452, 11557,10575,9193, 10517,9761, 45560,40122,36875,44051,41743,1189, 1239, 4692, 41253,39942,14252,20108,27338, 38494,44458,28586,36240,40440,39717,44962,41874,40943,44457,14800,40754,16286,12148,36874,36239,42498,21372,13013,25375,20107,37608,5736, 41167,38353,40152,20901, 43698,11630,38031,41743,43395,40689,42010,42498,45559,38766,38975,40122,26265,19419,35713,40754,33311,40534,20106,17332,37889,38292,993, 1606, 37932,45559,38784, 35240,36874,40753,45559,11343,10601,5311, 41010,9789, 9541, 11486,39586,40265,8754, 38403,9269, 20547,41874,28150,9440, 11786,8359, 7149, 3505, 11485,39697,28149, 9911, 7747, 38, 39276,782, 5396, 7072, 44962,9461, 33102,1395, 8271, 41428,11785,14627,1895, 11414,38223,11629,8929, 38503,40753,38119,43141,39046,23049,44457, 35240,9925, 5735, 21642,35239,42156,31872,16684,38622,42691,3823, 40875,31235,17981,43395,23861,44457,39083,8025, 12408,42690,4244, 38692,2309, 40942,11937,42498, 38177,8967, 44457,26972,16841,11936,43141,13902,36874,41529,18346,35713,37608,36239,9348, 38135,37608,7075, 33804,21009,18508,13377,24110,41340,42323,37607,40152, 44962,43395,37607,25374,30619,39229,40875,10838,45559,28585,31234,39152,45558,34111,7915, 28584,18507,43698,44456,35713,30618,14396,33311,20546,15916,12135,35712, 20314,13884,32589,37607,10142,43698,23048,32902,34442,38063,42896,39276,42323,38349,32902,38380,3346, 42690,14215,41529,38202,33804,39847,36239,23442,44456,37756, 42690,39500,37923,5157, 41874,4453, 35239,41253,37990,43697,5958, 40586,40823,41339,42896,38359,39399,42690,36874,5959, 17740,6415, 3593, 43697,13743,41010,3246, 6417, 42689,38182,41009,39529,39195,42156,43697,38839,39275,39500,2636, 1977, 39799,71, 4351, 39483,40533,40061,44962,22681,44456,44456,23047,38877,2454, 33803, 44961,21245,34442,38114,40305,41167,35712,36239,34110,35239,4571, 73, 1805, 9097, 9554, 34821,39499,2979, 42010,39302,6787, 38515,43697,39559,45558,41428,12590, 32588,28148,17019,34442,33102,13953,1130, 25966,40874,30617,17401,43141,34821,39083,41339,10964,33538,11342,11039,11628,8636, 34110,34110,8693, 11129,9739, 10475, 40090,9656, 11627,35239,36238,37607,34110,33803,44051,35238,34820,491, 9818, 9774, 4812, 34442,406, 6786, 10628,45558,10997,8200, 9628, 10516,44051,33311,36238, 36238,4570, 36238,37606,20777,33102,21008,22067,31871,38726,45558,15661,5241, 42323,19650,38076,43696,161, 3467, 35238,41873,14245,41088,35238,42895,44051,42323, 7987, 42689,15116,36237,42689,16949,37606,35712,637, 29999,34820,24109,36873,34820,1879, 5240, 39717,345, 2113, 3779, 15996,5478, 40823,39739,5956, 2004, 45557, 39018,20313,41529,5016, 812, 33102,39575,35712,7953, 11484,36873,19341,42689,26264,34820,29487,32901,21917,39896,30616,149, 43394,3042, 43141,25120,1142, 38131, 40942,43394,45557,112, 41528,3345, 40822,43140,45557,2685, 41253,30615,40942,45557,688, 44961,2798, 25965,19649,37895,40822,34109,1022, 11626,35238,27730,26971, 44050,45556,44455,42688,42156,2825, 44961,37606,29036,33101,33803,37776,38532,40264,12876,38301,43140,39031,43696,43140,36873,37789,38558,34441,12875,12848,40488, 42155,16777,38569,44455,38197,40586,29998,4879, 3929, 40440,35711,40089,44961,4102, 21007,15351,44960,36237,42322,36237,36873,3744, 41528,39679,39918,42895,42688, 13441,2144, 42895,43140,39302,33538,36237,38091,10996,45556,42010,45556,16132,41633,34819,6416, 3542, 42155,39869,42322,25373,32587,38195,44050,33101,17400,4813, 39824,894, 44050,43696,42895,23441,3742, 38328,9373, 36872,37606,29486,35711,36236,40753,45556,42497,45555,41743,43394,45555,14327,34109,33538,15562,14854,33537, 29997,40122,40264,6099, 23650,36872,33537,28147,25119,16046,16131,42155,41427,3104, 22211,36236,36872,34819,34819,44455,37875,38736,4580, 44050,42894,43696,44960, 31233,25372,35711,28583,13574,32586,14260,4115, 41427,45555,948, 38030,39245,44455,17136,13255,37808,38153,30614,38723,44454,42155,29035,38744,39499,44049,43139, 39046,41088,35237,42322,39942,33101,44454,36236,42322,45555,42688,22210,8012, 44960,39178,6451, 3936, 44960,37937,44049,39399,38723,42688,38387,42321,3446, 37605, 39130,660, 42497,40028,40348,44959,35237,38969,41167,45554,21791,40689,41873,41087,42154,39275,39442,38093,38269,1201, 26263,18899,34441,34441,35237,14460,6970, 25964,34441,20900,34109,22529,7235, 45554,723, 6817, 3130, 34109,16393,32901,4067, 40347,40347,969, 619, 38730,8679, 33310,9267, 10600,9713, 9201, 10599,31232, 10084,42497,39038,12345,14306,35711,34819,8775, 949, 7360, 43139,9809, 9924, 34818,42687,24594,40390,13537,14656,8557, 362, 8211, 40533,8184, 40121,11413,33101, 34440,34108,36872,3549, 15132,36236,37798,41009,43695,40533,43695,19923,6969, 44454,29996,26970,21006,32585,1089, 7890, 44049,7152, 42154,35237,16840,45554,8397, 37605,3550, 44959,41528,41339,42497,700, 45554,40439,43394,36871,19485,44959,2122, 43695,44049,34108,35710,3313, 8936, 33100,13621,33537,1382, 1266, 1294, 28146, 36871,980, 7608, 41339,5581, 43139,43393,2693, 43393,40533,3276, 44454,1604, 41253,36235,42496,45553,1346, 39996,40152,42154,26262,18832,40264,39483,44453,42154, 1220, 7999, 44959,38950,40942,2990, 20899,44453,1347, 36235,24108,41087,36871,12624,45553,40439,6692, 14294,33803,18768,42496,41427,39868,43139,43393,43393,43138, 33100,43138,34818,32584,35236,33100,33100,28145,35710,38913,9760, 37605,37605,37604,36235,36871,33537,37604,36870,35710,34108,4320, 22870,10696,3352, 35710,33099, 36870,33536,42687,39415,35709,43695,29034,10374,9749, 36235,33310,33099,26261,35709,34440,30613,34440,21371,2808, 38347,38447,2535, 43392,36870,27337,37604,34440, 34818,32583,18574,43392,29995,36870,33802,41633,40439,44453,29994,45553,9773, 3055, 11483,9179, 11935,36869,8254, 41743,4029, 34818,4317, 42894,44453,3105, 45553, 43694,34817,9617, 36234,14352,36234,19748,35236,27729,42894,25371,35709,36869,28144,22528,11698,36234,35236,44958,41633,38276,40689,38635,39469,38871,45552,44958, 44048,40347,40689,41873,45552,41633,39515,44958,43392,41742,41087,39738,38056,39678,45552,38366,36234,37604,33099,34108,33310,22209,29485,33802,36869,41427,42009, 34817,4527, 35236,4156, 34817,35709,13029,36233,25963,44048,29484,3718, 36869,5770, 44452,40941,42894,38934,44958,34107,38401,42321,38573,41166,37603,36868,4897, 34107,44048,37603,37816,39442,44957,36868,45552,35708,32582,3351, 42496,4898, 38229,38865,39427,45551,43392,42009,38856,42893,39012,34439,5993, 44957,18025,23860, 23046,28582,33536,19571,40822,41338,32581,35708,25962,34817,34107,33310,30612,36868,19179,38130,38638,38812,38322,7768, 34107,8436, 42009,29033,41873,39415,42893, 45551,37603,42009,42496,39212,42687,44957,32580,41632,39575,39077,34106,33536,1912, 42153,44048,44957,44956,15085,39660,33536,15084,44452,17018,41087,2374, 8285, 19747,8898, 42321,7232, 11412,11784,36868,10026,8758, 34106,35708,40941,43694,39275,44452,11697,10627,3411, 11625,11482,8849, 45551,45551,10768,43694,39761,11975, 40347,12385,6966, 33099,14760,34439,33098,14284,36867,34816,31870,40089,43694,44956,35235,41166,37817,23649,38205,624, 39625,3935, 11341,42153,44047,38939,42008, 42893,44956,42008,40028,36867,38882,42495,38783,26260,8038, 7356, 8766, 8373, 44956,5994, 10917,8959, 7903, 23045,38566,39089,34816,16068,43138,33802,28143,22680, 31231,19570,42495,40304,33309,44955,35708,39212,42687,41528,39130,15115,18079,7541, 42686,35235,21005,25961,34816,35707,29993,26969,34816,18629,36867,3511, 45550, 17283,18767,26608,7374, 43138,19340,30611,42321,37836,38249,4319, 43391,38974,42893,43391,38117,42320,39941,41009,42320,41742,45550,40061,40753,37791,38886,44955, 38221,39254,38865,38004,35707,39559,14073,44452,40488,45550,34439,33098,18024,20105,35235,34439,35707,21641,35235,44955,43391,15730,18345,45550,20431,11624,33535, 15160,32901,36867,11179,41872,14495,35234,7720, 8028, 33802,10130,9414, 12757,41742,17551,10515,15066,33801,43693,12744,2395, 45549,43391,35234,36233,30610,29032, 33801,35234,34815,8707, 5182, 10546,10738,11556,33098,41742,44451,42495,38584,42153,39761,29483,37603,9783, 7979, 44451,2694, 7606, 14037,38189,42320,45549,29482, 34438,24593,33801,40688,43390,15628,39941,44451,41166,33309,40688,37957,39083,39918,38261,32901,7660, 42495,36233,44047,7285, 40822,43137,17169,7397, 39824,5101, 38334,39106,2891, 44955,38756,41338,24348,35707,22527,33309,44451,45549,45549,12480,36233,33535,39716,32579,35234,23236,28581,39499,36866,28580,44954,41009,38168, 44047,39469,41252,41086,29481,33535,37602,20312,7509, 41527,7363, 44047,38756,41741,36866,33535,40027,2396, 4265, 41741,40121,2271, 42686,37602,36232,2989, 1061, 40304,34815,39738,43390,43693,41741,38064,40688,40121,44954,39624,42153,40346,42686,23648,44954,32900,26259,41527,40439,41166,40874,31869,44450,36232,35706,39515, 42494,44046,35706,41426,19339,32900,21370,34815,42008,44954,33801,21790,11128,25118,10995,37602,37768,2721, 37948,38602,40586,37924,39161,38549,38038,44046,44450, 39918,2351, 38209,34815,23647,36866,39000,39624,39129,44953,41252,37858,45548,25370,38480,44953,10446,40941,45548,42892,13917,42892,2485, 41165,37602,41632,3106, 6317, 14879,34438,16156,21369,42008,34814,13991,44953,1777, 44450,44953,5494, 19569,36232,34106,19568,20545,2373, 42152,33534,43390,44046,3637, 35233,19178,7112, 37833,40027,25117,44046,41527,39624,38547,36232,21503,36231,44952,38018,41338,38934,38704,42320,44450,20014,33534,22208,34106,44045,45548,44952,45548,39245,28142, 42892,45547,45547,44952,43390,35233,41338,41741,4321, 44449,40390,41337,24592,24107,43389,45547,32578,35706,17886,7320, 44952,45547,20544,26607,36866,6818, 5666, 44951,44951,38148,6967, 32900,40941,40874,39624,25369,31868,25368,16776,39575,30609,39917,40532,20311,32577,42152,38201,41872,39195,41740,41426,45546,43693,38003, 31230,41632,13140,42152,30608,13758,42494,42494,43137,23646,4028, 4116, 35706,12318,33800,34814,25367,24106,40821,40586,32900,14853,37601,29031,32576,35233,31229, 36231,14878,32575,29992,35705,17786,35705,551, 2270, 3792, 13963,44951,41165,33309,25960,269, 4155, 36231,37601,34438,33308,34814,12407,45546,15454,33800,266, 41740,34105,25959,29991,40688,3758, 17689,37601,33098,43693,35705,43389,44449,32574,35705,44449,40264,36231,37601,14434,31867,39823,40438,37600,24591,1163, 3448, 4899, 42494,44449,38444,39350,39254,1441, 39018,5327, 2692, 40438,40488,36865,37600,33534,34105,36230,20898,42493,43389,44045,37975,40532,44448,23645,35233,4467, 29990,38635,43389,11934,45546,42152,37600,36230,7370, 13962,41872,44448,20202,28579,19418,37600,42151,40940,41872,42892,43137,41008,44951,16882,120, 39483,38726, 39823,19648,36865,31866,44448,12699,45546,33800,21640,14673,40061,34438,12980,16458,20201,24867,36865,21789,27336,22526,26258,37599,35704,23644,16018,26968,25366, 36230,30607,34105,40121,43388,44950,19746,41337,21137,36230,27728,12285,45545,34105,41086,45545,45545,2438, 34104,15065,33534,36865,35232,28141,44448,20104,37599, 44950,38871,42493,38137,39697,41165,42319,43388,35232,40438,33800,32573,45545,45544,1834, 13090,40636,44950,25652,4418, 42007,42007,35704,34104,19177,36229,36229, 35704,37599,35232,29480,34814,36229,37599,36229,33799,35232,30606,13077,36864,14454,7728, 39529,44045,35704,27727,37598,37813,3717, 40120,42493,20430,35703,44447, 45544,5416, 31228,42686,23859,12058,39398,9007, 23858,18186,35231,12033,41086,34437,33799,11555,36864,42319,13765,41252,34437,10767,8946, 26967,13585,34813,37598, 20776,34437,33799,34813,43137,42493,7476, 38962,36228,34813,34437,45544,44447,21788,44045,44447,44447,43136,44950,15803,40189,44044,39129,38716,41871,40687,38553, 44446,18344,43692,40874,40189,14232,38912,44949,38853,38329,39139,38986,38251,42319,40189,44446,39847,42492,41252,40752,34436,8366, 39046,414, 39398,36864,32572, 10474,11933,30605,36228,35231,16112,35703,9439, 19338,14977,35231,36864,42007,44446,5181, 12393,24105,7755, 43136,20543,45544,31227,32899,29989,12003,28578,19176, 21787,39895,25365,36863,34104,25958,33097,33799,19567,34436,43692,32899,36228,18343,36228,24866,4114, 36863,39187,36227,29988,34436,2026, 41527,18628,12334,29987, 37936,41632,42007,5261, 45543,39970,44044,44446,44445,32899,39847,44044,6590, 38016,42319,430, 38864,39415,40120,881, 36227,27726,39761,43136,43692,39229,42492, 45543,40438,29479,18627,14899,16285,5262, 1546, 35703,18078,34813,32899,37598,8097, 33798,1709, 5992, 17550,1522, 45543,41740,45543,7986, 38576,41871,44044,41165, 16775,22679,37598,33308,13234,15131,36863,21502,44445,38155,24347,29986,4705, 43136,42685,41086,39120,3131, 43135,41631,36227,31865,26966,38537,41426,39760,39235, 44445,41008,43692,39846,12434,42685,45542,45542,40188,6316, 37691,17103,16045,39275,44445,39139,44444,45542,44444,38610,36863,4417, 43388,13607,23857,45542,37773, 2938, 43691,7114, 43135,38093,37922,40263,40304,43135,38617,40940,39006,38580,42685,3475, 38194,37728,43691,36227,29030,35231,35703,33533,35230,39398,41251,38177, 40636,4468, 41631,43388,38232,45541,42492,39415,40532,41164,2437, 43135,40120,43134,41426,45541,39996,43387,43134,41425,42006,41164,44444,44043,38292,45541,40635, 45541,42685,42318,38999,39302,38265,44949,45540,39070,40752,42684,44043,36862,36862,43134,41871,44043,38464,44043,40821,38256,40873,43134,43691,38174,43133,38162, 40151,42006,38079,38238,41631,41871,38944,43387,44042,44949,41008,32898,43691,37968,42684,43387,40437,44042,23643,39290,39996,42006,40873,38626,42151,39221,38595, 38722,42006,44042,40687,41740,41251,38933,39738,42684,38058,41085,45540,43690,22066,5414, 5036, 44949,42151,38510,39120,41008,40231,37800,39082,119, 4901, 42318, 42318,44948,38441,38309,40635,39254,39030,43387,39868,41007,3887, 38512,38993,42891,39678,42492,39398,39586,44042,40437,40585,43386,39660,36862,38692,43133,39289, 39325,38205,42891,38897,44444,42491,40346,34104,43690,44443,43690,43690,44041,44443,44041,38456,39499,44041,38385,42005,38732,38962,41425,37918,38980,37805,42005, 40873,40585,38441,40585,44948,42151,41337,44443,39760,37975,41739,42150,39996,38663,43689,38375,40151,41337,41526,38928,40304,26606,40346,42318,24865,41336,40263, 40303,45540,43689,41739,41251,42317,42491,38825,43386,42317,40027,44443,40752,40487,41251,45540,41631,42317,42150,42150,42150,45539,45539,38443,38820,41250,39606, 12740,41870,39605,44041,44948,42005,58, 41085,42005,38476,39335,40151,45539,14588,36862,44442,3841, 17643,21368,33798,23642,34436,15519,24590,26257,40346,39868, 44948,41739,44040,39995,43386,45539,41085,40821,44040,39515,38993,44040,38102,41007,39414,39274,45538,8853, 38347,11127,40437,37991,37857,38979,38345,38218,44442, 43386,40231,40089,42149,9062, 43385,11783,10272,40390,43133,44442,39823,39781,39995,44040,38492,38556,39941,42891,43385,41250,44442,41870,4316, 3888, 44947,1027, 43385,40821,44441,38956,45538,17248,33533,39760,42684,39265,41085,44039,42149,20897,43385,39941,33533,25651,44441,43689,23044,37940,43689,43688,41164,39559,45538, 39171,38454,34435,38685,43688,39335,1850, 44947,38919,14562,40060,45538,42683,1609, 41870,29478,37597,35230,26256,27725,34812,42891,38580,39586,39469,39738,42683, 34103,41630,14746,36226,36861,35702,33097,22678,37597,40532,16647,43688,43133,41250,44039,44947,19566,38106,41250,45537,44947,38193,38386,41630,39187,38132,40531, 40585,44039,42491,41084,42149,39061,39605,38149,44039,38333,38700,39113,43132,43132,41007,42683,41739,39642,41249,41630,44038,38402,38969,38459,40060,39427,41630, 39995,45537,45537,38551,40752,42149,40584,44946,45537,43132,27724,45536,43384,38592,39543,43688,45536,39089,40687,43132,43687,38974,37965,41629,38370,38730,1265, 43687,18972,44946,42004,43384,15760,45536,34812,41629,41526,40231,38630,42148,40635,37895,40584,38558,39605,38877,45536,41526,41249,37597,29029,24589,30604,42491, 43384,40531,44441,40751,39642,40873,44946,40635,44441,41738,33798,34435,23043,13130,42890,27335,35230,27334,29028,12404,18626,35702,16774,13310,20013,34812,33097, 36861,1487, 38100,38950,39314,37992,44038,38589,40060,39696,13510,34812,13385,33533,12312,15759,35230,27723,33532,35702,8247, 44440,32898,39970,44440,39559,42490, 37597,13106,32571,641, 43384,13543,36226,2508, 39274,40940,41007,43383,42004,6098, 41629,7534, 35229,37596,35229,13840,31226,39660,34103,43131,38213,40872,38518, 40634,38694,42683,42317,42316,40634,38908,42004,42004,39799,38239,42316,37596,43131,39586,42890,40872,37908,39498,40487,39106,38694,41336,38162,38026,40027,24588, 42682,31864,41629,44038,41628,39129,40872,40437,34811,38902,39780,43131,44038,42682,42490,33097,35229,44037,43131,42316,41249,42682,38480,42490,43130,42003,39970, 39799,38366,8371, 38417,32570,18023,18625,44440,38423,36226,39895,41526,43383,44037,42003,41738,40188,38881,39716,44037,44946,45535,41870,41084,43383,38378,39868, 41869,36226,10271,8689, 44440,39161,38778,39178,43383,40303,44945,40060,39373,44945,40390,39113,938, 42148,42003,40751,40230,41869,40487,41869,37935,45535,38662, 39917,38605,39194,39139,39265,39660,40820,41084,40151,3314, 41869,39969,44439,42316,15873,34435,40820,45535,40436,40487,38819,43382,41336,40584,45535,1086, 45534, 42315,42315,37596,39362,34103,11554,38234,40026,40687,43382,40872,41425,39212,14869,39414,39659,41249,44439,40059,41425,34103,40871,41006,44945,37955,38897,38675, 39202,44037,41006,44439,41868,34811,2612, 33096,5995, 45534,45534,40686,44439,38062,44945,40436,43382,40059,39387,39514,41336,38825,41084,44944,44036,41738,43687, 45534,40634,42003,38482,40059,36225,37596,36861,12452,39314,40230,43130,40820,39082,40263,42682,45533,45533,43382,45533,37802,38710,42002,38651,38839,40120,40686, 42490,42489,41335,41248,44036,44438,38993,38407,39362,39245,38625,39760,41628,42681,41628,45533,37869,41525,42890,43130,38694,17247,43687,41083,39759,40263,39211, 43130,42315,41424,28140,40389,43381,42489,38808,38340,38766,39605,41424,37595,37956,37997,43129,38436,43686,43686,41006,44944,43686,12531,42489,18185,36225,29027, 15266,33532,4900, 34811,26255,42681,12009,42148,41164,45532,12817,38487,44036,94, 3603, 1112, 40230,38146,31225,14898,15872,20103,324, 45532,29026,5771, 3934, 1776, 40345,2123, 41868,38922,12702,45532,3510, 5260, 20429,45532,39940,1532, 45531,2667, 3716, 41738,29985,33096,15148,18077,21786,13470,35229,36861,34811,14420, 37595,20896,15643,36860,39254,39529,12015,40871,7113, 38504,42002,39678,38659,39716,25364,42002,33532,36860,33798,39846,41083,39349,42681,41335,40634,44944,4030, 40820,2291, 42002,21501,25650,40633,43129,44944,25116,34810,13264,12355,35228,34102,31863,6204, 40531,19175,35702,43381,22869,27333,39543,42681,38602,42680,40089, 44438,29025,28139,34102,15114,25649,34810,24864,26254,19745,41424,22868,36860,329, 42001,44943,44943,42680,37595,24587,42489,25957,21785,21500,15843,37873,43686, 42488,36860,37971,44943,41083,40751,41868,8444, 36859,9602, 10963,11290,32569,36225,10047,33797,27332,29024,36225,12919,44943,36859,13011,37595,15707,25648,33797, 21916,27722,13627,38890,35228,33797,43381,17246,9371, 40686,8061, 16731,13270,17483,42890,11553,40751,43685,10766,34102,20775,41737,34102,20012,22677,45531,37594, 12909,36224,23856,34810,40819,35701,42148,38870,41525,38503,24586,36224,10270,30603,36859,9842, 10250,7315, 44036,9298, 21136,44438,10473,24863,33797,9003, 8934, 9593, 8669, 44035,38030,37594,17939,28577,34435,25115,33532,42001,35228,45531,41163,36224,38450,42889,28138,23641,3759, 34434,25363,28137,13309,10445,27331,25362, 34434,13220,38398,42315,43381,5037, 43380,40819,16093,8814, 37594,18898,7538, 7789, 37981,39542,45531,38835,41868,36224,13190,25647,31224,42680,44942,33531,6101, 43129,35701,35701,25361,14305,23042,25114,45530,35228,4318, 41163,14266,39314,44942,4706, 45530,36859,889, 13658,40230,40486,19417,39187,39542,44942,43380,24585, 34434,38716,35701,38383,38798,39895,45530,33308,39077,38703,39696,43685,24104,19416,20102,34434,36858,33308,2372, 14976,17482,31223,13045,38018,39558,42001,39529, 41737,40750,42488,38732,36223,15386,31862,42680,16524,34101,44942,45530,40940,40819,44438,42001,44941,34810,36858,39055,41525,9434, 36223,36858,15177,11861,9946, 8851, 11552,18184,40584,44941,43685,39235,40486,42488,39514,23235,40531,3793, 36858,36223,34101,44437,22369,44035,35700,18342,17838,25956,33096,13340,20011,40633, 12384,28136,21915,42147,38691,41083,35700,9090, 36857,27721,18624,4322, 64, 27720,33796,41424,15896,44437,7355, 29477,29984,35227,44941,14453,32898,20200,28576, 21244,37594,35700,15491,44437,44941,29983,45529,16611,39969,38654,3021, 44940,2257, 45529,41082,1661, 24103,18409,18971,33796,35700,24346,21499,34809,37927,12886, 34433,24102,27330,38766,44437,19337,28575,36857,33531,13061,28574,25360,23440,28573,33796,23855,39623,33796,36857,41082,22867,17372,38389,38258,38788,1881, 42889, 14551,12738,36223,24101,15871,25113,30602,40530,36857,22676,31861,25359,37593,44436,43129,36222,26605,38383,44940,34809,16586,14702,36222,35699,34433,17168,16175, 24100,16913,35227,12705,45529,28572,13783,20667,31860,13017,43380,30601,3447, 35227,25955,33307,492, 3889, 38637,41335,40229,27719,14162,45529,33795,37695,41737, 37896,38718,42000,37736,44436,34433,37593,17938,18134,28135,36222,13303,35227,44436,32568,14831,41628,31859,44436,41335,41627,45528,37593,38341,41248,43380,38741, 42314,40633,36222,38253,44940,40389,37849,42679,39482,38928,44940,38276,43379,44939,39940,42488,43685,35226,44035,43128,42487,44435,40819,41525,40059,42889,42314, 38115,39642,38816,37825,39245,18506,42679,40303,14759,38234,43684,39152,31858,39759,37593,37765,45528,42000,41423,38412,42487,39641,40303,38240,39161,37592,33307, 42889,6968, 43128,36856,21135,37723,33531,39024,21784,41867,18133,37836,39823,42314,21783,40939,25358,38632,38666,40750,8384, 38794,27329,21639,38370,44939,38788, 41248,42487,36856,37592,40188,22207,21638,41423,36221,26604,34433,36221,36856,37971,39558,30600,31222,14694,44939,36221,35699,26253,21914,29023,32567,25954,13773, 33096,34809,35226,33095,36221,37592,33795,16988,39604,34101,21134,29022,34101,40389,40262,4896, 37866,43684,39054,45528,29021,38015,44939,38200,38566,15350,43684, 42314,41163,42000,43128,15542,34432,36220,26252,12867,41163,12828,18766,15431,41162,40058,40262,14395,80, 45528,3840, 110, 2095, 18765,36856,35699,44035,36855, 15975,16987,31857,43128,42888,25646,19832,5415, 33095,28571,36220,22866,35699,15780,15779,28134,28570,44435,41627,38579,34432,12017,43379,34432,33795,7721, 33795, 28569,36220,13675,15113,35226,749, 42487,33307,24345,17688,33794,35226,36855,36855,38544,42147,34432,37592,34431,35225,6318, 38950,17102,35225,34431,40530,45527, 28568,36220,40058,12929,36219,29982,38098,38517,39105,43127,39070,42679,44435,43684,38928,43683,42486,40750,39105,26965,15227,34809,28133,39641,33095,12743,41737, 45527,18076,39867,43379,41736,45527,40750,33794,33794,30599,33531,30598,36219,7229, 38850,36219,19922,36219,34431,30597,13465,22065,35225,18694,12955,41082,42888, 39313,39917,40345,2027, 19336,45527,41423,39441,33794,13542,17062,30596,37810,38825,28567,38576,28132,33095,33307,15321,42486,42888,38886,41627,42313,45526,37591, 38886,20774,44034,28566,33530,22206,44938,37942,31856,44938,41867,43127,44034,38949,40486,23439,35225,21498,38032,6589, 39822,38494,44435,44938,39846,38662,22368, 39289,44434,41082,40262,36218,35698,33094,33530,2860, 13894,17282,18183,42147,30595,2436, 41627,29476,12080,43683,38340,39264,44434,14251,44434,38557,42000,44434, 33530,20428,33306,34808,41334,41248,30594,15159,41999,39194,39482,41334,39696,39678,37761,2096, 2149, 6693, 36855,34431,23640,28565,44433,45526,538, 44938,40345, 15490,44034,1105, 43683,1242, 33306,34430,35224,42147,45526,35698,45526,24099,28131,23438,20010,38129,38160,39482,38732,4835, 38675,431, 44034,40119,40486,38289, 43127,38608,32898,44937,42313,39129,44433,36854,39128,40088,37938,43683,41999,41524,39302,24862,42486,23639,13832,16202,34100,37591,44433,33306,38662,17980,33793, 15064,38280,43682,41247,43379,6100, 43682,44433,39696,45525,38666,38309,36854,36218,31221,27718,40058,34100,35224,15007,35224,43378,39677,42313,41736,40939,24098, 34808,29981,33094,35224,37591,13450,11083,41081,25953,42679,36218,41999,32566,43378,5326, 44937,37591,35698,35698,22525,14046,16201,25645,31855,26603,36218,40058, 44937,37590,20666,45525,44432,31854,41423,43127,45525,36217,33793,30593,38104,42146,44432,13127,32897,38830,19415,45525,34808,17597,44432,32565,22675,20895,370, 5100, 41334,34808,25952,14626,38434,41999,38939,30592,20427,27717,42678,44432,17937,37876,45524,44033,38342,33306,37590,38024,44033,40150,38527,42888,44937,21133, 42486,1093, 20310,36854,42485,33094,43378,42887,23638,43126,19744,39604,39558,44431,41422,26602,41524,38314,37590,34100,29475,35697,33793,34100,10879,40818,38148, 22367,33793,24861,34430,13839,34430,43682,24584,40749,33792,44431,6450, 17017,45524,20101,10574,14469,10216,9466, 34807,10765,9959, 37846,38640,10737,10025,8562, 9686, 42887,8043, 38838,44936,8092, 9291, 15686,43682,22865,35223,21637,33305,40345,44431,17016,39372,38431,42678,43681,39313,39514,41867,40436,39759,41422,15385, 21497,2915, 42146,34430,35223,34807,36217,26601,41867,43681,44936,43681,44033,36217,24860,20199,39211,13086,33530,13020,40119,42485,35697,30591,34099,25357,13852, 32897,26251,30590,41626,41736,44431,29980,27328,23234,25951,34807,37590,26250,36854,14897,34099,29474,22674,14326,22673,23854,37589,19647,32897,42146,13666,17642, 33305,42313,41006,44033,13716,34807,39362,28564,14550,35697,17167,23853,45524,39161,29979,44936,17687,39917,34099,42485,31220,12619,22524,36217,23233,26249,25950, 22672,32897,12272,32564,40057,45524,38129,23852,36216,39441,25644,34806,40150,36216,44032,34099,38544,18463,17245,26248,16284,29473,19484,43378,39822,43377,32896, 24097,40026,29472,29020,40229,14701,22864,34806,24096,18897,36216,12994,44430,33094,42887,36853,41866,14139,35223,34806,37589,44936,12972,43377,40749,38136,43681, 38756,31853,13134,41081,39759,12841,44935,13374,34806,44935,20309,12868,38659,40871,33529,41998,22366,42887,40302,18970,34429,20426,28130,16912,22863,17837,19921, 43377,38877,44935,41247,41998,44430,40686,41736,42485,39514,45523,34805,29019,38154,41422,43680,42886,39469,25949,32896,2315, 44935,2509, 42886,42886,44032,39513, 34098,43377,12908,19033,40344,42146,45523,41735,21636,41162,35697,6323, 35223,24344,37589,17371,34805,35696,37589,39386,13243,42886,42145,18896,16155,25948,37588, 2036, 45523,44430,41334,40818,24583,32563,40119,1755, 42145,15384,38163,38651,3252, 40685,39498,31852,36853,39737,41998,4267, 40485,14609,40262,14786,33792,33792, 37588,45523,12067,29018,33305,31219,31851,41422,36853,35696,24859,40119,37943,42678,45522,43680,39542,39758,45522,42885,45522,35696,43126,11623,8243, 4117, 39822, 11782,42145,36216,36215,5496, 44032,37588,2258, 27716,25947,33792,1348, 38654,39128,39414,35696,26964,2960, 36215,44934,40026,42678,33305,40088,42312,39397,36853, 34805,3638, 3253, 11696,43376,43680,36852,36215,24582,37588,35222,34805,37587,39677,44430,40026,43680,35222,35222,23851,39846,42885,2238, 41333,40583,39737,26600, 28129,35222,27715,35695,44934,13901,33791,32562,29978,5103, 41421,29471,22205,26247,12995,38856,36852,36852,41866,27714,36852,27713,45522,40302,40485,38908,35221, 45521,40025,36851,42312,20773,23232,36215,14726,22365,18764,37587,34098,6206, 33529,19483,13536,26963,17514,34098,19831,26246,21496,35221,21367,25946,36214,35695, 35221,19830,26245,44032,44031,45521,29017,44031,14220,41247,45521,42885,14161,41421,38778,42312,45521,42145,29977,43376,39301,44934,38549,42144,39264,13506,34804, 29470,31850,35695,20772,24095,33529,23231,33304,35221,4068, 42312,34429,36851,36851,3761, 5867, 44429,644, 27712,12259,13185,34098,33304,37587,330, 4707, 44031, 35695,37587,37586,37586,16773,36851,45520,24094,33791,19032,35220,34804,26599,33304,34429,39604,12123,34429,333, 1097, 34804,17785,34428,39940,44031,2916, 41866, 36850,39335,43376,29976,3133, 15541,42311,35220,34097,25356,26598,38735,36214,44030,43126,32896,40939,38897,33093,37586,36214,37586,28128,20009,24343,45520,43376, 38200,38812,36214,41081,33529,16881,39098,44934,44429,30589,42677,16368,40749,31849,38635,1134, 43375,44933,44429,37585,18022,34428,40188,21635,42677,37585,14109, 29975,39940,41421,39335,45520,35220,34097,26244,32896,13179,23637,29469,39867,34097,38326,38699,41866,20665,43679,40939,13480,14191,18075,33093,32895,12238,38206, 38709,40389,40302,45520,42311,7620, 38740,45519,39012,36213,15430,7251, 18408,34097,26597,34804,21913,42311,44933,39061,44429,43375,40261,35694,35694,20425,36850, 34096,42484,31218,16200,44428,39780,40583,15627,41865,30588,17370,31848,42885,44428,37585,43126,36850,25945,27711,14877,27327,40818,41998,24581,38005,2778, 45519, 40436,38371,43679,38289,43375,39128,27326,44933,45519,42884,33791,33093,37855,44030,39659,39120,45519,22862,19646,44933,44030,39089,6694, 35694,2151, 13814,44428, 34428,21912,45518,44932,44428,34096,27325,20308,36850,38492,31847,19335,22861,35220,36213,1979, 42677,41005,41735,41081,38155,36849,21782,34428,41247,43125,6697, 27710,37585,4836, 41005,33093,1947, 44932,45518,6320, 42884,4708, 25112,38774,41865,2439, 5583, 2779, 42884,30587,36849,3132, 5264, 40749,44427,40261,40633,44932, 43375,9782, 11289,41162,39145,41005,45518,8169, 42484,27324,5997, 24858,37584,2583, 35694,7116, 13665,5331, 7798, 44932,11860,37584,7797, 41997,45518,10024,44427, 6971, 33092,36849,23230,28563,36849,36213,36848,41421,36213,27709,22523,42884,44931,44030,44931,34427,26962,35219,36848,44931,43125,43374,44931,42144,40871,38709, 41333,40229,44930,39916,45517,44029,44930,45517,42677,39969,41626,45517,38787,38307,6319, 6452, 6105, 5774, 41080,35219,41865,44930,44930,16231,37584,4118, 15802, 5328, 20894,6453, 37584,31217,33528,34803,43374,10962,45517,16283,35693,34096,31216,35219,37583,23850,35219,42676,37942,6973, 6103, 3794, 35218,33791,26961,41865, 35693,35693,37793,44929,38232,2314, 4581, 5775, 20664,36848,37583,35693,39468,36848,6822, 4323, 3842, 34096,42311,32895,5329, 33528,41162,36212,35218,32561,29016, 32895,34803,1533, 43374,16948,36212,33092,10472,36212,36847,11932,42310,45516,44029,16255,34427,36212,13290,19645,26243,34095,26596,18623,37583,10269,39468,22860, 23229,29974,23636,23228,29973,34095,28127,31846,23635,30586,13698,33528,25111,26960,18969,12882,41997,36211,14745,44427,34427,11082,11859,36847,36847,41246,43125, 43374,39235,31845,36211,10294,40685,35692,8431, 42676,2614, 5999, 36847,36211,22064,33092,109, 3760, 40302,32560,36211,33790,32895,29468,37583,7248, 3315, 34427, 36846,11340,34803,36210,25643,36846,18622,18693,13242,35692,9259, 10293,34095,22364,24857,33304,50, 2833, 35218,33790,25110,43125,36846,33303,36210,31844,44029, 33092,35692,34803,36846,33303,45516,16585,35218,2510, 12492,25944,13105,26595,34426,45516,37987,38561,39160,39397,4266, 36845,9323, 34095,45516,18132,37582,34094, 32559,19088,16986,45515,29972,18407,27708,33303,36210,7609, 25355,19565,14603,7632, 7952, 22671,27707,44929,34802,38321,37754,43124,41333,43679,38126,40685,42676, 38366,7458, 44029,41246,22670,37582,39939,34426,43679,45515,44929,40748,19829,15083,40187,42676,7313, 40685,40187,8319, 41005,10666,27706,35217,35692,37819,38699, 36210,561, 45515,5667, 39737,39152,37582,40344,37970,44028,15082,5495, 45515,45514,5330, 34426,34426,16985,18293,42484,35691,6821, 7668, 37582,28126,9460, 40632, 9174, 19743,16199,1164, 15081,772, 44929,19644,37874,39498,39780,40530,44427,33528,43678,40344,35217,9990, 10100,7762, 33303,9899, 7153, 39799,2150, 9211, 9989, 44426,10916,9592, 8291, 36845,35691,12096,31843,37581,14868,10764,10961,8037, 23227,39845,36845,9332, 5040, 44426,33527,30585,24580,42883,33527,17979,6102, 11622, 9178, 37581,29467,14655,10695,1221, 7927, 10010,36845,7240, 38630,3412, 2097, 31215,25109,36209,43373,37581,17135,34425,27705,33091,41735,27704,7719, 40632,7117, 29466,34802,24342,34802,17061,10763,40088,11038,10960,10069,29465,35691,2613, 33302,29015,37581,29971,39372,10444,8729, 39220,36844,31214,42675,44028,35217,5998, 30584,34094,35217,9219, 36844,22669,13428,35216,29970,11481,11695,9638, 9772, 37580,8903, 44028,16646,13446,44928,33302,37933,40187,6974, 41997,8139, 7782, 33790, 41864,43678,23041,32558,36209,9591, 40301,6322, 41333,7546, 39585,7942, 33527,32894,7655, 37862,5582, 41997,44028,39798,39372,41004,45514,5039, 2991, 445, 5866, 41864,44426,767, 777, 8591, 43373,29014,9405, 11226,8298, 26594,38944,9771, 37827,45514,40632,23226,38316,27703,43124,39574,7553, 33302,44426,36844,36209,8011, 26593,16730,37580,33790,20771,7526, 7300, 7367, 7973, 32894,7557, 37580,28562,35216,8019, 35691,16645,7358, 42310,33091,42675,33789,26242,20100,34425,30583,6106, 5263, 41626,40025,36209,33789,29013,35216,7637, 45514,44425,34802,36208,7399, 42484,6325, 36208,37580,39005,41332,41864,18763,20424,39822,33527,25642,39362,15518, 45513,34801,43373,42310,38387,39253,44425,37780,40187,38368,42144,43124,44027,38159,45513,37921,34801,37579,33789,4157, 35690,29969,24579,36208,25354,33302,30582, 36208,23437,37579,33091,29968,32894,34801,12722,35690,37766,38113,38314,21781,34425,23436,29012,15939,36844,26241,21495,34094,42883,24341,29464,43678,33789,36207, 36843,36843,43373,34801,21366,34425,7390, 40186,7118, 6321, 6324, 12409,18968,16772,31842,36843,44425,7147, 38630,1001, 9817, 6972, 5183, 2486, 12192,40938,19643, 12898,36843,12986,29011,26959,9748, 6592, 35216,11961,13248,19256,37579,45513,36842,19828,43678,44425,32894,26592,16523,1435, 5417, 13359,22668,8246, 40684,9505, 10626,41996,40186,44027,37579,29463,33788,12233,39325,38756,36207,41332,41161,40261,40583,41420,17446,36842,35215,10397,33788,38699,41332,9131, 37764,11781,39716, 15517,1170, 36842,34800,42144,43677,32557,43124,40748,41080,40684,1670, 2082, 6695, 1128, 5996, 1599, 2940, 6975, 37965,39659,41420,41524,37738,40388,43123,43372, 41864,34800,1900, 44424,42483,38716,44928,40301,44027,38616,2553, 4419, 11225,5497, 34424,26958,38490,44027,36207,33091,33788,26957,37742,39301,22522,36207,38504, 44026,39372,40388,42675,41863,37962,39780,41735,37578,38556,38446,35215,7307, 39542,8791, 17204,10471,7877, 11551,10625,32893,16610,8084, 37578,9072, 8398, 8938, 18021,40150,45513,37796,18131,35215,26591,36842,36841,38629,42883,16457,38999,19482,33526,35690,36206,36841,25641,37578,42143,22521,32556,18074,32893,16456,32893, 421, 41080,3233, 5668, 41080,21243,42883,38918,42310,36206,13883,34094,19087,26590,34424,9705, 43372,37578,41246,35215,36841,36841,37577,33301,42675,38363,41996, 41734,39677,41626,10168,8282, 42882,6820, 10009,9164, 37577,31213,34093,26589,34800,11550,36840,33526,16017,9470, 34424,35214,42309,45512,36206,33301,23849,27323, 30581,34424,36206,16254,33788,34093,21494,37577,44424,32555,8692, 8166, 21780,33090,17936,29967,8180, 9433, 40684,43123,40748,40025,33787,10373,37577,34093,35214, 33090,38320,39264,36840,33090,25353,39623,41246,44424,39037,44026,42143,39030,39324,34800,21365,36205,44928,44928,41863,44424,18341,25640,36840,37576,18239,33301, 34093,40583,42143,39046,42882,37576,40388,39969,39604,39695,39454,33090,363, 35690,38890,38068,38876,44423,42309,44423,26588,28125,34092,37576,44026,44423,21493, 37897,37824,41420,38469,39005,6819, 43372,43372,40088,43677,44423,37853,41863,38112,41161,38279,17935,41332,37576,12516,44927,23040,32893,40485,39289,43371,38752, 38985,43371,40301,40344,40818,42483,24578,42143,41625,42674,41734,43677,29010,44422,42882,41996,44026,44422,44927,36205,40343,39386,39313,39349,41079,39228,41079, 40150,45512,41331,17134,34092,35689,16455,44025,35214,36840,39758,44025,43371,42483,39995,43677,43676,41420,44927,44025,32892,11621,27322,33089,39641,38688,44422, 39324,33089,29462,21779,9759, 21132,43123,43676,42882,44927,41331,40632,43123,43371,8803, 10249,11694,29966,38650,45512,1442, 11178,37575,38760,35689,20099,3639, 36839,19174,20770,40582,21004,43122,25943,17399,8990, 36205,37874,43370,38395,39120,38266,1473, 2939, 29461,5038, 16454,32892,14896,16729,21778,32554,11858,35689, 43122,37575,2397, 2941, 45512,44422,23848,43370,5772, 20423,41524,38671,39513,41419,42309,40435,45511,40343,44926,42309,18831,32553,39513,44421,38144,38520,40870, 40025,39069,2292, 43676,38244,43370,37998,44926,39641,39558,38691,23847,41523,43676,44421,36205,41996,1644, 44421,45511,29965,44421,42881,40388,42674,40229,41995, 41734,43675,43370,35214,25352,44926,38671,39968,41523,41079,38763,37829,38732,42483,40186,45511,36204,44926,45511,41523,44025,44024,43675,44420,34799,27321,22063, 32892,19920,25108,41523,44925,36839,36839,35689,32552,27320,22859,30580,44420,34799,22858,35213,41245,44925,44925,989, 34423,40870,44024,39640,41331,34423,37575, 23846,43675,44925,27319,40582,43675,44420,42881,32551,37575,24856,22204,36204,34423,34799,34799,36839,23225,32892,35213,35213,41863,43122,42674,40631,21364,26956, 37574,39779,44420,35688,36204,20098,36838,38199,32550,38331,27702,25639,31212,20307,36204,18406,21003,32891,14639,37574,16486,37574,19827,34092,22667,32891,27318, 35688,36838,22520,6454, 40435,41995,33301,31211,32891,9093, 8966, 36838,34423,44419,34092,36203,24577,33089,8971, 35688,35213,36838,36837,28124,34422,44924,35212, 35688,34798,42881,18692,11620,37574,37573,33526,29460,6696, 25638,12802,23845,19742,33089,34798,32891,37573,39994,37573,23844,35687,33787,8067, 40817,41862,25107, 45510,40582,12800,35212,36837,44924,17281,14625,36837,37573,33088,25637,35687,37572,10915,36203,37572,35212,31210,28123,10344,20422,35687,29964,12762,30579,34798, 17686,35687,26587,18967,34422,17166,21492,31209,33088,34798,36837,26586,36836,37572,12982,36836,14023,25942,24093,25941,35212,25351,3890, 33088,37572,38547,39037, 40631,41004,7475, 24340,6591, 35686,44924,26240,37571,37571,37571,21131,25940,11693,36203,34091,42142,26585,33300,41862,42308,41331,10736,37571,20769,12474,37570, 2272, 40817,35211,37570,37570,9398, 25939,15516,12280,35211,36203,31841,36202,34797,33526,40582,36202,39758,8583, 40938,39758,38479,44924,44024,43674,41004,38532, 41245,44419,10694,38208,43674,3476, 38699,38725,42482,41734,11780,9167, 33300,27317,10268,34422,45510,40087,43369,34091,33088,39397,41625,43122,10372,44923,34091, 36202,16392,38127,43674,38271,40149,38918,39821,38216,34422,18020,29009,10428,44923,8509, 37570,45510,6205, 36836,32890,36202,35686,36836,14867,42482,9147, 31208, 7741, 36835,36835,7941, 34091,32890,26239,36835,37569,37569,11619,33525,37569,14239,21002,34090,25636,9242, 11126,37569,24092,13611,10046,11779,35686,19564,8361, 31207,36201,8633, 24339,11411,32549,30578,22363,34797,14561,37568,34421,34797,19741,18405,37568,23843,10807,3200, 9923, 9175, 7115, 32890,37568,31206,34421,32548, 20768,16367,19255,37568,11177,29459,37567,44419,1145, 1738, 6207, 19414,45510,14279,34090,32890,34421,21363,33787,39386,40530,44923,36835,17101,36834,39585,38718, 39289,3107, 36201,35686,14351,42881,20663,34797,44419,33525,35685,36834,20767,38297,39498,38766,38587,36201,28561,34090,33300,14744,7978, 26238,26584,22519,33525, 29458,33525,19173,33787,27316,22362,34090,32547,26955,34796,35211,35211,23224,33786,31840,33524,22062,34421,44923,40301,41625,35685,18238,25106,18237,39528,41161, 27315,35210,36834,43121,33524,42142,15098,24338,36834,33524,9758, 9146, 10735,36201,7592, 34796,34796,37567,41522,36200,35685,36833,36200,33087,17203,35685,36833, 35210,39497,28560,36833,33087,22666,29008,3512, 39045,39695,39069,39274,41330,42674,42673,43369,40186,44922,37908,39313,34420,35684,38864,45509,38097,39234,40631, 43121,41625,39482,44024,45509,33300,37567,37567,44418,37566,35210,35210,35209,37566,31839,17100,42142,23435,33524,36200,20766,13028,22518,35684,32889,45509,44922, 33786,25105,33299,8684, 11480,2335, 34089,34089,31838,26237,33087,22203,36833,18404,37566,9971, 36200,10734,1267, 22857,14876,34796,43369,38412,43674,9087, 33087, 44418,38428,40057,36199,41419,5773, 7631, 8215, 10624,16805,11778,36199,10114,31837,29457,25104,34420,29963,37566,16130,5102, 32889,25938,9958, 17934,36199,29007, 29456,33299,24091,14459,21911,14975,43673,7181, 10099,13461,24090,36199,7713, 40817,33086,35684,34089,44418,44418,41624,45509,36832,36198,44922,43369,44417,36198, 39414,6104, 35209,33086,17331,13782,13828,38798,33786,41419,37565,38529,43121,40581,33523,13000,35684,44023,45508,36832,7756, 44922,38064,37565,37565,38591,11857, 44417,26583,35683,40118,35683,7889, 11224,35683,37565,19919,34795,31836,33299,32546,40631,40149,42142,37911,39939,42141,40684,39152,21362,37564,25103,34795,24855, 3022, 35209,38517,39994,45508,39528,43368,43121,39349,38771,38047,36832,16984,45508,44921,45508,44921,44921,35683,33786,44921,41522,42880,44920,40683,40938,43673, 45507,36198,34089,44023,40387,44023,43120,44417,38561,41733,39054,41004,41079,41003,40343,26954,30577,31835,36832,36198,40300,43368,42880,42673,43368,38313,38714, 38693,41419,44023,45507,16553,41862,40118,39737,41330,36831,36831,43368,38938,39426,22361,41161,45507,38479,22517,37564,41160,41522,45507,24089,37564,39677,33086, 16911,32889,36831,36831,7299, 43120,44022,33086,36197,40118,43673,4196, 37564,7689, 35682,36830,33785,36197,45506,35682,37563,42308,42308,43673,44417,34795,37563, 33085,43367,41245,37563,27701,34088,36830,34088,37563,22360,41733,42308,15859,19334,31834,18691,35209,26953,19826,32889,36830,34795,43367,15453,7312, 28122,28559, 42307,33523,36830,27314,34088,44920,32545,17641,19172,35682,35682,34794,34420,33785,26236,16341,20008,36829,29006,21634,15057,30576,37562,32888,34420,41078,43672, 41733,41522,41862,44920,4031, 29455,33299,28121,21491,27700,34419,33785,25102,38261,38688,40485,39603,38400,41624,45506,40748,44416,44920,45506,35208,33785,38251, 38762,42307,41330,43367,44022,37855,38263,38020,40343,36197,36829,31205,37562,8032, 35208,33784,24854,21777,36829,33523,23634,23039,39454,29454,919, 42673,39757, 43120,38325,38334,44022,39736,39361,38456,24853,17244,15778,28120,40024,10045,1044, 39736,8553, 39061,42307,31204,16230,36829,37956,33298,22856,2237, 40581,6568, 36197,24337,37562,17133,36196,36828,7635, 32888,43120,43672,32544,23842,21776,20306,18895,39069,36196,32888,43119,37986,38431,39371,39005,11176,36828,38798,40870, 39736,44022,38192,45506,22202,35681,17836,21633,30575,16584,34088,42880,34794,38207,39895,9104, 36196,44416,36196,17549,6942, 39413,44021,38881,41418,463, 43672, 6086, 42673,37906,38022,40024,41160,38479,39736,39481,43119,38602,38671,44919,35208,43119,40228,41330,38208,39334,41078,37980,41003,39695,45505,33085,39105,39894, 42307,41995,6945, 37891,42880,38613,38922,40342,37562,44919,38725,43672,40747,41418,42482,41418,42141,31203,39054,38654,38152,41861,44416,41995,35208,40529,45505, 45505,36828,37561,36195,33523,40300,42672,45505,44021,40683,39994,42306,39845,44919,41624,44919,10514,44918,37561,22359,43367,45504,811, 37561,34087,35681,36195, 29453,34419,33085,22201,19254,6678, 40938,34087,36828,16307,34087,36195,44021,37561,33085,32888,34419,38592,42306,19413,34087,36195,41003,38400,39274,44416,41245, 19563,31202,20542,35681,20765,32887,38629,38423,44415,38944,38629,43671,39867,41994,37560,34086,35681,32543,31833,16947,25350,39112,35207,23038,33084,22200,41521, 45504,43366,42141,42879,38310,39301,35680,37560,1190, 39077,5023, 31201,842, 42141,44918,35207,18130,29962,6795, 649, 7489, 37560,36194,13252,36827,28119,31832, 36827,39481,42306,41861,43366,24336,15301,23434,31200,14775,43119,6674, 2909, 16366,41624,37560,43671,37559,7624, 10343,34794,43366,10878,10837,14250,23223,35207, 39968,38497,36194,42879,39005,41160,42306,43118,44918,43671,35207,14967,15561,35680,22061,20764,5483, 34086,20007,45504,7574, 19333,24088,17243,7516, 36827,37559, 35680,34086,36194,34794,7898, 36194,37559,29005,29452,25635,18182,44415,36193,7543, 20662,29961,16092,35206,36827,33084,33522,6567, 3074, 33084,5486, 11777,39695, 40149,45504,145, 6943, 44021,40024,40870,39264,44415,43671,38811,40747,44918,43366,44917,41078,43365,14602,42672,43365,39968,40484,37559,39371,25101,42482,34793, 35680,42672,74, 41994,2234, 1052, 9496, 8237, 40118,8191, 38897,33298,9039, 8547, 8415, 1343, 3830, 38850,40937,44415,40387,40869,38385,13858,45503,43365,45503, 12049,37558,41733,37558,34793,35679,16091,35679,41078,28558,34419,20893,29960,33784,27699,29004,34418,36826,36826,39735,36826,37558,42672,18403,38985,34793,43118, 33784,34086,36193,40817,34793,34085,32542,43670,113, 41861,4772, 6085, 36826,35206,18690,13219,44414,17015,5092, 26235,12415,24576,13023,35679,368, 2747, 41329, 44020,12969,25634,43118,17739,1456, 12655,45503,45503,45502,4700, 34418,36193,39349,42879,41732,42305,42481,40869,44917,42305,44917,39371,22358,35206,44917,41732, 43118,43365,30574,33522,43117,39187,37695,42140,25349,23433,33084,45502,24852,33083,37558,37804,31199,22516,10623,10733,26952,39453,39139,34418,36825,7684, 40435, 31198,3101, 13976,32541,35679,33298,36825,34792,44916,40261,21490,24575,36825,18966,37557,33298,36825,11479,36824,36193,22357,26234,36192,33297,38097,507, 38398, 43364,1182, 36824,37557,3748, 33083,36192,42305,13925,26582,42481,36192,13791,35206,36192,37557,41732,36824,19481,37557,31831,37556,41861,37556,37556,39220,37872, 38579,37770,40747,38278,45502,37556,37879,39211,39012,36824,42481,45502,29959,26233,36191,33784,26581,37555,35205,33522,24087,26951,34792,39441,44020,44916,42140, 42305,45501,623, 44414,34792,41329,38308,19740,44414,3015, 43670,13493,36823,19739,6297, 36191,19412,25348,40747,383, 3633, 43670,43117,39968,35205,31830,10427, 39623,40387,40683,42304,38445,24086,42304,39468,36823,43670,28118,36823,34792,43669,40746,44414,35678,22060,805, 5752, 26950,33083,1846, 22199,37555,39194,20892, 44413,34791,34418,43117,42671,44413,30573,13453,34791,33297,33297,36823,12724,37555,24335,36191,29958,35678,20541,17885,2008, 33783,22059,17202,18129,18128,12678, 18830,21632,42481,36822,19738,36822,35205,34791,39105,44916,32887,44916,44020,36191,16683,3376, 18236,31829,37555,19918,35678,35678,22855,2663, 23222,26949,27313, 38400,38755,40228,39994,32540,38227,39603,39145,32887,41003,24574,37554,40937,40387,43669,33783,44413,40581,44915,43669,35677,20421,44413,32887,43364,34791,22515, 43364,10877,45501,5168, 37554,34417,33083,43117,43364,8695, 9210, 22854,25937,33297,21631,31197,39541,42671,20540,37554,38285,43363,43116,41732,29003,1042, 6296, 1742, 44020,32886,19171,24851,17242,43363,16229,26948,17548,35677,4515, 42480,36190,20006,36822,42304,17596,33783,36822,33783,38472,37554,33522,41160,44915,23432, 15938,45501,40937,36821,37774,38974,38912,41244,44412,7874, 11410,3634, 39361,17445,21130,7718, 10490,28117,10665,9033, 37553,43669,20005,39659,35677,42671,26580, 29002,22853,21775,42671,45501,37553,32539,36190,17884,34790,39386,41521,42304,41521,39194,43363,34790,40386,34085,5406, 39676,34417,43668,42303,3441, 6679, 34790, 39453,38202,39060,39676,38144,38520,40816,43668,38236,1819, 40484,40117,38999,40630,29001,34417,7764, 19917,26232,36821,37553,38124,41418,44915,3831, 43363,45500, 557, 4456, 6082, 37553,42670,40342,40435,39735,45500,40937,38421,43362,42140,42140,39541,41860,41002,44412,23633,33521,34790,33782,26947,38070,38713,40936,38044, 38870,38956,38340,38057,39211,38685,40386,44019,41731,40149,44915,42670,43116,40529,42670,42879,41244,45500,38128,40386,39528,43668,40816,42139,36190,33296,39210, 43668,41077,44019,34417,39112,39541,43362,31828,37552,43362,41994,40342,38798,41077,44914,44019,41329,44019,39939,4146, 41731,45500,39993,41417,40581,39361,44018, 42480,44412,43116,45499,38885,44914,42878,40148,40087,41521,44412,45499,1607, 38164,42303,39845,41159,38830,40484,39104,41860,42139,41159,44914,39288,43667,43116, 45499,42480,40087,42303,5971, 37904,38311,44018,38263,38168,40185,41994,38122,40869,39186,41520,43667,41077,44411,39054,42139,41417,40087,38896,39867,41329,39481, 39574,38127,43115,41623,38409,33782,45499,29957,38557,44411,43667,17014,34416,31827,28116,39468,41520,40816,42480,43667,43666,39603,44018,40816,45498,36190,45498, 42303,44914,44411,44913,42670,41860,42878,43666,43362,38993,34789,40185,29000,39385,41623,41520,43666,41159,44913,44913,40580,43361,41244,36821,40342,43115,44411, 44913,45498,45498,39037,40683,38527,42139,44410,42669,19562,24085,596, 4827, 40434,44410,42302,40936,40434,42302,24850,38793,41623,41328,43115,42878,39821,44410, 42669,44018,43666,42878,45497,39334,41244,41731,41731,45497,44410,40580,44912,40746,44409,41730,41730,43115,42138,43114,38600,40434,44409,40682,42669,41993,40386, 38908,39467,41520,42877,40385,45497,42302,39160,41993,41993,40815,44017,34416,44017,39715,44912,38962,42302,40057,43114,39676,40580,41002,43361,45497,42877,45496, 41417,41077,40746,42877,43665,42877,41243,42876,40484,44912,45496,43665,40746,36821,44017,44409,41417,40148,41159,44409,44017,43114,38106,42301,44408,38749,33521, 44912,41993,17978,36189,44016,44016,33082,39060,39735,44016,40148,40057,40682,39513,42301,34085,42669,40745,42876,45496,38555,38364,39069,38707,39361,43361,39467, 38731,40936,41860,40260,41328,41859,39623,40745,41002,38183,41076,41859,45496,38555,39528,42668,38544,38555,41623,38469,39312,42301,39916,36820,33082,36189,34416, 44911,33782,34416,36820,20539,28115,20198,6675, 25100,42479,28114,43114,31826,43665,45495,45495,5093, 42876,38464,34415,29451,31196,2066, 5485, 5320, 41519,43361, 39119,44408,44016,41622,40483,41158,43113,41158,45495,39171,39497,44911,43665,41622,11931,37552,3073, 36820,3782, 45495,45494,36189,30572,38453,44408,40682,27312, 44911,36820,37923,38192,42301,40300,39622,41416,44408,43113,4258, 14895,8600, 39234,5251, 25099,12086,41328,18829,36189,19825,40341,38569,44911,4644, 44910,35677, 40483,3440, 42479,41519,18462,36188,15112,16880,35676,31195,13674,13777,33296,3507, 44910,37552,36819,34789,42300,36819,35205,35204,44407,32538,24849,35676,34415, 37552,32886,35676,37551,13067,19411,30571,40117,37551,33521,43360,38642,41328,43664,14433,43360,40815,26231,38607,30570,34085,36819,42300,247, 38356,43664,41416, 40434,43664,37954,41992,38523,45494,39104,41519,39866,43664,38575,12874,15995,968, 2662, 41158,40433,26946,978, 1741, 741, 4889, 39866,42479,11776,5657, 3968, 25936,12578,33521,13754,44015,33782,36819,33520,44407,35204,18402,35204,16552,43360,37713,31194,40385,37708,44407,33296,39348,3595, 29450,4257, 3875, 3439, 4310, 43663,1753, 4458, 5851, 3406, 1226, 42479,3783, 17330,7093, 300, 4699, 41519,42668,804, 4409, 2884, 1896, 6080, 4256, 897, 3508, 4643, 37551,13206,33296,37551, 37550,28999,35204,31825,37550,43113,37960,37704,43663,20097,3969, 2637, 45494,40936,39497,42668,33520,44910,13241,36818,35676,35675,21242,27311,34789,34789,37550, 19170,36818,35675,4886, 34415,35675,24084,37550,23431,27698,36818,41730,33520,43113,38079,41992,37549,917, 25935,36818,37831,36188,41992,42876,42138,8924, 26579, 32537,31824,28557,44407,38807,8799, 33781,34084,8312, 10664,24083,271, 3675, 259, 37549,28998,42138,34415,35203,3747, 25633,37549,35203,33781,34414,25632,31193, 17444,4021, 35675,25934,8018, 10008,32536,28113,29956,45494,38713,42668,44406,45493,44910,31192,44015,41076,41859,41622,44909,44406,40630,43360,43663,9281, 34788, 7566, 22852,23430,12431,7473, 11081,41243,44406,7562, 34788,4577, 5484, 8780, 575, 44015,10342,15915,36817,22851,11080,32886,43663,10693,36188,7272, 44909,37686, 43662,42300,8857, 31823,23632,34414,34414,20891,32535,12913,7156, 12643,28112,33520,39068,39413,33295,32886,25933,40260,38896,39894,15974,34788,44909,41859,36188, 5852, 37723,20305,45493,37549,30569,11409,37548,19737,23631,21361,10622,11692,18965,8017, 37548,42478,33519,35203,25098,9526, 36187,5094, 381, 41858,32885,16111, 33295,28556,26945,7909, 8158, 40260,5566, 36817,44909,7712, 44908,27697,13074,36187,4408, 11478,11477,7424, 7733, 41327,17835,22198,23221,19736,14547,38410,40682, 7701, 41327,45493,44015,36817,39497,45493,7993, 4774, 39348,2935, 10692,35203,1675, 44406,1808, 6796, 43662,2804, 19642,45492,44014,3876, 41622,34414,2718, 44908, 45492,1608, 28111,34788,13015,24848,29955,15973,4773, 38533,4357, 31822,37768,39234,39301,39845,40148,44908,39104,45492,41076,42478,38087,38969,39228,41416,41621, 41621,42478,41858,42667,36817,38752,41518,45492,40385,44405,31821,29954,45491,45491,34084,6946, 8388, 19332,33295,13620,32534,10489,22514,9420, 34084,34084,37548, 17933,11856,41327,8551, 34413,39263,9331, 9092, 11855,10914,24082,35674,37548,22665,26578,9637, 11408,7854, 43662,10732,35674,9525, 10198,8196, 43359,22664,4887, 44908,41416,40483,11618,42667,38472,43112,37547,13975,40815,36187,29953,33781,42478,40630,14830,36187,34413,35202,28997,37547,34787,36186,9988, 8699, 8151, 45491, 33295,36186,17241,28110,31820,37547,9258, 28109,44907,24334,37547,35202,17547,12488,34787,42477,37546,35202,34787,34083,15758,9465, 9885, 11407,8644, 19824,33294, 36186,35674,39348,39676,45491,27310,34413,38811,42667,35674,40869,35202,31819,35201,35673,13159,26230,22663,30568,40868,44014,24081,20763,19331,43662,10426,3102, 45490,28996,44907,40935,41621,39263,12621,40935,35673,9140, 35201,26229,11288,13530,37546,34083,35201,37897,5319, 28995,41730,39967,41621,41858,34787,37873,38876, 40341,39324,38341,16728,16067,6083, 43661,19735,4411, 34786,7430, 42138,6565, 22356,43661,38674,2116, 12838,5024, 39119,31818,34786,14504,5091, 44907,7186, 8060, 36186,32533,44907,24573,42667,24847,25097,32885,33082,36816,13632,34786,15006,28994,40147,41858,9341, 13440,45490,36816,14231,16174,18894,37546,21129,13551,26577, 13990,35673,29952,36816,36816,21001,29951,13916,37701,44906,24846,42875,33781,17834,43359,16090,13014,43661,40935,44405,36815,5754, 21241,36815,12911,39993,40433, 41415,43359,45490,39441,44405,12793,40147,45490,44014,37995,42875,41415,38226,42666,35201,40385,45489,43359,34786,41076,44405,35200,34413,26944,35200,27696,30567, 37546,12331,41620,36185,18292,33082,42666,40483,38206,38974,40117,13694,44906,41992,37545,36185,36185,4457, 2117, 43112,21630,37545,38572,40228,42300,41327,42137, 38036,6566, 34412,33519,33519,38266,44014,38487,34785,43358,43358,24845,7, 41326,841, 6434, 6189, 3749, 6191, 36815,5322, 40630,896, 28555,2746, 44906,40580, 6435, 31191,36815,16426,43112,34785,39866,41729,44906,41620,12365,42875,21774,21910,34083,38305,43661,40868,38527,41158,43358,29449,44905,38482,42666,23429,41002, 13120,39757,37545,39253,41857,41415,23220,33780,3784, 27309,38068,42299,41620,34785,43660,34785,38352,39273,36185,38393,39574,38310,39348,44905,24333,28993,15757, 39360,41991,45489,41518,39779,40228,38968,43358,40482,37816,10573,44013,6081, 38200,635, 6944, 44404,41620,38195,44905,376, 38985,40433,40433,35673,44013,37545, 40868,42477,42299,18964,33081,33780,33780,44013,17280,33780,39347,33294,21629,34784,37544,40815,10806,41518,18291,19086,35672,22850,36814,35672,35672,30566,6084, 32885,32885,37856,37944,31817,39151,38426,38447,45489,17738,43112,6676, 7514, 45489,37544,33294,26228,37544,35200,24572,20096,35672,42299,22058,33294,44404,17932, 34412,41857,41243,45488,42666,38802,40868,32532,38968,40260,3785, 39397,43660,44013,36184,16110,23630,38455,42137,6192, 39967,41415,37792,21628,6677, 43111,39844, 37947,38078,31816,43660,37544,26576,39220,45488,6433, 44905,45488,13289,35671,17329,20762,35200,37814,41243,38476,221, 38684,39160,38414,38762,44904,39640,37893, 1118, 35671,43660,45488,36184,40867,26227,41857,36184,41857,41326,34412,36814,39512,44904,39202,44404,45487,2983, 37815,42477,37938,44012,44404,38182,41991,38568, 44904,36184,44904,28108,35199,40867,33779,19823,38072,44403,33519,38640,36183,38659,38605,43659,501, 41326,31815,23841,1200, 42477,10167,45487,34083,22197,37543, 45487,39453,37912,44903,38579,43357,40117,44903,37922,39675,4410, 34412,25096,43659,20420,43111,425, 4309, 43357,41991,13325,35671,4407, 44012,42875,37909,45487, 38765,43659,43357,39916,14922,10488,45486,37543,44903,17099,19561,39018,42299,40024,24080,6947, 5250, 34082,5321, 41157,45486,1307, 7345, 13119,37543,35671,36814, 35199,21360,42665,31814,33293,13735,35670,34082,37543,35199,33518,21489,28992,40814,37542,33081,1634, 13025,34082,31813,35670,13671,36183,33293,35199,35198,23428, 33293,37542,36814,26943,28991,28554,25095,26575,25631,33518,525, 35670,44903,33779,36183,36183,45486,43659,16485,37542,25630,25932,35670,2853, 35669,1109, 43658, 41157,25931,15097,3405, 40259,44403,4828, 4698, 25094,37542,13961,6190, 20419,36182,27308,35669,35198,39273,41326,18181,32884,23840,37541,32531,43658,500, 40629, 34784,4956, 18505,5972, 32884,33779,38896,26942,7726, 43658,44902,29950,41325,35198,33779,37541,37541,40814,42665,22513,16771,32884,33778,30565,34784,23219,35669, 40681,43357,26941,37541,33081,23427,36182,13795,37540,40935,4888, 36813,18127,32530,21000,26226,42874,3306, 35198,12730,37540,33081,32529,37540,18963,19641,17737, 43658,30564,13407,36182,15801,34082,14350,33778,14911,35197,19410,41729,45486,20761,34784,32884,33293,24079,29949,35197,33292,17784,36182,43111,592, 39821,5753, 37540,33080,37539,13147,34081,29948,27307,37539,16173,36813,9476, 44403,11339,41619,35197,20538,19085,34411,38489,44902,29947,36813,44012,36813,40745,42476,44012, 44011,33292,43111,36181,36181,39622,36181,24844,34783,13225,37981,1668, 43110,40023,39603,43657,31812,39396,43110,44902,36812,33080,35197,32528,34783,37539,28107, 35669,41325,45485,38797,24571,16016,33778,38040,38097,40814,38735,41991,42137,39210,39178,43110,43657,18126,33778,26574,14683,12937,45485,12120,43356,44403,25629, 34411,36812,25628,3469, 31811,44011,24570,18340,10663,19480,34081,4955, 43356,33777,31810,13089,7092, 19560,39735,44902,24078,22057,37539,32527,31190,18828,44901, 18573,33777,21627,26940,44011,12695,34081,19409,36181,18504,36180,20999,28990,12104,34411,26939,34783,35668,15452,27695,37538,21240,14447,34783,33518,38258,36180, 37538,44901,36812,35668,20890,36180,35668,36180,30563,18762,30562,18180,35196,18179,35668,41729,42476,32883,33080,15515,35667,43356,40745,33777,43356,27306,32526, 18689,42298,39757,43657,43657,14713,26938,28553,13835,13170,41075,17883,36179,14700,22056,18688,17685,45485,32525,12485,15777,36179,1863, 14067,20661,35196,40341, 23037,14000,36812,15370,34081,37538,20197,41075,18178,31809,26225,21128,12567,36179,36179,37902,32883,17882,15914,34080,35196,30561,44901,36178,33080,36178,36811, 26224,18761,31808,35196,44011,15489,28552,31807,21359,36178,31189,17783,44901,20760,16770,37538,37537,37537,24843,37537,35195,21358,31806,36178,36177,36811,37537, 40934,44900,42476,23036,33079,38212,40681,2273, 11287,7310, 40259,37536,7823, 3980, 37536,2811, 16946,3323, 34080,38819,42137,24842,37536,36177,34411,18125,33079, 37536,35667,33079,13555,36177,37997,14956,13427,44402,41856,33079,33518,6835, 44402,16879,7992, 11617,39894,43110,42874,7377, 34782,42136,39177,44900,42665,39894, 44402,34782,7378, 44900,44900,33777,37535,35667,31805,13122,21239,10731,22196,36177,35195,35195,33292,23426,37535,20998,12879,40482,44402,12450,21238,33292,39993, 37894,36811,17782,26937,30560,13753,35667,36176,16453,7607, 42665,5588, 34410,37535,30559,29946,6710, 41075,4588, 5429, 3383, 33291,22355,44899,41242,39512,43656, 43355,14053,35195,23218,35666,37535,45485,6476, 37748,36811,33517,19822,34410,37534,16609,28106,16109,17132,17977,32883,34410,38533,41729,392, 42298,339, 6709, 111, 3136, 34410,34782,24569,37534,25627,23629,19330,19253,32883,21488,44401,21626,19559,19916,35666,36810,37534,43656,43355,40744,29448,22055,33291,7237, 44899, 7839, 10443,10545,8911, 21625,14774,11338,37884,40814,8845, 43109,44401,15800,37845,33517,44899,44899,43355,38443,38288,7478, 8904, 3899, 27305,36810,36176,40185, 41619,41990,27694,19169,44898,43109,41001,39347,38715,38066,44010,37866,39798,9868, 5883, 41728,3081, 40681,36810,38968,41728,44401,37534,45484,23839,38280,43355, 42874,44898,43109,37921,38992,38126,41856,33078,13459,13475,38103,45484,28989,25347,9475, 34409,19915,42664,14841,23035,4370, 21773,34409,26573,23628,23838,36176, 13370,34080,15265,23425,3519, 3798, 37768,44010,38291,39512,41001,36176,39413,37918,25930,642, 6223, 6219, 5885, 19734,12479,2106, 38476,1490, 37533,14608,39893, 45484,39119,35194,31804,42136,28551,37533,35194,42136,20889,21127,33078,23627,17684,20759,17443,33776,35666,34782,18893,19558,14238,36810,34781,30558,33078,34080, 37533,24077,19914,41619,9841, 10994,8611, 33291,25626,36175,39866,44010,45484,19031,21487,34079,24841,35194,40185,35666,15332,33291,17833,36809,43109,10836,41990, 43656,36175,13477,44898,16198,20660,24568,36809,27693,34781,20418,19168,35194,36809,36809,13723,37533,36175,36175,18760,25346,21486,28988,21624,24332,41518,44401, 20659,18073,18892,36174,42298,423, 5591, 42874,43354,42298,39171,45483,44010,39640,43354,38896,42136,42873,39622,39622,33290,44898,45483,24076,44897,44400,44400, 40341,9933, 33078,10805,7273, 39453,3687, 32882,17595,20004,17328,28105,36808,31803,7707, 6477, 34409,23626,18019,10059,27692,17736,41990,9867, 41728,21772,16340, 21126,33776,35193,7340, 7906, 38081,31802,42297,37532,25625,33290,26223,35665,17781,39396,36174,34079,6475, 34781,34409,23034,45483,20658,31188,33776,39186,40384, 22512,44400,9566, 33517,42873,23217,18235,8031, 39993,41990,8258, 39734,38881,43656,38488,45483,39844,44400,41242,45482,26572,34408,33077,31187,30557,28987,36174, 30556,17735,7572, 13096,13664,17640,33776,6833, 44897,7483, 39253,8127, 16282,19733,8250, 43354,23625,37532,11854,33775,11476,32524,17639,33290,4841, 3979, 7409, 40300,39053,41728,38230,39413,41157,44897,44897,7160, 39426,3062, 7778, 12002,34408,7271, 8920, 11223,9123, 44399,2399, 2945, 7140, 1874, 6222, 3237, 3900, 44399, 33775,20888,40934,42476,40744,44399,45482,2537, 43108,33290,45482,38131,45482,44399,38468,40086,44896,38380,44009,40023,39077,42873,8645, 43655,29945,36174,31186, 38232,44896,38703,22849,7589, 7869, 36173,34781,6711, 13334,16583,34079,25929,36808,24075,34079,33077,21357,19732,16452,17546,34780,4327, 35193,45481,37532,36173, 35193,14315,32882,4424, 40227,45481,4910, 43354,38131,43108,41856,35193,34780,36808,29944,30555,36173,33077,44009,42475,36173,41325,45481,41157,40681,44398,41856, 37532,28986,39324,39602,43655,43108,41727,32523,34408,33775,16582,21485,37531,27304,35665,34780,29447,36172,28550,36808,36172,21356,22848,34408,16839,42664,30554, 33517,11337,36172,26222,33775,37531,18827,37531,33516,33077,18234,35192,35665,19167,34780,34078,19731,32522,34407,25928,25624,35192,34407,23624,37531,22354,31801, 35192,37530,17780,33516,33076,40744,37847,38307,40384,38735,43655,39798,41727,38275,34779,43108,37530,35665,18339,8345, 11125,25345,24567,20997,36172,34779,30553, 37530,393, 44398,34779,36807,35664,20196,36807,22662,27691,35664,34779,16682,36807,27303,21125,40259,22054,32521,33516,29446,36171,33076,34778,35192,32882,36807, 22847,36806,33774,37766,44009,2555, 41989,44896,29943,32882,33076,6217, 45481,32881,19821,34407,36806,23424,11222,38979,11775,42664,9653, 8268, 39658,13969,24840, 9884, 33289,11336,10762,45480,42297,44398,36806,10267,36171,9069, 35191,7532, 39128,39757,5269, 35664,37530,9115, 8138, 12825,33289,39675,38589,40867,6003, 7594, 9957, 4160, 17060,17279,31800,12946,37529,37529,8289, 10023,9154, 10231,37529,31799,40086,38607,45480,38268,42475,39675,42297,41001,44398,3645, 584, 38706,40680, 45480,38706,43107,6113, 43107,40259,42873,44896,42297,41619,3518, 39186,42872,39798,42664,42872,35191,6480, 40867,39127,6983, 26221,1723, 45480,39253,38802,8541, 11691,10513,9524, 10129,10913,11549,11616,11406,354, 43655,39385,37529,41242,39734,44009,44397,38080,40744,39426,38165,40086,17545,36171,36806,34407,11690,11037, 9866, 33076,37528,19820,35191,36171,7597, 9163, 10598,7530, 18461,35664,10425,44895,9144, 11853,23623,39675,7564, 38793,39658,41989,41242,41075,11475,11036,44397, 8900, 11175,36805,44895,35663,8610, 44008,6115, 8673, 8720, 7866, 9103, 35191,34406,9270, 3137, 40866,42296,11286,8823, 676, 8146, 39602,43654,21484,10156,9495, 14955,37528,10487,20758,34778,33516,12962,28549,8350, 11285,11852,24074,41989,37528,18072,25344,34078,8267, 42296,11930,36805,36805,23837,22511,20095,39024,45479, 36170,33075,13981,22353,28548,36170,25623,29942,35663,29941,23216,11929,34778,9256, 42296,31798,33289,18503,34778,17240,27690,14200,32520,34777,7636, 41074,12134, 45479,12063,12540,42663,30552,28104,31185,27302,36805,44008,27301,41618,8063, 34078,35663,28547,10662,10396,21623,8777, 37528,30551,7347, 9956, 7848, 12954,22352, 45479,9747, 8860, 10197,6985, 45479,10835,9188, 8567, 39151,39821,41414,39621,45478,41325,41989,9321, 7853, 6220, 33075,14293,8027, 8596, 28103,30550,36170,26936, 37527,37722,38163,27300,32519,24073,38807,29445,8338, 34777,41517,23033,19030,43107,35663,42296,15514,19819,31797,11774,36804,7605, 45478,36170,43107,41988,44397, 40529,8668, 9922, 34078,39527,8969, 10470,7645, 10761,8582, 7697, 1836, 4712, 11284,4654, 4532, 16727,7653, 14283,9303, 11405,25927,37527,28546,42663,10341,39893, 43353,43106,45478,44008,44397,7570, 34077,43654,44396,6478, 43106,42663,43106,43106,42135,43654,40813,45478,42475,38564,38121,42872,38132,44396,38955,44396,44895, 38973,41074,44008,44007,43654,36169,22351,36169,45477,41324,40934,44396,42295,44895,42135,34077,45477,43353,41618,44395,41517,44894,38834,40629,44007,41074,18826, 20657,43105,44007,15147,41988,41618,34077,34406,41074,37527,10512,10022,11474,11335,11221,1222, 44007,40116,41324,39694,37938,42663,41414,16608,10993,7773, 22510, 7715, 34077,36804,36804,34777,24331,28985,10469,7573, 7847, 10098,8878, 9118, 7563, 8240, 38706,2353, 509, 41727,42872,44395,35190,41517,44395,41727,42662,35662, 33075,8275, 39557,11689,7582, 35662,28545,26571,35662,18290,7568, 36169,41517,32881,10292,8010, 14304,10248,44395,9865, 22509,7758, 38662,38111,38453,38324,41988, 43353,45477,41618,40299,38844,42135,45477,31184,40482,40743,44894,44006,42871,43653,650, 43653,44394,39674,39193,38393,44894,38517,39089,44006,40934,45476,38927, 41855,40933,43653,44006,41156,33515,40432,42295,40116,44394,37961,42135,20656,20094,33289,44894,43353,44893,9679, 41726,44006,44005,42134,42475,45476,37527,44394, 42474,34777,19730,43653,42295,42474,43105,39585,40340,38452,41241,41414,41516,21909,37526,45476,20537,36169,43652,40482,39779,5590, 38850,39715,40529,38698,38895, 39347,41001,38778,45476,41073,41241,40579,44893,44893,11928,40023,39481,41156,40481,45475,41000,43352,40227,43105,44005,37983,5784, 29940,21771,34406,17734,38176, 39734,44893,38807,40528,25622,42871,44394,40432,43352,40340,42134,45475,41414,42295,18572,38040,19084,41241,39263,40629,40933,9738, 10424,41988,38251,38032,42662, 44892,41516,41617,42294,1120, 36804,44892,34076,30549,40056,41073,38895,4784, 42134,15584,1925, 38034,38110,41156,44892,40147,32518,37526,37526,36168,36803,33774, 23622,13320,34406,23621,33288,17442,34776,28544,31183,28543,28102,25093,36803,35662,21622,25092,36803,36803,33075,1489, 43352,899, 39467,39288,40184,39018,36168, 12786,42662,6984, 36802,13432,36802,33288,20887,20093,17481,17098,22508,37526,12527,40258,23836,28101,36802,20304,39756,44393,44005,44393,17059,44393,40023,40022, 41413,45475,45475,44393,9019, 495, 36168,552, 40022,38240,38142,40813,39371,40866,39234,39967,41516,40579,40579,40086,45474,19479,21621,35661,31182,37525,36802, 13781,37525,42294,7968, 23835,10340,35661,44892,38555,41073,15255,37724,43352,43351,40528,44891,45474,42294,43105,41726,45474,5589, 15858,33515,7411, 32881,9590, 13197,41413,39939,42871,42662,35661,42661,37525,40432,45474,31181,25621,44005,44004,20886,36801,22350,19818,33774,41987,44392,45473,39160,42871,37525,42870,41324, 43652,44004,44392,141, 2862, 38537,38525,31796,3517, 4475, 854, 2944, 38684,42870,5192, 38968,39797,41156,43652,38829,38447,45473,44392,38849,44004,39273,43652, 41726,44392,41413,37524,37832,38115,41987,41855,44891,38420,41726,41000,45473,45473,40866,41855,38393,42661,43651,39992,42870,41987,43351,23834,45472,40933,44891, 42134,36801,3688, 42294,40813,3384, 36801,42661,35190,27689,34776,38536,40147,42474,40384,38465,39127,44891,43651,43351,43651,42870,41324,42661,38640,34405,26220, 38625,40340,42660,41617,25091,41516,45472,45472,32517,38284,41855,44004,39779,39621,43104,10730,42293,41854,41725,40085,45472,38413,39138,43651,44003,43650,44003, 34405,19557,36168,24839,44391,35190,34405,24330,13821,35190,32516,24329,36801,28542,33288,13260,4122, 44391,7138, 14758,32515,6604, 38604,40056,43351,38902,39037, 43350,39097,41515,43104,43104,35661,41854,6834, 38254,39127,44391,40933,43350,39658,9601, 38590,38992,3608, 42133,44003,40743,8049, 6114, 34076,25620,17131,27299, 32514,37524,36800,28100,36800,36167,16878,15994,36800,34405,41725,4839, 37524,37524,43650,40579,36800,27298,27297,7174, 39004,6118, 8310, 38485,7805, 7524, 8858, 23833,12309,17165,4840, 44391,7357, 8434, 23620,21124,33515,8301, 10876,32513,36799,34076,23215,37523,10291,28099,43650,9246, 10691,34776,8516, 10661,10875,9018, 33515,7225, 9143, 8538, 7441, 7619, 43650,34776,13693,36799,45471,35189,45471,45471,43104,41241,44390,609, 2670, 34775,34076,38844,38829,42474,39916,45471,44890, 37523,37523,28098,37523,7776, 42660,38607,5268, 41617,45470,33774,16945,36799,25619,27296,33288,36799,41854,43649,42869,36167,7515, 21123,7614, 33074,11035,34075, 35660,6708, 14624,41413,42293,41725,41240,20536,29444,14503,36167,42133,44390,37976,38181,44003,40116,39312,28097,31180,44002,36167,34404,29939,36166,36166,34775, 36798,12910,13747,37522,997, 40116,41412,41000,22661,34075,33514,37522,41617,22349,34404,34775,36798,33287,19252,27295,34404,33074,18289,4423, 41515,34075,16129, 24566,20535,45470,44390,8883, 44390,36798,34404,43350,42869,45470,21122,37522,6218, 43649,13133,34075,37820,36798,35189,37522,33074,8702, 35660,27294,14799,36166, 15729,34775,13056,12419,40258,24328,23619,37521,35189,41412,36166,13573,34403,5336, 45470,26570,43350,19729,26219,17733,29938,4587, 13655,36797,34774,20195,38668, 40932,33773,38811,39151,39440,41515,20757,31179,18571,34403,8593, 35189,37521,30548,32881,37521,10097,38825,38762,44890,41073,39036,45469,18502,35188,35660,44890, 42133,38191,41515,7641, 38722,40578,36797,11773,34774,8841, 10155,8656, 8546, 36797,14197,22348,8731, 33074,27688,23032,12577,35660,21770,16128,24327,8273, 43349, 24838,39252,40258,41000,44389,44890,35188,37521,44889,36165,44389,12325,36797,40384,43649,9729, 34403,35188,32880,36165,41323,34774,40999,41155,27687,37520,3479, 40085,1884, 11079,7593, 38335,42473,8266, 10690,9737, 40680,23618,13900,35188,26935,40932,39127,41987,38665,22846,33073,43649,43648,40481,43103,15857,42293,10395, 20092,43349,39734,33773,44889,31795,11851,8651, 18071,11850,23617,6116, 25926,33073,16522,13324,37520,43648,43103,42473,17480,33514,11220,11688,7220, 43648,8678, 10044,7827, 42660,8563, 11772,9474, 26218,40056,9827, 11078,8462, 42133,40866,44889,43648,44889,11771,20655,3847, 37520,27686,40743,3280, 38135,38797,44002,44002, 43647,24837,11404,8016, 44888,28541,34774,11473,44002,33287,33514,8281, 43103,39694,40528,42473,40481,42473,41616,43349,39820,41986,42660,39119,42293,9781, 7422, 44888,7139, 45469,9015, 8945, 36165,18401,37520,24072,12001,33287,44389,41854,44001,35187,41412,45469,40432,26569,8885, 39300,20417,9816, 10660,10096,10154,8198, 43647,10128,11927,37703,34773,41986,37831,40528,39938,42132,20756,32512,17513,37519,38087,42869,38295,43103,44888,38014,37709,41514,43647,36165,44001,33773,3324, 7382, 7851, 35187,42132,14441,20755,5676, 39967,21237,25618,15226,33773,18124,15913,23423,44888,636, 25925,39151,41412,37724,37765,45469,38028,38308,45468,37764, 38053,37711,41986,37865,39715,37978,29443,31178,9320, 37519,35187,11687,43349,41072,39778,41411,37855,44389,40999,43647,38819,9572, 37829,40184,6474, 40227,26217, 44388,43102,11770,33514,45468,45468,34773,5047, 39541,42132,40743,39674,42869,9736, 9616, 11219,17369,25343,31794,23031,34773,17368,27685,36796,34773,20416,22347, 29937,12701,33772,35187,6336, 40115,43102,6117, 42659,44887,38070,45468,37736,39966,40383,39118,40578,42868,38407,38933,43348,38575,38870,39820,42659,39602,38085, 40481,39602,44388,45467,2083, 6479, 43348,41323,41072,44001,41853,44001,42292,35186,40258,39938,37928,45467,37818,42659,38715,39715,40932,39756,44000,43348,5510, 45467,39674,44000,38169,39714,42868,40578,3940, 37926,38501,41986,38015,40742,41411,40527,44000,43102,42132,38671,40056,44887,43348,40022,43646,44887,41853,44000, 37739,39396,38295,43347,42659,41514,44887,39159,38575,42658,38731,40999,42658,38402,39820,43102,37519,40999,40932,43999,45467,38305,41616,39300,39938,41411,43999, 43999,41853,5884, 45466,37519,42292,40299,38236,39714,40227,44886,45466,38819,41853,41985,39966,41852,43999,40813,45466,45466,44388,44388,10804,43998,40431,41985, 43101,38080,38560,38474,41985,40629,44886,39893,38517,38382,41985,41852,39360,36796,21769,28984,32511,2616, 41072,40680,43998,44886,40431,42292,2028, 39370,41072, 39557,44886,40146,37822,43101,43347,40812,38572,38014,43646,37965,38881,43101,41725,44885,39540,41155,39347,43998,40628,43998,44885,38579,38912,38115,37903,2699, 38787,38949,38463,39011,41724,41852,40340,38992,38156,38955,38895,40742,41514,39714,41852,37806,38276,38532,38368,38050,39334,42131,43646,41240,44885,38515,39068, 39865,39915,39640,42131,40865,40431,41411,40812,42131,38661,41323,360, 42472,39300,38348,40339,39334,39088,41410,44387,43646,5785, 44387,44885,43997,38465,38434, 41616,44387,39527,41410,38818,933, 6221, 39440,42292,39452,38393,41155,42472,42472,43645,41514,42131,43997,38961,44884,38961,39186,40578,41616,5511, 39036,42658, 39300,43347,39053,41323,39621,41984,44387,45465,43101,39865,43347,40480,39966,40998,43997,44386,41155,41724,45465,45465,39440,44884,37913,39714,42130,43100,39797, 44386,40299,43997,43996,41851,40184,44386,42472,41240,38460,39966,41240,43996,41239,42130,475, 38114,42291,45465,43346,42291,41851,42868,17512,43996,43645,40577, 44884,39210,40680,39370,38604,44386,43645,41724,40527,40527,43645,41239,41851,43996,31177,37776,40339,43100,38849,40628,42868,19478,36164,35659,28096,33513,7141, 19251,37518,37518,36164,8024, 13663,33513,8048, 7834, 20415,33513,2213, 5245, 36796,31176,37518,35659,37518,22660,15300,45464,7677, 33287,26216,41724,9448, 44385, 44884,43346,22845,38749,42867,42471,43346,42130,41410,19817,35186,24836,35659,38087,43100,41615,37747,38417,44883,41154,42658,31175,26215,38157,44883,43995,43995, 32510,2090, 41984,40931,44385,39621,38395,38306,33073,28095,34074,37761,34772,41410,41984,43995,44883,38590,43644,41239,44385,39333,38618,38055,42867,42471,39288, 30547,33513,38992,39893,36164,28983,40742,39601,44883,10834,34074,13662,29936,24326,36164,28982,34772,3402, 41071,45464,25342,34772,36163,35186,35659,40931,36163, 21768,36163,35658,15320,33286,36796,31174,42130,37517,34403,21355,36163,38167,38079,38811,43995,27293,35658,36162,39068,42129,38252,39915,14939,38729,38802,43994, 41239,40679,37834,43994,30546,29442,27292,14725,34772,13882,16581,33286,42129,43994,32880,41238,32509,24071,20654,44882,34402,40577,4250, 31793,14852,38395,20996, 39938,35658,43644,44882,45464,26934,39126,38233,43994,44882,14966,43346,8603, 40742,5160, 38648,41409,44385,43993,32508,15111,37517,44882,38013,38861,41513,41513, 34402,34771,38490,43100,34771,35186,27684,34771,18962,83, 3825, 16726,21121,1762, 32507,23214,34074,34074,17441,19329,36162,14672,33772,34073,37517,36162,37517, 19083,33073,36795,37826,45464,37516,41984,38235,23832,44384,22346,37969,23422,10083,43993,4245, 42471,38149,33772,38185,40865,43345,43993,35185,34402,45463,34073, 36162,36161,34771,26933,36161,43345,29441,36795,38729,42867,30545,42867,38015,43345,42129,43099,38091,37726,38186,40931,40741,38032,41322,35185,5847, 43644,45463, 42657,16339,42291,43644,41513,42129,38072,4638, 32506,26932,18825,42657,1076, 39346,36795,37516,28981,19029,8478, 16172,14579,37516,37516,30544,9059, 44881,38434, 26931,17278,43993,4105, 2551, 40741,41238,40299,38045,43099,404, 45463,35185,37515,37515,43099,44881,32505,33072,37515,34770,33286,10339,42291,16983,42866,39915, 39036,44384,43643,39011,45463,28540,43345,42128,25924,36161,28980,17013,15130,29935,17779,29934,35185,34073,16644,22659,33772,17683,44384,35658,33072,31173,12679, 21767,38150,35184,4060, 12279,41238,39496,43099,44881,41615,45462,39797,34402,31792,33072,22507,33286,36161,25923,18177,14095,21908,35657,34073,29933,42290,22658, 32880,13175,41983,9652, 33072,34770,36160,25090,13831,35657,28094,12534,27291,23421,37515,21620,35657,26930,14533,23831,12917,22345,33512,34072,44881,14377,37514, 36160,34770,4061, 1817, 1964, 41071,38991,41615,19816,41615,6663, 40628,34770,31172,18338,20414,25922,32880,43992,39385,43344,38269,40998,41983,40085,43344,36160, 36160,38075,41614,41983,38437,39512,36795,35657,22344,34072,18621,35656,36159,37813,3827, 39915,42128,1047, 39220,44384,35184,38807,44383,42866,43992,43344,1740, 1095, 36159,38101,42471,41409,41513,41851,34401,37722,37916,40085,41238,44880,43643,40931,38501,40226,43643,41237,43643,43098,9152, 11403,6172, 1773, 41237,42657, 42470,41409,42470,37514,4304, 34072,36794,35656,37514,35184,36794,40383,45462,21354,24070,19166,37514,34072,35656,20303,27290,31171,35656,33771,36159,13631,32504, 36794,34401,12628,27683,37796,39452,40298,44383,45462,33071,44383,26568,37513,36794,36793,33285,35184,32879,13715,10423,37513,7902, 8073, 37513,36793,28539,40115, 22657,44383,17832,42470,32503,31170,35655,34401,11926,37835,41237,44382,44880,41237,37879,5744, 5018, 38991,42657,38568,38549,38476,42128,41723,34071,27682,33285, 16108,43344,33285,9511, 42290,36159,39440,41154,43642,8697, 8445, 37513,33512,37512,43343,31791,33071,17398,33071,35655,36793,40628,5161, 4880, 41723,35183,21120, 32502,37925,39228,41723,40679,4818, 26929,27681,38492,45462,37512,41850,39053,16643,44880,40577,2609, 41512,45461,6175, 19556,18759,42290,38991,40741,39439,40741, 34071,36793,36792,34401,31790,2573, 23420,42656,37512,36158,35183,45461,44880,18501,36792,36158,37512,29440,25921,19250,38128,43343,43343,42866,35183,35183,36792, 7709, 40577,37511,26214,20885,44879,45461,31789,38441,16425,37511,40339,38034,40431,41236,43992,44879,38368,41983,39323,33071,37711,43992,19165,5747, 38895,37766, 40998,37814,37829,24835,26567,36792,15429,32501,39992,43991,39439,36791,37852,37511,26928,36158,33070,26927,29439,37511,36158,20534,29932,37510,34400,37510,33771, 35182,44382,43642,37510,33512,36157,32500,34071,31169,18961,33771,27289,34769,20091,23030,37510,39694,29931,35182,29930,25089,34071,32879,31788,21907,44382,13742, 37509,35655,13259,21236,36791,28979,25341,9076, 9864, 9970, 8419, 41614,13203,11124,9290, 9655, 39346,6070, 9553, 7470, 10021,9636, 7380, 4251, 23029,16551,31168, 37859,38250,41850,42290,43991,38268,5244, 16804,20533,44382,43343,24565,38089,43098,15776,40998,26566,37509,30543,38991,42656,42656,40865,38744,30542,37792,25617, 31167,34769,23419,30541,19728,8047, 15993,34769,20754,29929,37509,43642,1462, 44879,2289, 37509,36157,39210,43642,40115,44381,41236,36791,41723,43342,43098,38415, 43098,39694,35182,40865,31787,39820,143, 4305, 45461,20090,43991,19727,37508,33771,45460,1253, 26926,28538,31166,39914,41071,16838,30540,14214,31786,15369,36791, 33070,34769,37508,42656,4247, 20532,28537,26213,28978,37508,45460,13619,22844,37508,4352, 16197,32879,35182,27288,34768,7832, 35655,17544,36790,33070,33070,36790, 37507,21766,37507,35181,4573, 34400,4018, 12101,33770,34400,37507,17931,37507,33770,43991,7998, 11849,8667, 34070,27287,10422,11283,14376,12162,7362, 9840, 35181, 36790,34400,43641,37506,41512,33285,40339,42866,33770,16837,25088,14494,116, 3043, 14910,40298,35654,19726,13308,20753,12708,33770,14671,26565,26925,26924,1305, 43097,19913,35181,19555,40480,24564,26923,27680,28536,20302,33512,34399,44879,13154,34768,16253,28093,37506,37506,15264,36790,37506,30539,40997,39620,42655,43990, 41071,45460,45460,30538,34070,40226,36157,27679,20194,39263,24563,2980, 42865,36789,38665,42655,39370,39693,41070,45459,44878,39992,38755,40022,41409,31785,816, 25920,17012,34768,36157,22656,33511,21765,39778,31165,3225, 24834,40055,41850,22053,32499,40298,45459,44878,34399,25616,43641,36156,38188,40864,42470,43641,19082, 39467,42128,2100, 1861, 6933, 1617, 6549, 3568, 3506, 6287, 43097,34399,23616,34399,43342,38818,44878,38679,43097,33769,39412,44878,45459,39159,45459,39892,41070, 45458,36789,33769,37505,33284,28092,33511,3872, 34398,32498,1077, 28091,31784,40740,37505,29438,34070,3826, 40679,43641,42865,36156,4246, 42289,42655,40430,7658, 11282,11769,4574, 6174, 44381,43640,17058,44381,35654,34398,28090,16944,45458,34070,12959,41322,26564,15756,17057,35181,12141,36789,44877,39557,43990,41722,39496, 41614,45458,35180,33769,37505,15203,25919,37505,39937,3745, 15685,22343,44877,18460,37973,39865,38178,43990,39733,42289,37904,3438, 29437,33511,41850,42127,33284, 26212,37783,41154,1510, 45458,39439,39844,37878,38475,40740,43640,33769,24562,30537,36156,28535,12375,33284,44877,40480,37791,37842,43990,38283,44877,38718,36156, 7078, 36789,39992,16365,18500,37504,37948,43342,38453,23028,38083,41322,33511,8083, 43097,32879,37504,38693,35180,25918,36788,33768,44876,44381,32497,30536,33069, 31164,22843,44876,37952,38069,27286,31783,43342,45457,36155,36788,44380,38382,35654,42655,28534,29436,16982,39323,41849,35654,37979,43341,40997,39511,40338,42289, 41154,32878,2255, 3124, 9309, 37504,35180,31782,35653,25917,2043, 42469,40997,39185,1578, 42654,29435,34768,44876,37974,43341,39797,42865,40930,45457,40864,41070, 44380,37786,38787,29434,29433,4059, 41153,40021,38296,39511,45457,43640,39865,12787,24561,14921,40627,32496,43989,24325,37706,4817, 41408,42289,45457,37932,39844, 17930,15005,35653,37922,37712,34069,43096,35180,33069,44876,41408,39466,36788,41849,40480,45456,38335,44875,34069,37504,1099, 43640,44380,40930,2769, 43341,40812, 39171,43096,45456,39713,43341,43340,38451,38049,39864,33768,37503,37853,37858,39991,38645,34767,42127,33510,13073,24324,20413,21353,34767,19477,35179,20193,24560, 35179,36788,28977,5652, 19476,28089,12601,13339,15540,19554,32495,30535,21906,13690,36787,25340,33768,32494,42469,34767,44875,32878,36155,37503,9910, 34398,15660, 34398,34397,20884,30534,36155,26211,28088,33284,29432,25916,279, 40383,6931, 41512,39819,884, 42654,36155,45456,4694, 45456,42127,41849,37928,6551, 4766, 43096, 43340,42469,4508, 41982,2063, 2456, 6550, 42288,44380,546, 45455,39574,6788, 38955,42469,40226,45455,38632,44379,34069,20883,43989,4507, 44379,40864,38658,38496, 28533,38158,42127,34767,45455,34069,39674,40627,33510,43989,37503,37710,40383,39557,41982,43639,20089,41614,33768,28976,33283,34766,12444,30533,19912,34068,255, 22842,37503,32878,4693, 43639,24323,33767,6419, 40864,22841,12062,6072, 39385,42865,40226,2801, 4765, 3966, 45455,4354, 165, 3373, 15202,43989,4353, 44875,6789, 5087, 14468,19640,33283,45454,38774,43096,5650, 42654,44379,1844, 37502,39733,37502,13302,23027,15822,34766,34397,1862, 38311,39991,4637, 42654,38740,42864,29928, 15472,41722,17881,33510,35653,12329,40863,42288,22195,25087,32878,18891,34068,28087,36787,12731,15319,30532,40679,17201,44875,36787,37757,43988,36154,44874,41322, 44379,40678,43988,40740,38091,42126,40430,41070,38594,44378,41236,41849,24322,43988,44874,42864,37502,43639,29431,17929,37502,38320,15626,41236,44874,31781,41408, 39601,43095,37501,13752,34766,44874,42288,36154,40084,41069,19408,41982,40084,40930,36154,35653,43639,38713,42126,34397,45454,1919, 39778,2907, 35652,32493,40930, 41153,12907,35652,23213,34397,14693,25339,6662, 15992,40929,4454, 45454,44378,23830,39892,14866,20995,31163,39892,42864,45454,40382,41722,39496,42468,40740,43988, 540, 2686, 40527,41848,45453,44873,40055,3824, 39082,39244,39024,41321,6552, 39778,40146,38146,43095,34766,37501,43340,37501,6071, 31780,43638,44873,41153,17732, 41613,41321,39177,38474,43987,38912,45453,39864,44873,44873,44872,23615,42468,40257,40678,33069,10319,28532,39112,40929,40739,41982,44872,39112,40115,40055,45453, 39777,28531,36787,45453,45452,39177,44378,30531,39693,33283,36154,15415,23026,44872,39991,45452,40576,45452,39585,43095,36153,33767,39892,33767,36786,25086,26563, 18233,33767,38844,40055,17880,35179,34068,33283,36786,30530,34396,19028,36153,38740,45452,22052,22051,26562,32492,37501,12188,23418,28975,43638,15684,34396,35652, 33510,44378,35652,32877,36786,35179,36153,35651,38183,20301,20994,36153,42864,34068,11402,2574, 12219,33766,32491,43095,30529,36152,17638,38313,34765,31162,5557, 41321,3544, 43987,35178,16281,44377,41069,41848,42653,20653,34396,2254, 40430,40627,38587,37930,41613,39185,42863,40678,41069,29430,41613,44872,22840,34765,38793, 42653,43340,29927,17879,37500,13606,43638,45451,38341,6932, 6073, 38955,22194,809, 41981,31161,39177,22342,40257,39036,41613,43987,40812,38560,43339,35178,34067, 33509,43638,10196,38834,41981,42653,43339,38536,39937,39573,45451,44377,44377,42468,28974,32490,13649,39540,44377,39658,41981,33766,37865,43339,44871,41981,42863, 38674,44871,40863,39843,43339,44871,44376,2482, 42126,9319, 8487, 40526,10318,33282,34067,8248, 10113,7972, 9757, 10195,43987,8026, 12040,36152,10803,33282,15642, 36786,11615,3272, 243, 45451,3543, 35178,43986,10194,9055, 20412,41512,39556,44871,11614,18070,39312,8831, 42288,44376,35651,2744, 43986,39819,41408,45451,34396, 15957,38501,37500,203, 4510, 4306, 44376,12317,43338,39088,39053,40257,9419, 7302, 6420, 36785,26922,44376,20192,41612,6935, 45450,43986,440, 43637,33509,24069, 38949,39312,29429,39693,19249,41069,39937,40114,43094,40678,41848,41153,45450,32877,28086,36785,38715,39311,43637,41511,41068,39991,45450,43986,41407,43985,36785, 42863,43637,1807, 4509, 295, 6548, 4104, 37500,40811,38001,12418,20531,25915,3010, 45450,39170,42287,42468,32489,24559,34067,44870,25914,40677,45449,36785,37955, 38373,42287,38901,44870,45449,42467,36152,36152,34067,24321,34066,28085,43338,32877,15912,18069,37500,37499,16015,42467,41848,41722,43637,14419,24833,36784,34765, 37499,37499,43094,42653,40225,42287,42652,34765,45449,32877,34764,28530,38625,39864,24832,39864,41721,39511,41321,12563,42287,21483,42863,35651,16154,33509,25085, 27285,41980,34395,37955,43985,36151,28084,19911,33069,44375,43338,43636,23417,34395,28529,37843,38025,32488,44375,40084,44375,37983,38783,41847,41320,22050,34395, 15318,14570,33068,32487,15937,34066,34395,25913,44375,36151,38449,9728, 32876,44374,43985,43985,43984,41612,41511,23025,41068,34394,14954,43636,31160,36151,36784, 35651,14560,23212,14066,34066,17976,36784,20411,33282,33068,34394,12958,19164,22655,42652,41068,908, 37813,39965,39273,37499,35650,37898,40811,38360,44374,41152, 38572,38374,41511,41235,39863,38645,24558,25912,40863,31779,37931,42126,38771,10, 44374,4186, 13646,43094,42125,42652,39843,4187, 41612,26921,42125,43984,41721, 39693,37498,43338,44870,39145,45449,41320,42652,36784,45448,44870,32876,20652,27678,44374,44373,12804,33766,38520,38933,43094,42651,20003,28973,23024,37901,38039, 40929,42467,38274,41980,20651,38668,31159,12052,43636,16681,37498,2800, 17097,43636,21482,17479,44869,42125,43635,41721,43635,44869,44373,30528,42286,42862,42862, 19725,38695,38365,41721,40811,12797,42651,39863,39272,38578,5558, 33509,43984,40997,40739,41612,43984,6288, 33766,537, 18288,44373,41320,15683,45448,41980,204, 43635,44869,40084,43093,44869,2850, 39004,39556,39452,44373,45448,39863,37747,42286,5961, 39176,43983,34764,43337,38253,24557,41068,29428,37845,38485,38440,40739, 38843,43983,37839,38019,36151,36150,38610,43093,34764,33068,41152,36783,40021,5962, 37993,38496,41847,41720,39426,39891,14219,45448,34764,5848, 39620,45447,33068, 5745, 39170,36150,38166,39819,29926,40114,39914,35650,35650,38421,1907, 2021, 42467,44868,45447,37498,34763,35650,32486,15503,34394,45447,45447,33282,34066,35178, 35177,7494, 38005,39639,39202,31158,35177,44868,5746, 40739,18400,36783,29925,27284,31778,42651,34394,24831,24830,15895,14965,14349,14638,14408,15400,26210,38306, 39733,42125,43983,32485,14272,40811,43093,44372,14446,34763,28972,42286,29924,36783,37498,14160,16228,42286,4764, 18687,42466,4249, 38740,38572,19407,38071,45446, 7079, 40479,41847,37953,3967, 5019, 37897,40929,40298,41407,33508,37497,23023,20752,36783,35649,24556,34065,36782,14004,33765,35649,23022,1240, 44868,41511,23829, 12044,45446,5651, 32484,34763,34763,32876,36782,20300,24829,24555,32876,38137,38488,28528,41980,33067,20882,34065,34393,24554,37734,38471,41979,39311,40738,43337, 40479,13158,31157,43983,42466,35649,37775,13416,40297,42651,38319,40863,38095,44868,24068,25338,14138,24553,26920,17200,23828,36782,30527,40928,32875,12181,26919, 16153,37497,12128,43093,37497,42862,6286, 41979,44867,1561, 26561,18570,36150,20751,3403, 4402, 14432,21235,39965,6934, 6173, 37497,40021,42862,39573,39819,41979, 34393,20299,18287,32483,38578,37496,40054,39639,35649,35648,23416,40627,43337,43635,25615,23021,39818,44867,40928,23614,44372,33765,21619,24828,41720,20750,40257, 34393,21618,14137,24067,36782,38224,38829,37496,45446,40146,44372,39891,20002,8960, 43092,42861,35648,41611,693, 11174,8188, 4248, 9052, 44867,33508,33281,8078, 37496,32875,11334,33067,40928,45446,33508,10802,10729,8632, 45445,7264, 8657, 7281, 36781,19639,36150,12689,31777,14785,10874,40114,24552,36149,37496,7172, 38901, 1218, 16836,16835,34762,33765,36149,39076,45445,33067,7525, 39333,5653, 35648,36149,12139,44372,38063,8126, 20650,35177,12790,33508,2099, 39990,42650,28083,34065, 28527,11848,28526,35177,40184,43092,35648,14418,14909,37495,17928,33765,36781,33507,32482,30526,27677,39030,31776,35647,32875,24551,13126,41611,20881,37837,38876, 24320,39097,43982,42861,39017,45445,41847,26560,37495,36781,34065,37495,40626,38390,43982,43634,43092,41846,18620,34393,31156,18286,21764,42285,33764,37495,34762, 17637,37494,15659,12174,44867,41846,1024, 45445,38141,38744,38237,42124,43634,2044, 43634,740, 36149,28971,43092,36148,35647,17975,42124,1806, 43982,7212, 41510, 8558, 11548,7412, 40996,40996,263, 43337,5743, 37494,34762,30525,7744, 10317,7268, 41979,10486,15728,28082,18824,43982,37494,17831,1023, 10371,37494,40225,45444, 7956, 44866,7414, 11401,20749,35647,15727,343, 31155,41978,43634,23613,33067,37493,37493,15488,10316,15349,57, 1199, 42466,2348, 4106, 5159, 37493,40297,26209, 40297,41067,45444,16107,44371,45444,36148,22654,39201,12072,34064,30524,568, 2852, 20880,41846,15991,3305, 35176,522, 44371,3569, 42285,6790, 38080,38885,44866, 40083,44371,38217,36781,33507,19815,24827,32481,36780,9024, 37493,33764,39657,41978,36148,37492,36780,30523,18399,14953,37492,35647,34762,27676,12352,6561, 44866, 41235,42650,5315, 22049,33066,37492,37492,31154,29427,39965,42861,42124,39219,39384,39733,43336,25337,36780,42285,33764,37491,43633,26559,37936,38908,42466,43091, 44371,44866,40114,38639,39914,43091,22341,28970,6176, 25614,40626,44865,19163,31153,39288,44865,35646,34064,23415,33507,41235,15539,34392,21617,13050,32480,34761, 35646,35646,33507,25084,43336,15187,32875,15502,19081,36780,40083,43981,24550,39796,37491,11218,434, 41407,40430,37491,28081,38482,23612,33764,38460,8762, 34761, 35646,25911,32874,34761,28525,23020,34761,34064,12843,37491,18285,30522,37490,31152,18398,35645,37732,38365,42861,42124,45444,38126,37744,38059,43981,40225,22048, 35176,15538,38338,23611,17927,35645,34760,35645,35176,36148,45443,39511,40576,1584, 38999,38226,38665,43633,40382,42465,45443,40996,27283,34392,34064,27282,44370, 41611,33763,5164, 35645,45443,17594,34392,32479,6423, 18284,17778,18890,37490,33281,2410, 35176,25613,27675,33763,36147,3071, 21119,21481,37490,17327,20879,26918, 34063,45443,16521,7885, 29426,36779,28969,40297,31775,33066,15682,45442,43091,43336,37490,37489,41235,41611,36779,2430, 4512, 36779,18960,25910,36147,35644,1025, 45442,6792, 33506,1706, 5562, 654, 3781, 4695, 36779,37489,33763,42285,41610,37694,25909,45442,42650,44370,20191,36147,43633,43981,37489,35175,37489,29425,44370, 37790,2091, 40576,1419, 45442,41152,40738,45441,4405, 12596,41846,41407,40928,1667, 37488,43336,45441,39713,43633,43335,43632,39159,38256,33506,36147,43981,19910, 38231,39843,42123,44865,42860,39692,5021, 32874,37488,43335,36778,38625,45441,38749,39299,41610,33763,33506,41234,45441,12717,20748,12726,15681,40225,21616,36778, 34760,35644,37488,6667, 1966, 7086, 32874,14724,44865,2826, 32, 44864,40021,3930, 6078, 4882, 916, 44370,563, 45440,65, 43091,5089, 1062, 1734, 1873, 4576, 2267, 37941,223, 3545, 810, 24, 3632, 5400, 234, 4355, 4253, 3126, 3012, 2982, 45440,44864,26, 1831, 1168, 4641, 37488,33281,44369,43335,17593,44864,45440, 34392,38487,40677,45440,28968,41320,45439,12647,25612,29923,35644,35644,37487,36778,36778,13704,2129, 37487,34391,45439,38885,40738,29922,43980,33762,41319,28524, 41152,36146,17011,34063,35643,36146,22047,37487,20530,34391,27281,27280,42650,43090,3011, 29921,34760,36777,37487,31151,19328,15583,45439,34063,43980,39024,39540, 39584,40738,43632,17731,20878,23610,4188, 17056,16171,17926,37795,42465,30521,34760,5481, 28967,26558,30520,33281,12168,15383,40526,23827,32874,33506,17730,3374, 36777,21615,38190,14992,26917,31150,24549,37486,12710,44864,19553,14143,36777,37486,36777,41234,44863,39863,42860,44863,44369,10659,40626,28966,31774,20529,27279, 36776,38503,40338,39425,39412,38265,39937,45439,41845,41610,40576,22193,15487,29920,39138,39104,45438,42123,38691,38026,41845,40020,23211,37486,1945, 17543,44369, 44863,6074, 40927,13189,19327,19080,4307, 45438,36776,41510,33066,39657,45438,39510,37774,45438,6293, 31773,4820, 1108, 6557, 33280,15331,25908,5479, 33762,34063, 33066,36776,22192,33505,35175,34391,27278,43632,34759,38349,43980,34391,16089,31772,21480,14654,39756,33762,36776,27674,37486,35175,43632,41845,33065,38336,35175, 14812,34759,24548,45437,22191,41151,36146,20649,16943,36775,37982,44369,40146,40927,33505,44368,35643,36146,39346,32478,33505,15706,39097,42649,23414,43090,33762, 43980,2006, 41234,43979,21234,38691,6666, 17542,40054,35643,40338,36145,26208,4404, 20648,40429,42860,42465,28523,17511,45437,42860,37808,40626,39796,45437,40996, 6425, 3156, 6424, 7082, 7085, 37485,43631,22046,24547,36775,33065,6936, 43631,44863,40677,5967, 6075, 44368,33280,33761,36775,37485,20993,16196,4952, 23413,37485, 42465,38860,42464,39439,42464,33761,16520,33761,36145,14818,23412,44862,27673,34390,33280,36775,7349, 44368,6077, 8739, 10127,4143, 9384, 8769, 42123,32873,34390, 21479,34390,33505,36145,43979,43979,45437,35643,24826,20992,36774,23411,45436,33761,14458,14938,32477,3828, 41610,33065,34390,33504,34759,37485,21905,16942,34759, 36774,36774,35642,34389,7081, 26207,23210,33504,30519,42123,43631,36774,35174,14314,21478,41151,13546,35174,33760,3631, 2145, 41978,19909,19814,31149,35642,13487, 27277,28080,12936,44862,28079,29919,35174,36145,34062,36773,26206,45436,37484,8296, 42859,33504,17729,22839,38293,39138,17777,12438,15842,36773,43090,43979,39584, 34062,36773,24825,42859,23826,17164,28078,14159,41845,35174,28965,11547,42859,40429,45436,41844,18758,33065,35642,5314, 24319,43631,33760,2851, 15254,35642,37484, 24066,31148,21477,41609,996, 25611,33280,32873,15428,17163,35641,26916,33504,35641,8797, 43090,34062,28522,25083,35641,7738, 25907,3044, 19552,22506,36144,26205, 43630,37484,28077,19406,35641,45436,5966, 37484,35640,34758,11400,31147,13192,35640,24546,35173,16227,35640,26915,12437,36144,16642,30518,14386,13720,2770, 36773, 34062,19326,44368,26204,18499,12509,36144,36772,34061,36144,43978,38547,44367,28521,34389,41510,24318,34061,34389,25906,37483,20088,36143,26557,37483,21763,33503, 45435,34389,20298,30517,43978,19325,36772,13601,32476,39796,39673,23825,42859,32873,12681,42464,43335,24317,18232,35173,33760,37483,618, 43978,35173,39620,37483, 35640,33760,26914,35639,12850,44367,14623,15110,34388,6555, 25082,33759,23209,23824,17199,34061,37482,34758,34758,36772,32475,19079,21352,1006, 12654,43334,41609, 4511, 43334,24545,38653,14546,25610,16306,37785,39126,6668, 3303, 5247, 36772,6291, 45435,35639,33279,37931,41319,31771,14174,41609,25905,36771,4640, 21351,31146, 34758,23019,16338,22653,41319,33279,44862,43978,35639,151, 2457, 24544,33759,43977,31145,40429,39540,44367,43089,36771,14237,6793, 21762,35639,973, 41978,35173, 1965, 2661, 43334,42649,37482,33503,34388,32873,33503,40677,34061,45435,45435,42122,39713,2981, 3048, 40296,43977,33759,45434,5968, 22505,13878,23823,36143,14094, 42122,26556,33759,35172,35638,23018,34757,33503,34388,13876,33502,37482,37482,14653,45434,20297,39346,39891,41319,17130,41720,33502,37481,39936,36143,33279,37481, 38096,45434,32872,45434,36771,15911,43977,44862,33758,38065,28964,35638,14974,30516,43630,42464,27276,23822,21350,11972,43977,35638,42463,36771,18959,41510,21476, 23410,988, 36770,39201,43089,43089,27275,43334,42463,39965,40054,23017,14325,36770,42122,12108,43976,37481,22504,33758,37481,5399, 28520,20747,331, 25336,37480, 43976,33064,24316,43976,36143,33064,43630,44367,3047, 42463,34388,29918,16424,18123,25904,35638,37480,35172,3570, 34757,45433,7587, 38890,28076,27274,31770,31769, 20746,28519,16641,31768,40479,42122,36142,15471,34757,31144,25609,12273,21349,9571, 10315,28518,21761,36770,36770,42858,35637,35637,20001,35637,37480,26913,29424, 21233,37480,37479,20087,34757,27672,32474,20745,8072, 18337,12170,33502,44861,17592,17096,29423,31767,36769,35637,28963,15263,12464,42284,36769,16423,35172,32872, 33502,20410,43333,7467, 35636,43976,37479,31766,34756,32872,22838,35636,43333,28517,40296,45433,7796, 36142,32473,29422,21760,17636,19908,31765,18336,14937,33758, 14003,22837,7167, 40113,12163,26203,20528,45433,31143,35172,25608,33501,42284,41609,35171,36769,36769,30515,25903,40382,17239,15299,13960,45433,44861,4252, 34387, 39138,39573,17162,18889,44861,19813,36768,42284,35636,15705,20086,41977,23821,33279,42858,26912,32872,33278,44366,19724,18888,34756,35171,8082, 43089,45432,36768, 33064,38133,42649,44366,40862,42463,32871,24065,12411,108, 2176, 5246, 15537,33278,38345,38639,38709,41318,21118,35636,33278,34756,33064,33278,14022,17728,44366, 36768,1484, 3154, 4403, 230, 4883, 35171,5397, 23016,24824,18887,33277,18335,24064,31142,36768,34756,31141,36767,36767,18231,18569,31140,37479,35635,36767,20296, 43088,32472,39412,28962,20295,45432,35635,18397,35635,36142,32471,32871,30514,28075,42649,16044,33758,31764,36767,22652,32871,23015,43088,29421,33277,16680,34755, 34387,30513,13746,33757,26911,34387,24543,22190,20409,40296,37761,42121,36766,29420,36766,34387,36142,7714, 34755,35171,42462,34755,42462,3046, 42648,37479,34386, 33063,36141,33063,12634,6289, 43333,20408,22503,9345, 29419,45432,24823,35635,9107, 8508, 10760,32871,31139,16640,8081, 27671,2802, 25081,9851, 10959,35170,44861, 16364,39756,16226,18886,35634,22651,29917,40429,39657,44366,42648,43630,41509,41318,45432,41509,44365,43629,6665, 35634,34755,27273,38433,44860,42462,16088,25902, 31138,10728,35634,40995,44860,14265,37478,34386,35170,36766,33063,24063,19027,18568,27272,6178, 19723,29418,35634,33501,33063,34386,38665,44860,39452,41151,15755, 34754,30512,37478,42284,40526,29417,34386,33277,36766,38305,43975,4767, 43975,37478,36765,36765,1715, 28516,34754,35633,28515,33277,34060,33501,36765,34754,18686, 33062,18567,36141,37478,37477,24822,28961,19722,34754,13568,35170,6664, 33757,19551,21614,32870,34060,23609,43629,44860,33062,16725,36765,20991,17541,36141,36141, 33757,34385,39818,44859,42648,44859,26555,33757,45431,20527,41234,34753,16724,43975,43975,35633,34385,25901,33756,36140,42648,35170,36764,35633,11925,34385,34385, 238, 42858,9712, 35169,31137,35633,25080,42121,8972, 25335,7787, 37477,33501,45431,39601,36764,25079,17095,7332, 43974,37477,606, 39990,6181, 38607,7087, 40382, 40995,45431,41844,38536,45431,39011,39088,41151,34753,5398, 39244,38139,41150,39082,410, 4062, 23608,36140,38523,39287,45430,40296,8439, 11546,6669, 8469, 11686, 1306, 26910,10658,10468,35169,36764,45430,43333,41318,36764,1162, 39843,11217,31136,44365,36763,10801,35632,11123,22340,38433,41720,43088,22650,17591,36140,9413, 40575,33756,35169,29916,34753,24542,36763,45430,34384,25078,34384,14440,38377,38155,40737,42647,37877,43088,41150,38824,28514,12548,40083,36763,33756,38598,39311, 45430,36140,39818,7468, 34384,23208,40428,43974,44859,40113,43087,41150,44859,42121,43974,40575,45429,11281,7157, 40020,2575, 11216,11333,10153,21117,40526,10043, 8958, 34060,8370, 44365,9376, 11122,9418, 11768,9480, 43332,8822, 10467,45429,34060,43629,45429,33500,26909,35169,35632,22836,45429,21116,23014,7199, 8302, 7985, 34753,9727, 43332,7429, 23013,7472, 14093,35168,5563, 45428,36139,35632,37477,9565, 17198,35632,32870,41608,36139,37476,25077,20526,37476,35168,33756,5965, 18018, 19721,34752,9127, 42462,45428,36139,30511,35168,41977,24062,21904,16723,36139,34059,28074,34752,39964,16451,9088, 7770, 43629,35631,27271,28960,39170,1503, 35168, 36763,39244,33500,22045,28073,36138,40927,1091, 36138,34752,22502,34059,27270,36762,9469, 34059,28072,28071,7691, 33062,31763,41406,26908,27670,29416,30510,37476, 23820,43974,36138,22044,10597,9404, 33276,10314,8347, 11847,33500,10992,22189,17197,34384,33500,33276,37476,34383,37475,6560, 34383,31762,33276,2201, 25900,34752, 22835,35631,40338,44858,45428,38423,40183,34383,41509,42461,45428,38927,41509,21759,18334,45427,29415,5964, 29915,4951, 5559, 4881, 37766,2934, 1254, 36762,36762, 35631,41977,38151,37719,26202,31761,35631,34383,34059,37475,19638,26554,43087,40927,15611,36762,35630,16580,43087,37475,7088, 5748, 1930, 2530, 6553, 1551, 6290, 3013, 2115, 45427,45427,35630,41150,36761,29414,31760,27269,37475,37474,37474,2505, 45427,33755,11846,33499,22649,19812,44858,35167,1131, 3347, 43628,33062,38568, 39964,45426,41406,39842,34751,37474,34751,37474,33755,37473,33755,37473,34751,33755,35630,45426,28070,29914,35167,31759,34058,26201,43332,33276,36761,10958,34751, 36138,34382,35630,6421, 21758,25607,33499,35629,1504, 212, 5963, 36137,34750,31758,21903,36761,34058,34382,42858,20407,27268,41149,34058,36137,26553,12949,12327, 37473,25076,36137,32470,31135,35629,34058,43973,28513,26200,34750,12065,43973,6179, 34382,36137,16607,18230,36761,39842,42647,4821, 11399,25606,26199,33275,37473, 31134,34382,33061,34750,34381,35629,34750,33275,36760,35629,25075,34749,16722,29913,27669,35167,30509,33754,36760,33499,19248,13535,37861,39144,38203,45426,34381, 39527,42461,34749,37472,6076, 44858,5163, 40575,45426,10112,36136,18685,24315,34057,40995,41067,19162,7865, 44858,42857,36136,35628,33499,7331, 11924,7508, 16014, 44857,33275,15990,33498,33061,33275,14045,22834,9045, 24061,45425,26907,20744,24060,34749,34057,20525,12597,28512,22339,37472,28511,34057,33754,26906,18176,31757, 12335,18017,26552,26198,16639,34057,21232,37472,15056,19247,35167,3375, 33061,12646,35628,18757,14244,14385,25899,12805,9308, 41977,36136,34056,17830,36136,28959, 20406,16910,43332,35628,29912,34381,14136,34056,33754,25898,34749,28958,13393,24314,18068,36135,17635,43087,26551,28510,31756,26550,33274,24313,16195,33274,11845, 14125,25074,15936,12430,43973,43331,23409,35628,32870,16363,17925,19246,31755,15560,36760,18566,28509,23207,43973,24821,25897,45425,36135,38100,44365,41719,45425, 44364,44857,33498,8353, 38864,20190,37472,34748,37471,24059,11034,61, 3155, 40575,19078,43628,20405,19550,36760,15032,37471,9432, 7905, 37471,18067,22043,5020, 45425,19637,25896,26905,44364,23012,14364,28069,35627,4575, 41976,37471,9711, 7795, 36759,23819,35166,13233,5162, 44857,23011,13952,24541,30508,31133,41508,34056, 34748,35166,3127, 35627,33498,37732,38156,37865,38979,38616,5849, 5317, 41406,190, 4356, 44857,40145,34748,32469,15754,36759,35627,20877,15799,3125, 36759,43972, 28508,35627,36759,36135,9252, 9780, 9627, 16721,37470,28957,36135,33274,36758,36758,15031,11472,36758,33498,33274,18823,36134,39466,36134,32870,37470,26549,24820, 36758,26548,7977, 11280,23206,35166,27668,37704,40926,6556, 6559, 37822,13085,40256,44364,37911,43628,35626,37470,39384,37801,839, 2367, 1623, 5316, 44364,42857, 32468,5654, 35626,5560, 45424,33497,37764,15030,29413,28068,42461,33497,3404, 5088, 41844,34056,9815, 38793,15559,29412,20000,35626,35166,32869,10338,8244, 34381, 42647,45424,43628,39287,39818,39480,41844,40926,43331,27667,40926,36757,31754,40256,29411,21902,33754,27666,35626,40479,44363,42121,36134,20743,32467,35165,15096, 15856,37470,19324,21613,36757,12522,34380,33497,18756,36757,18885,38108,37469,32869,19026,35625,35165,45424,43972,27267,36134,29911,44856,24312,37469,33497,34748, 11545,36757,15870,39936,20189,20524,7084, 42461,43972,13217,25605,25334,28507,40737,35625,23607,35165,35625,7321, 39639,8381, 6791, 36133,36133,10313,45424,7255, 8738, 10727,10421,22338,4144, 44856,24058,40676,17010,24819,17397,42283,41719,42120,18619,43972,35165,41149,39159,44856,43331,10420,23408,34747,43971,17974,41719, 39466,42283,6292, 43971,43331,39185,39990,1428, 44363,39299,41976,44856,3746, 34747,37469,40113,40224,32466,44855,34055,37469,8197, 43627,42857,40676,32465,38679, 45423,9863, 41608,39990,36133,34055,42647,29410,5561, 32869,38684,12283,38339,38470,24057,43330,39088,41719,44855,34747,43086,44363,19245,41976,38709,30507,39584, 45423,44363,40926,35625,34747,44855,41149,41976,44855,33496,10759,10957,15046,5482, 2232, 43330,6422, 42283,44362,42283,41843,40810,41233,38305,45423,39842,14920, 43627,42120,38286,2390, 38802,34746,35624,36133,2771, 43330,41233,33496,33061,4819, 3873, 44854,40862,45423,45422,40625,2958, 24818,35164,28956,35164,17829,24056, 26904,15029,41975,2064, 42460,648, 44854,3304, 7080, 23606,5480, 37925,41843,44854,32464,44854,33273,20404,26197,25073,24311,7083, 27665,38810,41067,39097,37966, 38998,38755,13505,44362,1072, 43086,36756,34746,37468,3045, 38824,39862,12906,18498,28067,24817,15972,31132,35624,6177, 37468,41406,31753,23205,43971,44853,33753, 45422,39510,43086,16941,41718,38446,44362,43627,40625,16391,2391, 40478,6554, 13129,36132,318, 40995,15625,34746,35164,24816,36132,34746,34745,35624,34745,33496, 35164,37468,34055,25604,35163,29910,31752,35624,37785,42646,26196,35163,31751,33496,35623,13980,36756,31131,31130,4639, 44362,12774,37468,33273,34380,2366, 926, 14712,37467,6180, 23204,20990,22501,43971,40337,40810,2005, 1132, 21231,6558, 28066,35623,5148, 33273,35623,36756,37921,18822,37689,45422,16981,12993,2848, 40574, 21475,1658, 37467,37467,34745,37467,29909,888, 9735, 35163,37466,34380,12992,32869,28506,183, 4944, 21230,5151, 45422,25072,34745,20294,4631, 987, 8680, 40145, 33060,16170,13361,36756,45421,26903,35623,35622,17924,35622,29409,28955,25071,13648,33753,3008, 12961,21474,34744,35163,34744,37466,44853,2881, 34380,35622,33753, 34744,36132,28505,199, 6775, 2089, 70, 3189, 45421,2743, 6057, 1674, 6279, 6406, 5150, 2427, 36132,33495,20876,36755,36755,44361,24815,27664,35622,34055,22833, 34379,37466,35621,2796, 39396,35621,36131,37466,34054,25070,28065,32868,25069,34379,37465,27663,13399,6776, 12070,37465,34744,36755,34743,3122, 22337,28954,33273, 34054,42282,43086,37743,39964,44361,1181, 37465,24310,37698,36131,36755,40054,22336,19077,15775,26547,37465,31750,34743,35162,36754,23818,34054,37464,37464,22500, 34054,33753,34743,12045,11991,7688, 36131,36131,35162,2060, 4943, 33495,433, 44361,44361,14152,4759, 37691,42282,17878,17540,3040, 37940,40525,34379,5733, 13125, 16877,35621,14108,29408,33495,41608,33752,42646,43970,6919, 23203,35621,36754,4942, 35162,35162,36130,4350, 36130,39796,15935,34743,15971,34742,21473,35161,33752, 19811,25603,2768, 44853,12305,44853,22042,37979,38192,29908,35620,33272,32463,29907,13458,23202,35161,24540,19720,38566,20085,5389, 13645,40183,35161,1804, 37464, 39914,12298,21612,38664,5945, 40224,40020,5731, 43330,40478,2364, 5729, 25602,1094, 45421,4758, 41508,34053,43627,32868,15934,39964,44852,3244, 45421,44360,44852, 39252,43626,43626,43626,44852,27266,1567, 5231, 42282,43329,7061, 32868,37464,36130,34742,6405, 1714, 34742,41843,35161,31749,23010,34379,18884,21115,33060,39323, 45420,43085,27265,34378,37463,4563, 12090,42120,2549, 18565,12916,23605,20293,29906,41975,36130,37463,40994,38038,32868,15201,34053,43329,1733, 41405,32867,35620, 23407,30506,41508,32462,12967,19244,36754,24814,34053,36754,36129,36129,21229,17682,35620,35160,22648,35160,18821,40224,34378,33752,36753,22041,37916,26902,36129, 19243,33495,23817,37463,35160,16834,28504,39620,12862,28064,41608,26901,33060,16337,26900,14417,38653,41233,44360,26899,35620,33494,34378,44360,37463,36753,1232, 25333,34378,2932, 6056, 3344, 4872, 41975,26898,2088, 5554, 28503,3149, 4239, 40925,16940,37738,37847,36129,22188,17326,32461,20084,34742,36128,4945, 29407,22040, 35160,24309,21472,19719,42460,14578,45420,28063,24539,37462,44852,12669,34053,21114,33752,15501,24538,29905,32867,20875,32460,16909,39438,42120,34741,37462,20523, 17727,18755,20647,30505,41508,45420,40994,43085,37960,40183,43970,40478,38353,39842,41507,37462,2229, 19475,25601,21757,35619,17877,33272,16106,33494,25600,35159, 27264,36128,38525,39795,31748,36753,20522,36128,35159,23201,36753,37903,34377,38260,43970,38961,42857,33751,34052,33494,16769,30504,26897,21756,36128,34377,16336, 39023,33751,43626,8255, 38600,6060, 45420,40183,40625,42646,40295,22039,10337,33272,39323,8749, 8733, 44360,38843,40737,34741,39299,22335,38011,40862,33060,40145, 39480,38398,37462,40625,34741,25599,34741,44359,43625,40295,38459,38749,40525,39480,38554,7536, 33272,34377,34377,34376,14130,14811,29406,40810,41607,43625,20646, 43085,15970,12054,43970,34740,27263,29904,8125, 39891,39360,20742,43625,45419,32459,18820,34052,38985,43625,36752,37461,36127,33751,31129,29903,35159,4689, 40381, 17094,7064, 39045,40053,38236,38990,43085,31747,4565, 35619,17876,15186,22187,36127,30503,34376,26546,36752,37958,26896,44851,4500, 44359,21228,39311,38824,38918, 42856,41975,38515,43084,42460,3740, 43084,25332,43624,41843,38009,38943,44359,33751,34740,35159,43624,25895,36752,45419,40624,38010,38880,41405,38013,38695,43084, 38152,39732,42460,38933,38410,41405,23200,42282,41718,35158,23199,34740,28062,35619,22499,13392,43624,34740,21755,40624,20741,6773, 44359,36127,33750,1153, 43329, 37461,41974,12100,41233,38243,14044,40737,40994,35619,8086, 36752,39233,39657,2479, 40810,43624,40113,3541, 44358,44851,43623,40224,36127,36126,38949,42459,40676, 38490,37461,26195,40862,43623,25894,34739,36751,33750,34376,16980,43969,28061,43329,23198,44851,37977,41842,44851,40994,43623,4451, 38648,37461,22186,41149,44358, 32458,20403,33494,32867,36126,25331,38201,43623,21471,35158,23816,18459,34376,21227,12204,44358,44850,35618,34739,39144,45419,36751,41607,19323,4873, 24813,19161, 38984,36751,34052,12022,44850,24812,17367,10991,29902,21754,43969,19474,13332,6774, 37460,41842,28060,17093,40020,40337,33271,41974,36751,11613,35618,26895,26545, 3504, 44358,41067,36126,45419,39252,37708,2635, 38437,38030,15821,39556,39395,2175, 43622,32457,17478,32456,36750,34739,34739,14102,22038,3398, 39150,4397, 38560, 39989,18229,31128,11988,41718,1449, 5011, 32867,21470,36750,43622,23406,2607, 13584,37460,24308,32455,1103, 12133,42281,36750,14407,23815,20645,22185,40295,29405, 20188,28953,20402,17009,37847,38653,33493,26194,5730, 28952,25068,42856,2200, 21226,22037,35618,33750,43328,17277,34375,33750,24055,18819,17161,39890,13044,12337, 19810,38984,41718,38984,39126,43084,45418,41717,34738,7308, 41842,37460,37460,34738,31746,7487, 21753,28059,16335,3671, 32866,9365, 44850,33271,7638, 37459,7178, 42459,6408, 22832,35158,33749,19718,37459,33749,15348,26544,35158,23009,2230, 33493,35618,42119,39287,40624,1455, 14723,37459,33059,34738,25893,14228,23814,29901, 36126,35617,8833, 40736,45418,8569, 44850,38650,37459,10042,34375,2408, 34375,25067,38099,36125,34738,33271,14333,43328,41974,42646,5641, 35617,42645,18458,41717, 15004,11033,39510,42645,16066,24537,45418,43328,33271,32866,19025,38214,36125,16833,36750,13670,26894,34052,35157,33059,34737,33059,40676,42645,41717,39299,43328, 30502,38792,41607,39777,44357,37458,16679,25892,1540, 22184,35617,33270,12048,41148,28502,45418,15158,12262,14371,35157,35617,31745,43083,41717,45417,16979,12885, 44357,32866,14101,17366,38703,39425,36125,34375,39936,18333,34374,43969,2905, 16638,5840, 19907,45417,19242,44357,12451,29404,43083,13247,25330,17238,42645,35616, 25598,17055,36749,35616,39619,35616,4809, 39252,37458,6541, 40182,19999,45417,21901,42644,19998,2365, 21225,40525,44357,23008,36125,38438,40993,45417,44849,34374, 36749,37458,42856,31127,41232,45416,35157,20521,28501,11544,9142, 35616,15045,41148,34051,43327,21469,37458,35157,14142,42459,30501,26543,43969,35615,15368,41716, 42856,35615,28058,41607,34737,33270,37457,24811,35615,33059,17634,1642, 43622,43968,23604,33749,33493,34374,15704,42855,39219,38907,36749,33749,33748,35615,37457, 45416,35614,29403,36749,37457,28057,35156,16152,36748,42459,14370,28500,40574,20083,37457,37456,40223,25066,31126,35614,32866,3148, 21468,24054,40223,36748,38423, 41232,33493,40736,45416,33748,40223,14894,44849,37456,40428,36748,2266, 21224,43622,1427, 42855,44356,40478,33492,45416,20989,36124,32454,17633,29402,37456,34374, 37456,36124,12328,33492,25891,42855,33492,44356,44849,23813,44356,44849,36124,42855,13745,35156,33748,19322,26542,27662,20740,40295,17396,26893,37455,41405,36124, 33748,44356,2453, 41148,34051,37914,42644,41232,38549,40525,41232,43968,38384,42644,40294,37946,44355,40182,40574,43968,2112, 44848,27661,13246,40337,40428,43621, 7604, 43968,13021,37941,43621,45415,42458,38024,39936,41507,38013,39584,39777,38713,38755,41404,41842,44848,40053,33270,13798,41841,13283,41974,18332,14313,44355, 38762,40624,44848,35156,44848,41716,33747,35614,35614,35156,34737,40477,43327,36123,36748,21752,35613,36123,31744,43083,35155,43621,1918, 26541,32453,37455,33747, 18175,40925,45415,30500,25890,42119,42119,45415,33747,4398, 45415,41716,44355,43327,26892,35613,35155,22334,33747,29401,5083, 35155,35155,35613,5388, 43327,6921, 6407, 3371, 43967,40337,36747,45414,8440, 33270,35613,35154,37455,36747,1304, 14282,26193,41148,1302, 1340, 41973,41973,38075,38797,42644,39052,37941,41606,17875, 36747,39298,44355,1842, 40861,41973,39438,34373,28951,45414,38216,17365,33492,36123,31125,40993,45414,45414,16252,5946, 34737,7063, 34736,35154,36747,35612,35154, 33746,33491,32865,34736,36123,22036,36122,10572,37455,29900,36746,28056,34373,15003,35612,23405,34736,37454,37454,29400,43967,45413,15680,36746,22647,12506,33491, 34736,26540,12324,24307,23812,34735,25889,41318,42458,27262,35154,37454,17726,45413,37718,38599,39601,38296,37963,41231,38156,13610,35612,35612,43083,15225,34735, 41404,31743,36122,33746,23404,35611,33746,34373,21751,23811,38632,38279,43326,39935,40993,35611,33491,40428,23197,39817,16169,30499,29899,13307,37454,35153,35153, 30498,33491,33746,35153,31742,23196,42643,34735,19906,34051,37453,35153,26539,25888,23403,14288,9187, 7477, 31741,35152,29399,13669,18958,41231,25065,37453,34373, 38105,44847,44847,43326,37878,19160,21113,26891,35611,39126,40574,2387, 42119,33490,13851,386, 28499,26890,34735,41404,34051,85, 3629, 40736,4236, 36746,20739, 22646,34050,40524,34372,27660,25887,42643,34734,31124,36746,26538,45413,39556,18564,29898,43967,37453,30497,34372,18818,17160,28498,14559,39510,32452,24306,24053, 36745,24052,41606,21223,32865,41606,33490,42643,45413,6167, 22333,3926, 36745,19405,32451,34372,43967,43326,19809,37453,44354,40573,43966,40861,32865,823, 31123, 31122,29897,14348,44354,28950,33745,34050,39755,40675,45412,33269,41231,33490,34372,40112,43082,13043,42854,40736,2571, 34371,40223,23603,28949,25329,261, 3190, 32865,44847,35152,22498,33490,39890,40993,38082,36122,38973,40573,42854,37452,35611,13053,36745,15367,36122,31121,17923,31120,19159,36121,22497,44847,35610,43966, 14359,36745,37452,36121,16978,36744,32450,18754,36121,37452,6657, 18331,35610,7681, 43326,25064,36744,42458,36121,44846,41066,32449,41066,44846,36120,32448,18228, 21467,9117, 35610,34050,21750,44846,13178,36744,3399, 36120,7505, 24810,35610,16768,37452,41841,36120,44354,36744,43325,19473,31740,26537,12806,21900,23810,12142, 22183,42281,34371,37451,41841,22332,37451,25328,38106,19472,41716,41606,39023,37451,40477,43621,35152,34371,31739,43966,31738,36743,43620,45412,37926,16720,35609, 20187,35152,32864,27659,37715,43620,41605,44354,14363,3540, 35609,28055,12242,37451,37450,28497,42118,33745,36120,21112,8575, 39692,10544,8401, 10689,42118,17828, 16390,5640, 33269,10873,11471,22035,44353,36743,41507,2388, 3188, 41605,44846,42643,40524,43966,45412,40336,3739, 43620,39795,44845,6059, 5732, 41715,44845,43965, 21466,34734,37450,14569,43965,44845,25327,33489,27658,34371,30496,32864,26536,12832,37450,16065,36119,43325,33269,35151,34050,32447,17681,45412,31737,33489,19905, 36743,34049,21749,36743,38289,40222,38777,43325,43620,36119,44353,43082,32864,2428, 33489,24305,39817,32864,13924,41147,13391,43082,35151,40861,34734,18174,32863, 18563,37450,14622,40256,41317,33745,36119,17632,44845,35151,30495,41605,16168,15841,37449,17196,16484,31736,22645,37449,21348,37449,39862,20292,35151,42642,13282, 20082,37862,27261,29896,45411,27657,36742,10336,34734,31735,27656,34733,35150,35609,36119,34370,35150,18396,30494,24304,19471,27260,26889,19636,40861,20401,36742, 28496,13360,17510,20400,24051,18883,22034,33058,18684,28054,31734,36118,13301,33269,33058,13300,17477,18817,36742,34370,33745,37449,14893,35150,32446,37448,14773, 31733,21111,3592, 41715,42642,34733,22644,43619,34733,17440,24050,36118,36118,19024,25597,44353,42458,28948,17590,28947,20081,3123, 45411,701, 3672, 43082,36118, 37448,12760,28053,34370,8137, 35150,21465,36742,28946,19023,15262,21899,33744,37448,41715,38824,17922,43965,9696, 42642,43965,35609,36117,25596,35608,13005,35608, 35149,29895,37448,37807,39890,5838, 41147,38668,41317,40860,40053,44844,18066,39103,34370,42118,40992,44844,44844,35608,38927,42281,13155,41605,43964,1560, 42854, 35608,24303,34369,36117,19808,30493,40623,42118,37447,30492,22496,24302,34369,18330,25063,29894,34733,42854,3670, 41841,7650, 38853,2363, 41715,33268,33744,37447, 37447,31119,44844,16389,31118,15317,36741,40112,32445,42642,12222,44843,45411,15470,8827, 37777,26888,44353,34369,34369,2713, 18882,44352,41404,38343,27655,41147, 3965, 10370,45411,37447,44843,33489,11980,44352,25886,32863,21347,35607,36117,22331,19717,35607,32863,39017,21611,15610,16637,11398,29398,19321,18395,9017, 22033, 36741,35149,24049,34368,35607,38620,42641,42853,15316,18227,37446,1871, 16483,45410,17973,21610,40623,4874, 38158,44352,19076,43325,37446,38973,34049,42641,39370, 40573,40145,45410,23809,35149,38343,40573,34368,32863,36741,39841,43964,33488,28945,18394,18122,36741,37446,37446,32444,35607,15774,15609,28052,43081,39193,18065, 20738,29397,23808,34368,15366,42281,19997,16482,37445,13331,33744,24301,31117,33744,33488,23007,18016,35149,36740,35606,36117,36740,37445,23602,34732,28495,14577, 28494,2880, 16280,26887,2140, 38984,39692,13711,24536,28944,34049,14502,34732,36740,15028,30491,20291,29396,22495,16519,12053,43964,3964, 33058,32443,23807,982, 45410,16550,34732,13338,12948,18015,33268,20290,35606,33488,36740,36739,19075,31116,34049,62, 3738, 40112,40294,36739,2020, 35148,30490,32442,36739,22330,15213, 28943,27654,16549,18283,12316,13968,13881,25062,19074,24300,36739,32441,36738,35148,6055, 38979,20399,12301,40019,34732,36116,33268,14637,43964,31732,1303, 41147, 43081,41714,14079,45410,10230,33268,37445,30489,33267,17874,26535,12436,35606,37768,40524,40925,44352,5476, 44351,40019,41604,40336,38829,38185,39817,26192,33743, 45409,38954,40572,42641,44843,44351,39841,39466,38735,41507,40524,37801,43619,38172,23601,43619,39228,14772,38291,34368,41506,45409,40925,42117,42280,45409,37781, 39425,34367,38052,42853,43324,42117,45409,38801,13744,3301, 31731,34731,30488,36738,43324,26191,14865,12789,37445,23600,37799,41403,39841,23195,41840,39244,37893, 44843,45408,42117,44842,39573,44351,38894,10800,37444,36738,33743,34048,35606,7947, 40019,20520,36738,37444,41840,22831,39150,42853,33058,13177,24535,35605,13640, 40477,13528,24048,30487,15015,35605,17589,12827,29395,35148,167, 43081,3708, 32440,32439,16013,10312,27259,20186,34731,6922, 29394,35148,1418, 44351,37444,19904, 24809,43963,30486,14343,21346,42457,27653,17539,7616, 27652,37444,36737,34367,36737,37443,24534,18064,23599,24808,12161,36737,34731,22032,21345,35605,26534,36737, 1021, 45408,12176,36116,45408,34048,33488,37443,40427,29893,37443,33267,37443,35147,33267,43324,33267,28493,37737,43619,37716,40675,37824,42853,37794,38173,38413, 38262,6280, 37993,37731,44350,617, 2906, 3822, 2409, 7065, 6772, 45408,36736,25595,22031,4237, 21344,33487,29393,39185,40427,38806,38708,36736,33266,34048,18957, 36116,19470,23006,14394,36736,16719,36736,38403,40860,43081,18226,44842,28492,42641,44842,37442,35147,2795, 43080,30485,37442,37442,37740,28051,45407,16012,4630, 18683,2849, 118, 4238, 16064,31115,34367,18393,36116,12928,33057,41066,44842,1262, 36735,38584,14692,36735,32438,34367,43618,18618,16977,32862,12505,12594,44350, 36115,14181,36735,32437,37933,14393,20185,562, 4056, 25885,2429, 40477,43324,21222,4014, 36115,14711,43963,6166, 41317,12236,13560,43323,34366,42280,34731,35147, 24807,28942,35605,22494,16251,15055,16043,22329,23194,36115,13095,33487,40809,33057,23598,37442,23005,37441,21748,31114,41506,37441,33487,17325,19241,35147,14259, 29392,21898,17324,35146,7634, 8110, 36115,4501, 35604,16167,22, 39480,1761, 40053,44841,30484,43963,14516,38071,40256,13772,34730,39913,36735,25061,41066,40572, 37441,38624,12286,41506,43080,38016,40992,14052,34048,40476,28050,30483,23193,34730,23402,20289,14829,44350,33266,16548,22030,27651,34730,16105,7185, 42640,37441, 13951,25594,33743,12127,40924,43080,32436,35146,40809,45407,34047,25326,36734,5391, 44841,32862,37440,26533,40860,15910,24533,43080,21221,12482,16279,30482,35604, 32862,44841,5149, 44841,31730,8452, 8874, 9311, 16718,32435,21220,16334,28941,12382,32434,11969,43323,12064,43323,23597,37440,31113,17092,19549,27650,34366,27258, 36114,44350,7211, 39509,3223, 31729,32862,38474,35146,23596,23595,31728,40924,21464,31112,36734,12122,40924,21609,35146,33487,15027,17588,16976,29892,32433,23806, 22182,26532,23805,34047,34047,26190,33057,17776,34047,12823,23401,35604,12470,15414,40809,36734,25884,16127,35145,34730,26531,34366,32861,35604,36734,41317,36114, 18457,17680,43323,19635,33743,18881,32432,31727,34729,36733,26886,17725,15726,25060,17054,19716,19469,16305,33486,22029,19240,43079,38198,44349,35145,17538,4301, 43618,41714,45407,39081,35603,24806,19022,19996,31726,27257,15558,36114,15284,22028,26530,35603,36114,33486,26885,32861,37440,36733,33486,34729,35145,36733,41840, 17775,37440,35145,18956,14636,23400,32861,20874,13278,21343,33266,28491,23804,34366,34046,45407,37821,15347,7976, 41840,24299,25593,15315,24047,36733,20288,11279, 22328,38998,41316,19715,41604,39673,18063,37439,42640,40427,40476,44840,45406,29891,43322,25059,12924,27256,17195,31725,40992,34046,13066,44349,40809,24298,12869, 43322,39322,15346,41403,37760,37994,39227,42640,38002,12593,40019,13194,18497,33742,41403,44840,20644,38606,33486,15658,21608,38703,38670,43963,28940,37439,37439, 24532,17439,5839, 8451, 44840,14088,34365,5390, 35144,27649,37439,24046,34365,16876,33057,23192,31724,34046,17827,22181,15212,33266,19404,28939,34365,19239,23004, 42852,28490,26529,37438,38587,39913,45406,39890,44840,16908,38843,38770,39713,39989,34046,17237,28938,36732,31111,36113,34365,36732,22830,37438,14249,34729,32431, 21219,24045,32861,37438,35603,40808,34364,4450, 45406,19021,35144,36113,37686,2502, 23191,27648,16975,37735,24531,35603,14281,44839,31723,36113,31110,33485,35144, 37438,33485,33265,41839,33742,19634,43962,38777,37736,31109,34729,36732,31108,39118,45406,439, 4502, 3187, 29391,14532,13572,43962,27255,36113,22829,17587,739, 3271, 28489,42280,12103,19903,45405,14199,6920, 37437,6058, 13729,19468,16278,33265,17826,6918, 13710,35144,33485,13709,3270, 37437,35143,18955,32430,30481,34728, 23594,9304, 27647,12696,19238,31107,28488,32860,12050,29890,16518,24805,28487,34045,9359, 40735,37437,42640,33056,19020,34728,35143,31106,31105,35602,23593,20080, 12227,37437,17323,12753,27254,44839,24297,29889,40623,28049,27253,13923,39527,41146,8023, 21218,32429,4564, 40924,44839,40860,8279, 36732,35602,36112,26528,32428, 44839,30480,27252,16481,16678,16362,42280,35602,17276,28486,27251,35143,30479,7062, 40623,17091,6771, 37829,37784,44838,37695,34364,21342,35602,37744,39103,38253, 38834,43079,42279,36731,43322,38035,23399,40923,43962,42279,41316,40622,38078,36731,28937,40808,14757,43618,39935,39333,44838,39345,45405,44349,45405,37436,40018, 43962,43618,40294,44838,38635,14919,37991,41973,45405,43322,45404,42639,41506,40808,19902,36731,44349,38244,17774,38948,40222,45404,14936,38540,39935,37970,42279, 40112,41146,43961,38033,13431,19807,39479,36731,45404,43961,23803,37721,37735,33485,43079,37901,41403,40294,34364,13492,17008,36730,20737,26527,34728,38426,40859, 41065,39732,37751,38838,39103,38592,43961,45404,39712,40255,40336,39555,40735,39052,40052,41604,44348,39030,1800, 43321,43617,44838,40808,40018,27250,35143,33056, 38029,41604,39555,3163, 7590, 42279,34045,34364,43079,44837,8194, 42278,1721, 33484,20398,35142,42278,39451,32427,14173,43961,44837,32860,39600,44348,311, 38449, 27646,39193,38571,40052,43321,39227,37436,42278,24804,33056,38017,44837,40675,34363,21110,44348,36730,32860,31722,27249,12638,35142,40293,44348,28048,42639,36112, 16450,14021,45403,36112,28936,28485,28935,34728,22493,37436,17825,37436,32426,43960,44837,27645,40992,44836,15157,36112,27644,33484,39712,7322, 40222,40222,23592, 45403,19714,40991,17972,36111,30478,3318, 32860,16126,33742,15002,44836,36730,32859,28047,26884,21897,24296,13299,43617,35601,28046,9808, 10688,10571,41839,39555, 45403,37435,37435,38918,36111,6598, 38490,23003,25883,35601,37435,31721,31104,25325,45403,43078,35601,30477,36730,33265,17007,34363,27248,35142,33056,15176,36111, 23398,21607,15500,15095,31103,34045,40381,20643,33484,34727,37435,36729,25882,25592,35601,34045,34044,33484,34363,26189,36111,35142,33265,34044,20736,35600,41065, 40923,2203, 44836,45402,44836,43617,34727,33264,35600,37434,34727,23190,10193,32859,24530,25881,37434,28045,37434,22180,23802,24529,34727,8380, 4909, 36729,36110, 29390,35141,26526,28044,33742,11923,25591,44347,35141,41231,38748,44835,40221,39045,34044,11278,36110,34363,27643,18173,33055,25324,16225,33264,15129,25058,22492, 24528,32859,41714,29389,35600,14531,35600,34362,37434,29888,17631,44835,36110,33055,3607, 23002,21463,27247,24044,32425,35599,42457,35141,36729,36729,35599,28934, 36110,40476,16042,33055,19320,34044,17236,26525,33483,37433,15001,31720,33741,38734,35599,40735,40476,43321,41839,19901,17275,33264,29887,16832,25057,37433,40083, 32859,29886,33055,21109,28484,35599,25056,23001,35141,37433,36728,328, 11470,37433,31719,31718,33054,33483,37432,33741,37968,36109,35140,24295,8156, 41505,8706, 38748,8666, 33483,34362,27642,37432,20519,36109,9044, 34043,20287,33483,33264,38496,43960,39555,37432,37432,44347,5334, 41839,35598,37725,42457,44835,38152,41146, 21896,29388,5335, 40991,34726,26524,9447, 13187,875, 39795,45402,28933,33054,36109,40923,18880,31102,39963,35598,35598,7131, 54, 432, 1080, 5110, 6473, 2810, 41603,43321,3606, 36728,44347,42639,29885,39201,26883,2318, 32858,13936,18879,37431,17971,29387,35598,3024, 40475,36109,16422,35140,35597,34726,34043,35597,35140, 37431,12860,45402,4711, 20397,42457,37705,41505,37883,3977, 41972,38312,41402,37863,38367,43078,45402,42852,222, 38128,22327,36108,41065,27246,34362,28932,25880, 13080,35597,36728,28043,37431,20286,23000,36108,36728,41505,36108,15933,26523,20735,45401,34726,33263,18062,40523,36727,37431,45401,45401,5506, 36727,34043,22643, 27245,31717,31716,37430,33263,42456,44347,33263,41402,40622,41146,41316,43078,37430,22326,13630,37430,29386,36727,31715,32424,36108,36107,34726,27244,39137,43617, 36727,43320,42278,33482,37430,16939,32423,12032,44835,42639,17873,15869,18014,14918,12635,12998,31101,33263,703, 33262,37429,18282,29884,36726,36726,26882,32422, 1351, 35140,34043,44346,37429,36107,37429,35597,34725,35596,15557,18061,36107,27641,36107,13174,22999,36726,34042,21895,28931,37429,35596,31714,32421,22642,37428, 31100,15840,16421,36726,45401,20873,26188,44346,14324,17586,23189,33482,35596,25879,35139,44346,40082,28042,29385,37428,24803,32420,26881,34042,8572, 10266,35139, 5674, 22828,33482,19237,16517,7187, 39451,4369, 43616,40859,36725,37428,19548,25323,34362,16767,25055,25590,12448,42638,12510,378, 23591,22998,34725,2995, 19806, 35596,25589,33741,30476,34361,34725,37428,41603,39600,19547,31713,33741,19073,19900,28483,36106,44834,28482,21894,43616,45400,18878,30475,37427,32419,37427,8056, 39243,43616,36725,7267, 1924, 33054,37427,34725,39712,16516,754, 16803,34361,33054,44834,33053,28481,43320,32858,37689,37802,42852,38860,37427,15063,40475,18392, 43960,37426,44346,37426,7129, 43616,3896, 32418,33482,35139,44345,7711, 20734,34042,3450, 36725,24802,9362, 34361,11469,7316, 34724,34724,15679,35139,40923,35595, 30474,21893,45400,9670, 37426,1823, 44345,35595,11685,10485,10166,9081, 43320,41714,33740,19633,33053,21747,17679,34724,17006,11983,37426,36725,13527,43078,34724, 40572,44834,45400,35595,37981,41603,19072,44345,38838,39076,41230,38777,40182,41713,16677,18753,27640,40427,15243,29883,36106,33262,34042,25054,17824,12195,35138, 43077,14691,34723,30473,15932,12561,13559,35595,33053,24043,14342,20642,33740,12952,38151,34361,7793, 37854,16717,19995,36724,37425,15582,44345,7860, 37425,1822, 45400,35138,23397,33262,28041,36724,10912,18617,34041,33053,11173,32417,36106,19319,34041,28480,25588,44834,35138,22827,33052,28930,26187,37425,37425,22826,34041, 14287,35138,42117,38901,40293,43077,40991,39712,37424,24294,34723,32416,33740,26186,29882,12487,16388,31099,35594,23396,42638,27243,21892,32415,19994,34723,12676, 20733,44344,42638,40221,5043, 31712,23395,34360,16224,42638,37424,9726, 22997,36724,34723,20079,31098,35594,20396,43615,45399,26880,35137,31711,36724,7136, 34722, 34722,21746,16606,30472,34041,12796,3897, 33740,36106,34360,44344,44344,44833,43960,43077,33481,45399,37424,33481,34722,35594,31097,18752,36723,3322, 34360,36723, 36723,23590,33481,32414,3764, 36105,36105,15894,19019,43615,5504, 42116,6981, 2168, 2783, 6472, 1586, 6831, 3514, 3135, 14347,41838,15253,36723,36722,20641,33739, 24801,19993,17509,31710,43320,44833,33739,45399,29384,34360,25878,34722,44833,41065,25053,40293,34359,30471,35137,34359,16676,12965,35594,29383,27639,35593,12526, 28040,20285,42116,20078,12079,15513,14218,1457, 37890,40991,38787,42277,41972,39777,42852,37927,42116,35137,32413,30470,20988,11277,36722,33052,11612,29382,42851, 37424,43615,43959,35593,20640,36105,36722,35593,24293,18013,35137,19805,25587,14798,31096,33262,27638,14078,32412,12291,23188,14991,37782,41838,26879,45399,4368, 38183,38254,29881,24292,3685, 958, 6333, 42456,35136,5881, 39889,41972,44833,39989,38181,37423,38642,41838,18225,16515,42456,43615,13258,38374,40475,19158,33739, 37423,33739,28479,24042,5425, 6215, 41713,41603,44832,43077,42456,38661,42637,42455,43959,43614,5267, 43614,28929,10799,7922, 39060,45398,8840, 16579,35136,34040, 18954,34721,33261,29381,8400, 39732,10833,14917,17364,26522,9379, 10657,33481,33738,22027,36105,43959,37423,10656,15094,36722,21891,19899,31709,17159,34359,31095, 33261,27637,31094,11987,42455,15855,8892, 36721,5045, 45398,40807,44832,28478,29880,37423,28039,28477,15868,35593,37422,23801,36104,26878,22996,35592,13040,36104, 22995,25877,31708,37422,16514,13751,35592,44344,33480,13692,6468, 32858,34359,6469, 29879,22825,19546,10095,34358,22026,20987,34358,25876,7991, 32411,29380,23394, 26185,34721,35136,37422,45398,43614,42116,37422,34721,32858,37421,14475,36721,44832,37821,36104,29379,36721,34358,37421,31707,34721,32410,34040,19071,35136,42277, 34358,43319,43614,37421,26521,34720,37421,36721,45398,29378,15725,43613,45397,13719,41838,40572,42637,19898,34357,34040,34040,22025,25586,12260,29878,33052,35592, 41713,35135,40336,36104,35592,24041,12989,18456,39795,22824,36720,44832,17129,20732,37420,27636,21606,13689,35591,36103,19157,35135,18329,34039,13369,38336,42455, 37420,40990,12661,39554,17476,27242,33261,19403,27635,33480,35135,24800,1106, 7997, 17194,33480,36720,36720,33480,45397,3553, 40221,43959,24527,31706,5507, 43613, 42637,45397,21890,41602,39176,32409,29377,36103,7700, 26877,30469,34357,26184,38037,37990,40293,38606,35135,43613,10265,42851,36103,36720,37420,13827,33738,36103, 25875,41972,35591,22491,18816,20731,25585,11468,33261,34039,22179,34357,22325,26183,35591,28476,36719,33260,41505,44343,40622,44343,42115,34357,36719,45397,42851, 41602,43613,34356,41971,39572,39029,41064,40990,37851,41971,43612,43319,5783, 39035,13915,39572,25584,43319,42277,44343,41316,42455,30468,31705,14248,35134,29376, 41602,1970, 2181, 41402,578, 38211,14875,6216, 43319,40622,38122,25583,34720,25052,40182,21889,39711,16974,33260,16513,16087,38743,36102,3451, 34720,34039,4326, 33479,38310,40381,45396,43318,40144,35591,44343,43958,9725, 24040,16194,36719,43318,16802,29375,37420,36102,29877,14743,26182,17508,42454,35590,44831,39755,45396, 33052,22823,18877,31093,572, 35590,40052,1202, 45396,44831,44831,12517,38739,22641,31092,8117, 20986,43612,43612,41064,44831,40807,13504,17363,34039,34720,35590, 33479,33260,34038,35590,34038,16333,19992,23393,18224,36719,24039,21888,18751,37, 3061, 21341,42851,23392,45396,41315,29374,29373,31704,28038,35134,28475,33260, 35134,36718,29876,21340,12416,35589,12310,25874,22822,34719,18121,24799,35589,15185,35589,22490,23589,33479,16420,36718,44830,21217,36718,33051,33479,36102,20872, 23187,24291,20395,34719,36718,17475,37419,33738,35134,25582,36717,24798,27634,7135, 33738,35589,37419,18328,36102,17438,13728,41602,34356,17823,32857,36101,12183, 44342,14406,30467,33737,34038,34038,36717,36101,23800,16277,21745,37419,23186,12391,45395,36717,27241,36717,34356,22821,37419,36101,14810,26876,17724,28928,26520, 40859,36101,34719,24526,24038,34719,41837,27240,35588,4907, 13457,36100,45395,44830,42850,36100,36716,33051,44830,26519,33478,36716,33259,21887,35133,21744,36100, 43958,2893, 37418,22178,42454,43958,45395,36100,43076,36716,34037,29372,16011,34718,35588,41315,3235, 44830,4200, 31703,28037,29875,32857,35133,17235,37418,13145, 33737,36716,12785,15451,37742,31702,32857,12754,39451,12706,36715,24290,36715,40335,39862,37860,39395,32857,35588,22177,31701,38373,25322,40292,37982,35588,39639, 5044, 20985,28927,2152, 24525,26181,6832, 15200,835, 38221,33737,40475,45395,43958,17585,28036,40111,45394,41601,22640,5882, 29874,19545,35587,12187,43076,17322, 35587,22639,16716,37418,24797,20984,25581,21108,25873,30466,37418,34718,15931,34356,16675,27633,44829,41837,44829,12846,9510, 35133,13153,9048, 24796,17274,16276, 34355,31091,39425,18060,34718,18750,37417,13070,31090,14426,35587,35133,7731, 17437,22638,32856,35587,14828,22176,21107,35132,33478,38431,44829,12359,42850,17321, 37417,8326, 40571,2398, 34718,1067, 38216,43076,14168,12642,18616,35132,33478,32408,12092,18953,36715,29873,18455,32856,36099,25872,30465,41230,41315,45394,26518, 36099,28035,36715,30464,28926,34037,26517,33478,20284,36099,18172,33737,37417,19713,33051,40735,34037,16801,37417,35586,12217,37416,21216,35132,20283,37416,22024, 33051,23588,14009,28925,31089,36099,15956,34717,20730,31088,36714,21462,23185,36714,37416,37997,4782, 36714,22489,3236, 24289,18171,34355,32407,21605,36714,29872, 31700,40990,15427,36713,40859,44829,35132,37712,45394,45394,3478, 43076,38748,43957,45393,41601,38431,5877, 2489, 41971,44342,43957,2554, 2782, 7428, 2049, 3452, 40571,1883, 1958, 38575,39711,42637,39841,37416,38136,44828,39554,32406,39526,38495,41064,42115,43075,44342,40255,34717,32405,35586,39176,44828,44342,42115,39176, 41315,40571,34037,31699,16419,34717,41713,451, 1746, 3515, 41145,42850,24524,18749,42454,35586,37830,37930,38273,37735,42636,3320, 5426, 13491,7133, 33736,35586, 38090,41837,44341,42454,44828,4783, 43957,33736,8070, 1626, 36098,26516,43612,40571,44828,41402,44341,42277,33259,37415,4586, 37827,43318,38548,43957,40426,23391, 38406,39862,39081,38725,43956,41401,44341,42850,44827,37415,35585,36713,35131,31087,41837,6001, 12410,42636,41401,42276,41712,43956,41712,40990,37415,38022,36098, 38252,3686, 661, 4838, 39345,13371,4038, 40335,26875,5675, 42115,37415,42849,43075,39692,12714,19318,37414,42114,3577, 38961,33259,25580,44341,26874,2536, 3319, 39424,44340,17005,44340,2339, 192, 7134, 3079, 33736,28924,27239,12459,42114,6599, 37730,38484,38268,3978, 38138,38492,40144,43956,38178,32404,30463,34036,40858, 37847,36098,29371,12193,36713,20871,15450,39711,36713,12680,29370,39554,33050,25321,43956,41145,44827,13875,32856,22488,31086,7447, 44827,7938, 25051,15469,40734, 36098,18682,44827,42276,35131,37414,2615, 19897,37414,32856,33736,35585,32403,2698, 41064,40922,34717,33735,29369,35131,40807,39732,38823,12466,29871,36712,17872, 32402,19467,25320,20729,1536, 12636,43075,13275,35585,903, 5508, 348, 26873,35585,16250,39209,15175,32855,22324,99, 1610, 28474,6982, 34355,40989,43955,15449, 37414,44826,36097,22023,40052,37892,39369,40858,40181,39817,40018,32401,19991,38489,40335,36712,39479,35131,494, 39889,6602, 40082,45393,5509, 6707, 42276,42849, 441, 41145,41712,29870,34355,36712,42849,42636,15678,32400,16223,43075,21886,33050,37413,20394,37413,9170, 9570, 9850, 44826,33735,12206,41971,33050,14722,20870, 31085,35130,35584,33259,38739,8100, 43955,31698,34036,6002, 45393,41970,16875,25871,36712,33735,22487,45393,33477,21215,25579,13007,20983,21106,21105,20184,42636, 41970,37771,39656,39731,41145,38751,21743,36711,32855,37413,37413,37412,38264,42635,38417,38777,41314,43074,36097,35584,39111,15365,17871,34354,38563,42114,41970, 39096,39509,35584,34354,40221,44340,21885,29368,4421, 45392,3078, 1563, 44340,16387,236, 42453,37856,43955,6334, 39233,6600, 4908, 45392,42114,44826,3279, 44339, 6470, 39889,5428, 479, 4474, 4422, 3382, 2669, 3797, 3060, 411, 39345,876, 2697, 42276,44826,6111, 913, 3321, 35130,39262,2513, 920, 3644, 25319,33258,44825, 31697,37412,35584,20282,21104,23587,14065,23586,37412,4710, 34716,31084,33050,13654,35583,20982,36711,14652,30462,3898, 16766,35583,14208,19070,42113,4969, 3277, 41836,39963,7132, 43955,33735,28034,37797,41836,38901,34036,42849,37781,33258,34716,37726,41836,3203, 44825,44339,41970,40570,44825,38734,20183,44339,39170,43611, 40144,27238,23799,24288,36711,13205,1882, 41063,2834, 2641, 42113,41601,33477,35583,33734,36097,34716,39052,36097,18562,20639,12344,5505, 30461,33477,34036,36096, 27237,30460,34354,37412,35583,36711,25578,43318,34035,37411,3278, 43954,43317,39262,34035,36096,35130,19018,17584,30459,37411,37890,44825,41712,38589,37793,40018, 12018,43074,19156,44824,36096,3080, 33477,12660,12158,19712,34354,19402,15703,22022,15909,34035,38008,28923,19236,22175,34716,40989,14271,23390,17436,22021,45392, 40426,33734,45392,19235,22486,31083,24523,44824,18223,24037,14952,20281,20869,32399,34715,14431,37411,39412,20518,8022, 32855,6603, 5880, 36096,33734,34715,37411, 5587, 35130,37410,35582,26872,25577,34715,35582,20868,42275,11684,14935,45391,44824,1386, 1352, 1176, 25318,44824,41601,12264,16874,22174,20077,17507,43954,39496, 43074,44823,12808,7432, 32855,35129,21339,29869,28922,43954,34353,19155,34035,24036,40621,16973,36095,23184,40734,27236,27632,45391,18059,40051,41063,33734,13512, 43611,30458,34715,34714,15839,13959,24287,35129,32398,18615,36710,28033,30457,12106,43317,16086,11172,18120,37410,34353,34714,13232,19401,36710,35582,35582,33733, 33476,31082,41711,7990, 12373,26871,43954,28921,25870,30456,44339,12429,36095,18012,28920,30455,37410,42848,6112, 26515,36710,36710,33476,37891,34353,37410,43611, 22323,43317,5046, 34353,24522,39861,43074,45391,33476,36095,44338,39219,13114,45391,7375, 33476,35129,12039,35129,15556,21884,19804,36709,33475,35581,36095,28473, 7519, 7137, 39600,34352,24521,43317,23585,36094,37409,42275,9945, 19234,36709,35128,31081,36709,34352,25869,22173,28472,28471,40523,35128,16972,34034,9826, 32854, 33049,16674,34352,29868,21742,28470,5782, 44823,12554,34034,32854,35581,22322,43073,36709,23798,36094,25050,16715,14621,33733,34714,33733,36094,34034,35581,26870, 28919,32854,22820,37832,41144,40734,38706,44338,40111,37409,43316,38495,35128,30454,36094,19154,7581, 33258,16971,41314,44338,41230,6335, 34034,44338,16275,15298, 31696,43073,45390,42113,44337,38786,38602,45390,38598,38743,43316,39673,28469,41600,45390,13985,26869,513, 39989,43611,44823,42113,41401,44337,38178,45390,42453, 43316,41401,39251,39411,33475,45389,42275,41600,44823,26514,34033,12483,13899,13684,34714,40675,44337,42848,43073,44822,43610,5191, 38658,43073,43953,42112,25868, 14601,37805,38302,40523,27235,33049,17053,7790, 40426,45389,42275,26868,38983,19317,43316,37900,39495,45389,38967,37409,37409,44337,37408,30453,42635,40017,18454, 28918,33258,42848,36708,19316,35128,41969,17362,27234,40570,44336,20728,22020,36093,24035,12095,43953,30452,39384,25, 2011, 39209,41400,600, 15677,13688,33257, 32397,41504,36708,25867,34352,18815,31080,23584,10020,20867,36708,17320,35581,37408,29867,34713,13224,37408,18561,42848,37408,39913,33257,25317,20393,24034,34713, 36093,41504,35127,33475,12662,41144,34713,28917,34351,35127,35127,40922,40426,42274,32854,1948, 38594,43953,42635,43072,37407,37852,40922,25576,32396,12536,32395, 37737,14157,39479,41314,37963,43072,37957,22172,21883,32853,34351,36093,37774,38203,38917,26513,21741,25316,25315,43610,34033,35127,38578,44822,45389,23797,1722, 975, 6980, 15624,2068, 34351,21604,43953,5878, 27233,36708,26512,17234,20981,34713,37407,40734,36707,15184,40621,26511,18614,39272,41711,2010, 41711,13708,40082, 28468,36093,15623,36092,14587,15468,33733,33732,14827,12866,12486,13173,18560,36707,37407,42847,23389,13914,19544,37407,21740,37406,15199,13591,34351,43610,34350, 25049,44822,36707,33475,35126,34033,33474,37406,35580,34712,13553,32394,43952,11844,15345,36092,10687,41836,13629,36092,6467, 44336,29866,12803,43610,18058,17273, 42274,41711,43315,43952,43072,43952,33257,5111, 43315,44822,40570,44336,10570,45388,41600,40922,21103,37406,37406,39656,45388,43952,36092,15798,34033,28032,15930, 8046, 33049,21214,30451,22637,44336,41144,42847,33474,30450,33732,32853,32853,15448,33257,34350,11611,10596,40220,41969,39816,42453,42274,40858,1959, 43951,42453, 42452,40921,25048,43072,40051,41969,40674,40144,42635,43071,40220,38786,41314,40111,42452,41400,43609,31695,44821,45388,40181,41710,39711,39438,43609,29865,28916, 20182,36091,27631,33256,35126,41835,40674,45388,38754,43951,42452,10264,44821,6471, 43071,42452,44335,41504,42847,37694,41504,41313,44821,37699,6601, 33732,26510, 34032,33474,42634,43951,3516, 7383, 39184,34712,35126,34032,34712,33049,36707,31694,36706,5586, 2722, 35580,1081, 12865,6214, 17537,36091,44335,6830, 42112,3765, 44821,43071,44820,990, 7130, 38759,3643, 5879, 43951,106, 5427, 28031,37405,43609,41063,34350,42634,44820,44335,40381,20392,39673,40220,45387,3944, 41600,44335, 40807,8323, 42634,40181,9798, 24286,37727,41710,38922,43609,43950,41313,43950,41835,40474,38014,4598, 10094,9390, 38774,44334,39310,38748,42847,38479,40674,39963, 38278,43608,42846,42274,45387,44820,37905,43950,39233,38590,44334,39889,43315,44820,41835,45387,39794,43071,43608,44334,4279, 5515, 5274, 43070,10192,34032,41313, 29367,5341, 2137, 40474,42451,40733,34712,32393,16970,41835,10263,7520, 43315,42451,42451,39913,43314,39691,39243,38095,38818,40921,38658,32853,6125, 35580,2029, 41969,3326, 35580,28915,35579,34350,34349,2355, 2725, 2617, 6719, 3802, 4126, 34349,27232,8052, 41834,43608,21338,35126,32852,25575,8832, 10569,10655,34032,9559, 11215,8931, 26180,20727,45387,9862, 24285,5340, 11543,35125,38139,39465,45386,44334,40082,36706,41144,33256,2084, 3691, 41503,6354, 38425,42112,43314,44333,41968, 42451,43950,3801, 39638,41834,40621,45386,43949,40570,9797, 6996, 6491, 1766, 1458, 84, 6232, 3857, 4127, 9695, 10798,9473, 38150,40523,38907,41143,39125,38252, 42450,40733,37834,38346,43070,41400,38584,28467,43070,28914,3905, 6855, 41503,41063,43070,45386,45386,34349,36706,32392,35125,45385,33256,36706,28030,8981, 18327, 33732,18281,42846,44819,40921,42112,39672,35579,22171,42111,43069,38917,44333,34711,38510,38019,28913,40989,41834,43069,43608,41834,41599,40858,39731,40335,45385, 43949,45385,39438,1971, 40334,45385,38880,38998,43949,8734, 36705,34711,38563,42634,44819,40425,850, 45384,43069,41968,40255,41503,44819,43949,39262,42273,44819, 41599,39479,41833,44333,44333,40857,41313,39478,32852,42111,43948,42846,2085, 43069,45384,41062,40857,42273,39691,41599,42633,42846,42845,43068,39840,40806,2672, 43948,40989,44818,40111,40921,43948,42633,40733,39988,45384,42633,39465,40081,41710,44332,40806,43607,39262,38702,41230,39988,38806,37763,45384,38684,39554,42450, 42273,41968,39509,41229,40292,41062,41968,37916,42111,39861,40220,45383,43948,40334,41229,43314,41312,43607,43607,42111,38419,38117,43607,42273,38133,40292,44818, 39437,38259,45383,4659, 41833,45383,45383,40017,40051,41710,40380,40733,42845,45382,42450,41599,38860,38943,38960,40806,43947,39495,45382,39840,44332,43314,45382, 44818,43947,38783,39638,39169,42272,43947,41503,35579,44332,43313,8671, 36705,40017,27231,44818,44332,42845,42845,45382,42450,40380,40988,40621,37405,43606,41400, 41709,38510,43947,45381,44331,42449,43606,41312,42633,44817,44331,43606,42632,40674,41833,39096,44331,39888,44817,40522,40732,41399,44817,44817,44331,24795,42632, 41312,44330,39539,40732,44330,41598,44330,6493, 44816,38320,38648,42272,38251,42632,39935,45381,40425,42632,42631,41967,37405,39988,43606,44816,45381,34031,45381, 41502,39411,38401,38722,38670,38018,42110,42844,45380,38880,39137,43313,43313,42631,39816,39219,40017,45380,42449,41062,41598,45380,40425,43313,42449,44816,43946, 45380,39158,41709,44816,39861,41062,41709,41502,40474,42844,41833,38776,39227,40673,39963,40988,37909,38124,44815,39656,39322,44815,43946,40569,44815,1887, 41709, 41832,41708,43946,45379,2785, 40920,41143,43946,43605,38066,41832,43312,45379,42449,39287,44815,31079,45379,43312,38536,38465,41399,45379,42844,45378,40920,42448, 39672,9241, 41832,547, 44814,877, 42448,906, 38695,39861,39201,40143,39023,42631,42110,41143,44814,43945,40110,40143,43312,43068,43068,41229,41229,40181,40806, 43312,41061,43945,43605,798, 43945,10419,45378,42631,43945,40425,43944,42110,41708,45378,44814,38574,42448,41143,39384,44814,2868, 41967,44813,40805,40522,36705, 45378,40569,39731,43311,45377,43944,44813,39369,42844,41967,40424,40920,39103,44330,38731,41967,38487,45377,4978, 6494, 43605,43311,43068,41708,43311,45377,10568, 40732,45377,9061, 43311,38650,40051,40569,44329,42630,40334,42110,45376,8464, 45376,41966,39638,43944,39286,17128,42843,40988,33256,36705,1171, 42448,42272,40805, 40424,40380,38705,41312,38514,40805,40732,43944,43605,44329,39934,42630,41832,1914, 43067,42447,39816,44329,40180,39988,44813,40081,43067,43943,36091,6231, 42630, 3357, 45376,750, 39860,43310,42630,40522,41311,41399,43943,41311,44329,40474,45376,43310,40219,40522,43067,44813,38217,40180,41311,42843,40620,43310,44328,38843, 42109,38698,40620,39360,41502,37830,43604,44812,41502,38446,42272,41061,43310,44812,42271,40424,42447,45375,43604,45375,44328,44812,40292,44328,45375,45375,9724, 16085,37405,35125,9651, 9710, 37404,35579,40473,33474,43309,41598,10418,16449,31078,43309,41061,45374,45374,19466,33473,15242,24520,45374,23796,36704,34349,30449, 15867,12292,28466,41399,42447,44812,39583,39251,8676, 45374,35578,45373,32391,35125,42271,41142,41501,45373,41311,40473,41228,41228,42447,44811,41398,43309,39395, 39068,39096,43943,43309,43604,41708,41061,43308,40805,42446,42109,21337,45373,35124,38983,39934,39045,40180,40180,41831,40179,38927,44811,39656,39451,41501,39060, 40620,44811,41966,40255,43604,41831,38554,38729,38523,39526,45373,41310,30448,44811,44328,43603,34031,13107,39539,39251,34031,43067,42843,44810,41501,38443,41966, 21461,40334,41966,21460,24033,41142,42843,38695,41707,37885,36704,42109,39987,42842,42109,42629,45372,41142,43943,45372,33731,10872,33731,43942,23388,22019,38978, 42271,41501,40920,41598,39638,40473,43603,44327,44810,1018, 40424,38522,40380,36704,43603,40379,44810,39209,42446,39637,38584,39637,39934,44327,41500,38618,44810, 40620,3421, 41500,43603,2495, 3260, 37782,6997, 39731,41142,38948,40619,40110,43066,39539,42446,41597,43308,39730,43066,42629,38182,38408,44809,41141,39087,39184, 30447,27630,37404,42446,40569,4660, 40423,41965,44327,39437,44327,38294,43602,42108,38664,40619,39004,38583,40521,39169,5683, 41597,3989, 40673,39286,42271,40919, 42842,38384,42842,38421,44809,45372,39118,37844,43942,45372,43942,43942,43941,44809,38435,43602,40521,38262,39102,42629,38722,43941,41965,40731,40568,39193,42270, 40254,38360,43602,39298,43066,39029,41597,45371,39478,45371,43602,41398,40016,43308,43941,41965,39816,38356,38761,38725,39227,38072,40673,40473,44809,42842,41831, 39044,40988,45371,43601,45371,784, 40919,43066,40333,42445,38508,4075, 39840,39286,40919,42841,38264,38420,43941,38103,38378,41310,40179,38243,40673,39962,39815, 38792,38239,40016,39004,43940,38508,43940,43601,4845, 2540, 40333,42629,40731,42445,43065,39583,39934,43065,45370,43940,40423,45370,2418, 44326,37934,37839,44808, 37856,41500,38279,40291,44808,38352,39815,38849,41831,38864,43601,41597,41398,39125,44808,45370,41830,44808,44807,45370,40731,43308,38828,38361,40987,38287,43065, 45369,131, 41965,44326,44807,39962,39539,44326,39888,39637,39583,40987,42270,44326,44807,44807,45369,39478,40254,39815,38721,44806,38105,38620,38293,39465,39755, 44325,44806,40919,45369,39572,43065,41830,45369,41596,40619,42108,43940,39243,38049,43939,39272,44325,39251,38926,42445,41707,40619,39067,38445,43939,40333,45368, 39298,39003,39860,41500,6351, 37706,40731,41228,43064,40568,43307,45368,43307,44806,1581, 43601,41499,40016,39526,43939,39233,45368,40050,38132,38554,42270,39383, 39209,40618,44325,44806,45368,39200,44805,2673, 40804,39691,40379,41499,41228,45367,39672,45367,40672,40568,44325,45367,39465,39553,44324,39888,39776,43064,40568, 41596,39175,40857,39369,38519,41596,39860,43939,42270,41398,38943,36091,44805,39011,38954,38661,39526,44324,38510,38023,44324,39369,39987,38747,45367,39619,40472, 38482,39840,41499,39272,44805,42445,39637,5052, 42628,38440,38214,38770,38023,37936,39987,38443,43938,41830,42444,40987,42628,43600,41397,38486,37823,38739,38634, 38967,41499,41707,36704,36703,37404,34711,41498,9139, 3259, 9181, 9251, 35578,43938,21336,41498,44324,35578,39169,44805,31693,22018,3524, 6854, 7452, 27629,11332, 40254,42444,39815,41498,42841,41498,14190,34711,645, 9796, 8217, 44323,31077,32390,20391,10511,29366,8461, 1048, 2444, 4597, 13889,43600,37404,34348,8392, 43600, 10093,38411,44804,41830,43307,38500,40804,43938,40672,566, 15929,38751,38938,43938,45366,43600,39776,42269,45366,38990,42108,41227,45366,44323,39096,43307,38743, 45366,45365,41596,43599,43937,43937,42444,43599,40567,40379,40179,43599,39411,42841,36703,7550, 14973,7498, 7745, 10871,7396, 38776,36090,14077,15364,37403,8449, 41497,43306,9250, 9080, 39495,8722, 37403,6124, 11767,7680, 7996, 36090,25047,33473,10543,34031,14207,36703,2557, 10041,18222,36703,33731,23583,35578,28465,36090, 33255,33255,17361,26867,31692,33048,28912,36702,29365,31076,6352, 34030,7846, 10870,7739, 10686,32852,22321,41141,37881,43599,4073, 44323,38642,19543,41595,26866, 37403,19400,7512, 21335,37714,35124,42841,11121,36090,39933,38911,45365,39691,14180,14568,43598,43306,7492, 41964,41595,43937,37403,8071, 24794,7725, 43306,37974, 43598,39102,38063,41141,5051, 4165, 31691,37996,37785,37687,1554, 38255,6009, 40618,38371,39286,40730,39672,39730,38433,38870,42444,39600,38046,3858, 35577,42269, 41060,37402,34348,45365,8677, 7391, 10567,36089,43598,33731,37402,10956,35577,24793,22170,13850,4330, 24284,15467,35124,29864,9200, 13003,18119,43064,33730,9358, 42840,7880, 8802, 38682,28911,36702,34348,186, 37402,34030,36089,43598,35124,19632,37402,33048,11276,11077,21213,34710,31690,45365,29364,17158,41964,43064,40918, 4594, 43937,45364,45364,35123,36089,38954,31075,16907,38445,42628,2588, 42840,9874, 36702,43306,36702,11076,37401,6353, 10911,9746, 8124, 7194, 38049,5516, 1711, 42628,294, 5896, 43063,40379,34710,10019,11610,43063,38022,43597,39583,43597,10247,8374, 40016,43597,40804,4979, 35123,9023, 16041,41060,1063, 35123,4041, 11467, 45364,15224,11967,39424,2183, 4433, 42840,45364,8748, 19896,7260, 2724, 19711,18681,12166,13521,37401,12633,26179,14916,32389,35577,32388,42840,10542,9389, 9344, 8227, 42443,8515, 10797,10595,32387,7775, 29363,11331,19542,11609,10955,36089,10832,9135, 10262,39572,9694, 9086, 40672,37401,10726,8080, 14809,34710,40521,39059, 16873,31689,21882,43597,3027, 19153,45363,36088,34030,36088,33255,45363,41310,235, 5599, 4595, 19541,18680,29863,35577,37845,44323,43596,42269,43063,43305,40804, 31074,43596,7386, 11466,18170,9650, 22819,37401,6233, 326, 38514,4539, 23387,41829,33473,40987,43305,42108,24283,43596,19803,10796,11075,36088,43063,11922,7897, 1619, 43062,7623, 40730,7844, 44322,7786, 11397,7814, 16765,7142, 44322,3170, 755, 2558, 4074, 13844,4329, 44322,8001, 42839,8859, 8287, 40050,3168, 16125,11683, 9678, 35576,7971, 38818,1801, 9849, 41227,3207, 40803,40730,8475, 39450,8574, 10007,33730,32852,10831,30446,36088,22636,23582,35576,8218, 40986,11465,43596,31688, 36087,10910,9509, 42443,34348,9202, 9060, 11171,8663, 9987, 14784,10229,8186, 43305,10369,10311,10165,8686, 34030,9955, 24519,43062,11682,35123,8207, 43062,36701, 24518,8472, 44804,11542,22017,9535, 8711, 41964,44804,8555, 36701,10954,34347,34347,10466,38932,41829,44804,43062,35122,21603,43061,45363,35122,15893,10654,8988, 35122,10909,9431, 27628,9032, 35122,37400,36087,35576,34347,34347,28029,42107,34710,32851,14043,32386,35576,19540,15314,25866,23795,27230,1137, 26509,30445,34346, 11766,33048,21739,720, 2816, 3169, 1436, 40521,30444,9148, 11843,19315,34709,17773,34029,31073,17822,13231,20280,33730,29862,18679,34346,35575,7352, 37400,36087, 42627,2242, 6492, 16274,45363,18057,25046,26865,8055, 34709,22818,33255,22635,33730,36087,7908, 13893,28910,32385,7820, 36086,43936,7629, 36086,40730,34346,32384, 7723, 37400,40567,30443,23581,34709,9112, 196, 3238, 33048,7165, 38495,1537, 6230, 181, 35121,16222,37400,36086,42627,9883, 43936,45362,34029,18613,36701,35575, 37399,1802, 4596, 34709,7361, 44322,30442,44803,43061,35121,18559,36086,39730,39411,7288, 43061,40803,45362,3612, 29861,5195, 10990,44803,2376, 3803, 2496, 6998, 9276, 11032,11170,8747, 15486,34708,34708,7717, 42269,9723, 10310,37399,1235, 40472,2277, 4538, 45362,37912,38491,8199, 43305,9459, 19631,33254,9649, 11074,10541, 28909,11275,9577, 8331, 37399,36085,11120,8871, 8759, 35121,19465,10989,9166, 8340, 11921,8498, 8598, 44321,8790, 36085,24792,38747,31687,34346,34708,7932, 45362, 1695, 5826, 33473,41497,43595,1116, 5368, 5631, 7039, 44321,4553, 43595,45361,43595,43061,38112,43595,22994,42443,7258, 43594,17127,34029,7961, 15156,31686,13348, 3815, 12367,44803,7256, 29362,11765,6751, 20866,31072,7171, 42839,20181,13069,7667, 36701,34345,42268,35121,29860,43594,43594,25045,24517,39538,34029,31685,44321, 21602,4741, 3367, 4133, 44321,40081,45361,36085,16673,36700,38394,9161, 11541,34708,37399,34028,10908,11274,10191,9609, 10126,11842,10335,10830,8682, 9523, 10484, 10290,5539, 34028,24032,12034,27627,45361,17052,3094, 8576, 4446, 36085,10368,11608,23580,33472,36700,9677, 24791,9626, 8402, 8147, 39067,9944, 36700,11330,26508, 12202,33047,8418, 40219,2225, 44803,34707,12189,45361,15641,22993,34028,45360,28464,33472,38932,41310,40472,39059,37398,25865,36700,30441,26864,19233,33047,7571, 9882, 8130, 34028,37398,37398,8136, 35575,7421, 7217, 42443,4053, 22634,37398,8807, 43060,11073,1469, 34707,43304,9779, 37968,40472,44802,40050,21881,1975, 39035, 5466, 11681,7493, 30440,13552,7884, 7301, 8116, 36084,8115, 37397,8902, 10907,9669, 44320,7858, 7945, 7180, 38967,4004, 1448, 44320,41595,36699,33047,32851,10215, 9426, 35575,19990,41964,33472,34707,7579, 7228, 41227,6261, 19539,16764,10465,40803,10228,11764,8167, 38442,1050, 36084,8355, 41060,11072,22817,15382,45360,41309, 415, 35574,11763,45360,43936,42268,7266, 1198, 10795,11762,36699,43060,14030,5132, 1985, 2876, 2955, 43304,14405,10953,34707,31684,9943, 43594,43060,22016,33729, 42839,8639, 40918,44802,42839,8696, 41060,38363,2899, 15283,38382,35574,38849,3463, 36699,36699,33254,43593,23794,1250, 1289, 1376, 42627,40986,45360,44320,6157, 3706, 33047,41309,45359,42838,43936,41497,41141,41829,32383,16578,8300, 39310,40378,8698, 33729,42838,7289, 40110,6385, 1496, 3814, 1771, 25864,12205,34706,17870, 37397,6386, 17090,42268,25574,4554, 26507,7381, 45359,43304,7238, 39553,2077, 4804, 43060,10334,10246,34706,37397,31071,1197, 4225, 6262, 44320,31070,18496,44802, 11920,36084,37397,39076,41397,40803,1277, 22816,33729,39464,41227,41497,34345,44319,1729, 2346, 5827, 929, 6156, 3184, 2326, 43304,6384, 26178,36084,42838,8203, 10040,11169,9586, 8305, 8460, 40081,8288, 10906,37396,7633, 11680,34345,39437,14670,34027,31069,43935,38126,35120,33254,8724, 33472,35574,39333,37396,3242, 42442, 45359,40471,43935,10289,8471, 8772, 37396,36083,8533, 7192, 39118,5004, 10566,5632, 4492, 36698,5294, 945, 7270, 10510,9370, 9600, 5071, 35120,33471,42442,7669, 9881, 11540,7417, 38739,34027,42107,39730,43593,10794,4347, 8605, 11396,2900, 34027,36698,34027,37396,20980,15241,17678,27626,35574,7143, 38907,4292, 6260, 2252, 10988,7455, 7742, 2875, 4678, 4677, 34026,9880, 11273,5538, 7819, 35573,1750, 6752, 9356, 7392, 45359,7236, 40219,4491, 5072, 32382,12497,17157,34706,11464,37395, 11957,40378,4346, 33471,36083,12153,16714,21601,9986, 36698,11919,37876,38318,13297,26177,38352,40802,38199,45358,43593,39553,45358,1613, 2450, 7042, 36083,37879, 40857,43059,807, 3464, 2449, 38224,45358,41963,38205,10793,9685, 8735, 34345,28463,10227,42107,19895,40567,11329,34344,43059,15093,35573,42627,24516,34706,13503, 44802,40423,25573,10952,9839, 37395,4864, 36698,7293, 10685,7944, 35120,7810, 33471,7196, 44319,7469, 7043, 37395,36697,44801,11168,10141,10190,11959,3096, 42838, 45358,6155, 11841,8726, 45357,11272,3095, 34344,34344,9795, 9504, 3624, 22633,35120,33046,37395,19710,36697,28908,20, 41226,2286, 338, 27229,44801,33254,8036, 43303,7309, 13464,15485,31683,7169, 7852, 8992, 8597, 11607,10189,7150, 42268,3733, 4555, 9615, 8262, 23183,18280,9372, 19630,7518, 40333,22992,7179, 40729,3067, 7893, 8658, 16304,10417,8096, 10951,35119,18391,45357,7777, 44319,18558,3120, 40918,34026,21334,38547,39125,39081,43059,40918,40143,34705,38311,43303,44319,42626, 44318,11606,37394,36083,33046,42107,9393, 37394,19894,28462,8369, 42106,11539,23579,8190, 43303,9552, 10261,41829,5369, 12683,33046,31682,39636,41707,40378,7040, 24282,35573,3917, 43593,44318,7193, 8497, 8276, 43935,33253,33046,12542,34344,37394,4224, 31681,35119,25863,43059,39933,13520,8059, 40802,45357,37866,39464,39310, 39285,43592,38922,38428,38192,39383,40729,9096, 35119,40567,43058,9954, 8205, 44318,15989,41963,28907,7904, 8210, 43592,30439,35119,24515,2954, 42267,40802,40050, 4937, 37394,41828,40917,28461,22815,733, 41828,43592,40729,3585, 36697,40566,44318,6259, 45357,42442,39755,35118,11395,10621,11394,44801,7864, 9425, 8230, 39395, 7395, 38287,8940, 10829,9921, 9336, 8918, 22485,34026,8193, 40378,3813, 11463,4490, 10540,9022, 42626,9898, 11761,8989, 7371, 9942, 7423, 42626,35573,3068, 43935, 44801,42267,44317,3918, 34026,37918,38157,1393, 43934,40917,38224,38943,42106,39243,37393,40377,40986,36082,8317, 6899, 2627, 34705,9302, 31680,35118,32851,886, 42442,11462,37393,130, 41828,4805, 42837,29361,5295, 10684,44800,38260,22169,43592,4742, 40566,28906,24514,7041, 11679,43303,41309,40254,44317,37393,44317,33045, 33045,520, 5224, 6387, 18952,34343,35118,6388, 34025,28905,35572,37393,21880,24513,36697,37392,34705,37392,41963,37392,38876,45356,33729,36696,40377,40917,38048, 39571,22320,43302,37687,3565, 3867, 11328,2287, 33471,571, 43591,41140,40377,39226,40332,38624,41595,7197, 42626,2657, 9116, 8776, 28904,21600,36696,1046, 37392, 15753,32851,10309,38907,995, 45356,40672,6154, 5887, 44800,36082,29360,31679,37391,36082,44800,6987, 9879, 34343,35118,10068,10018,37391,39271,43058,10594,34705, 20638,33253,22632,33253,30438,31678,33728,23793,35572,37391,20865,35572,35572,35571,31677,44800,36082,40471,40618,36081,21459,32381,36696,45356,31676,35117,36696, 33470,16763,19399,17233,5431, 14172,31068,35117,2296, 11167,44799,37391,34343,28460,10905,36695,18814,33045,34025,13051,42625,9053, 9122, 34025,34025,4970, 34704, 43934,37390,9693, 35571,36695,36695,35117,34704,34343,34342,34342,27228,34342,36695,25572,42267,11461,41226,36694,25044,34342,10725,10125,33728,33045,30437,21458, 31675,22631,36694,34704,37390,33253,35117,36081,24512,36694,29859,36081,36694,11760,33728,33728,36693,26506,34024,36693,37390,35571,34341,36081,35116,37390,37389, 11538,10792,43302,34704,35116,29858,40377,45356,11327,34341,35571,37389,34024,30436,26863,29857,31674,45355,34703,9479, 2812, 11326,28903,37389,14682,19152,36080, 26176,33470,26862,20180,19709,33252,36693,32850,35570,31673,36693,22319,37389,32380,35116,32850,22015,36692,44317,36080,35570,36080,10006,36080,36079,9704, 9703, 10987,33470,11678,9838, 12609,39359,34703,36692,11840,26175,1148, 1795, 21738,23578,36079,11393,41828,32379,37388,36079,33252,34341,28459,34341,1353, 34340,21102, 29359,35116,20279,34340,33044,35570,43302,33727,36079,36692,29358,13161,32850,34340,36078,27625,43934,17970,5430, 23386,33727,37388,23182,36692,36078,33470,34703, 41140,8514, 5677, 45355,43934,33727,22991,9969, 6224, 22318,36691,43058,1165, 36078,36078,35570,37388,21599,25314,40471,7843, 11271,36691,37388,35115,8154, 39017, 4477, 11071,44316,44799,10367,21101,37387,37387,8545, 9745, 44316,24281,11605,8661, 8884, 10791,39776,11460,9589, 7354, 43933,8503, 27624,10683,9941, 28028,42267, 43591,29357,33469,23577,33727,11070,11604,10565,37387,36691,10509,41496,22630,30435,16872,32378,37387,43058,18118,9383, 11918,11325,9635, 9330, 18221,35569,36077, 20979,36691,35569,14874,37386,42441,10164,9770, 9209, 11839,25313,26174,36690,36690,31067,35569,36690,34703,10950,10790,9011, 36690,33044,10005,35115,43933,34702, 32377,33469,8943, 24031,11603,3415, 11759,10724,35115,39087,23181,34340,34702,15640,35115,31672,37386,20179,33044,36077,9551, 36689,34024,35114,10442,34702,37386, 35114,34702,34024,36077,18326,37386,23576,36077,17723,37385,35114,36689,36689,41226,45355,746, 41827,6341, 34701,35569,8584, 39987,11270,8975, 11166,11758,19232, 42625,10226,33469,10188,8771, 11459,10653,11458,10333,11838,8202, 40566,10366,9038, 10652,9106, 8952, 8177, 9794, 11602,9909, 36076,11031,8927, 8223, 9940, 8212, 41594,3981, 10004,10214,10758,8333, 39150,2441, 9035, 10508,45355,17272,33252,36689,44799,10723,4427, 8435, 44799,35114,34023,10152,8917, 42441,12968,35568,36688, 34023,43933,26505,26504,35568,13430,36688,36688,12773,43933,17969,22317,33469,36688,33252,35568,29856,34701,18678,34701,31066,18117,18325,34339,45354,34339,35568, 37385,24790,41963,24789,25312,34339,656, 35113,8409, 38698,26173,43302,42837,11324,11537,8935, 37385,8473, 9793, 10507,10394,10288,16713,21598,19398,37385,43591, 35113,35113,29855,43301,30434,35113,43932,10441,42837,5787, 44798,3849, 26503,36076,29356,37384,36687,33044,30433,32376,36076,34701,18056,15484,43591,25571,42837, 26172,35567,33043,31671,13445,13888,18612,33726,32850,33251,25311,35567,29854,14020,35112,14493,36076,42625,33251,45354,33726,36075,43590,35567,45354,9939, 15363, 33726,15198,25570,29355,23792,35567,31065,34700,15330,15211,15080,31670,15079,36075,18677,43932,14545,11677,34700,14797,22484,9625, 13337,10757,7982, 13469,14600, 15197,28458,34700,37713,1270, 40671,39794,37861,4476, 37912,43590,45354,37384,35566,20726,24511,19464,35112,10151,28027,32849,20076,30432,14934,20278,33468,34023, 20637,37384,41962,45353,26502,21597,41962,36075,27227,35566,35566,36687,16332,31064,33043,25862,36075,10039,17677,11165,36074,17004,36687,37384,25043,34339,11917, 33468,36687,36074,34700,17630,8736, 25569,36686,16249,34023,34699,7200, 42106,6340, 11030,25861,37383,20277,34338,36686,4713, 37383,21100,37383,34022,31669,25310, 10067,33468,34338,16248,18055,34699,16938,10869,10416,15426,26861,31668,36686,12246,44798,32849,17676,36074,21737,12138,8500, 8587, 28457,23180,34699,27226,42266, 36686,18324,30431,13874,33251,26171,34699,15838,8208, 38606,2204, 10415,20390,34698,42106,37383,27623,8670, 36685,11119,10058,15044,40917,34338,24510,23179,19151, 23575,32375,7459, 11392,26170,42836,37382,34698,34698,24030,24509,37382,34698,43301,12473,36685,35566,15223,17319,36685,29853,35112,32849,37382,36074,35565,35112, 20864,7384, 44316,10756,32849,42625,11029,11391,7601, 38906,33726,24029,35111,44798,8534, 7654, 20389,35111,37382,36073,34022,36685,35565,8123, 1234, 11118,37381, 10440,33251,33043,42836,6119, 32848,34697,9908, 36073,26860,34022,32374,17474,34697,34697,34697,33250,12084,40729,42266,41594,24280,37381,21099,25042,34696,17156, 28902,28026,7158, 21736,13104,43932,36073,33043,21596,34022,7802, 17869,7431, 39052,4533, 44798,5048, 36073,43301,32848,3281, 28901,41827,7406, 44797,37381,33250, 7951, 2069, 1764, 2464, 21098,8923, 9953, 10163,14236,43301,45353,6481, 30430,37381,20863,20178,20177,19314,26169,33250,30429,28456,36072,32373,35565,25041,37826, 11390,37380,22014,37380,34696,29852,18220,36684,35111,33725,33250,11214,34021,32848,26859,8740, 38842,36684,37380,37380,34338,17722,27225,33468,7639, 6986, 13166, 23385,5888, 21595,21594,13463,42441,4273, 12591,8653, 28025,9288, 17536,7449, 36684,32848,16151,25860,18279,19629,35565,22990,22989,11028,36684,35111,30428,21457, 37379,35110,9458, 44316,9472, 36683,37379,31063,35564,19150,35564,29354,20388,36072,33042,34021,36072,18748,12988,37379,29851,16418,20517,31667,24279,4123, 37379, 44315,17629,36072,36683,32847,34337,34696,15702,38869,31062,26168,18951,35564,33249,36683,25309,28455,33042,36071,22988,39636,28454,27622,11269,11916,7921, 35110, 45353,45353,7164, 38801,1960, 43932,40179,37378,44315,44797,12651,41962,1765, 44315,40566,41496,13644,41827,35110,45352,43300,43590,13092,16010,2354, 2835, 45352, 36683,11757,44315,42624,33249,36682,37378,23384,11676,36071,33249,36071,35110,13426,45352,28453,32847,39023,42266,36682,34337,40856,44797,34696,33467,18116,40916, 44314,44797,9457, 34337,10506,9873, 24028,34695,30427,28452,17435,35109,42105,37903,44796,44796,44796,45352,43057,38070,1811, 40291,38259,30426,45351,38613,44796, 44795,38205,36071,12781,16512,41140,39933,39035,37795,38540,32847,44795,38524,38116,39208,45351,39671,38228,41496,42441,44795,38544,39285,4161, 37756,43931,15581, 36682,36070,35564,15701,16937,35563,34021,18495,31061,37378,19397,33467,36070,34337,37378,26858,10287,4426, 37377,18950,33042,10949,33042,28451,33249,10286,29353, 37377,34021,7879, 36070,43300,21593,17921,36682,18390,34336,36681,33041,43590,25308,3416, 5786, 5338, 33041,34695,28900,26167,33725,16969,36070,19017,36681,34020, 30425,36069,29850,31060,30424,17920,10260,36681,27224,13245,34020,24027,25307,34695,37740,5337, 6482, 29352,37377,3850, 35109,19069,37771,22629,19396,36, 40110, 3204, 4071, 27621,13913,36681,37377,39776,37884,26166,39962,16831,45351,36069,34336,16762,4655, 4971, 35563,2863, 37376,36680,33248,17721,33041,38197,31059,33725, 16386,33041,43057,42105,44314,39986,39912,13999,3480, 34336,41309,44795,35563,37376,37376,8259, 40376,12943,34695,24026,10365,9861, 9111, 8704, 10092,10620,20516, 25306,8165, 44314,10789,13502,44794,26501,37376,11837,24508,15580,35109,19068,13849,43589,26857,36680,31666,18323,11164,10828,5886, 33248,34694,14990,27620,33248, 37375,37375,3848, 34020,23574,6836, 42836,44794,36069,26856,34694,36680,30423,18747,16906,28024,11457,15413,22814,13474,45351,34336,27619,26500,36069,20978,24788, 21592,36068,37717,44314,42836,37846,34335,28450,30422,14264,39111,44794,40471,28449,34335,19893,33725,34694,41962,38409,38681,10308,16871,32847,38101,36068,35109, 34335,34694,11984,39261,1587, 44794,18011,2514, 30421,1636, 31665,1885, 28899,6004, 36680,2962, 33040,16273,36679,33248,15043,2400, 35108,35563,41059,22987,44313, 24025,28898,39509,45350,44313,41397,1121, 18389,22013,31664,23573,34335,29351,29350,34020,33040,18876,22986,25859,34019,35108,28448,12756,34334,24278,34019,37375, 33467,44313,43300,43057,33247,18875,18278,13734,45350,42835,20176,20515,20387,25040,35108,35562,33247,33467,30420,27618,37375,24787,26165,19708,34693,36068,36679, 42266,45350,7958, 43589,33724,35562,41706,16331,2836, 43589,40015,35108,41140,9228, 35107,45350,36679,12428,44793,27223,20977,33247,37374,36068,15362,26164,35562, 38761,7403, 43300,33247,33724,20276,28897,30419,37374,17089,36067,12780,28023,22483,17193,37374,12930,37374,17155,15210,35107,16417,27617,34334,34334,35562,37373, 16936,31058,31663,27222,34019,8743, 11601,9878, 3941, 35561,20636,30418,25858,29349,19538,26499,35107,36679,13558,15988,9255, 34334,36678,33466,34693,31662,6120, 19463,36678,37810,44793,35561,36067,33246,37998,27221,20075,37373,25568,39424,1866, 37373,42265,37993,18746,5112, 34019,33466,1523, 25567,40618,26855,17720,25566, 37373,11163,43057,33246,34018,31661,30417,34333,33246,37901,40728,14721,28896,16672,3082, 44793,2070, 33724,41706,43589,36678,24786,22316,28447,33466,27616,336, 4272, 24024,11389,44793,26163,18874,33724,35561,24507,22012,17271,42440,18494,390, 44792,2274, 1700, 3766, 5113, 33040,31057,43588,40291,25857,28446,44792,4039, 11268,16870,34333,11388,10082,36678,32372,2642, 3354, 1155, 3690, 3689, 34333,35107,6121, 34333,28022,34018,33466,33723,41496,33040,34693,33246,24785,27615,10986, 36067,36067,35106,37372,35561,25305,20862,17968,36066,17967,36066,31660,35560,28895,34332,36677,37372,25565,29849,36677,31056,6483, 33465,33465,37372,27220,24506, 12346,45349,37372,36677,30416,25564,35106,37371,25563,37371,36066,22482,33039,23178,22985,34693,36066,37371,22481,33245,33039,44313,33245,31055,37371,29348,33039, 30415,28894,34332,37370,44312,37370,28893,44792,28021,34018,34692,33465,34332,24023,27614,34692,26162,37370,36677,12631,34692,36065,16330,26854,33039,37370,32371, 16416,15608,24505,33465,2050, 18322,36676,34332,36065,36676,35560,34692,33038,36676,36065,35106,36065,23383,33245,4656, 33038,29848,24277,41495,24504,34018,37369, 23382,35560,29347,29346,17318,30414,31054,25304,16448,22168,34331,37369,33038,31659,34331,36064,35560,41594,33038,32846,33245,39814,44312,34017,45349,44792,31053, 33723,33464,28020,34017,37369,33464,34331,39508,44791,45349,34691,33723,34017,37369,33244,12630,31658,32370,36676,41827,27613,31052,22315,28892,34017,28445,27219, 29847,20275,38378,43931,41139,43931,40219,21735,42105,38197,37368,31657,35559,36064,32846,36675,43299,21097,18493,33244,37729,37368,32846,31656,24503,2919, 1646, 44791,44312,35559,836, 36675,36675,36064,45349,38036,43931,31655,117, 5271, 18745,43588,18557,1663, 27218,22628,35106,35559,36064,34691,35559,21096,18492,39839, 42440,30413,39888,39095,44791,39775,44312,28891,36063,29345,29846,42440,34691,37910,43930,35558,38119,43299,3138, 40916,38428,42265,39887,39359,35558,35105,36063, 34016,33464,15282,35558,23791,23177,17919,33723,20386,38869,39250,14303,43056,16040,35105,22480,37686,44791,45348,43056,41308,37882,40565,38134,42835,12816,35105, 14851,37810,37749,12519,45348,36675,40728,43056,43588,6339, 14710,15622,28019,36674,6837, 26161,36674,36063,20274,22984,13618,33722,43056,36063,37368,36062,18453, 35558,6338, 31654,24022,37368,42835,43055,36062,36674,35105,12603,30412,35104,34331,16968,36674,36673,37367,34691,31051,37367,35104,29845,34016,36062,36673,41059, 37367,657, 36062,37367,31050,13642,16480,34690,27217,33037,34016,34016,33244,36061,29844,13567,35104,33244,36673,21456,34690,23381,32369,35104,33243,30411,17270, 16415,37366,36061,31049,27216,33722,33037,26160,26853,37366,19802,28444,28443,31653,15483,13873,36061,33037,33722,27215,35557,31652,37366,15676,34015,35103,33037, 32846,25039,27612,33722,3282, 39933,41594,34330,4425, 4842, 4843, 37366,34690,22011,36673,37365,33721,14474,30410,36672,37365,33036,34015,34015,32368,35103,36672, 36672,35103,33036,21734,34690,17473,28442,32367,35557,23572,36672,36671,29843,35103,41593,25856,34015,37365,4429, 34689,37365,43588,35557,37364,28441,31048,25855, 900, 25854,35557,31651,29842,35102,33243,32366,23380,44790,19892,28890,36671,37364,3385, 34689,33721,33036,36061,41706,44311,41308,2671, 5270, 16605,15000,17232, 14690,24021,19989,37364,44790,35556,33243,42440,34689,31047,21591,23571,45348,22314,35556,35556,37364,14933,34689,36671,36671,36060,36670,30409,36670,37363,35102, 32845,36670,31650,24784,32365,37363,41226,34330,34688,4428, 38731,38838,41308,33036,8095, 37363,28440,9676, 24783,2417, 34688,36060,16447,34688,33035,31046,34014, 36060,45348,27214,19801,23176,11387,30408,7919, 36060,33243,33242,35556,36670,36669,33721,31045,35555,39932,36669,25562,36059,37363,36669,17088,37362,36059,33464, 33721,34330,36059,43930,36669,18219,33242,35102,21879,33463,37362,17395,35555,28018,34688,42624,3255, 35555,33035,35555,33720,33463,36668,33463,37362,33463,34014, 33720,36059,34330,36668,35102,23570,27213,31044,32364,36668,34687,26498,35554,45347,34687,35101,34014,35554,44790,10213,23379,34687,20175,36668,15579,33462,16869, 21212,36667,37362,36667,28017,36058,36058,32845,37361,36058,39814,45347,44790,17535,18556,37361,29344,19891,33720,14100,25038,43930,45347,34329,15892,33720,29841, 33719,36667,33462,34329,42265,19890,36058,37361,15240,35554,35554,36667,44311,25853,44311,33242,36057,28016,33719,37361,34329,35553,33719,16329,36057,13415,34687, 35553,35101,22167,8114, 24276,45347,26497,34329,37360,22313,36057,35101,19800,5788, 36666,12435,39754,45346,34014,29343,35553,34013,35101,16868,17087,36666,37360, 34686,34328,31649,34686,36666,32363,28015,37360,18277,19988,35553,14258,12927,33719,31043,15607,34328,41706,32362,43587,29342,25037,20273,32361,36666,44789,33242, 33241,35100,14404,28889,33462,43587,34013,34686,24020,28014,23175,32360,43930,31042,31041,31040,35552,35100,29840,34328,36665,34686,34685,37360,35100,36665,23790, 29839,34685,28013,16577,41308,43929,34328,36057,31039,35552,34013,23569,24782,44789,17360,35100,28888,35099,36056,35099,28439,32845,42624,35099,33241,36665,19231, 33035,32845,25561,19889,29838,15512,35099,22983,35552,33241,34685,29837,29341,35098,35552,22627,28012,37359,43299,33241,43929,33718,7679, 34327,11915,28887,43587, 28886,11069,34685,43929,8521, 9301, 9769, 11675,33240,10651,8334, 40802,33240,8455, 10003,8618, 8354, 6337, 8426, 39383,8476, 11914,8109, 36056,7502, 8489, 9897, 44311,35098,35551,36665,37359,29340,29339,37359,29836,36056,17868,40801,25303,36664,34684,33462,37359,43587,28438,32359,27611,32844,19987,35098,28011,36664,20074, 15578,24019,25852,37358,35551,26496,37358,23568,36056,33240,32844,5930, 26495,25302,36664,19230,41397,34684,35551,19537,27212,33035,33034,34684,45346,40565,36055, 12914,15928,14206,35098,34684,32844,36055,36055,20725,21590,20385,36664,34327,36663,21589,36663,34013,37358,21588,36055,37358,35551,13595,3866, 37357,4344, 41826, 10539,8542, 11117,11386,11116,9138, 36054,9768, 34012,31648,35097,36663,5465, 1300, 42624,34327,1299, 1375, 43586,45346,33461,36663,29338,32358,30407,39839,36054, 36054,20635,33034,36662,21095,36054,36662,34012,35097,35550,42105,34327,33718,29337,6151, 19986,32844,25851,33034,34683,30406,33461,30405,35550,36662,32357,36662, 36053,40728,25301,33718,19707,16636,32356,25036,35097,36661,36661,35550,22813,33240,32843,33239,30404,20073,7037, 16547,18452,19888,13039,37357,33034,36661,16604, 34683,36661,37357,36660,27211,33718,35097,19067,28437,36053,29336,35550,29335,25850,36660,35096,25849,37357,37356,31038,35549,22982,33033,36660,27210,25560,33033, 36660,36053,21455,31647,20514,29334,37356,10017,16414,41826,3216, 31646,23378,23567,43586,34012,36659,33461,43929,36053,42265,34326,36659,39538,2523, 36659,15252, 17192,36052,34683,35096,36659,30403,15466,15482,21454,36658,32355,37356,34012,33461,32354,36658,36658,28885,36052,35549,37356,24781,37355,7372, 43055,33717,19395, 7751, 13347,35549,42835,36658,37355,20861,42834,36657,33460,35549,36657,32843,36052,19536,35548,34326,27209,36657,20272,36657,31037,34011,24502,37355,35096,34326, 36052,37355,17394,35548,43928,14501,33033,37354,36656,23789,45346,35548,36051,36051,45345,21587,16576,44310,40671,34683,35548,19016,33239,35096,36051,21586,18054, 21733,33033,32353,33460,9397, 7263, 38842,9837, 25300,16800,9266, 33460,10464,10505,39508,11115,9558, 43055,9540, 44789,33239,18321,26494,9576, 28010,44789,45345, 45345,6749, 6383, 43586,34011,32843,41495,27610,33032,19628,21094,34011,22479,27609,33717,44310,36656,35547,15381,34682,38683,40423,33032,15908,43055,39986,31036, 26493,2524, 34326,36656,26492,34682,28436,37354,16104,38256,44788,39242,21211,34682,44788,45345,44310,36656,43586,37354,26491,39860,16761,35095,2285, 33717,35547, 35547,17583,33032,17772,26159,32843,39424,2224, 33032,36655,41961,16760,34325,22812,33031,39495,40291,25299,34682,44788,41225,42439,36655,42834,34011,8737, 40178, 20271,14492,20860,37354,10985,24501,30402,36051,35547,33239,45344,36050,9985, 9692, 33717,38241,41139,13312,41826,39208,36655,44788,33031,34325,40218,19799,36050, 36655,33460,18169,32352,33459,35546,37353,24500,33238,19394,21732,44310,11913,14689,28009,478, 36050,44309,43299,34681,9021, 43928,35546,11456,34325,13594,36050, 38613,43054,5222, 44787,28008,43054,35095,36049,36654,36049,514, 42623,43928,13093,36654,44309,8794, 26490,39285,23788,36654,22166,23377,23787,37798,38754,41705, 5367, 1516, 40986,42834,11600,5464, 36654,33238,18676,36653,19149,41705,32351,27608,35546,15752,25035,33031,34325,28435,35546,15820,35095,36653,32842,14908,35545, 35545,20634,28007,18949,36653,18555,32842,25034,30401,26158,21333,34681,45344,23566,17771,35095,17191,34324,34681,19229,33238,32350,33459,35545,36653,26489,37353, 36652,19148,35545,26157,37353,6257, 37353,35094,41495,44309,36049,4388, 22811,43928,36652,40422,36652,41826,26852,35094,33716,34324,23174,36049,41961,33238,37352, 26851,36652,2680, 19393,35544,35094,24275,45344,36651,29333,11455,16272,22810,9932, 35094,34681,30400,38121,33031,36651,37352,17126,10124,20859,11756,41495,28434, 16546,33716,33237,34324,17434,13357,35544,33030,33716,37352,45344,36651,36651,36048,32842,17269,11068,35093,12864,11536,45343,8963, 35093,11385,11599,42834,139, 4863, 19706,9126, 9564, 44787,4803, 38522,4936, 18276,36048,40856,11114,10948,19313,34324,8919, 11267,3182, 10984,5364, 1141, 5366, 17154,34680,40049,35093,32349, 35093,37352,33459,27607,35544,17918,16759,33716,42264,29835,13002,40049,33459,37351,32842,44309,32841,20174,26850,7539, 32841,26849,31645,33458,24274,21878,20270, 34010,34010,35544,25559,42104,12381,43298,5293, 22478,32841,44308,24780,12218,27208,32348,28006,33715,36048,24273,37351,34323,34323,42264,32347,33458,20513,26488, 32346,39962,32841,38214,36048,44787,36650,43298,3584, 19798,33237,37351,41705,31035,32840,34010,43054,31034,34010,20072,35092,14607,18948,33458,34009,23786,34323, 11836,22312,7586, 16221,34680,33458,20633,33030,19228,40801,26487,33457,33715,32345,18218,38529,17125,37351,23173,28005,21332,36047,34323,37350,17086,26486,19535, 24272,37350,41961,33457,33237,36650,40218,31644,22477,28433,9446, 45343,8590, 30399,32344,12764,7369, 2033, 42833,1140, 5537, 25558,30398,33715,28432,13113,18168, 34322,21210,37350,25298,33030,38392,33030,17582,37350,17719,13330,18010,35092,31643,44308,43054,33715,35543,24779,35543,32840,18873,38445,39423,42833,13626,39710, 38900,43585,43927,26848,6041, 43298,44787,40332,43053,35092,7763, 33457,35543,36047,24778,33029,16603,35092,30397,13867,36650,12566,39017,36047,36047,40422,44308, 44786,43927,31033,36046,37349,20976,25297,12124,40801,10619,11067,40143,36650,9778, 14892,23565,12433,20173,15297,13157,35543,34322,36649,34680,26485,15854,35542, 19147,37349,37349,13368,43927,16799,37349,23785,34680,28004,36046,12815,27606,15078,36649,36046,26484,31032,33029,37348,35542,33029,36046,32840,13306,22626,33714, 38120,39464,43927,34322,24271,14635,9836, 37348,11213,13398,24018,29834,10057,29833,41139,37348,14019,44786,29832,40856,33714,32343,11323,33457,15891,34679,35091, 22476,7842, 36045,37348,11266,23564,34679,28003,37347,31642,36649,44786,26156,41307,36649,37347,31641,25557,2086, 45343,33029,36648,32840,32342,3366, 41705,34322, 27605,36648,34321,14742,33714,37347,36648,44786,25296,17359,34009,35091,33714,43053,36045,23784,43585,40290,42439,25033,41961,45343,35542,25032,36648,36647,33237, 24499,35542,34009,16575,36045,26155,26483,33028,37347,22010,15109,37346,35091,20632,14524,32839,15344,17433,37346,22809,42623,22009,36045,39754,39839,43053,40985, 34321,2708, 43053,10983,9375, 12551,26482,33236,10364,35091,10788,33236,22808,30396,30395,36647,40985,40985,36044,40916,41494,44308,34679,25031,42833,11912,41825, 34009,23783,38049,40671,34679,17358,38705,38894,24777,10245,35090,25556,21585,32839,19627,36647,36044,10187,33713,9238, 11212,20071,17718,34008,31640,13169,16867, 36044,24776,34008,34008,19797,25555,15907,17966,20512,22625,10722,33236,17581,15296,37346,16446,42264,11911,35541,34321,27604,32341,21209,34678,35090,34321,7170, 39464,3732, 42623,12458,12173,33713,37346,9756, 32340,41396,42833,44785,24498,43052,41494,40671,41225,30394,18115,10868,18744,35090,36044,32839,43052,36647,37345, 37345,36646,2975, 5535, 5825, 41307,22981,15380,9588, 11835,11598,11535,9218, 11755,11113,11910,35090,26847,40985,11834,17628,41225,39986,15866,11162,9095, 32339, 10259,37345,34678,31031,34008,36646,31639,22008,35089,29332,36646,19015,19887,43585,17472,36646,36645,11027,32839,37345,36645,25554,33713,36645,30393,34320,36645, 34320,20631,3731, 13571,44307,34678,31030,33236,37713,38776,15092,39538,31638,45342,28884,31637,38885,418, 30392,41396,9108, 11066,8626, 10867,40520,42623,37757, 37871,43585,41825,37752,44785,41396,45342,10721,38180,6038, 13031,19066,42622,26846,35089,41960,16479,10538,10244,37344,15146,21208,1102, 10982,1796, 36644,1115, 41704,34320,45342,8820, 40290,44785,35089,34007,35541,34007,9445, 8687, 10363,22311,9498, 35089,19392,34007,8505, 11026,9160, 9438, 10243,40218,45342,10947,34007, 24497,36644,24775,16511,36043,38817,45341,29331,40015,40376,22980,15724,24270,36644,36043,35541,35541,36043,43584,32838,13958,21453,24496,34320,27207,32338,33713, 45341,36644,43584,36643,33235,29831,33235,36643,18554,33712,33712,15723,28002,32838,37925,41225,34678,38503,45341,34319,40178,38046,36043,41059,43052,13935,34319, 34319,33235,38639,39218,28883,34319,34006,37344,34677,10755,41825,38435,39463,41825,43584,42832,40142,42104,42439,40520,40422,38165,44307,31636,30391,36643,8163, 38629,3959, 11265,11754,10593,18743,42622,9508, 8665, 43584,8303, 41824,6896, 11909,7511, 40565,9984, 3960, 9265, 9668, 9043, 21584,10720,33456,33456,8332, 40470, 30390,11161,10463,34318,25553,26845,29830,27603,27206,10946,37344,33235,34006,25552,34677,36042,24495,19227,36042,34006,20858,39986,9920, 5823, 23376,16328,16905, 25030,35540,35540,37344,43583,33456,34006,11534,33028,15379,11160,37343,31635,10650,35088,28882,33234,17153,7862, 10150,8578, 22310,35088,20070,39081,42439,11211, 10945,33712,35540,34005,14669,17506,9575, 20069,10016,32838,40916,44785,14467,9569, 11112,9792, 11833,10649,10981,8930, 35088,36643,39794,33028,10307,8948, 10754, 35088,8007, 36042,34005,43926,40728,39582,29829,24269,15927,34005,21731,13838,15251,22807,34005,17580,5003, 44307,38511,39044,45341,34677,40801,41059,34004,7204, 36642,36042,29330,28881,13826,12140,45340,38543,39859,36642,35087,14147,29329,37864,39619,43298,45340,38180,41960,44784,35087,28880,22309,43052,31634,26154,35087, 39150,43583,39690,19014,40915,38408,10504,34318,14796,42104,21730,13984,40332,35087,45340,36041,39775,43583,39538,13806,33712,35540,33028,18675,31633,43926,7815, 37343,43297,45340,45339,34318,3393, 36041,35539,21583,43926,43051,17471,33711,32838,38678,35539,34004,26481,35086,2766, 8774, 36041,12558,23375,33027,37881,24268, 38179,42104,38921,38906,43926,45339,38263,34318,35539,38148,38754,43925,42103,43925,38176,39729,8407, 34004,14989,38449,15675,39690,36041,39463,21877,40520,13139, 23374,32837,25848,11384,10258,37343,32837,9835, 8113, 35086,39961,44784,38921,8824, 9005, 8789, 11674,25295,40565,38522,36642,33711,40984,40984,41960,14808,28879, 37836,37886,41396,43051,43297,38168,39250,41224,13590,34317,38712,41960,34317,43583,39051,43582,33027,37758,21093,17152,9767, 28001,472, 16247,16303,16866,32837, 9563, 37720,22165,37343,26844,43582,32337,37995,39775,37790,14056,22475,28431,18947,37342,19312,27602,35086,12555,33027,34004,23563,16166,25294,25293,26480,5365, 36642,15606,17821,33234,35539,34003,36641,33234,19985,21582,11753,35086,14576,20630,17231,35085,36040,17770,36040,36641,35538,22979,39137,43582,38275,41824,39285, 39655,27601,36641,9931, 25847,13257,23373,34677,23172,44307,20629,14457,16150,18451,13402,39226,20857,42264,29328,36040,35085,32837,34003,28000,14964,19796,35538, 33456,35085,35085,1460, 11978,40422,9102, 10648,10462,16327,40218,28878,26479,4615, 4552, 22164,35538,21331,33234,2304, 41494,22474,35084,34003,11210,28877,15837, 41494,41224,38571,41959,16009,9907, 34003,23171,27600,35084,36040,33233,36039,41824,42263,35538,36641,25551,31632,15447,23170,32836,17357,22163,25846,32836,42263, 30389,32836,19626,20856,19625,17867,33027,33455,37729,43051,39794,39553,43051,17717,4676, 40856,2601, 37951,42438,42832,45339,45339,44306,38425,35537,36039,33711, 22473,19984,34676,301, 39599,24774,7038, 14951,19391,36039,22007,15128,24017,40142,13922,36039,37342,32836,33711,43297,43925,36640,1685, 43050,44306,39961,40253, 41307,39010,44784,38594,45338,30388,26478,5630, 34317,33455,34002,2475, 3462, 16574,33026,29828,34676,26153,37998,41139,45338,35084,36640,12975,14915,37342,33233, 23782,35537,33233,22806,36640,34002,23781,17505,33233,34002,31631,35084,28430,36640,11264,23562,34002,34001,37342,35537,18450,34001,37341,37341,32336,32835,11752, 35537,33026,34001,36639,33710,8129, 34001,8564, 11673,10719,32835,15313,25029,17769,36639,20384,8813, 9468, 34000,44306,5629, 36639,36639,33026,36638,37341,19311, 35083,36638,34676,33710,21876,9503, 33710,27205,10904,25292,35083,31630,36638,22308,36638,19534,39729,42622,33232,35536,21207,21875,23780,20511,21729,31029,32335, 34676,18553,29827,17470,11383,4345, 8604, 11672,7496, 44784,28876,9028, 36637,20975,22006,6150, 27204,34675,33026,15577,22307,14720,31028,27999,36637,36038,43582, 24267,27599,28429,37341,14416,24266,17469,15446,42622,35083,17965,37340,35083,38417,4935, 37340,29327,36038,44783,43297,36038,15511,31027,18053,16635,13668,5363, 27998,33232,45338,13768,43925,17820,34675,37340,34675,7861, 44306,14599,8051, 8396, 44305,28428,7914, 31629,45338,38765,33025,40253,37340,1374, 34675,38759,39111, 41593,32334,25550,18674,36637,41224,41395,33232,21452,41224,11832,36038,26843,33455,10332,34317,45337,9872, 34674,851, 18217,7935, 45337,30387,17317,35082,8650, 8862, 22472,39463,33455,40049,37339,17675,44305,29826,27598,32333,35536,36037,13797,14850,35536,33025,31628,33710,35536,7303, 38348,8728, 11263,7298, 8417, 7246, 43924,10393,32332,7612, 44783,9467, 7757, 35535,36037,22805,17468,20974,35082,34316,11111,44783,11831,10647,10646,8984, 9340, 10718,11262,2600, 44783,21092,7850, 9196, 19065,9274, 42103,44782,18114,31026,37339,23169,40984,37339,35082,43050,32835,36637,33709,24265,43924,19462,36636,33025,21091,40617,41593,41704,37339,10362, 20068,36636,15209,23372,15605,37338,43050,22005,7280, 7876, 9078, 43050,24494,31627,2737, 2656, 3033, 663, 45337,1216, 42103,6382, 36037,29326,34316,36037,30386, 25845,11830,18611,10242,34674,33709,41395,40421,18946,34316,9860, 8723, 9968, 8284, 41824,36636,36636,37338,36036,25028,37338,33232,2019, 20855,37825,15604,44305, 43924,39284,8995, 32835,32834,37338,27997,38024,38364,34674,40332,924, 7327, 43049,44305,36635,33454,24493,45337,37707,9075, 30385,36635,43581,27597,25844,41493, 24773,18813,33231,8839, 38759,8519, 44782,11065,9071, 33709,6642, 11, 752, 1058, 6152, 6750, 6153, 21728,16510,38093,6039, 1651, 31025,21581,16967,37896,43924, 43049,42832,40142,38111,9234, 31024,40376,37886,32834,28875,39298,10186,37337,35082,39793,33709,14972,36635,32834,20628,36635,35081,34000,33025,33024,16712,28427, 43923,20510,37954,41493,43049,28426,38698,32834,33454,34316,32331,24016,27203,29325,23779,38932,44782,16830,23168,30384,27996,36634,31023,33454,36634,14087,29825, 26477,7503, 35535,32833,13921,12778,22978,22471,17467,34674,24772,32833,32833,41823,44304,32330,18113,43049,36634,41138,25843,12719,15926,20172,36634,14681,41823, 19461,36036,25291,44304,31626,29824,17627,34000,11977,44782,43581,33024,22624,37337,38009,39192,5002, 42832,1974, 43923,3004, 19146,32833,31625,37337,36036,35535, 14051,37337,35081,23561,34673,10866,36633,6898, 44781,41223,16904,42438,9464, 45336,39332,39383,39345,45336,29823,40049,1225, 9859, 9952, 1288, 11159,1336, 36633, 37985,39410,43923,44781,41823,25549,11454,9105, 39985,41959,41704,39200,42103,41493,40421,34673,41223,40178,36036,21330,34000,45336,33454,41138,29822,610, 43923, 40727,10865,39437,41138,38086,45336,44781,33708,43048,41307,33024,20973,40048,30383,39636,38057,39463,41223,42831,36035,43922,37336,24015,33024,30382,22804,36633, 29821,31022,44781,1249, 39814,898, 37336,41593,44304,1713, 1770, 36633,33999,643, 41138,33023,22004,41306,35535,9599, 15925,36035,36632,20627,41592,33999,8850, 44304,41823,43048,36035,18945,44780,36632,30381,25548,15906,34673,34673,33023,43048,33708,32832,33453,34315,32832,43048,33231,37870,27995,29324,41137,10111,37336, 44303,33453,37944,11533,31624,35081,44780,37336,37335,27202,36632,27596,37335,38041,41822,42621,34315,37703,38219,45335,41395,43296,41058,45335,319, 41137,42438, 40080,39932,44303,43922,39961,45335,38724,41592,38064,44303,40855,36632,38074,38683,39087,43296,39793,44303,34672,43296,33453,18388,17085,36035,37815,40376,5824, 31021,35081,43581,32329,42831,38514,42831,42102,2383, 45335,43047,14826,31623,36631,41058,44302,35080,6149, 39332,38765,25290,39087,15853,44780,1656, 38449,699, 22306,41493,28425,10361,15239,45334,33708,34672,41492,37689,37885,40520,38101,40470,22003,37878,38990,44780,38628,37335,36631,8589, 43922,42831,43296,39525,5931, 36034,22002,13974,35080,10645,8896, 8939, 8994, 26476,40727,38693,43922,33708,33999,10149,17917,26152,14864,11597,11532,11671,8239, 44779,11670,25842,8372, 41492, 45334,37335,26475,36034,8837, 39793,8763, 39184,13617,14756,36034,33453,38754,36631,28874,43295,33023,39410,36631,40290,36630,39297,36034,41959,33452,43581,43047, 42621,3119, 42621,33231,38420,43295,39284,38588,41704,43295,38419,32832,38333,43047,35080,33707,15196,24492,41223,39271,43047,39322,40670,5712, 44779,13188,8937, 36630,36630,15145,26151,27994,36630,7785, 43295,6897, 34315,13707,8144, 40915,3496, 20269,36629,28873,25841,35534,14932,43921,32328,33231,33023,36033,22001,37334, 18944,36033,17579,21090,37334,17534,35080,27595,41959,34315,14064,20509,34314,15819,19145,33999,36629,36629,14530,35079,16758,20508,31622,33230,14439,3034, 43580, 8438, 38815,8437, 43921,20626,33022,15797,11669,9456, 37780,40048,43046,41958,42263,33998,42438,28872,33022,22977,18387,38702,21727,33452,39250,35079,41958,22162, 42263,38035,43921,9807, 39985,35079,1071, 45334,33998,36629,20171,40727,19390,39149,40375,42621,37334,43294,18943,19795,44779,43580,10066,36033,34314,23778,38806, 24264,36628,13319,45334,25289,39271,18009,4617, 40984,15425,36628,1941, 39571,31621,13898,34314,35534,9634, 33022,28424,37334,32832,28423,9478, 8054, 43294,27201, 43580,36628,22470,10038,35534,26842,32327,22976,24491,33230,14107,34672,24014,44302,5932, 15700,7735, 33022,33452,26474,1953, 41137,33707,7506, 36628,6894, 3183, 2448, 33998,36033,28871,22305,24263,31620,21089,31020,33452,22623,37333,40617,41822,21206,37333,32326,34672,36032,42620,17151,21874,40470,29820,20383,44779,38005, 13813,33451,18491,33451,27993,25027,38511,21205,21580,34314,36627,44778,45333,1151, 43294,44778,43921,41395,38451,42620,36627,43580,14795,36627,20854,21579,8152, 39552,11668,2736, 11382,10682,40855,33230,36627,36032,9624, 36626,26841,9522, 27992,10537,27991,23560,25840,35534,33230,24490,36626,34313,33451,21726,9938, 44302, 37333,9489, 37854,36626,38856,37715,38194,39599,39359,41137,37873,38837,15412,40109,41703,18812,39242,41703,9814, 34313,34671,43046,43046,18112,24013,5536, 41306, 42262,40331,38273,40178,44778,38948,41306,43579,27594,4616, 45333,40470,43579,43294,36626,30380,2738, 30379,33451,35533,31619,25839,22161,39671,21873,43579,42830, 42437,37697,4445, 13897,13857,6895, 32831,30378,20268,42437,14567,31618,34671,37333,17504,37332,17466,33998,41058,22469,27593,33997,13912,36032,42830,37746,37929, 40469,14369,41222,18320,38310,42830,45333,22468,21725,202, 634, 32831,37787,19013,35533,41306,10618,41222,39102,37789,33997,12585,24262,36032,35079,33229,37908, 38890,9042, 39080,8908, 38792,9623, 33450,32831,465, 33707,42620,40670,20170,9667, 11453,9455, 17674,6042, 10903,8742, 39793,6258, 5223, 6040, 2384, 4387, 43920, 17230,27592,19460,39051,35533,21724,41703,38176,41305,37332,37332,36031,16413,2626, 2986, 1659, 3881, 2913, 33450,7296, 44778,31617,38172,38301,43579,29819,39271, 39599,39242,41058,25547,10392,39912,45333,42102,39242,24012,44777,39619,44302,42437,40983,41703,24771,32831,23777,45332,38304,43046,43293,25026,40983,26150,34671, 31019,44301,35533,34671,4191, 33707,40469,34670,1434, 40519,31018,43920,38021,44777,44777,42262,39436,28422,33997,38650,38823,36625,40670,38232,39636,43045,36625, 25288,23167,27200,34313,31616,37948,35532,30377,16545,34313,5757, 13004,37939,38203,2774, 40142,39261,13367,12884,36031,39029,35532,14473,30376,24261,19794,35078, 34312,7096, 37332,18490,38797,41222,39436,13288,2119, 25287,44301,5977, 40855,5756, 42262,25546,28870,38667,40080,45332,45332,42437,44301,33450,37331,37331,20507, 33997,37331,35078,17465,19793,15639,19886,36625,33450,37331,36625,32325,22467,33706,34670,33996,32830,20853,28421,25286,33706,38508,33449,35532,15576,14719,13757, 45332,16149,43293,37330,12362,40015,2775, 36624,36031,33996,40375,4891, 41394,17316,33996,38604,38563,40217,45331,41958,1293, 32830,40217,39985,14523,25838,15603, 34312,35078,35532,18167,22304,34670,37330,33996,16271,35531,36031,37330,28869,33706,28868,10681,35531,44301,45331,40800,45331,8232, 11667,36030,32830,21451,40375, 44777,26473,35078,23166,36624,3715, 35531,26472,8492, 45331,5760, 9054, 29818,19705,27591,2120, 43920,20267,5856, 43293,6303, 2392, 4023, 6300, 10617,25545,27990, 17673,37330,14415,37329,15575,14312,13957,35531,16671,44776,24011,43578,34312,7801, 9192, 36030,35077,11261,35530,162, 44300,5409, 25025,38978,44776,44776,39200, 38021,43920,39729,20266,34670,44300,33229,3971, 1716, 879, 18742,12037,40217,25544,34669,37329,3310, 36624,35077,36624,3788, 34669,41592,28420,37831,33449,38848, 43919,23165,36623,5755, 36030,825, 39436,24489,40519,3408, 19226,19885,29323,25837,15054,37329,13468,42436,35077,23164,6198, 37329,21450,33706,33995,41822,31017, 32830,28419,27989,10110,38848,42262,10257,41702,45330,7368, 8050, 26840,33995,33449,33229,43919,44300,37692,43293,43578,32829,37818,42830,1592, 40855,38201,37728, 39525,4892, 44300,37754,39839,43919,37328,35077,42436,13657,42436,40469,13501,45330,43919,39076,45330,43918,38535,43292,18872,40177,42102,40564,41394,45330,18008, 37996,40800,38842,40800,42829,3017, 35076,42436,37328,34669,40290,42620,37328,35076,35530,35530,15536,37328,13892,11986,25543,33229,27590,44776,44775,21578,33995, 43045,8549, 8875, 11158,33228,30375,26839,35076,44299,29817,37875,39599,35076,44299,39571,42619,41958,10483,11829,27988,10109,10081,38074,40915,9906, 39710,7474, 8586, 34312,28867,8595, 34311,33228,24260,27987,14050,9557, 30374,35075,20265,36623,34669,34311,7443, 43292,11110,33021,8329, 11157,36030,36029,8219, 11156,10717, 34311,8793, 11908,36029,11064,9755, 9329, 21449,36029,10902,11025,36623,8685, 39598,33705,8306, 45329,9848, 9245, 11109,8901, 11907,42829,35075,35530,36623,37327, 23371,10716,25024,6682, 37327,35075,36622,37781,28866,37327,1016, 42261,4064, 38422,38514,22160,25542,37327,40253,37883,45329,30373,23559,38027,37326,39010,27986, 32829,37754,35529,40141,30372,41822,36029,29816,39284,43578,37946,41394,34668,12711,38796,39775,39125,33995,8652, 42435,37326,25836,2855, 20625,8715, 42829,9335, 10536,36028,16412,37999,23558,38747,34311,18386,42435,29815,40109,41592,29322,34310,9074, 11751,36622,33705,18871,37326,34668,20624,23776,34668,35529,21448,37326, 35075,7584, 40670,7216, 8910, 8224, 44775,39261,35529,42435,34310,1689, 38683,45329,41821,37325,21329,36622,33994,19533,11828,8664, 33705,37325,11906,34310,40727, 45329,33994,38056,42435,34668,33994,10241,21088,42102,30371,40669,34310,33994,33449,16361,39, 39297,3196, 5857, 4832, 2459, 42619,31016,39359,4148, 35529,37790, 20852,1676, 15144,14213,17916,17229,15143,42261,42829,42619,6574, 43578,13064,10592,36028,43045,1096, 502, 40217,457, 36622,239, 40669,2579, 36028,724, 30370, 31015,6683, 22803,218, 37807,45328,3158, 40519,13364,11905,21447,40726,4112, 874, 32829,9648, 35074,39690,37939,42434,29321,33021,20623,30369,33021,35074,18007, 41821,12201,7486, 36028,25541,27985,38170,40564,41821,41957,12535,33228,39035,36621,34309,39003,41222,40375,15796,26838,33705,34309,20264,17578,1735, 21087,33704, 13059,41821,18552,32324,15378,15905,38245,40331,38294,10185,7683, 5978, 20972,16634,37325,12853,36027,43292,19389,19884,42261,36027,1344, 10056,8942, 11666,9328, 40669,11750,26149,37325,43045,36621,36621,19624,32323,33228,36621,36620,43918,44775,45328,44299,24010,39932,28865,12213,41394,5253, 36027,30368,27199,36027,35074, 14651,29814,36620,12829,21872,37324,36620,35528,24770,11108,8601, 32322,9967, 26148,7824, 40800,35074,34309,9562, 7831, 9919, 18275,963, 9339, 8788, 11209,8286, 10123,8643, 39436,7857, 31615,44775,8108, 37809,41393,36026,28864,39912,34309,34308,34308,14931,17503,30367,41305,39144,38693,34308,33227,45328,225, 39208,553, 3075, 1849, 2578, 5573, 6573, 40253,10212,9208, 23370,8348, 38504,27984,28863,37324,33227,35073,36620,35528,37324,39450,10616,36026,11155,36619,20506,27983,34308, 36619,18741,13659,44774,33227,9834, 34307,31014,25285,24009,33227,19623,34307,37324,32321,32829,21328,45328,8778, 28418,25540,26471,36619,6953, 36026,33226,11063, 17124,37323,25284,34667,29813,21204,6089, 30366,44299,38661,41702,33993,12897,40617,32828,36619,34307,33704,33704,33226,8122, 44298,7243, 8806, 10680,37323,27982, 43918,13550,34307,42261,33226,20622,31013,27981,7427, 38477,35528,27589,19310,16302,21577,42619,36618,31614,35073,8491, 42260,43577,39774,45327,9521, 42618,6302, 17150,36618,24008,37323,33226,40519,3470, 44298,42101,36026,45327,36025,33704,5028, 43044,21086,23163,39184,44298,45327,14332,20851,40799,43044,40726,22303,12394, 44774,41305,36618,39912,38455,40669,22466,37846,6803, 39571,39671,43292,43291,39961,13109,29320,31613,15481,33021,20505,24488,42434,23557,36025,33225,16084,32320, 20971,33993,17084,37323,34306,23775,13439,36025,37322,38894,42260,42828,36618,45327,38531,16270,35073,36025,13647,19309,34306,24007,16039,23774,8713, 20724,32828, 35528,31612,25283,34306,9813, 17228,29319,33993,23369,40518,32319,9284, 40915,43918,38938,39537,45326,44774,37322,41702,26837,41136,42618,39124,43044,11531,10331, 28862,33703,33703,34667,37322,22000,40564,37322,37321,27980,40799,24769,13478,17051,2334, 15042,28417,42101,45326,44774,43577,22622,27979,18740,32828,7419, 43577, 44298,29812,36617,34306,33448,24768,33703,35073,29318,15142,35527,35527,37321,12893,42828,4459, 10108,35527,43577,43044,33993,41820,41393,38842,34305,8484, 36617, 36617,42101,29811,33448,23162,35072,22975,35527,32318,37321,43291,9766, 33703,33702,39322,40914,43291,44773,31012,8382, 34305,29317,19144,35072,32828,39618,37808, 45326,25539,26470,17315,9983, 7435, 13356,7892, 44773,41702,27978,42260,41957,9233, 25282,12169,36024,43917,43917,31011,8465, 26469,11107,7981, 34667,9847, 9534, 33992,10679,10564,30365,10944,8560, 36024,34305,43917,8307, 44773,11596,8511, 9002, 37321,10753,8135, 44773,12922,12250,43576,42828,39029,33992,41492,43043,35072, 36617,33225,14072,31010,23161,33448,42101,45326,45325,43576,43576,42260,39200,41393,42259,43576,44772,5571, 37901,45325,41820,34305,27588,36616,44297,15638,31009, 37320,37320,15091,33020,8393, 41701,11062,33225,28416,23556,32827,26468,8537, 24767,35526,20723,13866,7943, 39117,33992,43043,34304,45325,34304,19012,44772,31611, 43043,5254, 43291,17626,43290,34667,22621,29810,44772,4110, 37320,33702,36024,36616,36024,36616,44772,36023,37320,2092, 37319,34304,12674,33448,44771,18111,33020, 34666,14414,18610,26836,24487,30364,18870,45325,33020,35072,17190,37319,7098, 17464,27587,13911,35526,37319,43290,9937, 22465,37319,40914,43917,44771,43575,26467, 43043,743, 41136,40080,38531,44297,35526,32317,38906,36616,45324,19011,35526,35071,37318,12768,10414,37318,22620,17083,34304,43042,35071,32827,6301, 28861,23160, 35525,37318,38486,40854,12275,45324,34666,15377,29316,35071,36615,36615,31008,9664, 40421,41701,36615,12870,37318,15657,33020,21446,44771,19532,35071,36023,29315, 15183,37317,28860,28859,37317,33225,33447,26835,40983,40564,34303,15555,25538,16063,21871,4149, 3834, 33447,16220,37317,43042,37317,19531,33992,33991,17502,14522, 33991,16165,33702,22974,40854,40668,22802,2311, 35525,36023,33702,10461,39838,34303,37316,42100,11827,10306,10122,38344,33224,38678,33701,36023,27586,45324,4111, 5976, 30363,12772,42828,40854,974, 41057,30362,27198,22159,27585,195, 12613,37316,16544,20970,37718,35070,33224,11260,9025, 11154,8466, 43916,8987, 39960,35070, 17768,16038,32316,30361,19308,8951, 41393,33019,10162,16193,33019,33701,191, 43575,3714, 36615,33224,36022,29809,38973,10715,5408, 849, 695, 43290,1119, 37316, 34666,35525,33019,778, 17866,43575,36614,36614,41221,13476,14146,28858,29314,34666,43575,25835,29313,13691,19530,35070,36022,43916,35070,8429, 40617,20504,35069, 10055,45324,37316,35525,34303,15535,33224,14817,33991,2164, 44771,21999,11322,24006,36022,33447,36614,36022,31007,6572, 36614,37315,11321,32315,19459,21445,10161, 10563,13967,33447,37315,10644,36613,38112,44297,38531,32314,9382, 10864,8808, 9488, 19792,37315,12902,2034, 4963, 10615,5572, 21576,21723,33991,35069,45323,15969, 33446,27977,47, 40109,3197, 16757,3574, 40983,257, 41957,1292, 4893, 38311,39358,3309, 43574,41057,2580, 40982,2688, 43574,34303,17227,41957,24766,39859,36613, 37315,28415,33701,27197,42259,33701,33019,41591,37314,21870,41591,18942,3677, 39887,42259,4645, 41820,42827,38678,44770,45323,43574,43042,41701,40109,24259,9598, 45323,44770,379, 1775, 15890,26834,20969,36613,35069,2912, 4775, 37314,33223,34665,35069,18166,42827,37314,41305,43916,14008,24765,30360,37314,32313,15818,36613, 43042,36612,11826,6805, 14116,45323,21869,35524,36612,22158,33446,42618,34302,43041,36612,13486,25537,29312,31006,36021,26147,3880, 37313,28414,27584,27196,36612, 28413,32312,41492,14106,17819,12881,44770,28857,23555,23368,20722,9014, 10752,35068,38140,40854,10391,1309, 44770,39710,14544,33223,37313,37927,42618,39358,42259, 10943,39297,41221,34665,30359,39932,36611,21868,37313,21085,2080, 45322,41136,39332,42434,42258,44297,38047,36611,6438, 35524,33223,19622,40982,37313,6437, 43916, 39117,36611,33446,23773,36611,22302,45322,41392,38565,40421,40374,42617,38618,34302,35068,24764,28412,20382,10080,11106,34665,41591,34665,29311,36021,34302,37312, 34302,11381,11595,34664,36610,21084,25536,40799,27583,35524,36021,35524,32311,37312,32827,24005,2749, 45322,38433,40563,33700,19307,37312,29310,37312,33990,7818, 42434,41956,44769,9361, 43290,38759,41701,14491,36021,34664,35068,1512, 7097, 37697,11904,25834,39570,44296,37311,38770,40331,37759,44296,44296,38344,45322,15656, 38648,37311,37752,42433,36610,35068,35067,19306,37311,24004,29808,36610,33700,5758, 37311,37310,33018,37310,44296,45321,36610,2664, 42100,28856,29309,36609,34664, 40914,43574,37310,33223,17003,42433,43289,34664,26833,26466,28411,7456, 39450,8094, 32827,45321,24486,34663,36609,12306,38118,25535,32826,33990,19064,37310,11903, 36609,39814,33990,35067,43289,18811,44295,40289,41591,43041,43915,34301,9905, 800, 4831, 39931,44295,22157,44769,40982,8088, 10390,36020,21444,33990,8888, 36020, 17189,34301,10360,9191, 33446,27195,44769,11380,24763,38783,45321,38190,37309,36020,37309,1669, 32826,35067,32310,35523,34301,24258,25023,16633,18869,15602,37309, 36020,10980,35067,33445,45321,18941,35066,7099, 8912, 43915,30358,13680,35523,5858, 40518,30357,22464,25833,35066,33700,44769,45320,28855,35523,12315,12935,27194, 11153,21083,36609,18165,42258,23159,34301,40374,33445,31005,36608,40177,40015,36019,28410,33445,37309,44295,31004,33700,41956,22301,43041,11749,25534,37870,38837, 5759, 45320,2829, 41590,44768,38670,43573,8490, 39635,44768,41700,42433,33989,10439,41392,5979, 36608,35523,37308,44295,37950,10863,38104,41590,38113,38513,1073, 40374,44294,40563,39859,39570,36019,21082,28854,45320,35066,36019,34300,43915,44294,37308,26832,36608,35522,24257,2413, 2987, 22973,37308,33018,20621,33989,15411, 29308,4192, 45320,14755,10413,6806, 40177,42433,33989,39450,16326,44768,41590,21081,40853,6575, 32826,33699,20503,33018,9280, 37308,33699,11105,36608,1848, 37307, 36019,5488, 36018,23158,8594, 40914,3972, 43573,23157,36607,22972,15499,39618,42827,41136,41135,21575,29807,4776, 21327,34663,1279, 5255, 18739,30356,35522,36018, 27976,37307,33989,27193,37307,41057,25281,34300,35522,36607,33445,25022,31610,33988,41304,3228, 44294,22156,34663,5489, 33988,16543,22300,14586,25021,40668,43915, 44294,45319,35066,27975,6804, 38468,43914,39911,41057,31609,43573,1280, 42827,40726,40048,36607,39710,44768,43289,41590,11530,33444,39525,41589,44767,43289,42617, 42100,43288,42258,21574,33444,4259, 41392,18110,38724,38828,42258,39051,39537,41056,38917,40289,18868,27582,42826,37307,26465,33699,33988,20721,33699,33444,24003, 3879, 37306,12951,34300,38967,3664, 12274,26831,36607,34300,37306,41700,32309,40853,43914,38448,40420,38430,28853,43573,35522,42432,35065,12284,40982,28409,17356, 22299,35521,28852,5372, 24256,28851,15621,38774,43041,40799,4680, 37306,33444,17964,20381,4227, 40216,37306,41700,37305,22298,20380,30355,35521,32826,28850,45319, 26464,36018,19458,32825,45319,13305,36606,45319,38171,42826,33018,21998,33443,39297,34663,986, 17767,36606,29307,39598,36606,41135,34662,34662,37305,35065,33443, 33988,24762,38583,43914,29806,41221,3219, 5371, 34299,19529,6753, 43572,32825,28408,6046, 8079, 41820,43572,7249, 8796, 36018,31003,36606,39911,40141,40563,41491, 43914,40374,39887,20263,39911,43040,822, 32825,27974,34662,37753,43288,43572,33698,25832,18551,37305,17050,25533,43913,24002,22155,33987,15817,35065,37305,11320, 16829,36017,10643,14362,43913,17268,40518,41819,35521,39382,43040,9026, 18216,33698,34662,3337, 11529,35521,11061,37988,39690,33698,33698,34661,36605,35520,9547, 36605,22801,27581,20379,37304,1040, 6044, 24255,35065,36017,33987,15816,5934, 36017,25020,25532,35520,7333, 17818,26146,24485,41056,20502,7661, 9318, 11748,9516, 10862,7748, 11902,8540, 9403, 5073, 8107, 7918, 11319,14472,36605,30354,21997,15836,17123,33017,40289,37304,11452,41135,17355,33987,18550,31608,36605,44767,38998, 35064,35520,35064,8983, 8375, 28407,20262,35064,8907, 44293,28406,32825,32308,22971,37304,32824,33017,2386, 7464, 41956,35064,25531,7504, 15554,19528,34299,26463, 13251,7282, 8805, 40289,34661,11665,20169,34661,42432,36604,19621,40177,35520,37304,17865,36017,10359,25280,33443,37303,14598,23156,35519,37303,20720,11594,8150, 41589,10751,9791, 10091,10482,9614, 8442, 40216,10121,7872, 10503,41056,5830, 9307, 44767,11259,10305,10901,33443,8922, 33697,10861,11104,8404, 39655,42826,43040, 40726,10285,8783, 39175,33697,33222,21443,10481,9099, 34661,8779, 11528,10714,42617,8450, 27973,33222,36604,10211,11825,34299,11451,8957, 8148, 38683,3297, 42617, 9369, 9424, 8385, 43572,9020, 11664,20501,9502, 9185, 8513, 7761, 8716, 9734, 11024,8921, 35063,44293,28849,11747,8185, 43571,4005, 10358,9966, 11663,11662,35063, 8982, 11152,41221,43571,4618, 38966,43913,10900,9546, 9982, 10827,8221, 9207, 13228,8619, 40288,33697,34299,41700,32307,44767,10899,148, 3394, 18810,27972,4619, 35063,1783, 555, 6389, 24001,28848,22463,815, 3296, 33442,7279, 43040,6264, 6643, 8318, 44766,26830,5297, 38145,40141,8752, 19388,9195, 11151,44766,8757, 8660, 42826,9550, 39124,41819,35519,8787, 3868, 10942,40853,8269, 9317, 7155, 39462,5134, 33697,38653,32824,19620,30353,2628, 40141,23155,37303,38046,43571,45318,45318, 38828,41304,39838,11746,18489,33017,14145,4620, 34298,32824,40373,40616,36604,40853,10460,24254,40080,42616,17267,45318,15835,36016,22970,36016,36016,42100,23367, 42825,32306,18867,24484,12525,30352,44766,42616,29805,7989, 36016,32305,35063,43288,8624, 10713,29804,21442,23366,174, 44766,42825,10330,10860,9327, 40725,8379, 15987,42257,33017,29306,536, 7657, 11023,31002,6043, 43571,9702, 39635,7569, 35062,14144,15361,9396, 9355, 5133, 44293,42432,35062,33696,30351,24000,22462,25019, 10304,31001,37303,31000,10357,9520, 35519,32824,36604,35519,35062,36603,32304,38875,44293,19010,35518,40252,44292,19305,37302,32823,37302,36015,34298,22969,33987, 16192,23365,44292,9613, 20378,32303,10502,24483,12405,7841, 9047, 11450,14741,16219,36603,35518,34660,13121,37802,38954,37752,39358,29803,33442,29305,18215,35518, 36015,28405,20168,22461,14740,33696,38702,16542,18673,36015,37815,38012,3430, 26145,36603,32302,34298,20719,32823,29802,45318,40176,37302,40518,12364,41491,8394, 45317,39192,42616,12068,39462,41392,21867,30350,40563,39838,36603,31607,37302,33222,36602,33986,26829,35518,17082,35062,39232,8602, 44292,35061,19143,14227,14566, 34660,35517,24761,25530,26462,44765,36015,15090,33016,37301,35517,36602,11258,8179, 41220,36602,9691, 39552,44765,6263, 41699,43570,40469,45317,40331,11970,40014, 3431, 8782, 15141,34298,9918, 17864,26144,18738,17963,37301,11973,1615, 44765,33696,42825,39410,35517,14129,15445,26828,15955,32301,33442,34297,31606,24253,35517, 34297,36014,41699,25279,22968,7507, 40330,30999,39552,44765,15295,42616,35516,36014,35061,40048,8980, 9666, 44292,11449,40725,38690,7253, 11208,14452,45317,44764, 8142, 39332,10614,10787,10613,8479, 11060,8944, 11527,9137, 9041, 11901,485, 9806, 1976, 9951, 9568, 11900,8864, 11257,10002,11150,11318,41956,33986,33696,25018, 33222,33016,43913,35061,33986,43912,36014,10107,4806, 42257,35061,39754,18449,36602,35060,33695,32823,33442,35060,27971,39931,43912,37301,39754,43912,33441,32300, 26461,24760,5541, 39368,5713, 35516,37301,17049,39394,45317,34660,36014,21996,35060,30349,36601,33986,38367,25017,39931,25831,33441,41819,39044,36013,32299,41699, 43570,12082,41699,4006, 33221,33985,44291,40668,40725,35060,42099,38286,16935,39911,39689,42257,43288,38387,42432,44291,40252,41819,36601,36601,34660,20850,34659, 37300,18737,44764,21441,18609,21326,30348,39618,34659,42615,41220,12374,30347,34297,14634,38218,40725,28847,22800,1035, 43287,33016,35059,36601,20968,15834,44291, 35516,27580,17, 39960,946, 6530, 2821, 2226, 2407, 43039,40140,43570,45316,34659,40420,43039,5715, 39410,44291,13390,14633,43039,17863,25016,25830,26143,35516, 28846,33221,27579,41491,44764,42257,2843, 34297,27192,37300,33016,12985,3005, 37300,28404,33441,27970,41955,36600,37964,41955,5714, 39226,38407,23554,38823,40616, 42099,38139,45316,44290,36600,26827,38094,40373,33985,41698,36013,38180,43039,43038,44290,39344,36013,39435,39618,38860,37300,43287,45316,38193,11745,34296,37299, 34296,41698,44290,42256,44764,40014,27969,28403,14907,16934,17817,16828,17463,16445,41955,15795,7365, 12442,18385,33985,36600,15637,37299,35515,34296,33695,35059, 25829,18319,36013,10037,10750,12386,31605,16573,36600,34659,35059,33441,16124,44763,43912,8812, 9237, 36599,41056,33440,39598,28402,43911,34658,37299,34296,30998, 13887,15722,13525,33985,30997,24252,44763,43911,39331,82, 4226, 40140,40616,34658,13848,22967,29304,20167,27191,32823,34658,33015,34658,33440,33984,7436, 40373, 17432,35059,19983,44290,30346,5542, 37735,45316,27968,38848,18736,37915,26460,15773,32298,35515,35515,21440,7166, 39368,1530, 12113,36012,37299,4557, 25015,33440, 35515,27190,22460,8390, 35514,32822,13950,32822,33221,21325,14891,32297,34657,24251,29801,25529,15751,14257,38682,41955,36599,37298,13741,34657,5225, 40798,28845, 731, 40724,45315,39617,21722,37298,34657,33695,33440,18164,33984,44763,36599,29800,27189,44289,43570,34295,26142,21866,9454, 8681, 9165, 27188,22459,32822,32296, 32295,22799,36599,39494,20500,26826,34295,14124,34657,27967,42431,33695,37298,28401,35058,33984,18672,15312,9013, 36598,8365, 40981,13467,7317, 7699, 27187,25014, 9159, 11899,7926, 24759,18735,41491,39813,25828,36012,35058,45315,34656,26141,33984,93, 8784, 34656,32822,33694,7376, 39067,5296, 36598,37298,34656,5716, 41304, 2681, 14668,15954,29303,5074, 9647, 40798,4743, 43911,955, 4744, 42431,42825,11593,41220,8004, 3587, 541, 8391, 2000, 20261,18809,32821,226, 4091, 2087, 3566, 12194,45315,9227, 43569,42824,33983,41304,37701,30345,10712,9608, 34295,13583,42099,36598,40468,34295,30344,2545, 37297,10642,36012,30996,32821,12733,15674,12940, 35058,33015,7045, 12824,40668,43911,25528,32294,2227, 15281,34656,29799,25278,36012,17462,9254, 35514,12533,19982,35514,24482,33221,18940,12694,42431,11256,22458, 36598,40252,38141,39169,33015,37297,17862,44763,45315,33439,8746, 36011,12622,30995,32293,25827,44762,23154,36011,35514,25826,27578,30994,35058,34655,33220,3465, 6755, 5828, 4865, 16478,41490,32292,26825,9722, 35057,37297,25013,16827,20620,34655,37297,37296,33694,15889,25012,26140,28844,27966,23553,35057,37296,34655,19457, 21080,32821,30993,35513,34655,44762,43038,16826,30343,14323,17861,23999,14620,37296,35513,37296,21079,36011,36011,24250,14346,35057,12896,33983,18384,17915,31604, 37295,17962,19225,23153,37295,33220,36010,26824,17625,44289,14425,36597,18006,23552,44762,35513,18163,19142,17860,29798,34294,33015,33014,41055,25277,33439,15174, 20718,19704,37295,12343,36597,16825,36010,19791,36010,19387,36010,27186,12987,36009,16824,24249,15294,37295,35513,13142,18808,32291,12547,18866,36597,32290,30342, 31603,15553,30992,15077,37294,32821,33220,37294,20260,26139,36009,37294,17226,33983,24248,34654,12891,33983,33694,35057,33982,15953,32820,21203,36597,37294,28843, 18807,35056,37293,34294,35512,36596,20967,20067,37293,22966,36009,14331,37293,12323,45314,14205,34294,12700,19224,25527,18318,31602,15552,24247,17624,33014,37293, 36009,22619,32289,13176,26823,32820,23364,29797,20499,18383,23363,33439,35512,12443,24758,34294,36008,14863,40798,26138,31601,32288,15986,23772,35056,35512,22965, 18448,15968,35056,22618,34654,13063,13794,34293,13118,37292,31600,33220,29302,33219,23152,36008,35056,30991,34654,14963,15985,34293,35512,37292,15140,34654,13653, 36596,24757,33982,21324,14688,12530,17766,32820,17122,26137,36008,37292,25011,34293,26136,37292,33219,37291,37291,24756,24481,18549,13240,37291,17914,18608,37291, 14515,37290,33694,36596,35511,32287,26459,33014,33439,33438,28842,12592,22798,30990,15261,13143,14825,14012,35055,12358,33982,36008,28841,35055,34293,35055,34653, 33014,18548,36596,43569,18214,30989,16385,12157,13449,35511,27965,19981,35511,21721,25276,14099,17354,12771,16360,18671,15280,21078,34653,23771,25825,28840,22154, 36595,33982,16246,26135,33693,34292,27964,34292,29796,33219,34653,20066,36595,15238,29301,33219,34292,33218,33438,32286,21077,36595,21865,30988,24755,16325,14824, 35511,12604,19619,18607,25275,15360,14278,35055,21995,16037,35054,33981,36007,19883,30341,17859,15173,15833,33981,33981,12228,15195,36007,37290,17353,29300,34653, 12997,16632,36595,23551,33013,26822,25526,12066,40667,33438,35510,25274,14575,13318,26134,15260,19386,15967,20849,30340,34652,12507,27577,32820,34292,14256,22297, 44762,17765,25525,34652,34291,35054,22457,16572,27963,37290,35054,36594,33693,35054,24480,19527,33438,35053,13019,25010,20717,23362,25524,20848,30339,34291,36594, 21076,19141,29795,32285,16602,24246,15329,12777,24754,32284,33981,15108,21720,33013,33980,12670,14606,18162,33437,25523,31599,33437,32283,25522,37290,26821,31598, 29794,30338,33693,36007,18606,22797,32282,27576,18670,37289,27575,32281,30337,28839,17225,43287,21719,18547,36007,27185,21994,31597,32819,34291,34291,12278,26820, 33693,15699,23550,37289,44761,35510,12933,25273,32819,13024,43910,33437,32280,35053,30987,33218,17121,16359,35510,31596,12915,16601,40176,33692,30986,36594,33218, 27574,33218,30985,35510,29793,28838,20259,36006,25272,32819,17533,28837,18213,36594,23361,24245,28400,30336,33692,37289,16218,14392,12742,20377,33013,29299,21993, 25824,33217,22153,34290,36006,12925,13208,28399,37289,26458,13934,14962,15966,36593,33217,19304,44289,29792,29791,30335,23770,19703,35053,33437,17816,12396,32279, 25521,34652,33980,36593,16966,23151,32819,36006,14490,20966,36593,13865,35053,33436,29298,37288,23998,30334,18605,37288,19702,33217,15089,34652,14667,12602,36593, 20847,12640,36592,33436,17314,31595,22296,33692,36006,20716,37288,19618,30333,30984,12698,35052,17081,37288,36592,33980,39689,12570,20166,22964,25520,15698,33436, 32278,35509,33436,18447,29297,35509,16444,33692,25009,37287,32818,30332,33435,13317,43910,15620,33217,25008,33691,12392,15888,15721,32818,23150,33691,34651,25007, 17858,34651,34290,20065,30331,23997,30330,17961,33980,36592,8003, 12212,20619,36005,22295,13641,41954,44761,32277,21439,23549,37287,18488,34651,25519,18669,33216, 25006,36592,37287,12615,36591,19223,16384,36005,26819,34651,13625,33435,20965,16083,36005,16756,33979,31594,29790,32276,12350,36005,14816,36004,37287,26457,37286, 29789,12544,34650,33013,33435,16324,26456,14666,14358,25823,33435,12784,37286,28398,33216,19303,24479,18487,33216,35509,32275,14086,36591,12811,33012,36004,27184, 36591,37286,22152,19980,13214,26818,15619,20165,21438,24753,33012,29788,16148,33012,36004,12618,14988,22617,13823,17080,21864,18806,17266,28836,34290,33434,17764, 26133,21323,13316,16191,26132,34650,39617,14914,35052,18865,33691,30329,21437,24478,14543,13341,36591,13483,13315,16245,19882,21573,31593,21436,36004,13131,37286, 23149,24477,35052,36590,30983,26131,25271,14619,36003,27573,19617,35052,12666,44289,13137,26455,33691,36590,32274,23769,27183,23996,23548,17431,31592,23995,22294, 33434,19979,37285,28835,26454,27962,27182,45314,14391,36590,14390,34290,35509,37285,32273,17501,25270,25518,17352,31591,33012,26130,17763,29296,34650,12735,36590, 32272,20498,21572,27572,33216,33215,36589,25269,26453,13083,33011,36589,35508,32271,30328,25822,33979,29295,29294,36589,19978,34650,33979,13295,26817,36589,23994, 35051,36588,36003,26452,15636,36588,19616,26129,27571,18486,33979,22456,29787,21571,35051,33978,15376,29293,20497,24244,33978,37285,37285,33690,34649,36003,15279, 33215,21992,14906,21718,31590,32818,34289,35051,36003,35051,31589,20496,32270,14123,32269,13294,36588,34289,4092, 42824,42256,36588,15444,33690,28397,13207,13037, 20164,29786,14255,19977,4745, 12627,21435,8810, 42099,9487, 16323,34289,34289,38089,32268,26816,33011,38770,27570,37694,41135,41055,22293,42615,37962,38997,38817, 45314,42615,44288,37759,37794,38100,38327,27181,30982,35508,15510,26128,19881,41134,40047,38557,39931,40913,38856,39930,38045,2288, 40913,35508,2451, 2525, 41303, 43038,21570,20618,42098,40981,40852,17913,2306, 39321,40252,39394,42098,33011,9833, 32267,27569,35050,37284,33978,35508,34288,36002,11744,38761,41698,40216,33978, 33011,22616,40468,44761,43287,39525,44761,37284,13448,28834,18212,33215,36587,36587,22796,43286,17623,38295,43038,45314,43286,3919, 5831, 43286,37909,40108,42431, 44760,41391,42615,38183,40216,41589,38055,43569,41055,41954,39617,12180,39960,40913,42430,42824,40014,40420,37284,38628,45313,33215,35507,13820,14849,15551,35507, 34288,13593,39051,36587,45313,3218, 27568,10079,21863,36002,44760,43910,45313,35507,14286,36002,14179,15194,18211,12852,35507,39617,42614,34649,31588,29292,35506, 37753,41220,37795,39985,38406,38594,34288,17762,10065,44760,4679, 1596, 45313,2360, 8292, 43037,4178, 4939, 1614, 42614,45312,45312,3185, 35506,10859,35050,29785, 8565, 10412,35506,36587,25821,36002,33434,23148,33010,36586,33010,34288,30981,36586,33977,31587,37284,18546,37283,9353, 27180,13500,31586,35050,23768,26815,24243, 40047,36586,30980,36586,20715,33977,28396,35506,32818,30327,32266,34649,44760,26127,27179,36585,29291,32817,21322,19302,2927, 37283,36585,12608,18864,25005,35050, 34649,7917, 40468,10858,41134,15026,36585,11149,11592,8328, 45312,5933, 17048,10591,27961,8612, 34648,23360,18863,24752,37283,19009,10979,33010,44288,20846,19701, 33010,35049,12677,37283,25004,27178,35049,35505,19140,32817,23359,37282,24751,11379,37282,34287,11148,22615,25268,9612, 8362, 36001,8865, 8171, 41391,2385, 42256, 10786,11147,30979,33434,30326,34287,35049,36585,33977,32265,21202,36584,32264,37282,35505,37282,10641,30325,33690,23147,9110, 32263,9257, 14807,2629, 24476,18668, 2327, 42430,43286,18545,33977,33214,17047,33976,11207,11743,9283, 41134,39887,8483, 40420,32262,9721, 4556, 33690,9675, 36584,35505,29784,35049,4938, 19301,38718, 39137,39394,39344,12447,40616,23146,30978,35048,34287,8947, 19790,7261, 9430, 45312,10590,274, 38170,39075,41818,44288,33009,3217, 1020, 41134,42430,45311,36584, 33433,38743,40981,41818,39435,2602, 38347,45311,37982,39462,39582,41818,37899,41391,41133,1290, 45311,38494,14322,2928, 41818,43285,43910,36001,34648,14128,30324, 33689,16711,39168,33433,42256,23993,28395,24750,21569,34648,14815,35048,16269,12229,36584,33976,32261,25003,42614,18862,27960,35048,42255,37281,21321,33689,21568, 12177,33433,10941,44288,35505,31585,18485,41490,36001,15053,18161,1337, 33976,24749,44287,24475,36583,37281,41303,34287,40140,20258,34648,17960,36583,35048,33009, 32817,34647,40330,36583,37281,35504,43909,40330,35504,44287,37281,36583,41817,43037,33976,29783,34647,33975,43285,42824,38154,40517,38875,37280,31584,5829, 37858, 37989,39168,40667,37280,30977,33433,38855,43569,45311,36001,37280,20845,38398,45310,45310,36582,39774,32260,21567,32259,27567,33432,4179, 3776, 41391,3734, 23358, 36000,36000,34286,37280,16477,43568,35047,12565,37279,20714,142, 11317,40798,40517,12687,39075,42823,13780,41589,37279,39655,14794,39859,35047,41490,42823,33009, 30323,23767,17577,574, 45310,5370, 43285,9709, 41698,38848,8559, 38387,37279,36582,35047,41697,36582,39449,43909,43909,10711,28394,18734,11448,1759, 10640,10329, 42823,17265,11898,39270,39886,33214,33975,25002,35047,18274,34647,18861,26126,36000,40330,33689,7545, 10284,68, 4007, 25517,24748,33214,3497, 11255,3774, 42823, 43037,10480,29290,30322,36582,36581,16008,10283,20713,14840,10898,39910,17815,33009,33214,11206,7746, 40724,15794,37279,37278,31583,37278,12513,16509,35046,26814, 9217, 18109,34647,30321,35504,37278,44759,26813,37278,34286,33432,18382,33213,37277,11824,41133,34646,17430,41133,7812, 32817,19456,33008,9896, 23145,15172,45310, 43285,36581,32816,45309,37277,41490,34646,27959,35504,41954,33432,34286,37277,33689,37277,34646,33975,43037,44759,44287,35046,43036,35503,37855,37839,38103,32258, 38068,15410,41697,42430,13186,34646,27177,35503,23547,39136,4228, 17814,17046,43909,40852,14542,33432,38238,20064,36581,24242,35503,17813,38619,26451,20376,33213, 33975,32816,33431,5298, 9674, 42429,41588,1942, 6045, 246, 848, 36581,42255,41954,39136,44759,41133,40981,41697,43908,29782,13172,18210,30320,36000,39158,44759, 44287,38990,44286,39409,35503,39858,43908,41588,36580,32816,20844,41697,1696, 42098,44286,38938,6900, 33974,45309,35502,31582,40913,44758,25516,35502,5135, 20843, 43568,42822,44286,5467, 45309,41303,2901, 37276,44286,24747,17224,13366,43568,33008,29289,13687,25267,15171,35046,35502,20063,23992,40615,32257,29781,37276,37276, 3498, 40615,402, 12155,28833,33974,44758,12463,28393,13072,37276,35046,44758,44758,8848, 39508,33008,1338, 5543, 1301, 33688,1278, 10940,9351, 7588, 37275,36580, 28392,39218,30319,20163,11897,7145, 38474,734, 3816, 9130, 11378,36580,20495,13979,32816,18381,8134, 19139,43036,8085, 26450,31581,27958,33431,34286,17351,35999, 35045,13344,30976,7283, 28391,36580,7674, 37275,41953,44285,8295, 44757,34285,35999,35999,33431,11316,10479,9089, 8162, 39910,35999,16244,29780,21075,31580,27566, 377, 10225,36579,35998,34285,32256,35998,36579,35998,2844, 43568,20617,33974,26449,16322,15984,92, 41303,3773, 10256,10054,17672,30975,30318,6644, 37275,3775, 45309,44757,41489,44757,42255,2568, 44757,39321,42614,39655,40912,2305, 1251, 39016,43567,45308,19063,43567,35998,21566,42822,37841,21862,17393,42429,33688,12586, 41953,45308,39838,2500, 40079,19138,27176,27565,17912,7044, 41588,41817,163, 4621, 5540, 45308,39382,45308,45307,39886,40562,42613,37275,3144, 39858,43908,40797, 44756,36579,32255,34645,33008,41390,43908,17959,30317,18939,36579,35997,18860,23766,25266,44756,37274,11447,26812,36578,36578,43907,26125,36578,32254,33974,9199, 35045,43907,37724,38463,42429,44756,45307,44756,1893, 33973,24474,37274,37274,36578,10939,26448,39792,41588,44285,45307,44755,7286, 38815,10710,45307,26811,30974, 34645,35045,37274,33007,33688,43567,34645,39296,31579,25001,38658,10857,11591,35502,7244, 38960,3586, 5005, 5633, 6754, 7967, 19700,26810,33213,2756, 43907,19385, 33431,1443, 41696,3283, 43284,11022,25265,26124,10255,36577,42098,37273,36577,34645,36577,35501,37273,35045,24241,37273,31578,10148,41390,10678,8877, 14292,6122, 8053, 30973,35501,34644,33213,2240, 28390,21074,26809,32815,37273,27957,35501,14862,36577,30972,38942,11059,36576,35997,42429,39382,8201, 38190,40251,26808,30316, 12214,5891, 34644,41953,26123,16217,2516, 7887, 37272,36576,31577,30315,33973,40468,22963,32253,36576,9733, 41817,36576,44755,38153,27956,39984,37272,38039,44755, 40912,2947, 34285,41302,41953,40852,38546,34285,36575,16670,43284,43567,17500,7686, 26807,34644,30314,19526,27175,33007,42255,43907,38758,11962,40251,4072, 36575, 43566,35501,36575,17857,20375,22795,44285,36575,23546,33688,19455,9037, 8765, 44285,35044,13055,41952,39837,26806,15673,28389,25515,39910,45306,33687,21201,29779, 23545,37837,39321,40667,38207,41587,23544,22292,44755,37272,23991,19008,36574,37272,12098,17958,19525,30971,35044,40912,3610, 29778,35044,24240,11526,15772,14226, 33430,7931, 43036,37271,36574,28388,41489,35997,30313,45306,34644,28387,21200,34284,43906,35044,28386,7341, 43906,33212,40014,33687,40852,35500,9343, 40797,42613, 44754,37271,23543,43566,43284,13329,10282,9109, 36574,37271,19137,31576,23542,35043,37271,24239,42428,45306,43906,16443,23765,26122,34643,33212,26805,37724,39494, 10938,38599,44754,35043,36574,25820,36573,43284,9754, 39250,39774,42613,38175,43036,45306,8592, 37770,39044,33007,33430,43906,40980,36573,35500,33007,4201, 33430, 13144,39753,43035,36573,35997,40517,8836, 39175,43566,41817,1059, 34643,33006,35500,34284,39508,41696,18052,30312,3901, 7975, 38583,39654,42254,27955,35043,21861, 29288,35043,39689,44284,2755, 40329,45305,3520, 38563,42428,40108,36573,42254,39858,44284,34643,45305,38351,41390,40667,4373, 41952,26121,26120,32815,16301,36572, 35042,23541,10562,42254,22794,6712, 40140,19222,15399,8378, 35996,33973,42097,21565,30970,43283,29777,35042,44754,32252,12834,42822,39494,40615,44754,33212,33973, 37270,33972,33687,40373,32815,41952,38738,11823,14541,37270,27564,13847,39598,34284,43905,15088,38593,43905,44753,37734,45305,41489,37765,35042,41587,20964,37270, 37270,8015, 9917, 20842,33212,35042,31575,38001,38481,39435,40215,1476, 24473,37269,41952,17120,8599, 42613,32251,32250,37269,24238,10785,14574,33430,11058,33429, 9556, 8421, 9673, 36572,9287, 30311,22291,34643,38109,43566,39028,31574,9744, 43283,10826,30969,41951,42428,42822,39462,45305,41587,44753,42428,36572,35500,4371, 43565,30310,35996,41816,42097,44284,41696,42427,44284,43905,41587,42821,45304,43565,37269,33972,27174,38030,45304,40215,35041,38303,43035,37269,2515, 2946, 3554, 40215,43565,43035,39382,33687,11021,44283,40912,34284,44283,44753,43565,22455,29287,33972,33429,33211,45304,42097,42254,44753,40517,42612,45304,39461,34283,25819, 35996,34283,40911,41302,40911,43283,21073,44283,42612,41816,39075,43283,34642,40419,41951,44283,45303,43564,37751,37830,38484,33211,38989,38911,41132,37839,1136, 43905,42821,40797,39597,43904,5116, 39368,3355, 42427,44752,42427,45303,39886,9297, 3386, 39597,43564,37800,38371,41489,37980,41488,1038, 9347, 11896,36572,39524, 40467,6840, 41951,45303,579, 44752,21564,40176,36571,41219,35041,43904,8428, 3026, 39270,38351,40288,38539,43282,35996,35499,7838, 40288,22614,7811, 11994,43035, 33972,37268,16358,36571,28832,35041,18108,36571,41302,44752,35041,43564,35040,41586,32815,45303,36571,33006,11822,11057,40911,37268,27173,3256, 9581, 37268,36570, 20712,23540,40797,25818,8956, 10937,34642,39837,45302,27954,38203,41055,44282,42612,39331,13805,22793,40176,43034,37268,35499,33006,20616,35995,33686,26447,35995, 11446,34642,14071,17079,26804,24472,24237,5593, 44752,31573,36570,5433, 39774,33006,36570,37267,40079,43904,1934, 2490, 783, 3165, 2340, 42097,4478, 6605, 2643, 827, 44282,33211,23764,35499,35499,40615,39813,43282,40851,33211,16933,43564,33429,20494,20841,1778, 42821,43563,41302,45302,12942,44751,40108,43563,41696,41488, 3902, 45302,44282,34283,36570,34642,6989, 35995,13541,40562,40108,39270,41695,42821,44751,45302,36569,43904,30309,30308,35498,38983,38098,43282,38742,42253,1477, 3418, 5115, 3800, 41695,42253,42096,44751,38451,38937,9812, 32814,45301,8077, 40796,37267,37267,20615,42820,41816,43282,40288,43563,37267,37266,35040,30307,3111, 18446,39773,42820,35040,35498,27953,14961,43563,33429,35995,35498,35498,14189,15480,33210,34283,35994,36569,34641,35040,36569,45301,33428,22792,18209,24746,14018, 25000,14709,35994,34282,29776,34641,37266,24471,37266,35039,41586,6713, 34641,43903,37266,7940, 7760, 7828, 18805,33971,5193, 35497,35994,41951,44751,32814,17392, 36569,36568,22613,28831,13600,42820,37265,34641,36568,35994,44750,37265,40980,24236,9846, 32814,37265,20840,7656, 40614,40851,44750,43903,42096,32249,32248,6843, 14500,2205, 37265,34640,34282,34640,45301,33005,37264,45301,43281,41816,39218,42612,27172,26446,30306,42820,44282,37264,33971,37264,43903,42253,38926,39409,41054, 40215,45300,35497,23990,24235,35993,42819,43281,40419,43281,44281,42427,36568,43903,35993,12427,26119,23989,21717,35497,18938,27563,28830,15697,44750,37264,36568, 17812,36567,34640,22612,33971,27171,8712, 33971,34640,17313,43281,8453, 38769,1475, 4589, 18005,42819,29286,23144,1465, 37263,44281,23988,43034,30968,41219,36567, 27952,35039,43034,37964,29775,37777,39331,39792,40287,42096,41488,38115,40107,38926,41219,41132,40419,37867,38438,6606, 3205, 3110, 29774,2136, 36567,38953,2784, 8550, 15952,37263,15983,14011,3164, 3719, 44750,40372,37263,34639,35039,22454,40079,34639,20162,28385,34282,44749,37263,17761,35039,36567,35497,33210,531, 43902, 6990, 43280,43034,37262,2700, 42611,34282,43902,26118,16082,28384,4430, 34281,33686,35038,34281,11377,41132,20963,98, 38639,455, 5889, 43033,43033,2864, 33210, 1949, 35496,33428,35993,19454,33686,37262,18380,28829,33970,34281,18107,43562,43902,30967,27562,5791, 32247,41815,42253,44281,37262,1126, 43033,41132,34281,8272, 38983,6842, 28383,34639,25264,5679, 35993,9094, 43902,8470, 41488,1647, 39960,39537,39321,15250,33428,13949,42426,12, 40467,939, 4912, 3985, 2276, 36566,21991, 23539,36566,11376,27951,30305,34280,41054,45300,42611,43280,45300,42426,36566,41219,45300,41950,1824, 45299,45299,42819,21860,12421,33428,17045,36566,37262,35496, 25817,10589,38729,44749,33427,14930,6839, 24999,137, 41695,3984, 38548,41390,40372,33686,4535, 44749,26803,36565,14806,29773,33005,26802,33210,29772,13998,35496, 426, 6344, 33970,32814,36565,14036,13358,32813,35992,33005,35992,35992,7617, 32813,5114, 36565,38841,35496,19300,21199,21859,35495,44281,21320,34280,35992,18544, 35991,16007,33005,16216,37261,36565,6844, 37261,15025,33427,32246,35991,12945,33004,32813,29771,32245,15832,35991,27170,12150,39837,37895,39494,39729,37797,19299, 18379,34639,33970,22151,28828,37261,36564,37261,34280,34638,34638,37260,32813,33685,12504,33970,1045, 34280,5790, 35991,33969,34638,32244,37260,32243,7675, 4714, 24470,40724,40666,42611,25514,19524,42819,43901,45299,45299,2644, 37942,25263,44749,39671,12319,34638,36564,24745,598, 44280,33969,1724, 12617,41131,3982, 43280, 3419, 33004,12741,20614,23763,41487,3799, 39086,41301,40980,5890, 40516,4534, 43562,41301,21072,35495,11964,45298,20613,16965,30966,34637,17856,20493,19298,28382, 24234,15924,40287,21858,747, 2538, 41218,4844, 37260,39886,31572,4372, 40980,42611,41815,42252,39813,44748,37260,41131,43901,33427,12854,19523,33004,35038,27950, 28827,33969,33427,28381,38312,40013,12083,18106,45298,39423,39552,39241,12354,36564,35495,36564,21434,24998,36563,35990,41695,35038,35495,36563,39635,37259,34637, 35494,33685,35494,4328, 43901,37700,40079,32812,14848,35494,22791,27561,15041,16631,32242,16865,25262,16710,13490,33209,19699,36563,18378,20062,20061,33004,33426, 36563,26801,27169,12178,34637,42818,43562,37259,33969,30304,32812,23538,34637,22962,14521,23357,25816,35990,30965,12010,39728,3720, 6841, 33426,24744,35038,35494, 39208,44280,35990,24997,36562,36562,33685,6991, 38705,43280,37259,19615,11146,30964,15465,37259,33685,27560,33684,12645,35037,24743,33209,30963,28380,24469,25815, 21563,3609, 22611,24233,36562,37258,14597,43033,28826,42818,20257,35493,33684,36562,42252,45298,42252,26117,39059,37744,39709,39368,21990,23143,43562,40287,38419, 23537,24996,40796,39409,27559,35493,41694,33003,42818,45298,44748,910, 32241,39284,45297,26445,31571,35493,31570,34279,30962,14171,36561,35493,41950,14999,33684, 12006,32240,27558,37258,12160,42818,36561,29285,17119,14115,33968,22790,12422,12579,42817,37258,34636,40287,38269,41815,40666,43561,39003,42817,43901,36561,30961, 41815,6988, 15359,17957,29770,3064, 43561,43279,5789, 41694,40851,44280,3417, 29769,5678, 33684,43561,12369,40796,39654,23142,35492,41814,37258,45297,36561,33209, 15024,25814,27949,33003,33968,35037,33003,3063, 33968,8069, 36560,13343,37257,17350,36560,12889,7549, 42252,44280,7966, 13763,23987,15237,33209,35492,33683,19880, 12456,35037,44279,17499,26800,39910,40851,44748,12276,37257,23986,37257,32812,21071,37745,24742,35990,38084,45297,16190,43900,21070,38235,41131,16147,21989,39773, 43561,42426,179, 37904,4274, 45297,44748,38372,42817,39507,41950,40047,39792,42426,45296,44747,41487,2153, 33208,42610,41054,43279,36560,43560,38250,39670,40516, 39016,43900,45296,39207,42096,43900,43560,38298,38837,33683,14204,4972, 5680, 37257,18273,38583,43900,40724,38436,40047,43560,43279,1524, 6486, 6006, 27948,13750, 19976,34279,22961,35037,34636,36560,39158,33208,33003,34279,27168,24232,42817,35989,16798,35492,30303,44747,31569,23762,37256,34279,44747,33683,13910,16006,24741, 22290,41950,43032,36559,33208,15139,6485, 219, 41949,38466,44747,43032,33968,39423,35492,38837,40516,40723,32812,18733,34636,33208,34278,39394,42251,44746,45296, 38966,45296,631, 2275, 33967,4657, 6484, 45295,44279,5893, 45295,34278,40286,23761,39709,42251,37840,43032,40372,41218,40467,41131,38937,42816,43899,16215,35989, 33967,22610,37750,44746,37876,38574,39597,41694,39885,38114,37824,40175,37954,39136,37827,37256,35989,23536,33967,41814,35989,25261,22789,27167,15509,41218,35036, 45295,28379,24995,18105,43560,43032,17622,16755,13997,35988,14861,12950,16541,28378,35036,24740,25260,36559,18667,487, 43899,27166,10001,10535,9368, 37256,35036, 21716,43559,6007, 2442, 21198,6845, 44746,33967,24739,28377,34636,45295,35988,6714, 43559,28376,37703,22289,42816,44746,43031,35988,37256,33683,40372,35491,40139, 37255,35988,41586,33682,34278,44745,45294,43031,42425,16754,43559,42610,39654,21715,37255,37255,34635,34635,45294,6005, 35036,33682,12472,43279,37255,29284,37254, 45294,44745,35987,34635,5892, 6342, 6488, 43899,205, 5512, 38863,43278,42251,36559,35987,5432, 39249,40723,42251,40214,43899,44745,35035,40516,15574,35491,37254, 33966,36559,10106,34635,15040,22609,29283,34278,45294,29282,43278,27165,35987,40467,41949,35491,17002,43031,1113, 37254,19384,45293,25259,33002,19136,39393,33426, 43278,44279,20839,25813,35491,29768,22960,16669,36558,37254,15852,37253,36558,14650,31568,26116,21988,21319,30302,44745,12716,21433,35490,37253,27557,37253,16411, 13101,3453, 22608,26115,14430,21987,14793,18317,43278,21857,36558,26114,12254,15358,25513,36558,43031,32811,39320,42425,43030,42610,38624,43898,43277,43898,43030, 39753,43559,45293,43277,43277,19062,43898,34634,18484,21318,39537,45293,39597,33426,26444,41814,43898,44744,41218,39959,17149,29281,25258,29280,21317,41814,43558, 38738,41813,32811,33682,42610,42609,44279,35987,33966,37253,35490,35490,4202, 41586,44278,1851, 43030,23535,35035,14424,23356,37252,41813,19453,35986,20161,22150, 34634,37252,27556,34277,22149,35490,15672,29279,19879,41487,16146,27947,33207,25812,16630,4431, 41389,36557,42095,37252,13026,23141,37252,15618,37251,33682,35489, 35035,30301,36557,19975,12244,16864,17312,35035,28375,25257,20160,13499,41217,6607, 29767,34277,29278,33002,25512,11315,35986,1980, 44744,35034,35034,21986,39461, 33681,44278,35034,35034,32811,34634,33681,35489,34634,24738,37251,26113,35489,34277,18051,45293,36557,33966,33002,37828,44278,38050,38211,43897,39158,44278,39043, 40251,38622,35489,18316,13099,43030,27946,37251,32811,36557,20492,8539, 13285,35986,42816,25811,43277,30960,17760,22148,17188,18732,35488,12012,42425,5272, 19297, 28374,12071,45292,14188,36556,36556,35986,25256,33966,28373,26112,37251,25511,13843,23760,44277,24994,34633,33002,37250,37250,33001,3206, 37250,32239,37250,35985, 35488,37249,6343, 1354, 43276,3983, 37249,37249,40419,14483,42095,38298,44744,40013,18315,15398,39596,1618, 39536,33425,42425,39885,28372,427, 40979,2154, 3851, 38574,42424,33207,39124,39478,16932,1244, 32810,44744,41694,41217,44277,40723,45292,44277,44743,39837,40107,45292,41054,34633,16164,44277,26111,15107,33001,20374, 41949,25255,37727,38014,39270,39493,6838, 44276,33001,15, 829, 5594, 6225, 4911, 1491, 3025, 40139,33965,38712,24468,41487,35033,29766,5592, 40214,45292,41217, 45291,44743,41389,45291,44276,26799,21562,40139,43558,18804,45291,35033,45291,35985,43276,40666,45290,41813,41130,6487, 29277,35033,39792,21432,41585,41217,38459, 40371,33681,43897,43897,35033,36556,40796,37249,41486,37248,6715, 43897,18803,3521, 43896,3578, 16629,12051,29276,11821,4780, 37248,44743,20256,5422, 34633,45290, 26443,7809, 42095,45290,3795, 1205, 22788,45290,9916, 35985,1625, 6327, 8518, 37248,35032,34277,25254,41949,9622, 13519,27945,26798,11445,10534,8623, 7482, 11820, 9950, 10389,9437, 8395, 40979,37736,44276,25253,4197, 39331,38299,39753,38060,1913, 39330,44276,42609,44743,42609,41693,39086,43029,41813,44275,39959,41812,6208, 10533,3353, 6326, 42609,43896,44275,44742,30300,41693,33207,35985,37248,41693,43029,22959,44275,15375,22288,37247,20962,33207,44742,40175,29765,2668, 9845, 29764, 13342,28371,36556,11314,20612,8950, 8544, 30299,20373,34633,35488,44275,32238,36555,45289,36555,33681,33965,36555,28370,24467,14699,33680,36555,36554,44274,39409, 14466,31567,35032,45289,14805,11819,35488,38123,38688,40371,33001,38481,33965,20711,36554,43276,35487,37247,38397,44742,25252,33425,15106,21197,36554,34632,35487, 28825,15982,33425,18802,34632,26110,29275,28824,42816,28369,582, 43558,11525,37247,9326, 10078,10438,33425,35984,43276,32810,35984,26109,34632,15750,17811,37247, 22287,21431,11254,37246,5184, 44274,41948,37730,38083,37917,40795,40013,37973,42095,43896,39689,37246,35984,42815,9101, 10561,42815,12974,11742,11444,9545, 10588, 8153, 40979,4902, 6825, 44274,45289,8928, 30959,1268, 35487,42608,44742,39885,9646, 26797,34276,8873, 40286,38397,42608,40418,44741,4034, 43029,44741,37246,41948, 23985,10303,8411, 43275,6827, 44741,13396,39075,39393,26108,7883, 42815,28823,18859,17001,36554,40795,45289,18445,23355,40286,35984,37246,41948,40795,2178, 42608, 41948,13609,37245,33424,15831,35487,35983,41947,10709,32237,20710,42815,41812,42094,45288,43558,43275,42814,39524,35983,34276,36553,19061,20255,32810,44274,35983, 35983,6328, 23534,37245,41947,43275,37245,22453,38697,39858,43275,42814,40911,37245,3641, 16005,36553,43896,35486,39635,43557,39885,35982,95, 6701, 4837, 34276, 43895,39310,43274,12399,44741,40013,34632,35982,33424,16189,28822,24231,43029,22452,32810,35032,36553,35982,33424,43557,43274,35032,965, 2942, 34631,45288,11443, 19789,16383,28368,9000, 8356, 38430,5265, 11741,8447, 6823, 10897,5871, 38196,40418,5868, 36553,33206,30958,9402, 7247, 40329,4268, 8066, 8035, 5332, 7338, 44740, 27164,15601,40078,38048,43028,42250,18208,41693,11205,38863,39930,9477, 42608,40795,21985,10437,36552,36552,40329,37991,45288,38255,38020,39791,42814,37244,33965, 41301,19296,450, 21069,44273,21856,20838,19522,37244,39393,44273,43274,41216,39381,5776, 41301,41812,34276,13489,5584, 40139,34631,36552,24466,36552,34631,19383, 25510,15573,20491,12481,21855,45288,17187,41692,35982,43557,40012,42094,40614,41947,43895,33424,35486,36551,44273,35981,3316, 43028,6455, 26442,13656,34631,35031, 43557,36551,35486,16823,6976, 38852,39477,32809,37840,6698, 127, 1990, 666, 1002, 5499, 44740,5870, 43556,2415, 17429,35981,24465,12199,18104,27944,37244,34275, 25251,15534,40850,42424,37861,38806,41389,38730,41389,38053,43556,17956,24464,35981,38333,45287,29274,40794,42814,42250,32236,34630,29273,43895,27555,24993,37244, 25810,1809, 42094,16214,38234,37969,43556,39909,37945,44740,44273,38493,42250,43895,39551,41585,36551,11818,44740,43274,42607,42094,41300,7671, 39582,11056,25250, 33206,9444, 34630,44272,24737,37698,41812,37803,14499,45287,44739,44739,38440,38420,33423,42250,37243,38921,39791,41947,39320,44272,37769,38855,45287,37243,37243, 15635,33000,14792,12984,21714,37243,27554,17759,42249,41130,13444,42813,38087,44272,40723,40722,40175,39199,44739,41216,33206,35486,42093,40371,40371,43273,39728, 38758,41053,39320,39524,39423,40329,14520,23984,28367,39309,40138,13509,31566,22286,22958,27553,43894,41216,33964,37242,11895,35981,33000,29763,24736,35485,37242, 31565,35485,34630,38301,40175,38096,44739,42424,43273,41300,38315,40138,43028,39408,40251,27552,34630,7359, 39283,14823,37242,21430,16410,24735,43273,32809,33000, 37242,32235,33000,37859,35980,30957,35485,42607,8835, 10240,9981, 36551,8700, 43894,9858, 9753, 37241,36550,8377, 43273,26441,21316,37241,32809,9091, 15617,30298, 37241,17118,11992,42813,29272,23354,35031,35031,34275,639, 37241,16081,33423,29762,39241,44738,3843, 9645, 1313, 11204,35485,10120,11590,4469, 41300,40562,38332, 42093,42249,42424,1488, 35980,35980,39095,39616,41946,9395, 45287,18103,37240,35980,10639,5777, 35979,32999,17532,34629,35979,38124,38543,40614,37889,43894,41946, 11145,16709,13842,30297,36550,26796,15749,14950,39857,34275,36550,43556,37240,38548,17186,41692,6108, 43555,35484,7730, 45286,10254,27943,42093,32999,42249,21561, 45286,29761,36550,34629,41486,3134, 33206,28821,35979,37240,8614, 14321,25809,28366,1154, 43555,43272,39909,34275,25808,18050,31564,29271,35031,32234,35484,21315, 32999,34629,35484,26795,14860,14212,36549,35979,32999,33964,37240,31563,40328,41053,35978,40107,40418,42813,45286,43028,42607,40910,43272,39320,45286,42093,39183, 40666,41946,5106, 33964,36549,8863, 2124, 33680,15023,32233,37239,18377,37239,37698,45285,44272,6826, 40107,42092,27163,35484,39157,19135,4119, 25509,36549,34274, 8030, 37239,35030,20709,18604,44738,25508,24734,5266, 22451,37239,25249,13966,1933, 25807,21196,39449,43272,33423,23983,26440,13856,3056, 6828, 35483,13733,27551, 37238,19974,41811,12840,41486,39043,35978,40562,43272,25806,37238,32809,30956,17000,35483,32998,14585,17117,2048, 18801,22787,39283,41811,39381,12226,39175,36549, 40665,44738,28820,5421, 5872, 1719, 2316, 613, 27550,37776,43894,45285,42092,36548,40250,44271,14127,12901,16999,38201,45285,45285,38678,45284,42249,40561,43271, 34274,33680,41692,43555,40561,12289,45284,35483,4649, 214, 3604, 37733,3845, 41053,7120, 33680,34274,32232,16797,10210,11524,31562,14141,10239,34274,12165,11589, 36548,10077,39507,1383, 1385, 1384, 40665,45284,43027,41811,30296,35483,26794,33423,45284,42813,22957,32998,37722,3201, 3891, 38455,38586,38828,44271,5874, 35978, 42092,43271,10053,33422,35482,10076,7205, 8535, 21195,36548,11817,16903,12099,34629,12420,43555,36548,7418, 38609,40850,2917, 16931,17461,19521,18731,13518,36547, 34628,9360, 10140,36547,23353,43893,24463,35978,18483,33679,36547,38765,44271,40910,7703, 10532,22147,34273,18937,25507,8091, 41946,32998,4470, 27942,10587,9533, 44271,24992,35977,7510, 10075,28819,36547,8209, 39449,2511, 35977,9805, 21194,35030,25805,10184,37238,35482,28818,32808,30955,28365,40250,36546,44270,11253,29760, 9701, 36546,33679,35030,33679,24733,38339,41945,41216,35030,35977,35482,34273,37238,37237,35977,33679,22450,34273,36546,32808,34628,33678,12746,14017,18603,20611, 35482,34628,20159,17116,29270,33964,37237,35976,33963,37237,33678,33422,35976,21314,33678,36546,11252,14016,36545,41811,37237,12792,34628,40515,7868, 43893,43893, 3640, 44270,735, 43271,6458, 39524,28817,37709,38427,39909,39493,41692,40012,43271,39984,39959,44270,41691,18800,43027,44270,40850,6461, 38031,43027,38657,44269, 6459, 38073,44738,41945,42812,40614,4471, 40515,18482,36545,44737,2293, 43554,9453, 43027,10302,9352, 33678,8655, 40613,3937, 42812,6700, 41388,37952,10119,36545, 6699, 9607, 34627,9177, 41053,37833,42248,32231,27941,42812,9895, 20158,20708,18376,43893,43554,4032, 5670, 5041, 23759,20254,1562, 24230,35481,8405, 40328,37891, 5869, 4120, 40370,1078, 42092,38609,41130,40286,38278,9930, 1350, 5498, 1192, 4070, 4069, 35481,37236,42091,17264,39836,467, 38674,25248,43892,2780, 42812,542, 506, 42607,45283,44737,394, 6107, 23140,27162,29759,11251,36545,27940,36544,31561,22285,33205,34627,33963,33963,35481,350, 6824, 17810,34627,33677,30295,33205, 8914, 40910,34627,2961, 41486,45283,3938, 41945,43026,42606,6594, 35976,33677,26793,5105, 31560,43270,41052,45283,24732,27161,22146,30954,35481,12983,44737,12537, 41585,34626,37236,35480,35480,6456, 43892,3254, 3844, 2135, 3762, 4269, 43270,27939,7535, 40665,28816,33422,39168,31559,40418,12562,42091,45283,14035,43554,33422, 33205,22284,35976,42423,37785,41945,41691,37983,37236,32998,11588,21854,43270,35029,19221,41052,3892, 25506,32808,41215,40722,42811,42423,32230,40012,44269,7695, 33205,7480, 43892,9765, 25804,10139,9070, 8906, 2487, 29758,35029,23139,35975,7121, 44737,41944,28364,32997,30294,27549,11144,45282,45282,45282,40794,38080,38823, 39381,22956,2752, 8829, 7646, 14302,18858,26792,36544,35029,35480,35029,14301,6000, 6210, 21313,32997,34626,1204, 20253,15865,33963,28815,34626,17311,25505,31558, 10784,4033, 43892,44736,42091,42606,42606,40794,39670,43891,40466,40979,44736,40561,41130,44736,3513, 35028,8955, 6209, 42606,37814,6460, 39344,41215,43554,45282, 1312, 44269,42091,35480,44269,43553,4967, 2943, 42605,38226,43891,45281,39984,43891,43891,2105, 2337, 41388,1314, 38413,14665,23982,35028,26791,38792,39218,40046, 41215,42423,42605,42811,34273,35479,39709,2009, 37953,33962,8403, 38900,42605,10783,35975,21312,7644, 7882, 44736,35028,24731,34272,43890,21193,17621,33204,35975, 26790,33204,32997,35028,23138,35975,33962,35974,43026,21192,35479,35974,33421,24229,26439,17349,30293,35974,37236,8889, 26107,37235,9708, 15533,7122, 37235,32808, 14859,10896,9857, 35027,36544,37235,34626,37235,35974,31557,39836,8235, 40722,37863,42090,42090,38246,7252, 42605,7878, 33204,24228,5873, 34625,35479,27938,26106, 35027,35973,12235,37234,33962,20610,13485,32807,32229,16628,40328,44735,43890,3108, 34272,22283,45281,19382,18936,44268,37234,22449,30953,39393,40722,12372,34625, 18444,34625,30952,3023, 40850,44268,4035, 13035,27160,37911,41585,42604,40515,44735,32997,36544,39144,40794,44268,40849,31556,33962,41052,40466,43890,43553,40793, 40106,4582, 39408,42811,44735,41691,32228,42423,43026,38960,41944,37929,6593, 41388,34625,39034,43026,38086,36543,44268,41388,38058,43270,37707,38026,41485,41215, 36543,37234,37894,40978,44735,37928,39616,38972,42422,39309,39059,43553,42090,38880,40466,5104, 20837,45281,39959,42604,40466,5420, 23758,32807,36543,30292,43269, 17911,40665,43890,44267,42090,7119, 43025,45281,44734,41810,26438,40910,43269,9611, 37234,22955,12964,34624,38677,40793,3413, 9606, 33961,27548,39728,40515,469, 41485,23352,33677,37233,5419, 33677,23981,44267,42811,35479,16062,29757,38634,33421,27937,39174,14029,20961,3682, 33961,16080,37976,18314,35478,34624,32996,33421, 15572,33961,6457, 43025,34624,36543,14357,24991,42810,5418, 45280,44267,1349, 42089,44734,35027,45280,11661,42422,33961,34624,45280,30291,26789,23757,38416,2336, 2317, 40106,41300,1506, 5669, 44734,13855,962, 3308, 39507,39570,44734,40514,15851,45280,45279,32807,35973,44267,36542,38966,37784,39773,39773,7767, 40793,42422, 45279,41052,39709,44733,44733,43889,34623,37233,29756,14987,12340,43025,15443,13667,18443,34623,37233,20060,19614,37233,39067,43553,37232,35027,43025,41129,43889, 42810,45279,35478,37945,40417,40721,41584,38747,2118, 38917,41584,42248,40078,22954,19698,19295,33676,37232,34272,45279,40561,40560,35973,40138,41810,43552,4412, 45278,42422,42810,42421,41810,37939,44266,40465,42089,45278,15278,23756,15887,43024,45278,21068,39168,42248,43024,35478,24462,2936, 39753,43889,45278,41691,19381, 34272,36542,9663, 14708,34623,43269,19973,30951,3751, 43269,32227,36542,35026,34623,2214, 10612,31555,32807,35026,42604,1009, 35026,22282,1598, 42810,5095, 44266, 41387,44266,40793,45277,26105,35026,19294,6952, 44733,44733,39066,43552,37983,39813,40664,41944,40721,43552,42421,43889,44732,40978,41944,45277,42604,43268,40138, 44732,39772,43888,42603,39708,38458,40046,39283,45277,42603,45277,41584,38054,40909,36542,13346,35478,37232,43888,1601, 13447,23351,15277,6088, 42809,36541,34622, 43268,2773, 15052,38458,30950,44732,30290,33676,25803,3786, 41051,36541,28363,23350,36541,19972,35477,34622,10856,45276,41485,41485,33676,3377, 35973,35972,19293, 36541,17428,35972,32996,22953,21853,27159,31554,35972,36540,32806,5172, 27547,32996,42603,41387,33676,39183,44732,42603,45276,35972,42809,41484,40328,1511, 38266, 43268,41943,38369,43268,39536,38622,41484,42602,40613,2984, 39192,38628,39493,43888,39930,39523,43267,43267,44266,3878, 40613,38859,43024,22145,37232,33960,19697, 35025,16753,33960,12209,34622,15655,17716,35025,39728,43552,43888,44265,40214,43024,43267,43887,41214,38712,40417,42421,38894,39582,39812,43267,39857,40721,38231, 39058,38513,42602,39157,44731,4701, 39772,33960,39616,39309,45276,42089,45276,42421,43551,42602,41484,39183,39857,39772,42809,37938,37811,24461,37231,22448,14341, 29755,34622,14858,38225,39022,38769,39058,35025,27936,40560,44265,43887,43023,39688,40909,44265,38953,40285,42602,38604,42420,38336,41129,43887,41690,39449,38174, 43551,44731,3226, 32806,27546,31553,12632,16036,30949,9871, 35477,35025,36540,36540,31552,12833,15464,12424,42420,9085, 7788, 40909,471, 6680, 43551,43887,11523, 36540,19007,19452,43266,37231,41943,35024,39477,23980,38186,22447,35971,25802,33960,35971,10936,7722, 32226,15208,34621,37231,8872, 9904, 37231,33959,10560,5854, 42248,7913, 19220,37230,35024,12729,23979,33675,33675,35971,12720,27545,33959,39958,44265,41690,29269,27158,16004,33421,32806,44264,4962, 35024,43023,4311, 24460, 33204,34621,36539,5658, 33203,35971,35970,32806,35477,22446,33959,33420,35024,43886,36539,25504,8809, 41214,44731,41584,36539,35477,39523,39095,38705,40514,41943, 41299,40978,38212,41484,38786,37852,42601,35970,38040,38149,43886,34271,35023,38715,41690,37775,37887,40664,38240,38717,38061,38164,39207,42809,41810,33959,44731, 35970,35970,6950, 39984,42601,5252, 39752,18799,45275,36539,37973,42420,45275,25801,24227,34621,24226,25503,18272,13566,35023,21311,13933,24459,30289,14330,18271, 36538,44730,23533,16930,39727,23137,44264,35023,36538,24458,32996,20609,37230,21310,13336,18049,35023,43886,36538,24990,33420,38356,39217,40792,44264,44730,44730, 38960,37230,21560,29268,35969,36538,29267,27544,24989,37230,40849,6800, 33958,35022,4109, 34271,1579, 1008, 33675,41299,30288,1968, 40792,41809,39836,40285,33675, 41943,13973,43886,31551,32995,42089,24730,30287,21713,33958,37693,43023,34271,44730,14092,29754,18207,28362,34271,39136,45275,43885,42247,44729,36537,37229,6797, 35476,2333, 3249, 34270,36537,34621,35969,42247,37229,45275,35969,33203,44264,37229,34620,37229,35022,35022,8710, 39909,24988,32225,37228,33674,33203,19006,40417, 43266,24457,35476,32805,33674,42601,21309,44729,24729,21067,33420,37985,38533,37228,21308,14002,43266,41387,36537,1401, 5026, 37228,33958,15720,43551,23349,13517, 42247,35022,5487, 7929, 11103,32224,32995,22607,26788,19134,9388, 11816,37228,35476,44263,8413, 18375,37227,37227,35969,10935,7901, 11313,16822,15343,10000,27543, 14243,14847,23532,14519,8579, 15087,35476,12149,44729,3832, 16476,23531,21066,34270,24987,31550,26437,39551,8486, 39028,2854, 23136,15748,34620,19451,8616, 32805, 37227,19450,29266,35021,35475,35475,41690,24728,30286,23530,16863,14680,22786,18374,35021,19696,34270,12468,37227,12118,22952,35021,18373,37226,29753,35021,33674, 29752,14311,10224,44263,6798, 39791,5973, 9221, 41299,37226,14389,20960,7444, 17498,32223,36537,18543,852, 1897, 39581,45274,40174,42808,39708,43023,7995, 6298, 39983,41214,41689,43885,43266,42247,45274,43022,35475,41583,43022,9412, 37226,29265,39249,33674,37743,41689,43022,41809,35475,40909,37895,12432,10328,12799,12629, 34620,1688, 6195, 37853,41051,45274,3051, 3750, 20252,39283,11660,29264,32805,4022, 4890, 35968,11250,29263,35020,33958,367, 37704,45274,2370, 6569, 36536,37226, 29751,36536,24456,41483,43550,33203,40664,33673,35968,5170, 14846,35474,37995,40849,43550,42420,40327,43265,44263,44729,31549,41942,43265,43885,38350,41689,41583, 43550,2268, 39296,43885,42808,3307, 40174,1602, 42808,3877, 45273,3227, 43550,44263,43265,44262,38284,43549,29262,43549,43265,45273,40792,8480, 41583,35968,34620, 44728,34270,37762,4961, 38010,39367,44728,38013,41051,6196, 39670,42246,5661, 38458,35474,794, 38226,45273,43022,41214,38451,39670,39536,45273,38578,38039,43884, 39392,38593,39183,40327,3444, 44728,41387,34619,35020,8953, 45272,5855, 30948,37967,42419,38869,28361,40106,37225,33420,34619,24455,26787,33202,24454,39034,43884, 41129,44262,40417,43549,38742,37916,43884,40792,38652,40370,38254,40560,39551,43884,41583,40560,44728,35020,39241,43021,37225,40978,41213,41483,43549,43548,39930, 6197, 39058,42088,3194, 45272,38165,40465,38162,43021,4957, 16213,30947,17531,45272,8613, 35474,1908, 41299,34269,3349, 11522,9236, 43883,23529,35020,34269,55, 2412, 2332, 43548,34269,44727,29750,40977,40416,26436,39066,42601,43021,45272,41129,45271,5567, 39422,43883,40977,44727,2411, 41483,17348,41689,23348,39066,35019, 37799,44262,38021,38546,41298,39157,37974,41128,39551,38346,43021,42808,35968,43264,15424,42807,37225,27157,31548,18666,21065,35474,34269,35473,40514,34619,2147, 15039,37225,15498,37224,26435,7965, 23755,4108, 42807,11521,44262,39022,39226,43020,43020,41942,39884,42419,35473,38458,45271,45271,40327,39241,38486,40908,39857, 38855,1789, 44261,2687, 42807,35019,4190, 32995,41688,41128,39282,40465,44727,43548,44261,39422,38077,42419,38764,40250,43548,41483,43883,43264,31547,36536,42807, 32995,2024, 1143, 42246,3596, 34268,25800,35967,26104,7438, 38717,35473,29749,34268,25247,34619,19613,32994,21559,36536,27935,23347,33673,35967,35473,19520,40285, 44727,40370,44726,35967,20251,13989,28360,27542,44261,33673,35967,21852,36535,21712,37224,28814,15249,30946,13996,35019,12895,9665, 39461,39523,43020,39523,6799, 44726,12515,28359,32805,36535,43547,25246,37224,35472,33957,30285,34618,37224,31546,22445,44261,37223,29261,39477,15696,12302,17427,24225,22606,16188,42806,40908, 41809,45271,24727,12069,33957,29748,32994,28813,40174,44726,16243,30945,7925, 40613,7513, 24453,6436, 36535,30944,36535,26786,35472,4312, 34268,36534,12686,12322, 26785,22281,15207,24452,7287, 2025, 5974, 45270,33419,35472,32222,32994,15014,20608,15695,37223,36534,24726,33957,20490,34268,45270,35966,30943,34618,35966,1788, 41482,34618,35472,29260,19971,34267,35966,22951,29747,35019,37223,14429,29259,34618,24224,5975, 37223,10183,33957,25799,15719,29746,43883,45270,16902,22950,37222, 36534,29745,27541,37222,35471,18730,37222,35966,37222,20836,27934,43020,14783,30284,40174,32221,19292,33673,37221,29258,20157,34267,15276,17809,7875, 14839,41482, 8062, 15965,35965,22444,11815,13401,20059,8014, 45270,3443, 37711,38234,37903,37926,42419,34617,35018,9877, 7425, 14596,35018,13218,36534,6681, 35965,18857,33672, 17855,6570, 36533,28358,32994,35018,33672,34267,18481,25798,27933,7575, 41809,43547,34267,29257,22949,302, 2483, 4518, 18313,38348,37221,34266,35471,13557,45269, 37960,38397,45269,40012,9894, 42600,37221,27932,35018,45269,32220,38989,39983,38047,37923,35017,45269,17148,45268,45268,43547,44726,5659, 38893,42600,3573, 43264, 43882,40559,40214,41582,36533,39958,42418,38989,41128,41128,43882,40213,40173,39669,43882,41688,27156,38852,44725,38140,43547,39102,44260,45268,40664,43882,43264, 40721,42600,40720,43546,2910, 23754,44725,43881,44725,757, 42246,42806,33419,39581,40849,34266,40327,44725,42418,45268,42806,44724,40514,45267,33202,45267,44724, 40513,5407, 42088,40416,42600,41582,39669,39812,38761,44260,37221,42599,40663,40848,39182,44260,37799,5853, 40416,43263,41688,41808,42806,38869,37914,45267,27155, 44724,42418,35965,42246,43546,43263,44724,43546,24725,7095, 39381,18798,36533,43546,34617,42599,41298,41582,42245,27154,38546,41688,37700,44723,41298,39688,42599, 44723,35017,25245,40848,28357,21064,43263,44260,38948,43545,15718,44259,19133,32219,44259,45267,41687,38810,45266,43019,45266,43019,43545,43881,43019,42805,40465, 41386,43019,39884,44259,45266,41127,41213,39207,43881,43263,44723,33202,32218,42805,41942,43018,41942,39477,39422,38416,39727,42418,44259,36533,25502,40908,3509, 30942,42805,43881,39772,41386,28356,39476,39958,40513,1568, 39261,16752,35471,30941,38327,44723,42805,43018,45266,42245,43545,43545,40848,39309,42417,39616,39708, 38906,38562,42417,39296,45265,45265,43880,39908,40720,41482,39282,40720,42804,44722,45265,39856,41127,44258,40370,33419,43544,42804,43262,43880,42245,41687,42088, 14034,44722,45265,43544,43262,43880,41482,45264,41808,39058,44722,13267,41687,43880,19005,41687,40369,42599,36532,40720,42598,38782,38805,42417,42804,40250,42417, 33419,41481,35965,29744,20835,35471,32804,40559,43544,43018,34266,38764,7499, 43879,25244,12060,19612,38531,41051,16862,42416,33202,22948,38511,38233,40416,39016, 39448,34266,37220,39476,38261,44722,18206,32804,40977,45264,35964,43879,19611,1787, 41582,18729,32217,38455,43018,28355,34265,27153,33956,45264,42088,18270,26784, 5568, 39908,33201,37220,36532,35470,11894,10825,11520,35017,37220,37220,7094, 23346,40513,16901,42804,2638, 6193, 41298,2805, 2611, 43879,36532,33418,37219,7442, 16998,22785,37219,12553,43262,36532,31545,41213,43879,3016, 40612,33418,37928,39929,12520,37219,30283,36531,34617,43262,1169, 29256,45264,42087,42803,45263,40249, 34265,5169, 36531,35017,37962,38121,45263,39157,41686,702, 38200,2458, 213, 3407, 40415,40612,30282,35016,42087,38230,39199,37219,2046, 3833, 38868,35470,40326, 34617,40908,39358,3676, 40137,44258,16357,5660, 44258,35964,22784,35964,13103,12338,28354,33672,37218,44258,32993,34616,40612,2885, 27540,16442,39522,27539,43017, 22144,29255,43017,37218,37218,30281,17955,28812,27152,34616,32216,12991,34616,31544,16300,12243,39199,45263,39174,19970,36531,39232,43261,38341,37218,35470,39581, 37217,32993,42416,35016,37217,44721,37217,21851,37217,35964,37883,8725, 39958,26783,41213,26434,37216,37811,42245,14247,42598,38144,39581,43261,39836,39522,34265, 38926,38037,2911, 5027, 44721,43878,38491,13200,40907,38064,42803,38884,40559,38081,41808,43544,39367,32993,6801, 33956,20707,19878,32804,42087,38937,5570, 37216, 14986,37216,33418,35470,742, 41581,45263,1553, 42598,17954,13191,979, 6948, 23528,25797,37216,44257,44721,35963,45262,41481,33418,25501,25243,38586,35016,21850, 32215,35469,45262,42803,41941,44721,16964,43017,45262,33956,42087,36531,30280,36530,30279,37215,35016,36530,45262,32993,23978,18542,35963,33417,35015,32992,6802, 36530,37215,43543,16299,9936, 22947,24724,34616,45261,45261,12097,39791,23345,33672,35963,17910,11249,26433,35015,37215,17044,21558,39654,5569, 34615,3597, 34615, 20959,32214,28353,17460,17576,33671,45261,26432,41941,43261,44720,38847,42598,44257,43261,33201,14584,35469,36530,8499, 42803,37215,9129, 8178, 45261,10281,10356, 35469,11893,35469,42597,40907,42244,44720,35468,40559,41808,29743,11375,44257,37214,1420, 33956,35963,35015,2828, 6951, 4829, 38473,42802,36529,35015,1847, 25242, 41581,43017,2130, 44257,6299, 6571, 45260,45260,43260,35468,27931,33671,795, 41807,37214,35468,35014,28811,35468,20489,38805,45260,28352,40907,35467,41386,33201, 32992,1308, 33201,31543,22946,35014,33671,25241,35014,45260,44720,4959, 18160,38150,35962,36529,38746,39688,41807,37214,42802,18269,35014,42244,31542,26103,41481, 34615,43260,14949,35962,31541,45259,28351,33955,40046,9068, 41127,31540,35962,44720,27538,37214,30278,530, 4830, 44719,175, 3195, 719, 5025, 101, 40719,36529, 15248,36529,24986,40369,37954,37213,41212,40326,33417,878, 9929, 32213,34615,27151,33200,39812,44719,21557,35962,33955,38325,41212,6949, 34265,24723,30940,34264, 35961,42244,37213,35961,26782,36528,41686,44719,30277,15850,36528,33671,21711,37848,41212,43878,42802,44719,45259,45259,37908,42597,38323,43543,42416,43878,6194, 35013,23135,4516, 25796,21191,44718,23753,32804,14368,6087, 42802,35467,23752,13793,17078,41050,43878,43877,39669,43016,44718,19877,38403,35013,43543,3442, 41297, 14649,484, 22945,2531, 33670,35013,4958, 39596,42244,43016,35961,41807,42243,39066,42597,39570,43016,38796,43260,3970, 40907,39043,40663,43877,33200,23134,41481, 43016,43877,39117,38291,39983,45259,43260,45258,45258,27150,43877,39357,39752,43876,43259,43259,37213,41386,32803,35013,41385,44718,28810,42416,35467,25500,33955, 30276,40791,15864,38037,40848,45258,42801,16600,35012,17909,15182,29254,32803,31539,38181,39522,39249,21556,16997,40791,28809,39143,37792,33955,32212,34264,35467, 13328,41385,21984,35012,32211,526, 3787, 22280,29253,13762,44718,44717,16751,41941,25240,36528,12368,41941,41807,17908,38384,12000,39117,45258,20706,22783,27930, 23527,16900,19788,38090,19132,14985,43543,45257,25239,45257,18442,12232,42597,43542,34614,35466,35466,43542,40464,43015,28808,29252,39101,44256,42596,35466,32210, 27537,37213,25499,36528,18797,29742,17907,41212,33954,23344,13771,34614,24223,45257,27536,33954,3248, 38468,40977,44717,13582,13570,33670,32803,24722,35012,29741, 36527,36527,32992,19610,37212,30275,34614,31538,25795,36527,13941,12484,1410, 43259,14033,35961,35466,14845,32803,28350,16963,35465,13534,12349,37746,40326,18268, 41480,36527,40906,19449,29251,43542,11374,39156,39507,10105,42243,43876,39240,41940,40791,40173,40464,40369,38067,38237,40847,16708,41050,38147,14960,40046,24985, 42596,43542,20058,38875,37943,40078,42086,38218,38815,44717,16321,14413,38143,37762,38628,39086,33954,23343,34264,34264,41686,4960, 1092, 22143,27149,32209,35960, 31537,16571,16899,33954,35465,27929,22782,20488,23133,16898,31536,35960,35960,33200,35465,33670,33417,35012,21307,34263,36526,37212,30274,35465,28349,19969,40906, 33953,7759, 35464,34263,29740,36526,35, 4147, 1988, 3247, 43876,38847,40285,41050,42801,36526,28348,1643, 45257,37966,38932,39022,44717,37212,33417,38454,36526, 44256,38153,37212,34263,512, 38543,40513,40106,39422,43259,32992,4517, 2772, 15815,36525,43541,3348, 607, 40011,5171, 416, 2985, 2748, 1521, 1, 476, 3752, 4998, 2707, 3215, 37783,38921,39908,44716,42596,35464,28347,45256,44256,41211,17906,18665,32802,40512,20834,41480,39727,44256,40719,41127,11659,4290, 35960,8520, 42415,35959,28807,33953,37211,28346,38399,39771,27535,27928,532, 5708, 25794,35011,40791,44255,12763,37211,12476,37211,23977,40173,40906,43876,43875,33953,41297, 38597,30939,15051,33200,35959,38215,34614,37987,41806,5129, 43258,40011,44255,42415,40612,44716,39111,42415,10138,37890,38827,41297,38077,33953,20705,27534,15423, 39634,30938,30273,33952,37211,24721,19695,44716,22781,31535,10934,43875,8703, 8628, 8143, 25793,7183, 40611,2263, 24451,10182,2018, 40105,41686,647, 38500,44716, 39983,42243,44715,41480,40249,43541,39634,43015,40173,40663,33952,43541,8399, 23132,45256,29250,13846,41211,38294,43541,39050,12038,36525,8585, 9204, 43258,28806, 44255,16599,12303,8506, 40790,44715,31534,35959,34613,20607,7154, 38422,8236, 33199,11020,33416,10436,8548, 14707,29739,25238,4088, 37210,19131,9273, 8274, 45256, 27927,17310,11587,16187,21063,36525,32802,33199,6146, 25237,14423,13565,44715,18541,21429,43540,39207,45256,42415,43540,35959,37210,27148,30272,42086,42243,40906, 37210,31533,35464,30271,35958,5928, 34613,32208,37210,35958,763, 43875,32802,36525,34263,37209,31532,21710,37209,36524,5358, 35958,27147,24720,33952,34613,37209, 41050,21062,33670,35464,31531,33952,31530,29249,21061,39010,43540,44715,41480,44255,39536,45255,33951,22605,14063,44714,34613,42414,21190,37209,38539,44714,38905, 41297,5461, 34612,36524,30937,33951,26781,40045,37949,42801,26780,38582,39124,12110,37208,18796,39240,41940,45255,41940,38966,38394,23751,35958,13018,14498,35011, 32207,42596,39344,6888, 16821,37907,4174, 38571,40078,44254,42595,2820, 34612,253, 5215, 13830,4443, 13323,32206,30270,37208,14529,16861,41126,42242,45255,45255, 35011,33951,34262,36524,96, 4927, 1575, 40326,12847,34262,19876,35957,8363, 5625, 10181,10478,8886, 9316, 573, 21983,5067, 34612,11248,17575,41479,16186,28805, 12186,19291,35463,39408,40213,41479,14055,20057,24222,31529,34612,5927, 24984,18795,20372,5926, 1211, 31528,35463,40369,44714,35957,30269,32205,37208,35011,33951, 28804,12650,43875,36524,3268, 44714,16475,45254,42242,42086,43874,33669,24983,22443,20487,37208,41806,45254,37207,39357,39884,44254,42801,33950,36523,40719,6744, 40976,17185,13920,14042,39653,41296,44713,40172,40611,39957,40512,44713,16268,42086,37207,29248,43258,17263,33416,27533,7311, 6637, 26431,33199,16123,14804,39835, 35010,40512,14679,581, 40719,39812,44254,15793,16508,40464,15830,41211,43258,39199,4050, 4547, 6886, 45254,40558,15170,42085,19694,43257,42414,40368,18312,42085, 21555,25792,27146,45254,42414,32204,846, 30268,36523,33950,43874,806, 21709,39580,9605, 8242, 41940,45253,19004,45253,25791,17758,34611,33416,27145,11892,33669, 20371,36523,44713,29247,37207,42595,36523,20704,34262,35463,34611,41211,34262,42595,22780,16962,30267,23976,34611,41806,36522,36522,20250,20370,37207,4049, 35463, 35010,16929,18935,33199,33416,35462,33198,22779,20056,16079,28803,17808,33950,44713,13199,42414,32802,25236,26779,35957,33415,34261,26778,35462,37206,19968,31527, 33950,35010,35462,34611,37206,35462,23526,37206,15792,44712,32801,17115,43015,25790,35461,20486,40905,41685,35461,37206,26430,34610,14300,35461,32991,34261,43257, 44254,12224,35010,26777,36522,41210,44712,43874,35461,25498,41685,38805,37205,40105,33949,35957,36522,14422,41685,901, 39569,40415,25789,25788,16035,20485,23975, 14782,28345,33669,37205,39156,13516,18664,35956,44253,8876, 32203,40105,41939,45253,3031, 2250, 45253,31526,33415,1333, 45252,535, 5128, 1538, 45252,32801,40077, 19130,42242,372, 5707, 42595,39835,24221,18267,19219,23750,41685,13165,29738,37205,40905,45252,44253,27532,43874,33198,15311,35956,35956,18540,10611,10782,35009, 9662, 39550,41296,27926,26102,37205,12599,35460,34261,35460,20833,38875,8408, 42413,3091, 45252,37204,27531,33198,6638, 36521,16163,23974,39308,43540,32991,39421, 45251,44712,44253,45251,42594,21982,41479,30266,45251,38931,45251,43015,45250,41806,22604,4929, 38355,42800,1482, 37204,18159,41939,42594,30936,23525,45250,43873, 37705,42242,38069,13082,2733, 32991,16961,5530, 28802,33415,36521,34610,43, 2566, 43257,44712,35009,40464,39448,16897,35460,33415,41385,751, 1590, 44253,43257, 44711,29246,37204,15169,22142,13697,33198,29737,34610,19003,36521,40663,40718,32801,38034,39408,43256,42594,39407,43014,39435,42594,42413,42085,35956,34610,35460, 21849,44711,45250,44252,7542, 36521,45250,18728,13452,33669,15981,27530,33197,35955,33949,13603,35459,43014,23524,17715,36520,42241,35955,20156,11995,37772,4801, 41581,43256,38925,42241,37920,42085,32801,38308,45249,38225,40790,38773,40213,41805,38859,41939,38606,44711,43873,45249,29736,34261,35955,24982,37825,33197,43256, 34260,33197,43873,40105,34609,23973,43539,43873,38847,44711,27529,24981,35459,26101,38079,42084,43539,45249,39615,42084,44710,43014,40463,44710,43256,42593,43255, 45249,39249,39957,33949,29735,45248,12105,24980,33668,34609,39296,39884,27144,9364, 33949,18441,32202,34260,33668,42084,40213,37204,38296,38616,19967,43872,38084, 38702,40463,39596,43255,44710,22141,19609,32991,21708,36520,35459,29245,36520,37203,35459,33197,36520,41939,39550,45248,37203,44252,41210,45248,40284,42084,40463, 43539,43539,44710,45248,42800,35955,26429,20155,15259,19129,32990,36519,30935,32800,34609,36519,35954,17905,31525,24450,37203,15409,17904,37203,33196,33948,34260, 36519,29244,28344,15616,16668,36519,19608,33414,36518,12035,14126,33668,16996,34260,42593,40325,42413,41479,45247,41478,32201,35954,43872,6521, 44709,34259,34259, 33196,18311,41210,41805,39407,44709,36518,33668,44252,37750,38697,40011,41938,37827,39123,38231,43255,41938,37994,42083,43255,45247,37202,38432,39010,38334,44252, 42800,45247,34609,39050,39957,43538,40137,39330,41938,43254,44709,2543, 40558,30265,44709,39240,32990,43254,35458,42800,35458,40905,40284,42083,680, 40512,41126, 33948,45247,37202,26428,33948,44251,6380, 35458,20154,36518,34259,33414,34259,44251,22442,32990,39669,45246,32200,42799,32800,42799,42593,36518,33948,39957,29243, 35009,36517,43014,43254,42799,41049,36517,42799,42413,40137,7465, 38769,35458,33947,7184, 40212,1984, 4930, 29734,20369,14739,45246,28801,36517,7031, 1829, 12137, 41385,12461,35954,12021,41478,38868,42798,34258,35954,27528,15275,40011,39003,43538,44251,44708,45246,23749,34258,35009,33667,34608,8732, 37202,42412,37202,29733, 35008,37201,29732,34258,20484,21189,35457,36517,23748,21981,33667,23131,15571,40010,32199,20055,33414,22279,10531,18934,28800,42083,40284,21980,11442,44708,35008, 32990,33196,32198,25235,45246,36516,27143,35953,33414,35457,22778,35008,25787,34258,34608,36516,17953,39110,31524,33196,18539,21428,35008,32197,35007,18372,33667, 32989,20958,25497,33195,44708,26776,28343,18266,15771,35953,36516,40662,33667,35953,15654,13473,22140,32196,23747,16298,16003,12668,25496,41478,5360, 27527,44251, 32195,32194,2473, 32800,37201,30934,33413,22441,44708,35007,15181,43538,34257,35457,16474,35457,33195,27925,27924,28799,18310,45245,15615,14320,43538,24220,45245, 42798,40325,18440,36516,35456,34257,6639, 33195,29731,30933,33947,33666,36515,35007,6742, 35007,23130,31523,13196,14451,23342,36515,34608,35953,320, 5531, 44707, 4548, 40905,45245,44250,36515,20832,31522,40172,40249,40284,3142, 35952,42412,16928,27526,38343,44250,45245,42798,44707,4488, 33666,35006,42241,44707,11586,33947, 38280,30264,14450,37201,12395,33413,42241,39982,44707,39790,3090, 37201,45244,40847,38989,4997, 35006,16061,7537, 41296,38893,45244,44706,42240,30932,37200,34257, 19693,22139,44250,32989,42083,33195,33947,7234, 39167,32800,3293, 33194,45244,40976,29242,2497, 44250,43537,45244,35006,2111, 7226, 39074,44249,40976,24219,11998, 37200,9244, 35456,1213, 44706,34257,34256,36515,9338, 39929,44706,39956,44706,38751,34256,16570,18794,34256,28342,29241,33666,19607,13049,2171, 5460, 41581,40790, 43872,44705,45243,37848,38769,45243,40511,88, 2192, 37200,37753,39167,39110,38323,40790,32799,43013,41384,45243,39883,38352,43013,44249,42798,45243,977, 44705, 5925, 43537,45242,43872,3032, 42797,38539,40249,38571,42082,40248,40325,42412,38554,39392,45242,5623, 914, 31521,658, 38593,45242,2973, 45242,6034, 11441,9539, 10933,9538, 9580, 8234, 43013,43537,44705,38042,38360,43254,38847,44249,41580,41296,43253,4175, 44249,44705,42240,42593,10137,33666,1334, 40976,38320,45241,3092, 6525, 1335, 34608,32799,35952,35006,18602,16145,2405, 27525,549, 37200,34607,1556, 33946,33413,43871,35952,44704,34256,33946,34255,22440,34255,32989,2598, 34607, 35005,37199,32989,35952,16242,22777,2872, 31520,36514,28798,37779,14558,37893,20054,23341,37199,32988,13613,42797,27524,34607,14041,43537,32799,39174,42412,12692, 36514,33413,39752,38050,42240,760, 42797,36514,1582, 25495,37199,27142,43253,44704,35951,26100,33194,2623, 2873, 42240,39461,38377,41049,33665,44704,30931,33946, 25494,36514,1212, 2764, 43871,35951,2732, 37804,39929,39206,37899,45241,39752,1555, 43536,42797,44704,24218,44703,37823,42796,38051,37199,44248,42592,10824,23523, 37198,45241,32799,37198,28797,31519,37198,37198,17184,35951,38597,12111,44248,39569,34255,2301, 43013,17426,35456,35005,33412,36513,29730,33665,32193,7596, 32798, 24217,31518,35951,12957,39392,45241,33194,35456,33412,26775,27523,30930,19519,43871,40415,3533, 39727,24719,16320,33412,44703,44703,23340,3460, 44248,42592,976, 31517,45240,45240,36513,35950,35950,23129,4549, 33194,38560,43871,41295,40975,1702, 12939,35455,33193,37197,22138,34607,35950,9268, 40045,30263,36513,45240,33412, 36513,16060,38402,40789,33665,40611,45240,28341,4612, 43870,40558,33193,40325,30262,36512,16540,33193,37197,38392,27522,42592,45239,32988,40248,35455,41049,33411, 32798,35950,35949,33193,37197,33411,2381, 43536,39198,38070,22603,22439,1973, 40368,6144, 6743, 33192,37197,37196,35455,18205,14070,45239,560, 4673, 6889, 44248, 391, 5218, 40611,44703,44247,41684,13425,44702,37196,33411,20368,41805,42411,3564, 45239,38911,35005,40415,42592,40558,33946,37196,33192,15770,25786,44247,16896, 38139,1903, 39751,44702,33192,43253,42591,13048,24216,28340,27141,37196,32988,35455,32192,22278,20483,24979,36512,5216, 22277,43012,776, 35454,34255,35005,3392, 29729,2567, 44247,21979,34254,37195,34254,39260,23972,972, 19606,36512,15747,28796,2017, 5459, 17671,9980, 34254,35949,28339,45239,16078,30929,43870,15600,43870, 2925, 44247,4221, 35454,5359, 39101,30928,35454,26774,37195,20831,33411,40904,27521,27923,35454,15532,40718,44702,19290,40975,40010,43870,45238,39434,34606,14196, 45238,39421,6033, 35453,45238,22776,14754,31516,36512,40662,37195,27140,35949,36511,35004,33410,1414, 41580,35453,33665,20367,43869,45238,38386,3772, 45237,13062, 39260,39009,35949,36511,42239,15247,25234,2284, 32191,34254,15408,23971,3662, 42591,36511,45237,32988,41684,42796,518, 44702,44701,1224, 41126,18004,22602,30927, 36511,34606,34606,34606,16441,44701,26427,18856,43536,37195,2679, 35453,34253,23522,32987,41684,34605,38302,42411,38196,5924, 40324,43253,1629, 3002, 41478,43252, 44701,1928, 41210,7033, 32798,4089, 28795,1057, 35453,38364,39569,16077,44246,2706, 41209,19060,42411,39367,21848,42591,42082,43012,39407,28794,15951,33664,15422, 40010,14310,42082,44246,27922,44701,23339,29240,42796,35452,12356,32190,12403,39240,29239,43536,3267, 38147,41049,42591,29238,18933,42796,41805,44700,7032, 3957, 42795,31515,21060,32987,23128,17425,32798,819, 41938,4999, 6254, 34253,3729, 45237,41684,33945,36510,35452,25785,34253,32797,44700,6379, 6143, 42411,37720,44700, 45237,5624, 44700,38551,30926,45236,40104,19605,41477,37933,5622, 33410,37194,39615,35004,34253,37194,35948,35452,38724,41048,42082,44246,39751,45236,38248,43869, 41048,41209,39956,43535,42081,35452,20957,27139,42410,36510,37194,32189,37780,41126,28338,30261,37870,44699,40662,38721,999, 41295,40077,41125,39357,42081,16297, 43252,37971,39615,42081,42590,37891,37864,38169,43535,40172,44246,15180,38405,35451,43535,39708,38687,34605,30925,40045,45236,18371,25233,34252,3865, 45236,32188, 6526, 40283,45235,1013, 4928, 43869,21847,38117,44245,42795,44245,40718,37194,45235,44699,35948,31514,38481,39156,44245,27921,43252,325, 45235,44245,32797,4737, 45235,40212,45234,39596,13237,33945,39074,43012,32187,33192,3956, 27138,36510,33664,35451,40172,36510,37193,37760,42410,4001, 41580,5068, 44699,397, 4002, 5290, 43869,37969,126, 43868,3916, 38450,37193,40904,39116,44244,45234,42410,39116,37806,5458, 43535,43534,5532, 41580,42410,38246,4000, 6378, 42795,43868,1655, 40975, 18158,38539,38612,40368,39239,44699,44244,28337,38295,41295,40662,24718,41579,4087, 38150,43012,39522,40324,41683,37892,41048,39929,44244,33945,45234,42590,38746, 40511,22601,41125,42409,43011,44698,32987,37193,37193,20153,22438,37913,43534,33945,18480,36509,22600,43868,37192,37192,35004,33410,35451,40904,40368,43868,36509, 40212,37192,36509,35004,34605,6887, 34252,27137,43867,6522, 33410,37192,38463,20606,28793,36509,41048,19875,40975,38846,35451,36508,25493,43534,33664,16598,41125, 29237,36508,42409,43252,18265,39535,40463,40847,43867,42409,32797,36508,38457,39771,43011,40010,43534,5217, 32987,17347,1779, 43533,42590,39149,43533,29728,19692, 17424,12953,42590,43867,42081,30924,42795,43867,39771,41579,41579,43011,22137,14948,41047,37935,44244,44698,33664,3118, 40045,35003,43533,20605,33944,34605,1868, 6523, 39295,43866,41125,44243,42589,41937,34604,33191,23338,32986,45234,32986,27920,20053,35948,38094,41047,43866,43011,45233,38692,30923,43010,41209,36508,32186, 35450,42409,44698,40414,32185,34604,36507,33944,40077,42794,13047,32986,35450,38405,39116,30922,35003,44698,38652,39535,35450,38376,7034, 36507,18932,26773,34252, 34604,43251,15168,16995,34604,21306,28792,43010,14254,37191,37752,37191,41804,38599,15236,40104,38488,38734,38055,39493,3428, 36507,31513,13829,31512,42239,40789, 1297, 38768,38916,39248,41937,28791,36507,17903,44697,23970,23521,3809, 3364, 11658,39167,40104,544, 1748, 5821, 45233,43010,5289, 44697,39192,35003,37191,23746, 36506,33663,45233,21305,35450,14367,15849,33663,27919,39688,890, 43010,42408,43009,28790,37191,36506,35003,38552,43009,21846,43533,40414,37190,34603,33663,42080, 35449,28789,37190,23745,28788,361, 38786,43009,39771,32184,44243,39634,43009,38204,33944,36506,35002,35449,30921,15904,35948,33663,33191,42239,38690,40077,45233, 41124,34603,38359,39908,45232,34252,45232,38245,39790,39687,39580,38386,34603,20152,26772,34251,24449,12873,40904,35947,39143,14781,20703,33409,36506,12713,37190, 42589,42589,34603,17391,33662,37190,39653,23520,28336,42794,13184,44243,35947,39123,42794,41477,43251,40903,7030, 8694, 43008,1440, 4800, 34602,33944,4341, 33943, 17620,26771,19380,41047,43866,41804,40137,39550,43532,44243,45232,43532,45232,23337,33943,45231,34602,35947,42080,43532,43532,30260,19691,15814,6145, 36505,45231, 25232,32797,44242,2474, 42239,44697,39535,35947,33943,43531,12938,35449,2058, 14822,13909,41579,23969,45231,3429, 37189,39811,22599,35449,31511,23336,36505,32986, 35946,640, 25492,34251,30259,22598,35448,23519,44242,35448,2222, 40974,35448,36505,6524, 40511,3334, 2624, 43008,39009,40610,44242,1495, 460, 5000, 224, 5069, 37967,42408,2544, 12361,17530,24978,41047,44242,40076,43866,4894, 2047, 486, 40789,1718, 6440, 1697, 45231,626, 3053, 45230,44697,3835, 37818,14814,39907,44241, 42408,39653,23518,35002,33409,2350, 27136,40903,26770,628, 12321,32796,32796,34602,712, 20366,35946,34602,35002,23127,37189,30920,23517,3378, 33943,37189,2581, 32796,19787,15886,40903,22775,38721,29727,3836, 44696,44696,40610,43531,42408,42794,38893,41477,34251,36505,2750, 2394, 6811, 44696,38498,40171,43008,38168,39434, 41804,38836,44241,37189,33409,36504,22136,28335,40414,30919,38568,28334,33942,33662,32796,40718,41937,35946,40557,7912, 44696,38721,1910, 6684, 156, 6442, 4358, 283, 3312, 44695,2856, 40414,1989, 4260, 34251,43531,16409,37188,43251,35002,91, 3680, 1791, 10477,38024,30918,39726,38852,32985,32795,40903,34601,33662,33662, 33942,44241,33409,12078,43251,33191,14706,44695,34250,37188,39856,45230,33661,45230,41804,9216, 45230,30917,13908,39110,41295,16382,44695,1545, 34601,33661,30258, 31510,35448,32985,24717,39476,43865,43865,34601,38491,40661,43250,44241,42589,45229,44240,44240,43531,1931, 40557,22597,33661,35447,18855,35946,37188,38841,40044, 3250, 35001,43865,40324,45229,13262,41683,44695,39239,37900,40557,41937,38982,37939,38810,43865,20365,44694,22944,20151,39653,44694,39074,38815,41046,42080,38488, 39615,39956,38524,38085,37800,37867,1978, 13216,41803,1864, 39282,40789,38546,29726,22774,35945,38641,41803,41683,39065,35001,35945,40788,37188,37187,35447,27520, 3837, 43008,42588,42238,14844,43864,5174, 43250,38782,39217,40902,40462,40367,42407,40324,41683,43864,36504,40212,1585, 38538,35447,18048,18264,24448,44694,40511, 38196,39407,34250,35001,35001,2691, 26769,41209,44694,33661,38414,39614,35945,42238,45229,43530,35000,40211,43530,44693,42407,36504,12297,41384,45229,44693,39726, 35000,44693,45228,43250,30916,34250,35447,25491,12682,40661,43864,22943,40788,33191,28787,26426,41477,14156,20052,18204,20830,19966,31509,28786,40974,40661,43530, 30257,28333,38325,3018, 44240,25231,2886, 38138,38380,38953,38814,41046,33660,40283,40610,33190,41384,39217,35446,43250,1485, 40104,37187,43864,16296,31508,12036, 33408,291, 39907,1821, 44693,39652,38012,45228,41803,44692,39116,45228,12239,37187,35446,27519,35000,35000,41578,32985,33408,23968,29236,18102,23744,30915,36504, 35446,2102, 3229, 774, 3471, 2103, 33190,38486,43863,38018,37999,39687,39668,38574,42588,41046,42080,27918,35945,34601,13965,43863,42588,22437,41803,44692,5859, 27135,36503,40171,38841,41124,40788,41802,37187,44692,23967,44692,45228,41208,43863,40717,26099,34600,38859,39956,43249,23126,34600,36503,37186,34600,13250,13577, 32183,37186,35446,28785,12347,30256,37186,43530,43249,29725,35944,35445,38044,38724,38388,38457,42079,38297,31507,43007,36503,37186,40076,43007,3311, 44240,39707, 43863,44691,27134,36503,22436,41578,39343,42793,39492,43007,40510,40136,41802,41384,43249,34250,17670,39050,885, 44691,45227,39434,38513,40009,43249,43248,43529, 40610,44691,45227,29724,40462,35944,1034, 41294,41802,35944,42407,43862,45227,40009,38900,38972,39319,42079,41383,41208,43529,42793,43248,24716,42407,35944,16440, 40902,45227,45226,40847,35943,44239,38043,41124,39392,40171,43007,43529,39421,39790,44691,43862,42793,38215,37841,43529,40902,34600,34599,27917,35445,21978,39595, 40609,41578,44690,41936,45226,38868,35943,33660,766, 7103, 42793,39080,40717,40413,40717,41682,3019, 39521,41294,39022,4646, 44239,21554,38405,38942,40248,32182, 17807,33660,15342,19690,40557,41383,43528,38972,44690,38634,39343,40367,45226,38143,37994,41476,38657,45226,39726,39955,42588,40323,42406,43006,40413,38723,37185, 826, 37185,33408,43528,40211,36502,13624,39955,39535,44690,41936,38577,41124,38220,37866,40974,38773,45225,38953,40171,41046,38135,37185,39856,43862,39206,39982, 42587,43862,41578,45225,44690,32985,36502,44239,43861,3599, 39955,37803,38288,40902,39016,39191,39955,27518,28332,13508,37185,18601,37897,44689,38791,37184,45225, 39856,29723,38677,22942,13940,30914,26425,24715,26424,37944,39330,39492,40009,39002,23335,35943,35445,34999,30913,36502,30912,40462,39883,2806, 7461, 23334,33190, 26768,40367,38801,40323,42406,45225,39595,32984,33408,37917,38538,38071,39550,40609,40248,39614,42587,37184,36502,44239,33407,39115,39380,37731,24215,5490, 39687, 34999,25490,27517,39707,42792,45224,43861,34249,24214,38500,43861,39652,40788,41936,44238,37184,40609,34999,39248,41476,38681,40103,45224,33942,32795,17619,41682, 27133,14319,39330,44238,43248,42587,34249,32984,29235,33407,12027,39954,39086,30255,38920,41208,14998,29234,9300, 43528,38753,44689,18931,41802,41801,17309,38138, 32795,43006,18309,43528,14905,31506,37184,24977,21977,41682,35943,29722,37183,36501,13812,43861,19128,41383,42792,20956,30911,39282,14618,35445,45224,32795,22435, 36501,27516,7100, 35444,40009,42238,40901,38690,37183,42079,43860,32181,38290,34999,23333,41208,32180,42406,5860, 34249,39156,19289,22596,40661,41801,43860,29721, 1717, 188, 42079,4965, 44238,40413,72, 44689,40974,6959, 39391,38394,40247,45224,43006,27916,30254,38728,43527,38925,37183,20150,18793,34998,13880,5256, 37183, 21976,38235,39043,1898, 42587,4964, 41383,45223,38282,32179,27132,25230,44689,43527,26767,27131,16539,38712,43248,44688,38293,40413,30253,11312,8410, 20829,37860, 45223,38216,43247,38567,40136,42238,3379, 41382,13060,35942,13327,26423,28331,36501,38159,317, 4314, 39028,41682,20051,26098,38543,39476,39232,33660,35942,39907, 38189,41294,39595,45223,42237,33659,41207,41382,45223,40412,39569,37737,33942,42078,38773,43860,39406,45222,32178,2666, 42586,41681,37733,41382,43006,41123,40787, 37828,16820,36501,43005,38174,39101,38338,40609,39687,39343,39907,38050,39549,43860,39460,42237,44238,41476,44237,21427,33941,28784,32177,41681,41801,41382,43527, 44688,45222,27130,44237,42237,43859,44688,43527,21426,41801,12851,42406,43859,42078,41800,42405,40660,45222,2104, 38528,39217,38435,39770,40076,39549,38318,38440, 40510,38997,42237,38751,33407,39634,43247,42405,44688,38893,38008,41294,39135,38982,45222,38025,41936,39883,33941,32794,14540,45221,43526,38331,40846,42236,2689, 39790,38900,24447,39269,41123,43005,42405,39406,38043,43526,40367,39174,43005,39135,39095,38152,42078,43859,40901,41800,39928,45221,34249,41800,41800,42586,42236, 37986,42792,40787,42405,40462,40103,40211,40044,38852,21975,42586,45221,41293,40461,43526,38102,44237,42404,42404,39595,43247,37767,37880,38274,38708,41293,41381, 39434,39149,41577,42404,39811,38704,41577,40323,40717,41476,44687,44687,40412,42586,40076,37182,43526,38952,39726,43525,43859,39652,43005,37918,44687,41935,41935, 39549,40461,39725,44687,41681,43858,43004,38177,38468,39319,38086,23966,40136,5986, 43004,3755, 36500,42792,44686,39182,42078,44686,40608,38315,40556,38273,38475, 42585,38997,38822,45221,43858,27915,44237,34599,44686,35444,43247,42404,44236,39534,16408,41293,39448,45220,43858,42791,39074,39751,40136,38814,41935,39406,44686, 43858,42077,33941,41475,35942,5764, 40787,44685,39652,43525,5030, 45220,35942,27515,40366,44236,41681,15479,30252,40973,42585,38942,40608,39633,35444,37817,38319, 45220,44685,38048,38090,44685,38376,39110,42236,39506,43525,39042,43525,38667,42077,44236,45220,43246,41799,40044,41799,42077,38155,35941,32794,27514,38889,42791, 5980, 44685,39534,42403,44684,44684,32984,35941,37850,41799,42236,32176,40247,17043,33941,42791,22434,18727,34599,44684,37838,38416,38411,39883,26766,31505,17390, 33659,31504,20955,43246,39954,38384,38327,38692,40973,11019,40461,45219,39651,39329,19379,42791,41475,37182,38711,43004,43246,40716,31503,41123,41799,40247,39260, 39835,40412,44236,15341,36500,33407,37182,28330,41207,40716,3754, 41577,35941,33190,45219,40412,43524,1921, 4313, 40461,22941,9661, 35941,40103,44235,23332,42235, 37940,39448,35444,13864,36500,35940,12502,32175,37182,40460,24213,33940,42077,44684,22135,35443,34998,36500,41798,35940,21974,34248,35940,39954,12894,36499,33189, 24976,42585,41381,45219,40247,45219,42235,38905,38645,39594,39015,43524,38817,38247,44235,39770,39835,38427,42790,41381,638, 43857,42585,44683,41123,45218,38728, 40660,39421,42790,38746,39182,42076,44683,41798,40323,43246,43857,41935,38637,44683,33189,30251,6580, 45218,5982, 39668,40460,44235,41934,37880,41045,44683,40716, 42790,40460,38516,43524,39191,39357,38868,39308,43245,42790,39173,37181,27513,34599,33940,45218,22773,43857,32794,3883, 41293,45218,12814,35940,3575, 3198, 45217, 39433,6956, 40211,43857,45217,43524,43856,36499,14771,20604,33659,38632,33189,5257, 40787,5176, 4150, 33659,42076,39568,1920, 17223,37181,44682,12574,33189,35443, 18047,25229,36499,5861, 23125,32794,38905,41475,45217,40786,34248,27914,35939,19059,35939,29233,14803,37181,40210,35443,37820,36499,37181,34998,37920,14195,44235, 15531,13854,23331,38055,22772,34248,24975,45217,38590,21188,40716,34598,39549,42789,27913,3884, 43245,38223,43004,28783,18157,19874,41680,43856,43523,33188,38586, 39198,3882, 17222,44234,39002,398, 5762, 4066, 39319,6579, 38141,44682,38851,39506,39042,41475,41122,41045,42584,44682,40075,41292,32984,27512,43856,38354,41474, 38526,2215, 42235,39057,38335,43856,45216,42076,38372,5985, 5763, 1790, 1736, 39751,43245,41934,34998,42789,42235,41798,36498,40510,39580,39789,38742,40786,39651, 2132, 37180,1593, 44682,5662, 5175, 11203,44681,13277,38863,39206,40608,40973,25784,27912,34598,2858, 33188,14122,38123,43855,45216,39034,43855,40901,32174,40973, 40972,43855,42789,34997,44234,45216,45216,19965,26765,33188,43245,43855,44681,39686,34598,19127,35443,14445,86, 5984, 35939,38035,41934,39750,38959,24212,42789, 43523,34997,38108,28329,23743,17346,41474,34598,18726,36498,42788,38637,39789,39080,38814,41934,35939,30910,22595,44681,34597,32983,44234,33188,13424,16597,44234, 33658,36498,22940,37883,29720,36498,38028,41292,44233,39367,42403,38029,40283,39216,30909,34997,2665, 37180,1909, 1281, 444, 5491, 8, 477, 5411, 6200, 6687, 1820, 722, 6957, 3052, 796, 2690, 45215,6090, 30250,33406,14595,13293,22276,42076,38120,22275,43854,1451, 39380,37862,40556,45215,27911,35442,34997,30249,38631, 41933,39750,42584,7101, 5983, 6809, 20149,26422,6955, 44233,42584,44681,41798,34597,26764,13907,42075,16507,45215,21304,35442,3679, 33406,744, 40715,44233,43244, 43854,24974,43244,36497,1191, 34996,931, 834, 6685, 4578, 3273, 2433, 178, 2131, 5663, 1263, 5410, 6954, 6577, 39568,6808, 7102, 1264, 10459,1381, 1379, 1345, 1380, 6439, 6576, 1505, 2807, 667, 4024, 1743, 19218,38867,19873,43003,40322,18725,41045,45215,41680,45214,42584,39686,34597,30908,36497,35442,39633,33940,45214, 5767, 36497,42788,43003,35938,32173,43523,40210,38509,39651,39198,44680,38782,1255, 14121,43244,21553,1310, 35938,42075,35442,24211,34248,33658,3789, 40660,42403, 40901,2312, 32983,39521,42788,43244,14040,37180,39954,44680,45214,45214,39406,40246,38202,43854,39225,43854,43523,41680,38207,44233,38841,23124,38528,41292,35441, 3753, 4261, 30248,41381,43853,24714,35938,19964,36497,2484, 34247,35938,33658,23516,35441,40411,4413, 32793,43243,9012, 39928,6305, 34996,28328,10090,2532, 39143, 42234,34996,45213,42788,43853,43853,44232,28782,45213,7621, 34996,37180,43853,45213,34597,28781,35441,42403,45213,16895,20603,27129,30247,33940,2887, 5173, 37769, 43522,39475,38760,38265,41292,37811,44232,44680,20050,14062,10280,41933,40786,45212,37871,36496,38805,40510,39811,35441,37988,40715,39568,44680,43243,37856,40715, 38589,31502,33187,14211,22771,19604,32172,17574,35937,26097,15950,29719,39953,44679,44679,16381,25489,31501,40786,7706, 43852,37179,44232,15746,33187,12320,40170, 25783,33406,35937,12996,15599,20702,27511,13108,26763,14135,33187,13102,33187,43852,43003,7970, 27910,7649, 44679,42583,45212,36496,43522,36496,34596,34596,22939, 17345,41045,40660,21707,23330,13906,22770,35937,24973,37894,37784,39580,40103,43852,43852,38697,43243,41797,35937,40460,38634,39789,41291,43522,41577,44679,42787, 26096,41207,40459,45212,43851,38436,41122,39982,44678,43522,40008,37179,11247,1660, 779, 1603, 2148, 655, 2093, 3598, 4113, 358, 3678, 4833, 2719, 5, 593, 788, 4461, 5177, 1799, 256, 1544, 1396, 6306, 6807, 5862, 4065, 3350, 18663,23742,25228,26762,33406,26761,32793,44678,45212,19378,38859,44678,140, 40972,5981, 4360, 33658,39295,18662,35936,37179,20364,40246,19288,40170,28327,35440,19217,33939,20148,32793,33939,32983,33939,16627,32983,32982,41380,42787,38062,1017, 40366, 43521,2035, 6304, 5096, 1241, 39475,42787,26421,32793,38355,8536, 11373,42075,9186, 8220, 39065,3790, 10355,10223,8727, 33939,39855,29718,45211,16569,371, 35440, 41207,39260,45211,38631,42402,25227,39206,43851,40411,41680,32982,40715,17114,34596,40900,32982,9915, 40785,40714,43003,33186,33938,37179,28780,26095,35440,35936, 38099,44232,6578, 37178,17854,16994,34247,35440,31500,25782,17573,13803,13948,43851,45211,44678,40044,43851,38988,40283,3409, 44677,33938,32171,33405,30907,32982, 37937,41679,39475,45211,41044,3931, 41679,43243,40714,40459,41797,44677,44677,40282,39366,20482,33657,37178,22274,33657,32792,31499,29232,30906,37178,17113,37178, 34995,33405,36496,8856, 40282,10559,16927,11440,7339, 9226, 34995,37875,39391,43002,26094,25226,27909,15421,37177,42402,33405,41122,37177,39198,27908,41679,26760, 42075,35439,37177,14904,37177,34995,38477,35439,18156,44231,40659,40846,40714,35439,22433,20602,39002,33938,13498,33938,21845,43850,2506, 33186,30246,34596,39750, 13737,44231,10932,21706,32170,34247,28779,44677,45210,32792,32792,24713,18600,17077,34995,32981,34595,38879,40900,496, 155, 2081, 40900,42402,22769,39534,18724, 39668,42583,2256, 41380,42234,42074,41797,23515,43521,28778,34595,38042,43521,10238,20249,260, 5029, 4460, 5766, 42402,41933,45210,24210,40556,19448,13141,13034, 35936,40282,37176,37176,44231,38920,2776, 34994,14438,42234,42401,25781,16439,39770,5761, 35936,11055,34595,34994,37946,38072,39789,44231,39460,40282,44676,38282, 39855,44230,30905,1174, 33657,34994,44676,34595,35439,44230,33405,23329,42401,26420,18538,23328,41474,32981,28326,8006, 15717,23965,43002,41474,34594,21705,38746, 43242,7203, 42074,6958, 6201, 33186,14194,41473,6091, 45210,16796,32981,40785,33404,34994,39094,41380,35438,24972,33404,41206,6810, 39953,42787,2830, 5765, 35935, 43242,37176,35438,33186,34594,41797,17497,32792,18537,32169,33404,13182,33657,34594,40210,35438,27128,20363,12023,7533, 37176,5664, 35935,23741,42234,45210,34247, 34993,7329, 8432, 43850,4359, 10209,45209,12087,39633,4315, 41473,37175,36495,43521,32981,3380, 4702, 15694,30904,34993,19963,33185,2857, 34993,12251,41796,34993, 32791,36495,33185,41796,10118,10677,33656,27510,41679,38573,42233,43520,43002,42233,3159, 25225,18723,15274,9979, 7554, 7276, 17572,10676,20601,7817, 8818, 13438, 36495,21187,34594,29231,32791,35438,7466, 927, 40608,36495,34992,7219, 40210,4519, 11246,35935,34246,35437,15013,34593,1233, 38299,12855,28, 1471, 150, 2393, 43002,44230,22594,38892,41473,12826,40411,44676,16707,39492,37175,22432,34246,44676,35935,20481,37175,13576,23964,18370,40102,34593,35934,40008,43850,34246,33404, 39750,37175,38502,38528,17042,42233,7663, 39725,39953,44675,39749,6199, 42074,40322,43850,33656,33937,34246,35934,20248,25780,24971,32980,14277,32791,38300,41933, 45209,36494,6441, 44675,43520,34992,36494,37174,29230,41576,45209,689, 42583,6686, 35934,6443, 8571, 44675,29717,38204,38858,43520,43849,23963,42786,44230,45209, 33937,18722,577, 42583,22593,29716,22768,24712,17308,43520,30245,36494,34245,14718,8009, 34593,28325,10855,44229,19377,9893, 44675,43519,30244,34593,33403,24209, 33403,12794,30243,29229,37174,24970,33185,29228,35437,43849,40366,2604, 36494,36493,27509,2361, 7052, 4447, 34245,36493,39594,37174,33656,43519,5076, 40785,41380, 19786,14187,18369,38129,40459,2308, 35437,39380,38817,17459,17902,20828,37822,37174,34992,24446,32980,30903,34245,43849,43519,35437,39447,38834,40246,42786,38599, 41678,43519,41122,41796,39882,42582,43518,36493,30902,23514,12770,4681, 24711,38390,43242,35934,34992,39882,35436,29227,34592,42233,39269,39651,30901,45208,44674, 41206,44674,37173,37173,40322,33937,40714,33937,22273,15671,27127,15407,34991,35933,35933,35933,33656,34991,7342, 39668,30242,33936,25779,37173,34245,34244,16103, 22938,37173,35933,43849,43242,43518,45208,35436,39281,44674,42401,32791,674, 2195, 32790,30241,37172,42074,1117, 30240,41796,30900,41473,37172,41121,39216,43241, 41472,37172,43848,40509,40659,44229,34592,41932,35932,37172,30899,39855,32790,14617,15550,24208,14616,45208,16568,36493,7269, 39770,19126,43848,37171,22272,18661, 35932,34991,14984,37171,34244,10501,42401,27126,20147,8042, 24710,25778,10781,37171,17853,26419,8041, 7177, 11891,7781, 10208,43241,17529,28777,34244,15769,36492, 26093,30239,2196, 38874,32980,40607,37171,44674,43518,15745,3736, 44673,44229,44673,40659,45208,14225,37937,38165,39094,45207,38470,43848,42582,38290,38952,39080, 38110,44673,40135,41932,39982,44673,40972,37778,39405,4134, 43848,3006, 43241,42232,41678,37905,40043,42786,38687,34991,37741,13652,35436,26418,34592,37730,37170, 21844,45207,33185,45207,38720,39085,44672,41932,40135,38899,41044,40043,38008,45207,40322,37802,37902,38645,38874,37691,38331,41291,42786,41121,43518,43847,45206, 19125,43847,22134,20954,17112,33936,43517,11439,35436,40008,960, 42785,42232,43241,44229,38677,36492,13978,42232,21425,42785,39769,41576,41121,43847,43240,32790, 35435,35435,37754,38206,39981,42400,27508,39855,35435,37952,26759,43847,43517,45206,43240,41206,44672,42785,41206,39329,41576,39319,10500,44672,34592,31498,13605, 16407,24207,8647, 41205,42582,10435,10388,9136, 8175, 39143,3567, 11018,35932,8642, 1784, 3869, 43240,39906,40556,26758,28776,1686, 4295, 17458,42073,33403,35435, 33655,35434,7049, 40281,39101,45206,43846,33184,39065,40209,41205,41291,32790,38142,41291,44672,45206,41205,43001,44671,40281,40607,43846,35932,34591,42785,38670, 39205,38427,38258,39834,43517,40785,38836,33936,38382,38071,43517,38284,43846,40281,40411,43516,39100,45205,43240,26092,41044,41795,43846,37170,36492,36492,42582, 23740,21059,38597,39769,40509,45205,44228,41379,45205,38997,43845,42581,40008,43516,42232,22431,37170,35434,24445,3921, 42784,43001,35434,38833,40209,41205,39749, 42581,44228,44671,41472,43001,39707,42581,39248,41932,42581,38033,38565,39834,40713,43516,43516,40170,44671,38397,38833,40246,38947,40366,41931,43515,4866, 39707, 40459,39706,42580,40784,39769,43239,42073,38664,41044,41379,45205,42231,43515,41678,43515,41290,37170,37875,42580,38965,39015,42580,38810,42400,38017,32789,40972, 45204,40784,38422,38619,38745,40784,38239,41290,43001,43239,41204,41121,44671,43845,39882,40102,44228,39854,43515,40509,42784,42784,45204,40509,13732,42400,41931, 40410,40245,37169,40607,42580,40971,41576,44670,40659,37169,40555,290, 10780,2478, 4230, 3777, 5139, 2228, 34990,5634, 45204,13776,33184,15293,45204,3920, 33184, 14770,27507,13703,26757,22937,36491,30898,412, 31497,4622, 15206,35931,40784,43514,40607,39953,45203,41575,42400,43239,32168,32980,39928,34990,33655,28324,44670, 4294, 31496,22430,21552,2845, 20247,4390, 34990,5719, 681, 41575,5007, 2476, 40713,42073,26417,43239,34990,21303,24969,26756,41795,45203,43514,35434,5138, 35433, 5935, 34591,3298, 34244,907, 15497,13292,837, 42399,28775,45203,34989,40245,41575,24206,4940, 36491,1631, 44228,26416,5226, 521, 2264, 37169,6903, 5140, 208, 171, 4135, 6158, 39259,39015,43000,42231,40321,42784,43238,40245,22429,34989,6267, 2603, 375, 2822, 33184,3220, 13046,42579,12008,42783,13384,35931,37169,14471, 39706,2793, 42783,33655,40971,36491,43000,40007,16319,44670,40713,42783,40209,42231,11519,41472,44670,44227,22133,14482,12293,42073,45203,21973,36491,30238,43238, 16102,12266,45202,38738,4391, 38657,42231,39050,43000,40410,43845,43238,44669,39109,38432,40281,34591,36490,35433,38965,45202,43000,45202,40075,43238,40846,44227, 39197,40280,40458,38874,43514,42399,45202,40075,4494, 40658,44669,39142,41043,42783,40043,39492,42072,41575,39073,45201,41472,40606,42782,45201,38657,44227,41574, 45201,44227,39725,41290,40365,11102,7873, 39749,41795,43514,40410,38151,38338,38822,39002,42782,30897,39460,39906,41931,41574,40783,15328,13714,45201,36490,38959, 41290,38530,38649,39049,42399,10237,44669,42230,44669,44226,10458,42399,23123,35433,5299, 7678, 41043,34591,40658,41931,40075,42072,45200,44668,8309, 7334, 11814, 11372,39149,39749,44668,41574,20953,14997,38624,42230,43513,44226,44668,39021,41289,26091,23962,17669,41678,37979,38528,42072,42230,39460,40209,40208,34243,38061, 44668,39433,40846,42230,42999,39981,39148,43513,45200,42999,35433,38570,44226,42072,41204,34989,40170,42071,30896,25224,32167,38416,40783,39225,40713,39614,44226, 35432,22936,35931,20600,32979,37168,35931,34590,29226,37168,33936,33935,44667,45200,41574,39216,40971,42579,37168,37168,14687,18930,33935,34989,36490,20146,33655, 23513,15062,35930,28774,24205,34243,36490,12167,34988,21551,31495,36489,21550,35930,22271,37167,19216,35930,37167,35432,31494,34590,30895,28773,39356,42999,43845, 32166,10854,34988,26090,30237,28323,12739,27506,36489,31493,27125,41930,43237,16506,19785,22428,9264, 7856, 35432,29715,43237,41677,6647, 33935,33183,30894,28772, 32789,19215,34590,19603,43513,42999,41795,40102,20827,14539,15829,19602,35432,36489,34590,18101,35930,34988,33654,42579,35929,25488,41677,31492,35431,34243,40169, 40043,43844,43844,35929,42579,44225,44225,32979,14791,33403,33402,35431,20362,36489,37167,30236,21186,27124,30893,16241,33935,35929,36488,35431,11585,35929,14489, 42578,27907,37167,33183,36488,35928,36488,16144,32165,38833,38616,39811,38874,44667,40606,41204,43237,41471,44667,43844,43844,41471,42229,43843,45200,40365,43843, 38768,38223,41289,40783,39810,38867,37807,42398,38228,38674,39405,38704,42998,37837,45199,44667,40321,38647,42229,38978,39748,43513,43512,45199,38086,38209,40410, 39318,31491,42998,39769,42998,40508,39009,32164,34243,44666,38804,42998,45199,42229,44225,41573,38276,41794,39205,38245,43237,22767,38660,41379,38524,44225,41204, 45199,43236,40508,39380,45198,39706,34988,32163,5077, 40409,41677,41573,41794,41573,39433,41930,38833,28322,40280,11518,45198,38099,40658,39768,39042,38355,36488, 41203,36487,22935,41573,39167,38541,39491,43843,39021,39433,40321,40712,16505,34987,43236,44666,41794,43236,42578,10530,39928,45198,37719,38041,38343,38460,42229, 28321,36487,23739,30235,17041,37931,38322,38728,40845,38673,38160,4746, 16473,41930,45198,41572,41572,40169,34987,44666,23738,35928,36487,28320,42782,39927,43236, 41677,44224,41379,42997,11438,37166,41794,44666,39391,38405,38889,40508,38242,38425,40900,42398,39475,27906,30234,34987,32162,36487,32161,18792,8622, 9113, 39952, 42578,8106, 43843,45197,23327,9417, 9416, 8021, 17571,3299, 11517,32160,24444,11584,33934,33934,36486,33183,20480,40208,41378,34589,43842,45197,38931,36486,41676, 35431,14375,7408, 9892, 20145,8322, 44224,7050, 1066, 32979,43842,43512,42997,41930,45197,10853,39748,41929,40899,42228,44665,31490,14632,17389,17714,1339, 33654, 25223,36486,9240, 36486,23122,36485,20246,11740,36485,34589,14291,28319,19872,21302,35430,15310,41793,32159,41043,45197,42782,42578,40783,41203,43235,39854,43842, 34589,18599,34589,38040,42577,38942,42577,21058,38559,37166,13863,32789,39356,44224,40458,26415,22592,21549,26414,20245,36485,25222,44665,37166,36485,40074,41929, 43235,45196,42577,44224,42228,43842,45196,38633,39981,41793,40712,39295,41929,44665,25221,38615,43512,43841,45196,43235,38109,39952,39366,38905,41676,41378,45196, 41289,40782,44223,45195,36484,39706,42071,42398,44223,44223,39952,33402,32789,33654,38323,43235,38609,38904,42228,39705,40658,43841,44223,40899,45195,42398,42228, 43234,41793,42781,24709,40208,37871,44222,38601,43234,38292,41793,38677,38656,38268,37166,42577,44665,19518,25777,33654,37165,41378,38317,39521,44222,39356,39952, 39248,38785,38899,42397,39810,43512,41792,45195,41120,43234,42781,44664,44664,41120,43841,41378,45195,40712,41043,44222,45194,44664,40712,3735, 41120,42576,5136, 38796,39247,42227,43511,41676,39834,41676,38134,41471,44222,42781,36484,37165,38189,38687,39308,39232,6268, 38557,38545,40365,40245,38612,41042,42397,41203,45194, 44221,44221,42397,44664,41471,40899,45194,22591,38959,41203,36484,41377,42576,1539, 38682,44221,44663,43234,39281,37975,41377,42071,44221,39748,44220,39079,40555, 45194,40845,41929,33402,16750,22766,38638,40899,41289,42071,39420,44663,42576,26755,38506,38498,38879,39034,40711,44220,41470,45193,39366,44663,29225,44220,44663, 41377,38627,44662,40657,4229, 41470,39281,42397,42070,41042,40007,39085,39079,39447,40409,45193,40169,43841,38319,39028,40365,40606,39474,43511,26413,19447,35928, 43840,39033,43511,38768,38889,42576,41928,38577,42997,43511,42781,43510,40208,39491,38553,30892,37859,41928,39021,38619,40207,39834,39594,41675,43840,38669,33653, 40971,43840,39548,40074,43510,38354,39281,38822,40711,38277,40364,38615,37980,37905,41792,41202,35928,29714,35430,11101,20244,37763,41792,37976,42780,38892,44662, 39705,24443,42575,38690,42780,23326,39135,20826,45193,44662,41377,45193,44662,38836,39356,43510,40970,40169,41470,38495,39239,39748,21704,44661,39506,45192,42575, 40657,39705,42396,44661,39521,38340,40898,41120,37974,38656,44661,40970,45192,43510,43840,41572,41376,40409,41470,45192,9263, 39343,44220,39768,41042,39705,42780, 42780,39951,44219,44661,41572,43233,23961,2739, 39491,41571,43509,44660,9151, 27905,39049,40898,27505,44219,45192,42396,40898,43839,45191,38884,2929, 38697,41469, 39788,43839,41469,40657,44219,40409,41571,38899,41928,44219,44660,45191,40042,39810,39318,41202,41675,42997,39579,45191,43839,42575,41376,43509,10136,33653,21548, 36484,37877,38242,38465,38758,44660,33934,36483,27123,7610, 26754,38462,42227,40321,39704,42779,34588,32788,15980,32158,35927,33934,8965, 38965,34242,36483,43233, 32979,38524,38785,39259,40458,41571,39548,41792,22427,44660,18479,30233,35927,18929,38042,40408,42396,41119,41928,43233,38374,44218,42575,33933,40782,38377,39173, 42396,41571,41202,43509,43839,32788,34987,802, 40074,43838,39704,39594,23512,18791,42227,40970,44659,42779,40606,43509,43508,40711,39882,42070,42996,40135,44218, 40074,40782,40007,43838,41570,40605,5468, 38925,43838,39474,42779,44218,39981,43838,4867, 40207,41119,42574,41675,38359,39686,39725,40168,39614,41675,45191,33933, 36483,6646, 39459,43837,40782,44659,43233,7439, 41674,37165,25220,26412,32788,30232,41674,8105, 33933,26753,36483,12088,41202,9057, 30231,10931,7577, 33653,33933, 29224,12061,34588,23960,14403,37165,34588,33402,20701,33183,24442,32157,7047, 45190,36482,23121,36482,9046, 34242,21185,11739,20049,35430,27122,43837,25776,41570, 40135,8058, 34986,42070,43508,9684, 18721,34242,33932,16626,29713,42395,42996,2902, 5832, 43232,5635, 13556,14270,8826, 36482,20825,16034,12223,30230,35927,36482, 10895,8278, 17757,28771,25775,5075, 33653,34588,39205,43837,24708,20700,41570,16122,35430,26752,38904,45190,41791,11738,35429,33932,42395,40605,45190,38422,42779, 33652,35429,32978,33401,44218,5717, 29223,38879,40320,45190,19287,33401,43508,38714,41570,40134,39881,45189,21703,11657,7324, 34986,26411,26089,34986,14947,19002, 19871,34986,27121,12093,12494,7148, 38411,1840, 44217,12151,12016,23511,26751,35429,17423,26088,9387, 39001,18660,20599,7046, 37164,24968,39568,13266,7937, 22132, 29222,41674,16667,37164,34985,12626,35429,13094,34985,7326, 38701,37164,14959,25774,34242,27504,33182,35927,30891,35926,14514,31489,18659,43508,27503,21843,14230, 44659,7451, 10978,7694, 32788,30890,15246,13152,42395,7274, 39881,5300, 7896, 43232,37164,33182,39686,16059,39980,28318,14085,42395,12718,36481,41791,42778,40364, 37163,37842,42778,34241,31488,34587,18658,15167,18368,14010,4493, 38728,37848,39142,30229,35428,14488,22934,18046,40458,41288,37887,40457,40042,38128,27904,20479, 33652,13956,34587,26087,34587,24707,25487,33401,36481,37163,30889,36481,28317,36481,16504,41569,24967,33652,34241,29712,36480,34985,36480,20952,18657,34985,20478, 27120,35428,24966,37163,34984,37163,24441,35926,30888,34241,32978,35926,23510,18478,29711,42227,32978,33182,17496,28770,35428,35926,31487,33401,28769,33932,28316, 35925,27903,15744,40781,18720,29221,45189,17713,5544, 42226,38801,40605,42574,40320,15813,19689,34241,21972,29710,39148,32156,32787,28768,41927,14513,24204,18928, 22131,33932,12727,34984,34240,42574,34587,37688,38664,40508,31486,39567,39650,40898,35428,2173, 42996,6047, 38036,38867,40457,35427,17618,33400,7692, 44217,44217, 42996,41791,33931,14913,34586,33931,34984,37162,27502,43507,19962,45189,10104,43232,39881,20144,12129,41376,6159, 35925,37162,11737,37162,40168,34586,27119,29220, 3961, 45189,9732, 15768,32155,35427,34586,44659,35925,16143,36480,34984,34586,36480,41469,21702,14105,35925,25773,22426,23325,37841,44217,32978,37162,32977,35427, 17617,36479,42995,37161,27902,44658,38472,32787,45188,19961,37161,4180, 38282,41927,35427,36479,43837,31485,29709,40042,31484,34240,19784,37161,36479,33931,45188, 39927,44658,35924,42226,17221,34240,35924,33652,16380,12311,38562,44658,40711,19058,35924,39432,36479,2501, 15222,42070,31483,38281,34240,29219,36478,22933,1007, 35426,27901,44658,38911,2740, 44216,44216,35426,37161,19214,39854,6756, 10103,24440,38164,35924,40007,34585,21842,39579,23120,37160,27900,21971,37160,38785,18598, 37886,34585,38738,38734,32977,38530,38548,18536,18308,33931,31482,40710,33400,32154,28767,36478,35923,34983,23959,26750,23737,42574,36478,34983,44657,33400,8245, 30887,40320,22270,35923,15273,13079,40845,35426,40207,23324,28315,40320,37160,37160,34239,44216,26749,16666,12932,44657,31481,38605,43836,39420,16960,33182,29218, 23958,43836,37159,11371,34983,39854,40605,41288,16121,12606,37159,27501,23957,33651,38061,44216,12014,41288,45188,22932,16212,36478,6269, 12572,5374, 40319,18927, 36477,26086,43232,42573,33181,34585,39833,34585,36477,11202,34983,37159,25772,37159,19870,34239,37158,37158,21701,35923,14356,36477,35923,22590,5636, 12623,25486, 41469,37857,42394,41288,40781,36477,43231,42778,44215,2631, 3035, 3097, 38796,43507,41376,4009, 7051, 35426,40168,39833,20361,19783,39405,45188,40280,13581,38559, 39833,40897,44215,37158,32977,7652, 43507,34239,35922,26410,31480,32977,35922,14224,38522,35922,39768,40042,39567,38368,11245,7600, 39927,9173, 39197,12288,35425, 25485,7784, 11583,38652,41375,36476,11890,33930,33930,30228,15357,31479,37158,12378,7144, 42226,1652, 8325, 33181,7319, 11996,12094,39593,20360,16033,17422,17616, 35425,22931,32976,21057,27118,22930,8241, 44657,4682, 18439,14028,14242,11974,44215,43836,35922,13088,20951,37157,16142,36476,12490,37157,37157,33181,42226,4558, 3818, 2307, 33651,44657,4293, 6757, 42778,15634,7946, 32153,41791,14437,8524, 22765,32787,32152,35921,29217,7687, 33651,12813,6266, 34982,13702,33651,11201,27899, 10749,8568, 37157,17457,34982,33400,35425,32976,21841,37156,9121, 321, 43836,34584,40073,40970,1124, 43835,200, 6390, 1905, 19057,45187,6531, 3036, 2042, 32787, 32151,37156,4348, 4093, 5718, 39379,36476,45187,37156,33181,5373, 1986, 7048, 508, 35921,1090, 14061,20359,7595, 915, 6645, 35425,34584,7648, 45187,29708,36476, 35424,35424,37156,17262,18155,30886,30885,26085,35921,16162,38742,37155,35921,33399,15397,37155,30227,33399,40408,41790,3817, 28314,7448, 28766,34239,7591, 12287, 35424,33180,40969,34982,22130,34584,29707,2477, 7250, 44656,35424,7481, 36475,29706,34584,7855, 35423,18719,7690, 15205,13138,11976,34238,37155,24439,29705,18597, 12215,34583,41287,37155,13010,36475,16295,33930,35920,18718,37154,35920,21301,33650,33399,36475,32150,35920,34238,35920,33650,19124,35919,35919,23119,36475,36474, 20598,30226,33930,20477,29216,23323,37154,35919,34982,35423,32786,34981,35919,24706,19123,35423,7527, 7191, 39927,4008, 12007,21970,21547,35918,34583,36474,34583, 25219,34238,3432, 6901, 44215,21969,16185,28313,36474,31478,33929,36474,33650,30884,34238,33650,33929,30883,33929,30225,23736,32786,15598,21968,36473,34237,21967, 18307,35918,36473,35423,37154,44214,34981,33399,33398,36473,35422,12523,35422,32976,16625,32149,35422,37154,32148,7351, 44656,9777, 11813,7540, 15653,43507,38151, 21056,36473,39951,4389, 34237,8104, 44656,16356,24965,43835,34583,45187,41927,33929,23735,36472,37937,33649,7950, 20243,38663,36472,1772, 44214,43506,43835,40408, 36472,36472,34981,5137, 38207,23956,12653,30224,26084,29704,19376,35918,38612,615, 45186,35422,44656,27117,17307,27116,35918,24203,9386, 44655,37153,22589,10476, 7552, 42069,10036,38173,41119,42225,30882,33398,40319,42225,45186,34582,33398,13296,40897,43835,24202,4231, 33398,37922,40073,45186,44655,43506,45186,35917,34237, 37153,32976,30881,38453,10222,36471,7716, 42777,5006, 21184,12171,36471,34582,35917,21300,16472,32147,30223,32146,1032, 32786,13592,9342, 33649,41042,43834,42777, 25771,6902, 7350, 44214,9544, 7162, 5545, 2630, 22129,33649,31477,8629, 42394,17306,43231,37153,21424,20048,40207,15127,1252, 34981,10089,36471,25484,9707, 10387, 7294, 27115,7799, 25218,16624,34980,38011,4747, 44655,43506,43834,40604,40657,44655,20047,34980,33928,17901,2161, 44214,16406,37153,37729,39318,45185,41041,40244, 41468,41119,38269,40364,39094,38195,42777,36471,13790,44654,17852,34237,19122,25483,25217,7491, 44654,42777,597, 44654,13515,1859, 41287,6265, 44654,43834,39650, 38791,38346,40845,13322,39280,21546,34236,16058,26083,37152,35917,35421,31476,20597,33928,7676, 38673,8253, 45185,12859,35917,9415, 9158, 12750,25770,9133, 8526, 9411, 8753, 8895, 9965, 44213,9764, 8844, 11311,10236,10457,10499,9016, 39379,8976, 35916,9949, 8709, 41790,41375,13254,35916,43231,20699,23734,9825, 36470,27114, 32786,39685,42776,7640, 8206, 39173,8962, 9597, 9296, 17183,20242,31475,33397,32145,34236,245, 44653,43231,33180,32975,35916,11437,11516,10977,32975,9001, 10930, 20950,37152,9429, 19001,8552, 8344, 41287,19517,13345,11736,28765,25216,15597,30880,13223,16819,20358,19375,23118,17712,35916,30879,37152,37152,11889,24201,7886, 35915,37151,16860,29215,30222,37151,35421,24705,32144,26082,13789,33649,28312,24200,33648,38399,34236,8691, 11812,35421,36470,45185,35421,37151,41468,42394,36470, 10558,34582,8459, 41468,37151,22425,35420,34980,23733,9501, 36470,24704,11515,9184, 36469,35915,45185,8926, 33928,33648,20824,37150,34236,27898,41790,12031,39318, 37150,11143,33397,29703,33180,37150,37150,33648,32143,15327,42776,24438,13311,11735,22929,32975,36469,19516,42776,17952,35915,25215,21055,29702,19286,31474,36469, 34980,40134,42573,42573,42225,5090, 44213,11244,32975,34979,45184,6670, 41201,44213,44213,41927,40006,43506,35420,29214,25769,23322,29701,33928,12532,33397,33180, 21700,35420,26081,32142,21966,22764,36469,32141,36468,36468,37149,16859,20476,22424,18263,37149,20046,44653,37149,37149,44653,31473,36468,29213,34979,42069,30878, 42394,2163, 34235,28311,44653,39342,36468,27500,43505,33927,35420,32785,19446,35419,23509,33648,40457,34582,34979,37959,44652,41375,42776,42393,39420,42775,44212, 33927,26409,25768,19121,40969,35915,37961,42995,42573,40206,40710,14678,15964,41375,17615,35419,15292,33647,33927,19056,12822,34581,24964,14345,41790,34581,15166, 15885,30221,32785,34581,43230,43834,45184,38267,2908, 41926,42775,40897,19000,34979,38184,42572,41926,42393,34581,31472,33927,24963,19960,35914,2349, 34580,2233, 41789,34235,13679,34978,41926,37148,17711,40408,12300,13533,25214,9056, 35419,36467,6183, 12690,24199,34978,19120,18438,33397,13886,43505,40206,28764,30220,27897, 35419,31471,9299, 9596, 9350, 9253, 8522, 40555,33926,35914,41674,8925, 42995,38229,39810,7206, 39667,7837, 43833,27499,40781,2745, 42572,5655, 15963,7602, 22588, 35914,15165,33179,33179,25767,15038,19055,40364,33396,11436,35418,27113,33179,24198,21183,10675,31470,33926,17182,32974,35418,33647,33396,28763,5166, 3050, 3571, 1832, 4020, 3468, 4768, 39166,42069,43833,3713, 33926,1342, 28762,19213,21965,25482,20143,33647,39506,1041, 44652,45184,38832,39881,3049, 37148,42393,36467,32140, 7335, 33647,19688,33179,36467,44212,11370,35418,10088,42069,7836, 17181,35418,21182,12960,16665,36467,43505,35417,31469,37148,40280,8231, 41926,9515, 35914,36466, 17951,33646,35417,26748,12956,35417,33646,20596,26408,40781,35913,43505,32974,13564,25766,34580,21964,29212,34978,30219,21299,38098,6563, 34580,43230,42225,21423, 35417,35416,45184,34978,35416,36466,33178,35913,35913,42068,35416,36466,22763,30877,35416,33178,19869,28761,8103, 7457, 42775,7460, 43833,34580,16959,27896,8112, 35415,33178,18477,37148,33646,33178,21545,43504,16706,36466,33646,35415,28310,36465,29700,13686,35913,35415,44212,13164,28760,34579,30218,33926,42393,42775,42774, 18999,33177,37147,9999, 35912,34977,7500, 5969, 1818, 5248, 33177,6940, 20475,40897,5564, 25213,26407,37147,26080,16567,17756,29699,5656, 42774,41569,34977,18437, 27112,36465,34235,34977,42995,37924,43504,37984,38381,39085,23321,12126,37147,30876,37990,44652,38925,39853,44212,37885,28309,36465,38916,33177,36465,35415,35414, 7090, 44652,23320,36464,4885, 33925,42994,42994,33645,31468,27111,26406,41468,41041,19119,32974,41201,38430,32974,42068,38627,39432,39926,41789,34977,17570,34579, 44651,42392,34235,17388,40710,43504,42068,4953, 42068,37147,35414,37146,37146,19285,24703,37146,40363,32785,39906,42994,41041,16858,17710,32785,29211,33645,20595, 17387,305, 40656,44211,39042,37791,33645,40780,44211,34234,38111,41201,39135,15716,14186,30875,31467,37146,36464,33925,19212,37145,7949, 33177,15061,23508,32139, 17569,34234,34579,35912,41789,40279,41201,20594,34976,36464,35912,40041,32138,12576,284, 44651,38656,32137,40896,29210,28308,13222,35414,5970, 19211,36464,40969, 27110,17261,36463,36463,22762,7808, 36463,33396,41467,7613, 7222, 40041,6427, 41925,33925,34579,33396,728, 45183,1497, 39009,36463,12614,31466,34578,33395,14615, 28759,41925,41569,28307,43504,41789,41673,33176,7673, 1520, 3874, 6937, 34976,36462,19959,8160, 42994,10894,9486, 11200,11243,31465,12900,8834, 38965,44211,36462, 34976,30874,19687,16926,12755,23319,30217,16211,45183,44211,33925,24702,30216,17495,37145,33176,27895,27894,37145,37145,24197,36462,42993,7089, 40780,16538,7190, 38644,11369,24437,26747,29209,34578,32973,37144,35912,28758,26079,35911,36462,30873,32973,26746,29208,40969,34578,13673,35911,29207,21963,43503,37144,35414,34234, 26078,30872,37144,33176,24436,37144,26405,20949,34234,37143,45183,44210,42224,42224,40844,43833,42572,40206,6672, 18367,33645,37143,37143,37143,32784,31464,18535, 34976,28757,14487,21962,29206,35911,35911,27893,35413,32973,31463,35910,34975,45183,22128,23117,22127,27498,1708, 41200,35910,44651,34233,27497,41788,40457,42993, 28306,16664,9401, 1498, 36461,36461,32784,33644,4884, 15903,19374,35413,38855,39809,40710,30215,9914, 6431, 2330, 8448, 38904,5022, 45182,44651,44650,33644,16032, 33924,11017,33176,37142,10354,16894,34578,38484,41467,18366,36461,34975,33395,20948,41788,40844,44650,38442,38240,38978,38988,43230,39880,42392,45182,34577,37142, 32973,39308,42067,43503,40168,39880,39049,41041,16267,45182,9743, 33644,8530, 2827, 35413,45182,40968,40134,13298,35910,39033,39015,17305,35910,37142,34975,11199, 20241,32972,35413,31462,34233,33175,13314,24962,35412,34975,18154,3594, 37789,45181,42392,44650,42993,41467,38462,38497,43230,39379,1552, 42224,5850, 33175,32972, 24701,17806,6079, 33644,34577,33924,36461,40968,35412,44650,32972,34974,37142,14738,35412,14664,21840,43503,37141,44210,44649,35412,43832,35411,33924,36460,33175, 33643,34233,4254, 41287,41788,36460,37141,32136,30871,37703,43503,32784,4770, 32135,38577,1967, 44649,37141,39491,27496,1173, 717, 43832,39724,40363,18262,34974, 26404,33395,2007, 41569,43502,37141,42067,587, 22587,7091, 27109,44649,19118,4406, 16925,36460,37140,26745,37140,26403,33395,37867,38256,43832,34577,36460,41568, 42993,36459,2045, 34577,7924, 40407,19284,24961,38211,43832,28305,26402,40363,31461,34974,28304,42774,4954, 34576,22761,35909,37140,42224,38627,12652,4826, 35909, 35909,37140,12265,35411,34233,35909,14890,15258,13639,16002,38180,26077,2803, 19445,37964,39613,10386,15791,14946,38521,41118,42992,44210,13198,33643,21961,24435, 32134,44210,41673,34232,30870,9683, 12500,40363,28756,43502,45181,36459,14134,44649,39768,43502,34974,33394,34576,36459,15478,37139,15396,8648, 35908,31460,35908, 45181,26076,8308, 39685,2883, 42992,33924,36459,36458,34576,21699,29205,34232,1026, 42392,18365,36458,20240,35411,10235,4063, 30214,39633,32133,45181,41200,4455, 5751, 35411,22423,33643,32972,2310, 37139,31459,38329,44648,13514,20239,42572,35908,37139,41568,21544,42992,39632,26744,20357,42774,43831,39085,44648,42773,32784, 7402, 37139,39001,42773,26401,42391,32132,34232,39391,26743,7484, 35908,31458,31457,35410,34232,23955,41925,32783,16101,18436,34576,32783,34231,42992,26742,20142, 33923,34231,7325, 38502,4513, 33394,37138,6673, 34973,16623,100, 41788,10353,39906,34575,9235, 37138,25212,45180,4308, 30213,6432, 37731,37138,11582,32783,24434, 38010,39613,37138,2331, 40896,10779,5401, 37137,36458,34575,33394,7672, 33643,15037,32783,323, 41286,1037, 6184, 43831,43831,42991,6188, 17494,45180,19782,37137, 37137,35907,13837,42991,43502,15126,27495,40407,32782,29698,12671,34973,13651,2368, 25765,277, 603, 6941, 8013, 34575,7201, 4769, 12999,16857,941, 11811,16818, 34575,35410,42991,14263,16031,12005,3572, 40896,43229,42067,41374,40041,39459,41787,38321,11242,31456,34231,44209,6564, 43831,34973,32782,42571,44648,16001,41118, 38307,41118,26741,43229,11310,37137,22760,33642,23954,41200,28755,14753,35410,34574,34231,32971,20593,36458,12026,13116,33642,23116,34230,37136,30869,9225, 41925, 20238,10929,1774, 17180,26740,34230,28303,34230,44209,17851,34973,15155,449, 35907,34972,35410,37136,29697,35409,12326,44209,37136,25764,42773,28754,8157, 6428, 36457,24196,4107, 4824, 33394,13580,43830,18306,33393,19601,35409,38184,38481,8458, 43830,2717, 38681,23732,36457,42991,21422,7727, 3193, 37136,7888, 21698,35409, 41787,24433,43229,41200,41787,3072, 5565, 5750, 27494,9549, 9543, 34574,39667,34574,42990,18305,36457,37135,28753,33393,33393,19054,21181,32971,27493,27892,25481, 10748,34230,28302,43229,26075,34229,44209,19053,22759,18304,36457,34972,24960,9282, 38840,33923,36456,18100,5405, 37135,36456,17614,12657,33642,45180,21543,35409, 35408,34229,35408,44208,23953,33642,35907,29204,43501,34972,31455,30868,12234,41467,27492,32782,17668,26739,33923,25480,10557,21054,41924,20823,33641,8102, 36456, 36456,37135,26074,28752,16161,12270,41374,41374,34972,37135,18476,21542,42990,33923,35408,33641,32131,33393,22586,42391,22758,33922,13395,2576, 2079, 43228,19600, 39905,42391,8225, 42773,19686,33392,34574,24959,8164, 39788,1687, 11810,33392,11809,2716, 21180,34971,39155,39100,44648,33175,41924,37134,28301,34229,43830,41924, 34573,37134,39094,19515,12791,10434,35408,10327,32130,34573,9731, 37134,39109,41673,35907,33174,32129,33174,33174,17568,45180,36455,22422,45179,34573,37134,34971, 35407,26400,33641,40456,30212,41924,19373,34971,17304,23318,26399,43501,31454,17303,26073,36455,36455,19599,6187, 4019, 41673,8986, 45179,2577, 42990,5749, 34971, 42571,18656,33922,32782,26072,34970,15125,32971,23507,32128,35407,2101, 4255, 37133,43228,1519, 41040,1633, 41923,42223,41118,20947,29696,5403, 38493,39905,30211, 28300,20045,6939, 42391,44208,38687,40780,38252,41923,38977,39307,16379,37133,35906,40844,37133,26398,14098,20946,37133,33392,32127,16705,18261,42571,42223,37999, 41568,41117,352, 11656,41286,11054,33392,38274,16355,13701,40102,42067,43501,14945,41374,33641,32971,39809,44208,14241,41923,27108,36455,42571,7551, 30210,15221, 23731,25479,35906,26738,27107,34573,27491,39880,33391,12332,8891, 32970,33174,35407,34970,33391,3014, 2022, 4145, 36454,29695,35407,20592,31453,29203,36454,33640, 22421,12467,36454,44208,45179,40780,23317,10556,8997, 29694,26737,22757,34229,35906,37786,6426, 37869,30209,35906,23115,34228,40555,36454,34572,39049,33173,18854, 42390,37993,38937,36453,20044,33922,4822, 42570,37132,34572,36453,17493,45179,40134,44647,43830,27106,44647,15374,39880,42570,41787,43829,19514,28299,42390,34572, 34572,33640,42990,28751,12413,15373,35905,33640,34228,6182, 15743,6295, 42570,14769,44647,6562, 35905,43829,34970,37132,33173,20141,15902,43829,38597,37733,42989, 35406,22126,16924,13995,24958,37132,41040,15790,37760,35905,40896,44647,42066,34970,36453,33173,35905,33640,43501,44207,21053,29202,42570,20474,20237,44646,43829, 38542,42569,13872,37132,18435,27490,43500,18853,33639,33639,44646,43828,45178,43228,31452,39613,43828,39534,42989,43828,43500,43500,26071,38390,40895,39613,42066, 5165, 44207,43228,45178,39980,38582,38753,41568,41466,45178,2959, 4514, 17613,14752,41286,20822,2610, 41923,40968,40779,44646,35406,35406,35904,41040,36453,38552, 6938, 33639,18260,30867,14140,14971,39612,41786,30208,34571,37988,39073,44646,37804,42569,38380,38513,675, 35904,44645,35904,37131,40101,40895,44645,38372,1845, 9519, 35904,32781,29201,33922,39879,41672,39008,42066,37131,41117,38022,40844,20945,45178,39926,39459,43227,42223,44645,37131,32781,34571,37131,38493,39980,37907, 43227,39295,38785,41199,38840,40407,43828,40456,43500,34228,18998,19444,35406,36452,32781,37130,34969,31451,26736,33173,22420,29693,32781,34571,18596,35903,34969, 12858,14049,34571,13333,21839,13616,35903,35903,18717,23316,36452,34969,34228,27891,4825, 20473,18364,30207,33639,33638,35903,34570,30206,21838,34570,21298,27105, 25211,33391,37130,35405,37130,37130,18595,34570,23952,36452,33172,17179,18716,19781,37129,37129,33921,35405,33921,34227,33172,33391,36452,33390,22125,36451,28750, 29200,35902,38017,41199,38439,39650,40101,19210,26397,33172,17040,27489,21179,30205,28298,27488,17667,35405,35405,25763,34227,21178,35404,32970,30866,30204,26396, 35404,34227,5249, 37129,14048,34969,19283,35902,33638,37129,41199,35902,38375,42989,41567,39405,39280,34570,37128,30865,16704,26735,43227,16266,33172,34227,42390, 31450,32970,22585,35902,33921,15060,32126,34968,36451,34226,34968,35901,20236,35901,23730,26070,30203,36451,29692,37128,27104,30202,35901,32970,33921,29199,18153, 33920,37128,23951,23950,36451,39926,42390,35404,17805,35404,29198,27890,36450,33171,31449,33920,30201,28749,36450,18534,21837,37128,19868,35901,23949,29197,34569, 34569,28748,22756,29196,15812,14170,39329,42223,37127,35900,27103,22124,35900,32125,34569,15811,43827,37127,35900,23506,25478,24195,25762,34226,27889,32969,43827, 44207,41199,40779,31448,30864,33920,30863,32780,36450,30200,27888,34226,33171,29195,35403,16958,32124,19443,37127,30862,37127,37126,37126,37126,22584,33390,22755, 37126,43827,35403,34569,23114,36450,36449,31447,22419,32780,33920,33638,23729,33390,36449,34968,34226,37125,34968,45177,36449,37125,22123,16503,35900,35403,32969, 44645,18203,37125,34967,29194,33919,23505,26734,45177,35899,37125,22928,37124,28297,29691,34568,14557,34967,12143,44644,33171,33919,32123,36449,34967,33390,37124, 19372,35403,36448,32780,13054,13394,44207,29690,43827,43227,39767,24700,43499,35402,33638,34967,24432,34966,34966,35899,35899,34966,40206,33919,37124,44206,21297, 32122,35899,12253,30199,17421,33637,33171,36448,33637,37124,34225,34225,23948,35402,42989,31446,37123,34568,35898,43826,36448,33170,35898,26395,23947,18715,37123, 37123,35898,35898,30861,16749,26394,24431,32969,35402,35402,34568,19685,37123,34568,35897,32780,24194,34966,30198,35401,36448,14185,36447,40279,42988,41567,38681, 41922,45177,36447,29193,15742,35401,35897,27887,33637,35401,17950,32121,33919,34567,32969,31445,33170,35401,34225,34965,37122,33637,34567,34567,37747,39041,40843, 24430,40843,27487,43499,20944,34225,40073,41117,38354,42389,40244,41672,34224,44644,18714,16057,34965,33918,32120,31444,20943,37122,15257,43826,36447,33918,33170, 36447,26393,14178,35400,33918,33170,37122,18655,34224,35897,16210,34965,27486,30197,45177,24193,19780,30196,35400,34965,25761,13939,24429,36446,15923,35400,36446, 35400,32779,36446,34964,33636,35399,34567,20140,34224,27102,35897,16817,16378,35399,34566,12469,34566,22754,24699,34566,28296,33918,24957,26392,21296,34964,25477, 37122,34224,35896,14889,32119,36446,36445,19371,33917,35896,22418,27886,24428,37121,30195,30194,23946,21052,35399,37121,28747,19779,33169,23728,33169,37121,14114, 34223,25476,18997,17666,32779,14384,36445,26733,34566,12240,30860,13597,15652,35896,13775,35896,20942,17147,17039,34223,37121,36445,34565,34565,32118,35895,24427, 33389,33169,27885,32779,37120,34565,13947,18594,21421,33636,17038,35399,35895,20139,14113,29192,34565,28295,33169,33917,28746,24956,24192,35895,25760,32968,35398, 33917,33636,12342,32779,35895,37120,29191,32968,14151,36445,43499,34964,33389,35398,36444,34964,22122,24698,32968,32778,33917,15124,23727,33916,33636,22927,34564, 37120,33389,34564,34963,37120,34564,35894,35398,37119,35398,34564,24955,26069,37119,37119,30859,35397,35397,29689,36444,36444,31443,38081,42772,38720,41567,38641, 38959,33916,43226,34223,29190,26391,25759,6794, 32968,44644,45176,38822,37119,26390,45176,35397,37118,31442,41922,17344,35397,26732,15463,29688,30193,35894,41672, 44644,42988,28294,35894,42569,35396,40656,40456,34563,33168,129, 4771, 1707, 6294, 42988,38879,42222,43226,42222,39567,87, 2023, 39317,33635,44206,42988,37118, 43826,41286,43826,42772,35894,35396,38069,42772,43499,38827,45176,38637,40167,41466,40554,287, 3712, 299, 44206,34563,7704, 2065, 44643,42222,595, 43825,45176, 7173, 39632,2146, 22753,7826, 40073,24426,33635,6185, 436, 43226,40362,42066,41672,42772,40604,43498,2431, 40779,6186, 44643,32778,40656,38878,33635,34563,30192, 36444,19117,43825,41466,43498,3829, 37118,39833,40656,39269,44206,41198,21541,39490,40319,39225,40456,41117,40041,42771,44205,4642, 2432, 40279,38282,39182,39809, 39231,40133,40006,38161,39008,4696, 41373,38088,39166,38686,41285,38212,42987,39181,41466,40604,45175,37978,42987,39650,38006,42065,38521,39093,41116,42389,5318, 5167, 42065,44643,43226,25475,15949,45175,37118,38377,42222,40554,42771,44643,37117,24954,30858,34223,20356,36443,37117,33168,14184,24191,38884,41922,41116,42389, 6429, 41040,43225,39065,41116,40040,40279,1798, 13263,41373,42389,45175,44642,40278,44642,40362,32967,42221,41373,2369, 4697, 44642,38467,39459,27884,42987,41922, 20043,36443,43825,840, 43498,45175,42771,39490,40968,38535,6430, 42065,44642,37117,21540,19778,42388,42569,45174,38936,30857,43498,43825,45174,42065,44641,5404, 32778,42987,42221,4823, 26068,32967,40554,18434,39447,45174,34222,34963,20355,28293,37117,33168,37116,5402, 3157, 40133,21539,23945,30856,43824,19867,34963,34963, 4189, 41373,43824,6671, 3100, 37116,14285,45174,16076,38425,22417,19513,43824,34222,13171,4730, 17612,42771,14060,23504,43225,44641,21295,27485,40006,42221,2761, 7233, 39832,41465,34962,43824,34222,44641,2592, 35893,855, 38764,40507,38313,14663,43225,28292,28745,43497,35893,16703,17037,28744,12693,35396,35893,39853,42770, 22752,43823,44205,45173,41567,6878, 38161,41465,42221,41116,39490,37888,42770,40895,41671,38832,45173,39447,14929,38177,792, 41115,19052,36443,19209,35893,6737, 27484,5698, 31441,19958,13484,44641,1939, 7708, 44205,33635,33916,35892,36443,36442,37116,30855,34962,34222,33168,34962,10778,4992, 36442,45173,33634,33389,20472, 35396,31440,40278,42220,37746,45173,39420,44205,41465,39342,37900,21420,32117,39134,23944,77, 40072,2593, 44640,40362,38158,41566,43497,39649,38873,37969,26731, 41566,38174,40554,38776,42986,43497,38210,39390,42388,41921,40362,556, 40507,19282,36442,40604,40407,39593,42064,39173,41285,32116,24697,27483,22751,18099,45172, 45172,43497,34962,27883,27101,37116,41671,30854,5697, 30853,33634,38450,39142,42220,39649,39980,2654, 45172,33634,45172,35892,23315,38867,31439,39432,40507,25758, 34563,34221,18259,33634,33167,35892,34562,39704,37796,38430,2379, 41039,2041, 32778,39979,32115,34961,37115,15105,29189,35892,16184,28291,37788,11888,22926,37115, 37898,41671,40244,37786,37742,35891,44204,42388,45171,39905,45171,45171,25474,37960,44204,23314,43496,35891,28290,40205,43225,34221,42986,16100,33916,32967,35891, 19957,21697,32967,16893,26389,33633,39548,41786,33915,16438,29188,23313,42388,42064,33388,22750,27882,13365,34961,36442,33915,37115,34961,19116,18593,15340,25473, 15651,28743,28742,35395,33915,33167,21419,34562,35395,36441,22121,34961,37115,35395,37114,36441,14888,30852,29687,23726,34221,15715,36441,26730,41671,37114,3391, 42770,45171,30851,32777,35395,33633,43823,26388,34562,14383,33915,39073,40133,44204,41039,39366,39879,33388,30850,37114,14158,25210,23312,35394,40040,36441,24696, 2189, 39788,42387,39155,43823,41115,45170,43224,45170,8915, 10456,34960,37862,38078,41786,40319,39134,45170,42986,22749,25472,32777,35394,33914,9125, 34562,35394, 30191,35394,37893,37114,16099,32114,22583,34960,39853,45170,12560,32113,34221,41115,38066,24425,35891,25471,41465,15138,8798, 42220,19051,34960,34220,21538,33388, 18713,37113,27100,35890,40244,31438,23113,35393,33167,34220,43224,24953,44640,35890,22582,35393,45169,40779,35890,42064,37113,39879,35890,34220,34960,38530,44640, 43496,38931,44640,31437,43823,39294,41198,42387,959, 45169,44639,42986,34959,35393,33633,26387,26386,37113,33388,5064, 519, 20138,37113,41464,11368,11581,38863, 38172,40655,34220,3329, 35393,37803,33633,42387,45169,38033,42568,38502,5126, 22416,36440,28741,37112,33914,37112,35889,1261, 22120,37112,34561,40553,44639,38163, 41670,39548,32777,16795,3953, 34219,32777,37112,38827,39579,45169,40361,27482,39533,39474,41372,37793,38972,4545, 39724,35889,38358,44204,42387,39057,39239,38092, 39225,40709,39853,40318,40167,43496,43822,37816,43496,32776,38107,39021,39259,39259,33632,19512,28289,43822,39685,38827,41566,40655,1838, 41786,3657, 5613, 43495, 6136, 33914,40133,38140,38399,39505,40843,39767,38809,26067,29686,33632,34219,32966,44639,45168,1494, 11808,37111,40507,37111,42985,35392,33632,14538,20698,40318, 39093,43822,42985,45168,42220,44203,42770,33632,39632,21960,11309,41566,31436,32112,34561,45168,34959,36440,8867, 43495,41921,43495,40455,940, 30190,32966,41785, 38286,40506,19684,33167,7404, 38526,34959,38272,34561,10207,31435,21959,37781,37881,40843,11100,39979,25757,13972,34561,37111,34560,43822,22119,35889,4288, 44203, 35392,17665,12524,32111,31434,45168,17456,36440,35392,35889,35888,37111,36440,38936,28288,38795,23311,33914,38800,38054,38208,34959,2969, 41785,38851,704, 45167, 43224,38274,41921,37110,21958,38359,44203,35888,34958,42769,1295, 22925,36439,37110,33631,38228,15076,31433,37110,16622,34958,28287,29187,43821,44203,36439,39342, 41285,7405, 40243,33631,45167,36439,25470,35392,27881,38419,19777,11308,45167,35888,41785,38066,40655,37989,38121,38652,44639,45167,16056,38958,33166,35888,38526, 17220,34219,35887,35887,41921,37851,1453, 4611, 38392,42064,38527,40406,38235,40553,33387,38028,18712,38631,39612,39520,24424,27481,41115,37110,27480,36439,26385, 15309,26384,34219,44638,33631,36438,20354,18996,17260,6629, 27880,5527, 28740,40895,33631,14269,40894,13030,38884,39612,39505,41039,6250, 2403, 1209, 4854, 36438, 6735, 43821,3807, 40318,40167,43821,39458,42985,3954, 4337, 6026, 170, 6137, 33166,45166,12552,5286, 954, 39547,38826,1640, 3426, 2595, 3700, 3997, 59, 2445, 5453, 2404, 4795, 3658, 2655, 6027, 2898, 5526, 3952, 32776,22924,30849,34560,35391,30848,23503,37109,35391,15596,7825, 36438,4669, 36438,34218,32966,33630,10326, 45166,42769,36437,24190,44638,44638,40006,35887,38952,40506,14481,34560,33630,35887,39905,44638,42219,19050,41285,8101, 34218,35391,27479,34218,32966,32776,43821, 14662,34560,39458,35886,44637,22581,36437,32965,35886,43224,16, 1060, 6877, 43223,38369,41039,26383,25756,39724,11307,34218,21177,12979,33630,12439,33913,33630, 38947,42985,45166,43495,3330, 43820,42063,653, 13822,21051,35886,33913,28739,1184, 32110,23943,9633, 43494,38053,41372,38851,5063, 42568,6249, 38605,42063,43820, 41920,11198,40894,38342,35886,10352,11367,5525, 39166,11142,11099,30847,44637,37842,36437,45166,38615,44637,1769, 11514,9690, 39579,39490,2841, 32109,40455,36437, 20137,29685,29186,38021,40967,40506,9494, 39747,41565,9644, 44637,35391,34559,34217,5814, 45165,1029, 32965,36436,42769,4851, 1055, 41198,8744, 11053,34559,16120, 24189,9243, 11807,9978, 33913,37109,7323, 43820,5919, 10279,11513,9935, 10638,16377,34217,1331, 4218, 42568,41920,33629,35390,9977, 2170, 44202,10253,9720, 11306, 9976, 14698,44636,18363,28738,17611,34958,14177,23310,8705, 9832, 41464,8264, 2968, 9378, 37109,44202,9497, 11806,11435,9514, 9408, 9913, 11016,11580,35885,1296, 1571, 34559,38073,38936,42984,37109,39788,43223,44636,40243,13373,33913,34217,37108,44202,11434,44202,11141,40655,9579, 11305,27478,33912,11366,42219,37851,38184, 10015,41920,43223,41372,11887,10014,27099,38638,8606, 11512,11052,31432,45165,11734,11433,38191,10117,1138, 4990, 41198,15595,43820,1330, 2378, 9485, 10637,9077, 32108,17610,11805,30846,11432,8161, 39269,9407, 12256,12765,41464,43223,42984,42568,44201,40967,37108,41038,31431,26382,34217,43222,36436,37108,43819,23112,36436, 34958,37108,34957,10928,34216,30845,8855, 25469,34559,34558,10529,9451, 1208, 30189,44201,20821,42984,40205,38964,40005,45165,32107,35390,17567,33629,23502,33912, 36436,18790,21957,37107,7434, 8764, 10976,10674,11304,11365,43819,38567,685, 44636,13437,19370,42984,39073,45165,10206,11364,10747,12091,42219,10673,10035,11431, 35390,34957,29185,40842,7231, 38782,8609, 43819,10301,10278,10672,11655,42769,20941,41464,10034,9232, 9574, 8034, 41372,1768, 37872,37809,37699,12821,22415,24423, 23111,23725,17302,37107,33629,23110,22580,13638,20591,33912,33629,17709,45164,27098,11511,4172, 42567,26729,34216,45164,38396,18433,34216,42983,33912,45164,30188, 43819,41284,4922, 24422,13065,20235,42219,35885,30844,27477,26066,26728,8760, 44636,3559, 41565,4853, 44201,39072,27879,34216,41284,38427,42768,13383,22269,20697, 29184,13156,30843,21537,33166,33387,1328, 45164,32106,33911,8265, 41114,10746,37107,21836,33628,41284,10636,10300,29684,340, 5920, 44201,33911,7523, 44635,39852, 36435,24952,38814,35390,8507, 14001,11015,41920,24188,26065,27097,8488, 45163,41038,29683,9367, 9194, 41919,35389,27096,5452, 8430, 42983,31430,30187,24187,37107, 36435,37106,38364,40894,40842,12207,44200,39809,35389,37106,45163,1424, 34558,10975,10555,33387,1203, 10064,39904,37106,1274, 11430,10823,20353,33387,33628,11804, 35389,40205,12637,20820,36435,21418,15650,34215,12457,40967,40842,26381,27878,12580,30842,34558,37106,25468,11140,30186,42567,33911,33628,43818,37105,24695,36435, 8968, 11363,17528,33166,34957,8512, 11733,35885,37821,38325,9561, 38171,39724,43494,10974,11241,10033,11240,9621, 28737,11732,10528,34558,1180, 10527,10708,7488, 44200,35885,32776,9975, 37105,45163,36434,21176,10433,19208,8433, 28286,8630, 11579,9578, 14235,9903, 29183,8260, 40778,9700, 18789,8040, 10852,8443, 11051,44200, 36434,28736,20042,29182,35884,28285,23109,32965,21835,38351,39355,40709,41919,43494,33165,28284,4850, 30185,19442,35389,34557,33628,33627,9215, 38656,44635,41565, 12465,34215,27877,24421,36434,34215,15670,28283,20590,36434,38117,41670,40072,43222,40005,45163,38846,41038,44200,21417,37105,35388,11303,30184,33386,41371,17804, 34215,27476,1891, 35388,33911,20471,32775,33910,34214,36433,30841,27876,21175,28735,33627,6630, 42063,33386,42567,43818,37105,26380,14903,35388,34957,21696,39685, 33386,37104,38478,41670,3581, 39951,42768,30183,44199,25755,38663,36433,27095,14549,4486, 42768,37104,38737,37963,39684,18258,34956,35884,13462,13589,45162,38821, 41114,13811,38964,26379,35388,34214,38840,34956,20589,38573,44199,27475,42567,42768,44635,10586,30840,35884,35387,27094,11014,32775,34557,26064,16502,33910,17850, 32965,42983,11013,9719, 10822,9493, 8168, 9824, 35387,29181,37959,43818,44199,38545,42767,34956,23309,355, 39904,45162,41565,4083, 45162,233, 45162,41785,44199, 41784,41038,40278,6632, 44635,43818,42767,40506,1727, 43222,5614, 27875,15442,17900,36433,40318,16030,23942,37104,35884,11429,11731,45161,32105,45161,41197,45161, 21050,33386,43817,33627,33910,38029,38275,3805, 17178,39027,42983,13802,40778,5357, 41919,38107,43817,41284,44198,42218,41037,16566,12899,34956,33165,37907,33165, 17949,36433,22414,1493, 3361, 43222,25209,23941,34955,32104,36432,34214,33165,8133, 10234,9169, 11730,43221,34214,35883,33627,10325,11139,30182,3141, 39307,13287, 17803,33385,37843,692, 43494,39723,40205,38023,42063,40654,38931,37935,43817,18852,44198,45161,43493,43817,44634,39787,42062,5285, 41463,43816,38002,39258,36432, 26063,36432,15570,43816,15614,21834,37104,12440,39723,45160,42982,41114,39832,41564,42218,35883,42982,14480,37103,41283,40967,42767,45160,33626,38496,40966,20352, 38673,44198,42386,21174,43493,454, 10635,37103,43816,37790,43816,845, 41037,38116,30839,29180,18711,1371, 36432,37702,42982,37902,38682,38095,38367,37776,43493, 44634,34213,27874,29179,33626,16621,16794,38768,40842,38791,42218,38418,38883,4670, 44634,41197,41037,24951,42982,39365,35883,37732,39148,17708,42062,41037,40841, 37898,37951,43493,40167,40709,16957,41197,41564,37986,26727,43492,1223, 38270,44198,41036,37961,43815,1480, 44634,15356,40553,40966,39048,40132,42981,41564,39612, 37103,34955,25208,39667,17177,33626,40243,40778,33385,26378,34213,42981,41784,45160,41670,3806, 39632,26726,38217,42566,41919,40361,39832,42386,42767,16537,30838, 35883,37103,6736, 45160,17259,39520,43492,44633,21294,20351,43815,43492,41784,43815,41283,42981,44633,41784,17258,39979,42062,20819,42981,45159,10147,1332, 1391, 1390, 1247, 37102,35387,18098,35387,36431,18654,40553,41463,26062,34955,37102,21956,26061,24694,33910,40132,43492,42766,25754,42386,37102,22748,27873,43221,35882, 1329, 43491,33626,34955,14084,14717,23108,26377,18152,13482,35882,32103,26725,15462,15235,36431,15220,37102,37101,19598,34213,17386,1248, 21833,34557,42062,45159, 43221,1372, 16663,29178,32964,35882,34954,34557,22268,32964,24186,39578,43221,17385,37772,41918,21049,43491,39767,17755,32964,40778,38809,5127, 44633,35386,22118, 23724,4217, 44197,45159,11138,42980,44197,5815, 45159,42980,41463,34954,21048,36431,40406,43491,19369,14887,39355,39904,29682,34954,9831, 27872,23107,42980,41669, 21536,42386,41371,37784,6029, 27871,43815,42766,40841,42061,40204,42385,43491,37101,19049,21695,41783,38535,40204,43220,37101,37101,8128, 41918,42566,30181,34213, 32775,24693,36431,35386,43490,41036,37777,42218,43490,45158,43220,44633,29681,41918,42980,39404,4731, 43814,42566,39667,34556,32775,32774,33385,26060,38271,42061, 40040,39057,41783,39832,44197,25753,39904,40966,32774,36430,7393, 42766,32774,42061,45158,22923,37100,26724,33909,37100,21694,37100,17343,33625,28734,35882,35881, 24692,35386,37100,22747,32774,40406,13406,33625,20041,25467,33385,32102,24420,36430,34954,2110, 37099,32101,34556,37099,44197,25207,43490,33164,37099,6631, 37099, 31429,210, 41669,5813, 30180,44196,45158,36430,17384,24950,27870,24949,42217,42385,18653,21293,27093,43490,32100,26376,34953,39342,43814,42061,38003,40777,39879, 40552,44632,39072,41463,14780,3659, 5451, 19368,4384, 16265,40406,37806,38143,42060,43814,42385,45158,38673,38339,45157,39903,10385,38012,39611,38621,41036,42385, 42060,42766,38444,39704,40405,40505,26375,34212,45157,38144,9804, 35881,31428,45157,14203,37098,34212,17664,20940,35881,33625,7743, 24691,36430,35386,35881,22267, 34556,35385,22579,38225,41918,43489,42979,38188,41371,14614,35880,32099,35880,6880, 44632,33909,38586,44632,44632,44631,41114,19683,35880,27092,42566,30837,42979, 33164,37098,15962,37934,34212,33625,41462,34953,33164,37098,12185,45157,10777,18151,16793,49, 3029, 25206,3582, 21173,33164,33163,45156,14097,23940,15693,31427, 17383,44196,34212,34953,40278,44196,24419,23723,20939,35880,16956,8581, 42384,22746,37098,11137,35879,35879,19281,19441,2159, 44631,4991, 36429,22922,33624,26059, 34953,24948,26374,24418,32098,32773,6517, 41462,29680,34952,38480,41113,42384,44196,37097,34952,31426,34211,21955,33624,36429,40405,33624,25752,20588,35385,37097, 18362,22117,12043,12905,33163,27091,7497, 38964,3290, 10074,16662,8816, 39419,19207,19206,33163,22413,35879,35385,35385,11098,13112,20696,15461,19205,44631,44631, 41564,43489,36429,14133,459, 4732, 39020,40277,42060,44630,40709,41917,42217,37972,12075,42060,698, 12136,33384,29177,43814,42384,44195,40894,44195,40777,45156, 40893,11729,42979,35384,2054, 2594, 37739,37097,34556,38655,39341,42765,39903,12912,45156,37097,27474,44630,10411,20587,35879,27090,18851,45156,6879, 37931,5616, 39379,43813,42765,33909,13195,43489,40040,13068,40966,38067,711, 41462,30836,39355,13862,29679,12387,18652,44195,39216,41371,45155,24690,26373,36429,3617, 45155, 38292,35384,41113,32097,39148,27089,33384,40505,23939,3996, 1612, 40455,35878,45155,21535,37096,34952,34555,37096,25466,38596,34555,40455,41917,35384,9891, 33624, 45155,16405,15549,43489,43488,42765,26723,25751,44195,26058,44630,25465,40039,18150,41370,30179,36428,33909,32964,39419,43488,42765,39684,11578,39926,30835,43488, 27869,2299, 34555,34211,43813,37096,37096,24947,31425,17420,42217,39593,35878,15594,27473,14083,33623,37095,21534,17492,25464,34952,42979,34211,14411,3912, 45154, 44630,37764,38125,42565,41669,38027,34951,43220,27472,18995,44629,40454,42978,1856, 43813,37095,2211, 1875, 44629,35878,33384,36428,36428,38559,33384,38714,42978, 42059,34951,33163,39064,37835,19682,26372,41917,37820,41197,38556,27471,37095,33383,32773,10893,34555,42978,33623,34554,33623,34554,36428,36427,35384,36427,30178, 35878,34951,34554,43488,2246, 42217,37095,4173, 44194,5284, 11886,38259,40505,38542,39578,38147,4989, 6028, 40552,38006,44194,39020,31424,13168,21172,39878,43220, 6516, 42216,33162,33908,27868,33908,35877,35877,34554,41563,37094,36427,43813,13588,45154,34553,34553,33383,37094,34951,37094,25463,34211,36427,35383,39649,43487, 40965,34950,33623,37094,4216, 40072,43487,42216,42978,1528, 43219,39593,38804,33908,41113,34553,33383,16141,19367,36426,41563,23722,944, 6738, 41783,43812,39951, 40317,25462,19511,7025, 13905,34210,44629,40072,42384,33162,38846,45154,43487,39072,42216,39787,44194,43219,43219,21416,35877,36426,4852, 40893,42977,14217,2563, 42216,44194,42565,39390,33622,1855, 16792,38862,6876, 24185,6633, 43219,45154,36426,26371,25461,38281,43812,37093,40552,42565,41036,43812,41917,37972,41783,6875, 45153,40204,12425,45153,2040, 6248, 38166,38836,614, 44629,5615, 39268,288, 41563,25750,42764,44628,38567,39684,34553,40552,41113,42383,1963, 33383,43218,37093, 22921,34950,34950,30834,35383,44628,3701, 30833,34552,22745,7024, 33622,34552,36426,37093,33908,24946,30177,35383,28733,37093,35877,37092,37092,43218,41462,38472, 40708,5213, 35876,40777,2471, 5699, 43487,37911,39341,3702, 3458, 6031, 3531, 30176,41916,895, 42977,44193,43812,43218,1172, 3532, 41563,41461,29176,36425,14648, 2705, 42764,41562,42215,35876,13971,45153,41562,34950,33907,15272,35876,41562,38696,27470,5620, 39155,3618, 43811,39787,41196,40405,43811,24417,35876,42215,32963, 37092,41196,43811,38457,23501,5816, 38964,2819, 39903,1649, 40965,1665, 5701, 40277,42383,42059,17301,43811,29678,12759,43810,35875,35875,15848,33382,15767,33907, 32773,20695,12831,40277,42215,42977,2031, 3001, 42565,23308,22412,39474,39205,34552,33382,17849,41370,41112,22116,18097,43486,31423,24184,33907,42977,168, 40039, 4441, 39684,39378,30832,43486,16000,39432,42976,44628,39166,39567,34949,40405,14262,41196,43486,33162,42764,32773,37778,3214, 42976,41283,37874,39950,39703,42764, 38773,44628,43486,39979,1508, 41196,42763,37092,30831,38750,45153,39578,36425,39100,44193,35875,27469,43218,36425,34949,37919,27088,43810,45152,29175,40893,24945, 38612,39925,42564,40404,39808,41669,1123, 3620, 42383,40603,38381,2247, 39147,39878,37091,26370,34210,22266,26722,38676,44627,38596,38509,36425,2923, 39123,41370, 6253, 5621, 44627,43217,5704, 39355,39431,37091,40166,42763,42564,40893,27867,33622,43810,2924, 38059,419, 38227,4856, 39341,3089, 45152,38832,39027,39404,32963, 40777,41782,42976,45152,27468,4996, 33382,34949,12541,42215,38478,40243,33622,18926,39592,45152,41916,43485,37091,14751,27467,40242,42383,34210,42059,35383,30175, 14318,35875,32096,40892,40317,40551,4130, 44193,23307,36424,44627,39008,34552,41461,39950,42564,28732,35382,29677,20136,37091,33382,37899,39950,39505,42976,35874, 4220, 27466,39566,35382,38246,37090,41668,20818,42564,39547,41916,4797, 44627,41782,3915, 41112,37090,41035,45151,39683,44626,40505,41370,42975,43485,37090,43217, 44193,44192,43485,42563,43485,45151,35874,42059,18149,45151,22411,24689,334, 585, 2763, 2191, 25749,33381,10498,38717,4733, 44626,40504,44626,41562,42214,37090, 26721,32963,12446,37089,38466,39431,14382,27465,45151,34949,41461,13678,13372,41668,34210,37838,5528, 6636, 45150,38371,38904,39878,22744,40654,38941,38826,44192, 3619, 25205,34948,25748,34209,39419,40892,42763,42975,35382,19115,3363, 2324, 29676,37898,38651,4736, 23106,39020,38118,2446, 20470,42214,22115,34209,35874,36424, 30174,41461,44626,23721,43810,37924,5454, 39020,1407, 932, 43484,38214,34948,40404,42763,6139, 23105,41782,11956,41283,4338, 42382,35382,21047,42214,16856,16748, 21693,34948,36424,2704, 15271,41112,24416,7029, 33621,37089,29675,39747,35381,856, 37089,6881, 22920,17527,41916,814, 4799, 38866,1210, 41915,2472, 43484,107, 3862, 3660, 1728, 38160,41782,34209,35874,42382,37089,23938,45150,35873,35873,35873,34551,34551,33381,41915,45150,42563,39747,14647,44625,534, 45150,41282,44192, 3998, 41369,39238,37088,42563,5922, 40841,42058,24688,15308,35381,33381,35873,19204,35872,2016, 42762,36424,38941,40101,40708,42563,42762,39631,44192,33162,30830, 4289, 44625,1529, 37088,6885, 36423,33907,30173,33906,37088,31422,33621,15496,38667,42975,38598,42975,6251, 43484,39431,41035,44625,42762,41460,40317,664, 42762, 1839, 42562,1166, 44625,45149,38290,33906,2731, 17899,38717,1107, 40277,2620, 3863, 39978,6882, 41781,41369,42974,43809,42974,33381,971, 39747,41282,42974,44191, 42058,45149,20350,28731,42562,14821,38158,41112,42058,2621, 42214,38358,40276,4340, 37088,32963,34551,34551,26369,34948,13244,43809,45149,39631,40965,40892,32962, 13006,27464,30172,19203,34209,20938,6739, 43809,505, 40361,12749,37087,39317,42761,35381,6375, 43809,2345, 42562,44624,43484,43808,41035,41035,409, 43808,6883, 40603,15245,43808,41369,42058,39329,45149,43808,45148,41460,44624,27087,39566,41282,41369,34947,21046,13946,34947,16075,3914, 34947,41460,4442, 3066, 38930,103, 3771, 34947,40361,40132,39631,43217,40005,44624,43807,40965,43807,42562,40071,40166,40964,32772,12455,42382,399, 42761,44191,44624,43217,44623,33906,4859, 3030, 5287, 43483,45148,45148,13929,39703,43483,3999, 41195,43216,39458,686, 44623,42761,44191,45148,40404,41282,42561,42561,38676,40360,43483,3292, 40892,40708,42761, 2190, 42974,30171,12121,40071,44191,39041,39238,39268,39611,38339,42382,45147,40654,44623,41111,44623,14928,40964,43807,40551,30829,34550,20586,38704,44622,43216, 44622,21045,34550,35872,30828,37087,35381,31421,34208,32962,36423,30827,33161,41111,45147,44622,32095,31, 1694, 4924, 6884, 4926, 21171,36423,33380,42561,38019, 44190,41368,42973,40776,44190,40454,39666,43483,38113,42561,45147,40654,40317,44190,38609,42560,40708,42057,38358,32094,8781, 1573, 38171,39703,42381,45147,30170, 32093,21832,37087,35872,34946,36423,27866,32772,22265,32092,35380,39808,45146,44622,45146,34208,35872,37087,37086,38641,44621,33906,8870, 36422,38100,3427, 548, 39852,41368,42381,42973,40166,44190,41460,40603,42973,6252, 2597, 42973,33621,40551,36422,19866,40166,36422,39115,11803,16183,42213,42760,40653,32091,30826,34208, 17036,32772,28730,25460,33621,8005, 31420,6141, 36422,32962,34208,10745,14779,6373, 20937,21831,6635, 41915,18202,38010,34550,1070, 42760,38439,32090,34946,38835, 9198, 2596, 39925,26720,40005,41368,43216,17754,21044,34550,33620,42972,41915,44189,43216,44621,38162,41459,32089,43807,30825,45146,32962,12595,41914,39079,4671, 33620,14813,19114,33620,30169,33380,25204,33620,41111,34549,2762, 19366,7643, 35871,16294,18148,37086,45146,22578,30824,27086,23306,25747,23937,27865,38179,40276, 38138,38484,42760,40101,39307,42760,41368,37696,45145,37751,44189,43215,38029,38011,37882,37725,44621,38002,44621,37978,11510,34946,14838,5457, 15863,37086,44620, 38104,43215,37696,44620,37945,43806,43482,38045,37779,41195,38644,37812,42759,32088,10180,30823,33905,26057,14444,19365,43806,34946,38116,11728,40454,6376, 33380, 37086,36421,44620,8750, 29674,37889,41281,20349,38307,42213,45145,33161,8420, 1983, 2729, 34945,8588, 35380,6374, 34207,34549,21692,37085,40841,42057,16264,27463, 37779,38075,44620,38795,39950,3332, 5618, 42972,41668,38249,25459,43482,1857, 8474, 2622, 6138, 10384,34549,10497,38301,40039,36421,37748,43806,37913,1572, 6634, 44619,39419,42213,42759,34207,45145,40840,44189,39683,43806,16263,33619,33905,8566, 207, 44189,5455, 38660,41781,39134,34549,34207,33161,3955, 5705, 35871,34548, 36421,10634,37085,32087,42560,35871,42972,35380,41367,42759,45145,44619,39109,38467,38538,39155,41459,13071,13700,40504,34945,21954,29174,15460,14583,35871,33380, 35870,37085,42972,39294,37085,44619,41367,40964,15137,16354,24944,20936,41367,39165,45144,40404,37700,42759,37788,38582,39787,45144,40204,37084,33379,37084,38506, 33619,12788,35870,21292,45144,33379,36421,32086,34945,37084,14518,45144,12197,34945,20817,24415,35870,26368,17146,21043,21042,35870,41914,35380,39431,19510,37879, 42057,34548,16074,26056,42560,35379,34944,26367,42057,3864, 43215,38446,37796,35379,38059,41367,38800,43215,43805,37779,44188,41561,38112,44188,43214,44188,38862, 38076,41111,38582,43482,44188,21041,42381,13788,43805,45143,33905,37945,21170,44187,15508,44619,36420,40039,22919,15136,1828, 31419,43482,43805,44187,45143,3178, 4546, 5617, 45143,45143,45142,30168,25746,40038,19509,24943,17491,39181,40316,44187,13397,27864,41034,43805,37084,38936,40100,35379,22114,34207,21040,18592,27863, 14374,36420,14512,3266, 42971,32085,34944,44618,44187,45142,26366,37902,45142,39592,40454,3459, 44618,39204,39631,43214,43804,41561,41781,33619,23936,37083,28282, 25203,40242,33619,5921, 31418,22918,20816,25458,39115,28729,29173,32961,33379,21291,27862,34944,13896,29673,35869,32084,15420,36420,32961,37083,34548,3661, 35869, 39231,32961,40316,33379,34944,34548,43481,13412,42381,43804,42560,36420,38470,2015, 41914,4084, 1432, 1757, 159, 6740, 13919,4858, 20469,31417,44618,43804,28281, 35869,28728,38043,39307,42971,45142,36419,30167,34943,5923, 42056,38924,42213,33378,45141,11509,42971,6032, 39666,41281,15123,12371,79, 3362, 35869,22577,33378, 44186,40964,15122,43481,45141,3560, 44618,992, 5818, 1997, 6518, 35868,44186,40776,42380,44186,40242,22264,44617,19508,30822,44186,43481,40165,45141,29172,13281, 39473,39925,35379,2262, 42380,37083,41781,33618,36419,37798,42212,1758, 43804,4796, 41366,37887,21039,38470,14119,45141,37083,34547,14996,43481,12587,29672,36419, 41668,37082,31416,19364,37794,5703, 27861,44185,38720,40203,41561,40242,41780,40316,43214,4131, 39978,39115,42971,38930,38577,38188,38406,42380,39566,42212,43214, 39306,41281,17609,36419,45140,34943,40241,45140,39611,43803,38775,42559,40165,42970,42758,19113,38701,42758,44617,3179, 2323, 40203,41110,39458,44617,44185,41561, 27462,37973,39197,39666,1127, 5529, 43803,44185,39123,3456, 42212,43213,41195,5288, 388, 22113,33378,13904,23935,34547,36418,34547,22112,25457,24942,15884,21830, 30821,25745,33618,37899,42212,33618,39317,20815,39041,15270,43213,38435,31415,42559,12663,12107,39703,4219, 42380,37801,41034,43480,45140,37958,34943,38832,22743, 32961,24687,35868,37859,34206,37082,34943,27461,17566,38720,4339, 26365,35868,37082,36418,15714,26364,32772,40963,40776,45140,35868,34942,33161,37082,6520, 43480, 4086, 42056,2322, 7027, 45139,13436,40504,27460,34547,42056,39072,35867,34942,138, 44185,3291, 646, 1574, 39215,1747, 32083,41780,17948,33618,26363,42379,39808, 35378,18994,26055,36418,19865,19112,30166,36418,36417,6030, 35378,31414,27085,707, 40551,3265, 40100,3913, 3213, 4672, 41034,44617,37081,33617,38388,22410,35867, 38619,16404,22409,39611,15883,40603,32960,41560,38229,18533,40038,39294,34206,20814,43213,38816,40403,26054,17455,24414,15633,3241, 43803,22742,38636,27459,34206, 40653,42211,34942,44184,36417,4735, 42211,43803,17300,529, 43213,39505,38414,38809,44184,41560,42970,35867,26362,44616,35867,38212,38396,40276,490, 19864,40653, 45139,44616,39204,45139,33378,20348,16747,23500,36417,33905,12926,5702, 2076, 41366,42758,41914,42758,41780,42757,41560,25744,5700, 38791,42757,44616,41034,41667, 41780,42379,4855, 42757,3770, 45139,41779,37984,40840,44184,44184,38958,5065, 2564, 5706, 37081,35866,28727,22408,38330,38478,43802,39746,40360,42379,41560,37987, 38132,42970,40132,45138,41195,45138,34206,28726,34205,3621, 4993, 45138,40602,45138,33617,29671,39354,40653,40891,40276,44616,40652,38772,41033,4048, 6741, 39328, 44615,41913,480, 43212,3331, 37081,14944,44183,43802,4857, 31413,33377,33377,42559,36417,1982, 4734, 44615,696, 43480,42970,44183,3563, 33904,34546,37949,40840, 39258,43802,32960,38644,38506,42211,44615,40165,20694,4798, 43480,39949,41366,41667,40891,35866,33617,34942,24941,15339,20468,35378,19863,32960,35866,35866,13269, 40891,40004,34546,44615,36416,42211,37081,27860,36416,34546,33617,31412,20040,33377,43479,43802,38603,19862,40707,40963,44183,1940, 45137,38790,41194,44614,43801, 33904,39610,33904,33904,36416,24940,36416,36415,43479,36415,15789,44183,43801,705, 45137,12880,38336,41913,41194,4995, 35378,41281,33616,40652,16471,42969,41033, 41280,40203,45137,1468, 5214, 44614,1185, 43479,34941,5819, 24686,36415,19507,33903,33377,15164,4385, 43212,43801,40403,29670,32771,40707,25202,14511,39258,33903, 37080,33376,20347,3562, 43479,39723,44182,4994, 1150, 39504,37080,15882,29669,20813,20585,14202,23934,44614,22741,1892, 3703, 44182,3704, 1287, 38516,45137,42969, 45136,38317,43478,35377,41280,42056,45136,3808, 42969,41110,42757,45136,38116,39457,43212,40038,44614,2970, 40963,44613,39204,45136,43212,42559,42558,44182,42969, 42379,5066, 43478,41913,42378,40004,41033,43801,41459,41559,41779,38304,39473,43211,40004,2447, 33903,5619, 28280,35865,44613,44613,33376,30165,41366,43800,18651, 13810,40131,41459,1481, 40203,570, 43478,6377, 40963,41779,43800,41667,37080,706, 33376,34205,34941,20935,32771,36415,40316,42558,43800,6519, 40891,44182,2160, 2565, 793, 3212, 2380, 21415,36414,18925,38535,38541,39341,24939,39365,45135,40131,39592,42210,38324,33376,20584,39473,41559,41365,11654,8351, 43800,7028, 24685, 33616,34205,33903,35377,8773, 9349, 8634, 14943,40275,30820,15741,41667,27859,24684,8320, 43478,42968,35377,35377,39197,18096,34546,37080,37079,37079,44613,37079, 36414,37997,42055,41280,42558,40840,12508,40360,38056,39365,29668,39702,19681,42756,43211,40453,44181,22740,15154,40403,37079,44181,33902,19956,36414,13479,34545, 33375,33902,34205,21414,36414,29667,27458,35865,35376,28725,38952,30819,26719,39925,41194,36413,39418,23720,40038,42558,38123,40890,39504,45135,38114,40202,36413, 39746,42968,33375,38930,40360,24938,25743,39446,43211,13414,4386, 39306,39592,3333, 42210,41779,38483,42557,38644,18201,23933,40359,29666,34941,38878,13382,1056, 43477,28279,32960,41365,37078,34941,14661,18532,25456,21691,43477,32082,27858,29665,27857,19048,18257,45135,40359,15740,36413,44612,34545,35376,38125,40707,40131, 41194,14082,45135,23719,14677,7611, 39340,33902,34204,36413,23932,27457,32959,40202,16470,32771,37078,37773,45134,37078,1408, 41193,38553,5817, 37078,34545,33160, 33616,18850,34545,40315,37845,20812,16993,41280,43477,40839,37077,18475,38215,44612,41033,21533,37077,14340,18147,7964, 32771,3561, 41778,22111,35865,19111,26053, 31411,33616,21532,35865,37077,34544,33160,34204,35864,24683,24682,35376,36412,43477,7245, 44181,5820, 34940,21690,41458,33375,34204,43799,16209,33902,16437,24937, 23305,23104,12748,35864,33160,44181,41559,35376,33901,41032,42378,23103,13472,13355,34544,21829,293, 42210,6142, 6140, 2248, 30818,39831,2057, 40359,41559,20693, 32081,3457, 4925, 43211,1392, 38462,39903,40100,2730, 2249, 586, 44612,35375,41558,41458,38082,12707,43210,21531,27456,13891,13151,35864,28278,9537, 42378,44180, 3861, 41558,37077,14837,37076,7026, 2971, 35375,35375,27856,13524,24681,12921,39489,42968,21689,37076,39404,41110,694, 45134,2055, 41458,4487, 37739,4923, 37778, 3495, 38585,39702,37832,39566,39165,41110,193, 37968,42378,42055,39100,39172,40403,38262,48, 33615,38971,38971,45134,11239,33615,35375,42756,37076,35864,36412, 37076,35863,38643,37849,39280,39565,44612,42756,38044,38795,14886,43799,38004,42055,45134,44611,39033,43210,30164,43476,2300, 39446,37868,41666,40962,39902,40652, 38781,42377,41279,41109,39808,21828,13443,39649,31410,44180,38526,43476,39878,34544,40890,38404,39666,43799,41558,35374,18591,28724,35374,33615,34940,27855,36412, 15291,44180,43799,37075,37075,38452,36412,37722,37989,28723,39786,37857,35374,5456, 38166,22407,34940,14339,33375,34940,18650,34204,42756,22917,43798,24936,42210, 15766,43798,38327,34544,38404,43476,32770,39648,37934,43210,42968,38092,39181,821, 42557,38552,39831,41778,40131,42755,43798,39949,35374,34203,32770,27455,13756, 35373,17035,35373,25455,33160,19861,32770,22916,31409,41193,33901,20039,14537,30817,33901,2056, 12237,2972, 102, 35863,43476,17419,24183,14032,44180,29664,32959, 35863,44611,39807,45133,43475,35863,38916,44179,25201,41032,39365,43475,34543,23304,7732, 41458,35373,9934, 10585,32080,8708, 30816,38358,39430,34543,21827,11428, 23499,10410,37075,15219,34543,18649,32079,30163,32959,23718,21826,4085, 33901,40890,15569,33900,35862,17342,26052,25742,23931,32959,32770,18590,33615,37075,36411, 31408,32769,19110,28277,36411,28276,37074,35373,29663,12684,36411,19597,37074,33614,37074,24680,14631,27084,34939,34203,39949,13435,32078,37074,33159,26361,33614, 32958,28275,32077,27083,36411,34939,18361,35862,33900,18924,33159,16353,36410,28722,32769,21530,8386, 5062, 33900,30162,27854,31407,29171,30815,37073,36410,20234, 34939,34939,33900,33159,24182,38971,27454,31406,34938,32076,44611,36410,40165,33899,22739,42557,44611,37985,39258,22406,39831,22738,34203,26360,21038,37073,21688, 24181,44610,42755,37073,43798,41457,20346,33614,34203,31405,37826,34938,35372,11302,42055,43475,40776,44179,41365,39191,44179,2038, 9718, 7583, 10160,950, 11136, 8182, 40164,45133,33899,9381, 7175, 40037,4920, 9400, 12109,12521,9856, 11727,43475,35862,41913,45133,39446,22737,35372,14167,27853,38332,42054,38772,37073,38193, 40315,35372,22736,45133,32769,36410,2651, 40550,42209,43474,45132,40004,42967,42967,37072,37072,36409,7603, 34938,33374,13354,18256,17219,42209,33614,25741,43474, 41109,39224,21037,34202,40275,22576,38996,45132,36409,36409,33159,35372,35371,33374,35371,7215, 39364,1916, 7159, 41457,3616, 12076,12268,23717,19680,27852,26359, 44610,34938,7800, 39165,12981,40315,42967,44610,12004,42377,6508, 42054,35862,40775,42755,45132,30161,43474,35861,33374,33613,32958,20934,17526,35371,18432,34937, 21290,25740,35861,37843,24180,39746,40962,40504,38169,27453,30814,19109,22575,20811,5611, 16565,1972, 43797,726, 15441,34543,33899,22735,35371,35861,19955,37072, 33374,38655,43474,43797,18360,41666,3176, 43210,42557,45132,44179,32958,24179,26718,36409,34937,35370,42377,43209,34937,34542,13272,5448, 44610,37706,37072,33373, 22263,33373,22110,37071,16055,44609,21953,30160,30813,41778,15406,22734,15022,40962,17565,32958,32957,37071,33899,20810,1098, 22915,43473,41666,44609,45131,39807, 41109,43209,43797,27851,35370,14705,30159,23716,20583,2283, 45131,818, 44609,26051,691, 40202,42377,38379,45131,4793, 40202,38889,38421,524, 6626, 14750,34542, 12863,41109,35370,27452,42755,43797,44609,42376,38647,34202,36408,6512, 39430,39294,45131,41279,39340,40839,39109,41032,37071,36408,17753,16892,15739,36408,35861, 35370,21529,19860,29170,7021, 32957,29662,22109,13871,35369,34937,37071,20345,32075,17564,32074,26717,34936,33613,33158,36408,28721,38074,43796,45130,24679,29661, 41032,44178,18531,36407,40037,34542,16501,36407,37070,37070,35369,41778,39978,41912,1083, 36407,39591,33613,16403,36407,42967,43473,2282, 21825,13755,17111,43209, 13869,29660,34202,43209,27850,36406,30812,35369,26050,35860,36406,37070,19047,33898,41365,20038,31404,34542,34541,34202,34201,35860,42209,37070,36406,29169,40652, 25454,15922,12330,33898,36406,27082,37927,38092,35369,43796,15121,36405,22733,23102,35368,18849,26049,37069,26048,28274,35860,17454,16661,22405,38060,40839,42054, 40651,34936,39746,40201,44608,40890,12102,40775,4335, 1902, 33898,32769,36405,22732,37069,26047,24935,1286, 3697, 36405,2728, 33373,44608,34201,22108,23930,13809, 32073,871, 31403,33613,6135, 41364,44608,41666,2423, 30158,16119,36405,35860,36404,44608,38046,30811,6505, 6244, 26716,33898,39489,43208,42556,39147,42376,42556, 17707,35859,37714,35368,6243, 45130,37069,37844,4794, 43473,42754,38127,38270,41777,35859,38516,22731,28720,35368,37872,41193,39665,43473,17898,40275,38498,13819, 24678,38007,41279,34541,36404,26046,37069,31402,799, 40275,3654, 39610,31401,26358,40651,35859,16620,994, 36404,35368,34936,34541,22574,34936,34541,37773,39807, 40651,43796,2650, 43472,45130,42054,44178,23929,15881,31400,18431,33373,37068,32768,22914,18095,22730,22913,30157,12675,35367,32768,33897,33372,13497,39008,13124, 40889,42209,44178,14630,40889,16855,16240,35367,35859,34935,20344,14836,29168,20135,24677,33158,37068,44178,44177,16262,27081,24934,43208,18589,30156,33897,34540, 37812,43796,38404,39114,45130,40453,43472,45129,43472,39831,44607,14873,39902,45129,43795,44607,16352,41108,42556,19954,27080,19363,37068,35367,37068,14942,28719, 38903,41558,35858,21289,37869,38996,44177,1569, 41108,38263,39154,38862,39280,44177,41031,40707,42376,35858,30155,2210, 42556,39268,41193,40201,32957,33372,34935, 19440,44177,5449, 29659,39723,42555,40201,42966,42376,43795,38411,29167,34540,28718,6246, 41665,13253,3652, 15961,29658,21288,36404,33158,32957,25200,22107,24933, 35858,22404,14510,34935,43208,38963,4170, 39648,39328,25453,30154,16500,39830,18710,34540,39565,20933,26357,45129,41364,36403,38775,41557,42966,4211, 41665,2541, 44607,42208,24932,34201,34935,33158,34201,23498,36403,34934,41777,34200,37835,43472,42555,14015,45129,44176,25199,38140,32956,43795,33372,35367,37067,35858,34540, 34539,30810,27849,31399,30153,40274,20582,30152,35366,37067,33612,38027,40839,45128,43471,40241,37067,44176,44607,40889,38084,38611,44606,38971,40130,38332,39807, 40550,40602,39722,37067,42208,29657,37066,17490,34539,39806,19046,12295,34539,16596,16261,38846,41457,35366,38357,40706,1179, 42375,35366,27848,5210, 721, 2321, 45128,38570,39418,44176,26715,38363,13326,18045,20809,20692,40071,38402,43471,1447, 40651,40359,5121, 6367, 44606,28273,19596,39268,1938, 40003,39767,40037,43208, 30151,32956,40962,43795,42966,17453,30809,27847,25452,18530,34539,35857,37066,45128,18044,24931,27846,34934,33157,37066,37066,2281, 45128,2897, 39489,41457,34934, 39064,43471,41279,40650,42966,16160,26045,17299,17706,33612,33612,34200,36403,35857,36403,32768,12529,43207,38052,38851,42754,38519,32072,32956,35857,21952,36402, 34538,31398,14605,26044,37065,33897,13036,33897,15948,21824,18993,22262,27079,37065,45127,24178,26356,33896,32956,18146,38370,34538,35366,40775,38733,41912,41665, 41456,38505,39978,40706,38737,36402,24676,42375,42555,37065,39108,15459,35365,33896,40775,22261,42965,42208,35857,37065,33896,37064,44176,33896,4609, 31397,45127, 27845,35856,7413, 41108,40274,32768,43794,42208,43794,14528,38324,41665,13265,3558, 26714,40706,35856,37871,35856,23928,33895,35856,14704,33157,34934,24177,1039, 22260,44175,21687,38007,43207,38095,35365,16351,38611,38596,2074, 44606,40037,44606,30808,25451,2139, 3727, 40889,32071,33612,37949,38173,34200,35855,34538,33895, 37745,39215,38452,5809, 39122,37064,33157,42754,39547,31396,24413,15075,35855,24412,17663,34933,27078,39766,39247,42754,40706,30807,37064,14381,33895,40650,42053, 17110,33157,34538,35855,36402,42375,2703, 40358,40705,40602,36402,39033,2789, 38840,43207,38491,43794,44175,39578,35855,34933,43794,250, 4729, 41192,39533,39390, 42555,29656,42375,36401,36401,22403,15880,41031,33156,15193,20037,34537,15163,15879,34537,15477,30150,34933,32767,19953,26355,27844,35854,33611,37064,25739,30149, 40315,42374,24411,20233,16536,29655,35365,27451,27450,38051,44605,15395,43471,42965,40003,41278,2966, 44175,29654,42965,21413,35854,44175,1327, 11885,34200,12667, 40774,19776,1273, 43793,44605,43793,40838,39093,40402,43470,42374,41777,16499,45127,33156,39565,37963,41777,41278,43793,42207,39064,44174,42207,18043,33895,43207, 44605,44174,672, 45127,39565,44605,43470,44174,41364,43793,3211, 40164,43206,42753,39231,40071,42053,36401,41664,41456,40164,44604,41456,41192,41456,43206,43470, 43792,45126,45126,42374,42554,42753,44174,38862,40961,39364,41912,44173,45126,42965,44604,38753,43470,42374,43206,41776,40503,43792,42373,41031,38400,45126,40705, 41776,44173,4921, 42964,40070,42964,44173,2619, 43206,35365,3699, 38647,39134,41364,41776,249, 4987, 42053,38161,39520,43205,41455,43792,44604,39877,42053,42753, 42373,45125,37751,32955,37063,33611,710, 45125,44604,6020, 5060, 2402, 401, 45125,41031,2030, 41912,1682, 229, 45125,516, 33372,762, 40402,39378,25450,42554, 37063,34199,33371,37930,37905,33371,30806,33894,17705,32955,34199,10383,30148,34933,41911,37063,8337, 44603,10633,39238,5910, 43792,10221,31395,34537,33894,45124, 42964,29653,44173,38135,38326,38127,39665,39378,43469,45124,39238,40888,41192,40402,39830,43791,44603,35364,42753,44172,37063,671, 44172,3950, 43205,45124,43469, 29652,42554,5912, 44172,32955,37062,23497,37994,38335,38187,38362,38764,43205,39328,34537,13353,34932,36401,29651,17298,26354,39786,38175,41030,38821,32766,43469, 37921,44172,39306,38581,39745,38171,45124,39745,18200,40503,41278,43205,38328,26713,10382,6732, 37062,44603,37062,44171,36400,35364,34932,11508,43469,42964,32955, 35854,34536,41278,40602,32954,33371,33156,45123,18199,12055,43468,34199,41557,43468,33156,35364,3653, 34536,32070,42963,43468,37062,29650,37061,30805,42207,34536, 37061,42963,33611,36400,43468,33894,11507,24176,21287,28717,43467,43791,42963,35854,40503,41911,42963,63, 40241,1853, 6509, 3949, 27077,45123,24175,28716,36400, 35853,15878,12400,36400,34536,42052,35364,17034,21823,2126, 28715,38958,39215,40961,40201,25198,13284,32069,42052,44603,42752,15059,44171,17525,31394,40274,23303, 9134, 43467,40036,43791,42554,42373,45123,25738,39766,40774,1065, 985, 42373,40314,18255,36399,37061,28714,34932,34199,17218,33894,26043,34932,35363,4214, 34535, 6731, 21169,44602,32068,34535,4607, 3327, 42553,42207,7948, 35853,39404,23302,39014,41911,44602,5211, 11884,44602,870, 44602,4439, 38636,44601,2727, 1611, 42553, 41557,40550,27843,770, 42052,40, 44601,43204,45123,42752,43467,37061,337, 44171,2188, 3725, 40314,45122,43791,45122,44171,44601,39237,44601,1194, 39293,40888, 43467,41664,40838,38429,41455,39852,21, 44600,39204,43204,45122,40070,43466,39014,34198,5523, 43790,42962,42553,2422, 5447, 42206,39014,41664,39806,45122,44600, 39806,40070,40705,39591,38988,45121,42052,37060,44170,33611,38596,44600,41911,42372,42962,37060,40314,40100,41664,38383,38312,41663,37887,39154,41108,41557,38878, 38347,39520,37833,37744,38507,41556,44600,45121,40650,40650,41363,42962,5123, 45121,44599,3424, 42752,35363,45121,35853,34535,29649,33610,17217,35363,37699,45120, 38193,27449,1681, 43466,38337,41107,42206,42553,45120,44599,349, 5209, 42051,1413, 40036,38332,43204,39390,41776,37965,41775,43466,38996,40705,42752,35363,35362, 30147,30804,20932,34931,41910,34931,36399,45120,44599,44170,43204,40099,32954,37817,37861,38247,38655,39403,16098,40241,33610,33893,38534,40649,40961,25197,33371, 43466,39564,39610,38337,39354,40358,42552,38264,5807, 6511, 38567,45120,38039,38222,40003,39389,39019,39257,37060,34931,8149, 39786,3994, 8719, 10032,8964, 9682, 8717, 40503,45119,4082, 33370,8485, 41910,6730, 44599,36399,37980,40453,38509,41663,38424,40314,38708,42051,42372,39215,39032,38745,38790,38146,41455,44598,40601, 18003,39630,38963,41775,26712,42206,10892,34931,26042,19362,27448,34535,32954,13081,31393,34198,26353,34930,17662,42372,15074,22729,35853,18648,17297,12211,41363, 42206,44598,45119,35362,30803,9231, 10146,9392, 8294, 42751,43790,8933, 11362,9912, 44170,511, 9763, 35852,37060,34930,41277,24675,45119,45119,35852,44170,10973, 35852,19045,40550,42051,30802,44169,44169,35852,28272,14338,33155,33893,43465,45118,35851,18002,12845,33370,36399,1406, 39007,40888,40774,39702,42051,38873,39340, 43790,6022, 36398,13209,40649,35851,28713,35362,33370,19044,20581,22912,15355,35362,41910,13945,42962,20232,40130,12304,37059,33370,33893,39533,34930,42751,41277, 45118,30801,18529,35851,40961,41107,33610,20580,27447,43465,32067,24174,24674,36398,13091,37059,33610,33893,34198,22911,43790,39224,42751,42751,43789,26352,39924, 41277,42961,26041,41663,44169,22402,34930,36398,36398,37823,37059,24673,39473,15947,33369,32765,39648,38188,43465,38189,39533,41775,38835,21686,43203,40402,39949, 42750,42552,44169,17897,38947,33609,35361,32954,42372,2562, 43789,2261, 34534,35361,36397,12528,42205,20467,38467,44598,39446,40070,42050,36397,365, 3177, 43789, 24173,39702,38781,37818,40838,37858,25737,33155,44598,26351,3529, 39001,33609,1082, 1595, 29166,1074, 35851,31392,34929,36397,18788,43789,38413,42205,39224,44597, 42750,44597,34198,37059,34534,21168,13423,36397,35850,38958,35361,42050,41455,44168,18145,35850,35850,34929,32066,20036,35850,42750,23715,25736,35849,35849,13146, 41663,6733, 42961,23101,18001,23714,36396,26711,41775,21036,44168,38175,37058,2470, 44597,41910,41662,591, 42552,28271,40130,44597,32065,17296,33155,16816,44168, 23713,33369,40130,33892,21167,21685,19952,38161,1054, 3804, 42371,2280, 21822,39027,18042,16791,19202,20035,17489,40704,40774,44168,41192,41030,43203,41556,14902, 38947,35849,34929,32953,38063,42750,41454,38300,35849,43203,40704,39007,44596,14076,38631,42961,44167,26710,24172,35848,39532,36396,41454,44596,39745,27842,43203, 44167,42371,5914, 15877,24171,1854, 599, 1981, 2818, 38302,38462,35361,40099,32953,34197,6625, 32953,8800, 9699, 40704,42552,37058,289, 39766,19201,28270,23100, 12691,26350,36396,40401,41454,42371,25735,23712,33892,36396,33369,4606, 7490, 27076,8261, 11012,34534,41191,44167,38500,39948,43788,42371,42050,42370,39852,32064, 42551,41556,20931,19951,6245, 38941,40773,39701,38392,39165,41556,39745,42551,38346,42205,44596,44596,39591,44595,44595,42551,42961,20034,40099,42205,41454,38910, 44595,4986, 37058,42551,45118,45118,45117,44167,43788,40099,10496,32953,34929,40502,44166,1756, 44595,10610,37058,44166,45117,29648,39164,43465,39079,36395,550, 40838,6510, 42204,41030,44166,38473,42050,32063,37057,27075,35848,3494, 16923,36395,34197,36395,33892,34197,29647,45117,33892,21951,32764,34928,36395,36394,35848, 35360,33369,41909,35848,35360,37057,16436,34197,35847,37057,43788,5911, 34196,35360,36394,35847,21528,24672,5122, 8866, 16498,37057,38361,40129,36394,15338,45117, 10432,37056,33891,8615, 38873,45116,37840,41191,44594,44166,42550,32763,38391,44594,44165,45116,41662,38745,39257,32952,30800,34928,41363,13599,37056,33891,22910, 39133,45116,28712,42370,35847,43464,44594,43788,44165,39489,42049,27074,13637,26349,39378,41030,44594,42204,42960,39364,3698, 1693, 1549, 39504,44165,41107,44593, 39547,39504,39114,43787,32762,7911, 39924,42049,34196,33891,36394,34196,31391,36393,20808,6366, 43464,33609,37056,27073,36393,42049,5355, 23496,16260,14613,44593, 39722,29646,14995,40098,38285,42049,40888,32062,41909,39546,35847,19950,35846,34196,5693, 44593,5524, 10972,44593,36393,5061, 38210,40549,6623, 40401,43464,32061, 39744,41909,44592,39071,44165,11726,22573,41774,7022, 35846,40649,34195,42960,10891,17145,33609,30799,40502,45116,11725,22259,43202,17896,36393,11883,42370,37056, 36392,37697,34195,44164,5694, 33368,37804,40274,39445,39591,41555,40773,41191,37819,32060,43464,33608,43202,38621,37759,10409,37055,36392,20579,23495,33608,21035, 19361,14253,13540,15507,41029,29645,42960,42370,38360,38096,38370,43202,38888,33155,38888,35846,9790, 19200,42749,10554,4212, 10851,37705,40960,37805,39877,41191, 41453,38136,37718,20578,38130,41277,38110,42550,21950,32952,15946,38988,41909,12767,34195,44592,36392,33368,16350,32952,43463,21684,22401,24671,35360,45115,3000, 35846,42960,33154,34195,19280,23494,10584,11301,10324,41555,44592,9890, 8368, 35359,32952,10052,15050,36392,43202,11300,34194,34928,34194,40129,43463,10821,8890, 10495,9286, 45115,9050, 33154,10776,18359,8608, 10455,10063,8690, 9998, 35359,10890,36391,34534,10431,25734,34194,11197,8932, 8625, 8216, 41555,5808, 37055,40704, 5806, 41276,39665,39977,27072,42204,41107,9008, 36391,22400,14169,9272, 33608,15021,34533,32059,42959,11882,11299,11097,33891,9752, 34194,951, 9855, 34193,34928, 43787,6506, 44592,40887,40960,39430,45115,44591,2678, 6021, 19595,34193,45115,36391,8882, 30146,9788, 35359,34193,8916, 42959,10087,38326,44591,40200,1230, 43201, 16595,42550,39108,21286,34927,23927,10381,37055,44591,22572,44164,24670,12518,36391,34927,41106,27446,45114,26040,37055,34193,35845,26039,45114,34927,35359,5913, 44164,40200,41276,8376, 6624, 44591,22571,34533,44590,37849,42550,38930,44164,38432,42959,13033,37054,39445,42959,17848,26038,15692,29644,35845,38929,37054,20691, 35845,40098,42958,41363,24410,45114,8343, 42048,10205,42958,34533,18198,39877,21285,22258,41555,44590,34927,28269,20134,37054,45114,34926,19594,17802,42958,1683, 32058,26709,11653,911, 41908,39786,40358,39328,1370, 727, 11196,22257,43787,25196,23493,30798,41029,41908,42048,27445,40549,42048,33368,19859,4485, 36390,37054, 16293,11195,42749,26348,17033,43201,34926,33890,26708,9974, 35845,18923,45113,24930,36390,34192,37053,3951, 35358,18528,45113,40240,25449,15036,21683,28268,9162, 1178, 42549,33154,37053,43463,25733,8580, 5212, 10850,11298,35358,11011,38576,20133,45113,43463,41276,34926,41453,42549,31390,33890,33890,23926,35844,37944,45113, 38916,40036,43462,41554,38507,43462,39648,2053, 35358,12271,30145,40960,28267,9334, 25448,23301,44590,43787,16746,34533,42549,44163,44163,35844,42958,34926,713, 39924,39354,12890,11238,42048,37053,24409,28266,21682,37053,32951,36390,33608,37996,25732,22728,40837,42957,39877,26707,44590,11881,36390,716, 33607,34925,44163, 36389,14556,18588,34925,34192,27444,18197,40502,33368,32951,42204,35844,9220, 9585, 9742, 40773,33154,11724,26037,10351,9620, 45112,44163,38651,38418,45112,44162, 43786,45112,42369,41774,3726, 42203,39902,35844,33890,33153,11965,39048,2870, 11880,37052,1229, 3140, 6507, 4213, 4608, 3493, 43201,42749,37935,1159, 39488,38426, 39057,36389,38250,41662,28265,42957,34925,33607,34532,32761,13042,15192,38321,41190,36389,35843,37842,40036,40649,29643,41190,42957,42203,38601,44589,41106,41276, 36389,38065,34192,42369,43462,38502,38197,42549,42749,30144,38873,40773,4668, 38781,18647,42957,44589,30797,34192,34532,35843,33367,23925,2952, 38741,42047,39766, 38220,40240,41029,42956,2193, 38795,2974, 1139, 42369,41908,41190,38409,42548,34532,45112,7364, 41190,17418,45111,7576, 43462,3295, 3622, 20231,16702,18646,40003, 28711,25731,21412,23099,14337,35358,37052,43201,27841,665, 9660, 2303, 42369,38410,38611,5362, 374, 4551, 40837,39064,44162,23924,26036,37052,15495,44589,20033, 37984,41453,20230,34925,43200,23098,41662,11802,32951,35357,37052,37051,34191,34532,33607,35357,37051,35357,35843,11652,27840,37051,17608,15530,15788,37051,33367, 34191,3181, 33367,35357,41774,45111,5463, 45111,39306,41189,42047,43200,44589,43461,43200,42748,35843,44162,44588,43200,3958, 38281,43461,41029,43461,44162,44588, 42956,39701,30143,39765,44588,42956,41774,38016,42548,45111,44161,33889,29165,19679,37694,43199,35842,6035, 45110,36388,42748,18527,15218,21949,38615,37749,22256, 35842,17607,37910,43461,39876,11651,33153,37050,10820,11650,36388,26347,36388,1749, 45110,4861, 33153,38410,41773,41189,45110,38444,41189,40772,40601,42203,1558, 34924,11506,45110,3583, 13204,24669,23097,32951,14007,20032,39630,41453,39014,38258,3663, 43786,39133,39154,41106,38767,38813,2499, 42047,9399, 41773,42748,30796, 39806,39701,44161,37050,37050,34191,4613, 38297,40837,40549,39164,38719,40401,40240,44588,44161,44587,39013,42956,32760,35842,11723,44587,24929,43460,1276, 25195, 43460,37050,10031,15073,30795,36388,4862, 39647,15548,43199,10526,10454,134, 4933, 20229,31389,17752,37049,41028,18430,40960,42748,34924,39924,38267,38326,41275, 35842,38412,45109,41661,41908,39214,42955,40601,42955,41028,45109,10062,8002, 11879,37049,36387,44161,37049,23923,45109,1509, 9206, 36387,19199,30794,45109,10744, 3336, 36387,7528, 8195, 45108,9518, 11361,9172, 9513, 41773,31388,23300,8561, 11722,11360,11096,42747,33153,38088,3335, 41452,4343, 39630,39765,43786,40959,40549, 42747,44160,4934, 39876,38105,34531,37049,35356,22255,42203,9082, 38866,36387,44160,40453,6891, 7407, 35356,24668,2842, 16790,42368,35356,33152,7548, 7410, 10380, 43199,8346, 43460,37836,42548,38125,8838, 33889,5534, 45108,37981,44587,38249,41189,41907,38872,15921,11721,26706,34531,40313,40002,38442,27839,2302, 39830,38888, 38854,41106,40401,40648,41907,7198, 44587,5533, 45108,18922,34924,30142,18094,35841,34191,10707,37048,21166,34531,33607,37972,8978, 9205, 18787,43786,33606,33367, 33152,34, 761, 5626, 400, 2625, 4739, 16349,45108,40098,33152,38362,280, 2792, 19506,5130, 42202,41028,42747,39364,42368,38153,39164,44160,45107,43785,38243, 272, 2734, 6527, 26035,39744,3705, 37970,730, 40035,41105,40887,19505,34190,42047,44160,45107,28710,35356,26346,16564,33606,4931, 29642,29641,35355,36386,37048, 25730,35355,29164,22909,27071,6381, 33152,42955,44586,714, 1780, 24408,37048,37048,39099,39805,40313,34924,44586,43785,34190,42202,40772,33151,44586,43460,20343, 34923,13304,34531,31387,7833, 7659, 42955,36386,34923,35841,18429,864, 35355,29163,36386,23711,10252,37047,33889,15828,33606,37047,37710,2926, 36386,41661,7933, 37704,39181,38608,40129,38608,43199,37716,38041,43459,44159,1704, 37047,10553,43785,13877,17488,30141,42548,37685,38145,37934,37690,44159,44586,29640,15568,36385, 42202,40502,43785,33606,39744,42046,43784,18428,13481,37047,35841,9406, 34190,38272,43198,40501,38647,42046,40452,42202,35355,5462, 35354,24407,2358, 2172, 4223, 4444, 9423, 36385,23492,3143, 5220, 4052, 3093, 38283,33366,3269, 5219, 38821,44159,1684, 3003, 44159,35841,36385,35354,37046,37723,38643,16701,16435,20577,1904, 1876, 44158,19949,8263, 34190,44585,37699,39851,201, 6748, 38432,43784,40648,37987,37697,7984, 19108,9171, 7284, 10849,43784,44158,41907,892, 42954,40098,40097, 38845,38077,41773,40164,43459,38291,42954,40273,38111,44585,37690,38588,38233,39457,37702,40548,42201,40400,38330,41028,40548,42368,8213, 38179,8683, 10350,11135, 15738,34923,15901,40002,40097,44585,40959,40703,366, 5627, 7780, 21165,33605,33366,16594,9997, 43784,37713,42747,43783,5822, 2382, 33889,39830,42954,42201,45107, 42368,45107,24928,5131, 5710, 6641, 34189,32950,34923,21948,4342, 37691,37947,37814,37714,40959,37851,11095,44585,34530,16563,29162,17563,32950,32950,22106,14486, 14465,38350,35354,44584,41772,40273,45106,36385,27070,38304,15810,20031,17661,36384,30793,33888,33888,29161,5709, 36384,17847,22908,38996,41661,39630,6037, 14509, 34922,22570,41554,38187,41105,37701,44158,38386,41452,40240,37707,41105,39196,37690,40002,41554,42046,38414,37734,37798,42367,39224,42547,45106,42046,42547,37904, 41027,39317,38471,2599, 40129,40703,41275,37886,31386,37686,42954,38516,43198,4291, 37046,5929, 32759,42045,32057,37046,25447,35354,16789,17660,25446,33605,7151, 44584,1630, 11801,9830, 10525,13084,19504,35353,23922,37046,33151,34922,44158,16992,35840,11971,42746,21681,15494,13213,29639,41554,34189,1858, 1557, 1650, 41027, 10971,42953,43783,42367,33888,41188,33888,33605,35353,23299,29160,44157,37045,32950,33366,14336,35353,20466,34922,34530,15737,33605,34189,33604,13419,42201,26705, 36384,38963,44157,45106,15419,26345,40548,23921,36384,12277,43459,38816,45106,39071,16054,13291,42953,21947,17109,35353,37045,15162,37716,38355,37770,37705,38480, 44157,43459,39722,38007,43783,37848,38257,41452,27838,24927,34189,15649,14118,42746,39041,19948,40501,42547,38020,38250,38060,38131,39805,38296,20030,37733,40273, 44157,41452,1999, 40703,24926,26704,10135,27069,39564,37957,44584,18992,45105,38714,38113,6529, 39629,40548,40002,42201,40547,1591, 43198,45105,39142,40547,20342, 45105,33604,45105,36383,38920,32949,33887,44156,33151,35840,22399,34530,39744,37717,38603,44584,37812,41553,44583,17295,44583,40547,37998,38085,38611,40452,37045, 27068,6890, 43458,42746,45104,36383,44583,44583,43783,44582,10204,41451,36383,39172,45104,1275, 39430,33887,34530,2325, 42746,2874, 43198,1830, 33604,40097,442, 6745, 41027,35840,60, 1703, 3294, 42367,42367,42953,42045,45104,41362,35352,17562,41105,21527,37045,7670, 32056,16815,35352,34529,37044,37044,38204,42200,41188, 13818,38737,44582,34188,9584, 33887,44582,41553,30140,172, 37044,35352,44156,40772,42045,8854, 41772,1196, 9385, 10583,26034,1373, 9295, 32949,30139,15765,24667, 23491,32949,14646,2735, 39564,41188,45104,42745,37044,35840,4090, 6147, 15104,33366,20930,34188,38689,39032,40837,37996,43782,44156,37992,38872,756, 41451,3812, 40836,5001, 3623, 35839,32949,40887,6148, 41027,33887,41451,44156,24666,38957,45103,41907,41275,28709,13115,39532,42745,38096,33604,24406,26344,33886,33886,25729, 22398,11577,29159,37043,42045,11134,32758,24925,14885,34188,9672, 38623,10408,36383,34529,28264,30792,29638,11427,6528, 11050,9271, 9587, 8251, 40959,3730, 36382, 33365,10073,36382,29158,147, 687, 5628, 3461, 6746, 6255, 90, 3810, 35352,30, 3180, 4176, 23920,18254,859, 39948,42953,42366,44155,39305,45103,40239,44155, 43782,2522, 44155,21680,35351,27067,31385,14737,11359,41104,34529,11576,33603,39977,40601,24665,19279,37828,37043,15593,14820,19775,13615,9153, 28708,42745,18144, 28263,12648,26703,18253,26343,27443,25194,44155,25193,39683,43197,43458,14970,7305, 39785,891, 17659,30791,44582,33603,41553,37692,38381,39876,40097,41275,41104, 4674, 38929,40648,37770,42952,39902,45103,45103,40648,11649,45102,38075,40163,44154,23490,28707,15669,42745,24170,16159,35839,44581,39223,41188,20690,13335,18000, 44154,7556, 38313,42952,33365,7168, 40501,43197,33886,37043,27066,20341,6036, 8233, 41187,29157,26033,18303,25728,16073,32055,21411,32948,20340,36382,43458,24169, 21410,33603,43782,1186, 40772,36382,30790,17895,33365,45102,19278,40600,41187,36381,39851,11094,42744,748, 4132, 42744,43782,42200,26032,25445,35351,32948,33365, 1998, 33603,28706,36381,17658,21409,33602,36381,22254,42744,41274,13727,33886,35839,42952,1215, 38067,36381,42044,44154,26342,38903,34188,31384,3534, 44581,20576, 29637,33151,39722,44154,40200,28705,29156,24405,41906,38454,35351,41274,40703,42744,13038,41661,14299,44581,33602,38545,37831,42743,38970,40452,33885,40400,33602, 27442,35839,11878,32757,34922,32756,33602,32948,14555,34921,11800,9049, 30138,32054,35838,38614,40035,42952,4051, 42951,633, 3365, 1075, 27065,5070, 5291, 38334, 41553,1672, 40035,4614, 34529,5361, 237, 27441,2765, 35351,41026,19360,2223, 1298, 8045, 37043,45102,42200,44581,32053,34528,33885,6747, 45102,4177, 41552,45101, 33364,38007,40400,4003, 43458,45101,40600,41772,41772,38910,19678,34187,26341,17417,11010,31383,7974, 36380,11505,13455,24168,34528,33601,45101,44580,7559, 39610, 6640, 35350,11799,28704,23298,40452,7916, 34528,24664,37740,38242,6893, 7227, 39108,45101,44580,40702,42951,45100,39923,33601,37042,40200,42044,3811, 35838,42743, 39876,29636,33150,34921,38379,32755,36380,4675, 36380,32052,37042,40313,45100,41771,45100,37720,40836,45100,11194,45099,41451,38157,39503,44580,43197,39267,31382, 30137,23096,42951,39013,43781,33150,37042,38737,34921,28262,19107,21679,13027,37708,34528,33601,14802,35350,17524,14857,42743,39503,40702,38772,40547,20929,35838, 35838,15103,2359, 39093,43197,45099,45099,18526,19503,27440,34527,37042,26031,27439,16072,37041,20928,14464,43781,36380,17751,34187,35350,17750,42547,7290, 39377, 28703,4550, 33885,37041,21946,43457,7522, 9279, 29155,30136,29154,30135,27438,33601,34527,35837,33600,21408,1952, 39327,39063,27437,20575,37041,42743,41026,251, 27436,28702,41274,34527,9689, 30134,34187,20339,32948,43781,10927,33600,10116,10889,25727,35837,33885,22397,14210,13076,35350,42366,7555, 43196,45099,7297, 40771, 2251, 2406, 11193,10552,37756,38614,44580,13239,25444,17216,33600,44579,29153,1238, 11093,40546,10632,34921,23710,38300,42951,33600,18143,40273,11648,8065, 1214, 42366,34187,44579,38106,39851,8427, 8755, 39377,44579,10407,37730,38323,34186,40400,11575,11877,9346, 45098,23489,37041,24924,43781,34920,35837,38434,44153,40163, 42044,6256, 7242, 38550,8349, 5221, 42200,42199,23709,5711, 37743,34186,28701,23488,37040,1781, 4489, 16700,35349,17032,2953, 38519,35837,35836,16788,20228,17031, 4740, 2194, 5292, 26030,7035, 41362,45098,37040,39923,38442,41187,23708,38899,42950,41906,43457,23919,39721,41771,42742,4222, 42199,33150,39948,2791, 43457,44153, 34920,40399,6892, 2032, 1869, 27435,41660,34920,44579,21678,309, 29635,4738, 2498, 34920,38562,41906,40451,39851,44153,34919,39629,23297,40958,1782, 31381,19677, 42199,44153,24923,38633,44578,41906,7036, 44152,11720,42366,21407,34527,22727,27837,34526,38753,39875,40771,32051,41771,37040,38498,45098,34526,28261,34186,28700, 17215,32947,31380,9084, 4860, 27836,23918,11574,35349,45098,43196,33150,4932, 35349,35349,24167,39293,41187,40501,34526,38275,1085, 35348,37040,34919,37039,15153, 33884,1160, 37039,4802, 44152,42365,25726,42199,32947,36379,39901,20132,35348,36379,33884,15458,32050,44152,12020,16891,13825,11237,42742,11297,32947,33149,41274, 41552,33884,45097,44152,44578,41186,45097,32049,34526,34919,38231,38733,42365,38781,42742,7936, 39701,45097,16239,21034,9394, 11426,1231, 10848,33364,38303,38701, 4415, 41771,44578,39180,37039,27064,34186,34185,37942,41186,45097,45096,44578,37801,44577,36379,40546,42546,13853,41905,32754,1690, 7104, 3547, 40239,15736,1133, 4703, 33884,37810,44577,39743,42546,40600,38069,38767,23487,25192,43780,38238,22396,33149,39092,34525,37849,41905,38929,39609,45096,44577,42546,34525,40399,34185, 44151,40647,26029,25725,38118,41905,44577,32947,40600,41770,39305,39354,45096,38920,42546,43457,39829,27063,28699,36379,23296,17416,41362,11573,13511,37039,32946, 10706,38211,38109,18645,40313,38288,40451,22569,38509,38381,41660,3756, 20574,41362,16259,23095,22568,37758,39164,16348,18196,15827,42044,41104,44576,18474,15035, 21033,12296,42742,19676,26702,41273,23486,32753,35348,34919,38804,42741,41186,41026,40163,44151,9619, 29152,42043,40035,209, 38404,34918,27434,34185,44151,33364, 32752,33364,38238,45096,38946,10051,4834, 39163,8416, 43196,45095,43456,34525,8532, 41552,8659, 40500,37906,38672,2434, 44576,38396,38299,41450,40887,1624, 42950, 8249, 44151,7109, 6095, 35348,36378,38041,39040,40546,43196,2890, 43456,40239,44576,38866,38288,38534,40599,39389,33599,45095,36378,1737, 42198,43780,40163,34185, 21945,41104,38330,35347,35836,38354,40034,14039,38309,45095,38696,2751, 40399,40162,37979,41905,29634,38298,40451,44150,3230, 43456,45095,38098,16402,44576,1402, 43195,45094,41026,40034,38941,40771,40096,40358,33599,30789,19359,41552,19858,14412,36378,38444,43456,5098, 41551,44150,41660,42741,45094,12380,44575,37038,1880, 37038,35347,10705,33149,43455,3472, 45094,44150,41273,40162,41361,1956, 39683,40958,790, 44575,45094,42741,43780,45093,40162,39805,2720, 38277,40886,38570,39429, 41186,40546,39765,40545,43455,45093,45093,40451,42950,39609,40001,42545,44150,42545,45093,42741,4361, 38977,42950,45092,39122,40272,41361,41103,39389,45092,41904, 43780,39099,42365,41273,40096,39445,41904,41660,37038,39403,42545,38454,45092,43455,42043,38741,39191,40450,40162,42740,42949,28698,40647,44575,42043,34918,16292, 38130,25724,43195,14927,38118,3054, 43455,41770,41103,39190,38058,42740,38919,44149,39590,39682,15326,44149,21821,37038,31379,38348,44149,38946,45092,39647,44575, 35347,37037,23094,33599,33599,35836,37037,909, 38951,2859, 43195,40771,38407,40770,43195,44574,39590,39048,33598,21032,38110,44149,28260,40450,43454,34525,7257, 4521, 42043,33883,34184,19675,43779,35347,21406,38790,44574,37037,35346,13672,33883,19043,40272,29151,40199,41770,29633,40128,27433,41361,40958,42545,42740,15418, 14835,29632,11049,33883,40001,29150,2777, 41273,41185,39875,39765,37925,41272,38035,42544,43779,42740,38187,38107,43194,39293,38646,39609,34524,5323, 1472, 34918, 33883,37933,42739,16787,5574, 43194,43454,41450,34524,11798,38233,29631,4522, 35346,37037,34524,25443,37036,24166,35346,26340,34918,13636,37036,3933, 42949,35346, 42198,33149,33598,28259,32751,15120,40599,45091,28258,39875,33363,38892,40770,33882,43779,43779,43194,42949,27835,9492, 39743,45091,40770,40545,42198,40357,32946, 7580, 42365,31378,7750, 37036,24165,43194,33598,40450,9604, 36378,34917,40450,44574,44148,44148,43454,39056,35345,9573, 9811, 21164,37036,35345,35836,9156, 33363, 29630,12499,36377,28257,7426, 43778,23295,6961, 19198,7881, 39503,34524,32946,6962, 39682,43454,41450,37936,40399,39247,22253,3020, 41450,44148,45091,29629,35835, 34917,45091,5178, 33598,42364,39457,15394,36377,38987,44574,41361,45090,36377,35345,35345,43778,40836,41904,39279,33363,14983,44573,23093,8577, 40239,37943,38532, 34523,42042,30788,32048,42739,40770,43778,38182,44148,1754, 43778,2235, 37769,34184,38190,28697,43777,40500,43777,45090,44147,40958,17894,861, 39488,41770,41025, 21677,35344,43777,36377,40238,36376,42949,39340,32946,36376,13834,34523,39901,23294,41769,24922,44573,41360,42739,32047,8646, 35835,37035,44147,10970,42739,7910, 4152, 382, 5768, 42544,35835,42948,41904,25191,33882,34523,40312,35344,32945,37832,42948,43777,34523,38618,44573,5492, 41272,13376,43193,6688, 42364,3445, 30133, 32046,13256,37035,33363,38915,36376,3681, 33597,36376,40500,39293,34917,44147,38719,44573,33597,9717, 8654, 26701,9374, 7783, 40161,40647,37035,2133, 35344,27432, 37035,37034,843, 41360,43776,42364,29628,23707,22907,6960, 39805,45090,33597,37034,36375,41659,1144, 21031,37034,33362,37034,26028,37033,33597,24921,22105,35344, 41659,34522,34917,33148,1088, 25723,1946, 4466, 41551,41185,22726,42948,40199,28256,16118,27062,1110, 35835,22725,33882,39764,738, 5769, 40769,38636,33362,42198, 40034,38733,42544,5325, 44572,42738,35343,21163,23293,36375,19593,35834,29149,40238,35834,17561,16955,20927,13268,19774,42738,44572,42544,248, 43453,5258, 38780, 33148,40702,42948,38475,42364,39267,41551,33148,19857,17294,43193,31377,44147,7106, 45090,14104,6309, 14276,33882,20926,34522,43776,35834,16182,45089,28696,23292, 44572,21030,18991,20338,7275, 38903,34184,17846,6583, 43776,17176,35834,44146,45089,27061,38915,862, 41272,5097, 44146,797, 41769,45089,4579, 40599,678, 44572, 15668,26339,23291,10179,10159,8701, 37747,19674,6446, 33362,34184,2888, 27060,43776,17415,20925,42363,19673,21944,43775,41272,41025,14843,12539,10743,18093,34522, 33596,33881,27059,37956,7740, 42543,27431,24920,27058,37033,24663,19439,41360,880, 6092, 40886,37033,44146,30132,38257,1043, 33148,31376,14155,33881,18990,42947, 35833,17606,37756,38057,44146,41769,38466,4966, 27, 38758,39389,19856,37746,43193,39721,39665,39647,34522,32945,45089,38418,37717,37033,26338,42738,43193,41659, 17999,24164,18786,26337,42947,41903,33147,35833,24919,22724,43775,16619,33881,36375,31375,32945,35833,44571,2313, 36375,2937, 3975, 1219, 3838, 2371, 8090, 42197, 34916,41271,29627,14275,41185,38585,43775,41659,44571,37032,39154,42947,28695,42363,17704,33362,14736,39092,12077,42197,36374,41271,4465, 42543,34916,16318,26027, 36374,33596,1792, 43192,40886,36374,30787,44571,36374,32045,36373,21029,6814, 41449,43453,15152,30131,35833,33596,39503,37032,36373,11647,7107, 43775,32750,6094, 4779, 18092,43192,26700,25722,30130,33361,18473,28694,29148,35343,23290,16434,39546,22723,35832,40199,42363,33881,45088,40769,35832,33880,26336,33147,33596,39564, 42543,38601,42738,41103,45088,45088,3410, 3103, 2462, 38367,39147,31374,39305,44145,23917,34521,42363,42947,40647,35343,41903,45088,29147,35832,38424,44145,17893, 39108,27057,34183,18427,24918,37032,3886, 36373,19277,41449,33880,42946,34521,42042,26026,23706,44145,44571,918, 2165, 6690, 40769,44570,33880,36373,35832,27834, 33361,23705,33880,16890,39629,42543,45087,21405,37032,37031,39027,18426,44570,14735,42197,6812, 36372,629, 42737,1763, 39590,35831,39785,44570,36372,41103,14834, 28693,43192,15862,36372,29626,39721,37755,45087,16376,40836,33595,33361,18041,15234,43192,43774,37809,11997,44145,45087,35831,15191,42197,37950,4463, 41903,23092, 33879,33879,41551,40312,41449,43453,40272,30786,37912,35343,26699,41449,14176,41769,31373,30129,42542,37031,34521,206, 7108, 42042,6581, 33361,33879,32749,32044, 42737,39032,44570,4416, 40312,42542,44569,25442,44569,21526,36372,42946,33360,7105, 44144,36371,45087,21162,22906,29625,33879,38935,45086,41658,39231,43191,44569, 30128,19042,35831,43774,13016,34916,42196,33595,6202, 42542,42946,10609,40312,41360,35342,40311,10203,11192,10888,41903,4895, 42196,41902,41768,37031,37031,24917, 19502,29624,42042,20465,38995,42542,42737,41768,787, 44569,41359,39875,29623,37972,39388,40272,37817,40449,31372,37920,45086,45086,34183,28692,39874,18587,25441, 4520, 41768,9058, 2914, 20573,38365,36371,29622,34916,41025,45086,28691,45085,5324, 41271,3275, 45085,38160,40599,41448,45085,37030,33147,38337,2218, 1486, 38957, 40069,42946,39488,39901,45085,20689,45084,40957,42362,43774,18848,41550,41025,34521,35831,35830,20227,18472,29621,42737,43774,35830,9443, 19592,36371,43773,35342, 36371,6096, 24404,10406,28690,37030,39339,11572,41448,43773,42196,7330, 41550,2460, 25190,35830,37030,34520,36370,42945,26698,17452,43191,15307,18525,11797,34915, 35830,37915,40069,38562,40069,45084,42541,2461, 38324,39850,39457,43773,41902,9422, 40886,6308, 43773,21404,7960, 7980, 35342,42541,7994, 7366, 17487,34183,36370, 7626, 5988, 36370,37030,19591,5577, 5412, 44144,34915,30127,43191,34915,22252,40835,42945,37029,30785,6689, 24163,36370,35342,36369,36369,45084,42541,24662,26025, 6584, 45084,45083,41550,44144,40702,40885,38002,39026,40957,43453,44568,30126,27833,37029,29620,45083,29619,29146,40957,45083,41902,43772,11133,34183,40128,42196, 32043,18644,37029,33878,32748,22567,19276,33878,37029,16535,34520,19590,32747,16140,27832,13801,11425,33595,39429,2582, 1183, 38757,39316,40598,3757, 39316,34520, 44568,33878,7710, 33595,21820,37028,35829,33878,33877,24916,41768,33877,5031, 41448,32945,45083,37028,32746,39353,42195,44568,34915,30125,4462, 45082,44144,37028, 25440,9314, 38646,42541,38935,41550,41549,42945,43191,45082,43452,45082,40128,42945,40449,40545,42540,42195,45082,39629,44568,41902,42195,40646,39901,39577,39388, 40646,39923,40701,39377,42736,42362,40835,43190,38052,39019,43452,43452,44567,39472,42944,41359,43772,41767,43452,44567,45081,41102,39764,42736,45081,3128, 39353, 40238,45081,42540,44143,44567,41359,43451,45081,44143,44567,37880,39519,41448,40001,44566,40357,45080,38082,39609,45080,40957,44566,43772,40069,41359,41549,41102, 38298,43190,43190,42195,45080,40646,42362,44143,41185,40096,42362,45080,45079,39874,40068,43190,42540,41271,45079,39721,42736,41447,41358,44566,37767,39664,45079, 41658,36369,38478,44143,43189,28689,44566,2889, 29618,38623,39363,45079,40598,43451,44565,43772,42361,42361,42736,42041,42735,38314,40096,15405,35341,44142,42194, 45078,44142,45078,40500,39339,42361,43771,40885,38273,42540,39488,44565,41767,43451,44142,43451,41658,44142,37720,38646,40956,38058,38260,41447,39092,38062,40161, 4025, 42539,44141,6582, 42041,6203, 39279,40068,38236,12390,39133,45078,45078,43771,42539,42194,42735,41270,44141,43771,44141,42735,45077,40598,38723,40835,39743, 39900,40598,39664,41767,42361,43189,40199,44141,42944,39923,43189,43189,39163,39563,44140,44140,43450,42944,39532,41658,41767,38473,40271,41657,41184,44565,44140, 38244,40769,44140,39279,40198,45077,42944,41657,41657,38910,39292,44139,41657,40701,45077,42539,43188,41024,40311,39133,41270,38457,37966,41024,40701,44139,42539, 42194,44139,34520,26335,33594,14027,44139,43450,42360,38935,44565,42943,44564,40068,38299,40499,39720,40701,42943,40068,41102,44138,45077,37028,858, 41447,44138, 39804,42360,38248,41549,44138,38892,45076,43450,41358,43771,45076,40311,44564,44564,45076,41901,44138,44137,43770,43450,42735,43770,41270,39279,43770,38191,45076, 40700,38396,41358,43770,38103,43188,39922,220, 45075,38408,39720,4778, 39327,42734,38108,44137,44564,41766,38170,44137,41549,43188,41548,20131,2831, 44563,42538, 41270,44137,40271,40067,43449,37803,45075,44136,38260,38809,39804,41024,39327,41447,41901,38977,39563,42734,42360,44563,41901,39647,45075,42360,40449,40357,43449, 44563,43188,42538,41766,41358,35341,39646,41446,38839,45075,44136,44563,41548,41548,40499,41656,43769,45074,40095,39327,40768,40198,41102,42041,42943,38711,41101, 40885,40768,40357,40545,43449,39147,40095,45074,43187,39720,40271,42538,45074,41184,42194,44562,42193,35341,36369,40311,43769,42193,42538,1923, 40310,39180,43449, 45074,24162,40161,39948,45073,41548,39977,41184,42359,45073,43448,34182,17523,42359,41269,42734,43769,44562,44562,44136,41184,41269,42041,45073,42193,44136,42943, 44135,44135,45073,40885,40095,40356,37027,36368,42359,40499,42359,36368,42942,41656,39947,39804,42193,44135,40646,39001,40161,43187,42192,44135,40884,43187,42192, 41101,40700,40398,31371,43448,43769,43768,45072,44562,44561,36368,41656,40095,39084,41269,33877,37741,41183,39092,43448,40645,45072,41024,42358,41101,43187,43768, 39874,41766,45072,43448,38199,40067,45072,41901,40835,39850,42040,43186,43447,41547,40884,44561,40597,45071,39900,41183,43768,42942,40544,42192,44561,42192,45071, 37027,43186,28255,33877,41547,40238,40356,41101,14508,39874,38716,40544,40884,42537,25189,34182,21676,33876,39977,39873,41269,44561,43186,21403,42734,43768,41183, 33360,39628,43447,43447,41268,42942,41766,33147,41900,42191,44560,6307, 37990,40499,38448,42942,43767,42733,39947,43186,43447,42191,42040,44134,40645,37787,39532, 38439,15135,43185,32944,38383,22722,42733,44560,37867,39720,44134,40160,33876,42537,37027,38872,13977,5578, 40956,42040,3932, 33594,35829,33876,40001,3974, 33594, 32944,13944,25721,35829,28254,35341,23091,22104,37027,14958,43767,27430,16317,33360,24403,24915,44560,34182,38821,41268,40956,38351,42358,39646,40498,41183,23090, 16922,14355,24914,26334,33594,34914,42941,45071,44134,42191,43767,44134,39107,37026,35340,33146,34914,37026,42040,32042,43767,44133,17998,33593,36368,34914,18921, 29617,34914,3546, 23704,33360,8741, 34182,44133,45071,1955, 44133,41357,3274, 44133,37745,38344,43185,38317,37026,27429,1411, 2216, 45070,41182,34519,38317,10847, 15900,44132,42358,40884,37863,5576, 2067, 2507, 26697,23485,16347,43766,5259, 43766,42941,37977,37884,45070,39267,10926,22721,41900,43766,42358,40160,34519,42537, 40198,44132,15269,40067,2121, 28253,35340,17845,21675,41547,39032,33146,42537,42733,44560,40498,3231, 44132,3973, 3160, 4027, 4026, 33359,6816, 30124,31370,35829, 5987, 45070,37026,33876,33875,32745,18142,42039,41547,39628,40544,43766,1513, 44559,42941,39976,23484,33593,17703,42941,44132,39646,45070,32744,37025,36367,17749, 37025,39107,42536,41900,45069,9313, 18847,34519,1452, 44131,4777, 42733,20572,35340,5493, 44559,489, 41357,44559,34913,35828,38970,39339,43185,35828,32944,14449, 35340,34913,34913,17341,33359,44559,39190,35339,8039, 42940,36367,32041,14485,8842, 36367,35828,23703,28252,36367,32743,10323,4414, 29145,32040,14366,34913,12263, 17293,34181,19589,20226,41546,37025,12798,11191,35339,28688,18040,15787,43765,7829, 9380, 12612,38304,39664,36366,16291,26333,12732,30784,21819,11236,8954, 45069, 37025,8290, 39873,15217,31369,19588,10631,32944,37024,39646,43446,24661,1922, 6815, 45069,38858,41268,43185,45069,41446,41900,29144,35339,35828,43184,39873,36366, 37024,3885, 32742,17257,32943,35827,32039,35827,13434,26696,20464,14734,20924,34519,43765,24402,15012,27056,1531, 41765,5863, 34518,35339,10158,35827,33359,18586, 33875,36366,22566,34518,24401,34518,32038,45068,32741,33359,41268,9463, 44558,11092,25720,38369,41182,38210,39873,38565,39487,17292,41765,28251,42732,42940,42940, 5413, 42357,395, 42940,6447, 38209,40160,7560, 42732,35338,34912,35338,23916,34181,36366,34912,26695,33146,36365,30783,21674,33146,20337,27428,34181,32943,32943, 37024,34912,35338,33593,25188,15809,17108,22251,7630, 30123,34181,19438,31368,43184,35827,4193, 43184,28687,10582,35338,43184,40160,42939,34518,44131,33145,35337, 35826,26694,32037,40834,34517,36365,22250,17560,35337,28250,32036,35337,34912,37024,24400,27055,37023,22905,22103,28686,35337,32943,34911,45068,24660,24161,32740, 36365,35826,33875,28685,35826,22904,29616,33145,32739,31367,33875,30782,16469,33358,33874,34517,34911,30122,30121,34911,33358,26693,32942,37023,36365,35336,32035, 36364,21028,36364,33593,35826,23483,32942,36364,37023,36364,34911,37023,37022,29615,37022,15920,35825,24160,26024,34910,37022,34910,17340,34517,33358,30781,32942, 34180,34910,14081,26332,34517,34910,34180,18643,23089,37022,31366,10299,36363,8335, 38561,6445, 11296,22102,34516,22101,31365,12820,31364,26692,37021,22395,37021, 4151, 25187,22100,1175, 37021,33358,35336,34909,35825,35825,20130,15493,32942,36363,14335,25439,37021,17748,36363,25186,35825,35824,11424,33874,34516,2217, 39377, 38910,42039,40645,10608,39247,33874,29614,20463,30780,37020,18709,37020,25185,36363,27427,32941,37020,35336,37020,34909,26023,15337,34909,13623,11571,43765,35824, 37019,20571,26691,11504,19106,10134,22565,33874,23702,30120,23289,17801,34516,34516,32034,35336,34515,29143,35335,15808,32033,16375,38499,39418,39257,30779,35824, 41546,22564,33873,36362,37019,34909,20462,26331,16562,22394,44131,36362,33873,5575, 20225,28684,34180,33873,34908,30119,34515,20688,34180,30118,33357,34515,27426, 33357,27425,29142,37019,34908,34515,33873,25438,15476,21284,37019,25437,20336,18846,36362,33872,30117,44131,43446,43765,44130,45068,28683,22099,30778,34514,34908, 42939,36362,33592,32032,13731,35824,37018,17382,12190,34908,25436,17144,33145,41100,40645,33145,44130,44130,41446,41023,42939,43764,42732,43446,38940,37018,38813, 37018,37018,22393,34179,33357,19041,32031,36361,21525,13411,32030,14150,42536,37017,44130,23482,33592,29613,34179,40034,29612,31363,37017,42732,26022,37017,33357, 34907,41023,43446,39203,45068,40398,43764,43445,41267,35823,33592,28682,11048,36361,33872,36361,11876,37017,41899,34907,31362,44558,20335,12145,23915,20570,19672, 30116,30777,21402,39132,28681,35823,41765,45067,43764,24159,39743,33356,45067,27054,12703,37016,12231,42039,29141,14733,38538,43445,32738,29611,4194, 26021,29140, 43445,33592,42731,30115,31361,12414,4464, 6093, 6813, 17747,15567,22903,34179,20569,27053,21027,23288,33591,35823,35823,38374,42357,34179,2290, 2094, 9203, 2988, 44558,34907,33872,43183,44558,44557,12282,41357,43183,38061,745, 38790,38924,39305,43764,45067,44557,41446,42191,3791, 3600, 34514,38426,39376,42731,37016,25435, 39056,34907,40597,16534,29139,42731,39418,38995,23914,24399,24913,36361,35822,13236,33872,17746,33144,33356,13796,29610,12557,34514,23701,12818,41765,9354, 37016, 34906,12575,9787, 35335,18302,44557,10704,8422, 45067,6444, 35822,45066,19855,35335,34178,24912,9741, 1311, 45066,45066,10349,15151,33871,37016,33871,30114,37015, 39214,42731,33356,39456,35822,40449,42730,37015,42357,15354,32029,44129,44557,9410, 42039,11646,27831,21524,29138,40128,45066,38630,40198,10405,35822,44129,9491, 35335,27424,33591,11570,33871,4262, 42730,30776,34906,32941,38946,44556,26330,44556,42939,40237,39326,28249,41267,22392,33871,42536,42938,44129,42730,37015,42357, 40498,13405,45065,3903, 43445,42536,32941,45065,32941,34514,39872,44129,9289, 27830,41899,44556,34906,33144,10846,8358, 42938,43183,27829,36360,41546,37015,39146, 42356,1886, 34513,42356,33870,26329,9222, 43183,25434,33144,10551,35821,23287,38617,10671,36360,15592,35334,36360,17997,30113,16814,18708,10670,29137,15945,34513, 38929,42356,33144,9643, 36360,45065,32737,33356,42190,40544,39304,43444,43763,34178,41023,2221, 3904, 9428, 10775,33870,10774,11875,33591,43444,42356,33591,31360, 16561,35334,33870,40033,2492, 42355,7696, 8493, 44556,36359,29609,9278, 21943,10178,11719,9214, 9964, 36359,9889, 7318, 44128,33355,34513,9829, 8299, 11874,11503, 34906,18785,23481,32736,21818,36359,39764,4976, 44128,42535,37014,39339,42038,41445,10298,38221,41267,33355,35821,35334,38780,33143,42535,43763,40498,42938,11235, 1403, 34178,37014,43444,34905,45065,22720,37702,6717, 32735,37014,36359,33355,23700,26690,34178,34513,37014,43763,23286,38940,36358,35821,40033,34512,14329,9239, 17892,39519,22902,42038,44555,42038,45064,45064,10819,39976,42730,40883,33870,41267,42038,37013,39519,42938,30775,42355,1422, 6348, 44128,37013,15020,42937,43763, 35821,25184,6849, 5194, 2241, 39700,2220, 39063,35334,30774,40768,34905,34905,33355,35333,29136,25719,32734,26689,38669,39278,40497,4916, 41266,41445,40448,44128, 42037,41182,29608,31359,45064,41899,33590,38225,33869,9854, 7585, 44555,43444,45064,28248,39872,38519,43443,43762,13579,37013,13058,14697,9512, 8341, 43182,8389, 27052,28247,33869,32028,41100,8510, 45063,41656,34905,36358,44127,38025,39114,41655,43182,38277,7224, 38643,6847, 34177,8257, 39487,40956,41764,11796,30112,24911, 41764,45063,830, 9183, 14507,25183,33143,25182,488, 44127,5595, 2920, 45063,38831,38497,41023,40768,43443,2645, 36358,34177,17800,26020,34512,15919,33590,33869, 35820,32027,4374, 44555,22249,35333,15336,36358,30111,33354,17256,23285,41655,33143,2814, 40094,38027,41182,5596, 39445,39444,41899,39682,4479, 32733,44555,33354, 33143,13032,41898,40356,39742,43443,33354,28246,35820,36357,30110,44127,41655,42190,20129,44554,37951,38858,185, 464, 3768, 9427, 40543,42190,39114,40356,34904, 40597,35820,33590,39180,27423,42355,12878,43443,32940,28245,33354,27422,34904,34904,32732,2465, 40834,41100,16158,28680,32026,39417,40955,19587,13466,26019,37013, 34177,18252,25718,32940,28244,35333,34177,34176,33869,34904,21673,31358,30773,12857,23913,35333,19773,36357,15306,12546,34176,35820,44127,15566,35819,19197,33868, 30772,42535,27828,36357,13111,26328,33868,34903,20568,30771,26688,44554,25433,8999, 28679,36357,9853, 35819,34903,7705, 44554,33142,40955,7254, 41357,4593, 33590, 10086,8132, 44554,41764,20923,44126,35819,37012,45063,41898,42190,39063,44553,40197,40094,33868,14274,36356,35819,17076,43182,35332,16660,11795,34176,10220,35332, 44126,42037,33589,12801,3856, 27421,20687,40644,29135,8383, 34512,39785,38946,41181,39563,38708,39947,6848, 38243,38321,40767,8425, 43762,9436, 41764,35818,10133, 3481, 41898,44126,312, 1101, 6008, 1678, 37012,34512,45062,6350, 40067,21283,35332,28678,32731,34511,43762,33868,11190,8819, 7400, 44126,8064, 934, 9150, 42355, 8176, 5513, 39278,39742,43762,42937,42037,3167, 39078,42937,39071,41356,44553,40767,29607,44553,38217,40597,2815, 44125,35332,42937,34511,36356,42189,26687,44553, 43761,31357,34903,44552,45062,40700,39326,543, 43442,41356,38608,37896,33142,38680,38878,44125,38255,38581,42729,38456,38915,35331,44125,20461,37012,39353,3611, 40271,43761,41898,42729,9716, 28243,38736,15011,36356,43182,43761,34903,39056,39403,39353,35331,33589,27827,36356,30770,32730,26686,7754, 33142,43442,41181,34902, 32940,32025,34176,45062,35818,35331,9963, 44125,10379,27420,44124,35331,30769,42189,44124,24158,25181,22391,30768,8324, 9442, 32024,24659,44124,3988, 41445,10177, 10157,25432,6489, 42189,45062,4974, 44552,22901,24398,27826,42729,42936,6846, 35330,42037,3420, 34511,36355,33353,39546,39900,44552,43761,41897,44552,37012,38556, 27419,33589,18989,35330,36355,22563,35818,13087,35330,41897,27418,37011,29134,27825,37011,18988,42936,13622,42354,32940,34902,37854,44124,41546,38379,44551,38861, 19772,44123,16181,39223,33867,44551,30767,32939,34511,28677,37011,42189,39519,41897,5273, 43760,4480, 35818,45061,43760,41897,33353,16699,26685,23912,19947,33142, 3852, 42729,27417,35330,37011,2466, 4915, 40270,35817,34510,17891,35329,35817,35329,35817,16786,6349, 43760,40955,41181,31356,3112, 39682,39326,40237,38493,38888, 39563,39146,43442,2723, 16117,39196,41545,40197,8099, 33589,35817,11645,9067, 29606,36355,34902,21161,34510,9962, 32939,34175,275, 6718, 37010,41896,17175,35816, 25431,29605,42936,29604,30109,45061,34175,36355,27051,45061,36354,6994, 34902,44551,45061,33141,2107, 26327,23480,25717,32729,36354,13221,29603,30108,23088,37989, 43760,38672,42535,38982,38507,28676,38963,44123,33867,39352,42728,45060,41655,44123,33867,43759,43442,39352,39742,42728,20224,26684,45060,34901,38315,39146,282, 4977, 1499, 18784,26683,30766,34510,21817,29602,20922,40497,26018,43759,2443, 37010,15179,36354,36354,35329,1813, 40066,35329,35816,40497,38194,41022,39976,2586, 34510,35328,18585,36353,35328,2587, 3258, 2948, 3987, 4432, 33867,42534,6490, 5339, 44551,42534,42728,33141,33353,40596,42534,44550,43759,33353,34509,34175,32728, 24397,16374,40834,42354,18524,39429,44123,39163,42936,43759,27824,13181,33866,36353,23479,16813,36353,24157,44550,12196,42354,35328,37010,34901,35816,42354,25180, 36353,31355,18091,35816,25179,30765,33352,42935,14565,32023,24910,4591, 39278,28242,43441,27050,28241,3853, 43441,29601,29600,36352,39292,19275,18141,37010,33866, 26326,34901,42534,14120,41763,34175,36352,34901,12115,22562,38711,13955,40700,18920,35328,21401,35327,14479,34174,32727,35327,34509,34509,34174,36352,33588,30764, 34509,6995, 36352,33866,44122,10102,33588,22900,35815,35327,42188,34900,42533,40644,32726,36351,30763,21523,16854,36351,39190,44122,37706,39785,44550,13706,38056, 38390,20807,17414,35327,38915,37773,44122,36351,38281,43758,39608,40355,16290,45060,41022,42036,43758,45060,35326,37838,13928,35326,24909,34174,35326,21942,43758, 34174,38505,44550,41545,38429,36351,37009,35815,34508,29599,34508,27823,35326,24658,29598,26325,19437,34900,19671,36350,19436,35815,32939,12888,30107,36350,36350, 18090,29597,7955, 4275, 35815,16954,28675,33141,34508,21672,33866,29133,13575,14309,26682,20223,26324,33865,16745,30762,36350,37009,37769,32022,37878,34173,37009, 11979,40497,41545,32939,39976,39099,37873,37009,28674,41356,23087,24657,34900,38124,21816,43441,12445,43758,23284,40883,12056,41445,22390,32725,45059,42935,15691, 35814,12641,38164,43757,40496,18642,2052, 22899,16238,14594,32021,30106,14054,26681,42353,39872,40000,39700,12568,8121, 42533,5681, 3483, 21815,1514, 41444,29132, 33352,29131,22248,2155, 20806,12249,41654,29596,34508,15547,34900,38872,43441,7182, 40596,37008,34507,12198,36349,35325,33588,24656,42353,16497,34507,36349,19586, 35325,35814,12299,45059,10818,28240,32724,16889,41444,43440,40355,32020,16316,33141,33140,34173,37008,37008,15632,36349,42935,21941,11423,22247,36349,37008,39267, 8228, 44549,38883,39190,4278, 38649,41356,35325,36348,33865,10925,11873,12842,36348,34173,40448,43757,43181,9823, 43757,32938,523, 42353,2108, 34507,24396,37742, 5434, 41545,27822,21671,23699,37690,40596,39304,37715,42533,38005,36348,43181,42188,37741,32723,4913, 36348,44122,42935,5792, 40834,37007,35814,45059,23911,1245, 12019,43757,19274,14059,41763,33865,20128,23910,20805,34899,38621,43440,28673,12426,3325, 24156,35325,38294,41181,33588,32019,34173,2539, 23698,39784,42934,27821, 14801,44121,22719,19040,5895, 40699,40955,42934,17174,24155,27416,25430,966, 44121,38711,2517, 37920,33865,20222,35814,39266,39214,42188,38977,43440,40833,39141, 38038,36347,34899,22718,33864,38147,45059,34172,33864,12267,23909,38000,39829,20567,16315,45058,40699,41180,39363,20566,38186,42353,39664,41444,36347,12085,42352, 17030,22246,31354,17339,252, 4125, 1867, 36347,38914,40699,29595,45058,40159,37007,25716,2341, 42728,38461,44549,40543,16888,41896,36347,3767, 37007,35813,35813, 30761,38092,37007,20804,43440,13456,42727,38483,20334,40767,38701,3721, 12041,41896,32722,43181,40954,44549,44549,35813,32018,34172,25178,27820,42727,34172,32721, 34172,29594,41654,37723,45058,25429,42036,42934,24395,45058,35324,2813, 17947,37006,33352,37006,36346,14969,37006,2518, 37917,43756,38267,42727,41444,44121,41896, 38081,35813,34507,16373,37763,42727,41544,38078,2051, 43439,40954,38780,42533,39608,37826,43756,23697,37797,39026,3284, 42532,4785, 24394,45057,41180,5437, 40197, 27819,42532,39719,39700,18707,40094,1398, 14080,40398,43439,41895,12339,6228, 33140,42532,42726,4716, 22245,25177,12117,34171,36346,34171,27415,24908,37006,30760, 12024,41763,13787,29130,14593,18251,32938,33140,33864,12220,44548,44121,33140,33864,14290,33863,43181,11295,38283,39922,41180,44548,43756,42934,38623,45057,38686, 6851, 42352,35324,32720,41266,44548,41022,26323,42933,41895,28672,33863,32938,4658, 19358,45057,8098, 16433,33863,20921,7213, 40644,4536, 35812,32017,37910,44548, 27414,40644,42036,37824,37719,37698,42036,37737,34171,38057,40310,40398,42188,39829,40767,33139,21026,42933,38573,39562,33352,4592, 42187,38780,44547,44120,38060, 44547,41654,38105,37843,36346,44120,39829,37846,38471,39444,31353,620, 42726,44547,29593,38083,40833,25715,32938,17890,42187,14872,31352,23908,29129,15019,33139, 13808,34899,16698,16401,22244,39828,12132,44120,37005,43439,12074,12918,40766,44547,6229, 12248,2493, 1812, 45057,23478,44120,39153,41654,12314,43180,22243,34171, 30759,39146,33863,32719,31351,42187,37772,39257,38601,21160,15713,35324,28671,25714,32718,33862,39292,41355,43756,21814,41763,44546,40496,5435, 38379,43755,41100, 44119,34899,801, 5597, 19771,43439,25176,32016,40496,43755,36346,37783,38957,43438,37915,42532,38820,25713,17799,39141,12581,35812,35812,14478,6993, 43180,42726, 42726,39214,39681,45056,41022,37005,18783,25712,44546,6992, 1588, 37005,21159,43438,33351,31350,35324,27818,34506,16744,34506,36345,33351,37005,24393,24907,26322, 35812,34506,27049,37004,36345,34898,37004,35811,33587,10817,33587,21670,32717,30105,36345,34506,33862,30758,29592,29591,33139,33139,34170,28239,19105,35811,16812, 35323,19770,42531,17214,33862,38767,40066,39417,39531,38065,45056,42933,44546,34505,39742,36345,25175,37004,36344,34170,34505,20920,36344,16743,17381,34170,16289, 37004,35811,30757,16468,23696,15861,20333,14112,32015,34505,34505,33138,23283,28238,34170,33862,36344,34898,28237,25711,34169,33587,37003,14354,24906,18845,35811, 13400,37809,40954,39577,39223,43180,40448,39266,44546,44119,41099,29590,34898,15591,20127,31349,17702,28670,30104,35810,33351,18301,36344,25174,26321,32014,37003, 26320,39975,18641,24905,12538,35810,34504,24904,37003,28236,19854,26017,26319,26680,19273,26679,35810,37003,16618,12172,44545,27817,125, 1421, 39947,42352,39417, 32716,43180,30103,2865, 34504,42187,7737, 24154,38643,37002,23282,25710,15735,37688,34898,26678,29589,35323,13767,39804,44545,25428,37002,6226, 17291,25427,44545, 13994,32013,42352,29588,12073,37002,43755,45056,34169,35810,24903,12990,43755,30102,19039,17075,43754,17213,33587,33861,40496,32012,33861,33351,10924,39562,36343, 42725,34897,17451,34169,7420, 35809,8977, 11502,41544,23086,36343,42531,26677,20460,11358,30756,34897,14592,8256, 41895,4914, 32937,1355, 15807,29128,30755,35809, 33861,35809,35323,22717,33586,34897,11501,31348,33586,31347,35323,37731,33861,33350,35322,36343,14941,15440,20459,33350,44119,25426,14768,32937,35809,40699,6716, 5514, 37695,37789,1677, 40883,34504,37002,34504,30754,33860,37001,7983, 34503,30101,36343,37001,37001,3855, 35322,18640,34503,35808,31346,20803,14732,8087, 35808, 32715,37001,17212,33350,35808,37000,7306, 40270,21940,37000,23085,29127,17107,33586,34897,19946,41895,35808,37000,34896,35807,32937,16400,12059,42351,35322,20919, 20458,44545,39429,13496,18140,26676,30753,41653,36342,16659,25709,15590,27816,43438,19670,32011,34169,24902,17844,1271, 43179,24655,14527,21669,20802,1114, 27048, 35807,21158,37000,23907,26675,19945,36999,20801,35322,34503,14280,7859, 40448,17843,35807,16533,36999,45056,27047,32714,31345,22716,35807,36999,36342,510, 44119, 3083, 36999,25425,27046,21157,35321,23695,19853,45055,44118,17701,905, 44544,45055,4715, 41894,33860,34503,13841,26674,31344,45055,45055,18471,45054,25708,29126, 21522,23694,34168,36998,24153,34168,33586,5050, 38627,38804,44544,41355,42035,45054,42933,40596,40643,40397,39403,43179,44118,38987,13375,5598, 12978,43438,32937, 39900,44544,40766,28235,45054,40000,14749,44118,43754,34896,42932,37685,41266,37926,45054,38585,41266,38088,37701,39502,42351,44544,38808,42932,44118,36998,44543, 45053,105, 38504,43179,13736,44543,40094,45053,40397,41894,42932,38415,3257, 38800,41355,6347, 42932,5436, 4277, 45053,42035,45053,5794, 34896,35321,14154,45052, 13938,15233,34896,36342,40595,38143,43437,38376,42351,41355,36998,12147,36998,32936,36997,45052,41021,33350,43437,19944,1100, 34168,29125,44543,43437,44543,15529, 26318,27815,33860,33138,37692,40197,43437,38342,40698,37967,37700,38000,39063,37778,39472,38914,39122,45052,43754,42351,44542,45052,38054,34895,22561,43754,37865, 27814,36342,34168,16346,36997,21025,38349,43753,37728,42350,33349,39518,24152,32010,34502,21282,23693,40033,24901,37738,35806,40270,39518,42350,42186,43179,42186, 42725,39172,44117,34895,43436,38835,45051,39975,38120,42035,21813,5049, 38727,42931,39803,13970,32713,41653,40833,44542,41544,40833,36341,34502,18782,19769,40355, 41180,43178,4163, 1011, 42725,3454, 20332,25173,22560,34895,36341,36997,2491, 40543,38660,40643,45051,38559,45051,4276, 40159,40883,39338,17290,12423,43178,27813, 33860,26317,27045,28234,15072,36341,11968,40447,4537, 21668,35321,4975, 36997,35321,15404,20686,45051,31343,36341,25707,34167,12462,24151,25172,16345,14767,35806, 32936,18470,38861,40495,35806,31342,25424,16053,28233,30100,17074,44117,3166, 42035,35320,5894, 25706,19435,14912,21521,44542,2757, 45050,43436,2012, 43178,12766, 36996,17211,26673,35320,14149,24150,36340,24654,35320,12819,35320,36996,13635,14884,13587,27413,17073,15290,13677,27044,8501, 38883,34895,33859,34502,22242,29124, 14373,13545,13454,17338,16921,13495,30752,25423,35319,41544,35806,36996,19272,32009,27812,13699,14766,43753,35319,31341,42931,44542,36340,19669,26016,32936,14582, 39326,45050,45050,5795, 38248,38858,39402,37749,38976,37765,45050,44541,39899,24900,2556, 44541,37947,39040,37726,42350,42350,44117,40033,42531,38094,40196,37982, 44117,41653,39256,39975,44116,42349,43753,42034,38887,41179,41265,45049,42531,605, 44541,41653,39502,42186,43436,29587,17143,44541,37914,36996,44540,41443,44116, 44540,38065,40270,38523,41099,662, 44540,15806,45049,35805,35805,33859,34167,2125, 35319,21812,41652,6608, 44116,33859,36995,34502,42349,32008,32936,15826,34167, 828, 42349,36340,34894,33349,14463,21811,30099,34501,16052,36995,36340,34501,35319,30751,23477,19585,20565,14564,36995,24899,36339,32007,35805,38167,33349,41652, 20029,42034,45049,18919,42725,39663,43436,28232,3646, 34894,23281,34501,36995,19271,20028,36994,38614,42530,41021,40832,40832,30750,40954,21400,36339,34894,35805, 33585,23906,30749,23692,27811,28669,36339,30098,34894,43753,36339,36338,27043,34501,17413,36994,35318,17996,41099,41265,45049,32935,32006,34167,34893,17605,43752, 34893,21281,22389,42931,32712,33859,32005,36994,23691,33585,15353,36994,36993,36338,30748,34166,21520,21939,36993,36993,36993,37749,38199,2701, 2867, 34893,39899, 17798,33858,35318,41179,44540,32711,35318,23280,41443,12441,20800,6346, 44539,44539,35804,42931,42930,18523,45048,43752,36992,18089,22715,25171,33858,20126,40495, 29586,22559,34166,35804,34893,30097,35318,35804,14629,8020, 7495, 35317,16742,20331,34500,7445, 7807, 17657,45048,12402,27412,24392,25705,29585,37956,9010, 36992, 3522, 3523, 673, 559, 14536,45048,35804,12697,42930,29584,25422,39062,24149,39577,29583,2182, 45048,36992,28668,39645,39975,2297, 34166,43435,4375, 35803,19768, 6852, 33138,39071,4164, 21399,34500,34892,36338,14229,12478,17412,7779, 7517, 7627, 30747,26672,13845,35803,9157, 33585,35803,11872,35803,15648,20457,22098,21810, 18918,15667,21809,33585,17995,29582,26671,12398,13389,19584,17142,23279,12388,12511,32710,35317,34892,14353,37693,38280,8795, 36992,33138,20456,20125,7398, 23084, 20918,32004,19501,40355,28231,6227, 23278,34892,11718,10050,45047,27810,36991,33137,35317,28667,13817,11644,10607,30096,13964,33858,40766,42186,20124,34166,6123, 10430,7146, 40159,1525, 11958,42930,35802,12152,8131, 35802,36991,33137,35317,45047,7202, 5682, 10969,41179,15071,36991,27809,29581,42724,37877,13722,39223,40766, 12116,20221,34500,24391,19434,36338,23905,30095,34500,29123,29122,44116,11569,40882,13721,332, 39681,40595,41021,12963,28230,14790,7963, 42724,5438, 41179,34892, 8352, 34165,7772, 24653,32003,35802,23083,17994,15647,33137,24652,35802,22714,41762,11963,40495,6850, 15417,36337,45047,35316,45047,7895, 34499,21398,36991,21280, 31340,25170,45046,36990,42185,30094,15268,22388,42185,24148,20123,19196,35316,16467,39376,36990,40832,41021,34499,29580,22713,32002,34891,34499,28229,20455,21938, 30093,40447,17559,31339,15134,37961,41762,42724,43752,42034,34891,17558,44539,23904,36337,42724,43178,14298,30746,40127,36337,36337,43177,19357,21667,3854, 42530, 19195,36336,37877,41020,528, 4590, 43177,45046,42723,38854,39402,2866, 45046,3986, 44539,44538,34499,42185,9500, 37948,43752,26670,36990,21156,20564,30745,35801, 30744,33858,35801,35316,24390,44538,19038,34891,14581,8120, 7753, 21937,15393,13993,16432,15305,6345, 45046,34891,40698,35801,17289,33584,21519,28666,22241,36336, 15304,30743,41894,27411,3482, 3942, 16953,29579,38154,34890,33349,34890,8617, 44115,23903,39363,43435,23082,19270,34498,35801,32709,22097,42034,19194,27808,13388, 27042,35800,42530,43751,24651,13352,35316,40543,182, 44115,4973, 4040, 27410,8804, 41762,9484, 35800,43435,38179,23476,40269,41543,34165,9182, 44538,32935,38328, 27041,23475,35315,32001,34890,13650,39946,33584,22387,34890,14361,211, 43751,4124, 24650,152, 40595,2996, 6853, 35315,36336,12543,33857,33348,39663,34165,17797, 45045,34498,32935,32708,38331,38558,44538,44537,41099,36336,39803,36990,36989,38219,45045,38707,3943, 20220,33137,38763,43177,44115,35315,39562,36335,34889,20917, 33136,33584,15289,21518,45045,33136,39000,36335,1648, 12333,44115,30742,42930,31338,41762,4717, 43751,36335,31337,34889,39645,13201,28665,19269,41098,37755,34165, 27807,3356, 38508,38222,39376,43751,38525,34498,28664,44537,44537,35800,35315,36989,32707,18584,26316,36989,35800,15475,31336,36989,45045,34889,33857,33136,36335, 35799,34164,36334,32706,33857,37909,40159,35799,39040,15918,38097,36988,44114,40595,36334,28663,36334,37687,17946,39376,39741,42185,42184,39899,44537,42184,34164, 35314,41265,42530,26315,44114,27806,33348,32935,14428,13471,30741,40765,40066,39828,37978,35314,41354,37741,15786,40643,42929,34498,17993,41894,27040,19268,37932, 38951,38262,40698,36334,39278,12752,15439,41652,19356,29578,34497,38649,45044,35799,33857,34164,27039,36333,42929,34497,40765,18844,33856,27805,36988,32934,26669, 32934,36333,33584,39132,42184,36988,22096,30740,2494, 44536,23690,29121,33583,35314,128, 4162, 4481, 5793, 41178,35314,44536,34164,44536,33348,38757,39784,6867, 36988,45044,41354,33583,45044,32705,28662,43750,35799,36987,14334,35313,32000,34497,29577,36987,31335,29576,32704,44114,33856,35313,21936,17288,35798,33856,36987, 36987,33348,35313,23081,38076,40127,25704,33856,30739,33855,27804,43750,35798,16097,16496,35798,32934,39922,32934,364, 41265,2467, 12252,43750,3948, 19583,35798, 34889,36986,13418,729, 22386,40953,2138, 34497,45044,28661,34888,1069, 14388,4169, 679, 39163,33855,31334,18987,32933,36986,23689,24898,28228,34163,28660,34888, 33347,30092,36986,35313,34888,45043,36333,44536,34163,30091,34496,33347,29120,36986,19500,20219,35797,32933,30738,567, 2344, 45043,21155,718, 6620, 25169,12872, 2561, 37728,38142,41893,42723,44535,42349,2677, 39562,34888,622, 16466,34163,42929,37797,38471,45043,32933,27409,37716,10816,37811,3360, 38309,42348,5803, 37783, 39784,41264,37852,40354,34163,38816,36985,41020,12366,30737,45043,44535,36333,34887,38727,36985,12029,13608,12471,21666,33347,23902,15734,24389,44114,14926,35312, 44535,21935,15232,23080,33583,17522,32703,19037,35312,35797,17210,3490, 34162,28227,35312,34162,36332,4168, 34887,34496,15119,36985,34887,36985,17029,28226,41020, 43177,33855,17992,36984,35312,7501, 12971,19852,12057,41443,44113,20799,33583,43435,13274,4484, 40643,44113,12600,5352, 45042,14856,684, 1492, 20218,43176,38857, 3556, 2208, 41893,42723,44113,2676, 41761,1467, 43750,39546,17945,25421,36332,15325,34887,18522,33582,15876,18088,19355,21024,35797,34162,16399,44113,34162,19668, 16344,36984,30090,24649,34886,25420,29575,35797,28659,36332,40127,41761,43749,44535,44534,42348,45042,18469,20122,12807,36332,20027,30736,25168,4129, 40066,44112, 19267,44534,37763,40698,43176,35311,15018,42529,31999,1031, 37813,39645,5202, 38210,36984,42929,36984,37711,40397,42033,40542,44112,37815,38376,42928,38623,44534, 38752,37758,41264,40882,12782,43434,42723,5205, 39828,37837,39784,44534,41264,35796,37734,38228,40642,38694,38826,12966,36983,38689,35311,31998,44112,40310,45042, 44112,25167,38945,43749,45042,35311,45041,38537,36983,22712,14026,37787,42722,38338,44533,37745,43749,38093,42348,37800,36331,45041,44533,16029,38845,44111,45041, 40269,42928,41178,39764,19667,37868,30735,45041,37821,15899,17287,23688,45040,35311,13726,38553,27803,39256,14645,35796,18250,35796,36331,21517,30089,44533,37692, 43749,37854,38914,41652,37721,43434,44111,45040,31333,38109,38385,42928,38499,42033,44111,3911, 16853,36331,3210, 42529,41761,33136,3286, 42928,45040,785, 38230, 38267,40542,42348,39084,39222,34496,39189,13410,43176,40158,41761,41264,44533,41651,44111,38218,41178,38727,40697,39277,41020,30088,14261,3087, 43748,41651,16920, 3088, 41354,4728, 43434,2896, 38542,40832,39237,42722,11985,36983,35796,20798,40000,42184,41893,39872,936, 7014, 40354,39922,42529,35795,36331,38585,34886,39316, 45040,41098,37844,1937, 40542,38987,38857,42033,42347,37949,43434,42033,38028,22558,34496,20797,30087,37717,41443,40237,40542,42347,41543,40882,30734,37915,24388, 44110,44110,34886,34886,43433,6866, 7012, 41893,36983,31997,36982,26314,19666,21808,23079,19104,26015,36982,26313,25419,22240,18917,30733,39162,43748,35310,43176, 769, 44532,36982,42722,7011, 43748,45039,2377, 3288, 1258, 3115, 3557, 5522, 15944,12453,43433,24897,16658,42927,16560,23901,27802,29574,34885,26312,34161,36982, 31332,34495,31996,33855,34161,14443,24648,36981,14111,21665,24896,39946,42183,39545,36981,41442,42032,34885,11717,36330,44110,34885,22898,12745,15118,9973, 28225, 4726, 35310,16593,26014,36330,18300,24147,23900,35310,5906, 36981,6239, 6128, 1548, 41651,44532,38088,39946,34495,270, 42529,11294,40882,45039,2922, 817, 1228, 1360, 1227, 36981,1285, 9642, 13429,6728, 6869, 5905, 5207, 6868, 6131, 10887,5607, 5804, 6362, 2158, 1324, 4438, 396, 470, 4919, 124, 4378, 1547, 6363, 240, 5687, 3388, 5059, 38362,44532,42183,45039,42927,37819,43748,44532,40541,44110,40354,45039,43747,34885,35310,38460,44531,41760,34161,35309,5688, 39062,33347,40495, 35309,9363, 33346,21023,11716,11643,16465,36980,25703,34884,11357,45038,33346,1207, 5120, 5204, 44531,11422,41178,43433,42722,41892,38610,44531,43175,16811,23687, 144, 7013, 5279, 15825,31995,34495,34884,24387,30732,21807,36980,6014, 30731,36980,35795,44109,40447,40697,43433,10453,13954,16398,34161,19767,23474,34160,14183, 12665,36980,10581,36979,39338,36979,39899,303, 7016, 51, 1019, 4727, 32702,45038,40196,6365, 33346,33135,33854,36330,31994,28658,29119,35309,40594,18986,30730, 16237,33582,27801,12849,38241,33582,21806,38507,44109,26311,33346,40310,29118,21397,28657,35795,36979,9659, 11047,33582,35309,11091,36979,12307,33854,35795,34495, 39946,44531,45038,45038,3724, 45037,13532,25702,30729,13381,36978,31331,13261,34160,37689,3491, 43432,42528,5608, 34884,33345,36978,37759,38133,5802, 38813,41892, 25418,34884,35794,34494,36978,33854,41442,41543,44530,40697,44530,44530,36978,32933,37729,32701,28656,22239,20685,42032,28655,22557,33135,29117,31993,33854,45037, 45037,44109,35794,27408,14193,43432,43747,45037,34883,36977,43747,28224,35308,11356,38176,44530,28223,38686,33853,42721,41442,38148,43747,33853,75, 3263, 29116, 43432,41892,40196,23277,21279,33135,41651,10378,42528,45036,24386,22238,43746,33853,19851,11421,23686,36977,36330,21664,33345,36977,36329,23685,35794,35308,35794, 34883,45036,8949, 43432,34494,6618, 11009,4605, 41019,6870, 37870,39292,44529,2014, 41892,29573,35308,9828, 21278,19943,43431,11642,43431,34494,6871, 44529,40158, 45036,34883,44529,39316,1322, 17380,27407,6238, 43746,40765,44529,6502, 45036,42721,33853,6617, 43431,20330,34160,36977,36329,32700,36329,19193,24647,16028,2169, 42927,12923,34160,39277,42032,6129, 4379, 39545,23078,29115,17656,41760,42183,44109,26668,10030,25166,24646,26667,8330, 10176,10815,44528,9658, 1366, 11008,33852, 35793,41442,7900, 23899,33345,31992,34494,15712,42721,15546,28222,36976,40447,34883,12496,41891,27800,40309,41098,42347,36976,35793,43746,2401, 6237, 17745,11641, 9681, 32932,31991,36976,23077,34882,13740,36329,26013,36976,37740,37725,37688,40697,37712,33852,36975,5441, 11355,16852,28654,24385,34493,11982,5206, 27799,13380, 38173,36975,39132,36328,18843,23076,32699,17255,42347,41098,36975,35793,22237,27038,15049,19103,43746,37687,41441,40765,35793,2073, 37777,37727,38145,41891,40000, 29572,34159,30086,3117, 38406,40196,44108,33581,44108,44528,25417,2186, 42528,4081, 41760,1259, 6016, 34493,1193, 35308,1237, 38982,41543,17796,36975,11794,43745, 27406,10923,11640,10814,10703,39999,11007,41650,42528,44528,2590, 2964, 10175,34493,10968,36328,22556,11568,41097,42927,36974,10922,33345,11715,33344,36328,35307, 44528,26310,42721,8482, 11639,8315, 6616, 6130, 5690, 2209, 2869, 6503, 1926, 5442, 3489, 3528, 29571,2357, 34882,21154,35792,35792,21022,34882,36328,36327,36974, 34882,29114,36974,27798,36974,30085,38249,39871,34881,7928, 21021,7314, 5443, 34493,35792,7774, 34492,33852,8076, 40696,8089, 21805,44108,11567,41354,34881,36327, 35307,36327,45035,22711,33581,36973,33344,28653,29113,17604,34881,35792,36973,36973,28652,9961, 36973,33852,33851,19354,26666,44527,35791,35791,36327,27405,35791, 31330,31329,29570,23898,22095,36326,34881,20026,35307,45035,33851,42926,25701,15979,41097,29569,12664,27037,36326,17842,33344,11871,28651,36326,38031,39719,23075, 10029,40594,7813, 33135,1325, 33581,36326,27797,34492,43745,45035,43745,4437, 35791,36325,13628,1890, 24895,2343, 36325,40309,5280, 33134,36325,34492,43745,5521, 2320, 5689, 4284, 6361, 31328,15034,14901,36325,33851,35790,5610, 36972,30084,36972,34159,32932,33851,29112,34492,26665,41891,44108,43175,33581,33580,34491,35307, 13286,34159,34491,36324,26309,33134,32932,16785,42346,27036,35790,30083,42183,42346,3287, 7015, 6364, 25165,34159,34158,33580,35306,29111,36324,36972,36972,34491, 28221,27796,20796,36324,26308,13879,29568,36971,36324,45035,25700,23473,5609, 43175,34880,36971,17991,9114, 40093,36323,43431,36971,27404,33580,32698,35790,22897, 9224, 31990,45034,15288,19665,40158,35790,21277,23276,8887, 31989,43430,33580,21804,36971,32932,33134,9786, 9310, 39663,20454,33579,42926,1320, 11714,2675, 36323, 36970,33579,21663,10013,25699,35306,34880,32931,32931,35789,30728,32697,35789,33134,28650,13725,3175, 45034,44107,30727,773, 45034,27795,20916,31988,36323,18985, 24384,38196,3116, 42527,41097,43430,41097,29110,31327,36323,36970,43744,35789,42032,43744,41760,32696,34158,11354,36970,36970,34491,1951, 35789,1367, 1365, 35306, 1260, 1364, 1363, 1362, 1361, 42346,1319, 1283, 1359, 1389, 1388, 1284, 1358, 1321, 1368, 26012,29109,2999, 33344,27403,33343,36969,36322,36969,13280,34880,14833, 22555,31987,29108,23074,26664,36322,33343,36969,33579,2421, 5203, 33343,22236,3527, 34880,41650,4791, 35788,35788,39700,44107,32931,36322,44107,35306,35305,42720, 13816,31986,34879,12351,16697,9785, 41650,31326,35788,44107,40764,36322,36969,32931,36968,36321,36321,16051,27035,36968,16372,35788,34490,33579,36968,44106,27034, 33850,34879,44527,32695,41096,935, 35787,45034,42527,42527,3859, 2521, 6132, 32930,45033,33850,33133,21153,36968,40594,10174,18521,44527,36967,35787,43744,6619, 36967,36967,31325,34879,3615, 34158,9249, 43175,24645,33850,39107,42182,10524,34158,35305,36321,9262, 9641, 44527,24146,31985,24894,33578,23472,34879,31324,42926, 43174,590, 41353,39472,9698, 38259,42182,40127,1323, 11713,40397,42346,14765,45033,5201, 6865, 42527,7017, 6015, 45033,43430,9715, 9532, 34878,43174,34878,44106, 18639,35305,1936, 5281, 36321,4332, 5909, 6242, 1369, 35305,16657,23471,35787,33343,45033,32694,36320,36320,28649,34157,19433,20795,36967,24644,36320,34157,34490, 35787,32930,36320,34157,40396,42926,20794,9844, 33133,10132,23275,10028,4382, 35304,36319,35304,36319,18916,34157,12977,36966,28648,35786,22896,34156,5446, 26307, 33850,36319,35304,44526,43430,44526,6019, 34490,36966,32930,29567,35304,26663,35303,30726,36966,23470,33578,3423, 1028, 998, 115, 176, 6018, 2839, 5908, 31323, 31322,26011,32693,36319,36966,6872, 1527, 1712, 6504, 298, 5208, 2965, 1726 }; #define SizeChildMapEntry 6 static const unsigned char ChildMap[6617*6] = { 138,255,229,255,255,127, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 16, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 192,255,1, 0, 0, 0, 128,0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 192,0, 0, 0, 0, 0, 128,96, 0, 0, 0, 0, 128,65, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 128,0, 64, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 0, 128,2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 128,1, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 2, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 32, 0, 0, 0, 0, 32, 0, 0, 128,129,0, 0, 0, 0, 128,255,97, 5, 115,17, 128,39, 0, 0, 0, 0, 128,223,1, 0, 0, 0, 128,255,1, 0, 0, 0, 0, 128,0, 0, 0, 0, 128,7, 0, 0, 0, 0, 128,79, 0, 0, 0, 0, 0, 95, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 128,136,0, 0, 0, 0, 128,47, 0, 0, 33, 0, 128,251,1, 0, 0, 0, 128,125,33, 0, 32, 0, 0, 29, 32, 0, 32, 0, 0, 96, 32, 0, 0, 0, 0, 64, 32, 0, 0, 0, 0, 128,1, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 128,0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 96, 0, 128,0, 128,16, 0, 0, 0, 0, 0, 0, 0, 128,0, 0, 0, 0, 0, 0, 128,0, 0, 0, 0, 0, 0, 16, 0, 61, 0, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 103,1, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 144,0, 0, 0, 0, 0, 131,0, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 16, 0, 0, 4, 0, 0, 0, 0, 8, 0, 0, 0, 69, 1, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 73, 0, 0, 0, 0, 0, 130,0, 0, 0, 0, 128,249,1, 0, 0, 0, 0, 255,1, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 32, 0, 64, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 8, 0, 0, 0, 0, 0, 0, 4, 0, 0, 32, 32, 8, 0, 0, 0, 128,0, 1, 0, 0, 0, 32, 0, 0, 2, 0, 2, 32, 0, 0, 8, 0, 2, 0, 0, 0, 16, 128,255,1, 20, 0, 0, 128,19, 0, 0, 128,0, 128,31, 0, 0, 0, 0, 128,0, 0, 64, 0, 0, 0, 70, 0, 0, 0, 0, 0, 42, 0, 0, 128,0, 0, 34, 0, 0, 0, 0, 128,3, 0, 0, 0, 0, 128,130,0, 0, 0, 0, 0, 0, 160,0, 0, 0, 128,255,1, 160,162,25, 128,4, 0, 0, 0, 0, 128,32, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 134,1, 0, 0, 0, 0, 6, 0, 0, 0, 0, 128,6, 0, 0, 0, 0, 0, 128,0, 0, 16, 0, 128,255,1, 2, 198,105, 0, 129,0, 0, 0, 0, 128,106,0, 0, 0, 0, 128,10, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 25, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 109,0, 0, 0, 0, 0, 64, 0, 64, 0, 0, 0, 0, 0, 4, 0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 128,0, 0, 8, 128,255,0, 0, 65, 8, 128,16, 128,0, 128,0, 0, 20, 1, 0, 0, 0, 0, 18, 0, 0, 0, 0, 128,22, 0, 0, 0, 0, 0, 25, 64, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 208,0, 0, 32, 0, 0, 16, 128,0, 4, 0, 128,255,193,0, 0, 2, 0, 48, 0, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 96, 1, 0, 0, 0, 0, 160,0, 0, 0, 0, 0, 32, 129,0, 0, 0, 128,255,65, 136,0, 18, 0, 64, 0, 1, 0, 0, 0, 65, 0, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 112,0, 0, 0, 0, 0, 196,0, 0, 0, 0, 0, 64, 1, 0, 0, 0, 128,105,1, 0, 0, 0, 0, 245,1, 97, 0, 6, 0, 128,0, 4, 0, 0, 128,8, 0, 0, 0, 0, 128,177,1, 0, 128,0, 0, 2, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 4, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 1, 224,255,255,127, 0, 0, 0, 32, 64, 0, 0, 2, 64, 0, 0, 0, 0, 0, 96, 24, 199,8, 0, 5, 96, 24, 68, 0, 0, 0, 32, 16, 0, 0, 0, 1, 32, 16, 64, 0, 0, 1, 32, 16, 0, 0, 0, 0, 0, 8, 64, 0, 0, 0, 0, 9, 24, 0, 4, 1, 0, 0, 128,0, 0, 0, 224,111,221,34, 0, 0, 192,33, 197,1, 0, 0, 32, 128,0, 2, 0, 0, 0, 34, 130,0, 0, 0, 128,2, 0, 0, 0, 0, 32, 34, 200,34, 0, 0, 0, 0, 130,1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 3, 4, 0, 0, 0, 128,1, 4, 0, 0, 0, 128,0, 4, 0, 0, 0, 0, 0, 0, 1, 32, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 16, 0, 0, 0, 0, 32, 34, 8, 2, 0, 0, 0, 0, 10, 0, 0, 0, 0, 34, 0, 0, 0, 0, 128,0, 65, 0, 0, 0, 0, 34, 8, 0, 0, 0, 0, 0, 12, 0, 4, 0, 0, 42, 69, 33, 0, 0, 32, 2, 129,0, 0, 0, 128,1, 69, 0, 0, 0, 32, 2, 0, 0, 0, 0, 0, 0, 4, 1, 0, 0, 0, 9, 1, 1, 0, 0, 32, 35, 0, 0, 0, 0, 0, 32, 0, 32, 0, 0, 0, 0, 192,0, 0, 0, 0, 0, 64, 33, 0, 0, 0, 32, 1, 0, 0, 0, 32, 1, 75, 6, 0, 0, 0, 32, 0, 1, 0, 0, 64, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 128,16, 130,16, 0, 0, 0, 2, 136,0, 0, 0, 0, 0, 8, 4, 0, 0, 32, 8, 8, 6, 0, 0, 32, 0, 6, 0, 0, 0, 0, 0, 144,64, 0, 0, 128,18, 8, 3, 0, 0, 128,0, 0, 1, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 65, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 129,0, 0, 0, 64, 0, 16, 0, 0, 0, 0, 34, 128,0, 0, 0, 0, 1, 4, 0, 0, 0, 32, 32, 72, 0, 0, 0, 0, 3, 196,1, 0, 0, 32, 32, 0, 0, 0, 0, 0, 1, 192,0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 1, 128,0, 0, 0, 0, 0, 130,0, 0, 0, 160,179,111,3, 0, 0, 0, 1, 16, 1, 0, 0, 32, 0, 128,0, 0, 0, 32, 34, 9, 2, 0, 0, 0, 32, 8, 0, 0, 0, 0, 0, 149,1, 0, 0, 0, 0, 128,2, 0, 0, 32, 34, 128,0, 0, 0, 64, 0, 4, 0, 0, 1, 1, 34, 8, 0, 0, 0, 0, 2, 0, 64, 0, 0, 32, 0, 129,0, 0, 0, 0, 0, 195,2, 0, 0, 0, 0, 18, 0, 0, 0, 32, 0, 1, 0, 0, 0, 128,0, 128,0, 0, 0, 0, 34, 2, 0, 0, 0, 32, 34, 0, 0, 0, 0, 0, 0, 194,0, 0, 0, 32, 34, 0, 1, 0, 0, 96, 1, 194,39, 0, 0, 32, 0, 8, 0, 0, 0, 0, 34, 10, 33, 0, 0, 0, 3, 132,0, 0, 0, 0, 2, 5, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 72, 0, 0, 0, 0, 3, 4, 0, 0, 0, 64, 0, 136,0, 0, 0, 0, 34, 7, 0, 0, 0, 0, 0, 131,0, 0, 0, 0, 0, 136,0, 0, 0, 0, 0, 128,32, 0, 0, 0, 1, 130,0, 0, 0, 64, 4, 192,2, 0, 0, 0, 32, 0, 4, 0, 0, 0, 0, 192,1, 0, 0, 0, 2, 8, 32, 0, 0, 0, 2, 128,32, 0, 0, 64, 0, 128,0, 0, 0, 0, 0, 128,1, 0, 0, 0, 34, 200,2, 0, 0, 0, 0, 140,4, 0, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 4, 87, 1, 0, 0, 160,251,207,14, 0, 0, 128,56, 23, 40, 0, 0, 64, 32, 0, 0, 4, 3, 160,34, 136,0, 0, 0, 0, 32, 4, 0, 0, 0, 32, 0, 0, 32, 0, 0, 0, 128,128,0, 0, 0, 0, 128,8, 0, 0, 0, 128,0, 0, 16, 0, 0, 32, 34, 193,32, 0, 0, 0, 32, 66, 0, 0, 0, 0, 1, 196,0, 0, 0, 0, 2, 0, 32, 0, 0, 128,2, 132,1, 0, 0, 64, 0, 117,0, 0, 0, 32, 34, 131,0, 0, 0, 32, 0, 4, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 3, 76, 0, 0, 0, 32, 2, 8, 2, 0, 0, 0, 32, 0, 2, 0, 0, 32, 34, 2, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 132,0, 0, 0, 0, 0, 64, 32, 0, 0, 0, 0, 196,1, 0, 1, 0, 32, 0, 0, 0, 0, 64, 0, 1, 1, 0, 0, 64, 0, 8, 0, 0, 0, 64, 0, 85, 0, 0, 0, 0, 6, 16, 0, 0, 0, 0, 0, 12, 4, 0, 0, 32, 34, 4, 0, 0, 0, 0, 32, 128,0, 0, 0, 32, 6, 0, 0, 0, 0, 32, 2, 4, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 18, 128,0, 0, 0, 32, 34, 8, 0, 0, 0, 0, 1, 194,0, 0, 0, 128,0, 129,0, 0, 0, 0, 8, 192,0, 0, 0, 0, 32, 12, 0, 0, 0, 64, 1, 144,0, 0, 0, 0, 0, 26, 0, 0, 0, 32, 44, 73, 1, 0, 0, 32, 34, 73, 0, 0, 0, 0, 1, 69, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 8, 135,9, 0, 0, 96, 42, 77, 35, 0, 0, 0, 32, 70, 1, 0, 0, 0, 1, 0, 8, 0, 0, 0, 35, 197,0, 0, 0, 160,1, 0, 1, 0, 0, 32, 18, 0, 0, 0, 0, 0, 1, 5, 1, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 129,8, 0, 0, 0, 0, 196,0, 0, 0, 64, 0, 2, 8, 0, 0, 0, 3, 128,0, 0, 0, 32, 33, 130,0, 0, 0, 128,0, 16, 0, 0, 0, 0, 0, 4, 2, 0, 0, 32, 35, 137,0, 0, 0, 0, 1, 128,32, 0, 0, 32, 0, 69, 0, 0, 0, 32, 2, 1, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 2, 0, 4, 4, 0, 32, 18, 207,3, 0, 0, 64, 16, 64, 0, 0, 0, 32, 0, 66, 0, 0, 0, 64, 10, 162,0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0, 6, 0, 0, 0, 128,171,207,1, 0, 0, 0, 34, 10, 0, 0, 0, 0, 0, 4, 32, 0, 0, 0, 34, 138,0, 0, 0, 0, 2, 132,0, 0, 0, 0, 34, 129,0, 0, 0, 0, 3, 0, 0, 4, 0, 32, 0, 128,1, 0, 0, 128,0, 1, 8, 4, 0, 192,55, 147,41, 0, 0, 32, 0, 8, 2, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 16, 1, 0, 0, 0, 0, 0, 36, 0, 0, 32, 16, 17, 0, 0, 0, 128,130,8, 0, 0, 0, 0, 0, 64, 16, 0, 0, 96, 50, 82, 0, 0, 0, 0, 128,71, 32, 4, 0, 225,175,255,111, 0, 0, 64, 33, 214,33, 0, 0, 0, 33, 0, 0, 0, 0, 0, 128,6, 0, 4, 0, 32, 34, 12, 0, 0, 0, 0, 0, 128,64, 0, 0, 128,32, 2, 0, 4, 0, 0, 34, 128,0, 0, 0, 0, 128,0, 1, 0, 0, 32, 34, 64, 2, 0, 0, 128,0, 69, 3, 0, 0, 0, 34, 8, 32, 0, 0, 32, 2, 64, 0, 0, 0, 0, 34, 66, 0, 0, 0, 0, 41, 128,1, 4, 0, 32, 48, 136,32, 0, 0, 0, 2, 140,0, 0, 0, 0, 8, 0, 1, 0, 0, 0, 0, 226,0, 0, 0, 32, 50, 8, 0, 0, 0, 0, 0, 69, 65, 0, 0, 128,16, 196,5, 0, 0, 0, 130,0, 0, 0, 0, 32, 34, 72, 0, 0, 0, 128,0, 22, 96, 0, 0, 128,1, 0, 0, 0, 0, 160,234,238,57, 4, 0, 0, 160,0, 0, 0, 0, 0, 129,128,0, 0, 0, 0, 48, 128,0, 4, 0, 32, 32, 8, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 1, 0, 32, 0, 0, 64, 0, 64, 0, 0, 0, 0, 0, 76, 0, 4, 0, 224,143,206,85, 0, 0, 0, 8, 128,0, 4, 1, 32, 0, 0, 0, 0, 0, 0, 16, 4, 0, 0, 0, 32, 50, 136,1, 4, 8, 96, 43, 222,35, 0, 0, 0, 48, 70, 9, 0, 0, 64, 10, 199,36, 0, 0, 32, 2, 8, 0, 4, 1, 96, 3, 128,8, 0, 0, 128,2, 128,0, 0, 0, 128,0, 132,8, 0, 0, 32, 10, 132,1, 0, 0, 0, 11, 0, 0, 0, 0, 128,0, 64, 41, 0, 0, 64, 0, 4, 32, 0, 0, 192,16, 24, 3, 0, 0, 0, 0, 64, 1, 4, 0, 0, 0, 132,0, 0, 0, 32, 34, 40, 32, 0, 0, 0, 9, 196,64, 0, 0, 32, 0, 0, 64, 0, 0, 0, 33, 4, 33, 0, 0, 0, 1, 132,0, 0, 0, 0, 54, 140,3, 0, 0, 32, 10, 132,64, 0, 0, 32, 50, 128,0, 0, 0, 128,0, 0, 2, 0, 1, 64, 0, 0, 0, 0, 0, 128,0, 0, 64, 0, 0, 32, 1, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 64, 32, 24, 3, 0, 0, 32, 50, 202,0, 0, 0, 0, 8, 66, 4, 0, 0, 128,0, 198,0, 0, 0, 64, 16, 0, 0, 0, 0, 160,34, 134,0, 0, 0, 0, 4, 64, 2, 0, 0, 128,2, 2, 65, 0, 0, 64, 40, 6, 0, 0, 0, 0, 0, 8, 2, 0, 0, 160,0, 2, 0, 0, 0, 64, 32, 4, 0, 0, 0, 32, 34, 8, 32, 0, 0, 0, 16, 64, 0, 0, 0, 32, 3, 8, 0, 0, 0, 0, 0, 132,64, 0, 0, 32, 0, 193,96, 0, 0, 32, 34, 132,1, 0, 0, 32, 1, 132,64, 0, 0, 32, 50, 136,0, 0, 0, 96, 50, 222,43, 0, 0, 192,145,197,101, 0, 0, 0, 144,0, 0, 0, 0, 0, 36, 0, 0, 0, 0, 0, 1, 12, 1, 0, 0, 0, 2, 8, 2, 0, 0, 32, 34, 73, 2, 4, 1, 0, 8, 129,0, 0, 0, 32, 10, 0, 5, 0, 0, 32, 0, 12, 2, 0, 0, 0, 0, 193,0, 0, 0, 0, 8, 4, 0, 0, 0, 0, 3, 197,113, 0, 0, 0, 32, 1, 2, 0, 0, 32, 33, 0, 1, 0, 0, 0, 2, 138,0, 0, 0, 0, 32, 138,0, 0, 0, 128,0, 4, 0, 4, 1, 0, 0, 132,0, 4, 0, 32, 0, 136,0, 0, 0, 128,0, 32, 0, 0, 0, 0, 18, 0, 2, 0, 0, 160,11, 196,0, 0, 0, 32, 128,8, 0, 0, 0, 0, 16, 136,0, 0, 0, 0, 129,196,2, 0, 0, 0, 2, 128,0, 0, 0, 32, 50, 129,2, 0, 0, 0, 0, 1, 1, 0, 0, 0, 50, 0, 0, 0, 0, 128,1, 17, 1, 0, 0, 128,0, 133,0, 4, 0, 0, 0, 8, 16, 0, 0, 160,255,140,127, 4, 0, 192,154,215,33, 0, 0, 0, 2, 1, 0, 0, 0, 0, 16, 8, 0, 0, 0, 0, 32, 136,32, 0, 0, 0, 0, 128,65, 0, 0, 0, 34, 0, 1, 0, 0, 0, 178,72, 0, 0, 0, 0, 0, 68, 4, 0, 0, 32, 34, 130,0, 0, 0, 32, 34, 72, 34, 0, 0, 0, 0, 1, 36, 0, 0, 96, 2, 130,1, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 1, 4, 0, 0, 32, 34, 8, 98, 4, 0, 32, 2, 152,41, 0, 1, 0, 0, 128,0, 0, 0, 160,2, 128,0, 0, 0, 0, 32, 130,0, 4, 0, 32, 0, 8, 0, 0, 0, 128,0, 131,11, 0, 0, 32, 34, 201,10, 0, 0, 0, 0, 65, 4, 4, 1, 32, 34, 137,34, 0, 0, 160,128,37, 1, 4, 0, 0, 0, 132,2, 0, 0, 0, 2, 130,1, 0, 0, 32, 2, 141,0, 0, 0, 0, 8, 20, 0, 0, 0, 32, 0, 134,0, 0, 0, 0, 0, 65, 5, 0, 0, 0, 32, 129,0, 0, 0, 64, 128,134,1, 0, 0, 32, 18, 128,0, 0, 0, 32, 2, 69, 0, 4, 0, 32, 34, 10, 34, 4, 0, 64, 0, 135,0, 4, 0, 0, 0, 135,17, 0, 0, 128,146,128,5, 0, 0, 0, 0, 0, 34, 0, 0, 32, 0, 7, 0, 0, 0, 0, 0, 3, 0, 0, 0, 32, 2, 25, 9, 0, 0, 32, 0, 0, 33, 4, 0, 160,178,201,10, 0, 0, 128,8, 65, 0, 0, 0, 0, 0, 197,0, 0, 0, 0, 0, 12, 2, 0, 0, 0, 34, 72, 0, 0, 0, 32, 0, 2, 0, 4, 65, 0, 0, 0, 0, 0, 0, 192,25, 173,4, 0, 0, 0, 48, 129,0, 0, 0, 0, 32, 133,0, 0, 0, 32, 34, 0, 34, 0, 0, 32, 34, 0, 32, 0, 0, 32, 2, 12, 0, 0, 0, 0, 128,2, 0, 0, 0, 96, 16, 26, 9, 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 48, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 128,65, 0, 0, 0, 32, 62, 89, 3, 0, 0, 128,0, 64, 1, 0, 0, 0, 16, 2, 0, 0, 0, 0, 0, 193,16, 0, 0, 128,2, 1, 0, 0, 0, 128,1, 149,1, 0, 0, 0, 32, 9, 0, 0, 0, 0, 34, 0, 32, 0, 0, 0, 2, 128,64, 0, 0, 32, 2, 201,0, 0, 0, 32, 0, 69, 1, 0, 0, 64, 0, 1, 0, 0, 0, 0, 1, 0, 64, 0, 1, 64, 72, 150,41, 0, 0, 32, 2, 0, 32, 0, 0, 160,2, 4, 0, 0, 0, 64, 0, 4, 1, 0, 0, 128,16, 4, 0, 0, 0, 32, 0, 16, 20, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 66, 1, 0, 0, 224,235,255,111, 0, 0, 192,168,70, 39, 0, 0, 0, 18, 0, 0, 0, 0, 0, 128,0, 8, 0, 0, 96, 32, 0, 0, 0, 0, 32, 73, 0, 1, 0, 0, 32, 34, 8, 34, 0, 0, 0, 0, 1, 2, 0, 0, 0, 8, 65, 2, 0, 0, 32, 50, 8, 3, 0, 0, 0, 0, 15, 0, 0, 0, 96, 35, 0, 10, 0, 0, 0, 34, 6, 0, 0, 0, 0, 1, 204,32, 0, 0, 64, 2, 1, 5, 0, 0, 32, 0, 0, 1, 0, 0, 32, 34, 72, 2, 0, 0, 0, 32, 5, 0, 0, 0, 224,48, 133,37, 4, 16, 0, 0, 128,0, 4, 0, 32, 1, 128,65, 0, 0, 0, 2, 128,1, 0, 0, 0, 18, 72, 34, 0, 0, 0, 1, 129,1, 0, 0, 0, 64, 5, 0, 4, 0, 32, 0, 128,0, 0, 0, 160,15, 140,65, 0, 0, 0, 2, 16, 1, 0, 0, 32, 0, 10, 0, 0, 0, 32, 3, 4, 1, 0, 0, 0, 16, 0, 1, 4, 0, 224,38, 152,40, 0, 0, 128,9, 134,0, 0, 0, 0, 34, 0, 2, 0, 0, 0, 1, 197,0, 0, 0, 0, 33, 0, 1, 0, 0, 0, 75, 132,1, 0, 0, 0, 9, 0, 0, 0, 0, 0, 32, 68, 2, 0, 0, 0, 2, 72, 0, 0, 0, 32, 51, 136,10, 0, 0, 0, 2, 193,33, 0, 0, 0, 0, 5, 9, 4, 0, 0, 0, 8, 0, 0, 0, 128,0, 198,2, 0, 0, 0, 4, 0, 8, 0, 0, 32, 50, 72, 2, 0, 0, 0, 32, 134,32, 0, 0, 0, 2, 2, 0, 0, 0, 32, 9, 173,96, 0, 0, 160,10, 140,4, 0, 0, 0, 8, 0, 64, 0, 0, 0, 8, 5, 104, 0, 0, 32, 16, 136,0, 0, 0, 0, 16, 132,0, 4, 0, 32, 54, 202,106, 0, 0, 32, 8, 66, 0, 0, 0, 64, 32, 0, 32, 0, 0, 0, 0, 64, 2, 0, 0, 160,14, 129,0, 0, 0, 0, 16, 1, 2, 4, 0, 0, 34, 192,0, 0, 0, 64, 40, 0, 0, 0, 0, 0, 3, 133,64, 0, 0, 224,187,147,35, 4, 0, 0, 160,213,33, 0, 0, 0, 34, 64, 34, 0, 0, 0, 18, 8, 0, 0, 0, 128,1, 128,0, 0, 0, 128,32, 0, 0, 0, 1, 32, 4, 0, 64, 0, 1, 32, 72, 0, 0, 0, 1, 0, 64, 0, 0, 0, 0, 224,6, 75, 11, 4, 0, 0, 0, 7, 0, 0, 0, 32, 0, 64, 34, 0, 0, 0, 4, 2, 0, 0, 0, 32, 1, 6, 0, 0, 0, 32, 34, 192,8, 0, 0, 0, 1, 0, 40, 0, 0, 0, 2, 0, 33, 0, 0, 32, 50, 1, 0, 0, 0, 128,0, 68, 0, 0, 0, 160,54, 10, 10, 0, 0, 0, 33, 128,2, 0, 0, 0, 1, 195,1, 0, 0, 0, 8, 130,0, 0, 0, 128,0, 64, 0, 0, 0, 0, 0, 70, 0, 0, 0, 0, 34, 16, 0, 0, 0, 32, 50, 73, 2, 0, 0, 128,32, 64, 0, 0, 0, 0, 0, 68, 2, 0, 1, 0, 0, 149,1, 4, 0, 160,178,79, 9, 0, 0, 0, 0, 0, 33, 0, 0, 32, 34, 9, 0, 0, 0, 32, 2, 9, 0, 0, 0, 0, 0, 6, 32, 0, 0, 32, 32, 68, 32, 0, 0, 0, 2, 1, 4, 0, 0, 0, 0, 22, 0, 0, 0, 32, 34, 72, 10, 0, 0, 192,40, 4, 8, 0, 0, 64, 0, 198,0, 0, 0, 128,0, 65, 1, 0, 0, 32, 0, 128,2, 0, 0, 192,239,199,117, 0, 0, 0, 144,9, 1, 0, 0, 32, 34, 64, 0, 0, 0, 96, 2, 8, 1, 0, 0, 0, 34, 8, 1, 0, 0, 0, 58, 130,2, 4, 0, 32, 34, 8, 34, 0, 0, 0, 1, 1, 1, 0, 0, 0, 11, 0, 1, 4, 0, 0, 32, 128,32, 0, 0, 192,48, 147,1, 4, 5, 0, 0, 0, 0, 0, 0, 0, 50, 72, 2, 0, 0, 32, 32, 0, 32, 0, 0, 64, 8, 22, 0, 0, 0, 0, 32, 8, 32, 0, 0, 0, 32, 5, 1, 0, 0, 32, 0, 9, 0, 0, 0, 128,0, 128,33, 0, 0, 128,0, 69, 96, 0, 0, 32, 32, 1, 0, 0, 0, 32, 9, 0, 3, 0, 0, 32, 50, 128,33, 0, 0, 32, 1, 133,5, 0, 0, 32, 8, 0, 0, 0, 0, 128,32, 5, 0, 0, 0, 96, 190,76, 8, 0, 0, 0, 161,65, 32, 0, 0, 0, 128,5, 0, 0, 0, 0, 38, 129,1, 0, 0, 224,3, 207,2, 0, 0, 0, 128,69, 1, 0, 0, 0, 34, 8, 8, 0, 0, 0, 0, 4, 3, 0, 0, 0, 16, 128,0, 0, 0, 224,162,192,67, 0, 0, 0, 1, 65, 64, 0, 0, 0, 0, 64, 4, 0, 0, 0, 0, 2, 64, 0, 0, 0, 2, 68, 0, 4, 0, 225,119,91, 43, 0, 0, 224,255,223,127, 0, 0, 32, 129,192,0, 0, 0, 224,34, 136,34, 0, 0, 0, 128,64, 0, 0, 0, 0, 0, 193,1, 0, 0, 160,2, 141,0, 0, 0, 0, 10, 8, 0, 4, 0, 224,45, 129,0, 0, 16, 0, 32, 0, 0, 0, 0, 160,176,8, 3, 0, 0, 32, 16, 0, 2, 0, 0, 0, 0, 66, 0, 0, 0, 32, 34, 78, 3, 4, 0, 0, 2, 128,0, 4, 0, 64, 63, 219,43, 0, 0, 0, 34, 1, 1, 0, 0, 64, 8, 0, 0, 0, 0, 0, 2, 144,0, 4, 0, 96, 43, 15, 34, 0, 0, 64, 0, 129,0, 0, 0, 32, 0, 68, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 144,64, 64, 4, 0, 64, 59, 151,8, 0, 0, 32, 34, 136,32, 0, 0, 0, 1, 64, 1, 0, 0, 0, 2, 12, 0, 0, 0, 32, 2, 71, 0, 0, 0, 0, 138,207,65, 0, 0, 0, 34, 137,32, 0, 0, 0, 1, 192,32, 0, 0, 0, 6, 4, 0, 0, 0, 64, 2, 128,1, 0, 0, 0, 3, 8, 0, 0, 0, 32, 162,137,2, 0, 0, 0, 1, 196,8, 4, 1, 0, 32, 128,32, 0, 0, 224,167,203,65, 0, 0, 0, 0, 132,32, 0, 0, 0, 16, 0, 2, 0, 0, 0, 32, 8, 2, 0, 0, 32, 146,8, 0, 0, 0, 32, 34, 76, 42, 0, 0, 32, 0, 192,0, 0, 0, 0, 40, 8, 0, 0, 0, 128,0, 132,2, 0, 0, 160,2, 8, 0, 4, 0, 32, 42, 218,40, 0, 0, 0, 9, 198,0, 0, 0, 0, 1, 196,73, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 12, 11, 0, 1, 0, 0, 0, 32, 0, 0, 32, 2, 8, 32, 0, 0, 0, 136,4, 0, 0, 0, 0, 128,0, 32, 0, 0, 32, 50, 72, 66, 0, 0, 0, 0, 64, 64, 0, 0, 128,2, 2, 0, 0, 0, 96, 6, 4, 0, 0, 0, 128,0, 6, 0, 0, 0, 160,255,172,103, 4, 0, 32, 34, 128,40, 0, 0, 0, 136,192,96, 0, 0, 64, 178,129,0, 0, 0, 32, 128,128,0, 4, 0, 96, 50, 200,32, 0, 0, 0, 0, 192,32, 0, 0, 0, 2, 2, 1, 0, 0, 32, 2, 128,0, 4, 0, 224,191,207,107, 0, 0, 192,208,132,41, 0, 0, 0, 32, 136,0, 0, 0, 0, 0, 128,8, 4, 0, 32, 34, 200,2, 0, 0, 0, 9, 192,1, 0, 0, 160,0, 4, 0, 0, 0, 160,3, 97, 0, 4, 0, 32, 32, 136,0, 4, 0, 0, 0, 64, 0, 0, 0, 0, 0, 132,66, 0, 0, 32, 50, 9, 66, 0, 0, 0, 4, 0, 1, 0, 0, 32, 34, 136,8, 0, 0, 0, 37, 133,1, 0, 0, 32, 0, 1, 32, 0, 0, 0, 34, 9, 0, 0, 0, 192,0, 133,3, 0, 0, 32, 51, 139,2, 0, 0, 0, 3, 64, 1, 0, 0, 32, 51, 136,42, 0, 0, 192,0, 192,0, 0, 0, 0, 0, 192,33, 0, 0, 0, 128,1, 0, 0, 0, 128,0, 166,0, 4, 0, 32, 34, 0, 0, 0, 0, 32, 34, 136,34, 0, 0, 128,8, 132,65, 0, 0, 0, 128,1, 2, 0, 0, 128,33, 197,1, 4, 0, 32, 2, 8, 0, 0, 0, 128,10, 141,1, 0, 0, 32, 128,0, 0, 0, 0, 0, 0, 140,8, 4, 1, 0, 0, 2, 1, 0, 0, 128,144,4, 1, 4, 0, 32, 154,207,98, 0, 0, 0, 130,69, 0, 0, 0, 0, 0, 129,1, 0, 0, 0, 4, 8, 0, 0, 0, 0, 0, 2, 8, 0, 0, 64, 0, 128,33, 4, 0, 0, 0, 0, 1, 0, 0, 0, 128,133,8, 0, 0, 0, 0, 6, 1, 0, 0, 0, 16, 0, 64, 0, 0, 128,0, 132,0, 0, 0, 160,191,173,43, 0, 0, 64, 1, 195,32, 0, 1, 0, 0, 1, 0, 0, 0, 32, 38, 8, 0, 0, 0, 64, 0, 128,1, 0, 0, 0, 54, 11, 0, 0, 0, 32, 3, 13, 0, 4, 0, 160,51, 202,97, 0, 0, 0, 0, 130,32, 0, 0, 0, 34, 192,1, 0, 0, 32, 34, 137,32, 0, 0, 0, 1, 68, 32, 0, 0, 0, 34, 128,33, 0, 0, 64, 16, 8, 0, 4, 0, 0, 13, 128,0, 0, 0, 192,155,194,65, 0, 0, 0, 16, 8, 2, 4, 0, 0, 0, 129,0, 0, 0, 32, 2, 6, 0, 0, 0, 96, 26, 1, 0, 0, 0, 0, 1, 4, 1, 0, 0, 128,0, 2, 0, 0, 0, 128,32, 0, 64, 0, 0, 0, 131,1, 0, 0, 0, 0, 2, 1, 9, 4, 0, 96, 2, 143,10, 0, 0, 0, 4, 128,0, 0, 0, 0, 32, 2, 0, 0, 0, 32, 34, 8, 64, 0, 0, 96, 128,0, 0, 0, 64, 64, 0, 0, 0, 4, 0, 224,255,239,111, 0, 0, 192,185,199,71, 0, 1, 32, 6, 1, 34, 0, 0, 0, 2, 129,32, 0, 0, 0, 2, 131,0, 0, 0, 32, 34, 136,0, 0, 0, 64, 34, 128,0, 4, 1, 224,35, 128,32, 0, 0, 32, 2, 138,0, 0, 0, 0, 0, 9, 1, 0, 1, 0, 32, 137,0, 0, 0, 32, 34, 205,33, 0, 0, 128,0, 128,64, 0, 0, 0, 32, 1, 32, 0, 0, 192,69, 211,5, 0, 0, 32, 16, 8, 0, 0, 0, 0, 6, 0, 0, 4, 0, 32, 2, 0, 0, 0, 0, 160,146,76, 3, 0, 0, 0, 0, 2, 2, 0, 0, 32, 2, 1, 1, 0, 0, 32, 54, 143,42, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 3, 1, 4, 0, 96, 47, 221,9, 0, 0, 0, 128,1, 4, 0, 0, 0, 0, 69, 32, 0, 0, 128,0, 136,0, 0, 0, 0, 48, 24, 0, 4, 0, 192,157,215,69, 0, 0, 32, 34, 1, 8, 0, 0, 0, 2, 1, 32, 0, 0, 128,2, 128,32, 0, 0, 128,34, 0, 64, 4, 0, 64, 0, 131,32, 0, 0, 0, 0, 9, 8, 0, 0, 0, 16, 129,0, 0, 0, 32, 32, 72, 2, 0, 0, 96, 42, 205,2, 0, 0, 0, 0, 4, 33, 0, 0, 0, 32, 132,0, 0, 0, 32, 34, 79, 2, 0, 0, 0, 0, 69, 4, 4, 0, 32, 0, 0, 0, 0, 0, 32, 34, 7, 0, 0, 0, 128,203,197,1, 0, 0, 0, 130,2, 0, 0, 0, 128,18, 4, 0, 0, 0, 0, 18, 0, 64, 0, 0, 0, 162,0, 0, 0, 0, 160,239,143,71, 4, 0, 64, 160,196,1, 0, 0, 32, 0, 4, 8, 0, 0, 32, 32, 9, 0, 0, 0, 32, 32, 64, 0, 0, 0, 0, 1, 0, 4, 0, 0, 32, 2, 133,69, 0, 0, 0, 4, 0, 4, 0, 0, 32, 32, 4, 0, 4, 0, 96, 50, 138,38, 4, 1, 128,60, 198,4, 0, 0, 0, 9, 0, 33, 4, 0, 32, 4, 192,12, 0, 0, 0, 0, 144,0, 0, 0, 0, 11, 132,4, 0, 0, 0, 0, 38, 9, 0, 0, 0, 0, 8, 32, 4, 0, 96, 0, 0, 0, 0, 0, 32, 0, 4, 13, 0, 0, 0, 178,8, 0, 4, 0, 0, 18, 200,72, 0, 0, 0, 10, 128,0, 0, 0, 96, 34, 8, 0, 4, 0, 224,255,140,109, 0, 0, 0, 0, 65, 7, 0, 0, 0, 50, 8, 0, 0, 0, 32, 34, 137,34, 0, 0, 0, 1, 65, 0, 0, 0, 128,8, 4, 16, 0, 0, 32, 205,132,69, 0, 0, 128,2, 0, 1, 0, 0, 128,16, 0, 0, 0, 0, 32, 2, 136,1, 0, 0, 0, 8, 4, 1, 0, 0, 32, 2, 8, 64, 4, 0, 0, 2, 0, 0, 0, 0, 0, 34, 128,32, 0, 0, 0, 4, 64, 33, 0, 0, 128,10, 140,0, 0, 0, 0, 6, 0, 1, 0, 0, 0, 36, 0, 32, 0, 0, 128,178,11, 0, 0, 0, 0, 50, 137,64, 0, 0, 0, 0, 0, 10, 0, 0, 32, 0, 128,1, 0, 0, 224,187,239,107, 0, 0, 0, 0, 68, 1, 0, 0, 32, 4, 132,65, 0, 0, 64, 41, 128,1, 4, 0, 32, 191,239,2, 0, 0, 32, 0, 198,37, 0, 0, 0, 20, 0, 0, 0, 0, 0, 50, 139,0, 0, 0, 64, 0, 1, 32, 0, 0, 0, 0, 0, 12, 0, 0, 32, 32, 0, 2, 0, 0, 0, 42, 0, 0, 0, 0, 32, 34, 0, 2, 0, 0, 32, 64, 9, 0, 0, 0, 32, 51, 136,1, 0, 0, 192,1, 193,1, 4, 0, 32, 34, 8, 0, 0, 0, 128,32, 64, 33, 0, 0, 128,2, 4, 1, 4, 0, 128,0, 64, 0, 0, 0, 0, 0, 1, 33, 0, 0, 32, 3, 136,0, 0, 0, 0, 2, 8, 1, 0, 0, 32, 50, 200,2, 0, 0, 0, 5, 0, 0, 0, 0, 64, 0, 2, 0, 0, 0, 160,50, 139,1, 0, 0, 160,2, 0, 1, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 68, 33, 0, 0, 64, 2, 136,0, 4, 0, 160,146,192,73, 4, 0, 32, 162,1, 34, 0, 0, 0, 0, 68, 32, 0, 0, 0, 32, 4, 32, 0, 0, 32, 0, 193,0, 0, 0, 128,2, 0, 32, 0, 0, 128,50, 201,32, 4, 0, 0, 0, 196,0, 0, 0, 128,17, 0, 0, 0, 0, 32, 40, 0, 0, 4, 0, 96, 2, 0, 0, 0, 0, 0, 0, 131,1, 0, 0, 32, 32, 129,0, 0, 0, 0, 2, 133,0, 0, 0, 128,0, 1, 1, 0, 0, 32, 2, 13, 0, 0, 0, 0, 136,7, 1, 0, 0, 224,255,223,85, 0, 0, 0, 12, 141,1, 0, 0, 64, 34, 1, 32, 0, 0, 128,0, 8, 0, 0, 0, 128,146,0, 34, 0, 0, 0, 22, 7, 0, 0, 0, 0, 35, 193,8, 0, 0, 0, 34, 1, 32, 0, 0, 128,0, 4, 16, 0, 0, 64, 153,197,0, 0, 0, 96, 34, 129,0, 0, 0, 192,34, 135,32, 4, 1, 224,95, 223,11, 0, 0, 32, 34, 9, 34, 0, 0, 64, 0, 136,32, 0, 0, 0, 34, 129,34, 0, 0, 64, 18, 0, 0, 4, 0, 0, 0, 192,0, 0, 0, 96, 171,137,33, 0, 0, 0, 8, 1, 1, 0, 0, 32, 2, 72, 32, 0, 0, 0, 0, 0, 40, 4, 1, 96, 34, 138,34, 0, 0, 64, 4, 0, 0, 0, 0, 160,10, 28, 1, 4, 0, 0, 0, 1, 0, 0, 0, 128,0, 0, 9, 4, 1, 64, 2, 0, 0, 0, 0, 64, 0, 0, 32, 0, 0, 0, 1, 0, 17, 0, 0, 0, 0, 10, 64, 0, 0, 32, 175,141,65, 0, 0, 32, 26, 139,0, 0, 0, 0, 0, 137,32, 0, 0, 128,27, 151,1, 0, 0, 0, 16, 0, 32, 0, 0, 32, 0, 24, 0, 0, 0, 128,139,78, 1, 0, 0, 32, 6, 1, 0, 4, 4, 192,35, 138,41, 0, 0, 0, 18, 133,0, 0, 0, 64, 34, 0, 0, 0, 0, 0, 33, 210,0, 4, 9, 0, 0, 128,0, 0, 0, 224,58, 186,1, 0, 0, 32, 18, 8, 2, 0, 0, 0, 0, 65, 16, 0, 0, 128,128,0, 0, 0, 0, 128,34, 133,65, 4, 1, 32, 34, 0, 32, 0, 0, 0, 34, 13, 32, 0, 0, 0, 0, 135,0, 0, 0, 32, 160,0, 66, 0, 0, 0, 128,4, 0, 0, 0, 0, 0, 35, 0, 0, 0, 192,185,199,99, 4, 1, 224,119,143,40, 0, 0, 0, 1, 196,1, 0, 0, 128,2, 4, 0, 0, 0, 0, 16, 10, 1, 0, 0, 0, 35, 0, 0, 0, 1, 0, 0, 132,0, 0, 0, 0, 0, 196,64, 4, 1, 0, 0, 199,32, 0, 0, 0, 1, 193,0, 0, 0, 128,135,132,1, 0, 0, 32, 144,8, 0, 4, 0, 0, 0, 192,1, 0, 0, 0, 36, 8, 0, 0, 0, 0, 34, 129,32, 0, 0, 32, 2, 0, 2, 0, 0, 128,178,144,65, 0, 0, 32, 16, 0, 65, 0, 0, 0, 0, 128,4, 0, 0, 0, 1, 5, 0, 4, 0, 0, 193,192,0, 0, 0, 160,51, 134,47, 0, 0, 128,128,0, 66, 0, 0, 128,1, 16, 1, 0, 0, 0, 129,2, 0, 0, 0, 128,8, 150,85, 0, 0, 0, 2, 16, 0, 0, 0, 128,137,4, 1, 0, 0, 0, 38, 133,0, 0, 1, 0, 34, 128,32, 0, 0, 0, 16, 128,1, 0, 0, 128,16, 0, 64, 0, 0, 224,153,142,59, 0, 0, 96, 50, 130,0, 0, 0, 64, 138,32, 0, 0, 0, 0, 1, 82, 0, 4, 0, 64, 58, 129,41, 0, 0, 32, 16, 0, 11, 0, 0, 64, 38, 160,0, 0, 0, 0, 40, 132,0, 0, 0, 64, 118,142,3, 0, 0, 64, 55, 198,3, 0, 0, 96, 0, 0, 0, 4, 15, 65, 71, 214,33, 0, 1, 0, 128,2, 1, 0, 0, 96, 2, 128,0, 0, 0, 0, 129,0, 1, 0, 0, 64, 0, 64, 1, 0, 4, 64, 0, 0, 0, 4, 0, 224,255,255,127, 0, 0, 0, 145,197,65, 4, 0, 0, 1, 2, 0, 4, 0, 0, 34, 194,8, 4, 0, 0, 50, 199,8, 4, 1, 224,163,201,2, 0, 0, 128,5, 0, 0, 0, 0, 0, 34, 136,32, 0, 0, 0, 2, 4, 1, 4, 1, 0, 2, 0, 0, 0, 0, 160,144,8, 0, 0, 0, 0, 2, 10, 0, 0, 0, 32, 179,77, 96, 0, 0, 0, 8, 133,32, 0, 0, 0, 6, 5, 0, 4, 0, 0, 8, 0, 0, 0, 0, 128,185,215,5, 0, 0, 0, 2, 0, 16, 0, 0, 0, 0, 7, 0, 0, 0, 64, 8, 132,0, 0, 0, 128,2, 24, 0, 0, 0, 128,8, 0, 0, 0, 0, 32, 27, 205,34, 0, 0, 128,0, 196,0, 0, 0, 0, 128,0, 64, 0, 0, 32, 34, 129,0, 0, 0, 32, 34, 79, 0, 0, 0, 128,0, 5, 1, 0, 0, 32, 0, 12, 0, 0, 0, 160,130,133,5, 0, 0, 32, 1, 64, 0, 0, 0, 0, 136,0, 0, 0, 0, 0, 2, 128,4, 0, 0, 32, 47, 143,97, 0, 0, 0, 34, 131,3, 0, 0, 0, 192,196,40, 0, 0, 64, 1, 0, 0, 0, 0, 128,2, 12, 5, 0, 0, 32, 34, 138,0, 0, 0, 0, 8, 132,0, 0, 0, 0, 18, 0, 1, 0, 0, 0, 34, 136,64, 0, 0, 0, 1, 68, 0, 0, 0, 96, 10, 2, 0, 4, 0, 32, 34, 136,0, 0, 0, 96, 255,140,103, 0, 0, 128,1, 214,13, 0, 0, 0, 0, 12, 64, 132,0, 32, 34, 128,34, 4, 0, 64, 17, 197,33, 0, 0, 0, 10, 64, 0, 0, 0, 0, 6, 5, 1, 0, 0, 128,0, 0, 32, 0, 0, 0, 42, 8, 0, 0, 0, 32, 0, 193,37, 0, 0, 0, 2, 0, 8, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 192,64, 0, 0, 192,186,222,97, 4, 0, 128,162,139,8, 0, 0, 64, 50, 0, 0, 0, 1, 32, 0, 128,0, 0, 0, 0, 50, 137,32, 0, 0, 64, 2, 192,0, 0, 0, 224,235,239,99, 0, 0, 64, 24, 4, 0, 0, 0, 32, 2, 13, 8, 0, 0, 32, 0, 197,0, 0, 0, 32, 129,199,32, 0, 0, 0, 50, 134,0, 0, 0, 160,0, 132,0, 0, 0, 0, 0, 74, 0, 0, 0, 0, 4, 132,10, 0, 0, 0, 8, 65, 0, 0, 0, 0, 2, 64, 8, 0, 0, 0, 16, 13, 64, 0, 0, 128,4, 150,0, 0, 0, 160,178,175,9, 0, 0, 128,128,64, 0, 0, 0, 32, 48, 8, 0, 4, 0, 32, 50, 2, 32, 0, 0, 0, 34, 72, 8, 0, 0, 160,146,204,67, 0, 0, 0, 18, 1, 0, 0, 0, 160,34, 137,0, 0, 0, 128,8, 4, 0, 4, 0, 0, 1, 128,0, 4, 0, 0, 2, 129,0, 0, 0, 128,175,231,45, 0, 0, 32, 0, 4, 1, 0, 0, 32, 3, 9, 9, 0, 0, 0, 0, 64, 37, 0, 0, 128,1, 0, 1, 0, 0, 96, 203,165,0, 0, 0, 0, 146,34, 0, 0, 0, 0, 50, 200,9, 0, 0, 0, 0, 37, 0, 0, 0, 32, 163,199,41, 0, 0, 96, 35, 128,2, 0, 0, 0, 10, 0, 0, 0, 0, 128,34, 129,16, 4, 0, 224,183,133,67, 4, 0, 32, 2, 128,0, 0, 0, 32, 0, 192,1, 4, 0, 0, 2, 0, 1, 0, 0, 32, 34, 10, 64, 0, 0, 0, 128,3, 0, 4, 2, 0, 0, 0, 0, 128,16, 32, 34, 72, 106, 4, 0, 224,191,199,127, 0, 0, 96, 18, 136,0, 0, 0, 160,178,0, 32, 0, 0, 64, 1, 193,40, 0, 0, 0, 2, 68, 1, 0, 0, 64, 0, 192,0, 4, 0, 64, 55, 129,41, 4, 0, 0, 0, 128,8, 0, 0, 32, 11, 128,2, 0, 0, 32, 0, 3, 0, 0, 0, 0, 1, 133,1, 0, 0, 0, 2, 129,0, 4, 0, 32, 34, 129,40, 0, 0, 64, 4, 192,0, 0, 0, 96, 18, 139,8, 0, 0, 160,183,140,35, 0, 0, 0, 8, 6, 0, 0, 0, 32, 34, 141,41, 0, 0, 0, 35, 197,8, 0, 0, 0, 0, 5, 8, 0, 0, 32, 8, 4, 0, 0, 0, 128,4, 8, 1, 0, 0, 64, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 128,54, 129,10, 4, 0, 32, 38, 0, 0, 0, 0, 128,16, 128,9, 0, 0, 128,11, 7, 1, 4, 0, 0, 3, 128,0, 0, 0, 0, 17, 197,0, 0, 0, 0, 0, 2, 32, 0, 1, 0, 0, 0, 1, 0, 0, 64, 0, 131,1, 0, 0, 32, 34, 1, 64, 0, 0, 32, 2, 5, 0, 0, 0, 160,187,207,111, 0, 0, 128,129,196,67, 0, 0, 0, 0, 128,9, 4, 0, 32, 39, 136,11, 0, 0, 64, 0, 0, 8, 0, 0, 0, 0, 65, 32, 0, 0, 32, 34, 129,1, 0, 0, 0, 0, 1, 16, 0, 0, 128,17, 197,64, 0, 0, 0, 34, 137,0, 0, 0, 0, 11, 132,1, 0, 0, 64, 130,1, 0, 0, 0, 0, 2, 6, 0, 0, 0, 128,3, 4, 1, 4, 1, 0, 0, 4, 0, 0, 0, 128,32, 70, 0, 0, 1, 0, 0, 137,0, 0, 0, 128,146,133,1, 0, 0, 0, 34, 3, 0, 0, 0, 0, 16, 8, 65, 0, 0, 0, 34, 128,1, 0, 0, 224,11, 143,81, 0, 1, 32, 1, 12, 1, 0, 0, 0, 18, 135,0, 0, 0, 0, 1, 65, 33, 0, 0, 32, 10, 0, 8, 4, 0, 32, 8, 132,0, 0, 0, 0, 18, 2, 0, 0, 0, 0, 4, 210,9, 0, 0, 0, 5, 196,0, 0, 0, 128,34, 129,0, 0, 0, 0, 2, 65, 1, 0, 0, 32, 58, 6, 0, 0, 0, 32, 2, 0, 1, 0, 0, 0, 2, 11, 8, 0, 0, 32, 48, 0, 0, 0, 0, 32, 18, 131,0, 0, 0, 128,171,133,1, 0, 0, 0, 8, 64, 32, 0, 0, 192,130,137,1, 0, 0, 32, 32, 8, 32, 0, 0, 0, 2, 9, 0, 0, 0, 0, 0, 1, 8, 0, 0, 160,34, 140,65, 4, 0, 32, 34, 13, 0, 0, 0, 224,175,159,123, 0, 0, 192,55, 132,42, 0, 0, 160,178,0, 0, 4, 0, 0, 50, 139,8, 0, 0, 96, 51, 198,33, 0, 0, 128,136,64, 0, 0, 0, 32, 1, 4, 0, 0, 0, 32, 130,0, 0, 0, 0, 96, 5, 1, 0, 0, 0, 128,139,132,88, 0, 0, 0, 144,8, 0, 0, 0, 128,129,2, 0, 0, 0, 0, 6, 128,0, 0, 0, 0, 178,132,1, 0, 0, 32, 2, 2, 0, 0, 0, 0, 18, 2, 65, 4, 0, 0, 16, 129,1, 0, 0, 0, 184,132,8, 0, 0, 64, 35, 132,0, 4, 1, 0, 38, 201,0, 0, 0, 0, 16, 1, 0, 0, 0, 192,63, 135,33, 0, 0, 160,146,0, 0, 0, 0, 128,8, 70, 0, 0, 0, 0, 24, 7, 0, 0, 0, 64, 38, 131,8, 0, 0, 32, 2, 1, 32, 0, 0, 128,0, 1, 2, 0, 0, 160,239,142,65, 0, 0, 32, 32, 65, 33, 0, 0, 0, 0, 8, 13, 0, 0, 128,16, 168,1, 0, 0, 0, 34, 0, 8, 0, 0, 32, 42, 8, 2, 0, 0, 160,131,142,0, 4, 1, 0, 1, 4, 1, 0, 0, 192,255,223,113, 0, 0, 96, 32, 65, 2, 0, 0, 32, 34, 1, 0, 0, 35, 0, 0, 128,0, 0, 0, 128,178,8, 96, 0, 0, 96, 18, 15, 9, 4, 0, 96, 50, 143,40, 0, 0, 0, 0, 69, 33, 0, 0, 0, 1, 192,40, 0, 0, 0, 130,0, 32, 4, 0, 32, 57, 199,72, 0, 0, 0, 128,16, 32, 0, 0, 32, 50, 0, 32, 4, 3, 64, 0, 128,0, 0, 0, 128,24, 197,1, 0, 0, 32, 33, 72, 1, 0, 0, 0, 132,8, 0, 0, 0, 32, 162,136,34, 0, 0, 0, 1, 64, 33, 4, 1, 64, 0, 0, 0, 4, 0, 96, 8, 139,0, 0, 0, 128,130,129,1, 0, 0, 0, 34, 128,2, 0, 0, 96, 236,139,1, 0, 0, 0, 16, 1, 32, 4, 0, 160,55, 154,42, 4, 0, 0, 32, 144,1, 0, 3, 0, 18, 0, 0, 4, 0, 64, 12, 146,0, 0, 0, 32, 0, 65, 32, 0, 0, 32, 162,128,34, 0, 0, 128,169,132,33, 0, 0, 32, 34, 65, 33, 0, 0, 160,18, 1, 0, 0, 0, 0, 50, 129,0, 0, 0, 224,191,223,105, 0, 0, 32, 32, 192,32, 0, 0, 0, 176,0, 64, 0, 0, 32, 38, 0, 0, 0, 0, 32, 42, 8, 0, 0, 0, 0, 0, 132,1, 0, 0, 32, 0, 133,0, 4, 0, 128,0, 0, 0, 0, 0, 32, 59, 75, 34, 0, 0, 0, 0, 205,1, 0, 0, 0, 32, 128,32, 0, 0, 0, 0, 138,32, 0, 0, 32, 50, 131,0, 0, 0, 0, 0, 67, 0, 0, 0, 0, 32, 128,40, 0, 0, 0, 0, 40, 0, 0, 0, 32, 50, 137,9, 0, 0, 0, 1, 193,33, 0, 0, 32, 0, 132,0, 0, 0, 0, 34, 136,0, 0, 0, 0, 3, 133,1, 0, 0, 0, 0, 1, 10, 0, 0, 0, 2, 9, 1, 0, 0, 128,34, 143,1, 0, 0, 224,178,131,41, 0, 0, 0, 0, 196,32, 0, 0, 96, 34, 14, 40, 0, 0, 0, 3, 193,32, 0, 0, 64, 2, 2, 8, 0, 0, 32, 34, 3, 0, 0, 0, 0, 0, 193,32, 0, 0, 0, 2, 13, 0, 0, 0, 160,146,197,65, 0, 0, 64, 32, 1, 32, 0, 1, 0, 130,0, 0, 4, 0, 32, 54, 207,0, 0, 0, 192,38, 130,40, 0, 0, 128,34, 136,0, 0, 0, 96, 0, 0, 64, 4, 0, 32, 162,136,32, 0, 0, 96, 190,223,75, 0, 0, 32, 9, 12, 2, 4, 64, 224,51, 239,107, 0, 0, 224,255,255,111, 4, 0, 96, 35, 201,0, 0, 0, 0, 1, 69, 1, 0, 0, 0, 0, 65, 64, 0, 0, 0, 64, 64, 0, 0, 0, 160,178,8, 33, 0, 0, 32, 35, 75, 40, 0, 0, 0, 10, 1, 64, 0, 0, 0, 8, 64, 1, 0, 0, 160,6, 0, 0, 0, 0, 0, 34, 64, 0, 0, 0, 0, 34, 64, 32, 0, 0, 0, 10, 5, 0, 0, 0, 32, 34, 12, 2, 0, 0, 160,0, 197,1, 4, 0, 224,191,79, 103, 0, 0, 64, 48, 70, 8, 0, 0, 32, 32, 8, 2, 0, 0, 32, 2, 0, 40, 0, 0, 32, 32, 8, 8, 0, 0, 64, 0, 196,32, 0, 0, 192,14, 134,17, 0, 0, 32, 4, 8, 0, 4, 0, 96, 34, 136,2, 0, 0, 0, 24, 196,9, 0, 0, 0, 73, 196,32, 0, 0, 0, 0, 136,32, 0, 0, 128,26, 132,0, 0, 0, 0, 0, 4, 10, 0, 0, 224,39, 218,2, 0, 0, 128,2, 64, 0, 0, 0, 32, 43, 8, 0, 0, 1, 0, 64, 75, 0, 4, 1, 0, 32, 137,1, 4, 0, 0, 0, 130,0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 2, 69, 0, 0, 0, 0, 8, 64, 2, 0, 0, 96, 38, 136,3, 0, 0, 32, 1, 193,0, 0, 0, 0, 0, 141,0, 0, 0, 0, 0, 129,4, 4, 0, 224,63, 156,39, 0, 0, 32, 1, 209,4, 4, 1, 0, 0, 0, 32, 0, 0, 0, 33, 0, 32, 0, 0, 0, 50, 64, 2, 0, 0, 32, 34, 73, 32, 0, 0, 128,0, 4, 2, 0, 0, 160,3, 129,0, 4, 0, 0, 0, 193,8, 4, 1, 160,0, 130,0, 0, 0, 0, 3, 145,69, 0, 0, 0, 1, 156,64, 0, 0, 64, 1, 4, 1, 0, 0, 0, 1, 193,32, 0, 0, 64, 0, 4, 64, 0, 0, 0, 0, 5, 1, 0, 0, 0, 2, 20, 36, 0, 0, 32, 34, 73, 42, 0, 0, 0, 2, 64, 32, 0, 0, 128,0, 17, 0, 4, 0, 160,34, 217,3, 0, 0, 192,1, 133,0, 0, 0, 128,17, 197,1, 0, 0, 128,0, 129,65, 0, 0, 0, 0, 128,33, 0, 0, 192,0, 68, 65, 0, 0, 160,0, 8, 0, 0, 0, 0, 48, 1, 2, 4, 0, 32, 32, 8, 2, 0, 0, 32, 0, 1, 1, 4, 0, 224,255,223,47, 0, 0, 192,1, 134,13, 0, 0, 0, 64, 1, 0, 0, 0, 0, 9, 8, 64, 0, 0, 0, 64, 65, 2, 0, 0, 0, 8, 2, 0, 4, 0, 96, 34, 136,42, 0, 0, 32, 14, 15, 0, 0, 0, 0, 8, 1, 4, 0, 0, 0, 0, 132,68, 0, 1, 64, 15, 133,41, 0, 0, 224,11, 133,1, 0, 0, 32, 40, 8, 0, 4, 0, 96, 34, 137,35, 4, 0, 128,0, 4, 0, 0, 0, 0, 2, 4, 41, 0, 0, 0, 2, 133,1, 0, 0, 128,0, 132,73, 0, 0, 64, 2, 136,1, 4, 0, 0, 0, 13, 0, 0, 0, 32, 0, 69, 16, 0, 0, 0, 144,1, 0, 0, 0, 0, 1, 4, 2, 0, 0, 0, 24, 1, 1, 0, 0, 32, 8, 193,37, 0, 0, 128,2, 4, 4, 0, 0, 64, 1, 133,107, 4, 0, 32, 34, 129,34, 0, 0, 32, 10, 0, 0, 4, 0, 0, 2, 4, 0, 0, 0, 0, 1, 68, 1, 0, 0, 0, 9, 197,8, 0, 0, 128,0, 8, 64, 0, 0, 32, 96, 77, 33, 0, 0, 160,10, 141,69, 0, 0, 0, 0, 133,73, 0, 0, 0, 2, 8, 9, 0, 0, 32, 50, 203,40, 0, 0, 0, 11, 69, 0, 4, 1, 0, 0, 1, 0, 0, 0, 0, 8, 12, 2, 0, 0, 32, 0, 129,1, 0, 0, 224,179,157,43, 0, 0, 192,9, 197,70, 0, 0, 32, 0, 8, 32, 0, 0, 32, 1, 8, 0, 0, 0, 64, 1, 193,32, 0, 0, 32, 38, 10, 8, 0, 0, 0, 1, 68, 8, 0, 0, 32, 2, 15, 1, 0, 0, 32, 34, 10, 33, 0, 0, 0, 1, 68, 3, 0, 0, 0, 0, 193,35, 0, 0, 64, 0, 131,0, 0, 0, 0, 3, 12, 3, 0, 0, 32, 34, 205,0, 0, 0, 0, 8, 5, 0, 0, 0, 0, 0, 4, 8, 0, 0, 64, 0, 134,0, 4, 1, 160,55, 207,9, 0, 0, 128,0, 213,1, 0, 0, 0, 0, 9, 2, 0, 0, 32, 37, 8, 32, 0, 0, 0, 32, 128,2, 0, 2, 0, 34, 0, 32, 0, 0, 128,2, 0, 64, 0, 0, 0, 34, 144,8, 0, 0, 160,34, 73, 32, 0, 0, 0, 32, 128,1, 0, 0, 0, 34, 4, 0, 0, 0, 0, 128,0, 2, 0, 0, 32, 34, 9, 32, 0, 0, 192,13, 129,1, 0, 0, 0, 2, 66, 1, 0, 0, 0, 167,0, 0, 0, 0, 64, 34, 0, 1, 0, 0, 32, 34, 12, 0, 0, 0, 0, 8, 69, 64, 0, 0, 128,34, 65, 2, 0, 0, 0, 8, 0, 2, 0, 0, 32, 1, 198,33, 0, 0, 160,0, 4, 1, 0, 0, 0, 32, 0, 33, 0, 0, 160,2, 11, 9, 0, 0, 32, 0, 68, 1, 0, 0, 32, 34, 0, 64, 4, 0, 192,2, 0, 0, 0, 1, 128,0, 0, 0, 0, 0, 224,111,223,69, 0, 0, 128,50, 0, 0, 0, 0, 32, 34, 1, 32, 0, 0, 128,64, 0, 0, 0, 0, 64, 1, 196,0, 0, 0, 224,0, 132,2, 0, 0, 32, 3, 0, 0, 0, 0, 96, 34, 154,2, 0, 0, 32, 41, 128,1, 0, 0, 0, 6, 144,0, 0, 0, 224,35, 78, 7, 0, 0, 224,0, 2, 64, 0, 0, 32, 32, 128,0, 0, 0, 160,2, 0, 0, 0, 0, 0, 0, 68, 64, 0, 0, 32, 2, 144,1, 0, 0, 32, 0, 20, 0, 0, 16, 32, 50, 75, 34, 0, 0, 192,191,223,103, 0, 0, 0, 146,8, 0, 4, 0, 64, 3, 0, 8, 0, 0, 0, 38, 0, 0, 0, 0, 0, 1, 198,1, 4, 0, 0, 2, 130,0, 0, 0, 0, 166,11, 2, 0, 0, 0, 12, 0, 0, 0, 0, 64, 3, 29, 0, 0, 1, 32, 32, 143,0, 0, 0, 160,43, 5, 3, 0, 1, 0, 0, 129,32, 0, 0, 32, 2, 65, 0, 4, 0, 0, 34, 0, 0, 0, 0, 32, 34, 192,0, 0, 0, 32, 35, 151,3, 0, 0, 0, 2, 1, 34, 0, 0, 224,43, 95, 1, 4, 0, 0, 32, 128,0, 0, 0, 0, 34, 8, 2, 0, 0, 0, 0, 136,1, 0, 0, 0, 2, 130,0, 0, 0, 32, 34, 136,33, 0, 0, 64, 2, 132,33, 0, 1, 0, 2, 0, 9, 4, 3, 0, 0, 128,0, 0, 0, 0, 34, 192,0, 0, 0, 0, 34, 138,1, 4, 1, 0, 1, 192,0, 0, 0, 32, 22, 130,1, 0, 0, 64, 96, 0, 1, 0, 0, 128,23, 132,4, 0, 0, 160,55, 239,111, 0, 0, 0, 33, 16, 1, 0, 0, 32, 50, 136,10, 4, 0, 64, 34, 155,2, 0, 0, 192,128,208,5, 0, 0, 64, 0, 128,32, 0, 0, 0, 38, 129,32, 0, 0, 0, 34, 2, 32, 0, 1, 192,1, 130,0, 0, 0, 0, 2, 129,1, 0, 0, 32, 42, 12, 0, 0, 0, 0, 32, 64, 2, 0, 0, 0, 0, 0, 37, 0, 0, 32, 34, 79, 34, 0, 0, 160,2, 129,0, 0, 0, 64, 4, 0, 8, 0, 0, 96, 18, 133,9, 0, 0, 64, 34, 9, 0, 0, 0, 0, 2, 132,32, 0, 1, 0, 0, 2, 1, 0, 0, 32, 2, 64, 32, 0, 0, 0, 0, 193,64, 0, 0, 64, 34, 132,32, 0, 0, 160,159,255,71, 0, 0, 0, 10, 0, 1, 4, 0, 32, 34, 129,32, 4, 1, 0, 34, 144,0, 0, 0, 0, 132,134,0, 4, 0, 0, 4, 128,0, 0, 0, 0, 8, 9, 0, 0, 0, 128,35, 129,1, 4, 0, 64, 50, 193,0, 0, 0, 0, 34, 128,34, 0, 0, 32, 2, 20, 0, 0, 0, 160,138,156,0, 4, 0, 0, 0, 2, 1, 0, 0, 0, 1, 0, 2, 4, 0, 0, 0, 147,0, 0, 0, 0, 1, 64, 8, 0, 0, 128,32, 24, 0, 0, 0, 0, 18, 11, 2, 0, 0, 128,0, 8, 9, 0, 0, 0, 34, 0, 34, 0, 0, 160,226,255,46, 0, 0, 0, 0, 192,8, 0, 0, 32, 0, 16, 0, 0, 0, 0, 34, 208,0, 0, 0, 0, 3, 0, 2, 0, 0, 0, 33, 4, 1, 0, 0, 0, 34, 8, 98, 4, 1, 192,0, 146,1, 4, 0, 32, 34, 143,32, 0, 0, 96, 34, 128,0, 0, 0, 32, 2, 140,0, 0, 0, 0, 2, 128,33, 0, 0, 0, 4, 20, 0, 0, 0, 0, 0, 134,0, 0, 0, 224,40, 95, 33, 4, 1, 0, 34, 129,32, 0, 0, 96, 0, 147,0, 0, 0, 0, 128,16, 0, 4, 0, 0, 34, 2, 8, 0, 0, 0, 2, 4, 64, 0, 0, 224,15, 223,7, 4, 0, 64, 0, 78, 0, 0, 0, 128,4, 0, 0, 0, 0, 32, 1, 1, 0, 0, 0, 160,50, 0, 0, 0, 0, 128,0, 4, 8, 0, 0, 0, 64, 196,0, 0, 0, 128,32, 1, 0, 0, 0, 32, 32, 10, 0, 0, 0, 160,3, 36, 1, 0, 0, 0, 48, 8, 0, 0, 0, 32, 34, 64, 32, 0, 0, 0, 16, 72, 0, 0, 0, 128,4, 1, 1, 0, 0, 0, 16, 88, 0, 0, 0, 160,34, 104,2, 0, 0, 32, 0, 1, 2, 0, 0, 0, 32, 131,0, 4, 0, 0, 2, 192,0, 0, 0, 128,4, 128,4, 0, 0, 32, 10, 5, 0, 0, 0, 0, 48, 0, 2, 0, 0, 128,128,140,1, 0, 0, 32, 34, 64, 33, 4, 0, 0, 0, 0, 8, 0, 0, 64, 5, 1, 0, 0, 0, 0, 32, 128,8, 0, 0, 32, 0, 128,65, 0, 0, 32, 34, 8, 35, 0, 0, 224,47, 214,63, 0, 0, 0, 4, 1, 0, 0, 0, 0, 10, 8, 2, 0, 0, 64, 0, 66, 0, 0, 0, 0, 3, 2, 4, 0, 0, 64, 0, 154,0, 0, 0, 128,137,0, 1, 0, 0, 0, 16, 16, 1, 0, 0, 32, 163,12, 32, 4, 0, 0, 0, 4, 33, 0, 0, 192,5, 132,1, 0, 0, 32, 16, 0, 32, 0, 0, 64, 18, 152,0, 0, 0, 0, 34, 75, 32, 0, 0, 128,6, 0, 0, 0, 0, 0, 34, 136,1, 0, 0, 0, 0, 133,0, 4, 0, 192,0, 146,9, 0, 0, 32, 0, 72, 2, 0, 0, 160,46, 223,13, 0, 0, 0, 0, 68, 5, 0, 0, 0, 34, 129,2, 0, 0, 0, 24, 0, 0, 0, 0, 128,0, 128,65, 0, 0, 128,1, 4, 0, 0, 0, 0, 0, 80, 1, 0, 0, 0, 168,2, 0, 0, 0, 192,6, 182,69, 0, 0, 0, 34, 132,0, 4, 0, 0, 18, 136,0, 0, 0, 96, 2, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 160,170,0, 1, 0, 0, 32, 34, 130,32, 0, 0, 64, 0, 144,0, 0, 0, 224,171,220,47, 4, 0, 0, 34, 128,8, 0, 0, 128,34, 0, 1, 0, 0, 0, 34, 128,3, 32, 0, 0, 1, 197,3, 0, 0, 0, 48, 128,1, 0, 0, 0, 9, 128,1, 4, 0, 1, 34, 128,32, 0, 0, 32, 3, 132,0, 4, 0, 0, 38, 128,0, 0, 0, 192,142,198,1, 4, 0, 64, 16, 128,0, 0, 0, 0, 138,0, 0, 0, 0, 96, 1, 6, 0, 0, 8, 64, 0, 0, 0, 4, 0, 224,255,223,127, 0, 0, 128,129,197,17, 0, 1, 0, 34, 2, 0, 0, 0, 32, 42, 1, 0, 0, 0, 0, 34, 132,1, 0, 0, 96, 34, 73, 42, 0, 0, 0, 32, 65, 0, 0, 0, 0, 0, 80, 32, 0, 0, 160,144,8, 2, 0, 0, 128,32, 4, 0, 0, 0, 96, 6, 215,33, 0, 0, 32, 4, 0, 0, 0, 0, 160,0, 12, 0, 0, 0, 32, 35, 5, 32, 0, 0, 0, 1, 208,16, 0, 0, 0, 1, 69, 18, 0, 0, 0, 2, 69, 32, 4, 0, 0, 16, 128,0, 0, 0, 96, 59, 133,0, 0, 0, 32, 34, 76, 0, 0, 0, 32, 2, 196,0, 0, 0, 32, 0, 136,0, 0, 0, 0, 4, 69, 1, 0, 0, 224,47, 191,47, 0, 0, 0, 33, 196,13, 0, 0, 0, 0, 72, 2, 0, 0, 32, 2, 8, 34, 0, 0, 0, 16, 9, 0, 0, 0, 64, 34, 213,8, 4, 0, 0, 10, 199,33, 0, 0, 128,0, 132,1, 4, 0, 32, 1, 128,0, 0, 0, 32, 34, 10, 34, 0, 0, 64, 9, 80, 65, 0, 0, 64, 3, 128,0, 0, 0, 160,11, 69, 33, 0, 0, 128,11, 132,4, 0, 0, 0, 32, 8, 1, 0, 0, 0, 8, 198,2, 0, 0, 0, 34, 12, 32, 0, 0, 32, 2, 128,64, 4, 0, 32, 38, 128,0, 0, 8, 0, 18, 202,0, 4, 0, 224,38, 219,1, 0, 0, 32, 34, 136,2, 0, 0, 32, 42, 0, 0, 0, 0, 96, 41, 193,3, 0, 0, 0, 3, 64, 0, 0, 0, 128,1, 128,1, 0, 0, 0, 2, 132,1, 0, 0, 0, 2, 145,8, 0, 0, 128,0, 172,1, 0, 0, 32, 50, 73, 3, 0, 0, 128,1, 228,1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 2, 17, 0, 0, 0, 1, 129,0, 0, 0, 0, 0, 132,2, 0, 0, 0, 34, 0, 3, 4, 0, 224,239,237,111, 0, 0, 0, 0, 68, 9, 0, 0, 32, 33, 212,1, 4, 0, 0, 0, 136,0, 0, 0, 128,0, 64, 2, 0, 0, 0, 34, 72, 2, 0, 0, 0, 0, 199,0, 0, 0, 128,2, 2, 1, 4, 0, 32, 34, 128,0, 0, 0, 128,36, 0, 0, 0, 0, 0, 1, 192,1, 0, 0, 0, 9, 196,1, 0, 0, 32, 3, 73, 0, 0, 0, 0, 32, 2, 8, 0, 0, 0, 8, 68, 0, 0, 0, 0, 2, 1, 8, 0, 0, 0, 1, 1, 32, 0, 0, 0, 0, 65, 2, 0, 0, 160,1, 65, 1, 0, 0, 128,0, 12, 0, 0, 0, 128,6, 20, 4, 0, 0, 0, 32, 69, 0, 0, 0, 160,34, 25, 3, 0, 0, 128,0, 100,0, 0, 0, 0, 9, 128,0, 0, 0, 128,0, 0, 65, 0, 0, 0, 2, 3, 0, 0, 0, 0, 34, 18, 0, 0, 0, 128,40, 2, 0, 0, 0, 0, 16, 198,16, 0, 0, 0, 42, 0, 2, 0, 0, 0, 32, 192,2, 0, 0, 192,33, 208,0, 4, 0, 0, 34, 136,0, 0, 0, 32, 0, 65, 0, 0, 0, 64, 0, 0, 5, 0, 0, 0, 8, 128,33, 0, 0, 128,0, 5, 0, 0, 0, 128,169,87, 5, 4, 0, 64, 34, 128,0, 0, 0, 224,59, 135,0, 0, 0, 0, 1, 12, 0, 0, 0, 160,6, 192,1, 4, 1, 32, 0, 130,8, 4, 0, 32, 50, 145,35, 0, 0, 0, 8, 0, 4, 0, 0, 0, 20, 144,0, 4, 0, 128,32, 64, 0, 0, 0, 224,163,223,111, 0, 0, 0, 1, 3, 65, 0, 0, 32, 34, 72, 32, 0, 0, 0, 32, 1, 1, 0, 0, 32, 34, 205,35, 0, 0, 0, 32, 193,0, 0, 0, 64, 0, 4, 70, 0, 0, 32, 2, 5, 33, 0, 0, 32, 10, 132,0, 0, 0, 32, 2, 4, 1, 0, 0, 0, 34, 194,34, 0, 0, 0, 0, 136,41, 0, 0, 64, 55, 130,42, 0, 0, 32, 65, 65, 33, 0, 0, 32, 0, 136,2, 0, 0, 128,2, 132,0, 0, 0, 0, 2, 200,2, 0, 0, 0, 1, 9, 0, 0, 0, 160,33, 193,32, 0, 0, 128,11, 4, 4, 0, 0, 0, 40, 64, 2, 0, 0, 0, 34, 77, 0, 0, 0, 0, 0, 192,80, 128,0, 0, 0, 0, 1, 0, 0, 224,42, 151,41, 0, 0, 0, 0, 145,0, 0, 0, 32, 34, 141,2, 0, 0, 0, 8, 133,0, 0, 0, 32, 50, 206,33, 0, 0, 32, 34, 75, 0, 0, 0, 128,47, 213,69, 0, 0, 0, 1, 137,1, 4, 0, 32, 0, 4, 0, 0, 0, 32, 0, 0, 4, 0, 0, 128,32, 128,1, 4, 0, 0, 0, 11, 0, 4, 0, 0, 34, 11, 0, 4, 0, 0, 35, 193,32, 0, 0, 32, 36, 154,0, 0, 0, 160,32, 132,37, 4, 0, 0, 50, 204,40, 0, 0, 0, 1, 202,0, 4, 0, 0, 2, 136,0, 0, 0, 128,2, 136,3, 4, 0, 32, 35, 128,3, 4, 0, 96, 59, 139,0, 0, 0, 0, 3, 69, 0, 4, 0, 0, 2, 8, 0, 0, 0, 32, 130,13, 1, 0, 0, 96, 32, 8, 96, 0, 0, 192,45, 215,111, 0, 0, 64, 40, 129,1, 0, 0, 128,130,64, 0, 0, 0, 0, 50, 145,0, 0, 0, 0, 64, 128,0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 26, 9, 0, 0, 0, 64, 8, 5, 0, 4, 0, 0, 18, 8, 2, 0, 0, 0, 2, 18, 0, 0, 0, 192,167,142,8, 4, 0, 0, 0, 192,32, 0, 0, 0, 0, 152,0, 0, 0, 128,18, 128,0, 0, 1, 0, 35, 0, 0, 0, 0, 0, 4, 129,0, 0, 0, 0, 1, 64, 32, 0, 0, 128,0, 140,1, 0, 1, 64, 0, 2, 0, 0, 0, 160,59, 190,13, 0, 0, 128,136,198,1, 4, 0, 0, 34, 144,32, 0, 0, 0, 2, 10, 32, 0, 0, 128,137,209,0, 4, 0, 0, 34, 136,32, 0, 0, 32, 10, 8, 0, 0, 0, 128,0, 144,9, 0, 0, 0, 1, 0, 65, 0, 0, 0, 2, 2, 40, 0, 0, 224,139,151,5, 0, 0, 0, 10, 1, 0, 0, 0, 0, 34, 146,0, 0, 0, 160,34, 155,9, 0, 0, 32, 0, 2, 32, 0, 0, 128,34, 0, 65, 0, 0, 160,174,191,123, 0, 0, 128,146,8, 0, 0, 0, 0, 4, 8, 1, 0, 0, 0, 0, 128,16, 0, 0, 32, 34, 17, 8, 0, 0, 160,162,0, 34, 0, 0, 64, 178,129,9, 0, 0, 224,62, 79, 8, 0, 0, 32, 18, 8, 0, 0, 0, 128,18, 0, 65, 0, 0, 128,0, 148,1, 4, 0, 64, 39, 133,1, 0, 0, 128,35, 151,81, 0, 0, 96, 4, 0, 0, 0, 0, 0, 3, 8, 2, 0, 0, 0, 2, 1, 1, 0, 0, 128,128,128,0, 0, 0, 64, 2, 83, 8, 0, 0, 0, 0, 129,32, 0, 0, 32, 18, 8, 1, 0, 0, 128,18, 0, 0, 0, 0, 0, 6, 1, 0, 0, 0, 96, 35, 152,1, 0, 0, 32, 36, 0, 2, 0, 0, 224,111,223,53, 0, 0, 160,176,0, 2, 0, 0, 32, 67, 6, 8, 0, 0, 0, 1, 197,5, 0, 0, 192,176,27, 13, 0, 0, 0, 34, 8, 34, 0, 0, 0, 8, 132,1, 0, 0, 0, 32, 136,2, 0, 0, 192,32, 146,2, 0, 0, 32, 27, 5, 33, 0, 0, 0, 12, 4, 0, 0, 0, 192,34, 152,0, 0, 0, 224,39, 199,69, 0, 0, 0, 1, 132,1, 0, 0, 0, 2, 12, 1, 0, 0, 0, 3, 192,32, 0, 0, 128,0, 128,1, 0, 0, 32, 32, 140,0, 0, 0, 64, 1, 128,0, 0, 0, 32, 48, 144,3, 4, 0, 224,50, 201,9, 0, 0, 0, 4, 16, 0, 0, 0, 0, 4, 0, 2, 0, 0, 0, 52, 0, 0, 0, 0, 0, 50, 128,0, 0, 0, 224,8, 215,65, 0, 0, 0, 0, 20, 1, 0, 0, 0, 18, 64, 0, 0, 0, 32, 32, 128,2, 0, 0, 0, 64, 80, 0, 4, 32, 33, 123,207,111, 0, 0, 32, 18, 24, 32, 0, 0, 0, 24, 199,0, 0, 0, 128,8, 0, 1, 4, 0, 192,191,255,111, 0, 0, 96, 2, 76, 2, 0, 0, 32, 178,120,34, 4, 2, 32, 35, 129,32, 4, 1, 0, 0, 136,0, 0, 0, 0, 1, 2, 8, 0, 0, 0, 32, 72, 32, 0, 0, 32, 10, 15, 2, 0, 0, 64, 0, 132,0, 0, 0, 64, 186,162,0, 0, 0, 0, 8, 231,64, 0, 0, 32, 2, 128,1, 4, 1, 0, 1, 0, 0, 0, 0, 224,170,219,99, 0, 0, 0, 64, 204,32, 0, 0, 32, 0, 5, 0, 0, 0, 32, 34, 2, 32, 0, 0, 0, 32, 128,4, 0, 0, 96, 34, 222,0, 0, 0, 0, 8, 196,1, 4, 0, 160,191,143,103, 4, 0, 0, 18, 0, 0, 0, 0, 32, 6, 72, 34, 0, 0, 0, 144,129,1, 4, 0, 0, 4, 136,0, 0, 0, 160,130,129,1, 4, 1, 32, 66, 137,0, 0, 0, 0, 130,8, 0, 0, 0, 64, 34, 8, 0, 0, 0, 0, 34, 8, 66, 0, 0, 0, 48, 88, 64, 0, 0, 224,171,205,45, 0, 0, 0, 1, 133,0, 0, 0, 32, 2, 140,3, 0, 0, 32, 2, 207,0, 0, 0, 0, 2, 4, 32, 0, 0, 32, 35, 1, 0, 0, 0, 128,24, 0, 0, 0, 0, 128,8, 132,0, 0, 0, 0, 18, 8, 1, 0, 0, 0, 18, 130,33, 0, 0, 32, 58, 128,1, 0, 0, 64, 34, 4, 8, 4, 0, 32, 34, 200,65, 4, 0, 0, 1, 193,0, 0, 0, 64, 11, 215,65, 0, 0, 0, 3, 0, 1, 4, 0, 0, 0, 128,32, 0, 0, 32, 34, 12, 32, 4, 1, 128,1, 132,32, 0, 0, 160,11, 133,0, 4, 1, 96, 2, 128,0, 0, 0, 0, 139,141,0, 4, 0, 224,19, 133,9, 0, 1, 0, 1, 0, 0, 0, 64, 224,255,255,127, 0, 0, 128,141,229,5, 0, 0, 64, 18, 211,40, 4, 0, 64, 34, 130,33, 4, 0, 32, 11, 132,0, 0, 0, 64, 35, 131,33, 0, 0, 0, 16, 137,0, 4, 33, 64, 2, 129,8, 4, 0, 96, 34, 77, 3, 0, 0, 128,0, 64, 3, 0, 0, 0, 32, 64, 1, 0, 0, 0, 32, 64, 32, 0, 0, 32, 8, 68, 0, 0, 0, 32, 34, 197,8, 0, 0, 128,32, 0, 32, 0, 0, 32, 178,73, 2, 0, 0, 0, 5, 214,33, 0, 0, 160,32, 151,0, 0, 0, 160,0, 0, 1, 0, 0, 192,1, 146,0, 0, 0, 32, 2, 130,0, 0, 0, 0, 1, 207,35, 0, 0, 0, 0, 80, 16, 0, 0, 0, 2, 0, 34, 0, 0, 32, 2, 16, 0, 0, 0, 0, 34, 66, 2, 0, 0, 64, 0, 8, 4, 0, 0, 96, 201,215,69, 0, 0, 0, 2, 192,0, 0, 0, 128,2, 1, 1, 0, 0, 0, 50, 2, 0, 0, 0, 0, 2, 128,65, 0, 0, 160,38, 73, 35, 0, 0, 128,0, 3, 66, 0, 0, 160,0, 77, 0, 0, 0, 192,1, 128,0, 0, 0, 224,2, 5, 0, 0, 0, 0, 2, 200,0, 0, 0, 0, 11, 66, 0, 0, 0, 32, 34, 70, 2, 0, 0, 0, 2, 196,2, 0, 0, 0, 0, 12, 32, 0, 0, 0, 18, 4, 0, 0, 0, 160,5, 128,77, 0, 0, 32, 2, 30, 32, 0, 0, 32, 0, 192,36, 0, 0, 0, 4, 0, 32, 0, 0, 64, 25, 196,1, 0, 0, 128,34, 192,96, 0, 0, 128,2, 64, 4, 0, 0, 32, 130,9, 0, 4, 0, 224,191,223,71, 0, 0, 128,60, 223,47, 0, 0, 0, 2, 72, 32, 0, 0, 32, 0, 64, 2, 0, 0, 160,11, 8, 32, 0, 0, 32, 40, 10, 0, 0, 0, 32, 1, 0, 32, 0, 0, 128,10, 12, 1, 0, 0, 224,8, 207,68, 0, 0, 0, 3, 32, 0, 0, 0, 32, 48, 1, 0, 0, 0, 32, 34, 128,32, 4, 0, 32, 35, 0, 32, 0, 0, 0, 0, 80, 0, 0, 0, 32, 34, 196,98, 0, 0, 96, 42, 130,0, 0, 0, 32, 16, 192,0, 0, 0, 32, 48, 72, 0, 0, 0, 128,41, 132,16, 0, 0, 160,32, 0, 0, 0, 0, 224,162,155,34, 0, 0, 0, 40, 196,33, 0, 0, 160,162,128,33, 0, 0, 32, 0, 205,97, 0, 0, 128,34, 136,32, 0, 0, 160,0, 8, 2, 0, 0, 128,0, 197,0, 0, 0, 128,144,0, 0, 0, 0, 128,8, 213,35, 4, 0, 64, 33, 136,33, 0, 0, 224,187,143,101, 0, 0, 0, 2, 65, 2, 0, 0, 160,130,194,65, 0, 0, 0, 18, 136,1, 0, 0, 0, 16, 68, 33, 0, 0, 64, 8, 14, 34, 0, 0, 0, 34, 11, 0, 0, 0, 32, 34, 201,34, 0, 0, 32, 34, 89, 3, 0, 0, 0, 1, 201,66, 0, 0, 0, 34, 2, 2, 0, 0, 0, 0, 160,0, 0, 0, 128,1, 68, 8, 0, 0, 0, 0, 197,1, 0, 0, 96, 183,222,35, 0, 0, 0, 32, 134,0, 0, 0, 0, 136,7, 4, 0, 0, 128,1, 132,5, 0, 0, 192,8, 132,2, 0, 0, 128,10, 132,0, 0, 0, 224,247,223,5, 0, 0, 0, 32, 5, 2, 0, 0, 0, 18, 72, 0, 0, 0, 0, 32, 0, 5, 0, 0, 32, 32, 12, 0, 0, 0, 0, 32, 64, 104, 0, 0, 0, 9, 195,0, 0, 0, 0, 96, 128,1, 0, 0, 0, 0, 65, 3, 0, 0, 192,0, 128,0, 0, 0, 32, 50, 74, 9, 0, 0, 0, 40, 194,0, 0, 0, 0, 0, 4, 18, 0, 0, 0, 34, 3, 32, 0, 0, 128,2, 16, 17, 0, 0, 0, 0, 197,2, 4, 0, 0, 0, 0, 32, 0, 0, 0, 8, 0, 32, 0, 0, 0, 8, 1, 16, 0, 0, 0, 3, 148,0, 0, 0, 160,2, 141,5, 4, 32, 64, 35, 131,0, 0, 0, 128,2, 140,0, 0, 0, 32, 0, 8, 1, 0, 0, 0, 34, 197,35, 0, 0, 96, 50, 136,32, 0, 0, 32, 0, 69, 32, 0, 0, 0, 2, 69, 1, 0, 0, 32, 2, 0, 64, 0, 0, 0, 0, 17, 0, 0, 0, 224,239,223,87, 0, 0, 64, 8, 223,65, 0, 2, 0, 0, 128,0, 0, 0, 32, 10, 4, 0, 0, 0, 96, 34, 201,0, 0, 0, 32, 178,8, 3, 0, 0, 0, 0, 82, 0, 0, 0, 0, 32, 16, 0, 0, 0, 128,1, 193,32, 4, 1, 0, 51, 131,40, 0, 0, 0, 97, 12, 32, 0, 0, 192,157,215,67, 0, 0, 0, 2, 66, 0, 0, 0, 128,161,136,0, 0, 0, 160,34, 192,64, 0, 0, 32, 38, 65, 2, 0, 0, 0, 34, 1, 2, 0, 0, 0, 2, 68, 32, 0, 0, 96, 58, 198,0, 0, 0, 32, 0, 74, 3, 0, 1, 0, 32, 1, 0, 0, 0, 96, 171,11, 11, 0, 0, 0, 0, 11, 33, 0, 0, 32, 34, 138,32, 0, 0, 64, 16, 66, 4, 0, 0, 0, 16, 68, 32, 0, 0, 0, 8, 12, 0, 0, 0, 128,0, 1, 65, 0, 0, 32, 34, 155,10, 0, 0, 0, 40, 193,3, 0, 0, 128,32, 0, 1, 0, 0, 0, 0, 204,0, 0, 0, 128,0, 4, 1, 0, 0, 32, 186,140,8, 0, 0, 0, 16, 84, 1, 0, 0, 0, 19, 192,0, 0, 0, 64, 19, 139,42, 0, 0, 0, 3, 192,0, 0, 0, 128,0, 4, 65, 0, 0, 0, 0, 212,16, 0, 0, 32, 50, 153,0, 0, 0, 0, 0, 137,0, 0, 0, 0, 0, 4, 64, 0, 0, 0, 163,9, 3, 4, 0, 32, 0, 128,32, 0, 0, 224,191,255,1, 0, 0, 64, 9, 213,4, 0, 0, 0, 1, 8, 1, 0, 0, 32, 50, 201,2, 0, 0, 0, 0, 71, 7, 0, 0, 0, 3, 16, 1, 0, 0, 32, 34, 14, 8, 0, 0, 32, 0, 192,4, 0, 0, 0, 28, 0, 1, 0, 0, 128,1, 0, 32, 0, 0, 0, 0, 5, 32, 0, 1, 0, 0, 1, 8, 0, 0, 64, 0, 68, 8, 0, 0, 0, 41, 0, 0, 0, 0, 32, 2, 73, 2, 0, 0, 0, 0, 16, 4, 0, 0, 128,1, 68, 0, 0, 0, 0, 1, 22, 0, 0, 0, 0, 4, 5, 0, 0, 0, 192,0, 0, 0, 0, 0, 128,0, 138,67, 0, 0, 64, 2, 74, 32, 0, 0, 32, 34, 8, 22, 0, 1, 0, 0, 128,1, 0, 0, 160,42, 0, 0, 0, 0, 32, 0, 8, 64, 4, 0, 224,251,255,127, 0, 0, 64, 128,4, 0, 0, 0, 64, 162,201,32, 0, 0, 0, 128,64, 32, 0, 0, 32, 32, 8, 34, 4, 0, 0, 178,128,3, 0, 0, 0, 41, 200,8, 0, 1, 64, 1, 68, 0, 4, 0, 64, 8, 208,1, 0, 0, 0, 36, 196,0, 4, 1, 224,29, 146,8, 0, 0, 0, 42, 8, 32, 0, 0, 0, 2, 198,0, 0, 0, 0, 8, 69, 1, 0, 0, 0, 162,8, 0, 0, 0, 224,35, 159,1, 0, 0, 128,0, 198,64, 4, 0, 32, 54, 136,32, 0, 0, 0, 17, 128,0, 0, 0, 96, 162,24, 0, 0, 0, 0, 40, 4, 0, 0, 0, 0, 130,64, 0, 0, 0, 0, 32, 196,0, 0, 0, 32, 42, 168,32, 0, 0, 128,128,40, 0, 4, 0, 160,251,143,99, 0, 0, 0, 25, 1, 5, 0, 0, 0, 8, 193,33, 0, 0, 128,0, 128,5, 4, 1, 0, 0, 4, 1, 4, 0, 32, 0, 0, 32, 0, 0, 0, 28, 64, 12, 0, 0, 128,130,0, 0, 0, 0, 64, 157,199,65, 0, 0, 0, 32, 9, 32, 4, 0, 64, 128,134,8, 0, 0, 32, 34, 145,0, 0, 0, 160,174,207,45, 0, 0, 0, 33, 4, 65, 0, 0, 32, 18, 0, 32, 0, 0, 32, 2, 6, 33, 0, 0, 32, 2, 132,1, 0, 0, 0, 4, 128,32, 0, 0, 96, 34, 0, 0, 0, 0, 0, 0, 128,11, 0, 0, 160,50, 8, 1, 0, 0, 160,16, 8, 0, 0, 0, 160,50, 192,1, 0, 0, 128,50, 128,33, 0, 0, 192,137,196,5, 0, 0, 0, 38, 128,0, 4, 0, 32, 56, 1, 0, 0, 0, 64, 2, 4, 1, 0, 0, 32, 51, 197,1, 4, 0, 32, 62, 209,43, 0, 0, 0, 1, 196,32, 0, 0, 0, 32, 72, 0, 0, 0, 32, 34, 24, 34, 0, 0, 192,190,151,111, 128,0, 32, 10, 141,0, 4, 33, 64, 6, 128,0, 0, 0, 32, 34, 144,0, 0, 0, 32, 1, 192,0, 0, 0, 64, 51, 132,0, 0, 0, 160,59, 135,60, 0, 0, 0, 1, 66, 0, 0, 0, 224,34, 129,33, 0, 0, 64, 3, 129,1, 0, 0, 192,19, 128,0, 4, 0, 0, 2, 196,0, 4, 0, 0, 6, 0, 0, 0, 0, 64, 14, 149,68, 0, 0, 0, 34, 128,8, 0, 0, 32, 34, 128,40, 0, 0, 0, 0, 197,8, 0, 0, 0, 162,157,78, 0, 0, 0, 128,17, 0, 4, 0, 128,11, 198,1, 0, 0, 0, 160,0, 0, 4, 0, 0, 8, 128,0, 0, 0, 0, 24, 131,0, 0, 0, 0, 35, 128,8, 0, 0, 224,191,255,77, 0, 0, 96, 34, 201,34, 0, 0, 128,0, 192,32, 0, 0, 0, 32, 196,8, 0, 0, 128,32, 32, 0, 0, 0, 160,146,73, 1, 4, 0, 0, 34, 131,40, 0, 0, 32, 11, 133,64, 4, 0, 128,129,134,0, 0, 0, 128,0, 197,33, 0, 0, 0, 128,192,0, 0, 0, 32, 38, 76, 2, 0, 0, 0, 34, 11, 32, 0, 0, 0, 2, 65, 33, 0, 0, 32, 8, 72, 2, 0, 0, 0, 0, 197,32, 4, 0, 0, 1, 195,1, 0, 0, 160,34, 11, 34, 0, 0, 128,128,4, 32, 0, 0, 32, 1, 132,0, 0, 0, 96, 163,27, 0, 0, 0, 32, 2, 137,0, 0, 0, 0, 162,0, 32, 0, 0, 0, 34, 129,1, 0, 0, 224,191,159,13, 0, 0, 0, 8, 0, 12, 0, 0, 0, 8, 2, 12, 0, 0, 0, 32, 2, 4, 0, 0, 0, 18, 10, 9, 0, 0, 0, 0, 8, 64, 0, 0, 32, 2, 89, 2, 0, 0, 0, 0, 192,16, 0, 0, 96, 191,221,35, 0, 0, 64, 0, 5, 65, 0, 0, 0, 9, 0, 1, 0, 0, 128,0, 148,0, 0, 0, 128,162,128,65, 0, 0, 128,162,64, 33, 0, 0, 32, 0, 8, 8, 0, 0, 0, 32, 68, 32, 0, 0, 160,170,135,0, 0, 0, 0, 146,0, 2, 0, 0, 0, 33, 192,0, 0, 0, 32, 6, 9, 0, 4, 16, 224,255,255,127, 0, 0, 128,137,215,5, 0, 0, 0, 35, 128,32, 4, 3, 0, 0, 128,1, 0, 1, 0, 0, 8, 0, 0, 0, 0, 31, 215,13, 0, 0, 32, 2, 132,32, 0, 0, 0, 48, 1, 0, 4, 0, 32, 0, 35, 32, 0, 0, 0, 34, 9, 33, 0, 0, 64, 50, 203,8, 0, 0, 0, 0, 208,0, 0, 0, 64, 1, 5, 8, 0, 0, 0, 50, 154,0, 0, 33, 0, 0, 4, 0, 0, 0, 96, 34, 205,0, 0, 0, 0, 16, 129,1, 0, 0, 128,176,137,67, 0, 0, 0, 0, 5, 4, 0, 0, 96, 22, 131,0, 0, 0, 0, 0, 84, 0, 4, 0, 96, 43, 207,47, 0, 0, 32, 32, 129,32, 0, 0, 0, 1, 199,32, 0, 0, 32, 50, 3, 32, 0, 0, 128,17, 195,9, 0, 0, 192,10, 132,1, 0, 0, 0, 50, 136,0, 0, 0, 0, 1, 128,1, 0, 0, 128,130,69, 40, 0, 0, 32, 36, 64, 34, 0, 0, 128,10, 4, 0, 0, 0, 96, 10, 77, 34, 0, 0, 0, 50, 223,0, 0, 0, 64, 0, 68, 1, 0, 0, 0, 136,128,0, 4, 0, 128,0, 131,1, 0, 0, 0, 18, 67, 1, 0, 0, 0, 58, 3, 0, 0, 0, 192,141,199,1, 0, 0, 64, 50, 15, 0, 0, 0, 0, 16, 4, 1, 0, 0, 0, 50, 128,32, 0, 0, 128,34, 15, 0, 0, 0, 0, 0, 71, 0, 0, 0, 64, 16, 130,0, 0, 0, 64, 177,131,0, 0, 0, 224,191,223,111, 0, 0, 64, 33, 150,105, 0, 0, 0, 128,129,0, 0, 0, 224,141,94, 36, 0, 0, 0, 0, 134,64, 0, 0, 128,1, 135,0, 0, 0, 160,75, 142,81, 0, 0, 32, 8, 8, 0, 0, 0, 32, 18, 136,0, 4, 0, 64, 0, 0, 0, 0, 0, 96, 2, 8, 0, 0, 0, 32, 34, 142,104, 0, 0, 64, 13, 68, 1, 4, 0, 96, 1, 136,8, 0, 0, 64, 0, 130,32, 0, 0, 128,6, 156,1, 0, 0, 0, 8, 0, 8, 0, 0, 0, 2, 128,9, 0, 0, 0, 2, 8, 8, 0, 0, 0, 33, 48, 34, 0, 0, 96, 34, 28, 41, 4, 0, 32, 0, 68, 40, 0, 0, 0, 32, 8, 64, 0, 1, 0, 0, 16, 0, 4, 0, 224,63, 219,3, 0, 0, 160,0, 0, 2, 0, 0, 32, 136,64, 0, 0, 0, 32, 40, 128,33, 0, 0, 128,136,133,1, 4, 0, 0, 0, 5, 0, 0, 0, 0, 8, 68, 1, 0, 0, 32, 50, 201,1, 0, 0, 0, 2, 4, 4, 0, 0, 224,127,239,101, 0, 0, 192,0, 2, 0, 0, 0, 32, 178,73, 32, 0, 0, 0, 1, 64, 2, 4, 0, 32, 34, 201,2, 0, 0, 128,0, 4, 4, 0, 0, 0, 33, 70, 0, 0, 0, 96, 51, 227,2, 0, 0, 64, 0, 199,0, 0, 0, 64, 144,130,0, 0, 0, 64, 16, 194,0, 0, 0, 0, 9, 0, 16, 0, 0, 128,0, 195,2, 0, 0, 128,8, 32, 0, 0, 0, 128,34, 11, 11, 0, 0, 0, 0, 7, 32, 0, 0, 32, 50, 200,72, 0, 1, 0, 34, 151,1, 0, 0, 128,0, 68, 1, 0, 0, 0, 32, 20, 0, 0, 0, 0, 130,20, 0, 0, 0, 128,48, 153,5, 0, 0, 128,5, 149,65, 0, 0, 32, 0, 21, 5, 0, 0, 224,187,223,63, 0, 0, 224,128,132,0, 0, 0, 128,130,140,0, 4, 1, 32, 144,128,0, 0, 0, 32, 34, 128,64, 0, 0, 64, 0, 0, 9, 0, 0, 0, 2, 1, 64, 0, 0, 160,152,187,1, 0, 0, 32, 16, 72, 2, 0, 0, 0, 64, 87, 1, 0, 0, 128,10, 128,0, 0, 0, 64, 0, 64, 5, 0, 0, 64, 2, 1, 0, 0, 0, 0, 0, 65, 8, 0, 0, 32, 34, 77, 32, 0, 0, 0, 33, 68, 0, 0, 0, 128,64, 68, 32, 0, 0, 0, 0, 76, 1, 0, 0, 0, 0, 129,64, 0, 0, 64, 0, 65, 4, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 196,16, 0, 0, 32, 50, 73, 8, 0, 0, 64, 6, 213,36, 0, 0, 128,50, 137,69, 0, 0, 32, 34, 68, 32, 0, 0, 128,2, 12, 0, 0, 0, 0, 2, 36, 0, 0, 0, 192,15, 213,1, 0, 0, 0, 0, 144,1, 0, 0, 128,17, 213,1, 0, 0, 0, 137,136,0, 4, 0, 0, 0, 197,33, 0, 0, 0, 4, 128,2, 4, 0, 96, 59, 131,41, 0, 0, 64, 1, 10, 9, 0, 0, 160,3, 133,1, 0, 0, 128,128,5, 0, 0, 0, 32, 0, 0, 6, 0, 0, 96, 34, 0, 41, 4, 0, 160,54, 61, 19, 0, 0, 128,8, 131,0, 0, 0, 0, 32, 144,0, 0, 0, 0, 2, 209,0, 0, 0, 0, 1, 128,64, 0, 0, 128,0, 195,1, 0, 0, 0, 10, 129,1, 0, 1, 0, 34, 9, 8, 0, 0, 0, 1, 128,3, 0, 0, 0, 32, 6, 0, 0, 0, 128,1, 138,5, 0, 0, 0, 10, 215,1, 4, 0, 64, 45, 209,8, 0, 0, 0, 2, 64, 64, 0, 0, 0, 128,33, 0, 136,0, 96, 119,107,122, 0, 0, 224,251,199,127, 0, 0, 96, 34, 65, 2, 0, 0, 32, 162,0, 3, 0, 0, 0, 1, 195,33, 0, 0, 32, 0, 13, 0, 0, 0, 0, 34, 136,2, 0, 0, 0, 35, 128,0, 0, 0, 32, 42, 133,2, 0, 0, 0, 34, 197,32, 0, 0, 0, 8, 197,5, 0, 0, 0, 34, 137,2, 4, 0, 224,62, 199,40, 4, 1, 0, 4, 1, 0, 0, 0, 224,170,137,77, 0, 0, 0, 4, 68, 0, 0, 1, 0, 2, 128,0, 0, 0, 64, 16, 128,0, 0, 0, 96, 34, 136,0, 0, 0, 0, 0, 20, 10, 0, 0, 160,175,132,3, 0, 0, 0, 48, 0, 32, 0, 0, 64, 0, 128,2, 4, 0, 224,190,239,13, 0, 0, 192,24, 0, 8, 0, 0, 0, 1, 128,8, 0, 0, 32, 2, 132,0, 0, 0, 96, 50, 129,0, 0, 0, 32, 18, 128,10, 0, 0, 0, 88, 69, 0, 0, 0, 0, 50, 130,0, 0, 0, 160,50, 136,1, 0, 0, 160,50, 131,67, 4, 0, 0, 5, 128,0, 4, 0, 0, 50, 129,0, 0, 0, 192,8, 197,21, 0, 0, 128,145,0, 1, 0, 0, 160,32, 0, 1, 0, 34, 0, 0, 128,0, 0, 0, 192,128,5, 4, 0, 0, 0, 34, 0, 64, 4, 0, 224,187,207,79, 0, 0, 0, 8, 192,65, 0, 0, 0, 38, 141,32, 0, 0, 0, 16, 128,2, 0, 0, 64, 0, 65, 2, 0, 0, 0, 146,0, 1, 0, 0, 32, 35, 201,0, 0, 0, 0, 160,8, 0, 0, 0, 64, 17, 135,65, 0, 0, 0, 42, 128,32, 0, 0, 32, 2, 128,32, 0, 0, 32, 10, 1, 0, 0, 0, 0, 2, 65, 0, 0, 0, 128,74, 135,1, 0, 0, 64, 10, 130,0, 0, 0, 224,163,27, 3, 0, 0, 64, 146,130,1, 0, 0, 128,0, 148,80, 0, 0, 32, 1, 0, 1, 4, 0, 0, 16, 128,1, 0, 0, 0, 2, 142,98, 0, 0, 32, 32, 10, 2, 0, 0, 128,43, 199,9, 0, 0, 32, 0, 72, 32, 0, 0, 224,171,207,5, 0, 0, 64, 0, 5, 0, 0, 0, 32, 32, 136,0, 0, 0, 0, 34, 69, 33, 0, 0, 0, 10, 133,1, 0, 0, 0, 0, 145,1, 0, 0, 160,48, 0, 67, 0, 0, 0, 16, 139,0, 0, 0, 128,3, 64, 0, 0, 1, 0, 4, 0, 0, 0, 0, 224,223,239,87, 0, 0, 64, 2, 64, 0, 0, 0, 32, 146,0, 3, 0, 0, 0, 0, 133,1, 0, 0, 0, 11, 9, 0, 0, 0, 128,13, 197,0, 4, 0, 0, 34, 130,0, 0, 0, 128,0, 202,32, 0, 0, 0, 50, 0, 32, 0, 0, 32, 26, 130,2, 0, 0, 64, 162,155,1, 4, 0, 0, 1, 192,1, 0, 0, 224,0, 28, 0, 0, 0, 0, 32, 24, 0, 0, 0, 0, 50, 130,32, 4, 0, 0, 34, 138,0, 16, 0, 0, 0, 0, 0, 0, 0, 160,175,141,67, 0, 0, 0, 8, 7, 0, 0, 0, 0, 35, 129,0, 0, 0, 32, 2, 1, 34, 0, 0, 64, 13, 193,1, 4, 0, 64, 34, 148,1, 0, 0, 64, 2, 129,0, 0, 0, 32, 0, 64, 32, 0, 0, 0, 8, 69, 32, 0, 0, 128,10, 0, 0, 0, 0, 0, 162,146,1, 4, 1, 224,21, 151,8, 0, 0, 32, 32, 1, 2, 0, 0, 0, 0, 73, 0, 4, 0, 32, 34, 129,0, 0, 0, 64, 16, 129,0, 0, 0, 128,146,129,1, 0, 0, 192,54, 142,33, 0, 0, 0, 38, 129,0, 0, 0, 160,26, 132,65, 0, 0, 0, 26, 147,40, 4, 0, 128,0, 192,0, 0, 0, 192,185,214,63, 0, 0, 0, 8, 145,0, 0, 0, 0, 8, 67, 0, 0, 0, 96, 34, 2, 0, 0, 0, 32, 171,132,0, 0, 0, 0, 64, 144,0, 0, 1, 64, 35, 3, 32, 0, 0, 64, 22, 129,11, 0, 0, 64, 8, 4, 0, 0, 0, 160,59, 134,27, 0, 0, 64, 8, 128,0, 0, 0, 128,168,70, 1, 0, 0, 0, 32, 0, 8, 0, 0, 0, 8, 128,1, 0, 0, 32, 18, 2, 0, 0, 0, 0, 32, 10, 0, 0, 0, 0, 3, 140,1, 0, 0, 0, 2, 6, 32, 0, 0, 128,10, 214,1, 0, 0, 128,136,4, 1, 0, 0, 64, 34, 0, 2, 0, 0, 0, 4, 146,0, 0, 0, 160,26, 218,43, 0, 0, 0, 129,64, 64, 0, 0, 0, 42, 128,0, 4, 0, 64, 2, 128,0, 0, 0, 0, 16, 144,0, 0, 0, 0, 128,132,97, 0, 0, 32, 1, 140,0, 0, 3, 0, 32, 128,32, 0, 0, 64, 191,204,17, 0, 0, 128,16, 0, 1, 0, 0, 0, 34, 0, 33, 4, 0, 64, 46, 4, 1, 0, 0, 224,43, 223,123, 0, 0, 32, 144,0, 2, 0, 0, 0, 11, 8, 0, 0, 0, 32, 26, 1, 1, 0, 0, 32, 171,131,73, 0, 0, 32, 34, 10, 32, 0, 0, 128,43, 140,69, 0, 0, 32, 2, 69, 2, 0, 0, 64, 129,193,1, 4, 0, 0, 50, 144,0, 4, 0, 96, 114,151,8, 0, 0, 224,175,239,43, 0, 0, 0, 136,4, 32, 0, 0, 0, 5, 144,0, 0, 0, 64, 2, 4, 0, 4, 0, 0, 22, 128,32, 0, 0, 160,62, 214,13, 0, 0, 32, 162,2, 1, 0, 0, 0, 36, 128,1, 0, 0, 96, 34, 136,2, 0, 0, 0, 33, 1, 0, 0, 0, 128,1, 132,0, 0, 0, 32, 0, 192,32, 0, 0, 96, 26, 3, 33, 0, 0, 0, 50, 198,34, 0, 0, 128,0, 64, 8, 0, 0, 0, 6, 4, 1, 4, 1, 0, 0, 128,1, 0, 0, 64, 145,129,1, 0, 0, 128,8, 229,1, 0, 0, 0, 139,0, 0, 0, 0, 0, 18, 0, 32, 0, 0, 64, 128,1, 0, 0, 0, 0, 130,128,0, 0, 0, 64, 22, 0, 57, 0, 0, 128,185,215,111, 0, 0, 32, 32, 78, 0, 0, 0, 0, 1, 197,64, 0, 0, 0, 1, 192,8, 4, 0, 128,163,164,65, 0, 0, 32, 178,136,0, 4, 1, 0, 54, 137,34, 0, 0, 64, 4, 128,0, 0, 0, 32, 32, 0, 66, 0, 0, 128,50, 0, 2, 0, 0, 0, 2, 0, 67, 0, 0, 0, 9, 129,1, 0, 0, 224,43, 239,47, 4, 0, 32, 39, 72, 0, 0, 0, 64, 55, 215,74, 0, 0, 32, 0, 136,32, 0, 0, 64, 11, 71, 1, 0, 0, 128,130,8, 65, 0, 0, 0, 32, 2, 32, 0, 0, 128,18, 36, 0, 0, 0, 0, 2, 15, 8, 0, 0, 0, 0, 0, 73, 0, 0, 224,43, 149,85, 0, 0, 32, 9, 3, 0, 0, 0, 0, 1, 197,1, 0, 0, 96, 2, 67, 1, 0, 0, 32, 56, 8, 0, 0, 0, 0, 136,0, 1, 0, 0, 192,176,8, 0, 0, 0, 128,16, 128,65, 4, 0, 224,27, 143,75, 0, 0, 0, 17, 131,0, 4, 0, 0, 12, 130,0, 0, 0, 96, 0, 2, 0, 0, 0, 128,160,0, 1, 0, 0, 0, 32, 0, 64, 0, 2, 32, 32, 128,0, 0, 0, 64, 34, 0, 32, 0, 0, 128,42, 130,1, 0, 0, 128,36, 129,32, 0, 0, 0, 16, 8, 1, 0, 0, 32, 34, 202,0, 0, 0, 224,219,239,65, 0, 0, 128,176,2, 0, 4, 33, 48, 54, 138,43, 0, 2, 0, 0, 16, 0, 0, 0, 32, 9, 0, 0, 0, 0, 0, 16, 197,0, 0, 0, 0, 176,10, 9, 0, 0, 0, 33, 134,0, 0, 0, 192,156,155,13, 0, 0, 96, 34, 2, 40, 0, 0, 0, 0, 136,64, 4, 0, 160,159,132,1, 0, 0, 0, 144,0, 1, 0, 0, 192,46, 199,35, 0, 0, 32, 2, 0, 34, 0, 0, 128,0, 192,0, 0, 0, 128,0, 0, 4, 0, 0, 160,34, 136,1, 0, 0, 64, 34, 4, 32, 0, 0, 192,32, 200,3, 0, 0, 32, 132,8, 0, 4, 4, 97, 126,207,47, 0, 0, 0, 17, 4, 0, 0, 0, 224,127,223,127, 0, 0, 96, 2, 73, 32, 4, 0, 0, 16, 65, 0, 4, 1, 32, 2, 129,0, 0, 0, 128,146,0, 32, 0, 0, 96, 43, 137,72, 0, 0, 64, 1, 8, 0, 0, 0, 64, 1, 65, 1, 0, 0, 0, 34, 12, 0, 0, 0, 32, 10, 133,0, 0, 0, 32, 0, 70, 0, 0, 0, 0, 10, 197,3, 4, 0, 0, 2, 1, 0, 0, 0, 0, 38, 136,0, 0, 0, 96, 47, 153,47, 0, 0, 128,16, 212,85, 0, 0, 0, 40, 0, 1, 0, 0, 0, 1, 0, 96, 0, 0, 32, 0, 72, 0, 4, 0, 32, 0, 196,32, 0, 0, 160,128,23, 64, 0, 0, 64, 32, 8, 0, 0, 0, 32, 1, 128,0, 0, 0, 96, 34, 138,34, 0, 0, 128,24, 68, 9, 0, 0, 0, 24, 198,33, 0, 0, 160,154,14, 4, 0, 0, 0, 8, 20, 8, 0, 0, 128,0, 144,64, 0, 0, 96, 34, 138,2, 0, 0, 128,8, 64, 0, 4, 0, 0, 1, 64, 0, 4, 0, 192,0, 201,65, 0, 0, 32, 79, 141,65, 0, 0, 32, 18, 8, 32, 4, 0, 96, 34, 193,40, 0, 0, 0, 1, 195,0, 0, 0, 0, 2, 4, 2, 0, 0, 64, 130,0, 0, 0, 0, 0, 18, 4, 67, 0, 0, 224,47, 207,109, 0, 0, 64, 12, 132,33, 0, 0, 32, 2, 9, 2, 0, 0, 0, 34, 12, 2, 0, 0, 32, 0, 1, 33, 0, 0, 32, 32, 9, 2, 0, 0, 192,10, 140,0, 0, 0, 32, 2, 136,0, 0, 0, 32, 0, 192,33, 0, 0, 0, 4, 4, 2, 0, 0, 64, 16, 64, 9, 0, 0, 128,9, 140,1, 0, 0, 0, 0, 4, 9, 0, 0, 0, 178,8, 1, 0, 0, 0, 50, 79, 0, 0, 0, 224,178,187,1, 0, 0, 32, 2, 6, 32, 0, 0, 128,50, 201,73, 4, 0, 0, 145,129,8, 0, 0, 0, 139,135,69, 0, 0, 0, 161,1, 1, 0, 0, 0, 48, 68, 0, 0, 0, 32, 0, 5, 1, 0, 0, 0, 131,65, 0, 0, 0, 64, 19, 135,1, 0, 0, 32, 35, 16, 64, 0, 0, 64, 1, 1, 1, 0, 0, 224,191,223,35, 0, 0, 128,8, 68, 1, 4, 0, 0, 50, 137,32, 0, 0, 96, 18, 8, 0, 0, 0, 0, 128,192,65, 0, 0, 0, 2, 67, 0, 0, 0, 0, 162,11, 0, 0, 0, 64, 10, 131,1, 0, 0, 0, 50, 129,65, 0, 0, 0, 8, 4, 64, 0, 0, 96, 32, 203,5, 0, 0, 96, 34, 130,0, 0, 0, 160,43, 204,67, 0, 0, 32, 0, 192,5, 4, 1, 0, 0, 129,0, 0, 0, 32, 2, 128,3, 0, 0, 160,0, 2, 4, 0, 0, 32, 50, 197,64, 0, 0, 0, 12, 211,0, 4, 1, 32, 0, 132,1, 0, 0, 0, 16, 20, 0, 0, 0, 224,191,207,45, 0, 0, 128,8, 69, 0, 0, 0, 32, 8, 4, 1, 0, 0, 32, 32, 0, 1, 0, 0, 0, 34, 140,32, 0, 0, 0, 0, 135,10, 0, 0, 0, 0, 10, 8, 0, 0, 0, 50, 196,66, 0, 0, 128,130,144,3, 4, 0, 160,50, 138,67, 0, 0, 0, 34, 131,32, 0, 0, 0, 2, 8, 16, 0, 1, 0, 8, 0, 0, 0, 0, 32, 2, 68, 1, 0, 0, 0, 0, 193,2, 0, 1, 64, 0, 136,0, 0, 0, 96, 15, 223,71, 4, 0, 128,32, 102,0, 0, 0, 64, 0, 3, 0, 0, 0, 64, 0, 18, 0, 0, 0, 128,42, 12, 3, 0, 0, 96, 2, 141,0, 0, 0, 128,11, 5, 0, 0, 0, 64, 12, 193,0, 0, 0, 0, 146,3, 2, 0, 0, 64, 34, 129,32, 4, 0, 32, 42, 141,2, 4, 0, 224,159,211,33, 0, 0, 32, 2, 72, 0, 0, 0, 32, 1, 68, 0, 0, 0, 32, 50, 138,34, 0, 0, 0, 0, 198,0, 0, 0, 0, 0, 199,33, 0, 0, 160,154,135,1, 0, 0, 0, 0, 24, 0, 0, 0, 128,8, 12, 0, 0, 0, 64, 2, 19, 0, 0, 0, 32, 139,140,97, 0, 0, 0, 50, 1, 0, 0, 1, 64, 32, 0, 0, 0, 0, 192,2, 0, 0, 0, 0, 0, 32, 64, 4, 0, 0, 0, 9, 4, 0, 0, 0, 32, 13, 137,5, 0, 0, 0, 4, 65, 2, 4, 0, 0, 36, 128,32, 0, 0, 0, 33, 4, 2, 0, 0, 32, 0, 0, 16, 0, 0, 0, 18, 8, 32, 0, 0, 128,50, 11, 1, 0, 0, 32, 18, 1, 1, 0, 0, 0, 4, 135,0, 0, 0, 32, 50, 0, 0, 0, 0, 32, 32, 198,1, 0, 1, 0, 1, 128,0, 0, 0, 32, 32, 2, 0, 0, 0, 32, 35, 136,34, 0, 0, 128,3, 198,111, 0, 0, 0, 39, 133,42, 0, 0, 128,1, 0, 69, 0, 0, 128,10, 148,0, 0, 0, 192,6, 14, 40, 0, 0, 32, 35, 132,0, 0, 0, 0, 0, 130,5, 4, 0, 0, 9, 4, 4, 0, 0, 192,3, 134,1, 0, 0, 0, 3, 128,1, 0, 0, 224,1, 218,45, 0, 0, 192,34, 6, 1, 0, 0, 0, 33, 4, 0, 0, 0, 0, 0, 192,9, 4, 0, 0, 1, 69, 9, 4, 0, 64, 34, 129,2, 0, 0, 32, 32, 69, 0, 4, 0, 96, 167,155,72, 0, 0, 0, 0, 66, 8, 0, 0, 0, 0, 1, 34, 0, 0, 0, 0, 66, 2, 0, 0, 32, 128,0, 1, 0, 0, 32, 26, 0, 0, 0, 0, 0, 0, 128,40, 0, 0, 32, 2, 26, 0, 0, 0, 192,144,213,9, 0, 0, 0, 40, 129,1, 0, 0, 0, 16, 128,65, 0, 0, 32, 24, 8, 2, 0, 0, 32, 2, 76, 0, 0, 0, 0, 128,68, 1, 0, 0, 32, 39, 153,67, 0, 0, 64, 0, 132,32, 0, 0, 96, 38, 198,9, 0, 1, 64, 2, 64, 0, 0, 0, 192,0, 10, 9, 0, 0, 64, 0, 2, 32, 0, 1, 64, 42, 22, 0, 0, 0, 160,9, 128,1, 0, 0, 32, 34, 6, 34, 0, 0, 0, 17, 0, 0, 0, 0, 64, 0, 6, 0, 0, 0, 64, 34, 18, 0, 0, 0, 0, 0, 192,65, 0, 0, 160,11, 140,97, 0, 0, 32, 130,8, 0, 0, 0, 32, 0, 128,64, 0, 0, 192,143,157,1, 4, 0, 224,191,215,41, 0, 0, 0, 2, 24, 1, 0, 1, 64, 1, 2, 0, 0, 0, 224,187,207,33, 4, 0, 0, 0, 0, 4, 0, 0, 128,2, 133,32, 0, 0, 160,51, 151,9, 0, 0, 32, 34, 10, 0, 0, 0, 0, 8, 66, 0, 4, 0, 160,18, 202,33, 0, 0, 32, 6, 137,8, 0, 0, 128,41, 81, 5, 0, 0, 0, 1, 3, 0, 0, 0, 32, 35, 5, 0, 0, 0, 32, 34, 72, 98, 0, 0, 192,127,151,111, 0, 0, 96, 34, 137,1, 4, 1, 0, 5, 129,32, 0, 0, 0, 35, 140,34, 0, 0, 0, 8, 128,32, 4, 0, 96, 2, 147,0, 4, 0, 96, 63, 14, 69, 0, 0, 224,231,146,34, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 192,2, 0, 0, 0, 0, 140,0, 4, 0, 0, 0, 2, 0, 4, 0, 0, 50, 128,64, 0, 0, 0, 18, 16, 0, 0, 0, 0, 4, 128,4, 0, 0, 0, 2, 146,1, 0, 0, 0, 50, 11, 32, 0, 0, 32, 34, 0, 67, 0, 0, 0, 4, 132,1, 0, 0, 64, 2, 128,0, 0, 0, 0, 1, 197,33, 4, 0, 192,3, 132,8, 0, 0, 224,46, 135,47, 0, 0, 0, 0, 197,5, 0, 0, 64, 1, 64, 0, 0, 1, 0, 18, 141,0, 0, 0, 128,16, 8, 0, 0, 0, 160,129,197,5, 4, 1, 96, 55, 155,14, 0, 0, 32, 2, 1, 2, 0, 0, 128,0, 0, 34, 4, 0, 32, 10, 136,0, 0, 0, 0, 32, 3, 0, 0, 0, 32, 33, 12, 64, 0, 0, 160,18, 0, 65, 4, 0, 0, 16, 128,8, 0, 0, 192,79, 151,69, 0, 0, 0, 3, 129,0, 0, 0, 192,12, 208,5, 0, 0, 64, 2, 0, 1, 4, 0, 0, 34, 8, 32, 0, 0, 0, 2, 4, 33, 0, 0, 32, 2, 139,8, 0, 0, 0, 16, 1, 1, 0, 0, 160,11, 132,1, 0, 0, 0, 34, 144,0, 0, 0, 96, 18, 131,9, 0, 0, 0, 4, 64, 0, 0, 0, 224,63, 159,15, 0, 0, 0, 0, 148,0, 0, 0, 0, 1, 130,5, 0, 0, 192,18, 131,68, 0, 0, 64, 162,15, 0, 0, 0, 0, 18, 0, 65, 0, 0, 128,0, 149,1, 4, 0, 64, 50, 129,8, 0, 0, 0, 34, 133,1, 0, 0, 64, 39, 151,12, 0, 0, 64, 18, 0, 8, 0, 0, 64, 2, 16, 0, 0, 0, 0, 43, 8, 9, 0, 0, 0, 2, 2, 32, 0, 0, 0, 48, 130,0, 0, 0, 0, 1, 16, 0, 0, 0, 128,0, 16, 17, 0, 0, 224,63, 215,97, 0, 0, 128,105,87, 37, 0, 0, 32, 33, 4, 0, 0, 0, 0, 33, 128,0, 4, 0, 32, 34, 192,0, 0, 0, 128,0, 2, 8, 0, 0, 32, 42, 66, 0, 0, 0, 64, 1, 197,4, 0, 0, 160,34, 196,1, 0, 0, 0, 40, 1, 0, 0, 0, 128,27, 207,65, 0, 0, 64, 1, 197,0, 0, 0, 64, 39, 3, 1, 0, 0, 0, 0, 70, 1, 4, 0, 0, 34, 0, 32, 0, 0, 32, 2, 4, 32, 0, 0, 32, 0, 128,36, 0, 0, 32, 34, 0, 33, 0, 1, 0, 32, 128,0, 0, 0, 224,36, 25, 32, 0, 0, 0, 1, 0, 33, 0, 0, 192,0, 8, 4, 0, 0, 64, 32, 146,0, 0, 0, 32, 2, 136,32, 4, 0, 64, 173,151,1, 0, 0, 32, 2, 65, 32, 0, 0, 0, 1, 65, 1, 0, 0, 32, 136,197,10, 0, 0, 0, 2, 64, 33, 4, 0, 0, 146,131,1, 0, 0, 0, 130,4, 0, 4, 0, 0, 178,139,65, 0, 0, 0, 2, 64, 2, 4, 0, 0, 2, 134,5, 0, 0, 0, 34, 2, 96, 0, 0, 32, 98, 8, 32, 4, 0, 0, 1, 4, 0, 0, 0, 0, 8, 87, 0, 4, 2, 160,122,222,107, 0, 128,224,255,247,127, 0, 0, 160,156,213,1, 0, 0, 160,10, 65, 0, 0, 0, 0, 129,131,0, 0, 0, 192,0, 0, 1, 0, 0, 0, 146,0, 0, 0, 0, 96, 38, 135,40, 0, 0, 0, 0, 66, 32, 0, 0, 32, 103,141,8, 0, 0, 128,20, 64, 2, 0, 0, 96, 42, 207,42, 0, 0, 0, 1, 70, 0, 0, 0, 0, 1, 199,0, 0, 0, 0, 141,197,1, 0, 0, 0, 16, 1, 8, 4, 0, 192,11, 213,32, 0, 0, 32, 34, 65, 2, 4, 0, 225,167,155,15, 0, 0, 0, 16, 132,32, 0, 0, 96, 18, 73, 9, 0, 0, 64, 4, 4, 1, 4, 0, 96, 38, 170,42, 0, 0, 0, 17, 130,0, 0, 0, 128,2, 69, 33, 0, 0, 64, 27, 132,8, 0, 0, 96, 35, 223,2, 0, 0, 0, 129,199,0, 0, 0, 0, 2, 73, 34, 0, 0, 0, 33, 193,0, 0, 0, 128,128,68, 0, 0, 1, 0, 34, 129,0, 0, 0, 128,0, 3, 1, 4, 0, 224,191,205,79, 4, 0, 192,190,219,40, 0, 0, 0, 8, 129,0, 0, 0, 64, 1, 192,32, 0, 0, 0, 128,197,32, 0, 0, 32, 34, 142,0, 4, 0, 0, 34, 129,32, 0, 0, 0, 28, 6, 0, 0, 0, 0, 0, 195,32, 0, 0, 224,22, 11, 0, 4, 3, 0, 1, 2, 0, 0, 0, 224,239,223,13, 0, 0, 0, 1, 197,8, 0, 0, 0, 18, 73, 2, 0, 0, 224,59, 207,41, 0, 0, 0, 8, 128,8, 0, 0, 0, 10, 199,32, 0, 0, 0, 2, 134,0, 0, 0, 64, 0, 71, 8, 0, 0, 0, 1, 0, 12, 0, 0, 0, 8, 196,0, 0, 0, 0, 34, 69, 0, 0, 0, 0, 0, 70, 32, 4, 0, 32, 38, 137,0, 0, 0, 32, 35, 136,0, 0, 0, 0, 1, 192,33, 0, 0, 0, 11, 143,0, 0, 0, 64, 0, 136,1, 0, 0, 0, 1, 17, 10, 4, 1, 0, 0, 2, 0, 0, 0, 96, 0, 3, 0, 4, 0, 0, 126,223,107, 0, 0, 0, 0, 129,33, 0, 0, 0, 22, 8, 1, 0, 0, 0, 6, 9, 0, 0, 0, 224,146,133,9, 0, 0, 0, 17, 0, 1, 0, 0, 0, 0, 1, 9, 0, 0, 0, 0, 66, 33, 0, 0, 32, 34, 9, 1, 4, 0, 160,54, 195,1, 0, 0, 0, 1, 193,1, 0, 0, 0, 5, 192,0, 0, 0, 64, 0, 4, 8, 0, 0, 192,142,213,1, 0, 0, 160,2, 7, 0, 0, 0, 0, 4, 207,32, 4, 0, 0, 18, 128,0, 0, 0, 0, 128,131,1, 0, 0, 96, 130,205,5, 0, 16, 32, 0, 0, 0, 4, 0, 0, 34, 129,8, 0, 0, 160,31, 135,9, 0, 0, 64, 128,128,0, 0, 0, 32, 34, 3, 96, 0, 0, 64, 50, 69, 9, 0, 0, 0, 8, 0, 41, 4, 0, 224,255,223,63, 0, 0, 0, 1, 145,0, 0, 0, 192,25, 213,5, 4, 0, 96, 63, 243,40, 0, 0, 0, 35, 128,33, 0, 0, 0, 35, 205,1, 4, 0, 96, 22, 129,41, 4, 0, 0, 50, 137,8, 0, 0, 128,2, 66, 0, 4, 1, 0, 32, 1, 0, 0, 0, 0, 34, 68, 32, 0, 0, 96, 3, 68, 0, 0, 0, 32, 144,8, 3, 4, 0, 32, 2, 131,0, 0, 0, 64, 41, 195,40, 0, 0, 0, 34, 17, 0, 0, 0, 192,16, 208,0, 0, 0, 128,128,4, 0, 0, 0, 64, 15, 197,33, 0, 0, 32, 10, 133,40, 0, 0, 0, 1, 67, 0, 0, 0, 192,143,199,65, 0, 0, 0, 170,73, 33, 0, 0, 128,0, 71, 0, 0, 0, 96, 2, 129,0, 0, 0, 96, 163,201,97, 4, 0, 128,128,70, 32, 0, 0, 0, 18, 129,1, 0, 0, 0, 128,2, 64, 0, 0, 96, 175,155,77, 0, 0, 128,0, 20, 18, 4, 0, 96, 54, 206,42, 0, 3, 0, 16, 136,0, 0, 0, 96, 162,129,2, 4, 0, 0, 38, 131,0, 0, 0, 0, 34, 9, 1, 0, 0, 96, 162,155,0, 0, 0, 0, 2, 128,8, 0, 0, 0, 8, 69, 0, 0, 0, 0, 18, 129,0, 0, 0, 160,187,199,73, 0, 0, 0, 2, 73, 0, 0, 0, 128,130,0, 16, 0, 0, 64, 8, 64, 0, 0, 0, 64, 128,134,0, 0, 0, 0, 12, 132,0, 0, 0, 128,138,32, 0, 4, 1, 0, 128,0, 0, 0, 0, 128,18, 9, 0, 0, 0, 32, 56, 136,64, 0, 0, 96, 132,21, 8, 4, 0, 224,175,223,79, 0, 0, 0, 10, 4, 0, 0, 0, 32, 34, 138,1, 4, 0, 96, 37, 192,9, 0, 0, 128,1, 140,0, 0, 0, 0, 0, 133,32, 0, 0, 0, 1, 13, 1, 0, 0, 32, 35, 8, 0, 0, 0, 0, 2, 8, 64, 4, 0, 0, 35, 5, 2, 0, 0, 128,1, 14, 0, 0, 0, 128,18, 10, 0, 0, 0, 96, 130,2, 0, 0, 0, 32, 18, 1, 64, 0, 0, 64, 0, 9, 0, 0, 0, 64, 38, 8, 0, 0, 0, 128,160,129,1, 0, 0, 0, 22, 193,67, 0, 0, 64, 18, 192,0, 0, 0, 32, 34, 129,8, 0, 0, 32, 51, 135,40, 0, 0, 224,219,223,117, 0, 0, 64, 35, 133,0, 0, 0, 32, 34, 139,0, 0, 0, 0, 128,64, 16, 0, 0, 32, 35, 0, 32, 0, 0, 96, 5, 198,1, 0, 0, 32, 16, 200,0, 0, 0, 96, 25, 133,2, 4, 0, 96, 22, 135,9, 0, 0, 0, 96, 0, 0, 0, 0, 224,139,131,97, 0, 0, 0, 2, 8, 34, 0, 0, 64, 8, 68, 0, 0, 0, 0, 0, 194,32, 4, 0, 224,50, 142,41, 0, 0, 64, 8, 196,32, 0, 0, 32, 130,132,0, 0, 0, 0, 32, 2, 1, 0, 0, 64, 0, 140,96, 4, 0, 96, 2, 130,0, 0, 0, 160,139,206,65, 0, 0, 128,2, 3, 0, 0, 0, 0, 34, 227,2, 0, 0, 0, 8, 195,32, 0, 0, 0, 64, 128,1, 0, 0, 0, 16, 152,0, 0, 0, 128,0, 144,0, 0, 0, 32, 163,136,1, 0, 0, 0, 32, 7, 33, 0, 0, 0, 144,6, 1, 0, 0, 0, 4, 2, 32, 0, 0, 160,130,145,1, 4, 0, 160,18, 131,65, 0, 0, 128,50, 2, 0, 0, 0, 0, 137,68, 16, 0, 0, 96, 11, 140,0, 0, 0, 128,0, 68, 2, 0, 0, 0, 144,0, 2, 0, 0, 32, 139,140,2, 0, 0, 0, 163,130,0, 0, 0, 0, 149,213,33, 0, 0, 0, 6, 130,0, 0, 0, 32, 2, 151,0, 0, 0, 32, 10, 193,11, 4, 0, 128,0, 128,0, 0, 0, 0, 18, 15, 0, 0, 0, 32, 162,0, 0, 0, 0, 224,47, 239,111, 0, 0, 0, 17, 0, 8, 0, 0, 32, 34, 202,2, 0, 0, 0, 4, 134,0, 0, 0, 128,33, 134,0, 0, 0, 0, 7, 134,0, 0, 0, 32, 34, 10, 40, 0, 0, 64, 1, 68, 8, 0, 0, 64, 7, 134,0, 0, 0, 0, 16, 0, 8, 0, 0, 64, 0, 86, 8, 4, 1, 0, 4, 0, 8, 0, 0, 96, 10, 169,0, 0, 0, 128,8, 2, 8, 0, 0, 160,50, 8, 9, 0, 0, 0, 40, 68, 0, 0, 0, 32, 154,138,64, 0, 0, 192,2, 3, 0, 0, 0, 128,4, 128,0, 0, 0, 0, 50, 2, 8, 0, 0, 96, 34, 74, 0, 0, 0, 192,8, 219,41, 0, 0, 0, 129,0, 0, 0, 0, 0, 0, 16, 8, 32, 0, 0, 0, 0, 0, 0, 1, 0, 64, 136,2, 0, 0, 128,2, 5, 0, 0, 0, 160,171,12, 97, 0, 0, 0, 144,4, 0, 0, 0, 32, 1, 192,32, 4, 1, 192,3, 130,8, 0, 0, 192,143,221,37, 0, 0, 0, 2, 129,8, 0, 0, 128,34, 138,0, 0, 0, 0, 174,144,9, 4, 0, 0, 5, 129,8, 0, 0, 160,171,206,13, 0, 0, 128,128,4, 1, 0, 0, 0, 0, 12, 66, 0, 0, 96, 35, 128,34, 0, 1, 32, 0, 2, 0, 0, 0, 64, 13, 0, 0, 0, 0, 128,38, 3, 1, 4, 1, 64, 16, 210,32, 0, 0, 160,182,147,1, 0, 0, 32, 161,192,32, 4, 0, 32, 34, 137,32, 0, 0, 224,27, 219,73, 0, 0, 0, 0, 0, 48, 4, 32, 0, 0, 128,0, 0, 0, 0, 18, 16, 1, 0, 0, 128,157,215,65, 4, 0, 0, 40, 129,0, 0, 0, 32, 179,3, 1, 4, 1, 192,153,199,40, 0, 0, 128,0, 130,64, 0, 0, 32, 35, 129,0, 0, 0, 128,33, 130,0, 4, 0, 96, 163,193,97, 0, 0, 0, 1, 195,4, 0, 0, 0, 0, 197,37, 0, 0, 0, 34, 13, 1, 0, 4, 96, 38, 8, 2, 0, 0, 32, 2, 4, 2, 0, 0, 224,191,223,127, 0, 0, 0, 32, 68, 0, 0, 0, 224,34, 69, 0, 0, 0, 32, 2, 129,34, 0, 0, 32, 139,143,0, 0, 0, 64, 8, 193,37, 0, 0, 0, 2, 10, 1, 0, 0, 0, 4, 134,1, 0, 0, 32, 34, 138,33, 0, 0, 32, 58, 137,2, 0, 0, 0, 0, 132,33, 0, 0, 0, 16, 4, 3, 0, 0, 0, 48, 2, 0, 0, 0, 96, 163,139,1, 0, 0, 0, 34, 3, 1, 0, 0, 64, 8, 162,0, 0, 0, 96, 39, 154,6, 0, 0, 0, 162,141,0, 0, 0, 96, 50, 128,1, 0, 0, 129,155,133,65, 0, 0, 96, 34, 1, 2, 4, 0, 0, 34, 137,64, 0, 0, 64, 32, 2, 0, 0, 0, 0, 4, 80, 0, 0, 0, 0, 5, 205,9, 0, 0, 64, 1, 128,32, 0, 0, 32, 36, 129,1, 0, 0, 96, 146,138,1, 0, 0, 0, 160,64, 0, 0, 0, 64, 0, 70, 1, 0, 0, 128,16, 133,65, 0, 0, 128,34, 0, 0, 0, 0, 0, 20, 0, 1, 0, 0, 128,2, 133,0, 0, 0, 224,11, 223,2, 4, 0, 0, 0, 68, 0, 0, 0, 128,8, 1, 0, 0, 0, 32, 3, 0, 1, 0, 0, 0, 3, 128,2, 0, 0, 0, 128,130,0, 0, 0, 0, 18, 28, 0, 0, 0, 96, 33, 128,5, 0, 0, 128,9, 1, 1, 0, 0, 0, 2, 11, 1, 4, 32, 224,255,255,127, 0, 0, 0, 33, 131,4, 4, 0, 0, 32, 1, 0, 0, 0, 128,9, 70, 8, 0, 32, 32, 32, 192,18, 0, 0, 32, 178,8, 2, 0, 0, 192,17, 23, 0, 4, 0, 32, 35, 13, 34, 4, 0, 0, 16, 1, 0, 0, 0, 32, 4, 13, 0, 0, 0, 128,4, 0, 1, 0, 0, 128,0, 130,1, 0, 0, 64, 24, 13, 2, 0, 0, 32, 1, 65, 0, 4, 0, 32, 17, 64, 0, 0, 0, 32, 35, 137,2, 0, 0, 32, 132,0, 0, 0, 0, 32, 34, 142,2, 0, 0, 0, 40, 128,34, 1, 3, 0, 0, 0, 0, 0, 0, 96, 51, 91, 2, 0, 0, 0, 40, 6, 1, 0, 0, 32, 2, 72, 2, 0, 0, 128,1, 64, 0, 0, 0, 128,32, 193,1, 0, 0, 160,1, 68, 1, 0, 0, 32, 36, 128,5, 0, 0, 0, 0, 193,3, 0, 0, 0, 8, 128,64, 0, 0, 64, 0, 18, 4, 4, 0, 224,255,255,111, 0, 0, 192,9, 213,2, 0, 0, 0, 2, 2, 4, 0, 0, 128,0, 212,0, 0, 0, 0, 1, 140,1, 0, 0, 0, 24, 70, 0, 0, 0, 0, 34, 24, 0, 0, 0, 32, 1, 2, 0, 0, 0, 192,6, 149,17, 0, 0, 160,14, 200,4, 0, 0, 128,0, 16, 1, 0, 0, 128,0, 12, 4, 0, 0, 192,0, 129,0, 0, 0, 64, 5, 177,84, 0, 0, 128,0, 71, 1, 0, 0, 0, 34, 200,0, 0, 0, 0, 34, 4, 1, 0, 0, 96, 18, 203,8, 0, 0, 64, 8, 197,0, 0, 0, 0, 16, 130,1, 0, 0, 0, 8, 36, 1, 4, 0, 32, 162,136,2, 0, 0, 160,0, 16, 2, 0, 0, 160,162,25, 3, 0, 0, 128,0, 86, 0, 0, 0, 0, 9, 148,0, 0, 0, 0, 8, 3, 0, 0, 0, 0, 8, 5, 1, 0, 0, 64, 4, 65, 0, 0, 0, 32, 34, 66, 0, 0, 0, 32, 98, 72, 2, 0, 0, 0, 8, 197,0, 0, 0, 160,110,215,5, 0, 0, 0, 0, 130,2, 0, 0, 0, 32, 4, 4, 0, 0, 0, 160,1, 0, 0, 0, 0, 1, 71, 15, 0, 0, 0, 2, 152,0, 0, 0, 96, 162,75, 14, 0, 0, 0, 48, 36, 1, 0, 0, 160,0, 140,64, 0, 1, 0, 0, 2, 0, 4, 0, 128,34, 130,32, 0, 0, 0, 13, 145,4, 0, 0, 224,178,207,1, 0, 0, 224,161,2, 66, 4, 0, 0, 128,128,0, 4, 0, 32, 0, 1, 0, 0, 0, 64, 177,0, 0, 0, 0, 64, 0, 7, 1, 0, 0, 32, 16, 64, 0, 4, 0, 160,50, 128,3, 0, 0, 0, 0, 197,33, 0, 0, 0, 0, 142,1, 0, 0, 32, 2, 40, 66, 0, 0, 0, 8, 32, 1, 4, 0, 32, 114,136,35, 0, 0, 192,191,247,126, 0, 0, 64, 2, 201,0, 0, 0, 32, 178,41, 2, 4, 1, 32, 114,251,32, 4, 17, 0, 0, 128,0, 0, 0, 64, 0, 2, 17, 0, 1, 0, 32, 136,34, 0, 0, 0, 0, 131,65, 0, 0, 32, 2, 1, 8, 0, 0, 0, 42, 137,2, 4, 0, 64, 18, 129,0, 4, 0, 0, 64, 128,32, 0, 0, 64, 0, 0, 4, 0, 0, 32, 32, 65, 0, 0, 0, 0, 2, 193,32, 132,1, 64, 0, 136,1, 0, 0, 0, 130,133,0, 4, 0, 160,171,140,103, 0, 0, 0, 130,128,32, 4, 0, 160,162,133,41, 4, 0, 0, 16, 0, 1, 0, 0, 160,130,196,1, 0, 0, 128,18, 0, 64, 0, 0, 128,0, 128,14, 0, 0, 0, 130,138,0, 0, 0, 0, 0, 129,65, 0, 0, 224,91, 238,68, 0, 0, 128,1, 4, 1, 0, 0, 32, 1, 1, 1, 0, 0, 0, 162,154,1, 0, 0, 64, 0, 196,0, 0, 0, 0, 8, 32, 64, 0, 0, 96, 2, 136,0, 4, 0, 224,83, 206,8, 0, 0, 0, 0, 2, 72, 0, 0, 160,159,223,75, 4, 0, 96, 110,151,1, 0, 0, 64, 0, 144,69, 0, 0, 0, 66, 128,0, 4, 0, 128,98, 200,34, 0, 0, 0, 160,129,0, 0, 0, 64, 4, 2, 0, 4, 0, 160,227,204,65, 0, 0, 0, 2, 1, 5, 0, 0, 128,4, 132,0, 0, 0, 0, 4, 193,33, 0, 0, 0, 14, 132,0, 0, 0, 32, 171,207,6, 0, 0, 64, 0, 2, 64, 0, 0, 128,129,1, 0, 0, 0, 128,162,146,11, 4, 1, 0, 2, 20, 0, 0, 0, 160,130,0, 0, 0, 1, 32, 32, 0, 0, 4, 0, 0, 18, 128,1, 0, 0, 64, 162,128,0, 0, 0, 0, 2, 193,0, 0, 0, 64, 128,0, 8, 0, 0, 32, 28, 79, 69, 4, 0, 32, 8, 128,0, 4, 0, 64, 98, 130,0, 0, 0, 32, 139,68, 24, 4, 0, 225,255,223,111, 0, 0, 128,1, 164,0, 0, 0, 32, 34, 132,0, 4, 0, 0, 34, 129,0, 4, 1, 224,64, 199,33, 4, 5, 96, 107,142,0, 4, 17, 0, 0, 0, 0, 0, 0, 0, 34, 72, 1, 4, 0, 0, 1, 192,32, 0, 0, 32, 34, 129,33, 0, 0, 224,235,132,0, 0, 0, 0, 144,128,1, 0, 1, 0, 32, 0, 33, 4, 3, 0, 0, 0, 0, 0, 0, 0, 50, 145,3, 0, 0, 0, 4, 17, 33, 4, 1, 0, 32, 128,0, 4, 0, 0, 32, 0, 2, 4, 0, 160,70, 204,0, 0, 0, 64, 0, 0, 2, 0, 0, 224,251,215,5, 0, 0, 32, 171,136,32, 4, 0, 0, 1, 130,0, 0, 0, 0, 24, 136,2, 0, 0, 0, 32, 0, 34, 0, 0, 0, 2, 148,0, 0, 0, 32, 2, 136,2, 4, 0, 32, 2, 12, 0, 4, 1, 32, 0, 4, 1, 4, 0, 32, 0, 0, 1, 0, 0, 64, 0, 24, 0, 0, 0, 128,235,136,1, 0, 0, 0, 34, 10, 32, 0, 0, 160,170,8, 32, 0, 0, 0, 128,0, 72, 0, 8, 32, 35, 10, 2, 4, 1, 32, 34, 0, 0, 0, 2, 160,242,223,46, 4, 0, 224,255,223,111, 0, 0, 0, 178,1, 96, 0, 0, 0, 0, 10, 32, 0, 0, 0, 17, 201,0, 0, 0, 0, 134,0, 1, 0, 0, 96, 8, 133,73, 0, 0, 224,162,147,35, 0, 0, 0, 20, 198,3, 0, 0, 0, 32, 76, 33, 0, 0, 32, 34, 2, 2, 0, 0, 32, 162,82, 0, 0, 0, 0, 161,69, 0, 4, 0, 32, 32, 0, 0, 0, 0, 32, 6, 130,0, 0, 0, 32, 43, 140,65, 0, 0, 0, 25, 198,0, 4, 0, 0, 128,129,0, 0, 0, 32, 0, 144,0, 0, 0, 0, 2, 76, 64, 0, 0, 32, 34, 217,2, 0, 0, 32, 18, 1, 0, 0, 0, 224,43, 223,41, 0, 0, 128,192,143,3, 0, 0, 0, 2, 13, 32, 4, 1, 32, 32, 0, 0, 0, 0, 0, 130,134,0, 4, 0, 32, 34, 136,32, 0, 0, 160,51, 148,67, 0, 0, 160,50, 203,97, 4, 0, 0, 0, 193,0, 0, 0, 32, 34, 67, 32, 4, 1, 0, 0, 64, 0, 0, 0, 64, 4, 209,65, 0, 0, 0, 20, 2, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 152,135,0, 4, 0, 160,2, 129,8, 0, 0, 32, 35, 10, 2, 0, 0, 128,65, 0, 0, 0, 0, 224,191,223,109, 0, 0, 128,25, 199,13, 0, 0, 64, 0, 133,0, 0, 0, 0, 48, 137,33, 0, 0, 0, 176,0, 0, 0, 0, 192,29, 213,69, 0, 0, 0, 2, 79, 0, 0, 0, 0, 132,211,65, 0, 0, 0, 18, 1, 1, 4, 1, 32, 0, 1, 0, 0, 0, 160,163,151,5, 0, 0, 32, 34, 142,34, 0, 0, 0, 18, 70, 33, 4, 0, 0, 16, 2, 0, 0, 0, 0, 18, 132,0, 0, 0, 160,50, 8, 0, 0, 0, 0, 0, 12, 96, 0, 0, 64, 2, 31, 0, 0, 0, 0, 2, 1, 2, 0, 0, 32, 150,149,1, 4, 0, 32, 235,143,105, 0, 0, 32, 34, 73, 64, 0, 0, 64, 36, 0, 1, 0, 0, 32, 35, 8, 32, 0, 0, 96, 1, 67, 33, 4, 0, 0, 2, 12, 2, 0, 0, 0, 8, 2, 10, 0, 0, 0, 16, 21, 1, 0, 0, 224,166,207,13, 0, 0, 0, 2, 136,32, 0, 0, 0, 128,132,0, 0, 0, 128,18, 0, 1, 0, 0, 0, 32, 4, 1, 0, 0, 0, 178,133,65, 0, 0, 128,2, 68, 3, 0, 0, 32, 34, 1, 4, 0, 0, 32, 0, 17, 0, 4, 0, 192,18, 151,8, 0, 0, 0, 129,135,0, 4, 0, 0, 8, 4, 0, 0, 0, 0, 0, 5, 2, 0, 0, 224,223,223,109, 0, 0, 0, 16, 68, 0, 0, 0, 96, 34, 137,32, 4, 0, 32, 1, 198,8, 0, 0, 128,21, 213,5, 0, 0, 0, 26, 0, 0, 0, 0, 192,171,223,1, 0, 0, 32, 0, 72, 34, 0, 0, 0, 16, 72, 2, 0, 0, 96, 234,154,0, 0, 0, 0, 16, 2, 32, 0, 0, 64, 3, 68, 0, 0, 0, 160,0, 13, 0, 4, 0, 64, 162,219,66, 4, 0, 0, 0, 64, 32, 0, 0, 160,171,207,101, 4, 0, 32, 34, 197,1, 4, 0, 0, 151,210,1, 0, 0, 64, 4, 1, 1, 0, 0, 32, 2, 137,32, 0, 0, 0, 32, 70, 0, 0, 0, 96, 0, 66, 32, 0, 0, 32, 18, 11, 1, 4, 0, 0, 18, 17, 0, 0, 0, 224,160,132,13, 0, 0, 0, 18, 14, 0, 4, 0, 64, 18, 151,8, 0, 0, 64, 0, 68, 32, 0, 0, 64, 2, 6, 0, 0, 0, 32, 50, 143,1, 0, 0, 32, 162,7, 32, 0, 0, 192,0, 6, 0, 0, 0, 0, 3, 193,0, 4, 0, 160,130,132,65, 0, 0, 128,0, 10, 0, 0, 0, 0, 34, 193,32, 4, 1, 128,128,128,0, 0, 0, 32, 56, 214,11, 0, 0, 0, 16, 16, 0, 0, 0, 192,38, 22, 37, 4, 0, 64, 19, 146,0, 0, 0, 0, 130,18, 0, 0, 0, 128,2, 22, 1, 0, 0, 64, 0, 68, 9, 0, 0, 128,138,0, 1, 0, 0, 64, 2, 11, 0, 0, 0, 192,0, 212,1, 0, 0, 128,10, 134,21, 0, 0, 32, 162,8, 2, 0, 0, 224,14, 80, 2, 0, 0, 64, 66, 0, 0, 0, 0, 128,32, 144,0, 0, 0, 64, 6, 128,0, 0, 0, 192,34, 81, 72, 0, 0, 128,1, 129,0, 0, 0, 0, 4, 144,0, 0, 0, 128,14, 144,5, 0, 0, 0, 130,192,0, 0, 1, 0, 18, 136,0, 0, 0, 192,134,89, 59, 0, 0, 0, 35, 136,0, 4, 0, 32, 34, 133,0, 0, 0, 128,3, 145,1, 0, 0, 0, 18, 128,1, 0, 0, 224,255,223,79, 0, 0, 96, 2, 201,2, 0, 0, 0, 176,128,66, 0, 0, 64, 24, 213,0, 0, 0, 0, 132,148,0, 0, 0, 32, 2, 79, 2, 0, 0, 96, 162,139,1, 0, 0, 0, 128,196,0, 0, 0, 160,171,72, 105, 0, 0, 64, 168,149,33, 0, 0, 160,134,81, 0, 0, 0, 224,55, 150,35, 0, 0, 32, 0, 4, 32, 0, 0, 64, 6, 64, 0, 0, 0, 160,178,139,33, 0, 0, 0, 162,0, 2, 0, 0, 160,18, 201,67, 0, 0, 128,128,1, 0, 0, 0, 0, 130,0, 8, 0, 0, 64, 13, 68, 32, 0, 0, 0, 32, 4, 33, 0, 0, 128,35, 128,0, 0, 0, 32, 34, 3, 34, 0, 0, 224,255,151,111, 0, 0, 64, 8, 2, 0, 0, 0, 64, 2, 18, 0, 4, 0, 0, 32, 0, 0, 0, 0, 0, 128,8, 64, 0, 0, 0, 18, 128,64, 0, 0, 224,254,151,47, 0, 0, 0, 9, 132,1, 0, 0, 128,18, 131,0, 0, 0, 0, 3, 4, 64, 0, 0, 192,139,135,69, 0, 0, 64, 8, 196,0, 0, 0, 0, 138,128,0, 0, 0, 0, 18, 144,1, 4, 0, 32, 34, 9, 32, 0, 0, 0, 130,5, 0, 0, 1, 32, 2, 0, 0, 0, 0, 192,190,159,3, 0, 0, 0, 130,16, 0, 0, 0, 64, 2, 8, 0, 0, 0, 0, 36, 16, 0, 0, 0, 128,139,147,1, 0, 0, 0, 144,0, 64, 0, 0, 32, 160,16, 0, 0, 0, 224,255,215,105, 0, 0, 0, 32, 7, 1, 0, 0, 160,128,132,1, 0, 0, 0, 178,0, 64, 0, 0, 192,144,134,1, 0, 0, 32, 114,17, 64, 0, 0, 128,136,132,0, 0, 0, 96, 2, 50, 0, 0, 0, 160,171,128,65, 0, 0, 128,18, 2, 1, 0, 0, 128,166,0, 0, 0, 0, 0, 43, 9, 99, 0, 0, 0, 50, 0, 64, 0, 0, 128,146,128,1, 0, 0, 160,32, 132,65, 0, 0, 0, 160,132,0, 0, 0, 0, 138,207,67, 0, 0, 0, 128,4, 1, 4, 130,96, 186,9, 35, 0, 0, 64, 128,0, 0, 4, 0, 96, 34, 201,35, 0, 0, 0, 1, 64, 35, 0, 0, 0, 2, 70, 34, 0, 0, 32, 35, 4, 0, 0, 0, 0, 144,32, 0, 0, 0, 32, 178,105,35, 0, 0, 0, 0, 128,100, 0, 0, 0, 5, 192,40, 0, 0, 32, 2, 76, 2, 0, 0, 0, 34, 141,0, 4, 0, 32, 35, 143,42, 0, 0, 0, 0, 196,8, 4, 0, 64, 0, 128,0, 0, 0, 32, 6, 73, 2, 0, 0, 0, 2, 69, 34, 0, 0, 128,11, 0, 1, 0, 0, 32, 10, 78, 2, 0, 0, 128,0, 196,64, 0, 0, 0, 0, 34, 0, 0, 0, 32, 34, 74, 1, 0, 0, 192,9, 213,65, 0, 0, 0, 26, 8, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 38, 198,44, 0, 0, 32, 160,9, 0, 0, 0, 96, 162,154,32, 0, 0, 128,9, 196,97, 0, 0, 128,128,96, 0, 0, 0, 128,35, 201,0, 0, 0, 32, 0, 64, 33, 0, 0, 0, 48, 136,32, 0, 0, 0, 0, 68, 3, 0, 0, 0, 33, 128,1, 0, 0, 0, 178,219,1, 4, 0, 160,191,158,99, 0, 0, 32, 46, 203,41, 0, 0, 0, 128,0, 6, 0, 0, 0, 0, 138,0, 0, 0, 0, 128,69, 8, 0, 0, 224,0, 3, 1, 4, 0, 0, 0, 129,33, 0, 0, 32, 55, 207,11, 0, 0, 32, 10, 4, 1, 0, 0, 32, 4, 0, 32, 0, 0, 64, 39, 0, 0, 0, 0, 32, 50, 64, 64, 0, 0, 32, 51, 217,1, 0, 0, 160,187,206,39, 0, 0, 192,34, 2, 8, 0, 0, 32, 131,132,0, 0, 0, 128,0, 142,5, 0, 0, 128,0, 164,2, 0, 0, 128,144,32, 0, 0, 0, 128,0, 130,4, 0, 0, 0, 0, 32, 8, 0, 0, 0, 8, 0, 16, 0, 0, 160,178,153,77, 0, 0, 32, 48, 8, 2, 0, 1, 0, 64, 128,0, 0, 0, 96, 34, 9, 10, 0, 0, 0, 0, 4, 42, 0, 0, 32, 162,9, 0, 0, 0, 0, 32, 68, 1, 0, 0, 0, 34, 193,0, 0, 0, 160,178,204,71, 0, 0, 0, 0, 197,16, 0, 0, 160,8, 142,1, 0, 0, 0, 0, 196,34, 4, 0, 32, 0, 192,0, 0, 0, 192,15, 199,85, 0, 0, 64, 1, 2, 0, 0, 0, 32, 34, 199,1, 0, 0, 160,35, 0, 0, 4, 1, 0, 0, 5, 0, 0, 0, 32, 2, 5, 1, 4, 1, 160,2, 0, 1, 0, 0, 160,2, 4, 1, 0, 0, 128,0, 69, 33, 0, 0, 32, 11, 68, 33, 0, 0, 0, 8, 140,0, 4, 0, 32, 54, 199,37, 0, 0, 0, 33, 130,0, 0, 0, 160,39, 143,1, 0, 0, 0, 4, 0, 6, 0, 0, 160,34, 8, 96, 0, 0, 0, 3, 200,66, 0, 0, 224,255,255,127, 0, 0, 192,157,215,5, 0, 0, 64, 34, 129,0, 0, 0, 32, 35, 140,0, 0, 0, 0, 50, 128,1, 0, 0, 0, 1, 4, 32, 0, 0, 0, 178,0, 3, 0, 0, 0, 0, 130,8, 0, 0, 0, 2, 0, 10, 0, 0, 0, 178,73, 35, 0, 0, 0, 2, 5, 2, 0, 0, 128,0, 6, 2, 0, 0, 96, 47, 9, 106, 4, 0, 160,145,215,77, 0, 0, 160,134,73, 1, 0, 0, 0, 0, 64, 6, 0, 0, 0, 2, 136,40, 4, 0, 32, 42, 207,72, 0, 0, 128,0, 197,2, 0, 0, 0, 0, 12, 1, 0, 0, 0, 2, 136,1, 0, 0, 32, 2, 78, 1, 0, 0, 224,207,151,5, 0, 0, 96, 58, 136,8, 0, 0, 32, 50, 128,35, 0, 0, 160,18, 4, 64, 0, 0, 224,162,27, 2, 0, 0, 0, 32, 196,32, 0, 0, 0, 128,72, 0, 0, 0, 128,0, 69, 0, 0, 0, 32, 1, 129,32, 0, 0, 160,187,140,65, 4, 0, 0, 16, 64, 0, 0, 0, 0, 26, 4, 3, 0, 0, 0, 0, 4, 16, 0, 0, 0, 32, 68, 16, 0, 0, 0, 32, 136,64, 4, 0, 0, 0, 215,1, 0, 0, 32, 58, 8, 1, 0, 0, 32, 3, 1, 0, 0, 0, 32, 34, 93, 0, 0, 0, 0, 136,64, 0, 0, 0, 160,2, 14, 0, 0, 0, 0, 0, 4, 34, 0, 0, 224,178,151,3, 0, 0, 32, 34, 11, 0, 4, 0, 224,51, 138,1, 0, 3, 0, 0, 4, 0, 0, 0, 0, 41, 8, 0, 4, 0, 0, 48, 130,0, 0, 0, 192,128,4, 1, 0, 0, 0, 34, 229,33, 0, 0, 0, 10, 128,1, 0, 0, 32, 34, 128,1, 0, 0, 0, 8, 32, 0, 0, 0, 32, 0, 128,97, 0, 0, 32, 35, 0, 33, 0, 0, 0, 32, 2, 3, 0, 0, 64, 3, 192,6, 0, 0, 160,0, 9, 0, 4, 0, 224,207,255,71, 0, 0, 64, 160,222,0, 0, 0, 96, 34, 64, 34, 0, 0, 32, 0, 2, 1, 0, 0, 0, 34, 131,1, 0, 0, 192,13, 196,11, 0, 0, 128,162,139,0, 0, 0, 0, 130,3, 0, 4, 0, 64, 24, 193,1, 0, 0, 32, 58, 8, 2, 4, 1, 192,54, 133,40, 0, 0, 0, 1, 133,8, 0, 0, 96, 98, 9, 32, 4, 0, 128,16, 0, 0, 0, 0, 96, 10, 128,0, 0, 0, 96, 34, 26, 0, 0, 0, 32, 2, 200,2, 0, 0, 0, 16, 193,40, 4, 0, 0, 8, 133,2, 0, 0, 160,191,173,79, 0, 0, 96, 62, 237,32, 4, 1, 0, 16, 3, 2, 0, 0, 0, 2, 71, 0, 0, 0, 32, 2, 1, 33, 4, 0, 96, 17, 198,3, 4, 0, 32, 138,9, 2, 0, 0, 0, 36, 128,0, 0, 0, 32, 0, 7, 32, 0, 0, 128,130,9, 0, 0, 0, 0, 16, 140,64, 4, 0, 64, 146,128,0, 0, 0, 32, 182,158,1, 0, 0, 128,130,8, 1, 0, 0, 224,178,137,65, 4, 0, 64, 0, 5, 0, 0, 0, 224,178,131,77, 0, 0, 0, 2, 203,0, 0, 1, 0, 68, 210,9, 0, 0, 32, 34, 136,4, 4, 0, 0, 2, 146,0, 4, 0, 96, 2, 8, 64, 4, 0, 64, 0, 74, 0, 0, 0, 32, 6, 8, 5, 0, 0, 224,255,223,111, 0, 0, 128,37, 68, 1, 0, 0, 96, 35, 140,2, 0, 0, 32, 51, 207,10, 0, 0, 0, 42, 64, 8, 0, 0, 64, 20, 192,77, 0, 0, 32, 12, 33, 1, 0, 0, 0, 48, 136,34, 0, 0, 32, 58, 136,10, 0, 0, 128,0, 128,3, 0, 0, 32, 0, 198,1, 0, 0, 32, 129,196,1, 0, 0, 0, 178,0, 0, 0, 0, 96, 2, 24, 2, 0, 0, 0, 0, 132,48, 0, 0, 160,43, 140,64, 4, 1, 0, 0, 8, 0, 0, 0, 0, 0, 193,40, 0, 0, 0, 8, 136,0, 4, 0, 224,119,143,9, 0, 0, 0, 1, 196,4, 0, 0, 0, 17, 0, 9, 0, 0, 64, 140,222,1, 0, 0, 32, 34, 16, 0, 0, 0, 0, 18, 128,32, 0, 0, 32, 34, 208,0, 0, 0, 0, 0, 194,64, 0, 0, 224,43, 206,35, 4, 0, 0, 34, 128,32, 0, 0, 32, 3, 5, 1, 0, 0, 160,2, 128,65, 0, 0, 160,2, 143,1, 0, 0, 32, 11, 0, 0, 0, 0, 160,178,136,1, 0, 0, 0, 130,192,32, 4, 0, 0, 48, 128,67, 4, 0, 160,171,245,21, 0, 0, 32, 34, 128,4, 0, 0, 32, 34, 0, 4, 0, 0, 64, 8, 5, 1, 4, 33, 224,97, 199,33, 0, 0, 128,0, 64, 4, 4, 1, 96, 32, 134,0, 0, 0, 0, 35, 199,33, 0, 0, 0, 3, 2, 0, 0, 0, 160,1, 8, 0, 0, 0, 224,255,255,117, 0, 0, 96, 34, 72, 32, 0, 0, 160,178,64, 98, 0, 0, 32, 7, 13, 3, 4, 0, 32, 34, 138,32, 0, 0, 0, 1, 68, 33, 4, 65, 0, 1, 136,0, 0, 0, 0, 43, 9, 44, 0, 0, 192,9, 65, 5, 0, 0, 0, 130,0, 1, 0, 0, 0, 145,0, 1, 0, 0, 0, 132,0, 1, 0, 0, 128,8, 128,64, 4, 0, 0, 16, 196,8, 0, 0, 96, 32, 147,0, 0, 0, 0, 96, 0, 33, 0, 0, 0, 130,128,34, 0, 0, 160,11, 132,65, 4, 0, 128,0, 68, 1, 0, 0, 64, 34, 64, 1, 0, 0, 96, 58, 162,32, 0, 0, 0, 34, 24, 3, 0, 0, 160,162,65, 68, 0, 0, 32, 3, 4, 0, 0, 0, 224,146,128,1, 0, 0, 0, 34, 192,32, 0, 0, 0, 210,74, 73, 0, 2, 0, 2, 0, 0, 0, 0, 0, 2, 0, 3, 0, 0, 32, 48, 0, 64, 0, 0, 224,167,207,5, 0, 0, 96, 0, 64, 0, 0, 0, 32, 2, 24, 0, 0, 0, 160,139,141,24, 0, 0, 160,0, 1, 1, 4, 16, 224,179,223,107, 0, 0, 96, 144,131,0, 0, 0, 32, 0, 130,0, 0, 0, 128,24, 192,0, 0, 0, 64, 34, 73, 0, 4, 0, 224,191,127,46, 0, 0, 0, 1, 69, 10, 0, 0, 32, 2, 8, 1, 0, 0, 64, 0, 65, 0, 0, 0, 0, 129,0, 32, 0, 0, 0, 32, 72, 34, 0, 0, 32, 34, 13, 3, 0, 0, 32, 2, 140,4, 4, 0, 32, 34, 141,32, 0, 0, 128,32, 64, 2, 0, 0, 160,187,207,42, 0, 0, 0, 12, 67, 32, 0, 0, 0, 2, 4, 16, 0, 0, 128,8, 0, 16, 0, 0, 0, 32, 71, 8, 0, 0, 32, 10, 133,1, 0, 0, 0, 1, 4, 16, 0, 0, 0, 0, 69, 16, 0, 0, 0, 9, 1, 64, 0, 0, 64, 9, 197,64, 0, 0, 32, 36, 0, 0, 0, 0, 96, 43, 221,42, 0, 0, 0, 1, 69, 65, 0, 0, 0, 3, 197,5, 0, 0, 128,1, 133,0, 4, 5, 32, 160,130,0, 0, 0, 128,2, 32, 0, 0, 0, 32, 35, 79, 2, 0, 0, 64, 4, 85, 1, 0, 0, 0, 2, 2, 2, 0, 0, 32, 11, 207,65, 4, 0, 64, 50, 195,0, 0, 0, 32, 14, 137,13, 0, 0, 160,162,8, 2, 0, 0, 0, 2, 192,9, 0, 0, 0, 0, 64, 36, 0, 0, 0, 1, 217,3, 0, 0, 0, 10, 136,0, 0, 0, 224,183,159,39, 0, 0, 192,184,196,37, 0, 0, 128,128,132,37, 0, 0, 224,136,132,64, 0, 0, 32, 34, 200,32, 0, 0, 0, 0, 70, 104, 0, 0, 64, 8, 32, 0, 0, 0, 128,0, 76, 35, 0, 0, 96, 34, 8, 64, 4, 0, 128,0, 132,0, 0, 0, 0, 24, 205,1, 0, 0, 32, 1, 194,0, 0, 0, 0, 50, 8, 2, 0, 0, 0, 0, 65, 68, 0, 0, 96, 0, 1, 0, 0, 0, 160,10, 4, 0, 0, 0, 0, 0, 192,34, 0, 0, 32, 50, 73, 34, 0, 0, 160,14, 153,1, 0, 0, 32, 50, 140,32, 0, 0, 0, 1, 97, 32, 0, 0, 0, 2, 140,16, 0, 0, 0, 0, 205,8, 0, 0, 32, 32, 72, 32, 0, 0, 0, 0, 36, 0, 0, 0, 0, 166,9, 2, 0, 0, 32, 50, 73, 98, 0, 0, 32, 6, 192,1, 4, 0, 32, 8, 0, 0, 0, 0, 0, 18, 145,2, 0, 0, 224,251,255,119, 0, 0, 192,185,132,7, 0, 0, 32, 1, 8, 1, 0, 0, 32, 2, 65, 2, 0, 0, 32, 50, 72, 98, 0, 0, 32, 2, 5, 2, 4, 0, 0, 0, 134,3, 0, 0, 128,0, 130,0, 0, 0, 32, 128,133,65, 0, 0, 128,8, 8, 0, 0, 0, 32, 50, 72, 34, 0, 0, 0, 0, 64, 3, 4, 0, 32, 2, 0, 1, 4, 0, 0, 0, 196,1, 0, 0, 0, 2, 0, 35, 4, 0, 0, 0, 65, 1, 0, 0, 224,202,157,7, 4, 1, 128,16, 6, 0, 0, 0, 32, 146,65, 0, 4, 1, 0, 0, 65, 1, 0, 0, 32, 43, 8, 2, 4, 0, 128,0, 64, 1, 0, 1, 0, 0, 193,64, 4, 33, 0, 0, 4, 1, 4, 0, 160,18, 136,0, 0, 0, 32, 32, 4, 64, 4, 1, 32, 58, 139,42, 0, 0, 0, 3, 193,33, 4, 0, 32, 34, 144,1, 0, 0, 0, 4, 128,12, 0, 0, 64, 0, 2, 1, 0, 0, 96, 34, 8, 32, 0, 0, 0, 2, 68, 33, 4, 0, 0, 0, 68, 8, 0, 0, 128,32, 13, 7, 0, 0, 0, 5, 0, 1, 0, 0, 0, 2, 192,65, 0, 0, 0, 0, 36, 9, 0, 0, 0, 68, 0, 0, 0, 0, 160,51, 9, 65, 4, 0, 96, 2, 2, 0, 0, 0, 32, 50, 140,98, 0, 0, 32, 2, 198,0, 4, 1, 32, 35, 136,0, 0, 0, 32, 128,129,80, 4, 0, 0, 2, 160,64, 4, 0, 96, 66, 193,0, 0, 0, 224,179,233,3, 0, 0, 0, 160,2, 0, 0, 8, 96, 2, 12, 0, 4, 1, 0, 2, 128,0, 0, 0, 128,8, 68, 32, 0, 0, 0, 0, 197,35, 0, 0, 32, 2, 6, 4, 4, 1, 192,38, 146,32, 0, 0, 0, 40, 20, 0, 0, 0, 128,1, 65, 0, 0, 0, 160,178,205,99, 0, 0, 0, 1, 195,5, 0, 0, 64, 34, 2, 0, 0, 0, 0, 0, 8, 16, 4, 0, 0, 3, 200,34, 0, 0, 32, 34, 64, 34, 0, 0, 0, 32, 198,40, 0, 0, 0, 0, 128,10, 0, 0, 160,0, 133,0, 0, 0, 0, 0, 128,3, 0, 0, 32, 0, 2, 16, 0, 0, 0, 128,8, 2, 0, 0, 0, 33, 142,66, 4, 0, 32, 178,200,32, 0, 0, 0, 33, 200,8, 4, 0, 32, 2, 132,0, 0, 0, 0, 2, 128,40, 0, 0, 160,2, 141,16, 0, 0, 128,0, 140,16, 0, 0, 0, 128,193,1, 4, 0, 128,175,213,69, 0, 0, 0, 3, 1, 1, 4, 1, 224,55, 138,25, 4, 0, 32, 34, 8, 2, 0, 0, 0, 0, 5, 64, 4, 0, 224,23, 223,13, 4, 0, 0, 128,5, 0, 0, 0, 0, 2, 197,0, 0, 0, 32, 1, 196,3, 0, 0, 32, 35, 8, 98, 0, 32, 0, 0, 64, 0, 0, 0, 32, 2, 36, 0, 0, 0, 224,255,255,79, 0, 0, 64, 5, 71, 7, 0, 0, 32, 34, 25, 0, 0, 0, 0, 2, 0, 20, 0, 0, 32, 35, 4, 2, 0, 0, 32, 2, 64, 2, 0, 0, 32, 16, 73, 2, 0, 0, 96, 45, 199,43, 0, 0, 0, 11, 197,37, 0, 0, 32, 18, 4, 33, 0, 0, 0, 128,1, 1, 0, 0, 0, 36, 198,35, 0, 0, 32, 3, 0, 32, 0, 0, 32, 2, 197,0, 0, 0, 32, 1, 12, 1, 0, 0, 0, 2, 192,34, 0, 0, 32, 32, 207,47, 0, 0, 96, 32, 2, 0, 0, 0, 32, 160,4, 4, 0, 0, 0, 129,66, 37, 0, 0, 128,131,68, 40, 0, 0, 0, 16, 192,32, 0, 0, 96, 34, 199,1, 0, 0, 128,0, 2, 2, 0, 0, 0, 3, 65, 1, 0, 0, 0, 1, 68, 4, 0, 0, 32, 0, 1, 4, 0, 0, 0, 0, 69, 10, 0, 0, 32, 0, 5, 14, 0, 0, 0, 21, 65, 44, 0, 0, 0, 1, 65, 3, 0, 0, 0, 2, 64, 1, 0, 0, 0, 16, 68, 15, 0, 0, 0, 34, 64, 36, 0, 0, 32, 18, 8, 9, 0, 0, 0, 16, 0, 4, 0, 0, 64, 0, 69, 4, 0, 0, 0, 0, 1, 5, 0, 0, 128,0, 128,4, 0, 0, 0, 1, 130,3, 0, 0, 0, 4, 69, 14, 0, 0, 0, 33, 20, 41, 0, 0, 128,36, 64, 32, 0, 0, 0, 2, 197,4, 0, 0, 0, 0, 64, 12, 0, 0, 0, 34, 72, 34, 0, 0, 32, 34, 13, 2, 0, 0, 0, 40, 7, 32, 0, 0, 160,34, 205,33, 0, 0, 0, 16, 5, 4, 0, 0, 0, 1, 68, 64, 0, 0, 64, 3, 135,5, 0, 0, 0, 2, 133,64, 0, 0, 128,36, 70, 46, 0, 0, 32, 34, 207,0, 0, 0, 128,176,196,1, 0, 0, 32, 32, 4, 2, 0, 0, 0, 1, 80, 1, 0, 0, 64, 32, 67, 35, 0, 0, 32, 38, 67, 9, 0, 0, 128,0, 193,0, 0, 0, 128,6, 132,1, 0, 0, 0, 8, 65, 1, 0, 0, 0, 32, 0, 3, 0, 0, 0, 33, 64, 32, 0, 0, 0, 8, 197,1, 0, 0, 0, 16, 24, 8, 0, 0, 32, 50, 64, 0, 4, 0, 224,255,223,123, 0, 0, 192,153,197,3, 0, 0, 0, 34, 132,41, 0, 0, 64, 16, 129,32, 0, 0, 128,144,0, 66, 0, 0, 160,39, 201,6, 0, 0, 32, 32, 65, 4, 0, 0, 160,2, 12, 7, 0, 1, 32, 32, 128,0, 0, 0, 0, 32, 65, 1, 0, 0, 0, 4, 0, 64, 0, 0, 128,154,199,1, 4, 0, 32, 58, 1, 0, 0, 0, 0, 1, 87, 1, 0, 0, 32, 0, 65, 1, 0, 0, 0, 50, 0, 1, 0, 0, 0, 139,196,17, 0, 0, 0, 24, 128,0, 0, 0, 0, 179,0, 65, 4, 0, 224,63, 237,39, 0, 0, 128,33, 128,32, 0, 0, 160,1, 133,3, 0, 0, 32, 16, 128,0, 0, 0, 128,1, 4, 33, 0, 0, 0, 35, 136,64, 0, 0, 64, 2, 26, 0, 0, 0, 0, 32, 192,0, 4, 0, 160,235,140,67, 0, 0, 128,136,80, 0, 0, 0, 224,2, 12, 68, 0, 0, 0, 0, 133,64, 0, 0, 32, 6, 133,0, 0, 0, 32, 4, 4, 0, 0, 0, 0, 0, 84, 3, 0, 0, 160,34, 0, 1, 0, 0, 32, 50, 8, 64, 0, 0, 160,175,207,109, 0, 0, 0, 0, 132,65, 0, 0, 32, 178,0, 34, 0, 0, 0, 134,1, 0, 0, 0, 128,1, 133,9, 0, 0, 160,30, 15, 9, 0, 0, 0, 50, 8, 64, 0, 0, 160,179,235,65, 0, 0, 32, 34, 7, 32, 0, 2, 160,58, 201,65, 0, 0, 64, 0, 19, 32, 0, 1, 0, 0, 129,0, 0, 0, 32, 34, 8, 6, 0, 0, 0, 42, 5, 0, 0, 0, 160,223,239,115, 4, 0, 0, 16, 14, 0, 0, 0, 160,146,65, 0, 0, 0, 32, 34, 13, 0, 0, 0, 0, 2, 69, 2, 4, 3, 32, 34, 128,0, 0, 0, 160,34, 136,0, 0, 0, 0, 1, 0, 16, 0, 0, 32, 38, 137,0, 0, 0, 0, 138,8, 1, 4, 0, 0, 34, 1, 32, 0, 0, 192,4, 148,8, 0, 0, 32, 139,205,43, 0, 0, 64, 0, 2, 9, 0, 0, 128,129,193,1, 0, 0, 0, 130,132,0, 0, 0, 128,16, 4, 64, 0, 0, 32, 25, 69, 2, 4, 0, 32, 32, 4, 32, 0, 0, 32, 2, 10, 0, 4, 35, 0, 0, 131,32, 0, 0, 96, 183,237,11, 0, 0, 0, 9, 70, 64, 0, 0, 0, 35, 8, 0, 0, 0, 0, 0, 72, 34, 0, 0, 32, 0, 192,40, 0, 1, 0, 128,0, 1, 0, 0, 32, 0, 65, 2, 0, 0, 160,0, 136,1, 0, 0, 0, 34, 130,32, 0, 0, 32, 58, 142,41, 0, 0, 0, 8, 68, 32, 0, 0, 0, 35, 4, 0, 4, 1, 64, 0, 128,0, 0, 0, 160,138,206,0, 0, 0, 32, 0, 16, 1, 4, 0, 0, 0, 132,1, 0, 0, 64, 34, 138,0, 0, 0, 160,251,140,55, 0, 0, 0, 146,0, 32, 0, 0, 0, 1, 2, 32, 4, 0, 32, 38, 129,40, 4, 0, 32, 21, 203,32, 0, 0, 32, 32, 136,4, 0, 0, 224,6, 142,20, 0, 0, 128,144,0, 1, 0, 0, 128,6, 128,16, 0, 0, 32, 8, 64, 65, 0, 0, 0, 144,8, 1, 0, 0, 32, 2, 136,98, 0, 0, 160,34, 204,65, 0, 0, 192,8, 71, 0, 0, 0, 0, 32, 129,32, 0, 0, 192,255,223,3, 0, 0, 32, 18, 72, 0, 0, 0, 0, 162,1, 0, 0, 0, 160,0, 0, 32, 0, 0, 1, 0, 128,0, 0, 0, 32, 130,73, 0, 0, 0, 0, 0, 20, 8, 0, 0, 0, 34, 24, 35, 0, 0, 32, 34, 73, 34, 0, 0, 160,50, 204,65, 4, 0, 32, 34, 0, 2, 0, 0, 32, 34, 137,0, 0, 0, 0, 34, 4, 4, 0, 0, 0, 34, 136,3, 0, 0, 0, 144,130,64, 0, 0, 0, 2, 64, 66, 0, 0, 0, 144,18, 0, 0, 0, 0, 129,5, 1, 0, 0, 160,146,0, 65, 0, 0, 0, 34, 136,34, 0, 0, 32, 50, 1, 98, 0, 0, 32, 2, 9, 32, 4, 0, 128,17, 197,0, 0, 0, 32, 2, 1, 10, 0, 0, 32, 35, 199,0, 0, 0, 64, 32, 70, 8, 0, 0, 32, 43, 141,97, 0, 0, 0, 34, 200,32, 0, 0, 128,144,192,33, 0, 0, 0, 0, 198,1, 4, 0, 32, 2, 154,0, 4, 0, 32, 0, 136,2, 0, 0, 160,255,204,67, 0, 0, 128,24, 193,1, 0, 0, 32, 50, 64, 2, 0, 0, 0, 8, 129,33, 4, 1, 64, 0, 146,0, 0, 0, 32, 0, 73, 0, 0, 0, 0, 0, 75, 0, 0, 0, 0, 33, 8, 0, 0, 0, 128,138,32, 65, 0, 0, 128,24, 81, 17, 0, 0, 32, 34, 8, 1, 0, 0, 0, 10, 0, 4, 0, 0, 96, 62, 220,102, 0, 0, 0, 8, 165,4, 0, 0, 64, 0, 0, 7, 0, 0, 32, 3, 8, 32, 0, 1, 0, 0, 64, 0, 0, 0, 160,106,199,64, 0, 0, 32, 33, 0, 0, 0, 0, 0, 66, 129,0, 0, 0, 0, 8, 68, 34, 0, 0, 128,139,215,1, 4, 0, 64, 35, 131,0, 0, 0, 32, 50, 130,0, 0, 0, 0, 21, 193,0, 0, 1, 0, 0, 144,0, 0, 0, 224,175,255,37, 0, 0, 192,41, 133,13, 0, 0, 0, 9, 8, 1, 4, 0, 32, 119,143,33, 0, 0, 160,0, 8, 1, 0, 0, 0, 0, 8, 65, 4, 1, 32, 4, 0, 1, 0, 0, 32, 130,135,1, 0, 0, 128,0, 132,64, 0, 0, 0, 1, 0, 10, 0, 0, 0, 18, 200,0, 0, 0, 32, 42, 11, 34, 4, 0, 0, 32, 129,0, 0, 0, 128,4, 130,0, 0, 0, 224,178,163,1, 0, 0, 0, 0, 132,8, 0, 0, 64, 0, 195,32, 0, 0, 32, 50, 137,97, 4, 1, 0, 52, 129,0, 0, 0, 0, 4, 9, 4, 0, 0, 128,0, 69, 8, 0, 0, 192,32, 136,0, 0, 0, 32, 1, 197,73, 0, 0, 128,9, 128,1, 0, 0, 160,102,128,0, 0, 1, 0, 0, 193,37, 4, 0, 0, 38, 144,40, 4, 0, 0, 1, 194,0, 0, 0, 96, 35, 69, 0, 0, 0, 32, 3, 5, 0, 0, 128,0, 2, 0, 0, 0, 0, 96, 32, 136,2, 0, 0, 128,255,223,67, 0, 0, 128,144,8, 2, 0, 0, 32, 2, 11, 0, 0, 0, 0, 34, 65, 0, 0, 0, 0, 9, 192,4, 0, 0, 128,8, 197,1, 0, 0, 0, 35, 9, 32, 0, 0, 224,183,97, 13, 0, 0, 96, 33, 132,0, 0, 0, 32, 40, 128,0, 0, 0, 0, 128,0, 16, 0, 0, 0, 0, 22, 1, 0, 0, 0, 16, 64, 32, 4, 0, 96, 4, 146,0, 0, 0, 160,175,205,97, 0, 0, 0, 50, 64, 32, 0, 0, 32, 34, 136,41, 0, 0, 0, 16, 4, 32, 0, 0, 128,2, 0, 69, 0, 0, 128,2, 8, 1, 0, 0, 160,173,95, 1, 0, 0, 128,129,15, 41, 0, 0, 160,186,133,67, 0, 0, 32, 18, 9, 2, 0, 0, 0, 0, 64, 34, 0, 0, 160,0, 5, 0, 4, 1, 32, 176,2, 0, 4, 0, 32, 34, 12, 32, 0, 0, 128,36, 68, 0, 0, 51, 0, 0, 128,0, 0, 0, 160,50, 137,67, 0, 0, 32, 0, 32, 0, 0, 0, 224,155,221,19, 0, 0, 32, 34, 13, 1, 0, 0, 0, 2, 17, 1, 0, 0, 128,132,32, 0, 4, 0, 224,235,12, 43, 0, 0, 224,237,255,111, 0, 0, 128,146,0, 0, 0, 0, 0, 4, 0, 65, 0, 0, 32, 10, 137,32, 0, 0, 0, 32, 14, 1, 0, 0, 0, 129,195,5, 0, 0, 32, 178,8, 0, 0, 0, 0, 56, 142,33, 0, 0, 96, 2, 1, 0, 0, 0, 96, 34, 2, 2, 4, 0, 0, 1, 129,1, 0, 0, 160,43, 140,1, 0, 0, 192,0, 136,1, 0, 0, 0, 0, 0, 66, 0, 0, 32, 176,153,0, 0, 0, 160,33, 76, 46, 0, 0, 0, 32, 6, 32, 0, 0, 160,51, 137,9, 0, 0, 32, 32, 64, 32, 4, 0, 160,50, 8, 3, 4, 1, 32, 32, 128,0, 0, 0, 0, 0, 136,4, 4, 0, 32, 0, 128,8, 0, 0, 0, 12, 131,33, 0, 0, 0, 64, 197,0, 0, 0, 32, 40, 0, 64, 4, 1, 224,255,223,127, 0, 0, 128,9, 197,7, 0, 0, 0, 34, 9, 32, 0, 0, 0, 16, 13, 0, 0, 0, 64, 2, 65, 2, 0, 0, 32, 130,72, 1, 4, 0, 0, 32, 129,1, 4, 0, 32, 3, 64, 0, 0, 0, 128,23, 193,0, 4, 0, 0, 50, 197,32, 0, 0, 0, 0, 210,0, 0, 0, 0, 3, 0, 32, 0, 0, 32, 0, 73, 2, 0, 0, 160,157,199,1, 4, 0, 0, 48, 129,0, 0, 0, 0, 161,139,0, 0, 0, 64, 34, 72, 0, 0, 0, 128,0, 192,1, 0, 0, 32, 40, 4, 0, 0, 0, 32, 16, 8, 1, 0, 0, 64, 39, 206,36, 0, 0, 64, 16, 131,1, 0, 0, 32, 16, 137,73, 0, 0, 0, 0, 1, 41, 0, 0, 64, 30, 70, 73, 0, 0, 0, 16, 145,1, 0, 0, 0, 0, 197,3, 4, 0, 0, 0, 131,1, 0, 0, 0, 0, 133,3, 0, 0, 224,182,191,33, 0, 0, 128,20, 219,11, 0, 0, 0, 16, 3, 0, 0, 0, 32, 16, 8, 34, 0, 0, 224,159,215,93, 0, 0, 160,146,9, 2, 0, 0, 32, 8, 193,41, 4, 0, 0, 34, 141,32, 0, 0, 0, 1, 197,32, 0, 0, 0, 1, 129,3, 4, 1, 0, 0, 1, 1, 0, 0, 192,17, 199,13, 0, 0, 64, 2, 64, 64, 0, 0, 160,34, 0, 0, 0, 0, 32, 24, 0, 0, 0, 0, 0, 0, 8, 66, 0, 0, 0, 26, 64, 0, 4, 0, 128,62, 139,41, 0, 0, 32, 11, 129,0, 0, 0, 64, 33, 194,1, 0, 0, 32, 78, 12, 1, 0, 0, 64, 32, 192,1, 0, 0, 64, 144,136,9, 0, 0, 0, 17, 10, 32, 0, 0, 32, 130,192,73, 0, 0, 0, 8, 0, 18, 0, 0, 64, 34, 73, 2, 0, 0, 0, 0, 194,1, 0, 0, 0, 178,0, 1, 0, 0, 32, 35, 128,2, 0, 0, 128,0, 3, 0, 0, 0, 32, 10, 1, 2, 0, 0, 32, 39, 1, 1, 0, 0, 160,46, 140,4, 0, 0, 0, 0, 4, 17, 0, 0, 128,129,204,0, 0, 0, 224,175,227,13, 4, 0, 0, 17, 1, 0, 0, 0, 96, 58, 163,32, 4, 0, 128,0, 1, 0, 0, 0, 0, 2, 0, 9, 0, 0, 0, 16, 8, 64, 0, 0, 160,34, 193,10, 0, 0, 64, 129,192,32, 0, 0, 160,50, 76, 9, 0, 0, 128,4, 12, 0, 0, 0, 0, 8, 196,4, 0, 0, 0, 32, 129,1, 0, 0, 160,0, 128,1, 4, 0, 32, 51, 5, 0, 0, 0, 128,129,0, 32, 0, 16, 0, 0, 12, 0, 0, 0, 224,173,223,105, 0, 0, 0, 0, 17, 1, 0, 0, 96, 2, 18, 0, 4, 0, 160,18, 141,66, 0, 0, 0, 0, 130,64, 0, 0, 0, 18, 192,1, 0, 0, 224,82, 194,1, 0, 0, 32, 16, 8, 32, 0, 0, 224,91, 251,32, 0, 0, 0, 16, 4, 64, 0, 0, 32, 32, 66, 0, 0, 0, 192,155,71, 1, 0, 0, 0, 128,192,1, 0, 0, 0, 35, 131,9, 0, 0, 0, 50, 131,0, 0, 0, 32, 99, 205,37, 0, 0, 32, 33, 197,32, 0, 0, 0, 25, 75, 1, 0, 0, 0, 8, 32, 5, 0, 0, 128,8, 2, 0, 0, 0, 128,34, 8, 1, 0, 0, 160,178,8, 3, 0, 0, 0, 32, 135,0, 0, 0, 0, 0, 81, 0, 4, 0, 32, 59, 77, 42, 0, 0, 64, 34, 137,0, 0, 0, 96, 24, 199,1, 0, 0, 0, 16, 70, 10, 0, 0, 128,17, 195,1, 0, 0, 0, 128,128,1, 4, 0, 32, 22, 0, 1, 0, 0, 96, 50, 205,2, 4, 0, 0, 35, 130,0, 0, 0, 96, 33, 73, 2, 0, 0, 0, 0, 196,9, 0, 0, 32, 50, 203,33, 0, 0, 0, 40, 197,32, 0, 0, 0, 34, 194,0, 0, 0, 32, 34, 73, 3, 0, 0, 32, 5, 6, 96, 0, 0, 0, 2, 65, 32, 0, 0, 0, 128,68, 32, 0, 0, 64, 1, 70, 0, 0, 0, 224,47, 159,37, 0, 0, 0, 28, 133,66, 4, 0, 32, 54, 202,0, 0, 0, 32, 72, 198,53, 0, 0, 0, 14, 22, 4, 4, 1, 32, 32, 8, 0, 0, 0, 0, 14, 128,4, 0, 0, 0, 18, 136,65, 0, 0, 0, 16, 69, 0, 0, 0, 32, 8, 69, 0, 0, 0, 128,8, 132,1, 0, 0, 128,16, 64, 0, 4, 0, 224,171,141,41, 4, 0, 32, 32, 137,33, 4, 0, 0, 0, 10, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 145,26, 73, 0, 0, 0, 0, 18, 2, 0, 0, 32, 55, 209,3, 0, 0, 0, 35, 133,2, 0, 0, 32, 2, 4, 3, 0, 0, 128,0, 14, 0, 4, 0, 224,171,223,79, 0, 0, 192,16, 7, 1, 0, 0, 32, 34, 12, 10, 0, 0, 0, 0, 4, 36, 0, 0, 96, 108,15, 1, 0, 0, 32, 2, 2, 1, 0, 0, 128,14, 13, 1, 0, 0, 32, 3, 128,0, 0, 0, 32, 0, 212,66, 0, 0, 224,32, 8, 0, 0, 0, 32, 50, 15, 0, 0, 0, 224,186,155,15, 0, 0, 0, 128,66, 0, 0, 0, 0, 0, 8, 34, 0, 0, 0, 40, 64, 0, 0, 0, 0, 130,68, 0, 0, 0, 64, 2, 65, 8, 0, 0, 64, 28, 3, 0, 0, 0, 0, 4, 130,8, 0, 0, 32, 50, 136,9, 0, 0, 0, 0, 198,32, 0, 128,192,43, 197,65, 0, 0, 0, 65, 3, 0, 4, 4, 224,191,223,13, 0, 0, 0, 162,18, 1, 0, 4, 32, 34, 0, 2, 0, 0, 224,223,247,109, 0, 0, 32, 16, 72, 0, 0, 0, 32, 0, 14, 0, 0, 0, 0, 32, 72, 2, 0, 0, 160,0, 72, 0, 0, 0, 32, 2, 4, 16, 0, 0, 160,34, 141,10, 0, 0, 64, 38, 5, 32, 0, 0, 96, 34, 11, 66, 0, 0, 0, 2, 32, 0, 0, 4, 160,247,221,63, 128,0, 0, 0, 8, 0, 0, 0, 0, 2, 137,0, 0, 0, 128,178,11, 65, 0, 0, 128,0, 9, 0, 0, 0, 0, 4, 12, 1, 0, 0, 0, 43, 203,2, 0, 0, 160,0, 1, 0, 0, 0, 0, 128,1, 65, 0, 0, 32, 1, 193,1, 0, 0, 0, 137,197,69, 4, 0, 0, 134,137,1, 0, 0, 32, 162,155,35, 0, 0, 128,5, 70, 67, 0, 0, 0, 128,204,37, 0, 0, 32, 0, 10, 32, 0, 0, 96, 35, 168,1, 4, 0, 0, 32, 0, 1, 0, 0, 0, 2, 192,1, 0, 0, 0, 0, 142,0, 0, 0, 96, 32, 8, 0, 4, 0, 32, 34, 16, 2, 0, 0, 128,8, 194,0, 0, 0, 64, 128,145,1, 0, 0, 96, 34, 193,0, 0, 0, 128,144,136,0, 0, 0, 64, 1, 1, 0, 0, 0, 32, 50, 200,34, 0, 0, 0, 64, 4, 0, 0, 0, 32, 162,80, 0, 4, 0, 64, 9, 197,96, 4, 0, 192,2, 128,8, 0, 0, 0, 2, 5, 1, 0, 0, 160,191,223,69, 0, 0, 192,201,151,33, 0, 0, 0, 34, 12, 34, 0, 0, 0, 193,132,0, 4, 0, 32, 50, 128,0, 0, 0, 32, 128,129,64, 0, 1, 0, 50, 0, 0, 4, 0, 32, 50, 131,8, 0, 0, 0, 2, 64, 65, 0, 0, 0, 0, 64, 10, 0, 0, 128,1, 1, 0, 0, 0, 128,0, 135,64, 0, 0, 0, 8, 141,0, 0, 0, 0, 18, 9, 0, 4, 0, 160,50, 205,32, 0, 0, 0, 3, 68, 0, 0, 0, 160,3, 132,1, 0, 0, 160,178,169,73, 0, 0, 32, 16, 8, 2, 0, 1, 32, 162,26, 9, 0, 0, 64, 8, 65, 1, 128,0, 0, 1, 8, 0, 0, 1, 0, 1, 0, 1, 0, 3, 1, 0, 128,0, 0, 0, 0, 1, 65, 2, 4, 0, 160,50, 207,65, 0, 0, 0, 137,9, 0, 0, 0, 0, 2, 197,1, 0, 0, 32, 6, 136,8, 0, 0, 0, 32, 4, 2, 0, 0, 160,1, 12, 0, 0, 0, 0, 3, 196,0, 0, 0, 0, 9, 145,1, 4, 0, 32, 179,153,34, 0, 0, 0, 130,4, 1, 0, 0, 0, 129,3, 32, 0, 0, 32, 146,0, 0, 0, 0, 0, 34, 133,0, 4, 0, 224,35, 223,1, 0, 0, 192,169,197,37, 0, 1, 0, 4, 2, 1, 0, 0, 128,2, 131,33, 0, 1, 0, 98, 130,32, 0, 0, 64, 0, 1, 8, 0, 0, 32, 176,136,3, 0, 0, 32, 35, 73, 0, 0, 0, 160,8, 0, 0, 0, 0, 64, 161,209,13, 4, 0, 32, 8, 196,2, 0, 0, 0, 12, 85, 17, 0, 0, 160,182,25, 71, 0, 0, 32, 50, 10, 2, 0, 0, 128,8, 64, 33, 0, 0, 0, 0, 160,9, 0, 0, 224,63, 207,73, 0, 0, 0, 13, 5, 65, 0, 0, 0, 32, 8, 5, 0, 0, 32, 34, 65, 0, 0, 0, 128,0, 132,17, 0, 0, 96, 34, 136,32, 0, 0, 64, 1, 65, 32, 0, 0, 128,2, 135,16, 4, 1, 192,0, 16, 8, 0, 0, 32, 160,8, 0, 0, 0, 32, 50, 9, 64, 0, 0, 128,8, 6, 0, 0, 0, 0, 34, 145,33, 0, 1, 0, 65, 138,0, 4, 0, 160,255,223,103, 0, 0, 0, 8, 5, 66, 0, 0, 0, 50, 8, 32, 0, 0, 0, 32, 21, 0, 0, 0, 32, 41, 197,97, 0, 1, 0, 34, 9, 0, 0, 0, 128,10, 159,0, 0, 0, 0, 0, 4, 19, 4, 0, 32, 2, 129,0, 0, 0, 32, 32, 65, 65, 0, 0, 32, 178,157,2, 4, 0, 32, 32, 132,0, 0, 0, 32, 48, 0, 2, 0, 0, 0, 8, 128,68, 0, 0, 160,178,168,1, 0, 0, 32, 50, 200,107, 4, 0, 0, 2, 65, 32, 4, 1, 64, 128,150,0, 0, 0, 64, 18, 8, 0, 0, 0, 0, 0, 1, 17, 0, 0, 0, 136,194,0, 0, 0, 128,134,5, 0, 0, 0, 0, 4, 149,12, 0, 0, 32, 34, 64, 42, 0, 0, 0, 0, 68, 41, 0, 0, 192,14, 132,9, 0, 0, 32, 38, 73, 2, 0, 0, 0, 20, 69, 2, 0, 0, 0, 10, 2, 0, 0, 0, 32, 34, 89, 34, 0, 0, 0, 42, 199,35, 0, 0, 0, 10, 4, 1, 0, 0, 32, 34, 74, 0, 0, 0, 96, 10, 77, 8, 0, 0, 32, 128,16, 0, 0, 0, 96, 4, 149,0, 4, 0, 96, 50, 155,32, 4, 0, 0, 0, 144,0, 0, 0, 0, 34, 16, 32, 4, 0, 0, 2, 144,0, 0, 0, 64, 2, 140,3, 0, 0, 64, 0, 4, 2, 0, 0, 0, 42, 136,32, 4, 0, 0, 1, 128,32, 4, 1, 128,74, 132,0, 0, 0, 128,0, 40, 0, 0, 0, 160,143,255,85, 0, 0, 128,0, 140,65, 0, 0, 168,144,140,1, 0, 0, 8, 0, 0, 0, 0, 0, 32, 38, 213,34, 0, 0, 0, 0, 4, 67, 4, 0, 128,147,213,1, 0, 0, 128,2, 218,0, 4, 0, 0, 1, 0, 32, 0, 0, 0, 34, 192,64, 4, 0, 0, 30, 223,1, 0, 0, 160,170,137,65, 0, 0, 0, 0, 84, 1, 4, 0, 0, 0, 128,64, 0, 0, 0, 9, 128,2, 0, 0, 224,187,157,67, 0, 0, 0, 4, 65, 1, 0, 0, 0, 178,0, 2, 0, 0, 160,41, 201,1, 0, 0, 0, 34, 81, 0, 0, 0, 128,0, 13, 0, 0, 0, 32, 54, 141,34, 0, 0, 0, 1, 73, 33, 0, 0, 0, 128,8, 1, 0, 0, 128,0, 5, 3, 4, 0, 0, 178,16, 0, 0, 0, 128,32, 4, 1, 0, 0, 160,144,128,1, 0, 0, 224,180,198,99, 4, 0, 32, 34, 130,0, 4, 1, 0, 0, 194,64, 0, 0, 128,168,166,115, 0, 0, 32, 162,0, 64, 4, 0, 64, 129,134,0, 0, 0, 128,0, 129,1, 4, 0, 160,162,132,1, 0, 0, 32, 34, 128,96, 0, 0, 128,130,130,1, 0, 0, 32, 54, 12, 97, 4, 0, 32, 129,193,0, 4, 0, 0, 0, 12, 0, 0, 0, 128,8, 128,1, 4, 0, 96, 62, 218,9, 0, 3, 0, 0, 128,0, 0, 0, 96, 35, 134,13, 0, 0, 0, 1, 128,2, 0, 0, 0, 0, 193,4, 0, 1, 0, 1, 192,0, 0, 0, 160,0, 128,0, 0, 0, 128,16, 128,64, 0, 0, 0, 10, 130,1, 0, 0, 128,130,156,43, 0, 0, 0, 128,132,65, 0, 0, 0, 12, 64, 0, 0, 0, 0, 35, 2, 0, 0, 0, 128,43, 198,5, 0, 0, 96, 2, 147,0, 0, 0, 0, 137,0, 0, 0, 16, 0, 2, 0, 0, 0, 0, 224,191,255,111, 0, 0, 128,8, 64, 1, 0, 0, 0, 144,8, 2, 0, 0, 64, 134,128,0, 0, 0, 0, 170,200,2, 0, 0, 0, 16, 211,1, 0, 0, 0, 160,192,0, 0, 0, 0, 8, 8, 2, 0, 0, 0, 8, 212,3, 0, 0, 0, 35, 129,32, 0, 0, 0, 1, 194,32, 0, 0, 160,190,137,97, 0, 0, 128,128,196,0, 0, 0, 128,0, 132,33, 0, 0, 128,1, 142,65, 0, 0, 0, 34, 8, 65, 0, 0, 32, 34, 138,34, 0, 0, 0, 1, 4, 33, 0, 0, 128,160,0, 0, 0, 0, 0, 136,8, 1, 0, 0, 0, 10, 22, 0, 0, 0, 32, 2, 210,0, 0, 0, 0, 0, 6, 2, 0, 0, 128,43, 128,97, 0, 0, 32, 4, 9, 2, 0, 0, 192,149,213,5, 4, 32, 0, 50, 128,0, 0, 0, 0, 50, 152,32, 0, 0, 0, 18, 3, 0, 4, 0, 160,178,153,10, 0, 0, 0, 192,128,32, 0, 0, 128,4, 16, 4, 0, 0, 160,166,236,65, 0, 8, 0, 128,24, 0, 0, 1, 0, 8, 128,0, 0, 0, 32, 182,207,74, 0, 0, 0, 2, 205,2, 4, 0, 32, 20, 0, 0, 0, 0, 160,2, 137,0, 0, 0, 0, 132,65, 0, 0, 0, 160,50, 148,1, 0, 0, 0, 33, 196,34, 0, 0, 160,50, 155,3, 0, 0, 0, 0, 20, 4, 4, 0, 32, 50, 216,7, 0, 0, 32, 2, 68, 0, 0, 0, 128,9, 197,1, 0, 0, 0, 32, 136,1, 0, 0, 0, 35, 5, 8, 4, 1, 64, 54, 145,0, 0, 0, 0, 3, 4, 1, 0, 0, 160,34, 8, 98, 0, 0, 128,187,135,45, 0, 0, 128,136,0, 0, 0, 0, 32, 50, 9, 1, 0, 0, 224,127,223,111, 0, 0, 32, 50, 73, 0, 0, 0, 32, 0, 156,0, 0, 0, 128,8, 4, 2, 0, 0, 32, 35, 6, 0, 0, 0, 128,0, 66, 0, 0, 0, 32, 2, 192,0, 0, 0, 0, 2, 128,2, 0, 0, 224,58, 133,3, 4, 0, 0, 34, 15, 10, 0, 0, 32, 32, 72, 8, 0, 0, 0, 34, 65, 65, 0, 0, 192,11, 143,69, 0, 0, 0, 11, 2, 0, 0, 0, 32, 1, 130,0, 0, 0, 64, 1, 130,3, 4, 0, 0, 18, 129,0, 0, 0, 32, 34, 90, 2, 0, 0, 128,0, 193,5, 0, 0, 32, 1, 64, 1, 0, 0, 128,139,128,65, 4, 1, 0, 0, 129,1, 0, 0, 0, 34, 136,66, 4, 0, 224,223,159,95, 0, 0, 32, 146,73, 3, 0, 0, 0, 33, 0, 2, 0, 0, 32, 38, 8, 2, 4, 0, 0, 34, 152,0, 0, 0, 0, 130,8, 3, 0, 0, 32, 18, 216,2, 0, 0, 160,162,144,1, 0, 0, 32, 18, 200,64, 0, 0, 128,40, 128,2, 0, 0, 160,0, 0, 64, 0, 0, 128,128,129,8, 0, 0, 0, 35, 134,32, 0, 0, 128,35, 8, 0, 4, 0, 96, 1, 145,1, 0, 0, 96, 6, 0, 0, 0, 0, 192,239,215,65, 4, 0, 32, 32, 1, 0, 0, 0, 160,34, 155,4, 0, 0, 128,1, 128,65, 0, 0, 32, 34, 138,2, 0, 0, 96, 0, 18, 0, 0, 0, 0, 178,128,0, 0, 0, 160,169,128,113, 0, 0, 32, 34, 192,32, 0, 0, 0, 160,16, 0, 0, 0, 224,171,213,39, 0, 0, 0, 32, 8, 34, 0, 0, 0, 4, 4, 1, 0, 0, 0, 34, 1, 10, 0, 0, 160,146,128,32, 0, 43, 128,2, 130,0, 4, 0, 32, 18, 198,67, 0, 0, 96, 204,93, 3, 0, 1, 225,60, 36, 10, 0, 0, 0, 0, 0, 72, 0, 1, 0, 0, 32, 24, 0, 0, 0, 16, 32, 0, 0, 0, 32, 34, 74, 32, 0, 0, 128,185,199,33, 0, 0, 0, 5, 5, 0, 0, 0, 128,9, 0, 1, 0, 8, 0, 32, 0, 2, 0, 0, 32, 128,69, 1, 0, 0, 0, 2, 0, 66, 4, 0, 64, 2, 131,0, 0, 0, 32, 48, 0, 1, 0, 0, 96, 66, 197,70, 4, 1, 32, 34, 128,0, 0, 0, 0, 128,64, 1, 0, 0, 128,34, 72, 33, 0, 0, 32, 34, 4, 1, 4, 0, 32, 34, 132,0, 0, 0, 224,75, 215,85, 0, 0, 192,34, 13, 1, 0, 0, 32, 2, 32, 0, 0, 1, 32, 0, 96, 0, 0, 1, 32, 0, 32, 1, 0, 9, 0, 0, 0, 96, 0, 65, 0, 0, 0, 2, 0, 2, 224,114,200,59, 0, 0, 1, 1, 0, 0, 0, 0, 64, 0, 144,1, 0, 0, 96, 34, 72, 34, 0, 0, 160,178,40, 96, 0, 0, 128,17, 192,33, 0, 1, 0, 0, 128,16, 4, 1, 0, 2, 129,0, 0, 0, 160,0, 140,0, 0, 0, 160,167,141,99, 0, 0, 64, 128,64, 1, 0, 0, 64, 0, 67, 0, 0, 0, 160,8, 140,3, 4, 0, 32, 18, 129,0, 0, 0, 32, 38, 0, 1, 0, 0, 32, 42, 141,3, 0, 0, 32, 34, 70, 0, 0, 0, 96, 5, 143,1, 0, 0, 0, 34, 193,8, 4, 0, 224,55, 138,44, 0, 33, 0, 0, 128,0, 0, 0, 224,34, 8, 1, 0, 0, 0, 2, 145,0, 0, 0, 224,227,222,3, 0, 0, 0, 137,69, 0, 0, 1, 0, 0, 0, 2, 0, 0, 32, 0, 193,32, 0, 0, 0, 6, 197,0, 0, 0, 96, 35, 8, 0, 0, 0, 0, 0, 128,34, 0, 0, 32, 34, 201,32, 0, 0, 0, 2, 192,32, 0, 0, 96, 5, 8, 0, 0, 0, 32, 32, 128,64, 0, 0, 32, 50, 153,3, 0, 0, 0, 0, 148,64, 0, 0, 224,146,158,3, 0, 0, 32, 16, 8, 64, 0, 0, 32, 34, 1, 2, 0, 0, 96, 0, 0, 2, 4, 0, 224,178,135,65, 0, 0, 0, 64, 0, 1, 4, 0, 0, 1, 192,0, 0, 0, 64, 198,10, 0, 0, 0, 0, 12, 12, 0, 0, 0, 32, 34, 65, 32, 0, 0, 192,25, 145,1, 0, 1, 128,2, 136,8, 0, 0, 0, 19, 129,0, 4, 0, 224,14, 207,0, 0, 0, 128,9, 215,39, 0, 0, 0, 16, 32, 1, 0, 0, 32, 16, 9, 0, 0, 0, 64, 34, 3, 33, 0, 0, 0, 35, 192,4, 0, 0, 0, 0, 2, 16, 0, 0, 96, 34, 10, 2, 0, 0, 128,160,65, 0, 0, 0, 160,128,0, 0, 0, 1, 0, 0, 137,64, 0, 0, 0, 0, 73, 2, 0, 0, 160,178,73, 99, 0, 0, 0, 0, 85, 0, 0, 0, 0, 33, 148,0, 0, 0, 0, 2, 28, 1, 0, 0, 0, 40, 71, 6, 0, 0, 128,0, 132,4, 4, 1, 224,63, 223,10, 0, 0, 0, 34, 8, 40, 0, 0, 128,134,71, 36, 0, 0, 160,5, 196,0, 0, 0, 192,149,149,21, 4, 0, 0, 2, 128,34, 4, 0, 0, 0, 66, 0, 0, 0, 0, 38, 73, 2, 0, 0, 0, 10, 132,1, 0, 0, 32, 42, 76, 2, 0, 0, 0, 32, 69, 1, 0, 0, 0, 2, 142,0, 0, 0, 96, 135,79, 34, 0, 0, 192,159,135,1, 0, 0, 32, 2, 3, 0, 0, 0, 0, 18, 5, 0, 0, 0, 224,183,138,12, 0, 0, 128,0, 2, 1, 0, 0, 128,51, 134,73, 0, 0, 32, 18, 130,64, 0, 0, 32, 38, 9, 34, 0, 0, 0, 0, 16, 49, 0, 0, 32, 12, 4, 4, 0, 0, 160,14, 132,4, 0, 0, 0, 5, 128,4, 0, 0, 96, 34, 159,34, 0, 0, 0, 161,69, 1, 0, 0, 64, 1, 66, 0, 0, 0, 160,8, 133,1, 4, 0, 0, 1, 68, 5, 0, 0, 160,175,172,105, 0, 0, 0, 34, 67, 35, 0, 0, 32, 10, 0, 9, 0, 0, 0, 32, 1, 14, 0, 0, 128,48, 0, 0, 4, 0, 32, 38, 136,64, 0, 0, 32, 178,217,3, 0, 0, 0, 32, 192,32, 0, 0, 96, 9, 0, 0, 0, 0, 64, 8, 1, 1, 0, 0, 160,50, 157,3, 0, 0, 32, 1, 70, 1, 0, 0, 0, 38, 130,0, 0, 0, 0, 9, 133,0, 0, 0, 0, 0, 69, 2, 0, 0, 32, 38, 128,0, 0, 0, 32, 38, 201,0, 0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 195,0, 0, 0, 32, 50, 72, 99, 0, 0, 0, 160,69, 0, 0, 0, 32, 12, 128,0, 0, 0, 64, 0, 133,1, 0, 0, 32, 34, 9, 6, 4, 0, 0, 128,67, 0, 0, 0, 0, 2, 129,4, 0, 0, 0, 0, 0, 7, 0, 0, 32, 32, 96, 0, 4, 0, 32, 4, 64, 32, 0, 0, 64, 130,140,0, 0, 0, 0, 16, 2, 1, 0, 0, 96, 34, 2, 105, 0, 1, 128,0, 128,0, 0, 0, 32, 1, 6, 1, 0, 0, 32, 3, 0, 34, 0, 0, 64, 3, 0, 0, 0, 0, 224,255,159,87, 0, 0, 224,2, 129,0, 0, 0, 128,0, 128,2, 4, 0, 160,146,8, 0, 0, 0, 96, 34, 7, 9, 4, 0, 32, 34, 131,32, 0, 0, 0, 43, 13, 0, 4, 0, 32, 0, 204,0, 4, 0, 0, 0, 129,8, 0, 0, 0, 2, 3, 32, 0, 0, 0, 134,1, 1, 0, 0, 64, 59, 141,0, 0, 0, 0, 6, 143,32, 0, 0, 96, 2, 130,0, 0, 0, 160,171,148,1, 0, 0, 0, 6, 136,0, 4, 0, 0, 43, 137,10, 0, 0, 0, 64, 213,3, 0, 0, 32, 130,145,1, 0, 0, 0, 3, 77, 33, 0, 0, 160,178,157,1, 0, 0, 0, 2, 11, 0, 0, 0, 0, 0, 68, 8, 0, 0, 160,2, 192,67, 0, 0, 0, 32, 196,1, 4, 0, 96, 36, 200,0, 0, 0, 0, 130,8, 68, 0, 0, 128,1, 198,1, 4, 0, 192,176,192,40, 0, 0, 0, 48, 0, 1, 4, 0, 96, 34, 141,35, 0, 0, 128,32, 192,0, 0, 0, 128,0, 197,32, 4, 1, 32, 32, 136,0, 0, 0, 128,3, 197,1, 0, 0, 0, 3, 128,64, 0, 0, 160,176,96, 0, 0, 0, 32, 54, 8, 0, 0, 0, 0, 4, 129,64, 4, 0, 0, 54, 207,40, 0, 0, 0, 5, 64, 33, 4, 0, 32, 171,206,8, 0, 0, 64, 128,196,0, 0, 0, 0, 20, 205,8, 4, 0, 128,8, 4, 0, 0, 0, 0, 34, 128,64, 0, 0, 192,145,215,9, 0, 0, 128,130,133,0, 0, 0, 32, 26, 76, 2, 0, 0, 32, 3, 71, 40, 0, 0, 32, 38, 10, 0, 0, 0, 32, 39, 153,0, 0, 0, 0, 5, 64, 32, 0, 0, 0, 5, 4, 0, 0, 0, 96, 34, 94, 2, 4, 0, 128,32, 196,0, 4, 0, 160,34, 136,1, 0, 0, 0, 0, 201,32, 4, 0, 160,171,36, 0, 0, 0, 0, 133,150,1, 4, 0, 0, 38, 130,32, 0, 0, 32, 162,0, 32, 0, 0, 224,178,143,41, 4, 0, 0, 1, 199,3, 0, 0, 64, 34, 0, 36, 4, 0, 224,0, 199,41, 0, 0, 0, 2, 72, 2, 0, 0, 128,0, 8, 2, 0, 0, 224,143,168,73, 0, 0, 160,2, 13, 1, 4, 0, 0, 128,64, 0, 0, 0, 32, 8, 128,0, 0, 0, 0, 4, 3, 0, 4, 0, 96, 34, 11, 0, 0, 0, 32, 0, 193,1, 0, 0, 0, 128,68, 0, 0, 0, 160,50, 206,67, 0, 0, 192,14, 195,8, 0, 0, 0, 34, 2, 8, 0, 0, 0, 45, 215,81, 0, 0, 32, 34, 69, 1, 4, 0, 32, 34, 128,35, 0, 0, 0, 50, 137,1, 0, 32, 0, 1, 192,0, 0, 0, 96, 35, 193,1, 0, 0, 96, 32, 0, 32, 4, 0, 224,2, 129,0, 4, 0, 32, 34, 6, 96, 0, 0, 64, 128,3, 0, 0, 0, 32, 0, 32, 4, 0, 0, 224,191,223,103, 0, 0, 96, 34, 205,32, 0, 0, 128,176,0, 0, 0, 0, 0, 34, 131,2, 0, 0, 32, 43, 15, 96, 0, 0, 64, 0, 197,0, 0, 0, 0, 10, 134,0, 0, 0, 192,29, 129,1, 0, 0, 0, 38, 8, 2, 0, 0, 160,32, 13, 0, 0, 0, 0, 40, 136,0, 0, 0, 64, 26, 192,0, 0, 0, 0, 64, 132,64, 0, 0, 0, 4, 132,0, 0, 0, 64, 6, 219,0, 0, 0, 0, 20, 1, 2, 0, 0, 160,171,172,41, 0, 0, 32, 2, 41, 0, 0, 0, 128,4, 16, 0, 0, 0, 32, 2, 81, 1, 0, 0, 128,178,132,65, 0, 2, 0, 39, 3, 1, 0, 3, 0, 1, 0, 0, 0, 0, 32, 176,1, 65, 4, 0, 32, 34, 129,4, 0, 0, 224,139,7, 0, 0, 0, 64, 2, 0, 64, 4, 0, 160,254,255,127, 0, 0, 160,32, 26, 0, 0, 0, 224,255,215,63, 0, 0, 64, 1, 195,5, 0, 0, 96, 34, 73, 0, 0, 0, 32, 0, 76, 0, 0, 0, 160,144,64, 0, 0, 0, 0, 2, 194,0, 0, 0, 96, 35, 77, 34, 0, 0, 0, 2, 160,0, 0, 0, 128,14, 21, 65, 0, 0, 32, 38, 65, 0, 0, 0, 128,152,193,9, 0, 0, 32, 42, 75, 2, 0, 0, 32, 96, 74, 0, 0, 0, 32, 137,197,97, 0, 0, 64, 34, 136,0, 0, 0, 0, 35, 0, 65, 0, 0, 0, 32, 3, 1, 4, 0, 224,171,139,103, 0, 0, 32, 33, 198,65, 0, 0, 0, 18, 198,0, 0, 0, 0, 0, 18, 8, 0, 0, 64, 3, 134,68, 0, 0, 0, 32, 30, 0, 0, 0, 0, 34, 132,104, 0, 0, 96, 2, 2, 8, 4, 1, 96, 39, 218,2, 0, 0, 32, 17, 196,33, 4, 0, 32, 0, 64, 0, 0, 0, 32, 16, 76, 0, 0, 0, 32, 3, 64, 0, 0, 0, 224,239,223,69, 0, 0, 64, 8, 4, 1, 0, 0, 0, 0, 10, 1, 0, 0, 96, 62, 251,47, 0, 0, 0, 24, 1, 6, 0, 0, 0, 7, 71, 64, 0, 0, 128,4, 131,0, 4, 0, 0, 15, 4, 0, 0, 0, 64, 2, 64, 76, 0, 0, 0, 32, 72, 1, 0, 0, 0, 18, 192,0, 0, 0, 192,128,8, 0, 4, 0, 160,36, 199,0, 0, 0, 32, 41, 8, 0, 0, 0, 96, 8, 133,64, 0, 0, 0, 34, 152,0, 0, 0, 224,237,223,45, 4, 0, 192,60, 4, 5, 0, 0, 160,144,164,1, 0, 0, 32, 2, 10, 32, 4, 0, 160,50, 200,35, 4, 32, 0, 32, 0, 0, 0, 0, 0, 4, 1, 9, 0, 0, 160,11, 199,5, 0, 0, 0, 128,128,8, 0, 0, 32, 34, 8, 4, 0, 1, 0, 1, 193,0, 0, 0, 128,32, 64, 34, 0, 0, 128,163,192,33, 0, 0, 64, 2, 10, 1, 4, 0, 96, 34, 205,8, 0, 0, 0, 9, 64, 1, 0, 0, 32, 50, 73, 18, 0, 0, 192,45, 87, 5, 0, 0, 32, 39, 17, 96, 0, 0, 0, 0, 146,0, 0, 0, 0, 33, 133,1, 0, 0, 224,39, 217,37, 0, 0, 32, 36, 8, 0, 0, 0, 32, 34, 79, 10, 0, 0, 224,167,87, 3, 0, 0, 0, 5, 81, 0, 0, 0, 0, 5, 64, 0, 0, 0, 192,0, 4, 0, 0, 0, 128,130,4, 65, 0, 0, 64, 7, 16, 1, 0, 0, 64, 6, 131,8, 0, 0, 224,39, 87, 30, 0, 0, 64, 1, 132,0, 0, 0, 128,131,0, 0, 0, 0, 64, 38, 82, 65, 0, 0, 224,134,215,64, 0, 0, 64, 4, 193,0, 0, 0, 0, 129,1, 64, 0, 0, 192,8, 4, 1, 0, 0, 128,42, 214,34, 0, 0, 128,5, 132,0, 0, 0, 0, 129,0, 64, 0, 0, 64, 2, 140,1, 0, 0, 64, 2, 20, 1, 0, 0, 0, 1, 5, 2, 0, 0, 0, 32, 0, 65, 4, 0, 0, 128,0, 0, 0, 0, 0, 9, 192,0, 0, 0, 0, 144,2, 65, 0, 0, 192,160,81, 2, 0, 0, 128,34, 1, 1, 0, 0, 128,3, 0, 1, 0, 0, 0, 0, 72, 64, 0, 0, 192,6, 95, 11, 0, 0, 192,5, 22, 1, 0, 0, 64, 54, 64, 8, 0, 0, 32, 2, 1, 65, 0, 0, 96, 2, 2, 0, 0, 0, 64, 20, 128,0, 0, 0, 0, 132,5, 0, 4, 0, 192,46, 128,41, 0, 0, 128,11, 6, 0, 0, 0, 0, 128,0, 65, 0, 0, 192,37, 6, 0, 0, 0, 64, 6, 4, 0, 0, 0, 192,2, 6, 4, 0, 0, 64, 4, 64, 4, 0, 0, 160,3, 7, 33, 0, 0, 192,31, 215,35, 0, 0, 0, 130,1, 0, 0, 0, 0, 131,3, 65, 0, 0, 32, 2, 18, 0, 0, 0, 0, 8, 66, 64, 0, 0, 64, 9, 69, 0, 0, 0, 0, 131,0, 65, 0, 0, 64, 130,2, 0, 0, 0, 64, 42, 84, 0, 0, 0, 0, 8, 132,65, 0, 0, 128,129,0, 0, 0, 0, 128,2, 6, 0, 0, 0, 160,2, 222,4, 0, 0, 64, 12, 93, 15, 0, 0, 192,0, 16, 1, 0, 0, 128,35, 20, 64, 0, 0, 192,1, 192,0, 0, 0, 128,32, 129,1, 4, 1, 0, 32, 136,32, 4, 1, 0, 34, 128,0, 0, 0, 64, 0, 22, 9, 0, 0, 64, 34, 144,0, 0, 0, 32, 2, 0, 8, 0, 0, 64, 35, 128,34, 0, 0, 64, 0, 18, 5, 0, 0, 0, 8, 9, 3, 0, 0, 64, 12, 22, 1, 0, 0, 64, 5, 199,65, 0, 0, 224,191,255,63, 0, 0, 192,29, 247,45, 0, 0, 0, 2, 73, 2, 0, 0, 32, 2, 137,2, 4, 0, 128,2, 0, 0, 0, 0, 224,38, 129,32, 0, 0, 128,50, 8, 0, 4, 0, 64, 34, 136,1, 0, 0, 96, 2, 72, 0, 0, 0, 32, 144,73, 3, 4, 1, 32, 32, 129,0, 0, 0, 96, 43, 9, 10, 0, 0, 0, 0, 3, 5, 0, 0, 128,128,0, 32, 4, 0, 64, 189,215,37, 0, 0, 32, 2, 75, 2, 0, 0, 0, 34, 68, 0, 0, 0, 64, 15, 151,65, 0, 0, 0, 2, 7, 1, 0, 0, 0, 2, 17, 0, 0, 0, 96, 167,155,77, 0, 0, 128,0, 4, 32, 0, 0, 128,0, 68, 32, 0, 0, 96, 34, 27, 1, 0, 0, 0, 2, 0, 65, 0, 0, 32, 175,140,1, 0, 0, 128,8, 128,32, 0, 0, 0, 33, 133,0, 0, 0, 32, 34, 68, 64, 0, 0, 32, 34, 144,3, 0, 0, 96, 47, 222,6, 0, 0, 160,10, 12, 1, 0, 0, 128,1, 132,1, 0, 0, 160,128,129,1, 4, 0, 96, 48, 137,67, 0, 64, 0, 0, 128,1, 0, 0, 32, 34, 140,35, 0, 0, 0, 10, 5, 32, 0, 41, 32, 42, 155,51, 0, 33, 64, 8, 139,0, 0, 0, 96, 6, 131,0, 0, 8, 0, 2, 8, 0, 0, 0, 32, 178,74, 35, 4, 0, 192,191,255,127, 0, 0, 96, 0, 8, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 5, 128,0, 0, 0, 0, 35, 73, 40, 0, 1, 0, 32, 128,32, 0, 0, 0, 6, 64, 1, 0, 0, 32, 2, 9, 1, 0, 0, 96, 34, 216,2, 0, 0, 32, 0, 9, 2, 0, 0, 0, 133,1, 0, 4, 0, 32, 187,141,1, 0, 0, 0, 22, 0, 0, 4, 1, 0, 128,65, 32, 0, 0, 160,128,32, 1, 0, 0, 32, 0, 131,0, 0, 0, 32, 34, 17, 0, 0, 0, 96, 163,91, 35, 0, 0, 0, 19, 197,0, 0, 0, 128,134,5, 1, 0, 1, 64, 34, 128,32, 0, 0, 0, 0, 5, 65, 0, 0, 0, 2, 9, 2, 0, 0, 64, 12, 5, 0, 0, 0, 32, 1, 4, 1, 4, 0, 160,10, 133,4, 4, 0, 32, 3, 4, 1, 4, 0, 224,55, 215,15, 0, 0, 0, 21, 71, 1, 0, 0, 0, 35, 140,0, 0, 0, 64, 177,213,1, 4, 0, 0, 16, 0, 0, 0, 0, 96, 55, 191,5, 0, 0, 32, 0, 0, 34, 0, 1, 0, 0, 0, 4, 0, 0, 32, 54, 130,32, 0, 0, 32, 34, 0, 8, 0, 0, 32, 170,4, 1, 0, 0, 32, 18, 17, 0, 0, 0, 96, 166,91, 40, 0, 0, 128,135,129,1, 0, 0, 0, 1, 4, 8, 0, 0, 224,183,215,101, 0, 0, 0, 16, 65, 0, 0, 0, 0, 4, 65, 3, 0, 0, 32, 162,26, 0, 0, 0, 32, 171,140,33, 4, 0, 0, 2, 147,41, 0, 0, 0, 3, 64, 32, 0, 0, 32, 162,9, 1, 4, 0, 64, 22, 129,1, 0, 0, 224,79, 223,15, 4, 0, 64, 16, 135,0, 4, 0, 32, 2, 1, 1, 0, 0, 32, 131,4, 3, 0, 0, 32, 0, 0, 8, 0, 0, 0, 128,16, 1, 4, 0, 0, 130,145,1, 0, 0, 64, 2, 64, 1, 0, 0, 224,50, 197,41, 4, 0, 0, 8, 128,9, 0, 0, 0, 8, 149,1, 4, 0, 32, 2, 4, 0, 4, 0, 224,43, 206,33, 0, 0, 128,135,0, 12, 0, 0, 0, 3, 7, 4, 4, 0, 32, 32, 128,0, 0, 0, 128,5, 24, 34, 0, 0, 64, 8, 6, 0, 0, 0, 224,175,223,5, 0, 0, 128,0, 2, 32, 0, 0, 0, 6, 8, 0, 0, 0, 32, 8, 64, 0, 0, 0, 32, 2, 3, 65, 0, 0, 0, 32, 128,64, 0, 0, 32, 32, 152,8, 0, 0, 0, 2, 3, 1, 0, 0, 0, 3, 128,3, 0, 0, 224,143,255,95, 0, 0, 64, 2, 65, 34, 0, 0, 160,160,8, 2, 0, 0, 64, 34, 13, 0, 4, 0, 32, 51, 207,8, 4, 0, 224,129,197,9, 0, 0, 192,12, 199,12, 0, 0, 32, 6, 67, 8, 0, 0, 32, 128,4, 0, 0, 0, 0, 4, 0, 3, 0, 0, 32, 30, 71, 2, 0, 0, 224,190,139,5, 0, 0, 64, 24, 130,0, 0, 0, 0, 34, 138,32, 0, 1, 96, 32, 135,41, 0, 0, 224,178,155,2, 0, 0, 0, 0, 200,0, 0, 0, 0, 128,204,0, 4, 1, 32, 35, 132,33, 4, 0, 32, 0, 1, 1, 0, 0, 32, 130,129,0, 0, 1, 0, 0, 192,1, 0, 0, 224,255,172,35, 0, 0, 32, 8, 128,8, 0, 0, 64, 0, 64, 2, 0, 0, 0, 18, 153,0, 0, 0, 160,162,203,0, 0, 0, 128,130,141,1, 4, 0, 0, 16, 129,0, 0, 0, 160,146,201,67, 0, 0, 0, 130,144,1, 0, 0, 1, 0, 4, 0, 0, 0, 32, 1, 195,0, 0, 0, 64, 9, 161,0, 0, 0, 32, 98, 73, 34, 0, 0, 32, 60, 69, 1, 0, 0, 32, 160,128,0, 0, 0, 0, 131,0, 0, 0, 0, 64, 1, 192,0, 0, 0, 32, 3, 85, 9, 0, 0, 0, 0, 135,1, 0, 0, 64, 39, 215,5, 0, 0, 96, 32, 0, 2, 0, 0, 0, 1, 138,0, 4, 0, 0, 16, 133,0, 0, 0, 0, 0, 144,8, 0, 0, 0, 0, 2, 5, 0, 0, 0, 136,73, 14, 0, 0, 64, 4, 5, 1, 4, 0, 0, 19, 197,8, 0, 0, 192,41, 150,47, 0, 0, 0, 139,0, 1, 0, 0, 0, 130,8, 1, 0, 1, 0, 1, 196,0, 0, 0, 96, 35, 130,1, 0, 0, 32, 43, 22, 79, 0, 0, 0, 128,18, 5, 0, 0, 0, 34, 137,40, 0, 0, 128,141,22, 13, 0, 0, 0, 66, 130,32, 0, 0, 0, 128,144,0, 0, 0, 224,8, 158,15, 0, 0, 0, 50, 133,0, 0, 0, 0, 137,198,1, 0, 0, 0, 2, 0, 5, 0, 0, 32, 162,200,35, 0, 0, 128,33, 193,33, 0, 0, 0, 34, 146,8, 0, 1, 32, 2, 128,33, 0, 0, 32, 9, 1, 1, 0, 1, 0, 1, 128,32, 0, 0, 0, 18, 193,65, 4, 1, 64, 82, 128,40, 0, 0, 160,136,73, 3, 0, 1, 0, 1, 192,32, 0, 0, 32, 163,0, 0, 0, 0, 128,41, 193,1, 0, 0, 0, 0, 4, 65, 0, 0, 0, 2, 193,1, 0, 0, 128,172,80, 103, 0, 0, 0, 130,65, 0, 0, 0, 0, 129,64, 0, 0, 0, 0, 1, 64, 64, 0, 0, 128,7, 16, 5, 0, 0, 64, 129,72, 15, 0, 0, 0, 128,16, 65, 0, 0, 0, 162,0, 1, 4, 0, 192,39, 130,32, 0, 0, 192,12, 0, 0, 0, 0, 0, 128,80, 0, 0, 0, 64, 32, 128,32, 0, 0, 192,34, 201,2, 0, 0, 128,176,72, 0, 0, 0, 32, 51, 136,0, 0, 0, 64, 24, 163,0, 0, 0, 0, 17, 5, 0, 0, 0, 32, 36, 64, 1, 0, 0, 96, 34, 5, 41, 0, 0, 0, 0, 77, 0, 0, 0, 0, 4, 128,8, 0, 0, 96, 51, 137,7, 0, 0, 0, 9, 8, 0, 0, 0, 0, 33, 199,0, 0, 0, 128,3, 131,69, 0, 0, 0, 0, 70, 8, 0, 0, 64, 2, 10, 0, 4, 0, 64, 17, 216,9, 0, 0, 32, 0, 128,4, 4, 0, 32, 235,204,35, 0, 0, 32, 18, 64, 0, 4, 0, 0, 2, 128,8, 0, 0, 0, 12, 66, 4, 0, 0, 32, 4, 128,0, 0, 0, 0, 129,12, 1, 0, 0, 0, 146,80, 0, 0, 0, 224,171,76, 3, 0, 0, 96, 50, 4, 1, 0, 0, 128,143,213,69, 4, 0, 32, 38, 131,0, 0, 0, 32, 34, 145,1, 0, 0, 128,2, 144,0, 0, 0, 224,42, 145,9, 0, 0, 32, 51, 5, 0, 0, 0, 32, 114,73, 34, 0, 0, 128,191,199,107, 0, 0, 192,1, 194,32, 0, 0, 64, 169,140,0, 0, 0, 32, 186,193,1, 0, 0, 32, 0, 0, 96, 0, 0, 32, 50, 0, 67, 0, 0, 160,51, 69, 109, 0, 0, 0, 129,192,0, 0, 0, 128,32, 130,0, 0, 0, 128,178,128,3, 0, 0, 32, 6, 2, 0, 0, 1, 0, 162,129,1, 0, 0, 128,9, 193,0, 0, 1, 64, 32, 136,40, 0, 0, 128,11, 192,0, 0, 0, 128,9, 136,1, 0, 0, 96, 0, 75, 0, 0, 0, 128,143,197,5, 0, 0, 0, 146,8, 32, 4, 1, 0, 0, 130,0, 0, 0, 0, 137,193,9, 0, 0, 64, 2, 2, 0, 4, 1, 0, 1, 192,32, 0, 0, 0, 129,3, 1, 0, 0, 32, 131,140,34, 4, 0, 0, 2, 128,2, 0, 0, 0, 6, 129,65, 0, 0, 128,4, 4, 1, 0, 0, 128,183,206,3, 0, 0, 32, 8, 128,1, 0, 0, 0, 136,69, 0, 0, 0, 0, 0, 137,1, 0, 0, 32, 34, 8, 38, 0, 0, 192,169,38, 43, 0, 0, 0, 40, 1, 2, 0, 0, 160,50, 4, 0, 0, 0, 0, 8, 6, 1, 4, 0, 0, 38, 128,1, 0, 0, 128,0, 1, 11, 0, 0, 128,34, 4, 0, 0, 0, 64, 8, 193,1, 0, 0, 64, 1, 197,9, 0, 0, 64, 11, 197,0, 0, 0, 128,128,0, 2, 0, 0, 160,127,219,43, 0, 0, 224,191,215,39, 0, 0, 64, 32, 129,0, 0, 0, 0, 178,0, 32, 0, 0, 0, 50, 144,0, 0, 0, 0, 34, 1, 33, 0, 0, 64, 17, 1, 0, 0, 0, 0, 18, 6, 32, 0, 0, 128,0, 64, 32, 0, 0, 64, 2, 137,0, 0, 0, 128,0, 130,8, 0, 0, 128,163,5, 77, 0, 0, 160,34, 138,8, 0, 0, 96, 38, 26, 0, 4, 0, 224,191,157,73, 4, 0, 96, 34, 217,2, 4, 0, 0, 2, 9, 0, 0, 0, 0, 16, 1, 64, 0, 0, 64, 6, 0, 0, 4, 33, 192,175,199,77, 0, 0, 0, 144,64, 0, 0, 0, 0, 8, 20, 1, 0, 1, 0, 34, 193,2, 0, 0, 0, 176,128,65, 0, 0, 32, 128,128,2, 0, 0, 0, 50, 137,67, 4, 0, 0, 17, 199,8, 0, 0, 128,0, 140,0, 0, 0, 32, 2, 194,1, 0, 0, 64, 5, 4, 0, 0, 0, 0, 34, 140,1, 0, 0, 0, 16, 65, 32, 0, 0, 224,127,87, 111, 0, 0, 0, 137,71, 0, 0, 0, 0, 52, 2, 40, 0, 0, 0, 9, 85, 4, 0, 0, 0, 18, 2, 32, 0, 0, 96, 0, 0, 8, 0, 0, 192,11, 7, 1, 0, 0, 96, 182,3, 0, 0, 0, 0, 0, 3, 65, 0, 0, 32, 50, 2, 34, 0, 0, 64, 2, 147,0, 0, 0, 192,11, 172,64, 0, 0, 0, 0, 73, 1, 0, 0, 96, 21, 151,1, 0, 0, 64, 162,79, 0, 0, 0, 64, 34, 128,2, 0, 0, 0, 16, 129,65, 0, 0, 224,2, 0, 0, 4, 1, 0, 0, 196,0, 0, 0, 128,143,87, 5, 0, 0, 0, 34, 135,34, 0, 0, 64, 12, 68, 0, 0, 0, 0, 4, 1, 1, 0, 0, 32, 10, 3, 0, 0, 0, 0, 2, 129,9, 0, 0, 32, 34, 134,8, 0, 0, 32, 0, 146,2, 0, 0, 128,138,132,1, 4, 0, 0, 34, 193,32, 0, 0, 0, 10, 193,0, 0, 0, 192,191,223,47, 4, 0, 96, 55, 151,41, 0, 0, 128,24, 128,1, 0, 0, 224,0, 0, 0, 0, 0, 32, 130,1, 0, 0, 0, 0, 38, 17, 65, 0, 0, 32, 0, 18, 0, 0, 0, 0, 42, 0, 34, 4, 17, 192,145,194,40, 0, 0, 0, 137,17, 1, 0, 0, 0, 48, 145,40, 0, 0, 160,227,79, 33, 4, 0, 0, 5, 192,32, 4, 1, 0, 34, 136,32, 4, 0, 64, 0, 129,1, 0, 0, 0, 13, 64, 1, 0, 0, 128,177,151,111, 0, 0, 0, 7, 1, 0, 0, 0, 32, 6, 128,0, 0, 0, 32, 11, 136,0, 0, 0, 0, 17, 64, 0, 0, 0, 0, 38, 10, 3, 0, 0, 128,2, 128,64, 0, 0, 192,12, 128,0, 0, 0, 64, 1, 132,1, 0, 0, 224,59, 151,45, 0, 0, 0, 128,2, 1, 4, 0, 128,2, 131,9, 0, 0, 192,12, 128,1, 0, 0, 0, 136,0, 2, 0, 0, 192,135,20, 5, 0, 0, 0, 54, 128,32, 0, 0, 0, 34, 145,1, 0, 0, 192,223,159,75, 0, 0, 96, 2, 162,32, 0, 0, 64, 18, 1, 0, 0, 0, 0, 9, 144,1, 0, 0, 192,41, 150,37, 0, 0, 224,151,87, 1, 0, 0, 0, 0, 65, 33, 0, 0, 0, 35, 131,33, 0, 0, 64, 0, 68, 0, 0, 0, 0, 2, 8, 4, 4, 0, 0, 0, 128,2, 0, 0, 64, 128,2, 4, 0, 0, 64, 4, 16, 0, 0, 0, 0, 22, 152,32, 0, 0, 0, 136,4, 1, 0, 0, 32, 162,24, 0, 0, 1, 0, 34, 1, 0, 0, 0, 128,137,66, 1, 0, 0, 0, 18, 129,65, 0, 0, 128,2, 203,16, 0, 0, 224,191,223,101, 0, 0, 0, 0, 68, 68, 0, 0, 224,115,219,111, 0, 0, 0, 0, 96, 0, 0, 1, 0, 38, 8, 0, 0, 0, 160,35, 139,32, 0, 0, 96, 35, 137,8, 4, 0, 32, 1, 197,32, 0, 0, 32, 59, 64, 0, 4, 1, 128,2, 147,32, 0, 0, 128,0, 196,1, 4, 0, 160,2, 136,1, 0, 0, 32, 166,145,67, 0, 0, 0, 128,0, 5, 0, 0, 96, 34, 223,1, 0, 0, 64, 0, 4, 33, 4, 33, 161,20, 129,5, 4, 0, 64, 173,197,3, 0, 0, 96, 34, 232,1, 0, 0, 0, 2, 65, 5, 0, 0, 128,18, 16, 1, 0, 0, 0, 2, 92, 0, 4, 1, 192,55, 150,12, 0, 0, 32, 2, 73, 0, 0, 0, 96, 47, 87, 36, 4, 0, 96, 34, 0, 0, 0, 0, 32, 0, 5, 33, 0, 0, 160,48, 147,33, 4, 1, 32, 0, 4, 0, 4, 0, 0, 0, 32, 0, 0, 0, 128,22, 81, 3, 0, 0, 128,17, 4, 3, 0, 0, 0, 73, 4, 0, 0, 0, 192,61, 215,33, 0, 0, 0, 32, 16, 32, 0, 0, 96, 171,132,1, 0, 0, 0, 35, 6, 1, 0, 0, 0, 16, 136,72, 0, 0, 0, 34, 4, 64, 0, 0, 160,35, 87, 65, 0, 0, 0, 34, 146,33, 0, 0, 96, 34, 1, 0, 0, 0, 0, 1, 84, 65, 0, 1, 64, 50, 159,32, 0, 0, 160,31, 215,69, 0, 0, 0, 143,132,1, 0, 0, 0, 20, 0, 32, 0, 0, 64, 12, 93, 32, 0, 0, 192,131,215,69, 0, 0, 160,3, 12, 1, 0, 0, 0, 8, 16, 0, 0, 0, 160,34, 24, 40, 0, 0, 64, 5, 1, 1, 0, 0, 128,128,0, 9, 0, 0, 128,128,144,1, 4, 2, 224,242,218,111, 0, 0, 96, 34, 201,2, 0, 0, 0, 136,192,1, 4, 0, 160,0, 192,1, 0, 0, 0, 178,136,33, 0, 0, 64, 0, 130,0, 4, 0, 32, 3, 17, 32, 4, 0, 32, 8, 129,2, 4, 0, 192,0, 213,9, 0, 0, 0, 42, 137,0, 0, 0, 32, 8, 0, 1, 0, 0, 128,152,128,32, 0, 0, 0, 17, 206,3, 0, 0, 224,167,139,1, 0, 0, 0, 16, 66, 4, 0, 0, 96, 0, 128,1, 0, 0, 96, 34, 219,2, 0, 0, 0, 0, 193,33, 0, 0, 0, 161,193,64, 0, 0, 128,130,130,0, 0, 0, 224,235,140,97, 0, 0, 0, 128,132,32, 0, 0, 0, 34, 9, 34, 0, 0, 32, 136,130,1, 0, 0, 0, 145,68, 32, 4, 0, 0, 1, 192,8, 0, 0, 32, 64, 12, 0, 4, 0, 96, 187,255,109, 4, 0, 0, 144,5, 64, 0, 0, 0, 6, 132,0, 0, 0, 0, 2, 9, 32, 0, 0, 32, 2, 129,1, 0, 0, 64, 8, 66, 0, 0, 0, 128,178,129,1, 0, 0, 32, 96, 0, 0, 0, 0, 64, 5, 129,0, 0, 0, 32, 50, 202,35, 4, 0, 224,10, 197,1, 0, 0, 32, 34, 18, 0, 4, 0, 128,1, 132,0, 0, 0, 96, 0, 133,0, 0, 0, 0, 0, 2, 65, 0, 0, 224,255,255,95, 4, 0, 128,12, 243,1, 4, 0, 0, 34, 130,8, 0, 0, 0, 38, 128,32, 0, 0, 0, 35, 17, 1, 4, 0, 0, 39, 64, 0, 0, 0, 0, 9, 215,5, 0, 0, 32, 32, 128,32, 0, 0, 128,8, 128,17, 0, 0, 32, 7, 3, 0, 0, 0, 0, 140,146,21, 4, 0, 32, 34, 138,33, 0, 0, 64, 162,26, 0, 4, 1, 0, 34, 153,3, 0, 0, 224,63, 156,3, 0, 0, 0, 0, 198,33, 0, 0, 0, 38, 17, 2, 0, 0, 160,51, 215,72, 0, 0, 32, 4, 133,64, 0, 0, 128,0, 132,32, 0, 0, 128,36, 20, 64, 4, 0, 64, 6, 9, 1, 0, 0, 160,144,200,1, 0, 3, 32, 34, 154,33, 0, 0, 192,132,6, 0, 0, 0, 32, 50, 193,65, 0, 0, 32, 130,128,0, 0, 0, 64, 34, 136,2, 4, 0, 32, 34, 76, 59, 4, 0, 128,169,207,59, 0, 0, 32, 154,16, 0, 4, 0, 160,18, 128,1, 0, 0, 0, 1, 129,4, 0, 0, 224,191,207,43, 0, 0, 0, 0, 1, 3, 0, 0, 32, 6, 8, 2, 0, 0, 0, 2, 2, 8, 0, 0, 64, 1, 69, 0, 4, 0, 96, 36, 136,0, 0, 0, 0, 1, 193,4, 0, 0, 32, 50, 16, 2, 0, 0, 32, 20, 64, 0, 0, 0, 224,10, 199,4, 0, 0, 0, 5, 195,4, 0, 0, 0, 138,133,0, 0, 0, 0, 1, 128,33, 4, 0, 0, 32, 128,1, 0, 0, 0, 3, 199,2, 0, 0, 32, 34, 146,2, 4, 1, 0, 34, 136,1, 4, 0, 96, 162,159,1, 0, 0, 64, 19, 129,9, 0, 0, 32, 2, 129,8, 4, 0, 0, 0, 144,1, 0, 0, 128,4, 1, 4, 0, 0, 224,10, 30, 11, 0, 0, 64, 34, 132,0, 0, 0, 0, 11, 199,32, 0, 0, 0, 32, 148,0, 0, 0, 64, 129,0, 0, 0, 1, 64, 32, 128,0, 0, 0, 64, 8, 199,0, 0, 0, 64, 1, 0, 1, 0, 0, 0, 32, 80, 0, 0, 0, 64, 8, 194,0, 0, 0, 224,207,223,69, 0, 0, 0, 0, 78, 0, 0, 0, 64, 34, 1, 2, 0, 0, 32, 146,0, 1, 0, 0, 96, 35, 2, 40, 0, 0, 0, 5, 198,3, 0, 0, 160,0, 68, 0, 0, 0, 0, 64, 0, 64, 0, 0, 32, 4, 0, 1, 0, 0, 0, 26, 4, 2, 0, 67, 128,0, 128,1, 0, 0, 0, 2, 197,32, 0, 0, 64, 15, 135,1, 4, 0, 64, 162,155,1, 0, 0, 0, 2, 192,2, 4, 0, 0, 1, 201,33, 0, 0, 0, 129,4, 1, 0, 0, 0, 48, 4, 0, 0, 0, 160,175,143,33, 4, 0, 0, 64, 0, 0, 0, 0, 160,34, 80, 32, 0, 0, 0, 1, 129,32, 0, 0, 224,17, 136,0, 0, 0, 160,36, 139,67, 4, 0, 224,175,223,127, 0, 0, 32, 17, 10, 0, 4, 0, 0, 32, 1, 32, 0, 0, 0, 65, 149,8, 0, 0, 32, 64, 0, 0, 0, 0, 0, 132,0, 0, 0, 0, 32, 36, 0, 3, 4, 0, 32, 34, 0, 32, 0, 0, 96, 167,139,0, 0, 0, 96, 34, 128,32, 0, 0, 0, 4, 192,33, 0, 0, 0, 0, 132,4, 4, 0, 224,166,155,1, 0, 0, 0, 48, 197,1, 0, 0, 32, 34, 128,66, 0, 0, 0, 160,192,32, 0, 0, 128,128,0, 1, 0, 0, 32, 235,140,33, 0, 0, 32, 2, 128,2, 0, 0, 160,10, 140,1, 0, 0, 128,145,79, 1, 0, 0, 64, 2, 130,0, 0, 0, 96, 2, 0, 2, 0, 0, 0, 48, 129,32, 0, 0, 96, 2, 19, 0, 4, 0, 160,59, 157,0, 0, 0, 0, 128,129,1, 0, 0, 0, 48, 16, 32, 0, 0, 224,170,254,33, 0, 0, 32, 0, 129,64, 0, 0, 96, 2, 4, 4, 0, 0, 32, 2, 40, 0, 0, 0, 128,8, 132,96, 0, 0, 128,48, 128,1, 0, 0, 32, 50, 138,1, 0, 0, 128,24, 213,1, 0, 0, 32, 35, 128,33, 0, 0, 0, 34, 132,4, 0, 0, 32, 34, 197,0, 0, 0, 0, 6, 25, 0, 0, 0, 192,127,150,63, 0, 0, 0, 178,0, 97, 0, 0, 32, 3, 192,32, 0, 0, 0, 0, 21, 1, 0, 0, 0, 32, 137,32, 4, 0, 160,10, 166,1, 0, 0, 160,46, 155,38, 0, 0, 32, 2, 144,0, 0, 0, 128,34, 0, 32, 0, 0, 64, 10, 2, 1, 0, 0, 0, 16, 2, 72, 0, 0, 160,0, 133,8, 0, 0, 96, 255,159,125, 0, 0, 32, 32, 130,8, 0, 0, 32, 2, 8, 3, 4, 0, 128,28, 132,1, 0, 0, 0, 6, 64, 0, 0, 0, 96, 2, 26, 0, 0, 0, 160,17, 0, 1, 4, 0, 0, 16, 8, 0, 0, 0, 160,0, 144,0, 0, 16, 224,15, 191,23, 0, 0, 0, 9, 197,0, 0, 0, 96, 34, 9, 2, 0, 0, 32, 178,0, 32, 0, 0, 0, 1, 192,2, 0, 0, 32, 186,0, 0, 0, 0, 0, 1, 0, 3, 4, 0, 0, 34, 153,0, 0, 0, 0, 17, 0, 17, 0, 0, 0, 17, 1, 1, 0, 0, 0, 2, 137,1, 0, 0, 32, 0, 132,1, 0, 0, 128,126,159,63, 0, 0, 0, 50, 16, 0, 0, 0, 128,0, 144,1, 0, 0, 64, 8, 148,1, 4, 1, 0, 5, 130,0, 0, 1, 0, 0, 6, 1, 0, 0, 224,103,143,17, 4, 1, 32, 34, 129,0, 0, 0, 64, 16, 195,17, 0, 0, 32, 0, 137,34, 0, 0, 96, 0, 16, 0, 0, 0, 0, 129,4, 64, 0, 0, 0, 34, 134,40, 0, 0, 64, 34, 140,0, 0, 0, 160,34, 8, 2, 0, 0, 0, 32, 12, 4, 0, 0, 128,224,4, 1, 0, 0, 224,175,223,49, 0, 0, 128,144,128,0, 0, 0, 64, 8, 0, 8, 0, 0, 0, 2, 136,2, 0, 0, 96, 34, 15, 2, 0, 0, 32, 187,132,0, 0, 0, 32, 32, 16, 0, 0, 0, 224,143,247,5, 0, 0, 0, 176,8, 0, 4, 1, 0, 34, 137,0, 0, 0, 224,35, 187,1, 0, 0, 0, 0, 5, 33, 0, 0, 160,176,128,33, 4, 0, 32, 178,136,11, 0, 0, 0, 33, 132,1, 0, 0, 32, 3, 69, 0, 0, 0, 0, 129,4, 65, 0, 0, 0, 4, 1, 4, 0, 0, 128,13, 197,17, 0, 0, 32, 170,128,0, 4, 0, 0, 24, 194,0, 0, 0, 224,170,223,7, 0, 0, 0, 160,8, 64, 4, 0, 192,159,223,111, 0, 0, 128,16, 1, 0, 0, 128,128,17, 2, 0, 0, 0, 0, 16, 67, 10, 0, 0, 224,34, 195,35, 0, 0, 32, 2, 74, 0, 0, 0, 96, 52, 147,0, 0, 0, 192,13, 214,15, 0, 0, 160,128,1, 0, 0, 0, 32, 50, 9, 2, 4, 0, 32, 0, 132,0, 0, 0, 0, 0, 78, 6, 0, 0, 0, 4, 18, 0, 0, 0, 128,5, 128,5, 0, 0, 160,4, 196,1, 0, 0, 224,159,215,9, 0, 0, 32, 1, 38, 21, 0, 0, 0, 32, 130,1, 0, 0, 128,0, 65, 2, 0, 0, 32, 18, 73, 2, 0, 0, 128,5, 175,5, 0, 0, 0, 130,8, 32, 0, 0, 0, 20, 128,5, 4, 0, 0, 2, 128,32, 0, 0, 128,128,130,0, 0, 0, 160,0, 0, 4, 0, 0, 0, 4, 0, 5, 0, 0, 160,40, 163,1, 0, 0, 0, 0, 24, 1, 0, 0, 160,50, 24, 43, 0, 0, 0, 36, 4, 5, 0, 0, 160,2, 65, 1, 0, 0, 192,32, 212,0, 0, 0, 0, 0, 196,4, 4, 0, 192,157,221,41, 0, 0, 0, 8, 128,4, 0, 0, 128,50, 8, 1, 0, 0, 0, 40, 68, 1, 0, 0, 224,170,238,3, 0, 0, 0, 40, 0, 2, 0, 0, 0, 65, 0, 0, 4, 0, 32, 51, 198,3, 0, 1, 64, 8, 64, 0, 0, 0, 0, 5, 193,0, 0, 0, 32, 50, 139,65, 0, 0, 0, 24, 2, 2, 0, 0, 0, 17, 2, 0, 0, 0, 224,191,201,38, 0, 0, 128,57, 247,111, 0, 0, 160,18, 0, 2, 4, 0, 160,179,75, 7, 0, 0, 0, 1, 192,64, 0, 0, 0, 0, 208,64, 0, 0, 0, 1, 196,65, 0, 0, 128,2, 0, 65, 0, 0, 32, 3, 164,2, 0, 0, 128,66, 197,97, 4, 0, 224,191,255,77, 0, 0, 128,128,199,3, 0, 0, 0, 34, 65, 2, 0, 0, 0, 0, 2, 3, 0, 0, 64, 24, 71, 46, 0, 0, 192,156,147,76, 0, 0, 0, 6, 72, 0, 0, 0, 0, 2, 20, 0, 0, 0, 0, 0, 128,96, 0, 0, 160,136,145,5, 0, 0, 0, 6, 72, 42, 0, 0, 0, 128,128,64, 0, 0, 160,2, 129,1, 0, 0, 128,16, 2, 1, 0, 0, 128,0, 193,4, 0, 0, 160,32, 1, 3, 0, 0, 0, 48, 8, 32, 0, 0, 224,43, 207,39, 0, 0, 192,186,161,1, 0, 0, 128,0, 129,32, 0, 0, 32, 32, 5, 0, 0, 0, 0, 9, 129,17, 0, 0, 32, 17, 0, 0, 0, 0, 160,123,205,109, 0, 0, 32, 42, 0, 1, 0, 0, 160,35, 11, 5, 0, 0, 0, 0, 224,64, 0, 0, 128,0, 32, 1, 0, 0, 128,0, 0, 67, 4, 0, 160,43, 140,67, 0, 0, 128,10, 194,97, 0, 0, 128,2, 128,1, 0, 0, 224,59, 207,39, 0, 0, 0, 64, 65, 0, 0, 0, 0, 4, 129,65, 0, 0, 128,128,32, 0, 0, 0, 32, 34, 0, 3, 0, 0, 128,32, 0, 9, 0, 0, 0, 34, 64, 2, 0, 0, 128,2, 144,3, 4, 0, 32, 2, 200,1, 0, 1, 0, 1, 65, 0, 0, 0, 224,171,223,85, 0, 0, 64, 8, 69, 3, 4, 0, 32, 178,72, 1, 0, 0, 0, 34, 196,32, 4, 1, 0, 34, 128,32, 0, 0, 32, 2, 66, 0, 4, 1, 0, 10, 128,1, 0, 0, 0, 32, 69, 41, 0, 0, 0, 42, 13, 2, 0, 0, 0, 160,64, 1, 4, 0, 128,44, 215,5, 0, 0, 64, 0, 24, 5, 0, 0, 64, 0, 4, 4, 0, 0, 0, 34, 8, 6, 0, 0, 0, 2, 0, 6, 0, 0, 160,163,140,97, 4, 0, 0, 0, 69, 32, 4, 0, 0, 136,0, 0, 0, 0, 0, 8, 192,33, 0, 0, 32, 43, 0, 3, 0, 0, 0, 8, 3, 32, 0, 0, 160,178,136,3, 0, 0, 64, 0, 8, 1, 0, 0, 32, 34, 72, 3, 0, 0, 160,3, 0, 1, 0, 0, 128,0, 4, 64, 0, 0, 128,17, 128,0, 0, 0, 128,187,207,43, 0, 0, 0, 128,1, 64, 0, 0, 32, 2, 1, 3, 0, 0, 128,9, 193,1, 0, 0, 160,169,151,71, 0, 0, 64, 2, 194,0, 0, 0, 224,137,196,0, 0, 0, 0, 9, 66, 0, 0, 0, 160,178,23, 1, 0, 0, 64, 18, 130,0, 0, 0, 32, 50, 64, 1, 0, 0, 128,8, 129,0, 0, 0, 128,170,13, 32, 0, 0, 128,40, 5, 5, 128,8, 32, 58, 221,63, 0, 0, 224,255,247,127, 0, 0, 32, 24, 1, 0, 0, 0, 32, 144,40, 0, 0, 0, 32, 183,135,0, 0, 0, 32, 42, 143,2, 0, 0, 0, 1, 198,0, 0, 0, 0, 32, 153,1, 0, 0, 64, 10, 162,2, 0, 0, 64, 13, 197,5, 0, 0, 0, 32, 192,8, 4, 0, 32, 50, 194,8, 0, 0, 0, 20, 199,34, 0, 0, 224,167,247,73, 0, 0, 0, 144,8, 66, 0, 0, 32, 34, 206,4, 0, 1, 128,0, 68, 8, 0, 0, 0, 0, 20, 2, 0, 0, 0, 34, 138,10, 4, 0, 96, 34, 156,32, 4, 0, 0, 82, 206,64, 0, 0, 64, 0, 146,0, 0, 0, 160,171,132,1, 4, 0, 32, 34, 137,64, 4, 0, 192,191,215,105, 4, 0, 0, 2, 193,8, 0, 0, 32, 48, 132,64, 0, 0, 160,162,136,0, 0, 0, 128,133,65, 0, 0, 0, 128,0, 12, 16, 0, 0, 0, 48, 136,0, 0, 0, 160,177,158,65, 0, 0, 224,34, 72, 32, 0, 0, 128,130,5, 0, 0, 0, 32, 34, 10, 2, 0, 0, 160,150,193,75, 0, 0, 32, 39, 2, 0, 4, 1, 192,62, 147,40, 0, 0, 32, 34, 16, 32, 4, 0, 192,2, 135,9, 4, 0, 224,59, 215,61, 0, 0, 0, 129,213,5, 4, 0, 96, 22, 131,8, 0, 0, 192,2, 210,0, 0, 0, 0, 11, 133,0, 0, 0, 128,139,214,76, 0, 8, 0, 34, 138,0, 4, 0, 0, 3, 129,0, 0, 0, 0, 2, 71, 2, 0, 0, 0, 18, 131,0, 0, 0, 224,155,215,1, 0, 0, 0, 2, 7, 0, 0, 0, 32, 18, 13, 0, 4, 0, 0, 2, 131,0, 0, 0, 64, 190,194,1, 0, 0, 64, 186,134,65, 0, 0, 64, 42, 2, 0, 0, 0, 224,133,149,65, 0, 0, 96, 34, 143,2, 0, 0, 0, 2, 136,96, 0, 0, 128,171,204,67, 0, 0, 32, 34, 73, 33, 0, 0, 0, 18, 1, 72, 0, 0, 0, 131,77, 1, 0, 0, 0, 18, 130,64, 0, 0, 192,2, 141,1, 0, 0, 0, 34, 15, 0, 4, 0, 224,62, 219,8, 0, 0, 0, 2, 72, 34, 0, 0, 64, 12, 7, 4, 0, 0, 0, 20, 8, 0, 0, 0, 0, 18, 147,65, 0, 0, 32, 2, 67, 0, 0, 0, 32, 163,8, 43, 0, 0, 128,17, 199,65, 4, 0, 32, 1, 0, 32, 0, 0, 0, 4, 0, 37, 4, 0, 160,35, 141,43, 0, 0, 128,1, 195,36, 0, 0, 32, 3, 69, 41, 0, 0, 192,34, 138,10, 0, 0, 160,2, 17, 0, 4, 0, 96, 36, 8, 6, 0, 0, 0, 1, 192,4, 0, 0, 128,13, 215,65, 0, 0, 0, 34, 4, 32, 4, 0, 0, 128,145,0, 0, 0, 0, 32, 16, 8, 0, 0, 64, 130,31, 1, 0, 0, 224,182,199,9, 4, 1, 224,61, 207,40, 0, 0, 0, 34, 12, 1, 0, 0, 32, 34, 11, 32, 0, 0, 0, 1, 70, 1, 4, 0, 224,3, 219,0, 0, 0, 0, 0, 154,0, 0, 0, 0, 38, 1, 1, 4, 0, 0, 0, 132,33, 0, 0, 224,143,215,93, 0, 0, 0, 178,131,8, 0, 0, 0, 43, 78, 1, 0, 0, 192,9, 199,9, 0, 0, 0, 3, 130,64, 0, 0, 32, 60, 129,1, 0, 0, 224,191,199,35, 0, 0, 224,54, 207,9, 4, 0, 0, 0, 6, 0, 4, 0, 224,54, 158,32, 0, 0, 0, 35, 66, 0, 0, 0, 0, 5, 198,33, 0, 0, 224,14, 135,1, 0, 1, 0, 0, 130,0, 0, 0, 0, 16, 5, 0, 0, 0, 64, 1, 4, 10, 0, 0, 64, 38, 200,64, 0, 0, 64, 2, 146,0, 0, 0, 224,175,205,73, 4, 0, 64, 118,155,43, 0, 0, 0, 1, 193,16, 0, 0, 0, 0, 6, 8, 0, 0, 0, 16, 8, 3, 4, 0, 64, 40, 195,0, 0, 0, 160,55, 207,0, 0, 0, 160,6, 4, 0, 0, 0, 0, 38, 137,32, 0, 0, 0, 134,4, 0, 0, 0, 0, 146,9, 1, 0, 0, 0, 18, 73, 64, 4, 1, 1, 16, 192,0, 0, 0, 0, 38, 16, 0, 0, 0, 128,34, 136,33, 4, 0, 0, 1, 135,1, 0, 0, 32, 16, 0, 64, 0, 0, 128,51, 159,9, 0, 0, 160,25, 199,0, 0, 0, 96, 38, 10, 33, 0, 0, 128,150,199,73, 4, 0, 160,34, 0, 32, 0, 0, 32, 51, 204,0, 0, 0, 32, 32, 132,0, 0, 0, 0, 2, 66, 32, 0, 0, 0, 34, 199,40, 0, 0, 32, 130,0, 2, 0, 0, 96, 222,95, 79, 0, 0, 0, 4, 193,0, 0, 0, 0, 14, 0, 8, 0, 0, 160,32, 4, 1, 0, 0, 160,128,8, 0, 0, 0, 224,167,157,5, 0, 0, 0, 128,4, 4, 4, 4, 0, 46, 219,0, 0, 0, 32, 2, 134,0, 0, 0, 0, 66, 2, 64, 4, 0, 0, 137,132,1, 0, 0, 64, 38, 129,0, 0, 0, 0, 191,159,73, 0, 0, 224,183,211,40, 0, 0, 0, 2, 64, 34, 0, 0, 64, 48, 10, 33, 0, 1, 32, 0, 0, 0, 0, 0, 224,7, 195,14, 0, 0, 0, 0, 68, 36, 0, 0, 128,135,207,1, 4, 0, 0, 34, 145,2, 4, 0, 32, 35, 155,2, 0, 0, 32, 50, 129,41, 0, 0, 0, 16, 11, 64, 0, 0, 32, 34, 1, 40, 0, 0, 128,40, 20, 33, 0, 0, 160,1, 132,1, 0, 0, 0, 24, 1, 0, 0, 17, 0, 1, 128,0, 0, 0, 0, 0, 136,8, 0, 0, 64, 0, 4, 3, 0, 0, 0, 7, 129,0, 0, 8, 0, 3, 205,65, 0, 0, 0, 16, 8, 8, 0, 0, 224,139,207,69, 0, 0, 32, 163,12, 1, 0, 6, 32, 102,110,51, 0, 0, 32, 0, 78, 0, 0, 1, 0, 0, 0, 16, 0, 1, 0, 0, 0, 64, 4, 0, 96, 58, 175,111, 0, 0, 32, 154,130,2, 4, 0, 224,219,215,107, 0, 0, 0, 136,1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 128,128,2, 0, 0, 96, 0, 130,2, 0, 0, 0, 25, 142,67, 0, 0, 128,171,12, 1, 0, 0, 64, 9, 70, 0, 0, 0, 0, 48, 130,2, 0, 0, 160,59, 223,7, 0, 0, 0, 153,198,1, 0, 0, 64, 16, 0, 32, 4, 2, 64, 40, 141,41, 0, 0, 0, 2, 138,1, 0, 0, 64, 3, 1, 5, 0, 1, 0, 16, 128,0, 0, 0, 64, 171,8, 1, 4, 0, 0, 130,128,33, 0, 0, 0, 131,20, 1, 4, 0, 160,155,223,43, 0, 0, 160,137,0, 1, 0, 0, 0, 163,0, 3, 0, 0, 0, 131,14, 64, 4, 0, 0, 131,207,7, 0, 1, 64, 130,226,0, 0, 0, 0, 1, 2, 34, 0, 0, 160,154,223,1, 0, 16, 0, 0, 0, 32, 0, 0, 96, 51, 139,123, 0, 0, 192,253,247,111, 0, 0, 160,144,0, 0, 4, 0, 32, 2, 66, 0, 0, 0, 32, 0, 71, 0, 0, 0, 96, 32, 24, 66, 0, 0, 160,171,8, 99, 0, 0, 32, 48, 80, 0, 0, 1, 0, 0, 0, 24, 0, 4, 0, 0, 0, 8, 0, 0, 160,3, 65, 65, 0, 0, 0, 8, 0, 65, 0, 0, 160,8, 4, 0, 0, 0, 224,183,255,7, 0, 0, 0, 8, 1, 64, 0, 0, 32, 35, 7, 0, 0, 0, 32, 34, 6, 0, 0, 0, 96, 128,1, 0, 0, 0, 32, 169,140,65, 0, 0, 96, 40, 74, 69, 0, 0, 128,34, 128,0, 0, 0, 32, 32, 128,1, 0, 0, 128,143,223,65, 0, 0, 128,1, 5, 0, 0, 0, 160,141,132,1, 0, 0, 0, 4, 17, 0, 0, 0, 192,128,0, 0, 0, 0, 192,63, 79, 2, 4, 0, 0, 0, 1, 32, 0, 0, 32, 1, 9, 1, 0, 0, 32, 170,0, 0, 0, 0, 0, 128,194,1, 0, 0, 32, 41, 68, 1, 0, 0, 224,182,87, 77, 0, 0, 32, 2, 3, 2, 0, 0, 0, 0, 146,8, 0, 0, 160,34, 4, 0, 0, 32, 32, 34, 8, 0, 0, 0, 0, 24, 64, 0, 0, 1, 0, 0, 0, 68, 0, 0, 128,8, 133,8, 0, 0, 0, 0, 0, 80 }; #define CHARSET_SIZE 40 #define NumLargeCounts 157 static const unsigned char EndCountLge[157] = { 0, 1, 14, 2, 1, 1, 1, 25, 4, 1, 4, 1, 2, 4, 4, 1, 3, 26, 6, 1, 3, 1, 2, 8, 1, 2, 1, 2, 1, 18, 2, 6, 1, 3, 1, 2, 1, 1, 9, 1, 1, 1, 12, 2, 1, 1, 1, 1, 2, 1, 14, 2, 1, 1, 2, 3, 1, 1, 16, 5, 1, 3, 1, 3, 1, 6, 3, 5, 1, 1, 1, 10, 2, 1, 2, 1, 1, 15, 4, 3, 2, 2, 1, 24, 8, 1, 2, 3, 3, 3, 4, 1, 6, 1, 1, 1, 1, 5, 19, 4, 3, 1, 2, 1, 2, 3, 1, 1, 1, 1, 16, 2, 6, 1, 3, 1, 36, 3, 3, 2, 3, 4, 1, 2, 1, 2, 2, 6, 1, 1, 1, 2, 1, 14, 2, 2, 2, 1, 2, 2, 1, 1, 3, 2, 6, 2, 1, 1, 1, 11, 2, 2, 1, 2, 1, 1, 2 }; static const unsigned char EndCountSml[36480] = { 82, 34, 240,87, 229,217,7, 151,240,111,135,250,2, 17, 121,68, 61, 196,136,214,244,112,1, 50, 48, 69, 2, 172,115,211,156,87, 2, 176,83, 174,71, 196,131,74, 81, 58, 196,63, 154,235,145,236,64, 41, 69, 245,176,121,97, 27, 27, 137,191,9, 68, 199,180,217,202,14, 93, 53, 166,43, 49, 130,1, 219,4, 85, 13, 29, 180,29, 174,189, 105,171,153,48, 76, 80, 36, 67, 30, 196,224,123,205,71, 145,242,92, 39, 106,18, 2, 44, 162,145,27, 111,52, 44, 171,245,55, 235,176,109,18, 196,221,88, 36, 66, 90, 75, 37, 6, 123,103,161,64, 82, 130,27, 193,89, 3, 19, 99, 57, 245,5, 1, 68, 3, 63, 82, 24, 88, 183,72, 125,32, 123,226,143,219,22, 2, 2, 2, 2, 1, 3, 3, 3, 3, 2, 35, 2, 2, 2, 2, 1, 1, 1, 11, 8, 6, 5, 2, 1, 2, 1, 1, 1, 1, 1, 7, 3, 2, 1, 1, 1, 4, 4, 3, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 5, 1, 1, 3, 3, 2, 2, 38, 11, 2, 12, 3, 2, 2, 12, 2, 2, 1, 2, 1, 36, 6, 1, 1, 1, 12, 6, 4, 3, 2, 1, 1, 2, 15, 4, 2, 2, 1, 3, 2, 2, 3, 71, 9, 1, 13, 3, 2, 2, 2, 10, 33, 2, 4, 3, 3, 2, 2, 2, 16, 1, 1, 1, 1, 10, 7, 5, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 3, 3, 2, 1, 1, 4, 4, 1, 17, 4, 3, 2, 2, 1, 1, 2, 1, 7, 1, 2, 2, 2, 4, 2, 2, 2, 8, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 4, 2, 2, 6, 2, 2, 1, 3, 3, 2, 2, 5, 3, 2, 1, 1, 1, 1, 1, 71, 5, 2, 2, 9, 12, 3, 1, 1, 12, 2, 1, 1, 2, 1, 1, 11, 2, 1, 1, 11, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 4, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 7, 3, 3, 3, 3, 2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 68, 16, 9, 3, 2, 2, 1, 1, 1, 1, 3, 3, 2, 2, 1, 1, 1, 1, 5, 2, 2, 1, 1, 3, 3, 2, 13, 7, 5, 4, 3, 1, 1, 7, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 8, 3, 3, 2, 2, 5, 4, 1, 1, 7, 3, 3, 2, 2, 2, 2, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 49, 5, 3, 2, 2, 5, 1, 1, 2, 2, 2, 2, 2, 6, 3, 1, 1, 3, 2, 1, 8, 1, 6, 5, 4, 3, 1, 1, 1, 2, 2, 2, 2, 2, 2, 5, 1, 2, 1, 1, 1, 1, 1, 3, 2, 2, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 52, 2, 2, 2, 3, 2, 1, 1, 10, 4, 2, 2, 1, 1, 1, 2, 3, 2, 7, 5, 5, 4, 3, 2, 1, 8, 2, 2, 6, 1, 2, 1, 1, 1, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 50, 5, 2, 2, 1, 1, 6, 6, 4, 2, 2, 4, 2, 2, 3, 2, 2, 7, 3, 3, 2, 10, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 5, 4, 3, 2, 1, 1, 1, 1, 6, 2, 2, 2, 2, 1, 1, 4, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 3, 1, 1, 8, 8, 5, 4, 3, 2, 2, 1, 1, 1, 1, 5, 3, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 2, 3, 2, 1, 1, 4, 1, 1, 1, 1, 1, 2, 2, 8, 6, 5, 4, 3, 1, 10, 2, 8, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 5, 5, 5, 4, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 3, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 9, 7, 5, 4, 3, 2, 2, 5, 5, 5, 4, 3, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 5, 3, 3, 2, 2, 2, 2, 50, 31, 1, 21, 16, 10, 7, 4, 3, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 3, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 8, 1, 1, 1, 5, 5, 2, 2, 2, 2, 2, 217,19, 1, 3, 3, 2, 1, 5, 5, 5, 5, 1, 3, 1, 22, 4, 1, 1, 4, 3, 2, 7, 6, 3, 1, 1, 1, 1, 1, 2, 12, 3, 3, 3, 1, 8, 2, 2, 5, 4, 3, 19, 2, 2, 2, 1, 1, 1, 1, 1, 5, 3, 2, 2, 1, 1, 1, 10, 6, 6, 3, 2, 2, 3, 26, 7, 2, 3, 2, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 10, 2, 2, 2, 2, 5, 1, 2, 2, 1, 1, 1, 4, 1, 3, 7, 3, 4, 4, 4, 4, 4, 19, 1, 2, 1, 2, 2, 2, 2, 1, 2, 5, 1, 1, 4, 2, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 27, 14, 1, 1, 1, 1, 1, 5, 5, 5, 3, 5, 1, 1, 1, 2, 2, 1, 5, 3, 4, 2, 2, 1, 1, 31, 1, 1, 1, 4, 4, 2, 2, 3, 2, 15, 8, 5, 5, 3, 3, 2, 6, 5, 2, 1, 5, 1, 1, 1, 1, 2, 3, 3, 3, 14, 1, 4, 4, 3, 3, 1, 6, 4, 2, 3, 2, 227,7, 5, 5, 5, 4, 3, 1, 1, 1, 1, 1, 1, 86, 2, 2, 2, 26, 6, 6, 6, 6, 5, 2, 3, 3, 1, 6, 6, 2, 10, 10, 2, 3, 3, 2, 1, 5, 5, 5, 5, 5, 2, 4, 2, 2, 2, 2, 2, 2, 2, 33, 1, 1, 16, 4, 4, 4, 4, 4, 12, 3, 3, 2, 9, 9, 6, 6, 2, 2, 2, 2, 6, 6, 3, 2, 1, 9, 8, 8, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 16, 3, 3, 3, 3, 3, 4, 3, 2, 9, 2, 2, 5, 1, 1, 16, 2, 1, 2, 1, 2, 1, 3, 2, 22, 6, 1, 1, 9, 5, 5, 4, 2, 4, 3, 1, 6, 4, 1, 1, 1, 1, 1, 1, 17, 5, 5, 2, 2, 2, 2, 4, 2, 5, 5, 5, 5, 5, 5, 5, 4, 2, 1, 2, 7, 2, 2, 2, 2, 2, 12, 12, 5, 4, 4, 4, 2, 2, 1, 1, 7, 3, 2, 2, 2, 8, 4, 3, 1, 2, 1, 1, 32, 19, 2, 3, 3, 1, 13, 7, 7, 3, 2, 4, 4, 2, 2, 2, 2, 5, 5, 4, 2, 1, 1, 8, 2, 2, 2, 2, 2, 2, 2, 247,34, 1, 2, 1, 1, 20, 4, 1, 2, 2, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2, 31, 3, 1, 5, 1, 2, 2, 16, 6, 6, 3, 2, 2, 3, 2, 4, 4, 3, 3, 4, 4, 4, 4, 25, 2, 2, 2, 14, 3, 2, 2, 3, 3, 2, 3, 1, 1, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 1, 6, 2, 1, 1, 1, 1, 1, 1, 1, 14, 1, 2, 2, 2, 2, 3, 1, 2, 2, 8, 7, 7, 2, 1, 5, 1, 1, 4, 2, 2, 4, 3, 34, 30, 10, 8, 8, 8, 3, 3, 5, 5, 5, 3, 11, 5, 2, 2, 3, 3, 3, 6, 4, 2, 3, 3, 3, 2, 23, 8, 3, 3, 3, 3, 3, 3, 3, 2, 6, 6, 4, 7, 15, 1, 1, 4, 4, 4, 4, 2, 10, 6, 5, 3, 3, 2, 1, 1, 1, 4, 4, 4, 1, 1, 42, 9, 9, 6, 5, 3, 3, 3, 3, 18, 6, 6, 4, 4, 12, 5, 2, 7, 6, 6, 5, 12, 10, 5, 3, 3, 3, 3, 3, 1, 1, 17, 3, 2, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 2, 1, 4, 2, 2, 2, 2, 2, 2, 58, 2, 1, 1, 31, 2, 8, 8, 8, 5, 4, 4, 12, 2, 2, 2, 5, 5, 5, 5, 4, 4, 2, 2, 2, 3, 2, 2, 2, 4, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 3, 3, 3, 13, 12, 12, 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 2, 1, 2, 2, 2, 103,10, 2, 2, 1, 1, 1, 3, 2, 2, 1, 1, 1, 1, 15, 1, 1, 9, 3, 2, 11, 9, 4, 4, 4, 4, 4, 4, 4, 3, 10, 2, 5, 5, 5, 4, 3, 1, 9, 3, 2, 1, 1, 1, 1, 1, 1, 5, 5, 3, 3, 1, 1, 17, 3, 9, 1, 7, 3, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 22, 5, 2, 1, 2, 10, 5, 2, 2, 3, 2, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 43, 5, 4, 2, 7, 7, 6, 5, 8, 3, 1, 1, 1, 1, 1, 1, 4, 3, 2, 2, 2, 5, 4, 4, 2, 1, 1, 2, 2, 1, 1, 94, 1, 1, 8, 3, 1, 8, 3, 3, 8, 3, 7, 2, 2, 2, 2, 2, 7, 4, 1, 1, 2, 1, 1, 39, 4, 2, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 2, 6, 5, 2, 3, 2, 5, 2, 2, 3, 3, 3, 1, 1, 5, 4, 1, 5, 1, 7, 1, 1, 2, 3, 2, 3, 3, 23, 3, 1, 6, 5, 1, 7, 1, 1, 3, 1, 1, 1, 1, 41, 3, 3, 2, 5, 5, 2, 9, 2, 2, 1, 8, 6, 2, 4, 2, 2, 1, 2, 52, 15, 1, 1, 1, 1, 1, 6, 3, 2, 2, 2, 22, 1, 1, 19, 13, 4, 3, 6, 3, 1, 4, 1, 2, 5, 1, 1, 1, 1, 1, 26, 9, 3, 3, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 12, 2, 4, 4, 4, 3, 2, 21, 7, 6, 5, 1, 2, 5, 3, 2, 2, 62, 4, 2, 1, 3, 1, 1, 7, 5, 5, 5, 5, 1, 1, 1, 1, 1, 5, 4, 9, 3, 2, 4, 2, 2, 2, 4, 3, 1, 17, 11, 1, 9, 9, 3, 6, 3, 1, 1, 1, 1, 16, 5, 1, 2, 2, 2, 2, 3, 2, 6, 6, 5, 12, 2, 5, 4, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 50, 4, 4, 1, 1, 1, 6, 4, 6, 6, 5, 4, 1, 1, 2, 2, 10, 2, 2, 2, 1, 126,1, 11, 2, 2, 2, 1, 1, 3, 2, 2, 1, 43, 12, 3, 3, 3, 1, 1, 2, 2, 11, 3, 1, 1, 6, 4, 3, 2, 1, 5, 4, 1, 2, 19, 3, 2, 2, 5, 4, 3, 1, 1, 4, 13, 8, 4, 1, 2, 10, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 6, 4, 26, 11, 1, 4, 2, 2, 6, 1, 1, 1, 1, 2, 1, 1, 6, 1, 1, 4, 1, 1, 19, 9, 3, 1, 1, 2, 2, 1, 20, 2, 4, 3, 11, 8, 5, 5, 5, 4, 2, 2, 2, 2, 2, 1, 1, 1, 1, 5, 2, 1, 3, 2, 2, 2, 9, 3, 2, 2, 56, 5, 1, 1, 1, 1, 1, 1, 1, 21, 2, 1, 1, 1, 16, 2, 7, 7, 1, 1, 1, 1, 1, 6, 3, 8, 3, 2, 4, 2, 6, 2, 1, 10, 3, 3, 3, 3, 1, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 1, 1, 7, 5, 1, 1, 2, 31, 9, 7, 2, 3, 12, 7, 2, 8, 4, 3, 9, 2, 7, 2, 1, 1, 1, 1, 1, 1, 228,47, 1, 2, 6, 2, 3, 4, 1, 1, 7, 3, 9, 3, 1, 3, 2, 2, 2, 5, 2, 2, 1, 1, 1, 7, 2, 34, 3, 3, 3, 3, 3, 3, 6, 6, 10, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 7, 6, 6, 5, 6, 2, 2, 42, 1, 5, 10, 6, 1, 1, 14, 10, 9, 9, 5, 3, 1, 1, 1, 3, 1, 1, 2, 1, 1, 25, 3, 3, 4, 3, 5, 2, 2, 4, 10, 1, 1, 2, 1, 5, 4, 2, 1, 1, 6, 3, 3, 2, 2, 3, 3, 1, 1, 1, 1, 20, 2, 4, 8, 3, 4, 3, 3, 13, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 2, 1, 9, 1, 1, 1, 5, 4, 3, 2, 2, 1, 1, 10, 2, 2, 2, 2, 4, 41, 2, 3, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 3, 1, 1, 12, 1, 1, 8, 4, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 2, 6, 6, 5, 4, 4, 2, 2, 2, 1, 1, 16, 2, 9, 8, 6, 69, 4, 1, 1, 12, 11, 1, 6, 6, 2, 3, 1, 38, 6, 3, 20, 5, 2, 1, 1, 3, 4, 4, 5, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 1, 1, 14, 2, 2, 2, 2, 2, 6, 6, 6, 6, 3, 3, 2, 2, 2, 3, 3, 1, 1, 2, 2, 79, 1, 1, 40, 33, 3, 12, 4, 5, 2, 1, 5, 10, 4, 1, 1, 1, 1, 12, 2, 6, 2, 4, 3, 2, 2, 2, 6, 3, 2, 1, 17, 8, 6, 1, 4, 2, 3, 2, 1, 1, 1, 9, 2, 3, 2, 63, 15, 4, 4, 4, 3, 3, 3, 3, 2, 11, 2, 1, 1, 3, 12, 3, 2, 2, 2, 2, 14, 7, 7, 7, 6, 7, 7, 3, 1, 1, 1, 12, 2, 2, 2, 4, 3, 3, 2, 2, 2, 2, 2, 18, 4, 4, 4, 3, 5, 5, 5, 106, 9, 4, 4, 4, 4, 3, 1, 1, 9, 1, 5, 3, 3, 1, 1, 13, 2, 6, 1, 5, 4, 4, 3, 3, 3, 3, 3, 34, 4, 2, 2, 7, 1, 4, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 3, 3, 1, 1, 1, 1, 1, 1, 1, 4, 4, 22, 18, 8, 3, 3, 4, 4, 6, 1, 2, 2, 4, 3, 4, 1, 2, 2, 1, 4, 4, 20, 3, 1, 3, 3, 3, 3, 2, 2, 2, 1, 5, 4, 3, 3, 3, 2, 1, 5, 3, 3, 2, 2, 1, 4, 2, 1, 170,9, 1, 6, 1, 5, 3, 3, 2, 2, 4, 2, 2, 2, 2, 2, 1, 1, 3, 1, 1, 2, 1, 25, 2, 2, 2, 2, 2, 1, 16, 1, 4, 4, 11, 9, 7, 4, 1, 3, 3, 1, 1, 1, 1, 1, 1, 109,7, 2, 5, 1, 3, 29, 12, 6, 3, 1, 6, 5, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 6, 6, 3, 3, 35, 4, 4, 18, 4, 2, 2, 2, 1, 2, 1, 1, 1, 11, 5, 5, 5, 5, 5, 5, 32, 4, 4, 4, 3, 11, 6, 6, 6, 6, 3, 4, 4, 4, 1, 1, 16, 4, 4, 4, 4, 4, 4, 4, 3, 1, 5, 3, 3, 3, 3, 3, 10, 5, 2, 3, 3, 3, 2, 1, 10, 10, 5, 2, 2, 2, 5, 2, 33, 5, 3, 7, 4, 3, 3, 2, 1, 2, 2, 2, 3, 2, 3, 1, 5, 15, 2, 1, 5, 4, 4, 4, 2, 2, 5, 1, 1, 2, 1, 1, 1, 50, 6, 3, 5, 1, 1, 1, 34, 7, 2, 3, 3, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 9, 1, 2, 2, 2, 2, 11, 2, 5, 5, 5, 4, 2, 2, 2, 2, 2, 1, 1, 17, 6, 4, 2, 3, 2, 3, 1, 2, 28, 4, 1, 1, 1, 1, 1, 1, 3, 2, 2, 2, 11, 4, 2, 2, 2, 2, 29, 7, 6, 1, 5, 4, 16, 1, 6, 2, 3, 5, 5, 4, 4, 1, 32, 6, 4, 4, 2, 2, 1, 1, 8, 4, 3, 2, 2, 2, 2, 2, 1, 4, 1, 1, 2, 2, 1, 4, 1, 1, 16, 9, 3, 4, 3, 59, 13, 1, 2, 2, 1, 1, 6, 4, 4, 4, 2, 2, 1, 1, 1, 12, 2, 7, 2, 2, 2, 2, 3, 1, 1, 9, 3, 1, 1, 1, 7, 3, 6, 5, 3, 27, 2, 8, 1, 1, 3, 9, 3, 3, 1, 1, 17, 2, 2, 2, 1, 1, 1, 1, 1, 4, 2, 2, 1, 1, 1, 6, 4, 4, 4, 3, 2, 1, 1, 1, 1, 58, 13, 2, 2, 2, 1, 7, 2, 1, 14, 2, 1, 1, 1, 1, 4, 1, 1, 1, 1, 15, 2, 1, 7, 2, 4, 2, 1, 13, 2, 6, 11, 8, 1, 5, 3, 1, 3, 3, 2, 2, 53, 2, 2, 2, 11, 2, 7, 3, 1, 2, 2, 2, 1, 4, 23, 7, 2, 2, 2, 4, 2, 2, 2, 2, 1, 7, 6, 1, 3, 1, 8, 7, 2, 4, 2, 11, 1, 1, 7, 3, 1, 1, 11, 6, 5, 2, 1, 1, 3, 3, 2, 1, 1, 2, 15, 1, 12, 3, 3, 1, 1, 6, 5, 3, 2, 1, 1, 2, 44, 5, 1, 1, 1, 1, 1, 1, 5, 2, 2, 2, 4, 1, 13, 6, 3, 2, 2, 3, 2, 3, 2, 6, 1, 10, 3, 3, 3, 8, 3, 2, 3, 2, 2, 3, 2, 2, 16, 1, 1, 1, 4, 2, 1, 1, 7, 1, 1, 6, 2, 83, 15, 2, 8, 8, 7, 7, 4, 4, 4, 1, 21, 2, 6, 6, 6, 2, 5, 5, 4, 2, 2, 5, 4, 4, 4, 15, 5, 2, 8, 8, 4, 4, 9, 7, 6, 6, 6, 2, 14, 6, 7, 4, 2, 41, 4, 3, 3, 2, 2, 2, 12, 5, 4, 4, 4, 2, 3, 3, 16, 3, 12, 6, 2, 4, 4, 1, 1, 1, 1, 2, 1, 2, 2, 3, 1, 1, 1, 1, 1, 138,2, 8, 7, 2, 2, 1, 4, 2, 22, 5, 9, 2, 3, 2, 6, 5, 5, 2, 3, 1, 6, 2, 4, 3, 5, 4, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 6, 3, 2, 2, 9, 2, 6, 3, 3, 3, 2, 65, 20, 1, 13, 7, 6, 3, 1, 1, 1, 1, 20, 2, 2, 2, 4, 11, 7, 3, 2, 2, 10, 4, 3, 3, 3, 3, 3, 3, 10, 7, 7, 7, 3, 3, 3, 3, 4, 185,10, 3, 2, 3, 1, 9, 1, 1, 5, 5, 5, 5, 2, 32, 2, 2, 2, 21, 3, 2, 1, 12, 6, 5, 3, 3, 7, 3, 2, 2, 1, 1, 1, 1, 1, 22, 3, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 12, 12, 12, 5, 5, 2, 2, 2, 1, 4, 2, 4, 2, 9, 1, 1, 6, 10, 3, 4, 3, 3, 2, 27, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 17, 5, 3, 1, 5, 4, 5, 3, 3, 3, 44, 16, 5, 4, 4, 4, 4, 11, 11, 9, 4, 2, 5, 3, 21, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 8, 5, 5, 3, 3, 2, 4, 3, 3, 3, 1, 1, 1, 2, 2, 1, 2, 1, 1, 77, 18, 3, 3, 2, 5, 2, 2, 2, 2, 6, 2, 3, 36, 5, 2, 2, 2, 10, 5, 4, 18, 3, 2, 2, 5, 2, 14, 2, 4, 2, 2, 1, 8, 5, 5, 41, 19, 6, 6, 5, 1, 2, 2, 3, 3, 3, 3, 3, 2, 3, 2, 4, 4, 2, 13, 5, 3, 2, 2, 2, 27, 7, 2, 1, 1, 1, 1, 1, 1, 3, 4, 1, 3, 1, 2, 22, 4, 1, 3, 2, 2, 2, 2, 1, 1, 1, 1, 4, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 6, 55, 3, 5, 7, 3, 13, 1, 1, 1, 6, 3, 2, 2, 21, 3, 1, 1, 1, 1, 4, 4, 3, 5, 4, 4, 85, 3, 2, 6, 4, 2, 18, 7, 5, 4, 4, 1, 3, 49, 3, 2, 4, 4, 4, 3, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 1, 10, 6, 3, 2, 2, 3, 1, 2, 1, 4, 3, 3, 4, 3, 2, 2, 2, 43, 6, 2, 1, 1, 1, 2, 2, 2, 4, 2, 5, 2, 1, 1, 9, 7, 4, 2, 4, 1, 2, 2, 2, 3, 7, 3, 4, 4, 3, 36, 4, 14, 4, 4, 2, 2, 2, 1, 4, 2, 3, 2, 2, 1, 13, 5, 4, 2, 2, 42, 18, 6, 4, 2, 2, 1, 1, 1, 3, 6, 1, 4, 2, 4, 23, 1, 12, 7, 2, 3, 2, 2, 160,9, 4, 3, 1, 1, 1, 1, 4, 2, 8, 1, 4, 2, 24, 6, 5, 2, 2, 4, 3, 4, 2, 2, 2, 5, 1, 2, 1, 1, 1, 1, 8, 2, 3, 2, 2, 6, 57, 11, 3, 2, 15, 4, 2, 2, 3, 2, 3, 2, 2, 2, 1, 8, 2, 3, 8, 4, 2, 2, 2, 1, 3, 1, 6, 1, 2, 1, 7, 3, 3, 2, 1, 6, 2, 2, 16, 2, 3, 2, 2, 4, 1, 2, 2, 1, 3, 2, 19, 15, 4, 4, 1, 3, 2, 2, 1, 1, 99, 9, 2, 4, 3, 2, 2, 1, 13, 5, 1, 7, 1, 2, 2, 14, 1, 4, 3, 1, 1, 5, 3, 23, 6, 4, 6, 3, 3, 3, 3, 3, 3, 10, 4, 3, 3, 2, 1, 2, 3, 1, 2, 1, 9, 8, 8, 6, 4, 20, 3, 3, 2, 2, 5, 2, 2, 50, 15, 2, 1, 8, 4, 18, 4, 8, 2, 6, 4, 4, 3, 2, 17, 5, 4, 3, 3, 2, 2, 4, 8, 1, 1, 14, 3, 1, 3, 6, 2, 13, 6, 4, 5, 2, 11, 1, 1, 3, 2, 21, 2, 7, 1, 2, 2, 1, 7, 4, 2, 3, 2, 29, 6, 2, 9, 3, 4, 2, 2, 2, 2, 2, 12, 1, 7, 3, 71, 10, 3, 2, 23, 5, 3, 3, 7, 5, 23, 4, 3, 7, 3, 3, 1, 5, 5, 2, 7, 5, 1, 1, 9, 2, 3, 2, 67, 11, 4, 4, 4, 9, 2, 2, 4, 4, 3, 2, 3, 3, 2, 12, 9, 4, 2, 1, 17, 9, 6, 3, 3, 2, 3, 3, 109,2, 1, 5, 4, 3, 17, 5, 5, 5, 3, 4, 11, 2, 3, 14, 5, 3, 4, 12, 8, 1, 1, 7, 3, 3, 2, 2, 2, 4, 4, 3, 15, 5, 4, 3, 3, 2, 18, 5, 4, 1, 7, 3, 1, 82, 2, 3, 3, 2, 2, 1, 5, 12, 5, 5, 3, 1, 5, 3, 4, 2, 3, 39, 2, 1, 15, 10, 3, 8, 4, 3, 9, 8, 1, 1, 1, 3, 48, 3, 5, 3, 2, 2, 2, 2, 4, 4, 8, 5, 2, 1, 2, 1, 14, 3, 5, 5, 5, 3, 1, 3, 3, 1, 3, 2, 2, 5, 4, 1, 1, 29, 4, 3, 9, 3, 3, 3, 4, 2, 3, 1, 14, 4, 3, 4, 2, 1, 2, 10, 8, 7, 6, 4, 1, 1, 1, 1, 155,2, 12, 10, 3, 1, 7, 3, 3, 3, 7, 1, 9, 2, 8, 2, 1, 1, 1, 2, 1, 1, 26, 3, 9, 3, 2, 3, 2, 3, 9, 6, 21, 3, 3, 3, 3, 3, 3, 3, 37, 2, 1, 6, 4, 2, 2, 1, 9, 3, 3, 2, 2, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 8, 4, 3, 3, 6, 4, 3, 1, 1, 1, 2, 2, 5, 4, 58, 3, 2, 4, 3, 2, 8, 2, 4, 2, 35, 9, 4, 3, 3, 3, 3, 2, 2, 2, 4, 3, 2, 2, 3, 1, 44, 2, 6, 2, 2, 3, 2, 1, 1, 5, 4, 4, 1, 1, 4, 3, 6, 1, 1, 5, 5, 1, 1, 1, 1, 2, 66, 2, 8, 7, 3, 5, 2, 5, 1, 1, 3, 4, 9, 1, 3, 4, 1, 7, 3, 3, 3, 8, 1, 1, 3, 3, 2, 2, 2, 2, 2, 30, 5, 2, 6, 9, 9, 6, 1, 1, 1, 43, 11, 8, 5, 4, 3, 4, 2, 5, 4, 2, 2, 12, 4, 4, 2, 30, 1, 2, 2, 2, 2, 3, 2, 1, 1, 4, 5, 1, 2, 4, 5, 2, 2, 2, 2, 3, 169,10, 2, 3, 3, 2, 3, 1, 1, 1, 1, 5, 2, 1, 6, 2, 2, 2, 2, 2, 23, 10, 7, 4, 1, 2, 2, 2, 2, 2, 5, 65, 16, 1, 4, 1, 1, 2, 11, 2, 1, 1, 3, 2, 2, 2, 12, 1, 1, 5, 11, 2, 2, 4, 1, 1, 1, 1, 4, 3, 11, 6, 6, 3, 5, 14, 2, 2, 2, 5, 3, 4, 2, 5, 1, 3, 7, 2, 165,7, 1, 3, 2, 2, 3, 9, 6, 1, 1, 1, 1, 18, 3, 3, 6, 2, 2, 30, 5, 2, 2, 3, 2, 9, 2, 7, 2, 2, 2, 3, 4, 1, 4, 2, 7, 1, 1, 2, 2, 9, 2, 1, 1, 5, 7, 4, 4, 3, 3, 2, 23, 9, 5, 4, 9, 1, 1, 4, 4, 5, 2, 12, 2, 1, 1, 16, 3, 2, 1, 1, 4, 3, 3, 2, 1, 1, 1, 2, 2, 2, 4, 2, 1, 1, 4, 4, 4, 2, 206,7, 2, 2, 2, 2, 2, 2, 1, 1, 3, 16, 2, 2, 2, 8, 2, 35, 2, 2, 2, 1, 11, 1, 3, 1, 3, 3, 3, 4, 3, 2, 1, 1, 3, 2, 14, 4, 1, 1, 1, 2, 3, 7, 2, 2, 4, 4, 9, 4, 1, 1, 1, 3, 48, 20, 2, 1, 2, 10, 10, 5, 8, 3, 4, 3, 3, 7, 4, 3, 1, 1, 1, 17, 4, 2, 8, 4, 1, 3, 4, 2, 2, 29, 3, 1, 1, 3, 2, 6, 1, 1, 2, 2, 5, 6, 3, 3, 2, 2, 2, 2, 2, 1, 2, 37, 4, 4, 2, 2, 3, 1, 1, 1, 1, 11, 5, 8, 3, 2, 74, 7, 3, 3, 1, 2, 1, 2, 17, 5, 3, 5, 1, 1, 1, 8, 7, 7, 1, 1, 4, 29, 10, 3, 1, 4, 1, 1, 8, 5, 5, 3, 7, 2, 2, 26, 4, 1, 13, 3, 9, 4, 8, 4, 1, 2, 5, 3, 1, 1, 9, 4, 4, 2, 3, 2, 2, 10, 9, 1, 1, 4, 20, 2, 2, 5, 2, 2, 2, 1, 7, 6, 3, 3, 2, 3, 14, 5, 2, 5, 2, 1, 1, 1, 1, 23, 4, 1, 1, 1, 1, 1, 14, 6, 4, 2, 1, 3, 1, 1, 2, 15, 7, 4, 1, 2, 2, 33, 2, 2, 2, 10, 2, 2, 2, 3, 1, 1, 11, 2, 91, 2, 5, 2, 19, 2, 1, 3, 2, 3, 6, 3, 2, 2, 9, 3, 3, 3, 2, 3, 2, 14, 4, 3, 2, 3, 2, 2, 4, 2, 2, 2, 1, 3, 5, 2, 2, 5, 3, 3, 1, 1, 1, 2, 2, 3, 1, 4, 3, 2, 8, 5, 3, 3, 77, 3, 1, 1, 7, 3, 2, 2, 2, 2, 1, 1, 1, 49, 4, 2, 1, 1, 1, 3, 4, 23, 3, 7, 7, 4, 3, 6, 4, 3, 1, 6, 2, 2, 4, 2, 3, 4, 2, 41, 5, 3, 2, 2, 2, 12, 1, 2, 2, 5, 1, 4, 2, 1, 1, 1, 1, 3, 1, 1, 23, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 3, 3, 3, 3, 2, 1, 1, 4, 4, 4, 3, 1, 1, 1, 4, 3, 2, 2, 1, 1, 4, 2, 2, 2, 2, 2, 2, 2, 1, 60, 8, 7, 2, 2, 1, 22, 1, 1, 4, 6, 2, 1, 2, 7, 2, 1, 3, 12, 11, 5, 5, 5, 37, 1, 1, 1, 10, 2, 1, 3, 2, 2, 2, 4, 3, 1, 1, 2, 2, 2, 8, 4, 3, 3, 2, 37, 8, 8, 5, 2, 17, 9, 2, 1, 5, 1, 1, 3, 4, 2, 2, 7, 2, 2, 1, 2, 12, 3, 2, 1, 8, 8, 4, 4, 2, 216,7, 4, 3, 2, 1, 1, 66, 66, 2, 7, 1, 2, 2, 2, 8, 2, 2, 2, 4, 3, 3, 3, 2, 2, 4, 2, 9, 5, 4, 4, 3, 3, 2, 9, 3, 2, 3, 3, 4, 2, 2, 7, 4, 3, 10, 4, 3, 1, 1, 1, 1, 1, 1, 3, 17, 14, 2, 2, 2, 7, 5, 41, 15, 5, 7, 4, 5, 14, 8, 4, 3, 2, 1, 1, 2, 24, 2, 3, 4, 2, 2, 2, 2, 2, 2, 1, 1, 8, 2, 2, 2, 3, 5, 3, 1, 5, 11, 8, 3, 2, 47, 9, 4, 4, 2, 2, 3, 2, 1, 8, 5, 2, 1, 7, 5, 6, 5, 3, 3, 3, 1, 2, 3, 45, 2, 2, 1, 1, 1, 2, 3, 21, 10, 2, 2, 2, 7, 1, 1, 7, 4, 5, 2, 2, 115,15, 12, 2, 2, 2, 2, 3, 1, 6, 10, 10, 4, 3, 38, 25, 2, 11, 2, 3, 2, 2, 2, 3, 3, 1, 1, 1, 2, 2, 2, 11, 4, 4, 4, 2, 2, 7, 6, 4, 4, 2, 5, 18, 2, 2, 1, 2, 85, 3, 1, 1, 43, 5, 2, 9, 5, 3, 3, 1, 2, 1, 4, 2, 2, 2, 7, 3, 4, 8, 4, 2, 1, 9, 4, 4, 4, 8, 2, 4, 4, 6, 1, 3, 3, 2, 2, 1, 1, 34, 1, 13, 10, 2, 2, 2, 12, 2, 1, 37, 4, 15, 6, 4, 2, 1, 3, 2, 2, 2, 14, 2, 1, 1, 4, 1, 1, 5, 1, 2, 2, 2, 35, 3, 9, 4, 1, 2, 1, 1, 8, 2, 2, 4, 2, 1, 41, 3, 3, 3, 2, 9, 4, 4, 1, 3, 1, 5, 7, 1, 1, 1, 3, 2, 34, 9, 2, 5, 5, 8, 2, 1, 1, 1, 3, 34, 8, 5, 1, 7, 2, 2, 8, 6, 2, 3, 1, 1, 3, 27, 9, 5, 3, 1, 4, 8, 4, 1, 1, 1, 1, 1, 1, 2, 1, 74, 4, 4, 11, 2, 11, 3, 2, 1, 10, 2, 2, 17, 3, 3, 5, 3, 5, 2, 2, 2, 2, 2, 9, 4, 22, 17, 6, 4, 4, 2, 4, 3, 2, 1, 1, 1, 1, 1, 1, 114,15, 1, 1, 2, 4, 1, 11, 1, 18, 1, 3, 1, 2, 1, 3, 2, 3, 3, 1, 8, 2, 2, 1, 1, 2, 1, 13, 2, 2, 2, 5, 2, 2, 3, 15, 10, 3, 3, 1, 7, 2, 1, 1, 2, 4, 3, 2, 2, 1, 1, 4, 1, 1, 1, 2, 97, 9, 2, 2, 9, 6, 2, 23, 3, 4, 2, 2, 2, 2, 2, 5, 2, 2, 2, 2, 1, 1, 9, 4, 4, 1, 2, 4, 1, 4, 9, 18, 5, 1, 1, 2, 3, 3, 3, 118,6, 1, 4, 4, 2, 2, 2, 17, 11, 2, 4, 2, 15, 1, 2, 4, 3, 2, 14, 5, 2, 3, 1, 5, 2, 3, 4, 1, 1, 2, 2, 8, 4, 1, 2, 7, 2, 2, 2, 13, 6, 2, 2, 5, 6, 2, 6, 1, 61, 3, 7, 1, 3, 2, 4, 3, 2, 2, 4, 3, 3, 16, 2, 5, 2, 2, 3, 11, 3, 4, 2, 50, 2, 4, 1, 1, 3, 8, 6, 5, 1, 26, 1, 7, 2, 2, 2, 1, 3, 5, 4, 9, 6, 2, 2, 1, 102,7, 6, 2, 3, 2, 2, 1, 7, 6, 3, 2, 1, 16, 2, 3, 6, 2, 16, 8, 6, 2, 1, 2, 21, 1, 2, 5, 2, 1, 2, 2, 1, 1, 1, 5, 16, 2, 2, 1, 1, 4, 2, 3, 2, 1, 1, 7, 3, 2, 3, 52, 5, 2, 14, 3, 6, 3, 3, 13, 4, 3, 3, 3, 3, 3, 2, 1, 1, 10, 5, 54, 3, 3, 2, 2, 6, 2, 2, 7, 3, 3, 3, 3, 3, 2, 4, 5, 3, 8, 3, 3, 2, 5, 14, 3, 4, 2, 1, 1, 1, 1, 1, 3, 2, 6, 32, 2, 2, 2, 9, 16, 10, 4, 1, 4, 31, 3, 2, 2, 4, 2, 4, 3, 3, 2, 1, 2, 4, 1, 1, 1, 1, 2, 2, 13, 6, 5, 26, 3, 17, 3, 2, 3, 2, 2, 2, 4, 4, 3, 1, 1, 1, 6, 5, 4, 2, 13, 3, 2, 2, 82, 5, 7, 5, 35, 11, 3, 2, 5, 2, 6, 2, 2, 3, 9, 2, 2, 9, 4, 3, 3, 2, 4, 27, 6, 3, 4, 9, 2, 3, 11, 6, 13, 1, 5, 1, 1, 15, 11, 1, 3, 3, 5, 3, 8, 3, 18, 7, 2, 3, 6, 4, 2, 196,55, 4, 1, 1, 22, 2, 2, 2, 4, 3, 2, 2, 2, 2, 1, 5, 13, 13, 2, 1, 1, 1, 3, 1, 7, 3, 4, 2, 1, 5, 20, 2, 7, 2, 1, 4, 2, 4, 15, 2, 2, 7, 2, 2, 2, 2, 1, 1, 1, 2, 4, 28, 1, 10, 4, 4, 3, 7, 5, 6, 2, 1, 17, 3, 1, 3, 2, 2, 5, 7, 2, 2, 1, 1, 1, 3, 2, 2, 3, 2, 14, 4, 3, 3, 5, 3, 3, 3, 1, 1, 4, 2, 2, 2, 226,11, 8, 6, 4, 2, 13, 11, 5, 28, 10, 1, 2, 4, 3, 3, 16, 13, 2, 2, 2, 1, 4, 20, 8, 2, 2, 6, 25, 5, 2, 3, 2, 2, 10, 8, 3, 3, 2, 8, 6, 3, 10, 4, 32, 2, 4, 4, 6, 9, 2, 2, 1, 2, 3, 29, 5, 2, 2, 2, 2, 7, 4, 2, 6, 4, 37, 2, 2, 2, 2, 2, 3, 22, 8, 6, 3, 3, 3, 2, 3, 2, 1, 1, 1, 231,23, 21, 3, 3, 3, 3, 1, 2, 2, 3, 2, 3, 25, 1, 4, 3, 17, 3, 1, 2, 2, 4, 1, 21, 1, 7, 4, 2, 1, 1, 1, 2, 6, 2, 1, 1, 1, 4, 2, 2, 8, 6, 2, 1, 1, 1, 3, 2, 7, 19, 7, 2, 2, 1, 29, 3, 18, 5, 3, 3, 2, 4, 2, 7, 3, 2, 2, 16, 2, 5, 2, 13, 9, 7, 6, 16, 5, 5, 2, 2, 3, 3, 3, 3, 2, 27, 2, 2, 18, 3, 3, 2, 1, 4, 2, 2, 3, 3, 1, 1, 138,2, 11, 1, 1, 3, 5, 2, 2, 9, 1, 2, 2, 3, 7, 3, 7, 5, 3, 24, 6, 3, 2, 3, 2, 7, 4, 48, 4, 2, 14, 3, 6, 5, 2, 1, 3, 5, 1, 3, 5, 14, 1, 1, 5, 3, 8, 3, 23, 8, 7, 4, 4, 1, 3, 1, 1, 2, 1, 1, 19, 14, 6, 5, 1, 76, 4, 1, 1, 1, 3, 2, 2, 22, 8, 7, 1, 3, 2, 2, 1, 41, 3, 1, 2, 7, 1, 5, 4, 2, 2, 2, 2, 10, 6, 2, 2, 3, 3, 1, 1, 3, 45, 4, 16, 2, 7, 7, 5, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 30, 4, 4, 4, 5, 10, 3, 1, 1, 1, 1, 1, 1, 2, 2, 2, 26, 24, 5, 3, 6, 2, 2, 3, 4, 20, 3, 10, 4, 4, 19, 2, 1, 10, 9, 4, 4, 2, 2, 1, 1, 5, 1, 78, 3, 6, 2, 4, 1, 1, 4, 48, 8, 1, 7, 3, 11, 7, 1, 1, 1, 1, 7, 3, 2, 2, 6, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 25, 5, 1, 2, 2, 2, 4, 10, 1, 2, 56, 4, 3, 12, 2, 12, 4, 2, 2, 1, 3, 7, 8, 3, 2, 1, 1, 6, 2, 2, 5, 4, 2, 226,8, 4, 12, 8, 2, 3, 2, 17, 6, 1, 5, 3, 12, 6, 6, 4, 4, 4, 38, 3, 13, 3, 1, 1, 3, 2, 4, 4, 4, 5, 1, 1, 2, 2, 2, 5, 25, 7, 7, 5, 2, 4, 3, 14, 6, 2, 4, 3, 3, 6, 3, 3, 2, 24, 8, 5, 3, 2, 3, 2, 5, 27, 4, 9, 3, 8, 3, 3, 10, 3, 5, 10, 1, 1, 1, 95, 4, 1, 1, 6, 5, 4, 5, 1, 20, 2, 7, 3, 1, 1, 12, 3, 7, 7, 7, 7, 1, 1, 2, 2, 4, 2, 1, 13, 5, 3, 2, 19, 2, 2, 4, 6, 3, 69, 4, 7, 7, 4, 4, 4, 2, 3, 2, 4, 41, 20, 18, 2, 2, 2, 2, 5, 2, 1, 2, 2, 1, 3, 5, 3, 3, 8, 18, 2, 15, 6, 1, 49, 5, 2, 6, 4, 1, 5, 1, 3, 3, 2, 2, 1, 1, 1, 12, 3, 4, 2, 2, 2, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 52, 11, 2, 5, 2, 5, 2, 2, 2, 1, 1, 1, 7, 2, 2, 8, 7, 4, 4, 4, 5, 3, 9, 3, 4, 3, 20, 2, 5, 3, 2, 2, 1, 1, 6, 3, 1, 1, 1, 1, 34, 4, 3, 5, 2, 7, 4, 5, 2, 2, 3, 3, 3, 2, 1, 1, 1, 3, 12, 1, 2, 1, 8, 5, 2, 2, 9, 5, 1, 9, 3, 2, 2, 2, 19, 3, 2, 4, 3, 3, 2, 4, 3, 2, 1, 4, 4, 172,14, 3, 3, 6, 4, 1, 16, 4, 2, 1, 10, 8, 7, 7, 2, 13, 1, 10, 8, 2, 3, 10, 4, 4, 3, 1, 2, 23, 4, 3, 2, 2, 2, 4, 2, 2, 2, 2, 1, 3, 2, 1, 1, 3, 53, 9, 1, 16, 4, 4, 17, 3, 2, 2, 5, 3, 5, 2, 2, 7, 4, 2, 1, 2, 11, 5, 3, 4, 1, 4, 2, 122,13, 11, 3, 1, 2, 3, 16, 1, 4, 3, 2, 6, 3, 2, 1, 1, 23, 9, 1, 9, 4, 5, 13, 7, 5, 3, 3, 6, 3, 2, 3, 1, 1, 41, 11, 4, 4, 2, 2, 3, 3, 3, 6, 2, 2, 7, 5, 2, 3, 2, 188,21, 8, 4, 3, 3, 17, 12, 9, 5, 2, 2, 43, 4, 2, 5, 4, 3, 2, 15, 7, 5, 10, 9, 2, 2, 7, 11, 5, 3, 1, 1, 1, 10, 4, 1, 26, 5, 2, 7, 2, 6, 3, 3, 3, 2, 2, 6, 2, 3, 7, 3, 30, 3, 1, 1, 6, 4, 8, 5, 5, 4, 5, 3, 3, 3, 1, 122, 9, 3, 2, 2, 2, 21, 2, 7, 5, 3, 3, 2, 2, 15, 1, 1, 1, 11, 9, 7, 5, 4, 7, 4, 8, 1, 1, 1, 2, 2, 17, 6, 4, 3, 2, 2, 3, 3, 2, 2, 12, 2, 9, 5, 2, 2, 7, 2, 18, 4, 4, 7, 2, 5, 6, 1, 1, 27, 3, 2, 2, 1, 1, 4, 5, 3, 5, 2, 1, 3, 20, 4, 9, 1, 1, 1, 7, 2, 2, 2, 1, 1, 1, 7, 2, 2, 2, 3, 2, 2, 43, 3, 1, 8, 3, 3, 16, 5, 3, 5, 1, 1, 1, 1, 9, 24, 1, 4, 3, 5, 3, 3, 1, 4, 2, 2, 25, 2, 2, 1, 3, 1, 1, 2, 2, 2, 1, 5, 3, 3, 2, 2, 2, 1, 1, 55, 4, 10, 2, 11, 3, 2, 11, 3, 7, 2, 5, 28, 4, 12, 7, 3, 6, 4, 2, 5, 3, 26, 5, 1, 10, 6, 3, 2, 2, 1, 35, 23, 3, 3, 6, 6, 3, 4, 4, 2, 1, 1, 21, 14, 7, 7, 4, 2, 3, 2, 1, 1, 1, 69, 11, 3, 2, 1, 14, 4, 25, 2, 6, 2, 2, 2, 1, 1, 2, 2, 11, 3, 3, 2, 2, 3, 10, 3, 4, 1, 1, 3, 3, 43, 2, 10, 3, 3, 3, 6, 4, 4, 7, 1, 1, 1, 3, 3, 4, 3, 3, 2, 2, 6, 2, 14, 2, 9, 3, 3, 4, 209,22, 1, 1, 1, 5, 1, 1, 1, 4, 2, 2, 4, 1, 1, 1, 13, 3, 2, 2, 20, 2, 4, 5, 5, 2, 14, 2, 3, 3, 5, 3, 3, 2, 8, 4, 4, 50, 17, 5, 3, 5, 3, 3, 4, 2, 1, 14, 6, 2, 3, 3, 11, 2, 2, 2, 1, 4, 3, 1, 1, 61, 10, 2, 2, 6, 3, 18, 11, 10, 3, 3, 3, 2, 2, 2, 3, 2, 10, 2, 2, 2, 6, 5, 2, 2, 5, 5, 7, 2, 1, 4, 8, 2, 2, 2, 1, 1, 5, 4, 4, 117,29, 3, 1, 1, 1, 1, 1, 13, 1, 2, 6, 6, 6, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 1, 11, 10, 4, 17, 4, 4, 4, 3, 11, 3, 3, 2, 2, 2, 2, 1, 1, 1, 3, 2, 25, 3, 3, 10, 7, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 4, 3, 2, 3, 1, 1, 8, 1, 1, 2, 36, 2, 5, 2, 2, 4, 4, 8, 3, 1, 1, 7, 4, 7, 1, 1, 1, 4, 4, 2, 45, 20, 1, 13, 1, 5, 5, 5, 3, 1, 3, 2, 14, 2, 2, 4, 9, 4, 3, 1, 5, 3, 3, 3, 14, 4, 2, 2, 5, 2, 1, 3, 3, 2, 2, 2, 10, 7, 7, 6, 4, 1, 216,6, 5, 2, 1, 13, 2, 1, 4, 6, 6, 10, 4, 1, 3, 2, 1, 1, 1, 1, 1, 3, 3, 3, 3, 2, 55, 2, 22, 10, 9, 7, 7, 2, 2, 7, 7, 5, 4, 11, 2, 2, 4, 16, 1, 2, 2, 2, 1, 1, 2, 2, 2, 5, 5, 3, 29, 1, 1, 1, 1, 6, 3, 3, 3, 19, 8, 2, 6, 2, 2, 2, 2, 3, 3, 2, 5, 2, 64, 2, 2, 2, 1, 2, 12, 4, 1, 1, 3, 1, 4, 4, 4, 2, 2, 4, 10, 3, 2, 2, 2, 13, 5, 5, 5, 7, 7, 6, 2, 3, 3, 3, 5, 1, 3, 8, 2, 2, 3, 2, 12, 4, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 2, 2, 6, 3, 11, 2, 1, 3, 1, 3, 9, 5, 4, 2, 1, 18, 5, 3, 7, 2, 2, 3, 22, 2, 3, 2, 2, 2, 1, 4, 8, 7, 7, 6, 2, 2, 36, 12, 7, 7, 4, 3, 1, 5, 3, 2, 1, 3, 16, 3, 5, 4, 4, 3, 2, 2, 51, 7, 6, 2, 8, 2, 2, 3, 4, 8, 4, 8, 6, 1, 11, 2, 5, 4, 31, 2, 1, 1, 1, 9, 4, 5, 5, 2, 7, 4, 99, 11, 8, 8, 8, 8, 5, 3, 3, 3, 2, 2, 1, 2, 1, 1, 2, 9, 1, 1, 13, 6, 5, 2, 29, 3, 11, 1, 5, 7, 6, 4, 4, 8, 2, 3, 2, 1, 1, 3, 10, 2, 24, 6, 4, 3, 2, 8, 2, 3, 3, 19, 1, 1, 2, 10, 7, 5, 12, 1, 2, 2, 2, 1, 3, 3, 3, 2, 11, 3, 3, 2, 4, 3, 217,19, 4, 12, 21, 1, 1, 19, 5, 2, 2, 1, 37, 4, 12, 3, 4, 4, 4, 11, 8, 2, 2, 2, 3, 2, 1, 7, 4, 3, 10, 6, 3, 3, 3, 3, 3, 13, 3, 3, 2, 2, 2, 36, 1, 3, 8, 3, 2, 7, 3, 1, 1, 7, 4, 3, 4, 3, 30, 1, 1, 1, 4, 3, 6, 11, 5, 4, 3, 3, 2, 16, 4, 2, 4, 5, 3, 2, 11, 2, 2, 2, 2, 1, 172,9, 3, 4, 1, 2, 27, 5, 3, 2, 17, 2, 9, 8, 2, 2, 1, 8, 4, 1, 3, 1, 1, 1, 1, 38, 4, 3, 2, 15, 1, 2, 2, 2, 5, 5, 3, 10, 2, 4, 10, 5, 3, 1, 1, 20, 4, 1, 2, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 13, 2, 7, 3, 2, 2, 8, 3, 1, 1, 1, 1, 10, 1, 1, 2, 9, 1, 5, 2, 5, 3, 2, 2, 2, 2, 9, 2, 1, 1, 7, 4, 2, 2, 2, 2, 4, 3, 3, 3, 1, 1, 1, 80, 7, 5, 4, 4, 4, 7, 3, 1, 1, 1, 6, 1, 1, 1, 5, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 3, 3, 9, 2, 2, 6, 3, 13, 7, 4, 2, 1, 2, 6, 3, 5, 1, 1, 1, 1, 4, 2, 99, 2, 1, 77, 76, 4, 64, 3, 12, 8, 21, 11, 10, 2, 6, 2, 1, 8, 8, 5, 13, 4, 3, 2, 7, 5, 5, 4, 1, 1, 16, 5, 2, 2, 2, 2, 10, 7, 4, 3, 3, 1, 1, 1, 1, 2, 2, 1, 60, 4, 3, 3, 11, 11, 4, 3, 8, 8, 3, 2, 10, 7, 7, 2, 4, 1, 1, 1, 1, 1, 186,12, 1, 1, 2, 4, 3, 3, 2, 1, 3, 1, 2, 1, 15, 2, 2, 6, 2, 2, 2, 2, 1, 2, 2, 4, 3, 11, 2, 1, 1, 1, 6, 4, 3, 1, 1, 4, 4, 4, 2, 5, 1, 1, 3, 6, 2, 26, 4, 1, 1, 2, 10, 2, 2, 3, 3, 3, 2, 3, 4, 6, 2, 2, 1, 1, 2, 2, 42, 3, 25, 19, 5, 5, 6, 6, 5, 7, 2, 2, 1, 1, 3, 3, 3, 3, 2, 6, 1, 3, 1, 1, 1, 1, 8, 2, 2, 2, 1, 1, 22, 4, 7, 4, 4, 4, 2, 5, 2, 1, 1, 3, 4, 15, 14, 3, 11, 8, 2, 3, 171,2, 3, 2, 1, 2, 5, 2, 11, 1, 6, 1, 1, 12, 3, 1, 5, 2, 9, 3, 1, 1, 7, 36, 6, 2, 19, 2, 4, 2, 3, 5, 3, 3, 1, 1, 1, 1, 1, 6, 27, 3, 19, 9, 4, 3, 2, 2, 2, 1, 1, 1, 1, 22, 9, 6, 12, 4, 2, 2, 2, 2, 2, 2, 3, 2, 5, 21, 8, 4, 2, 2, 2, 1, 1, 3, 2, 2, 3, 3, 103,34, 16, 5, 1, 1, 10, 6, 5, 3, 3, 2, 2, 1, 3, 21, 12, 12, 3, 6, 3, 3, 3, 3, 9, 5, 1, 4, 4, 2, 5, 2, 2, 1, 1, 1, 7, 2, 2, 8, 7, 2, 81, 8, 6, 5, 4, 4, 9, 7, 2, 10, 2, 31, 4, 4, 3, 10, 3, 1, 2, 2, 2, 1, 5, 5, 4, 1, 1, 1, 2, 2, 4, 10, 7, 2, 2, 2, 1, 105,2, 2, 2, 2, 7, 7, 2, 3, 1, 5, 1, 1, 9, 2, 2, 3, 3, 1, 19, 13, 13, 8, 4, 15, 8, 2, 2, 3, 5, 4, 2, 1, 12, 6, 1, 1, 44, 7, 4, 6, 1, 6, 3, 2, 6, 4, 9, 4, 8, 3, 3, 1, 1, 1, 2, 1, 1, 35, 7, 7, 4, 2, 7, 7, 1, 8, 1, 1, 3, 31, 3, 1, 1, 1, 9, 4, 4, 3, 3, 3, 3, 3, 3, 2, 53, 4, 1, 1, 3, 5, 4, 3, 34, 2, 1, 7, 3, 2, 2, 2, 8, 3, 2, 2, 5, 4, 4, 4, 2, 2, 6, 23, 6, 9, 1, 1, 1, 4, 3, 12, 2, 3, 20, 3, 16, 10, 4, 4, 2, 20, 2, 2, 4, 1, 1, 2, 2, 3, 2, 1, 17, 2, 1, 8, 3, 2, 3, 16, 1, 3, 9, 5, 5, 5, 5, 4, 5, 4, 232,16, 5, 3, 1, 1, 2, 1, 2, 1, 1, 1, 1, 8, 3, 2, 4, 1, 1, 2, 1, 12, 2, 1, 20, 1, 3, 2, 3, 3, 2, 4, 2, 9, 5, 1, 81, 19, 5, 5, 5, 5, 4, 5, 31, 3, 3, 11, 11, 5, 2, 5, 4, 22, 10, 6, 3, 2, 3, 4, 2, 3, 2, 3, 2, 2, 1, 39, 6, 4, 15, 4, 6, 2, 2, 1, 1, 1, 7, 5, 2, 3, 4, 4, 1, 1, 1, 1, 11, 3, 1, 2, 2, 11, 9, 5, 3, 4, 4, 2, 7, 3, 2, 1, 20, 2, 2, 4, 7, 7, 4, 4, 4, 2, 21, 2, 5, 4, 3, 3, 1, 1, 8, 7, 7, 7, 10, 4, 2, 97, 15, 14, 14, 6, 23, 2, 2, 2, 2, 15, 3, 4, 3, 2, 1, 1, 7, 3, 5, 5, 4, 4, 4, 26, 10, 8, 7, 7, 7, 11, 6, 4, 13, 5, 7, 1, 1, 1, 19, 16, 14, 6, 6, 6, 3, 3, 131,27, 2, 2, 6, 4, 3, 3, 11, 3, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 19, 4, 4, 10, 3, 7, 6, 4, 5, 1, 4, 35, 9, 2, 2, 2, 7, 7, 11, 6, 4, 4, 3, 14, 7, 5, 5, 5, 5, 16, 10, 3, 2, 3, 3, 17, 11, 4, 4, 4, 2, 5, 5, 2, 4, 4, 9, 4, 4, 5, 5, 4, 2, 2, 2, 7, 2, 62, 1, 34, 5, 5, 6, 5, 5, 5, 5, 4, 3, 8, 4, 2, 3, 2, 1, 9, 8, 4, 4, 7, 4, 4, 1, 1, 5, 2, 1, 51, 11, 4, 4, 2, 2, 2, 2, 2, 14, 10, 8, 8, 8, 12, 2, 3, 1, 4, 6, 8, 6, 5, 9, 3, 2, 69, 18, 4, 4, 4, 4, 5, 8, 8, 4, 27, 12, 9, 6, 4, 3, 1, 5, 5, 3, 3, 3, 5, 4, 5, 3, 7, 7, 7, 7, 3, 3, 3, 6, 1, 5, 24, 5, 3, 3, 2, 2, 1, 1, 13, 5, 5, 4, 4, 4, 4, 2, 7, 6, 6, 1, 3, 1, 3, 2, 2, 9, 1, 7, 2, 2, 1, 9, 3, 4, 3, 3, 6, 2, 37, 3, 1, 19, 8, 8, 5, 4, 3, 4, 7, 7, 1, 1, 2, 2, 7, 7, 7, 6, 110,2, 5, 5, 2, 2, 3, 15, 2, 1, 5, 2, 2, 3, 3, 3, 3, 5, 4, 3, 3, 18, 10, 9, 9, 5, 3, 2, 2, 2, 6, 6, 4, 3, 11, 8, 4, 2, 3, 3, 2, 8, 8, 1, 7, 4, 27, 9, 8, 6, 3, 3, 2, 2, 2, 2, 8, 2, 6, 6, 5, 3, 3, 3, 8, 1, 6, 5, 5, 22, 3, 10, 8, 4, 9, 114,17, 5, 7, 7, 4, 4, 26, 6, 6, 1, 2, 9, 6, 7, 6, 19, 18, 3, 3, 3, 3, 3, 11, 3, 2, 5, 1, 1, 1, 1, 44, 22, 10, 1, 1, 1, 1, 9, 6, 6, 6, 6, 4, 11, 8, 8, 8, 7, 5, 3, 2, 2, 2, 2, 1, 1, 1, 46, 31, 10, 3, 4, 18, 3, 8, 5, 5, 5, 5, 5, 2, 2, 6, 3, 3, 11, 5, 5, 5, 5, 2, 1, 2, 2, 2, 4, 1, 1, 84, 1, 16, 6, 3, 22, 2, 6, 4, 5, 3, 9, 14, 13, 12, 5, 5, 9, 7, 6, 6, 6, 6, 1, 4, 56, 4, 11, 2, 5, 3, 3, 1, 1, 1, 6, 1, 1, 6, 18, 9, 6, 3, 2, 2, 5, 8, 3, 13, 5, 4, 2, 1, 33, 3, 8, 3, 5, 2, 2, 6, 2, 1, 10, 13, 2, 6, 3, 1, 12, 3, 1, 2, 1, 8, 6, 4, 3, 38, 2, 17, 5, 4, 5, 2, 3, 2, 2, 2, 4, 2, 1, 1, 3, 2, 2, 2, 13, 2, 2, 10, 4, 5, 15, 6, 6, 5, 4, 51, 7, 3, 23, 8, 8, 5, 2, 7, 7, 7, 7, 7, 4, 12, 3, 4, 3, 2, 2, 2, 2, 4, 11, 4, 3, 15, 5, 2, 3, 3, 3, 5, 2, 2, 3, 2, 2, 59, 3, 2, 4, 4, 2, 2, 9, 3, 2, 2, 4, 1, 1, 1, 6, 1, 3, 27, 8, 1, 1, 4, 3, 4, 6, 3, 3, 36, 5, 3, 1, 2, 2, 1, 20, 4, 4, 3, 2, 5, 158,4, 3, 1, 1, 8, 6, 2, 1, 1, 15, 7, 2, 3, 2, 2, 1, 3, 52, 8, 7, 7, 10, 10, 10, 2, 32, 16, 13, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 6, 5, 12, 8, 5, 38, 2, 2, 2, 2, 1, 1, 4, 1, 6, 21, 5, 1, 1, 2, 3, 3, 2, 12, 2, 3, 5, 5, 9, 2, 3, 3, 1, 2, 2, 2, 31, 3, 24, 4, 3, 12, 2, 4, 39, 7, 5, 4, 5, 5, 5, 3, 2, 7, 3, 2, 2, 3, 3, 2, 2, 2, 4, 10, 1, 1, 1, 12, 2, 2, 4, 1, 1, 1, 159,10, 1, 1, 1, 16, 1, 13, 4, 3, 2, 2, 4, 2, 10, 9, 4, 3, 5, 10, 6, 9, 5, 29, 2, 2, 3, 5, 3, 2, 6, 2, 8, 4, 11, 8, 7, 8, 6, 12, 8, 3, 7, 10, 4, 3, 4, 1, 133,38, 2, 8, 8, 13, 6, 3, 3, 1, 1, 14, 4, 3, 1, 1, 7, 5, 24, 4, 3, 2, 11, 3, 4, 12, 5, 4, 1, 4, 2, 5, 3, 18, 5, 4, 4, 3, 5, 3, 2, 4, 1, 1, 4, 1, 2, 127,6, 4, 7, 2, 5, 3, 2, 3, 16, 4, 8, 7, 4, 4, 1, 1, 5, 2, 2, 10, 6, 45, 2, 2, 4, 2, 2, 2, 1, 1, 1, 8, 3, 4, 18, 3, 7, 5, 3, 26, 3, 3, 4, 2, 10, 9, 5, 2, 6, 4, 2, 153,4, 4, 10, 4, 3, 2, 6, 3, 2, 9, 2, 1, 2, 12, 2, 3, 9, 6, 2, 6, 3, 38, 2, 3, 27, 1, 5, 1, 1, 2, 1, 1, 4, 7, 3, 11, 4, 3, 1, 1, 27, 7, 3, 3, 6, 88, 8, 6, 4, 3, 2, 6, 5, 5, 9, 1, 1, 1, 1, 7, 2, 25, 10, 7, 5, 4, 5, 6, 5, 5, 14, 3, 5, 7, 5, 5, 1, 4, 2, 24, 1, 2, 2, 3, 2, 2, 2, 5, 5, 2, 7, 5, 4, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 3, 3, 18, 4, 4, 2, 4, 4, 2, 15, 2, 1, 1, 4, 1, 1, 2, 2, 3, 2, 13, 7, 5, 11, 2, 2, 11, 11, 3, 3, 2, 2, 3, 2, 2, 2, 51, 6, 4, 4, 3, 2, 2, 1, 16, 5, 6, 3, 2, 2, 2, 2, 2, 2, 3, 2, 6, 3, 1, 11, 3, 5, 5, 2, 2, 26, 8, 5, 4, 2, 4, 3, 8, 2, 2, 5, 4, 3, 2, 2, 1, 1, 21, 4, 2, 2, 1, 1, 1, 7, 6, 2, 3, 3, 2, 16, 2, 2, 3, 6, 2, 1, 94, 5, 1, 3, 2, 5, 3, 6, 4, 3, 10, 5, 4, 4, 11, 6, 1, 1, 4, 17, 8, 7, 6, 2, 2, 2, 6, 13, 3, 4, 3, 6, 4, 36, 3, 2, 8, 1, 1, 4, 2, 3, 5, 2, 15, 12, 4, 3, 8, 2, 44, 2, 5, 2, 2, 2, 2, 5, 2, 5, 2, 2, 3, 4, 5, 2, 2, 9, 1, 3, 3, 2, 1, 1, 2, 52, 1, 8, 5, 5, 1, 2, 6, 6, 4, 4, 2, 2, 1, 1, 1, 1, 2, 2, 2, 4, 2, 2, 8, 4, 4, 2, 2, 7, 3, 3, 3, 7, 2, 2, 2, 2, 1, 1, 1, 19, 10, 3, 2, 4, 3, 3, 1, 7, 5, 5, 3, 3, 3, 3, 3, 3, 3, 1, 1, 2, 2, 21, 12, 2, 1, 1, 2, 2, 2, 2, 3, 1, 1, 2, 2, 1, 1, 1, 1, 3, 2, 2, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 11, 1, 4, 2, 15, 2, 1, 3, 2, 23, 2, 1, 1, 3, 11, 1, 6, 1, 3, 1, 1, 6, 4, 1, 20, 4, 3, 5, 3, 3, 4, 1, 21, 14, 3, 3, 5, 29, 4, 2, 5, 6, 2, 2, 2, 9, 4, 5, 2, 51, 2, 4, 2, 12, 3, 4, 2, 1, 1, 1, 1, 3, 13, 4, 1, 2, 2, 3, 1, 2, 1, 2, 2, 56, 11, 2, 2, 1, 1, 4, 4, 3, 3, 6, 7, 3, 5, 2, 8, 2, 2, 2, 5, 7, 5, 134,2, 5, 2, 14, 9, 11, 2, 4, 1, 6, 13, 7, 6, 27, 3, 15, 15, 1, 1, 1, 4, 2, 3, 7, 3, 14, 4, 1, 3, 2, 1, 1, 4, 3, 7, 2, 2, 2, 9, 2, 2, 3, 3, 5, 2, 2, 10, 4, 1, 1, 1, 1, 3, 3, 3, 2, 2, 119,2, 1, 3, 2, 1, 8, 1, 1, 4, 9, 3, 2, 2, 2, 1, 1, 2, 11, 16, 1, 4, 3, 2, 2, 3, 13, 3, 4, 6, 6, 4, 8, 3, 3, 18, 3, 3, 6, 3, 4, 9, 2, 2, 2, 4, 4, 3, 2, 3, 16, 1, 1, 9, 2, 2, 2, 1, 1, 1, 21, 3, 7, 3, 34, 5, 4, 1, 1, 3, 2, 1, 1, 12, 12, 1, 3, 1, 8, 4, 4, 3, 3, 3, 3, 3, 3, 2, 38, 2, 8, 1, 24, 9, 4, 2, 21, 3, 7, 4, 21, 1, 1, 3, 3, 3, 3, 4, 2, 2, 3, 1, 8, 3, 3, 7, 7, 7, 6, 4, 1, 104,4, 2, 2, 20, 3, 2, 2, 2, 7, 5, 1, 1, 2, 1, 1, 17, 6, 4, 1, 1, 16, 4, 3, 3, 3, 3, 20, 3, 7, 4, 12, 9, 1, 3, 75, 13, 2, 5, 5, 1, 1, 6, 4, 9, 1, 1, 3, 1, 1, 1, 4, 1, 1, 5, 2, 1, 1, 1, 3, 2, 16, 3, 2, 4, 12, 5, 3, 2, 3, 6, 3, 120,21, 4, 4, 2, 3, 3, 3, 2, 2, 2, 18, 2, 2, 5, 2, 1, 3, 4, 3, 2, 18, 5, 3, 3, 3, 3, 4, 4, 9, 5, 1, 3, 10, 6, 6, 3, 32, 4, 4, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 10, 8, 8, 3, 3, 3, 1, 8, 5, 2, 20, 3, 5, 2, 8, 8, 6, 4, 44, 2, 6, 5, 3, 3, 6, 1, 5, 1, 4, 2, 2, 2, 2, 102,6, 31, 5, 5, 17, 2, 2, 9, 5, 4, 3, 1, 1, 28, 3, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 12, 7, 6, 7, 4, 4, 4, 10, 7, 2, 1, 10, 6, 5, 4, 2, 1, 2, 1, 3, 3, 3, 45, 5, 10, 8, 4, 4, 4, 4, 2, 3, 1, 1, 23, 15, 3, 3, 2, 2, 2, 4, 3, 1, 3, 1, 17, 6, 2, 1, 1, 4, 2, 2, 2, 25, 3, 2, 3, 2, 3, 2, 1, 4, 8, 2, 13, 6, 1, 2, 2, 2, 4, 3, 3, 6, 2, 58, 5, 1, 2, 1, 3, 2, 1, 1, 3, 1, 1, 1, 1, 4, 1, 2, 1, 1, 1, 2, 3, 2, 10, 2, 4, 7, 2, 1, 2, 3, 1, 1, 1, 3, 5, 2, 2, 8, 5, 3, 1, 1, 13, 1, 2, 3, 2, 1, 1, 5, 1, 4, 8, 4, 2, 1, 1, 3, 2, 1, 47, 4, 4, 4, 4, 4, 8, 5, 3, 3, 5, 5, 5, 3, 5, 3, 3, 3, 3, 4, 3, 5, 7, 7, 7, 13, 3, 1, 1, 1, 3, 1, 13, 5, 2, 5, 3, 29, 2, 2, 3, 10, 3, 5, 1, 2, 1, 1, 13, 5, 3, 3, 2, 4, 2, 2, 3, 2, 6, 16, 5, 2, 7, 3, 2, 1, 1, 2, 127,32, 5, 16, 2, 2, 3, 2, 3, 4, 3, 1, 27, 3, 3, 3, 3, 3, 7, 1, 2, 7, 6, 4, 11, 4, 2, 2, 1, 1, 3, 40, 2, 2, 2, 2, 4, 4, 18, 1, 1, 8, 7, 7, 7, 7, 3, 1, 4, 2, 3, 3, 2, 4, 108,7, 3, 2, 1, 1, 4, 2, 2, 19, 3, 1, 2, 3, 6, 3, 3, 19, 6, 7, 4, 13, 4, 4, 4, 4, 4, 2, 2, 1, 3, 3, 6, 14, 4, 1, 5, 3, 5, 1, 1, 1, 1, 92, 19, 2, 3, 8, 8, 4, 4, 4, 4, 2, 1, 2, 13, 9, 9, 2, 4, 3, 7, 5, 5, 2, 7, 5, 2, 3, 15, 9, 7, 6, 5, 18, 2, 9, 8, 8, 5, 2, 1, 3, 5, 3, 2, 1, 1, 1, 76, 7, 2, 2, 8, 1, 2, 1, 1, 12, 4, 4, 1, 1, 1, 1, 1, 16, 2, 2, 2, 2, 2, 1, 1, 1, 6, 4, 2, 2, 13, 9, 3, 3, 3, 2, 1, 2, 161,9, 6, 5, 3, 1, 1, 1, 22, 7, 7, 6, 3, 2, 7, 7, 3, 1, 1, 15, 3, 11, 6, 1, 1, 12, 7, 1, 3, 23, 1, 10, 10, 2, 7, 2, 4, 3, 3, 3, 6, 3, 2, 1, 1, 4, 1, 1, 1, 6, 1, 2, 18, 3, 5, 5, 4, 8, 5, 5, 1, 1, 2, 2, 2, 6, 1, 3, 24, 2, 1, 1, 1, 9, 8, 1, 11, 6, 5, 5, 5, 1, 1, 1, 72, 11, 3, 7, 30, 10, 10, 6, 4, 14, 4, 4, 6, 5, 3, 2, 1, 11, 7, 7, 3, 3, 2, 2, 2, 2, 7, 2, 8, 3, 3, 3, 3, 107,18, 3, 4, 3, 2, 5, 5, 5, 5, 4, 22, 8, 8, 8, 3, 5, 7, 2, 3, 37, 5, 3, 12, 1, 1, 1, 3, 8, 2, 2, 2, 3, 3, 26, 3, 4, 3, 5, 3, 5, 27, 8, 3, 3, 8, 2, 3, 3, 2, 3, 3, 4, 2, 1, 1, 1, 1, 3, 1, 2, 1, 59, 7, 3, 3, 3, 3, 1, 11, 8, 8, 8, 2, 3, 3, 3, 2, 9, 3, 1, 7, 4, 4, 9, 4, 2, 2, 2, 3, 1, 3, 2, 2, 3, 1, 1, 17, 2, 2, 1, 1, 1, 1, 4, 5, 2, 1, 2, 2, 58, 4, 2, 7, 2, 2, 30, 11, 4, 3, 4, 2, 5, 2, 2, 2, 1, 1, 1, 10, 9, 9, 4, 1, 15, 4, 3, 3, 3, 1, 1, 1, 2, 61, 2, 6, 2, 3, 4, 3, 3, 2, 1, 3, 3, 3, 2, 2, 1, 1, 1, 9, 3, 17, 1, 5, 3, 4, 2, 1, 26, 2, 1, 17, 10, 1, 1, 7, 5, 5, 3, 3, 3, 3, 3, 2, 2, 1, 2, 2, 2, 2, 44, 2, 1, 7, 1, 1, 11, 4, 12, 2, 6, 4, 4, 6, 4, 2, 2, 2, 3, 56, 4, 3, 3, 2, 4, 5, 1, 1, 3, 23, 6, 2, 2, 6, 2, 2, 5, 3, 3, 2, 2, 2, 2, 6, 2, 48, 12, 1, 1, 4, 2, 2, 10, 3, 4, 8, 6, 4, 6, 3, 3, 1, 1, 3, 2, 2, 2, 60, 7, 9, 19, 5, 5, 6, 6, 5, 4, 4, 2, 1, 1, 1, 11, 5, 2, 2, 27, 4, 1, 1, 1, 1, 5, 2, 2, 6, 6, 6, 6, 6, 3, 32, 15, 13, 13, 5, 5, 5, 1, 9, 3, 2, 2, 3, 45, 5, 1, 1, 1, 1, 7, 7, 7, 2, 1, 1, 4, 17, 17, 6, 6, 6, 6, 6, 6, 5, 5, 4, 4, 1, 1, 1, 3, 4, 1, 1, 1, 2, 66, 2, 1, 6, 3, 3, 7, 7, 7, 5, 5, 3, 3, 3, 26, 2, 2, 1, 6, 1, 2, 2, 3, 2, 2, 1, 1, 8, 5, 6, 6, 6, 12, 8, 2, 2, 2, 3, 2, 2, 4, 7, 7, 6, 5, 3, 2, 2, 2, 2, 2, 2, 2, 12, 4, 3, 3, 3, 1, 1, 8, 5, 5, 19, 4, 1, 1, 1, 1, 6, 6, 2, 3, 3, 3, 2, 13, 2, 2, 2, 2, 2, 10, 3, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 4, 4, 3, 1, 1, 3, 10, 4, 5, 2, 2, 16, 6, 4, 2, 2, 2, 2, 5, 5, 5, 4, 4, 4, 1, 1, 14, 4, 4, 1, 7, 3, 3, 1, 1, 3, 3, 3, 3, 35, 7, 3, 2, 4, 8, 7, 7, 9, 7, 3, 3, 2, 2, 2, 6, 6, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 3, 3, 3, 2, 2, 2, 14, 6, 2, 4, 4, 4, 4, 4, 7, 22, 1, 1, 1, 1, 1, 8, 2, 6, 1, 5, 1, 4, 51, 7, 4, 3, 2, 3, 10, 1, 9, 5, 5, 3, 3, 3, 4, 4, 20, 7, 6, 6, 3, 3, 8, 6, 6, 6, 2, 2, 6, 6, 22, 2, 9, 1, 1, 2, 3, 1, 3, 2, 49, 18, 12, 7, 5, 3, 3, 18, 5, 4, 7, 1, 1, 1, 7, 2, 5, 5, 2, 8, 4, 5, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 5, 26, 4, 1, 4, 2, 3, 6, 2, 1, 1, 1, 31, 11, 4, 2, 8, 7, 7, 7, 7, 7, 7, 7, 3, 19, 11, 9, 4, 17, 6, 5, 1, 30, 3, 2, 2, 2, 2, 3, 2, 11, 2, 7, 2, 9, 2, 5, 54, 4, 3, 9, 2, 5, 4, 1, 19, 5, 4, 4, 2, 2, 2, 4, 4, 3, 60, 5, 1, 3, 5, 3, 3, 12, 5, 4, 4, 4, 3, 3, 3, 34, 31, 9, 6, 1, 6, 9, 5, 2, 2, 2, 1, 2, 1, 97, 4, 3, 22, 5, 3, 10, 3, 4, 3, 13, 2, 5, 2, 4, 1, 2, 4, 17, 5, 8, 5, 4, 11, 1, 3, 3, 4, 1, 44, 6, 4, 2, 2, 6, 3, 3, 1, 14, 3, 3, 3, 2, 1, 12, 2, 3, 1, 1, 1, 1, 95, 7, 2, 4, 11, 1, 3, 2, 3, 11, 3, 2, 4, 2, 10, 4, 3, 7, 2, 1, 7, 4, 4, 6, 2, 10, 2, 2, 3, 8, 4, 2, 22, 2, 1, 5, 2, 5, 1, 1, 1, 16, 6, 49, 16, 2, 7, 5, 1, 1, 5, 4, 2, 2, 1, 3, 17, 8, 3, 2, 3, 2, 8, 1, 6, 2, 1, 60, 2, 6, 4, 3, 36, 7, 1, 2, 3, 3, 2, 2, 2, 6, 3, 2, 3, 1, 3, 2, 2, 7, 8, 5, 99, 3, 4, 2, 6, 5, 28, 4, 15, 11, 2, 2, 2, 7, 7, 1, 4, 4, 6, 6, 3, 3, 6, 5, 2, 2, 2, 2, 2, 14, 4, 3, 2, 4, 79, 20, 5, 14, 4, 3, 1, 3, 3, 2, 6, 1, 1, 1, 1, 6, 4, 15, 9, 4, 9, 4, 3, 5, 1, 1, 2, 53, 3, 3, 6, 6, 3, 3, 5, 11, 9, 5, 3, 5, 3, 11, 9, 1, 46, 2, 3, 2, 3, 7, 8, 5, 5, 1, 1, 3, 2, 3, 7, 5, 2, 1, 42, 5, 2, 8, 4, 3, 2, 15, 1, 8, 6, 6, 3, 3, 7, 2, 1, 1, 1, 5, 37, 5, 3, 2, 3, 7, 2, 6, 6, 4, 3, 1, 39, 4, 11, 4, 2, 5, 3, 14, 2, 3, 1, 24, 4, 3, 2, 9, 2, 1, 1, 3, 3, 16, 3, 3, 4, 2, 23, 13, 5, 3, 3, 2, 14, 5, 5, 2, 7, 3, 2, 8, 8, 1, 28, 5, 4, 2, 10, 6, 2, 38, 4, 9, 2, 2, 1, 2, 6, 2, 2, 1, 1, 3, 10, 2, 83, 4, 6, 4, 4, 3, 2, 7, 4, 1, 14, 6, 2, 1, 4, 3, 5, 2, 2, 3, 15, 4, 3, 4, 3, 3, 8, 2, 2, 2, 3, 2, 22, 4, 7, 3, 3, 3, 1, 1, 1, 1, 6, 57, 11, 6, 4, 3, 2, 3, 5, 3, 2, 4, 7, 3, 3, 3, 2, 2, 2, 1, 2, 22, 4, 3, 3, 3, 2, 9, 3, 18, 3, 3, 4, 2, 3, 1, 4, 3, 2, 3, 22, 6, 3, 7, 5, 5, 6, 3, 2, 2, 2, 46, 4, 6, 5, 8, 4, 2, 1, 1, 5, 2, 6, 5, 4, 3, 7, 1, 1, 2, 2, 2, 2, 2, 2, 2, 4, 3, 1, 1, 1, 1, 1, 156,3, 7, 15, 3, 9, 8, 3, 5, 3, 2, 61, 2, 2, 17, 6, 4, 3, 2, 3, 2, 2, 1, 1, 12, 3, 2, 4, 4, 1, 1, 1, 2, 11, 11, 4, 3, 3, 2, 2, 37, 5, 4, 23, 2, 10, 8, 1, 3, 2, 2, 1, 16, 3, 1, 1, 6, 4, 1, 5, 5, 4, 3, 3, 3, 3, 35, 3, 4, 2, 15, 12, 3, 5, 3, 4, 4, 3, 2, 71, 5, 2, 2, 2, 2, 2, 2, 2, 2, 15, 2, 1, 1, 7, 2, 4, 3, 3, 3, 1, 1, 1, 5, 27, 9, 1, 1, 5, 2, 4, 6, 2, 2, 3, 2, 2, 10, 7, 6, 6, 5, 4, 1, 1, 1, 3, 3, 3, 4, 4, 2, 1, 2, 1, 1, 126,12, 7, 4, 3, 2, 2, 15, 6, 2, 4, 23, 2, 14, 3, 2, 21, 3, 1, 1, 12, 5, 5, 5, 16, 3, 2, 7, 7, 5, 5, 5, 3, 3, 3, 1, 9, 2, 2, 7, 7, 7, 3, 8, 6, 6, 6, 2, 18, 8, 7, 6, 4, 1, 2, 2, 2, 27, 20, 1, 8, 7, 7, 4, 3, 1, 6, 3, 3, 3, 2, 3, 3, 3, 3, 1, 50, 4, 3, 23, 7, 1, 4, 3, 3, 3, 1, 3, 2, 2, 4, 3, 3, 2, 8, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 3, 3, 3, 24, 6, 5, 3, 12, 3, 2, 2, 2, 83, 22, 19, 7, 2, 2, 1, 3, 3, 2, 2, 3, 6, 2, 1, 2, 10, 8, 8, 2, 2, 2, 2, 2, 7, 6, 4, 3, 2, 1, 1, 26, 2, 2, 18, 13, 5, 3, 2, 1, 1, 3, 2, 3, 2, 2, 2, 4, 2, 2, 2, 2, 2, 10, 1, 1, 2, 2, 2, 19, 1, 3, 2, 4, 5, 3, 1, 16, 6, 4, 1, 1, 3, 2, 5, 3, 69, 6, 3, 3, 29, 29, 5, 2, 20, 6, 5, 3, 13, 2, 2, 1, 1, 1, 5, 1, 5, 4, 4, 4, 6, 3, 3, 3, 9, 7, 7, 8, 4, 3, 2, 1, 1, 1, 1, 49, 5, 3, 2, 1, 5, 5, 5, 5, 5, 3, 10, 4, 2, 2, 10, 6, 4, 4, 4, 3, 1, 7, 4, 4, 65, 4, 1, 26, 1, 1, 12, 4, 3, 8, 3, 23, 8, 7, 3, 3, 5, 1, 3, 3, 9, 2, 2, 15, 4, 4, 3, 1, 3, 25, 4, 2, 7, 3, 2, 3, 2, 4, 1, 4, 12, 3, 8, 3, 5, 3, 4, 183,10, 3, 5, 2, 2, 2, 1, 1, 1, 53, 20, 3, 1, 1, 14, 7, 6, 6, 6, 1, 2, 12, 1, 1, 1, 5, 5, 5, 5, 4, 1, 2, 2, 6, 4, 3, 11, 2, 2, 3, 1, 19, 6, 13, 2, 6, 5, 2, 22, 9, 4, 4, 4, 3, 18, 4, 7, 4, 4, 7, 1, 4, 4, 4, 40, 5, 5, 5, 2, 4, 6, 5, 3, 3, 1, 3, 3, 10, 10, 10, 7, 4, 3, 3, 3, 6, 4, 1, 2, 10, 4, 3, 33, 2, 1, 1, 1, 1, 1, 1, 1, 8, 7, 7, 7, 2, 1, 4, 3, 2, 10, 8, 3, 3, 3, 5, 3, 2, 2, 2, 2, 2, 2, 2, 40, 5, 4, 4, 4, 11, 6, 3, 3, 3, 4, 4, 10, 7, 1, 4, 4, 12, 4, 5, 4, 8, 2, 5, 5, 65, 6, 3, 2, 20, 2, 2, 2, 11, 2, 7, 7, 7, 4, 17, 5, 4, 4, 8, 3, 5, 3, 2, 2, 1, 1, 6, 6, 6, 6, 3, 6, 6, 6, 10, 1, 7, 4, 4, 4, 4, 3, 2, 6, 2, 10, 1, 2, 2, 3, 3, 3, 3, 17, 10, 4, 2, 4, 4, 4, 4, 4, 1, 19, 1, 1, 1, 4, 1, 4, 2, 5, 67, 4, 2, 2, 2, 2, 19, 18, 4, 4, 4, 6, 6, 6, 6, 6, 1, 5, 5, 4, 4, 4, 3, 12, 3, 3, 5, 3, 18, 5, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, 2, 14, 2, 2, 10, 6, 6, 6, 6, 6, 6, 6, 4, 4, 2, 2, 4, 2, 43, 5, 1, 4, 3, 15, 2, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 4, 11, 4, 3, 4, 1, 1, 19, 19, 9, 5, 2, 4, 1, 1, 1, 9, 1, 1, 2, 2, 2, 2, 120,11, 8, 6, 1, 1, 1, 3, 7, 1, 22, 12, 4, 5, 3, 8, 2, 2, 4, 3, 3, 1, 1, 13, 5, 4, 4, 4, 7, 1, 1, 2, 2, 1, 1, 16, 5, 3, 3, 2, 4, 3, 4, 3, 3, 3, 3, 1, 1, 1, 185,43, 23, 8, 6, 4, 7, 4, 4, 11, 2, 5, 2, 1, 3, 1, 3, 1, 7, 3, 2, 2, 2, 6, 3, 2, 3, 2, 2, 2, 2, 1, 32, 5, 1, 1, 2, 9, 1, 2, 2, 1, 3, 1, 1, 1, 9, 6, 4, 1, 1, 2, 3, 3, 2, 8, 8, 3, 5, 2, 2, 19, 4, 8, 5, 3, 4, 60, 14, 8, 6, 6, 22, 3, 5, 4, 2, 7, 3, 10, 4, 2, 3, 2, 46, 4, 1, 2, 3, 3, 3, 25, 4, 3, 8, 3, 5, 2, 8, 2, 2, 2, 2, 3, 2, 1, 4, 2, 1, 1, 51, 4, 2, 1, 1, 1, 1, 7, 6, 6, 5, 3, 4, 4, 4, 2, 2, 2, 2, 13, 3, 7, 4, 3, 1, 6, 2, 4, 2, 3, 1, 1, 1, 2, 139,35, 5, 6, 5, 5, 5, 13, 6, 6, 6, 6, 2, 2, 73, 9, 4, 17, 4, 6, 40, 2, 4, 3, 1, 3, 2, 4, 2, 4, 2, 2, 13, 4, 3, 3, 3, 3, 20, 5, 5, 5, 3, 4, 2, 2, 11, 7, 3, 3, 3, 3, 20, 5, 6, 2, 2, 2, 22, 4, 1, 1, 3, 4, 4, 11, 7, 7, 4, 2, 2, 2, 2, 55, 3, 2, 2, 2, 23, 5, 6, 4, 3, 6, 6, 2, 2, 4, 4, 4, 4, 4, 5, 8, 7, 3, 1, 1, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2, 8, 1, 5, 4, 25, 11, 9, 9, 6, 3, 4, 4, 3, 7, 3, 2, 2, 18, 7, 6, 6, 6, 8, 5, 3, 3, 3, 3, 3, 3, 3, 17, 8, 8, 5, 4, 3, 2, 3, 8, 4, 1, 1, 3, 3, 3, 101,5, 5, 35, 9, 9, 4, 4, 4, 3, 6, 3, 2, 2, 3, 13, 10, 4, 6, 6, 6, 6, 2, 3, 4, 4, 30, 7, 3, 3, 3, 2, 2, 20, 5, 7, 2, 2, 4, 4, 14, 2, 2, 2, 4, 4, 7, 11, 11, 11, 11, 2, 2, 2, 2, 2, 57, 19, 11, 5, 8, 6, 6, 6, 5, 5, 3, 3, 3, 3, 5, 5, 5, 3, 2, 28, 20, 7, 2, 2, 2, 2, 2, 3, 1, 1, 1, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7, 7, 4, 1, 33, 22, 5, 3, 3, 1, 1, 5, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 7, 4, 1, 2, 1, 1, 1, 1, 2, 27, 4, 5, 10, 7, 3, 39, 12, 2, 2, 3, 8, 4, 4, 4, 2, 14, 8, 2, 9, 2, 18, 3, 2, 5, 3, 3, 3, 3, 2, 9, 2, 1, 1, 2, 1, 1, 1, 63, 11, 2, 6, 5, 35, 4, 3, 1, 3, 1, 2, 2, 5, 2, 1, 1, 1, 1, 4, 2, 1, 1, 3, 1, 3, 8, 7, 3, 3, 3, 7, 2, 2, 2, 55, 3, 1, 7, 6, 5, 4, 2, 2, 9, 5, 3, 1, 15, 2, 4, 4, 5, 2, 2, 19, 3, 2, 2, 11, 9, 7, 6, 4, 2, 47, 5, 4, 6, 4, 2, 2, 3, 1, 9, 4, 14, 11, 1, 1, 9, 5, 2, 2, 107,7, 2, 5, 3, 2, 2, 2, 10, 4, 5, 3, 11, 11, 5, 3, 3, 2, 2, 28, 8, 7, 3, 5, 4, 1, 2, 2, 2, 8, 3, 32, 10, 9, 6, 5, 5, 5, 3, 7, 41, 7, 5, 2, 5, 11, 8, 8, 2, 5, 3, 1, 1, 1, 6, 4, 47, 4, 3, 6, 4, 4, 3, 3, 14, 2, 2, 1, 6, 2, 5, 1, 1, 2, 9, 2, 2, 2, 2, 5, 3, 2, 26, 5, 3, 15, 11, 5, 5, 4, 4, 4, 7, 1, 5, 7, 3, 1, 3, 1, 2, 1, 31, 3, 10, 2, 4, 12, 6, 6, 6, 3, 2, 2, 5, 2, 7, 3, 2, 1, 22, 3, 3, 11, 9, 4, 3, 3, 3, 3, 1, 1, 32, 2, 8, 10, 2, 5, 4, 6, 3, 1, 22, 1, 1, 4, 3, 6, 3, 3, 72, 2, 2, 2, 10, 17, 9, 7, 3, 2, 2, 17, 6, 2, 4, 2, 5, 5, 2, 12, 15, 5, 5, 4, 4, 44, 6, 3, 8, 2, 2, 2, 10, 7, 2, 4, 3, 3, 3, 2, 2, 5, 1, 2, 94, 2, 1, 4, 2, 2, 10, 4, 2, 2, 4, 2, 4, 13, 7, 5, 5, 3, 1, 1, 2, 43, 11, 2, 2, 3, 5, 14, 4, 3, 12, 3, 4, 2, 6, 6, 5, 4, 3, 2, 17, 4, 2, 11, 3, 3, 5, 5, 21, 11, 7, 6, 2, 8, 2, 2, 4, 4, 4, 1, 8, 7, 7, 6, 4, 1, 1, 1, 1, 1, 10, 2, 5, 5, 4, 2, 2, 7, 3, 3, 1, 2, 20, 4, 10, 5, 3, 3, 14, 4, 1, 7, 5, 3, 1, 29, 3, 1, 9, 9, 3, 3, 3, 10, 3, 3, 11, 2, 7, 2, 2, 33, 1, 1, 5, 8, 8, 16, 6, 2, 2, 4, 10, 3, 4, 2, 2, 1, 1, 2, 65, 6, 16, 2, 2, 9, 3, 4, 2, 2, 18, 6, 5, 4, 2, 10, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 6, 1, 115,23, 11, 6, 9, 8, 5, 7, 6, 11, 3, 14, 2, 4, 17, 16, 16, 2, 6, 2, 2, 4, 4, 1, 9, 5, 12, 1, 1, 5, 3, 1, 4, 4, 4, 3, 10, 6, 2, 3, 3, 1, 12, 11, 1, 1, 6, 4, 1, 1, 61, 43, 2, 6, 3, 2, 2, 2, 2, 8, 5, 4, 4, 2, 1, 4, 2, 5, 4, 1, 2, 4, 2, 2, 7, 7, 5, 50, 8, 2, 2, 5, 3, 29, 7, 2, 2, 8, 5, 4, 4, 3, 1, 8, 2, 35, 1, 2, 1, 1, 8, 19, 2, 2, 5, 3, 3, 3, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, 5, 5, 12, 4, 4, 123,8, 2, 1, 5, 5, 13, 5, 2, 3, 3, 2, 18, 3, 4, 1, 5, 15, 14, 3, 3, 2, 22, 2, 3, 2, 9, 8, 6, 1, 5, 3, 8, 5, 5, 79, 4, 5, 3, 1, 4, 1, 1, 2, 14, 2, 2, 3, 3, 1, 10, 8, 6, 6, 1, 1, 9, 3, 4, 6, 1, 1, 7, 5, 5, 4, 4, 3, 2, 2, 4, 3, 53, 9, 8, 3, 4, 4, 15, 6, 5, 3, 2, 2, 11, 1, 7, 4, 6, 6, 2, 2, 2, 2, 2, 93, 6, 6, 3, 14, 5, 7, 2, 6, 34, 3, 14, 7, 3, 3, 3, 14, 4, 3, 3, 3, 2, 7, 2, 5, 2, 2, 11, 7, 6, 41, 3, 1, 5, 5, 3, 6, 5, 1, 1, 1, 3, 6, 6, 12, 3, 3, 2, 2, 5, 2, 11, 3, 6, 6, 5, 3, 16, 2, 3, 4, 5, 4, 8, 4, 51, 7, 2, 8, 3, 2, 20, 2, 2, 1, 4, 9, 9, 4, 3, 2, 34, 1, 11, 4, 2, 2, 1, 1, 2, 11, 6, 5, 2, 4, 2, 2, 46, 11, 1, 1, 5, 3, 3, 25, 3, 3, 3, 1, 1, 2, 5, 2, 2, 1, 218,6, 10, 5, 4, 2, 9, 5, 3, 8, 1, 1, 41, 3, 4, 1, 1, 2, 2, 3, 3, 3, 2, 11, 3, 3, 4, 3, 2, 1, 4, 3, 26, 14, 8, 4, 7, 5, 4, 2, 3, 2, 7, 3, 1, 29, 11, 5, 1, 1, 1, 3, 5, 4, 5, 5, 5, 8, 1, 4, 3, 7, 6, 5, 4, 16, 3, 3, 2, 36, 3, 4, 1, 1, 9, 3, 4, 10, 9, 4, 3, 5, 2, 2, 4, 4, 4, 20, 2, 2, 2, 8, 3, 3, 3, 4, 5, 3, 2, 2, 39, 8, 11, 7, 4, 4, 4, 4, 8, 4, 3, 4, 8, 6, 14, 2, 1, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 174,6, 5, 3, 2, 12, 3, 2, 3, 3, 3, 2, 11, 3, 3, 8, 6, 83, 34, 11, 9, 7, 2, 4, 3, 9, 4, 4, 25, 6, 4, 1, 1, 4, 5, 2, 4, 5, 7, 3, 2, 1, 11, 3, 11, 6, 5, 5, 4, 2, 2, 8, 3, 1, 1, 4, 7, 4, 186,12, 10, 1, 1, 4, 2, 5, 2, 36, 7, 14, 10, 10, 8, 6, 2, 2, 2, 7, 7, 7, 5, 50, 7, 2, 2, 6, 3, 2, 10, 4, 4, 3, 2, 2, 4, 4, 3, 6, 4, 3, 1, 2, 14, 3, 2, 2, 3, 13, 6, 6, 1, 3, 4, 4, 4, 4, 3, 2, 2, 19, 4, 12, 5, 4, 8, 7, 4, 4, 3, 2, 6, 2, 118,7, 6, 10, 4, 40, 17, 5, 3, 3, 3, 10, 10, 4, 4, 3, 3, 3, 7, 3, 2, 1, 14, 2, 8, 8, 5, 4, 5, 14, 3, 4, 4, 12, 3, 4, 1, 4, 3, 64, 1, 2, 1, 3, 5, 1, 9, 4, 3, 2, 2, 2, 2, 2, 8, 3, 3, 2, 13, 2, 2, 1, 1, 1, 7, 1, 3, 6, 5, 2, 2, 1, 1, 24, 7, 7, 3, 6, 5, 5, 5, 5, 4, 10, 1, 1, 2, 2, 41, 2, 36, 2, 1, 1, 1, 6, 2, 2, 2, 4, 3, 3, 3, 2, 2, 3, 3, 3, 1, 7, 15, 1, 3, 5, 2, 2, 1, 11, 2, 2, 4, 1, 3, 6, 5, 7, 7, 1, 8, 2, 1, 5, 1, 1, 49, 2, 8, 6, 6, 2, 2, 3, 20, 3, 2, 6, 3, 1, 2, 4, 2, 2, 4, 2, 7, 4, 53, 7, 2, 4, 4, 4, 4, 14, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3, 4, 5, 2, 9, 3, 2, 2, 2, 2, 2, 49, 4, 3, 4, 4, 3, 11, 3, 5, 3, 5, 3, 8, 2, 4, 4, 3, 2, 2, 21, 2, 4, 5, 2, 3, 3, 6, 13, 2, 2, 4, 4, 4, 4, 1, 1, 1, 1, 1, 6, 2, 1, 3, 1, 1, 1, 1, 1, 3, 36, 3, 7, 2, 6, 3, 1, 1, 1, 3, 2, 1, 13, 13, 12, 12, 5, 5, 21, 1, 3, 5, 2, 2, 3, 8, 2, 1, 3, 6, 6, 16, 3, 4, 4, 3, 4, 6, 2, 26, 2, 5, 12, 3, 2, 1, 3, 2, 137,22, 3, 3, 3, 5, 1, 3, 3, 1, 4, 2, 4, 2, 11, 4, 2, 2, 15, 2, 3, 4, 2, 60, 12, 2, 2, 1, 1, 1, 13, 4, 16, 2, 3, 2, 2, 9, 3, 4, 2, 2, 2, 5, 2, 2, 3, 2, 49, 4, 17, 3, 7, 5, 14, 2, 2, 9, 65, 12, 5, 3, 2, 2, 2, 2, 2, 2, 24, 3, 2, 2, 1, 1, 5, 1, 3, 4, 3, 2, 5, 4, 4, 7, 4, 2, 2, 171,9, 1, 1, 11, 4, 3, 7, 2, 3, 22, 13, 10, 3, 4, 4, 5, 4, 3, 2, 1, 10, 2, 2, 2, 2, 10, 2, 2, 2, 8, 5, 7, 2, 12, 6, 4, 2, 6, 3, 2, 31, 5, 7, 5, 12, 3, 2, 5, 8, 2, 2, 12, 2, 4, 47, 3, 2, 2, 7, 2, 2, 3, 2, 2, 2, 7, 3, 10, 5, 7, 2, 1, 2, 48, 2, 12, 1, 1, 4, 3, 10, 5, 5, 7, 5, 2, 7, 2, 3, 44, 8, 4, 4, 10, 4, 2, 2, 5, 3, 4, 7, 2, 10, 5, 5, 1, 9, 3, 2, 28, 3, 7, 4, 5, 4, 14, 2, 6, 3, 2, 2, 2, 1, 1, 1, 1, 1, 15, 11, 2, 8, 6, 4, 3, 18, 4, 4, 4, 2, 2, 1, 1, 1, 16, 5, 9, 3, 24, 3, 13, 4, 3, 20, 2, 2, 1, 7, 2, 5, 1, 11, 1, 1, 2, 2, 5, 102,3, 5, 1, 36, 20, 14, 5, 9, 4, 3, 4, 5, 4, 4, 5, 4, 3, 10, 4, 3, 3, 7, 2, 2, 8, 4, 3, 2, 3, 20, 2, 2, 8, 7, 5, 3, 3, 3, 2, 41, 3, 3, 2, 5, 1, 4, 4, 4, 4, 3, 4, 4, 4, 3, 2, 2, 2, 2, 1, 1, 1, 25, 25, 10, 3, 13, 6, 1, 1, 1, 1, 1, 1, 4, 109, 13, 3, 5, 4, 1, 1, 6, 4, 4, 5, 2, 2, 4, 7, 2, 2, 5, 2, 13, 9, 7, 2, 7, 1, 8, 2, 4, 9, 3, 5, 2, 12, 3, 3, 3, 5, 4, 3, 2, 2, 15, 1, 1, 1, 3, 6, 3, 2, 1, 33, 2, 2, 2, 2, 1, 2, 4, 2, 14, 5, 3, 2, 2, 2, 2, 3, 1, 1, 7, 7, 7, 6, 4, 28, 4, 9, 3, 2, 2, 2, 2, 13, 3, 2, 5, 5, 1, 3, 42, 8, 3, 4, 8, 4, 2, 2, 2, 1, 18, 9, 3, 1, 1, 1, 1, 1, 2, 2, 1, 1, 5, 4, 1, 23, 10, 2, 4, 2, 3, 2, 3, 11, 1, 6, 2, 2, 2, 21, 2, 7, 9, 3, 6, 3, 19, 3, 3, 3, 6, 5, 3, 1, 3, 2, 109,12, 6, 4, 4, 3, 3, 2, 2, 1, 8, 5, 2, 1, 1, 1, 2, 1, 2, 1, 4, 3, 53, 5, 1, 12, 2, 3, 2, 24, 6, 4, 2, 3, 3, 4, 8, 3, 1, 4, 3, 2, 2, 2, 2, 4, 2, 12, 2, 1, 2, 5, 3, 29, 4, 1, 1, 1, 2, 7, 4, 4, 2, 4, 2, 10, 5, 3, 3, 4, 3, 3, 3, 39, 7, 2, 3, 3, 4, 1, 1, 1, 2, 12, 3, 3, 7, 2, 4, 12, 1, 3, 3, 1, 8, 4, 3, 1, 10, 2, 2, 2, 1, 4, 2, 2, 2, 15, 10, 1, 3, 8, 5, 5, 3, 1, 1, 199,84, 3, 20, 5, 3, 3, 2, 2, 5, 4, 3, 14, 4, 24, 3, 3, 3, 14, 2, 2, 4, 3, 3, 1, 5, 27, 5, 1, 3, 2, 15, 5, 2, 2, 1, 6, 1, 1, 25, 2, 6, 4, 4, 4, 2, 5, 42, 5, 3, 1, 1, 1, 3, 11, 7, 2, 2, 3, 5, 4, 2, 1, 1, 6, 1, 1, 1, 1, 14, 2, 4, 2, 3, 2, 5, 1, 3, 8, 6, 1, 1, 1, 19, 3, 3, 6, 20, 4, 4, 4, 2, 7, 2, 5, 3, 1, 1, 59, 4, 2, 7, 4, 10, 8, 6, 3, 1, 3, 4, 2, 2, 1, 1, 1, 7, 6, 7, 3, 2, 2, 1, 1, 8, 2, 1, 2, 30, 2, 2, 3, 2, 6, 2, 9, 4, 3, 9, 4, 2, 2, 11, 2, 1, 5, 2, 6, 9, 6, 3, 2, 93, 5, 46, 3, 4, 1, 7, 5, 3, 1, 4, 1, 3, 13, 4, 2, 1, 1, 1, 4, 1, 1, 2, 4, 2, 4, 1, 2, 1, 12, 3, 2, 7, 2, 1, 10, 2, 2, 3, 8, 4, 44, 2, 2, 1, 4, 2, 2, 3, 3, 2, 1, 1, 6, 3, 3, 1, 2, 4, 2, 14, 9, 8, 4, 2, 3, 111,3, 2, 69, 3, 1, 10, 3, 2, 3, 3, 2, 2, 2, 2, 2, 3, 6, 7, 5, 2, 2, 2, 2, 6, 6, 2, 4, 2, 6, 4, 2, 7, 2, 9, 5, 1, 1, 2, 57, 4, 1, 1, 14, 5, 3, 4, 3, 7, 3, 7, 1, 2, 3, 3, 1, 4, 30, 4, 2, 2, 13, 3, 4, 3, 1, 1, 29, 4, 4, 2, 2, 5, 3, 1, 1, 10, 2, 1, 1, 2, 2, 2, 2, 2, 1, 29, 4, 4, 9, 3, 4, 5, 3, 1, 1, 17, 14, 12, 11, 14, 5, 5, 2, 2, 2, 19, 4, 5, 3, 1, 1, 18, 9, 8, 3, 22, 6, 2, 2, 7, 7, 4, 8, 3, 4, 5, 3, 6, 4, 2, 16, 1, 5, 87, 8, 4, 3, 2, 2, 47, 3, 2, 2, 2, 2, 2, 6, 6, 3, 3, 3, 3, 5, 4, 3, 3, 4, 6, 3, 4, 4, 5, 3, 3, 4, 2, 4, 7, 10, 4, 1, 1, 5, 19, 12, 2, 2, 2, 15, 6, 3, 2, 2, 4, 23, 15, 1, 1, 4, 3, 4, 3, 1, 1, 11, 2, 1, 1, 10, 7, 3, 161,20, 6, 4, 9, 3, 67, 50, 3, 2, 2, 7, 3, 2, 4, 2, 2, 7, 2, 4, 1, 4, 2, 8, 5, 4, 22, 3, 12, 10, 5, 5, 3, 10, 3, 2, 5, 4, 10, 3, 3, 2, 4, 5, 151,4, 4, 2, 2, 6, 2, 22, 1, 1, 1, 6, 2, 2, 7, 22, 19, 4, 11, 3, 4, 3, 5, 1, 1, 9, 8, 18, 4, 3, 3, 2, 4, 18, 8, 3, 2, 2, 10, 5, 21, 3, 2, 2, 7, 4, 2, 6, 3, 6, 6, 2, 138,3, 6, 4, 5, 4, 2, 1, 3, 4, 3, 2, 3, 2, 18, 9, 2, 2, 4, 5, 27, 2, 1, 2, 16, 1, 1, 1, 8, 2, 3, 2, 22, 3, 10, 10, 1, 1, 2, 6, 7, 7, 3, 12, 3, 3, 3, 55, 9, 4, 4, 8, 5, 2, 7, 4, 2, 19, 5, 4, 2, 2, 5, 1, 1, 1, 2, 2, 1, 1, 6, 1, 1, 1, 2, 2, 2, 1, 43, 1, 1, 7, 4, 2, 1, 1, 1, 2, 1, 3, 1, 1, 22, 4, 4, 4, 4, 4, 14, 2, 7, 4, 2, 2, 2, 2, 2, 2, 4, 2, 10, 2, 3, 1, 1, 1, 43, 2, 2, 26, 4, 4, 13, 2, 4, 5, 3, 8, 3, 3, 2, 2, 8, 2, 2, 1, 5, 4, 2, 2, 1, 78, 15, 7, 2, 32, 5, 4, 3, 2, 2, 13, 6, 2, 2, 1, 4, 3, 2, 5, 11, 5, 2, 10, 4, 4, 2, 2, 1, 1, 2, 1, 29, 4, 2, 2, 2, 2, 2, 16, 3, 4, 5, 16, 5, 5, 2, 2, 3, 2, 50, 9, 5, 5, 3, 16, 3, 9, 3, 4, 2, 2, 7, 1, 1, 2, 2, 5, 3, 3, 18, 2, 1, 5, 4, 2, 31, 2, 7, 3, 4, 13, 7, 32, 2, 1, 1, 4, 1, 2, 2, 5, 3, 3, 2, 3, 10, 6, 5, 2, 3, 15, 3, 3, 4, 8, 4, 1, 1, 1, 14, 1, 1, 6, 6, 2, 1, 1, 1, 1, 1, 3, 3, 14, 5, 4, 4, 4, 3, 3, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1, 20, 3, 3, 3, 1, 1, 20, 12, 7, 1, 1, 2, 6, 4, 26, 23, 1, 1, 1, 6, 3, 3, 3, 25, 2, 8, 3, 1, 3, 8, 2, 1, 10, 3, 4, 50, 6, 14, 3, 1, 3, 13, 4, 3, 4, 2, 3, 3, 2, 2, 10, 5, 4, 3, 2, 2, 58, 5, 8, 3, 2, 2, 7, 23, 2, 3, 2, 2, 2, 5, 3, 5, 2, 4, 2, 3, 2, 9, 2, 2, 124,4, 5, 3, 3, 1, 4, 2, 5, 10, 1, 5, 2, 2, 3, 56, 4, 2, 8, 1, 1, 12, 1, 3, 3, 3, 11, 7, 5, 7, 5, 5, 5, 2, 5, 3, 3, 8, 3, 3, 2, 5, 1, 4, 2, 2, 101,9, 1, 1, 1, 1, 16, 4, 3, 5, 3, 2, 2, 3, 3, 8, 3, 2, 8, 4, 6, 5, 31, 6, 2, 16, 10, 5, 6, 5, 8, 3, 2, 4, 5, 3, 1, 1, 1, 175,3, 2, 4, 51, 5, 4, 1, 3, 3, 3, 5, 2, 5, 2, 2, 2, 1, 1, 1, 1, 9, 7, 3, 1, 8, 2, 4, 4, 4, 1, 1, 8, 14, 4, 4, 15, 6, 2, 4, 5, 26, 9, 6, 6, 2, 3, 2, 1, 22, 3, 2, 2, 3, 6, 3, 2, 3, 5, 2, 3, 24, 22, 8, 7, 5, 8, 2, 11, 2, 4, 17, 3, 3, 6, 8, 1, 1, 1, 2, 53, 3, 3, 1, 2, 14, 4, 2, 10, 4, 3, 3, 2, 2, 6, 2, 2, 7, 1, 1, 10, 3, 1, 6, 1, 3, 6, 11, 4, 2, 2, 14, 7, 19, 2, 5, 5, 2, 3, 13, 7, 4, 3, 3, 19, 4, 4, 6, 3, 3, 41, 2, 1, 6, 3, 21, 6, 7, 7, 4, 10, 7, 2, 2, 59, 4, 5, 7, 3, 2, 3, 3, 4, 4, 3, 1, 12, 2, 2, 2, 2, 4, 3, 2, 5, 15, 7, 2, 5, 4, 61, 1, 1, 2, 10, 4, 2, 3, 4, 2, 2, 5, 3, 6, 4, 2, 18, 6, 2, 1, 1, 11, 3, 4, 42, 9, 9, 6, 5, 7, 3, 11, 3, 2, 53, 4, 2, 2, 11, 6, 3, 5, 3, 12, 2, 2, 2, 2, 4, 35, 5, 20, 5, 8, 2, 2, 5, 2, 2, 3, 3, 2, 30, 3, 3, 3, 17, 6, 4, 4, 3, 1, 2, 51, 5, 1, 3, 2, 3, 4, 3, 4, 6, 4, 2, 7, 2, 5, 4, 6, 2, 5, 27, 4, 3, 13, 11, 6, 4, 2, 1, 1, 4, 154,1, 1, 45, 2, 2, 2, 1, 1, 1, 11, 6, 4, 2, 4, 3, 2, 1, 1, 1, 2, 1, 1, 1, 7, 3, 15, 4, 6, 6, 3, 1, 40, 4, 4, 22, 8, 3, 4, 3, 3, 2, 3, 1, 1, 1, 1, 1, 1, 3, 23, 13, 3, 1, 2, 7, 5, 2, 17, 7, 5, 5, 3, 1, 12, 4, 3, 3, 28, 2, 20, 5, 5, 1, 2, 2, 25, 3, 11, 8, 2, 3, 3, 19, 4, 4, 1, 18, 11, 5, 1, 4, 2, 3, 19, 5, 6, 3, 2, 9, 7, 5, 4, 3, 2, 2, 110,2, 2, 1, 22, 10, 2, 3, 2, 1, 1, 1, 3, 2, 1, 1, 6, 5, 4, 4, 2, 2, 5, 8, 2, 35, 1, 10, 3, 3, 2, 2, 2, 2, 3, 3, 6, 9, 7, 1, 1, 130,2, 6, 2, 4, 1, 1, 5, 5, 6, 3, 5, 3, 8, 3, 3, 43, 3, 4, 2, 2, 2, 2, 4, 4, 9, 3, 1, 1, 1, 2, 2, 2, 21, 5, 4, 2, 3, 14, 3, 2, 6, 1, 4, 41, 3, 3, 2, 4, 3, 3, 1, 4, 8, 5, 5, 5, 7, 1, 1, 1, 1, 4, 4, 4, 6, 3, 119,4, 5, 2, 3, 20, 6, 5, 4, 2, 11, 7, 3, 4, 2, 3, 5, 2, 2, 5, 3, 1, 1, 1, 1, 4, 24, 11, 5, 3, 10, 5, 5, 20, 16, 6, 3, 3, 2, 4, 12, 2, 2, 2, 4, 3, 8, 1, 13, 3, 4, 171,6, 11, 3, 1, 8, 15, 1, 3, 2, 3, 1, 3, 4, 3, 2, 25, 11, 9, 1, 1, 1, 1, 4, 1, 1, 1, 1, 7, 3, 2, 14, 6, 5, 5, 2, 2, 3, 3, 13, 2, 5, 23, 3, 6, 8, 3, 18, 2, 2, 11, 2, 5, 1, 12, 3, 2, 4, 1, 2, 2, 7, 28, 6, 6, 6, 2, 2, 5, 3, 10, 4, 2, 1, 1, 1, 4, 15, 2, 2, 2, 1, 1, 2, 1, 1, 4, 3, 5, 4, 2, 1, 12, 4, 2, 3, 1, 1, 25, 5, 2, 3, 2, 3, 1, 1, 7, 7, 7, 6, 4, 2, 2, 17, 8, 4, 3, 3, 1, 1, 21, 2, 18, 5, 2, 1, 1, 1, 1, 1, 1, 3, 3, 11, 1, 6, 3, 3, 19, 3, 1, 7, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 49, 2, 8, 2, 2, 1, 1, 5, 5, 2, 2, 2, 30, 2, 10, 3, 3, 5, 4, 4, 4, 4, 2, 2, 2, 2, 2, 1, 1, 5, 3, 3, 7, 2, 94, 6, 5, 4, 5, 1, 18, 10, 3, 3, 3, 2, 2, 2, 3, 5, 3, 3, 41, 2, 4, 3, 3, 11, 4, 8, 4, 2, 2, 7, 11, 2, 2, 2, 2, 4, 2, 1, 1, 77, 10, 7, 2, 2, 2, 3, 18, 8, 5, 2, 9, 5, 8, 3, 2, 4, 2, 2, 1, 1, 2, 2, 2, 3, 9, 2, 1, 3, 4, 21, 17, 5, 2, 1, 3, 4, 1, 1, 1, 1, 1, 1, 2, 28, 6, 6, 1, 1, 1, 2, 8, 6, 6, 2, 31, 2, 4, 3, 2, 2, 8, 5, 10, 2, 7, 7, 5, 1, 1, 30, 11, 11, 2, 4, 4, 4, 2, 7, 4, 2, 4, 2, 2, 5, 4, 3, 2, 18, 7, 3, 5, 23, 4, 9, 4, 2, 2, 2, 3, 25, 10, 2, 2, 4, 4, 3, 13, 7, 3, 2, 26, 3, 4, 11, 3, 3, 3, 1, 1, 2, 25, 6, 3, 3, 1, 3, 3, 22, 15, 3, 5, 2, 1, 1, 2, 3, 2, 2, 29, 6, 5, 3, 7, 3, 3, 1, 3, 3, 3, 14, 3, 2, 4, 2, 7, 5, 8, 2, 4, 1, 1, 199,4, 6, 4, 3, 3, 2, 2, 20, 8, 5, 2, 7, 1, 3, 11, 4, 2, 70, 13, 6, 5, 14, 6, 4, 3, 5, 21, 11, 7, 4, 2, 3, 11, 5, 2, 7, 3, 3, 3, 14, 3, 2, 5, 1, 1, 2, 2, 2, 17, 1, 2, 1, 1, 9, 5, 19, 1, 1, 3, 1, 6, 1, 1, 5, 2, 1, 8, 2, 2, 56, 3, 3, 32, 4, 3, 2, 3, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 6, 3, 3, 2, 1, 7, 3, 3, 3, 3, 3, 7, 4, 3, 3, 3, 3, 3, 64, 3, 5, 23, 3, 3, 16, 1, 1, 1, 6, 5, 5, 5, 2, 2, 3, 5, 2, 15, 1, 9, 3, 5, 5, 2, 1, 1, 1, 55, 2, 6, 2, 1, 1, 10, 5, 3, 2, 6, 6, 3, 11, 5, 2, 1, 33, 10, 4, 10, 5, 1, 1, 3, 115,7, 3, 1, 4, 7, 3, 3, 3, 5, 3, 27, 6, 3, 2, 2, 7, 3, 3, 4, 4, 1, 2, 16, 2, 1, 7, 3, 2, 3, 25, 13, 1, 1, 3, 2, 2, 4, 10, 4, 4, 1, 1, 1, 2, 60, 3, 9, 3, 5, 5, 7, 7, 6, 6, 5, 2, 6, 2, 21, 8, 3, 2, 5, 4, 50, 3, 3, 2, 2, 1, 2, 4, 4, 2, 2, 5, 2, 2, 3, 2, 3, 3, 3, 3, 3, 7, 3, 8, 3, 3, 81, 5, 4, 3, 8, 5, 3, 3, 7, 5, 1, 6, 38, 28, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4, 3, 3, 3, 7, 3, 2, 2, 2, 5, 1, 19, 2, 12, 2, 7, 1, 2, 1, 1, 42, 6, 3, 3, 3, 2, 11, 3, 2, 7, 3, 4, 11, 3, 1, 1, 1, 11, 1, 1, 1, 1, 2, 1, 1, 2, 2, 6, 5, 2, 4, 19, 9, 2, 1, 3, 3, 12, 11, 4, 4, 1, 3, 2, 2, 21, 5, 5, 4, 3, 2, 3, 15, 2, 2, 2, 4, 2, 18, 14, 3, 3, 2, 2, 1, 1, 1, 1, 1, 37, 7, 1, 11, 4, 10, 4, 2, 1, 2, 5, 2, 2, 1, 10, 3, 3, 2, 36, 2, 4, 7, 2, 2, 7, 6, 72, 9, 9, 2, 2, 12, 5, 5, 3, 2, 12, 4, 2, 7, 7, 9, 4, 2, 2, 2, 8, 2, 5, 15, 2, 1, 1, 1, 7, 2, 4, 68, 4, 4, 6, 3, 3, 2, 1, 9, 4, 3, 2, 8, 3, 6, 3, 2, 9, 2, 2, 2, 19, 5, 5, 2, 1, 6, 47, 4, 13, 4, 3, 5, 4, 11, 2, 5, 4, 4, 4, 8, 44, 3, 4, 6, 3, 1, 1, 1, 10, 4, 3, 8, 3, 3, 7, 29, 13, 13, 4, 6, 5, 3, 7, 3, 3, 5, 1, 3, 105,3, 1, 1, 3, 16, 4, 12, 6, 2, 5, 2, 2, 2, 2, 2, 2, 7, 3, 3, 3, 3, 2, 5, 8, 3, 8, 3, 35, 12, 9, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 3, 3, 17, 5, 1, 1, 1, 1, 4, 4, 1, 1, 2, 2, 2, 2, 2, 7, 2, 2, 5, 4, 2, 2, 1, 1, 1, 1, 1, 1, 11, 6, 6, 6, 5, 2, 2, 1, 1, 1, 1, 1, 10, 2, 3, 2, 1, 1, 1, 18, 3, 3, 2, 1, 2, 9, 7, 1, 2, 1, 1, 9, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 27, 15, 4, 2, 1, 2, 2, 2, 2, 2, 2, 3, 1, 2, 3, 2, 1, 53, 3, 4, 24, 10, 7, 4, 3, 9, 9, 9, 6, 4, 8, 7, 1, 1, 1, 1, 1, 1, 4, 1, 1, 4, 2, 1, 6, 4, 2, 2, 2, 2, 2, 1, 1, 2, 4, 2, 2, 2, 25, 4, 3, 13, 2, 7, 7, 3, 3, 2, 5, 1, 1, 1, 2, 2, 1, 7, 6, 6, 5, 4, 1, 1, 8, 6, 52, 2, 1, 1, 1, 5, 4, 2, 28, 7, 5, 2, 1, 1, 5, 3, 1, 10, 3, 3, 3, 3, 3, 7, 5, 5, 4, 4, 9, 8, 8, 4, 4, 4, 1, 1, 1, 206,18, 13, 3, 10, 10, 5, 12, 2, 6, 3, 3, 3, 1, 1, 2, 1, 1, 1, 3, 3, 3, 4, 1, 1, 27, 4, 1, 3, 1, 2, 9, 2, 2, 3, 4, 3, 3, 3, 7, 2, 2, 2, 4, 3, 3, 3, 3, 3, 131,13, 3, 2, 2, 2, 2, 26, 3, 1, 3, 3, 2, 1, 1, 16, 2, 2, 3, 3, 3, 3, 6, 6, 6, 6, 1, 1, 22, 4, 1, 2, 2, 10, 8, 4, 24, 9, 9, 4, 4, 10, 3, 3, 3, 3, 38, 1, 14, 3, 3, 3, 10, 10, 7, 4, 4, 5, 3, 3, 17, 1, 4, 2, 2, 2, 11, 5, 8, 4, 4, 23, 3, 2, 4, 3, 1, 2, 2, 2, 5, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 3, 2, 2, 102,15, 3, 3, 3, 2, 2, 5, 3, 11, 5, 4, 2, 2, 14, 6, 6, 6, 5, 3, 3, 3, 3, 3, 2, 8, 2, 4, 32, 1, 2, 2, 2, 10, 1, 1, 1, 1, 7, 2, 2, 2, 1, 1, 1, 1, 13, 2, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 6, 3, 3, 2, 1, 11, 8, 5, 2, 3, 9, 4, 3, 3, 1, 4, 97, 23, 3, 2, 2, 4, 3, 3, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 46, 7, 5, 2, 1, 13, 9, 9, 4, 2, 2, 2, 7, 2, 4, 3, 7, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 7, 1, 3, 2, 18, 1, 1, 1, 6, 5, 5, 4, 6, 6, 6, 4, 2, 20, 1, 1, 3, 3, 2, 2, 2, 1, 1, 2, 1, 1, 3, 3, 3, 6, 1, 5, 3, 2, 2, 2, 2, 2, 2, 94, 15, 1, 8, 8, 2, 2, 16, 7, 7, 4, 7, 2, 2, 2, 2, 14, 2, 1, 3, 6, 5, 5, 1, 22, 9, 4, 2, 5, 2, 4, 7, 6, 6, 14, 1, 1, 12, 12, 7, 4, 4, 3, 7, 4, 1, 1, 1, 3, 3, 6, 3, 3, 42, 3, 2, 15, 1, 4, 2, 3, 2, 2, 5, 3, 11, 6, 3, 1, 2, 2, 2, 23, 9, 4, 4, 4, 5, 8, 7, 5, 4, 4, 4, 4, 2, 2, 13, 2, 9, 9, 9, 6, 4, 15, 6, 9, 3, 4, 2, 2, 2, 4, 3, 4, 3, 26, 3, 3, 2, 5, 9, 7, 6, 6, 4, 2, 3, 2, 2, 2, 1, 6, 2, 2, 1, 1, 1, 1, 1, 2, 8, 8, 7, 4, 3, 3, 3, 126,4, 1, 4, 3, 2, 16, 6, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 5, 5, 21, 4, 5, 3, 3, 2, 2, 1, 1, 5, 6, 6, 3, 1, 4, 15, 8, 8, 8, 7, 7, 43, 13, 6, 6, 6, 4, 2, 2, 2, 2, 15, 4, 4, 4, 6, 6, 6, 4, 4, 4, 2, 12, 12, 8, 8, 4, 4, 4, 4, 17, 2, 2, 2, 2, 2, 2, 2, 7, 6, 1, 1, 1, 1, 179,3, 127,7, 6, 5, 8, 7, 4, 4, 4, 16, 4, 2, 8, 5, 5, 5, 92, 5, 5, 4, 10, 4, 2, 2, 2, 2, 3, 1, 6, 6, 6, 3, 3, 8, 2, 6, 6, 4, 1, 2, 2, 5, 2, 2, 2, 2, 3, 1, 12, 4, 2, 2, 2, 8, 1, 1, 1, 1, 6, 6, 6, 14, 5, 7, 7, 7, 4, 2, 2, 1, 1, 12, 4, 4, 6, 6, 6, 3, 3, 9, 9, 5, 4, 4, 1, 6, 2, 2, 2, 3, 3, 28, 3, 2, 2, 1, 7, 1, 1, 10, 7, 7, 7, 2, 1, 7, 5, 3, 1, 63, 10, 4, 3, 3, 2, 1, 28, 8, 8, 2, 18, 18, 12, 11, 11, 11, 13, 2, 1, 3, 7, 11, 6, 1, 1, 1, 3, 2, 2, 12, 4, 2, 1, 4, 1, 1, 1, 1, 1, 1, 76, 9, 2, 3, 6, 9, 4, 3, 2, 3, 11, 11, 1, 4, 29, 2, 18, 1, 1, 1, 1, 3, 3, 3, 3, 3, 1, 1, 3, 1, 1, 1, 1, 5, 2, 2, 2, 3, 3, 3, 4, 2, 2, 9, 7, 7, 1, 3, 96, 24, 6, 5, 7, 7, 7, 4, 2, 1, 3, 2, 2, 9, 5, 3, 2, 11, 2, 2, 1, 5, 1, 1, 2, 2, 4, 12, 8, 5, 5, 2, 2, 3, 10, 5, 5, 6, 5, 5, 5, 3, 8, 3, 3, 1, 1, 22, 5, 5, 4, 4, 4, 4, 1, 1, 4, 1, 2, 27, 7, 5, 2, 12, 1, 5, 4, 2, 3, 3, 3, 7, 7, 3, 12, 3, 1, 1, 1, 1, 1, 5, 2, 14, 6, 3, 3, 2, 2, 2, 85, 2, 1, 3, 3, 3, 39, 5, 3, 3, 8, 3, 5, 2, 3, 1, 1, 1, 1, 1, 6, 5, 5, 14, 10, 4, 20, 1, 19, 12, 6, 3, 3, 3, 4, 5, 1, 3, 4, 3, 14, 3, 4, 2, 3, 4, 3, 14, 9, 3, 4, 12, 2, 5, 4, 3, 2, 6, 1, 1, 1, 2, 39, 7, 2, 1, 15, 9, 2, 9, 4, 6, 4, 101,5, 2, 3, 2, 25, 2, 4, 6, 24, 4, 3, 5, 5, 4, 4, 6, 2, 8, 4, 9, 4, 1, 6, 3, 5, 4, 4, 3, 8, 8, 5, 2, 40, 2, 3, 4, 3, 2, 3, 12, 2, 7, 3, 1, 1, 1, 16, 2, 3, 3, 3, 5, 10, 4, 4, 4, 5, 2, 7, 3, 3, 22, 2, 2, 3, 1, 1, 7, 4, 35, 3, 1, 2, 2, 32, 2, 2, 1, 1, 7, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 8, 3, 1, 1, 2, 5, 1, 1, 2, 1, 1, 1, 14, 2, 6, 27, 25, 10, 10, 4, 1, 1, 6, 4, 1, 2, 1, 1, 1, 1, 14, 11, 2, 5, 2, 2, 57, 2, 6, 6, 2, 6, 24, 2, 8, 10, 3, 3, 3, 2, 6, 4, 4, 4, 4, 3, 6, 4, 2, 54, 4, 2, 3, 7, 4, 7, 3, 8, 2, 2, 1, 4, 11, 4, 2, 1, 1, 1, 2, 40, 20, 7, 9, 4, 5, 6, 4, 15, 2, 8, 3, 13, 1, 1, 9, 8, 2, 5, 3, 1, 1, 1, 76, 3, 2, 11, 1, 8, 8, 2, 5, 1, 25, 4, 3, 5, 3, 4, 2, 8, 18, 2, 4, 2, 2, 3, 1, 1, 2, 2, 2, 7, 7, 7, 6, 4, 1, 1, 1, 16, 2, 2, 10, 5, 2, 8, 3, 10, 2, 7, 3, 2, 6, 22, 2, 2, 4, 3, 2, 1, 1, 3, 54, 13, 13, 9, 4, 3, 39, 1, 3, 2, 3, 2, 2, 1, 7, 4, 1, 12, 4, 3, 14, 10, 4, 4, 2, 10, 8, 23, 7, 1, 3, 7, 4, 3, 2, 33, 8, 3, 2, 8, 5, 3, 2, 2, 14, 10, 5, 4, 3, 3, 32, 16, 4, 8, 8, 3, 6, 2, 2, 1, 2, 17, 14, 2, 11, 6, 6, 4, 5, 6, 3, 3, 2, 2, 2, 3, 3, 17, 3, 3, 3, 2, 2, 2, 2, 2, 218,7, 4, 3, 3, 1, 31, 4, 10, 6, 7, 3, 3, 2, 1, 8, 4, 1, 1, 1, 2, 1, 7, 5, 2, 1, 3, 1, 3, 3, 35, 5, 26, 11, 8, 11, 5, 14, 4, 1, 9, 2, 40, 3, 2, 4, 6, 3, 9, 5, 11, 2, 4, 3, 1, 4, 4, 3, 3, 24, 4, 2, 3, 4, 4, 4, 4, 6, 2, 3, 2, 2, 27, 25, 2, 1, 16, 6, 2, 4, 2, 2, 1, 1, 1, 1, 1, 1, 6, 2, 15, 6, 3, 3, 3, 2, 2, 3, 7, 2, 5, 2, 1, 1, 1, 4, 2, 12, 3, 4, 16, 3, 3, 3, 3, 3, 1, 1, 53, 9, 2, 1, 1, 1, 1, 8, 2, 1, 8, 3, 3, 2, 12, 3, 4, 3, 2, 2, 2, 1, 4, 40, 11, 4, 6, 4, 2, 7, 1, 4, 4, 3, 8, 2, 46, 8, 9, 3, 6, 2, 2, 3, 2, 3, 5, 4, 3, 7, 2, 21, 2, 4, 2, 6, 3, 1, 101,14, 3, 1, 1, 1, 2, 13, 3, 5, 11, 4, 2, 15, 3, 2, 4, 5, 7, 2, 5, 4, 6, 3, 3, 7, 2, 3, 3, 4, 1, 46, 3, 3, 3, 7, 3, 3, 2, 12, 3, 3, 3, 7, 3, 2, 1, 2, 2, 72, 6, 3, 2, 1, 1, 7, 3, 30, 5, 3, 9, 6, 4, 2, 4, 4, 6, 1, 1, 1, 5, 4, 7, 5, 4, 3, 17, 6, 4, 2, 1, 1, 4, 2, 2, 1, 10, 10, 1, 1, 2, 2, 18, 3, 6, 4, 10, 2, 1, 2, 2, 2, 2, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 37, 5, 5, 8, 2, 2, 4, 8, 1, 2, 3, 52, 2, 4, 1, 10, 4, 8, 9, 3, 7, 3, 2, 6, 8, 31, 3, 2, 6, 5, 9, 6, 2, 1, 1, 1, 63, 2, 36, 3, 13, 5, 6, 3, 1, 1, 10, 4, 25, 3, 2, 2, 3, 1, 14, 92, 3, 14, 4, 4, 3, 2, 2, 4, 1, 5, 3, 35, 6, 18, 2, 4, 3, 6, 4, 4, 2, 2, 6, 1, 3, 3, 6, 4, 1, 1, 1, 1, 1, 11, 4, 4, 55, 2, 5, 3, 3, 2, 3, 2, 3, 2, 1, 3, 3, 6, 3, 2, 6, 4, 11, 3, 5, 3, 2, 26, 4, 2, 1, 2, 8, 5, 4, 6, 3, 24, 9, 9, 4, 2, 1, 11, 7, 5, 1, 10, 3, 4, 3, 17, 3, 2, 28, 18, 1, 7, 4, 3, 2, 1, 1, 1, 4, 2, 2, 2, 5, 2, 3, 3, 3, 2, 2, 2, 1, 1, 1, 4, 7, 4, 2, 14, 14, 4, 3, 1, 1, 27, 8, 11, 8, 8, 6, 3, 32, 3, 2, 4, 3, 7, 2, 2, 2, 7, 2, 4, 2, 2, 5, 3, 79, 8, 3, 4, 1, 3, 4, 3, 2, 2, 38, 4, 9, 12, 8, 6, 4, 2, 4, 2, 4, 5, 55, 25, 13, 11, 8, 4, 4, 4, 7, 3, 4, 2, 10, 5, 3, 3, 119,11, 5, 3, 3, 2, 24, 6, 3, 2, 2, 2, 2, 6, 3, 21, 3, 7, 2, 10, 2, 2, 3, 16, 5, 6, 2, 1, 1, 14, 4, 2, 4, 5, 2, 11, 6, 57, 3, 3, 8, 7, 2, 1, 1, 19, 1, 5, 2, 2, 2, 2, 15, 7, 7, 3, 3, 6, 4, 39, 3, 1, 2, 22, 4, 7, 3, 4, 4, 47, 3, 7, 7, 5, 4, 24, 6, 4, 4, 3, 2, 6, 2, 2, 4, 2, 1, 4, 3, 2, 2, 1, 1, 5, 5, 5, 4, 3, 148,29, 3, 3, 5, 4, 3, 2, 2, 2, 45, 3, 3, 5, 2, 2, 16, 12, 1, 1, 1, 7, 2, 3, 1, 1, 2, 2, 37, 3, 5, 1, 1, 1, 5, 2, 19, 3, 1, 10, 5, 2, 4, 3, 2, 17, 1, 1, 1, 1, 3, 1, 3, 2, 7, 20, 2, 4, 2, 2, 6, 4, 2, 2, 2, 155,25, 4, 3, 3, 1, 7, 1, 4, 3, 1, 6, 22, 8, 2, 3, 4, 2, 3, 2, 1, 36, 9, 8, 4, 3, 1, 1, 1, 1, 4, 3, 2, 3, 6, 6, 6, 7, 6, 4, 2, 4, 3, 2, 55, 7, 3, 12, 11, 2, 2, 3, 5, 2, 14, 1, 5, 3, 16, 4, 4, 4, 4, 3, 1, 1, 5, 14, 4, 1, 1, 1, 15, 9, 4, 1, 1, 2, 1, 4, 3, 27, 4, 4, 6, 2, 3, 7, 3, 2, 2, 5, 3, 5, 18, 8, 1, 3, 4, 1, 1, 4, 3, 3, 30, 6, 3, 4, 3, 4, 3, 2, 5, 3, 1, 1, 7, 3, 3, 22, 2, 2, 3, 1, 1, 1, 3, 3, 2, 17, 2, 4, 22, 2, 2, 2, 3, 2, 9, 5, 2, 34, 3, 4, 1, 1, 1, 1, 5, 1, 7, 2, 2, 2, 2, 2, 1, 4, 35, 4, 2, 4, 1, 4, 3, 3, 2, 3, 7, 3, 1, 22, 2, 3, 4, 2, 1, 2, 4, 11, 1, 1, 2, 4, 1, 1, 1, 18, 13, 5, 1, 4, 1, 2, 3, 10, 8, 8, 16, 3, 2, 2, 6, 67, 3, 3, 3, 2, 2, 1, 1, 1, 4, 1, 2, 4, 2, 7, 3, 3, 6, 5, 2, 6, 2, 1, 1, 3, 9, 6, 1, 2, 2, 53, 4, 1, 17, 8, 3, 2, 5, 2, 4, 4, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 64, 8, 3, 2, 1, 7, 4, 32, 2, 25, 6, 8, 5, 3, 4, 1, 1, 3, 40, 7, 3, 4, 7, 4, 3, 1, 1, 3, 31, 4, 1, 2, 2, 2, 4, 5, 3, 1, 6, 2, 4, 10, 2, 2, 2, 8, 8, 1, 4, 2, 2, 2, 2, 1, 1, 188,7, 22, 4, 4, 9, 2, 2, 9, 5, 5, 2, 13, 1, 5, 4, 1, 1, 1, 3, 1, 11, 3, 5, 3, 3, 3, 4, 2, 21, 4, 3, 3, 7, 8, 1, 1, 23, 4, 3, 2, 5, 4, 5, 19, 1, 2, 8, 2, 3, 13, 4, 2, 10, 2, 1, 2, 5, 2, 2, 2, 14, 7, 2, 2, 2, 4, 1, 2, 3, 24, 8, 5, 2, 1, 3, 2, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 59, 11, 5, 3, 9, 6, 3, 2, 1, 1, 18, 1, 2, 11, 2, 2, 2, 10, 3, 4, 3, 2, 1, 64, 4, 12, 5, 3, 3, 12, 3, 2, 3, 2, 1, 1, 3, 10, 3, 2, 2, 2, 11, 1, 3, 3, 5, 1, 3, 2, 1, 37, 3, 6, 6, 2, 3, 1, 3, 7, 3, 2, 2, 2, 2, 1, 1, 43, 5, 2, 4, 2, 5, 2, 7, 2, 2, 4, 16, 5, 3, 2, 7, 3, 2, 2, 1, 5, 3, 2, 2, 1, 1, 1, 1, 36, 8, 2, 2, 5, 3, 6, 1, 1, 1, 6, 2, 2, 7, 4, 9, 27, 2, 7, 3, 5, 2, 4, 1, 1, 1, 28, 18, 2, 5, 3, 1, 2, 6, 19, 4, 2, 3, 7, 2, 4, 2, 108,21, 1, 2, 3, 7, 2, 3, 2, 1, 25, 2, 11, 7, 6, 3, 2, 1, 3, 11, 4, 4, 2, 2, 3, 4, 3, 17, 7, 3, 3, 2, 3, 4, 3, 2, 20, 3, 2, 3, 192,3, 10, 4, 2, 2, 63, 5, 2, 10, 4, 4, 2, 2, 6, 4, 4, 3, 4, 3, 6, 9, 3, 2, 9, 3, 3, 1, 8, 43, 9, 3, 2, 2, 4, 1, 3, 5, 2, 2, 3, 4, 2, 2, 3, 1, 2, 7, 5, 10, 4, 5, 4, 4, 3, 9, 2, 2, 12, 4, 4, 2, 2, 5, 2, 1, 1, 43, 3, 1, 8, 2, 1, 6, 2, 3, 6, 5, 4, 4, 2, 5, 2, 2, 91, 10, 1, 2, 3, 4, 3, 7, 7, 6, 1, 12, 4, 5, 3, 11, 6, 14, 4, 4, 2, 1, 2, 3, 3, 2, 86, 6, 6, 2, 1, 7, 4, 23, 8, 2, 2, 3, 3, 2, 4, 3, 10, 3, 2, 4, 14, 6, 3, 2, 9, 2, 2, 81, 4, 2, 11, 2, 2, 7, 4, 15, 6, 2, 12, 3, 2, 3, 6, 6, 4, 2, 13, 2, 5, 4, 2, 1, 1, 109,5, 10, 2, 8, 2, 1, 5, 2, 18, 18, 2, 3, 4, 4, 3, 2, 17, 7, 6, 6, 3, 1, 39, 5, 16, 2, 8, 13, 3, 2, 3, 5, 3, 1, 1, 1, 3, 55, 11, 5, 4, 3, 24, 4, 14, 2, 2, 10, 4, 9, 7, 50, 4, 3, 4, 3, 3, 4, 5, 6, 6, 3, 2, 5, 3, 5, 24, 4, 5, 26, 14, 11, 2, 4, 3, 100,3, 12, 5, 3, 4, 7, 3, 16, 4, 3, 2, 6, 11, 8, 3, 10, 9, 7, 4, 4, 11, 7, 3, 3, 19, 5, 3, 6, 3, 3, 26, 5, 4, 2, 5, 2, 6, 28, 7, 4, 1, 4, 4, 2, 3, 2, 38, 3, 5, 2, 1, 2, 4, 4, 4, 27, 6, 3, 3, 7, 4, 1, 1, 7, 1, 2, 60, 13, 7, 5, 1, 9, 4, 4, 10, 4, 3, 11, 4, 4, 4, 4, 4, 3, 3, 2, 2, 3, 5, 2, 20, 4, 6, 4, 1, 3, 69, 7, 2, 4, 3, 5, 2, 1, 6, 6, 2, 2, 2, 11, 1, 15, 2, 2, 2, 9, 3, 2, 2, 7, 4, 53, 9, 2, 3, 5, 2, 3, 13, 4, 3, 1, 4, 9, 7, 1, 3, 3, 62, 5, 3, 6, 3, 3, 7, 9, 3, 2, 6, 4, 8, 3, 6, 39, 26, 7, 6, 6, 5, 2, 2, 6, 4, 2, 3, 3, 20, 3, 5, 3, 2, 3, 3, 2, 2, 2, 1, 10, 3, 63, 4, 8, 2, 3, 3, 3, 2, 1, 2, 5, 2, 2, 6, 2, 2, 1, 4, 3, 2, 1, 13, 3, 3, 5, 3, 3, 1, 3, 2, 48, 2, 3, 3, 2, 1, 3, 5, 7, 7, 5, 5, 2, 1, 5, 15, 6, 6, 5, 3, 6, 2, 1, 58, 9, 4, 3, 28, 5, 6, 10, 2, 18, 6, 7, 3, 19, 3, 2, 5, 3, 3, 7, 4, 7, 4, 2, 2, 8, 1, 1, 5, 1, 1, 3, 2, 1, 1, 1, 18, 2, 5, 34, 5, 2, 18, 17, 10, 7, 6, 6, 5, 4, 2, 36, 2, 7, 5, 10, 7, 4, 3, 13, 4, 3, 2, 1, 2, 12, 5, 2, 2, 2, 40, 15, 6, 4, 2, 2, 2, 2, 7, 2, 1, 2, 5, 3, 3, 27, 21, 3, 4, 2, 4, 2, 3, 3, 5, 44, 3, 3, 33, 33, 3, 2, 1, 7, 2, 6, 2, 2, 2, 4, 2, 1, 13, 8, 2, 1, 41, 5, 2, 8, 2, 1, 1, 1, 1, 19, 12, 5, 3, 2, 44, 3, 9, 2, 9, 8, 7, 9, 2, 4, 157,4, 2, 7, 4, 53, 7, 7, 3, 2, 3, 2, 14, 2, 3, 5, 2, 2, 2, 2, 3, 9, 2, 23, 3, 6, 4, 3, 2, 16, 7, 1, 5, 2, 4, 3, 3, 3, 11, 2, 2, 1, 1, 1, 10, 7, 2, 7, 2, 2, 6, 1, 1, 2, 10, 8, 2, 35, 2, 1, 12, 4, 1, 1, 8, 4, 2, 11, 11, 2, 5, 5, 4, 4, 4, 2, 46, 5, 1, 3, 2, 4, 2, 4, 3, 5, 3, 15, 9, 7, 58, 4, 2, 2, 9, 8, 6, 2, 4, 2, 3, 3, 2, 2, 25, 7, 5, 11, 11, 2, 31, 18, 2, 8, 2, 2, 4, 8, 6, 5, 2, 21, 10, 5, 4, 2, 2, 5, 4, 4, 4, 3, 2, 22, 11, 8, 2, 2, 2, 3, 2, 1, 1, 5, 5, 4, 3, 22, 6, 6, 6, 22, 2, 5, 3, 5, 3, 3, 3, 3, 57, 15, 5, 2, 7, 4, 1, 30, 4, 7, 3, 4, 2, 2, 3, 1, 3, 12, 6, 4, 16, 3, 3, 2, 2, 2, 3, 2, 15, 1, 9, 3, 26, 4, 3, 4, 7, 2, 2, 2, 3, 2, 12, 4, 4, 13, 1, 3, 1, 5, 4, 7, 1, 1, 1, 2, 1, 16, 2, 9, 3, 2, 3, 16, 7, 2, 4, 3, 2, 1, 1, 1, 1, 82, 6, 5, 4, 15, 5, 3, 2, 1, 1, 4, 3, 46, 2, 3, 2, 1, 1, 2, 7, 2, 2, 1, 3, 3, 2, 3, 8, 2, 2, 3, 2, 2, 2, 3, 3, 5, 2, 53, 15, 2, 3, 4, 6, 5, 7, 11, 6, 15, 2, 2, 5, 2, 2, 1, 2, 2, 1, 82, 6, 3, 7, 33, 4, 19, 10, 3, 2, 5, 14, 4, 8, 4, 3, 28, 2, 3, 1, 9, 2, 5, 3, 2, 1, 21, 4, 3, 9, 67, 2, 11, 5, 3, 2, 12, 10, 3, 9, 7, 1, 5, 6, 2, 2, 4, 3, 3, 69, 5, 53, 3, 2, 2, 15, 3, 5, 3, 2, 10, 4, 2, 3, 10, 6, 4, 33, 3, 12, 3, 2, 3, 3, 6, 3, 8, 4, 3, 8, 4, 2, 1, 7, 16, 2, 5, 4, 4, 4, 77, 6, 4, 2, 5, 4, 1, 25, 5, 4, 4, 3, 25, 7, 2, 2, 4, 8, 3, 2, 2, 19, 3, 4, 1, 3, 1, 1, 1, 3, 3, 24, 4, 3, 3, 4, 1, 1, 3, 2, 3, 2, 2, 3, 4, 1, 2, 5, 7, 2, 1, 1, 2, 5, 8, 3, 3, 16, 5, 3, 2, 8, 1, 8, 4, 2, 3, 26, 9, 5, 5, 2, 2, 2, 8, 2, 49, 7, 8, 7, 20, 2, 2, 5, 1, 2, 2, 3, 2, 2, 6, 3, 2, 13, 4, 4, 1, 2, 19, 2, 4, 4, 2, 18, 2, 3, 2, 7, 21, 5, 3, 5, 2, 3, 1, 1, 2, 6, 2, 3, 3, 1, 6, 2, 83, 3, 2, 12, 6, 3, 3, 6, 2, 2, 32, 4, 4, 8, 3, 6, 5, 3, 3, 4, 2, 1, 1, 5, 11, 2, 1, 3, 2, 1, 1, 1, 3, 6, 17, 2, 9, 3, 3, 158,17, 4, 3, 1, 1, 2, 7, 2, 3, 2, 9, 4, 2, 3, 3, 2, 1, 9, 2, 1, 1, 5, 2, 2, 2, 4, 3, 3, 3, 3, 3, 3, 5, 1, 1, 1, 1, 1, 1, 1, 27, 4, 5, 10, 2, 2, 2, 2, 2, 6, 1, 4, 3, 18, 4, 2, 7, 1, 3, 1, 30, 10, 3, 2, 3, 2, 7, 2, 4, 3, 2, 8, 4, 1, 1, 1, 3, 5, 3, 4, 2, 2, 3, 2, 2, 1, 5, 2, 1, 1, 1, 1, 1, 91, 12, 1, 1, 1, 1, 3, 3, 2, 3, 2, 15, 1, 4, 4, 16, 2, 2, 6, 2, 1, 1, 1, 9, 4, 4, 3, 2, 6, 3, 11, 2, 4, 2, 16, 1, 2, 2, 6, 4, 3, 8, 6, 3, 111,16, 5, 3, 2, 3, 2, 3, 5, 5, 4, 4, 11, 2, 9, 5, 18, 13, 3, 1, 5, 3, 3, 29, 5, 4, 2, 8, 2, 5, 2, 8, 7, 5, 4, 4, 3, 2, 3, 1, 4, 2, 1, 3, 42, 18, 2, 4, 4, 1, 4, 2, 3, 5, 3, 2, 7, 5, 3, 79, 6, 2, 18, 2, 2, 2, 4, 25, 4, 1, 3, 4, 3, 5, 4, 1, 1, 2, 12, 3, 3, 2, 27, 2, 2, 10, 2, 5, 4, 2, 3, 9, 9, 2, 3, 29, 4, 2, 13, 8, 7, 149,23, 1, 3, 2, 2, 2, 3, 4, 2, 1, 1, 2, 1, 3, 2, 6, 5, 4, 1, 2, 13, 3, 3, 1, 5, 1, 1, 4, 4, 4, 4, 4, 1, 21, 4, 2, 6, 3, 5, 2, 2, 2, 32, 6, 4, 8, 3, 4, 10, 3, 1, 4, 2, 1, 1, 1, 1, 9, 4, 1, 1, 3, 9, 2, 3, 2, 21, 5, 8, 4, 2, 20, 11, 9, 2, 16, 3, 3, 8, 3, 30, 6, 4, 10, 4, 4, 3, 6, 2, 2, 1, 10, 4, 4, 4, 4, 4, 3, 32, 7, 5, 5, 5, 5, 3, 10, 7, 3, 2, 2, 37, 4, 6, 4, 4, 8, 7, 7, 7, 2, 2, 8, 8, 8, 8, 8, 2, 1, 1, 2, 2, 8, 3, 30, 3, 9, 2, 10, 5, 7, 1, 1, 7, 3, 21, 6, 2, 5, 1, 1, 3, 2, 1, 22, 6, 3, 5, 2, 1, 20, 5, 4, 6, 6, 6, 6, 6, 6, 3, 2, 2, 2, 2, 2, 2, 4, 1, 12, 3, 3, 3, 2, 11, 3, 24, 2, 3, 1, 7, 2, 9, 2, 3, 2, 69, 3, 3, 2, 15, 12, 5, 21, 5, 7, 6, 15, 4, 2, 6, 3, 1, 7, 3, 6, 3, 3, 1, 14, 2, 4, 3, 49, 14, 12, 7, 7, 4, 9, 4, 1, 1, 6, 8, 3, 2, 7, 4, 3, 3, 3, 2, 2, 1, 95, 15, 9, 2, 2, 6, 10, 4, 3, 1, 5, 24, 7, 10, 7, 1, 10, 5, 2, 2, 2, 2, 2, 57, 17, 8, 8, 6, 3, 9, 3, 4, 1, 1, 3, 31, 5, 2, 2, 11, 5, 4, 6, 9, 4, 4, 2, 6, 3, 19, 4, 5, 3, 2, 13, 13, 7, 3, 27, 3, 2, 10, 6, 2, 35, 2, 2, 6, 2, 2, 14, 7, 5, 3, 3, 2, 2, 3, 2, 1, 1, 72, 3, 15, 6, 5, 6, 4, 3, 34, 4, 3, 23, 2, 1, 1, 7, 6, 6, 3, 5, 4, 2, 9, 5, 5, 3, 2, 2, 24, 6, 2, 2, 2, 2, 8, 2, 2, 8, 141,4, 15, 4, 3, 4, 2, 2, 2, 4, 2, 9, 1, 5, 2, 7, 4, 8, 2, 2, 6, 2, 2, 2, 2, 2, 41, 16, 4, 1, 1, 1, 4, 11, 3, 2, 2, 9, 2, 36, 23, 20, 3, 3, 3, 3, 6, 5, 1, 1, 1, 1, 4, 4, 4, 194,8, 1, 1, 9, 9, 2, 2, 3, 2, 2, 22, 10, 5, 5, 5, 3, 3, 43, 21, 5, 5, 5, 5, 5, 4, 3, 15, 4, 6, 2, 11, 4, 3, 3, 7, 5, 2, 2, 13, 10, 2, 54, 13, 6, 13, 8, 4, 11, 2, 5, 10, 4, 3, 7, 51, 3, 4, 1, 4, 9, 1, 3, 13, 3, 6, 4, 1, 2, 4, 2, 2, 35, 15, 11, 4, 2, 2, 2, 2, 2, 76, 5, 10, 7, 2, 9, 4, 2, 2, 7, 2, 6, 4, 12, 3, 1, 6, 3, 4, 2, 4, 2, 1, 1, 37, 4, 2, 2, 2, 4, 4, 1, 4, 4, 4, 1, 15, 3, 2, 3, 6, 2, 2, 2, 39, 4, 2, 3, 2, 11, 4, 2, 8, 2, 2, 2, 2, 2, 1, 4, 2, 2, 3, 3, 3, 19, 4, 5, 3, 7, 2, 2, 3, 3, 3, 249,79, 3, 3, 2, 2, 12, 10, 3, 5, 1, 12, 3, 3, 3, 26, 6, 9, 3, 2, 2, 5, 2, 1, 1, 6, 2, 3, 3, 1, 2, 2, 2, 2, 53, 15, 2, 4, 1, 1, 15, 3, 2, 2, 6, 7, 5, 4, 7, 8, 3, 1, 1, 51, 11, 9, 3, 2, 7, 9, 5, 2, 2, 2, 9, 3, 3, 2, 3, 29, 10, 2, 7, 3, 3, 2, 6, 3, 2, 1, 34, 2, 3, 2, 2, 13, 13, 4, 4, 3, 7, 3, 3, 3, 3, 38, 9, 5, 3, 2, 3, 2, 2, 3, 1, 17, 8, 4, 4, 4, 3, 3, 3, 6, 5, 5, 1, 33, 3, 3, 3, 20, 2, 1, 1, 7, 3, 3, 1, 2, 2, 1, 1, 4, 2, 2, 2, 6, 3, 24, 16, 4, 7, 6, 4, 2, 6, 2, 3, 3, 1, 107,20, 4, 9, 5, 3, 3, 8, 3, 1, 3, 19, 1, 7, 4, 3, 1, 1, 2, 7, 4, 2, 13, 5, 1, 6, 4, 4, 13, 1, 3, 2, 2, 2, 2, 4, 18, 9, 3, 4, 2, 2, 13, 3, 3, 6, 3, 10, 5, 1, 2, 2, 20, 6, 2, 2, 2, 1, 9, 6, 1, 2, 2, 2, 81, 7, 39, 5, 2, 1, 1, 5, 3, 2, 7, 4, 3, 11, 3, 3, 5, 2, 2, 29, 2, 2, 2, 2, 16, 8, 2, 2, 2, 2, 1, 3, 39, 19, 1, 3, 6, 3, 2, 15, 3, 3, 2, 2, 2, 47, 16, 5, 2, 3, 1, 1, 11, 2, 7, 7, 3, 5, 2, 3, 3, 12, 3, 3, 8, 3, 3, 52, 12, 2, 2, 2, 2, 26, 5, 8, 4, 4, 4, 3, 10, 4, 3, 3, 3, 4, 1, 16, 3, 2, 3, 10, 3, 5, 2, 1, 22, 22, 7, 3, 2, 4, 2, 10, 2, 3, 3, 12, 4, 5, 1, 1, 2, 11, 4, 2, 1, 3, 2, 1, 3, 2, 6, 2, 2, 2, 2, 5, 3, 13, 4, 2, 5, 3, 2, 2, 2, 1, 1, 62, 3, 3, 9, 4, 4, 6, 16, 3, 6, 5, 5, 2, 2, 2, 2, 1, 3, 9, 5, 5, 4, 8, 2, 1, 2, 18, 11, 7, 5, 5, 3, 4, 1, 66, 5, 3, 1, 5, 4, 3, 10, 2, 2, 2, 4, 31, 4, 16, 9, 4, 4, 1, 2, 5, 1, 1, 1, 1, 5, 2, 2, 2, 26, 3, 4, 3, 6, 2, 2, 23, 13, 1, 1, 1, 1, 2, 2, 3, 1, 15, 3, 5, 5, 31, 3, 12, 4, 8, 3, 3, 1, 2, 122,12, 10, 2, 5, 3, 4, 4, 2, 4, 8, 5, 5, 3, 2, 19, 2, 4, 6, 5, 4, 26, 4, 6, 2, 8, 3, 7, 3, 17, 6, 2, 6, 7, 3, 2, 5, 4, 28, 6, 3, 3, 4, 3, 14, 11, 2, 2, 6, 113,8, 2, 2, 3, 2, 29, 11, 4, 2, 2, 8, 6, 7, 2, 7, 4, 3, 1, 1, 7, 3, 2, 8, 2, 2, 2, 7, 1, 20, 5, 5, 5, 4, 3, 2, 1, 2, 2, 1, 134,6, 30, 3, 2, 6, 3, 5, 4, 4, 2, 2, 6, 7, 6, 5, 5, 4, 15, 4, 2, 2, 2, 2, 6, 11, 3, 3, 3, 3, 4, 24, 19, 3, 3, 3, 7, 2, 2, 8, 3, 3, 59, 4, 2, 5, 1, 2, 3, 2, 2, 2, 2, 1, 1, 2, 2, 1, 28, 3, 10, 3, 5, 2, 9, 5, 5, 3, 1, 70, 17, 2, 2, 2, 6, 3, 1, 1, 1, 1, 6, 6, 4, 4, 2, 2, 2, 8, 2, 3, 3, 16, 2, 3, 6, 4, 1, 1, 3, 2, 2, 2, 6, 2, 2, 1, 1, 1, 1, 3, 2, 2, 2, 2, 4, 8, 2, 5, 2, 3, 3, 3, 3, 6, 6, 6, 4, 1, 1, 1, 8, 2, 101,5, 3, 59, 26, 11, 11, 10, 3, 2, 23, 3, 19, 3, 3, 1, 4, 5, 2, 16, 8, 4, 16, 16, 1, 2, 2, 4, 2, 2, 2, 2, 42, 12, 2, 2, 7, 7, 2, 2, 6, 5, 3, 2, 2, 3, 2, 3, 5, 2, 2, 2, 2, 18, 1, 5, 1, 4, 4, 3, 2, 30, 2, 2, 7, 7, 4, 4, 1, 3, 3, 3, 5, 5, 6, 6, 4, 2, 7, 2, 2, 2, 2, 4, 3, 37, 3, 17, 4, 3, 5, 2, 4, 2, 2, 2, 3, 162,14, 2, 6, 2, 8, 2, 3, 2, 7, 3, 2, 10, 4, 3, 5, 5, 11, 7, 4, 8, 3, 73, 8, 3, 18, 6, 2, 5, 1, 1, 2, 2, 28, 2, 3, 3, 4, 5, 5, 3, 1, 5, 4, 8, 3, 2, 1, 1, 1, 1, 3, 2, 2, 5, 2, 1, 1, 12, 5, 2, 155,9, 4, 2, 9, 4, 2, 2, 18, 6, 2, 1, 1, 1, 21, 2, 7, 10, 29, 3, 2, 8, 2, 7, 1, 4, 2, 4, 15, 4, 1, 2, 8, 3, 10, 4, 2, 6, 2, 10, 7, 3, 4, 4, 1, 2, 41, 18, 4, 2, 4, 2, 2, 3, 1, 1, 1, 1, 7, 3, 3, 4, 3, 138,1, 19, 6, 2, 2, 2, 4, 4, 3, 1, 1, 1, 1, 6, 2, 2, 5, 1, 1, 1, 1, 1, 4, 2, 2, 1, 1, 1, 1, 5, 3, 2, 2, 2, 2, 2, 9, 2, 7, 3, 3, 3, 6, 3, 3, 2, 2, 2, 2, 1, 11, 4, 1, 8, 6, 2, 2, 2, 4, 4, 4, 4, 4, 4, 2, 2, 4, 3, 3, 1, 1, 2, 1, 1, 1, 1, 3, 2, 6, 5, 2, 2, 2, 2, 2, 24, 13, 3, 5, 5, 3, 2, 2, 3, 2, 26, 6, 5, 4, 2, 2, 10, 5, 5, 4, 4, 4, 4, 4, 4, 3, 3, 3, 44, 2, 14, 14, 8, 5, 2, 1, 1, 3, 2, 9, 2, 7, 2, 9, 10, 8, 3, 2, 1, 2, 10, 4, 1, 4, 2, 3, 1, 1, 8, 5, 5, 4, 4, 4, 4, 3, 10, 16, 2, 2, 5, 5, 4, 3, 4, 2, 19, 2, 1, 2, 1, 1, 10, 2, 2, 1, 1, 1, 1, 1, 1, 1, 40, 1, 1, 20, 6, 5, 4, 4, 6, 6, 11, 3, 4, 3, 2, 2, 2, 2, 15, 2, 2, 3, 2, 3, 9, 7, 9, 3, 3, 25, 9, 4, 2, 1, 4, 15, 2, 9, 2, 5, 4, 4, 3, 1, 4, 2, 2, 79, 8, 3, 12, 4, 2, 2, 17, 3, 3, 3, 8, 6, 3, 8, 7, 5, 19, 8, 2, 3, 1, 3, 5, 3, 2, 25, 8, 8, 8, 7, 4, 1, 2, 249,15, 2, 7, 9, 2, 1, 3, 14, 5, 5, 2, 17, 2, 3, 9, 1, 2, 1, 3, 3, 3, 21, 6, 3, 8, 4, 1, 17, 2, 4, 10, 6, 6, 6, 4, 14, 4, 2, 2, 2, 1, 3, 3, 3, 4, 3, 17, 2, 2, 3, 3, 6, 4, 2, 2, 95, 23, 5, 6, 2, 2, 9, 3, 1, 1, 34, 1, 1, 1, 2, 2, 5, 3, 2, 1, 1, 8, 3, 3, 5, 1, 1, 2, 2, 2, 2, 2, 4, 7, 3, 8, 1, 1, 4, 5, 2, 3, 2, 3, 3, 3, 3, 2, 59, 5, 4, 5, 2, 18, 2, 4, 3, 4, 4, 3, 3, 15, 8, 2, 6, 4, 10, 3, 218,29, 8, 2, 6, 2, 2, 2, 6, 2, 2, 37, 2, 9, 4, 2, 5, 3, 5, 2, 21, 8, 6, 8, 6, 2, 2, 2, 2, 1, 2, 3, 18, 3, 2, 3, 2, 7, 6, 7, 5, 5, 4, 2, 12, 2, 6, 8, 8, 3, 3, 1, 1, 23, 4, 14, 10, 3, 3, 4, 30, 7, 5, 7, 2, 2, 3, 3, 7, 2, 3, 2, 49, 2, 8, 5, 2, 9, 3, 4, 4, 3, 6, 6, 3, 3, 3, 6, 3, 5, 2, 76, 2, 5, 22, 1, 1, 1, 18, 18, 7, 6, 6, 6, 3, 2, 15, 10, 8, 8, 14, 9, 3, 2, 11, 3, 3, 3, 69, 10, 5, 4, 1, 20, 15, 6, 5, 5, 1, 9, 6, 14, 8, 3, 1, 11, 9, 1, 14, 8, 6, 15, 6, 3, 3, 2, 12, 5, 2, 10, 2, 6, 3, 1, 2, 1, 1, 9, 4, 3, 1, 1, 1, 21, 10, 4, 2, 8, 2, 1, 2, 2, 11, 4, 4, 4, 1, 1, 1, 1, 1, 11, 3, 3, 2, 2, 1, 12, 11, 3, 1, 8, 6, 5, 2, 2, 2, 5, 1, 1, 2, 3, 3, 2, 1, 1, 80, 3, 7, 3, 2, 4, 5, 3, 1, 2, 3, 29, 5, 11, 7, 11, 7, 2, 2, 2, 13, 13, 2, 1, 7, 7, 3, 2, 1, 6, 3, 2, 2, 22, 5, 8, 2, 3, 66, 3, 10, 5, 14, 3, 2, 1, 1, 1, 6, 6, 2, 1, 6, 2, 8, 2, 77, 15, 3, 1, 4, 6, 5, 3, 16, 11, 10, 5, 3, 3, 2, 3, 3, 7, 7, 15, 3, 7, 3, 5, 85, 4, 13, 5, 2, 3, 2, 5, 2, 2, 6, 3, 14, 11, 7, 4, 4, 2, 10, 5, 4, 3, 19, 12, 6, 6, 1, 1, 33, 9, 7, 2, 5, 2, 2, 1, 5, 1, 4, 1, 1, 5, 4, 3, 2, 1, 1, 1, 1, 1, 72, 2, 1, 1, 4, 3, 1, 3, 1, 1, 6, 3, 3, 1, 1, 1, 20, 2, 5, 2, 6, 3, 15, 2, 1, 1, 1, 1, 11, 4, 4, 3, 7, 3, 7, 6, 1, 1, 1, 4, 1, 1, 3, 7, 2, 8, 2, 2, 2, 3, 14, 5, 3, 2, 2, 4, 2, 1, 16, 5, 3, 15, 3, 2, 3, 2, 23, 14, 3, 2, 2, 2, 3, 3, 1, 1, 1, 1, 10, 2, 1, 2, 5, 17, 3, 8, 2, 2, 36, 9, 4, 2, 3, 6, 6, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 23, 2, 1, 1, 1, 1, 5, 5, 3, 2, 9, 3, 3, 2, 44, 5, 2, 12, 7, 7, 5, 4, 1, 1, 6, 3, 2, 10, 3, 2, 7, 5, 2, 2, 33, 5, 4, 3, 1, 1, 1, 2, 3, 2, 5, 9, 3, 54, 12, 8, 3, 3, 1, 5, 11, 10, 9, 4, 12, 7, 7, 4, 2, 3, 3, 6, 6, 3, 3, 1, 3, 20, 1, 5, 4, 3, 5, 5, 4, 3, 3, 32, 15, 8, 5, 3, 2, 7, 5, 5, 5, 3, 1, 1, 3, 1, 1, 1, 10, 6, 5, 2, 3, 2, 1, 1, 6, 5, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 31, 7, 2, 2, 2, 2, 2, 8, 1, 1, 1, 2, 6, 7, 1, 3, 1, 1, 20, 4, 4, 4, 8, 3, 2, 4, 4, 1, 1, 1, 2, 1, 1, 1, 8, 3, 32, 19, 4, 3, 7, 7, 1, 3, 4, 3, 2, 1, 29, 5, 5, 4, 4, 10, 5, 5, 4, 3, 7, 7, 7, 7, 6, 3, 4, 2, 43, 7, 3, 3, 13, 13, 12, 12, 8, 3, 3, 4, 4, 1, 6, 4, 3, 1, 1, 1, 23, 2, 13, 5, 2, 3, 13, 8, 1, 3, 1, 1, 1, 4, 4, 2, 6, 3, 1, 24, 2, 4, 1, 6, 4, 20, 3, 6, 2, 1, 8, 26, 7, 6, 6, 2, 2, 8, 2, 2, 4, 4, 4, 4, 39, 4, 2, 2, 2, 2, 1, 1, 1, 2, 2, 2, 2, 7, 3, 3, 2, 12, 11, 3, 3, 3, 2, 1, 4, 2, 2, 2, 6, 4, 3, 3, 30, 4, 17, 9, 3, 1, 1, 9, 4, 84, 13, 4, 4, 2, 3, 8, 2, 2, 6, 6, 3, 3, 2, 2, 2, 5, 4, 2, 7, 2, 3, 2, 5, 4, 4, 2, 3, 2, 2, 22, 2, 1, 3, 5, 4, 4, 4, 2, 2, 4, 3, 3, 2, 2, 4, 3, 9, 1, 3, 3, 5, 9, 4, 4, 3, 91, 2, 1, 1, 10, 2, 2, 16, 3, 2, 10, 10, 32, 9, 8, 2, 3, 3, 3, 4, 4, 28, 1, 23, 8, 6, 4, 2, 48, 4, 2, 8, 2, 2, 4, 4, 2, 1, 1, 1, 1, 1, 1, 3, 5, 7, 2, 4, 2, 4, 1, 4, 3, 1, 1, 2, 1, 1, 1, 39, 3, 6, 29, 28, 3, 2, 1, 2, 1, 3, 2, 5, 5, 5, 1, 4, 2, 1, 17, 4, 5, 6, 4, 2, 13, 3, 4, 10, 3, 1, 1, 2, 25, 10, 4, 2, 3, 2, 1, 1, 2, 8, 7, 3, 2, 21, 9, 7, 1, 5, 2, 20, 8, 2, 5, 2, 1, 1, 2, 2, 2, 4, 2, 3, 5, 2, 1, 2, 1, 6, 6, 5, 4, 4, 21, 8, 6, 6, 5, 4, 2, 6, 2, 2, 2, 9, 9, 6, 3, 1, 9, 3, 2, 2, 2, 6, 2, 2, 24, 4, 4, 5, 3, 3, 4, 18, 5, 9, 9, 6, 23, 1, 1, 4, 1, 1, 2, 1, 1, 4, 2, 1, 1, 1, 3, 2, 4, 2, 2, 2, 1, 1, 1, 1, 1, 20, 5, 7, 6, 5, 4, 3, 2, 3, 106,5, 6, 3, 3, 3, 20, 2, 2, 4, 3, 6, 2, 2, 2, 4, 4, 14, 12, 4, 6, 4, 5, 3, 3, 21, 19, 2, 5, 2, 3, 3, 2, 5, 2, 2, 22, 9, 2, 2, 8, 2, 2, 5, 2, 53, 6, 3, 8, 3, 3, 3, 10, 6, 6, 4, 2, 2, 13, 6, 5, 4, 3, 3, 2, 2, 6, 1, 1, 1, 1, 1, 1, 1, 11, 2, 2, 1, 5, 26, 5, 2, 9, 8, 5, 3, 6, 18, 6, 2, 1, 5, 4, 1, 3, 1, 1, 1, 117,4, 4, 3, 7, 5, 2, 6, 3, 3, 5, 3, 2, 2, 2, 14, 8, 1, 1, 2, 2, 18, 1, 3, 5, 3, 1, 1, 2, 14, 2, 1, 7, 6, 3, 3, 5, 4, 26, 2, 2, 5, 5, 4, 4, 4, 3, 5, 7, 3, 1, 2, 1, 1, 1, 40, 4, 1, 1, 1, 1, 1, 1, 2, 3, 1, 5, 1, 3, 7, 3, 6, 4, 4, 2, 2, 2, 2, 1, 1, 31, 4, 2, 2, 2, 2, 3, 2, 2, 2, 3, 1, 5, 3, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 5, 3, 3, 3, 1, 1, 26, 13, 3, 3, 3, 9, 3, 2, 121,7, 4, 1, 29, 2, 1, 15, 2, 1, 1, 3, 4, 3, 5, 3, 11, 11, 11, 11, 8, 11, 11, 6, 4, 4, 1, 1, 2, 11, 1, 1, 3, 4, 3, 37, 7, 4, 4, 3, 3, 17, 10, 10, 5, 7, 7, 1, 11, 3, 3, 3, 3, 3, 4, 3, 2, 2, 55, 6, 1, 1, 1, 23, 4, 2, 2, 1, 1, 1, 1, 17, 9, 6, 7, 4, 3, 6, 4, 4, 1, 1, 1, 1, 2, 2, 2, 4, 1, 1, 4, 1, 4, 4, 3, 6, 4, 2, 52, 4, 7, 13, 1, 1, 2, 2, 4, 13, 6, 2, 1, 1, 1, 2, 2, 2, 2, 17, 1, 1, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 5, 2, 2, 2, 2, 54, 2, 1, 45, 22, 12, 9, 10, 9, 5, 4, 4, 2, 4, 4, 3, 2, 1, 23, 5, 2, 1, 7, 6, 3, 4, 2, 35, 10, 1, 1, 2, 3, 2, 6, 6, 5, 4, 3, 7, 2, 4, 1, 10, 1, 5, 2, 2, 43, 11, 9, 3, 1, 3, 2, 2, 6, 3, 1, 1, 5, 2, 1, 1, 1, 1, 1, 8, 3, 124,12, 4, 1, 1, 20, 6, 3, 2, 2, 2, 1, 16, 1, 1, 1, 3, 1, 1, 2, 34, 3, 26, 6, 4, 14, 10, 4, 10, 7, 2, 2, 6, 2, 2, 7, 2, 1, 3, 2, 2, 1, 1, 5, 5, 5, 4, 30, 9, 2, 4, 10, 4, 4, 4, 5, 3, 3, 1, 3, 1, 59, 3, 5, 3, 15, 3, 2, 3, 2, 7, 2, 6, 4, 4, 2, 2, 6, 1, 3, 2, 4, 2, 2, 2, 2, 35, 3, 4, 1, 1, 19, 13, 9, 7, 5, 3, 95, 3, 1, 34, 14, 1, 3, 2, 18, 18, 14, 8, 4, 3, 2, 1, 1, 1, 1, 7, 3, 3, 3, 3, 2, 25, 5, 2, 2, 2, 2, 15, 5, 5, 5, 5, 4, 7, 4, 4, 4, 4, 4, 2, 15, 13, 6, 3, 1, 1, 1, 3, 3, 224,1, 1, 1, 18, 1, 4, 6, 4, 9, 6, 9, 7, 4, 4, 4, 4, 4, 24, 2, 9, 7, 6, 5, 5, 3, 17, 4, 1, 2, 2, 24, 21, 17, 15, 6, 8, 4, 4, 4, 3, 2, 27, 5, 5, 5, 2, 10, 9, 9, 7, 2, 6, 1, 8, 2, 4, 10, 11, 4, 2, 2, 4, 2, 11, 2, 3, 7, 7, 5, 4, 8, 5, 10, 2, 1, 1, 4, 19, 4, 3, 5, 4, 2, 2, 1, 2, 2, 2, 3, 2, 2, 4, 2, 2, 2, 2, 89, 3, 7, 5, 4, 1, 1, 4, 9, 1, 1, 1, 1, 1, 3, 4, 5, 2, 3, 6, 2, 2, 2, 4, 3, 2, 3, 1, 30, 16, 3, 1, 1, 1, 12, 2, 2, 2, 2, 10, 2, 6, 1, 1, 2, 1, 1, 1, 1, 4, 4, 4, 42, 2, 3, 10, 7, 5, 2, 1, 1, 2, 1, 22, 3, 9, 3, 4, 127,3, 3, 3, 1, 1, 1, 3, 3, 2, 5, 5, 4, 5, 2, 3, 3, 4, 95, 2, 1, 6, 2, 4, 3, 7, 5, 3, 3, 6, 5, 4, 1, 4, 3, 2, 19, 7, 3, 3, 4, 6, 2, 2, 2, 2, 2, 1, 1, 4, 1, 2, 8, 6, 4, 4, 10, 3, 1, 2, 2, 3, 2, 1, 6, 2, 2, 2, 162,1, 9, 4, 3, 146,142,5, 2, 1, 1, 10, 3, 2, 14, 3, 2, 2, 8, 5, 2, 2, 2, 1, 1, 1, 1, 10, 5, 3, 3, 2, 2, 1, 1, 4, 2, 2, 1, 9, 6, 6, 3, 2, 2, 1, 1, 11, 3, 2, 7, 3, 8, 3, 3, 2, 1, 1, 1, 1, 1, 11, 4, 3, 3, 20, 5, 1, 1, 2, 2, 2, 9, 4, 2, 2, 15, 2, 4, 3, 2, 5, 5, 9, 2, 5, 5, 5, 1, 1, 1, 4, 2, 28, 1, 1, 1, 7, 5, 11, 7, 9, 1, 1, 1, 2, 2, 2, 1, 10, 5, 2, 16, 2, 2, 3, 2, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 51, 11, 6, 3, 1, 1, 11, 6, 5, 17, 5, 7, 29, 10, 3, 1, 4, 3, 6, 3, 3, 3, 24, 5, 4, 11, 5, 4, 3, 1, 3, 39, 3, 23, 2, 2, 2, 12, 4, 3, 3, 5, 3, 7, 3, 3, 3, 3, 93, 17, 4, 3, 2, 2, 2, 16, 2, 4, 1, 2, 2, 1, 1, 6, 4, 9, 5, 2, 21, 3, 2, 6, 4, 4, 11, 7, 3, 3, 1, 1, 2, 3, 20, 5, 4, 2, 1, 8, 3, 106,8, 2, 3, 2, 9, 3, 3, 2, 2, 2, 15, 4, 1, 4, 11, 4, 9, 1, 1, 1, 1, 1, 2, 1, 1, 9, 8, 6, 3, 5, 3, 3, 1, 25, 4, 3, 4, 3, 4, 4, 4, 2, 10, 8, 2, 2, 46, 16, 2, 1, 1, 1, 3, 2, 1, 15, 2, 11, 2, 1, 3, 2, 6, 6, 4, 2, 3, 3, 248,56, 2, 3, 3, 11, 5, 5, 2, 2, 7, 1, 4, 10, 4, 3, 1, 1, 4, 3, 3, 1, 4, 2, 1, 1, 1, 1, 1, 1, 1, 5, 1, 3, 2, 2, 2, 8, 3, 4, 2, 11, 5, 15, 9, 9, 15, 10, 3, 2, 2, 3, 24, 8, 2, 7, 5, 2, 13, 4, 4, 2, 2, 2, 2, 4, 11, 7, 2, 2, 2, 7, 1, 25, 7, 3, 8, 2, 5, 6, 47, 2, 4, 1, 22, 12, 7, 6, 6, 3, 3, 6, 6, 6, 4, 2, 114,2, 17, 5, 2, 6, 4, 3, 3, 5, 4, 6, 6, 5, 3, 51, 3, 8, 4, 4, 1, 9, 9, 4, 4, 3, 10, 8, 8, 5, 21, 6, 2, 2, 3, 3, 4, 2, 1, 117,5, 10, 10, 6, 1, 16, 5, 4, 5, 3, 13, 6, 10, 5, 5, 3, 2, 3, 28, 15, 2, 7, 4, 4, 12, 6, 6, 20, 9, 5, 6, 3, 42, 32, 5, 10, 6, 1, 5, 2, 29, 2, 1, 9, 4, 5, 10, 5, 4, 4, 17, 2, 8, 3, 3, 2, 5, 4, 33, 4, 2, 2, 2, 4, 2, 3, 3, 4, 1, 1, 65, 13, 6, 6, 4, 6, 4, 23, 11, 4, 6, 2, 3, 4, 28, 3, 3, 4, 2, 2, 2, 10, 4, 1, 3, 5, 4, 1, 1, 2, 32, 3, 5, 2, 6, 3, 3, 7, 3, 3, 2, 2, 8, 5, 36, 5, 1, 6, 8, 3, 5, 3, 3, 3, 12, 1, 5, 1, 2, 2, 3, 1, 13, 2, 2, 2, 3, 2, 61, 2, 3, 2, 3, 4, 22, 13, 4, 4, 3, 2, 4, 2, 8, 3, 1, 9, 2, 7, 6, 2, 2, 2, 1, 1, 133,15, 8, 1, 4, 14, 2, 1, 8, 6, 4, 3, 3, 2, 2, 9, 2, 6, 2, 5, 3, 2, 8, 2, 1, 1, 1, 1, 36, 3, 1, 1, 9, 4, 12, 4, 1, 1, 1, 8, 2, 10, 1, 6, 4, 4, 2, 18, 7, 3, 1, 1, 2, 2, 2, 3, 5, 4, 4, 4, 23, 14, 10, 8, 2, 10, 4, 2, 21, 10, 4, 4, 4, 3, 2, 3, 4, 16, 3, 22, 9, 8, 8, 9, 9, 9, 4, 3, 2, 2, 27, 2, 5, 5, 5, 1, 2, 2, 2, 2, 4, 4, 4, 4, 10, 4, 4, 7, 2, 13, 4, 3, 6, 2, 2, 4, 1, 3, 10, 3, 1, 14, 9, 9, 5, 5, 5, 4, 4, 1, 33, 3, 9, 4, 11, 5, 7, 3, 51, 2, 7, 5, 1, 1, 2, 11, 4, 4, 1, 1, 1, 1, 16, 16, 9, 7, 4, 3, 4, 2, 2, 5, 5, 7, 2, 2, 2, 2, 1, 6, 2, 8, 5, 5, 31, 2, 5, 2, 2, 1, 1, 1, 1, 4, 3, 10, 3, 3, 2, 129,24, 18, 4, 6, 11, 9, 5, 5, 49, 7, 7, 10, 2, 2, 17, 2, 3, 4, 4, 3, 7, 3, 2, 2, 2, 31, 8, 1, 16, 5, 3, 2, 2, 2, 2, 3, 3, 2, 3, 2, 2, 2, 27, 6, 1, 1, 1, 1, 1, 1, 1, 6, 2, 2, 2, 2, 1, 4, 3, 3, 2, 2, 6, 3, 2, 1, 1, 195,36, 1, 8, 4, 1, 1, 1, 1, 1, 15, 4, 4, 4, 4, 4, 2, 2, 2, 2, 5, 19, 2, 3, 5, 4, 4, 4, 4, 1, 3, 2, 64, 1, 1, 56, 4, 1, 1, 1, 3, 2, 2, 1, 1, 1, 1, 6, 2, 2, 2, 1, 1, 13, 10, 6, 3, 2, 13, 9, 7, 8, 5, 5, 5, 5, 4, 2, 1, 2, 1, 1, 47, 3, 3, 8, 4, 2, 2, 11, 5, 1, 1, 1, 3, 3, 3, 2, 20, 20, 3, 3, 3, 10, 9, 9, 9, 9, 1, 1, 1, 1, 1, 5, 2, 20, 5, 3, 2, 2, 15, 14, 10, 5, 3, 3, 3, 3, 15, 6, 2, 3, 87, 1, 6, 3, 13, 2, 2, 4, 5, 4, 2, 9, 3, 38, 16, 3, 6, 2, 4, 5, 5, 2, 2, 2, 2, 3, 6, 11, 8, 7, 5, 2, 4, 48, 5, 4, 2, 2, 1, 3, 2, 21, 7, 4, 2, 2, 1, 2, 3, 3, 7, 3, 2, 26, 7, 3, 3, 2, 2, 3, 5, 1, 55, 9, 4, 5, 4, 3, 3, 5, 2, 3, 3, 20, 7, 4, 4, 3, 2, 5, 14, 4, 4, 4, 10, 2, 2, 115,6, 2, 2, 12, 6, 2, 2, 2, 22, 4, 3, 3, 2, 4, 6, 3, 3, 5, 21, 3, 2, 2, 2, 12, 6, 3, 5, 2, 3, 3, 3, 4, 1, 1, 1, 9, 3, 1, 9, 5, 2, 18, 6, 6, 3, 2, 2, 2, 17, 8, 2, 4, 2, 3, 28, 5, 3, 5, 2, 9, 7, 2, 2, 2, 5, 43, 11, 1, 9, 5, 2, 2, 2, 14, 2, 3, 3, 2, 3, 2, 6, 3, 18, 3, 14, 7, 3, 2, 1, 1, 166,19, 12, 2, 2, 4, 7, 13, 7, 1, 1, 1, 3, 3, 3, 2, 2, 1, 34, 9, 2, 5, 2, 2, 5, 12, 3, 4, 12, 1, 1, 2, 2, 1, 1, 1, 6, 3, 34, 11, 2, 2, 4, 2, 2, 5, 6, 4, 36, 7, 5, 4, 5, 2, 3, 3, 3, 3, 2, 3, 2, 3, 48, 23, 16, 5, 5, 5, 4, 5, 5, 1, 6, 2, 2, 3, 4, 4, 7, 2, 1, 2, 31, 1, 2, 2, 10, 4, 6, 43, 4, 4, 17, 1, 5, 4, 4, 7, 3, 3, 2, 1, 5, 3, 2, 1, 1, 1, 1, 10, 5, 5, 12, 7, 6, 6, 1, 2, 12, 2, 1, 3, 2, 2, 1, 2, 2, 1, 13, 1, 1, 1, 1, 5, 3, 2, 1, 4, 4, 33, 12, 1, 1, 10, 1, 4, 10, 5, 5, 3, 3, 14, 8, 3, 3, 122,17, 2, 5, 5, 3, 7, 3, 41, 10, 4, 3, 3, 3, 1, 5, 15, 8, 6, 3, 1, 1, 28, 5, 5, 4, 4, 5, 2, 3, 6, 2, 2, 2, 2, 2, 8, 3, 3, 3, 2, 23, 4, 6, 2, 2, 2, 4, 2, 2, 2, 8, 3, 3, 2, 1, 31, 3, 2, 4, 2, 1, 15, 6, 4, 1, 2, 47, 2, 10, 1, 2, 11, 1, 8, 51, 5, 2, 4, 3, 6, 20, 6, 6, 2, 2, 3, 3, 5, 5, 5, 98, 2, 9, 2, 2, 4, 1, 3, 1, 3, 10, 6, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 58, 6, 4, 13, 5, 2, 2, 3, 7, 3, 3, 5, 1, 7, 7, 5, 2, 2, 2, 75, 2, 8, 9, 7, 7, 1, 1, 15, 9, 8, 7, 4, 4, 36, 2, 8, 4, 2, 3, 3, 5, 4, 1, 3, 1, 5, 1, 4, 1, 1, 1, 1, 2, 50, 4, 2, 10, 5, 3, 5, 3, 2, 2, 3, 1, 17, 7, 5, 4, 2, 32, 1, 2, 2, 1, 1, 1, 1, 8, 2, 3, 10, 4, 4, 3, 26, 4, 4, 16, 12, 6, 3, 1, 3, 5, 5, 5, 4, 3, 69, 11, 10, 10, 7, 1, 1, 4, 1, 1, 1, 5, 2, 2, 6, 2, 9, 4, 3, 4, 13, 2, 6, 9, 9, 6, 6, 27, 4, 3, 3, 3, 6, 6, 5, 1, 8, 2, 2, 1, 6, 2, 2, 2, 20, 4, 3, 3, 2, 2, 2, 1, 1, 1, 10, 8, 7, 2, 2, 2, 2, 1, 1, 7, 2, 1, 1, 1, 3, 11, 9, 8, 4, 3, 2, 1, 1, 1, 3, 3, 5, 4, 4, 4, 4, 7, 3, 3, 2, 2, 2, 15, 3, 2, 3, 3, 2, 6, 3, 8, 2, 2, 1, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 2, 14, 7, 7, 2, 2, 1, 4, 2, 1, 1, 1, 1, 1, 91, 3, 2, 2, 1, 9, 2, 6, 6, 19, 9, 8, 3, 8, 7, 2, 2, 2, 1, 10, 10, 9, 7, 5, 3, 20, 5, 5, 13, 5, 2, 2, 7, 6, 2, 2, 19, 9, 7, 5, 3, 3, 23, 6, 4, 9, 8, 8, 6, 3, 2, 2, 157,2, 15, 6, 2, 5, 10, 5, 3, 3, 16, 11, 8, 2, 2, 27, 10, 3, 2, 7, 2, 2, 2, 2, 2, 39, 19, 10, 4, 3, 9, 9, 5, 2, 3, 1, 10, 9, 2, 6, 4, 4, 3, 13, 3, 2, 6, 6, 6, 3, 3, 13, 5, 4, 4, 4, 3, 2, 23, 8, 4, 6, 6, 6, 38, 1, 1, 16, 3, 3, 5, 8, 8, 3, 2, 6, 4, 2, 1, 5, 3, 3, 3, 2, 2, 18, 3, 3, 12, 12, 6, 4, 28, 1, 11, 11, 11, 6, 6, 6, 3, 9, 2, 4, 2, 2, 1, 1, 16, 14, 7, 7, 7, 7, 7, 4, 5, 4, 4, 4, 4, 9, 9, 9, 9, 4, 5, 2, 2, 3, 3, 26, 2, 1, 1, 6, 5, 10, 8, 5, 5, 9, 5, 5, 5, 1, 1, 4, 43, 2, 9, 3, 7, 6, 13, 4, 4, 4, 4, 4, 9, 4, 3, 3, 3, 3, 3, 3, 3, 1, 35, 1, 1, 10, 9, 9, 9, 3, 13, 12, 5, 5, 7, 3, 6, 3, 3, 38, 24, 11, 11, 5, 9, 9, 3, 2, 7, 2, 2, 2, 1, 10, 5, 41, 2, 13, 8, 4, 2, 2, 16, 8, 7, 2, 5, 3, 10, 3, 9, 4, 3, 28, 8, 6, 4, 1, 4, 3, 7, 4, 4, 5, 3, 1, 2, 2, 40, 3, 2, 2, 2, 2, 1, 33, 33, 33, 3, 2, 8, 5, 5, 5, 4, 21, 2, 2, 2, 2, 2, 1, 1, 5, 5, 5, 4, 3, 3, 3, 3, 4, 2, 2, 2, 2, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 3, 2, 15, 15, 9, 3, 3, 6, 6, 13, 5, 5, 5, 3, 8, 6, 8, 8, 8, 3, 3, 10, 2, 4, 4, 2, 4, 38, 2, 2, 5, 17, 3, 4, 5, 2, 6, 2, 15, 3, 2, 10, 39, 12, 7, 1, 5, 5, 3, 2, 10, 8, 8, 2, 7, 1, 1, 1, 1, 12, 9, 3, 3, 74, 1, 5, 4, 6, 6, 5, 2, 11, 4, 3, 2, 4, 3, 3, 8, 5, 4, 4, 1, 3, 15, 5, 1, 6, 3, 3, 1, 1, 4, 3, 34, 2, 11, 2, 16, 10, 19, 1, 2, 6, 1, 1, 1, 1, 3, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 1, 3, 3, 1, 5, 7, 2, 4, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 7, 6, 3, 3, 3, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 4, 3, 1, 1, 1, 6, 1, 1, 5, 5, 4, 3, 2, 248,87, 4, 3, 1, 1, 5, 3, 1, 8, 4, 3, 4, 3, 12, 8, 6, 4, 12, 3, 6, 1, 1, 1, 3, 26, 3, 3, 3, 5, 15, 12, 11, 4, 4, 5, 2, 1, 6, 5, 1, 4, 3, 50, 2, 2, 2, 10, 7, 6, 5, 4, 3, 7, 1, 1, 1, 1, 16, 10, 7, 7, 7, 1, 1, 95, 10, 8, 5, 3, 3, 3, 3, 8, 3, 33, 4, 3, 17, 4, 3, 5, 1, 2, 1, 10, 3, 3, 3, 7, 4, 3, 3, 4, 2, 12, 9, 3, 2, 1, 1, 23, 21, 18, 14, 8, 3, 2, 1, 3, 1, 1, 2, 2, 1, 5, 30, 4, 9, 7, 5, 4, 2, 6, 3, 2, 3, 57, 1, 3, 12, 1, 4, 2, 12, 2, 9, 7, 9, 1, 12, 6, 4, 2, 3, 3, 3, 2, 3, 61, 8, 2, 5, 29, 7, 3, 5, 3, 10, 2, 2, 2, 3, 4, 3, 2, 5, 2, 20, 3, 8, 2, 2, 5, 30, 5, 4, 5, 2, 3, 2, 2, 2, 11, 2, 3, 72, 7, 4, 11, 4, 4, 2, 31, 5, 5, 4, 2, 1, 1, 3, 3, 3, 3, 2, 2, 15, 4, 4, 1, 2, 5, 3, 2, 8, 5, 2, 1, 15, 2, 7, 3, 5, 5, 78, 8, 13, 2, 2, 4, 3, 1, 1, 1, 1, 9, 4, 9, 1, 1, 3, 1, 1, 1, 1, 1, 8, 5, 7, 2, 2, 1, 13, 3, 5, 77, 4, 9, 5, 3, 3, 24, 4, 3, 4, 4, 6, 3, 5, 9, 7, 4, 7, 4, 3, 7, 6, 42, 8, 4, 7, 14, 4, 3, 3, 3, 3, 2, 3, 2, 2, 2, 2, 8, 3, 48, 2, 8, 10, 4, 4, 3, 3, 3, 4, 2, 1, 1, 5, 3, 70, 3, 2, 1, 5, 3, 5, 12, 3, 14, 10, 8, 6, 6, 3, 3, 19, 3, 3, 10, 8, 3, 2, 2, 21, 3, 2, 2, 5, 3, 2, 28, 5, 16, 2, 9, 2, 1, 7, 3, 1, 15, 10, 4, 4, 40, 3, 5, 3, 2, 7, 4, 3, 7, 3, 3, 7, 1, 1, 121,17, 5, 2, 2, 2, 10, 5, 14, 5, 4, 4, 29, 17, 8, 3, 3, 6, 3, 6, 3, 3, 11, 3, 3, 23, 9, 8, 11, 3, 2, 4, 2, 2, 37, 3, 2, 2, 2, 17, 5, 9, 4, 3, 8, 3, 3, 5, 3, 3, 160,12, 5, 3, 2, 21, 8, 3, 6, 6, 4, 3, 3, 3, 7, 3, 2, 1, 16, 7, 3, 3, 6, 3, 11, 6, 2, 2, 2, 2, 56, 9, 9, 9, 6, 2, 2, 8, 1, 7, 6, 6, 6, 18, 4, 4, 4, 2, 4, 1, 1, 3, 8, 3, 5, 1, 4, 4, 4, 10, 8, 3, 5, 5, 13, 5, 5, 5, 2, 2, 2, 6, 6, 6, 7, 3, 2, 1, 5, 3, 3, 3, 4, 115,2, 5, 2, 2, 17, 4, 8, 2, 1, 17, 3, 4, 1, 2, 1, 1, 1, 1, 6, 2, 2, 5, 3, 12, 2, 1, 6, 2, 4, 3, 3, 5, 4, 4, 2, 5, 8, 2, 9, 5, 3, 5, 3, 40, 2, 6, 4, 6, 3, 4, 2, 1, 6, 4, 3, 1, 1, 1, 1, 67, 13, 13, 7, 6, 4, 8, 5, 4, 10, 10, 8, 8, 4, 3, 12, 7, 7, 7, 4, 4, 4, 4, 4, 4, 17, 5, 3, 75, 16, 5, 5, 9, 7, 3, 24, 6, 6, 4, 4, 4, 4, 9, 8, 4, 3, 8, 7, 6, 4, 12, 11, 11, 6, 1, 1, 1, 31, 8, 4, 3, 2, 2, 6, 6, 5, 5, 5, 2, 2, 1, 130,6, 5, 14, 13, 5, 6, 7, 4, 1, 1, 1, 2, 6, 6, 7, 2, 2, 47, 3, 2, 2, 3, 2, 6, 4, 4, 4, 4, 5, 4, 3, 9, 2, 2, 5, 4, 3, 3, 16, 2, 12, 2, 12, 6, 3, 3, 3, 3, 3, 4, 3, 75, 25, 13, 10, 5, 5, 7, 11, 5, 2, 2, 24, 4, 6, 4, 4, 4, 3, 3, 3, 3, 3, 5, 3, 3, 3, 86, 20, 5, 5, 3, 7, 3, 13, 5, 4, 6, 5, 19, 11, 2, 3, 3, 3, 3, 3, 3, 19, 4, 4, 4, 2, 3, 6, 2, 2, 108,14, 2, 16, 10, 5, 2, 2, 18, 7, 2, 2, 2, 4, 5, 4, 3, 2, 3, 9, 3, 3, 11, 3, 14, 3, 2, 5, 7, 4, 4, 3, 3, 3, 3, 115,15, 8, 5, 2, 4, 18, 6, 5, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 3, 3, 18, 8, 6, 5, 1, 7, 5, 5, 3, 12, 9, 9, 5, 3, 4, 28, 14, 13, 8, 8, 8, 4, 5, 2, 9, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 12, 3, 3, 3, 3, 3, 5, 17, 17, 5, 11, 4, 4, 4, 3, 3, 5, 176,12, 4, 2, 2, 2, 32, 5, 5, 5, 5, 5, 10, 3, 7, 9, 8, 3, 24, 11, 8, 6, 3, 6, 6, 2, 15, 6, 3, 4, 3, 3, 29, 11, 10, 10, 3, 5, 4, 4, 4, 11, 11, 7, 5, 4, 38, 5, 4, 4, 4, 3, 1, 10, 7, 14, 5, 5, 5, 5, 1, 1, 1, 15, 5, 3, 3, 2, 70, 17, 6, 5, 5, 5, 2, 6, 1, 2, 10, 1, 4, 22, 5, 5, 4, 4, 7, 5, 5, 6, 1, 2, 2, 2, 4, 4, 3, 13, 5, 5, 4, 78, 6, 32, 9, 3, 15, 6, 4, 5, 19, 6, 8, 5, 17, 13, 6, 6, 6, 5, 5, 15, 5, 5, 4, 4, 8, 3, 3, 20, 1, 1, 1, 11, 6, 5, 3, 2, 5, 2, 2, 60, 4, 15, 3, 2, 2, 2, 3, 2, 2, 12, 11, 7, 3, 4, 15, 2, 7, 3, 3, 11, 3, 9, 3, 4, 13, 3, 5, 2, 83, 7, 4, 4, 8, 3, 4, 32, 8, 8, 10, 3, 4, 3, 4, 30, 3, 15, 4, 3, 3, 2, 5, 2, 2, 4, 1, 44, 12, 6, 6, 1, 11, 3, 7, 5, 8, 4, 4, 4, 4, 50, 3, 3, 10, 6, 1, 1, 5, 3, 4, 2, 1, 1, 1, 1, 2, 2, 8, 4, 2, 1, 12, 6, 44, 15, 2, 5, 15, 13, 3, 2, 5, 2, 5, 8, 6, 8, 2, 2, 55, 5, 3, 5, 2, 1, 1, 1, 1, 1, 6, 2, 24, 6, 2, 4, 4, 3, 2, 5, 3, 1, 1, 1, 1, 1, 11, 25, 17, 9, 1, 4, 41, 6, 2, 1, 4, 3, 7, 3, 5, 8, 4, 30, 3, 6, 6, 4, 1, 1, 1, 7, 4, 3, 3, 3, 3, 5, 35, 7, 1, 1, 23, 3, 3, 2, 12, 3, 5, 3, 3, 12, 7, 3, 1, 1, 1, 34, 3, 17, 2, 2, 2, 1, 4, 4, 89, 6, 3, 1, 1, 17, 6, 7, 22, 1, 14, 8, 20, 4, 4, 2, 2, 9, 4, 1, 6, 5, 8, 6, 2, 64, 2, 4, 18, 3, 9, 4, 37, 11, 5, 4, 5, 3, 2, 62, 7, 2, 5, 2, 15, 1, 1, 6, 3, 3, 3, 3, 5, 2, 17, 3, 2, 14, 9, 6, 4, 4, 33, 2, 4, 5, 3, 3, 2, 7, 3, 20, 3, 7, 2, 1, 1, 3, 28, 8, 9, 4, 4, 3, 1, 1, 1, 5, 4, 2, 3, 58, 6, 6, 2, 6, 8, 3, 27, 10, 6, 2, 2, 2, 1, 7, 2, 3, 2, 61, 33, 24, 6, 4, 5, 5, 5, 2, 2, 10, 2, 6, 3, 1, 29, 5, 3, 6, 2, 2, 5, 51, 8, 8, 2, 12, 3, 3, 3, 8, 3, 2, 2, 8, 2, 6, 2, 3, 3, 3, 7, 2, 180,35, 14, 3, 7, 4, 2, 6, 4, 3, 3, 1, 2, 1, 4, 72, 9, 3, 3, 3, 1, 11, 5, 5, 4, 3, 32, 11, 2, 4, 3, 4, 2, 2, 2, 3, 2, 1, 1, 2, 2, 2, 1, 4, 4, 15, 4, 2, 4, 2, 2, 2, 4, 26, 8, 2, 6, 8, 3, 2, 59, 10, 2, 1, 5, 1, 20, 7, 2, 3, 2, 2, 1, 1, 2, 2, 2, 1, 1, 4, 10, 5, 2, 2, 2, 3, 3, 65, 2, 13, 3, 10, 1, 1, 1, 2, 2, 1, 1, 1, 1, 11, 9, 1, 1, 14, 6, 5, 15, 5, 4, 4, 5, 3, 2, 27, 2, 2, 6, 7, 5, 5, 13, 7, 6, 19, 7, 7, 3, 4, 17, 4, 2, 6, 2, 6, 1, 1, 1, 5, 5, 4, 3, 4, 1, 1, 2, 2, 1, 1, 1, 5, 40, 3, 2, 2, 2, 8, 9, 3, 9, 4, 3, 4, 11, 2, 7, 41, 3, 8, 9, 2, 2, 8, 1, 2, 2, 2, 3, 3, 16, 3, 1, 4, 22, 2, 2, 14, 6, 5, 17, 9, 4, 3, 3, 6, 1, 3, 8, 1, 1, 15, 7, 3, 1, 3, 32, 8, 4, 5, 6, 4, 6, 3, 36, 3, 7, 3, 8, 4, 3, 3, 15, 3, 5, 2, 60, 4, 12, 3, 3, 21, 2, 8, 5, 6, 4, 3, 13, 3, 4, 26, 13, 3, 3, 1, 6, 3, 1, 1, 1, 3, 3, 2, 2, 1, 1, 1, 40, 7, 5, 10, 4, 1, 2, 5, 3, 6, 2, 1, 8, 1, 12, 3, 2, 1, 1, 47, 13, 3, 6, 2, 5, 4, 3, 2, 6, 3, 7, 7, 3, 7, 4, 4, 29, 4, 2, 6, 7, 3, 11, 2, 2, 2, 7, 5, 5, 5, 2, 2, 2, 29, 9, 4, 4, 6, 3, 11, 1, 2, 2, 2, 23, 13, 1, 1, 1, 1, 5, 2, 21, 6, 2, 5, 2, 1, 1, 1, 1, 1, 1, 2, 2, 7, 2, 34, 3, 11, 2, 11, 6, 2, 1, 15, 5, 2, 4, 2, 160,25, 5, 7, 2, 2, 2, 2, 3, 3, 2, 13, 5, 3, 18, 3, 2, 6, 3, 2, 13, 3, 2, 12, 3, 4, 4, 10, 3, 8, 3, 13, 2, 5, 4, 19, 12, 4, 4, 2, 2, 3, 3, 8, 2, 1, 1, 77, 13, 3, 2, 2, 3, 4, 6, 13, 3, 3, 2, 7, 12, 2, 6, 6, 2, 7, 4, 208,5, 1, 1, 1, 1, 1, 10, 7, 5, 1, 1, 2, 83, 8, 6, 2, 19, 2, 9, 3, 2, 11, 2, 3, 9, 1, 7, 3, 3, 2, 2, 4, 4, 1, 5, 5, 5, 3, 2, 5, 4, 3, 7, 2, 1, 1, 2, 1, 1, 4, 1, 9, 1, 1, 1, 5, 2, 55, 18, 2, 2, 1, 6, 2, 2, 7, 3, 3, 2, 14, 2, 1, 4, 4, 1, 1, 13, 3, 12, 3, 3, 3, 6, 69, 14, 1, 5, 3, 1, 1, 6, 5, 5, 3, 3, 6, 2, 6, 3, 3, 1, 3, 2, 7, 3, 7, 3, 3, 2, 24, 2, 5, 1, 12, 2, 3, 45, 4, 4, 5, 5, 3, 4, 2, 2, 2, 9, 6, 6, 2, 9, 1, 7, 7, 3, 7, 7, 3, 2, 50, 8, 6, 4, 4, 4, 1, 9, 3, 4, 5, 3, 5, 5, 3, 3, 3, 2, 46, 13, 5, 5, 3, 3, 11, 1, 1, 11, 6, 10, 2, 13, 4, 3, 8, 3, 2, 23, 6, 5, 1, 120,3, 4, 3, 4, 26, 4, 4, 6, 3, 6, 9, 5, 19, 5, 3, 2, 2, 2, 5, 4, 3, 1, 2, 1, 1, 1, 1, 40, 5, 2, 2, 6, 2, 2, 6, 4, 3, 6, 4, 2, 2, 14, 10, 2, 66, 6, 3, 5, 4, 3, 4, 6, 5, 3, 4, 3, 7, 2, 9, 2, 3, 9, 1, 1, 1, 1, 3, 87, 4, 2, 6, 5, 6, 4, 4, 13, 6, 3, 6, 5, 2, 11, 2, 7, 1, 1, 4, 2, 16, 2, 4, 2, 2, 6, 3, 1, 1, 1, 57, 5, 4, 3, 3, 2, 2, 2, 9, 3, 2, 1, 4, 4, 12, 4, 6, 3, 3, 3, 5, 2, 5, 3, 3, 2, 4, 4, 2, 2, 2, 2, 56, 10, 4, 3, 3, 23, 7, 2, 2, 3, 2, 1, 3, 8, 5, 4, 10, 1, 3, 3, 3, 2, 2, 5, 38, 5, 5, 4, 2, 18, 5, 3, 5, 6, 4, 6, 3, 2, 34, 10, 2, 2, 10, 2, 3, 2, 8, 4, 3, 3, 86, 21, 1, 14, 3, 3, 3, 3, 3, 3, 16, 6, 5, 3, 3, 4, 6, 21, 15, 1, 4, 2, 4, 2, 2, 2, 55, 13, 4, 3, 13, 4, 1, 1, 2, 4, 10, 4, 2, 14, 5, 3, 2, 66, 2, 5, 3, 9, 3, 1, 4, 18, 3, 2, 7, 2, 2, 10, 5, 2, 2, 5, 2, 5, 2, 1, 55, 25, 1, 3, 6, 12, 7, 6, 2, 1, 4, 3, 1, 20, 7, 2, 2, 4, 4, 9, 1, 1, 1, 1, 3, 4, 1, 25, 6, 1, 2, 1, 1, 6, 6, 4, 4, 2, 2, 1, 2, 1, 4, 3, 3, 3, 1, 2, 1, 1, 98, 2, 4, 9, 2, 4, 12, 4, 4, 2, 3, 4, 21, 5, 2, 6, 4, 4, 4, 18, 11, 3, 13, 2, 2, 4, 7, 5, 95, 25, 2, 5, 4, 11, 5, 25, 6, 8, 6, 1, 11, 2, 17, 5, 2, 5, 5, 1, 1, 4, 12, 5, 4, 3, 2, 2, 14, 4, 1, 3, 3, 3, 3, 2, 2, 34, 4, 9, 4, 5, 5, 4, 2, 4, 2, 4, 4, 3, 2, 2, 2, 1, 1, 1, 1, 113,16, 5, 4, 4, 1, 3, 2, 2, 2, 9, 4, 4, 3, 11, 10, 2, 4, 19, 5, 2, 13, 3, 2, 5, 5, 11, 2, 2, 3, 3, 6, 2, 18, 6, 5, 5, 5, 4, 4, 2, 5, 2, 44, 1, 3, 2, 2, 9, 7, 7, 14, 13, 11, 4, 5, 8, 7, 4, 33, 8, 5, 3, 1, 5, 5, 3, 3, 7, 7, 3, 55, 2, 4, 2, 4, 2, 6, 8, 2, 8, 3, 2, 23, 8, 3, 3, 2, 3, 2, 5, 4, 49, 5, 13, 6, 2, 1, 1, 4, 1, 4, 3, 2, 5, 3, 4, 4, 6, 1, 76, 2, 6, 3, 2, 12, 9, 9, 8, 3, 6, 3, 3, 3, 18, 7, 3, 2, 4, 3, 8, 3, 2, 2, 2, 38, 6, 4, 2, 7, 4, 1, 11, 1, 1, 1, 5, 4, 3, 3, 3, 4, 2, 2, 86, 8, 5, 4, 7, 2, 6, 3, 4, 5, 3, 5, 4, 4, 22, 4, 6, 6, 3, 22, 8, 5, 7, 4, 4, 4, 4, 3, 22, 6, 6, 6, 2, 6, 3, 2, 3, 3, 2, 10, 10, 7, 4, 83, 7, 5, 5, 2, 7, 7, 3, 1, 1, 6, 4, 11, 4, 3, 1, 4, 12, 8, 5, 3, 23, 5, 4, 4, 6, 12, 5, 4, 14, 8, 2, 2, 2, 32, 3, 3, 14, 7, 6, 3, 35, 28, 8, 5, 19, 3, 6, 9, 3, 41, 4, 3, 2, 2, 3, 5, 3, 11, 2, 1, 12, 2, 2, 3, 2, 3, 1, 2, 2, 2, 6, 2, 3, 11, 11, 4, 2, 4, 4, 27, 7, 2, 2, 8, 6, 3, 9, 4, 5, 2, 4, 19, 10, 4, 3, 3, 3, 3, 6, 13, 3, 5, 3, 3, 26, 6, 3, 6, 2, 1, 4, 2, 1, 1, 59, 4, 6, 9, 1, 4, 2, 2, 2, 2, 2, 2, 7, 7, 5, 9, 4, 13, 7, 2, 1, 1, 2, 4, 3, 2, 75, 2, 10, 2, 2, 9, 7, 4, 3, 3, 4, 3, 18, 9, 4, 2, 1, 1, 9, 2, 3, 2, 12, 7, 11, 6, 12, 1, 3, 3, 19, 3, 8, 5, 231,17, 7, 3, 3, 3, 3, 8, 3, 2, 25, 4, 3, 4, 4, 4, 3, 10, 6, 3, 49, 5, 3, 4, 18, 6, 2, 4, 5, 5, 2, 12, 2, 1, 1, 2, 3, 16, 2, 2, 5, 4, 4, 2, 63, 3, 5, 2, 2, 14, 2, 6, 3, 15, 5, 3, 3, 3, 2, 2, 4, 172,4, 2, 9, 7, 16, 4, 3, 8, 10, 5, 3, 3, 3, 17, 4, 1, 1, 5, 3, 2, 23, 3, 3, 4, 4, 4, 24, 4, 2, 8, 4, 2, 2, 2, 2, 26, 4, 6, 6, 4, 3, 2, 19, 4, 2, 2, 3, 4, 10, 7, 4, 3, 1, 3, 1, 181,4, 8, 8, 2, 11, 4, 3, 6, 1, 2, 12, 3, 13, 9, 16, 2, 2, 4, 4, 4, 33, 4, 26, 4, 1, 6, 3, 3, 2, 2, 8, 21, 12, 9, 3, 3, 3, 3, 10, 8, 25, 6, 3, 2, 1, 57, 2, 1, 16, 5, 4, 16, 7, 5, 3, 3, 11, 1, 4, 11, 4, 2, 2, 2, 87, 5, 4, 4, 10, 8, 5, 4, 3, 8, 3, 11, 5, 1, 6, 3, 2, 7, 3, 2, 3, 15, 2, 9, 4, 4, 1, 6, 2, 2, 4, 2, 10, 3, 3, 3, 26, 4, 4, 3, 3, 17, 7, 48, 1, 1, 1, 8, 2, 2, 3, 26, 4, 2, 1, 3, 3, 3, 1, 1, 4, 3, 49, 8, 7, 3, 3, 3, 2, 3, 11, 7, 3, 4, 2, 6, 4, 4, 4, 53, 3, 10, 7, 2, 1, 6, 4, 26, 9, 6, 3, 7, 6, 4, 6, 6, 2, 2, 2, 5, 2, 2, 77, 3, 6, 6, 6, 2, 2, 7, 6, 3, 5, 10, 3, 32, 4, 24, 18, 4, 2, 4, 4, 4, 94, 5, 4, 8, 2, 10, 7, 5, 10, 4, 4, 4, 22, 20, 6, 3, 18, 1, 2, 10, 6, 3, 8, 8, 6, 6, 2, 78, 4, 10, 7, 4, 3, 2, 2, 5, 24, 2, 6, 9, 8, 3, 2, 2, 3, 8, 7, 4, 1, 1, 3, 14, 2, 5, 1, 1, 1, 23, 2, 7, 3, 2, 3, 25, 5, 11, 5, 5, 32, 1, 4, 1, 8, 4, 3, 5, 4, 4, 12, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 6, 2, 2, 15, 3, 2, 8, 3, 11, 7, 6, 6, 177,32, 2, 1, 3, 6, 5, 3, 9, 8, 3, 3, 3, 3, 33, 1, 1, 10, 5, 4, 7, 3, 3, 1, 1, 3, 3, 3, 1, 6, 6, 6, 3, 61, 4, 5, 13, 10, 3, 5, 9, 4, 9, 6, 5, 2, 3, 2, 2, 19, 3, 3, 2, 2, 2, 4, 3, 3, 3, 2, 1, 1, 8, 4, 20, 3, 3, 6, 2, 2, 1, 2, 2, 2, 3, 2, 91, 3, 6, 5, 8, 4, 9, 2, 2, 9, 1, 11, 6, 3, 6, 5, 5, 4, 4, 4, 11, 7, 4, 14, 48, 4, 4, 3, 2, 6, 2, 2, 23, 14, 3, 3, 3, 3, 2, 3, 62, 9, 5, 3, 5, 7, 5, 4, 9, 3, 1, 10, 5, 5, 9, 1, 6, 6, 53, 6, 4, 3, 2, 3, 3, 4, 4, 3, 4, 2, 5, 2, 12, 4, 3, 36, 2, 7, 5, 8, 2, 6, 3, 6, 4, 3, 185,38, 7, 7, 2, 12, 11, 4, 11, 9, 23, 6, 2, 2, 11, 7, 2, 2, 43, 5, 9, 5, 3, 26, 17, 6, 4, 3, 2, 1, 3, 1, 1, 49, 13, 9, 2, 1, 12, 3, 5, 4, 3, 14, 4, 4, 10, 7, 1, 1, 1, 1, 5, 5, 5, 1, 1, 19, 3, 7, 5, 4, 9, 3, 180,44, 7, 6, 2, 12, 6, 3, 5, 6, 6, 1, 1, 1, 28, 10, 8, 4, 2, 7, 7, 5, 33, 3, 1, 10, 9, 9, 6, 5, 3, 3, 3, 60, 3, 2, 12, 4, 5, 8, 2, 29, 11, 6, 5, 3, 3, 3, 2, 4, 3, 3, 4, 2, 2, 12, 2, 3, 6, 5, 3, 13, 5, 5, 24, 7, 3, 6, 3, 2, 2, 32, 5, 5, 5, 4, 17, 10, 8, 6, 3, 4, 2, 2, 2, 6, 3, 20, 12, 10, 2, 2, 2, 4, 1, 20, 1, 1, 15, 6, 2, 4, 6, 4, 2, 3, 1, 5, 5, 5, 3, 81, 12, 4, 5, 3, 7, 11, 3, 2, 18, 5, 5, 5, 4, 2, 1, 1, 3, 2, 8, 4, 7, 2, 5, 4, 5, 4, 44, 2, 33, 4, 4, 4, 3, 3, 2, 5, 10, 8, 6, 6, 4, 3, 8, 7, 6, 3, 1, 1, 48, 3, 5, 6, 2, 2, 5, 11, 7, 6, 1, 1, 1, 4, 21, 4, 6, 4, 7, 5, 1, 15, 9, 6, 2, 2, 2, 56, 3, 5, 3, 2, 5, 5, 4, 3, 8, 3, 4, 13, 6, 5, 3, 9, 8, 7, 2, 1, 1, 1, 3, 1, 1, 1, 80, 3, 2, 2, 13, 3, 10, 10, 2, 7, 4, 28, 27, 3, 6, 4, 4, 3, 6, 2, 3, 2, 7, 2, 2, 2, 2, 2, 3, 1, 13, 3, 2, 2, 132,13, 9, 2, 2, 9, 4, 2, 5, 4, 4, 3, 2, 5, 4, 25, 5, 5, 7, 41, 3, 2, 4, 1, 3, 13, 5, 5, 7, 5, 4, 10, 4, 3, 2, 152,19, 9, 4, 3, 7, 1, 53, 25, 16, 16, 8, 3, 6, 5, 4, 2, 4, 14, 6, 4, 5, 5, 5, 5, 5, 20, 4, 3, 11, 2, 3, 8, 11, 9, 17, 5, 5, 7, 12, 2, 1, 2, 2, 4, 2, 2, 2, 3, 2, 2, 118,10, 5, 10, 9, 6, 15, 2, 2, 4, 3, 1, 1, 9, 7, 13, 9, 23, 3, 3, 4, 3, 4, 16, 4, 8, 8, 4, 13, 5, 4, 1, 28, 9, 5, 5, 3, 5, 4, 4, 3, 3, 13, 4, 4, 5, 83, 1, 6, 6, 2, 8, 7, 17, 1, 6, 4, 2, 7, 5, 5, 3, 3, 3, 14, 6, 12, 8, 4, 2, 12, 3, 6, 5, 96, 33, 2, 2, 3, 3, 6, 3, 4, 4, 5, 14, 6, 6, 3, 2, 3, 28, 4, 20, 11, 4, 3, 2, 6, 3, 13, 8, 6, 3, 20, 4, 13, 2, 3, 3, 2, 69, 69, 30, 3, 5, 2, 3, 5, 5, 15, 9, 5, 5, 23, 2, 2, 2, 2, 9, 4, 7, 3, 2, 2, 2, 2, 1, 1, 1, 3, 11, 1, 1, 1, 7, 2, 5, 4, 3, 3, 14, 6, 6, 5, 4, 4, 22, 5, 5, 3, 3, 2, 7, 8, 2, 4, 6, 6, 18, 7, 5, 4, 2, 9, 8, 3, 17, 7, 2, 7, 3, 8, 7, 34, 3, 7, 16, 4, 3, 20, 2, 3, 2, 8, 3, 84, 3, 3, 4, 25, 4, 3, 4, 2, 5, 2, 2, 2, 2, 2, 4, 8, 5, 3, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 3, 2, 7, 2, 3, 8, 6, 6, 82, 4, 5, 2, 2, 2, 2, 4, 8, 9, 6, 4, 7, 5, 2, 2, 13, 5, 12, 5, 45, 15, 5, 2, 12, 6, 6, 4, 4, 4, 10, 3, 9, 4, 2, 2, 5, 1, 1, 2, 2, 9, 5, 1, 1, 1, 2, 32, 7, 4, 7, 7, 2, 2, 5, 2, 8, 6, 29, 8, 1, 6, 3, 2, 2, 4, 6, 27, 13, 13, 5, 3, 14, 2, 9, 6, 4, 6, 3, 3, 2, 45, 3, 3, 3, 3, 2, 31, 10, 2, 2, 3, 2, 5, 4, 3, 4, 2, 1, 1, 17, 12, 3, 2, 2, 2, 2, 2, 1, 1, 11, 4, 21, 3, 3, 3, 2, 1, 1, 1, 5, 47, 4, 3, 1, 3, 3, 2, 19, 10, 9, 6, 6, 2, 3, 3, 2, 2, 6, 2, 28, 2, 5, 5, 4, 4, 4, 2, 3, 3, 3, 7, 2, 2, 7, 4, 15, 13, 13, 7, 7, 4, 3, 2, 4, 4, 2, 2, 2, 2, 139,19, 16, 3, 5, 8, 1, 3, 11, 6, 4, 7, 2, 22, 4, 14, 1, 2, 3, 2, 2, 15, 3, 8, 8, 27, 2, 2, 4, 8, 8, 5, 4, 4, 8, 1, 3, 11, 3, 4, 8, 3, 3, 2, 3, 2, 4, 3, 3, 233,3, 32, 32, 2, 3, 3, 4, 5, 2, 3, 3, 2, 5, 2, 6, 2, 3, 3, 1, 3, 4, 9, 6, 18, 6, 6, 3, 2, 9, 4, 4, 4, 36, 30, 6, 2, 4, 3, 2, 3, 4, 4, 4, 14, 2, 13, 6, 3, 44, 8, 2, 4, 1, 1, 10, 3, 1, 7, 2, 2, 2, 6, 3, 1, 11, 3, 2, 4, 4, 7, 2, 4, 3, 117,5, 3, 6, 2, 4, 19, 11, 11, 11, 7, 1, 1, 3, 1, 22, 4, 14, 7, 5, 4, 5, 5, 4, 16, 8, 7, 6, 2, 13, 3, 3, 6, 1, 1, 1, 1, 10, 4, 3, 3, 5, 66, 9, 5, 3, 3, 13, 10, 1, 1, 3, 8, 2, 9, 6, 5, 5, 3, 5, 5, 5, 5, 52, 2, 11, 7, 4, 4, 2, 6, 9, 8, 2, 14, 2, 1, 1, 66, 4, 1, 1, 6, 3, 1, 1, 6, 10, 7, 6, 2, 4, 4, 7, 2, 33, 4, 7, 4, 4, 4, 4, 1, 1, 4, 2, 6, 2, 3, 2, 1, 1, 1, 1, 139,3, 3, 12, 9, 2, 2, 2, 10, 1, 9, 5, 24, 2, 6, 4, 7, 4, 4, 1, 1, 1, 1, 9, 9, 2, 4, 19, 6, 11, 12, 4, 4, 12, 1, 6, 6, 17, 3, 6, 4, 2, 11, 3, 3, 17, 8, 4, 2, 2, 1, 6, 90, 2, 1, 2, 1, 1, 3, 3, 1, 1, 1, 1, 2, 2, 3, 3, 1, 7, 6, 6, 6, 1, 8, 5, 4, 3, 3, 3, 9, 2, 2, 2, 5, 3, 1, 1, 3, 3, 3, 3, 1, 1, 1, 27, 3, 3, 3, 4, 3, 3, 3, 3, 3, 2, 1, 8, 8, 4, 11, 8, 7, 5, 5, 4, 3, 3, 10, 3, 2, 1, 3, 4, 4, 4, 4, 2, 2, 2, 41, 18, 11, 7, 7, 1, 1, 5, 3, 4, 18, 5, 18, 2, 7, 4, 1, 1, 14, 2, 3, 15, 15, 5, 5, 5, 28, 13, 10, 10, 7, 1, 20, 14, 5, 1, 1, 31, 4, 2, 3, 9, 5, 3, 3, 5, 1, 58, 5, 2, 1, 1, 2, 3, 3, 34, 7, 5, 3, 20, 18, 1, 2, 3, 2, 4, 2, 65, 4, 1, 25, 6, 3, 9, 4, 4, 3, 3, 3, 2, 8, 6, 3, 3, 2, 8, 3, 1, 2, 1, 1, 89, 49, 49, 2, 3, 1, 2, 1, 3, 2, 4, 2, 1, 1, 4, 1, 7, 4, 2, 4, 2, 1, 2, 7, 7, 3, 7, 7, 1, 1, 30, 2, 10, 3, 5, 13, 7, 7, 6, 3, 3, 2, 4, 4, 4, 8, 5, 4, 2, 2, 80, 2, 4, 12, 3, 11, 8, 3, 4, 3, 3, 9, 6, 16, 9, 4, 6, 5, 11, 3, 1, 1, 8, 8, 2, 2, 42, 9, 8, 4, 1, 1, 4, 3, 15, 11, 6, 4, 3, 5, 4, 4, 21, 2, 3, 3, 3, 2, 2, 6, 2, 2, 1, 2, 12, 5, 5, 12, 11, 5, 1, 1, 2, 86, 1, 1, 3, 3, 2, 1, 4, 2, 5, 4, 4, 7, 4, 20, 2, 3, 2, 5, 18, 1, 12, 2, 2, 2, 5, 2, 1, 1, 1, 2, 1, 5, 8, 4, 90, 25, 8, 2, 3, 3, 15, 5, 4, 7, 32, 4, 5, 2, 20, 1, 2, 2, 4, 2, 7, 5, 5, 4, 82, 2, 4, 1, 3, 2, 2, 1, 5, 3, 11, 7, 3, 24, 7, 2, 4, 7, 3, 5, 2, 12, 9, 9, 2, 4, 2, 2, 22, 8, 6, 4, 2, 2, 1, 1, 104,3, 2, 1, 5, 4, 3, 2, 15, 3, 3, 3, 11, 6, 4, 4, 3, 24, 9, 2, 2, 7, 7, 5, 2, 2, 2, 3, 2, 9, 4, 4, 4, 2, 1, 3, 3, 3, 24, 3, 2, 5, 4, 3, 3, 3, 3, 5, 3, 2, 3, 2, 2, 2, 5, 5, 4, 2, 2, 4, 4, 10, 2, 2, 5, 3, 9, 8, 7, 7, 3, 3, 3, 1, 25, 5, 1, 2, 2, 2, 2, 2, 4, 1, 1, 1, 5, 1, 6, 5, 1, 1, 1, 1, 1, 2, 42, 6, 4, 4, 2, 1, 5, 3, 3, 13, 11, 2, 2, 2, 2, 3, 7, 3, 30, 5, 1, 2, 11, 4, 6, 4, 13, 2, 4, 7, 4, 13, 1, 6, 3, 2, 2, 5, 2, 29, 11, 2, 5, 1, 28, 8, 2, 2, 16, 2, 4, 4, 66, 7, 1, 4, 3, 4, 2, 7, 4, 4, 6, 2, 1, 2, 11, 5, 16, 3, 1, 1, 4, 3, 2, 4, 3, 4, 58, 10, 2, 6, 1, 3, 3, 11, 7, 10, 1, 1, 8, 2, 77, 3, 1, 1, 3, 4, 17, 6, 2, 8, 10, 4, 3, 3, 11, 2, 7, 1, 5, 8, 1, 1, 4, 1, 1, 25, 6, 4, 9, 4, 2, 78, 12, 6, 4, 3, 8, 5, 5, 2, 2, 3, 5, 1, 1, 9, 4, 2, 2, 12, 6, 3, 3, 3, 33, 6, 5, 7, 4, 10, 7, 1, 23, 3, 3, 9, 2, 2, 2, 2, 4, 23, 5, 6, 5, 2, 12, 3, 7, 5, 3, 9, 4, 13, 7, 2, 2, 8, 4, 3, 4, 2, 2, 53, 10, 9, 5, 2, 3, 1, 5, 2, 10, 7, 10, 2, 3, 2, 19, 17, 15, 10, 8, 5, 4, 4, 4, 3, 14, 6, 4, 2, 2, 2, 2, 30, 1, 12, 5, 4, 3, 2, 7, 2, 4, 2, 2, 10, 4, 4, 2, 1, 4, 3, 3, 1, 57, 33, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 1, 12, 3, 3, 3, 5, 5, 4, 2, 2, 2, 1, 1, 1, 4, 3, 3, 4, 4, 3, 18, 6, 4, 4, 44, 2, 37, 12, 9, 6, 7, 5, 4, 3, 3, 6, 69, 6, 2, 2, 3, 11, 5, 3, 3, 6, 14, 6, 3, 5, 5, 4, 5, 7, 3, 3, 3, 2, 6, 1, 98, 3, 1, 11, 6, 2, 5, 1, 14, 12, 10, 8, 6, 2, 2, 53, 11, 1, 4, 7, 2, 22, 3, 3, 7, 4, 4, 4, 3, 9, 8, 6, 1, 1, 1, 1, 1, 1, 52, 8, 4, 35, 4, 4, 4, 14, 3, 4, 3, 3, 2, 1, 1, 15, 3, 3, 3, 3, 1, 1, 2, 2, 4, 1, 18, 6, 2, 9, 67, 5, 3, 2, 4, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 17, 11, 4, 3, 3, 1, 1, 4, 12, 4, 3, 141,10, 7, 4, 4, 3, 3, 7, 3, 3, 3, 2, 9, 2, 5, 8, 2, 1, 14, 2, 4, 3, 3, 2, 5, 3, 41, 7, 7, 1, 4, 17, 5, 6, 3, 5, 4, 1, 1, 1, 2, 4, 3, 2, 11, 1, 3, 3, 2, 2, 1, 6, 4, 1, 2, 6, 81, 9, 2, 2, 3, 3, 6, 6, 5, 17, 4, 4, 3, 2, 23, 8, 7, 4, 3, 10, 5, 7, 1, 1, 98, 26, 12, 9, 4, 3, 3, 4, 51, 4, 2, 5, 2, 2, 23, 7, 3, 3, 4, 3, 3, 3, 3, 4, 12, 7, 7, 6, 4, 4, 4, 81, 6, 1, 1, 23, 11, 4, 7, 7, 3, 2, 1, 3, 2, 11, 31, 4, 1, 1, 1, 1, 1, 7, 4, 2, 3, 3, 3, 9, 10, 7, 6, 49, 4, 11, 5, 4, 10, 8, 8, 8, 3, 2, 16, 4, 5, 4, 3, 1, 1, 5, 8, 4, 15, 7, 2, 4, 2, 2, 23, 2, 17, 6, 6, 13, 3, 25, 5, 1, 1, 3, 7, 3, 4, 13, 11, 6, 6, 30, 9, 9, 4, 13, 12, 6, 3, 2, 1, 30, 16, 5, 5, 2, 2, 2, 1, 54, 6, 3, 3, 1, 14, 3, 2, 2, 2, 6, 1, 14, 4, 4, 3, 4, 58, 3, 7, 4, 1, 11, 6, 2, 10, 5, 5, 2, 3, 3, 6, 5, 2, 3, 1, 1, 1, 22, 15, 7, 6, 3, 3, 3, 14, 3, 1, 6, 15, 4, 1, 3, 26, 6, 5, 4, 4, 2, 6, 2, 2, 1, 1, 1, 1, 1, 1, 1, 12, 3, 8, 8, 19, 2, 6, 3, 2, 3, 5, 3, 17, 5, 8, 10, 3, 2, 7, 2, 5, 2, 2, 2, 2, 7, 4, 10, 3, 3, 44, 4, 11, 2, 6, 5, 17, 1, 8, 3, 2, 70, 20, 12, 3, 6, 2, 2, 2, 8, 1, 1, 5, 3, 2, 10, 2, 2, 5, 4, 3, 3, 2, 4, 45, 3, 7, 6, 11, 2, 3, 3, 3, 48, 2, 5, 8, 5, 5, 1, 18, 11, 2, 2, 2, 3, 36, 3, 5, 3, 4, 2, 8, 5, 90, 4, 3, 2, 6, 6, 3, 5, 4, 3, 1, 1, 11, 2, 3, 2, 1, 4, 4, 6, 2, 3, 5, 4, 3, 3, 17, 3, 10, 1, 2, 2, 2, 3, 2, 15, 4, 5, 3, 5, 5, 18, 2, 2, 5, 3, 6, 4, 21, 8, 7, 3, 4, 3, 3, 48, 14, 14, 5, 8, 7, 2, 3, 14, 2, 6, 4, 3, 1, 4, 1, 4, 36, 3, 10, 4, 16, 7, 2, 1, 1, 1, 2, 7, 5, 3, 1, 1, 1, 1, 1, 6, 3, 1, 3, 30, 7, 4, 2, 1, 4, 4, 4, 14, 7, 6, 8, 1, 1, 6, 4, 4, 6, 4, 26, 6, 13, 5, 4, 1, 15, 8, 85, 5, 4, 4, 4, 3, 68, 4, 1, 1, 1, 6, 3, 3, 3, 3, 3, 13, 5, 5, 5, 3, 4, 2, 2, 7, 4, 8, 7, 7, 5, 11, 3, 6, 2, 2, 2, 2, 10, 6, 7, 6, 1, 1, 15, 1, 1, 1, 1, 6, 6, 4, 4, 6, 23, 17, 12, 4, 4, 1, 4, 9, 1, 2, 131,33, 3, 2, 8, 9, 5, 12, 3, 1, 1, 2, 12, 2, 2, 3, 1, 3, 2, 1, 2, 2, 3, 17, 7, 6, 2, 2, 4, 12, 3, 3, 4, 3, 14, 2, 6, 6, 6, 4, 3, 2, 2, 8, 3, 3, 2, 166,1, 14, 6, 4, 2, 2, 2, 18, 5, 3, 4, 19, 1, 2, 12, 5, 4, 5, 1, 9, 6, 2, 1, 1, 1, 4, 3, 13, 1, 3, 17, 2, 6, 3, 6, 2, 3, 28, 8, 7, 3, 3, 12, 5, 1, 1, 7, 3, 7, 3, 4, 4, 4, 4, 9, 3, 6, 3, 2, 1, 99, 2, 2, 2, 6, 4, 4, 3, 6, 6, 4, 4, 3, 5, 5, 9, 4, 4, 2, 6, 1, 8, 5, 26, 11, 1, 10, 9, 2, 4, 5, 3, 4, 4, 4, 1, 1, 90, 2, 4, 2, 2, 12, 10, 1, 1, 6, 3, 13, 2, 2, 3, 3, 3, 7, 14, 5, 7, 4, 5, 15, 4, 3, 10, 3, 1, 5, 5, 9, 3, 20, 5, 4, 4, 1, 1, 2, 7, 1, 1, 1, 2, 1, 5, 5, 5, 4, 3, 13, 6, 4, 2, 2, 2, 2, 9, 6, 4, 3, 3, 7, 3, 3, 6, 5, 3, 4, 3, 19, 3, 12, 4, 3, 19, 6, 5, 3, 1, 2, 4, 1, 1, 25, 2, 6, 4, 5, 5, 4, 2, 5, 2, 2, 2, 97, 3, 11, 2, 2, 2, 1, 7, 2, 4, 2, 13, 5, 4, 3, 4, 31, 3, 4, 6, 5, 3, 2, 3, 2, 1, 4, 4, 6, 4, 4, 15, 4, 3, 3, 21, 2, 5, 5, 2, 5, 3, 3, 2, 1, 1, 1, 91, 7, 25, 3, 11, 4, 3, 3, 3, 2, 47, 3, 5, 3, 13, 5, 8, 9, 6, 6, 9, 3, 3, 63, 1, 2, 2, 2, 2, 2, 5, 4, 4, 8, 2, 2, 2, 12, 5, 2, 2, 4, 12, 4, 3, 5, 5, 1, 1, 1, 1, 3, 1, 1, 1, 3, 3, 5, 1, 1, 3, 2, 10, 2, 1, 1, 2, 5, 13, 4, 4, 4, 2, 2, 2, 2, 2, 2, 34, 2, 1, 9, 2, 3, 3, 2, 9, 1, 5, 5, 5, 4, 2, 2, 2, 25, 3, 2, 8, 3, 3, 3, 3, 1, 2, 6, 2, 2, 2, 28, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 4, 2, 1, 1, 1, 3, 3, 1, 2, 1, 4, 4, 2, 1, 1, 1, 1, 2, 1, 1, 19, 1, 1, 7, 3, 1, 1, 1, 2, 2, 2, 2, 1, 1, 3, 1, 1, 4, 3, 51, 4, 3, 1, 1, 2, 2, 2, 6, 5, 1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 8, 1, 6, 26, 5, 1, 1, 2, 2, 2, 1, 1, 1, 14, 1, 2, 1, 1, 5, 4, 4, 4, 1, 1, 1, 4, 3, 3, 3, 3, 1, 1, 2, 1, 1, 2, 97, 1, 1, 79, 1, 2, 70, 2, 1, 1, 1, 1, 4, 2, 2, 4, 1, 1, 1, 1, 2, 4, 4, 4, 4, 4, 4, 8, 2, 3, 3, 2, 2, 2, 1, 6, 5, 5, 4, 4, 1, 4, 3, 1, 1, 1, 1, 1, 13, 11, 9, 6, 5, 6, 5, 5, 7, 2, 2, 2, 1, 1, 2, 2, 2, 2, 7, 5, 3, 1, 1, 1, 1, 1, 1, 4, 1, 1, 3, 20, 3, 1, 3, 3, 3, 3, 3, 2, 1, 1, 1, 2, 2, 2, 2, 1, 2, 2, 2, 6, 6, 2, 2, 2, 3, 2, 2, 1, 29, 7, 4, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 12, 1, 6, 1, 3, 1, 2, 2, 2, 2, 3, 2, 1, 1, 2, 1, 1, 9, 3, 1, 1, 1, 1, 1, 11, 5, 3, 2, 2, 3, 52, 5, 2, 1, 1, 1, 5, 4, 4, 4, 2, 2, 2, 2, 5, 2, 2, 2, 2, 2, 3, 1, 1, 1, 12, 1, 2, 1, 2, 1, 5, 5, 1, 1, 2, 1, 1, 1, 2, 2, 7, 9, 9, 9, 9, 2, 2, 5, 3, 3, 22, 1, 5, 4, 6, 1, 1, 3, 9, 4, 9, 5, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 10, 3, 6, 2, 2, 2, 2, 4, 1, 1, 1, 5, 2, 2, 2, 1, 1, 1, 1, 22, 5, 1, 1, 1, 5, 1, 2, 2, 2, 2, 2, 2, 2, 1, 9, 5, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 2, 3, 3, 1, 24, 5, 3, 15, 4, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 3, 1, 1, 1, 1, 3, 45, 7, 3, 3, 3, 3, 4, 1, 1, 1, 2, 1, 12, 4, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 3, 1, 1, 8, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 25, 4, 2, 3, 1, 1, 1, 5, 3, 6, 3, 3, 3, 5, 2, 2, 1, 1, 3, 3, 3, 1, 1, 2, 17, 4, 1, 1, 2, 1, 7, 1, 2, 2, 2, 2, 3, 2, 1, 1, 1, 3, 2, 71, 2, 3, 2, 2, 8, 2, 2, 2, 4, 4, 9, 2, 5, 4, 2, 2, 2, 5, 3, 2, 2, 1, 1, 5, 6, 3, 3, 15, 3, 3, 3, 6, 6, 6, 1, 1, 1, 68, 5, 4, 11, 9, 7, 3, 7, 1, 8, 6, 19, 2, 2, 7, 6, 3, 2, 2, 2, 3, 3, 2, 2, 6, 2, 1, 1, 37, 4, 12, 2, 2, 3, 5, 2, 4, 2, 1, 3, 2, 3, 21, 6, 1, 3, 3, 3, 3, 5, 5, 4, 10, 7, 6, 4, 3, 1, 1, 1, 4, 3, 1, 1, 5, 1, 1, 1, 2, 26, 10, 6, 4, 4, 7, 3, 2, 4, 4, 4, 4, 9, 5, 2, 2, 10, 2, 3, 3, 1, 2, 9, 7, 2, 1, 124,8, 3, 3, 2, 1, 1, 10, 6, 2, 2, 2, 2, 2, 1, 36, 1, 1, 1, 18, 12, 9, 7, 3, 2, 11, 8, 3, 2, 1, 13, 6, 3, 32, 6, 1, 2, 14, 3, 7, 5, 4, 5, 1, 5, 4, 3, 10, 2, 7, 5, 5, 16, 2, 5, 3, 3, 2, 1, 1, 1, 1, 2, 1, 9, 3, 1, 1, 1, 1, 2, 1, 1, 2, 1, 11, 2, 2, 2, 4, 2, 94, 8, 6, 2, 68, 2, 1, 3, 5, 4, 2, 1, 1, 1, 43, 4, 2, 2, 2, 8, 2, 5, 1, 3, 3, 2, 4, 2, 4, 1, 3, 4, 2, 2, 2, 5, 3, 2, 4, 2, 1, 7, 5, 2, 1, 3, 2, 1, 4, 2, 3, 3, 2, 5, 4, 15, 5, 4, 2, 2, 9, 2, 3, 3, 3, 19, 2, 1, 2, 12, 4, 2, 10, 5, 1, 1, 1, 1, 9, 2, 2, 3, 2, 1, 2, 4, 2, 2, 16, 6, 2, 4, 4, 4, 1, 8, 1, 3, 2, 2, 12, 6, 2, 1, 2, 1, 1, 8, 3, 2, 1, 1, 6, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 13, 4, 3, 3, 1, 3, 2, 1, 1, 2, 5, 3, 3, 5, 4, 4, 1, 55, 3, 3, 6, 14, 7, 3, 4, 3, 12, 3, 3, 4, 1, 1, 38, 5, 3, 2, 1, 2, 2, 6, 2, 2, 2, 3, 6, 5, 6, 5, 4, 13, 1, 7, 7, 3, 3, 1, 1, 1, 1, 1, 1, 9, 2, 7, 5, 5, 5, 3, 2, 3, 5, 16, 7, 7, 7, 2, 4, 3, 12, 6, 2, 33, 11, 3, 4, 2, 2, 1, 2, 5, 2, 2, 1, 1, 3, 3, 2, 2, 80, 5, 2, 8, 4, 3, 3, 2, 14, 2, 2, 4, 2, 2, 2, 4, 4, 3, 3, 27, 6, 4, 4, 4, 1, 7, 6, 2, 2, 2, 2, 2, 9, 9, 4, 4, 125,5, 2, 1, 1, 8, 4, 16, 4, 5, 3, 3, 8, 2, 1, 2, 1, 9, 1, 2, 2, 5, 11, 4, 4, 3, 2, 15, 3, 4, 5, 6, 5, 3, 6, 2, 15, 5, 3, 3, 2, 1, 9, 2, 1, 4, 1, 2, 2, 2, 25, 3, 2, 3, 2, 4, 3, 6, 5, 4, 8, 2, 1, 18, 7, 6, 2, 2, 2, 5, 5, 1, 1, 1, 1, 13, 4, 3, 9, 6, 6, 5, 57, 6, 6, 4, 3, 6, 3, 1, 1, 1, 5, 2, 15, 4, 3, 3, 19, 5, 5, 14, 14, 8, 21, 5, 12, 10, 4, 4, 3, 2, 28, 3, 4, 7, 4, 2, 2, 8, 2, 17, 2, 5, 5, 4, 4, 3, 4, 3, 9, 5, 3, 71, 5, 2, 62, 49, 2, 1, 5, 6, 1, 9, 4, 2, 2, 3, 7, 2, 1, 6, 3, 2, 2, 2, 1, 1, 1, 9, 2, 1, 1, 3, 47, 16, 12, 7, 4, 5, 1, 4, 4, 2, 2, 8, 3, 3, 3, 2, 2, 2, 21, 21, 9, 9, 6, 7, 4, 4, 35, 4, 1, 15, 11, 3, 7, 3, 2, 1, 7, 7, 2, 4, 40, 4, 6, 2, 3, 2, 2, 2, 16, 4, 4, 10, 2, 5, 5, 5, 29, 12, 7, 5, 3, 3, 6, 1, 2, 26, 7, 3, 6, 2, 11, 4, 3, 5, 3, 3, 2, 2, 1, 1, 6, 5, 2, 1, 173,6, 6, 1, 1, 2, 5, 4, 15, 7, 5, 1, 1, 2, 4, 16, 5, 4, 3, 3, 3, 3, 48, 2, 4, 3, 3, 2, 1, 2, 7, 2, 2, 11, 4, 3, 4, 9, 6, 6, 3, 13, 1, 1, 4, 1, 1, 1, 1, 8, 2, 4, 4, 15, 2, 2, 1, 2, 8, 2, 3, 11, 6, 9, 2, 7, 4, 5, 3, 3, 3, 9, 2, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 9, 2, 2, 1, 1, 3, 5, 5, 5, 4, 3, 2, 1, 1, 1, 1, 1, 7, 3, 2, 14, 5, 2, 6, 1, 26, 8, 3, 1, 4, 6, 3, 1, 5, 3, 28, 8, 2, 6, 2, 2, 2, 1, 1, 1, 16, 11, 4, 1, 1, 1, 40, 3, 2, 5, 2, 2, 19, 1, 1, 1, 15, 12, 3, 142,2, 5, 6, 25, 3, 6, 8, 2, 2, 3, 1, 2, 21, 8, 2, 5, 4, 33, 6, 3, 9, 6, 4, 3, 3, 2, 3, 2, 7, 3, 27, 7, 7, 3, 5, 2, 2, 8, 3, 3, 48, 2, 11, 7, 6, 5, 6, 6, 4, 11, 4, 1, 1, 2, 1, 1, 110,4, 1, 2, 13, 4, 3, 2, 10, 4, 2, 7, 2, 4, 2, 1, 5, 4, 3, 11, 3, 16, 7, 3, 3, 16, 4, 4, 3, 8, 3, 4, 2, 8, 3, 1, 61, 3, 18, 2, 2, 5, 5, 4, 5, 1, 3, 2, 12, 3, 2, 9, 6, 1, 1, 65, 1, 13, 13, 26, 26, 3, 1, 1, 5, 3, 2, 1, 1, 4, 2, 8, 3, 1, 2, 2, 3, 2, 12, 7, 2, 2, 2, 2, 2, 1, 1, 1, 5, 20, 3, 3, 5, 4, 56, 12, 5, 3, 3, 3, 4, 4, 7, 5, 17, 17, 17, 17, 3, 2, 2, 4, 5, 15, 4, 2, 2, 2, 5, 3, 2, 27, 10, 4, 1, 5, 5, 5, 2, 2, 2, 6, 4, 4, 4, 4, 4, 37, 7, 10, 4, 3, 4, 2, 10, 4, 2, 1, 1, 15, 2, 9, 3, 116,5, 4, 9, 5, 18, 2, 2, 10, 6, 5, 22, 4, 4, 4, 2, 2, 3, 2, 1, 13, 10, 4, 27, 2, 6, 3, 3, 2, 1, 3, 2, 1, 7, 2, 1, 1, 4, 4, 57, 8, 5, 5, 4, 8, 2, 22, 5, 5, 4, 4, 2, 3, 2, 7, 4, 1, 1, 1, 54, 2, 21, 5, 3, 3, 5, 2, 2, 5, 3, 3, 2, 9, 2, 3, 3, 2, 26, 2, 4, 2, 2, 2, 3, 5, 3, 2, 8, 3, 84, 3, 2, 3, 3, 2, 2, 12, 5, 4, 59, 8, 3, 3, 12, 11, 2, 2, 6, 2, 4, 1, 3, 4, 2, 2, 7, 19, 6, 5, 5, 2, 3, 2, 9, 2, 1, 1, 1, 1, 1, 68, 6, 6, 6, 3, 6, 4, 3, 6, 1, 37, 9, 4, 3, 2, 1, 1, 1, 1, 1, 2, 2, 5, 2, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 66, 6, 4, 2, 22, 18, 1, 2, 2, 6, 3, 5, 6, 15, 13, 4, 2, 1, 1, 8, 4, 2, 184,5, 4, 4, 3, 6, 3, 7, 4, 10, 5, 4, 4, 27, 3, 3, 7, 2, 5, 5, 6, 113,3, 3, 4, 2, 47, 3, 1, 3, 7, 2, 7, 4, 8, 2, 1, 1, 1, 7, 5, 2, 5, 3, 24, 8, 4, 5, 6, 5, 3, 1, 1, 46, 4, 1, 1, 6, 5, 1, 1, 1, 3, 2, 11, 7, 3, 8, 3, 8, 2, 4, 2, 3, 34, 3, 3, 5, 2, 25, 10, 2, 3, 5, 3, 2, 27, 1, 1, 10, 1, 4, 3, 5, 5, 52, 4, 2, 4, 1, 10, 7, 1, 1, 4, 2, 2, 2, 3, 4, 2, 6, 3, 8, 3, 1, 8, 6, 2, 5, 29, 18, 4, 4, 6, 4, 3, 2, 6, 214,9, 4, 7, 5, 34, 3, 3, 11, 1, 5, 2, 1, 1, 2, 2, 6, 4, 2, 2, 11, 6, 5, 5, 2, 2, 18, 9, 4, 5, 90, 6, 2, 19, 3, 5, 2, 32, 12, 7, 4, 4, 5, 4, 6, 2, 3, 7, 1, 1, 5, 10, 2, 5, 5, 12, 4, 2, 15, 6, 2, 2, 2, 4, 2, 173,6, 2, 5, 4, 3, 41, 2, 7, 3, 2, 3, 2, 8, 6, 3, 1, 1, 6, 2, 2, 1, 16, 3, 3, 3, 3, 2, 5, 3, 3, 23, 2, 9, 4, 6, 16, 6, 4, 4, 4, 16, 7, 8, 4, 15, 4, 5, 1, 15, 8, 8, 1, 1, 2, 2, 8, 20, 9, 5, 54, 3, 2, 1, 16, 3, 1, 2, 3, 13, 5, 2, 1, 7, 2, 84, 10, 9, 31, 7, 7, 6, 6, 12, 9, 3, 3, 2, 2, 2, 2, 2, 26, 8, 3, 4, 3, 3, 4, 2, 4, 4, 3, 3, 2, 1, 1, 1, 1, 4, 5, 4, 4, 6, 3, 7, 7, 2, 2, 1, 1, 12, 1, 1, 1, 1, 4, 4, 1, 1, 5, 2, 73, 4, 2, 3, 2, 2, 3, 2, 27, 7, 3, 2, 5, 3, 3, 10, 2, 2, 1, 2, 1, 1, 3, 6, 3, 6, 5, 4, 21, 10, 7, 2, 6, 2, 4, 24, 15, 12, 12, 4, 1, 118,63, 5, 2, 3, 4, 3, 4, 5, 7, 2, 2, 8, 3, 2, 10, 1, 2, 2, 4, 3, 3, 3, 3, 2, 2, 26, 4, 4, 2, 3, 3, 2, 3, 5, 104,2, 2, 2, 2, 2, 11, 23, 2, 1, 4, 3, 8, 5, 13, 10, 4, 4, 15, 4, 8, 7, 7, 3, 16, 11, 3, 3, 2, 15, 11, 9, 4, 2, 2, 2, 2, 1, 1, 1, 1, 1, 93, 16, 2, 3, 3, 6, 2, 26, 6, 6, 6, 5, 6, 6, 6, 2, 32, 5, 3, 3, 2, 10, 6, 6, 5, 5, 4, 12, 15, 3, 9, 7, 1, 1, 1, 1, 1, 1, 25, 1, 1, 4, 3, 2, 2, 5, 2, 4, 4, 3, 1, 1, 1, 7, 1, 1, 5, 5, 4, 3, 1, 1, 1, 52, 3, 4, 3, 7, 3, 16, 4, 2, 2, 4, 2, 5, 2, 46, 1, 1, 1, 1, 1, 7, 6, 2, 5, 3, 2, 1, 8, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 2, 1, 9, 9, 8, 7, 5, 3, 3, 1, 1, 2, 10, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 129, 2, 1, 1, 7, 1, 3, 6, 3, 4, 7, 3, 1, 4, 2, 1, 2, 22, 17, 3, 3, 2, 3, 2, 30, 3, 3, 12, 8, 5, 3, 3, 17, 6, 2, 5, 8, 3, 2, 2, 4, 4, 4, 3, 120,42, 5, 24, 1, 3, 3, 2, 6, 4, 1, 3, 23, 16, 7, 5, 2, 3, 1, 3, 13, 2, 4, 13, 4, 4, 4, 5, 2, 2, 1, 1, 1, 1, 1, 1, 14, 4, 4, 3, 3, 1, 2, 135,2, 7, 3, 2, 3, 5, 3, 8, 3, 5, 3, 3, 9, 3, 12, 7, 12, 1, 9, 9, 54, 23, 14, 2, 8, 3, 6, 4, 5, 5, 1, 1, 1, 1, 2, 1, 1, 36, 2, 3, 2, 1, 1, 1, 1, 1, 5, 3, 2, 3, 5, 2, 5, 3, 2, 2, 5, 2, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 7, 7, 6, 4, 3, 1, 153, 11, 3, 2, 2, 2, 2, 1, 1, 20, 2, 12, 5, 5, 2, 3, 5, 4, 1, 1, 4, 10, 5, 1, 1, 4, 2, 5, 1, 1, 2, 1, 7, 4, 15, 3, 2, 2, 5, 3, 3, 1, 1, 19, 2, 2, 5, 1, 1, 13, 3, 7, 1, 5, 3, 2, 15, 4, 2, 2, 3, 1, 1, 3, 2, 1, 4, 4, 3, 3, 1, 2, 1, 2, 2, 2, 1, 120,2, 2, 4, 4, 5, 2, 3, 2, 9, 3, 1, 26, 3, 4, 3, 2, 10, 3, 2, 4, 23, 2, 4, 3, 2, 4, 4, 2, 2, 6, 3, 2, 17, 1, 6, 1, 3, 7, 4, 2, 2, 18, 5, 3, 3, 1, 2, 7, 91, 2, 2, 1, 17, 4, 3, 1, 3, 4, 5, 7, 1, 4, 10, 2, 1, 6, 5, 5, 21, 1, 1, 7, 3, 3, 1, 2, 12, 10, 4, 5, 3, 2, 2, 2, 1, 1, 1, 2, 58, 2, 6, 2, 2, 11, 7, 2, 1, 3, 3, 8, 2, 9, 2, 4, 1, 11, 2, 1, 1, 1, 1, 1, 1, 52, 4, 9, 4, 3, 5, 3, 3, 1, 2, 1, 3, 3, 7, 2, 2, 4, 3, 2, 6, 2, 1, 12, 1, 1, 3, 2, 2, 5, 2, 1, 1, 1, 8, 7, 5, 4, 3, 1, 6, 8, 8, 1, 7, 6, 4 }; static const char CharSet[48] = "!&'*+-.0123456789;?\\_abcdefghijklmnopqrstuvwxyz"; #define ROOT_NODE_LOC 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/zxcvbn.cpp���������������������������������������������������������0000664�0000000�0000000�00000170500�15162662266�0020553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off /********************************************************************************** * C implementation of the zxcvbn password strength estimation method. * Copyright (c) 2015-2017 Tony Evans * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **********************************************************************************/ #include "zxcvbn.h" #include <ctype.h> #include <string.h> #include <stdint.h> #include <math.h> #include <float.h> #ifdef USE_DICT_FILE #if defined(USE_FILE_IO) || !defined(__cplusplus) #include <stdio.h> #else #include <fstream> #endif #endif #ifdef _MSC_VER #pragma warning(disable: 4244) // conversion from '__int64' to 'int', possible loss of data (in 64 bit build) #pragma warning(disable: 4267) // conversion from 'size_t' to 'int', possible loss of data (in 64 bit build) #endif /* Minimum number of characters in a incrementing/decrementing sequence match */ #define MIN_SEQUENCE_LEN 3 /* Year range for data matching */ #define MIN_YEAR 1901 #define MAX_YEAR 2050 /* Minimum number of characters in a spatial matching sequence */ #define MIN_SPATIAL_LEN 3 /* Minimum number of characters in a repeat sequence match */ #define MIN_REPEAT_LEN 2 /* Additional entropy to add when password is made of multiple matches. Use different * amounts depending on whether the match is at the end of the password, or in the * middle. If the match is at the begining then there is no additional entropy. */ #define MULTI_END_ADDITION 1.0 #define MULTI_MID_ADDITION 1.75 /*################################################################################* *################################################################################* * Begin utility function code *################################################################################* *################################################################################*/ /********************************************************************************** * Binomial coefficient function. Uses method described at * http://blog.plover.com/math/choose.html */ static double nCk(int n, int k) { int d; double r; if (k > n) return 0.0; if (!k) return 1.0; r = 1.0; for(d = 1; d <= k; ++d) { r *= n--; r /= d; } return r; } /********************************************************************************** * Binary search function to find a character in a string. * Parameters: * Ch The character to find * Ents The string to search * NumEnts The number character groups in the string Ents * SizeEnt The size of each character group. * Returns a pointer to the found character, or null if not found. */ static const uint8_t *CharBinSearch(uint8_t Ch, const uint8_t *Ents, unsigned int NumEnts, unsigned int SizeEnt) { while(NumEnts > 0) { const uint8_t *Mid = Ents + (NumEnts >> 1) * SizeEnt; int Dif = Ch - *Mid; if (!Dif) { return Mid; } if (Dif > 0) { Ents = Mid + SizeEnt; --NumEnts; } NumEnts /= 2; } return 0; } /********************************************************************************** * Calculate potential number of different characters in the passed string. * Parameters: * Str The string of characters * Len The maximum number of characters to scan * Returns the potential number of different characters in the string. */ static int Cardinality(const uint8_t *Str, int Len) { int Card=0, Types=0; int c; while(Len > 0) { c = *Str++ & 0xFF; if (!c) break; if (islower(c)) Types |= 1; /* Lowercase letter */ else if (isupper(c)) Types |= 2; /* Uppercase letter */ else if (isdigit(c)) Types |= 4; /* Numeric digit */ else if (c <= 0x7F) Types |= 8; /* Punctuation character */ else Types |= 16; /* Other (Unicode?) */ --Len; } if (Types & 1) Card += 26; if (Types & 2) Card += 26; if (Types & 4) Card += 10; if (Types & 8) Card += 33; if (Types & 16) Card += 100; return Card; } /********************************************************************************** * Allocate a ZxcMatch_t struct, clear it to zero */ static ZxcMatch_t *AllocMatch() { ZxcMatch_t *p = MallocFn(ZxcMatch_t, 1); memset(p, 0, sizeof *p); return p; } /********************************************************************************** * Add new match struct to linked list of matches. List ordered with shortest at * head of list. Note: passed new match struct in parameter Nu may be de allocated. */ static void AddResult(ZxcMatch_t **HeadRef, ZxcMatch_t *Nu, int MaxLen) { /* Adjust the entropy to be used for calculations depending on whether the passed match is * at the begining, middle or end of the password */ if (Nu->Begin) { if (Nu->Length >= MaxLen) Nu->MltEnpy = Nu->Entrpy + MULTI_END_ADDITION * log(2.0); else Nu->MltEnpy = Nu->Entrpy + MULTI_MID_ADDITION * log(2.0); } else Nu->MltEnpy = Nu->Entrpy; /* Find the correct insert point */ while(*HeadRef && ((*HeadRef)->Length < Nu->Length)) HeadRef = &((*HeadRef)->Next); /* Add new entry or replace existing */ if (*HeadRef && ((*HeadRef)->Length == Nu->Length)) { /* New entry has same length as existing, so one of them needs discarding */ if ((*HeadRef)->MltEnpy <= Nu->MltEnpy) { /* Existing entry has lower entropy - keep it, discard new entry */ FreeFn(Nu); } else { /* New entry has lower entropy - replace existing entry */ Nu->Next = (*HeadRef)->Next; FreeFn(*HeadRef); *HeadRef = Nu; } } else { /* New entry has different length, so add it */ Nu->Next = *HeadRef; *HeadRef = Nu; } } /********************************************************************************** * See if the match is repeated. If it is then add a new repeated match to the results. */ static void AddMatchRepeats(ZxcMatch_t **Result, ZxcMatch_t *Match, const uint8_t *Passwd, int MaxLen) { int Len = Match->Length; const uint8_t *Rpt = Passwd + Len; int RepeatCount = 2; while(MaxLen >= (Len * RepeatCount)) { if (strncmp((const char *)Passwd, (const char *)Rpt, Len) == 0) { /* Found a repeat */ ZxcMatch_t *p = AllocMatch(); p->Entrpy = Match->Entrpy + log((double)RepeatCount); p->Type = (ZxcTypeMatch_t)(Match->Type + MULTIPLE_MATCH); p->Length = Len * RepeatCount; p->Begin = Match->Begin; AddResult(Result, p, MaxLen); } else break; ++RepeatCount; Rpt += Len; } } /*################################################################################* *################################################################################* * Begin dictionary matching code *################################################################################* *################################################################################*/ #ifdef USE_DICT_FILE /* Use dictionary data from file */ #if defined(USE_FILE_IO) || !defined(__cplusplus) /* Use the FILE streams from stdio.h */ typedef FILE *FileHandle; #define MyOpenFile(f, name) (f = fopen(name, "rb")) #define MyReadFile(f, buf, bytes) (fread(buf, 1, bytes, f) == (bytes)) #define MyCloseFile(f) fclose(f) #else /* Use the C++ iostreams */ typedef std::ifstream FileHandle; static inline void MyOpenFile(FileHandle & f, const char *Name) { f.open(Name, std::ifstream::in | std::ifstream::binary); } static inline bool MyReadFile(FileHandle & f, void *Buf, unsigned int Num) { return (bool)f.read((char *)Buf, Num); } static inline void MyCloseFile(FileHandle & f) { f.close(); } #endif /* Include file contains the CRC of the dictionary data file. Used to detect corruption */ /* of the file. */ #include "dict-crc.h" #define MAX_DICT_FILE_SIZE (100+WORD_FILE_SIZE) #define CHK_INIT 0xffffffffffffffffULL /* Static table used for the crc implementation. */ static const uint64_t CrcTable[16] = { 0x0000000000000000ULL, 0x7d08ff3b88be6f81ULL, 0xfa11fe77117cdf02ULL, 0x8719014c99c2b083ULL, 0xdf7adabd7a6e2d6fULL, 0xa2722586f2d042eeULL, 0x256b24ca6b12f26dULL, 0x5863dbf1e3ac9decULL, 0x95ac9329ac4bc9b5ULL, 0xe8a46c1224f5a634ULL, 0x6fbd6d5ebd3716b7ULL, 0x12b5926535897936ULL, 0x4ad64994d625e4daULL, 0x37deb6af5e9b8b5bULL, 0xb0c7b7e3c7593bd8ULL, 0xcdcf48d84fe75459ULL }; static const unsigned int MAGIC = 'z' + ('x'<< 8) + ('c' << 16) + ('v' << 24); static unsigned int NumNodes, NumChildLocs, NumRanks, NumWordEnd, NumChildMaps; static unsigned int SizeChildMapEntry, NumLargeCounts, NumSmallCounts, SizeCharSet; static unsigned int *DictNodes; static uint8_t *WordEndBits; static unsigned int *ChildLocs; static unsigned short *Ranks; static uint8_t *ChildMap; static uint8_t *EndCountLge; static uint8_t *EndCountSml; static char *CharSet; /********************************************************************************** * Calculate the CRC-64 of passed data. * Parameters: * Crc The initial or previous CRC value * v Pointer to the data to add to CRC calculation * Len Length of the passed data * Returns the updated CRC value. */ static uint64_t CalcCrc64(uint64_t Crc, const void *v, unsigned int Len) { const uint8_t *Data = (const unsigned char *)v; while(Len--) { Crc = CrcTable[(Crc ^ (*Data >> 0)) & 0x0f] ^ (Crc >> 4); Crc = CrcTable[(Crc ^ (*Data >> 4)) & 0x0f] ^ (Crc >> 4); ++Data; } return Crc; } /********************************************************************************** * Read the dictionary data from file. * Parameters: * Filename Name of the file to read. * Returns 1 on success, 0 on error */ int ZxcvbnInit(const char *Filename) { FileHandle f; uint64_t Crc = CHK_INIT; if (DictNodes) return 1; MyOpenFile(f, Filename); if (f) { unsigned int i, DictSize; /* Get magic number */ if (!MyReadFile(f, &i, sizeof i)) i = 0; /* Get header data */ if (!MyReadFile(f, &NumNodes, sizeof NumNodes)) i = 0; if (!MyReadFile(f, &NumChildLocs, sizeof NumChildLocs)) i = 0; if (!MyReadFile(f, &NumRanks, sizeof NumRanks)) i = 0; if (!MyReadFile(f, &NumWordEnd, sizeof NumWordEnd)) i = 0; if (!MyReadFile(f, &NumChildMaps, sizeof NumChildMaps)) i = 0; if (!MyReadFile(f, &SizeChildMapEntry, sizeof SizeChildMapEntry)) i = 0; if (!MyReadFile(f, &NumLargeCounts, sizeof NumLargeCounts)) i = 0; if (!MyReadFile(f, &NumSmallCounts, sizeof NumSmallCounts)) i = 0; if (!MyReadFile(f, &SizeCharSet, sizeof SizeCharSet)) i = 0; /* Validate the header data */ if (NumNodes >= (1<<17)) i = 1; if (NumChildLocs >= (1<<BITS_CHILD_MAP_INDEX)) i = 2; if (NumChildMaps >= (1<<BITS_CHILD_PATT_INDEX)) i = 3; if ((SizeChildMapEntry*8) < SizeCharSet) i = 4; if (NumLargeCounts >= (1<<9)) i = 5; if (NumSmallCounts != NumNodes) i = 6; if (i != MAGIC) { MyCloseFile(f); return 0; } Crc = CalcCrc64(Crc, &i, sizeof i); Crc = CalcCrc64(Crc, &NumNodes, sizeof NumNodes); Crc = CalcCrc64(Crc, &NumChildLocs, sizeof NumChildLocs); Crc = CalcCrc64(Crc, &NumRanks, sizeof NumRanks); Crc = CalcCrc64(Crc, &NumWordEnd, sizeof NumWordEnd); Crc = CalcCrc64(Crc, &NumChildMaps, sizeof NumChildMaps); Crc = CalcCrc64(Crc, &SizeChildMapEntry, sizeof SizeChildMapEntry); Crc = CalcCrc64(Crc, &NumLargeCounts, sizeof NumLargeCounts); Crc = CalcCrc64(Crc, &NumSmallCounts, sizeof NumSmallCounts); Crc = CalcCrc64(Crc, &SizeCharSet, sizeof SizeCharSet); DictSize = NumNodes*sizeof(*DictNodes) + NumChildLocs*sizeof(*ChildLocs) + NumRanks*sizeof(*Ranks) + NumWordEnd + NumChildMaps*SizeChildMapEntry + NumLargeCounts + NumSmallCounts + SizeCharSet; if (DictSize < MAX_DICT_FILE_SIZE) { DictNodes = MallocFn(unsigned int, DictSize / sizeof(unsigned int) + 1); if (!MyReadFile(f, DictNodes, DictSize)) { FreeFn(DictNodes); DictNodes = 0; } } MyCloseFile(f); if (!DictNodes) return 0; /* Check crc */ Crc = CalcCrc64(Crc, DictNodes, DictSize); if (memcmp(&Crc, WordCheck, sizeof Crc)) { /* File corrupted */ FreeFn(DictNodes); DictNodes = 0; return 0; } fflush(stdout); /* Set pointers to the data */ ChildLocs = DictNodes + NumNodes; Ranks = (unsigned short *)(ChildLocs + NumChildLocs); WordEndBits = (unsigned char *)(Ranks + NumRanks); ChildMap = (unsigned char*)(WordEndBits + NumWordEnd); EndCountLge = ChildMap + NumChildMaps*SizeChildMapEntry; EndCountSml = EndCountLge + NumLargeCounts; CharSet = (char *)EndCountSml + NumSmallCounts; CharSet[SizeCharSet] = 0; return 1; } return 0; } /********************************************************************************** * Free the data allocated by ZxcvbnInit(). */ void ZxcvbnUnInit() { if (DictNodes) FreeFn(DictNodes); DictNodes = 0; } #else /* Include the source file containing the dictionary data */ #include "dict-src.h" #endif /********************************************************************************** * Leet conversion strings */ /* String of normal chars that could be given as leet chars in the password */ static const uint8_t L33TChr[] = "abcegilostxz"; /* String of leet,normal,normal char triples. Used to convert supplied leet char to normal. */ static const uint8_t L33TCnv[] = "!i $s %x (c +t 0o 1il2z 3e 4a 5s 6g 7lt8b 9g <c @a [c {c |il"; #define LEET_NORM_MAP_SIZE 3 /* Struct holding additional data on the word match */ typedef struct { int Rank; /* Rank of word in dictionary */ int Caps; /* Number of capital letters */ int Lower; /* Number of lower case letters */ int NumLeet; /* Total number of leeted characters */ uint8_t Leeted[sizeof L33TChr]; /* Number of leeted chars for each char found in L33TChr */ uint8_t UnLeet[sizeof L33TChr]; /* Number of normal chars for each char found in L33TChr */ } DictMatchInfo_t; /* Struct holding working data for the word match */ typedef struct { uint32_t StartLoc; int Ordinal; int PwdLength; int Begin; int Caps; int Lower; int NumPossChrs; uint8_t Leeted[sizeof L33TChr]; uint8_t UnLeet[sizeof L33TChr]; uint8_t LeetCnv[sizeof L33TCnv / LEET_NORM_MAP_SIZE + 1]; uint8_t First; uint8_t PossChars[CHARSET_SIZE]; } DictWork_t; /********************************************************************************** * Given a map entry create a string of all possible characters for following to * a child node */ static int ListPossibleChars(uint8_t *List, const uint8_t *Map) { unsigned int i, j, k; int Len = 0; for(k = i = 0; i < SizeChildMapEntry; ++i, ++Map) { if (!*Map) { k += 8; continue; } for(j = 0; j < 8; ++j) { if (*Map & (1<<j)) { *List++ = CharSet[k]; ++Len; } ++k; } } *List=0; return Len; } /********************************************************************************** * Increment count of each char that could be leeted. */ static void AddLeetChr(uint8_t c, int IsLeet, uint8_t *Leeted, uint8_t *UnLeet) { const uint8_t *p = CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1); if (p) { size_t i = p - L33TChr; if (IsLeet > 0) { Leeted[i] = static_cast<uint8_t>(Leeted[i] + 1); } else if (IsLeet < 0) { Leeted[i] = static_cast<uint8_t>(Leeted[i] + 1); UnLeet[i] = static_cast<uint8_t>(UnLeet[i] - 1); } else { UnLeet[i] = static_cast<uint8_t>(UnLeet[i] + 1); } } } /********************************************************************************** * Given details of a word match, update it with the entropy (as natural log of * number of possiblities) */ static void DictionaryEntropy(ZxcMatch_t *m, DictMatchInfo_t *Extra, const uint8_t *Pwd) { double e = 0.0; /* Add allowance for uppercase letters */ if (Extra->Caps) { if (Extra->Caps == m->Length) { /* All uppercase, common case so only 1 bit */ e += log(2.0); } else if ((Extra->Caps == 1) && (isupper(*Pwd) || isupper(Pwd[m->Length - 1]))) { /* Only first or last uppercase, also common so only 1 bit */ e += log(2.0); } else { /* Get number of combinations of lowercase, uppercase letters */ int Up = Extra->Caps; int Lo = Extra->Lower; int i = Up; if (i > Lo) i = Lo; for(Lo += Up; i >= 0; --i) e += nCk(Lo, i); if (e > 0.0) e = log(e); } } /* Add allowance for using leet substitution */ if (Extra->NumLeet) { int i; double d = 0.0; for(i = sizeof Extra->Leeted - 1; i >= 0; --i) { int Sb = Extra->Leeted[i]; if (Sb) { int Un = Extra->UnLeet[i]; int j = m->Length - Extra->NumLeet; if ((j >= 0) && (Un > j)) Un = j; j = Sb; if (j > Un) j = Un; for(Un += Sb; j >= 0; --j) { double z = nCk(Un, j); d += z; } } } if (d > 0.0) d = log(d); if (d < log(2.0)) d = log(2.0); e += d; } /* Add entropy due to word's rank */ e += log((double)Extra->Rank); m->Entrpy = e; } /********************************************************************************** * Function that does the word matching */ static void DoDictMatch(const uint8_t *Passwd, int Start, int MaxLen, DictWork_t *Wrk, ZxcMatch_t **Result, DictMatchInfo_t *Extra, int Lev) { int Len; uint8_t TempLeet[LEET_NORM_MAP_SIZE]; int Ord = Wrk->Ordinal; int Caps = Wrk->Caps; int Lower = Wrk->Lower; unsigned int NodeLoc = Wrk->StartLoc; uint8_t *PossChars = Wrk->PossChars; int NumPossChrs = Wrk->NumPossChrs; const uint8_t *Pwd = Passwd; uint32_t NodeData = DictNodes[NodeLoc]; Passwd += Start; for(Len = 0; *Passwd && (Len < MaxLen); ++Len, ++Passwd) { uint8_t c; int w, x, y; const uint8_t *q; if (!Len && Wrk->First) { c = Wrk->First; } else { /* Get char and set of possible chars at current point in word. */ const uint8_t *Bmap; c = *Passwd; Bmap = ChildMap + (NodeData & ((1<<BITS_CHILD_PATT_INDEX)-1)) * SizeChildMapEntry; NumPossChrs = ListPossibleChars(PossChars, Bmap); /* Make it lowercase and update lowercase, uppercase counts */ if (isupper(c)) { c = static_cast<uint8_t>(tolower(c)); ++Caps; } else if (islower(c)) { ++Lower; } /* See if current char is a leet and can be converted */ q = CharBinSearch(c, L33TCnv, sizeof L33TCnv / LEET_NORM_MAP_SIZE, LEET_NORM_MAP_SIZE); if (q) { /* Found, see if used before */ unsigned int j; unsigned int i = static_cast<unsigned int>((q - L33TCnv ) / LEET_NORM_MAP_SIZE); if (Wrk->LeetCnv[i]) { /* Used before, so limit characters to try */ TempLeet[0] = c; TempLeet[1] = Wrk->LeetCnv[i]; TempLeet[2] = 0; q = TempLeet; } for(j = 0; (*q > ' ') && (j < LEET_NORM_MAP_SIZE); ++j, ++q) { const uint8_t *r = CharBinSearch(*q, PossChars, NumPossChrs, 1); if (r) { /* valid conversion from leet */ DictWork_t w; w = *Wrk; w.StartLoc = NodeLoc; w.Ordinal = Ord; w.PwdLength += Len; w.Caps = Caps; w.Lower = Lower; w.First = *r; w.NumPossChrs = NumPossChrs; memcpy(w.PossChars, PossChars, sizeof w.PossChars); if (j) { w.LeetCnv[i] = *r; AddLeetChr(*r, -1, w.Leeted, w.UnLeet); } DoDictMatch(Pwd, static_cast<int>(Passwd - Pwd), MaxLen - Len, &w, Result, Extra, Lev+1); } } return; } } q = CharBinSearch(c, PossChars, NumPossChrs, 1); if (q) { /* Found the char as a normal char */ if (CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1)) { /* Char matches, but also a normal equivalent to a leet char */ AddLeetChr(c, 0, Wrk->Leeted, Wrk->UnLeet); } } if (!q) { /* No match for char - return */ return; } /* Add all the end counts of the child nodes before the one that matches */ x = static_cast<int>(q - Wrk->PossChars); y = (NodeData >> BITS_CHILD_PATT_INDEX) & ((1 << BITS_CHILD_MAP_INDEX) - 1); NodeLoc = ChildLocs[x+y]; for(w=0; w<x; ++w) { unsigned int Cloc = ChildLocs[w+y]; int z = EndCountSml[Cloc]; if (Cloc < NumLargeCounts) z += EndCountLge[Cloc]*256; Ord += z; } /* Move to next node */ NodeData = DictNodes[NodeLoc]; if (WordEndBits[NodeLoc >> 3] & (1<<(NodeLoc & 7))) { /* Word matches, save result */ unsigned int v; ZxcMatch_t *p; v = Ranks[Ord]; if (v & (1<<15)) v = (v & ((1 << 15) - 1)) * 4 + (1 << 15); Extra->Caps = Caps; Extra->Rank = v; Extra->Lower = Lower; for(x = 0, y = sizeof Extra->Leeted - 1; y >= 0; --y) x += Wrk->Leeted[y]; Extra->NumLeet = x; memcpy(Extra->UnLeet, Wrk->UnLeet, sizeof Extra->UnLeet); memcpy(Extra->Leeted, Wrk->Leeted, sizeof Extra->Leeted); p = AllocMatch(); if (x) p->Type = DICT_LEET_MATCH; else p->Type = DICTIONARY_MATCH; p->Length = Wrk->PwdLength + Len + 1; p->Begin = Wrk->Begin; DictionaryEntropy(p, Extra, Pwd); AddMatchRepeats(Result, p, Pwd, MaxLen); AddResult(Result, p, MaxLen); ++Ord; } } } /********************************************************************************** * Try to match password part with user supplied dictionary words * Parameters: * Result Pointer head of linked list used to store results * Words Array of pointers to dictionary words * Passwd The start of the password * Start Where in the password to start attempting to match * MaxLen Maximum number characters to consider */ static void UserMatch(ZxcMatch_t **Result, const char *Words[], const uint8_t *Passwd, int Start, int MaxLen) { int Rank; if (!Words) return; Passwd += Start; for(Rank = 0; Words[Rank]; ++Rank) { DictMatchInfo_t Extra; uint8_t LeetChr[sizeof L33TCnv / LEET_NORM_MAP_SIZE + 1]; uint8_t TempLeet[3]; int Len = 0; int Caps = 0; int Lowers = 0; int Leets = 0; const uint8_t *Wrd = (const uint8_t *)(Words[Rank]); const uint8_t *Pwd = Passwd; memset(Extra.Leeted, 0, sizeof Extra.Leeted); memset(Extra.UnLeet, 0, sizeof Extra.UnLeet); memset(LeetChr, 0, sizeof LeetChr); while(*Wrd) { const uint8_t *q; uint8_t d = static_cast<uint8_t>(tolower(*Wrd++)); uint8_t c = *Pwd++; if (isupper(c)) { c = static_cast<uint8_t>(tolower(c)); ++Caps; } else if (islower(c)) { ++Lowers; } /* See if current char is a leet and can be converted */ q = CharBinSearch(c, L33TCnv, sizeof L33TCnv / LEET_NORM_MAP_SIZE, LEET_NORM_MAP_SIZE); if (q) { /* Found, see if used before */ unsigned int j; unsigned int i = static_cast<unsigned int>((q - L33TCnv ) / LEET_NORM_MAP_SIZE); if (LeetChr[i]) { /* Used before, so limit characters to try */ TempLeet[0] = c; TempLeet[1] = LeetChr[i]; TempLeet[2] = 0; q = TempLeet; } c = static_cast<uint8_t>(d+1); for(j = 0; (*q > ' ') && (j < LEET_NORM_MAP_SIZE); ++j, ++q) { if (d == *q) { c = d; if (i) { LeetChr[i] = c; AddLeetChr(c, 1, Extra.Leeted, Extra.UnLeet); ++Leets; } break; } } if (c != d) { Len = 0; break; } } else if (c == d) { /* Found the char as a normal char */ if (CharBinSearch(c, L33TChr, sizeof L33TChr - 1, 1)) { /* Char matches, but also a normal equivalent to a leet char */ AddLeetChr(c, 0, Extra.Leeted, Extra.UnLeet); } } else { /* No Match */ Len = 0; break; } if (++Len > MaxLen) { Len = 0; break; } } if (Len) { ZxcMatch_t *p = AllocMatch(); if (!Leets) p->Type = USER_MATCH; else p->Type = USER_LEET_MATCH; p->Length = Len; p->Begin = Start; /* Add Entrpy */ Extra.Caps = Caps; Extra.Lower = Lowers; Extra.NumLeet = Leets; Extra.Rank = Rank+1; DictionaryEntropy(p, &Extra, Passwd); AddMatchRepeats(Result, p, Passwd, MaxLen); AddResult(Result, p, MaxLen); } } } /********************************************************************************** * Try to match password part with the dictionary words * Parameters: * Result Pointer head of linked list used to store results * Passwd The start of the password * Start Where in the password to start attempting to match * MaxLen Maximum number characters to consider */ static void DictionaryMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) { DictWork_t Wrk; DictMatchInfo_t Extra; memset(&Extra, 0, sizeof Extra); memset(&Wrk, 0, sizeof Wrk); Wrk.Ordinal = 1; Wrk.StartLoc = ROOT_NODE_LOC; Wrk.Begin = Start; DoDictMatch(Passwd+Start, 0, MaxLen, &Wrk, Result, &Extra, 0); } /*################################################################################* *################################################################################* * Begin keyboard spatial sequence matching code *################################################################################* *################################################################################*/ /* Struct to hold information on a keyboard layout */ typedef struct Keyboard { const uint8_t *Keys; const uint8_t *Shifts; int NumKeys; int NumNear; int NumShift; int NumBlank; } Keyboard_t; /* Struct to hold information on the match */ typedef struct { int Keyb; int Turns; int Shifts; } SpatialMatchInfo_t; /* Shift mapping, characters in pairs: first is shifted, second un-shifted. */ static const uint8_t UK_Shift[] = "!1\"2$4%5&7(9)0*8:;<,>.?/@'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~#€4£3¬`"; static const uint8_t US_Shift[] = "!1\"'#3$4%5&7(9)0*8:;<,>.?/@2AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz^6_-{[|\\}]~`"; /* Neighour tables */ static const uint8_t UK_Qwerty[48*7] = { /* key, left, up-left, up-right, right, down-right, down-left */ '#', '\'',']', 0, 0, 0, 0, '\'',';', '[', ']', '#', 0, '/', ',', 'm', 'k', 'l', '.', 0, 0, '-', '0', 0, 0, '-', 'p', 'o', '.', ',', 'l', ';', '/', 0, 0, '/', '.', ';', '\'', 0, 0, 0, '0', '9', 0, 0, '-', 'p', 'o', '1', '`', 0, 0, '2', 'q', 0, '2', '1', 0, 0, '3', 'w', 'q', '3', '2', 0, 0, '4', 'e', 'w', '4', '3', 0, 0, '5', 'r', 'e', '5', '4', 0, 0, '6', 't', 'r', '6', '5', 0, 0, '7', 'y', 't', '7', '6', 0, 0, '8', 'u', 'y', '8', '7', 0, 0, '9', 'i', 'u', '9', '8', 0, 0, '0', 'o', 'i', ';', 'l', 'o', 'p','\'', '/', '.', '=', '-', 0, 0, 0, ']', '[', '[', 'p', '-', '=', ']', '\'',';', '\\', 0, 0, 'a', 'z', 0, 0, ']', '[', '=', 0, 0, '#','\'', '`', 0, 0, 0, '1', 0, 0, 'a', 0, 'q', 'w', 's', 'z','\\', 'b', 'v', 'g', 'h', 'n', 0, 0, 'c', 'x', 'd', 'f', 'v', 0, 0, 'd', 's', 'e', 'r', 'f', 'c', 'x', 'e', 'w', '3', '4', 'r', 'd', 's', 'f', 'd', 'r', 't', 'g', 'v', 'c', 'g', 'f', 't', 'y', 'h', 'b', 'v', 'h', 'g', 'y', 'u', 'j', 'n', 'b', 'i', 'u', '8', '9', 'o', 'k', 'j', 'j', 'h', 'u', 'i', 'k', 'm', 'n', 'k', 'j', 'i', 'o', 'l', ',', 'm', 'l', 'k', 'o', 'p', ';', '.', ',', 'm', 'n', 'j', 'k', ',', 0, 0, 'n', 'b', 'h', 'j', 'm', 0, 0, 'o', 'i', '9', '0', 'p', 'l', 'k', 'p', 'o', '0', '-', '[', ';', 'l', 'q', 0, '1', '2', 'w', 'a', 0, 'r', 'e', '4', '5', 't', 'f', 'd', 's', 'a', 'w', 'e', 'd', 'x', 'z', 't', 'r', '5', '6', 'y', 'g', 'f', 'u', 'y', '7', '8', 'i', 'j', 'h', 'v', 'c', 'f', 'g', 'b', 0, 0, 'w', 'q', '2', '3', 'e', 's', 'a', 'x', 'z', 's', 'd', 'c', 0, 0, 'y', 't', '6', '7', 'u', 'h', 'g', 'z', '\\','a', 's', 'x', 0, 0 }; static const uint8_t US_Qwerty[47*7] = { /* key, left, up-left, up-right, right, down-right, down-left */ '\'',';', '[', ']', 0, 0, '/', ',', 'm', 'k', 'l', '.', 0, 0, '-', '0', 0, 0, '=', '[', 'p', '.', ',', 'l', ';', '/', 0, 0, '/', '.', ';','\'', 0, 0, 0, '0', '9', 0, 0, '-', 'p', 'o', '1', '`', 0, 0, '2', 'q', 0, '2', '1', 0, 0, '3', 'w', 'q', '3', '2', 0, 0, '4', 'e', 'w', '4', '3', 0, 0, '5', 'r', 'e', '5', '4', 0, 0, '6', 't', 'r', '6', '5', 0, 0, '7', 'y', 't', '7', '6', 0, 0, '8', 'u', 'y', '8', '7', 0, 0, '9', 'i', 'u', '9', '8', 0, 0, '0', 'o', 'i', ';', 'l', 'p', '[','\'', '/', '.', '=', '-', 0, 0, 0, ']', '[', '[', 'p', '-', '=', ']','\'', ';', '\\',']', 0, 0, 0, 0, 0, ']', '[', '=', 0,'\\', 0,'\'', '`', 0, 0, 0, '1', 0, 0, 'a', 0, 'q', 'w', 's', 'z', 0, 'b', 'v', 'g', 'h', 'n', 0, 0, 'c', 'x', 'd', 'f', 'v', 0, 0, 'd', 's', 'e', 'r', 'f', 'c', 'x', 'e', 'w', '3', '4', 'r', 'd', 's', 'f', 'd', 'r', 't', 'g', 'v', 'c', 'g', 'f', 't', 'y', 'h', 'b', 'v', 'h', 'g', 'y', 'u', 'j', 'n', 'b', 'i', 'u', '8', '9', 'o', 'k', 'j', 'j', 'h', 'u', 'i', 'k', 'm', 'n', 'k', 'j', 'i', 'o', 'l', ',', 'm', 'l', 'k', 'o', 'p', ';', '.', ',', 'm', 'n', 'j', 'k', ',', 0, 0, 'n', 'b', 'h', 'j', 'm', 0, 0, 'o', 'i', '9', '0', 'p', 'l', 'k', 'p', 'o', '0', '-', '[', ';', 'l', 'q', 0, '1', '2', 'w', 'a', 0, 'r', 'e', '4', '5', 't', 'f', 'd', 's', 'a', 'w', 'e', 'd', 'x', 'z', 't', 'r', '5', '6', 'y', 'g', 'f', 'u', 'y', '7', '8', 'i', 'j', 'h', 'v', 'c', 'f', 'g', 'b', 0, 0, 'w', 'q', '2', '3', 'e', 's', 'a', 'x', 'z', 's', 'd', 'c', 0, 0, 'y', 't', '6', '7', 'u', 'h', 'g', 'z', 0, 'a', 's', 'x', 0, 0, }; static const uint8_t Dvorak[48*7] = { '\'', 0, '1', '2', ',', 'a', 0, ',','\'', '2', '3', '.', 'o', 'a', '-', 's', '/', '=', 0, 0, 'z', '.', ',', '3', '4', 'p', 'e', 'o', '/', 'l', '[', ']', '=', '-', 's', '0', '9', 0, 0, '[', 'l', 'r', '1', '`', 0, 0, '2','\'', 0, '2', '1', 0, 0, '3', ',','\'', '3', '2', 0, 0, '4', '.', ',', '4', '3', 0, 0, '5', 'p', '.', '5', '4', 0, 0, '6', 'y', 'p', '6', '5', 0, 0, '7', 'f', 'y', '7', '6', 0, 0, '8', 'g', 'f', '8', '7', 0, 0, '9', 'c', 'g', '9', '8', 0, 0, '0', 'r', 'c', ';', 0, 'a', 'o', 'q', 0, 0, '=', '/', ']', 0,'\\', 0, '-', '[', '0', 0, 0, ']', '/', 'l', '\\','=', 0, 0, 0, 0, 0, ']', '[', 0, 0, 0, '=', '/', '`', 0, 0, 0, '1', 0, 0, 'a', 0,'\'', ',', 'o', ';', 0, 'b', 'x', 'd', 'h', 'm', 0, 0, 'c', 'g', '8', '9', 'r', 't', 'h', 'd', 'i', 'f', 'g', 'h', 'b', 'x', 'e', 'o', '.', 'p', 'u', 'j', 'q', 'f', 'y', '6', '7', 'g', 'd', 'i', 'g', 'f', '7', '8', 'c', 'h', 'd', 'h', 'd', 'g', 'c', 't', 'm', 'b', 'i', 'u', 'y', 'f', 'd', 'x', 'k', 'j', 'q', 'e', 'u', 'k', 0, 0, 'k', 'j', 'u', 'i', 'x', 0, 0, 'l', 'r', '0', '[', '/', 's', 'n', 'm', 'b', 'h', 't', 'w', 0, 0, 'n', 't', 'r', 'l', 's', 'v', 'w', 'o', 'a', ',', '.', 'e', 'q', ';', 'p', '.', '4', '5', 'y', 'u', 'e', 'q', ';', 'o', 'e', 'j', 0, 0, 'r', 'c', '9', '0', 'l', 'n', 't', 's', 'n', 'l', '/', '-', 'z', 'v', 't', 'h', 'c', 'r', 'n', 'w', 'm', 'u', 'e', 'p', 'y', 'i', 'k', 'j', 'v', 'w', 'n', 's', 'z', 0, 0, 'w', 'm', 't', 'n', 'v', 0, 0, 'x', 'k', 'i', 'd', 'b', 0, 0, 'y', 'p', '5', '6', 'f', 'i', 'u', 'z', 'v', 's', '-', 0, 0, 0 }; static const uint8_t PC_Keypad[15*9] = { /*Key, left, up-left, up, up-right, right, down-right, down, down-left */ '*', '/', 0, 0, 0, '-', '+', '9', '8', '+', '9', '*', '-', 0, 0, 0, 0, '6', '-', '*', 0, 0, 0, 0, 0, '+', '9', '.', '0', '2', '3', 0, 0, 0, 0, 0, '/', 0, 0, 0, 0, '*', '9', '8', '7', '0', 0, '1', '2', '3', '.', 0, 0, 0, '1', 0, 0, '4', '5', '2', '0', 0, 0, '2', '1', '4', '5', '6', '3', '.', '0', 0, '3', '2', '5', '6', 0, 0, 0, '.', '0', '4', 0, 0, '7', '8', '5', '2', '1', 0, '5', '4', '7', '8', '9', '6', '3', '2', '1', '6', '5', '8', '9', '+', 0, 0, '3', '2', '7', 0, 0, 0, '/', '8', '5', '4', 0, '8', '7', 0, '/', '*', '9', '6', '5', '4', '9', '8', '/', '*', '-', '+', 0, '6', '5' }; static const uint8_t MacKeypad[16*9] = { '*', '/', 0, 0, 0, 0, 0, '-', '9', '+', '6', '9', '-', 0, 0, 0, 0, '3', '-', '9', '/', '*', 0, 0, 0, '+', '6', '.', '0', '2', '3', 0, 0, 0, 0, 0, '/', '=', 0, 0, 0, '*', '-', '9', '8', '0', 0, '1', '2', '3', '.', 0, 0, 0, '1', 0, 0, '4', '5', '2', '0', 0, 0, '2', '1', '4', '5', '6', '3', '.', '0', 0, '3', '2', '5', '6', '+', 0, 0, '.', '0', '4', 0, 0, '7', '8', '5', '2', '1', 0, '5', '4', '7', '8', '9', '6', '3', '2', '1', '6', '5', '8', '9', '-', '+', 0, '3', '2', '7', 0, 0, 0, '=', '8', '5', '4', 0, '8', '7', 0, '=', '/', '9', '6', '5', '4', '9', '8', '=', '/', '*', '-', '+', '6', '5', '=', 0, 0, 0, 0, '/', '9', '8', '7' }; static const Keyboard_t Keyboards[] = { { US_Qwerty, US_Shift, sizeof US_Qwerty / 7, 7, sizeof US_Shift / 2, 66 }, { Dvorak, US_Shift, sizeof Dvorak / 7, 7, sizeof US_Shift / 2, 66 }, { UK_Qwerty, UK_Shift, sizeof UK_Qwerty / 7, 7, sizeof UK_Shift / 2, 66 }, { MacKeypad, 0, sizeof MacKeypad / 9, 9, 0, 44 }, { PC_Keypad, 0, sizeof PC_Keypad / 9, 9, 0, 44 } }; /********************************************************************************** * Match password for the given keyboard layout */ static int DoSptlMatch(const uint8_t *Passwd, int MaxLen, const Keyboard_t *Keyb, SpatialMatchInfo_t *Extra) { int i; int ShiftCount = 0; int Turns = 0; int Dir = -1; int Len = 0; uint8_t PrevChar = 0; for( ; *Passwd && (Len < MaxLen); ++Passwd, ++Len) { const uint8_t *Key; int s = 0; uint8_t CurChar = *Passwd; /* Try to unshift the character */ if (Keyb->Shifts) { Key = CharBinSearch(CurChar, Keyb->Shifts, Keyb->NumShift, 2); if (Key) { /* Shifted char */ CurChar = Key[1]; s = 1; } } if (PrevChar) { /* See if the pattern can be extended */ i = 0; Key = CharBinSearch(PrevChar, Keyb->Keys, Keyb->NumKeys, Keyb->NumNear); if (Key) { for(i = Keyb->NumNear - 1; i > 0; --i) { if (Key[i] == CurChar) break; } } if (i) { Turns += (i != Dir); Dir = i; ShiftCount += s; } else { break; } } PrevChar = CurChar; } if (Len >= MIN_SPATIAL_LEN) { Extra->Turns = Turns; Extra->Shifts = ShiftCount; return Len; } return 0; } /********************************************************************************** * Try to match spatial patterns on the keyboard * Parameters: * Result Pointer head of linked list used to store results * Passwd The start of the password * Start Where in the password to start attempting to match * MaxLen Maximum number characters to consider */ static void SpatialMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) { unsigned int Indx; int Len, CurLen; SpatialMatchInfo_t Extra; const Keyboard_t *k; Passwd += Start; for(CurLen = MaxLen; CurLen >= MIN_SPATIAL_LEN;CurLen = Len - 1) { Len = 0; memset(&Extra, 0, sizeof Extra); for(k = Keyboards, Indx = 0; Indx < (sizeof Keyboards / sizeof Keyboards[0]); ++Indx, ++k) { Len = DoSptlMatch(Passwd, CurLen, k, &Extra); if (Len > 0) { /* Got a sequence of required length so add to result list */ int i, j, s; double Degree, Entropy; ZxcMatch_t *p; Degree = (k->NumNear-1) - (double)k->NumBlank / (double)k->NumKeys; s = k->NumKeys; if (k->Shifts) s *= 2; /* Estimate the number of possible patterns with length ranging 2 to match length and */ /* with turns ranging from 0 to match turns */ Entropy = 0.0; for(i = 2; i <= Len; ++i) { int PossTurns = Extra.Turns; if (PossTurns >= i) PossTurns = i-1; for(j = 1; j <= PossTurns; ++j) Entropy += nCk(i-1, j-1) * pow(Degree, j) * s; } if (Entropy > 0.0) Entropy = log(Entropy); if (Extra.Shifts) { /* Add extra entropy for shifted keys. (% instead of 5, A instead of a etc.) */ /* Math is similar to extra entropy from uppercase letters in dictionary matches. */ int Shift = Extra.Shifts; int Unshift = Len - Shift; Degree = 0.0; j = Shift; if (j > Unshift) j = Unshift; for(i = 0; i <= j; ++i) { Degree += nCk(Len, i); } if (Degree > 0.0) Entropy += log(Degree); } p = AllocMatch(); p->Type = SPATIAL_MATCH; p->Begin = Start; p->Entrpy = Entropy; p->Length = Len; AddMatchRepeats(Result, p, Passwd, MaxLen); AddResult(Result, p, MaxLen); break; } } } } /*################################################################################* *################################################################################* * Begin date matching code *################################################################################* *################################################################################*/ /* The possible date formats ordered by length (d for day, m for month, */ /* y for year, ? for separator) */ static const char *Formats[] = { "yyyy", "d?m?yy", "ddmmyy", "dmyyyy", "dd?m?yy", "d?mm?yy", "ddmyyyy", "dmmyyyy", "yyyymmd", "yyyymdd", "d?m?yyyy", "dd?mm?yy", "ddmmyyyy", "yyyy?m?d", "yyyymmdd", "dd?m?yyyy", "d?mm?yyyy", "yyyy?mm?d", "yyyy?m?dd", "dd?mm?yyyy", "yyyy?mm?dd", 0 }; /* Possible separator characters that could be used */ static const char DateSeperators[] = "/\\-_. "; /********************************************************************************** * Try to match the password with the formats above. */ static void DateMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) { int CurFmt; int YrLen = 0; int PrevLen = 0; uint8_t Sep = 0; Passwd += Start; for(CurFmt = 0; Formats[CurFmt]; ++CurFmt) { int Len = 0; int Year = 0; int Mon = 0; int Day = 0; int Fail = 0; const uint8_t *p = Passwd; const char *Fmt; YrLen = 0; Sep = 0; /* Scan along the format, trying to match the password */ for(Fmt = Formats[CurFmt]; *Fmt && !Fail; ++Fmt) { if (*Fmt == '?') { if (!Sep && strchr(DateSeperators, *p)) Sep = *p; Fail = (*p != Sep); } else if (isdigit(*p)) { if (*Fmt == 'd') { Day = 10 * Day + *p - '0'; } else if (*Fmt == 'm') { Mon = 10 * Mon + *p - '0'; } else { Year = 10 * Year + *p - '0'; ++YrLen; } } else { Fail = 1; } ++p; ++Len; if (Len >= MaxLen) break; } if (Len < 4) Fail = 1; if (!Fail) { /* Character matching is OK, now check to see if valid date */ if (((YrLen > 3) || (Len <= 4)) && ((Year < MIN_YEAR) || (Year > MAX_YEAR))) Fail = 1; else if (Len > 4) { if ((Mon > 12) && (Day < 13)) { /* Swap day,month to try to make both in range */ int i = Mon; Mon = Day; Day = i; } /* Check for valid day, month. Currently assumes all months have 31 days. */ if ((Mon < 1) || (Mon > 12)) Fail = 1; else if ((Day < 1) || (Day > 31)) Fail = 1; } } if (!Fail && (Len > PrevLen)) { /* String matched the date, store result */ double e; ZxcMatch_t *p = AllocMatch(); if (Len <= 4) e = log(MAX_YEAR - MIN_YEAR + 1.0); else if (YrLen > 3) e = log(31 * 12 * (MAX_YEAR - MIN_YEAR + 1.0)); else e = log(31 * 12 * 100.0); if (Sep) e += log(4.0); /* Extra 2 bits for separator */ p->Entrpy = e; p->Type = DATE_MATCH; p->Length = Len; p->Begin = Start; AddMatchRepeats(Result, p, Passwd, MaxLen); AddResult(Result, p, MaxLen); PrevLen = Len; } } } /*################################################################################* *################################################################################* * Begin repeated character matching code *################################################################################* *################################################################################*/ /********************************************************************************** * Try to match password part as a set of repeated characters. * Parameters: * Result Pointer head of linked list used to store results * Passwd The start of the password * Start Where in the password to start attempting to match * MaxLen Maximum number characters to consider */ static void RepeatMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) { int Len, i; uint8_t c; Passwd += Start; /* Remember first char and the count its occurances */ c = *Passwd; for(Len = 1; (Len < MaxLen) && (c == Passwd[Len]); ++Len) { } if (Len >= MIN_REPEAT_LEN) { /* Enough repeated char, so create results from number found down to min acceptable repeats */ double Card = Cardinality(&c, 1); for(i = Len; i >= MIN_REPEAT_LEN; --i) { ZxcMatch_t *p = AllocMatch(); p->Type = REPEATS_MATCH; p->Begin = Start; p->Length = i; p->Entrpy = log(Card * i); AddResult(Result, p, MaxLen); } } /* Try to match a repeated sequence e.g. qxno6qxno6 */ for(Len = MaxLen/2; Len >= MIN_REPEAT_LEN; --Len) { const uint8_t *Rpt = Passwd + Len; int RepeatCount = 2; while(MaxLen >= (Len * RepeatCount)) { if (strncmp((const char *)Passwd, (const char *)Rpt, Len) == 0) { /* Found a repeat */ int c = Cardinality(Passwd, Len); ZxcMatch_t *p = AllocMatch(); p->Entrpy = log((double)c) * Len + log((double)RepeatCount); p->Type = (ZxcTypeMatch_t)(BRUTE_MATCH + MULTIPLE_MATCH); p->Length = Len * RepeatCount; p->Begin = Start; AddResult(Result, p, MaxLen); } else break; ++RepeatCount; Rpt += Len; } } } /********************************************************************************** ********************************************************************************** * Begin character sequence matching code ********************************************************************************** *********************************************************************************/ #define MAX_SEQUENCE_STEP 5 /********************************************************************************** * Try to match password part as a set of incrementing or decrementing characters. * Parameters: * Result Pointer head of linked list used to store results * Passwd The start of the password * Start Where in the password to start attempting to match * MaxLen Maximum number characters to consider */ static void SequenceMatch(ZxcMatch_t **Result, const uint8_t *Passwd, int Start, int MaxLen) { int Len=0; int SetLow, SetHigh, Dir; uint8_t First, Next, IsDigits; const uint8_t *Pwd; Passwd += Start; Pwd = Passwd; First = Passwd[0]; Dir = Passwd[1] - First; Len = 0; IsDigits = 0; /* Decide on min and max character code for sequence */ if (islower(*Passwd)) { SetLow = 'a'; SetHigh = 'z'; } else if (isupper(*Passwd)) { SetLow = 'A'; SetHigh = 'Z'; } else if (isdigit(*Passwd)) { SetLow = '0'; SetHigh = '9'; if ((First == '0') && isdigit(Passwd[1]) && (Dir > MAX_SEQUENCE_STEP)) { /* Special case for decrementing sequence of digits, treat '0 as a 'ten' character */ Dir = Passwd[1] - ('9' + 1); } IsDigits = 1; } else return; /* Only consider it a sequence if the character increment is not too large */ if (Dir && (Dir <= MAX_SEQUENCE_STEP) && (Dir >= -MAX_SEQUENCE_STEP)) { ++Len; while(1) { Next = static_cast<uint8_t>(Passwd[0] + Dir); if (IsDigits && (Dir > 0) && (Next == ('9' + 1)) && (Passwd[1] == '0')) { /* Incrementing digits, consider '0' to be same as a 'ten' character */ ++Len; ++Passwd; break; } if (IsDigits && (Dir < 0) && (Passwd[0] == '0') && (Passwd[1] == ('9'+1 + Dir))) { ++Len; ++Passwd; break; } if ((Next > SetHigh) || (Next < SetLow) || (Passwd[1] != Next)) break; ++Len; ++Passwd; if (Len >= MaxLen) break; } } if (Len >= MIN_SEQUENCE_LEN) { /* Enough repeated char, so create results from number found down to min acceptable length */ int i; double e; if ((First == 'a') || (First == 'A') || (First == 'z') || (First == 'Z') || (First == '0') || (First == '1') || (First == '9')) e = log(2.0); else if (IsDigits) e = log(10.0); else if (isupper(First)) e = log(26*2.0); else e = log(26.0); if (Dir < 0) e += log(2.0); for(i = Len; i >= MIN_SEQUENCE_LEN; --i) { ZxcMatch_t *p = AllocMatch(); /* Add new result to head of list as it has lower entropy */ p->Type = SEQUENCE_MATCH; p->Begin = Start; p->Length = i; p->Entrpy = e + log((double)i); AddMatchRepeats(Result, p, Pwd, MaxLen); AddResult(Result, p, MaxLen); } } } /********************************************************************************** ********************************************************************************** * Begin top level zxcvbn code ********************************************************************************** *********************************************************************************/ /********************************************************************************** * Matching a password is treated as a problem of finding the minimum distance * between two vertexes in a graph. This is solved using Dijkstra's algorithm. * * There are a series of nodes (or vertexes in graph terminology) which correspond * to points between each character of the password. Also there is a start node * before the first character and an end node after the last character. * * The paths between the nodes (or edges in graph terminology) correspond to the * matched parts of the password (e.g. dictionary word, repeated characters etc). * The distance of the path is equal to the entropy of the matched part. A default * single character bruteforce match path is also added for all nodes except the * end node. * * Dijkstra's algorithm finds the combination of these part matches (or paths) * which gives the lowest entropy (or smallest distance) from begining to end * of the password. */ /* Struct to hold the data of a node (imaginary point between password characters) */ typedef struct { ZxcMatch_t *Paths; /* Partial matches that lead to a following node */ double Dist; /* Distance (or entropy) from start of password to this node */ ZxcMatch_t *From; /* Which path was used to get to this node with lowest distance/entropy */ int Visit; /* Non zero when node has been visited during Dijkstra evaluation */ } Node_t; /********************************************************************************** * Main function of the zxcvbn password entropy estimation */ double ZxcvbnMatch(const char *Pwd, const char *UserDict[], ZxcMatch_t **Info) { int i, j; ZxcMatch_t *Zp; Node_t *Np; double e; int Len = static_cast<int>(strlen(Pwd)); const uint8_t *Passwd = (const uint8_t *)Pwd; uint8_t *RevPwd; /* Create the paths */ Node_t *Nodes = MallocFn(Node_t, Len+1); memset(Nodes, 0, (Len+1) * sizeof *Nodes); i = Cardinality(Passwd, Len); e = log((double)i); /* Do matching for all parts of the password */ for(i = 0; i < Len; ++i) { int MaxLen = Len - i; /* Add all the 'paths' between groups of chars in the password, for current starting char */ UserMatch(&(Nodes[i].Paths), UserDict, Passwd, i, MaxLen); DictionaryMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); DateMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); SpatialMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); SequenceMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); RepeatMatch(&(Nodes[i].Paths), Passwd, i, MaxLen); /* Initially set distance to nearly infinite */ Nodes[i].Dist = DBL_MAX; } /* Reverse dictionary words check */ RevPwd = MallocFn(uint8_t, Len+1); for(i = Len-1, j = 0; i >= 0; --i, ++j) RevPwd[j] = Pwd[i]; RevPwd[j] = 0; for(i = 0; i < Len; ++i) { ZxcMatch_t *Path = 0; int MaxLen = Len - i; DictionaryMatch(&Path, RevPwd, i, MaxLen); UserMatch(&Path, UserDict, RevPwd, i, MaxLen); /* Now transfer any reverse matches to the normal results */ while(Path) { ZxcMatch_t *Nxt = Path->Next; Path->Next = 0; Path->Begin = Len - (Path->Begin + Path->Length); AddResult(&(Nodes[Path->Begin].Paths), Path, MaxLen); Path = Nxt; } } /* Add a set of brute force matches. Start by getting all the start points and all */ /* points at character position after end of the matches. */ memset(RevPwd, 0, Len+1); for(i = 0; i < Len; ++i) { ZxcMatch_t *Path = Nodes[i].Paths; while(Path) { RevPwd[Path->Begin] |= 1; RevPwd[Path->Begin + Path->Length] |= 2; Path = Path->Next; } } RevPwd[0] = 1; RevPwd[Len] = 2; /* Add the brute force matches */ for(i = 0; i < Len; ++i) { int MaxLen = Len - i; int j; if (!RevPwd[i]) continue; for(j = i+1; j <= Len; ++j) { if (RevPwd[j]) { Zp = AllocMatch(); Zp->Type = BRUTE_MATCH; Zp->Begin = i; Zp->Length = j - i; Zp->Entrpy = e * (j - i); AddResult(&(Nodes[i].Paths), Zp, MaxLen); } } } FreeFn(RevPwd); /* End node has infinite distance/entropy, start node has 0 distance */ Nodes[i].Dist = DBL_MAX; Nodes[0].Dist = 0.0; /* Reduce the paths using Dijkstra's algorithm */ for(i = 0; i < Len; ++i) { int j; double MinDist = DBL_MAX; int MinIdx = 0; /* Find the unvisited node with minimum distance or entropy */ for(Np = Nodes, j = 0; j < Len; ++j, ++Np) { if (!Np->Visit && (Np->Dist < MinDist)) { MinIdx = j; MinDist = Np->Dist; } } /* Mark the minimum distance node as visited */ Np = Nodes + MinIdx; Np->Visit = 1; e = Np->Dist; /* Update all unvisited neighbouring nodes with their new distance. A node is a */ /* neighbour if there is a path/match from current node Np to it. The neighbour */ /* distance is the current node distance plus the path distance/entropy. Only */ /* update if the new distance is smaller. */ for(Zp = Np->Paths; Zp; Zp = Zp->Next) { Node_t *Ep = Np + Zp->Length; double d = e + Zp->MltEnpy; if (!Ep->Visit && (d < Ep->Dist)) { /* Update as lower dist, also remember the 'from' node */ Ep->Dist = d; Ep->From = Zp; } } /* If we got to the end node stop early */ /*if (Nodes[Len].Dist < DBL_MAX/2.0) */ /* break; */ } /* Make e hold entropy result and adjust to log base 2 */ e = Nodes[Len].Dist / log(2.0); if (Info) { /* Construct info on password parts */ *Info = 0; for(Zp = Nodes[Len].From; Zp; ) { ZxcMatch_t *Xp; i = Zp->Begin; /* Remove all but required path */ Xp = Nodes[i].Paths; Nodes[i].Paths = 0; while(Xp) { ZxcMatch_t *p = Xp->Next; if (Xp == Zp) { /* Adjust the entropy to log to base 2 */ Xp->Entrpy /= log(2.0); Xp->MltEnpy /= log(2.0); /* Put previous part at head of info list */ Xp->Next = *Info; *Info = Xp; } else { /* Not going on info list, so free it */ FreeFn(Xp); } Xp = p; } Zp = Nodes[i].From; } } /* Free all paths. Any being returned to caller have already been freed */ for(i = 0; i <= Len; ++i) { Zp = Nodes[i].Paths; while(Zp) { ZxcMatch_t *p = Zp->Next; FreeFn(Zp); Zp = p; } } FreeFn(Nodes); return e; } /********************************************************************************** * Free the path info returned by ZxcvbnMatch(). */ void ZxcvbnFreeInfo(ZxcMatch_t *Info) { ZxcMatch_t *p; while(Info) { p = Info->Next; FreeFn(Info); Info = p; } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/third_party/zxcvbn-c/zxcvbn.h�����������������������������������������������������������0000664�0000000�0000000�00000012060�15162662266�0020214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// clang-format off #ifndef ZXCVBN_H_F98183CE2A01_INCLUDED #define ZXCVBN_H_F98183CE2A01_INCLUDED /********************************************************************************** * C implementation of the zxcvbn password strength estimation method. * Copyright (c) 2015-2017 Tony Evans * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **********************************************************************************/ /* If this is defined, the dictiononary data is read from file. When undefined */ /* dictionary data is included in the source code. */ /*#define USE_DICT_FILE */ /* If this is defined, C++ builds which read dictionary data from file will use */ /* stdio FILE streams (and fopen,fread,fclose). When undefined, C++ builds will */ /* use std::ifstream to read dictionary data. Ignored for C builds (stdio FILE */ /* streams are always used). */ /*#define USE_FILE_IO */ #ifndef __cplusplus /* C build. Use the standard malloc/free for heap memory */ #include <stdlib.h> #define MallocFn(T,N) ((T *)malloc((N) * sizeof(T))) #define FreeFn(P) free(P) #else /* C++ build. Use the new/delete operators for heap memory */ #define MallocFn(T,N) (new T[N]) #define FreeFn(P) (delete [] P) #endif /* Enum for the types of match returned in the Info arg to ZxcvbnMatch */ typedef enum { NON_MATCH, /* 0 */ BRUTE_MATCH, /* 1 */ DICTIONARY_MATCH, /* 2 */ DICT_LEET_MATCH, /* 3 */ USER_MATCH, /* 4 */ USER_LEET_MATCH, /* 5 */ REPEATS_MATCH, /* 6 */ SEQUENCE_MATCH, /* 7 */ SPATIAL_MATCH, /* 8 */ DATE_MATCH, /* 9 */ YEAR_MATCH, /* 10 */ MULTIPLE_MATCH = 32 /* Added to above to indicate matching part has been repeated */ } ZxcTypeMatch_t; /* Linked list of information returned in the Info arg to ZxcvbnMatch */ struct ZxcMatch { int Begin; /* Char position of begining of match */ int Length; /* Number of chars in the match */ double Entrpy; /* The entropy of the match */ double MltEnpy; /* Entropy with additional allowance for multipart password */ ZxcTypeMatch_t Type; /* Type of match (Spatial/Dictionary/Order/Repeat) */ struct ZxcMatch *Next; }; typedef struct ZxcMatch ZxcMatch_t; #ifdef __cplusplus extern "C" { #endif #ifdef USE_DICT_FILE /********************************************************************************** * Read the dictionnary data from the given file. Returns 1 if OK, 0 if error. * Called once at program startup. */ int ZxcvbnInit(const char *); /********************************************************************************** * Free the dictionnary data after use. Called once at program shutdown. */ void ZxcvbnUnInit(); #else /* As the dictionary data is included in the source, define these functions to do nothing. */ #define ZxcvbnInit(s) 1 #define ZxcvbnUnInit() do {} while(0) #endif /********************************************************************************** * The main password matching function. May be called multiple times. * The parameters are: * Passwd The password to be tested. Null terminated string. * UserDict User supplied dictionary words to be considered particulary bad. Passed * as a pointer to array of string pointers, with null last entry (like * the argv parameter to main()). May be null or point to empty array when * there are no user dictionary words. * Info The address of a pointer variable to receive information on the parts * of the password. This parameter can be null if no information is wanted. * The data should be freed by calling ZxcvbnFreeInfo(). * * Returns the entropy of the password (in bits). */ double ZxcvbnMatch(const char *Passwd, const char *UserDict[], ZxcMatch_t **Info); /********************************************************************************** * Free the data returned in the Info parameter to ZxcvbnMatch(). */ void ZxcvbnFreeInfo(ZxcMatch_t *Info); #ifdef __cplusplus } #endif #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/����������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0013607�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0015625�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/CMakeLists.txt����������������������������������������������������������0000664�0000000�0000000�00000003562�15162662266�0020373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_library(libgfxworker STATIC) target_sources(libgfxworker PRIVATE src/logger.h src/processor.h src/server.h src/thread_pool.h src/logger.cpp src/processor.cpp src/thread_pool.cpp ) target_sources_conditional(libgfxworker FLAG WIN32 PRIVATE src/win32/server.h src/win32/server.cpp ) target_sources_conditional(libgfxworker FLAG UNIX PRIVATE src/posix/server.h src/posix/server.cpp ) target_include_directories(libgfxworker PUBLIC src ) if (MSVC) # strcpy and other warnings target_compile_definitions(libgfxworker PRIVATE _CRT_SECURE_NO_WARNINGS) endif() target_link_libraries(libgfxworker PUBLIC MEGA::SDKlib ) if (WIN32) target_compile_definitions(libgfxworker PUBLIC UNICODE ) endif() # Adjust compilation flags for warnings and errors for libgfxworker target_platform_compile_options( TARGET libgfxworker WINDOWS /W4 /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$<CONFIG:Debug>:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET libgfxworker WINDOWS /WX UNIX $<$<CONFIG:Debug>: -Werror> ) endif() add_executable(gfxworker src/main.cpp ) target_link_libraries(gfxworker PRIVATE libgfxworker ) # Adjust compilation flags for warnings and errors for gfxworker target_platform_compile_options( TARGET gfxworker WINDOWS /W4 /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$<CONFIG:Debug>:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET gfxworker WINDOWS /WX UNIX $<$<CONFIG:Debug>: -Werror> ) endif() if(ENABLE_SDKLIB_TESTS) add_subdirectory(tests/integration) endif() ����������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/��������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016414�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/logger.cpp����������������������������������������������������������0000664�0000000�0000000�00000113000�15162662266�0020372�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file Logger.cpp * @brief Logger class with log rotation and background write to file * * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGAproxy. * * MEGAproxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #include "logger.h" #include "mega/filesystem.h" #include "mega/scoped_helpers.h" #include "mega/utils.h" #include "megaapi_impl.h" #include <assert.h> #include <chrono> #include <condition_variable> #include <cstring> #include <ctime> #include <fstream> #include <future> #include <iomanip> #include <iostream> #include <map> #include <regex> #include <sstream> #include <string> #include <thread> #include <zlib.h> #define LOG_TIME_CHARS 25 #define LOG_LEVEL_CHARS 5 #define LOG_FILE_NAME_EXTENSION ".gz" #define SSTR( x ) static_cast< const std::ostringstream & >( \ ( std::ostringstream() << std::dec << x ) ).str() namespace mega { namespace gfx { namespace { #ifdef WIN32 std::wstring cstrFromLocalPath(const mega::LocalPath &localPath) { std::wstring pathW; std::string path = localPath.toPath(false); mega::LocalPath::path2local(&path, &pathW); return pathW; } #else std::string cstrFromLocalPath(const mega::LocalPath &localPath) { return localPath.platformEncoded(); } #endif } enum ArchiveType {archiveTypeNumbered, archiveTypeTimestamp}; using DirectLogFunction = std::function <void (std::ostream *)>; struct LogLinkedList { LogLinkedList* mNext = nullptr; unsigned mAllocated = 0; unsigned mUsed = 0; int mLastMessage = -1; unsigned int mLastMessageRepeats = 0; bool mOomGap = false; DirectLogFunction *mDirectLoggingFunction = nullptr; // we cannot use a non pointer due to the malloc allocation of new entries std::promise<void>* mCompletionPromise = nullptr; // we cannot use a unique_ptr due to the malloc allocation of new entries char mMessage[1]; static LogLinkedList* create(LogLinkedList* prev, size_t size) { LogLinkedList* entry = (LogLinkedList*)malloc(size); if (entry) { entry->mNext = nullptr; entry->mAllocated = unsigned(size - sizeof(LogLinkedList)); entry->mUsed = 0; entry->mLastMessage = -1; entry->mLastMessageRepeats = 0; entry->mOomGap = false; entry->mDirectLoggingFunction = nullptr; entry->mCompletionPromise = nullptr; prev->mNext = entry; } return entry; } bool messageFits(size_t size) { return mUsed + size + 2 < mAllocated; } bool needsDirectOutput() { return mDirectLoggingFunction != nullptr; } void append(const char* s, unsigned int n = 0) { n = n ? n : unsigned(strlen(s)); assert(mUsed + n + 1 < mAllocated); strcpy(mMessage + mUsed, s); mUsed += n; } void notifyWaiter() { if (mCompletionPromise) { mCompletionPromise->set_value(); } } }; class MegaFileLoggerLoggingThread { MegaFileLogger &mLogger; std::unique_ptr<std::thread> mLogThread; std::condition_variable mLogConditionVariable; std::mutex mLogMutex; std::mutex mLogRotationMutex; LogLinkedList mLogListFirst; LogLinkedList* mLogListLast = &mLogListFirst; bool mLogExit = false; bool mFlushLog = false; bool mCloseLog = false; bool mForceRenew = false; //to force removal of all logs and create an empty new log int mFlushOnLevel = mega::MegaApi::LOG_LEVEL_WARNING; std::chrono::seconds mLogFlushPeriod = std::chrono::seconds(10); std::chrono::steady_clock::time_point mNextFlushTime = std::chrono::steady_clock::now() + mLogFlushPeriod; std::unique_ptr<FileSystemAccess> mFsAccess; ArchiveType mArchiveType = archiveTypeTimestamp; std::atomic<std::chrono::seconds> mArchiveMaxFileAgeSeconds = std::chrono::seconds(30 * 86400); // one month std::atomic_int mMaxArchiveLogsToKeep = 50; std::atomic<size_t> mLogFileSize = 50 * 1024 * 1024; friend MegaFileLogger; public: MegaFileLoggerLoggingThread(MegaFileLogger& logger): mLogger(logger) { mFsAccess = mega::createFSA(); } ~MegaFileLoggerLoggingThread() { // if the gzip operation is onging, we can't destroy this object // yet, because this mutex is locked on that thread, and // on windows at least, we will crash. So wait to lock it. std::lock_guard<std::mutex> g(mLogRotationMutex); } void startLoggingThread(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { if (!mLogThread) { mLogThread.reset(new std::thread([this, logsPath, fileName]() { logThreadFunction(logsPath, fileName); })); } } void log(int loglevel, const char *message, const char **directMessages = nullptr, size_t *directMessagesSizes = nullptr, int numberMessages = 0); static void gzipCompressOnRotate(mega::LocalPath source, mega::LocalPath destination) { auto sourcePlatformEncoded = cstrFromLocalPath(source); std::ifstream file(sourcePlatformEncoded.c_str(), std::ofstream::out); if (!file.is_open()) { //EVLOG_err(LOGGER_COMPRESSING_LOGFILE_ON_ROTATION) << "Unable to open log file for reading: " << sourcePlatformEncoded; return; } auto destinationPlatformEncoded = cstrFromLocalPath(destination); auto gzdeleter = [](gzFile_s* f) { if (f) gzclose(f); }; #ifdef _WIN32 std::unique_ptr<gzFile_s, decltype(gzdeleter)> gzfile{ gzopen_w(destinationPlatformEncoded.c_str(), "wb"), gzdeleter}; #else std::unique_ptr<gzFile_s, decltype(gzdeleter)> gzfile{ gzopen(destinationPlatformEncoded.c_str(), "wb"), gzdeleter }; #endif if (!gzfile) { //EVLOG_err(LOGGER_COMPRESSING_LOGFILE_ON_ROTATION) << "Unable to open gzfile for writing: " << sourcePlatformEncoded; return; } std::string line; while (std::getline(file, line)) { line.push_back('\n'); if (gzputs(gzfile.get(), line.c_str()) == -1) { //EVLOG_err(LOGGER_COMPRESSING_LOGFILE_ON_ROTATION) << "Unable to compress log file: " << sourcePlatformEncoded; return; } } // We must release the open file handle or the unlink below will fail file.close(); auto fsAccess = mega::createFSA(); fsAccess->unlinklocal(source); } private: mega::LocalPath logArchiveNumbered_getFilename(const mega::LocalPath& baseFileName, int logNumber) { mega::LocalPath newFileName = baseFileName; newFileName.append(mega::LocalPath::fromRelativePath("." + SSTR(logNumber) + LOG_FILE_NAME_EXTENSION)); return newFileName; } void logArchiveNumbered_cleanUpFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { for (int i = mMaxArchiveLogsToKeep - 1; i >= 0; --i) { mega::LocalPath toDeleteFileName = logArchiveNumbered_getFilename(fileName, i); mega::LocalPath toDeletePath = logsPath; toDeletePath.appendWithSeparator(toDeleteFileName, false); if (!mFsAccess->unlinklocal(toDeletePath)) { //EVLOG_err(LOGGER_CLEANING_UP) << "Error removing log file " << i; } } } void logArchiveNumbered_rotateFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { const int maxArchiveLogsToKeep = mMaxArchiveLogsToKeep; for (int i = maxArchiveLogsToKeep - 1; i >= 0; --i) { mega::LocalPath toRenameFileName = logArchiveNumbered_getFilename(fileName, i); mega::LocalPath toRenamePath = logsPath; toRenamePath.appendWithSeparator(toRenameFileName, false); auto fileAccess = mFsAccess->newfileaccess(); if (fileAccess->fopen(toRenamePath, mega::FSLogging::logExceptFileNotFound)) { if (i + 1 >= maxArchiveLogsToKeep) { if (!mFsAccess->unlinklocal(toRenamePath)) { //EVLOG_err(LOGGER_ROTATING_LOGFILES) << "Error removing log file " << i; } } else { mega::LocalPath nextFileName = logArchiveNumbered_getFilename(fileName, i + 1); mega::LocalPath nextPath = logsPath; nextPath.appendWithSeparator(nextFileName, false); if (!mFsAccess->renamelocal(toRenamePath, nextPath, true)) { //EVLOG_err(LOGGER_ROTATING_LOGFILES) << "Error renaming log file " << i; } } } } } // note: this function can be removed after we have dropped support for the legacy timestamp format. (see logArchiveTimestamp_rotateFiles()) std::string getTimeString(std::time_t timestamp, int64_t ms) { std::ostringstream oss; oss << std::put_time(std::localtime(×tamp), "%y%m%d%H%M%S") << '.' << std::setfill('0') << std::setw(3) << ms; return oss.str(); } std::string getTimeString(std::chrono::seconds offsetFromNowSec = std::chrono::seconds(0)) { auto now = std::chrono::system_clock::now(); std::time_t timestamp = std::chrono::system_clock::to_time_t(now + offsetFromNowSec); int64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() % 1000; return getTimeString(timestamp, ms); } mega::LocalPath logArchiveTimestamp_getFilename(const mega::LocalPath& baseFileName) { mega::LocalPath newFileName = baseFileName; newFileName.append(mega::LocalPath::fromRelativePath("." + getTimeString() + LOG_FILE_NAME_EXTENSION)); return newFileName; } void logArchiveTimestamp_walkArchivedFiles( const mega::LocalPath& logsPath, const mega::LocalPath& fileName, const std::function< void(const mega::LocalPath&, const mega::LocalPath&) > & walker) { std::string logFileName = fileName.toName(*mFsAccess); if (!logFileName.empty()) { mega::LocalPath leafNamePath; auto da = mFsAccess->newdiraccess(); mega::nodetype_t dirEntryType; mega::LocalPath logsPathCopy(logsPath); da->dopen(&logsPathCopy, NULL, false); while (da->dnext(logsPathCopy, leafNamePath, false, &dirEntryType)) { std::string leafName = leafNamePath.toName(*mFsAccess); if (leafName.size() > logFileName.size()) { auto res = std::mismatch(logFileName.begin(), logFileName.end(), leafName.begin()); if (res.first == logFileName.end()) { // logFileName is prefix of leafName walker(logsPath, leafNamePath); } } } } } void logArchiveTimestamp_cleanUpFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { logArchiveTimestamp_walkArchivedFiles( logsPath, fileName, [this](const mega::LocalPath& logsPath, const mega::LocalPath& leafNamePath) { mega::LocalPath leafNameFullPath = logsPath; leafNameFullPath.appendWithSeparator(leafNamePath, false); if (!mFsAccess->unlinklocal(leafNameFullPath)) { //EVLOG_err(LOGGER_CLEANING_UP) << "Error removing log file " << leafNameFullPath; } }); } void logArchiveTimestamp_rotateFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { struct TsAndPath { double mTimestamp; mega::LocalPath mPath; bool operator>(const TsAndPath& other) const { return mTimestamp > other.mTimestamp; } }; std::priority_queue<TsAndPath, std::vector<TsAndPath>, std::greater<TsAndPath>> archivedTimestampsPathPairs; const size_t fileNameLength = fileName.toPath(true).size(); const size_t fileExtLength = strlen(LOG_FILE_NAME_EXTENSION); const double oldestTimeToKeep = std::stod(getTimeString(- mArchiveMaxFileAgeSeconds.load())); const int maxArchiveLogsToKeep = mMaxArchiveLogsToKeep; // 1. remove the logs that have a timestamp that is older than the "oldest to keep" logArchiveTimestamp_walkArchivedFiles( logsPath, fileName, [this, oldestTimeToKeep, maxArchiveLogsToKeep, &archivedTimestampsPathPairs, fileNameLength, fileExtLength] (const mega::LocalPath& logsPath,const mega::LocalPath& leafNamePath) { std::string leafName = leafNamePath.toPath(true); if (leafName.substr(leafName.size() - fileExtLength) != LOG_FILE_NAME_EXTENSION || // matching extension leafName.size() <= (fileNameLength + 1 + fileExtLength)) // leafName must be longer than filename + "." + extension { return; } // pick the part of 'yymmddHHMMSS.msec' from the leaf name std::string leafTimeStr = leafName.substr(fileNameLength + 1, leafName.size() - (fileNameLength + 1 + fileExtLength)); // to be removed: try to compatible with the legacy timestamp format if (leafTimeStr.size() <= 10) { try { int64_t timestamp = std::stoll(leafTimeStr); leafTimeStr = getTimeString(timestamp, 0); } catch (...) { return; // ignore non-supported log names } } double leafTime = 0; try { size_t leafTimeStrSize; leafTime = std::stod(leafTimeStr, &leafTimeStrSize); if (leafTimeStrSize != 16) // not a "yyyymmddHHMMSS.msec" number string { throw; } } catch (...) { return; // ignore non-supported log names } mega::LocalPath leafNameFullPath = logsPath; leafNameFullPath.appendWithSeparator(leafNamePath, false); if (leafTime < oldestTimeToKeep || !maxArchiveLogsToKeep) { if (!mFsAccess->unlinklocal(leafNameFullPath)) { //EVLOG_err(LOGGER_CLEANING_UP) << "Error removing log file " << leafNameFullPath; } } else if (maxArchiveLogsToKeep > 0) { archivedTimestampsPathPairs.push({leafTime, leafNameFullPath}); } }); // 2. remove the oldest log until total logs < max logs to keep // (because rotating logs happens before creating the new log, so here we only needs to keep max-1 logs) if (maxArchiveLogsToKeep > 0) { while (archivedTimestampsPathPairs.size() >= (size_t)maxArchiveLogsToKeep) { const TsAndPath &oldest = archivedTimestampsPathPairs.top(); const mega::LocalPath &leafNameFullPathToDelete = oldest.mPath; if (!mFsAccess->unlinklocal(leafNameFullPathToDelete)) { //EVLOG_err(LOGGER_CLEANING_UP) << "Error removing log file " << leafNameFullPathToDelete; } archivedTimestampsPathPairs.pop(); } } } mega::LocalPath logArchive_getNewFilename(const mega::LocalPath& fileName) { return mArchiveType == archiveTypeNumbered ? logArchiveNumbered_getFilename(fileName, 0) : logArchiveTimestamp_getFilename(fileName); } void logArchive_cleanUpFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { if (mArchiveType == archiveTypeNumbered) { logArchiveNumbered_cleanUpFiles(logsPath, fileName); } else { logArchiveTimestamp_cleanUpFiles(logsPath, fileName); } } void logArchive_rotateFiles(const mega::LocalPath& logsPath, const mega::LocalPath& fileName) { if (mArchiveType == archiveTypeNumbered) { logArchiveNumbered_rotateFiles(logsPath, fileName); } else { logArchiveTimestamp_rotateFiles(logsPath, fileName); } } void logThreadFunction(mega::LocalPath logsPath, mega::LocalPath fileName) { MegaFileLogger::setThreadName("LoggerMain"); // Avoid cycles and possible deadloks - no logging from this log output thread. mega::SimpleLogger::mThreadLocalLoggingDisabled = true; // Error messages from this thread will be output directly to file // they are collected here std::string threadErrors; mega::LocalPath fileNameFullPath = logsPath; fileNameFullPath.appendWithSeparator(fileName, false); auto fileNameFullPathPlatformEncoded = cstrFromLocalPath(fileNameFullPath); std::ofstream outputFile(fileNameFullPathPlatformEncoded.c_str(), std::ofstream::out | std::ofstream::app); outputFile << "----------------------------- program start -----------------------------\n"; int64_t outFileSize = outputFile.tellp(); // Auxiliary thread used for zipping in the background: std::atomic_bool zippingThreadExit; zippingThreadExit.store(false); std::mutex zippingQueueMutex; std::deque<mega::LocalPath> zippingQueueFiles; std::condition_variable zippingWakeCv; std::thread zippingThread([&](){ MegaFileLogger::setThreadName("LoggerZipping"); mega::LocalPath newNameDone; while(true) { { std::unique_lock<std::mutex> lock(zippingQueueMutex); zippingWakeCv.wait(lock, [&](){ return zippingThreadExit.load() || !zippingQueueFiles.empty();}); if (zippingQueueFiles.empty()) // Let it deplete the queue and zip all the pending ones before exiting { assert(zippingThreadExit.load()); return; } newNameDone = std::move(zippingQueueFiles.front()); zippingQueueFiles.pop_front(); } { //do zip auto newNameZipping = newNameDone; newNameZipping.append(mega::LocalPath::fromRelativePath(".zipping")); std::lock_guard<std::mutex> g(mLogRotationMutex); // Ensure no concurrency issue with while rotating thread regarding cleanups/.zipping file removals gzipCompressOnRotate(newNameZipping, newNameDone); } } }); // Ensure we finish and wait for zipping thread auto guard = makeScopedDestructor( [&]() { zippingThreadExit.store(true); zippingWakeCv.notify_one(); zippingThread.join(); }); auto pushToZippingThread = [&](mega::LocalPath &&newNameDone) { { std::lock_guard<std::mutex> g(zippingQueueMutex); zippingQueueFiles.push_back(std::move(newNameDone)); } zippingWakeCv.notify_one(); }; while (!mLogExit) { if (!threadErrors.empty()) { outputFile << threadErrors << std::endl; threadErrors.clear(); } if (mForceRenew) { std::lock_guard<std::mutex> g(mLogRotationMutex); logArchive_cleanUpFiles(logsPath, fileName); outputFile.close(); if (!mFsAccess->unlinklocal(fileNameFullPath)) { threadErrors += "Error removing log file " + fileNameFullPath.toPath(true) + "\n"; } outputFile.open(fileNameFullPathPlatformEncoded.c_str(), std::ofstream::out); outFileSize = 0; mForceRenew = false; } else if (outFileSize > (int64_t)mLogFileSize.load()) { std::lock_guard<std::mutex> g(mLogRotationMutex); logArchive_rotateFiles(logsPath, fileName); outputFile.close(); if (mMaxArchiveLogsToKeep > 0) { auto newNameDone = logsPath; newNameDone.appendWithSeparator(logArchive_getNewFilename(fileName), false); auto newNameZipping = newNameDone; newNameZipping.append(mega::LocalPath::fromRelativePath(".zipping")); // Ensure there does not exist a clashing .zipping file: if (!mFsAccess->unlinklocal(newNameZipping) && errno != ENOENT /*ignore if file not exist*/) { threadErrors += "Failed to unlink log file: " + newNameZipping.toPath(true) + "\n"; } // rename to .zipping and queue the zipping into the zipping thread: if (mFsAccess->renamelocal(fileNameFullPath, newNameZipping, true)) { pushToZippingThread(std::move(newNameDone)); } else { threadErrors += "Failed to rename log file: " + fileNameFullPath.toPath(true) + " to " + newNameZipping.toPath(true) + "\n"; } } outputFile.open(fileNameFullPathPlatformEncoded.c_str(), std::ofstream::out); outFileSize = 0; } LogLinkedList* newMessages = nullptr; bool topLevelMemoryGap = false; { std::unique_lock<std::mutex> lock(mLogMutex); mLogConditionVariable.wait_for(lock, std::chrono::milliseconds(500), [this, &newMessages, &topLevelMemoryGap]() { if (mForceRenew || mLogListFirst.mNext || mLogExit || mFlushLog || mCloseLog) { newMessages = mLogListFirst.mNext; mLogListFirst.mNext = nullptr; mLogListLast = &mLogListFirst; topLevelMemoryGap = mLogListFirst.mOomGap; mLogListFirst.mOomGap = false; return true; } else return false; }); } if (topLevelMemoryGap) { if (outputFile) { outputFile << "<log gap - out of logging memory at this point>\n"; } } while (newMessages) { auto p = newMessages; newMessages = newMessages->mNext; if (outputFile) { if (p->needsDirectOutput()) { (*p->mDirectLoggingFunction)(&outputFile); } else { outputFile << p->mMessage; outFileSize += p->mUsed; if (p->mOomGap) { outputFile << "<log gap - out of logging memory at this point>\n"; } } } if (mLogger.mLogToStdout) { if (p->needsDirectOutput()) { (*p->mDirectLoggingFunction)(&std::cout); } else { std::cout << p->mMessage; } std::cout << std::flush; //always flush into stdout (DEBUG mode) } p->notifyWaiter(); free(p); } if (mFlushLog || mNextFlushTime <= std::chrono::steady_clock::now()) { mFlushLog = false; outputFile.flush(); if (mLogger.mLogToStdout) { std::cout << std::flush; } mNextFlushTime = std::chrono::steady_clock::now() + mLogFlushPeriod; } if (mCloseLog) { outputFile.close(); return; // This request means we have received a termination signal; close and exit the thread as quick & clean as possible } } } static std::string currentThreadName() { std::ostringstream s; s << std::this_thread::get_id() << " "; return s.str(); } static char* filltime(char *s, struct tm *gmt, int microsec) { twodigit(s, gmt->tm_mday); *s++ = '/'; twodigit(s, gmt->tm_mon + 1); *s++ = '/'; twodigit(s, gmt->tm_year % 100); *s++ = '-'; twodigit(s, gmt->tm_hour); *s++ = ':'; twodigit(s, gmt->tm_min); *s++ = ':'; twodigit(s, gmt->tm_sec); *s++ = '.'; s[5] = static_cast<char>(microsec % 10 + '0'); s[4] = static_cast<char>((microsec /= 10) % 10 + '0'); s[3] = static_cast<char>((microsec /= 10) % 10 + '0'); s[2] = static_cast<char>((microsec /= 10) % 10 + '0'); s[1] = static_cast<char>((microsec /= 10) % 10 + '0'); s[0] = static_cast<char>((microsec /= 10) % 10 + '0'); s += 6; *s++ = ' '; *s = 0; return s; } static inline void twodigit(char *&s, int n) { *s++ = static_cast<char>(n / 10 + '0'); *s++ = static_cast<char>(n % 10 + '0'); } }; thread_local std::string MegaFileLogger::sThreadName = {}; #ifdef DEBUG const std::string MegaFileLogger::sDefaultLogLevelStr{"max"}; #else const std::string MegaFileLogger::sDefaultLogLevelStr{"debug"}; #endif MegaFileLogger::MegaFileLogger() : mLogLevelStringToEnumMap({ #define GENERATOR_MACRO(Enum, String) { String, Enum }, GENERATE_LOG_LEVELS_FROM_CFG_STRING #undef GENERATOR_MACRO }), mInited(false) { } MegaFileLogger::~MegaFileLogger() { stopLogger(); } void MegaFileLogger::stopLogger() { if (!mInited) return; // note: possible race/crash here if there are any other threads calling log() // this mLoggingThread is about to be deleted, and the currently // logging threads call into it mega::MegaApi::removeLoggerObject(this, true); { std::lock_guard<std::mutex> g(mLoggingThread->mLogMutex); mLoggingThread->mLogExit = true; mLoggingThread->mLogConditionVariable.notify_one(); } if (mLoggingThread->mLogThread) { mLoggingThread->mLogThread->join(); mLoggingThread->mLogThread.reset(); } } void MegaFileLogger::initialize(const char * logsPath, const char * logFileName, bool logToStdout) { auto logsPathLocalPath = mega::LocalPath::fromAbsolutePath(logsPath); auto logFileNameLocalPath = mega::LocalPath::fromRelativePath(logFileName); mLogToStdout = logToStdout; auto fsAccess = mega::createFSA(); fsAccess->mkdirlocal(logsPathLocalPath, false, false); if (mLoggingThread) { stopLogger(); } // note: probable crash here if other threads are currently logging // since we are about to delete the mLoggingThread out from under them // (stopLogger and MegaApi::removeLoggerObject do not lock, so have // no idea whether anything has currently called into RPL) mLoggingThread.reset(new MegaFileLoggerLoggingThread(*this)); mLoggingThread->startLoggingThread(logsPathLocalPath, logFileNameLocalPath); mega::MegaApi::setLogLevel(logLevelFromString(sDefaultLogLevelStr)); mega::MegaApi::addLoggerObject(this, true); mInited = true; } void MegaFileLogger::setArchiveNumbered() { if (!mInited) return; mLoggingThread->mArchiveType = archiveTypeNumbered; } void MegaFileLogger::setMaxArchiveAge(std::chrono::seconds maxAgeSeconds) { if (!mInited) return; mLoggingThread->mArchiveType = archiveTypeTimestamp; mLoggingThread->mArchiveMaxFileAgeSeconds = maxAgeSeconds; } void MegaFileLogger::setMaxArchivesToKeep(int maxArchives) { if (!mInited) return; mLoggingThread->mMaxArchiveLogsToKeep = maxArchives; } void MegaFileLogger::setLogFileSize(size_t size) { if (!mInited) return; mLoggingThread->mLogFileSize = size; } void MegaFileLogger::log(const char*, int loglevel, const char*, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, int numberMessages #endif ) { if (!mInited) return; mLoggingThread->log(loglevel, message #ifdef ENABLE_LOG_PERFORMANCE , directMessages, directMessagesSizes, numberMessages #endif ); } void MegaFileLoggerLoggingThread::log(int loglevel, const char *message, const char **directMessages, size_t *directMessagesSizes, int numberMessages) { bool direct = #ifdef ENABLE_LOG_PERFORMANCE directMessages != nullptr; #else false; #endif char timebuf[LOG_TIME_CHARS + 1]; auto now = std::chrono::system_clock::now(); time_t t = std::chrono::system_clock::to_time_t(now); struct tm gmt; memset(&gmt, 0, sizeof(struct tm)); mega::m_gmtime(t, &gmt); static thread_local std::string threadname = currentThreadName(); if (!MegaFileLogger::sThreadName.empty()) { threadname = std::string(std::move(MegaFileLogger::sThreadName)) + " "; } auto microsec = std::chrono::duration_cast<std::chrono::microseconds>(now - std::chrono::system_clock::from_time_t(t)); filltime(timebuf, &gmt, (int)microsec.count() % 1000000); const char* loglevelstring = " "; switch (loglevel) // keeping these at 4 chars makes nice columns, easy to read { case mega::MegaApi::LOG_LEVEL_FATAL: loglevelstring = "CRIT "; break; case mega::MegaApi::LOG_LEVEL_ERROR: loglevelstring = "ERR "; break; case mega::MegaApi::LOG_LEVEL_WARNING: loglevelstring = "WARN "; break; case mega::MegaApi::LOG_LEVEL_INFO: loglevelstring = "INFO "; break; case mega::MegaApi::LOG_LEVEL_DEBUG: loglevelstring = "DBG "; break; case mega::MegaApi::LOG_LEVEL_MAX: loglevelstring = "DTL "; break; } auto messageLen = strlen(message); auto threadnameLen = threadname.size(); auto lineLen = LOG_TIME_CHARS + threadnameLen + LOG_LEVEL_CHARS + messageLen; bool notify = false; { std::unique_ptr<std::lock_guard<std::mutex>> g(new std::lock_guard<std::mutex>(mLogMutex)); bool isRepeat = !direct && mLogListLast != &mLogListFirst && mLogListLast->mLastMessage >= 0 && !strncmp(message, mLogListLast->mMessage + mLogListLast->mLastMessage, messageLen); if (isRepeat) { ++mLogListLast->mLastMessageRepeats; } else { unsigned reportRepeats = mLogListLast != &mLogListFirst ? mLogListLast->mLastMessageRepeats : 0; if (reportRepeats) { lineLen += 30; mLogListLast->mLastMessageRepeats = 0; } if (direct) { if (LogLinkedList* newentry = LogLinkedList::create(mLogListLast, 1 + sizeof(LogLinkedList))) //create a new "empty" element { mLogListLast = newentry; std::promise<void> promise; mLogListLast->mCompletionPromise = &promise; auto future = mLogListLast->mCompletionPromise->get_future(); auto threadnameCStr = threadname.c_str(); DirectLogFunction func = [&timebuf, threadnameCStr, &loglevelstring, &directMessages, &directMessagesSizes, numberMessages](std::ostream *oss) { *oss << timebuf << threadnameCStr << loglevelstring; for(int i = 0; i < numberMessages; i++) { oss->write(directMessages[i], static_cast<std::streamsize>(directMessagesSizes[i])); } *oss << std::endl; }; mLogListLast->mDirectLoggingFunction = &func; g.reset(); //to liberate the mutex and let the logging thread call the logging function mLogConditionVariable.notify_one(); //wait for until logging thread completes the outputting future.get(); return; } else { mLogListLast->mOomGap = true; } } else { if (mLogListLast == &mLogListFirst || mLogListLast->mOomGap || !mLogListLast->messageFits(lineLen)) { if (LogLinkedList* newentry = LogLinkedList::create(mLogListLast, std::max<size_t>(lineLen, 8192) + sizeof(LogLinkedList) + 10)) { mLogListLast = newentry; } else { mLogListLast->mOomGap = true; } } if (!mLogListLast->mOomGap) { if (reportRepeats) { char repeatbuf[31]; // this one can occur very frequently with many in a row: cURL DEBUG: schannel: failed to decrypt data, need more data int n = snprintf(repeatbuf, 30, "[repeated x%u]\n", reportRepeats); assert(n && "Unexpected snprintf failure"); if (n > 0) { mLogListLast->append(repeatbuf, static_cast<unsigned>(n)); } } mLogListLast->append(timebuf, LOG_TIME_CHARS); mLogListLast->append(threadname.c_str(), unsigned(threadnameLen)); mLogListLast->append(loglevelstring, LOG_LEVEL_CHARS); mLogListLast->mLastMessage = static_cast<int>(mLogListLast->mUsed); mLogListLast->append(message, unsigned(messageLen)); mLogListLast->append("\n", 1); notify = mLogListLast->mUsed + 1024 > mLogListLast->mAllocated; } } } if (loglevel <= mFlushOnLevel) { mFlushLog = true; } } if (notify) { // notify outside the mutex lock is better (and correct) for much less chance the other // thread wakes up just to find the mutex locked. (saw lower cpu on the other thread like this) // Still, this notify call was taking 1% when notifying on every log line, so let the other thead // wake up by itself every 500ms without notify for the common case. // But still wake it if our memory block is getting full mLogConditionVariable.notify_one(); } } bool MegaFileLogger::cleanLogs() { if (!mInited) return false; std::lock_guard<std::mutex> g(mLoggingThread->mLogMutex); mLoggingThread->mForceRenew = true; mLoggingThread->mLogConditionVariable.notify_one(); return true; } void MegaFileLogger::flush() { if (!mInited) return; std::lock_guard<std::mutex> g(mLoggingThread->mLogMutex); mLoggingThread->mFlushLog = true; mLoggingThread->mLogConditionVariable.notify_one(); } void MegaFileLogger::flushAndClose() { if (!mInited) return; try { mLoggingThread->log(mega::MegaApi::LOG_LEVEL_FATAL, "***CRASH DETECTED: FLUSHING AND CLOSING***"); } catch (const std::exception& e) { std::cerr << "Unhandle exception on flushAndClose: "<< e.what() << std::endl; } mLoggingThread->mFlushLog = true; mLoggingThread->mCloseLog = true; mLoggingThread->mLogConditionVariable.notify_one(); // This is called on crash so the app may be unstable. Don't assume the thread is working properly. // It might be the one that crashed. Just give it 1 second to complete #ifdef WIN32 Sleep(1000); #else usleep(1000000); #endif } int MegaFileLogger::logLevelFromString(const std::string &logLevelStr) { // have lower case copy auto levelStr = logLevelStr; std::transform(std::begin(levelStr), std::end(levelStr), std::begin(levelStr), [](unsigned char c) { return std::tolower(c); }); auto it = mLogLevelStringToEnumMap.find(levelStr); if (it == mLogLevelStringToEnumMap.end()) { return -1; } return it->second; } void MegaFileLogger::setLogLevel(const std::string &str) { auto level = logLevelFromString(str); if (level == -1) { assert(false && "Invalid log level string"); level = static_cast<int>(logLevelFromString(sDefaultLogLevelStr)); } mega::MegaApi::setLogLevel(level); } void MegaFileLogger::setLogLevel(int level) { mega::MegaApi::setLogLevel(level); } MegaFileLogger& MegaFileLogger::get() { static MegaFileLogger logger; return logger; } } } sdk-10.11.0/tools/gfxworker/src/logger.h������������������������������������������������������������0000664�0000000�0000000�00000005442�15162662266�0020051�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * @file Logger.h * @brief Logger class with log rotation and background write to file * * (c) 2013 by Mega Limited, Auckland, New Zealand * * This file is part of the MEGAproxy. * * MEGAproxy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * @copyright Simplified (2-clause) BSD License. * * You should have received a copy of the license along with this * program. */ #pragma once #include <mega/types.h> #include "megaapi.h" #include <atomic> #include <memory> #include <mutex> #include <string> #include <unordered_map> namespace mega { namespace gfx { #define GENERATE_LOG_LEVELS_FROM_CFG_STRING \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_FATAL , "fatal") \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_ERROR , "error") \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_WARNING , "warn") \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_INFO , "info") \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_DEBUG , "debug") \ GENERATOR_MACRO(mega::MegaApi::LOG_LEVEL_MAX , "max") class MegaFileLoggerLoggingThread; class MegaFileLogger : public ::mega::MegaLogger { friend class MegaFileLoggerLoggingThread; static thread_local std::string sThreadName; void stopLogger(); public: /* sDefaultLogLevelStr is used when: 1. MegaFileLogger is initiated, before any call to MegaFileLogger::setLogLevel() 2. The default configuration value for megaproxy_log_level 3. The log level string passed to MegaFileLogger::setLogLevel() is not one of the possible values of {fatal, error, warning, info, debug, max} */ static const std::string sDefaultLogLevelStr; MegaFileLogger(); ~MegaFileLogger(); void initialize(const char * logsPath, const char * logFileName, bool logToStdout); static void setThreadName(const std::string &threadName) { sThreadName = threadName; } void log(const char *time, int loglevel, const char *source, const char *message #ifdef ENABLE_LOG_PERFORMANCE , const char **directMessages, size_t *directMessagesSizes, int numberMessages #endif ) override; bool mLogToStdout = false; void setArchiveNumbered(); void setMaxArchiveAge(std::chrono::seconds maxAgeSeconds); void setMaxArchivesToKeep(int maxArchives); void setLogFileSize(size_t size); void flushAndClose(); void flush(); bool cleanLogs(); int logLevelFromString(const std::string&); void setLogLevel(int); void setLogLevel(const std::string&); // one global instance static MegaFileLogger& get(); private: std::unique_ptr<MegaFileLoggerLoggingThread> mLoggingThread; std::unordered_map<std::string, int> mLogLevelStringToEnumMap; std::atomic_bool mInited; }; } }������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/main.cpp������������������������������������������������������������0000664�0000000�0000000�00000010461�15162662266�0020046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "processor.h" #include "server.h" #include "logger.h" #include "mega.h" #include "mega/arguments.h" #include <algorithm> #include <iostream> #include <memory> #include <unordered_map> #include <utility> using mega::gfx::GfxProcessor; using mega::gfx::RequestProcessor; using mega::gfx::Server; using mega::gfx::MegaFileLogger; using mega::ArgumentsParser; using mega::Arguments; using mega::LocalPath; namespace { std::string USAGE = R"( GFX processing server Usage: gfxworker [OPTION...] -h Show help -l=arg Keep alive in seconds without receiving any requests, 0 is INFINITE (default: 60) -t=arg Request processing thread pool size, minimum 1 (default: 5) -q=arg The size of this queue determines the capacity for pending requests when all threads in the pool are busy. Minimum 1 (default: 10) -n=arg Endpoint name (default: mega_gfxworker) -d=arg Log directory (default: .) -f=arg Log filename (default: mega.gfxworker.<endpointName>.log) )"; struct Config { unsigned short keepAliveInSeconds; size_t threadCount; size_t queueSize; std::string endpointName; std::string logDirectory; std::string logFilename; static Config fromArguments(const Arguments& arguments); }; Config Config::fromArguments(const Arguments& arguments) { Config config; // alive config.keepAliveInSeconds = static_cast<unsigned short>(std::stoi(arguments.getValue("-l", "60"))); // thread count, minimum 1 config.threadCount = static_cast<size_t>(std::stoi(arguments.getValue("-t", "5"))); config.threadCount = std::max<size_t>(1, config.threadCount); // queue size, minimum 1 config.queueSize = static_cast<size_t>(std::stoi(arguments.getValue("-q", "10"))); config.queueSize = std::max<size_t>(1, config.queueSize); // endpoint name config.endpointName = arguments.getValue("-n", "mega_gfxworker"); // log directory config.logDirectory = arguments.getValue("-d", "."); // log file name config.logFilename = arguments.getValue("-f", "mega.gfxworker." + config.endpointName + ".log"); return config; } // // We'll use a debug version and test gfx processing crashing in Jenkins // this prevents from presenting dialog with "Debug Error! abort()..." on // windows // void set_abort_behaviour() { #if defined(WIN32) && defined(DEBUG) SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); _set_abort_behavior(0, _WRITE_ABORT_MSG); _set_error_mode(_OUT_TO_STDERR); #endif } } int main(int argc, char** argv) { set_abort_behaviour(); auto arguments = ArgumentsParser::parse(argc, argv); // help if (arguments.contains("-h")) { std::cout << USAGE << std::endl; return 0; } // config from arguments Config config; try { config = Config::fromArguments(arguments); } catch(...) { std::cout << USAGE << std::endl; return 0; } // init logger MegaFileLogger::get().initialize(config.logDirectory.c_str(), config.logFilename.c_str(), false); LOG_info << "Gfxworker server starting" << ", endpoint name: " << config.endpointName << ", threads: " << config.threadCount << ", queue size: " << config.queueSize << ", live in seconds: " << config.keepAliveInSeconds; // start server Server server( std::make_unique<RequestProcessor>(config.threadCount, config.queueSize), config.endpointName, config.keepAliveInSeconds ); std::thread serverThread(std::ref(server)); // run forever until server thread stops if (serverThread.joinable()) { serverThread.join(); } return 0; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/posix/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017556�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/posix/server.cpp����������������������������������������������������0000664�0000000�0000000�00000003551�15162662266�0021574�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "posix/server.h" #include "processor.h" #include "mega/logging.h" #include "mega/gfx/worker/comms.h" #include "mega/posix/gfx/worker/comms.h" #include "mega/posix/gfx/worker/socket_utils.h" #include <memory> namespace mega { namespace gfx { ServerPosix::ServerPosix(std::unique_ptr<RequestProcessor> requestProcessor, const std::string& socketName, unsigned short aliveSeconds) : mRequestProcessor{std::move(requestProcessor)} , mSocketName{socketName} , mWaitMs{ aliveSeconds == 0 ? -1 : aliveSeconds * 1000} // negative is infinitely { } void ServerPosix::operator()() { serverListeningLoop(); } void ServerPosix::serverListeningLoop() { // Listen const auto [ret, listenFd] = SocketUtils::listen(SocketUtils::toSocketPath(mSocketName)); if (ret) { LOG_err << "Failed to listen on " << mSocketName << ": " << ret.message(); return; } // Take ownership const auto listenSocket = std::make_unique<Socket>(listenFd, "listen"); // Process connections for (;;) { const auto [errorCode, dataFd] = SocketUtils::accept(listenFd, mWaitMs); if (errorCode == std::errc::timed_out) { LOG_info << "Exit listening loop, No more requests."; break; } if (errorCode) { LOG_info << "Exit listening loop, Error: " << errorCode.message(); break; } // Take ownership auto dataSocket = std::make_unique<Socket>(dataFd, "accept"); // Process requests bool stopRunning = false; if (mRequestProcessor) { stopRunning = mRequestProcessor->process(std::move(dataSocket)); } if (stopRunning) { LOG_info << "Exit listening loop by request"; break; } } } } } �������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/posix/server.h������������������������������������������������������0000664�0000000�0000000�00000001743�15162662266�0021242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <chrono> #include <memory> #include <string> namespace mega { namespace gfx { class RequestProcessor; class ServerPosix { public: /** * @brief A server listening on the named unix domain socket for alive seconds * * @param requestProcessor the request processor * @param socketName the socket name * @param aliveSeconds keep alive if the sever hasn't receive any request for * the given seconds. 0 mean keeping infinitely running even * if there is no request coming. */ ServerPosix(std::unique_ptr<RequestProcessor> requestProcessor, const std::string& socketName = "mega_gfxworker", unsigned short aliveSeconds = 60); void operator()(); private: void serverListeningLoop(); std::unique_ptr<RequestProcessor> mRequestProcessor; std::string mSocketName; const std::chrono::milliseconds mWaitMs{60000}; }; } // namespace } �����������������������������sdk-10.11.0/tools/gfxworker/src/processor.cpp�������������������������������������������������������0000664�0000000�0000000�00000014003�15162662266�0021135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "processor.h" #include "logger.h" #include "mega/filesystem.h" #include "mega/gfx.h" #include "mega/gfx/freeimage.h" #include "mega/gfx/worker/command_serializer.h" #include "mega/gfx/worker/commands.h" #include "mega/gfx/worker/comms.h" #include "mega/logging.h" #include <algorithm> #include <chrono> #include <iterator> #include <numeric> namespace mega { namespace gfx { GfxProcessor::GfxProcessor() : mGfxProvider(std::make_unique<GfxProviderFreeImage>()) { } GfxTaskResult GfxProcessor::process(const GfxTask& task) { std::vector<std::string> outputImages(task.Dimensions.size()); auto path = LocalPath::fromPlatformEncodedAbsolute(task.Path); if (task.Dimensions.empty()) { LOG_err << "Received empty dimensions for " << path; return GfxTaskResult(std::move(outputImages), GfxTaskProcessStatus::ERR); } // descending sort Dimensions index for its width using SizeType = decltype(task.Dimensions.size()); auto& dimensions = task.Dimensions; std::vector<SizeType> indices(task.Dimensions.size()); std::iota(std::begin(indices), std::end(indices), 0); std::sort(std::begin(indices), std::end(indices), [&dimensions](SizeType i1, SizeType i2) { return dimensions[i1].w() > dimensions[i2].w(); }); // new sorted dimensions based on sorted indices std::vector<GfxDimension> sortedDimensions; std::transform(std::begin(indices), std::end(indices), std::back_insert_iterator<std::vector<GfxDimension>>(sortedDimensions), [&dimensions](SizeType i){ return GfxDimension{ dimensions[i].w(), dimensions[i].h()}; }); // generate thumbnails LOG_info << "generate for, " << path; auto images = mGfxProvider->generateImages(path, sortedDimensions); // assign back to original order for (decltype(images)::size_type i = 0; i < images.size(); ++i) { outputImages[indices[i]] = std::move(images[i]); } return GfxTaskResult(std::move(outputImages), GfxTaskProcessStatus::SUCCESS); } // // Put more probmatic format (likely crash) by freeimage here in extraFormatsByWorker // note order by length of ext. If we has this order: .tiff.tif, the match with .tif fails // see how GfxProc::isgfx is implemented. // std::string GfxProcessor::supportedformats() const { std::string extraFormatsByWorker = ".tif.exr.pic.pct.tiff.pict."; auto formats = mGfxProvider->supportedformats(); return formats ? std::string(formats) + extraFormatsByWorker : ""; } std::string GfxProcessor::supportedvideoformats() const { auto videoformats = mGfxProvider->supportedvideoformats(); return videoformats ? std::string(videoformats) : ""; } RequestProcessor::RequestProcessor(size_t threadCount, size_t maxQueueSize) : mGfxProcessor() , mThreadPool(threadCount, maxQueueSize) { } bool RequestProcessor::process(std::unique_ptr<IEndpoint> endpoint) { bool stopRunning = false; // read command ProtocolReader reader{ endpoint.get() }; std::shared_ptr<ICommand> command = reader.readCommand(READ_TIMEOUT); if (!command) { LOG_err << "command couldn't be unserialized"; return stopRunning; } stopRunning = command->type() == CommandType::SHUTDOWN; const LogLevel logLevel = command->logLevel(); // execute command LOG_level(logLevel) << "execute the command in the thread pool: " << static_cast<int>(command->type()) << "/" << command->typeStr(); // gfx processing might crash as processing bad images // we flush log for every request to avoid a large portion log lost MegaFileLogger::get().flush(); std::shared_ptr<IEndpoint> sharedEndpoint = std::move(endpoint); mThreadPool.push( [sharedEndpoint, command, this]() { switch (command->type()) { case CommandType::HELLO: { processHello(sharedEndpoint.get()); break; } case CommandType::SHUTDOWN: { processShutDown(sharedEndpoint.get()); break; } case CommandType::NEW_GFX: { processGfx(sharedEndpoint.get(), dynamic_cast<CommandNewGfx*>(command.get())); break; } case CommandType::SUPPORT_FORMATS: { processSupportFormats(sharedEndpoint.get()); break; } default: break; } }); return stopRunning; } void RequestProcessor::processHello(IEndpoint* endpoint) { CommandHelloResponse response; ProtocolWriter writer{ endpoint }; writer.writeCommand(&response, WRITE_TIMEOUT); } void RequestProcessor::processShutDown(IEndpoint* endpoint) { CommandShutDownResponse response; ProtocolWriter writer{ endpoint }; writer.writeCommand(&response, WRITE_TIMEOUT); } void RequestProcessor::processGfx(IEndpoint* endpoint, CommandNewGfx* request) { assert(endpoint); assert(request); LOG_info << "gfx processing"; auto result = mGfxProcessor.process(request->Task); CommandNewGfxResponse response; response.ErrorCode = static_cast<uint32_t>(result.ProcessStatus); response.ErrorText = result.ProcessStatus == GfxTaskProcessStatus::SUCCESS ? "OK" : "ERROR"; response.Images = std::move(result.OutputImages); LOG_info << "gfx result, " << response.ErrorText; ProtocolWriter writer{ endpoint }; writer.writeCommand(&response, WRITE_TIMEOUT); } void RequestProcessor::processSupportFormats(IEndpoint* endpoint) { assert(endpoint); CommandSupportFormatsResponse response; response.formats = mGfxProcessor.supportedformats(); response.videoformats = mGfxProcessor.supportedvideoformats(); ProtocolWriter writer{ endpoint }; writer.writeCommand(&response, WRITE_TIMEOUT); } } // namespace } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/processor.h���������������������������������������������������������0000664�0000000�0000000�00000002346�15162662266�0020611�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include "mega/gfx/worker/commands.h" #include "mega/gfx/worker/comms.h" #include "mega/gfx/worker/tasks.h" #include "megafs.h" #include "thread_pool.h" #include <chrono> #include <memory> namespace mega { namespace gfx { class GfxProcessor { public: GfxProcessor(); GfxTaskResult process(const GfxTask& task); std::string supportedformats() const; std::string supportedvideoformats() const; private: mega::FSACCESS_CLASS mFaccess; std::unique_ptr<::mega::IGfxProvider> mGfxProvider; }; class RequestProcessor { public: RequestProcessor(size_t threadCount = 6, size_t maxQueueSize = 12); // process the request. return true if processsing should // be stopped such as received a shutdown request bool process(std::unique_ptr<IEndpoint> endpoint); private: void processHello(IEndpoint* endpoint); void processShutDown(IEndpoint* endpoint); void processGfx(IEndpoint* endpoint, CommandNewGfx* request); void processSupportFormats(IEndpoint* endpoint); GfxProcessor mGfxProcessor; ThreadPool mThreadPool; static constexpr std::chrono::milliseconds READ_TIMEOUT{5000}; static constexpr std::chrono::milliseconds WRITE_TIMEOUT{5000}; }; } // namespace } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/server.h������������������������������������������������������������0000664�0000000�0000000�00000000456�15162662266�0020100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/** * Covenience, include this file instead of platform headers * * */ #pragma once #if defined(WIN32) #include "win32/server.h" #else #include "posix/server.h" #endif namespace mega { namespace gfx { #if defined(WIN32) using Server = ServerWin32; #else using Server = ServerPosix; #endif } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/thread_pool.cpp�����������������������������������������������������0000664�0000000�0000000�00000003205�15162662266�0021420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "thread_pool.h" #include "mega/logging.h" #include <algorithm> namespace mega { namespace gfx { ThreadPool::ThreadPool(size_t threadCount, size_t maxQueueSize) : mMaxQueueSize(std::max<size_t>(maxQueueSize, 1)) // minimum 1 { threadCount = std::max<size_t>(threadCount, 1); // minimum 1 for (size_t i = threadCount; i--; ) { mThreads.emplace_back([this]() mutable { asyncThreadLoop(); }); } } void ThreadPool::shutdown() { { std::lock_guard<std::mutex> g(mMutex); mDone = true; } mConditionVariable.notify_all(); for (auto& thread : mThreads) { if (thread.joinable()) { thread.join(); } } } ThreadPool::~ThreadPool() { LOG_verbose << "~ThreadPool"; shutdown(); } bool ThreadPool::push(Entry&& entry) { { std::lock_guard<std::mutex> g(mMutex); if (mDone) return false; if (mMaxQueueSize > 0 && mQueue.size() >= mMaxQueueSize) { return false; } mQueue.emplace_back(std::move(entry)); } mConditionVariable.notify_one(); return true; } void ThreadPool::asyncThreadLoop() { for(;;) { Entry entry; { std::unique_lock<std::mutex> g(mMutex); mConditionVariable.wait(g, [this]() { return !mQueue.empty() || mDone; }); // due to shutdown if (mDone) return; // due to job if (!mQueue.empty()) { entry = std::move(mQueue.front()); mQueue.pop_front(); } } if (entry) entry(); } } } // namespace }�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/thread_pool.h�������������������������������������������������������0000664�0000000�0000000�00000001455�15162662266�0021072�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <functional> #include <mutex> #include <deque> #include <condition_variable> #include <thread> namespace mega { namespace gfx { class ThreadPool { public: using Entry = std::function<void()>; ThreadPool(size_t threadCount, size_t maxQueueSize); ThreadPool(const ThreadPool&) = delete; ThreadPool& operator=(const ThreadPool&) = delete; ~ThreadPool(); bool push(Entry&& entry); private: void shutdown(); void asyncThreadLoop(); std::mutex mMutex; std::condition_variable mConditionVariable; std::vector<std::thread> mThreads; size_t mMaxQueueSize = 10; // mQueue and mDone is condition should be protected with mMutex and notify by mConditionVariable std::deque<Entry> mQueue; bool mDone = false; }; } // namespace }�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/win32/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0017356�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/src/win32/server.cpp����������������������������������������������������0000664�0000000�0000000�00000007721�15162662266�0021377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "win32/server.h" #include "processor.h" #include "mega/logging.h" #include "mega/utils.h" namespace mega { namespace gfx { const std::error_code ServerWin32::OK; ServerNamedPipe::~ServerNamedPipe() { if (isValid()) { FlushFileBuffers(mPipeHandle); DisconnectNamedPipe(mPipeHandle); } } void ServerWin32::operator()() { serverListeningLoop(); } std::error_code ServerWin32::waitForClient(HANDLE hPipe, WinOverlapped& overlapped) { assert(hPipe != INVALID_HANDLE_VALUE); assert(overlapped.isValid()); // Wait for the client to connect asynchronous; if it succeeds, // the function returns a nonzero value. // If the function returns zero, // GetLastError returns ERROR_PIPE_CONNECTED, the IO is connected // GetLastError returns ERROR_IO_PENDING, the IO is pending bool success = ConnectNamedPipe(hPipe, overlapped.data()); if (success) { LOG_verbose << "Client connected"; return OK; } if (GetLastError() == ERROR_PIPE_CONNECTED) { LOG_verbose << "Client connected"; return OK; } if (GetLastError() != ERROR_IO_PENDING) { LOG_verbose << "Client couldn't connect, error=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); return std::make_error_code(std::errc::not_connected); } // Wait if (auto [error, errorText] = overlapped.waitForCompletion(mWaitMs); error) { LOG_verbose << "Client " << errorText; return error; } // Get result DWORD numberOfBytesTransferred = 0; if (GetOverlappedResult(hPipe, overlapped.data(), &numberOfBytesTransferred, false /*bWait*/)) { LOG_verbose << "Client connected"; return OK; } LOG_verbose << "Client couldn't connect, error=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); return std::make_error_code(std::errc::not_connected); } void ServerWin32::serverListeningLoop() { WinOverlapped overlapped; if (!overlapped.isValid()) { return; } LOG_verbose << "server awaiting client connection"; const auto fullPipeName = win_utils::toFullPipeName(mPipeName); // first instance to prevent two processes create the same pipe DWORD firstInstance = FILE_FLAG_FIRST_PIPE_INSTANCE; const DWORD BUFSIZE = 512; for (;;) { auto hPipe = CreateNamedPipe( fullPipeName.c_str(), // pipe name PIPE_ACCESS_DUPLEX | // read/write access FILE_FLAG_OVERLAPPED | // overlapped firstInstance, // first instance or not PIPE_TYPE_MESSAGE | // message type pipe PIPE_READMODE_BYTE | // message-read mode PIPE_WAIT, // blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances BUFSIZE, // output buffer size BUFSIZE, // input buffer size 0, // client time-out NULL); // default security attribute if (hPipe == INVALID_HANDLE_VALUE) { LOG_err << "CreateNamedPipe failed, Error=" << GetLastError() << " " << mega::winErrorMessage(GetLastError()); break; } // not first instance for next iteration firstInstance = 0; bool stopRunning = false; auto err_code = waitForClient(hPipe, overlapped); if (err_code) { // if has timeout and expires, we'll stop running stopRunning = (mWaitMs != INFINITE && err_code == std::make_error_code(std::errc::timed_out)); CloseHandle(hPipe); } else if (mRequestProcessor) { stopRunning = mRequestProcessor->process(std::make_unique<ServerNamedPipe>(hPipe)); } if (stopRunning) { LOG_info << "Exiting listening loop"; break; } } } } } �����������������������������������������������sdk-10.11.0/tools/gfxworker/src/win32/server.h������������������������������������������������������0000664�0000000�0000000�00000003075�15162662266�0021042�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include "mega/types.h" #include "mega/win32/gfx/worker/comms.h" #include <system_error> namespace mega { namespace gfx { class RequestProcessor; class ServerNamedPipe : public NamedPipe { public: ServerNamedPipe(HANDLE h) : NamedPipe(h, "server") {} ~ServerNamedPipe(); private: Type type() const { return Type::Server; } }; class ServerWin32 { public: /** * @brief A server listening on the named pipe for alive seconds * * @param requestProcessor the request processor * @param pipeName the name of the pipe * @param keepAliveInSeconds keep alive if the sever hasn't receive any request for * the given seconds. 0 mean keeping infinitely running even * if there is no request coming. */ ServerWin32(std::unique_ptr<RequestProcessor> requestProcessor, const std::string& pipeName = "mega_gfxworker", unsigned short keepAliveInSeconds = 60) : mRequestProcessor(std::move(requestProcessor)) , mPipeName(pipeName) { // wait for client to connect timeout is set accordingly mWaitMs = keepAliveInSeconds == 0 ? INFINITE : static_cast<DWORD>(keepAliveInSeconds * 1000); } void operator()(); private: const static std::error_code OK; void serverListeningLoop(); std::error_code waitForClient(HANDLE hPipe, WinOverlapped& overlapped); std::unique_ptr<RequestProcessor> mRequestProcessor; std::string mPipeName; DWORD mWaitMs = INFINITE; }; } // namespace } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/������������������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0016767�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/������������������������������������������������������0000775�0000000�0000000�00000000000�15162662266�0021312�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/CMakeLists.txt����������������������������������������0000664�0000000�0000000�00000002135�15162662266�0024053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������add_executable(gfxworker_test_integration executable_dir.h executable_dir.cpp main.cpp server_client_test.cpp getDefaultLogName.cpp ) target_link_libraries(gfxworker_test_integration PRIVATE libgfxworker MEGA::test_tools MEGA::SDKlib ) # Look for the libraries needed for both integration and unit tests. if(VCPKG_ROOT) find_package(GTest CONFIG REQUIRED) target_link_libraries(gfxworker_test_integration PRIVATE GTest::gtest) else() pkg_check_modules(gtest REQUIRED IMPORTED_TARGET gtest) target_link_libraries(gfxworker_test_integration PRIVATE PkgConfig::gtest) endif() # Adjust compilation flags for warnings and errors for libgfxworker target_platform_compile_options( TARGET gfxworker_test_integration WINDOWS /W4 /we4800 # Implicit conversion from 'type' to bool. Possible information loss UNIX $<$<CONFIG:Debug>:-ggdb3> -Wall -Wextra -Wconversion ) if(ENABLE_SDKLIB_WERROR) target_platform_compile_options( TARGET gfxworker_test_integration WINDOWS /WX UNIX $<$<CONFIG:Debug>: -Werror> ) endif() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/executable_dir.cpp������������������������������������0000664�0000000�0000000�00000000620�15162662266�0024773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "executable_dir.h" #include <filesystem> namespace mega_test { namespace fs = std::filesystem; std::string ExecutableDir::mDir; void ExecutableDir::init(const std::string& executable) { auto executableDir = fs::absolute(fs::path(executable).parent_path()); ExecutableDir::mDir = executableDir.string(); } std::string ExecutableDir::get() { return ExecutableDir::mDir; } } ����������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/executable_dir.h��������������������������������������0000664�0000000�0000000�00000000461�15162662266�0024443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#pragma once #include <string> namespace mega_test { // // It provides setting up the executable's directory and then getting later globally // class ExecutableDir { public: static void init(const std::string& executable); static std::string get(); private: static std::string mDir; }; }���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/getDefaultLogName.cpp���������������������������������0000664�0000000�0000000�00000000217�15162662266�0025345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include <string> const std::string& getDefaultLogName() { static const std::string k = "gfxworker_test_integration.log"; return k; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/tools/gfxworker/tests/integration/main.cpp����������������������������������������������0000664�0000000�0000000�00000000775�15162662266�0022753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "executable_dir.h" // headers from gfxworker #include "logger.h" // headers from sdk #include "megaapi.h" #include <gtest/gtest.h> int main (int argc, char *argv[]) { // init ExecutableDir mega_test::ExecutableDir::init(argv[0]); // log ::mega::MegaApi::setLogLevel(mega::MegaApi::LOG_LEVEL_MAX); ::mega::gfx::MegaFileLogger::get().initialize(".", "gfxworker_test_integration.log", false); // test testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } ���sdk-10.11.0/tools/gfxworker/tests/integration/server_client_test.cpp��������������������������������0000664�0000000�0000000�00000012067�15162662266�0025727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#include "executable_dir.h" #include "mega/gfx.h" #include "mega/gfx/worker/client.h" #include "mega/logging.h" #include "mega/utils.h" #include "processor.h" #include "sdk_test_data_provider.h" #include "server.h" #include <gtest/gtest.h> #include <chrono> #include <cstdlib> #include <memory> #include <system_error> #include <thread> #include <vector> using mega::gfx::Server; using mega::gfx::RequestProcessor; using mega::gfx::GfxClient; using mega::gfx::GfxCommunicationsClient; using mega::GfxDimension; using mega::LocalPath; using mega::getCurrentPid; using mega_test::ExecutableDir; using namespace std::chrono_literals; #if !defined(WIN32) && defined(ENABLE_ISOLATED_GFX) #include "mega/posix/gfx/worker/socket_utils.h" #include <filesystem> using mega::gfx::SocketUtils; #endif class ServerClientTest: public testing::Test { protected: void SetUp() override { std::ostringstream oss; oss << "MEGA_GFXWOKER_UNIT_TEST_" << getCurrentPid(); mEndpointName = oss.str(); } void TearDown() override { #if !defined(WIN32) && defined(ENABLE_ISOLATED_GFX) // Clean up socket file on UNIX if (std::error_code errorCode = SocketUtils::removeSocketFile(mEndpointName)) { LOG_err << "Failed to remove socket path " << mEndpointName << ": " << errorCode.message(); } #endif } std::string mEndpointName; // Used as pipe name on Windows, domain socket name on Linux }; TEST_F(ServerClientTest, RunGfxTaskSuccessfully) { Server server( std::make_unique<RequestProcessor>(), mEndpointName ); std::thread serverThread(std::ref(server)); auto dimensions = std::vector<GfxDimension> { { 200, 0 }, // THUMBNAIL: square thumbnail, cropped from near center { 1000, 1000 } // PREVIEW: scaled version inside 1000x1000 bounding square }; // one png std::string testImage{"logo.png"}; fs::path testImageLocalPath = fs::path{ExecutableDir::get()} / testImage; ASSERT_TRUE(getFileFromArtifactory("test-data/" + testImage, testImageLocalPath)); std::vector<std::string> images; EXPECT_TRUE(GfxClient(std::make_unique<GfxCommunicationsClient>(mEndpointName)) .runGfxTask(testImageLocalPath.string(), dimensions, images)); EXPECT_EQ(images.size(), 2); EXPECT_GT(images[0].size(), 4500); // Use > as the size is different between MacOS and other platforms EXPECT_GT(images[1].size(), 650); // Use > as the above // shutdown EXPECT_TRUE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runShutDown() ); if (serverThread.joinable()) { serverThread.join(); } } TEST_F(ServerClientTest, RunHelloRequestResponseSuccessfully) { Server server( std::make_unique<RequestProcessor>(), mEndpointName ); std::thread serverThread(std::ref(server)); // allow server starting up as runHello doesn't have connect retry std::this_thread::sleep_for(1000ms); EXPECT_TRUE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runHello("") ); EXPECT_TRUE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runShutDown() ); if (serverThread.joinable()) { serverThread.join(); } } TEST_F(ServerClientTest, RunSupportformatsRequestResponseSuccessfully) { Server server( std::make_unique<RequestProcessor>(), mEndpointName ); std::thread serverThread(std::ref(server)); // Get from isolated process std::string formats, videoformats; EXPECT_TRUE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runSupportFormats(formats, videoformats) ); // compare with local auto provider = mega::IGfxProvider::createInternalGfxProvider(); auto internalFormats = provider->supportedformats(); if (internalFormats) { EXPECT_EQ(formats.find(internalFormats), 0); // the formats starts with internalFormats, extra part are not checked here for simplicity EXPECT_TRUE(formats.back() == '.'); // ends with '.' } else { EXPECT_EQ(formats, std::string()); } EXPECT_EQ(videoformats, provider->supportedvideoformats() ? std::string(provider->supportedvideoformats()) : std::string()); EXPECT_TRUE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runShutDown() ); if (serverThread.joinable()) { serverThread.join(); } } TEST_F(ServerClientTest, RunCommandsReturnFalseWhileServerIsNotRunning) { EXPECT_FALSE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runShutDown() ); // could be any dimensions std::vector<GfxDimension> dimensions; std::vector<std::string> images; EXPECT_FALSE( GfxClient( std::make_unique<GfxCommunicationsClient>(mEndpointName) ).runGfxTask("anyimagename.jpg", dimensions, images) ); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������sdk-10.11.0/vcpkg.json������������������������������������������������������������������������������0000664�0000000�0000000�00000004711�15162662266�0014457�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "name": "sdklib", "homepage": "https://github.com/meganz/sdk", "features": { "use-openssl": { "description": "OpenSSL library", "dependencies": [ "openssl" ] }, "use-mediainfo": { "description": "MediaInfo library", "dependencies": [ "libmediainfo" ] }, "use-freeimage": { "description": "FreeImage library", "dependencies": [ "freeimage", { "name": "jasper", "default-features": false } ] }, "use-ffmpeg": { "description": "FFMpeg library", "dependencies": [ { "name": "ffmpeg", "default-features": false, "features": [ "avcodec", "avformat", "swresample", "swscale" ] } ] }, "use-libuv": { "description": "libuv library", "dependencies": [ "libuv" ] }, "use-pdfium": { "description": "pdfium library", "dependencies": [ "pdfium" ] }, "use-readline": { "description": "Readline library", "dependencies": [ "readline" ] }, "sdk-tests": { "description": "gtests library for the integration and unit tests", "dependencies": [ "gtest" ] }, "c-ares-backend-curl": { "description": "Enable c-ares backend for curl", "dependencies": [ { "name": "curl", "features": [ "c-ares" ] } ] } }, "dependencies": [ "cryptopp", { "name": "curl", "features": [ "zstd" ] }, "icu", "libsodium", "sqlite3" ], "builtin-baseline": "ef7dbf94b9198bc58f45951adcf1f041fcbc5ea0", "overrides": [ { "name": "icu", "version": "74.2#4" } ] } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������